Email             :
RPubs            : https://rpubs.com/brigitatiaraem/
Jurusan          : Statistika
Address         : ARA Center, Matana University Tower
                         Jl. CBD Barat Kav, RT.1, Curug Sangereng, Kelapa Dua, Tangerang, Banten 15810.


1 CUSTOMER CHURN ANALYSIS


Goal:
End to end analysis on Telco customer churn data main focus being: - Identify what features contribute to customer churn.
- Magnitude of each feature’s contribution and uncertainties associated. - Business conclusion that we can share with Telco.

I added references at the end of the notebook that all composed the bits and pieces of this notebook. Please go check them out.


library(tidyverse) # imports ggplot2, dplyr, tidyr, readr, purrr, tibble, stringr, and forcats

# Normality Test
library(car) # for qqplot
library(nortest) # Anderson-Darling normality test

# Correspondence Analysis 
library(FactoMineR)
library(factoextra)

# time-to-event (survival) analysis packages
library(KMsurv)
library(survival)
library(survminer)

# correlation 
library(corrr)

# contingency table
library(MASS)


# plotting 
library(gridExtra) # grid.arrange

# library(glmnet)
# library(factoextra)
# library(ggfortify)
# library(devtools)
# # library(ggbiplot)
# library(factoextra)
# library(MASS)
# library(cluster)
# library(gridExtra)
# library(party)


# set the max number of columns displayed in Jupyter Notebook without truncation
options(repr.matrix.max.cols=30, repr.matrix.max.rows=100)
# load data 
telco_df <- read.csv("WA_Fn-UseC_-Telco-Customer-Churn.csv")

2 1. Initial Data Quality Check

  • Are the customerIDs unique? Check if is there only one row (data point) per customerID?
  • Any NAs?

2.1 1.1. Is the customerID unique? That is, is there only one row per customerID?

row_count = nrow(telco_df)
uniqueID_count =length(unique(telco_df$customerID))
print(paste0("Number of Rows : ", row_count))
## [1] "Number of Rows : 7043"
print(paste0("Number of Unique CustomerID : ", uniqueID_count))
## [1] "Number of Unique CustomerID : 7043"
if (row_count == uniqueID_count) {
    print("Customer ID is unique; ther is one data point per Customer ID.")
} else {
    print("Customer ID is not unique.")
}
## [1] "Customer ID is unique; ther is one data point per Customer ID."

OBSERVATIONS: - There is only one row per Customer ID. Each Customer ID is unique!

2.2 1.2. Any NAs?

summary(telco_df)
##   customerID           gender          SeniorCitizen      Partner         
##  Length:7043        Length:7043        Min.   :0.0000   Length:7043       
##  Class :character   Class :character   1st Qu.:0.0000   Class :character  
##  Mode  :character   Mode  :character   Median :0.0000   Mode  :character  
##                                        Mean   :0.1621                     
##                                        3rd Qu.:0.0000                     
##                                        Max.   :1.0000                     
##                                                                           
##   Dependents            tenure      PhoneService       MultipleLines     
##  Length:7043        Min.   : 0.00   Length:7043        Length:7043       
##  Class :character   1st Qu.: 9.00   Class :character   Class :character  
##  Mode  :character   Median :29.00   Mode  :character   Mode  :character  
##                     Mean   :32.37                                        
##                     3rd Qu.:55.00                                        
##                     Max.   :72.00                                        
##                                                                          
##  InternetService    OnlineSecurity     OnlineBackup       DeviceProtection  
##  Length:7043        Length:7043        Length:7043        Length:7043       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##  TechSupport        StreamingTV        StreamingMovies      Contract        
##  Length:7043        Length:7043        Length:7043        Length:7043       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##  PaperlessBilling   PaymentMethod      MonthlyCharges    TotalCharges   
##  Length:7043        Length:7043        Min.   : 18.25   Min.   :  18.8  
##  Class :character   Class :character   1st Qu.: 35.50   1st Qu.: 401.4  
##  Mode  :character   Mode  :character   Median : 70.35   Median :1397.5  
##                                        Mean   : 64.76   Mean   :2283.3  
##                                        3rd Qu.: 89.85   3rd Qu.:3794.7  
##                                        Max.   :118.75   Max.   :8684.8  
##                                                         NA's   :11      
##     Churn          
##  Length:7043       
##  Class :character  
##  Mode  :character  
##                    
##                    
##                    
## 

OBSERVATIONS: - No N/As in all columns!

3 2. Data Exploration

Let’s start with the simpler observations and move onto the ones that require a bit more efforts (e.g., data distribution, correlation).

3.1 2.1. Univariate Data Exploration

3.1.1 2.1.1. Numerical Variables

options(repr.plot.width = 14, repr.plot.height = 8)

# check out distribution of numeric feature(s) 
telco_df %>%
  keep(is.numeric) %>% 
  gather() %>% 
  ggplot(aes(value)) + theme_minimal() +
    facet_wrap(~ key, scales = "free") +
    geom_histogram() + 
    theme(axis.title=element_text(size=16,face="bold")) + 
    theme(text = element_text(size = 20))  

# more on tenure distribution (Number of Months)
min(telco_df$tenure)
## [1] 0
max(telco_df$tenure)
## [1] 72
sprintf('Max tenure is %i years.', max(telco_df$tenure)/12)
## [1] "Max tenure is 6 years."

OBSERVATIONS: - Again, we see that the values of SeniorCitizen is using numeric 0 and 1 as binary label for True and False, respectively. - MonthlyCharges column has a high concentration around 20, and the rest the values are somewhat a normal distribution with skew to the left. - TotalCharges column seem to follow an exponential distribution.

3.1.2 2.1.2. Categorical Variables

ref: https://stackoverflow.com/questions/38184288/how-to-plot-multiple-factor-columns-with-ggplot

colnames(telco_df)
##  [1] "customerID"       "gender"           "SeniorCitizen"    "Partner"         
##  [5] "Dependents"       "tenure"           "PhoneService"     "MultipleLines"   
##  [9] "InternetService"  "OnlineSecurity"   "OnlineBackup"     "DeviceProtection"
## [13] "TechSupport"      "StreamingTV"      "StreamingMovies"  "Contract"        
## [17] "PaperlessBilling" "PaymentMethod"    "MonthlyCharges"   "TotalCharges"    
## [21] "Churn"
options(repr.plot.width = 14, repr.plot.height = 10)

telco_df %>%
  keep(is.numeric) %>% 
  gather() %>% 
  ggplot(aes(value)) + theme_minimal() +
    facet_wrap(~ key, scales = "free") +
    geom_histogram() + 
    theme(axis.title=element_text(size=16,face="bold")) + 
    theme(text = element_text(size = 20)) 

OBSERVATIONS: - We have a unique key (customerID), 16 categorical columns, and 4 numerical columns. - SeniorCitizen, one of the numerical columns, uses 1 and 0 as a binary label (wonder it is not in True/False like many other binary categorical columns). - 5 columns are only composef of Yes or No.  - 6 columns have Yes, No, and No internet service option. - 1 column, MultipleLines, has Yes, No, and No phone service option. - The relaitonships of “Yes/No + third option” columns can be visualized like the following:

PhoneService MultipleLines
No No phone service
Yes Yes
Yes No
InteretService OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTV, StreamingMovie
No No internet service
Yes Yes
Yes No

The column values on the right are dependent on the column values on the left, thus 7 columns alone can explain the 2 columns on the left.

3.2 2.2. MonthlyCharges distributions separated by categorical features.

  • We saw above that the MonthlyCharges seem to be composed of multiple distributions (maybe of a narrow tall normal distribution with mean ~20 + a shallow wide normal distribution with mean ~ 90)
  • In secion 1.2., we saw that we have categorical features InternetService and PhoneService which could determine the distribution of the MonthlyCharges. So that is what we will be seeing here.
ggplot(telco_df, aes(x=MonthlyCharges, color=InternetService)) +
  geom_histogram(fill="white", alpha=0.5, position="identity") + theme_minimal() + 
    ggtitle("`MonthlyCharges` by `InternetService`") + 
    theme(axis.title=element_text(size=12,face="bold")) + 
    theme(text = element_text(size = 14)) +
    labs(color="InternetService")

OBSERVATIONS:

  • There is only one row per Customer ID. Each Customer ID is unique!
  • Customers’ MonthlyCharges could be highly determined by the type of InternetServices. Fiber optics with average ~90, DSL ~60, and no service ~20.
  • Multiple sources validated that fiber optic services are faster than DSL and are do cost more https://www.highspeedinternet.com/resources/dsl-vs-fiber
ggplot(telco_df, aes(x=MonthlyCharges, color=PhoneService)) +
  geom_histogram(fill="white", alpha=0.5, position="identity") + theme_minimal() + 
    ggtitle("`MonthlyCharges` by `PhoneService`") + 
    theme(axis.title=element_text(size=12,face="bold")) + 
    theme(text = element_text(size = 14)) +
    labs(color="PhoneService")

OBSERVATIONS:

  • There is only one row per Customer ID. Each Customer ID is unique!
  • Comparing with “MonthlyCharges by InternetService” plot, we see there is no overlap between the customers without (No) PhoneService and fiber optics internet service.
# How about the combination of the two - Contingency Table
telco_df$InternetPhoneServices <- paste('Phone: ', telco_df$PhoneService, ' | ',
                                        'Internet: ', telco_df$InternetService
                                       ) 
                                        
ggplot(telco_df, aes(x=MonthlyCharges, color=InternetPhoneServices)) +
  geom_histogram(fill="white", alpha=0.5, position="identity") + theme_minimal() + 
    ggtitle("`MonthlyCharges` by `PhoneService` and `InternetService`") + 
    theme(axis.title=element_text(size=12,face="bold")) + 
    theme(text = element_text(size = 14)) +
    labs(color="Phone | InternetService")

ggplot(telco_df, aes(x = MonthlyCharges)) + 
    geom_histogram(fill="white", aes(color=InternetPhoneServices)) + theme_minimal() + 
    facet_wrap( ~ InternetPhoneServices) + 
    theme(axis.title=element_text(size=12,face="bold")) + 
    theme(text = element_text(size = 14)) +
    labs(color="Phone | InternetService")

# TODO: statistical check on whether the distributions are coming from different populations.

3.3 2.3. Is TotalCharges equal to Tenrue*MonthlyCharges?

  • We will check if the total charges computed by MonthlyCharges * Tenure (in number of months) is identical to the actual TotalCharges in the data.
  • Method:
    1. Compute theoretical total charges total_charges_theoretical = MonthlyCharges * Tenure.
    2. Take the difference between total_charges_theoretical and TotalCharges. Save to total_charges_diff.
    3. Analyze the distribution of total_charges_diff.
# 1. Compute theoretical total charges total_charges_theoretical = MonthlyCharges * Tenure.
telco_df <- telco_df %>% mutate(total_charges_theoretical = tenure*MonthlyCharges)

# 2. Take the difference between total_charges_theoretical and TotalCharges. Save to total_charges_diff.
telco_df <- telco_df %>% mutate(total_charges_diff = TotalCharges - total_charges_theoretical)

# 3. Analyze the distribution of total_charges_diff - let's start with a histogram
ggplot(telco_df, aes(total_charges_diff)) + geom_histogram(bins = 200) + theme_minimal()

OBSERVATION:
- We have a sharp vertical bar at 0. - The distribution of the rest of the values look close to a normal distribuion (let’s check this just for fun!).

# remove 0 and plot histogram 
diff_hist_data <- telco_df %>% 
    filter(total_charges_diff != 0)
ggplot(diff_hist_data, aes(total_charges_diff)) + geom_histogram(bins = 200) + theme_minimal()

OBSERVATION:
- Looks roughly normal? - This is a good time to test out normality testing methods. - Method 1: QQ-Plot - Method 2: Kolmogorov-Smirnov test - Method 3: Anderson-Darling test

# Method 1: QQ-Plot 
qqPlot(diff_hist_data$total_charges_diff)

## [1] 1297 1600
# Method 2: Kolmogorov-Smirnov test
# ref: https://stackoverflow.com/questions/26715843/kolmogorov-smirnov-test-in-r
ks.test(diff_hist_data$total_charges_diff, 'pnorm', 0, sd(diff_hist_data$total_charges_diff))
## 
##  Asymptotic one-sample Kolmogorov-Smirnov test
## 
## data:  diff_hist_data$total_charges_diff
## D = 0.070153, p-value < 2.2e-16
## alternative hypothesis: two-sided
# Method 3: Anderson-Darling test
ad.test(diff_hist_data$total_charges_diff)
## 
##  Anderson-Darling normality test
## 
## data:  diff_hist_data$total_charges_diff
## A = 74.325, p-value < 2.2e-16

OBSERVATIONS: - QQ-plot indicates that the distribution has light tails. - ref: - https://www.youtube.com/watch?v=vMaKx9fmJHE - https://stats.stackexchange.com/questions/101274/how-to-interpret-a-qq-plot - Both KS and AD normality tests indicate that there is not enough evidence to concluse the distribution is not normal.

(TODO) other side question(s): - What features are related to the difference in the theoretical and actual total charges? 1. Divide the difference in theoretical - actual total charges by tenure to obtain monthly difference (how much more or less one paid compared to the mean). 2. Conduct regression method to see which feature(s) are related to the difference. Something like a regression y(difference) ~ x1(tenure) + x2(PhoneService) + … + xn 3. potential conclusion: Is tenure negatively correlated with monthly charge difference? Longer tenure result in a larger discount?

4 3.2. Bivariate Data Exploration

Clearly independent categorical features:

Fisher’s exact test is more accurate than the chi-square test or G–test of independence when the expected numbers are small. I recommend you use Fisher’s exact test when the total sample size is less than 1000, and use the chi-square or G–test for larger sample sizes. ref: http://www.biostathandbook.com/fishers.html#:~:text=Fisher’s%20exact%20test%20is%20more,test%20for%20larger%20sample%20sizes.

table(telco_df$InternetService, telco_df$MultipleLines)
##              
##                 No No phone service  Yes
##   DSL         1048              682  691
##   Fiber optic 1158                0 1938
##   No          1184                0  342
# %% [code]
table(telco_df$MultipleLines, telco_df$PhoneService)
##                   
##                      No  Yes
##   No                  0 3390
##   No phone service  682    0
##   Yes                 0 2971
table(telco_df$OnlineSecurity, telco_df$OnlineBackup)
##                      
##                         No No internet service  Yes
##   No                  2195                   0 1303
##   No internet service    0                1526    0
##   Yes                  893                   0 1126
table(telco_df$Partner, telco_df$Dependents)
##      
##         No  Yes
##   No  3280  361
##   Yes 1653 1749
fisher.test(telco_df$Partner, telco_df$Dependents)
## 
##  Fisher's Exact Test for Count Data
## 
## data:  telco_df$Partner and telco_df$Dependents
## p-value < 2.2e-16
## alternative hypothesis: true odds ratio is not equal to 1
## 95 percent confidence interval:
##   8.447856 10.950901
## sample estimates:
## odds ratio 
##   9.607857

OBSERVATION: At significance level of 5%, we reject the null hypothesis that there is significant relationship between Partner and Dependents.

table(telco_df$Partner, telco_df$MultipleLines)
##      
##         No No phone service  Yes
##   No  1981              371 1289
##   Yes 1409              311 1682
fisher.test(telco_df$Partner, telco_df$MultipleLines)
## 
##  Fisher's Exact Test for Count Data
## 
## data:  telco_df$Partner and telco_df$MultipleLines
## p-value < 2.2e-16
## alternative hypothesis: two.sided

OBSERVATION: At significance level of 5%, we reject the null hypothesis that there is significant relationship between Partner and Dependents.

table(telco_df$Dependents, telco_df$MultipleLines)
##      
##         No No phone service  Yes
##   No  2337              476 2120
##   Yes 1053              206  851
fisher.test(telco_df$Dependents, telco_df$MultipleLines)
## 
##  Fisher's Exact Test for Count Data
## 
## data:  telco_df$Dependents and telco_df$MultipleLines
## p-value = 0.1082
## alternative hypothesis: two.sided

OBSERVATION: At significance level of 5%, we fail to reject the null hypothesis that there is significant relationship between Dependents and MultipleLines.
Which makes sense as having dependents likely means there is a need for multiple lines.

OBSERVATION: At significance level of 5%, we fail to reject the null hypothesis that there is significant relationship between Dependents and MultipleLines.
Which makes sense as having dependents likely means there is a need for multiple lines.

TODO: Correspondence Analysis


5 Analysis 1 - Survival Analysis

  • Kaplan-Meier Cuve: Probability curve in survival analysis
  • Cox Proportional-Hazard (PH): Hypothesis testing in survival analysis
  • Log Rank Test: Regression in survival analysis

(TODO): Add Explanation

5.1 Analysis 1.1. Kaplan-Meier & Logrank

The null hypothesis in Logrank test states that there is no difference between the populations in the probability of an event (here a churn) ref: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC403858/#:~:text=The%20logrank%20test%20is%20used,of%20events%20(here%20deaths).

# add 'is_churn' column to conduct survival anlaysis 
telco_df$is_churn <- ifelse(telco_df$Churn == 'Yes', 1, 0)
# check N/A in each column. 
plot(survfit(Surv(tenure, is_churn) ~ 1, data = telco_df), 
     xlab = "Days", 
     ylab = "Overall survival probability")

surv_object <- Surv(time = telco_df$tenure, event = telco_df$is_churn)
fit <- survfit(surv_object ~ gender, data = telco_df)
ggsurvplot(fit, data = telco_df, 
           pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE
          )

OBSERVATION:
The evidence is not sufficient to reject the null hypothesis.
Null hypothesis: Gender is not a factor that distinguishes the probability to churn.

fit1 <- survfit(surv_object ~ SeniorCitizen, data = telco_df)
ggsurvplot(fit1, data = telco_df, 
           pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)

OBSERVATION:

The evidence is sufficient to reject the null hypothesis.
Null hypothesis: SeniorCitizen is not a factor that distinguishes the probability to churn.

Non-senior citizens (SeniorCitizen = 0) are more likely to stay longer with the Telco service. The difference in the probabilities between the Senior and non-Senior Citizens staying with Telco service becomes more different as the tenure gets longer. Staying with the firm for 60 weeks is ~75% for non-senior citizens vs. ~50% for senior citiznes.

fit1 <- survfit(surv_object ~ Partner, data = telco_df)
ggsurvplot(fit1, data = telco_df, 
           pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)

OBSERVATION:

The evidence is sufficient to reject the null hypothesis. Null hypothesis: Being a Partner is not a factor that distinguishes the probability to churn. Customer that are partners (Partner = 1) are more likely to stay longer with the Telco service. Different from SeniorCitizen, the difference in survival probability for Partner diverged quickly in the earlier tenure, and the difference in probability roughly identical (roughly parallel) throughout the tenures.

fit1 <- survfit(surv_object ~ Dependents, data = telco_df)
ggsurvplot(fit1, data = telco_df, 
           pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)

OBSERVATION:
The evidence is sufficient to reject the null hypothesis. Null hypothesis: Dependents is not a factor that distinguishes the probability to churn. Having Dependents is correlated with staying longer with the Telco service, meaning less likely to churn keeping the tenure identical.

fit1 <- survfit(surv_object ~ PhoneService, data = telco_df)
ggsurvplot(fit1, data = telco_df, 
           pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)

OBSERVATION:

The evidence is not sufficient to reject the null hypothesis. Null hypothesis: PhoneService is not a factor that distinguishes the probability to churn. Telco should be aware that customer having a PhoneService is not a contributing factor to the customer’s staying with the service.

fit1 <- survfit(surv_object ~ MultipleLines, data = telco_df)
ggsurvplot(fit1, data = telco_df, 
           pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)

OBSERVATION:

The evidence is sufficient to reject the null hypothesis. Null hypothesis: MultipleLines is not a factor that distinguishes the probability to churn. What was interesting here was that the churn probability was in the following order: (most likely to get churned earlier) Single Line of phone service - No phone service - Multiple lines of phone service (least likely to get churned earlier)

fit1 <- survfit(surv_object ~ InternetService, data = telco_df)
ggsurvplot(fit1, data = telco_df, 
           pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)

OBSERVATION (perhaps the most interesting): The evidence is sufficient to reject the null hypothesis. Null hypothesis: InternetSrervice is not a factor that distinguishes the probability to churn. The magnitude of differences across groups were the very large for InternetService. Interestingly, customers without InternetService (in this data, meaning those only with phone service) had the largest survival rate. We had the largest churn rate for customers with Fiber Optics for the internet service.

More similar looking plots
plotting ref: https://rpkgs.datanovia.com/survminer/reference/arrange_ggsurvplots.html

options(repr.plot.width = 14, repr.plot.height = 10)

splots <- list()

fit <- survfit(surv_object ~ OnlineSecurity, data = telco_df)
online_security_plt <- ggsurvplot(fit, data = telco_df, 
                                  pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)
splots[[1]] <- online_security_plt

fit <- survfit(surv_object ~ OnlineBackup, data = telco_df)
online_backup_plt <- ggsurvplot(fit, data = telco_df, 
                                pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)
splots[[2]] <- online_backup_plt

fit <- survfit(surv_object ~ DeviceProtection, data = telco_df)
device_protection_plt <- ggsurvplot(fit, data = telco_df, 
                                    pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)
splots[[3]] <- device_protection_plt

fit <- survfit(surv_object ~ TechSupport, data = telco_df)
tech_support_plt <- ggsurvplot(fit, data = telco_df, 
                               pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)
splots[[4]] <- tech_support_plt

arrange_ggsurvplots(splots, print = TRUE, ncol = 2, nrow = 1)

OBSERVATION:
The above four plots look very similar. Need to check if the four services are highly correlated (e.g, the majority of customers have no OnlineSecurity also does not have OnlineBackup, DeviceProtection, and TechSupport), which sounds very likely.

options(repr.plot.width = 14, repr.plot.height = 10)

splots <- list()


fit <- survfit(surv_object ~ StreamingTV, data = telco_df)
streamingTV <- ggsurvplot(fit, data = telco_df, 
                          pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)
splots[[1]] <- streamingTV

fit <- survfit(surv_object ~ StreamingMovies, data = telco_df)
streamingMovie <- ggsurvplot(fit, data = telco_df, 
                             pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)
splots[[2]] <- streamingMovie

arrange_ggsurvplots(splots, print = TRUE, ncol = 2, nrow = 1)

OBSERVATION:
The above two plots look very similar. Need to check if the two services are highly correlated (e.g, the majority of customers who have StreaminbTV also have StreamingMovie), which sounds very likely.

fit1 <- survfit(surv_object ~ Contract, data = telco_df)
ggsurvplot(fit1, data = telco_df, 
           pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)

OBSERVATION: The evidence is sufficient to reject the null hypothesis. Null hypothesis: Contract is not a factor that distinguishes the probability to churn. This result is hardly surprising given one year ~= 52 weeks and two years ~= 104 weeks, so do not see any significant drop before that many weeks for the One year and Two year contracts.

fit1 <- survfit(surv_object ~ PaperlessBilling, data = telco_df)
ggsurvplot(fit1, data = telco_df, 
           pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)

OBSERVATION: The evidence is sufficient to reject the null hypothesis. Null hypothesis: PaperlessBilling is not a factor that distinguishes the probability to churn. This was interesting as PaperlessBilling having correlation to time-to-churn was not very intuitive; customers who rolled in for PaperlessBilling was more likely to churn quicker compared to those without PaperlessBilling.

fit1 <- survfit(surv_object ~ PaymentMethod, data = telco_df)
ggsurvplot(fit1, data = telco_df, 
           pval = TRUE, 
           conf.int = TRUE, 
           risk.table = TRUE)

OBSERVATION: The evidence is sufficient to reject the null hypothesis. Null hypothesis: PaymentMethod is not a factor that distinguishes the probability to churn. Those who signed up for automatic payment services (bank transfer and credit card) were more likely to stay longer with the service.Telco could assist the customers with setting up the automated payments or have a promotion that could give enough motivation for the customers to sign up for the automated payments.

5.2 Analysis 1.2. CoxPH

head(telco_df)
##   customerID gender SeniorCitizen Partner Dependents tenure PhoneService
## 1 7590-VHVEG Female             0     Yes         No      1           No
## 2 5575-GNVDE   Male             0      No         No     34          Yes
## 3 3668-QPYBK   Male             0      No         No      2          Yes
## 4 7795-CFOCW   Male             0      No         No     45           No
## 5 9237-HQITU Female             0      No         No      2          Yes
## 6 9305-CDSKC Female             0      No         No      8          Yes
##      MultipleLines InternetService OnlineSecurity OnlineBackup DeviceProtection
## 1 No phone service             DSL             No          Yes               No
## 2               No             DSL            Yes           No              Yes
## 3               No             DSL            Yes          Yes               No
## 4 No phone service             DSL            Yes           No              Yes
## 5               No     Fiber optic             No           No               No
## 6              Yes     Fiber optic             No           No              Yes
##   TechSupport StreamingTV StreamingMovies       Contract PaperlessBilling
## 1          No          No              No Month-to-month              Yes
## 2          No          No              No       One year               No
## 3          No          No              No Month-to-month              Yes
## 4         Yes          No              No       One year               No
## 5          No          No              No Month-to-month              Yes
## 6          No         Yes             Yes Month-to-month              Yes
##               PaymentMethod MonthlyCharges TotalCharges Churn
## 1          Electronic check          29.85        29.85    No
## 2              Mailed check          56.95      1889.50    No
## 3              Mailed check          53.85       108.15   Yes
## 4 Bank transfer (automatic)          42.30      1840.75    No
## 5          Electronic check          70.70       151.65   Yes
## 6          Electronic check          99.65       820.50   Yes
##                    InternetPhoneServices total_charges_theoretical
## 1          Phone:  No  |  Internet:  DSL                     29.85
## 2         Phone:  Yes  |  Internet:  DSL                   1936.30
## 3         Phone:  Yes  |  Internet:  DSL                    107.70
## 4          Phone:  No  |  Internet:  DSL                   1903.50
## 5 Phone:  Yes  |  Internet:  Fiber optic                    141.40
## 6 Phone:  Yes  |  Internet:  Fiber optic                    797.20
##   total_charges_diff is_churn
## 1               0.00        0
## 2             -46.80        0
## 3               0.45        1
## 4             -62.75        0
## 5              10.25        1
## 6              23.30        1

Since we have features that are hierarchical, we can stratify customers based on columns:
Where, a subset of features are only applicable for a sertain subset of groups (e.g., OnlineSecurity is not an applicable column for those who does NOT have InternetService) 1. w/ only PhoneService 2. w/ only InternetService 3. w/ both InternetService and PhoneService

# there is no true or false column for internet service 
telco_df$has_InternetService <- ifelse(telco_df$InternetService != "No", "Yes", "No")
telco_df$has_InternetService <- as.factor(telco_df$has_InternetService)

telco_df %>% count(PhoneService, has_InternetService)
##   PhoneService has_InternetService    n
## 1           No                 Yes  682
## 2          Yes                  No 1526
## 3          Yes                 Yes 4835
# observation: we have descent numbers of data points per PhoneService and has_InternetService combinations


# subset data based on internet and phone services. 
only_phone_service_df <- telco_df %>% 
    filter(PhoneService == "Yes" & has_InternetService == "No") %>% 

    dplyr::select(-c(Churn, InternetService, OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTV, StreamingMovies, TotalCharges, InternetPhoneServices, total_charges_theoretical, total_charges_diff))

only_internet_service_df <- telco_df %>% 
    filter(PhoneService == "No" & has_InternetService == "Yes") %>%
    dplyr::select(-c(Churn, MultipleLines, PhoneService, TotalCharges, InternetPhoneServices, total_charges_theoretical, total_charges_diff))

both_services_df <- telco_df %>% 
    filter(PhoneService == "Yes" & has_InternetService == "Yes") %>%
    dplyr::select(-c(Churn, TotalCharges, InternetPhoneServices, total_charges_theoretical, total_charges_diff))
# only phone service 
only_phone_fit <- coxph(Surv(tenure, is_churn) ~ 
                        gender + factor(SeniorCitizen) + 
                        MultipleLines + Partner + Dependents + Contract + PaperlessBilling + PaymentMethod + MonthlyCharges, 
                        data = only_phone_service_df)
only_phone_ftest <- cox.zph(only_phone_fit)
only_phone_ftest
##                         chisq df     p
## gender                 0.3955  1 0.529
## factor(SeniorCitizen)  0.3407  1 0.559
## MultipleLines          3.9501  1 0.047
## Partner                0.0411  1 0.839
## Dependents             0.0212  1 0.884
## Contract               3.1695  2 0.205
## PaperlessBilling       1.6147  1 0.204
## PaymentMethod          6.0521  3 0.109
## MonthlyCharges         1.7684  1 0.184
## GLOBAL                16.0418 12 0.189
head(only_internet_service_df)
##   customerID gender SeniorCitizen Partner Dependents tenure InternetService
## 1 7590-VHVEG Female             0     Yes         No      1             DSL
## 2 7795-CFOCW   Male             0      No         No     45             DSL
## 3 6713-OKOMC Female             0      No         No     10             DSL
## 4 8779-QRDMV   Male             1      No         No      1             DSL
## 5 8665-UTDHZ   Male             0     Yes        Yes      1             DSL
## 6 0526-SXDJP   Male             0     Yes         No     72             DSL
##   OnlineSecurity OnlineBackup DeviceProtection TechSupport StreamingTV
## 1             No          Yes               No          No          No
## 2            Yes           No              Yes         Yes          No
## 3            Yes           No               No          No          No
## 4             No           No              Yes          No          No
## 5             No          Yes               No          No          No
## 6            Yes          Yes              Yes          No          No
##   StreamingMovies       Contract PaperlessBilling             PaymentMethod
## 1              No Month-to-month              Yes          Electronic check
## 2              No       One year               No Bank transfer (automatic)
## 3              No Month-to-month               No              Mailed check
## 4             Yes Month-to-month              Yes          Electronic check
## 5              No Month-to-month               No          Electronic check
## 6              No       Two year               No Bank transfer (automatic)
##   MonthlyCharges is_churn has_InternetService
## 1          29.85        0                 Yes
## 2          42.30        0                 Yes
## 3          29.75        0                 Yes
## 4          39.65        1                 Yes
## 5          30.20        1                 Yes
## 6          42.10        0                 Yes

6 REFERENCE

LS0tDQp0aXRsZTogIkZJTkFMIEVYQU0iDQpzdWJ0aXRsZTogIlNVUlZJVkFMTU9ERUxTIg0KYXV0aG9yOiAiQnJpZ2l0YSBUaWFyYSBFbGdpdHlhbmEgTWVsYW50aWthICgyMDIwNDkyMDAwMSkiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy5EYXRlKCksICclQiAlZCwgJVknKWAiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50OiANCiAgICBodG1sX2RvY3VtZW50OiBudWxsDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgdGhlbWU6IHNhbmRzdG9uZQ0KICAgIGNzczogc3R5bGUxLmNzcw0KICAgIGhpZ2hsaWdodDogbW9ub2Nocm9tZQ0KLS0tDQoNCmBgYHtyIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoY2xhc3Muc291cmNlID0gIm5vY29weSIsDQogICAgICAgICAgICAgICAgICAgICAgY2xhc3Mub3V0cHV0ID0gIm5vY29weSIsDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEYsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEYpDQpgYGAgICAgICAgICAgICAgICAgICAgICAgDQoNCjxpbWcgc3R5bGU9ImZsb2F0OiByaWdodDsgbWFyZ2luOiAwcHggMTAwcHggMHB4IDBweDsgd2lkdGg6MjUlIiBzcmM9ImZvdG9iYXJ1a3UuanBlZyIvPiANCg0KYGBge3IgbG9nbywgZWNobz1GQUxTRSxmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aCA9ICczMCUnfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImxvZ29tYXRhbmEucG5nIikNCmBgYA0KDQpFbWFpbCAmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsmbmJzcDs6ICBicmlnaXRhLm1lbGFudGlrYUBzdHVkZW50Lm1hdGFuYXVuaXZlcnNpdHkuYWMuaWQgPGJyPg0KUlB1YnMgICZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOzogaHR0cHM6Ly9ycHVicy5jb20vYnJpZ2l0YXRpYXJhZW0vIDxicj4NCkp1cnVzYW4gJm5ic3A7ICZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOzogW1N0YXRpc3Rpa2FdKGh0dHBzOi8vbWF0YW5hdW5pdmVyc2l0eS5hYy5pZC8/bHk9YWNhZGVtaWMmYz1zYikgPGJyPg0KQWRkcmVzcyAgJm5ic3A7ICZuYnNwOyAmbmJzcDsgJm5ic3A7IDogQVJBIENlbnRlciwgTWF0YW5hIFVuaXZlcnNpdHkgVG93ZXIgPGJyPg0KJm5ic3A7ICZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsmbmJzcDsgSmwuIENCRCBCYXJhdCBLYXYsIFJULjEsIEN1cnVnIFNhbmdlcmVuZywgS2VsYXBhIER1YSwgVGFuZ2VyYW5nLCBCYW50ZW4gMTU4MTAuDQoNCioqKioNCg0KIyBDVVNUT01FUiBDSFVSTiBBTkFMWVNJUyANCg0KLS0tDQoqKkdvYWw6KiogIA0KRW5kIHRvIGVuZCBhbmFseXNpcyBvbiBUZWxjbyBjdXN0b21lciBjaHVybiBkYXRhIG1haW4gZm9jdXMgYmVpbmc6IA0KLSBJZGVudGlmeSB3aGF0IGZlYXR1cmVzIGNvbnRyaWJ1dGUgdG8gY3VzdG9tZXIgY2h1cm4uICANCi0gTWFnbml0dWRlIG9mIGVhY2ggZmVhdHVyZSdzIGNvbnRyaWJ1dGlvbiBhbmQgdW5jZXJ0YWludGllcyBhc3NvY2lhdGVkLiANCi0gQnVzaW5lc3MgY29uY2x1c2lvbiB0aGF0IHdlIGNhbiBzaGFyZSB3aXRoIFRlbGNvLiAgDQoNCg0KSSBhZGRlZCByZWZlcmVuY2VzIGF0IHRoZSBlbmQgb2YgdGhlIG5vdGVib29rIHRoYXQgYWxsIGNvbXBvc2VkIHRoZSBiaXRzIGFuZCBwaWVjZXMgb2YgdGhpcyBub3RlYm9vay4gUGxlYXNlIGdvIGNoZWNrIHRoZW0gb3V0LiANCg0KKioqDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpICMgaW1wb3J0cyBnZ3Bsb3QyLCBkcGx5ciwgdGlkeXIsIHJlYWRyLCBwdXJyciwgdGliYmxlLCBzdHJpbmdyLCBhbmQgZm9yY2F0cw0KDQojIE5vcm1hbGl0eSBUZXN0DQpsaWJyYXJ5KGNhcikgIyBmb3IgcXFwbG90DQpsaWJyYXJ5KG5vcnRlc3QpICMgQW5kZXJzb24tRGFybGluZyBub3JtYWxpdHkgdGVzdA0KDQojIENvcnJlc3BvbmRlbmNlIEFuYWx5c2lzIA0KbGlicmFyeShGYWN0b01pbmVSKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KDQojIHRpbWUtdG8tZXZlbnQgKHN1cnZpdmFsKSBhbmFseXNpcyBwYWNrYWdlcw0KbGlicmFyeShLTXN1cnYpDQpsaWJyYXJ5KHN1cnZpdmFsKQ0KbGlicmFyeShzdXJ2bWluZXIpDQoNCiMgY29ycmVsYXRpb24gDQpsaWJyYXJ5KGNvcnJyKQ0KDQojIGNvbnRpbmdlbmN5IHRhYmxlDQpsaWJyYXJ5KE1BU1MpDQoNCg0KIyBwbG90dGluZyANCmxpYnJhcnkoZ3JpZEV4dHJhKSAjIGdyaWQuYXJyYW5nZQ0KDQojIGxpYnJhcnkoZ2xtbmV0KQ0KIyBsaWJyYXJ5KGZhY3RvZXh0cmEpDQojIGxpYnJhcnkoZ2dmb3J0aWZ5KQ0KIyBsaWJyYXJ5KGRldnRvb2xzKQ0KIyAjIGxpYnJhcnkoZ2diaXBsb3QpDQojIGxpYnJhcnkoZmFjdG9leHRyYSkNCiMgbGlicmFyeShNQVNTKQ0KIyBsaWJyYXJ5KGNsdXN0ZXIpDQojIGxpYnJhcnkoZ3JpZEV4dHJhKQ0KIyBsaWJyYXJ5KHBhcnR5KQ0KDQoNCiMgc2V0IHRoZSBtYXggbnVtYmVyIG9mIGNvbHVtbnMgZGlzcGxheWVkIGluIEp1cHl0ZXIgTm90ZWJvb2sgd2l0aG91dCB0cnVuY2F0aW9uDQpvcHRpb25zKHJlcHIubWF0cml4Lm1heC5jb2xzPTMwLCByZXByLm1hdHJpeC5tYXgucm93cz0xMDApDQpgYGANCg0KDQpgYGB7cn0NCiMgbG9hZCBkYXRhIA0KdGVsY29fZGYgPC0gcmVhZC5jc3YoIldBX0ZuLVVzZUNfLVRlbGNvLUN1c3RvbWVyLUNodXJuLmNzdiIpDQpgYGANCg0KDQoqKioNCiMgMS4gSW5pdGlhbCBEYXRhIFF1YWxpdHkgQ2hlY2sNCg0KLSBBcmUgdGhlIGBjdXN0b21lcklEYHMgdW5pcXVlPyBDaGVjayBpZiBpcyB0aGVyZSBvbmx5IG9uZSByb3cgKGRhdGEgcG9pbnQpIHBlciBgY3VzdG9tZXJJRGA/IA0KLSBBbnkgTkFzPyANCg0KIyMgMS4xLiBJcyB0aGUgY3VzdG9tZXJJRCB1bmlxdWU/IFRoYXQgaXMsIGlzIHRoZXJlIG9ubHkgb25lIHJvdyBwZXIgY3VzdG9tZXJJRD8NCg0KYGBge3J9DQpyb3dfY291bnQgPSBucm93KHRlbGNvX2RmKQ0KdW5pcXVlSURfY291bnQgPWxlbmd0aCh1bmlxdWUodGVsY29fZGYkY3VzdG9tZXJJRCkpDQpwcmludChwYXN0ZTAoIk51bWJlciBvZiBSb3dzIDogIiwgcm93X2NvdW50KSkNCnByaW50KHBhc3RlMCgiTnVtYmVyIG9mIFVuaXF1ZSBDdXN0b21lcklEIDogIiwgdW5pcXVlSURfY291bnQpKQ0KDQppZiAocm93X2NvdW50ID09IHVuaXF1ZUlEX2NvdW50KSB7DQogICAgcHJpbnQoIkN1c3RvbWVyIElEIGlzIHVuaXF1ZTsgdGhlciBpcyBvbmUgZGF0YSBwb2ludCBwZXIgQ3VzdG9tZXIgSUQuIikNCn0gZWxzZSB7DQogICAgcHJpbnQoIkN1c3RvbWVyIElEIGlzIG5vdCB1bmlxdWUuIikNCn0NCmBgYA0KDQogKipPQlNFUlZBVElPTlM6KioNCi0gVGhlcmUgaXMgb25seSBvbmUgcm93IHBlciBDdXN0b21lciBJRC4gRWFjaCBDdXN0b21lciBJRCBpcyB1bmlxdWUhDQoNCiMjIDEuMi4gQW55IE5Bcz8NCg0KYGBge3J9DQpzdW1tYXJ5KHRlbGNvX2RmKQ0KYGBgDQoNCiAqKk9CU0VSVkFUSU9OUzoqKg0KLSBObyBOL0FzIGluIGFsbCBjb2x1bW5zIQ0KDQojIDIuIERhdGEgRXhwbG9yYXRpb24NCg0KTGV0J3Mgc3RhcnQgd2l0aCB0aGUgc2ltcGxlciBvYnNlcnZhdGlvbnMgYW5kIG1vdmUgb250byB0aGUgb25lcyB0aGF0IHJlcXVpcmUgYSBiaXQgbW9yZSBlZmZvcnRzIChlLmcuLCBkYXRhIGRpc3RyaWJ1dGlvbiwgY29ycmVsYXRpb24pLiANCg0KIyMgMi4xLiBVbml2YXJpYXRlIERhdGEgRXhwbG9yYXRpb24gDQoNCiMjIyAyLjEuMS4gTnVtZXJpY2FsIFZhcmlhYmxlcw0KDQpgYGB7cn0NCm9wdGlvbnMocmVwci5wbG90LndpZHRoID0gMTQsIHJlcHIucGxvdC5oZWlnaHQgPSA4KQ0KDQojIGNoZWNrIG91dCBkaXN0cmlidXRpb24gb2YgbnVtZXJpYyBmZWF0dXJlKHMpIA0KdGVsY29fZGYgJT4lDQogIGtlZXAoaXMubnVtZXJpYykgJT4lIA0KICBnYXRoZXIoKSAlPiUgDQogIGdncGxvdChhZXModmFsdWUpKSArIHRoZW1lX21pbmltYWwoKSArDQogICAgZmFjZXRfd3JhcCh+IGtleSwgc2NhbGVzID0gImZyZWUiKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oKSArIA0KICAgIHRoZW1lKGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYsZmFjZT0iYm9sZCIpKSArIA0KICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSkgIA0KYGBgDQoNCg0KYGBge3J9DQojIG1vcmUgb24gdGVudXJlIGRpc3RyaWJ1dGlvbiAoTnVtYmVyIG9mIE1vbnRocykNCm1pbih0ZWxjb19kZiR0ZW51cmUpDQptYXgodGVsY29fZGYkdGVudXJlKQ0Kc3ByaW50ZignTWF4IHRlbnVyZSBpcyAlaSB5ZWFycy4nLCBtYXgodGVsY29fZGYkdGVudXJlKS8xMikNCmBgYA0KDQoNCiAqKk9CU0VSVkFUSU9OUzoqKg0KLSBBZ2Fpbiwgd2Ugc2VlIHRoYXQgdGhlIHZhbHVlcyBvZiBgU2VuaW9yQ2l0aXplbmAgaXMgdXNpbmcgbnVtZXJpYyAwIGFuZCAxIGFzIGJpbmFyeSBsYWJlbCBmb3IgVHJ1ZSBhbmQgRmFsc2UsIHJlc3BlY3RpdmVseS4gDQotIGBNb250aGx5Q2hhcmdlc2AgY29sdW1uIGhhcyBhIGhpZ2ggY29uY2VudHJhdGlvbiBhcm91bmQgMjAsIGFuZCB0aGUgcmVzdCB0aGUgdmFsdWVzIGFyZSBzb21ld2hhdCBhIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBza2V3IHRvIHRoZSBsZWZ0Lg0KLSBgVG90YWxDaGFyZ2VzYCBjb2x1bW4gc2VlbSB0byBmb2xsb3cgYW4gZXhwb25lbnRpYWwgZGlzdHJpYnV0aW9uLiANCg0KIyMjIDIuMS4yLiBDYXRlZ29yaWNhbCBWYXJpYWJsZXMNCnJlZjogaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMzgxODQyODgvaG93LXRvLXBsb3QtbXVsdGlwbGUtZmFjdG9yLWNvbHVtbnMtd2l0aC1nZ3Bsb3QNCg0KYGBge3J9DQpjb2xuYW1lcyh0ZWxjb19kZikNCmBgYA0KDQpgYGB7cn0NCm9wdGlvbnMocmVwci5wbG90LndpZHRoID0gMTQsIHJlcHIucGxvdC5oZWlnaHQgPSAxMCkNCg0KdGVsY29fZGYgJT4lDQogIGtlZXAoaXMubnVtZXJpYykgJT4lIA0KICBnYXRoZXIoKSAlPiUgDQogIGdncGxvdChhZXModmFsdWUpKSArIHRoZW1lX21pbmltYWwoKSArDQogICAgZmFjZXRfd3JhcCh+IGtleSwgc2NhbGVzID0gImZyZWUiKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oKSArIA0KICAgIHRoZW1lKGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYsZmFjZT0iYm9sZCIpKSArIA0KICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSkgDQpgYGANCg0KDQogKipPQlNFUlZBVElPTlM6KioNCi0gV2UgaGF2ZSBhIHVuaXF1ZSBrZXkgKGBjdXN0b21lcklEYCksIDE2IGNhdGVnb3JpY2FsIGNvbHVtbnMsIGFuZCA0IG51bWVyaWNhbCBjb2x1bW5zLiANCi0gYFNlbmlvckNpdGl6ZW5gLCBvbmUgb2YgdGhlIG51bWVyaWNhbCBjb2x1bW5zLCB1c2VzIDEgYW5kIDAgYXMgYSBiaW5hcnkgbGFiZWwgKHdvbmRlciBpdCBpcyBub3QgaW4gVHJ1ZS9GYWxzZSBsaWtlIG1hbnkgb3RoZXIgYmluYXJ5IGNhdGVnb3JpY2FsIGNvbHVtbnMpLiAtIDUgY29sdW1ucyBhcmUgb25seSBjb21wb3NlZiBvZiBZZXMgb3IgTm8uIA0KLSA2IGNvbHVtbnMgaGF2ZSBZZXMsIE5vLCBhbmQgYE5vIGludGVybmV0IHNlcnZpY2VgIG9wdGlvbi4gDQotIDEgY29sdW1uLCBgTXVsdGlwbGVMaW5lc2AsIGhhcyBZZXMsIE5vLCBhbmQgYE5vIHBob25lIHNlcnZpY2VgIG9wdGlvbi4NCi0gVGhlIHJlbGFpdG9uc2hpcHMgb2YgIlllcy9ObyArIHRoaXJkIG9wdGlvbiIgY29sdW1ucyBjYW4gYmUgdmlzdWFsaXplZCBsaWtlIHRoZSBmb2xsb3dpbmc6IA0KIA0KIHwgYFBob25lU2VydmljZWAgfCBgTXVsdGlwbGVMaW5lc2AgfCANCiB8IC0tLSB8IC0tLSB8DQogfE5vIHwgTm8gcGhvbmUgc2VydmljZSANCiB8IFllcyB8IFllcyB8IA0KIHwgWWVzIHwgTm8gfCANCiANCiB8IGBJbnRlcmV0U2VydmljZWAgfCBgT25saW5lU2VjdXJpdHlgLCBgT25saW5lQmFja3VwYCwgYERldmljZVByb3RlY3Rpb25gLCBgVGVjaFN1cHBvcnRgLCBgU3RyZWFtaW5nVFZgLCBgU3RyZWFtaW5nTW92aWVgIHwgDQogfCAtLS0gfCAtLS0gfA0KIHxObyB8IE5vIGludGVybmV0IHNlcnZpY2UgDQogfCBZZXMgfCBZZXMgfCANCiB8IFllcyB8IE5vIHwgDQogDQoNCiBUaGUgY29sdW1uIHZhbHVlcyBvbiB0aGUgcmlnaHQgYXJlIGRlcGVuZGVudCBvbiB0aGUgY29sdW1uIHZhbHVlcyBvbiB0aGUgbGVmdCwgdGh1cyA3IGNvbHVtbnMgYWxvbmUgY2FuIGV4cGxhaW4gdGhlIDIgY29sdW1ucyBvbiB0aGUgbGVmdC4gDQoNCiMjIDIuMi4gYE1vbnRobHlDaGFyZ2VzYCBkaXN0cmlidXRpb25zIHNlcGFyYXRlZCBieSBjYXRlZ29yaWNhbCBmZWF0dXJlcy4gDQoNCi0gV2Ugc2F3IGFib3ZlIHRoYXQgdGhlIGBNb250aGx5Q2hhcmdlc2Agc2VlbSB0byBiZSBjb21wb3NlZCBvZiBtdWx0aXBsZSBkaXN0cmlidXRpb25zIChtYXliZSBvZiBhIG5hcnJvdyB0YWxsIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBtZWFuIH4yMCArIGEgc2hhbGxvdyB3aWRlIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBtZWFuIH4gOTApDQotIEluIHNlY2lvbiAxLjIuLCB3ZSBzYXcgdGhhdCB3ZSBoYXZlIGNhdGVnb3JpY2FsIGZlYXR1cmVzIGBJbnRlcm5ldFNlcnZpY2VgIGFuZCBgUGhvbmVTZXJ2aWNlYCB3aGljaCBjb3VsZCBkZXRlcm1pbmUgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgYE1vbnRobHlDaGFyZ2VzYC4gU28gdGhhdCBpcyB3aGF0IHdlIHdpbGwgYmUgc2VlaW5nIGhlcmUuDQoNCmBgYHtyfQ0KZ2dwbG90KHRlbGNvX2RmLCBhZXMoeD1Nb250aGx5Q2hhcmdlcywgY29sb3I9SW50ZXJuZXRTZXJ2aWNlKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsPSJ3aGl0ZSIsIGFscGhhPTAuNSwgcG9zaXRpb249ImlkZW50aXR5IikgKyB0aGVtZV9taW5pbWFsKCkgKyANCiAgICBnZ3RpdGxlKCJgTW9udGhseUNoYXJnZXNgIGJ5IGBJbnRlcm5ldFNlcnZpY2VgIikgKyANCiAgICB0aGVtZShheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEyLGZhY2U9ImJvbGQiKSkgKyANCiAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCkpICsNCiAgICBsYWJzKGNvbG9yPSJJbnRlcm5ldFNlcnZpY2UiKQ0KYGBgDQoNCg0KICoqT0JTRVJWQVRJT05TOioqDQogDQotIFRoZXJlIGlzIG9ubHkgb25lIHJvdyBwZXIgQ3VzdG9tZXIgSUQuIEVhY2ggQ3VzdG9tZXIgSUQgaXMgdW5pcXVlIQ0KLSBDdXN0b21lcnMnIGBNb250aGx5Q2hhcmdlc2AgY291bGQgYmUgaGlnaGx5IGRldGVybWluZWQgYnkgdGhlIHR5cGUgb2YgYEludGVybmV0U2VydmljZWBzLiBGaWJlciBvcHRpY3Mgd2l0aCBhdmVyYWdlIH45MCwgRFNMIH42MCwgYW5kIG5vIHNlcnZpY2UgfjIwLiANCi0gTXVsdGlwbGUgc291cmNlcyB2YWxpZGF0ZWQgdGhhdCBmaWJlciBvcHRpYyBzZXJ2aWNlcyBhcmUgZmFzdGVyIHRoYW4gRFNMIGFuZCBhcmUgZG8gY29zdCBtb3JlIGh0dHBzOi8vd3d3LmhpZ2hzcGVlZGludGVybmV0LmNvbS9yZXNvdXJjZXMvZHNsLXZzLWZpYmVyDQoNCmBgYHtyfQ0KZ2dwbG90KHRlbGNvX2RmLCBhZXMoeD1Nb250aGx5Q2hhcmdlcywgY29sb3I9UGhvbmVTZXJ2aWNlKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsPSJ3aGl0ZSIsIGFscGhhPTAuNSwgcG9zaXRpb249ImlkZW50aXR5IikgKyB0aGVtZV9taW5pbWFsKCkgKyANCiAgICBnZ3RpdGxlKCJgTW9udGhseUNoYXJnZXNgIGJ5IGBQaG9uZVNlcnZpY2VgIikgKyANCiAgICB0aGVtZShheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEyLGZhY2U9ImJvbGQiKSkgKyANCiAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCkpICsNCiAgICBsYWJzKGNvbG9yPSJQaG9uZVNlcnZpY2UiKQ0KYGBgDQoNCiAqKk9CU0VSVkFUSU9OUzoqKg0KIA0KLSBUaGVyZSBpcyBvbmx5IG9uZSByb3cgcGVyIEN1c3RvbWVyIElELiBFYWNoIEN1c3RvbWVyIElEIGlzIHVuaXF1ZSENCi0gQ29tcGFyaW5nIHdpdGggImBNb250aGx5Q2hhcmdlc2AgYnkgYEludGVybmV0U2VydmljZWAiIHBsb3QsIHdlIHNlZSB0aGVyZSBpcyBubyBvdmVybGFwIGJldHdlZW4gdGhlIGN1c3RvbWVycyB3aXRob3V0IChgTm9gKSBQaG9uZVNlcnZpY2UgYW5kIGZpYmVyIG9wdGljcyBpbnRlcm5ldCBzZXJ2aWNlLiANCg0KYGBge3J9DQojIEhvdyBhYm91dCB0aGUgY29tYmluYXRpb24gb2YgdGhlIHR3byAtIENvbnRpbmdlbmN5IFRhYmxlDQp0ZWxjb19kZiRJbnRlcm5ldFBob25lU2VydmljZXMgPC0gcGFzdGUoJ1Bob25lOiAnLCB0ZWxjb19kZiRQaG9uZVNlcnZpY2UsICcgfCAnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdJbnRlcm5ldDogJywgdGVsY29fZGYkSW50ZXJuZXRTZXJ2aWNlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KZ2dwbG90KHRlbGNvX2RmLCBhZXMoeD1Nb250aGx5Q2hhcmdlcywgY29sb3I9SW50ZXJuZXRQaG9uZVNlcnZpY2VzKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsPSJ3aGl0ZSIsIGFscGhhPTAuNSwgcG9zaXRpb249ImlkZW50aXR5IikgKyB0aGVtZV9taW5pbWFsKCkgKyANCiAgICBnZ3RpdGxlKCJgTW9udGhseUNoYXJnZXNgIGJ5IGBQaG9uZVNlcnZpY2VgIGFuZCBgSW50ZXJuZXRTZXJ2aWNlYCIpICsgDQogICAgdGhlbWUoYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMixmYWNlPSJib2xkIikpICsgDQogICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpKSArDQogICAgbGFicyhjb2xvcj0iUGhvbmUgfCBJbnRlcm5ldFNlcnZpY2UiKQ0KDQoNCmdncGxvdCh0ZWxjb19kZiwgYWVzKHggPSBNb250aGx5Q2hhcmdlcykpICsgDQogICAgZ2VvbV9oaXN0b2dyYW0oZmlsbD0id2hpdGUiLCBhZXMoY29sb3I9SW50ZXJuZXRQaG9uZVNlcnZpY2VzKSkgKyB0aGVtZV9taW5pbWFsKCkgKyANCiAgICBmYWNldF93cmFwKCB+IEludGVybmV0UGhvbmVTZXJ2aWNlcykgKyANCiAgICB0aGVtZShheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEyLGZhY2U9ImJvbGQiKSkgKyANCiAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCkpICsNCiAgICBsYWJzKGNvbG9yPSJQaG9uZSB8IEludGVybmV0U2VydmljZSIpDQpgYGANCg0KYGBge3J9DQojIFRPRE86IHN0YXRpc3RpY2FsIGNoZWNrIG9uIHdoZXRoZXIgdGhlIGRpc3RyaWJ1dGlvbnMgYXJlIGNvbWluZyBmcm9tIGRpZmZlcmVudCBwb3B1bGF0aW9ucy4NCmBgYA0KDQoNCiMjIDIuMy4gSXMgYFRvdGFsQ2hhcmdlc2AgZXF1YWwgdG8gYFRlbnJ1ZWAqYE1vbnRobHlDaGFyZ2VzYD88L2ZvbnQ+DQotIFdlIHdpbGwgY2hlY2sgaWYgdGhlIHRvdGFsIGNoYXJnZXMgY29tcHV0ZWQgYnkgYE1vbnRobHlDaGFyZ2VzYCAqIGBUZW51cmVgIChpbiBudW1iZXIgb2YgbW9udGhzKSBpcyBpZGVudGljYWwgdG8gdGhlIGFjdHVhbCBgVG90YWxDaGFyZ2VzYCBpbiB0aGUgZGF0YS4gDQotIE1ldGhvZDogDQogICAgIDEuIENvbXB1dGUgdGhlb3JldGljYWwgdG90YWwgY2hhcmdlcyBgdG90YWxfY2hhcmdlc190aGVvcmV0aWNhbGAgPSBgTW9udGhseUNoYXJnZXNgICogYFRlbnVyZWAuIA0KICAgICAyLiBUYWtlIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gYHRvdGFsX2NoYXJnZXNfdGhlb3JldGljYWxgIGFuZCBgVG90YWxDaGFyZ2VzYC4gU2F2ZSB0byBgdG90YWxfY2hhcmdlc19kaWZmYC4NCiAgICAgMy4gQW5hbHl6ZSB0aGUgZGlzdHJpYnV0aW9uIG9mIGB0b3RhbF9jaGFyZ2VzX2RpZmZgLiANCiANCg0KYGBge3J9DQojIDEuIENvbXB1dGUgdGhlb3JldGljYWwgdG90YWwgY2hhcmdlcyB0b3RhbF9jaGFyZ2VzX3RoZW9yZXRpY2FsID0gTW9udGhseUNoYXJnZXMgKiBUZW51cmUuDQp0ZWxjb19kZiA8LSB0ZWxjb19kZiAlPiUgbXV0YXRlKHRvdGFsX2NoYXJnZXNfdGhlb3JldGljYWwgPSB0ZW51cmUqTW9udGhseUNoYXJnZXMpDQoNCiMgMi4gVGFrZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRvdGFsX2NoYXJnZXNfdGhlb3JldGljYWwgYW5kIFRvdGFsQ2hhcmdlcy4gU2F2ZSB0byB0b3RhbF9jaGFyZ2VzX2RpZmYuDQp0ZWxjb19kZiA8LSB0ZWxjb19kZiAlPiUgbXV0YXRlKHRvdGFsX2NoYXJnZXNfZGlmZiA9IFRvdGFsQ2hhcmdlcyAtIHRvdGFsX2NoYXJnZXNfdGhlb3JldGljYWwpDQoNCiMgMy4gQW5hbHl6ZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHRvdGFsX2NoYXJnZXNfZGlmZiAtIGxldCdzIHN0YXJ0IHdpdGggYSBoaXN0b2dyYW0NCmdncGxvdCh0ZWxjb19kZiwgYWVzKHRvdGFsX2NoYXJnZXNfZGlmZikpICsgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDIwMCkgKyB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoNCiAqKk9CU0VSVkFUSU9OOioqICANCi0gV2UgaGF2ZSBhIHNoYXJwIHZlcnRpY2FsIGJhciBhdCAwLiANCi0gVGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgcmVzdCBvZiB0aGUgdmFsdWVzIGxvb2sgY2xvc2UgdG8gYSBub3JtYWwgZGlzdHJpYnVpb24gKGxldCdzIGNoZWNrIHRoaXMganVzdCBmb3IgZnVuISkuIA0KDQpgYGB7cn0NCiMgcmVtb3ZlIDAgYW5kIHBsb3QgaGlzdG9ncmFtIA0KZGlmZl9oaXN0X2RhdGEgPC0gdGVsY29fZGYgJT4lIA0KICAgIGZpbHRlcih0b3RhbF9jaGFyZ2VzX2RpZmYgIT0gMCkNCmdncGxvdChkaWZmX2hpc3RfZGF0YSwgYWVzKHRvdGFsX2NoYXJnZXNfZGlmZikpICsgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDIwMCkgKyB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoNCiAqKk9CU0VSVkFUSU9OOioqICANCi0gTG9va3Mgcm91Z2hseSBub3JtYWw/IA0KLSBUaGlzIGlzIGEgZ29vZCB0aW1lIHRvIHRlc3Qgb3V0IG5vcm1hbGl0eSB0ZXN0aW5nIG1ldGhvZHMuIA0KICAtICBNZXRob2QgMTogUVEtUGxvdCANCiAgLSAgTWV0aG9kIDI6IEtvbG1vZ29yb3YtU21pcm5vdiB0ZXN0IA0KICAtICBNZXRob2QgMzogQW5kZXJzb24tRGFybGluZyB0ZXN0DQoNCmBgYHtyfQ0KIyBNZXRob2QgMTogUVEtUGxvdCANCnFxUGxvdChkaWZmX2hpc3RfZGF0YSR0b3RhbF9jaGFyZ2VzX2RpZmYpDQoNCiMgTWV0aG9kIDI6IEtvbG1vZ29yb3YtU21pcm5vdiB0ZXN0DQojIHJlZjogaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjY3MTU4NDMva29sbW9nb3Jvdi1zbWlybm92LXRlc3QtaW4tcg0Ka3MudGVzdChkaWZmX2hpc3RfZGF0YSR0b3RhbF9jaGFyZ2VzX2RpZmYsICdwbm9ybScsIDAsIHNkKGRpZmZfaGlzdF9kYXRhJHRvdGFsX2NoYXJnZXNfZGlmZikpDQoNCiMgTWV0aG9kIDM6IEFuZGVyc29uLURhcmxpbmcgdGVzdA0KYWQudGVzdChkaWZmX2hpc3RfZGF0YSR0b3RhbF9jaGFyZ2VzX2RpZmYpDQpgYGANCg0KICoqT0JTRVJWQVRJT05TOioqDQotIFFRLXBsb3QgaW5kaWNhdGVzIHRoYXQgdGhlIGRpc3RyaWJ1dGlvbiBoYXMgbGlnaHQgdGFpbHMuIA0KICAgIC0gcmVmOiANCiAgICAgICAgIC0gaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj12TWFLeDlmbUpIRQ0KICAgICAgICAgLSBodHRwczovL3N0YXRzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy8xMDEyNzQvaG93LXRvLWludGVycHJldC1hLXFxLXBsb3QNCi0gQm90aCBLUyBhbmQgQUQgbm9ybWFsaXR5IHRlc3RzIGluZGljYXRlIHRoYXQgdGhlcmUgaXMgbm90IGVub3VnaCBldmlkZW5jZSB0byBjb25jbHVzZSB0aGUgZGlzdHJpYnV0aW9uIGlzIG5vdCBub3JtYWwuDQoNCiANCiAoVE9ETykgb3RoZXIgc2lkZSBxdWVzdGlvbihzKTogDQotIFdoYXQgZmVhdHVyZXMgYXJlIHJlbGF0ZWQgdG8gdGhlIGRpZmZlcmVuY2UgaW4gdGhlIHRoZW9yZXRpY2FsIGFuZCBhY3R1YWwgdG90YWwgY2hhcmdlcz8gDQogICAgMS4gRGl2aWRlIHRoZSBkaWZmZXJlbmNlIGluIHRoZW9yZXRpY2FsIC0gYWN0dWFsIHRvdGFsIGNoYXJnZXMgYnkgdGVudXJlIHRvIG9idGFpbiBtb250aGx5IGRpZmZlcmVuY2UgKGhvdyBtdWNoIG1vcmUgb3IgbGVzcyBvbmUgcGFpZCBjb21wYXJlZCB0byB0aGUgbWVhbikuIA0KICAgMi4gQ29uZHVjdCByZWdyZXNzaW9uIG1ldGhvZCB0byBzZWUgd2hpY2ggZmVhdHVyZShzKSBhcmUgcmVsYXRlZCB0byB0aGUgZGlmZmVyZW5jZS4gU29tZXRoaW5nIGxpa2UgYSByZWdyZXNzaW9uIF95KGRpZmZlcmVuY2UpIH4geDEodGVudXJlKSArIHgyKFBob25lU2VydmljZSkgKyAuLi4gKyB4bl8NCiAgIDMuIHBvdGVudGlhbCBjb25jbHVzaW9uOiBJcyB0ZW51cmUgbmVnYXRpdmVseSBjb3JyZWxhdGVkIHdpdGggbW9udGhseSBjaGFyZ2UgZGlmZmVyZW5jZT8gTG9uZ2VyIHRlbnVyZSByZXN1bHQgaW4gYSBsYXJnZXIgZGlzY291bnQ/DQogICANCg0KDQojIDMuMi4gQml2YXJpYXRlIERhdGEgRXhwbG9yYXRpb24NCg0KIENsZWFybHkgaW5kZXBlbmRlbnQgY2F0ZWdvcmljYWwgZmVhdHVyZXM6IA0KDQoqRmlzaGVyJ3MgZXhhY3QgdGVzdCBpcyBtb3JlIGFjY3VyYXRlIHRoYW4gdGhlIGNoaS1zcXVhcmUgdGVzdCBvciBH4oCTdGVzdCBvZiBpbmRlcGVuZGVuY2Ugd2hlbiB0aGUgZXhwZWN0ZWQgbnVtYmVycyBhcmUgc21hbGwuIEkgcmVjb21tZW5kIHlvdSB1c2UgRmlzaGVyJ3MgZXhhY3QgdGVzdCB3aGVuIHRoZSB0b3RhbCBzYW1wbGUgc2l6ZSBpcyBsZXNzIHRoYW4gMTAwMCwgYW5kIHVzZSB0aGUgY2hpLXNxdWFyZSBvciBH4oCTdGVzdCBmb3IgbGFyZ2VyIHNhbXBsZSBzaXplcy4qDQpyZWY6IGh0dHA6Ly93d3cuYmlvc3RhdGhhbmRib29rLmNvbS9maXNoZXJzLmh0bWwjOn46dGV4dD1GaXNoZXIncyUyMGV4YWN0JTIwdGVzdCUyMGlzJTIwbW9yZSx0ZXN0JTIwZm9yJTIwbGFyZ2VyJTIwc2FtcGxlJTIwc2l6ZXMuDQoNCmBgYHtyfQ0KdGFibGUodGVsY29fZGYkSW50ZXJuZXRTZXJ2aWNlLCB0ZWxjb19kZiRNdWx0aXBsZUxpbmVzKQ0KYGBgDQoNCmBgYHtyfQ0KIyAlJSBbY29kZV0NCnRhYmxlKHRlbGNvX2RmJE11bHRpcGxlTGluZXMsIHRlbGNvX2RmJFBob25lU2VydmljZSkNCmBgYA0KDQpgYGB7cn0NCnRhYmxlKHRlbGNvX2RmJE9ubGluZVNlY3VyaXR5LCB0ZWxjb19kZiRPbmxpbmVCYWNrdXApDQpgYGANCg0KYGBge3J9DQp0YWJsZSh0ZWxjb19kZiRQYXJ0bmVyLCB0ZWxjb19kZiREZXBlbmRlbnRzKQ0KZmlzaGVyLnRlc3QodGVsY29fZGYkUGFydG5lciwgdGVsY29fZGYkRGVwZW5kZW50cykNCmBgYA0KDQoNCiAqKk9CU0VSVkFUSU9OOioqIEF0IHNpZ25pZmljYW5jZSBsZXZlbCBvZiA1JSwgd2UgcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMgdGhhdCB0aGVyZSBpcyBzaWduaWZpY2FudCByZWxhdGlvbnNoaXAgYmV0d2VlbiBgUGFydG5lcmAgYW5kIGBEZXBlbmRlbnRzYC4gDQoNCmBgYHtyfQ0KdGFibGUodGVsY29fZGYkUGFydG5lciwgdGVsY29fZGYkTXVsdGlwbGVMaW5lcykNCmZpc2hlci50ZXN0KHRlbGNvX2RmJFBhcnRuZXIsIHRlbGNvX2RmJE11bHRpcGxlTGluZXMpDQpgYGANCg0KDQoqKk9CU0VSVkFUSU9OOioqIEF0IHNpZ25pZmljYW5jZSBsZXZlbCBvZiA1JSwgd2UgcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMgdGhhdCB0aGVyZSBpcyBzaWduaWZpY2FudCByZWxhdGlvbnNoaXAgYmV0d2VlbiBQYXJ0bmVyIGFuZCBEZXBlbmRlbnRzLg0KDQpgYGB7cn0NCnRhYmxlKHRlbGNvX2RmJERlcGVuZGVudHMsIHRlbGNvX2RmJE11bHRpcGxlTGluZXMpDQpmaXNoZXIudGVzdCh0ZWxjb19kZiREZXBlbmRlbnRzLCB0ZWxjb19kZiRNdWx0aXBsZUxpbmVzKQ0KYGBgDQoNCiAqKk9CU0VSVkFUSU9OOioqIEF0IHNpZ25pZmljYW5jZSBsZXZlbCBvZiA1JSwgd2UgZmFpbCB0byByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcyB0aGF0IHRoZXJlIGlzIHNpZ25pZmljYW50IHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGBEZXBlbmRlbnRzYCBhbmQgYE11bHRpcGxlTGluZXNgLiAgIA0KV2hpY2ggbWFrZXMgc2Vuc2UgYXMgaGF2aW5nIGRlcGVuZGVudHMgbGlrZWx5IG1lYW5zIHRoZXJlIGlzIGEgbmVlZCBmb3IgbXVsdGlwbGUgbGluZXMuDQoNCiAqKk9CU0VSVkFUSU9OOioqIEF0IHNpZ25pZmljYW5jZSBsZXZlbCBvZiA1JSwgd2UgZmFpbCB0byByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcyB0aGF0IHRoZXJlIGlzIHNpZ25pZmljYW50IHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGBEZXBlbmRlbnRzYCBhbmQgYE11bHRpcGxlTGluZXNgLiAgIA0KIFdoaWNoIG1ha2VzIHNlbnNlIGFzIGhhdmluZyBkZXBlbmRlbnRzIGxpa2VseSBtZWFucyB0aGVyZSBpcyBhIG5lZWQgZm9yIG11bHRpcGxlIGxpbmVzLg0KDQpUT0RPOiBDb3JyZXNwb25kZW5jZSBBbmFseXNpcw0KDQoqKioNCg0KIyBBbmFseXNpcyAxIC0gU3Vydml2YWwgQW5hbHlzaXMgDQotIEthcGxhbi1NZWllciBDdXZlOiBQcm9iYWJpbGl0eSBjdXJ2ZSBpbiBzdXJ2aXZhbCBhbmFseXNpcyANCi0gQ294IFByb3BvcnRpb25hbC1IYXphcmQgKFBIKTogSHlwb3RoZXNpcyB0ZXN0aW5nIGluIHN1cnZpdmFsIGFuYWx5c2lzIA0KLSBMb2cgUmFuayBUZXN0OiBSZWdyZXNzaW9uIGluIHN1cnZpdmFsIGFuYWx5c2lzDQogDQogKFRPRE8pOiBBZGQgRXhwbGFuYXRpb24NCg0KIyMgQW5hbHlzaXMgMS4xLiBLYXBsYW4tTWVpZXIgJiBMb2dyYW5rDQogVGhlIG51bGwgaHlwb3RoZXNpcyBpbiBMb2dyYW5rIHRlc3Qgc3RhdGVzIHRoYXQgX3RoZXJlIGlzIG5vIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgcG9wdWxhdGlvbnMgaW4gdGhlIHByb2JhYmlsaXR5IG9mIGFuIGV2ZW50IChoZXJlIGEgY2h1cm4pXw0KIHJlZjogaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNDAzODU4LyM6fjp0ZXh0PVRoZSUyMGxvZ3JhbmslMjB0ZXN0JTIwaXMlMjB1c2VkLG9mJTIwZXZlbnRzJTIwKGhlcmUlMjBkZWF0aHMpLg0KDQpgYGB7cn0NCiMgYWRkICdpc19jaHVybicgY29sdW1uIHRvIGNvbmR1Y3Qgc3Vydml2YWwgYW5sYXlzaXMgDQp0ZWxjb19kZiRpc19jaHVybiA8LSBpZmVsc2UodGVsY29fZGYkQ2h1cm4gPT0gJ1llcycsIDEsIDApDQpgYGANCg0KYGBge3J9DQojIGNoZWNrIE4vQSBpbiBlYWNoIGNvbHVtbi4gDQpwbG90KHN1cnZmaXQoU3Vydih0ZW51cmUsIGlzX2NodXJuKSB+IDEsIGRhdGEgPSB0ZWxjb19kZiksIA0KICAgICB4bGFiID0gIkRheXMiLCANCiAgICAgeWxhYiA9ICJPdmVyYWxsIHN1cnZpdmFsIHByb2JhYmlsaXR5IikNCmBgYA0KDQpgYGB7cn0NCnN1cnZfb2JqZWN0IDwtIFN1cnYodGltZSA9IHRlbGNvX2RmJHRlbnVyZSwgZXZlbnQgPSB0ZWxjb19kZiRpc19jaHVybikNCmBgYA0KDQpgYGB7cn0NCmZpdCA8LSBzdXJ2Zml0KHN1cnZfb2JqZWN0IH4gZ2VuZGVyLCBkYXRhID0gdGVsY29fZGYpDQpnZ3N1cnZwbG90KGZpdCwgZGF0YSA9IHRlbGNvX2RmLCANCiAgICAgICAgICAgcHZhbCA9IFRSVUUsIA0KICAgICAgICAgICBjb25mLmludCA9IFRSVUUsIA0KICAgICAgICAgICByaXNrLnRhYmxlID0gVFJVRQ0KICAgICAgICAgICkNCmBgYA0KDQogKipPQlNFUlZBVElPTjoqKiAgDQpUaGUgZXZpZGVuY2UgaXMgKipub3Qgc3VmZmljaWVudCoqIHRvIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzLiAgDQpOdWxsIGh5cG90aGVzaXM6IGBHZW5kZXJgIGlzIF9ub3RfIGEgZmFjdG9yIHRoYXQgZGlzdGluZ3Vpc2hlcyB0aGUgcHJvYmFiaWxpdHkgdG8gY2h1cm4uIA0KDQpgYGB7cn0NCmZpdDEgPC0gc3VydmZpdChzdXJ2X29iamVjdCB+IFNlbmlvckNpdGl6ZW4sIGRhdGEgPSB0ZWxjb19kZikNCmdnc3VydnBsb3QoZml0MSwgZGF0YSA9IHRlbGNvX2RmLCANCiAgICAgICAgICAgcHZhbCA9IFRSVUUsIA0KICAgICAgICAgICBjb25mLmludCA9IFRSVUUsIA0KICAgICAgICAgICByaXNrLnRhYmxlID0gVFJVRSkNCmBgYA0KDQogKipPQlNFUlZBVElPTjoqKiANCiANClRoZSBldmlkZW5jZSAqKmlzKiogc3VmZmljaWVudCB0byByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcy4gIA0KTnVsbCBoeXBvdGhlc2lzOiBgU2VuaW9yQ2l0aXplbmAgaXMgX25vdF8gYSBmYWN0b3IgdGhhdCBkaXN0aW5ndWlzaGVzIHRoZSBwcm9iYWJpbGl0eSB0byBjaHVybi4gDQoNCk5vbi1zZW5pb3IgY2l0aXplbnMgKGBTZW5pb3JDaXRpemVuYCA9IDApIGFyZSBtb3JlIGxpa2VseSB0byBzdGF5IGxvbmdlciB3aXRoIHRoZSBUZWxjbyBzZXJ2aWNlLiBUaGUgZGlmZmVyZW5jZSBpbiB0aGUgcHJvYmFiaWxpdGllcyBiZXR3ZWVuIHRoZSBTZW5pb3IgYW5kIG5vbi1TZW5pb3IgQ2l0aXplbnMgc3RheWluZyB3aXRoIFRlbGNvIHNlcnZpY2UgYmVjb21lcyBtb3JlIGRpZmZlcmVudCBhcyB0aGUgdGVudXJlIGdldHMgbG9uZ2VyLiBTdGF5aW5nIHdpdGggdGhlIGZpcm0gZm9yIDYwIHdlZWtzIGlzIH43NSUgZm9yIG5vbi1zZW5pb3IgY2l0aXplbnMgdnMuIH41MCUgZm9yIHNlbmlvciBjaXRpem5lcy4gDQoNCmBgYHtyfQ0KZml0MSA8LSBzdXJ2Zml0KHN1cnZfb2JqZWN0IH4gUGFydG5lciwgZGF0YSA9IHRlbGNvX2RmKQ0KZ2dzdXJ2cGxvdChmaXQxLCBkYXRhID0gdGVsY29fZGYsIA0KICAgICAgICAgICBwdmFsID0gVFJVRSwgDQogICAgICAgICAgIGNvbmYuaW50ID0gVFJVRSwgDQogICAgICAgICAgIHJpc2sudGFibGUgPSBUUlVFKQ0KYGBgDQoNCiAqKk9CU0VSVkFUSU9OOioqICANCiANClRoZSBldmlkZW5jZSAqKmlzKiogc3VmZmljaWVudCB0byByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcy4gTnVsbCBoeXBvdGhlc2lzOiBCZWluZyBhIGBQYXJ0bmVyYCBpcyBfbm90XyBhIGZhY3RvciB0aGF0IGRpc3Rpbmd1aXNoZXMgdGhlIHByb2JhYmlsaXR5IHRvIGNodXJuLiBDdXN0b21lciB0aGF0IF9hcmVfIHBhcnRuZXJzIChgUGFydG5lcmAgPSAxKSBhcmUgbW9yZSBsaWtlbHkgdG8gc3RheSBsb25nZXIgd2l0aCB0aGUgVGVsY28gc2VydmljZS4gRGlmZmVyZW50IGZyb20gYFNlbmlvckNpdGl6ZW5gLCB0aGUgZGlmZmVyZW5jZSBpbiBzdXJ2aXZhbCBwcm9iYWJpbGl0eSBmb3IgYFBhcnRuZXJgIGRpdmVyZ2VkIHF1aWNrbHkgaW4gdGhlIGVhcmxpZXIgdGVudXJlLCBhbmQgdGhlIGRpZmZlcmVuY2UgaW4gcHJvYmFiaWxpdHkgcm91Z2hseSBpZGVudGljYWwgKHJvdWdobHkgcGFyYWxsZWwpIHRocm91Z2hvdXQgdGhlIHRlbnVyZXMuICANCg0KYGBge3J9DQpmaXQxIDwtIHN1cnZmaXQoc3Vydl9vYmplY3QgfiBEZXBlbmRlbnRzLCBkYXRhID0gdGVsY29fZGYpDQpnZ3N1cnZwbG90KGZpdDEsIGRhdGEgPSB0ZWxjb19kZiwgDQogICAgICAgICAgIHB2YWwgPSBUUlVFLCANCiAgICAgICAgICAgY29uZi5pbnQgPSBUUlVFLCANCiAgICAgICAgICAgcmlzay50YWJsZSA9IFRSVUUpDQpgYGANCg0KICoqT0JTRVJWQVRJT046KiogIA0KVGhlIGV2aWRlbmNlICoqaXMqKiBzdWZmaWNpZW50IHRvIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzLiBOdWxsIGh5cG90aGVzaXM6IGBEZXBlbmRlbnRzYCBpcyBfbm90XyBhIGZhY3RvciB0aGF0IGRpc3Rpbmd1aXNoZXMgdGhlIHByb2JhYmlsaXR5IHRvIGNodXJuLiBIYXZpbmcgYERlcGVuZGVudHNgIGlzIGNvcnJlbGF0ZWQgd2l0aCBzdGF5aW5nIGxvbmdlciB3aXRoIHRoZSBUZWxjbyBzZXJ2aWNlLCBtZWFuaW5nIGxlc3MgbGlrZWx5IHRvIGNodXJuIGtlZXBpbmcgdGhlIHRlbnVyZSBpZGVudGljYWwuICANCg0KYGBge3J9DQpmaXQxIDwtIHN1cnZmaXQoc3Vydl9vYmplY3QgfiBQaG9uZVNlcnZpY2UsIGRhdGEgPSB0ZWxjb19kZikNCmdnc3VydnBsb3QoZml0MSwgZGF0YSA9IHRlbGNvX2RmLCANCiAgICAgICAgICAgcHZhbCA9IFRSVUUsIA0KICAgICAgICAgICBjb25mLmludCA9IFRSVUUsIA0KICAgICAgICAgICByaXNrLnRhYmxlID0gVFJVRSkNCmBgYA0KDQogKipPQlNFUlZBVElPTjoqKiANCiANClRoZSBldmlkZW5jZSBpcyAqKm5vdCBzdWZmaWNpZW50KiogdG8gcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMuIE51bGwgaHlwb3RoZXNpczogYFBob25lU2VydmljZWAgaXMgX25vdF8gYSBmYWN0b3IgdGhhdCBkaXN0aW5ndWlzaGVzIHRoZSBwcm9iYWJpbGl0eSB0byBjaHVybi4gVGVsY28gc2hvdWxkIGJlIGF3YXJlIHRoYXQgY3VzdG9tZXIgaGF2aW5nIGEgYFBob25lU2VydmljZWAgaXMgbm90IGEgY29udHJpYnV0aW5nIGZhY3RvciB0byB0aGUgY3VzdG9tZXIncyBzdGF5aW5nIHdpdGggdGhlIHNlcnZpY2UuIA0KDQpgYGB7cn0NCmZpdDEgPC0gc3VydmZpdChzdXJ2X29iamVjdCB+IE11bHRpcGxlTGluZXMsIGRhdGEgPSB0ZWxjb19kZikNCmdnc3VydnBsb3QoZml0MSwgZGF0YSA9IHRlbGNvX2RmLCANCiAgICAgICAgICAgcHZhbCA9IFRSVUUsIA0KICAgICAgICAgICBjb25mLmludCA9IFRSVUUsIA0KICAgICAgICAgICByaXNrLnRhYmxlID0gVFJVRSkNCmBgYA0KDQogKipPQlNFUlZBVElPTjoqKiANCiANClRoZSBldmlkZW5jZSAqKmlzKiogc3VmZmljaWVudCB0byByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcy4gTnVsbCBoeXBvdGhlc2lzOiBgTXVsdGlwbGVMaW5lc2AgaXMgX25vdF8gYSBmYWN0b3IgdGhhdCBkaXN0aW5ndWlzaGVzIHRoZSBwcm9iYWJpbGl0eSB0byBjaHVybi4gV2hhdCB3YXMgaW50ZXJlc3RpbmcgaGVyZSB3YXMgdGhhdCB0aGUgY2h1cm4gcHJvYmFiaWxpdHkgd2FzIGluIHRoZSBmb2xsb3dpbmcgb3JkZXI6IChtb3N0IGxpa2VseSB0byBnZXQgY2h1cm5lZCBlYXJsaWVyKSBTaW5nbGUgTGluZSBvZiBwaG9uZSBzZXJ2aWNlIC0gTm8gcGhvbmUgc2VydmljZSAtIE11bHRpcGxlIGxpbmVzIG9mIHBob25lIHNlcnZpY2UgKGxlYXN0IGxpa2VseSB0byBnZXQgY2h1cm5lZCBlYXJsaWVyKSAgDQoNCmBgYHtyfQ0KZml0MSA8LSBzdXJ2Zml0KHN1cnZfb2JqZWN0IH4gSW50ZXJuZXRTZXJ2aWNlLCBkYXRhID0gdGVsY29fZGYpDQpnZ3N1cnZwbG90KGZpdDEsIGRhdGEgPSB0ZWxjb19kZiwgDQogICAgICAgICAgIHB2YWwgPSBUUlVFLCANCiAgICAgICAgICAgY29uZi5pbnQgPSBUUlVFLCANCiAgICAgICAgICAgcmlzay50YWJsZSA9IFRSVUUpDQpgYGANCg0KICoqT0JTRVJWQVRJT04gKHBlcmhhcHMgdGhlIG1vc3QgaW50ZXJlc3RpbmcpKio6DQpUaGUgZXZpZGVuY2UgKippcyoqIHN1ZmZpY2llbnQgdG8gcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMuIE51bGwgaHlwb3RoZXNpczogYEludGVybmV0U3JlcnZpY2VgIGlzIF9ub3RfIGEgZmFjdG9yIHRoYXQgZGlzdGluZ3Vpc2hlcyB0aGUgcHJvYmFiaWxpdHkgdG8gY2h1cm4uIFRoZSBtYWduaXR1ZGUgb2YgZGlmZmVyZW5jZXMgYWNyb3NzIGdyb3VwcyB3ZXJlIHRoZSB2ZXJ5IGxhcmdlIGZvciBgSW50ZXJuZXRTZXJ2aWNlYC4gSW50ZXJlc3RpbmdseSwgY3VzdG9tZXJzIHdpdGhvdXQgYEludGVybmV0U2VydmljZWAgKGluIHRoaXMgZGF0YSwgbWVhbmluZyB0aG9zZSBvbmx5IHdpdGggcGhvbmUgc2VydmljZSkgaGFkIHRoZSBsYXJnZXN0IHN1cnZpdmFsIHJhdGUuIFdlIGhhZCB0aGUgbGFyZ2VzdCBjaHVybiByYXRlIGZvciBjdXN0b21lcnMgd2l0aCBGaWJlciBPcHRpY3MgZm9yIHRoZSBpbnRlcm5ldCBzZXJ2aWNlLiANCg0KTW9yZSBzaW1pbGFyIGxvb2tpbmcgcGxvdHMgIA0KIHBsb3R0aW5nIHJlZjogaHR0cHM6Ly9ycGtncy5kYXRhbm92aWEuY29tL3N1cnZtaW5lci9yZWZlcmVuY2UvYXJyYW5nZV9nZ3N1cnZwbG90cy5odG1sDQoNCg0KYGBge3J9DQpvcHRpb25zKHJlcHIucGxvdC53aWR0aCA9IDE0LCByZXByLnBsb3QuaGVpZ2h0ID0gMTApDQoNCnNwbG90cyA8LSBsaXN0KCkNCg0KZml0IDwtIHN1cnZmaXQoc3Vydl9vYmplY3QgfiBPbmxpbmVTZWN1cml0eSwgZGF0YSA9IHRlbGNvX2RmKQ0Kb25saW5lX3NlY3VyaXR5X3BsdCA8LSBnZ3N1cnZwbG90KGZpdCwgZGF0YSA9IHRlbGNvX2RmLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdmFsID0gVFJVRSwgDQogICAgICAgICAgIGNvbmYuaW50ID0gVFJVRSwgDQogICAgICAgICAgIHJpc2sudGFibGUgPSBUUlVFKQ0Kc3Bsb3RzW1sxXV0gPC0gb25saW5lX3NlY3VyaXR5X3BsdA0KDQpmaXQgPC0gc3VydmZpdChzdXJ2X29iamVjdCB+IE9ubGluZUJhY2t1cCwgZGF0YSA9IHRlbGNvX2RmKQ0Kb25saW5lX2JhY2t1cF9wbHQgPC0gZ2dzdXJ2cGxvdChmaXQsIGRhdGEgPSB0ZWxjb19kZiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB2YWwgPSBUUlVFLCANCiAgICAgICAgICAgY29uZi5pbnQgPSBUUlVFLCANCiAgICAgICAgICAgcmlzay50YWJsZSA9IFRSVUUpDQpzcGxvdHNbWzJdXSA8LSBvbmxpbmVfYmFja3VwX3BsdA0KDQpmaXQgPC0gc3VydmZpdChzdXJ2X29iamVjdCB+IERldmljZVByb3RlY3Rpb24sIGRhdGEgPSB0ZWxjb19kZikNCmRldmljZV9wcm90ZWN0aW9uX3BsdCA8LSBnZ3N1cnZwbG90KGZpdCwgZGF0YSA9IHRlbGNvX2RmLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB2YWwgPSBUUlVFLCANCiAgICAgICAgICAgY29uZi5pbnQgPSBUUlVFLCANCiAgICAgICAgICAgcmlzay50YWJsZSA9IFRSVUUpDQpzcGxvdHNbWzNdXSA8LSBkZXZpY2VfcHJvdGVjdGlvbl9wbHQNCg0KZml0IDwtIHN1cnZmaXQoc3Vydl9vYmplY3QgfiBUZWNoU3VwcG9ydCwgZGF0YSA9IHRlbGNvX2RmKQ0KdGVjaF9zdXBwb3J0X3BsdCA8LSBnZ3N1cnZwbG90KGZpdCwgZGF0YSA9IHRlbGNvX2RmLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdmFsID0gVFJVRSwgDQogICAgICAgICAgIGNvbmYuaW50ID0gVFJVRSwgDQogICAgICAgICAgIHJpc2sudGFibGUgPSBUUlVFKQ0Kc3Bsb3RzW1s0XV0gPC0gdGVjaF9zdXBwb3J0X3BsdA0KDQphcnJhbmdlX2dnc3VydnBsb3RzKHNwbG90cywgcHJpbnQgPSBUUlVFLCBuY29sID0gMiwgbnJvdyA9IDEpDQpgYGANCg0KICoqT0JTRVJWQVRJT046KiogIA0KVGhlIGFib3ZlIGZvdXIgcGxvdHMgbG9vayB2ZXJ5IHNpbWlsYXIuIE5lZWQgdG8gY2hlY2sgaWYgdGhlIGZvdXIgc2VydmljZXMgYXJlIGhpZ2hseSBjb3JyZWxhdGVkIChlLmcsIHRoZSBtYWpvcml0eSBvZiBjdXN0b21lcnMgaGF2ZSBubyBgT25saW5lU2VjdXJpdHlgIGFsc28gZG9lcyBub3QgaGF2ZSBgT25saW5lQmFja3VwYCwgYERldmljZVByb3RlY3Rpb25gLCBhbmQgYFRlY2hTdXBwb3J0YCksIHdoaWNoIHNvdW5kcyB2ZXJ5IGxpa2VseS4gIA0KDQpgYGB7cn0NCm9wdGlvbnMocmVwci5wbG90LndpZHRoID0gMTQsIHJlcHIucGxvdC5oZWlnaHQgPSAxMCkNCg0Kc3Bsb3RzIDwtIGxpc3QoKQ0KDQoNCmZpdCA8LSBzdXJ2Zml0KHN1cnZfb2JqZWN0IH4gU3RyZWFtaW5nVFYsIGRhdGEgPSB0ZWxjb19kZikNCnN0cmVhbWluZ1RWIDwtIGdnc3VydnBsb3QoZml0LCBkYXRhID0gdGVsY29fZGYsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBwdmFsID0gVFJVRSwgDQogICAgICAgICAgIGNvbmYuaW50ID0gVFJVRSwgDQogICAgICAgICAgIHJpc2sudGFibGUgPSBUUlVFKQ0Kc3Bsb3RzW1sxXV0gPC0gc3RyZWFtaW5nVFYNCg0KZml0IDwtIHN1cnZmaXQoc3Vydl9vYmplY3QgfiBTdHJlYW1pbmdNb3ZpZXMsIGRhdGEgPSB0ZWxjb19kZikNCnN0cmVhbWluZ01vdmllIDwtIGdnc3VydnBsb3QoZml0LCBkYXRhID0gdGVsY29fZGYsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdmFsID0gVFJVRSwgDQogICAgICAgICAgIGNvbmYuaW50ID0gVFJVRSwgDQogICAgICAgICAgIHJpc2sudGFibGUgPSBUUlVFKQ0Kc3Bsb3RzW1syXV0gPC0gc3RyZWFtaW5nTW92aWUNCg0KYXJyYW5nZV9nZ3N1cnZwbG90cyhzcGxvdHMsIHByaW50ID0gVFJVRSwgbmNvbCA9IDIsIG5yb3cgPSAxKQ0KYGBgDQoNCiAqKk9CU0VSVkFUSU9OOioqICANClRoZSBhYm92ZSB0d28gcGxvdHMgbG9vayB2ZXJ5IHNpbWlsYXIuIE5lZWQgdG8gY2hlY2sgaWYgdGhlIHR3byBzZXJ2aWNlcyBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQgKGUuZywgdGhlIG1ham9yaXR5IG9mIGN1c3RvbWVycyB3aG8gaGF2ZSBgU3RyZWFtaW5iVFZgIGFsc28gaGF2ZSBgU3RyZWFtaW5nTW92aWVgKSwgd2hpY2ggc291bmRzIHZlcnkgbGlrZWx5LiAgDQoNCmBgYHtyfQ0KZml0MSA8LSBzdXJ2Zml0KHN1cnZfb2JqZWN0IH4gQ29udHJhY3QsIGRhdGEgPSB0ZWxjb19kZikNCmdnc3VydnBsb3QoZml0MSwgZGF0YSA9IHRlbGNvX2RmLCANCiAgICAgICAgICAgcHZhbCA9IFRSVUUsIA0KICAgICAgICAgICBjb25mLmludCA9IFRSVUUsIA0KICAgICAgICAgICByaXNrLnRhYmxlID0gVFJVRSkNCmBgYA0KDQoqKk9CU0VSVkFUSU9OKio6DQpUaGUgZXZpZGVuY2UgKippcyoqIHN1ZmZpY2llbnQgdG8gcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMuIE51bGwgaHlwb3RoZXNpczogYENvbnRyYWN0YCBpcyBfbm90XyBhIGZhY3RvciB0aGF0IGRpc3Rpbmd1aXNoZXMgdGhlIHByb2JhYmlsaXR5IHRvIGNodXJuLiBUaGlzIHJlc3VsdCBpcyBoYXJkbHkgc3VycHJpc2luZyBnaXZlbiBvbmUgeWVhciB+PSA1MiB3ZWVrcyBhbmQgdHdvIHllYXJzIH49IDEwNCB3ZWVrcywgc28gZG8gbm90IHNlZSBhbnkgc2lnbmlmaWNhbnQgZHJvcCBiZWZvcmUgdGhhdCBtYW55IHdlZWtzIGZvciB0aGUgYE9uZSB5ZWFyYCBhbmQgYFR3byB5ZWFyYCBjb250cmFjdHMuIA0KDQpgYGB7cn0NCmZpdDEgPC0gc3VydmZpdChzdXJ2X29iamVjdCB+IFBhcGVybGVzc0JpbGxpbmcsIGRhdGEgPSB0ZWxjb19kZikNCmdnc3VydnBsb3QoZml0MSwgZGF0YSA9IHRlbGNvX2RmLCANCiAgICAgICAgICAgcHZhbCA9IFRSVUUsIA0KICAgICAgICAgICBjb25mLmludCA9IFRSVUUsIA0KICAgICAgICAgICByaXNrLnRhYmxlID0gVFJVRSkNCmBgYA0KDQogKipPQlNFUlZBVElPTioqOg0KVGhlIGV2aWRlbmNlICoqaXMqKiBzdWZmaWNpZW50IHRvIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzLiBOdWxsIGh5cG90aGVzaXM6IGBQYXBlcmxlc3NCaWxsaW5nYCBpcyBfbm90XyBhIGZhY3RvciB0aGF0IGRpc3Rpbmd1aXNoZXMgdGhlIHByb2JhYmlsaXR5IHRvIGNodXJuLiBUaGlzIHdhcyBpbnRlcmVzdGluZyBhcyBQYXBlcmxlc3NCaWxsaW5nIGhhdmluZyBjb3JyZWxhdGlvbiB0byB0aW1lLXRvLWNodXJuIHdhcyBub3QgdmVyeSBpbnR1aXRpdmU7IGN1c3RvbWVycyB3aG8gcm9sbGVkIGluIGZvciBQYXBlcmxlc3NCaWxsaW5nIHdhcyBtb3JlIGxpa2VseSB0byBjaHVybiBxdWlja2VyIGNvbXBhcmVkIHRvIHRob3NlIHdpdGhvdXQgUGFwZXJsZXNzQmlsbGluZy4gDQoNCmBgYHtyfQ0KZml0MSA8LSBzdXJ2Zml0KHN1cnZfb2JqZWN0IH4gUGF5bWVudE1ldGhvZCwgZGF0YSA9IHRlbGNvX2RmKQ0KZ2dzdXJ2cGxvdChmaXQxLCBkYXRhID0gdGVsY29fZGYsIA0KICAgICAgICAgICBwdmFsID0gVFJVRSwgDQogICAgICAgICAgIGNvbmYuaW50ID0gVFJVRSwgDQogICAgICAgICAgIHJpc2sudGFibGUgPSBUUlVFKQ0KYGBgDQoNCg0KICoqT0JTRVJWQVRJT04qKjoNClRoZSBldmlkZW5jZSAqKmlzKiogc3VmZmljaWVudCB0byByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcy4gTnVsbCBoeXBvdGhlc2lzOiBgUGF5bWVudE1ldGhvZGAgaXMgX25vdF8gYSBmYWN0b3IgdGhhdCBkaXN0aW5ndWlzaGVzIHRoZSBwcm9iYWJpbGl0eSB0byBjaHVybi4gVGhvc2Ugd2hvIHNpZ25lZCB1cCBmb3IgYXV0b21hdGljIHBheW1lbnQgc2VydmljZXMgKGJhbmsgdHJhbnNmZXIgYW5kIGNyZWRpdCBjYXJkKSB3ZXJlIG1vcmUgbGlrZWx5IHRvIHN0YXkgbG9uZ2VyIHdpdGggdGhlIHNlcnZpY2UuVGVsY28gY291bGQgYXNzaXN0IHRoZSBjdXN0b21lcnMgd2l0aCBzZXR0aW5nIHVwIHRoZSBhdXRvbWF0ZWQgcGF5bWVudHMgb3IgaGF2ZSBhIHByb21vdGlvbiB0aGF0IGNvdWxkIGdpdmUgZW5vdWdoIG1vdGl2YXRpb24gZm9yIHRoZSBjdXN0b21lcnMgdG8gc2lnbiB1cCBmb3IgdGhlIGF1dG9tYXRlZCBwYXltZW50cy4gDQoNCg0KIyMgQW5hbHlzaXMgMS4yLiBDb3hQSA0KDQoNCmBgYHtyfQ0KaGVhZCh0ZWxjb19kZikNCmBgYA0KDQpTaW5jZSB3ZSBoYXZlIGZlYXR1cmVzIHRoYXQgYXJlIGhpZXJhcmNoaWNhbCwgd2UgY2FuIHN0cmF0aWZ5IGN1c3RvbWVycyBiYXNlZCBvbiBjb2x1bW5zOiAgDQpXaGVyZSwgYSBzdWJzZXQgb2YgZmVhdHVyZXMgYXJlIG9ubHkgYXBwbGljYWJsZSBmb3IgYSBzZXJ0YWluIHN1YnNldCBvZiBncm91cHMgKGUuZy4sIE9ubGluZVNlY3VyaXR5IGlzIG5vdCBhbiBhcHBsaWNhYmxlIGNvbHVtbiBmb3IgdGhvc2Ugd2hvIGRvZXMgTk9UIGhhdmUgSW50ZXJuZXRTZXJ2aWNlKQ0KIDEuIHcvIG9ubHkgUGhvbmVTZXJ2aWNlDQogMi4gdy8gb25seSBJbnRlcm5ldFNlcnZpY2UgDQogMy4gdy8gYm90aCBJbnRlcm5ldFNlcnZpY2UgYW5kIFBob25lU2VydmljZSANCg0KYGBge3J9DQojIHRoZXJlIGlzIG5vIHRydWUgb3IgZmFsc2UgY29sdW1uIGZvciBpbnRlcm5ldCBzZXJ2aWNlIA0KdGVsY29fZGYkaGFzX0ludGVybmV0U2VydmljZSA8LSBpZmVsc2UodGVsY29fZGYkSW50ZXJuZXRTZXJ2aWNlICE9ICJObyIsICJZZXMiLCAiTm8iKQ0KdGVsY29fZGYkaGFzX0ludGVybmV0U2VydmljZSA8LSBhcy5mYWN0b3IodGVsY29fZGYkaGFzX0ludGVybmV0U2VydmljZSkNCg0KdGVsY29fZGYgJT4lIGNvdW50KFBob25lU2VydmljZSwgaGFzX0ludGVybmV0U2VydmljZSkNCiMgb2JzZXJ2YXRpb246IHdlIGhhdmUgZGVzY2VudCBudW1iZXJzIG9mIGRhdGEgcG9pbnRzIHBlciBQaG9uZVNlcnZpY2UgYW5kIGhhc19JbnRlcm5ldFNlcnZpY2UgY29tYmluYXRpb25zDQoNCg0KIyBzdWJzZXQgZGF0YSBiYXNlZCBvbiBpbnRlcm5ldCBhbmQgcGhvbmUgc2VydmljZXMuIA0Kb25seV9waG9uZV9zZXJ2aWNlX2RmIDwtIHRlbGNvX2RmICU+JSANCiAgICBmaWx0ZXIoUGhvbmVTZXJ2aWNlID09ICJZZXMiICYgaGFzX0ludGVybmV0U2VydmljZSA9PSAiTm8iKSAlPiUgDQoNCiAgICBkcGx5cjo6c2VsZWN0KC1jKENodXJuLCBJbnRlcm5ldFNlcnZpY2UsIE9ubGluZVNlY3VyaXR5LCBPbmxpbmVCYWNrdXAsIERldmljZVByb3RlY3Rpb24sIFRlY2hTdXBwb3J0LCBTdHJlYW1pbmdUViwgU3RyZWFtaW5nTW92aWVzLCBUb3RhbENoYXJnZXMsIEludGVybmV0UGhvbmVTZXJ2aWNlcywgdG90YWxfY2hhcmdlc190aGVvcmV0aWNhbCwgdG90YWxfY2hhcmdlc19kaWZmKSkNCg0Kb25seV9pbnRlcm5ldF9zZXJ2aWNlX2RmIDwtIHRlbGNvX2RmICU+JSANCiAgICBmaWx0ZXIoUGhvbmVTZXJ2aWNlID09ICJObyIgJiBoYXNfSW50ZXJuZXRTZXJ2aWNlID09ICJZZXMiKSAlPiUNCiAgICBkcGx5cjo6c2VsZWN0KC1jKENodXJuLCBNdWx0aXBsZUxpbmVzLCBQaG9uZVNlcnZpY2UsIFRvdGFsQ2hhcmdlcywgSW50ZXJuZXRQaG9uZVNlcnZpY2VzLCB0b3RhbF9jaGFyZ2VzX3RoZW9yZXRpY2FsLCB0b3RhbF9jaGFyZ2VzX2RpZmYpKQ0KDQpib3RoX3NlcnZpY2VzX2RmIDwtIHRlbGNvX2RmICU+JSANCiAgICBmaWx0ZXIoUGhvbmVTZXJ2aWNlID09ICJZZXMiICYgaGFzX0ludGVybmV0U2VydmljZSA9PSAiWWVzIikgJT4lDQogICAgZHBseXI6OnNlbGVjdCgtYyhDaHVybiwgVG90YWxDaGFyZ2VzLCBJbnRlcm5ldFBob25lU2VydmljZXMsIHRvdGFsX2NoYXJnZXNfdGhlb3JldGljYWwsIHRvdGFsX2NoYXJnZXNfZGlmZikpDQpgYGANCg0KYGBge3J9DQojIG9ubHkgcGhvbmUgc2VydmljZSANCm9ubHlfcGhvbmVfZml0IDwtIGNveHBoKFN1cnYodGVudXJlLCBpc19jaHVybikgfiANCiAgICAgICAgICAgICAgICAgICAgICAgIGdlbmRlciArIGZhY3RvcihTZW5pb3JDaXRpemVuKSArIA0KICAgICAgICAgICAgICAgICAgICAgICAgTXVsdGlwbGVMaW5lcyArIFBhcnRuZXIgKyBEZXBlbmRlbnRzICsgQ29udHJhY3QgKyBQYXBlcmxlc3NCaWxsaW5nICsgUGF5bWVudE1ldGhvZCArIE1vbnRobHlDaGFyZ2VzLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBvbmx5X3Bob25lX3NlcnZpY2VfZGYpDQpvbmx5X3Bob25lX2Z0ZXN0IDwtIGNveC56cGgob25seV9waG9uZV9maXQpDQpvbmx5X3Bob25lX2Z0ZXN0DQpgYGANCg0KYGBge3J9DQpoZWFkKG9ubHlfaW50ZXJuZXRfc2VydmljZV9kZikNCmBgYA0KDQoNCiMgUkVGRVJFTkNFDQoNCi0gc3Vydml2YWwgYW5hbHlzaXMgY2hlYXRzaGVldDogaHR0cHM6Ly9ycGtncy5kYXRhbm92aWEuY29tL3N1cnZtaW5lci9zdXJ2bWluZXJfY2hlYXRzaGVldC5wZGYNCi0gY29udGluZ2VuY3kgdGFibGUgYW5kIGluZGVwZW5kZW5jZSB0ZXN0czogDQogICAgIDEuIGh0dHBzOi8vd3d3LmRhdGFjYW1wLmNvbS9jb21tdW5pdHkvdHV0b3JpYWxzL2NvbnRpbmdlbmN5LXRhYmxlcy1yDQogICAgIDIuIGNvbnRpbmdlbmN5IHRhYmxlIGFuZCBjaGktc3F1YXJlZCB0ZXN0IGZvciBpbmRlcGVuZGVuY2U6IGh0dHA6Ly93d3cuci10dXRvci5jb20vZWxlbWVudGFyeS1zdGF0aXN0aWNzL2dvb2RuZXNzLWZpdC9jaGktc3F1YXJlZC10ZXN0LWluZGVwZW5kZW5jZQ0KICAgICAzLiBjb250aW5nZW5jeSB0YWJsZTogaHR0cDovL3d3dy5zdGhkYS5jb20vZW5nbGlzaC9hcnRpY2xlcy8zMS1wcmluY2lwYWwtY29tcG9uZW50LW1ldGhvZHMtaW4tci1wcmFjdGljYWwtZ3VpZGUvMTEzLWNhLWNvcnJlc3BvbmRlbmNlLWFuYWx5c2lzLWluLXItZXNzZW50aWFscy8jOn46dGV4dD1Db3JyZXNwb25kZW5jZSUyMGFuYWx5c2lzJTIwaXMlMjBhJTIwZ2VvbWV0cmljLHRoZWlyJTIwYXNzb2NpYXRpb25zJTIwaW4lMjB0aGUlMjB0YWJsZS4NCiAgICAgNC4gIHN0YXRpc3RpY2FsIHRlc3Rpbmcgdy8gY29udGluZ2VuY3kgdGFibGU6IGh0dHA6Ly93d3cuc3RhdC55YWxlLmVkdS9Db3Vyc2VzLzE5OTctOTgvMTAxL2NoaXNxLmh0bSM6fjp0ZXh0PVdoZW4lMjBhbmFseXNpcyUyMG9mJTIwY2F0ZWdvcmljYWwlMjBkYXRhLGJhc2lzJTIwb2YlMjB0aGUlMjBkYXRhJTIwb2JzZXJ2ZWQuDQotIG5vcm1hbGl0eSB0ZXN0OiBodHRwczovL3d3dy5zdGF0aXN0aWNzaG93dG8uY29tL2Fzc3VtcHRpb24tb2Ytbm9ybWFsaXR5LXRlc3QvICANCi0gY29ycmVzcG9uZGVuY2UgYW5hbHlzaXMgMTogaHR0cDovL3d3dy5zdGhkYS5jb20vZW5nbGlzaC9hcnRpY2xlcy8zMi1yLWdyYXBoaWNzLWVzc2VudGlhbHMvMTI5LXZpc3VhbGl6aW5nLW11bHRpdmFyaWF0ZS1jYXRlZ29yaWNhbC1kYXRhLyAgDQotIGNvcnJlc3BvbmRlbmNlIGFuYWx5c2lzIDI6IGh0dHA6Ly93d3cuc3RoZGEuY29tL2VuZ2xpc2gvYXJ0aWNsZXMvMzEtcHJpbmNpcGFsLWNvbXBvbmVudC1tZXRob2RzLWluLXItcHJhY3RpY2FsLWd1aWRlLzExMy1jYS1jb3JyZXNwb25kZW5jZS1hbmFseXNpcy1pbi1yLWVzc2VudGlhbHMvIzp+OnRleHQ9Q29ycmVzcG9uZGVuY2UlMjBhbmFseXNpcyUyMGlzJTIwYSUyMGdlb21ldHJpYyx0aGVpciUyMGFzc29jaWF0aW9ucyUyMGluJTIwdGhlJTIwdGFibGUuDQotIHN1cnZpdmFsIGFuYWx5c2lzOiBodHRwczovL3d3dy5kYXRhY2FtcC5jb20vY29tbXVuaXR5L3R1dG9yaWFscy9zdXJ2aXZhbC1hbmFseXNpcy1SDQoNCg0KDQo=