Introduction
Logistic regression is a probabilistic linear classification method that can be used to estimate the probability that an observation belongs to a particular class based on the feature values. Logistic regression can be adapted for use in multi-class classification problems, but we will begin by discussing the standard version of the algorithm, which is a binary classifier.
We will introduce this concept with a simple example.
Example 1: Exam Prep (6 Obs)
Assume that students in a certain field have to take a professional exam. We wish to determine the effect that time spent studying has on a students chances of passing the exam. We collect a dataset consisting of 6 students. For each student, we have recorded the number of hours that this student spent studying, as well as the results of the exam (0 for failing and 1 for passing).
The cell below creates a dataframe to store the student records.
hours <- c(50, 80, 110, 120, 140, 160)
passed <- c(0, 0, 1, 0, 1, 1)
df <- data.frame(hours, passed)
df
If we interpret passed
as a quantitative variable, then we can create a scatter plot of passed
against hours
. Such a plot is provided in the figure below.

Furthermore, if we are interpreting passed
as being quantitative, then there is nothing that would prevent us from creating a simple linear regression model by regressing passed
against hours
. We build such a model in the cell below.
slr_mod <- lm(passed ~ hours, df)
summary(slr_mod)
Call:
lm(formula = passed ~ hours, data = df)
Residuals:
1 2 3 4 5 6
1.000e-01 -2.000e-01 5.000e-01 -6.000e-01 2.000e-01 -6.939e-17
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.600000 0.542083 -1.107 0.3304
hours 0.010000 0.004677 2.138 0.0993 .
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.4183 on 4 degrees of freedom
Multiple R-squared: 0.5333, Adjusted R-squared: 0.4167
F-statistic: 4.571 on 1 and 4 DF, p-value: 0.0993
The fitted line for this data is shown in the figure below.

There are a number of serious concerns with this using a linear model such as this to describe the relationship between study hours and the ability to pass the exam. We will discuss some of these deficiencies in class.
A more appropriate model for this task is provided by a technique called logistic regression. Before discussing the details of logistic regression, we need to first introduce the sigmoid function.
The Sigmoid Function
The sigmoid function, also called the logistic function, is defined as follows:
\[\sigma(z) = \frac{e^z}{1 + e^z} = \frac{1}{1 + e^{-z}}\]
A plot of the sigmoid function is provided below. Note that the domain of \(\sigma\) is the entire real line, and its outputs are confined between 0 and 1.

Since the range of the \(\sigma\) is the interval \(\left(0,1\right)\), we can interpret the output of the sigmoid function as representing a probability. We can imagine that the sigmoid function converts arbitrary real numbers into probabilities in a specific way.
Logistic Regression Model: Single Predictor
Let \(Y\) be a categorical response variable with two levels. Assume that the labels are encoded as 0 and 1. Let \(p(x)\) denote the probability that \(Y = 1\) given that \(X=x\). That is:
- \(p(x) = P\left[Y = 1 | X = x \right]\)
A logistic regression model assumes that \(p\) is defined in terms of \(X\) as follows:
Our goal is to approximate this hypothetical relationship by finding estimates \(\hat\beta_0\) and \(\hat\beta_1\) for the unknown model parameters. As with ordinary least squares regression, our goal is to find parameter estimates that result in a model that “best fits” the training data according to some metric.
We will now discuss how logistic regression models are scored.
Scoring Logistic Regression Models
Assume that two logistic regression models have been proposed for this dataset:
Model 1: \(Z = 3 - 0.03 X, \hspace{0.6 em} p(X) = \sigma(Z)\)
Model 2: \(Z = 10 - 0.1 X, \hspace{0.6 em} p(X) = \sigma(Z)\)
The figure below displays plots for both of these models.

Likelihood
We can calculate a likelihood score for each of these models. This score measures how likely it is that the observed data could have been generated by the model being scored. We prefer models with higher likelihood scores. Likelihood is calculated as follows:
Step 1: Calculate \(p_i = p(x_i)\) for each training observation.
Recall that this number is the model’s estimate of the probability that a student will pass the exam, given the number of hours that they have studied.
Step 2: Calculate \(\pi_i\) for each training observation.
For each training observation, define \(\pi_i\) as follows:
\(\pi_i =\left\{\begin{array}{ll} p_i & \text{if }~ y_i = 1 \\ 1 - p_i & \text{if } ~y_i = 0 \end{array}\right.\)
The value \(\pi_i\) can be interpreted as the probability that student \(i\) will have obtained the observed outcome, given the number of hours that they studied.
Step 3: Calculate Likelihood, \(L\).
The model’s likelihood score is given by: \(L = \pi_1 \cdot \pi_2 \cdot ... \cdot \pi_n = \prod_{i=1}^{n} \pi_i\).
This score is equal to the probability that we would have seen the outcomes that we have observed, assuming that the model is correct. We prefer models with higher likelihood scores.
The figure below contains bars representing the values of \(\pi_i\) for each observation according to each model.

In the cell below, we calculate the likelihood scores for both of the two models we are considering.
z1 <- 3 - 0.03*df$hours
p1 <- 1 / (1 + exp(-z1))
pi1 <- ifelse(df$passed == 1, p1, 1-p1)
lik1 <- prod(pi1)
z2 <- 10 - 0.1*df$hours
p2 <- 1 / (1 + exp(-z2))
pi2 <- ifelse(df$passed == 1, p2, 1-p2)
lik2 <- prod(pi2)
cat('Likelihood score for Model 1: ', lik1, '\n',
'Likelihood score for Model 2: ', lik2, sep='')
Likelihood score for Model 1: 0.0005831859
Likelihood score for Model 2: 8.404835e-09
Negative Log-Likelihood
Although we can, in theory, compare models using their likelihood score, \(L\), it is more common in practice to score models using negative log-likelihood, \(NLL\). By definition, \(NLL = -\ln(L)\), but it is actually calculated using the following steps:
Step 1: Calculate \(p_i = p(x_i)\) for each training observation.
Step 2: Calculate \(\pi_i\) for each training observation.
For each training observation, calculate:
\(\pi_i =\left\{\begin{array}{ll} p_i & \text{if }~ y_i = 1 \\ 1 - p_i & \text{if } ~y_i = 0 \end{array}\right.\)
Step 3: Calculate Negative Log-Likelihood, \(NLL\).
The model’s negative log-likelihood score is given by:
\(NLL = - \left[\ln(\pi_1) + \ln(\pi_2) + ... + \ln(\pi_n)\right] = - \sum_{i=1}^n \ln(\pi_i)\).
Since the natural logarithm is an increasing function, maximizing likelihood is equivalent to maximizing log-likelihood, and thus equivalent to minimizing negative log-likelihood. Therefore, we prefer models with low \(NLL\) scores.
We calculate the negative log-likelihood of each of our proposed models in the cell below.
nll1 = -sum(log(pi1)) # nll = -log(lik1)
nll2 = -sum(log(pi2)) # nll = -log(lik2)
cat('Negative Log-Likelihood score for Model 1: ', nll1, '\n',
'Negative Log-Likelihood score for Model 2: ', nll2, sep='')
Negative Log-Likelihood score for Model 1: 7.447005
Negative Log-Likelihood score for Model 2: 18.59446
Finding Optimal Logistic Regression Model
R provides a glm()
function for performing logistic regression. The syntax for using this function is illustrated in the cell below, where we create a logistic regression model for our exam prep data.
mod <- glm(passed ~ hours, data=df, family=binomial(link="logit"))
summary(mod)
Call:
glm(formula = passed ~ hours, family = binomial(link = "logit"),
data = df)
Deviance Residuals:
1 2 3 4 5 6
-0.1047 -0.3528 1.3018 -1.4098 0.4650 0.2095
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -9.29923 8.24186 -1.128 0.259
hours 0.08192 0.07006 1.169 0.242
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 8.3178 on 5 degrees of freedom
Residual deviance: 4.0778 on 4 degrees of freedom
AIC: 8.0778
Number of Fisher Scoring iterations: 6
We can see from the model summary that the optimal logistic regression model for this dataset is given by:
\[Z = -9.3 + 0.082 \cdot X\]
\[p(X) = \sigma(Z) = \frac{1}{1 + e^{9.3 - 0.083 \cdot X}}\]
The figure below shows a plot of the optimal logistic regression model for this dataset.

We can use the predict()
function along with the parameter type="response"
to see the estimated probabilities of passing for each student in our training set, according to our model
predict(mod, df, type="response")
1 2 3 4 5 6
0.005469362 0.060344876 0.428551145 0.629823359 0.897514421 0.978296836
We will now calculate the likelihood and log-likelihood scores for our optimal model.
p <- predict(mod, df, type="response")
pi <- ifelse(df$passed == 1, p, 1-p)
lik <- prod(pi)
nll <- -log(lik)
cat('Log-Likelihood for Optimal Model : ', lik, '\n',
'Negative Log-Likelihood for Optimal Model: ', nll, sep='')
Log-Likelihood for Optimal Model : 0.1301699
Negative Log-Likelihood for Optimal Model: 2.038915
Let’s now consider a similar example, but with more observations.
Example 2: Exam Prep
The data for this example is stored in the tab-separated file data/exam_prep.txt
. We load the data and view a summary of the data.
exam <- read.table("data/exam_prep.txt", sep="\t", header=TRUE)
exam$Passed <- factor(exam$Passed)
summary(exam)
Passed Hours
0:44 Min. : 50.0
1:56 1st Qu.:116.2
Median :189.0
Mean :178.2
3rd Qu.:241.5
Max. :299.0
We will explore the relationship between hours studied and exam outcome by creating a boxplot.
ggplot(exam, aes(x=Passed, y=Hours, fill=Passed)) + geom_boxplot()

We will use the glm()
function to create a logistic regression model.
exam_mod <- glm(Passed ~ Hours, exam, family=binomial(link="logit"))
summary(exam_mod)
Call:
glm(formula = Passed ~ Hours, family = binomial(link = "logit"),
data = exam)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.1727 -0.7261 0.4391 0.7149 1.9716
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -3.185091 0.715405 -4.452 8.50e-06 ***
Hours 0.019662 0.003892 5.052 4.38e-07 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 137.19 on 99 degrees of freedom
Residual deviance: 100.28 on 98 degrees of freedom
AIC: 104.28
Number of Fisher Scoring iterations: 4
We can see from the model summary that the optimal logistic regression model for this dataset is given by:
\[Z = -3.19 + 0.02 X\]
\[p(X) = \sigma(Z) = \frac{1}{1 + e^{3.19 - 0.02 X}}\]
In the cell below, we will use our model to estimate the probability that a student will pass the exam if they have studied for each of the following numbers of hours: 0, 50, 100, 150, 200
nd <- data.frame(Hours = c(0, 50, 100, 150, 200))
prob <- predict(exam_mod, newdata=nd, type="response")
prob
1 2 3 4 5
0.03973065 0.09956985 0.22812386 0.44130744 0.67857270
Training Accuracy
A common way to evaluate the performance of a classification model is to calculate its accuracy on some set. The accuracy on a particular set is equal to the proportion of observations in the set for which the model predicts the correct class.
In the cell below, we calculate our model’s accuracy on the training set.
prob <- predict(exam_mod, exam, type="response")
pred <- ifelse(prob < 0.5, 0, 1)
tr_acc <- mean(pred == exam$Passed)
cat('Training Accuracy: ', tr_acc, sep='')
Training Accuracy: 0.77
Interpreting Model Coefficients
Interpreting the coefficients in a logistic regression model is not as direct as in a linear regression model. However, it is possible to do. Because discussing how we would interpret these coefficients, we need to discuss the concept of odds ratios.
Odds Ratios
The odds of an event occurring is defined as the probability of an event occurring divided by the probability of the event not occurring. The probability of an event \(P_E\), and the odds of an event \(O_E\) are related by the following expressions:
\[O_E = \frac{P_E}{1 - P_E} \hspace{3 em} P_E = \frac{O_E}{1 + O_E}\]
You might be familiar with the concept of odds from sports or games of chance. Assume that an event is twice as likely to occur as not. Then we might say that the event has “2 to 1 odds”. The odds of such an event is often denoted by 2:1 or 2/1. This notion of odds is equivalent to the one that we defined above.
The table below shows several ways of expressing odds, as well as the probability associated with each odds value.
Odds
|
Odds
|
Odds
|
Prob
|
1:1
|
1/1
|
1.00
|
0.50
|
2:1
|
2/1
|
2.00
|
0.67
|
1:2
|
1/2
|
0.50
|
0.33
|
1:3
|
1/3
|
0.33
|
0.25
|
Note that odds are always positive, but can be arbitrarily large.
Coefficient Interpretation
The interpretation of \(\beta_1\) in a logistic regression model is as follows: If we increase the predictor \(X\) by a single unit, that will result in an increase of \(\beta_1\) units in log-odds, and approximately a \(\beta_1*100\%\) increase in the odds of \(Y=1\).
Returning to our exam prep example, we recall that our model was given by:
\[p(X) = \frac{1}{1 + e^{3.19 - 0.02 X}}\]
This can be rewritten as:
\[\ln\left[\frac{p(X)}{1 - p(X)} \right] = -3.19 + 0.02 X \]
It follows that each additional hour of studying will increase the odds (not the probability) of passing the exam by about 2% (or in other words, by a factor of 1.02).
Example 3: Pima Diabetes Dataset
For this example, we will be working with the Pima Diabetes Dataset. This dataset is originally from the National Institute of Diabetes and Digestive and Kidney Diseases. The objective is to predict based on diagnostic measurements whether a patient has diabetes. All patients are females at least 21 years old of Pima Indian heritage.
The columns in this dataset are described below.
- Pregnancies: Number of times pregnant
- Glucose: Plasma glucose concentration a 2 hours in an oral glucose tolerance test
- BloodPressure: Diastolic blood pressure (mm Hg)
- SkinThickness: Triceps skin fold thickness (mm)
- Insulin: 2-Hour serum insulin (mu U/ml)
- BMI: Body mass index (weight in kg/(height in m)^2)
- DiabetesPedigreeFunction: Diabetes pedigree function
- Age: Age (years)
- Outcome: Class variable (0 or 1)
Loading the Data
We will read in the data, convert the Outcome column to a factor variable, and then print a summary of the dataset.
pima <- read.table("data/diabetes.csv", sep=",", header=TRUE)
pima$Outcome <- factor(pima$Outcome)
summary(pima)
Pregnancies Glucose BloodPressure SkinThickness Insulin
Min. : 0.000 Min. : 0.0 Min. : 0.00 Min. : 0.00 Min. : 0.0
1st Qu.: 1.000 1st Qu.: 99.0 1st Qu.: 62.00 1st Qu.: 0.00 1st Qu.: 0.0
Median : 3.000 Median :117.0 Median : 72.00 Median :23.00 Median : 30.5
Mean : 3.845 Mean :120.9 Mean : 69.11 Mean :20.54 Mean : 79.8
3rd Qu.: 6.000 3rd Qu.:140.2 3rd Qu.: 80.00 3rd Qu.:32.00 3rd Qu.:127.2
Max. :17.000 Max. :199.0 Max. :122.00 Max. :99.00 Max. :846.0
BMI DiabetesPedigreeFunction Age Outcome
Min. : 0.00 Min. :0.0780 Min. :21.00 0:500
1st Qu.:27.30 1st Qu.:0.2437 1st Qu.:24.00 1:268
Median :32.00 Median :0.3725 Median :29.00
Mean :31.99 Mean :0.4719 Mean :33.24
3rd Qu.:36.60 3rd Qu.:0.6262 3rd Qu.:41.00
Max. :67.10 Max. :2.4200 Max. :81.00
Visualizing the Data
The figure below displays density plots for each of our predictors, and for each value of our response variable.

Splitting the Data
We will consider multiple logistic regression models on this dataset. We will create training and validation sets. The models will be trained on the training set, and compared based on their accuracy on the validation set.
The caret
package provides a function called createDataPartition()
which can be used to create a train/validation split. By default, this function creates a stratified random split. As a result, the distribution of the response variable will be roughly the same in the training set and validation set.
In the cell below, we create a 70/30 split of our dataset.
library(caret)
Loading required package: lattice
Registered S3 method overwritten by 'data.table':
method from
print.data.table
set.seed(1)
train.index <- createDataPartition(pima$Outcome, p = .7, list=FALSE)
train <- pima[ train.index,]
valid <- pima[-train.index,]
summary(train$Outcome)
0 1
350 188
summary(valid$Outcome)
0 1
150 80
Create Models
We will now train our model on the training set.
pima_mod_1 <- glm(Outcome ~ ., train, family=binomial(link="logit"))
summary(pima_mod_1)
Call:
glm(formula = Outcome ~ ., family = binomial(link = "logit"),
data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.4622 -0.6897 -0.3771 0.7121 2.8527
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -9.5166331 0.9353565 -10.174 < 2e-16 ***
Pregnancies 0.1142149 0.0382633 2.985 0.00284 **
Glucose 0.0328008 0.0044197 7.422 1.16e-13 ***
BloodPressure -0.0113590 0.0066079 -1.719 0.08561 .
SkinThickness -0.0100998 0.0081048 -1.246 0.21271
Insulin -0.0004845 0.0010896 -0.445 0.65660
BMI 0.1080848 0.0188664 5.729 1.01e-08 ***
DiabetesPedigreeFunction 1.5592974 0.3777531 4.128 3.66e-05 ***
Age 0.0312125 0.0112356 2.778 0.00547 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 696.28 on 537 degrees of freedom
Residual deviance: 487.97 on 529 degrees of freedom
AIC: 505.97
Number of Fisher Scoring iterations: 5
It appears that three of our predictors, Insulin
, SkinThickness
, and BloodPressure
do not have statistically significant effects on the prescience of diabetes. We will remove them one at a time.
pima_mod_2 <- glm(Outcome ~ . - Insulin, train, family=binomial(link="logit"))
summary(pima_mod_2)
Call:
glm(formula = Outcome ~ . - Insulin, family = binomial(link = "logit"),
data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.4591 -0.6866 -0.3798 0.7075 2.8425
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -9.467154 0.927116 -10.211 < 2e-16 ***
Pregnancies 0.115198 0.038156 3.019 0.00253 **
Glucose 0.032150 0.004151 7.746 9.51e-15 ***
BloodPressure -0.011115 0.006567 -1.692 0.09056 .
SkinThickness -0.011555 0.007391 -1.563 0.11795
BMI 0.108058 0.018842 5.735 9.75e-09 ***
DiabetesPedigreeFunction 1.549068 0.377035 4.109 3.98e-05 ***
Age 0.031393 0.011228 2.796 0.00517 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 696.28 on 537 degrees of freedom
Residual deviance: 488.17 on 530 degrees of freedom
AIC: 504.17
Number of Fisher Scoring iterations: 5
pima_mod_3 <- glm(Outcome ~ . - Insulin - SkinThickness, train, family=binomial(link="logit"))
summary(pima_mod_3)
Call:
glm(formula = Outcome ~ . - Insulin - SkinThickness, family = binomial(link = "logit"),
data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.4798 -0.6973 -0.3835 0.6820 2.8184
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -9.298915 0.910983 -10.208 < 2e-16 ***
Pregnancies 0.117716 0.038248 3.078 0.00209 **
Glucose 0.032033 0.004148 7.722 1.14e-14 ***
BloodPressure -0.012371 0.006476 -1.910 0.05609 .
BMI 0.098521 0.017582 5.603 2.10e-08 ***
DiabetesPedigreeFunction 1.462223 0.370645 3.945 7.98e-05 ***
Age 0.032487 0.011280 2.880 0.00398 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 696.28 on 537 degrees of freedom
Residual deviance: 490.62 on 531 degrees of freedom
AIC: 504.62
Number of Fisher Scoring iterations: 5
pima_mod_4 <- glm(Outcome ~ . - Insulin - SkinThickness - BloodPressure, train, family=binomial(link="logit"))
summary(pima_mod_4)
Call:
glm(formula = Outcome ~ . - Insulin - SkinThickness - BloodPressure,
family = binomial(link = "logit"), data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.5241 -0.6849 -0.3820 0.6919 2.7797
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -9.704090 0.893021 -10.867 < 2e-16 ***
Pregnancies 0.116444 0.037992 3.065 0.00218 **
Glucose 0.031563 0.004114 7.672 1.69e-14 ***
BMI 0.091049 0.016922 5.381 7.42e-08 ***
DiabetesPedigreeFunction 1.455119 0.367779 3.957 7.61e-05 ***
Age 0.028194 0.010960 2.572 0.01010 *
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 696.28 on 537 degrees of freedom
Residual deviance: 494.30 on 532 degrees of freedom
AIC: 506.3
Number of Fisher Scoring iterations: 5
Notice that all predictors are statistically significant in our fourth model.
Calculate Training and Validation Accuracies
We will now calculate the training and validation accuracies for each of our four models. The cell below shows the code for calculating these metrics using Model 1. The same values are also calculated for the other three models, but the code has been hidden in the output. It can be seen in the Rmd file for this lesson, however.
tr_prob_1 <- predict(pima_mod_1, train, type="response")
tr_pred_1 <- ifelse(tr_prob_1 < 0.5, 0, 1)
tr_acc_1 <- mean(tr_pred_1 == train$Outcome)
va_prob_1 <- predict(pima_mod_1, valid, type="response")
va_pred_1 <- ifelse(va_prob_1 < 0.5, 0, 1)
va_acc_1 <- mean(va_pred_1 == valid$Outcome)
We will now compare the accuracy scores for each of our models.
cat("Model 1 Training Accuracy: ", tr_acc_1, "\n",
"Model 1 Validation Accuracy: ", va_acc_1, "\n\n",
"Model 2 Training Accuracy: ", tr_acc_2, "\n",
"Model 2 Validation Accuracy: ", va_acc_2, "\n\n",
"Model 3 Training Accuracy: ", tr_acc_3, "\n",
"Model 3 Validation Accuracy: ", va_acc_3, "\n\n",
"Model 4 Training Accuracy: ", tr_acc_4, "\n",
"Model 4 Validation Accuracy: ", va_acc_4, sep=""
)
Model 1 Training Accuracy: 0.7843866
Model 1 Validation Accuracy: 0.7652174
Model 2 Training Accuracy: 0.7843866
Model 2 Validation Accuracy: 0.7695652
Model 3 Training Accuracy: 0.7750929
Model 3 Validation Accuracy: 0.7608696
Model 4 Training Accuracy: 0.7750929
Model 4 Validation Accuracy: 0.7695652
Notice that Models 2 and 4 are actually tied in their performance on the validation set, although the score is similar for all four models. In this case, we would likely prefer Model 4 since it does not contain any non-significant predictors.
LS0tDQp0aXRsZTogIkxlc3NvbiA0LjEgLSBMb2dpc3RpYyBSZWdyZXNzaW9uIg0KYXV0aG9yOiAiUm9iYmllIEJlYW5lIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KLS0tDQoNCg0KIyMjICoqSW50cm9kdWN0aW9uKioNCg0KTG9naXN0aWMgcmVncmVzc2lvbiBpcyBhIHByb2JhYmlsaXN0aWMgbGluZWFyIGNsYXNzaWZpY2F0aW9uIG1ldGhvZCB0aGF0IGNhbiBiZSB1c2VkIHRvIGVzdGltYXRlIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGFuIG9ic2VydmF0aW9uIGJlbG9uZ3MgdG8gYSBwYXJ0aWN1bGFyIGNsYXNzIGJhc2VkIG9uIHRoZSBmZWF0dXJlIHZhbHVlcy4gTG9naXN0aWMgcmVncmVzc2lvbiBjYW4gYmUgYWRhcHRlZCBmb3IgdXNlIGluIG11bHRpLWNsYXNzIGNsYXNzaWZpY2F0aW9uIHByb2JsZW1zLCBidXQgd2Ugd2lsbCBiZWdpbiBieSBkaXNjdXNzaW5nIHRoZSBzdGFuZGFyZCB2ZXJzaW9uIG9mIHRoZSBhbGdvcml0aG0sIHdoaWNoIGlzIGEgYmluYXJ5IGNsYXNzaWZpZXIuDQoNCldlIHdpbGwgaW50cm9kdWNlIHRoaXMgY29uY2VwdCB3aXRoIGEgc2ltcGxlIGV4YW1wbGUuDQoNCiMjIyAqKkV4YW1wbGUgMTogRXhhbSBQcmVwICg2IE9icykqKg0KDQpBc3N1bWUgdGhhdCBzdHVkZW50cyBpbiBhIGNlcnRhaW4gZmllbGQgaGF2ZSB0byB0YWtlIGEgcHJvZmVzc2lvbmFsIGV4YW0uIFdlIHdpc2ggdG8gZGV0ZXJtaW5lIHRoZSBlZmZlY3QgdGhhdCB0aW1lIHNwZW50IHN0dWR5aW5nIGhhcyBvbiBhIHN0dWRlbnRzIGNoYW5jZXMgb2YgcGFzc2luZyB0aGUgZXhhbS4gV2UgY29sbGVjdCBhIGRhdGFzZXQgY29uc2lzdGluZyBvZiA2IHN0dWRlbnRzLiBGb3IgZWFjaCBzdHVkZW50LCB3ZSBoYXZlIHJlY29yZGVkIHRoZSBudW1iZXIgb2YgaG91cnMgdGhhdCB0aGlzIHN0dWRlbnQgc3BlbnQgc3R1ZHlpbmcsIGFzIHdlbGwgYXMgdGhlIHJlc3VsdHMgb2YgdGhlIGV4YW0gKDAgZm9yIGZhaWxpbmcgYW5kIDEgZm9yIHBhc3NpbmcpLg0KDQpUaGUgY2VsbCBiZWxvdyBjcmVhdGVzIGEgZGF0YWZyYW1lIHRvIHN0b3JlIHRoZSBzdHVkZW50IHJlY29yZHMuIA0KDQoNCmBgYHtyfQ0KaG91cnMgPC0gYyg1MCwgODAsIDExMCwgMTIwLCAxNDAsIDE2MCkNCnBhc3NlZCA8LSBjKDAsIDAsIDEsIDAsIDEsIDEpDQpkZiA8LSBkYXRhLmZyYW1lKGhvdXJzLCBwYXNzZWQpDQpkZg0KYGBgDQoNCklmIHdlIGludGVycHJldCBgcGFzc2VkYCBhcyBhIHF1YW50aXRhdGl2ZSB2YXJpYWJsZSwgdGhlbiB3ZSBjYW4gY3JlYXRlIGEgc2NhdHRlciBwbG90IG9mIGBwYXNzZWRgIGFnYWluc3QgYGhvdXJzYC4gU3VjaCBhIHBsb3QgaXMgcHJvdmlkZWQgaW4gdGhlIGZpZ3VyZSBiZWxvdy4gDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KcGxvdChwYXNzZWRbZGYkcGFzc2VkID09IDFdIH4gaG91cnNbZGYkcGFzc2VkID09IDFdLCBjZXg9MiwgcGNoPTIxLCBiZz0nY29ybmZsb3dlcmJsdWUnLCANCiAgICAgeGxpbT1jKDMwLDE4MCksIHlsaW09YygtMC4yLCAxLjIpLCB4bGFiPSdTdHVkeSBIb3VycycsIHlsYWI9J1Bhc3NlZCcpDQpsaW5lcyhjKDAsMjAwKSwgYygwLDApLCBsdHk9MikNCmxpbmVzKGMoMCwyMDApLCBjKDEsMSksIGx0eT0yKQ0KcG9pbnRzKHBhc3NlZFtkZiRwYXNzZWQgPT0gMF0gfiBob3Vyc1tkZiRwYXNzZWQgPT0gMF0sIGNleD0yLCBwY2g9MjEsIGJnPSdzYWxtb24nKQ0KcG9pbnRzKHBhc3NlZFtkZiRwYXNzZWQgPT0gMV0gfiBob3Vyc1tkZiRwYXNzZWQgPT0gMV0sIGNleD0yLCBwY2g9MjEsIGJnPSdjb3JuZmxvd2VyYmx1ZScpDQpgYGANCg0KRnVydGhlcm1vcmUsIGlmIHdlIGFyZSBpbnRlcnByZXRpbmcgYHBhc3NlZGAgYXMgYmVpbmcgcXVhbnRpdGF0aXZlLCB0aGVuIHRoZXJlIGlzIG5vdGhpbmcgdGhhdCB3b3VsZCBwcmV2ZW50IHVzIGZyb20gY3JlYXRpbmcgYSBzaW1wbGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgYnkgcmVncmVzc2luZyBgcGFzc2VkYCBhZ2FpbnN0IGBob3Vyc2AuIFdlIGJ1aWxkIHN1Y2ggYSBtb2RlbCBpbiB0aGUgY2VsbCBiZWxvdy4gDQoNCmBgYHtyfQ0Kc2xyX21vZCA8LSBsbShwYXNzZWQgfiBob3VycywgZGYpDQpzdW1tYXJ5KHNscl9tb2QpDQpgYGANCg0KVGhlIGZpdHRlZCBsaW5lIGZvciB0aGlzIGRhdGEgaXMgc2hvd24gaW4gdGhlIGZpZ3VyZSBiZWxvdy4gDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KcGxvdChwYXNzZWRbZGYkcGFzc2VkID09IDFdIH4gaG91cnNbZGYkcGFzc2VkID09IDFdLCBjZXg9MiwgcGNoPTIxLCBiZz0nY29ybmZsb3dlcmJsdWUnLCANCiAgICAgeGxpbT1jKDMwLDE4MCksIHlsaW09YygtMC4yLCAxLjIpLCB4bGFiPSdTdHVkeSBIb3VycycsIHlsYWI9J1Bhc3NlZCcpDQphYmxpbmUoc2xyX21vZCRjb2VmZmljaWVudHMsIGx3ZD0yKQ0KbGluZXMoYygwLDIwMCksIGMoMCwwKSwgbHR5PTIpDQpsaW5lcyhjKDAsMjAwKSwgYygxLDEpLCBsdHk9MikNCnBvaW50cyhwYXNzZWRbZGYkcGFzc2VkID09IDBdIH4gaG91cnNbZGYkcGFzc2VkID09IDBdLCBjZXg9MiwgcGNoPTIxLCBiZz0nc2FsbW9uJykNCnBvaW50cyhwYXNzZWRbZGYkcGFzc2VkID09IDFdIH4gaG91cnNbZGYkcGFzc2VkID09IDFdLCBjZXg9MiwgcGNoPTIxLCBiZz0nY29ybmZsb3dlcmJsdWUnKQ0KYGBgDQoNClRoZXJlIGFyZSBhIG51bWJlciBvZiBzZXJpb3VzIGNvbmNlcm5zIHdpdGggdGhpcyB1c2luZyBhIGxpbmVhciBtb2RlbCBzdWNoIGFzIHRoaXMgdG8gZGVzY3JpYmUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHN0dWR5IGhvdXJzIGFuZCB0aGUgYWJpbGl0eSB0byBwYXNzIHRoZSBleGFtLiBXZSB3aWxsIGRpc2N1c3Mgc29tZSBvZiB0aGVzZSBkZWZpY2llbmNpZXMgaW4gY2xhc3MuIA0KDQpBIG1vcmUgYXBwcm9wcmlhdGUgbW9kZWwgZm9yIHRoaXMgdGFzayBpcyBwcm92aWRlZCBieSBhIHRlY2huaXF1ZSBjYWxsZWQgbG9naXN0aWMgcmVncmVzc2lvbi4gQmVmb3JlIGRpc2N1c3NpbmcgdGhlIGRldGFpbHMgb2YgbG9naXN0aWMgcmVncmVzc2lvbiwgd2UgbmVlZCB0byBmaXJzdCBpbnRyb2R1Y2UgdGhlIHNpZ21vaWQgZnVuY3Rpb24uIA0KDQoNCiMjIyAqKlRoZSBTaWdtb2lkIEZ1bmN0aW9uKioNCg0KVGhlICoqc2lnbW9pZCBmdW5jdGlvbioqLCBhbHNvIGNhbGxlZCB0aGUgKipsb2dpc3RpYyBmdW5jdGlvbioqLCBpcyBkZWZpbmVkIGFzIGZvbGxvd3M6DQoNCiQkXHNpZ21hKHopID0gXGZyYWN7ZV56fXsxICsgZV56fSA9IFxmcmFjezF9ezEgKyBlXnsten19JCQNCg0KQSBwbG90IG9mIHRoZSBzaWdtb2lkIGZ1bmN0aW9uIGlzIHByb3ZpZGVkIGJlbG93LiBOb3RlIHRoYXQgdGhlIGRvbWFpbiBvZiAkXHNpZ21hJCBpcyB0aGUgZW50aXJlIHJlYWwgbGluZSwgYW5kIGl0cyBvdXRwdXRzIGFyZSBjb25maW5lZCBiZXR3ZWVuIDAgYW5kIDEuIA0KDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0Kel9zaWcgPC0gc2VxKC02LCA2LCAwLjEpDQp5X3NpZyA8LSAxIC8gKDEgKyBleHAoLXpfc2lnKSkNCnBsb3QoeV9zaWcgfiB6X3NpZywgcGNoPScuJywgY29sPSJ3aGl0ZSIsIHhsYWI9IloiLCB5bGFiPSJwIiwgbWFpbj0iUGxvdCBvZiBTaWdtb2lkIEZ1bmN0aW9uIikNCg0KbGluZXMoYygwLDApLCBjKC0xLDEpLCBsdHk9MiwgbHdkPTEsIGNvbD0nZ3JleTUwJykNCmxpbmVzKGMoLTgsOCksIGMoMCwwKSwgbHR5PTIsIGx3ZD0xLCBjb2w9J2dyZXk1MCcpDQpsaW5lcyhjKC04LDgpLCBjKDEsMSksIGx0eT0yLCBsd2Q9MSwgY29sPSdncmV5NTAnKQ0KDQpsaW5lcyh5X3NpZyB+IHpfc2lnLCBsd2Q9MywgY29sPSJzdGVlbGJsdWUiKQ0KDQpgYGANCg0KU2luY2UgdGhlIHJhbmdlIG9mIHRoZSAkXHNpZ21hJCBpcyB0aGUgaW50ZXJ2YWwgJFxsZWZ0KDAsMVxyaWdodCkkLCB3ZSBjYW4gaW50ZXJwcmV0IHRoZSBvdXRwdXQgb2YgdGhlIHNpZ21vaWQgZnVuY3Rpb24gYXMgcmVwcmVzZW50aW5nIGEgcHJvYmFiaWxpdHkuIFdlIGNhbiBpbWFnaW5lIHRoYXQgdGhlIHNpZ21vaWQgZnVuY3Rpb24gY29udmVydHMgYXJiaXRyYXJ5IHJlYWwgbnVtYmVycyBpbnRvIHByb2JhYmlsaXRpZXMgaW4gYSBzcGVjaWZpYyB3YXkuIA0KDQojIyMgKipMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsOiBTaW5nbGUgUHJlZGljdG9yKioNCg0KTGV0ICRZJCBiZSBhIGNhdGVnb3JpY2FsIHJlc3BvbnNlIHZhcmlhYmxlIHdpdGggdHdvIGxldmVscy4gQXNzdW1lIHRoYXQgdGhlIGxhYmVscyBhcmUgZW5jb2RlZCBhcyAwIGFuZCAxLiBMZXQgJHAoeCkkIGRlbm90ZSB0aGUgcHJvYmFiaWxpdHkgdGhhdCAkWSA9IDEkIGdpdmVuIHRoYXQgJFg9eCQuIFRoYXQgaXM6DQoNCiogJHAoeCkgPSBQXGxlZnRbWSA9IDEgfCBYID0geCBccmlnaHRdJA0KDQpBIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgYXNzdW1lcyB0aGF0ICRwJCBpcyBkZWZpbmVkIGluIHRlcm1zIG9mICRYJCBhcyBmb2xsb3dzOg0KDQoqICRaID0gXGJldGFfMCArIFxiZXRhXzEgIFgkDQoNCiogJHAoWCkgPSBcc2lnbWEoWikgPSBcc2lnbWEoXGJldGFfMCArIFxiZXRhXzEgWCkgPSBcZnJhY3sxfXsxICsgZV57LVxsZWZ0KFxiZXRhXzAgKyBcYmV0YV8xICBYIFxyaWdodCl9fSQNCg0KT3VyIGdvYWwgaXMgdG8gYXBwcm94aW1hdGUgdGhpcyBoeXBvdGhldGljYWwgcmVsYXRpb25zaGlwIGJ5IGZpbmRpbmcgZXN0aW1hdGVzICRcaGF0XGJldGFfMCQgYW5kICRcaGF0XGJldGFfMSQgZm9yIHRoZSB1bmtub3duIG1vZGVsIHBhcmFtZXRlcnMuIEFzIHdpdGggb3JkaW5hcnkgbGVhc3Qgc3F1YXJlcyByZWdyZXNzaW9uLCBvdXIgZ29hbCBpcyB0byBmaW5kIHBhcmFtZXRlciBlc3RpbWF0ZXMgdGhhdCByZXN1bHQgaW4gYSBtb2RlbCB0aGF0ICJiZXN0IGZpdHMiIHRoZSB0cmFpbmluZyBkYXRhIGFjY29yZGluZyB0byBzb21lIG1ldHJpYy4gDQoNCldlIHdpbGwgbm93IGRpc2N1c3MgaG93IGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWxzIGFyZSBzY29yZWQuIA0KDQoNCiMjIyAqKlNjb3JpbmcgTG9naXN0aWMgUmVncmVzc2lvbiBNb2RlbHMqKg0KDQpBc3N1bWUgdGhhdCB0d28gbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMgaGF2ZSBiZWVuIHByb3Bvc2VkIGZvciB0aGlzIGRhdGFzZXQ6DQoNCiogKipNb2RlbCAxOioqICRaID0gMyAtIDAuMDMgIFgsIFxoc3BhY2V7MC42IGVtfSBwKFgpID0gXHNpZ21hKFopJA0KDQoqICoqTW9kZWwgMjoqKiAkWiA9IDEwIC0gMC4xICBYLCBcaHNwYWNlezAuNiBlbX0gcChYKSA9IFxzaWdtYShaKSQNCg0KVGhlIGZpZ3VyZSBiZWxvdyBkaXNwbGF5cyBwbG90cyBmb3IgYm90aCBvZiB0aGVzZSBtb2RlbHMuIA0KDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KeF9ncmlkIDwtIHNlcSgwLCAyMDApDQoNCg0KcGFyKG1mcm93PWMoMSwyKSkNCg0KcGxvdChwYXNzZWRbZGYkcGFzc2VkID09IDFdIH4gaG91cnNbZGYkcGFzc2VkID09IDFdLCBjZXg9MiwgcGNoPTIxLCBiZz0nY29ybmZsb3dlcmJsdWUnLCANCiAgICAgeGxpbT1jKDMwLDE4MCksIHlsaW09YygtMC4yLCAxLjIpLCB4bGFiPSdTdHVkeSBIb3VycycsIHlsYWI9J1Bhc3NlZCcpDQoNCmxpbmVzKHhfZ3JpZCwgMSAvICgxICsgZXhwKDMgLSAwLjAzICogeF9ncmlkKSksIGx3ZD0yKQ0KbGluZXMoYygwLDIwMCksIGMoMCwwKSwgbHR5PTIpDQpsaW5lcyhjKDAsMjAwKSwgYygxLDEpLCBsdHk9MikNCnBvaW50cyhwYXNzZWRbZGYkcGFzc2VkID09IDBdIH4gaG91cnNbZGYkcGFzc2VkID09IDBdLCBjZXg9MiwgcGNoPTIxLCBiZz0nc2FsbW9uJykNCnBvaW50cyhwYXNzZWRbZGYkcGFzc2VkID09IDFdIH4gaG91cnNbZGYkcGFzc2VkID09IDFdLCBjZXg9MiwgcGNoPTIxLCBiZz0nY29ybmZsb3dlcmJsdWUnKQ0KDQpwbG90KHBhc3NlZFtkZiRwYXNzZWQgPT0gMV0gfiBob3Vyc1tkZiRwYXNzZWQgPT0gMV0sIGNleD0yLCBwY2g9MjEsIGJnPSdjb3JuZmxvd2VyYmx1ZScsIA0KICAgICB4bGltPWMoMzAsMTgwKSwgeWxpbT1jKC0wLjIsIDEuMiksIHhsYWI9J1N0dWR5IEhvdXJzJywgeWxhYj0nUGFzc2VkJykNCg0KbGluZXMoeF9ncmlkLCAxIC8gKDEgKyBleHAoMTAgLSAwLjEwICogeF9ncmlkKSksIGx3ZD0yKQ0KbGluZXMoYygwLDIwMCksIGMoMCwwKSwgbHR5PTIpDQpsaW5lcyhjKDAsMjAwKSwgYygxLDEpLCBsdHk9MikNCnBvaW50cyhwYXNzZWRbZGYkcGFzc2VkID09IDBdIH4gaG91cnNbZGYkcGFzc2VkID09IDBdLCBjZXg9MiwgcGNoPTIxLCBiZz0nc2FsbW9uJykNCnBvaW50cyhwYXNzZWRbZGYkcGFzc2VkID09IDFdIH4gaG91cnNbZGYkcGFzc2VkID09IDFdLCBjZXg9MiwgcGNoPTIxLCBiZz0nY29ybmZsb3dlcmJsdWUnKQ0KDQpwYXIobWZyb3c9YygxLDEpKQ0KYGBgDQoNCiMjIyMgKipMaWtlbGlob29kKioNCg0KV2UgY2FuIGNhbGN1bGF0ZSBhICoqbGlrZWxpaG9vZCoqIHNjb3JlIGZvciBlYWNoIG9mIHRoZXNlIG1vZGVscy4gVGhpcyBzY29yZSBtZWFzdXJlcyBob3cgbGlrZWx5IGl0IGlzIHRoYXQgdGhlIG9ic2VydmVkIGRhdGEgY291bGQgaGF2ZSBiZWVuIGdlbmVyYXRlZCBieSB0aGUgbW9kZWwgYmVpbmcgc2NvcmVkLiBXZSBwcmVmZXIgbW9kZWxzIHdpdGggaGlnaGVyIGxpa2VsaWhvb2Qgc2NvcmVzLiBMaWtlbGlob29kIGlzIGNhbGN1bGF0ZWQgYXMgZm9sbG93czoNCg0KKiAqKlN0ZXAgMToqKiBDYWxjdWxhdGUgJHBfaSA9IHAoeF9pKSQgZm9yIGVhY2ggdHJhaW5pbmcgb2JzZXJ2YXRpb24uIA0KDQogIFJlY2FsbCB0aGF0IHRoaXMgbnVtYmVyIGlzIHRoZSBtb2RlbCdzIGVzdGltYXRlIG9mIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGEgIHN0dWRlbnQgd2lsbCBwYXNzIHRoZSBleGFtLCBnaXZlbiB0aGUgbnVtYmVyIG9mIGhvdXJzIHRoYXQgdGhleSBoYXZlIHN0dWRpZWQuICANCg0KKiAqKlN0ZXAgMjoqKiBDYWxjdWxhdGUgJFxwaV9pJCBmb3IgZWFjaCB0cmFpbmluZyBvYnNlcnZhdGlvbi4NCg0KICBGb3IgZWFjaCB0cmFpbmluZyBvYnNlcnZhdGlvbiwgZGVmaW5lICRccGlfaSQgYXMgZm9sbG93czogDQogIA0KICAkXHBpX2kgPVxsZWZ0XHtcYmVnaW57YXJyYXl9e2xsfSBwX2kgJiBcdGV4dHtpZiB9fiB5X2kgPSAxIFxcIDEgLSBwX2kgJiBcdGV4dHtpZiB9IH55X2kgPSAwIFxlbmR7YXJyYXl9XHJpZ2h0LiQNCiAgDQogIFRoZSB2YWx1ZSAkXHBpX2kkIGNhbiBiZSBpbnRlcnByZXRlZCBhcyB0aGUgcHJvYmFiaWxpdHkgdGhhdCBzdHVkZW50ICRpJCB3aWxsIGhhdmUgb2J0YWluZWQgdGhlIG9ic2VydmVkIG91dGNvbWUsIGdpdmVuIHRoZSBudW1iZXIgb2YgaG91cnMgdGhhdCB0aGV5IHN0dWRpZWQuIA0KICANCiogKipTdGVwIDM6KiogQ2FsY3VsYXRlIExpa2VsaWhvb2QsICRMJC4NCg0KICBUaGUgbW9kZWwncyAqKmxpa2VsaWhvb2QqKiBzY29yZSBpcyBnaXZlbiBieTogJEwgPSBccGlfMSBcY2RvdCBccGlfMiBcY2RvdCAuLi4gXGNkb3QgXHBpX24gPSBccHJvZF97aT0xfV57bn0gXHBpX2kkLiANCiAgDQogIFRoaXMgc2NvcmUgaXMgZXF1YWwgdG8gdGhlIHByb2JhYmlsaXR5IHRoYXQgd2Ugd291bGQgaGF2ZSBzZWVuIHRoZSBvdXRjb21lcyB0aGF0IHdlIGhhdmUgb2JzZXJ2ZWQsIGFzc3VtaW5nIHRoYXQgdGhlIG1vZGVsIGlzIGNvcnJlY3QuIFdlIHByZWZlciBtb2RlbHMgd2l0aCBoaWdoZXIgbGlrZWxpaG9vZCBzY29yZXMuICANCg0KVGhlIGZpZ3VyZSBiZWxvdyBjb250YWlucyBiYXJzIHJlcHJlc2VudGluZyB0aGUgdmFsdWVzIG9mICRccGlfaSQgZm9yIGVhY2ggb2JzZXJ2YXRpb24gYWNjb3JkaW5nIHRvIGVhY2ggbW9kZWwuIA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCnhfZ3JpZCA8LSBzZXEoMCwgMjAwKQ0KDQpwMSA8LSAxIC8gKDEgKyBleHAoKDMgLSAwLjAzKmRmJGhvdXJzKSkpDQpwaTEgPC0gaWZlbHNlKGRmJHBhc3NlZCA9PSAxLCBwMSwgMS1wMSkNCnAyIDwtIDEgLyAoMSArIGV4cCgoMTAgLSAwLjEqZGYkaG91cnMpKSkNCnBpMiA8LSBpZmVsc2UoZGYkcGFzc2VkID09IDEsIHAyLCAxLXAyKQ0KDQoNCnBhcihtZnJvdz1jKDEsMikpDQoNCnBsb3QocGFzc2VkW2RmJHBhc3NlZCA9PSAxXSB+IGhvdXJzW2RmJHBhc3NlZCA9PSAxXSwgY2V4PTIsIHBjaD0yMSwgYmc9J2Nvcm5mbG93ZXJibHVlJywgDQogICAgIHhsaW09YygzMCwxODApLCB5bGltPWMoLTAuMiwgMS4yKSwgeGxhYj0nU3R1ZHkgSG91cnMnLCB5bGFiPSdQYXNzZWQnKQ0KDQoNCnNlZ21lbnRzKHgwPWhvdXJzW3Bhc3NlZD09MF0sIHkwPXAxW3Bhc3NlZD09MF0sIHkxPTEsIGNvbD0nc2FsbW9uJykNCnNlZ21lbnRzKHgwPWhvdXJzW3Bhc3NlZD09MV0sIHkwPTAsIHkxPXAxW3Bhc3NlZD09MV0sIGNvbD0nY29ybmZsb3dlcmJsdWUnKQ0KDQpsaW5lcyh4X2dyaWQsIDEgLyAoMSArIGV4cCgzIC0gMC4wMyAqIHhfZ3JpZCkpLCBsd2Q9MikNCmxpbmVzKGMoMCwyMDApLCBjKDAsMCksIGx0eT0yKQ0KbGluZXMoYygwLDIwMCksIGMoMSwxKSwgbHR5PTIpDQpwb2ludHMocGFzc2VkW2RmJHBhc3NlZCA9PSAwXSB+IGhvdXJzW2RmJHBhc3NlZCA9PSAwXSwgY2V4PTIsIHBjaD0yMSwgYmc9J3NhbG1vbicpDQpwb2ludHMocGFzc2VkW2RmJHBhc3NlZCA9PSAxXSB+IGhvdXJzW2RmJHBhc3NlZCA9PSAxXSwgY2V4PTIsIHBjaD0yMSwgYmc9J2Nvcm5mbG93ZXJibHVlJykNCg0KcGxvdChwYXNzZWRbZGYkcGFzc2VkID09IDFdIH4gaG91cnNbZGYkcGFzc2VkID09IDFdLCBjZXg9MiwgcGNoPTIxLCBiZz0nY29ybmZsb3dlcmJsdWUnLCANCiAgICAgeGxpbT1jKDMwLDE4MCksIHlsaW09YygtMC4yLCAxLjIpLCB4bGFiPSdTdHVkeSBIb3VycycsIHlsYWI9J1Bhc3NlZCcpDQoNCnNlZ21lbnRzKHgwPWhvdXJzW3Bhc3NlZD09MF0sIHkwPXAyW3Bhc3NlZD09MF0sIHkxPTEsIGNvbD0nc2FsbW9uJykNCnNlZ21lbnRzKHgwPWhvdXJzW3Bhc3NlZD09MV0sIHkwPTAsIHkxPXAyW3Bhc3NlZD09MV0sIGNvbD0nY29ybmZsb3dlcmJsdWUnKQ0KDQpsaW5lcyh4X2dyaWQsIDEgLyAoMSArIGV4cCgxMCAtIDAuMTAgKiB4X2dyaWQpKSwgbHdkPTIpDQpsaW5lcyhjKDAsMjAwKSwgYygwLDApLCBsdHk9MikNCmxpbmVzKGMoMCwyMDApLCBjKDEsMSksIGx0eT0yKQ0KcG9pbnRzKHBhc3NlZFtkZiRwYXNzZWQgPT0gMF0gfiBob3Vyc1tkZiRwYXNzZWQgPT0gMF0sIGNleD0yLCBwY2g9MjEsIGJnPSdzYWxtb24nKQ0KcG9pbnRzKHBhc3NlZFtkZiRwYXNzZWQgPT0gMV0gfiBob3Vyc1tkZiRwYXNzZWQgPT0gMV0sIGNleD0yLCBwY2g9MjEsIGJnPSdjb3JuZmxvd2VyYmx1ZScpDQoNCnBhcihtZnJvdz1jKDEsMSkpDQpgYGANCg0KDQpJbiB0aGUgY2VsbCBiZWxvdywgd2UgY2FsY3VsYXRlIHRoZSBsaWtlbGlob29kIHNjb3JlcyBmb3IgYm90aCBvZiB0aGUgdHdvIG1vZGVscyB3ZSBhcmUgY29uc2lkZXJpbmcuDQoNCmBgYHtyfQ0KejEgPC0gMyAtIDAuMDMqZGYkaG91cnMNCnAxIDwtIDEgLyAoMSArIGV4cCgtejEpKQ0KcGkxIDwtIGlmZWxzZShkZiRwYXNzZWQgPT0gMSwgcDEsIDEtcDEpDQpsaWsxIDwtIHByb2QocGkxKQ0KDQp6MiA8LSAxMCAtIDAuMSpkZiRob3Vycw0KcDIgPC0gMSAvICgxICsgZXhwKC16MikpDQpwaTIgPC0gaWZlbHNlKGRmJHBhc3NlZCA9PSAxLCBwMiwgMS1wMikNCmxpazIgPC0gcHJvZChwaTIpDQoNCmNhdCgnTGlrZWxpaG9vZCBzY29yZSBmb3IgTW9kZWwgMTogJywgbGlrMSwgJ1xuJywgDQogICAgJ0xpa2VsaWhvb2Qgc2NvcmUgZm9yIE1vZGVsIDI6ICcsIGxpazIsIHNlcD0nJykNCmBgYA0KDQojIyMjICoqTmVnYXRpdmUgTG9nLUxpa2VsaWhvb2QqKg0KDQpBbHRob3VnaCB3ZSBjYW4sIGluIHRoZW9yeSwgY29tcGFyZSBtb2RlbHMgdXNpbmcgdGhlaXIgbGlrZWxpaG9vZCBzY29yZSwgJEwkLCBpdCBpcyBtb3JlIGNvbW1vbiBpbiBwcmFjdGljZSB0byBzY29yZSBtb2RlbHMgdXNpbmcgKipuZWdhdGl2ZSBsb2ctbGlrZWxpaG9vZCoqLCAkTkxMJC4gQnkgZGVmaW5pdGlvbiwgJE5MTCA9IC1cbG4oTCkkLCBidXQgaXQgaXMgYWN0dWFsbHkgY2FsY3VsYXRlZCB1c2luZyB0aGUgZm9sbG93aW5nIHN0ZXBzOg0KDQoNCiogKipTdGVwIDE6KiogQ2FsY3VsYXRlICRwX2kgPSBwKHhfaSkkIGZvciBlYWNoIHRyYWluaW5nIG9ic2VydmF0aW9uLiANCg0KKiAqKlN0ZXAgMjoqKiBDYWxjdWxhdGUgJFxwaV9pJCBmb3IgZWFjaCB0cmFpbmluZyBvYnNlcnZhdGlvbi4NCg0KICBGb3IgZWFjaCB0cmFpbmluZyBvYnNlcnZhdGlvbiwgY2FsY3VsYXRlOg0KICANCiAgJFxwaV9pID1cbGVmdFx7XGJlZ2lue2FycmF5fXtsbH0gcF9pICYgXHRleHR7aWYgfX4geV9pID0gMSBcXCAxIC0gcF9pICYgXHRleHR7aWYgfSB+eV9pID0gMCBcZW5ke2FycmF5fVxyaWdodC4kDQogIA0KKiAqKlN0ZXAgMzoqKiBDYWxjdWxhdGUgTmVnYXRpdmUgTG9nLUxpa2VsaWhvb2QsICROTEwkLg0KDQogIFRoZSBtb2RlbCdzICoqbmVnYXRpdmUgbG9nLWxpa2VsaWhvb2QqKiBzY29yZSBpcyBnaXZlbiBieTogDQogIA0KICAkTkxMID0gLSBcbGVmdFtcbG4oXHBpXzEpICsgXGxuKFxwaV8yKSArIC4uLiArIFxsbihccGlfbilccmlnaHRdID0gLSBcc3VtX3tpPTF9Xm4gXGxuKFxwaV9pKSQuIA0KICANClNpbmNlIHRoZSBuYXR1cmFsIGxvZ2FyaXRobSBpcyBhbiBpbmNyZWFzaW5nIGZ1bmN0aW9uLCBtYXhpbWl6aW5nIGxpa2VsaWhvb2QgaXMgZXF1aXZhbGVudCB0byBtYXhpbWl6aW5nIGxvZy1saWtlbGlob29kLCBhbmQgdGh1cyBlcXVpdmFsZW50IHRvIG1pbmltaXppbmcgbmVnYXRpdmUgbG9nLWxpa2VsaWhvb2QuIFRoZXJlZm9yZSwgd2UgcHJlZmVyIG1vZGVscyB3aXRoICoqbG93KiogJE5MTCQgc2NvcmVzLiANCg0KV2UgY2FsY3VsYXRlIHRoZSBuZWdhdGl2ZSBsb2ctbGlrZWxpaG9vZCBvZiBlYWNoIG9mIG91ciBwcm9wb3NlZCBtb2RlbHMgaW4gdGhlIGNlbGwgYmVsb3cuIA0KDQpgYGB7cn0NCm5sbDEgPSAtc3VtKGxvZyhwaTEpKSAgICMgbmxsID0gLWxvZyhsaWsxKQ0KbmxsMiA9IC1zdW0obG9nKHBpMikpICAgIyBubGwgPSAtbG9nKGxpazIpDQoNCmNhdCgnTmVnYXRpdmUgTG9nLUxpa2VsaWhvb2Qgc2NvcmUgZm9yIE1vZGVsIDE6ICcsIG5sbDEsICdcbicsIA0KICAgICdOZWdhdGl2ZSBMb2ctTGlrZWxpaG9vZCBzY29yZSBmb3IgTW9kZWwgMjogJywgbmxsMiwgc2VwPScnKQ0KYGBgDQoNCg0KIyMjICoqRmluZGluZyBPcHRpbWFsIExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwqKg0KDQpSIHByb3ZpZGVzIGEgYGdsbSgpYCBmdW5jdGlvbiBmb3IgcGVyZm9ybWluZyBsb2dpc3RpYyByZWdyZXNzaW9uLiBUaGUgc3ludGF4IGZvciB1c2luZyB0aGlzIGZ1bmN0aW9uIGlzIGlsbHVzdHJhdGVkIGluIHRoZSBjZWxsIGJlbG93LCB3aGVyZSB3ZSBjcmVhdGUgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGZvciBvdXIgZXhhbSBwcmVwIGRhdGEuIA0KDQpgYGB7cn0NCm1vZCA8LSBnbG0ocGFzc2VkIH4gaG91cnMsIGRhdGE9ZGYsIGZhbWlseT1iaW5vbWlhbChsaW5rPSJsb2dpdCIpKQ0Kc3VtbWFyeShtb2QpDQpgYGANCg0KV2UgY2FuIHNlZSBmcm9tIHRoZSBtb2RlbCBzdW1tYXJ5IHRoYXQgdGhlIG9wdGltYWwgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBmb3IgdGhpcyBkYXRhc2V0IGlzIGdpdmVuIGJ5OiANCg0KJCRaID0gLTkuMyArIDAuMDgyIFxjZG90IFgkJA0KDQokJHAoWCkgPSBcc2lnbWEoWikgPSBcZnJhY3sxfXsxICsgZV57OS4zIC0gMC4wODMgXGNkb3QgWH19JCQNCg0KVGhlIGZpZ3VyZSBiZWxvdyBzaG93cyBhIHBsb3Qgb2YgdGhlIG9wdGltYWwgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBmb3IgdGhpcyBkYXRhc2V0LiANCg0KYGBge3IsIGVjaG89RkFMU0V9DQpwbG90KHBhc3NlZFtkZiRwYXNzZWQgPT0gMV0gfiBob3Vyc1tkZiRwYXNzZWQgPT0gMV0sIGNleD0yLCBwY2g9MjEsIGJnPSdjb3JuZmxvd2VyYmx1ZScsIA0KICAgICB4bGltPWMoMzAsMTgwKSwgeWxpbT1jKC0wLjIsIDEuMiksIHhsYWI9J1N0dWR5IEhvdXJzJywgeWxhYj0nUGFzc2VkJykNCg0KcCA8LSBwcmVkaWN0KG1vZCwgZGF0YS5mcmFtZShob3Vycz14X2dyaWQpLCB0eXBlPSJyZXNwb25zZSIpDQpsaW5lcyh4X2dyaWQsIHAsIGx3ZD0yKQ0KDQpib3VuZGFyeSA8LSAtbW9kJGNvZWZmaWNpZW50c1sxXSAvIG1vZCRjb2VmZmljaWVudHNbMl0NCmxpbmVzKGMoYm91bmRhcnksIGJvdW5kYXJ5KSwgYygwLDEpLCBsdHk9MiwgY29sPSJyZWQiKQ0KbGluZXMoYygwLDIwMCksIGMoMCwwKSwgbHR5PTIpDQpsaW5lcyhjKDAsMjAwKSwgYygxLDEpLCBsdHk9MikNCnBvaW50cyhwYXNzZWRbZGYkcGFzc2VkID09IDBdIH4gaG91cnNbZGYkcGFzc2VkID09IDBdLCBjZXg9MiwgcGNoPTIxLCBiZz0nc2FsbW9uJykNCnBvaW50cyhwYXNzZWRbZGYkcGFzc2VkID09IDFdIH4gaG91cnNbZGYkcGFzc2VkID09IDFdLCBjZXg9MiwgcGNoPTIxLCBiZz0nY29ybmZsb3dlcmJsdWUnKQ0KYGBgDQoNCldlIGNhbiB1c2UgdGhlIGBwcmVkaWN0KClgIGZ1bmN0aW9uIGFsb25nIHdpdGggdGhlIHBhcmFtZXRlciBgdHlwZT0icmVzcG9uc2UiYCB0byBzZWUgdGhlIGVzdGltYXRlZCBwcm9iYWJpbGl0aWVzIG9mIHBhc3NpbmcgZm9yIGVhY2ggc3R1ZGVudCBpbiBvdXIgdHJhaW5pbmcgc2V0LCBhY2NvcmRpbmcgdG8gb3VyIG1vZGVsIA0KDQpgYGB7cn0NCnByZWRpY3QobW9kLCBkZiwgdHlwZT0icmVzcG9uc2UiKQ0KYGBgDQoNCldlIHdpbGwgbm93IGNhbGN1bGF0ZSB0aGUgbGlrZWxpaG9vZCBhbmQgbG9nLWxpa2VsaWhvb2Qgc2NvcmVzIGZvciBvdXIgb3B0aW1hbCBtb2RlbC4gDQoNCmBgYHtyfQ0KcCA8LSBwcmVkaWN0KG1vZCwgZGYsIHR5cGU9InJlc3BvbnNlIikNCnBpIDwtIGlmZWxzZShkZiRwYXNzZWQgPT0gMSwgcCwgMS1wKQ0KbGlrIDwtIHByb2QocGkpDQpubGwgPC0gLWxvZyhsaWspDQoNCmNhdCgnTG9nLUxpa2VsaWhvb2QgZm9yIE9wdGltYWwgTW9kZWwgICAgICAgICA6ICcsIGxpaywgJ1xuJywgDQogICAgJ05lZ2F0aXZlIExvZy1MaWtlbGlob29kIGZvciBPcHRpbWFsIE1vZGVsOiAnLCBubGwsIHNlcD0nJykNCg0KYGBgDQoNCkxldCdzIG5vdyBjb25zaWRlciBhIHNpbWlsYXIgZXhhbXBsZSwgYnV0IHdpdGggbW9yZSBvYnNlcnZhdGlvbnMuIA0KDQojIyMgKipFeGFtcGxlIDI6IEV4YW0gUHJlcCoqDQoNClRoZSBkYXRhIGZvciB0aGlzIGV4YW1wbGUgaXMgc3RvcmVkIGluIHRoZSB0YWItc2VwYXJhdGVkIGZpbGUgYGRhdGEvZXhhbV9wcmVwLnR4dGAuIFdlIGxvYWQgdGhlIGRhdGEgYW5kIHZpZXcgYSBzdW1tYXJ5IG9mIHRoZSBkYXRhLiANCg0KYGBge3J9DQpleGFtIDwtIHJlYWQudGFibGUoImRhdGEvZXhhbV9wcmVwLnR4dCIsIHNlcD0iXHQiLCBoZWFkZXI9VFJVRSkNCmV4YW0kUGFzc2VkIDwtIGZhY3RvcihleGFtJFBhc3NlZCkNCnN1bW1hcnkoZXhhbSkNCmBgYA0KDQpXZSB3aWxsIGV4cGxvcmUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGhvdXJzIHN0dWRpZWQgYW5kIGV4YW0gb3V0Y29tZSBieSBjcmVhdGluZyBhIGJveHBsb3QuIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgZWNobz1GQUxTRX0NCmxpYnJhcnkoZ2dwbG90MikNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChleGFtLCBhZXMoeD1QYXNzZWQsIHk9SG91cnMsIGZpbGw9UGFzc2VkKSkgKyBnZW9tX2JveHBsb3QoKQ0KYGBgDQoNCldlIHdpbGwgdXNlIHRoZSBgZ2xtKClgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwuIA0KDQoNCmBgYHtyfQ0KZXhhbV9tb2QgPC0gZ2xtKFBhc3NlZCB+IEhvdXJzLCBleGFtLCBmYW1pbHk9Ymlub21pYWwobGluaz0ibG9naXQiKSkNCnN1bW1hcnkoZXhhbV9tb2QpDQpgYGANCg0KV2UgY2FuIHNlZSBmcm9tIHRoZSBtb2RlbCBzdW1tYXJ5IHRoYXQgdGhlIG9wdGltYWwgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBmb3IgdGhpcyBkYXRhc2V0IGlzIGdpdmVuIGJ5OiANCg0KJCRaID0gLTMuMTkgKyAwLjAyIFgkJA0KDQokJHAoWCkgPSBcc2lnbWEoWikgPSBcZnJhY3sxfXsxICsgZV57My4xOSAtIDAuMDIgIFh9fSQkDQoNCkluIHRoZSBjZWxsIGJlbG93LCB3ZSB3aWxsIHVzZSBvdXIgbW9kZWwgdG8gZXN0aW1hdGUgdGhlIHByb2JhYmlsaXR5IHRoYXQgYSBzdHVkZW50IHdpbGwgcGFzcyB0aGUgZXhhbSBpZiB0aGV5IGhhdmUgc3R1ZGllZCBmb3IgZWFjaCBvZiB0aGUgZm9sbG93aW5nIG51bWJlcnMgb2YgaG91cnM6IDAsIDUwLCAxMDAsIDE1MCwgMjAwDQoNCmBgYHtyfQ0KbmQgPC0gZGF0YS5mcmFtZShIb3VycyA9IGMoMCwgNTAsIDEwMCwgMTUwLCAyMDApKQ0KcHJvYiA8LSBwcmVkaWN0KGV4YW1fbW9kLCBuZXdkYXRhPW5kLCB0eXBlPSJyZXNwb25zZSIpDQoNCnByb2INCmBgYA0KDQojIyMgKipUcmFpbmluZyBBY2N1cmFjeSoqDQoNCkEgY29tbW9uIHdheSB0byBldmFsdWF0ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgYSBjbGFzc2lmaWNhdGlvbiBtb2RlbCBpcyB0byBjYWxjdWxhdGUgaXRzICoqYWNjdXJhY3kqKiBvbiBzb21lIHNldC4gVGhlIGFjY3VyYWN5IG9uIGEgcGFydGljdWxhciBzZXQgaXMgZXF1YWwgdG8gdGhlIHByb3BvcnRpb24gb2Ygb2JzZXJ2YXRpb25zIGluIHRoZSBzZXQgZm9yIHdoaWNoIHRoZSBtb2RlbCBwcmVkaWN0cyB0aGUgY29ycmVjdCBjbGFzcy4gDQoNCkluIHRoZSBjZWxsIGJlbG93LCB3ZSBjYWxjdWxhdGUgb3VyIG1vZGVsJ3MgYWNjdXJhY3kgb24gdGhlIHRyYWluaW5nIHNldC4gDQoNCmBgYHtyfQ0KcHJvYiA8LSBwcmVkaWN0KGV4YW1fbW9kLCBleGFtLCB0eXBlPSJyZXNwb25zZSIpDQpwcmVkIDwtIGlmZWxzZShwcm9iIDwgMC41LCAwLCAxKQ0KdHJfYWNjIDwtIG1lYW4ocHJlZCA9PSBleGFtJFBhc3NlZCkNCmNhdCgnVHJhaW5pbmcgQWNjdXJhY3k6ICcsIHRyX2FjYywgc2VwPScnKQ0KYGBgDQoNCg0KIyMjICoqSW50ZXJwcmV0aW5nIE1vZGVsIENvZWZmaWNpZW50cyoqDQoNCkludGVycHJldGluZyB0aGUgY29lZmZpY2llbnRzIGluIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBpcyBub3QgYXMgZGlyZWN0IGFzIGluIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwuIEhvd2V2ZXIsIGl0IGlzIHBvc3NpYmxlIHRvIGRvLiBCZWNhdXNlIGRpc2N1c3NpbmcgaG93IHdlIHdvdWxkIGludGVycHJldCB0aGVzZSBjb2VmZmljaWVudHMsIHdlIG5lZWQgdG8gZGlzY3VzcyB0aGUgY29uY2VwdCBvZiBvZGRzIHJhdGlvcy4gDQoNCiMjIyMgKipPZGRzIFJhdGlvcyoqDQoNClRoZSAqKm9kZHMqKiBvZiBhbiBldmVudCBvY2N1cnJpbmcgaXMgZGVmaW5lZCBhcyB0aGUgcHJvYmFiaWxpdHkgb2YgYW4gZXZlbnQgb2NjdXJyaW5nIGRpdmlkZWQgYnkgdGhlIHByb2JhYmlsaXR5IG9mIHRoZSBldmVudCBub3Qgb2NjdXJyaW5nLiBUaGUgcHJvYmFiaWxpdHkgb2YgYW4gZXZlbnQgJFBfRSQsIGFuZCB0aGUgb2RkcyBvZiBhbiBldmVudCAkT19FJCBhcmUgcmVsYXRlZCBieSB0aGUgZm9sbG93aW5nIGV4cHJlc3Npb25zOg0KDQokJE9fRSA9IFxmcmFje1BfRX17MSAtIFBfRX0gXGhzcGFjZXszIGVtfSBQX0UgPSBcZnJhY3tPX0V9ezEgKyBPX0V9JCQNCg0KWW91IG1pZ2h0IGJlIGZhbWlsaWFyIHdpdGggdGhlIGNvbmNlcHQgb2Ygb2RkcyBmcm9tIHNwb3J0cyBvciBnYW1lcyBvZiBjaGFuY2UuIEFzc3VtZSB0aGF0IGFuIGV2ZW50IGlzIHR3aWNlIGFzIGxpa2VseSB0byBvY2N1ciBhcyBub3QuIFRoZW4gd2UgbWlnaHQgc2F5IHRoYXQgdGhlIGV2ZW50IGhhcyAiMiB0byAxIG9kZHMiLiBUaGUgb2RkcyBvZiBzdWNoIGFuIGV2ZW50IGlzIG9mdGVuIGRlbm90ZWQgYnkgMjoxIG9yIDIvMS4gVGhpcyBub3Rpb24gb2Ygb2RkcyBpcyBlcXVpdmFsZW50IHRvIHRoZSBvbmUgdGhhdCB3ZSBkZWZpbmVkIGFib3ZlLiANCg0KVGhlIHRhYmxlIGJlbG93IHNob3dzIHNldmVyYWwgd2F5cyBvZiBleHByZXNzaW5nIG9kZHMsIGFzIHdlbGwgYXMgdGhlIHByb2JhYmlsaXR5IGFzc29jaWF0ZWQgd2l0aCBlYWNoIG9kZHMgdmFsdWUuIA0KDQo8Y2VudGVyPg0KPHRhYmxlIHdpZHRoPTQwMD4NCjx0cj4NCjx0ZD4gKipPZGRzKiogPC90ZD48dGQ+ICoqT2RkcyoqIDwvdGQ+PHRkPiAqKk9kZHMqKiA8L3RkPjx0ZD4gKipQcm9iKiogPC90ZD4NCjwvdHI+DQo8dHI+DQo8dGQ+IDE6MSA8L3RkPjx0ZD4gMS8xIDwvdGQ+PHRkPiAxLjAwIDwvdGQ+PHRkPiAwLjUwIDwvdGQ+DQo8L3RyPg0KPHRyPg0KPHRkPiAyOjEgPC90ZD48dGQ+IDIvMSA8L3RkPjx0ZD4gMi4wMCA8L3RkPjx0ZD4gMC42NyA8L3RkPg0KPC90cj4NCjx0cj4NCjx0ZD4gMToyIDwvdGQ+PHRkPiAxLzIgPC90ZD48dGQ+IDAuNTAgPC90ZD48dGQ+IDAuMzMgPC90ZD4NCjwvdHI+DQo8dHI+DQo8dGQ+IDE6MyA8L3RkPjx0ZD4gMS8zIDwvdGQ+PHRkPiAwLjMzIDwvdGQ+PHRkPiAwLjI1IDwvdGQ+DQo8L3RyPg0KPC90YWJsZT4NCjwvY2VudGVyPg0KPC9icj4NCg0KTm90ZSB0aGF0IG9kZHMgYXJlIGFsd2F5cyBwb3NpdGl2ZSwgYnV0IGNhbiBiZSBhcmJpdHJhcmlseSBsYXJnZS4gDQoNCiMjIyMgKipBbHRlcm5hdGUgRm9ybSBGb3IgTG9naXN0aWMgUmVncmVzc2lvbiBNb2RlbCoqDQoNCkNvbnNpZGVyIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBvZiB0aGUgZm9ybTogDQoNCiQkcChYKSA9IFxmcmFjezF9ezEgKyBlXnstXGxlZnQoXGJldGFfMCArIFxiZXRhXzEgIFggXHJpZ2h0KX19JCQNCkFmdGVyIGEgbGl0dGxlIGJpdCBvZiBhbGdlYnJhaWMgbWFuaXB1bGF0aW9uLCB3ZSBjYW4gcmV3cml0ZSB0aGlzIGV4cHJlc3Npb24gaW4gdGhlIGZvbGxvd2luZyBmb3JtOg0KDQokJFxsblxsZWZ0W1xmcmFje3AoWCl9ezEgLSBwKFgpfSBccmlnaHRdID0gXGJldGFfMCArIFxiZXRhXzEgIFggJCQNCg0KTm90aWNlIHRoYXQgdGhlIGxlZnQtaGFuZCBzaWRlIG9mIHRoaXMgZm9ybXVsYSBpcyB0aGUgbmF0dXJhbCBsb2cgb2YgdGhlIG9kZHMgdGhhdCAkWT0xJCBnaXZlbiAkWCQuIFRoaXMgcXVhbnRpdHkgaXMgb2Z0ZW4gcmVmZXJyZWQgdG8gYXMgdGhlICoqbG9nLW9kZHMqKi4gDQoNClRoaXMgc2hvd3MgdGhhdCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgcmVhbGx5IGlzIGEgbGluZWFyIG1vZGVsIGZvciBsb2ctb2Rkcy4gDQoNCiMjIyMgKipDb2VmZmljaWVudCBJbnRlcnByZXRhdGlvbioqDQoNClRoZSBpbnRlcnByZXRhdGlvbiBvZiAkXGJldGFfMSQgaW4gYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGlzIGFzIGZvbGxvd3M6IElmIHdlIGluY3JlYXNlIHRoZSBwcmVkaWN0b3IgJFgkIGJ5IGEgc2luZ2xlIHVuaXQsIHRoYXQgd2lsbCByZXN1bHQgaW4gYW4gaW5jcmVhc2Ugb2YgJFxiZXRhXzEkIHVuaXRzIGluIGxvZy1vZGRzLCBhbmQgYXBwcm94aW1hdGVseSBhICRcYmV0YV8xKjEwMFwlJCBpbmNyZWFzZSBpbiB0aGUgb2RkcyBvZiAkWT0xJC4NCg0KUmV0dXJuaW5nIHRvIG91ciBleGFtIHByZXAgZXhhbXBsZSwgd2UgcmVjYWxsIHRoYXQgb3VyIG1vZGVsIHdhcyBnaXZlbiBieToNCg0KJCRwKFgpID0gXGZyYWN7MX17MSArIGVeezMuMTkgLSAwLjAyICBYfX0kJA0KDQpUaGlzIGNhbiBiZSByZXdyaXR0ZW4gYXM6DQoNCg0KJCRcbG5cbGVmdFtcZnJhY3twKFgpfXsxIC0gcChYKX0gXHJpZ2h0XSA9IC0zLjE5ICsgMC4wMiAgWCAkJA0KDQpJdCBmb2xsb3dzIHRoYXQgZWFjaCBhZGRpdGlvbmFsIGhvdXIgb2Ygc3R1ZHlpbmcgd2lsbCBpbmNyZWFzZSB0aGUgb2RkcyAobm90IHRoZSBwcm9iYWJpbGl0eSkgb2YgcGFzc2luZyB0aGUgZXhhbSBieSBhYm91dCAyJSAob3IgaW4gb3RoZXIgd29yZHMsIGJ5IGEgZmFjdG9yIG9mIDEuMDIpLg0KDQoNCiMjIyAqKkV4YW1wbGUgMzogUGltYSBEaWFiZXRlcyBEYXRhc2V0KioNCg0KRm9yIHRoaXMgZXhhbXBsZSwgd2Ugd2lsbCBiZSB3b3JraW5nIHdpdGggdGhlIFBpbWEgRGlhYmV0ZXMgRGF0YXNldC4gVGhpcyBkYXRhc2V0IGlzIG9yaWdpbmFsbHkgZnJvbSB0aGUgTmF0aW9uYWwgSW5zdGl0dXRlIG9mIERpYWJldGVzIGFuZCBEaWdlc3RpdmUgYW5kIEtpZG5leSBEaXNlYXNlcy4gVGhlIG9iamVjdGl2ZSBpcyB0byBwcmVkaWN0IGJhc2VkIG9uIGRpYWdub3N0aWMgbWVhc3VyZW1lbnRzIHdoZXRoZXIgYSBwYXRpZW50IGhhcyBkaWFiZXRlcy4gQWxsIHBhdGllbnRzIGFyZSBmZW1hbGVzIGF0IGxlYXN0IDIxIHllYXJzIG9sZCBvZiBQaW1hIEluZGlhbiBoZXJpdGFnZS4gDQoNClRoZSBjb2x1bW5zIGluIHRoaXMgZGF0YXNldCBhcmUgZGVzY3JpYmVkIGJlbG93LiANCg0KKiAqKlByZWduYW5jaWVzKio6IE51bWJlciBvZiB0aW1lcyBwcmVnbmFudA0KKiAqKkdsdWNvc2UqKjogUGxhc21hIGdsdWNvc2UgY29uY2VudHJhdGlvbiBhIDIgaG91cnMgaW4gYW4gb3JhbCBnbHVjb3NlIHRvbGVyYW5jZSB0ZXN0DQoqICoqQmxvb2RQcmVzc3VyZSoqOiBEaWFzdG9saWMgYmxvb2QgcHJlc3N1cmUgKG1tIEhnKQ0KKiAqKlNraW5UaGlja25lc3MqKjogVHJpY2VwcyBza2luIGZvbGQgdGhpY2tuZXNzIChtbSkNCiogKipJbnN1bGluKio6IDItSG91ciBzZXJ1bSBpbnN1bGluIChtdSBVL21sKQ0KKiAqKkJNSSoqOiBCb2R5IG1hc3MgaW5kZXggKHdlaWdodCBpbiBrZy8oaGVpZ2h0IGluIG0pXjIpDQoqICoqRGlhYmV0ZXNQZWRpZ3JlZUZ1bmN0aW9uKio6IERpYWJldGVzIHBlZGlncmVlIGZ1bmN0aW9uDQoqICoqQWdlKio6IEFnZSAoeWVhcnMpDQoqICoqT3V0Y29tZSoqOiBDbGFzcyB2YXJpYWJsZSAoMCBvciAxKQ0KDQojIyMjICoqTG9hZGluZyB0aGUgRGF0YSoqDQoNCldlIHdpbGwgcmVhZCBpbiB0aGUgZGF0YSwgY29udmVydCB0aGUgKipPdXRjb21lKiogY29sdW1uIHRvIGEgZmFjdG9yIHZhcmlhYmxlLCBhbmQgdGhlbiBwcmludCBhIHN1bW1hcnkgb2YgdGhlIGRhdGFzZXQuIA0KDQpgYGB7cn0NCnBpbWEgPC0gcmVhZC50YWJsZSgiZGF0YS9kaWFiZXRlcy5jc3YiLCBzZXA9IiwiLCBoZWFkZXI9VFJVRSkNCnBpbWEkT3V0Y29tZSA8LSBmYWN0b3IocGltYSRPdXRjb21lKQ0Kc3VtbWFyeShwaW1hKQ0KYGBgDQojIyMjICoqVmlzdWFsaXppbmcgdGhlIERhdGEqKg0KDQpUaGUgZmlndXJlIGJlbG93IGRpc3BsYXlzIGRlbnNpdHkgcGxvdHMgZm9yIGVhY2ggb2Ygb3VyIHByZWRpY3RvcnMsIGFuZCBmb3IgZWFjaCB2YWx1ZSBvZiBvdXIgcmVzcG9uc2UgdmFyaWFibGUuIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgZWNobz1GQUxTRX0NCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KYGBgDQoNCg0KYGBge3IsIGVjaG89RkFMU0V9DQptZWx0KHBpbWEsIGlkLnZhcnMgPSAiT3V0Y29tZSIpICU+JQ0KICBnZ3Bsb3QoYWVzKHZhbHVlLCBmaWxsPU91dGNvbWUpKSArIGZhY2V0X3dyYXAofiB2YXJpYWJsZSwgc2NhbGU9ImZyZWUiKSArIA0KICBnZW9tX2RlbnNpdHkoYWxwaGE9MC40KQ0KYGBgDQoNCiMjIyMgKipTcGxpdHRpbmcgdGhlIERhdGEqKg0KDQpXZSB3aWxsIGNvbnNpZGVyIG11bHRpcGxlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWxzIG9uIHRoaXMgZGF0YXNldC4gV2Ugd2lsbCBjcmVhdGUgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gc2V0cy4gVGhlIG1vZGVscyB3aWxsIGJlIHRyYWluZWQgb24gdGhlIHRyYWluaW5nIHNldCwgYW5kIGNvbXBhcmVkIGJhc2VkIG9uIHRoZWlyIGFjY3VyYWN5IG9uIHRoZSB2YWxpZGF0aW9uIHNldC4gDQoNClRoZSBgY2FyZXRgIHBhY2thZ2UgcHJvdmlkZXMgYSBmdW5jdGlvbiBjYWxsZWQgYGNyZWF0ZURhdGFQYXJ0aXRpb24oKWAgd2hpY2ggY2FuIGJlIHVzZWQgdG8gY3JlYXRlIGEgdHJhaW4vdmFsaWRhdGlvbiBzcGxpdC4gQnkgZGVmYXVsdCwgdGhpcyBmdW5jdGlvbiBjcmVhdGVzIGEgc3RyYXRpZmllZCByYW5kb20gc3BsaXQuIEFzIGEgcmVzdWx0LCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSByZXNwb25zZSB2YXJpYWJsZSB3aWxsIGJlIHJvdWdobHkgdGhlIHNhbWUgaW4gdGhlIHRyYWluaW5nIHNldCBhbmQgdmFsaWRhdGlvbiBzZXQuIA0KDQpJbiB0aGUgY2VsbCBiZWxvdywgd2UgY3JlYXRlIGEgNzAvMzAgc3BsaXQgb2Ygb3VyIGRhdGFzZXQuIA0KDQpgYGB7cn0NCmxpYnJhcnkoY2FyZXQpDQpzZXQuc2VlZCgxKQ0KdHJhaW4uaW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihwaW1hJE91dGNvbWUsIHAgPSAuNywgbGlzdD1GQUxTRSkNCnRyYWluIDwtIHBpbWFbIHRyYWluLmluZGV4LF0NCnZhbGlkICA8LSBwaW1hWy10cmFpbi5pbmRleCxdDQoNCnN1bW1hcnkodHJhaW4kT3V0Y29tZSkNCnN1bW1hcnkodmFsaWQkT3V0Y29tZSkNCmBgYA0KDQojIyMjICoqQ3JlYXRlIE1vZGVscyoqDQoNCldlIHdpbGwgbm93IHRyYWluIG91ciBtb2RlbCBvbiB0aGUgdHJhaW5pbmcgc2V0LiANCg0KYGBge3J9DQpwaW1hX21vZF8xIDwtIGdsbShPdXRjb21lIH4gLiwgdHJhaW4sIGZhbWlseT1iaW5vbWlhbChsaW5rPSJsb2dpdCIpKQ0Kc3VtbWFyeShwaW1hX21vZF8xKQ0KYGBgDQoNCkl0IGFwcGVhcnMgdGhhdCB0aHJlZSBvZiBvdXIgcHJlZGljdG9ycywgYEluc3VsaW5gLCBgU2tpblRoaWNrbmVzc2AsIGFuZCBgQmxvb2RQcmVzc3VyZWAgZG8gbm90IGhhdmUgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBlZmZlY3RzIG9uIHRoZSBwcmVzY2llbmNlIG9mIGRpYWJldGVzLiBXZSB3aWxsIHJlbW92ZSB0aGVtIG9uZSBhdCBhIHRpbWUuIA0KDQpgYGB7cn0NCnBpbWFfbW9kXzIgPC0gZ2xtKE91dGNvbWUgfiAuIC0gSW5zdWxpbiwgdHJhaW4sIGZhbWlseT1iaW5vbWlhbChsaW5rPSJsb2dpdCIpKQ0Kc3VtbWFyeShwaW1hX21vZF8yKQ0KYGBgDQoNCmBgYHtyfQ0KcGltYV9tb2RfMyA8LSBnbG0oT3V0Y29tZSB+IC4gLSBJbnN1bGluIC0gU2tpblRoaWNrbmVzcywgdHJhaW4sIGZhbWlseT1iaW5vbWlhbChsaW5rPSJsb2dpdCIpKQ0Kc3VtbWFyeShwaW1hX21vZF8zKQ0KYGBgDQoNCmBgYHtyfQ0KcGltYV9tb2RfNCA8LSBnbG0oT3V0Y29tZSB+IC4gLSBJbnN1bGluIC0gU2tpblRoaWNrbmVzcyAtIEJsb29kUHJlc3N1cmUsIHRyYWluLCBmYW1pbHk9Ymlub21pYWwobGluaz0ibG9naXQiKSkNCnN1bW1hcnkocGltYV9tb2RfNCkNCmBgYA0KDQpOb3RpY2UgdGhhdCBhbGwgcHJlZGljdG9ycyBhcmUgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBpbiBvdXIgZm91cnRoIG1vZGVsLiANCg0KIyMjIyAqKkNhbGN1bGF0ZSBUcmFpbmluZyBhbmQgVmFsaWRhdGlvbiBBY2N1cmFjaWVzKioNCg0KV2Ugd2lsbCBub3cgY2FsY3VsYXRlIHRoZSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBhY2N1cmFjaWVzIGZvciBlYWNoIG9mIG91ciBmb3VyIG1vZGVscy4gVGhlIGNlbGwgYmVsb3cgc2hvd3MgdGhlIGNvZGUgZm9yIGNhbGN1bGF0aW5nIHRoZXNlIG1ldHJpY3MgdXNpbmcgTW9kZWwgMS4gVGhlIHNhbWUgdmFsdWVzIGFyZSBhbHNvIGNhbGN1bGF0ZWQgZm9yIHRoZSBvdGhlciB0aHJlZSBtb2RlbHMsIGJ1dCB0aGUgY29kZSBoYXMgYmVlbiBoaWRkZW4gaW4gdGhlIG91dHB1dC4gSXQgY2FuIGJlIHNlZW4gaW4gdGhlIFJtZCBmaWxlIGZvciB0aGlzIGxlc3NvbiwgaG93ZXZlci4gDQoNCg0KYGBge3J9DQp0cl9wcm9iXzEgPC0gcHJlZGljdChwaW1hX21vZF8xLCB0cmFpbiwgdHlwZT0icmVzcG9uc2UiKQ0KdHJfcHJlZF8xIDwtIGlmZWxzZSh0cl9wcm9iXzEgPCAwLjUsIDAsIDEpDQp0cl9hY2NfMSA8LSBtZWFuKHRyX3ByZWRfMSA9PSB0cmFpbiRPdXRjb21lKQ0KDQp2YV9wcm9iXzEgPC0gcHJlZGljdChwaW1hX21vZF8xLCB2YWxpZCwgdHlwZT0icmVzcG9uc2UiKQ0KdmFfcHJlZF8xIDwtIGlmZWxzZSh2YV9wcm9iXzEgPCAwLjUsIDAsIDEpDQp2YV9hY2NfMSA8LSBtZWFuKHZhX3ByZWRfMSA9PSB2YWxpZCRPdXRjb21lKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KdHJfcHJvYl8yIDwtIHByZWRpY3QocGltYV9tb2RfMiwgdHJhaW4sIHR5cGU9InJlc3BvbnNlIikNCnRyX3ByZWRfMiA8LSBpZmVsc2UodHJfcHJvYl8yIDwgMC41LCAwLCAxKQ0KdHJfYWNjXzIgPC0gbWVhbih0cl9wcmVkXzIgPT0gdHJhaW4kT3V0Y29tZSkNCg0KdmFfcHJvYl8yIDwtIHByZWRpY3QocGltYV9tb2RfMiwgdmFsaWQsIHR5cGU9InJlc3BvbnNlIikNCnZhX3ByZWRfMiA8LSBpZmVsc2UodmFfcHJvYl8yIDwgMC41LCAwLCAxKQ0KdmFfYWNjXzIgPC0gbWVhbih2YV9wcmVkXzIgPT0gdmFsaWQkT3V0Y29tZSkNCg0KdHJfcHJvYl8zIDwtIHByZWRpY3QocGltYV9tb2RfMywgdHJhaW4sIHR5cGU9InJlc3BvbnNlIikNCnRyX3ByZWRfMyA8LSBpZmVsc2UodHJfcHJvYl8zIDwgMC41LCAwLCAxKQ0KdHJfYWNjXzMgPC0gbWVhbih0cl9wcmVkXzMgPT0gdHJhaW4kT3V0Y29tZSkNCg0KdmFfcHJvYl8zIDwtIHByZWRpY3QocGltYV9tb2RfMywgdmFsaWQsIHR5cGU9InJlc3BvbnNlIikNCnZhX3ByZWRfMyA8LSBpZmVsc2UodmFfcHJvYl8zIDwgMC41LCAwLCAxKQ0KdmFfYWNjXzMgPC0gbWVhbih2YV9wcmVkXzMgPT0gdmFsaWQkT3V0Y29tZSkNCg0KdHJfcHJvYl80IDwtIHByZWRpY3QocGltYV9tb2RfNCwgdHJhaW4sIHR5cGU9InJlc3BvbnNlIikNCnRyX3ByZWRfNCA8LSBpZmVsc2UodHJfcHJvYl80IDwgMC41LCAwLCAxKQ0KdHJfYWNjXzQgPC0gbWVhbih0cl9wcmVkXzQgPT0gdHJhaW4kT3V0Y29tZSkNCg0KdmFfcHJvYl80IDwtIHByZWRpY3QocGltYV9tb2RfNCwgdmFsaWQsIHR5cGU9InJlc3BvbnNlIikNCnZhX3ByZWRfNCA8LSBpZmVsc2UodmFfcHJvYl80IDwgMC41LCAwLCAxKQ0KdmFfYWNjXzQgPC0gbWVhbih2YV9wcmVkXzQgPT0gdmFsaWQkT3V0Y29tZSkNCmBgYA0KDQpXZSB3aWxsIG5vdyBjb21wYXJlIHRoZSBhY2N1cmFjeSBzY29yZXMgZm9yIGVhY2ggb2Ygb3VyIG1vZGVscy4gDQoNCmBgYHtyfQ0KY2F0KCJNb2RlbCAxIFRyYWluaW5nIEFjY3VyYWN5OiAgICIsIHRyX2FjY18xLCAiXG4iLA0KICAgICJNb2RlbCAxIFZhbGlkYXRpb24gQWNjdXJhY3k6ICIsIHZhX2FjY18xLCAiXG5cbiIsDQogICAgDQogICAgIk1vZGVsIDIgVHJhaW5pbmcgQWNjdXJhY3k6ICAgIiwgdHJfYWNjXzIsICJcbiIsDQogICAgIk1vZGVsIDIgVmFsaWRhdGlvbiBBY2N1cmFjeTogIiwgdmFfYWNjXzIsICJcblxuIiwNCiAgICANCiAgICAiTW9kZWwgMyBUcmFpbmluZyBBY2N1cmFjeTogICAiLCB0cl9hY2NfMywgIlxuIiwNCiAgICAiTW9kZWwgMyBWYWxpZGF0aW9uIEFjY3VyYWN5OiAiLCB2YV9hY2NfMywgIlxuXG4iLA0KICAgIA0KICAgICJNb2RlbCA0IFRyYWluaW5nIEFjY3VyYWN5OiAgICIsIHRyX2FjY180LCAiXG4iLA0KICAgICJNb2RlbCA0IFZhbGlkYXRpb24gQWNjdXJhY3k6ICIsIHZhX2FjY180LCBzZXA9IiINCikNCg0KYGBgDQoNCk5vdGljZSB0aGF0IE1vZGVscyAyIGFuZCA0IGFyZSBhY3R1YWxseSB0aWVkIGluIHRoZWlyIHBlcmZvcm1hbmNlIG9uIHRoZSB2YWxpZGF0aW9uIHNldCwgYWx0aG91Z2ggdGhlIHNjb3JlIGlzIHNpbWlsYXIgZm9yIGFsbCBmb3VyIG1vZGVscy4gSW4gdGhpcyBjYXNlLCB3ZSB3b3VsZCBsaWtlbHkgcHJlZmVyIE1vZGVsIDQgc2luY2UgaXQgZG9lcyBub3QgY29udGFpbiBhbnkgbm9uLXNpZ25pZmljYW50IHByZWRpY3RvcnMuIA0KDQo=