In this lecture, we explore non-parametric tree-based algorithms and techniques for imbalanced data sets.

Required Libraries

# Core ecosystem
library(tidyverse)
library(ISLR2)       # data set
library(caret)       # Unified interface for training and evaluation
library(DT)          # fancy tables

# Tree-based model packages
library(rpart)       # Single decision trees
library(rpart.plot)  # Visualizing CART structures
library(randomForest)# Bagging & Random Forests
library(xgboost)     # Gradient boosted decision trees

# Imbalanced data resampling
library(themis)      # Tidymodels / recipe SMOTE methods
library(recipes)     # Preprocessing pipelines

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:

  • Gini Index:

\[G = \sum_{k=1}^{K} \hat{p}_{mk} (1 - \hat{p}_{mk})\]

  • Cross-Entropy:

\[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 ...
table(Default$default)

  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:

plotcp(full_tree)

  • 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)

  1. The Root Node (Top): Shows the overall default rate in your training set and the total percentage of data (100%).
  2. Splitting Variables: The primary feature chosen at each node represents the most significant feature for separating defaults from non-defaults.
  3. 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

  1. 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)\]
  2. 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:

  1. 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.
  2. 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.
  3. 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:

  1. 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.
  1. 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

Model Primary Advantage Best Use Case
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