In this lecture, we explore non-parametric tree-based algorithms and
techniques for imbalanced data sets.
Single Decision Trees (CART) and Pruning
Theoretical Foundation: Recursive Binary Splitting
Decision trees split the predictor space \(R\) into distinct, non-overlapping regions
\(R_1, R_2, \dots, R_J\). For
classification, we aim to minimize a measure of node impurity:
\[G = \sum_{k=1}^{K} \hat{p}_{mk} (1 -
\hat{p}_{mk})\]
\[D = -\sum_{k=1}^{K} \hat{p}_{mk} \log
\hat{p}_{mk}\]
Where \(\hat{p}_{mk}\) represents
the proportion of training observations in the \(m\)-th region that belong to class \(k\).
Data Preparation (ISLR2::Default)
We use the Default dataset from ISLR2. The objective is
to predict whether a customer will default on their credit card debt
based on student, balance, and
income.
data("Default", package = "ISLR2")
# Inspect structure and target distribution
str(Default)
'data.frame': 10000 obs. of 4 variables:
$ default: Factor w/ 2 levels "No","Yes": 1 1 1 1 1 1 1 1 1 1 ...
$ student: Factor w/ 2 levels "No","Yes": 1 2 1 1 1 2 1 2 1 1 ...
$ balance: num 730 817 1074 529 786 ...
$ income : num 44362 12106 31767 35704 38463 ...
No Yes
9667 333
# Train / Test split (80/20) using caret
set.seed(42)
train_idx <- createDataPartition(Default$default, p = 0.8, list = FALSE)
train_df <- Default[train_idx, ]
test_df <- Default[-train_idx, ]
Fitting a Full Tree & Cost-Complexity Pruning
A fully grown tree overfits noise. We prune using Cost-Complexity
Pruning (indexed by parameter \(\alpha\) or \(cp\) in rpart):
\[C_\alpha(T) = \sum_{m=1}^{|T|} N_m
Q_m(T) + \alpha |T|\]
When you run printcp(), R prints a table with
columns like CP, nsplit,
rel error, xerror, and
xstd.
# Fit initial tree with low complexity parameter to allow deep growth
full_tree <- rpart(default ~ ., data = train_df, method = "class", control = rpart.control(cp = 0.001))
# Plot Complexity Parameter (CP) table
printcp(full_tree)
Classification tree:
rpart(formula = default ~ ., data = train_df, method = "class",
control = rpart.control(cp = 0.001))
Variables actually used in tree construction:
[1] balance income student
Root node error: 267/8001 = 0.033371
n= 8001
CP nsplit rel error xerror xstd
1 0.0973783 0 1.00000 1.00000 0.060169
2 0.0823970 1 0.90262 0.93633 0.058286
3 0.0524345 2 0.82022 0.86517 0.056096
4 0.0112360 3 0.76779 0.80524 0.054174
5 0.0049938 5 0.74532 0.83146 0.055024
6 0.0037453 8 0.73034 0.84270 0.055384
7 0.0018727 10 0.72285 0.85393 0.055741
8 0.0010701 12 0.71910 0.88390 0.056682
9 0.0010000 19 0.71161 0.91011 0.057490
CP (Complexity Parameter): The penalty
applied to the tree’s size. Smaller CP values mean bigger,
more complex trees.
nsplit: The number of splits
(branches) in the tree.
rel error: The classification error on
your training set. This will almost always go down as
the tree gets bigger.
xerror (Cross-Validation Error): The
error estimated on unseen data (out-of-sample). This is your
primary target.
xstd: The standard deviation/standard
error of xerror.
You want to see where xerror stops dropping and starts
going back up (a sign of overfitting).
The Goal: Find the row with the lowest
xerror.
which.min(full_tree$cptable[, "xerror"]) automatically
selects the CP from this row to prune the tree.
plotcp(full_tree) gives you a visual plot of the
table above:

- X-axis (top/bottom):
CP value and size
of tree (number of splits).
- Y-axis: Cross-validated error
(
xerror).
- Dashed Horizontal Line: Represents the “1-SE rule”
(1 standard error above the minimum
xerror).
What to Look For:
- A U-shaped or elbow curve.
- The lowest point on the line corresponds to the optimal tree
size.
- If the curve drops rapidly and then flattens out, the “elbow point”
gives you a good balance between model simplicity and accuracy.
rpart.plot(...) prints the visual representation
of your pruned decision tree.
# Extract optimal CP corresponding to minimum cross-validated error
opt_cp <- full_tree$cptable[which.min(full_tree$cptable[, "xerror"]), "CP"]
# Prune the tree
pruned_tree <- rpart::prune(full_tree, cp = opt_cp)
# Visualize the pruned tree
rpart.plot(pruned_tree, main = "Pruned Decision Tree (Default Classification)", extra = 104)

- The Root Node (Top): Shows the overall default rate
in your training set and the total percentage of data (100%).
- Splitting Variables: The primary feature chosen at
each node represents the most significant feature for separating
defaults from non-defaults.
- The Terminal Nodes (Leaves at the Bottom):
- Top Number: Predicted class (e.g.,
No
for No Default, Yes for Default).
- Percentages:
- First percentage: Proportion of observations falling into
class 0 vs class 1 at that specific leaf.
- Second percentage: The percentage of your overall data set
that landed in that leaf node.
Ensemble Methods — Random Forests
Why Single Trees Fail
Single trees suffer from high variance: small
changes in training data yield radically different tree structures.
Bagging to Random Forests
- Bagging (Bootstrap Aggregating): Fits \(B\) bootstrap samples and averages
predictions: \[\hat{f}_{bag}(x) = \frac{1}{B}
\sum_{b=1}^{B} \hat{f}^{*b}(x)\]
- Random Forests: Solves tree correlation in bagging
by restricting each split to consider a random sample of \(m \approx \sqrt{p}\) predictors out of
\(p\) available features.
Fitting a Random Forest in R (caret)
fit_control <- trainControl(
method = "cv",
number = 5,
classProbs = TRUE,
summaryFunction = twoClassSummary
)
set.seed(42)
rf_fit <- train(
default ~ .,
data = train_df,
method = "rf",
metric = "ROC",
trControl = fit_control,
tuneGrid = expand.grid(mtry = c(1, 2, 3)),
ntree = 300
)
print(rf_fit)
Random Forest
8001 samples
3 predictor
2 classes: 'No', 'Yes'
No pre-processing
Resampling: Cross-Validated (5 fold)
Summary of sample sizes: 6401, 6400, 6401, 6402, 6400
Resampling results across tuning parameters:
mtry ROC Sens Spec
1 0.8565031 0.9956039 0.2664570
2 0.8867287 0.9906902 0.3113906
3 0.8908265 0.9900437 0.3303284
ROC was used to select the optimal model using the largest value.
The final value used for the model was mtry = 3.
In Random Forest models, mtry is the
number of variables (features) randomly sampled as candidates at each
split in a tree.
Instead of testing every single feature in the data set to
decide how to split a node (which is what a single, standard decision
tree does), a Random Forest randomly picks a small subset of
features—here, exactly 3—and only evaluates those 3 to
find the best split point.
The main reason Random Forests perform so much better than individual
decision trees comes down to decorrelating the
trees:
- Prevents Dominant Features: If your data set has
one ultra-strong predictor (e.g., Credit Score), a standard
tree will always split on it first. Every single tree in an
ensemble would end up looking almost identical, giving you a forest full
of identical trees.
- Forces Variety: By forcing the model to pick from
only
mtry = 3 random features at each split, it
occasionally forces a tree to ignore that dominant feature and discover
hidden, secondary relationships in other variables.
- Reduces Variance: Averaging predictions across
hundreds of slightly different, uncorrelated trees drastically cuts down
on model variance (overfitting) without increasing bias.
Packages like caret or randomForest in R,
mtry is typically the main hyperparameter tuned during
cross-validation. Default starting values generally follow these
standard conventions:
- Classification: \(\sqrt{p}\) (where \(p\) is the total number of predictor
variables)
- Regression: \(\frac{p}{3}\)
Variable Importance
While a single decision tree gives you an explicit flowchart to read,
a Random Forest combines hundreds of trees, making it a bit of a “black
box.”
Running varImp() gives you the transparency back:
- Interpretability: You can easily explain to
non-technical stakeholders which features actually drive the
predictions.
- Feature Selection: If you have a massive dataset
with 50+ variables, you can use this plot to prune out the bottom half
that aren’t contributing, simplifying future data collection and
speeding up model execution.
plot(varImp(rf_fit), main = "Variable Importance - Random Forest")

In a Random Forest, feature importance measures how much worse the
model performs without that feature. It’s generally calculated in one of
two ways:
- Mean Decrease in Accuracy (Permutation
Importance):
- The model takes out-of-bag (OOB) data and randomly shuffles
(scrambles) the values of a single variable, keeping everything else the
same.
- It measures how much the model’s prediction accuracy drops. If
accuracy plummets, that variable was doing heavy lifting.
- Mean Decrease in Gini / Impurity:
- It sums up the total reduction in node impurity (e.g., Gini impurity
for classification or MSE for regression) brought about by splits on
that specific variable, averaged across all trees in the forest.
Gradient Boosted Decision Trees (XGBoost)
Boosting Mechanics
Unlike Random Forests (independent parallel trees),
Boosting builds trees sequentially. Each new tree is
fitted to the residual errors of prior trees: \[f_b(x) = f_{b-1}(x) + \lambda
\hat{f}^b(x)\] XGBoost (eXtreme Gradient
Boosting) optimizes second-order Taylor approximations of the loss
function with L1/L2 regularization to prevent overfitting.
Fitting Native XGBoost with xgb.train
(Note: Trained using xgboost directly to interface
cleanly with modern C++ ALTREP memory pointers.)
# One-hot encode predictors for XGBoost
X_train <- model.matrix(default ~ . - 1, data = train_df)
y_train <- ifelse(train_df$default == "Yes", 1, 0)
dtrain <- xgb.DMatrix(data = X_train, label = y_train)
# Set hyperparameter configuration
params <- list(
booster = "gbtree",
objective = "binary:logistic",
eval_metric = "auc",
eta = 0.1,
max_depth = 4,
gamma = 0,
colsample_bytree = 0.8,
min_child_weight = 1,
subsample = 0.8
)
set.seed(42)
xgb_fit <- xgb.train(
params = params,
data = dtrain,
nrounds = 100,
verbose = 0
)
print(xgb_fit)
##### xgb.Booster
call:
xgb.train(params = params, data = dtrain, nrounds = 100, verbose = 0)
# of features: 4
# of rounds: 100
Dealing with Imbalanced Datasets
The Imbalance Problem
In the Default dataset, default events account for only ~3.3% of
total samples. A naive model predicting No for all
instances yields ~96.7% accuracy, rendering standard accuracy
uninformative.
Resampling Strategies
- Down-sampling: Randomly drops majority
instances.
- Up-sampling: Randomly duplicates minority
instances.
- SMOTE (Synthetic Minority Over-sampling Technique):
Creates synthetic minority samples along line segments connecting \(k\)-nearest minority neighbors.
Implementing SMOTE & Comparing Resampling in caret
# Recipe pipeline for SMOTE preprocessing
smote_rec <- recipe(default ~ ., data = train_df) %>%
step_dummy(all_nominal_predictors()) %>% # Converts 'student' into numeric dummy variables
step_smote(default, over_ratio = 0.8) # Now all predictors are double/integer!
# Control structure tuned for Sensitivity/Recall
eval_control <- trainControl(
method = "cv",
number = 5,
classProbs = TRUE,
summaryFunction = twoClassSummary
)
# 1. Baseline Model (Standard RF)
set.seed(42)
rf_base <- train(default ~ ., data = train_df, method = "rf", metric = "ROC", trControl = eval_control)
note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 .
# 2. Down-Sampled RF
control_down <- eval_control
control_down$sampling <- "down"
set.seed(42)
rf_down <- train(default ~ ., data = train_df, method = "rf", metric = "ROC", trControl = control_down)
note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 .
# 3. SMOTE-Sampled RF via recipes/themis
set.seed(42)
rf_smote <- train(smote_rec, data = train_df, method = "rf", metric = "ROC", trControl = eval_control)
note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 .
Comprehensive Model Comparison
We evaluate performance on the held-out test data set using
Precision, Recall (Sensitivity), Specificity, and ROC-AUC.
models <- list(
"Pruned CART" = pruned_tree,
"Random Forest"= rf_fit,
"XGBoost" = xgb_fit,
"RF (SMOTE)" = rf_smote
)
evaluate_model <- function(model, test_data, target_col = "default") {
if (inherits(model, "rpart")) {
probs <- predict(model, test_data, type = "prob")[, "Yes"]
preds <- factor(ifelse(probs > 0.5, "Yes", "No"), levels = c("No", "Yes"))
} else if (inherits(model, "xgb.Booster")) {
# Prepare test matrix matching model.matrix structure
X_test <- model.matrix(as.formula(paste(target_col, "~ . - 1")), data = test_data)
dtest <- xgb.DMatrix(data = X_test)
probs <- predict(model, dtest)
preds <- factor(ifelse(probs > 0.5, "Yes", "No"), levels = c("No", "Yes"))
} else {
probs <- predict(model, test_data, type = "prob")[, "Yes"]
preds <- predict(model, test_data)
}
cm <- confusionMatrix(preds, test_data[[target_col]], positive = "Yes")
c(
Accuracy = unname(cm$overall["Accuracy"]),
Sensitivity = unname(cm$byClass["Sensitivity"]), # Recall
Specificity = unname(cm$byClass["Specificity"]),
Precision = unname(cm$byClass["Pos Pred Value"]),
F1 = unname(cm$byClass["F1"])
)
}
results <- sapply(models, evaluate_model, test_data = test_df) %>% t()
datatable(round(results, 4), caption = "Out-of-Sample Performance Comparison on Credit Default Test Set")
When deciding which model to choose for credit default prediction,
raw accuracy is misleading. Because default events are
relatively rare (~3.3% of the data set), predicting “No” for everyone
yields ~96.7% accuracy.
In credit risk modeling, the business cost of a False
Negative (approving a loan for a borrower who defaults, leading
to charge-offs) is drastically higher than the cost of a False
Positive (flagging a borrower who would have paid, resulting in
a minor review or slight loss of transaction fee).
Therefore, model selection comes down to your primary risk
objective:
RF (SMOTE) for Risk Management
If the primary goal is loss mitigation and catching as many
potential defaults as possible, RF (SMOTE) is
the clear choice.
- Sensitivity/Recall (0.7727): It identifies
77.3% of all actual defaults on unseen test data,
compared to only 37.8%–43.9% for the un-resampled models.
- The Trade-Off: Because SMOTE balances the class
distribution during training, it trades off Precision (0.2315) and
Specificity (0.9141). You will flag more non-defaulting borrowers for
secondary review, but you protect the business from massive credit
losses.
Bottom Line for Credit Risk: Missing a default costs
thousands in principal loss; doing a secondary manual review on a false
flag costs a few dollars. RF (SMOTE) aligns best with actual bank
economics.
Pruned CART for Balanced Automation:
If the goal is to balance predictive performance with
regulatory interpretability and operational simplicity,
Pruned CART is the strongest candidate.
- Best Unadjusted Performance: Among the non-SMOTE
models, it yields the highest Sensitivity (0.4394), the highest F1 score
(0.5179), and competitive Accuracy (0.9730).
- Interpretability & Compliance: Under financial
compliance frameworks (e.g., FCRA/ECOA adverse action notices), a single
pruned tree (
rpart.plot) provides transparent, explicit
rules for why a applicant was denied credit. Ensemble black-boxes like
standard Random Forest and XGBoost offer virtually no performance gain
here to justify losing that interpretability.
Comparison Summary Table
| RF (SMOTE) |
Maximum Defaulter Detection (75.8% Recall) |
High-risk lending / Loss minimization |
| Pruned CART |
Clear decision rules + solid baseline F1 |
Regulatory compliance / Explainable approval
engines |
| XGBoost / Standard RF |
High Precision (~64%), but poor Recall (~38–39%) |
Conservative screening where False Positives are heavily
penalized |
If you are optimizing for financial loss prevention,
choose RF (SMOTE). If you are optimizing for
transparent automated underwriting, choose
Pruned CART.
LS0tDQp0aXRsZTogIkRlY2lzaW9uIFRyZWVzLCBFbnNlbWJsZSBNZXRob2RzLCBhbmQgQ2xhc3MgSW1iYWxhbmNlIg0Kc3VidGl0bGU6ICJGcm9tIENBUlQgJiBQcnVuaW5nIHRvIFJhbmRvbSBGb3Jlc3RzLCBYR0Jvb3N0LCBhbmQgU01PVEUiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIGhpZ2hsaWdodDogdGFuZ28NCi0tLQ0KDQpJbiB0aGlzIGxlY3R1cmUsIHdlIGV4cGxvcmUgbm9uLXBhcmFtZXRyaWMgdHJlZS1iYXNlZCBhbGdvcml0aG1zIGFuZCB0ZWNobmlxdWVzIGZvciBpbWJhbGFuY2VkIGRhdGEgc2V0cy4NCg0KIyMjIFJlcXVpcmVkIExpYnJhcmllcw0KYGBge3IgbGlicmFyaWVzfQ0KIyBDb3JlIGVjb3N5c3RlbQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KElTTFIyKSAgICAgICAjIGRhdGEgc2V0DQpsaWJyYXJ5KGNhcmV0KSAgICAgICAjIFVuaWZpZWQgaW50ZXJmYWNlIGZvciB0cmFpbmluZyBhbmQgZXZhbHVhdGlvbg0KbGlicmFyeShEVCkgICAgICAgICAgIyBmYW5jeSB0YWJsZXMNCg0KIyBUcmVlLWJhc2VkIG1vZGVsIHBhY2thZ2VzDQpsaWJyYXJ5KHJwYXJ0KSAgICAgICAjIFNpbmdsZSBkZWNpc2lvbiB0cmVlcw0KbGlicmFyeShycGFydC5wbG90KSAgIyBWaXN1YWxpemluZyBDQVJUIHN0cnVjdHVyZXMNCmxpYnJhcnkocmFuZG9tRm9yZXN0KSMgQmFnZ2luZyAmIFJhbmRvbSBGb3Jlc3RzDQpsaWJyYXJ5KHhnYm9vc3QpICAgICAjIEdyYWRpZW50IGJvb3N0ZWQgZGVjaXNpb24gdHJlZXMNCg0KIyBJbWJhbGFuY2VkIGRhdGEgcmVzYW1wbGluZw0KbGlicmFyeSh0aGVtaXMpICAgICAgIyBUaWR5bW9kZWxzIC8gcmVjaXBlIFNNT1RFIG1ldGhvZHMNCmxpYnJhcnkocmVjaXBlcykgICAgICMgUHJlcHJvY2Vzc2luZyBwaXBlbGluZXMNCmBgYA0KDQoNCiMjIFNpbmdsZSBEZWNpc2lvbiBUcmVlcyAoQ0FSVCkgYW5kIFBydW5pbmcNCg0KIyMjIFRoZW9yZXRpY2FsIEZvdW5kYXRpb246IFJlY3Vyc2l2ZSBCaW5hcnkgU3BsaXR0aW5nICANCkRlY2lzaW9uIHRyZWVzIHNwbGl0IHRoZSBwcmVkaWN0b3Igc3BhY2UgJFIkIGludG8gZGlzdGluY3QsIG5vbi1vdmVybGFwcGluZyByZWdpb25zICRSXzEsIFJfMiwgXGRvdHMsIFJfSiQuIEZvciBjbGFzc2lmaWNhdGlvbiwgd2UgYWltIHRvIG1pbmltaXplIGEgbWVhc3VyZSBvZiBub2RlIGltcHVyaXR5Og0KDQogIC0gKipHaW5pIEluZGV4Kio6DQogIA0KJCRHID0gXHN1bV97az0xfV57S30gXGhhdHtwfV97bWt9ICgxIC0gXGhhdHtwfV97bWt9KSQkDQogIA0KICAtICoqQ3Jvc3MtRW50cm9weSoqOg0KDQokJEQgPSAtXHN1bV97az0xfV57S30gXGhhdHtwfV97bWt9IFxsb2cgXGhhdHtwfV97bWt9JCQNCg0KV2hlcmUgJFxoYXR7cH1fe21rfSQgcmVwcmVzZW50cyB0aGUgcHJvcG9ydGlvbiBvZiB0cmFpbmluZyBvYnNlcnZhdGlvbnMgaW4gdGhlICRtJC10aCByZWdpb24gdGhhdCBiZWxvbmcgdG8gY2xhc3MgJGskLg0KDQojIyMgRGF0YSBQcmVwYXJhdGlvbiAoYElTTFIyOjpEZWZhdWx0YCkNCldlIHVzZSB0aGUgRGVmYXVsdCBkYXRhc2V0IGZyb20gYElTTFIyYC4gVGhlIG9iamVjdGl2ZSBpcyB0byBwcmVkaWN0IHdoZXRoZXIgYSBjdXN0b21lciB3aWxsIGRlZmF1bHQgb24gdGhlaXIgY3JlZGl0IGNhcmQgZGVidCBiYXNlZCBvbiBgc3R1ZGVudGAsIGBiYWxhbmNlYCwgYW5kIGBpbmNvbWVgLg0KDQpgYGB7ciBkYXRhLXByZXB9DQpkYXRhKCJEZWZhdWx0IiwgcGFja2FnZSA9ICJJU0xSMiIpDQoNCiMgSW5zcGVjdCBzdHJ1Y3R1cmUgYW5kIHRhcmdldCBkaXN0cmlidXRpb24NCnN0cihEZWZhdWx0KQ0KdGFibGUoRGVmYXVsdCRkZWZhdWx0KQ0KDQojIFRyYWluIC8gVGVzdCBzcGxpdCAoODAvMjApIHVzaW5nIGNhcmV0DQpzZXQuc2VlZCg0MikNCnRyYWluX2lkeCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKERlZmF1bHQkZGVmYXVsdCwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQ0KdHJhaW5fZGYgIDwtIERlZmF1bHRbdHJhaW5faWR4LCBdDQp0ZXN0X2RmICAgPC0gRGVmYXVsdFstdHJhaW5faWR4LCBdDQpgYGANCg0KIyMjIEZpdHRpbmcgYSBGdWxsIFRyZWUgJiBDb3N0LUNvbXBsZXhpdHkgUHJ1bmluZw0KDQpBIGZ1bGx5IGdyb3duIHRyZWUgb3ZlcmZpdHMgbm9pc2UuIFdlIHBydW5lIHVzaW5nIFtDb3N0LUNvbXBsZXhpdHkgUHJ1bmluZ10oaHR0cHM6Ly9vbmxpbmUuc3RhdC5wc3UuZWR1L3N0YXQ4NTcvbm9kZS82MC8pIChpbmRleGVkIGJ5IHBhcmFtZXRlciAkXGFscGhhJCBvciAkY3AkIGluIGBycGFydGApOg0KDQokJENfXGFscGhhKFQpID0gXHN1bV97bT0xfV57fFR8fSBOX20gUV9tKFQpICsgXGFscGhhIHxUfCQkDQoNCioqV2hlbiB5b3UgcnVuIGBwcmludGNwKClgLCBSIHByaW50cyBhIHRhYmxlIHdpdGggY29sdW1ucyBsaWtlIGBDUGAsIGBuc3BsaXRgLCBgcmVsIGVycm9yYCwgYHhlcnJvcmAsIGFuZCBgeHN0ZGAuKioNCmBgYHtyfQ0KIyBGaXQgaW5pdGlhbCB0cmVlIHdpdGggbG93IGNvbXBsZXhpdHkgcGFyYW1ldGVyIHRvIGFsbG93IGRlZXAgZ3Jvd3RoDQpmdWxsX3RyZWUgPC0gcnBhcnQoZGVmYXVsdCB+IC4sIGRhdGEgPSB0cmFpbl9kZiwgbWV0aG9kID0gImNsYXNzIiwgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woY3AgPSAwLjAwMSkpDQoNCiMgUGxvdCBDb21wbGV4aXR5IFBhcmFtZXRlciAoQ1ApIHRhYmxlDQpwcmludGNwKGZ1bGxfdHJlZSkNCmBgYA0KDQoqICoqYENQYCAoQ29tcGxleGl0eSBQYXJhbWV0ZXIpOioqIFRoZSBwZW5hbHR5IGFwcGxpZWQgdG8gdGhlIHRyZWUncyBzaXplLiBTbWFsbGVyIGBDUGAgdmFsdWVzIG1lYW4gYmlnZ2VyLCBtb3JlIGNvbXBsZXggdHJlZXMuDQoqICoqYG5zcGxpdGA6KiogVGhlIG51bWJlciBvZiBzcGxpdHMgKGJyYW5jaGVzKSBpbiB0aGUgdHJlZS4NCiogKipgcmVsIGVycm9yYDoqKiBUaGUgY2xhc3NpZmljYXRpb24gZXJyb3Igb24geW91ciAqKnRyYWluaW5nIHNldCoqLiBUaGlzIHdpbGwgYWxtb3N0IGFsd2F5cyBnbyBkb3duIGFzIHRoZSB0cmVlIGdldHMgYmlnZ2VyLg0KKiAqKmB4ZXJyb3JgIChDcm9zcy1WYWxpZGF0aW9uIEVycm9yKToqKiBUaGUgZXJyb3IgZXN0aW1hdGVkIG9uIHVuc2VlbiBkYXRhIChvdXQtb2Ytc2FtcGxlKS4gKipUaGlzIGlzIHlvdXIgcHJpbWFyeSB0YXJnZXQuKioNCiogKipgeHN0ZGA6KiogVGhlIHN0YW5kYXJkIGRldmlhdGlvbi9zdGFuZGFyZCBlcnJvciBvZiBgeGVycm9yYC4NCg0KDQpZb3Ugd2FudCB0byBzZWUgd2hlcmUgYHhlcnJvcmAgc3RvcHMgZHJvcHBpbmcgYW5kIHN0YXJ0cyBnb2luZyBiYWNrIHVwIChhIHNpZ24gb2Ygb3ZlcmZpdHRpbmcpLg0KDQo+ICoqVGhlIEdvYWw6KiogRmluZCB0aGUgcm93IHdpdGggdGhlIGxvd2VzdCBgeGVycm9yYC4gYHdoaWNoLm1pbihmdWxsX3RyZWUkY3B0YWJsZVssICJ4ZXJyb3IiXSlgIGF1dG9tYXRpY2FsbHkgc2VsZWN0cyB0aGUgYENQYCBmcm9tIHRoaXMgcm93IHRvIHBydW5lIHRoZSB0cmVlLg0KDQoNCg0KKipgcGxvdGNwKGZ1bGxfdHJlZSlgIGdpdmVzIHlvdSBhIHZpc3VhbCBwbG90IG9mIHRoZSB0YWJsZSBhYm92ZToqKg0KDQpgYGB7cn0NCnBsb3RjcChmdWxsX3RyZWUpDQoNCmBgYA0KDQoqICoqWC1heGlzICh0b3AvYm90dG9tKToqKiBgQ1BgIHZhbHVlIGFuZCBzaXplIG9mIHRyZWUgKG51bWJlciBvZiBzcGxpdHMpLg0KKiAqKlktYXhpczoqKiBDcm9zcy12YWxpZGF0ZWQgZXJyb3IgKGB4ZXJyb3JgKS4NCiogKipEYXNoZWQgSG9yaXpvbnRhbCBMaW5lOioqIFJlcHJlc2VudHMgdGhlICIxLVNFIHJ1bGUiICgxIHN0YW5kYXJkIGVycm9yIGFib3ZlIHRoZSBtaW5pbXVtIGB4ZXJyb3JgKS4NCg0KIyMjIFdoYXQgdG8gTG9vayBGb3I6DQoNCiogQSAqKlUtc2hhcGVkIG9yIGVsYm93IGN1cnZlKiouDQoqIFRoZSBsb3dlc3QgcG9pbnQgb24gdGhlIGxpbmUgY29ycmVzcG9uZHMgdG8gdGhlIG9wdGltYWwgdHJlZSBzaXplLg0KKiBJZiB0aGUgY3VydmUgZHJvcHMgcmFwaWRseSBhbmQgdGhlbiBmbGF0dGVucyBvdXQsIHRoZSAiZWxib3cgcG9pbnQiIGdpdmVzIHlvdSBhIGdvb2QgYmFsYW5jZSBiZXR3ZWVuIG1vZGVsIHNpbXBsaWNpdHkgYW5kIGFjY3VyYWN5Lg0KDQoqKmBycGFydC5wbG90KC4uLilgIHByaW50cyB0aGUgdmlzdWFsIHJlcHJlc2VudGF0aW9uIG9mIHlvdXIgcHJ1bmVkIGRlY2lzaW9uIHRyZWUuKioNCg0KYGBge3J9DQojIEV4dHJhY3Qgb3B0aW1hbCBDUCBjb3JyZXNwb25kaW5nIHRvIG1pbmltdW0gY3Jvc3MtdmFsaWRhdGVkIGVycm9yDQpvcHRfY3AgPC0gZnVsbF90cmVlJGNwdGFibGVbd2hpY2gubWluKGZ1bGxfdHJlZSRjcHRhYmxlWywgInhlcnJvciJdKSwgIkNQIl0NCg0KIyBQcnVuZSB0aGUgdHJlZQ0KcHJ1bmVkX3RyZWUgPC0gcnBhcnQ6OnBydW5lKGZ1bGxfdHJlZSwgY3AgPSBvcHRfY3ApDQoNCiMgVmlzdWFsaXplIHRoZSBwcnVuZWQgdHJlZQ0KcnBhcnQucGxvdChwcnVuZWRfdHJlZSwgbWFpbiA9ICJQcnVuZWQgRGVjaXNpb24gVHJlZSAoRGVmYXVsdCBDbGFzc2lmaWNhdGlvbikiLCBleHRyYSA9IDEwNCkNCmBgYA0KDQoxLiAqKlRoZSBSb290IE5vZGUgKFRvcCk6KiogU2hvd3MgdGhlIG92ZXJhbGwgZGVmYXVsdCByYXRlIGluIHlvdXIgdHJhaW5pbmcgc2V0IGFuZCB0aGUgdG90YWwgcGVyY2VudGFnZSBvZiBkYXRhICgxMDAlKS4NCjIuICoqU3BsaXR0aW5nIFZhcmlhYmxlczoqKiBUaGUgcHJpbWFyeSBmZWF0dXJlIGNob3NlbiBhdCBlYWNoIG5vZGUgcmVwcmVzZW50cyB0aGUgbW9zdCBzaWduaWZpY2FudCBmZWF0dXJlIGZvciBzZXBhcmF0aW5nIGRlZmF1bHRzIGZyb20gbm9uLWRlZmF1bHRzLg0KMy4gKipUaGUgVGVybWluYWwgTm9kZXMgKExlYXZlcyBhdCB0aGUgQm90dG9tKToqKiAgDQogICAgKiAqKlRvcCBOdW1iZXI6KiogUHJlZGljdGVkIGNsYXNzIChlLmcuLCBgTm9gIGZvciBObyBEZWZhdWx0LCBgWWVzYCBmb3IgRGVmYXVsdCkuDQogICAgKiAqKlBlcmNlbnRhZ2VzOioqDQogICAgICAqIF9GaXJzdCBwZXJjZW50YWdlOl8gUHJvcG9ydGlvbiBvZiBvYnNlcnZhdGlvbnMgZmFsbGluZyBpbnRvIGNsYXNzIDAgdnMgY2xhc3MgMSBhdCB0aGF0IHNwZWNpZmljIGxlYWYuDQogICAgICAqIF9TZWNvbmQgcGVyY2VudGFnZTpfIFRoZSBwZXJjZW50YWdlIG9mIHlvdXIgb3ZlcmFsbCBkYXRhIHNldCB0aGF0IGxhbmRlZCBpbiB0aGF0IGxlYWYgbm9kZS4NCg0KIyMgRW5zZW1ibGUgTWV0aG9kcyDigJQgUmFuZG9tIEZvcmVzdHMNCg0KIyMjIFdoeSBTaW5nbGUgVHJlZXMgRmFpbA0KU2luZ2xlIHRyZWVzIHN1ZmZlciBmcm9tICoqaGlnaCB2YXJpYW5jZSoqOiBzbWFsbCBjaGFuZ2VzIGluIHRyYWluaW5nIGRhdGEgeWllbGQgcmFkaWNhbGx5IGRpZmZlcmVudCB0cmVlIHN0cnVjdHVyZXMuDQoNCiMjIyBCYWdnaW5nIHRvIFJhbmRvbSBGb3Jlc3RzDQoxLiAqKkJhZ2dpbmcgKEJvb3RzdHJhcCBBZ2dyZWdhdGluZykqKjogRml0cyAkQiQgYm9vdHN0cmFwIHNhbXBsZXMgYW5kIGF2ZXJhZ2VzIHByZWRpY3Rpb25zOg0KJCRcaGF0e2Z9X3tiYWd9KHgpID0gXGZyYWN7MX17Qn0gXHN1bV97Yj0xfV57Qn0gXGhhdHtmfV57KmJ9KHgpJCQNCjIuICoqUmFuZG9tIEZvcmVzdHMqKjogU29sdmVzIHRyZWUgY29ycmVsYXRpb24gaW4gYmFnZ2luZyBieSByZXN0cmljdGluZyBlYWNoIHNwbGl0IHRvIGNvbnNpZGVyIGEgcmFuZG9tIHNhbXBsZSBvZiAkbSBcYXBwcm94IFxzcXJ0e3B9JCBwcmVkaWN0b3JzIG91dCBvZiAkcCQgYXZhaWxhYmxlIGZlYXR1cmVzLg0KDQojIyMgRml0dGluZyBhIFJhbmRvbSBGb3Jlc3QgaW4gUiAoY2FyZXQpDQpgYGB7ciByYW5kb20tZm9yZXN0fQ0KZml0X2NvbnRyb2wgPC0gdHJhaW5Db250cm9sKA0KICBtZXRob2QgPSAiY3YiLA0KICBudW1iZXIgPSA1LA0KICBjbGFzc1Byb2JzID0gVFJVRSwNCiAgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5DQopDQoNCnNldC5zZWVkKDQyKQ0KcmZfZml0IDwtIHRyYWluKA0KICBkZWZhdWx0IH4gLiwNCiAgZGF0YSA9IHRyYWluX2RmLA0KICBtZXRob2QgPSAicmYiLA0KICBtZXRyaWMgPSAiUk9DIiwNCiAgdHJDb250cm9sID0gZml0X2NvbnRyb2wsDQogIHR1bmVHcmlkID0gZXhwYW5kLmdyaWQobXRyeSA9IGMoMSwgMiwgMykpLA0KICBudHJlZSA9IDMwMA0KKQ0KDQpwcmludChyZl9maXQpDQpgYGANCkluIFJhbmRvbSBGb3Jlc3QgbW9kZWxzLCAqKmBtdHJ5YCoqIGlzIHRoZSBudW1iZXIgb2YgdmFyaWFibGVzIChmZWF0dXJlcykgcmFuZG9tbHkgc2FtcGxlZCBhcyBjYW5kaWRhdGVzIGF0IGVhY2ggc3BsaXQgaW4gYSB0cmVlLg0KDQpJbnN0ZWFkIG9mIHRlc3RpbmcgKmV2ZXJ5IHNpbmdsZSBmZWF0dXJlKiBpbiB0aGUgZGF0YSBzZXQgdG8gZGVjaWRlIGhvdyB0byBzcGxpdCBhIG5vZGUgKHdoaWNoIGlzIHdoYXQgYSBzaW5nbGUsIHN0YW5kYXJkIGRlY2lzaW9uIHRyZWUgZG9lcyksIGEgUmFuZG9tIEZvcmVzdCByYW5kb21seSBwaWNrcyBhIHNtYWxsIHN1YnNldCBvZiBmZWF0dXJlc+KAlGhlcmUsIGV4YWN0bHkgKiozKirigJRhbmQgb25seSBldmFsdWF0ZXMgdGhvc2UgMyB0byBmaW5kIHRoZSBiZXN0IHNwbGl0IHBvaW50Lg0KDQpUaGUgbWFpbiByZWFzb24gUmFuZG9tIEZvcmVzdHMgcGVyZm9ybSBzbyBtdWNoIGJldHRlciB0aGFuIGluZGl2aWR1YWwgZGVjaXNpb24gdHJlZXMgY29tZXMgZG93biB0byAqKmRlY29ycmVsYXRpbmcgdGhlIHRyZWVzKio6DQoNCjEuICoqUHJldmVudHMgRG9taW5hbnQgRmVhdHVyZXM6KiogSWYgeW91ciBkYXRhIHNldCBoYXMgb25lIHVsdHJhLXN0cm9uZyBwcmVkaWN0b3IgKGUuZy4sICpDcmVkaXQgU2NvcmUqKSwgYSBzdGFuZGFyZCB0cmVlIHdpbGwgKmFsd2F5cyogc3BsaXQgb24gaXQgZmlyc3QuIEV2ZXJ5IHNpbmdsZSB0cmVlIGluIGFuIGVuc2VtYmxlIHdvdWxkIGVuZCB1cCBsb29raW5nIGFsbW9zdCBpZGVudGljYWwsIGdpdmluZyB5b3UgYSBmb3Jlc3QgZnVsbCBvZiBpZGVudGljYWwgdHJlZXMuDQoyLiAqKkZvcmNlcyBWYXJpZXR5OioqIEJ5IGZvcmNpbmcgdGhlIG1vZGVsIHRvIHBpY2sgZnJvbSBvbmx5IGBtdHJ5ID0gM2AgcmFuZG9tIGZlYXR1cmVzIGF0IGVhY2ggc3BsaXQsIGl0IG9jY2FzaW9uYWxseSBmb3JjZXMgYSB0cmVlIHRvIGlnbm9yZSB0aGF0IGRvbWluYW50IGZlYXR1cmUgYW5kIGRpc2NvdmVyIGhpZGRlbiwgc2Vjb25kYXJ5IHJlbGF0aW9uc2hpcHMgaW4gb3RoZXIgdmFyaWFibGVzLg0KMy4gKipSZWR1Y2VzIFZhcmlhbmNlOioqIEF2ZXJhZ2luZyBwcmVkaWN0aW9ucyBhY3Jvc3MgaHVuZHJlZHMgb2Ygc2xpZ2h0bHkgZGlmZmVyZW50LCB1bmNvcnJlbGF0ZWQgdHJlZXMgZHJhc3RpY2FsbHkgY3V0cyBkb3duIG9uIG1vZGVsIHZhcmlhbmNlIChvdmVyZml0dGluZykgd2l0aG91dCBpbmNyZWFzaW5nIGJpYXMuDQoNClBhY2thZ2VzIGxpa2UgYGNhcmV0YCBvciBgcmFuZG9tRm9yZXN0YCBpbiBSLCBgbXRyeWAgaXMgdHlwaWNhbGx5IHRoZSBtYWluIGh5cGVycGFyYW1ldGVyIHR1bmVkIGR1cmluZyBjcm9zcy12YWxpZGF0aW9uLiBEZWZhdWx0IHN0YXJ0aW5nIHZhbHVlcyBnZW5lcmFsbHkgZm9sbG93IHRoZXNlIHN0YW5kYXJkIGNvbnZlbnRpb25zOg0KDQoqICoqQ2xhc3NpZmljYXRpb246KiogJFxzcXJ0e3B9JCAod2hlcmUgJHAkIGlzIHRoZSB0b3RhbCBudW1iZXIgb2YgcHJlZGljdG9yIHZhcmlhYmxlcykNCiogKipSZWdyZXNzaW9uOioqICRcZnJhY3twfXszfSQNCg0KDQojIyMjIFZhcmlhYmxlIEltcG9ydGFuY2UNCg0KV2hpbGUgYSBzaW5nbGUgZGVjaXNpb24gdHJlZSBnaXZlcyB5b3UgYW4gZXhwbGljaXQgZmxvd2NoYXJ0IHRvIHJlYWQsIGEgUmFuZG9tIEZvcmVzdCBjb21iaW5lcyBodW5kcmVkcyBvZiB0cmVlcywgbWFraW5nIGl0IGEgYml0IG9mIGEgImJsYWNrIGJveC4iDQoNClJ1bm5pbmcgYHZhckltcCgpYCBnaXZlcyB5b3UgdGhlIHRyYW5zcGFyZW5jeSBiYWNrOg0KDQoqICoqSW50ZXJwcmV0YWJpbGl0eToqKiBZb3UgY2FuIGVhc2lseSBleHBsYWluIHRvIG5vbi10ZWNobmljYWwgc3Rha2Vob2xkZXJzICp3aGljaCogZmVhdHVyZXMgYWN0dWFsbHkgZHJpdmUgdGhlIHByZWRpY3Rpb25zLg0KKiAqKkZlYXR1cmUgU2VsZWN0aW9uOioqIElmIHlvdSBoYXZlIGEgbWFzc2l2ZSBkYXRhc2V0IHdpdGggNTArIHZhcmlhYmxlcywgeW91IGNhbiB1c2UgdGhpcyBwbG90IHRvIHBydW5lIG91dCB0aGUgYm90dG9tIGhhbGYgdGhhdCBhcmVuJ3QgY29udHJpYnV0aW5nLCBzaW1wbGlmeWluZyBmdXR1cmUgZGF0YSBjb2xsZWN0aW9uIGFuZCBzcGVlZGluZyB1cCBtb2RlbCBleGVjdXRpb24uDQoNCmBgYHtyfQ0KcGxvdCh2YXJJbXAocmZfZml0KSwgbWFpbiA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIC0gUmFuZG9tIEZvcmVzdCIpDQpgYGANCg0KDQpJbiBhIFJhbmRvbSBGb3Jlc3QsIGZlYXR1cmUgaW1wb3J0YW5jZSBtZWFzdXJlcyBob3cgbXVjaCB3b3JzZSB0aGUgbW9kZWwgcGVyZm9ybXMgd2l0aG91dCB0aGF0IGZlYXR1cmUuIEl0J3MgZ2VuZXJhbGx5IGNhbGN1bGF0ZWQgaW4gb25lIG9mIHR3byB3YXlzOg0KDQoxLiAqKk1lYW4gRGVjcmVhc2UgaW4gQWNjdXJhY3kgKFBlcm11dGF0aW9uIEltcG9ydGFuY2UpOioqDQoqIFRoZSBtb2RlbCB0YWtlcyBvdXQtb2YtYmFnIChPT0IpIGRhdGEgYW5kIHJhbmRvbWx5IHNodWZmbGVzIChzY3JhbWJsZXMpIHRoZSB2YWx1ZXMgb2YgYSBzaW5nbGUgdmFyaWFibGUsIGtlZXBpbmcgZXZlcnl0aGluZyBlbHNlIHRoZSBzYW1lLg0KKiBJdCBtZWFzdXJlcyBob3cgbXVjaCB0aGUgbW9kZWwncyBwcmVkaWN0aW9uIGFjY3VyYWN5IGRyb3BzLiBJZiBhY2N1cmFjeSBwbHVtbWV0cywgdGhhdCB2YXJpYWJsZSB3YXMgZG9pbmcgaGVhdnkgbGlmdGluZy4NCg0KMi4gKipNZWFuIERlY3JlYXNlIGluIEdpbmkgLyBJbXB1cml0eToqKg0KKiBJdCBzdW1zIHVwIHRoZSB0b3RhbCByZWR1Y3Rpb24gaW4gbm9kZSBpbXB1cml0eSAoZS5nLiwgR2luaSBpbXB1cml0eSBmb3IgY2xhc3NpZmljYXRpb24gb3IgTVNFIGZvciByZWdyZXNzaW9uKSBicm91Z2h0IGFib3V0IGJ5IHNwbGl0cyBvbiB0aGF0IHNwZWNpZmljIHZhcmlhYmxlLCBhdmVyYWdlZCBhY3Jvc3MgYWxsIHRyZWVzIGluIHRoZSBmb3Jlc3QuDQoNCiMjIEdyYWRpZW50IEJvb3N0ZWQgRGVjaXNpb24gVHJlZXMgKFhHQm9vc3QpDQoNCiMjIyBCb29zdGluZyBNZWNoYW5pY3MNClVubGlrZSBSYW5kb20gRm9yZXN0cyAoaW5kZXBlbmRlbnQgcGFyYWxsZWwgdHJlZXMpLCAqKkJvb3N0aW5nKiogYnVpbGRzIHRyZWVzIHNlcXVlbnRpYWxseS4gRWFjaCBuZXcgdHJlZSBpcyBmaXR0ZWQgdG8gdGhlIHJlc2lkdWFsIGVycm9ycyBvZiBwcmlvciB0cmVlczoNCiQkZl9iKHgpID0gZl97Yi0xfSh4KSArIFxsYW1iZGEgXGhhdHtmfV5iKHgpJCQNCioqWEdCb29zdCoqIChlWHRyZW1lIEdyYWRpZW50IEJvb3N0aW5nKSBvcHRpbWl6ZXMgc2Vjb25kLW9yZGVyIFRheWxvciBhcHByb3hpbWF0aW9ucyBvZiB0aGUgbG9zcyBmdW5jdGlvbiB3aXRoIEwxL0wyIHJlZ3VsYXJpemF0aW9uIHRvIHByZXZlbnQgb3ZlcmZpdHRpbmcuDQoNCiMjIyBGaXR0aW5nIE5hdGl2ZSBYR0Jvb3N0IHdpdGggYHhnYi50cmFpbmANCiooTm90ZTogVHJhaW5lZCB1c2luZyBgeGdib29zdGAgZGlyZWN0bHkgdG8gaW50ZXJmYWNlIGNsZWFubHkgd2l0aCBtb2Rlcm4gQysrIEFMVFJFUCBtZW1vcnkgcG9pbnRlcnMuKSoNCg0KYGBge3IgeGdib29zdC1maXR9DQojIE9uZS1ob3QgZW5jb2RlIHByZWRpY3RvcnMgZm9yIFhHQm9vc3QNClhfdHJhaW4gPC0gbW9kZWwubWF0cml4KGRlZmF1bHQgfiAuIC0gMSwgZGF0YSA9IHRyYWluX2RmKQ0KeV90cmFpbiA8LSBpZmVsc2UodHJhaW5fZGYkZGVmYXVsdCA9PSAiWWVzIiwgMSwgMCkNCg0KZHRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3RyYWluLCBsYWJlbCA9IHlfdHJhaW4pDQoNCiMgU2V0IGh5cGVycGFyYW1ldGVyIGNvbmZpZ3VyYXRpb24NCnBhcmFtcyA8LSBsaXN0KA0KICBib29zdGVyICAgICAgICAgID0gImdidHJlZSIsDQogIG9iamVjdGl2ZSAgICAgICAgPSAiYmluYXJ5OmxvZ2lzdGljIiwNCiAgZXZhbF9tZXRyaWMgICAgICA9ICJhdWMiLA0KICBldGEgICAgICAgICAgICAgID0gMC4xLA0KICBtYXhfZGVwdGggICAgICAgID0gNCwNCiAgZ2FtbWEgICAgICAgICAgICA9IDAsDQogIGNvbHNhbXBsZV9ieXRyZWUgPSAwLjgsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSAxLA0KICBzdWJzYW1wbGUgICAgICAgID0gMC44DQopDQoNCnNldC5zZWVkKDQyKQ0KeGdiX2ZpdCA8LSB4Z2IudHJhaW4oDQogIHBhcmFtcyAgICA9IHBhcmFtcywNCiAgZGF0YSAgICAgID0gZHRyYWluLA0KICBucm91bmRzICAgPSAxMDAsDQogIHZlcmJvc2UgICA9IDANCikNCg0KcHJpbnQoeGdiX2ZpdCkNCmBgYA0KDQojIyBEZWFsaW5nIHdpdGggSW1iYWxhbmNlZCBEYXRhc2V0cw0KDQojIyMgVGhlIEltYmFsYW5jZSBQcm9ibGVtDQpJbiB0aGUgRGVmYXVsdCBkYXRhc2V0LCBkZWZhdWx0IGV2ZW50cyBhY2NvdW50IGZvciBvbmx5IH4zLjMlIG9mIHRvdGFsIHNhbXBsZXMuIEEgbmFpdmUgbW9kZWwgcHJlZGljdGluZyBgTm9gIGZvciBhbGwgaW5zdGFuY2VzIHlpZWxkcyB+OTYuNyUgYWNjdXJhY3ksIHJlbmRlcmluZyBzdGFuZGFyZCBhY2N1cmFjeSB1bmluZm9ybWF0aXZlLg0KDQojIyMgUmVzYW1wbGluZyBTdHJhdGVnaWVzDQogIC0gKipEb3duLXNhbXBsaW5nKio6IFJhbmRvbWx5IGRyb3BzIG1ham9yaXR5IGluc3RhbmNlcy4NCiAgLSAqKlVwLXNhbXBsaW5nKio6IFJhbmRvbWx5IGR1cGxpY2F0ZXMgbWlub3JpdHkgaW5zdGFuY2VzLg0KICAtICoqU01PVEUgKFN5bnRoZXRpYyBNaW5vcml0eSBPdmVyLXNhbXBsaW5nIFRlY2huaXF1ZSkqKjogQ3JlYXRlcyBzeW50aGV0aWMgbWlub3JpdHkgc2FtcGxlcyBhbG9uZyBsaW5lIHNlZ21lbnRzIGNvbm5lY3RpbmcgJGskLW5lYXJlc3QgbWlub3JpdHkgbmVpZ2hib3JzLg0KDQojIyMgSW1wbGVtZW50aW5nIFNNT1RFICYgQ29tcGFyaW5nIFJlc2FtcGxpbmcgaW4gY2FyZXQNCmBgYHtyIHNtb3RlLXJlc2FtcGxpbmd9DQojIFJlY2lwZSBwaXBlbGluZSBmb3IgU01PVEUgcHJlcHJvY2Vzc2luZw0Kc21vdGVfcmVjIDwtIHJlY2lwZShkZWZhdWx0IH4gLiwgZGF0YSA9IHRyYWluX2RmKSAlPiUNCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JSAgIyBDb252ZXJ0cyAnc3R1ZGVudCcgaW50byBudW1lcmljIGR1bW15IHZhcmlhYmxlcw0KICBzdGVwX3Ntb3RlKGRlZmF1bHQsIG92ZXJfcmF0aW8gPSAwLjgpICAgICAjIE5vdyBhbGwgcHJlZGljdG9ycyBhcmUgZG91YmxlL2ludGVnZXIhDQoNCiMgQ29udHJvbCBzdHJ1Y3R1cmUgdHVuZWQgZm9yIFNlbnNpdGl2aXR5L1JlY2FsbA0KZXZhbF9jb250cm9sIDwtIHRyYWluQ29udHJvbCgNCiAgbWV0aG9kID0gImN2IiwNCiAgbnVtYmVyID0gNSwNCiAgY2xhc3NQcm9icyA9IFRSVUUsDQogIHN1bW1hcnlGdW5jdGlvbiA9IHR3b0NsYXNzU3VtbWFyeQ0KKQ0KDQojIDEuIEJhc2VsaW5lIE1vZGVsIChTdGFuZGFyZCBSRikNCnNldC5zZWVkKDQyKQ0KcmZfYmFzZSA8LSB0cmFpbihkZWZhdWx0IH4gLiwgZGF0YSA9IHRyYWluX2RmLCBtZXRob2QgPSAicmYiLCBtZXRyaWMgPSAiUk9DIiwgdHJDb250cm9sID0gZXZhbF9jb250cm9sKQ0KDQojIDIuIERvd24tU2FtcGxlZCBSRg0KY29udHJvbF9kb3duIDwtIGV2YWxfY29udHJvbA0KY29udHJvbF9kb3duJHNhbXBsaW5nIDwtICJkb3duIg0Kc2V0LnNlZWQoNDIpDQpyZl9kb3duIDwtIHRyYWluKGRlZmF1bHQgfiAuLCBkYXRhID0gdHJhaW5fZGYsIG1ldGhvZCA9ICJyZiIsIG1ldHJpYyA9ICJST0MiLCB0ckNvbnRyb2wgPSBjb250cm9sX2Rvd24pDQoNCiMgMy4gU01PVEUtU2FtcGxlZCBSRiB2aWEgcmVjaXBlcy90aGVtaXMNCnNldC5zZWVkKDQyKQ0KcmZfc21vdGUgPC0gdHJhaW4oc21vdGVfcmVjLCBkYXRhID0gdHJhaW5fZGYsIG1ldGhvZCA9ICJyZiIsIG1ldHJpYyA9ICJST0MiLCB0ckNvbnRyb2wgPSBldmFsX2NvbnRyb2wpDQpgYGANCg0KIyMgQ29tcHJlaGVuc2l2ZSBNb2RlbCBDb21wYXJpc29uDQpXZSBldmFsdWF0ZSBwZXJmb3JtYW5jZSBvbiB0aGUgaGVsZC1vdXQgdGVzdCBkYXRhIHNldCB1c2luZyBQcmVjaXNpb24sIFJlY2FsbCAoU2Vuc2l0aXZpdHkpLCBTcGVjaWZpY2l0eSwgYW5kIFJPQy1BVUMuDQoNCmBgYHtyIG1vZGVsLWV2YWx1YXRpb259DQptb2RlbHMgPC0gbGlzdCgNCiAgIlBydW5lZCBDQVJUIiAgPSBwcnVuZWRfdHJlZSwNCiAgIlJhbmRvbSBGb3Jlc3QiPSByZl9maXQsDQogICJYR0Jvb3N0IiAgICAgID0geGdiX2ZpdCwNCiAgIlJGIChTTU9URSkiICAgPSByZl9zbW90ZQ0KKQ0KDQpldmFsdWF0ZV9tb2RlbCA8LSBmdW5jdGlvbihtb2RlbCwgdGVzdF9kYXRhLCB0YXJnZXRfY29sID0gImRlZmF1bHQiKSB7DQogIA0KICBpZiAoaW5oZXJpdHMobW9kZWwsICJycGFydCIpKSB7DQogICAgcHJvYnMgPC0gcHJlZGljdChtb2RlbCwgdGVzdF9kYXRhLCB0eXBlID0gInByb2IiKVssICJZZXMiXQ0KICAgIHByZWRzIDwtIGZhY3RvcihpZmVsc2UocHJvYnMgPiAwLjUsICJZZXMiLCAiTm8iKSwgbGV2ZWxzID0gYygiTm8iLCAiWWVzIikpDQogICAgDQogIH0gZWxzZSBpZiAoaW5oZXJpdHMobW9kZWwsICJ4Z2IuQm9vc3RlciIpKSB7DQogICAgIyBQcmVwYXJlIHRlc3QgbWF0cml4IG1hdGNoaW5nIG1vZGVsLm1hdHJpeCBzdHJ1Y3R1cmUNCiAgICBYX3Rlc3QgPC0gbW9kZWwubWF0cml4KGFzLmZvcm11bGEocGFzdGUodGFyZ2V0X2NvbCwgIn4gLiAtIDEiKSksIGRhdGEgPSB0ZXN0X2RhdGEpDQogICAgZHRlc3QgIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3Rlc3QpDQogICAgDQogICAgcHJvYnMgIDwtIHByZWRpY3QobW9kZWwsIGR0ZXN0KQ0KICAgIHByZWRzICA8LSBmYWN0b3IoaWZlbHNlKHByb2JzID4gMC41LCAiWWVzIiwgIk5vIiksIGxldmVscyA9IGMoIk5vIiwgIlllcyIpKQ0KICAgIA0KICB9IGVsc2Ugew0KICAgIHByb2JzIDwtIHByZWRpY3QobW9kZWwsIHRlc3RfZGF0YSwgdHlwZSA9ICJwcm9iIilbLCAiWWVzIl0NCiAgICBwcmVkcyA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0X2RhdGEpDQogIH0NCiAgDQogIGNtIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkcywgdGVzdF9kYXRhW1t0YXJnZXRfY29sXV0sIHBvc2l0aXZlID0gIlllcyIpDQogIA0KICBjKA0KICAgIEFjY3VyYWN5ICAgID0gdW5uYW1lKGNtJG92ZXJhbGxbIkFjY3VyYWN5Il0pLA0KICAgIFNlbnNpdGl2aXR5ID0gdW5uYW1lKGNtJGJ5Q2xhc3NbIlNlbnNpdGl2aXR5Il0pLCAjIFJlY2FsbA0KICAgIFNwZWNpZmljaXR5ID0gdW5uYW1lKGNtJGJ5Q2xhc3NbIlNwZWNpZmljaXR5Il0pLA0KICAgIFByZWNpc2lvbiAgID0gdW5uYW1lKGNtJGJ5Q2xhc3NbIlBvcyBQcmVkIFZhbHVlIl0pLA0KICAgIEYxICAgICAgICAgID0gdW5uYW1lKGNtJGJ5Q2xhc3NbIkYxIl0pDQogICkNCn0NCg0KcmVzdWx0cyA8LSBzYXBwbHkobW9kZWxzLCBldmFsdWF0ZV9tb2RlbCwgdGVzdF9kYXRhID0gdGVzdF9kZikgJT4lIHQoKQ0KZGF0YXRhYmxlKHJvdW5kKHJlc3VsdHMsIDQpLCBjYXB0aW9uID0gIk91dC1vZi1TYW1wbGUgUGVyZm9ybWFuY2UgQ29tcGFyaXNvbiBvbiBDcmVkaXQgRGVmYXVsdCBUZXN0IFNldCIpDQpgYGANCg0KV2hlbiBkZWNpZGluZyB3aGljaCBtb2RlbCB0byBjaG9vc2UgZm9yIGNyZWRpdCBkZWZhdWx0IHByZWRpY3Rpb24sICoqcmF3IGFjY3VyYWN5IGlzIG1pc2xlYWRpbmcqKi4gQmVjYXVzZSBkZWZhdWx0IGV2ZW50cyBhcmUgcmVsYXRpdmVseSByYXJlICh+My4zJSBvZiB0aGUgZGF0YSBzZXQpLCBwcmVkaWN0aW5nICJObyIgZm9yIGV2ZXJ5b25lIHlpZWxkcyB+OTYuNyUgYWNjdXJhY3kuDQoNCkluIGNyZWRpdCByaXNrIG1vZGVsaW5nLCB0aGUgYnVzaW5lc3MgY29zdCBvZiBhICoqRmFsc2UgTmVnYXRpdmUqKiAoYXBwcm92aW5nIGEgbG9hbiBmb3IgYSBib3Jyb3dlciB3aG8gZGVmYXVsdHMsIGxlYWRpbmcgdG8gY2hhcmdlLW9mZnMpIGlzIGRyYXN0aWNhbGx5IGhpZ2hlciB0aGFuIHRoZSBjb3N0IG9mIGEgKipGYWxzZSBQb3NpdGl2ZSoqIChmbGFnZ2luZyBhIGJvcnJvd2VyIHdobyB3b3VsZCBoYXZlIHBhaWQsIHJlc3VsdGluZyBpbiBhIG1pbm9yIHJldmlldyBvciBzbGlnaHQgbG9zcyBvZiB0cmFuc2FjdGlvbiBmZWUpLg0KDQpUaGVyZWZvcmUsIG1vZGVsIHNlbGVjdGlvbiBjb21lcyBkb3duIHRvIHlvdXIgcHJpbWFyeSByaXNrIG9iamVjdGl2ZToNCg0KIyMjICoqUkYgKFNNT1RFKSoqIGZvciBSaXNrIE1hbmFnZW1lbnQNCg0KSWYgdGhlIHByaW1hcnkgZ29hbCBpcyAqKmxvc3MgbWl0aWdhdGlvbiBhbmQgY2F0Y2hpbmcgYXMgbWFueSBwb3RlbnRpYWwgZGVmYXVsdHMgYXMgcG9zc2libGUqKiwgKipSRiAoU01PVEUpKiogaXMgdGhlIGNsZWFyIGNob2ljZS4NCg0KKiAqKlNlbnNpdGl2aXR5L1JlY2FsbCAoMC43NzI3KToqKiBJdCBpZGVudGlmaWVzICoqNzcuMyUgb2YgYWxsIGFjdHVhbCBkZWZhdWx0cyoqIG9uIHVuc2VlbiB0ZXN0IGRhdGEsIGNvbXBhcmVkIHRvIG9ubHkgMzcuOCXigJM0My45JSBmb3IgdGhlIHVuLXJlc2FtcGxlZCBtb2RlbHMuICANCiogKipUaGUgVHJhZGUtT2ZmOioqIEJlY2F1c2UgU01PVEUgYmFsYW5jZXMgdGhlIGNsYXNzIGRpc3RyaWJ1dGlvbiBkdXJpbmcgdHJhaW5pbmcsIGl0IHRyYWRlcyBvZmYgUHJlY2lzaW9uICgwLjIzMTUpIGFuZCBTcGVjaWZpY2l0eSAoMC45MTQxKS4gWW91IHdpbGwgZmxhZyBtb3JlIG5vbi1kZWZhdWx0aW5nIGJvcnJvd2VycyBmb3Igc2Vjb25kYXJ5IHJldmlldywgYnV0IHlvdSBwcm90ZWN0IHRoZSBidXNpbmVzcyBmcm9tIG1hc3NpdmUgY3JlZGl0IGxvc3Nlcy4NCg0KPiAqKkJvdHRvbSBMaW5lIGZvciBDcmVkaXQgUmlzazoqKiBNaXNzaW5nIGEgZGVmYXVsdCBjb3N0cyB0aG91c2FuZHMgaW4gcHJpbmNpcGFsIGxvc3M7IGRvaW5nIGEgc2Vjb25kYXJ5IG1hbnVhbCByZXZpZXcgb24gYSBmYWxzZSBmbGFnIGNvc3RzIGEgZmV3IGRvbGxhcnMuIFJGIChTTU9URSkgYWxpZ25zIGJlc3Qgd2l0aCBhY3R1YWwgYmFuayBlY29ub21pY3MuDQoNCiMjIyAqKlBydW5lZCBDQVJUKiogZm9yIEJhbGFuY2VkIEF1dG9tYXRpb246IA0KDQpJZiB0aGUgZ29hbCBpcyB0byAqKmJhbGFuY2UgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSB3aXRoIHJlZ3VsYXRvcnkgaW50ZXJwcmV0YWJpbGl0eSBhbmQgb3BlcmF0aW9uYWwgc2ltcGxpY2l0eSoqLCAqKlBydW5lZCBDQVJUKiogaXMgdGhlIHN0cm9uZ2VzdCBjYW5kaWRhdGUuDQoNCiogKipCZXN0IFVuYWRqdXN0ZWQgUGVyZm9ybWFuY2U6KiogQW1vbmcgdGhlIG5vbi1TTU9URSBtb2RlbHMsIGl0IHlpZWxkcyB0aGUgaGlnaGVzdCBTZW5zaXRpdml0eSAoMC40Mzk0KSwgdGhlIGhpZ2hlc3QgRjEgc2NvcmUgKDAuNTE3OSksIGFuZCBjb21wZXRpdGl2ZSBBY2N1cmFjeSAoMC45NzMwKS4NCiogKipJbnRlcnByZXRhYmlsaXR5ICYgQ29tcGxpYW5jZToqKiBVbmRlciBmaW5hbmNpYWwgY29tcGxpYW5jZSBmcmFtZXdvcmtzIChlLmcuLCBGQ1JBL0VDT0EgYWR2ZXJzZSBhY3Rpb24gbm90aWNlcyksIGEgc2luZ2xlIHBydW5lZCB0cmVlIChgcnBhcnQucGxvdGApIHByb3ZpZGVzIHRyYW5zcGFyZW50LCBleHBsaWNpdCBydWxlcyBmb3Igd2h5IGEgYXBwbGljYW50IHdhcyBkZW5pZWQgY3JlZGl0LiBFbnNlbWJsZSBibGFjay1ib3hlcyBsaWtlIHN0YW5kYXJkIFJhbmRvbSBGb3Jlc3QgYW5kIFhHQm9vc3Qgb2ZmZXIgdmlydHVhbGx5IG5vIHBlcmZvcm1hbmNlIGdhaW4gaGVyZSB0byBqdXN0aWZ5IGxvc2luZyB0aGF0IGludGVycHJldGFiaWxpdHkuDQoNCiMjIyBDb21wYXJpc29uIFN1bW1hcnkgVGFibGUNCg0KfCBNb2RlbCB8IFByaW1hcnkgQWR2YW50YWdlIHwgQmVzdCBVc2UgQ2FzZSB8DQp8IC0tLSB8IC0tLSB8IC0tLSB8DQp8ICoqUkYgKFNNT1RFKSoqIHwgTWF4aW11bSBEZWZhdWx0ZXIgRGV0ZWN0aW9uICg3NS44JSBSZWNhbGwpIHwgKipIaWdoLXJpc2sgbGVuZGluZyAvIExvc3MgbWluaW1pemF0aW9uKiogfA0KfCAqKlBydW5lZCBDQVJUKiogfCBDbGVhciBkZWNpc2lvbiBydWxlcyArIHNvbGlkIGJhc2VsaW5lIEYxIHwgKipSZWd1bGF0b3J5IGNvbXBsaWFuY2UgLyBFeHBsYWluYWJsZSBhcHByb3ZhbCBlbmdpbmVzKiogfA0KfCAqKlhHQm9vc3QgLyBTdGFuZGFyZCBSRioqIHwgSGlnaCBQcmVjaXNpb24gKH42NCUpLCBidXQgcG9vciBSZWNhbGwgKH4zOOKAkzM5JSkgfCBDb25zZXJ2YXRpdmUgc2NyZWVuaW5nIHdoZXJlIEZhbHNlIFBvc2l0aXZlcyBhcmUgaGVhdmlseSBwZW5hbGl6ZWQgfA0KDQpJZiB5b3UgYXJlIG9wdGltaXppbmcgZm9yICoqZmluYW5jaWFsIGxvc3MgcHJldmVudGlvbioqLCBjaG9vc2UgKipSRiAoU01PVEUpKiouIElmIHlvdSBhcmUgb3B0aW1pemluZyBmb3IgKip0cmFuc3BhcmVudCBhdXRvbWF0ZWQgdW5kZXJ3cml0aW5nKiosIGNob29zZSAqKlBydW5lZCBDQVJUKiou