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.
Explore data and implement preprocessing steps
- Replace dots in column names to underscores. Change all column names to lowercase
- Confirm each row is one customer (no duplicates)
- Check for missing values (11 NA values found in the column total_charges. All other columns are good. No further steps needed.)
- Add county values for each record for spatial analysis (future)
# 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
- How wrong is the model?
- How accurate are the model’s posterior classifications?
- 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
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
LS0tDQp0aXRsZTogIkN1c3RvbWVyIGNodXJuIHByZWRpY3Rpb24iDQphdXRob3I6ICJTYXRpc2ggSXllbmdhciINCmRhdGU6ICI5LzIzLzIwMjEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCmBgYHtyIGxvYWRfcGFja2FnZXMsIGluY2x1ZGUgPSBGQUxTRSwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KbGlicmFyeShzdXJ2aXZhbCkNCmxpYnJhcnkoaGVyZSkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShzaGlueSkNCmxpYnJhcnkoRFQpDQpsaWJyYXJ5KHN1cnZtaW5lcikNCmxpYnJhcnkocGVjKQ0KbGlicmFyeShwYXJ0eSkNCmxpYnJhcnkocm1zKQ0KbGlicmFyeShzcCkNCmxpYnJhcnkobWFwcykNCmxpYnJhcnkobWFwdG9vbHMpDQpsaWJyYXJ5KGJybXMpDQpsaWJyYXJ5KEFtZWxpYSkNCmxpYnJhcnkoSVNMUikNCg0KbGlicmFyeShqYW5pdG9yKQ0KbGlicmFyeShwdXJycikNCmxpYnJhcnkodmlyaWRpcykNCmxpYnJhcnkodGlkeWJheWVzKQ0KbGlicmFyeShiYXllc3Bsb3QpDQpsaWJyYXJ5KGFybSkNCmxpYnJhcnkobW9kZWxyKQ0KbGlicmFyeShiYXllc3J1bGVzKQ0KDQpgYGANCg0KDQpgYGB7ciBib29sZWFuX3ZhcnMsIGluY2x1ZGU9RkFMU0V9DQpiQnVpbGRfbWRsX2JheWVzX0ZFIDwtIDANCmJSdW5DVl9tZGxfYmF5ZXNfRkUgPC0gMA0KYkJ1aWxkX21kbF9iYXllc19SRSA8LSAwDQpiUnVuQ1ZfbWRsX2JheWVzX1JFIDwtIDANCmJDb21wYXJlTW9kZWxzIDwtIDANCg0KYGBgDQoNCiMjIFdoYXQgaXMgQ3VzdG9tZXIgQ2h1cm4/DQoNCkN1c3RvbWVyIGNodXJuIGlzIGxvc3Mgb2YgY3VzdG9tZXJzL3N1YnNjcmliZXJzLiBJcyBpdCBwb3NzaWJsZSB0byB1c2UgQUkvTUwvRGF0YSBTY2llbmNlIHRlY2huaXF1ZXMgdG8gY29uc3RydWN0IGEgbW9kZWwgdGhhdCBjYW4gaGVscCBwcmVkaWN0IGN1c3RvbWVyIGNodXJuPyANCg0KV2UgY2FuIGZvcm11bGF0ZSB0aGlzIHByb2JsZW0gaW4gYXQgbGVhc3QgdHdvIGRpZmZlcmVudCB3YXlzOg0KDQotIEFwcGx5IGNsYXNzaWNhbCB0ZWNobmlxdWVzIHN1Y2ggYXMgbG9naXN0aWMgcmVncmVzc2lvbiB0byBwcmVkaWN0IGlmIGEgY3VzdG9tZXIgd2lsbCBjaHVybiAoWWVzL05vKSBnaXZlbiAqcmVsZXZhbnQgY292YXJpYXRlcyogc3VjaCBhcyBoaXMvaGVyIHRlbnVyZSB3aXRoIHRoZSBjb21wYW55IHNvIGZhci4NCg0KLSBCdWlsZCAqdGltZS10by1ldmVudCogbW9kZWxzIHVzaW5nIHN1cnZpdmFsIGFuYWx5c2lzIHdoaWNoIHdpbGwgYWxsb3cgdXMgdG8gYW5zd2VyIHF1ZXN0aW9ucyBzdWNoIGFzOiBXaGF0IGlzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBjdXN0b21lciB3aWxsIGNodXJuIGluIHRoZSBuZXh0ICR4JCBkYXlzLg0KDQpJbiB0aGlzIHJlcG9ydCwgd2Ugd2lsbCBzaG93IGhvdyBib3RoIHRoZXNlIGFwcHJvYWNoZXMgY2FuIGJlIGFwcGxpZWQgYW5kIHByZXNlbnQgcmVzdWx0cy4NCg0KDQojIyBSZWFkIGRhdGENCg0KLSBSYXcgZGF0YSB1c2VkIGluIHRoaXMgcmVwb3J0IGNhbiBiZSBkb3dubG9hZGVkIGZyb20gaGVyZTogW0V4dGVuZGVkIHRlbGNvIGNodXJuIGRhdGFdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20veWVhbnpjL3RlbGNvLWN1c3RvbWVyLWNodXJuLWlibS1kYXRhc2V0KQ0KDQoNCmBgYHtyIHJlYWRfZGF0YX0NCiMgUmVhZCBkYXRhDQpkYXRhX2ZpbGUgPC0gaGVyZSgiZGF0YSIsICJjdXN0b21lcl9jaHVybl9kYXRhX2V4dGVuZGVkLmNzdiIpDQpjaHVybl9kYXRhIDwtIHJlYWQuY3N2KGRhdGFfZmlsZSkNCmRhdGF0YWJsZShjaHVybl9kYXRhLCByb3duYW1lcyA9IEZBTFNFLCBmaWx0ZXI9InRvcCIsIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSA2LCBzY3JvbGxYPSBUKSkNCg0KYGBgDQoNCiMjIyBRdWljayBtaXNzaW5nIGRhdGEgY2hlY2sNCg0KYGBge3J9DQptaXNzbWFwKGNodXJuX2RhdGEsIGNvbD1jKCJibHVlIiwgInJlZCIpLCBsZWdlbmQ9RkFMU0UpDQoNCmBgYA0KDQotIExvb2tzIGxpa2UgdGhlcmUgYXJlIHNvbWUgbWlzc2luZyB2YWx1ZXMgaW4gKnRvdGFsX2NoYXJnZXMqLiBCdXQgb3ZlcmFsbCBkYXRhIHF1YWxpdHkgaXMgZ29vZC4gDQoNCiMjIERhdGEgRGVzY3JpcHRpb24gKFJlZmVyZW5jZTogW0V4dGVuZGVkIHRlbGNvIGNodXJuIGRhdGFdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20veWVhbnpjL3RlbGNvLWN1c3RvbWVyLWNodXJuLWlibS1kYXRhc2V0KSkNCg0KLSA3MDQzIG9ic2VydmF0aW9ucyB3aXRoIDMzIHZhcmlhYmxlcw0KLSBDdXN0b21lcklEOiBBIHVuaXF1ZSBJRCB0aGF0IGlkZW50aWZpZXMgZWFjaCBjdXN0b21lci4NCi0gQ291bnQ6IEEgdmFsdWUgdXNlZCBpbiByZXBvcnRpbmcvZGFzaGJvYXJkaW5nIHRvIHN1bSB1cCB0aGUgbnVtYmVyIG9mIGN1c3RvbWVycyBpbiBhIGZpbHRlcmVkIHNldC4NCi0gQ291bnRyeTogVGhlIGNvdW50cnkgb2YgdGhlIGN1c3RvbWVy4oCZcyBwcmltYXJ5IHJlc2lkZW5jZTogVW5pdGVkIFN0YXRlcy4NCi0gU3RhdGU6IFRoZSBzdGF0ZSBvZiB0aGUgY3VzdG9tZXLigJlzIHByaW1hcnkgcmVzaWRlbmNlOiBDYWxpZm9ybmlhLg0KLSBDaXR5OiBUaGUgY2l0eSBvZiB0aGUgY3VzdG9tZXLigJlzIHByaW1hcnkgcmVzaWRlbmNlOiAxMTI5IGNpdGllcyBpbiBDQS4NCi0gWmlwIENvZGU6IFRoZSB6aXAgY29kZSBvZiB0aGUgY3VzdG9tZXLigJlzIHByaW1hcnkgcmVzaWRlbmNlOiAxNjUyIHppcCBjb2Rlcy4NCi0gTGF0IExvbmc6IFRoZSBjb21iaW5lZCBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIG9mIHRoZSBjdXN0b21lcuKAmXMgcHJpbWFyeSByZXNpZGVuY2U6IDE2NTIgbGF0X2xvbnMuDQotIExhdGl0dWRlOiBUaGUgbGF0aXR1ZGUgb2YgdGhlIGN1c3RvbWVy4oCZcyBwcmltYXJ5IHJlc2lkZW5jZTogMTY1MiBsYXRzLg0KLSBMb25naXR1ZGU6IFRoZSBsb25naXR1ZGUgb2YgdGhlIGN1c3RvbWVy4oCZcyBwcmltYXJ5IHJlc2lkZW5jZTogMTY1MiBsb25zLg0KLSBHZW5kZXI6IFRoZSBjdXN0b21lcuKAmXMgZ2VuZGVyOiBNYWxlICgzNTU1KSwgRmVtYWxlICgzNDg4KS4NCi0gU2VuaW9yIENpdGl6ZW46IEluZGljYXRlcyBpZiB0aGUgY3VzdG9tZXIgaXMgNjUgb3Igb2xkZXI6IFllcyAoMTE0MiksIE5vICg1OTAxKS4NCi0gUGFydG5lcjogSW5kaWNhdGUgaWYgdGhlIGN1c3RvbWVyIGhhcyBhIHBhcnRuZXI6IFllcyAoMzQwMiksIE5vICgzNjQxKS4NCi0gRGVwZW5kZW50czogSW5kaWNhdGVzIGlmIHRoZSBjdXN0b21lciBsaXZlcyB3aXRoIGFueSBkZXBlbmRlbnRzOiBZZXMgKDE2MjcpLCBObyAoNTQxNikuIERlcGVuZGVudHMgY291bGQgYmUgY2hpbGRyZW4sIHBhcmVudHMsIGdyYW5kcGFyZW50cywgZXRjLg0KLSBUZW51cmUgTW9udGhzOiBJbmRpY2F0ZXMgdGhlIHRvdGFsIGFtb3VudCBvZiBtb250aHMgdGhhdCB0aGUgY3VzdG9tZXIgaGFzIGJlZW4gd2l0aCB0aGUgY29tcGFueSBieSB0aGUgZW5kIG9mIHRoZSBxdWFydGVyIHNwZWNpZmllZCBhYm92ZS4NCi0gUGhvbmUgU2VydmljZTogSW5kaWNhdGVzIGlmIHRoZSBjdXN0b21lciBzdWJzY3JpYmVzIHRvIGhvbWUgcGhvbmUgc2VydmljZSB3aXRoIHRoZSBjb21wYW55OiBZZXMgKDYzNjEpLCBObyAoNjgyKS4NCi0gTXVsdGlwbGUgTGluZXM6IEluZGljYXRlcyBpZiB0aGUgY3VzdG9tZXIgc3Vic2NyaWJlcyB0byBtdWx0aXBsZSB0ZWxlcGhvbmUgbGluZXMgd2l0aCB0aGUgY29tcGFueTogWWVzICgyOTcxKSwgTm8gKDMzOTApLCBObyBwaG9uZSBzZXJ2aWNlICg2ODIpLg0KLSBJbnRlcm5ldCBTZXJ2aWNlOiBJbmRpY2F0ZXMgaWYgdGhlIGN1c3RvbWVyIHN1YnNjcmliZXMgdG8gSW50ZXJuZXQgc2VydmljZSB3aXRoIHRoZSBjb21wYW55OiBObyAoMTUyNiksIERTTCAoMjQyMSksIEZpYmVyIE9wdGljICgzMDk2KS4NCi0gT25saW5lIFNlY3VyaXR5OiBJbmRpY2F0ZXMgaWYgdGhlIGN1c3RvbWVyIHN1YnNjcmliZXMgdG8gYW4gYWRkaXRpb25hbCBvbmxpbmUgc2VjdXJpdHkgc2VydmljZSBwcm92aWRlZCBieSB0aGUgY29tcGFueTogWWVzICgyMDE5KSwgTm8gKDM0OTgpLCBObyBpbnRlcm5ldCBzZXJ2aWNlICgxNTI2KS4NCi0gT25saW5lIEJhY2t1cDogSW5kaWNhdGVzIGlmIHRoZSBjdXN0b21lciBzdWJzY3JpYmVzIHRvIGFuIGFkZGl0aW9uYWwgb25saW5lIGJhY2t1cCBzZXJ2aWNlIHByb3ZpZGVkIGJ5IHRoZSBjb21wYW55OiBZZXMgKDI0MjkpLCBObyAoMzA4OCksIE5vIGludGVybmV0IHNlcnZpY2UgKDE1MjYpLg0KLSBEZXZpY2UgUHJvdGVjdGlvbjogSW5kaWNhdGVzIGlmIHRoZSBjdXN0b21lciBzdWJzY3JpYmVzIHRvIGFuIGFkZGl0aW9uYWwgZGV2aWNlIHByb3RlY3Rpb24gcGxhbiBmb3IgdGhlaXIgSW50ZXJuZXQgZXF1aXBtZW50IHByb3ZpZGVkIGJ5IHRoZSBjb21wYW55OiBZZXMgKDI0MjIpLCBObyAoMzA5NSksIE5vIGludGVybmV0IHNlcnZpY2UgKDE1MjYpLg0KLSBUZWNoIFN1cHBvcnQ6IEluZGljYXRlcyBpZiB0aGUgY3VzdG9tZXIgc3Vic2NyaWJlcyB0byBhbiBhZGRpdGlvbmFsIHRlY2huaWNhbCBzdXBwb3J0IHBsYW4gZnJvbSB0aGUgY29tcGFueSB3aXRoIHJlZHVjZWQgd2FpdCB0aW1lczogWWVzICgyMDQ0KSwgTm8gKDM0NzMpLCBObyBpbnRlcm5ldCBzZXJ2aWNlICgxNTI2KS4NCi0gU3RyZWFtaW5nIFRWOiBJbmRpY2F0ZXMgaWYgdGhlIGN1c3RvbWVyIHVzZXMgdGhlaXIgSW50ZXJuZXQgc2VydmljZSB0byBzdHJlYW0gdGVsZXZpc2lvbiBwcm9ncmFtaW5nIGZyb20gYSB0aGlyZCBwYXJ0eSBwcm92aWRlcjogWWVzICgyNzA3KSwgTm8gKDI4MTApLCBObyBpbnRlcm5ldCBzZXJ2aWNlICgxNTI2KS4gVGhlIGNvbXBhbnkgZG9lcyBub3QgY2hhcmdlIGFuIGFkZGl0aW9uYWwgZmVlIGZvciB0aGlzIHNlcnZpY2UuDQotIFN0cmVhbWluZyBNb3ZpZXM6IEluZGljYXRlcyBpZiB0aGUgY3VzdG9tZXIgdXNlcyB0aGVpciBJbnRlcm5ldCBzZXJ2aWNlIHRvIHN0cmVhbSBtb3ZpZXMgZnJvbSBhIHRoaXJkIHBhcnR5IHByb3ZpZGVyOiBZZXMgKDI3MzIpLCBObyAoMjc4NSksIE5vIGludGVybmV0IHNlcnZpY2UgKDE1MjYpLiBUaGUgY29tcGFueSBkb2VzIG5vdCBjaGFyZ2UgYW4gYWRkaXRpb25hbCBmZWUgZm9yIHRoaXMgc2VydmljZS4NCi0gQ29udHJhY3Q6IEluZGljYXRlcyB0aGUgY3VzdG9tZXLigJlzIGN1cnJlbnQgY29udHJhY3QgdHlwZTogTW9udGgtdG8tTW9udGggKDM4NzUpLCBPbmUgWWVhciAoMTQ3MyksIFR3byBZZWFyICgxNjk1KS4NCi0gUGFwZXJsZXNzIEJpbGxpbmc6IEluZGljYXRlcyBpZiB0aGUgY3VzdG9tZXIgaGFzIGNob3NlbiBwYXBlcmxlc3MgYmlsbGluZzogWWVzICg0MTcxKSwgTm8gKDI4NzIpLg0KLSBQYXltZW50IE1ldGhvZDogSW5kaWNhdGVzIGhvdyB0aGUgY3VzdG9tZXIgcGF5cyB0aGVpciBiaWxsOiBCYW5rIHRyYW5zZmVyICgxNTQ0KSwgQ3JlZGl0IENhcmQgKDE1MjIpLCBFbGVjdHJvbmljIGNoZWNrICgyMzY1KSwgTWFpbGVkIENoZWNrICgxNjEyKS4NCi0gTW9udGhseSBDaGFyZ2U6IEluZGljYXRlcyB0aGUgY3VzdG9tZXLigJlzIGN1cnJlbnQgdG90YWwgbW9udGhseSBjaGFyZ2UgZm9yIGFsbCB0aGVpciBzZXJ2aWNlcyBmcm9tIHRoZSBjb21wYW55Lg0KLSBUb3RhbCBDaGFyZ2VzOiBJbmRpY2F0ZXMgdGhlIGN1c3RvbWVy4oCZcyB0b3RhbCBjaGFyZ2VzLCBjYWxjdWxhdGVkIHRvIHRoZSBlbmQgb2YgdGhlIHF1YXJ0ZXIgc3BlY2lmaWVkIGFib3ZlLg0KLSBDaHVybiBMYWJlbDogWWVzID0gdGhlIGN1c3RvbWVyIGxlZnQgdGhlIGNvbXBhbnkgdGhpcyBxdWFydGVyICgxODY5KS4gTm8gPSB0aGUgY3VzdG9tZXIgcmVtYWluZWQgd2l0aCB0aGUgY29tcGFueSAoNTE3NCkuIERpcmVjdGx5IHJlbGF0ZWQgdG8gQ2h1cm4gVmFsdWUuDQotIENodXJuIFZhbHVlOiAxID0gdGhlIGN1c3RvbWVyIGxlZnQgdGhlIGNvbXBhbnkgdGhpcyBxdWFydGVyICgxODY5KS4gMCA9IHRoZSBjdXN0b21lciByZW1haW5lZCB3aXRoIHRoZSBjb21wYW55LiBEaXJlY3RseSByZWxhdGVkIHRvIENodXJuIExhYmVsICg1MTc0KS4NCi0gQ2h1cm4gU2NvcmU6IEEgdmFsdWUgZnJvbSAwLTEwMCB0aGF0IGlzIGNhbGN1bGF0ZWQgdXNpbmcgdGhlIHByZWRpY3RpdmUgdG9vbCBJQk0gU1BTUyBNb2RlbGVyLiBUaGUgbW9kZWwgaW5jb3Jwb3JhdGVzIG11bHRpcGxlIGZhY3RvcnMga25vd24gdG8gY2F1c2UgY2h1cm4uIFRoZSBoaWdoZXIgdGhlIHNjb3JlLCB0aGUgbW9yZSBsaWtlbHkgdGhlIGN1c3RvbWVyIHdpbGwgY2h1cm4uDQotIENMVFY6IEN1c3RvbWVyIExpZmV0aW1lIFZhbHVlLiBBIHByZWRpY3RlZCBDTFRWIGlzIGNhbGN1bGF0ZWQgdXNpbmcgY29ycG9yYXRlIGZvcm11bGFzIGFuZCBleGlzdGluZyBkYXRhLiBUaGUgaGlnaGVyIHRoZSB2YWx1ZSwgdGhlIG1vcmUgdmFsdWFibGUgdGhlIGN1c3RvbWVyLiBIaWdoIHZhbHVlIGN1c3RvbWVycyBzaG91bGQgYmUgbW9uaXRvcmVkIGZvciBjaHVybi4NCi0gQ2h1cm4gUmVhc29uOiBBIGN1c3RvbWVy4oCZcyBzcGVjaWZpYyByZWFzb24gZm9yIGxlYXZpbmcgdGhlIGNvbXBhbnkuIERpcmVjdGx5IHJlbGF0ZWQgdG8gQ2h1cm4gQ2F0ZWdvcnk6IDE5IHVuaXF1ZSByZWFzb25zLg0KDQojIyBFeHBsb3JlIGRhdGEgYW5kIGltcGxlbWVudCBwcmVwcm9jZXNzaW5nIHN0ZXBzDQoNCi0gUmVwbGFjZSBkb3RzIGluIGNvbHVtbiBuYW1lcyB0byB1bmRlcnNjb3Jlcy4gQ2hhbmdlIGFsbCBjb2x1bW4gbmFtZXMgdG8gbG93ZXJjYXNlDQotIENvbmZpcm0gZWFjaCByb3cgaXMgb25lIGN1c3RvbWVyIChubyBkdXBsaWNhdGVzKQ0KLSBDaGVjayBmb3IgbWlzc2luZyB2YWx1ZXMgKDExIE5BIHZhbHVlcyBmb3VuZCBpbiB0aGUgY29sdW1uICp0b3RhbF9jaGFyZ2VzKi4gQWxsIG90aGVyIGNvbHVtbnMgYXJlIGdvb2QuIE5vIGZ1cnRoZXIgc3RlcHMgbmVlZGVkLikNCi0gQWRkIGNvdW50eSB2YWx1ZXMgZm9yIGVhY2ggcmVjb3JkIGZvciBzcGF0aWFsIGFuYWx5c2lzIChmdXR1cmUpDQoNCmBgYHtyIHByZXByb2Nlc3NfZGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgU3RhbmRhcmRpemUgY29sdW1uIG5hbWVzDQpjb2xuYW1lcyhjaHVybl9kYXRhKSA8LSB0b2xvd2VyKGNvbG5hbWVzKGNodXJuX2RhdGEpKQ0KY29sbmFtZXMoY2h1cm5fZGF0YSkgPC0gZ3N1YigiXFwuIiwgIl8iLCBjb2xuYW1lcyhjaHVybl9kYXRhKSkNCg0KIyBDb25maXJtIHVuaXF1ZSBjdXN0b21lcnMgKG5vIGR1cGxpY2F0ZXMpDQojIyBpZiBubyBkdXBsaWNhdGVzIHRoZSBmb2xsb3dpbmcgbGVuZ3RoIGNvbXB1dGF0aW9uIHNob3VsZCBiZSBlcXVhbCB0byB0aGUgbnVtYmVyIG9mIHJvd3Mgb2YgdGhlIGRhdGFmcmFtZSANCmxlbmd0aCh1bmlxdWUoY2h1cm5fZGF0YSRjdXN0b21lcmlkKSkNCg0KIyBNaXNzaW5nIHZhbHVlcw0Kc3VtbWFyeShjaHVybl9kYXRhKQ0KDQpgYGANCg0KYGBge3IgZmluZF9jb3VudHksIHdhcm5pbmc9RkFMU0UsIGVycm9yPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBUaGUgc2luZ2xlIGFyZ3VtZW50IHRvIHRoaXMgZnVuY3Rpb24sIHBvaW50c0RGLCBpcyBhIGRhdGEuZnJhbWUgaW4gd2hpY2g6DQojICAgLSBjb2x1bW4gMSBjb250YWlucyB0aGUgbG9uZ2l0dWRlIGluIGRlZ3JlZXMgKG5lZ2F0aXZlIGluIHRoZSBVUykNCiMgICAtIGNvbHVtbiAyIGNvbnRhaW5zIHRoZSBsYXRpdHVkZSBpbiBkZWdyZWVzDQpsYXRsb25nMmNvdW50eSA8LSBmdW5jdGlvbihwb2ludHNERikgew0KICAjIFByZXBhcmUgU3BhdGlhbFBvbHlnb25zIG9iamVjdCB3aXRoIG9uZSBTcGF0aWFsUG9seWdvbg0KICAjIHBlciBjb3VudHkNCiAgY291bnRpZXMgPC0gbWFwKCdjb3VudHknLCBmaWxsPVRSVUUsIGNvbD0idHJhbnNwYXJlbnQiLCBwbG90PUZBTFNFKQ0KICBJRHMgPC0gc2FwcGx5KHN0cnNwbGl0KGNvdW50aWVzJG5hbWVzLCAiOiIpLCBmdW5jdGlvbih4KSB4WzFdKQ0KICBjb3VudGllc19zcCA8LSBtYXAyU3BhdGlhbFBvbHlnb25zKGNvdW50aWVzLCBJRHM9SURzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2o0c3RyaW5nPUNSUygiK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQiKSkNCiAgIyBDb252ZXJ0IHBvaW50c0RGIHRvIGEgU3BhdGlhbFBvaW50cyBvYmplY3QgDQogIHBvaW50c1NQIDwtIFNwYXRpYWxQb2ludHMocG9pbnRzREYsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2o0c3RyaW5nPUNSUygiK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQiKSkNCiAgIyBVc2UgJ292ZXInIHRvIGdldCBfaW5kaWNlc18gb2YgdGhlIFBvbHlnb25zIG9iamVjdCBjb250YWluaW5nIGVhY2ggcG9pbnQgDQogIGluZGljZXMgPC0gb3Zlcihwb2ludHNTUCwgY291bnRpZXNfc3ApDQogICMgUmV0dXJuIHRoZSBjb3VudHkgbmFtZXMgb2YgdGhlIFBvbHlnb25zIG9iamVjdCBjb250YWluaW5nIGVhY2ggcG9pbnQNCiAgY291bnR5TmFtZXMgPC0gc2FwcGx5KGNvdW50aWVzX3NwQHBvbHlnb25zLCBmdW5jdGlvbih4KSB4QElEKQ0KICBjb3VudHlOYW1lc1tpbmRpY2VzXQ0KfQ0KY2h1cm5fZGF0YSA8LSBjaHVybl9kYXRhICU+JQ0KICBtdXRhdGUoDQogICAgY291bnR5ID0gbGF0bG9uZzJjb3VudHkoZGF0YS5mcmFtZSh4ID0gY2h1cm5fZGF0YSRsb25naXR1ZGUsIHkgPSBjaHVybl9kYXRhJGxhdGl0dWRlKSkNCiAgKQ0KZGYgPC0gcmVhZC5jc3YoImRmX3VwZGF0ZWQuY3N2IikNCmNodXJuX2RhdGEgPC0gY2h1cm5fZGF0YSAlPiUgZHBseXI6OmZpbHRlcighaXMubmEoY291bnR5KSkgJT4lDQogIHJiaW5kKGRmKQ0KDQpgYGANCg0KIyMjIENodXJuZWQgdnMuIE5vdCBjaHVybmVkDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9DQpjaHVybl9kYXRhICU+JSBnZ3Bsb3QoYWVzKHggPSBjaHVybl9sYWJlbCwgZmlsbCA9IGNodXJuX2xhYmVsKSkgKyBnZW9tX2JhcigpICsgdGhlbWVfbWluaW1hbCgpDQoNCmBgYA0KDQojIyMgSG93IGdvb2QgaXMgdGhlIGNvbXBhbnkgaW4gcmV0YWluaW5nIGl0cyBjdXN0b21lcnM/DQoNCg0KYGBge3J9DQptZWFuKGNodXJuX2RhdGEkY2h1cm5fdmFsdWUpDQoNCmBgYA0KDQpPbmUgY2FuIGFuc3dlciB0aGlzIHF1ZXN0aW9uIGVhc2lseSBieSBlc3RpbWF0aW5nIHN1cnZpdmFsIGN1cnZlIHVzaW5nIHRoZSBnaXZlbiBkYXRhLiBUaGUgS2FwbGFuLU1laXIgZXN0aW1hdGUgZm9yIHRoZSBnaXZlbiBjaHVybiBkYXRhIGlzIHNob3duIGJlbG93Lg0KDQoNCg0KYGBge3Iga21maXQsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmNodXJuX2RhdGEgPC0gY2h1cm5fZGF0YSAlPiUNCiAgbXV0YXRlKA0KICAgIGJfY2h1cm4gPSBpZmVsc2UoY2h1cm5fbGFiZWwgPT0gIlllcyIsIDEsIDApDQogICkNCmttZml0IDwtIHN1cnZmaXQoU3Vydih0ZW51cmVfbW9udGhzLCBiX2NodXJuKSB+IDEsIGRhdGEgPSBjaHVybl9kYXRhKQ0Ka21fc3VydnBsb3QgPC0gc3Vydm1pbmVyOjpnZ3N1cnZwbG90KA0KICBmaXQgPSBrbWZpdCwgDQogIHN1cnYubWVkaWFuLmxpbmUgPSAibm9uZSIsDQogIHhsYWIgPSAiTW9udGhzIiwgDQogIHlsYWIgPSAiU3Vydml2YWwgcHJvYmFiaWxpdHkiLCB5bGltID0gYygwLjQsIDEpKQ0Ka21fc3VydnBsb3QkcGxvdCArIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNjEsIGxpbmV0eXBlID0gImRhc2hlZCIpICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNzAsIGxpbmV0eXBlID0gImRhc2hlZCIpDQoNCmBgYA0KDQotIFRoZSBwcm9iYWJpbGl0eSB0aGF0IGFuIGF2ZXJhZ2UgY3VzdG9tZXIgd2lsbCBjb250aW51ZSB3aXRoIHRoZSBjb21wYW55IGJleW9uZCA3MCBtb250aHMgaXMgJDAuNjEkLg0KLSBFdmVuIGFmdGVyIDcwIG1vbnRocywgdGhlIGNvbXBhbnkgaXMgYWJsZSB0byByZXRhaW4gNjElIG9yIG1vcmUgb2YgdGhlaXIgY3VzdG9tZXJzLg0KDQojIyMgQ2xvc2VyIGxvb2sgYXQgYWxsIHZhcmlhYmxlcyANCg0KKipTdGF0ZSBoeXBvdGhlc2VzIGJlZm9yZSBidWlsZGluZyBhIGRhdGEtZHJpdmVuIG1vZGVsKioNCg0KLSBnZW5kZXI6IERvIG5vdCBleHBlY3QgdG8gYmUgYSBzdHJvbmcgcHJlZGljdG9yIG9mIGNodXJuIHJpc2suIFdlIHdpbGwgZmlyc3QgYnVpbGQgYSBtb2RlbCB3aXRob3V0IHRoaXMgdmFyaWFibGUsIGFuZCByZXZpc2l0IG9ubHkgaWYgbmVlZGVkLg0KLSBzZW5pb3JfY2l0aXplbjogRG8gbm90IGV4cGVjdCB0byBiZSBhIHN0cm9uZyBwcmVkaWN0b3Igb2YgY2h1cm4gcmlzay4gV2Ugd2lsbCBmaXJzdCBidWlsZCBhIG1vZGVsIHdpdGhvdXQgdGhpcyB2YXJpYWJsZSwgYW5kIHJldmlzaXQgb25seSBpZiBuZWVkZWQuDQotIHBhcnRuZXIvZGVwZW5kZW50czogRG8gbm90IGV4cGVjdCB0byBiZSBhIHN0cm9uZyBwcmVkaWN0b3Igb2YgY2h1cm4gcmlzay4gV2Ugd2lsbCBmaXJzdCBidWlsZCBhIG1vZGVsIHdpdGhvdXQgdGhpcyB2YXJpYWJsZSwgYW5kIHJldmlzaXQgb25seSBpZiBuZWVkZWQuDQotICoqdGVudXJlX21vbnRocyoqOiBJbiBhbiBhdmVyYWdlIHNlbnNlLCBhIGxvbmcgdGltZSBjdXN0b21lciBpcyBtb3JlIGxpa2VseSB0byBzdGF5IHdpdGggdGhlIGNvbXBhbnkgKGxvdyBjaHVybiByaXNrKQ0KLSBwaG9uZV9zZXJ2aWNlL2ludGVybmV0X3NlcnZpY2U6IEN1c3RvbWVycyB3aXRoIGJvdGggcGhvbmUgYW5kIGludGVybmV0IHNlcnZpY2Ugc2hvdWxkIGJlIGxlc3MgbGlrZWx5IHRvIGNodXJuLg0KLSAqKm11bHRpcGxlX2xpbmVzKio6IEhhdmluZyBtdWx0aXBsZSBsaW5lcyBzaG91bGQgcmVkdWNlIGNodXJuIHJpc2suIE11bHRpcGxlIGxpbmVzIGJldHRlciBkaXNjb3VudCBtb3JlIGxpa2VseSB0byBiZSBjb3N0IGVmZmljaWVudCBmb3IgdGhlIGN1c3RvbWVyLg0KLSBvbmxpbmVfc2VjdXJpdHksIG9ubGluZV9iYWNrdXAsIGRldmljZV9wcm90ZWN0aW9uLCBzdHJlYW1pbmdfdHYsIHN0cmVhbWluZ19tb3ZpZXM6IERhdGEgZGVzY3JpcHRpb24gdGVsbHMgdXMgdGhhdCBjdXN0b21lcnMgZG8gbm90IHBheSBmb3IgZXh0cmEgc2VydmljZXMuIEluIGdlbmVyYWwsIGN1c3RvbWVycyB3aXRoIG1vcmUgc2VydmljZXMgYW5kIGRpdmVyc2UgdXNhZ2Ugc2hvdWxkIGJlIGxlc3MgbGlrZWx5IHRvIGNodXJuLg0KLSAqKmNvbnRyYWN0Kio6IEV4cGVjdCB0byBiZSBhIHN0cm9uZyBwcmVkaWN0b3IuIEN1c3RvbWVycyB3aXRoIGxvbmcgdGVybSBjb250cmFjdHMgbGVzcyBsaWtlbHkgdG8gY2h1cm4gdGhhbiB0aG9zZSB3aXRoIG1vbnRoLXRvLW1vbnRoIGNvbnRyYWN0cy4NCi0gKipwYXltZW50X21ldGhvZCoqOiBjdXN0b21lcnMgd2l0aCBhdXRvbWF0aWMgcGF5bWVudCBzZXR1cCAoYXV0b21hdGljIGJhbmsgdHJhbnNmZXIgb3IgY3JlZGl0IGNhcmQpIGxlc3MgbGlrZWx5IHRvIGNodXJuIHRoYW4gbWFudWFsIHBheW1lbnQgbWV0aG9kcy4NCi0gKiptb250aGx5X2NoYXJnZXMqKjogY2h1cm4gcmlzayBzaG91bGQgaW5jcmVhc2Ugd2l0aCBoaWdoZXIgbW9udGhseSBjaGFyZ2VzLiBIb3dldmVyLCBuZWVkIHRvIGJlIGNhcmVmdWwgYXMgdGhpcyBpcyBub3Qgbm9ybWFsaXplZCB3aXRoIHJlc3BlY3QgdG8gbnVtYmVyIG9mIGxpbmVzLCBzZXJ2aWNlcywgZXRjLg0KLSB0b3RhbF9jaGFyZ2VzOiB0b3RhbCBjaGFyZ2VzIGlzIGN1bXVsYXRpdmUgYW5kIGRlcGVuZHMgb24gdGhlIGR1cmF0aW9uIG9mIHNlcnZpY2UuIE5vdCBhIGdvb2QgaWRlYSB0byB1c2UgdGhpcyBpbiBpdHMgcmF3IGZvcm0NCg0KDQojIyMgVmlzdWFsaXplICprZXkqIHZhcmlhYmxlcw0KDQojIyMjIFRlbnVyZQ0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCB3YXJuaW5nPUZBTFNFfQ0KY2h1cm5fZGF0YSAlPiUgZ2dwbG90KGFlcyh4ID0gdGVudXJlX21vbnRocywgZmlsbCA9IGNodXJuX2xhYmVsKSkgKyBnZW9tX2hpc3RvZ3JhbSgpICsgZmFjZXRfd3JhcChjaHVybl9sYWJlbCB+IC4pDQoNCmBgYA0KDQoNCg0KDQoNCiMjIyMgTXVsdGlwbGUgbGluZXMNCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJywgd2FybmluZz1GQUxTRX0NCmdncGxvdChjaHVybl9kYXRhLCBhZXMoeCA9IG11bHRpcGxlX2xpbmVzLCB5ID0gdGVudXJlX21vbnRocywgZmlsbCA9IGNodXJuX2xhYmVsKSkgKyBnZW9tX2JveHBsb3QoKSArIGZhY2V0X3dyYXAofiBtdWx0aXBsZV9saW5lcywgc2NhbGUgPSAiZnJlZSIpDQoNCmBgYA0KDQoNCg0KIyMjIyBDb250cmFjdA0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KZ2dwbG90KGNodXJuX2RhdGEsIGFlcyh4ID0gY29udHJhY3QsIHkgPSB0ZW51cmVfbW9udGhzLCBmaWxsID0gY2h1cm5fbGFiZWwpKSArIGdlb21fYm94cGxvdCgpICsgZmFjZXRfd3JhcCh+IGNvbnRyYWN0LCBzY2FsZSA9ICJmcmVlIikNCg0KYGBgDQoNCiMjIyMgUGF5bWVudCBtZXRob2QNCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmdncGxvdChjaHVybl9kYXRhLCBhZXMoeCA9IHBheW1lbnRfbWV0aG9kLCB5ID0gdGVudXJlX21vbnRocywgZmlsbCA9IGNodXJuX2xhYmVsKSkgKyBnZW9tX2JveHBsb3QoKSArIGZhY2V0X3dyYXAofiBwYXltZW50X21ldGhvZCwgc2NhbGUgPSAiZnJlZSIpDQoNCmBgYA0KDQoNCg0KIyMgTG9naXN0aWMgcmVncmVzc2lvbg0KDQpXZSBjb25zaWRlciB0aGUgZm9sbG93aW5nIG1vZGVsOg0KDQpcYmVnaW57ZXFuYXJyYXl9DQp5X2kgJiBcc2ltICYgQmVybm91bGxpKFx0aGV0YV9pKSBcXA0KXHRleHR7bG9naXR9XGxlZnQoXHRoZXRhX2lccmlnaHQpICYgPSAmIFxiZXRhXzAgKyBcYmV0YV8xIHhfMSArIFxjZG90cyArIFxiZXRhX24geF9uLCBcXA0KXGJldGFfMCwgXGJldGFfMSwgXGxkb3RzLCBcYmV0YV9uICYgXHNpbSAmIFxtYXRoY2Fse059KDAsIDUpIFxoc3BhY2V7MTBwdH0gKHByaW9ycykNClxlbmR7ZXFuYXJyYXl9DQoNCndoZXJlICR5X2kkIGlzIGJpbmFyeSBkYXRhIChjaHVybmVkIG9yIG5vdCBjaHVybmVkKSwgJFx0aGV0YV9pJCBpcyB0aGUgcHJvYmFiaWxpdHkgb2YgY2h1cm4gYW5kICR4X2kncyQgYXJlIGNvdmFyaWF0ZXMgb2YgY3VzdG9tZXIgJGkkLiAkXGJldGFfMCwgXGJldGFfMSwgXGxkb3RzLCBcYmV0YV9uJCBhcmUgbW9kZWwgcGFyYW1ldGVycyB3aGljaCB3aWxsIGJlIGVzdGltYXRlZCB1c2luZyB0aGUgY29sbGVjdGVkIGRhdGEuIA0KDQojIyMgTW9kZWwgc3VtbWFyeQ0KDQpgYGB7cn0NCmlmKCFiQnVpbGRfbWRsX2JheWVzX0ZFKXsNCiAgbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cyA8LSByZWFkUkRTKCJtZGxfbG9naXRfYmF5ZXNfMS5yZHMiKQ0KfSBlbHNlIHsNCiAgbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cyA8LSBicm0oZm9ybXVsYSA9IGNodXJuX3ZhbHVlIH4gY29udHJhY3QgICsgbXVsdGlwbGVfbGluZXMgKyBwYXltZW50X21ldGhvZCArIHRlbnVyZV9tb250aHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9Y2h1cm5fZGF0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmVybm91bGxpKGxpbmsgPSAibG9naXQiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpb3IgPSBjKHByaW9yKG5vcm1hbCgwLCAyKSwgY2xhc3MgPSBJbnRlcmNlcHQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpb3Iobm9ybWFsKDAsIDIpLCBjbGFzcyA9IGIpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3ByaW9yID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2FybXVwID0gNTAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdGVyID0gMzAwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2hhaW5zID0gNCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5pdHM9ICIwIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29yZXMgPSA4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMTIzKQ0KICBzYXZlUkRTKG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMsICJtZGxfbG9naXRfRml4ZWRFZmZlY3RzLnJkcyIpDQp9DQpzdW1tYXJ5KG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMpDQoNCmBgYA0KDQojIyMjIFBvc3RlcmlvciBkaXN0cmlidXRpb24gb2YgcGFyYW1ldGVycw0KDQotIFByb2JhYmlsaXR5IG9mIHBhcmFtZXRlcnMgJFx0aGV0YSQgZ2l2ZW4gZXZpZGVuY2UgKGRhdGEpDQotIFdlIHNob3cgaG93IGRhdGEgbW9kaWZpZXMgb3VyIGEgcHJpb3JpIGFzc3VtcHRpb24gb2YgJFx0aGV0YSQuIFdlIHBsb3Qgb25seSBvbmUgcGFyYW1ldGVyIGZvciBzaW1wbGljaXR5Lg0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCnZhcm5hbWUgPC0gImJfY29udHJhY3RUd295ZWFyIg0KZHJhd3NfcHJpb3IgPC0gcHJpb3JfZHJhd3MobWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cywgdmFybmFtZSkgJT4lDQogIGRwbHlyOjpyZW5hbWUoeCA9ICEhYXMubmFtZSh2YXJuYW1lKSkgJT4lIA0KICBtdXRhdGUoDQogICAgbGFiZWwgPSAicHJpb3IiDQogICkNCmRyYXdzX3Bvc3QgPC0gYXNfZHJhd3NfZGYobWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cykgJT4lDQogIG11dGF0ZSgNCiAgICBsYWJlbCA9ICJwb3N0ZXJpb3IiDQogICkgDQoNCmRmX3Bvc3QgPC0gZHJhd3NfcG9zdCAlPiUNCiAgZHBseXI6OnNlbGVjdChhcy5uYW1lKHZhcm5hbWUpLCBsYWJlbCkgJT4lDQogIHJlbmFtZSh4ID0gISFhcy5uYW1lKHZhcm5hbWUpKQ0KYGBgDQoNCmBgYHtyfQ0KcmJpbmQoZHJhd3NfcHJpb3IsIGRmX3Bvc3QpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0geCwgZmlsbCA9IGxhYmVsKSkgKyBnZW9tX2RlbnNpdHkoKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpcyhkaXNjcmV0ZSA9IFRSVUUsIGFscGhhID0gMC41KSArIHRoZW1lX21pbmltYWwoKSArIA0KICBsYWJzKHggPSB2YXJuYW1lKQ0KDQpgYGANCg0KDQojIyMgUG9zdGVyaW9yIHByZWRpY3RpdmUgZGlzdHJpYnV0aW9uDQoNCi0gRGlzdHJpYnV0aW9uIG9mIHBvc3NpYmxlIHVub2JzZXJ2ZWQgdmFsdWVzIGNvbmRpdGlvbmFsIG9uIG9ic2VydmVkIGRhdGE6ICRwKHlfe25ld318eSkkLg0KDQpgYGB7cn0NCnRlbnVyZTEgPSAxNS44DQp0ZW51cmUyID0gMTAuNQ0KDQpYbmV3IDwtIGNodXJuX2RhdGFbMSwgXQ0KWG5ldyR0ZW51cmVfbW9udGhzIDwtIHRlbnVyZTENCnAxID0gcG9zdGVyaW9yX2xpbnByZWQobWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cywgdHJhbnNmb3JtPVRSVUUsIG5ld2RhdGEgPSBYbmV3KQ0KZGZfcDEgPC0gZGF0YS5mcmFtZShwID0gcDEsIHRlbnVyZV9tb250aHMgPSB0ZW51cmUxKQ0KDQoNClhuZXcgPC0gY2h1cm5fZGF0YVsxLCBdDQpYbmV3JHRlbnVyZV9tb250aHMgPC0gdGVudXJlMg0KI3RhYmxlKFhuZXckdGVudXJlX21vbnRocyA9PSBjaHVybl9kYXRhJHRlbnVyZV9tb250aHMpDQpwMiA9IHBvc3Rlcmlvcl9saW5wcmVkKG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMsIHRyYW5zZm9ybT1UUlVFLCBuZXdkYXRhID0gWG5ldykNCmRmX3AyIDwtIGRhdGEuZnJhbWUocCA9IHAyLCB0ZW51cmVfbW9udGhzID0gdGVudXJlMikNCmRmX3AgPC0gcmJpbmQoZGZfcDEsIGRmX3AyKQ0KZGZfcCR0ZW51cmVfbW9udGhzIDwtIGFzLmZhY3RvcihkZl9wJHRlbnVyZV9tb250aHMpDQoNCmdncGxvdChkZl9wLCBhZXMoeCA9IHAsIGZpbGwgPSB0ZW51cmVfbW9udGhzKSkgKyBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpICsNCiAgbGFicyh0aXRsZSA9IHBhc3RlMCgiQ29udHJhY3Q6ICIsIFhuZXckY29udHJhY3QsICIsIE11bHRpcGxlIGxpbmVzOiAiLCBYbmV3JG11bHRpcGxlX2xpbmVzLCAiLCBQYXltZW50IG1ldGhvZDogIiwNCiAgICAgICAgICAgICAgICAgICAgICBYbmV3JHBheW1lbnRfbWV0aG9kKSkNCg0KYGBgDQoNCg0KIyMjIE1vZGVsIEV2YWx1YXRpb24NCg0KMSkgSG93IHdyb25nIGlzIHRoZSBtb2RlbD8NCjIpIEhvdyBhY2N1cmF0ZSBhcmUgdGhlIG1vZGVs4oCZcyBwb3N0ZXJpb3IgY2xhc3NpZmljYXRpb25zPw0KMykgTW9kZWwgY29tcGFyaXNvbnMNCg0KVG8gYW5zd2VyIHF1ZXN0aW9uICgxKSwgd2Ugd2lsbCBmaXJzdCBjaGVjayBjb252ZXJnZW5jZSBvZiBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9uIG9mIHBhcmFtZXRlcnMgdXNpbmcgdHJhY2VwbG90cy4gTmV4dCwgd2UgcGVyZm9ybSBwb3N0ZXJpb3IgcHJlZGljdGl2ZSBjaGVja3MgdG8gY29uZmlybSB0aGF0IHRoZSBkYXRhIHNpbXVsYXRlZCBmcm9tIG91ciBwb3N0ZXJpb3IgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBoYXMgZmVhdHVyZXMgc2ltaWxhciB0byB0aGUgb3JpZ2luYWwgZGF0YS4gVGhhdCBpcywgc2ltdWxhdGlvbnMgZnJvbSB0aGUgZml0IG1vZGVsIHJlc3VsdCBpbiBzaW1pbGFyIG51bWJlciBvZiBvbmVzIGFuZCB6ZXJvcyBhcyBpbiB0aGUgb3JpZ2luYWwgZGF0YS4gRm9yICgyKSwgd2Ugd2lsbCBhZGRyZXNzIHRoaXMgcXVlc3Rpb24gaW4gdHdvIHdheXM6IHdpdGggYW5kIHdpdGhvdXQgY3Jvc3MtdmFsaWRhdGlvbi4gDQoNCiMjIyMgQ2hlY2sgbW9kZWwgY29udmVyZ2VuY2UgYnkgdmlzdWFsIGluc3BlY3Rpb24gDQoNCkZpZ3VyZSBiZWxvdyBzaG93cyB0aGF0IHRoZXJlIGFyZSBubyBkaXZlcmdlbmNlIGNoYWlucyBhbmQgaGVuY2Ugd2UgY2FuIGNvbmNsdWRlIHRoYXQgb3VyIG1vZGVsIGRvZXMgbm90IGhhdmUgYW55IGNvbnZlcmdlbmNlIHByb2JsZW1zLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuaGVpZ2h0PTYsIGZpZy5oZWlnaHQ9Nn0NCnBsb3QobWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cywgYXNrID0gRkFMU0UsIG5ld3BhZ2UgPSBGQUxTRSwgTiA9IDIpDQoNCmBgYA0KDQojIyMjIFBvc3RlcmlvciBwcmVkaWN0aXZlIGNoZWNrcw0KVG8gY2hlY2sgaGUgZml0IG9mIHRoZSBtb2RlbCB0byBkYXRhIHdlIGRyYXcgc2ltdWxhdGVkIHZhbHVlcyBmcm9tIHRoZSBwb3N0ZXJpb3IgcHJlZGljdGl2ZSBkaXN0cmlidXRpb24gb2YgcmVwbGljYXRlZCBkYXRhLCAkeV57cmVwfSQgYW5kIGNvbXBhcmUgdGhlIHNhbXBsZXMgdG8gb3JpZ2luYWwgZGF0YS4gQW55IHN5c3RlbWF0aWMgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgc2ltdWxhdGlvbnMgYW5kIHRoZSBkYXRhIHdpbGwgaW5kaWNhdGUgcG90ZW50aWFsIGZhaWxpbmdzIG9mIHRoZSBtb2RlbCAoZS5nLiwgdW5zdWl0YWJsZSBwcmlvcnMpLiBUaGUgcGxvdCBiZWxvdyBjb21wYXJlcyBvcmlnaW5hbCBkYXRhIHdpdGggJDUwMCQgZHJhd3MgZnJvbSB0aGUgZml0IG1vZGVsLiBUaGUgZmlndXJlIGNsZWFybHkgc2hvd3MgdGhhdCB0aGUgbW9kZWwgcGFzc2VzIHRoaXMgY2hlY2sNCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJ30NCnBwX2NoZWNrKG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMsIG5kcmF3cyA9IDUwMCkNCg0KYGBgDQoNCiMjIyBDbGFzc2lmaWNhdGlvbiBhY2N1cmFjeQ0KDQojIyMjIFRyYWluaW5nIGFjY3VyYWN5DQoNCi0gVGhyZXNob2xkID0gMC41DQoNCg0KYGBge3J9DQpzZXQuc2VlZCg4NDczNSkNCnRocmVzaG9sZCA9IDAuNQ0KIyBQcmVkaWN0ZWQgcHJvYmFiaWxpdGllcw0KI2xpbnByZWQgPC0gcG9zdGVyaW9yX2xpbnByZWQobWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cykNCnByZWRzIDwtIHBvc3Rlcmlvcl9saW5wcmVkKG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMsIHRyYW5zZm9ybT1UUlVFKQ0KcHJlZCA8LSBjb2xNZWFucyhwcmVkcykNCnByIDwtIGFzLmludGVnZXIocHJlZCA+PSAwLjUpDQoNCiMgcG9zdGVyaW9yIGNsYXNzaWZpY2F0aW9uIGFjY3VyYWN5DQpyb3VuZChtZWFuKHhvcihwcixhcy5pbnRlZ2VyKGNodXJuX2RhdGEkY2h1cm5fdmFsdWU9PTApKSksNikNCg0KDQpgYGANCiMjIyMgQ29uZnVzaW9uIG1hdHJpeA0KDQpgYGB7cn0NCnRhYmxlKGRhdGEuZnJhbWUoY2h1cm5fdmFsdWUgPSBjaHVybl9kYXRhJGNodXJuX3ZhbHVlLCBjaHVybl9wcmVkID0gcHIpKQ0KDQpgYGANCiMjIyMgUmVjZWl2ZXIgT3BlcmF0aW5nIENoYXJhY3RlcmlzdGljIChST0MpIGN1cnZlDQoNCmBgYHtyfQ0KdGhyZXNob2xkIDwtIHNlcSgwLCAxLCBieSA9IDAuMDUpDQpjb21wdXRlX2NodXJuX2RlY2lzaW9uIDwtIGZ1bmN0aW9uKHRocmVzaG9sZCwgcHJlZCwgY2h1cm5fdmFsdWUpew0KICBwciA8LSBhcy5pbnRlZ2VyKHByZWQgPj0gdGhyZXNob2xkKQ0KICB5IDwtIGRhdGEuZnJhbWUoY2h1cm5fdmFsdWUgPSBjaHVybl92YWx1ZSwgY2h1cm5fcHJlZCA9IHByKQ0KICBwZCA8LSB5ICU+JSBkcGx5cjo6ZmlsdGVyKGNodXJuX3ZhbHVlID09IDEpICU+JQ0KICAgIHN1bW1hcmlzZShwZCA9IG1lYW4oY2h1cm5fcHJlZCkpICU+JSBwdWxsKCkNCiAgcGYgPC0geSAlPiUgZHBseXI6OmZpbHRlcihjaHVybl92YWx1ZSA9PSAwKSAlPiUNCiAgICBzdW1tYXJpc2UoDQogICAgICBwZiA9IG1lYW4oY2h1cm5fcHJlZCkNCiAgICApICU+JSBwdWxsKCkNCiAgcmV0dXJuKGxpc3QoZGF0YS5mcmFtZSgicGYiID0gcGYsICJwZCIgPSBwZCwgInRoIiA9IHRocmVzaG9sZCkpKQ0KfQ0KDQpyb2MgPSBsYXBwbHkodGhyZXNob2xkLCBjb21wdXRlX2NodXJuX2RlY2lzaW9uLCBwcmVkID0gcHJlZCwgY2h1cm5fdmFsdWUgPSBjaHVybl9kYXRhJGNodXJuX3ZhbHVlKSAlPiUNCiAgYmluZF9yb3dzKCkNCnJvYyA8LSByb2MgJT4lDQogIG11dGF0ZSgNCiAgICB0eXBlID0gInRyYWluaW5nIHNldCINCiAgKQ0KDQpnZ3Bsb3Qocm9jLCBhZXMoeCA9IHBmLCB5ID0gcGQpKSArIGdlb21fbGluZShzaXplID0gMikgKyANCiAgZ2VvbV9wb2ludChzaXplID0gNSkgKyANCiAgbGFicyh0aXRsZSA9ICJST0MiLCB4ID0gIlByb2JhYmlsaXR5IG9mIGZhbHNlIGFsYXJtIiwgeSA9ICJQcm9iYWJpbGl0eSBvZiBkZXRlY3Rpb24iKQ0KDQoNCmBgYA0KDQojIyMjIENyb3NzIHZhbGlkYXRpb24NClRvIGJldHRlciBlc3RpbWF0ZSB0aGUgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBmb3IgbmV3IG5vdCB5ZXQgc2VlbiBkYXRhIHdlIG5leHQgdXNlIGxlYXZlLW9uZS1vdXQgY3Jvc3MtdmFsaWRhdGlvbi4NCg0KYGBge3J9DQppZihiUnVuQ1ZfbWRsX2JheWVzX0ZFKXsNCiAgbG9vX21kbF9sb2dpdF9GaXhlZEVmZmVjdHMgPC0gbG9vKG1kbF9sb2dpdF9GaXhlZEVmZmVjdHMsIHNhdmVfcHNpcyA9IFRSVUUpDQogIHNhdmVSRFMobG9vX21kbF9sb2dpdF9GaXhlZEVmZmVjdHMsICJsb29fbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cy5yZHMiKQ0KICAjIExPTyBwcmVkaWN0aXZlIHByb2JhYmlsaXRpZXMNCiAgcGxvb19tZGxfbG9naXRfRml4ZWRFZmZlY3RzPUVfbG9vKHByZWRzLCBsb29fbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cyRwc2lzX29iamVjdCwgdHlwZT0ibWVhbiIsIGxvZ19yYXRpb3MgPSAtbG9nX2xpayhtZGxfbG9naXRfRml4ZWRFZmZlY3RzKSkkdmFsdWUNCiAgc2F2ZVJEUyhwbG9vLCAicGxvb19tZGxfbG9naXRfRml4ZWRFZmZlY3RzLnJkcyIpDQp9IGVsc2V7DQogIGxvb19tZGxfbG9naXRfRml4ZWRFZmZlY3RzIDwtIHJlYWRSRFMoImxvb19tZGxfbG9naXRfRml4ZWRFZmZlY3RzLnJkcyIpDQogIHBsb29fbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0cyA8LSByZWFkUkRTKCJwbG9vX21kbF9sb2dpdF9GaXhlZEVmZmVjdHMucmRzIikNCn0NCg0KIyMgTE9PIGNsYXNzaWZpY2F0aW9uIGFjY3VyYWN5DQpyb3VuZChtZWFuKHhvcihwbG9vPjAuNSxhcy5pbnRlZ2VyKGNodXJuX2RhdGEkY2h1cm5fdmFsdWU9PTApKSksIDYpDQoNCmBgYA0KDQoNCiMjIyBDaGFsbGVuZ2VyIG1vZGVsOiBUbyBkbw0KDQotIENodXJuIHJhdGUgdnMuIEdlb2dyYXBoaWNhbCBsb2NhdGlvbg0KLSBNdWx0aWxldmVsIG1vZGVsIHRvIGluY29ycG9yYXRlIGxvY2F0aW9uIHZhcmlhdGlvbnMgKGVzcGVjaWFsbHkgdXNlZnVsIHRvIHJlZHVjZSBwcmVkaWN0aW9uIHVuY2VydGFpbnR5IGluIHNtYWxsIHNhbXBsZSBzaXplIGNvdW50aWVzKQ0KDQoNCmBgYHtyfQ0KdGFibGUoY2h1cm5fZGF0YSRjb3VudHkpDQoNCmBgYA0KDQoNCg0KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9DQoNCmNodXJuX2RhdGEgJT4lIGdyb3VwX2J5KGNvdW50eSkgJT4lDQogIHN1bW1hcmlzZShwID0gbWVhbihjaHVybl92YWx1ZSkpICU+JSBhcnJhbmdlKGRlc2MocCkpICU+JQ0KICBtdXRhdGUoY291bnR5ID0gZmN0X3Jlb3JkZXIoY291bnR5LCBwKSkgJT4lDQogIGdncGxvdCggYWVzKHg9Y291bnR5LCB5PXApKSArDQogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArDQogICAgY29vcmRfZmxpcCgpICsNCiAgICB4bGFiKCIiKSArIHlsYWIoImNodXJuIHJhdGUiKSArIA0KICAgIHRoZW1lX2J3KCkNCg0KYGBgDQoNCg0KIyMjIENvbXBhcmlzb24gd2l0aCBjbGFzc2ljYWwgbG9naXN0aWMgcmVncmVzc2lvbiAoRnJlcXVlbnRpc3QpDQoNCmBgYHtyfQ0KbWRsX2xvZ2l0X0ZpeGVkRWZmZWN0c19jbGFzc2ljYWwgPC0gZ2xtKGNodXJuX3ZhbHVlIH4gY29udHJhY3QgICsgbXVsdGlwbGVfbGluZXMgKyBwYXltZW50X21ldGhvZCArIHRlbnVyZV9tb250aHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGNodXJuX2RhdGEsIGZhbWlseSA9ICJiaW5vbWlhbCIpDQpzdW1tYXJ5KG1kbF9sb2dpdF9GaXhlZEVmZmVjdHNfY2xhc3NpY2FsKQ0KDQpgYGANCi0gQm90aCBjbGFzc2ljYWwgYW5kIEJheWVzaWFuIHNvbHV0aW9ucyBtYXRjaCBwcmV0dHkgd2VsbA0KDQojIyBTdXJ2aXZhbCBhbmFseXNpczogVG8gRG8NCg0KDQo=