the following is a step by step procedure for predicting credit score in using Random Forest Algorithm in R. We first talk about the random forest algorithm and how it works. We then start with analyzing the data and tune it to better suit our solution.

This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.

Using Random Forest For Credit Scoring: “Improve on the state of the art in credit scoring by predicting the probability that somebody will experience financial distress in the next two years.”

We first set the working directory and upload the test and train files into the local R database.

Random forest is a type of decision tree algorithm. A decision tree algorithm is a supervised learning algorithm which has a predefined target variable that is mostly used in classification problems. It works for both categorical and continuous input and output variables and thus is the panacea of the data science problem. R has a package called RandomForest which makes life pretty easy to solve such a problem but as it is with Data Science Problems, the true zest lies in tuning and woking with the data to come to a point where randomForest can efficiently solve it. Here in this problem the code is quite readable but having said that, I have done my best to go step byu step to explain each procedure.

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

setwd("C:/Users/Atul/Desktop/risk analysis")
train1 <- read.csv("cs-training.csv")
test1 <- read.csv("cs-test.csv")
head(test1)
head(train1)

We find explore both the databases and try to understand the underlying problem. We check data structures of each of the data features and check which of the following features are NA’s.

combi <- rbind(train1,test1)
str(combi)
'data.frame':   251503 obs. of  12 variables:
 $ X                                   : int  1 2 3 4 5 6 7 8 9 10 ...
 $ SeriousDlqin2yrs                    : int  1 0 0 0 0 0 0 0 0 0 ...
 $ RevolvingUtilizationOfUnsecuredLines: num  0.766 0.957 0.658 0.234 0.907 ...
 $ age                                 : int  45 40 38 30 49 74 57 39 27 57 ...
 $ NumberOfTime30.59DaysPastDueNotWorse: int  2 0 1 0 1 0 0 0 0 0 ...
 $ DebtRatio                           : num  0.803 0.1219 0.0851 0.036 0.0249 ...
 $ MonthlyIncome                       : int  9120 2600 3042 3300 63588 3500 NA 3500 NA 23684 ...
 $ NumberOfOpenCreditLinesAndLoans     : int  13 4 2 5 7 3 8 8 2 9 ...
 $ NumberOfTimes90DaysLate             : int  0 0 1 0 0 0 0 0 0 0 ...
 $ NumberRealEstateLoansOrLines        : int  6 0 0 0 1 1 3 0 0 4 ...
 $ NumberOfTime60.89DaysPastDueNotWorse: int  0 0 0 0 0 0 0 0 0 0 ...
 $ NumberOfDependents                  : int  2 1 0 0 0 1 0 0 NA 2 ...
summary(combi)
       X          SeriousDlqin2yrs RevolvingUtilizationOfUnsecuredLines      age        
 Min.   :     1   Min.   :0.00     Min.   :    0.00                     Min.   :  0.00  
 1st Qu.: 31439   1st Qu.:0.00     1st Qu.:    0.03                     1st Qu.: 41.00  
 Median : 62876   Median :0.00     Median :    0.15                     Median : 52.00  
 Mean   : 65214   Mean   :0.07     Mean   :    5.75                     Mean   : 52.34  
 3rd Qu.: 94314   3rd Qu.:0.00     3rd Qu.:    0.56                     3rd Qu.: 63.00  
 Max.   :150000   Max.   :1.00     Max.   :50708.00                     Max.   :109.00  
                  NA's   :101503                                                        
 NumberOfTime30.59DaysPastDueNotWorse   DebtRatio        MonthlyIncome     NumberOfOpenCreditLinesAndLoans
 Min.   : 0.0000                      Min.   :     0.0   Min.   :      0   Min.   : 0.000                 
 1st Qu.: 0.0000                      1st Qu.:     0.2   1st Qu.:   3400   1st Qu.: 5.000                 
 Median : 0.0000                      Median :     0.4   Median :   5400   Median : 8.000                 
 Mean   : 0.4343                      Mean   :   349.6   Mean   :   6745   Mean   : 8.453                 
 3rd Qu.: 0.0000                      3rd Qu.:     0.9   3rd Qu.:   8212   3rd Qu.:11.000                 
 Max.   :98.0000                      Max.   :329664.0   Max.   :7727000   Max.   :85.000                 
                                                         NA's   :49834                                    
 NumberOfTimes90DaysLate NumberRealEstateLoansOrLines NumberOfTime60.89DaysPastDueNotWorse
 Min.   : 0.0000         Min.   : 0.000               Min.   : 0.0000                     
 1st Qu.: 0.0000         1st Qu.: 0.000               1st Qu.: 0.0000                     
 Median : 0.0000         Median : 1.000               Median : 0.0000                     
 Mean   : 0.2784         Mean   : 1.016               Mean   : 0.2525                     
 3rd Qu.: 0.0000         3rd Qu.: 2.000               3rd Qu.: 0.0000                     
 Max.   :98.0000         Max.   :54.000               Max.   :98.0000                     
                                                                                          
 NumberOfDependents
 Min.   : 0.000    
 1st Qu.: 0.000    
 Median : 0.000    
 Mean   : 0.762    
 3rd Qu.: 1.000    
 Max.   :43.000    
 NA's   :6550      

What we did is we combined both test and train datasets, and got the strusture and summary of the combi datatset to get some good insights about the data. The features are self explanatory and can tell a good thing or two about the data itself.

library(rpart)
combi$AgeClass <- '64+'
combi$AgeClass[combi$age > 0 & combi$age <= 40] <- '0-40'
combi$AgeClass[combi$age > 40 & combi$age <=52] <- '41-52'
combi$AgeClass[combi$age > 52 & combi$age <=64] <- '43-54'
combi$AgeClass <- as.factor(combi$AgeClass)
#combi$AgeClass[is.na(combi$age)] <- NA

we now divide age into different cattegories to make our classification tree more efficient. Here, one can see that we havw now divided the feature age in 4 classes.

library(gmodels)
package <U+393C><U+3E31>gmodels<U+393C><U+3E32> was built under R version 3.3.3
CrossTable(combi$SeriousDlqin2yrs)

 
   Cell Contents
|-------------------------|
|                       N |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  150000 

 
          |         0 |         1 | 
          |-----------|-----------|
          |    139974 |     10026 | 
          |     0.933 |     0.067 | 
          |-----------|-----------|



 

The following table gives a good insight about the number of 0s and 1s in the datasets. We know that around 93.3% have had serious deliquencies in two years.

DependentsFit <- rpart(NumberOfDependents ~ RevolvingUtilizationOfUnsecuredLines + age + NumberOfOpenCreditLinesAndLoans 
                       + MonthlyIncome + NumberRealEstateLoansOrLines + DebtRatio,
                       data=combi[!is.na(combi$NumberOfDependents),], 
                       method="anova")
combi$NumberOfDependents[is.na(combi$NumberOfDependents)] <- predict(DependentsFit, combi[is.na(combi$NumberOfDependents),])
sum(is.na(combi$NumberOfDependents))
[1] 0

In the above chunk of code we have successfully replaced all the Na’s with predicted values acquired from the anova method. It we could also exclude the rows with Na’s but this seems like a better choice as we have a lot of data to learn from and edting a little would do no harm.

Further, Lets now look at another predictor variable, lets look at the number of times people were lateb by 30-59 dats, lets change the datatype of the variable from integer to factor and change the levels.

combi$NumberOfTime30.59DaysPastDueNotWorse <- as.factor(combi$NumberOfTime30.59DaysPastDueNotWorse)

Lets apply the same the numberoftimepast 60-89 days column. And for deliquencies more than 90 days.

combi$NumberOfTime60.89DaysPastDueNotWorse <- as.factor(combi$NumberOfTime60.89DaysPastDueNotWorse)
combi$NumberOfTimes90DaysLate <- as.factor(combi$NumberOfTimes90DaysLate)

Let’s now fill Na’s in the monthy income column by regression using anova technique.

IncomeFit <- rpart(MonthlyIncome ~ RevolvingUtilizationOfUnsecuredLines + age + NumberOfOpenCreditLinesAndLoans + NumberOfDependents + NumberRealEstateLoansOrLines + DebtRatio,
                   data=combi[!is.na(combi$MonthlyIncome),], 
                   method="anova")
combi$MonthlyIncome[is.na(combi$MonthlyIncome)] <- predict(IncomeFit, combi[is.na(combi$MonthlyIncome),])
sum(is.na(combi$MonthlyIncome))
[1] 0

Let’s now go ahead to the salary variable and create differnet classes for the salary variable data.

combi$IncomeClass[combi$MonthlyIncome >= 0 & combi$MonthlyIncome <= 1000] <- '0-1000'
combi$IncomeClass[combi$MonthlyIncome > 1000 & combi$MonthlyIncome <= 2000] <- '1001-2000'
combi$IncomeClass[combi$MonthlyIncome > 2000 & combi$MonthlyIncome <= 3000] <- '2001-3000'
combi$IncomeClass[combi$MonthlyIncome > 3000 & combi$MonthlyIncome <= 4000] <- '3001-4000'
combi$IncomeClass[combi$MonthlyIncome > 4000 & combi$MonthlyIncome <= 6000] <- '4001-6000'
combi$IncomeClass[combi$MonthlyIncome > 6001 & combi$MonthlyIncome <= 8000] <- '6001-8000'
invalid factor level, NA generated
combi$IncomeClass[combi$MonthlyIncome > 6000 & combi$MonthlyIncome <= 10000] <- '8001-10000'
combi$IncomeClass[combi$MonthlyIncome > 10000 & combi$MonthlyIncome <= 20000] <- '10001-20000'
combi$IncomeClass[combi$MonthlyIncome > 20000] <- '20000+'
combi$IncomeClass <- as.factor(combi$IncomeClass)

One of the most insightful variable in my opinion is the debt ratrio, this has to make a lo0t of sense once we model the Cross Table of Debt Ratio.

combi$DebtRatioClass <- '100+'
combi$DebtRatioClass[combi$DebtRatio >= 0 & combi$DebtRatio <= 0.5] <- '0-0.5'
combi$DebtRatioClass[combi$DebtRatio > 0.5 & combi$DebtRatio <= 1] <- '0.5-1'
combi$DebtRatioClass[combi$DebtRatio > 1 & combi$DebtRatio <= 2] <- '1-2'
combi$DebtRatioClass[combi$DebtRatio > 2 & combi$DebtRatio <= 10] <- '2-10'
combi$DebtRatioClass[combi$DebtRatio > 10 & combi$DebtRatio <= 100] <- '10-100'
combi$DebtRatioClass <- as.factor(combi$DebtRatioClass)
summary(combi$DebtRatioClass)
 0-0.5  0.5-1    1-2 10-100   100+   2-10 
157240  35548   6855   7521  40753   3586 
CrossTable(combi$DebtRatioClass)

 
   Cell Contents
|-------------------------|
|                       N |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  251503 

 
          |     0-0.5 |     0.5-1 |       1-2 |    10-100 |      100+ | 
          |-----------|-----------|-----------|-----------|-----------|
          |    157240 |     35548 |      6855 |      7521 |     40753 | 
          |     0.625 |     0.141 |     0.027 |     0.030 |     0.162 | 
          |-----------|-----------|-----------|-----------|-----------|

          |      2-10 | 
          |-----------|
          |      3586 | 
          |     0.014 | 
          |-----------|



 
CrossTable(combi$SeriousDlqin2yrs,combi$DebtRatioClass, prop.r = TRUE, prop.c = FALSE, prop.t = FALSE, prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|-------------------------|

 
Total Observations in Table:  150000 

 
                       | combi$DebtRatioClass 
combi$SeriousDlqin2yrs |     0-0.5 |     0.5-1 |       1-2 |    10-100 |      100+ |      2-10 | Row Total | 
-----------------------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|
                     0 |     88053 |     19075 |      3553 |      4298 |     22970 |      2025 |    139974 | 
                       |     0.629 |     0.136 |     0.025 |     0.031 |     0.164 |     0.014 |     0.933 | 
-----------------------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|
                     1 |      5655 |      2080 |       539 |       199 |      1410 |       143 |     10026 | 
                       |     0.564 |     0.207 |     0.054 |     0.020 |     0.141 |     0.014 |     0.067 | 
-----------------------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|
          Column Total |     93708 |     21155 |      4092 |      4497 |     24380 |      2168 |    150000 | 
-----------------------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|

 

As can be seen using the comparison, this makes perfect sense..

The next feature of the dataset to be considered, is the RevolvingUtilizationOfUnsecuredLine, this should be on the same lines of that of debt ratio.

summary(combi$NumberOfOpenCreditLinesAndLoans)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   5.000   8.000   8.453  11.000  85.000 
plot(density(combi$NumberOfOpenCreditLinesAndLoans))

#Let us model categorically for this variable
combi$OpenCreditClass <- '20+'
combi$OpenCreditClass[combi$NumberOfOpenCreditLinesAndLoans >= 0 & combi$NumberOfOpenCreditLinesAndLoans<=5] <- '0-5'
combi$OpenCreditClass[combi$NumberOfOpenCreditLinesAndLoans > 5 & combi$NumberOfOpenCreditLinesAndLoans<=10] <- '5-10'
combi$OpenCreditClass[combi$NumberOfOpenCreditLinesAndLoans > 10 & combi$NumberOfOpenCreditLinesAndLoans<=15] <- '10-15'
combi$OpenCreditClass[combi$NumberOfOpenCreditLinesAndLoans > 15 & combi$NumberOfOpenCreditLinesAndLoans<= 20] <- '15-20'
combi$OpenCreditClass <- as.factor(combi$OpenCreditClass)
CrossTable(combi$OpenCreditClass)

 
   Cell Contents
|-------------------------|
|                       N |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  251503 

 
          |       0-5 |     10-15 |     15-20 |       20+ |      5-10 | 
          |-----------|-----------|-----------|-----------|-----------|
          |     78018 |     48912 |     16442 |      6679 |    101452 | 
          |     0.310 |     0.194 |     0.065 |     0.027 |     0.403 | 
          |-----------|-----------|-----------|-----------|-----------|



 
CrossTable(combi$SeriousDlqin2yrs,combi$OpenCreditClass, prop.r = TRUE, prop.c = FALSE, prop.t = FALSE, prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|-------------------------|

 
Total Observations in Table:  150000 

 
                       | combi$OpenCreditClass 
combi$SeriousDlqin2yrs |       0-5 |     10-15 |     15-20 |       20+ |      5-10 | Row Total | 
-----------------------|-----------|-----------|-----------|-----------|-----------|-----------|
                     0 |     42668 |     27380 |      9170 |      3701 |     57055 |    139974 | 
                       |     0.305 |     0.196 |     0.066 |     0.026 |     0.408 |     0.933 | 
-----------------------|-----------|-----------|-----------|-----------|-----------|-----------|
                     1 |      3922 |      1804 |       676 |       279 |      3345 |     10026 | 
                       |     0.391 |     0.180 |     0.067 |     0.028 |     0.334 |     0.067 | 
-----------------------|-----------|-----------|-----------|-----------|-----------|-----------|
          Column Total |     46590 |     29184 |      9846 |      3980 |     60400 |    150000 | 
-----------------------|-----------|-----------|-----------|-----------|-----------|-----------|

 
#independently, this variable does not seem as insightful as other, but i am certain collectively it will be very important.
summary(combi$NumberRealEstateLoansOrLines)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   0.000   1.000   1.016   2.000  54.000 
combi$RealtyLinesClass <- '3+' 
combi$RealtyLinesClass[combi$NumberRealEstateLoansOrLines >=0 & combi$NumberRealEstateLoansOrLines <= 1] <- '0-1'
combi$RealtyLinesClass[combi$NumberRealEstateLoansOrLines >1 & combi$NumberRealEstateLoansOrLines <= 2] <- '1-2'
combi$RealtyLinesClass[combi$NumberRealEstateLoansOrLines >2 & combi$NumberRealEstateLoansOrLines <= 3] <- '2-3'
combi$RealtyLinesClass <- as.factor(combi$RealtyLinesClass)
CrossTable(combi$RealtyLinesClass)

 
   Cell Contents
|-------------------------|
|                       N |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  251503 

 
          |       0-1 |       1-2 |       2-3 |        3+ | 
          |-----------|-----------|-----------|-----------|
          |    182262 |     52477 |     10723 |      6041 | 
          |     0.725 |     0.209 |     0.043 |     0.024 | 
          |-----------|-----------|-----------|-----------|



 
CrossTable(combi$SeriousDlqin2yrs,combi$RealtyLinesClass, prop.r = TRUE, prop.c = FALSE, prop.t = FALSE, prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|-------------------------|

 
Total Observations in Table:  150000 

 
                       | combi$RealtyLinesClass 
combi$SeriousDlqin2yrs |       0-1 |       1-2 |       2-3 |        3+ | Row Total | 
-----------------------|-----------|-----------|-----------|-----------|-----------|
                     0 |    101106 |     29757 |      5878 |      3233 |    139974 | 
                       |     0.722 |     0.213 |     0.042 |     0.023 |     0.933 | 
-----------------------|-----------|-----------|-----------|-----------|-----------|
                     1 |      7420 |      1765 |       422 |       419 |     10026 | 
                       |     0.740 |     0.176 |     0.042 |     0.042 |     0.067 | 
-----------------------|-----------|-----------|-----------|-----------|-----------|
          Column Total |    108526 |     31522 |      6300 |      3652 |    150000 | 
-----------------------|-----------|-----------|-----------|-----------|-----------|

 
str(combi)
'data.frame':   251503 obs. of  17 variables:
 $ X                                   : int  1 2 3 4 5 6 7 8 9 10 ...
 $ SeriousDlqin2yrs                    : int  1 0 0 0 0 0 0 0 0 0 ...
 $ RevolvingUtilizationOfUnsecuredLines: num  0.766 0.957 0.658 0.234 0.907 ...
 $ age                                 : int  45 40 38 30 49 74 57 39 27 57 ...
 $ NumberOfTime30.59DaysPastDueNotWorse: Factor w/ 17 levels "0","1","2","3",..: 3 1 2 1 2 1 1 1 1 1 ...
 $ DebtRatio                           : num  0.803 0.1219 0.0851 0.036 0.0249 ...
 $ MonthlyIncome                       : num  9120 2600 3042 3300 63588 ...
 $ NumberOfOpenCreditLinesAndLoans     : int  13 4 2 5 7 3 8 8 2 9 ...
 $ NumberOfTimes90DaysLate             : Factor w/ 21 levels "0","1","2","3",..: 1 1 2 1 1 1 1 1 1 1 ...
 $ NumberRealEstateLoansOrLines        : int  6 0 0 0 1 1 3 0 0 4 ...
 $ NumberOfTime60.89DaysPastDueNotWorse: Factor w/ 13 levels "0","1","2","3",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ NumberOfDependents                  : num  2 1 0 0 0 ...
 $ AgeClass                            : Factor w/ 4 levels "0-40","41-52",..: 2 1 1 1 2 4 3 1 1 3 ...
 $ IncomeClass                         : Factor w/ 8 levels "0-1000","10001-20000",..: 8 5 6 6 4 6 8 6 6 4 ...
 $ DebtRatioClass                      : Factor w/ 6 levels "0-0.5","0.5-1",..: 2 1 1 1 1 1 5 1 4 2 ...
 $ OpenCreditClass                     : Factor w/ 5 levels "0-5","10-15",..: 2 1 1 1 5 1 5 5 1 5 ...
 $ RealtyLinesClass                    : Factor w/ 4 levels "0-1","1-2","2-3",..: 4 1 1 1 1 1 3 1 1 4 ...

After turing all the parameters that we think are important, we now go ahead to solve the predictive model and using Random Forest Algorithm.

library(randomForest)
randomForest 4.6-12
Type rfNews() to see new features/changes/bug fixes.
set.seed(888)
nrow(combi)
[1] 251503
train <- combi[1:150000,]
test <- combi[150001:251503,]

To do this we have again divided the combi dataset into train and test datasets.

fit <- randomForest(as.factor(SeriousDlqin2yrs) ~ NumberOfTimes90DaysLate + NumberOfTime60.89DaysPastDueNotWorse
                    + NumberOfTime30.59DaysPastDueNotWorse + NumberOfDependents + AgeClass + DebtRatioClass +
                      RevolvingUtilizationOfUnsecuredLines+ OpenCreditClass + RealtyLinesClass,
                    data=train, 
                    importance=TRUE, 
                    ntree=25, keep.forest = TRUE)
sum(is.na(train$SeriousDlqin2yrs))
[1] 0
Prediction <- predict(fit, test, type = "prob")
submit <- data.frame(Id = test$X, Probability = Prediction)
entry <- data.frame(Id = submit$Id, Probability = submit$Probability.1)
write.csv(entry, file = "Entry.csv", row.names = FALSE)
nrow(test)
[1] 101503

Let’s plot ctree..

library("partykit")
x <- ctree(as.factor(SeriousDlqin2yrs) ~ NumberOfTimes90DaysLate + NumberOfTime60.89DaysPastDueNotWorse
           + NumberOfTime30.59DaysPastDueNotWorse + NumberOfDependents + AgeClass + DebtRatioClass +
             RevolvingUtilizationOfUnsecuredLines + OpenCreditClass + RealtyLinesClass,
           data=train)
  
plot(x, gp = gpar(fontsize = 5),     # font size changed to 6
     inner_panel=node_inner,
     ip_args=list(
       abbreviate = FALSE, 
       id = FALSE)
)

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQp0aGUgZm9sbG93aW5nIGlzIGEgc3RlcCBieSBzdGVwIHByb2NlZHVyZSBmb3IgcHJlZGljdGluZyBjcmVkaXQgc2NvcmUgaW4gdXNpbmcgUmFuZG9tIEZvcmVzdCBBbGdvcml0aG0gaW4gUi4gV2UgZmlyc3QgdGFsayBhYm91dCB0aGUgcmFuZG9tIGZvcmVzdCBhbGdvcml0aG0gYW5kIGhvdyBpdCB3b3Jrcy4gDQpXZSB0aGVuIHN0YXJ0IHdpdGggYW5hbHl6aW5nIHRoZSBkYXRhIGFuZCB0dW5lIGl0IHRvIGJldHRlciBzdWl0IG91ciBzb2x1dGlvbi4NCg0KDQpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vay4gV2hlbiB5b3UgZXhlY3V0ZSBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSByZXN1bHRzIGFwcGVhciBiZW5lYXRoIHRoZSBjb2RlLiANCg0KVHJ5IGV4ZWN1dGluZyB0aGlzIGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqUnVuKiBidXR0b24gd2l0aGluIHRoZSBjaHVuayBvciBieSBwbGFjaW5nIHlvdXIgY3Vyc29yIGluc2lkZSBpdCBhbmQgcHJlc3NpbmcgKkN0cmwrU2hpZnQrRW50ZXIqLiANCg0KVXNpbmcgUmFuZG9tIEZvcmVzdCBGb3IgQ3JlZGl0IFNjb3Jpbmc6DQoiSW1wcm92ZSBvbiB0aGUgc3RhdGUgb2YgdGhlIGFydCBpbiBjcmVkaXQgc2NvcmluZyBieSBwcmVkaWN0aW5nIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHNvbWVib2R5IHdpbGwgZXhwZXJpZW5jZSBmaW5hbmNpYWwgZGlzdHJlc3MgaW4gdGhlIG5leHQgdHdvIHllYXJzLiAiDQoNCldlIGZpcnN0IHNldCB0aGUgd29ya2luZyBkaXJlY3RvcnkgYW5kIHVwbG9hZCB0aGUgdGVzdCBhbmQgdHJhaW4gZmlsZXMgaW50byB0aGUgbG9jYWwgUiBkYXRhYmFzZS4NCg0KUmFuZG9tIGZvcmVzdCBpcyBhIHR5cGUgb2YgZGVjaXNpb24gdHJlZSBhbGdvcml0aG0uIEEgZGVjaXNpb24gdHJlZSBhbGdvcml0aG0gaXMgYSBzdXBlcnZpc2VkIGxlYXJuaW5nIGFsZ29yaXRobSB3aGljaCBoYXMgYSBwcmVkZWZpbmVkIHRhcmdldCB2YXJpYWJsZSB0aGF0IGlzIG1vc3RseSB1c2VkIGluIGNsYXNzaWZpY2F0aW9uIHByb2JsZW1zLiBJdCB3b3JrcyBmb3IgYm90aCBjYXRlZ29yaWNhbCBhbmQgY29udGludW91cyBpbnB1dCBhbmQgb3V0cHV0IHZhcmlhYmxlcyBhbmQgdGh1cyBpcyB0aGUgcGFuYWNlYSBvZiB0aGUgZGF0YSBzY2llbmNlIHByb2JsZW0uIFIgaGFzIGEgcGFja2FnZSBjYWxsZWQgUmFuZG9tRm9yZXN0IHdoaWNoIG1ha2VzIGxpZmUgcHJldHR5IGVhc3kgdG8gc29sdmUgc3VjaCBhIHByb2JsZW0gYnV0IGFzIGl0IGlzIHdpdGggRGF0YSBTY2llbmNlIFByb2JsZW1zLCB0aGUgdHJ1ZSB6ZXN0IGxpZXMgaW4gdHVuaW5nIGFuZCB3b2tpbmcgd2l0aCB0aGUgZGF0YSB0byBjb21lIHRvIGEgcG9pbnQgd2hlcmUgcmFuZG9tRm9yZXN0IGNhbiBlZmZpY2llbnRseSBzb2x2ZSBpdC4gSGVyZSBpbiB0aGlzIHByb2JsZW0gdGhlIGNvZGUgaXMgcXVpdGUgcmVhZGFibGUgYnV0IGhhdmluZyBzYWlkIHRoYXQsIEkgaGF2ZSBkb25lIG15IGJlc3QgdG8gZ28gc3RlcCBieXUgc3RlcCB0byBleHBsYWluIGVhY2ggcHJvY2VkdXJlLg0KDQoNCiANCkFkZCBhIG5ldyBjaHVuayBieSBjbGlja2luZyB0aGUgKkluc2VydCBDaHVuayogYnV0dG9uIG9uIHRoZSB0b29sYmFyIG9yIGJ5IHByZXNzaW5nICpDdHJsK0FsdCtJKi4NCmBgYHtyfQ0Kc2V0d2QoIkM6L1VzZXJzL0F0dWwvRGVza3RvcC9yaXNrIGFuYWx5c2lzIikNCnRyYWluMSA8LSByZWFkLmNzdigiY3MtdHJhaW5pbmcuY3N2IikNCnRlc3QxIDwtIHJlYWQuY3N2KCJjcy10ZXN0LmNzdiIpDQpoZWFkKHRlc3QxKQ0KaGVhZCh0cmFpbjEpDQpgYGANCg0KDQpXZSBmaW5kIGV4cGxvcmUgYm90aCB0aGUgZGF0YWJhc2VzIGFuZCB0cnkgdG8gdW5kZXJzdGFuZCB0aGUgdW5kZXJseWluZyBwcm9ibGVtLiBXZSBjaGVjayBkYXRhIHN0cnVjdHVyZXMgb2YgZWFjaCBvZiB0aGUgZGF0YSBmZWF0dXJlcyBhbmQgY2hlY2sgd2hpY2ggb2YgdGhlIGZvbGxvd2luZyBmZWF0dXJlcyBhcmUgTkEncy4NCg0KYGBge3J9DQpjb21iaSA8LSByYmluZCh0cmFpbjEsdGVzdDEpDQpzdHIoY29tYmkpDQpzdW1tYXJ5KGNvbWJpKQ0KYGBgDQogV2hhdCB3ZSBkaWQgaXMgd2UgY29tYmluZWQgYm90aCB0ZXN0IGFuZCB0cmFpbiBkYXRhc2V0cywgYW5kIGdvdCB0aGUgc3RydXN0dXJlIGFuZCBzdW1tYXJ5IG9mIHRoZSBjb21iaSBkYXRhdHNldCB0byBnZXQgc29tZSBnb29kIGluc2lnaHRzIGFib3V0IHRoZSBkYXRhLiBUaGUgZmVhdHVyZXMgYXJlIHNlbGYgZXhwbGFuYXRvcnkgYW5kIGNhbiB0ZWxsIGEgZ29vZCB0aGluZyBvciB0d28gYWJvdXQgdGhlIGRhdGEgaXRzZWxmLiANCiANCiANCmBgYHtyfQ0KbGlicmFyeShycGFydCkNCmNvbWJpJEFnZUNsYXNzIDwtICc2NCsnDQpjb21iaSRBZ2VDbGFzc1tjb21iaSRhZ2UgPiAwICYgY29tYmkkYWdlIDw9IDQwXSA8LSAnMC00MCcNCmNvbWJpJEFnZUNsYXNzW2NvbWJpJGFnZSA+IDQwICYgY29tYmkkYWdlIDw9NTJdIDwtICc0MS01MicNCmNvbWJpJEFnZUNsYXNzW2NvbWJpJGFnZSA+IDUyICYgY29tYmkkYWdlIDw9NjRdIDwtICc0My01NCcNCmNvbWJpJEFnZUNsYXNzIDwtIGFzLmZhY3Rvcihjb21iaSRBZ2VDbGFzcykNCiNjb21iaSRBZ2VDbGFzc1tpcy5uYShjb21iaSRhZ2UpXSA8LSBOQQ0KYGBgDQoNCndlIG5vdyBkaXZpZGUgYWdlIGludG8gZGlmZmVyZW50IGNhdHRlZ29yaWVzIHRvIG1ha2Ugb3VyIGNsYXNzaWZpY2F0aW9uIHRyZWUgbW9yZSBlZmZpY2llbnQuIEhlcmUsIG9uZSBjYW4gc2VlIHRoYXQgd2UgaGF2dyBub3cgZGl2aWRlZCB0aGUgZmVhdHVyZSBhZ2UgaW4gNCBjbGFzc2VzLiANCg0KYGBge3J9DQpsaWJyYXJ5KGdtb2RlbHMpDQpDcm9zc1RhYmxlKGNvbWJpJFNlcmlvdXNEbHFpbjJ5cnMpDQpgYGANCg0KDQpUaGUgZm9sbG93aW5nIHRhYmxlIGdpdmVzIGEgZ29vZCBpbnNpZ2h0IGFib3V0IHRoZSBudW1iZXIgb2YgMHMgYW5kIDFzIGluIHRoZSBkYXRhc2V0cy4gV2Uga25vdyB0aGF0IGFyb3VuZCA5My4zJSBoYXZlIGhhZCBzZXJpb3VzIGRlbGlxdWVuY2llcyBpbiB0d28geWVhcnMuIA0KDQoNCmBgYHtyfQ0KRGVwZW5kZW50c0ZpdCA8LSBycGFydChOdW1iZXJPZkRlcGVuZGVudHMgfiBSZXZvbHZpbmdVdGlsaXphdGlvbk9mVW5zZWN1cmVkTGluZXMgKyBhZ2UgKyBOdW1iZXJPZk9wZW5DcmVkaXRMaW5lc0FuZExvYW5zIA0KICAgICAgICAgICAgICAgICAgICAgICArIE1vbnRobHlJbmNvbWUgKyBOdW1iZXJSZWFsRXN0YXRlTG9hbnNPckxpbmVzICsgRGVidFJhdGlvLA0KICAgICAgICAgICAgICAgICAgICAgICBkYXRhPWNvbWJpWyFpcy5uYShjb21iaSROdW1iZXJPZkRlcGVuZGVudHMpLF0sIA0KICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9ImFub3ZhIikNCg0KY29tYmkkTnVtYmVyT2ZEZXBlbmRlbnRzW2lzLm5hKGNvbWJpJE51bWJlck9mRGVwZW5kZW50cyldIDwtIHByZWRpY3QoRGVwZW5kZW50c0ZpdCwgY29tYmlbaXMubmEoY29tYmkkTnVtYmVyT2ZEZXBlbmRlbnRzKSxdKQ0Kc3VtKGlzLm5hKGNvbWJpJE51bWJlck9mRGVwZW5kZW50cykpDQoNCmBgYA0KDQpJbiB0aGUgYWJvdmUgY2h1bmsgb2YgY29kZSB3ZSBoYXZlIHN1Y2Nlc3NmdWxseSByZXBsYWNlZCBhbGwgdGhlIE5hJ3Mgd2l0aCBwcmVkaWN0ZWQgdmFsdWVzIGFjcXVpcmVkIGZyb20gdGhlIGFub3ZhIG1ldGhvZC4gSXQgd2UgY291bGQgYWxzbyBleGNsdWRlIHRoZSByb3dzIHdpdGggTmEncyBidXQgdGhpcyBzZWVtcyBsaWtlIGEgYmV0dGVyIGNob2ljZSBhcyB3ZSBoYXZlIGEgbG90IG9mIGRhdGEgdG8gbGVhcm4gZnJvbSBhbmQgZWR0aW5nIGEgbGl0dGxlIHdvdWxkIGRvIG5vIGhhcm0uDQoNCg0KRnVydGhlciwgTGV0cyBub3cgbG9vayBhdCBhbm90aGVyIHByZWRpY3RvciB2YXJpYWJsZSwgbGV0cyBsb29rIGF0IHRoZSBudW1iZXIgb2YgdGltZXMgcGVvcGxlIHdlcmUgbGF0ZWIgYnkgMzAtNTkgZGF0cywgbGV0cyBjaGFuZ2UgdGhlIGRhdGF0eXBlIG9mIHRoZSB2YXJpYWJsZSBmcm9tIGludGVnZXIgdG8gZmFjdG9yIGFuZCBjaGFuZ2UgdGhlIGxldmVscy4NCg0KYGBge3J9DQpjb21iaSROdW1iZXJPZlRpbWUzMC41OURheXNQYXN0RHVlTm90V29yc2UgPC0gYXMuZmFjdG9yKGNvbWJpJE51bWJlck9mVGltZTMwLjU5RGF5c1Bhc3REdWVOb3RXb3JzZSkNCg0KDQpgYGANCkxldHMgYXBwbHkgdGhlIHNhbWUgdGhlIG51bWJlcm9mdGltZXBhc3QgNjAtODkgZGF5cyBjb2x1bW4uIEFuZCBmb3IgZGVsaXF1ZW5jaWVzIG1vcmUgdGhhbiA5MCBkYXlzLiANCg0KYGBge3J9DQpjb21iaSROdW1iZXJPZlRpbWU2MC44OURheXNQYXN0RHVlTm90V29yc2UgPC0gYXMuZmFjdG9yKGNvbWJpJE51bWJlck9mVGltZTYwLjg5RGF5c1Bhc3REdWVOb3RXb3JzZSkNCmNvbWJpJE51bWJlck9mVGltZXM5MERheXNMYXRlIDwtIGFzLmZhY3Rvcihjb21iaSROdW1iZXJPZlRpbWVzOTBEYXlzTGF0ZSkNCmBgYA0KDQpMZXQncyBub3cgZmlsbCBOYSdzIGluIHRoZSBtb250aHkgaW5jb21lIGNvbHVtbiBieSByZWdyZXNzaW9uIHVzaW5nIGFub3ZhIHRlY2huaXF1ZS4NCg0KYGBge3J9DQpJbmNvbWVGaXQgPC0gcnBhcnQoTW9udGhseUluY29tZSB+IFJldm9sdmluZ1V0aWxpemF0aW9uT2ZVbnNlY3VyZWRMaW5lcyArIGFnZSArIE51bWJlck9mT3BlbkNyZWRpdExpbmVzQW5kTG9hbnMgKyBOdW1iZXJPZkRlcGVuZGVudHMgKyBOdW1iZXJSZWFsRXN0YXRlTG9hbnNPckxpbmVzICsgRGVidFJhdGlvLA0KICAgICAgICAgICAgICAgICAgIGRhdGE9Y29tYmlbIWlzLm5hKGNvbWJpJE1vbnRobHlJbmNvbWUpLF0sIA0KICAgICAgICAgICAgICAgICAgIG1ldGhvZD0iYW5vdmEiKQ0KDQpjb21iaSRNb250aGx5SW5jb21lW2lzLm5hKGNvbWJpJE1vbnRobHlJbmNvbWUpXSA8LSBwcmVkaWN0KEluY29tZUZpdCwgY29tYmlbaXMubmEoY29tYmkkTW9udGhseUluY29tZSksXSkNCnN1bShpcy5uYShjb21iaSRNb250aGx5SW5jb21lKSkjQ2hlY2sgdGhlIG51bWJlciBvZiBOYSdzIHRvIG1ha2Ugc3VyZSB0aGUgY29kZSBoYXMgYmVlbiBleGVjdXRlZCBwcm9wZXJseS4NCmBgYA0KTGV0J3Mgbm93IGdvIGFoZWFkIHRvIHRoZSBzYWxhcnkgdmFyaWFibGUgYW5kIGNyZWF0ZSBkaWZmZXJuZXQgY2xhc3NlcyBmb3IgdGhlIHNhbGFyeSB2YXJpYWJsZSBkYXRhLg0KYGBge3J9DQpjb21iaSRJbmNvbWVDbGFzc1tjb21iaSRNb250aGx5SW5jb21lID49IDAgJiBjb21iaSRNb250aGx5SW5jb21lIDw9IDEwMDBdIDwtICcwLTEwMDAnDQpjb21iaSRJbmNvbWVDbGFzc1tjb21iaSRNb250aGx5SW5jb21lID4gMTAwMCAmIGNvbWJpJE1vbnRobHlJbmNvbWUgPD0gMjAwMF0gPC0gJzEwMDEtMjAwMCcNCmNvbWJpJEluY29tZUNsYXNzW2NvbWJpJE1vbnRobHlJbmNvbWUgPiAyMDAwICYgY29tYmkkTW9udGhseUluY29tZSA8PSAzMDAwXSA8LSAnMjAwMS0zMDAwJw0KY29tYmkkSW5jb21lQ2xhc3NbY29tYmkkTW9udGhseUluY29tZSA+IDMwMDAgJiBjb21iaSRNb250aGx5SW5jb21lIDw9IDQwMDBdIDwtICczMDAxLTQwMDAnDQpjb21iaSRJbmNvbWVDbGFzc1tjb21iaSRNb250aGx5SW5jb21lID4gNDAwMCAmIGNvbWJpJE1vbnRobHlJbmNvbWUgPD0gNjAwMF0gPC0gJzQwMDEtNjAwMCcNCmNvbWJpJEluY29tZUNsYXNzW2NvbWJpJE1vbnRobHlJbmNvbWUgPiA2MDAxICYgY29tYmkkTW9udGhseUluY29tZSA8PSA4MDAwXSA8LSAnNjAwMS04MDAwJw0KY29tYmkkSW5jb21lQ2xhc3NbY29tYmkkTW9udGhseUluY29tZSA+IDYwMDAgJiBjb21iaSRNb250aGx5SW5jb21lIDw9IDEwMDAwXSA8LSAnODAwMS0xMDAwMCcNCmNvbWJpJEluY29tZUNsYXNzW2NvbWJpJE1vbnRobHlJbmNvbWUgPiAxMDAwMCAmIGNvbWJpJE1vbnRobHlJbmNvbWUgPD0gMjAwMDBdIDwtICcxMDAwMS0yMDAwMCcNCmNvbWJpJEluY29tZUNsYXNzW2NvbWJpJE1vbnRobHlJbmNvbWUgPiAyMDAwMF0gPC0gJzIwMDAwKycNCmNvbWJpJEluY29tZUNsYXNzIDwtIGFzLmZhY3Rvcihjb21iaSRJbmNvbWVDbGFzcykNCg0KYGBgDQoNCk9uZSBvZiB0aGUgbW9zdCBpbnNpZ2h0ZnVsIHZhcmlhYmxlIGluIG15IG9waW5pb24gaXMgdGhlIGRlYnQgcmF0cmlvLCB0aGlzIGhhcyB0byBtYWtlIGEgbG8wdCBvZiBzZW5zZSBvbmNlIHdlIG1vZGVsIHRoZSBDcm9zcyBUYWJsZSBvZiBEZWJ0IFJhdGlvLg0KDQpgYGB7cn0NCmNvbWJpJERlYnRSYXRpb0NsYXNzIDwtICcxMDArJw0KY29tYmkkRGVidFJhdGlvQ2xhc3NbY29tYmkkRGVidFJhdGlvID49IDAgJiBjb21iaSREZWJ0UmF0aW8gPD0gMC41XSA8LSAnMC0wLjUnDQpjb21iaSREZWJ0UmF0aW9DbGFzc1tjb21iaSREZWJ0UmF0aW8gPiAwLjUgJiBjb21iaSREZWJ0UmF0aW8gPD0gMV0gPC0gJzAuNS0xJw0KY29tYmkkRGVidFJhdGlvQ2xhc3NbY29tYmkkRGVidFJhdGlvID4gMSAmIGNvbWJpJERlYnRSYXRpbyA8PSAyXSA8LSAnMS0yJw0KY29tYmkkRGVidFJhdGlvQ2xhc3NbY29tYmkkRGVidFJhdGlvID4gMiAmIGNvbWJpJERlYnRSYXRpbyA8PSAxMF0gPC0gJzItMTAnDQpjb21iaSREZWJ0UmF0aW9DbGFzc1tjb21iaSREZWJ0UmF0aW8gPiAxMCAmIGNvbWJpJERlYnRSYXRpbyA8PSAxMDBdIDwtICcxMC0xMDAnDQpjb21iaSREZWJ0UmF0aW9DbGFzcyA8LSBhcy5mYWN0b3IoY29tYmkkRGVidFJhdGlvQ2xhc3MpDQpzdW1tYXJ5KGNvbWJpJERlYnRSYXRpb0NsYXNzKQ0KQ3Jvc3NUYWJsZShjb21iaSREZWJ0UmF0aW9DbGFzcykNCkNyb3NzVGFibGUoY29tYmkkU2VyaW91c0RscWluMnlycyxjb21iaSREZWJ0UmF0aW9DbGFzcywgcHJvcC5yID0gVFJVRSwgcHJvcC5jID0gRkFMU0UsIHByb3AudCA9IEZBTFNFLCBwcm9wLmNoaXNxID0gRkFMU0UpDQpgYGANCg0KQXMgY2FuIGJlIHNlZW4gdXNpbmcgdGhlIGNvbXBhcmlzb24sIHRoaXMgbWFrZXMgcGVyZmVjdCBzZW5zZS4uDQoNClRoZSBuZXh0IGZlYXR1cmUgb2YgdGhlIGRhdGFzZXQgdG8gYmUgY29uc2lkZXJlZCwgaXMgdGhlIFJldm9sdmluZ1V0aWxpemF0aW9uT2ZVbnNlY3VyZWRMaW5lLCB0aGlzIHNob3VsZCBiZSBvbiB0aGUgc2FtZSBsaW5lcyBvZiB0aGF0IG9mIGRlYnQgcmF0aW8uDQoNCmBgYHtyfQ0Kc3VtbWFyeShjb21iaSROdW1iZXJPZk9wZW5DcmVkaXRMaW5lc0FuZExvYW5zKQ0KcGxvdChkZW5zaXR5KGNvbWJpJE51bWJlck9mT3BlbkNyZWRpdExpbmVzQW5kTG9hbnMpKQ0KI0xldCB1cyBtb2RlbCBjYXRlZ29yaWNhbGx5IGZvciB0aGlzIHZhcmlhYmxlDQpjb21iaSRPcGVuQ3JlZGl0Q2xhc3MgPC0gJzIwKycNCmNvbWJpJE9wZW5DcmVkaXRDbGFzc1tjb21iaSROdW1iZXJPZk9wZW5DcmVkaXRMaW5lc0FuZExvYW5zID49IDAgJiBjb21iaSROdW1iZXJPZk9wZW5DcmVkaXRMaW5lc0FuZExvYW5zPD01XSA8LSAnMC01Jw0KY29tYmkkT3BlbkNyZWRpdENsYXNzW2NvbWJpJE51bWJlck9mT3BlbkNyZWRpdExpbmVzQW5kTG9hbnMgPiA1ICYgY29tYmkkTnVtYmVyT2ZPcGVuQ3JlZGl0TGluZXNBbmRMb2Fuczw9MTBdIDwtICc1LTEwJw0KY29tYmkkT3BlbkNyZWRpdENsYXNzW2NvbWJpJE51bWJlck9mT3BlbkNyZWRpdExpbmVzQW5kTG9hbnMgPiAxMCAmIGNvbWJpJE51bWJlck9mT3BlbkNyZWRpdExpbmVzQW5kTG9hbnM8PTE1XSA8LSAnMTAtMTUnDQpjb21iaSRPcGVuQ3JlZGl0Q2xhc3NbY29tYmkkTnVtYmVyT2ZPcGVuQ3JlZGl0TGluZXNBbmRMb2FucyA+IDE1ICYgY29tYmkkTnVtYmVyT2ZPcGVuQ3JlZGl0TGluZXNBbmRMb2Fuczw9IDIwXSA8LSAnMTUtMjAnDQpjb21iaSRPcGVuQ3JlZGl0Q2xhc3MgPC0gYXMuZmFjdG9yKGNvbWJpJE9wZW5DcmVkaXRDbGFzcykNCkNyb3NzVGFibGUoY29tYmkkT3BlbkNyZWRpdENsYXNzKQ0KQ3Jvc3NUYWJsZShjb21iaSRTZXJpb3VzRGxxaW4yeXJzLGNvbWJpJE9wZW5DcmVkaXRDbGFzcywgcHJvcC5yID0gVFJVRSwgcHJvcC5jID0gRkFMU0UsIHByb3AudCA9IEZBTFNFLCBwcm9wLmNoaXNxID0gRkFMU0UpDQojaW5kZXBlbmRlbnRseSwgdGhpcyB2YXJpYWJsZSBkb2VzIG5vdCBzZWVtIGFzIGluc2lnaHRmdWwgYXMgb3RoZXIsIGJ1dCBpIGFtIGNlcnRhaW4gY29sbGVjdGl2ZWx5IGl0IHdpbGwgYmUgdmVyeSBpbXBvcnRhbnQuDQoNCmBgYA0KYGBge3J9DQpzdW1tYXJ5KGNvbWJpJE51bWJlclJlYWxFc3RhdGVMb2Fuc09yTGluZXMpDQpjb21iaSRSZWFsdHlMaW5lc0NsYXNzIDwtICczKycgDQpjb21iaSRSZWFsdHlMaW5lc0NsYXNzW2NvbWJpJE51bWJlclJlYWxFc3RhdGVMb2Fuc09yTGluZXMgPj0wICYgY29tYmkkTnVtYmVyUmVhbEVzdGF0ZUxvYW5zT3JMaW5lcyA8PSAxXSA8LSAnMC0xJw0KY29tYmkkUmVhbHR5TGluZXNDbGFzc1tjb21iaSROdW1iZXJSZWFsRXN0YXRlTG9hbnNPckxpbmVzID4xICYgY29tYmkkTnVtYmVyUmVhbEVzdGF0ZUxvYW5zT3JMaW5lcyA8PSAyXSA8LSAnMS0yJw0KY29tYmkkUmVhbHR5TGluZXNDbGFzc1tjb21iaSROdW1iZXJSZWFsRXN0YXRlTG9hbnNPckxpbmVzID4yICYgY29tYmkkTnVtYmVyUmVhbEVzdGF0ZUxvYW5zT3JMaW5lcyA8PSAzXSA8LSAnMi0zJw0KY29tYmkkUmVhbHR5TGluZXNDbGFzcyA8LSBhcy5mYWN0b3IoY29tYmkkUmVhbHR5TGluZXNDbGFzcykNCkNyb3NzVGFibGUoY29tYmkkUmVhbHR5TGluZXNDbGFzcykNCkNyb3NzVGFibGUoY29tYmkkU2VyaW91c0RscWluMnlycyxjb21iaSRSZWFsdHlMaW5lc0NsYXNzLCBwcm9wLnIgPSBUUlVFLCBwcm9wLmMgPSBGQUxTRSwgcHJvcC50ID0gRkFMU0UsIHByb3AuY2hpc3EgPSBGQUxTRSkNCnN0cihjb21iaSkNCmBgYA0KDQpBZnRlciB0dXJpbmcgYWxsIHRoZSBwYXJhbWV0ZXJzIHRoYXQgd2UgdGhpbmsgYXJlIGltcG9ydGFudCwgd2Ugbm93IGdvIGFoZWFkIHRvIHNvbHZlIHRoZSBwcmVkaWN0aXZlIG1vZGVsIGFuZCB1c2luZyBSYW5kb20gRm9yZXN0IEFsZ29yaXRobS4gDQoNCmBgYHtyfQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpzZXQuc2VlZCg4ODgpDQpucm93KGNvbWJpKQ0KdHJhaW4gPC0gY29tYmlbMToxNTAwMDAsXQ0KdGVzdCA8LSBjb21iaVsxNTAwMDE6MjUxNTAzLF0NCmBgYA0KVG8gZG8gdGhpcyB3ZSBoYXZlIGFnYWluIGRpdmlkZWQgdGhlIGNvbWJpIGRhdGFzZXQgaW50byB0cmFpbiBhbmQgdGVzdCBkYXRhc2V0cy4NCg0KYGBge3J9DQoNCmZpdCA8LSByYW5kb21Gb3Jlc3QoYXMuZmFjdG9yKFNlcmlvdXNEbHFpbjJ5cnMpIH4gTnVtYmVyT2ZUaW1lczkwRGF5c0xhdGUgKyBOdW1iZXJPZlRpbWU2MC44OURheXNQYXN0RHVlTm90V29yc2UNCiAgICAgICAgICAgICAgICAgICAgKyBOdW1iZXJPZlRpbWUzMC41OURheXNQYXN0RHVlTm90V29yc2UgKyBOdW1iZXJPZkRlcGVuZGVudHMgKyBBZ2VDbGFzcyArIERlYnRSYXRpb0NsYXNzICsNCiAgICAgICAgICAgICAgICAgICAgICBSZXZvbHZpbmdVdGlsaXphdGlvbk9mVW5zZWN1cmVkTGluZXMrIE9wZW5DcmVkaXRDbGFzcyArIFJlYWx0eUxpbmVzQ2xhc3MsDQogICAgICAgICAgICAgICAgICAgIGRhdGE9dHJhaW4sIA0KICAgICAgICAgICAgICAgICAgICBpbXBvcnRhbmNlPVRSVUUsIA0KICAgICAgICAgICAgICAgICAgICBudHJlZT0yNSwga2VlcC5mb3Jlc3QgPSBUUlVFKQ0KDQpzdW0oaXMubmEodHJhaW4kU2VyaW91c0RscWluMnlycykpDQoNClByZWRpY3Rpb24gPC0gcHJlZGljdChmaXQsIHRlc3QsIHR5cGUgPSAicHJvYiIpDQpzdWJtaXQgPC0gZGF0YS5mcmFtZShJZCA9IHRlc3QkWCwgUHJvYmFiaWxpdHkgPSBQcmVkaWN0aW9uKQ0KZW50cnkgPC0gZGF0YS5mcmFtZShJZCA9IHN1Ym1pdCRJZCwgUHJvYmFiaWxpdHkgPSBzdWJtaXQkUHJvYmFiaWxpdHkuMSkNCndyaXRlLmNzdihlbnRyeSwgZmlsZSA9ICJFbnRyeS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCm5yb3codGVzdCkNCg0KYGBgDQpMZXQncyBwbG90IGN0cmVlLi4NCg0KYGBge3J9DQpsaWJyYXJ5KCJwYXJ0eWtpdCIpDQp4IDwtIGN0cmVlKGFzLmZhY3RvcihTZXJpb3VzRGxxaW4yeXJzKSB+IE51bWJlck9mVGltZXM5MERheXNMYXRlICsgTnVtYmVyT2ZUaW1lNjAuODlEYXlzUGFzdER1ZU5vdFdvcnNlDQogICAgICAgICAgICsgTnVtYmVyT2ZUaW1lMzAuNTlEYXlzUGFzdER1ZU5vdFdvcnNlICsgTnVtYmVyT2ZEZXBlbmRlbnRzICsgQWdlQ2xhc3MgKyBEZWJ0UmF0aW9DbGFzcyArDQogICAgICAgICAgICAgUmV2b2x2aW5nVXRpbGl6YXRpb25PZlVuc2VjdXJlZExpbmVzICsgT3BlbkNyZWRpdENsYXNzICsgUmVhbHR5TGluZXNDbGFzcywNCiAgICAgICAgICAgZGF0YT10cmFpbikNCiAgDQpwbG90KHgsIGdwID0gZ3Bhcihmb250c2l6ZSA9IDUpLCAgICAgIyBmb250IHNpemUgY2hhbmdlZCB0byA2DQogICAgIGlubmVyX3BhbmVsPW5vZGVfaW5uZXIsDQogICAgIGlwX2FyZ3M9bGlzdCgNCiAgICAgICBhYmJyZXZpYXRlID0gRkFMU0UsIA0KICAgICAgIGlkID0gRkFMU0UpDQopDQpgYGANCg0KDQpXaGVuIHlvdSBzYXZlIHRoZSBub3RlYm9vaywgYW4gSFRNTCBmaWxlIGNvbnRhaW5pbmcgdGhlIGNvZGUgYW5kIG91dHB1dCB3aWxsIGJlIHNhdmVkIGFsb25nc2lkZSBpdCAoY2xpY2sgdGhlICpQcmV2aWV3KiBidXR0b24gb3IgcHJlc3MgKkN0cmwrU2hpZnQrSyogdG8gcHJldmlldyB0aGUgSFRNTCBmaWxlKS4NCg==