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]

2 Methodology

In order to answer the question “What factors are associated with time spent at the company” a multiple linear regression model must be built. The response variable of interest for this specific question is “time_spend_company”, which depicts the amount of times in year an employee spends at IBM.

2.1 Asumptions of Multible Linear and Logistical Regression

In order for a model to be built for multiple linear regression, the following assumptions must be made: The response variable, “time_spend_company” is a normal random variable and its mean is influenced by explanatory variables but not the variance. All the explanatory variables are assumed to be non-random. They are assumed to be uncorrelated to each other, and must check for multi-collinearity.The data is a random sample. These are all assumed and the model is fitted, but these assumptions will be checked in exploratory data analysis.

The Assumptions of logistical regression are below. There are three assumptions for the binary logistic regression model. These assumptions are that the response variable must be binary,the predictor variables are assumed to be uncorrelated, and t he functional form of the predictor variables is correctly specified. ## Candidate Models

Once we fit the assumptions of the model, we must create multiple candidate models in order to evaluate the best options. Here we fit three models, an initial, transformed, and a final model using step-wise selection, dropping insignificant terms.

2.2 Cross-Validation and Model Selection

For linear regression we will cross validate and select the model in the following way: The data will be two-way split, with the entire sample into training data (80%) and testing data (20%). Then, creating 5-fold data, which will randomly split the training data into 5 folds of approximate equal size. Once the models are fit, cross-validation would be performed, using the 5 different combinations of folds. The performance of the model will be based on these folds. Finally the average of the MSE will be reported for selection.

The process for selecting the correct logistical model we be done in the following way: The data will be split into 70% training/validation and 30% testing. The performance metric to be used is the AUC. The average of the validated AUC will be compared with the AUC calculated from the testing data. We will find the optimal point on the ROC curve that meets the requirements of sensitivity and specificity. For other optimal points such as the point that maximizes the accuracy of the prediction, we can use the same process to search the cut-off probability.

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

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

3.2 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 month
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 == 'mar' | BankMarketing$month == 'apr' | BankMarketing$month == 'may'), 'spring',
                            ifelse((BankMarketing$month == 'jun' | BankMarketing$month == 'jul' | BankMarketing$month == 'aug'), 'summer', 
                            ifelse((BankMarketing$month == 'sep' | BankMarketing$month == 'oct' | 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]
#BankMarketingCampaign = BankMarketing[,var.names]

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

4 Linear Regression Modeling

We can use a linear model for finding association variables and the duration of the phone calls in seconds.The variable, duration, which tells tells how long the phone call last is the reponse response variable.

Before we can model, all binary and categorical variables must be changes to numerical dummy variables. This is done below.

# 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$default <- factor(BankMarketingCampaign$default, 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"))
  
BankMarketingCampaign$poutcome <- factor(BankMarketingCampaign$poutcome, levels = c(" unknown", " success", " failure", "  other"), labels = c("0", "1", "2", "3"))
#Linear Dataset
BankMarketingL <- BankMarketingCampaign %>%
   mutate(y = BankMarketingCampaign$y, 
          age=BankMarketingCampaign$age,
          balance=BankMarketingCampaign$balance,
          day = BankMarketingCampaign$day,
         grp.job = BankMarketingCampaign$grp.job,
         marital = BankMarketingCampaign$marital,
         education = BankMarketingCampaign$education,
         housing = BankMarketingCampaign$housing,
         loan = BankMarketingCampaign$loan,
         default = BankMarketingCampaign$default,
         contact=BankMarketingCampaign$contact,
         grp.campaign = BankMarketingCampaign$grp.campaign,
         grp.pdays =BankMarketingCampaign$grp.pdays,
         grp.previous = BankMarketingCampaign$grp.previous,
         poutcome = BankMarketingCampaign$poutcome, 
         duration = BankMarketing$duration)

BankMarketingL <- BankMarketingL[BankMarketingL$duration > 0, ]

4.1 Create Candidate Models

The full/initial model containing all of the predictor variables will be made first, with ‘duration’ (length of call in seconds) 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
initial.model =lm(duration ~ age + balance + day + grp.job + marital + education + default + housing + loan + contact + grp.campaign+grp.pdays+grp.previous+poutcome, data = BankMarketingL)

plot(initial.model)

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 t value Pr(>|t|)
(Intercept) 278.1019715 11.3974233 24.4004249 0.0000000
age 0.0083785 0.1400149 0.0598403 0.9522831
balance 0.0016194 0.0004136 3.9150836 0.0000905
day -0.7807526 0.1506568 -5.1823251 0.0000002
grp.job1 -11.0742342 4.8736308 -2.2722760 0.0230747
grp.job2 -21.2324631 4.9945461 -4.2511297 0.0000213
grp.job3 -25.5521708 4.5833783 -5.5749644 0.0000000
marital1 2.4519149 4.6382482 0.5286295 0.5970652
marital2 -10.6850180 4.0030828 -2.6691974 0.0076061
education1 -3.0816026 6.9654635 -0.4424117 0.6581935
education2 3.0902802 6.3912414 0.4835180 0.6287304
education3 0.8134778 6.7958463 0.1197022 0.9047196
default1 -16.8459618 9.2242703 -1.8262650 0.0678172
housing1 11.3060522 2.6986059 4.1895900 0.0000280
loan1 -4.0230166 3.4012535 -1.1828041 0.2368933
contact1 -8.9728967 5.5933888 -1.6041968 0.1086780
contact2 15.5759868 2.9591009 5.2637565 0.0000001
grp.campaign1 14.4395410 2.7973165 5.1619261 0.0000002
grp.campaign2 -30.5705356 3.4047983 -8.9786627 0.0000000
grp.pdays1 89.7478592 114.9371431 0.7808430 0.4348991
grp.pdays2 92.0183672 114.9937921 0.8002029 0.4235976
grp.previous1 -8.1042460 8.6448724 -0.9374628 0.3485258
poutcome1 -36.2983161 114.9997510 -0.3156382 0.7522786
poutcome2 -110.1359174 114.8666385 -0.9588155 0.3376570

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.

library(MASS)
boxcox(duration ~ age + balance + day + grp.job + marital + education + default + housing + loan + contact + grp.campaign+grp.pdays+grp.previous+poutcome, 
       data = BankMarketingL, 
       lambda = seq(-1, 1.5, length = 10), 
       xlab=expression(paste(lambda)))
title(main = "Box-Cox Transformation: 95% CI of lambda",
      col.main = "navy", cex.main = 0.9)

transform.model = lm(log(duration) ~ age + balance + day + grp.job + marital + education + default + housing + loan + contact + grp.campaign+grp.pdays+grp.previous+poutcome, data  = BankMarketingL)
par(mfrow=c(2,2), mar = c(2,2,2,2))
plot(transform.model)

kable(summary(transform.model)$coef, caption = "Summarized statistics of the regression 
      coefficients of the model with a log-transformed response")
Summarized statistics of the regression coefficients of the model with a log-transformed response
Estimate Std. Error t value Pr(>|t|)
(Intercept) 5.2446258 0.0404001 129.8171789 0.0000000
age 0.0003610 0.0004963 0.7273715 0.4670023
balance 0.0000046 0.0000015 3.1105647 0.0018685
day -0.0046024 0.0005340 -8.6183358 0.0000000
grp.job1 -0.0546546 0.0172754 -3.1637207 0.0015587
grp.job2 -0.0809790 0.0177040 -4.5740453 0.0000048
grp.job3 -0.0945196 0.0162466 -5.8178233 0.0000000
marital1 0.0066519 0.0164411 0.4045879 0.6857825
marital2 -0.0155715 0.0141896 -1.0973904 0.2724769
education1 0.0010528 0.0246903 0.0426405 0.9659883
education2 0.0353853 0.0226548 1.5619326 0.1183112
education3 0.0072181 0.0240890 0.2996424 0.7644514
default1 -0.0390313 0.0326970 -1.1937290 0.2325905
housing1 0.0370216 0.0095657 3.8702601 0.0001089
loan1 -0.0091656 0.0120563 -0.7602324 0.4471198
contact1 -0.1259604 0.0198267 -6.3530670 0.0000000
contact2 0.1005060 0.0104890 9.5820066 0.0000000
grp.campaign1 0.0534748 0.0099156 5.3930224 0.0000001
grp.campaign2 -0.3061935 0.0120689 -25.3704911 0.0000000
grp.pdays1 0.3727512 0.4074141 0.9149197 0.3602389
grp.pdays2 0.3298483 0.4076149 0.8092154 0.4183957
grp.previous1 0.0334652 0.0306432 1.0920923 0.2747986
poutcome1 -0.1099400 0.4076360 -0.2697013 0.7873914
poutcome2 -0.4576910 0.4071642 -1.1240945 0.2609792
##
final.model =  step(transform.model, direction = "backward", trace = 0)
kable(summary(final.model)$coef, caption = "Summary statistics of the regression 
      coefficients of the final model")
Summary statistics of the regression coefficients of the final model
Estimate Std. Error t value Pr(>|t|)
(Intercept) 5.2568691 0.0270577 194.2834143 0.0000000
balance 0.0000049 0.0000015 3.3433635 0.0008284
day -0.0045638 0.0005332 -8.5599667 0.0000000
grp.job1 -0.0608151 0.0168262 -3.6143003 0.0003015
grp.job2 -0.0863436 0.0174652 -4.9437415 0.0000008
grp.job3 -0.0981317 0.0158954 -6.1735990 0.0000000
education1 -0.0015857 0.0246293 -0.0643814 0.9486669
education2 0.0336210 0.0225818 1.4888525 0.1365335
education3 0.0085905 0.0239801 0.3582348 0.7201693
housing1 0.0341156 0.0093986 3.6298434 0.0002839
contact1 -0.1263981 0.0196831 -6.4216538 0.0000000
contact2 0.0996263 0.0104714 9.5141151 0.0000000
grp.campaign1 0.0522764 0.0099031 5.2788041 0.0000001
grp.campaign2 -0.3083250 0.0120384 -25.6117119 0.0000000
poutcome1 0.2836532 0.0243289 11.6591264 0.0000000
poutcome2 -0.0791844 0.0145742 -5.4332078 0.0000001

4.2 Use Cross-validation for Model Selection

Use cross-validation and MSE as a predictive performance measure to select the best model.

Now we have three candidate models to select from. We extract the coefficient of determination R2 of each of the three candidate models.

r.ini.model = summary(initial.model)$r.squared
r.transfd.model = summary(transform.model)$r.squared
r.final.model = summary(final.model)$r.squared
##
Rsquare = cbind(initial.model = r.ini.model, transfd.model = r.transfd.model, 
                final.model = r.final.model)
kable(Rsquare, caption="Coefficients of correlation of the three candidate models")
Coefficients of correlation of the three candidate models
initial.model transfd.model final.model
0.0106387 0.0376558 0.0373992

All three of these models are frankly terrible with Rsquare scores of 1.1, 3.76 and 3.37. Out of the three the best performing model is the reduced/transformed model. The summary of the transformed model is seen below.

kable(summary(transform.model)$coef, caption = "Summarized statistics of the regression 
      coefficients of the model with a log-transformed response")
Summarized statistics of the regression coefficients of the model with a log-transformed response
Estimate Std. Error t value Pr(>|t|)
(Intercept) 5.2446258 0.0404001 129.8171789 0.0000000
age 0.0003610 0.0004963 0.7273715 0.4670023
balance 0.0000046 0.0000015 3.1105647 0.0018685
day -0.0046024 0.0005340 -8.6183358 0.0000000
grp.job1 -0.0546546 0.0172754 -3.1637207 0.0015587
grp.job2 -0.0809790 0.0177040 -4.5740453 0.0000048
grp.job3 -0.0945196 0.0162466 -5.8178233 0.0000000
marital1 0.0066519 0.0164411 0.4045879 0.6857825
marital2 -0.0155715 0.0141896 -1.0973904 0.2724769
education1 0.0010528 0.0246903 0.0426405 0.9659883
education2 0.0353853 0.0226548 1.5619326 0.1183112
education3 0.0072181 0.0240890 0.2996424 0.7644514
default1 -0.0390313 0.0326970 -1.1937290 0.2325905
housing1 0.0370216 0.0095657 3.8702601 0.0001089
loan1 -0.0091656 0.0120563 -0.7602324 0.4471198
contact1 -0.1259604 0.0198267 -6.3530670 0.0000000
contact2 0.1005060 0.0104890 9.5820066 0.0000000
grp.campaign1 0.0534748 0.0099156 5.3930224 0.0000001
grp.campaign2 -0.3061935 0.0120689 -25.3704911 0.0000000
grp.pdays1 0.3727512 0.4074141 0.9149197 0.3602389
grp.pdays2 0.3298483 0.4076149 0.8092154 0.4183957
grp.previous1 0.0334652 0.0306432 1.0920923 0.2747986
poutcome1 -0.1099400 0.4076360 -0.2697013 0.7873914
poutcome2 -0.4576910 0.4071642 -1.1240945 0.2609792

4.3 Results and Conclusions

Report results based on the final model and conclude the statement of questions.

In summary, balance, day, job, housing, contact, and campaign are statistically significant (p-value <.05). Day and job negatively correlated to duration. Balance, housing, contact and campaign are positively correlated. When given a the balance of an account (balance) and increases by 1 , and all other variables are held constant, there is an 0.0000046 second increase in duration. Alternatively, when given the day and it changes to the next, and all other variables are held constant, there is a 0.0046024 second decrease in the duration of a call.

5 Logistic Regression

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

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

5.2 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,]

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

## Creation of testing model
test.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 = 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.62)
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.8891838

Here in our accuracy test we find that it is accurate 88.7% of the time. This indcates there is no under-fitting for our model.

5.4 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.57)
### 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.1078306 0.9900875 0.5853659 0.1078306 0.1821138

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 98.8%.

5.4.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 a final model ROC curve for sensitivity and (1-specificity)
cut.off.seq = seq(0.01, 0.99, length = 100)
sensitivity.vec = NULL
specificity.vec = NULL
for (i in 1:100){
  testing$test.subscribe = as.numeric(pred.prob.test > cut.off.seq[i])
### 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.vec[i] = p1.a1 / (p1.a1 + p0.a1)
specificity.vec[i] = p0.a0 / (p0.a0 + p1.a0)
}
one.minus.spec = c(1,1 - specificity.vec)
sens.vec = c(1,sensitivity.vec)
##
par(pty = "s")   # make a square figure
plot(one.minus.spec, sens.vec, type = "l", xlim = c(0,1),
     xlab ="1 - specificity",
     ylab = "sensitivity",
     main = "ROC curve of Logistic Term Deposit Subscription Final Model",
     lwd = 2,
     col = "blue", )
segments(0,0,1,1, col = "red", lty = 2, lwd = 2)
AUC = round(sum(sens.vec*(one.minus.spec[-101]-one.minus.spec[-1])),4)
text(0.8, 0.3, paste("AUC = ", AUC), col = "blue")

# Creating an initial model ROC curve for sensitivity and (1-specificity)
## 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, 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)
  }
}

test.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 = 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.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")

cut.off.seq = seq(0.01, 0.99, length = 100)
sensitivity.vec = NULL
specificity.vec = NULL
for (i in 1:100){
  testing$test.subscribe = as.numeric(pred.prob.test > cut.off.seq[i])
### 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.vec[i] = p1.a1 / (p1.a1 + p0.a1)
specificity.vec[i] = p0.a0 / (p0.a0 + p1.a0)
}
one.minus.spec = c(1,1 - specificity.vec)
sens.vec = c(1,sensitivity.vec)
##
par(pty = "s")   # make a square figure
plot(one.minus.spec, sens.vec, type = "l", xlim = c(0,1),
     xlab ="1 - specificity",
     ylab = "sensitivity",
     main = "ROC curve of Logistic Term Deposit Subscription Initial Model",
     lwd = 2,
     col = "blue", )
segments(0,0,1,1, col = "red", lty = 2, lwd = 2)
AUC = round(sum(sens.vec*(one.minus.spec[-101]-one.minus.spec[-1])),4)
text(0.8, 0.3, paste("AUC = ", AUC), col = "blue")

# Creating a reduced model ROC curve for sensitivity and (1-specificity)
## 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 ~ day + grp.job + marital + housing + loan + contact + grp.duration + grp.campaign + 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)
  }
}

test.model = glm(y.subscribe ~ day + grp.job + marital + housing + loan + contact + grp.duration + grp.campaign + grp.previous, family = binomial(link = logit), data = training)
newBankingTestingData = data.frame(day= testing$day, grp.job= testing$grp.job, marital= testing$marital, housing= testing$housing, loan= testing$loan, contact= testing$contact, grp.duration= testing$grp.duration, grp.campaign= testing$grp.campaign, grp.previous= testing$grp.previous)

pred.prob.test = predict.glm(test.model, newBankingTestingData, type = "response")

cut.off.seq = seq(0.01, 0.99, length = 100)
sensitivity.vec = NULL
specificity.vec = NULL
for (i in 1:100){
  testing$test.subscribe = as.numeric(pred.prob.test > cut.off.seq[i])
### 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.vec[i] = p1.a1 / (p1.a1 + p0.a1)
specificity.vec[i] = p0.a0 / (p0.a0 + p1.a0)
}
one.minus.spec = c(1,1 - specificity.vec)
sens.vec = c(1,sensitivity.vec)
##
par(pty = "s")   # make a square figure
plot(one.minus.spec, sens.vec, type = "l", xlim = c(0,1),
     xlab ="1 - specificity",
     ylab = "sensitivity",
     main = "ROC curve of Logistic Term Deposit Subscription Reduced Model",
     lwd = 2,
     col = "blue", )
segments(0,0,1,1, col = "red", lty = 2, lwd = 2)
AUC = round(sum(sens.vec*(one.minus.spec[-101]-one.minus.spec[-1])),4)
text(0.8, 0.3, paste("AUC = ", AUC), col = "blue")

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 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 .8525, even though the initial model has the same score. ## 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.

6 Summary and Discussion

Overall in this project we have done some EDA with linear and logistical regression to answer two questions:

  1. What factors affect the duration of a call?
  2. If we can predict whether a client will subscribe to direct marketing.

In our modeling we cannot rely on the linear model as the assumption have be violated and there is no strong evidence to support and claims. But, in our logistic model we were able to accurately predict whether a client will subscribe to the marketing. Some more EDA and wrangling is needed to fit the linear model, or maybe reworking the question is a more viable solution.

LS0tDQp0aXRsZTogJ0RpcmVjdCBNYXJrZXRpbmcgQ2FtcGFpZ25zIFJlZ3Jlc3Npb24gQW5heWxzaXMnDQphdXRob3I6ICJKZXNzaWNhIEdvcnIiDQpkYXRlOiAiT2N0b2JlciAyNCwgMjAyNCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jX2NvbGxhcHNlZDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogeWVzDQogICAgdGhlbWU6IGx1bWVuDQogIHBkZl9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBmaWdfd2lkdGg6IDMNCiAgICBmaWdfaGVpZ2h0OiAzDQogIHdvcmRfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIGtlZXBfbWQ6IHllcw0KZWRpdG9yX29wdGlvbnM6IA0KICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lDQotLS0NCg0KYGBgez1odG1sfQ0KDQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KDQovKiBDYXNjYWRpbmcgU3R5bGUgU2hlZXRzIChDU1MpIGlzIGEgc3R5bGVzaGVldCBsYW5ndWFnZSB1c2VkIHRvIGRlc2NyaWJlIHRoZSBwcmVzZW50YXRpb24gb2YgYSBkb2N1bWVudCB3cml0dGVuIGluIEhUTUwgb3IgWE1MLiBpdCBpcyBhIHNpbXBsZSBtZWNoYW5pc20gZm9yIGFkZGluZyBzdHlsZSAoZS5nLiwgZm9udHMsIGNvbG9ycywgc3BhY2luZykgdG8gV2ViIGRvY3VtZW50cy4gKi8NCg0KaDEudGl0bGUgeyAgLyogVGl0bGUgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIHRoZSByZXBvcnQgdGl0bGUgKi8NCiAgZm9udC1zaXplOiAyNHB4Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgY29sb3I6IERhcmtSZWQ7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgZm9udC1mYW1pbHk6ICJHaWxsIFNhbnMiLCBzYW5zLXNlcmlmOw0KfQ0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBhdXRob3JzICAqLw0KICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7DQogIGNvbG9yOiByZWQ7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCmg0LmRhdGUgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIHRoZSBkYXRlICAqLw0KICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgxIHsgLyogSGVhZGVyIDEgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAxIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAyMnB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgyIHsgLyogSGVhZGVyIDIgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAyIHNlY3Rpb24gdGl0bGUgKi8NCiAgICBmb250LXNpemU6IDIwcHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCn0NCg0KaDMgeyAvKiBIZWFkZXIgMyAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgMyBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoNCB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCA0IHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmJvZHkgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCi5oaWdobGlnaHRtZSB7IGJhY2tncm91bmQtY29sb3I6eWVsbG93OyB9DQoNCnAgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCjwvc3R5bGU+DQpgYGANCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQojIGNvZGUgY2h1bmsgc3BlY2lmaWVzIHdoZXRoZXIgdGhlIFIgY29kZSwgd2FybmluZ3MsIGFuZCBvdXRwdXQNCiMgd2lsbCBiZSBpbmNsdWRlZCBpbiB0aGUgb3V0cHV0IGZpbGVzLg0KaWYgKCFyZXF1aXJlKCJrbml0ciIpKSB7DQogaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKQ0KIGxpYnJhcnkoa25pdHIpDQp9DQppZiAoIXJlcXVpcmUoIk1BU1MiKSkgew0KIGluc3RhbGwucGFja2FnZXMoIk1BU1MiKQ0KIGxpYnJhcnkoTUFTUykNCn0NCmlmICghcmVxdWlyZSgibGVhZmxldCIpKSB7DQogaW5zdGFsbC5wYWNrYWdlcygibGVhZmxldCIpDQogbGlicmFyeShsZWFmbGV0KQ0KfQ0KaWYgKCFyZXF1aXJlKCJmYWN0b2V4dHJhIikpIHsNCiBpbnN0YWxsLnBhY2thZ2VzKCJmYWN0b2V4dHJhIikNCiBsaWJyYXJ5KGZhY3RvZXh0cmEpDQp9DQppZiAoIXJlcXVpcmUoIndlYnNob3QiKSkgew0KIGluc3RhbGwucGFja2FnZXMoIndlYnNob3QiKQ0KIGxpYnJhcnkod2Vic2hvdCkNCn0NCmlmICghcmVxdWlyZSgiVFNzdHVkaW8iKSkgew0KIGluc3RhbGwucGFja2FnZXMoIlRTc3R1ZGlvIikNCiBsaWJyYXJ5KFRTc3R1ZGlvKQ0KfQ0KaWYgKCFyZXF1aXJlKCJwbG90cml4IikpIHsNCiBpbnN0YWxsLnBhY2thZ2VzKCJwbG90cml4IikNCmxpYnJhcnkocGxvdHJpeCkNCn0NCmlmICghcmVxdWlyZSgiZ2dyaWRnZXMiKSkgew0KIGluc3RhbGwucGFja2FnZXMoImdncmlkZ2VzIikNCmxpYnJhcnkoZ2dyaWRnZXMpDQp9DQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSB7DQogaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KfQ0KaWYgKCFyZXF1aXJlKCJHR2FsbHkiKSkgew0KIGluc3RhbGwucGFja2FnZXMoIkdHYWxseSIpDQpsaWJyYXJ5KEdHYWxseSkNCn0NCmlmICghcmVxdWlyZSgiZHBseXIiKSkgew0KIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikNCmxpYnJhcnkoZHBseXIpDQp9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsICMgaW5jbHVkZSBjb2RlIGNodW5rIGluIHRoZQ0KICMgb3V0cHV0IGZpbGUNCiB3YXJuaW5ncyA9IEZBTFNFLCAjIHNvbWV0aW1lcywgeW91IGNvZGUgbWF5DQogIyBwcm9kdWNlIHdhcm5pbmcgbWVzc2FnZXMsDQojIHlvdSBjYW4gY2hvb3NlIHRvIGluY2x1ZGUNCiMgdGhlIHdhcm5pbmcgbWVzc2FnZXMgaW4NCiAjIHRoZSBvdXRwdXQgZmlsZS4NCiByZXN1bHRzID0gVFJVRSwgIyB5b3UgY2FuIGFsc28gZGVjaWRlIHdoZXRoZXINCiAjIHRvIGluY2x1ZGUgdGhlIG91dHB1dA0KIyBpbiB0aGUgb3V0cHV0IGZpbGUuDQogbWVzc2FnZSA9IEZBTFNFDQopICANCmBgYA0KDQoNClwNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KVGhlIGRhdGEgaXMgcmVsYXRlZCB3aXRoIGRpcmVjdCBtYXJrZXRpbmcgY2FtcGFpZ25zIG9mIGEgUG9ydHVndWVzZSBiYW5raW5nIGluc3RpdHV0aW9uLiBUaGUgbWFya2V0aW5nIGNhbXBhaWducyB3ZXJlIGJhc2VkIG9uIHBob25lIGNhbGxzLiBPZnRlbiwgbW9yZSB0aGFuIG9uZSBjb250YWN0IHRvIHRoZSBzYW1lIGNsaWVudCB3YXMgcmVxdWlyZWQuIFRoZSBkYXRhIGNvbGxlY3RlZCBmcm9tIHRoZXNlIG1hcmtldGluZyBjYXBhaWducyB3YXMgY29sbGVjdGVkIGZyb20gTWF5IDIwMDggdG8gTm92ZW1iZXIgMjAxMC4gVGhlcmUgaXMgYSB0b3RhbCBudW1iZXIgb2YgNDUsMjExIG9ic2VydmF0aW9uIGluIHRoaXMgZGF0YSBzZXQuIFRoZSBkYXRhIHNldCBjb25zaXN0cyBvZiAxNyB2YXJpYWJsZXMsIGluY2x1ZGluZyB0aGUgcmVzcG9uc2UgdmFyaWFibGUgd2l0aCB0aGUgbmFtZSAneScuIEEgZGVzY3JpcHRpb24gb2YgdGhlIHByZWRpY3RvciBhbmQgb3V0Y29tZSBmZWF0dXJlcyBhcmUgYmVsb3c6DQoNCjEgLSBhZ2UgKG51bWVyaWMpDQoNCjIgLSBqb2IgOiBKb2IgdHlwZSAoY2F0ZWdvcmljYWwpOiAiYWRtaW4uIiwgInVua25vd24iLCAidW5lbXBsb3llZCIsICJtYW5hZ2VtZW50IiwgImhvdXNlbWFpZCIsICJlbnRyZXByZW5ldXIiLCAic3R1ZGVudCIsICJibHVlLWNvbGxhciIsICJzZWxmLWVtcGxveWVkIiwgInJldGlyZWQiLCAidGVjaG5pY2lhbiIsICJzZXJ2aWNlcyINCg0KMyAtIG1hcml0YWwgOiBNYXJpdGFsIHN0YXR1cyAoY2F0ZWdvcmljYWwpOiAibWFycmllZCIsICJkaXZvcmNlZCIsICJzaW5nbGUiDQogIA0KNCAtIGVkdWNhdGlvbiAoY2F0ZWdvcmljYWwpOiAidW5rbm93biIsInNlY29uZGFyeSIsInByaW1hcnkiLCJ0ZXJ0aWFyeSINCg0KNSAtIGRlZmF1bHQ6IERvZXMgdGhlIGNsaWVudCBoYXZlIGNyZWRpdCBpbiBkZWZhdWx0PyAoYmluYXJ5OiAieWVzIiwibm8iKQ0KDQo2IC0gYmFsYW5jZTogQXZlcmFnZSB5ZWFybHkgYmFsYW5jZSAobnVtZXJpYywgaW4gZXVyb3MpDQoNCjcgLSBob3VzaW5nOiBEb2VzIHRoZSBjbGllbnQgaGF2ZSBhIGhvdXNpbmcgbG9hbj8gKGJpbmFyeTogInllcyIsIm5vIikNCg0KOCAtIGxvYW46IERvZXMgdGhlIGNsaWVudCBoYXZlIGEgcGVyc29uYWwgbG9hbj8gKGJpbmFyeTogInllcyIsIm5vIikNCg0KOSAtIGNvbnRhY3Q6IENvbnRhY3QgY29tbXVuaWNhdGlvbiB0eXBlIChjYXRlZ29yaWNhbCk6ICJ1bmtub3duIiwidGVsZXBob25lIiwiY2VsbHVsYXIiDQoNCjEwIC0gZGF5OiBMYXN0IGNvbnRhY3QgZGF5IG9mIHRoZSBtb250aCAobnVtZXJpYywgZGlzY3JldGUpDQoNCjExIC0gbW9udGg6IExhc3QgY29udGFjdCBtb250aCBvZiB5ZWFyIChjYXRlZ29yaWNhbCk6ICJqYW4iLCAiZmViIiwgIm1hciIsICJhcHIiLCAibWF5IiwgImp1biIsICJqdWwiLCAgImF1ZyIsICJzZXAiLCAib2N0IiwgIm5vdiIsICJkZWMiDQoNCjEyIC0gZHVyYXRpb246IExhc3QgY29udGFjdCBkdXJhdGlvbiAobnVtZXJpYywgaW4gc2Vjb25kcykNCg0KMTMgLSBjYW1wYWlnbjogVGhlIG51bWJlciBvZiBjb250YWN0cyBwZXJmb3JtZWQgZHVyaW5nIHRoaXMgY2FtcGFpZ24gYW5kIGZvciB0aGlzIGNsaWVudCAobnVtZXJpYywgZGlzY3JldGUpDQoNCjE0IC0gcGRheXM6IFRoZSBudW1iZXIgb2YgZGF5cyBhZnRlciB0aGUgY2xpZW50IHdhcyBsYXN0IGNvbnRhY3RlZCBmcm9tIGEgcHJldmlvdXMgY2FtcGFpZ24gKG51bWVyaWMsIGRpc2NyZXRlKSAgICANCg0KMTUgLSBwcmV2aW91czogVGhlIG51bWJlciBvZiBjb250YWN0cyBwZXJmb3JtZWQgYmVmb3JlIHRoaXMgY2FtcGFpZ24gYW5kIGZvciB0aGlzIGNsaWVudCAobnVtZXJpYykNCg0KMTYgLSBwb3V0Y29tZTogVGhlIG91dGNvbWUgb2YgdGhlIHByZXZpb3VzIG1hcmtldGluZyBjYW1wYWlnbiAoY2F0ZWdvcmljYWwpOiAidW5rbm93biIsICJvdGhlciIsICJmYWlsdXJlIiwgInN1Y2Nlc3MiDQoNCjE3IC0geSBvT3V0Y29tZSBjbGFzcyB2YXJpYWJsZSk6IEhhcyB0aGUgY2xpZW50IHN1YnNjcmliZWQgYSB0ZXJtIGRlcG9zaXQ/IChiaW5hcnk6ICJ5ZXMiLCJubyIpDQoNCkEgcHVibGljIGxpbmsgdG8gdGhlIGRhdGEgY2FuIGJlIGZvdW5kIGhlcmU6IGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9kYXRhc2V0LzIyMi9iYW5rK21hcmtldGluZw0KDQoNCg0KDQoNCldoZW4gbG9va2luZyBhdCB0aGlzIGRhdGFzZXQsIHR3byBhcmVhcyB3ZSB3b3VsZCB3YW50IHRvIGV4cGxvcmUgdGhyb3VnaCBsaW5lYXIgYW5kIGxvZ2lzdGljIHJlZ3Jlc3Npb24uDQoNCjEuIElmIHRoZXJlIGlzIGEgYXNzb2NpYXRpb24gd2l0aCBhbnkgb2YgdGhlIHZhcmlhYmxlcyBhbmQgaG93IGxvbmcgb2YgdGhlIGR1cmF0aW9uIG9mIGEgY2FsbCAoTGluZWFyKS4NCg0KMi4gUHJlZGljdCBpZiBhIGNsaWVudCBoYXMgc3Vic2NyaWJlZCBhIHRlcm0gZGVwb3NpdCBhZnRlciBkaXJlY3QgbWFya2V0aW5nIGNhbXBhaWducyAoTG9naXN0aWMpLg0KDQpgYGB7cn0NCiNMb2FkIHRoZSBzYW1wbGUgZGF0YQ0KQmFua01hcmtldGluZyA9IHJlYWQuY3N2KCJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy9CYW5rTWFya2V0aW5nL0JhbmtNYXJrZXRpbmdDU1YuY3N2IilbLCAtMV0NCmBgYA0KDQojIE1ldGhvZG9sb2d5DQoNCkluIG9yZGVyIHRvIGFuc3dlciB0aGUgcXVlc3Rpb24gIldoYXQgZmFjdG9ycyBhcmUgYXNzb2NpYXRlZCB3aXRoIHRpbWUgc3BlbnQgYXQgdGhlIGNvbXBhbnkiIGEgbXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgbXVzdCBiZSBidWlsdC4gVGhlIHJlc3BvbnNlIHZhcmlhYmxlIG9mIGludGVyZXN0IGZvciB0aGlzIHNwZWNpZmljIHF1ZXN0aW9uIGlzICJ0aW1lX3NwZW5kX2NvbXBhbnkiLCB3aGljaCBkZXBpY3RzIHRoZSBhbW91bnQgb2YgdGltZXMgaW4geWVhciBhbiBlbXBsb3llZSBzcGVuZHMgYXQgSUJNLg0KDQojIyBBc3VtcHRpb25zIG9mIE11bHRpYmxlIExpbmVhciBhbmQgTG9naXN0aWNhbCBSZWdyZXNzaW9uDQoNCkluIG9yZGVyIGZvciBhIG1vZGVsIHRvIGJlIGJ1aWx0IGZvciBtdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbiwgdGhlIGZvbGxvd2luZyBhc3N1bXB0aW9ucyBtdXN0IGJlIG1hZGU6DQpUaGUgcmVzcG9uc2UgdmFyaWFibGUsICJ0aW1lX3NwZW5kX2NvbXBhbnkiIGlzIGEgbm9ybWFsIHJhbmRvbSB2YXJpYWJsZSBhbmQgaXRzIG1lYW4gaXMgaW5mbHVlbmNlZCBieSBleHBsYW5hdG9yeSB2YXJpYWJsZXMgYnV0IG5vdCB0aGUgdmFyaWFuY2UuIEFsbCB0aGUgZXhwbGFuYXRvcnkgdmFyaWFibGVzIGFyZSBhc3N1bWVkIHRvIGJlIG5vbi1yYW5kb20uIFRoZXkgYXJlIGFzc3VtZWQgdG8gYmUgdW5jb3JyZWxhdGVkIHRvIGVhY2ggb3RoZXIsIGFuZCBtdXN0IGNoZWNrIGZvciBtdWx0aS1jb2xsaW5lYXJpdHkuVGhlIGRhdGEgaXMgYSByYW5kb20gc2FtcGxlLiBUaGVzZSBhcmUgYWxsIGFzc3VtZWQgYW5kIHRoZSBtb2RlbCBpcyBmaXR0ZWQsIGJ1dCB0aGVzZSBhc3N1bXB0aW9ucyB3aWxsIGJlIGNoZWNrZWQgaW4gZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcy4NCg0KVGhlIEFzc3VtcHRpb25zIG9mIGxvZ2lzdGljYWwgcmVncmVzc2lvbiBhcmUgYmVsb3cuDQpUaGVyZSBhcmUgdGhyZWUgYXNzdW1wdGlvbnMgZm9yIHRoZSBiaW5hcnkgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbC4gVGhlc2UgYXNzdW1wdGlvbnMgYXJlIHRoYXQgdGhlIHJlc3BvbnNlIHZhcmlhYmxlIG11c3QgYmUgYmluYXJ5LHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzIGFyZSBhc3N1bWVkIHRvIGJlIHVuY29ycmVsYXRlZCwgYW5kIHQgaGUgZnVuY3Rpb25hbCBmb3JtIG9mIHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzIGlzIGNvcnJlY3RseSBzcGVjaWZpZWQuDQojIyBDYW5kaWRhdGUgTW9kZWxzDQoNCk9uY2Ugd2UgZml0IHRoZSBhc3N1bXB0aW9ucyBvZiB0aGUgbW9kZWwsIHdlIG11c3QgY3JlYXRlIG11bHRpcGxlIGNhbmRpZGF0ZSBtb2RlbHMgaW4gb3JkZXIgdG8gZXZhbHVhdGUgdGhlIGJlc3Qgb3B0aW9ucy4gSGVyZSB3ZSBmaXQgdGhyZWUgbW9kZWxzLCBhbiBpbml0aWFsLCB0cmFuc2Zvcm1lZCwgYW5kIGEgZmluYWwgbW9kZWwgdXNpbmcgc3RlcC13aXNlIHNlbGVjdGlvbiwgZHJvcHBpbmcgaW5zaWduaWZpY2FudCB0ZXJtcy4gDQoNCiMjIENyb3NzLVZhbGlkYXRpb24gYW5kIE1vZGVsIFNlbGVjdGlvbg0KDQpGb3IgbGluZWFyIHJlZ3Jlc3Npb24gd2Ugd2lsbCBjcm9zcyB2YWxpZGF0ZSBhbmQgc2VsZWN0IHRoZSBtb2RlbCBpbiB0aGUgZm9sbG93aW5nIHdheToNClRoZSBkYXRhIHdpbGwgYmUgdHdvLXdheSBzcGxpdCwgd2l0aCB0aGUgZW50aXJlIHNhbXBsZSBpbnRvIHRyYWluaW5nIGRhdGEgKDgwJSkgYW5kIHRlc3RpbmcgZGF0YSAoMjAlKS4gVGhlbiwgY3JlYXRpbmcgNS1mb2xkIGRhdGEsIHdoaWNoIHdpbGwgcmFuZG9tbHkgc3BsaXQgdGhlIHRyYWluaW5nIGRhdGEgaW50byA1IGZvbGRzIG9mIGFwcHJveGltYXRlIGVxdWFsIHNpemUuIE9uY2UgdGhlIG1vZGVscyBhcmUgZml0LCBjcm9zcy12YWxpZGF0aW9uIHdvdWxkIGJlIHBlcmZvcm1lZCwgdXNpbmcgdGhlIDUgZGlmZmVyZW50IGNvbWJpbmF0aW9ucyBvZiBmb2xkcy4gVGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBtb2RlbCB3aWxsIGJlIGJhc2VkIG9uIHRoZXNlIGZvbGRzLiBGaW5hbGx5IHRoZSBhdmVyYWdlIG9mIHRoZSBNU0Ugd2lsbCBiZSByZXBvcnRlZCBmb3Igc2VsZWN0aW9uLg0KDQpUaGUgcHJvY2VzcyBmb3Igc2VsZWN0aW5nIHRoZSBjb3JyZWN0IGxvZ2lzdGljYWwgbW9kZWwgd2UgYmUgZG9uZSBpbiB0aGUgZm9sbG93aW5nIHdheToNClRoZSBkYXRhIHdpbGwgYmUgc3BsaXQgaW50byA3MCUgdHJhaW5pbmcvdmFsaWRhdGlvbiBhbmQgMzAlIHRlc3RpbmcuIFRoZSBwZXJmb3JtYW5jZSBtZXRyaWMgdG8gYmUgdXNlZCBpcyB0aGUgQVVDLiBUaGUgYXZlcmFnZSBvZiB0aGUgdmFsaWRhdGVkIEFVQyB3aWxsIGJlIGNvbXBhcmVkIHdpdGggdGhlIEFVQyBjYWxjdWxhdGVkIGZyb20gdGhlIHRlc3RpbmcgZGF0YS4gV2Ugd2lsbCBmaW5kIHRoZSBvcHRpbWFsIHBvaW50IG9uIHRoZSBST0MgY3VydmUgdGhhdCBtZWV0cyB0aGUgcmVxdWlyZW1lbnRzIG9mIHNlbnNpdGl2aXR5IGFuZCBzcGVjaWZpY2l0eS4gRm9yIG90aGVyIG9wdGltYWwgcG9pbnRzIHN1Y2ggYXMgdGhlIHBvaW50IHRoYXQgbWF4aW1pemVzIHRoZSBhY2N1cmFjeSBvZiB0aGUgcHJlZGljdGlvbiwgd2UgY2FuIHVzZSB0aGUgc2FtZSBwcm9jZXNzIHRvIHNlYXJjaCB0aGUgY3V0LW9mZiBwcm9iYWJpbGl0eS4NCg0KIyBFREEgYW5kIEZlYXR1cmUgRW5naW5lZXJpbmcNCg0KSW4gb2RlciB0byBwZXJmb20gc28gRURBLCB3ZSBtdXN0IGhhdmUgYSBiYXNpYyB1bmRlcnN0YW5kaW5nIG9mIHRoZSBkYXRhLiBBIHN1bW1hcnkgb2YgdGVoIGRhdGEgaXMgcHJpbnRlZCBiZWxvdy4NCg0KYGBge3J9DQoNCiNTdW1tYXJpemVkIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MgZm9yIGFsbCB2YXJpYWJsZXMgaW4gdGhlIGRhdGEgc2V0DQpzdW1tYXJ5KEJhbmtNYXJrZXRpbmcpDQpgYGANCg0KV2UgY2FuIHNlZSBmcm9tIHRoZSBhYm92ZSBzdW1tYXJ5IHRhYmxlIHRoYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBzb21lIG9mIHRoZSBudW1lcmljIHZhcmlhYmxlcyBpcyBza2V3ZWQgYW5kIGNvbnRhaW5zIG91dGxpZXJzIHRoYXQgbmVlZCB0byBiZSBmdXJ0aGVyIGV4cGxvcmVkLiANCg0KIyMgSGFuZGxpbmcgTWlzc2luZyBWYWx1ZXMNCg0KVGhpcyBkYXRhc2V0IGRvZXMgbm90IGNvbnRhaW4gYW55IG1pc3NpbmcgdmFsdWVzLCB0aGVyZWZvcmUgd2UgZG8gbm90IGhhdmUgdG8gZHJvcCBhbnkgcm93cyBvciBpbnB1dCBhbnkgdmFsdWVzLg0KDQoNCiMjIFNpbmdsZSBWYXJpYWJsZSBEaXN0cmlidXRpb24NCg0KVGhlIGRpc3RyaWJ1dGlvbiBmb3Igb3VyIGNvbnRpbnVvdXMgbnVtZXJpY2FsIHZhcmlhYmxlcyBmb3IgYXZlcmFnZV9tb250aGx5X2hvdXJzIGFuZCB0aW1lX3NwZW5kX2NvbXBhbnkgYXJlIHNob3duIGJlbG93LiBUaW1lIHNwZW50IGlzIHNrZXdlZCB0byB0aGUgcmlnaHQsIGFuZCBpcyBub3Qgbm9ybWFsLiBBdmVyYWdlIG1vbnRobHkgaG91cnMgc28gbm90IHRvbyBiYWQsIGFuZCBjYW4gYmUgZGVlbWVkIGFwcHJveGlhdGx5IG5vcm1hbC4NCg0KSW4gb3JkZXIgdG8gZXhhbWluZSBhbmQgZGV0ZXJtaW5lIHRoZSBvdXRjb21lIG9mIGFueSBvZiB0aGUgb3V0bGllcnMgYW5kIGxvb2tpbmcgYXQgdGhlIHNrZXduZXNzIG9mIGNlcnRhaW4gbnVtZXJpY2FsIHZhcmlhYmxlcywgc3VjaCBhcyAiZHVyYXRpb24iLCBkaXNjcmV0aXphdGlvbiB3aWxsIGJlIHVzZWQgdG8gZGl2aWRlIHRoZSBkaWZmZXJlbnQgY2F0ZWdvcmljYWwgdmFyaWJsZXMgaW50byBncm91cHMuIFRoaXMgdmFyaWFibGUgc2hvdWxkIGJlIGRpc2NyZXRpemVkIGR1ZSB0byB0aGVudW1iZXIgb2YgaGlnaCBvdXRsaWVycyBpdCBjb250YWlucy4gSW4gbG9va2luZyBhdCB0aGlzIHZhcmlhYmxlJ3MgZGlzdHJpYnV0aW9uLCB0aGUgdGhyZWUgZ3JvdXBzIHRoYXQgd2VyZSBjcmVhdGVkICgwLTE4MCwgMTgxLTMxOSwgYW5kIDMyMCspIHNlZW0gc2ltaWxhciBlbm91Z2ggaW4gdGhlIGZyZXF1ZW5jeSBvZiBjbGllbnQgb2JzZXJ2YXRpb25zLiBUaGlzIG5ldyB2YXJpYWJsZSB3aWxsIG5vdyBiZSB1c2VkIGZvciBidWlsZGluZyBmdXR1cmUgbW9kZWxzLiBUaGUgaGlzdG9ncmFtIGNhbiBiZSBzZWVuIGJlbG93Lg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcid9DQojIGhpc3RvZ3JhbSBzaG93aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGR1cmF0aW9uIHZhcmlhYmxlDQpoaXN0KEJhbmtNYXJrZXRpbmckZHVyYXRpb24sIHhsYWIgPSAiRHVyYXRpb24iLCB5bGFiID0gImNvdW50IiwgbWFpbiA9ICJEdXJhdGlvbnMgb2YgTGFzdCBDb250YWN0IikNCmBgYA0KDQpgYGB7cix3YXJuaW5nPUZBTFNFfQ0KIyBOZXcgZ3JvdXBpbmcgdmFyaWFibGUgZm9yIGR1cmF0aW9uDQpCYW5rTWFya2V0aW5nJGdycC5kdXJhdGlvbiA8LSBpZmVsc2UoQmFua01hcmtldGluZyRkdXJhdGlvbiA8PSAxODAsICcwLTE4MCcsDQogICAgICAgICAgICAgICBpZmVsc2UoQmFua01hcmtldGluZyRkdXJhdGlvbiA+PSAzMjAsICczMjArJywgJ1sxODEsIDMxOV0nKSkNCmBgYA0KDQoNCk5vdyB3ZSB3YW50IHRvIGxvb2sgYXQgc29tZSBvZiB0aGUgY2F0ZWdvcmljYWwgYW5kIGJpbmFyeSBmZWF0dXJlcy4gV2hlbiBsb29raW5nIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgY29udGFjdHMgcGVyZm9ybWVkIGR1cmluZyB0aGUgY2FtcGFpbiwgaXQgaXMgU2tld2VkIHRvIHRoZSByaWdodC4gVGhpcyBtZWFucyB0aGF0IGdyb3VwcyBzaG91bGQgYmUgY3JlYXRlZC4gRm9yIGNhbXBhaWduLCB0aGUgdmFsdWUgb2YgMSBjb250YWN0IHNob3VsZCBiZSBpdHMgb3duIGdyb3VwIHNpbmNlIGl0IGhhcyB0aGUgaGlnaGVzdCBmcmVxdWVuY3kgb2Ygb2JzZXJ2YXRpb25zLiBWYWx1ZXMgMiAmIDMgbnVtYmVyIG9mIGNvbnRhY3RzIGNvbWJpbmVkIGhhdmUgY2xvc2UgdG8gdGhlIHNhbWUgZnJlcXVlbmN5LCBzbyB0aGV5IHNob3VsZCBiZSBwYWlyZWQgdG9nZXRoZXIgaW4gdGhlaXIgb3duIGdyb3VwLiBUaGUgcmVtYWluaW5nIG9ic2VydmF0aW9ucyBzaG91bGQgYmUgY29tYmluZWQgaW50byB0aGUgZmluYWwgZ3JvdXAuIA0KDQpXaGVuIGxvb2tpbmcgYXQgcGRheXMsIHRoZSB2YWx1ZSBvZiAtMSBpcyBhbiBpbmRpY2F0b3IgdGhhdCBhIGNsaWVudCB3YXMgbm90IHByZXZpb3VzbHkgY29udGFjdGVkIGJlZm9yZSB0aGUgY2FtcGFpZ24uIFNpbmNlIHRoaXMgbWFrZXMgdXAgbW9zdCBvZiB0aGUgb2JlcnN2YXRpb25zLCBpdCB3aWxsIGJlY29tZSBpdHMgb3duIGdyb3VwLiBUaGUgcmVzdCBvZiB0aGUgb2JzZXJ2YXRpb25zIHdlcmUgc3BsaXQgaW50byBncm91cHMgb2YgMS0yMDAgZGF5cyBhbmQgMjAwIGRheXMgb3IgbW9yZS4gDQoNCg0KVGhlIHByZXZpb3VzIHZhcmlhYmxlIHdhcyBhbHNvIHNwbGl0IGludG8gMyBncm91cHMuIFRoZSB2YWx1ZSBvZiAwIGNvbnRhY3RzIGlzIG9uZSBncm91cCBzaW5jZSBpdCBoYXMgdGhlIG1vc3Qgb2JzZXJ2YXRpb25zLiBUaGUgdmFsdWVzIG9mIDEgdG8gMyBjb250YWN0cyBpcyBhbm90aGVyIGNhdGVnb3J5IHNpbmNlIHRoZXkgYm90aCBtYWtlIGEgZmFpciBhbW91bnQgb2YgdGhlIG9ic2VydmF0aW9ucy4gU2FtZSBnb2VzIGZvciBvYnNlcnZhdGlvbnMgd2l0aCA0IG9yIG1vcmUgY29udGFjdHMuDQoNCkFsbCBvZiB0aGVzZSBiYXIgcGxvdHMgY2FuIGJlIHNlZW4gYmVsb3cuDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcid9DQojIGJhcnBsb3Qgc2hvd2luZyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBjYW1wYWlnbiB2YXJpYWJsZQ0KbWFya2V0Y2FtcGFpZ25zID0gdGFibGUoQmFua01hcmtldGluZyRjYW1wYWlnbikNCmJhcnBsb3QobWFya2V0Y2FtcGFpZ25zLCBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBDb250YWN0cyBQZXJmb3JtZWQgRHVyaW5nIENhbXBhaWduIiwgeGxhYiA9ICJOdW1iZXIgb2YgQ29udGFjdHMiKQ0KYGBgDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJ30NCiMgYmFycGxvdCBzaG93aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHBkYXlzIHZhcmlhYmxlDQpkYXlzcGFzc2VkID0gdGFibGUoQmFua01hcmtldGluZyRwZGF5cykNCmJhcnBsb3QoZGF5c3Bhc3NlZCwgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgRGF5cyBQYXNzZWQgQWZ0ZXIgQ2xpZW50IExhc3QgQ29udGFjdGVkIChQZGF5cykgIiwgeGxhYiA9ICJOdW1iZXIgb2YgRGF5cyIpDQpgYGANCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInfQ0KIyBiYXJwbG90IHNob3dpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgcHJldmlvdXMgdmFyaWFibGUNCnByZXYgPSB0YWJsZShCYW5rTWFya2V0aW5nJHByZXZpb3VzKQ0KYmFycGxvdChwcmV2LCBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBQcmV2aW91cyBDb250YWN0cyIsIHhsYWIgPSAiTnVtYmVyIG9mIENvbnRhY3RzIikNCmBgYA0KVGhlc2UgbmV3IGdyb3VwZWQgdmFyaWFibGVzIHdpbGwgYmUgdXNlZCBpbiBmdXR1cmUgbW9kZWwgYnVpbGQuIFRoZSBjYXRlZ29yaWVzIGZvciBlYWNoIHZhcmlhYmxlIGFyZSBhcyBmb2xsb3dzOg0KDQpjYW1wYWlnbjogMSwgMi0zLCA0Kw0KcGRheXM6IC0xLCAxLTE5OSwgMjAwKw0KcHJldmlvdXM6IDAsIDEtMywgNCsNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInfQ0KIyBOZXcgZ3JvdXBpbmcgdmFyaWFibGUgZm9yIG1vbnRoDQpCYW5rTWFya2V0aW5nJGdycC5jYW1wYWlnbiA8LSBpZmVsc2UoQmFua01hcmtldGluZyRjYW1wYWlnbiA8PSAxLCAnMScsDQogICAgICAgICAgICAgICBpZmVsc2UoQmFua01hcmtldGluZyRjYW1wYWlnbiA+PSA0LCAnNCsnLCAnWzIsIDNdJykpDQoNCiMgTmV3IGdyb3VwaW5nIHZhcmlhYmxlIGZvciBwZGF5cw0KQmFua01hcmtldGluZyRncnAucGRheXMgPC0gaWZlbHNlKEJhbmtNYXJrZXRpbmckcGRheXMgPD0gLTEsICdDbGllbnQgTm90IFByZXZpb3VzbHkgQ29udGFjdGVkJywgaWZlbHNlKEJhbmtNYXJrZXRpbmckcGRheXMgPj0gMjAwLCAnMjAwKycsICdbMSwgMTk5XScpKQ0KDQojIE5ldyBncm91cGluZyB2YXJpYWJsZSBmb3IgcHJldmlvdXMNCkJhbmtNYXJrZXRpbmckZ3JwLnByZXZpb3VzIDwtIGlmZWxzZShCYW5rTWFya2V0aW5nJHByZXZpb3VzIDw9IDAsICcwJywNCiAgICAgICAgICAgICAgIGlmZWxzZShCYW5rTWFya2V0aW5nJHByZXZpb3VzID4gNCwgJzQrJywgJ1sxLDNdJykpDQpgYGANCg0KDQpOb3cgd2UgbW92ZSBvbnRvIGNhdGVnb3JpY2FsIHZhcmlibGVzLg0KDQpUaGUgY2F0ZWdvcmljYWwgdmFyaWFibGUgb2YgbW9udGggaGFzIGFsc28gYmVlbiBkaXNjcmV0aXplZCBieSBzZWFzb25zIHNpbmNlIHRoZSBiYXIgcGxvdCBiZWxvdyBpcyBhbHNvIHNrZXdlZCBmb3IgY2VydGFpbiBtb250aHMuIEFsc28sIGhhbmRsaW5nIHNtYWxsZXIgZ3JvdXBzIGludG8gc2Vhc29ucyBpcyBlYXNpZXIgdGhhbiBoYW5saW5nIDEyIG1vbnRocy4gQSBuZXcgZmVhdHVyZSBjYWxsZWQgInNlYXNvbnMiIHdhcyBjcmVhdGVkLg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcid9DQojIGJhcnBsb3Qgc2hvd2luZyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBtb250aCB2YXJpYWJsZQ0Kc2Vhc29ucyA9IHRhYmxlKEJhbmtNYXJrZXRpbmckbW9udGgpDQpiYXJwbG90KHNlYXNvbnMsIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIE51bWJlciBvZiBDb250YWN0cyBieSBNb250aCIsIHhsYWIgPSAiTnVtYmVyIG9mIENvbnRhY3RzIikNCmBgYA0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIE5ldyBncm91cGluZyB2YXJpYWJsZSBmb3IgbW9udGgNCkJhbmtNYXJrZXRpbmckZ3JwLm1vbnRoIDwtICBpZmVsc2UoKEJhbmtNYXJrZXRpbmckbW9udGggPT0gJ21hcicgfCBCYW5rTWFya2V0aW5nJG1vbnRoID09ICdhcHInIHwgQmFua01hcmtldGluZyRtb250aCA9PSAnbWF5JyksICdzcHJpbmcnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSgoQmFua01hcmtldGluZyRtb250aCA9PSAnanVuJyB8IEJhbmtNYXJrZXRpbmckbW9udGggPT0gJ2p1bCcgfCBCYW5rTWFya2V0aW5nJG1vbnRoID09ICdhdWcnKSwgJ3N1bW1lcicsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSgoQmFua01hcmtldGluZyRtb250aCA9PSAnc2VwJyB8IEJhbmtNYXJrZXRpbmckbW9udGggPT0gJ29jdCcgfCBCYW5rTWFya2V0aW5nJG1vbnRoID09ICdub3YnKSwgJ2ZhbGwnLCAnd2ludGVyJykpKQ0KDQoNCmBgYA0KDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJ30NCiMgYmFycGxvdCBzaG93aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGpvYiB2YXJpYWJsZQ0Kam9iY2F0ZWdvcnkgPSB0YWJsZShCYW5rTWFya2V0aW5nJGpvYikNCmJhcnBsb3Qoam9iY2F0ZWdvcnksIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIEpvYiBUeXBlIiwgeGxhYiA9ICJOdW1iZXIgb2YgQ2xpZW50cyBpbiBFYWNoIEpvYiIpDQoNCg0KIyBOZXcgZ3JvdXBpbmcgdmFyaWFibGUgZm9yIGpvYg0KQmFua01hcmtldGluZyRncnAuam9iID0gaWZlbHNlKEJhbmtNYXJrZXRpbmckam9iID09ICIgdW5rbm93biIsICJub3Qgd29ya2luZyIsIGlmZWxzZShCYW5rTWFya2V0aW5nJGpvYiA9PSAiIHVuZW1wbG95ZWQiLCAibm90IHdvcmtpbmciLCBpZmVsc2UoQmFua01hcmtldGluZyRqb2IgPT0gIiByZXRpcmVkIiwgIm5vdCB3b3JraW5nIiwgaWZlbHNlKEJhbmtNYXJrZXRpbmckam9iID09ICIgYmx1ZS1jb2xsYXIiLCAid29ya2VycyIsIGlmZWxzZShCYW5rTWFya2V0aW5nJGpvYiA9PSAiIGVudHJlcHJlbmV1ciIsICJib3NzZXMiLCBpZmVsc2UoQmFua01hcmtldGluZyRqb2IgPT0gIiBob3VzZW1haWQiLCAid29ya2VycyIsIGlmZWxzZShCYW5rTWFya2V0aW5nJGpvYiA9PSAiIG1hbmFnZW1lbnQiLCAiYm9zc2VzIiwgaWZlbHNlKEJhbmtNYXJrZXRpbmckam9iID09ICIgc2VsZi1lbXBsb3llZCIsICJib3NzZXMiLCBpZmVsc2UoQmFua01hcmtldGluZyRqb2IgPT0gIiBzZXJ2aWNlcyIsICJ3aGl0ZS1jb2xsYXIiLCBpZmVsc2UoQmFua01hcmtldGluZyRqb2IgPT0gIiB0ZWNobmljaWFuIiwgIndoaXRlLWNvbGxhciIsIGlmZWxzZShCYW5rTWFya2V0aW5nJGpvYiA9PSAiIHN0dWRlbnQiLCAibm90IHdvcmtpbmciLCAid2hpdGUtY29sbGFyIikpKSkpKSkpKSkpDQpgYGANCg0KTm93IHRoYXQgd2UgaGF2ZSBOb3cgdGhhdCB0aGUgdmFyaWFibGVzIGhhdmUgYmVlbiBkaXNjcmV0aXplZCwgYW5kIGNyZWF0ZWQgbmV3IHZhcmlhYmxlcywgdGhlIGRhdGFzZXQgbm93IG11c3QgYmUgY2xlYW5lZCB0byByZWZsZWN0IHRob3NlIGNoYW5nZXMuDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KIyBBc3NlbWJsaW5nIHRoZSBkaXNjcmV0aXplZCB2YXJpYWJsZXMgYW5kIG90aGVyIHZhcmlhYmxlcyB0byBtYWtlIHRoZSBtb2RlbGluZyBkYXRhIHNldA0KdmFyLm5hbWVzID0gYygiYWdlIiwgImJhbGFuY2UiLCAiZGF5IiwgImdycC5qb2IiLCAibWFyaXRhbCIsICJlZHVjYXRpb24iLCAiZGVmYXVsdCIsICJob3VzaW5nIiwgImxvYW4iLCAiY29udGFjdCIsICJncnAubW9udGgiLCAiZ3JwLmR1cmF0aW9uIiwgImdycC5jYW1wYWlnbiIsICJncnAucGRheXMiLCAiZ3JwLnByZXZpb3VzIiwgInBvdXRjb21lIiwgInkiKSANCg0KQmFua01hcmtldGluZ0NhbXBhaWduIDwtIEJhbmtNYXJrZXRpbmdbLCB2YXIubmFtZXNdDQojQmFua01hcmtldGluZ0NhbXBhaWduID0gQmFua01hcmtldGluZ1ssdmFyLm5hbWVzXQ0KYGBgDQojIyBBc3Nlc3NpbmcgUGFpcndpc2VkIFJlbGF0aW9uc2hpcA0KV2Ugd2FudCB0byBzZWUgaWYgdGhlcmUgaXMgYW55IGxpbmVhciBhc3NvY2lhdGlvbiBiZXR3ZWVuIHRoZSBudW1lcmljIHZhcmlhYmxlcy4gQSBQYWlyd2lzZSBzY2F0dGVyIHBsb3QgbGlrZSBiZWxvdywgaXMgYSBnb29kIHZpc3VhbC4gVGhpcyBzY2F0dGVycGxvdCBibG93IGxvb2tzIGF0IHRoZSBkYXRhLg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9DQojIFBhaXItd2lzZSBzY2F0dGVyIHBsb3QgZm9yIG51bWVyaWMgdmFyaWFibGVzDQpnZ3BhaXJzKEJhbmtNYXJrZXRpbmdDYW1wYWlnbiwgICMgRGF0YSBmcmFtZQ0KICAgICAgICBjb2x1bW5zID0gMTozLCAgIyBDb2x1bW5zDQogICAgICAgIGFlcyhjb2xvciA9IHksICAjIENvbG9yIGJ5IGdyb3VwIChjYXQuIHZhcmlhYmxlKQ0KICAgICAgICAgICAgYWxwaGEgPSAwLjUpKQ0KYGBgDQpXZSBzZWUgdGhhdCBub25lIG9mIHRoZSBudW1lcmljIHZhcmlhYmxlcyBhcHBlYXIgdG8gYmUgc2lnbmlmaWNhbnRseSBjb3JyZWxhdGVkIHdoZW4gbG9va2luZyBhdCB0aGUgbnVtYmVycy4gQnV0LCB0aGUgc3RhY2tlZCBkZW5zaXR5IGN1cmVzIGFuZCBubyBjb21wbGV0ZWx5IG92ZXJsYXBwaW5nLCBpbmRpY2F0aW5nIGEgd2VlayBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBudW1lcmljIHZhcmlhYmxlcyBhbmQgb3VyIHJlc3BvbnNlICd5Jy4gVGhlIHN0cm9uZ2VzdCBidXQgJ3dlYWsgY29ycmVsYXRpb24gd2UgY2FuIHNlZSB0aHJvdWdoIHRoZSBwbG90cyBpcyBhZ2UgYW5kIGJhbGFuY2UuDQoNCg0KTm93IHRoYXQgd2UgbG9va2VkIGF0IHRoZSBudW1lcmljIHZhcmlhYmxlcywgd2UgY2FuIHR1cm4gdGhlIGF0dGVudGlvbiB0byBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYW5kIGRlcGVuZGVuY3kuIEluIG9yZGVyIHRvIHNlZSBkZXBlbmRlbmN5LCBtb3NhaWMgcGxvdHMgd2lsbCBiZSBzaG93biBiZWxvdy4NCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD04fQ0KIyBNb3NhaWMgcGxvdHMgdG8gc2hvdyBjYXRlZ29yaWNhbCB2YXJpYWJsZSBkZXBlbmRlbmN5IHRvIHRoZSByZXNwb25zZS4NCnBhcihtZnJvdyA9IGMoMiwyKSkNCm1vc2FpY3Bsb3QoZ3JwLmpvYiB+IHksIGRhdGE9QmFua01hcmtldGluZ0NhbXBhaWduLGNvbD1jKCJCbHVlIiwiUmVkIiksIG1haW49ImpvYiB2cyB0ZXJtIGRlcG9zaXQgIikNCm1vc2FpY3Bsb3QobWFyaXRhbCB+IHksIGRhdGE9QmFua01hcmtldGluZ0NhbXBhaWduLGNvbD1jKCJCbHVlIiwiUmVkIiksIG1haW49Im1hcml0YWwgdnMgdGVybSBkZXBvc2l0ICIpDQptb3NhaWNwbG90KGVkdWNhdGlvbiB+IHksIGRhdGE9QmFua01hcmtldGluZ0NhbXBhaWduLGNvbD1jKCJCbHVlIiwiUmVkIiksIG1haW49ImVkdWNhdGlvbiB2cyB0ZXJtIGRlcG9zaXQgIikNCm1vc2FpY3Bsb3QoZGVmYXVsdCB+IHksIGRhdGE9QmFua01hcmtldGluZ0NhbXBhaWduLGNvbD1jKCJCbHVlIiwiUmVkIiksIG1haW49ImRlZmF1bHQgdnMgdGVybSBkZXBvc2l0ICIpDQpgYGANCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD04fQ0KIyBNb3NhaWMgcGxvdHMgdG8gc2hvdyBjYXRlZ29yaWNhbCB2YXJpYWJsZSBkZXBlbmRlbmN5IHRvIHRoZSByZXNwb25zZS4NCnBhcihtZnJvdyA9IGMoMiwyKSkNCm1vc2FpY3Bsb3QoaG91c2luZyB+IHksIGRhdGE9QmFua01hcmtldGluZ0NhbXBhaWduLGNvbD1jKCJCbHVlIiwiUmVkIiksIG1haW49ImhvdXNpbmcgdnMgdGVybSBkZXBvc2l0ICIpDQptb3NhaWNwbG90KGxvYW4gfiB5LCBkYXRhPUJhbmtNYXJrZXRpbmdDYW1wYWlnbixjb2w9YygiQmx1ZSIsIlJlZCIpLCBtYWluPSJsb2FuIHZzIHRlcm0gZGVwb3NpdCAiKQ0KbW9zYWljcGxvdChjb250YWN0IH4geSwgZGF0YT1CYW5rTWFya2V0aW5nQ2FtcGFpZ24sY29sPWMoIkJsdWUiLCJSZWQiKSwgbWFpbj0iY29udGFjdCB2cyB0ZXJtIGRlcG9zaXQgIikNCm1vc2FpY3Bsb3QoZ3JwLm1vbnRoIH4geSwgZGF0YT1CYW5rTWFya2V0aW5nQ2FtcGFpZ24sY29sPWMoIkJsdWUiLCJSZWQiKSwgbWFpbj0ibW9udGggdnMgdGVybSBkZXBvc2l0ICIpDQpgYGANCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD04fQ0KIyBNb3NhaWMgcGxvdHMgdG8gc2hvdyBjYXRlZ29yaWNhbCB2YXJpYWJsZSBkZXBlbmRlbmN5IHRvIHRoZSByZXNwb25zZS4NCnBhcihtZnJvdyA9IGMoMywyKSkNCm1vc2FpY3Bsb3QoZ3JwLmR1cmF0aW9uIH4geSwgZGF0YT1CYW5rTWFya2V0aW5nQ2FtcGFpZ24sY29sPWMoIkJsdWUiLCJSZWQiKSwgbWFpbj0iZHVyYXRpb24gdnMgdGVybSBkZXBvc2l0ICIpDQptb3NhaWNwbG90KGdycC5jYW1wYWlnbiB+IHksIGRhdGE9QmFua01hcmtldGluZ0NhbXBhaWduLGNvbD1jKCJCbHVlIiwiUmVkIiksIG1haW49ImNhbXBhaWduIHZzIHRlcm0gZGVwb3NpdCAiKQ0KbW9zYWljcGxvdChncnAucGRheXMgfiB5LCBkYXRhPUJhbmtNYXJrZXRpbmdDYW1wYWlnbixjb2w9YygiQmx1ZSIsIlJlZCIpLCBtYWluPSJwZGF5cyB2cyB0ZXJtIGRlcG9zaXQgIikNCm1vc2FpY3Bsb3QoZ3JwLnByZXZpb3VzIH4geSwgZGF0YT1CYW5rTWFya2V0aW5nQ2FtcGFpZ24sY29sPWMoIkJsdWUiLCJSZWQiKSwgbWFpbj0icHJldmlvdXMgdnMgdGVybSBkZXBvc2l0ICIpDQptb3NhaWNwbG90KHBvdXRjb21lIH4geSwgZGF0YT1CYW5rTWFya2V0aW5nQ2FtcGFpZ24sY29sPWMoIkJsdWUiLCJSZWQiKSwgbWFpbj0icG91dGNvbWUgdnMgdGVybSBkZXBvc2l0ICIpDQpgYGANCg0KDQojIExpbmVhciBSZWdyZXNzaW9uIE1vZGVsaW5nDQoNCldlIGNhbiB1c2UgYSBsaW5lYXIgbW9kZWwgZm9yIGZpbmRpbmcgYXNzb2NpYXRpb24gdmFyaWFibGVzIGFuZCB0aGUgZHVyYXRpb24gb2YgdGhlIHBob25lIGNhbGxzIGluIHNlY29uZHMuVGhlIHZhcmlhYmxlLCBkdXJhdGlvbiwgd2hpY2ggdGVsbHMgdGVsbHMgaG93IGxvbmcgdGhlIHBob25lIGNhbGwgbGFzdCBpcyB0aGUgcmVwb25zZSByZXNwb25zZSB2YXJpYWJsZS4NCg0KQmVmb3JlIHdlIGNhbiBtb2RlbCwgYWxsIGJpbmFyeSBhbmQgY2F0ZWdvcmljYWwgdmFyaWFibGVzIG11c3QgYmUgY2hhbmdlcyB0byBudW1lcmljYWwgZHVtbXkgdmFyaWFibGVzLiBUaGlzIGlzIGRvbmUgYmVsb3cuDQoNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIENyZWF0ZSBudW1lcmljYWwgdmFsdWUgbGFiZWxzIGZvciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMNCkJhbmtNYXJrZXRpbmdDYW1wYWlnbiR5IDwtIGZhY3RvcihCYW5rTWFya2V0aW5nQ2FtcGFpZ24keSwgbGV2ZWxzID0gYygiIG5vIiwgIiB5ZXMiKSwgbGFiZWxzID0gYygiMCIsICIxIikpDQoNCkJhbmtNYXJrZXRpbmdDYW1wYWlnbiRncnAuam9iIDwtIGZhY3RvcihCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZ3JwLmpvYiwgbGV2ZWxzID0gYygibm90IHdvcmtpbmciLCAid29ya2VycyIsICJib3NzZXMiLCAid2hpdGUtY29sbGFyIiksIGxhYmVscyA9IGMoIjAiLCAiMSIsICIyIiwgIjMiKSkNCg0KQmFua01hcmtldGluZ0NhbXBhaWduJG1hcml0YWwgPC0gZmFjdG9yKEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRtYXJpdGFsLCBsZXZlbHMgPSBjKCIgZGl2b3JjZWQiLCAiIHNpbmdsZSIsICIgbWFycmllZCIpLCBsYWJlbHMgPSBjKCIwIiwgIjEiLCAiMiIpKQ0KDQpCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZWR1Y2F0aW9uIDwtIGZhY3RvcihCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZWR1Y2F0aW9uLCBsZXZlbHMgPSBjKCIgdW5rbm93biIsICIgcHJpbWFyeSIsICIgc2Vjb25kYXJ5IiwgIiB0ZXJ0aWFyeSIpLCBsYWJlbHMgPSBjKCIwIiwgIjEiLCAiMiIsICIzIikpDQogIA0KQmFua01hcmtldGluZ0NhbXBhaWduJGhvdXNpbmcgPC0gZmFjdG9yKEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRob3VzaW5nLCBsZXZlbHMgPSBjKCIgbm8iLCAiIHllcyIpLCBsYWJlbHMgPSBjKCIwIiwgIjEiKSkNCiAgDQpCYW5rTWFya2V0aW5nQ2FtcGFpZ24kbG9hbiA8LSBmYWN0b3IoQmFua01hcmtldGluZ0NhbXBhaWduJGxvYW4sIGxldmVscyA9IGMoIiBubyIsICIgeWVzIiksIGxhYmVscyA9IGMoIjAiLCAiMSIpKQ0KQmFua01hcmtldGluZ0NhbXBhaWduJGRlZmF1bHQgPC0gZmFjdG9yKEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRkZWZhdWx0LCBsZXZlbHMgPSBjKCIgbm8iLCAiIHllcyIpLCBsYWJlbHMgPSBjKCIwIiwgIjEiKSkNCkJhbmtNYXJrZXRpbmdDYW1wYWlnbiRjb250YWN0IDwtIGZhY3RvcihCYW5rTWFya2V0aW5nQ2FtcGFpZ24kY29udGFjdCwgbGV2ZWxzID0gYygiIHVua25vd24iLCAiIHRlbGVwaG9uZSIsICIgY2VsbHVsYXIiKSwgbGFiZWxzID0gYygiMCIsICIxIiwgIjIiKSkNCg0KQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5tb250aCA8LSBmYWN0b3IoQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5tb250aCwgbGV2ZWxzID0gYygid2ludGVyIiwgInNwcmluZyIsICJzdW1tZXIiLCAiZmFsbCIpLCBsYWJlbHMgPSBjKCIwIiwgIjEiLCAiMiIsICIzIikpDQoNCkJhbmtNYXJrZXRpbmdDYW1wYWlnbiRncnAuZHVyYXRpb24gPC0gZmFjdG9yKEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRncnAuZHVyYXRpb24sIGxldmVscyA9IGMoIjAtMTgwIiwgIlsxODEsIDMxOV0iLCAiMzIwKyIpLCBsYWJlbHMgPSBjKCIwIiwgIjEiLCAiMiIpKQ0KICANCkJhbmtNYXJrZXRpbmdDYW1wYWlnbiRncnAuY2FtcGFpZ24gPC0gZmFjdG9yKEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRncnAuY2FtcGFpZ24sIGxldmVscyA9IGMoIjEiLCAiWzIsIDNdIiwgIjQrIiksIGxhYmVscyA9IGMoIjAiLCAiMSIsICIyIikpDQogIA0KQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5wZGF5cyA8LSBmYWN0b3IoQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5wZGF5cywgbGV2ZWxzID0gYygiQ2xpZW50IE5vdCBQcmV2aW91c2x5IENvbnRhY3RlZCIsICJbMSwgMTk5XSIsICIyMDArIiksIGxhYmVscyA9IGMoIjAiLCAiMSIsICIyIikpDQogIA0KQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5wcmV2aW91cyA8LSBmYWN0b3IoQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5wcmV2aW91cywgbGV2ZWxzID0gYygiMCIsICJbMSwzXSIsICI0KyIpLCBsYWJlbHMgPSBjKCIwIiwgIjEiLCAiMiIpKQ0KICANCkJhbmtNYXJrZXRpbmdDYW1wYWlnbiRwb3V0Y29tZSA8LSBmYWN0b3IoQmFua01hcmtldGluZ0NhbXBhaWduJHBvdXRjb21lLCBsZXZlbHMgPSBjKCIgdW5rbm93biIsICIgc3VjY2VzcyIsICIgZmFpbHVyZSIsICIgIG90aGVyIiksIGxhYmVscyA9IGMoIjAiLCAiMSIsICIyIiwgIjMiKSkNCg0KYGBgDQoNCmBgYHtyLCBXYXJuaW5nPUZBTFNFfQ0KI0xpbmVhciBEYXRhc2V0DQpCYW5rTWFya2V0aW5nTCA8LSBCYW5rTWFya2V0aW5nQ2FtcGFpZ24gJT4lDQogICBtdXRhdGUoeSA9IEJhbmtNYXJrZXRpbmdDYW1wYWlnbiR5LCANCiAgICAgICAgICBhZ2U9QmFua01hcmtldGluZ0NhbXBhaWduJGFnZSwNCiAgICAgICAgICBiYWxhbmNlPUJhbmtNYXJrZXRpbmdDYW1wYWlnbiRiYWxhbmNlLA0KICAgICAgICAgIGRheSA9IEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRkYXksDQogICAgICAgICBncnAuam9iID0gQmFua01hcmtldGluZ0NhbXBhaWduJGdycC5qb2IsDQogICAgICAgICBtYXJpdGFsID0gQmFua01hcmtldGluZ0NhbXBhaWduJG1hcml0YWwsDQogICAgICAgICBlZHVjYXRpb24gPSBCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZWR1Y2F0aW9uLA0KICAgICAgICAgaG91c2luZyA9IEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRob3VzaW5nLA0KICAgICAgICAgbG9hbiA9IEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRsb2FuLA0KICAgICAgICAgZGVmYXVsdCA9IEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRkZWZhdWx0LA0KICAgICAgICAgY29udGFjdD1CYW5rTWFya2V0aW5nQ2FtcGFpZ24kY29udGFjdCwNCiAgICAgICAgIGdycC5jYW1wYWlnbiA9IEJhbmtNYXJrZXRpbmdDYW1wYWlnbiRncnAuY2FtcGFpZ24sDQogICAgICAgICBncnAucGRheXMgPUJhbmtNYXJrZXRpbmdDYW1wYWlnbiRncnAucGRheXMsDQogICAgICAgICBncnAucHJldmlvdXMgPSBCYW5rTWFya2V0aW5nQ2FtcGFpZ24kZ3JwLnByZXZpb3VzLA0KICAgICAgICAgcG91dGNvbWUgPSBCYW5rTWFya2V0aW5nQ2FtcGFpZ24kcG91dGNvbWUsIA0KICAgICAgICAgZHVyYXRpb24gPSBCYW5rTWFya2V0aW5nJGR1cmF0aW9uKQ0KDQpCYW5rTWFya2V0aW5nTCA8LSBCYW5rTWFya2V0aW5nTFtCYW5rTWFya2V0aW5nTCRkdXJhdGlvbiA+IDAsIF0NCg0KYGBgDQoNCg0KDQoNCiMjIENyZWF0ZSBDYW5kaWRhdGUgTW9kZWxzDQoNClRoZSBmdWxsL2luaXRpYWwgbW9kZWwgY29udGFpbmluZyBhbGwgb2YgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMgd2lsbCBiZSBtYWRlIGZpcnN0LCB3aXRoICdkdXJhdGlvbicgKGxlbmd0aCBvZiBjYWxsIGluIHNlY29uZHMpIGFzIHRoZSByZXNwb25zZS4gVGhlIHZhcmlhYmxlcyBiYWxhbmNlIGFuZCBkZWZhdWx0IGFyZSBub3QgaW5jbHVkZWQgc2luY2Ugb3VyIEVEQSBzaG93ZWQgdGhhdCByZW1vdmluZyB0aGVtIGZyb20gdGhlIG1vZGVsIG1pZ2h0IGhlbHAgdGhlIHJlc3VsdHMuDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KIyBDcmVhdGUgdGhlIGluaXRpYWwgZnVsbCBtb2RlbA0KaW5pdGlhbC5tb2RlbCA9bG0oZHVyYXRpb24gfiBhZ2UgKyBiYWxhbmNlICsgZGF5ICsgZ3JwLmpvYiArIG1hcml0YWwgKyBlZHVjYXRpb24gKyBkZWZhdWx0ICsgaG91c2luZyArIGxvYW4gKyBjb250YWN0ICsgZ3JwLmNhbXBhaWduK2dycC5wZGF5cytncnAucHJldmlvdXMrcG91dGNvbWUsIGRhdGEgPSBCYW5rTWFya2V0aW5nTCkNCg0KcGxvdChpbml0aWFsLm1vZGVsKQ0KY29lZmZpY2llbnQudGFibGUgPSBzdW1tYXJ5KGluaXRpYWwubW9kZWwpJGNvZWYNCmthYmxlKGNvZWZmaWNpZW50LnRhYmxlLCBjYXB0aW9uID0gIlNpZ25pZmljYW5jZSB0ZXN0cyBvZiBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIikNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgdGhlcmUgYXJlIHNvbWUgaW5zaWduaWZpY2FudCBwcmVkaWN0b3IgdmFyaWFibGVzLCBhbmQgdGhleSBzaG91bGQgYmUgZHJvcHBlZCBmcm9tIHRoZSBtb2RlbCB0byBjcmVhdGUgYSByZWR1Y2VkIG1vZGVsLiBVc2luZyB0aGUgc3RlcCgpIGZ1bmN0aW9uLCB3ZSB3aWxsIG5vdyBmaW5kICByZWR1Y2VkIGFuZCBmaW5hbCBtb2RlbHMuIFRoZSBmaW5hbCBiZXN0IG1vZGVsIHdpbGwgYmUgYSBtb2RlbCB0aGF0IGlzIGJldHdlZW4gdGhlIGZ1bGwgYW5kIHJlZHVjZWQgbW9kZWxzLg0KDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KDQpsaWJyYXJ5KE1BU1MpDQpib3hjb3goZHVyYXRpb24gfiBhZ2UgKyBiYWxhbmNlICsgZGF5ICsgZ3JwLmpvYiArIG1hcml0YWwgKyBlZHVjYXRpb24gKyBkZWZhdWx0ICsgaG91c2luZyArIGxvYW4gKyBjb250YWN0ICsgZ3JwLmNhbXBhaWduK2dycC5wZGF5cytncnAucHJldmlvdXMrcG91dGNvbWUsIA0KICAgICAgIGRhdGEgPSBCYW5rTWFya2V0aW5nTCwgDQogICAgICAgbGFtYmRhID0gc2VxKC0xLCAxLjUsIGxlbmd0aCA9IDEwKSwgDQogICAgICAgeGxhYj1leHByZXNzaW9uKHBhc3RlKGxhbWJkYSkpKQ0KdGl0bGUobWFpbiA9ICJCb3gtQ294IFRyYW5zZm9ybWF0aW9uOiA5NSUgQ0kgb2YgbGFtYmRhIiwNCiAgICAgIGNvbC5tYWluID0gIm5hdnkiLCBjZXgubWFpbiA9IDAuOSkNCg0KDQoNCnRyYW5zZm9ybS5tb2RlbCA9IGxtKGxvZyhkdXJhdGlvbikgfiBhZ2UgKyBiYWxhbmNlICsgZGF5ICsgZ3JwLmpvYiArIG1hcml0YWwgKyBlZHVjYXRpb24gKyBkZWZhdWx0ICsgaG91c2luZyArIGxvYW4gKyBjb250YWN0ICsgZ3JwLmNhbXBhaWduK2dycC5wZGF5cytncnAucHJldmlvdXMrcG91dGNvbWUsIGRhdGEgID0gQmFua01hcmtldGluZ0wpDQpwYXIobWZyb3c9YygyLDIpLCBtYXIgPSBjKDIsMiwyLDIpKQ0KcGxvdCh0cmFuc2Zvcm0ubW9kZWwpDQoNCmthYmxlKHN1bW1hcnkodHJhbnNmb3JtLm1vZGVsKSRjb2VmLCBjYXB0aW9uID0gIlN1bW1hcml6ZWQgc3RhdGlzdGljcyBvZiB0aGUgcmVncmVzc2lvbiANCiAgICAgIGNvZWZmaWNpZW50cyBvZiB0aGUgbW9kZWwgd2l0aCBhIGxvZy10cmFuc2Zvcm1lZCByZXNwb25zZSIpDQoNCg0KIyMNCmZpbmFsLm1vZGVsID0gIHN0ZXAodHJhbnNmb3JtLm1vZGVsLCBkaXJlY3Rpb24gPSAiYmFja3dhcmQiLCB0cmFjZSA9IDApDQprYWJsZShzdW1tYXJ5KGZpbmFsLm1vZGVsKSRjb2VmLCBjYXB0aW9uID0gIlN1bW1hcnkgc3RhdGlzdGljcyBvZiB0aGUgcmVncmVzc2lvbiANCiAgICAgIGNvZWZmaWNpZW50cyBvZiB0aGUgZmluYWwgbW9kZWwiKQ0KDQpgYGANCg0KIyMgVXNlIENyb3NzLXZhbGlkYXRpb24gZm9yIE1vZGVsIFNlbGVjdGlvbg0KDQpVc2UgY3Jvc3MtdmFsaWRhdGlvbiBhbmQgTVNFIGFzIGEgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBtZWFzdXJlIHRvIHNlbGVjdCB0aGUgYmVzdCBtb2RlbC4NCg0KTm93IHdlIGhhdmUgdGhyZWUgY2FuZGlkYXRlIG1vZGVscyB0byBzZWxlY3QgZnJvbS4gV2UgZXh0cmFjdCB0aGUgY29lZmZpY2llbnQgb2YgZGV0ZXJtaW5hdGlvbiBSMg0Kb2YgZWFjaCBvZiB0aGUgdGhyZWUgY2FuZGlkYXRlIG1vZGVscy4NCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0Kci5pbmkubW9kZWwgPSBzdW1tYXJ5KGluaXRpYWwubW9kZWwpJHIuc3F1YXJlZA0Kci50cmFuc2ZkLm1vZGVsID0gc3VtbWFyeSh0cmFuc2Zvcm0ubW9kZWwpJHIuc3F1YXJlZA0Kci5maW5hbC5tb2RlbCA9IHN1bW1hcnkoZmluYWwubW9kZWwpJHIuc3F1YXJlZA0KIyMNClJzcXVhcmUgPSBjYmluZChpbml0aWFsLm1vZGVsID0gci5pbmkubW9kZWwsIHRyYW5zZmQubW9kZWwgPSByLnRyYW5zZmQubW9kZWwsIA0KICAgICAgICAgICAgICAgIGZpbmFsLm1vZGVsID0gci5maW5hbC5tb2RlbCkNCmthYmxlKFJzcXVhcmUsIGNhcHRpb249IkNvZWZmaWNpZW50cyBvZiBjb3JyZWxhdGlvbiBvZiB0aGUgdGhyZWUgY2FuZGlkYXRlIG1vZGVscyIpDQoNCmBgYA0KQWxsIHRocmVlIG9mIHRoZXNlIG1vZGVscyBhcmUgZnJhbmtseSB0ZXJyaWJsZSB3aXRoIFJzcXVhcmUgc2NvcmVzIG9mIDEuMSwgMy43NiBhbmQgMy4zNy4gT3V0IG9mIHRoZSB0aHJlZSB0aGUgYmVzdCBwZXJmb3JtaW5nIG1vZGVsIGlzIHRoZSByZWR1Y2VkL3RyYW5zZm9ybWVkIG1vZGVsLiBUaGUgc3VtbWFyeSBvZiB0aGUgdHJhbnNmb3JtZWQgbW9kZWwgaXMgc2VlbiBiZWxvdy4NCmBgYHtyLCBXYXJuaW5nPUZBTFNFfQ0KDQoNCmthYmxlKHN1bW1hcnkodHJhbnNmb3JtLm1vZGVsKSRjb2VmLCBjYXB0aW9uID0gIlN1bW1hcml6ZWQgc3RhdGlzdGljcyBvZiB0aGUgcmVncmVzc2lvbiANCiAgICAgIGNvZWZmaWNpZW50cyBvZiB0aGUgbW9kZWwgd2l0aCBhIGxvZy10cmFuc2Zvcm1lZCByZXNwb25zZSIpDQoNCmBgYA0KDQojIyBSZXN1bHRzIGFuZCBDb25jbHVzaW9ucw0KDQpSZXBvcnQgcmVzdWx0cyBiYXNlZCBvbiB0aGUgZmluYWwgbW9kZWwgYW5kIGNvbmNsdWRlIHRoZSBzdGF0ZW1lbnQgb2YgcXVlc3Rpb25zLg0KDQpJbiBzdW1tYXJ5LCBiYWxhbmNlLCBkYXksIGpvYiwgaG91c2luZywgY29udGFjdCwgYW5kIGNhbXBhaWduIGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IChwLXZhbHVlIDwuMDUpLiBEYXkgYW5kIGpvYiBuZWdhdGl2ZWx5IGNvcnJlbGF0ZWQgdG8gZHVyYXRpb24uIEJhbGFuY2UsIGhvdXNpbmcsIGNvbnRhY3QgYW5kIGNhbXBhaWduIGFyZSBwb3NpdGl2ZWx5IGNvcnJlbGF0ZWQuIFdoZW4gZ2l2ZW4gYSB0aGUgYmFsYW5jZSBvZiBhbiBhY2NvdW50IChiYWxhbmNlKSBhbmQgaW5jcmVhc2VzIGJ5IDEgLCBhbmQgYWxsIG90aGVyIHZhcmlhYmxlcyBhcmUgaGVsZCBjb25zdGFudCwgdGhlcmUgaXMgYW4gMC4wMDAwMDQ2IHNlY29uZCBpbmNyZWFzZSBpbiBkdXJhdGlvbi4gQWx0ZXJuYXRpdmVseSwgd2hlbiBnaXZlbiB0aGUgZGF5ICBhbmQgaXQgY2hhbmdlcyB0byB0aGUgbmV4dCwgYW5kIGFsbCBvdGhlciB2YXJpYWJsZXMgYXJlIGhlbGQgY29uc3RhbnQsIHRoZXJlIGlzIGEgMC4wMDQ2MDI0IHNlY29uZCBkZWNyZWFzZSBpbiB0aGUgZHVyYXRpb24gb2YgYSBjYWxsLg0KDQojIExvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KV2UgY2FuIHVzZSBhIGxvZ2lzdGljYWwgbW9kZWwgZm9yIHByZWRpY3Rpbmcgd2hldGhlciBvciBub3QgYSBjbGllbnQgaGFzIHN1YnNjcmliZWQgYSB0ZXJtIGRlcG9zaXQgYWZ0ZXIgZGlyZWN0IG1hcmtldGluZyBjYW1wYWlnbnMuVGhlIHZhcmlhYmxlLCB5LCB3aGljaCB0ZWxscyB3aGV0aGVyIGEgY2xpZW50IGhhcyBzdWJzY3JpYmVkIGEgdGVybSBkZXBvc2l0LCBhY3RzIGFzIHRoZSBiaW5hcnkgcmVzcG9uc2UgdmFyaWFibGUgb2YgYWxsIHRoZSBsb2dpc3RpYyBtb2RlbHMuIFRoZSByZXN0IG9mIHRoZSB2YXJpYWJsZXMsIGluY2x1ZGluZyB0aGUgbmV3IGRpc2NyZXRpemVkIHZhcmlhYmxlcywgb2YgdGhlIHJldmlzZWQgZGF0YSBzZXQgYWN0IGFzIHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzIHRoYXQgd2lsbCBwb3NzaWJseSBhZmZlY3QgdGhlIHJlc3BvbnNlICd5Jy4NCg0KDQojIyBDcmVhdGUgQ2FuZGlkYXRlIE1vZGVscw0KDQoNClRoZSBmdWxsL2luaXRpYWwgbW9kZWwgY29udGFpbmluZyBhbGwgb2YgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMgd2lsbCBiZSBtYWRlIGZpcnN0LCB3aXRoICd5JyAod2hldGhlciBvciBub3QgYSBjbGllbnQgaGFzIHN1YnNjcmliZWQgYSB0ZXJtIGRlcG9zaXQpIGFzIHRoZSByZXNwb25zZS4gVGhlIHZhcmlhYmxlcyBiYWxhbmNlIGFuZCBkZWZhdWx0IGFyZSBub3QgaW5jbHVkZWQgc2luY2Ugb3VyIEVEQSBzaG93ZWQgdGhhdCByZW1vdmluZyB0aGVtIGZyb20gdGhlIG1vZGVsIG1pZ2h0IGhlbHAgdGhlIHJlc3VsdHMuDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KIyBDcmVhdGUgdGhlIGluaXRpYWwgZnVsbCBtb2RlbA0KaW5pdGlhbC5tb2RlbCA9IGdsbSh5IH4gYWdlICsgZGF5ICsgZ3JwLmpvYiArIG1hcml0YWwgKyBlZHVjYXRpb24gKyBob3VzaW5nICsgbG9hbiArIGNvbnRhY3QgKyBncnAuZHVyYXRpb24gKyBncnAuY2FtcGFpZ24gKyBncnAucGRheXMgKyBncnAucHJldmlvdXMsIGZhbWlseSA9IGJpbm9taWFsLCBkYXRhID0gQmFua01hcmtldGluZ0NhbXBhaWduKQ0KY29lZmZpY2llbnQudGFibGUgPSBzdW1tYXJ5KGluaXRpYWwubW9kZWwpJGNvZWYNCmthYmxlKGNvZWZmaWNpZW50LnRhYmxlLCBjYXB0aW9uID0gIlNpZ25pZmljYW5jZSB0ZXN0cyBvZiBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIikNCmBgYA0KV2UgY2FuIHNlZSB0aGF0IHRoZXJlIGFyZSBzb21lIGluc2lnbmlmaWNhbnQgcHJlZGljdG9yIHZhcmlhYmxlcywgYW5kIHRoZXkgc2hvdWxkIGJlIGRyb3BwZWQgZnJvbSB0aGUgbW9kZWwgdG8gY3JlYXRlIGEgcmVkdWNlZCBtb2RlbC4gVXNpbmcgdGhlIHN0ZXAoKSBmdW5jdGlvbiwgd2Ugd2lsbCBub3cgZmluZCAgcmVkdWNlZCBhbmQgZmluYWwgbW9kZWxzLiBUaGUgZmluYWwgYmVzdCBtb2RlbCB3aWxsIGJlIGEgbW9kZWwgdGhhdCBpcyBiZXR3ZWVuIHRoZSBmdWxsIGFuZCByZWR1Y2VkIG1vZGVscy4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIENyZWF0aW5nIHRoZSByZWR1Y2VkIGFuZCBmaW5hbCBtb2RlbHMNCmZ1bGwubW9kZWwgPSBpbml0aWFsLm1vZGVsICAjIHRoZSAqYmlnZ2VzdCBtb2RlbCogdGhhdCBpbmNsdWRlcyBhbGwgcHJlZGljdG9yIHZhcmlhYmxlcw0KcmVkdWNlZC5tb2RlbCA9IGdsbSh5IH4gZGF5ICsgZ3JwLmpvYiArIG1hcml0YWwgKyBob3VzaW5nICsgbG9hbiArIGNvbnRhY3QgKyBncnAuZHVyYXRpb24gKyBncnAuY2FtcGFpZ24gKyBncnAucHJldmlvdXMsIGZhbWlseSA9IGJpbm9taWFsLCBkYXRhID0gQmFua01hcmtldGluZ0NhbXBhaWduKQ0KZmluYWwubW9kZWwgPSAgc3RlcChmdWxsLm1vZGVsLCANCiAgICAgICAgICAgICAgICAgICAgc2NvcGU9bGlzdChsb3dlcj1mb3JtdWxhKHJlZHVjZWQubW9kZWwpLHVwcGVyPWZvcm11bGEoZnVsbC5tb2RlbCkpLA0KICAgICAgICAgICAgICAgICAgICBkYXRhID0gQmFua01hcmtldGluZ0NhbXBhaWduLCANCiAgICAgICAgICAgICAgICAgICAgZGlyZWN0aW9uID0gImJhY2t3YXJkIiwNCiAgICAgICAgICAgICAgICAgICAgdHJhY2UgPSAwKSAgICMgdHJhY2UgPSAwOiBzdXBwcmVzcyB0aGUgZGV0YWlsZWQgc2VsZWN0aW9uIHByb2Nlc3MNCmZpbmFsLm1vZGVsLmNvZWYgPSBzdW1tYXJ5KGZpbmFsLm1vZGVsKSRjb2VmDQprYWJsZShmaW5hbC5tb2RlbC5jb2VmLCBjYXB0aW9uID0gIlN1bW1hcnkgdGFibGUgb2Ygc2lnbmlmaWNhbnQgdGVzdHMiKQ0KYGBgDQojIyBNb2RlbCBTZWxlY3Rpb24NCg0KDQpOb3cgdGhhdCB3ZSBoYXZlIG91ciBjYW5pZGF0ZSBtb2RlbHMuIEl0IGlzIHRpbWUgdG8gcGVyZm9ybSBzb21lIG1vZGVsIHNlbGN0aW9uIHVzaW5nIHRoZSBST0MgY3VydmUgYW5kIEFVQy4NCg0KU2luY2Ugb3VyIHNhbXBsZSBpcyByZWxhdGl2ZWx5IGxhcmdlLCB3ZSB3aWxsIHJhbmRvbWx5IHNwbGl0IHRoZSBvdmVyYWxsIGRhdGEgc2V0IGludG8gdHdvIGRhdGEgc2V0cy4gNzAlIG9mIHRoZSBkYXRhIHdpbGwgYmUgcHV0IGluIGEgdHJhaW5pbmcgZGF0YSBzZXQgZm9yIHRyYWluaW5nIGFuZCB2YWxpZGF0aW5nIG1vZGVscy4gVGhlIG90aGVyIDMwJSBnb2VzIGludG8gYSB0ZXN0aW5nIGRhdGEgc2V0IHVzZWQgZm9yIHRlc3RpbmcgdGhlIGZpbmFsIG1vZGVsLiBUaGUgdmFsdWUgbGFiZWxzIG9mIHRoZSByZXNwb25zZSAoeWVzL25vKSB1c2VkIGZvciB0ZXN0aW5nIGFuZCB2YWxpZGF0aW9uIGRhdGEgd2lsbCBiZSByZW1vdmVkIHdoZW4gY2FsY3VsYXRpbmcgdGhlIGFjY3VyYWN5IG1lYXN1cmVzIGxhdGVyLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMjIFJlY29kZSByZXNwb25zZSB2YXJpYWJsZTogeWVzID0gMSBhbmQgbm8gPSAwDQp5ZXMuaWQgPSB3aGljaChCYW5rTWFya2V0aW5nQ2FtcGFpZ24keSA9PSAiMSIpIA0Kbm8uaWQgPSB3aGljaChCYW5rTWFya2V0aW5nQ2FtcGFpZ24keSA9PSAiMCIpDQoNCiMjIENyZWF0aW5nIHRoZSB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhIHNldHMNCkJhbmtNYXJrZXRpbmdDYW1wYWlnbiR5LnN1YnNjcmliZSA9IDENCkJhbmtNYXJrZXRpbmdDYW1wYWlnbiR5LnN1YnNjcmliZVtuby5pZF0gPSAwDQp2YXIubmFtZXMgPSBjKCJhZ2UiLCAiZGF5IiwgImdycC5qb2IiLCAibWFyaXRhbCIsImVkdWNhdGlvbiIsImhvdXNpbmciLCJsb2FuIiwiY29udGFjdCIsImdycC5tb250aCIsICAgICJncnAuZHVyYXRpb24iLCJncnAuY2FtcGFpZ24iLCJncnAucGRheXMiLCJncnAucHJldmlvdXMiLCAieS5zdWJzY3JpYmUiICkNCkJhbmtNYXJrZXRpbmdDYW1wYWlnbiA9IEJhbmtNYXJrZXRpbmdDYW1wYWlnblssIHZhci5uYW1lc10NCm5uID0gZGltKEJhbmtNYXJrZXRpbmdDYW1wYWlnbilbMV0NCnRyYWluLmlkID0gc2FtcGxlKDE6bm4sIHJvdW5kKG5uKjAuNyksIHJlcGxhY2UgPSBGQUxTRSkgDQojIyMjDQp0cmFpbmluZyA9IEJhbmtNYXJrZXRpbmdDYW1wYWlnblt0cmFpbi5pZCxdDQp0ZXN0aW5nID0gQmFua01hcmtldGluZ0NhbXBhaWduWy10cmFpbi5pZCxdDQpgYGANCg0KIyMgQ3V0LW9mZiBQcm9iYWJpbGl0eSBTZWFyY2ggYW5kIEFjY3VyYWN5IFNjb3JlDQoNCkluIG9yZGVyIHRvIGZpbmQgYW4gb3B0aW1hbCBjdXQtb2ZmIHByb2JhYmlsaXR5LCBhIHNlcXVlbmNlIG9mIDIwIGNhbmRpZGF0ZSBjdXQtb2ZmIHByb2JhYmlsaXRpZXMgd2lsbCBiZSBkZWZpbmVkLiBUaGVuLCBhIDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uIHdpbGwgYmUgcGVyZm9ybWVkIHRvIGZpbmQgdGhlIG9wdGltYWwgY3V0LW9mZiBwcm9iYWJpbGl0eSBvZiB0aGUgZmluYWwgbW9kZWwuIEFsbCB0aHJlZSBtb2RlbHMgY3JlYXRlZCB3aWxsIGJlIHVzZWQgdG8gZmluZCB0aGUgb3B0aW1hbCBjdXQtb2ZmLiBUaGlzIGlzIHNob3duIGJlbG93Lg0KYGBge3IsIGZpZy5hbGlnbj0gJ2NlbnRlcicsIGZpZy5jYXA9IjUtZm9sZCBDViBwZXJmb3JtYW5jZSBwbG90Iiwgd2FybmluZz1GQUxTRX0NCm4wID0gZGltKHRyYWluaW5nKVsxXS81DQoNCiMgY2FuZGlkYXRlIGN1dCBvZmYgcHJvYg0KY3V0LjBmZi5wcm9iID0gc2VxKDAsMSwgbGVuZ3RoID0gMjIpWy1jKDEsMjIpXQ0KDQojIG51bGwgdmVjdG9yIGZvciBzdG9yaW5nIHByZWRpY3Rpb24gYWNjdXJhY3kNCnByZWQuYWNjdXJhY3kgPSBtYXRyaXgoMCxuY29sPTIwLCBucm93PTUsIGJ5cm93ID0gVCkNCg0KIyMgNS1mb2xkIENWDQpmb3IgKGkgaW4gMTo1KXsNCiAgdmFsaWQuaWQgPSAoKGktMSkqbjAgKyAxKTooaSpuMCkNCiAgdmFsaWQuZGF0YSA9IHRyYWluaW5nW3ZhbGlkLmlkLF0NCiAgdHJhaW4uZGF0YSA9IHRyYWluaW5nWy12YWxpZC5pZCxdDQogIHRyYWluLm1vZGVsID0gZ2xtKHkuc3Vic2NyaWJlIH4gYWdlICsgZGF5ICsgZ3JwLmpvYiArIG1hcml0YWwgKyBlZHVjYXRpb24gKyBob3VzaW5nICsgbG9hbiArIGNvbnRhY3QgKyAgZ3JwLmR1cmF0aW9uICsgZ3JwLmNhbXBhaWduICsgZ3JwLnBkYXlzICsgZ3JwLnByZXZpb3VzLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gbG9naXQpLCBkYXRhID0gdHJhaW4uZGF0YSkNCiMjIyMNCiAgcHJlZC5wcm9iID0gcHJlZGljdC5nbG0odHJhaW4ubW9kZWwsIHZhbGlkLmRhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQ0KICAjIGRlZmluZSBjb25mdXNpb24gbWF0cml4IGFuZCBhY2N1cmFjeQ0KICBmb3IoaiBpbiAxOjIwKXsNCiAgICAjcHJlZC5zdWJzY3JpYmUgPSByZXAoMCxsZW5ndGgocHJlZC5wcm9iKSkNCiAgICB2YWxpZC5kYXRhJHByZWQuc3Vic2NyaWJlID0gYXMubnVtZXJpYyhwcmVkLnByb2IgPiBjdXQuMGZmLnByb2Jbal0pDQogICAgYTExID0gc3VtKHZhbGlkLmRhdGEkcHJlZC5zdWJzY3JpYmUgPT0gdmFsaWQuZGF0YSR5LnN1YnNjcmliZSkNCiAgICBwcmVkLmFjY3VyYWN5W2ksal0gPSBhMTEvbGVuZ3RoKHByZWQucHJvYikNCiAgfQ0KfQ0KIyMNCmF2Zy5hY2N1cmFjeSA9IGFwcGx5KHByZWQuYWNjdXJhY3ksIDIsIG1lYW4pDQptYXguaWQgPSB3aGljaChhdmcuYWNjdXJhY3kgPT1tYXgoYXZnLmFjY3VyYWN5KSkNCg0KIyMjIHZpc3VhbCByZXByZXNlbnRhdGlvbg0KdGljay5sYWJlbCA9IGFzLmNoYXJhY3Rlcihyb3VuZChjdXQuMGZmLnByb2IsMikpDQpwbG90KDE6MjAsIGF2Zy5hY2N1cmFjeSwgdHlwZSA9ICJiIiwNCiAgICAgeGxpbT1jKDEsMjApLCANCiAgICAgeWxpbT1jKDAuNSwxKSwgDQogICAgIGF4ZXMgPSBGQUxTRSwNCiAgICAgeGxhYiA9ICJDdXQtb2ZmIFByb2JhYmlsaXR5IiwNCiAgICAgeWxhYiA9ICJBY2N1cmFjeSIsDQogICAgIG1haW4gPSAiNS1mb2xkIENWIHBlcmZvcm1hbmNlIg0KICAgICApDQpheGlzKDEsIGF0PTE6MjAsIGxhYmVsID0gdGljay5sYWJlbCwgbGFzID0gMikNCmF4aXMoMikNCnNlZ21lbnRzKG1heC5pZCwgMC41LCBtYXguaWQsIGF2Zy5hY2N1cmFjeVttYXguaWRdLCBjb2wgPSAicmVkIikNCnRleHQobWF4LmlkLCBhdmcuYWNjdXJhY3lbbWF4LmlkXSswLjAzLCBhcy5jaGFyYWN0ZXIocm91bmQoYXZnLmFjY3VyYWN5W21heC5pZF0sNCkpLCBjb2wgPSAicmVkIiwgY2V4ID0gMC44KQ0KYGBgDQoNClRoZSBhYm92ZSBmaWd1cmUgaW5kaWNhdGVzIHRoYXQgdGhlIG9wdGltYWwgY3V0LW9mZiBwcm9iYWJpbGl0eSB0aGF0IHlpZWxkcyB0aGUgYmVzdCBhY2N1cmFjeSBpcyAwLjU3Lg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMjIENyZWF0aW9uIG9mIHRlc3RpbmcgbW9kZWwNCnRlc3QubW9kZWwgPSBnbG0oeS5zdWJzY3JpYmUgfiBhZ2UgKyBkYXkgKyBncnAuam9iICsgbWFyaXRhbCArIGVkdWNhdGlvbiArIGhvdXNpbmcgKyBsb2FuICsgY29udGFjdCArIGdycC5kdXJhdGlvbiArIGdycC5jYW1wYWlnbiArIGdycC5wZGF5cyArIGdycC5wcmV2aW91cywgZmFtaWx5ID0gYmlub21pYWwobGluayA9IGxvZ2l0KSwgZGF0YSA9IHRyYWluaW5nKQ0KbmV3QmFua2luZ1Rlc3RpbmdEYXRhID0gZGF0YS5mcmFtZShhZ2U9IHRlc3RpbmckYWdlLCBkYXk9IHRlc3RpbmckZGF5LCBncnAuam9iPSB0ZXN0aW5nJGdycC5qb2IsIG1hcml0YWw9IHRlc3RpbmckbWFyaXRhbCwgZWR1Y2F0aW9uPSB0ZXN0aW5nJGVkdWNhdGlvbiwgaG91c2luZz0gdGVzdGluZyRob3VzaW5nLCBsb2FuPSB0ZXN0aW5nJGxvYW4sIGNvbnRhY3Q9IHRlc3RpbmckY29udGFjdCwgZ3JwLm1vbnRoPSB0ZXN0aW5nJGdycC5tb250aCwgZ3JwLmR1cmF0aW9uPSB0ZXN0aW5nJGdycC5kdXJhdGlvbiwgZ3JwLmNhbXBhaWduPSB0ZXN0aW5nJGdycC5jYW1wYWlnbiwgZ3JwLnBkYXlzPSB0ZXN0aW5nJGdycC5wZGF5cywgZ3JwLnByZXZpb3VzPSB0ZXN0aW5nJGdycC5wcmV2aW91cykNCg0KcHJlZC5wcm9iLnRlc3QgPSBwcmVkaWN0LmdsbSh0ZXN0Lm1vZGVsLCBuZXdCYW5raW5nVGVzdGluZ0RhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQojIyBBc3Nlc3NpbmcgTW9kZWwgQWNjdXJhY3kNCnRlc3RpbmckdGVzdC5zdWJzY3JpYmUgPSBhcy5udW1lcmljKHByZWQucHJvYi50ZXN0ID4gMC42MikNCmExMSA9IHN1bSh0ZXN0aW5nJHRlc3Quc3Vic2NyaWJlID09IHRlc3RpbmckeS5zdWJzY3JpYmUpDQp0ZXN0LmFjY3VyYWN5ID0gYTExL2xlbmd0aChwcmVkLnByb2IudGVzdCkNCmthYmxlKGFzLmRhdGEuZnJhbWUodGVzdC5hY2N1cmFjeSksIGFsaWduPSdjJykNCmBgYA0KSGVyZSBpbiBvdXIgYWNjdXJhY3kgdGVzdCB3ZSBmaW5kIHRoYXQgaXQgaXMgYWNjdXJhdGUgODguNyUgb2YgdGhlIHRpbWUuIFRoaXMgaW5kY2F0ZXMgdGhlcmUgaXMgbm8gdW5kZXItZml0dGluZyBmb3Igb3VyIG1vZGVsLg0KDQojIyBMb2NhbCBhbmQgR2xvYmFsIFJPQyBNZXRyaWNzDQoNClVzaW5nIHRoZSBvcHRpbWFsIGN1dC1vZmYgcHJvYmFiaWxpdHkgb2YgMC41Nywgd2hpY2ggd2FzIGZvdW5kIGFib3ZlLCB3ZSB3aWxsIG5vdyByZXBvcnQgdGhlIGxvY2FsIG1lYXN1cmVzIHVzaW5nIG91ciB0ZXN0aW5nIGRhdGEuIFRoaXMgaW5jbHVkZXMgc3BlY2lmaWNpdHkgYW5kIHNlbnNpdGl2aXR5IGJhc2VkIG9uIGVhY2ggb2YgdGhlc2UgY3V0LW9mZnMgZm9yIHRoZSAyMCBzdWItaW50ZXJ2YWxzLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMgTG9va2luZyBhdCBzZW5zaXRpdml0eSBhbmQgc3BlY2lmaWNpdHkgcGVyZm9ybWFuY2UgbWVhc3VyZW1lbnRzDQp0ZXN0aW5nJHRlc3Quc3Vic2NyaWJlID0gYXMubnVtZXJpYyhwcmVkLnByb2IudGVzdCA+IDAuNTcpDQojIyMgY29tcG9uZW50cyBmb3IgZGVmaW5pbmcgdmFyaW91cyBtZWFzdXJlcw0KcDAuYTAgPSBzdW0odGVzdGluZyR0ZXN0LnN1YnNjcmliZSA9PTAgJiB0ZXN0aW5nJHkuc3Vic2NyaWJlID09MCkNCnAwLmExID0gc3VtKHRlc3RpbmckdGVzdC5zdWJzY3JpYmUgPT0wICYgdGVzdGluZyR5LnN1YnNjcmliZSA9PTEpDQpwMS5hMCA9IHN1bSh0ZXN0aW5nJHRlc3Quc3Vic2NyaWJlID09MSAmIHRlc3RpbmckeS5zdWJzY3JpYmUgPT0wKQ0KcDEuYTEgPSBzdW0odGVzdGluZyR0ZXN0LnN1YnNjcmliZSA9PTEgJiB0ZXN0aW5nJHkuc3Vic2NyaWJlID09MSkNCiMjIw0Kc2Vuc2l0aXZpdHkgPSBwMS5hMSAvIChwMS5hMSArIHAwLmExKQ0Kc3BlY2lmaWNpdHkgPSBwMC5hMCAvIChwMC5hMCArIHAxLmEwKQ0KIyMjDQpwcmVjaXNpb24gPSBwMS5hMSAvIChwMS5hMSArIHAxLmEwKQ0KcmVjYWxsID0gc2Vuc2l0aXZpdHkNCkYxID0gMipwcmVjaXNpb24qcmVjYWxsLyhwcmVjaXNpb24gKyByZWNhbGwpDQptZXRyaWMubGlzdCA9IGNiaW5kKHNlbnNpdGl2aXR5ID0gc2Vuc2l0aXZpdHksIA0KICAgICAgICAgICAgICAgICAgICBzcGVjaWZpY2l0eSA9IHNwZWNpZmljaXR5LCANCiAgICAgICAgICAgICAgICAgICAgcHJlY2lzaW9uID0gcHJlY2lzaW9uLA0KICAgICAgICAgICAgICAgICAgICByZWNhbGwgPSByZWNhbGwsDQogICAgICAgICAgICAgICAgICAgIEYxID0gRjEpDQprYWJsZShhcy5kYXRhLmZyYW1lKG1ldHJpYy5saXN0KSwgYWxpZ249J2MnLCBjYXB0aW9uID0gIkxvY2FsIHBlcmZvcm1hbmNlIG1ldHJpY3MiKQ0KYGBgDQoNClRoZSBzZW5zaXRpdml0eSBpbmRpY2F0ZXMgdGhlIHByb2JhYmlsaXR5IG9mIHRob3NlIGNsaWVudHMgd2hvIGFyZSBzYWlkIHRvIGhhdmUgc3Vic2NyaWJlZCBhIHRlcm0gZGVwb3NpdCBhdCB0aGUgYmFua2luZyBpbnN0aXR1dGlvbiBvdXQgb2YgdGhvc2Ugd2hvIGFjdHVhbGx5IGRpZCBpcyBhYm91dCA4LTEyJS4gVGhlIHNwZWNpZmljaXR5IGluZGljYXRlcyB0aGUgcHJvYmFiaWxpdHkgb2YgdGhvc2UgY2xpZW50cyB3aG8gYXJlIHNhaWQgdG8gaGF2ZSBub3Qgc3Vic2NyaWJlZCBhIHRlcm0gZGVwb3NpdCBhdCB0aGUgYmFua2luZyBpbnN0aXR1dGlvbiBvdXQgb2YgdGhvc2Ugd2hvIGFjdHVhbGx5IGRpZCBub3QgaXMgYWJvdXQgOTguOCUuIA0KDQojIyMgUk9DIEdsb2JhbCBNZWFzdXJlIEFuYWx5c2lzDQoNCkZvciB0aGUgbGFzdCBwYXJ0IG9mIHRoaXMgc2VjdGlvbiwgYSBST0MgKHJlY2VpdmVyIG9wZXJhdGluZyBjaGFyYWN0ZXJpc3RpYykgY3VydmUgd2lsbCBiZSBwbG90dGVkIGJ5IHNlbGVjdGluZyBhIHNlcXVlbmNlIG9mIGRlY2lzaW9uIHRocmVzaG9sZHMgYW5kIGNhbGN1bGF0aW5nIGNvcnJlc3BvbmRpbmcgc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5LiANCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIENyZWF0aW5nIGEgZmluYWwgbW9kZWwgUk9DIGN1cnZlIGZvciBzZW5zaXRpdml0eSBhbmQgKDEtc3BlY2lmaWNpdHkpDQpjdXQub2ZmLnNlcSA9IHNlcSgwLjAxLCAwLjk5LCBsZW5ndGggPSAxMDApDQpzZW5zaXRpdml0eS52ZWMgPSBOVUxMDQpzcGVjaWZpY2l0eS52ZWMgPSBOVUxMDQpmb3IgKGkgaW4gMToxMDApew0KICB0ZXN0aW5nJHRlc3Quc3Vic2NyaWJlID0gYXMubnVtZXJpYyhwcmVkLnByb2IudGVzdCA+IGN1dC5vZmYuc2VxW2ldKQ0KIyMjIGNvbXBvbmVudHMgZm9yIGRlZmluaW5nIHZhcmlvdXMgbWVhc3VyZXMNCnAwLmEwID0gc3VtKHRlc3RpbmckdGVzdC5zdWJzY3JpYmUgPT0wICYgdGVzdGluZyR5LnN1YnNjcmliZSA9PTApDQpwMC5hMSA9IHN1bSh0ZXN0aW5nJHRlc3Quc3Vic2NyaWJlID09MCAmIHRlc3RpbmckeS5zdWJzY3JpYmUgPT0xKQ0KcDEuYTAgPSBzdW0odGVzdGluZyR0ZXN0LnN1YnNjcmliZSA9PTEgJiB0ZXN0aW5nJHkuc3Vic2NyaWJlID09MCkNCnAxLmExID0gc3VtKHRlc3RpbmckdGVzdC5zdWJzY3JpYmUgPT0xICYgdGVzdGluZyR5LnN1YnNjcmliZSA9PTEpDQojIyMNCnNlbnNpdGl2aXR5LnZlY1tpXSA9IHAxLmExIC8gKHAxLmExICsgcDAuYTEpDQpzcGVjaWZpY2l0eS52ZWNbaV0gPSBwMC5hMCAvIChwMC5hMCArIHAxLmEwKQ0KfQ0Kb25lLm1pbnVzLnNwZWMgPSBjKDEsMSAtIHNwZWNpZmljaXR5LnZlYykNCnNlbnMudmVjID0gYygxLHNlbnNpdGl2aXR5LnZlYykNCiMjDQpwYXIocHR5ID0gInMiKSAgICMgbWFrZSBhIHNxdWFyZSBmaWd1cmUNCnBsb3Qob25lLm1pbnVzLnNwZWMsIHNlbnMudmVjLCB0eXBlID0gImwiLCB4bGltID0gYygwLDEpLA0KICAgICB4bGFiID0iMSAtIHNwZWNpZmljaXR5IiwNCiAgICAgeWxhYiA9ICJzZW5zaXRpdml0eSIsDQogICAgIG1haW4gPSAiUk9DIGN1cnZlIG9mIExvZ2lzdGljIFRlcm0gRGVwb3NpdCBTdWJzY3JpcHRpb24gRmluYWwgTW9kZWwiLA0KICAgICBsd2QgPSAyLA0KICAgICBjb2wgPSAiYmx1ZSIsICkNCnNlZ21lbnRzKDAsMCwxLDEsIGNvbCA9ICJyZWQiLCBsdHkgPSAyLCBsd2QgPSAyKQ0KQVVDID0gcm91bmQoc3VtKHNlbnMudmVjKihvbmUubWludXMuc3BlY1stMTAxXS1vbmUubWludXMuc3BlY1stMV0pKSw0KQ0KdGV4dCgwLjgsIDAuMywgcGFzdGUoIkFVQyA9ICIsIEFVQyksIGNvbCA9ICJibHVlIikNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMgQ3JlYXRpbmcgYW4gaW5pdGlhbCBtb2RlbCBST0MgY3VydmUgZm9yIHNlbnNpdGl2aXR5IGFuZCAoMS1zcGVjaWZpY2l0eSkNCiMjIDUtZm9sZCBDVg0KZm9yIChpIGluIDE6NSl7DQogIHZhbGlkLmlkID0gKChpLTEpKm4wICsgMSk6KGkqbjApDQogIHZhbGlkLmRhdGEgPSB0cmFpbmluZ1t2YWxpZC5pZCxdDQogIHRyYWluLmRhdGEgPSB0cmFpbmluZ1stdmFsaWQuaWQsXQ0KICB0cmFpbi5tb2RlbCA9IGdsbSh5LnN1YnNjcmliZSB+IGFnZSArIGRheSArIGdycC5qb2IgKyBtYXJpdGFsICsgZWR1Y2F0aW9uICsgaG91c2luZyArIGxvYW4gKyBjb250YWN0ICsgZ3JwLmR1cmF0aW9uICsgZ3JwLmNhbXBhaWduICsgZ3JwLnBkYXlzICsgZ3JwLnByZXZpb3VzLCBmYW1pbHkgPSBiaW5vbWlhbCwgZGF0YSA9IHRyYWluLmRhdGEpDQojIyMjDQogIHByZWQucHJvYiA9IHByZWRpY3QuZ2xtKHRyYWluLm1vZGVsLCB2YWxpZC5kYXRhLCB0eXBlID0gInJlc3BvbnNlIikNCiAgIyBkZWZpbmUgY29uZnVzaW9uIG1hdHJpeCBhbmQgYWNjdXJhY3kNCiAgZm9yKGogaW4gMToyMCl7DQogICAgI3ByZWQuc3Vic2NyaWJlID0gcmVwKDAsbGVuZ3RoKHByZWQucHJvYikpDQogICAgdmFsaWQuZGF0YSRwcmVkLnN1YnNjcmliZSA9IGFzLm51bWVyaWMocHJlZC5wcm9iID4gY3V0LjBmZi5wcm9iW2pdKQ0KICAgIGExMSA9IHN1bSh2YWxpZC5kYXRhJHByZWQuc3Vic2NyaWJlID09IHZhbGlkLmRhdGEkeS5zdWJzY3JpYmUpDQogICAgcHJlZC5hY2N1cmFjeVtpLGpdID0gYTExL2xlbmd0aChwcmVkLnByb2IpDQogIH0NCn0NCg0KdGVzdC5tb2RlbCA9IGdsbSh5LnN1YnNjcmliZSB+IGFnZSArIGRheSArIGdycC5qb2IgKyBtYXJpdGFsICsgZWR1Y2F0aW9uICsgaG91c2luZyArIGxvYW4gKyBjb250YWN0ICsgZ3JwLmR1cmF0aW9uICsgZ3JwLmNhbXBhaWduICsgZ3JwLnBkYXlzICsgZ3JwLnByZXZpb3VzLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gbG9naXQpLCBkYXRhID0gdHJhaW5pbmcpDQpuZXdCYW5raW5nVGVzdGluZ0RhdGEgPSBkYXRhLmZyYW1lKGFnZT0gdGVzdGluZyRhZ2UsIGRheT0gdGVzdGluZyRkYXksIGdycC5qb2I9IHRlc3RpbmckZ3JwLmpvYiwgbWFyaXRhbD0gdGVzdGluZyRtYXJpdGFsLCBlZHVjYXRpb249IHRlc3RpbmckZWR1Y2F0aW9uLCBob3VzaW5nPSB0ZXN0aW5nJGhvdXNpbmcsIGxvYW49IHRlc3RpbmckbG9hbiwgY29udGFjdD0gdGVzdGluZyRjb250YWN0LCBncnAuZHVyYXRpb249IHRlc3RpbmckZ3JwLmR1cmF0aW9uLCBncnAuY2FtcGFpZ249IHRlc3RpbmckZ3JwLmNhbXBhaWduLCBncnAucGRheXM9IHRlc3RpbmckZ3JwLnBkYXlzLCBncnAucHJldmlvdXM9IHRlc3RpbmckZ3JwLnByZXZpb3VzKQ0KDQpwcmVkLnByb2IudGVzdCA9IHByZWRpY3QuZ2xtKHRlc3QubW9kZWwsIG5ld0JhbmtpbmdUZXN0aW5nRGF0YSwgdHlwZSA9ICJyZXNwb25zZSIpDQoNCmN1dC5vZmYuc2VxID0gc2VxKDAuMDEsIDAuOTksIGxlbmd0aCA9IDEwMCkNCnNlbnNpdGl2aXR5LnZlYyA9IE5VTEwNCnNwZWNpZmljaXR5LnZlYyA9IE5VTEwNCmZvciAoaSBpbiAxOjEwMCl7DQogIHRlc3RpbmckdGVzdC5zdWJzY3JpYmUgPSBhcy5udW1lcmljKHByZWQucHJvYi50ZXN0ID4gY3V0Lm9mZi5zZXFbaV0pDQojIyMgY29tcG9uZW50cyBmb3IgZGVmaW5pbmcgdmFyaW91cyBtZWFzdXJlcw0KcDAuYTAgPSBzdW0odGVzdGluZyR0ZXN0LnN1YnNjcmliZSA9PTAgJiB0ZXN0aW5nJHkuc3Vic2NyaWJlID09MCkNCnAwLmExID0gc3VtKHRlc3RpbmckdGVzdC5zdWJzY3JpYmUgPT0wICYgdGVzdGluZyR5LnN1YnNjcmliZSA9PTEpDQpwMS5hMCA9IHN1bSh0ZXN0aW5nJHRlc3Quc3Vic2NyaWJlID09MSAmIHRlc3RpbmckeS5zdWJzY3JpYmUgPT0wKQ0KcDEuYTEgPSBzdW0odGVzdGluZyR0ZXN0LnN1YnNjcmliZSA9PTEgJiB0ZXN0aW5nJHkuc3Vic2NyaWJlID09MSkNCiMjIw0Kc2Vuc2l0aXZpdHkudmVjW2ldID0gcDEuYTEgLyAocDEuYTEgKyBwMC5hMSkNCnNwZWNpZmljaXR5LnZlY1tpXSA9IHAwLmEwIC8gKHAwLmEwICsgcDEuYTApDQp9DQpvbmUubWludXMuc3BlYyA9IGMoMSwxIC0gc3BlY2lmaWNpdHkudmVjKQ0Kc2Vucy52ZWMgPSBjKDEsc2Vuc2l0aXZpdHkudmVjKQ0KIyMNCnBhcihwdHkgPSAicyIpICAgIyBtYWtlIGEgc3F1YXJlIGZpZ3VyZQ0KcGxvdChvbmUubWludXMuc3BlYywgc2Vucy52ZWMsIHR5cGUgPSAibCIsIHhsaW0gPSBjKDAsMSksDQogICAgIHhsYWIgPSIxIC0gc3BlY2lmaWNpdHkiLA0KICAgICB5bGFiID0gInNlbnNpdGl2aXR5IiwNCiAgICAgbWFpbiA9ICJST0MgY3VydmUgb2YgTG9naXN0aWMgVGVybSBEZXBvc2l0IFN1YnNjcmlwdGlvbiBJbml0aWFsIE1vZGVsIiwNCiAgICAgbHdkID0gMiwNCiAgICAgY29sID0gImJsdWUiLCApDQpzZWdtZW50cygwLDAsMSwxLCBjb2wgPSAicmVkIiwgbHR5ID0gMiwgbHdkID0gMikNCkFVQyA9IHJvdW5kKHN1bShzZW5zLnZlYyoob25lLm1pbnVzLnNwZWNbLTEwMV0tb25lLm1pbnVzLnNwZWNbLTFdKSksNCkNCnRleHQoMC44LCAwLjMsIHBhc3RlKCJBVUMgPSAiLCBBVUMpLCBjb2wgPSAiYmx1ZSIpDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIENyZWF0aW5nIGEgcmVkdWNlZCBtb2RlbCBST0MgY3VydmUgZm9yIHNlbnNpdGl2aXR5IGFuZCAoMS1zcGVjaWZpY2l0eSkNCiMjIDUtZm9sZCBDVg0KZm9yIChpIGluIDE6NSl7DQogIHZhbGlkLmlkID0gKChpLTEpKm4wICsgMSk6KGkqbjApDQogIHZhbGlkLmRhdGEgPSB0cmFpbmluZ1t2YWxpZC5pZCxdDQogIHRyYWluLmRhdGEgPSB0cmFpbmluZ1stdmFsaWQuaWQsXQ0KICB0cmFpbi5tb2RlbCA9IGdsbSh5LnN1YnNjcmliZSB+IGRheSArIGdycC5qb2IgKyBtYXJpdGFsICsgaG91c2luZyArIGxvYW4gKyBjb250YWN0ICsgZ3JwLmR1cmF0aW9uICsgZ3JwLmNhbXBhaWduICsgZ3JwLnByZXZpb3VzLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gbG9naXQpLCBkYXRhID0gdHJhaW4uZGF0YSkNCiMjIyMNCiAgcHJlZC5wcm9iID0gcHJlZGljdC5nbG0odHJhaW4ubW9kZWwsIHZhbGlkLmRhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQ0KICAjIGRlZmluZSBjb25mdXNpb24gbWF0cml4IGFuZCBhY2N1cmFjeQ0KICBmb3IoaiBpbiAxOjIwKXsNCiAgICAjcHJlZC5zdWJzY3JpYmUgPSByZXAoMCxsZW5ndGgocHJlZC5wcm9iKSkNCiAgICB2YWxpZC5kYXRhJHByZWQuc3Vic2NyaWJlID0gYXMubnVtZXJpYyhwcmVkLnByb2IgPiBjdXQuMGZmLnByb2Jbal0pDQogICAgYTExID0gc3VtKHZhbGlkLmRhdGEkcHJlZC5zdWJzY3JpYmUgPT0gdmFsaWQuZGF0YSR5LnN1YnNjcmliZSkNCiAgICBwcmVkLmFjY3VyYWN5W2ksal0gPSBhMTEvbGVuZ3RoKHByZWQucHJvYikNCiAgfQ0KfQ0KDQp0ZXN0Lm1vZGVsID0gZ2xtKHkuc3Vic2NyaWJlIH4gZGF5ICsgZ3JwLmpvYiArIG1hcml0YWwgKyBob3VzaW5nICsgbG9hbiArIGNvbnRhY3QgKyBncnAuZHVyYXRpb24gKyBncnAuY2FtcGFpZ24gKyBncnAucHJldmlvdXMsIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSBsb2dpdCksIGRhdGEgPSB0cmFpbmluZykNCm5ld0JhbmtpbmdUZXN0aW5nRGF0YSA9IGRhdGEuZnJhbWUoZGF5PSB0ZXN0aW5nJGRheSwgZ3JwLmpvYj0gdGVzdGluZyRncnAuam9iLCBtYXJpdGFsPSB0ZXN0aW5nJG1hcml0YWwsIGhvdXNpbmc9IHRlc3RpbmckaG91c2luZywgbG9hbj0gdGVzdGluZyRsb2FuLCBjb250YWN0PSB0ZXN0aW5nJGNvbnRhY3QsIGdycC5kdXJhdGlvbj0gdGVzdGluZyRncnAuZHVyYXRpb24sIGdycC5jYW1wYWlnbj0gdGVzdGluZyRncnAuY2FtcGFpZ24sIGdycC5wcmV2aW91cz0gdGVzdGluZyRncnAucHJldmlvdXMpDQoNCnByZWQucHJvYi50ZXN0ID0gcHJlZGljdC5nbG0odGVzdC5tb2RlbCwgbmV3QmFua2luZ1Rlc3RpbmdEYXRhLCB0eXBlID0gInJlc3BvbnNlIikNCg0KY3V0Lm9mZi5zZXEgPSBzZXEoMC4wMSwgMC45OSwgbGVuZ3RoID0gMTAwKQ0Kc2Vuc2l0aXZpdHkudmVjID0gTlVMTA0Kc3BlY2lmaWNpdHkudmVjID0gTlVMTA0KZm9yIChpIGluIDE6MTAwKXsNCiAgdGVzdGluZyR0ZXN0LnN1YnNjcmliZSA9IGFzLm51bWVyaWMocHJlZC5wcm9iLnRlc3QgPiBjdXQub2ZmLnNlcVtpXSkNCiMjIyBjb21wb25lbnRzIGZvciBkZWZpbmluZyB2YXJpb3VzIG1lYXN1cmVzDQpwMC5hMCA9IHN1bSh0ZXN0aW5nJHRlc3Quc3Vic2NyaWJlID09MCAmIHRlc3RpbmckeS5zdWJzY3JpYmUgPT0wKQ0KcDAuYTEgPSBzdW0odGVzdGluZyR0ZXN0LnN1YnNjcmliZSA9PTAgJiB0ZXN0aW5nJHkuc3Vic2NyaWJlID09MSkNCnAxLmEwID0gc3VtKHRlc3RpbmckdGVzdC5zdWJzY3JpYmUgPT0xICYgdGVzdGluZyR5LnN1YnNjcmliZSA9PTApDQpwMS5hMSA9IHN1bSh0ZXN0aW5nJHRlc3Quc3Vic2NyaWJlID09MSAmIHRlc3RpbmckeS5zdWJzY3JpYmUgPT0xKQ0KIyMjDQpzZW5zaXRpdml0eS52ZWNbaV0gPSBwMS5hMSAvIChwMS5hMSArIHAwLmExKQ0Kc3BlY2lmaWNpdHkudmVjW2ldID0gcDAuYTAgLyAocDAuYTAgKyBwMS5hMCkNCn0NCm9uZS5taW51cy5zcGVjID0gYygxLDEgLSBzcGVjaWZpY2l0eS52ZWMpDQpzZW5zLnZlYyA9IGMoMSxzZW5zaXRpdml0eS52ZWMpDQojIw0KcGFyKHB0eSA9ICJzIikgICAjIG1ha2UgYSBzcXVhcmUgZmlndXJlDQpwbG90KG9uZS5taW51cy5zcGVjLCBzZW5zLnZlYywgdHlwZSA9ICJsIiwgeGxpbSA9IGMoMCwxKSwNCiAgICAgeGxhYiA9IjEgLSBzcGVjaWZpY2l0eSIsDQogICAgIHlsYWIgPSAic2Vuc2l0aXZpdHkiLA0KICAgICBtYWluID0gIlJPQyBjdXJ2ZSBvZiBMb2dpc3RpYyBUZXJtIERlcG9zaXQgU3Vic2NyaXB0aW9uIFJlZHVjZWQgTW9kZWwiLA0KICAgICBsd2QgPSAyLA0KICAgICBjb2wgPSAiYmx1ZSIsICkNCnNlZ21lbnRzKDAsMCwxLDEsIGNvbCA9ICJyZWQiLCBsdHkgPSAyLCBsd2QgPSAyKQ0KQVVDID0gcm91bmQoc3VtKHNlbnMudmVjKihvbmUubWludXMuc3BlY1stMTAxXS1vbmUubWludXMuc3BlY1stMV0pKSw0KQ0KdGV4dCgwLjgsIDAuMywgcGFzdGUoIkFVQyA9ICIsIEFVQyksIGNvbCA9ICJibHVlIikNCmBgYA0KDQpUaGUgYXJlYSB1bmRlciB0aGUgY3VydmUgKEFVQykgZm9yIHRoZSByZWR1Y2VkIG1vZGVsIGFuZCB0aGUgUk9DIGN1cnZlIGlzIGxlc3MgdGhhbiB0aGUgb3RoZXIgdHdvIGdyYXBocy4gSGlnaGVyIEFVQyBpbmRpY2F0ZXMgdGhlIG1vZGVsIGZvciB0aGF0IGN1cnZlIGlzIGJldHRlci4gVGhlcmVmb3JlLCB0aGUgcmVkdWNlZCBtb2RlbCBpcyBub3QgdGhlIGJlc3QgbW9kZWwgdG8gdXNlIGFuZCBpcyBubyBsb25nZXIgY29uc2lkZXJlZC4gQnV0LCBpdCBzaG91bGQgYmUgbm90ZWQgdGhhdCBpdCBpcyBub3QgZmFyIG9mZiB0aGUgb3RoZXIgMiBtb2RlbHMuDQoNCkxvb2tpbmcgYXQgdGhlIGluaXRpYWwgYW5kIGZpbmFsIG1vZGVscywgdGhleSBoYXZlIHRoZSBzYW1lIGN1cnZlIHNpbmNlIGJvdGggbW9kZWxzIGNvbnRhaW4gYWxsIGZlYXR1cmUgdmFyaWFibGVzIHVzZWQgaW4gdGhlIGluaXRpYWwgbW9kZWwuIE91dCBvZiB0aGVzZSBvdGhlciB0d28gbW9kZWxzLCB0aGUgZmluYWwgbW9kZWwgd29ya3MgYmV0dGVyIGNvbXBhcmVkIHRvIHRoZSBpbml0aWFsIG1vZGVsLiBJdCBoYXMgYmVlbiBwcm92ZW4gdG8gYmUgYWNjdXJhdGUgaW4gbW9kZWxpbmcgcGVyZm9ybWFuY2UsIGhhcyBoaWdoIHNwZWNpZmljaXR5LCBhbmQgaXRzIFJPQyBjdXJ2ZSBpcyByZW1haW5pbmcgYXdheSBmcm9tIHRoZSA0NSBkZWdyZWVzIG1hcmsuIFBsdXMsIHRoZSBBVUMgaXMgZmFpcmx5IGhpZ2ggYXQgLjg1MjUsIGV2ZW4gdGhvdWdoIHRoZSBpbml0aWFsIG1vZGVsIGhhcyB0aGUgc2FtZSBzY29yZS4NCiMjIFJlc3VsdHMgYW5kIENvbmNsdXNpb24NCk5vdyB0aGF0IHdlIGhhdmUgY2hvc2VkIHRoZSBiZXN0IG1vZGVsIG91dCBvZiB0aGUgdGhyZWUgY2FuZGlkYXRlIG1vZGVscywgd2UgY2FuIHVzZSBpdCB0byBwcmVkaWN0IHdoZXRoZXIgb3Igbm90IGEgY2xpZW50IGhhcyBzdWJzY3JpYmVkIGEgdGVybSBkZXBvc2l0LiBVc2luZyBvdXQgb3B0aW1hbCBjdXQtb2ZmIG9mIC41Nw0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMgUHJlZGljdGluZyBSZXNwb25zZSBWYWx1ZSBmb3IgQmFua2luZyBDbGllbnQgR2l2ZW4gVmFyaWFibGUgVmFsdWVzIGZvciB0aGUgRmluYWwgTW9kZWwNCnBkYXRhID0gZGF0YS5mcmFtZShhZ2U9YygyNSw2NCksDQogICAgICAgICAgICAgICAgICAgICAgIGRheSA9IGMoNSw1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgZ3JwLmpvYiA9IGMoIjEiLCIyIiksDQogICAgICAgICAgICAgICAgICAgICAgIG1hcml0YWwgPSBjKCIwIiwiMSIpLA0KICAgICAgICAgICAgICAgICAgICAgICBlZHVjYXRpb24gPSBjKCIyIiwiMCIpLA0KICAgICAgICAgICAgICAgICAgICAgICBob3VzaW5nID0gYygiMSIsIjEiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgbG9hbiA9IGMoIjAiLCIxIiksDQogICAgICAgICAgICAgICAgICAgICAgIGNvbnRhY3QgPSBjKCIwIiwiMCIpLA0KICAgICAgICAgICAgICAgICAgICAgICBncnAuZHVyYXRpb24gPSBjKCIwIiwiMSIpLA0KICAgICAgICAgICAgICAgICAgICAgICBncnAuY2FtcGFpZ24gPSBjKCIxIiwiMCIpLA0KICAgICAgICAgICAgICAgICAgICAgICBncnAucGRheXMgPSBjKCIxIiwiMiIpLA0KICAgICAgICAgICAgICAgICAgICAgIHBvdXRjb21lPWMoIjAiLCIwIiksDQogICAgICAgICAgICAgICAgICAgICAgIGdycC5wcmV2aW91cyA9IGMoIjEiLCIyIikpDQogICAgICAgICAgDQoNCg0KDQpwcmVkLnN1Y2Nlc3MucHJvYiA9IHByZWRpY3QoZmluYWwubW9kZWwsIG5ld2RhdGEgPSBwZGF0YSwgdHlwZT0icmVzcG9uc2UiKQ0KDQojIyB0aHJlc2hvbGQgcHJvYmFiaWxpdHkNCmN1dC5vZmYucHJvYiA9IDAuNTcNCnByZWQucmVzcG9uc2UgPSBpZmVsc2UocHJlZC5zdWNjZXNzLnByb2IgPiBjdXQub2ZmLnByb2IsIDEsIDApICAjIFRoaXMgcHJlZGljdHMgdGhlIHJlc3BvbnNlDQpwcmVkLnJlc3BvbnNlDQojIEFkZCB0aGUgbmV3IHByZWRpY3RlZCByZXNwb25zZSB0byBwZGF0YQ0KDQojcGRhdGEkcHJlZC5yZXNwb25zZSA8LSBwcmVkaWN0KGZpbmFsLm1vZGVsLCBuZXdkYXRhID0gcGRhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQ0KcGRhdGEkUHJlZC5SZXNwb25zZSA9IHByZWQucmVzcG9uc2UNCmthYmxlKHBkYXRhLCBjYXB0aW9uID0gIlByZWRpY3RlZCBWYWx1ZSBvZiByZXNwb25zZSB2YXJpYWJsZSB3aXRoIHRoZSBnaXZlbiBjdXQtb2ZmIHByb2JhYmlsaXR5IikNCmBgYA0KDQpZb3UgY2FuIHNlZSB0aGF0IG5laXRoZXIgb2YgdGhlIHR3byBvYnNlcnZhdGlvbnMgd2lsbCBiZSBzdWJzY3JpYmluZy4gTW9yZSB0ZXN0aW5nIGNhbiBiZSBkb25lIHdpdGggdGhlIHRlc3RpbmcgZGF0YXNldC4NCg0KDQojIFN1bW1hcnkgYW5kIERpc2N1c3Npb24NCg0KT3ZlcmFsbCBpbiB0aGlzIHByb2plY3Qgd2UgaGF2ZSBkb25lIHNvbWUgRURBIHdpdGggbGluZWFyIGFuZCBsb2dpc3RpY2FsIHJlZ3Jlc3Npb24gdG8gYW5zd2VyIHR3byBxdWVzdGlvbnM6DQogIA0KICAxLiBXaGF0IGZhY3RvcnMgYWZmZWN0IHRoZSBkdXJhdGlvbiBvZiBhIGNhbGw/DQogIDIuIElmIHdlIGNhbiBwcmVkaWN0IHdoZXRoZXIgYSBjbGllbnQgd2lsbCBzdWJzY3JpYmUgdG8gZGlyZWN0IG1hcmtldGluZy4NCiAgDQogIEluIG91ciBtb2RlbGluZyB3ZSBjYW5ub3QgcmVseSBvbiB0aGUgbGluZWFyIG1vZGVsIGFzIHRoZSBhc3N1bXB0aW9uIGhhdmUgYmUgdmlvbGF0ZWQgYW5kIHRoZXJlIGlzIG5vIHN0cm9uZyBldmlkZW5jZSB0byBzdXBwb3J0IGFuZCBjbGFpbXMuIEJ1dCwgaW4gb3VyIGxvZ2lzdGljIG1vZGVsIHdlIHdlcmUgYWJsZSB0byBhY2N1cmF0ZWx5IHByZWRpY3Qgd2hldGhlciBhIGNsaWVudCB3aWxsIHN1YnNjcmliZSB0byB0aGUgbWFya2V0aW5nLiBTb21lIG1vcmUgRURBIGFuZCB3cmFuZ2xpbmcgaXMgbmVlZGVkIHRvIGZpdCB0aGUgbGluZWFyIG1vZGVsLCBvciBtYXliZSByZXdvcmtpbmcgdGhlIHF1ZXN0aW9uIGlzIGEgbW9yZSB2aWFibGUgc29sdXRpb24uDQoNCg0KDQoNCg==