What is Customer Churn?

Customer churn is loss of customers/subscribers. Is it possible to use AI/ML/Data Science techniques to construct a model that can help predict customer churn?

We can formulate this problem in at least two different ways:

In this report, we will show how both these approaches can be applied and present results.

Read data

# Read data
data_file <- here("data", "customer_churn_data_extended.csv")
churn_data <- read.csv(data_file)
datatable(churn_data, rownames = FALSE, filter="top", options = list(pageLength = 6, scrollX= T))
Warning in instance$preRenderHook(instance) :
  It seems your data is too big for client-side DataTables. You may consider server-side processing: https://rstudio.github.io/DT/server.html
Warning in instance$preRenderHook(instance) :
  It seems your data is too big for client-side DataTables. You may consider server-side processing: https://rstudio.github.io/DT/server.html

Quick missing data check

missmap(churn_data, col=c("blue", "red"), legend=FALSE)

  • Looks like there are some missing values in total_charges. But overall data quality is good.

Data Description (Reference: Extended telco churn data)

Explore data and implement preprocessing steps

# Standardize column names
colnames(churn_data) <- tolower(colnames(churn_data))
colnames(churn_data) <- gsub("\\.", "_", colnames(churn_data))

# Confirm unique customers (no duplicates)
## if no duplicates the following length computation should be equal to the number of rows of the dataframe 
length(unique(churn_data$customerid))
[1] 7043
# Missing values
summary(churn_data)
  customerid            count     country             state               city              zip_code       lat_long            latitude    
 Length:7043        Min.   :1   Length:7043        Length:7043        Length:7043        Min.   :90001   Length:7043        Min.   :32.56  
 Class :character   1st Qu.:1   Class :character   Class :character   Class :character   1st Qu.:92102   Class :character   1st Qu.:34.03  
 Mode  :character   Median :1   Mode  :character   Mode  :character   Mode  :character   Median :93552   Mode  :character   Median :36.39  
                    Mean   :1                                                            Mean   :93522                      Mean   :36.28  
                    3rd Qu.:1                                                            3rd Qu.:95351                      3rd Qu.:38.22  
                    Max.   :1                                                            Max.   :96161                      Max.   :41.96  
                                                                                                                                           
   longitude         gender          senior_citizen       partner           dependents        tenure_months   phone_service     
 Min.   :-124.3   Length:7043        Length:7043        Length:7043        Length:7043        Min.   : 0.00   Length:7043       
 1st Qu.:-121.8   Class :character   Class :character   Class :character   Class :character   1st Qu.: 9.00   Class :character  
 Median :-119.7   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :29.00   Mode  :character  
 Mean   :-119.8                                                                               Mean   :32.37                     
 3rd Qu.:-118.0                                                                               3rd Qu.:55.00                     
 Max.   :-114.2                                                                               Max.   :72.00                     
                                                                                                                                
 multiple_lines     internet_service   online_security    online_backup      device_protection  tech_support       streaming_tv      
 Length:7043        Length:7043        Length:7043        Length:7043        Length:7043        Length:7043        Length:7043       
 Class :character   Class :character   Class :character   Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                                                                                     
                                                                                                                                     
                                                                                                                                     
                                                                                                                                     
 streaming_movies     contract         paperless_billing  payment_method     monthly_charges  total_charges    churn_label         churn_value    
 Length:7043        Length:7043        Length:7043        Length:7043        Min.   : 18.25   Min.   :  18.8   Length:7043        Min.   :0.0000  
 Class :character   Class :character   Class :character   Class :character   1st Qu.: 35.50   1st Qu.: 401.4   Class :character   1st Qu.:0.0000  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median : 70.35   Median :1397.5   Mode  :character   Median :0.0000  
                                                                             Mean   : 64.76   Mean   :2283.3                      Mean   :0.2654  
                                                                             3rd Qu.: 89.85   3rd Qu.:3794.7                      3rd Qu.:1.0000  
                                                                             Max.   :118.75   Max.   :8684.8                      Max.   :1.0000  
                                                                                              NA's   :11                                          
  churn_score         cltv      churn_reason      
 Min.   :  5.0   Min.   :2003   Length:7043       
 1st Qu.: 40.0   1st Qu.:3469   Class :character  
 Median : 61.0   Median :4527   Mode  :character  
 Mean   : 58.7   Mean   :4400                     
 3rd Qu.: 75.0   3rd Qu.:5380                     
 Max.   :100.0   Max.   :6500                     
                                                  

Churned vs. Not churned

churn_data %>% ggplot(aes(x = churn_label, fill = churn_label)) + geom_bar() + theme_minimal()

How good is the company in retaining its customers?

mean(churn_data$churn_value)
[1] 0.2653699

One can answer this question easily by estimating survival curve using the given data. The Kaplan-Meir estimate for the given churn data is shown below.

churn_data <- churn_data %>%
  mutate(
    b_churn = ifelse(churn_label == "Yes", 1, 0)
  )
kmfit <- survfit(Surv(tenure_months, b_churn) ~ 1, data = churn_data)
km_survplot <- survminer::ggsurvplot(
  fit = kmfit, 
  surv.median.line = "none",
  xlab = "Months", 
  ylab = "Survival probability", ylim = c(0.4, 1))
km_survplot$plot + geom_hline(yintercept = 0.61, linetype = "dashed") + geom_vline(xintercept = 70, linetype = "dashed")

  • The probability that an average customer will continue with the company beyond 70 months is \(0.61\).
  • Even after 70 months, the company is able to retain 61% or more of their customers.

Closer look at all variables

State hypotheses before building a data-driven model

  • gender: Do not expect to be a strong predictor of churn risk. We will first build a model without this variable, and revisit only if needed.
  • senior_citizen: Do not expect to be a strong predictor of churn risk. We will first build a model without this variable, and revisit only if needed.
  • partner/dependents: Do not expect to be a strong predictor of churn risk. We will first build a model without this variable, and revisit only if needed.
  • tenure_months: In an average sense, a long time customer is more likely to stay with the company (low churn risk)
  • phone_service/internet_service: Customers with both phone and internet service should be less likely to churn.
  • multiple_lines: Having multiple lines should reduce churn risk. Multiple lines better discount more likely to be cost efficient for the customer.
  • online_security, online_backup, device_protection, streaming_tv, streaming_movies: Data description tells us that customers do not pay for extra services. In general, customers with more services and diverse usage should be less likely to churn.
  • contract: Expect to be a strong predictor. Customers with long term contracts less likely to churn than those with month-to-month contracts.
  • payment_method: customers with automatic payment setup (automatic bank transfer or credit card) less likely to churn than manual payment methods.
  • monthly_charges: churn risk should increase with higher monthly charges. However, need to be careful as this is not normalized with respect to number of lines, services, etc.
  • total_charges: total charges is cumulative and depends on the duration of service. Not a good idea to use this in its raw form

Visualize key variables

Tenure

churn_data %>% ggplot(aes(x = tenure_months, fill = churn_label)) + geom_histogram() + facet_wrap(churn_label ~ .)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Multiple lines

ggplot(churn_data, aes(x = multiple_lines, y = tenure_months, fill = churn_label)) + geom_boxplot() + facet_wrap(~ multiple_lines, scale = "free")

Contract

ggplot(churn_data, aes(x = contract, y = tenure_months, fill = churn_label)) + geom_boxplot() + facet_wrap(~ contract, scale = "free")

Payment method

ggplot(churn_data, aes(x = payment_method, y = tenure_months, fill = churn_label)) + geom_boxplot() + facet_wrap(~ payment_method, scale = "free")

Logistic regression

We consider the following model:

\[\begin{eqnarray} y_i & \sim & Bernoulli(\theta_i) \\ \text{logit}\left(\theta_i\right) & = & \beta_0 + \beta_1 x_1 + \cdots + \beta_n x_n, \\ \beta_0, \beta_1, \ldots, \beta_n & \sim & \mathcal{N}(0, 5) \hspace{10pt} (priors) \end{eqnarray}\]

where \(y_i\) is binary data (churned or not churned), \(\theta_i\) is the probability of churn and \(x_i's\) are covariates of customer \(i\). \(\beta_0, \beta_1, \ldots, \beta_n\) are model parameters which will be estimated using the collected data.

Model summary

if(!bBuild_mdl_bayes_FE){
  mdl_logit_FixedEffects <- readRDS("mdl_logit_bayes_1.rds")
} else {
  mdl_logit_FixedEffects <- brm(formula = churn_value ~ contract  + multiple_lines + payment_method + tenure_months,
                                data=churn_data,
                                family = bernoulli(link = "logit"),
                                prior = c(prior(normal(0, 2), class = Intercept),
                                          prior(normal(0, 2), class = b)),
                                sample_prior = TRUE,
                                warmup = 500,
                                iter = 3000,
                                chains = 4,
                                inits= "0",
                                cores = 8,
                                seed = 123)
  saveRDS(mdl_logit_FixedEffects, "mdl_logit_FixedEffects.rds")
}
summary(mdl_logit_FixedEffects)
 Family: bernoulli 
  Links: mu = logit 
Formula: churn_value ~ contract + multiple_lines + payment_method + tenure_months 
   Data: churn_data (Number of observations: 7043) 
  Draws: 4 chains, each with iter = 3000; warmup = 500; thin = 1;
         total post-warmup draws = 10000

Population-Level Effects: 
                                  Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
Intercept                            -0.32      0.09    -0.50    -0.14 1.00     6841     7598
contractOneyear                      -1.07      0.10    -1.26    -0.88 1.00    10375     7535
contractTwoyear                      -2.12      0.16    -2.45    -1.80 1.00     8931     7159
multiple_linesNophoneservice          0.20      0.11    -0.02     0.41 1.00     9998     7716
multiple_linesYes                     0.71      0.07     0.57     0.85 1.00     9656     7536
payment_methodCreditcardautomatic    -0.09      0.11    -0.30     0.13 1.00     7496     7672
payment_methodElectroniccheck         0.64      0.09     0.47     0.82 1.00     7001     7450
payment_methodMailedcheck            -0.42      0.11    -0.63    -0.22 1.00     7023     7838
tenure_months                        -0.03      0.00    -0.03    -0.02 1.00    11053     9002

Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
and Tail_ESS are effective sample size measures, and Rhat is the potential
scale reduction factor on split chains (at convergence, Rhat = 1).

Posterior distribution of parameters

  • Probability of parameters \(\theta\) given evidence (data)
  • We show how data modifies our a priori assumption of \(\theta\). We plot only one parameter for simplicity.
rbind(draws_prior, df_post) %>% 
  ggplot(aes(x = x, fill = label)) + geom_density() +
  scale_fill_viridis(discrete = TRUE, alpha = 0.5) + theme_minimal() + 
  labs(x = varname)

Posterior predictive distribution

  • Distribution of possible unobserved values conditional on observed data: \(p(y_{new}|y)\).
tenure1 = 15.8
tenure2 = 10.5

Xnew <- churn_data[1, ]
Xnew$tenure_months <- tenure1
p1 = posterior_linpred(mdl_logit_FixedEffects, transform=TRUE, newdata = Xnew)
df_p1 <- data.frame(p = p1, tenure_months = tenure1)


Xnew <- churn_data[1, ]
Xnew$tenure_months <- tenure2
#table(Xnew$tenure_months == churn_data$tenure_months)
p2 = posterior_linpred(mdl_logit_FixedEffects, transform=TRUE, newdata = Xnew)
df_p2 <- data.frame(p = p2, tenure_months = tenure2)
df_p <- rbind(df_p1, df_p2)
df_p$tenure_months <- as.factor(df_p$tenure_months)

ggplot(df_p, aes(x = p, fill = tenure_months)) + geom_density(alpha = 0.5) +
  labs(title = paste0("Contract: ", Xnew$contract, ", Multiple lines: ", Xnew$multiple_lines, ", Payment method: ",
                      Xnew$payment_method))

Model Evaluation

  1. How wrong is the model?
  2. How accurate are the model’s posterior classifications?
  3. Model comparisons

To answer question (1), we will first check convergence of posterior distribution of parameters using traceplots. Next, we perform posterior predictive checks to confirm that the data simulated from our posterior logistic regression model has features similar to the original data. That is, simulations from the fit model result in similar number of ones and zeros as in the original data. For (2), we will address this question in two ways: with and without cross-validation.

Check model convergence by visual inspection

Figure below shows that there are no divergence chains and hence we can conclude that our model does not have any convergence problems.

plot(mdl_logit_FixedEffects, ask = FALSE, newpage = FALSE, N = 2)

Posterior predictive checks

To check he fit of the model to data we draw simulated values from the posterior predictive distribution of replicated data, \(y^{rep}\) and compare the samples to original data. Any systematic differences between the simulations and the data will indicate potential failings of the model (e.g., unsuitable priors). The plot below compares original data with \(500\) draws from the fit model. The figure clearly shows that the model passes this check

pp_check(mdl_logit_FixedEffects, ndraws = 500)

Classification accuracy

Training accuracy

  • Threshold = 0.5
set.seed(84735)
threshold = 0.5
# Predicted probabilities
#linpred <- posterior_linpred(mdl_logit_FixedEffects)
preds <- posterior_linpred(mdl_logit_FixedEffects, transform=TRUE)
pred <- colMeans(preds)
pr <- as.integer(pred >= 0.5)

# posterior classification accuracy
round(mean(xor(pr,as.integer(churn_data$churn_value==0))),6)
[1] 0.781769

Confusion matrix

table(data.frame(churn_value = churn_data$churn_value, churn_pred = pr))
           churn_pred
churn_value    0    1
          0 4648  526
          1 1011  858

Receiver Operating Characteristic (ROC) curve

threshold <- seq(0, 1, by = 0.05)
compute_churn_decision <- function(threshold, pred, churn_value){
  pr <- as.integer(pred >= threshold)
  y <- data.frame(churn_value = churn_value, churn_pred = pr)
  pd <- y %>% dplyr::filter(churn_value == 1) %>%
    summarise(pd = mean(churn_pred)) %>% pull()
  pf <- y %>% dplyr::filter(churn_value == 0) %>%
    summarise(
      pf = mean(churn_pred)
    ) %>% pull()
  return(list(data.frame("pf" = pf, "pd" = pd, "th" = threshold)))
}

roc = lapply(threshold, compute_churn_decision, pred = pred, churn_value = churn_data$churn_value) %>%
  bind_rows()
roc <- roc %>%
  mutate(
    type = "training set"
  )

ggplot(roc, aes(x = pf, y = pd)) + geom_line(size = 2) + 
  geom_point(size = 5) + 
  labs(title = "ROC", x = "Probability of false alarm", y = "Probability of detection")

NA
NA

Cross validation

To better estimate the predictive performance for new not yet seen data we next use leave-one-out cross-validation.

if(bRunCV_mdl_bayes_FE){
  loo_mdl_logit_FixedEffects <- loo(mdl_logit_FixedEffects, save_psis = TRUE)
  saveRDS(loo_mdl_logit_FixedEffects, "loo_mdl_logit_FixedEffects.rds")
  # LOO predictive probabilities
  ploo_mdl_logit_FixedEffects=E_loo(preds, loo_mdl_logit_FixedEffects$psis_object, type="mean", log_ratios = -log_lik(mdl_logit_FixedEffects))$value
  saveRDS(ploo, "ploo_mdl_logit_FixedEffects.rds")
} else{
  loo_mdl_logit_FixedEffects <- readRDS("loo_mdl_logit_FixedEffects.rds")
  ploo_mdl_logit_FixedEffects <- readRDS("ploo_mdl_logit_FixedEffects.rds")
}

## LOO classification accuracy
round(mean(xor(ploo>0.5,as.integer(churn_data$churn_value==0))), 6)
[1] 0.781627

Challenger model: To do

  • Churn rate vs. Geographical location
  • Multilevel model to incorporate location variations (especially useful to reduce prediction uncertainty in small sample size counties)
table(churn_data$county)

        california,alameda          california,alpine          california,amador           california,butte       california,calaveras 
                       180                          8                         36                         72                         72 
         california,colusa    california,contra costa       california,del norte       california,el dorado          california,fresno 
                        24                        140                         16                         84                        200 
          california,glenn        california,humboldt        california,imperial            california,inyo            california,kern 
                        28                        136                         70                         40                        168 
          california,kings            california,lake          california,lassen     california,los angeles          california,madera 
                        32                         52                         60                       1352                         48 
          california,marin        california,mariposa       california,mendocino          california,merced           california,modoc 
                       108                         32                        100                         72                         36 
           california,mono        california,monterey            california,napa          california,nevada          california,orange 
                        28                        112                         36                         28                        341 
         california,placer          california,plumas       california,riverside      california,sacramento      california,san benito 
                       124                         48                        308                        196                         16 
 california,san bernardino       california,san diego   california,san francisco     california,san joaquin california,san luis obispo 
                       347                        475                        104                        104                         76 
      california,san mateo   california,santa barbara     california,santa clara      california,santa cruz          california,shasta 
                       116                         96                        220                         64                        108 
         california,sierra        california,siskiyou          california,solano          california,sonoma      california,stanislaus 
                        32                         84                         52                        140                         96 
         california,sutter          california,tehama         california,trinity          california,tulare        california,tuolumne 
                        36                         56                         52                        132                         48 
        california,ventura            california,yolo            california,yuba 
                       102                         56                         44 

churn_data %>% group_by(county) %>%
  summarise(p = mean(churn_value)) %>% arrange(desc(p)) %>%
  mutate(county = fct_reorder(county, p)) %>%
  ggplot( aes(x=county, y=p)) +
    geom_bar(stat="identity") +
    coord_flip() +
    xlab("") + ylab("churn rate") + 
    theme_bw()

Comparison with classical logistic regression (Frequentist)

mdl_logit_FixedEffects_classical <- glm(churn_value ~ contract  + multiple_lines + payment_method + tenure_months,
                                        data = churn_data, family = "binomial")
summary(mdl_logit_FixedEffects_classical)

Call:
glm(formula = churn_value ~ contract + multiple_lines + payment_method + 
    tenure_months, family = "binomial", data = churn_data)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.6242  -0.7999  -0.3223   0.8172   3.0003  

Coefficients:
                                       Estimate Std. Error z value Pr(>|z|)    
(Intercept)                           -0.314931   0.091434  -3.444 0.000572 ***
contractOne year                      -1.070434   0.099166 -10.794  < 2e-16 ***
contractTwo year                      -2.127030   0.164503 -12.930  < 2e-16 ***
multiple_linesNo phone service         0.202842   0.110110   1.842 0.065451 .  
multiple_linesYes                      0.712004   0.072611   9.806  < 2e-16 ***
payment_methodCredit card (automatic) -0.087631   0.109170  -0.803 0.422149    
payment_methodElectronic check         0.638239   0.089455   7.135 9.70e-13 ***
payment_methodMailed check            -0.424191   0.105653  -4.015 5.95e-05 ***
tenure_months                         -0.027519   0.001997 -13.778  < 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: 8150.1  on 7042  degrees of freedom
Residual deviance: 6321.2  on 7034  degrees of freedom
AIC: 6339.2

Number of Fisher Scoring iterations: 6
  • Both classical and Bayesian solutions match pretty well

Survival analysis: To Do

LS0tDQp0aXRsZTogIkN1c3RvbWVyIGNodXJuIHByZWRpY3Rpb24iDQphdXRob3I6ICJTYXRpc2ggSXllbmdhciINCmRhdGU6ICI5LzIzLzIwMjEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCmBgYHtyIGxvYWRfcGFja2FnZXMsIGluY2x1ZGUgPSBGQUxTRSwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KbGlicmFyeShzdXJ2aXZhbCkNCmxpYnJhcnkoaGVyZSkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShzaGlueSkNCmxpYnJhcnkoRFQpDQpsaWJyYXJ5KHN1cnZtaW5lcikNCmxpYnJhcnkocGVjKQ0KbGlicmFyeShwYXJ0eSkNCmxpYnJhcnkocm1zKQ0KbGlicmFyeShzcCkNCmxpYnJhcnkobWFwcykNCmxpYnJhcnkobWFwdG9vbHMpDQpsaWJyYXJ5KGJybXMpDQpsaWJyYXJ5KEFtZWxpYSkNCmxpYnJhcnkoSVNMUikNCg0KbGlicmFyeShqYW5pdG9yKQ0KbGlicmFyeShwdXJycikNCmxpYnJhcnkodmlyaWRpcykNCmxpYnJhcnkodGlkeWJheWVzKQ0KbGlicmFyeShiYXllc3Bsb3QpDQpsaWJyYXJ5KGFybSkNCmxpYnJhcnkobW9kZWxyKQ0KbGlicmFyeShiYXllc3J1bGVzKQ0KDQpgYGANCg0KDQpgYGB7ciBib29sZWFuX3ZhcnMsIGluY2x1ZGU9RkFMU0V9DQpiQnVpbGRfbWRsX2JheWVzX0ZFIDwtIDANCmJSdW5DVl9tZGxfYmF5ZXNfRkUgPC0gMA0KYkJ1aWxkX21kbF9iYXllc19SRSA8LSAwDQpiUnVuQ1ZfbWRsX2JheWVzX1JFIDwtIDANCmJDb21wYXJlTW9kZWxzIDwtIDANCg0KYGBgDQoNCiMjIFdoYXQgaXMgQ3VzdG9tZXIgQ2h1cm4/DQoNCkN1c3RvbWVyIGNodXJuIGlzIGxvc3Mgb2YgY3VzdG9tZXJzL3N1YnNjcmliZXJzLiBJcyBpdCBwb3NzaWJsZSB0byB1c2UgQUkvTUwvRGF0YSBTY2llbmNlIHRlY2huaXF1ZXMgdG8gY29uc3RydWN0IGEgbW9kZWwgdGhhdCBjYW4gaGVscCBwcmVkaWN0IGN1c3RvbWVyIGNodXJuPyANCg0KV2UgY2FuIGZvcm11bGF0ZSB0aGlzIHByb2JsZW0gaW4gYXQgbGVhc3QgdHdvIGRpZmZlcmVudCB3YXlzOg0KDQotIEFwcGx5IGNsYXNzaWNhbCB0ZWNobmlxdWVzIHN1Y2ggYXMgbG9naXN0aWMgcmVncmVzc2lvbiB0byBwcmVkaWN0IGlmIGEgY3VzdG9tZXIgd2lsbCBjaHVybiAoWWVzL05vKSBnaXZlbiAqcmVsZXZhbnQgY292YXJpYXRlcyogc3VjaCBhcyBoaXMvaGVyIHRlbnVyZSB3aXRoIHRoZSBjb21wYW55IHNvIGZhci4NCg0KLSBCdWlsZCAqdGltZS10by1ldmVudCogbW9kZWxzIHVzaW5nIHN1cnZpdmFsIGFuYWx5c2lzIHdoaWNoIHdpbGwgYWxsb3cgdXMgdG8gYW5zd2VyIHF1ZXN0aW9ucyBzdWNoIGFzOiBXaGF0IGlzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBjdXN0b21lciB3aWxsIGNodXJuIGluIHRoZSBuZXh0ICR4JCBkYXlzLg0KDQpJbiB0aGlzIHJlcG9ydCwgd2Ugd2lsbCBzaG93IGhvdyBib3RoIHRoZXNlIGFwcHJvYWNoZXMgY2FuIGJlIGFwcGxpZWQgYW5kIHByZXNlbnQgcmVzdWx0cy4NCg0KDQojIyBSZWFkIGRhdGENCg0KLSBSYXcgZGF0YSB1c2VkIGluIHRoaXMgcmVwb3J0IGNhbiBiZSBkb3dubG9hZGVkIGZyb20gaGVyZTogW0V4dGVuZGVkIHRlbGNvIGNodXJuIGRhdGFdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20veWVhbnpjL3RlbGNvLWN1c3RvbWVyLWNodXJuLWlibS1kYXRhc2V0KQ0KDQoNCmBgYHtyIHJlYWRfZGF0YX0NCiMgUmVhZCBkYXRhDQpkYXRhX2ZpbGUgPC0gaGVyZSgiZGF0YSIsICJjdXN0b21lcl9jaHVybl9kYXRhX2V4dGVuZGVkLmNzdiIpDQpjaHVybl9kYXRhIDwtIHJlYWQuY3N2KGRhdGFfZmlsZSkNCmRhdGF0YWJsZShjaHVybl9kYXRhLCByb3duYW1lcyA9IEZBTFNFLCBmaWx0ZXI9InRvcCIsIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSA2LCBzY3JvbGxYPSBUKSkNCg0KYGBgDQoNCiMjIyBRdWljayBtaXNzaW5nIGRhdGEgY2hlY2sNCg0KYGBge3J9DQptaXNzbWFwKGNodXJuX2RhdGEsIGNvbD1jKCJibHVlIiwgInJlZCIpLCBsZWdlbmQ9RkFMU0UpDQoNCmBgYA0KDQotIExvb2tzIGxpa2UgdGhlcmUgYXJlIHNvbWUgbWlzc2luZyB2YWx1ZXMgaW4gKnRvdGFsX2NoYXJnZXMqLiBCdXQgb3ZlcmFsbCBkYXRhIHF1YWxpdHkgaXMgZ29vZC4gDQoNCiMjIERhdGEgRGVzY3JpcHRpb24gKFJlZmVyZW5jZTogW0V4dGVuZGVkIHRlbGNvIGNodXJuIGRhdGFdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20veWVhbnpjL3RlbGNvLWN1c3RvbWVyLWNodXJuLWlibS1kYXRhc2V0KSkNCg0KLSA3MDQzIG9ic2VydmF0aW9ucyB3aXRoIDMzIHZhcmlhYmxlcw0KLSBDdXN0b21lcklEOiBBIHVuaXF1ZSBJRCB0aGF0IGlkZW50aWZpZXMgZWFjaCBjdXN0b21lci4NCi0gQ291bnQ6IEEgdmFsdWUgdXNlZCBpbiByZXBvcnRpbmcvZGFzaGJvYXJkaW5nIHRvIHN1bSB1cCB0aGUgbnVtYmVyIG9mIGN1c3RvbWVycyBpbiBhIGZpbHRlcmVkIHNldC4NCi0gQ291bnRyeTogVGhlIGNvdW50cnkgb2YgdGhlIGN1c3RvbWVy4oCZcyBwcmltYXJ5IHJlc2lkZW5jZTogVW5pdGVkIFN0YXRlcy4NCi0gU3RhdGU6IFRoZSBzdGF0ZSBvZiB0aGUgY3VzdG9tZXLigJlzIHByaW1hcnkgcmVzaWRlbmNlOiBDYWxpZm9ybmlhLg0KLSBDaXR5OiBUaGUgY2l0eSBvZiB0aGUgY3VzdG9tZXLigJlzIHByaW1hcnkgcmVzaWRlbmNlOiAxMTI5IGNpdGllcyBpbiBDQS4NCi0gWmlwIENvZGU6IFRoZSB6aXAgY29kZSBvZiB0aGUgY3VzdG9tZXLigJlzIHByaW1hcnkgcmVzaWRlbmNlOiAxNjUyIHppcCBjb2Rlcy4NCi0gTGF0IExvbmc6IFRoZSBjb21iaW5lZCBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIG9mIHRoZSBjdXN0b21lcuKAmXMgcHJpbWFyeSByZXNpZGVuY2U6IDE2NTIgbGF0X2xvbnMuDQotIExhdGl0dWRlOiBUaGUgbGF0aXR1ZGUgb2YgdGhlIGN1c3RvbWVy4oCZcyBwcmltYXJ5IHJlc2lkZW5jZTogMTY1MiBsYXRzLg0KLSBMb25naXR1ZGU6IFRoZSBsb25naXR1ZGUgb2YgdGhlIGN1c3RvbWVy4oCZcyBwcmltYXJ5IHJlc2lkZW5jZTogMTY1MiBsb25zLg0KLSBHZW5kZXI6IFRoZSBjdXN0b21lcuKAmXMgZ2VuZGVyOiBNYWxlICgzNTU1KSwgRmVtYWxlICgzNDg4KS4NCi0gU2VuaW9yIENpdGl6ZW46IEluZGljYXRlcyBpZiB0aGUgY3VzdG9tZXIgaXMgNjUgb3Igb2xkZXI6IFllcyAoMTE0MiksIE5vICg1OTAxKS4NCi0gUGFydG5lcjogSW5kaWNhdGUgaWYgdGhlIGN1c3RvbWVyIGhhcyBhIHBhcnRuZXI6IFllcyAoMzQwMiksIE5vICgzNjQxKS4NCi0gRGVwZW5kZW50czogSW5kaWNhdGVzIGlmIHRoZSBjdXN0b21lciBsaXZlcyB3aXRoIGFueSBkZXBlbmRlbnRzOiBZZXMgKDE2MjcpLCBObyAoNTQxNikuIERlcGVuZGVudHMgY291bGQgYmUgY2hpbGRyZW4sIHBhcmVudHMsIGdyYW5kcGFyZW50cywgZXRjLg0KLSBUZW51cmUgTW9udGhzOiBJbmRpY2F0ZXMgdGhlIHRvdGFsIGFtb3VudCBvZiBtb250aHMgdGhhdCB0aGUgY3VzdG9tZXIgaGFzIGJlZW4gd2l0aCB0aGUgY29tcGFueSBieSB0aGUgZW5kIG9mIHRoZSBxdWFydGVyIHNwZWNpZmllZCBhYm92ZS4NCi0gUGhvbmUgU2VydmljZTogSW5kaWNhdGVzIGlmIHRoZSBjdXN0b21lciBzdWJzY3JpYmVzIHRvIGhvbWUgcGhvbmUgc2VydmljZSB3aXRoIHRoZSBjb21wYW55OiBZZXMgKDYzNjEpLCBObyAoNjgyKS4NCi0gTXVsdGlwbGUgTGluZXM6IEluZGljYXRlcyBpZiB0aGUgY3VzdG9tZXIgc3Vic2NyaWJlcyB0byBtdWx0aXBsZSB0ZWxlcGhvbmUgbGluZXMgd2l0aCB0aGUgY29tcGFueTogWWVzICgyOTcxKSwgTm8gKDMzOTApLCBObyBwaG9uZSBzZXJ2aWNlICg2ODIpLg0KLSBJbnRlcm5ldCBTZXJ2aWNlOiBJbmRpY2F0ZXMgaWYgdGhlIGN1c3RvbWVyIHN1YnNjcmliZXMgdG8gSW50ZXJuZXQgc2VydmljZSB3aXRoIHRoZSBjb21wYW55OiBObyAoMTUyNiksIERTTCAoMjQyMSksIEZpYmVyIE9wdGljICgzMDk2KS4NCi0gT25saW5lIFNlY3VyaXR5OiBJbmRpY2F0ZXMgaWYgdGhlIGN1c3RvbWVyIHN1YnNjcmliZXMgdG8gYW4gYWRkaXRpb25hbCBvbmxpbmUgc2VjdXJpdHkgc2VydmljZSBwcm92aWRlZCBieSB0aGUgY29tcGFueTogWWVzICgyMDE5KSwgTm8gKDM0OTgpLCBObyBpbnRlcm5ldCBzZXJ2aWNlICgxNTI2KS4NCi0gT25saW5lIEJhY2t1cDogSW5kaWNhdGVzIGlmIHRoZSBjdXN0b21lciBzdWJzY3JpYmVzIHRvIGFuIGFkZGl0aW9uYWwgb25saW5lIGJhY2t1cCBzZXJ2aWNlIHByb3ZpZGVkIGJ5IHRoZSBjb21wYW55OiBZZXMgKDI0MjkpLCBObyAoMzA4OCksIE5vIGludGVybmV0IHNlcnZpY2UgKDE1MjYpLg0KLSBEZXZpY2UgUHJvdGVjdGlvbjogSW5kaWNhdGVzIGlmIHRoZSBjdXN0b21lciBzdWJzY3JpYmVzIHRvIGFuIGFkZGl0aW9uYWwgZGV2aWNlIHByb3RlY3Rpb24gcGxhbiBmb3IgdGhlaXIgSW50ZXJuZXQgZXF1aXBtZW50IHByb3ZpZGVkIGJ5IHRoZSBjb21wYW55OiBZZXMgKDI0MjIpLCBObyAoMzA5NSksIE5vIGludGVybmV0IHNlcnZpY2UgKDE1MjYpLg0KLSBUZWNoIFN1cHBvcnQ6IEluZGljYXRlcyBpZiB0aGUgY3VzdG9tZXIgc3Vic2NyaWJlcyB0byBhbiBhZGRpdGlvbmFsIHRlY2huaWNhbCBzdXBwb3J0IHBsYW4gZnJvbSB0aGUgY29tcGFueSB3aXRoIHJlZHVjZWQgd2FpdCB0aW1lczogWWVzICgyMDQ0KSwgTm8gKDM0NzMpLCBObyBpbnRlcm5ldCBzZXJ2aWNlICgxNTI2KS4NCi0gU3RyZWFtaW5nIFRWOiBJbmRpY2F0ZXMgaWYgdGhlIGN1c3RvbWVyIHVzZXMgdGhlaXIgSW50ZXJuZXQgc2VydmljZSB0byBzdHJlYW0gdGVsZXZpc2lvbiBwcm9ncmFtaW5nIGZyb20gYSB0aGlyZCBwYXJ0eSBwcm92aWRlcjogWWVzICgyNzA3KSwgTm8gKDI4MTApLCBObyBpbnRlcm5ldCBzZXJ2aWNlICgxNTI2KS4gVGhlIGNvbXBhbnkgZG9lcyBub3QgY2hhcmdlIGFuIGFkZGl0aW9uYWwgZmVlIGZvciB0aGlzIHNlcnZpY2UuDQotIFN0cmVhbWluZyBNb3ZpZXM6IEluZGljYXRlcyBpZiB0aGUgY3VzdG9tZXIgdXNlcyB0aGVpciBJbnRlcm5ldCBzZXJ2aWNlIHRvIHN0cmVhbSBtb3ZpZXMgZnJvbSBhIHRoaXJkIHBhcnR5IHByb3ZpZGVyOiBZZXMgKDI3MzIpLCBObyAoMjc4NSksIE5vIGludGVybmV0IHNlcnZpY2UgKDE1MjYpLiBUaGUgY29tcGFueSBkb2VzIG5vdCBjaGFyZ2UgYW4gYWRkaXRpb25hbCBmZWUgZm9yIHRoaXMgc2VydmljZS4NCi0gQ29udHJhY3Q6IEluZGljYXRlcyB0aGUgY3VzdG9tZXLigJlzIGN1cnJlbnQgY29udHJhY3QgdHlwZTogTW9udGgtdG8tTW9udGggKDM4NzUpLCBPbmUgWWVhciAoMTQ3MyksIFR3byBZZWFyICgxNjk1KS4NCi0gUGFwZXJsZXNzIEJpbGxpbmc6IEluZGljYXRlcyBpZiB0aGUgY3VzdG9tZXIgaGFzIGNob3NlbiBwYXBlcmxlc3MgYmlsbGluZzogWWVzICg0MTcxKSwgTm8gKDI4NzIpLg0KLSBQYXltZW50IE1ldGhvZDogSW5kaWNhdGVzIGhvdyB0aGUgY3VzdG9tZXIgcGF5cyB0aGVpciBiaWxsOiBCYW5rIHRyYW5zZmVyICgxNTQ0KSwgQ3JlZGl0IENhcmQgKDE1MjIpLCBFbGVjdHJvbmljIGNoZWNrICgyMzY1KSwgTWFpbGVkIENoZWNrICgxNjEyKS4NCi0gTW9udGhseSBDaGFyZ2U6IEluZGljYXRlcyB0aGUgY3VzdG9tZXLigJlzIGN1cnJlbnQgdG90YWwgbW9udGhseSBjaGFyZ2UgZm9yIGFsbCB0aGVpciBzZXJ2aWNlcyBmcm9tIHRoZSBjb21wYW55Lg0KLSBUb3RhbCBDaGFyZ2VzOiBJbmRpY2F0ZXMgdGhlIGN1c3RvbWVy4oCZcyB0b3RhbCBjaGFyZ2VzLCBjYWxjdWxhdGVkIHRvIHRoZSBlbmQgb2YgdGhlIHF1YXJ0ZXIgc3BlY2lmaWVkIGFib3ZlLg0KLSBDaHVybiBMYWJlbDogWWVzID0gdGhlIGN1c3RvbWVyIGxlZnQgdGhlIGNvbXBhbnkgdGhpcyBxdWFydGVyICgxODY5KS4gTm8gPSB0aGUgY3VzdG9tZXIgcmVtYWluZWQgd2l0aCB0aGUgY29tcGFueSAoNTE3NCkuIERpcmVjdGx5IHJlbGF0ZWQgdG8gQ2h1cm4gVmFsdWUuDQotIENodXJuIFZhbHVlOiAxID0gdGhlIGN1c3RvbWVyIGxlZnQgdGhlIGNvbXBhbnkgdGhpcyBxdWFydGVyICgxODY5KS4gMCA9IHRoZSBjdXN0b21lciByZW1haW5lZCB3aXRoIHRoZSBjb21wYW55LiBEaXJlY3RseSByZWxhdGVkIHRvIENodXJuIExhYmVsICg1MTc0KS4NCi0gQ2h1cm4gU2NvcmU6IEEgdmFsdWUgZnJvbSAwLTEwMCB0aGF0IGlzIGNhbGN1bGF0ZWQgdXNpbmcgdGhlIHByZWRpY3RpdmUgdG9vbCBJQk0gU1BTUyBNb2RlbGVyLiBUaGUgbW9kZWwgaW5jb3Jwb3JhdGVzIG11bHRpcGxlIGZhY3RvcnMga25vd24gdG8gY2F1c2UgY2h1cm4uIFRoZSBoaWdoZXIgdGhlIHNjb3JlLCB0aGUgbW9yZSBsaWtlbHkgdGhlIGN1c3RvbWVyIHdpbGwgY2h1cm4uDQotIENMVFY6IEN1c3RvbWVyIExpZmV0aW1lIFZhbHVlLiBBIHByZWRpY3RlZCBDTFRWIGlzIGNhbGN1bGF0ZWQgdXNpbmcgY29ycG9yYXRlIGZvcm11bGFzIGFuZCBleGlzdGluZyBkYXRhLiBUaGUgaGlnaGVyIHRoZSB2YWx1ZSwgdGhlIG1vcmUgdmFsdWFibGUgdGhlIGN1c3RvbWVyLiBIaWdoIHZhbHVlIGN1c3RvbWVycyBzaG91bGQgYmUgbW9uaXRvcmVkIGZvciBjaHVybi4NCi0gQ2h1cm4gUmVhc29uOiBBIGN1c3RvbWVy4oCZcyBzcGVjaWZpYyByZWFzb24gZm9yIGxlYXZpbmcgdGhlIGNvbXBhbnkuIERpcmVjdGx5IHJlbGF0ZWQgdG8gQ2h1cm4gQ2F0ZWdvcnk6IDE5IHVuaXF1ZSByZWFzb25zLg0KDQojIyBFeHBsb3JlIGRhdGEgYW5kIGltcGxlbWVudCBwcmVwcm9jZXNzaW5nIHN0ZXBzDQoNCi0gUmVwbGFjZSBkb3RzIGluIGNvbHVtbiBuYW1lcyB0byB1bmRlcnNjb3Jlcy4gQ2hhbmdlIGFsbCBjb2x1bW4gbmFtZXMgdG8gbG93ZXJjYXNlDQotIENvbmZpcm0gZWFjaCByb3cgaXMgb25lIGN1c3RvbWVyIChubyBkdXBsaWNhdGVzKQ0KLSBDaGVjayBmb3IgbWlzc2luZyB2YWx1ZXMgKDExIE5BIHZhbHVlcyBmb3VuZCBpbiB0aGUgY29sdW1uICp0b3RhbF9jaGFyZ2VzKi4gQWxsIG90aGVyIGNvbHVtbnMgYXJlIGdvb2QuIE5vIGZ1cnRoZXIgc3RlcHMgbmVlZGVkLikNCi0gQWRkIGNvdW50eSB2YWx1ZXMgZm9yIGVhY2ggcmVjb3JkIGZvciBzcGF0aWFsIGFuYWx5c2lzIChmdXR1cmUpDQoNCmBgYHtyIHByZXByb2Nlc3NfZGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgU3RhbmRhcmRpemUgY29sdW1uIG5hbWVzDQpjb2xuYW1lcyhjaHVybl9kYXRhKSA8LSB0b2xvd2VyKGNvbG5hbWVzKGNodXJuX2RhdGEpKQ0KY29sbmFtZXMoY2h1cm5fZGF0YSkgPC0gZ3N1YigiXFwuIiwgIl8iLCBjb2xuYW1lcyhjaHVybl9kYXRhKSkNCg0KIyBDb25maXJtIHVuaXF1ZSBjdXN0b21lcnMgKG5vIGR1cGxpY2F0ZXMpDQojIyBpZiBubyBkdXBsaWNhdGVzIHRoZSBmb2xsb3dpbmcgbGVuZ3RoIGNvbXB1dGF0aW9uIHNob3VsZCBiZSBlcXVhbCB0byB0aGUgbnVtYmVyIG9mIHJvd3Mgb2YgdGhlIGRhdGFmcmFtZSANCmxlbmd0aCh1bmlxdWUoY2h1cm5fZGF0YSRjdXN0b21lcmlkKSkNCg0KIyBNaXNzaW5nIHZhbHVlcw0Kc3VtbWFyeShjaHVybl9kYXRhKQ0KDQpgYGANCg0KYGBge3IgZmluZF9jb3VudHksIHdhcm5pbmc9RkFMU0UsIGVycm9yPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBUaGUgc2luZ2xlIGFyZ3VtZW50IHRvIHRoaXMgZnVuY3Rpb24sIHBvaW50c0RGLCBpcyBhIGRhdGEuZnJhbWUgaW4gd2hpY2g6DQojICAgLSBjb2x1bW4gMSBjb250YWlucyB0aGUgbG9uZ2l0dWRlIGluIGRlZ3JlZXMgKG5lZ2F0aXZlIGluIHRoZSBVUykNCiMgICAtIGNvbHVtbiAyIGNvbnRhaW5zIHRoZSBsYXRpdHVkZSBpbiBkZWdyZWVzDQpsYXRsb25nMmNvdW50eSA8LSBmdW5jdGlvbihwb2ludHNERikgew0KICAjIFByZXBhcmUgU3BhdGlhbFBvbHlnb25zIG9iamVjdCB3aXRoIG9uZSBTcGF0aWFsUG9seWdvbg0KICAjIHBlciBjb3VudHkNCiAgY291bnRpZXMgPC0gbWFwKCdjb3VudHknLCBmaWxsPVRSVUUsIGNvbD0idHJhbnNwYXJlbnQiLCBwbG90PUZBTFNFKQ0KICBJRHMgPC0gc2FwcGx5KHN0cnNwbGl0KGNvdW50aWVzJG5hbWVzLCAiOiIpLCBmdW5jdGlvbih4KSB4WzFdKQ0KICBjb3VudGllc19zcCA8LSBtYXAyU3BhdGlhbFBvbHlnb25zKGNvdW50aWVzLCBJRHM9SURzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2o0c3RyaW5nPUNSUygiK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQiKSkNCiAgIyBDb252ZXJ0IHBvaW50c0RGIHRvIGEgU3BhdGlhbFBvaW50cyBvYmplY3QgDQogIHBvaW50c1NQIDwtIFNwYXRpYWxQb2ludHMocG9pbnRzREYsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2o0c3RyaW5nPUNSUygiK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQiKSkNCiAgIyBVc2UgJ292ZXInIHRvIGdldCBfaW5kaWNlc18gb2YgdGhlIFBvbHlnb25zIG9iamVjdCBjb250YWluaW5nIGVhY2ggcG9pbnQgDQogIGluZGljZXMgPC0gb3Zlcihwb2ludHNTUCwgY291bnRpZXNfc3ApDQogICMgUmV0dXJuIHRoZSBjb3VudHkgbmFtZXMgb2YgdGhlIFBvbHlnb25zIG9iamVjdCBjb250YWluaW5nIGVhY2ggcG9pbnQNCiAgY291bnR5TmFtZXMgPC0gc2FwcGx5KGNvdW50aWVzX3NwQHBvbHlnb25zLCBmdW5jdGlvbih4KSB4QElEKQ0KICBjb3VudHlOYW1lc1tpbmRpY2VzXQ0KfQ0KY2h1cm5fZGF0YSA8LSBjaHVybl9kYXRhICU+JQ0KICBtdXRhdGUoDQogICAgY291bnR5ID0gbGF0bG9uZzJjb3VudHkoZGF0YS5mcmFtZSh4ID0gY2h1cm5fZGF0YSRsb25naXR1ZGUsIHkgPSBjaHVybl9kYXRhJGxhdGl0dWRlKSkNCiAgKQ0KZGYgPC0gcmVhZC5jc3YoImRmX3VwZGF0ZWQuY3N2IikNCmNodXJuX2RhdGEgPC0gY2h1cm5fZGF0YSAlPiUgZHBseXI6OmZpbHRlcighaXMubmEoY291bnR5KSkgJT4lDQogIHJiaW5kKGRmKQ0KDQpgYGANCg0KIyMjIENodXJuZWQgdnMuIE5vdCBjaHVybmVkDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9DQpjaHVybl9kYXRhICU+JSBnZ3Bsb3QoYWVzKHggPSBjaHVybl9sYWJlbCwgZmlsbCA9IGNodXJuX2xhYmVsKSkgKyBnZW9tX2JhcigpICsgdGhlbWVfbWluaW1hbCgpDQoNCmBgYA0KDQojIyMgSG93IGdvb2QgaXMgdGhlIGNvbXBhbnkgaW4gcmV0YWluaW5nIGl0cyBjdXN0b21lcnM/DQoNCg0KYGBge3J9DQptZWFuKGNodXJuX2RhdGEkY2h1cm5fdmFsdWUpDQoNCmBgYA0KDQpPbmUgY2FuIGFuc3dlciB0aGlzIHF1ZXN0aW9uIGVhc2lseSBieSBlc3RpbWF0aW5nIHN1cnZpdmFsIGN1cnZlIHVzaW5nIHRoZSBnaXZlbiBkYXRhLiBUaGUgS2FwbGFuLU1laXIgZXN0aW1hdGUgZm9yIHRoZSBnaXZlbiBjaHVybiBkYXRhIGlzIHNob3duIGJlbG93Lg0KDQoNCg0KYGBge3Iga21maXQsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmNodXJuX2RhdGEgPC0gY2h1cm5fZGF0YSAlPiUNCiAgbXV0YXRlKA0KICAgIGJfY2h1cm4gPSBpZmVsc2UoY2h1cm5fbGFiZWwgPT0gIlllcyIsIDEsIDApDQogICkNCmttZml0IDwtIHN1cnZmaXQoU3Vydih0ZW51cmVfbW9udGhzLCBiX2NodXJuKSB+IDEsIGRhdGEgPSBjaHVybl9kYXRhKQ0Ka21fc3VydnBsb3QgPC0gc3Vydm1pbmVyOjpnZ3N1cnZwbG90KA0KICBmaXQgPSBrbWZpdCwgDQogIHN1cnYubWVkaWFuLmxpbmUgPSAibm9uZSIsDQogIHhsYWIgPSAiTW9udGhzIiwgDQogIHlsYWIgPSAiU3Vydml2YWwgcHJvYmFiaWxpdHkiLCB5bGltID0gYygwLjQsIDEpKQ0Ka21fc3VydnBsb3QkcGxvdCArIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNjEsIGxpbmV0eXBlID0gImRhc2hlZCIpICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNzAsIGxpbmV0eXBlID0gImRhc2hlZCIpDQoNCmBgYA0KDQotIFRoZSBwcm9iYWJpbGl0eSB0aGF0IGFuIGF2ZXJhZ2UgY3VzdG9tZXIgd2lsbCBjb250aW51ZSB3aXRoIHRoZSBjb21wYW55IGJleW9uZCA3MCBtb250aHMgaXMgJDAuNjEkLg0KLSBFdmVuIGFmdGVyIDcwIG1vbnRocywgdGhlIGNvbXBhbnkgaXMgYWJsZSB0byByZXRhaW4gNjElIG9yIG1vcmUgb2YgdGhlaXIgY3VzdG9tZXJzLg0KDQojIyMgQ2xvc2VyIGxvb2sgYXQgYWxsIHZhcmlhYmxlcyANCg0KKipTdGF0ZSBoeXBvdGhlc2VzIGJlZm9yZSBidWlsZGluZyBhIGRhdGEtZHJpdmVuIG1vZGVsKioNCg0KLSBnZW5kZXI6IERvIG5vdCBleHBlY3QgdG8gYmUgYSBzdHJvbmcgcHJlZGljdG9yIG9mIGNodXJuIHJpc2suIFdlIHdpbGwgZmlyc3QgYnVpbGQgYSBtb2RlbCB3aXRob3V0IHRoaXMgdmFyaWFibGUsIGFuZCByZXZpc2l0IG9ubHkgaWYgbmVlZGVkLg0KLSBzZW5pb3JfY2l0aXplbjogRG8gbm90IGV4cGVjdCB0byBiZSBhIHN0cm9uZyBwcmVkaWN0b3Igb2YgY2h1cm4gcmlzay4gV2Ugd2lsbCBmaXJzdCBidWlsZCBhIG1vZGVsIHdpdGhvdXQgdGhpcyB2YXJpYWJsZSwgYW5kIHJldmlzaXQgb25seSBpZiBuZWVkZWQuDQotIHBhcnRuZXIvZGVwZW5kZW50czogRG8gbm90IGV4cGVjdCB0byBiZSBhIHN0cm9uZyBwcmVkaWN0b3Igb2YgY2h1cm4gcmlzay4gV2Ugd2lsbCBmaXJzdCBidWlsZCBhIG1vZGVsIHdpdGhvdXQgdGhpcyB2YXJpYWJsZSwgYW5kIHJldmlzaXQgb25seSBpZiBuZWVkZWQuDQotICoqdGVudXJlX21vbnRocyoqOiBJbiBhbiBhdmVyYWdlIHNlbnNlLCBhIGxvbmcgdGltZSBjdXN0b21lciBpcyBtb3JlIGxpa2VseSB0byBzdGF5IHdpdGggdGhlIGNvbXBhbnkgKGxvdyBjaHVybiByaXNrKQ0KLSBwaG9uZV9zZXJ2aWNlL2ludGVybmV0X3NlcnZpY2U6IEN1c3RvbWVycyB3aXRoIGJvdGggcGhvbmUgYW5kIGludGVybmV0IHNlcnZpY2Ugc2hvdWxkIGJlIGxlc3MgbGlrZWx5IHRvIGNodXJuLg0KLSAqKm11bHRpcGxlX2xpbmVzKio6IEhhdmluZyBtdWx0aXBsZSBsaW5lcyBzaG91bGQgcmVkdWNlIGNodXJuIHJpc2suIE11bHRpcGxlIGxpbmVzIGJldHRlciBkaXNjb3VudCBtb3JlIGxpa2VseSB0byBiZSBjb3N0IGVmZmljaWVudCBmb3IgdGhlIGN1c3RvbWVyLg0KLSBvbmxpbmVfc2VjdXJpdHksIG9ubGluZV9iYWNrdXAsIGRldmljZV9wcm90ZWN0aW9uLCBzdHJlYW1pbmdfdHYsIHN0cmVhbWluZ19tb3ZpZXM6IERhdGEgZGVzY3JpcHRpb24gdGVsbHMgdXMgdGhhdCBjdXN0b21lcnMgZG8gbm90IHBheSBmb3IgZXh0cmEgc2VydmljZXMuIEluIGdlbmVyYWwsIGN1c3RvbWVycyB3aXRoIG1vcmUgc2VydmljZXMgYW5kIGRpdmVyc2UgdXNhZ2Ugc2hvdWxkIGJlIGxlc3MgbGlrZWx5IHRvIGNodXJuLg0KLSAqKmNvbnRyYWN0Kio6IEV4cGVjdCB0byBiZSBhIHN0cm9uZyBwcmVkaWN0b3IuIEN1c3RvbWVycyB3aXRoIGxvbmcgdGVybSBjb250cmFjdHMgbGVzcyBsaWtlbHkgdG8gY2h1cm4gdGhhbiB0aG9zZSB3aXRoIG1vbnRoLXRvLW1vbnRoIGNvbnRyYWN0cy4NCi0gKipwYXltZW50X21ldGhvZCoqOiBjdXN0b21lcnMgd2l0aCBhdXRvbWF0aWMgcGF5bWVudCBzZXR1cCAoYXV0b21hdGljIGJhbmsgdHJhbnNmZXIgb3IgY3JlZGl0IGNhcmQpIGxlc3MgbGlrZWx5IHRvIGNodXJuIHRoYW4gbWFudWFsIHBheW1lbnQgbWV0aG9kcy4NCi0gKiptb250aGx5X2NoYXJnZXMqKjogY2h1cm4gcmlzayBzaG91bGQgaW5jcmVhc2Ugd2l0aCBoaWdoZXIgbW9udGhseSBjaGFyZ2VzLiBIb3dldmVyLCBuZWVkIHRvIGJlIGNhcmVmdWwgYXMgdGhpcyBpcyBub3Qgbm9ybWFsaXplZCB3aXRoIHJlc3BlY3QgdG8gbnVtYmVyIG9mIGxpbmVzLCBzZXJ2aWNlcywgZXRjLg0KLSB0b3RhbF9jaGFyZ2VzOiB0b3RhbCBjaGFyZ2VzIGlzIGN1bXVsYXRpdmUgYW5kIGRlcGVuZHMgb24gdGhlIGR1cmF0aW9uIG9mIHNlcnZpY2UuIE5vdCBhIGdvb2QgaWRlYSB0byB1c2UgdGhpcyBpbiBpdHMgcmF3IGZvcm0NCg0KDQojIyMgVmlzdWFsaXplICprZXkqIHZhcmlhYmxlcw0KDQojIyMjIFRlbnVyZQ0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCB3YXJuaW5nPUZBTFNFfQ0KY2h1cm5fZGF0YSAlPiUgZ2dwbG90KGFlcyh4ID0gdGVudXJlX21vbnRocywgZmlsbCA9IGNodXJuX2xhYmVsKSkgKyBnZW9tX2hpc3RvZ3JhbSgpICsgZmFjZXRfd3JhcChjaHVybl9sYWJlbCB+IC4pDQoNCmBgYA0KDQoNCg0KDQoNCiMjIyMgTXVsdGlwbGUgbGluZXMNCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJywgd2FybmluZz1GQUxTRX0NCmdncGxvdChjaHVybl9kYXRhLCBhZXMoeCA9IG11bHRpcGxlX2xpbmVzLCB5ID0gdGVudXJlX21vbnRocywgZmlsbCA9IGNodXJuX2xhYmVsKSkgKyBnZW9tX2JveHBsb3QoKSArIGZhY2V0X3dyYXAofiBtdWx0aXBsZV9saW5lcywgc2NhbGUgPSAiZnJlZSIpDQoNCmBgYA0KDQoNCg0KIyMjIyBDb250cmFjdA0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KZ2dwbG90KGNodXJuX2RhdGEsIGFlcyh4ID0gY29udHJhY3QsIHkgPSB0ZW51cmVfbW9udGhzLCBmaWxsID0gY2h1cm5fbGFiZWwpKSArIGdlb21fYm94cGxvdCgpICsgZmFjZXRfd3JhcCh+IGNvbnRyYWN0LCBzY2FsZSA9ICJmcmVlIikNCg0KYGBgDQoNCiMjIyMgUGF5bWVudCBtZXRob2QNCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmdncGxvdChjaHVybl9kYXRhLCBhZXMoeCA9IHBheW1lbnRfbWV0aG9kLCB5ID0gdGVudXJlX21vbnRocywgZmlsbCA9IGNodXJuX2xhYmVsKSkgKyBnZW9tX2JveHBsb3QoKSArIGZhY2V0X3dyYXAofiBwYXltZW50X21ldGhvZCwgc2NhbGUgPSAiZnJlZSIpDQoNCmBgYA0KDQoNCg0KIyMgTG9naXN0aWMgcmVncmVzc2lvbg0KDQpXZSBjb25zaWRlciB0aGUgZm9sbG93aW5nIG1vZGVsOg0KDQpcYmVnaW57ZXFuYXJyYXl9DQp5X2kgJiBcc2ltICYgQmVybm91bGxpKFx0aGV0YV9pKSBcXA0KXHRleHR7bG9naXR9XGxlZnQoXHRoZXRhX2lccmlnaHQpICYgPSAmIFxiZXRhXzAgKyBcYmV0YV8xIHhfMSArIFxjZG90cyArIFxiZXRhX24geF9uLCBcXA0KXGJldGFfMCwgXGJldGFfMSwgXGxkb3RzLCBcYmV0YV9uICYgXHNpbSAmIFxtYXRoY2Fse059KDAsIDUpIFxoc3BhY2V7MTBwdH0gKHByaW9ycykNClxlbmR7ZXFuYXJyYXl9DQoNCndoZXJlICR5X2kkIGlzIGJpbmFyeSBkYXRhIChjaHVybmVkIG9yIG5vdCBjaHVybmVkKSwgJFx0aGV0YV9pJCBpcyB0aGUgcHJvYmFiaWxpdHkgb2YgY2h1cm4gYW5kICR4X2kncyQgYXJlIGNvdmFyaWF0ZXMgb2YgY3VzdG9tZXIgJGkkLiAkXGJldGFfMCwgXGJldGFfMSwgXGxkb3RzLCBcYmV0YV9uJCBhcmUgbW9kZWwgcGFyYW1ldGVycyB3aGljaCB3aWxsIGJlIGVzdGltYXRlZCB1c2luZyB0aGUgY29sbGVjdGVkIGRhdGEuIA0KDQojIyMgTW9kZWwgc3VtbWFyeQ0KDQpgYGB7cn0NCmlmKCFiQnVpbGRfbWRsX2JheWVzX0ZFKXsNCiAgbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cyA8LSByZWFkUkRTKCJtZGxfbG9naXRfYmF5ZXNfMS5yZHMiKQ0KfSBlbHNlIHsNCiAgbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cyA8LSBicm0oZm9ybXVsYSA9IGNodXJuX3ZhbHVlIH4gY29udHJhY3QgICsgbXVsdGlwbGVfbGluZXMgKyBwYXltZW50X21ldGhvZCArIHRlbnVyZV9tb250aHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9Y2h1cm5fZGF0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmVybm91bGxpKGxpbmsgPSAibG9naXQiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpb3IgPSBjKHByaW9yKG5vcm1hbCgwLCAyKSwgY2xhc3MgPSBJbnRlcmNlcHQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpb3Iobm9ybWFsKDAsIDIpLCBjbGFzcyA9IGIpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3ByaW9yID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2FybXVwID0gNTAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdGVyID0gMzAwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2hhaW5zID0gNCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5pdHM9ICIwIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29yZXMgPSA4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMTIzKQ0KICBzYXZlUkRTKG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMsICJtZGxfbG9naXRfRml4ZWRFZmZlY3RzLnJkcyIpDQp9DQpzdW1tYXJ5KG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMpDQoNCmBgYA0KDQojIyMjIFBvc3RlcmlvciBkaXN0cmlidXRpb24gb2YgcGFyYW1ldGVycw0KDQotIFByb2JhYmlsaXR5IG9mIHBhcmFtZXRlcnMgJFx0aGV0YSQgZ2l2ZW4gZXZpZGVuY2UgKGRhdGEpDQotIFdlIHNob3cgaG93IGRhdGEgbW9kaWZpZXMgb3VyIGEgcHJpb3JpIGFzc3VtcHRpb24gb2YgJFx0aGV0YSQuIFdlIHBsb3Qgb25seSBvbmUgcGFyYW1ldGVyIGZvciBzaW1wbGljaXR5Lg0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCnZhcm5hbWUgPC0gImJfY29udHJhY3RUd295ZWFyIg0KZHJhd3NfcHJpb3IgPC0gcHJpb3JfZHJhd3MobWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cywgdmFybmFtZSkgJT4lDQogIGRwbHlyOjpyZW5hbWUoeCA9ICEhYXMubmFtZSh2YXJuYW1lKSkgJT4lIA0KICBtdXRhdGUoDQogICAgbGFiZWwgPSAicHJpb3IiDQogICkNCmRyYXdzX3Bvc3QgPC0gYXNfZHJhd3NfZGYobWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cykgJT4lDQogIG11dGF0ZSgNCiAgICBsYWJlbCA9ICJwb3N0ZXJpb3IiDQogICkgDQoNCmRmX3Bvc3QgPC0gZHJhd3NfcG9zdCAlPiUNCiAgZHBseXI6OnNlbGVjdChhcy5uYW1lKHZhcm5hbWUpLCBsYWJlbCkgJT4lDQogIHJlbmFtZSh4ID0gISFhcy5uYW1lKHZhcm5hbWUpKQ0KYGBgDQoNCmBgYHtyfQ0KcmJpbmQoZHJhd3NfcHJpb3IsIGRmX3Bvc3QpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0geCwgZmlsbCA9IGxhYmVsKSkgKyBnZW9tX2RlbnNpdHkoKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpcyhkaXNjcmV0ZSA9IFRSVUUsIGFscGhhID0gMC41KSArIHRoZW1lX21pbmltYWwoKSArIA0KICBsYWJzKHggPSB2YXJuYW1lKQ0KDQpgYGANCg0KDQojIyMgUG9zdGVyaW9yIHByZWRpY3RpdmUgZGlzdHJpYnV0aW9uDQoNCi0gRGlzdHJpYnV0aW9uIG9mIHBvc3NpYmxlIHVub2JzZXJ2ZWQgdmFsdWVzIGNvbmRpdGlvbmFsIG9uIG9ic2VydmVkIGRhdGE6ICRwKHlfe25ld318eSkkLg0KDQpgYGB7cn0NCnRlbnVyZTEgPSAxNS44DQp0ZW51cmUyID0gMTAuNQ0KDQpYbmV3IDwtIGNodXJuX2RhdGFbMSwgXQ0KWG5ldyR0ZW51cmVfbW9udGhzIDwtIHRlbnVyZTENCnAxID0gcG9zdGVyaW9yX2xpbnByZWQobWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cywgdHJhbnNmb3JtPVRSVUUsIG5ld2RhdGEgPSBYbmV3KQ0KZGZfcDEgPC0gZGF0YS5mcmFtZShwID0gcDEsIHRlbnVyZV9tb250aHMgPSB0ZW51cmUxKQ0KDQoNClhuZXcgPC0gY2h1cm5fZGF0YVsxLCBdDQpYbmV3JHRlbnVyZV9tb250aHMgPC0gdGVudXJlMg0KI3RhYmxlKFhuZXckdGVudXJlX21vbnRocyA9PSBjaHVybl9kYXRhJHRlbnVyZV9tb250aHMpDQpwMiA9IHBvc3Rlcmlvcl9saW5wcmVkKG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMsIHRyYW5zZm9ybT1UUlVFLCBuZXdkYXRhID0gWG5ldykNCmRmX3AyIDwtIGRhdGEuZnJhbWUocCA9IHAyLCB0ZW51cmVfbW9udGhzID0gdGVudXJlMikNCmRmX3AgPC0gcmJpbmQoZGZfcDEsIGRmX3AyKQ0KZGZfcCR0ZW51cmVfbW9udGhzIDwtIGFzLmZhY3RvcihkZl9wJHRlbnVyZV9tb250aHMpDQoNCmdncGxvdChkZl9wLCBhZXMoeCA9IHAsIGZpbGwgPSB0ZW51cmVfbW9udGhzKSkgKyBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpICsNCiAgbGFicyh0aXRsZSA9IHBhc3RlMCgiQ29udHJhY3Q6ICIsIFhuZXckY29udHJhY3QsICIsIE11bHRpcGxlIGxpbmVzOiAiLCBYbmV3JG11bHRpcGxlX2xpbmVzLCAiLCBQYXltZW50IG1ldGhvZDogIiwNCiAgICAgICAgICAgICAgICAgICAgICBYbmV3JHBheW1lbnRfbWV0aG9kKSkNCg0KYGBgDQoNCg0KIyMjIE1vZGVsIEV2YWx1YXRpb24NCg0KMSkgSG93IHdyb25nIGlzIHRoZSBtb2RlbD8NCjIpIEhvdyBhY2N1cmF0ZSBhcmUgdGhlIG1vZGVs4oCZcyBwb3N0ZXJpb3IgY2xhc3NpZmljYXRpb25zPw0KMykgTW9kZWwgY29tcGFyaXNvbnMNCg0KVG8gYW5zd2VyIHF1ZXN0aW9uICgxKSwgd2Ugd2lsbCBmaXJzdCBjaGVjayBjb252ZXJnZW5jZSBvZiBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9uIG9mIHBhcmFtZXRlcnMgdXNpbmcgdHJhY2VwbG90cy4gTmV4dCwgd2UgcGVyZm9ybSBwb3N0ZXJpb3IgcHJlZGljdGl2ZSBjaGVja3MgdG8gY29uZmlybSB0aGF0IHRoZSBkYXRhIHNpbXVsYXRlZCBmcm9tIG91ciBwb3N0ZXJpb3IgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBoYXMgZmVhdHVyZXMgc2ltaWxhciB0byB0aGUgb3JpZ2luYWwgZGF0YS4gVGhhdCBpcywgc2ltdWxhdGlvbnMgZnJvbSB0aGUgZml0IG1vZGVsIHJlc3VsdCBpbiBzaW1pbGFyIG51bWJlciBvZiBvbmVzIGFuZCB6ZXJvcyBhcyBpbiB0aGUgb3JpZ2luYWwgZGF0YS4gRm9yICgyKSwgd2Ugd2lsbCBhZGRyZXNzIHRoaXMgcXVlc3Rpb24gaW4gdHdvIHdheXM6IHdpdGggYW5kIHdpdGhvdXQgY3Jvc3MtdmFsaWRhdGlvbi4gDQoNCiMjIyMgQ2hlY2sgbW9kZWwgY29udmVyZ2VuY2UgYnkgdmlzdWFsIGluc3BlY3Rpb24gDQoNCkZpZ3VyZSBiZWxvdyBzaG93cyB0aGF0IHRoZXJlIGFyZSBubyBkaXZlcmdlbmNlIGNoYWlucyBhbmQgaGVuY2Ugd2UgY2FuIGNvbmNsdWRlIHRoYXQgb3VyIG1vZGVsIGRvZXMgbm90IGhhdmUgYW55IGNvbnZlcmdlbmNlIHByb2JsZW1zLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuaGVpZ2h0PTYsIGZpZy5oZWlnaHQ9Nn0NCnBsb3QobWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cywgYXNrID0gRkFMU0UsIG5ld3BhZ2UgPSBGQUxTRSwgTiA9IDIpDQoNCmBgYA0KDQojIyMjIFBvc3RlcmlvciBwcmVkaWN0aXZlIGNoZWNrcw0KVG8gY2hlY2sgaGUgZml0IG9mIHRoZSBtb2RlbCB0byBkYXRhIHdlIGRyYXcgc2ltdWxhdGVkIHZhbHVlcyBmcm9tIHRoZSBwb3N0ZXJpb3IgcHJlZGljdGl2ZSBkaXN0cmlidXRpb24gb2YgcmVwbGljYXRlZCBkYXRhLCAkeV57cmVwfSQgYW5kIGNvbXBhcmUgdGhlIHNhbXBsZXMgdG8gb3JpZ2luYWwgZGF0YS4gQW55IHN5c3RlbWF0aWMgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgc2ltdWxhdGlvbnMgYW5kIHRoZSBkYXRhIHdpbGwgaW5kaWNhdGUgcG90ZW50aWFsIGZhaWxpbmdzIG9mIHRoZSBtb2RlbCAoZS5nLiwgdW5zdWl0YWJsZSBwcmlvcnMpLiBUaGUgcGxvdCBiZWxvdyBjb21wYXJlcyBvcmlnaW5hbCBkYXRhIHdpdGggJDUwMCQgZHJhd3MgZnJvbSB0aGUgZml0IG1vZGVsLiBUaGUgZmlndXJlIGNsZWFybHkgc2hvd3MgdGhhdCB0aGUgbW9kZWwgcGFzc2VzIHRoaXMgY2hlY2sNCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJ30NCnBwX2NoZWNrKG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMsIG5kcmF3cyA9IDUwMCkNCg0KYGBgDQoNCiMjIyBDbGFzc2lmaWNhdGlvbiBhY2N1cmFjeQ0KDQojIyMjIFRyYWluaW5nIGFjY3VyYWN5DQoNCi0gVGhyZXNob2xkID0gMC41DQoNCg0KYGBge3J9DQpzZXQuc2VlZCg4NDczNSkNCnRocmVzaG9sZCA9IDAuNQ0KIyBQcmVkaWN0ZWQgcHJvYmFiaWxpdGllcw0KI2xpbnByZWQgPC0gcG9zdGVyaW9yX2xpbnByZWQobWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cykNCnByZWRzIDwtIHBvc3Rlcmlvcl9saW5wcmVkKG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMsIHRyYW5zZm9ybT1UUlVFKQ0KcHJlZCA8LSBjb2xNZWFucyhwcmVkcykNCnByIDwtIGFzLmludGVnZXIocHJlZCA+PSAwLjUpDQoNCiMgcG9zdGVyaW9yIGNsYXNzaWZpY2F0aW9uIGFjY3VyYWN5DQpyb3VuZChtZWFuKHhvcihwcixhcy5pbnRlZ2VyKGNodXJuX2RhdGEkY2h1cm5fdmFsdWU9PTApKSksNikNCg0KDQpgYGANCiMjIyMgQ29uZnVzaW9uIG1hdHJpeA0KDQpgYGB7cn0NCnRhYmxlKGRhdGEuZnJhbWUoY2h1cm5fdmFsdWUgPSBjaHVybl9kYXRhJGNodXJuX3ZhbHVlLCBjaHVybl9wcmVkID0gcHIpKQ0KDQpgYGANCiMjIyMgUmVjZWl2ZXIgT3BlcmF0aW5nIENoYXJhY3RlcmlzdGljIChST0MpIGN1cnZlDQoNCmBgYHtyfQ0KdGhyZXNob2xkIDwtIHNlcSgwLCAxLCBieSA9IDAuMDUpDQpjb21wdXRlX2NodXJuX2RlY2lzaW9uIDwtIGZ1bmN0aW9uKHRocmVzaG9sZCwgcHJlZCwgY2h1cm5fdmFsdWUpew0KICBwciA8LSBhcy5pbnRlZ2VyKHByZWQgPj0gdGhyZXNob2xkKQ0KICB5IDwtIGRhdGEuZnJhbWUoY2h1cm5fdmFsdWUgPSBjaHVybl92YWx1ZSwgY2h1cm5fcHJlZCA9IHByKQ0KICBwZCA8LSB5ICU+JSBkcGx5cjo6ZmlsdGVyKGNodXJuX3ZhbHVlID09IDEpICU+JQ0KICAgIHN1bW1hcmlzZShwZCA9IG1lYW4oY2h1cm5fcHJlZCkpICU+JSBwdWxsKCkNCiAgcGYgPC0geSAlPiUgZHBseXI6OmZpbHRlcihjaHVybl92YWx1ZSA9PSAwKSAlPiUNCiAgICBzdW1tYXJpc2UoDQogICAgICBwZiA9IG1lYW4oY2h1cm5fcHJlZCkNCiAgICApICU+JSBwdWxsKCkNCiAgcmV0dXJuKGxpc3QoZGF0YS5mcmFtZSgicGYiID0gcGYsICJwZCIgPSBwZCwgInRoIiA9IHRocmVzaG9sZCkpKQ0KfQ0KDQpyb2MgPSBsYXBwbHkodGhyZXNob2xkLCBjb21wdXRlX2NodXJuX2RlY2lzaW9uLCBwcmVkID0gcHJlZCwgY2h1cm5fdmFsdWUgPSBjaHVybl9kYXRhJGNodXJuX3ZhbHVlKSAlPiUNCiAgYmluZF9yb3dzKCkNCnJvYyA8LSByb2MgJT4lDQogIG11dGF0ZSgNCiAgICB0eXBlID0gInRyYWluaW5nIHNldCINCiAgKQ0KDQpnZ3Bsb3Qocm9jLCBhZXMoeCA9IHBmLCB5ID0gcGQpKSArIGdlb21fbGluZShzaXplID0gMikgKyANCiAgZ2VvbV9wb2ludChzaXplID0gNSkgKyANCiAgbGFicyh0aXRsZSA9ICJST0MiLCB4ID0gIlByb2JhYmlsaXR5IG9mIGZhbHNlIGFsYXJtIiwgeSA9ICJQcm9iYWJpbGl0eSBvZiBkZXRlY3Rpb24iKQ0KDQoNCmBgYA0KDQojIyMjIENyb3NzIHZhbGlkYXRpb24NClRvIGJldHRlciBlc3RpbWF0ZSB0aGUgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBmb3IgbmV3IG5vdCB5ZXQgc2VlbiBkYXRhIHdlIG5leHQgdXNlIGxlYXZlLW9uZS1vdXQgY3Jvc3MtdmFsaWRhdGlvbi4NCg0KYGBge3J9DQppZihiUnVuQ1ZfbWRsX2JheWVzX0ZFKXsNCiAgbG9vX21kbF9sb2dpdF9GaXhlZEVmZmVjdHMgPC0gbG9vKG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMsIHNhdmVfcHNpcyA9IFRSVUUpDQogIHNhdmVSRFMobG9vX21kbF9sb2dpdF9GaXhlZEVmZmVjdHMsICJsb29fbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cy5yZHMiKQ0KICAjIExPTyBwcmVkaWN0aXZlIHByb2JhYmlsaXRpZXMNCiAgcGxvb19tZGxfbG9naXRfRml4ZWRFZmZlY3RzPUVfbG9vKHByZWRzLCBsb29fbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cyRwc2lzX29iamVjdCwgdHlwZT0ibWVhbiIsIGxvZ19yYXRpb3MgPSAtbG9nX2xpayhtZGxfbG9naXRfRml4ZWRFZmZlY3RzKSkkdmFsdWUNCiAgc2F2ZVJEUyhwbG9vLCAicGxvb19tZGxfbG9naXRfRml4ZWRFZmZlY3RzLnJkcyIpDQp9IGVsc2V7DQogIGxvb19tZGxfbG9naXRfRml4ZWRFZmZlY3RzIDwtIHJlYWRSRFMoImxvb19tZGxfbG9naXRfRml4ZWRFZmZlY3RzLnJkcyIpDQogIHBsb29fbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cyA8LSByZWFkUkRTKCJwbG9vX21kbF9sb2dpdF9GaXhlZEVmZmVjdHMucmRzIikNCn0NCg0KIyMgTE9PIGNsYXNzaWZpY2F0aW9uIGFjY3VyYWN5DQpyb3VuZChtZWFuKHhvcihwbG9vPjAuNSxhcy5pbnRlZ2VyKGNodXJuX2RhdGEkY2h1cm5fdmFsdWU9PTApKSksIDYpDQoNCmBgYA0KDQoNCiMjIyBDaGFsbGVuZ2VyIG1vZGVsOiBUbyBkbw0KDQotIENodXJuIHJhdGUgdnMuIEdlb2dyYXBoaWNhbCBsb2NhdGlvbg0KLSBNdWx0aWxldmVsIG1vZGVsIHRvIGluY29ycG9yYXRlIGxvY2F0aW9uIHZhcmlhdGlvbnMgKGVzcGVjaWFsbHkgdXNlZnVsIHRvIHJlZHVjZSBwcmVkaWN0aW9uIHVuY2VydGFpbnR5IGluIHNtYWxsIHNhbXBsZSBzaXplIGNvdW50aWVzKQ0KDQoNCmBgYHtyfQ0KdGFibGUoY2h1cm5fZGF0YSRjb3VudHkpDQoNCmBgYA0KDQoNCg0KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9DQoNCmNodXJuX2RhdGEgJT4lIGdyb3VwX2J5KGNvdW50eSkgJT4lDQogIHN1bW1hcmlzZShwID0gbWVhbihjaHVybl92YWx1ZSkpICU+JSBhcnJhbmdlKGRlc2MocCkpICU+JQ0KICBtdXRhdGUoY291bnR5ID0gZmN0X3Jlb3JkZXIoY291bnR5LCBwKSkgJT4lDQogIGdncGxvdCggYWVzKHg9Y291bnR5LCB5PXApKSArDQogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArDQogICAgY29vcmRfZmxpcCgpICsNCiAgICB4bGFiKCIiKSArIHlsYWIoImNodXJuIHJhdGUiKSArIA0KICAgIHRoZW1lX2J3KCkNCg0KYGBgDQoNCg0KIyMjIENvbXBhcmlzb24gd2l0aCBjbGFzc2ljYWwgbG9naXN0aWMgcmVncmVzc2lvbiAoRnJlcXVlbnRpc3QpDQoNCmBgYHtyfQ0KbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0c19jbGFzc2ljYWwgPC0gZ2xtKGNodXJuX3ZhbHVlIH4gY29udHJhY3QgICsgbXVsdGlwbGVfbGluZXMgKyBwYXltZW50X21ldGhvZCArIHRlbnVyZV9tb250aHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGNodXJuX2RhdGEsIGZhbWlseSA9ICJiaW5vbWlhbCIpDQpzdW1tYXJ5KG1kbF9sb2dpdF9GaXhlZEVmZmVjdHNfY2xhc3NpY2FsKQ0KDQpgYGANCi0gQm90aCBjbGFzc2ljYWwgYW5kIEJheWVzaWFuIHNvbHV0aW9ucyBtYXRjaCBwcmV0dHkgd2VsbA0KDQojIyBTdXJ2aXZhbCBhbmFseXNpczogVG8gRG8NCg0KDQo=