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

  1. If there is a association with any of the variables and how long of the duration of a call (Linear).

  2. 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]

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

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

2.1 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]

2.2 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 ")

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

3.1 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"))

3.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
Estimate Std. Error z value Pr(>|z|)
(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
Estimate Std. Error z value Pr(>|z|)
(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

3.3 Prediction

3.4 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
age day grp.job marital education housing loan contact grp.month grp.duration grp.campaign grp.pdays grp.previous Pred.Response
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,]

3.5 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)
5-fold CV performance plot

5-fold CV performance plot

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')
test.accuracy
0.845167

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.

3.6 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
sensitivity specificity precision recall F1
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%.

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

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

4.1 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"

4.2 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

4.2.1 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")
Single-layer backpropagation Neural network model for Banking Institution Client Term Deposit Subscription

Single-layer backpropagation Neural network model for Banking Institution Client Term Deposit Subscription

logiModel = glm(factor(y) ~., family = binomial, data = BankMarketingNeural)
pander(summary(logiModel)$coefficients)
  Estimate Std. Error z value Pr(>|z|)
(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

4.3 Model Performance Testing

4.4 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
age day grp.job marital education housing loan contact grp.duration grp.campaign grp.pdays poutcome grp.previous Pred.Response
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.

4.5 Testing Model Accuracy and Performance

This optimal cut-off probability and testing data will now be used to look at the accuracy and confusion matrix of the model.

#Testing resulting output and finding model accuracy and confusion matrix
nn.results <- predict(NetworkModel, testData)
results <- data.frame(actual = testData[,27], prediction = nn.results > .86)
confMatrix = table(results$prediction, results$actual)               # confusion matrix
accuracy=sum(results$actual == results$prediction)/length(results$prediction)
list(confusion.matrix = confMatrix, accuracy = accuracy)       
## $confusion.matrix
##        
##             0     1
##   FALSE 27994  3654
## 
## $accuracy
## [1] 0.8845425

The accuracy score we see is 88.1%. This indicates that there is no under-fitting. THis result is similar to what we saw in the logistic regression model.

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

5 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

5.1 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")
Penalized decision tree models when cost of false negatives is 10 times the positives using Gini index (left) and entropy (right).

Penalized decision tree models when cost of false negatives is 10 times the positives using Gini index (left) and entropy (right).

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.

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

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

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

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

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

8 Summary and Discussion

Overall in this project we have done predictive modeling consisting of logistic regression, neural networks, decision tree analysis, and bagging in order to predict if a client will subscribe after a direct marketing campaign. After looking at all the final candidate models, the best one to used based of AUC would be the logistical model with AUC = .8541, followed by the neural network with an AUC = .8534. The logistical model is the final model and is the simplest model in the program. This proves that sometimes simple models can answer some questions better than complex models such as decision trees.

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