Introduction
Logistic regression is the most commonly used statistical technique
for binary response data. Many marketing applications are concerning
binary consumer decisions:
- does a consumer respond or not respond to marketing?
- do they subscribe or not subscribe?
- do they churn or not churn?
We’ll use a data set on customer churn for a telecommunications
company with several different services. We’ll use demographic, service
usage, and customer history to predict churn. We then apply this model
to a new, holdout set of customers. We calclate the confusion matrix,
the lift table, and use it to do targeted proactive churn selection.
Installing the packages and loading the data
# install.packages("pRoc")
# install.packages("plotrix")
library(car)
library(tidyverse)
library(pROC)
library(plotrix)
library(tidyverse)
library(readr)
library(kableExtra)
# set working directory
telco <- read_csv("telco.csv")
telco_holdout <- read_csv("telco_holdout.csv")
options("scipen"=200, "digits"=3)
Inspecting the data
Let’s get rid of the ID column, since we never need to use it. We’ll
make senior citizen a factor variable, and recode total charges so that
it’s in thousands of dollars. We also need to recode Churn for yes/no to
0/1.
# drop the ID column, make senior citizen a factor variable, and divide totalcharges by 1000
telco <- telco[-c(1)]
telco$SeniorCitizen<-as.factor(telco$SeniorCitizen)
telco$TotalCharges<-telco$TotalCharges/1000
# Change Churn from "no" "yes" to 0 1
telco <- telco %>%
mutate(Churn = ifelse(Churn == "No",0,1))
Churn
What fraction of customers churn (quit)? This is the dependent
variable we want to predict. We need to use the “as.numeric” function to
transform it from a factor variable to a 0/1 continuous variable in R.
We report the average churn rate of the customer below.
summary(telco$Churn)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.000 0.000 0.000 0.266 1.000 1.000
rbar <- mean(telco$Churn)
The average churn rate in the customer base is 0.266.
Tenure
One important driver of churn is likely to be
tenure, how long a customer has been a customer for. We
can see below that there is a spike at 1, many customers just started,
and a smaller peak at 72.
par(mai=c(.9,.8,.2,.2))
hist(telco$tenure, main = "", xlab="Tenure (# months a customer)", breaks = 71)
Churn Rate Variation
How does the rate churn vary by tenure? We create a dataset of length
72, one for each level of tenure. We calculate the proportion churning,
number of churners (n_churn), number of customers in the tenure group,
the standard error of the proportion churning (discussed in previous
lectures), and the lower and upper confidence intervals.
churn_tenure <- telco %>%
as.data.frame() %>%
group_by(tenure) %>%
summarize(tenure=mean(tenure),
p_churn=mean(Churn),
n_churners=sum(Churn), n=n(),
p_churn_se= sqrt((p_churn)*(1-p_churn)/n)) %>%
mutate(lower_CI_pchurn = p_churn - 1.96*p_churn_se, ## CI of churn by tenure-lev.
upper_CI_pchurn = p_churn + 1.96*p_churn_se)
head(churn_tenure) %>%
kbl() %>%
kable_styling()
tenure
|
p_churn
|
n_churners
|
n
|
p_churn_se
|
lower_CI_pchurn
|
upper_CI_pchurn
|
1
|
0.620
|
380
|
613
|
0.020
|
0.581
|
0.658
|
2
|
0.517
|
123
|
238
|
0.032
|
0.453
|
0.580
|
3
|
0.470
|
94
|
200
|
0.035
|
0.401
|
0.539
|
4
|
0.472
|
83
|
176
|
0.038
|
0.398
|
0.545
|
5
|
0.481
|
64
|
133
|
0.043
|
0.396
|
0.566
|
6
|
0.364
|
40
|
110
|
0.046
|
0.274
|
0.454
|
par(mai=c(.9,.8,.2,.2))
plot(x = churn_tenure$tenure, y = churn_tenure$p_churn, main="Proportion of customers who churn by tenure", xlab="Tenure (# months a customer)", ylab="proportion of customer churning")
The figure shows a clear negative relationship: the longer the
customer has been a customer, the lower the probability of churn (churn
rate).
Estimating the logistic regression
- Model 0 is the simplest: The only variable is
tenure and it is treated as a continuous variable.
# fit
model_0 <- glm(Churn ~ tenure, data=telco, family = binomial(link="logit"))
# show us coefficients and other model fit statistics
summary(model_0)
##
## Call:
## glm(formula = Churn ~ tenure, family = binomial(link = "logit"),
## data = telco)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -1.177 -0.840 -0.479 1.178 2.380
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 0.03730 0.04232 0.88 0.38
## tenure -0.03901 0.00141 -27.69 <0.0000000000000002 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 8143.4 on 7031 degrees of freedom
## Residual deviance: 7176.3 on 7030 degrees of freedom
## AIC: 7180
##
## Number of Fisher Scoring iterations: 4
## [1] -0.0383
Interpretation: Having 1 additional unit of tenure of
decreases the odds of churn by 0.0383
or by
3.8%
.
Plot: Compare observed proportion of churn by tenure
calculated separately for each level of tenure; with model
predictions.
I’m just creating a new data with the regression results:
# create data set of tenure from 1 to 72
plotdat <- data.frame(tenure=(1:72))
# put predictions and 95% confidence intervals of those
preddat <- predict(model_0,
type = "link",
newdata=plotdat, ## Prediction by each level of tenure
se.fit=TRUE) %>%
as.data.frame() %>%
mutate(tenure=(1:72), ### HERE we are putting our results ####
# model object model_0 has a component called linkinv that
# is a function that inverts the link function of the GLM:
lower = model_0$family$linkinv(fit - 1.96*se.fit),
point.estimate = model_0$family$linkinv(fit),
upper = model_0$family$linkinv(fit + 1.96*se.fit))
Final Plot:
# plot actual vs. logistic regression
par(mai=c(.9,.8,.2,.2))
plot(x = churn_tenure$tenure, y = churn_tenure$p_churn, main="Proportion of customers who churn by tenure", xlab="Tenure (# months a customer)", ylab="proportion of customer churning")
lines(x=preddat$tenure, y=preddat$point.estimate, col="red", lwd=2)
legend('topright',legend=c("churn proportion", "logistic regression"),col=c("black","red"),pch=c(1,NA),lty=c(NA,1), lwd=c(NA,2))
eq <- paste0("logit(p) = ",round(coef(model_0)[1],4),
ifelse(coef(model_0)[2]<0,round(coef(model_0)[2],4),
paste("+",round(coef(model_0)[2],4))),
paste(" tenure"))
# puts equation in figure
mtext(eq, 1,-3)
Compare the confidence intervals of the model predictions (dashed
red) to those by doing them separately for each level of tenure.
You can see we get quite a reduction in uncertainty by having a model
that relates these proportions to each other.
The cost of our lower error or error reduction is
higher bias – if the model’s functional form deviates
from the actual response rate. In other words, we have reduced variance,
but at the expense of bias.
par(mai=c(.9,.8,.2,.2))
plotCI(x = churn_tenure$tenure, # plotrix plot with confidence intervals
y = churn_tenure$p_churn,
li = churn_tenure$lower_CI_pchurn,
ui = churn_tenure$upper_CI_pchurn, main="Proportion of customers who churn by tenure", xlab="Tenure (# months a customer)", ylab="proportion of customer churning")
lines(x=preddat$tenure, y=preddat$point.estimate, col="red", lwd=2, type = "l")
lines(x=preddat$tenure, y=preddat$lower, col="red", lty=2, lwd=1, type = "l")
lines(x=preddat$tenure, y=preddat$upper, col="red", lty=2, lwd=1, type = "l")
- Model 1 is more complex: every variable is
included, not just tenure; tenure is treated as a continuous
variable as before.
options(width = 200)
model_1 <- glm(Churn ~ . , data=telco, family="binomial")
- Model 2 is more complex: like Model 1, except that
tenure is treated a categorical variable. In other
words there is a dummy variable for every level of tenure but one. This
way, we can flexibly capture a pattern between tenure and
churn. In R, all you have to do is write
as.factor(tenure) instead of
tenure.
model_2 <- glm(Churn ~ . +as.factor(tenure) -tenure , data=telco, family="binomial")
- Model 2 has 94 coefficients.
- Model 3 is the most complex: like Model 2, except
that there is an interaction between payment type and
tenure. Note in general and interaction is the coefficient on
the product of two variables.
model_3 <- glm(Churn ~ . +as.factor(tenure)*as.factor(PaymentMethod) -tenure -PaymentMethod, data=telco, family="binomial")
Model 3 has 307 coefficients. Note a lot of them
have large coefficients and large standard errors. If a variable is zero
almost always, (tenure==34)*(PaymentMethod==Electronic check), there is
little variation to estimate the coefficient, making it look
unstable.
So, we’ve estimated 3 models each one increasing in the number of
coefficients. Let’s see how well they predict.
Deviance and proportion of deviance explained (R2)
Deviance is an error measure, \(-2
\ln(\textrm{likelihood})\). We want it to be as small as
possible. The difference between the residual and the null deviance then
gives us some sense of how well our model fits overall, taken
together.
You can also look at the proportion of deviance explained by the
variables in the model.
\[
R^2 = \frac{D_0 - D}{D_0} = 1 - \frac{D}{D_0}
\]
models <- paste0("model_", 0:3) # list of models
D <- sapply(models, function(x) get(x)$deviance) # get deviance D for each
D0 <- model_0$null.deviance # D_0 is the same for all models
R2 <- 1-D/D0
par(mai=c(.9,.8,.2,.2))
barplot(R2, names.arg = c("model 0","model 1", "model 2", "model 3"), main=expression(paste("In-Sample R"^"2")), xlab="Model", ylab=expression(paste("R"^"2")))
Models 0, 1, 2 and 3 are explaining 12% 28%, 31% and 34%,
respectively, of the deviance in customer churn.
Overfitting, K-fold out of sample
But, is the better performance of model a result of
overfitting?
What we really care about is being able to predict
new data. The R2 and deviance measures are all about
in-sample, not out-of-sample fit. So it doesn’t tell us how well our
model performs on other data.
We can mimic the presence of new data by holding out part of the
data.
We use K-fold out of sample validation.
# you don't need to know how to write this code.
set.seed(19103)
n = nrow(telco)
K = 10 # # folds
foldid = rep(1:K, each=ceiling(n/K))[sample(1:n)]
# foldid[1:10]
OOS <- data.frame(model0=rep(NA, K), model1=rep(NA,K), model2=rep(NA,K), model3=rep(NA,K))
## pred must be probabilities (0<pred<1) for binomial
deviance <- function(y, pred, family=c("gaussian","binomial")){
family <- match.arg(family)
if(family=="gaussian"){
return( sum( (y-pred)^2 ) )
}else{
if(is.factor(y)) y <- as.numeric(y)>1
return( -2*sum( y*log(pred) + (1-y)*log(1-pred) ) )
}
}
## get null devaince too, and return R2
R2 <- function(y, pred, family=c("gaussian","binomial")){
fam <- match.arg(family)
if(fam=="binomial"){
if(is.factor(y)){ y <- as.numeric(y)>1 }
}
dev <- deviance(y, pred, family=fam)
dev0 <- deviance(y, mean(y), family=fam)
return(1-dev/dev0)
}
# this part will take several minutes, fitting 3 models K times each
for(k in 1:K){
train = which(foldid!=k) # data used to train
# fit regressions
model_0<- glm(Churn ~ tenure, data=telco[train,], family="binomial")
summary(model_0)
model_1 <- glm(Churn ~ . , data=telco[train,], family="binomial")
summary(model_1)
model_2 <- glm(Churn ~ . +as.factor(tenure) -tenure, data=telco[train,], family="binomial")
summary(model_2)
model_3 <- glm(Churn ~ . +as.factor(tenure)*as.factor(PaymentMethod) -tenure -PaymentMethod, data=telco[train,], family="binomial")
summary(model_3)
# predict on holdout data (-train)
pred0<- predict(model_0, newdata=telco[-train,], type = "response")
pred1<- predict(model_1, newdata=telco[-train,], type = "response")
pred2<- predict(model_2, newdata=telco[-train,], type = "response")
pred3<- predict(model_3, newdata=telco[-train,], type = "response")
# calculate R2
OOS$model0[k]<-R2(y = telco$Churn[-train],pred=pred0, family="binomial")
OOS$model1[k]<-R2(y = telco$Churn[-train],pred=pred1, family="binomial")
OOS$model2[k]<-R2(y = telco$Churn[-train],pred=pred2, family="binomial")
OOS$model3[k]<-R2(y = telco$Churn[-train],pred=pred3, family="binomial")
# print progress
cat(k, " ")
}
## 1 2 3 4 5 6 7 8 9 10
Plot Results:
par(mai=c(.9,.8,.2,.2))
boxplot(OOS[,1:4], data=OOS, main=expression(paste("Out-of-Sample R"^"2")),
xlab="Model", ylab=expression(paste("R"^"2")))
Model 3 had the highest in-sample \(R^2\), and now it has the worst
out-of-sample \(R^2\). It’s even
negative!
Bottom line: Model 3 is over-fitting. It is capturing patterns in
the in-sample data that do not generalize to the out-of-sample data.
This is why it does such a poor job at predicting.
Models 1 and 2 have basically the same out of sample \(R^2\).
This means favoring the simpler models. Model 1, being the
simplest, and tied for the best predictive performance is the
winner.
Predict
Here we use model 1 to predict the probability of default for a
certain customer with a specific profile: a male, senior citizen without
a partner or dependents, etc. See below.
newdata = data.frame(gender = "Male", SeniorCitizen=as.factor(1),Partner="No",Dependents="No", tenure=72,PhoneService="Yes",MultipleLines="No", InternetService="DSL", OnlineSecurity="No", OnlineBackup="No", DeviceProtection="No", TechSupport="Yes", StreamingTV="Yes", StreamingMovies="No", Contract="One year", PaperlessBilling="No", PaymentMethod="Mailed check", MonthlyCharges=30,TotalCharges=1)
predict(model_1,newdata,type="response")
## 1
## 0.0166
The probability of churn is low.
Holdout sample
Now we look at how well model 1 performs on one holdout sample,
holdout_telco.csv.
The churn rate we see in the holdout sample, 0.274, is close to that
in the estimation sample we used earlier, 0.266.
Now we use the model estimated on the other data to make predictions
on this new data. Note that our predicted probabilities lie between 0
and 1, whereas our data are binary. We can get the predictions for each
customer and graph them with the 0/1 churn decisions.
# predicted x'beta part of
xb <- predict(model_1, type = "link", newdata=holdout_telco)
# the predicted probability
prob <- predict(model_1, type = "response", newdata=holdout_telco)
head(cbind(xb,prob)) %>%
kbl() %>%
kable_styling()
xb
|
prob
|
-3.715
|
0.024
|
-0.857
|
0.298
|
-1.460
|
0.188
|
-1.942
|
0.125
|
-5.194
|
0.006
|
-0.419
|
0.397
|
# order customers from least likely to churn (according to model) to most likely
ind <- order(prob)
Plot
par(mai=c(.9,.8,.2,.2))
plot(xb[ind],holdout_telco$Churn[ind], pch=4,cex=0.3,col="blue", xlab="x'beta",ylab="P(Churn) on holdout data")
lines(x=xb[ind], y=prob[ind], col="red", lwd=2)
legend('left',legend=c("actual", "predicted (model 1)"),col=c("blue","red"), pch=c(1,NA),lty=c(NA,1), lwd=c(NA,2))
Confusion matrix
We can also classify predictions by turning them into 0’s
and 1’s. If \(\hat{p}_i > 0.5, \;
\textrm{pred} = 1\) otherwise 0.
confusion_matrix <- (table(holdout_telco$Churn, prob > 0.5))
confusion_matrix <- as.data.frame.matrix(confusion_matrix)
colnames(confusion_matrix) <- c("No", "Yes")
confusion_matrix$Percentage_Correct <- confusion_matrix[1,]$No/(confusion_matrix[1,]$No+confusion_matrix[1,]$Yes)*100
confusion_matrix[2,]$Percentage_Correct <- confusion_matrix[2,]$Yes/(confusion_matrix[2,]$No+confusion_matrix[2,]$Yes)*100
print(confusion_matrix)
## No Yes Percentage_Correct
## 0 1421 92 93.9
## 1 331 239 41.9
cat('Overall Percentage:', (confusion_matrix[1,1]+confusion_matrix[2,2])/nrow(holdout_telco)*100)
## Overall Percentage: 79.7
ROC curves
par(mai=c(.9,.8,.2,.2))
plot(roc(holdout_telco$Churn, prob), print.auc=TRUE,
col="black", lwd=1, main="ROC curve", xlab="Specificity: true negative rate", ylab="Sensitivity: true positive rate", xlim=c(1,0))
text(confusion_matrix$Percentage_Correct[[1]]/100, confusion_matrix$Percentage_Correct[[2]]/100, ".5 threshold")
abline(h=confusion_matrix$Percentage_Correct[[2]]/100, col="red",lwd=.3)
abline(v=confusion_matrix$Percentage_Correct[[1]]/100, col="red",lwd=.3)
Lift curves
Lift is a common measure in marketing of model performance. The lift
asks how much more likely are customers in the top \(k^{\textrm{th}}\) decile to churn compared
to the average.
ntiles <- function(x, bins) {
quantiles = seq(from=0, to = 1, length.out=bins+1)
cut(ecdf(x)(x),breaks=quantiles, labels=F)
}
# create deciles
prob_decile = ntiles(prob, 10)
# prob, decile and actual
pred<-data.frame(cbind(prob,prob_decile, holdout_telco$Churn))
colnames(pred)<-c("predicted","decile", "actual")
# create lift table by decile
# average churn rate by decile
# lift is the actual churn rate in the decile divided by average overall churn rate
lift_table<-pred %>% group_by(decile) %>% summarize(actual_churn = mean(actual), lift = actual_churn/rbar_ho, n_customers=n()) %>% arrange(desc(decile)) %>% mutate(cum_customers=cumsum(n_customers)) %>% mutate(cum_lift=cumsum(actual_churn)/sum(actual_churn)*100)
head(lift_table) %>%
kbl() %>%
kable_styling()
decile
|
actual_churn
|
lift
|
n_customers
|
cum_customers
|
cum_lift
|
10
|
0.785
|
2.868
|
209
|
209
|
28.7
|
9
|
0.591
|
2.161
|
208
|
417
|
50.3
|
8
|
0.404
|
1.476
|
208
|
625
|
65.1
|
7
|
0.311
|
1.137
|
209
|
834
|
76.5
|
6
|
0.245
|
0.896
|
208
|
1042
|
85.4
|
5
|
0.139
|
0.510
|
208
|
1250
|
90.5
|
Customers in the top decile are the top 10% most likely to churn
according to our model. The top decile lift is 2.868. Customers
in the top decile are 2.868 times more likely to actually churn
than the average customer.
The rightmost column shows the cumulative lift. The cumulative lift
for the \(k\) decile is the percentage
of all churners accounted for cumulatively by the first \(k\) deciles. The first decile contains 209%
of all churners in the data set (in total there are 570 churners in the
holdout dataset).
The cumulative lift of decile 2 is 208% of all churners are in the
top 2 deciles. In the bottom most deciles there are barely any churners,
so the cumulative lift increases little or not at all.
We can graph this out below. The top three deciles account for 208%
of all churners. We can use this to compare models. The higher the lift
for a given decile, the better the model. A straight line, where we
randomly sorted customers instead of using a model, is the naive
model.
# order from highest to smallest in terms of prob
# percentage of churners from beginning to end.
pred<-pred %>% arrange(desc(predicted)) %>% mutate(prop_churn = cumsum(actual)/sum(actual)*100, prop_cust = seq(nrow(pred))/nrow(pred)*100)
head(pred) %>%
kbl() %>%
kable_styling()
|
predicted
|
decile
|
actual
|
prop_churn
|
prop_cust
|
430
|
0.840
|
10
|
1
|
0.175
|
0.048
|
1887
|
0.840
|
10
|
1
|
0.351
|
0.096
|
320
|
0.821
|
10
|
1
|
0.526
|
0.144
|
811
|
0.820
|
10
|
1
|
0.702
|
0.192
|
179
|
0.817
|
10
|
1
|
0.877
|
0.240
|
88
|
0.814
|
10
|
1
|
1.053
|
0.288
|
# Plotting percentage of churners as a function of percentage of customers
par(mai=c(.9,.8,.2,.2))
plot(pred$prop_cust,pred$prop_churn,type="l",xlab="% of customers targeted using model",ylab="% of churners accounted for",xlim = c(0,100), ,ylim = c(0,100),col="blue")
legend('topleft', legend=c("Naive", "Logistic"), col=c("red", "blue"), lty=1:1, cex=0.8)
abline(a=0,b=1,col="red")
points(x=30, y= lift_table$cum_lift[3], pch=4, col="red", cex=2, lwd=2)
text(x = 28,y= lift_table$cum_lift[3]+5, paste(round(lift_table$cum_lift[3],0), "%" ))
Selecting deciles to target
Once we have used the model to put customers in the right decile,
targeting is simple. We calculate the profit from each n-tile and target
customers who are in the profitable tiles. We will use the proactive
churn framework from Blattberg, Kim and Neslin to calculate expected
profits. This approach takes into account the actual proportion of
churners as identified by the model.
The key parameter is \(\beta_K\),
the proportion of churners in the top \(K\) deciles contacted.
\[
\beta_K = \frac{\sum_{k=1}^{K} \; r_k \, n_k}{\sum_{k=1}^{K} \; n_k}
\quad \textrm{where} \; K = 1, 2, .. \dots, 10
\] We calculate \(\beta\), the
probability that a targeted customer is a churner, by taking the
cumulative proportion of churners in the top \(k\) deciles.
gamma = 0.1 # probability that customer is rescued if he or she is a churner
LTV = 500 # lifetime value of rescued customer
delta = 50 # cost of incentive
c = 0.50 # cost of contact
# re-order lift from highest to lowest
# add columns to our lift table
profit_table<-lift_table %>% mutate(
cum_prop_churners = cumsum(actual_churn*n_customers)/cum_customers,
profit = cum_customers*((gamma*LTV+delta*(1-gamma))*cum_prop_churners-delta-c),
decile=11-decile)
head(profit_table) %>%
kbl() %>%
kable_styling()
decile
|
actual_churn
|
lift
|
n_customers
|
cum_customers
|
cum_lift
|
cum_prop_churners
|
profit
|
1
|
0.785
|
2.868
|
209
|
209
|
28.7
|
0.785
|
5026
|
2
|
0.591
|
2.161
|
208
|
417
|
50.3
|
0.688
|
6206
|
3
|
0.404
|
1.476
|
208
|
625
|
65.1
|
0.594
|
3682
|
4
|
0.311
|
1.137
|
209
|
834
|
76.5
|
0.523
|
-697
|
5
|
0.245
|
0.896
|
208
|
1042
|
85.4
|
0.467
|
-6356
|
6
|
0.139
|
0.510
|
208
|
1250
|
90.5
|
0.413
|
-14105
|
par(mai=c(.9,.8,.2,.2))
bp<-barplot(profit_table$profit ~ profit_table$decile, main="expected profits by # of deciles targeted", xlab="# deciles targeted", ylab="expected profits")
We see from the table below that given this model, the profit
maximizing number of deciles to target is the top 2.
LS0tDQp0aXRsZTogIlR1dG9yaWFsIDM6IExvZ2l0IFJlZ3Jlc3Npb25zIg0KYXV0aG9yOiAiRGFuaWVsIFJlZGVsIg0KZGF0ZTogIjIwMjItMTEtMDciDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCi0tLQ0KDQojIyMgSW50cm9kdWN0aW9uDQoNCkxvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgdGhlIG1vc3QgY29tbW9ubHkgdXNlZCBzdGF0aXN0aWNhbCB0ZWNobmlxdWUgZm9yIGJpbmFyeSByZXNwb25zZSBkYXRhLiBNYW55IG1hcmtldGluZyBhcHBsaWNhdGlvbnMgYXJlIGNvbmNlcm5pbmcgYmluYXJ5IGNvbnN1bWVyIGRlY2lzaW9uczoNCg0KLSAgIGRvZXMgYSBjb25zdW1lciByZXNwb25kIG9yIG5vdCByZXNwb25kIHRvIG1hcmtldGluZz8NCi0gICBkbyB0aGV5IHN1YnNjcmliZSBvciBub3Qgc3Vic2NyaWJlPw0KLSAgIGRvIHRoZXkgY2h1cm4gb3Igbm90IGNodXJuPw0KDQpXZSdsbCB1c2UgYSBkYXRhIHNldCBvbiBjdXN0b21lciBjaHVybiBmb3IgYSB0ZWxlY29tbXVuaWNhdGlvbnMgY29tcGFueSB3aXRoIHNldmVyYWwgZGlmZmVyZW50IHNlcnZpY2VzLiBXZSdsbCB1c2UgZGVtb2dyYXBoaWMsIHNlcnZpY2UgdXNhZ2UsIGFuZCBjdXN0b21lciBoaXN0b3J5IHRvIHByZWRpY3QgY2h1cm4uIFdlIHRoZW4gYXBwbHkgdGhpcyBtb2RlbCB0byBhIG5ldywgaG9sZG91dCBzZXQgb2YgY3VzdG9tZXJzLiBXZSBjYWxjbGF0ZSB0aGUgY29uZnVzaW9uIG1hdHJpeCwgdGhlIGxpZnQgdGFibGUsIGFuZCB1c2UgaXQgdG8gZG8gdGFyZ2V0ZWQgcHJvYWN0aXZlIGNodXJuIHNlbGVjdGlvbi4NCg0KIyMjIEluc3RhbGxpbmcgdGhlIHBhY2thZ2VzIGFuZCBsb2FkaW5nIHRoZSBkYXRhDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRX0NCiMgaW5zdGFsbC5wYWNrYWdlcygicFJvYyIpDQojIGluc3RhbGwucGFja2FnZXMoInBsb3RyaXgiKSAgIA0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocFJPQykNCmxpYnJhcnkocGxvdHJpeCkgIA0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KDQojIHNldCB3b3JraW5nIGRpcmVjdG9yeSANCnRlbGNvIDwtIHJlYWRfY3N2KCJ0ZWxjby5jc3YiKQ0KdGVsY29faG9sZG91dCA8LSByZWFkX2NzdigidGVsY29faG9sZG91dC5jc3YiKQ0KDQpvcHRpb25zKCJzY2lwZW4iPTIwMCwgImRpZ2l0cyI9MykNCmBgYA0KDQojIyMgSW5zcGVjdGluZyB0aGUgZGF0YQ0KDQpMZXQncyBnZXQgcmlkIG9mIHRoZSBJRCBjb2x1bW4sIHNpbmNlIHdlIG5ldmVyIG5lZWQgdG8gdXNlIGl0LiBXZSdsbCBtYWtlIHNlbmlvciBjaXRpemVuIGEgZmFjdG9yIHZhcmlhYmxlLCBhbmQgcmVjb2RlIHRvdGFsIGNoYXJnZXMgc28gdGhhdCBpdCdzIGluIHRob3VzYW5kcyBvZiBkb2xsYXJzLiBXZSBhbHNvIG5lZWQgdG8gcmVjb2RlIENodXJuIGZvciB5ZXMvbm8gdG8gMC8xLg0KDQpgYGB7cn0NCiMgZHJvcCB0aGUgSUQgY29sdW1uLCBtYWtlIHNlbmlvciBjaXRpemVuIGEgZmFjdG9yIHZhcmlhYmxlLCBhbmQgZGl2aWRlIHRvdGFsY2hhcmdlcyBieSAxMDAwDQp0ZWxjbyA8LSB0ZWxjb1stYygxKV0NCnRlbGNvJFNlbmlvckNpdGl6ZW48LWFzLmZhY3Rvcih0ZWxjbyRTZW5pb3JDaXRpemVuKQ0KdGVsY28kVG90YWxDaGFyZ2VzPC10ZWxjbyRUb3RhbENoYXJnZXMvMTAwMA0KDQojIENoYW5nZSBDaHVybiBmcm9tICJubyIgInllcyIgdG8gMCAxDQp0ZWxjbyA8LSB0ZWxjbyAlPiUNCiAgICAgIG11dGF0ZShDaHVybiA9IGlmZWxzZShDaHVybiA9PSAiTm8iLDAsMSkpDQpgYGANCg0KIyMjIENodXJuDQoNCldoYXQgZnJhY3Rpb24gb2YgY3VzdG9tZXJzIGNodXJuIChxdWl0KT8gVGhpcyBpcyB0aGUgZGVwZW5kZW50IHZhcmlhYmxlIHdlIHdhbnQgdG8gcHJlZGljdC4gV2UgbmVlZCB0byB1c2UgdGhlICJhcy5udW1lcmljIiBmdW5jdGlvbiB0byB0cmFuc2Zvcm0gaXQgZnJvbSBhIGZhY3RvciB2YXJpYWJsZSB0byBhIDAvMSBjb250aW51b3VzIHZhcmlhYmxlIGluIFIuIFdlIHJlcG9ydCB0aGUgYXZlcmFnZSBjaHVybiByYXRlIG9mIHRoZSBjdXN0b21lciBiZWxvdy4NCg0KYGBge3J9DQpzdW1tYXJ5KHRlbGNvJENodXJuKQ0KcmJhciA8LSBtZWFuKHRlbGNvJENodXJuKQ0KYGBgDQoNClRoZSBhdmVyYWdlIGNodXJuIHJhdGUgaW4gdGhlIGN1c3RvbWVyIGJhc2UgaXMgYHIgcm91bmQocmJhciwzKWAuDQoNCiMjIyBUZW51cmUNCg0KT25lIGltcG9ydGFudCBkcml2ZXIgb2YgY2h1cm4gaXMgbGlrZWx5IHRvIGJlICoqdGVudXJlKiosIGhvdyBsb25nIGEgY3VzdG9tZXIgaGFzIGJlZW4gYSBjdXN0b21lciBmb3IuIFdlIGNhbiBzZWUgYmVsb3cgdGhhdCB0aGVyZSBpcyBhIHNwaWtlIGF0IDEsIG1hbnkgY3VzdG9tZXJzIGp1c3Qgc3RhcnRlZCwgYW5kIGEgc21hbGxlciBwZWFrIGF0IDcyLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnBhcihtYWk9YyguOSwuOCwuMiwuMikpDQpoaXN0KHRlbGNvJHRlbnVyZSwgbWFpbiA9ICIiLCB4bGFiPSJUZW51cmUgKCMgbW9udGhzIGEgY3VzdG9tZXIpIiwgYnJlYWtzID0gNzEpDQpgYGANCg0KIyMjIyBDaHVybiBSYXRlIFZhcmlhdGlvbg0KDQpIb3cgZG9lcyB0aGUgcmF0ZSBjaHVybiB2YXJ5IGJ5IHRlbnVyZT8gV2UgY3JlYXRlIGEgZGF0YXNldCBvZiBsZW5ndGggNzIsIG9uZSBmb3IgZWFjaCBsZXZlbCBvZiB0ZW51cmUuIFdlIGNhbGN1bGF0ZSB0aGUgcHJvcG9ydGlvbiBjaHVybmluZywgbnVtYmVyIG9mIGNodXJuZXJzIChuX2NodXJuKSwgbnVtYmVyIG9mIGN1c3RvbWVycyBpbiB0aGUgdGVudXJlIGdyb3VwLCB0aGUgc3RhbmRhcmQgZXJyb3Igb2YgdGhlIHByb3BvcnRpb24gY2h1cm5pbmcgKGRpc2N1c3NlZCBpbiBwcmV2aW91cyBsZWN0dXJlcyksIGFuZCB0aGUgbG93ZXIgYW5kIHVwcGVyIGNvbmZpZGVuY2UgaW50ZXJ2YWxzLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCmNodXJuX3RlbnVyZSA8LSB0ZWxjbyAlPiUgDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogIGdyb3VwX2J5KHRlbnVyZSkgJT4lIA0KICBzdW1tYXJpemUodGVudXJlPW1lYW4odGVudXJlKSwgDQogICAgICAgICAgICBwX2NodXJuPW1lYW4oQ2h1cm4pLCANCiAgICAgICAgICAgIG5fY2h1cm5lcnM9c3VtKENodXJuKSwgbj1uKCksIA0KICAgICAgICAgICAgcF9jaHVybl9zZT0gc3FydCgocF9jaHVybikqKDEtcF9jaHVybikvbikpICU+JSANCiAgbXV0YXRlKGxvd2VyX0NJX3BjaHVybiA9IHBfY2h1cm4gLSAxLjk2KnBfY2h1cm5fc2UsICMjIENJIG9mIGNodXJuIGJ5IHRlbnVyZS1sZXYuDQogICAgICAgICB1cHBlcl9DSV9wY2h1cm4gPSBwX2NodXJuICsgMS45NipwX2NodXJuX3NlKSANCmhlYWQoY2h1cm5fdGVudXJlKSAlPiUgDQogIGtibCgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpgYGB7cn0NCnBhcihtYWk9YyguOSwuOCwuMiwuMikpDQpwbG90KHggPSBjaHVybl90ZW51cmUkdGVudXJlLCB5ID0gY2h1cm5fdGVudXJlJHBfY2h1cm4sIG1haW49IlByb3BvcnRpb24gb2YgY3VzdG9tZXJzIHdobyBjaHVybiBieSB0ZW51cmUiLCB4bGFiPSJUZW51cmUgKCMgbW9udGhzIGEgY3VzdG9tZXIpIiwgeWxhYj0icHJvcG9ydGlvbiBvZiBjdXN0b21lciBjaHVybmluZyIpDQpgYGANCg0KVGhlIGZpZ3VyZSBzaG93cyBhIGNsZWFyIG5lZ2F0aXZlIHJlbGF0aW9uc2hpcDogdGhlIGxvbmdlciB0aGUgY3VzdG9tZXIgaGFzIGJlZW4gYSBjdXN0b21lciwgdGhlIGxvd2VyIHRoZSBwcm9iYWJpbGl0eSBvZiBjaHVybiAoY2h1cm4gcmF0ZSkuDQoNCiMjIyBFc3RpbWF0aW5nIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uDQoNCi0gICAqKk1vZGVsIDAqKiBpcyB0aGUgc2ltcGxlc3Q6IFRoZSBvbmx5IHZhcmlhYmxlIGlzIHRlbnVyZSBhbmQgaXQgaXMgdHJlYXRlZCBhcyBhIGNvbnRpbnVvdXMgdmFyaWFibGUuDQoNCmBgYHtyfQ0KIyBmaXQgDQptb2RlbF8wIDwtIGdsbShDaHVybiB+IHRlbnVyZSwgZGF0YT10ZWxjbywgZmFtaWx5ID0gYmlub21pYWwobGluaz0ibG9naXQiKSkNCg0KIyBzaG93IHVzIGNvZWZmaWNpZW50cyBhbmQgb3RoZXIgbW9kZWwgZml0IHN0YXRpc3RpY3MNCnN1bW1hcnkobW9kZWxfMCkNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmV4cCgtMC4wMzkwMSktMQ0KYGBgDQoNCkludGVycHJldGF0aW9uOiBIYXZpbmcgMSBhZGRpdGlvbmFsIHVuaXQgb2YgdGVudXJlIG9mICoqZGVjcmVhc2VzKiogdGhlIG9kZHMgb2YgY2h1cm4gYnkgYDAuMDM4M2BvciBieSBgMy44JWAuDQoNCioqUGxvdCoqOiBDb21wYXJlIG9ic2VydmVkIHByb3BvcnRpb24gb2YgY2h1cm4gYnkgdGVudXJlIGNhbGN1bGF0ZWQgc2VwYXJhdGVseSAqZm9yIGVhY2ggbGV2ZWwgb2YgdGVudXJlKjsgd2l0aCBtb2RlbCBwcmVkaWN0aW9ucy4NCg0KSSdtIGp1c3QgY3JlYXRpbmcgYSBuZXcgZGF0YSB3aXRoIHRoZSByZWdyZXNzaW9uIHJlc3VsdHM6DQoNCmBgYHtyfQ0KIyBjcmVhdGUgZGF0YSBzZXQgb2YgdGVudXJlIGZyb20gMSB0byA3Mg0KcGxvdGRhdCA8LSBkYXRhLmZyYW1lKHRlbnVyZT0oMTo3MikpDQoNCiMgcHV0IHByZWRpY3Rpb25zIGFuZCA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbHMgb2YgdGhvc2UgDQpwcmVkZGF0IDwtIHByZWRpY3QobW9kZWxfMCwNCiAgICAgICAgICAgICAgIHR5cGUgPSAibGluayIsDQogICAgICAgICAgICAgICBuZXdkYXRhPXBsb3RkYXQsICMjIFByZWRpY3Rpb24gYnkgZWFjaCBsZXZlbCBvZiB0ZW51cmUNCiAgICAgICAgICAgICAgIHNlLmZpdD1UUlVFKSAlPiUgDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogIG11dGF0ZSh0ZW51cmU9KDE6NzIpLCAjIyMgSEVSRSB3ZSBhcmUgcHV0dGluZyBvdXIgcmVzdWx0cyAjIyMjDQogIyBtb2RlbCBvYmplY3QgbW9kZWxfMCBoYXMgYSBjb21wb25lbnQgY2FsbGVkIGxpbmtpbnYgdGhhdCANCiAjIGlzIGEgZnVuY3Rpb24gdGhhdCBpbnZlcnRzIHRoZSBsaW5rIGZ1bmN0aW9uIG9mIHRoZSBHTE06DQogICAgICAgICBsb3dlciA9IG1vZGVsXzAkZmFtaWx5JGxpbmtpbnYoZml0IC0gMS45NipzZS5maXQpLCANCiAgICAgICAgIHBvaW50LmVzdGltYXRlID0gbW9kZWxfMCRmYW1pbHkkbGlua2ludihmaXQpLCANCiAgICAgICAgIHVwcGVyID0gbW9kZWxfMCRmYW1pbHkkbGlua2ludihmaXQgKyAxLjk2KnNlLmZpdCkpDQpgYGANCg0KKipGaW5hbCBQbG90Kio6DQoNCmBgYHtyfQ0KIyBwbG90IGFjdHVhbCB2cy4gbG9naXN0aWMgcmVncmVzc2lvbg0KcGFyKG1haT1jKC45LC44LC4yLC4yKSkNCnBsb3QoeCA9IGNodXJuX3RlbnVyZSR0ZW51cmUsIHkgPSBjaHVybl90ZW51cmUkcF9jaHVybiwgbWFpbj0iUHJvcG9ydGlvbiBvZiBjdXN0b21lcnMgd2hvIGNodXJuIGJ5IHRlbnVyZSIsIHhsYWI9IlRlbnVyZSAoIyBtb250aHMgYSBjdXN0b21lcikiLCB5bGFiPSJwcm9wb3J0aW9uIG9mIGN1c3RvbWVyIGNodXJuaW5nIikNCmxpbmVzKHg9cHJlZGRhdCR0ZW51cmUsIHk9cHJlZGRhdCRwb2ludC5lc3RpbWF0ZSwgY29sPSJyZWQiLCBsd2Q9MikNCmxlZ2VuZCgndG9wcmlnaHQnLGxlZ2VuZD1jKCJjaHVybiBwcm9wb3J0aW9uIiwgImxvZ2lzdGljIHJlZ3Jlc3Npb24iKSxjb2w9YygiYmxhY2siLCJyZWQiKSxwY2g9YygxLE5BKSxsdHk9YyhOQSwxKSwgbHdkPWMoTkEsMikpDQoNCmVxIDwtIHBhc3RlMCgibG9naXQocCkgPSAiLHJvdW5kKGNvZWYobW9kZWxfMClbMV0sNCksDQogICAgICAgICAgICAgaWZlbHNlKGNvZWYobW9kZWxfMClbMl08MCxyb3VuZChjb2VmKG1vZGVsXzApWzJdLDQpLA0KICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiKyIscm91bmQoY29lZihtb2RlbF8wKVsyXSw0KSkpLA0KICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiIHRlbnVyZSIpKQ0KIyBwdXRzIGVxdWF0aW9uIGluIGZpZ3VyZQ0KbXRleHQoZXEsIDEsLTMpDQpgYGANCg0KQ29tcGFyZSB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbHMgb2YgdGhlIG1vZGVsIHByZWRpY3Rpb25zICgqZGFzaGVkIHJlZCopIHRvIHRob3NlIGJ5IGRvaW5nIHRoZW0gc2VwYXJhdGVseSBmb3IgZWFjaCBsZXZlbCBvZiB0ZW51cmUuIFlvdSBjYW4gc2VlIHdlIGdldCBxdWl0ZSBhIHJlZHVjdGlvbiBpbiB1bmNlcnRhaW50eSBieSBoYXZpbmcgYSBtb2RlbCB0aGF0IHJlbGF0ZXMgdGhlc2UgcHJvcG9ydGlvbnMgdG8gZWFjaCBvdGhlci4NCg0KVGhlIGNvc3Qgb2Ygb3VyICoqbG93ZXIgZXJyb3IqKiBvciBlcnJvciByZWR1Y3Rpb24gaXMgKipoaWdoZXIgYmlhcyoqIC0tIGlmIHRoZSBtb2RlbCdzIGZ1bmN0aW9uYWwgZm9ybSBkZXZpYXRlcyBmcm9tIHRoZSBhY3R1YWwgcmVzcG9uc2UgcmF0ZS4gSW4gb3RoZXIgd29yZHMsIHdlIGhhdmUgcmVkdWNlZCB2YXJpYW5jZSwgYnV0IGF0IHRoZSBleHBlbnNlIG9mIGJpYXMuDQoNCmBgYHtyfQ0KcGFyKG1haT1jKC45LC44LC4yLC4yKSkNCnBsb3RDSSh4ID0gY2h1cm5fdGVudXJlJHRlbnVyZSwgICAgICAgICAgICAgICAjIHBsb3RyaXggcGxvdCB3aXRoIGNvbmZpZGVuY2UgaW50ZXJ2YWxzDQogICAgICAgeSA9IGNodXJuX3RlbnVyZSRwX2NodXJuLA0KICAgICAgIGxpID0gY2h1cm5fdGVudXJlJGxvd2VyX0NJX3BjaHVybiwNCiAgICAgICB1aSA9IGNodXJuX3RlbnVyZSR1cHBlcl9DSV9wY2h1cm4sIG1haW49IlByb3BvcnRpb24gb2YgY3VzdG9tZXJzIHdobyBjaHVybiBieSB0ZW51cmUiLCB4bGFiPSJUZW51cmUgKCMgbW9udGhzIGEgY3VzdG9tZXIpIiwgeWxhYj0icHJvcG9ydGlvbiBvZiBjdXN0b21lciBjaHVybmluZyIpDQoNCmxpbmVzKHg9cHJlZGRhdCR0ZW51cmUsIHk9cHJlZGRhdCRwb2ludC5lc3RpbWF0ZSwgY29sPSJyZWQiLCBsd2Q9MiwgdHlwZSA9ICJsIikNCmxpbmVzKHg9cHJlZGRhdCR0ZW51cmUsIHk9cHJlZGRhdCRsb3dlciwgY29sPSJyZWQiLCBsdHk9MiwgbHdkPTEsIHR5cGUgPSAibCIpDQpsaW5lcyh4PXByZWRkYXQkdGVudXJlLCB5PXByZWRkYXQkdXBwZXIsIGNvbD0icmVkIiwgbHR5PTIsIGx3ZD0xLCB0eXBlID0gImwiKQ0KDQpgYGANCg0KLSAgICoqTW9kZWwgMSoqIGlzIG1vcmUgY29tcGxleDogKipldmVyeSB2YXJpYWJsZSBpcyBpbmNsdWRlZCoqLCBub3QganVzdCB0ZW51cmU7IHRlbnVyZSBpcyB0cmVhdGVkIGFzIGEgY29udGludW91cyB2YXJpYWJsZSBhcyBiZWZvcmUuDQoNCmBgYHtyfQ0Kb3B0aW9ucyh3aWR0aCA9IDIwMCkNCm1vZGVsXzEgPC0gZ2xtKENodXJuIH4gLiAsIGRhdGE9dGVsY28sIGZhbWlseT0iYmlub21pYWwiKQ0KYGBgDQoNCi0gICAqKk1vZGVsIDIqKiBpcyBtb3JlIGNvbXBsZXg6IGxpa2UgTW9kZWwgMSwgZXhjZXB0IHRoYXQgKip0ZW51cmUgaXMgdHJlYXRlZCBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlKiouIEluIG90aGVyIHdvcmRzIHRoZXJlIGlzIGEgZHVtbXkgdmFyaWFibGUgZm9yIGV2ZXJ5IGxldmVsIG9mIHRlbnVyZSBidXQgb25lLiBUaGlzIHdheSwgd2UgY2FuICpmbGV4aWJseSBjYXB0dXJlIGEgcGF0dGVybiBiZXR3ZWVuKiB0ZW51cmUgYW5kIGNodXJuLiBJbiBSLCBhbGwgeW91IGhhdmUgdG8gZG8gaXMgd3JpdGUgKiphcy5mYWN0b3IodGVudXJlKSoqIGluc3RlYWQgb2YgKip0ZW51cmUqKi4NCg0KYGBge3J9DQptb2RlbF8yIDwtIGdsbShDaHVybiB+IC4gK2FzLmZhY3Rvcih0ZW51cmUpIC10ZW51cmUgLCBkYXRhPXRlbGNvLCBmYW1pbHk9ImJpbm9taWFsIikNCmBgYA0KDQotICAgKipNb2RlbCAyKiogaGFzIGByIGxlbmd0aChjb2VmKG1vZGVsXzIpKWAgY29lZmZpY2llbnRzLg0KLSAgICoqTW9kZWwgMyoqIGlzIHRoZSBtb3N0IGNvbXBsZXg6IGxpa2UgTW9kZWwgMiwgZXhjZXB0IHRoYXQgdGhlcmUgaXMgYW4gKippbnRlcmFjdGlvbiBiZXR3ZWVuIHBheW1lbnQgdHlwZSBhbmQgdGVudXJlKiouIE5vdGUgaW4gZ2VuZXJhbCBhbmQgaW50ZXJhY3Rpb24gaXMgdGhlIGNvZWZmaWNpZW50IG9uIHRoZSBwcm9kdWN0IG9mIHR3byB2YXJpYWJsZXMuDQoNCmBgYHtyfQ0KbW9kZWxfMyA8LSBnbG0oQ2h1cm4gfiAuICthcy5mYWN0b3IodGVudXJlKSphcy5mYWN0b3IoUGF5bWVudE1ldGhvZCkgLXRlbnVyZSAtUGF5bWVudE1ldGhvZCwgZGF0YT10ZWxjbywgZmFtaWx5PSJiaW5vbWlhbCIpDQpgYGANCg0KLSAgICoqTW9kZWwgMyoqIGhhcyBgciBsZW5ndGgoY29lZihtb2RlbF8zKSlgIGNvZWZmaWNpZW50cy4gTm90ZSBhIGxvdCBvZiB0aGVtIGhhdmUgbGFyZ2UgY29lZmZpY2llbnRzIGFuZCBsYXJnZSBzdGFuZGFyZCBlcnJvcnMuIElmIGEgdmFyaWFibGUgaXMgemVybyBhbG1vc3QgYWx3YXlzLCAodGVudXJlPT0zNClcKihQYXltZW50TWV0aG9kPT1FbGVjdHJvbmljIGNoZWNrKSwgdGhlcmUgaXMgbGl0dGxlIHZhcmlhdGlvbiB0byBlc3RpbWF0ZSB0aGUgY29lZmZpY2llbnQsIG1ha2luZyBpdCBsb29rIHVuc3RhYmxlLg0KDQotICAgU28sIHdlJ3ZlIGVzdGltYXRlZCAzIG1vZGVscyBlYWNoIG9uZSBpbmNyZWFzaW5nIGluIHRoZSBudW1iZXIgb2YgY29lZmZpY2llbnRzLiBMZXQncyBzZWUgaG93IHdlbGwgdGhleSBwcmVkaWN0Lg0KDQojIyMgRGV2aWFuY2UgYW5kIHByb3BvcnRpb24gb2YgZGV2aWFuY2UgZXhwbGFpbmVkIChSMikNCg0KRGV2aWFuY2UgaXMgYW4gZXJyb3IgbWVhc3VyZSwgJC0yIFxsbihcdGV4dHJte2xpa2VsaWhvb2R9KSQuIFdlIHdhbnQgaXQgdG8gYmUgYXMgc21hbGwgYXMgcG9zc2libGUuIFRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHJlc2lkdWFsIGFuZCB0aGUgbnVsbCBkZXZpYW5jZSB0aGVuIGdpdmVzIHVzIHNvbWUgc2Vuc2Ugb2YgaG93IHdlbGwgb3VyIG1vZGVsIGZpdHMgb3ZlcmFsbCwgdGFrZW4gdG9nZXRoZXIuDQoNCllvdSBjYW4gYWxzbyBsb29rIGF0IHRoZSBwcm9wb3J0aW9uIG9mIGRldmlhbmNlIGV4cGxhaW5lZCBieSB0aGUgdmFyaWFibGVzIGluIHRoZSBtb2RlbC4NCg0KJCQNClJeMiA9IFxmcmFje0RfMCAtIER9e0RfMH0gPSAxIC0gXGZyYWN7RH17RF8wfQ0KJCQNCg0KYGBge3J9DQptb2RlbHMgPC0gcGFzdGUwKCJtb2RlbF8iLCAwOjMpICMgbGlzdCBvZiBtb2RlbHMNCkQgPC0gc2FwcGx5KG1vZGVscywgZnVuY3Rpb24oeCkgZ2V0KHgpJGRldmlhbmNlKSAjIGdldCBkZXZpYW5jZSBEIGZvciBlYWNoDQpEMCA8LSBtb2RlbF8wJG51bGwuZGV2aWFuY2UgIyBEXzAgaXMgdGhlIHNhbWUgZm9yIGFsbCBtb2RlbHMNClIyIDwtIDEtRC9EMA0KYGBgDQoNCmBgYHtyfQ0KcGFyKG1haT1jKC45LC44LC4yLC4yKSkNCmJhcnBsb3QoUjIsIG5hbWVzLmFyZyA9IGMoIm1vZGVsIDAiLCJtb2RlbCAxIiwgIm1vZGVsIDIiLCAibW9kZWwgMyIpLCBtYWluPWV4cHJlc3Npb24ocGFzdGUoIkluLVNhbXBsZSBSIl4iMiIpKSwgeGxhYj0iTW9kZWwiLCB5bGFiPWV4cHJlc3Npb24ocGFzdGUoIlIiXiIyIikpKQ0KYGBgDQoNCk1vZGVscyAwLCAxLCAyIGFuZCAzIGFyZSBleHBsYWluaW5nIGByIHJvdW5kKFIyWzFdLDIpKjEwMGAlIGByIHJvdW5kKFIyWzJdLDIpKjEwMGAlLCBgciByb3VuZChSMlszXSwyKSoxMDBgJSBhbmQgYHIgcm91bmQoUjJbNF0sMikqMTAwYCUsIHJlc3BlY3RpdmVseSwgb2YgdGhlIGRldmlhbmNlIGluIGN1c3RvbWVyIGNodXJuLg0KDQojIyMgT3ZlcmZpdHRpbmcsIEstZm9sZCBvdXQgb2Ygc2FtcGxlDQoNCioqQnV0LCBpcyB0aGUgYmV0dGVyIHBlcmZvcm1hbmNlIG9mIG1vZGVsIGEgcmVzdWx0IG9mIG92ZXJmaXR0aW5nPyoqDQoNCldoYXQgd2UgcmVhbGx5IGNhcmUgYWJvdXQgaXMgYmVpbmcgYWJsZSB0byBwcmVkaWN0ICoqbmV3KiogZGF0YS4gVGhlIFIyIGFuZCBkZXZpYW5jZSBtZWFzdXJlcyBhcmUgYWxsIGFib3V0IGluLXNhbXBsZSwgbm90IG91dC1vZi1zYW1wbGUgZml0LiBTbyBpdCBkb2Vzbid0IHRlbGwgdXMgaG93IHdlbGwgb3VyIG1vZGVsIHBlcmZvcm1zIG9uIG90aGVyIGRhdGEuDQoNCldlIGNhbiBtaW1pYyB0aGUgcHJlc2VuY2Ugb2YgbmV3IGRhdGEgYnkgaG9sZGluZyBvdXQgcGFydCBvZiB0aGUgZGF0YS4NCg0KV2UgdXNlIEstZm9sZCBvdXQgb2Ygc2FtcGxlIHZhbGlkYXRpb24uDQoNCmBgYHtyLCBjYWNoZT1UUlVFfQ0KIyB5b3UgZG9uJ3QgbmVlZCB0byBrbm93IGhvdyB0byB3cml0ZSB0aGlzIGNvZGUuDQpzZXQuc2VlZCgxOTEwMykNCm4gPSBucm93KHRlbGNvKQ0KSyA9IDEwICMgIyBmb2xkcw0KZm9sZGlkID0gcmVwKDE6SywgZWFjaD1jZWlsaW5nKG4vSykpW3NhbXBsZSgxOm4pXQ0KIyBmb2xkaWRbMToxMF0NCk9PUyA8LSBkYXRhLmZyYW1lKG1vZGVsMD1yZXAoTkEsIEspLCBtb2RlbDE9cmVwKE5BLEspLCBtb2RlbDI9cmVwKE5BLEspLCBtb2RlbDM9cmVwKE5BLEspKQ0KDQoNCiMjIHByZWQgbXVzdCBiZSBwcm9iYWJpbGl0aWVzICgwPHByZWQ8MSkgZm9yIGJpbm9taWFsDQogIGRldmlhbmNlIDwtIGZ1bmN0aW9uKHksIHByZWQsIGZhbWlseT1jKCJnYXVzc2lhbiIsImJpbm9taWFsIikpew0KICAgIGZhbWlseSA8LSBtYXRjaC5hcmcoZmFtaWx5KQ0KICAgIGlmKGZhbWlseT09ImdhdXNzaWFuIil7DQogICAgICByZXR1cm4oIHN1bSggKHktcHJlZCleMiApICkNCiAgICB9ZWxzZXsNCiAgICAgIGlmKGlzLmZhY3Rvcih5KSkgeSA8LSBhcy5udW1lcmljKHkpPjENCiAgICAgIHJldHVybiggLTIqc3VtKCB5KmxvZyhwcmVkKSArICgxLXkpKmxvZygxLXByZWQpICkgKQ0KICAgIH0NCiAgfQ0KDQojIyBnZXQgbnVsbCBkZXZhaW5jZSB0b28sIGFuZCByZXR1cm4gUjINCiAgUjIgPC0gZnVuY3Rpb24oeSwgcHJlZCwgZmFtaWx5PWMoImdhdXNzaWFuIiwiYmlub21pYWwiKSl7DQogIGZhbSA8LSBtYXRjaC5hcmcoZmFtaWx5KQ0KICBpZihmYW09PSJiaW5vbWlhbCIpew0KICAgIGlmKGlzLmZhY3Rvcih5KSl7IHkgPC0gYXMubnVtZXJpYyh5KT4xIH0NCiAgfQ0KICBkZXYgPC0gZGV2aWFuY2UoeSwgcHJlZCwgZmFtaWx5PWZhbSkNCiAgZGV2MCA8LSBkZXZpYW5jZSh5LCBtZWFuKHkpLCBmYW1pbHk9ZmFtKQ0KICByZXR1cm4oMS1kZXYvZGV2MCkNCiAgfSAgDQoNCiMgdGhpcyBwYXJ0IHdpbGwgdGFrZSBzZXZlcmFsIG1pbnV0ZXMsIGZpdHRpbmcgMyBtb2RlbHMgSyB0aW1lcyBlYWNoDQogIA0KZm9yKGsgaW4gMTpLKXsNCiAgdHJhaW4gPSB3aGljaChmb2xkaWQhPWspICMgZGF0YSB1c2VkIHRvIHRyYWluDQogIA0KICAjIGZpdCByZWdyZXNzaW9ucw0KICBtb2RlbF8wPC0gZ2xtKENodXJuIH4gdGVudXJlLCBkYXRhPXRlbGNvW3RyYWluLF0sIGZhbWlseT0iYmlub21pYWwiKQ0KICBzdW1tYXJ5KG1vZGVsXzApDQogIA0KICBtb2RlbF8xIDwtIGdsbShDaHVybiB+IC4gLCBkYXRhPXRlbGNvW3RyYWluLF0sIGZhbWlseT0iYmlub21pYWwiKQ0KICBzdW1tYXJ5KG1vZGVsXzEpDQogIA0KICBtb2RlbF8yIDwtIGdsbShDaHVybiB+IC4gK2FzLmZhY3Rvcih0ZW51cmUpIC10ZW51cmUsIGRhdGE9dGVsY29bdHJhaW4sXSwgZmFtaWx5PSJiaW5vbWlhbCIpDQogIHN1bW1hcnkobW9kZWxfMikNCiAgDQogIG1vZGVsXzMgPC0gZ2xtKENodXJuIH4gLiArYXMuZmFjdG9yKHRlbnVyZSkqYXMuZmFjdG9yKFBheW1lbnRNZXRob2QpIC10ZW51cmUgLVBheW1lbnRNZXRob2QsIGRhdGE9dGVsY29bdHJhaW4sXSwgZmFtaWx5PSJiaW5vbWlhbCIpDQogIHN1bW1hcnkobW9kZWxfMykNCiAgDQogIA0KICAjIHByZWRpY3Qgb24gaG9sZG91dCBkYXRhICgtdHJhaW4pDQogIHByZWQwPC0gcHJlZGljdChtb2RlbF8wLCBuZXdkYXRhPXRlbGNvWy10cmFpbixdLCB0eXBlID0gInJlc3BvbnNlIikNCiAgcHJlZDE8LSBwcmVkaWN0KG1vZGVsXzEsIG5ld2RhdGE9dGVsY29bLXRyYWluLF0sIHR5cGUgPSAicmVzcG9uc2UiKQ0KICBwcmVkMjwtIHByZWRpY3QobW9kZWxfMiwgbmV3ZGF0YT10ZWxjb1stdHJhaW4sXSwgdHlwZSA9ICJyZXNwb25zZSIpDQogIHByZWQzPC0gcHJlZGljdChtb2RlbF8zLCBuZXdkYXRhPXRlbGNvWy10cmFpbixdLCB0eXBlID0gInJlc3BvbnNlIikNCiAgDQogICMgY2FsY3VsYXRlIFIyDQogIE9PUyRtb2RlbDBba108LVIyKHkgPSB0ZWxjbyRDaHVyblstdHJhaW5dLHByZWQ9cHJlZDAsIGZhbWlseT0iYmlub21pYWwiKQ0KICBPT1MkbW9kZWwxW2tdPC1SMih5ID0gdGVsY28kQ2h1cm5bLXRyYWluXSxwcmVkPXByZWQxLCBmYW1pbHk9ImJpbm9taWFsIikNCiAgT09TJG1vZGVsMltrXTwtUjIoeSA9IHRlbGNvJENodXJuWy10cmFpbl0scHJlZD1wcmVkMiwgZmFtaWx5PSJiaW5vbWlhbCIpDQogIE9PUyRtb2RlbDNba108LVIyKHkgPSB0ZWxjbyRDaHVyblstdHJhaW5dLHByZWQ9cHJlZDMsIGZhbWlseT0iYmlub21pYWwiKQ0KICANCiAgIyBwcmludCBwcm9ncmVzcw0KICBjYXQoaywgIiAgIikNCiAgICANCn0NCmBgYA0KDQpQbG90IFJlc3VsdHM6DQoNCmBgYHtyfQ0KcGFyKG1haT1jKC45LC44LC4yLC4yKSkgIA0KYm94cGxvdChPT1NbLDE6NF0sIGRhdGE9T09TLCBtYWluPWV4cHJlc3Npb24ocGFzdGUoIk91dC1vZi1TYW1wbGUgUiJeIjIiKSksDQogICAgICAgIHhsYWI9Ik1vZGVsIiwgeWxhYj1leHByZXNzaW9uKHBhc3RlKCJSIl4iMiIpKSkNCmBgYA0KDQotICAgTW9kZWwgMyBoYWQgdGhlIGhpZ2hlc3QgaW4tc2FtcGxlICRSXjIkLCBhbmQgbm93IGl0IGhhcyB0aGUgd29yc3Qgb3V0LW9mLXNhbXBsZSAkUl4yJC4gSXQncyBldmVuICoqbmVnYXRpdmUqKiENCg0KLSAgIEJvdHRvbSBsaW5lOiBNb2RlbCAzIGlzIG92ZXItZml0dGluZy4gSXQgaXMgY2FwdHVyaW5nIHBhdHRlcm5zIGluIHRoZSBpbi1zYW1wbGUgZGF0YSB0aGF0IGRvIG5vdCBnZW5lcmFsaXplIHRvIHRoZSBvdXQtb2Ytc2FtcGxlIGRhdGEuIFRoaXMgaXMgd2h5IGl0IGRvZXMgc3VjaCBhIHBvb3Igam9iIGF0IHByZWRpY3RpbmcuDQoNCi0gICBNb2RlbHMgMSBhbmQgMiBoYXZlIGJhc2ljYWxseSB0aGUgc2FtZSBvdXQgb2Ygc2FtcGxlICRSXjIkLg0KDQotICAgVGhpcyBtZWFucyBmYXZvcmluZyB0aGUgc2ltcGxlciBtb2RlbHMuIE1vZGVsIDEsIGJlaW5nIHRoZSBzaW1wbGVzdCwgYW5kIHRpZWQgZm9yIHRoZSBiZXN0IHByZWRpY3RpdmUgcGVyZm9ybWFuY2UgaXMgdGhlIHdpbm5lci4NCg0KIyMjIFByZWRpY3QNCg0KSGVyZSB3ZSB1c2UgbW9kZWwgMSB0byBwcmVkaWN0IHRoZSBwcm9iYWJpbGl0eSBvZiBkZWZhdWx0IGZvciBhIGNlcnRhaW4gY3VzdG9tZXIgd2l0aCBhIHNwZWNpZmljIHByb2ZpbGU6IGEgbWFsZSwgc2VuaW9yIGNpdGl6ZW4gd2l0aG91dCBhIHBhcnRuZXIgb3IgZGVwZW5kZW50cywgZXRjLiBTZWUgYmVsb3cuDQoNCmBgYHtyfQ0KbmV3ZGF0YSA9IGRhdGEuZnJhbWUoZ2VuZGVyID0gIk1hbGUiLCBTZW5pb3JDaXRpemVuPWFzLmZhY3RvcigxKSxQYXJ0bmVyPSJObyIsRGVwZW5kZW50cz0iTm8iLCB0ZW51cmU9NzIsUGhvbmVTZXJ2aWNlPSJZZXMiLE11bHRpcGxlTGluZXM9Ik5vIiwgSW50ZXJuZXRTZXJ2aWNlPSJEU0wiLCBPbmxpbmVTZWN1cml0eT0iTm8iLCBPbmxpbmVCYWNrdXA9Ik5vIiwgRGV2aWNlUHJvdGVjdGlvbj0iTm8iLCBUZWNoU3VwcG9ydD0iWWVzIiwgU3RyZWFtaW5nVFY9IlllcyIsIFN0cmVhbWluZ01vdmllcz0iTm8iLCBDb250cmFjdD0iT25lIHllYXIiLCBQYXBlcmxlc3NCaWxsaW5nPSJObyIsIFBheW1lbnRNZXRob2Q9Ik1haWxlZCBjaGVjayIsIE1vbnRobHlDaGFyZ2VzPTMwLFRvdGFsQ2hhcmdlcz0xKQ0KDQpwcmVkaWN0KG1vZGVsXzEsbmV3ZGF0YSx0eXBlPSJyZXNwb25zZSIpDQpgYGANCg0KVGhlIHByb2JhYmlsaXR5IG9mIGNodXJuIGlzIGxvdy4NCg0KIyMjIEhvbGRvdXQgc2FtcGxlDQoNCk5vdyB3ZSBsb29rIGF0IGhvdyB3ZWxsIG1vZGVsIDEgcGVyZm9ybXMgb24gb25lIGhvbGRvdXQgc2FtcGxlLCAqKmhvbGRvdXRfdGVsY28uY3N2KiouDQoNCmBgYHtyLCBpbmNsdWRlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQpob2xkb3V0X3RlbGNvIDwtIHJlYWRfY3N2KCJ0ZWxjb19ob2xkb3V0LmNzdiIpDQoNCiMgSUQgY29sdW1uIGRvbid0IG5lZWQgdG8gZHJvcC4NCiMgbWFrZSBzZW5pb3IgY2l0aXplbiBhIGZhY3RvciB2YXJpYWJsZSwgYW5kIGRpdmlkZSB0b3RhbGNoYXJnZXMgYnkgMTAwMA0KaG9sZG91dF90ZWxjbyRTZW5pb3JDaXRpemVuPC1hcy5mYWN0b3IoaG9sZG91dF90ZWxjbyRTZW5pb3JDaXRpemVuKQ0KaG9sZG91dF90ZWxjbyRUb3RhbENoYXJnZXM8LWhvbGRvdXRfdGVsY28kVG90YWxDaGFyZ2VzLzEwMDANCg0KIyBDaGFuZ2UgQ2h1cm4gZnJvbSAibm8iICJ5ZXMiIHRvIDAgMQ0KDQpob2xkb3V0X3RlbGNvIDwtIGhvbGRvdXRfdGVsY28gJT4lDQogICAgICBtdXRhdGUoQ2h1cm4gPSBpZmVsc2UoQ2h1cm4gPT0gIk5vIiwwLDEpKQ0Kbl9jaHVybmVyczwtc3VtKGhvbGRvdXRfdGVsY28kQ2h1cm4pDQpyYmFyX2hvIDwtIG1lYW4oaG9sZG91dF90ZWxjbyRDaHVybikNCmBgYA0KDQpUaGUgY2h1cm4gcmF0ZSB3ZSBzZWUgaW4gdGhlIGhvbGRvdXQgc2FtcGxlLCBgciByb3VuZChyYmFyX2hvLDMpYCwgaXMgY2xvc2UgdG8gdGhhdCBpbiB0aGUgZXN0aW1hdGlvbiBzYW1wbGUgd2UgdXNlZCBlYXJsaWVyLCBgciByYmFyYC4NCg0KTm93IHdlIHVzZSB0aGUgbW9kZWwgZXN0aW1hdGVkIG9uIHRoZSBvdGhlciBkYXRhIHRvIG1ha2UgcHJlZGljdGlvbnMgb24gdGhpcyBuZXcgZGF0YS4gTm90ZSB0aGF0IG91ciBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBsaWUgYmV0d2VlbiAwIGFuZCAxLCB3aGVyZWFzIG91ciBkYXRhIGFyZSBiaW5hcnkuIFdlIGNhbiBnZXQgdGhlIHByZWRpY3Rpb25zIGZvciBlYWNoIGN1c3RvbWVyIGFuZCBncmFwaCB0aGVtIHdpdGggdGhlIDAvMSBjaHVybiBkZWNpc2lvbnMuDQoNCmBgYHtyfQ0KIyBwcmVkaWN0ZWQgeCdiZXRhIHBhcnQgb2YgDQp4YiA8LSBwcmVkaWN0KG1vZGVsXzEsIHR5cGUgPSAibGluayIsIG5ld2RhdGE9aG9sZG91dF90ZWxjbykNCiMgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0eSANCnByb2IgPC0gcHJlZGljdChtb2RlbF8xLCB0eXBlID0gInJlc3BvbnNlIiwgbmV3ZGF0YT1ob2xkb3V0X3RlbGNvKQ0KaGVhZChjYmluZCh4Yixwcm9iKSkgJT4lIA0KICBrYmwoKSAlPiUNCiAga2FibGVfc3R5bGluZygpDQojIG9yZGVyIGN1c3RvbWVycyBmcm9tIGxlYXN0IGxpa2VseSB0byBjaHVybiAoYWNjb3JkaW5nIHRvIG1vZGVsKSB0byBtb3N0IGxpa2VseQ0KaW5kIDwtIG9yZGVyKHByb2IpDQpgYGANCg0KUGxvdA0KDQpgYGB7cn0NCnBhcihtYWk9YyguOSwuOCwuMiwuMikpDQpwbG90KHhiW2luZF0saG9sZG91dF90ZWxjbyRDaHVybltpbmRdLCBwY2g9NCxjZXg9MC4zLGNvbD0iYmx1ZSIsIHhsYWI9IngnYmV0YSIseWxhYj0iUChDaHVybikgb24gaG9sZG91dCBkYXRhIikNCmxpbmVzKHg9eGJbaW5kXSwgeT1wcm9iW2luZF0sIGNvbD0icmVkIiwgbHdkPTIpDQpsZWdlbmQoJ2xlZnQnLGxlZ2VuZD1jKCJhY3R1YWwiLCAicHJlZGljdGVkIChtb2RlbCAxKSIpLGNvbD1jKCJibHVlIiwicmVkIiksIHBjaD1jKDEsTkEpLGx0eT1jKE5BLDEpLCBsd2Q9YyhOQSwyKSkNCmBgYA0KDQojIyMgQ29uZnVzaW9uIG1hdHJpeA0KDQpXZSBjYW4gYWxzbyAqY2xhc3NpZnkqIHByZWRpY3Rpb25zIGJ5IHR1cm5pbmcgdGhlbSBpbnRvIDAncyBhbmQgMSdzLiBJZiAkXGhhdHtwfV9pID4gMC41LCBcOyBcdGV4dHJte3ByZWR9ID0gMSQgb3RoZXJ3aXNlIDAuDQoNCmBgYHtyfQ0KY29uZnVzaW9uX21hdHJpeCA8LSAodGFibGUoaG9sZG91dF90ZWxjbyRDaHVybiwgcHJvYiA+IDAuNSkpDQpjb25mdXNpb25fbWF0cml4IDwtIGFzLmRhdGEuZnJhbWUubWF0cml4KGNvbmZ1c2lvbl9tYXRyaXgpDQpjb2xuYW1lcyhjb25mdXNpb25fbWF0cml4KSA8LSBjKCJObyIsICJZZXMiKQ0KY29uZnVzaW9uX21hdHJpeCRQZXJjZW50YWdlX0NvcnJlY3QgPC0gY29uZnVzaW9uX21hdHJpeFsxLF0kTm8vKGNvbmZ1c2lvbl9tYXRyaXhbMSxdJE5vK2NvbmZ1c2lvbl9tYXRyaXhbMSxdJFllcykqMTAwDQpjb25mdXNpb25fbWF0cml4WzIsXSRQZXJjZW50YWdlX0NvcnJlY3QgPC0gY29uZnVzaW9uX21hdHJpeFsyLF0kWWVzLyhjb25mdXNpb25fbWF0cml4WzIsXSRObytjb25mdXNpb25fbWF0cml4WzIsXSRZZXMpKjEwMA0KDQpwcmludChjb25mdXNpb25fbWF0cml4KQ0KYGBgDQoNCmBgYHtyfQ0KY2F0KCdPdmVyYWxsIFBlcmNlbnRhZ2U6JywgKGNvbmZ1c2lvbl9tYXRyaXhbMSwxXStjb25mdXNpb25fbWF0cml4WzIsMl0pL25yb3coaG9sZG91dF90ZWxjbykqMTAwKQ0KYGBgDQoNCiMjIyBST0MgY3VydmVzDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KcGFyKG1haT1jKC45LC44LC4yLC4yKSkNCnBsb3Qocm9jKGhvbGRvdXRfdGVsY28kQ2h1cm4sIHByb2IpLCBwcmludC5hdWM9VFJVRSwgDQogICAgIGNvbD0iYmxhY2siLCBsd2Q9MSwgbWFpbj0iUk9DIGN1cnZlIiwgeGxhYj0iU3BlY2lmaWNpdHk6IHRydWUgbmVnYXRpdmUgcmF0ZSIsIHlsYWI9IlNlbnNpdGl2aXR5OiB0cnVlIHBvc2l0aXZlIHJhdGUiLCB4bGltPWMoMSwwKSkNCnRleHQoY29uZnVzaW9uX21hdHJpeCRQZXJjZW50YWdlX0NvcnJlY3RbWzFdXS8xMDAsIGNvbmZ1c2lvbl9tYXRyaXgkUGVyY2VudGFnZV9Db3JyZWN0W1syXV0vMTAwLCAiLjUgdGhyZXNob2xkIikNCmFibGluZShoPWNvbmZ1c2lvbl9tYXRyaXgkUGVyY2VudGFnZV9Db3JyZWN0W1syXV0vMTAwLCBjb2w9InJlZCIsbHdkPS4zKQ0KYWJsaW5lKHY9Y29uZnVzaW9uX21hdHJpeCRQZXJjZW50YWdlX0NvcnJlY3RbWzFdXS8xMDAsIGNvbD0icmVkIixsd2Q9LjMpDQpgYGANCg0KIyMjIExpZnQgY3VydmVzDQoNCkxpZnQgaXMgYSBjb21tb24gbWVhc3VyZSBpbiBtYXJrZXRpbmcgb2YgbW9kZWwgcGVyZm9ybWFuY2UuIFRoZSBsaWZ0IGFza3MgaG93IG11Y2ggbW9yZSBsaWtlbHkgYXJlIGN1c3RvbWVycyBpbiB0aGUgdG9wICRrXntcdGV4dHJte3RofX0kIGRlY2lsZSB0byBjaHVybiBjb21wYXJlZCB0byB0aGUgYXZlcmFnZS4NCg0KYGBge3J9DQpudGlsZXMgPC0gZnVuY3Rpb24oeCwgYmlucykgew0KICBxdWFudGlsZXMgPSBzZXEoZnJvbT0wLCB0byA9IDEsIGxlbmd0aC5vdXQ9YmlucysxKQ0KICBjdXQoZWNkZih4KSh4KSxicmVha3M9cXVhbnRpbGVzLCBsYWJlbHM9RikNCn0NCiMgY3JlYXRlIGRlY2lsZXMNCnByb2JfZGVjaWxlID0gbnRpbGVzKHByb2IsIDEwKQ0KDQojIHByb2IsIGRlY2lsZSBhbmQgYWN0dWFsDQpwcmVkPC1kYXRhLmZyYW1lKGNiaW5kKHByb2IscHJvYl9kZWNpbGUsIGhvbGRvdXRfdGVsY28kQ2h1cm4pKQ0KY29sbmFtZXMocHJlZCk8LWMoInByZWRpY3RlZCIsImRlY2lsZSIsICJhY3R1YWwiKQ0KDQojIGNyZWF0ZSBsaWZ0IHRhYmxlIGJ5IGRlY2lsZQ0KIyBhdmVyYWdlIGNodXJuIHJhdGUgYnkgZGVjaWxlDQoNCiMgbGlmdCBpcyB0aGUgYWN0dWFsIGNodXJuIHJhdGUgaW4gdGhlIGRlY2lsZSBkaXZpZGVkIGJ5IGF2ZXJhZ2Ugb3ZlcmFsbCBjaHVybiByYXRlDQogIA0KbGlmdF90YWJsZTwtcHJlZCAlPiUgZ3JvdXBfYnkoZGVjaWxlKSAlPiUgIHN1bW1hcml6ZShhY3R1YWxfY2h1cm4gPSBtZWFuKGFjdHVhbCksIGxpZnQgPSBhY3R1YWxfY2h1cm4vcmJhcl9obywgbl9jdXN0b21lcnM9bigpKSAlPiUgYXJyYW5nZShkZXNjKGRlY2lsZSkpICU+JSBtdXRhdGUoY3VtX2N1c3RvbWVycz1jdW1zdW0obl9jdXN0b21lcnMpKSAlPiUgbXV0YXRlKGN1bV9saWZ0PWN1bXN1bShhY3R1YWxfY2h1cm4pL3N1bShhY3R1YWxfY2h1cm4pKjEwMCkNCg0KaGVhZChsaWZ0X3RhYmxlKSAlPiUgDQogIGtibCgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpDdXN0b21lcnMgaW4gdGhlIHRvcCBkZWNpbGUgYXJlIHRoZSB0b3AgMTAlIG1vc3QgbGlrZWx5IHRvIGNodXJuICphY2NvcmRpbmcgdG8gb3VyIG1vZGVsKi4gVGhlIHRvcCBkZWNpbGUgbGlmdCBpcyBgciBsaWZ0X3RhYmxlW1sxLDNdXWAuIEN1c3RvbWVycyBpbiB0aGUgdG9wIGRlY2lsZSBhcmUgYHIgbGlmdF90YWJsZVtbMSwzXV1gIHRpbWVzIG1vcmUgbGlrZWx5IHRvICphY3R1YWxseSogY2h1cm4gdGhhbiB0aGUgYXZlcmFnZSBjdXN0b21lci4NCg0KVGhlIHJpZ2h0bW9zdCBjb2x1bW4gc2hvd3MgdGhlIGN1bXVsYXRpdmUgbGlmdC4gVGhlIGN1bXVsYXRpdmUgbGlmdCBmb3IgdGhlICRrJCBkZWNpbGUgaXMgdGhlIHBlcmNlbnRhZ2Ugb2YgYWxsIGNodXJuZXJzIGFjY291bnRlZCBmb3IgY3VtdWxhdGl2ZWx5IGJ5IHRoZSBmaXJzdCAkayQgZGVjaWxlcy4gVGhlIGZpcnN0IGRlY2lsZSBjb250YWlucyBgciByb3VuZChsaWZ0X3RhYmxlW1sxLDRdXSwwKWAlIG9mIGFsbCBjaHVybmVycyBpbiB0aGUgZGF0YSBzZXQgKGluIHRvdGFsIHRoZXJlIGFyZSBgciBuX2NodXJuZXJzYCBjaHVybmVycyBpbiB0aGUgaG9sZG91dCBkYXRhc2V0KS4NCg0KVGhlIGN1bXVsYXRpdmUgbGlmdCBvZiBkZWNpbGUgMiBpcyBgciByb3VuZChsaWZ0X3RhYmxlW1syLDRdXSwwKWAlIG9mIGFsbCBjaHVybmVycyBhcmUgaW4gdGhlIHRvcCAyIGRlY2lsZXMuIEluIHRoZSBib3R0b20gbW9zdCBkZWNpbGVzIHRoZXJlIGFyZSBiYXJlbHkgYW55IGNodXJuZXJzLCBzbyB0aGUgY3VtdWxhdGl2ZSBsaWZ0IGluY3JlYXNlcyBsaXR0bGUgb3Igbm90IGF0IGFsbC4NCg0KV2UgY2FuIGdyYXBoIHRoaXMgb3V0IGJlbG93LiBUaGUgdG9wIHRocmVlIGRlY2lsZXMgYWNjb3VudCBmb3IgYHIgcm91bmQobGlmdF90YWJsZVtbMyw0XV0sMClgJSBvZiBhbGwgY2h1cm5lcnMuIFdlIGNhbiB1c2UgdGhpcyB0byBjb21wYXJlIG1vZGVscy4gVGhlIGhpZ2hlciB0aGUgbGlmdCBmb3IgYSBnaXZlbiBkZWNpbGUsIHRoZSBiZXR0ZXIgdGhlIG1vZGVsLiBBIHN0cmFpZ2h0IGxpbmUsIHdoZXJlIHdlIHJhbmRvbWx5IHNvcnRlZCBjdXN0b21lcnMgaW5zdGVhZCBvZiB1c2luZyBhIG1vZGVsLCBpcyB0aGUgbmFpdmUgbW9kZWwuDQoNCmBgYHtyfQ0KIyBvcmRlciBmcm9tIGhpZ2hlc3QgdG8gc21hbGxlc3QgaW4gdGVybXMgb2YgcHJvYg0KIyBwZXJjZW50YWdlIG9mIGNodXJuZXJzIGZyb20gYmVnaW5uaW5nIHRvIGVuZC4NCnByZWQ8LXByZWQgJT4lIGFycmFuZ2UoZGVzYyhwcmVkaWN0ZWQpKSAlPiUgbXV0YXRlKHByb3BfY2h1cm4gPSBjdW1zdW0oYWN0dWFsKS9zdW0oYWN0dWFsKSoxMDAsIHByb3BfY3VzdCA9IHNlcShucm93KHByZWQpKS9ucm93KHByZWQpKjEwMCkNCmhlYWQocHJlZCkgJT4lIA0KICBrYmwoKSAlPiUNCiAga2FibGVfc3R5bGluZygpDQoNCmBgYA0KDQpgYGB7cn0NCiMgUGxvdHRpbmcgcGVyY2VudGFnZSBvZiBjaHVybmVycyBhcyBhIGZ1bmN0aW9uIG9mIHBlcmNlbnRhZ2Ugb2YgY3VzdG9tZXJzDQpwYXIobWFpPWMoLjksLjgsLjIsLjIpKQ0KcGxvdChwcmVkJHByb3BfY3VzdCxwcmVkJHByb3BfY2h1cm4sdHlwZT0ibCIseGxhYj0iJSBvZiBjdXN0b21lcnMgdGFyZ2V0ZWQgdXNpbmcgbW9kZWwiLHlsYWI9IiUgb2YgY2h1cm5lcnMgYWNjb3VudGVkIGZvciIseGxpbSA9IGMoMCwxMDApLCAseWxpbSA9IGMoMCwxMDApLGNvbD0iYmx1ZSIpDQpsZWdlbmQoJ3RvcGxlZnQnLCBsZWdlbmQ9YygiTmFpdmUiLCAiTG9naXN0aWMiKSwgY29sPWMoInJlZCIsICJibHVlIiksIGx0eT0xOjEsIGNleD0wLjgpDQphYmxpbmUoYT0wLGI9MSxjb2w9InJlZCIpDQpwb2ludHMoeD0zMCwgeT0gbGlmdF90YWJsZSRjdW1fbGlmdFszXSwgcGNoPTQsIGNvbD0icmVkIiwgIGNleD0yLCBsd2Q9MikNCnRleHQoeCA9IDI4LHk9IGxpZnRfdGFibGUkY3VtX2xpZnRbM10rNSwgcGFzdGUocm91bmQobGlmdF90YWJsZSRjdW1fbGlmdFszXSwwKSwgIiUiICkpDQpgYGANCg0KLSAgIFRoaXMgZ2l2ZXMgdXMgZXF1aXZhbGVudCBpbmZvcm1hdGlvbiB0byB0aGUgY2h1cm4gdGFibGUuDQoNCi0gICB0YXJnZXRpbmcgdGhlIHRvcCAxMCUgdXNpbmcgdGhlIG1vZGVsIHdvdWxkIGdpdmUgdXMgYHIgcHJlZCRwcm9wX2NodXJuW3doaWNoLm1pbihhYnMocHJlZCRwcm9wX2N1c3QtMTApKV1gJSBvZiB0b3RhbCBjaHVybmVycyBpbiB0aGUgZGF0YS4NCg0KIyMjIFNlbGVjdGluZyBkZWNpbGVzIHRvIHRhcmdldA0KDQpPbmNlIHdlIGhhdmUgdXNlZCB0aGUgbW9kZWwgdG8gcHV0IGN1c3RvbWVycyBpbiB0aGUgcmlnaHQgZGVjaWxlLCB0YXJnZXRpbmcgaXMgc2ltcGxlLiBXZSBjYWxjdWxhdGUgdGhlIHByb2ZpdCBmcm9tIGVhY2ggbi10aWxlIGFuZCB0YXJnZXQgY3VzdG9tZXJzIHdobyBhcmUgaW4gdGhlIHByb2ZpdGFibGUgdGlsZXMuIFdlIHdpbGwgdXNlIHRoZSBwcm9hY3RpdmUgY2h1cm4gZnJhbWV3b3JrIGZyb20gQmxhdHRiZXJnLCBLaW0gYW5kIE5lc2xpbiB0byBjYWxjdWxhdGUgZXhwZWN0ZWQgcHJvZml0cy4gVGhpcyBhcHByb2FjaCB0YWtlcyBpbnRvIGFjY291bnQgdGhlIGFjdHVhbCBwcm9wb3J0aW9uIG9mIGNodXJuZXJzIGFzIGlkZW50aWZpZWQgYnkgdGhlIG1vZGVsLg0KDQpUaGUga2V5IHBhcmFtZXRlciBpcyAkXGJldGFfSyQsIHRoZSBwcm9wb3J0aW9uIG9mIGNodXJuZXJzIGluIHRoZSB0b3AgJEskIGRlY2lsZXMgY29udGFjdGVkLlwNCiQkDQpcYmV0YV9LID0gXGZyYWN7XHN1bV97az0xfV57S30gXDsgcl9rIFwsIG5fa317XHN1bV97az0xfV57S30gXDsgbl9rfSBccXVhZCBcdGV4dHJte3doZXJlfSBcOyBLID0gMSwgMiwgLi4gXGRvdHMsICAxMA0KJCQgV2UgY2FsY3VsYXRlICRcYmV0YSQsIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGEgdGFyZ2V0ZWQgY3VzdG9tZXIgaXMgYSBjaHVybmVyLCBieSB0YWtpbmcgdGhlIGN1bXVsYXRpdmUgcHJvcG9ydGlvbiBvZiBjaHVybmVycyBpbiB0aGUgdG9wICRrJCBkZWNpbGVzLg0KDQpgYGB7cn0NCmdhbW1hID0gMC4xICAjIHByb2JhYmlsaXR5IHRoYXQgY3VzdG9tZXIgaXMgcmVzY3VlZCBpZiBoZSBvciBzaGUgaXMgYSBjaHVybmVyDQpMVFYgPSA1MDAgICAjIGxpZmV0aW1lIHZhbHVlIG9mIHJlc2N1ZWQgY3VzdG9tZXINCmRlbHRhID0gNTAgICMgY29zdCBvZiBpbmNlbnRpdmUNCmMgPSAwLjUwICAjIGNvc3Qgb2YgY29udGFjdA0KDQojIHJlLW9yZGVyIGxpZnQgZnJvbSBoaWdoZXN0IHRvIGxvd2VzdA0KIyBhZGQgY29sdW1ucyB0byBvdXIgbGlmdCB0YWJsZQ0KDQpwcm9maXRfdGFibGU8LWxpZnRfdGFibGUgJT4lIG11dGF0ZSgNCiAgY3VtX3Byb3BfY2h1cm5lcnMgPSBjdW1zdW0oYWN0dWFsX2NodXJuKm5fY3VzdG9tZXJzKS9jdW1fY3VzdG9tZXJzLCANCiAgcHJvZml0ID0gY3VtX2N1c3RvbWVycyooKGdhbW1hKkxUVitkZWx0YSooMS1nYW1tYSkpKmN1bV9wcm9wX2NodXJuZXJzLWRlbHRhLWMpLA0KICBkZWNpbGU9MTEtZGVjaWxlKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KaGVhZChwcm9maXRfdGFibGUpICU+JSANCiAga2JsKCkgJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KYGBgDQoNCmBgYHtyfQ0KcGFyKG1haT1jKC45LC44LC4yLC4yKSkNCmJwPC1iYXJwbG90KHByb2ZpdF90YWJsZSRwcm9maXQgfiBwcm9maXRfdGFibGUkZGVjaWxlLCBtYWluPSJleHBlY3RlZCBwcm9maXRzIGJ5ICMgb2YgZGVjaWxlcyB0YXJnZXRlZCIsIHhsYWI9IiMgZGVjaWxlcyB0YXJnZXRlZCIsIHlsYWI9ImV4cGVjdGVkIHByb2ZpdHMiKQ0KYGBgDQoNCldlIHNlZSBmcm9tIHRoZSB0YWJsZSBiZWxvdyB0aGF0IGdpdmVuIHRoaXMgbW9kZWwsIHRoZSBwcm9maXQgbWF4aW1pemluZyBudW1iZXIgb2YgZGVjaWxlcyB0byB0YXJnZXQgaXMgdGhlIHRvcCAyLg0K