Instructions
Consider the the Cervical Cancer (Risk Factors) data set (available from UCI repository) and try to accurately classify Dx.Cancer.
You must compare different approaches and parameters of a) single decision tree, b) random forest.
Evaluation of derived models should follow a correct methodology, comparing different estimates of generalization error (i.e. holdout, cross-validation, bootstrap, …)
Submit a report (in PDF, generated from R) with the code and the resulting analysis.
The Dataset
The dataset comprises of 36 variables, being 24 categorical and 12 continuous variables. The dataset was collected at ‘Hospital Universitario de Caracas’ in Caracas, Venezuela. The dataset comprises demographic information, habits, and historic medical records of 858 patients. Several patients decided not to answer some of the questions because of privacy concerns (missing values), mainly use of contraceptives, IUD and concerning the STDs.
The Exercise
The objective of this exercise is to attemtp to classify the variable Dx:Cancer using some available algorithms and compare them.
There are several algorithms for that we may use:
- Linear regression, Logistic Regression and Support Vector Machines
- Perceptrons and Artificial Neural Networks
- Naive Bayes Classifier
- Decision Trees
- k-nearest neighbor
Here is a “brief” example of assumptions that we may take in account for each method:
Regression or Classification?
First, we need to explain what is Regression and what is Classification, since this often causes confusion:
- Both are under the same umbrella of supervised machine learning.
- Both utilize a known dataset (training dataset) to make predictions.
- The main difference is the output: for regression is numerical (or continuous) while for classification is categorical (or discrete).
In this exercise, we have a classification problem, that will attempt to classify for true (has cancer) or false (doesn’t have cancer), having no other class involved.
So, the current exercise will use the following classification methods:
- Decision tree
- Random forest
Decision Tree
First, let’s create a partition with 80% of the data:
Than, let’s create a model with Gini impurity index and Information Gain:
# Learn tree with Gini impurity
tree.gini <- rpart(
dx_cancer ~ .,
data = data.train,
parms = list(split = "gini"),
method = "class"
)
# Lean tree with information gain
tree.information <- rpart(
dx_cancer ~ .,
data = data.train,
parms = list(split = "information"),
method = "class"
)
par(mfrow = c(1, 2))
rpart.plot(tree.gini, main = "Gini Index", type = 5)
rpart.plot(tree.information, main = "Information Gain", type = 5)

Both algorithms find that dx_hpv is a strong predictor for dx_cancer.
This seems a little odd (not impossible), let’s take a look in the dataset:
Using the nearZeroVar from caret we see that there are several variables that contains near-zero variance predictors (i.e.: variables that take an unique value across samples).
We don’t want to simply remove these variables. So we will try another approach: subsampling.
Subsampling
Subsampling tries to balance a class-unbalanced dataset. We don’t want to artificially balance the test set, so we will take the training set and, before model fitting, and sample the data. There are two issues with this approach according to caret package help pages:
Firstly, during model tuning the holdout samples generated during resampling are also glanced and may not reflect the class imbalance that future predictions would encounter. This is likely to lead to overly optimistic estimates of performance.
Secondly, the subsampling process will probably induce more model uncertainty. Would the model results differ under a different subsample? As above, the resampling statistics are more likely to make the model appear more effective than it actually is.
The author also suggests as an alternative, to include the subsampling inside of the usual resampling procedure (cross-validation, bootstrap, …), in exchange of computing time.
There we will try two algorithms, down-sampling and up-sampling. The latter is know to be more optimistic, but since we have so few cases, it seems the more interesting for us.
Down Sampling
First, create the down-sampled training set.
Now let’s model again:
# Learn tree with Gini impurity
tree.gini <- rpart(
dx_cancer ~ .,
data = down_train,
parms = list(split = "gini"),
method = "class"
)
# Lean tree with information gain
tree.information <- rpart(
dx_cancer ~ .,
data = down_train,
parms = list(split = "information"),
method = "class"
)
par(mfrow = c(1, 2))
rpart.plot(tree.gini, main = "Gini Index", type = 5)
rpart.plot(tree.information, main = "Information Gain", type = 5)

We still have the same as before: a strong prediction based on dx_hpv.
Up Sampling
First, create the up-sampled training set.
Now let’s model again:
# Learn tree with Gini impurity
tree.gini <- rpart(
dx_cancer ~ .,
data = up_train,
parms = list(split = "gini"),
method = "class"
)
# Lean tree with information gain
tree.information <- rpart(
dx_cancer ~ .,
data = up_train,
parms = list(split = "information"),
method = "class"
)
par(mfrow = c(1, 2))
rpart.plot(tree.gini, main = "Gini Index", type = 5)
rpart.plot(tree.information, main = "Information Gain", type = 5)

Now we have something interesting. It seems that dx became also important for our decision tree.
In the next step we will then evaluate the Decision Tree model and the Random Forest model.
Random Forest and evaluations
First we will compare both models using the down-sampled training data:
metric <- "ROC"
control <- trainControl(
method = "LGOCV", p = 0.8, number = 150,
summaryFunction = twoClassSummary,
classProbs = T,
savePredictions = T
)
set.seed(7)
fit.rpart.down <- train(
dx_cancer ~ .,
data = down_train,
method = "rpart",
metric = metric,
trControl = control,
na.action = na.rpart
)
# Random Forest
set.seed(7)
fit.rf.down <- train(
dx_cancer ~ .,
data = down_train,
method = "rf",
metric = metric,
trControl = control,
na.action = randomForest::na.roughfix
)
# Summarize accuracy of models
results <- resamples(list(rpart = fit.rpart.down, rf = fit.rf.down))
summary(results)
Call:
summary.resamples(object = results)
Models: rpart, rf
Number of resamples: 150
ROC
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
rpart 0.6666667 1 1 0.9655556 1 1 0
rf 1.0000000 1 1 1.0000000 1 1 0
Sens
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
rpart 0.3333333 1 1 0.9311111 1 1 0
rf 0.3333333 1 1 0.9311111 1 1 0
Spec
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
rpart 1 1 1 1 1 1 0
rf 1 1 1 1 1 1 0
Setting levels: control = 1, case = 2
Setting direction: controls < cases

Setting levels: control = 1, case = 2
Setting direction: controls < cases


metric <- "ROC"
control <- trainControl(
method = "LGOCV", p = 0.8, number = 150,
summaryFunction = twoClassSummary,
classProbs = T,
savePredictions = T
)
set.seed(7)
fit.rpart.up <- train(
dx_cancer ~ .,
data = up_train,
method = "rpart",
metric = metric,
trControl = control,
na.action = na.rpart
)
# Random Forest
set.seed(7)
fit.rf.up <- train(
dx_cancer ~ .,
data = up_train,
method = "rf",
metric = metric,
trControl = control,
na.action = randomForest::na.roughfix
)
# Summarize accuracy of models
results <- resamples(list(rpart = fit.rpart.up, rf = fit.rf.up))
summary(results)
Call:
summary.resamples(object = results)
Models: rpart, rf
Number of resamples: 150
ROC
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
rpart 0.9948207 0.9990811 0.9996658 0.9988108 0.9999095 1 0
rf 1.0000000 1.0000000 1.0000000 1.0000000 1.0000000 1 0
Sens
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
rpart 1 1 1 1 1 1 0
rf 1 1 1 1 1 1 0
Spec
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
rpart 0.9701493 0.9850746 0.9925373 0.9896020 0.9925373 1 0
rf 0.9850746 1.0000000 1.0000000 0.9979602 1.0000000 1 0
Setting levels: control = 1, case = 2
Setting direction: controls < cases

Setting levels: control = 1, case = 2
Setting direction: controls < cases


Validation
Now it is time to validate the models using the test set we created from the original dataset:
Confusion Matrix and Statistics
Reference
Prediction yes no
yes 3 4
no 0 164
Accuracy : 0.9766
95% CI : (0.9412, 0.9936)
No Information Rate : 0.9825
P-Value [Acc > NIR] : 0.8168
Kappa : 0.5899
Mcnemar's Test P-Value : 0.1336
Sensitivity : 1.00000
Specificity : 0.97619
Pos Pred Value : 0.42857
Neg Pred Value : 1.00000
Prevalence : 0.01754
Detection Rate : 0.01754
Detection Prevalence : 0.04094
Balanced Accuracy : 0.98810
'Positive' Class : yes
Confusion Matrix and Statistics
Reference
Prediction yes no
yes 3 4
no 0 164
Accuracy : 0.9766
95% CI : (0.9412, 0.9936)
No Information Rate : 0.9825
P-Value [Acc > NIR] : 0.8168
Kappa : 0.5899
Mcnemar's Test P-Value : 0.1336
Sensitivity : 1.00000
Specificity : 0.97619
Pos Pred Value : 0.42857
Neg Pred Value : 1.00000
Prevalence : 0.01754
Detection Rate : 0.01754
Detection Prevalence : 0.04094
Balanced Accuracy : 0.98810
'Positive' Class : yes
Confusion Matrix and Statistics
Reference
Prediction yes no
yes 2 1
no 1 167
Accuracy : 0.9883
95% CI : (0.9584, 0.9986)
No Information Rate : 0.9825
P-Value [Acc > NIR] : 0.4212
Kappa : 0.6607
Mcnemar's Test P-Value : 1.0000
Sensitivity : 0.66667
Specificity : 0.99405
Pos Pred Value : 0.66667
Neg Pred Value : 0.99405
Prevalence : 0.01754
Detection Rate : 0.01170
Detection Prevalence : 0.01754
Balanced Accuracy : 0.83036
'Positive' Class : yes
Confusion Matrix and Statistics
Reference
Prediction yes no
yes 3 4
no 0 164
Accuracy : 0.9766
95% CI : (0.9412, 0.9936)
No Information Rate : 0.9825
P-Value [Acc > NIR] : 0.8168
Kappa : 0.5899
Mcnemar's Test P-Value : 0.1336
Sensitivity : 1.00000
Specificity : 0.97619
Pos Pred Value : 0.42857
Neg Pred Value : 1.00000
Prevalence : 0.01754
Detection Rate : 0.01754
Detection Prevalence : 0.04094
Balanced Accuracy : 0.98810
'Positive' Class : yes
We see that all algorithms had similar performances in the validation step. Even augmenting the dataset isn’t enough to overcome the inherent problems of this dataset with such heavy class imbalances. I would dare to say that in the validation step, the best result was with down sampling instead of upsampling, since we had fewer false negatives (that is a big problem in diagnostic tests).
LS0tCnRpdGxlOiAiSEVBRFMgLSBISURBIC0gTEVBUk46IEFzc2lnbm1lbnQgMSIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGZpZ19oZWlnaHQ6IDgKICAgIGZpZ193aWR0aDogMTAKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRoZW1lOiB1bml0ZWQKICAgIHRvYzogeWVzCmF1dGhvcjogRnJhbmNpc2NvIEJpc2Nob2ZmCi0tLQoKIyMgSW5zdHJ1Y3Rpb25zCgpDb25zaWRlciB0aGUgdGhlIENlcnZpY2FsIENhbmNlciAoUmlzayBGYWN0b3JzKSBkYXRhIHNldCAoYXZhaWxhYmxlIGZyb20gVUNJIHJlcG9zaXRvcnkpIGFuZCB0cnkgdG8gYWNjdXJhdGVseSBjbGFzc2lmeSBEeC5DYW5jZXIuCgpZb3UgbXVzdCBjb21wYXJlIGRpZmZlcmVudCBhcHByb2FjaGVzIGFuZCBwYXJhbWV0ZXJzIG9mIGEpIHNpbmdsZSBkZWNpc2lvbiB0cmVlLCBiKSByYW5kb20gZm9yZXN0LgoKRXZhbHVhdGlvbiBvZiBkZXJpdmVkIG1vZGVscyBzaG91bGQgZm9sbG93IGEgY29ycmVjdCBtZXRob2RvbG9neSwgY29tcGFyaW5nIGRpZmZlcmVudCBlc3RpbWF0ZXMgb2YgZ2VuZXJhbGl6YXRpb24gZXJyb3IgKGkuZS4gaG9sZG91dCwgY3Jvc3MtdmFsaWRhdGlvbiwgYm9vdHN0cmFwLCAuLi4pCgpTdWJtaXQgYSByZXBvcnQgKGluIFBERiwgZ2VuZXJhdGVkIGZyb20gUikgd2l0aCB0aGUgY29kZSBhbmQgdGhlIHJlc3VsdGluZyBhbmFseXNpcy4gCgpgYGB7ciBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZyA9IFRSVUUpCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoZXhwc3MpCmxpYnJhcnkocnBhcnQpCmxpYnJhcnkocnBhcnQucGxvdCkgIyBiZXR0ZXIgcGxvdHMKbGlicmFyeShwUk9DKQpkYXRhc2V0IDwtIHJlYWRfY3N2KCJyaXNrX2ZhY3RvcnNfY2VydmljYWxfY2FuY2VyLmNzdiIsIG5hID0gIj8iKQojIGNvZXJjZSBpdCB0byBkYXRhLmZyYW1lLCBmb3Igc2ltcGxpY2l0eQpkYXRhc2V0IDwtIGRhdGEuZnJhbWUoZGF0YXNldCkKY29sdW1ucyA8LSBjKAogICJhZ2UiLAogICJwYXJ0bmVycyIsCiAgInNleGFyY2EiLAogICJwcmVnbmFuY2llcyIsCiAgInNtb2tlcyIsCiAgInNtb2tlc195ZWFycyIsCiAgInNtb2tlcnNfcGFja195ZWFyIiwKICAib2hjIiwKICAib2hjX3llYXJzIiwKICAiaXVkIiwKICAiaXVkX3llYXJzIiwKICAic3RkIiwKICAic3RkX251bSIsCiAgInN0ZF9jb25keWxvbWEiLAogICJzdGRfY2VydmljYWwiLAogICJzdGRfdmFnaW5hbCIsCiAgInN0ZF92dWx2b19wZXJpdCIsCiAgInN0ZF9zeXBoaWxpcyIsCiAgInN0ZF9waWQiLAogICJzdGRfaGVycGVzIiwKICAic3RkX21vbGx1c2N1bSIsCiAgInN0ZF9haWRzIiwKICAic3RkX2hpdiIsCiAgInN0ZF9oZXBiIiwKICAic3RkX2hwdiIsCiAgInN0ZF9udW1fb2ZfZGlhZyIsCiAgInN0ZF90aW1lX3NpbmNlX2ZpcnN0IiwKICAic3RkX3RpbWVfc2luY2VfbGFzdCIsCiAgImR4X2NhbmNlciIsCiAgImR4X2NpbiIsCiAgImR4X2hwdiIsCiAgImR4IiwKICAiaGluc2VsbWFubiIsCiAgInNjaGlsbGVyIiwKICAiY2l0b2xvZ3kiLAogICJiaW9wc3kiCikKbmFtZXMoZGF0YXNldCkgPC0gY29sdW1ucwojIHVnbHkgd2F5IHRvIG1vdmUgdGhlIGRlcGVuZGFudCB2YXJpYWJsZSB0byB0aGUgZW5kOgpkYXRhc2V0IDwtIGNiaW5kKGRhdGFzZXRbLCAtd2hpY2goY29sdW1ucyA9PSAiZHhfY2FuY2VyIildLAogICAgICAgICAgICAgICAgIGR4X2NhbmNlciA9IGRhdGFzZXQkZHhfY2FuY2VyKQoKZGF0YXNldCRzbW9rZXMgPC0gZmFjdG9yKGRhdGFzZXQkc21va2VzLAogIGxldmVscyA9IGMoMSwgMCksCiAgbGFiZWxzID0gYygieWVzIiwgIm5vIikKKQpkYXRhc2V0JG9oYyA8LSBmYWN0b3IoZGF0YXNldCRvaGMsCiAgbGV2ZWxzID0gYygxLCAwKSwKICBsYWJlbHMgPSBjKCJ5ZXMiLCAibm8iKQopCmRhdGFzZXQkaXVkIDwtIGZhY3RvcihkYXRhc2V0JGl1ZCwKICBsZXZlbHMgPSBjKDEsIDApLAogIGxhYmVscyA9IGMoInllcyIsICJubyIpCikKZGF0YXNldCRzdGQgPC0gZmFjdG9yKGRhdGFzZXQkc3RkLAogIGxldmVscyA9IGMoMSwgMCksCiAgbGFiZWxzID0gYygieWVzIiwgIm5vIikKKQpkYXRhc2V0JHN0ZF9jb25keWxvbWEgPC0gZmFjdG9yKGRhdGFzZXQkc3RkX2NvbmR5bG9tYSwKICBsZXZlbHMgPSBjKDEsIDApLAogIGxhYmVscyA9IGMoInllcyIsICJubyIpCikKZGF0YXNldCRzdGRfY2VydmljYWwgPC0gZmFjdG9yKGRhdGFzZXQkc3RkX2NlcnZpY2FsLAogIGxldmVscyA9IGMoMSwgMCksCiAgbGFiZWxzID0gYygieWVzIiwgIm5vIikKKQpkYXRhc2V0JHN0ZF92YWdpbmFsIDwtIGZhY3RvcihkYXRhc2V0JHN0ZF92YWdpbmFsLAogIGxldmVscyA9IGMoMSwgMCksCiAgbGFiZWxzID0gYygieWVzIiwgIm5vIikKKQpkYXRhc2V0JHN0ZF92dWx2b19wZXJpdCA8LSBmYWN0b3IoZGF0YXNldCRzdGRfdnVsdm9fcGVyaXQsCiAgbGV2ZWxzID0gYygxLCAwKSwKICBsYWJlbHMgPSBjKCJ5ZXMiLCAibm8iKQopCmRhdGFzZXQkc3RkX3N5cGhpbGlzIDwtIGZhY3RvcihkYXRhc2V0JHN0ZF9zeXBoaWxpcywKICBsZXZlbHMgPSBjKDEsIDApLAogIGxhYmVscyA9IGMoInllcyIsICJubyIpCikKZGF0YXNldCRzdGRfcGlkIDwtIGZhY3RvcihkYXRhc2V0JHN0ZF9waWQsCiAgbGV2ZWxzID0gYygxLCAwKSwKICBsYWJlbHMgPSBjKCJ5ZXMiLCAibm8iKQopCmRhdGFzZXQkc3RkX2hlcnBlcyA8LSBmYWN0b3IoZGF0YXNldCRzdGRfaGVycGVzLAogIGxldmVscyA9IGMoMSwgMCksCiAgbGFiZWxzID0gYygieWVzIiwgIm5vIikKKQpkYXRhc2V0JHN0ZF9tb2xsdXNjdW0gPC0gZmFjdG9yKGRhdGFzZXQkc3RkX21vbGx1c2N1bSwKICBsZXZlbHMgPSBjKDEsIDApLAogIGxhYmVscyA9IGMoInllcyIsICJubyIpCikKZGF0YXNldCRzdGRfYWlkcyA8LSBmYWN0b3IoZGF0YXNldCRzdGRfYWlkcywKICBsZXZlbHMgPSBjKDEsIDApLAogIGxhYmVscyA9IGMoInllcyIsICJubyIpCikKZGF0YXNldCRzdGRfaGl2IDwtIGZhY3RvcihkYXRhc2V0JHN0ZF9oaXYsCiAgbGV2ZWxzID0gYygxLCAwKSwKICBsYWJlbHMgPSBjKCJ5ZXMiLCAibm8iKQopCmRhdGFzZXQkc3RkX2hlcGIgPC0gZmFjdG9yKGRhdGFzZXQkc3RkX2hlcGIsCiAgbGV2ZWxzID0gYygxLCAwKSwKICBsYWJlbHMgPSBjKCJ5ZXMiLCAibm8iKQopCmRhdGFzZXQkc3RkX2hwdiA8LSBmYWN0b3IoZGF0YXNldCRzdGRfaHB2LAogIGxldmVscyA9IGMoMSwgMCksCiAgbGFiZWxzID0gYygieWVzIiwgIm5vIikKKQpkYXRhc2V0JGR4X2NhbmNlciA8LSBmYWN0b3IoZGF0YXNldCRkeF9jYW5jZXIsCiAgbGV2ZWxzID0gYygxLCAwKSwKICBsYWJlbHMgPSBjKCJ5ZXMiLCAibm8iKQopCmRhdGFzZXQkZHhfY2luIDwtIGZhY3RvcihkYXRhc2V0JGR4X2NpbiwKICBsZXZlbHMgPSBjKDEsIDApLAogIGxhYmVscyA9IGMoInllcyIsICJubyIpCikKZGF0YXNldCRkeF9ocHYgPC0gZmFjdG9yKGRhdGFzZXQkZHhfaHB2LAogIGxldmVscyA9IGMoMSwgMCksCiAgbGFiZWxzID0gYygieWVzIiwgIm5vIikKKQpkYXRhc2V0JGR4IDwtIGZhY3RvcihkYXRhc2V0JGR4LAogIGxldmVscyA9IGMoMSwgMCksCiAgbGFiZWxzID0gYygieWVzIiwgIm5vIikKKQpkYXRhc2V0JGhpbnNlbG1hbm4gPC0gZmFjdG9yKGRhdGFzZXQkaGluc2VsbWFubiwKICBsZXZlbHMgPSBjKDEsIDApLAogIGxhYmVscyA9IGMoInllcyIsICJubyIpCikKZGF0YXNldCRzY2hpbGxlciA8LSBmYWN0b3IoZGF0YXNldCRzY2hpbGxlciwKICBsZXZlbHMgPSBjKDEsIDApLAogIGxhYmVscyA9IGMoInllcyIsICJubyIpCikKZGF0YXNldCRjaXRvbG9neSA8LSBmYWN0b3IoZGF0YXNldCRjaXRvbG9neSwKICBsZXZlbHMgPSBjKDEsIDApLAogIGxhYmVscyA9IGMoInllcyIsICJubyIpCikKZGF0YXNldCRiaW9wc3kgPC0gZmFjdG9yKGRhdGFzZXQkYmlvcHN5LAogIGxldmVscyA9IGMoMSwgMCksCiAgbGFiZWxzID0gYygieWVzIiwgIm5vIikKKQpgYGAKCiMjIFRoZSBEYXRhc2V0CgpUaGUgZGF0YXNldCBjb21wcmlzZXMgb2YgMzYgdmFyaWFibGVzLCBiZWluZyAyNCBjYXRlZ29yaWNhbCBhbmQgMTIgY29udGludW91cyB2YXJpYWJsZXMuIFRoZSBkYXRhc2V0IHdhcyBjb2xsZWN0ZWQgYXQgJ0hvc3BpdGFsIFVuaXZlcnNpdGFyaW8gZGUgQ2FyYWNhcycgaW4gQ2FyYWNhcywgVmVuZXp1ZWxhLiBUaGUgZGF0YXNldCBjb21wcmlzZXMgZGVtb2dyYXBoaWMgaW5mb3JtYXRpb24sIGhhYml0cywgYW5kIGhpc3RvcmljIG1lZGljYWwgcmVjb3JkcyBvZiA4NTggcGF0aWVudHMuIFNldmVyYWwgcGF0aWVudHMgZGVjaWRlZCBub3QgdG8gYW5zd2VyIHNvbWUgb2YgdGhlIHF1ZXN0aW9ucyBiZWNhdXNlIG9mIHByaXZhY3kgY29uY2VybnMgKG1pc3NpbmcgdmFsdWVzKSwgbWFpbmx5IHVzZSBvZiBjb250cmFjZXB0aXZlcywgSVVEIGFuZCBjb25jZXJuaW5nIHRoZSBTVERzLgoKIyMgVGhlIEV4ZXJjaXNlCgpUaGUgb2JqZWN0aXZlIG9mIHRoaXMgZXhlcmNpc2UgaXMgdG8gYXR0ZW10cCB0byBjbGFzc2lmeSB0aGUgdmFyaWFibGUgRHg6Q2FuY2VyIHVzaW5nIHNvbWUgYXZhaWxhYmxlIGFsZ29yaXRobXMgYW5kIGNvbXBhcmUgdGhlbS4KClRoZXJlIGFyZSBzZXZlcmFsIGFsZ29yaXRobXMgZm9yIHRoYXQgd2UgbWF5IHVzZToKCi0gTGluZWFyIHJlZ3Jlc3Npb24sIExvZ2lzdGljIFJlZ3Jlc3Npb24gYW5kIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzCi0gUGVyY2VwdHJvbnMgYW5kIEFydGlmaWNpYWwgTmV1cmFsIE5ldHdvcmtzCi0gTmFpdmUgQmF5ZXMgQ2xhc3NpZmllcgotIERlY2lzaW9uIFRyZWVzCi0gay1uZWFyZXN0IG5laWdoYm9yCgpIZXJlIGlzIGEgImJyaWVmIiBleGFtcGxlIG9mIGFzc3VtcHRpb25zIHRoYXQgd2UgbWF5IHRha2UgaW4gYWNjb3VudCBmb3IgZWFjaCBtZXRob2Q6CgohW10oaHR0cHM6Ly9taXJvLm1lZGl1bS5jb20vbWF4LzE3MjgvMSprTzJ4RTN0UUZ4Zzl4cDBaU0tXT0lRLnBuZyk8ZGl2IGFsaWduID0gImNlbnRlciI+QWxsIHRoZSBBbm5veWluZyBBc3N1bXB0aW9ucywgZnJvbSBbRGlwIFJhbmphbiBDaGF0dGVyamVlCl0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2FsbC10aGUtYW5ub3lpbmctYXNzdW1wdGlvbnMtMzFiNTVkZjI0NmMzKTwvZGl2PgoKIyMjIFJlZ3Jlc3Npb24gb3IgQ2xhc3NpZmljYXRpb24/CkZpcnN0LCB3ZSBuZWVkIHRvIGV4cGxhaW4gd2hhdCBpcyBSZWdyZXNzaW9uIGFuZCB3aGF0IGlzIENsYXNzaWZpY2F0aW9uLCBzaW5jZSB0aGlzIG9mdGVuIGNhdXNlcyBjb25mdXNpb246CgotIEJvdGggYXJlIHVuZGVyIHRoZSBzYW1lIHVtYnJlbGxhIG9mIHN1cGVydmlzZWQgbWFjaGluZSBsZWFybmluZy4KLSBCb3RoIHV0aWxpemUgYSBrbm93biBkYXRhc2V0ICh0cmFpbmluZyBkYXRhc2V0KSB0byBtYWtlIHByZWRpY3Rpb25zLgotIFRoZSBtYWluIGRpZmZlcmVuY2UgaXMgdGhlIG91dHB1dDogZm9yIHJlZ3Jlc3Npb24gaXMgbnVtZXJpY2FsIChvciBjb250aW51b3VzKSB3aGlsZSBmb3IgY2xhc3NpZmljYXRpb24gaXMgY2F0ZWdvcmljYWwgKG9yIGRpc2NyZXRlKS4KCkluIHRoaXMgZXhlcmNpc2UsIHdlIGhhdmUgYSBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtLCB0aGF0IHdpbGwgYXR0ZW1wdCB0byBjbGFzc2lmeSBmb3IgKipgdHJ1ZWAqKiAoaGFzIGNhbmNlcikgb3IgKipgZmFsc2VgKiogKGRvZXNuJ3QgaGF2ZSBjYW5jZXIpLCBoYXZpbmcgbm8gb3RoZXIgY2xhc3MgaW52b2x2ZWQuCgpTbywgdGhlIGN1cnJlbnQgZXhlcmNpc2Ugd2lsbCB1c2UgdGhlIGZvbGxvd2luZyBjbGFzc2lmaWNhdGlvbiBtZXRob2RzOgoKYSkgRGVjaXNpb24gdHJlZQpiKSBSYW5kb20gZm9yZXN0CgojIyBEZWNpc2lvbiBUcmVlCgpGaXJzdCwgbGV0J3MgY3JlYXRlIGEgcGFydGl0aW9uIHdpdGggODAlIG9mIHRoZSBkYXRhOgoKYGBge3IgcHJlcGFyZV9kYXRhfQpzZXQuc2VlZCgyMDIwKQojIENyZWF0ZSB0aGUgaW5kZXhlcyBvZiB0aGUgODAlIHBhcnRpdGlvbgpwYXJ0X2lkeHMgPC0KICBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdGFzZXQkZHhfY2FuY2VyLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCmRhdGEudHJhaW4gPC0gZGF0YXNldFtwYXJ0X2lkeHMsIF0KZGF0YS50ZXN0IDwtIGRhdGFzZXRbLXBhcnRfaWR4cywgXQpgYGAKClRoYW4sIGxldCdzIGNyZWF0ZSBhIG1vZGVsIHdpdGggR2luaSBpbXB1cml0eSBpbmRleCBhbmQgSW5mb3JtYXRpb24gR2FpbjoKCmBgYHtyIHNpbmdsZV90cmVlLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD0xMH0KIyBMZWFybiB0cmVlIHdpdGggR2luaSBpbXB1cml0eQp0cmVlLmdpbmkgPC0gcnBhcnQoCiAgZHhfY2FuY2VyIH4gLiwKICBkYXRhID0gZGF0YS50cmFpbiwKICBwYXJtcyA9IGxpc3Qoc3BsaXQgPSAiZ2luaSIpLAogIG1ldGhvZCA9ICJjbGFzcyIKKQojIExlYW4gdHJlZSB3aXRoIGluZm9ybWF0aW9uIGdhaW4KdHJlZS5pbmZvcm1hdGlvbiA8LSBycGFydCgKICBkeF9jYW5jZXIgfiAuLAogIGRhdGEgPSBkYXRhLnRyYWluLAogIHBhcm1zID0gbGlzdChzcGxpdCA9ICJpbmZvcm1hdGlvbiIpLAogIG1ldGhvZCA9ICJjbGFzcyIKKQpwYXIobWZyb3cgPSBjKDEsIDIpKQpycGFydC5wbG90KHRyZWUuZ2luaSwgbWFpbiA9ICJHaW5pIEluZGV4IiwgdHlwZSA9IDUpCnJwYXJ0LnBsb3QodHJlZS5pbmZvcm1hdGlvbiwgbWFpbiA9ICJJbmZvcm1hdGlvbiBHYWluIiwgdHlwZSA9IDUpCmBgYAoKQm90aCBhbGdvcml0aG1zIGZpbmQgdGhhdCAqKmR4X2hwdioqIGlzIGEgc3Ryb25nIHByZWRpY3RvciBmb3IgKipkeF9jYW5jZXIqKi4KClRoaXMgc2VlbXMgYSBsaXR0bGUgb2RkIChub3QgaW1wb3NzaWJsZSksIGxldCdzIHRha2UgYSBsb29rIGluIHRoZSBkYXRhc2V0OgoKYGBge3IgY2hlY2tfZGF0YSwgZWNobz1UUlVFfQpuZWFyIDwtIG5lYXJaZXJvVmFyKGRhdGFzZXQsIHNhdmVNZXRyaWNzID0gVFJVRSkKbmVhcltuZWFyWywgInplcm9WYXIiXSArIG5lYXJbLCAibnp2Il0gPiAwLCBdCmBgYAoKVXNpbmcgdGhlIGBuZWFyWmVyb1ZhcmAgZnJvbSBgY2FyZXRgIHdlIHNlZSB0aGF0IHRoZXJlIGFyZSBzZXZlcmFsIHZhcmlhYmxlcyB0aGF0IGNvbnRhaW5zIG5lYXItemVybyB2YXJpYW5jZSBwcmVkaWN0b3JzIChpLmUuOiB2YXJpYWJsZXMgdGhhdCB0YWtlIGFuIHVuaXF1ZSB2YWx1ZSBhY3Jvc3Mgc2FtcGxlcykuCgpXZSBkb24ndCB3YW50IHRvIHNpbXBseSByZW1vdmUgdGhlc2UgdmFyaWFibGVzLiBTbyB3ZSB3aWxsIHRyeSBhbm90aGVyIGFwcHJvYWNoOiBzdWJzYW1wbGluZy4KCiMjIFN1YnNhbXBsaW5nCgpTdWJzYW1wbGluZyB0cmllcyB0byBiYWxhbmNlIGEgY2xhc3MtdW5iYWxhbmNlZCBkYXRhc2V0LiBXZSBkb24ndCB3YW50IHRvIGFydGlmaWNpYWxseSBiYWxhbmNlIHRoZSB0ZXN0IHNldCwgc28gd2Ugd2lsbCB0YWtlIHRoZSB0cmFpbmluZyBzZXQgYW5kLCBiZWZvcmUgbW9kZWwgZml0dGluZywgYW5kIHNhbXBsZSB0aGUgZGF0YS4gVGhlcmUgYXJlIHR3byBpc3N1ZXMgd2l0aCB0aGlzIGFwcHJvYWNoIGFjY29yZGluZyB0byBgY2FyZXRgIHBhY2thZ2UgaGVscCBwYWdlczoKCi0gRmlyc3RseSwgZHVyaW5nIG1vZGVsIHR1bmluZyB0aGUgaG9sZG91dCBzYW1wbGVzIGdlbmVyYXRlZCBkdXJpbmcgcmVzYW1wbGluZyBhcmUgYWxzbyBnbGFuY2VkIGFuZCBtYXkgbm90IHJlZmxlY3QgdGhlIGNsYXNzIGltYmFsYW5jZSB0aGF0IGZ1dHVyZSBwcmVkaWN0aW9ucyB3b3VsZCBlbmNvdW50ZXIuIFRoaXMgaXMgbGlrZWx5IHRvIGxlYWQgdG8gb3Zlcmx5IG9wdGltaXN0aWMgZXN0aW1hdGVzIG9mIHBlcmZvcm1hbmNlLgoKLSBTZWNvbmRseSwgdGhlIHN1YnNhbXBsaW5nIHByb2Nlc3Mgd2lsbCBwcm9iYWJseSBpbmR1Y2UgbW9yZSBtb2RlbCB1bmNlcnRhaW50eS4gV291bGQgdGhlIG1vZGVsIHJlc3VsdHMgZGlmZmVyIHVuZGVyIGEgZGlmZmVyZW50IHN1YnNhbXBsZT8gQXMgYWJvdmUsIHRoZSByZXNhbXBsaW5nIHN0YXRpc3RpY3MgYXJlIG1vcmUgbGlrZWx5IHRvIG1ha2UgdGhlIG1vZGVsIGFwcGVhciBtb3JlIGVmZmVjdGl2ZSB0aGFuIGl0IGFjdHVhbGx5IGlzLgoKVGhlIGF1dGhvciBhbHNvIHN1Z2dlc3RzIGFzIGFuIGFsdGVybmF0aXZlLCB0byBpbmNsdWRlIHRoZSBzdWJzYW1wbGluZyBpbnNpZGUgb2YgdGhlIHVzdWFsIHJlc2FtcGxpbmcgcHJvY2VkdXJlIChjcm9zcy12YWxpZGF0aW9uLCBib290c3RyYXAsIC4uLiksIGluIGV4Y2hhbmdlIG9mIGNvbXB1dGluZyB0aW1lLgoKVGhlcmUgd2Ugd2lsbCB0cnkgdHdvIGFsZ29yaXRobXMsIF9kb3duLXNhbXBsaW5nXyBhbmQgX3VwLXNhbXBsaW5nXy4gVGhlIGxhdHRlciBpcyBrbm93IHRvIGJlIG1vcmUgb3B0aW1pc3RpYywgYnV0IHNpbmNlIHdlIGhhdmUgc28gZmV3IGNhc2VzLCBpdCBzZWVtcyB0aGUgbW9yZSBpbnRlcmVzdGluZyBmb3IgdXMuCgojIyMgRG93biBTYW1wbGluZwoKRmlyc3QsIGNyZWF0ZSB0aGUgZG93bi1zYW1wbGVkIHRyYWluaW5nIHNldC4KCmBgYHtyIGRvd25fc2FtcGxlfQpzZXQuc2VlZCgyMDIwKQpkb3duX3RyYWluIDwtIGRvd25TYW1wbGUoCiAgeCA9IGRhdGEudHJhaW5bLCAtbmNvbChkYXRhLnRyYWluKV0sCiAgeSA9IGRhdGEudHJhaW4kZHhfY2FuY2VyLAogIHluYW1lID0gImR4X2NhbmNlciIKKQpgYGAKCk5vdyBsZXQncyBtb2RlbCBhZ2FpbjoKCmBgYHtyIGRvd25fdHJlZSwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9MTB9CiMgTGVhcm4gdHJlZSB3aXRoIEdpbmkgaW1wdXJpdHkKdHJlZS5naW5pIDwtIHJwYXJ0KAogIGR4X2NhbmNlciB+IC4sCiAgZGF0YSA9IGRvd25fdHJhaW4sCiAgcGFybXMgPSBsaXN0KHNwbGl0ID0gImdpbmkiKSwKICBtZXRob2QgPSAiY2xhc3MiCikKIyBMZWFuIHRyZWUgd2l0aCBpbmZvcm1hdGlvbiBnYWluCnRyZWUuaW5mb3JtYXRpb24gPC0gcnBhcnQoCiAgZHhfY2FuY2VyIH4gLiwKICBkYXRhID0gZG93bl90cmFpbiwKICBwYXJtcyA9IGxpc3Qoc3BsaXQgPSAiaW5mb3JtYXRpb24iKSwKICBtZXRob2QgPSAiY2xhc3MiCikKcGFyKG1mcm93ID0gYygxLCAyKSkKcnBhcnQucGxvdCh0cmVlLmdpbmksIG1haW4gPSAiR2luaSBJbmRleCIsIHR5cGUgPSA1KQpycGFydC5wbG90KHRyZWUuaW5mb3JtYXRpb24sIG1haW4gPSAiSW5mb3JtYXRpb24gR2FpbiIsIHR5cGUgPSA1KQpgYGAKCldlIHN0aWxsIGhhdmUgdGhlIHNhbWUgYXMgYmVmb3JlOiBhIHN0cm9uZyBwcmVkaWN0aW9uIGJhc2VkIG9uICoqZHhfaHB2KiouCgojIyMgVXAgU2FtcGxpbmcKCkZpcnN0LCBjcmVhdGUgdGhlIHVwLXNhbXBsZWQgdHJhaW5pbmcgc2V0LgoKYGBge3IgdXBfc2FtcGxlfQpzZXQuc2VlZCgyMDIwKQp1cF90cmFpbiA8LSB1cFNhbXBsZSgKICB4ID0gZGF0YS50cmFpblssIC1uY29sKGRhdGEudHJhaW4pXSwKICB5ID0gZGF0YS50cmFpbiRkeF9jYW5jZXIsCiAgeW5hbWUgPSAiZHhfY2FuY2VyIgopCmBgYAoKTm93IGxldCdzIG1vZGVsIGFnYWluOgoKYGBge3IgdXBfdHJlZSwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9MTB9CiMgTGVhcm4gdHJlZSB3aXRoIEdpbmkgaW1wdXJpdHkKdHJlZS5naW5pIDwtIHJwYXJ0KAogIGR4X2NhbmNlciB+IC4sCiAgZGF0YSA9IHVwX3RyYWluLAogIHBhcm1zID0gbGlzdChzcGxpdCA9ICJnaW5pIiksCiAgbWV0aG9kID0gImNsYXNzIgopCiMgTGVhbiB0cmVlIHdpdGggaW5mb3JtYXRpb24gZ2Fpbgp0cmVlLmluZm9ybWF0aW9uIDwtIHJwYXJ0KAogIGR4X2NhbmNlciB+IC4sCiAgZGF0YSA9IHVwX3RyYWluLAogIHBhcm1zID0gbGlzdChzcGxpdCA9ICJpbmZvcm1hdGlvbiIpLAogIG1ldGhvZCA9ICJjbGFzcyIKKQpwYXIobWZyb3cgPSBjKDEsIDIpKQpycGFydC5wbG90KHRyZWUuZ2luaSwgbWFpbiA9ICJHaW5pIEluZGV4IiwgdHlwZSA9IDUpCnJwYXJ0LnBsb3QodHJlZS5pbmZvcm1hdGlvbiwgbWFpbiA9ICJJbmZvcm1hdGlvbiBHYWluIiwgdHlwZSA9IDUpCmBgYAoKTm93IHdlIGhhdmUgc29tZXRoaW5nIGludGVyZXN0aW5nLiBJdCBzZWVtcyB0aGF0ICoqZHgqKiBiZWNhbWUgYWxzbyBpbXBvcnRhbnQgZm9yIG91ciBkZWNpc2lvbiB0cmVlLgoKSW4gdGhlIG5leHQgc3RlcCB3ZSB3aWxsIHRoZW4gZXZhbHVhdGUgdGhlIERlY2lzaW9uIFRyZWUgbW9kZWwgYW5kIHRoZSBSYW5kb20gRm9yZXN0IG1vZGVsLgoKIyMgUmFuZG9tIEZvcmVzdCBhbmQgZXZhbHVhdGlvbnMKCkZpcnN0IHdlIHdpbGwgY29tcGFyZSBib3RoIG1vZGVscyB1c2luZyB0aGUgZG93bi1zYW1wbGVkIHRyYWluaW5nIGRhdGE6CgpgYGB7ciBkb3duX2V2YWx1YXRpb24sIHdhcm5pbmc9RkFMU0V9Cm1ldHJpYyA8LSAiUk9DIgpjb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAiTEdPQ1YiLCBwID0gMC44LCBudW1iZXIgPSAxNTAsCiAgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5LAogIGNsYXNzUHJvYnMgPSBULAogIHNhdmVQcmVkaWN0aW9ucyA9IFQKKQoKc2V0LnNlZWQoNykKZml0LnJwYXJ0LmRvd24gPC0gdHJhaW4oCiAgZHhfY2FuY2VyIH4gLiwKICBkYXRhID0gZG93bl90cmFpbiwKICBtZXRob2QgPSAicnBhcnQiLAogIG1ldHJpYyA9IG1ldHJpYywKICB0ckNvbnRyb2wgPSBjb250cm9sLAogIG5hLmFjdGlvbiA9IG5hLnJwYXJ0CikKIyBSYW5kb20gRm9yZXN0CnNldC5zZWVkKDcpCmZpdC5yZi5kb3duIDwtIHRyYWluKAogIGR4X2NhbmNlciB+IC4sCiAgZGF0YSA9IGRvd25fdHJhaW4sCiAgbWV0aG9kID0gInJmIiwKICBtZXRyaWMgPSBtZXRyaWMsCiAgdHJDb250cm9sID0gY29udHJvbCwKICBuYS5hY3Rpb24gPSByYW5kb21Gb3Jlc3Q6Om5hLnJvdWdoZml4CikKCiMgU3VtbWFyaXplIGFjY3VyYWN5IG9mIG1vZGVscwpyZXN1bHRzIDwtIHJlc2FtcGxlcyhsaXN0KHJwYXJ0ID0gZml0LnJwYXJ0LmRvd24sIHJmID0gZml0LnJmLmRvd24pKQpzdW1tYXJ5KHJlc3VsdHMpCnBsb3Qucm9jKGFzLm51bWVyaWMoZml0LnJwYXJ0LmRvd24kcHJlZCRvYnMpLCBhcy5udW1lcmljKGZpdC5ycGFydC5kb3duJHByZWQkcHJlZCksIG1haW4gPSBmaXQucnBhcnQuZG93biRtZXRob2QsIHByaW50LmF1YyA9IFQpCnBsb3Qucm9jKGFzLm51bWVyaWMoZml0LnJmLmRvd24kcHJlZCRvYnMpLCBhcy5udW1lcmljKGZpdC5yZi5kb3duJHByZWQkcHJlZCksIG1haW4gPSBmaXQucmYuZG93biRtZXRob2QsIHByaW50LmF1YyA9IFQpCiMgQ29tcGFyZSBhY2N1cmFjeSBvZiBtb2RlbHMKZG90cGxvdChyZXN1bHRzKQpgYGAKCmBgYHtyIHVwX2V2YWx1YXRpb24sIHdhcm5pbmc9RkFMU0V9Cm1ldHJpYyA8LSAiUk9DIgpjb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAiTEdPQ1YiLCBwID0gMC44LCBudW1iZXIgPSAxNTAsCiAgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5LAogIGNsYXNzUHJvYnMgPSBULAogIHNhdmVQcmVkaWN0aW9ucyA9IFQKKQoKc2V0LnNlZWQoNykKZml0LnJwYXJ0LnVwIDwtIHRyYWluKAogIGR4X2NhbmNlciB+IC4sCiAgZGF0YSA9IHVwX3RyYWluLAogIG1ldGhvZCA9ICJycGFydCIsCiAgbWV0cmljID0gbWV0cmljLAogIHRyQ29udHJvbCA9IGNvbnRyb2wsCiAgbmEuYWN0aW9uID0gbmEucnBhcnQKKQojIFJhbmRvbSBGb3Jlc3QKc2V0LnNlZWQoNykKZml0LnJmLnVwIDwtIHRyYWluKAogIGR4X2NhbmNlciB+IC4sCiAgZGF0YSA9IHVwX3RyYWluLAogIG1ldGhvZCA9ICJyZiIsCiAgbWV0cmljID0gbWV0cmljLAogIHRyQ29udHJvbCA9IGNvbnRyb2wsCiAgbmEuYWN0aW9uID0gcmFuZG9tRm9yZXN0OjpuYS5yb3VnaGZpeAopCgojIFN1bW1hcml6ZSBhY2N1cmFjeSBvZiBtb2RlbHMKcmVzdWx0cyA8LSByZXNhbXBsZXMobGlzdChycGFydCA9IGZpdC5ycGFydC51cCwgcmYgPSBmaXQucmYudXApKQpzdW1tYXJ5KHJlc3VsdHMpCnBsb3Qucm9jKGFzLm51bWVyaWMoZml0LnJwYXJ0LnVwJHByZWQkb2JzKSwgYXMubnVtZXJpYyhmaXQucnBhcnQudXAkcHJlZCRwcmVkKSwgbWFpbiA9IGZpdC5ycGFydC51cCRtZXRob2QsIHByaW50LmF1YyA9IFQpCnBsb3Qucm9jKGFzLm51bWVyaWMoZml0LnJmLnVwJHByZWQkb2JzKSwgYXMubnVtZXJpYyhmaXQucmYudXAkcHJlZCRwcmVkKSwgbWFpbiA9IGZpdC5yZi51cCRtZXRob2QsIHByaW50LmF1YyA9IFQpCiMgQ29tcGFyZSBhY2N1cmFjeSBvZiBtb2RlbHMKZG90cGxvdChyZXN1bHRzKQpgYGAKCiMjIFZhbGlkYXRpb24KCk5vdyBpdCBpcyB0aW1lIHRvIHZhbGlkYXRlIHRoZSBtb2RlbHMgdXNpbmcgdGhlIHRlc3Qgc2V0IHdlIGNyZWF0ZWQgZnJvbSB0aGUgb3JpZ2luYWwgZGF0YXNldDoKCmBgYHtyIHZhbGlkYXRpb259CiMgTWFrZSBwcmVkaWN0aW9ucwpwcmVkLnJwYXJ0LnVwIDwtIHByZWRpY3QoZml0LnJwYXJ0LnVwLCBkYXRhLnRlc3QsIG5hLmFjdGlvbiA9IG5hLnJwYXJ0KQpwcmVkLnJwYXJ0LmRvd24gPC0gcHJlZGljdChmaXQucnBhcnQudXAsIGRhdGEudGVzdCwgbmEuYWN0aW9uID0gbmEucnBhcnQpCnByZWQucmYudXAgPC0gcHJlZGljdChmaXQucmYudXAsIGRhdGEudGVzdCwgbmEuYWN0aW9uID0gcmFuZG9tRm9yZXN0OjpuYS5yb3VnaGZpeCkKcHJlZC5yZi5kb3duIDwtIHByZWRpY3QoZml0LnJmLmRvd24sIGRhdGEudGVzdCwgbmEuYWN0aW9uID0gcmFuZG9tRm9yZXN0OjpuYS5yb3VnaGZpeCkKCmNvbmZ1c2lvbk1hdHJpeChwcmVkLnJwYXJ0LnVwLCBkYXRhLnRlc3QkZHhfY2FuY2VyKQpjb25mdXNpb25NYXRyaXgocHJlZC5ycGFydC5kb3duLCBkYXRhLnRlc3QkZHhfY2FuY2VyKQpjb25mdXNpb25NYXRyaXgocHJlZC5yZi51cCwgZGF0YS50ZXN0JGR4X2NhbmNlcikKY29uZnVzaW9uTWF0cml4KHByZWQucmYuZG93biwgZGF0YS50ZXN0JGR4X2NhbmNlcikKYGBgCgpXZSBzZWUgdGhhdCBhbGwgYWxnb3JpdGhtcyBoYWQgc2ltaWxhciBwZXJmb3JtYW5jZXMgaW4gdGhlIHZhbGlkYXRpb24gc3RlcC4gRXZlbiBhdWdtZW50aW5nIHRoZSBkYXRhc2V0IGlzbid0IGVub3VnaCB0byBvdmVyY29tZSB0aGUgaW5oZXJlbnQgcHJvYmxlbXMgb2YgdGhpcyBkYXRhc2V0IHdpdGggc3VjaCBoZWF2eSBjbGFzcyBpbWJhbGFuY2VzLiBJIHdvdWxkIGRhcmUgdG8gc2F5IHRoYXQgaW4gdGhlIHZhbGlkYXRpb24gc3RlcCwgdGhlIGJlc3QgcmVzdWx0IHdhcyB3aXRoIGRvd24gc2FtcGxpbmcgaW5zdGVhZCBvZiB1cHNhbXBsaW5nLCBzaW5jZSB3ZSBoYWQgZmV3ZXIgZmFsc2UgbmVnYXRpdmVzICh0aGF0IGlzIGEgYmlnIHByb2JsZW0gaW4gZGlhZ25vc3RpYyB0ZXN0cykuCg==