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)
citeturn0file0 and to the video transcript (Module 8 Asynch)
citeturn0file1. 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
- 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.
citeturn0file1
• 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. citeturn0file1
• 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.) citeturn0file0
• 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. citeturn0file1
- General Boosting Algorithm Steps
- Fit a weak learner to the data (for example, a very shallow
tree).
- Evaluate its predictions and compute the residuals or errors.
- Make those residuals (or a transformed version) the new “target” in
the next iteration.
- Fit a new weak learner to these residuals.
- Repeat until a stopping criterion is reached (e.g., a maximum number
of iterations or minimal improvement). citeturn0file1
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.). citeturn0file0
- 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. citeturn0file1
- Handwritten Formula (Simplified) Below is a simple version of the
boosting update for regression, using a generic loss function L(y,
F(x)):
Initialize: F₀(x) = arg minᵧ ∑ᵢ L(yᵢ, y).
For m = 1 to M:
- Compute pseudo-residuals:
rᵢₘ = – [∂/∂F(xᵢ)] L(yᵢ, F(xᵢ)) evaluated at F = Fₘ₋₁(xᵢ).
- Fit a weak learner hₘ(x) to the {rᵢₘ}.
- Compute multiplier γₘ = arg minᵧ ∑ᵢ L(yᵢ, Fₘ₋₁(xᵢ) + γ
hₘ(xᵢ)).
- Update model:
Fₘ(x) = Fₘ₋₁(x) + ν ⋅ γₘ hₘ(x).
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. citeturn0file0
- 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.
- 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. citeturn0file1
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) citeturn0file0 and the
video transcript content (Module 8 Asynch) citeturn0file1. I also
provide sample code in Python, R, and SAS for illustration.
SECTION 2: XGBOOST
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. citeturn0file0
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.
citeturn0file1
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. citeturn0file1
- 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. citeturn0file1
- 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).
- 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;
- 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)
citeturn0file0 and the video transcript (Module 8 Asynch)
citeturn0file1. I will also provide some brief code snippets in
Python, R, and SAS for illustration.
SECTION 3: HYPERPARAMETERS
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). citeturn0file0
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.
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. citeturn0file1
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.
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:
- Choose a range or distribution for each hyperparameter.
- Sample different combinations of these hyperparameters.
- Fit the model on training folds, evaluate on a validation
fold.
- Pick the combination that yields the best average validation
score.
- Refit on the full training data if needed.
- 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.
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.
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)
citeturn0file0 and the video transcript content (Module 8 Asynch)
citeturn0file1. 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)
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.
Steps to Replicate
- 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).
Split into Training and Test
• We can do a simple 70–30 split or use cross-validation.
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.
Train an XGBoost Classifier
• For a multiclass task, set the objective to “multi:softprob” or
“multi:softmax” and specify num_class=10.
Evaluate Accuracy
• Use predictions on the test set and measure classification accuracy or
confusion matrix.
- 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.
- 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)
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.
Steps to Replicate
- 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.
Split into Training and Test
Build XGBoost Regressor
• objective=‘reg:squarederror’ (for standard regression).
Evaluate MSE or R²
- 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)
- 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)
citeturn0file0 and content from the transcripts (Module 8 Asynch)
citeturn0file1. As usual, I’ll also provide copy/paste friendly code
snippets for multiple programming languages.
SECTION 5: GRID SEARCH
- 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:
- Define ranges (or sets) of possible values for each hyperparameter:
for example, max_depth ∈ {2,3,4}, learning_rate ∈ {0.01, 0.1},
etc.
- 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).
- Select the combination that yields the best performance
metric.
- Optionally, refit the model with the chosen hyperparameters on the
entire training set.
- 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.
- 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
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.”
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()
Grid Search
grid_search = GridSearchCV( estimator=model, param_grid=param_grid,
scoring=‘accuracy’, cv=5, # 5-fold cross-validation n_jobs=-1 # use all
available CPU cores )
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)
- 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
- 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.
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.
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) citeturn0file0 and the Module 8 Asynchronous transcript
material citeturn0file1. Code snippets are given in a copy/paste
friendly format.
SECTION 6: RANDOM SEARCH
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.
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.
Basic Steps
- 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).
- Randomly sample a set of hyperparameter configurations.
- For each sampled configuration, train the model (e.g., with
cross-validation) and record a performance metric.
- Keep track of the best-performing combination and possibly keep
searching as resources allow.
- 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()
Random Search
rand_search = RandomizedSearchCV( estimator=model,
param_distributions=param_dist, n_iter=10, # number of parameter
settings to try scoring=‘neg_mean_squared_error’, cv=5, random_state=42,
n_jobs=-1 )
rand_search.fit(X, y)
best_params = rand_search.best_params_ best_score =
-rand_search.best_score_ # because we used neg MSE print(“Best Params:”,
best_params) print(“Best CV MSE:”, best_score)
- Example: R (Using caret with “search = ‘random’”)
library(caret)
data(iris) set.seed(123)
train_control <- trainControl(method = “cv”, number = 5, search =
“random”)
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
- 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);
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.
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:
- 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.
- It is highly flexible and scalable, making it easy to adjust the
number of tested combinations if more time or resources become
available.
- 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:
- In what types of scenarios might a purely random approach fail to
locate good hyperparameters, and how could you mitigate this?
- How can domain knowledge guide your choice of distributions for the
hyperparameters, instead of using uniform or naive distributions?
- 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