Below is first section of the study guide on Boosting and a Boosting Walk-through. I drew from the Elements of Statistical Learning (ESL) and the Module 8 Asynchronous transcripts you provided. I am writing in first person and keeping the material copy/paste friendly. I include references to the text (ESL, The Elements of Statistical Learning) citeturn0file0 and to the video transcript (Module 8 Asynch) citeturn0file1. I also provide example code in R, SAS, and Python so that you can see how the method is implemented across different environments.

SECTION 1: BOOSTING / BOOSTING WALK-THROUGH

  1. Overview and Intuition of Boosting I see boosting as a method for leveraging many weak learners (each only slightly better than random guessing) and combining them to form a single strong learner. The main idea is that each new model in the “boosting sequence” focuses on the errors made by the previous models. This means boosting is a sequential (not parallel) procedure that iteratively refines its predictions. citeturn0file1

• Contrasting with Bagging: – Bagging uses bootstrap samples in parallel and then averages all the resulting models. – Boosting proceeds in a forward-stagewise manner, reweighting or re-focusing on the hard-to-predict samples each round. citeturn0file1

• Key Advantages: – Often yields lower bias than a single model like a single tree, since boosting iteratively “corrects” itself. – Flexible: can work with decision trees, linear models, or other base learners. – Can handle a variety of loss functions (classification, regression, etc.) citeturn0file0

• Key Disadvantages: – Can be more computationally expensive (because of its sequential nature). – Potential to overfit if not properly regularized or if too many iterations are used. citeturn0file1

  1. General Boosting Algorithm Steps
  1. Fit a weak learner to the data (for example, a very shallow tree).
  2. Evaluate its predictions and compute the residuals or errors.
  3. Make those residuals (or a transformed version) the new “target” in the next iteration.
  4. Fit a new weak learner to these residuals.
  5. Repeat until a stopping criterion is reached (e.g., a maximum number of iterations or minimal improvement). citeturn0file1

Mathematically (high-level), the procedure in a regression setting can be thought of like this:

• Let F(x) denote the current model (initialized to something simple, such as a constant).
• At iteration m: – Compute the residuals rᵢ = yᵢ – F(xᵢ).
– Fit a weak learner hₘ(x) to these residuals.
– Update F(x) ← F(x) + ν ⋅ hₘ(x), where ν is a learning rate.

In practice, variations of this formula exist, especially for different loss functions (logistic loss, etc.). citeturn0file0

  1. Boosting Walk-Through Example (Conceptual) • Step 1: Suppose I have a simple dataset (x, y) where y is somewhat nonlinear in x. I start by fitting a “weak” model—a stump (decision tree of depth=1).
    • Step 2: Calculate residuals: errors = y – prediction. These errors still show clear structure (not random).
    • Step 3: Fit another stump to these errors.
    • Step 4: Add that stump’s predictions (scaled by a small learning rate) to the overall model’s prediction.
    • Step 5: Repeat, each time focusing on what the last model didn’t catch.

By iteration 30 or so, the overall model can capture quite complicated patterns. That is essentially the “magic” of boosting. It is a simple forward-stagewise procedure that can produce powerful results even if each learner is fairly weak. citeturn0file1

  1. Handwritten Formula (Simplified) Below is a simple version of the boosting update for regression, using a generic loss function L(y, F(x)):
  1. Initialize: F₀(x) = arg minᵧ ∑ᵢ L(yᵢ, y).

  2. For m = 1 to M:

    1. Compute pseudo-residuals:
      rᵢₘ = – [∂/∂F(xᵢ)] L(yᵢ, F(xᵢ)) evaluated at F = Fₘ₋₁(xᵢ).
    2. Fit a weak learner hₘ(x) to the {rᵢₘ}.
    3. Compute multiplier γₘ = arg minᵧ ∑ᵢ L(yᵢ, Fₘ₋₁(xᵢ) + γ hₘ(xᵢ)).
    4. Update model:
      Fₘ(x) = Fₘ₋₁(x) + ν ⋅ γₘ hₘ(x).
  3. Final model: Fₘ(x).

Here, ν is a learning rate (0 < ν ≤ 1) and M is the maximum number of iterations. This is the formula you’ll often see in references, including ESL Chapter 10. citeturn0file0

  1. Example Code in Python, R, and SAS

–––––––––––––––––––––––––––––––––––––––––––––––––––– A) Python Example (Using scikit-learn’s AdaBoost as a Basic Boosting)

from sklearn.ensemble import AdaBoostRegressor from sklearn.tree import DecisionTreeRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error

Suppose X, y are your features and targets

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

A “weak learner”: a shallow decision tree

weak_learner = DecisionTreeRegressor(max_depth=1)

Build the AdaBoost ensemble

boost_model = AdaBoostRegressor( base_estimator=weak_learner, n_estimators=30, # Number of iterations learning_rate=0.1, # Shrinkage or step size loss=‘linear’ # For regression )

boost_model.fit(X_train, y_train) preds = boost_model.predict(X_test)

mse = mean_squared_error(y_test, preds) print(“Boosted Model MSE:”, mse)

–––––––––––––––––––––––––––––––––––––––––––––––––––– B) R Example (Using the gbm Package)

install.packages(“gbm”)

library(gbm)

Suppose we have a data frame df with columns for features and a column “y”

We’ll do a simple split:

set.seed(123) n <- nrow(df) train_idx <- sample(1:n, size = floor(0.8*n)) train_data <- df[train_idx, ] test_data <- df[-train_idx, ]

Fit a boosted model with Gaussian loss (regression)

boost_fit <- gbm( formula = y ~ ., distribution = “gaussian”, data = train_data, n.trees = 30, interaction.depth = 1, # max tree depth shrinkage = 0.1, # learning rate bag.fraction = 1.0, # no random sub-sampling cv.folds = 0 # can set this >0 for cross-validation )

Predict on test data

preds <- predict(boost_fit, test_data, n.trees = 30) mse <- mean((test_data$y - preds)^2) print(mse)

–––––––––––––––––––––––––––––––––––––––––––––––––––– C) SAS Example (Using PROC GRADBOOST in SAS Viya or HPFOREST / HPSPLIT Variation) In older Base SAS, there isn’t a built-in “boosting” procedure, so I might emulate it with macros or code. In newer SAS Viya releases, there is PROC GRADBOOST. Below is an illustrative syntax:

/* Assuming we have a CAS session and a data set MYDATA with inputs x1-xp and target y / proc gradboost data=mycas.mydata; input x1-xp / level=interval; / or level=nominal for categorical / target y / level=interval; autotune NTree=(30) / or specify a range to tune / LearningRate=(0.1) / or specify a search range */; savestate rstore=mycas.boost_model; run;

/* Score new data */ proc gradboost score data=mycas.newdata rstore=mycas.boost_model out=mycas.scored; run;

If PROC GRADBOOST is not available, some people replicate boosting by repeatedly fitting residuals in a macro loop with procedures like PROC HPSPLIT (for trees) and saving predictions. But in modern SAS, GRADBOOST handles it directly.

  1. Tips, Pitfalls, and Summary • Learning Rate (Shrinkage): Often set to a relatively small value (e.g., 0.1 or 0.01), because a large learning rate can cause overfitting quickly.
    • Number of Iterations (n_estimators / n.trees): Larger M can improve fit but also increase the risk of overfitting. A common practice is to combine a small learning rate with a larger M.
    • Base Learner Complexity: For decision-tree-based boosting, a max depth of 1–5 is typical.
    • Early Stopping: Use cross-validation or a validation set to stop when the improvement flattens out.

In summary, boosting is a powerful, conceptually simple method that incrementally zeroes in on difficult-to-predict observations. Each new iteration “boosts” the performance by learning from mistakes of the earlier ones. By the end, we have a strong ensemble of weak learners that often achieves excellent predictive accuracy. citeturn0file1

That concludes this first section on Boosting and a Boosting Walk-through.

NEXT

Below is my second section of the study guide, focusing on XGBoost. I drew from the Elements of Statistical Learning (ESL) and the Module 8 Asynchronous transcripts you provided. I’m writing in first person and keeping the material easy to copy/paste. I will include references to ESL (The Elements of Statistical Learning) citeturn0file0 and the video transcript content (Module 8 Asynch) citeturn0file1. I also provide sample code in Python, R, and SAS for illustration.

SECTION 2: XGBOOST

  1. What is XGBoost? I see “XGBoost” (eXtreme Gradient Boosting) as an efficient, high-performance implementation of the gradient boosting framework. It can handle different types of data, offers several parameter-tuning options, and uses second-order gradient approximations to optimize a user-chosen loss function. This approach often yields state-of-the-art results in machine-learning competitions.
    Key references in ESL: Chapter 10 (Boosting), plus general ensemble methods references. citeturn0file0

  2. Core Ideas • Gradient Boosting Foundation. XGBoost is a specific realization of the “gradient boosting” algorithm. It uses the gradient (and sometimes the second derivative, or Hessian) of the loss function with respect to model predictions at each iteration.
    • Approximate Tree Learning. XGBoost grows decision trees level-by-level, with an approximate method for split finding that can handle large datasets efficiently.
    • Regularization for Trees. Unlike some earlier tree-based boosting implementations, XGBoost includes L1 (lasso) and L2 (ridge) penalties on the leaf weights. It also penalizes the total number of leaves (T) in each tree via a parameter gamma, thereby controlling overfitting. citeturn0file1

  3. The XGBoost Objective The typical XGBoost objective function can be summarized as:

Obj = ∑(ᵢ=1 to n) L(yᵢ, Fₘ₋₁(xᵢ) + fₘ(xᵢ)) + Ω(fₘ),

where • L is a loss function, e.g. mean squared error, logistic loss, etc.
• fₘ(x) is the new tree (or base learner) being added in iteration m.
• Ω(fₘ) is a regularization term, typically of the form:
Ω(f) = γ ⋅ T + ½ λ ∑(wⱼ²),
– T = number of leaves in the tree f.
– wⱼ = leaf weights (scores).
– λ corresponds to L2 penalty on the leaf weights.
– γ penalizes each leaf, encouraging shallower trees.

XGBoost fits the tree by (1) approximating the loss with a second-order Taylor expansion, (2) finding the best splits based on that approximation, and (3) updating the model. citeturn0file1

  1. Important Hyperparameters • n_estimators (nrounds in R, n.trees in some references): the maximum number of boosting rounds (trees).
    • eta (learning_rate): shrinkage parameter that scales each tree’s contribution (0 < eta ≤ 1). Smaller values slow down learning but can improve generalization.
    • max_depth: maximum depth of each tree. Deeper trees are more expressive but can overfit.
    • gamma: minimum loss reduction required to make a further partition in a leaf node (i.e., cost complexity). Higher gamma means more conservative tree growth.
    • subsample: fraction of the training data to sample in each boosting round (similar to bagging).
    • colsample_bytree / colsample_bynode: fraction of features to sample in each tree (or split).
    • λ (reg_lambda): L2 regularization on leaf weights (on by default).
    • α (reg_alpha): L1 regularization on leaf weights (off by default, set to > 0 to enable).

These hyperparameters help manage overfitting and can drastically affect XGBoost’s performance. Typically, a methodical approach (grid search, random search, or Bayesian optimization) is needed to find optimal values. citeturn0file1

  1. Pseudocode for XGBoost

Initialization: • F₀(x) = constant (e.g., the average of y if it’s regression).

For m = 1 to M: 1) For each observation i, compute: gᵢ = ∂/∂F(xᵢ) L(yᵢ, Fₘ₋₁(xᵢ)), hᵢ = ∂²/∂F(xᵢ)² L(yᵢ, Fₘ₋₁(xᵢ)). # second derivative 2) Fit a regression tree to the points {(gᵢ, hᵢ)}, with specialized splitting criteria that accounts for gᵢ and hᵢ. 3) For each leaf j, compute the optimal weight wⱼ that minimizes the approximate loss plus regularization. 4) Update Fₘ(x) = Fₘ₋₁(x) + η ⋅ fₘ(x).

Return Fₘ(x).

  1. Example Code Snippets in Python, R, and SAS

–––––––––––––––––––––––––––––––––––––––––––––––––––– A) Python Example

import xgboost as xgb from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error

Suppose X, y are your data and targets (NumPy arrays or Pandas DataFrames)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

Convert into DMatrix, which is a specialized XGBoost data structure

dtrain = xgb.DMatrix(data=X_train, label=y_train) dtest = xgb.DMatrix(data=X_test, label=y_test)

Set parameters: for regression

params = { ‘objective’: ‘reg:squarederror’, ‘eta’: 0.1, ‘max_depth’: 3, ‘subsample’: 0.8, ‘colsample_bytree’: 0.8, ‘lambda’: 1.0, # L2 reg ‘alpha’: 0.0 # L1 reg }

num_rounds = 100 # number of boosting rounds xgb_model = xgb.train(params, dtrain, num_boost_round=num_rounds)

Make predictions

preds = xgb_model.predict(dtest) mse = mean_squared_error(y_test, preds) print(“XGBoost MSE:”, mse)

–––––––––––––––––––––––––––––––––––––––––––––––––––– B) R Example (xgboost library)

install.packages(“xgboost”)

library(xgboost)

Suppose df is our data frame, with numeric columns for X and a numeric y

Make matrices

X <- as.matrix(df[, -which(names(df) == “y”)]) y <- df$y

Train/test split

set.seed(123) train_idx <- sample(nrow(df), size = 0.8 * nrow(df)) X_train <- X[train_idx, ] y_train <- y[train_idx] X_test <- X[-train_idx, ] y_test <- y[-train_idx]

Create xgb.DMatrix

dtrain <- xgb.DMatrix(data = X_train, label = y_train) dtest <- xgb.DMatrix(data = X_test, label = y_test)

Set parameters

params <- list( objective = “reg:squarederror”, eta = 0.1, max_depth = 3, subsample = 0.8, colsample_bytree = 0.8, lambda = 1, alpha = 0 )

Train

xgb_model <- xgb.train( params = params, data = dtrain, nrounds = 100, watchlist = list(train = dtrain, test = dtest), early_stopping_rounds = 10 )

Predict

preds <- predict(xgb_model, X_test) mse <- mean((y_test - preds)^2) print(mse)

–––––––––––––––––––––––––––––––––––––––––––––––––––– C) SAS Example In SAS, you can replicate XGBoost-like functionality in a few ways. If you have SAS Viya, you can leverage PROC XGBOOST. Otherwise, you can approximate it with PROC GRADBOOST or macros for gradient boosting. Here is an example with PROC XGBOOST in SAS Viya:

/* In SAS Viya: / proc cas; session mysession; loadactionset “decisionTree”; / Assuming we have table ‘mytable’ with ‘y’ as target, and x1, x2, …, xp as predictors. / action xgboost.train / table={name=“mytable”} target=“y” inputs={“x1”,“x2”,…, “xp”} nominals={} / specify categorical variables if needed */ nTree=100 objective=“reg:squarederror” maxDepth=3 eta=0.1 subsample=0.8 colSampleByTree=0.8 regLambda=1 regAlpha=0 seed=12345 savestate={name=“myXGBmodel”}; run;

/* Score new data */ proc cas; action xgboost.score / modelState={name=“myXGBmodel”} table={name=“myNewData”} casOut={name=“myScoredData”, replace=True}; run;

  1. Practical Tips and Summary • Regularization Tuning. Don’t neglect gamma, λ (reg_lambda), and α (reg_alpha). These can be key to controlling overfitting.
    • Learning Rate. Typically pick a smaller eta (e.g. 0.01–0.2) and combine with more boosting rounds.
    • Subsampling. Using subsample < 1.0 or colsample_bytree < 1.0 often helps reduce variance and speed up training.
    • Early Stopping. Using early_stopping_rounds can save time by halting training when the model stops improving on a validation set.
    • Custom Losses. One huge advantage of XGBoost is that you can define custom loss functions, as long as they’re differentiable and you can provide gradient and hessian.

In short, XGBoost is a highly optimized framework for gradient boosting with built-in regularization and sophisticated tree-building. It’s widely used in practice for structured data problems and can often outperform simpler methods, provided that you tune the parameters carefully.

Below is my third section of the study guide, focusing on Hyperparameters. I drew from The Elements of Statistical Learning (ESL) and the Module 8 Asynchronous transcripts you provided. I will include references to ESL (The Elements of Statistical Learning) citeturn0file0 and the video transcript (Module 8 Asynch) citeturn0file1. I will also provide some brief code snippets in Python, R, and SAS for illustration.

SECTION 3: HYPERPARAMETERS

  1. What Are Hyperparameters? I consider hyperparameters to be the “settings” or “knobs” that guide the learning process of a model. For a simple linear regression, the parameters are the slopes and intercept (learned from data), but we usually have no hyperparameters to tune. In contrast, for tree-based methods, boosting, and advanced models like neural networks, we have hyperparameters that control complexity, learning rate, regularization, and so on. These hyperparameters are not learned directly from the training data in a simple closed-form manner; instead, we pick them (for instance, by cross-validation or other search methods).
    Relevant references: ESL, Chapter 7 on model assessment and selection; also chapters dealing with each algorithm’s specific tunable knobs (e.g., Chapter 10 on boosting). citeturn0file0

  2. Common Hyperparameters for Tree-Based Models • max_depth: the maximum depth of the tree, controlling how many splits can occur from root to leaf.
    • min_samples_split or min_child_weight (in XGBoost): the minimum number of samples needed in a leaf node or child node, which helps prevent overly small partitions and thus overfitting.
    • gamma (in XGBoost): additional penalty on leaf splits, requiring a minimum loss reduction before a split can be made.
    • sub_sample or bagging_fraction: fraction of the training data to randomly sample for each round (adds randomness, reduces variance).
    • col_sample_by_tree: fraction of features used in each tree.

  3. Learning Rate vs. Number of Estimators • learning_rate (eta): how fast or slow we incorporate a new learner’s contribution in each boosting iteration. Lower learning rates typically require more iterations (n_estimators) to achieve good accuracy, but often generalize better.
    • n_estimators (M): the number of boosting rounds (trees in a boosted ensemble). Too few can underfit; too many might overfit if we don’t monitor for early stopping.
    These two hyperparameters are typically tuned together: small learning_rate with a large n_estimators can yield a high-performing model at a cost of more computation. citeturn0file1

  4. Regularization Hyperparameters • L1 regularization α (alpha) encourages sparsity in tree weights (or other model parameters).
    • L2 regularization λ (lambda) shrinks the weights, penalizing large values.
    • gamma (for XGBoost, LightGBM, etc.) also plays a role in regularization by adding a cost for each leaf in a tree.
    • penalty in logistic regression: can be “l1”, “l2”, or “elastic net”, controlling how coefficients are shrunk or forced to zero.

  5. Searching for Good Hyperparameters I commonly use systematic search procedures such as grid search or random search, possibly augmented by cross-validation. Automated hyperparameter optimization methods (Bayesian optimization, genetic algorithms, Hyperopt, Optuna, etc.) can also be used. The procedure typically involves:

  1. Choose a range or distribution for each hyperparameter.
  2. Sample different combinations of these hyperparameters.
  3. Fit the model on training folds, evaluate on a validation fold.
  4. Pick the combination that yields the best average validation score.
  5. Refit on the full training data if needed.
  1. Example Code Snippet for Hyperparameter Tuning

–––––––––––––––––––––––––––––––––––––––––––––––––––– A) Python (Using scikit-learn’s GridSearchCV)

from sklearn.model_selection import GridSearchCV from sklearn.ensemble import GradientBoostingRegressor

param_grid = { ‘n_estimators’: [50, 100], ‘learning_rate’: [0.01, 0.1, 0.2], ‘max_depth’: [1, 3, 5] }

gbm = GradientBoostingRegressor() grid_search = GridSearchCV( estimator=gbm, param_grid=param_grid, scoring=‘neg_mean_squared_error’, cv=5, n_jobs=-1 )

grid_search.fit(X_train, y_train) print(“Best Params:”, grid_search.best_params_) print(“Best CV Score:”, -grid_search.best_score_)

–––––––––––––––––––––––––––––––––––––––––––––––––––– B) R (Using caret for tuning a GBM)

install.packages(“caret”)

library(caret)

train_control <- trainControl(method = “cv”, number = 5) tune_grid <- expand.grid( n.trees = c(50, 100), interaction.depth = c(1, 3, 5), shrinkage = c(0.01, 0.1, 0.2), n.minobsinnode = c(5, 10) )

set.seed(123) gbm_fit <- train( y ~ ., data = df, method = “gbm”, trControl = train_control, tuneGrid = tune_grid, metric = “RMSE”, verbose = FALSE )

gbm_fit\(bestTune gbm_fit\)results

–––––––––––––––––––––––––––––––––––––––––––––––––––– C) SAS (PROC OPTMODEL or HPC Tuning in SAS Viya) SAS has macros or procedures (like PROC HPFOREST, HPGENSELECT) that can do some hyperparameter selection, but you may need a manual or macro-based approach.

Example snippet using a macro-based approach for searching hyperparameters in SAS older versions (conceptual outline):

%macro tuneMyForest(data=, target=, maxdepth=, ntrees=); proc hpforest data=&data; target &target.; input x1-xp; ntree=&ntrees.; maxdepth=&maxdepth.; /* additional hyperparameters, etc. */ ods output FitStatistics=FitStats; run; %mend;

%tuneMyForest(data=mydata, target=y, maxdepth=5, ntrees=50); /* gather FitStats, compare, etc. */

In modern SAS Viya, you can use AutoTune in actions like decisionTree.gbtreeTrain or xgboost.train, specifying search ranges for the hyperparameters.

  1. Hyperparameter Tuning Pitfalls • Overfitting on validation sets if repeatedly searching a large hyperparameter space.
    • Setting ranges or distributions too narrow can miss better solutions.
    • Computation time can explode with large parameter grids.
    • Sometimes it’s easy to fix certain parameters to well-known defaults (e.g., small learning rate) and only tune the critical ones (like n_estimators, max_depth) to reduce complexity.

  2. Summing Up Hyperparameters are crucial in controlling the behavior and performance of advanced machine-learning models, especially tree-based methods and boosted ensembles. They govern model complexity, regularization strength, and how learning progresses. The right combination of hyperparameters can dramatically improve predictive accuracy while preventing overfitting.

Below is my fourth section of the study guide, focusing on two XGBoost Demos. I drew from the Elements of Statistical Learning (ESL) and the Module 8 Asynchronous transcripts you provided. I will include references to ESL (The Elements of Statistical Learning) citeturn0file0 and the video transcript content (Module 8 Asynch) citeturn0file1. I will also provide sample code so you can replicate the demonstrations.

SECTION 4: XGBOOST DEMO 1 AND 2

DEMO 1: HANDWRITTEN DIGITS CLASSIFICATION (SCIKIT-LEARN + XGBOOST)

  1. Data Overview
    I see the digits dataset from scikit-learn as an example for multiclass classification. It contains 1,797 samples of 8×8 pixel images representing the digits 0 through 9. Each pixel is a feature, so we have 64 features. We want to classify which digit each image represents.

  2. Steps to Replicate

  1. Load Libraries and Data
    • Use scikit-learn’s built-in digits dataset:
    from sklearn.datasets import load_digits
    digits = load_digits()

• X will be digits.data, a (1797 × 64) array, and y will be digits.target (digits 0–9).

  1. Split into Training and Test
    • We can do a simple 70–30 split or use cross-validation.

  2. Convert Data into XGBoost’s DMatrix Format
    • xgb.DMatrix is a specialized data structure for XGBoost, but we can train directly with scikit-learn API too.

  3. Train an XGBoost Classifier
    • For a multiclass task, set the objective to “multi:softprob” or “multi:softmax” and specify num_class=10.

  4. Evaluate Accuracy
    • Use predictions on the test set and measure classification accuracy or confusion matrix.

  1. Demo 1: Code Example (Python)

import xgboost as xgb from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score

Load data

digits = load_digits() X = digits.data # shape (1797, 64) y = digits.target # labels 0 through 9

Train/test split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

XGBoost Classifier using scikit-learn API

xgb_clf = xgb.XGBClassifier( objective=‘multi:softprob’, num_class=10, max_depth=3, learning_rate=0.1, n_estimators=100, subsample=0.8, colsample_bytree=0.8, random_state=42 )

xgb_clf.fit(X_train, y_train)

Predict

y_pred = xgb_clf.predict(X_test) accuracy = accuracy_score(y_test, y_pred) print(“XGBoost classification accuracy:”, accuracy)

• You will typically see accuracy in the 0.95–0.98 range depending on parameter settings.

  1. Notes • Because the images are small (8×8), a simple approach like unrolled pixels is enough to get decent results.
    • For more complex images, more advanced features or deep learning might be appropriate, but XGBoost can still perform surprisingly well on structured tabular data.

DEMO 2: REGRESSION EXAMPLE (CALIFORNIA HOUSING DATA)

  1. Data Overview
    • The California Housing dataset (available in scikit-learn) is a regression problem predicting median house prices based on demographic and geographic features.
    • Features include average income, average house age, average rooms, etc.

  2. Steps to Replicate

  1. Load Libraries and Data
    • from sklearn.datasets import fetch_california_housing
    • cal_housing = fetch_california_housing()

• The input features are in cal_housing.data, the target is cal_housing.target.

  1. Split into Training and Test

  2. Build XGBoost Regressor
    • objective=‘reg:squarederror’ (for standard regression).

  3. Evaluate MSE or R²

  1. Demo 2: Code Example (Python)

import xgboost as xgb from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error

Load data

cal_housing = fetch_california_housing() X = cal_housing.data y = cal_housing.target

Split into train/test

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

XGBoost Regressor

xgb_reg = xgb.XGBRegressor( objective=‘reg:squarederror’, max_depth=4, learning_rate=0.1, n_estimators=200, subsample=0.8, colsample_bytree=0.8, alpha=0, lambda=1, random_state=42 )

xgb_reg.fit(X_train, y_train) preds = xgb_reg.predict(X_test)

mse = mean_squared_error(y_test, preds) print(“XGBoost MSE:”, mse) print(“XGBoost RMSE:”, mse**0.5)

  1. Interpretation and Potential Tuning • You can tune max_depth, learning_rate, alpha, lambda, etc. using a grid search or other methods.
    • Typical MSE might be around 0.3–0.4 or so depending on how the target is scaled, and the RMSE around 0.55–0.63 for the default approach.

SAMPLE CODE IN R

• For the digits classification example, you could build your own dataset or use the “mnist” data. For regression on the California Housing, you can pull from external sources or pre-downloaded data. R code is similar, using xgboost::xgb.train or xgboost::xgboost.

library(xgboost) # Suppose X, y are numeric matrices or vectors # For classification with multiple classes, set objective=“multi:softprob” and num_class=10 # For regression, objective=“reg:squarederror”

dtrain <- xgb.DMatrix(data=X_train, label=y_train) dtest <- xgb.DMatrix(data=X_test, label=y_test)

params <- list( objective = “reg:squarederror”, max_depth = 4, eta = 0.1, subsample = 0.8, colsample_bytree = 0.8 )

bst <- xgb.train(params = params, data = dtrain, nrounds=200) preds <- predict(bst, newdata=dtest)

Evaluate MSE

mse <- mean((y_test - preds)^2) cat(“MSE:”, mse, “”)

SAMPLE CODE IN SAS

• For classification, use something like:

proc cas; session mysession; loadactionset “decisionTree”; action xgboost.train / table={name=“digits_table”} target=“digit_label” inputs={“pixel1”,“pixel2”,…, “pixel64”} objective=“multi:softmax” numClasses=10 nTree=100 maxDepth=3 eta=0.1 subsample=0.8 colSampleByTree=0.8 randomSeed=42 savestate={name=“digits_xgb_model”}; run;

• For regression, set objective=“reg:squarederror” and remove the numClasses parameter.

SUMMARY • XGBoost is straightforward to apply once you’re familiar with the API.
• Classification tasks use objective=“multi:softmax” or “multi:softprob”.
• Regression tasks use objective=“reg:squarederror” or sometimes “reg:linear” in older versions.
• The two demos illustrate typical use cases: classification (handwritten digits) and regression (housing prices).

Below is my fifth section of the study guide, focusing on Grid Search. I based it on The Elements of Statistical Learning (ESL) and the Module 8 Asynchronous transcripts you provided. I will include references to ESL (The Elements of Statistical Learning) citeturn0file0 and content from the transcripts (Module 8 Asynch) citeturn0file1. As usual, I’ll also provide copy/paste friendly code snippets for multiple programming languages.

SECTION 5: GRID SEARCH

  1. What Is Grid Search? I view “Grid Search” as a brute-force method for systematically exploring multiple combinations of hyperparameters. You define a discrete set of possible values (a “grid”) for each hyperparameter, then train and evaluate the model for every possible combination. The key steps are:
  1. Define ranges (or sets) of possible values for each hyperparameter: for example, max_depth ∈ {2,3,4}, learning_rate ∈ {0.01, 0.1}, etc.
  2. For each combination of hyperparameters in the Cartesian product of these sets, train the model on training folds and evaluate on a validation fold (or use cross-validation).
  3. Select the combination that yields the best performance metric.
  4. Optionally, refit the model with the chosen hyperparameters on the entire training set.
  1. Advantages and Disadvantages • Advantages:
    – Straightforward and easy to understand.
    – For a small parameter space, it can be quite effective.

• Disadvantages:
– Potentially expensive in computation time, since we evaluate every combination.
– The total number of combinations grows exponentially with the number of hyperparameters or the size of each grid.

  1. Pseudocode Outline

Given: – A model M(θ) with hyperparameters θ ∈ Θ1 × Θ2 × … × Θp.
– A performance metric Perf(·).
– A method for model evaluation, e.g. K-fold cross-validation.

Algorithm: 1) best_perf ← –∞ (or some minimal reference) 2) For each combination (θ₁, θ₂, …, θp) in Θ₁ × Θ₂ × … × Θp: a) Train model M(θ) on training folds. b) Evaluate on validation fold(s) and compute Perf(θ). c) If Perf(θ) > best_perf: i) best_perf ← Perf(θ) ii) best_params ← θ 3) Return best_params, best_perf

  1. Practical Implementation with Cross-Validation • Typically, in scikit-learn or R’s caret, GridSearchCV or train() automatically uses cross-validation for each combination of hyperparameters.
    • The final model is often retrained using the selected “best_params.”

  2. Example: Python (Using scikit-learn’s GridSearchCV)

from sklearn.model_selection import GridSearchCV from sklearn.ensemble import GradientBoostingClassifier from sklearn.datasets import load_iris from sklearn.metrics import accuracy_score

Load data

iris = load_iris() X, y = iris.data, iris.target

Define the parameter grid

param_grid = { ‘n_estimators’: [50, 100], ‘learning_rate’: [0.01, 0.1], ‘max_depth’: [2, 3, 4] }

Initialize model

model = GradientBoostingClassifier()

Fit

grid_search.fit(X, y) print(“Best Params:”, grid_search.best_params_) print(“Best CV Score:”, grid_search.best_score_)

Evaluate on the same data or separate test set

best_model = grid_search.best_estimator_ preds = best_model.predict(X) accuracy = accuracy_score(y, preds) print(“Accuracy on entire dataset:”, accuracy)

  1. Example: R (Using caret)

library(caret)

Suppose df is a data frame with predictor columns and a factor target “Species”

train_control <- trainControl(method = “cv”, number = 5)

grid <- expand.grid( n.trees = c(50, 100), interaction.depth = c(2, 3, 4), shrinkage = c(0.01, 0.1), n.minobsinnode = c(5) )

set.seed(123) gbm_fit <- train( Species ~ ., data = iris, method = “gbm”, trControl = train_control, tuneGrid = grid, verbose = FALSE ) gbm_fit$bestTune gbm_fit

  1. Example: SAS (Conceptual) In some SAS environments, you can do manual looping for each hyperparameter combination or use autotuning options. For example, in SAS Viya’s CAS environment, certain actions (like xgboost.train) have an Autotune or “tune” parameter:

proc cas; session mySession; loadactionset “decisionTree”; action xgboost.train / table={name=“your_data”} target=“your_target” inputs={“x1”,“x2”,“x3”} autotune={ steps=10, objective=“AUTO”, searchmethod=“grid”, parameters={ { name=“nTree”, values=“50,100” }, { name=“maxDepth”, values=“2,4” }, { name=“eta”, values=“0.01,0.1” } } } ; run;

If your SAS version lacks these features, you can create a macro loop that calls PROC HPFOREST or PROC GRADBOOST with different parameter settings and collects metrics.

  1. When to Use Grid search is most useful when your hyperparameter space is small or you have strong prior intuition about what ranges to explore. If you have many hyperparameters or a wide range, you might consider Random Search or other optimization approaches.

  2. Summary Grid Search systematically explores a predefined set of hyperparameter values, which can guarantee that you don’t miss any combination in that grid. While exhaustive, it can be expensive for large parameter spaces. However, for moderate problem sizes, it remains a standard tool for model selection and can yield excellent results.

Below is my sixth (and final) section of the study guide, focusing on Random Search. As before, I drew on The Elements of Statistical Learning (ESL) citeturn0file0 and the Module 8 Asynchronous transcript material citeturn0file1. Code snippets are given in a copy/paste friendly format.

SECTION 6: RANDOM SEARCH

  1. What is Random Search? I consider Random Search an alternative hyperparameter optimization strategy to Grid Search. Instead of exhaustively enumerating a grid of possible hyperparameter values, we randomly sample from specified distributions for each hyperparameter. The key idea is that randomly chosen points in a high-dimensional space can often cover diverse regions more efficiently than an exhaustive (grid) method with the same computational budget.

  2. Advantages over Grid Search • Efficiency: For the same number of trials, random search often finds better parameter settings than a coarse grid, especially when only a few hyperparameters are truly influential.
    • Scalability: By sampling from each hyperparameter’s distribution, you can easily add more samples or draw from specialized distributions (log scale, uniform, etc.).
    • Adaptability: If you discover you need more trials, you can just continue sampling.

  3. Basic Steps

  1. Define a probability distribution or range for each hyperparameter. For example, learning_rate ∼ Uniform(0.01, 0.2), max_depth ∈ {2, 3, 4, 5}, or alpha ∼ LogUniform(1e–5, 1).
  2. Randomly sample a set of hyperparameter configurations.
  3. For each sampled configuration, train the model (e.g., with cross-validation) and record a performance metric.
  4. Keep track of the best-performing combination and possibly keep searching as resources allow.
  1. Example: Python (Using scikit-learn’s RandomizedSearchCV)

from sklearn.model_selection import RandomizedSearchCV from sklearn.ensemble import GradientBoostingRegressor from sklearn.datasets import load_boston from sklearn.metrics import mean_squared_error import numpy as np

Load data

boston = load_boston() X, y = boston.data, boston.target

Define parameter distributions

param_dist = { ‘n_estimators’: np.random.randint(50, 200, size=50), # sample integers from 50..200 ‘learning_rate’: np.linspace(0.01, 0.2, num=20), # sample from 0.01..0.2 ‘max_depth’: np.random.randint(2, 6, size=4) # sample from {2,3,4,5} }

Model

model = GradientBoostingRegressor()

Define the model (e.g., gbm for boosting)

model <- train( Species ~ ., data = iris, method = “gbm”, trControl = train_control, tuneLength = 10 # will try 10 random combinations )

model\(bestTune model\)results

  1. Example: SAS (Conceptual) In SAS, you can randomly generate parameter sets in a macro or in a CAS action if available. If you do not have an automated random search function, you can do something like:

%macro run_random_search(num_runs=10); %do i=1 %to &num_runs; %let ntrees = %sysfunc(rand(integer, 50, 200)); %let depth = %sysfunc(rand(integer, 2, 6)); %let lr = %sysevalf(%sysfunc(rand(uniform))*(0.2-0.01)+0.01);

  /* Then call your chosen procedure (e.g., PROC GRADBOOST, HPFOREST, or xgboost.train)
     with these parameters and record performance. */

%end; %mend;

%run_random_search(num_runs=10);

  1. When to Use Random Search • Especially helpful if your model has many hyperparameters or if you believe only a subset of them significantly affects performance.
    • Useful as a first pass to locate promising regions, followed by a more fine-grained search or a Bayesian optimization method.
    • If you want to quickly scale the number of trials or budget more computing time, random search is easy to extend.

  2. Summary Random Search is a flexible and often surprisingly effective approach to hyperparameter tuning, particularly in higher-dimensional spaces where grid search becomes prohibitively expensive. By specifying meaningful distributions for each hyperparameter, you can focus your search in promising regions and efficiently uncover a strong model configuration.

Here are three key takeaways from the Random Search section:

  1. Random Search samples hyperparameter combinations from predefined distributions, often providing better coverage of the search space than an exhaustive grid for the same number of trials.
  2. It is highly flexible and scalable, making it easy to adjust the number of tested combinations if more time or resources become available.
  3. Random Search can be combined with other methods (like Bayesian optimization) to refine the search after identifying promising regions of hyperparameter values.

Here are three thought-provoking questions to consider:

  1. In what types of scenarios might a purely random approach fail to locate good hyperparameters, and how could you mitigate this?
  2. How can domain knowledge guide your choice of distributions for the hyperparameters, instead of using uniform or naive distributions?
  3. After finding a good hyperparameter set through random search, how do we decide whether to refine it further or accept it as final?
LS0tDQp0aXRsZTogIkJvb3N0aW5nIC0gNzMzMyAtIFFUVyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCkJlbG93IGlzIGZpcnN0IHNlY3Rpb24gb2YgdGhlIHN0dWR5IGd1aWRlIG9uIEJvb3N0aW5nIGFuZCBhIEJvb3N0aW5nIFdhbGstdGhyb3VnaC4gSSBkcmV3IGZyb20gdGhlIEVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nIChFU0wpIGFuZCB0aGUgTW9kdWxlIDggQXN5bmNocm9ub3VzIHRyYW5zY3JpcHRzIHlvdSBwcm92aWRlZC4gSSBhbSB3cml0aW5nIGluIGZpcnN0IHBlcnNvbiBhbmQga2VlcGluZyB0aGUgbWF0ZXJpYWwgY29weS9wYXN0ZSBmcmllbmRseS4gSSBpbmNsdWRlIHJlZmVyZW5jZXMgdG8gdGhlIHRleHQgKEVTTCwgVGhlIEVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nKSDuiIBjaXRl7oiCdHVybjBmaWxlMO6IgSBhbmQgdG8gdGhlIHZpZGVvIHRyYW5zY3JpcHQgKE1vZHVsZSA4IEFzeW5jaCkg7oiAY2l0Ze6IgnR1cm4wZmlsZTHuiIEuIEkgYWxzbyBwcm92aWRlIGV4YW1wbGUgY29kZSBpbiBSLCBTQVMsIGFuZCBQeXRob24gc28gdGhhdCB5b3UgY2FuIHNlZSBob3cgdGhlIG1ldGhvZCBpcyBpbXBsZW1lbnRlZCBhY3Jvc3MgZGlmZmVyZW50IGVudmlyb25tZW50cy4NCg0KU0VDVElPTiAxOiBCT09TVElORyAvIEJPT1NUSU5HIFdBTEstVEhST1VHSA0KDQoxLiBPdmVydmlldyBhbmQgSW50dWl0aW9uIG9mIEJvb3N0aW5nDQpJIHNlZSBib29zdGluZyBhcyBhIG1ldGhvZCBmb3IgbGV2ZXJhZ2luZyBtYW55IHdlYWsgbGVhcm5lcnMgKGVhY2ggb25seSBzbGlnaHRseSBiZXR0ZXIgdGhhbiByYW5kb20gZ3Vlc3NpbmcpIGFuZCBjb21iaW5pbmcgdGhlbSB0byBmb3JtIGEgc2luZ2xlIHN0cm9uZyBsZWFybmVyLiBUaGUgbWFpbiBpZGVhIGlzIHRoYXQgZWFjaCBuZXcgbW9kZWwgaW4gdGhlIOKAnGJvb3N0aW5nIHNlcXVlbmNl4oCdIGZvY3VzZXMgb24gdGhlIGVycm9ycyBtYWRlIGJ5IHRoZSBwcmV2aW91cyBtb2RlbHMuIFRoaXMgbWVhbnMgYm9vc3RpbmcgaXMgYSBzZXF1ZW50aWFsIChub3QgcGFyYWxsZWwpIHByb2NlZHVyZSB0aGF0IGl0ZXJhdGl2ZWx5IHJlZmluZXMgaXRzIHByZWRpY3Rpb25zLiDuiIBjaXRl7oiCdHVybjBmaWxlMe6IgQ0KDQrigKIgQ29udHJhc3Rpbmcgd2l0aCBCYWdnaW5nOg0KICDigJMgQmFnZ2luZyB1c2VzIGJvb3RzdHJhcCBzYW1wbGVzIGluIHBhcmFsbGVsIGFuZCB0aGVuIGF2ZXJhZ2VzIGFsbCB0aGUgcmVzdWx0aW5nIG1vZGVscy4NCiAg4oCTIEJvb3N0aW5nIHByb2NlZWRzIGluIGEgZm9yd2FyZC1zdGFnZXdpc2UgbWFubmVyLCByZXdlaWdodGluZyBvciByZS1mb2N1c2luZyBvbiB0aGUgaGFyZC10by1wcmVkaWN0IHNhbXBsZXMgZWFjaCByb3VuZC4g7oiAY2l0Ze6IgnR1cm4wZmlsZTHuiIENCg0K4oCiIEtleSBBZHZhbnRhZ2VzOg0KICDigJMgT2Z0ZW4geWllbGRzIGxvd2VyIGJpYXMgdGhhbiBhIHNpbmdsZSBtb2RlbCBsaWtlIGEgc2luZ2xlIHRyZWUsIHNpbmNlIGJvb3N0aW5nIGl0ZXJhdGl2ZWx5IOKAnGNvcnJlY3Rz4oCdIGl0c2VsZi4NCiAg4oCTIEZsZXhpYmxlOiBjYW4gd29yayB3aXRoIGRlY2lzaW9uIHRyZWVzLCBsaW5lYXIgbW9kZWxzLCBvciBvdGhlciBiYXNlIGxlYXJuZXJzLg0KICDigJMgQ2FuIGhhbmRsZSBhIHZhcmlldHkgb2YgbG9zcyBmdW5jdGlvbnMgKGNsYXNzaWZpY2F0aW9uLCByZWdyZXNzaW9uLCBldGMuKSDuiIBjaXRl7oiCdHVybjBmaWxlMO6IgQ0KDQrigKIgS2V5IERpc2FkdmFudGFnZXM6DQogIOKAkyBDYW4gYmUgbW9yZSBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlIChiZWNhdXNlIG9mIGl0cyBzZXF1ZW50aWFsIG5hdHVyZSkuDQogIOKAkyBQb3RlbnRpYWwgdG8gb3ZlcmZpdCBpZiBub3QgcHJvcGVybHkgcmVndWxhcml6ZWQgb3IgaWYgdG9vIG1hbnkgaXRlcmF0aW9ucyBhcmUgdXNlZC4g7oiAY2l0Ze6IgnR1cm4wZmlsZTHuiIENCg0KMi4gR2VuZXJhbCBCb29zdGluZyBBbGdvcml0aG0gU3RlcHMNCjEpIEZpdCBhIHdlYWsgbGVhcm5lciB0byB0aGUgZGF0YSAoZm9yIGV4YW1wbGUsIGEgdmVyeSBzaGFsbG93IHRyZWUpLiAgDQoyKSBFdmFsdWF0ZSBpdHMgcHJlZGljdGlvbnMgYW5kIGNvbXB1dGUgdGhlIHJlc2lkdWFscyBvciBlcnJvcnMuICANCjMpIE1ha2UgdGhvc2UgcmVzaWR1YWxzIChvciBhIHRyYW5zZm9ybWVkIHZlcnNpb24pIHRoZSBuZXcg4oCcdGFyZ2V04oCdIGluIHRoZSBuZXh0IGl0ZXJhdGlvbi4gIA0KNCkgRml0IGEgbmV3IHdlYWsgbGVhcm5lciB0byB0aGVzZSByZXNpZHVhbHMuICANCjUpIFJlcGVhdCB1bnRpbCBhIHN0b3BwaW5nIGNyaXRlcmlvbiBpcyByZWFjaGVkIChlLmcuLCBhIG1heGltdW0gbnVtYmVyIG9mIGl0ZXJhdGlvbnMgb3IgbWluaW1hbCBpbXByb3ZlbWVudCkuIO6IgGNpdGXuiIJ0dXJuMGZpbGUx7oiBDQoNCk1hdGhlbWF0aWNhbGx5IChoaWdoLWxldmVsKSwgdGhlIHByb2NlZHVyZSBpbiBhIHJlZ3Jlc3Npb24gc2V0dGluZyBjYW4gYmUgdGhvdWdodCBvZiBsaWtlIHRoaXM6DQoNCuKAoiBMZXQgRih4KSBkZW5vdGUgdGhlIGN1cnJlbnQgbW9kZWwgKGluaXRpYWxpemVkIHRvIHNvbWV0aGluZyBzaW1wbGUsIHN1Y2ggYXMgYSBjb25zdGFudCkuICANCuKAoiBBdCBpdGVyYXRpb24gbToNCiAg4oCTIENvbXB1dGUgdGhlIHJlc2lkdWFscyBy4bWiID0geeG1oiDigJMgRih44bWiKS4gIA0KICDigJMgRml0IGEgd2VhayBsZWFybmVyIGjigpgoeCkgdG8gdGhlc2UgcmVzaWR1YWxzLiAgDQogIOKAkyBVcGRhdGUgRih4KSDihpAgRih4KSArIM69IOKLhSBo4oKYKHgpLCB3aGVyZSDOvSBpcyBhIGxlYXJuaW5nIHJhdGUuICANCg0KSW4gcHJhY3RpY2UsIHZhcmlhdGlvbnMgb2YgdGhpcyBmb3JtdWxhIGV4aXN0LCBlc3BlY2lhbGx5IGZvciBkaWZmZXJlbnQgbG9zcyBmdW5jdGlvbnMgKGxvZ2lzdGljIGxvc3MsIGV0Yy4pLiDuiIBjaXRl7oiCdHVybjBmaWxlMO6IgQ0KDQozLiBCb29zdGluZyBXYWxrLVRocm91Z2ggRXhhbXBsZSAoQ29uY2VwdHVhbCkNCuKAoiBTdGVwIDE6IFN1cHBvc2UgSSBoYXZlIGEgc2ltcGxlIGRhdGFzZXQgKHgsIHkpIHdoZXJlIHkgaXMgc29tZXdoYXQgbm9ubGluZWFyIGluIHguIEkgc3RhcnQgYnkgZml0dGluZyBhIOKAnHdlYWvigJ0gbW9kZWzigJRhIHN0dW1wIChkZWNpc2lvbiB0cmVlIG9mIGRlcHRoPTEpLiAgDQrigKIgU3RlcCAyOiBDYWxjdWxhdGUgcmVzaWR1YWxzOiBlcnJvcnMgPSB5IOKAkyBwcmVkaWN0aW9uLiBUaGVzZSBlcnJvcnMgc3RpbGwgc2hvdyBjbGVhciBzdHJ1Y3R1cmUgKG5vdCByYW5kb20pLiAgDQrigKIgU3RlcCAzOiBGaXQgYW5vdGhlciBzdHVtcCB0byB0aGVzZSBlcnJvcnMuICANCuKAoiBTdGVwIDQ6IEFkZCB0aGF0IHN0dW1w4oCZcyBwcmVkaWN0aW9ucyAoc2NhbGVkIGJ5IGEgc21hbGwgbGVhcm5pbmcgcmF0ZSkgdG8gdGhlIG92ZXJhbGwgbW9kZWzigJlzIHByZWRpY3Rpb24uICANCuKAoiBTdGVwIDU6IFJlcGVhdCwgZWFjaCB0aW1lIGZvY3VzaW5nIG9uIHdoYXQgdGhlIGxhc3QgbW9kZWwgZGlkbuKAmXQgY2F0Y2guICANCg0KQnkgaXRlcmF0aW9uIDMwIG9yIHNvLCB0aGUgb3ZlcmFsbCBtb2RlbCBjYW4gY2FwdHVyZSBxdWl0ZSBjb21wbGljYXRlZCBwYXR0ZXJucy4gVGhhdCBpcyBlc3NlbnRpYWxseSB0aGUg4oCcbWFnaWPigJ0gb2YgYm9vc3RpbmcuIEl0IGlzIGEgc2ltcGxlIGZvcndhcmQtc3RhZ2V3aXNlIHByb2NlZHVyZSB0aGF0IGNhbiBwcm9kdWNlIHBvd2VyZnVsIHJlc3VsdHMgZXZlbiBpZiBlYWNoIGxlYXJuZXIgaXMgZmFpcmx5IHdlYWsuIO6IgGNpdGXuiIJ0dXJuMGZpbGUx7oiBDQoNCjQuIEhhbmR3cml0dGVuIEZvcm11bGEgKFNpbXBsaWZpZWQpDQpCZWxvdyBpcyBhIHNpbXBsZSB2ZXJzaW9uIG9mIHRoZSBib29zdGluZyB1cGRhdGUgZm9yIHJlZ3Jlc3Npb24sIHVzaW5nIGEgZ2VuZXJpYyBsb3NzIGZ1bmN0aW9uIEwoeSwgRih4KSk6DQoNCjEpIEluaXRpYWxpemU6DQogICBG4oKAKHgpID0gYXJnIG1pbuG1pyDiiJHhtaIgTCh54bWiLCB5KS4gIA0KDQoyKSBGb3IgbSA9IDEgdG8gTToNCiAgIGEpIENvbXB1dGUgcHNldWRvLXJlc2lkdWFsczogIA0KICAgICAgcuG1ouKCmCA9IOKAkyBb4oiCL+KIgkYoeOG1oildIEwoeeG1oiwgRih44bWiKSkgZXZhbHVhdGVkIGF0IEYgPSBG4oKY4oKL4oKBKHjhtaIpLiAgDQogICBiKSBGaXQgYSB3ZWFrIGxlYXJuZXIgaOKCmCh4KSB0byB0aGUge3LhtaLigph9LiAgDQogICBjKSBDb21wdXRlIG11bHRpcGxpZXIgzrPigpggPSBhcmcgbWlu4bWnIOKIkeG1oiBMKHnhtaIsIEbigpjigovigoEoeOG1oikgKyDOsyBo4oKYKHjhtaIpKS4gIA0KICAgZCkgVXBkYXRlIG1vZGVsOiAgDQogICAgICBG4oKYKHgpID0gRuKCmOKCi+KCgSh4KSArIM69IOKLhSDOs+KCmCBo4oKYKHgpLiAgDQoNCjMpIEZpbmFsIG1vZGVsOiBG4oKYKHgpLiAgDQoNCkhlcmUsIM69IGlzIGEgbGVhcm5pbmcgcmF0ZSAoMCA8IM69IOKJpCAxKSBhbmQgTSBpcyB0aGUgbWF4aW11bSBudW1iZXIgb2YgaXRlcmF0aW9ucy4gVGhpcyBpcyB0aGUgZm9ybXVsYSB5b3XigJlsbCBvZnRlbiBzZWUgaW4gcmVmZXJlbmNlcywgaW5jbHVkaW5nIEVTTCBDaGFwdGVyIDEwLiDuiIBjaXRl7oiCdHVybjBmaWxlMO6IgQ0KDQo1LiBFeGFtcGxlIENvZGUgaW4gUHl0aG9uLCBSLCBhbmQgU0FTDQoNCuKAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAkw0KQSkgUHl0aG9uIEV4YW1wbGUgKFVzaW5nIHNjaWtpdC1sZWFybuKAmXMgQWRhQm9vc3QgYXMgYSBCYXNpYyBCb29zdGluZykNCg0KZnJvbSBza2xlYXJuLmVuc2VtYmxlIGltcG9ydCBBZGFCb29zdFJlZ3Jlc3Nvcg0KZnJvbSBza2xlYXJuLnRyZWUgaW1wb3J0IERlY2lzaW9uVHJlZVJlZ3Jlc3Nvcg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgdHJhaW5fdGVzdF9zcGxpdA0KZnJvbSBza2xlYXJuLm1ldHJpY3MgaW1wb3J0IG1lYW5fc3F1YXJlZF9lcnJvcg0KDQojIFN1cHBvc2UgWCwgeSBhcmUgeW91ciBmZWF0dXJlcyBhbmQgdGFyZ2V0cw0KWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KFgsIHksIHRlc3Rfc2l6ZT0wLjIpDQoNCiMgQSAid2VhayBsZWFybmVyIjogYSBzaGFsbG93IGRlY2lzaW9uIHRyZWUNCndlYWtfbGVhcm5lciA9IERlY2lzaW9uVHJlZVJlZ3Jlc3NvcihtYXhfZGVwdGg9MSkNCg0KIyBCdWlsZCB0aGUgQWRhQm9vc3QgZW5zZW1ibGUNCmJvb3N0X21vZGVsID0gQWRhQm9vc3RSZWdyZXNzb3IoDQogICAgYmFzZV9lc3RpbWF0b3I9d2Vha19sZWFybmVyLA0KICAgIG5fZXN0aW1hdG9ycz0zMCwgICAgICAgIyBOdW1iZXIgb2YgaXRlcmF0aW9ucw0KICAgIGxlYXJuaW5nX3JhdGU9MC4xLCAgICAgIyBTaHJpbmthZ2Ugb3Igc3RlcCBzaXplDQogICAgbG9zcz0nbGluZWFyJyAgICAgICAgICAjIEZvciByZWdyZXNzaW9uDQopDQoNCmJvb3N0X21vZGVsLmZpdChYX3RyYWluLCB5X3RyYWluKQ0KcHJlZHMgPSBib29zdF9tb2RlbC5wcmVkaWN0KFhfdGVzdCkNCg0KbXNlID0gbWVhbl9zcXVhcmVkX2Vycm9yKHlfdGVzdCwgcHJlZHMpDQpwcmludCgiQm9vc3RlZCBNb2RlbCBNU0U6IiwgbXNlKQ0KDQrigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJMNCkIpIFIgRXhhbXBsZSAoVXNpbmcgdGhlIGdibSBQYWNrYWdlKQ0KDQojIGluc3RhbGwucGFja2FnZXMoImdibSIpDQpsaWJyYXJ5KGdibSkNCg0KIyBTdXBwb3NlIHdlIGhhdmUgYSBkYXRhIGZyYW1lIGRmIHdpdGggY29sdW1ucyBmb3IgZmVhdHVyZXMgYW5kIGEgY29sdW1uICJ5Ig0KIyBXZSdsbCBkbyBhIHNpbXBsZSBzcGxpdDoNCnNldC5zZWVkKDEyMykNCm4gPC0gbnJvdyhkZikNCnRyYWluX2lkeCA8LSBzYW1wbGUoMTpuLCBzaXplID0gZmxvb3IoMC44Km4pKQ0KdHJhaW5fZGF0YSA8LSBkZlt0cmFpbl9pZHgsIF0NCnRlc3RfZGF0YSAgPC0gZGZbLXRyYWluX2lkeCwgXQ0KDQojIEZpdCBhIGJvb3N0ZWQgbW9kZWwgd2l0aCBHYXVzc2lhbiBsb3NzIChyZWdyZXNzaW9uKQ0KYm9vc3RfZml0IDwtIGdibSgNCiAgZm9ybXVsYSA9IHkgfiAuLCANCiAgZGlzdHJpYnV0aW9uID0gImdhdXNzaWFuIiwNCiAgZGF0YSA9IHRyYWluX2RhdGEsDQogIG4udHJlZXMgPSAzMCwNCiAgaW50ZXJhY3Rpb24uZGVwdGggPSAxLCAgIyBtYXggdHJlZSBkZXB0aA0KICBzaHJpbmthZ2UgPSAwLjEsICAgICAgICAjIGxlYXJuaW5nIHJhdGUNCiAgYmFnLmZyYWN0aW9uID0gMS4wLCAgICAgIyBubyByYW5kb20gc3ViLXNhbXBsaW5nDQogIGN2LmZvbGRzID0gMCAgICAgICAgICAgICMgY2FuIHNldCB0aGlzID4wIGZvciBjcm9zcy12YWxpZGF0aW9uDQopDQoNCiMgUHJlZGljdCBvbiB0ZXN0IGRhdGENCnByZWRzIDwtIHByZWRpY3QoYm9vc3RfZml0LCB0ZXN0X2RhdGEsIG4udHJlZXMgPSAzMCkNCm1zZSA8LSBtZWFuKCh0ZXN0X2RhdGEkeSAtIHByZWRzKV4yKQ0KcHJpbnQobXNlKQ0KDQrigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJMNCkMpIFNBUyBFeGFtcGxlIChVc2luZyBQUk9DIEdSQURCT09TVCBpbiBTQVMgVml5YSBvciBIUEZPUkVTVCAvIEhQU1BMSVQgVmFyaWF0aW9uKQ0KSW4gb2xkZXIgQmFzZSBTQVMsIHRoZXJlIGlzbuKAmXQgYSBidWlsdC1pbiDigJxib29zdGluZ+KAnSBwcm9jZWR1cmUsIHNvIEkgbWlnaHQgZW11bGF0ZSBpdCB3aXRoIG1hY3JvcyBvciBjb2RlLiBJbiBuZXdlciBTQVMgVml5YSByZWxlYXNlcywgdGhlcmUgaXMgUFJPQyBHUkFEQk9PU1QuIEJlbG93IGlzIGFuIGlsbHVzdHJhdGl2ZSBzeW50YXg6DQoNCi8qIEFzc3VtaW5nIHdlIGhhdmUgYSBDQVMgc2Vzc2lvbiBhbmQgYSBkYXRhIHNldCBNWURBVEEgd2l0aCBpbnB1dHMgeDEteHAgYW5kIHRhcmdldCB5ICovDQpwcm9jIGdyYWRib29zdCBkYXRhPW15Y2FzLm15ZGF0YTsNCiAgIGlucHV0IHgxLXhwIC8gbGV2ZWw9aW50ZXJ2YWw7IC8qIG9yIGxldmVsPW5vbWluYWwgZm9yIGNhdGVnb3JpY2FsICovDQogICB0YXJnZXQgeSAvIGxldmVsPWludGVydmFsOw0KICAgYXV0b3R1bmUgTlRyZWU9KDMwKSAgICAgICAgICAgLyogb3Igc3BlY2lmeSBhIHJhbmdlIHRvIHR1bmUgKi8NCiAgICAgICAgICAgIExlYXJuaW5nUmF0ZT0oMC4xKSAgIC8qIG9yIHNwZWNpZnkgYSBzZWFyY2ggcmFuZ2UgKi87DQogICBzYXZlc3RhdGUgcnN0b3JlPW15Y2FzLmJvb3N0X21vZGVsOyANCnJ1bjsNCg0KLyogU2NvcmUgbmV3IGRhdGEgKi8NCnByb2MgZ3JhZGJvb3N0IHNjb3JlIGRhdGE9bXljYXMubmV3ZGF0YQ0KICAgcnN0b3JlPW15Y2FzLmJvb3N0X21vZGVsDQogICBvdXQ9bXljYXMuc2NvcmVkOw0KcnVuOw0KDQpJZiBQUk9DIEdSQURCT09TVCBpcyBub3QgYXZhaWxhYmxlLCBzb21lIHBlb3BsZSByZXBsaWNhdGUgYm9vc3RpbmcgYnkgcmVwZWF0ZWRseSBmaXR0aW5nIHJlc2lkdWFscyBpbiBhIG1hY3JvIGxvb3Agd2l0aCBwcm9jZWR1cmVzIGxpa2UgUFJPQyBIUFNQTElUIChmb3IgdHJlZXMpIGFuZCBzYXZpbmcgcHJlZGljdGlvbnMuIEJ1dCBpbiBtb2Rlcm4gU0FTLCBHUkFEQk9PU1QgaGFuZGxlcyBpdCBkaXJlY3RseS4NCg0KNi4gVGlwcywgUGl0ZmFsbHMsIGFuZCBTdW1tYXJ5DQrigKIgTGVhcm5pbmcgUmF0ZSAoU2hyaW5rYWdlKTogT2Z0ZW4gc2V0IHRvIGEgcmVsYXRpdmVseSBzbWFsbCB2YWx1ZSAoZS5nLiwgMC4xIG9yIDAuMDEpLCBiZWNhdXNlIGEgbGFyZ2UgbGVhcm5pbmcgcmF0ZSBjYW4gY2F1c2Ugb3ZlcmZpdHRpbmcgcXVpY2tseS4gIA0K4oCiIE51bWJlciBvZiBJdGVyYXRpb25zIChuX2VzdGltYXRvcnMgLyBuLnRyZWVzKTogTGFyZ2VyIE0gY2FuIGltcHJvdmUgZml0IGJ1dCBhbHNvIGluY3JlYXNlIHRoZSByaXNrIG9mIG92ZXJmaXR0aW5nLiBBIGNvbW1vbiBwcmFjdGljZSBpcyB0byBjb21iaW5lIGEgc21hbGwgbGVhcm5pbmcgcmF0ZSB3aXRoIGEgbGFyZ2VyIE0uICANCuKAoiBCYXNlIExlYXJuZXIgQ29tcGxleGl0eTogRm9yIGRlY2lzaW9uLXRyZWUtYmFzZWQgYm9vc3RpbmcsIGEgbWF4IGRlcHRoIG9mIDHigJM1IGlzIHR5cGljYWwuICANCuKAoiBFYXJseSBTdG9wcGluZzogVXNlIGNyb3NzLXZhbGlkYXRpb24gb3IgYSB2YWxpZGF0aW9uIHNldCB0byBzdG9wIHdoZW4gdGhlIGltcHJvdmVtZW50IGZsYXR0ZW5zIG91dC4gIA0KDQpJbiBzdW1tYXJ5LCBib29zdGluZyBpcyBhIHBvd2VyZnVsLCBjb25jZXB0dWFsbHkgc2ltcGxlIG1ldGhvZCB0aGF0IGluY3JlbWVudGFsbHkgemVyb2VzIGluIG9uIGRpZmZpY3VsdC10by1wcmVkaWN0IG9ic2VydmF0aW9ucy4gRWFjaCBuZXcgaXRlcmF0aW9uIOKAnGJvb3N0c+KAnSB0aGUgcGVyZm9ybWFuY2UgYnkgbGVhcm5pbmcgZnJvbSBtaXN0YWtlcyBvZiB0aGUgZWFybGllciBvbmVzLiBCeSB0aGUgZW5kLCB3ZSBoYXZlIGEgc3Ryb25nIGVuc2VtYmxlIG9mIHdlYWsgbGVhcm5lcnMgdGhhdCBvZnRlbiBhY2hpZXZlcyBleGNlbGxlbnQgcHJlZGljdGl2ZSBhY2N1cmFjeS4g7oiAY2l0Ze6IgnR1cm4wZmlsZTHuiIENCg0KVGhhdCBjb25jbHVkZXMgdGhpcyBmaXJzdCBzZWN0aW9uIG9uIEJvb3N0aW5nIGFuZCBhIEJvb3N0aW5nIFdhbGstdGhyb3VnaC4gIA0KDQoNCk5FWFQNCg0KQmVsb3cgaXMgbXkgc2Vjb25kIHNlY3Rpb24gb2YgdGhlIHN0dWR5IGd1aWRlLCBmb2N1c2luZyBvbiBYR0Jvb3N0LiBJIGRyZXcgZnJvbSB0aGUgRWxlbWVudHMgb2YgU3RhdGlzdGljYWwgTGVhcm5pbmcgKEVTTCkgYW5kIHRoZSBNb2R1bGUgOCBBc3luY2hyb25vdXMgdHJhbnNjcmlwdHMgeW91IHByb3ZpZGVkLiBJ4oCZbSB3cml0aW5nIGluIGZpcnN0IHBlcnNvbiBhbmQga2VlcGluZyB0aGUgbWF0ZXJpYWwgZWFzeSB0byBjb3B5L3Bhc3RlLiBJIHdpbGwgaW5jbHVkZSByZWZlcmVuY2VzIHRvIEVTTCAoVGhlIEVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nKSDuiIBjaXRl7oiCdHVybjBmaWxlMO6IgSBhbmQgdGhlIHZpZGVvIHRyYW5zY3JpcHQgY29udGVudCAoTW9kdWxlIDggQXN5bmNoKSDuiIBjaXRl7oiCdHVybjBmaWxlMe6IgS4gSSBhbHNvIHByb3ZpZGUgc2FtcGxlIGNvZGUgaW4gUHl0aG9uLCBSLCBhbmQgU0FTIGZvciBpbGx1c3RyYXRpb24uDQoNClNFQ1RJT04gMjogWEdCT09TVA0KDQoxLiBXaGF0IGlzIFhHQm9vc3Q/DQpJIHNlZSDigJxYR0Jvb3N04oCdIChlWHRyZW1lIEdyYWRpZW50IEJvb3N0aW5nKSBhcyBhbiBlZmZpY2llbnQsIGhpZ2gtcGVyZm9ybWFuY2UgaW1wbGVtZW50YXRpb24gb2YgdGhlIGdyYWRpZW50IGJvb3N0aW5nIGZyYW1ld29yay4gSXQgY2FuIGhhbmRsZSBkaWZmZXJlbnQgdHlwZXMgb2YgZGF0YSwgb2ZmZXJzIHNldmVyYWwgcGFyYW1ldGVyLXR1bmluZyBvcHRpb25zLCBhbmQgdXNlcyBzZWNvbmQtb3JkZXIgZ3JhZGllbnQgYXBwcm94aW1hdGlvbnMgdG8gb3B0aW1pemUgYSB1c2VyLWNob3NlbiBsb3NzIGZ1bmN0aW9uLiBUaGlzIGFwcHJvYWNoIG9mdGVuIHlpZWxkcyBzdGF0ZS1vZi10aGUtYXJ0IHJlc3VsdHMgaW4gbWFjaGluZS1sZWFybmluZyBjb21wZXRpdGlvbnMuICANCktleSByZWZlcmVuY2VzIGluIEVTTDogQ2hhcHRlciAxMCAoQm9vc3RpbmcpLCBwbHVzIGdlbmVyYWwgZW5zZW1ibGUgbWV0aG9kcyByZWZlcmVuY2VzLiDuiIBjaXRl7oiCdHVybjBmaWxlMO6IgQ0KDQoyLiBDb3JlIElkZWFzDQrigKIgR3JhZGllbnQgQm9vc3RpbmcgRm91bmRhdGlvbi4gWEdCb29zdCBpcyBhIHNwZWNpZmljIHJlYWxpemF0aW9uIG9mIHRoZSDigJxncmFkaWVudCBib29zdGluZ+KAnSBhbGdvcml0aG0uIEl0IHVzZXMgdGhlIGdyYWRpZW50IChhbmQgc29tZXRpbWVzIHRoZSBzZWNvbmQgZGVyaXZhdGl2ZSwgb3IgSGVzc2lhbikgb2YgdGhlIGxvc3MgZnVuY3Rpb24gd2l0aCByZXNwZWN0IHRvIG1vZGVsIHByZWRpY3Rpb25zIGF0IGVhY2ggaXRlcmF0aW9uLiAgDQrigKIgQXBwcm94aW1hdGUgVHJlZSBMZWFybmluZy4gWEdCb29zdCBncm93cyBkZWNpc2lvbiB0cmVlcyBsZXZlbC1ieS1sZXZlbCwgd2l0aCBhbiBhcHByb3hpbWF0ZSBtZXRob2QgZm9yIHNwbGl0IGZpbmRpbmcgdGhhdCBjYW4gaGFuZGxlIGxhcmdlIGRhdGFzZXRzIGVmZmljaWVudGx5LiAgDQrigKIgUmVndWxhcml6YXRpb24gZm9yIFRyZWVzLiBVbmxpa2Ugc29tZSBlYXJsaWVyIHRyZWUtYmFzZWQgYm9vc3RpbmcgaW1wbGVtZW50YXRpb25zLCBYR0Jvb3N0IGluY2x1ZGVzIEwxIChsYXNzbykgYW5kIEwyIChyaWRnZSkgcGVuYWx0aWVzIG9uIHRoZSBsZWFmIHdlaWdodHMuIEl0IGFsc28gcGVuYWxpemVzIHRoZSB0b3RhbCBudW1iZXIgb2YgbGVhdmVzIChUKSBpbiBlYWNoIHRyZWUgdmlhIGEgcGFyYW1ldGVyIGdhbW1hLCB0aGVyZWJ5IGNvbnRyb2xsaW5nIG92ZXJmaXR0aW5nLiDuiIBjaXRl7oiCdHVybjBmaWxlMe6IgQ0KDQozLiBUaGUgWEdCb29zdCBPYmplY3RpdmUNClRoZSB0eXBpY2FsIFhHQm9vc3Qgb2JqZWN0aXZlIGZ1bmN0aW9uIGNhbiBiZSBzdW1tYXJpemVkIGFzOg0KDQpPYmogPSDiiJEo4bWiPTEgdG8gbikgTCh54bWiLCBG4oKY4oKL4oKBKHjhtaIpICsgZuKCmCh44bWiKSkgKyDOqShm4oKYKSwgIA0KDQp3aGVyZQ0K4oCiIEwgaXMgYSBsb3NzIGZ1bmN0aW9uLCBlLmcuIG1lYW4gc3F1YXJlZCBlcnJvciwgbG9naXN0aWMgbG9zcywgZXRjLiAgDQrigKIgZuKCmCh4KSBpcyB0aGUgbmV3IHRyZWUgKG9yIGJhc2UgbGVhcm5lcikgYmVpbmcgYWRkZWQgaW4gaXRlcmF0aW9uIG0uICANCuKAoiDOqShm4oKYKSBpcyBhIHJlZ3VsYXJpemF0aW9uIHRlcm0sIHR5cGljYWxseSBvZiB0aGUgZm9ybTogIA0KICDOqShmKSA9IM6zIOKLhSBUICsgwr0gzrsg4oiRKHfisbzCsiksICANCiAg4oCTIFQgPSBudW1iZXIgb2YgbGVhdmVzIGluIHRoZSB0cmVlIGYuICANCiAg4oCTIHfisbwgPSBsZWFmIHdlaWdodHMgKHNjb3JlcykuICANCiAg4oCTIM67IGNvcnJlc3BvbmRzIHRvIEwyIHBlbmFsdHkgb24gdGhlIGxlYWYgd2VpZ2h0cy4gIA0KICDigJMgzrMgcGVuYWxpemVzIGVhY2ggbGVhZiwgZW5jb3VyYWdpbmcgc2hhbGxvd2VyIHRyZWVzLiAgDQoNClhHQm9vc3QgZml0cyB0aGUgdHJlZSBieSAoMSkgYXBwcm94aW1hdGluZyB0aGUgbG9zcyB3aXRoIGEgc2Vjb25kLW9yZGVyIFRheWxvciBleHBhbnNpb24sICgyKSBmaW5kaW5nIHRoZSBiZXN0IHNwbGl0cyBiYXNlZCBvbiB0aGF0IGFwcHJveGltYXRpb24sIGFuZCAoMykgdXBkYXRpbmcgdGhlIG1vZGVsLiDuiIBjaXRl7oiCdHVybjBmaWxlMe6IgQ0KDQo0LiBJbXBvcnRhbnQgSHlwZXJwYXJhbWV0ZXJzDQrigKIgbl9lc3RpbWF0b3JzIChucm91bmRzIGluIFIsIG4udHJlZXMgaW4gc29tZSByZWZlcmVuY2VzKTogdGhlIG1heGltdW0gbnVtYmVyIG9mIGJvb3N0aW5nIHJvdW5kcyAodHJlZXMpLiAgDQrigKIgZXRhIChsZWFybmluZ19yYXRlKTogc2hyaW5rYWdlIHBhcmFtZXRlciB0aGF0IHNjYWxlcyBlYWNoIHRyZWXigJlzIGNvbnRyaWJ1dGlvbiAoMCA8IGV0YSDiiaQgMSkuIFNtYWxsZXIgdmFsdWVzIHNsb3cgZG93biBsZWFybmluZyBidXQgY2FuIGltcHJvdmUgZ2VuZXJhbGl6YXRpb24uICANCuKAoiBtYXhfZGVwdGg6IG1heGltdW0gZGVwdGggb2YgZWFjaCB0cmVlLiBEZWVwZXIgdHJlZXMgYXJlIG1vcmUgZXhwcmVzc2l2ZSBidXQgY2FuIG92ZXJmaXQuICANCuKAoiBnYW1tYTogbWluaW11bSBsb3NzIHJlZHVjdGlvbiByZXF1aXJlZCB0byBtYWtlIGEgZnVydGhlciBwYXJ0aXRpb24gaW4gYSBsZWFmIG5vZGUgKGkuZS4sIGNvc3QgY29tcGxleGl0eSkuIEhpZ2hlciBnYW1tYSBtZWFucyBtb3JlIGNvbnNlcnZhdGl2ZSB0cmVlIGdyb3d0aC4gIA0K4oCiIHN1YnNhbXBsZTogZnJhY3Rpb24gb2YgdGhlIHRyYWluaW5nIGRhdGEgdG8gc2FtcGxlIGluIGVhY2ggYm9vc3Rpbmcgcm91bmQgKHNpbWlsYXIgdG8gYmFnZ2luZykuICANCuKAoiBjb2xzYW1wbGVfYnl0cmVlIC8gY29sc2FtcGxlX2J5bm9kZTogZnJhY3Rpb24gb2YgZmVhdHVyZXMgdG8gc2FtcGxlIGluIGVhY2ggdHJlZSAob3Igc3BsaXQpLiAgDQrigKIgzrsgKHJlZ19sYW1iZGEpOiBMMiByZWd1bGFyaXphdGlvbiBvbiBsZWFmIHdlaWdodHMgKG9uIGJ5IGRlZmF1bHQpLiAgDQrigKIgzrEgKHJlZ19hbHBoYSk6IEwxIHJlZ3VsYXJpemF0aW9uIG9uIGxlYWYgd2VpZ2h0cyAob2ZmIGJ5IGRlZmF1bHQsIHNldCB0byA+IDAgdG8gZW5hYmxlKS4gIA0KDQpUaGVzZSBoeXBlcnBhcmFtZXRlcnMgaGVscCBtYW5hZ2Ugb3ZlcmZpdHRpbmcgYW5kIGNhbiBkcmFzdGljYWxseSBhZmZlY3QgWEdCb29zdOKAmXMgcGVyZm9ybWFuY2UuIFR5cGljYWxseSwgYSBtZXRob2RpY2FsIGFwcHJvYWNoIChncmlkIHNlYXJjaCwgcmFuZG9tIHNlYXJjaCwgb3IgQmF5ZXNpYW4gb3B0aW1pemF0aW9uKSBpcyBuZWVkZWQgdG8gZmluZCBvcHRpbWFsIHZhbHVlcy4g7oiAY2l0Ze6IgnR1cm4wZmlsZTHuiIENCg0KNS4gUHNldWRvY29kZSBmb3IgWEdCb29zdA0KDQpJbml0aWFsaXphdGlvbjoNCuKAoiBG4oKAKHgpID0gY29uc3RhbnQgKGUuZy4sIHRoZSBhdmVyYWdlIG9mIHkgaWYgaXTigJlzIHJlZ3Jlc3Npb24pLg0KDQpGb3IgbSA9IDEgdG8gTToNCiAxKSBGb3IgZWFjaCBvYnNlcnZhdGlvbiBpLCBjb21wdXRlOg0KICAgIGfhtaIgPSDiiIIv4oiCRih44bWiKSBMKHnhtaIsIEbigpjigovigoEoeOG1oikpLA0KICAgIGjhtaIgPSDiiILCsi/iiIJGKHjhtaIpwrIgTCh54bWiLCBG4oKY4oKL4oKBKHjhtaIpKS4gICMgc2Vjb25kIGRlcml2YXRpdmUNCiAyKSBGaXQgYSByZWdyZXNzaW9uIHRyZWUgdG8gdGhlIHBvaW50cyB7KGfhtaIsIGjhtaIpfSwgd2l0aCBzcGVjaWFsaXplZCBzcGxpdHRpbmcgY3JpdGVyaWEgdGhhdCBhY2NvdW50cyBmb3IgZ+G1oiBhbmQgaOG1oi4NCiAzKSBGb3IgZWFjaCBsZWFmIGosIGNvbXB1dGUgdGhlIG9wdGltYWwgd2VpZ2h0IHfisbwgdGhhdCBtaW5pbWl6ZXMgdGhlIGFwcHJveGltYXRlIGxvc3MgcGx1cyByZWd1bGFyaXphdGlvbi4NCiA0KSBVcGRhdGUgRuKCmCh4KSA9IEbigpjigovigoEoeCkgKyDOtyDii4UgZuKCmCh4KS4gIA0KDQpSZXR1cm4gRuKCmCh4KS4gIA0KDQo2LiBFeGFtcGxlIENvZGUgU25pcHBldHMgaW4gUHl0aG9uLCBSLCBhbmQgU0FTDQoNCuKAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAkw0KQSkgUHl0aG9uIEV4YW1wbGUNCg0KaW1wb3J0IHhnYm9vc3QgYXMgeGdiDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCB0cmFpbl90ZXN0X3NwbGl0DQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgbWVhbl9zcXVhcmVkX2Vycm9yDQoNCiMgU3VwcG9zZSBYLCB5IGFyZSB5b3VyIGRhdGEgYW5kIHRhcmdldHMgKE51bVB5IGFycmF5cyBvciBQYW5kYXMgRGF0YUZyYW1lcykNClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4yKQ0KDQojIENvbnZlcnQgaW50byBETWF0cml4LCB3aGljaCBpcyBhIHNwZWNpYWxpemVkIFhHQm9vc3QgZGF0YSBzdHJ1Y3R1cmUNCmR0cmFpbiA9IHhnYi5ETWF0cml4KGRhdGE9WF90cmFpbiwgbGFiZWw9eV90cmFpbikNCmR0ZXN0ID0geGdiLkRNYXRyaXgoZGF0YT1YX3Rlc3QsIGxhYmVsPXlfdGVzdCkNCg0KIyBTZXQgcGFyYW1ldGVyczogZm9yIHJlZ3Jlc3Npb24NCnBhcmFtcyA9IHsNCiAgICAnb2JqZWN0aXZlJzogJ3JlZzpzcXVhcmVkZXJyb3InLA0KICAgICdldGEnOiAwLjEsDQogICAgJ21heF9kZXB0aCc6IDMsDQogICAgJ3N1YnNhbXBsZSc6IDAuOCwNCiAgICAnY29sc2FtcGxlX2J5dHJlZSc6IDAuOCwNCiAgICAnbGFtYmRhJzogMS4wLCAgIyBMMiByZWcNCiAgICAnYWxwaGEnOiAwLjAgICAgIyBMMSByZWcNCn0NCg0KbnVtX3JvdW5kcyA9IDEwMCAgIyBudW1iZXIgb2YgYm9vc3Rpbmcgcm91bmRzDQp4Z2JfbW9kZWwgPSB4Z2IudHJhaW4ocGFyYW1zLCBkdHJhaW4sIG51bV9ib29zdF9yb3VuZD1udW1fcm91bmRzKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCnByZWRzID0geGdiX21vZGVsLnByZWRpY3QoZHRlc3QpDQptc2UgPSBtZWFuX3NxdWFyZWRfZXJyb3IoeV90ZXN0LCBwcmVkcykNCnByaW50KCJYR0Jvb3N0IE1TRTogIiwgbXNlKQ0KDQrigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJMNCkIpIFIgRXhhbXBsZSAoeGdib29zdCBsaWJyYXJ5KQ0KDQojIGluc3RhbGwucGFja2FnZXMoInhnYm9vc3QiKQ0KbGlicmFyeSh4Z2Jvb3N0KQ0KDQojIFN1cHBvc2UgZGYgaXMgb3VyIGRhdGEgZnJhbWUsIHdpdGggbnVtZXJpYyBjb2x1bW5zIGZvciBYIGFuZCBhIG51bWVyaWMgeQ0KIyBNYWtlIG1hdHJpY2VzDQpYIDwtIGFzLm1hdHJpeChkZlssIC13aGljaChuYW1lcyhkZikgPT0gInkiKV0pDQp5IDwtIGRmJHkNCg0KIyBUcmFpbi90ZXN0IHNwbGl0DQpzZXQuc2VlZCgxMjMpDQp0cmFpbl9pZHggPC0gc2FtcGxlKG5yb3coZGYpLCBzaXplID0gMC44ICogbnJvdyhkZikpDQpYX3RyYWluIDwtIFhbdHJhaW5faWR4LCBdDQp5X3RyYWluIDwtIHlbdHJhaW5faWR4XQ0KWF90ZXN0ICA8LSBYWy10cmFpbl9pZHgsIF0NCnlfdGVzdCAgPC0geVstdHJhaW5faWR4XQ0KDQojIENyZWF0ZSB4Z2IuRE1hdHJpeA0KZHRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3RyYWluLCBsYWJlbCA9IHlfdHJhaW4pDQpkdGVzdCAgPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdGVzdCwgIGxhYmVsID0geV90ZXN0KQ0KDQojIFNldCBwYXJhbWV0ZXJzDQpwYXJhbXMgPC0gbGlzdCgNCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLA0KICBldGEgPSAwLjEsDQogIG1heF9kZXB0aCA9IDMsDQogIHN1YnNhbXBsZSA9IDAuOCwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IDAuOCwNCiAgbGFtYmRhID0gMSwNCiAgYWxwaGEgPSAwDQopDQoNCiMgVHJhaW4NCnhnYl9tb2RlbCA8LSB4Z2IudHJhaW4oDQogIHBhcmFtcyA9IHBhcmFtcywgDQogIGRhdGEgPSBkdHJhaW4sIA0KICBucm91bmRzID0gMTAwLA0KICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gZHRyYWluLCB0ZXN0ID0gZHRlc3QpLA0KICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAxMA0KKQ0KDQojIFByZWRpY3QNCnByZWRzIDwtIHByZWRpY3QoeGdiX21vZGVsLCBYX3Rlc3QpDQptc2UgPC0gbWVhbigoeV90ZXN0IC0gcHJlZHMpXjIpDQpwcmludChtc2UpDQoNCuKAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAkw0KQykgU0FTIEV4YW1wbGUNCkluIFNBUywgeW91IGNhbiByZXBsaWNhdGUgWEdCb29zdC1saWtlIGZ1bmN0aW9uYWxpdHkgaW4gYSBmZXcgd2F5cy4gSWYgeW91IGhhdmUgU0FTIFZpeWEsIHlvdSBjYW4gbGV2ZXJhZ2UgUFJPQyBYR0JPT1NULiBPdGhlcndpc2UsIHlvdSBjYW4gYXBwcm94aW1hdGUgaXQgd2l0aCBQUk9DIEdSQURCT09TVCBvciBtYWNyb3MgZm9yIGdyYWRpZW50IGJvb3N0aW5nLiBIZXJlIGlzIGFuIGV4YW1wbGUgd2l0aCBQUk9DIFhHQk9PU1QgaW4gU0FTIFZpeWE6DQoNCi8qIEluIFNBUyBWaXlhOiAqLw0KcHJvYyBjYXM7DQogICBzZXNzaW9uIG15c2Vzc2lvbjsNCiAgIGxvYWRhY3Rpb25zZXQgImRlY2lzaW9uVHJlZSI7DQogICAvKiBBc3N1bWluZyB3ZSBoYXZlIHRhYmxlICdteXRhYmxlJyB3aXRoICd5JyBhcyB0YXJnZXQsDQogICAgICBhbmQgeDEsIHgyLCAuLi4sIHhwIGFzIHByZWRpY3RvcnMuICovDQogICBhY3Rpb24geGdib29zdC50cmFpbiAvDQogICAgIHRhYmxlPXtuYW1lPSJteXRhYmxlIn0NCiAgICAgdGFyZ2V0PSJ5Ig0KICAgICBpbnB1dHM9eyJ4MSIsIngyIiwuLi4sICJ4cCJ9DQogICAgIG5vbWluYWxzPXt9ICAgICAgICAgICAgICAgICAgLyogc3BlY2lmeSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaWYgbmVlZGVkICovDQogICAgIG5UcmVlPTEwMA0KICAgICBvYmplY3RpdmU9InJlZzpzcXVhcmVkZXJyb3IiDQogICAgIG1heERlcHRoPTMNCiAgICAgZXRhPTAuMQ0KICAgICBzdWJzYW1wbGU9MC44DQogICAgIGNvbFNhbXBsZUJ5VHJlZT0wLjgNCiAgICAgcmVnTGFtYmRhPTENCiAgICAgcmVnQWxwaGE9MA0KICAgICBzZWVkPTEyMzQ1DQogICAgIHNhdmVzdGF0ZT17bmFtZT0ibXlYR0Jtb2RlbCJ9Ow0KcnVuOw0KDQovKiBTY29yZSBuZXcgZGF0YSAqLw0KcHJvYyBjYXM7DQogICBhY3Rpb24geGdib29zdC5zY29yZSAvDQogICAgIG1vZGVsU3RhdGU9e25hbWU9Im15WEdCbW9kZWwifQ0KICAgICB0YWJsZT17bmFtZT0ibXlOZXdEYXRhIn0NCiAgICAgY2FzT3V0PXtuYW1lPSJteVNjb3JlZERhdGEiLCByZXBsYWNlPVRydWV9Ow0KcnVuOw0KDQo3LiBQcmFjdGljYWwgVGlwcyBhbmQgU3VtbWFyeQ0K4oCiIFJlZ3VsYXJpemF0aW9uIFR1bmluZy4gRG9u4oCZdCBuZWdsZWN0IGdhbW1hLCDOuyAocmVnX2xhbWJkYSksIGFuZCDOsSAocmVnX2FscGhhKS4gVGhlc2UgY2FuIGJlIGtleSB0byBjb250cm9sbGluZyBvdmVyZml0dGluZy4gIA0K4oCiIExlYXJuaW5nIFJhdGUuIFR5cGljYWxseSBwaWNrIGEgc21hbGxlciBldGEgKGUuZy4gMC4wMeKAkzAuMikgYW5kIGNvbWJpbmUgd2l0aCBtb3JlIGJvb3N0aW5nIHJvdW5kcy4gIA0K4oCiIFN1YnNhbXBsaW5nLiBVc2luZyBzdWJzYW1wbGUgPCAxLjAgb3IgY29sc2FtcGxlX2J5dHJlZSA8IDEuMCBvZnRlbiBoZWxwcyByZWR1Y2UgdmFyaWFuY2UgYW5kIHNwZWVkIHVwIHRyYWluaW5nLiAgDQrigKIgRWFybHkgU3RvcHBpbmcuIFVzaW5nIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyBjYW4gc2F2ZSB0aW1lIGJ5IGhhbHRpbmcgdHJhaW5pbmcgd2hlbiB0aGUgbW9kZWwgc3RvcHMgaW1wcm92aW5nIG9uIGEgdmFsaWRhdGlvbiBzZXQuICANCuKAoiBDdXN0b20gTG9zc2VzLiBPbmUgaHVnZSBhZHZhbnRhZ2Ugb2YgWEdCb29zdCBpcyB0aGF0IHlvdSBjYW4gZGVmaW5lIGN1c3RvbSBsb3NzIGZ1bmN0aW9ucywgYXMgbG9uZyBhcyB0aGV54oCZcmUgZGlmZmVyZW50aWFibGUgYW5kIHlvdSBjYW4gcHJvdmlkZSBncmFkaWVudCBhbmQgaGVzc2lhbi4gIA0KDQpJbiBzaG9ydCwgWEdCb29zdCBpcyBhIGhpZ2hseSBvcHRpbWl6ZWQgZnJhbWV3b3JrIGZvciBncmFkaWVudCBib29zdGluZyB3aXRoIGJ1aWx0LWluIHJlZ3VsYXJpemF0aW9uIGFuZCBzb3BoaXN0aWNhdGVkIHRyZWUtYnVpbGRpbmcuIEl04oCZcyB3aWRlbHkgdXNlZCBpbiBwcmFjdGljZSBmb3Igc3RydWN0dXJlZCBkYXRhIHByb2JsZW1zIGFuZCBjYW4gb2Z0ZW4gb3V0cGVyZm9ybSBzaW1wbGVyIG1ldGhvZHMsIHByb3ZpZGVkIHRoYXQgeW91IHR1bmUgdGhlIHBhcmFtZXRlcnMgY2FyZWZ1bGx5LiAgDQoNCg0KQmVsb3cgaXMgbXkgdGhpcmQgc2VjdGlvbiBvZiB0aGUgc3R1ZHkgZ3VpZGUsIGZvY3VzaW5nIG9uIEh5cGVycGFyYW1ldGVycy4gSSBkcmV3IGZyb20gVGhlIEVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nIChFU0wpIGFuZCB0aGUgTW9kdWxlIDggQXN5bmNocm9ub3VzIHRyYW5zY3JpcHRzIHlvdSBwcm92aWRlZC4gSSB3aWxsIGluY2x1ZGUgcmVmZXJlbmNlcyB0byBFU0wgKFRoZSBFbGVtZW50cyBvZiBTdGF0aXN0aWNhbCBMZWFybmluZykg7oiAY2l0Ze6IgnR1cm4wZmlsZTDuiIEgYW5kIHRoZSB2aWRlbyB0cmFuc2NyaXB0IChNb2R1bGUgOCBBc3luY2gpIO6IgGNpdGXuiIJ0dXJuMGZpbGUx7oiBLiBJIHdpbGwgYWxzbyBwcm92aWRlIHNvbWUgYnJpZWYgY29kZSBzbmlwcGV0cyBpbiBQeXRob24sIFIsIGFuZCBTQVMgZm9yIGlsbHVzdHJhdGlvbi4NCg0KU0VDVElPTiAzOiBIWVBFUlBBUkFNRVRFUlMNCg0KMS4gV2hhdCBBcmUgSHlwZXJwYXJhbWV0ZXJzPw0KSSBjb25zaWRlciBoeXBlcnBhcmFtZXRlcnMgdG8gYmUgdGhlIOKAnHNldHRpbmdz4oCdIG9yIOKAnGtub2Jz4oCdIHRoYXQgZ3VpZGUgdGhlIGxlYXJuaW5nIHByb2Nlc3Mgb2YgYSBtb2RlbC4gRm9yIGEgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uLCB0aGUgcGFyYW1ldGVycyBhcmUgdGhlIHNsb3BlcyBhbmQgaW50ZXJjZXB0IChsZWFybmVkIGZyb20gZGF0YSksIGJ1dCB3ZSB1c3VhbGx5IGhhdmUgbm8gaHlwZXJwYXJhbWV0ZXJzIHRvIHR1bmUuIEluIGNvbnRyYXN0LCBmb3IgdHJlZS1iYXNlZCBtZXRob2RzLCBib29zdGluZywgYW5kIGFkdmFuY2VkIG1vZGVscyBsaWtlIG5ldXJhbCBuZXR3b3Jrcywgd2UgaGF2ZSBoeXBlcnBhcmFtZXRlcnMgdGhhdCBjb250cm9sIGNvbXBsZXhpdHksIGxlYXJuaW5nIHJhdGUsIHJlZ3VsYXJpemF0aW9uLCBhbmQgc28gb24uIFRoZXNlIGh5cGVycGFyYW1ldGVycyBhcmUgbm90IGxlYXJuZWQgZGlyZWN0bHkgZnJvbSB0aGUgdHJhaW5pbmcgZGF0YSBpbiBhIHNpbXBsZSBjbG9zZWQtZm9ybSBtYW5uZXI7IGluc3RlYWQsIHdlIHBpY2sgdGhlbSAoZm9yIGluc3RhbmNlLCBieSBjcm9zcy12YWxpZGF0aW9uIG9yIG90aGVyIHNlYXJjaCBtZXRob2RzKS4gIA0KUmVsZXZhbnQgcmVmZXJlbmNlczogRVNMLCBDaGFwdGVyIDcgb24gbW9kZWwgYXNzZXNzbWVudCBhbmQgc2VsZWN0aW9uOyBhbHNvIGNoYXB0ZXJzIGRlYWxpbmcgd2l0aCBlYWNoIGFsZ29yaXRobeKAmXMgc3BlY2lmaWMgdHVuYWJsZSBrbm9icyAoZS5nLiwgQ2hhcHRlciAxMCBvbiBib29zdGluZykuIO6IgGNpdGXuiIJ0dXJuMGZpbGUw7oiBDQoNCjIuIENvbW1vbiBIeXBlcnBhcmFtZXRlcnMgZm9yIFRyZWUtQmFzZWQgTW9kZWxzDQrigKIgbWF4X2RlcHRoOiB0aGUgbWF4aW11bSBkZXB0aCBvZiB0aGUgdHJlZSwgY29udHJvbGxpbmcgaG93IG1hbnkgc3BsaXRzIGNhbiBvY2N1ciBmcm9tIHJvb3QgdG8gbGVhZi4gIA0K4oCiIG1pbl9zYW1wbGVzX3NwbGl0IG9yIG1pbl9jaGlsZF93ZWlnaHQgKGluIFhHQm9vc3QpOiB0aGUgbWluaW11bSBudW1iZXIgb2Ygc2FtcGxlcyBuZWVkZWQgaW4gYSBsZWFmIG5vZGUgb3IgY2hpbGQgbm9kZSwgd2hpY2ggaGVscHMgcHJldmVudCBvdmVybHkgc21hbGwgcGFydGl0aW9ucyBhbmQgdGh1cyBvdmVyZml0dGluZy4gIA0K4oCiIGdhbW1hIChpbiBYR0Jvb3N0KTogYWRkaXRpb25hbCBwZW5hbHR5IG9uIGxlYWYgc3BsaXRzLCByZXF1aXJpbmcgYSBtaW5pbXVtIGxvc3MgcmVkdWN0aW9uIGJlZm9yZSBhIHNwbGl0IGNhbiBiZSBtYWRlLiAgDQrigKIgc3ViX3NhbXBsZSBvciBiYWdnaW5nX2ZyYWN0aW9uOiBmcmFjdGlvbiBvZiB0aGUgdHJhaW5pbmcgZGF0YSB0byByYW5kb21seSBzYW1wbGUgZm9yIGVhY2ggcm91bmQgKGFkZHMgcmFuZG9tbmVzcywgcmVkdWNlcyB2YXJpYW5jZSkuICANCuKAoiBjb2xfc2FtcGxlX2J5X3RyZWU6IGZyYWN0aW9uIG9mIGZlYXR1cmVzIHVzZWQgaW4gZWFjaCB0cmVlLiAgDQoNCjMuIExlYXJuaW5nIFJhdGUgdnMuIE51bWJlciBvZiBFc3RpbWF0b3JzDQrigKIgbGVhcm5pbmdfcmF0ZSAoZXRhKTogaG93IGZhc3Qgb3Igc2xvdyB3ZSBpbmNvcnBvcmF0ZSBhIG5ldyBsZWFybmVy4oCZcyBjb250cmlidXRpb24gaW4gZWFjaCBib29zdGluZyBpdGVyYXRpb24uIExvd2VyIGxlYXJuaW5nIHJhdGVzIHR5cGljYWxseSByZXF1aXJlIG1vcmUgaXRlcmF0aW9ucyAobl9lc3RpbWF0b3JzKSB0byBhY2hpZXZlIGdvb2QgYWNjdXJhY3ksIGJ1dCBvZnRlbiBnZW5lcmFsaXplIGJldHRlci4gIA0K4oCiIG5fZXN0aW1hdG9ycyAoTSk6IHRoZSBudW1iZXIgb2YgYm9vc3Rpbmcgcm91bmRzICh0cmVlcyBpbiBhIGJvb3N0ZWQgZW5zZW1ibGUpLiBUb28gZmV3IGNhbiB1bmRlcmZpdDsgdG9vIG1hbnkgbWlnaHQgb3ZlcmZpdCBpZiB3ZSBkb27igJl0IG1vbml0b3IgZm9yIGVhcmx5IHN0b3BwaW5nLiAgDQpUaGVzZSB0d28gaHlwZXJwYXJhbWV0ZXJzIGFyZSB0eXBpY2FsbHkgdHVuZWQgdG9nZXRoZXI6IHNtYWxsIGxlYXJuaW5nX3JhdGUgd2l0aCBhIGxhcmdlIG5fZXN0aW1hdG9ycyBjYW4geWllbGQgYSBoaWdoLXBlcmZvcm1pbmcgbW9kZWwgYXQgYSBjb3N0IG9mIG1vcmUgY29tcHV0YXRpb24uIO6IgGNpdGXuiIJ0dXJuMGZpbGUx7oiBDQoNCjQuIFJlZ3VsYXJpemF0aW9uIEh5cGVycGFyYW1ldGVycw0K4oCiIEwxIHJlZ3VsYXJpemF0aW9uIM6xIChhbHBoYSkgZW5jb3VyYWdlcyBzcGFyc2l0eSBpbiB0cmVlIHdlaWdodHMgKG9yIG90aGVyIG1vZGVsIHBhcmFtZXRlcnMpLiAgDQrigKIgTDIgcmVndWxhcml6YXRpb24gzrsgKGxhbWJkYSkgc2hyaW5rcyB0aGUgd2VpZ2h0cywgcGVuYWxpemluZyBsYXJnZSB2YWx1ZXMuICANCuKAoiBnYW1tYSAoZm9yIFhHQm9vc3QsIExpZ2h0R0JNLCBldGMuKSBhbHNvIHBsYXlzIGEgcm9sZSBpbiByZWd1bGFyaXphdGlvbiBieSBhZGRpbmcgYSBjb3N0IGZvciBlYWNoIGxlYWYgaW4gYSB0cmVlLiAgDQrigKIgcGVuYWx0eSBpbiBsb2dpc3RpYyByZWdyZXNzaW9uOiBjYW4gYmUg4oCcbDHigJ0sIOKAnGwy4oCdLCBvciDigJxlbGFzdGljIG5ldOKAnSwgY29udHJvbGxpbmcgaG93IGNvZWZmaWNpZW50cyBhcmUgc2hydW5rIG9yIGZvcmNlZCB0byB6ZXJvLiAgDQoNCjUuIFNlYXJjaGluZyBmb3IgR29vZCBIeXBlcnBhcmFtZXRlcnMNCkkgY29tbW9ubHkgdXNlIHN5c3RlbWF0aWMgc2VhcmNoIHByb2NlZHVyZXMgc3VjaCBhcyBncmlkIHNlYXJjaCBvciByYW5kb20gc2VhcmNoLCBwb3NzaWJseSBhdWdtZW50ZWQgYnkgY3Jvc3MtdmFsaWRhdGlvbi4gQXV0b21hdGVkIGh5cGVycGFyYW1ldGVyIG9wdGltaXphdGlvbiBtZXRob2RzIChCYXllc2lhbiBvcHRpbWl6YXRpb24sIGdlbmV0aWMgYWxnb3JpdGhtcywgSHlwZXJvcHQsIE9wdHVuYSwgZXRjLikgY2FuIGFsc28gYmUgdXNlZC4gVGhlIHByb2NlZHVyZSB0eXBpY2FsbHkgaW52b2x2ZXM6ICANCjEpIENob29zZSBhIHJhbmdlIG9yIGRpc3RyaWJ1dGlvbiBmb3IgZWFjaCBoeXBlcnBhcmFtZXRlci4gIA0KMikgU2FtcGxlIGRpZmZlcmVudCBjb21iaW5hdGlvbnMgb2YgdGhlc2UgaHlwZXJwYXJhbWV0ZXJzLiAgDQozKSBGaXQgdGhlIG1vZGVsIG9uIHRyYWluaW5nIGZvbGRzLCBldmFsdWF0ZSBvbiBhIHZhbGlkYXRpb24gZm9sZC4gIA0KNCkgUGljayB0aGUgY29tYmluYXRpb24gdGhhdCB5aWVsZHMgdGhlIGJlc3QgYXZlcmFnZSB2YWxpZGF0aW9uIHNjb3JlLiAgDQo1KSBSZWZpdCBvbiB0aGUgZnVsbCB0cmFpbmluZyBkYXRhIGlmIG5lZWRlZC4gIA0KDQo2LiBFeGFtcGxlIENvZGUgU25pcHBldCBmb3IgSHlwZXJwYXJhbWV0ZXIgVHVuaW5nDQoNCuKAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAk+KAkw0KQSkgUHl0aG9uIChVc2luZyBzY2lraXQtbGVhcm7igJlzIEdyaWRTZWFyY2hDVikNCg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgR3JpZFNlYXJjaENWDQpmcm9tIHNrbGVhcm4uZW5zZW1ibGUgaW1wb3J0IEdyYWRpZW50Qm9vc3RpbmdSZWdyZXNzb3INCg0KcGFyYW1fZ3JpZCA9IHsNCiAgICAnbl9lc3RpbWF0b3JzJzogWzUwLCAxMDBdLA0KICAgICdsZWFybmluZ19yYXRlJzogWzAuMDEsIDAuMSwgMC4yXSwNCiAgICAnbWF4X2RlcHRoJzogWzEsIDMsIDVdDQp9DQoNCmdibSA9IEdyYWRpZW50Qm9vc3RpbmdSZWdyZXNzb3IoKQ0KZ3JpZF9zZWFyY2ggPSBHcmlkU2VhcmNoQ1YoDQogICAgZXN0aW1hdG9yPWdibSwNCiAgICBwYXJhbV9ncmlkPXBhcmFtX2dyaWQsDQogICAgc2NvcmluZz0nbmVnX21lYW5fc3F1YXJlZF9lcnJvcicsDQogICAgY3Y9NSwNCiAgICBuX2pvYnM9LTENCikNCg0KZ3JpZF9zZWFyY2guZml0KFhfdHJhaW4sIHlfdHJhaW4pDQpwcmludCgiQmVzdCBQYXJhbXM6IiwgZ3JpZF9zZWFyY2guYmVzdF9wYXJhbXNfKQ0KcHJpbnQoIkJlc3QgQ1YgU2NvcmU6IiwgLWdyaWRfc2VhcmNoLmJlc3Rfc2NvcmVfKQ0KDQrigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJPigJMNCkIpIFIgKFVzaW5nIGNhcmV0IGZvciB0dW5pbmcgYSBHQk0pDQoNCiMgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQ0KbGlicmFyeShjYXJldCkNCg0KdHJhaW5fY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gNSkNCnR1bmVfZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgbi50cmVlcyA9IGMoNTAsIDEwMCksDQogIGludGVyYWN0aW9uLmRlcHRoID0gYygxLCAzLCA1KSwNCiAgc2hyaW5rYWdlID0gYygwLjAxLCAwLjEsIDAuMiksDQogIG4ubWlub2JzaW5ub2RlID0gYyg1LCAxMCkNCikNCg0Kc2V0LnNlZWQoMTIzKQ0KZ2JtX2ZpdCA8LSB0cmFpbigNCiAgeSB+IC4sIGRhdGEgPSBkZiwNCiAgbWV0aG9kID0gImdibSIsDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gdHVuZV9ncmlkLA0KICBtZXRyaWMgPSAiUk1TRSIsDQogIHZlcmJvc2UgPSBGQUxTRQ0KKQ0KDQpnYm1fZml0JGJlc3RUdW5lDQpnYm1fZml0JHJlc3VsdHMNCg0K4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCT4oCTDQpDKSBTQVMgKFBST0MgT1BUTU9ERUwgb3IgSFBDIFR1bmluZyBpbiBTQVMgVml5YSkNClNBUyBoYXMgbWFjcm9zIG9yIHByb2NlZHVyZXMgKGxpa2UgUFJPQyBIUEZPUkVTVCwgSFBHRU5TRUxFQ1QpIHRoYXQgY2FuIGRvIHNvbWUgaHlwZXJwYXJhbWV0ZXIgc2VsZWN0aW9uLCBidXQgeW91IG1heSBuZWVkIGEgbWFudWFsIG9yIG1hY3JvLWJhc2VkIGFwcHJvYWNoLg0KDQpFeGFtcGxlIHNuaXBwZXQgdXNpbmcgYSBtYWNyby1iYXNlZCBhcHByb2FjaCBmb3Igc2VhcmNoaW5nIGh5cGVycGFyYW1ldGVycyBpbiBTQVMgb2xkZXIgdmVyc2lvbnMgKGNvbmNlcHR1YWwgb3V0bGluZSk6DQoNCiVtYWNybyB0dW5lTXlGb3Jlc3QoZGF0YT0sIHRhcmdldD0sIG1heGRlcHRoPSwgbnRyZWVzPSk7DQogICBwcm9jIGhwZm9yZXN0IGRhdGE9JmRhdGE7DQogICAgICB0YXJnZXQgJnRhcmdldC47DQogICAgICBpbnB1dCB4MS14cDsNCiAgICAgIG50cmVlPSZudHJlZXMuOw0KICAgICAgbWF4ZGVwdGg9Jm1heGRlcHRoLjsNCiAgICAgIC8qIGFkZGl0aW9uYWwgaHlwZXJwYXJhbWV0ZXJzLCBldGMuICovDQogICAgICBvZHMgb3V0cHV0IEZpdFN0YXRpc3RpY3M9Rml0U3RhdHM7DQogICBydW47DQolbWVuZDsNCg0KJXR1bmVNeUZvcmVzdChkYXRhPW15ZGF0YSwgdGFyZ2V0PXksIG1heGRlcHRoPTUsIG50cmVlcz01MCk7DQovKiBnYXRoZXIgRml0U3RhdHMsIGNvbXBhcmUsIGV0Yy4gKi8NCg0KSW4gbW9kZXJuIFNBUyBWaXlhLCB5b3UgY2FuIHVzZSBBdXRvVHVuZSBpbiBhY3Rpb25zIGxpa2UgZGVjaXNpb25UcmVlLmdidHJlZVRyYWluIG9yIHhnYm9vc3QudHJhaW4sIHNwZWNpZnlpbmcgc2VhcmNoIHJhbmdlcyBmb3IgdGhlIGh5cGVycGFyYW1ldGVycy4gIA0KDQo3LiBIeXBlcnBhcmFtZXRlciBUdW5pbmcgUGl0ZmFsbHMNCuKAoiBPdmVyZml0dGluZyBvbiB2YWxpZGF0aW9uIHNldHMgaWYgcmVwZWF0ZWRseSBzZWFyY2hpbmcgYSBsYXJnZSBoeXBlcnBhcmFtZXRlciBzcGFjZS4gIA0K4oCiIFNldHRpbmcgcmFuZ2VzIG9yIGRpc3RyaWJ1dGlvbnMgdG9vIG5hcnJvdyBjYW4gbWlzcyBiZXR0ZXIgc29sdXRpb25zLiAgDQrigKIgQ29tcHV0YXRpb24gdGltZSBjYW4gZXhwbG9kZSB3aXRoIGxhcmdlIHBhcmFtZXRlciBncmlkcy4gIA0K4oCiIFNvbWV0aW1lcyBpdOKAmXMgZWFzeSB0byBmaXggY2VydGFpbiBwYXJhbWV0ZXJzIHRvIHdlbGwta25vd24gZGVmYXVsdHMgKGUuZy4sIHNtYWxsIGxlYXJuaW5nIHJhdGUpIGFuZCBvbmx5IHR1bmUgdGhlIGNyaXRpY2FsIG9uZXMgKGxpa2Ugbl9lc3RpbWF0b3JzLCBtYXhfZGVwdGgpIHRvIHJlZHVjZSBjb21wbGV4aXR5LiAgDQoNCjguIFN1bW1pbmcgVXANCkh5cGVycGFyYW1ldGVycyBhcmUgY3J1Y2lhbCBpbiBjb250cm9sbGluZyB0aGUgYmVoYXZpb3IgYW5kIHBlcmZvcm1hbmNlIG9mIGFkdmFuY2VkIG1hY2hpbmUtbGVhcm5pbmcgbW9kZWxzLCBlc3BlY2lhbGx5IHRyZWUtYmFzZWQgbWV0aG9kcyBhbmQgYm9vc3RlZCBlbnNlbWJsZXMuIFRoZXkgZ292ZXJuIG1vZGVsIGNvbXBsZXhpdHksIHJlZ3VsYXJpemF0aW9uIHN0cmVuZ3RoLCBhbmQgaG93IGxlYXJuaW5nIHByb2dyZXNzZXMuIFRoZSByaWdodCBjb21iaW5hdGlvbiBvZiBoeXBlcnBhcmFtZXRlcnMgY2FuIGRyYW1hdGljYWxseSBpbXByb3ZlIHByZWRpY3RpdmUgYWNjdXJhY3kgd2hpbGUgcHJldmVudGluZyBvdmVyZml0dGluZy4gIA0KDQpCZWxvdyBpcyBteSBmb3VydGggc2VjdGlvbiBvZiB0aGUgc3R1ZHkgZ3VpZGUsIGZvY3VzaW5nIG9uIHR3byBYR0Jvb3N0IERlbW9zLiBJIGRyZXcgZnJvbSB0aGUgRWxlbWVudHMgb2YgU3RhdGlzdGljYWwgTGVhcm5pbmcgKEVTTCkgYW5kIHRoZSBNb2R1bGUgOCBBc3luY2hyb25vdXMgdHJhbnNjcmlwdHMgeW91IHByb3ZpZGVkLiBJIHdpbGwgaW5jbHVkZSByZWZlcmVuY2VzIHRvIEVTTCAoVGhlIEVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nKSDuiIBjaXRl7oiCdHVybjBmaWxlMO6IgSBhbmQgdGhlIHZpZGVvIHRyYW5zY3JpcHQgY29udGVudCAoTW9kdWxlIDggQXN5bmNoKSDuiIBjaXRl7oiCdHVybjBmaWxlMe6IgS4gSSB3aWxsIGFsc28gcHJvdmlkZSBzYW1wbGUgY29kZSBzbyB5b3UgY2FuIHJlcGxpY2F0ZSB0aGUgZGVtb25zdHJhdGlvbnMuDQoNClNFQ1RJT04gNDogWEdCT09TVCBERU1PIDEgQU5EIDINCg0KREVNTyAxOiBIQU5EV1JJVFRFTiBESUdJVFMgQ0xBU1NJRklDQVRJT04gKFNDSUtJVC1MRUFSTiArIFhHQk9PU1QpDQoNCjEuIERhdGEgT3ZlcnZpZXcgIA0KSSBzZWUgdGhlIGRpZ2l0cyBkYXRhc2V0IGZyb20gc2Npa2l0LWxlYXJuIGFzIGFuIGV4YW1wbGUgZm9yIG11bHRpY2xhc3MgY2xhc3NpZmljYXRpb24uIEl0IGNvbnRhaW5zIDEsNzk3IHNhbXBsZXMgb2YgOMOXOCBwaXhlbCBpbWFnZXMgcmVwcmVzZW50aW5nIHRoZSBkaWdpdHMgMCB0aHJvdWdoIDkuIEVhY2ggcGl4ZWwgaXMgYSBmZWF0dXJlLCBzbyB3ZSBoYXZlIDY0IGZlYXR1cmVzLiBXZSB3YW50IHRvIGNsYXNzaWZ5IHdoaWNoIGRpZ2l0IGVhY2ggaW1hZ2UgcmVwcmVzZW50cy4gIA0KDQoyLiBTdGVwcyB0byBSZXBsaWNhdGUNCg0KKEEpIExvYWQgTGlicmFyaWVzIGFuZCBEYXRhICANCuKAoiBVc2Ugc2Npa2l0LWxlYXJu4oCZcyBidWlsdC1pbiBkaWdpdHMgZGF0YXNldDogIA0KICBmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IGxvYWRfZGlnaXRzICANCiAgZGlnaXRzID0gbG9hZF9kaWdpdHMoKSAgDQoNCuKAoiBYIHdpbGwgYmUgZGlnaXRzLmRhdGEsIGEgKDE3OTcgw5cgNjQpIGFycmF5LCBhbmQgeSB3aWxsIGJlIGRpZ2l0cy50YXJnZXQgKGRpZ2l0cyAw4oCTOSkuDQoNCihCKSBTcGxpdCBpbnRvIFRyYWluaW5nIGFuZCBUZXN0ICANCuKAoiBXZSBjYW4gZG8gYSBzaW1wbGUgNzDigJMzMCBzcGxpdCBvciB1c2UgY3Jvc3MtdmFsaWRhdGlvbi4gIA0KDQooQykgQ29udmVydCBEYXRhIGludG8gWEdCb29zdOKAmXMgRE1hdHJpeCBGb3JtYXQgIA0K4oCiIHhnYi5ETWF0cml4IGlzIGEgc3BlY2lhbGl6ZWQgZGF0YSBzdHJ1Y3R1cmUgZm9yIFhHQm9vc3QsIGJ1dCB3ZSBjYW4gdHJhaW4gZGlyZWN0bHkgd2l0aCBzY2lraXQtbGVhcm4gQVBJIHRvby4gIA0KDQooRCkgVHJhaW4gYW4gWEdCb29zdCBDbGFzc2lmaWVyICANCuKAoiBGb3IgYSBtdWx0aWNsYXNzIHRhc2ssIHNldCB0aGUgb2JqZWN0aXZlIHRvICJtdWx0aTpzb2Z0cHJvYiIgb3IgIm11bHRpOnNvZnRtYXgiIGFuZCBzcGVjaWZ5IG51bV9jbGFzcz0xMC4gIA0KDQooRSkgRXZhbHVhdGUgQWNjdXJhY3kgIA0K4oCiIFVzZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBzZXQgYW5kIG1lYXN1cmUgY2xhc3NpZmljYXRpb24gYWNjdXJhY3kgb3IgY29uZnVzaW9uIG1hdHJpeC4gIA0KDQozLiBEZW1vIDE6IENvZGUgRXhhbXBsZSAoUHl0aG9uKQ0KDQppbXBvcnQgeGdib29zdCBhcyB4Z2INCmZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgbG9hZF9kaWdpdHMNCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQNCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBhY2N1cmFjeV9zY29yZQ0KDQojIExvYWQgZGF0YQ0KZGlnaXRzID0gbG9hZF9kaWdpdHMoKQ0KWCA9IGRpZ2l0cy5kYXRhICAgICAgICMgc2hhcGUgKDE3OTcsIDY0KQ0KeSA9IGRpZ2l0cy50YXJnZXQgICAgICMgbGFiZWxzIDAgdGhyb3VnaCA5DQoNCiMgVHJhaW4vdGVzdCBzcGxpdA0KWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KFgsIHksIHRlc3Rfc2l6ZT0wLjMsIHJhbmRvbV9zdGF0ZT00MikNCg0KIyBYR0Jvb3N0IENsYXNzaWZpZXIgdXNpbmcgc2Npa2l0LWxlYXJuIEFQSQ0KeGdiX2NsZiA9IHhnYi5YR0JDbGFzc2lmaWVyKA0KICAgIG9iamVjdGl2ZT0nbXVsdGk6c29mdHByb2InLA0KICAgIG51bV9jbGFzcz0xMCwNCiAgICBtYXhfZGVwdGg9MywNCiAgICBsZWFybmluZ19yYXRlPTAuMSwNCiAgICBuX2VzdGltYXRvcnM9MTAwLA0KICAgIHN1YnNhbXBsZT0wLjgsDQogICAgY29sc2FtcGxlX2J5dHJlZT0wLjgsDQogICAgcmFuZG9tX3N0YXRlPTQyDQopDQoNCnhnYl9jbGYuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCiMgUHJlZGljdA0KeV9wcmVkID0geGdiX2NsZi5wcmVkaWN0KFhfdGVzdCkNCmFjY3VyYWN5ID0gYWNjdXJhY3lfc2NvcmUoeV90ZXN0LCB5X3ByZWQpDQpwcmludCgiWEdCb29zdCBjbGFzc2lmaWNhdGlvbiBhY2N1cmFjeToiLCBhY2N1cmFjeSkNCg0K4oCiIFlvdSB3aWxsIHR5cGljYWxseSBzZWUgYWNjdXJhY3kgaW4gdGhlIDAuOTXigJMwLjk4IHJhbmdlIGRlcGVuZGluZyBvbiBwYXJhbWV0ZXIgc2V0dGluZ3MuICANCg0KNC4gTm90ZXMNCuKAoiBCZWNhdXNlIHRoZSBpbWFnZXMgYXJlIHNtYWxsICg4w5c4KSwgYSBzaW1wbGUgYXBwcm9hY2ggbGlrZSB1bnJvbGxlZCBwaXhlbHMgaXMgZW5vdWdoIHRvIGdldCBkZWNlbnQgcmVzdWx0cy4gIA0K4oCiIEZvciBtb3JlIGNvbXBsZXggaW1hZ2VzLCBtb3JlIGFkdmFuY2VkIGZlYXR1cmVzIG9yIGRlZXAgbGVhcm5pbmcgbWlnaHQgYmUgYXBwcm9wcmlhdGUsIGJ1dCBYR0Jvb3N0IGNhbiBzdGlsbCBwZXJmb3JtIHN1cnByaXNpbmdseSB3ZWxsIG9uIHN0cnVjdHVyZWQgdGFidWxhciBkYXRhLiAgDQoNCkRFTU8gMjogUkVHUkVTU0lPTiBFWEFNUExFIChDQUxJRk9STklBIEhPVVNJTkcgREFUQSkNCg0KMS4gRGF0YSBPdmVydmlldyAgDQrigKIgVGhlIENhbGlmb3JuaWEgSG91c2luZyBkYXRhc2V0IChhdmFpbGFibGUgaW4gc2Npa2l0LWxlYXJuKSBpcyBhIHJlZ3Jlc3Npb24gcHJvYmxlbSBwcmVkaWN0aW5nIG1lZGlhbiBob3VzZSBwcmljZXMgYmFzZWQgb24gZGVtb2dyYXBoaWMgYW5kIGdlb2dyYXBoaWMgZmVhdHVyZXMuICANCuKAoiBGZWF0dXJlcyBpbmNsdWRlIGF2ZXJhZ2UgaW5jb21lLCBhdmVyYWdlIGhvdXNlIGFnZSwgYXZlcmFnZSByb29tcywgZXRjLiAgDQoNCjIuIFN0ZXBzIHRvIFJlcGxpY2F0ZQ0KDQooQSkgTG9hZCBMaWJyYXJpZXMgYW5kIERhdGEgIA0K4oCiIGZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgZmV0Y2hfY2FsaWZvcm5pYV9ob3VzaW5nICANCuKAoiBjYWxfaG91c2luZyA9IGZldGNoX2NhbGlmb3JuaWFfaG91c2luZygpICANCg0K4oCiIFRoZSBpbnB1dCBmZWF0dXJlcyBhcmUgaW4gY2FsX2hvdXNpbmcuZGF0YSwgdGhlIHRhcmdldCBpcyBjYWxfaG91c2luZy50YXJnZXQuDQoNCihCKSBTcGxpdCBpbnRvIFRyYWluaW5nIGFuZCBUZXN0ICANCg0KKEMpIEJ1aWxkIFhHQm9vc3QgUmVncmVzc29yICANCuKAoiBvYmplY3RpdmU9J3JlZzpzcXVhcmVkZXJyb3InIChmb3Igc3RhbmRhcmQgcmVncmVzc2lvbikuICANCg0KKEQpIEV2YWx1YXRlIE1TRSBvciBSwrIgIA0KDQozLiBEZW1vIDI6IENvZGUgRXhhbXBsZSAoUHl0aG9uKQ0KDQppbXBvcnQgeGdib29zdCBhcyB4Z2INCmZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgZmV0Y2hfY2FsaWZvcm5pYV9ob3VzaW5nDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCB0cmFpbl90ZXN0X3NwbGl0DQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgbWVhbl9zcXVhcmVkX2Vycm9yDQoNCiMgTG9hZCBkYXRhDQpjYWxfaG91c2luZyA9IGZldGNoX2NhbGlmb3JuaWFfaG91c2luZygpDQpYID0gY2FsX2hvdXNpbmcuZGF0YQ0KeSA9IGNhbF9ob3VzaW5nLnRhcmdldA0KDQojIFNwbGl0IGludG8gdHJhaW4vdGVzdA0KWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KFgsIHksIHRlc3Rfc2l6ZT0wLjIsIHJhbmRvbV9zdGF0ZT00MikNCg0KIyBYR0Jvb3N0IFJlZ3Jlc3Nvcg0KeGdiX3JlZyA9IHhnYi5YR0JSZWdyZXNzb3IoDQogICAgb2JqZWN0aXZlPSdyZWc6c3F1YXJlZGVycm9yJywNCiAgICBtYXhfZGVwdGg9NCwNCiAgICBsZWFybmluZ19yYXRlPTAuMSwNCiAgICBuX2VzdGltYXRvcnM9MjAwLA0KICAgIHN1YnNhbXBsZT0wLjgsDQogICAgY29sc2FtcGxlX2J5dHJlZT0wLjgsDQogICAgYWxwaGE9MCwNCiAgICBsYW1iZGE9MSwNCiAgICByYW5kb21fc3RhdGU9NDINCikNCg0KeGdiX3JlZy5maXQoWF90cmFpbiwgeV90cmFpbikNCnByZWRzID0geGdiX3JlZy5wcmVkaWN0KFhfdGVzdCkNCg0KbXNlID0gbWVhbl9zcXVhcmVkX2Vycm9yKHlfdGVzdCwgcHJlZHMpDQpwcmludCgiWEdCb29zdCBNU0U6IiwgbXNlKQ0KcHJpbnQoIlhHQm9vc3QgUk1TRToiLCBtc2UqKjAuNSkNCg0KNC4gSW50ZXJwcmV0YXRpb24gYW5kIFBvdGVudGlhbCBUdW5pbmcNCuKAoiBZb3UgY2FuIHR1bmUgbWF4X2RlcHRoLCBsZWFybmluZ19yYXRlLCBhbHBoYSwgbGFtYmRhLCBldGMuIHVzaW5nIGEgZ3JpZCBzZWFyY2ggb3Igb3RoZXIgbWV0aG9kcy4gIA0K4oCiIFR5cGljYWwgTVNFIG1pZ2h0IGJlIGFyb3VuZCAwLjPigJMwLjQgb3Igc28gZGVwZW5kaW5nIG9uIGhvdyB0aGUgdGFyZ2V0IGlzIHNjYWxlZCwgYW5kIHRoZSBSTVNFIGFyb3VuZCAwLjU14oCTMC42MyBmb3IgdGhlIGRlZmF1bHQgYXBwcm9hY2guICANCg0KU0FNUExFIENPREUgSU4gUg0KDQrigKIgRm9yIHRoZSBkaWdpdHMgY2xhc3NpZmljYXRpb24gZXhhbXBsZSwgeW91IGNvdWxkIGJ1aWxkIHlvdXIgb3duIGRhdGFzZXQgb3IgdXNlIHRoZSDigJxtbmlzdOKAnSBkYXRhLiBGb3IgcmVncmVzc2lvbiBvbiB0aGUgQ2FsaWZvcm5pYSBIb3VzaW5nLCB5b3UgY2FuIHB1bGwgZnJvbSBleHRlcm5hbCBzb3VyY2VzIG9yIHByZS1kb3dubG9hZGVkIGRhdGEuIFIgY29kZSBpcyBzaW1pbGFyLCB1c2luZyB4Z2Jvb3N0Ojp4Z2IudHJhaW4gb3IgeGdib29zdDo6eGdib29zdC4gIA0KDQpsaWJyYXJ5KHhnYm9vc3QpDQojIFN1cHBvc2UgWCwgeSBhcmUgbnVtZXJpYyBtYXRyaWNlcyBvciB2ZWN0b3JzDQojIEZvciBjbGFzc2lmaWNhdGlvbiB3aXRoIG11bHRpcGxlIGNsYXNzZXMsIHNldCBvYmplY3RpdmU9Im11bHRpOnNvZnRwcm9iIiBhbmQgbnVtX2NsYXNzPTEwDQojIEZvciByZWdyZXNzaW9uLCBvYmplY3RpdmU9InJlZzpzcXVhcmVkZXJyb3IiDQoNCmR0cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhPVhfdHJhaW4sIGxhYmVsPXlfdHJhaW4pDQpkdGVzdCAgPC0geGdiLkRNYXRyaXgoZGF0YT1YX3Rlc3QsIGxhYmVsPXlfdGVzdCkNCg0KcGFyYW1zIDwtIGxpc3QoDQogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwNCiAgbWF4X2RlcHRoID0gNCwNCiAgZXRhID0gMC4xLA0KICBzdWJzYW1wbGUgPSAwLjgsDQogIGNvbHNhbXBsZV9ieXRyZWUgPSAwLjgNCikNCg0KYnN0IDwtIHhnYi50cmFpbihwYXJhbXMgPSBwYXJhbXMsIGRhdGEgPSBkdHJhaW4sIG5yb3VuZHM9MjAwKQ0KcHJlZHMgPC0gcHJlZGljdChic3QsIG5ld2RhdGE9ZHRlc3QpDQoNCiMgRXZhbHVhdGUgTVNFDQptc2UgPC0gbWVhbigoeV90ZXN0IC0gcHJlZHMpXjIpDQpjYXQoIk1TRToiLCBtc2UsICJcbiIpDQoNClNBTVBMRSBDT0RFIElOIFNBUw0KDQrigKIgRm9yIGNsYXNzaWZpY2F0aW9uLCB1c2Ugc29tZXRoaW5nIGxpa2U6DQoNCnByb2MgY2FzOw0KICBzZXNzaW9uIG15c2Vzc2lvbjsNCiAgbG9hZGFjdGlvbnNldCAiZGVjaXNpb25UcmVlIjsNCiAgYWN0aW9uIHhnYm9vc3QudHJhaW4gLw0KICAgIHRhYmxlPXtuYW1lPSJkaWdpdHNfdGFibGUifQ0KICAgIHRhcmdldD0iZGlnaXRfbGFiZWwiDQogICAgaW5wdXRzPXsicGl4ZWwxIiwicGl4ZWwyIiwuLi4sICJwaXhlbDY0In0NCiAgICBvYmplY3RpdmU9Im11bHRpOnNvZnRtYXgiDQogICAgbnVtQ2xhc3Nlcz0xMA0KICAgIG5UcmVlPTEwMA0KICAgIG1heERlcHRoPTMNCiAgICBldGE9MC4xDQogICAgc3Vic2FtcGxlPTAuOA0KICAgIGNvbFNhbXBsZUJ5VHJlZT0wLjgNCiAgICByYW5kb21TZWVkPTQyDQogICAgc2F2ZXN0YXRlPXtuYW1lPSJkaWdpdHNfeGdiX21vZGVsIn07DQpydW47DQoNCuKAoiBGb3IgcmVncmVzc2lvbiwgc2V0IG9iamVjdGl2ZT0icmVnOnNxdWFyZWRlcnJvciIgYW5kIHJlbW92ZSB0aGUgbnVtQ2xhc3NlcyBwYXJhbWV0ZXIuICANCg0KU1VNTUFSWQ0K4oCiIFhHQm9vc3QgaXMgc3RyYWlnaHRmb3J3YXJkIHRvIGFwcGx5IG9uY2UgeW914oCZcmUgZmFtaWxpYXIgd2l0aCB0aGUgQVBJLiAgDQrigKIgQ2xhc3NpZmljYXRpb24gdGFza3MgdXNlIG9iamVjdGl2ZT0ibXVsdGk6c29mdG1heCIgb3IgIm11bHRpOnNvZnRwcm9iIi4gIA0K4oCiIFJlZ3Jlc3Npb24gdGFza3MgdXNlIG9iamVjdGl2ZT0icmVnOnNxdWFyZWRlcnJvciIgb3Igc29tZXRpbWVzICJyZWc6bGluZWFyIiBpbiBvbGRlciB2ZXJzaW9ucy4gIA0K4oCiIFRoZSB0d28gZGVtb3MgaWxsdXN0cmF0ZSB0eXBpY2FsIHVzZSBjYXNlczogY2xhc3NpZmljYXRpb24gKGhhbmR3cml0dGVuIGRpZ2l0cykgYW5kIHJlZ3Jlc3Npb24gKGhvdXNpbmcgcHJpY2VzKS4gIA0KDQoNCkJlbG93IGlzIG15IGZpZnRoIHNlY3Rpb24gb2YgdGhlIHN0dWR5IGd1aWRlLCBmb2N1c2luZyBvbiBHcmlkIFNlYXJjaC4gSSBiYXNlZCBpdCBvbiBUaGUgRWxlbWVudHMgb2YgU3RhdGlzdGljYWwgTGVhcm5pbmcgKEVTTCkgYW5kIHRoZSBNb2R1bGUgOCBBc3luY2hyb25vdXMgdHJhbnNjcmlwdHMgeW91IHByb3ZpZGVkLiBJIHdpbGwgaW5jbHVkZSByZWZlcmVuY2VzIHRvIEVTTCAoVGhlIEVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nKSDuiIBjaXRl7oiCdHVybjBmaWxlMO6IgSBhbmQgY29udGVudCBmcm9tIHRoZSB0cmFuc2NyaXB0cyAoTW9kdWxlIDggQXN5bmNoKSDuiIBjaXRl7oiCdHVybjBmaWxlMe6IgS4gQXMgdXN1YWwsIEknbGwgYWxzbyBwcm92aWRlIGNvcHkvcGFzdGUgZnJpZW5kbHkgY29kZSBzbmlwcGV0cyBmb3IgbXVsdGlwbGUgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzLg0KDQpTRUNUSU9OIDU6IEdSSUQgU0VBUkNIDQoNCjEuIFdoYXQgSXMgR3JpZCBTZWFyY2g/DQpJIHZpZXcg4oCcR3JpZCBTZWFyY2jigJ0gYXMgYSBicnV0ZS1mb3JjZSBtZXRob2QgZm9yIHN5c3RlbWF0aWNhbGx5IGV4cGxvcmluZyBtdWx0aXBsZSBjb21iaW5hdGlvbnMgb2YgaHlwZXJwYXJhbWV0ZXJzLiBZb3UgZGVmaW5lIGEgZGlzY3JldGUgc2V0IG9mIHBvc3NpYmxlIHZhbHVlcyAoYSDigJxncmlk4oCdKSBmb3IgZWFjaCBoeXBlcnBhcmFtZXRlciwgdGhlbiB0cmFpbiBhbmQgZXZhbHVhdGUgdGhlIG1vZGVsIGZvciBldmVyeSBwb3NzaWJsZSBjb21iaW5hdGlvbi4gVGhlIGtleSBzdGVwcyBhcmU6DQoNCjEpIERlZmluZSByYW5nZXMgKG9yIHNldHMpIG9mIHBvc3NpYmxlIHZhbHVlcyBmb3IgZWFjaCBoeXBlcnBhcmFtZXRlcjogZm9yIGV4YW1wbGUsIG1heF9kZXB0aCDiiIggezIsMyw0fSwgbGVhcm5pbmdfcmF0ZSDiiIggezAuMDEsIDAuMX0sIGV0Yy4gIA0KMikgRm9yIGVhY2ggY29tYmluYXRpb24gb2YgaHlwZXJwYXJhbWV0ZXJzIGluIHRoZSBDYXJ0ZXNpYW4gcHJvZHVjdCBvZiB0aGVzZSBzZXRzLCB0cmFpbiB0aGUgbW9kZWwgb24gdHJhaW5pbmcgZm9sZHMgYW5kIGV2YWx1YXRlIG9uIGEgdmFsaWRhdGlvbiBmb2xkIChvciB1c2UgY3Jvc3MtdmFsaWRhdGlvbikuICANCjMpIFNlbGVjdCB0aGUgY29tYmluYXRpb24gdGhhdCB5aWVsZHMgdGhlIGJlc3QgcGVyZm9ybWFuY2UgbWV0cmljLiAgDQo0KSBPcHRpb25hbGx5LCByZWZpdCB0aGUgbW9kZWwgd2l0aCB0aGUgY2hvc2VuIGh5cGVycGFyYW1ldGVycyBvbiB0aGUgZW50aXJlIHRyYWluaW5nIHNldC4gIA0KDQoyLiBBZHZhbnRhZ2VzIGFuZCBEaXNhZHZhbnRhZ2VzDQrigKIgQWR2YW50YWdlczogIA0KICDigJMgU3RyYWlnaHRmb3J3YXJkIGFuZCBlYXN5IHRvIHVuZGVyc3RhbmQuICANCiAg4oCTIEZvciBhIHNtYWxsIHBhcmFtZXRlciBzcGFjZSwgaXQgY2FuIGJlIHF1aXRlIGVmZmVjdGl2ZS4gIA0KDQrigKIgRGlzYWR2YW50YWdlczogIA0KICDigJMgUG90ZW50aWFsbHkgZXhwZW5zaXZlIGluIGNvbXB1dGF0aW9uIHRpbWUsIHNpbmNlIHdlIGV2YWx1YXRlIGV2ZXJ5IGNvbWJpbmF0aW9uLiAgDQogIOKAkyBUaGUgdG90YWwgbnVtYmVyIG9mIGNvbWJpbmF0aW9ucyBncm93cyBleHBvbmVudGlhbGx5IHdpdGggdGhlIG51bWJlciBvZiBoeXBlcnBhcmFtZXRlcnMgb3IgdGhlIHNpemUgb2YgZWFjaCBncmlkLiAgDQoNCjMuIFBzZXVkb2NvZGUgT3V0bGluZQ0KDQpHaXZlbjoNCuKAkyBBIG1vZGVsIE0ozrgpIHdpdGggaHlwZXJwYXJhbWV0ZXJzIM64IOKIiCDOmDEgw5cgzpgyIMOXIOKApiDDlyDOmHAuICANCuKAkyBBIHBlcmZvcm1hbmNlIG1ldHJpYyBQZXJmKMK3KS4gIA0K4oCTIEEgbWV0aG9kIGZvciBtb2RlbCBldmFsdWF0aW9uLCBlLmcuIEstZm9sZCBjcm9zcy12YWxpZGF0aW9uLg0KDQpBbGdvcml0aG06DQoxKSBiZXN0X3BlcmYg4oaQIOKAk+KIniAob3Igc29tZSBtaW5pbWFsIHJlZmVyZW5jZSkNCjIpIEZvciBlYWNoIGNvbWJpbmF0aW9uICjOuOKCgSwgzrjigoIsIOKApiwgzrhwKSBpbiDOmOKCgSDDlyDOmOKCgiDDlyDigKYgw5cgzphwOg0KICAgYSkgVHJhaW4gbW9kZWwgTSjOuCkgb24gdHJhaW5pbmcgZm9sZHMuDQogICBiKSBFdmFsdWF0ZSBvbiB2YWxpZGF0aW9uIGZvbGQocykgYW5kIGNvbXB1dGUgUGVyZijOuCkuDQogICBjKSBJZiBQZXJmKM64KSA+IGJlc3RfcGVyZjoNCiAgICAgIGkpIGJlc3RfcGVyZiDihpAgUGVyZijOuCkNCiAgICAgIGlpKSBiZXN0X3BhcmFtcyDihpAgzrgNCjMpIFJldHVybiBiZXN0X3BhcmFtcywgYmVzdF9wZXJmICANCg0KNC4gUHJhY3RpY2FsIEltcGxlbWVudGF0aW9uIHdpdGggQ3Jvc3MtVmFsaWRhdGlvbg0K4oCiIFR5cGljYWxseSwgaW4gc2Npa2l0LWxlYXJuIG9yIFLigJlzIGNhcmV0LCBHcmlkU2VhcmNoQ1Ygb3IgdHJhaW4oKSBhdXRvbWF0aWNhbGx5IHVzZXMgY3Jvc3MtdmFsaWRhdGlvbiBmb3IgZWFjaCBjb21iaW5hdGlvbiBvZiBoeXBlcnBhcmFtZXRlcnMuICANCuKAoiBUaGUgZmluYWwgbW9kZWwgaXMgb2Z0ZW4gcmV0cmFpbmVkIHVzaW5nIHRoZSBzZWxlY3RlZCDigJxiZXN0X3BhcmFtcy7igJ0gIA0KDQo1LiBFeGFtcGxlOiBQeXRob24gKFVzaW5nIHNjaWtpdC1sZWFybuKAmXMgR3JpZFNlYXJjaENWKQ0KDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCBHcmlkU2VhcmNoQ1YNCmZyb20gc2tsZWFybi5lbnNlbWJsZSBpbXBvcnQgR3JhZGllbnRCb29zdGluZ0NsYXNzaWZpZXINCmZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgbG9hZF9pcmlzDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgYWNjdXJhY3lfc2NvcmUNCg0KIyBMb2FkIGRhdGENCmlyaXMgPSBsb2FkX2lyaXMoKQ0KWCwgeSA9IGlyaXMuZGF0YSwgaXJpcy50YXJnZXQNCg0KIyBEZWZpbmUgdGhlIHBhcmFtZXRlciBncmlkDQpwYXJhbV9ncmlkID0gew0KICAgICduX2VzdGltYXRvcnMnOiBbNTAsIDEwMF0sDQogICAgJ2xlYXJuaW5nX3JhdGUnOiBbMC4wMSwgMC4xXSwNCiAgICAnbWF4X2RlcHRoJzogWzIsIDMsIDRdDQp9DQoNCiMgSW5pdGlhbGl6ZSBtb2RlbA0KbW9kZWwgPSBHcmFkaWVudEJvb3N0aW5nQ2xhc3NpZmllcigpDQoNCiMgR3JpZCBTZWFyY2gNCmdyaWRfc2VhcmNoID0gR3JpZFNlYXJjaENWKA0KICAgIGVzdGltYXRvcj1tb2RlbCwNCiAgICBwYXJhbV9ncmlkPXBhcmFtX2dyaWQsDQogICAgc2NvcmluZz0nYWNjdXJhY3knLA0KICAgIGN2PTUsICAgICAgICAjIDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uDQogICAgbl9qb2JzPS0xICAgICMgdXNlIGFsbCBhdmFpbGFibGUgQ1BVIGNvcmVzDQopDQoNCiMgRml0DQpncmlkX3NlYXJjaC5maXQoWCwgeSkNCnByaW50KCJCZXN0IFBhcmFtczoiLCBncmlkX3NlYXJjaC5iZXN0X3BhcmFtc18pDQpwcmludCgiQmVzdCBDViBTY29yZToiLCBncmlkX3NlYXJjaC5iZXN0X3Njb3JlXykNCg0KIyBFdmFsdWF0ZSBvbiB0aGUgc2FtZSBkYXRhIG9yIHNlcGFyYXRlIHRlc3Qgc2V0DQpiZXN0X21vZGVsID0gZ3JpZF9zZWFyY2guYmVzdF9lc3RpbWF0b3JfDQpwcmVkcyA9IGJlc3RfbW9kZWwucHJlZGljdChYKQ0KYWNjdXJhY3kgPSBhY2N1cmFjeV9zY29yZSh5LCBwcmVkcykNCnByaW50KCJBY2N1cmFjeSBvbiBlbnRpcmUgZGF0YXNldDoiLCBhY2N1cmFjeSkNCg0KNi4gRXhhbXBsZTogUiAoVXNpbmcgY2FyZXQpDQoNCmxpYnJhcnkoY2FyZXQpDQoNCiMgU3VwcG9zZSBkZiBpcyBhIGRhdGEgZnJhbWUgd2l0aCBwcmVkaWN0b3IgY29sdW1ucyBhbmQgYSBmYWN0b3IgdGFyZ2V0ICJTcGVjaWVzIg0KdHJhaW5fY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gNSkNCg0KZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgbi50cmVlcyA9IGMoNTAsIDEwMCksDQogIGludGVyYWN0aW9uLmRlcHRoID0gYygyLCAzLCA0KSwNCiAgc2hyaW5rYWdlID0gYygwLjAxLCAwLjEpLA0KICBuLm1pbm9ic2lubm9kZSA9IGMoNSkNCikNCg0Kc2V0LnNlZWQoMTIzKQ0KZ2JtX2ZpdCA8LSB0cmFpbigNCiAgU3BlY2llcyB+IC4sDQogIGRhdGEgPSBpcmlzLA0KICBtZXRob2QgPSAiZ2JtIiwNCiAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgdHVuZUdyaWQgPSBncmlkLA0KICB2ZXJib3NlID0gRkFMU0UNCikNCmdibV9maXQkYmVzdFR1bmUNCmdibV9maXQNCg0KNy4gRXhhbXBsZTogU0FTIChDb25jZXB0dWFsKQ0KSW4gc29tZSBTQVMgZW52aXJvbm1lbnRzLCB5b3UgY2FuIGRvIG1hbnVhbCBsb29waW5nIGZvciBlYWNoIGh5cGVycGFyYW1ldGVyIGNvbWJpbmF0aW9uIG9yIHVzZSBhdXRvdHVuaW5nIG9wdGlvbnMuIEZvciBleGFtcGxlLCBpbiBTQVMgVml5YeKAmXMgQ0FTIGVudmlyb25tZW50LCBjZXJ0YWluIGFjdGlvbnMgKGxpa2UgeGdib29zdC50cmFpbikgaGF2ZSBhbiBBdXRvdHVuZSBvciDigJx0dW5l4oCdIHBhcmFtZXRlcjoNCg0KcHJvYyBjYXM7DQogICBzZXNzaW9uIG15U2Vzc2lvbjsNCiAgIGxvYWRhY3Rpb25zZXQgImRlY2lzaW9uVHJlZSI7DQogICBhY3Rpb24geGdib29zdC50cmFpbiAvDQogICAgIHRhYmxlPXtuYW1lPSJ5b3VyX2RhdGEifQ0KICAgICB0YXJnZXQ9InlvdXJfdGFyZ2V0Ig0KICAgICBpbnB1dHM9eyJ4MSIsIngyIiwieDMifQ0KICAgICBhdXRvdHVuZT17DQogICAgICAgc3RlcHM9MTAsDQogICAgICAgb2JqZWN0aXZlPSJBVVRPIiwNCiAgICAgICBzZWFyY2htZXRob2Q9ImdyaWQiLA0KICAgICAgIHBhcmFtZXRlcnM9ew0KICAgICAgICAgeyBuYW1lPSJuVHJlZSIsIHZhbHVlcz0iNTAsMTAwIiB9LA0KICAgICAgICAgeyBuYW1lPSJtYXhEZXB0aCIsIHZhbHVlcz0iMiw0IiB9LA0KICAgICAgICAgeyBuYW1lPSJldGEiLCB2YWx1ZXM9IjAuMDEsMC4xIiB9DQogICAgICAgfQ0KICAgICB9DQogICAgIDsNCnJ1bjsNCg0KSWYgeW91ciBTQVMgdmVyc2lvbiBsYWNrcyB0aGVzZSBmZWF0dXJlcywgeW91IGNhbiBjcmVhdGUgYSBtYWNybyBsb29wIHRoYXQgY2FsbHMgUFJPQyBIUEZPUkVTVCBvciBQUk9DIEdSQURCT09TVCB3aXRoIGRpZmZlcmVudCBwYXJhbWV0ZXIgc2V0dGluZ3MgYW5kIGNvbGxlY3RzIG1ldHJpY3MuICANCg0KOC4gV2hlbiB0byBVc2UNCkdyaWQgc2VhcmNoIGlzIG1vc3QgdXNlZnVsIHdoZW4geW91ciBoeXBlcnBhcmFtZXRlciBzcGFjZSBpcyBzbWFsbCBvciB5b3UgaGF2ZSBzdHJvbmcgcHJpb3IgaW50dWl0aW9uIGFib3V0IHdoYXQgcmFuZ2VzIHRvIGV4cGxvcmUuIElmIHlvdSBoYXZlIG1hbnkgaHlwZXJwYXJhbWV0ZXJzIG9yIGEgd2lkZSByYW5nZSwgeW91IG1pZ2h0IGNvbnNpZGVyIFJhbmRvbSBTZWFyY2ggb3Igb3RoZXIgb3B0aW1pemF0aW9uIGFwcHJvYWNoZXMuICANCg0KOS4gU3VtbWFyeQ0KR3JpZCBTZWFyY2ggc3lzdGVtYXRpY2FsbHkgZXhwbG9yZXMgYSBwcmVkZWZpbmVkIHNldCBvZiBoeXBlcnBhcmFtZXRlciB2YWx1ZXMsIHdoaWNoIGNhbiBndWFyYW50ZWUgdGhhdCB5b3UgZG9u4oCZdCBtaXNzIGFueSBjb21iaW5hdGlvbiBpbiB0aGF0IGdyaWQuIFdoaWxlIGV4aGF1c3RpdmUsIGl0IGNhbiBiZSBleHBlbnNpdmUgZm9yIGxhcmdlIHBhcmFtZXRlciBzcGFjZXMuIEhvd2V2ZXIsIGZvciBtb2RlcmF0ZSBwcm9ibGVtIHNpemVzLCBpdCByZW1haW5zIGEgc3RhbmRhcmQgdG9vbCBmb3IgbW9kZWwgc2VsZWN0aW9uIGFuZCBjYW4geWllbGQgZXhjZWxsZW50IHJlc3VsdHMuICANCg0KDQpCZWxvdyBpcyBteSBzaXh0aCAoYW5kIGZpbmFsKSBzZWN0aW9uIG9mIHRoZSBzdHVkeSBndWlkZSwgZm9jdXNpbmcgb24gUmFuZG9tIFNlYXJjaC4gQXMgYmVmb3JlLCBJIGRyZXcgb24gVGhlIEVsZW1lbnRzIG9mIFN0YXRpc3RpY2FsIExlYXJuaW5nIChFU0wpIO6IgGNpdGXuiIJ0dXJuMGZpbGUw7oiBIGFuZCB0aGUgTW9kdWxlIDggQXN5bmNocm9ub3VzIHRyYW5zY3JpcHQgbWF0ZXJpYWwg7oiAY2l0Ze6IgnR1cm4wZmlsZTHuiIEuIENvZGUgc25pcHBldHMgYXJlIGdpdmVuIGluIGEgY29weS9wYXN0ZSBmcmllbmRseSBmb3JtYXQuDQoNClNFQ1RJT04gNjogUkFORE9NIFNFQVJDSA0KDQoxLiBXaGF0IGlzIFJhbmRvbSBTZWFyY2g/DQpJIGNvbnNpZGVyIFJhbmRvbSBTZWFyY2ggYW4gYWx0ZXJuYXRpdmUgaHlwZXJwYXJhbWV0ZXIgb3B0aW1pemF0aW9uIHN0cmF0ZWd5IHRvIEdyaWQgU2VhcmNoLiBJbnN0ZWFkIG9mIGV4aGF1c3RpdmVseSBlbnVtZXJhdGluZyBhIGdyaWQgb2YgcG9zc2libGUgaHlwZXJwYXJhbWV0ZXIgdmFsdWVzLCB3ZSByYW5kb21seSBzYW1wbGUgZnJvbSBzcGVjaWZpZWQgZGlzdHJpYnV0aW9ucyBmb3IgZWFjaCBoeXBlcnBhcmFtZXRlci4gVGhlIGtleSBpZGVhIGlzIHRoYXQgcmFuZG9tbHkgY2hvc2VuIHBvaW50cyBpbiBhIGhpZ2gtZGltZW5zaW9uYWwgc3BhY2UgY2FuIG9mdGVuIGNvdmVyIGRpdmVyc2UgcmVnaW9ucyBtb3JlIGVmZmljaWVudGx5IHRoYW4gYW4gZXhoYXVzdGl2ZSAoZ3JpZCkgbWV0aG9kIHdpdGggdGhlIHNhbWUgY29tcHV0YXRpb25hbCBidWRnZXQuICANCg0KMi4gQWR2YW50YWdlcyBvdmVyIEdyaWQgU2VhcmNoDQrigKIgRWZmaWNpZW5jeTogRm9yIHRoZSBzYW1lIG51bWJlciBvZiB0cmlhbHMsIHJhbmRvbSBzZWFyY2ggb2Z0ZW4gZmluZHMgYmV0dGVyIHBhcmFtZXRlciBzZXR0aW5ncyB0aGFuIGEgY29hcnNlIGdyaWQsIGVzcGVjaWFsbHkgd2hlbiBvbmx5IGEgZmV3IGh5cGVycGFyYW1ldGVycyBhcmUgdHJ1bHkgaW5mbHVlbnRpYWwuICANCuKAoiBTY2FsYWJpbGl0eTogQnkgc2FtcGxpbmcgZnJvbSBlYWNoIGh5cGVycGFyYW1ldGVy4oCZcyBkaXN0cmlidXRpb24sIHlvdSBjYW4gZWFzaWx5IGFkZCBtb3JlIHNhbXBsZXMgb3IgZHJhdyBmcm9tIHNwZWNpYWxpemVkIGRpc3RyaWJ1dGlvbnMgKGxvZyBzY2FsZSwgdW5pZm9ybSwgZXRjLikuICANCuKAoiBBZGFwdGFiaWxpdHk6IElmIHlvdSBkaXNjb3ZlciB5b3UgbmVlZCBtb3JlIHRyaWFscywgeW91IGNhbiBqdXN0IGNvbnRpbnVlIHNhbXBsaW5nLiAgDQoNCjMuIEJhc2ljIFN0ZXBzDQoxKSBEZWZpbmUgYSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24gb3IgcmFuZ2UgZm9yIGVhY2ggaHlwZXJwYXJhbWV0ZXIuIEZvciBleGFtcGxlLCBsZWFybmluZ19yYXRlIOKIvCBVbmlmb3JtKDAuMDEsIDAuMiksIG1heF9kZXB0aCDiiIggezIsIDMsIDQsIDV9LCBvciBhbHBoYSDiiLwgTG9nVW5pZm9ybSgxZeKAkzUsIDEpLiAgDQoyKSBSYW5kb21seSBzYW1wbGUgYSBzZXQgb2YgaHlwZXJwYXJhbWV0ZXIgY29uZmlndXJhdGlvbnMuICANCjMpIEZvciBlYWNoIHNhbXBsZWQgY29uZmlndXJhdGlvbiwgdHJhaW4gdGhlIG1vZGVsIChlLmcuLCB3aXRoIGNyb3NzLXZhbGlkYXRpb24pIGFuZCByZWNvcmQgYSBwZXJmb3JtYW5jZSBtZXRyaWMuICANCjQpIEtlZXAgdHJhY2sgb2YgdGhlIGJlc3QtcGVyZm9ybWluZyBjb21iaW5hdGlvbiBhbmQgcG9zc2libHkga2VlcCBzZWFyY2hpbmcgYXMgcmVzb3VyY2VzIGFsbG93LiAgDQoNCjQuIEV4YW1wbGU6IFB5dGhvbiAoVXNpbmcgc2Npa2l0LWxlYXJu4oCZcyBSYW5kb21pemVkU2VhcmNoQ1YpDQoNCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IFJhbmRvbWl6ZWRTZWFyY2hDVg0KZnJvbSBza2xlYXJuLmVuc2VtYmxlIGltcG9ydCBHcmFkaWVudEJvb3N0aW5nUmVncmVzc29yDQpmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IGxvYWRfYm9zdG9uDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgbWVhbl9zcXVhcmVkX2Vycm9yDQppbXBvcnQgbnVtcHkgYXMgbnANCg0KIyBMb2FkIGRhdGENCmJvc3RvbiA9IGxvYWRfYm9zdG9uKCkNClgsIHkgPSBib3N0b24uZGF0YSwgYm9zdG9uLnRhcmdldA0KDQojIERlZmluZSBwYXJhbWV0ZXIgZGlzdHJpYnV0aW9ucw0KcGFyYW1fZGlzdCA9IHsNCiAgICAnbl9lc3RpbWF0b3JzJzogbnAucmFuZG9tLnJhbmRpbnQoNTAsIDIwMCwgc2l6ZT01MCksICAjIHNhbXBsZSBpbnRlZ2VycyBmcm9tIDUwLi4yMDANCiAgICAnbGVhcm5pbmdfcmF0ZSc6IG5wLmxpbnNwYWNlKDAuMDEsIDAuMiwgbnVtPTIwKSwgICAgICAgIyBzYW1wbGUgZnJvbSAwLjAxLi4wLjINCiAgICAnbWF4X2RlcHRoJzogbnAucmFuZG9tLnJhbmRpbnQoMiwgNiwgc2l6ZT00KSAgICAgICAgICAjIHNhbXBsZSBmcm9tIHsyLDMsNCw1fQ0KfQ0KDQojIE1vZGVsDQptb2RlbCA9IEdyYWRpZW50Qm9vc3RpbmdSZWdyZXNzb3IoKQ0KDQojIFJhbmRvbSBTZWFyY2gNCnJhbmRfc2VhcmNoID0gUmFuZG9taXplZFNlYXJjaENWKA0KICAgIGVzdGltYXRvcj1tb2RlbCwNCiAgICBwYXJhbV9kaXN0cmlidXRpb25zPXBhcmFtX2Rpc3QsDQogICAgbl9pdGVyPTEwLCAgICAgICAgICAgICAgICMgbnVtYmVyIG9mIHBhcmFtZXRlciBzZXR0aW5ncyB0byB0cnkNCiAgICBzY29yaW5nPSduZWdfbWVhbl9zcXVhcmVkX2Vycm9yJywNCiAgICBjdj01LA0KICAgIHJhbmRvbV9zdGF0ZT00MiwNCiAgICBuX2pvYnM9LTENCikNCg0KcmFuZF9zZWFyY2guZml0KFgsIHkpDQoNCmJlc3RfcGFyYW1zID0gcmFuZF9zZWFyY2guYmVzdF9wYXJhbXNfDQpiZXN0X3Njb3JlID0gLXJhbmRfc2VhcmNoLmJlc3Rfc2NvcmVfICAjIGJlY2F1c2Ugd2UgdXNlZCBuZWcgTVNFDQpwcmludCgiQmVzdCBQYXJhbXM6IiwgYmVzdF9wYXJhbXMpDQpwcmludCgiQmVzdCBDViBNU0U6IiwgYmVzdF9zY29yZSkNCg0KNS4gRXhhbXBsZTogUiAoVXNpbmcgY2FyZXQgd2l0aCDigJxzZWFyY2ggPSAncmFuZG9tJ+KAnSkNCg0KbGlicmFyeShjYXJldCkNCg0KZGF0YShpcmlzKQ0Kc2V0LnNlZWQoMTIzKQ0KDQp0cmFpbl9jb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSA1LCBzZWFyY2ggPSAicmFuZG9tIikNCg0KIyBEZWZpbmUgdGhlIG1vZGVsIChlLmcuLCBnYm0gZm9yIGJvb3N0aW5nKQ0KbW9kZWwgPC0gdHJhaW4oDQogIFNwZWNpZXMgfiAuLA0KICBkYXRhID0gaXJpcywNCiAgbWV0aG9kID0gImdibSIsDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogIHR1bmVMZW5ndGggPSAxMCAgICMgd2lsbCB0cnkgMTAgcmFuZG9tIGNvbWJpbmF0aW9ucw0KKQ0KDQptb2RlbCRiZXN0VHVuZQ0KbW9kZWwkcmVzdWx0cw0KDQo2LiBFeGFtcGxlOiBTQVMgKENvbmNlcHR1YWwpDQpJbiBTQVMsIHlvdSBjYW4gcmFuZG9tbHkgZ2VuZXJhdGUgcGFyYW1ldGVyIHNldHMgaW4gYSBtYWNybyBvciBpbiBhIENBUyBhY3Rpb24gaWYgYXZhaWxhYmxlLiBJZiB5b3UgZG8gbm90IGhhdmUgYW4gYXV0b21hdGVkIHJhbmRvbSBzZWFyY2ggZnVuY3Rpb24sIHlvdSBjYW4gZG8gc29tZXRoaW5nIGxpa2U6DQoNCiVtYWNybyBydW5fcmFuZG9tX3NlYXJjaChudW1fcnVucz0xMCk7DQogICAlZG8gaT0xICV0byAmbnVtX3J1bnM7DQogICAgICAlbGV0IG50cmVlcyA9ICVzeXNmdW5jKHJhbmQoaW50ZWdlciwgNTAsIDIwMCkpOw0KICAgICAgJWxldCBkZXB0aCA9ICVzeXNmdW5jKHJhbmQoaW50ZWdlciwgMiwgNikpOw0KICAgICAgJWxldCBsciAgICA9ICVzeXNldmFsZiglc3lzZnVuYyhyYW5kKHVuaWZvcm0pKSooMC4yLTAuMDEpKzAuMDEpOw0KDQogICAgICAvKiBUaGVuIGNhbGwgeW91ciBjaG9zZW4gcHJvY2VkdXJlIChlLmcuLCBQUk9DIEdSQURCT09TVCwgSFBGT1JFU1QsIG9yIHhnYm9vc3QudHJhaW4pDQogICAgICAgICB3aXRoIHRoZXNlIHBhcmFtZXRlcnMgYW5kIHJlY29yZCBwZXJmb3JtYW5jZS4gKi8NCiAgICVlbmQ7DQolbWVuZDsNCg0KJXJ1bl9yYW5kb21fc2VhcmNoKG51bV9ydW5zPTEwKTsNCg0KNy4gV2hlbiB0byBVc2UgUmFuZG9tIFNlYXJjaA0K4oCiIEVzcGVjaWFsbHkgaGVscGZ1bCBpZiB5b3VyIG1vZGVsIGhhcyBtYW55IGh5cGVycGFyYW1ldGVycyBvciBpZiB5b3UgYmVsaWV2ZSBvbmx5IGEgc3Vic2V0IG9mIHRoZW0gc2lnbmlmaWNhbnRseSBhZmZlY3RzIHBlcmZvcm1hbmNlLiAgDQrigKIgVXNlZnVsIGFzIGEgZmlyc3QgcGFzcyB0byBsb2NhdGUgcHJvbWlzaW5nIHJlZ2lvbnMsIGZvbGxvd2VkIGJ5IGEgbW9yZSBmaW5lLWdyYWluZWQgc2VhcmNoIG9yIGEgQmF5ZXNpYW4gb3B0aW1pemF0aW9uIG1ldGhvZC4gIA0K4oCiIElmIHlvdSB3YW50IHRvIHF1aWNrbHkgc2NhbGUgdGhlIG51bWJlciBvZiB0cmlhbHMgb3IgYnVkZ2V0IG1vcmUgY29tcHV0aW5nIHRpbWUsIHJhbmRvbSBzZWFyY2ggaXMgZWFzeSB0byBleHRlbmQuICANCg0KOC4gU3VtbWFyeQ0KUmFuZG9tIFNlYXJjaCBpcyBhIGZsZXhpYmxlIGFuZCBvZnRlbiBzdXJwcmlzaW5nbHkgZWZmZWN0aXZlIGFwcHJvYWNoIHRvIGh5cGVycGFyYW1ldGVyIHR1bmluZywgcGFydGljdWxhcmx5IGluIGhpZ2hlci1kaW1lbnNpb25hbCBzcGFjZXMgd2hlcmUgZ3JpZCBzZWFyY2ggYmVjb21lcyBwcm9oaWJpdGl2ZWx5IGV4cGVuc2l2ZS4gQnkgc3BlY2lmeWluZyBtZWFuaW5nZnVsIGRpc3RyaWJ1dGlvbnMgZm9yIGVhY2ggaHlwZXJwYXJhbWV0ZXIsIHlvdSBjYW4gZm9jdXMgeW91ciBzZWFyY2ggaW4gcHJvbWlzaW5nIHJlZ2lvbnMgYW5kIGVmZmljaWVudGx5IHVuY292ZXIgYSBzdHJvbmcgbW9kZWwgY29uZmlndXJhdGlvbi4NCg0KSGVyZSBhcmUgdGhyZWUga2V5IHRha2Vhd2F5cyBmcm9tIHRoZSBSYW5kb20gU2VhcmNoIHNlY3Rpb246DQoNCjEpIFJhbmRvbSBTZWFyY2ggc2FtcGxlcyBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbnMgZnJvbSBwcmVkZWZpbmVkIGRpc3RyaWJ1dGlvbnMsIG9mdGVuIHByb3ZpZGluZyBiZXR0ZXIgY292ZXJhZ2Ugb2YgdGhlIHNlYXJjaCBzcGFjZSB0aGFuIGFuIGV4aGF1c3RpdmUgZ3JpZCBmb3IgdGhlIHNhbWUgbnVtYmVyIG9mIHRyaWFscy4gIA0KMikgSXQgaXMgaGlnaGx5IGZsZXhpYmxlIGFuZCBzY2FsYWJsZSwgbWFraW5nIGl0IGVhc3kgdG8gYWRqdXN0IHRoZSBudW1iZXIgb2YgdGVzdGVkIGNvbWJpbmF0aW9ucyBpZiBtb3JlIHRpbWUgb3IgcmVzb3VyY2VzIGJlY29tZSBhdmFpbGFibGUuICANCjMpIFJhbmRvbSBTZWFyY2ggY2FuIGJlIGNvbWJpbmVkIHdpdGggb3RoZXIgbWV0aG9kcyAobGlrZSBCYXllc2lhbiBvcHRpbWl6YXRpb24pIHRvIHJlZmluZSB0aGUgc2VhcmNoIGFmdGVyIGlkZW50aWZ5aW5nIHByb21pc2luZyByZWdpb25zIG9mIGh5cGVycGFyYW1ldGVyIHZhbHVlcy4NCg0KSGVyZSBhcmUgdGhyZWUgdGhvdWdodC1wcm92b2tpbmcgcXVlc3Rpb25zIHRvIGNvbnNpZGVyOg0KDQoxKSBJbiB3aGF0IHR5cGVzIG9mIHNjZW5hcmlvcyBtaWdodCBhIHB1cmVseSByYW5kb20gYXBwcm9hY2ggZmFpbCB0byBsb2NhdGUgZ29vZCBoeXBlcnBhcmFtZXRlcnMsIGFuZCBob3cgY291bGQgeW91IG1pdGlnYXRlIHRoaXM/ICANCjIpIEhvdyBjYW4gZG9tYWluIGtub3dsZWRnZSBndWlkZSB5b3VyIGNob2ljZSBvZiBkaXN0cmlidXRpb25zIGZvciB0aGUgaHlwZXJwYXJhbWV0ZXJzLCBpbnN0ZWFkIG9mIHVzaW5nIHVuaWZvcm0gb3IgbmFpdmUgZGlzdHJpYnV0aW9ucz8gIA0KMykgQWZ0ZXIgZmluZGluZyBhIGdvb2QgaHlwZXJwYXJhbWV0ZXIgc2V0IHRocm91Z2ggcmFuZG9tIHNlYXJjaCwgaG93IGRvIHdlIGRlY2lkZSB3aGV0aGVyIHRvIHJlZmluZSBpdCBmdXJ0aGVyIG9yIGFjY2VwdCBpdCBhcyBmaW5hbD8gIA0K