This project explores churn management within the cellular telecommunications industry. The primary objective is to develop a predictive model for customer churn and leverage its insights to design a strategic incentive plan aimed at customer retention.

Step 1: Data Exploration

print(dim(df))
[1] 71047    69

The data set consists of 69 attributes (columns) for 71,047 customers (rows), with the Churn column indicating whether a customer left the company two months after observation. The available data includes the following categories: * Customer’s Phone Usage - Monthly Call Minutes, Recurring Charges, Roaming Calls, Blocked Calls, Inbound Calls * Customer’s Phone Equipment - Handset Model, CurrentEquipmentDays, HandsetRefurbished?, HandsetWebCapable? * Customer Demographics - CreditRating (ex: A, AA), PrizmCode (ex: Rural, Town), Occupation (ex: Professor, Clerical), Marital Status * Advertising Data - BuysViaMailOrder?, RespondsToMailOffers?

dim(df)
[1] 69309    69

Correlation Between Numerical Variables:

Given the large number of features in the dataset, the first step is to identify variables with high correlations, as these may introduce bias into the model. Removing highly correlated variables offers several advantages, including reducing potential bias, improving model robustness, and enhancing interpretability.

From the correlation plot, we observe high correlations between the following variables: 1. Handset & HandsetPrice - 0.91 2. Handset & HandsetModel - 0.91 2. DroppedCalls & DroppedBlockedCalls - 0.88 3. DroppedBlockedCalls, PeakCallsInOut, OffPeakCallsInOut - show high correlations with several other predictors.

As a result, HandsetModel and HandsetPrice were removed, as their effects were already captured by Handset. Additionally, DroppedBlockedCalls, PeakCallsInOut, and OffPeakCallsInOut were dropped due to their high correlation with multiple other variables.

dim(df)
[1] 69309    64
#Scale the Dataset
df_scaled <- as.data.frame(lapply(df, function(x) {
  if(is.numeric(x)) {
    (x - min(x)) / (max(x) - min(x))
  } else {
    x
  }
}))

Step 2: Split the Data

Before proceeding with further steps such as feature selection and modeling, we will first split the dataset into a calibration set and a test set.

print(dim(test.data))
[1] 30368    62

Step 3: Feature Selection

To select the most relevant predictors for the predictive model, I employed both Forward and Backward Feature Selection methods. These techniques help identify the optimal subset of variables that maximize the model’s predictive performance.

Backward selection begins with a model that includes all variables and iteratively removes the least important features, evaluating the model’s performance at each step. In contrast, Forward selection starts with a model that only contains the intercept, gradually adding the most significant features at each iteration.

summary(logit.backward)

Call:
glm(formula = Churn ~ MonthlyRevenue + MonthlyMinutes + TotalRecurringCharge + 
    OverageMinutes + RoamingCalls + PercChangeMinutes + PercChangeRevenues + 
    DroppedCalls + BlockedCalls + CustomerCareCalls + ThreewayCalls + 
    InboundCalls + MonthsInService + UniqueSubs + ActiveSubs + 
    Handsets + CurrentEquipmentDays + AgeHH1 + ChildrenInHH. + 
    CreditRatingA. + CreditRatingAA. + PrizmCodeSuburban. + HandsetRefurbished. + 
    HandsetWebCapable. + OccupationHomemaker. + MaritalStatusMissing. + 
    RespondsToMailOffers. + NewCellphoneUser. + OwnsMotorcycle. + 
    HandsetPriceMissing. + MadeCallToRetentionTeam., family = "binomial", 
    data = calibrat.data)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.5199  -1.1389  -0.6925   1.1488   2.4782  

Coefficients:
                           Estimate Std. Error z value Pr(>|z|)    
(Intercept)                -0.33623    0.33043  -1.018 0.308902    
MonthlyRevenue              2.28941    0.96200   2.380 0.017321 *  
MonthlyMinutes             -2.03429    0.29494  -6.897 5.30e-12 ***
TotalRecurringCharge       -1.39297    0.35079  -3.971 7.16e-05 ***
OverageMinutes              3.22493    1.18550   2.720 0.006522 ** 
RoamingCalls                8.15306    2.28447   3.569 0.000358 ***
PercChangeMinutes          -4.45759    0.48145  -9.259  < 2e-16 ***
PercChangeRevenues          8.32346    1.32086   6.302 2.95e-10 ***
DroppedCalls                1.81708    0.33950   5.352 8.69e-08 ***
BlockedCalls                0.97697    0.35099   2.783 0.005378 ** 
CustomerCareCalls          -1.95427    0.89980  -2.172 0.029864 *  
ThreewayCalls              -1.89818    0.73479  -2.583 0.009786 ** 
InboundCalls               -1.21619    0.42089  -2.890 0.003858 ** 
MonthsInService            -1.23041    0.10226 -12.032  < 2e-16 ***
UniqueSubs                 35.75536    3.88456   9.204  < 2e-16 ***
ActiveSubs                -10.90967    1.47409  -7.401 1.35e-13 ***
Handsets                    1.52622    0.33966   4.493 7.01e-06 ***
CurrentEquipmentDays        2.63237    0.13256  19.857  < 2e-16 ***
AgeHH1                     -0.32529    0.06370  -5.107 3.28e-07 ***
ChildrenInHH.1              0.10548    0.02656   3.971 7.15e-05 ***
CreditRatingA.1            -0.17497    0.03528  -4.959 7.09e-07 ***
CreditRatingAA.1           -0.36479    0.03426 -10.646  < 2e-16 ***
PrizmCodeSuburban.1        -0.06211    0.02237  -2.776 0.005503 ** 
HandsetRefurbished.1        0.23519    0.03169   7.422 1.15e-13 ***
HandsetWebCapable.1        -0.15784    0.03744  -4.216 2.49e-05 ***
OccupationHomemaker.1       0.28766    0.18910   1.521 0.128214    
MaritalStatusMissing.1      0.06429    0.02739   2.347 0.018921 *  
RespondsToMailOffers.1     -0.12937    0.02632  -4.915 8.89e-07 ***
NewCellphoneUser.1         -0.07196    0.02650  -2.716 0.006612 ** 
OwnsMotorcycle.1            0.14334    0.08785   1.632 0.102758    
HandsetPriceMissing.1      -0.14887    0.03351  -4.442 8.91e-06 ***
MadeCallToRetentionTeam.1   0.73769    0.05773  12.779  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 53983  on 38940  degrees of freedom
Residual deviance: 52340  on 38909  degrees of freedom
AIC: 52404

Number of Fisher Scoring iterations: 4
summary(reality.forward)

Call:
glm(formula = Churn ~ CurrentEquipmentDays + MadeCallToRetentionTeam. + 
    CreditRatingAA. + AgeHH1 + HandsetRefurbished. + MonthsInService + 
    PercChangeMinutes + UniqueSubs + ActiveSubs + TotalRecurringCharge + 
    MonthlyRevenue + MonthlyMinutes + PercChangeRevenues + HandsetPriceMissing. + 
    DroppedCalls + CreditRatingA. + RespondsToMailOffers. + HandsetWebCapable. + 
    Handsets + ChildrenInHH. + RoamingCalls + InboundCalls + 
    NewCellphoneUser. + PrizmCodeSuburban. + OverageMinutes + 
    MaritalStatusMissing. + ThreewayCalls + BlockedCalls + CustomerCareCalls + 
    OwnsMotorcycle. + OccupationHomemaker., family = "binomial", 
    data = calibrat.data)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.5199  -1.1389  -0.6925   1.1488   2.4782  

Coefficients:
                           Estimate Std. Error z value Pr(>|z|)    
(Intercept)                -0.33623    0.33043  -1.018 0.308902    
CurrentEquipmentDays        2.63237    0.13256  19.857  < 2e-16 ***
MadeCallToRetentionTeam.1   0.73769    0.05773  12.779  < 2e-16 ***
CreditRatingAA.1           -0.36479    0.03426 -10.646  < 2e-16 ***
AgeHH1                     -0.32529    0.06370  -5.107 3.28e-07 ***
HandsetRefurbished.1        0.23519    0.03169   7.422 1.15e-13 ***
MonthsInService            -1.23041    0.10226 -12.032  < 2e-16 ***
PercChangeMinutes          -4.45759    0.48145  -9.259  < 2e-16 ***
UniqueSubs                 35.75536    3.88456   9.204  < 2e-16 ***
ActiveSubs                -10.90967    1.47409  -7.401 1.35e-13 ***
TotalRecurringCharge       -1.39297    0.35079  -3.971 7.16e-05 ***
MonthlyRevenue              2.28941    0.96200   2.380 0.017321 *  
MonthlyMinutes             -2.03429    0.29494  -6.897 5.30e-12 ***
PercChangeRevenues          8.32346    1.32086   6.302 2.95e-10 ***
HandsetPriceMissing.1      -0.14887    0.03351  -4.442 8.91e-06 ***
DroppedCalls                1.81708    0.33950   5.352 8.69e-08 ***
CreditRatingA.1            -0.17497    0.03528  -4.959 7.09e-07 ***
RespondsToMailOffers.1     -0.12937    0.02632  -4.915 8.89e-07 ***
HandsetWebCapable.1        -0.15784    0.03744  -4.216 2.49e-05 ***
Handsets                    1.52622    0.33966   4.493 7.01e-06 ***
ChildrenInHH.1              0.10548    0.02656   3.971 7.15e-05 ***
RoamingCalls                8.15306    2.28447   3.569 0.000358 ***
InboundCalls               -1.21619    0.42089  -2.890 0.003858 ** 
NewCellphoneUser.1         -0.07196    0.02650  -2.716 0.006612 ** 
PrizmCodeSuburban.1        -0.06211    0.02237  -2.776 0.005503 ** 
OverageMinutes              3.22493    1.18550   2.720 0.006522 ** 
MaritalStatusMissing.1      0.06429    0.02739   2.347 0.018921 *  
ThreewayCalls              -1.89818    0.73479  -2.583 0.009786 ** 
BlockedCalls                0.97697    0.35099   2.783 0.005378 ** 
CustomerCareCalls          -1.95427    0.89980  -2.172 0.029864 *  
OwnsMotorcycle.1            0.14334    0.08785   1.632 0.102758    
OccupationHomemaker.1       0.28766    0.18910   1.521 0.128214    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 53983  on 38940  degrees of freedom
Residual deviance: 52340  on 38909  degrees of freedom
AIC: 52404

Number of Fisher Scoring iterations: 4

Both forward and backward selection models returned a similar set of features:

Churn ~ CurrentEquipmentDays + MadeCallToRetentionTeam. + 
    CreditRatingAA. + AgeHH1 + HandsetRefurbished. + MonthsInService + 
    PercChangeMinutes + UniqueSubs + ActiveSubs + TotalRecurringCharge + 
    MonthlyRevenue + MonthlyMinutes + PercChangeRevenues + HandsetPriceMissing. + 
    DroppedCalls + CreditRatingA. + RespondsToMailOffers. + HandsetWebCapable. + 
    Handsets + ChildrenInHH. + RoamingCalls + InboundCalls + 
    NewCellphoneUser. + PrizmCodeSuburban. + OverageMinutes + 
    MaritalStatusMissing. + ThreewayCalls + BlockedCalls + CustomerCareCalls + 
    OwnsMotorcycle. + OccupationHomemaker.

Step 4: Logistic Regression

Using the selected features, we can train a logistic regression model on the calibration dataset and evaluate its performance on the unseen test dataset.

summary(logit.model)

Call:
glm(formula = Churn ~ MonthlyRevenue + MonthlyMinutes + TotalRecurringCharge + 
    OverageMinutes + RoamingCalls + PercChangeMinutes + PercChangeRevenues + 
    DroppedCalls + BlockedCalls + CustomerCareCalls + ThreewayCalls + 
    InboundCalls + MonthsInService + UniqueSubs + ActiveSubs + 
    Handsets + CurrentEquipmentDays + AgeHH1 + ChildrenInHH. + 
    CreditRatingA. + CreditRatingAA. + PrizmCodeSuburban. + HandsetRefurbished. + 
    HandsetWebCapable. + OccupationHomemaker. + MaritalStatusMissing. + 
    RespondsToMailOffers. + NewCellphoneUser. + OwnsMotorcycle. + 
    HandsetPriceMissing. + MadeCallToRetentionTeam., family = "binomial", 
    data = calibrat.data)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.5199  -1.1389  -0.6925   1.1488   2.4782  

Coefficients:
                           Estimate Std. Error z value Pr(>|z|)    
(Intercept)                -0.33623    0.33043  -1.018 0.308902    
MonthlyRevenue              2.28941    0.96200   2.380 0.017321 *  
MonthlyMinutes             -2.03429    0.29494  -6.897 5.30e-12 ***
TotalRecurringCharge       -1.39297    0.35079  -3.971 7.16e-05 ***
OverageMinutes              3.22493    1.18550   2.720 0.006522 ** 
RoamingCalls                8.15306    2.28447   3.569 0.000358 ***
PercChangeMinutes          -4.45759    0.48145  -9.259  < 2e-16 ***
PercChangeRevenues          8.32346    1.32086   6.302 2.95e-10 ***
DroppedCalls                1.81708    0.33950   5.352 8.69e-08 ***
BlockedCalls                0.97697    0.35099   2.783 0.005378 ** 
CustomerCareCalls          -1.95427    0.89980  -2.172 0.029864 *  
ThreewayCalls              -1.89818    0.73479  -2.583 0.009786 ** 
InboundCalls               -1.21619    0.42089  -2.890 0.003858 ** 
MonthsInService            -1.23041    0.10226 -12.032  < 2e-16 ***
UniqueSubs                 35.75536    3.88456   9.204  < 2e-16 ***
ActiveSubs                -10.90967    1.47409  -7.401 1.35e-13 ***
Handsets                    1.52622    0.33966   4.493 7.01e-06 ***
CurrentEquipmentDays        2.63237    0.13256  19.857  < 2e-16 ***
AgeHH1                     -0.32529    0.06370  -5.107 3.28e-07 ***
ChildrenInHH.1              0.10548    0.02656   3.971 7.15e-05 ***
CreditRatingA.1            -0.17497    0.03528  -4.959 7.09e-07 ***
CreditRatingAA.1           -0.36479    0.03426 -10.646  < 2e-16 ***
PrizmCodeSuburban.1        -0.06211    0.02237  -2.776 0.005503 ** 
HandsetRefurbished.1        0.23519    0.03169   7.422 1.15e-13 ***
HandsetWebCapable.1        -0.15784    0.03744  -4.216 2.49e-05 ***
OccupationHomemaker.1       0.28766    0.18910   1.521 0.128214    
MaritalStatusMissing.1      0.06429    0.02739   2.347 0.018921 *  
RespondsToMailOffers.1     -0.12937    0.02632  -4.915 8.89e-07 ***
NewCellphoneUser.1         -0.07196    0.02650  -2.716 0.006612 ** 
OwnsMotorcycle.1            0.14334    0.08785   1.632 0.102758    
HandsetPriceMissing.1      -0.14887    0.03351  -4.442 8.91e-06 ***
MadeCallToRetentionTeam.1   0.73769    0.05773  12.779  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 53983  on 38940  degrees of freedom
Residual deviance: 52340  on 38909  degrees of freedom
AIC: 52404

Number of Fisher Scoring iterations: 4
confusionMatrix(as.factor(as.numeric(test.data$predict_logit > 0.5)), as.factor(test.data$Churn))
Confusion Matrix and Statistics

          Reference
Prediction     0     1
         0 17893   252
         1 11889   334
                                          
               Accuracy : 0.6002          
                 95% CI : (0.5947, 0.6057)
    No Information Rate : 0.9807          
    P-Value [Acc > NIR] : 1               
                                          
                  Kappa : 0.0159          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.60080         
            Specificity : 0.56997         
         Pos Pred Value : 0.98611         
         Neg Pred Value : 0.02733         
             Prevalence : 0.98070         
         Detection Rate : 0.58921         
   Detection Prevalence : 0.59750         
      Balanced Accuracy : 0.58538         
                                          
       'Positive' Class : 0               
                                          
print(paste0("Recall = ", recall(as.factor(as.numeric(test.data$predict_logit > 0.5)), as.factor(test.data$Churn))))
[1] "Recall = 0.600799140420388"

Based on the confusion matrix, our model correctly identifies 17,893 non-churners and 334 churners. However, the model’s overall accuracy is only 60%, primarily due to misclassifying 11,889 non-churners as churners. This error is attributed to the high class imbalance in the dataset, as only about 2% of customers actually churn.

The organization can assess the costs associated with two types of errors: Type I error, where additional incentives are provided to a customer who is unlikely to churn, and Type II error, where a customer who actually churns is not provided with incentives due to the prediction that they would not churn. By evaluating these costs, the company could decide if a different threshold, other than 50%, should be applied to classify customers as likely to churn.

Despite the low overall accuracy, the model demonstrates an exceptionally high precision, with 98.6% of customers predicted to churn actually doing so. This precision is valuable for the company, as it ensures that resources and incentives are not wasted on customers who are unlikely to churn.

The model can also be re-run with a different threshold for classifying churners and non-churners. For instance, we could test the performance of the model by predicting a customer as a churner only when their probability of churning exceeds 70%, instead of the default 50% threshold.

print(confusionMatrix(as.factor(as.numeric(test.data$predict_logit > 0.7)), as.factor(test.data$Churn)))
Confusion Matrix and Statistics

          Reference
Prediction     0     1
         0 29293   562
         1   489    24
                                          
               Accuracy : 0.9654          
                 95% CI : (0.9633, 0.9674)
    No Information Rate : 0.9807          
    P-Value [Acc > NIR] : 1.00000         
                                          
                  Kappa : 0.0261          
                                          
 Mcnemar's Test P-Value : 0.02636         
                                          
            Sensitivity : 0.98358         
            Specificity : 0.04096         
         Pos Pred Value : 0.98118         
         Neg Pred Value : 0.04678         
             Prevalence : 0.98070         
         Detection Rate : 0.96460         
   Detection Prevalence : 0.98311         
      Balanced Accuracy : 0.51227         
                                          
       'Positive' Class : 0               
                                          
print(paste0("Recall (70% threshold) = ", recall(as.factor(as.numeric(test.data$predict_logit > 0.7)), as.factor(test.data$Churn))))
[1] "Recall (70% threshold) = 0.983580686320596"

The performance of the re-adjusted model significantly improved, achieving an accuracy of 96.5%. The recall saw a substantial increase, rising from 60% (with the 50% threshold) to 98.4%. However, this came at the cost of a slight decrease in precision, which dropped from 98.6% to 98.1%.

Both models demonstrate strengths in different aspects of classification, and the company can choose which model to use based on their specific business goals: * Minimizing the Risk of Missing a Churner (business cost: losing customers): * Model I (50% threshold): Fewer churners were predicted to be non-churners (252). * Model II (70% threshold): More churners were predicted as non-churners (562). * Minimizing the Cost of Incorrectly Identifying a Churner (business cost: spending unnecessary resources on customers who won’t churn): * Model I (50% threshold): A high number of non-churners were predicted to churn (11,889). * Model II (70% threshold): Fewer non-churners were predicted to churn (489).

Step 5: Actionable Insights

The final step of this project is to identify the key factors that contribute most to customer churn. This analysis can provide valuable insights to help the telecommunications company design a contingency-based incentive plan aimed at reducing churn.

The quantitative importance of the variables is calculated as:
* Numeric Variables: Standard Deviation of the Feature x Marginal Effect of the Feature * Categorical Variables: Marginal Effect of the Feature

Based on this, we can calculate the importance of all features, and rank them based on their importance.

The results above highlight the predictors in our dataset that have the greatest impact on a customer’s likelihood to churn. Key factors influencing churn include whether the customer has contacted the retention team, the number of days they have used their current equipment, their customer profile (such as credit rating), and details about their equipment and handsets.

It is important to note that while certain features, such as whether the customer is a homemaker or owns a motorcycle, are identified as important in the model, they are not statistically significant in predicting customer churn.

Based on this analysis, some recommended retention actions include:

LS0tDQp0aXRsZTogIkNodXJuIE1vZGVsaW5nIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KVGhpcyBwcm9qZWN0IGV4cGxvcmVzIGNodXJuIG1hbmFnZW1lbnQgd2l0aGluIHRoZSBjZWxsdWxhciB0ZWxlY29tbXVuaWNhdGlvbnMgaW5kdXN0cnkuIFRoZSBwcmltYXJ5IG9iamVjdGl2ZSBpcyB0byBkZXZlbG9wIGEgcHJlZGljdGl2ZSBtb2RlbCBmb3IgY3VzdG9tZXIgY2h1cm4gYW5kIGxldmVyYWdlIGl0cyBpbnNpZ2h0cyB0byBkZXNpZ24gYSBzdHJhdGVnaWMgaW5jZW50aXZlIHBsYW4gYWltZWQgYXQgY3VzdG9tZXIgcmV0ZW50aW9uLg0KIA0KIyMjICoqU3RlcCAxOiBEYXRhIEV4cGxvcmF0aW9uKioNCg0KYGBge3IsIGluY2x1ZGU9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KaW5zdGFsbC5wYWNrYWdlcygnY29ycnBsb3QnKQ0KaW5zdGFsbC5wYWNrYWdlcygnbWZ4JykNCmluc3RhbGwucGFja2FnZXMoJ2RwbHlyJykNCmluc3RhbGwucGFja2FnZXMoJ2NhcmV0JykNCg0KbGlicmFyeShtZngpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkocFJPQykNCmBgYA0KDQpgYGB7cn0NCmRmIDwtIHJlYWQuY3N2KCdjZWxsMmNlbGxkYXRhLmNzdicpDQpkZiA8LSBhcy5kYXRhLmZyYW1lKGRmKQ0KDQpwcmludChoZWFkKGRmKSkNCmBgYA0KYGBge3J9DQpwcmludChkaW0oZGYpKQ0KYGBgDQoNClRoZSBkYXRhIHNldCBjb25zaXN0cyBvZiA2OSBhdHRyaWJ1dGVzIChjb2x1bW5zKSBmb3IgNzEsMDQ3IGN1c3RvbWVycyAocm93cyksIHdpdGggdGhlIGBDaHVybmAgY29sdW1uIGluZGljYXRpbmcgd2hldGhlciBhIGN1c3RvbWVyIGxlZnQgdGhlIGNvbXBhbnkgdHdvIG1vbnRocyBhZnRlciBvYnNlcnZhdGlvbi4gVGhlIGF2YWlsYWJsZSBkYXRhIGluY2x1ZGVzIHRoZSBmb2xsb3dpbmcgY2F0ZWdvcmllczoNCiAgKiA8dT4gQ3VzdG9tZXIncyBQaG9uZSBVc2FnZTwvdT4gLSBNb250aGx5IENhbGwgTWludXRlcywgUmVjdXJyaW5nIENoYXJnZXMsIFJvYW1pbmcgQ2FsbHMsIEJsb2NrZWQgQ2FsbHMsIEluYm91bmQgQ2FsbHMNCiAgKiA8dT4gQ3VzdG9tZXIncyBQaG9uZSBFcXVpcG1lbnQ8L3U+IC0gSGFuZHNldCBNb2RlbCwgQ3VycmVudEVxdWlwbWVudERheXMsIEhhbmRzZXRSZWZ1cmJpc2hlZD8sIEhhbmRzZXRXZWJDYXBhYmxlPw0KICAqIDx1PiBDdXN0b21lciBEZW1vZ3JhcGhpY3M8L3U+IC0gQ3JlZGl0UmF0aW5nIChleDogQSwgQUEpLCBQcml6bUNvZGUgKGV4OiBSdXJhbCwgVG93biksIE9jY3VwYXRpb24gKGV4OiBQcm9mZXNzb3IsIENsZXJpY2FsKSwgTWFyaXRhbCBTdGF0dXMNCiAgKiA8dT5BZHZlcnRpc2luZyBEYXRhPC91PiAtIEJ1eXNWaWFNYWlsT3JkZXI/LCBSZXNwb25kc1RvTWFpbE9mZmVycz8NCiAgDQpgYGB7cn0NCiMgUHJlLXByb2Nlc3NpbmcgRGF0YQ0KDQojQ2hlY2sgZm9yIE1pc3NpbmcgVmFsdWVzDQpuYV9jb3VudCA8LSBzYXBwbHkoZGYsIGZ1bmN0aW9uKHkpIHN1bShsZW5ndGgod2hpY2goaXMubmEoeSkpKSkpDQoNCiNEcm9wIHRoZSBSZWNvcmRzIHdpdGggTWlzc2luZyBEYXRhDQpkZiA8LSBuYS5vbWl0KGRmKQ0KZGltKGRmKQ0KDQojQ29udmVydCB0aGUgQ2F0ZWdvcmljYWwgQ29sdW1ucyB0byBGYWN0b3INCmNhdF9jb2xzIDwtIGMoJ2NhbGlicmF0JywgJ0NoaWxkcmVuSW5ISC4nLCAnQ3JlZGl0UmF0aW5nQS4nLCAnQ3JlZGl0UmF0aW5nQUEuJywgJ1ByaXptQ29kZVJ1cmFsLicsICdQcml6bUNvZGVTdWJ1cmJhbi4nLCAnUHJpem1Db2RlVG93bi4nLCAnSGFuZHNldFJlZnVyYmlzaGVkLicsICdIYW5kc2V0V2ViQ2FwYWJsZS4nLCAnVHJ1Y2tPd25lci4nLCAnUlZPd25lci4nLCAnT2NjdXBhdGlvblByb2Zlc3Nvci4nLCAnT2NjdXBhdGlvbkNsZXJpY2FsLicsICdPY2N1cGF0aW9uQ3JhZnRzLicsICdPY2N1cGF0aW9uU3R1ZGVudC4nLCAnT2NjdXBhdGlvbkhvbWVtYWtlci4nLCAnT2NjdXBhdGlvblJldGlyZWQuJywgJ09jY3VwYXRpb25TZWxmZW1wbG95ZWQuJywgJ0hvbWVvd25lcnNoaXBVbmtub3duLicsICdNYXJpdGFsU3RhdHVzTWlzc2luZy4nLCAnTWFyaXRhbFN0YXR1cycsICdCdXlzVmlhTWFpbE9yZGVyLicsICdSZXNwb25kc1RvTWFpbE9mZmVycy4nLCAnT3B0T3V0TWFpbGluZ3MuJywgJ05vblVTVHJhdmVsLicsICdPd25zQ29tcHV0ZXIuJywgJ0hhc0NyZWRpdENhcmQuJywNCidOZXdDZWxscGhvbmVVc2VyLicsICdOb3ROZXdDZWxscGhvbmVVc2VyLicsICdJbmNvbWVHcm91cE1pc3NpbmcuJywgJ0luY29tZUdyb3VwJywgJ093bnNNb3RvcmN5Y2xlLicsICdIYW5kc2V0UHJpY2VNaXNzaW5nLicsICdNYWRlQ2FsbFRvUmV0ZW50aW9uVGVhbS4nKQ0KDQpkZltjYXRfY29sc10gPC0gbGFwcGx5KGRmW2NhdF9jb2xzXSwgZmFjdG9yKQ0KYGBgDQoNCioqQ29ycmVsYXRpb24gQmV0d2VlbiBOdW1lcmljYWwgVmFyaWFibGVzOioqDQoNCkdpdmVuIHRoZSBsYXJnZSBudW1iZXIgb2YgZmVhdHVyZXMgaW4gdGhlIGRhdGFzZXQsIHRoZSBmaXJzdCBzdGVwIGlzIHRvIGlkZW50aWZ5IHZhcmlhYmxlcyB3aXRoIGhpZ2ggY29ycmVsYXRpb25zLCBhcyB0aGVzZSBtYXkgaW50cm9kdWNlIGJpYXMgaW50byB0aGUgbW9kZWwuIFJlbW92aW5nIGhpZ2hseSBjb3JyZWxhdGVkIHZhcmlhYmxlcyBvZmZlcnMgc2V2ZXJhbCBhZHZhbnRhZ2VzLCBpbmNsdWRpbmcgcmVkdWNpbmcgcG90ZW50aWFsIGJpYXMsIGltcHJvdmluZyBtb2RlbCByb2J1c3RuZXNzLCBhbmQgZW5oYW5jaW5nIGludGVycHJldGFiaWxpdHkuDQoNCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTV9DQpkZl9udW1lcmljYWxfY29scyA8LSBkZiAlPiUgZHBseXI6OnNlbGVjdCgtb25lX29mKGNhdF9jb2xzKSkNCg0KIyMgVW5jb21tZW50IHRoZSBzZWN0aW9uIGJlbG93IHRvIGdldCB0aGUgZXhhY3QgY29ycmVsYXRpb24gY29lZmZpY2llbnRzIGJldHdlZW4gZWFjaCBwYWlyIG9mIG51bWVyaWNhbCBjb2x1bW5zOg0KDQojZGZfY29sX25hbWVzIDwtIGMoIkZlYXR1cmUxIiwgIkZlYXR1cmUyIiwgIkNvcnJlbGF0aW9uIikNCiNjb3JyLmRmIDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSAwLCBuY29sID0gbGVuZ3RoKGRmX2NvbF9uYW1lcykpKQ0KDQojZm9yIChjb2wxIGluIGNvbG5hbWVzKGRmX251bWVyaWNhbF9jb2xzKSl7DQojICBmb3IgKGNvbDIgaW4gY29sbmFtZXMoZGZfbnVtZXJpY2FsX2NvbHMpKXsNCiMgICAgY29ycmVsYXRpb25fdmFsdWUgPC0gY29yKGRmX251bWVyaWNhbF9jb2xzW2NvbDFdLCBkZl9udW1lcmljYWxfY29sc1tjb2wyXSwgbWV0aG9kID0gJ3NwZWFybWFuJywgdXNlID0gJ2NvbXBsZXRlLm9icycpDQojICAgIGlmIChjb3JyZWxhdGlvbl92YWx1ZVsxXSA+IDAuNzUgJiBjb2wxIT0gY29sMil7DQojICAgICAgcm93IDwtIGMoY29sMSwgY29sMiwgY29ycmVsYXRpb25fdmFsdWVbMV0pDQojICAgICAgY29yci5kZiA9IHJiaW5kKGNvcnIuZGYsIHJvdykNCiMgICAgfQ0KIyAgfQ0KI30NCg0KI2NvbG5hbWVzKGNvcnIuZGYpIDwtIGRmX2NvbF9uYW1lcw0KI2NvcnIuZGYNCg0KI1Bsb3QgdGhlIENvcnJlbGF0aW9ucw0KbGlicmFyeShjb3JycGxvdCkNCnBuZyhmaWxlbmFtZSA9ICdjb3JyZWxhdGlvbl9wbG90LnBuZycsIHdpZHRoID0gMjQwMCwgaGVpZ2h0ID0gMTYwMCkNCmNvcnJwbG90KGNvcihkZl9udW1lcmljYWxfY29scywgbWV0aG9kID0gJ3NwZWFybWFuJywgdXNlID0gJ2NvbXBsZXRlLm9icycpLCBtZXRob2QgPSAnY29sb3InLCB0eXBlID0gJ3VwcGVyJywgb3JkZXIgPSAnaGNsdXN0JywgdGwuY29sID0gJ2JsYWNrJywgdGwuc3J0ID0gOTAsIHRsLmNleCA9IDAuNywgY2wuY2V4ID0gMSwgYWRkQ29lZi5jb2wgPSAnYmxhY2snLCBudW1iZXIuY2V4ID0gMC43KQ0KYGBgDQoNCkZyb20gdGhlIGNvcnJlbGF0aW9uIHBsb3QsIHdlIG9ic2VydmUgaGlnaCBjb3JyZWxhdGlvbnMgYmV0d2VlbiB0aGUgZm9sbG93aW5nIHZhcmlhYmxlczoNCiAgMS4gSGFuZHNldCAmIEhhbmRzZXRQcmljZSAtIDAuOTENCiAgMi4gSGFuZHNldCAmIEhhbmRzZXRNb2RlbCAtIDAuOTENCiAgMi4gRHJvcHBlZENhbGxzICYgRHJvcHBlZEJsb2NrZWRDYWxscyAtIDAuODgNCiAgMy4gRHJvcHBlZEJsb2NrZWRDYWxscywgUGVha0NhbGxzSW5PdXQsIE9mZlBlYWtDYWxsc0luT3V0IC0gc2hvdyBoaWdoIGNvcnJlbGF0aW9ucyB3aXRoIHNldmVyYWwgb3RoZXIgcHJlZGljdG9ycy4NCiAgDQpBcyBhIHJlc3VsdCwgSGFuZHNldE1vZGVsIGFuZCBIYW5kc2V0UHJpY2Ugd2VyZSByZW1vdmVkLCBhcyB0aGVpciBlZmZlY3RzIHdlcmUgYWxyZWFkeSBjYXB0dXJlZCBieSBIYW5kc2V0LiBBZGRpdGlvbmFsbHksIERyb3BwZWRCbG9ja2VkQ2FsbHMsIFBlYWtDYWxsc0luT3V0LCBhbmQgT2ZmUGVha0NhbGxzSW5PdXQgd2VyZSBkcm9wcGVkIGR1ZSB0byB0aGVpciBoaWdoIGNvcnJlbGF0aW9uIHdpdGggbXVsdGlwbGUgb3RoZXIgdmFyaWFibGVzLg0KDQpgYGB7cn0NCmRmIDwtIHN1YnNldChkZiwgc2VsZWN0ID0gLWMoSGFuZHNldE1vZGVscywgSGFuZHNldFByaWNlLCBEcm9wcGVkQmxvY2tlZENhbGxzLCBQZWFrQ2FsbHNJbk91dCwgT2ZmUGVha0NhbGxzSW5PdXQpKQ0KZGltKGRmKQ0KYGBgDQpgYGB7cn0NCiNTY2FsZSB0aGUgRGF0YXNldA0KZGZfc2NhbGVkIDwtIGFzLmRhdGEuZnJhbWUobGFwcGx5KGRmLCBmdW5jdGlvbih4KSB7DQogIGlmKGlzLm51bWVyaWMoeCkpIHsNCiAgICAoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKQ0KICB9IGVsc2Ugew0KICAgIHgNCiAgfQ0KfSkpDQpgYGANCg0KIyMjICoqU3RlcCAyOiBTcGxpdCB0aGUgRGF0YSoqDQoNCkJlZm9yZSBwcm9jZWVkaW5nIHdpdGggZnVydGhlciBzdGVwcyBzdWNoIGFzIGZlYXR1cmUgc2VsZWN0aW9uIGFuZCBtb2RlbGluZywgd2Ugd2lsbCBmaXJzdCBzcGxpdCB0aGUgZGF0YXNldCBpbnRvIGEgY2FsaWJyYXRpb24gc2V0IGFuZCBhIHRlc3Qgc2V0Lg0KDQpgYGB7cn0NCmNhbGlicmF0LmRhdGEgPC0gZGZfc2NhbGVkW2RmX3NjYWxlZCRjYWxpYnJhdCA9PSAxLCBdDQp0ZXN0LmRhdGEgPC0gZGZfc2NhbGVkW2RmX3NjYWxlZCRjYWxpYnJhdCA9PSAwLCBdDQoNCmNhbGlicmF0LmRhdGEgPC0gY2FsaWJyYXQuZGF0YVssIGMoLTEsIC0yKV0NCnRlc3QuZGF0YSA8LSB0ZXN0LmRhdGFbLCBjKC0xLCAtMildDQoNCnByaW50KGRpbShjYWxpYnJhdC5kYXRhKSkNCnByaW50KGRpbSh0ZXN0LmRhdGEpKQ0KYGBgDQoNCiMjIyAqKlN0ZXAgMzogRmVhdHVyZSBTZWxlY3Rpb24qKg0KDQpUbyBzZWxlY3QgdGhlIG1vc3QgcmVsZXZhbnQgcHJlZGljdG9ycyBmb3IgdGhlIHByZWRpY3RpdmUgbW9kZWwsIEkgZW1wbG95ZWQgYm90aCBGb3J3YXJkIGFuZCBCYWNrd2FyZCBGZWF0dXJlIFNlbGVjdGlvbiBtZXRob2RzLiBUaGVzZSB0ZWNobmlxdWVzIGhlbHAgaWRlbnRpZnkgdGhlIG9wdGltYWwgc3Vic2V0IG9mIHZhcmlhYmxlcyB0aGF0IG1heGltaXplIHRoZSBtb2RlbCdzIHByZWRpY3RpdmUgcGVyZm9ybWFuY2UuIA0KDQpCYWNrd2FyZCBzZWxlY3Rpb24gYmVnaW5zIHdpdGggYSBtb2RlbCB0aGF0IGluY2x1ZGVzIGFsbCB2YXJpYWJsZXMgYW5kIGl0ZXJhdGl2ZWx5IHJlbW92ZXMgdGhlIGxlYXN0IGltcG9ydGFudCBmZWF0dXJlcywgZXZhbHVhdGluZyB0aGUgbW9kZWzigJlzIHBlcmZvcm1hbmNlIGF0IGVhY2ggc3RlcC4gSW4gY29udHJhc3QsIEZvcndhcmQgc2VsZWN0aW9uIHN0YXJ0cyB3aXRoIGEgbW9kZWwgdGhhdCBvbmx5IGNvbnRhaW5zIHRoZSBpbnRlcmNlcHQsIGdyYWR1YWxseSBhZGRpbmcgdGhlIG1vc3Qgc2lnbmlmaWNhbnQgZmVhdHVyZXMgYXQgZWFjaCBpdGVyYXRpb24uDQoNCmBgYHtyfQ0KIyBCYWNrd2FyZCBFbGltaW5hdGlvbg0KDQpsb2dpdC5sbS5hbGwgPC0gZ2xtKENodXJufi4sIGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSBjYWxpYnJhdC5kYXRhKQ0KbG9naXQuYmFja3dhcmQgPC0gc3RlcChsb2dpdC5sbS5hbGwsIGRpcmVjdGlvbiA9ICdiYWNrd2FyZCcsIHRyYWNlID0gMCkNCg0Kc3VtbWFyeShsb2dpdC5iYWNrd2FyZCkNCmBgYA0KYGBge3J9DQojIEZvcndhcmQgQWRkaXRpb24NCg0KbG9naXQubG0ubWluaW1hbCA8LSBnbG0oQ2h1cm4gfiAxLCBmYW1pbHkgPSAnYmlub21pYWwnLCBkYXRhID0gY2FsaWJyYXQuZGF0YSkNCnJlYWxpdHkuZm9yd2FyZCA8LSBzdGVwKGxvZ2l0LmxtLm1pbmltYWwsIHNjb3BlID0gbGlzdCh1cHBlciA9IGxvZ2l0LmxtLmFsbCwgbG93ZXIgPSBsb2dpdC5sbS5taW5pbWFsKSwgZGlyZWN0aW9uID0gJ2ZvcndhcmQnLCB0cmFjZSA9IDApDQoNCnN1bW1hcnkocmVhbGl0eS5mb3J3YXJkKQ0KYGBgDQpCb3RoIGZvcndhcmQgYW5kIGJhY2t3YXJkIHNlbGVjdGlvbiBtb2RlbHMgcmV0dXJuZWQgYSBzaW1pbGFyIHNldCBvZiBmZWF0dXJlczoNCg0KYGBgDQpDaHVybiB+IEN1cnJlbnRFcXVpcG1lbnREYXlzICsgTWFkZUNhbGxUb1JldGVudGlvblRlYW0uICsgDQogICAgQ3JlZGl0UmF0aW5nQUEuICsgQWdlSEgxICsgSGFuZHNldFJlZnVyYmlzaGVkLiArIE1vbnRoc0luU2VydmljZSArIA0KICAgIFBlcmNDaGFuZ2VNaW51dGVzICsgVW5pcXVlU3VicyArIEFjdGl2ZVN1YnMgKyBUb3RhbFJlY3VycmluZ0NoYXJnZSArIA0KICAgIE1vbnRobHlSZXZlbnVlICsgTW9udGhseU1pbnV0ZXMgKyBQZXJjQ2hhbmdlUmV2ZW51ZXMgKyBIYW5kc2V0UHJpY2VNaXNzaW5nLiArIA0KICAgIERyb3BwZWRDYWxscyArIENyZWRpdFJhdGluZ0EuICsgUmVzcG9uZHNUb01haWxPZmZlcnMuICsgSGFuZHNldFdlYkNhcGFibGUuICsgDQogICAgSGFuZHNldHMgKyBDaGlsZHJlbkluSEguICsgUm9hbWluZ0NhbGxzICsgSW5ib3VuZENhbGxzICsgDQogICAgTmV3Q2VsbHBob25lVXNlci4gKyBQcml6bUNvZGVTdWJ1cmJhbi4gKyBPdmVyYWdlTWludXRlcyArIA0KICAgIE1hcml0YWxTdGF0dXNNaXNzaW5nLiArIFRocmVld2F5Q2FsbHMgKyBCbG9ja2VkQ2FsbHMgKyBDdXN0b21lckNhcmVDYWxscyArIA0KICAgIE93bnNNb3RvcmN5Y2xlLiArIE9jY3VwYXRpb25Ib21lbWFrZXIuDQpgYGANCg0KIyMjICoqU3RlcCA0OiBMb2dpc3RpYyBSZWdyZXNzaW9uKioNCg0KVXNpbmcgdGhlIHNlbGVjdGVkIGZlYXR1cmVzLCB3ZSBjYW4gdHJhaW4gYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIG9uIHRoZSBjYWxpYnJhdGlvbiBkYXRhc2V0IGFuZCBldmFsdWF0ZSBpdHMgcGVyZm9ybWFuY2Ugb24gdGhlIHVuc2VlbiB0ZXN0IGRhdGFzZXQuDQoNCmBgYHtyfQ0KI0NyZWF0ZSAmIFRyYWluIHRoZSBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsDQoNCmxvZ2l0Lm1vZGVsIDwtIGdsbShDaHVybiB+IE1vbnRobHlSZXZlbnVlICsgTW9udGhseU1pbnV0ZXMgKyBUb3RhbFJlY3VycmluZ0NoYXJnZSArIA0KICAgIE92ZXJhZ2VNaW51dGVzICsgUm9hbWluZ0NhbGxzICsgUGVyY0NoYW5nZU1pbnV0ZXMgKyBQZXJjQ2hhbmdlUmV2ZW51ZXMgKyANCiAgICBEcm9wcGVkQ2FsbHMgKyBCbG9ja2VkQ2FsbHMgKyBDdXN0b21lckNhcmVDYWxscyArIFRocmVld2F5Q2FsbHMgKyANCiAgICBJbmJvdW5kQ2FsbHMgKyBNb250aHNJblNlcnZpY2UgKyBVbmlxdWVTdWJzICsgQWN0aXZlU3VicyArIA0KICAgIEhhbmRzZXRzICsgQ3VycmVudEVxdWlwbWVudERheXMgKyBBZ2VISDEgKyBDaGlsZHJlbkluSEguICsgDQogICAgQ3JlZGl0UmF0aW5nQS4gKyBDcmVkaXRSYXRpbmdBQS4gKyBQcml6bUNvZGVTdWJ1cmJhbi4gKyBIYW5kc2V0UmVmdXJiaXNoZWQuICsgDQogICAgSGFuZHNldFdlYkNhcGFibGUuICsgT2NjdXBhdGlvbkhvbWVtYWtlci4gKyBNYXJpdGFsU3RhdHVzTWlzc2luZy4gKyANCiAgICBSZXNwb25kc1RvTWFpbE9mZmVycy4gKyBOZXdDZWxscGhvbmVVc2VyLiArIE93bnNNb3RvcmN5Y2xlLiArIA0KICAgIEhhbmRzZXRQcmljZU1pc3NpbmcuICsgTWFkZUNhbGxUb1JldGVudGlvblRlYW0uLCBkYXRhID0gY2FsaWJyYXQuZGF0YSwgZmFtaWx5ID0gJ2Jpbm9taWFsJykNCg0Kc3VtbWFyeShsb2dpdC5tb2RlbCkNCg0KYGBgDQpgYGB7cn0NCiNBbmFseXppbmcgTW9kZWwgUGVyZm9ybWFuY2UNCg0KdGVzdC5kYXRhJHByZWRpY3RfbG9naXQgPC0gcHJlZGljdChsb2dpdC5tb2RlbCwgbmV3ZGF0YSA9IHRlc3QuZGF0YSwgdHlwZSA9ICdyZXNwb25zZScpDQoNCmNvbmZ1c2lvbk1hdHJpeChhcy5mYWN0b3IoYXMubnVtZXJpYyh0ZXN0LmRhdGEkcHJlZGljdF9sb2dpdCA+IDAuNSkpLCBhcy5mYWN0b3IodGVzdC5kYXRhJENodXJuKSkNCmBgYA0KYGBge3J9DQpwcmludChwYXN0ZTAoIlByZWNpc2lvbiA9ICIsIHByZWNpc2lvbihhcy5mYWN0b3IoYXMubnVtZXJpYyh0ZXN0LmRhdGEkcHJlZGljdF9sb2dpdCA+IDAuNSkpLCBhcy5mYWN0b3IodGVzdC5kYXRhJENodXJuKSkpKQ0KcHJpbnQocGFzdGUwKCJSZWNhbGwgPSAiLCByZWNhbGwoYXMuZmFjdG9yKGFzLm51bWVyaWModGVzdC5kYXRhJHByZWRpY3RfbG9naXQgPiAwLjUpKSwgYXMuZmFjdG9yKHRlc3QuZGF0YSRDaHVybikpKSkNCmBgYA0KQmFzZWQgb24gdGhlIGNvbmZ1c2lvbiBtYXRyaXgsIG91ciBtb2RlbCBjb3JyZWN0bHkgaWRlbnRpZmllcyAxNyw4OTMgbm9uLWNodXJuZXJzIGFuZCAzMzQgY2h1cm5lcnMuIEhvd2V2ZXIsIHRoZSBtb2RlbCdzIG92ZXJhbGwgYWNjdXJhY3kgaXMgb25seSA2MCUsIHByaW1hcmlseSBkdWUgdG8gbWlzY2xhc3NpZnlpbmcgMTEsODg5IG5vbi1jaHVybmVycyBhcyBjaHVybmVycy4gVGhpcyBlcnJvciBpcyBhdHRyaWJ1dGVkIHRvIHRoZSBoaWdoIGNsYXNzIGltYmFsYW5jZSBpbiB0aGUgZGF0YXNldCwgYXMgb25seSBhYm91dCAyJSBvZiBjdXN0b21lcnMgYWN0dWFsbHkgY2h1cm4uIA0KDQpUaGUgb3JnYW5pemF0aW9uIGNhbiBhc3Nlc3MgdGhlIGNvc3RzIGFzc29jaWF0ZWQgd2l0aCB0d28gdHlwZXMgb2YgZXJyb3JzOiBUeXBlIEkgZXJyb3IsIHdoZXJlIGFkZGl0aW9uYWwgaW5jZW50aXZlcyBhcmUgcHJvdmlkZWQgdG8gYSBjdXN0b21lciB3aG8gaXMgdW5saWtlbHkgdG8gY2h1cm4sIGFuZCBUeXBlIElJIGVycm9yLCB3aGVyZSBhIGN1c3RvbWVyIHdobyBhY3R1YWxseSBjaHVybnMgaXMgbm90IHByb3ZpZGVkIHdpdGggaW5jZW50aXZlcyBkdWUgdG8gdGhlIHByZWRpY3Rpb24gdGhhdCB0aGV5IHdvdWxkIG5vdCBjaHVybi4gQnkgZXZhbHVhdGluZyB0aGVzZSBjb3N0cywgdGhlIGNvbXBhbnkgY291bGQgZGVjaWRlIGlmIGEgZGlmZmVyZW50IHRocmVzaG9sZCwgb3RoZXIgdGhhbiA1MCUsIHNob3VsZCBiZSBhcHBsaWVkIHRvIGNsYXNzaWZ5IGN1c3RvbWVycyBhcyBsaWtlbHkgdG8gY2h1cm4uDQoNCkRlc3BpdGUgdGhlIGxvdyBvdmVyYWxsIGFjY3VyYWN5LCB0aGUgbW9kZWwgZGVtb25zdHJhdGVzIGFuIGV4Y2VwdGlvbmFsbHkgaGlnaCBwcmVjaXNpb24sIHdpdGggOTguNiUgb2YgY3VzdG9tZXJzIHByZWRpY3RlZCB0byBjaHVybiBhY3R1YWxseSBkb2luZyBzby4gVGhpcyBwcmVjaXNpb24gaXMgdmFsdWFibGUgZm9yIHRoZSBjb21wYW55LCBhcyBpdCBlbnN1cmVzIHRoYXQgcmVzb3VyY2VzIGFuZCBpbmNlbnRpdmVzIGFyZSBub3Qgd2FzdGVkIG9uIGN1c3RvbWVycyB3aG8gYXJlIHVubGlrZWx5IHRvIGNodXJuLg0KDQpUaGUgbW9kZWwgY2FuIGFsc28gYmUgcmUtcnVuIHdpdGggYSBkaWZmZXJlbnQgdGhyZXNob2xkIGZvciBjbGFzc2lmeWluZyBjaHVybmVycyBhbmQgbm9uLWNodXJuZXJzLiBGb3IgaW5zdGFuY2UsIHdlIGNvdWxkIHRlc3QgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBtb2RlbCBieSBwcmVkaWN0aW5nIGEgY3VzdG9tZXIgYXMgYSBjaHVybmVyIG9ubHkgd2hlbiB0aGVpciBwcm9iYWJpbGl0eSBvZiBjaHVybmluZyBleGNlZWRzIDcwJSwgaW5zdGVhZCBvZiB0aGUgZGVmYXVsdCA1MCUgdGhyZXNob2xkLg0KDQpgYGB7cn0NCnByaW50KGNvbmZ1c2lvbk1hdHJpeChhcy5mYWN0b3IoYXMubnVtZXJpYyh0ZXN0LmRhdGEkcHJlZGljdF9sb2dpdCA+IDAuNykpLCBhcy5mYWN0b3IodGVzdC5kYXRhJENodXJuKSkpDQpgYGANCg0KYGBge3J9DQpwcmludChwYXN0ZTAoIlByZWNpc2lvbiAoNzAlIHRocmVzaG9sZCkgPSAiLCBwcmVjaXNpb24oYXMuZmFjdG9yKGFzLm51bWVyaWModGVzdC5kYXRhJHByZWRpY3RfbG9naXQgPiAwLjcpKSwgYXMuZmFjdG9yKHRlc3QuZGF0YSRDaHVybikpKSkNCnByaW50KHBhc3RlMCgiUmVjYWxsICg3MCUgdGhyZXNob2xkKSA9ICIsIHJlY2FsbChhcy5mYWN0b3IoYXMubnVtZXJpYyh0ZXN0LmRhdGEkcHJlZGljdF9sb2dpdCA+IDAuNykpLCBhcy5mYWN0b3IodGVzdC5kYXRhJENodXJuKSkpKQ0KYGBgDQoNClRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgcmUtYWRqdXN0ZWQgbW9kZWwgc2lnbmlmaWNhbnRseSBpbXByb3ZlZCwgYWNoaWV2aW5nIGFuIGFjY3VyYWN5IG9mIDk2LjUlLiBUaGUgcmVjYWxsIHNhdyBhIHN1YnN0YW50aWFsIGluY3JlYXNlLCByaXNpbmcgZnJvbSA2MCUgKHdpdGggdGhlIDUwJSB0aHJlc2hvbGQpIHRvIDk4LjQlLiBIb3dldmVyLCB0aGlzIGNhbWUgYXQgdGhlIGNvc3Qgb2YgYSBzbGlnaHQgZGVjcmVhc2UgaW4gcHJlY2lzaW9uLCB3aGljaCBkcm9wcGVkIGZyb20gOTguNiUgdG8gOTguMSUuIA0KDQpCb3RoIG1vZGVscyBkZW1vbnN0cmF0ZSBzdHJlbmd0aHMgaW4gZGlmZmVyZW50IGFzcGVjdHMgb2YgY2xhc3NpZmljYXRpb24sIGFuZCB0aGUgY29tcGFueSBjYW4gY2hvb3NlIHdoaWNoIG1vZGVsIHRvIHVzZSBiYXNlZCBvbiB0aGVpciBzcGVjaWZpYyBidXNpbmVzcyBnb2FsczoNCiAgKiAqKk1pbmltaXppbmcgdGhlIFJpc2sgb2YgTWlzc2luZyBhIENodXJuZXIqKiAoYnVzaW5lc3MgY29zdDogbG9zaW5nIGN1c3RvbWVycyk6DQogICAgKiBNb2RlbCBJICg1MCUgdGhyZXNob2xkKTogRmV3ZXIgY2h1cm5lcnMgd2VyZSBwcmVkaWN0ZWQgdG8gYmUgbm9uLWNodXJuZXJzICgyNTIpLg0KICAgICogTW9kZWwgSUkgKDcwJSB0aHJlc2hvbGQpOiBNb3JlIGNodXJuZXJzIHdlcmUgcHJlZGljdGVkIGFzIG5vbi1jaHVybmVycyAoNTYyKS4NCiAgKiAqKk1pbmltaXppbmcgdGhlIENvc3Qgb2YgSW5jb3JyZWN0bHkgSWRlbnRpZnlpbmcgYSBDaHVybmVyKiogKGJ1c2luZXNzIGNvc3Q6IHNwZW5kaW5nIHVubmVjZXNzYXJ5IHJlc291cmNlcyBvbiBjdXN0b21lcnMgd2hvIHdvbid0IGNodXJuKToNCiAgICAqIE1vZGVsIEkgKDUwJSB0aHJlc2hvbGQpOiBBIGhpZ2ggbnVtYmVyIG9mIG5vbi1jaHVybmVycyB3ZXJlIHByZWRpY3RlZCB0byBjaHVybiAoMTEsODg5KS4NCiAgICAqIE1vZGVsIElJICg3MCUgdGhyZXNob2xkKTogRmV3ZXIgbm9uLWNodXJuZXJzIHdlcmUgcHJlZGljdGVkIHRvIGNodXJuICg0ODkpLg0KDQogIA0KIyMjICoqU3RlcCA1OiBBY3Rpb25hYmxlIEluc2lnaHRzKioNCg0KVGhlIGZpbmFsIHN0ZXAgb2YgdGhpcyBwcm9qZWN0IGlzIHRvIGlkZW50aWZ5IHRoZSBrZXkgZmFjdG9ycyB0aGF0IGNvbnRyaWJ1dGUgbW9zdCB0byBjdXN0b21lciBjaHVybi4gVGhpcyBhbmFseXNpcyBjYW4gcHJvdmlkZSB2YWx1YWJsZSBpbnNpZ2h0cyB0byBoZWxwIHRoZSB0ZWxlY29tbXVuaWNhdGlvbnMgY29tcGFueSBkZXNpZ24gYSBjb250aW5nZW5jeS1iYXNlZCBpbmNlbnRpdmUgcGxhbiBhaW1lZCBhdCByZWR1Y2luZyBjaHVybi4NCg0KVGhlIHF1YW50aXRhdGl2ZSBpbXBvcnRhbmNlIG9mIHRoZSB2YXJpYWJsZXMgaXMgY2FsY3VsYXRlZCBhczogPC9icj4NCiAgKiAqKk51bWVyaWMgVmFyaWFibGVzKio6IFN0YW5kYXJkIERldmlhdGlvbiBvZiB0aGUgRmVhdHVyZSB4IE1hcmdpbmFsIEVmZmVjdCBvZiB0aGUgRmVhdHVyZQ0KICAqICoqQ2F0ZWdvcmljYWwgVmFyaWFibGVzKio6IE1hcmdpbmFsIEVmZmVjdCBvZiB0aGUgRmVhdHVyZQ0KDQpCYXNlZCBvbiB0aGlzLCB3ZSBjYW4gY2FsY3VsYXRlIHRoZSBpbXBvcnRhbmNlIG9mIGFsbCBmZWF0dXJlcywgYW5kIHJhbmsgdGhlbSBiYXNlZCBvbiB0aGVpciBpbXBvcnRhbmNlLg0KDQpgYGB7cn0NCmxvZ2l0bWUgPC0gbG9naXRtZngoQ2h1cm5+IE1vbnRobHlSZXZlbnVlICsgTW9udGhseU1pbnV0ZXMgKyBUb3RhbFJlY3VycmluZ0NoYXJnZSArDQpPdmVyYWdlTWludXRlcyArIFJvYW1pbmdDYWxscyArIFBlcmNDaGFuZ2VNaW51dGVzICsgUGVyY0NoYW5nZVJldmVudWVzICsNCkRyb3BwZWRDYWxscyArIEJsb2NrZWRDYWxscyArIEN1c3RvbWVyQ2FyZUNhbGxzICsgVGhyZWV3YXlDYWxscyArDQpJbmJvdW5kQ2FsbHMgKyBNb250aHNJblNlcnZpY2UgKyBVbmlxdWVTdWJzICsgQWN0aXZlU3VicyArDQpIYW5kc2V0cyArIEN1cnJlbnRFcXVpcG1lbnREYXlzICsgQWdlSEgxICsgQ2hpbGRyZW5JbkhILiArDQpDcmVkaXRSYXRpbmdBLiArIENyZWRpdFJhdGluZ0FBLiArIFByaXptQ29kZVN1YnVyYmFuLiArIEhhbmRzZXRSZWZ1cmJpc2hlZC4gKw0KSGFuZHNldFdlYkNhcGFibGUuICsgT2NjdXBhdGlvbkhvbWVtYWtlci4gKyBNYXJpdGFsU3RhdHVzTWlzc2luZy4gKw0KUmVzcG9uZHNUb01haWxPZmZlcnMuICsgTmV3Q2VsbHBob25lVXNlci4gKyBPd25zTW90b3JjeWNsZS4gKw0KSGFuZHNldFByaWNlTWlzc2luZy4gKyBNYWRlQ2FsbFRvUmV0ZW50aW9uVGVhbS4sIGRhdGEgPSBjYWxpYnJhdC5kYXRhKQ0KDQojQ2FsY3VsYXRlIHRoZSBNYXJnaW5hbCBFZmZlY3Qgb2YgdGhlIFZhcmlhYmxlcw0KbWZ4IDwtIGxvZ2l0bWUkbWZ4ZXN0DQptZnggPC0gYXMuZGF0YS5mcmFtZShtZngpDQoNCiNDYWxjdWxhdGUgdGhlIFN0YW5kYXJkIERldmlhdGlvbiBvZiB0aGUgVmFyaWFibGVzDQpzdGQuZGV2IDwtIGFwcGx5KG5hLm9taXQoY2FsaWJyYXQuZGF0YSlbLGMoLTEpXSwgMiwgc2QpDQoNCiNBZGQgdGhlIFN0YW5kYXJkIERldmlhdGlvbiB0byB0aGUgTUZYIGRhdGFmcmFtZQ0KbWZ4JHN0ZF9kZXYgPC0gTkEgDQpjb21tb25fY29scyA8LSBpbnRlcnNlY3QobmFtZXMoc3RkLmRldiksIHJvd25hbWVzKG1meCkpDQoNCm1meFtjb21tb25fY29scywgInN0ZF9kZXYiXSA8LSBzdGQuZGV2W2NvbW1vbl9jb2xzXQ0KDQojQ2FsY3VsYXRlIHRoZSBRdWFudGl0YXRpdmUgSW1wb3J0YW5jZQ0KbWZ4IDwtIG1meFshaXMubmEobWZ4JGBkRi9keGApLCBdDQptZngkc3RkX2Rldltpcy5uYShtZngkc3RkX2RldildIDwtIDENCg0KbWZ4JFF1YW50aXRhdGl2ZUltcG9ydGFuY2UgPC0gYWJzKG1meCRgZEYvZHhgKSAqIGFicyhtZngkc3RkX2RldikNCm1meCRFZmZlY3QgPC0gaWZlbHNlKG1meCRgZEYvZHhgID4gMCwgIisiLCAiLSIpDQoNCm1meCA8LSBtZnhbb3JkZXIoLW1meCRRdWFudGl0YXRpdmVJbXBvcnRhbmNlKSwgXQ0KbWZ4IDwtIG1meFssIGMoImRGL2R4IiwgInN0ZF9kZXYiLCAiUXVhbnRpdGF0aXZlSW1wb3J0YW5jZSIsICJFZmZlY3QiICldDQoNCmhlYWQobWZ4LCAxMCkNCmBgYA0KDQpUaGUgcmVzdWx0cyBhYm92ZSBoaWdobGlnaHQgdGhlIHByZWRpY3RvcnMgaW4gb3VyIGRhdGFzZXQgdGhhdCBoYXZlIHRoZSBncmVhdGVzdCBpbXBhY3Qgb24gYSBjdXN0b21lcidzIGxpa2VsaWhvb2QgdG8gY2h1cm4uIEtleSBmYWN0b3JzIGluZmx1ZW5jaW5nIGNodXJuIGluY2x1ZGUgd2hldGhlciB0aGUgY3VzdG9tZXIgaGFzIGNvbnRhY3RlZCB0aGUgcmV0ZW50aW9uIHRlYW0sIHRoZSBudW1iZXIgb2YgZGF5cyB0aGV5IGhhdmUgdXNlZCB0aGVpciBjdXJyZW50IGVxdWlwbWVudCwgdGhlaXIgY3VzdG9tZXIgcHJvZmlsZSAoc3VjaCBhcyBjcmVkaXQgcmF0aW5nKSwgYW5kIGRldGFpbHMgYWJvdXQgdGhlaXIgZXF1aXBtZW50IGFuZCBoYW5kc2V0cy4NCg0KSXQgaXMgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCB3aGlsZSBjZXJ0YWluIGZlYXR1cmVzLCBzdWNoIGFzIHdoZXRoZXIgdGhlIGN1c3RvbWVyIGlzIGEgaG9tZW1ha2VyIG9yIG93bnMgYSBtb3RvcmN5Y2xlLCBhcmUgaWRlbnRpZmllZCBhcyBpbXBvcnRhbnQgaW4gdGhlIG1vZGVsLCB0aGV5IGFyZSBub3Qgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBpbiBwcmVkaWN0aW5nIGN1c3RvbWVyIGNodXJuLg0KDQpCYXNlZCBvbiB0aGlzIGFuYWx5c2lzLCBzb21lIHJlY29tbWVuZGVkIHJldGVudGlvbiBhY3Rpb25zIGluY2x1ZGU6DQoNCiAgKiAqKk9mZmVyIGEgbmV3IHBob25lIG9yIGVxdWlwbWVudCoqIHRvIGN1c3RvbWVycywgYXMgdGhlIHZhcmlhYmxlIGBDdXJyZW50RXF1aXBtZW50RGF5c2AgaXMgc3Ryb25nbHkgY29ycmVsYXRlZCB3aXRoIGNodXJuLg0KICAqICoqVGFyZ2V0IGN1c3RvbWVycyB3aXRoIEFBIGNyZWRpdCoqIHJhdGluZ3MgZm9yIGFjcXVpc2l0aW9uIHN0cmF0ZWdpZXMsIGFzIHRoZXNlIGN1c3RvbWVycyBhcmUgbGVzcyBsaWtlbHkgdG8gY2h1cm4sIHdoaWxlIGZvY3VzaW5nIGVmZm9ydHMgYXdheSBmcm9tIGhvbWVtYWtlcnMsIHdobyBoYXZlIGEgaGlnaGVyIGxpa2VsaWhvb2Qgb2YgY2h1cm5pbmcuLg0KICAqIEludHJvZHVjZSByZWJhdGUgcHJvZ3JhbXMgb3IgKipzcGVjaWFsIG9mZmVycyBmb3IgY3VzdG9tZXJzIHdpdGggYSBoaWdoIG51bWJlciBvZiB1bmlxdWUgc3Vic2NyaXB0aW9uIHBsYW5zKiosIGFzIHRoaXMgY291bGQgaGVscCByZXRhaW4gdGhlbS4NCiAgKiAqKlByb3ZpZGUgbmV3IGhhbmRzZXRzIG9yIGVuaGFuY2UgdGhlIHF1YWxpdHkgb2YgcmVmdXJiaXNoZWQgaGFuZHNldHMqKiwgYXMgY3VzdG9tZXJzIHdpdGggcmVmdXJiaXNoZWQgZXF1aXBtZW50IGFyZSBjdXJyZW50bHkgbW9yZSBsaWtlbHkgdG8gY2h1cm4uIg==