Introduction
The data is related with direct marketing campaigns of a Portuguese
banking institution. The marketing campaigns were based on phone calls.
Often, more than one contact to the same client was required. The data
collected from these marketing capaigns was collected from May 2008 to
November 2010. There is a total number of 45,211 observation in this
data set. The data set consists of 17 variables, including the response
variable with the name ‘y’. A description of the predictor and outcome
features are below:
1 - age (numeric)
2 - job : Job type (categorical): “admin.”, “unknown”, “unemployed”,
“management”, “housemaid”, “entrepreneur”, “student”, “blue-collar”,
“self-employed”, “retired”, “technician”, “services”
3 - marital : Marital status (categorical): “married”, “divorced”,
“single”
4 - education (categorical):
“unknown”,“secondary”,“primary”,“tertiary”
5 - default: Does the client have credit in default? (binary:
“yes”,“no”)
6 - balance: Average yearly balance (numeric, in euros)
7 - housing: Does the client have a housing loan? (binary:
“yes”,“no”)
8 - loan: Does the client have a personal loan? (binary:
“yes”,“no”)
9 - contact: Contact communication type (categorical):
“unknown”,“telephone”,“cellular”
10 - day: Last contact day of the month (numeric, discrete)
11 - month: Last contact month of year (categorical): “jan”, “feb”,
“mar”, “apr”, “may”, “jun”, “jul”, “aug”, “sep”, “oct”, “nov”, “dec”
12 - duration: Last contact duration (numeric, in seconds)
13 - campaign: The number of contacts performed during this campaign
and for this client (numeric, discrete)
14 - pdays: The number of days after the client was last contacted
from a previous campaign (numeric, discrete)
15 - previous: The number of contacts performed before this campaign
and for this client (numeric)
16 - poutcome: The outcome of the previous marketing campaign
(categorical): “unknown”, “other”, “failure”, “success”
17 - y oOutcome class variable): Has the client subscribed a term
deposit? (binary: “yes”,“no”)
A public link to the data can be found here: https://archive.ics.uci.edu/dataset/222/bank+marketing
When looking at this dataset, two areas we would want to explore
through linear and logistic regression.
If there is a association with any of the variables and how long
of the duration of a call (Linear).
Predict if a client has subscribed a term deposit after direct
marketing campaigns (Logistic).
#Load the sample data
BankMarketing = read.csv("https://pengdsci.github.io/datasets/BankMarketing/BankMarketingCSV.csv")[, -1]
Handling Missing
Values
This dataset does not contain any missing values, therefore we do not
have to drop any rows or input any values.
EDA and Feature
Engineering
In oder to perfom so EDA, we must have a basic understanding of the
data. A summary of teh data is printed below.
#Summarized descriptive statistics for all variables in the data set
summary(BankMarketing)
## age job marital education
## Min. :18.00 Length:45211 Length:45211 Length:45211
## 1st Qu.:33.00 Class :character Class :character Class :character
## Median :39.00 Mode :character Mode :character Mode :character
## Mean :40.94
## 3rd Qu.:48.00
## Max. :95.00
## default balance housing loan
## Length:45211 Min. : -8019 Length:45211 Length:45211
## Class :character 1st Qu.: 72 Class :character Class :character
## Mode :character Median : 448 Mode :character Mode :character
## Mean : 1362
## 3rd Qu.: 1428
## Max. :102127
## contact day month duration
## Length:45211 Min. : 1.00 Length:45211 Min. : 0.0
## Class :character 1st Qu.: 8.00 Class :character 1st Qu.: 103.0
## Mode :character Median :16.00 Mode :character Median : 180.0
## Mean :15.81 Mean : 258.2
## 3rd Qu.:21.00 3rd Qu.: 319.0
## Max. :31.00 Max. :4918.0
## campaign pdays previous poutcome
## Min. : 1.000 Min. : -1.0 Min. : 0.0000 Length:45211
## 1st Qu.: 1.000 1st Qu.: -1.0 1st Qu.: 0.0000 Class :character
## Median : 2.000 Median : -1.0 Median : 0.0000 Mode :character
## Mean : 2.764 Mean : 40.2 Mean : 0.5803
## 3rd Qu.: 3.000 3rd Qu.: -1.0 3rd Qu.: 0.0000
## Max. :63.000 Max. :871.0 Max. :275.0000
## y
## Length:45211
## Class :character
## Mode :character
##
##
##
We can see from the above summary table that the distribution of some
of the numeric variables is skewed and contains outliers that need to be
further explored.
Single Variable
Distribution
The distribution for our continuous numerical variables for
average_monthly_hours and time_spend_company are shown below. Time spent
is skewed to the right, and is not normal. Average monthly hours so not
too bad, and can be deemed approxiatly normal.
In order to examine and determine the outcome of any of the outliers
and looking at the skewness of certain numerical variables, such as
“duration”, discretization will be used to divide the different
categorical varibles into groups. This variable should be discretized
due to thenumber of high outliers it contains. In looking at this
variable’s distribution, the three groups that were created (0-180,
181-319, and 320+) seem similar enough in the frequency of client
observations. This new variable will now be used for building future
models. The histogram can be seen below.
# histogram showing the distribution of the duration variable
hist(BankMarketing$duration, xlab = "Duration", ylab = "count", main = "Durations of Last Contact")

# New grouping variable for duration
BankMarketing$grp.duration <- ifelse(BankMarketing$duration <= 180, '0-180',
ifelse(BankMarketing$duration >= 320, '320+', '181-319'))
Now we want to look at some of the categorical and binary features.
When looking at the distribution of contacts performed during the
campain, it is Skewed to the right. This means that groups should be
created. For campaign, the value of 1 contact should be its own group
since it has the highest frequency of observations. Values 2 & 3
number of contacts combined have close to the same frequency, so they
should be paired together in their own group. The remaining observations
should be combined into the final group.
When looking at pdays, the value of -1 is an indicator that a client
was not previously contacted before the campaign. Since this makes up
most of the obersvations, it will become its own group. The rest of the
observations were split into groups of 1-200 days and 200 days or
more.
The previous variable was also split into 3 groups. The value of 0
contacts is one group since it has the most observations. The values of
1 to 3 contacts is another category since they both make a fair amount
of the observations. Same goes for observations with 4 or more
contacts.
All of these bar plots can be seen below.
# barplot showing the distribution of the campaign variable
marketcampaigns = table(BankMarketing$campaign)
barplot(marketcampaigns, main = "Distribution of Contacts Performed During Campaign", xlab = "Number of Contacts")

# barplot showing the distribution of the pdays variable
dayspassed = table(BankMarketing$pdays)
barplot(dayspassed, main = "Distribution of Days Passed After Client Last Contacted (Pdays) ", xlab = "Number of Days")

# barplot showing the distribution of the previous variable
prev = table(BankMarketing$previous)
barplot(prev, main = "Distribution of Previous Contacts", xlab = "Number of Contacts")
These new grouped variables will be used in future model build. The
categories for each variable are as follows:
campaign: 1, 2-3, 4+ pdays: -1, 1-199, 200+ previous: 0, 1-3, 4+
# New grouping variable for campaign
BankMarketing$grp.campaign <- ifelse(BankMarketing$campaign <= 1, '1',
ifelse(BankMarketing$campaign >= 4, '4+', '2-3'))
# New grouping variable for pdays
BankMarketing$grp.pdays <- ifelse(BankMarketing$pdays <= -1, 'Client Not Previously Contacted', ifelse(BankMarketing$pdays >= 200, '200+', '1-199'))
# New grouping variable for previous
BankMarketing$grp.previous <- ifelse(BankMarketing$previous <= 0, '0',
ifelse(BankMarketing$previous > 4, '4+', '1-3'))
Now we move onto categorical varibles.
The categorical variable of month has also been discretized by
seasons since the bar plot below is also skewed for certain months.
Also, handling smaller groups into seasons is easier than hanling 12
months. A new feature called “seasons” was created.
# barplot showing the distribution of the month variable
seasons = table(BankMarketing$month)
barplot(seasons, main = "Distribution of Number of Contacts by Month", xlab = "Number of Contacts")

# New grouping variable for month
BankMarketing$grp.month = ifelse(BankMarketing$month == " jan", "winter", ifelse(BankMarketing$month == " feb", "winter", ifelse(BankMarketing$month == " mar", "spring", ifelse(BankMarketing$month == " apr", "spring", ifelse(BankMarketing$month == " may", "spring", ifelse(BankMarketing$month == " jun", "summer", ifelse(BankMarketing$month == " jul", "summer", ifelse(BankMarketing$month == " aug", "summer", ifelse(BankMarketing$month == " sep", "fall", ifelse(BankMarketing$month == " oct", "fall", ifelse(BankMarketing$month == " nov", "fall", "winter")))))))))))
# barplot showing the distribution of the job variable
jobcategory = table(BankMarketing$job)
barplot(jobcategory, main = "Distribution of Job Type", xlab = "Number of Clients in Each Job")

# New grouping variable for job
BankMarketing$grp.job = ifelse(BankMarketing$job == " unknown", "not working", ifelse(BankMarketing$job == " unemployed", "not working", ifelse(BankMarketing$job == " retired", "not working", ifelse(BankMarketing$job == " blue-collar", "workers", ifelse(BankMarketing$job == " entrepreneur", "bosses", ifelse(BankMarketing$job == " housemaid", "workers", ifelse(BankMarketing$job == " management", "bosses", ifelse(BankMarketing$job == " self-employed", "bosses", ifelse(BankMarketing$job == " services", "white-collar", ifelse(BankMarketing$job == " technician", "white-collar", ifelse(BankMarketing$job == " student", "not working", "white-collar")))))))))))
Now that we have Now that the variables have been discretized, and
created new variables, the dataset now must be cleaned to reflect those
changes.
# Assembling the discretized variables and other variables to make the modeling data set
var.names = c("age", "balance", "day", "grp.job", "marital", "education", "default", "housing", "loan", "contact", "grp.month", "grp.duration", "grp.campaign", "grp.pdays", "grp.previous", "poutcome", "y")
BankMarketingCampaign = BankMarketing[, var.names]
Assessing Pairwised
Relationship
We want to see if there is any linear association between the numeric
variables. A Pairwise scatter plot like below, is a good visual. This
scatterplot blow looks at the data.
# Pair-wise scatter plot for numeric variables
ggpairs(BankMarketingCampaign, # Data frame
columns = 1:3, # Columns
aes(color = y, # Color by group (cat. variable)
alpha = 0.5))
We see that none of the numeric variables appear to be significantly
correlated when looking at the numbers. But, the stacked density cures
and no completely overlapping, indicating a week correlation between the
numeric variables and our response ‘y’. The strongest but ’weak
correlation we can see through the plots is age and balance.
Now that we looked at the numeric variables, we can turn the
attention to categorical variables and dependency. In order to see
dependency, mosaic plots will be shown below.
# Mosaic plots to show categorical variable dependency to the response.
par(mfrow = c(2,2))
mosaicplot(grp.job ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="job vs term deposit ")
mosaicplot(marital ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="marital vs term deposit ")
mosaicplot(education ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="education vs term deposit ")
mosaicplot(default ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="default vs term deposit ")

# Mosaic plots to show categorical variable dependency to the response.
par(mfrow = c(2,2))
mosaicplot(housing ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="housing vs term deposit ")
mosaicplot(loan ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="loan vs term deposit ")
mosaicplot(contact ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="contact vs term deposit ")
mosaicplot(grp.month ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="month vs term deposit ")

# Mosaic plots to show categorical variable dependency to the response.
par(mfrow = c(3,2))
mosaicplot(grp.duration ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="duration vs term deposit ")
mosaicplot(grp.campaign ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="campaign vs term deposit ")
mosaicplot(grp.pdays ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="pdays vs term deposit ")
mosaicplot(grp.previous ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="previous vs term deposit ")
mosaicplot(poutcome ~ y, data=BankMarketingCampaign,col=c("Blue","Red"), main="poutcome vs term deposit ")

Predictive Modeling
with Logistic Regression
The revised data from the EDA done above will be used to run
different logistic regression models. The optimal final model will be
found from canidate models, which will be used to calculate
probabilities for predicting whether or not a client has subscribed
after direct marketing campaigns.
We can use a logistical model for predicting whether or not a client
has subscribed a term deposit after direct marketing campaigns.The
variable, y, which tells whether a client has subscribed a term deposit,
acts as the binary response variable of all the logistic models. The
rest of the variables, including the new discretized variables, of the
revised data set act as the predictor variables that will possibly
affect the response ‘y’.
In order to perform proper modeling, some categorical and binary
variables had to be changed, including the response (y) and the ones
changed in the EDA, to have numerical labels, thereby making them easier
to use for modeling.
The first logistic regression model that will be built is an initial
full model that contains all predictors variables of the data set.
Automatic variable selection will then be used to find a final model. In
looking at the p-values of the variables in the initial model, those
that are insignificant at the 0.05 level will be dropped.
The variables remaining that are either statistically significant or
important for the model will be used to create a sort of reduced model.
A third and final model, that is between the full and reduced models,
will then be found. Performance of predictive power will be analyzed for
all predictor variables as well as their association to the
response.
Finally, this final model will be used to calculate predictive
probability for values of the response variable. When values of
predictor values are entered, the predicted value of whether or not a
client has subscribed a term deposit (either Yes or No) is given.
Turning features in
Discrete Variables
All the categorical and binary varibles including the response must
be changed into discrete numerical varibles. It will be as as
follows:
y (response): 0=no, 1=yes
grp.job: 0=not working, 1=workers, 2=bosses, 3=white-collar
marital: 0=divorced, 1=single, 2=married
education: 0=unknown, 1=primary, 2=secondary, 3=tertiary
housing: 0=no, 1=yes
loan: 0=no, 1=yes
contact: 0=unknown, 1=telephone, 2=cellular
grp.month: 0=winter, 1=spring, 2=summer, 3=fall
grp.duration: 0=(0-180), 1=(181-319), 2=320+
grp.campaign: 0=1, 1=(2-3), 2=4+
grp.pdays: 0=Client Not Previously Contacted, 1=(1-199), 2=200+
grp.previous: 0=0, 1=(1-3), 2=4+
# Create numerical value labels for categorical variables
BankMarketingCampaign$y <- factor(BankMarketingCampaign$y, levels = c(" no", " yes"), labels = c("0", "1"))
BankMarketingCampaign$grp.job <- factor(BankMarketingCampaign$grp.job, levels = c("not working", "workers", "bosses", "white-collar"), labels = c("0", "1", "2", "3"))
BankMarketingCampaign$marital <- factor(BankMarketingCampaign$marital, levels = c(" divorced", " single", " married"), labels = c("0", "1", "2"))
BankMarketingCampaign$education <- factor(BankMarketingCampaign$education, levels = c(" unknown", " primary", " secondary", " tertiary"), labels = c("0", "1", "2", "3"))
BankMarketingCampaign$housing <- factor(BankMarketingCampaign$housing, levels = c(" no", " yes"), labels = c("0", "1"))
BankMarketingCampaign$loan <- factor(BankMarketingCampaign$loan, levels = c(" no", " yes"), labels = c("0", "1"))
BankMarketingCampaign$contact <- factor(BankMarketingCampaign$contact, levels = c(" unknown", " telephone", " cellular"), labels = c("0", "1", "2"))
BankMarketingCampaign$grp.month <- factor(BankMarketingCampaign$grp.month, levels = c("winter", "spring", "summer", "fall"), labels = c("0", "1", "2", "3"))
BankMarketingCampaign$grp.duration <- factor(BankMarketingCampaign$grp.duration, levels = c("0-180", "181-319", "320+"), labels = c("0", "1", "2"))
BankMarketingCampaign$grp.campaign <- factor(BankMarketingCampaign$grp.campaign, levels = c("1", "2-3", "4+"), labels = c("0", "1", "2"))
BankMarketingCampaign$grp.pdays <- factor(BankMarketingCampaign$grp.pdays, levels = c("Client Not Previously Contacted", "1-199", "200+"), labels = c("0", "1", "2"))
BankMarketingCampaign$grp.previous <- factor(BankMarketingCampaign$grp.previous, levels = c("0", "1-3", "4+"), labels = c("0", "1", "2"))
Create Candidate
Models
The full/initial model containing all of the predictor variables will
be made first, with ‘y’ (whether or not a client has subscribed a term
deposit) as the response. The variables balance and default are not
included since our EDA showed that removing them from the model might
help the results.
# Create the initial full model
# Create the initial full model
initial.model = glm(y ~ age + day + grp.job + marital + education + housing + loan + contact +grp.duration +grp.campaign +grp.pdays +grp.previous, family = binomial, data = BankMarketingCampaign)
coefficient.table = summary(initial.model)$coef
kable(coefficient.table, caption = "Significance tests of logistic regression model")
Significance tests of logistic regression model
| (Intercept) |
-3.5018560 |
0.1521815 |
-23.011052 |
0.0000000 |
| age |
0.0037427 |
0.0017733 |
2.110553 |
0.0348108 |
| day |
-0.0055270 |
0.0020224 |
-2.732896 |
0.0062780 |
| grp.job1 |
-0.5356687 |
0.0623519 |
-8.591057 |
0.0000000 |
| grp.job2 |
-0.4527472 |
0.0599608 |
-7.550719 |
0.0000000 |
| grp.job3 |
-0.3669178 |
0.0546304 |
-6.716368 |
0.0000000 |
| marital1 |
0.1692006 |
0.0610871 |
2.769826 |
0.0056086 |
| marital2 |
-0.1806493 |
0.0536963 |
-3.364279 |
0.0007674 |
| education1 |
-0.2609743 |
0.0938475 |
-2.780833 |
0.0054220 |
| education2 |
-0.1221068 |
0.0829586 |
-1.471899 |
0.1410481 |
| education3 |
0.1775338 |
0.0866458 |
2.048960 |
0.0404660 |
| housing1 |
-0.7303848 |
0.0366351 |
-19.936773 |
0.0000000 |
| loan1 |
-0.5591949 |
0.0540381 |
-10.348154 |
0.0000000 |
| contact1 |
0.9550897 |
0.0817113 |
11.688589 |
0.0000000 |
| contact2 |
1.0053064 |
0.0523061 |
19.219696 |
0.0000000 |
| grp.duration1 |
1.3339942 |
0.0503148 |
26.512978 |
0.0000000 |
| grp.duration2 |
2.7044986 |
0.0458332 |
59.007422 |
0.0000000 |
| grp.campaign1 |
-0.3253327 |
0.0364818 |
-8.917665 |
0.0000000 |
| grp.campaign2 |
-0.5201170 |
0.0505253 |
-10.294181 |
0.0000000 |
| grp.pdays1 |
1.4641585 |
0.0758033 |
19.315236 |
0.0000000 |
| grp.pdays2 |
0.6043423 |
0.0869219 |
6.952701 |
0.0000000 |
| grp.previous1 |
-0.2153398 |
0.0777604 |
-2.769273 |
0.0056181 |
We can see that there are some insignificant predictor variables, and
they should be dropped from the model to create a reduced model. Using
the step() function, we will now find reduced and final models. The
final best model will be a model that is between the full and reduced
models.
# Creating the reduced and final models
full.model = initial.model # the *biggest model* that includes all predictor variables
reduced.model = glm(y ~ day + grp.job + marital + housing + loan + contact + grp.duration + grp.campaign + grp.previous, family = binomial, data = BankMarketingCampaign)
final.model = step(full.model,
scope=list(lower=formula(reduced.model),upper=formula(full.model)),
data = BankMarketingCampaign,
direction = "backward",
trace = 0) # trace = 0: suppress the detailed selection process
final.model.coef = summary(final.model)$coef
kable(final.model.coef, caption = "Summary table of significant tests")
Summary table of significant tests
| (Intercept) |
-3.5018560 |
0.1521815 |
-23.011052 |
0.0000000 |
| age |
0.0037427 |
0.0017733 |
2.110553 |
0.0348108 |
| day |
-0.0055270 |
0.0020224 |
-2.732896 |
0.0062780 |
| grp.job1 |
-0.5356687 |
0.0623519 |
-8.591057 |
0.0000000 |
| grp.job2 |
-0.4527472 |
0.0599608 |
-7.550719 |
0.0000000 |
| grp.job3 |
-0.3669178 |
0.0546304 |
-6.716368 |
0.0000000 |
| marital1 |
0.1692006 |
0.0610871 |
2.769826 |
0.0056086 |
| marital2 |
-0.1806493 |
0.0536963 |
-3.364279 |
0.0007674 |
| education1 |
-0.2609743 |
0.0938475 |
-2.780833 |
0.0054220 |
| education2 |
-0.1221068 |
0.0829586 |
-1.471899 |
0.1410481 |
| education3 |
0.1775338 |
0.0866458 |
2.048960 |
0.0404660 |
| housing1 |
-0.7303848 |
0.0366351 |
-19.936773 |
0.0000000 |
| loan1 |
-0.5591949 |
0.0540381 |
-10.348154 |
0.0000000 |
| contact1 |
0.9550897 |
0.0817113 |
11.688589 |
0.0000000 |
| contact2 |
1.0053064 |
0.0523061 |
19.219696 |
0.0000000 |
| grp.duration1 |
1.3339942 |
0.0503148 |
26.512978 |
0.0000000 |
| grp.duration2 |
2.7044986 |
0.0458332 |
59.007422 |
0.0000000 |
| grp.campaign1 |
-0.3253327 |
0.0364818 |
-8.917665 |
0.0000000 |
| grp.campaign2 |
-0.5201170 |
0.0505253 |
-10.294181 |
0.0000000 |
| grp.pdays1 |
1.4641585 |
0.0758033 |
19.315236 |
0.0000000 |
| grp.pdays2 |
0.6043423 |
0.0869219 |
6.952701 |
0.0000000 |
| grp.previous1 |
-0.2153398 |
0.0777604 |
-2.769273 |
0.0056181 |
Prediction
Predictive
Probability Analysis for Clients Subscribing Term Deposits
Now that a final model has been created, we can predict whether or
not a client has subscribed a term deposit based on given values of the
predictor variables in the final model associated with two clients. A
threshold probability of 0.5 is used to predict the response value.
# Predicting Response Value for Banking Client Given Variable Values for the Final Model
mynewdata = data.frame(age=c(34,64),
day = c(3,5),
grp.job = c("1","1"),
marital = c("1","1"),
education = c("1","3"),
housing = c("1","1"),
loan = c("0","0"),
contact = c("1","0"),
grp.month = c("3","1"),
grp.duration = c("2","1"),
grp.campaign = c("0","1"),
grp.pdays = c("2","2"),
grp.previous = c("1","0"))
pred.success.prob = predict(final.model, newdata = mynewdata, type="response")
## Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type == :
## prediction from a rank-deficient fit may be misleading
## threshold probability
cut.off.prob = 0.5
pred.response = ifelse(pred.success.prob > cut.off.prob, 1, 0) # This predicts the response
# Add the new predicted response to Mynewdata
mynewdata$Pred.Response = pred.response
kable(mynewdata, caption = "Predicted Value of Response with given cut-off")
Predicted Value of Response with given cut-off
| 34 |
3 |
1 |
1 |
1 |
1 |
0 |
1 |
3 |
2 |
0 |
2 |
1 |
0 |
| 64 |
5 |
1 |
1 |
3 |
1 |
0 |
0 |
1 |
1 |
1 |
2 |
0 |
0 |
Looking at the predicted response, we can see that neither of these
clients will suscribe to the marketing campaign. ## Model Selection
Now that we have our canidate models. It is time to perform some
model selction using the ROC curve and AUC.
Since our sample is relatively large, we will randomly split the
overall data set into two data sets. 70% of the data will be put in a
training data set for training and validating models. The other 30% goes
into a testing data set used for testing the final model. The value
labels of the response (yes/no) used for testing and validation data
will be removed when calculating the accuracy measures later.
## Recode response variable: yes = 1 and no = 0
yes.id = which(BankMarketingCampaign$y == "1")
no.id = which(BankMarketingCampaign$y == "0")
## Creating the training and testing data sets
BankMarketingCampaign$y.subscribe = 1
BankMarketingCampaign$y.subscribe[no.id] = 0
var.names = c("age", "day", "grp.job", "marital","education","housing","loan","contact","grp.month", "grp.duration","grp.campaign","grp.pdays","grp.previous", "y.subscribe" )
BankMarketingCampaign = BankMarketingCampaign[, var.names]
nn = dim(BankMarketingCampaign)[1]
train.id = sample(1:nn, round(nn*0.7), replace = FALSE)
####
training = BankMarketingCampaign[train.id,]
testing = BankMarketingCampaign[-train.id,]
Cut-off Probability
Search and Accuracy Score
In order to find an optimal cut-off probability, a sequence of 20
candidate cut-off probabilities will be defined. Then, a 5-fold
cross-validation will be performed to find the optimal cut-off
probability of the final model. All three models created will be used to
find the optimal cut-off. This is shown below.
n0 = dim(training)[1]/5
# candidate cut off prob
cut.0ff.prob = seq(0,1, length = 22)[-c(1,22)]
# null vector for storing prediction accuracy
pred.accuracy = matrix(0,ncol=20, nrow=5, byrow = T)
## 5-fold CV
for (i in 1:5){
valid.id = ((i-1)*n0 + 1):(i*n0)
valid.data = training[valid.id,]
train.data = training[-valid.id,]
train.model = glm(y.subscribe ~ age + day + grp.job + marital + education + housing + loan + contact + grp.duration + grp.campaign + grp.pdays + grp.previous, family = binomial(link = logit), data = train.data)
####
pred.prob = predict.glm(train.model, valid.data, type = "response")
# define confusion matrix and accuracy
for(j in 1:20){
#pred.subscribe = rep(0,length(pred.prob))
valid.data$pred.subscribe = as.numeric(pred.prob > cut.0ff.prob[j])
a11 = sum(valid.data$pred.subscribe == valid.data$y.subscribe)
pred.accuracy[i,j] = a11/length(pred.prob)
}
}
##
avg.accuracy = apply(pred.accuracy, 2, mean)
max.id = which(avg.accuracy ==max(avg.accuracy))
### visual representation
tick.label = as.character(round(cut.0ff.prob,2))
plot(1:20, avg.accuracy, type = "b",
xlim=c(1,20),
ylim=c(0.5,1),
axes = FALSE,
xlab = "Cut-off Probability",
ylab = "Accuracy",
main = "5-fold CV performance"
)
axis(1, at=1:20, label = tick.label, las = 2)
axis(2)
segments(max.id, 0.5, max.id, avg.accuracy[max.id], col = "red")
text(max.id, avg.accuracy[max.id]+0.03, as.character(round(avg.accuracy[max.id],4)), col = "red", cex = 0.8)
The above figure indicates that the optimal cut-off probability that
yields the best accuracy is 0.52.
test.model = glm(y.subscribe ~ age + day + grp.job + marital + education + housing + loan + contact + grp.month + grp.duration + grp.campaign + grp.pdays + grp.previous, family = binomial(link = logit), data = training)
newBankingTestingData = data.frame(age= testing$age, day= testing$day, grp.job= testing$grp.job, marital= testing$marital, education= testing$education, housing= testing$housing, loan= testing$loan, contact= testing$contact, grp.month= testing$grp.month, grp.duration= testing$grp.duration, grp.campaign= testing$grp.campaign, grp.pdays= testing$grp.pdays, grp.previous= testing$grp.previous)
pred.prob.test = predict.glm(test.model, newBankingTestingData, type = "response")
## Assessing Model Accuracy
testing$test.subscribe = as.numeric(pred.prob.test > 0.22)
a11 = sum(testing$test.subscribe == testing$y.subscribe)
test.accuracy = a11/length(pred.prob.test)
kable(as.data.frame(test.accuracy), align='c')
Here in our accuracy test we find that it is accurate 84.1% of the
time. This indcates there is no under-fitting for our model.
Local and Global ROC
Metrics
Using the optimal cut-off probability of 0.57, which was found above,
we will now report the local measures using our testing data. This
includes specificity and sensitivity based on each of these cut-offs for
the 20 sub-intervals.
# Looking at sensitivity and specificity performance measurements
testing$test.subscribe = as.numeric(pred.prob.test > 0.52)
### components for defining various measures
p0.a0 = sum(testing$test.subscribe ==0 & testing$y.subscribe ==0)
p0.a1 = sum(testing$test.subscribe ==0 & testing$y.subscribe ==1)
p1.a0 = sum(testing$test.subscribe ==1 & testing$y.subscribe ==0)
p1.a1 = sum(testing$test.subscribe ==1 & testing$y.subscribe ==1)
###
sensitivity = p1.a1 / (p1.a1 + p0.a1)
specificity = p0.a0 / (p0.a0 + p1.a0)
###
precision = p1.a1 / (p1.a1 + p1.a0)
recall = sensitivity
F1 = 2*precision*recall/(precision + recall)
metric.list = cbind(sensitivity = sensitivity,
specificity = specificity,
precision = precision,
recall = recall,
F1 = F1)
kable(as.data.frame(metric.list), align='c', caption = "Local performance metrics")
Local performance metrics
| 0.152322 |
0.9844325 |
0.5694444 |
0.152322 |
0.2403517 |
The sensitivity indicates the probability of those clients who are
said to have subscribed a term deposit at the banking institution out of
those who actually did is about 8-12%. The specificity indicates the
probability of those clients who are said to have not subscribed a term
deposit at the banking institution out of those who actually did not is
about 97.3%.
ROC Global Measure
Analysis
For the last part of this section, a ROC (receiver operating
characteristic) curve will be plotted by selecting a sequence of
decision thresholds and calculating corresponding sensitivity and
specificity.
# Creating ROC curves for sensitivity and (1-specificity) for all 3 models
## Full Model
cut.off.seq = seq(0, 1, length = 100)
sensitivity.vec.full = NULL
specificity.vec.full = NULL
###
training.model.full = glm(y.subscribe ~ age + day + grp.job + marital + education + housing + loan + contact + grp.month + grp.duration + grp.campaign + grp.pdays + grp.previous, family = binomial(link = logit), data = training)
newBankTrainingData.full = data.frame(age= training$age, day= training$day, grp.job= training$grp.job, marital= training$marital, education= training$education, housing= training$housing, loan= training$loan, contact= training$contact, grp.month= training$grp.month, grp.duration= training$grp.duration, grp.campaign= training$grp.campaign, grp.pdays= training$grp.pdays, grp.previous= training$grp.previous)
pred.prob.train.full = predict.glm(training.model.full, newBankTrainingData.full, type = "response")
## Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type == :
## prediction from a rank-deficient fit may be misleading
for (i in 1:100){
training$train.subscribe = as.numeric(pred.prob.train.full > cut.off.seq[i])
### components for defining various measures
TN = sum(training$train.subscribe == 0 & training$y.subscribe == 0)
FN = sum(training$train.subscribe == 0 & training$y.subscribe == 1)
FP = sum(training$train.subscribe == 1 & training$y.subscribe == 0)
TP = sum(training$train.subscribe == 1 & training$y.subscribe == 1)
###
sensitivity.vec.full[i] = TP / (TP + FN)
specificity.vec.full[i] = TN / (TN + FP)
}
one.minus.spec.full = 1 - specificity.vec.full
sens.vec.full = sensitivity.vec.full
## A better approx of ROC, need library {pROC}
prediction.full = pred.prob.train.full
category.full = training$y.subscribe == 1
ROCobj.full <- roc(category.full, prediction.full)
AUCfull = round(auc(ROCobj.full),4)
## Reduced Model
cut.off.seq = seq(0, 1, length = 100)
sensitivity.vec.reduced = NULL
specificity.vec.reduced = NULL
###
training.model.reduced = glm(y.subscribe ~ day + grp.job + marital + housing + loan + contact + grp.duration + grp.campaign + grp.previous, family = binomial(link = logit), data = training)
newBankTrainingData.reduced = data.frame(day= training$day, grp.job= training$grp.job, marital= training$marital, housing= training$housing, loan= training$loan, contact= training$contact, grp.duration= training$grp.duration, grp.campaign= training$grp.campaign, grp.previous= training$grp.previous)
pred.prob.train.reduced = predict.glm(training.model.reduced, newBankTrainingData.reduced, type = "response")
for (i in 1:100){
training$train.subscribe = as.numeric(pred.prob.train.reduced > cut.off.seq[i])
### components for defining various measures
TN.reduced = sum(training$train.subscribe == 0 & training$y.subscribe == 0)
FN.reduced = sum(training$train.subscribe == 0 & training$y.subscribe == 1)
FP.reduced = sum(training$train.subscribe == 1 & training$y.subscribe == 0)
TP.reduced = sum(training$train.subscribe == 1 & training$y.subscribe == 1)
###
sensitivity.vec.reduced[i] = TP.reduced / (TP.reduced + FN.reduced)
specificity.vec.reduced[i] = TN.reduced / (TN.reduced + FP.reduced)
}
one.minus.spec.reduced = 1 - specificity.vec.reduced
sens.vec.reduced = sensitivity.vec.reduced
## A better approx of ROC, need library {pROC}
prediction.reduced = pred.prob.train.reduced
category.reduced = training$y.subscribe == 1
ROCobj.reduced <- roc(category.reduced, prediction.reduced)
AUCreduced = round(auc(ROCobj.reduced),4)
# Final Model
cut.off.seq = seq(0, 1, length = 100)
sensitivity.vec.final = NULL
specificity.vec.final = NULL
###
training.model.final = glm(y.subscribe ~ age + day + grp.job + marital + education + housing + loan + contact + grp.month + grp.duration + grp.campaign + grp.pdays + grp.previous, family = binomial(link = logit), data = training)
newBankTrainingData.final = data.frame(age= training$age, day= training$day, grp.job= training$grp.job, marital= training$marital, education= training$education, housing= training$housing, loan= training$loan, contact= training$contact, grp.month= training$grp.month, grp.duration= training$grp.duration, grp.campaign= training$grp.campaign, grp.pdays= training$grp.pdays, grp.previous= training$grp.previous)
pred.prob.train.final = predict.glm(training.model.final, newBankTrainingData.final, type = "response")
## Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type == :
## prediction from a rank-deficient fit may be misleading
for (i in 1:100){
training$train.subscribe = as.numeric(pred.prob.train.final > cut.off.seq[i])
### components for defining various measures
TN.final = sum(training$train.subscribe == 0 & training$y.subscribe == 0)
FN.final = sum(training$train.subscribe == 0 & training$y.subscribe == 1)
FP.final = sum(training$train.subscribe == 1 & training$y.subscribe == 0)
TP.final = sum(training$train.subscribe == 1 & training$y.subscribe == 1)
###
sensitivity.vec.final[i] = TP.final / (TP.final + FN.final)
specificity.vec.final[i] = TN.final / (TN.final + FP.final)
}
one.minus.spec.final = 1 - specificity.vec.final
sens.vec.final = sensitivity.vec.final
## A better approx of ROC, need library {pROC}
prediction.final = pred.prob.train.final
category.final = training$y.subscribe == 1
ROCobj.final <- roc(category.final, prediction.final)
AUCfinal = round(auc(ROCobj.final),4)
## Visual Representation of all 3 ROCs
par(pty = "s") # make a square figure
plot(one.minus.spec.full, sens.vec.full, type = "l", xlim = c(0,1), xlab ="1 - Specificity", ylab = "Sensitivity", main = "ROC Curves of Logistic Regression Models", lwd = 2, col = "blue")
lines(one.minus.spec.reduced, sens.vec.reduced, col = "green")
lines(one.minus.spec.final, sens.vec.final, col = "orange")
segments(0,0,1,1, col = "red", lty = 2, lwd = 2)
#AUCfull = round(sum(sens.vec.full*(one.minus.spec.full[-101]-one.minus.spec.full[-1])),4)
#AUCreduced = round(sum(sens.vec.reduced*(one.minus.spec.reduced[-101]-one.minus.spec.reduced[-1])),4)
#AUCfinal = round(sum(sens.vec.final*(one.minus.spec.final[-101]-one.minus.spec.final[-1])),4)
text(0.8, 0.5, paste("AUC = ", AUCfull), col = "blue")
text(0.8, 0.4, paste("AUC = ", AUCreduced), col = "green")
text(0.8, 0.3, paste("AUC = ", AUCfinal), col = "orange")
legend("bottomright", c("ROC of the Full Model", "ROC of the Reduced Model", "ROC of the Final Model"), lty=c(1,2), col = c("blue", "green", "orange"), bty = "n", cex = 0.8)

The area under the curve (AUC) for the reduced model and the ROC
curve is less than the other two graphs. Higher AUC indicates the model
for that curve is better. Therefore, the reduced model is not the best
model to use and is no longer considered. But, it should be noted that
it is not far off the other 2 models.
Looking at the initial and final models, they have the same curve
since both models contain all feature variables used in the initial
model. Out of these other two models, the final model works better
compared to the initial model. It is the parsimonious model. It has been
proven to be accurate in modeling performance, has high specificity, and
its ROC curve is remaining away from the 45 degrees mark. Plus, the AUC
is fairly high at .8541, even though the initial model has the same
score.
Neural Network
Modeling
Now we will create another model to predict using neural
networks.
Training and testing data sets and canidate models will be made once
again for the neural network model. From them, a final model will be
constructed and plotted to show backpropagation of the neural
network
Cross-validation will also be used be used for finding an optimal
cut-off probability for assessing model performance and accuracy.
Fianlly, an ROC curve will be made for the model to look at predictive
power and compare it to the other model just like logistic
regression.
Changing varibles
back to numeric form
Unlike in our logistic regression, the neuralnet library in R needs
all feature variables to be in numeric form. Categorical variables
become encoded into dummy variables. We want to be able to find all
names for the feature variables and write them in the neural network
model formula. The data set here will not any numerical labels for the
categorical variables as with the previous model. The code is below:
# Assembling the discretized variables and other variables to make the modeling data set
var.names = c("age", "day", "grp.job", "marital", "education", "housing", "loan", "contact", "grp.month", "grp.duration", "grp.campaign", "grp.pdays", "grp.previous", "y")
BankMarketingNeural = BankMarketing[, var.names]
The numerical varibles must be rescaled and the code is below:
#re-scaling the numerical variables
BankMarketingNeural$age = (BankMarketingNeural$age-min(BankMarketingNeural$age))/(max(BankMarketingNeural$age)-min(BankMarketingNeural$age))
BankMarketingNeural$day = (BankMarketingNeural$day-min(BankMarketingNeural$day))/(max(BankMarketingNeural$day)-min(BankMarketingNeural$day))
Now we must create dummy variables for all categorical features:
# Creating a model.matrix() and looking at the names of the variable columns
BankingMtx = model.matrix(~ ., data = BankMarketingNeural)
colnames(BankingMtx)
## [1] "(Intercept)"
## [2] "age"
## [3] "day"
## [4] "grp.jobnot working"
## [5] "grp.jobwhite-collar"
## [6] "grp.jobworkers"
## [7] "marital married"
## [8] "marital single"
## [9] "education secondary"
## [10] "education tertiary"
## [11] "education unknown"
## [12] "housing yes"
## [13] "loan yes"
## [14] "contact telephone"
## [15] "contact unknown"
## [16] "grp.monthspring"
## [17] "grp.monthsummer"
## [18] "grp.monthwinter"
## [19] "grp.duration181-319"
## [20] "grp.duration320+"
## [21] "grp.campaign2-3"
## [22] "grp.campaign4+"
## [23] "grp.pdays200+"
## [24] "grp.pdaysClient Not Previously Contacted"
## [25] "grp.previous1-3"
## [26] "grp.previous4+"
## [27] "y yes"
#Renaming the categorical variable columns in the model matrix
colnames(BankingMtx)[4] <- "grp.jobNotWorking"
colnames(BankingMtx)[5] <- "grp.jobWhiteCollar"
colnames(BankingMtx)[6] <- "grp.jobWorkers"
colnames(BankingMtx)[7] <- "maritalMarried"
colnames(BankingMtx)[8] <- "maritalSingle"
colnames(BankingMtx)[9] <- "education2nd"
colnames(BankingMtx)[10] <- "education3rd"
colnames(BankingMtx)[11] <- "educationUnknown"
colnames(BankingMtx)[12] <- "housingYes"
colnames(BankingMtx)[13] <- "loanYes"
colnames(BankingMtx)[14] <- "contactTelephone"
colnames(BankingMtx)[15] <- "contactUnknown"
colnames(BankingMtx)[16] <- "grp.monthSpring"
colnames(BankingMtx)[17] <- "grp.monthSummer"
colnames(BankingMtx)[18] <- "grp.monthWinter"
colnames(BankingMtx)[19] <- "grp.duration0To180"
colnames(BankingMtx)[20] <- "grp.duration320Plus"
colnames(BankingMtx)[21] <- "grp.campaign1"
colnames(BankingMtx)[22] <- "grp.campaign4Plus"
colnames(BankingMtx)[23] <- "grp.pdays200Plus"
colnames(BankingMtx)[24] <- "grp.pdaysCNPC"
colnames(BankingMtx)[25] <- "grp.previous0"
colnames(BankingMtx)[26] <- "grp.previous4Plus"
colnames(BankingMtx)[27] <- "yYes"
Create Candidate
Model
Here the neural network model is defined using the changed names of
the variables in the matrix.
# Defining the neural network model
columnNames = colnames(BankingMtx)
columnList = paste(columnNames[-c(1,length(columnNames))], collapse = "+")
columnList = paste(c(columnNames[length(columnNames)],"~",columnList), collapse="")
modelNeuralFormula = formula(columnList)
modelNeuralFormula
## yYes ~ age + day + grp.jobNotWorking + grp.jobWhiteCollar + grp.jobWorkers +
## maritalMarried + maritalSingle + education2nd + education3rd +
## educationUnknown + housingYes + loanYes + contactTelephone +
## contactUnknown + grp.monthSpring + grp.monthSummer + grp.monthWinter +
## grp.duration0To180 + grp.duration320Plus + grp.campaign1 +
## grp.campaign4Plus + grp.pdays200Plus + grp.pdaysCNPC + grp.previous0 +
## grp.previous4Plus
Now we will split the data into two datasets. 70% is for training the
neural network model and 30% is for testing.
# Creating the training and testing data sets for creating the model
n = dim(BankingMtx)[1]
testID = sample(1:n, round(n*0.7), replace = FALSE)
testData = BankingMtx[testID,]
trainData = BankingMtx[-testID,]
Now that we have a training and testing dataset, a single-layer
neural network model is created below.
# Creating the single-layer neural network and model
NetworkModel = neuralnet(modelNeuralFormula,
data = trainData,
hidden = 1, # single layer neural network
rep = 1, # number of replicates in training neural network
threshold = 0.01, # threshold for partial derivatives as stopping criteria.
learningrate = 0.1, # user selected rate
algorithm = "rprop+"
)
kable(NetworkModel$result.matrix)
| error |
561.4586549 |
| reached.threshold |
0.0098523 |
| steps |
1386.0000000 |
| Intercept.to.1layhid1 |
-2.0763005 |
| age.to.1layhid1 |
0.7364421 |
| day.to.1layhid1 |
-0.1827108 |
| grp.jobNotWorking.to.1layhid1 |
0.4423520 |
| grp.jobWhiteCollar.to.1layhid1 |
-0.1054039 |
| grp.jobWorkers.to.1layhid1 |
-0.1878254 |
| maritalMarried.to.1layhid1 |
-0.2473527 |
| maritalSingle.to.1layhid1 |
0.1384781 |
| education2nd.to.1layhid1 |
0.0611111 |
| education3rd.to.1layhid1 |
0.3979706 |
| educationUnknown.to.1layhid1 |
0.2486501 |
| housingYes.to.1layhid1 |
-0.9213009 |
| loanYes.to.1layhid1 |
-0.4505880 |
| contactTelephone.to.1layhid1 |
0.0687708 |
| contactUnknown.to.1layhid1 |
-1.0444169 |
| grp.monthSpring.to.1layhid1 |
0.1443116 |
| grp.monthSummer.to.1layhid1 |
-0.0988589 |
| grp.monthWinter.to.1layhid1 |
-0.4376882 |
| grp.duration0To180.to.1layhid1 |
1.3715509 |
| grp.duration320Plus.to.1layhid1 |
2.7830331 |
| grp.campaign1.to.1layhid1 |
-0.4222345 |
| grp.campaign4Plus.to.1layhid1 |
-0.4874602 |
| grp.pdays200Plus.to.1layhid1 |
-0.9464717 |
| grp.pdaysCNPC.to.1layhid1 |
-0.2810798 |
| grp.previous0.to.1layhid1 |
1.1599423 |
| grp.previous4Plus.to.1layhid1 |
1.4850339 |
| Intercept.to.yYes |
-0.0116703 |
| 1layhid1.to.yYes |
0.7258328 |
Perceptron
In order to get a sense of what is going on, a map of the
single-layer neural network is shown below:
# Plot for neural network model
plot(NetworkModel, rep="best")
logiModel = glm(factor(y) ~., family = binomial, data = BankMarketingNeural)
pander(summary(logiModel)$coefficients)
| (Intercept) |
-1.588 |
0.1378 |
-11.52 |
1.009e-30 |
| age |
0.2957 |
0.1367 |
2.163 |
0.03051 |
| day |
-0.2017 |
0.06161 |
-3.274 |
0.001062 |
| grp.jobnot working |
0.451 |
0.06017 |
7.496 |
6.574e-14 |
| grp.jobwhite-collar |
0.08888 |
0.04938 |
1.8 |
0.07187 |
| grp.jobworkers |
-0.09262 |
0.06162 |
-1.503 |
0.1328 |
| marital married |
-0.1794 |
0.05379 |
-3.336 |
0.0008495 |
| marital single |
0.1635 |
0.06121 |
2.672 |
0.007548 |
| education secondary |
0.1383 |
0.05898 |
2.344 |
0.01906 |
| education tertiary |
0.4419 |
0.06776 |
6.522 |
6.923e-11 |
| education unknown |
0.2613 |
0.094 |
2.78 |
0.005434 |
| housing yes |
-0.8169 |
0.03936 |
-20.76 |
1.034e-95 |
| loan yes |
-0.5359 |
0.05421 |
-9.886 |
4.792e-23 |
| contact telephone |
-0.0627 |
0.0684 |
-0.9167 |
0.3593 |
| contact unknown |
-1.054 |
0.0537 |
-19.63 |
7.983e-86 |
| grp.monthspring |
0.07709 |
0.05479 |
1.407 |
0.1594 |
| grp.monthsummer |
-0.1858 |
0.05287 |
-3.513 |
0.0004429 |
| grp.monthwinter |
-0.2336 |
0.06407 |
-3.646 |
0.0002669 |
| grp.duration181-319 |
1.328 |
0.05036 |
26.37 |
2.931e-153 |
| grp.duration320+ |
2.699 |
0.04588 |
58.82 |
0 |
| grp.campaign2-3 |
-0.3073 |
0.03671 |
-8.37 |
5.749e-17 |
| grp.campaign4+ |
-0.4754 |
0.05162 |
-9.21 |
3.275e-20 |
| grp.pdays200+ |
-0.8797 |
0.06761 |
-13.01 |
1.043e-38 |
| grp.pdaysClient Not Previously
Contacted |
-1.423 |
0.0767 |
-18.56 |
7.193e-77 |
| grp.previous1-3 |
-0.2155 |
0.07785 |
-2.768 |
0.005647 |
Model Performance
Testing
Cut-off Probability
Search and Accuracy Score
Cross-validation can be used in order to find the optimal cut-off
scores because sigmoid perceptron is used, Hyper-parameteres will also
be used to assess the performance of the neural network. The code is
shown below:
# Looking at optimal cut-off probabilities with cross-validation
n0 = dim(trainData)[1]/5
cut.off.score = seq(0,1, length = 22)[-c(1,22)] # candidate cut off prob
pred.accuracy = matrix(0,ncol=20, nrow=5, byrow = T) # null vector for storing prediction accuracy
## 5-fold CV
for (i in 1:5){
valid.id = ((i-1)*n0 + 1):(i*n0)
valid.data = trainData[valid.id,]
train.data = trainData[-valid.id,]
####
train.model = neuralnet(modelNeuralFormula,
data = train.data,
hidden = 1, # single layer NN
rep = 1, # number of replicates in training NN
threshold = 0.01, # threshold for partial derivatives as stopping criteria.
learningrate = 0.1, # user selected rate
algorithm = "rprop+"
)
pred.nn.score = predict(NetworkModel, valid.data)
for(j in 1:20){
#pred.status = rep(0,length(pred.nn.score))
pred.status = as.numeric(pred.nn.score > cut.off.score[j])
a11 = sum(pred.status == valid.data[,17])
pred.accuracy[i,j] = a11/length(pred.nn.score)
}
}
## Warning: Algorithm did not converge in 1 of 1 repetition(s) within the stepmax.
###
avg.accuracy = apply(pred.accuracy, 2, mean)
max.id = which(avg.accuracy ==max(avg.accuracy ))
### visual representation
tick.label = as.character(round(cut.off.score,2))
plot(1:20, avg.accuracy, type = "b",
xlim=c(1,20),
ylim=c(0.5,1),
axes = FALSE,
xlab = "Cut-off Score",
ylab = "Accuracy",
main = "5-fold CV performance"
)
axis(1, at=1:20, label = tick.label, las = 2)
axis(2)
segments(max.id, 0.5, max.id, avg.accuracy[max.id], col = "red")
text(max.id, avg.accuracy[max.id]+0.03, as.character(round(avg.accuracy[max.id],4)), col = "red", cex = 0.8)
The above figure indicates that the optimal cut-off probability that
yields the best accuracy is around 0.86.
Zooming in on the graphic above, we can see that the optimal cut-off
is around .86. ## Results and Conclusion Now that we have chosed the
best model out of the three candidate models, we can use it to predict
whether or not a client has subscribed a term deposit. Using out optimal
cut-off of .57
# Predicting Response Value for Banking Client Given Variable Values for the Final Model
pdata = data.frame(age=c(25,64),
day = c(5,5),
grp.job = c("1","2"),
marital = c("0","1"),
education = c("2","0"),
housing = c("1","1"),
loan = c("0","1"),
contact = c("0","0"),
grp.duration = c("0","1"),
grp.campaign = c("1","0"),
grp.pdays = c("1","2"),
poutcome=c("0","0"),
grp.previous = c("1","2"))
pred.success.prob = predict(final.model, newdata = pdata, type="response")
## threshold probability
cut.off.prob = 0.57
pred.response = ifelse(pred.success.prob > cut.off.prob, 1, 0) # This predicts the response
pred.response
## 1 2
## 0 0
# Add the new predicted response to pdata
#pdata$pred.response <- predict(final.model, newdata = pdata, type = "response")
pdata$Pred.Response = pred.response
kable(pdata, caption = "Predicted Value of response variable with the given cut-off probability")
Predicted Value of response variable with the given cut-off
probability
| 25 |
5 |
1 |
0 |
2 |
1 |
0 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
| 64 |
5 |
2 |
1 |
0 |
1 |
1 |
0 |
1 |
0 |
2 |
0 |
2 |
0 |
You can see that neither of the two observations will be subscribing.
More testing can be done with the testing dataset.
ROC Analysis
Now a ROC will be constructed for the neural net model based on the
training data set we split above.
nn.results = predict(NetworkModel, trainData) # Keep in mind that trainDat is a matrix!
cut0 = seq(0,1, length = 20)
SenSpe = matrix(0, ncol = length(cut0), nrow = 2, byrow = FALSE)
for (i in 1:length(cut0)){
a = sum(trainData[,"yYes"] == 1 & (nn.results > cut0[i]))
d = sum(trainData[,"yYes"] == 0 & (nn.results < cut0[i]))
b = sum(trainData[,"yYes"] == 0 & (nn.results > cut0[i]))
c = sum(trainData[,"yYes"] == 1 & (nn.results < cut0[i]))
sen = a/(a + c)
spe = d/(b + d)
SenSpe[,i] = c(sen, spe)
}
# plotting ROC
plot(1-SenSpe[2,], SenSpe[1,], type ="l", xlim=c(0,1), ylim=c(0,1),
xlab = "1 - Specificity", ylab = "Sensitivity", lty = 1,
main = "ROC Curve for Neural Network Model", col = "blue")
abline(0,1, lty = 2, col = "red")
## Calculate AUC
xx = 1-SenSpe[2,]
yy = SenSpe[1,]
width = xx[-length(xx)] - xx[-1]
height = yy[-1]
## A better approx of ROC, need library {pROC}
prediction = nn.results
category = trainData[,"yYes"] == 1
ROCobj <- roc(category, prediction)
## Warning in roc.default(category, prediction): Deprecated use a matrix as
## predictor. Unexpected results may be produced, please pass a numeric vector.
AUC.neural = auc(ROCobj)[1]
##
###
#AUC =mean(sum(width*height), sum(width*yy[-length(yy)]))
text(0.8, 0.3, paste("AUC = ", round(AUC.neural,4)), col = "purple", cex = 0.9)
legend("bottomright", c("ROC of the Model", "Random Guessing"), lty=c(1,2),
col = c("blue", "red"), bty = "n", cex = 0.8)

The above ROC curve show that the neural network model is than
guessing since the area under the curve (AUC) is significantly greater
than 0.5. Since the AUC = 0.8538 and is also significantly greater than
0.65, this means the predictive power of the model is adequate to
use.
Decsion Trees
Now we will build a third predictive model using decision trees. At
least 4 to 6 different decision trees will be constructed based on
impurity measures (Gini index and information entropy), penalty
coefficients, and costs of false negatives and positives.
An ROC curve will be used in order to find the best decision tree
model out of the candidate models. An optimal cut-off score will be
found through cross-validation for reporting predictive performance of
the final model.
Tree Algorithims are is based on conditional probabilities
statements, in order to identify certain sets of records to be used to
make a prediction of the response. Predictive performance of a decision
tree is dependent on the trained tree size. The Gini index and
information entropy are two impurity measures used to control size of a
decision for obtaining best performance. They also choose feature
variables for defining root and decision nodes, as well as how to split
the variables. Gini Index considers a split for each attribute and
measures the impurity of subgroups split by a feature variable.
The original data set, with the discretized variables, will be used.
The data will be split into a 70% data set for training the model and a
30% data set for testing the model.
# Read in the data set again without the NN changes
var.names = c("age", "day", "grp.job", "marital", "education", "housing", "loan", "contact", "grp.month", "grp.duration", "grp.campaign", "grp.pdays", "grp.previous", "y")
BankMarketingTrees = BankMarketing[, var.names]
# Random split approach for new training and testing data sets
n = dim(BankMarketingTrees)[1] # sample size
# caution: using without replacement
id.train = sample(1:n, round(0.7*n), replace = FALSE)
train = BankMarketingTrees[id.train, ] # training data
test = BankMarketingTrees[-id.train, ] # testing data
Building Trees
Using a wrapper we can pass in the arguments of impurity and penalty
measures, as well as costs of false positives and negatives, to
construct different decision trees.
we aim to create 6 trees:
Model 1: gini.tree.11 is based on the Gini index without penalizing
false positives and false negatives.
Model 2: info.tree.11 is based on entropy without penalizing false
positives and false negatives.
Model 3: gini.tree.110 is based on the Gini index: cost of false
negatives is 10 times the positives.
Model 4: info.tree.110 is based on entropy: cost of false negatives
is 10 times the positives.
Model 5: gini.tree.101 is based on the Gini index: cost of false
positive is 10 times the negatives.
Model 6: info.tree.101 is based on entropy: cost of false positive is
10 times the negatives.
The code for building the different candidate trees is below:
# Defining different tree models using the rpart() function
tree.builder = function(in.data, fp, fn, purity){
tree = rpart(y ~ ., # including all feature variables
data = in.data,
na.action = na.rpart, # By default, deleted if the outcome is missing,
method = "class", # Classification form factor
model = TRUE,
x = FALSE,
y = TRUE,
parms = list( # Penalizes false positives or negatives more heavily
loss = matrix(c(0, fp, fn, 0), ncol = 2, byrow = TRUE),
split = purity), # Gini index or information entropy
## rpart algorithm options start here
control = rpart.control(
minsplit = 10, # minimum number of observations required before split
minbucket= 10, # minimum number of observations in any terminal node
cp = 0.01, # complexity parameter for stopping rule
maxcompete = 5, # number of competitor splits retained in the output
maxsurrogate = 6, # number of surrogate splits retained in the output
maxdepth = 5,
xval = 10 # number of cross-validation )
)
)
}
Now the models can be fit below:
## Statements to recall the tree model wrapper
gini.tree.1.1 = tree.builder(in.data = train, fp = 1, fn = 1, purity = "gini")
info.tree.1.1 = tree.builder(in.data = train, fp = 1, fn = 1, purity = "information")
gini.tree.1.10 = tree.builder(in.data = train, fp = 1, fn = 10, purity = "gini")
info.tree.1.10 = tree.builder(in.data = train, fp = 1, fn = 10, purity = "information")
gini.tree.10.1 = tree.builder(in.data = train, fp = 10, fn = 1, purity = "gini")
info.tree.10.1 = tree.builder(in.data = train, fp = 10, fn = 1, purity = "information")
The two plots below show the two penalized decision models where cost
of false negative is 10 times the positives.
## Plotting the tree plots
par(mfrow=c(1,2))
rpart.plot(gini.tree.1.10, main = "Tree with Gini Index: Penalization")
rpart.plot(info.tree.1.10, main = "Tree with Entropy: Penalization")
The last two penalized decision models where cost of false positive
is 10 times the negatives will not run properly for the analysis or
produce any full models. This could be due to the built-in stopping
rule. It may indicate, based on penalizing weights, no significant
information gain for any plotting beyond the root node. Therefore, it
would be best to focus on just the two models of false negatives for our
predictive analysis analysis.
ROC Anaylsis
Looking at our two canidate models, we can look at the ROCs of both
these curves, as well as their AUCs in order to selet the optimal model.
A new function will be used to build 2 different trees and plot their
corresponding ROC curves, and calculate the AUCs for each, so we can see
the global performance of these tree algorithms. The code can be seen
below:
## Creating a function for returning a sensitivity and specificity matrix
SensSpec = function(in.data, fp, fn, purity){
cutoff = seq(0,1, length = 20) # 20 cut-offs including 0 and 1.
model = tree.builder(in.data, fp, fn, purity)
## Decision trees return both "success" and "failure" probabilities, but we need only "success" probability to define sensitivity and specificity
pred = predict(model, newdata = in.data, type = "prob") # two-column matrix.
senspe.mtx = matrix(0, ncol = length(cutoff), nrow= 2, byrow = FALSE)
for (i in 1:length(cutoff)){
# The following line uses only " yes" probability: pred[, " yes"]
pred.out = ifelse(pred[," yes"] >= cutoff[i], " yes", " no")
TP = sum(pred.out ==" yes" & in.data$y == " yes")
TN = sum(pred.out ==" no" & in.data$y == " no")
FP = sum(pred.out ==" yes" & in.data$y == " no")
FN = sum(pred.out ==" no" & in.data$y == " yes")
senspe.mtx[1,i] = TP/(TP + FN)
senspe.mtx[2,i] = TN/(TN + FP)
}
## A better approx of ROC, need library {pROC}
prediction = pred[, " yes"]
category = in.data$y == " yes"
ROCobj <- roc(category, prediction)
AUC.tree = auc(ROCobj)
##
list(senspe.mtx= senspe.mtx, AUC.tree = round(AUC.tree,4))
}
#Building the six tree models
giniROC110 = SensSpec(in.data = train, fp=1, fn=10, purity="gini")
infoROC110 = SensSpec(in.data = train, fp=1, fn=10, purity="information")
# Creating the ROC curves for both models
par(pty="s") # set up square plot through graphic parameter
plot(1-giniROC110$senspe.mtx[2,], giniROC110$senspe.mtx[1,], type = "l", xlim=c(0,1), ylim=c(0,1),
xlab="1 - Specificity: FPR", ylab="Sensitivity: TPR", col = "green2", lwd = 2,
main="ROC Curves of Decision Tree Models", cex.main = 0.9, col.main = "blue")
abline(0,1, lty = 2, col = "red", lwd = 2)
lines(1-infoROC110$senspe.mtx[2,], infoROC110$senspe.mtx[1,], col = "deeppink", lwd = 2)
legend("bottomright", c(paste("gini.1.10, AUC =",giniROC110$AUC.tree), paste("info.1.10, AUC =",infoROC110$AUC.tree)), col=c("green2","deeppink"), lty=c(1,2,rep(1,4)), lwd=rep(2,6), cex = 0.6, bty = "n")

We can see above the gini model AUC. This indicates that the
predictive model is both better than the random guess since the area
under the curve (AUC) is significantly greater than 0.5. Since all these
AUC are also significantly greater than 0.65, this means the predictive
power of the model is acceptable.
The Gini index model will be the final model used for finding the
optimal cut-off probability for predictive modeling since it has the
largest AUC.
Optimal Cut-Off Score
Determination
With the final decision tree model established, we will find the
optimal cut-off score for reporting predictive performance. This will be
done using the test data and cross-validation based on the training data
set. The code is below:
optm.cutoff = function(in.data, fp, fn, purity){
n0 = dim(in.data)[1]/5
cutoff = seq(0,1, length = 20) # candidate cut off prob
## accuracy for each candidate cut-off
accuracy.mtx = matrix(0, ncol=20, nrow=5) # 20 candidate cutoffs and gini.11
##
for (k in 1:5){
valid.id = ((k-1)*n0 + 1):(k*n0)
valid.dat = in.data[valid.id,]
train.dat = in.data[-valid.id,]
## tree model
tree.model = tree.builder(in.data, fp, fn, purity)
## prediction
pred = predict(tree.model, newdata = valid.dat, type = "prob")[,2]
## for-loop
for (i in 1:20){
## predicted probabilities
pc.1 = ifelse(pred > cutoff[i], " yes", " no")
## accuracy
a1 = mean(pc.1 == valid.dat$y)
accuracy.mtx[k,i] = a1
}
}
avg.acc = apply(accuracy.mtx, 2, mean)
## plots
n = length(avg.acc)
idx = which(avg.acc == max(avg.acc))
tick.label = as.character(round(cutoff,2))
##
plot(1:n, avg.acc, xlab="cut-off score", ylab="average accuracy",
ylim=c(min(avg.acc), 1),
axes = FALSE,
main=paste("5-fold CV optimal cut-off \n ",purity,"(fp, fn) = (", fp, ",", fn,")" , collapse = ""),
cex.main = 0.9,
col.main = "navy")
axis(1, at=1:20, label = tick.label, las = 2)
axis(2)
points(idx, avg.acc[idx], pch=19, col = "red")
segments(idx , min(avg.acc), idx , avg.acc[idx ], col = "red")
text(idx, avg.acc[idx]+0.03, as.character(round(avg.acc[idx],4)), col = "red", cex = 0.8)
}
par(mfrow=c(1,2))
optm.cutoff(in.data = train, fp=1, fn=10, purity="gini")
optm.cutoff(in.data = train, fp=1, fn=10, purity="information")

The above figure indicates that the optimal cut-off probabilities for
the Gini index (left) and entropy (right) models are relatively the same
in range. Therefore, the optimal average cut-off probability for the
final Gini index model is 0.74.
Bagging
Now we will build a final predictive model using bagging. We will
split the data into testing and training once again.
# We use a random split approach
n = dim(BankMarketingTrees)[1] # sample size
# caution: using without replacement
train.id = sample(1:n, round(0.7*n), replace = FALSE)
train = BankMarketingTrees[train.id, ] # training data
test = BankMarketingTrees[-train.id, ] # testing data
The code for fitting the bagging model is below:
boot_data <- function(df){
return(df[sample(1:nrow(df), replace = T),])
}
# (2) Function to run an Rpart model, provided
# `x` data and a model formula `form`
rpart_fit <- function(x, form){
rpart(form, data = x, method = "class")
}
# (3) Function to get predictions from models on new data
get_pred <- function(x, newdat) {
p <- predict(x, newdata = newdat)
return(p[, 2])
}
# (4) Function for random forest parameter selection
sample_p <- function(df, y, maxp = 3) {
x <- df[!names(df) %in% y]
x <- df[sample(names(x), maxp)]
return(cbind(x, df[y]))
}
# BAGGING
#-------------------#
# define number of bootstrap iterations
B = 100
# set up list to hold models, length B
blist <- vector(mode = "list", length = B)
# bootstrap B models
for(i in 1:B)
blist[[i]] <- boot_data(train)
# run vectorized model, adding our model formula in
bag_fit <- lapply(blist, rpart_fit, form = y ~ .)
# get predictions on new data
b_avg <- do.call(rbind, (lapply(bag_fit, get_pred, test)))
# average over predictions
b_final <- apply(b_avg, 2, mean)
ROC and Test
# Use pROC for AUC
# bagging and random forest
roc(test$y, b_final)
##
## Call:
## roc.default(response = test$y, predictor = b_final)
##
## Data: b_final in 11990 controls (test$y no) < 1573 cases (test$y yes).
## Area under the curve: 0.7285
# plotting ROC
plot(1-SenSpe[2,], SenSpe[1,], type ="l", xlim=c(0,1), ylim=c(0,1),
xlab = "1 - Specificity", ylab = "Sensitivity", lty = 1,
main = "ROC Curve for Bagging Model", col = "blue")
abline(0,1, lty = 2, col = "red")
## Calculate AUC
xx = 1-SenSpe[2,]
yy = SenSpe[1,]
width = xx[-length(xx)] - xx[-1]
height = yy[-1]
## A better approx of ROC, need library {pROC}
prediction = nn.results
category = trainData[,"yYes"] == 1
ROCobj <- roc(category, prediction)
## Warning in roc.default(category, prediction): Deprecated use a matrix as
## predictor. Unexpected results may be produced, please pass a numeric vector.
AUC.b_final = auc(test$y,b_final)
##
###
#AUC =mean(sum(width*height), sum(width*yy[-length(yy)]))
text(0.8, 0.3, paste("AUC = ", round(AUC.b_final,4)), col = "purple", cex = 0.9)
legend("bottomright", c("ROC of the Model", "Random Guessing"), lty=c(1,2),
col = c("blue", "red"), bty = "n", cex = 0.8)
We can see above bagging model AUC. This indicates that the predictive
model is both better than the random guess since the area under the
curve (AUC) is significantly greater than 0.5. Since all these AUC are
also significantly greater than 0.65, this means the predictive power of
the model is acceptable.
Model Comparison
# Plotting both ROC curves
plot(1-SenSpe[2,], SenSpe[1,], type ="l", xlim=c(0,1), ylim=c(0,1), xlab = "1 - Specificity", ylab = "Sensitivity", lty = 1, main = "ROC Curves of the Three Best Models", col = "purple")
abline(0,1, lty = 2, col = "red")
lines(one.minus.spec.final, sens.vec.final, col = "orange")
lines(1-giniROC110$senspe.mtx[2,], giniROC110$senspe.mtx[1,], col = "green2", lwd = 2)
lines(1-AUC.b_final, AUC.b_final ,col = "blue")
## Calculate AUCs for both
text(0.8, 0.4, paste("AUC = ", round(AUC.neural,4)), col = "purple", cex = 0.9)
text(0.8, 0.3, paste("AUC = ", round(AUC.b_final,4)), col = "blue", cex = 0.9)
AUC.final = round(sum(sens.vec.final*(one.minus.spec.final[-101]-one.minus.spec.final[-1])),4)
## Warning in one.minus.spec.final[-101] - one.minus.spec.final[-1]: longer object
## length is not a multiple of shorter object length
text(0.8, 0.5, paste("AUC = ", round(AUC.final,4)), col = "orange", cex = 0.9)
text(0.8, 0.3, paste("AUC = ", round(giniROC110$AUC,4)), col = "green2", cex = 0.9)
# Adding a legend to the figure
legend("bottomright", c("ROC of the Final Predictive Model", "ROC of the Neural Network Model", "ROC of the Final Deision Tree Model", "Random Guessing"), lty=c(1,2), col = c("orange", "purple", "green2", "red","black", ""), bty = "n", cex = 0.8)

LS0tDQp0aXRsZTogJ0RpcmVjdCBNYXJrZXRpbmcgQ2FtcGFpZ25zIFJlZ3Jlc3Npb24gQW5heWxzaXMnDQphdXRob3I6ICJKZXNzaWNhIEdvcnIiDQpkYXRlOiAiT2N0b2JlciAyNCwgMjAyNCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jX2NvbGxhcHNlZDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogeWVzDQogICAgdGhlbWU6IGx1bWVuDQogIHBkZl9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBmaWdfd2lkdGg6IDMNCiAgICBmaWdfaGVpZ2h0OiAzDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCi0tLQ0KDQpgYGB7PWh0bWx9DQoNCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoNCi8qIENhc2NhZGluZyBTdHlsZSBTaGVldHMgKENTUykgaXMgYSBzdHlsZXNoZWV0IGxhbmd1YWdlIHVzZWQgdG8gZGVzY3JpYmUgdGhlIHByZXNlbnRhdGlvbiBvZiBhIGRvY3VtZW50IHdyaXR0ZW4gaW4gSFRNTCBvciBYTUwuIGl0IGlzIGEgc2ltcGxlIG1lY2hhbmlzbSBmb3IgYWRkaW5nIHN0eWxlIChlLmcuLCBmb250cywgY29sb3JzLCBzcGFjaW5nKSB0byBXZWIgZG9jdW1lbnRzLiAqLw0KDQpoMS50aXRsZSB7ICAvKiBUaXRsZSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgdGhlIHJlcG9ydCB0aXRsZSAqLw0KICBmb250LXNpemU6IDI0cHg7DQogIGZvbnQtd2VpZ2h0OmJvbGQ7DQogIGNvbG9yOiBEYXJrUmVkOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCn0NCmg0LmF1dGhvciB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgYXV0aG9ycyAgKi8NCiAgZm9udC1zaXplOiAyMHB4Ow0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuZGF0ZSB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgdGhlIGRhdGUgICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgY29sb3I6IERhcmtCbHVlOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoMSB7IC8qIEhlYWRlciAxIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMSBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMjJweDsNCiAgICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICAgIGZvbnQtd2VpZ2h0OmJvbGQ7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCmgyIHsgLyogSGVhZGVyIDIgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAyIHNlY3Rpb24gdGl0bGUgKi8NCiAgICBmb250LXNpemU6IDIwcHg7DQogICAgZm9udC13ZWlnaHQ6Ym9sZDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoMyB7IC8qIEhlYWRlciAzIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCAzIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtd2VpZ2h0OmJvbGQ7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDQgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgNCBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMTZweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpib2R5IHsgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsgfQ0KDQouaGlnaGxpZ2h0bWUgeyBiYWNrZ3JvdW5kLWNvbG9yOnllbGxvdzsgfQ0KDQpwIHsgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsgfQ0KDQo8L3N0eWxlPg0KYGBgDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBjb2RlIGNodW5rIHNwZWNpZmllcyB3aGV0aGVyIHRoZSBSIGNvZGUsIHdhcm5pbmdzLCBhbmQgb3V0cHV0DQojIHdpbGwgYmUgaW5jbHVkZWQgaW4gdGhlIG91dHB1dCBmaWxlcy4NCmlmICghcmVxdWlyZSgia25pdHIiKSkgew0KIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikNCiBsaWJyYXJ5KGtuaXRyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJNQVNTIikpIHsNCiBpbnN0YWxsLnBhY2thZ2VzKCJNQVNTIikNCiBsaWJyYXJ5KE1BU1MpDQp9DQppZiAoIXJlcXVpcmUoImxlYWZsZXQiKSkgew0KIGluc3RhbGwucGFja2FnZXMoImxlYWZsZXQiKQ0KIGxpYnJhcnkobGVhZmxldCkNCn0NCmlmICghcmVxdWlyZSgiZmFjdG9leHRyYSIpKSB7DQogaW5zdGFsbC5wYWNrYWdlcygiZmFjdG9leHRyYSIpDQogbGlicmFyeShmYWN0b2V4dHJhKQ0KfQ0KaWYgKCFyZXF1aXJlKCJ3ZWJzaG90IikpIHsNCiBpbnN0YWxsLnBhY2thZ2VzKCJ3ZWJzaG90IikNCiBsaWJyYXJ5KHdlYnNob3QpDQp9DQppZiAoIXJlcXVpcmUoIlRTc3R1ZGlvIikpIHsNCiBpbnN0YWxsLnBhY2thZ2VzKCJUU3N0dWRpbyIpDQogbGlicmFyeShUU3N0dWRpbykNCn0NCmlmICghcmVxdWlyZSgibmV1cmFsbmV0IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIm5ldXJhbG5ldCIpDQogICBsaWJyYXJ5KG5ldXJhbG5ldCkNCn0NCmlmICghcmVxdWlyZSgicGxvdHJpeCIpKSB7DQogaW5zdGFsbC5wYWNrYWdlcygicGxvdHJpeCIpDQpsaWJyYXJ5KHBsb3RyaXgpDQp9DQppZiAoIXJlcXVpcmUoImdncmlkZ2VzIikpIHsNCiBpbnN0YWxsLnBhY2thZ2VzKCJnZ3JpZGdlcyIpDQpsaWJyYXJ5KGdncmlkZ2VzKQ0KfQ0KaWYgKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkgew0KIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCn0NCmlmICghcmVxdWlyZSgiR0dhbGx5IikpIHsNCiBpbnN0YWxsLnBhY2thZ2VzKCJHR2FsbHkiKQ0KbGlicmFyeShHR2FsbHkpDQp9DQppZiAoIXJlcXVpcmUoImRwbHlyIikpIHsNCiBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpDQpsaWJyYXJ5KGRwbHlyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJycGFydCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJycGFydCIpDQogICBsaWJyYXJ5KHJwYXJ0KQ0KfQ0KaWYgKCFyZXF1aXJlKCJwUk9DIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInBST0MiKQ0KICAgbGlicmFyeShycGFydCkNCn0NCg0KaWYgKCFyZXF1aXJlKCJyYXR0bGUiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicmF0dGxlIikNCiAgIGxpYnJhcnkocmF0dGxlKQ0KfQ0KaWYgKCFyZXF1aXJlKCJycGFydC5wbG90IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInJwYXJ0LnBsb3QiKQ0KICAgbGlicmFyeShycGFydC5wbG90KQ0KfQ0KaWYgKCFyZXF1aXJlKCJSQ29sb3JCcmV3ZXIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiUkNvbG9yQnJld2VyIikNCiAgIGxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJlMTA3MSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJlMTA3MSIpDQogICBsaWJyYXJ5KGUxMDcxKQ0KfQ0KDQppZiAoIXJlcXVpcmUoImtuaXRyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikNCiAgIGxpYnJhcnkoa25pdHIpDQp9DQppZiAoIXJlcXVpcmUoIklTTFIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiSVNMUiIpDQogICBsaWJyYXJ5KElTTFIpDQp9DQppZiAoIXJlcXVpcmUoInBhbmRlciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJwYW5kZXIiKQ0KICAgbGlicmFyeShwYW5kZXIpDQp9DQppZiAoIXJlcXVpcmUoInBST0MiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicFJPQyIpDQogICBsaWJyYXJ5KHBST0MpDQp9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsICMgaW5jbHVkZSBjb2RlIGNodW5rIGluIHRoZQ0KICMgb3V0cHV0IGZpbGUNCiB3YXJuaW5ncyA9IEZBTFNFLCAjIHNvbWV0aW1lcywgeW91IGNvZGUgbWF5DQogIyBwcm9kdWNlIHdhcm5pbmcgbWVzc2FnZXMsDQojIHlvdSBjYW4gY2hvb3NlIHRvIGluY2x1ZGUNCiMgdGhlIHdhcm5pbmcgbWVzc2FnZXMgaW4NCiAjIHRoZSBvdXRwdXQgZmlsZS4NCiByZXN1bHRzID0gVFJVRSwgIyB5b3UgY2FuIGFsc28gZGVjaWRlIHdoZXRoZXINCiAjIHRvIGluY2x1ZGUgdGhlIG91dHB1dA0KIyBpbiB0aGUgb3V0cHV0IGZpbGUuDQogbWVzc2FnZSA9IEZBTFNFDQopICANCmBgYA0KDQoNClwNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KVGhlIGRhdGEgaXMgcmVsYXRlZCB3aXRoIGRpcmVjdCBtYXJrZXRpbmcgY2FtcGFpZ25zIG9mIGEgUG9ydHVndWVzZSBiYW5raW5nIGluc3RpdHV0aW9uLiBUaGUgbWFya2V0aW5nIGNhbXBhaWducyB3ZXJlIGJhc2VkIG9uIHBob25lIGNhbGxzLiBPZnRlbiwgbW9yZSB0aGFuIG9uZSBjb250YWN0IHRvIHRoZSBzYW1lIGNsaWVudCB3YXMgcmVxdWlyZWQuIFRoZSBkYXRhIGNvbGxlY3RlZCBmcm9tIHRoZXNlIG1hcmtldGluZyBjYXBhaWducyB3YXMgY29sbGVjdGVkIGZyb20gTWF5IDIwMDggdG8gTm92ZW1iZXIgMjAxMC4gVGhlcmUgaXMgYSB0b3RhbCBudW1iZXIgb2YgNDUsMjExIG9ic2VydmF0aW9uIGluIHRoaXMgZGF0YSBzZXQuIFRoZSBkYXRhIHNldCBjb25zaXN0cyBvZiAxNyB2YXJpYWJsZXMsIGluY2x1ZGluZyB0aGUgcmVzcG9uc2UgdmFyaWFibGUgd2l0aCB0aGUgbmFtZSAneScuIEEgZGVzY3JpcHRpb24gb2YgdGhlIHByZWRpY3RvciBhbmQgb3V0Y29tZSBmZWF0dXJlcyBhcmUgYmVsb3c6DQoNCjEgLSBhZ2UgKG51bWVyaWMpDQoNCjIgLSBqb2IgOiBKb2IgdHlwZSAoY2F0ZWdvcmljYWwpOiAiYWRtaW4uIiwgInVua25vd24iLCAidW5lbXBsb3llZCIsICJtYW5hZ2VtZW50IiwgImhvdXNlbWFpZCIsICJlbnRyZXByZW5ldXIiLCAic3R1ZGVudCIsICJibHVlLWNvbGxhciIsICJzZWxmLWVtcGxveWVkIiwgInJldGlyZWQiLCAidGVjaG5pY2lhbiIsICJzZXJ2aWNlcyINCg0KMyAtIG1hcml0YWwgOiBNYXJpdGFsIHN0YXR1cyAoY2F0ZWdvcmljYWwpOiAibWFycmllZCIsICJkaXZvcmNlZCIsICJzaW5nbGUiDQogIA0KNCAtIGVkdWNhdGlvbiAoY2F0ZWdvcmljYWwpOiAidW5rbm93biIsInNlY29uZGFyeSIsInByaW1hcnkiLCJ0ZXJ0aWFyeSINCg0KNSAtIGRlZmF1bHQ6IERvZXMgdGhlIGNsaWVudCBoYXZlIGNyZWRpdCBpbiBkZWZhdWx0PyAoYmluYXJ5OiAieWVzIiwibm8iKQ0KDQo2IC0gYmFsYW5jZTogQXZlcmFnZSB5ZWFybHkgYmFsYW5jZSAobnVtZXJpYywgaW4gZXVyb3MpDQoNCjcgLSBob3VzaW5nOiBEb2VzIHRoZSBjbGllbnQgaGF2ZSBhIGhvdXNpbmcgbG9hbj8gKGJpbmFyeTogInllcyIsIm5vIikNCg0KOCAtIGxvYW46IERvZXMgdGhlIGNsaWVudCBoYXZlIGEgcGVyc29uYWwgbG9hbj8gKGJpbmFyeTogInllcyIsIm5vIikNCg0KOSAtIGNvbnRhY3Q6IENvbnRhY3QgY29tbXVuaWNhdGlvbiB0eXBlIChjYXRlZ29yaWNhbCk6ICJ1bmtub3duIiwidGVsZXBob25lIiwiY2VsbHVsYXIiDQoNCjEwIC0gZGF5OiBMYXN0IGNvbnRhY3QgZGF5IG9mIHRoZSBtb250aCAobnVtZXJpYywgZGlzY3JldGUpDQoNCjExIC0gbW9udGg6IExhc3QgY29udGFjdCBtb250aCBvZiB5ZWFyIChjYXRlZ29yaWNhbCk6ICJqYW4iLCAiZmViIiwgIm1hciIsICJhcHIiLCAibWF5IiwgImp1biIsICJqdWwiLCAgImF1ZyIsICJzZXAiLCAib2N0IiwgIm5vdiIsICJkZWMiDQoNCjEyIC0gZHVyYXRpb246IExhc3QgY29udGFjdCBkdXJhdGlvbiAobnVtZXJpYywgaW4gc2Vjb25kcykNCg0KMTMgLSBjYW1wYWlnbjogVGhlIG51bWJlciBvZiBjb250YWN0cyBwZXJmb3JtZWQgZHVyaW5nIHRoaXMgY2FtcGFpZ24gYW5kIGZvciB0aGlzIGNsaWVudCAobnVtZXJpYywgZGlzY3JldGUpDQoNCjE0IC0gcGRheXM6IFRoZSBudW1iZXIgb2YgZGF5cyBhZnRlciB0aGUgY2xpZW50IHdhcyBsYXN0IGNvbnRhY3RlZCBmcm9tIGEgcHJldmlvdXMgY2FtcGFpZ24gKG51bWVyaWMsIGRpc2NyZXRlKSAgICANCg0KMTUgLSBwcmV2aW91czogVGhlIG51bWJlciBvZiBjb250YWN0cyBwZXJmb3JtZWQgYmVmb3JlIHRoaXMgY2FtcGFpZ24gYW5kIGZvciB0aGlzIGNsaWVudCAobnVtZXJpYykNCg0KMTYgLSBwb3V0Y29tZTogVGhlIG91dGNvbWUgb2YgdGhlIHByZXZpb3VzIG1hcmtldGluZyBjYW1wYWlnbiAoY2F0ZWdvcmljYWwpOiAidW5rbm93biIsICJvdGhlciIsICJmYWlsdXJlIiwgInN1Y2Nlc3MiDQoNCjE3IC0geSBvT3V0Y29tZSBjbGFzcyB2YXJpYWJsZSk6IEhhcyB0aGUgY2xpZW50IHN1YnNjcmliZWQgYSB0ZXJtIGRlcG9zaXQ/IChiaW5hcnk6ICJ5ZXMiLCJubyIpDQoNCkEgcHVibGljIGxpbmsgdG8gdGhlIGRhdGEgY2FuIGJlIGZvdW5kIGhlcmU6IGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9kYXRhc2V0LzIyMi9iYW5rK21hcmtldGluZw0KDQoNCg0KDQoNCldoZW4gbG9va2luZyBhdCB0aGlzIGRhdGFzZXQsIHR3byBhcmVhcyB3ZSB3b3VsZCB3YW50IHRvIGV4cGxvcmUgdGhyb3VnaCBsaW5lYXIgYW5kIGxvZ2lzdGljIHJlZ3Jlc3Npb24uDQoNCjEuIElmIHRoZXJlIGlzIGEgYXNzb2NpYXRpb24gd2l0aCBhbnkgb2YgdGhlIHZhcmlhYmxlcyBhbmQgaG93IGxvbmcgb2YgdGhlIGR1cmF0aW9uIG9mIGEgY2FsbCAoTGluZWFyKS4NCg0KMi4gUHJlZGljdCBpZiBhIGNsaWVudCBoYXMgc3Vic2NyaWJlZCBhIHRlcm0gZGVwb3NpdCBhZnRlciBkaXJlY3QgbWFya2V0aW5nIGNhbXBhaWducyAoTG9naXN0aWMpLg0KDQpgYGB7cn0NCiNMb2FkIHRoZSBzYW1wbGUgZGF0YQ0KQmFua01hcmtldGluZyA9IHJlYWQuY3N2KCJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy9CYW5rTWFya2V0aW5nL0JhbmtNYXJrZXRpbmdDU1YuY3N2IilbLCAtMV0NCmBgYA0KDQojIyBIYW5kbGluZyBNaXNzaW5nIFZhbHVlcw0KDQpUaGlzIGRhdGFzZXQgZG9lcyBub3QgY29udGFpbiBhbnkgbWlzc2luZyB2YWx1ZXMsIHRoZXJlZm9yZSB3ZSBkbyBub3QgaGF2ZSB0byBkcm9wIGFueSByb3dzIG9yIGlucHV0IGFueSB2YWx1ZXMuDQoNCg0KDQojIEVEQSBhbmQgRmVhdHVyZSBFbmdpbmVlcmluZw0KDQpJbiBvZGVyIHRvIHBlcmZvbSBzbyBFREEsIHdlIG11c3QgaGF2ZSBhIGJhc2ljIHVuZGVyc3RhbmRpbmcgb2YgdGhlIGRhdGEuIEEgc3VtbWFyeSBvZiB0ZWggZGF0YSBpcyBwcmludGVkIGJlbG93Lg0KDQpgYGB7cn0NCg0KI1N1bW1hcml6ZWQgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyBmb3IgYWxsIHZhcmlhYmxlcyBpbiB0aGUgZGF0YSBzZXQNCnN1bW1hcnkoQmFua01hcmtldGluZykNCmBgYA0KDQpXZSBjYW4gc2VlIGZyb20gdGhlIGFib3ZlIHN1bW1hcnkgdGFibGUgdGhhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHNvbWUgb2YgdGhlIG51bWVyaWMgdmFyaWFibGVzIGlzIHNrZXdlZCBhbmQgY29udGFpbnMgb3V0bGllcnMgdGhhdCBuZWVkIHRvIGJlIGZ1cnRoZXIgZXhwbG9yZWQuIA0KDQoNCiMjIFNpbmdsZSBWYXJpYWJsZSBEaXN0cmlidXRpb24NCg0KVGhlIGRpc3RyaWJ1dGlvbiBmb3Igb3VyIGNvbnRpbnVvdXMgbnVtZXJpY2FsIHZhcmlhYmxlcyBmb3IgYXZlcmFnZV9tb250aGx5X2hvdXJzIGFuZCB0aW1lX3NwZW5kX2NvbXBhbnkgYXJlIHNob3duIGJlbG93LiBUaW1lIHNwZW50IGlzIHNrZXdlZCB0byB0aGUgcmlnaHQsIGFuZCBpcyBub3Qgbm9ybWFsLiBBdmVyYWdlIG1vbnRobHkgaG91cnMgc28gbm90IHRvbyBiYWQsIGFuZCBjYW4gYmUgZGVlbWVkIGFwcHJveGlhdGx5IG5vcm1hbC4NCg0KSW4gb3JkZXIgdG8gZXhhbWluZSBhbmQgZGV0ZXJtaW5lIHRoZSBvdXRjb21lIG9mIGFueSBvZiB0aGUgb3V0bGllcnMgYW5kIGxvb2tpbmcgYXQgdGhlIHNrZXduZXNzIG9mIGNlcnRhaW4gbnVtZXJpY2FsIHZhcmlhYmxlcywgc3VjaCBhcyAiZHVyYXRpb24iLCBkaXNjcmV0aXphdGlvbiB3aWxsIGJlIHVzZWQgdG8gZGl2aWRlIHRoZSBkaWZmZXJlbnQgY2F0ZWdvcmljYWwgdmFyaWJsZXMgaW50byBncm91cHMuIFRoaXMgdmFyaWFibGUgc2hvdWxkIGJlIGRpc2NyZXRpemVkIGR1ZSB0byB0aGVudW1iZXIgb2YgaGlnaCBvdXRsaWVycyBpdCBjb250YWlucy4gSW4gbG9va2luZyBhdCB0aGlzIHZhcmlhYmxlJ3MgZGlzdHJpYnV0aW9uLCB0aGUgdGhyZWUgZ3JvdXBzIHRoYXQgd2VyZSBjcmVhdGVkICgwLTE4MCwgMTgxLTMxOSwgYW5kIDMyMCspIHNlZW0gc2ltaWxhciBlbm91Z2ggaW4gdGhlIGZyZXF1ZW5jeSBvZiBjbGllbnQgb2JzZXJ2YXRpb25zLiBUaGlzIG5ldyB2YXJpYWJsZSB3aWxsIG5vdyBiZSB1c2VkIGZvciBidWlsZGluZyBmdXR1cmUgbW9kZWxzLiBUaGUgaGlzdG9ncmFtIGNhbiBiZSBzZWVuIGJlbG93Lg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcid9DQojIGhpc3RvZ3JhbSBzaG93aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGR1cmF0aW9uIHZhcmlhYmxlDQpoaXN0KEJhbmtNYXJrZXRpbmckZHVyYXRpb24sIHhsYWIgPSAiRHVyYXRpb24iLCB5bGFiID0gImNvdW50IiwgbWFpbiA9ICJEdXJhdGlvbnMgb2YgTGFzdCBDb250YWN0IikNCmBgYA0KDQpgYGB7cn0NCiMgTmV3IGdyb3VwaW5nIHZhcmlhYmxlIGZvciBkdXJhdGlvbg0KQmFua01hcmtldGluZyRncnAuZHVyYXRpb24gPC0gaWZlbHNlKEJhbmtNYXJrZXRpbmckZHVyYXRpb24gPD0gMTgwLCAnMC0xODAnLA0KICAgICAgICAgICAgICAgaWZlbHNlKEJhbmtNYXJrZXRpbmckZHVyYXRpb24gPj0gMzIwLCAnMzIwKycsICcxODEtMzE5JykpDQpgYGANCg0KDQpOb3cgd2Ugd2FudCB0byBsb29rIGF0IHNvbWUgb2YgdGhlIGNhdGVnb3JpY2FsIGFuZCBiaW5hcnkgZmVhdHVyZXMuIFdoZW4gbG9va2luZyBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGNvbnRhY3RzIHBlcmZvcm1lZCBkdXJpbmcgdGhlIGNhbXBhaW4sIGl0IGlzIFNrZXdlZCB0byB0aGUgcmlnaHQuIFRoaXMgbWVhbnMgdGhhdCBncm91cHMgc2hvdWxkIGJlIGNyZWF0ZWQuIEZvciBjYW1wYWlnbiwgdGhlIHZhbHVlIG9mIDEgY29udGFjdCBzaG91bGQgYmUgaXRzIG93biBncm91cCBzaW5jZSBpdCBoYXMgdGhlIGhpZ2hlc3QgZnJlcXVlbmN5IG9mIG9ic2VydmF0aW9ucy4gVmFsdWVzIDIgJiAzIG51bWJlciBvZiBjb250YWN0cyBjb21iaW5lZCBoYXZlIGNsb3NlIHRvIHRoZSBzYW1lIGZyZXF1ZW5jeSwgc28gdGhleSBzaG91bGQgYmUgcGFpcmVkIHRvZ2V0aGVyIGluIHRoZWlyIG93biBncm91cC4gVGhlIHJlbWFpbmluZyBvYnNlcnZhdGlvbnMgc2hvdWxkIGJlIGNvbWJpbmVkIGludG8gdGhlIGZpbmFsIGdyb3VwLiANCg0KV2hlbiBsb29raW5nIGF0IHBkYXlzLCB0aGUgdmFsdWUgb2YgLTEgaXMgYW4gaW5kaWNhdG9yIHRoYXQgYSBjbGllbnQgd2FzIG5vdCBwcmV2aW91c2x5IGNvbnRhY3RlZCBiZWZvcmUgdGhlIGNhbXBhaWduLiBTaW5jZSB0aGlzIG1ha2VzIHVwIG1vc3Qgb2YgdGhlIG9iZXJzdmF0aW9ucywgaXQgd2lsbCBiZWNvbWUgaXRzIG93biBncm91cC4gVGhlIHJlc3Qgb2YgdGhlIG9ic2VydmF0aW9ucyB3ZXJlIHNwbGl0IGludG8gZ3JvdXBzIG9mIDEtMjAwIGRheXMgYW5kIDIwMCBkYXlzIG9yIG1vcmUuIA0KDQoNClRoZSBwcmV2aW91cyB2YXJpYWJsZSB3YXMgYWxzbyBzcGxpdCBpbnRvIDMgZ3JvdXBzLiBUaGUgdmFsdWUgb2YgMCBjb250YWN0cyBpcyBvbmUgZ3JvdXAgc2luY2UgaXQgaGFzIHRoZSBtb3N0IG9ic2VydmF0aW9ucy4gVGhlIHZhbHVlcyBvZiAxIHRvIDMgY29udGFjdHMgaXMgYW5vdGhlciBjYXRlZ29yeSBzaW5jZSB0aGV5IGJvdGggbWFrZSBhIGZhaXIgYW1vdW50IG9mIHRoZSBvYnNlcnZhdGlvbnMuIFNhbWUgZ29lcyBmb3Igb2JzZXJ2YXRpb25zIHdpdGggNCBvciBtb3JlIGNvbnRhY3RzLg0KDQpBbGwgb2YgdGhlc2UgYmFyIHBsb3RzIGNhbiBiZSBzZWVuIGJlbG93Lg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInfQ0KIyBiYXJwbG90IHNob3dpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgY2FtcGFpZ24gdmFyaWFibGUNCm1hcmtldGNhbXBhaWducyA9IHRhYmxlKEJhbmtNYXJrZXRpbmckY2FtcGFpZ24pDQpiYXJwbG90KG1hcmtldGNhbXBhaWducywgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgQ29udGFjdHMgUGVyZm9ybWVkIER1cmluZyBDYW1wYWlnbiIsIHhsYWIgPSAiTnVtYmVyIG9mIENvbnRhY3RzIikNCmBgYA0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcid9DQojIGJhcnBsb3Qgc2hvd2luZyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBwZGF5cyB2YXJpYWJsZQ0KZGF5c3Bhc3NlZCA9IHRhYmxlKEJhbmtNYXJrZXRpbmckcGRheXMpDQpiYXJwbG90KGRheXNwYXNzZWQsIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIERheXMgUGFzc2VkIEFmdGVyIENsaWVudCBMYXN0IENvbnRhY3RlZCAoUGRheXMpICIsIHhsYWIgPSAiTnVtYmVyIG9mIERheXMiKQ0KYGBgDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJ30NCiMgYmFycGxvdCBzaG93aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHByZXZpb3VzIHZhcmlhYmxlDQpwcmV2ID0gdGFibGUoQmFua01hcmtldGluZyRwcmV2aW91cykNCmJhcnBsb3QocHJldiwgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgUHJldmlvdXMgQ29udGFjdHMiLCB4bGFiID0gIk51bWJlciBvZiBDb250YWN0cyIpDQpgYGANClRoZXNlIG5ldyBncm91cGVkIHZhcmlhYmxlcyB3aWxsIGJlIHVzZWQgaW4gZnV0dXJlIG1vZGVsIGJ1aWxkLiBUaGUgY2F0ZWdvcmllcyBmb3IgZWFjaCB2YXJpYWJsZSBhcmUgYXMgZm9sbG93czoNCg0KY2FtcGFpZ246IDEsIDItMywgNCsNCnBkYXlzOiAtMSwgMS0xOTksIDIwMCsNCnByZXZpb3VzOiAwLCAxLTMsIDQrDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJ30NCiMgTmV3IGdyb3VwaW5nIHZhcmlhYmxlIGZvciBjYW1wYWlnbg0KQmFua01hcmtldGluZyRncnAuY2FtcGFpZ24gPC0gaWZlbHNlKEJhbmtNYXJrZXRpbmckY2FtcGFpZ24gPD0gMSwgJzEnLA0KICAgICAgICAgICAgICAgaWZlbHNlKEJhbmtNYXJrZXRpbmckY2FtcGFpZ24gPj0gNCwgJzQrJywgJzItMycpKQ0KDQojIE5ldyBncm91cGluZyB2YXJpYWJsZSBmb3IgcGRheXMNCkJhbmtNYXJrZXRpbmckZ3JwLnBkYXlzIDwtIGlmZWxzZShCYW5rTWFya2V0aW5nJHBkYXlzIDw9IC0xLCAnQ2xpZW50IE5vdCBQcmV2aW91c2x5IENvbnRhY3RlZCcsIGlmZWxzZShCYW5rTWFya2V0aW5nJHBkYXlzID49IDIwMCwgJzIwMCsnLCAnMS0xOTknKSkNCg0KIyBOZXcgZ3JvdXBpbmcgdmFyaWFibGUgZm9yIHByZXZpb3VzDQpCYW5rTWFya2V0aW5nJGdycC5wcmV2aW91cyA8LSBpZmVsc2UoQmFua01hcmtldGluZyRwcmV2aW91cyA8PSAwLCAnMCcsDQogICAgICAgICAgICAgICBpZmVsc2UoQmFua01hcmtldGluZyRwcmV2aW91cyA+IDQsICc0KycsICcxLTMnKSkNCmBgYA0KDQoNCk5vdyB3ZSBtb3ZlIG9udG8gY2F0ZWdvcmljYWwgdmFyaWJsZXMuDQoNClRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBvZiBtb250aCBoYXMgYWxzbyBiZWVuIGRpc2NyZXRpemVkIGJ5IHNlYXNvbnMgc2luY2UgdGhlIGJhciBwbG90IGJlbG93IGlzIGFsc28gc2tld2VkIGZvciBjZXJ0YWluIG1vbnRocy4gQWxzbywgaGFuZGxpbmcgc21hbGxlciBncm91cHMgaW50byBzZWFzb25zIGlzIGVhc2llciB0aGFuIGhhbmxpbmcgMTIgbW9udGhzLiBBIG5ldyBmZWF0dXJlIGNhbGxlZCAic2Vhc29ucyIgd2FzIGNyZWF0ZWQuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJ30NCiMgYmFycGxvdCBzaG93aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIG1vbnRoIHZhcmlhYmxlDQpzZWFzb25zID0gdGFibGUoQmFua01hcmtldGluZyRtb250aCkNCmJhcnBsb3Qoc2Vhc29ucywgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgTnVtYmVyIG9mIENvbnRhY3RzIGJ5IE1vbnRoIiwgeGxhYiA9ICJOdW1iZXIgb2YgQ29udGFjdHMiKQ0KYGBgDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMgTmV3IGdyb3VwaW5nIHZhcmlhYmxlIGZvciBtb250aA0KQmFua01hcmtldGluZyRncnAubW9udGggPSBpZmVsc2UoQmFua01hcmtldGluZyRtb250aCA9PSAiIGphbiIsICJ3aW50ZXIiLCBpZmVsc2UoQmFua01hcmtldGluZyRtb250aCA9PSAiIGZlYiIsICJ3aW50ZXIiLCBpZmVsc2UoQmFua01hcmtldGluZyRtb250aCA9PSAiIG1hciIsICJzcHJpbmciLCBpZmVsc2UoQmFua01hcmtldGluZyRtb250aCA9PSAiIGFwciIsICJzcHJpbmciLCBpZmVsc2UoQmFua01hcmtldGluZyRtb250aCA9PSAiIG1heSIsICJzcHJpbmciLCBpZmVsc2UoQmFua01hcmtldGluZyRtb250aCA9PSAiIGp1biIsICJzdW1tZXIiLCBpZmVsc2UoQmFua01hcmtldGluZyRtb250aCA9PSAiIGp1bCIsICJzdW1tZXIiLCBpZmVsc2UoQmFua01hcmtldGluZyRtb250aCA9PSAiIGF1ZyIsICJzdW1tZXIiLCBpZmVsc2UoQmFua01hcmtldGluZyRtb250aCA9PSAiIHNlcCIsICJmYWxsIiwgaWZlbHNlKEJhbmtNYXJrZXRpbmckbW9udGggPT0gIiBvY3QiLCAiZmFsbCIsIGlmZWxzZShCYW5rTWFya2V0aW5nJG1vbnRoID09ICIgbm92IiwgImZhbGwiLCAid2ludGVyIikpKSkpKSkpKSkpDQoNCg0KYGBgDQoNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInfQ0KIyBiYXJwbG90IHNob3dpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgam9iIHZhcmlhYmxlDQpqb2JjYXRlZ29yeSA9IHRhYmxlKEJhbmtNYXJrZXRpbmckam9iKQ0KYmFycGxvdChqb2JjYXRlZ29yeSwgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgSm9iIFR5cGUiLCB4bGFiID0gIk51bWJlciBvZiBDbGllbnRzIGluIEVhY2ggSm9iIikNCg0KDQojIE5ldyBncm91cGluZyB2YXJpYWJsZSBmb3Igam9iDQpCYW5rTWFya2V0aW5nJGdycC5qb2IgPSBpZmVsc2UoQmFua01hcmtldGluZyRqb2IgPT0gIiB1bmtub3duIiwgIm5vdCB3b3JraW5nIiwgaWZlbHNlKEJhbmtNYXJrZXRpbmckam9iID09ICIgdW5lbXBsb3llZCIsICJub3Qgd29ya2luZyIsIGlmZWxzZShCYW5rTWFya2V0aW5nJGpvYiA9PSAiIHJldGlyZWQiLCAibm90IHdvcmtpbmciLCBpZmVsc2UoQmFua01hcmtldGluZyRqb2IgPT0gIiBibHVlLWNvbGxhciIsICJ3b3JrZXJzIiwgaWZlbHNlKEJhbmtNYXJrZXRpbmckam9iID09ICIgZW50cmVwcmVuZXVyIiwgImJvc3NlcyIsIGlmZWxzZShCYW5rTWFya2V0aW5nJGpvYiA9PSAiIGhvdXNlbWFpZCIsICJ3b3JrZXJzIiwgaWZlbHNlKEJhbmtNYXJrZXRpbmckam9iID09ICIgbWFuYWdlbWVudCIsICJib3NzZXMiLCBpZmVsc2UoQmFua01hcmtldGluZyRqb2IgPT0gIiBzZWxmLWVtcGxveWVkIiwgImJvc3NlcyIsIGlmZWxzZShCYW5rTWFya2V0aW5nJGpvYiA9PSAiIHNlcnZpY2VzIiwgIndoaXRlLWNvbGxhciIsIGlmZWxzZShCYW5rTWFya2V0aW5nJGpvYiA9PSAiIHRlY2huaWNpYW4iLCAid2hpdGUtY29sbGFyIiwgaWZlbHNlKEJhbmtNYXJrZXRpbmckam9iID09ICIgc3R1ZGVudCIsICJub3Qgd29ya2luZyIsICJ3aGl0ZS1jb2xsYXIiKSkpKSkpKSkpKSkNCmBgYA0KDQpOb3cgdGhhdCB3ZSBoYXZlIE5vdyB0aGF0IHRoZSB2YXJpYWJsZXMgaGF2ZSBiZWVuIGRpc2NyZXRpemVkLCBhbmQgY3JlYXRlZCBuZXcgdmFyaWFibGVzLCB0aGUgZGF0YXNldCBub3cgbXVzdCBiZSBjbGVhbmVkIHRvIHJlZmxlY3QgdGhvc2UgY2hhbmdlcy4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIEFzc2VtYmxpbmcgdGhlIGRpc2NyZXRpemVkIHZhcmlhYmxlcyBhbmQgb3RoZXIgdmFyaWFibGVzIHRvIG1ha2UgdGhlIG1vZGVsaW5nIGRhdGEgc2V0DQp2YXIubmFtZXMgPSBjKCJhZ2UiLCAiYmFsYW5jZSIsICJkYXkiLCAiZ3JwLmpvYiIsICJtYXJpdGFsIiwgImVkdWNhdGlvbiIsICJkZWZhdWx0IiwgImhvdXNpbmciLCAibG9hbiIsICJjb250YWN0IiwgImdycC5tb250aCIsICJncnAuZHVyYXRpb24iLCAiZ3JwLmNhbXBhaWduIiwgImdycC5wZGF5cyIsICJncnAucHJldmlvdXMiLCAicG91dGNvbWUiLCAieSIpIA0KQmFua01hcmtldGluZ0NhbXBhaWduID0gQmFua01hcmtldGluZ1ssIHZhci5uYW1lc10NCmBgYA0KIyMgQXNzZXNzaW5nIFBhaXJ3aXNlZCBSZWxhdGlvbnNoaXANCldlIHdhbnQgdG8gc2VlIGlmIHRoZXJlIGlzIGFueSBsaW5lYXIgYXNzb2NpYXRpb24gYmV0d2VlbiB0aGUgbnVtZXJpYyB2YXJpYWJsZXMuIEEgUGFpcndpc2Ugc2NhdHRlciBwbG90IGxpa2UgYmVsb3csIGlzIGEgZ29vZCB2aXN1YWwuIFRoaXMgc2NhdHRlcnBsb3QgYmxvdyBsb29rcyBhdCB0aGUgZGF0YS4NCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD04fQ0KIyBQYWlyLXdpc2Ugc2NhdHRlciBwbG90IGZvciBudW1lcmljIHZhcmlhYmxlcw0KZ2dwYWlycyhCYW5rTWFya2V0aW5nQ2FtcGFpZ24sICAjIERhdGEgZnJhbWUNCiAgICAgICAgY29sdW1ucyA9IDE6MywgICMgQ29sdW1ucw0KICAgICAgICBhZXMoY29sb3IgPSB5LCAgIyBDb2xvciBieSBncm91cCAoY2F0LiB2YXJpYWJsZSkNCiAgICAgICAgICAgIGFscGhhID0gMC41KSkNCmBgYA0KV2Ugc2VlIHRoYXQgbm9uZSBvZiB0aGUgbnVtZXJpYyB2YXJpYWJsZXMgYXBwZWFyIHRvIGJlIHNpZ25pZmljYW50bHkgY29ycmVsYXRlZCB3aGVuIGxvb2tpbmcgYXQgdGhlIG51bWJlcnMuIEJ1dCwgdGhlIHN0YWNrZWQgZGVuc2l0eSBjdXJlcyBhbmQgbm8gY29tcGxldGVseSBvdmVybGFwcGluZywgaW5kaWNhdGluZyBhIHdlZWsgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgbnVtZXJpYyB2YXJpYWJsZXMgYW5kIG91ciByZXNwb25zZSAneScuIFRoZSBzdHJvbmdlc3QgYnV0ICd3ZWFrIGNvcnJlbGF0aW9uIHdlIGNhbiBzZWUgdGhyb3VnaCB0aGUgcGxvdHMgaXMgYWdlIGFuZCBiYWxhbmNlLg0KDQoNCk5vdyB0aGF0IHdlIGxvb2tlZCBhdCB0aGUgbnVtZXJpYyB2YXJpYWJsZXMsIHdlIGNhbiB0dXJuIHRoZSBhdHRlbnRpb24gdG8gY2F0ZWdvcmljYWwgdmFyaWFibGVzIGFuZCBkZXBlbmRlbmN5LiBJbiBvcmRlciB0byBzZWUgZGVwZW5kZW5jeSwgbW9zYWljIHBsb3RzIHdpbGwgYmUgc2hvd24gYmVsb3cuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9OH0NCiMgTW9zYWljIHBsb3RzIHRvIHNob3cgY2F0ZWdvcmljYWwgdmFyaWFibGUgZGVwZW5kZW5jeSB0byB0aGUgcmVzcG9uc2UuDQpwYXIobWZyb3cgPSBjKDIsMikpDQptb3NhaWNwbG90KGdycC5qb2IgfiB5LCBkYXRhPUJhbmtNYXJrZXRpbmdDYW1wYWlnbixjb2w9YygiQmx1ZSIsIlJlZCIpLCBtYWluPSJqb2IgdnMgdGVybSBkZXBvc2l0ICIpDQptb3NhaWNwbG90KG1hcml0YWwgfiB5LCBkYXRhPUJhbmtNYXJrZXRpbmdDYW1wYWlnbixjb2w9YygiQmx1ZSIsIlJlZCIpLCBtYWluPSJtYXJpdGFsIHZzIHRlcm0gZGVwb3NpdCAiKQ0KbW9zYWljcGxvdChlZHVjYXRpb24gfiB5LCBkYXRhPUJhbmtNYXJrZXRpbmdDYW1wYWlnbixjb2w9YygiQmx1ZSIsIlJlZCIpLCBtYWluPSJlZHVjYXRpb24gdnMgdGVybSBkZXBvc2l0ICIpDQptb3NhaWNwbG90KGRlZmF1bHQgfiB5LCBkYXRhPUJhbmtNYXJrZXRpbmdDYW1wYWlnbixjb2w9YygiQmx1ZSIsIlJlZCIpLCBtYWluPSJkZWZhdWx0IHZzIHRlcm0gZGVwb3NpdCAiKQ0KYGBgDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9OH0NCiMgTW9zYWljIHBsb3RzIHRvIHNob3cgY2F0ZWdvcmljYWwgdmFyaWFibGUgZGVwZW5kZW5jeSB0byB0aGUgcmVzcG9uc2UuDQpwYXIobWZyb3cgPSBjKDIsMikpDQptb3NhaWNwbG90KGhvdXNpbmcgfiB5LCBkYXRhPUJhbmtNYXJrZXRpbmdDYW1wYWlnbixjb2w9YygiQmx1ZSIsIlJlZCIpLCBtYWluPSJob3VzaW5nIHZzIHRlcm0gZGVwb3NpdCAiKQ0KbW9zYWljcGxvdChsb2FuIH4geSwgZGF0YT1CYW5rTWFya2V0aW5nQ2FtcGFpZ24sY29sPWMoIkJsdWUiLCJSZWQiKSwgbWFpbj0ibG9hbiB2cyB0ZXJtIGRlcG9zaXQgIikNCm1vc2FpY3Bsb3QoY29udGFjdCB+IHksIGRhdGE9QmFua01hcmtldGluZ0NhbXBhaWduLGNvbD1jKCJCbHVlIiwiUmVkIiksIG1haW49ImNvbnRhY3QgdnMgdGVybSBkZXBvc2l0ICIpDQptb3NhaWNwbG90KGdycC5tb250aCB+IHksIGRhdGE9QmFua01hcmtldGluZ0NhbXBhaWduLGNvbD1jKCJCbHVlIiwiUmVkIiksIG1haW49Im1vbnRoIHZzIHRlcm0gZGVwb3NpdCAiKQ0KYGBgDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9OH0NCiMgTW9zYWljIHBsb3RzIHRvIHNob3cgY2F0ZWdvcmljYWwgdmFyaWFibGUgZGVwZW5kZW5jeSB0byB0aGUgcmVzcG9uc2UuDQpwYXIobWZyb3cgPSBjKDMsMikpDQptb3NhaWNwbG90KGdycC5kdXJhdGlvbiB+IHksIGRhdGE9QmFua01hcmtldGluZ0NhbXBhaWduLGNvbD1jKCJCbHVlIiwiUmVkIiksIG1haW49ImR1cmF0aW9uIHZzIHRlcm0gZGVwb3NpdCAiKQ0KbW9zYWljcGxvdChncnAuY2FtcGFpZ24gfiB5LCBkYXRhPUJhbmtNYXJrZXRpbmdDYW1wYWlnbixjb2w9YygiQmx1ZSIsIlJlZCIpLCBtYWluPSJjYW1wYWlnbiB2cyB0ZXJtIGRlcG9zaXQgIikNCm1vc2FpY3Bsb3QoZ3JwLnBkYXlzIH4geSwgZGF0YT1CYW5rTWFya2V0aW5nQ2FtcGFpZ24sY29sPWMoIkJsdWUiLCJSZWQiKSwgbWFpbj0icGRheXMgdnMgdGVybSBkZXBvc2l0ICIpDQptb3NhaWNwbG90KGdycC5wcmV2aW91cyB+IHksIGRhdGE9QmFua01hcmtldGluZ0NhbXBhaWduLGNvbD1jKCJCbHVlIiwiUmVkIiksIG1haW49InByZXZpb3VzIHZzIHRlcm0gZGVwb3NpdCAiKQ0KbW9zYWljcGxvdChwb3V0Y29tZSB+IHksIGRhdGE9QmFua01hcmtldGluZ0NhbXBhaWduLGNvbD1jKCJCbHVlIiwiUmVkIiksIG1haW49InBvdXRjb21lIHZzIHRlcm0gZGVwb3NpdCAiKQ0KYGBgDQoNCg0KDQojIFByZWRpY3RpdmUgTW9kZWxpbmcgd2l0aCBMb2dpc3RpYyBSZWdyZXNzaW9uDQpUaGUgcmV2aXNlZCBkYXRhIGZyb20gdGhlIEVEQSBkb25lIGFib3ZlIHdpbGwgYmUgdXNlZCB0byBydW4gZGlmZmVyZW50IGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWxzLiBUaGUgb3B0aW1hbCBmaW5hbCBtb2RlbCB3aWxsIGJlIGZvdW5kIGZyb20gY2FuaWRhdGUgbW9kZWxzLCB3aGljaCB3aWxsIGJlIHVzZWQgdG8gY2FsY3VsYXRlIHByb2JhYmlsaXRpZXMgZm9yIHByZWRpY3Rpbmcgd2hldGhlciBvciBub3QgYSBjbGllbnQgaGFzIHN1YnNjcmliZWQgYWZ0ZXIgZGlyZWN0IG1hcmtldGluZyBjYW1wYWlnbnMuDQoNCldlIGNhbiB1c2UgYSBsb2dpc3RpY2FsIG1vZGVsIGZvciBwcmVkaWN0aW5nIHdoZXRoZXIgb3Igbm90IGEgY2xpZW50IGhhcyBzdWJzY3JpYmVkIGEgdGVybSBkZXBvc2l0IGFmdGVyIGRpcmVjdCBtYXJrZXRpbmcgY2FtcGFpZ25zLlRoZSB2YXJpYWJsZSwgeSwgd2hpY2ggdGVsbHMgd2hldGhlciBhIGNsaWVudCBoYXMgc3Vic2NyaWJlZCBhIHRlcm0gZGVwb3NpdCwgYWN0cyBhcyB0aGUgYmluYXJ5IHJlc3BvbnNlIHZhcmlhYmxlIG9mIGFsbCB0aGUgbG9naXN0aWMgbW9kZWxzLiBUaGUgcmVzdCBvZiB0aGUgdmFyaWFibGVzLCBpbmNsdWRpbmcgdGhlIG5ldyBkaXNjcmV0aXplZCB2YXJpYWJsZXMsIG9mIHRoZSByZXZpc2VkIGRhdGEgc2V0IGFjdCBhcyB0aGUgcHJlZGljdG9yIHZhcmlhYmxlcyB0aGF0IHdpbGwgcG9zc2libHkgYWZmZWN0IHRoZSByZXNwb25zZSAneScuDQoNCg0KSW4gb3JkZXIgdG8gcGVyZm9ybSBwcm9wZXIgbW9kZWxpbmcsIHNvbWUgY2F0ZWdvcmljYWwgYW5kIGJpbmFyeSB2YXJpYWJsZXMgaGFkIHRvIGJlIGNoYW5nZWQsIGluY2x1ZGluZyB0aGUgcmVzcG9uc2UgKHkpIGFuZCB0aGUgb25lcyBjaGFuZ2VkIGluIHRoZSBFREEsIHRvIGhhdmUgbnVtZXJpY2FsIGxhYmVscywgdGhlcmVieSBtYWtpbmcgdGhlbSBlYXNpZXIgdG8gdXNlIGZvciBtb2RlbGluZy4NCg0KVGhlIGZpcnN0IGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgdGhhdCB3aWxsIGJlIGJ1aWx0IGlzIGFuIGluaXRpYWwgZnVsbCBtb2RlbCB0aGF0IGNvbnRhaW5zIGFsbCBwcmVkaWN0b3JzIHZhcmlhYmxlcyBvZiB0aGUgZGF0YSBzZXQuIEF1dG9tYXRpYyB2YXJpYWJsZSBzZWxlY3Rpb24gd2lsbCB0aGVuIGJlIHVzZWQgdG8gZmluZCBhIGZpbmFsIG1vZGVsLiBJbiBsb29raW5nIGF0IHRoZSBwLXZhbHVlcyBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBpbml0aWFsIG1vZGVsLCB0aG9zZSB0aGF0IGFyZSBpbnNpZ25pZmljYW50IGF0IHRoZSAwLjA1IGxldmVsIHdpbGwgYmUgZHJvcHBlZC4gDQoNClRoZSB2YXJpYWJsZXMgcmVtYWluaW5nIHRoYXQgYXJlIGVpdGhlciBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IG9yIGltcG9ydGFudCBmb3IgdGhlIG1vZGVsIHdpbGwgYmUgdXNlZCB0byBjcmVhdGUgYSBzb3J0IG9mIHJlZHVjZWQgbW9kZWwuIEEgdGhpcmQgYW5kIGZpbmFsIG1vZGVsLCB0aGF0IGlzIGJldHdlZW4gdGhlIGZ1bGwgYW5kIHJlZHVjZWQgbW9kZWxzLCB3aWxsIHRoZW4gYmUgZm91bmQuIFBlcmZvcm1hbmNlIG9mIHByZWRpY3RpdmUgcG93ZXIgd2lsbCBiZSBhbmFseXplZCBmb3IgYWxsIHByZWRpY3RvciB2YXJpYWJsZXMgYXMgd2VsbCBhcyB0aGVpciBhc3NvY2lhdGlvbiB0byB0aGUgcmVzcG9uc2UuDQoNCkZpbmFsbHksIHRoaXMgZmluYWwgbW9kZWwgd2lsbCBiZSB1c2VkIHRvIGNhbGN1bGF0ZSBwcmVkaWN0aXZlIHByb2JhYmlsaXR5IGZvciB2YWx1ZXMgb2YgdGhlIHJlc3BvbnNlIHZhcmlhYmxlLiBXaGVuIHZhbHVlcyBvZiBwcmVkaWN0b3IgdmFsdWVzIGFyZSBlbnRlcmVkLCB0aGUgcHJlZGljdGVkIHZhbHVlIG9mIHdoZXRoZXIgb3Igbm90IGEgY2xpZW50IGhhcyBzdWJzY3JpYmVkIGEgdGVybSBkZXBvc2l0IChlaXRoZXIgWWVzIG9yIE5vKSBpcyBnaXZlbi4NCg0KDQojIyBUdXJuaW5nIGZlYXR1cmVzIGluIERpc2NyZXRlIFZhcmlhYmxlcw0KDQpBbGwgdGhlIGNhdGVnb3JpY2FsIGFuZCBiaW5hcnkgdmFyaWJsZXMgaW5jbHVkaW5nIHRoZSByZXNwb25zZSBtdXN0IGJlIGNoYW5nZWQgaW50byBkaXNjcmV0ZSBudW1lcmljYWwgdmFyaWJsZXMuIEl0IHdpbGwgYmUgYXMgYXMgZm9sbG93czoNCg0KeSAocmVzcG9uc2UpOiAwPW5vLCAxPXllcw0KDQpncnAuam9iOiAwPW5vdCB3b3JraW5nLCAxPXdvcmtlcnMsIDI9Ym9zc2VzLCAzPXdoaXRlLWNvbGxhcg0KDQptYXJpdGFsOiAwPWRpdm9yY2VkLCAxPXNpbmdsZSwgMj1tYXJyaWVkDQoNCmVkdWNhdGlvbjogMD11bmtub3duLCAxPXByaW1hcnksIDI9c2Vjb25kYXJ5LCAzPXRlcnRpYXJ5DQoNCmhvdXNpbmc6IDA9bm8sIDE9eWVzDQoNCmxvYW46IDA9bm8sIDE9eWVzDQoNCmNvbnRhY3Q6IDA9dW5rbm93biwgMT10ZWxlcGhvbmUsIDI9Y2VsbHVsYXINCg0KZ3JwLm1vbnRoOiAwPXdpbnRlciwgMT1zcHJpbmcsIDI9c3VtbWVyLCAzPWZhbGwNCg0KZ3JwLmR1cmF0aW9uOiAwPSgwLTE4MCksIDE9KDE4MS0zMTkpLCAyPTMyMCsNCg0KZ3JwLmNhbXBhaWduOiAwPTEsIDE9KDItMyksIDI9NCsNCg0KZ3JwLnBkYXlzOiAwPUNsaWVudCBOb3QgUHJldmlvdXNseSBDb250YWN0ZWQsIDE9KDEtMTk5KSwgMj0yMDArDQoNCmdycC5wcmV2aW91czogMD0wLCAxPSgxLTMpLCAyPTQrDQoNCmBgYHtyfQ0KIyBDcmVhdGUgbnVtZXJpY2FsIHZhbHVlIGxhYmVscyBmb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzDQpCYW5rTWFya2V0aW5nQ2FtcGFpZ24keSA8LSBmYWN0b3IoQmFua01hcmtldGluZ0NhbXBhaWduJHksIGxldmVscyA9IGMoIiBubyIsICIgeWVzIiksIGxhYmVscyA9IGMoIjAiLCAiMSIpKQ0KDQpCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZ3JwLmpvYiA8LSBmYWN0b3IoQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5qb2IsIGxldmVscyA9IGMoIm5vdCB3b3JraW5nIiwgIndvcmtlcnMiLCAiYm9zc2VzIiwgIndoaXRlLWNvbGxhciIpLCBsYWJlbHMgPSBjKCIwIiwgIjEiLCAiMiIsICIzIikpDQoNCkJhbmtNYXJrZXRpbmdDYW1wYWlnbiRtYXJpdGFsIDwtIGZhY3RvcihCYW5rTWFya2V0aW5nQ2FtcGFpZ24kbWFyaXRhbCwgbGV2ZWxzID0gYygiIGRpdm9yY2VkIiwgIiBzaW5nbGUiLCAiIG1hcnJpZWQiKSwgbGFiZWxzID0gYygiMCIsICIxIiwgIjIiKSkNCg0KQmFua01hcmtldGluZ0NhbXBhaWduJGVkdWNhdGlvbiA8LSBmYWN0b3IoQmFua01hcmtldGluZ0NhbXBhaWduJGVkdWNhdGlvbiwgbGV2ZWxzID0gYygiIHVua25vd24iLCAiIHByaW1hcnkiLCAiIHNlY29uZGFyeSIsICIgdGVydGlhcnkiKSwgbGFiZWxzID0gYygiMCIsICIxIiwgIjIiLCAiMyIpKQ0KICANCkJhbmtNYXJrZXRpbmdDYW1wYWlnbiRob3VzaW5nIDwtIGZhY3RvcihCYW5rTWFya2V0aW5nQ2FtcGFpZ24kaG91c2luZywgbGV2ZWxzID0gYygiIG5vIiwgIiB5ZXMiKSwgbGFiZWxzID0gYygiMCIsICIxIikpDQogIA0KQmFua01hcmtldGluZ0NhbXBhaWduJGxvYW4gPC0gZmFjdG9yKEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRsb2FuLCBsZXZlbHMgPSBjKCIgbm8iLCAiIHllcyIpLCBsYWJlbHMgPSBjKCIwIiwgIjEiKSkNCg0KQmFua01hcmtldGluZ0NhbXBhaWduJGNvbnRhY3QgPC0gZmFjdG9yKEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRjb250YWN0LCBsZXZlbHMgPSBjKCIgdW5rbm93biIsICIgdGVsZXBob25lIiwgIiBjZWxsdWxhciIpLCBsYWJlbHMgPSBjKCIwIiwgIjEiLCAiMiIpKQ0KDQpCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZ3JwLm1vbnRoIDwtIGZhY3RvcihCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZ3JwLm1vbnRoLCBsZXZlbHMgPSBjKCJ3aW50ZXIiLCAic3ByaW5nIiwgInN1bW1lciIsICJmYWxsIiksIGxhYmVscyA9IGMoIjAiLCAiMSIsICIyIiwgIjMiKSkNCg0KQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5kdXJhdGlvbiA8LSBmYWN0b3IoQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5kdXJhdGlvbiwgbGV2ZWxzID0gYygiMC0xODAiLCAiMTgxLTMxOSIsICIzMjArIiksIGxhYmVscyA9IGMoIjAiLCAiMSIsICIyIikpDQogIA0KQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5jYW1wYWlnbiA8LSBmYWN0b3IoQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5jYW1wYWlnbiwgbGV2ZWxzID0gYygiMSIsICIyLTMiLCAiNCsiKSwgbGFiZWxzID0gYygiMCIsICIxIiwgIjIiKSkNCiAgDQpCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZ3JwLnBkYXlzIDwtIGZhY3RvcihCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZ3JwLnBkYXlzLCBsZXZlbHMgPSBjKCJDbGllbnQgTm90IFByZXZpb3VzbHkgQ29udGFjdGVkIiwgIjEtMTk5IiwgIjIwMCsiKSwgbGFiZWxzID0gYygiMCIsICIxIiwgIjIiKSkNCiAgDQpCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZ3JwLnByZXZpb3VzIDwtIGZhY3RvcihCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZ3JwLnByZXZpb3VzLCBsZXZlbHMgPSBjKCIwIiwgIjEtMyIsICI0KyIpLCBsYWJlbHMgPSBjKCIwIiwgIjEiLCAiMiIpKQ0KYGBgDQoNCiMjIENyZWF0ZSBDYW5kaWRhdGUgTW9kZWxzDQoNCg0KVGhlIGZ1bGwvaW5pdGlhbCBtb2RlbCBjb250YWluaW5nIGFsbCBvZiB0aGUgcHJlZGljdG9yIHZhcmlhYmxlcyB3aWxsIGJlIG1hZGUgZmlyc3QsIHdpdGggJ3knICh3aGV0aGVyIG9yIG5vdCBhIGNsaWVudCBoYXMgc3Vic2NyaWJlZCBhIHRlcm0gZGVwb3NpdCkgYXMgdGhlIHJlc3BvbnNlLiBUaGUgdmFyaWFibGVzIGJhbGFuY2UgYW5kIGRlZmF1bHQgYXJlIG5vdCBpbmNsdWRlZCBzaW5jZSBvdXIgRURBIHNob3dlZCB0aGF0IHJlbW92aW5nIHRoZW0gZnJvbSB0aGUgbW9kZWwgbWlnaHQgaGVscCB0aGUgcmVzdWx0cy4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIENyZWF0ZSB0aGUgaW5pdGlhbCBmdWxsIG1vZGVsDQojIENyZWF0ZSB0aGUgaW5pdGlhbCBmdWxsIG1vZGVsDQppbml0aWFsLm1vZGVsID0gZ2xtKHkgfiBhZ2UgKyBkYXkgKyBncnAuam9iICsgbWFyaXRhbCArIGVkdWNhdGlvbiArIGhvdXNpbmcgKyBsb2FuICsgY29udGFjdCArZ3JwLmR1cmF0aW9uICtncnAuY2FtcGFpZ24gK2dycC5wZGF5cyArZ3JwLnByZXZpb3VzLCBmYW1pbHkgPSBiaW5vbWlhbCwgZGF0YSA9IEJhbmtNYXJrZXRpbmdDYW1wYWlnbikNCmNvZWZmaWNpZW50LnRhYmxlID0gc3VtbWFyeShpbml0aWFsLm1vZGVsKSRjb2VmDQprYWJsZShjb2VmZmljaWVudC50YWJsZSwgY2FwdGlvbiA9ICJTaWduaWZpY2FuY2UgdGVzdHMgb2YgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCIpDQpgYGANCg0KV2UgY2FuIHNlZSB0aGF0IHRoZXJlIGFyZSBzb21lIGluc2lnbmlmaWNhbnQgcHJlZGljdG9yIHZhcmlhYmxlcywgYW5kIHRoZXkgc2hvdWxkIGJlIGRyb3BwZWQgZnJvbSB0aGUgbW9kZWwgdG8gY3JlYXRlIGEgcmVkdWNlZCBtb2RlbC4gVXNpbmcgdGhlIHN0ZXAoKSBmdW5jdGlvbiwgd2Ugd2lsbCBub3cgZmluZCAgcmVkdWNlZCBhbmQgZmluYWwgbW9kZWxzLiBUaGUgZmluYWwgYmVzdCBtb2RlbCB3aWxsIGJlIGEgbW9kZWwgdGhhdCBpcyBiZXR3ZWVuIHRoZSBmdWxsIGFuZCByZWR1Y2VkIG1vZGVscy4NCg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMgQ3JlYXRpbmcgdGhlIHJlZHVjZWQgYW5kIGZpbmFsIG1vZGVscw0KZnVsbC5tb2RlbCA9IGluaXRpYWwubW9kZWwgICMgdGhlICpiaWdnZXN0IG1vZGVsKiB0aGF0IGluY2x1ZGVzIGFsbCBwcmVkaWN0b3IgdmFyaWFibGVzDQpyZWR1Y2VkLm1vZGVsID0gZ2xtKHkgfiBkYXkgKyBncnAuam9iICsgbWFyaXRhbCArIGhvdXNpbmcgKyBsb2FuICsgY29udGFjdCArIGdycC5kdXJhdGlvbiArIGdycC5jYW1wYWlnbiArIGdycC5wcmV2aW91cywgZmFtaWx5ID0gYmlub21pYWwsIGRhdGEgPSBCYW5rTWFya2V0aW5nQ2FtcGFpZ24pDQpmaW5hbC5tb2RlbCA9ICBzdGVwKGZ1bGwubW9kZWwsIA0KICAgICAgICAgICAgICAgICAgICBzY29wZT1saXN0KGxvd2VyPWZvcm11bGEocmVkdWNlZC5tb2RlbCksdXBwZXI9Zm9ybXVsYShmdWxsLm1vZGVsKSksDQogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBCYW5rTWFya2V0aW5nQ2FtcGFpZ24sIA0KICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAiYmFja3dhcmQiLA0KICAgICAgICAgICAgICAgICAgICB0cmFjZSA9IDApICAgIyB0cmFjZSA9IDA6IHN1cHByZXNzIHRoZSBkZXRhaWxlZCBzZWxlY3Rpb24gcHJvY2Vzcw0KZmluYWwubW9kZWwuY29lZiA9IHN1bW1hcnkoZmluYWwubW9kZWwpJGNvZWYNCmthYmxlKGZpbmFsLm1vZGVsLmNvZWYsIGNhcHRpb24gPSAiU3VtbWFyeSB0YWJsZSBvZiBzaWduaWZpY2FudCB0ZXN0cyIpDQpgYGANCg0KIyMgUHJlZGljdGlvbg0KDQojIyBQcmVkaWN0aXZlIFByb2JhYmlsaXR5IEFuYWx5c2lzIGZvciBDbGllbnRzIFN1YnNjcmliaW5nIFRlcm0gRGVwb3NpdHMNCg0KTm93IHRoYXQgYSBmaW5hbCBtb2RlbCBoYXMgYmVlbiBjcmVhdGVkLCB3ZSBjYW4gcHJlZGljdCB3aGV0aGVyIG9yIG5vdCBhIGNsaWVudCBoYXMgc3Vic2NyaWJlZCBhIHRlcm0gZGVwb3NpdCBiYXNlZCBvbiBnaXZlbiB2YWx1ZXMgb2YgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMgaW4gdGhlIGZpbmFsIG1vZGVsIGFzc29jaWF0ZWQgd2l0aCB0d28gY2xpZW50cy4gQSB0aHJlc2hvbGQgcHJvYmFiaWxpdHkgb2YgMC41IGlzIHVzZWQgdG8gcHJlZGljdCB0aGUgcmVzcG9uc2UgdmFsdWUuDQoNCmBgYHtyfQ0KIyBQcmVkaWN0aW5nIFJlc3BvbnNlIFZhbHVlIGZvciBCYW5raW5nIENsaWVudCBHaXZlbiBWYXJpYWJsZSBWYWx1ZXMgZm9yIHRoZSBGaW5hbCBNb2RlbA0KbXluZXdkYXRhID0gZGF0YS5mcmFtZShhZ2U9YygzNCw2NCksDQogICAgICAgICAgICAgICAgICAgICAgIGRheSA9IGMoMyw1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgZ3JwLmpvYiA9IGMoIjEiLCIxIiksDQogICAgICAgICAgICAgICAgICAgICAgIG1hcml0YWwgPSBjKCIxIiwiMSIpLA0KICAgICAgICAgICAgICAgICAgICAgICBlZHVjYXRpb24gPSBjKCIxIiwiMyIpLA0KICAgICAgICAgICAgICAgICAgICAgICBob3VzaW5nID0gYygiMSIsIjEiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgbG9hbiA9IGMoIjAiLCIwIiksDQogICAgICAgICAgICAgICAgICAgICAgIGNvbnRhY3QgPSBjKCIxIiwiMCIpLA0KICAgICAgICAgICAgICAgICAgICAgICBncnAubW9udGggPSBjKCIzIiwiMSIpLA0KICAgICAgICAgICAgICAgICAgICAgICBncnAuZHVyYXRpb24gPSBjKCIyIiwiMSIpLA0KICAgICAgICAgICAgICAgICAgICAgICBncnAuY2FtcGFpZ24gPSBjKCIwIiwiMSIpLA0KICAgICAgICAgICAgICAgICAgICAgICBncnAucGRheXMgPSBjKCIyIiwiMiIpLA0KICAgICAgICAgICAgICAgICAgICAgICBncnAucHJldmlvdXMgPSBjKCIxIiwiMCIpKQ0KcHJlZC5zdWNjZXNzLnByb2IgPSBwcmVkaWN0KGZpbmFsLm1vZGVsLCBuZXdkYXRhID0gbXluZXdkYXRhLCB0eXBlPSJyZXNwb25zZSIpDQoNCiMjIHRocmVzaG9sZCBwcm9iYWJpbGl0eQ0KY3V0Lm9mZi5wcm9iID0gMC41DQpwcmVkLnJlc3BvbnNlID0gaWZlbHNlKHByZWQuc3VjY2Vzcy5wcm9iID4gY3V0Lm9mZi5wcm9iLCAxLCAwKSAgIyBUaGlzIHByZWRpY3RzIHRoZSByZXNwb25zZQ0KDQojIEFkZCB0aGUgbmV3IHByZWRpY3RlZCByZXNwb25zZSB0byBNeW5ld2RhdGENCm15bmV3ZGF0YSRQcmVkLlJlc3BvbnNlID0gcHJlZC5yZXNwb25zZQ0Ka2FibGUobXluZXdkYXRhLCBjYXB0aW9uID0gIlByZWRpY3RlZCBWYWx1ZSBvZiBSZXNwb25zZSB3aXRoIGdpdmVuIGN1dC1vZmYiKQ0KYGBgDQpMb29raW5nIGF0IHRoZSBwcmVkaWN0ZWQgcmVzcG9uc2UsIHdlIGNhbiBzZWUgdGhhdCBuZWl0aGVyIG9mIHRoZXNlIGNsaWVudHMgd2lsbCBzdXNjcmliZSB0byB0aGUgbWFya2V0aW5nIGNhbXBhaWduLg0KIyMgTW9kZWwgU2VsZWN0aW9uDQoNCg0KTm93IHRoYXQgd2UgaGF2ZSBvdXIgY2FuaWRhdGUgbW9kZWxzLiBJdCBpcyB0aW1lIHRvIHBlcmZvcm0gc29tZSBtb2RlbCBzZWxjdGlvbiB1c2luZyB0aGUgUk9DIGN1cnZlIGFuZCBBVUMuDQoNClNpbmNlIG91ciBzYW1wbGUgaXMgcmVsYXRpdmVseSBsYXJnZSwgd2Ugd2lsbCByYW5kb21seSBzcGxpdCB0aGUgb3ZlcmFsbCBkYXRhIHNldCBpbnRvIHR3byBkYXRhIHNldHMuIDcwJSBvZiB0aGUgZGF0YSB3aWxsIGJlIHB1dCBpbiBhIHRyYWluaW5nIGRhdGEgc2V0IGZvciB0cmFpbmluZyBhbmQgdmFsaWRhdGluZyBtb2RlbHMuIFRoZSBvdGhlciAzMCUgZ29lcyBpbnRvIGEgdGVzdGluZyBkYXRhIHNldCB1c2VkIGZvciB0ZXN0aW5nIHRoZSBmaW5hbCBtb2RlbC4gVGhlIHZhbHVlIGxhYmVscyBvZiB0aGUgcmVzcG9uc2UgKHllcy9ubykgdXNlZCBmb3IgdGVzdGluZyBhbmQgdmFsaWRhdGlvbiBkYXRhIHdpbGwgYmUgcmVtb3ZlZCB3aGVuIGNhbGN1bGF0aW5nIHRoZSBhY2N1cmFjeSBtZWFzdXJlcyBsYXRlci4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIyBSZWNvZGUgcmVzcG9uc2UgdmFyaWFibGU6IHllcyA9IDEgYW5kIG5vID0gMA0KeWVzLmlkID0gd2hpY2goQmFua01hcmtldGluZ0NhbXBhaWduJHkgPT0gIjEiKSANCm5vLmlkID0gd2hpY2goQmFua01hcmtldGluZ0NhbXBhaWduJHkgPT0gIjAiKQ0KDQojIyBDcmVhdGluZyB0aGUgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YSBzZXRzDQpCYW5rTWFya2V0aW5nQ2FtcGFpZ24keS5zdWJzY3JpYmUgPSAxDQpCYW5rTWFya2V0aW5nQ2FtcGFpZ24keS5zdWJzY3JpYmVbbm8uaWRdID0gMA0KdmFyLm5hbWVzID0gYygiYWdlIiwgImRheSIsICJncnAuam9iIiwgIm1hcml0YWwiLCJlZHVjYXRpb24iLCJob3VzaW5nIiwibG9hbiIsImNvbnRhY3QiLCJncnAubW9udGgiLCAgICAiZ3JwLmR1cmF0aW9uIiwiZ3JwLmNhbXBhaWduIiwiZ3JwLnBkYXlzIiwiZ3JwLnByZXZpb3VzIiwgInkuc3Vic2NyaWJlIiApDQpCYW5rTWFya2V0aW5nQ2FtcGFpZ24gPSBCYW5rTWFya2V0aW5nQ2FtcGFpZ25bLCB2YXIubmFtZXNdDQpubiA9IGRpbShCYW5rTWFya2V0aW5nQ2FtcGFpZ24pWzFdDQp0cmFpbi5pZCA9IHNhbXBsZSgxOm5uLCByb3VuZChubiowLjcpLCByZXBsYWNlID0gRkFMU0UpIA0KIyMjIw0KdHJhaW5pbmcgPSBCYW5rTWFya2V0aW5nQ2FtcGFpZ25bdHJhaW4uaWQsXQ0KdGVzdGluZyA9IEJhbmtNYXJrZXRpbmdDYW1wYWlnblstdHJhaW4uaWQsXQ0KYGBgDQoNCiMjIEN1dC1vZmYgUHJvYmFiaWxpdHkgU2VhcmNoIGFuZCBBY2N1cmFjeSBTY29yZQ0KDQpJbiBvcmRlciB0byBmaW5kIGFuIG9wdGltYWwgY3V0LW9mZiBwcm9iYWJpbGl0eSwgYSBzZXF1ZW5jZSBvZiAyMCBjYW5kaWRhdGUgY3V0LW9mZiBwcm9iYWJpbGl0aWVzIHdpbGwgYmUgZGVmaW5lZC4gVGhlbiwgYSA1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiB3aWxsIGJlIHBlcmZvcm1lZCB0byBmaW5kIHRoZSBvcHRpbWFsIGN1dC1vZmYgcHJvYmFiaWxpdHkgb2YgdGhlIGZpbmFsIG1vZGVsLiBBbGwgdGhyZWUgbW9kZWxzIGNyZWF0ZWQgd2lsbCBiZSB1c2VkIHRvIGZpbmQgdGhlIG9wdGltYWwgY3V0LW9mZi4gVGhpcyBpcyBzaG93biBiZWxvdy4NCmBgYHtyLCBmaWcuYWxpZ249ICdjZW50ZXInLCBmaWcuY2FwPSI1LWZvbGQgQ1YgcGVyZm9ybWFuY2UgcGxvdCIsIHdhcm5pbmc9RkFMU0V9DQpuMCA9IGRpbSh0cmFpbmluZylbMV0vNQ0KDQojIGNhbmRpZGF0ZSBjdXQgb2ZmIHByb2INCmN1dC4wZmYucHJvYiA9IHNlcSgwLDEsIGxlbmd0aCA9IDIyKVstYygxLDIyKV0NCg0KIyBudWxsIHZlY3RvciBmb3Igc3RvcmluZyBwcmVkaWN0aW9uIGFjY3VyYWN5DQpwcmVkLmFjY3VyYWN5ID0gbWF0cml4KDAsbmNvbD0yMCwgbnJvdz01LCBieXJvdyA9IFQpDQoNCiMjIDUtZm9sZCBDVg0KZm9yIChpIGluIDE6NSl7DQogIHZhbGlkLmlkID0gKChpLTEpKm4wICsgMSk6KGkqbjApDQogIHZhbGlkLmRhdGEgPSB0cmFpbmluZ1t2YWxpZC5pZCxdDQogIHRyYWluLmRhdGEgPSB0cmFpbmluZ1stdmFsaWQuaWQsXQ0KICB0cmFpbi5tb2RlbCA9IGdsbSh5LnN1YnNjcmliZSB+IGFnZSArIGRheSArIGdycC5qb2IgKyBtYXJpdGFsICsgZWR1Y2F0aW9uICsgaG91c2luZyArIGxvYW4gKyBjb250YWN0ICsgIGdycC5kdXJhdGlvbiArIGdycC5jYW1wYWlnbiArIGdycC5wZGF5cyArIGdycC5wcmV2aW91cywgZmFtaWx5ID0gYmlub21pYWwobGluayA9IGxvZ2l0KSwgZGF0YSA9IHRyYWluLmRhdGEpDQojIyMjDQogIHByZWQucHJvYiA9IHByZWRpY3QuZ2xtKHRyYWluLm1vZGVsLCB2YWxpZC5kYXRhLCB0eXBlID0gInJlc3BvbnNlIikNCiAgIyBkZWZpbmUgY29uZnVzaW9uIG1hdHJpeCBhbmQgYWNjdXJhY3kNCiAgZm9yKGogaW4gMToyMCl7DQogICAgI3ByZWQuc3Vic2NyaWJlID0gcmVwKDAsbGVuZ3RoKHByZWQucHJvYikpDQogICAgdmFsaWQuZGF0YSRwcmVkLnN1YnNjcmliZSA9IGFzLm51bWVyaWMocHJlZC5wcm9iID4gY3V0LjBmZi5wcm9iW2pdKQ0KICAgIGExMSA9IHN1bSh2YWxpZC5kYXRhJHByZWQuc3Vic2NyaWJlID09IHZhbGlkLmRhdGEkeS5zdWJzY3JpYmUpDQogICAgcHJlZC5hY2N1cmFjeVtpLGpdID0gYTExL2xlbmd0aChwcmVkLnByb2IpDQogIH0NCn0NCiMjDQphdmcuYWNjdXJhY3kgPSBhcHBseShwcmVkLmFjY3VyYWN5LCAyLCBtZWFuKQ0KbWF4LmlkID0gd2hpY2goYXZnLmFjY3VyYWN5ID09bWF4KGF2Zy5hY2N1cmFjeSkpDQoNCiMjIyB2aXN1YWwgcmVwcmVzZW50YXRpb24NCnRpY2subGFiZWwgPSBhcy5jaGFyYWN0ZXIocm91bmQoY3V0LjBmZi5wcm9iLDIpKQ0KcGxvdCgxOjIwLCBhdmcuYWNjdXJhY3ksIHR5cGUgPSAiYiIsDQogICAgIHhsaW09YygxLDIwKSwgDQogICAgIHlsaW09YygwLjUsMSksIA0KICAgICBheGVzID0gRkFMU0UsDQogICAgIHhsYWIgPSAiQ3V0LW9mZiBQcm9iYWJpbGl0eSIsDQogICAgIHlsYWIgPSAiQWNjdXJhY3kiLA0KICAgICBtYWluID0gIjUtZm9sZCBDViBwZXJmb3JtYW5jZSINCiAgICAgKQ0KYXhpcygxLCBhdD0xOjIwLCBsYWJlbCA9IHRpY2subGFiZWwsIGxhcyA9IDIpDQpheGlzKDIpDQpzZWdtZW50cyhtYXguaWQsIDAuNSwgbWF4LmlkLCBhdmcuYWNjdXJhY3lbbWF4LmlkXSwgY29sID0gInJlZCIpDQp0ZXh0KG1heC5pZCwgYXZnLmFjY3VyYWN5W21heC5pZF0rMC4wMywgYXMuY2hhcmFjdGVyKHJvdW5kKGF2Zy5hY2N1cmFjeVttYXguaWRdLDQpKSwgY29sID0gInJlZCIsIGNleCA9IDAuOCkNCmBgYA0KDQpUaGUgYWJvdmUgZmlndXJlIGluZGljYXRlcyB0aGF0IHRoZSBvcHRpbWFsIGN1dC1vZmYgcHJvYmFiaWxpdHkgdGhhdCB5aWVsZHMgdGhlIGJlc3QgYWNjdXJhY3kgaXMgMC41Mi4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQp0ZXN0Lm1vZGVsID0gZ2xtKHkuc3Vic2NyaWJlIH4gYWdlICsgZGF5ICsgZ3JwLmpvYiArIG1hcml0YWwgKyBlZHVjYXRpb24gKyBob3VzaW5nICsgbG9hbiArIGNvbnRhY3QgKyBncnAubW9udGggKyBncnAuZHVyYXRpb24gKyBncnAuY2FtcGFpZ24gKyBncnAucGRheXMgKyBncnAucHJldmlvdXMsIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSBsb2dpdCksIGRhdGEgPSB0cmFpbmluZykNCm5ld0JhbmtpbmdUZXN0aW5nRGF0YSA9IGRhdGEuZnJhbWUoYWdlPSB0ZXN0aW5nJGFnZSwgZGF5PSB0ZXN0aW5nJGRheSwgZ3JwLmpvYj0gdGVzdGluZyRncnAuam9iLCBtYXJpdGFsPSB0ZXN0aW5nJG1hcml0YWwsIGVkdWNhdGlvbj0gdGVzdGluZyRlZHVjYXRpb24sIGhvdXNpbmc9IHRlc3RpbmckaG91c2luZywgbG9hbj0gdGVzdGluZyRsb2FuLCBjb250YWN0PSB0ZXN0aW5nJGNvbnRhY3QsIGdycC5tb250aD0gdGVzdGluZyRncnAubW9udGgsIGdycC5kdXJhdGlvbj0gdGVzdGluZyRncnAuZHVyYXRpb24sIGdycC5jYW1wYWlnbj0gdGVzdGluZyRncnAuY2FtcGFpZ24sIGdycC5wZGF5cz0gdGVzdGluZyRncnAucGRheXMsIGdycC5wcmV2aW91cz0gdGVzdGluZyRncnAucHJldmlvdXMpDQoNCnByZWQucHJvYi50ZXN0ID0gcHJlZGljdC5nbG0odGVzdC5tb2RlbCwgbmV3QmFua2luZ1Rlc3RpbmdEYXRhLCB0eXBlID0gInJlc3BvbnNlIikNCg0KIyMgQXNzZXNzaW5nIE1vZGVsIEFjY3VyYWN5DQp0ZXN0aW5nJHRlc3Quc3Vic2NyaWJlID0gYXMubnVtZXJpYyhwcmVkLnByb2IudGVzdCA+IDAuMjIpDQphMTEgPSBzdW0odGVzdGluZyR0ZXN0LnN1YnNjcmliZSA9PSB0ZXN0aW5nJHkuc3Vic2NyaWJlKQ0KdGVzdC5hY2N1cmFjeSA9IGExMS9sZW5ndGgocHJlZC5wcm9iLnRlc3QpDQprYWJsZShhcy5kYXRhLmZyYW1lKHRlc3QuYWNjdXJhY3kpLCBhbGlnbj0nYycpDQpgYGANCkhlcmUgaW4gb3VyIGFjY3VyYWN5IHRlc3Qgd2UgZmluZCB0aGF0IGl0IGlzIGFjY3VyYXRlIDg0LjElIG9mIHRoZSB0aW1lLiBUaGlzIGluZGNhdGVzIHRoZXJlIGlzIG5vIHVuZGVyLWZpdHRpbmcgZm9yIG91ciBtb2RlbC4NCg0KIyMgTG9jYWwgYW5kIEdsb2JhbCBST0MgTWV0cmljcw0KDQpVc2luZyB0aGUgb3B0aW1hbCBjdXQtb2ZmIHByb2JhYmlsaXR5IG9mIDAuNTcsIHdoaWNoIHdhcyBmb3VuZCBhYm92ZSwgd2Ugd2lsbCBub3cgcmVwb3J0IHRoZSBsb2NhbCBtZWFzdXJlcyB1c2luZyBvdXIgdGVzdGluZyBkYXRhLiBUaGlzIGluY2x1ZGVzIHNwZWNpZmljaXR5IGFuZCBzZW5zaXRpdml0eSBiYXNlZCBvbiBlYWNoIG9mIHRoZXNlIGN1dC1vZmZzIGZvciB0aGUgMjAgc3ViLWludGVydmFscy4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIExvb2tpbmcgYXQgc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5IHBlcmZvcm1hbmNlIG1lYXN1cmVtZW50cw0KdGVzdGluZyR0ZXN0LnN1YnNjcmliZSA9IGFzLm51bWVyaWMocHJlZC5wcm9iLnRlc3QgPiAwLjUyKQ0KIyMjIGNvbXBvbmVudHMgZm9yIGRlZmluaW5nIHZhcmlvdXMgbWVhc3VyZXMNCnAwLmEwID0gc3VtKHRlc3RpbmckdGVzdC5zdWJzY3JpYmUgPT0wICYgdGVzdGluZyR5LnN1YnNjcmliZSA9PTApDQpwMC5hMSA9IHN1bSh0ZXN0aW5nJHRlc3Quc3Vic2NyaWJlID09MCAmIHRlc3RpbmckeS5zdWJzY3JpYmUgPT0xKQ0KcDEuYTAgPSBzdW0odGVzdGluZyR0ZXN0LnN1YnNjcmliZSA9PTEgJiB0ZXN0aW5nJHkuc3Vic2NyaWJlID09MCkNCnAxLmExID0gc3VtKHRlc3RpbmckdGVzdC5zdWJzY3JpYmUgPT0xICYgdGVzdGluZyR5LnN1YnNjcmliZSA9PTEpDQojIyMNCnNlbnNpdGl2aXR5ID0gcDEuYTEgLyAocDEuYTEgKyBwMC5hMSkNCnNwZWNpZmljaXR5ID0gcDAuYTAgLyAocDAuYTAgKyBwMS5hMCkNCiMjIw0KcHJlY2lzaW9uID0gcDEuYTEgLyAocDEuYTEgKyBwMS5hMCkNCnJlY2FsbCA9IHNlbnNpdGl2aXR5DQpGMSA9IDIqcHJlY2lzaW9uKnJlY2FsbC8ocHJlY2lzaW9uICsgcmVjYWxsKQ0KbWV0cmljLmxpc3QgPSBjYmluZChzZW5zaXRpdml0eSA9IHNlbnNpdGl2aXR5LCANCiAgICAgICAgICAgICAgICAgICAgc3BlY2lmaWNpdHkgPSBzcGVjaWZpY2l0eSwgDQogICAgICAgICAgICAgICAgICAgIHByZWNpc2lvbiA9IHByZWNpc2lvbiwNCiAgICAgICAgICAgICAgICAgICAgcmVjYWxsID0gcmVjYWxsLA0KICAgICAgICAgICAgICAgICAgICBGMSA9IEYxKQ0Ka2FibGUoYXMuZGF0YS5mcmFtZShtZXRyaWMubGlzdCksIGFsaWduPSdjJywgY2FwdGlvbiA9ICJMb2NhbCBwZXJmb3JtYW5jZSBtZXRyaWNzIikNCmBgYA0KDQpUaGUgc2Vuc2l0aXZpdHkgaW5kaWNhdGVzIHRoZSBwcm9iYWJpbGl0eSBvZiB0aG9zZSBjbGllbnRzIHdobyBhcmUgc2FpZCB0byBoYXZlIHN1YnNjcmliZWQgYSB0ZXJtIGRlcG9zaXQgYXQgdGhlIGJhbmtpbmcgaW5zdGl0dXRpb24gb3V0IG9mIHRob3NlIHdobyBhY3R1YWxseSBkaWQgaXMgYWJvdXQgOC0xMiUuIFRoZSBzcGVjaWZpY2l0eSBpbmRpY2F0ZXMgdGhlIHByb2JhYmlsaXR5IG9mIHRob3NlIGNsaWVudHMgd2hvIGFyZSBzYWlkIHRvIGhhdmUgbm90IHN1YnNjcmliZWQgYSB0ZXJtIGRlcG9zaXQgYXQgdGhlIGJhbmtpbmcgaW5zdGl0dXRpb24gb3V0IG9mIHRob3NlIHdobyBhY3R1YWxseSBkaWQgbm90IGlzIGFib3V0IDk3LjMlLiANCg0KIyMjIFJPQyBHbG9iYWwgTWVhc3VyZSBBbmFseXNpcw0KDQpGb3IgdGhlIGxhc3QgcGFydCBvZiB0aGlzIHNlY3Rpb24sIGEgUk9DIChyZWNlaXZlciBvcGVyYXRpbmcgY2hhcmFjdGVyaXN0aWMpIGN1cnZlIHdpbGwgYmUgcGxvdHRlZCBieSBzZWxlY3RpbmcgYSBzZXF1ZW5jZSBvZiBkZWNpc2lvbiB0aHJlc2hvbGRzIGFuZCBjYWxjdWxhdGluZyBjb3JyZXNwb25kaW5nIHNlbnNpdGl2aXR5IGFuZCBzcGVjaWZpY2l0eS4gDQoNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQ0KIyBDcmVhdGluZyBST0MgY3VydmVzIGZvciBzZW5zaXRpdml0eSBhbmQgKDEtc3BlY2lmaWNpdHkpIGZvciBhbGwgMyBtb2RlbHMNCiMjIEZ1bGwgTW9kZWwNCmN1dC5vZmYuc2VxID0gc2VxKDAsIDEsIGxlbmd0aCA9IDEwMCkNCnNlbnNpdGl2aXR5LnZlYy5mdWxsID0gTlVMTA0Kc3BlY2lmaWNpdHkudmVjLmZ1bGwgPSBOVUxMDQojIyMgDQp0cmFpbmluZy5tb2RlbC5mdWxsID0gZ2xtKHkuc3Vic2NyaWJlIH4gYWdlICsgZGF5ICsgZ3JwLmpvYiArIG1hcml0YWwgKyBlZHVjYXRpb24gKyBob3VzaW5nICsgbG9hbiArIGNvbnRhY3QgKyBncnAubW9udGggKyBncnAuZHVyYXRpb24gKyBncnAuY2FtcGFpZ24gKyBncnAucGRheXMgKyBncnAucHJldmlvdXMsIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSBsb2dpdCksIGRhdGEgPSB0cmFpbmluZykNCm5ld0JhbmtUcmFpbmluZ0RhdGEuZnVsbCA9IGRhdGEuZnJhbWUoYWdlPSB0cmFpbmluZyRhZ2UsIGRheT0gdHJhaW5pbmckZGF5LCBncnAuam9iPSB0cmFpbmluZyRncnAuam9iLCBtYXJpdGFsPSB0cmFpbmluZyRtYXJpdGFsLCBlZHVjYXRpb249IHRyYWluaW5nJGVkdWNhdGlvbiwgaG91c2luZz0gdHJhaW5pbmckaG91c2luZywgbG9hbj0gdHJhaW5pbmckbG9hbiwgY29udGFjdD0gdHJhaW5pbmckY29udGFjdCwgZ3JwLm1vbnRoPSB0cmFpbmluZyRncnAubW9udGgsIGdycC5kdXJhdGlvbj0gdHJhaW5pbmckZ3JwLmR1cmF0aW9uLCBncnAuY2FtcGFpZ249IHRyYWluaW5nJGdycC5jYW1wYWlnbiwgZ3JwLnBkYXlzPSB0cmFpbmluZyRncnAucGRheXMsIGdycC5wcmV2aW91cz0gdHJhaW5pbmckZ3JwLnByZXZpb3VzKQ0KcHJlZC5wcm9iLnRyYWluLmZ1bGwgPSBwcmVkaWN0LmdsbSh0cmFpbmluZy5tb2RlbC5mdWxsLCBuZXdCYW5rVHJhaW5pbmdEYXRhLmZ1bGwsIHR5cGUgPSAicmVzcG9uc2UiKQ0KZm9yIChpIGluIDE6MTAwKXsNCiAgdHJhaW5pbmckdHJhaW4uc3Vic2NyaWJlID0gYXMubnVtZXJpYyhwcmVkLnByb2IudHJhaW4uZnVsbCA+IGN1dC5vZmYuc2VxW2ldKQ0KIyMjIGNvbXBvbmVudHMgZm9yIGRlZmluaW5nIHZhcmlvdXMgbWVhc3VyZXMNClROID0gc3VtKHRyYWluaW5nJHRyYWluLnN1YnNjcmliZSA9PSAwICYgdHJhaW5pbmckeS5zdWJzY3JpYmUgPT0gMCkNCkZOID0gc3VtKHRyYWluaW5nJHRyYWluLnN1YnNjcmliZSA9PSAwICYgdHJhaW5pbmckeS5zdWJzY3JpYmUgPT0gMSkNCkZQID0gc3VtKHRyYWluaW5nJHRyYWluLnN1YnNjcmliZSA9PSAxICYgdHJhaW5pbmckeS5zdWJzY3JpYmUgPT0gMCkNClRQID0gc3VtKHRyYWluaW5nJHRyYWluLnN1YnNjcmliZSA9PSAxICYgdHJhaW5pbmckeS5zdWJzY3JpYmUgPT0gMSkNCiMjIw0Kc2Vuc2l0aXZpdHkudmVjLmZ1bGxbaV0gPSBUUCAvIChUUCArIEZOKQ0Kc3BlY2lmaWNpdHkudmVjLmZ1bGxbaV0gPSBUTiAvIChUTiArIEZQKQ0KfQ0Kb25lLm1pbnVzLnNwZWMuZnVsbCA9IDEgLSBzcGVjaWZpY2l0eS52ZWMuZnVsbA0Kc2Vucy52ZWMuZnVsbCA9IHNlbnNpdGl2aXR5LnZlYy5mdWxsDQojIyBBIGJldHRlciBhcHByb3ggb2YgUk9DLCBuZWVkIGxpYnJhcnkge3BST0N9DQogIHByZWRpY3Rpb24uZnVsbCA9IHByZWQucHJvYi50cmFpbi5mdWxsDQogIGNhdGVnb3J5LmZ1bGwgPSB0cmFpbmluZyR5LnN1YnNjcmliZSA9PSAxDQogIFJPQ29iai5mdWxsIDwtIHJvYyhjYXRlZ29yeS5mdWxsLCBwcmVkaWN0aW9uLmZ1bGwpDQogIEFVQ2Z1bGwgPSByb3VuZChhdWMoUk9Db2JqLmZ1bGwpLDQpDQogIA0KIyMgUmVkdWNlZCBNb2RlbA0KY3V0Lm9mZi5zZXEgPSBzZXEoMCwgMSwgbGVuZ3RoID0gMTAwKQ0Kc2Vuc2l0aXZpdHkudmVjLnJlZHVjZWQgPSBOVUxMDQpzcGVjaWZpY2l0eS52ZWMucmVkdWNlZCA9IE5VTEwNCiMjIyANCnRyYWluaW5nLm1vZGVsLnJlZHVjZWQgPSBnbG0oeS5zdWJzY3JpYmUgfiBkYXkgKyBncnAuam9iICsgbWFyaXRhbCArIGhvdXNpbmcgKyBsb2FuICsgY29udGFjdCArIGdycC5kdXJhdGlvbiArIGdycC5jYW1wYWlnbiArIGdycC5wcmV2aW91cywgZmFtaWx5ID0gYmlub21pYWwobGluayA9IGxvZ2l0KSwgZGF0YSA9IHRyYWluaW5nKQ0KbmV3QmFua1RyYWluaW5nRGF0YS5yZWR1Y2VkID0gZGF0YS5mcmFtZShkYXk9IHRyYWluaW5nJGRheSwgZ3JwLmpvYj0gdHJhaW5pbmckZ3JwLmpvYiwgbWFyaXRhbD0gdHJhaW5pbmckbWFyaXRhbCwgaG91c2luZz0gdHJhaW5pbmckaG91c2luZywgbG9hbj0gdHJhaW5pbmckbG9hbiwgY29udGFjdD0gdHJhaW5pbmckY29udGFjdCwgZ3JwLmR1cmF0aW9uPSB0cmFpbmluZyRncnAuZHVyYXRpb24sIGdycC5jYW1wYWlnbj0gdHJhaW5pbmckZ3JwLmNhbXBhaWduLCBncnAucHJldmlvdXM9IHRyYWluaW5nJGdycC5wcmV2aW91cykNCnByZWQucHJvYi50cmFpbi5yZWR1Y2VkID0gcHJlZGljdC5nbG0odHJhaW5pbmcubW9kZWwucmVkdWNlZCwgbmV3QmFua1RyYWluaW5nRGF0YS5yZWR1Y2VkLCB0eXBlID0gInJlc3BvbnNlIikNCmZvciAoaSBpbiAxOjEwMCl7DQogIHRyYWluaW5nJHRyYWluLnN1YnNjcmliZSA9IGFzLm51bWVyaWMocHJlZC5wcm9iLnRyYWluLnJlZHVjZWQgPiBjdXQub2ZmLnNlcVtpXSkNCiMjIyBjb21wb25lbnRzIGZvciBkZWZpbmluZyB2YXJpb3VzIG1lYXN1cmVzDQpUTi5yZWR1Y2VkID0gc3VtKHRyYWluaW5nJHRyYWluLnN1YnNjcmliZSA9PSAwICYgdHJhaW5pbmckeS5zdWJzY3JpYmUgPT0gMCkNCkZOLnJlZHVjZWQgPSBzdW0odHJhaW5pbmckdHJhaW4uc3Vic2NyaWJlID09IDAgJiB0cmFpbmluZyR5LnN1YnNjcmliZSA9PSAxKQ0KRlAucmVkdWNlZCA9IHN1bSh0cmFpbmluZyR0cmFpbi5zdWJzY3JpYmUgPT0gMSAmIHRyYWluaW5nJHkuc3Vic2NyaWJlID09IDApDQpUUC5yZWR1Y2VkID0gc3VtKHRyYWluaW5nJHRyYWluLnN1YnNjcmliZSA9PSAxICYgdHJhaW5pbmckeS5zdWJzY3JpYmUgPT0gMSkNCiMjIw0Kc2Vuc2l0aXZpdHkudmVjLnJlZHVjZWRbaV0gPSBUUC5yZWR1Y2VkIC8gKFRQLnJlZHVjZWQgKyBGTi5yZWR1Y2VkKQ0Kc3BlY2lmaWNpdHkudmVjLnJlZHVjZWRbaV0gPSBUTi5yZWR1Y2VkIC8gKFROLnJlZHVjZWQgKyBGUC5yZWR1Y2VkKQ0KfQ0Kb25lLm1pbnVzLnNwZWMucmVkdWNlZCA9IDEgLSBzcGVjaWZpY2l0eS52ZWMucmVkdWNlZA0Kc2Vucy52ZWMucmVkdWNlZCA9IHNlbnNpdGl2aXR5LnZlYy5yZWR1Y2VkDQojIyBBIGJldHRlciBhcHByb3ggb2YgUk9DLCBuZWVkIGxpYnJhcnkge3BST0N9DQogIHByZWRpY3Rpb24ucmVkdWNlZCA9IHByZWQucHJvYi50cmFpbi5yZWR1Y2VkDQogIGNhdGVnb3J5LnJlZHVjZWQgPSB0cmFpbmluZyR5LnN1YnNjcmliZSA9PSAxDQogIFJPQ29iai5yZWR1Y2VkIDwtIHJvYyhjYXRlZ29yeS5yZWR1Y2VkLCBwcmVkaWN0aW9uLnJlZHVjZWQpDQogIEFVQ3JlZHVjZWQgPSByb3VuZChhdWMoUk9Db2JqLnJlZHVjZWQpLDQpDQogIA0KIyBGaW5hbCBNb2RlbA0KY3V0Lm9mZi5zZXEgPSBzZXEoMCwgMSwgbGVuZ3RoID0gMTAwKQ0Kc2Vuc2l0aXZpdHkudmVjLmZpbmFsID0gTlVMTA0Kc3BlY2lmaWNpdHkudmVjLmZpbmFsID0gTlVMTA0KIyMjIA0KdHJhaW5pbmcubW9kZWwuZmluYWwgPSBnbG0oeS5zdWJzY3JpYmUgfiBhZ2UgKyBkYXkgKyBncnAuam9iICsgbWFyaXRhbCArIGVkdWNhdGlvbiArIGhvdXNpbmcgKyBsb2FuICsgY29udGFjdCArIGdycC5tb250aCArIGdycC5kdXJhdGlvbiArIGdycC5jYW1wYWlnbiArIGdycC5wZGF5cyArIGdycC5wcmV2aW91cywgZmFtaWx5ID0gYmlub21pYWwobGluayA9IGxvZ2l0KSwgZGF0YSA9IHRyYWluaW5nKQ0KbmV3QmFua1RyYWluaW5nRGF0YS5maW5hbCA9IGRhdGEuZnJhbWUoYWdlPSB0cmFpbmluZyRhZ2UsIGRheT0gdHJhaW5pbmckZGF5LCBncnAuam9iPSB0cmFpbmluZyRncnAuam9iLCBtYXJpdGFsPSB0cmFpbmluZyRtYXJpdGFsLCBlZHVjYXRpb249IHRyYWluaW5nJGVkdWNhdGlvbiwgaG91c2luZz0gdHJhaW5pbmckaG91c2luZywgbG9hbj0gdHJhaW5pbmckbG9hbiwgY29udGFjdD0gdHJhaW5pbmckY29udGFjdCwgZ3JwLm1vbnRoPSB0cmFpbmluZyRncnAubW9udGgsIGdycC5kdXJhdGlvbj0gdHJhaW5pbmckZ3JwLmR1cmF0aW9uLCBncnAuY2FtcGFpZ249IHRyYWluaW5nJGdycC5jYW1wYWlnbiwgZ3JwLnBkYXlzPSB0cmFpbmluZyRncnAucGRheXMsIGdycC5wcmV2aW91cz0gdHJhaW5pbmckZ3JwLnByZXZpb3VzKQ0KcHJlZC5wcm9iLnRyYWluLmZpbmFsID0gcHJlZGljdC5nbG0odHJhaW5pbmcubW9kZWwuZmluYWwsIG5ld0JhbmtUcmFpbmluZ0RhdGEuZmluYWwsIHR5cGUgPSAicmVzcG9uc2UiKQ0KZm9yIChpIGluIDE6MTAwKXsNCiAgdHJhaW5pbmckdHJhaW4uc3Vic2NyaWJlID0gYXMubnVtZXJpYyhwcmVkLnByb2IudHJhaW4uZmluYWwgPiBjdXQub2ZmLnNlcVtpXSkNCiMjIyBjb21wb25lbnRzIGZvciBkZWZpbmluZyB2YXJpb3VzIG1lYXN1cmVzDQpUTi5maW5hbCA9IHN1bSh0cmFpbmluZyR0cmFpbi5zdWJzY3JpYmUgPT0gMCAmIHRyYWluaW5nJHkuc3Vic2NyaWJlID09IDApDQpGTi5maW5hbCA9IHN1bSh0cmFpbmluZyR0cmFpbi5zdWJzY3JpYmUgPT0gMCAmIHRyYWluaW5nJHkuc3Vic2NyaWJlID09IDEpDQpGUC5maW5hbCA9IHN1bSh0cmFpbmluZyR0cmFpbi5zdWJzY3JpYmUgPT0gMSAmIHRyYWluaW5nJHkuc3Vic2NyaWJlID09IDApDQpUUC5maW5hbCA9IHN1bSh0cmFpbmluZyR0cmFpbi5zdWJzY3JpYmUgPT0gMSAmIHRyYWluaW5nJHkuc3Vic2NyaWJlID09IDEpDQojIyMNCnNlbnNpdGl2aXR5LnZlYy5maW5hbFtpXSA9IFRQLmZpbmFsIC8gKFRQLmZpbmFsICsgRk4uZmluYWwpDQpzcGVjaWZpY2l0eS52ZWMuZmluYWxbaV0gPSBUTi5maW5hbCAvIChUTi5maW5hbCArIEZQLmZpbmFsKQ0KfQ0Kb25lLm1pbnVzLnNwZWMuZmluYWwgPSAxIC0gc3BlY2lmaWNpdHkudmVjLmZpbmFsDQpzZW5zLnZlYy5maW5hbCA9IHNlbnNpdGl2aXR5LnZlYy5maW5hbA0KIyMgQSBiZXR0ZXIgYXBwcm94IG9mIFJPQywgbmVlZCBsaWJyYXJ5IHtwUk9DfQ0KICBwcmVkaWN0aW9uLmZpbmFsID0gcHJlZC5wcm9iLnRyYWluLmZpbmFsDQogIGNhdGVnb3J5LmZpbmFsID0gdHJhaW5pbmckeS5zdWJzY3JpYmUgPT0gMQ0KICBST0NvYmouZmluYWwgPC0gcm9jKGNhdGVnb3J5LmZpbmFsLCBwcmVkaWN0aW9uLmZpbmFsKQ0KICBBVUNmaW5hbCA9IHJvdW5kKGF1YyhST0NvYmouZmluYWwpLDQpDQogIA0KIyMgVmlzdWFsIFJlcHJlc2VudGF0aW9uIG9mIGFsbCAzIFJPQ3MNCnBhcihwdHkgPSAicyIpICAgIyBtYWtlIGEgc3F1YXJlIGZpZ3VyZQ0KcGxvdChvbmUubWludXMuc3BlYy5mdWxsLCBzZW5zLnZlYy5mdWxsLCB0eXBlID0gImwiLCB4bGltID0gYygwLDEpLCB4bGFiID0iMSAtIFNwZWNpZmljaXR5IiwgeWxhYiA9ICJTZW5zaXRpdml0eSIsIG1haW4gPSAiUk9DIEN1cnZlcyBvZiBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVscyIsIGx3ZCA9IDIsIGNvbCA9ICJibHVlIikNCmxpbmVzKG9uZS5taW51cy5zcGVjLnJlZHVjZWQsIHNlbnMudmVjLnJlZHVjZWQsIGNvbCA9ICJncmVlbiIpDQpsaW5lcyhvbmUubWludXMuc3BlYy5maW5hbCwgc2Vucy52ZWMuZmluYWwsIGNvbCA9ICJvcmFuZ2UiKQ0Kc2VnbWVudHMoMCwwLDEsMSwgY29sID0gInJlZCIsIGx0eSA9IDIsIGx3ZCA9IDIpDQoNCiNBVUNmdWxsID0gcm91bmQoc3VtKHNlbnMudmVjLmZ1bGwqKG9uZS5taW51cy5zcGVjLmZ1bGxbLTEwMV0tb25lLm1pbnVzLnNwZWMuZnVsbFstMV0pKSw0KQ0KI0FVQ3JlZHVjZWQgPSByb3VuZChzdW0oc2Vucy52ZWMucmVkdWNlZCoob25lLm1pbnVzLnNwZWMucmVkdWNlZFstMTAxXS1vbmUubWludXMuc3BlYy5yZWR1Y2VkWy0xXSkpLDQpDQojQVVDZmluYWwgPSByb3VuZChzdW0oc2Vucy52ZWMuZmluYWwqKG9uZS5taW51cy5zcGVjLmZpbmFsWy0xMDFdLW9uZS5taW51cy5zcGVjLmZpbmFsWy0xXSkpLDQpDQp0ZXh0KDAuOCwgMC41LCBwYXN0ZSgiQVVDID0gIiwgQVVDZnVsbCksIGNvbCA9ICJibHVlIikNCnRleHQoMC44LCAwLjQsIHBhc3RlKCJBVUMgPSAiLCBBVUNyZWR1Y2VkKSwgY29sID0gImdyZWVuIikNCnRleHQoMC44LCAwLjMsIHBhc3RlKCJBVUMgPSAiLCBBVUNmaW5hbCksIGNvbCA9ICJvcmFuZ2UiKQ0KbGVnZW5kKCJib3R0b21yaWdodCIsIGMoIlJPQyBvZiB0aGUgRnVsbCBNb2RlbCIsICJST0Mgb2YgdGhlIFJlZHVjZWQgTW9kZWwiLCAiUk9DIG9mIHRoZSBGaW5hbCBNb2RlbCIpLCBsdHk9YygxLDIpLCBjb2wgPSBjKCJibHVlIiwgImdyZWVuIiwgIm9yYW5nZSIpLCBidHkgPSAibiIsIGNleCA9IDAuOCkNCmBgYA0KDQpUaGUgYXJlYSB1bmRlciB0aGUgY3VydmUgKEFVQykgZm9yIHRoZSByZWR1Y2VkIG1vZGVsIGFuZCB0aGUgUk9DIGN1cnZlIGlzIGxlc3MgdGhhbiB0aGUgb3RoZXIgdHdvIGdyYXBocy4gSGlnaGVyIEFVQyBpbmRpY2F0ZXMgdGhlIG1vZGVsIGZvciB0aGF0IGN1cnZlIGlzIGJldHRlci4gVGhlcmVmb3JlLCB0aGUgcmVkdWNlZCBtb2RlbCBpcyBub3QgdGhlIGJlc3QgbW9kZWwgdG8gdXNlIGFuZCBpcyBubyBsb25nZXIgY29uc2lkZXJlZC4gQnV0LCBpdCBzaG91bGQgYmUgbm90ZWQgdGhhdCBpdCBpcyBub3QgZmFyIG9mZiB0aGUgb3RoZXIgMiBtb2RlbHMuDQoNCkxvb2tpbmcgYXQgdGhlIGluaXRpYWwgYW5kIGZpbmFsIG1vZGVscywgdGhleSBoYXZlIHRoZSBzYW1lIGN1cnZlIHNpbmNlIGJvdGggbW9kZWxzIGNvbnRhaW4gYWxsIGZlYXR1cmUgdmFyaWFibGVzIHVzZWQgaW4gdGhlIGluaXRpYWwgbW9kZWwuIE91dCBvZiB0aGVzZSBvdGhlciB0d28gbW9kZWxzLCB0aGUgZmluYWwgbW9kZWwgd29ya3MgYmV0dGVyIGNvbXBhcmVkIHRvIHRoZSBpbml0aWFsIG1vZGVsLiBJdCBpcyB0aGUgcGFyc2ltb25pb3VzIG1vZGVsLiBJdCBoYXMgYmVlbiBwcm92ZW4gdG8gYmUgYWNjdXJhdGUgaW4gbW9kZWxpbmcgcGVyZm9ybWFuY2UsIGhhcyBoaWdoIHNwZWNpZmljaXR5LCBhbmQgaXRzIFJPQyBjdXJ2ZSBpcyByZW1haW5pbmcgYXdheSBmcm9tIHRoZSA0NSBkZWdyZWVzIG1hcmsuIFBsdXMsIHRoZSBBVUMgaXMgZmFpcmx5IGhpZ2ggYXQgLjg1NDEsIGV2ZW4gdGhvdWdoIHRoZSBpbml0aWFsIG1vZGVsIGhhcyB0aGUgc2FtZSBzY29yZS4NCg0KIyBOZXVyYWwgTmV0d29yayBNb2RlbGluZw0KTm93IHdlICB3aWxsIGNyZWF0ZSBhbm90aGVyIG1vZGVsIHRvIHByZWRpY3QgdXNpbmcgbmV1cmFsIG5ldHdvcmtzLg0KDQpUcmFpbmluZyBhbmQgdGVzdGluZyBkYXRhIHNldHMgYW5kIGNhbmlkYXRlIG1vZGVscyB3aWxsIGJlIG1hZGUgb25jZSBhZ2FpbiBmb3IgdGhlIG5ldXJhbCBuZXR3b3JrIG1vZGVsLiBGcm9tIHRoZW0sIGEgZmluYWwgbW9kZWwgd2lsbCBiZSBjb25zdHJ1Y3RlZCBhbmQgcGxvdHRlZCB0byBzaG93IGJhY2twcm9wYWdhdGlvbiBvZiB0aGUgbmV1cmFsIG5ldHdvcmsgDQoNCkNyb3NzLXZhbGlkYXRpb24gd2lsbCBhbHNvIGJlIHVzZWQgYmUgdXNlZCBmb3IgZmluZGluZyBhbiBvcHRpbWFsIGN1dC1vZmYgcHJvYmFiaWxpdHkgZm9yIGFzc2Vzc2luZyBtb2RlbCBwZXJmb3JtYW5jZSBhbmQgYWNjdXJhY3kuIEZpYW5sbHksIGFuIFJPQyBjdXJ2ZSB3aWxsIGJlIG1hZGUgZm9yIHRoZSBtb2RlbCB0byBsb29rIGF0IHByZWRpY3RpdmUgcG93ZXIgYW5kIGNvbXBhcmUgaXQgdG8gdGhlIG90aGVyIG1vZGVsIGp1c3QgbGlrZSBsb2dpc3RpYyByZWdyZXNzaW9uLg0KDQojIyBDaGFuZ2luZyB2YXJpYmxlcyBiYWNrIHRvIG51bWVyaWMgZm9ybQ0KDQpVbmxpa2UgaW4gb3VyIGxvZ2lzdGljIHJlZ3Jlc3Npb24sIHRoZSBuZXVyYWxuZXQgbGlicmFyeSBpbiBSIG5lZWRzIGFsbCBmZWF0dXJlIHZhcmlhYmxlcyB0byBiZSBpbiBudW1lcmljIGZvcm0uIENhdGVnb3JpY2FsIHZhcmlhYmxlcyBiZWNvbWUgZW5jb2RlZCBpbnRvIGR1bW15IHZhcmlhYmxlcy4gV2Ugd2FudCB0byBiZSBhYmxlIHRvIGZpbmQgYWxsIG5hbWVzIGZvciB0aGUgZmVhdHVyZSB2YXJpYWJsZXMgYW5kIHdyaXRlIHRoZW0gaW4gdGhlIG5ldXJhbCBuZXR3b3JrIG1vZGVsIGZvcm11bGEuIFRoZSBkYXRhIHNldCBoZXJlIHdpbGwgbm90IGFueSBudW1lcmljYWwgbGFiZWxzIGZvciB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGFzIHdpdGggdGhlIHByZXZpb3VzIG1vZGVsLiBUaGUgY29kZSBpcyBiZWxvdzoNCg0KYGBge3J9DQojIEFzc2VtYmxpbmcgdGhlIGRpc2NyZXRpemVkIHZhcmlhYmxlcyBhbmQgb3RoZXIgdmFyaWFibGVzIHRvIG1ha2UgdGhlIG1vZGVsaW5nIGRhdGEgc2V0DQp2YXIubmFtZXMgPSBjKCJhZ2UiLCAiZGF5IiwgImdycC5qb2IiLCAibWFyaXRhbCIsICJlZHVjYXRpb24iLCAiaG91c2luZyIsICJsb2FuIiwgImNvbnRhY3QiLCAiZ3JwLm1vbnRoIiwgImdycC5kdXJhdGlvbiIsICJncnAuY2FtcGFpZ24iLCAiZ3JwLnBkYXlzIiwgImdycC5wcmV2aW91cyIsICJ5IikgDQpCYW5rTWFya2V0aW5nTmV1cmFsID0gQmFua01hcmtldGluZ1ssIHZhci5uYW1lc10NCmBgYA0KVGhlIG51bWVyaWNhbCB2YXJpYmxlcyBtdXN0IGJlIHJlc2NhbGVkIGFuZCB0aGUgY29kZSBpcyBiZWxvdzoNCmBgYHtyfQ0KI3JlLXNjYWxpbmcgdGhlIG51bWVyaWNhbCB2YXJpYWJsZXMNCkJhbmtNYXJrZXRpbmdOZXVyYWwkYWdlID0gKEJhbmtNYXJrZXRpbmdOZXVyYWwkYWdlLW1pbihCYW5rTWFya2V0aW5nTmV1cmFsJGFnZSkpLyhtYXgoQmFua01hcmtldGluZ05ldXJhbCRhZ2UpLW1pbihCYW5rTWFya2V0aW5nTmV1cmFsJGFnZSkpDQpCYW5rTWFya2V0aW5nTmV1cmFsJGRheSA9IChCYW5rTWFya2V0aW5nTmV1cmFsJGRheS1taW4oQmFua01hcmtldGluZ05ldXJhbCRkYXkpKS8obWF4KEJhbmtNYXJrZXRpbmdOZXVyYWwkZGF5KS1taW4oQmFua01hcmtldGluZ05ldXJhbCRkYXkpKQ0KYGBgDQpOb3cgd2UgbXVzdCBjcmVhdGUgZHVtbXkgdmFyaWFibGVzIGZvciBhbGwgY2F0ZWdvcmljYWwgZmVhdHVyZXM6DQpgYGB7cn0NCiMgQ3JlYXRpbmcgYSBtb2RlbC5tYXRyaXgoKSBhbmQgbG9va2luZyBhdCB0aGUgbmFtZXMgb2YgdGhlIHZhcmlhYmxlIGNvbHVtbnMNCkJhbmtpbmdNdHggPSBtb2RlbC5tYXRyaXgofiAuLCBkYXRhID0gQmFua01hcmtldGluZ05ldXJhbCkNCmNvbG5hbWVzKEJhbmtpbmdNdHgpDQoNCiNSZW5hbWluZyB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGUgY29sdW1ucyBpbiB0aGUgbW9kZWwgbWF0cml4DQpjb2xuYW1lcyhCYW5raW5nTXR4KVs0XSA8LSAiZ3JwLmpvYk5vdFdvcmtpbmciDQpjb2xuYW1lcyhCYW5raW5nTXR4KVs1XSA8LSAiZ3JwLmpvYldoaXRlQ29sbGFyIg0KY29sbmFtZXMoQmFua2luZ010eClbNl0gPC0gImdycC5qb2JXb3JrZXJzIg0KY29sbmFtZXMoQmFua2luZ010eClbN10gPC0gIm1hcml0YWxNYXJyaWVkIg0KY29sbmFtZXMoQmFua2luZ010eClbOF0gPC0gIm1hcml0YWxTaW5nbGUiDQpjb2xuYW1lcyhCYW5raW5nTXR4KVs5XSA8LSAiZWR1Y2F0aW9uMm5kIg0KY29sbmFtZXMoQmFua2luZ010eClbMTBdIDwtICJlZHVjYXRpb24zcmQiDQpjb2xuYW1lcyhCYW5raW5nTXR4KVsxMV0gPC0gImVkdWNhdGlvblVua25vd24iDQpjb2xuYW1lcyhCYW5raW5nTXR4KVsxMl0gPC0gImhvdXNpbmdZZXMiDQpjb2xuYW1lcyhCYW5raW5nTXR4KVsxM10gPC0gImxvYW5ZZXMiDQpjb2xuYW1lcyhCYW5raW5nTXR4KVsxNF0gPC0gImNvbnRhY3RUZWxlcGhvbmUiDQpjb2xuYW1lcyhCYW5raW5nTXR4KVsxNV0gPC0gImNvbnRhY3RVbmtub3duIg0KY29sbmFtZXMoQmFua2luZ010eClbMTZdIDwtICJncnAubW9udGhTcHJpbmciDQpjb2xuYW1lcyhCYW5raW5nTXR4KVsxN10gPC0gImdycC5tb250aFN1bW1lciINCmNvbG5hbWVzKEJhbmtpbmdNdHgpWzE4XSA8LSAiZ3JwLm1vbnRoV2ludGVyIg0KY29sbmFtZXMoQmFua2luZ010eClbMTldIDwtICJncnAuZHVyYXRpb24wVG8xODAiDQpjb2xuYW1lcyhCYW5raW5nTXR4KVsyMF0gPC0gImdycC5kdXJhdGlvbjMyMFBsdXMiDQpjb2xuYW1lcyhCYW5raW5nTXR4KVsyMV0gPC0gImdycC5jYW1wYWlnbjEiDQpjb2xuYW1lcyhCYW5raW5nTXR4KVsyMl0gPC0gImdycC5jYW1wYWlnbjRQbHVzIg0KY29sbmFtZXMoQmFua2luZ010eClbMjNdIDwtICJncnAucGRheXMyMDBQbHVzIg0KY29sbmFtZXMoQmFua2luZ010eClbMjRdIDwtICJncnAucGRheXNDTlBDIg0KY29sbmFtZXMoQmFua2luZ010eClbMjVdIDwtICJncnAucHJldmlvdXMwIg0KY29sbmFtZXMoQmFua2luZ010eClbMjZdIDwtICJncnAucHJldmlvdXM0UGx1cyINCmNvbG5hbWVzKEJhbmtpbmdNdHgpWzI3XSA8LSAieVllcyINCmBgYA0KIyMgQ3JlYXRlIENhbmRpZGF0ZSBNb2RlbA0KDQpIZXJlIHRoZSBuZXVyYWwgbmV0d29yayBtb2RlbCBpcyBkZWZpbmVkIHVzaW5nIHRoZSBjaGFuZ2VkIG5hbWVzIG9mIHRoZSB2YXJpYWJsZXMgaW4gdGhlIG1hdHJpeC4NCg0KYGBge3J9DQojIERlZmluaW5nIHRoZSBuZXVyYWwgbmV0d29yayBtb2RlbA0KY29sdW1uTmFtZXMgPSBjb2xuYW1lcyhCYW5raW5nTXR4KQ0KY29sdW1uTGlzdCA9IHBhc3RlKGNvbHVtbk5hbWVzWy1jKDEsbGVuZ3RoKGNvbHVtbk5hbWVzKSldLCBjb2xsYXBzZSA9ICIrIikNCmNvbHVtbkxpc3QgPSBwYXN0ZShjKGNvbHVtbk5hbWVzW2xlbmd0aChjb2x1bW5OYW1lcyldLCJ+Iixjb2x1bW5MaXN0KSwgY29sbGFwc2U9IiIpDQptb2RlbE5ldXJhbEZvcm11bGEgPSBmb3JtdWxhKGNvbHVtbkxpc3QpDQptb2RlbE5ldXJhbEZvcm11bGENCmBgYA0KTm93IHdlIHdpbGwgc3BsaXQgdGhlIGRhdGEgaW50byB0d28gZGF0YXNldHMuIDcwJSBpcyBmb3IgdHJhaW5pbmcgdGhlIG5ldXJhbCBuZXR3b3JrIG1vZGVsIGFuZCAzMCUgaXMgZm9yIHRlc3RpbmcuDQoNCmBgYHtyfQ0KIyBDcmVhdGluZyB0aGUgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YSBzZXRzIGZvciBjcmVhdGluZyB0aGUgbW9kZWwNCm4gPSBkaW0oQmFua2luZ010eClbMV0NCnRlc3RJRCA9IHNhbXBsZSgxOm4sIHJvdW5kKG4qMC43KSwgcmVwbGFjZSA9IEZBTFNFKQ0KdGVzdERhdGEgPSBCYW5raW5nTXR4W3Rlc3RJRCxdDQp0cmFpbkRhdGEgPSBCYW5raW5nTXR4Wy10ZXN0SUQsXQ0KYGBgDQoNCk5vdyB0aGF0IHdlIGhhdmUgYSB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhc2V0LCBhIHNpbmdsZS1sYXllciBuZXVyYWwgbmV0d29yayBtb2RlbCBpcyBjcmVhdGVkIGJlbG93Lg0KDQpgYGB7cn0NCiMgQ3JlYXRpbmcgdGhlIHNpbmdsZS1sYXllciBuZXVyYWwgbmV0d29yayBhbmQgbW9kZWwNCk5ldHdvcmtNb2RlbCA9IG5ldXJhbG5ldChtb2RlbE5ldXJhbEZvcm11bGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluRGF0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSAxLCAgICAgICAjIHNpbmdsZSBsYXllciBuZXVyYWwgbmV0d29yaw0KICAgICAgICAgICAgICAgICAgICAgICAgIHJlcCA9IDEsICAgICAgICAgIyBudW1iZXIgb2YgcmVwbGljYXRlcyBpbiB0cmFpbmluZyBuZXVyYWwgbmV0d29yaw0KICAgICAgICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZCA9IDAuMDEsICMgdGhyZXNob2xkIGZvciBwYXJ0aWFsIGRlcml2YXRpdmVzIGFzIHN0b3BwaW5nIGNyaXRlcmlhLg0KICAgICAgICAgICAgICAgICAgICAgICAgIGxlYXJuaW5ncmF0ZSA9IDAuMSwgICMgdXNlciBzZWxlY3RlZCByYXRlDQogICAgICAgICAgICAgICAgICAgICAgICAgYWxnb3JpdGhtID0gInJwcm9wKyINCiAgICAgICAgICAgICAgICAgICAgICAgICApDQprYWJsZShOZXR3b3JrTW9kZWwkcmVzdWx0Lm1hdHJpeCkNCmBgYA0KIyMjIFBlcmNlcHRyb24NCkluIG9yZGVyIHRvIGdldCBhIHNlbnNlIG9mIHdoYXQgaXMgZ29pbmcgb24sIGEgbWFwIG9mIHRoZSBzaW5nbGUtbGF5ZXIgbmV1cmFsIG5ldHdvcmsgaXMgc2hvd24gYmVsb3c6DQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9OCwgIGZpZy5jYXA9IlNpbmdsZS1sYXllciBiYWNrcHJvcGFnYXRpb24gTmV1cmFsIG5ldHdvcmsgbW9kZWwgZm9yIEJhbmtpbmcgSW5zdGl0dXRpb24gQ2xpZW50IFRlcm0gRGVwb3NpdCBTdWJzY3JpcHRpb24ifQ0KIyBQbG90IGZvciBuZXVyYWwgbmV0d29yayBtb2RlbA0KcGxvdChOZXR3b3JrTW9kZWwsIHJlcD0iYmVzdCIpDQoNCmxvZ2lNb2RlbCA9IGdsbShmYWN0b3IoeSkgfi4sIGZhbWlseSA9IGJpbm9taWFsLCBkYXRhID0gQmFua01hcmtldGluZ05ldXJhbCkNCnBhbmRlcihzdW1tYXJ5KGxvZ2lNb2RlbCkkY29lZmZpY2llbnRzKQ0KYGBgDQojIyBNb2RlbCBQZXJmb3JtYW5jZSBUZXN0aW5nDQoNCiMjIEN1dC1vZmYgUHJvYmFiaWxpdHkgU2VhcmNoIGFuZCBBY2N1cmFjeSBTY29yZQ0KDQpDcm9zcy12YWxpZGF0aW9uIGNhbiBiZSB1c2VkIGluIG9yZGVyIHRvIGZpbmQgdGhlIG9wdGltYWwgY3V0LW9mZiBzY29yZXMgYmVjYXVzZSBzaWdtb2lkIHBlcmNlcHRyb24gaXMgdXNlZCwgSHlwZXItcGFyYW1ldGVyZXMgd2lsbCBhbHNvIGJlIHVzZWQgdG8gYXNzZXNzIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgbmV1cmFsIG5ldHdvcmsuIFRoZSBjb2RlIGlzIHNob3duIGJlbG93Og0KDQpgYGB7ciBmaWcuYWxpZ249ICdjZW50ZXInfQ0KIyBMb29raW5nIGF0IG9wdGltYWwgY3V0LW9mZiBwcm9iYWJpbGl0aWVzIHdpdGggY3Jvc3MtdmFsaWRhdGlvbg0KbjAgPSBkaW0odHJhaW5EYXRhKVsxXS81DQpjdXQub2ZmLnNjb3JlID0gc2VxKDAsMSwgbGVuZ3RoID0gMjIpWy1jKDEsMjIpXSAgICMgY2FuZGlkYXRlIGN1dCBvZmYgcHJvYg0KcHJlZC5hY2N1cmFjeSA9IG1hdHJpeCgwLG5jb2w9MjAsIG5yb3c9NSwgYnlyb3cgPSBUKSAgIyBudWxsIHZlY3RvciBmb3Igc3RvcmluZyBwcmVkaWN0aW9uIGFjY3VyYWN5DQoNCiMjIDUtZm9sZCBDVg0KZm9yIChpIGluIDE6NSl7DQogIHZhbGlkLmlkID0gKChpLTEpKm4wICsgMSk6KGkqbjApDQogIHZhbGlkLmRhdGEgPSB0cmFpbkRhdGFbdmFsaWQuaWQsXQ0KICB0cmFpbi5kYXRhID0gdHJhaW5EYXRhWy12YWxpZC5pZCxdDQogICMjIyMNCiAgdHJhaW4ubW9kZWwgPSBuZXVyYWxuZXQobW9kZWxOZXVyYWxGb3JtdWxhLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbi5kYXRhLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IDEsICAgICAgICAgICMgc2luZ2xlIGxheWVyIE5ODQogICAgICAgICAgICAgICAgICAgICAgICAgcmVwID0gMSwgICAgICAgICAjIG51bWJlciBvZiByZXBsaWNhdGVzIGluIHRyYWluaW5nIE5ODQogICAgICAgICAgICAgICAgICAgICAgICAgdGhyZXNob2xkID0gMC4wMSwgICMgdGhyZXNob2xkIGZvciBwYXJ0aWFsIGRlcml2YXRpdmVzIGFzIHN0b3BwaW5nIGNyaXRlcmlhLg0KICAgICAgICAgICAgICAgICAgICAgICAgIGxlYXJuaW5ncmF0ZSA9IDAuMSwgICAjIHVzZXIgc2VsZWN0ZWQgcmF0ZQ0KICAgICAgICAgICAgICAgICAgICAgICAgIGFsZ29yaXRobSA9ICJycHJvcCsiDQogICAgICAgICAgICAgICAgICAgICAgICAgKQ0KICAgIHByZWQubm4uc2NvcmUgPSBwcmVkaWN0KE5ldHdvcmtNb2RlbCwgdmFsaWQuZGF0YSkNCiAgICBmb3IoaiBpbiAxOjIwKXsNCiAgICAjcHJlZC5zdGF0dXMgPSByZXAoMCxsZW5ndGgocHJlZC5ubi5zY29yZSkpDQogICAgcHJlZC5zdGF0dXMgPSBhcy5udW1lcmljKHByZWQubm4uc2NvcmUgPiBjdXQub2ZmLnNjb3JlW2pdKQ0KICAgIGExMSA9IHN1bShwcmVkLnN0YXR1cyA9PSB2YWxpZC5kYXRhWywxN10pDQogICAgcHJlZC5hY2N1cmFjeVtpLGpdID0gYTExL2xlbmd0aChwcmVkLm5uLnNjb3JlKQ0KICB9DQp9DQojIyMgIA0KYXZnLmFjY3VyYWN5ID0gYXBwbHkocHJlZC5hY2N1cmFjeSwgMiwgbWVhbikNCm1heC5pZCA9IHdoaWNoKGF2Zy5hY2N1cmFjeSA9PW1heChhdmcuYWNjdXJhY3kgKSkNCg0KIyMjIHZpc3VhbCByZXByZXNlbnRhdGlvbg0KdGljay5sYWJlbCA9IGFzLmNoYXJhY3Rlcihyb3VuZChjdXQub2ZmLnNjb3JlLDIpKQ0KcGxvdCgxOjIwLCBhdmcuYWNjdXJhY3ksIHR5cGUgPSAiYiIsDQogICAgIHhsaW09YygxLDIwKSwgDQogICAgIHlsaW09YygwLjUsMSksIA0KICAgICBheGVzID0gRkFMU0UsDQogICAgIHhsYWIgPSAiQ3V0LW9mZiBTY29yZSIsDQogICAgIHlsYWIgPSAiQWNjdXJhY3kiLA0KICAgICBtYWluID0gIjUtZm9sZCBDViBwZXJmb3JtYW5jZSINCiAgICAgKQ0KYXhpcygxLCBhdD0xOjIwLCBsYWJlbCA9IHRpY2subGFiZWwsIGxhcyA9IDIpDQpheGlzKDIpDQpzZWdtZW50cyhtYXguaWQsIDAuNSwgbWF4LmlkLCBhdmcuYWNjdXJhY3lbbWF4LmlkXSwgY29sID0gInJlZCIpDQp0ZXh0KG1heC5pZCwgYXZnLmFjY3VyYWN5W21heC5pZF0rMC4wMywgYXMuY2hhcmFjdGVyKHJvdW5kKGF2Zy5hY2N1cmFjeVttYXguaWRdLDQpKSwgY29sID0gInJlZCIsIGNleCA9IDAuOCkNCmBgYA0KVGhlIGFib3ZlIGZpZ3VyZSBpbmRpY2F0ZXMgdGhhdCB0aGUgb3B0aW1hbCBjdXQtb2ZmIHByb2JhYmlsaXR5IHRoYXQgeWllbGRzIHRoZSBiZXN0IGFjY3VyYWN5IGlzIGFyb3VuZCAwLjg2Lg0KDQpab29taW5nIGluIG9uIHRoZSBncmFwaGljIGFib3ZlLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIG9wdGltYWwgY3V0LW9mZiBpcyBhcm91bmQgLjg2Lg0KIyMgUmVzdWx0cyBhbmQgQ29uY2x1c2lvbg0KTm93IHRoYXQgd2UgaGF2ZSBjaG9zZWQgdGhlIGJlc3QgbW9kZWwgb3V0IG9mIHRoZSB0aHJlZSBjYW5kaWRhdGUgbW9kZWxzLCB3ZSBjYW4gdXNlIGl0IHRvIHByZWRpY3Qgd2hldGhlciBvciBub3QgYSBjbGllbnQgaGFzIHN1YnNjcmliZWQgYSB0ZXJtIGRlcG9zaXQuIFVzaW5nIG91dCBvcHRpbWFsIGN1dC1vZmYgb2YgLjU3DQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KIyBQcmVkaWN0aW5nIFJlc3BvbnNlIFZhbHVlIGZvciBCYW5raW5nIENsaWVudCBHaXZlbiBWYXJpYWJsZSBWYWx1ZXMgZm9yIHRoZSBGaW5hbCBNb2RlbA0KcGRhdGEgPSBkYXRhLmZyYW1lKGFnZT1jKDI1LDY0KSwNCiAgICAgICAgICAgICAgICAgICAgICAgZGF5ID0gYyg1LDUpLA0KICAgICAgICAgICAgICAgICAgICAgICBncnAuam9iID0gYygiMSIsIjIiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgbWFyaXRhbCA9IGMoIjAiLCIxIiksDQogICAgICAgICAgICAgICAgICAgICAgIGVkdWNhdGlvbiA9IGMoIjIiLCIwIiksDQogICAgICAgICAgICAgICAgICAgICAgIGhvdXNpbmcgPSBjKCIxIiwiMSIpLA0KICAgICAgICAgICAgICAgICAgICAgICBsb2FuID0gYygiMCIsIjEiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgY29udGFjdCA9IGMoIjAiLCIwIiksDQogICAgICAgICAgICAgICAgICAgICAgIGdycC5kdXJhdGlvbiA9IGMoIjAiLCIxIiksDQogICAgICAgICAgICAgICAgICAgICAgIGdycC5jYW1wYWlnbiA9IGMoIjEiLCIwIiksDQogICAgICAgICAgICAgICAgICAgICAgIGdycC5wZGF5cyA9IGMoIjEiLCIyIiksDQogICAgICAgICAgICAgICAgICAgICAgcG91dGNvbWU9YygiMCIsIjAiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgZ3JwLnByZXZpb3VzID0gYygiMSIsIjIiKSkNCiAgICAgICAgICANCg0KDQoNCnByZWQuc3VjY2Vzcy5wcm9iID0gcHJlZGljdChmaW5hbC5tb2RlbCwgbmV3ZGF0YSA9IHBkYXRhLCB0eXBlPSJyZXNwb25zZSIpDQoNCiMjIHRocmVzaG9sZCBwcm9iYWJpbGl0eQ0KY3V0Lm9mZi5wcm9iID0gMC41Nw0KcHJlZC5yZXNwb25zZSA9IGlmZWxzZShwcmVkLnN1Y2Nlc3MucHJvYiA+IGN1dC5vZmYucHJvYiwgMSwgMCkgICMgVGhpcyBwcmVkaWN0cyB0aGUgcmVzcG9uc2UNCnByZWQucmVzcG9uc2UNCiMgQWRkIHRoZSBuZXcgcHJlZGljdGVkIHJlc3BvbnNlIHRvIHBkYXRhDQoNCiNwZGF0YSRwcmVkLnJlc3BvbnNlIDwtIHByZWRpY3QoZmluYWwubW9kZWwsIG5ld2RhdGEgPSBwZGF0YSwgdHlwZSA9ICJyZXNwb25zZSIpDQpwZGF0YSRQcmVkLlJlc3BvbnNlID0gcHJlZC5yZXNwb25zZQ0Ka2FibGUocGRhdGEsIGNhcHRpb24gPSAiUHJlZGljdGVkIFZhbHVlIG9mIHJlc3BvbnNlIHZhcmlhYmxlIHdpdGggdGhlIGdpdmVuIGN1dC1vZmYgcHJvYmFiaWxpdHkiKQ0KYGBgDQoNCllvdSBjYW4gc2VlIHRoYXQgbmVpdGhlciBvZiB0aGUgdHdvIG9ic2VydmF0aW9ucyB3aWxsIGJlIHN1YnNjcmliaW5nLiBNb3JlIHRlc3RpbmcgY2FuIGJlIGRvbmUgd2l0aCB0aGUgdGVzdGluZyBkYXRhc2V0Lg0KDQojIyBUZXN0aW5nIE1vZGVsIEFjY3VyYWN5IGFuZCBQZXJmb3JtYW5jZQ0KDQpUaGlzIG9wdGltYWwgY3V0LW9mZiBwcm9iYWJpbGl0eSBhbmQgdGVzdGluZyBkYXRhIHdpbGwgbm93IGJlIHVzZWQgdG8gbG9vayBhdCB0aGUgYWNjdXJhY3kgYW5kIGNvbmZ1c2lvbiBtYXRyaXggb2YgdGhlIG1vZGVsLg0KDQpgYGB7cn0NCiNUZXN0aW5nIHJlc3VsdGluZyBvdXRwdXQgYW5kIGZpbmRpbmcgbW9kZWwgYWNjdXJhY3kgYW5kIGNvbmZ1c2lvbiBtYXRyaXgNCm5uLnJlc3VsdHMgPC0gcHJlZGljdChOZXR3b3JrTW9kZWwsIHRlc3REYXRhKQ0KcmVzdWx0cyA8LSBkYXRhLmZyYW1lKGFjdHVhbCA9IHRlc3REYXRhWywyN10sIHByZWRpY3Rpb24gPSBubi5yZXN1bHRzID4gLjg2KQ0KY29uZk1hdHJpeCA9IHRhYmxlKHJlc3VsdHMkcHJlZGljdGlvbiwgcmVzdWx0cyRhY3R1YWwpICAgICAgICAgICAgICAgIyBjb25mdXNpb24gbWF0cml4DQphY2N1cmFjeT1zdW0ocmVzdWx0cyRhY3R1YWwgPT0gcmVzdWx0cyRwcmVkaWN0aW9uKS9sZW5ndGgocmVzdWx0cyRwcmVkaWN0aW9uKQ0KbGlzdChjb25mdXNpb24ubWF0cml4ID0gY29uZk1hdHJpeCwgYWNjdXJhY3kgPSBhY2N1cmFjeSkgICAgICAgDQpgYGANClRoZSBhY2N1cmFjeSBzY29yZSB3ZSBzZWUgaXMgODguMSUuIFRoaXMgaW5kaWNhdGVzIHRoYXQgdGhlcmUgaXMgbm8gdW5kZXItZml0dGluZy4gVEhpcyByZXN1bHQgaXMgc2ltaWxhciB0byB3aGF0IHdlIHNhdyBpbiB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbC4NCg0KIyMgUk9DIEFuYWx5c2lzDQoNCk5vdyBhIFJPQyB3aWxsIGJlIGNvbnN0cnVjdGVkIGZvciB0aGUgbmV1cmFsIG5ldCBtb2RlbCBiYXNlZCBvbiB0aGUgdHJhaW5pbmcgZGF0YSBzZXQgd2Ugc3BsaXQgYWJvdmUuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0NCm5uLnJlc3VsdHMgPSBwcmVkaWN0KE5ldHdvcmtNb2RlbCwgdHJhaW5EYXRhKSAgIyBLZWVwIGluIG1pbmQgdGhhdCB0cmFpbkRhdCBpcyBhIG1hdHJpeCENCmN1dDAgPSBzZXEoMCwxLCBsZW5ndGggPSAyMCkNClNlblNwZSA9IG1hdHJpeCgwLCBuY29sID0gbGVuZ3RoKGN1dDApLCBucm93ID0gMiwgYnlyb3cgPSBGQUxTRSkNCmZvciAoaSBpbiAxOmxlbmd0aChjdXQwKSl7DQogICAgYSA9IHN1bSh0cmFpbkRhdGFbLCJ5WWVzIl0gPT0gMSAmIChubi5yZXN1bHRzID4gY3V0MFtpXSkpDQogICAgZCA9IHN1bSh0cmFpbkRhdGFbLCJ5WWVzIl0gPT0gMCAmIChubi5yZXN1bHRzIDwgY3V0MFtpXSkpDQogICAgYiA9IHN1bSh0cmFpbkRhdGFbLCJ5WWVzIl0gPT0gMCAmIChubi5yZXN1bHRzID4gY3V0MFtpXSkpICAgIA0KICAgIGMgPSBzdW0odHJhaW5EYXRhWywieVllcyJdID09IDEgJiAobm4ucmVzdWx0cyA8IGN1dDBbaV0pKSAgIA0KICAgIHNlbiA9IGEvKGEgKyBjKQ0KICAgIHNwZSA9IGQvKGIgKyBkKQ0KICAgIFNlblNwZVssaV0gPSBjKHNlbiwgc3BlKQ0KfQ0KDQojIHBsb3R0aW5nIFJPQw0KcGxvdCgxLVNlblNwZVsyLF0sIFNlblNwZVsxLF0sIHR5cGUgPSJsIiwgeGxpbT1jKDAsMSksIHlsaW09YygwLDEpLA0KICAgICB4bGFiID0gIjEgLSBTcGVjaWZpY2l0eSIsIHlsYWIgPSAiU2Vuc2l0aXZpdHkiLCBsdHkgPSAxLA0KICAgICBtYWluID0gIlJPQyBDdXJ2ZSBmb3IgTmV1cmFsIE5ldHdvcmsgTW9kZWwiLCBjb2wgPSAiYmx1ZSIpDQphYmxpbmUoMCwxLCBsdHkgPSAyLCBjb2wgPSAicmVkIikNCg0KIyMgQ2FsY3VsYXRlIEFVQw0KeHggPSAxLVNlblNwZVsyLF0NCnl5ID0gU2VuU3BlWzEsXQ0Kd2lkdGggPSB4eFstbGVuZ3RoKHh4KV0gLSB4eFstMV0NCmhlaWdodCA9IHl5Wy0xXQ0KDQojIyBBIGJldHRlciBhcHByb3ggb2YgUk9DLCBuZWVkIGxpYnJhcnkge3BST0N9DQogIHByZWRpY3Rpb24gPSBubi5yZXN1bHRzDQogIGNhdGVnb3J5ID0gdHJhaW5EYXRhWywieVllcyJdID09IDENCiAgUk9Db2JqIDwtIHJvYyhjYXRlZ29yeSwgcHJlZGljdGlvbikNCiAgQVVDLm5ldXJhbCA9IGF1YyhST0NvYmopWzFdDQogICMjDQojIyMNCg0KI0FVQyA9bWVhbihzdW0od2lkdGgqaGVpZ2h0KSwgc3VtKHdpZHRoKnl5Wy1sZW5ndGgoeXkpXSkpDQp0ZXh0KDAuOCwgMC4zLCBwYXN0ZSgiQVVDID0gIiwgcm91bmQoQVVDLm5ldXJhbCw0KSksIGNvbCA9ICJwdXJwbGUiLCBjZXggPSAwLjkpDQpsZWdlbmQoImJvdHRvbXJpZ2h0IiwgYygiUk9DIG9mIHRoZSBNb2RlbCIsICJSYW5kb20gR3Vlc3NpbmciKSwgbHR5PWMoMSwyKSwNCiAgICAgICBjb2wgPSBjKCJibHVlIiwgInJlZCIpLCBidHkgPSAibiIsIGNleCA9IDAuOCkNCmBgYCANCg0KVGhlIGFib3ZlIFJPQyBjdXJ2ZSBzaG93IHRoYXQgdGhlIG5ldXJhbCBuZXR3b3JrIG1vZGVsIGlzIHRoYW4gZ3Vlc3Npbmcgc2luY2UgdGhlIGFyZWEgdW5kZXIgdGhlIGN1cnZlIChBVUMpIGlzIHNpZ25pZmljYW50bHkgZ3JlYXRlciB0aGFuIDAuNS4gU2luY2UgdGhlIEFVQyA9IDAuODUzOCBhbmQgaXMgYWxzbyBzaWduaWZpY2FudGx5IGdyZWF0ZXIgdGhhbiAwLjY1LCB0aGlzIG1lYW5zIHRoZSBwcmVkaWN0aXZlIHBvd2VyIG9mIHRoZSBtb2RlbCBpcyBhZGVxdWF0ZSB0byB1c2UuDQoNCg0KIyBEZWNzaW9uIFRyZWVzDQoNCk5vdyB3ZSB3aWxsIGJ1aWxkIGEgdGhpcmQgcHJlZGljdGl2ZSBtb2RlbCB1c2luZyBkZWNpc2lvbiB0cmVlcy4gQXQgbGVhc3QgNCB0byA2IGRpZmZlcmVudCBkZWNpc2lvbiB0cmVlcyB3aWxsIGJlIGNvbnN0cnVjdGVkIGJhc2VkIG9uIGltcHVyaXR5IG1lYXN1cmVzIChHaW5pIGluZGV4IGFuZCBpbmZvcm1hdGlvbiBlbnRyb3B5KSwgcGVuYWx0eSBjb2VmZmljaWVudHMsIGFuZCBjb3N0cyBvZiBmYWxzZSBuZWdhdGl2ZXMgYW5kIHBvc2l0aXZlcy4gDQoNCkFuIFJPQyBjdXJ2ZSB3aWxsIGJlIHVzZWQgaW4gb3JkZXIgdG8gZmluZCB0aGUgYmVzdCBkZWNpc2lvbiB0cmVlIG1vZGVsIG91dCBvZiB0aGUgY2FuZGlkYXRlIG1vZGVscy4gQW4gb3B0aW1hbCBjdXQtb2ZmIHNjb3JlIHdpbGwgYmUgZm91bmQgdGhyb3VnaCBjcm9zcy12YWxpZGF0aW9uIGZvciByZXBvcnRpbmcgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBvZiB0aGUgZmluYWwgbW9kZWwuIA0KDQpUcmVlIEFsZ29yaXRoaW1zIGFyZSBpcyBiYXNlZCBvbiBjb25kaXRpb25hbCBwcm9iYWJpbGl0aWVzIHN0YXRlbWVudHMsIGluIG9yZGVyIHRvIGlkZW50aWZ5IGNlcnRhaW4gc2V0cyBvZiByZWNvcmRzIHRvIGJlIHVzZWQgdG8gbWFrZSBhIHByZWRpY3Rpb24gb2YgdGhlIHJlc3BvbnNlLiBQcmVkaWN0aXZlIHBlcmZvcm1hbmNlIG9mIGEgZGVjaXNpb24gdHJlZSBpcyBkZXBlbmRlbnQgb24gdGhlIHRyYWluZWQgdHJlZSBzaXplLiBUaGUgR2luaSBpbmRleCBhbmQgaW5mb3JtYXRpb24gZW50cm9weSBhcmUgdHdvIGltcHVyaXR5IG1lYXN1cmVzIHVzZWQgdG8gY29udHJvbCBzaXplIG9mIGEgZGVjaXNpb24gZm9yIG9idGFpbmluZyBiZXN0IHBlcmZvcm1hbmNlLiBUaGV5IGFsc28gY2hvb3NlIGZlYXR1cmUgdmFyaWFibGVzIGZvciBkZWZpbmluZyByb290IGFuZCBkZWNpc2lvbiBub2RlcywgYXMgd2VsbCBhcyBob3cgdG8gc3BsaXQgdGhlIHZhcmlhYmxlcy4gR2luaSBJbmRleCBjb25zaWRlcnMgYSBzcGxpdCBmb3IgZWFjaCBhdHRyaWJ1dGUgYW5kIG1lYXN1cmVzIHRoZSBpbXB1cml0eSBvZiBzdWJncm91cHMgc3BsaXQgYnkgYSBmZWF0dXJlIHZhcmlhYmxlLg0KDQoNClRoZSBvcmlnaW5hbCBkYXRhIHNldCwgd2l0aCB0aGUgZGlzY3JldGl6ZWQgdmFyaWFibGVzLCB3aWxsIGJlIHVzZWQuIFRoZSBkYXRhIHdpbGwgYmUgc3BsaXQgaW50byBhIDcwJSBkYXRhIHNldCBmb3IgdHJhaW5pbmcgdGhlIG1vZGVsIGFuZCBhIDMwJSBkYXRhIHNldCBmb3IgdGVzdGluZyB0aGUgbW9kZWwuDQoNCmBgYHtyfQ0KIyBSZWFkIGluIHRoZSBkYXRhIHNldCBhZ2FpbiB3aXRob3V0IHRoZSBOTiBjaGFuZ2VzDQp2YXIubmFtZXMgPSBjKCJhZ2UiLCAiZGF5IiwgImdycC5qb2IiLCAibWFyaXRhbCIsICJlZHVjYXRpb24iLCAiaG91c2luZyIsICJsb2FuIiwgImNvbnRhY3QiLCAiZ3JwLm1vbnRoIiwgImdycC5kdXJhdGlvbiIsICJncnAuY2FtcGFpZ24iLCAiZ3JwLnBkYXlzIiwgImdycC5wcmV2aW91cyIsICJ5IikgDQpCYW5rTWFya2V0aW5nVHJlZXMgPSBCYW5rTWFya2V0aW5nWywgdmFyLm5hbWVzXQ0KDQojIFJhbmRvbSBzcGxpdCBhcHByb2FjaCBmb3IgbmV3IHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGEgc2V0cw0KbiA9IGRpbShCYW5rTWFya2V0aW5nVHJlZXMpWzFdICAjIHNhbXBsZSBzaXplDQojIGNhdXRpb246IHVzaW5nIHdpdGhvdXQgcmVwbGFjZW1lbnQNCmlkLnRyYWluID0gc2FtcGxlKDE6biwgcm91bmQoMC43Km4pLCByZXBsYWNlID0gRkFMU0UpICANCnRyYWluID0gQmFua01hcmtldGluZ1RyZWVzW2lkLnRyYWluLCBdICAgICMgdHJhaW5pbmcgZGF0YQ0KdGVzdCA9IEJhbmtNYXJrZXRpbmdUcmVlc1staWQudHJhaW4sIF0gICAgIyB0ZXN0aW5nIGRhdGENCmBgYA0KDQojIyBCdWlsZGluZyBUcmVlcw0KDQpVc2luZyBhIHdyYXBwZXIgd2UgY2FuIHBhc3MgaW4gdGhlIGFyZ3VtZW50cyBvZiBpbXB1cml0eSBhbmQgcGVuYWx0eSBtZWFzdXJlcywgYXMgd2VsbCBhcyBjb3N0cyBvZiBmYWxzZSBwb3NpdGl2ZXMgYW5kIG5lZ2F0aXZlcywgdG8gY29uc3RydWN0IGRpZmZlcmVudCBkZWNpc2lvbiB0cmVlcy4gDQoNCndlIGFpbSB0byBjcmVhdGUgNiB0cmVlczoNCg0KTW9kZWwgMTogZ2luaS50cmVlLjExIGlzIGJhc2VkIG9uIHRoZSBHaW5pIGluZGV4IHdpdGhvdXQgcGVuYWxpemluZyBmYWxzZSBwb3NpdGl2ZXMgYW5kIGZhbHNlIG5lZ2F0aXZlcy4NCg0KTW9kZWwgMjogaW5mby50cmVlLjExIGlzIGJhc2VkIG9uIGVudHJvcHkgd2l0aG91dCBwZW5hbGl6aW5nIGZhbHNlIHBvc2l0aXZlcyBhbmQgZmFsc2UgbmVnYXRpdmVzLg0KDQpNb2RlbCAzOiBnaW5pLnRyZWUuMTEwIGlzIGJhc2VkIG9uIHRoZSBHaW5pIGluZGV4OiBjb3N0IG9mIGZhbHNlIG5lZ2F0aXZlcyBpcyAxMCB0aW1lcyB0aGUgcG9zaXRpdmVzLg0KDQpNb2RlbCA0OiBpbmZvLnRyZWUuMTEwIGlzIGJhc2VkIG9uIGVudHJvcHk6IGNvc3Qgb2YgZmFsc2UgbmVnYXRpdmVzIGlzIDEwIHRpbWVzIHRoZSBwb3NpdGl2ZXMuDQoNCk1vZGVsIDU6IGdpbmkudHJlZS4xMDEgaXMgYmFzZWQgb24gdGhlIEdpbmkgaW5kZXg6IGNvc3Qgb2YgZmFsc2UgcG9zaXRpdmUgaXMgMTAgdGltZXMgdGhlIG5lZ2F0aXZlcy4NCg0KTW9kZWwgNjogaW5mby50cmVlLjEwMSBpcyBiYXNlZCBvbiBlbnRyb3B5OiBjb3N0IG9mIGZhbHNlIHBvc2l0aXZlIGlzIDEwIHRpbWVzIHRoZSBuZWdhdGl2ZXMuDQoNClRoZSBjb2RlIGZvciBidWlsZGluZyB0aGUgZGlmZmVyZW50IGNhbmRpZGF0ZSB0cmVlcyBpcyBiZWxvdzoNCg0KYGBge3J9DQojIERlZmluaW5nIGRpZmZlcmVudCB0cmVlIG1vZGVscyB1c2luZyB0aGUgcnBhcnQoKSBmdW5jdGlvbg0KdHJlZS5idWlsZGVyID0gZnVuY3Rpb24oaW4uZGF0YSwgZnAsIGZuLCBwdXJpdHkpew0KICAgdHJlZSA9IHJwYXJ0KHkgfiAuLCAgICAgICAgICAgICAgICAjIGluY2x1ZGluZyBhbGwgZmVhdHVyZSB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICBkYXRhID0gaW4uZGF0YSwgDQogICAgICAgICAgICAgICAgbmEuYWN0aW9uICA9IG5hLnJwYXJ0LCAgICAgICAjIEJ5IGRlZmF1bHQsIGRlbGV0ZWQgaWYgdGhlIG91dGNvbWUgaXMgbWlzc2luZywgDQogICAgICAgICAgICAgICAgbWV0aG9kID0gImNsYXNzIiwgICAgICAgICAgICAjIENsYXNzaWZpY2F0aW9uIGZvcm0gZmFjdG9yDQogICAgICAgICAgICAgICAgbW9kZWwgID0gVFJVRSwNCiAgICAgICAgICAgICAgICB4ID0gRkFMU0UsDQogICAgICAgICAgICAgICAgeSA9IFRSVUUsDQogICAgICAgICAgICBwYXJtcyA9IGxpc3QoICMgUGVuYWxpemVzIGZhbHNlIHBvc2l0aXZlcyBvciBuZWdhdGl2ZXMgbW9yZSBoZWF2aWx5DQogICAgICAgICAgICAgICAgbG9zcyA9IG1hdHJpeChjKDAsIGZwLCBmbiwgMCksIG5jb2wgPSAyLCBieXJvdyA9IFRSVUUpLCAgIA0KICAgICAgICAgICAgICAgIHNwbGl0ID0gcHVyaXR5KSwgICAgICAgICAgIyBHaW5pIGluZGV4IG9yIGluZm9ybWF0aW9uIGVudHJvcHkNCiAgICAgICAgICAgICAjIyBycGFydCBhbGdvcml0aG0gb3B0aW9ucyBzdGFydCBoZXJlDQogICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICBtaW5zcGxpdCA9IDEwLCAgIyBtaW5pbXVtIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgcmVxdWlyZWQgYmVmb3JlIHNwbGl0DQogICAgICAgICAgICAgICAgICAgICAgICBtaW5idWNrZXQ9IDEwLCAgIyBtaW5pbXVtIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gYW55IHRlcm1pbmFsIG5vZGUNCiAgICAgICAgICAgICAgICAgICAgICAgIGNwICA9IDAuMDEsICAjIGNvbXBsZXhpdHkgcGFyYW1ldGVyIGZvciBzdG9wcGluZyBydWxlDQogICAgICAgICAgICAgICAgICAgICAgICBtYXhjb21wZXRlICA9IDUsICAgICAjIG51bWJlciBvZiBjb21wZXRpdG9yIHNwbGl0cyByZXRhaW5lZCBpbiB0aGUgb3V0cHV0DQogICAgICAgICAgICAgICAgICAgICAgICBtYXhzdXJyb2dhdGUgICA9IDYsICAjIG51bWJlciBvZiBzdXJyb2dhdGUgc3BsaXRzIHJldGFpbmVkIGluIHRoZSBvdXRwdXQNCiAgICAgICAgICAgICAgICAgICAgICAgIG1heGRlcHRoID0gNSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHh2YWwgPSAxMCAgICAgIyBudW1iZXIgb2YgY3Jvc3MtdmFsaWRhdGlvbiApDQogICAgICAgICAgICAgICAgICAgICAgICApDQogICAgICAgICAgICAgKQ0KICB9DQpgYGANCg0KDQpOb3cgdGhlIG1vZGVscyBjYW4gYmUgZml0IGJlbG93Og0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NSwgZmlnLmNhcD0iTm9uLXBlbmFsaXplZCBkZWNpc2lvbiB0cmVlIG1vZGVscyB1c2luZyBHaW5pIGluZGV4IChsZWZ0KSBhbmQgZW50cm9weSAocmlnaHQpLiJ9DQoNCiMjIFN0YXRlbWVudHMgdG8gcmVjYWxsIHRoZSB0cmVlIG1vZGVsIHdyYXBwZXINCmdpbmkudHJlZS4xLjEgPSB0cmVlLmJ1aWxkZXIoaW4uZGF0YSA9IHRyYWluLCBmcCA9IDEsIGZuID0gMSwgcHVyaXR5ID0gImdpbmkiKQ0KaW5mby50cmVlLjEuMSA9IHRyZWUuYnVpbGRlcihpbi5kYXRhID0gdHJhaW4sIGZwID0gMSwgZm4gPSAxLCBwdXJpdHkgPSAiaW5mb3JtYXRpb24iKQ0KZ2luaS50cmVlLjEuMTAgPSB0cmVlLmJ1aWxkZXIoaW4uZGF0YSA9IHRyYWluLCBmcCA9IDEsIGZuID0gMTAsIHB1cml0eSA9ICJnaW5pIikNCmluZm8udHJlZS4xLjEwID0gdHJlZS5idWlsZGVyKGluLmRhdGEgPSB0cmFpbiwgZnAgPSAxLCBmbiA9IDEwLCBwdXJpdHkgPSAiaW5mb3JtYXRpb24iKQ0KZ2luaS50cmVlLjEwLjEgPSB0cmVlLmJ1aWxkZXIoaW4uZGF0YSA9IHRyYWluLCBmcCA9IDEwLCBmbiA9IDEsIHB1cml0eSA9ICJnaW5pIikNCmluZm8udHJlZS4xMC4xID0gdHJlZS5idWlsZGVyKGluLmRhdGEgPSB0cmFpbiwgZnAgPSAxMCwgZm4gPSAxLCBwdXJpdHkgPSAiaW5mb3JtYXRpb24iKQ0KDQpgYGANCg0KDQpUaGUgdHdvIHBsb3RzIGJlbG93IHNob3cgdGhlIHR3byBwZW5hbGl6ZWQgZGVjaXNpb24gbW9kZWxzIHdoZXJlIGNvc3Qgb2YgZmFsc2UgbmVnYXRpdmUgaXMgMTAgdGltZXMgdGhlIHBvc2l0aXZlcy4NCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NSwgZmlnLmNhcD0iUGVuYWxpemVkIGRlY2lzaW9uIHRyZWUgbW9kZWxzIHdoZW4gY29zdCBvZiBmYWxzZSBuZWdhdGl2ZXMgaXMgMTAgdGltZXMgdGhlIHBvc2l0aXZlcyB1c2luZyBHaW5pIGluZGV4IChsZWZ0KSBhbmQgZW50cm9weSAocmlnaHQpLiJ9DQojIyBQbG90dGluZyB0aGUgdHJlZSBwbG90cw0KcGFyKG1mcm93PWMoMSwyKSkNCnJwYXJ0LnBsb3QoZ2luaS50cmVlLjEuMTAsIG1haW4gPSAiVHJlZSB3aXRoIEdpbmkgSW5kZXg6IFBlbmFsaXphdGlvbiIpDQpycGFydC5wbG90KGluZm8udHJlZS4xLjEwLCBtYWluID0gIlRyZWUgd2l0aCBFbnRyb3B5OiBQZW5hbGl6YXRpb24iKQ0KYGBgDQoNClRoZSBsYXN0IHR3byBwZW5hbGl6ZWQgZGVjaXNpb24gbW9kZWxzIHdoZXJlIGNvc3Qgb2YgZmFsc2UgcG9zaXRpdmUgaXMgMTAgdGltZXMgdGhlIG5lZ2F0aXZlcyB3aWxsIG5vdCBydW4gcHJvcGVybHkgZm9yIHRoZSBhbmFseXNpcyBvciBwcm9kdWNlIGFueSBmdWxsIG1vZGVscy4gVGhpcyBjb3VsZCBiZSBkdWUgdG8gdGhlIGJ1aWx0LWluIHN0b3BwaW5nIHJ1bGUuIEl0IG1heSBpbmRpY2F0ZSwgYmFzZWQgb24gcGVuYWxpemluZyB3ZWlnaHRzLCBubyBzaWduaWZpY2FudCBpbmZvcm1hdGlvbiBnYWluIGZvciBhbnkgcGxvdHRpbmcgYmV5b25kIHRoZSByb290IG5vZGUuIFRoZXJlZm9yZSwgaXQgd291bGQgYmUgYmVzdCB0byBmb2N1cyBvbiBqdXN0IHRoZSB0d28gbW9kZWxzIG9mIGZhbHNlIG5lZ2F0aXZlcyBmb3Igb3VyIHByZWRpY3RpdmUgYW5hbHlzaXMgYW5hbHlzaXMuDQoNCiMjIFJPQyBBbmF5bHNpcw0KDQpMb29raW5nIGF0IG91ciB0d28gY2FuaWRhdGUgbW9kZWxzLCB3ZSBjYW4gbG9vayBhdCB0aGUgUk9DcyBvZiBib3RoIHRoZXNlIGN1cnZlcywgYXMgd2VsbCBhcyB0aGVpciBBVUNzIGluIG9yZGVyIHRvIHNlbGV0IHRoZSBvcHRpbWFsIG1vZGVsLiBBIG5ldyBmdW5jdGlvbiB3aWxsIGJlIHVzZWQgdG8gYnVpbGQgMiBkaWZmZXJlbnQgdHJlZXMgYW5kIHBsb3QgdGhlaXIgY29ycmVzcG9uZGluZyBST0MgY3VydmVzLCBhbmQgY2FsY3VsYXRlIHRoZSBBVUNzIGZvciBlYWNoLCBzbyB3ZSBjYW4gc2VlIHRoZSBnbG9iYWwgcGVyZm9ybWFuY2Ugb2YgdGhlc2UgdHJlZSBhbGdvcml0aG1zLiBUaGUgY29kZSBjYW4gYmUgc2VlbiBiZWxvdzoNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQ0KIyMgQ3JlYXRpbmcgYSBmdW5jdGlvbiBmb3IgcmV0dXJuaW5nIGEgc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5IG1hdHJpeA0KU2Vuc1NwZWMgPSBmdW5jdGlvbihpbi5kYXRhLCBmcCwgZm4sIHB1cml0eSl7DQogIGN1dG9mZiA9IHNlcSgwLDEsIGxlbmd0aCA9IDIwKSAgICMgMjAgY3V0LW9mZnMgaW5jbHVkaW5nIDAgYW5kIDEuIA0KICBtb2RlbCA9IHRyZWUuYnVpbGRlcihpbi5kYXRhLCBmcCwgZm4sIHB1cml0eSkgDQogIA0KICAjIyBEZWNpc2lvbiB0cmVlcyByZXR1cm4gYm90aCAic3VjY2VzcyIgYW5kICJmYWlsdXJlIiBwcm9iYWJpbGl0aWVzLCBidXQgd2UgbmVlZCBvbmx5ICJzdWNjZXNzIiBwcm9iYWJpbGl0eSB0byBkZWZpbmUgc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5IA0KICBwcmVkID0gcHJlZGljdChtb2RlbCwgbmV3ZGF0YSA9IGluLmRhdGEsIHR5cGUgPSAicHJvYiIpICMgdHdvLWNvbHVtbiBtYXRyaXguDQogIHNlbnNwZS5tdHggPSBtYXRyaXgoMCwgbmNvbCA9IGxlbmd0aChjdXRvZmYpLCBucm93PSAyLCBieXJvdyA9IEZBTFNFKQ0KICBmb3IgKGkgaW4gMTpsZW5ndGgoY3V0b2ZmKSl7DQogICAgDQogICMgVGhlIGZvbGxvd2luZyBsaW5lIHVzZXMgb25seSAiIHllcyIgcHJvYmFiaWxpdHk6IHByZWRbLCAiIHllcyJdDQogIHByZWQub3V0ID0gIGlmZWxzZShwcmVkWywiIHllcyJdID49IGN1dG9mZltpXSwgIiB5ZXMiLCAiIG5vIikgIA0KICBUUCA9IHN1bShwcmVkLm91dCA9PSIgeWVzIiAmIGluLmRhdGEkeSA9PSAiIHllcyIpDQogIFROID0gc3VtKHByZWQub3V0ID09IiBubyIgJiBpbi5kYXRhJHkgPT0gIiBubyIpDQogIEZQID0gc3VtKHByZWQub3V0ID09IiB5ZXMiICYgaW4uZGF0YSR5ID09ICIgbm8iKQ0KICBGTiA9IHN1bShwcmVkLm91dCA9PSIgbm8iICYgaW4uZGF0YSR5ID09ICIgeWVzIikNCiAgc2Vuc3BlLm10eFsxLGldID0gVFAvKFRQICsgRk4pDQogIHNlbnNwZS5tdHhbMixpXSA9IFROLyhUTiArIEZQKQ0KICB9DQogICMjIEEgYmV0dGVyIGFwcHJveCBvZiBST0MsIG5lZWQgbGlicmFyeSB7cFJPQ30NCiAgcHJlZGljdGlvbiA9IHByZWRbLCAiIHllcyJdDQogIGNhdGVnb3J5ID0gaW4uZGF0YSR5ID09ICIgeWVzIg0KICBST0NvYmogPC0gcm9jKGNhdGVnb3J5LCBwcmVkaWN0aW9uKQ0KICBBVUMudHJlZSA9IGF1YyhST0NvYmopDQogICMjDQogIGxpc3Qoc2Vuc3BlLm10eD0gc2Vuc3BlLm10eCwgQVVDLnRyZWUgPSByb3VuZChBVUMudHJlZSw0KSkNCiB9DQoNCiNCdWlsZGluZyB0aGUgc2l4IHRyZWUgbW9kZWxzDQpnaW5pUk9DMTEwID0gU2Vuc1NwZWMoaW4uZGF0YSA9IHRyYWluLCBmcD0xLCBmbj0xMCwgcHVyaXR5PSJnaW5pIikNCmluZm9ST0MxMTAgPSBTZW5zU3BlYyhpbi5kYXRhID0gdHJhaW4sIGZwPTEsIGZuPTEwLCBwdXJpdHk9ImluZm9ybWF0aW9uIikNCg0KIyBDcmVhdGluZyB0aGUgUk9DIGN1cnZlcyBmb3IgYm90aCBtb2RlbHMNCnBhcihwdHk9InMiKSAgICAgICMgc2V0IHVwIHNxdWFyZSBwbG90IHRocm91Z2ggZ3JhcGhpYyBwYXJhbWV0ZXINCnBsb3QoMS1naW5pUk9DMTEwJHNlbnNwZS5tdHhbMixdLCBnaW5pUk9DMTEwJHNlbnNwZS5tdHhbMSxdLCB0eXBlID0gImwiLCB4bGltPWMoMCwxKSwgeWxpbT1jKDAsMSksIA0KICAgICB4bGFiPSIxIC0gU3BlY2lmaWNpdHk6IEZQUiIsIHlsYWI9IlNlbnNpdGl2aXR5OiBUUFIiLCBjb2wgPSAiZ3JlZW4yIiwgbHdkID0gMiwNCiAgICAgbWFpbj0iUk9DIEN1cnZlcyBvZiBEZWNpc2lvbiBUcmVlIE1vZGVscyIsIGNleC5tYWluID0gMC45LCBjb2wubWFpbiA9ICJibHVlIikNCmFibGluZSgwLDEsIGx0eSA9IDIsIGNvbCA9ICJyZWQiLCBsd2QgPSAyKQ0KbGluZXMoMS1pbmZvUk9DMTEwJHNlbnNwZS5tdHhbMixdLCBpbmZvUk9DMTEwJHNlbnNwZS5tdHhbMSxdLCBjb2wgPSAiZGVlcHBpbmsiLCBsd2QgPSAyKQ0KDQpsZWdlbmQoImJvdHRvbXJpZ2h0IiwgYyhwYXN0ZSgiZ2luaS4xLjEwLCBBVUMgPSIsZ2luaVJPQzExMCRBVUMudHJlZSksIHBhc3RlKCJpbmZvLjEuMTAsIEFVQyA9IixpbmZvUk9DMTEwJEFVQy50cmVlKSksIGNvbD1jKCJncmVlbjIiLCJkZWVwcGluayIpLCBsdHk9YygxLDIscmVwKDEsNCkpLCBsd2Q9cmVwKDIsNiksIGNleCA9IDAuNiwgYnR5ID0gIm4iKQ0KYGBgDQoNCldlIGNhbiBzZWUgYWJvdmUgdGhlIGdpbmkgbW9kZWwgQVVDLiBUaGlzIGluZGljYXRlcyB0aGF0IHRoZSBwcmVkaWN0aXZlIG1vZGVsIGlzIGJvdGggYmV0dGVyIHRoYW4gdGhlIHJhbmRvbSBndWVzcyBzaW5jZSB0aGUgYXJlYSB1bmRlciB0aGUgY3VydmUgKEFVQykgaXMgc2lnbmlmaWNhbnRseSBncmVhdGVyIHRoYW4gMC41LiBTaW5jZSBhbGwgdGhlc2UgQVVDIGFyZSBhbHNvIHNpZ25pZmljYW50bHkgZ3JlYXRlciB0aGFuIDAuNjUsIHRoaXMgbWVhbnMgdGhlIHByZWRpY3RpdmUgcG93ZXIgb2YgdGhlIG1vZGVsIGlzIGFjY2VwdGFibGUuDQoNClRoZSBHaW5pIGluZGV4IG1vZGVsIHdpbGwgYmUgdGhlIGZpbmFsIG1vZGVsIHVzZWQgZm9yIGZpbmRpbmcgdGhlIG9wdGltYWwgY3V0LW9mZiBwcm9iYWJpbGl0eSBmb3IgcHJlZGljdGl2ZSBtb2RlbGluZyBzaW5jZSBpdCBoYXMgdGhlIGxhcmdlc3QgQVVDLg0KDQojIyBPcHRpbWFsIEN1dC1PZmYgU2NvcmUgRGV0ZXJtaW5hdGlvbg0KDQpXaXRoIHRoZSBmaW5hbCBkZWNpc2lvbiB0cmVlIG1vZGVsIGVzdGFibGlzaGVkLCB3ZSB3aWxsIGZpbmQgdGhlIG9wdGltYWwgY3V0LW9mZiBzY29yZSBmb3IgcmVwb3J0aW5nIHByZWRpY3RpdmUgcGVyZm9ybWFuY2UuIFRoaXMgd2lsbCBiZSBkb25lIHVzaW5nIHRoZSB0ZXN0IGRhdGEgYW5kIGNyb3NzLXZhbGlkYXRpb24gYmFzZWQgb24gdGhlIHRyYWluaW5nIGRhdGEgc2V0LiBUaGUgY29kZSBpcyBiZWxvdzoNCg0KYGBge3J9DQpvcHRtLmN1dG9mZiA9IGZ1bmN0aW9uKGluLmRhdGEsIGZwLCBmbiwgcHVyaXR5KXsNCiAgbjAgPSBkaW0oaW4uZGF0YSlbMV0vNQ0KICBjdXRvZmYgPSBzZXEoMCwxLCBsZW5ndGggPSAyMCkgICAgICAgICAgICAgICAjIGNhbmRpZGF0ZSBjdXQgb2ZmIHByb2INCiAgIyMgYWNjdXJhY3kgZm9yIGVhY2ggY2FuZGlkYXRlIGN1dC1vZmYNCiAgYWNjdXJhY3kubXR4ID0gbWF0cml4KDAsIG5jb2w9MjAsIG5yb3c9NSkgICAgIyAyMCBjYW5kaWRhdGUgY3V0b2ZmcyBhbmQgZ2luaS4xMQ0KICAjIw0KICBmb3IgKGsgaW4gMTo1KXsNCiAgICAgdmFsaWQuaWQgPSAoKGstMSkqbjAgKyAxKTooaypuMCkNCiAgICAgdmFsaWQuZGF0ID0gaW4uZGF0YVt2YWxpZC5pZCxdDQogICAgIHRyYWluLmRhdCA9IGluLmRhdGFbLXZhbGlkLmlkLF0gDQogICAgICMjIHRyZWUgbW9kZWwNCiAgICAgdHJlZS5tb2RlbCA9IHRyZWUuYnVpbGRlcihpbi5kYXRhLCBmcCwgZm4sIHB1cml0eSkNCiAgICAgIyMgcHJlZGljdGlvbiANCiAgICAgcHJlZCA9IHByZWRpY3QodHJlZS5tb2RlbCwgbmV3ZGF0YSA9IHZhbGlkLmRhdCwgdHlwZSA9ICJwcm9iIilbLDJdDQogICAgICMjIGZvci1sb29wDQogICAgIGZvciAoaSBpbiAxOjIwKXsNCiAgICAgICAgIyMgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMNCiAgICAgICAgcGMuMSA9IGlmZWxzZShwcmVkID4gY3V0b2ZmW2ldLCAiIHllcyIsICIgbm8iKQ0KICAgICAgICAjIyBhY2N1cmFjeQ0KICAgICAgICBhMSA9IG1lYW4ocGMuMSA9PSB2YWxpZC5kYXQkeSkNCiAgICAgICAgYWNjdXJhY3kubXR4W2ssaV0gPSBhMQ0KICAgICAgIH0NCiAgICAgIH0NCiAgIGF2Zy5hY2MgPSBhcHBseShhY2N1cmFjeS5tdHgsIDIsIG1lYW4pDQogICAjIyBwbG90cw0KICAgbiA9IGxlbmd0aChhdmcuYWNjKQ0KICAgaWR4ID0gd2hpY2goYXZnLmFjYyA9PSBtYXgoYXZnLmFjYykpDQogICB0aWNrLmxhYmVsID0gYXMuY2hhcmFjdGVyKHJvdW5kKGN1dG9mZiwyKSkNCiAgICMjDQogICBwbG90KDE6biwgYXZnLmFjYywgeGxhYj0iY3V0LW9mZiBzY29yZSIsIHlsYWI9ImF2ZXJhZ2UgYWNjdXJhY3kiLCANCiAgICAgICAgeWxpbT1jKG1pbihhdmcuYWNjKSwgMSksIA0KICAgICAgICBheGVzID0gRkFMU0UsDQogICAgICAgIG1haW49cGFzdGUoIjUtZm9sZCBDViBvcHRpbWFsIGN1dC1vZmYgXG4gIixwdXJpdHksIihmcCwgZm4pID0gKCIsIGZwLCAiLCIsIGZuLCIpIiAsIGNvbGxhcHNlID0gIiIpLA0KICAgICAgICBjZXgubWFpbiA9IDAuOSwNCiAgICAgICAgY29sLm1haW4gPSAibmF2eSIpDQogICAgICAgIGF4aXMoMSwgYXQ9MToyMCwgbGFiZWwgPSB0aWNrLmxhYmVsLCBsYXMgPSAyKQ0KICAgICAgICBheGlzKDIpDQogICAgICAgIHBvaW50cyhpZHgsIGF2Zy5hY2NbaWR4XSwgcGNoPTE5LCBjb2wgPSAicmVkIikNCiAgICAgICAgc2VnbWVudHMoaWR4ICwgbWluKGF2Zy5hY2MpLCBpZHggLCBhdmcuYWNjW2lkeCBdLCBjb2wgPSAicmVkIikNCiAgICAgICB0ZXh0KGlkeCwgYXZnLmFjY1tpZHhdKzAuMDMsIGFzLmNoYXJhY3Rlcihyb3VuZChhdmcuYWNjW2lkeF0sNCkpLCBjb2wgPSAicmVkIiwgY2V4ID0gMC44KSANCiAgIH0NCg0KcGFyKG1mcm93PWMoMSwyKSkNCm9wdG0uY3V0b2ZmKGluLmRhdGEgPSB0cmFpbiwgZnA9MSwgZm49MTAsIHB1cml0eT0iZ2luaSIpDQpvcHRtLmN1dG9mZihpbi5kYXRhID0gdHJhaW4sIGZwPTEsIGZuPTEwLCBwdXJpdHk9ImluZm9ybWF0aW9uIikNCmBgYA0KDQpUaGUgYWJvdmUgZmlndXJlIGluZGljYXRlcyB0aGF0IHRoZSBvcHRpbWFsIGN1dC1vZmYgcHJvYmFiaWxpdGllcyBmb3IgdGhlIEdpbmkgaW5kZXggKGxlZnQpIGFuZCBlbnRyb3B5IChyaWdodCkgbW9kZWxzIGFyZSByZWxhdGl2ZWx5IHRoZSBzYW1lIGluIHJhbmdlLiBUaGVyZWZvcmUsIHRoZSBvcHRpbWFsIGF2ZXJhZ2UgY3V0LW9mZiBwcm9iYWJpbGl0eSBmb3IgdGhlIGZpbmFsIEdpbmkgaW5kZXggbW9kZWwgaXMgMC43NC4NCg0KIyBCYWdnaW5nDQoNCk5vdyB3ZSB3aWxsIGJ1aWxkIGEgZmluYWwgcHJlZGljdGl2ZSBtb2RlbCB1c2luZyBiYWdnaW5nLiBXZSB3aWxsIHNwbGl0IHRoZSBkYXRhIGludG8gdGVzdGluZyBhbmQgdHJhaW5pbmcgb25jZSBhZ2Fpbi4NCmBgYHtyfQ0KDQojIFdlIHVzZSBhIHJhbmRvbSBzcGxpdCBhcHByb2FjaA0KbiA9IGRpbShCYW5rTWFya2V0aW5nVHJlZXMpWzFdICAjIHNhbXBsZSBzaXplDQojIGNhdXRpb246IHVzaW5nIHdpdGhvdXQgcmVwbGFjZW1lbnQNCnRyYWluLmlkID0gc2FtcGxlKDE6biwgcm91bmQoMC43Km4pLCByZXBsYWNlID0gRkFMU0UpICANCnRyYWluID0gQmFua01hcmtldGluZ1RyZWVzW3RyYWluLmlkLCBdICAgICMgdHJhaW5pbmcgZGF0YQ0KdGVzdCA9IEJhbmtNYXJrZXRpbmdUcmVlc1stdHJhaW4uaWQsIF0gICAgIyB0ZXN0aW5nIGRhdGENCmBgYA0KDQoNClRoZSBjb2RlIGZvciBmaXR0aW5nIHRoZSBiYWdnaW5nIG1vZGVsIGlzIGJlbG93Og0KYGBge3J9DQpib290X2RhdGEgPC0gZnVuY3Rpb24oZGYpew0KICByZXR1cm4oZGZbc2FtcGxlKDE6bnJvdyhkZiksIHJlcGxhY2UgPSBUKSxdKQ0KfQ0KDQojICgyKSBGdW5jdGlvbiB0byBydW4gYW4gUnBhcnQgbW9kZWwsIHByb3ZpZGVkDQojIGB4YCBkYXRhIGFuZCBhIG1vZGVsIGZvcm11bGEgYGZvcm1gDQpycGFydF9maXQgPC0gZnVuY3Rpb24oeCwgZm9ybSl7DQogIHJwYXJ0KGZvcm0sIGRhdGEgPSB4LCBtZXRob2QgPSAiY2xhc3MiKQ0KfQ0KDQojICgzKSBGdW5jdGlvbiB0byBnZXQgcHJlZGljdGlvbnMgZnJvbSBtb2RlbHMgb24gbmV3IGRhdGENCmdldF9wcmVkIDwtIGZ1bmN0aW9uKHgsIG5ld2RhdCkgew0KICBwIDwtIHByZWRpY3QoeCwgbmV3ZGF0YSA9IG5ld2RhdCkNCiAgDQogIHJldHVybihwWywgMl0pDQp9DQoNCiMgKDQpIEZ1bmN0aW9uIGZvciByYW5kb20gZm9yZXN0IHBhcmFtZXRlciBzZWxlY3Rpb24NCnNhbXBsZV9wIDwtIGZ1bmN0aW9uKGRmLCB5LCBtYXhwID0gMykgew0KICB4IDwtIGRmWyFuYW1lcyhkZikgJWluJSB5XQ0KICB4IDwtIGRmW3NhbXBsZShuYW1lcyh4KSwgbWF4cCldDQogICAgDQogIHJldHVybihjYmluZCh4LCBkZlt5XSkpDQp9DQoNCiMgQkFHR0lORw0KIy0tLS0tLS0tLS0tLS0tLS0tLS0jDQojIGRlZmluZSBudW1iZXIgb2YgYm9vdHN0cmFwIGl0ZXJhdGlvbnMNCkIgPSAxMDANCg0KIyBzZXQgdXAgbGlzdCB0byBob2xkIG1vZGVscywgbGVuZ3RoIEINCmJsaXN0IDwtIHZlY3Rvcihtb2RlID0gImxpc3QiLCBsZW5ndGggPSBCKQ0KDQojIGJvb3RzdHJhcCBCIG1vZGVscw0KZm9yKGkgaW4gMTpCKQ0KICBibGlzdFtbaV1dIDwtIGJvb3RfZGF0YSh0cmFpbikNCg0KIyBydW4gdmVjdG9yaXplZCBtb2RlbCwgYWRkaW5nIG91ciBtb2RlbCBmb3JtdWxhIGluDQpiYWdfZml0IDwtIGxhcHBseShibGlzdCwgcnBhcnRfZml0LCBmb3JtID0geSB+IC4pDQoNCiMgZ2V0IHByZWRpY3Rpb25zIG9uIG5ldyBkYXRhDQpiX2F2ZyA8LSBkby5jYWxsKHJiaW5kLCAobGFwcGx5KGJhZ19maXQsIGdldF9wcmVkLCB0ZXN0KSkpDQoNCiMgYXZlcmFnZSBvdmVyIHByZWRpY3Rpb25zDQpiX2ZpbmFsIDwtIGFwcGx5KGJfYXZnLCAyLCBtZWFuKQ0KYGBgDQoNCiMjIFJPQyBhbmQgVGVzdA0KYGBge3J9DQojIFVzZSBwUk9DIGZvciBBVUMNCg0KIyBiYWdnaW5nIGFuZCByYW5kb20gZm9yZXN0DQpyb2ModGVzdCR5LCBiX2ZpbmFsKQ0KDQojIHBsb3R0aW5nIFJPQw0KcGxvdCgxLVNlblNwZVsyLF0sIFNlblNwZVsxLF0sIHR5cGUgPSJsIiwgeGxpbT1jKDAsMSksIHlsaW09YygwLDEpLA0KICAgICB4bGFiID0gIjEgLSBTcGVjaWZpY2l0eSIsIHlsYWIgPSAiU2Vuc2l0aXZpdHkiLCBsdHkgPSAxLA0KICAgICBtYWluID0gIlJPQyBDdXJ2ZSBmb3IgQmFnZ2luZyBNb2RlbCIsIGNvbCA9ICJibHVlIikNCmFibGluZSgwLDEsIGx0eSA9IDIsIGNvbCA9ICJyZWQiKQ0KDQojIyBDYWxjdWxhdGUgQVVDDQp4eCA9IDEtU2VuU3BlWzIsXQ0KeXkgPSBTZW5TcGVbMSxdDQp3aWR0aCA9IHh4Wy1sZW5ndGgoeHgpXSAtIHh4Wy0xXQ0KaGVpZ2h0ID0geXlbLTFdDQoNCiMjIEEgYmV0dGVyIGFwcHJveCBvZiBST0MsIG5lZWQgbGlicmFyeSB7cFJPQ30NCiAgcHJlZGljdGlvbiA9IG5uLnJlc3VsdHMNCiAgY2F0ZWdvcnkgPSB0cmFpbkRhdGFbLCJ5WWVzIl0gPT0gMQ0KICBST0NvYmogPC0gcm9jKGNhdGVnb3J5LCBwcmVkaWN0aW9uKQ0KICBBVUMuYl9maW5hbCA9IGF1Yyh0ZXN0JHksYl9maW5hbCkNCiAgIyMNCiMjIw0KDQojQVVDID1tZWFuKHN1bSh3aWR0aCpoZWlnaHQpLCBzdW0od2lkdGgqeXlbLWxlbmd0aCh5eSldKSkNCnRleHQoMC44LCAwLjMsIHBhc3RlKCJBVUMgPSAiLCByb3VuZChBVUMuYl9maW5hbCw0KSksIGNvbCA9ICJwdXJwbGUiLCBjZXggPSAwLjkpDQpsZWdlbmQoImJvdHRvbXJpZ2h0IiwgYygiUk9DIG9mIHRoZSBNb2RlbCIsICJSYW5kb20gR3Vlc3NpbmciKSwgbHR5PWMoMSwyKSwNCiAgICAgICBjb2wgPSBjKCJibHVlIiwgInJlZCIpLCBidHkgPSAibiIsIGNleCA9IDAuOCkNCg0KYGBgDQpXZSBjYW4gc2VlIGFib3ZlIGJhZ2dpbmcgbW9kZWwgQVVDLiBUaGlzIGluZGljYXRlcyB0aGF0IHRoZSBwcmVkaWN0aXZlIG1vZGVsIGlzIGJvdGggYmV0dGVyIHRoYW4gdGhlIHJhbmRvbSBndWVzcyBzaW5jZSB0aGUgYXJlYSB1bmRlciB0aGUgY3VydmUgKEFVQykgaXMgc2lnbmlmaWNhbnRseSBncmVhdGVyIHRoYW4gMC41LiBTaW5jZSBhbGwgdGhlc2UgQVVDIGFyZSBhbHNvIHNpZ25pZmljYW50bHkgZ3JlYXRlciB0aGFuIDAuNjUsIHRoaXMgbWVhbnMgdGhlIHByZWRpY3RpdmUgcG93ZXIgb2YgdGhlIG1vZGVsIGlzIGFjY2VwdGFibGUuDQoNCiMgTW9kZWwgQ29tcGFyaXNvbg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9DQojIFBsb3R0aW5nIGJvdGggUk9DIGN1cnZlcw0KcGxvdCgxLVNlblNwZVsyLF0sIFNlblNwZVsxLF0sIHR5cGUgPSJsIiwgeGxpbT1jKDAsMSksIHlsaW09YygwLDEpLCB4bGFiID0gIjEgLSBTcGVjaWZpY2l0eSIsIHlsYWIgPSAiU2Vuc2l0aXZpdHkiLCBsdHkgPSAxLCBtYWluID0gIlJPQyBDdXJ2ZXMgb2YgdGhlIFRocmVlIEJlc3QgTW9kZWxzIiwgY29sID0gInB1cnBsZSIpDQphYmxpbmUoMCwxLCBsdHkgPSAyLCBjb2wgPSAicmVkIikNCmxpbmVzKG9uZS5taW51cy5zcGVjLmZpbmFsLCBzZW5zLnZlYy5maW5hbCwgY29sID0gIm9yYW5nZSIpDQpsaW5lcygxLWdpbmlST0MxMTAkc2Vuc3BlLm10eFsyLF0sIGdpbmlST0MxMTAkc2Vuc3BlLm10eFsxLF0sIGNvbCA9ICJncmVlbjIiLCBsd2QgPSAyKQ0KbGluZXMoMS1BVUMuYl9maW5hbCwgQVVDLmJfZmluYWwgLGNvbCA9ICJibHVlIikNCg0KDQojIyBDYWxjdWxhdGUgQVVDcyBmb3IgYm90aA0KdGV4dCgwLjgsIDAuNCwgcGFzdGUoIkFVQyA9ICIsIHJvdW5kKEFVQy5uZXVyYWwsNCkpLCBjb2wgPSAicHVycGxlIiwgY2V4ID0gMC45KQ0KdGV4dCgwLjgsIDAuMywgcGFzdGUoIkFVQyA9ICIsIHJvdW5kKEFVQy5iX2ZpbmFsLDQpKSwgY29sID0gImJsdWUiLCBjZXggPSAwLjkpDQpBVUMuZmluYWwgPSByb3VuZChzdW0oc2Vucy52ZWMuZmluYWwqKG9uZS5taW51cy5zcGVjLmZpbmFsWy0xMDFdLW9uZS5taW51cy5zcGVjLmZpbmFsWy0xXSkpLDQpDQp0ZXh0KDAuOCwgMC41LCBwYXN0ZSgiQVVDID0gIiwgcm91bmQoQVVDLmZpbmFsLDQpKSwgY29sID0gIm9yYW5nZSIsIGNleCA9IDAuOSkNCnRleHQoMC44LCAwLjMsIHBhc3RlKCJBVUMgPSAiLCByb3VuZChnaW5pUk9DMTEwJEFVQyw0KSksIGNvbCA9ICJncmVlbjIiLCBjZXggPSAwLjkpDQoNCg0KIyBBZGRpbmcgYSBsZWdlbmQgdG8gdGhlIGZpZ3VyZQ0KbGVnZW5kKCJib3R0b21yaWdodCIsIGMoIlJPQyBvZiB0aGUgRmluYWwgUHJlZGljdGl2ZSBNb2RlbCIsICJST0Mgb2YgdGhlIE5ldXJhbCBOZXR3b3JrIE1vZGVsIiwgIlJPQyBvZiB0aGUgRmluYWwgRGVpc2lvbiBUcmVlIE1vZGVsIiwgIlJhbmRvbSBHdWVzc2luZyIpLCBsdHk9YygxLDIpLCBjb2wgPSBjKCJvcmFuZ2UiLCAicHVycGxlIiwgImdyZWVuMiIsICJyZWQiLCJibGFjayIsICIiKSwgYnR5ID0gIm4iLCBjZXggPSAwLjgpDQpgYGANCg0KIyBTdW1tYXJ5IGFuZCBEaXNjdXNzaW9uDQoNCk92ZXJhbGwgaW4gdGhpcyBwcm9qZWN0IHdlIGhhdmUgZG9uZSBwcmVkaWN0aXZlIG1vZGVsaW5nIGNvbnNpc3Rpbmcgb2YgbG9naXN0aWMgcmVncmVzc2lvbiwgbmV1cmFsIG5ldHdvcmtzLCBkZWNpc2lvbiB0cmVlIGFuYWx5c2lzLCBhbmQgYmFnZ2luZyBpbiBvcmRlciB0byBwcmVkaWN0IGlmIGEgY2xpZW50IHdpbGwgc3Vic2NyaWJlIGFmdGVyIGEgZGlyZWN0IG1hcmtldGluZyBjYW1wYWlnbi4gQWZ0ZXIgbG9va2luZyBhdCBhbGwgdGhlIGZpbmFsIGNhbmRpZGF0ZSBtb2RlbHMsIHRoZSBiZXN0IG9uZSB0byB1c2VkIGJhc2VkIG9mIEFVQyB3b3VsZCBiZSB0aGUgbG9naXN0aWNhbCBtb2RlbCB3aXRoIEFVQyA9IC44NTQxLCBmb2xsb3dlZCBieSB0aGUgbmV1cmFsIG5ldHdvcmsgd2l0aCBhbiBBVUMgPSAuODUzNC4gVGhlIGxvZ2lzdGljYWwgbW9kZWwgaXMgdGhlIGZpbmFsIG1vZGVsIGFuZCBpcyB0aGUgc2ltcGxlc3QgbW9kZWwgaW4gdGhlIHByb2dyYW0uIFRoaXMgcHJvdmVzIHRoYXQgc29tZXRpbWVzIHNpbXBsZSBtb2RlbHMgY2FuIGFuc3dlciBzb21lIHF1ZXN0aW9ucyBiZXR0ZXIgdGhhbiBjb21wbGV4IG1vZGVscyBzdWNoIGFzIGRlY2lzaW9uIHRyZWVzLg0KDQoNCg0K