EDA
Familiarization with Sales datasets extracted from excelbi
analytics requires understanding of dataset composition, dimensions,
column types, NA or Null value count, etc.
Data Composition
str(df_1k)
'data.frame': 1000 obs. of 14 variables:
$ Region : chr "Middle East and North Africa" "North America" "Middle East and North Africa" "Asia" ...
$ Country : chr "Libya" "Canada" "Libya" "Japan" ...
$ Item.Type : chr "Cosmetics" "Vegetables" "Baby Food" "Cereal" ...
$ Sales.Channel : chr "Offline" "Online" "Offline" "Offline" ...
$ Order.Priority: chr "M" "M" "C" "C" ...
$ Order.Date : chr "10/18/2014" "11/7/2011" "10/31/2016" "4/10/2010" ...
$ Order.ID : int 686800706 185941302 246222341 161442649 645713555 683458888 679414975 208630645 266467225 118598544 ...
$ Ship.Date : chr "10/31/2014" "12/8/2011" "12/9/2016" "5/12/2010" ...
$ Units.Sold : int 8446 3018 1517 3322 9845 9528 2844 7299 2428 4800 ...
$ Unit.Price : num 437.2 154.06 255.28 205.7 9.33 ...
$ Unit.Cost : num 263.33 90.93 159.42 117.11 6.92 ...
$ Total.Revenue : num 3692591 464953 387260 683335 91854 ...
$ Total.Cost : num 2224085 274427 241840 389039 68127 ...
$ Total.Profit : num 1468506 190526 145420 294296 23726 ...
str(df_100k)
'data.frame': 100000 obs. of 14 variables:
$ Region : chr "Middle East and North Africa" "Central America and the Caribbean" "Sub-Saharan Africa" "Sub-Saharan Africa" ...
$ Country : chr "Azerbaijan" "Panama" "Sao Tome and Principe" "Sao Tome and Principe" ...
$ Item.Type : chr "Snacks" "Cosmetics" "Fruits" "Personal Care" ...
$ Sales.Channel : chr "Online" "Offline" "Offline" "Online" ...
$ Order.Priority: chr "C" "L" "M" "M" ...
$ Order.Date : chr "10/8/2014" "2/22/2015" "12/9/2015" "9/17/2014" ...
$ Order.ID : int 535113847 874708545 854349935 892836844 129280602 473105037 754046475 772153747 847788178 471623599 ...
$ Ship.Date : chr "10/23/2014" "2/27/2015" "1/18/2016" "10/12/2014" ...
$ Units.Sold : int 934 4551 9986 9118 5858 1149 7964 6307 8217 2758 ...
$ Unit.Price : num 152.58 437.2 9.33 81.73 668.27 ...
$ Unit.Cost : num 97.44 263.33 6.92 56.67 502.54 ...
$ Total.Revenue : num 142510 1989697 93169 745214 3914726 ...
$ Total.Cost : num 91009 1198415 69103 516717 2943879 ...
$ Total.Profit : num 51501 791282 24066 228497 970846 ...
kable(as.data.frame(table(df_1k$Region)) %>% arrange(desc(Freq)),
caption = "Frequency Region df_1k")
Frequency Region df_1k
| Europe |
267 |
| Sub-Saharan Africa |
262 |
| Middle East and North Africa |
138 |
| Asia |
136 |
| Central America and the Caribbean |
99 |
| Australia and Oceania |
79 |
| North America |
19 |
kable(as.data.frame(table(df_100k$Region)) %>% arrange(desc(Freq)),
caption = "Frequency Region df_100k")
Frequency Region df_100k
| Sub-Saharan Africa |
26019 |
| Europe |
25877 |
| Asia |
14547 |
| Middle East and North Africa |
12580 |
| Central America and the Caribbean |
10731 |
| Australia and Oceania |
8113 |
| North America |
2133 |
kable(as.data.frame(table(df_1k$Item.Type )) %>% arrange(desc(Freq)),
caption = "Frequency Item.Type df_1k")
Frequency Item.Type df_1k
| Beverages |
101 |
| Vegetables |
97 |
| Office Supplies |
89 |
| Baby Food |
87 |
| Personal Care |
87 |
| Snacks |
82 |
| Cereal |
79 |
| Clothes |
78 |
| Meat |
78 |
| Household |
77 |
| Cosmetics |
75 |
| Fruits |
70 |
kable(as.data.frame(table(df_100k$Item.Type )) %>% arrange(desc(Freq)),
caption = "Frequency Item Type 100k")
Frequency Item Type 100k
| Office Supplies |
8426 |
| Cereal |
8421 |
| Baby Food |
8407 |
| Cosmetics |
8370 |
| Personal Care |
8364 |
| Meat |
8320 |
| Snacks |
8308 |
| Clothes |
8304 |
| Vegetables |
8282 |
| Household |
8278 |
| Fruits |
8262 |
| Beverages |
8258 |
kable(as.data.frame(table(df_1k$Sales.Channel )) %>% arrange(desc(Freq)),
caption = "Frequency Sales Channel 1k")
Frequency Sales Channel 1k
| Offline |
520 |
| Online |
480 |
kable(as.data.frame(table(df_100k$Sales.Channel )) %>% arrange(desc(Freq)),
caption = "Frequency Sales Channel 100k")
Frequency Sales Channel 100k
| Online |
50054 |
| Offline |
49946 |
var_label(df_1k)
$Region
NULL
$Country
NULL
$Item.Type
NULL
$Sales.Channel
NULL
$Order.Priority
NULL
$Order.Date
NULL
$Order.ID
NULL
$Ship.Date
NULL
$Units.Sold
NULL
$Unit.Price
NULL
$Unit.Cost
NULL
$Total.Revenue
NULL
$Total.Cost
NULL
$Total.Profit
NULL
var_label(df_100k)
$Region
NULL
$Country
NULL
$Item.Type
NULL
$Sales.Channel
NULL
$Order.Priority
NULL
$Order.Date
NULL
$Order.ID
NULL
$Ship.Date
NULL
$Units.Sold
NULL
$Unit.Price
NULL
$Unit.Cost
NULL
$Total.Revenue
NULL
$Total.Cost
NULL
$Total.Profit
NULL
# Dimensions
dim_1k_tmp<-dim(df_1k)
dim_100k_tmp<-dim(df_100k)
# Class
class_1k_tmp<-sapply(df_1k,class)
class_100k_tmp<-sapply(df_100k,class)
column_name_1k_tmp <- "Order.ID"
# Count the number of duplicates in the specified column
num_duplicates_1k_tmp <- sum(duplicated(df_1k[[column_name_1k_tmp]]) |
duplicated(df_1k[[column_name_1k_tmp]],
fromLast = TRUE))
column_name_100k_tmp <- "Order.ID"
# Count the number of duplicates in the specified column
num_duplicates_100k_tmp <- sum(duplicated(df_100k[[column_name_100k_tmp]]) |
duplicated(df_100k[[column_name_100k_tmp]],
fromLast = TRUE))
na_null_cnt_tmp<-(sum(colSums(is.na(df_1k) | is.null(df_1k)))+
sum(colSums(is.na(df_100k) | is.null(df_100k))))
region_tmp<-unique(df_1k$Region)
country_len_tmp<-length(unique(df_1k$Country))
The dataset of size 1000 is stored to df_1k and the
dataset size 100,000 is stored to df_100k
df_1k dimensions is 1000 rows and 14 columns.
df_100k dimensions is 100000 rows and 14 columns.
- The column types for df_1k are character, character, character,
character, character, character, integer, character, integer, numeric,
numeric, numeric, numeric, numeric
- The column types for df_100k are character, character, character,
character, character, character, integer, character, integer, numeric,
numeric, numeric, numeric, numeric
- Notable categories include
Order.Date and Ship.Date the only date
valued columns, but set to type chr and may need
converting.
Order.ID is compose of unique values with 0 duplicates
found in the df_1k data and 0 found in the
df_100k data.
Region and Country both of which define
location
Item.Type for type of item sold.
Sales.Channel defines sales method as an online or
offline purchase, or e-purchase vs in-store.
Order.Priority which has a ranking of severity.
- Attributes labeled with
Total that are calculated
values.
- Using the
length() functions we see that 185 countries
are listed in the data.
- Using the
table function we see: -Of the
Regions listed Sub-Saharan Africa and
Europe is most frequented.
- For
df_1k Beverages and
Vegetables is most frequented, however with
df_100k Office Supplies and
Cereals is.
- For
df_1k more purchases are done Offline
while for df_100k more is done Online Albiet
by a small margin in both cases.
With respect to dependencies, the formulas below highlight the
dependency that exists with calculated variables with the label
Total in there Attribute name.
\(Total.Cost=Units.Sold\times
Unit.Cost\) making Total.Cost dependent on Units.Sold and Unit
Cost \(Total.Revenue=Units.Sold\times
Unit.Price\) making Total.Revenue dependent on Units.Sold and
Unit.Price \(Total.Profit=Total.Revenue-Total.Cost\)
making the subsequent totals above the dependent variables for
Total.Profit
The Order.Priority have a dependency based on ranking of M, C, H, L
Which is Critical, High, Medium, Low in ascending order.
Date values are dependent in interpretation, with calculation of
Order.Date and Ship.Date being a factor of
performance or timeliness.
Correlation and Skewness
describe(df_1k%>%
dplyr::select(contains("Unit") | contains("Total"))) %>%
dplyr::select(c(mean,sd,min,max,range,se,skew))
describe(df_100k%>%
dplyr::select(contains("Unit") | contains("Total"))) %>%
dplyr::select(c(mean,sd,min,max,range,se,skew))
plot_numeric_1k<-df_1k%>%
dplyr::select(contains("Unit") | contains("Total")) %>%
gather(variable, value, 1:6) %>%
ggplot(aes(value)) +
facet_wrap(~variable, scales = "free") +
geom_density(fill = "lightgreen", alpha=0.9, color="lightgreen") +
geom_histogram(aes(y=after_stat(density)), alpha=0.2, fill = "lightblue",
color="darkred", position="identity", bins = 40)
plot_numeric_100k<-df_100k%>%
dplyr::select(contains("Unit") | contains("Total")) %>%
gather(variable, value, 1:6) %>%
ggplot(aes(value)) +
facet_wrap(~variable, scales = "free") +
geom_density(fill = "lightgreen", alpha=0.9, color="lightgreen") +
geom_histogram(aes(y=after_stat(density)), alpha=0.2, fill = "pink",
color="darkred", position="identity", bins = 40)
grid.arrange(plot_numeric_1k,plot_numeric_100k,ncol=1)

par(mfrow = c(1, 2), mar = c(0, 0, 3, 0))
plot_corr_1k <- cor(df_1k %>%
dplyr::select(contains("Unit") | contains("Total")))
corrplot(plot_corr_1k, tl.col = 'darkgreen', diag = FALSE, type = "lower",
order = "hclust", addCoef.col = "darkgreen",
title = "1k",mar=c(0,0,1,0))
# Plot correlation for df_100k
plot_corr_100k <- cor(df_100k %>%
dplyr::select(contains("Unit") | contains("Total")))
corrplot(plot_corr_100k, tl.col = 'darkgreen', diag = FALSE, type = "lower",
order = "hclust", addCoef.col = "darkgreen",
title = "100k",mar=c(0,0,1,0))

NA
NA
Skewness is a measure of symmetry, therefore the values near zero,
despite one being negative, did not particularly stand out, however for
both size data sets, Total - Revenue, Cost and Profit all are right
skewed. Skewness = 0: perfect symmetry. Skewness < 0: Negatively is
left skewed or has a tail. Skewness > 0: Positive is right skewed or
has a right tail.
Concern is not too big with respect to these values as for our model
I can try to normalize it as much as possible.
Correlation does more than just support the obvious relationships
noted earlier, rather it help identify if we have multicollinearity.
Multicollinearity occurs when two or more independent variables in a
data frame have a high correlation with one another, and can cause
issues with stability and size of an estimated regression coefficient,
which in turn makes unreliable inferences for our predictor
variables.
Of our variables, Unit.Cost and Total.Profit have the highest
correlation, while Unit.-Cost,Price and Sold show the weakest. The way
to interpret the correlation is understanding that the higher the
absolute value of a correlation coefficient is, the stronger the
relationship.
Because I suspect multicollinearity, I’ve chosen to not create my
second model off the numeric values, rather I am opting to make a
decision tree using one of the categorical values, whose variables I’ve
set to factors earlier. For my first I plan to do a simple regression
but I suspect normalizing it will not impact the data much.
Model Selection and logic
Simple Linear regression
For my first model I will choose a simple linear regression after
normalizing the data.
Normalization
Statology
provides a great walk through for normalization. Normalization ensures
all variables contribute equally to a model vs having one contribute
more because of its value.
# Function for normalization
min_max_norm <- function(x) {
(x - min(x)) / (max(x) - min(x))
}
# Run function using lapply and only with the numeric values
norm_1k <- as.data.frame(lapply(df_1k %>%
keep(is.numeric) , min_max_norm))
norm_100k <- as.data.frame(lapply(df_100k %>%
keep(is.numeric) , min_max_norm))
#stats
describe(norm_1k, fast=TRUE) %>%
dplyr::select(c(-vars,-n))
#distribution
norm_1k %>%
gather(variable, value, 1:6) %>%
ggplot(aes(value)) +
facet_wrap(~variable, scales = "free") +
geom_density(fill = "green", alpha=0.9, color="darkgreen") +
geom_histogram(aes(y=after_stat(density)), alpha=0.2, fill = "pink",
color="darkred", position="identity", bins = 40) +
theme_minimal()

#stats
describe(norm_100k, fast=TRUE) %>%
dplyr::select(c(-vars,-n))
#distribution
norm_100k %>%
gather(variable, value, 1:6) %>%
ggplot(aes(value)) +
facet_wrap(~variable, scales = "free") +
geom_density(fill = "green", alpha=0.9, color="darkgreen") +
geom_histogram(aes(y=after_stat(density)), alpha=0.2, fill = "pink",
color="darkred", position="identity", bins = 40) +
theme_minimal()
setwd("C:/Users/xusef/Documents/GitableGabe/DATA_622")

Model
set.seed(777)
simp_reg_sample_1k <- norm_1k$Total.Revenue %>%
createDataPartition(p = 0.8, list = FALSE)
simp1k_train <- norm_1k[simp_reg_sample_1k, ]
simp1k_test <- norm_1k[-simp_reg_sample_1k, ]
simp_reg_sample_100k <- norm_100k$Total.Revenue %>%
createDataPartition(p = 0.8, list = FALSE)
simp100k_train <- norm_100k[simp_reg_sample_100k, ]
simp100k_test <- norm_100k[-simp_reg_sample_100k, ]
simptrain1k_model<- lm(Total.Revenue~Units.Sold, data=simp1k_train )
summary(simptrain1k_model)
Call:
lm(formula = Total.Revenue ~ Units.Sold, data = simp1k_train)
Residuals:
Min 1Q Median 3Q Max
-0.38438 -0.11995 -0.02443 0.08641 0.59640
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.007133 0.013649 -0.523 0.601
Units.Sold 0.411394 0.023203 17.730 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.1925 on 798 degrees of freedom
Multiple R-squared: 0.2826, Adjusted R-squared: 0.2817
F-statistic: 314.4 on 1 and 798 DF, p-value: < 2.2e-16
# Make predictions
prediction <- simptrain1k_model %>% predict(simp1k_test)
class(simp1k_test$Total.Revenue)
[1] "numeric"
# Model performance
data.frame(
MAE = mae(prediction, simp1k_test$Total.Revenue),
RMSE = RMSE(prediction, simp1k_test$Total.Revenue),
R2 = R2(prediction, simp1k_test$Total.Revenue)
)
simptrain100k_model<- lm(Total.Revenue~Units.Sold, data=simp100k_train )
summary(simptrain100k_model)
Call:
lm(formula = Total.Revenue ~ Units.Sold, data = simp100k_train)
Residuals:
Min 1Q Median 3Q Max
-0.38676 -0.11840 -0.02697 0.08723 0.59917
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.0007499 0.0013246 -0.566 0.571
Units.Sold 0.4015808 0.0022946 175.009 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.1873 on 79999 degrees of freedom
Multiple R-squared: 0.2769, Adjusted R-squared: 0.2768
F-statistic: 3.063e+04 on 1 and 79999 DF, p-value: < 2.2e-16
# Make predictions
prediction <- simptrain100k_model %>% predict(simp100k_test)
class(simp100k_test$Total.Revenue)
[1] "numeric"
# Model performance
data.frame(
MAE = mae(prediction, simp100k_test$Total.Revenue),
RMSE = RMSE(prediction, simp100k_test$Total.Revenue),
R2 = R2(prediction, simp100k_test$Total.Revenue)
)
The steps taken for a simple regression were splitting the normalized
data into a train and test only using the numeric values. Using
Units.Sold as the predictor variable I run my models. The
R-squared value of 0.2826 and 0.2769 shows these are terrible models,
but that was expected from the EDA. The models accuracy is about 27%-28%
which just shows it was not a good model.
Decision Tree
To simplify decision tree, the approach I will use a attribute with a
lower number of unique values, but I’ve chosen not to
go with Sales.Channel, since this model is very much random and I hope
to implement a decision tree with more than 2 possible outcomes for
analysis. With this in mind I will make a decision tree model using
Region, which I already suspect will create an outcome where Europe and
Sub-Saharan Africa are the most likely the regions that will be
highlighted in my decision tree, because of its high frequency in the
data frames. I will use rpart
for my decision tree. NOTE: this will be my first time using rpart, so I
am curious on the results.
#split into test/train set
#For df_1k
set.seed(2341)
sample_set <- sample(nrow(df_1k), round(nrow(df_1k)*0.75), replace = FALSE)
df_1k_train <- df_1k[sample_set, ]
df_1k_test <- df_1k[-sample_set, ]
# For df_100k
sample_set <- sample(nrow(df_100k), round(nrow(df_100k)*0.75), replace = FALSE)
df_100k_train <- df_100k[sample_set, ]
df_100k_test <- df_100k[-sample_set, ]
#check class distribution of original, train, and test sets
table_1k<-round(prop.table(table(dplyr::select(df_1k, Region), exclude = NULL)),
4) * 100
table_1k_train<-round(prop.table(table(dplyr::select(df_1k_train , Region), exclude = NULL)),
4) * 100
table_1k_test<-round(prop.table(table(dplyr::select(df_1k_test, Region), exclude = NULL)),
4) * 100
table_100k<-round(prop.table(table(dplyr::select(df_100k, Region), exclude = NULL)),
4) * 100
table_100k_train<-round(prop.table(table(dplyr::select(df_100k_train, Region), exclude = NULL)),
4) * 100
table_100k_test<-round(prop.table(table(dplyr::select(df_100k_test, Region), exclude = NULL)),
4) * 100
as.data.frame(table_1k)
as.data.frame(table_1k_train)
as.data.frame(table_1k_test)
as.data.frame(table_100k)
as.data.frame(table_100k_train)
as.data.frame(table_100k_test)
NA
NA
Incorporating Order.ID in my model kept causing my file
to crash despite it not being made into a factor, therefore I opted to
remove it, so that I may see the results.
df_1k_train<-df_1k_train%>%
dplyr::select(-c(Order.ID))
#build model via rpart package
model_1k <- rpart(Region ~ Units.Sold,
method = "class",
data = df_1k_train,
control=rpart.control(minsplit=1, minbucket=1, cp=0.001)
)
#display decision tree
# rpart.plot(model_100k)
rpart.plot(model_1k, box.palette = "Blues")

Because the data is undecipherable in this form I opted to make a
simpler one with just categorical values.
#build model via rpart package
model_1k <- rpart(Item.Type ~ Order.Priority,
method = "class",
data = df_1k_train,
control=rpart.control(minsplit=1, minbucket=1, cp=0.001)
)
#display decision tree
# rpart.plot(model_100k)
rpart.plot(model_1k, box.palette = "Blues")

#build model via rpart package
model_100k <- rpart(Region ~ .-Country,
method = "class",
data = df_100k_train,
control=rpart.control(minsplit=1, minbucket=1, cp=0.001)
)
#display decision tree
# rpart.plot(model_100k)
rpart.plot(model_100k, box.palette = "Blues")

Response to Questions
2. Are there labels in your data? Did that impact your choice of
algorithm?
No, after checking both data sets, neither had any labels.
3. What are the pros and cons of each algorithm you selected?
The Simple Regression model helped identify the garbage in garbage
out data results we wer getting, and because of my familiarity with it I
was able to assess and understand the results very easily.
In contrast, this is the first time I’m using a Regression Tree and I
am not 100% comfortable with selecting data that is best used for this
model. For instance, originally I had decided to select
Region and Units.Sold for my tree, but
R did not make a useful of even viewable visual. I ended up
using to small categories in the 1k data so the result was printable,
but in contest with the data, all I can decipher is based on the
frequency this is the likelihood of a level of priority based on
Item.Type, which is still a somewhat confusing assessment
for me. I also read through the cran
r_project.org documentation for rpart their is limitations to the
amount of factors you may use, forcing me to disregard Country
altogether. Using the larger data set I feel a great deal of data was
ommitted considersing only 2 regions were represented here.
- How your choice of algorithm relates to the datasets (was your
choice of algorithm impacted by the datasets you chose)?
I chose simple regression when I figured the data had
multicollinearity and assumed that my transformations would not do much
to make the data a better fit.
- Which result will you trust if you need to make a business
decision?
Simple regression. I would have to circle back to business and
explain why the data would not be a suitable fit for prediction or
analysis.
- Do you think an analysis could be prone to errors when using too
much data, or when using the least amount possible?
Definitely the Decision Tree, but to be frank operator errors and
unfamiliarity with this method is definitely a major factor to account
for.
- How does the analysis between data sets compare?
No. After I assess the lack of usefulness of the numeric values I
opted to make this a learning opportunity in using a decision tree and
familiarizing myself with it for future use.
rm(list = ls(pattern = "_tmp$"))
LS0tDQp0aXRsZTogJ0RBVEEgNjIyOiBNYWNoaW5lIExlYXJuaW5nIGFuZCBCaWcgRGF0YSBIVzEnDQphdXRob3I6ICJHYWJyaWVsIENhbXBvcyINCmRhdGU6ICJMYXN0IGVkaXRlZCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBnZW9tZXRyeTogbGVmdD0wLjVjbSxyaWdodD0wLjVjbSx0b3A9MWNtLGJvdHRvbT0yY20NCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgcGRmX2RvY3VtZW50Og0KICAgIGxhdGV4X2VuZ2luZTogeGVsYXRleA0KdXJsY29sb3I6IGJsdWUNCi0tLQ0KDQoNCg0KIyBQYWNrYWdlcw0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkodGlkeW1vZGVscykNCmxpYnJhcnkocHN5Y2gpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShycGFydCkNCmxpYnJhcnkocnBhcnQucGxvdCkNCmxpYnJhcnkoY29ycnBsb3QpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmxpYnJhcnkobGFiZWxsZWQpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGdnZm9yY2UpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkoTWV0cmljcykNCmBgYA0KDQojIEluc3RydWN0aW9ucw0KDQojIyAqKkV4cGxvcmF0b3J5IGFuYWx5c2lzIGFuZCBlc3NheSoqDQoNCiMjICpQcmUtd29yayoNCg0KMS4gVmlzaXQgdGhlIGZvbGxvd2luZyB3ZWJzaXRlIGFuZCBleHBsb3JlIHRoZSByYW5nZSBvZiBzaXplcyBvZiB0aGlzIGRhdGFzZXQgKGZyb20gMTAwIHRvIDUgbWlsbGlvbiByZWNvcmRzKToNCmh0dHBzOi8vZXhjZWxiaWFuYWx5dGljcy5jb20vd3AvZG93bmxvYWRzLTE4LXNhbXBsZS1jc3YtZmlsZXMtZGF0YS1zZXRzLWZvci10ZXN0aW5nLXNhbGVzLyBvciAobmV3KSBodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzDQoyLiBTZWxlY3QgMiBmaWxlcyB0byBkb3dubG9hZA0KICAgIEJhc2VkIG9uIHlvdXIgY29tcHV0ZXIncyBjYXBhYmlsaXRpZXMgKG1lbW9yeSwgQ1BVKSwgc2VsZWN0IDIgZmlsZXMgeW91IGNhbiBoYW5kbGUgKHJlY29tbWVuZGVkIG9uZSBzbWFsbCwgb25lIGxhcmdlKQ0KMy4gRG93bmxvYWQgdGhlIGZpbGVzDQo0LiBSZXZpZXcgdGhlIHN0cnVjdHVyZSBhbmQgY29udGVudCBvZiB0aGUgdGFibGVzLCBhbmQgdGhpbmsgYWJvdXQgdGhlIGRhdGEgc2V0cyAoc3RydWN0dXJlLCBzaXplLCBkZXBlbmRlbmNpZXMsIGxhYmVscywgZXRjKQ0KNS4gQ29uc2lkZXIgdGhlIHNpbWlsYXJpdGllcyBhbmQgZGlmZmVyZW5jZXMgaW4gdGhlIHR3byBkYXRhIHNldHMgeW91IGhhdmUgZG93bmxvYWRlZA0KNi4gVGhpbmsgYWJvdXQgaG93IHRvIGFuYWx5emUgYW5kIHByZWRpY3QgYW4gb3V0Y29tZSBiYXNlZCBvbiB0aGUgZGF0YXNldHMgYXZhaWxhYmxlDQo3LiBCYXNlZCBvbiB0aGUgZGF0YSB5b3UgaGF2ZSwgdGhpbmsgd2hpY2ggdHdvIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBwcmVzZW50ZWQgc28gZmFyIGNvdWxkIGJlIHVzZWQgdG8gYW5hbHl6ZSB0aGUgZGF0YQ0KDQojIyAqRGVsaXZlcmFibGUqDQoNCjEuIEVzc2F5IChtaW5pbXVtIDUwMCB3b3JkIGRvY3VtZW50KQ0KICBXcml0ZSBhIHNob3J0IGVzc2F5IGV4cGxhaW5pbmcgeW91ciBzZWxlY3Rpb24gb2YgYWxnb3JpdGhtcyBhbmQgaG93IHRoZXkgcmVsYXRlIHRvIHRoZSBkYXRhIGFuZCB3aGF0IHlvdSBhcmUgdHJ5aW5nIHRvIGRvDQoyLiBFeHBsb3JhdG9yeSBBbmFseXNpcyAoKipFREEqKikgdXNpbmcgUiBvciBQeXRob24gKHN1Ym1pdCBjb2RlICsgZXJyb3JzICsgYW5hbHlzaXMgYXMgbm90ZWJvb2sgb3IgY29weS9wYXN0ZSB0byBkb2N1bWVudCkNCiAgRXhwbG9yZSBob3cgdG8gYW5hbHl6ZSBhbmQgcHJlZGljdCBhbiBvdXRjb21lIGJhc2VkIG9uIHRoZSBkYXRhIGF2YWlsYWJsZS4gVGhpcyB3aWxsIGJlIGFuIGV4cGxvcmF0b3J5IGV4ZXJjaXNlLCBzbyBmZWVsIGZyZWUgdG8gc2hvdyBlcnJvcnMgYW5kIHdhcm5pbmdzIHRoYXQgcmFpc2UgZHVyaW5nIHRoZSBhbmFseXNpcy4gVGVzdCB0aGUgY29kZSB3aXRoIGJvdGggZGF0YXNldHMgc2VsZWN0ZWQgYW5kIGNvbXBhcmUgdGhlIHJlc3VsdHMuDQoNCiMjICpBbnN3ZXIgcXVlc3Rpb25zIHN1Y2ggYXM6Kg0KMS4gQXJlIHRoZSBjb2x1bW5zIG9mIHlvdXIgZGF0YSBjb3JyZWxhdGVkPw0KMi4gQXJlIHRoZXJlIGxhYmVscyBpbiB5b3VyIGRhdGE/IERpZCB0aGF0IGltcGFjdCB5b3VyIGNob2ljZSBvZiBhbGdvcml0aG0/DQozLiBXaGF0IGFyZSB0aGUgcHJvcyBhbmQgY29ucyBvZiBlYWNoIGFsZ29yaXRobSB5b3Ugc2VsZWN0ZWQ/DQo0LiBIb3cgeW91ciBjaG9pY2Ugb2YgYWxnb3JpdGhtIHJlbGF0ZXMgdG8gdGhlIGRhdGFzZXRzICh3YXMgeW91ciBjaG9pY2Ugb2YgYWxnb3JpdGhtIGltcGFjdGVkIGJ5IHRoZSBkYXRhc2V0cyB5b3UgY2hvc2UpPw0KNS4gV2hpY2ggcmVzdWx0IHdpbGwgeW91IHRydXN0IGlmIHlvdSBuZWVkIHRvIG1ha2UgYSBidXNpbmVzcyBkZWNpc2lvbj8NCjYuIERvIHlvdSB0aGluayBhbiBhbmFseXNpcyBjb3VsZCBiZSBwcm9uZSB0byBlcnJvcnMgd2hlbiB1c2luZyB0b28gbXVjaCBkYXRhLCBvciB3aGVuIHVzaW5nIHRoZSBsZWFzdCBhbW91bnQgcG9zc2libGU/DQo3LiBIb3cgZG9lcyB0aGUgYW5hbHlzaXMgYmV0d2VlbiBkYXRhIHNldHMgY29tcGFyZT8NCg0KRGV2ZWxvcCB5b3VyIGV4cGxvcmF0b3J5IGFuYWx5c2lzIG9mIHRoZSBkYXRhIGFuZCB0aGUgZXNzYXkgaW4gdGhlIGZvbGxvd2luZyAyIHdlZWtzLg0KDQojIERBVEENCg0KYGBge3J9DQp1cmw8LSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vR2l0YWJsZUdhYmUvRGF0YTYyNF9EYXRhL21haW4vIg0KYGBgDQoNCg0KYGBge3J9DQpkZl8xayA8LSByZWFkLmNzdihwYXN0ZTAodXJsLCIxMDAwJTIwU2FsZXMlMjBSZWNvcmRzLmNzdiIpKQ0KZGZfMTAwayA8LSByZWFkLmNzdihwYXN0ZTAodXJsLCIxMDAwMDAlMjBTYWxlcyUyMFJlY29yZHMuY3N2IikpDQpgYGANCg0KIyAqKkVEQSoqDQoNCkZhbWlsaWFyaXphdGlvbiB3aXRoIFNhbGVzIGRhdGFzZXRzIGV4dHJhY3RlZCBmcm9tIFtleGNlbGJpIGFuYWx5dGljc10oaHR0cHM6Ly9leGNlbGJpYW5hbHl0aWNzLmNvbS93cC9kb3dubG9hZHMtMTgtc2FtcGxlLWNzdi1maWxlcy1kYXRhLXNldHMtZm9yLXRlc3Rpbmctc2FsZXMvKSByZXF1aXJlcyB1bmRlcnN0YW5kaW5nIG9mIGRhdGFzZXQgY29tcG9zaXRpb24sIGRpbWVuc2lvbnMsIGNvbHVtbiB0eXBlcywgYE5BYCBvciBgTnVsbGAgdmFsdWUgY291bnQsIGV0Yy4gDQoNCiMjIERhdGEgQ29tcG9zaXRpb24NCg0KYGBge3J9DQpzdHIoZGZfMWspDQpgYGANCg0KYGBge3J9DQpzdHIoZGZfMTAwaykNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD0yfQ0Ka2FibGUoYXMuZGF0YS5mcmFtZSh0YWJsZShkZl8xayRSZWdpb24pKSAlPiUgYXJyYW5nZShkZXNjKEZyZXEpKSwNCiAgICAgIGNhcHRpb24gPSAiRnJlcXVlbmN5IFJlZ2lvbiBkZl8xayIpDQpgYGANCg0KYGBge3J9DQprYWJsZShhcy5kYXRhLmZyYW1lKHRhYmxlKGRmXzEwMGskUmVnaW9uKSkgJT4lIGFycmFuZ2UoZGVzYyhGcmVxKSksDQogICAgICBjYXB0aW9uID0gIkZyZXF1ZW5jeSBSZWdpb24gZGZfMTAwayIpDQpgYGANCg0KDQpgYGB7cn0NCmthYmxlKGFzLmRhdGEuZnJhbWUodGFibGUoZGZfMWskSXRlbS5UeXBlICkpICU+JSBhcnJhbmdlKGRlc2MoRnJlcSkpLA0KICAgICAgY2FwdGlvbiA9ICJGcmVxdWVuY3kgSXRlbS5UeXBlIGRmXzFrIikNCmBgYA0KDQoNCmBgYHtyfQ0Ka2FibGUoYXMuZGF0YS5mcmFtZSh0YWJsZShkZl8xMDBrJEl0ZW0uVHlwZSApKSAlPiUgYXJyYW5nZShkZXNjKEZyZXEpKSwNCiAgICAgICAgICAgIGNhcHRpb24gPSAiRnJlcXVlbmN5IEl0ZW0gVHlwZSAxMDBrIikNCmBgYA0KDQoNCmBgYHtyfQ0Ka2FibGUoYXMuZGF0YS5mcmFtZSh0YWJsZShkZl8xayRTYWxlcy5DaGFubmVsICkpICU+JSBhcnJhbmdlKGRlc2MoRnJlcSkpLA0KICAgICAgY2FwdGlvbiA9ICJGcmVxdWVuY3kgU2FsZXMgQ2hhbm5lbCAxayIpDQpgYGANCg0KYGBge3J9DQprYWJsZShhcy5kYXRhLmZyYW1lKHRhYmxlKGRmXzEwMGskU2FsZXMuQ2hhbm5lbCApKSAlPiUgYXJyYW5nZShkZXNjKEZyZXEpKSwNCiAgICAgIGNhcHRpb24gPSAiRnJlcXVlbmN5IFNhbGVzIENoYW5uZWwgMTAwayIpDQpgYGANCg0KYGBge3J9DQp2YXJfbGFiZWwoZGZfMWspDQpgYGANCg0KYGBge3J9DQp2YXJfbGFiZWwoZGZfMTAwaykNCmBgYA0KDQoNCmBgYHtyfQ0KIyBEaW1lbnNpb25zDQpkaW1fMWtfdG1wPC1kaW0oZGZfMWspDQpkaW1fMTAwa190bXA8LWRpbShkZl8xMDBrKQ0KIyBDbGFzcw0KY2xhc3NfMWtfdG1wPC1zYXBwbHkoZGZfMWssY2xhc3MpDQpjbGFzc18xMDBrX3RtcDwtc2FwcGx5KGRmXzEwMGssY2xhc3MpDQoNCg0KDQpjb2x1bW5fbmFtZV8xa190bXAgPC0gIk9yZGVyLklEIg0KDQojIENvdW50IHRoZSBudW1iZXIgb2YgZHVwbGljYXRlcyBpbiB0aGUgc3BlY2lmaWVkIGNvbHVtbg0KbnVtX2R1cGxpY2F0ZXNfMWtfdG1wIDwtIHN1bShkdXBsaWNhdGVkKGRmXzFrW1tjb2x1bW5fbmFtZV8xa190bXBdXSkgfA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR1cGxpY2F0ZWQoZGZfMWtbW2NvbHVtbl9uYW1lXzFrX3RtcF1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbUxhc3QgPSBUUlVFKSkNCg0KY29sdW1uX25hbWVfMTAwa190bXAgPC0gIk9yZGVyLklEIg0KDQojIENvdW50IHRoZSBudW1iZXIgb2YgZHVwbGljYXRlcyBpbiB0aGUgc3BlY2lmaWVkIGNvbHVtbg0KbnVtX2R1cGxpY2F0ZXNfMTAwa190bXAgPC0gc3VtKGR1cGxpY2F0ZWQoZGZfMTAwa1tbY29sdW1uX25hbWVfMTAwa190bXBdXSkgfA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR1cGxpY2F0ZWQoZGZfMTAwa1tbY29sdW1uX25hbWVfMTAwa190bXBdXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyb21MYXN0ID0gVFJVRSkpDQoNCg0KbmFfbnVsbF9jbnRfdG1wPC0oc3VtKGNvbFN1bXMoaXMubmEoZGZfMWspIHwgaXMubnVsbChkZl8xaykpKSsNCiAgICAgICAgICAgICAgICAgICAgc3VtKGNvbFN1bXMoaXMubmEoZGZfMTAwaykgfCBpcy5udWxsKGRmXzEwMGspKSkpDQoNCnJlZ2lvbl90bXA8LXVuaXF1ZShkZl8xayRSZWdpb24pDQpjb3VudHJ5X2xlbl90bXA8LWxlbmd0aCh1bmlxdWUoZGZfMWskQ291bnRyeSkpDQoNCmBgYA0KDQoNClRoZSBkYXRhc2V0IG9mIHNpemUgMTAwMCBpcyBzdG9yZWQgdG8gYGRmXzFrYCBhbmQgdGhlIGRhdGFzZXQgc2l6ZSAxMDAsMDAwIGlzIHN0b3JlZCB0byBgZGZfMTAwa2ANCg0KKiBgZGZfMWtgIGRpbWVuc2lvbnMgaXMgYHIgZGltXzFrX3RtcFsxXWAgcm93cyBhbmQgYHIgZGltXzFrX3RtcFsyXWAgY29sdW1ucy4gDQoqIGBkZl8xMDBrYCBkaW1lbnNpb25zIGlzIGByIGRpbV8xMDBrX3RtcFsxXWAgcm93cyBhbmQgYHIgZGltXzEwMGtfdG1wWzJdYCBjb2x1bW5zLg0KKiBUaGUgY29sdW1uIHR5cGVzIGZvciBkZl8xayBhcmUNCmByIGNsYXNzXzFrX3RtcGAgDQoqIFRoZSBjb2x1bW4gdHlwZXMgZm9yIGRmXzEwMGsgYXJlIA0KYHIgY2xhc3NfMTAwa190bXBgDQoqIE5vdGFibGUgY2F0ZWdvcmllcyBpbmNsdWRlDQogIC0gYE9yZGVyLkRhdGVgIGFuZCBgU2hpcC5EYXRlYCB0aGUgb25seSBkYXRlIHZhbHVlZCBjb2x1bW5zLCBidXQgc2V0IHRvIHR5cGUgYGNocmAgYW5kIG1heSBuZWVkIGNvbnZlcnRpbmcuDQogIC0gYE9yZGVyLklEYCBpcyBjb21wb3NlIG9mIHVuaXF1ZSB2YWx1ZXMgd2l0aCBgciBudW1fZHVwbGljYXRlc18xa190bXBgIGR1cGxpY2F0ZXMgZm91bmQgaW4gdGhlIGBkZl8xa2AgZGF0YSBhbmQgYHIgbnVtX2R1cGxpY2F0ZXNfMTAwa190bXBgIGZvdW5kIGluIHRoZSBgZGZfMTAwa2AgZGF0YS4NCiAgLSBgUmVnaW9uYCBhbmQgYENvdW50cnlgIGJvdGggb2Ygd2hpY2ggZGVmaW5lIGxvY2F0aW9uDQogIC0gYEl0ZW0uVHlwZWAgZm9yIHR5cGUgb2YgaXRlbSBzb2xkLg0KICAtIGBTYWxlcy5DaGFubmVsYCBkZWZpbmVzIHNhbGVzIG1ldGhvZCBhcyBhbiBvbmxpbmUgb3Igb2ZmbGluZSBwdXJjaGFzZSwgb3IgZS1wdXJjaGFzZSB2cyBpbi1zdG9yZS4NCiAgLSBgT3JkZXIuUHJpb3JpdHlgIHdoaWNoIGhhcyBhIHJhbmtpbmcgb2Ygc2V2ZXJpdHkuDQogIC0gQXR0cmlidXRlcyBsYWJlbGVkIHdpdGggYFRvdGFsYCB0aGF0IGFyZSBjYWxjdWxhdGVkIHZhbHVlcy4NCiAgKiBVc2luZyB0aGUgYGxlbmd0aCgpYCBmdW5jdGlvbnMgd2Ugc2VlIHRoYXQgYHIgY291bnRyeV9sZW5fdG1wYCBjb3VudHJpZXMgYXJlIGxpc3RlZCBpbiB0aGUgZGF0YS4NCiogVXNpbmcgdGhlIGB0YWJsZWAgZnVuY3Rpb24gd2Ugc2VlOg0KICAgIC1PZiB0aGUgYFJlZ2lvbnNgIGxpc3RlZCBgU3ViLVNhaGFyYW4gQWZyaWNhYCBhbmQgYEV1cm9wZWAgaXMgbW9zdCBmcmVxdWVudGVkLg0KICAgIC0gRm9yIGBkZl8xa2AgYEJldmVyYWdlc2AgYW5kIGBWZWdldGFibGVzYCBpcyBtb3N0IGZyZXF1ZW50ZWQsIGhvd2V2ZXIgd2l0aCBgZGZfMTAwa2AgYE9mZmljZSBTdXBwbGllc2AgYW5kIGBDZXJlYWxzYCBpcy4NCiAgICAtIEZvciBgZGZfMWtgIG1vcmUgcHVyY2hhc2VzIGFyZSBkb25lIGBPZmZsaW5lYCB3aGlsZSBmb3IgYGRmXzEwMGtgIG1vcmUgaXMgZG9uZSBgT25saW5lYCBBbGJpZXQgYnkgYSBzbWFsbCBtYXJnaW4gaW4gYm90aCBjYXNlcy4NCg0KV2l0aCByZXNwZWN0IHRvIGRlcGVuZGVuY2llcywgdGhlIGZvcm11bGFzIGJlbG93IGhpZ2hsaWdodCB0aGUgZGVwZW5kZW5jeSB0aGF0IGV4aXN0cyB3aXRoIGNhbGN1bGF0ZWQgdmFyaWFibGVzIHdpdGggdGhlIGxhYmVsIGBUb3RhbGAgaW4gdGhlcmUgQXR0cmlidXRlIG5hbWUuDQoNCiRUb3RhbC5Db3N0PVVuaXRzLlNvbGRcdGltZXMgVW5pdC5Db3N0JCBtYWtpbmcgVG90YWwuQ29zdCBkZXBlbmRlbnQgb24gVW5pdHMuU29sZCBhbmQgVW5pdCBDb3N0DQokVG90YWwuUmV2ZW51ZT1Vbml0cy5Tb2xkXHRpbWVzIFVuaXQuUHJpY2UkIG1ha2luZyBUb3RhbC5SZXZlbnVlIGRlcGVuZGVudCBvbiBVbml0cy5Tb2xkIGFuZCBVbml0LlByaWNlDQokVG90YWwuUHJvZml0PVRvdGFsLlJldmVudWUtVG90YWwuQ29zdCQgbWFraW5nIHRoZSBzdWJzZXF1ZW50IHRvdGFscyBhYm92ZSB0aGUgZGVwZW5kZW50IHZhcmlhYmxlcyBmb3IgVG90YWwuUHJvZml0DQoNClRoZSBPcmRlci5Qcmlvcml0eSBoYXZlIGEgZGVwZW5kZW5jeSBiYXNlZCBvbiByYW5raW5nIG9mIGByIHVuaXF1ZShkZl8xayRPcmRlci5Qcmlvcml0eSlgDQpXaGljaCBpcyBDcml0aWNhbCwgSGlnaCwgTWVkaXVtLCBMb3cgaW4gYXNjZW5kaW5nIG9yZGVyLg0KDQpEYXRlIHZhbHVlcyBhcmUgZGVwZW5kZW50IGluIGludGVycHJldGF0aW9uLCB3aXRoIGNhbGN1bGF0aW9uIG9mIGBPcmRlci5EYXRlYCBhbmQgYFNoaXAuRGF0ZWAgYmVpbmcgYSBmYWN0b3Igb2YgcGVyZm9ybWFuY2Ugb3IgdGltZWxpbmVzcy4NCg0KDQojIyBEYXRhIFRyYW5mb3JtYXRpb24NCg0KYGBge3J9DQpkZl8xa1tbJ09yZGVyLkRhdGUnXV0gPC0gYXMuRGF0ZShkZl8xa1tbJ09yZGVyLkRhdGUnXV0sICIlbS8lZC8lWSIpDQpkZl8xa1tbJ1NoaXAuRGF0ZSddXSA8LSBhcy5EYXRlKGRmXzFrW1snU2hpcC5EYXRlJ11dLCAiJW0vJWQvJVkiKQ0KDQpkZl8xMDBrW1snT3JkZXIuRGF0ZSddXSA8LSBhcy5EYXRlKGRmXzEwMGtbWydPcmRlci5EYXRlJ11dLCAiJW0vJWQvJVkiKQ0KZGZfMTAwa1tbJ1NoaXAuRGF0ZSddXSA8LSBhcy5EYXRlKGRmXzEwMGtbWydTaGlwLkRhdGUnXV0sICIlbS8lZC8lWSIpDQoNCmRmXzFrJE9yZGVyLlByaW9yaXR5IDwtIGFzLmZhY3RvcihkZl8xayRPcmRlci5Qcmlvcml0eSkNCmRmXzEwMGskT3JkZXIuUHJpb3JpdHkgPC0gYXMuZmFjdG9yKGRmXzEwMGskT3JkZXIuUHJpb3JpdHkpDQpgYGANCg0KVGhlIG1vc3Qgb2J2aW91cyB0cmFuc2Zvcm1hdGlvbnMgd2VyZSB0aGUgZGF0ZSB2YWx1ZXMgYXMgbm90ZWQgaW4gKkVEQSogYW5kIGZhY3RvcmluZyB0aGUgY2F0ZWdvcmllcyBpbiBgT3JkZXIuUHJpb3JpdHlgDQoNCmBgYHtyfQ0KZGZfMWskU2FsZXMuQ2hhbm5lbCA8LSBhcy5mYWN0b3IoZGZfMWskU2FsZXMuQ2hhbm5lbCkNCmRmXzEwMGskU2FsZXMuQ2hhbm5lbCA8LSBhcy5mYWN0b3IoZGZfMTAwayRTYWxlcy5DaGFubmVsKQ0KZGZfMWskSXRlbS5UeXBlIDwtIGFzLmZhY3RvcihkZl8xayRJdGVtLlR5cGUpDQpkZl8xMDBrJEl0ZW0uVHlwZSA8LSBhcy5mYWN0b3IoZGZfMTAwayRJdGVtLlR5cGUpDQpkZl8xayRSZWdpb24gPC0gYXMuZmFjdG9yKGRmXzFrJFJlZ2lvbikNCmRmXzEwMGskUmVnaW9uIDwtIGFzLmZhY3RvcihkZl8xMDBrJFJlZ2lvbikNCmRmXzFrJENvdW50cnkgPC0gYXMuZmFjdG9yKGRmXzFrJENvdW50cnkpDQpkZl8xMDBrJENvdW50cnkgPC0gYXMuZmFjdG9yKGRmXzEwMGskQ291bnRyeSkNCg0KYGBgDQoNCmBTYWxlcy5DaGFubmVsYCxgSXRlbS5UeXBlYCBhbmQgYFJlZ2lvbmAgd2VyZSBhbHNvIGxvZ2ljYWwgY2hvaWNlcywgY29uc2lkZXJpbmcgdGhlIGFtb3VudCBvZiB1bmlxdWUgdmFsdWVzIGZvciBgQ291bnRyeWAgYW5kIHRoZSBuYXR1cmUgb2YgaXRzIHJlbGF0aW9uc2hpcCB3aXRoIFJlZ2lvbiwgSSBiZWxpZXZlIGlmIEkgbWFrZSBhIG1vZGVsIHdpdGggUmVnaW9uLCBDb3VudHJ5IHdvdWxkIGJlIGV4Y2x1ZGVkLg0KT3JkZXIuSUQgYXJlIGp1c3QgYXJiaXRyYXJ5LCBjaHJvbm9sb2dpY2FsIG9yIGluY3JlbWVudGVkIG51bWJlcnMgdGhlcmVmb3IgaXQgd2FzIG5vdCBzZXQgYXMgYSBmYWN0b3IuDQoNCmBgYHtyfQ0KbGV2ZWxzKGRmXzFrJFJlZ2lvbikNCmBgYA0KDQoNCiMjIENvcnJlbGF0aW9uIGFuZCBTa2V3bmVzcw0KDQpgYGB7cn0NCmRlc2NyaWJlKGRmXzFrJT4lDQogICAgICAgICAgIGRwbHlyOjpzZWxlY3QoY29udGFpbnMoIlVuaXQiKSB8IGNvbnRhaW5zKCJUb3RhbCIpKSkgJT4lIA0KICBkcGx5cjo6c2VsZWN0KGMobWVhbixzZCxtaW4sbWF4LHJhbmdlLHNlLHNrZXcpKQ0KYGBgDQoNCmBgYHtyfQ0KZGVzY3JpYmUoZGZfMTAwayU+JQ0KICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGNvbnRhaW5zKCJVbml0IikgfCBjb250YWlucygiVG90YWwiKSkpICU+JSANCiAgZHBseXI6OnNlbGVjdChjKG1lYW4sc2QsbWluLG1heCxyYW5nZSxzZSxza2V3KSkNCmBgYA0KDQpgYGB7cn0NCnBsb3RfbnVtZXJpY18xazwtZGZfMWslPiUNCiAgICAgIGRwbHlyOjpzZWxlY3QoY29udGFpbnMoIlVuaXQiKSB8IGNvbnRhaW5zKCJUb3RhbCIpKSAlPiUNCiAgZ2F0aGVyKHZhcmlhYmxlLCB2YWx1ZSwgMTo2KSAlPiUNCiAgZ2dwbG90KGFlcyh2YWx1ZSkpICsNCiAgICBmYWNldF93cmFwKH52YXJpYWJsZSwgc2NhbGVzID0gImZyZWUiKSArDQogICAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAibGlnaHRncmVlbiIsIGFscGhhPTAuOSwgY29sb3I9ImxpZ2h0Z3JlZW4iKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHk9YWZ0ZXJfc3RhdChkZW5zaXR5KSksIGFscGhhPTAuMiwgZmlsbCA9ICJsaWdodGJsdWUiLA0KICAgICAgICAgICAgICAgICAgIGNvbG9yPSJkYXJrcmVkIiwgcG9zaXRpb249ImlkZW50aXR5IiwgYmlucyA9IDQwKQ0KDQpwbG90X251bWVyaWNfMTAwazwtZGZfMTAwayU+JQ0KICAgICAgZHBseXI6OnNlbGVjdChjb250YWlucygiVW5pdCIpIHwgY29udGFpbnMoIlRvdGFsIikpICU+JQ0KICBnYXRoZXIodmFyaWFibGUsIHZhbHVlLCAxOjYpICU+JQ0KICBnZ3Bsb3QoYWVzKHZhbHVlKSkgKw0KICAgIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgICBnZW9tX2RlbnNpdHkoZmlsbCA9ICJsaWdodGdyZWVuIiwgYWxwaGE9MC45LCBjb2xvcj0ibGlnaHRncmVlbiIpICsNCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeT1hZnRlcl9zdGF0KGRlbnNpdHkpKSwgYWxwaGE9MC4yLCBmaWxsID0gInBpbmsiLA0KICAgICAgICAgICAgICAgICAgIGNvbG9yPSJkYXJrcmVkIiwgcG9zaXRpb249ImlkZW50aXR5IiwgYmlucyA9IDQwKQ0KDQpncmlkLmFycmFuZ2UocGxvdF9udW1lcmljXzFrLHBsb3RfbnVtZXJpY18xMDBrLG5jb2w9MSkNCmBgYA0KDQpgYGB7cn0NCnBhcihtZnJvdyA9IGMoMSwgMiksIG1hciA9IGMoMCwgMCwgMywgMCkpDQpwbG90X2NvcnJfMWsgPC0gY29yKGRmXzFrICU+JQ0KICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoY29udGFpbnMoIlVuaXQiKSB8IGNvbnRhaW5zKCJUb3RhbCIpKSkNCmNvcnJwbG90KHBsb3RfY29ycl8xaywgdGwuY29sID0gJ2RhcmtncmVlbicsIGRpYWcgPSBGQUxTRSwgdHlwZSA9ICJsb3dlciIsDQogICAgICAgICBvcmRlciA9ICJoY2x1c3QiLCBhZGRDb2VmLmNvbCA9ICJkYXJrZ3JlZW4iLA0KICAgICAgICAgdGl0bGUgPSAiMWsiLG1hcj1jKDAsMCwxLDApKQ0KDQojIFBsb3QgY29ycmVsYXRpb24gZm9yIGRmXzEwMGsNCnBsb3RfY29ycl8xMDBrIDwtIGNvcihkZl8xMDBrICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChjb250YWlucygiVW5pdCIpIHwgY29udGFpbnMoIlRvdGFsIikpKQ0KY29ycnBsb3QocGxvdF9jb3JyXzEwMGssIHRsLmNvbCA9ICdkYXJrZ3JlZW4nLCBkaWFnID0gRkFMU0UsIHR5cGUgPSAibG93ZXIiLA0KICAgICAgICAgb3JkZXIgPSAiaGNsdXN0IiwgYWRkQ29lZi5jb2wgPSAiZGFya2dyZWVuIiwNCiAgICAgICAgIHRpdGxlID0gIjEwMGsiLG1hcj1jKDAsMCwxLDApKQ0KDQoNCmBgYA0KDQpTa2V3bmVzcyBpcyBhIG1lYXN1cmUgb2Ygc3ltbWV0cnksIHRoZXJlZm9yZSB0aGUgdmFsdWVzIG5lYXIgemVybywgZGVzcGl0ZSBvbmUgYmVpbmcgbmVnYXRpdmUsIGRpZCBub3QgcGFydGljdWxhcmx5IHN0YW5kIG91dCwgaG93ZXZlciBmb3IgYm90aCBzaXplIGRhdGEgc2V0cywgVG90YWwgLSBSZXZlbnVlLCBDb3N0IGFuZCBQcm9maXQgYWxsIGFyZSByaWdodCBza2V3ZWQuDQogICAgU2tld25lc3MgPSAwOiBwZXJmZWN0IHN5bW1ldHJ5Lg0KICAgIFNrZXduZXNzIDwgMDogTmVnYXRpdmVseSBpcyBsZWZ0IHNrZXdlZCBvciBoYXMgYSB0YWlsLg0KICAgIFNrZXduZXNzID4gMDogUG9zaXRpdmUgaXMgcmlnaHQgc2tld2VkIG9yIGhhcyBhIHJpZ2h0IHRhaWwuDQogICAgDQpDb25jZXJuIGlzIG5vdCB0b28gYmlnIHdpdGggcmVzcGVjdCB0byB0aGVzZSB2YWx1ZXMgYXMgZm9yIG91ciBtb2RlbCBJIGNhbiB0cnkgdG8gbm9ybWFsaXplIGl0IGFzIG11Y2ggYXMgcG9zc2libGUuIA0KDQpDb3JyZWxhdGlvbiBkb2VzIG1vcmUgdGhhbiBqdXN0IHN1cHBvcnQgdGhlIG9idmlvdXMgcmVsYXRpb25zaGlwcyBub3RlZCBlYXJsaWVyLCByYXRoZXIgaXQgaGVscCBpZGVudGlmeSBpZiB3ZSBoYXZlIG11bHRpY29sbGluZWFyaXR5LiBNdWx0aWNvbGxpbmVhcml0eSBvY2N1cnMgd2hlbiB0d28gb3IgbW9yZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMgaW4gYSBkYXRhIGZyYW1lIGhhdmUgYSBoaWdoIGNvcnJlbGF0aW9uIHdpdGggb25lIGFub3RoZXIsIGFuZCBjYW4gY2F1c2UgaXNzdWVzIHdpdGggc3RhYmlsaXR5IGFuZCBzaXplIG9mIGFuIGVzdGltYXRlZCByZWdyZXNzaW9uIGNvZWZmaWNpZW50LCB3aGljaCBpbiB0dXJuIG1ha2VzIHVucmVsaWFibGUgaW5mZXJlbmNlcyBmb3Igb3VyIHByZWRpY3RvciB2YXJpYWJsZXMuDQoNCk9mIG91ciB2YXJpYWJsZXMsIFVuaXQuQ29zdCBhbmQgVG90YWwuUHJvZml0IGhhdmUgdGhlIGhpZ2hlc3QgY29ycmVsYXRpb24sIHdoaWxlIFVuaXQuLUNvc3QsUHJpY2UgYW5kIFNvbGQgc2hvdyB0aGUgd2Vha2VzdC4gVGhlIHdheSB0byBpbnRlcnByZXQgdGhlIGNvcnJlbGF0aW9uIGlzIHVuZGVyc3RhbmRpbmcgdGhhdCB0aGUgaGlnaGVyIHRoZSBhYnNvbHV0ZSB2YWx1ZSBvZiBhIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IGlzLCB0aGUgc3Ryb25nZXIgdGhlIHJlbGF0aW9uc2hpcC4NCg0KQmVjYXVzZSBJIHN1c3BlY3QgbXVsdGljb2xsaW5lYXJpdHksIEkndmUgY2hvc2VuIHRvIG5vdCBjcmVhdGUgbXkgc2Vjb25kIG1vZGVsIG9mZiB0aGUgbnVtZXJpYyB2YWx1ZXMsIHJhdGhlciBJIGFtIG9wdGluZyB0byBtYWtlIGEgZGVjaXNpb24gdHJlZSB1c2luZyBvbmUgb2YgdGhlIGNhdGVnb3JpY2FsIHZhbHVlcywgd2hvc2UgdmFyaWFibGVzIEkndmUgc2V0IHRvIGZhY3RvcnMgZWFybGllci4gRm9yIG15IGZpcnN0IEkgcGxhbiB0byBkbyBhIHNpbXBsZSByZWdyZXNzaW9uIGJ1dCBJIHN1c3BlY3Qgbm9ybWFsaXppbmcgaXQgd2lsbCBub3QgaW1wYWN0IHRoZSBkYXRhIG11Y2guDQoNCiMgTW9kZWwgU2VsZWN0aW9uIGFuZCBsb2dpYw0KDQoNCiMjIFNpbXBsZSBMaW5lYXIgcmVncmVzc2lvbg0KRm9yIG15IGZpcnN0IG1vZGVsIEkgd2lsbCBjaG9vc2UgYSBzaW1wbGUgbGluZWFyIHJlZ3Jlc3Npb24gYWZ0ZXIgbm9ybWFsaXppbmcgdGhlIGRhdGEuDQoNCiMjIyBOb3JtYWxpemF0aW9uDQoNCltTdGF0b2xvZ3ldKGh0dHBzOi8vd3d3LnN0YXRvbG9neS5vcmcvaG93LXRvLW5vcm1hbGl6ZS1kYXRhLWluLXIvKSBwcm92aWRlcyBhIGdyZWF0IHdhbGsgdGhyb3VnaCBmb3Igbm9ybWFsaXphdGlvbi4NCk5vcm1hbGl6YXRpb24gZW5zdXJlcyBhbGwgdmFyaWFibGVzIGNvbnRyaWJ1dGUgZXF1YWxseSB0byBhIG1vZGVsIHZzIGhhdmluZyBvbmUgY29udHJpYnV0ZSBtb3JlIGJlY2F1c2Ugb2YgaXRzIHZhbHVlLg0KDQoNCmBgYHtyfQ0KIyBGdW5jdGlvbiBmb3Igbm9ybWFsaXphdGlvbg0KbWluX21heF9ub3JtIDwtIGZ1bmN0aW9uKHgpIHsNCiAgICAoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKQ0KfQ0KDQojIFJ1biBmdW5jdGlvbiB1c2luZyBsYXBwbHkgYW5kIG9ubHkgd2l0aCB0aGUgbnVtZXJpYyB2YWx1ZXMNCm5vcm1fMWsgPC0gYXMuZGF0YS5mcmFtZShsYXBwbHkoZGZfMWsgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZWVwKGlzLm51bWVyaWMpICwgbWluX21heF9ub3JtKSkNCg0Kbm9ybV8xMDBrIDwtIGFzLmRhdGEuZnJhbWUobGFwcGx5KGRmXzEwMGsgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlZXAoaXMubnVtZXJpYykgLCBtaW5fbWF4X25vcm0pKQ0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojc3RhdHMNCmRlc2NyaWJlKG5vcm1fMWssIGZhc3Q9VFJVRSkgJT4lIA0KICBkcGx5cjo6c2VsZWN0KGMoLXZhcnMsLW4pKQ0KDQojZGlzdHJpYnV0aW9uDQpub3JtXzFrICU+JSANCiAgZ2F0aGVyKHZhcmlhYmxlLCB2YWx1ZSwgMTo2KSAlPiUNCiAgZ2dwbG90KGFlcyh2YWx1ZSkpICsNCiAgICBmYWNldF93cmFwKH52YXJpYWJsZSwgc2NhbGVzID0gImZyZWUiKSArDQogICAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAiZ3JlZW4iLCBhbHBoYT0wLjksIGNvbG9yPSJkYXJrZ3JlZW4iKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHk9YWZ0ZXJfc3RhdChkZW5zaXR5KSksIGFscGhhPTAuMiwgZmlsbCA9ICJwaW5rIiwgDQogICAgICAgICAgICAgICAgICAgY29sb3I9ImRhcmtyZWQiLCBwb3NpdGlvbj0iaWRlbnRpdHkiLCBiaW5zID0gNDApICsNCiAgICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KI3N0YXRzDQpkZXNjcmliZShub3JtXzEwMGssIGZhc3Q9VFJVRSkgJT4lIA0KICBkcGx5cjo6c2VsZWN0KGMoLXZhcnMsLW4pKQ0KDQojZGlzdHJpYnV0aW9uDQpub3JtXzEwMGsgJT4lIA0KICBnYXRoZXIodmFyaWFibGUsIHZhbHVlLCAxOjYpICU+JQ0KICBnZ3Bsb3QoYWVzKHZhbHVlKSkgKw0KICAgIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgICBnZW9tX2RlbnNpdHkoZmlsbCA9ICJncmVlbiIsIGFscGhhPTAuOSwgY29sb3I9ImRhcmtncmVlbiIpICsNCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeT1hZnRlcl9zdGF0KGRlbnNpdHkpKSwgYWxwaGE9MC4yLCBmaWxsID0gInBpbmsiLCANCiAgICAgICAgICAgICAgICAgICBjb2xvcj0iZGFya3JlZCIsIHBvc2l0aW9uPSJpZGVudGl0eSIsIGJpbnMgPSA0MCkgKw0KICAgIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg0KDQoNCiMjIE1vZGVsDQoNCg0KYGBge3J9DQpzZXQuc2VlZCg3NzcpDQoNCnNpbXBfcmVnX3NhbXBsZV8xayA8LSBub3JtXzFrJFRvdGFsLlJldmVudWUgJT4lDQogIGNyZWF0ZURhdGFQYXJ0aXRpb24ocCA9IDAuOCwgbGlzdCA9IEZBTFNFKQ0Kc2ltcDFrX3RyYWluICA8LSBub3JtXzFrW3NpbXBfcmVnX3NhbXBsZV8xaywgXQ0Kc2ltcDFrX3Rlc3QgPC0gbm9ybV8xa1stc2ltcF9yZWdfc2FtcGxlXzFrLCBdDQoNCg0Kc2ltcF9yZWdfc2FtcGxlXzEwMGsgPC0gbm9ybV8xMDBrJFRvdGFsLlJldmVudWUgJT4lDQogIGNyZWF0ZURhdGFQYXJ0aXRpb24ocCA9IDAuOCwgbGlzdCA9IEZBTFNFKQ0Kc2ltcDEwMGtfdHJhaW4gIDwtIG5vcm1fMTAwa1tzaW1wX3JlZ19zYW1wbGVfMTAwaywgXQ0Kc2ltcDEwMGtfdGVzdCA8LSBub3JtXzEwMGtbLXNpbXBfcmVnX3NhbXBsZV8xMDBrLCBdDQoNCmBgYA0KDQoNCmBgYHtyfQ0Kc2ltcHRyYWluMWtfbW9kZWw8LSBsbShUb3RhbC5SZXZlbnVlflVuaXRzLlNvbGQsIGRhdGE9c2ltcDFrX3RyYWluICkNCg0Kc3VtbWFyeShzaW1wdHJhaW4xa19tb2RlbCkNCiMgTWFrZSBwcmVkaWN0aW9ucw0KcHJlZGljdGlvbiA8LSBzaW1wdHJhaW4xa19tb2RlbCAlPiUgcHJlZGljdChzaW1wMWtfdGVzdCkNCg0KY2xhc3Moc2ltcDFrX3Rlc3QkVG90YWwuUmV2ZW51ZSkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBNb2RlbCBwZXJmb3JtYW5jZQ0KZGF0YS5mcmFtZSgNCiAgTUFFID0gbWFlKHByZWRpY3Rpb24sIHNpbXAxa190ZXN0JFRvdGFsLlJldmVudWUpLA0KICBSTVNFID0gUk1TRShwcmVkaWN0aW9uLCBzaW1wMWtfdGVzdCRUb3RhbC5SZXZlbnVlKSwNCiAgUjIgPSBSMihwcmVkaWN0aW9uLCBzaW1wMWtfdGVzdCRUb3RhbC5SZXZlbnVlKQ0KKQ0KYGBgDQoNCmBgYHtyfQ0Kc2ltcHRyYWluMTAwa19tb2RlbDwtIGxtKFRvdGFsLlJldmVudWV+VW5pdHMuU29sZCwgZGF0YT1zaW1wMTAwa190cmFpbiApDQoNCnN1bW1hcnkoc2ltcHRyYWluMTAwa19tb2RlbCkNCiMgTWFrZSBwcmVkaWN0aW9ucw0KcHJlZGljdGlvbiA8LSBzaW1wdHJhaW4xMDBrX21vZGVsICU+JSBwcmVkaWN0KHNpbXAxMDBrX3Rlc3QpDQoNCmNsYXNzKHNpbXAxMDBrX3Rlc3QkVG90YWwuUmV2ZW51ZSkNCmBgYA0KDQpgYGB7cn0NCiMgTW9kZWwgcGVyZm9ybWFuY2UNCmRhdGEuZnJhbWUoDQogIE1BRSA9IG1hZShwcmVkaWN0aW9uLCBzaW1wMTAwa190ZXN0JFRvdGFsLlJldmVudWUpLA0KICBSTVNFID0gUk1TRShwcmVkaWN0aW9uLCBzaW1wMTAwa190ZXN0JFRvdGFsLlJldmVudWUpLA0KICBSMiA9IFIyKHByZWRpY3Rpb24sIHNpbXAxMDBrX3Rlc3QkVG90YWwuUmV2ZW51ZSkNCikNCmBgYA0KDQoNClRoZSBzdGVwcyB0YWtlbiBmb3IgYSBzaW1wbGUgcmVncmVzc2lvbiB3ZXJlIHNwbGl0dGluZyB0aGUgbm9ybWFsaXplZCBkYXRhIGludG8gYSB0cmFpbiBhbmQgdGVzdCBvbmx5IHVzaW5nIHRoZSBudW1lcmljIHZhbHVlcy4gVXNpbmcgYFVuaXRzLlNvbGRgIGFzIHRoZSBwcmVkaWN0b3IgdmFyaWFibGUgSSBydW4gbXkgbW9kZWxzLg0KVGhlIFItc3F1YXJlZCB2YWx1ZSBvZiAwLjI4MjYgYW5kIDAuMjc2OSBzaG93cyB0aGVzZSBhcmUgdGVycmlibGUgbW9kZWxzLCBidXQgdGhhdCB3YXMgZXhwZWN0ZWQgZnJvbSB0aGUgRURBLiBUaGUgbW9kZWxzIGFjY3VyYWN5IGlzIGFib3V0IDI3JS0yOCUgd2hpY2gganVzdCBzaG93cyBpdCB3YXMgbm90IGEgZ29vZCBtb2RlbC4NCg0KDQojIyBEZWNpc2lvbiBUcmVlDQpUbyBzaW1wbGlmeSBkZWNpc2lvbiB0cmVlLCB0aGUgYXBwcm9hY2ggSSB3aWxsIHVzZSBhIGF0dHJpYnV0ZSB3aXRoIGEgbG93ZXIgbnVtYmVyIG9mIHVuaXF1ZSB2YWx1ZXMsIGJ1dCBJJ3ZlIGNob3NlbiAqKm5vdCoqIHRvIGdvIHdpdGggU2FsZXMuQ2hhbm5lbCwgc2luY2UgdGhpcyBtb2RlbCBpcyB2ZXJ5IG11Y2ggcmFuZG9tIGFuZCBJIGhvcGUgdG8gaW1wbGVtZW50IGEgZGVjaXNpb24gdHJlZSB3aXRoIG1vcmUgdGhhbiAyIHBvc3NpYmxlIG91dGNvbWVzIGZvciBhbmFseXNpcy4gV2l0aCB0aGlzIGluIG1pbmQgSSB3aWxsIG1ha2UgYSBkZWNpc2lvbiB0cmVlIG1vZGVsIHVzaW5nIFJlZ2lvbiwgd2hpY2ggSSBhbHJlYWR5IHN1c3BlY3Qgd2lsbCBjcmVhdGUgYW4gb3V0Y29tZSB3aGVyZSBFdXJvcGUgYW5kIFN1Yi1TYWhhcmFuIEFmcmljYSBhcmUgdGhlIG1vc3QgbGlrZWx5IHRoZSByZWdpb25zIHRoYXQgd2lsbCBiZSBoaWdobGlnaHRlZCBpbiBteSBkZWNpc2lvbiB0cmVlLCBiZWNhdXNlIG9mIGl0cyBoaWdoIGZyZXF1ZW5jeSBpbiB0aGUgZGF0YSBmcmFtZXMuIEkgd2lsbCB1c2UgW3JwYXJ0XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcnBhcnQvcnBhcnQucGRmKSBmb3IgbXkgZGVjaXNpb24gdHJlZS4NCk5PVEU6IHRoaXMgd2lsbCBiZSBteSBmaXJzdCB0aW1lIHVzaW5nIHJwYXJ0LCBzbyBJIGFtIGN1cmlvdXMgb24gdGhlIHJlc3VsdHMuDQoNCg0KDQoNCmBgYHtyfQ0KI3NwbGl0IGludG8gdGVzdC90cmFpbiBzZXQNCg0KI0ZvciBkZl8xaw0Kc2V0LnNlZWQoMjM0MSkNCnNhbXBsZV9zZXQgPC0gc2FtcGxlKG5yb3coZGZfMWspLCByb3VuZChucm93KGRmXzFrKSowLjc1KSwgcmVwbGFjZSA9IEZBTFNFKQ0KZGZfMWtfdHJhaW4gPC0gZGZfMWtbc2FtcGxlX3NldCwgXQ0KZGZfMWtfdGVzdCA8LSBkZl8xa1stc2FtcGxlX3NldCwgXQ0KDQojIEZvciBkZl8xMDBrDQoNCnNhbXBsZV9zZXQgPC0gc2FtcGxlKG5yb3coZGZfMTAwayksIHJvdW5kKG5yb3coZGZfMTAwaykqMC43NSksIHJlcGxhY2UgPSBGQUxTRSkNCmRmXzEwMGtfdHJhaW4gPC0gZGZfMTAwa1tzYW1wbGVfc2V0LCBdDQpkZl8xMDBrX3Rlc3QgPC0gZGZfMTAwa1stc2FtcGxlX3NldCwgXQ0KDQojY2hlY2sgY2xhc3MgZGlzdHJpYnV0aW9uIG9mIG9yaWdpbmFsLCB0cmFpbiwgYW5kIHRlc3Qgc2V0cw0KdGFibGVfMWs8LXJvdW5kKHByb3AudGFibGUodGFibGUoZHBseXI6OnNlbGVjdChkZl8xaywgUmVnaW9uKSwgZXhjbHVkZSA9IE5VTEwpKSwNCiAgICAgICAgICAgIDQpICogMTAwDQp0YWJsZV8xa190cmFpbjwtcm91bmQocHJvcC50YWJsZSh0YWJsZShkcGx5cjo6c2VsZWN0KGRmXzFrX3RyYWluICwgUmVnaW9uKSwgZXhjbHVkZSA9IE5VTEwpKSwNCiAgICAgICAgICAgIDQpICogMTAwDQp0YWJsZV8xa190ZXN0PC1yb3VuZChwcm9wLnRhYmxlKHRhYmxlKGRwbHlyOjpzZWxlY3QoZGZfMWtfdGVzdCwgUmVnaW9uKSwgZXhjbHVkZSA9IE5VTEwpKSwNCiAgICAgICAgICAgIDQpICogMTAwDQoNCnRhYmxlXzEwMGs8LXJvdW5kKHByb3AudGFibGUodGFibGUoZHBseXI6OnNlbGVjdChkZl8xMDBrLCBSZWdpb24pLCBleGNsdWRlID0gTlVMTCkpLA0KICAgICAgICAgICAgNCkgKiAxMDANCnRhYmxlXzEwMGtfdHJhaW48LXJvdW5kKHByb3AudGFibGUodGFibGUoZHBseXI6OnNlbGVjdChkZl8xMDBrX3RyYWluLCBSZWdpb24pLCBleGNsdWRlID0gTlVMTCkpLA0KICAgICAgICAgICAgNCkgKiAxMDANCnRhYmxlXzEwMGtfdGVzdDwtcm91bmQocHJvcC50YWJsZSh0YWJsZShkcGx5cjo6c2VsZWN0KGRmXzEwMGtfdGVzdCwgUmVnaW9uKSwgZXhjbHVkZSA9IE5VTEwpKSwNCiAgICAgICAgICAgIDQpICogMTAwDQoNCg0KYXMuZGF0YS5mcmFtZSh0YWJsZV8xaykNCmFzLmRhdGEuZnJhbWUodGFibGVfMWtfdHJhaW4pDQphcy5kYXRhLmZyYW1lKHRhYmxlXzFrX3Rlc3QpDQoNCmFzLmRhdGEuZnJhbWUodGFibGVfMTAwaykNCmFzLmRhdGEuZnJhbWUodGFibGVfMTAwa190cmFpbikNCmFzLmRhdGEuZnJhbWUodGFibGVfMTAwa190ZXN0KQ0KDQoNCmBgYA0KDQoNCg0KDQpJbmNvcnBvcmF0aW5nIGBPcmRlci5JRGAgaW4gbXkgbW9kZWwga2VwdCBjYXVzaW5nIG15IGZpbGUgdG8gY3Jhc2ggZGVzcGl0ZSBpdCBub3QgYmVpbmcgbWFkZSBpbnRvIGEgZmFjdG9yLCB0aGVyZWZvcmUgSSBvcHRlZCB0byByZW1vdmUgaXQsIHNvIHRoYXQgSSBtYXkgc2VlIHRoZSByZXN1bHRzLg0KDQpgYGB7cn0NCmRmXzFrX3RyYWluPC1kZl8xa190cmFpbiU+JQ0KICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoLWMoT3JkZXIuSUQpKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCiNidWlsZCBtb2RlbCB2aWEgcnBhcnQgcGFja2FnZQ0KbW9kZWxfMWsgPC0gcnBhcnQoUmVnaW9uIH4gVW5pdHMuU29sZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2xhc3MiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkZl8xa190cmFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9sPXJwYXJ0LmNvbnRyb2wobWluc3BsaXQ9MSwgbWluYnVja2V0PTEsIGNwPTAuMDAxKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICApDQoNCiNkaXNwbGF5IGRlY2lzaW9uIHRyZWUNCiMgcnBhcnQucGxvdChtb2RlbF8xMDBrKQ0KcnBhcnQucGxvdChtb2RlbF8xaywgYm94LnBhbGV0dGUgPSAiQmx1ZXMiKQ0KYGBgDQpCZWNhdXNlIHRoZSBkYXRhIGlzIHVuZGVjaXBoZXJhYmxlIGluIHRoaXMgZm9ybSBJIG9wdGVkIHRvIG1ha2UgYSBzaW1wbGVyIG9uZSB3aXRoIGp1c3QgY2F0ZWdvcmljYWwgdmFsdWVzLg0KDQpgYGB7cn0NCiNidWlsZCBtb2RlbCB2aWEgcnBhcnQgcGFja2FnZQ0KbW9kZWxfMWsgPC0gcnBhcnQoSXRlbS5UeXBlIH4gT3JkZXIuUHJpb3JpdHksDQogICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImNsYXNzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGZfMWtfdHJhaW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbD1ycGFydC5jb250cm9sKG1pbnNwbGl0PTEsIG1pbmJ1Y2tldD0xLCBjcD0wLjAwMSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KDQojZGlzcGxheSBkZWNpc2lvbiB0cmVlDQojIHJwYXJ0LnBsb3QobW9kZWxfMTAwaykNCnJwYXJ0LnBsb3QobW9kZWxfMWssIGJveC5wYWxldHRlID0gIkJsdWVzIikNCmBgYA0KDQoNCg0KYGBge3J9DQoNCiNidWlsZCBtb2RlbCB2aWEgcnBhcnQgcGFja2FnZQ0KbW9kZWxfMTAwayA8LSBycGFydChSZWdpb24gfiAuLUNvdW50cnksDQogICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImNsYXNzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGZfMTAwa190cmFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9sPXJwYXJ0LmNvbnRyb2wobWluc3BsaXQ9MSwgbWluYnVja2V0PTEsIGNwPTAuMDAxKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICApDQoNCiNkaXNwbGF5IGRlY2lzaW9uIHRyZWUNCiMgcnBhcnQucGxvdChtb2RlbF8xMDBrKQ0KcnBhcnQucGxvdChtb2RlbF8xMDBrLCBib3gucGFsZXR0ZSA9ICJCbHVlcyIpDQpgYGANCg0KIyBSZXNwb25zZSB0byBRdWVzdGlvbnMNCg0KIyMgMS4gQXJlIHRoZSBjb2x1bW5zIG9mIHlvdXIgZGF0YSBjb3JyZWxhdGVkPw0KDQpZZXMgdGhleSB3ZXJlLiBJdHMgYXBwYXJlbnQgaW4ganVzdCB0aGUgcmVsYXRpb25zaGlwcyBpbnZvbHZlZCwgc3VjaCBhcyBgQ291bnRyeWAgYmVpbmcgY2F0ZWdvcml6ZWQgaW4gYFJlZ2lvbmAsIGFuZCB0aGUgbnVtZXJpYyBhdHRyaWJ1dGVzIHdpdGggbGFiZWwgIlRvdGFsIiBiZWluZyBkZXJpdmVkIGZyb20gdGhlaXIgY2FsY3VsYXRpb25zLiBJIGFsc28gaW1tZWRpYXRlbHkgbm90ZWQgdGhlIG11bHRpY29sbGluZWFyaXR5IHdoaWNoIG1hZGUgaXQgVkVSWSBkaWZmaWN1bHQgb24gaG93IEkgd2FudGVkIHRvIHByb2NlZWQuDQoNCiMjIDIuIEFyZSB0aGVyZSBsYWJlbHMgaW4geW91ciBkYXRhPyBEaWQgdGhhdCBpbXBhY3QgeW91ciBjaG9pY2Ugb2YgYWxnb3JpdGhtPw0KDQpObywgYWZ0ZXIgY2hlY2tpbmcgYm90aCBkYXRhIHNldHMsIG5laXRoZXIgaGFkIGFueSBsYWJlbHMuDQoNCiMjIDMuIFdoYXQgYXJlIHRoZSBwcm9zIGFuZCBjb25zIG9mIGVhY2ggYWxnb3JpdGhtIHlvdSBzZWxlY3RlZD8NCg0KVGhlIFNpbXBsZSBSZWdyZXNzaW9uIG1vZGVsIGhlbHBlZCBpZGVudGlmeSB0aGUgZ2FyYmFnZSBpbiBnYXJiYWdlIG91dCBkYXRhIHJlc3VsdHMgd2Ugd2VyIGdldHRpbmcsIGFuZCBiZWNhdXNlIG9mIG15IGZhbWlsaWFyaXR5IHdpdGggaXQgSSB3YXMgYWJsZSB0byBhc3Nlc3MgYW5kIHVuZGVyc3RhbmQgdGhlIHJlc3VsdHMgdmVyeSBlYXNpbHkuDQoNCkluIGNvbnRyYXN0LCB0aGlzIGlzIHRoZSBmaXJzdCB0aW1lIEknbSB1c2luZyBhIFJlZ3Jlc3Npb24gVHJlZSBhbmQgSSBhbSBub3QgMTAwJSBjb21mb3J0YWJsZSB3aXRoIHNlbGVjdGluZyBkYXRhIHRoYXQgaXMgYmVzdCB1c2VkIGZvciB0aGlzIG1vZGVsLiBGb3IgaW5zdGFuY2UsIG9yaWdpbmFsbHkgSSBoYWQgZGVjaWRlZCB0byBzZWxlY3QgYFJlZ2lvbmAgYW5kIGBVbml0cy5Tb2xkYCBmb3IgbXkgdHJlZSwgYnV0IGBSYCBkaWQgbm90IG1ha2UgYSB1c2VmdWwgb2YgZXZlbiB2aWV3YWJsZSB2aXN1YWwuIEkgZW5kZWQgdXAgdXNpbmcgdG8gc21hbGwgY2F0ZWdvcmllcyBpbiB0aGUgMWsgZGF0YSBzbyB0aGUgcmVzdWx0IHdhcyBwcmludGFibGUsIGJ1dCBpbiBjb250ZXN0IHdpdGggdGhlIGRhdGEsIGFsbCBJIGNhbiBkZWNpcGhlciBpcyBiYXNlZCBvbiB0aGUgZnJlcXVlbmN5IHRoaXMgaXMgdGhlIGxpa2VsaWhvb2Qgb2YgYSBsZXZlbCBvZiBwcmlvcml0eSBiYXNlZCBvbiBgSXRlbS5UeXBlYCwgd2hpY2ggaXMgc3RpbGwgYSBzb21ld2hhdCBjb25mdXNpbmcgYXNzZXNzbWVudCBmb3IgbWUuIEkgYWxzbyByZWFkIHRocm91Z2ggdGhlIFtjcmFuIHJfcHJvamVjdC5vcmcgZG9jdW1lbnRhdGlvbiBmb3IgcnBhcnRdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9sYWJlbGxlZC92aWduZXR0ZXMvaW50cm9fbGFiZWxsZWQuaHRtbCkgdGhlaXIgaXMgbGltaXRhdGlvbnMgdG8gdGhlIGFtb3VudCBvZiBmYWN0b3JzIHlvdSBtYXkgdXNlLCBmb3JjaW5nIG1lIHRvIGRpc3JlZ2FyZCBDb3VudHJ5IGFsdG9nZXRoZXIuIFVzaW5nIHRoZSBsYXJnZXIgZGF0YSBzZXQgSSBmZWVsIGEgZ3JlYXQgZGVhbCBvZiBkYXRhIHdhcyBvbW1pdHRlZCBjb25zaWRlcnNpbmcgb25seSAyIHJlZ2lvbnMgd2VyZSByZXByZXNlbnRlZCBoZXJlLg0KDQo0LiBIb3cgeW91ciBjaG9pY2Ugb2YgYWxnb3JpdGhtIHJlbGF0ZXMgdG8gdGhlIGRhdGFzZXRzICh3YXMgeW91ciBjaG9pY2Ugb2YgYWxnb3JpdGhtIGltcGFjdGVkIGJ5IHRoZSBkYXRhc2V0cyB5b3UgY2hvc2UpPw0KDQpJIGNob3NlIHNpbXBsZSByZWdyZXNzaW9uIHdoZW4gSSBmaWd1cmVkIHRoZSBkYXRhIGhhZCBtdWx0aWNvbGxpbmVhcml0eSBhbmQgYXNzdW1lZCB0aGF0IG15IHRyYW5zZm9ybWF0aW9ucyB3b3VsZCBub3QgZG8gbXVjaCB0byBtYWtlIHRoZSBkYXRhIGEgYmV0dGVyIGZpdC4NCg0KNS4gV2hpY2ggcmVzdWx0IHdpbGwgeW91IHRydXN0IGlmIHlvdSBuZWVkIHRvIG1ha2UgYSBidXNpbmVzcyBkZWNpc2lvbj8NCg0KU2ltcGxlIHJlZ3Jlc3Npb24uIEkgd291bGQgaGF2ZSB0byBjaXJjbGUgYmFjayB0byBidXNpbmVzcyBhbmQgZXhwbGFpbiB3aHkgdGhlIGRhdGEgd291bGQgbm90IGJlIGEgc3VpdGFibGUgZml0IGZvciBwcmVkaWN0aW9uIG9yIGFuYWx5c2lzLg0KDQo2LiBEbyB5b3UgdGhpbmsgYW4gYW5hbHlzaXMgY291bGQgYmUgcHJvbmUgdG8gZXJyb3JzIHdoZW4gdXNpbmcgdG9vIG11Y2ggZGF0YSwgb3Igd2hlbiB1c2luZyB0aGUgbGVhc3QgYW1vdW50IHBvc3NpYmxlPw0KDQpEZWZpbml0ZWx5IHRoZSBEZWNpc2lvbiBUcmVlLCBidXQgdG8gYmUgZnJhbmsgb3BlcmF0b3IgZXJyb3JzIGFuZCB1bmZhbWlsaWFyaXR5IHdpdGggdGhpcyBtZXRob2QgaXMgZGVmaW5pdGVseSBhIG1ham9yIGZhY3RvciB0byBhY2NvdW50IGZvci4NCg0KNy4gSG93IGRvZXMgdGhlIGFuYWx5c2lzIGJldHdlZW4gZGF0YSBzZXRzIGNvbXBhcmU/DQoNCk5vLiBBZnRlciBJIGFzc2VzcyB0aGUgbGFjayBvZiB1c2VmdWxuZXNzIG9mIHRoZSBudW1lcmljIHZhbHVlcyBJIG9wdGVkIHRvIG1ha2UgdGhpcyBhIGxlYXJuaW5nIG9wcG9ydHVuaXR5IGluIHVzaW5nIGEgZGVjaXNpb24gdHJlZSBhbmQgZmFtaWxpYXJpemluZyBteXNlbGYgd2l0aCBpdCBmb3IgZnV0dXJlIHVzZS4NCg0KYGBge3J9DQpybShsaXN0ID0gbHMocGF0dGVybiA9ICJfdG1wJCIpKQ0KYGBgDQoNCg0K