1 Problem Statement and Background

The aim of the following analysis is to examine the relationships between different player measurements for NBA players in the 2023-24 season. This analysis hopes to identify key factors to cluster groups of players together, hoping to ease player comparison.

This dataset comes from Kaggle, a free web-based platform that data scientists and statisticians use to share both ideas and datasets. The link to the 2023-24 NBA Player Stats dataset is below:

Loan Approval Classification Dataset

This dataset may be updated over time. I downloaded this dataset on Sunday, November 17th, 2024. Link to this version of the dataset is below:

11/17/24 Version

R/R Markdown were used in this project as it is free and open-source, allowing users to customize their experience with various libraries and features that other coding software such as SAS do not have. R provides an easy-to-use and comprehensive toolset of statistical analyses and tests. While these advanced analyses will not be used in this project, further work can be done to provide additional insights.

This analysis seeks to answer the questions:

  • Can the dataset be split into separate groups using a multitude of different player statistics?
  • Can we determine any players who are considered outliers in comparison to their group?
nba.data <- read.csv("https://github.com/EPKeep32/STA551/raw/refs/heads/main/2023-2024%20NBA%20Player%20Stats%20-%20Regular.csv", sep = ";")

2 Descrption of the Data

Here is what the author on Kaggle had to say about the 2023-24 NBA Player Stats Dataset: “This dataset contains [2023-2024] regular season NBA player stats per game. Note that there are duplicate player names resulted from team changes.”

A detailed description of the variables within the dataset is given below:

Rk: Rank, (player identification number) [integer]

Player: Player’s name [character]

Pos: Position [categorical]

Age: Player’s age [numerical]

Tm: Team [categorical]

G: Games played [numerical]

GS: Games started [numerical]

MP: Minutes played per game [numerical]

FG: Field goals per game [numerical]

FGA: Field goal attempts per game [numerical]

FG%: Field goal percentage [numerical]

3P: 3-point field goals per game [numerical]

3PA: 3-point field goal attempts per game [numerical]

3P%: 3-point field goal percentage [numerical]

2P: 2-point field goals per game [numerical]

2PA: 2-point field goal attempts per game [numerical]

2P%: 2-point field goal percentage [numerical]

eFG%: Effective field goal percentage [numerical]

FT: Free throws per game [numerical]

FTA: Free throw attempts per game [numerical]

FT%: Free throw percentage [numerical]

ORB: Offensive rebounds per game [numerical]

DRB: Defensive rebounds per game [numerical]

TRB: Total rebounds per game [numerical]

AST: Assists per game [numerical]

STL: Steals per game [numerical]

BLK: Blocks per game [numerical]

TOV: Turnovers per game [numerical]

PF: Personal fouls per game [numerical]

PTS: Points per game [numerical]

2.1 Feature Engineering

Here are some additional feature engineering that is needed for ananysis found later in the report:

2.1.1 Team Reconfiguration

In the instance where a player played for multiple teams in the 2023-24 season, that player has multiple observations in the dataset, one for each team they played on. The player also has an additional observation with the overall season stats for both teams. This in indicated when Tm equals “TOT”, which stand for total.

To avoid duplicate results, any players who played for multiple teams have their non-total observations removed. Also, their Tm value will be updated to include both team names

For Example If Player A played for PHI and CHI, their new Tm value will be PHI-CHI

nba.data.non.duplicates <- nba.data %>%
  group_by(Player) %>%
  filter(n() == 1)

nba.data.duplicates <- nba.data %>%
  group_by(Player) %>%
  filter(n() > 1)

nba.data.duplictaes.updated <- nba.data.duplicates %>%
  mutate(
    Tm = ifelse(
      Tm == "TOT",
      paste(setdiff(unique(Tm), "TOT"), collapse = "-"),
      Tm
    )
  ) %>%
  filter(str_detect(Tm, "-"))

nba.data.filtered <- bind_rows(nba.data.non.duplicates, nba.data.duplictaes.updated)

2.1.2 Variable Renaming

All percentage variables will be renamed with a -P at the end of their variable name instead of an -% for more consistent processing. Also, all numbers that begin with a number will now have an “X” in front of its name. The table below reflects this change:

nba.data.filtered <- nba.data.filtered %>%
  rename(
    FGP = FG.,
    X3PP = X3P.,
    X2PP = X2P.,
    eFGP = eFG.,
    FTP= FT.
  )

perc.vars <- data.frame(
  org.name = c("FG%", "3P%", "2P%", "eFG%", "FT%", "3P", "3PA", "2P", "2PA"),
  new.name = c("FGP", "X3PP", "X2PP", "eFGP", "FTP", "X3P", "X3PA", "X2P", "X2PA")
)

kable(perc.vars,
      col.names = c("Original Name", "New Name"),
      caption = "Renaming of Percentage Variables") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
Renaming of Percentage Variables
Original Name New Name
FG% FGP
3P% X3PP
2P% X2PP
eFG% eFGP
FT% FTP
3P X3P
3PA X3PA
2P X2P
2PA X2PA

2.2 Handling Missing Values

The next step is to determine if there are any missing variables. Missing variables can cause biased models to be generated, ultimately causing decisions that may be incorrect.

Another issue with missing values is how to replace them. There are many ways to replace missing values, including mean and median imputation, and model-based imputation.

However, I will first determine if there are any missing values within the dataset before exploring replacement techniques.

missing.values.summary <- data.frame(
  "Variable Name" = names(nba.data.filtered),
  "Missing Values" = colSums(is.na(nba.data.filtered))
)

kable(missing.values.summary,
      col.names = c("Variable Name", "Missing Values"),
      caption = "Summary of Missing Values by Variable") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
Summary of Missing Values by Variable
Variable Name Missing Values
Rk Rk 0
Player Player 0
Pos Pos 0
Age Age 0
Tm Tm 0
G G 0
GS GS 0
MP MP 0
FG FG 0
FGA FGA 0
FGP FGP 0
X3P X3P 0
X3PA X3PA 0
X3PP X3PP 0
X2P X2P 0
X2PA X2PA 0
X2PP X2PP 0
eFGP eFGP 0
FT FT 0
FTA FTA 0
FTP FTP 0
ORB ORB 0
DRB DRB 0
TRB TRB 0
AST AST 0
STL STL 0
BLK BLK 0
TOV TOV 0
PF PF 0
PTS PTS 0

The above summary table shows that 0 of the variables have missing values. Therefore, no imputations are necessary for the dataset.

2.3 Final Dataset

The final dataset has 735 observations each with 30 variables. All 30 variables will be held within the dataset for any further research, regardless of their use in this research.

Below is a quick summary of all numeric variables in the final dataset:

num.data <- nba.data.filtered[, sapply(nba.data.filtered, is.numeric)]

num.sum <- data.frame(
  Variable = names(num.data),
  Min = sapply(num.data, function(x) min(x, na.rm = TRUE)),
  Mean = sapply(num.data, function(x) mean(x, na.rm = TRUE)),
  Median = sapply(num.data, function(x) median(x, na.rm = TRUE)),
  Max = sapply(num.data, function(x) max(x, na.rm = TRUE))
)

kable(num.sum,
      col.names = c("Variable", "Min", "Mean", "Median", "Max"),
      caption = "Summary of Numeric Variables") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
Summary of Numeric Variables
Variable Min Mean Median Max
Rk Rk 1.0 286.5000000 286.5000 572.000
Age Age 19.0 25.7430070 25.0000 39.000
G G 1.0 46.1538462 51.0000 84.000
GS GS 0.0 21.5034965 7.0000 82.000
MP MP 0.5 18.6506993 17.3500 37.800
FG FG 0.0 3.1202797 2.4000 11.500
FGA FGA 0.0 6.6833916 5.1000 23.600
FGP FGP 0.0 0.4495839 0.4520 0.747
X3P X3P 0.0 0.9409091 0.7000 4.800
X3PA X3PA 0.0 2.6463287 2.1000 11.800
X3PP X3PP 0.0 0.2994808 0.3390 1.000
X2P X2P 0.0 2.1774476 1.6000 11.000
X2PA X2PA 0.0 4.0363636 3.0000 18.300
X2PP X2PP 0.0 0.5196748 0.5340 1.000
eFGP eFGP 0.0 0.5164983 0.5330 0.917
FT FT 0.0 1.2442308 0.8000 10.200
FTA FTA 0.0 1.6013986 1.0000 11.600
FTP FTP 0.0 0.7013951 0.7605 1.000
ORB ORB 0.0 0.8576923 0.7000 4.600
DRB DRB 0.0 2.5229021 2.2000 10.100
TRB TRB 0.0 3.3730769 3.0000 13.700
AST AST 0.0 2.0013986 1.3000 10.900
STL STL 0.0 0.5912587 0.5500 2.100
BLK BLK 0.0 0.4027972 0.3000 3.600
TOV TOV 0.0 0.9846154 0.7000 4.400
PF PF 0.0 1.4909091 1.5000 3.600
PTS PTS 0.0 8.4232517 6.4000 34.700

Below is a quick summary of all categorical variables in the final dataset:

cat.data <- nba.data.filtered[, sapply(nba.data.filtered, is.character)]

cat.sum <- lapply(names(cat.data), function(var) {
  counts <- table(cat.data[[var]], useNA = "no")
  sorted.counts <- sort(counts, decreasing = TRUE)
  top.value <- if (length(sorted.counts) > 0) names(sorted.counts)[1] else NA
  top.count <- if (length(sorted.counts) > 0) as.numeric(sorted.counts[1]) else 0
  
  data.frame(
    Variable = var,
    `Top Value` = top.value,
    `Top Count` = top.count,
    `Unique Values` = length(counts)
  )
}) %>%
  bind_rows()

kable(cat.sum,
      col.names = c("Variable", "Top Value", "Top Count", "Unique Values"),
      caption = "Summary of Categorical Variables with Top Value") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
Summary of Categorical Variables with Top Value
Variable Top Value Top Count Unique Values
Player A.J. Green 1 572
Pos SG 131 12
Tm MEM 22 92

Note A.J. Green is listed as the top value for Player. This is because A.J. Green is the first player alphabetically within the dataset, and each player only appears once.

Note MEM is listed as the top value for Tm. This only includes players who played for MEM ONLY. Players who were traded within the season were not added to this count. A list of what each abbreviation stand for can be found within the next section.

3 Exploratory Data Analysis

Next I will perform some Exploratpory Data Analysis, or EDA. EDA is an important step in the overarching data analysis process, as it is important to familiarize yourself with the dataset.

3.1 Distribution of Position

Below is a pie chart of Pos:

nba.data.position.pie.chart <- nba.data.filtered %>%
  separate_rows(Pos, sep = "-")

position <- nba.data.position.pie.chart %>%
  group_by(Pos) %>%
  summarise(count = n()) %>%
  ungroup() %>%
  mutate(perc = count / sum(count)) %>%
  arrange(perc) %>%
  mutate(labels = scales::percent(perc))

ggplot(position, aes(x = "", y = perc, fill = Pos)) +
  geom_col() +
  geom_text(aes(label = paste(count, "\n(", labels, ")", sep = "")),
            position = position_stack(vjust = (0.5))) +
  coord_polar(theta = "y") +
  labs(title = "Pie Chart of Position", y = "", x = "") +
  guides(fill = guide_legend(title = "Position"))

As shown in the pie chart above, the most common position in the dataset is Shooting Guard (23.4%) and the least common position is Center (16.8%).

Note Any player who is classified as having multiple positions counts as both positions for this visual representation only.

3.2 Distribution of Age

Below is a histogram of Age:

hist(nba.data.filtered$Age,
     breaks = seq(15, 40, 1),
     main = "Distribution of Age",
     xlim = c(15, 40),
     xaxt = "n",
     xlab = "Age (years)",
     ylim = c(0, 100),
     ylab = "Number of Players",
     col = "red",
     labels = TRUE)
abline(h = seq(10, 100, 10), col = "gray", lty = "dotted")
axis(1, at = seq(15, 50, 5))

The histogram above shows that Age has a right-skewed distribution. The youngest players in the dataset are 18 years old, and the oldest player is 39 years old.

3.3 Distribution of Team

Below is a table of each team abbreviation un-abbreviated:

nba.teams <- data.frame(
  abbreviation = c("ATL", "BOS", "BKN", "CHA", "CHI", "CLE", "DAL", "DEN", "DET", "GSW", "HOU", "IND", "LAC", "LAL", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "OKC", "ORL", "PHI", "PHX", "POR", "SAC", "SAS", "TOR", "UTA", "WAS"),
  
  full.name = c("Atlanta Hawks", "Boston Celtics", "Brooklyn Nets", "Charlotte Hornets", "Chicago Bulls", "Cleveland Cavaliers", "Dallas Mavericks", "Denver Nuggets", "Detroit Pistons", "Golden State Warriors", "Houston Rockets", "Indiana Pacers", "Los Angeles Clippers", "Los Angeles Lakers", "Memphis Grizzlies", "Miami Heat", "Milwaukee Bucks", "Minnesota Timberwolves", "New Orleans Pelicans", "New York Knicks", "Oklahoma City Thunder", "Orlando Magic", "Philadelphia 76ers", "Phoenix Suns", "Portland Trail Blazers", "Sacramento Kings", "San Antonio Spurs", "Toronto Raptors", "Utah Jazz", "Washington Wizards")
)

kable(nba.teams,
      col.names = c("Team Abbreviation", "Full Name"),
      caption = "List of NBA Teams and Their Abbreviations") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
List of NBA Teams and Their Abbreviations
Team Abbreviation Full Name
ATL Atlanta Hawks
BOS Boston Celtics
BKN Brooklyn Nets
CHA Charlotte Hornets
CHI Chicago Bulls
CLE Cleveland Cavaliers
DAL Dallas Mavericks
DEN Denver Nuggets
DET Detroit Pistons
GSW Golden State Warriors
HOU Houston Rockets
IND Indiana Pacers
LAC Los Angeles Clippers
LAL Los Angeles Lakers
MEM Memphis Grizzlies
MIA Miami Heat
MIL Milwaukee Bucks
MIN Minnesota Timberwolves
NOP New Orleans Pelicans
NYK New York Knicks
OKC Oklahoma City Thunder
ORL Orlando Magic
PHI Philadelphia 76ers
PHX Phoenix Suns
POR Portland Trail Blazers
SAC Sacramento Kings
SAS San Antonio Spurs
TOR Toronto Raptors
UTA Utah Jazz
WAS Washington Wizards

Below is a table of Tm:

player.count <- nba.data.filtered %>%
  separate_rows(Tm, sep = "-") %>%
  group_by(Tm) %>%
  summarise(count = n()) %>%
  arrange(desc(count))

player.count %>%
  kable(
    col.names = c("Team", "Number of Players"),
    align = "lc",
    caption = "Players by Team"
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
Players by Team
Team Number of Players
MEM 33
DET 31
TOR 30
PHI 28
CHO 26
NYK 26
WAS 24
DAL 22
IND 22
OKC 22
PHO 22
POR 22
BRK 21
LAC 21
LAL 21
MIA 21
MIL 21
UTA 21
NOP 20
SAC 20
SAS 20
ATL 19
BOS 19
MIN 19
CHI 18
CLE 18
GSW 18
ORL 18
DEN 17
HOU 17

As shown in the table above, the Memphis Grizzlies had the most amount of players in the dataset.

Note Any player who is played for multiple teams counts for both teams in the above table.

3.4 Distribution of Games Played

Below is a histogram of G:

hist(nba.data.filtered$G,
     breaks = seq(0, 90, 5),
     main = "Distribution of Games Played",
     xlim = c(0, 90),
     xaxt = "n",
     xlab = "Games Played",
     ylim = c(0, 60),
     ylab = "Number of Players",
     col = "red",
     labels = TRUE)
abline(h = seq(5, 60, 5), col = "gray", lty = "dotted")
axis(1, at = seq(0, 90, 10))

The histogram above shows that the distribution of G has two peaks around the 70’s and 5-10. It is important to note that there are 82 games in a NBA regular season.

3.5 Distribution of Games Started

Below is a histogram of GS:

hist(nba.data.filtered$GS,
     breaks = seq(0, 90, 5),
     main = "Distribution of Games Started",
     xlim = c(0, 90),
     xaxt = "n",
     xlab = "Games Started",
     ylim = c(0, 300),
     ylab = "Number of Players",
     col = "red",
     labels = TRUE)
abline(h = seq(25, 300, 25), col = "gray", lty = "dotted")
axis(1, at = seq(0, 90, 10))

The histogram above shows that for GS, almost half of the players in the dataset started less than 5 games. Only 5 players can start a game per team per game, meaning that only ~ 1/3rd to 1/6th of an NBA team’s roster will not start on any given game, given their roster size.

3.6 Distribution of Minutes Played per Game

Below is a histogram of MP:

hist(nba.data.filtered$MP,
     breaks = seq(0, 48, 4),
     main = "Distribution of Minutes Played per Game",
     xlim = c(0, 48),
     xaxt = "n",
     xlab = "Minutes Played per Game",
     ylim = c(0, 100),
     ylab = "Number of Players",
     col = "red",
     labels = TRUE)
abline(h = seq(10, 100, 10), col = "gray", lty = "dotted")
axis(1, at = seq(0, 48, 8))

The histogram above shows that GS has a much more evenly-distributed distribution, This means that even though many players did not start a game all season, almost all players played at least 4 minutes per game on average.

3.7 Distribution of Field Goals

Below is a scatterplot of FGA vs FG, with a color scaling of FGP:

ggplot(nba.data.filtered, aes(x = FGA, y = FG)) +
  geom_point(aes(color = FGP)) +
  geom_abline(slope = 1, intercept = 0, color = "black", linetype = "dashed", size = 1) +
  scale_color_gradient(low = "red", high = "green") +
  labs(
    title = "Field Goal Attempts vs Field Goals Scored",
    x = "Field Goal Attempts per Game",
    y = "Field Goal Scored per Game",
    color = "Field Goal Percentage"
  ) +
  coord_equal() +
  scale_x_continuous(limits = c(0, 24)) +
  scale_y_continuous(limits = c(0, 24))

As shown in the scatterplot above, players seem to clump together for field goal data, following a straight-line. The black dashed line represents if a player has a 100% field goal percentage.

3.8 Distribution of 2-Point Field Goals

Below is a scatterplot of X2PA vs X2P, with a color scaling of X2PP:

ggplot(nba.data.filtered, aes(x = X2PA, y = X2P)) +
  geom_point(aes(color = X2PP)) +
  geom_abline(slope = 1, intercept = 0, color = "black", linetype = "dashed", size = 1) +
  scale_color_gradient(low = "red", high = "green") +
  labs(
    title = "2-Point Attempts vs 2-Point Scored",
    x = "2-Point Attempts per Game",
    y = "2-Point Scored per Game",
    color = "2-Point Percentage"
  ) +
  coord_equal() +
  scale_x_continuous(limits = c(0, 20)) +
  scale_y_continuous(limits = c(0, 20))

As shown above, there appears to be six outliers past the 8 2-point scored per game range. Those players are below:

two.pt.outliers <- nba.data.filtered %>%
  filter(X2P > 8) %>%
  dplyr::select(Player, X2PA, X2P, X2PP)

kable(two.pt.outliers,
      col.names = c("Player", "2-Point Attempts per Game", "2-Point Goals per Game", "2 Point Percentage"),
      caption = "Players with more than 8 2-Point Field Goals Scored") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
Players with more than 8 2-Point Field Goals Scored
Player 2-Point Attempts per Game 2-Point Goals per Game 2 Point Percentage
Giannis Antetokounmpo 17.1 11.0 0.645
Anthony Davis 15.5 9.0 0.582
Joel Embiid 18.3 10.2 0.556
Shai Gilgeous-Alexander 16.2 9.3 0.576
Nikola Jokic 14.9 9.4 0.626
Zion Williamson 15.4 8.8 0.574

Players Giannis Antetokounmpo, Anthony Davis, Joel Embiid, Shai Gilgeous-Alexander, Nikola Jokic, and Zion Williamson are the “unusual” players for 2-point field goals.

3.9 Distribution of 3-Point Field Goals

Below is a scatterplot of X3PA vs X3P, with a color scaling of X3PP:

ggplot(nba.data.filtered, aes(x = X3PA, y = X3P)) +
  geom_point(aes(color = X3PP)) +
  geom_abline(slope = 1, intercept = 0, color = "black", linetype = "dashed", size = 1) +
  scale_color_gradient(low = "red", high = "green") +
  labs(
    title = "3-Point Attempts vs 3-Point Scored",
    x = "3-Point Attempts per Game",
    y = "3-Point Scored per Game",
    color = "3-Point Percentage"
  ) +
  coord_equal() +
  scale_x_continuous(limits = c(0, 12)) +
  scale_y_continuous(limits = c(0, 12))

As shown above, there appears to be two outliers past the 10 3-point attempts per game range. Those players are below:

three.pt.outliers <- nba.data.filtered %>%
  filter(X3PA > 10) %>%
  dplyr::select(Player, X3PA, X3P, X3PP)

kable(three.pt.outliers,
      col.names = c("Player", "3-Point Attempts per Game", "3-Point Goals per Game", "3 Point Percentage"),
      caption = "Players with more than 10 3-Point Attemps") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
Players with more than 10 3-Point Attemps
Player 3-Point Attempts per Game 3-Point Goals per Game 3 Point Percentage
Stephen Curry 11.8 4.8 0.408
Luka Doncic 10.6 4.1 0.382

Players Stephen Curry and Luka Doncic are the standout players for 3-point field goals.

3.10 Distribution of Effective Field Goal Percentage

Effective Field Goal Percentage (eFGP) is an adjusted field goal percentage that accounts for 2-point field goals being worth 2 points, and 3-point field goals being worth 3 points.

Below is a scatterplot of X2PP vs X3PP, with a color scaling of eFGP:

ggplot(nba.data.filtered, aes(x = X2PP, y = X3PP)) +
  geom_point(aes(color = eFGP)) +
  scale_color_gradient(low = "red", high = "green") +
  labs(
    title = "3-Point Attempts vs 3-Point Scored",
    x = "2-Point Field Goal Percentage",
    y = "3-Point Field Goal Percentage",
    color = "Effective Field Goal Percentage"
  ) +
  coord_equal() +
  scale_x_continuous(limits = c(0, 1)) +
  scale_y_continuous(limits = c(0, 1))

As shown in the scatterplot above, there appear to be some players who have a 0 for X2PP and/or X3PP. Apart from these observations, most players are in a clump together.

3.11 Distribution of Free Throws

Below is a scatterplot of FTA vs FT, with a color scaling of FTP:

ggplot(nba.data.filtered, aes(x = FTA, y = FT)) +
  geom_point(aes(color = FTP)) +
  geom_abline(slope = 1, intercept = 0, color = "black", linetype = "dashed", size = 1) +
  scale_color_gradient(low = "red", high = "green") +
  labs(
    title = "Free Throw Attempts vs Free Throws Scored",
    x = "Free Throw Attempts per Game",
    y = "Free Throws Scored per Game",
    color = "Free Throw Percentage"
  ) +
  coord_equal() +
  scale_x_continuous(limits = c(0, 12)) +
  scale_y_continuous(limits = c(0, 12))

As shown above, there appears to be four outliers past the 8 free throw attempts per game range. Those players are below:

free.throw.outliers <- nba.data.filtered %>%
  filter(FTA > 8) %>%
  dplyr::select(Player, FTA, FT, FTP)

kable(free.throw.outliers,
      col.names = c("Player", "Free Throw Attempts per Game", "Free Throw per Game", "Free Throw Percentage"),
      caption = "Players with more than 10 Free Throw Attempts") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
Players with more than 10 Free Throw Attempts
Player Free Throw Attempts per Game Free Throw per Game Free Throw Percentage
Giannis Antetokounmpo 10.7 7.0 0.657
Luka Doncic 8.7 6.8 0.786
Joel Embiid 11.6 10.2 0.883
Shai Gilgeous-Alexander 8.7 7.6 0.874

Players Giannis Antetokounmpo, Luka Doncic, Joel Embiid, and Shai Gilgeous-Alexander are the “unusual” players for free throws.

3.12 Distribution of Rebounds

Below is a scatterplot of ORB vs DRB, with a color scaling of TRB:

ggplot(nba.data.filtered, aes(x = ORB, y = DRB)) +
  geom_point(aes(color = TRB)) +
  scale_color_gradient(low = "red", high = "green") +
  labs(
    title = "Offensive Rebounds vs Defensive Rebounds",
    x = "Offensive Rebounds per Game",
    y = "Defensive Rebounds per Game",
    color = "Total Rebounds"
  )

As shown above, the relationship between ORB and DRB have a slight fanning shape as both variables increase in value.

3.13 Distribution of Assists

Below is a histogram of AST:

hist(nba.data.filtered$AST,
     breaks = seq(0, 12, 1),
     main = "Distribution of Assists per Game",
     xlim = c(0, 12),
     xaxt = "n",
     xlab = "Assists per Game",
     ylim = c(0, 250),
     ylab = "Number of Players",
     col = "red",
     labels = TRUE)
abline(h = seq(25, 250, 25), col = "gray", lty = "dotted")
axis(1, at = seq(0, 12, 2))

The histogram above shows that AST has a right skewed-distribution.

3.14 Distribution of Steals

Below is a histogram of STL:

hist(nba.data.filtered$STL,
     breaks = seq(0, 2.5, .25),
     main = "Distribution of Steals per Game",
     xlim = c(0, 2.5),
     xaxt = "n",
     xlab = "Steals per Game",
     ylim = c(0, 200),
     ylab = "Number of Players",
     col = "red",
     labels = TRUE)
abline(h = seq(25, 200, 25), col = "gray", lty = "dotted")
axis(1, at = seq(0, 2.5, .5))

The histogram above shows that STL has right-skewed distribution, but not as aggressive as assists.

3.15 Distribution of Blocks

Below is a histogram of BLK:

hist(nba.data.filtered$BLK,
     breaks = seq(0, 4, .4),
     main = "Distribution of Blocks per Game",
     xlim = c(0, 4),
     xaxt = "n",
     xlab = "Blocks per Game",
     ylim = c(0, 400),
     ylab = "Number of Players",
     col = "red",
     labels = TRUE)
abline(h = seq(50, 400, 50), col = "gray", lty = "dotted")
axis(1, at = seq(0, 4, .8))

The histogram above shows that BLK has an aggressively right-skewed distribution. There appears to be one outlier who is above the 3 blocks per game. That player is below:

blocks.outliers <- nba.data.filtered %>%
  filter(BLK > 3) %>%
  dplyr::select(Player, G, GS, BLK)

kable(blocks.outliers,
      col.names = c("Player", "Games Played", "Games Started", "Blocks"),
      caption = "Players with more than 3 Blocks per Game") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
Players with more than 3 Blocks per Game
Player Games Played Games Started Blocks
Victor Wembanyama 71 71 3.6

Victor Wembanyama was the league leader in blocks per game.

3.16 Distribution of Turnovers

Below is a histogram of TOV:

hist(nba.data.filtered$TOV,
     breaks = seq(0, 5, .25),
     main = "Distribution of Turnovers per Game",
     xlim = c(0, 5),
     xaxt = "n",
     xlab = "Turnovers per Game",
     ylim = c(0, 150),
     ylab = "Number of Players",
     col = "red",
     labels = TRUE)
abline(h = seq(25, 150, 25), col = "gray", lty = "dotted")
axis(1, at = seq(0, 5, .5))

The histogram above shows that BLK has a right-skewed distribution. There appears to be one outlier who is above the 4 turnovers per game. That player is below:

turnovers.outliers <- nba.data.filtered %>%
  filter(TOV > 4) %>%
  dplyr::select(Player, G, GS, TOV)

kable(turnovers.outliers,
      col.names = c("Player", "Games Played", "Games Started", "Turnovers"),
      caption = "Players with more than 4 TUrnovers per Game") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
Players with more than 4 TUrnovers per Game
Player Games Played Games Started Turnovers
Trae Young 54 54 4.4

Trae Young was the league leader in blocks per game.

3.17 Distribution of Personal Fouls

Below is a histogram of PF:

hist(nba.data.filtered$PF,
     breaks = seq(0, 4, .4),
     main = "Distribution of Personal Fouls per Game",
     xlim = c(0, 4),
     xaxt = "n",
     xlab = "Personal Fouls per Game",
     ylim = c(0, 120),
     ylab = "Number of Players",
     col = "red",
     labels = TRUE)
abline(h = seq(10, 120, 10), col = "gray", lty = "dotted")
axis(1, at = seq(0, 4, .8))

The histogram above shows that BLK has a right-skewed distribution.

3.18 Distribution of Points Scored

Below is a histogram of PTS:

hist(nba.data.filtered$PTS,
     breaks = seq(0, 36, 4),
     main = "Distribution of Points Scored per Game",
     xlim = c(0, 36),
     xaxt = "n",
     xlab = "Points Scored per Game",
     ylim = c(0, 200),
     ylab = "Number of Players",
     col = "red",
     labels = TRUE)
abline(h = seq(25, 200, 25), col = "gray", lty = "dotted")
axis(1, at = seq(0, 36, 4))

4 K-Means Clustering Analysis

K-Means clustering is a machine learning algorithm that is used to partition a dataset into distinct groups or clusters based on their similarity. It is often used to identify natural groupings within data and simplify data interpretation. For this project, I will perform K-Means Clustering to group players together.

4.1 Determining the “K”

I will begin this process by determining the amount of clusters needed for the analysis. Below is an elbow plot to visualize the number of clusters needed. The number along the x-axis where a “kink” appears in the graph is an indicator of where the cluster cutoff should be

kmeans.data <- subset(num.data, select = -Rk)
kmeans.scaled <- as.data.frame(scale(kmeans.data))

wss = NULL
k = 10
for(i in 1:k){
  wss[i] = kmeans(kmeans.scaled, i, 1)$tot.withinss
}

plot(1:k, wss, type = "b",
     col = "red",
     xlab = "Number of Clusters",
     ylab = "Within Sums of Squares",
     main = "Elbow Plot for Selecting Optimal Number of Clusters")

As shown above, the elbow appears to be at k = 2. To confirm or deny this, below is a silhouette plot

fviz_nbclust(kmeans.scaled, FUN = hcut, method = "silhouette")

The silhouette plot confirms that the ideal number of clusters for the analysis is k = 2

4.2 Clustering

Next, I will split the dataset into two distinct clusters. The scatterplot below shows the relationship between PTS and FGP, with a the clusters shown by the color of each point.

k2 <- kmeans(x = kmeans.scaled,
             centers = 2,
             iter.max = 10,
             nstart = 25,
             algorithm = "Lloyd",
             trace = FALSE)

kmeans.scaled$group = as.factor(k2$cluster)

plot(kmeans.scaled$PTS, kmeans.scaled$FGP,
     pch = 19,
     col = factor(kmeans.scaled$group),
     xlab = "Points Scored (scaled)",
     ylab = "Field Goal Percentage (scaled)",
     main = "Clustering Performance Visual Check")

legend("bottomright",
       legend = levels(factor(kmeans.scaled$group)),
       pch = 19,
       col = 1:length(levels(kmeans.scaled$group)))

The scatterplot above shows how the dataset is split into two distinct groups.

5 Principal Component Analysis (PCA)

Principle Component Analysis (PCA) is a tool to reduce the dimensionality of the dataset while retaining as much variability as possible. The subsetted numerical dataset has 27 variables. Therefore, we want to reduce this dimension to a smaller number, while not losing any valuable information.

Below is the factor loading for the fitted PCA.

log.num.data = log(kmeans.data + 1)

log.pca <- prcomp(log.num.data, center = TRUE, scale = TRUE)

kable(round(log.pca$rotation, 2),
      caption = "PCA Loadings") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
PCA Loadings
PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8 PC9 PC10 PC11 PC12 PC13 PC14 PC15 PC16 PC17 PC18 PC19 PC20 PC21 PC22 PC23 PC24 PC25 PC26
Age -0.04 -0.05 0.06 -0.82 0.49 -0.14 0.16 -0.05 0.10 -0.01 -0.01 -0.05 0.04 0.03 0.07 -0.04 -0.03 0.02 0.01 0.02 0.00 -0.01 0.00 0.00 0.00 0.00
G -0.19 0.01 0.23 -0.16 -0.23 -0.22 -0.16 -0.22 -0.60 0.18 0.14 -0.05 0.31 -0.42 -0.11 -0.10 -0.03 -0.04 -0.01 0.00 0.01 0.02 -0.01 0.00 0.00 0.00
GS -0.22 -0.05 -0.05 -0.10 -0.10 0.04 -0.17 -0.24 -0.28 0.27 0.31 -0.08 -0.47 0.56 0.15 0.11 -0.07 0.00 0.05 0.10 -0.03 -0.02 -0.02 0.00 0.00 0.00
MP -0.24 -0.06 0.01 -0.11 -0.12 0.08 -0.16 0.01 0.02 -0.15 -0.05 0.07 0.06 0.06 -0.02 0.16 0.35 0.73 -0.27 -0.24 0.14 0.01 0.04 -0.01 -0.02 -0.01
FG -0.25 -0.04 -0.01 0.08 0.11 0.08 0.06 -0.08 0.00 -0.07 0.08 0.03 0.18 0.12 -0.03 0.00 -0.02 -0.16 0.24 -0.39 0.16 -0.02 0.08 0.21 0.21 -0.69
FGA -0.24 -0.13 -0.03 0.07 0.05 0.09 0.02 -0.09 0.04 -0.15 0.08 0.08 0.25 0.08 0.00 -0.03 0.05 0.14 0.39 0.26 -0.34 0.18 0.25 0.17 0.44 0.33
FGP -0.13 0.39 0.32 0.09 0.20 0.06 0.03 0.09 -0.05 0.01 -0.04 -0.04 -0.01 0.14 -0.29 0.05 -0.04 0.00 -0.30 0.22 0.07 -0.03 -0.19 0.61 0.07 0.07
X3P -0.17 -0.35 0.19 0.00 -0.05 0.18 0.02 -0.27 0.21 -0.03 0.01 -0.09 -0.12 -0.03 -0.21 -0.15 -0.07 -0.28 -0.47 -0.35 -0.28 0.00 0.15 0.00 0.06 0.18
X3PA -0.16 -0.38 0.16 0.03 -0.07 0.17 -0.05 -0.29 0.26 -0.11 0.01 0.00 -0.01 -0.13 0.05 -0.16 0.03 0.00 0.03 0.55 0.43 -0.11 -0.14 0.02 -0.13 -0.15
X3PP -0.09 -0.24 0.38 -0.05 -0.24 0.20 0.55 0.44 -0.21 0.06 -0.07 -0.02 -0.03 0.03 0.36 0.03 -0.02 0.01 -0.01 -0.02 -0.01 -0.01 -0.02 0.05 0.02 0.00
X2P -0.24 0.08 -0.13 0.14 0.20 0.01 0.08 0.01 -0.08 -0.03 0.09 0.05 0.28 0.15 0.16 0.07 -0.06 -0.22 -0.15 -0.14 0.55 -0.18 0.03 -0.24 0.16 0.43
X2PA -0.24 0.02 -0.17 0.13 0.17 0.01 0.09 0.06 -0.11 -0.09 0.08 0.08 0.36 0.18 0.09 0.06 -0.05 -0.05 -0.38 0.33 -0.37 0.09 0.08 -0.22 -0.34 -0.28
X2PP -0.10 0.34 0.37 0.16 0.22 0.05 -0.24 -0.15 0.20 0.05 0.10 0.02 -0.09 -0.25 0.65 -0.03 0.09 0.00 0.00 -0.10 -0.14 0.03 0.03 -0.03 -0.01 -0.01
eFGP -0.13 0.27 0.46 0.04 0.09 0.14 0.06 0.05 0.01 -0.01 -0.07 -0.07 -0.12 0.10 -0.43 -0.01 -0.03 0.07 0.23 0.06 0.05 0.03 0.22 -0.57 -0.04 -0.06
FT -0.22 -0.06 -0.14 0.19 0.15 -0.28 0.26 -0.04 0.04 0.16 -0.05 -0.31 -0.20 -0.16 -0.02 0.05 0.13 0.02 -0.05 0.00 0.18 0.69 -0.04 -0.01 0.00 0.00
FTA -0.22 -0.02 -0.16 0.18 0.14 -0.25 0.25 -0.04 0.01 0.14 -0.07 -0.33 -0.17 -0.21 -0.04 0.07 0.16 0.13 0.03 0.08 -0.17 -0.66 0.12 0.01 0.07 -0.04
FTP -0.13 -0.05 0.27 0.03 -0.26 -0.77 -0.03 0.11 0.30 -0.08 0.03 0.25 0.03 0.24 0.01 -0.02 -0.08 -0.05 -0.02 0.01 0.00 -0.04 0.04 0.00 0.02 -0.01
ORB -0.15 0.35 -0.21 -0.13 -0.24 0.02 0.13 0.09 -0.01 -0.34 0.19 -0.13 -0.14 0.03 0.05 -0.66 0.15 -0.05 -0.02 -0.01 0.07 0.01 0.18 0.08 -0.12 0.03
DRB -0.23 0.12 -0.11 -0.12 -0.13 0.06 0.09 -0.08 0.01 -0.22 -0.04 0.26 -0.22 -0.27 -0.02 0.48 -0.22 -0.13 0.11 -0.01 0.08 0.02 0.39 0.21 -0.34 0.09
TRB -0.22 0.19 -0.13 -0.13 -0.17 0.04 0.09 -0.02 0.02 -0.29 0.03 0.15 -0.21 -0.21 -0.02 0.16 -0.10 -0.07 -0.11 0.07 -0.14 0.01 -0.53 -0.27 0.44 -0.11
AST -0.21 -0.17 -0.05 -0.01 0.23 0.02 -0.24 0.35 -0.15 0.15 -0.11 0.47 -0.25 -0.11 -0.11 -0.14 0.48 -0.27 0.01 0.06 -0.01 -0.01 0.02 0.00 0.01 0.00
STL -0.20 -0.09 -0.05 -0.11 -0.06 0.09 -0.37 0.56 0.26 0.12 0.40 -0.37 0.07 -0.17 -0.08 0.13 -0.20 -0.03 0.03 0.02 0.01 0.00 0.01 0.01 0.00 0.00
BLK -0.15 0.26 -0.16 -0.15 -0.28 0.17 0.21 -0.10 0.36 0.68 -0.04 0.23 0.20 -0.01 -0.05 -0.06 0.06 0.01 0.00 0.02 -0.01 0.00 -0.01 0.00 0.00 0.00
TOV -0.23 -0.07 -0.12 0.10 0.16 -0.01 -0.11 0.10 -0.08 0.12 -0.32 0.18 -0.13 -0.07 0.05 -0.38 -0.65 0.32 0.02 -0.07 0.01 -0.01 -0.03 0.01 -0.01 0.01
PF -0.21 0.08 -0.04 -0.15 -0.22 0.02 -0.30 0.01 -0.01 -0.05 -0.71 -0.36 0.11 0.18 0.15 0.08 0.09 -0.25 0.05 0.05 -0.03 0.02 0.00 0.00 0.01 0.00
PTS -0.25 -0.06 0.04 0.08 0.06 0.03 0.07 -0.06 0.05 -0.09 0.06 -0.02 0.12 0.07 -0.09 -0.03 0.09 -0.01 0.38 -0.27 -0.17 -0.05 -0.55 0.04 -0.50 0.24

5.1 Determining the Amount of kept PCA’s

The object of PCA is to reduce the dimension without losing a significant amount of information. The following table gives the importance of the principal components

kable(summary(log.pca)$importance,
      caption = "Principle Component Importance") %>%
   kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
Principle Component Importance
PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8 PC9 PC10 PC11 PC12 PC13 PC14 PC15 PC16 PC17 PC18 PC19 PC20 PC21 PC22 PC23 PC24 PC25 PC26
Standard deviation 3.87184 1.732241 1.465887 1.026494 0.9386824 0.860554 0.7337873 0.6692782 0.6520896 0.5912356 0.5099273 0.4953068 0.4591667 0.4362574 0.3952644 0.3486441 0.3190559 0.2536455 0.1489889 0.147023 0.1055586 0.0901972 0.0653077 0.0486137 0.0447252 0.0264417
Proportion of Variance 0.57658 0.115410 0.082650 0.040530 0.0338900 0.028480 0.0207100 0.0172300 0.0163500 0.0134400 0.0100000 0.0094400 0.0081100 0.0073200 0.0060100 0.0046800 0.0039200 0.0024700 0.0008500 0.000830 0.0004300 0.0003100 0.0001600 0.0000900 0.0000800 0.0000300
Cumulative Proportion 0.57658 0.691990 0.774640 0.815170 0.8490600 0.877540 0.8982500 0.9154800 0.9318300 0.9452800 0.9552800 0.9647100 0.9728200 0.9801400 0.9861500 0.9908200 0.9947400 0.9972100 0.9980700 0.998900 0.9993300 0.9996400 0.9998100 0.9999000 0.9999700 1.0000000

TO visualize these PCA’s, below is a scree plot

var.explained <- log.pca$sdev^2 / sum(log.pca$sdev^2)

cum.var <- cumsum(var.explained)

plot(cum.var, 
     type = "b", 
     xlab = "Number of Principal Components",
     ylab = "Cumultive Variance Explained",
     main = "Scree Plot")
abline(h = .9, col = "red", lty = 2)

A common cutoff for PCA’s is 90% of cumulative variance explained. Therefore, we want to keep the first 8 PCA’s.

Below is the kept PCA scores:

kable(log.pca$x[1:10, 1:8],
      caption = "The first 8 PC scored transformed from the original variables") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = FALSE)
The first 8 PC scored transformed from the original variables
PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8
-5.9487309 1.6992602 -2.3070883 0.0507443 0.2395406 -0.5643309 0.9836520 1.0526817
-2.6714012 -0.1604082 -0.2615839 -0.0657525 -1.2372829 1.2102384 0.0787998 -0.6761835
-0.9340540 -1.1820280 1.2759344 -0.3404894 -0.7216541 0.4023261 -0.8165681 -0.0338455
-3.5556771 -1.1393787 1.4702335 -0.6947592 -0.3435695 0.3934371 -0.1131567 -0.3603651
-4.9375529 4.1384527 -2.4161628 0.1973171 0.5154219 -0.8481484 -0.0698728 -0.4158386
2.3798041 -0.1721751 -3.1649530 -0.5032157 -0.5854574 0.1727145 -2.1909562 0.5656501
-0.0069216 -1.2956092 0.8079002 -0.2161380 -0.4477417 0.5167683 -0.5796518 0.8300786
-1.2417220 0.6855849 -1.0356735 -1.1719566 0.6161056 -0.7643245 -0.5898543 1.0237781
-8.1703599 2.0229229 -2.2971960 0.1608672 1.6029111 -0.3808793 0.9772271 0.6130231
5.9158579 1.8010402 0.0861415 -0.9106740 2.4665981 1.0792647 -1.1276388 -0.6335666

6 Local Outlier Factor (LOF)

The LOF is the most well-known local anomaly detection algorithm whose idea is carried out in many nearest-neighbor-based algorithms. When a point is considered as an outlier bases on its local neighborhood, it is a local outlier. LOF will identify an outlier considering the density of the neighborhood. LOF performs well when the density of the data is not the same throughout the dataset.

6.1 Outlier Definition

For the sake of this analysis, I will define an outlier as any player who has a combined z-score for the following variables higher than 3:

  • MP
  • PTS
  • FGP
  • X3PP
  • TRB
  • AST
  • STL
  • BLK
  • TOV

A new variable Outlier will be created for each observation. If their combined z-score is higher than 3, their Outlier variable will be 1, otherwise it will be 0.

6.2 LOF Distribution

The distribution of LOF scores is usually very skewed. To create a better visual representation, I truncate the extremely large LOF scores. Next, I extracted the LOF scores of each player in the dataset and summarize the distribution.

Below is the summary:

lof.data <- nba.data.filtered[, c("MP", "PTS", "FGP", "X3PP", "TRB", "AST", "STL", "BLK", "TOV")]

lof.data <- scale(lof.data)

lof.scores.50 <- lof(lof.data, minPts = 50)
summary(lof.scores.50)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.9566  1.0154  1.0765  1.1638  1.2304  3.5534 
hist(lof.scores.50[lof.scores.50 < 5],
     breaks = seq(0, 4, .1),
     main = "Distribution of LOF",
     xlim = c(0, 4),
     xaxt = "n",
     xlab = "LOF < 5",
     ylim = c(0, 250),
     ylab = "Number of Players",
     col = "gray",
     labels = TRUE)
abline(h = seq(25, 250, 25), col = "gray", lty = "dotted")
axis(1, at = seq(0, 4, .4))

6.3 Determining LOF cutoff

In order for outliers to be identified, there must be a consistent cutoff point. To determine the cutoff point, I will graph the suggested LOF cutoffs against the successful LOF catching rate.

lof.cut = seq(1, 4, length = 20)

lof.catch.rate = numeric(length(lof.cut))

vars <- c("MP", "PTS", "FGP", "X3PP", "TRB", "AST", "STL", "BLK", "TOV")
z.scores <- scale(nba.data.filtered[, vars])
nba.data.filtered$combined.z <- apply(z.scores, 1, function(row) sqrt(sum(row^2)))
nba.data.filtered$Outlier <- nba.data.filtered$combined.z > 3

for (i in 1:length(lof.cut)){
  ID = lof.scores.50 > lof.cut[i]
  Outlier <- nba.data.filtered$Outlier[ID]
  lof.catch.rate[i] <- sum(Outlier) / sum(ID)
}

default.rate <- sum(nba.data.filtered$Outlier) / nrow(nba.data.filtered)
rel.detect.rate <- (lof.catch.rate - default.rate) / default.rate

par(mfrow = c(1, 2))

plot(lof.cut, 100*lof.catch.rate, type = "l", col = "red", lwd = 2,
     xlab = "LOF Cutoff",
     ylab = "LOF Catching Rate (%)",
     main = "LOF Catching Rate of Outliers")

plot(lof.cut, rel.detect.rate, type = "l", col = "red", lwd = 2,
     xlab = "LOF Cutoff",
     ylab = "LOF and random catching rate ratio",
     main = "LOF catching rate improvement")

The above figures show that, with k = 100, the LOF-based outlier-catching rate is much higher than the random guess. I will next sketch an ROC to assess the global performance in terms of catching rate.

category = as.character(nba.data.filtered$Outlier)
category <- ifelse(category == TRUE, "1", "0")

roc.obj.lof.50 <- roc(category, lof.scores.50, levels = c("1", "0"), direction = ">")

sen.lof.50 = roc.obj.lof.50$sensitivities
fnr.lof.50 = 1 - roc.obj.lof.50$specificities

par(type = "s")
plot(fnr.lof.50, sen.lof.50, type = "l", lwd = 2, col = "red",
     xlim = c(0, 1),
     ylim = c(0, 1),
     xlab = "1 - Specificity",
     ylab = "Sensitivity",
     main = "ROC Curves of LOF Detection")

segments(0, 0, 1, 1, lwd = 1, col = "black", lty = 2)
auc.50 = roc.obj.lof.50$auc
text(0.87, 0.20, paste("AUC (50) = ", round(auc.50, 4)), col = "red", cex = 0.7, adj = 1)

6.4 Tuning Hyperparameter k

The LOF defines local outliers. The LOF scores will be depending on the value of \(k\) (the number of nearest players of every player). For illustration purposes, I will use 3 alternate values of k in an attempt to improve the AUC value.

lof.scores.100 <- lof(lof.data, minPts = 100)

roc.obj.lof.100 <- roc(category, lof.scores.100, levels = c("1", "0"), direction = ">")

sen.lof.100 = roc.obj.lof.100$sensitivities
fnr.lof.100 = 1 - roc.obj.lof.100$specificities
lof.scores.200 <- lof(lof.data, minPts = 200)

roc.obj.lof.200 <- roc(category, lof.scores.200, levels = c("1", "0"), direction = ">")

sen.lof.200 = roc.obj.lof.200$sensitivities
fnr.lof.200 = 1 - roc.obj.lof.200$specificities
lof.scores.400 <- lof(lof.data, minPts = 400)

roc.obj.lof.400 <- roc(category, lof.scores.400, levels = c("1", "0"), direction = ">")

sen.lof.400 = roc.obj.lof.400$sensitivities
fnr.lof.400 = 1 - roc.obj.lof.400$specificities
par(type = "s")
colors = c("red", "blue", "orange", "darkgreen")

plot(fnr.lof.50, sen.lof.50, type = "l", lwd = 2, col = colors[1],
     xlim = c(0, 1),
     ylim = c(0, 1),
     xlab = "1 - Specificity",
     ylab = "Sensitivity",
     main = "ROC Curves of LOF Detection Comparison")

lines(fnr.lof.100, sen.lof.100, lwd = 2, lty = 2, col = colors[2])
lines(fnr.lof.200, sen.lof.200, lwd = 1, col = colors[3])
lines(fnr.lof.400, sen.lof.400, lwd = 1, col = colors[4])

segments(0, 0, 1, 1, lwd = 1, col = "black", lty = 2)
legend("bottomright", c("LOF.50", "LOF.100", "LOF.200", "LOF.400"),
       col = colors, lwd = c(2, 2, 1, 1, 1),
       lty = c(1, 2, 1, 1, 2), bty = "n", cex = 0.7)

AUC.50 = roc.obj.lof.50$auc
AUC.100 = roc.obj.lof.100$auc
AUC.200 = roc.obj.lof.200$auc
AUC.400 = roc.obj.lof.400$auc

text(0.87, 0.17, paste("AUC (50) = ", round(AUC.50, 4)), col = colors[1], cex = 0.7, adj = 1)
text(0.87, 0.12, paste("AUC (100) = ", round(AUC.100, 4)), col = colors[2], cex = 0.7, adj = 1)
text(0.87, 0.07, paste("AUC (200) = ", round(AUC.200, 4)), col = colors[3], cex = 0.7, adj = 1)
text(0.87, 0.02, paste("AUC (400) = ", round(AUC.400, 4)), col = colors[4], cex = 0.7, adj = 1)

As shown above, the best \(k\) for the given data from the selection is 400, as it has the highest AUC value. However, it is advised to be careful choosing a \(k\) value that is deemed to be “too close” to the number of observations within the dataset.

LS0tDQp0aXRsZTogIkdyb3VwaW5nIE5CQSBQbGF5ZXJzIFV0aWxpemluZyBVbnN1cGVydmlzZWQgTGVhcm5pbmcgQWxncm9pdGhtcyINCmF1dGhvcjogIkV2YW4gUGFya2VyIg0KZGF0ZTogIjIwMjQtMTEtMTciDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfd2lkdGg6IDYNCiAgICBmaWdfaGVpZ2h0OiA0DQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jX2NvbGxhcHNlZDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogeWVzDQogICAgdGhlbWU6IGx1bWVuDQotLS0NCg0KYGBgez1odG1sfQ0KDQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KDQovKiBDYXNjYWRpbmcgU3R5bGUgU2hlZXRzIChDU1MpIGlzIGEgc3R5bGVzaGVldCBsYW5ndWFnZSB1c2VkIHRvIGRlc2NyaWJlIHRoZSBwcmVzZW50YXRpb24gb2YgYSBkb2N1bWVudCB3cml0dGVuIGluIEhUTUwgb3IgWE1MLiBpdCBpcyBhIHNpbXBsZSBtZWNoYW5pc20gZm9yIGFkZGluZyBzdHlsZSAoZS5nLiwgZm9udHMsIGNvbG9ycywgc3BhY2luZykgdG8gV2ViIGRvY3VtZW50cy4gKi8NCg0KaDEudGl0bGUgeyAgLyogVGl0bGUgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIHRoZSByZXBvcnQgdGl0bGUgKi8NCiAgZm9udC1zaXplOiAyNHB4Ow0KICBmb250LXdlaWdodDpib2xkOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KICBmb250LWZhbWlseTogIkdpbGwgU2FucyIsIHNhbnMtc2VyaWY7DQp9DQpoNC5hdXRob3IgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGF1dGhvcnMgICovDQogIGZvbnQtc2l6ZTogMjBweDsNCiAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgY29sb3I6IERhcmtSZWQ7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCmg0LmRhdGUgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIHRoZSBkYXRlICAqLw0KICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDEgeyAvKiBIZWFkZXIgMSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGxldmVsIDEgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBmb250LXNpemU6IDIycHg7DQogICAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgICBmb250LXdlaWdodDpib2xkOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQpoMiB7IC8qIEhlYWRlciAyIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMiBzZWN0aW9uIHRpdGxlICovDQogICAgZm9udC1zaXplOiAyMHB4Ow0KICAgIGZvbnQtd2VpZ2h0OmJvbGQ7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDMgeyAvKiBIZWFkZXIgMyAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgMyBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LXdlaWdodDpib2xkOw0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmJvZHkgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCi5oaWdobGlnaHRtZSB7IGJhY2tncm91bmQtY29sb3I6eWVsbG93OyB9DQoNCnAgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCjwvc3R5bGU+DQpgYGANCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQpvcHRpb25zKHJlcG9zID0gbGlzdChDUkFOPSJodHRwOi8vY3Jhbi5yc3R1ZGlvLmNvbS8iKSkNCg0KdXBkYXRlLnBhY2thZ2VzKCkNCg0KaWYgKCFyZXF1aXJlKCJjYXIiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJjYXIiKQ0KICBsaWJyYXJ5KGNhcikNCn0NCg0KaWYgKCFyZXF1aXJlKCJjYXJldCIpKSB7DQogIGluc3RhbGwucGFja2FnZXMoImNhcmV0IikNCiAgbGlicmFyeShjYXJldCkNCn0NCg0KaWYgKCFyZXF1aXJlKCJjYVRvb2xzIikpIHsNCiAgaW5zdGFsbC5wYWNrYWdlcygiY2FUb29scyIpDQogIGxpYnJhcnkoQ2FUb29scykNCn0NCg0KaWYgKCFyZXF1aXJlKCJjbHVzdGVyIikpIHsNCiAgaW5zdGFsbC5wYWNrYWdlcygiY2x1c3RlciIpDQogIGxpYnJhcnkoY2x1c3RlcikNCn0NCg0KaWYgKCFyZXF1aXJlKCJkYnNjYW4iKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZGJzY2FuIikNCiAgIGxpYnJhcnkoZGJzY2FuKQ0KfQ0KDQppZiAoIXJlcXVpcmUoImRwbHlyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikNCiAgIGxpYnJhcnkoZHBseXIpDQp9DQoNCmlmICghcmVxdWlyZSgiZTEwNzEiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJlMTA3MSIpDQogIGxpYnJhcnkoZTEwNzEpDQp9DQoNCmlmICghcmVxdWlyZSgiZmFjdG9leHRyYSIpKSB7DQogIGluc3RhbGwucGFja2FnZXMoImZhY3RvZXh0cmEiKQ0KICBsaWJyYXJ5KGZhY3RvZXh0cmEpDQp9DQoNCmlmICghcmVxdWlyZSgiZ2dmb3J0aWZ5IikpIHsNCiAgaW5zdGFsbC5wYWNrYWdlcygiZ2dmb3J0aWZ5IikNCiAgbGlicmFyeShnZ2ZvcnRpZnkpDQp9DQoNCmlmICghcmVxdWlyZSgiZ2dwbG90MiIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCiAgIGxpYnJhcnkoZ2dwbG90MikNCn0NCg0KaWYgKCFyZXF1aXJlKCJnZ3B1YnIiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3B1YnIiKQ0KICBsaWJyYXJ5KGdncHVicikNCn0NCg0KaWYgKCFyZXF1aXJlKCJpcHJlZCIpKSB7DQogIGluc3RhbGwucGFja2FnZXMoImlwcmVkIikNCiAgbGlicmFyeShpcHJlZCkNCn0NCg0KaWYgKCFyZXF1aXJlKCJrYWJsZUV4dHJhIikpIHsNCiAgaW5zdGFsbC5wYWNrYWdlcygia2FibGVFeHRyYSIpDQogIGxpYnJhcnkoa2FibGVFeHRyYSkNCn0NCg0KaWYgKCFyZXF1aXJlKCJrZXJhcyIpKSB7DQogIGluc3RhbGwucGFja2FnZXMoImtlcmFzIikNCiAgbGlicmFyeShrZXJhcykNCn0NCg0KaWYgKCFyZXF1aXJlKCJrbml0ciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJrbml0ciIpDQogICBsaWJyYXJ5KGtuaXRyKQ0KfQ0KDQppZiAoIXJlcXVpcmUoIk1BU1MiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJNQVNTIikNCiAgbGlicmFyeShNQVNTKQ0KfQ0KDQppZiAoIXJlcXVpcmUoIm5ldXJhbG5ldCIpKSB7DQogIGluc3RhbGwucGFja2FnZXMoIm5ldXJhbG5ldCIpDQogIGxpYnJhcnkobmV1cmFsbmV0KQ0KfQ0KDQppZiAoIXJlcXVpcmUoIm5uZXQiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJubmV0IikNCiAgbGlicmFyeShubmV0KQ0KfQ0KDQppZiAoIXJlcXVpcmUoInBST0MiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJwUk9DIikNCiAgbGlicmFyeShwUk9DKQ0KfQ0KDQppZiAoIXJlcXVpcmUoInBzeWNoIikpIHsNCiAgaW5zdGFsbC5wYWNrYWdlcygicHN5Y2giKQ0KICBsaWJyYXJ5KHBzeWNoKQ0KfQ0KDQppZiAoIXJlcXVpcmUoInJwYXJ0IikpIHsNCiAgaW5zdGFsbC5wYWNrYWdlcygicnBhcnQiKQ0KICBsaWJyYXJ5KHJwYXJ0KQ0KfQ0KDQppZiAoIXJlcXVpcmUoInJwYXJ0LnBsb3QiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJycGFydC5wbG90IikNCiAgbGlicmFyeShycGFydC5wbG90KQ0KfQ0KDQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KICAgbGlicmFyeSh0aWR5dmVyc2UpDQp9DQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCAgIA0KICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdCA9IFRSVUUsICAgDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSBOQSkNCg0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQpgYGANCg0KIyBQcm9ibGVtIFN0YXRlbWVudCBhbmQgQmFja2dyb3VuZA0KDQpUaGUgYWltIG9mIHRoZSBmb2xsb3dpbmcgYW5hbHlzaXMgaXMgdG8gZXhhbWluZSB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGRpZmZlcmVudCBwbGF5ZXIgbWVhc3VyZW1lbnRzIGZvciBOQkEgcGxheWVycyBpbiB0aGUgMjAyMy0yNCBzZWFzb24uIFRoaXMgYW5hbHlzaXMgaG9wZXMgdG8gaWRlbnRpZnkga2V5IGZhY3RvcnMgdG8gY2x1c3RlciBncm91cHMgb2YgcGxheWVycyB0b2dldGhlciwgaG9waW5nIHRvIGVhc2UgcGxheWVyIGNvbXBhcmlzb24uDQoNClRoaXMgZGF0YXNldCBjb21lcyBmcm9tIEthZ2dsZSwgYSBmcmVlIHdlYi1iYXNlZCBwbGF0Zm9ybSB0aGF0IGRhdGEgc2NpZW50aXN0cyBhbmQgc3RhdGlzdGljaWFucyB1c2UgdG8gc2hhcmUgYm90aCBpZGVhcyBhbmQgZGF0YXNldHMuIFRoZSBsaW5rIHRvIHRoZSAqKjIwMjMtMjQgTkJBIFBsYXllciBTdGF0cyoqIGRhdGFzZXQgaXMgYmVsb3c6DQoNCltMb2FuIEFwcHJvdmFsIENsYXNzaWZpY2F0aW9uIERhdGFzZXRdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMvdml2b3ZpbmNvLzIwMjMtMjAyNC1uYmEtcGxheWVyLXN0YXRzL2RhdGEpDQoNClRoaXMgZGF0YXNldCBtYXkgYmUgdXBkYXRlZCBvdmVyIHRpbWUuIEkgZG93bmxvYWRlZCB0aGlzIGRhdGFzZXQgb24gU3VuZGF5LCBOb3ZlbWJlciAxN3RoLCAyMDI0LiBMaW5rIHRvIHRoaXMgdmVyc2lvbiBvZiB0aGUgZGF0YXNldCBpcyBiZWxvdzoNCg0KWzExLzE3LzI0IFZlcnNpb25dKGh0dHBzOi8vZ2l0aHViLmNvbS9FUEtlZXAzMi9TVEE1NTEvYmxvYi9tYWluLzIwMjMtMjAyNCUyME5CQSUyMFBsYXllciUyMFN0YXRzJTIwLSUyMFJlZ3VsYXIuY3N2KQ0KDQpSL1IgTWFya2Rvd24gd2VyZSB1c2VkIGluIHRoaXMgcHJvamVjdCBhcyBpdCBpcyBmcmVlIGFuZCBvcGVuLXNvdXJjZSwgYWxsb3dpbmcgdXNlcnMgdG8gY3VzdG9taXplIHRoZWlyIGV4cGVyaWVuY2Ugd2l0aCB2YXJpb3VzIGxpYnJhcmllcyBhbmQgZmVhdHVyZXMgdGhhdCBvdGhlciBjb2Rpbmcgc29mdHdhcmUgc3VjaCBhcyBTQVMgZG8gbm90IGhhdmUuIFIgcHJvdmlkZXMgYW4gZWFzeS10by11c2UgYW5kIGNvbXByZWhlbnNpdmUgdG9vbHNldCBvZiBzdGF0aXN0aWNhbCBhbmFseXNlcyBhbmQgdGVzdHMuIFdoaWxlIHRoZXNlIGFkdmFuY2VkIGFuYWx5c2VzIHdpbGwgbm90IGJlIHVzZWQgaW4gdGhpcyBwcm9qZWN0LCBmdXJ0aGVyIHdvcmsgY2FuIGJlIGRvbmUgdG8gcHJvdmlkZSBhZGRpdGlvbmFsIGluc2lnaHRzLg0KDQpUaGlzIGFuYWx5c2lzIHNlZWtzIHRvIGFuc3dlciB0aGUgcXVlc3Rpb25zOg0KDQotICAgKkNhbiB0aGUgZGF0YXNldCBiZSBzcGxpdCBpbnRvIHNlcGFyYXRlIGdyb3VwcyB1c2luZyBhIG11bHRpdHVkZSBvZiBkaWZmZXJlbnQgcGxheWVyIHN0YXRpc3RpY3M/Kg0KLSAgICpDYW4gd2UgZGV0ZXJtaW5lIGFueSBwbGF5ZXJzIHdobyBhcmUgY29uc2lkZXJlZCBvdXRsaWVycyBpbiBjb21wYXJpc29uIHRvIHRoZWlyIGdyb3VwPyoNCg0KYGBge3IgZGF0YSBpbnRlZ3JhdGlvbiwgaW5jbHVkZSA9IFRSVUV9DQpuYmEuZGF0YSA8LSByZWFkLmNzdigiaHR0cHM6Ly9naXRodWIuY29tL0VQS2VlcDMyL1NUQTU1MS9yYXcvcmVmcy9oZWFkcy9tYWluLzIwMjMtMjAyNCUyME5CQSUyMFBsYXllciUyMFN0YXRzJTIwLSUyMFJlZ3VsYXIuY3N2Iiwgc2VwID0gIjsiKQ0KYGBgDQoNCiMgRGVzY3JwdGlvbiBvZiB0aGUgRGF0YQ0KDQpIZXJlIGlzIHdoYXQgdGhlIGF1dGhvciBvbiBLYWdnbGUgaGFkIHRvIHNheSBhYm91dCB0aGUgKioyMDIzLTI0IE5CQSBQbGF5ZXIgU3RhdHMqKiBEYXRhc2V0OiAiVGhpcyBkYXRhc2V0IGNvbnRhaW5zIFsyMDIzLTIwMjRdIHJlZ3VsYXIgc2Vhc29uIE5CQSBwbGF5ZXIgc3RhdHMgcGVyIGdhbWUuIE5vdGUgdGhhdCB0aGVyZSBhcmUgZHVwbGljYXRlIHBsYXllciBuYW1lcyByZXN1bHRlZCBmcm9tIHRlYW0gY2hhbmdlcy4iDQoNCkEgZGV0YWlsZWQgZGVzY3JpcHRpb24gb2YgdGhlIHZhcmlhYmxlcyB3aXRoaW4gdGhlIGRhdGFzZXQgaXMgZ2l2ZW4gYmVsb3c6DQoNCmBSa2A6IFJhbmssIChwbGF5ZXIgaWRlbnRpZmljYXRpb24gbnVtYmVyKSBbKmludGVnZXIqXQ0KDQpgUGxheWVyYDogUGxheWVyJ3MgbmFtZSBbKmNoYXJhY3RlcipdDQoNCmBQb3NgOiBQb3NpdGlvbiBbKmNhdGVnb3JpY2FsKl0NCg0KYEFnZWA6IFBsYXllcidzIGFnZSBbKm51bWVyaWNhbCpdDQoNCmBUbWA6IFRlYW0gWypjYXRlZ29yaWNhbCpdDQoNCmBHYDogR2FtZXMgcGxheWVkIFsqbnVtZXJpY2FsKl0NCg0KYEdTYDogR2FtZXMgc3RhcnRlZCBbKm51bWVyaWNhbCpdDQoNCmBNUGA6IE1pbnV0ZXMgcGxheWVkIHBlciBnYW1lIFsqbnVtZXJpY2FsKl0NCg0KYEZHYDogRmllbGQgZ29hbHMgcGVyIGdhbWUgWypudW1lcmljYWwqXQ0KDQpgRkdBYDogRmllbGQgZ29hbCBhdHRlbXB0cyBwZXIgZ2FtZSBbKm51bWVyaWNhbCpdDQoNCmBGRyVgOiBGaWVsZCBnb2FsIHBlcmNlbnRhZ2UgWypudW1lcmljYWwqXQ0KDQpgM1BgOiAzLXBvaW50IGZpZWxkIGdvYWxzIHBlciBnYW1lIFsqbnVtZXJpY2FsKl0NCg0KYDNQQWA6IDMtcG9pbnQgZmllbGQgZ29hbCBhdHRlbXB0cyBwZXIgZ2FtZSBbKm51bWVyaWNhbCpdDQoNCmAzUCVgOiAzLXBvaW50IGZpZWxkIGdvYWwgcGVyY2VudGFnZSBbKm51bWVyaWNhbCpdDQoNCmAyUGA6IDItcG9pbnQgZmllbGQgZ29hbHMgcGVyIGdhbWUgWypudW1lcmljYWwqXQ0KDQpgMlBBYDogMi1wb2ludCBmaWVsZCBnb2FsIGF0dGVtcHRzIHBlciBnYW1lIFsqbnVtZXJpY2FsKl0NCg0KYDJQJWA6IDItcG9pbnQgZmllbGQgZ29hbCBwZXJjZW50YWdlIFsqbnVtZXJpY2FsKl0NCg0KYGVGRyVgOiBFZmZlY3RpdmUgZmllbGQgZ29hbCBwZXJjZW50YWdlIFsqbnVtZXJpY2FsKl0NCg0KYEZUYDogRnJlZSB0aHJvd3MgcGVyIGdhbWUgWypudW1lcmljYWwqXQ0KDQpgRlRBYDogRnJlZSB0aHJvdyBhdHRlbXB0cyBwZXIgZ2FtZSBbKm51bWVyaWNhbCpdDQoNCmBGVCVgOiBGcmVlIHRocm93IHBlcmNlbnRhZ2UgWypudW1lcmljYWwqXQ0KDQpgT1JCYDogT2ZmZW5zaXZlIHJlYm91bmRzIHBlciBnYW1lIFsqbnVtZXJpY2FsKl0NCg0KYERSQmA6IERlZmVuc2l2ZSByZWJvdW5kcyBwZXIgZ2FtZSBbKm51bWVyaWNhbCpdDQoNCmBUUkJgOiBUb3RhbCByZWJvdW5kcyBwZXIgZ2FtZSBbKm51bWVyaWNhbCpdDQoNCmBBU1RgOiBBc3Npc3RzIHBlciBnYW1lIFsqbnVtZXJpY2FsKl0NCg0KYFNUTGA6IFN0ZWFscyBwZXIgZ2FtZSBbKm51bWVyaWNhbCpdDQoNCmBCTEtgOiBCbG9ja3MgcGVyIGdhbWUgWypudW1lcmljYWwqXQ0KDQpgVE9WYDogVHVybm92ZXJzIHBlciBnYW1lIFsqbnVtZXJpY2FsKl0NCg0KYFBGYDogUGVyc29uYWwgZm91bHMgcGVyIGdhbWUgWypudW1lcmljYWwqXQ0KDQpgUFRTYDogUG9pbnRzIHBlciBnYW1lIFsqbnVtZXJpY2FsKl0NCg0KIyMgRmVhdHVyZSBFbmdpbmVlcmluZw0KDQpIZXJlIGFyZSBzb21lIGFkZGl0aW9uYWwgZmVhdHVyZSBlbmdpbmVlcmluZyB0aGF0IGlzIG5lZWRlZCBmb3IgYW5hbnlzaXMgZm91bmQgbGF0ZXIgaW4gdGhlIHJlcG9ydDoNCg0KIyMjIFRlYW0gUmVjb25maWd1cmF0aW9uDQoNCkluIHRoZSBpbnN0YW5jZSB3aGVyZSBhIHBsYXllciBwbGF5ZWQgZm9yIG11bHRpcGxlIHRlYW1zIGluIHRoZSAyMDIzLTI0IHNlYXNvbiwgdGhhdCBwbGF5ZXIgaGFzIG11bHRpcGxlIG9ic2VydmF0aW9ucyBpbiB0aGUgZGF0YXNldCwgb25lIGZvciBlYWNoIHRlYW0gdGhleSBwbGF5ZWQgb24uIFRoZSBwbGF5ZXIgYWxzbyBoYXMgYW4gYWRkaXRpb25hbCBvYnNlcnZhdGlvbiB3aXRoIHRoZSBvdmVyYWxsIHNlYXNvbiBzdGF0cyBmb3IgYm90aCB0ZWFtcy4gVGhpcyBpbiBpbmRpY2F0ZWQgd2hlbiBgVG1gIGVxdWFscyAiVE9UIiwgd2hpY2ggc3RhbmQgZm9yIHRvdGFsLg0KDQpUbyBhdm9pZCBkdXBsaWNhdGUgcmVzdWx0cywgYW55IHBsYXllcnMgd2hvIHBsYXllZCBmb3IgbXVsdGlwbGUgdGVhbXMgaGF2ZSB0aGVpciBub24tdG90YWwgb2JzZXJ2YXRpb25zIHJlbW92ZWQuIEFsc28sIHRoZWlyIGBUbWAgdmFsdWUgd2lsbCBiZSB1cGRhdGVkIHRvIGluY2x1ZGUgYm90aCB0ZWFtIG5hbWVzDQoNCioqRm9yIEV4YW1wbGUqKiBJZiBQbGF5ZXIgQSBwbGF5ZWQgZm9yIFBISSBhbmQgQ0hJLCB0aGVpciBuZXcgYFRtYCB2YWx1ZSB3aWxsIGJlIFBISS1DSEkNCg0KYGBge3IgdGVhbSByZWNvbmZpZ3VyYXRpb24sIGluY2x1ZGUgPSBUUlVFfQ0KbmJhLmRhdGEubm9uLmR1cGxpY2F0ZXMgPC0gbmJhLmRhdGEgJT4lDQogIGdyb3VwX2J5KFBsYXllcikgJT4lDQogIGZpbHRlcihuKCkgPT0gMSkNCg0KbmJhLmRhdGEuZHVwbGljYXRlcyA8LSBuYmEuZGF0YSAlPiUNCiAgZ3JvdXBfYnkoUGxheWVyKSAlPiUNCiAgZmlsdGVyKG4oKSA+IDEpDQoNCm5iYS5kYXRhLmR1cGxpY3RhZXMudXBkYXRlZCA8LSBuYmEuZGF0YS5kdXBsaWNhdGVzICU+JQ0KICBtdXRhdGUoDQogICAgVG0gPSBpZmVsc2UoDQogICAgICBUbSA9PSAiVE9UIiwNCiAgICAgIHBhc3RlKHNldGRpZmYodW5pcXVlKFRtKSwgIlRPVCIpLCBjb2xsYXBzZSA9ICItIiksDQogICAgICBUbQ0KICAgICkNCiAgKSAlPiUNCiAgZmlsdGVyKHN0cl9kZXRlY3QoVG0sICItIikpDQoNCm5iYS5kYXRhLmZpbHRlcmVkIDwtIGJpbmRfcm93cyhuYmEuZGF0YS5ub24uZHVwbGljYXRlcywgbmJhLmRhdGEuZHVwbGljdGFlcy51cGRhdGVkKQ0KYGBgDQoNCiMjIyBWYXJpYWJsZSBSZW5hbWluZw0KDQpBbGwgcGVyY2VudGFnZSB2YXJpYWJsZXMgd2lsbCBiZSByZW5hbWVkIHdpdGggYSAtUCBhdCB0aGUgZW5kIG9mIHRoZWlyIHZhcmlhYmxlIG5hbWUgaW5zdGVhZCBvZiBhbiAtJSBmb3IgbW9yZSBjb25zaXN0ZW50IHByb2Nlc3NpbmcuIEFsc28sIGFsbCBudW1iZXJzIHRoYXQgYmVnaW4gd2l0aCBhIG51bWJlciB3aWxsIG5vdyBoYXZlIGFuICJYIiBpbiBmcm9udCBvZiBpdHMgbmFtZS4gVGhlIHRhYmxlIGJlbG93IHJlZmxlY3RzIHRoaXMgY2hhbmdlOg0KDQpgYGB7ciBwZXJjZW50YWdlIHZhcmlhYmxlcywgaW5jbHVkZSA9IFRSVUV9DQpuYmEuZGF0YS5maWx0ZXJlZCA8LSBuYmEuZGF0YS5maWx0ZXJlZCAlPiUNCiAgcmVuYW1lKA0KICAgIEZHUCA9IEZHLiwNCiAgICBYM1BQID0gWDNQLiwNCiAgICBYMlBQID0gWDJQLiwNCiAgICBlRkdQID0gZUZHLiwNCiAgICBGVFA9IEZULg0KICApDQoNCnBlcmMudmFycyA8LSBkYXRhLmZyYW1lKA0KICBvcmcubmFtZSA9IGMoIkZHJSIsICIzUCUiLCAiMlAlIiwgImVGRyUiLCAiRlQlIiwgIjNQIiwgIjNQQSIsICIyUCIsICIyUEEiKSwNCiAgbmV3Lm5hbWUgPSBjKCJGR1AiLCAiWDNQUCIsICJYMlBQIiwgImVGR1AiLCAiRlRQIiwgIlgzUCIsICJYM1BBIiwgIlgyUCIsICJYMlBBIikNCikNCg0Ka2FibGUocGVyYy52YXJzLA0KICAgICAgY29sLm5hbWVzID0gYygiT3JpZ2luYWwgTmFtZSIsICJOZXcgTmFtZSIpLA0KICAgICAgY2FwdGlvbiA9ICJSZW5hbWluZyBvZiBQZXJjZW50YWdlIFZhcmlhYmxlcyIpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiLCAicmVzcG9uc2l2ZSIpLCBmdWxsX3dpZHRoID0gRkFMU0UpDQpgYGANCg0KIyMgSGFuZGxpbmcgTWlzc2luZyBWYWx1ZXMNCg0KVGhlIG5leHQgc3RlcCBpcyB0byBkZXRlcm1pbmUgaWYgdGhlcmUgYXJlIGFueSBtaXNzaW5nIHZhcmlhYmxlcy4gTWlzc2luZyB2YXJpYWJsZXMgY2FuIGNhdXNlIGJpYXNlZCBtb2RlbHMgdG8gYmUgZ2VuZXJhdGVkLCB1bHRpbWF0ZWx5IGNhdXNpbmcgZGVjaXNpb25zIHRoYXQgbWF5IGJlIGluY29ycmVjdC4NCg0KQW5vdGhlciBpc3N1ZSB3aXRoIG1pc3NpbmcgdmFsdWVzIGlzIGhvdyB0byByZXBsYWNlIHRoZW0uIFRoZXJlIGFyZSBtYW55IHdheXMgdG8gcmVwbGFjZSBtaXNzaW5nIHZhbHVlcywgaW5jbHVkaW5nIG1lYW4gYW5kIG1lZGlhbiBpbXB1dGF0aW9uLCBhbmQgbW9kZWwtYmFzZWQgaW1wdXRhdGlvbi4NCg0KSG93ZXZlciwgSSB3aWxsIGZpcnN0IGRldGVybWluZSBpZiB0aGVyZSBhcmUgYW55IG1pc3NpbmcgdmFsdWVzIHdpdGhpbiB0aGUgZGF0YXNldCBiZWZvcmUgZXhwbG9yaW5nIHJlcGxhY2VtZW50IHRlY2huaXF1ZXMuDQoNCmBgYHtyIG1pc3NpbmcgdmFsdWVzLCBpbmNsdWRlID0gVFJVRX0NCm1pc3NpbmcudmFsdWVzLnN1bW1hcnkgPC0gZGF0YS5mcmFtZSgNCiAgIlZhcmlhYmxlIE5hbWUiID0gbmFtZXMobmJhLmRhdGEuZmlsdGVyZWQpLA0KICAiTWlzc2luZyBWYWx1ZXMiID0gY29sU3Vtcyhpcy5uYShuYmEuZGF0YS5maWx0ZXJlZCkpDQopDQoNCmthYmxlKG1pc3NpbmcudmFsdWVzLnN1bW1hcnksDQogICAgICBjb2wubmFtZXMgPSBjKCJWYXJpYWJsZSBOYW1lIiwgIk1pc3NpbmcgVmFsdWVzIiksDQogICAgICBjYXB0aW9uID0gIlN1bW1hcnkgb2YgTWlzc2luZyBWYWx1ZXMgYnkgVmFyaWFibGUiKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiwgInJlc3BvbnNpdmUiKSwgZnVsbF93aWR0aCA9IEZBTFNFKQ0KYGBgDQoNClRoZSBhYm92ZSBzdW1tYXJ5IHRhYmxlIHNob3dzIHRoYXQgKiowKiogb2YgdGhlIHZhcmlhYmxlcyBoYXZlIG1pc3NpbmcgdmFsdWVzLiBUaGVyZWZvcmUsIG5vIGltcHV0YXRpb25zIGFyZSBuZWNlc3NhcnkgZm9yIHRoZSBkYXRhc2V0Lg0KDQojIyBGaW5hbCBEYXRhc2V0DQoNClRoZSBmaW5hbCBkYXRhc2V0IGhhcyAqKjczNSoqIG9ic2VydmF0aW9ucyBlYWNoIHdpdGggKiozMCoqIHZhcmlhYmxlcy4gQWxsIDMwIHZhcmlhYmxlcyB3aWxsIGJlIGhlbGQgd2l0aGluIHRoZSBkYXRhc2V0IGZvciBhbnkgZnVydGhlciByZXNlYXJjaCwgcmVnYXJkbGVzcyBvZiB0aGVpciB1c2UgaW4gdGhpcyByZXNlYXJjaC4NCg0KQmVsb3cgaXMgYSBxdWljayBzdW1tYXJ5IG9mIGFsbCBudW1lcmljIHZhcmlhYmxlcyBpbiB0aGUgZmluYWwgZGF0YXNldDoNCg0KYGBge3IgbnVtIGRhdGEsIGluY2x1ZGUgPSBUUlVFfQ0KbnVtLmRhdGEgPC0gbmJhLmRhdGEuZmlsdGVyZWRbLCBzYXBwbHkobmJhLmRhdGEuZmlsdGVyZWQsIGlzLm51bWVyaWMpXQ0KDQpudW0uc3VtIDwtIGRhdGEuZnJhbWUoDQogIFZhcmlhYmxlID0gbmFtZXMobnVtLmRhdGEpLA0KICBNaW4gPSBzYXBwbHkobnVtLmRhdGEsIGZ1bmN0aW9uKHgpIG1pbih4LCBuYS5ybSA9IFRSVUUpKSwNCiAgTWVhbiA9IHNhcHBseShudW0uZGF0YSwgZnVuY3Rpb24oeCkgbWVhbih4LCBuYS5ybSA9IFRSVUUpKSwNCiAgTWVkaWFuID0gc2FwcGx5KG51bS5kYXRhLCBmdW5jdGlvbih4KSBtZWRpYW4oeCwgbmEucm0gPSBUUlVFKSksDQogIE1heCA9IHNhcHBseShudW0uZGF0YSwgZnVuY3Rpb24oeCkgbWF4KHgsIG5hLnJtID0gVFJVRSkpDQopDQoNCmthYmxlKG51bS5zdW0sDQogICAgICBjb2wubmFtZXMgPSBjKCJWYXJpYWJsZSIsICJNaW4iLCAiTWVhbiIsICJNZWRpYW4iLCAiTWF4IiksDQogICAgICBjYXB0aW9uID0gIlN1bW1hcnkgb2YgTnVtZXJpYyBWYXJpYWJsZXMiKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiwgInJlc3BvbnNpdmUiKSwgZnVsbF93aWR0aCA9IEZBTFNFKQ0KYGBgDQoNCkJlbG93IGlzIGEgcXVpY2sgc3VtbWFyeSBvZiBhbGwgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGluIHRoZSBmaW5hbCBkYXRhc2V0Og0KDQpgYGB7ciBjYXQgZGF0YSwgaW5jbHVkZSA9IFRSVUV9DQpjYXQuZGF0YSA8LSBuYmEuZGF0YS5maWx0ZXJlZFssIHNhcHBseShuYmEuZGF0YS5maWx0ZXJlZCwgaXMuY2hhcmFjdGVyKV0NCg0KY2F0LnN1bSA8LSBsYXBwbHkobmFtZXMoY2F0LmRhdGEpLCBmdW5jdGlvbih2YXIpIHsNCiAgY291bnRzIDwtIHRhYmxlKGNhdC5kYXRhW1t2YXJdXSwgdXNlTkEgPSAibm8iKQ0KICBzb3J0ZWQuY291bnRzIDwtIHNvcnQoY291bnRzLCBkZWNyZWFzaW5nID0gVFJVRSkNCiAgdG9wLnZhbHVlIDwtIGlmIChsZW5ndGgoc29ydGVkLmNvdW50cykgPiAwKSBuYW1lcyhzb3J0ZWQuY291bnRzKVsxXSBlbHNlIE5BDQogIHRvcC5jb3VudCA8LSBpZiAobGVuZ3RoKHNvcnRlZC5jb3VudHMpID4gMCkgYXMubnVtZXJpYyhzb3J0ZWQuY291bnRzWzFdKSBlbHNlIDANCiAgDQogIGRhdGEuZnJhbWUoDQogICAgVmFyaWFibGUgPSB2YXIsDQogICAgYFRvcCBWYWx1ZWAgPSB0b3AudmFsdWUsDQogICAgYFRvcCBDb3VudGAgPSB0b3AuY291bnQsDQogICAgYFVuaXF1ZSBWYWx1ZXNgID0gbGVuZ3RoKGNvdW50cykNCiAgKQ0KfSkgJT4lDQogIGJpbmRfcm93cygpDQoNCmthYmxlKGNhdC5zdW0sDQogICAgICBjb2wubmFtZXMgPSBjKCJWYXJpYWJsZSIsICJUb3AgVmFsdWUiLCAiVG9wIENvdW50IiwgIlVuaXF1ZSBWYWx1ZXMiKSwNCiAgICAgIGNhcHRpb24gPSAiU3VtbWFyeSBvZiBDYXRlZ29yaWNhbCBWYXJpYWJsZXMgd2l0aCBUb3AgVmFsdWUiKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiwgInJlc3BvbnNpdmUiKSwgZnVsbF93aWR0aCA9IEZBTFNFKQ0KYGBgDQoNCioqTm90ZSoqIEEuSi4gR3JlZW4gaXMgbGlzdGVkIGFzIHRoZSB0b3AgdmFsdWUgZm9yIGBQbGF5ZXJgLiBUaGlzIGlzIGJlY2F1c2UgQS5KLiBHcmVlbiBpcyB0aGUgZmlyc3QgcGxheWVyIGFscGhhYmV0aWNhbGx5IHdpdGhpbiB0aGUgZGF0YXNldCwgYW5kIGVhY2ggcGxheWVyIG9ubHkgYXBwZWFycyBvbmNlLg0KDQoqKk5vdGUqKiBNRU0gaXMgbGlzdGVkIGFzIHRoZSB0b3AgdmFsdWUgZm9yIGBUbWAuIFRoaXMgb25seSBpbmNsdWRlcyBwbGF5ZXJzIHdobyBwbGF5ZWQgZm9yIE1FTSBPTkxZLiBQbGF5ZXJzIHdobyB3ZXJlIHRyYWRlZCB3aXRoaW4gdGhlIHNlYXNvbiB3ZXJlIG5vdCBhZGRlZCB0byB0aGlzIGNvdW50LiBBIGxpc3Qgb2Ygd2hhdCBlYWNoIGFiYnJldmlhdGlvbiBzdGFuZCBmb3IgY2FuIGJlIGZvdW5kIHdpdGhpbiB0aGUgbmV4dCBzZWN0aW9uLg0KDQojIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMNCg0KTmV4dCBJIHdpbGwgcGVyZm9ybSBzb21lICoqRSoqeHBsb3JhdHBvcnkgKipEKiphdGEgKipBKipuYWx5c2lzLCBvciAqKkVEQSoqLiBFREEgaXMgYW4gaW1wb3J0YW50IHN0ZXAgaW4gdGhlIG92ZXJhcmNoaW5nIGRhdGEgYW5hbHlzaXMgcHJvY2VzcywgYXMgaXQgaXMgaW1wb3J0YW50IHRvIGZhbWlsaWFyaXplIHlvdXJzZWxmIHdpdGggdGhlIGRhdGFzZXQuDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiBQb3NpdGlvbg0KDQpCZWxvdyBpcyBhIHBpZSBjaGFydCBvZiBgUG9zYDoNCg0KYGBge3IgcG9zaXRpb24gcGllIGNoYXJ0LCBpbmNsdWRlID0gVFJVRX0NCm5iYS5kYXRhLnBvc2l0aW9uLnBpZS5jaGFydCA8LSBuYmEuZGF0YS5maWx0ZXJlZCAlPiUNCiAgc2VwYXJhdGVfcm93cyhQb3MsIHNlcCA9ICItIikNCg0KcG9zaXRpb24gPC0gbmJhLmRhdGEucG9zaXRpb24ucGllLmNoYXJ0ICU+JQ0KICBncm91cF9ieShQb3MpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwZXJjID0gY291bnQgLyBzdW0oY291bnQpKSAlPiUNCiAgYXJyYW5nZShwZXJjKSAlPiUNCiAgbXV0YXRlKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudChwZXJjKSkNCg0KZ2dwbG90KHBvc2l0aW9uLCBhZXMoeCA9ICIiLCB5ID0gcGVyYywgZmlsbCA9IFBvcykpICsNCiAgZ2VvbV9jb2woKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZShjb3VudCwgIlxuKCIsIGxhYmVscywgIikiLCBzZXAgPSAiIikpLA0KICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9ICgwLjUpKSkgKw0KICBjb29yZF9wb2xhcih0aGV0YSA9ICJ5IikgKw0KICBsYWJzKHRpdGxlID0gIlBpZSBDaGFydCBvZiBQb3NpdGlvbiIsIHkgPSAiIiwgeCA9ICIiKSArDQogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIlBvc2l0aW9uIikpDQpgYGANCg0KQXMgc2hvd24gaW4gdGhlIHBpZSBjaGFydCBhYm92ZSwgdGhlIG1vc3QgY29tbW9uIHBvc2l0aW9uIGluIHRoZSBkYXRhc2V0IGlzIFNob290aW5nIEd1YXJkICgqMjMuNCUqKSBhbmQgdGhlIGxlYXN0IGNvbW1vbiBwb3NpdGlvbiBpcyBDZW50ZXIgKCoxNi44JSopLg0KDQoqKk5vdGUqKiBBbnkgcGxheWVyIHdobyBpcyBjbGFzc2lmaWVkIGFzIGhhdmluZyBtdWx0aXBsZSBwb3NpdGlvbnMgY291bnRzIGFzIGJvdGggcG9zaXRpb25zIGZvciB0aGlzIHZpc3VhbCByZXByZXNlbnRhdGlvbiBvbmx5Lg0KDQojIyBEaXN0cmlidXRpb24gb2YgQWdlDQoNCkJlbG93IGlzIGEgaGlzdG9ncmFtIG9mIGBBZ2VgOg0KDQpgYGB7ciBhZ2UgaGlzdG9ncmFtLCBpbmNsdWRlID0gVFJVRX0NCmhpc3QobmJhLmRhdGEuZmlsdGVyZWQkQWdlLA0KICAgICBicmVha3MgPSBzZXEoMTUsIDQwLCAxKSwNCiAgICAgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgQWdlIiwNCiAgICAgeGxpbSA9IGMoMTUsIDQwKSwNCiAgICAgeGF4dCA9ICJuIiwNCiAgICAgeGxhYiA9ICJBZ2UgKHllYXJzKSIsDQogICAgIHlsaW0gPSBjKDAsIDEwMCksDQogICAgIHlsYWIgPSAiTnVtYmVyIG9mIFBsYXllcnMiLA0KICAgICBjb2wgPSAicmVkIiwNCiAgICAgbGFiZWxzID0gVFJVRSkNCmFibGluZShoID0gc2VxKDEwLCAxMDAsIDEwKSwgY29sID0gImdyYXkiLCBsdHkgPSAiZG90dGVkIikNCmF4aXMoMSwgYXQgPSBzZXEoMTUsIDUwLCA1KSkNCmBgYA0KDQpUaGUgaGlzdG9ncmFtIGFib3ZlIHNob3dzIHRoYXQgYEFnZWAgaGFzIGEgcmlnaHQtc2tld2VkIGRpc3RyaWJ1dGlvbi4gVGhlIHlvdW5nZXN0IHBsYXllcnMgaW4gdGhlIGRhdGFzZXQgYXJlIDE4IHllYXJzIG9sZCwgYW5kIHRoZSBvbGRlc3QgcGxheWVyIGlzIDM5IHllYXJzIG9sZC4NCg0KIyMgRGlzdHJpYnV0aW9uIG9mIFRlYW0NCg0KQmVsb3cgaXMgYSB0YWJsZSBvZiBlYWNoIHRlYW0gYWJicmV2aWF0aW9uIHVuLWFiYnJldmlhdGVkOg0KDQpgYGB7ciB0ZWFtIGJhciBncmFwaCBjaGFydCwgaW5jbHVkZSA9IFRSVUV9DQpuYmEudGVhbXMgPC0gZGF0YS5mcmFtZSgNCiAgYWJicmV2aWF0aW9uID0gYygiQVRMIiwgIkJPUyIsICJCS04iLCAiQ0hBIiwgIkNISSIsICJDTEUiLCAiREFMIiwgIkRFTiIsICJERVQiLCAiR1NXIiwgIkhPVSIsICJJTkQiLCAiTEFDIiwgIkxBTCIsICJNRU0iLCAiTUlBIiwgIk1JTCIsICJNSU4iLCAiTk9QIiwgIk5ZSyIsICJPS0MiLCAiT1JMIiwgIlBISSIsICJQSFgiLCAiUE9SIiwgIlNBQyIsICJTQVMiLCAiVE9SIiwgIlVUQSIsICJXQVMiKSwNCiAgDQogIGZ1bGwubmFtZSA9IGMoIkF0bGFudGEgSGF3a3MiLCAiQm9zdG9uIENlbHRpY3MiLCAiQnJvb2tseW4gTmV0cyIsICJDaGFybG90dGUgSG9ybmV0cyIsICJDaGljYWdvIEJ1bGxzIiwgIkNsZXZlbGFuZCBDYXZhbGllcnMiLCAiRGFsbGFzIE1hdmVyaWNrcyIsICJEZW52ZXIgTnVnZ2V0cyIsICJEZXRyb2l0IFBpc3RvbnMiLCAiR29sZGVuIFN0YXRlIFdhcnJpb3JzIiwgIkhvdXN0b24gUm9ja2V0cyIsICJJbmRpYW5hIFBhY2VycyIsICJMb3MgQW5nZWxlcyBDbGlwcGVycyIsICJMb3MgQW5nZWxlcyBMYWtlcnMiLCAiTWVtcGhpcyBHcml6emxpZXMiLCAiTWlhbWkgSGVhdCIsICJNaWx3YXVrZWUgQnVja3MiLCAiTWlubmVzb3RhIFRpbWJlcndvbHZlcyIsICJOZXcgT3JsZWFucyBQZWxpY2FucyIsICJOZXcgWW9yayBLbmlja3MiLCAiT2tsYWhvbWEgQ2l0eSBUaHVuZGVyIiwgIk9ybGFuZG8gTWFnaWMiLCAiUGhpbGFkZWxwaGlhIDc2ZXJzIiwgIlBob2VuaXggU3VucyIsICJQb3J0bGFuZCBUcmFpbCBCbGF6ZXJzIiwgIlNhY3JhbWVudG8gS2luZ3MiLCAiU2FuIEFudG9uaW8gU3B1cnMiLCAiVG9yb250byBSYXB0b3JzIiwgIlV0YWggSmF6eiIsICJXYXNoaW5ndG9uIFdpemFyZHMiKQ0KKQ0KDQprYWJsZShuYmEudGVhbXMsDQogICAgICBjb2wubmFtZXMgPSBjKCJUZWFtIEFiYnJldmlhdGlvbiIsICJGdWxsIE5hbWUiKSwNCiAgICAgIGNhcHRpb24gPSAiTGlzdCBvZiBOQkEgVGVhbXMgYW5kIFRoZWlyIEFiYnJldmlhdGlvbnMiKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiwgInJlc3BvbnNpdmUiKSwgZnVsbF93aWR0aCA9IEZBTFNFKQ0KYGBgDQoNCkJlbG93IGlzIGEgdGFibGUgb2YgYFRtYDoNCg0KYGBge3IgdGVhbSBiYXIgZ3JhcGggY2hhcnQyLCBpbmNsdWRlID0gVFJVRX0NCnBsYXllci5jb3VudCA8LSBuYmEuZGF0YS5maWx0ZXJlZCAlPiUNCiAgc2VwYXJhdGVfcm93cyhUbSwgc2VwID0gIi0iKSAlPiUNCiAgZ3JvdXBfYnkoVG0pICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGRlc2MoY291bnQpKQ0KDQpwbGF5ZXIuY291bnQgJT4lDQogIGthYmxlKA0KICAgIGNvbC5uYW1lcyA9IGMoIlRlYW0iLCAiTnVtYmVyIG9mIFBsYXllcnMiKSwNCiAgICBhbGlnbiA9ICJsYyIsDQogICAgY2FwdGlvbiA9ICJQbGF5ZXJzIGJ5IFRlYW0iDQogICkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIsICJyZXNwb25zaXZlIiksIGZ1bGxfd2lkdGggPSBGQUxTRSkNCmBgYA0KDQpBcyBzaG93biBpbiB0aGUgdGFibGUgYWJvdmUsIHRoZSBNZW1waGlzIEdyaXp6bGllcyBoYWQgdGhlIG1vc3QgYW1vdW50IG9mIHBsYXllcnMgaW4gdGhlIGRhdGFzZXQuDQoNCioqTm90ZSoqIEFueSBwbGF5ZXIgd2hvIGlzIHBsYXllZCBmb3IgbXVsdGlwbGUgdGVhbXMgY291bnRzIGZvciBib3RoIHRlYW1zIGluIHRoZSBhYm92ZSB0YWJsZS4NCg0KIyMgRGlzdHJpYnV0aW9uIG9mIEdhbWVzIFBsYXllZA0KDQpCZWxvdyBpcyBhIGhpc3RvZ3JhbSBvZiBgR2A6DQoNCmBgYHtyIGdhbWVzIHBsYXllZCBoaXN0b2dyYW0sIGluY2x1ZGUgPSBUUlVFfQ0KaGlzdChuYmEuZGF0YS5maWx0ZXJlZCRHLA0KICAgICBicmVha3MgPSBzZXEoMCwgOTAsIDUpLA0KICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBHYW1lcyBQbGF5ZWQiLA0KICAgICB4bGltID0gYygwLCA5MCksDQogICAgIHhheHQgPSAibiIsDQogICAgIHhsYWIgPSAiR2FtZXMgUGxheWVkIiwNCiAgICAgeWxpbSA9IGMoMCwgNjApLA0KICAgICB5bGFiID0gIk51bWJlciBvZiBQbGF5ZXJzIiwNCiAgICAgY29sID0gInJlZCIsDQogICAgIGxhYmVscyA9IFRSVUUpDQphYmxpbmUoaCA9IHNlcSg1LCA2MCwgNSksIGNvbCA9ICJncmF5IiwgbHR5ID0gImRvdHRlZCIpDQpheGlzKDEsIGF0ID0gc2VxKDAsIDkwLCAxMCkpDQpgYGANCg0KVGhlIGhpc3RvZ3JhbSBhYm92ZSBzaG93cyB0aGF0IHRoZSBkaXN0cmlidXRpb24gb2YgYEdgIGhhcyB0d28gcGVha3MgYXJvdW5kIHRoZSA3MCdzIGFuZCA1LTEwLiBJdCBpcyBpbXBvcnRhbnQgdG8gbm90ZSB0aGF0IHRoZXJlIGFyZSA4MiBnYW1lcyBpbiBhIE5CQSByZWd1bGFyIHNlYXNvbi4NCg0KIyMgRGlzdHJpYnV0aW9uIG9mIEdhbWVzIFN0YXJ0ZWQNCg0KQmVsb3cgaXMgYSBoaXN0b2dyYW0gb2YgYEdTYDoNCg0KYGBge3IgZ2FtZXMgc3RhcnRlZCBoaXN0b2dyYW0sIGluY2x1ZGUgPSBUUlVFfQ0KaGlzdChuYmEuZGF0YS5maWx0ZXJlZCRHUywNCiAgICAgYnJlYWtzID0gc2VxKDAsIDkwLCA1KSwNCiAgICAgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgR2FtZXMgU3RhcnRlZCIsDQogICAgIHhsaW0gPSBjKDAsIDkwKSwNCiAgICAgeGF4dCA9ICJuIiwNCiAgICAgeGxhYiA9ICJHYW1lcyBTdGFydGVkIiwNCiAgICAgeWxpbSA9IGMoMCwgMzAwKSwNCiAgICAgeWxhYiA9ICJOdW1iZXIgb2YgUGxheWVycyIsDQogICAgIGNvbCA9ICJyZWQiLA0KICAgICBsYWJlbHMgPSBUUlVFKQ0KYWJsaW5lKGggPSBzZXEoMjUsIDMwMCwgMjUpLCBjb2wgPSAiZ3JheSIsIGx0eSA9ICJkb3R0ZWQiKQ0KYXhpcygxLCBhdCA9IHNlcSgwLCA5MCwgMTApKQ0KYGBgDQoNClRoZSBoaXN0b2dyYW0gYWJvdmUgc2hvd3MgdGhhdCBmb3IgYEdTYCwgYWxtb3N0IGhhbGYgb2YgdGhlIHBsYXllcnMgaW4gdGhlIGRhdGFzZXQgc3RhcnRlZCBsZXNzIHRoYW4gNSBnYW1lcy4gT25seSA1IHBsYXllcnMgY2FuIHN0YXJ0IGEgZ2FtZSBwZXIgdGVhbSBwZXIgZ2FtZSwgbWVhbmluZyB0aGF0IG9ubHkgXH4gMS8zcmQgdG8gMS82dGggb2YgYW4gTkJBIHRlYW0ncyByb3N0ZXIgd2lsbCBub3Qgc3RhcnQgb24gYW55IGdpdmVuIGdhbWUsIGdpdmVuIHRoZWlyIHJvc3RlciBzaXplLg0KDQojIyBEaXN0cmlidXRpb24gb2YgTWludXRlcyBQbGF5ZWQgcGVyIEdhbWUNCg0KQmVsb3cgaXMgYSBoaXN0b2dyYW0gb2YgYE1QYDoNCg0KYGBge3IgbWludXRlcyBwbGF5ZWQgaGlzdG9ncmFtLCBpbmNsdWRlID0gVFJVRX0NCmhpc3QobmJhLmRhdGEuZmlsdGVyZWQkTVAsDQogICAgIGJyZWFrcyA9IHNlcSgwLCA0OCwgNCksDQogICAgIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIE1pbnV0ZXMgUGxheWVkIHBlciBHYW1lIiwNCiAgICAgeGxpbSA9IGMoMCwgNDgpLA0KICAgICB4YXh0ID0gIm4iLA0KICAgICB4bGFiID0gIk1pbnV0ZXMgUGxheWVkIHBlciBHYW1lIiwNCiAgICAgeWxpbSA9IGMoMCwgMTAwKSwNCiAgICAgeWxhYiA9ICJOdW1iZXIgb2YgUGxheWVycyIsDQogICAgIGNvbCA9ICJyZWQiLA0KICAgICBsYWJlbHMgPSBUUlVFKQ0KYWJsaW5lKGggPSBzZXEoMTAsIDEwMCwgMTApLCBjb2wgPSAiZ3JheSIsIGx0eSA9ICJkb3R0ZWQiKQ0KYXhpcygxLCBhdCA9IHNlcSgwLCA0OCwgOCkpDQpgYGANCg0KVGhlIGhpc3RvZ3JhbSBhYm92ZSBzaG93cyB0aGF0IGBHU2AgaGFzIGEgbXVjaCBtb3JlIGV2ZW5seS1kaXN0cmlidXRlZCBkaXN0cmlidXRpb24sIFRoaXMgbWVhbnMgdGhhdCBldmVuIHRob3VnaCBtYW55IHBsYXllcnMgZGlkIG5vdCBzdGFydCBhIGdhbWUgYWxsIHNlYXNvbiwgYWxtb3N0IGFsbCBwbGF5ZXJzIHBsYXllZCBhdCBsZWFzdCA0IG1pbnV0ZXMgcGVyIGdhbWUgb24gYXZlcmFnZS4NCg0KIyMgRGlzdHJpYnV0aW9uIG9mIEZpZWxkIEdvYWxzDQoNCkJlbG93IGlzIGEgc2NhdHRlcnBsb3Qgb2YgYEZHQWAgdnMgYEZHYCwgd2l0aCBhIGNvbG9yIHNjYWxpbmcgb2YgYEZHUGA6DQoNCmBgYHtyIGZpZWxkIGdvYWwgc2NhdHRlcGxvdCwgaW5jbHVkZSA9IFRSVUV9DQpnZ3Bsb3QobmJhLmRhdGEuZmlsdGVyZWQsIGFlcyh4ID0gRkdBLCB5ID0gRkcpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gRkdQKSkgKw0KICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGNvbG9yID0gImJsYWNrIiwgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDEpICsNCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobG93ID0gInJlZCIsIGhpZ2ggPSAiZ3JlZW4iKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRmllbGQgR29hbCBBdHRlbXB0cyB2cyBGaWVsZCBHb2FscyBTY29yZWQiLA0KICAgIHggPSAiRmllbGQgR29hbCBBdHRlbXB0cyBwZXIgR2FtZSIsDQogICAgeSA9ICJGaWVsZCBHb2FsIFNjb3JlZCBwZXIgR2FtZSIsDQogICAgY29sb3IgPSAiRmllbGQgR29hbCBQZXJjZW50YWdlIg0KICApICsNCiAgY29vcmRfZXF1YWwoKSArDQogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDI0KSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAyNCkpDQpgYGANCg0KQXMgc2hvd24gaW4gdGhlIHNjYXR0ZXJwbG90IGFib3ZlLCBwbGF5ZXJzIHNlZW0gdG8gY2x1bXAgdG9nZXRoZXIgZm9yIGZpZWxkIGdvYWwgZGF0YSwgZm9sbG93aW5nIGEgc3RyYWlnaHQtbGluZS4gVGhlIGJsYWNrIGRhc2hlZCBsaW5lIHJlcHJlc2VudHMgaWYgYSBwbGF5ZXIgaGFzIGEgMTAwJSBmaWVsZCBnb2FsIHBlcmNlbnRhZ2UuDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiAyLVBvaW50IEZpZWxkIEdvYWxzDQoNCkJlbG93IGlzIGEgc2NhdHRlcnBsb3Qgb2YgYFgyUEFgIHZzIGBYMlBgLCB3aXRoIGEgY29sb3Igc2NhbGluZyBvZiBgWDJQUGA6DQoNCmBgYHtyIDIgcG9pbnQgc2NhdHRlcnBsb3QsIGluY2x1ZGUgPSBUUlVFfQ0KZ2dwbG90KG5iYS5kYXRhLmZpbHRlcmVkLCBhZXMoeCA9IFgyUEEsIHkgPSBYMlApKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gWDJQUCkpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJibGFjayIsIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAxKSArDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICJyZWQiLCBoaWdoID0gImdyZWVuIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIjItUG9pbnQgQXR0ZW1wdHMgdnMgMi1Qb2ludCBTY29yZWQiLA0KICAgIHggPSAiMi1Qb2ludCBBdHRlbXB0cyBwZXIgR2FtZSIsDQogICAgeSA9ICIyLVBvaW50IFNjb3JlZCBwZXIgR2FtZSIsDQogICAgY29sb3IgPSAiMi1Qb2ludCBQZXJjZW50YWdlIg0KICApICsNCiAgY29vcmRfZXF1YWwoKSArDQogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDIwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAyMCkpDQpgYGANCg0KQXMgc2hvd24gYWJvdmUsIHRoZXJlIGFwcGVhcnMgdG8gYmUgc2l4IG91dGxpZXJzIHBhc3QgdGhlIDggMi1wb2ludCBzY29yZWQgcGVyIGdhbWUgcmFuZ2UuIFRob3NlIHBsYXllcnMgYXJlIGJlbG93Og0KDQpgYGB7ciAyLXBvaW50IG91dGxpZXJzLCBpbmNsdWRlID0gVFJVRX0NCnR3by5wdC5vdXRsaWVycyA8LSBuYmEuZGF0YS5maWx0ZXJlZCAlPiUNCiAgZmlsdGVyKFgyUCA+IDgpICU+JQ0KICBkcGx5cjo6c2VsZWN0KFBsYXllciwgWDJQQSwgWDJQLCBYMlBQKQ0KDQprYWJsZSh0d28ucHQub3V0bGllcnMsDQogICAgICBjb2wubmFtZXMgPSBjKCJQbGF5ZXIiLCAiMi1Qb2ludCBBdHRlbXB0cyBwZXIgR2FtZSIsICIyLVBvaW50IEdvYWxzIHBlciBHYW1lIiwgIjIgUG9pbnQgUGVyY2VudGFnZSIpLA0KICAgICAgY2FwdGlvbiA9ICJQbGF5ZXJzIHdpdGggbW9yZSB0aGFuIDggMi1Qb2ludCBGaWVsZCBHb2FscyBTY29yZWQiKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiwgInJlc3BvbnNpdmUiKSwgZnVsbF93aWR0aCA9IEZBTFNFKQ0KYGBgDQoNClBsYXllcnMgR2lhbm5pcyBBbnRldG9rb3VubXBvLCBBbnRob255IERhdmlzLCBKb2VsIEVtYmlpZCwgU2hhaSBHaWxnZW91cy1BbGV4YW5kZXIsIE5pa29sYSBKb2tpYywgYW5kIFppb24gV2lsbGlhbXNvbiBhcmUgdGhlICJ1bnVzdWFsIiBwbGF5ZXJzIGZvciAyLXBvaW50IGZpZWxkIGdvYWxzLg0KDQojIyBEaXN0cmlidXRpb24gb2YgMy1Qb2ludCBGaWVsZCBHb2Fscw0KDQpCZWxvdyBpcyBhIHNjYXR0ZXJwbG90IG9mIGBYM1BBYCB2cyBgWDNQYCwgd2l0aCBhIGNvbG9yIHNjYWxpbmcgb2YgYFgzUFBgOg0KDQpgYGB7ciAzIHBvaW50IHNjYXR0ZXJwbG90LCBpbmNsdWRlID0gVFJVRX0NCmdncGxvdChuYmEuZGF0YS5maWx0ZXJlZCwgYWVzKHggPSBYM1BBLCB5ID0gWDNQKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IFgzUFApKSArDQogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgY29sb3IgPSAiYmxhY2siLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMSkgKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3cgPSAicmVkIiwgaGlnaCA9ICJncmVlbiIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICIzLVBvaW50IEF0dGVtcHRzIHZzIDMtUG9pbnQgU2NvcmVkIiwNCiAgICB4ID0gIjMtUG9pbnQgQXR0ZW1wdHMgcGVyIEdhbWUiLA0KICAgIHkgPSAiMy1Qb2ludCBTY29yZWQgcGVyIEdhbWUiLA0KICAgIGNvbG9yID0gIjMtUG9pbnQgUGVyY2VudGFnZSINCiAgKSArDQogIGNvb3JkX2VxdWFsKCkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxMikpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMTIpKQ0KYGBgDQoNCkFzIHNob3duIGFib3ZlLCB0aGVyZSBhcHBlYXJzIHRvIGJlIHR3byBvdXRsaWVycyBwYXN0IHRoZSAxMCAzLXBvaW50IGF0dGVtcHRzIHBlciBnYW1lIHJhbmdlLiBUaG9zZSBwbGF5ZXJzIGFyZSBiZWxvdzoNCg0KYGBge3IgMy1wb2ludCBvdXRsaWVycywgaW5jbHVkZSA9IFRSVUV9DQp0aHJlZS5wdC5vdXRsaWVycyA8LSBuYmEuZGF0YS5maWx0ZXJlZCAlPiUNCiAgZmlsdGVyKFgzUEEgPiAxMCkgJT4lDQogIGRwbHlyOjpzZWxlY3QoUGxheWVyLCBYM1BBLCBYM1AsIFgzUFApDQoNCmthYmxlKHRocmVlLnB0Lm91dGxpZXJzLA0KICAgICAgY29sLm5hbWVzID0gYygiUGxheWVyIiwgIjMtUG9pbnQgQXR0ZW1wdHMgcGVyIEdhbWUiLCAiMy1Qb2ludCBHb2FscyBwZXIgR2FtZSIsICIzIFBvaW50IFBlcmNlbnRhZ2UiKSwNCiAgICAgIGNhcHRpb24gPSAiUGxheWVycyB3aXRoIG1vcmUgdGhhbiAxMCAzLVBvaW50IEF0dGVtcHMiKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiwgInJlc3BvbnNpdmUiKSwgZnVsbF93aWR0aCA9IEZBTFNFKQ0KYGBgDQoNClBsYXllcnMgU3RlcGhlbiBDdXJyeSBhbmQgTHVrYSBEb25jaWMgYXJlIHRoZSBzdGFuZG91dCBwbGF5ZXJzIGZvciAzLXBvaW50IGZpZWxkIGdvYWxzLg0KDQojIyBEaXN0cmlidXRpb24gb2YgRWZmZWN0aXZlIEZpZWxkIEdvYWwgUGVyY2VudGFnZQ0KDQpFZmZlY3RpdmUgRmllbGQgR29hbCBQZXJjZW50YWdlIChgZUZHUGApIGlzIGFuIGFkanVzdGVkIGZpZWxkIGdvYWwgcGVyY2VudGFnZSB0aGF0IGFjY291bnRzIGZvciAyLXBvaW50IGZpZWxkIGdvYWxzIGJlaW5nIHdvcnRoIDIgcG9pbnRzLCBhbmQgMy1wb2ludCBmaWVsZCBnb2FscyBiZWluZyB3b3J0aCAzIHBvaW50cy4NCg0KQmVsb3cgaXMgYSBzY2F0dGVycGxvdCBvZiBgWDJQUGAgdnMgYFgzUFBgLCB3aXRoIGEgY29sb3Igc2NhbGluZyBvZiBgZUZHUGA6DQoNCmBgYHtyIGVGR1Agc2NhdHRlcnBsb3QsIGluY2x1ZGUgPSBUUlVFfQ0KZ2dwbG90KG5iYS5kYXRhLmZpbHRlcmVkLCBhZXMoeCA9IFgyUFAsIHkgPSBYM1BQKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGVGR1ApKSArDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICJyZWQiLCBoaWdoID0gImdyZWVuIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIjMtUG9pbnQgQXR0ZW1wdHMgdnMgMy1Qb2ludCBTY29yZWQiLA0KICAgIHggPSAiMi1Qb2ludCBGaWVsZCBHb2FsIFBlcmNlbnRhZ2UiLA0KICAgIHkgPSAiMy1Qb2ludCBGaWVsZCBHb2FsIFBlcmNlbnRhZ2UiLA0KICAgIGNvbG9yID0gIkVmZmVjdGl2ZSBGaWVsZCBHb2FsIFBlcmNlbnRhZ2UiDQogICkgKw0KICBjb29yZF9lcXVhbCgpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMSkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMSkpDQpgYGANCg0KQXMgc2hvd24gaW4gdGhlIHNjYXR0ZXJwbG90IGFib3ZlLCB0aGVyZSBhcHBlYXIgdG8gYmUgc29tZSBwbGF5ZXJzIHdobyBoYXZlIGEgMCBmb3IgYFgyUFBgIGFuZC9vciBgWDNQUGAuIEFwYXJ0IGZyb20gdGhlc2Ugb2JzZXJ2YXRpb25zLCBtb3N0IHBsYXllcnMgYXJlIGluIGEgY2x1bXAgdG9nZXRoZXIuDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiBGcmVlIFRocm93cw0KDQpCZWxvdyBpcyBhIHNjYXR0ZXJwbG90IG9mIGBGVEFgIHZzIGBGVGAsIHdpdGggYSBjb2xvciBzY2FsaW5nIG9mIGBGVFBgOg0KDQpgYGB7ciBmcmVlIHRocm93IHNjYXR0ZXJwbG90LCBpbmNsdWRlID0gVFJVRX0NCmdncGxvdChuYmEuZGF0YS5maWx0ZXJlZCwgYWVzKHggPSBGVEEsIHkgPSBGVCkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBGVFApKSArDQogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgY29sb3IgPSAiYmxhY2siLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMSkgKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3cgPSAicmVkIiwgaGlnaCA9ICJncmVlbiIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJGcmVlIFRocm93IEF0dGVtcHRzIHZzIEZyZWUgVGhyb3dzIFNjb3JlZCIsDQogICAgeCA9ICJGcmVlIFRocm93IEF0dGVtcHRzIHBlciBHYW1lIiwNCiAgICB5ID0gIkZyZWUgVGhyb3dzIFNjb3JlZCBwZXIgR2FtZSIsDQogICAgY29sb3IgPSAiRnJlZSBUaHJvdyBQZXJjZW50YWdlIg0KICApICsNCiAgY29vcmRfZXF1YWwoKSArDQogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDEyKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxMikpDQpgYGANCg0KQXMgc2hvd24gYWJvdmUsIHRoZXJlIGFwcGVhcnMgdG8gYmUgZm91ciBvdXRsaWVycyBwYXN0IHRoZSA4IGZyZWUgdGhyb3cgYXR0ZW1wdHMgcGVyIGdhbWUgcmFuZ2UuIFRob3NlIHBsYXllcnMgYXJlIGJlbG93Og0KDQpgYGB7ciBmcmVlIHRocm93IG91dGxpZXJzLCBpbmNsdWRlID0gVFJVRX0NCmZyZWUudGhyb3cub3V0bGllcnMgPC0gbmJhLmRhdGEuZmlsdGVyZWQgJT4lDQogIGZpbHRlcihGVEEgPiA4KSAlPiUNCiAgZHBseXI6OnNlbGVjdChQbGF5ZXIsIEZUQSwgRlQsIEZUUCkNCg0Ka2FibGUoZnJlZS50aHJvdy5vdXRsaWVycywNCiAgICAgIGNvbC5uYW1lcyA9IGMoIlBsYXllciIsICJGcmVlIFRocm93IEF0dGVtcHRzIHBlciBHYW1lIiwgIkZyZWUgVGhyb3cgcGVyIEdhbWUiLCAiRnJlZSBUaHJvdyBQZXJjZW50YWdlIiksDQogICAgICBjYXB0aW9uID0gIlBsYXllcnMgd2l0aCBtb3JlIHRoYW4gMTAgRnJlZSBUaHJvdyBBdHRlbXB0cyIpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiLCAicmVzcG9uc2l2ZSIpLCBmdWxsX3dpZHRoID0gRkFMU0UpDQpgYGANCg0KUGxheWVycyBHaWFubmlzIEFudGV0b2tvdW5tcG8sIEx1a2EgRG9uY2ljLCBKb2VsIEVtYmlpZCwgYW5kIFNoYWkgR2lsZ2VvdXMtQWxleGFuZGVyIGFyZSB0aGUgInVudXN1YWwiIHBsYXllcnMgZm9yIGZyZWUgdGhyb3dzLg0KDQojIyBEaXN0cmlidXRpb24gb2YgUmVib3VuZHMNCg0KQmVsb3cgaXMgYSBzY2F0dGVycGxvdCBvZiBgT1JCYCB2cyBgRFJCYCwgd2l0aCBhIGNvbG9yIHNjYWxpbmcgb2YgYFRSQmA6DQoNCmBgYHtyIHJlYm91bmQgc2NhdHRlcnBsb3QsIGluY2x1ZGUgPSBUUlVFfQ0KZ2dwbG90KG5iYS5kYXRhLmZpbHRlcmVkLCBhZXMoeCA9IE9SQiwgeSA9IERSQikpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBUUkIpKSArDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICJyZWQiLCBoaWdoID0gImdyZWVuIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk9mZmVuc2l2ZSBSZWJvdW5kcyB2cyBEZWZlbnNpdmUgUmVib3VuZHMiLA0KICAgIHggPSAiT2ZmZW5zaXZlIFJlYm91bmRzIHBlciBHYW1lIiwNCiAgICB5ID0gIkRlZmVuc2l2ZSBSZWJvdW5kcyBwZXIgR2FtZSIsDQogICAgY29sb3IgPSAiVG90YWwgUmVib3VuZHMiDQogICkNCmBgYA0KDQpBcyBzaG93biBhYm92ZSwgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGBPUkJgIGFuZCBgRFJCYCBoYXZlIGEgc2xpZ2h0IGZhbm5pbmcgc2hhcGUgYXMgYm90aCB2YXJpYWJsZXMgaW5jcmVhc2UgaW4gdmFsdWUuDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiBBc3Npc3RzDQoNCkJlbG93IGlzIGEgaGlzdG9ncmFtIG9mIGBBU1RgOg0KDQpgYGB7ciBhc3Npc3RzIGhpc3RvZ3JhbSwgaW5jbHVkZSA9IFRSVUV9DQpoaXN0KG5iYS5kYXRhLmZpbHRlcmVkJEFTVCwNCiAgICAgYnJlYWtzID0gc2VxKDAsIDEyLCAxKSwNCiAgICAgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgQXNzaXN0cyBwZXIgR2FtZSIsDQogICAgIHhsaW0gPSBjKDAsIDEyKSwNCiAgICAgeGF4dCA9ICJuIiwNCiAgICAgeGxhYiA9ICJBc3Npc3RzIHBlciBHYW1lIiwNCiAgICAgeWxpbSA9IGMoMCwgMjUwKSwNCiAgICAgeWxhYiA9ICJOdW1iZXIgb2YgUGxheWVycyIsDQogICAgIGNvbCA9ICJyZWQiLA0KICAgICBsYWJlbHMgPSBUUlVFKQ0KYWJsaW5lKGggPSBzZXEoMjUsIDI1MCwgMjUpLCBjb2wgPSAiZ3JheSIsIGx0eSA9ICJkb3R0ZWQiKQ0KYXhpcygxLCBhdCA9IHNlcSgwLCAxMiwgMikpDQpgYGANCg0KVGhlIGhpc3RvZ3JhbSBhYm92ZSBzaG93cyB0aGF0IGBBU1RgIGhhcyBhIHJpZ2h0IHNrZXdlZC1kaXN0cmlidXRpb24uDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiBTdGVhbHMNCg0KQmVsb3cgaXMgYSBoaXN0b2dyYW0gb2YgYFNUTGA6DQoNCmBgYHtyIHN0ZWFscyBoaXN0b2dyYW0sIGluY2x1ZGUgPSBUUlVFfQ0KaGlzdChuYmEuZGF0YS5maWx0ZXJlZCRTVEwsDQogICAgIGJyZWFrcyA9IHNlcSgwLCAyLjUsIC4yNSksDQogICAgIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIFN0ZWFscyBwZXIgR2FtZSIsDQogICAgIHhsaW0gPSBjKDAsIDIuNSksDQogICAgIHhheHQgPSAibiIsDQogICAgIHhsYWIgPSAiU3RlYWxzIHBlciBHYW1lIiwNCiAgICAgeWxpbSA9IGMoMCwgMjAwKSwNCiAgICAgeWxhYiA9ICJOdW1iZXIgb2YgUGxheWVycyIsDQogICAgIGNvbCA9ICJyZWQiLA0KICAgICBsYWJlbHMgPSBUUlVFKQ0KYWJsaW5lKGggPSBzZXEoMjUsIDIwMCwgMjUpLCBjb2wgPSAiZ3JheSIsIGx0eSA9ICJkb3R0ZWQiKQ0KYXhpcygxLCBhdCA9IHNlcSgwLCAyLjUsIC41KSkNCmBgYA0KDQpUaGUgaGlzdG9ncmFtIGFib3ZlIHNob3dzIHRoYXQgYFNUTGAgaGFzIHJpZ2h0LXNrZXdlZCBkaXN0cmlidXRpb24sIGJ1dCBub3QgYXMgYWdncmVzc2l2ZSBhcyBhc3Npc3RzLg0KDQojIyBEaXN0cmlidXRpb24gb2YgQmxvY2tzDQoNCkJlbG93IGlzIGEgaGlzdG9ncmFtIG9mIGBCTEtgOg0KDQpgYGB7ciBibG9ja3MgaGlzdG9ncmFtLCBpbmNsdWRlID0gVFJVRX0NCmhpc3QobmJhLmRhdGEuZmlsdGVyZWQkQkxLLA0KICAgICBicmVha3MgPSBzZXEoMCwgNCwgLjQpLA0KICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBCbG9ja3MgcGVyIEdhbWUiLA0KICAgICB4bGltID0gYygwLCA0KSwNCiAgICAgeGF4dCA9ICJuIiwNCiAgICAgeGxhYiA9ICJCbG9ja3MgcGVyIEdhbWUiLA0KICAgICB5bGltID0gYygwLCA0MDApLA0KICAgICB5bGFiID0gIk51bWJlciBvZiBQbGF5ZXJzIiwNCiAgICAgY29sID0gInJlZCIsDQogICAgIGxhYmVscyA9IFRSVUUpDQphYmxpbmUoaCA9IHNlcSg1MCwgNDAwLCA1MCksIGNvbCA9ICJncmF5IiwgbHR5ID0gImRvdHRlZCIpDQpheGlzKDEsIGF0ID0gc2VxKDAsIDQsIC44KSkNCmBgYA0KDQpUaGUgaGlzdG9ncmFtIGFib3ZlIHNob3dzIHRoYXQgYEJMS2AgaGFzIGFuIGFnZ3Jlc3NpdmVseSByaWdodC1za2V3ZWQgZGlzdHJpYnV0aW9uLiBUaGVyZSBhcHBlYXJzIHRvIGJlIG9uZSBvdXRsaWVyIHdobyBpcyBhYm92ZSB0aGUgMyBibG9ja3MgcGVyIGdhbWUuIFRoYXQgcGxheWVyIGlzIGJlbG93Og0KDQpgYGB7ciBibG9ja3Mgb3V0bGllcnMsIGluY2x1ZGUgPSBUUlVFfQ0KYmxvY2tzLm91dGxpZXJzIDwtIG5iYS5kYXRhLmZpbHRlcmVkICU+JQ0KICBmaWx0ZXIoQkxLID4gMykgJT4lDQogIGRwbHlyOjpzZWxlY3QoUGxheWVyLCBHLCBHUywgQkxLKQ0KDQprYWJsZShibG9ja3Mub3V0bGllcnMsDQogICAgICBjb2wubmFtZXMgPSBjKCJQbGF5ZXIiLCAiR2FtZXMgUGxheWVkIiwgIkdhbWVzIFN0YXJ0ZWQiLCAiQmxvY2tzIiksDQogICAgICBjYXB0aW9uID0gIlBsYXllcnMgd2l0aCBtb3JlIHRoYW4gMyBCbG9ja3MgcGVyIEdhbWUiKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiwgInJlc3BvbnNpdmUiKSwgZnVsbF93aWR0aCA9IEZBTFNFKQ0KYGBgDQoNClZpY3RvciBXZW1iYW55YW1hIHdhcyB0aGUgbGVhZ3VlIGxlYWRlciBpbiBibG9ja3MgcGVyIGdhbWUuDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiBUdXJub3ZlcnMNCg0KQmVsb3cgaXMgYSBoaXN0b2dyYW0gb2YgYFRPVmA6DQoNCmBgYHtyIHR1cm5vdmVycyBoaXN0b2dyYW0sIGluY2x1ZGUgPSBUUlVFfQ0KaGlzdChuYmEuZGF0YS5maWx0ZXJlZCRUT1YsDQogICAgIGJyZWFrcyA9IHNlcSgwLCA1LCAuMjUpLA0KICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBUdXJub3ZlcnMgcGVyIEdhbWUiLA0KICAgICB4bGltID0gYygwLCA1KSwNCiAgICAgeGF4dCA9ICJuIiwNCiAgICAgeGxhYiA9ICJUdXJub3ZlcnMgcGVyIEdhbWUiLA0KICAgICB5bGltID0gYygwLCAxNTApLA0KICAgICB5bGFiID0gIk51bWJlciBvZiBQbGF5ZXJzIiwNCiAgICAgY29sID0gInJlZCIsDQogICAgIGxhYmVscyA9IFRSVUUpDQphYmxpbmUoaCA9IHNlcSgyNSwgMTUwLCAyNSksIGNvbCA9ICJncmF5IiwgbHR5ID0gImRvdHRlZCIpDQpheGlzKDEsIGF0ID0gc2VxKDAsIDUsIC41KSkNCmBgYA0KDQpUaGUgaGlzdG9ncmFtIGFib3ZlIHNob3dzIHRoYXQgYEJMS2AgaGFzIGEgcmlnaHQtc2tld2VkIGRpc3RyaWJ1dGlvbi4gVGhlcmUgYXBwZWFycyB0byBiZSBvbmUgb3V0bGllciB3aG8gaXMgYWJvdmUgdGhlIDQgdHVybm92ZXJzIHBlciBnYW1lLiBUaGF0IHBsYXllciBpcyBiZWxvdzoNCg0KYGBge3IgdHVybm92ZXJzIG91dGxpZXJzLCBpbmNsdWRlID0gVFJVRX0NCnR1cm5vdmVycy5vdXRsaWVycyA8LSBuYmEuZGF0YS5maWx0ZXJlZCAlPiUNCiAgZmlsdGVyKFRPViA+IDQpICU+JQ0KICBkcGx5cjo6c2VsZWN0KFBsYXllciwgRywgR1MsIFRPVikNCg0Ka2FibGUodHVybm92ZXJzLm91dGxpZXJzLA0KICAgICAgY29sLm5hbWVzID0gYygiUGxheWVyIiwgIkdhbWVzIFBsYXllZCIsICJHYW1lcyBTdGFydGVkIiwgIlR1cm5vdmVycyIpLA0KICAgICAgY2FwdGlvbiA9ICJQbGF5ZXJzIHdpdGggbW9yZSB0aGFuIDQgVFVybm92ZXJzIHBlciBHYW1lIikgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIsICJyZXNwb25zaXZlIiksIGZ1bGxfd2lkdGggPSBGQUxTRSkNCmBgYA0KDQpUcmFlIFlvdW5nIHdhcyB0aGUgbGVhZ3VlIGxlYWRlciBpbiBibG9ja3MgcGVyIGdhbWUuDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiBQZXJzb25hbCBGb3Vscw0KDQpCZWxvdyBpcyBhIGhpc3RvZ3JhbSBvZiBgUEZgOg0KDQpgYGB7ciBwZXJzb25hbCBmb3VscyBoaXN0b2dyYW0sIGluY2x1ZGUgPSBUUlVFfQ0KaGlzdChuYmEuZGF0YS5maWx0ZXJlZCRQRiwNCiAgICAgYnJlYWtzID0gc2VxKDAsIDQsIC40KSwNCiAgICAgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgUGVyc29uYWwgRm91bHMgcGVyIEdhbWUiLA0KICAgICB4bGltID0gYygwLCA0KSwNCiAgICAgeGF4dCA9ICJuIiwNCiAgICAgeGxhYiA9ICJQZXJzb25hbCBGb3VscyBwZXIgR2FtZSIsDQogICAgIHlsaW0gPSBjKDAsIDEyMCksDQogICAgIHlsYWIgPSAiTnVtYmVyIG9mIFBsYXllcnMiLA0KICAgICBjb2wgPSAicmVkIiwNCiAgICAgbGFiZWxzID0gVFJVRSkNCmFibGluZShoID0gc2VxKDEwLCAxMjAsIDEwKSwgY29sID0gImdyYXkiLCBsdHkgPSAiZG90dGVkIikNCmF4aXMoMSwgYXQgPSBzZXEoMCwgNCwgLjgpKQ0KYGBgDQoNClRoZSBoaXN0b2dyYW0gYWJvdmUgc2hvd3MgdGhhdCBgQkxLYCBoYXMgYSByaWdodC1za2V3ZWQgZGlzdHJpYnV0aW9uLg0KDQojIyBEaXN0cmlidXRpb24gb2YgUG9pbnRzIFNjb3JlZA0KDQpCZWxvdyBpcyBhIGhpc3RvZ3JhbSBvZiBgUFRTYDoNCg0KYGBge3IgcG9pbnRzIHNjb3JlZCBoaXN0b2dyYW0sIGluY2x1ZGUgPSBUUlVFfQ0KaGlzdChuYmEuZGF0YS5maWx0ZXJlZCRQVFMsDQogICAgIGJyZWFrcyA9IHNlcSgwLCAzNiwgNCksDQogICAgIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIFBvaW50cyBTY29yZWQgcGVyIEdhbWUiLA0KICAgICB4bGltID0gYygwLCAzNiksDQogICAgIHhheHQgPSAibiIsDQogICAgIHhsYWIgPSAiUG9pbnRzIFNjb3JlZCBwZXIgR2FtZSIsDQogICAgIHlsaW0gPSBjKDAsIDIwMCksDQogICAgIHlsYWIgPSAiTnVtYmVyIG9mIFBsYXllcnMiLA0KICAgICBjb2wgPSAicmVkIiwNCiAgICAgbGFiZWxzID0gVFJVRSkNCmFibGluZShoID0gc2VxKDI1LCAyMDAsIDI1KSwgY29sID0gImdyYXkiLCBsdHkgPSAiZG90dGVkIikNCmF4aXMoMSwgYXQgPSBzZXEoMCwgMzYsIDQpKQ0KYGBgDQoNCiMgSy1NZWFucyBDbHVzdGVyaW5nIEFuYWx5c2lzDQoNCkstTWVhbnMgY2x1c3RlcmluZyBpcyBhIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtIHRoYXQgaXMgdXNlZCB0byBwYXJ0aXRpb24gYSBkYXRhc2V0IGludG8gZGlzdGluY3QgZ3JvdXBzIG9yIGNsdXN0ZXJzIGJhc2VkIG9uIHRoZWlyIHNpbWlsYXJpdHkuIEl0IGlzIG9mdGVuIHVzZWQgdG8gaWRlbnRpZnkgbmF0dXJhbCBncm91cGluZ3Mgd2l0aGluIGRhdGEgYW5kIHNpbXBsaWZ5IGRhdGEgaW50ZXJwcmV0YXRpb24uIEZvciB0aGlzIHByb2plY3QsIEkgd2lsbCBwZXJmb3JtIEstTWVhbnMgQ2x1c3RlcmluZyB0byBncm91cCBwbGF5ZXJzIHRvZ2V0aGVyLg0KDQojIyBEZXRlcm1pbmluZyB0aGUgIksiDQoNCkkgd2lsbCBiZWdpbiB0aGlzIHByb2Nlc3MgYnkgZGV0ZXJtaW5pbmcgdGhlIGFtb3VudCBvZiBjbHVzdGVycyBuZWVkZWQgZm9yIHRoZSBhbmFseXNpcy4gQmVsb3cgaXMgYW4gZWxib3cgcGxvdCB0byB2aXN1YWxpemUgdGhlIG51bWJlciBvZiBjbHVzdGVycyBuZWVkZWQuIFRoZSBudW1iZXIgYWxvbmcgdGhlIHgtYXhpcyB3aGVyZSBhICJraW5rIiBhcHBlYXJzIGluIHRoZSBncmFwaCBpcyBhbiBpbmRpY2F0b3Igb2Ygd2hlcmUgdGhlIGNsdXN0ZXIgY3V0b2ZmIHNob3VsZCBiZQ0KDQpgYGB7ciBlbGJvdyBwbG90LCBpbmNsdWRlID0gVFJVRX0NCmttZWFucy5kYXRhIDwtIHN1YnNldChudW0uZGF0YSwgc2VsZWN0ID0gLVJrKQ0Ka21lYW5zLnNjYWxlZCA8LSBhcy5kYXRhLmZyYW1lKHNjYWxlKGttZWFucy5kYXRhKSkNCg0Kd3NzID0gTlVMTA0KayA9IDEwDQpmb3IoaSBpbiAxOmspew0KICB3c3NbaV0gPSBrbWVhbnMoa21lYW5zLnNjYWxlZCwgaSwgMSkkdG90LndpdGhpbnNzDQp9DQoNCnBsb3QoMTprLCB3c3MsIHR5cGUgPSAiYiIsDQogICAgIGNvbCA9ICJyZWQiLA0KICAgICB4bGFiID0gIk51bWJlciBvZiBDbHVzdGVycyIsDQogICAgIHlsYWIgPSAiV2l0aGluIFN1bXMgb2YgU3F1YXJlcyIsDQogICAgIG1haW4gPSAiRWxib3cgUGxvdCBmb3IgU2VsZWN0aW5nIE9wdGltYWwgTnVtYmVyIG9mIENsdXN0ZXJzIikNCmBgYA0KDQpBcyBzaG93biBhYm92ZSwgdGhlIGVsYm93IGFwcGVhcnMgdG8gYmUgYXQgayA9IDIuIFRvIGNvbmZpcm0gb3IgZGVueSB0aGlzLCBiZWxvdyBpcyBhIHNpbGhvdWV0dGUgcGxvdA0KDQpgYGB7ciBzaWxob3VldHRlIHBsb3QsIGluY2x1ZGUgPSBUUlVFfQ0KZnZpel9uYmNsdXN0KGttZWFucy5zY2FsZWQsIEZVTiA9IGhjdXQsIG1ldGhvZCA9ICJzaWxob3VldHRlIikNCmBgYA0KDQpUaGUgc2lsaG91ZXR0ZSBwbG90IGNvbmZpcm1zIHRoYXQgdGhlIGlkZWFsIG51bWJlciBvZiBjbHVzdGVycyBmb3IgdGhlIGFuYWx5c2lzIGlzIGsgPSAqKjIqKg0KDQojIyBDbHVzdGVyaW5nDQoNCk5leHQsIEkgd2lsbCBzcGxpdCB0aGUgZGF0YXNldCBpbnRvIHR3byBkaXN0aW5jdCBjbHVzdGVycy4gVGhlIHNjYXR0ZXJwbG90IGJlbG93IHNob3dzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBgUFRTYCBhbmQgYEZHUGAsIHdpdGggYSB0aGUgY2x1c3RlcnMgc2hvd24gYnkgdGhlIGNvbG9yIG9mIGVhY2ggcG9pbnQuDQoNCmBgYHtyIGsyLCBpbmNsdWRlID0gVFJVRX0NCmsyIDwtIGttZWFucyh4ID0ga21lYW5zLnNjYWxlZCwNCiAgICAgICAgICAgICBjZW50ZXJzID0gMiwNCiAgICAgICAgICAgICBpdGVyLm1heCA9IDEwLA0KICAgICAgICAgICAgIG5zdGFydCA9IDI1LA0KICAgICAgICAgICAgIGFsZ29yaXRobSA9ICJMbG95ZCIsDQogICAgICAgICAgICAgdHJhY2UgPSBGQUxTRSkNCg0Ka21lYW5zLnNjYWxlZCRncm91cCA9IGFzLmZhY3RvcihrMiRjbHVzdGVyKQ0KDQpwbG90KGttZWFucy5zY2FsZWQkUFRTLCBrbWVhbnMuc2NhbGVkJEZHUCwNCiAgICAgcGNoID0gMTksDQogICAgIGNvbCA9IGZhY3RvcihrbWVhbnMuc2NhbGVkJGdyb3VwKSwNCiAgICAgeGxhYiA9ICJQb2ludHMgU2NvcmVkIChzY2FsZWQpIiwNCiAgICAgeWxhYiA9ICJGaWVsZCBHb2FsIFBlcmNlbnRhZ2UgKHNjYWxlZCkiLA0KICAgICBtYWluID0gIkNsdXN0ZXJpbmcgUGVyZm9ybWFuY2UgVmlzdWFsIENoZWNrIikNCg0KbGVnZW5kKCJib3R0b21yaWdodCIsDQogICAgICAgbGVnZW5kID0gbGV2ZWxzKGZhY3RvcihrbWVhbnMuc2NhbGVkJGdyb3VwKSksDQogICAgICAgcGNoID0gMTksDQogICAgICAgY29sID0gMTpsZW5ndGgobGV2ZWxzKGttZWFucy5zY2FsZWQkZ3JvdXApKSkNCmBgYA0KDQpUaGUgc2NhdHRlcnBsb3QgYWJvdmUgc2hvd3MgaG93IHRoZSBkYXRhc2V0IGlzIHNwbGl0IGludG8gdHdvIGRpc3RpbmN0IGdyb3Vwcy4NCg0KIyBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpDQoNClByaW5jaXBsZSBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkgaXMgYSB0b29sIHRvIHJlZHVjZSB0aGUgZGltZW5zaW9uYWxpdHkgb2YgdGhlIGRhdGFzZXQgd2hpbGUgcmV0YWluaW5nIGFzIG11Y2ggdmFyaWFiaWxpdHkgYXMgcG9zc2libGUuIFRoZSBzdWJzZXR0ZWQgbnVtZXJpY2FsIGRhdGFzZXQgaGFzIDI3IHZhcmlhYmxlcy4gVGhlcmVmb3JlLCB3ZSB3YW50IHRvIHJlZHVjZSB0aGlzIGRpbWVuc2lvbiB0byBhIHNtYWxsZXIgbnVtYmVyLCB3aGlsZSBub3QgbG9zaW5nIGFueSB2YWx1YWJsZSBpbmZvcm1hdGlvbi4NCg0KQmVsb3cgaXMgdGhlIGZhY3RvciBsb2FkaW5nIGZvciB0aGUgZml0dGVkIFBDQS4gDQoNCmBgYHtyIHBjYSBjcmVhdGlvbiwgaW5jbHVkZSA9IFRSVUV9DQpsb2cubnVtLmRhdGEgPSBsb2coa21lYW5zLmRhdGEgKyAxKQ0KDQpsb2cucGNhIDwtIHByY29tcChsb2cubnVtLmRhdGEsIGNlbnRlciA9IFRSVUUsIHNjYWxlID0gVFJVRSkNCg0Ka2FibGUocm91bmQobG9nLnBjYSRyb3RhdGlvbiwgMiksDQogICAgICBjYXB0aW9uID0gIlBDQSBMb2FkaW5ncyIpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiLCAicmVzcG9uc2l2ZSIpLCBmdWxsX3dpZHRoID0gRkFMU0UpDQpgYGANCg0KIyMgRGV0ZXJtaW5pbmcgdGhlIEFtb3VudCBvZiBrZXB0IFBDQSdzDQoNClRoZSBvYmplY3Qgb2YgUENBIGlzIHRvIHJlZHVjZSB0aGUgZGltZW5zaW9uIHdpdGhvdXQgbG9zaW5nIGEgc2lnbmlmaWNhbnQgYW1vdW50IG9mIGluZm9ybWF0aW9uLiBUaGUgZm9sbG93aW5nIHRhYmxlIGdpdmVzIHRoZSBpbXBvcnRhbmNlIG9mIHRoZSBwcmluY2lwYWwgY29tcG9uZW50cw0KDQpgYGB7ciBwY2EsIGluY2x1ZGUgPSBUUlVFfQ0Ka2FibGUoc3VtbWFyeShsb2cucGNhKSRpbXBvcnRhbmNlLA0KICAgICAgY2FwdGlvbiA9ICJQcmluY2lwbGUgQ29tcG9uZW50IEltcG9ydGFuY2UiKSAlPiUNCiAgIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIsICJyZXNwb25zaXZlIiksIGZ1bGxfd2lkdGggPSBGQUxTRSkNCmBgYA0KDQpUTyB2aXN1YWxpemUgdGhlc2UgUENBJ3MsIGJlbG93IGlzIGEgc2NyZWUgcGxvdA0KDQpgYGB7ciBzY3JlZSBwbG90LCBpbmNsdWRlID0gVFJVRX0NCnZhci5leHBsYWluZWQgPC0gbG9nLnBjYSRzZGV2XjIgLyBzdW0obG9nLnBjYSRzZGV2XjIpDQoNCmN1bS52YXIgPC0gY3Vtc3VtKHZhci5leHBsYWluZWQpDQoNCnBsb3QoY3VtLnZhciwgDQogICAgIHR5cGUgPSAiYiIsIA0KICAgICB4bGFiID0gIk51bWJlciBvZiBQcmluY2lwYWwgQ29tcG9uZW50cyIsDQogICAgIHlsYWIgPSAiQ3VtdWx0aXZlIFZhcmlhbmNlIEV4cGxhaW5lZCIsDQogICAgIG1haW4gPSAiU2NyZWUgUGxvdCIpDQphYmxpbmUoaCA9IC45LCBjb2wgPSAicmVkIiwgbHR5ID0gMikNCmBgYA0KDQpBIGNvbW1vbiBjdXRvZmYgZm9yIFBDQSdzIGlzICo5MCUqIG9mIGN1bXVsYXRpdmUgdmFyaWFuY2UgZXhwbGFpbmVkLiBUaGVyZWZvcmUsIHdlIHdhbnQgdG8ga2VlcCB0aGUgZmlyc3QgOCBQQ0Ency4NCg0KQmVsb3cgaXMgdGhlIGtlcHQgUENBIHNjb3JlczoNCg0KYGBge3Iga2VwdCBwY2FzLCBpbmNsdWRlID0gVFJVRX0NCmthYmxlKGxvZy5wY2EkeFsxOjEwLCAxOjhdLA0KICAgICAgY2FwdGlvbiA9ICJUaGUgZmlyc3QgOCBQQyBzY29yZWQgdHJhbnNmb3JtZWQgZnJvbSB0aGUgb3JpZ2luYWwgdmFyaWFibGVzIikgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIsICJyZXNwb25zaXZlIiksIGZ1bGxfd2lkdGggPSBGQUxTRSkNCmBgYA0KDQojIExvY2FsIE91dGxpZXIgRmFjdG9yIChMT0YpDQoNClRoZSBMT0YgaXMgdGhlIG1vc3Qgd2VsbC1rbm93biBsb2NhbCBhbm9tYWx5IGRldGVjdGlvbiBhbGdvcml0aG0gd2hvc2UgaWRlYSBpcyBjYXJyaWVkIG91dCBpbiBtYW55IG5lYXJlc3QtbmVpZ2hib3ItYmFzZWQgYWxnb3JpdGhtcy4gV2hlbiBhIHBvaW50IGlzIGNvbnNpZGVyZWQgYXMgYW4gb3V0bGllciBiYXNlcyBvbiBpdHMgbG9jYWwgbmVpZ2hib3Job29kLCBpdCBpcyBhIGxvY2FsIG91dGxpZXIuIExPRiB3aWxsIGlkZW50aWZ5IGFuIG91dGxpZXIgY29uc2lkZXJpbmcgdGhlIGRlbnNpdHkgb2YgdGhlIG5laWdoYm9yaG9vZC4gTE9GIHBlcmZvcm1zIHdlbGwgd2hlbiB0aGUgZGVuc2l0eSBvZiB0aGUgZGF0YSBpcyBub3QgdGhlIHNhbWUgdGhyb3VnaG91dCB0aGUgZGF0YXNldC4NCg0KIyMgT3V0bGllciBEZWZpbml0aW9uDQoNCkZvciB0aGUgc2FrZSBvZiB0aGlzIGFuYWx5c2lzLCBJIHdpbGwgZGVmaW5lIGFuIG91dGxpZXIgYXMgYW55IHBsYXllciB3aG8gaGFzIGEgY29tYmluZWQgei1zY29yZSBmb3IgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMgaGlnaGVyIHRoYW4gMzoNCg0KLSBgTVBgDQotIGBQVFNgDQotIGBGR1BgDQotIGBYM1BQYA0KLSBgVFJCYA0KLSBgQVNUYA0KLSBgU1RMYA0KLSBgQkxLYA0KLSBgVE9WYA0KDQpBIG5ldyB2YXJpYWJsZSBgT3V0bGllcmAgd2lsbCBiZSBjcmVhdGVkIGZvciBlYWNoIG9ic2VydmF0aW9uLiBJZiB0aGVpciBjb21iaW5lZCB6LXNjb3JlIGlzIGhpZ2hlciB0aGFuIDMsIHRoZWlyIGBPdXRsaWVyYCB2YXJpYWJsZSB3aWxsIGJlICoxKiwgb3RoZXJ3aXNlIGl0IHdpbGwgYmUgKjAqLg0KDQojIyBMT0YgRGlzdHJpYnV0aW9uDQoNClRoZSBkaXN0cmlidXRpb24gb2YgTE9GIHNjb3JlcyBpcyB1c3VhbGx5IHZlcnkgc2tld2VkLiBUbyBjcmVhdGUgYSBiZXR0ZXIgdmlzdWFsIHJlcHJlc2VudGF0aW9uLCBJIHRydW5jYXRlIHRoZSBleHRyZW1lbHkgbGFyZ2UgTE9GIHNjb3Jlcy4gTmV4dCwgSSBleHRyYWN0ZWQgdGhlIExPRiBzY29yZXMgb2YgZWFjaCBwbGF5ZXIgaW4gdGhlIGRhdGFzZXQgYW5kIHN1bW1hcml6ZSB0aGUgZGlzdHJpYnV0aW9uLg0KDQpCZWxvdyBpcyB0aGUgc3VtbWFyeToNCg0KYGBge3IgbG9mIHN1bW1hcnksIGluY2x1ZGUgPSBUUlVFfQ0KbG9mLmRhdGEgPC0gbmJhLmRhdGEuZmlsdGVyZWRbLCBjKCJNUCIsICJQVFMiLCAiRkdQIiwgIlgzUFAiLCAiVFJCIiwgIkFTVCIsICJTVEwiLCAiQkxLIiwgIlRPViIpXQ0KDQpsb2YuZGF0YSA8LSBzY2FsZShsb2YuZGF0YSkNCg0KbG9mLnNjb3Jlcy41MCA8LSBsb2YobG9mLmRhdGEsIG1pblB0cyA9IDUwKQ0Kc3VtbWFyeShsb2Yuc2NvcmVzLjUwKQ0KYGBgDQoNCmBgYHtyIGxvZiBoaXN0b2dyYW0sIGluY2x1ZGUgPSBUUlVFfQ0KaGlzdChsb2Yuc2NvcmVzLjUwW2xvZi5zY29yZXMuNTAgPCA1XSwNCiAgICAgYnJlYWtzID0gc2VxKDAsIDQsIC4xKSwNCiAgICAgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgTE9GIiwNCiAgICAgeGxpbSA9IGMoMCwgNCksDQogICAgIHhheHQgPSAibiIsDQogICAgIHhsYWIgPSAiTE9GIDwgNSIsDQogICAgIHlsaW0gPSBjKDAsIDI1MCksDQogICAgIHlsYWIgPSAiTnVtYmVyIG9mIFBsYXllcnMiLA0KICAgICBjb2wgPSAiZ3JheSIsDQogICAgIGxhYmVscyA9IFRSVUUpDQphYmxpbmUoaCA9IHNlcSgyNSwgMjUwLCAyNSksIGNvbCA9ICJncmF5IiwgbHR5ID0gImRvdHRlZCIpDQpheGlzKDEsIGF0ID0gc2VxKDAsIDQsIC40KSkNCmBgYA0KDQojIyBEZXRlcm1pbmluZyBMT0YgY3V0b2ZmDQoNCkluIG9yZGVyIGZvciBvdXRsaWVycyB0byBiZSBpZGVudGlmaWVkLCB0aGVyZSBtdXN0IGJlIGEgY29uc2lzdGVudCBjdXRvZmYgcG9pbnQuIFRvIGRldGVybWluZSB0aGUgY3V0b2ZmIHBvaW50LCBJIHdpbGwgZ3JhcGggdGhlIHN1Z2dlc3RlZCBMT0YgY3V0b2ZmcyBhZ2FpbnN0IHRoZSBzdWNjZXNzZnVsIExPRiBjYXRjaGluZyByYXRlLg0KDQpgYGB7ciBsb2YgY3V0b2ZmLCBpbmNsdWRlID0gVFJVRX0NCmxvZi5jdXQgPSBzZXEoMSwgNCwgbGVuZ3RoID0gMjApDQoNCmxvZi5jYXRjaC5yYXRlID0gbnVtZXJpYyhsZW5ndGgobG9mLmN1dCkpDQoNCnZhcnMgPC0gYygiTVAiLCAiUFRTIiwgIkZHUCIsICJYM1BQIiwgIlRSQiIsICJBU1QiLCAiU1RMIiwgIkJMSyIsICJUT1YiKQ0Kei5zY29yZXMgPC0gc2NhbGUobmJhLmRhdGEuZmlsdGVyZWRbLCB2YXJzXSkNCm5iYS5kYXRhLmZpbHRlcmVkJGNvbWJpbmVkLnogPC0gYXBwbHkoei5zY29yZXMsIDEsIGZ1bmN0aW9uKHJvdykgc3FydChzdW0ocm93XjIpKSkNCm5iYS5kYXRhLmZpbHRlcmVkJE91dGxpZXIgPC0gbmJhLmRhdGEuZmlsdGVyZWQkY29tYmluZWQueiA+IDMNCg0KZm9yIChpIGluIDE6bGVuZ3RoKGxvZi5jdXQpKXsNCiAgSUQgPSBsb2Yuc2NvcmVzLjUwID4gbG9mLmN1dFtpXQ0KICBPdXRsaWVyIDwtIG5iYS5kYXRhLmZpbHRlcmVkJE91dGxpZXJbSURdDQogIGxvZi5jYXRjaC5yYXRlW2ldIDwtIHN1bShPdXRsaWVyKSAvIHN1bShJRCkNCn0NCg0KZGVmYXVsdC5yYXRlIDwtIHN1bShuYmEuZGF0YS5maWx0ZXJlZCRPdXRsaWVyKSAvIG5yb3cobmJhLmRhdGEuZmlsdGVyZWQpDQpyZWwuZGV0ZWN0LnJhdGUgPC0gKGxvZi5jYXRjaC5yYXRlIC0gZGVmYXVsdC5yYXRlKSAvIGRlZmF1bHQucmF0ZQ0KDQpwYXIobWZyb3cgPSBjKDEsIDIpKQ0KDQpwbG90KGxvZi5jdXQsIDEwMCpsb2YuY2F0Y2gucmF0ZSwgdHlwZSA9ICJsIiwgY29sID0gInJlZCIsIGx3ZCA9IDIsDQogICAgIHhsYWIgPSAiTE9GIEN1dG9mZiIsDQogICAgIHlsYWIgPSAiTE9GIENhdGNoaW5nIFJhdGUgKCUpIiwNCiAgICAgbWFpbiA9ICJMT0YgQ2F0Y2hpbmcgUmF0ZSBvZiBPdXRsaWVycyIpDQoNCnBsb3QobG9mLmN1dCwgcmVsLmRldGVjdC5yYXRlLCB0eXBlID0gImwiLCBjb2wgPSAicmVkIiwgbHdkID0gMiwNCiAgICAgeGxhYiA9ICJMT0YgQ3V0b2ZmIiwNCiAgICAgeWxhYiA9ICJMT0YgYW5kIHJhbmRvbSBjYXRjaGluZyByYXRlIHJhdGlvIiwNCiAgICAgbWFpbiA9ICJMT0YgY2F0Y2hpbmcgcmF0ZSBpbXByb3ZlbWVudCIpDQpgYGANCg0KVGhlIGFib3ZlIGZpZ3VyZXMgc2hvdyB0aGF0LCB3aXRoIGsgPSAxMDAsIHRoZSBMT0YtYmFzZWQgb3V0bGllci1jYXRjaGluZyByYXRlIGlzIG11Y2ggaGlnaGVyIHRoYW4gdGhlIHJhbmRvbSBndWVzcy4gSSB3aWxsIG5leHQgc2tldGNoIGFuIFJPQyB0byBhc3Nlc3MgdGhlIGdsb2JhbCBwZXJmb3JtYW5jZSBpbiB0ZXJtcyBvZiBjYXRjaGluZyByYXRlLg0KDQpgYGB7ciByb2MsIGluY2x1ZGUgPSBUUlVFfQ0KY2F0ZWdvcnkgPSBhcy5jaGFyYWN0ZXIobmJhLmRhdGEuZmlsdGVyZWQkT3V0bGllcikNCmNhdGVnb3J5IDwtIGlmZWxzZShjYXRlZ29yeSA9PSBUUlVFLCAiMSIsICIwIikNCg0Kcm9jLm9iai5sb2YuNTAgPC0gcm9jKGNhdGVnb3J5LCBsb2Yuc2NvcmVzLjUwLCBsZXZlbHMgPSBjKCIxIiwgIjAiKSwgZGlyZWN0aW9uID0gIj4iKQ0KDQpzZW4ubG9mLjUwID0gcm9jLm9iai5sb2YuNTAkc2Vuc2l0aXZpdGllcw0KZm5yLmxvZi41MCA9IDEgLSByb2Mub2JqLmxvZi41MCRzcGVjaWZpY2l0aWVzDQoNCnBhcih0eXBlID0gInMiKQ0KcGxvdChmbnIubG9mLjUwLCBzZW4ubG9mLjUwLCB0eXBlID0gImwiLCBsd2QgPSAyLCBjb2wgPSAicmVkIiwNCiAgICAgeGxpbSA9IGMoMCwgMSksDQogICAgIHlsaW0gPSBjKDAsIDEpLA0KICAgICB4bGFiID0gIjEgLSBTcGVjaWZpY2l0eSIsDQogICAgIHlsYWIgPSAiU2Vuc2l0aXZpdHkiLA0KICAgICBtYWluID0gIlJPQyBDdXJ2ZXMgb2YgTE9GIERldGVjdGlvbiIpDQoNCnNlZ21lbnRzKDAsIDAsIDEsIDEsIGx3ZCA9IDEsIGNvbCA9ICJibGFjayIsIGx0eSA9IDIpDQphdWMuNTAgPSByb2Mub2JqLmxvZi41MCRhdWMNCnRleHQoMC44NywgMC4yMCwgcGFzdGUoIkFVQyAoNTApID0gIiwgcm91bmQoYXVjLjUwLCA0KSksIGNvbCA9ICJyZWQiLCBjZXggPSAwLjcsIGFkaiA9IDEpDQpgYGANCg0KIyMgVHVuaW5nIEh5cGVycGFyYW1ldGVyIGsNCg0KVGhlIExPRiBkZWZpbmVzIGxvY2FsIG91dGxpZXJzLiBUaGUgTE9GIHNjb3JlcyB3aWxsIGJlIGRlcGVuZGluZyBvbiB0aGUgdmFsdWUgb2YgJGskICh0aGUgbnVtYmVyIG9mIG5lYXJlc3QgcGxheWVycyBvZiBldmVyeSBwbGF5ZXIpLiBGb3IgaWxsdXN0cmF0aW9uIHB1cnBvc2VzLCBJIHdpbGwgdXNlIDMgYWx0ZXJuYXRlIHZhbHVlcyBvZiBrIGluIGFuIGF0dGVtcHQgdG8gaW1wcm92ZSB0aGUgQVVDIHZhbHVlLg0KDQpgYGB7ciBhbHQgayAxMDAsIGluY2x1ZGUgPSBUUlVFfQ0KbG9mLnNjb3Jlcy4xMDAgPC0gbG9mKGxvZi5kYXRhLCBtaW5QdHMgPSAxMDApDQoNCnJvYy5vYmoubG9mLjEwMCA8LSByb2MoY2F0ZWdvcnksIGxvZi5zY29yZXMuMTAwLCBsZXZlbHMgPSBjKCIxIiwgIjAiKSwgZGlyZWN0aW9uID0gIj4iKQ0KDQpzZW4ubG9mLjEwMCA9IHJvYy5vYmoubG9mLjEwMCRzZW5zaXRpdml0aWVzDQpmbnIubG9mLjEwMCA9IDEgLSByb2Mub2JqLmxvZi4xMDAkc3BlY2lmaWNpdGllcw0KYGBgDQoNCmBgYHtyIGFsdCBrIDIwMCwgaW5jbHVkZSA9IFRSVUV9DQpsb2Yuc2NvcmVzLjIwMCA8LSBsb2YobG9mLmRhdGEsIG1pblB0cyA9IDIwMCkNCg0Kcm9jLm9iai5sb2YuMjAwIDwtIHJvYyhjYXRlZ29yeSwgbG9mLnNjb3Jlcy4yMDAsIGxldmVscyA9IGMoIjEiLCAiMCIpLCBkaXJlY3Rpb24gPSAiPiIpDQoNCnNlbi5sb2YuMjAwID0gcm9jLm9iai5sb2YuMjAwJHNlbnNpdGl2aXRpZXMNCmZuci5sb2YuMjAwID0gMSAtIHJvYy5vYmoubG9mLjIwMCRzcGVjaWZpY2l0aWVzDQpgYGANCg0KYGBge3IgYWx0IGsgNDAwLCBpbmNsdWRlID0gVFJVRX0NCmxvZi5zY29yZXMuNDAwIDwtIGxvZihsb2YuZGF0YSwgbWluUHRzID0gNDAwKQ0KDQpyb2Mub2JqLmxvZi40MDAgPC0gcm9jKGNhdGVnb3J5LCBsb2Yuc2NvcmVzLjQwMCwgbGV2ZWxzID0gYygiMSIsICIwIiksIGRpcmVjdGlvbiA9ICI+IikNCg0Kc2VuLmxvZi40MDAgPSByb2Mub2JqLmxvZi40MDAkc2Vuc2l0aXZpdGllcw0KZm5yLmxvZi40MDAgPSAxIC0gcm9jLm9iai5sb2YuNDAwJHNwZWNpZmljaXRpZXMNCmBgYA0KDQpgYGB7ciBhbHQgYXVjLCBpbmNsdWRlID0gVFJVRX0NCnBhcih0eXBlID0gInMiKQ0KY29sb3JzID0gYygicmVkIiwgImJsdWUiLCAib3JhbmdlIiwgImRhcmtncmVlbiIpDQoNCnBsb3QoZm5yLmxvZi41MCwgc2VuLmxvZi41MCwgdHlwZSA9ICJsIiwgbHdkID0gMiwgY29sID0gY29sb3JzWzFdLA0KICAgICB4bGltID0gYygwLCAxKSwNCiAgICAgeWxpbSA9IGMoMCwgMSksDQogICAgIHhsYWIgPSAiMSAtIFNwZWNpZmljaXR5IiwNCiAgICAgeWxhYiA9ICJTZW5zaXRpdml0eSIsDQogICAgIG1haW4gPSAiUk9DIEN1cnZlcyBvZiBMT0YgRGV0ZWN0aW9uIENvbXBhcmlzb24iKQ0KDQpsaW5lcyhmbnIubG9mLjEwMCwgc2VuLmxvZi4xMDAsIGx3ZCA9IDIsIGx0eSA9IDIsIGNvbCA9IGNvbG9yc1syXSkNCmxpbmVzKGZuci5sb2YuMjAwLCBzZW4ubG9mLjIwMCwgbHdkID0gMSwgY29sID0gY29sb3JzWzNdKQ0KbGluZXMoZm5yLmxvZi40MDAsIHNlbi5sb2YuNDAwLCBsd2QgPSAxLCBjb2wgPSBjb2xvcnNbNF0pDQoNCnNlZ21lbnRzKDAsIDAsIDEsIDEsIGx3ZCA9IDEsIGNvbCA9ICJibGFjayIsIGx0eSA9IDIpDQpsZWdlbmQoImJvdHRvbXJpZ2h0IiwgYygiTE9GLjUwIiwgIkxPRi4xMDAiLCAiTE9GLjIwMCIsICJMT0YuNDAwIiksDQogICAgICAgY29sID0gY29sb3JzLCBsd2QgPSBjKDIsIDIsIDEsIDEsIDEpLA0KICAgICAgIGx0eSA9IGMoMSwgMiwgMSwgMSwgMiksIGJ0eSA9ICJuIiwgY2V4ID0gMC43KQ0KDQpBVUMuNTAgPSByb2Mub2JqLmxvZi41MCRhdWMNCkFVQy4xMDAgPSByb2Mub2JqLmxvZi4xMDAkYXVjDQpBVUMuMjAwID0gcm9jLm9iai5sb2YuMjAwJGF1Yw0KQVVDLjQwMCA9IHJvYy5vYmoubG9mLjQwMCRhdWMNCg0KdGV4dCgwLjg3LCAwLjE3LCBwYXN0ZSgiQVVDICg1MCkgPSAiLCByb3VuZChBVUMuNTAsIDQpKSwgY29sID0gY29sb3JzWzFdLCBjZXggPSAwLjcsIGFkaiA9IDEpDQp0ZXh0KDAuODcsIDAuMTIsIHBhc3RlKCJBVUMgKDEwMCkgPSAiLCByb3VuZChBVUMuMTAwLCA0KSksIGNvbCA9IGNvbG9yc1syXSwgY2V4ID0gMC43LCBhZGogPSAxKQ0KdGV4dCgwLjg3LCAwLjA3LCBwYXN0ZSgiQVVDICgyMDApID0gIiwgcm91bmQoQVVDLjIwMCwgNCkpLCBjb2wgPSBjb2xvcnNbM10sIGNleCA9IDAuNywgYWRqID0gMSkNCnRleHQoMC44NywgMC4wMiwgcGFzdGUoIkFVQyAoNDAwKSA9ICIsIHJvdW5kKEFVQy40MDAsIDQpKSwgY29sID0gY29sb3JzWzRdLCBjZXggPSAwLjcsIGFkaiA9IDEpDQpgYGANCg0KQXMgc2hvd24gYWJvdmUsIHRoZSBiZXN0ICRrJCBmb3IgdGhlIGdpdmVuIGRhdGEgZnJvbSB0aGUgc2VsZWN0aW9uIGlzIDQwMCwgYXMgaXQgaGFzIHRoZSBoaWdoZXN0IEFVQyB2YWx1ZS4gSG93ZXZlciwgaXQgaXMgYWR2aXNlZCB0byBiZSBjYXJlZnVsIGNob29zaW5nIGEgJGskIHZhbHVlIHRoYXQgaXMgZGVlbWVkIHRvIGJlICJ0b28gY2xvc2UiIHRvIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIHdpdGhpbiB0aGUgZGF0YXNldC4g