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]
Feature
Engineering
Here are some additional feature engineering that is needed for
ananysis found later in the report:
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)
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
|
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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))

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.
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.
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))

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)

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