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.0383or 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), "%" ))

  • This gives us equivalent information to the churn table.

  • targeting the top 10% using the model would give us 28.772% of total churners in the data.

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