Boosting Study Guide
1. Introduction to Boosting
Boosting is a machine learning ensemble technique that converts weak
learners into a strong learner by iteratively adjusting their weights
based on errors from previous models. Unlike bagging, which works with
independent models, boosting models are trained sequentially.
Key Concept: - Boosting focuses on reducing
bias by training weak models sequentially, where each model
corrects the errors of the previous one. - It uses weighted training,
meaning misclassified samples get higher weights in the next
iteration.
Steps in Boosting: 1. Train a weak model (e.g., a
simple decision tree). 2. Make predictions and compute residuals
(errors). 3. Adjust sample weights: Give more importance to
misclassified data. 4. Train the next weak model on adjusted data. 5.
Repeat until stopping criteria (e.g., error convergence) is met.
2. How Boosting Works
Boosting is an iterative process: - It starts with an initial model
that makes predictions. - Errors (residuals) from this model become the
new target. - The process repeats, and new models correct prior
mistakes. - The final prediction is a weighted sum of all weak
learners.
Mathematical Representation: Each new model learns
from the residuals: - Let \(F(x)\) be
the prediction at step \(t\). - The new
model \(h_t(x)\) fits the residuals
\(r_t\). - The updated model is: \[
F_{t+1}(x) = F_t(x) + \eta h_t(x)
\] where \(\eta\) is the
learning rate.
Comparison with Bagging: | Feature | Boosting |
Bagging | |———-|———|———| | Order of training | Sequential | Parallel | |
Focus | Reducing bias | Reducing variance | | Model dependency | Next
model corrects prior errors | Models are independent | | Example
algorithms | AdaBoost, Gradient Boosting | Random Forest |
3. Advantages and Disadvantages of Boosting
Pros: ✔ Works well with weak learners.
✔ Reduces bias and variance.
✔ Handles complex relationships in data.
✔ Usually provides better accuracy than bagging methods.
Cons: ✖ Can overfit with too many iterations.
✖ Computationally expensive.
✖ Sensitive to noise in the dataset.
4. Common Boosting Algorithms
- AdaBoost (Adaptive Boosting)
- Assigns higher weights to misclassified points.
- Uses an exponential loss function.
- Works well with small decision trees.
- Gradient Boosting (GBM - Gradient Boosting
Machines)
- Uses gradient descent to minimize a loss function.
- Each model corrects the residuals of the previous model.
- Allows custom loss functions (e.g., mean squared error, log
loss).
- XGBoost (Extreme Gradient Boosting)
- Optimized implementation of GBM.
- Uses regularization (L1, L2) to prevent overfitting.
- Supports parallel processing.
5. Implementation Example (Python)
Basic Boosting with Scikit-learn
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# Create dataset
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)
# Split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Create and train AdaBoost model
base_model = DecisionTreeClassifier(max_depth=1) # Weak learner
model = AdaBoostClassifier(base_estimator=base_model, n_estimators=50, learning_rate=0.1, random_state=42)
model.fit(X_train, y_train)
# Predictions
y_pred = model.predict(X_test)
print("Accuracy:", accuracy_score(y_test, y_pred))
XGBoost Study Guide
1. Introduction to XGBoost
XGBoost (Extreme Gradient Boosting) is an optimized
boosting algorithm that improves upon traditional boosting methods. It
is widely used in machine learning competitions and industry
applications due to its efficiency and high accuracy.
Why XGBoost? ✔ Faster and more scalable than
traditional Gradient Boosting
✔ Supports parallel computation (unlike traditional
boosting)
✔ Implements regularization (L1 and L2) to prevent
overfitting
✔ Can handle missing values automatically
✔ Works well with large datasets
2. How XGBoost Works
XGBoost is an ensemble of decision trees, trained
sequentially to minimize residual errors. However, it optimizes the
boosting process using two key techniques: 1. Gradient
Boosting – Uses gradient descent to minimize the loss function.
2. Regularization – Applies L1 (Lasso) and L2 (Ridge)
regularization to prevent overfitting.
3. The XGBoost Algorithm
- Initialize the model with weak learners (decision
trees).
- Compute Residuals – Calculate errors from the
previous iteration.
- Fit a New Tree – Train the next decision tree on
residuals.
- Optimize the Loss Function – Uses gradient
descent to minimize errors.
- Apply Regularization – Shrinks tree complexity to
avoid overfitting.
- Repeat until stopping criteria are met (e.g., max
trees, early stopping).
4. Key Features of XGBoost
- Parallel Tree Learning – Unlike traditional
boosting, XGBoost can build trees simultaneously.
- Regularized Learning – XGBoost applies L1
and L2 regularization to prevent overfitting.
- Weighted Quantile Sketch – Handles missing values
and skewed data efficiently.
- Pruning – XGBoost uses maximum depth
instead of pre-pruning for better control.
5. XGBoost vs. Traditional Boosting
Regularization |
L1 & L2 (Ridge, Lasso) |
No built-in regularization |
Computation Speed |
Fast, optimized |
Slower |
Missing Values |
Handled automatically |
Needs imputation |
Parallelism |
Yes |
No |
Tree Growth |
Depth-wise |
Leaf-wise |
6. Loss Function in XGBoost
XGBoost minimizes a combination of: - Loss Function
(L): Measures prediction error (e.g., Mean Squared Error) -
Regularization Term (Ω): Penalizes complex models
\[
\text{Loss} = L(y, \hat{y}) + \Omega(f)
\] Where: - \(y\) is the true
label - \(\hat{y}\) is the predicted
value - \(f\) is the function learned
by the model - \(\Omega\) is the
complexity penalty term
7. Implementation: XGBoost in Python
Step 1: Install XGBoost
pip install xgboost
Step 2: Import Libraries
import xgboost as xgb
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import numpy as np
Step 3: Load and Prepare Data
# Load dataset
boston = load_boston()
X, y = boston.data, boston.target
# Train-test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
Step 4: Train an XGBoost Model
# Convert to DMatrix format (optimized for XGBoost)
train_data = xgb.DMatrix(X_train, label=y_train)
test_data = xgb.DMatrix(X_test, label=y_test)
# Define model parameters
params = {
'objective': 'reg:squarederror', # Regression task
'eval_metric': 'rmse', # Root Mean Squared Error
'max_depth': 4, # Depth of trees
'learning_rate': 0.1, # Step size
'n_estimators': 100 # Number of boosting rounds
}
# Train model
model = xgb.train(params, train_data, num_boost_round=100)
Step 5: Make Predictions and Evaluate
# Predictions
y_pred = model.predict(test_data)
# Calculate RMSE
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"Root Mean Squared Error: {rmse}")
8. Summary
- XGBoost is an optimized form of boosting that is
faster and more accurate.
- It uses gradient boosting and
regularization to improve performance.
- Parallelization makes it highly efficient on large
datasets.
- It automatically handles missing values and feature
selection.
- XGBoost requires hyperparameter tuning for optimal
results.
Next Step: Hyperparameter Tuning in XGBoost
1. Importance of Hyperparameter Tuning
Hyperparameters in XGBoost significantly impact the model’s
performance, and tuning them is essential to maximize accuracy and
minimize overfitting.
Key Hyperparameters to Tune: -
learning_rate
(or eta
):
Controls the step size during optimization. Lower values make the
learning process more gradual, potentially improving accuracy but
requiring more boosting rounds. -
max_depth
: The maximum depth of individual
trees. Deeper trees are more complex and can overfit the data. -
n_estimators
: The number of boosting
rounds (trees) to train. - subsample
: The
fraction of samples used per boosting round. Helps prevent overfitting.
- colsample_bytree
: Fraction of features
to use per boosting round, promoting diversity in the trees and
preventing overfitting. - gamma
: Controls
the minimum loss reduction required to make a further partition. Higher
values make the algorithm more conservative. -
lambda
(L2 regularization) and
alpha
(L1 regularization): Help prevent
overfitting by penalizing large coefficients in the trees.
2. Grid Search for Hyperparameter Tuning
A common method for hyperparameter tuning is Grid
Search, where you define a grid of potential hyperparameter
values, and then exhaustively train and evaluate models using all
combinations.
Step 1: Define Hyperparameter Grid
You can create a grid of possible hyperparameter values:
from sklearn.model_selection import GridSearchCV
import xgboost as xgb
# Define the model
xgb_model = xgb.XGBClassifier()
# Define the parameter grid
param_grid = {
'learning_rate': [0.01, 0.1, 0.2],
'max_depth': [3, 5, 7],
'n_estimators': [50, 100, 150],
'subsample': [0.8, 1.0],
'colsample_bytree': [0.8, 1.0]
}
Step 3: Evaluate the Best Model
Once the best parameters are identified, you can evaluate the
model:
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test)
# Calculate accuracy (for classification)
from sklearn.metrics import accuracy_score
print("Accuracy of the best model: ", accuracy_score(y_test, y_pred))
3. Random Search for Hyperparameter Tuning
While Grid Search is exhaustive, it can be
computationally expensive. Random Search randomly
samples hyperparameters from a predefined distribution and is often
faster and just as effective.
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import uniform
# Define the parameter distribution
param_dist = {
'learning_rate': uniform(0.01, 0.2),
'max_depth': [3, 5, 7],
'n_estimators': [50, 100, 150],
'subsample': uniform(0.5, 0.5),
'colsample_bytree': uniform(0.5, 0.5)
}
# Define the model and RandomizedSearchCV
random_search = RandomizedSearchCV(estimator=xgb.XGBClassifier(), param_distributions=param_dist, n_iter=100, cv=5, verbose=1)
random_search.fit(X_train, y_train)
# Best hyperparameters
print("Best parameters found: ", random_search.best_params_)
Next Steps:
feature importance visualization in XGBoost &
advanced hyperparameter tuning strategies such as
Bayesian Optimization
Feature Importance in XGBoost
Understanding which features contribute the most to your model’s
predictions can help in: - Feature selection - Model interpretation -
Reducing overfitting by eliminating unimportant features
1. Types of Feature Importance in XGBoost
XGBoost provides several ways to measure feature importance: 1.
Weight (Frequency): Number of times a feature is used
in a split across all trees. 2. Gain (Information
Gain): Contribution of a feature to the model based on its
average gain when used in splits. 3. Cover: Number of
samples affected by the feature’s split.
By default, XGBoost uses Gain to determine
importance.
3. Interpreting Feature Importance Results
- High Importance: Features that contribute the most
to predictions.
- Low Importance: Features with little contribution,
potentially removable to simplify the model.
- Feature Engineering: If a less important feature is
domain-relevant, try transforming it.
Bayesian Optimization for Hyperparameter
Tuning
Now that we understand feature importance, let’s optimize
hyperparameters more efficiently.
1. Why Bayesian Optimization?
Unlike Grid Search and Random
Search, Bayesian Optimization: ✔ Learns from past evaluations
to choose better hyperparameters next time
✔ Reduces computational cost by not testing unnecessary
combinations
✔ Finds optimal hyperparameters faster
It balances exploration (trying new parameters) and
exploitation (refining known good parameters).
2. Implementing Bayesian Optimization in
XGBoost
We use the BayesianOptimization
package to automatically
find the best hyperparameters.
Step 1: Install Required Packages
pip install bayesian-optimization
Step 2: Import Required Libraries
from bayes_opt import BayesianOptimization
import xgboost as xgb
from sklearn.model_selection import cross_val_score
import numpy as np
Step 3: Define the Objective Function
Bayesian Optimization requires a function to
maximize. We define a function that returns the
negative mean squared error (MSE).
# Define the function to optimize
def xgb_evaluate(learning_rate, max_depth, subsample, colsample_bytree):
params = {
'objective': 'reg:squarederror',
'learning_rate': learning_rate,
'max_depth': int(max_depth), # Must be integer
'subsample': subsample,
'colsample_bytree': colsample_bytree,
'n_estimators': 100
}
# Perform cross-validation
scores = cross_val_score(xgb.XGBRegressor(**params), X_train, y_train, scoring="neg_mean_squared_error", cv=3)
return np.mean(scores) # Return mean negative MSE
Step 4: Set Up the Bayesian Optimization Search
Space
# Define the search space for hyperparameters
pbounds = {
'learning_rate': (0.01, 0.3),
'max_depth': (3, 10),
'subsample': (0.5, 1.0),
'colsample_bytree': (0.5, 1.0)
}
# Initialize Bayesian Optimization
optimizer = BayesianOptimization(
f=xgb_evaluate, # The function to maximize
pbounds=pbounds, # Search space
random_state=42
)
Step 5: Run the Optimization
optimizer.maximize(init_points=5, n_iter=25)
# Best parameters found
print("Best Hyperparameters:", optimizer.max)
init_points=5
: Randomly tests 5 initial points.
n_iter=25
: Runs 25 optimization steps to find the best
parameters.
Step 6: Train the Best Model
# Extract best parameters
best_params = optimizer.max['params']
best_params['max_depth'] = int(best_params['max_depth']) # Convert depth to integer
# Train the final model
final_model = xgb.XGBRegressor(objective='reg:squarederror', **best_params, n_estimators=100)
final_model.fit(X_train, y_train)
# Evaluate model performance
y_pred = final_model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"Optimized RMSE: {rmse:.4f}")
3. Comparison of Hyperparameter Tuning Methods
Grid Search |
Exhaustive, guarantees best parameters |
Computationally expensive |
Random Search |
Faster than Grid Search, good for large search spaces |
May miss the best parameters |
Bayesian Optimization |
Learns from previous evaluations, fewer iterations needed |
Requires additional setup |
5. Summary
✔ Grid Search: Exhaustive, best for small datasets,
guarantees optimal parameters.
✔ Random Search: Faster, better for large datasets,
good approximation of optimal parameters.
✔ Best practice: Use Random Search
first, then fine-tune with Grid Search.
Next Topic: Hyperparameters in XGBoost
1. Introduction to XGBoost Hyperparameters
Hyperparameters in XGBoost control how the model learns and
generalizes. Proper tuning of these parameters can significantly improve
model performance.
XGBoost has three main types of hyperparameters: 1.
Tree-related parameters (control how trees are built).
2. Boosting-related parameters (affect learning and
regularization). 3. Miscellaneous parameters (affect
computation and optimization).
2. Key XGBoost Hyperparameters
2.1 Tree Structure Hyperparameters
These parameters control the depth and complexity of
decision trees in XGBoost.
max_depth |
Maximum depth of trees. Higher values increase complexity. |
3-10 |
min_child_weight |
Minimum sum of weights required to split a node. Prevents
overfitting. |
1-10 |
gamma |
Minimum loss reduction required for further tree partitioning. Helps
pruning. |
0-5 |
colsample_bytree |
Fraction of features used for each tree. Reduces overfitting. |
0.5-1.0 |
colsample_bylevel |
Fraction of features used per split. More randomness improves
generalization. |
0.5-1.0 |
📌 Best Practice:
- Use lower max_depth
for small
datasets.
- Increase min_child_weight
and gamma
to
prevent overfitting.
2.2 Boosting Hyperparameters
These parameters control how trees are boosted
during training.
learning_rate (eta) |
Step size shrinkage to prevent overfitting. Lower values improve
accuracy but need more trees. |
0.01-0.3 |
n_estimators |
Number of boosting rounds (trees). More trees capture more
patterns. |
50-500 |
subsample |
Fraction of training samples used per tree. Reduces
overfitting. |
0.5-1.0 |
📌 Best Practice:
- Lower learning_rate
(e.g.,
0.1
) and increase
n_estimators
(e.g., 100+
) for better
accuracy.
- Use lower subsample
(e.g.,
0.8
) to introduce randomness and improve
generalization.
2.3 Regularization Hyperparameters
These parameters prevent overfitting by penalizing
complex models.
lambda |
L2 regularization (Ridge regression) to prevent large weights. |
0-10 |
alpha |
L1 regularization (Lasso regression) for feature selection. |
0-5 |
📌 Best Practice:
- Increase lambda
and alpha
for noisy
datasets.
- Use alpha > 0
for automatic feature
selection.
2.4 Computational Hyperparameters
These parameters optimize memory and speed.
tree_method |
Algorithm for training trees. hist speeds up training
on large datasets. |
auto , hist , gpu_hist |
gpu_id |
Use GPU for acceleration. |
0 (default GPU) |
📌 Best Practice:
- Use tree_method=hist
for large
datasets.
- Set gpu_id=0
to enable GPU
acceleration.
3. Implementing Hyperparameter Tuning in
XGBoost
3.1 Setting Default Hyperparameters
import xgboost as xgb
# Define default parameters
params = {
'objective': 'reg:squarederror',
'learning_rate': 0.1,
'max_depth': 6,
'min_child_weight': 1,
'gamma': 0,
'colsample_bytree': 0.8,
'subsample': 0.8,
'lambda': 1,
'alpha': 0
}
# Train model with default parameters
model = xgb.XGBRegressor(**params, n_estimators=100)
3.2 Automating Hyperparameter Tuning
Instead of manually trying different values, we can use Grid
Search and Random Search.
Grid Search for Hyperparameter Optimization
from sklearn.model_selection import GridSearchCV
param_grid = {
'max_depth': [3, 5, 7],
'learning_rate': [0.01, 0.1, 0.2],
'n_estimators': [50, 100, 200],
'subsample': [0.8, 1.0],
'colsample_bytree': [0.8, 1.0]
}
grid_search = GridSearchCV(xgb.XGBRegressor(objective='reg:squarederror'), param_grid, scoring='neg_mean_squared_error', cv=5, verbose=1)
grid_search.fit(X_train, y_train)
print("Best Parameters:", grid_search.best_params_)
3.3 Random Search for Faster Hyperparameter
Tuning
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import uniform
param_dist = {
'learning_rate': uniform(0.01, 0.2),
'max_depth': [3, 5, 7],
'n_estimators': [50, 100, 200],
'subsample': uniform(0.5, 0.5),
'colsample_bytree': uniform(0.5, 0.5)
}
random_search = RandomizedSearchCV(xgb.XGBRegressor(objective='reg:squarederror'), param_distributions=param_dist, n_iter=25, scoring='neg_mean_squared_error', cv=5, verbose=1)
random_search.fit(X_train, y_train)
print("Best Parameters from Random Search:", random_search.best_params_)
4. Best Practices for Hyperparameter Tuning
✔ Start with Random Search to narrow down the best
ranges.
✔ Use Grid Search for fine-tuning once you find a good
range.
✔ Use early stopping to find the optimal number of
trees.
✔ Consider Bayesian Optimization for efficient
tuning.
XGBoost Demo 1 & 2 Study Guide
These demos walk through real-world applications of
XGBoost, covering: - Demo 1: XGBoost
for classification (handwritten digit recognition). -
Demo 2: XGBoost for regression
(housing price prediction).
XGBoost Demo 1: Handwritten Digit
Classification
1. Overview
In this demo, we use XGBoost for classification on
the Digits dataset from sklearn
. The
dataset consists of 8×8 pixel grayscale images of handwritten
digits (0-9).
2. Steps for Implementing XGBoost for
Classification
Step 1: Import Required Libraries
import numpy as np
import xgboost as xgb
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
Step 2: Load and Visualize the Data
# Load Digits dataset
digits = datasets.load_digits()
X, y = digits.data, digits.target # Features (pixel values) and labels
# Show an example digit
plt.gray()
plt.matshow(digits.images[0]) # Show first image
plt.show()
print("Label:", digits.target[0]) # Print corresponding label
📌 Explanation: - The dataset contains 8×8
pixel grayscale images of handwritten digits (0-9). - Each
image is flattened into a 64-dimensional vector for
processing.
Step 3: Split Data into Training and Test Sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
📌 Why split?
- 80% of the data is used for training.
- 20% is used for testing the model’s accuracy on unseen
data.
Step 5: Define Model Parameters
params = {
'objective': 'multi:softmax', # Multi-class classification
'num_class': 10, # 10 classes (digits 0-9)
'eval_metric': 'mlogloss', # Multi-class log loss
'max_depth': 5,
'learning_rate': 0.1,
'n_estimators': 100
}
Step 6: Train the XGBoost Model
model = xgb.train(params, dtrain, num_boost_round=100)
Step 7: Make Predictions and Evaluate
y_pred = model.predict(dtest)
# Calculate accuracy
accuracy = accuracy_score(y_test, y_pred)
print(f"XGBoost Classification Accuracy: {accuracy:.4f}")
📌 What happens here? - The trained model
predicts the digit labels on the test set. - We calculate
accuracy to evaluate the model’s performance.
✅ Expected Result: Around 97-99%
accuracy on the test set.
XGBoost Demo 2: Regression on Boston Housing
Data
1. Overview
This demo applies XGBoost for regression using the
Boston Housing Dataset, predicting housing
prices based on features like crime rate, number of rooms, and
location.
2. Steps for Implementing XGBoost for
Regression
Step 1: Import Required Libraries
import numpy as np
import xgboost as xgb
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
Step 2: Load and Explore the Data
# Load dataset
boston = load_boston()
X, y = boston.data, boston.target # Features and target (house prices)
# Print feature names
print("Feature Names:", boston.feature_names)
📌 Dataset Details: - The dataset contains
13 features related to housing. - The target variable
y represents house prices.
Step 3: Split Data into Training and Test Sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
📌 Why split?
- 80% of the data is used for training.
- 20% is used for evaluating predictions on unseen
data.
Step 5: Define Model Parameters
params = {
'objective': 'reg:squarederror', # Regression task
'eval_metric': 'rmse', # Root Mean Squared Error
'max_depth': 4,
'learning_rate': 0.1,
'n_estimators': 100
}
Step 6: Train the XGBoost Model
model = xgb.train(params, dtrain, num_boost_round=100)
Step 7: Make Predictions and Evaluate
y_pred = model.predict(dtest)
# Calculate RMSE
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"XGBoost Regression RMSE: {rmse:.4f}")
📌 What happens here? - The trained model
predicts house prices for the test set. - RMSE (Root
Mean Squared Error) measures prediction error.
✅ Expected Result: RMSE between
3-5, depending on dataset splits.
3. Key Takeaways from Both Demos
XGBoost for Classification
✔ Works well with high-dimensional structured data
(like images).
✔ Handles multi-class classification efficiently.
✔ Fast training time compared to other boosting
methods.
XGBoost for Regression
✔ Suitable for continuous value predictions.
✔ Handles complex, nonlinear relationships well.
✔ Reduces overfitting via regularization and
tree pruning.
4. Summary
✔ Demo 1: XGBoost classification
for handwritten digit recognition.
✔ Demo 2: XGBoost regression for
housing price prediction.
✔ Uses DMatrix for efficient computation.
✔ Uses tunable hyperparameters like
learning_rate
, max_depth
, and
n_estimators
.
✔ Evaluation Metrics: -
Classification: accuracy_score
-
Regression: RMSE
Advanced XGBoost Technique: Feature Interaction
Constraints
One powerful yet less commonly used feature in XGBoost is
Feature Interaction Constraints, which limits
which features can be combined in decision trees. This is
particularly useful when: - You want to enforce domain
knowledge (e.g., restricting interactions between certain
variables). - You want to reduce overfitting by
limiting tree complexity. - You want to improve
interpretability by ensuring logical feature
relationships.
1. What Are Feature Interaction Constraints?
Normally, XGBoost allows any feature to interact
with any other feature in a tree. However, Feature Interaction
Constraints allow you to specify groups of features
that can interact, preventing certain combinations.
📌 Example Use Case:
- In a medical dataset, we may not want age to interact
with genetic markers. - In real estate, we may want to
limit interactions between location-based features and
financial attributes.
2. How to Define Feature Interaction
Constraints
The constraints are passed as a list of lists. Each
sublist defines a group of features that can
interact.
Example:
interaction_constraints = [
[0, 1, 2], # Features 0, 1, and 2 can interact.
[3, 4], # Features 3 and 4 can interact.
[5, 6, 7] # Features 5, 6, and 7 can interact.
]
In this case: - Features in group 1 can interact
among themselves but not with those in group 2
or 3.
3. Implementing Feature Interaction Constraints in
XGBoost
Step 1: Import Required Libraries
import xgboost as xgb
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
import numpy as np
Step 2: Load and Prepare the Data
# Load dataset
boston = load_boston()
X, y = boston.data, boston.target
feature_names = boston.feature_names # Column names
# Split data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Convert data into DMatrix format
dtrain = xgb.DMatrix(X_train, label=y_train, feature_names=feature_names)
dtest = xgb.DMatrix(X_test, label=y_test, feature_names=feature_names)
Step 3: Define Feature Interaction Constraints
We assume: - Features 0-3 (e.g., crime rate,
location-based features) can interact. - Features 4-7
(e.g., economic factors) can interact separately. - Features
8-12 (e.g., structural home characteristics) are
another independent group.
interaction_constraints = [
[0, 1, 2, 3], # First group
[4, 5, 6, 7], # Second group
[8, 9, 10, 11, 12] # Third group
]
Step 4: Define and Train the XGBoost Model
params = {
'objective': 'reg:squarederror',
'eval_metric': 'rmse',
'max_depth': 4,
'learning_rate': 0.1,
'n_estimators': 100,
'interaction_constraints': interaction_constraints
}
# Train model
model = xgb.train(params, dtrain, num_boost_round=100)
Step 5: Make Predictions and Evaluate
from sklearn.metrics import mean_squared_error
# Make predictions
y_pred = model.predict(dtest)
# Calculate RMSE
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"XGBoost with Feature Interaction Constraints RMSE: {rmse:.4f}")
📌 Expected Results:
- RMSE should be similar or slightly better compared to
a model without constraints. - The model is less prone to
overfitting, as it follows domain knowledge.
4. Benefits of Feature Interaction Constraints
✔ Better Generalization: Reduces overfitting by
limiting unnecessary interactions.
✔ More Interpretability: Enforces logical relationships
between features.
✔ Domain-Specific Control: Allows integration of expert
knowledge into the model.
✅ Use Feature Interaction Constraints when: -
Certain features shouldn’t be combined for
interpretability. - You want to reduce complexity in
large feature spaces.
Grid Search and Random Search for Hyperparameter Tuning in
XGBoost
Hyperparameter tuning is essential for improving model performance.
Grid Search and Random Search are two common methods
for optimizing XGBoost models.
1. Grid Search vs. Random Search
Grid Search |
Tests all possible combinations of hyperparameters. |
Exhaustive, guarantees finding the best combination. |
Computationally expensive, slow for large parameter grids. |
Random Search |
Randomly selects a subset of hyperparameter combinations. |
Faster than Grid Search, finds good hyperparameters quickly. |
Might miss the best hyperparameters since not all are tested. |
📌 Best Practice: Use Random Search
first to find a good range of values, then Grid
Search for fine-tuning.
2. Grid Search in XGBoost
Grid Search systematically searches through all
possible combinations of hyperparameters to find the best-performing
model.
2.1 Implementing Grid Search
Step 1: Import Required Libraries
from sklearn.model_selection import GridSearchCV
import xgboost as xgb
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
Step 2: Load and Prepare Data
# Load dataset
boston = load_boston()
X, y = boston.data, boston.target
# Train-test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
Step 3: Define the Hyperparameter Grid
# Define XGBoost model
xgb_model = xgb.XGBRegressor(objective='reg:squarederror')
# Define hyperparameter grid
param_grid = {
'learning_rate': [0.01, 0.1, 0.2],
'max_depth': [3, 5, 7],
'n_estimators': [50, 100, 200],
'subsample': [0.8, 1.0],
'colsample_bytree': [0.8, 1.0]
}
3. Random Search in XGBoost
Random Search randomly selects hyperparameter
combinations instead of testing all possibilities, making it more
efficient for large parameter spaces.
3.1 Implementing Random Search
Step 1: Import Required Libraries
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import uniform
import numpy as np
Step 2: Define the Hyperparameter Distribution
Instead of defining a fixed set of values, Random Search
samples from continuous distributions.
param_dist = {
'learning_rate': uniform(0.01, 0.3), # Continuous range from 0.01 to 0.31
'max_depth': [3, 5, 7, 9], # Discrete values
'n_estimators': [50, 100, 200], # Discrete values
'subsample': uniform(0.5, 0.5), # From 0.5 to 1.0
'colsample_bytree': uniform(0.5, 0.5) # From 0.5 to 1.0
}
4. Grid Search vs. Random Search: Which One to
Use?
Small dataset & few hyperparameters |
✅ Yes |
❌ No |
Large dataset with many hyperparameters |
❌ No |
✅ Yes |
Need to find best hyperparameters quickly |
❌ No |
✅ Yes |
Want to guarantee best parameters |
✅ Yes |
❌ No |
Best Practice: Use Random Search First
- Start with Random Search to quickly find a good
range of values.
- Refine with Grid Search on a smaller hyperparameter
space.
5. Summary
✔ Grid Search: Exhaustive, best for small datasets,
guarantees optimal parameters.
✔ Random Search: Faster, better for large datasets,
good approximation of optimal parameters.
✔ Best practice: Use Random Search
first, then fine-tune with Grid Search.
# Final Recap & Summary of XGBoost Study
Guide
1. Summary of Key Concepts Covered
Boosting & How It Works
✔ Boosting sequentially improves weak models,
correcting previous errors.
✔ It reduces bias while maintaining low variance.
✔ Popular Boosting algorithms: AdaBoost, Gradient Boosting, and
XGBoost.
XGBoost Fundamentals
✔ XGBoost is an optimized form of Gradient Boosting
with regularization and parallel computation.
✔ It efficiently handles large datasets, missing values, and
feature selection.
✔ It has built-in L1 (Lasso) & L2 (Ridge)
regularization to prevent overfitting.
Hyperparameter Tuning
✔ Key hyperparameters: learning_rate
,
max_depth
, n_estimators
,
subsample
, colsample_bytree
,
gamma
, lambda
, and alpha
.
✔ Grid Search is exhaustive and guarantees the best
parameters but is slow for large datasets.
✔ Random Search finds a good approximation
faster but doesn’t guarantee the optimal
parameters.
XGBoost Demos
✔ Demo 1 (Classification): Handwritten digit
classification using multi:softmax
.
✔ Demo 2 (Regression): Predicting housing prices with
reg:squarederror
.
Advanced XGBoost Techniques
✔ SHAP (SHapley Additive Explanations) helps
understand model predictions at a feature level.
✔ Feature Interaction Constraints control which
features interact in trees, reducing overfitting and improving
interpretability.
✔ Early Stopping prevents overfitting by stopping
training when validation error stops improving.
✔ Custom Loss Functions allow domain-specific
objectives (e.g., Huber loss for robust
regression).
2. Three Key Takeaways
1. Feature Importance Matters
Understanding which features drive predictions (via
SHAP or plot_importance()
) helps in: - Feature
selection
- Model debugging
- Explaining predictions in high-stakes applications
2. Hyperparameter Tuning is Critical
Choosing the right combination of: - Tree complexity
(max_depth
, min_child_weight
) -
Boosting settings (learning_rate
,
n_estimators
) - Regularization
(lambda
, alpha
)
can dramatically improve model performance.
3. Simplicity Beats Complexity
More complex models don’t always perform
better.
- Feature constraints and early
stopping help control model complexity.
- Smaller trees with meaningful splits generalize
better.
- Grid Search isn’t always needed—Random Search or Bayesian
Optimization can be more efficient.
3. Three Thought-Provoking Questions
2. Are You Tuning the Right Hyperparameters or Just
Experimenting?
- Have you checked feature importance first before
tuning?
- Do you need a complex hyperparameter tuning strategy, or would
early stopping + default settings work
just as well?
3. Can You Reduce Training Time Without Losing
Accuracy?
- Would GPU acceleration
(
tree_method='gpu_hist'
) speed up training?
- Would lowering
n_estimators
and
increasing learning_rate
give the same accuracy
faster?
## Practical Real-World Dataset Analysis Using XGBoost +
Bayesian Optimization for Hyperparameter Tuning We will
combine both topics into a practical real-world
dataset analysis using XGBoost while optimizing
hyperparameters with Bayesian Optimization. |
2. Step-by-Step Implementation
Step 1: Install Required Packages
pip install xgboost bayesian-optimization scikit-learn pandas numpy matplotlib
Step 2: Import Libraries
import numpy as np
import pandas as pd
import xgboost as xgb
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from bayes_opt import BayesianOptimization
Step 3: Load and Prepare Data
# Load the California housing dataset
data = fetch_california_housing()
X, y = data.data, data.target
feature_names = data.feature_names # Column names
# Split into train & test sets (80/20)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Convert to XGBoost's DMatrix format
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)
Step 5: Define Bayesian Optimization for Hyperparameter
Tuning
Instead of using Grid Search or Random Search, we
will use Bayesian Optimization.
What is Bayesian Optimization?
- Instead of testing all combinations (Grid Search) or random
combinations (Random Search), Bayesian Optimization learns from
previous trials to find better hyperparameters
faster.
- It balances exploration & exploitation:
- Exploration: Tries new
combinations to improve the model.
- Exploitation: Focuses on already promising
values to refine performance.
Step 6: Define the Objective Function for Bayesian
Optimization
Bayesian Optimization needs an objective function
that returns a performance score (negative RMSE in our
case).
# Define the function to optimize
def xgb_evaluate(learning_rate, max_depth, subsample, colsample_bytree):
params = {
'objective': 'reg:squarederror',
'learning_rate': learning_rate,
'max_depth': int(max_depth), # Must be integer
'subsample': subsample,
'colsample_bytree': colsample_bytree,
'eval_metric': 'rmse'
}
# Perform cross-validation to evaluate performance
scores = xgb.cv(params, dtrain, num_boost_round=100, nfold=3, metrics="rmse", early_stopping_rounds=10)
return -scores["test-rmse-mean"].min() # We minimize RMSE, so return its negative value
Step 7: Set Up Bayesian Optimization
Now, we define search boundaries for
hyperparameters.
# Define search space for hyperparameters
pbounds = {
'learning_rate': (0.01, 0.3),
'max_depth': (3, 10),
'subsample': (0.5, 1.0),
'colsample_bytree': (0.5, 1.0)
}
# Initialize Bayesian Optimization
optimizer = BayesianOptimization(
f=xgb_evaluate, # Objective function
pbounds=pbounds, # Search space
random_state=42
)
Step 8: Run Bayesian Optimization
We now optimize hyperparameters over 20
iterations.
optimizer.maximize(init_points=5, n_iter=20) # 5 initial random points, 20 optimization steps
# Print the best parameters found
best_params = optimizer.max["params"]
best_params["max_depth"] = int(best_params["max_depth"]) # Ensure integer value for max_depth
print("Best Parameters Found:", best_params)
Step 9: Train XGBoost with Optimized
Hyperparameters
Once we find the best parameters, we train XGBoost with them.
# Train final model with optimized parameters
optimized_model = xgb.train(best_params, dtrain, num_boost_round=100)
# Make predictions
y_pred_opt = optimized_model.predict(dtest)
# Compute RMSE
optimized_rmse = np.sqrt(mean_squared_error(y_test, y_pred_opt))
print(f"Optimized RMSE: {optimized_rmse:.4f}")
Step 10: Compare Baseline vs. Optimized Model
print(f"Baseline RMSE: {baseline_rmse:.4f}")
print(f"Optimized RMSE: {optimized_rmse:.4f}")
improvement = (baseline_rmse - optimized_rmse) / baseline_rmse * 100
print(f"Performance Improvement: {improvement:.2f}%")
📌 Expected Result:
- Optimized RMSE should be lower than the baseline
RMSE. - The model should generalize better to unseen
data.
3. Summary & Key Takeaways
Key Takeaways
✔ Bayesian Optimization finds better hyperparameters
faster than Grid Search or Random Search.
✔ Performance improvement: Tuning hyperparameters can
significantly lower RMSE and improve predictions.
✔ XGBoost + Bayesian Optimization is efficient for real-world
datasets (e.g., house price prediction).
4. Thought-Provoking Questions
1. Can Bayesian Optimization Replace Grid Search
Entirely?
- Would Bayesian Optimization always be faster and more
efficient, or are there cases where Grid Search might still be
useful?
2. How Do Feature Engineering and Hyperparameter Tuning
Compare?
- If a model’s performance improves only slightly
after tuning, should we focus more on feature engineering
instead?
3. What Are the Best Stopping Criteria for Bayesian
Optimization?
- Should we limit the number of iterations to prevent
overfitting?
- How do we decide when we’ve found the best
hyperparameters?
LS0tDQp0aXRsZTogIlFUVyBNb2R1bGUgOCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoqKlNlY3Rpb24gMTogQm9vc3RpbmcqKiBmb3IgdGhlIHN0dWR5IGd1aWRlLg0KDQotLS0NCg0KIyMgKipCb29zdGluZyBTdHVkeSBHdWlkZSoqDQoNCiMjIyAqKjEuIEludHJvZHVjdGlvbiB0byBCb29zdGluZyoqDQpCb29zdGluZyBpcyBhIG1hY2hpbmUgbGVhcm5pbmcgZW5zZW1ibGUgdGVjaG5pcXVlIHRoYXQgY29udmVydHMgd2VhayBsZWFybmVycyBpbnRvIGEgc3Ryb25nIGxlYXJuZXIgYnkgaXRlcmF0aXZlbHkgYWRqdXN0aW5nIHRoZWlyIHdlaWdodHMgYmFzZWQgb24gZXJyb3JzIGZyb20gcHJldmlvdXMgbW9kZWxzLiBVbmxpa2UgYmFnZ2luZywgd2hpY2ggd29ya3Mgd2l0aCBpbmRlcGVuZGVudCBtb2RlbHMsIGJvb3N0aW5nIG1vZGVscyBhcmUgdHJhaW5lZCBzZXF1ZW50aWFsbHkuDQoNCioqS2V5IENvbmNlcHQ6KioNCi0gQm9vc3RpbmcgZm9jdXNlcyBvbiAqKnJlZHVjaW5nIGJpYXMqKiBieSB0cmFpbmluZyB3ZWFrIG1vZGVscyBzZXF1ZW50aWFsbHksIHdoZXJlIGVhY2ggbW9kZWwgY29ycmVjdHMgdGhlIGVycm9ycyBvZiB0aGUgcHJldmlvdXMgb25lLg0KLSBJdCB1c2VzIHdlaWdodGVkIHRyYWluaW5nLCBtZWFuaW5nIG1pc2NsYXNzaWZpZWQgc2FtcGxlcyBnZXQgaGlnaGVyIHdlaWdodHMgaW4gdGhlIG5leHQgaXRlcmF0aW9uLg0KDQoqKlN0ZXBzIGluIEJvb3N0aW5nOioqDQoxLiBUcmFpbiBhIHdlYWsgbW9kZWwgKGUuZy4sIGEgc2ltcGxlIGRlY2lzaW9uIHRyZWUpLg0KMi4gTWFrZSBwcmVkaWN0aW9ucyBhbmQgY29tcHV0ZSByZXNpZHVhbHMgKGVycm9ycykuDQozLiBBZGp1c3Qgc2FtcGxlIHdlaWdodHM6IEdpdmUgbW9yZSBpbXBvcnRhbmNlIHRvIG1pc2NsYXNzaWZpZWQgZGF0YS4NCjQuIFRyYWluIHRoZSBuZXh0IHdlYWsgbW9kZWwgb24gYWRqdXN0ZWQgZGF0YS4NCjUuIFJlcGVhdCB1bnRpbCBzdG9wcGluZyBjcml0ZXJpYSAoZS5nLiwgZXJyb3IgY29udmVyZ2VuY2UpIGlzIG1ldC4NCg0KLS0tDQoNCiMjIyAqKjIuIEhvdyBCb29zdGluZyBXb3JrcyoqDQpCb29zdGluZyBpcyBhbiBpdGVyYXRpdmUgcHJvY2VzczoNCi0gSXQgc3RhcnRzIHdpdGggYW4gaW5pdGlhbCBtb2RlbCB0aGF0IG1ha2VzIHByZWRpY3Rpb25zLg0KLSBFcnJvcnMgKHJlc2lkdWFscykgZnJvbSB0aGlzIG1vZGVsIGJlY29tZSB0aGUgbmV3IHRhcmdldC4NCi0gVGhlIHByb2Nlc3MgcmVwZWF0cywgYW5kIG5ldyBtb2RlbHMgY29ycmVjdCBwcmlvciBtaXN0YWtlcy4NCi0gVGhlIGZpbmFsIHByZWRpY3Rpb24gaXMgYSB3ZWlnaHRlZCBzdW0gb2YgYWxsIHdlYWsgbGVhcm5lcnMuDQoNCioqTWF0aGVtYXRpY2FsIFJlcHJlc2VudGF0aW9uOioqDQpFYWNoIG5ldyBtb2RlbCBsZWFybnMgZnJvbSB0aGUgcmVzaWR1YWxzOg0KLSBMZXQgXCggRih4KSBcKSBiZSB0aGUgcHJlZGljdGlvbiBhdCBzdGVwIFwoIHQgXCkuDQotIFRoZSBuZXcgbW9kZWwgXCggaF90KHgpIFwpIGZpdHMgdGhlIHJlc2lkdWFscyBcKCByX3QgXCkuDQotIFRoZSB1cGRhdGVkIG1vZGVsIGlzOg0KICBcWw0KICBGX3t0KzF9KHgpID0gRl90KHgpICsgXGV0YSBoX3QoeCkNCiAgXF0NCiAgd2hlcmUgXCggXGV0YSBcKSBpcyB0aGUgbGVhcm5pbmcgcmF0ZS4NCg0KKipDb21wYXJpc29uIHdpdGggQmFnZ2luZzoqKg0KfCBGZWF0dXJlICB8IEJvb3N0aW5nIHwgQmFnZ2luZyB8DQp8LS0tLS0tLS0tLXwtLS0tLS0tLS18LS0tLS0tLS0tfA0KfCBPcmRlciBvZiB0cmFpbmluZyB8IFNlcXVlbnRpYWwgfCBQYXJhbGxlbCB8DQp8IEZvY3VzIHwgUmVkdWNpbmcgYmlhcyB8IFJlZHVjaW5nIHZhcmlhbmNlIHwNCnwgTW9kZWwgZGVwZW5kZW5jeSB8IE5leHQgbW9kZWwgY29ycmVjdHMgcHJpb3IgZXJyb3JzIHwgTW9kZWxzIGFyZSBpbmRlcGVuZGVudCB8DQp8IEV4YW1wbGUgYWxnb3JpdGhtcyB8IEFkYUJvb3N0LCBHcmFkaWVudCBCb29zdGluZyB8IFJhbmRvbSBGb3Jlc3QgfA0KDQotLS0NCg0KIyMjICoqMy4gQWR2YW50YWdlcyBhbmQgRGlzYWR2YW50YWdlcyBvZiBCb29zdGluZyoqDQoqKlByb3M6KioNCuKclCBXb3JrcyB3ZWxsIHdpdGggd2VhayBsZWFybmVycy4gIA0K4pyUIFJlZHVjZXMgYmlhcyBhbmQgdmFyaWFuY2UuICANCuKclCBIYW5kbGVzIGNvbXBsZXggcmVsYXRpb25zaGlwcyBpbiBkYXRhLiAgDQrinJQgVXN1YWxseSBwcm92aWRlcyBiZXR0ZXIgYWNjdXJhY3kgdGhhbiBiYWdnaW5nIG1ldGhvZHMuDQoNCioqQ29uczoqKg0K4pyWIENhbiBvdmVyZml0IHdpdGggdG9vIG1hbnkgaXRlcmF0aW9ucy4gIA0K4pyWIENvbXB1dGF0aW9uYWxseSBleHBlbnNpdmUuICANCuKcliBTZW5zaXRpdmUgdG8gbm9pc2UgaW4gdGhlIGRhdGFzZXQuICANCg0KLS0tDQoNCiMjIyAqKjQuIENvbW1vbiBCb29zdGluZyBBbGdvcml0aG1zKioNCjEuICoqQWRhQm9vc3QgKEFkYXB0aXZlIEJvb3N0aW5nKSoqICANCiAgIC0gQXNzaWducyBoaWdoZXIgd2VpZ2h0cyB0byBtaXNjbGFzc2lmaWVkIHBvaW50cy4NCiAgIC0gVXNlcyBhbiBleHBvbmVudGlhbCBsb3NzIGZ1bmN0aW9uLg0KICAgLSBXb3JrcyB3ZWxsIHdpdGggc21hbGwgZGVjaXNpb24gdHJlZXMuDQoNCjIuICoqR3JhZGllbnQgQm9vc3RpbmcgKEdCTSAtIEdyYWRpZW50IEJvb3N0aW5nIE1hY2hpbmVzKSoqICANCiAgIC0gVXNlcyBncmFkaWVudCBkZXNjZW50IHRvIG1pbmltaXplIGEgbG9zcyBmdW5jdGlvbi4NCiAgIC0gRWFjaCBtb2RlbCBjb3JyZWN0cyB0aGUgcmVzaWR1YWxzIG9mIHRoZSBwcmV2aW91cyBtb2RlbC4NCiAgIC0gQWxsb3dzIGN1c3RvbSBsb3NzIGZ1bmN0aW9ucyAoZS5nLiwgbWVhbiBzcXVhcmVkIGVycm9yLCBsb2cgbG9zcykuDQoNCjMuICoqWEdCb29zdCAoRXh0cmVtZSBHcmFkaWVudCBCb29zdGluZykqKiAgDQogICAtIE9wdGltaXplZCBpbXBsZW1lbnRhdGlvbiBvZiBHQk0uDQogICAtIFVzZXMgcmVndWxhcml6YXRpb24gKEwxLCBMMikgdG8gcHJldmVudCBvdmVyZml0dGluZy4NCiAgIC0gU3VwcG9ydHMgcGFyYWxsZWwgcHJvY2Vzc2luZy4NCg0KLS0tDQoNCiMjIyAqKjUuIEltcGxlbWVudGF0aW9uIEV4YW1wbGUgKFB5dGhvbikqKg0KIyMjIyAqKkJhc2ljIEJvb3N0aW5nIHdpdGggU2Npa2l0LWxlYXJuKioNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLmVuc2VtYmxlIGltcG9ydCBBZGFCb29zdENsYXNzaWZpZXINCmZyb20gc2tsZWFybi50cmVlIGltcG9ydCBEZWNpc2lvblRyZWVDbGFzc2lmaWVyDQpmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IG1ha2VfY2xhc3NpZmljYXRpb24NCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQNCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBhY2N1cmFjeV9zY29yZQ0KDQojIENyZWF0ZSBkYXRhc2V0DQpYLCB5ID0gbWFrZV9jbGFzc2lmaWNhdGlvbihuX3NhbXBsZXM9MTAwMCwgbl9mZWF0dXJlcz0yMCwgcmFuZG9tX3N0YXRlPTQyKQ0KDQojIFNwbGl0IGRhdGENClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4yLCByYW5kb21fc3RhdGU9NDIpDQoNCiMgQ3JlYXRlIGFuZCB0cmFpbiBBZGFCb29zdCBtb2RlbA0KYmFzZV9tb2RlbCA9IERlY2lzaW9uVHJlZUNsYXNzaWZpZXIobWF4X2RlcHRoPTEpICAjIFdlYWsgbGVhcm5lcg0KbW9kZWwgPSBBZGFCb29zdENsYXNzaWZpZXIoYmFzZV9lc3RpbWF0b3I9YmFzZV9tb2RlbCwgbl9lc3RpbWF0b3JzPTUwLCBsZWFybmluZ19yYXRlPTAuMSwgcmFuZG9tX3N0YXRlPTQyKQ0KbW9kZWwuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCiMgUHJlZGljdGlvbnMNCnlfcHJlZCA9IG1vZGVsLnByZWRpY3QoWF90ZXN0KQ0KcHJpbnQoIkFjY3VyYWN5OiIsIGFjY3VyYWN5X3Njb3JlKHlfdGVzdCwgeV9wcmVkKSkNCmBgYA0KDQotLS0NCg0KIyMgKipYR0Jvb3N0IFN0dWR5IEd1aWRlKioNCg0KIyMjICoqMS4gSW50cm9kdWN0aW9uIHRvIFhHQm9vc3QqKg0KKipYR0Jvb3N0KiogKEV4dHJlbWUgR3JhZGllbnQgQm9vc3RpbmcpIGlzIGFuIG9wdGltaXplZCBib29zdGluZyBhbGdvcml0aG0gdGhhdCBpbXByb3ZlcyB1cG9uIHRyYWRpdGlvbmFsIGJvb3N0aW5nIG1ldGhvZHMuIEl0IGlzIHdpZGVseSB1c2VkIGluIG1hY2hpbmUgbGVhcm5pbmcgY29tcGV0aXRpb25zIGFuZCBpbmR1c3RyeSBhcHBsaWNhdGlvbnMgZHVlIHRvIGl0cyBlZmZpY2llbmN5IGFuZCBoaWdoIGFjY3VyYWN5Lg0KDQoqKldoeSBYR0Jvb3N0PyoqDQrinJQgRmFzdGVyIGFuZCBtb3JlIHNjYWxhYmxlIHRoYW4gdHJhZGl0aW9uYWwgR3JhZGllbnQgQm9vc3RpbmcgIA0K4pyUIFN1cHBvcnRzICoqcGFyYWxsZWwgY29tcHV0YXRpb24qKiAodW5saWtlIHRyYWRpdGlvbmFsIGJvb3N0aW5nKSAgDQrinJQgSW1wbGVtZW50cyAqKnJlZ3VsYXJpemF0aW9uKiogKEwxIGFuZCBMMikgdG8gcHJldmVudCBvdmVyZml0dGluZyAgDQrinJQgQ2FuIGhhbmRsZSAqKm1pc3NpbmcgdmFsdWVzIGF1dG9tYXRpY2FsbHkqKiAgDQrinJQgV29ya3Mgd2VsbCB3aXRoIGxhcmdlIGRhdGFzZXRzICANCg0KLS0tDQoNCiMjIyAqKjIuIEhvdyBYR0Jvb3N0IFdvcmtzKioNClhHQm9vc3QgaXMgYW4gKiplbnNlbWJsZSBvZiBkZWNpc2lvbiB0cmVlcyoqLCB0cmFpbmVkIHNlcXVlbnRpYWxseSB0byBtaW5pbWl6ZSByZXNpZHVhbCBlcnJvcnMuIEhvd2V2ZXIsIGl0IG9wdGltaXplcyB0aGUgYm9vc3RpbmcgcHJvY2VzcyB1c2luZyB0d28ga2V5IHRlY2huaXF1ZXM6DQoxLiAqKkdyYWRpZW50IEJvb3N0aW5nKiog4oCTIFVzZXMgZ3JhZGllbnQgZGVzY2VudCB0byBtaW5pbWl6ZSB0aGUgbG9zcyBmdW5jdGlvbi4NCjIuICoqUmVndWxhcml6YXRpb24qKiDigJMgQXBwbGllcyBMMSAoTGFzc28pIGFuZCBMMiAoUmlkZ2UpIHJlZ3VsYXJpemF0aW9uIHRvIHByZXZlbnQgb3ZlcmZpdHRpbmcuDQoNCi0tLQ0KDQojIyMgKiozLiBUaGUgWEdCb29zdCBBbGdvcml0aG0qKg0KMS4gKipJbml0aWFsaXplKiogdGhlIG1vZGVsIHdpdGggd2VhayBsZWFybmVycyAoZGVjaXNpb24gdHJlZXMpLg0KMi4gKipDb21wdXRlIFJlc2lkdWFscyoqIOKAkyBDYWxjdWxhdGUgZXJyb3JzIGZyb20gdGhlIHByZXZpb3VzIGl0ZXJhdGlvbi4NCjMuICoqRml0IGEgTmV3IFRyZWUqKiDigJMgVHJhaW4gdGhlIG5leHQgZGVjaXNpb24gdHJlZSBvbiByZXNpZHVhbHMuDQo0LiAqKk9wdGltaXplIHRoZSBMb3NzIEZ1bmN0aW9uKiog4oCTIFVzZXMgKipncmFkaWVudCBkZXNjZW50KiogdG8gbWluaW1pemUgZXJyb3JzLg0KNS4gKipBcHBseSBSZWd1bGFyaXphdGlvbioqIOKAkyBTaHJpbmtzIHRyZWUgY29tcGxleGl0eSB0byBhdm9pZCBvdmVyZml0dGluZy4NCjYuICoqUmVwZWF0KiogdW50aWwgc3RvcHBpbmcgY3JpdGVyaWEgYXJlIG1ldCAoZS5nLiwgbWF4IHRyZWVzLCBlYXJseSBzdG9wcGluZykuDQoNCi0tLQ0KDQojIyMgKio0LiBLZXkgRmVhdHVyZXMgb2YgWEdCb29zdCoqDQotICoqUGFyYWxsZWwgVHJlZSBMZWFybmluZyoqIOKAkyBVbmxpa2UgdHJhZGl0aW9uYWwgYm9vc3RpbmcsIFhHQm9vc3QgY2FuIGJ1aWxkIHRyZWVzIHNpbXVsdGFuZW91c2x5Lg0KLSAqKlJlZ3VsYXJpemVkIExlYXJuaW5nKiog4oCTIFhHQm9vc3QgYXBwbGllcyAqKkwxIGFuZCBMMiByZWd1bGFyaXphdGlvbioqIHRvIHByZXZlbnQgb3ZlcmZpdHRpbmcuDQotICoqV2VpZ2h0ZWQgUXVhbnRpbGUgU2tldGNoKiog4oCTIEhhbmRsZXMgbWlzc2luZyB2YWx1ZXMgYW5kIHNrZXdlZCBkYXRhIGVmZmljaWVudGx5Lg0KLSAqKlBydW5pbmcqKiDigJMgWEdCb29zdCB1c2VzICoqbWF4aW11bSBkZXB0aCBpbnN0ZWFkIG9mIHByZS1wcnVuaW5nKiogZm9yIGJldHRlciBjb250cm9sLg0KDQotLS0NCg0KIyMjICoqNS4gWEdCb29zdCB2cy4gVHJhZGl0aW9uYWwgQm9vc3RpbmcqKg0KfCBGZWF0dXJlIHwgWEdCb29zdCB8IFRyYWRpdGlvbmFsIEJvb3N0aW5nIHwNCnwtLS0tLS0tLS18LS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgUmVndWxhcml6YXRpb24gfCBMMSAmIEwyIChSaWRnZSwgTGFzc28pIHwgTm8gYnVpbHQtaW4gcmVndWxhcml6YXRpb24gfA0KfCBDb21wdXRhdGlvbiBTcGVlZCB8IEZhc3QsIG9wdGltaXplZCB8IFNsb3dlciB8DQp8IE1pc3NpbmcgVmFsdWVzIHwgSGFuZGxlZCBhdXRvbWF0aWNhbGx5IHwgTmVlZHMgaW1wdXRhdGlvbiB8DQp8IFBhcmFsbGVsaXNtIHwgWWVzIHwgTm8gfA0KfCBUcmVlIEdyb3d0aCB8IERlcHRoLXdpc2UgfCBMZWFmLXdpc2UgfA0KDQotLS0NCg0KIyMjICoqNi4gTG9zcyBGdW5jdGlvbiBpbiBYR0Jvb3N0KioNClhHQm9vc3QgbWluaW1pemVzIGEgY29tYmluYXRpb24gb2Y6DQotICoqTG9zcyBGdW5jdGlvbiAoTCkqKjogTWVhc3VyZXMgcHJlZGljdGlvbiBlcnJvciAoZS5nLiwgTWVhbiBTcXVhcmVkIEVycm9yKQ0KLSAqKlJlZ3VsYXJpemF0aW9uIFRlcm0gKM6pKSoqOiBQZW5hbGl6ZXMgY29tcGxleCBtb2RlbHMNCg0KXFsNClx0ZXh0e0xvc3N9ID0gTCh5LCBcaGF0e3l9KSArIFxPbWVnYShmKQ0KXF0NCldoZXJlOg0KLSBcKCB5IFwpIGlzIHRoZSB0cnVlIGxhYmVsDQotIFwoIFxoYXR7eX0gXCkgaXMgdGhlIHByZWRpY3RlZCB2YWx1ZQ0KLSBcKCBmIFwpIGlzIHRoZSBmdW5jdGlvbiBsZWFybmVkIGJ5IHRoZSBtb2RlbA0KLSBcKCBcT21lZ2EgXCkgaXMgdGhlIGNvbXBsZXhpdHkgcGVuYWx0eSB0ZXJtDQoNCi0tLQ0KDQojIyMgKio3LiBJbXBsZW1lbnRhdGlvbjogWEdCb29zdCBpbiBQeXRob24qKg0KIyMjIyAqKlN0ZXAgMTogSW5zdGFsbCBYR0Jvb3N0KioNCmBgYHB5dGhvbg0KcGlwIGluc3RhbGwgeGdib29zdA0KYGBgDQoNCiMjIyMgKipTdGVwIDI6IEltcG9ydCBMaWJyYXJpZXMqKg0KYGBgcHl0aG9uDQppbXBvcnQgeGdib29zdCBhcyB4Z2INCmZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgbG9hZF9ib3N0b24NCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQNCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBtZWFuX3NxdWFyZWRfZXJyb3INCmltcG9ydCBudW1weSBhcyBucA0KYGBgDQoNCiMjIyMgKipTdGVwIDM6IExvYWQgYW5kIFByZXBhcmUgRGF0YSoqDQpgYGBweXRob24NCiMgTG9hZCBkYXRhc2V0DQpib3N0b24gPSBsb2FkX2Jvc3RvbigpDQpYLCB5ID0gYm9zdG9uLmRhdGEsIGJvc3Rvbi50YXJnZXQNCg0KIyBUcmFpbi10ZXN0IHNwbGl0DQpYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoWCwgeSwgdGVzdF9zaXplPTAuMiwgcmFuZG9tX3N0YXRlPTQyKQ0KYGBgDQoNCiMjIyMgKipTdGVwIDQ6IFRyYWluIGFuIFhHQm9vc3QgTW9kZWwqKg0KYGBgcHl0aG9uDQojIENvbnZlcnQgdG8gRE1hdHJpeCBmb3JtYXQgKG9wdGltaXplZCBmb3IgWEdCb29zdCkNCnRyYWluX2RhdGEgPSB4Z2IuRE1hdHJpeChYX3RyYWluLCBsYWJlbD15X3RyYWluKQ0KdGVzdF9kYXRhID0geGdiLkRNYXRyaXgoWF90ZXN0LCBsYWJlbD15X3Rlc3QpDQoNCiMgRGVmaW5lIG1vZGVsIHBhcmFtZXRlcnMNCnBhcmFtcyA9IHsNCiAgICAnb2JqZWN0aXZlJzogJ3JlZzpzcXVhcmVkZXJyb3InLCAgIyBSZWdyZXNzaW9uIHRhc2sNCiAgICAnZXZhbF9tZXRyaWMnOiAncm1zZScsICAjIFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yDQogICAgJ21heF9kZXB0aCc6IDQsICAjIERlcHRoIG9mIHRyZWVzDQogICAgJ2xlYXJuaW5nX3JhdGUnOiAwLjEsICAjIFN0ZXAgc2l6ZQ0KICAgICduX2VzdGltYXRvcnMnOiAxMDAgICMgTnVtYmVyIG9mIGJvb3N0aW5nIHJvdW5kcw0KfQ0KDQojIFRyYWluIG1vZGVsDQptb2RlbCA9IHhnYi50cmFpbihwYXJhbXMsIHRyYWluX2RhdGEsIG51bV9ib29zdF9yb3VuZD0xMDApDQpgYGANCg0KIyMjIyAqKlN0ZXAgNTogTWFrZSBQcmVkaWN0aW9ucyBhbmQgRXZhbHVhdGUqKg0KYGBgcHl0aG9uDQojIFByZWRpY3Rpb25zDQp5X3ByZWQgPSBtb2RlbC5wcmVkaWN0KHRlc3RfZGF0YSkNCg0KIyBDYWxjdWxhdGUgUk1TRQ0Kcm1zZSA9IG5wLnNxcnQobWVhbl9zcXVhcmVkX2Vycm9yKHlfdGVzdCwgeV9wcmVkKSkNCnByaW50KGYiUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3I6IHtybXNlfSIpDQpgYGANCg0KLS0tDQoNCiMjIyAqKjguIFN1bW1hcnkqKg0KLSAqKlhHQm9vc3QqKiBpcyBhbiBvcHRpbWl6ZWQgZm9ybSBvZiBib29zdGluZyB0aGF0IGlzIGZhc3RlciBhbmQgbW9yZSBhY2N1cmF0ZS4NCi0gSXQgdXNlcyAqKmdyYWRpZW50IGJvb3N0aW5nKiogYW5kICoqcmVndWxhcml6YXRpb24qKiB0byBpbXByb3ZlIHBlcmZvcm1hbmNlLg0KLSAqKlBhcmFsbGVsaXphdGlvbioqIG1ha2VzIGl0IGhpZ2hseSBlZmZpY2llbnQgb24gbGFyZ2UgZGF0YXNldHMuDQotIEl0IGF1dG9tYXRpY2FsbHkgaGFuZGxlcyAqKm1pc3NpbmcgdmFsdWVzIGFuZCBmZWF0dXJlIHNlbGVjdGlvbioqLg0KLSBYR0Jvb3N0IHJlcXVpcmVzICoqaHlwZXJwYXJhbWV0ZXIgdHVuaW5nKiogZm9yIG9wdGltYWwgcmVzdWx0cy4NCg0KLS0tDQoNCiMjICoqWEdCb29zdCBEZW1vIDEgJiAyIFN0dWR5IEd1aWRlKioNCg0KIyMjICoqMS4gSW50cm9kdWN0aW9uIHRvIFhHQm9vc3QgRGVtb25zdHJhdGlvbioqDQpUaGVzZSB0d28gZGVtb25zdHJhdGlvbnMgd2FsayB0aHJvdWdoIHJlYWwtd29ybGQgYXBwbGljYXRpb25zIG9mICoqWEdCb29zdCoqLCBjb3ZlcmluZzoNCi0gKipEZW1vIDE6KiogVXNpbmcgWEdCb29zdCBmb3IgaGFuZHdyaXR0ZW4gZGlnaXQgY2xhc3NpZmljYXRpb24uDQotICoqRGVtbyAyOioqIEFwcGx5aW5nIFhHQm9vc3QgZm9yIHJlZ3Jlc3Npb24gdGFza3MuDQoNCkJvdGggZGVtb3MgaGlnaGxpZ2h0ICoqZGF0YSBwcmVwcm9jZXNzaW5nLCBtb2RlbCB0cmFpbmluZywgYW5kIGV2YWx1YXRpb24qKi4NCg0KLS0tDQoNCiMjICoqWEdCb29zdCBEZW1vIDE6IEhhbmR3cml0dGVuIERpZ2l0IENsYXNzaWZpY2F0aW9uKioNCiMjIyAqKjEuIE92ZXJ2aWV3KioNCkluIHRoaXMgZGVtbywgd2UgdXNlICoqWEdCb29zdCBmb3IgY2xhc3NpZmljYXRpb24qKiBvbiB0aGUgKipEaWdpdHMgZGF0YXNldCoqIGZyb20gYHNrbGVhcm5gLiBUaGUgZGF0YXNldCBjb25zaXN0cyBvZiAqKjh4OCBwaXhlbCBncmF5c2NhbGUgaW1hZ2VzIG9mIGhhbmR3cml0dGVuIGRpZ2l0cyAoMC05KSoqLg0KDQojIyMgKioyLiBTdGVwcyoqDQojIyMjICoqU3RlcCAxOiBJbXBvcnQgTmVjZXNzYXJ5IExpYnJhcmllcyoqDQpgYGBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KaW1wb3J0IHhnYm9vc3QgYXMgeGdiDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQpmcm9tIHNrbGVhcm4gaW1wb3J0IGRhdGFzZXRzDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCB0cmFpbl90ZXN0X3NwbGl0DQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgYWNjdXJhY3lfc2NvcmUNCmBgYA0KDQojIyMjICoqU3RlcCAyOiBMb2FkIGFuZCBWaXN1YWxpemUgdGhlIERhdGEqKg0KYGBgcHl0aG9uDQojIExvYWQgRGlnaXRzIGRhdGFzZXQNCmRpZ2l0cyA9IGRhdGFzZXRzLmxvYWRfZGlnaXRzKCkNClgsIHkgPSBkaWdpdHMuZGF0YSwgZGlnaXRzLnRhcmdldCAgIyBGZWF0dXJlcyAocGl4ZWwgdmFsdWVzKSBhbmQgbGFiZWxzDQoNCiMgU2hvdyBhbiBleGFtcGxlIGRpZ2l0DQpwbHQuZ3JheSgpDQpwbHQubWF0c2hvdyhkaWdpdHMuaW1hZ2VzWzBdKSAgIyBTaG93IGZpcnN0IGltYWdlDQpwbHQuc2hvdygpDQpwcmludCgiTGFiZWw6IiwgZGlnaXRzLnRhcmdldFswXSkgICMgUHJpbnQgY29ycmVzcG9uZGluZyBsYWJlbA0KYGBgDQoNCiMjIyMgKipTdGVwIDM6IFNwbGl0IERhdGEgaW50byBUcmFpbmluZyBhbmQgVGVzdCBTZXRzKioNCmBgYHB5dGhvbg0KWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KFgsIHksIHRlc3Rfc2l6ZT0wLjIsIHJhbmRvbV9zdGF0ZT00MikNCmBgYA0KDQojIyMjICoqU3RlcCA0OiBDb252ZXJ0IERhdGEgaW50byBYR0Jvb3N0J3MgRE1hdHJpeCBGb3JtYXQqKg0KYGBgcHl0aG9uDQpkdHJhaW4gPSB4Z2IuRE1hdHJpeChYX3RyYWluLCBsYWJlbD15X3RyYWluKQ0KZHRlc3QgPSB4Z2IuRE1hdHJpeChYX3Rlc3QsIGxhYmVsPXlfdGVzdCkNCmBgYA0KDQojIyMjICoqU3RlcCA1OiBEZWZpbmUgTW9kZWwgUGFyYW1ldGVycyoqDQpgYGBweXRob24NCnBhcmFtcyA9IHsNCiAgICAnb2JqZWN0aXZlJzogJ211bHRpOnNvZnRtYXgnLCAgIyBNdWx0aS1jbGFzcyBjbGFzc2lmaWNhdGlvbg0KICAgICdudW1fY2xhc3MnOiAxMCwgICMgMTAgY2xhc3NlcyAoZGlnaXRzIDAtOSkNCiAgICAnZXZhbF9tZXRyaWMnOiAnbWxvZ2xvc3MnLCAgIyBNdWx0aS1jbGFzcyBsb2cgbG9zcw0KICAgICdtYXhfZGVwdGgnOiA1LA0KICAgICdsZWFybmluZ19yYXRlJzogMC4xLA0KICAgICduX2VzdGltYXRvcnMnOiAxMDANCn0NCmBgYA0KDQojIyMjICoqU3RlcCA2OiBUcmFpbiB0aGUgWEdCb29zdCBNb2RlbCoqDQpgYGBweXRob24NCm1vZGVsID0geGdiLnRyYWluKHBhcmFtcywgZHRyYWluLCBudW1fYm9vc3Rfcm91bmQ9MTAwKQ0KYGBgDQoNCiMjIyMgKipTdGVwIDc6IE1ha2UgUHJlZGljdGlvbnMgYW5kIEV2YWx1YXRlKioNCmBgYHB5dGhvbg0KeV9wcmVkID0gbW9kZWwucHJlZGljdChkdGVzdCkNCg0KIyBDYWxjdWxhdGUgYWNjdXJhY3kNCmFjY3VyYWN5ID0gYWNjdXJhY3lfc2NvcmUoeV90ZXN0LCB5X3ByZWQpDQpwcmludChmIlhHQm9vc3QgQ2xhc3NpZmljYXRpb24gQWNjdXJhY3k6IHthY2N1cmFjeTouNGZ9IikNCmBgYA0KDQotLS0NCg0KIyMgKipYR0Jvb3N0IERlbW8gMjogUmVncmVzc2lvbiBvbiBCb3N0b24gSG91c2luZyBEYXRhKioNCiMjIyAqKjEuIE92ZXJ2aWV3KioNClRoaXMgZGVtbyBhcHBsaWVzICoqWEdCb29zdCBmb3IgcmVncmVzc2lvbioqIHVzaW5nIHRoZSAqKkJvc3RvbiBIb3VzaW5nIERhdGFzZXQqKiwgcHJlZGljdGluZyBob3VzaW5nIHByaWNlcyBiYXNlZCBvbiBmZWF0dXJlcyBsaWtlIGNyaW1lIHJhdGUsIG51bWJlciBvZiByb29tcywgYW5kIGRpc3RhbmNlIHRvIGVtcGxveW1lbnQgY2VudGVycy4NCg0KIyMjICoqMi4gU3RlcHMqKg0KIyMjIyAqKlN0ZXAgMTogSW1wb3J0IE5lY2Vzc2FyeSBMaWJyYXJpZXMqKg0KYGBgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCB4Z2Jvb3N0IGFzIHhnYg0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBsb2FkX2Jvc3Rvbg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgdHJhaW5fdGVzdF9zcGxpdA0KZnJvbSBza2xlYXJuLm1ldHJpY3MgaW1wb3J0IG1lYW5fc3F1YXJlZF9lcnJvcg0KYGBgDQoNCiMjIyMgKipTdGVwIDI6IExvYWQgYW5kIEV4cGxvcmUgdGhlIERhdGEqKg0KYGBgcHl0aG9uDQojIExvYWQgZGF0YXNldA0KYm9zdG9uID0gbG9hZF9ib3N0b24oKQ0KWCwgeSA9IGJvc3Rvbi5kYXRhLCBib3N0b24udGFyZ2V0ICAjIEZlYXR1cmVzIGFuZCB0YXJnZXQgKGhvdXNlIHByaWNlcykNCg0KIyBQcmludCBmZWF0dXJlIG5hbWVzDQpwcmludCgiRmVhdHVyZSBOYW1lczoiLCBib3N0b24uZmVhdHVyZV9uYW1lcykNCmBgYA0KDQojIyMjICoqU3RlcCAzOiBTcGxpdCBEYXRhIGludG8gVHJhaW5pbmcgYW5kIFRlc3QgU2V0cyoqDQpgYGBweXRob24NClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4yLCByYW5kb21fc3RhdGU9NDIpDQpgYGANCg0KIyMjIyAqKlN0ZXAgNDogQ29udmVydCBEYXRhIGludG8gWEdCb29zdCdzIERNYXRyaXggRm9ybWF0KioNCmBgYHB5dGhvbg0KZHRyYWluID0geGdiLkRNYXRyaXgoWF90cmFpbiwgbGFiZWw9eV90cmFpbikNCmR0ZXN0ID0geGdiLkRNYXRyaXgoWF90ZXN0LCBsYWJlbD15X3Rlc3QpDQpgYGANCg0KIyMjIyAqKlN0ZXAgNTogRGVmaW5lIE1vZGVsIFBhcmFtZXRlcnMqKg0KYGBgcHl0aG9uDQpwYXJhbXMgPSB7DQogICAgJ29iamVjdGl2ZSc6ICdyZWc6c3F1YXJlZGVycm9yJywgICMgUmVncmVzc2lvbiB0YXNrDQogICAgJ2V2YWxfbWV0cmljJzogJ3Jtc2UnLCAgIyBSb290IE1lYW4gU3F1YXJlZCBFcnJvcg0KICAgICdtYXhfZGVwdGgnOiA0LA0KICAgICdsZWFybmluZ19yYXRlJzogMC4xLA0KICAgICduX2VzdGltYXRvcnMnOiAxMDANCn0NCmBgYA0KDQojIyMjICoqU3RlcCA2OiBUcmFpbiB0aGUgWEdCb29zdCBNb2RlbCoqDQpgYGBweXRob24NCm1vZGVsID0geGdiLnRyYWluKHBhcmFtcywgZHRyYWluLCBudW1fYm9vc3Rfcm91bmQ9MTAwKQ0KYGBgDQoNCiMjIyMgKipTdGVwIDc6IE1ha2UgUHJlZGljdGlvbnMgYW5kIEV2YWx1YXRlKioNCmBgYHB5dGhvbg0KeV9wcmVkID0gbW9kZWwucHJlZGljdChkdGVzdCkNCg0KIyBDYWxjdWxhdGUgUk1TRQ0Kcm1zZSA9IG5wLnNxcnQobWVhbl9zcXVhcmVkX2Vycm9yKHlfdGVzdCwgeV9wcmVkKSkNCnByaW50KGYiWEdCb29zdCBSZWdyZXNzaW9uIFJNU0U6IHtybXNlOi40Zn0iKQ0KYGBgDQoNCi0tLQ0KDQojIyAqKjMuIEtleSBUYWtlYXdheXMgZnJvbSBCb3RoIERlbW9zKioNCiMjIyAqKlhHQm9vc3QgZm9yIENsYXNzaWZpY2F0aW9uKioNCuKclCBXb3JrcyB3ZWxsIHdpdGggKipoaWdoLWRpbWVuc2lvbmFsIHN0cnVjdHVyZWQgZGF0YSoqIChsaWtlIGltYWdlcykuICANCuKclCBIYW5kbGVzICoqbXVsdGktY2xhc3MgY2xhc3NpZmljYXRpb24gZWZmaWNpZW50bHkqKi4gIA0K4pyUICoqRmFzdCB0cmFpbmluZyB0aW1lKiogY29tcGFyZWQgdG8gb3RoZXIgYm9vc3RpbmcgbWV0aG9kcy4gIA0KDQojIyMgKipYR0Jvb3N0IGZvciBSZWdyZXNzaW9uKioNCuKclCBTdWl0YWJsZSBmb3IgKipjb250aW51b3VzIHZhbHVlIHByZWRpY3Rpb25zKiouICANCuKclCBIYW5kbGVzICoqY29tcGxleCwgbm9ubGluZWFyIHJlbGF0aW9uc2hpcHMqKiB3ZWxsLiAgDQrinJQgUmVkdWNlcyBvdmVyZml0dGluZyB2aWEgKipyZWd1bGFyaXphdGlvbioqIGFuZCAqKnRyZWUgcHJ1bmluZyoqLiAgDQoNCi0tLQ0KDQojIyAqKkFkZGl0aW9uYWwgRXhwbGFuYXRpb25zIGZvciBYR0Jvb3N0IERlbW9zKioNCg0KIyMjICoqMS4gWEdCb29zdCBEZW1vIDE6IEhhbmR3cml0dGVuIERpZ2l0IENsYXNzaWZpY2F0aW9uKioNCg0KIyMjIyAqKkRhdGEgUHJlcGFyYXRpb24gYW5kIFZpc3VhbGl6YXRpb24qKg0KLSAqKkRhdGFzZXQ6KiogVGhlIGBza2xlYXJuLmRhdGFzZXRzLmxvYWRfZGlnaXRzKClgIGZ1bmN0aW9uIGxvYWRzIGFuICoqOHg4IHBpeGVsKiogZ3JheXNjYWxlIGltYWdlIGRhdGFzZXQgb2YgaGFuZHdyaXR0ZW4gZGlnaXRzICgwLTkpLg0KLSAqKlRhcmdldCBWYXJpYWJsZToqKiBUaGUgdGFyZ2V0IHZhbHVlcyBhcmUgdGhlIGFjdHVhbCBkaWdpdCBsYWJlbHMsIHJhbmdpbmcgZnJvbSAwIHRvIDkuDQotICoqRmVhdHVyZSBSZXByZXNlbnRhdGlvbjoqKiBUaGUgZmVhdHVyZXMgYXJlIHRoZSBwaXhlbCB2YWx1ZXMgb2YgZWFjaCBpbWFnZSBmbGF0dGVuZWQgaW50byBhIDY0LWRpbWVuc2lvbmFsIHZlY3Rvci4NCg0KIyMjIyAqKlNwbGl0dGluZyBEYXRhKioNCi0gVGhlIGRhdGEgaXMgc3BsaXQgaW50byAqKnRyYWluaW5nKiogKDgwJSkgYW5kICoqdGVzdGluZyoqICgyMCUpIHVzaW5nIGB0cmFpbl90ZXN0X3NwbGl0KClgIHRvIGVuc3VyZSB0aGF0IHdlIHRyYWluIHRoZSBtb2RlbCBvbiBvbmUgc2V0IG9mIGRhdGEgYW5kIGV2YWx1YXRlIGl0IG9uIHVuc2VlbiBkYXRhLg0KDQojIyMjICoqRE1hdHJpeCBDb252ZXJzaW9uKioNCi0gKipETWF0cml4OioqIFRoaXMgaXMgYW4gaW50ZXJuYWwgZGF0YSBzdHJ1Y3R1cmUgdXNlZCBieSBYR0Jvb3N0IGZvciBlZmZpY2llbmN5LiBJdCBzdG9yZXMgYm90aCB0aGUgZmVhdHVyZXMgYW5kIGxhYmVscyBidXQgYWxzbyBhbGxvd3MgZm9yIG9wdGltaXplZCBtZW1vcnkgYW5kIGNvbXB1dGF0aW9uIGhhbmRsaW5nLg0KICBgYGBweXRob24NCiAgZHRyYWluID0geGdiLkRNYXRyaXgoWF90cmFpbiwgbGFiZWw9eV90cmFpbikNCiAgZHRlc3QgPSB4Z2IuRE1hdHJpeChYX3Rlc3QsIGxhYmVsPXlfdGVzdCkNCiAgYGBgDQoNCiMjIyMgKipUcmFpbmluZyB0aGUgTW9kZWwqKg0KLSAqKk9iamVjdGl2ZSBGdW5jdGlvbiAoYG11bHRpOnNvZnRtYXhgKSoqOiBUaGlzIGlzIHVzZWQgZm9yICoqbXVsdGktY2xhc3MgY2xhc3NpZmljYXRpb24qKiAoc2luY2UgdGhlcmUgYXJlIDEwIGNsYXNzZXMgb2YgZGlnaXRzKS4NCi0gKipgbnVtX2NsYXNzYCoqOiBUaGlzIHNwZWNpZmllcyB0aGUgbnVtYmVyIG9mIGNsYXNzZXMgaW4gdGhlIGNsYXNzaWZpY2F0aW9uIHRhc2sgKDEwIGluIHRoaXMgY2FzZSkuDQotICoqYGV2YWxfbWV0cmljYCoqOiBUaGUgZXZhbHVhdGlvbiBtZXRyaWMsIGBtbG9nbG9zc2AsIG1lYXN1cmVzIHRoZSBhY2N1cmFjeSBvZiB0aGUgbW9kZWzigJlzIGNsYXNzIHByb2JhYmlsaXR5IHByZWRpY3Rpb25zIHVzaW5nIGxvZ2FyaXRobWljIGxvc3MuDQotICoqQm9vc3RpbmcgUm91bmRzKio6IFRoZSBtb2RlbCB3aWxsIGl0ZXJhdGUgb3ZlciB0aGUgZGF0YSAxMDAgdGltZXMgKDEwMCByb3VuZHMgb2YgYm9vc3RpbmcpIHRvIG9wdGltaXplIHRoZSBwcmVkaWN0aW9ucy4NCg0KIyMjIyAqKkV2YWx1YXRpbmcgdGhlIE1vZGVsKioNCi0gKipBY2N1cmFjeSBDYWxjdWxhdGlvbjoqKiBBZnRlciB0cmFpbmluZywgd2UgdXNlIGBtb2RlbC5wcmVkaWN0KGR0ZXN0KWAgdG8gbWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBzZXQuIFRoZW4sIHdlIGNvbXBhcmUgdGhlIHByZWRpY3RlZCB2YWx1ZXMgd2l0aCB0aGUgdHJ1ZSBsYWJlbHMgdG8gY29tcHV0ZSB0aGUgKiphY2N1cmFjeSoqLg0KDQotLS0NCg0KIyMjICoqMi4gWEdCb29zdCBEZW1vIDI6IFJlZ3Jlc3Npb24gb24gQm9zdG9uIEhvdXNpbmcgRGF0YSoqDQoNCiMjIyMgKipEYXRhIFByZXBhcmF0aW9uKioNCi0gKipEYXRhc2V0OioqIFRoZSAqKkJvc3RvbiBIb3VzaW5nIERhdGFzZXQqKiBjb250YWlucyAxMyBmZWF0dXJlcyByZXByZXNlbnRpbmcgYXNwZWN0cyBvZiBob3VzaW5nIChlLmcuLCBjcmltZSByYXRlLCBudW1iZXIgb2Ygcm9vbXMpIGFuZCB0aGUgdGFyZ2V0IHZhcmlhYmxlIGlzIHRoZSAqKnByaWNlIG9mIGhvdXNlcyoqLg0KLSAqKkZlYXR1cmVzOioqIEluY2x1ZGVzIHZhcmlhYmxlcyBsaWtlIGF2ZXJhZ2UgbnVtYmVyIG9mIHJvb21zIHBlciBkd2VsbGluZyAoYFJNYCksIGRpc3RhbmNlIHRvIGVtcGxveW1lbnQgY2VudGVycyAoYERJU2ApLCBhbmQgcHVwaWwtdGVhY2hlciByYXRpbyBpbiBzY2hvb2xzIChgUFRSQVRJT2ApLg0KLSAqKlRhcmdldCBWYXJpYWJsZToqKiBUaGUgaG91c2UgcHJpY2VzIGluIHRob3VzYW5kcyBvZiBkb2xsYXJzLg0KDQojIyMjICoqU3BsaXR0aW5nIERhdGEqKg0KLSBMaWtlIHRoZSBjbGFzc2lmaWNhdGlvbiBkZW1vLCB3ZSBzcGxpdCB0aGUgZGF0YXNldCBpbnRvICoqdHJhaW5pbmcqKiBhbmQgKip0ZXN0Kiogc2V0cyB1c2luZyBgdHJhaW5fdGVzdF9zcGxpdCgpYC4gVGhpcyBlbnN1cmVzIHRoYXQgdGhlIG1vZGVsIHRyYWlucyBvbiBvbmUgc2V0IG9mIGRhdGEgYW5kIGlzIGV2YWx1YXRlZCBvbiB1bnNlZW4gZGF0YS4NCg0KIyMjIyAqKkRNYXRyaXggQ29udmVyc2lvbioqDQotIFNpbWlsYXIgdG8gdGhlIGNsYXNzaWZpY2F0aW9uIGRlbW8sIHdlIGNvbnZlcnQgdGhlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGEgaW50byAqKkRNYXRyaXgqKiBmb3JtYXQgdG8gb3B0aW1pemUgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2U6DQogIGBgYHB5dGhvbg0KICBkdHJhaW4gPSB4Z2IuRE1hdHJpeChYX3RyYWluLCBsYWJlbD15X3RyYWluKQ0KICBkdGVzdCA9IHhnYi5ETWF0cml4KFhfdGVzdCwgbGFiZWw9eV90ZXN0KQ0KICBgYGANCg0KIyMjIyAqKlRyYWluaW5nIHRoZSBNb2RlbCoqDQotICoqT2JqZWN0aXZlIEZ1bmN0aW9uIChgcmVnOnNxdWFyZWRlcnJvcmApKio6IEZvciByZWdyZXNzaW9uIHRhc2tzLCB3ZSB1c2UgdGhlICoqbWVhbiBzcXVhcmVkIGVycm9yKiogbG9zcyBmdW5jdGlvbi4NCi0gKipSZWd1bGFyaXphdGlvbiAoYG1heF9kZXB0aGAsIGBsZWFybmluZ19yYXRlYCkqKjogV2UgY29udHJvbCB0aGUgY29tcGxleGl0eSBvZiB0aGUgdHJlZXMgdXNpbmcgdGhlIGBtYXhfZGVwdGhgIHBhcmFtZXRlciAoNCkgYW5kIHVzZSBhIGxlYXJuaW5nIHJhdGUgKGBsZWFybmluZ19yYXRlID0gMC4xYCkgdG8gY29udHJvbCBob3cgbXVjaCB0aGUgbW9kZWwgY2hhbmdlcyB3aXRoIGVhY2ggYm9vc3Rpbmcgcm91bmQuDQotICoqQm9vc3RpbmcgUm91bmRzOioqIFdlIHNldCB0aGUgbnVtYmVyIG9mIGJvb3N0aW5nIHJvdW5kcyB0byAxMDAuIFRoZSBtb2RlbCBpdGVyYXRpdmVseSBpbXByb3ZlcyBvdmVyIHRoZXNlIHJvdW5kcy4NCg0KIyMjIyAqKkV2YWx1YXRpbmcgdGhlIE1vZGVsKioNCi0gKipSb290IE1lYW4gU3F1YXJlZCBFcnJvciAoUk1TRSk6KiogQWZ0ZXIgbWFraW5nIHByZWRpY3Rpb25zIHdpdGggYG1vZGVsLnByZWRpY3QoKWAsIHdlIGNhbGN1bGF0ZSB0aGUgKipSTVNFKiogdG8gbWVhc3VyZSBob3cgd2VsbCB0aGUgbW9kZWwgaXMgcHJlZGljdGluZyBob3VzaW5nIHByaWNlcy4NCiAgLSBBIGxvd2VyIFJNU0UgbWVhbnMgYmV0dGVyIHByZWRpY3Rpb25zLg0KDQotLS0NCg0KIyMgKipOZXh0IFN0ZXA6IEh5cGVycGFyYW1ldGVyIFR1bmluZyBpbiBYR0Jvb3N0KioNCg0KIyMjICoqMS4gSW1wb3J0YW5jZSBvZiBIeXBlcnBhcmFtZXRlciBUdW5pbmcqKg0KSHlwZXJwYXJhbWV0ZXJzIGluIFhHQm9vc3Qgc2lnbmlmaWNhbnRseSBpbXBhY3QgdGhlIG1vZGVs4oCZcyBwZXJmb3JtYW5jZSwgYW5kIHR1bmluZyB0aGVtIGlzIGVzc2VudGlhbCB0byBtYXhpbWl6ZSBhY2N1cmFjeSBhbmQgbWluaW1pemUgb3ZlcmZpdHRpbmcuDQoNCioqS2V5IEh5cGVycGFyYW1ldGVycyB0byBUdW5lOioqDQotICoqYGxlYXJuaW5nX3JhdGVgIChvciBgZXRhYCkqKjogQ29udHJvbHMgdGhlIHN0ZXAgc2l6ZSBkdXJpbmcgb3B0aW1pemF0aW9uLiBMb3dlciB2YWx1ZXMgbWFrZSB0aGUgbGVhcm5pbmcgcHJvY2VzcyBtb3JlIGdyYWR1YWwsIHBvdGVudGlhbGx5IGltcHJvdmluZyBhY2N1cmFjeSBidXQgcmVxdWlyaW5nIG1vcmUgYm9vc3Rpbmcgcm91bmRzLg0KLSAqKmBtYXhfZGVwdGhgKio6IFRoZSBtYXhpbXVtIGRlcHRoIG9mIGluZGl2aWR1YWwgdHJlZXMuIERlZXBlciB0cmVlcyBhcmUgbW9yZSBjb21wbGV4IGFuZCBjYW4gb3ZlcmZpdCB0aGUgZGF0YS4NCi0gKipgbl9lc3RpbWF0b3JzYCoqOiBUaGUgbnVtYmVyIG9mIGJvb3N0aW5nIHJvdW5kcyAodHJlZXMpIHRvIHRyYWluLg0KLSAqKmBzdWJzYW1wbGVgKio6IFRoZSBmcmFjdGlvbiBvZiBzYW1wbGVzIHVzZWQgcGVyIGJvb3N0aW5nIHJvdW5kLiBIZWxwcyBwcmV2ZW50IG92ZXJmaXR0aW5nLg0KLSAqKmBjb2xzYW1wbGVfYnl0cmVlYCoqOiBGcmFjdGlvbiBvZiBmZWF0dXJlcyB0byB1c2UgcGVyIGJvb3N0aW5nIHJvdW5kLCBwcm9tb3RpbmcgZGl2ZXJzaXR5IGluIHRoZSB0cmVlcyBhbmQgcHJldmVudGluZyBvdmVyZml0dGluZy4NCi0gKipgZ2FtbWFgKio6IENvbnRyb2xzIHRoZSBtaW5pbXVtIGxvc3MgcmVkdWN0aW9uIHJlcXVpcmVkIHRvIG1ha2UgYSBmdXJ0aGVyIHBhcnRpdGlvbi4gSGlnaGVyIHZhbHVlcyBtYWtlIHRoZSBhbGdvcml0aG0gbW9yZSBjb25zZXJ2YXRpdmUuDQotICoqYGxhbWJkYWAgKEwyIHJlZ3VsYXJpemF0aW9uKSoqIGFuZCAqKmBhbHBoYWAgKEwxIHJlZ3VsYXJpemF0aW9uKSoqOiBIZWxwIHByZXZlbnQgb3ZlcmZpdHRpbmcgYnkgcGVuYWxpemluZyBsYXJnZSBjb2VmZmljaWVudHMgaW4gdGhlIHRyZWVzLg0KDQojIyMgKioyLiBHcmlkIFNlYXJjaCBmb3IgSHlwZXJwYXJhbWV0ZXIgVHVuaW5nKioNCkEgY29tbW9uIG1ldGhvZCBmb3IgaHlwZXJwYXJhbWV0ZXIgdHVuaW5nIGlzICoqR3JpZCBTZWFyY2gqKiwgd2hlcmUgeW91IGRlZmluZSBhIGdyaWQgb2YgcG90ZW50aWFsIGh5cGVycGFyYW1ldGVyIHZhbHVlcywgYW5kIHRoZW4gZXhoYXVzdGl2ZWx5IHRyYWluIGFuZCBldmFsdWF0ZSBtb2RlbHMgdXNpbmcgYWxsIGNvbWJpbmF0aW9ucy4NCg0KIyMjIyAqKlN0ZXAgMTogRGVmaW5lIEh5cGVycGFyYW1ldGVyIEdyaWQqKg0KWW91IGNhbiBjcmVhdGUgYSBncmlkIG9mIHBvc3NpYmxlIGh5cGVycGFyYW1ldGVyIHZhbHVlczoNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgR3JpZFNlYXJjaENWDQppbXBvcnQgeGdib29zdCBhcyB4Z2INCg0KIyBEZWZpbmUgdGhlIG1vZGVsDQp4Z2JfbW9kZWwgPSB4Z2IuWEdCQ2xhc3NpZmllcigpDQoNCiMgRGVmaW5lIHRoZSBwYXJhbWV0ZXIgZ3JpZA0KcGFyYW1fZ3JpZCA9IHsNCiAgICAnbGVhcm5pbmdfcmF0ZSc6IFswLjAxLCAwLjEsIDAuMl0sDQogICAgJ21heF9kZXB0aCc6IFszLCA1LCA3XSwNCiAgICAnbl9lc3RpbWF0b3JzJzogWzUwLCAxMDAsIDE1MF0sDQogICAgJ3N1YnNhbXBsZSc6IFswLjgsIDEuMF0sDQogICAgJ2NvbHNhbXBsZV9ieXRyZWUnOiBbMC44LCAxLjBdDQp9DQpgYGANCg0KIyMjIyAqKlN0ZXAgMjogUGVyZm9ybSBHcmlkIFNlYXJjaCoqDQpZb3UgY2FuIHVzZSBgR3JpZFNlYXJjaENWYCB0byBldmFsdWF0ZSBhbGwgY29tYmluYXRpb25zIG9mIHBhcmFtZXRlcnMuDQpgYGBweXRob24NCmdyaWRfc2VhcmNoID0gR3JpZFNlYXJjaENWKGVzdGltYXRvcj14Z2JfbW9kZWwsIHBhcmFtX2dyaWQ9cGFyYW1fZ3JpZCwgY3Y9NSwgdmVyYm9zZT0xKQ0KZ3JpZF9zZWFyY2guZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCiMgQmVzdCBoeXBlcnBhcmFtZXRlcnMNCnByaW50KCJCZXN0IHBhcmFtZXRlcnMgZm91bmQ6ICIsIGdyaWRfc2VhcmNoLmJlc3RfcGFyYW1zXykNCmBgYA0KDQojIyMjICoqU3RlcCAzOiBFdmFsdWF0ZSB0aGUgQmVzdCBNb2RlbCoqDQpPbmNlIHRoZSBiZXN0IHBhcmFtZXRlcnMgYXJlIGlkZW50aWZpZWQsIHlvdSBjYW4gZXZhbHVhdGUgdGhlIG1vZGVsOg0KYGBgcHl0aG9uDQpiZXN0X21vZGVsID0gZ3JpZF9zZWFyY2guYmVzdF9lc3RpbWF0b3JfDQp5X3ByZWQgPSBiZXN0X21vZGVsLnByZWRpY3QoWF90ZXN0KQ0KDQojIENhbGN1bGF0ZSBhY2N1cmFjeSAoZm9yIGNsYXNzaWZpY2F0aW9uKQ0KZnJvbSBza2xlYXJuLm1ldHJpY3MgaW1wb3J0IGFjY3VyYWN5X3Njb3JlDQpwcmludCgiQWNjdXJhY3kgb2YgdGhlIGJlc3QgbW9kZWw6ICIsIGFjY3VyYWN5X3Njb3JlKHlfdGVzdCwgeV9wcmVkKSkNCmBgYA0KDQojIyMgKiozLiBSYW5kb20gU2VhcmNoIGZvciBIeXBlcnBhcmFtZXRlciBUdW5pbmcqKg0KV2hpbGUgKipHcmlkIFNlYXJjaCoqIGlzIGV4aGF1c3RpdmUsIGl0IGNhbiBiZSBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlLiAqKlJhbmRvbSBTZWFyY2gqKiByYW5kb21seSBzYW1wbGVzIGh5cGVycGFyYW1ldGVycyBmcm9tIGEgcHJlZGVmaW5lZCBkaXN0cmlidXRpb24gYW5kIGlzIG9mdGVuIGZhc3RlciBhbmQganVzdCBhcyBlZmZlY3RpdmUuDQoNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgUmFuZG9taXplZFNlYXJjaENWDQpmcm9tIHNjaXB5LnN0YXRzIGltcG9ydCB1bmlmb3JtDQoNCiMgRGVmaW5lIHRoZSBwYXJhbWV0ZXIgZGlzdHJpYnV0aW9uDQpwYXJhbV9kaXN0ID0gew0KICAgICdsZWFybmluZ19yYXRlJzogdW5pZm9ybSgwLjAxLCAwLjIpLA0KICAgICdtYXhfZGVwdGgnOiBbMywgNSwgN10sDQogICAgJ25fZXN0aW1hdG9ycyc6IFs1MCwgMTAwLCAxNTBdLA0KICAgICdzdWJzYW1wbGUnOiB1bmlmb3JtKDAuNSwgMC41KSwNCiAgICAnY29sc2FtcGxlX2J5dHJlZSc6IHVuaWZvcm0oMC41LCAwLjUpDQp9DQoNCiMgRGVmaW5lIHRoZSBtb2RlbCBhbmQgUmFuZG9taXplZFNlYXJjaENWDQpyYW5kb21fc2VhcmNoID0gUmFuZG9taXplZFNlYXJjaENWKGVzdGltYXRvcj14Z2IuWEdCQ2xhc3NpZmllcigpLCBwYXJhbV9kaXN0cmlidXRpb25zPXBhcmFtX2Rpc3QsIG5faXRlcj0xMDAsIGN2PTUsIHZlcmJvc2U9MSkNCnJhbmRvbV9zZWFyY2guZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCiMgQmVzdCBoeXBlcnBhcmFtZXRlcnMNCnByaW50KCJCZXN0IHBhcmFtZXRlcnMgZm91bmQ6ICIsIHJhbmRvbV9zZWFyY2guYmVzdF9wYXJhbXNfKQ0KYGBgDQoNCi0tLQ0KDQojIyMgKipOZXh0IFN0ZXBzOioqDQogKipmZWF0dXJlIGltcG9ydGFuY2UgdmlzdWFsaXphdGlvbioqIGluIFhHQm9vc3QgJiAqKmFkdmFuY2VkIGh5cGVycGFyYW1ldGVyIHR1bmluZyBzdHJhdGVnaWVzKiogc3VjaCBhcyAqKkJheWVzaWFuIE9wdGltaXphdGlvbioqDQoNCg0KDQoNCiMjICoqRmVhdHVyZSBJbXBvcnRhbmNlIGluIFhHQm9vc3QqKg0KVW5kZXJzdGFuZGluZyB3aGljaCBmZWF0dXJlcyBjb250cmlidXRlIHRoZSBtb3N0IHRvIHlvdXIgbW9kZWzigJlzIHByZWRpY3Rpb25zIGNhbiBoZWxwIGluOg0KLSBGZWF0dXJlIHNlbGVjdGlvbg0KLSBNb2RlbCBpbnRlcnByZXRhdGlvbg0KLSBSZWR1Y2luZyBvdmVyZml0dGluZyBieSBlbGltaW5hdGluZyB1bmltcG9ydGFudCBmZWF0dXJlcw0KDQojIyMgKioxLiBUeXBlcyBvZiBGZWF0dXJlIEltcG9ydGFuY2UgaW4gWEdCb29zdCoqDQpYR0Jvb3N0IHByb3ZpZGVzIHNldmVyYWwgd2F5cyB0byBtZWFzdXJlIGZlYXR1cmUgaW1wb3J0YW5jZToNCjEuICoqV2VpZ2h0IChGcmVxdWVuY3kpKio6IE51bWJlciBvZiB0aW1lcyBhIGZlYXR1cmUgaXMgdXNlZCBpbiBhIHNwbGl0IGFjcm9zcyBhbGwgdHJlZXMuDQoyLiAqKkdhaW4gKEluZm9ybWF0aW9uIEdhaW4pKio6IENvbnRyaWJ1dGlvbiBvZiBhIGZlYXR1cmUgdG8gdGhlIG1vZGVsIGJhc2VkIG9uIGl0cyBhdmVyYWdlIGdhaW4gd2hlbiB1c2VkIGluIHNwbGl0cy4NCjMuICoqQ292ZXIqKjogTnVtYmVyIG9mIHNhbXBsZXMgYWZmZWN0ZWQgYnkgdGhlIGZlYXR1cmUncyBzcGxpdC4NCg0KQnkgZGVmYXVsdCwgKipYR0Jvb3N0IHVzZXMgR2FpbiB0byBkZXRlcm1pbmUgaW1wb3J0YW5jZSoqLg0KDQotLS0NCg0KIyMjICoqMi4gRXh0cmFjdGluZyBGZWF0dXJlIEltcG9ydGFuY2UgaW4gWEdCb29zdCoqDQpPbmNlIGFuIFhHQm9vc3QgbW9kZWwgaXMgdHJhaW5lZCwgeW91IGNhbiBleHRyYWN0IGZlYXR1cmUgaW1wb3J0YW5jZSBzY29yZXMuDQoNCiMjIyMgKipTdGVwIDE6IFRyYWluIGFuIFhHQm9vc3QgTW9kZWwqKg0KYGBgcHl0aG9uDQppbXBvcnQgeGdib29zdCBhcyB4Z2INCmZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgbG9hZF9ib3N0b24NCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQNCmltcG9ydCBwYW5kYXMgYXMgcGQNCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCg0KIyBMb2FkIGRhdGFzZXQNCmJvc3RvbiA9IGxvYWRfYm9zdG9uKCkNClgsIHkgPSBib3N0b24uZGF0YSwgYm9zdG9uLnRhcmdldA0KZmVhdHVyZV9uYW1lcyA9IGJvc3Rvbi5mZWF0dXJlX25hbWVzICAjIENvbHVtbiBuYW1lcw0KDQojIFNwbGl0IGRhdGENClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4yLCByYW5kb21fc3RhdGU9NDIpDQoNCiMgVHJhaW4gWEdCb29zdCBtb2RlbA0KbW9kZWwgPSB4Z2IuWEdCUmVncmVzc29yKG9iamVjdGl2ZT0ncmVnOnNxdWFyZWRlcnJvcicsIG5fZXN0aW1hdG9ycz0xMDAsIG1heF9kZXB0aD00KQ0KbW9kZWwuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQpgYGANCg0KIyMjIyAqKlN0ZXAgMjogUmV0cmlldmUgYW5kIERpc3BsYXkgRmVhdHVyZSBJbXBvcnRhbmNlKioNCmBgYHB5dGhvbg0KIyBHZXQgZmVhdHVyZSBpbXBvcnRhbmNlIHNjb3Jlcw0KaW1wb3J0YW5jZSA9IG1vZGVsLmdldF9ib29zdGVyKCkuZ2V0X3Njb3JlKGltcG9ydGFuY2VfdHlwZT0nZ2FpbicpDQoNCiMgQ29udmVydCB0byBEYXRhRnJhbWUgZm9yIGJldHRlciB2aXN1YWxpemF0aW9uDQppbXBvcnRhbmNlX2RmID0gcGQuRGF0YUZyYW1lKHsnRmVhdHVyZSc6IGltcG9ydGFuY2Uua2V5cygpLCAnSW1wb3J0YW5jZSc6IGltcG9ydGFuY2UudmFsdWVzKCl9KQ0KaW1wb3J0YW5jZV9kZiA9IGltcG9ydGFuY2VfZGYuc29ydF92YWx1ZXMoYnk9IkltcG9ydGFuY2UiLCBhc2NlbmRpbmc9RmFsc2UpDQoNCiMgUGxvdCBmZWF0dXJlIGltcG9ydGFuY2UNCnBsdC5maWd1cmUoZmlnc2l6ZT0oMTAsIDUpKQ0KcGx0LmJhcmgoaW1wb3J0YW5jZV9kZlsnRmVhdHVyZSddLCBpbXBvcnRhbmNlX2RmWydJbXBvcnRhbmNlJ10sIGNvbG9yPSdibHVlJykNCnBsdC54bGFiZWwoIkZlYXR1cmUgSW1wb3J0YW5jZSAoR2FpbikiKQ0KcGx0LnlsYWJlbCgiRmVhdHVyZSBOYW1lIikNCnBsdC50aXRsZSgiRmVhdHVyZSBJbXBvcnRhbmNlIGluIFhHQm9vc3QiKQ0KcGx0LmdjYSgpLmludmVydF95YXhpcygpICAjIEZsaXAgdGhlIGNoYXJ0IHRvIHNob3cgdGhlIG1vc3QgaW1wb3J0YW50IGZlYXR1cmUgYXQgdGhlIHRvcA0KcGx0LnNob3coKQ0KYGBgDQoNCiMjIyMgKipBbHRlcm5hdGl2ZSBNZXRob2QgVXNpbmcgYHBsb3RfaW1wb3J0YW5jZSgpYCoqDQpYR0Jvb3N0IGFsc28gcHJvdmlkZXMgYSBidWlsdC1pbiBmdW5jdGlvbjoNCmBgYHB5dGhvbg0KeGdiLnBsb3RfaW1wb3J0YW5jZShtb2RlbCwgaW1wb3J0YW5jZV90eXBlPSdnYWluJywgbWF4X251bV9mZWF0dXJlcz0xMCkNCnBsdC5zaG93KCkNCmBgYA0KLSBgaW1wb3J0YW5jZV90eXBlPSdnYWluJ2A6IFNvcnRzIGZlYXR1cmVzIGJ5IHRoZWlyIGluZm9ybWF0aW9uIGdhaW4uDQotIGBtYXhfbnVtX2ZlYXR1cmVzPTEwYDogTGltaXRzIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgc2hvd24uDQoNCi0tLQ0KDQojIyMgKiozLiBJbnRlcnByZXRpbmcgRmVhdHVyZSBJbXBvcnRhbmNlIFJlc3VsdHMqKg0KLSAqKkhpZ2ggSW1wb3J0YW5jZSoqOiBGZWF0dXJlcyB0aGF0IGNvbnRyaWJ1dGUgdGhlIG1vc3QgdG8gcHJlZGljdGlvbnMuDQotICoqTG93IEltcG9ydGFuY2UqKjogRmVhdHVyZXMgd2l0aCBsaXR0bGUgY29udHJpYnV0aW9uLCBwb3RlbnRpYWxseSByZW1vdmFibGUgdG8gc2ltcGxpZnkgdGhlIG1vZGVsLg0KLSAqKkZlYXR1cmUgRW5naW5lZXJpbmcqKjogSWYgYSBsZXNzIGltcG9ydGFudCBmZWF0dXJlIGlzIGRvbWFpbi1yZWxldmFudCwgdHJ5IHRyYW5zZm9ybWluZyBpdC4NCg0KLS0tDQoNCiMjICoqQmF5ZXNpYW4gT3B0aW1pemF0aW9uIGZvciBIeXBlcnBhcmFtZXRlciBUdW5pbmcqKg0KTm93IHRoYXQgd2UgdW5kZXJzdGFuZCBmZWF0dXJlIGltcG9ydGFuY2UsIGxldOKAmXMgKipvcHRpbWl6ZSBoeXBlcnBhcmFtZXRlcnMgbW9yZSBlZmZpY2llbnRseSoqLg0KDQojIyMgKioxLiBXaHkgQmF5ZXNpYW4gT3B0aW1pemF0aW9uPyoqDQpVbmxpa2UgKipHcmlkIFNlYXJjaCoqIGFuZCAqKlJhbmRvbSBTZWFyY2gqKiwgQmF5ZXNpYW4gT3B0aW1pemF0aW9uOg0K4pyUIExlYXJucyBmcm9tIHBhc3QgZXZhbHVhdGlvbnMgdG8gKipjaG9vc2UgYmV0dGVyIGh5cGVycGFyYW1ldGVycyBuZXh0IHRpbWUqKiAgDQrinJQgUmVkdWNlcyBjb21wdXRhdGlvbmFsIGNvc3QgYnkgKipub3QgdGVzdGluZyB1bm5lY2Vzc2FyeSBjb21iaW5hdGlvbnMqKiAgDQrinJQgRmluZHMgKipvcHRpbWFsIGh5cGVycGFyYW1ldGVycyBmYXN0ZXIqKiAgDQoNCkl0IGJhbGFuY2VzICoqZXhwbG9yYXRpb24gKHRyeWluZyBuZXcgcGFyYW1ldGVycykqKiBhbmQgKipleHBsb2l0YXRpb24gKHJlZmluaW5nIGtub3duIGdvb2QgcGFyYW1ldGVycykuKioNCg0KLS0tDQoNCiMjIyAqKjIuIEltcGxlbWVudGluZyBCYXllc2lhbiBPcHRpbWl6YXRpb24gaW4gWEdCb29zdCoqDQpXZSB1c2UgdGhlIGBCYXllc2lhbk9wdGltaXphdGlvbmAgcGFja2FnZSB0byBhdXRvbWF0aWNhbGx5IGZpbmQgdGhlIGJlc3QgaHlwZXJwYXJhbWV0ZXJzLg0KDQojIyMjICoqU3RlcCAxOiBJbnN0YWxsIFJlcXVpcmVkIFBhY2thZ2VzKioNCmBgYHB5dGhvbg0KcGlwIGluc3RhbGwgYmF5ZXNpYW4tb3B0aW1pemF0aW9uDQpgYGANCg0KIyMjIyAqKlN0ZXAgMjogSW1wb3J0IFJlcXVpcmVkIExpYnJhcmllcyoqDQpgYGBweXRob24NCmZyb20gYmF5ZXNfb3B0IGltcG9ydCBCYXllc2lhbk9wdGltaXphdGlvbg0KaW1wb3J0IHhnYm9vc3QgYXMgeGdiDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCBjcm9zc192YWxfc2NvcmUNCmltcG9ydCBudW1weSBhcyBucA0KYGBgDQoNCiMjIyMgKipTdGVwIDM6IERlZmluZSB0aGUgT2JqZWN0aXZlIEZ1bmN0aW9uKioNCkJheWVzaWFuIE9wdGltaXphdGlvbiByZXF1aXJlcyBhIGZ1bmN0aW9uIHRvICoqbWF4aW1pemUqKi4gV2UgZGVmaW5lIGEgZnVuY3Rpb24gdGhhdCByZXR1cm5zIHRoZSAqKm5lZ2F0aXZlIG1lYW4gc3F1YXJlZCBlcnJvciAoTVNFKSoqLg0KYGBgcHl0aG9uDQojIERlZmluZSB0aGUgZnVuY3Rpb24gdG8gb3B0aW1pemUNCmRlZiB4Z2JfZXZhbHVhdGUobGVhcm5pbmdfcmF0ZSwgbWF4X2RlcHRoLCBzdWJzYW1wbGUsIGNvbHNhbXBsZV9ieXRyZWUpOg0KICAgIHBhcmFtcyA9IHsNCiAgICAgICAgJ29iamVjdGl2ZSc6ICdyZWc6c3F1YXJlZGVycm9yJywNCiAgICAgICAgJ2xlYXJuaW5nX3JhdGUnOiBsZWFybmluZ19yYXRlLA0KICAgICAgICAnbWF4X2RlcHRoJzogaW50KG1heF9kZXB0aCksICAjIE11c3QgYmUgaW50ZWdlcg0KICAgICAgICAnc3Vic2FtcGxlJzogc3Vic2FtcGxlLA0KICAgICAgICAnY29sc2FtcGxlX2J5dHJlZSc6IGNvbHNhbXBsZV9ieXRyZWUsDQogICAgICAgICduX2VzdGltYXRvcnMnOiAxMDANCiAgICB9DQogICAgDQogICAgIyBQZXJmb3JtIGNyb3NzLXZhbGlkYXRpb24NCiAgICBzY29yZXMgPSBjcm9zc192YWxfc2NvcmUoeGdiLlhHQlJlZ3Jlc3NvcigqKnBhcmFtcyksIFhfdHJhaW4sIHlfdHJhaW4sIHNjb3Jpbmc9Im5lZ19tZWFuX3NxdWFyZWRfZXJyb3IiLCBjdj0zKQ0KICAgIHJldHVybiBucC5tZWFuKHNjb3JlcykgICMgUmV0dXJuIG1lYW4gbmVnYXRpdmUgTVNFDQpgYGANCg0KIyMjIyAqKlN0ZXAgNDogU2V0IFVwIHRoZSBCYXllc2lhbiBPcHRpbWl6YXRpb24gU2VhcmNoIFNwYWNlKioNCmBgYHB5dGhvbg0KIyBEZWZpbmUgdGhlIHNlYXJjaCBzcGFjZSBmb3IgaHlwZXJwYXJhbWV0ZXJzDQpwYm91bmRzID0gew0KICAgICdsZWFybmluZ19yYXRlJzogKDAuMDEsIDAuMyksDQogICAgJ21heF9kZXB0aCc6ICgzLCAxMCksDQogICAgJ3N1YnNhbXBsZSc6ICgwLjUsIDEuMCksDQogICAgJ2NvbHNhbXBsZV9ieXRyZWUnOiAoMC41LCAxLjApDQp9DQoNCiMgSW5pdGlhbGl6ZSBCYXllc2lhbiBPcHRpbWl6YXRpb24NCm9wdGltaXplciA9IEJheWVzaWFuT3B0aW1pemF0aW9uKA0KICAgIGY9eGdiX2V2YWx1YXRlLCAgIyBUaGUgZnVuY3Rpb24gdG8gbWF4aW1pemUNCiAgICBwYm91bmRzPXBib3VuZHMsICAjIFNlYXJjaCBzcGFjZQ0KICAgIHJhbmRvbV9zdGF0ZT00Mg0KKQ0KYGBgDQoNCiMjIyMgKipTdGVwIDU6IFJ1biB0aGUgT3B0aW1pemF0aW9uKioNCmBgYHB5dGhvbg0Kb3B0aW1pemVyLm1heGltaXplKGluaXRfcG9pbnRzPTUsIG5faXRlcj0yNSkNCg0KIyBCZXN0IHBhcmFtZXRlcnMgZm91bmQNCnByaW50KCJCZXN0IEh5cGVycGFyYW1ldGVyczoiLCBvcHRpbWl6ZXIubWF4KQ0KYGBgDQotIGBpbml0X3BvaW50cz01YDogUmFuZG9tbHkgdGVzdHMgNSBpbml0aWFsIHBvaW50cy4NCi0gYG5faXRlcj0yNWA6IFJ1bnMgMjUgb3B0aW1pemF0aW9uIHN0ZXBzIHRvIGZpbmQgdGhlIGJlc3QgcGFyYW1ldGVycy4NCg0KIyMjIyAqKlN0ZXAgNjogVHJhaW4gdGhlIEJlc3QgTW9kZWwqKg0KYGBgcHl0aG9uDQojIEV4dHJhY3QgYmVzdCBwYXJhbWV0ZXJzDQpiZXN0X3BhcmFtcyA9IG9wdGltaXplci5tYXhbJ3BhcmFtcyddDQpiZXN0X3BhcmFtc1snbWF4X2RlcHRoJ10gPSBpbnQoYmVzdF9wYXJhbXNbJ21heF9kZXB0aCddKSAgIyBDb252ZXJ0IGRlcHRoIHRvIGludGVnZXINCg0KIyBUcmFpbiB0aGUgZmluYWwgbW9kZWwNCmZpbmFsX21vZGVsID0geGdiLlhHQlJlZ3Jlc3NvcihvYmplY3RpdmU9J3JlZzpzcXVhcmVkZXJyb3InLCAqKmJlc3RfcGFyYW1zLCBuX2VzdGltYXRvcnM9MTAwKQ0KZmluYWxfbW9kZWwuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCiMgRXZhbHVhdGUgbW9kZWwgcGVyZm9ybWFuY2UNCnlfcHJlZCA9IGZpbmFsX21vZGVsLnByZWRpY3QoWF90ZXN0KQ0Kcm1zZSA9IG5wLnNxcnQobWVhbl9zcXVhcmVkX2Vycm9yKHlfdGVzdCwgeV9wcmVkKSkNCnByaW50KGYiT3B0aW1pemVkIFJNU0U6IHtybXNlOi40Zn0iKQ0KYGBgDQoNCi0tLQ0KDQojIyMgKiozLiBDb21wYXJpc29uIG9mIEh5cGVycGFyYW1ldGVyIFR1bmluZyBNZXRob2RzKioNCnwgTWV0aG9kIHwgUHJvcyB8IENvbnMgfA0KfC0tLS0tLS0tfC0tLS0tLXwtLS0tLS18DQp8ICoqR3JpZCBTZWFyY2gqKiB8IEV4aGF1c3RpdmUsIGd1YXJhbnRlZXMgYmVzdCBwYXJhbWV0ZXJzIHwgQ29tcHV0YXRpb25hbGx5IGV4cGVuc2l2ZSB8DQp8ICoqUmFuZG9tIFNlYXJjaCoqIHwgRmFzdGVyIHRoYW4gR3JpZCBTZWFyY2gsIGdvb2QgZm9yIGxhcmdlIHNlYXJjaCBzcGFjZXMgfCBNYXkgbWlzcyB0aGUgYmVzdCBwYXJhbWV0ZXJzIHwNCnwgKipCYXllc2lhbiBPcHRpbWl6YXRpb24qKiB8IExlYXJucyBmcm9tIHByZXZpb3VzIGV2YWx1YXRpb25zLCBmZXdlciBpdGVyYXRpb25zIG5lZWRlZCB8IFJlcXVpcmVzIGFkZGl0aW9uYWwgc2V0dXAgfA0KDQotLS0NCg0KIyMgKipGaW5hbCBTdW1tYXJ5KioNCjEuICoqRmVhdHVyZSBJbXBvcnRhbmNlIGluIFhHQm9vc3QqKiBoZWxwcyBpZGVudGlmeSB3aGljaCB2YXJpYWJsZXMgY29udHJpYnV0ZSBtb3N0IHRvIHByZWRpY3Rpb25zLg0KMi4gKipCYXllc2lhbiBPcHRpbWl6YXRpb24qKiBwcm92aWRlcyBhbiAqKmludGVsbGlnZW50IGFwcHJvYWNoKiogdG8gaHlwZXJwYXJhbWV0ZXIgdHVuaW5nLg0KMy4gKipYR0Jvb3N0IHdpdGggQmF5ZXNpYW4gT3B0aW1pemF0aW9uKiogb3V0cGVyZm9ybXMgdHJhZGl0aW9uYWwgdHVuaW5nIG1ldGhvZHMuDQoNCi0tLQ0KDQoNCiMjICoqU0hBUCAoU0hhcGxleSBBZGRpdGl2ZSBleFBsYW5hdGlvbnMpIGFuZCBBZHZhbmNlZCBYR0Jvb3N0IFRlY2huaXF1ZXMqKg0KU0hBUCBpcyBhIHBvd2VyZnVsIG1ldGhvZCBmb3IgKippbnRlcnByZXRpbmcgbW9kZWwgcHJlZGljdGlvbnMqKiwgd2hpbGUgZWFybHkgc3RvcHBpbmcgYW5kIGN1c3RvbSBsb3NzIGZ1bmN0aW9ucyBjYW4gKiplbmhhbmNlIG1vZGVsIHBlcmZvcm1hbmNlKiouDQoNCi0tLQ0KDQojICoqMS4gU0hBUCAoU0hhcGxleSBBZGRpdGl2ZSBFeHBsYW5hdGlvbnMpKioNCiMjIyAqKjEuMSBXaGF0IGlzIFNIQVA/KioNClNIQVAgcHJvdmlkZXMgKipnYW1lLXRoZW9yZXRpYyBleHBsYW5hdGlvbnMqKiBvZiBtYWNoaW5lIGxlYXJuaW5nIG1vZGVsIHByZWRpY3Rpb25zLiBJdCBjYWxjdWxhdGVzIGhvdyBtdWNoIGVhY2ggZmVhdHVyZSAqKmNvbnRyaWJ1dGVzIHRvIGFuIGluZGl2aWR1YWwgcHJlZGljdGlvbioqIGFuZCBleHBsYWlucyB3aGV0aGVyIGl0IHB1c2hlcyB0aGUgcHJlZGljdGlvbiAqKmhpZ2hlciBvciBsb3dlcioqLg0KDQrinJQgKipJbnRlcnByZXRhYmlsaXR5Kio6IFVuZGVyc3RhbmQgaG93IGVhY2ggZmVhdHVyZSBhZmZlY3RzIHByZWRpY3Rpb25zLiAgDQrinJQgKipGZWF0dXJlIEluZmx1ZW5jZSoqOiBJZGVudGlmeSBpbXBvcnRhbnQgZmVhdHVyZXMgZm9yIGRlY2lzaW9uLW1ha2luZy4gIA0K4pyUICoqV29ya3Mgd2l0aCBBbnkgTW9kZWwqKjogU0hBUCBzdXBwb3J0cyBYR0Jvb3N0LCBMaWdodEdCTSwgYW5kIG90aGVyIG1vZGVscy4NCg0KLS0tDQoNCiMjIyAqKjEuMiBJbXBsZW1lbnRpbmcgU0hBUCBpbiBYR0Jvb3N0KioNCiMjIyMgKipTdGVwIDE6IEluc3RhbGwgU0hBUCoqDQpgYGBweXRob24NCnBpcCBpbnN0YWxsIHNoYXANCmBgYA0KDQojIyMjICoqU3RlcCAyOiBJbXBvcnQgTGlicmFyaWVzKioNCmBgYHB5dGhvbg0KaW1wb3J0IHNoYXANCmltcG9ydCB4Z2Jvb3N0IGFzIHhnYg0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQpmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IGxvYWRfYm9zdG9uDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCB0cmFpbl90ZXN0X3NwbGl0DQpgYGANCg0KIyMjIyAqKlN0ZXAgMzogTG9hZCBhbmQgVHJhaW4gYW4gWEdCb29zdCBNb2RlbCoqDQpgYGBweXRob24NCiMgTG9hZCBkYXRhc2V0DQpib3N0b24gPSBsb2FkX2Jvc3RvbigpDQpYLCB5ID0gYm9zdG9uLmRhdGEsIGJvc3Rvbi50YXJnZXQNCmZlYXR1cmVfbmFtZXMgPSBib3N0b24uZmVhdHVyZV9uYW1lcw0KDQojIFRyYWluLXRlc3Qgc3BsaXQNClhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0ID0gdHJhaW5fdGVzdF9zcGxpdChYLCB5LCB0ZXN0X3NpemU9MC4yLCByYW5kb21fc3RhdGU9NDIpDQoNCiMgVHJhaW4gYW4gWEdCb29zdCBtb2RlbA0KbW9kZWwgPSB4Z2IuWEdCUmVncmVzc29yKG9iamVjdGl2ZT0ncmVnOnNxdWFyZWRlcnJvcicsIG5fZXN0aW1hdG9ycz0xMDAsIG1heF9kZXB0aD00KQ0KbW9kZWwuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQpgYGANCg0KIyMjIyAqKlN0ZXAgNDogRXhwbGFpbiBNb2RlbCBQcmVkaWN0aW9ucyB3aXRoIFNIQVAqKg0KYGBgcHl0aG9uDQojIENyZWF0ZSBTSEFQIGV4cGxhaW5lcg0KZXhwbGFpbmVyID0gc2hhcC5FeHBsYWluZXIobW9kZWwpDQoNCiMgQ29tcHV0ZSBTSEFQIHZhbHVlcw0Kc2hhcF92YWx1ZXMgPSBleHBsYWluZXIoWF90ZXN0KQ0KDQojIFN1bW1hcnkgcGxvdCBvZiBmZWF0dXJlIGltcG9ydGFuY2UNCnNoYXAuc3VtbWFyeV9wbG90KHNoYXBfdmFsdWVzLCBYX3Rlc3QsIGZlYXR1cmVfbmFtZXM9ZmVhdHVyZV9uYW1lcykNCmBgYA0K8J+agCAqKlNIQVAgc3VtbWFyeSBwbG90Kiogc2hvd3MgdGhlIGltcGFjdCBvZiBlYWNoIGZlYXR1cmUgb24gbW9kZWwgcHJlZGljdGlvbnMuDQoNCiMjIyMgKipTdGVwIDU6IEZvcmNlIFBsb3QgZm9yIEluZGl2aWR1YWwgUHJlZGljdGlvbioqDQpgYGBweXRob24NCiMgRm9yY2UgcGxvdCBmb3IgYSBzaW5nbGUgcHJlZGljdGlvbg0Kc2hhcC5mb3JjZV9wbG90KGV4cGxhaW5lci5leHBlY3RlZF92YWx1ZSwgc2hhcF92YWx1ZXNbMF0udmFsdWVzLCBYX3Rlc3RbMF0sIGZlYXR1cmVfbmFtZXM9ZmVhdHVyZV9uYW1lcykNCmBgYA0K8J+OryAqKkZvcmNlIHBsb3RzKiogaGVscCB2aXN1YWxpemUgaG93IGVhY2ggZmVhdHVyZSBtb3ZlcyB0aGUgcHJlZGljdGlvbiBoaWdoZXIgb3IgbG93ZXIuDQoNCiMjIyMgKipTdGVwIDY6IFNIQVAgRGVwZW5kZW5jZSBQbG90KioNCmBgYHB5dGhvbg0KIyBTSEFQIGRlcGVuZGVuY2UgcGxvdCBmb3IgYSBmZWF0dXJlDQpzaGFwLmRlcGVuZGVuY2VfcGxvdCgiUk0iLCBzaGFwX3ZhbHVlcy52YWx1ZXMsIFhfdGVzdCwgZmVhdHVyZV9uYW1lcz1mZWF0dXJlX25hbWVzKQ0KYGBgDQrwn5OKICoqRGVwZW5kZW5jZSBwbG90cyoqIHNob3cgaG93IGEgZmVhdHVyZSBpbnRlcmFjdHMgd2l0aCB0aGUgdGFyZ2V0Lg0KDQotLS0NCg0KIyAqKjIuIEFkdmFuY2VkIFhHQm9vc3QgVGVjaG5pcXVlcyoqDQojIyAqKjIuMSBFYXJseSBTdG9wcGluZyBpbiBYR0Jvb3N0KioNCkVhcmx5IHN0b3BwaW5nICoqcHJldmVudHMgb3ZlcmZpdHRpbmcqKiBieSBzdG9wcGluZyB0cmFpbmluZyB3aGVuIHRoZSBtb2RlbCBzdG9wcyBpbXByb3Zpbmcgb24gdmFsaWRhdGlvbiBkYXRhLg0KDQojIyMgKioyLjEuMSBIb3cgRWFybHkgU3RvcHBpbmcgV29ya3MqKg0KMS4gU3BsaXQgZGF0YSBpbnRvICoqdHJhaW5pbmcqKiBhbmQgKip2YWxpZGF0aW9uKiogc2V0cy4NCjIuIFRyYWNrIHBlcmZvcm1hbmNlIHVzaW5nIGFuICoqZXZhbHVhdGlvbiBtZXRyaWMqKi4NCjMuIFN0b3AgdHJhaW5pbmcgaWYgdGhlIG1ldHJpYyAqKnN0b3BzIGltcHJvdmluZyoqIGFmdGVyIGEgY2VydGFpbiBudW1iZXIgb2Ygcm91bmRzLg0KDQojIyMgKioyLjEuMiBJbXBsZW1lbnRpbmcgRWFybHkgU3RvcHBpbmcgaW4gWEdCb29zdCoqDQpgYGBweXRob24NCiMgQ29udmVydCB0byBETWF0cml4IGZvcm1hdA0KZHRyYWluID0geGdiLkRNYXRyaXgoWF90cmFpbiwgbGFiZWw9eV90cmFpbikNCmR0ZXN0ID0geGdiLkRNYXRyaXgoWF90ZXN0LCBsYWJlbD15X3Rlc3QpDQoNCiMgRGVmaW5lIHBhcmFtZXRlcnMNCnBhcmFtcyA9IHsNCiAgICAnb2JqZWN0aXZlJzogJ3JlZzpzcXVhcmVkZXJyb3InLA0KICAgICdsZWFybmluZ19yYXRlJzogMC4xLA0KICAgICdtYXhfZGVwdGgnOiA0LA0KICAgICdldmFsX21ldHJpYyc6ICdybXNlJw0KfQ0KDQojIFRyYWluIHdpdGggZWFybHkgc3RvcHBpbmcNCm1vZGVsID0geGdiLnRyYWluKHBhcmFtcywgZHRyYWluLCBudW1fYm9vc3Rfcm91bmQ9NTAwLCBldmFscz1bKGR0ZXN0LCAiZXZhbCIpXSwgDQogICAgICAgICAgICAgICAgICBlYXJseV9zdG9wcGluZ19yb3VuZHM9MjAsIHZlcmJvc2VfZXZhbD0xMCkNCmBgYA0KLSBgbnVtX2Jvb3N0X3JvdW5kPTUwMGA6IE1heGltdW0gdHJlZXMgdG8gdHJhaW4uDQotIGBlYXJseV9zdG9wcGluZ19yb3VuZHM9MjBgOiBTdG9wIHRyYWluaW5nIGlmIFJNU0UgZG9lc27igJl0IGltcHJvdmUgZm9yICoqMjAgY29uc2VjdXRpdmUgcm91bmRzKiouDQoNCuKchSAqKlJlc3VsdHMqKjogRWFybHkgc3RvcHBpbmcgKippbXByb3ZlcyBnZW5lcmFsaXphdGlvbiBhbmQgc3BlZWRzIHVwIHRyYWluaW5nKiouDQoNCi0tLQ0KDQojIyAqKjIuMiBDdXN0b20gTG9zcyBGdW5jdGlvbnMgaW4gWEdCb29zdCoqDQpYR0Jvb3N0IGFsbG93cyAqKmN1c3RvbSBsb3NzIGZ1bmN0aW9ucyoqIGZvciBzcGVjaWFsaXplZCB0YXNrcy4NCg0KIyMjICoqMi4yLjEgRXhhbXBsZTogSHViZXIgTG9zcyBGdW5jdGlvbioqDQpIdWJlciBMb3NzICoqY29tYmluZXMqKiBNZWFuIFNxdWFyZWQgRXJyb3IgKE1TRSkgYW5kIE1lYW4gQWJzb2x1dGUgRXJyb3IgKE1BRSkgZm9yICoqcm9idXN0IHJlZ3Jlc3Npb24qKi4NCg0KIyMjIyAqKkRlZmluZSBDdXN0b20gSHViZXIgTG9zcyoqDQpgYGBweXRob24NCmRlZiBodWJlcl9sb3NzKHByZWRzLCBkdHJhaW4pOg0KICAgIGRlbHRhID0gMS4wDQogICAgbGFiZWxzID0gZHRyYWluLmdldF9sYWJlbCgpDQogICAgcmVzaWR1YWwgPSBwcmVkcyAtIGxhYmVscw0KICAgIGNvbmRpdGlvbiA9IG5wLmFicyhyZXNpZHVhbCkgPD0gZGVsdGENCiAgICBncmFkaWVudCA9IG5wLndoZXJlKGNvbmRpdGlvbiwgcmVzaWR1YWwsIGRlbHRhICogbnAuc2lnbihyZXNpZHVhbCkpDQogICAgaGVzc2lhbiA9IG5wLndoZXJlKGNvbmRpdGlvbiwgMSwgMCkNCiAgICByZXR1cm4gZ3JhZGllbnQsIGhlc3NpYW4NCmBgYA0KDQojIyMjICoqVHJhaW4gWEdCb29zdCB3aXRoIEh1YmVyIExvc3MqKg0KYGBgcHl0aG9uDQojIERlZmluZSBwYXJhbWV0ZXJzDQpwYXJhbXMgPSB7DQogICAgJ29iamVjdGl2ZSc6ICdyZWc6c3F1YXJlZGVycm9yJywNCiAgICAnbGVhcm5pbmdfcmF0ZSc6IDAuMSwNCiAgICAnbWF4X2RlcHRoJzogNA0KfQ0KDQojIFRyYWluIG1vZGVsIHdpdGggY3VzdG9tIGxvc3MNCm1vZGVsID0geGdiLnRyYWluKHBhcmFtcywgZHRyYWluLCBudW1fYm9vc3Rfcm91bmQ9MTAwLCBvYmo9aHViZXJfbG9zcykNCmBgYA0KDQrinIUgKipXaHkgdXNlIGN1c3RvbSBsb3NzIGZ1bmN0aW9ucz8qKg0KLSBUYWlsb3IgbW9kZWxzIHRvICoqc3BlY2lmaWMgYnVzaW5lc3MgbmVlZHMqKi4NCi0gSW1wcm92ZSByb2J1c3RuZXNzIGFnYWluc3QgKipvdXRsaWVycyoqLg0KDQotLS0NCg0KIyMgKipGaW5hbCBTdW1tYXJ5KioNCiMjIyAqKlNIQVAgZm9yIE1vZGVsIEludGVycHJldGF0aW9uKioNCuKclCBJZGVudGlmaWVzICoqaW1wb3J0YW50IGZlYXR1cmVzKiouICANCuKclCBFeHBsYWlucyAqKmluZGl2aWR1YWwgcHJlZGljdGlvbnMqKi4gIA0K4pyUIFByb3ZpZGVzICoqZ2xvYmFsIGFuZCBsb2NhbCBmZWF0dXJlIGVmZmVjdHMqKi4NCg0KIyMjICoqQWR2YW5jZWQgWEdCb29zdCBUZWNobmlxdWVzKioNCuKclCAqKkVhcmx5IHN0b3BwaW5nKiogcHJldmVudHMgb3ZlcmZpdHRpbmcuICANCuKclCAqKkN1c3RvbSBsb3NzIGZ1bmN0aW9ucyoqIGFsbG93IGZvciBzcGVjaWFsaXplZCBtb2RlbHMuICANCg0KLS0tDQoNCiMjICoqR3JpZCBTZWFyY2ggYW5kIFJhbmRvbSBTZWFyY2ggZm9yIEh5cGVycGFyYW1ldGVyIFR1bmluZyBpbiBYR0Jvb3N0KioNCkh5cGVycGFyYW1ldGVyIHR1bmluZyBpcyBlc3NlbnRpYWwgZm9yIGltcHJvdmluZyBtb2RlbCBwZXJmb3JtYW5jZS4gKipHcmlkIFNlYXJjaCBhbmQgUmFuZG9tIFNlYXJjaCoqIGFyZSB0d28gY29tbW9uIG1ldGhvZHMgZm9yIG9wdGltaXppbmcgWEdCb29zdCBtb2RlbHMuDQoNCi0tLQ0KDQojICoqMS4gR3JpZCBTZWFyY2ggdnMuIFJhbmRvbSBTZWFyY2gqKg0KfCAqKk1ldGhvZCoqIHwgKipIb3cgSXQgV29ya3MqKiB8ICoqUHJvcyoqIHwgKipDb25zKiogfA0KfC0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS18LS0tLS0tLS0tLXwNCnwgKipHcmlkIFNlYXJjaCoqIHwgVGVzdHMgYWxsIHBvc3NpYmxlIGNvbWJpbmF0aW9ucyBvZiBoeXBlcnBhcmFtZXRlcnMuIHwgRXhoYXVzdGl2ZSwgZ3VhcmFudGVlcyBmaW5kaW5nIHRoZSBiZXN0IGNvbWJpbmF0aW9uLiB8IENvbXB1dGF0aW9uYWxseSBleHBlbnNpdmUsIHNsb3cgZm9yIGxhcmdlIHBhcmFtZXRlciBncmlkcy4gfA0KfCAqKlJhbmRvbSBTZWFyY2gqKiB8IFJhbmRvbWx5IHNlbGVjdHMgYSBzdWJzZXQgb2YgaHlwZXJwYXJhbWV0ZXIgY29tYmluYXRpb25zLiB8IEZhc3RlciB0aGFuIEdyaWQgU2VhcmNoLCBmaW5kcyBnb29kIGh5cGVycGFyYW1ldGVycyBxdWlja2x5LiB8IE1pZ2h0IG1pc3MgdGhlIGJlc3QgaHlwZXJwYXJhbWV0ZXJzIHNpbmNlIG5vdCBhbGwgYXJlIHRlc3RlZC4gfA0KDQotLS0NCg0KIyMgKioyLiBHcmlkIFNlYXJjaCBpbiBYR0Jvb3N0KioNCioqR3JpZCBTZWFyY2gqKiBzeXN0ZW1hdGljYWxseSBzZWFyY2hlcyB0aHJvdWdoIGFsbCBwb3NzaWJsZSBjb21iaW5hdGlvbnMgb2YgaHlwZXJwYXJhbWV0ZXJzIHRvIGZpbmQgdGhlIGJlc3QtcGVyZm9ybWluZyBtb2RlbC4NCg0KIyMjICoqMi4xIEltcGxlbWVudGluZyBHcmlkIFNlYXJjaCoqDQojIyMjICoqU3RlcCAxOiBJbXBvcnQgUmVxdWlyZWQgTGlicmFyaWVzKioNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgR3JpZFNlYXJjaENWDQppbXBvcnQgeGdib29zdCBhcyB4Z2INCmZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgbG9hZF9ib3N0b24NCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQNCmBgYA0KDQojIyMjICoqU3RlcCAyOiBMb2FkIGFuZCBQcmVwYXJlIERhdGEqKg0KYGBgcHl0aG9uDQojIExvYWQgZGF0YXNldA0KYm9zdG9uID0gbG9hZF9ib3N0b24oKQ0KWCwgeSA9IGJvc3Rvbi5kYXRhLCBib3N0b24udGFyZ2V0DQoNCiMgVHJhaW4tdGVzdCBzcGxpdA0KWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KFgsIHksIHRlc3Rfc2l6ZT0wLjIsIHJhbmRvbV9zdGF0ZT00MikNCmBgYA0KDQojIyMjICoqU3RlcCAzOiBEZWZpbmUgdGhlIEh5cGVycGFyYW1ldGVyIEdyaWQqKg0KYGBgcHl0aG9uDQojIERlZmluZSBYR0Jvb3N0IG1vZGVsDQp4Z2JfbW9kZWwgPSB4Z2IuWEdCUmVncmVzc29yKG9iamVjdGl2ZT0ncmVnOnNxdWFyZWRlcnJvcicpDQoNCiMgRGVmaW5lIGh5cGVycGFyYW1ldGVyIGdyaWQNCnBhcmFtX2dyaWQgPSB7DQogICAgJ2xlYXJuaW5nX3JhdGUnOiBbMC4wMSwgMC4xLCAwLjJdLA0KICAgICdtYXhfZGVwdGgnOiBbMywgNSwgN10sDQogICAgJ25fZXN0aW1hdG9ycyc6IFs1MCwgMTAwLCAyMDBdLA0KICAgICdzdWJzYW1wbGUnOiBbMC44LCAxLjBdLA0KICAgICdjb2xzYW1wbGVfYnl0cmVlJzogWzAuOCwgMS4wXQ0KfQ0KYGBgDQoNCiMjIyMgKipTdGVwIDQ6IFBlcmZvcm0gR3JpZCBTZWFyY2gqKg0KYGBgcHl0aG9uDQpncmlkX3NlYXJjaCA9IEdyaWRTZWFyY2hDVigNCiAgICBlc3RpbWF0b3I9eGdiX21vZGVsLCANCiAgICBwYXJhbV9ncmlkPXBhcmFtX2dyaWQsIA0KICAgIHNjb3Jpbmc9J25lZ19tZWFuX3NxdWFyZWRfZXJyb3InLCANCiAgICBjdj01LCANCiAgICB2ZXJib3NlPTENCikNCg0KIyBUcmFpbiBtb2RlbA0KZ3JpZF9zZWFyY2guZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCiMgUHJpbnQgYmVzdCBwYXJhbWV0ZXJzDQpwcmludCgiQmVzdCBQYXJhbWV0ZXJzOiIsIGdyaWRfc2VhcmNoLmJlc3RfcGFyYW1zXykNCmBgYA0K8J+TjCAqKldoYXQgaGFwcGVucyBoZXJlPyoqDQotICoqRXhoYXVzdGl2ZWx5IHRlc3RzIGFsbCBjb21iaW5hdGlvbnMqKiBmcm9tIGBwYXJhbV9ncmlkYC4NCi0gVXNlcyAqKmNyb3NzLXZhbGlkYXRpb24gKGN2PTUpKiogdG8gZmluZCB0aGUgYmVzdCBoeXBlcnBhcmFtZXRlcnMuDQotICoqU2NvcmluZyBtZXRyaWMqKjogYG5lZ19tZWFuX3NxdWFyZWRfZXJyb3JgIChsb3dlciBpcyBiZXR0ZXIpLg0KLSBQcmludHMgdGhlICoqYmVzdCBjb21iaW5hdGlvbiBvZiBoeXBlcnBhcmFtZXRlcnMqKi4NCg0KLS0tDQoNCiMjICoqMy4gUmFuZG9tIFNlYXJjaCBpbiBYR0Jvb3N0KioNCioqUmFuZG9tIFNlYXJjaCoqIHJhbmRvbWx5IHNlbGVjdHMgaHlwZXJwYXJhbWV0ZXIgY29tYmluYXRpb25zIGluc3RlYWQgb2YgdGVzdGluZyBhbGwgcG9zc2liaWxpdGllcywgbWFraW5nIGl0IG1vcmUgZWZmaWNpZW50IGZvciBsYXJnZSBwYXJhbWV0ZXIgc3BhY2VzLg0KDQojIyMgKiozLjEgSW1wbGVtZW50aW5nIFJhbmRvbSBTZWFyY2gqKg0KIyMjIyAqKlN0ZXAgMTogSW1wb3J0IFJlcXVpcmVkIExpYnJhcmllcyoqDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IFJhbmRvbWl6ZWRTZWFyY2hDVg0KZnJvbSBzY2lweS5zdGF0cyBpbXBvcnQgdW5pZm9ybQ0KaW1wb3J0IG51bXB5IGFzIG5wDQpgYGANCg0KIyMjIyAqKlN0ZXAgMjogRGVmaW5lIHRoZSBIeXBlcnBhcmFtZXRlciBEaXN0cmlidXRpb24qKg0KSW5zdGVhZCBvZiBkZWZpbmluZyBhIGZpeGVkIHNldCBvZiB2YWx1ZXMsICoqUmFuZG9tIFNlYXJjaCBzYW1wbGVzIGZyb20gY29udGludW91cyBkaXN0cmlidXRpb25zKiouDQpgYGBweXRob24NCnBhcmFtX2Rpc3QgPSB7DQogICAgJ2xlYXJuaW5nX3JhdGUnOiB1bmlmb3JtKDAuMDEsIDAuMyksICAjIENvbnRpbnVvdXMgcmFuZ2UgZnJvbSAwLjAxIHRvIDAuMzENCiAgICAnbWF4X2RlcHRoJzogWzMsIDUsIDcsIDldLCAgIyBEaXNjcmV0ZSB2YWx1ZXMNCiAgICAnbl9lc3RpbWF0b3JzJzogWzUwLCAxMDAsIDIwMF0sICAjIERpc2NyZXRlIHZhbHVlcw0KICAgICdzdWJzYW1wbGUnOiB1bmlmb3JtKDAuNSwgMC41KSwgICMgRnJvbSAwLjUgdG8gMS4wDQogICAgJ2NvbHNhbXBsZV9ieXRyZWUnOiB1bmlmb3JtKDAuNSwgMC41KSAgIyBGcm9tIDAuNSB0byAxLjANCn0NCmBgYA0KDQojIyMjICoqU3RlcCAzOiBQZXJmb3JtIFJhbmRvbSBTZWFyY2gqKg0KYGBgcHl0aG9uDQpyYW5kb21fc2VhcmNoID0gUmFuZG9taXplZFNlYXJjaENWKA0KICAgIGVzdGltYXRvcj14Z2JfbW9kZWwsDQogICAgcGFyYW1fZGlzdHJpYnV0aW9ucz1wYXJhbV9kaXN0LA0KICAgIG5faXRlcj0yNSwgICMgTnVtYmVyIG9mIHJhbmRvbSBjb21iaW5hdGlvbnMgdG8gdHJ5DQogICAgc2NvcmluZz0nbmVnX21lYW5fc3F1YXJlZF9lcnJvcicsDQogICAgY3Y9NSwNCiAgICB2ZXJib3NlPTEsDQogICAgcmFuZG9tX3N0YXRlPTQyDQopDQoNCiMgVHJhaW4gbW9kZWwNCnJhbmRvbV9zZWFyY2guZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCiMgUHJpbnQgYmVzdCBwYXJhbWV0ZXJzDQpwcmludCgiQmVzdCBQYXJhbWV0ZXJzIGZyb20gUmFuZG9tIFNlYXJjaDoiLCByYW5kb21fc2VhcmNoLmJlc3RfcGFyYW1zXykNCmBgYA0KDQrwn5OMICoqV2hhdCBoYXBwZW5zIGhlcmU/KioNCi0gUmFuZG9tbHkgKipzYW1wbGVzIDI1IGRpZmZlcmVudCBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbnMqKi4NCi0gVXNlcyAqKmNvbnRpbnVvdXMgZGlzdHJpYnV0aW9ucyoqIGluc3RlYWQgb2YgcHJlZGVmaW5lZCB2YWx1ZXMuDQotIFJ1bnMgKio1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbioqIGZvciBlYWNoIHNhbXBsZWQgY29tYmluYXRpb24uDQotIE91dHB1dHMgdGhlICoqYmVzdCBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbioqLg0KDQotLS0NCg0KIyAqKjQuIEdyaWQgU2VhcmNoIHZzLiBSYW5kb20gU2VhcmNoOiBXaGljaCBPbmUgdG8gVXNlPyoqDQp8ICoqU2NlbmFyaW8qKiB8ICoqVXNlIEdyaWQgU2VhcmNoKiogfCAqKlVzZSBSYW5kb20gU2VhcmNoKiogfA0KfC0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tfA0KfCBTbWFsbCBkYXRhc2V0ICYgZmV3IGh5cGVycGFyYW1ldGVycyB8IOKchSBZZXMgfCDinYwgTm8gfA0KfCBMYXJnZSBkYXRhc2V0IHdpdGggbWFueSBoeXBlcnBhcmFtZXRlcnMgfCDinYwgTm8gfCDinIUgWWVzIHwNCnwgTmVlZCB0byBmaW5kIGJlc3QgaHlwZXJwYXJhbWV0ZXJzICoqcXVpY2tseSoqIHwg4p2MIE5vIHwg4pyFIFllcyB8DQp8IFdhbnQgdG8gKipndWFyYW50ZWUqKiBiZXN0IHBhcmFtZXRlcnMgfCDinIUgWWVzIHwg4p2MIE5vIHwNCg0KIyMjICoqQmVzdCBQcmFjdGljZTogVXNlIFJhbmRvbSBTZWFyY2ggRmlyc3QqKg0KMS4gKipTdGFydCB3aXRoIFJhbmRvbSBTZWFyY2gqKiB0byBxdWlja2x5IGZpbmQgYSBnb29kIHJhbmdlIG9mIHZhbHVlcy4NCjIuICoqUmVmaW5lIHdpdGggR3JpZCBTZWFyY2gqKiBvbiBhIHNtYWxsZXIgaHlwZXJwYXJhbWV0ZXIgc3BhY2UuDQoNCi0tLQ0KDQojICoqNS4gU3VtbWFyeSoqDQrinJQgKipHcmlkIFNlYXJjaCoqOiBFeGhhdXN0aXZlLCBiZXN0IGZvciBzbWFsbCBkYXRhc2V0cywgZ3VhcmFudGVlcyBvcHRpbWFsIHBhcmFtZXRlcnMuICANCuKclCAqKlJhbmRvbSBTZWFyY2gqKjogRmFzdGVyLCBiZXR0ZXIgZm9yIGxhcmdlIGRhdGFzZXRzLCBnb29kIGFwcHJveGltYXRpb24gb2Ygb3B0aW1hbCBwYXJhbWV0ZXJzLiAgDQrinJQgKipCZXN0IHByYWN0aWNlKio6ICoqVXNlIFJhbmRvbSBTZWFyY2ggZmlyc3QqKiwgdGhlbiBmaW5lLXR1bmUgd2l0aCAqKkdyaWQgU2VhcmNoKiouDQoNCi0tLQ0KDQoNCiMjICoqTmV4dCBUb3BpYzogSHlwZXJwYXJhbWV0ZXJzIGluIFhHQm9vc3QqKg0KDQojIyMgKioxLiBJbnRyb2R1Y3Rpb24gdG8gWEdCb29zdCBIeXBlcnBhcmFtZXRlcnMqKg0KSHlwZXJwYXJhbWV0ZXJzIGluIFhHQm9vc3QgY29udHJvbCBob3cgdGhlIG1vZGVsIGxlYXJucyBhbmQgZ2VuZXJhbGl6ZXMuIFByb3BlciB0dW5pbmcgb2YgdGhlc2UgcGFyYW1ldGVycyBjYW4gc2lnbmlmaWNhbnRseSBpbXByb3ZlIG1vZGVsIHBlcmZvcm1hbmNlLg0KDQpYR0Jvb3N0IGhhcyB0aHJlZSBtYWluIHR5cGVzIG9mIGh5cGVycGFyYW1ldGVyczoNCjEuICoqVHJlZS1yZWxhdGVkIHBhcmFtZXRlcnMqKiAoY29udHJvbCBob3cgdHJlZXMgYXJlIGJ1aWx0KS4NCjIuICoqQm9vc3RpbmctcmVsYXRlZCBwYXJhbWV0ZXJzKiogKGFmZmVjdCBsZWFybmluZyBhbmQgcmVndWxhcml6YXRpb24pLg0KMy4gKipNaXNjZWxsYW5lb3VzIHBhcmFtZXRlcnMqKiAoYWZmZWN0IGNvbXB1dGF0aW9uIGFuZCBvcHRpbWl6YXRpb24pLg0KDQotLS0NCg0KIyMgKioyLiBLZXkgWEdCb29zdCBIeXBlcnBhcmFtZXRlcnMqKg0KIyMjICoqMi4xIFRyZWUgU3RydWN0dXJlIEh5cGVycGFyYW1ldGVycyoqDQpUaGVzZSBwYXJhbWV0ZXJzIGNvbnRyb2wgdGhlICoqZGVwdGggYW5kIGNvbXBsZXhpdHkqKiBvZiBkZWNpc2lvbiB0cmVlcyBpbiBYR0Jvb3N0Lg0KDQp8IEh5cGVycGFyYW1ldGVyIHwgRGVzY3JpcHRpb24gfCBUeXBpY2FsIFJhbmdlIHwNCnwtLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tfA0KfCBgbWF4X2RlcHRoYCB8IE1heGltdW0gZGVwdGggb2YgdHJlZXMuIEhpZ2hlciB2YWx1ZXMgaW5jcmVhc2UgY29tcGxleGl0eS4gfCAzLTEwIHwNCnwgYG1pbl9jaGlsZF93ZWlnaHRgIHwgTWluaW11bSBzdW0gb2Ygd2VpZ2h0cyByZXF1aXJlZCB0byBzcGxpdCBhIG5vZGUuIFByZXZlbnRzIG92ZXJmaXR0aW5nLiB8IDEtMTAgfA0KfCBgZ2FtbWFgIHwgTWluaW11bSBsb3NzIHJlZHVjdGlvbiByZXF1aXJlZCBmb3IgZnVydGhlciB0cmVlIHBhcnRpdGlvbmluZy4gSGVscHMgcHJ1bmluZy4gfCAwLTUgfA0KfCBgY29sc2FtcGxlX2J5dHJlZWAgfCBGcmFjdGlvbiBvZiBmZWF0dXJlcyB1c2VkIGZvciBlYWNoIHRyZWUuIFJlZHVjZXMgb3ZlcmZpdHRpbmcuIHwgMC41LTEuMCB8DQp8IGBjb2xzYW1wbGVfYnlsZXZlbGAgfCBGcmFjdGlvbiBvZiBmZWF0dXJlcyB1c2VkIHBlciBzcGxpdC4gTW9yZSByYW5kb21uZXNzIGltcHJvdmVzIGdlbmVyYWxpemF0aW9uLiB8IDAuNS0xLjAgfA0KDQrwn5OMICoqQmVzdCBQcmFjdGljZToqKiAgDQotIFVzZSAqKmxvd2VyIGBtYXhfZGVwdGhgKiogZm9yIHNtYWxsIGRhdGFzZXRzLiAgDQotIEluY3JlYXNlIGBtaW5fY2hpbGRfd2VpZ2h0YCBhbmQgYGdhbW1hYCB0byBwcmV2ZW50ICoqb3ZlcmZpdHRpbmcqKi4gIA0KDQotLS0NCg0KIyMjICoqMi4yIEJvb3N0aW5nIEh5cGVycGFyYW1ldGVycyoqDQpUaGVzZSBwYXJhbWV0ZXJzIGNvbnRyb2wgKipob3cgdHJlZXMgYXJlIGJvb3N0ZWQqKiBkdXJpbmcgdHJhaW5pbmcuDQoNCnwgSHlwZXJwYXJhbWV0ZXIgfCBEZXNjcmlwdGlvbiB8IFR5cGljYWwgUmFuZ2UgfA0KfC0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS18DQp8IGBsZWFybmluZ19yYXRlYCAoZXRhKSB8IFN0ZXAgc2l6ZSBzaHJpbmthZ2UgdG8gcHJldmVudCBvdmVyZml0dGluZy4gTG93ZXIgdmFsdWVzIGltcHJvdmUgYWNjdXJhY3kgYnV0IG5lZWQgbW9yZSB0cmVlcy4gfCAwLjAxLTAuMyB8DQp8IGBuX2VzdGltYXRvcnNgIHwgTnVtYmVyIG9mIGJvb3N0aW5nIHJvdW5kcyAodHJlZXMpLiBNb3JlIHRyZWVzIGNhcHR1cmUgbW9yZSBwYXR0ZXJucy4gfCA1MC01MDAgfA0KfCBgc3Vic2FtcGxlYCB8IEZyYWN0aW9uIG9mIHRyYWluaW5nIHNhbXBsZXMgdXNlZCBwZXIgdHJlZS4gUmVkdWNlcyBvdmVyZml0dGluZy4gfCAwLjUtMS4wIHwNCg0K8J+TjCAqKkJlc3QgUHJhY3RpY2U6KiogIA0KLSAqKkxvd2VyIGBsZWFybmluZ19yYXRlYCoqIChlLmcuLCBgMC4xYCkgYW5kICoqaW5jcmVhc2UgYG5fZXN0aW1hdG9yc2AqKiAoZS5nLiwgYDEwMCtgKSBmb3IgYmV0dGVyIGFjY3VyYWN5LiAgDQotIFVzZSAqKmxvd2VyIGBzdWJzYW1wbGVgKiogKGUuZy4sIGAwLjhgKSB0byBpbnRyb2R1Y2UgcmFuZG9tbmVzcyBhbmQgaW1wcm92ZSBnZW5lcmFsaXphdGlvbi4NCg0KLS0tDQoNCiMjIyAqKjIuMyBSZWd1bGFyaXphdGlvbiBIeXBlcnBhcmFtZXRlcnMqKg0KVGhlc2UgcGFyYW1ldGVycyAqKnByZXZlbnQgb3ZlcmZpdHRpbmcqKiBieSBwZW5hbGl6aW5nIGNvbXBsZXggbW9kZWxzLg0KDQp8IEh5cGVycGFyYW1ldGVyIHwgRGVzY3JpcHRpb24gfCBUeXBpY2FsIFJhbmdlIHwNCnwtLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tfA0KfCBgbGFtYmRhYCB8IEwyIHJlZ3VsYXJpemF0aW9uIChSaWRnZSByZWdyZXNzaW9uKSB0byBwcmV2ZW50IGxhcmdlIHdlaWdodHMuIHwgMC0xMCB8DQp8IGBhbHBoYWAgfCBMMSByZWd1bGFyaXphdGlvbiAoTGFzc28gcmVncmVzc2lvbikgZm9yIGZlYXR1cmUgc2VsZWN0aW9uLiB8IDAtNSB8DQoNCvCfk4wgKipCZXN0IFByYWN0aWNlOioqICANCi0gSW5jcmVhc2UgYGxhbWJkYWAgYW5kIGBhbHBoYWAgZm9yICoqbm9pc3kgZGF0YXNldHMqKi4gIA0KLSBVc2UgYGFscGhhID4gMGAgZm9yICoqYXV0b21hdGljIGZlYXR1cmUgc2VsZWN0aW9uKiouDQoNCi0tLQ0KDQojIyMgKioyLjQgQ29tcHV0YXRpb25hbCBIeXBlcnBhcmFtZXRlcnMqKg0KVGhlc2UgcGFyYW1ldGVycyBvcHRpbWl6ZSAqKm1lbW9yeSBhbmQgc3BlZWQqKi4NCg0KfCBIeXBlcnBhcmFtZXRlciB8IERlc2NyaXB0aW9uIHwgVHlwaWNhbCBSYW5nZSB8DQp8LS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLXwNCnwgYHRyZWVfbWV0aG9kYCB8IEFsZ29yaXRobSBmb3IgdHJhaW5pbmcgdHJlZXMuIGBoaXN0YCBzcGVlZHMgdXAgdHJhaW5pbmcgb24gbGFyZ2UgZGF0YXNldHMuIHwgYGF1dG9gLCBgaGlzdGAsIGBncHVfaGlzdGAgfA0KfCBgZ3B1X2lkYCB8IFVzZSBHUFUgZm9yIGFjY2VsZXJhdGlvbi4gfCBgMGAgKGRlZmF1bHQgR1BVKSB8DQoNCvCfk4wgKipCZXN0IFByYWN0aWNlOioqICANCi0gVXNlICoqYHRyZWVfbWV0aG9kPWhpc3RgKiogZm9yIGxhcmdlIGRhdGFzZXRzLiAgDQotIFNldCBgZ3B1X2lkPTBgIHRvICoqZW5hYmxlIEdQVSBhY2NlbGVyYXRpb24qKi4NCg0KLS0tDQoNCiMjICoqMy4gSW1wbGVtZW50aW5nIEh5cGVycGFyYW1ldGVyIFR1bmluZyBpbiBYR0Jvb3N0KioNCiMjIyAqKjMuMSBTZXR0aW5nIERlZmF1bHQgSHlwZXJwYXJhbWV0ZXJzKioNCmBgYHB5dGhvbg0KaW1wb3J0IHhnYm9vc3QgYXMgeGdiDQoNCiMgRGVmaW5lIGRlZmF1bHQgcGFyYW1ldGVycw0KcGFyYW1zID0gew0KICAgICdvYmplY3RpdmUnOiAncmVnOnNxdWFyZWRlcnJvcicsDQogICAgJ2xlYXJuaW5nX3JhdGUnOiAwLjEsDQogICAgJ21heF9kZXB0aCc6IDYsDQogICAgJ21pbl9jaGlsZF93ZWlnaHQnOiAxLA0KICAgICdnYW1tYSc6IDAsDQogICAgJ2NvbHNhbXBsZV9ieXRyZWUnOiAwLjgsDQogICAgJ3N1YnNhbXBsZSc6IDAuOCwNCiAgICAnbGFtYmRhJzogMSwNCiAgICAnYWxwaGEnOiAwDQp9DQoNCiMgVHJhaW4gbW9kZWwgd2l0aCBkZWZhdWx0IHBhcmFtZXRlcnMNCm1vZGVsID0geGdiLlhHQlJlZ3Jlc3NvcigqKnBhcmFtcywgbl9lc3RpbWF0b3JzPTEwMCkNCmBgYA0KDQotLS0NCg0KIyMjICoqMy4yIEF1dG9tYXRpbmcgSHlwZXJwYXJhbWV0ZXIgVHVuaW5nKioNCkluc3RlYWQgb2YgbWFudWFsbHkgdHJ5aW5nIGRpZmZlcmVudCB2YWx1ZXMsIHdlIGNhbiB1c2UgKipHcmlkIFNlYXJjaCBhbmQgUmFuZG9tIFNlYXJjaCoqLg0KDQojIyMjICoqR3JpZCBTZWFyY2ggZm9yIEh5cGVycGFyYW1ldGVyIE9wdGltaXphdGlvbioqDQpgYGBweXRob24NCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IEdyaWRTZWFyY2hDVg0KDQpwYXJhbV9ncmlkID0gew0KICAgICdtYXhfZGVwdGgnOiBbMywgNSwgN10sDQogICAgJ2xlYXJuaW5nX3JhdGUnOiBbMC4wMSwgMC4xLCAwLjJdLA0KICAgICduX2VzdGltYXRvcnMnOiBbNTAsIDEwMCwgMjAwXSwNCiAgICAnc3Vic2FtcGxlJzogWzAuOCwgMS4wXSwNCiAgICAnY29sc2FtcGxlX2J5dHJlZSc6IFswLjgsIDEuMF0NCn0NCg0KZ3JpZF9zZWFyY2ggPSBHcmlkU2VhcmNoQ1YoeGdiLlhHQlJlZ3Jlc3NvcihvYmplY3RpdmU9J3JlZzpzcXVhcmVkZXJyb3InKSwgcGFyYW1fZ3JpZCwgc2NvcmluZz0nbmVnX21lYW5fc3F1YXJlZF9lcnJvcicsIGN2PTUsIHZlcmJvc2U9MSkNCmdyaWRfc2VhcmNoLmZpdChYX3RyYWluLCB5X3RyYWluKQ0KDQpwcmludCgiQmVzdCBQYXJhbWV0ZXJzOiIsIGdyaWRfc2VhcmNoLmJlc3RfcGFyYW1zXykNCmBgYA0KDQotLS0NCg0KIyMjICoqMy4zIFJhbmRvbSBTZWFyY2ggZm9yIEZhc3RlciBIeXBlcnBhcmFtZXRlciBUdW5pbmcqKg0KYGBgcHl0aG9uDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCBSYW5kb21pemVkU2VhcmNoQ1YNCmZyb20gc2NpcHkuc3RhdHMgaW1wb3J0IHVuaWZvcm0NCg0KcGFyYW1fZGlzdCA9IHsNCiAgICAnbGVhcm5pbmdfcmF0ZSc6IHVuaWZvcm0oMC4wMSwgMC4yKSwNCiAgICAnbWF4X2RlcHRoJzogWzMsIDUsIDddLA0KICAgICduX2VzdGltYXRvcnMnOiBbNTAsIDEwMCwgMjAwXSwNCiAgICAnc3Vic2FtcGxlJzogdW5pZm9ybSgwLjUsIDAuNSksDQogICAgJ2NvbHNhbXBsZV9ieXRyZWUnOiB1bmlmb3JtKDAuNSwgMC41KQ0KfQ0KDQpyYW5kb21fc2VhcmNoID0gUmFuZG9taXplZFNlYXJjaENWKHhnYi5YR0JSZWdyZXNzb3Iob2JqZWN0aXZlPSdyZWc6c3F1YXJlZGVycm9yJyksIHBhcmFtX2Rpc3RyaWJ1dGlvbnM9cGFyYW1fZGlzdCwgbl9pdGVyPTI1LCBzY29yaW5nPSduZWdfbWVhbl9zcXVhcmVkX2Vycm9yJywgY3Y9NSwgdmVyYm9zZT0xKQ0KcmFuZG9tX3NlYXJjaC5maXQoWF90cmFpbiwgeV90cmFpbikNCg0KcHJpbnQoIkJlc3QgUGFyYW1ldGVycyBmcm9tIFJhbmRvbSBTZWFyY2g6IiwgcmFuZG9tX3NlYXJjaC5iZXN0X3BhcmFtc18pDQpgYGANCg0KLS0tDQoNCiMjICoqNC4gQmVzdCBQcmFjdGljZXMgZm9yIEh5cGVycGFyYW1ldGVyIFR1bmluZyoqDQrinJQgU3RhcnQgd2l0aCAqKlJhbmRvbSBTZWFyY2gqKiB0byBuYXJyb3cgZG93biB0aGUgYmVzdCByYW5nZXMuICANCuKclCBVc2UgKipHcmlkIFNlYXJjaCoqIGZvciBmaW5lLXR1bmluZyBvbmNlIHlvdSBmaW5kIGEgZ29vZCByYW5nZS4gIA0K4pyUIFVzZSAqKmVhcmx5IHN0b3BwaW5nKiogdG8gZmluZCB0aGUgb3B0aW1hbCBudW1iZXIgb2YgdHJlZXMuICANCuKclCBDb25zaWRlciAqKkJheWVzaWFuIE9wdGltaXphdGlvbioqIGZvciBlZmZpY2llbnQgdHVuaW5nLiAgDQoNCi0tLQ0KDQoNCg0KDQojIyAqKlhHQm9vc3QgRGVtbyAxICYgMiBTdHVkeSBHdWlkZSoqDQoNClRoZXNlIGRlbW9zIHdhbGsgdGhyb3VnaCByZWFsLXdvcmxkIGFwcGxpY2F0aW9ucyBvZiAqKlhHQm9vc3QqKiwgY292ZXJpbmc6DQotICoqRGVtbyAxOioqIFhHQm9vc3QgZm9yICoqY2xhc3NpZmljYXRpb24qKiAoaGFuZHdyaXR0ZW4gZGlnaXQgcmVjb2duaXRpb24pLg0KLSAqKkRlbW8gMjoqKiBYR0Jvb3N0IGZvciAqKnJlZ3Jlc3Npb24qKiAoaG91c2luZyBwcmljZSBwcmVkaWN0aW9uKS4NCg0KLS0tDQoNCiMjICoqWEdCb29zdCBEZW1vIDE6IEhhbmR3cml0dGVuIERpZ2l0IENsYXNzaWZpY2F0aW9uKioNCiMjIyAqKjEuIE92ZXJ2aWV3KioNCkluIHRoaXMgZGVtbywgd2UgdXNlICoqWEdCb29zdCBmb3IgY2xhc3NpZmljYXRpb24qKiBvbiB0aGUgKipEaWdpdHMgZGF0YXNldCoqIGZyb20gYHNrbGVhcm5gLiBUaGUgZGF0YXNldCBjb25zaXN0cyBvZiAqKjjDlzggcGl4ZWwgZ3JheXNjYWxlIGltYWdlcyBvZiBoYW5kd3JpdHRlbiBkaWdpdHMgKDAtOSkuKioNCg0KLS0tDQoNCiMjIyAqKjIuIFN0ZXBzIGZvciBJbXBsZW1lbnRpbmcgWEdCb29zdCBmb3IgQ2xhc3NpZmljYXRpb24qKg0KIyMjIyAqKlN0ZXAgMTogSW1wb3J0IFJlcXVpcmVkIExpYnJhcmllcyoqDQpgYGBweXRob24NCmltcG9ydCBudW1weSBhcyBucA0KaW1wb3J0IHhnYm9vc3QgYXMgeGdiDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQpmcm9tIHNrbGVhcm4gaW1wb3J0IGRhdGFzZXRzDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCB0cmFpbl90ZXN0X3NwbGl0DQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgYWNjdXJhY3lfc2NvcmUNCmBgYA0KDQojIyMjICoqU3RlcCAyOiBMb2FkIGFuZCBWaXN1YWxpemUgdGhlIERhdGEqKg0KYGBgcHl0aG9uDQojIExvYWQgRGlnaXRzIGRhdGFzZXQNCmRpZ2l0cyA9IGRhdGFzZXRzLmxvYWRfZGlnaXRzKCkNClgsIHkgPSBkaWdpdHMuZGF0YSwgZGlnaXRzLnRhcmdldCAgIyBGZWF0dXJlcyAocGl4ZWwgdmFsdWVzKSBhbmQgbGFiZWxzDQoNCiMgU2hvdyBhbiBleGFtcGxlIGRpZ2l0DQpwbHQuZ3JheSgpDQpwbHQubWF0c2hvdyhkaWdpdHMuaW1hZ2VzWzBdKSAgIyBTaG93IGZpcnN0IGltYWdlDQpwbHQuc2hvdygpDQpwcmludCgiTGFiZWw6IiwgZGlnaXRzLnRhcmdldFswXSkgICMgUHJpbnQgY29ycmVzcG9uZGluZyBsYWJlbA0KYGBgDQoNCvCfk4wgKipFeHBsYW5hdGlvbjoqKg0KLSBUaGUgZGF0YXNldCBjb250YWlucyAqKjjDlzggcGl4ZWwgZ3JheXNjYWxlIGltYWdlcyoqIG9mIGhhbmR3cml0dGVuIGRpZ2l0cyAoMC05KS4NCi0gRWFjaCBpbWFnZSBpcyAqKmZsYXR0ZW5lZCoqIGludG8gYSA2NC1kaW1lbnNpb25hbCB2ZWN0b3IgZm9yIHByb2Nlc3NpbmcuDQoNCiMjIyMgKipTdGVwIDM6IFNwbGl0IERhdGEgaW50byBUcmFpbmluZyBhbmQgVGVzdCBTZXRzKioNCmBgYHB5dGhvbg0KWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KFgsIHksIHRlc3Rfc2l6ZT0wLjIsIHJhbmRvbV9zdGF0ZT00MikNCmBgYA0KDQrwn5OMICoqV2h5IHNwbGl0PyoqICANCi0gODAlIG9mIHRoZSBkYXRhIGlzIHVzZWQgZm9yIHRyYWluaW5nLiAgDQotIDIwJSBpcyB1c2VkIGZvciAqKnRlc3RpbmcgdGhlIG1vZGVsJ3MgYWNjdXJhY3kgb24gdW5zZWVuIGRhdGEqKi4NCg0KIyMjIyAqKlN0ZXAgNDogQ29udmVydCBEYXRhIGludG8gWEdCb29zdOKAmXMgRE1hdHJpeCBGb3JtYXQqKg0KYGBgcHl0aG9uDQpkdHJhaW4gPSB4Z2IuRE1hdHJpeChYX3RyYWluLCBsYWJlbD15X3RyYWluKQ0KZHRlc3QgPSB4Z2IuRE1hdHJpeChYX3Rlc3QsIGxhYmVsPXlfdGVzdCkNCmBgYA0K8J+TjCAqKldoeSB1c2UgRE1hdHJpeD8qKiAgDQotIERNYXRyaXggb3B0aW1pemVzIGNvbXB1dGF0aW9uIGZvciAqKmZhc3RlciB0cmFpbmluZyBhbmQgcHJlZGljdGlvbioqLg0KDQojIyMjICoqU3RlcCA1OiBEZWZpbmUgTW9kZWwgUGFyYW1ldGVycyoqDQpgYGBweXRob24NCnBhcmFtcyA9IHsNCiAgICAnb2JqZWN0aXZlJzogJ211bHRpOnNvZnRtYXgnLCAgIyBNdWx0aS1jbGFzcyBjbGFzc2lmaWNhdGlvbg0KICAgICdudW1fY2xhc3MnOiAxMCwgICMgMTAgY2xhc3NlcyAoZGlnaXRzIDAtOSkNCiAgICAnZXZhbF9tZXRyaWMnOiAnbWxvZ2xvc3MnLCAgIyBNdWx0aS1jbGFzcyBsb2cgbG9zcw0KICAgICdtYXhfZGVwdGgnOiA1LA0KICAgICdsZWFybmluZ19yYXRlJzogMC4xLA0KICAgICduX2VzdGltYXRvcnMnOiAxMDANCn0NCmBgYA0KDQojIyMjICoqU3RlcCA2OiBUcmFpbiB0aGUgWEdCb29zdCBNb2RlbCoqDQpgYGBweXRob24NCm1vZGVsID0geGdiLnRyYWluKHBhcmFtcywgZHRyYWluLCBudW1fYm9vc3Rfcm91bmQ9MTAwKQ0KYGBgDQoNCiMjIyMgKipTdGVwIDc6IE1ha2UgUHJlZGljdGlvbnMgYW5kIEV2YWx1YXRlKioNCmBgYHB5dGhvbg0KeV9wcmVkID0gbW9kZWwucHJlZGljdChkdGVzdCkNCg0KIyBDYWxjdWxhdGUgYWNjdXJhY3kNCmFjY3VyYWN5ID0gYWNjdXJhY3lfc2NvcmUoeV90ZXN0LCB5X3ByZWQpDQpwcmludChmIlhHQm9vc3QgQ2xhc3NpZmljYXRpb24gQWNjdXJhY3k6IHthY2N1cmFjeTouNGZ9IikNCmBgYA0KDQrwn5OMICoqV2hhdCBoYXBwZW5zIGhlcmU/KioNCi0gVGhlICoqdHJhaW5lZCBtb2RlbCBwcmVkaWN0cyB0aGUgZGlnaXQgbGFiZWxzKiogb24gdGhlIHRlc3Qgc2V0Lg0KLSBXZSBjYWxjdWxhdGUgKiphY2N1cmFjeSoqIHRvIGV2YWx1YXRlIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlLg0KDQrinIUgKipFeHBlY3RlZCBSZXN1bHQ6KiogQXJvdW5kICoqOTctOTklIGFjY3VyYWN5Kiogb24gdGhlIHRlc3Qgc2V0Lg0KDQotLS0NCg0KIyMgKipYR0Jvb3N0IERlbW8gMjogUmVncmVzc2lvbiBvbiBCb3N0b24gSG91c2luZyBEYXRhKioNCiMjIyAqKjEuIE92ZXJ2aWV3KioNClRoaXMgZGVtbyBhcHBsaWVzICoqWEdCb29zdCBmb3IgcmVncmVzc2lvbioqIHVzaW5nIHRoZSAqKkJvc3RvbiBIb3VzaW5nIERhdGFzZXQqKiwgcHJlZGljdGluZyAqKmhvdXNpbmcgcHJpY2VzKiogYmFzZWQgb24gZmVhdHVyZXMgbGlrZSBjcmltZSByYXRlLCBudW1iZXIgb2Ygcm9vbXMsIGFuZCBsb2NhdGlvbi4NCg0KLS0tDQoNCiMjIyAqKjIuIFN0ZXBzIGZvciBJbXBsZW1lbnRpbmcgWEdCb29zdCBmb3IgUmVncmVzc2lvbioqDQojIyMjICoqU3RlcCAxOiBJbXBvcnQgUmVxdWlyZWQgTGlicmFyaWVzKioNCmBgYHB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgeGdib29zdCBhcyB4Z2INCmZyb20gc2tsZWFybi5kYXRhc2V0cyBpbXBvcnQgbG9hZF9ib3N0b24NCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQNCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBtZWFuX3NxdWFyZWRfZXJyb3INCmBgYA0KDQojIyMjICoqU3RlcCAyOiBMb2FkIGFuZCBFeHBsb3JlIHRoZSBEYXRhKioNCmBgYHB5dGhvbg0KIyBMb2FkIGRhdGFzZXQNCmJvc3RvbiA9IGxvYWRfYm9zdG9uKCkNClgsIHkgPSBib3N0b24uZGF0YSwgYm9zdG9uLnRhcmdldCAgIyBGZWF0dXJlcyBhbmQgdGFyZ2V0IChob3VzZSBwcmljZXMpDQoNCiMgUHJpbnQgZmVhdHVyZSBuYW1lcw0KcHJpbnQoIkZlYXR1cmUgTmFtZXM6IiwgYm9zdG9uLmZlYXR1cmVfbmFtZXMpDQpgYGANCg0K8J+TjCAqKkRhdGFzZXQgRGV0YWlsczoqKg0KLSBUaGUgZGF0YXNldCBjb250YWlucyAqKjEzIGZlYXR1cmVzKiogcmVsYXRlZCB0byBob3VzaW5nLg0KLSBUaGUgdGFyZ2V0IHZhcmlhYmxlICoqeSoqIHJlcHJlc2VudHMgKipob3VzZSBwcmljZXMqKi4NCg0KIyMjIyAqKlN0ZXAgMzogU3BsaXQgRGF0YSBpbnRvIFRyYWluaW5nIGFuZCBUZXN0IFNldHMqKg0KYGBgcHl0aG9uDQpYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoWCwgeSwgdGVzdF9zaXplPTAuMiwgcmFuZG9tX3N0YXRlPTQyKQ0KYGBgDQoNCvCfk4wgKipXaHkgc3BsaXQ/KiogIA0KLSA4MCUgb2YgdGhlIGRhdGEgaXMgdXNlZCBmb3IgKip0cmFpbmluZyoqLiAgDQotIDIwJSBpcyB1c2VkIGZvciAqKmV2YWx1YXRpbmcgcHJlZGljdGlvbnMgb24gdW5zZWVuIGRhdGEqKi4NCg0KIyMjIyAqKlN0ZXAgNDogQ29udmVydCBEYXRhIGludG8gWEdCb29zdOKAmXMgRE1hdHJpeCBGb3JtYXQqKg0KYGBgcHl0aG9uDQpkdHJhaW4gPSB4Z2IuRE1hdHJpeChYX3RyYWluLCBsYWJlbD15X3RyYWluKQ0KZHRlc3QgPSB4Z2IuRE1hdHJpeChYX3Rlc3QsIGxhYmVsPXlfdGVzdCkNCmBgYA0K8J+TjCAqKldoeSB1c2UgRE1hdHJpeD8qKiAgDQotIEltcHJvdmVzICoqc3BlZWQqKiBhbmQgKiptZW1vcnkgZWZmaWNpZW5jeSoqIGR1cmluZyB0cmFpbmluZy4NCg0KIyMjIyAqKlN0ZXAgNTogRGVmaW5lIE1vZGVsIFBhcmFtZXRlcnMqKg0KYGBgcHl0aG9uDQpwYXJhbXMgPSB7DQogICAgJ29iamVjdGl2ZSc6ICdyZWc6c3F1YXJlZGVycm9yJywgICMgUmVncmVzc2lvbiB0YXNrDQogICAgJ2V2YWxfbWV0cmljJzogJ3Jtc2UnLCAgIyBSb290IE1lYW4gU3F1YXJlZCBFcnJvcg0KICAgICdtYXhfZGVwdGgnOiA0LA0KICAgICdsZWFybmluZ19yYXRlJzogMC4xLA0KICAgICduX2VzdGltYXRvcnMnOiAxMDANCn0NCmBgYA0KDQojIyMjICoqU3RlcCA2OiBUcmFpbiB0aGUgWEdCb29zdCBNb2RlbCoqDQpgYGBweXRob24NCm1vZGVsID0geGdiLnRyYWluKHBhcmFtcywgZHRyYWluLCBudW1fYm9vc3Rfcm91bmQ9MTAwKQ0KYGBgDQoNCiMjIyMgKipTdGVwIDc6IE1ha2UgUHJlZGljdGlvbnMgYW5kIEV2YWx1YXRlKioNCmBgYHB5dGhvbg0KeV9wcmVkID0gbW9kZWwucHJlZGljdChkdGVzdCkNCg0KIyBDYWxjdWxhdGUgUk1TRQ0Kcm1zZSA9IG5wLnNxcnQobWVhbl9zcXVhcmVkX2Vycm9yKHlfdGVzdCwgeV9wcmVkKSkNCnByaW50KGYiWEdCb29zdCBSZWdyZXNzaW9uIFJNU0U6IHtybXNlOi40Zn0iKQ0KYGBgDQoNCvCfk4wgKipXaGF0IGhhcHBlbnMgaGVyZT8qKg0KLSBUaGUgdHJhaW5lZCBtb2RlbCAqKnByZWRpY3RzIGhvdXNlIHByaWNlcyoqIGZvciB0aGUgdGVzdCBzZXQuDQotIFJNU0UgKFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yKSBtZWFzdXJlcyAqKnByZWRpY3Rpb24gZXJyb3IqKi4NCg0K4pyFICoqRXhwZWN0ZWQgUmVzdWx0OioqIFJNU0UgKipiZXR3ZWVuIDMtNSoqLCBkZXBlbmRpbmcgb24gZGF0YXNldCBzcGxpdHMuDQoNCi0tLQ0KDQojIyAqKjMuIEtleSBUYWtlYXdheXMgZnJvbSBCb3RoIERlbW9zKioNCiMjIyAqKlhHQm9vc3QgZm9yIENsYXNzaWZpY2F0aW9uKioNCuKclCBXb3JrcyB3ZWxsIHdpdGggKipoaWdoLWRpbWVuc2lvbmFsIHN0cnVjdHVyZWQgZGF0YSoqIChsaWtlIGltYWdlcykuICANCuKclCBIYW5kbGVzICoqbXVsdGktY2xhc3MgY2xhc3NpZmljYXRpb24gZWZmaWNpZW50bHkqKi4gIA0K4pyUICoqRmFzdCB0cmFpbmluZyB0aW1lKiogY29tcGFyZWQgdG8gb3RoZXIgYm9vc3RpbmcgbWV0aG9kcy4gIA0KDQojIyMgKipYR0Jvb3N0IGZvciBSZWdyZXNzaW9uKioNCuKclCBTdWl0YWJsZSBmb3IgKipjb250aW51b3VzIHZhbHVlIHByZWRpY3Rpb25zKiouICANCuKclCBIYW5kbGVzICoqY29tcGxleCwgbm9ubGluZWFyIHJlbGF0aW9uc2hpcHMqKiB3ZWxsLiAgDQrinJQgUmVkdWNlcyBvdmVyZml0dGluZyB2aWEgKipyZWd1bGFyaXphdGlvbioqIGFuZCAqKnRyZWUgcHJ1bmluZyoqLiAgDQoNCi0tLQ0KDQojIyAqKjQuIFN1bW1hcnkqKg0K4pyUICoqRGVtbyAxOioqIFhHQm9vc3QgKipjbGFzc2lmaWNhdGlvbioqIGZvciBoYW5kd3JpdHRlbiBkaWdpdCByZWNvZ25pdGlvbi4gIA0K4pyUICoqRGVtbyAyOioqIFhHQm9vc3QgKipyZWdyZXNzaW9uKiogZm9yIGhvdXNpbmcgcHJpY2UgcHJlZGljdGlvbi4gIA0K4pyUIFVzZXMgKipETWF0cml4KiogZm9yIGVmZmljaWVudCBjb21wdXRhdGlvbi4gIA0K4pyUIFVzZXMgKip0dW5hYmxlIGh5cGVycGFyYW1ldGVycyoqIGxpa2UgYGxlYXJuaW5nX3JhdGVgLCBgbWF4X2RlcHRoYCwgYW5kIGBuX2VzdGltYXRvcnNgLiAgDQrinJQgKipFdmFsdWF0aW9uIE1ldHJpY3MqKjoNCiAgLSAqKkNsYXNzaWZpY2F0aW9uOioqIGBhY2N1cmFjeV9zY29yZWANCiAgLSAqKlJlZ3Jlc3Npb246KiogYFJNU0VgICANCg0KLS0tDQoNCg0KDQoNCiMjICoqQWR2YW5jZWQgWEdCb29zdCBUZWNobmlxdWU6IEZlYXR1cmUgSW50ZXJhY3Rpb24gQ29uc3RyYWludHMqKg0KT25lIHBvd2VyZnVsIHlldCBsZXNzIGNvbW1vbmx5IHVzZWQgZmVhdHVyZSBpbiBYR0Jvb3N0IGlzICoqRmVhdHVyZSBJbnRlcmFjdGlvbiBDb25zdHJhaW50cyoqLCB3aGljaCAqKmxpbWl0cyB3aGljaCBmZWF0dXJlcyBjYW4gYmUgY29tYmluZWQqKiBpbiBkZWNpc2lvbiB0cmVlcy4gVGhpcyBpcyBwYXJ0aWN1bGFybHkgdXNlZnVsIHdoZW46DQotIFlvdSB3YW50IHRvIGVuZm9yY2UgKipkb21haW4ga25vd2xlZGdlKiogKGUuZy4sIHJlc3RyaWN0aW5nIGludGVyYWN0aW9ucyBiZXR3ZWVuIGNlcnRhaW4gdmFyaWFibGVzKS4NCi0gWW91IHdhbnQgdG8gcmVkdWNlICoqb3ZlcmZpdHRpbmcqKiBieSBsaW1pdGluZyB0cmVlIGNvbXBsZXhpdHkuDQotIFlvdSB3YW50IHRvIGltcHJvdmUgKippbnRlcnByZXRhYmlsaXR5KiogYnkgZW5zdXJpbmcgbG9naWNhbCBmZWF0dXJlIHJlbGF0aW9uc2hpcHMuDQoNCi0tLQ0KDQojIyMgKioxLiBXaGF0IEFyZSBGZWF0dXJlIEludGVyYWN0aW9uIENvbnN0cmFpbnRzPyoqDQpOb3JtYWxseSwgWEdCb29zdCBhbGxvd3MgKiphbnkgZmVhdHVyZSoqIHRvIGludGVyYWN0IHdpdGggYW55IG90aGVyIGZlYXR1cmUgaW4gYSB0cmVlLiBIb3dldmVyLCAqKkZlYXR1cmUgSW50ZXJhY3Rpb24gQ29uc3RyYWludHMqKiBhbGxvdyB5b3UgdG8gc3BlY2lmeSAqKmdyb3VwcyBvZiBmZWF0dXJlcyB0aGF0IGNhbiBpbnRlcmFjdCoqLCBwcmV2ZW50aW5nIGNlcnRhaW4gY29tYmluYXRpb25zLg0KDQrwn5OMICoqRXhhbXBsZSBVc2UgQ2FzZToqKiAgDQotIEluIGEgbWVkaWNhbCBkYXRhc2V0LCB3ZSBtYXkgbm90IHdhbnQgKiphZ2UqKiB0byBpbnRlcmFjdCB3aXRoICoqZ2VuZXRpYyBtYXJrZXJzKiouDQotIEluIHJlYWwgZXN0YXRlLCB3ZSBtYXkgd2FudCB0byBsaW1pdCBpbnRlcmFjdGlvbnMgYmV0d2VlbiAqKmxvY2F0aW9uLWJhc2VkIGZlYXR1cmVzKiogYW5kICoqZmluYW5jaWFsIGF0dHJpYnV0ZXMqKi4NCg0KLS0tDQoNCiMjIyAqKjIuIEhvdyB0byBEZWZpbmUgRmVhdHVyZSBJbnRlcmFjdGlvbiBDb25zdHJhaW50cyoqDQpUaGUgY29uc3RyYWludHMgYXJlIHBhc3NlZCBhcyBhICoqbGlzdCBvZiBsaXN0cyoqLiBFYWNoIHN1Ymxpc3QgZGVmaW5lcyBhICoqZ3JvdXAgb2YgZmVhdHVyZXMgdGhhdCBjYW4gaW50ZXJhY3QqKi4NCg0KKipFeGFtcGxlOioqDQpgYGBweXRob24NCmludGVyYWN0aW9uX2NvbnN0cmFpbnRzID0gWw0KICAgIFswLCAxLCAyXSwgICMgRmVhdHVyZXMgMCwgMSwgYW5kIDIgY2FuIGludGVyYWN0Lg0KICAgIFszLCA0XSwgICAgICMgRmVhdHVyZXMgMyBhbmQgNCBjYW4gaW50ZXJhY3QuDQogICAgWzUsIDYsIDddICAgIyBGZWF0dXJlcyA1LCA2LCBhbmQgNyBjYW4gaW50ZXJhY3QuDQpdDQpgYGANCkluIHRoaXMgY2FzZToNCi0gRmVhdHVyZXMgaW4gKipncm91cCAxKiogY2FuIGludGVyYWN0IGFtb25nIHRoZW1zZWx2ZXMgYnV0ICoqbm90Kiogd2l0aCB0aG9zZSBpbiAqKmdyb3VwIDIgb3IgMyoqLg0KDQotLS0NCg0KIyMjICoqMy4gSW1wbGVtZW50aW5nIEZlYXR1cmUgSW50ZXJhY3Rpb24gQ29uc3RyYWludHMgaW4gWEdCb29zdCoqDQojIyMjICoqU3RlcCAxOiBJbXBvcnQgUmVxdWlyZWQgTGlicmFyaWVzKioNCmBgYHB5dGhvbg0KaW1wb3J0IHhnYm9vc3QgYXMgeGdiDQpmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IGxvYWRfYm9zdG9uDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCB0cmFpbl90ZXN0X3NwbGl0DQppbXBvcnQgbnVtcHkgYXMgbnANCmBgYA0KDQojIyMjICoqU3RlcCAyOiBMb2FkIGFuZCBQcmVwYXJlIHRoZSBEYXRhKioNCmBgYHB5dGhvbg0KIyBMb2FkIGRhdGFzZXQNCmJvc3RvbiA9IGxvYWRfYm9zdG9uKCkNClgsIHkgPSBib3N0b24uZGF0YSwgYm9zdG9uLnRhcmdldA0KZmVhdHVyZV9uYW1lcyA9IGJvc3Rvbi5mZWF0dXJlX25hbWVzICAjIENvbHVtbiBuYW1lcw0KDQojIFNwbGl0IGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdCBzZXRzDQpYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoWCwgeSwgdGVzdF9zaXplPTAuMiwgcmFuZG9tX3N0YXRlPTQyKQ0KDQojIENvbnZlcnQgZGF0YSBpbnRvIERNYXRyaXggZm9ybWF0DQpkdHJhaW4gPSB4Z2IuRE1hdHJpeChYX3RyYWluLCBsYWJlbD15X3RyYWluLCBmZWF0dXJlX25hbWVzPWZlYXR1cmVfbmFtZXMpDQpkdGVzdCA9IHhnYi5ETWF0cml4KFhfdGVzdCwgbGFiZWw9eV90ZXN0LCBmZWF0dXJlX25hbWVzPWZlYXR1cmVfbmFtZXMpDQpgYGANCg0KIyMjIyAqKlN0ZXAgMzogRGVmaW5lIEZlYXR1cmUgSW50ZXJhY3Rpb24gQ29uc3RyYWludHMqKg0KV2UgYXNzdW1lOg0KLSBGZWF0dXJlcyAqKjAtMyoqIChlLmcuLCBjcmltZSByYXRlLCBsb2NhdGlvbi1iYXNlZCBmZWF0dXJlcykgY2FuIGludGVyYWN0Lg0KLSBGZWF0dXJlcyAqKjQtNyoqIChlLmcuLCBlY29ub21pYyBmYWN0b3JzKSBjYW4gaW50ZXJhY3Qgc2VwYXJhdGVseS4NCi0gRmVhdHVyZXMgKio4LTEyKiogKGUuZy4sIHN0cnVjdHVyYWwgaG9tZSBjaGFyYWN0ZXJpc3RpY3MpIGFyZSBhbm90aGVyIGluZGVwZW5kZW50IGdyb3VwLg0KDQpgYGBweXRob24NCmludGVyYWN0aW9uX2NvbnN0cmFpbnRzID0gWw0KICAgIFswLCAxLCAyLCAzXSwgICAgIyBGaXJzdCBncm91cA0KICAgIFs0LCA1LCA2LCA3XSwgICAgIyBTZWNvbmQgZ3JvdXANCiAgICBbOCwgOSwgMTAsIDExLCAxMl0gICMgVGhpcmQgZ3JvdXANCl0NCmBgYA0KDQojIyMjICoqU3RlcCA0OiBEZWZpbmUgYW5kIFRyYWluIHRoZSBYR0Jvb3N0IE1vZGVsKioNCmBgYHB5dGhvbg0KcGFyYW1zID0gew0KICAgICdvYmplY3RpdmUnOiAncmVnOnNxdWFyZWRlcnJvcicsDQogICAgJ2V2YWxfbWV0cmljJzogJ3Jtc2UnLA0KICAgICdtYXhfZGVwdGgnOiA0LA0KICAgICdsZWFybmluZ19yYXRlJzogMC4xLA0KICAgICduX2VzdGltYXRvcnMnOiAxMDAsDQogICAgJ2ludGVyYWN0aW9uX2NvbnN0cmFpbnRzJzogaW50ZXJhY3Rpb25fY29uc3RyYWludHMNCn0NCg0KIyBUcmFpbiBtb2RlbA0KbW9kZWwgPSB4Z2IudHJhaW4ocGFyYW1zLCBkdHJhaW4sIG51bV9ib29zdF9yb3VuZD0xMDApDQpgYGANCg0KIyMjIyAqKlN0ZXAgNTogTWFrZSBQcmVkaWN0aW9ucyBhbmQgRXZhbHVhdGUqKg0KYGBgcHl0aG9uDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgbWVhbl9zcXVhcmVkX2Vycm9yDQoNCiMgTWFrZSBwcmVkaWN0aW9ucw0KeV9wcmVkID0gbW9kZWwucHJlZGljdChkdGVzdCkNCg0KIyBDYWxjdWxhdGUgUk1TRQ0Kcm1zZSA9IG5wLnNxcnQobWVhbl9zcXVhcmVkX2Vycm9yKHlfdGVzdCwgeV9wcmVkKSkNCnByaW50KGYiWEdCb29zdCB3aXRoIEZlYXR1cmUgSW50ZXJhY3Rpb24gQ29uc3RyYWludHMgUk1TRToge3Jtc2U6LjRmfSIpDQpgYGANCg0K8J+TjCAqKkV4cGVjdGVkIFJlc3VsdHM6KiogIA0KLSBSTVNFIHNob3VsZCBiZSAqKnNpbWlsYXIgb3Igc2xpZ2h0bHkgYmV0dGVyKiogY29tcGFyZWQgdG8gYSBtb2RlbCB3aXRob3V0IGNvbnN0cmFpbnRzLg0KLSBUaGUgbW9kZWwgaXMgKipsZXNzIHByb25lIHRvIG92ZXJmaXR0aW5nKiosIGFzIGl0IGZvbGxvd3MgZG9tYWluIGtub3dsZWRnZS4NCg0KLS0tDQoNCiMjIyAqKjQuIEJlbmVmaXRzIG9mIEZlYXR1cmUgSW50ZXJhY3Rpb24gQ29uc3RyYWludHMqKg0K4pyUICoqQmV0dGVyIEdlbmVyYWxpemF0aW9uKio6IFJlZHVjZXMgb3ZlcmZpdHRpbmcgYnkgbGltaXRpbmcgdW5uZWNlc3NhcnkgaW50ZXJhY3Rpb25zLiAgDQrinJQgKipNb3JlIEludGVycHJldGFiaWxpdHkqKjogRW5mb3JjZXMgbG9naWNhbCByZWxhdGlvbnNoaXBzIGJldHdlZW4gZmVhdHVyZXMuICANCuKclCAqKkRvbWFpbi1TcGVjaWZpYyBDb250cm9sKio6IEFsbG93cyBpbnRlZ3JhdGlvbiBvZiBleHBlcnQga25vd2xlZGdlIGludG8gdGhlIG1vZGVsLiAgDQoNCuKchSAqKlVzZSBGZWF0dXJlIEludGVyYWN0aW9uIENvbnN0cmFpbnRzIHdoZW46KioNCi0gQ2VydGFpbiBmZWF0dXJlcyAqKnNob3VsZG7igJl0IGJlIGNvbWJpbmVkKiogZm9yIGludGVycHJldGFiaWxpdHkuDQotIFlvdSB3YW50IHRvICoqcmVkdWNlIGNvbXBsZXhpdHkqKiBpbiBsYXJnZSBmZWF0dXJlIHNwYWNlcy4NCg0KLS0tDQoNCg0KIyMgKipHcmlkIFNlYXJjaCBhbmQgUmFuZG9tIFNlYXJjaCBmb3IgSHlwZXJwYXJhbWV0ZXIgVHVuaW5nIGluIFhHQm9vc3QqKg0KSHlwZXJwYXJhbWV0ZXIgdHVuaW5nIGlzIGVzc2VudGlhbCBmb3IgaW1wcm92aW5nIG1vZGVsIHBlcmZvcm1hbmNlLiAqKkdyaWQgU2VhcmNoIGFuZCBSYW5kb20gU2VhcmNoKiogYXJlIHR3byBjb21tb24gbWV0aG9kcyBmb3Igb3B0aW1pemluZyBYR0Jvb3N0IG1vZGVscy4NCg0KLS0tDQoNCiMjICoqMS4gR3JpZCBTZWFyY2ggdnMuIFJhbmRvbSBTZWFyY2gqKg0KfCAqKk1ldGhvZCoqIHwgKipIb3cgSXQgV29ya3MqKiB8ICoqUHJvcyoqIHwgKipDb25zKiogfA0KfC0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS18LS0tLS0tLS0tLXwNCnwgKipHcmlkIFNlYXJjaCoqIHwgVGVzdHMgYWxsIHBvc3NpYmxlIGNvbWJpbmF0aW9ucyBvZiBoeXBlcnBhcmFtZXRlcnMuIHwgRXhoYXVzdGl2ZSwgZ3VhcmFudGVlcyBmaW5kaW5nIHRoZSBiZXN0IGNvbWJpbmF0aW9uLiB8IENvbXB1dGF0aW9uYWxseSBleHBlbnNpdmUsIHNsb3cgZm9yIGxhcmdlIHBhcmFtZXRlciBncmlkcy4gfA0KfCAqKlJhbmRvbSBTZWFyY2gqKiB8IFJhbmRvbWx5IHNlbGVjdHMgYSBzdWJzZXQgb2YgaHlwZXJwYXJhbWV0ZXIgY29tYmluYXRpb25zLiB8IEZhc3RlciB0aGFuIEdyaWQgU2VhcmNoLCBmaW5kcyBnb29kIGh5cGVycGFyYW1ldGVycyBxdWlja2x5LiB8IE1pZ2h0IG1pc3MgdGhlIGJlc3QgaHlwZXJwYXJhbWV0ZXJzIHNpbmNlIG5vdCBhbGwgYXJlIHRlc3RlZC4gfA0KDQrwn5OMICoqQmVzdCBQcmFjdGljZToqKiBVc2UgKipSYW5kb20gU2VhcmNoIGZpcnN0KiogdG8gZmluZCBhIGdvb2QgcmFuZ2Ugb2YgdmFsdWVzLCB0aGVuICoqR3JpZCBTZWFyY2gqKiBmb3IgZmluZS10dW5pbmcuDQoNCi0tLQ0KDQojIyAqKjIuIEdyaWQgU2VhcmNoIGluIFhHQm9vc3QqKg0KKipHcmlkIFNlYXJjaCoqIHN5c3RlbWF0aWNhbGx5IHNlYXJjaGVzIHRocm91Z2ggYWxsIHBvc3NpYmxlIGNvbWJpbmF0aW9ucyBvZiBoeXBlcnBhcmFtZXRlcnMgdG8gZmluZCB0aGUgYmVzdC1wZXJmb3JtaW5nIG1vZGVsLg0KDQojIyMgKioyLjEgSW1wbGVtZW50aW5nIEdyaWQgU2VhcmNoKioNCiMjIyMgKipTdGVwIDE6IEltcG9ydCBSZXF1aXJlZCBMaWJyYXJpZXMqKg0KYGBgcHl0aG9uDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCBHcmlkU2VhcmNoQ1YNCmltcG9ydCB4Z2Jvb3N0IGFzIHhnYg0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBsb2FkX2Jvc3Rvbg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgdHJhaW5fdGVzdF9zcGxpdA0KYGBgDQoNCiMjIyMgKipTdGVwIDI6IExvYWQgYW5kIFByZXBhcmUgRGF0YSoqDQpgYGBweXRob24NCiMgTG9hZCBkYXRhc2V0DQpib3N0b24gPSBsb2FkX2Jvc3RvbigpDQpYLCB5ID0gYm9zdG9uLmRhdGEsIGJvc3Rvbi50YXJnZXQNCg0KIyBUcmFpbi10ZXN0IHNwbGl0DQpYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoWCwgeSwgdGVzdF9zaXplPTAuMiwgcmFuZG9tX3N0YXRlPTQyKQ0KYGBgDQoNCiMjIyMgKipTdGVwIDM6IERlZmluZSB0aGUgSHlwZXJwYXJhbWV0ZXIgR3JpZCoqDQpgYGBweXRob24NCiMgRGVmaW5lIFhHQm9vc3QgbW9kZWwNCnhnYl9tb2RlbCA9IHhnYi5YR0JSZWdyZXNzb3Iob2JqZWN0aXZlPSdyZWc6c3F1YXJlZGVycm9yJykNCg0KIyBEZWZpbmUgaHlwZXJwYXJhbWV0ZXIgZ3JpZA0KcGFyYW1fZ3JpZCA9IHsNCiAgICAnbGVhcm5pbmdfcmF0ZSc6IFswLjAxLCAwLjEsIDAuMl0sDQogICAgJ21heF9kZXB0aCc6IFszLCA1LCA3XSwNCiAgICAnbl9lc3RpbWF0b3JzJzogWzUwLCAxMDAsIDIwMF0sDQogICAgJ3N1YnNhbXBsZSc6IFswLjgsIDEuMF0sDQogICAgJ2NvbHNhbXBsZV9ieXRyZWUnOiBbMC44LCAxLjBdDQp9DQpgYGANCg0KIyMjIyAqKlN0ZXAgNDogUGVyZm9ybSBHcmlkIFNlYXJjaCoqDQpgYGBweXRob24NCmdyaWRfc2VhcmNoID0gR3JpZFNlYXJjaENWKA0KICAgIGVzdGltYXRvcj14Z2JfbW9kZWwsIA0KICAgIHBhcmFtX2dyaWQ9cGFyYW1fZ3JpZCwgDQogICAgc2NvcmluZz0nbmVnX21lYW5fc3F1YXJlZF9lcnJvcicsIA0KICAgIGN2PTUsIA0KICAgIHZlcmJvc2U9MQ0KKQ0KDQojIFRyYWluIG1vZGVsDQpncmlkX3NlYXJjaC5maXQoWF90cmFpbiwgeV90cmFpbikNCg0KIyBQcmludCBiZXN0IHBhcmFtZXRlcnMNCnByaW50KCJCZXN0IFBhcmFtZXRlcnM6IiwgZ3JpZF9zZWFyY2guYmVzdF9wYXJhbXNfKQ0KYGBgDQrwn5OMICoqV2hhdCBoYXBwZW5zIGhlcmU/KioNCi0gKipFeGhhdXN0aXZlbHkgdGVzdHMgYWxsIGNvbWJpbmF0aW9ucyoqIGZyb20gYHBhcmFtX2dyaWRgLg0KLSBVc2VzICoqY3Jvc3MtdmFsaWRhdGlvbiAoY3Y9NSkqKiB0byBmaW5kIHRoZSBiZXN0IGh5cGVycGFyYW1ldGVycy4NCi0gKipTY29yaW5nIG1ldHJpYyoqOiBgbmVnX21lYW5fc3F1YXJlZF9lcnJvcmAgKGxvd2VyIGlzIGJldHRlcikuDQotIFByaW50cyB0aGUgKipiZXN0IGNvbWJpbmF0aW9uIG9mIGh5cGVycGFyYW1ldGVycyoqLg0KDQotLS0NCg0KIyMgKiozLiBSYW5kb20gU2VhcmNoIGluIFhHQm9vc3QqKg0KKipSYW5kb20gU2VhcmNoKiogcmFuZG9tbHkgc2VsZWN0cyBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbnMgaW5zdGVhZCBvZiB0ZXN0aW5nIGFsbCBwb3NzaWJpbGl0aWVzLCBtYWtpbmcgaXQgbW9yZSBlZmZpY2llbnQgZm9yIGxhcmdlIHBhcmFtZXRlciBzcGFjZXMuDQoNCiMjIyAqKjMuMSBJbXBsZW1lbnRpbmcgUmFuZG9tIFNlYXJjaCoqDQojIyMjICoqU3RlcCAxOiBJbXBvcnQgUmVxdWlyZWQgTGlicmFyaWVzKioNCmBgYHB5dGhvbg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgUmFuZG9taXplZFNlYXJjaENWDQpmcm9tIHNjaXB5LnN0YXRzIGltcG9ydCB1bmlmb3JtDQppbXBvcnQgbnVtcHkgYXMgbnANCmBgYA0KDQojIyMjICoqU3RlcCAyOiBEZWZpbmUgdGhlIEh5cGVycGFyYW1ldGVyIERpc3RyaWJ1dGlvbioqDQpJbnN0ZWFkIG9mIGRlZmluaW5nIGEgZml4ZWQgc2V0IG9mIHZhbHVlcywgKipSYW5kb20gU2VhcmNoIHNhbXBsZXMgZnJvbSBjb250aW51b3VzIGRpc3RyaWJ1dGlvbnMqKi4NCmBgYHB5dGhvbg0KcGFyYW1fZGlzdCA9IHsNCiAgICAnbGVhcm5pbmdfcmF0ZSc6IHVuaWZvcm0oMC4wMSwgMC4zKSwgICMgQ29udGludW91cyByYW5nZSBmcm9tIDAuMDEgdG8gMC4zMQ0KICAgICdtYXhfZGVwdGgnOiBbMywgNSwgNywgOV0sICAjIERpc2NyZXRlIHZhbHVlcw0KICAgICduX2VzdGltYXRvcnMnOiBbNTAsIDEwMCwgMjAwXSwgICMgRGlzY3JldGUgdmFsdWVzDQogICAgJ3N1YnNhbXBsZSc6IHVuaWZvcm0oMC41LCAwLjUpLCAgIyBGcm9tIDAuNSB0byAxLjANCiAgICAnY29sc2FtcGxlX2J5dHJlZSc6IHVuaWZvcm0oMC41LCAwLjUpICAjIEZyb20gMC41IHRvIDEuMA0KfQ0KYGBgDQoNCiMjIyMgKipTdGVwIDM6IFBlcmZvcm0gUmFuZG9tIFNlYXJjaCoqDQpgYGBweXRob24NCnJhbmRvbV9zZWFyY2ggPSBSYW5kb21pemVkU2VhcmNoQ1YoDQogICAgZXN0aW1hdG9yPXhnYl9tb2RlbCwNCiAgICBwYXJhbV9kaXN0cmlidXRpb25zPXBhcmFtX2Rpc3QsDQogICAgbl9pdGVyPTI1LCAgIyBOdW1iZXIgb2YgcmFuZG9tIGNvbWJpbmF0aW9ucyB0byB0cnkNCiAgICBzY29yaW5nPSduZWdfbWVhbl9zcXVhcmVkX2Vycm9yJywNCiAgICBjdj01LA0KICAgIHZlcmJvc2U9MSwNCiAgICByYW5kb21fc3RhdGU9NDINCikNCg0KIyBUcmFpbiBtb2RlbA0KcmFuZG9tX3NlYXJjaC5maXQoWF90cmFpbiwgeV90cmFpbikNCg0KIyBQcmludCBiZXN0IHBhcmFtZXRlcnMNCnByaW50KCJCZXN0IFBhcmFtZXRlcnMgZnJvbSBSYW5kb20gU2VhcmNoOiIsIHJhbmRvbV9zZWFyY2guYmVzdF9wYXJhbXNfKQ0KYGBgDQoNCvCfk4wgKipXaGF0IGhhcHBlbnMgaGVyZT8qKg0KLSBSYW5kb21seSAqKnNhbXBsZXMgMjUgZGlmZmVyZW50IGh5cGVycGFyYW1ldGVyIGNvbWJpbmF0aW9ucyoqLg0KLSBVc2VzICoqY29udGludW91cyBkaXN0cmlidXRpb25zKiogaW5zdGVhZCBvZiBwcmVkZWZpbmVkIHZhbHVlcy4NCi0gUnVucyAqKjUtZm9sZCBjcm9zcy12YWxpZGF0aW9uKiogZm9yIGVhY2ggc2FtcGxlZCBjb21iaW5hdGlvbi4NCi0gT3V0cHV0cyB0aGUgKipiZXN0IGh5cGVycGFyYW1ldGVyIGNvbWJpbmF0aW9uKiouDQoNCi0tLQ0KDQojIyAqKjQuIEdyaWQgU2VhcmNoIHZzLiBSYW5kb20gU2VhcmNoOiBXaGljaCBPbmUgdG8gVXNlPyoqDQp8ICoqU2NlbmFyaW8qKiB8ICoqVXNlIEdyaWQgU2VhcmNoKiogfCAqKlVzZSBSYW5kb20gU2VhcmNoKiogfA0KfC0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tfA0KfCBTbWFsbCBkYXRhc2V0ICYgZmV3IGh5cGVycGFyYW1ldGVycyB8IOKchSBZZXMgfCDinYwgTm8gfA0KfCBMYXJnZSBkYXRhc2V0IHdpdGggbWFueSBoeXBlcnBhcmFtZXRlcnMgfCDinYwgTm8gfCDinIUgWWVzIHwNCnwgTmVlZCB0byBmaW5kIGJlc3QgaHlwZXJwYXJhbWV0ZXJzICoqcXVpY2tseSoqIHwg4p2MIE5vIHwg4pyFIFllcyB8DQp8IFdhbnQgdG8gKipndWFyYW50ZWUqKiBiZXN0IHBhcmFtZXRlcnMgfCDinIUgWWVzIHwg4p2MIE5vIHwNCg0KIyMjICoqQmVzdCBQcmFjdGljZTogVXNlIFJhbmRvbSBTZWFyY2ggRmlyc3QqKg0KMS4gKipTdGFydCB3aXRoIFJhbmRvbSBTZWFyY2gqKiB0byBxdWlja2x5IGZpbmQgYSBnb29kIHJhbmdlIG9mIHZhbHVlcy4NCjIuICoqUmVmaW5lIHdpdGggR3JpZCBTZWFyY2gqKiBvbiBhIHNtYWxsZXIgaHlwZXJwYXJhbWV0ZXIgc3BhY2UuDQoNCi0tLQ0KDQojIyAqKjUuIFN1bW1hcnkqKg0K4pyUICoqR3JpZCBTZWFyY2gqKjogRXhoYXVzdGl2ZSwgYmVzdCBmb3Igc21hbGwgZGF0YXNldHMsIGd1YXJhbnRlZXMgb3B0aW1hbCBwYXJhbWV0ZXJzLiAgDQrinJQgKipSYW5kb20gU2VhcmNoKio6IEZhc3RlciwgYmV0dGVyIGZvciBsYXJnZSBkYXRhc2V0cywgZ29vZCBhcHByb3hpbWF0aW9uIG9mIG9wdGltYWwgcGFyYW1ldGVycy4gIA0K4pyUICoqQmVzdCBwcmFjdGljZSoqOiAqKlVzZSBSYW5kb20gU2VhcmNoIGZpcnN0KiosIHRoZW4gZmluZS10dW5lIHdpdGggKipHcmlkIFNlYXJjaCoqLiAgDQoNCi0tLQ0KDQoNCiAjICoqRmluYWwgUmVjYXAgJiBTdW1tYXJ5IG9mIFhHQm9vc3QgU3R1ZHkgR3VpZGUqKg0KDQojIyAqKjEuIFN1bW1hcnkgb2YgS2V5IENvbmNlcHRzIENvdmVyZWQqKg0KIyMjICoqQm9vc3RpbmcgJiBIb3cgSXQgV29ya3MqKg0K4pyUIEJvb3N0aW5nICoqc2VxdWVudGlhbGx5IGltcHJvdmVzIHdlYWsgbW9kZWxzKiosIGNvcnJlY3RpbmcgcHJldmlvdXMgZXJyb3JzLiAgDQrinJQgSXQgcmVkdWNlcyAqKmJpYXMqKiB3aGlsZSBtYWludGFpbmluZyBsb3cgdmFyaWFuY2UuICANCuKclCBQb3B1bGFyIEJvb3N0aW5nIGFsZ29yaXRobXM6ICoqQWRhQm9vc3QsIEdyYWRpZW50IEJvb3N0aW5nLCBhbmQgWEdCb29zdCoqLiAgDQoNCiMjIyAqKlhHQm9vc3QgRnVuZGFtZW50YWxzKioNCuKclCAqKlhHQm9vc3QgaXMgYW4gb3B0aW1pemVkIGZvcm0gb2YgR3JhZGllbnQgQm9vc3RpbmcqKiB3aXRoICoqcmVndWxhcml6YXRpb24gYW5kIHBhcmFsbGVsIGNvbXB1dGF0aW9uKiouICANCuKclCBJdCBlZmZpY2llbnRseSBoYW5kbGVzICoqbGFyZ2UgZGF0YXNldHMsIG1pc3NpbmcgdmFsdWVzLCBhbmQgZmVhdHVyZSBzZWxlY3Rpb24qKi4gIA0K4pyUIEl0IGhhcyAqKmJ1aWx0LWluIEwxIChMYXNzbykgJiBMMiAoUmlkZ2UpIHJlZ3VsYXJpemF0aW9uKiogdG8gcHJldmVudCBvdmVyZml0dGluZy4gIA0KDQojIyMgKipIeXBlcnBhcmFtZXRlciBUdW5pbmcqKg0K4pyUICoqS2V5IGh5cGVycGFyYW1ldGVycyoqOiBgbGVhcm5pbmdfcmF0ZWAsIGBtYXhfZGVwdGhgLCBgbl9lc3RpbWF0b3JzYCwgYHN1YnNhbXBsZWAsIGBjb2xzYW1wbGVfYnl0cmVlYCwgYGdhbW1hYCwgYGxhbWJkYWAsIGFuZCBgYWxwaGFgLiAgDQrinJQgKipHcmlkIFNlYXJjaCoqIGlzIGV4aGF1c3RpdmUgYW5kIGd1YXJhbnRlZXMgdGhlIGJlc3QgcGFyYW1ldGVycyBidXQgaXMgKipzbG93IGZvciBsYXJnZSBkYXRhc2V0cyoqLiAgDQrinJQgKipSYW5kb20gU2VhcmNoKiogZmluZHMgYSBnb29kIGFwcHJveGltYXRpb24gKipmYXN0ZXIqKiBidXQgZG9lc27igJl0IGd1YXJhbnRlZSB0aGUgb3B0aW1hbCBwYXJhbWV0ZXJzLiAgDQoNCiMjIyAqKlhHQm9vc3QgRGVtb3MqKg0K4pyUICoqRGVtbyAxIChDbGFzc2lmaWNhdGlvbikqKjogSGFuZHdyaXR0ZW4gZGlnaXQgY2xhc3NpZmljYXRpb24gdXNpbmcgYG11bHRpOnNvZnRtYXhgLiAgDQrinJQgKipEZW1vIDIgKFJlZ3Jlc3Npb24pKio6IFByZWRpY3RpbmcgaG91c2luZyBwcmljZXMgd2l0aCBgcmVnOnNxdWFyZWRlcnJvcmAuICANCg0KIyMjICoqQWR2YW5jZWQgWEdCb29zdCBUZWNobmlxdWVzKioNCuKclCAqKlNIQVAgKFNIYXBsZXkgQWRkaXRpdmUgRXhwbGFuYXRpb25zKSoqIGhlbHBzIHVuZGVyc3RhbmQgbW9kZWwgcHJlZGljdGlvbnMgYXQgYSBmZWF0dXJlIGxldmVsLiAgDQrinJQgKipGZWF0dXJlIEludGVyYWN0aW9uIENvbnN0cmFpbnRzKiogY29udHJvbCB3aGljaCBmZWF0dXJlcyBpbnRlcmFjdCBpbiB0cmVlcywgcmVkdWNpbmcgb3ZlcmZpdHRpbmcgYW5kIGltcHJvdmluZyBpbnRlcnByZXRhYmlsaXR5LiAgDQrinJQgKipFYXJseSBTdG9wcGluZyoqIHByZXZlbnRzIG92ZXJmaXR0aW5nIGJ5IHN0b3BwaW5nIHRyYWluaW5nIHdoZW4gdmFsaWRhdGlvbiBlcnJvciBzdG9wcyBpbXByb3ZpbmcuICANCuKclCAqKkN1c3RvbSBMb3NzIEZ1bmN0aW9ucyoqIGFsbG93IGRvbWFpbi1zcGVjaWZpYyBvYmplY3RpdmVzIChlLmcuLCAqKkh1YmVyIGxvc3MqKiBmb3Igcm9idXN0IHJlZ3Jlc3Npb24pLiAgDQoNCi0tLQ0KDQojIyAqKjIuIFRocmVlIEtleSBUYWtlYXdheXMqKg0KIyMjICoqMS4gRmVhdHVyZSBJbXBvcnRhbmNlIE1hdHRlcnMqKg0KVW5kZXJzdGFuZGluZyAqKndoaWNoIGZlYXR1cmVzIGRyaXZlIHByZWRpY3Rpb25zKiogKHZpYSBTSEFQIG9yIGBwbG90X2ltcG9ydGFuY2UoKWApIGhlbHBzIGluOg0KLSBGZWF0dXJlIHNlbGVjdGlvbiAgDQotIE1vZGVsIGRlYnVnZ2luZyAgDQotIEV4cGxhaW5pbmcgcHJlZGljdGlvbnMgaW4gaGlnaC1zdGFrZXMgYXBwbGljYXRpb25zICANCg0KIyMjICoqMi4gSHlwZXJwYXJhbWV0ZXIgVHVuaW5nIGlzIENyaXRpY2FsKioNCkNob29zaW5nIHRoZSByaWdodCBjb21iaW5hdGlvbiBvZjoNCi0gKipUcmVlIGNvbXBsZXhpdHkgKGBtYXhfZGVwdGhgLCBgbWluX2NoaWxkX3dlaWdodGApKioNCi0gKipCb29zdGluZyBzZXR0aW5ncyAoYGxlYXJuaW5nX3JhdGVgLCBgbl9lc3RpbWF0b3JzYCkqKg0KLSAqKlJlZ3VsYXJpemF0aW9uIChgbGFtYmRhYCwgYGFscGhhYCkqKiAgDQpjYW4gKipkcmFtYXRpY2FsbHkgaW1wcm92ZSBtb2RlbCBwZXJmb3JtYW5jZSoqLiAgDQoNCiMjIyAqKjMuIFNpbXBsaWNpdHkgQmVhdHMgQ29tcGxleGl0eSoqDQpNb3JlIGNvbXBsZXggbW9kZWxzICoqZG9u4oCZdCBhbHdheXMgcGVyZm9ybSBiZXR0ZXIqKi4gIA0KLSAqKkZlYXR1cmUgY29uc3RyYWludHMqKiBhbmQgKiplYXJseSBzdG9wcGluZyoqIGhlbHAgY29udHJvbCBtb2RlbCBjb21wbGV4aXR5LiAgDQotICoqU21hbGxlciB0cmVlcyB3aXRoIG1lYW5pbmdmdWwgc3BsaXRzKiogZ2VuZXJhbGl6ZSBiZXR0ZXIuICANCi0gKipHcmlkIFNlYXJjaCBpc27igJl0IGFsd2F5cyBuZWVkZWTigJRSYW5kb20gU2VhcmNoIG9yIEJheWVzaWFuIE9wdGltaXphdGlvbiBjYW4gYmUgbW9yZSBlZmZpY2llbnQqKi4gIA0KDQotLS0NCg0KIyMgKiozLiBUaHJlZSBUaG91Z2h0LVByb3Zva2luZyBRdWVzdGlvbnMqKg0KIyMjICoqMS4gQ2FuIEZlYXR1cmUgQ29uc3RyYWludHMgSW1wcm92ZSBFeHBsYWluYWJpbGl0eSBXaXRob3V0IFNhY3JpZmljaW5nIFBlcmZvcm1hbmNlPyoqDQotIElmIGNlcnRhaW4gZmVhdHVyZXMgKipzaG91bGRu4oCZdCBpbnRlcmFjdCoqIGR1ZSB0byBkb21haW4gY29uc3RyYWludHMsIGhvdyBtdWNoIGRvZXMgbGltaXRpbmcgaW50ZXJhY3Rpb25zIGltcGFjdCBhY2N1cmFjeT8gIA0KLSBXb3VsZCB0aGUgbW9kZWwgYmUgKiptb3JlIGludGVycHJldGFibGUgYW5kIGdlbmVyYWxpemFibGUqKiBpZiBjb25zdHJhaW50cyB3ZXJlIGFkZGVkPyAgDQoNCiMjIyAqKjIuIEFyZSBZb3UgVHVuaW5nIHRoZSBSaWdodCBIeXBlcnBhcmFtZXRlcnMgb3IgSnVzdCBFeHBlcmltZW50aW5nPyoqDQotIEhhdmUgeW91IGNoZWNrZWQgKipmZWF0dXJlIGltcG9ydGFuY2UqKiBmaXJzdCBiZWZvcmUgdHVuaW5nPyAgDQotIERvIHlvdSBuZWVkIGEgY29tcGxleCBoeXBlcnBhcmFtZXRlciB0dW5pbmcgc3RyYXRlZ3ksIG9yIHdvdWxkICoqZWFybHkgc3RvcHBpbmcqKiArICoqZGVmYXVsdCBzZXR0aW5ncyoqIHdvcmsganVzdCBhcyB3ZWxsPyAgDQoNCiMjIyAqKjMuIENhbiBZb3UgUmVkdWNlIFRyYWluaW5nIFRpbWUgV2l0aG91dCBMb3NpbmcgQWNjdXJhY3k/KioNCi0gV291bGQgKipHUFUgYWNjZWxlcmF0aW9uIChgdHJlZV9tZXRob2Q9J2dwdV9oaXN0J2ApKiogc3BlZWQgdXAgdHJhaW5pbmc/ICANCi0gV291bGQgKipsb3dlcmluZyBgbl9lc3RpbWF0b3JzYCoqIGFuZCBpbmNyZWFzaW5nIGBsZWFybmluZ19yYXRlYCBnaXZlIHRoZSBzYW1lIGFjY3VyYWN5IGZhc3Rlcj8gIA0KDQotLS0NCiMjICoqUHJhY3RpY2FsIFJlYWwtV29ybGQgRGF0YXNldCBBbmFseXNpcyBVc2luZyBYR0Jvb3N0ICsgQmF5ZXNpYW4gT3B0aW1pemF0aW9uIGZvciBIeXBlcnBhcmFtZXRlciBUdW5pbmcqKg0KV2Ugd2lsbCAqKmNvbWJpbmUgYm90aCB0b3BpY3MqKiBpbnRvIGEgcHJhY3RpY2FsICoqcmVhbC13b3JsZCBkYXRhc2V0IGFuYWx5c2lzIHVzaW5nIFhHQm9vc3QqKiB3aGlsZSAqKm9wdGltaXppbmcgaHlwZXJwYXJhbWV0ZXJzIHdpdGggQmF5ZXNpYW4gT3B0aW1pemF0aW9uKiouDQoNCi0tLQ0KDQojICoqMS4gRGF0YXNldCBTZWxlY3Rpb24qKg0KV2Ugd2lsbCB1c2UgdGhlICoqQ2FsaWZvcm5pYSBIb3VzaW5nIFByaWNlcyBEYXRhc2V0KiogZnJvbSBgc2tsZWFybi5kYXRhc2V0c2AuICANCi0gKipUYXNrKio6ICoqUHJlZGljdCBob3VzZSBwcmljZXMqKiBiYXNlZCBvbiBmZWF0dXJlcyBsaWtlIG1lZGlhbiBpbmNvbWUsIHBvcHVsYXRpb24sIGFuZCBsb2NhdGlvbi4gIA0KLSAqKk9iamVjdGl2ZSoqOiAqKlJlZ3Jlc3Npb24gcHJvYmxlbSoqIChjb250aW51b3VzIHRhcmdldCB2YXJpYWJsZSkuICANCi0gKipPcHRpbWl6YXRpb24gR29hbCoqOiBUdW5lIFhHQm9vc3QgKipoeXBlcnBhcmFtZXRlcnMgdXNpbmcgQmF5ZXNpYW4gT3B0aW1pemF0aW9uKiogZm9yIGJlc3QgcGVyZm9ybWFuY2UuDQoNCi0tLQ0KDQojICoqMi4gU3RlcC1ieS1TdGVwIEltcGxlbWVudGF0aW9uKioNCiMjIyAqKlN0ZXAgMTogSW5zdGFsbCBSZXF1aXJlZCBQYWNrYWdlcyoqDQpgYGBweXRob24NCnBpcCBpbnN0YWxsIHhnYm9vc3QgYmF5ZXNpYW4tb3B0aW1pemF0aW9uIHNjaWtpdC1sZWFybiBwYW5kYXMgbnVtcHkgbWF0cGxvdGxpYg0KYGBgDQoNCi0tLQ0KDQojIyMgKipTdGVwIDI6IEltcG9ydCBMaWJyYXJpZXMqKg0KYGBgcHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBwYW5kYXMgYXMgcGQNCmltcG9ydCB4Z2Jvb3N0IGFzIHhnYg0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBmZXRjaF9jYWxpZm9ybmlhX2hvdXNpbmcNCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQNCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBtZWFuX3NxdWFyZWRfZXJyb3INCmZyb20gYmF5ZXNfb3B0IGltcG9ydCBCYXllc2lhbk9wdGltaXphdGlvbg0KYGBgDQoNCi0tLQ0KDQojIyMgKipTdGVwIDM6IExvYWQgYW5kIFByZXBhcmUgRGF0YSoqDQpgYGBweXRob24NCiMgTG9hZCB0aGUgQ2FsaWZvcm5pYSBob3VzaW5nIGRhdGFzZXQNCmRhdGEgPSBmZXRjaF9jYWxpZm9ybmlhX2hvdXNpbmcoKQ0KWCwgeSA9IGRhdGEuZGF0YSwgZGF0YS50YXJnZXQNCmZlYXR1cmVfbmFtZXMgPSBkYXRhLmZlYXR1cmVfbmFtZXMgICMgQ29sdW1uIG5hbWVzDQoNCiMgU3BsaXQgaW50byB0cmFpbiAmIHRlc3Qgc2V0cyAoODAvMjApDQpYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoWCwgeSwgdGVzdF9zaXplPTAuMiwgcmFuZG9tX3N0YXRlPTQyKQ0KDQojIENvbnZlcnQgdG8gWEdCb29zdCdzIERNYXRyaXggZm9ybWF0DQpkdHJhaW4gPSB4Z2IuRE1hdHJpeChYX3RyYWluLCBsYWJlbD15X3RyYWluKQ0KZHRlc3QgPSB4Z2IuRE1hdHJpeChYX3Rlc3QsIGxhYmVsPXlfdGVzdCkNCmBgYA0KDQotLS0NCg0KIyMjICoqU3RlcCA0OiBEZWZpbmUgWEdCb29zdCBNb2RlbCBhbmQgQmFzZWxpbmUgUGVyZm9ybWFuY2UqKg0KQmVmb3JlIHR1bmluZywgbGV0J3MgdHJhaW4gYSAqKmRlZmF1bHQgWEdCb29zdCBtb2RlbCoqIHRvIGVzdGFibGlzaCBhIGJhc2VsaW5lLg0KDQpgYGBweXRob24NCiMgRGVmaW5lIGJhc2VsaW5lIHBhcmFtZXRlcnMNCmJhc2VsaW5lX3BhcmFtcyA9IHsNCiAgICAnb2JqZWN0aXZlJzogJ3JlZzpzcXVhcmVkZXJyb3InLA0KICAgICdldmFsX21ldHJpYyc6ICdybXNlJywNCiAgICAnbWF4X2RlcHRoJzogNiwNCiAgICAnbGVhcm5pbmdfcmF0ZSc6IDAuMSwNCiAgICAnbl9lc3RpbWF0b3JzJzogMTAwLA0KICAgICdzdWJzYW1wbGUnOiAwLjgsDQogICAgJ2NvbHNhbXBsZV9ieXRyZWUnOiAwLjgNCn0NCg0KIyBUcmFpbiB0aGUgbW9kZWwNCmJhc2VsaW5lX21vZGVsID0geGdiLnRyYWluKGJhc2VsaW5lX3BhcmFtcywgZHRyYWluLCBudW1fYm9vc3Rfcm91bmQ9MTAwKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCnlfcHJlZCA9IGJhc2VsaW5lX21vZGVsLnByZWRpY3QoZHRlc3QpDQoNCiMgQ29tcHV0ZSBSTVNFDQpiYXNlbGluZV9ybXNlID0gbnAuc3FydChtZWFuX3NxdWFyZWRfZXJyb3IoeV90ZXN0LCB5X3ByZWQpKQ0KcHJpbnQoZiJCYXNlbGluZSBSTVNFOiB7YmFzZWxpbmVfcm1zZTouNGZ9IikNCmBgYA0KDQrwn5OMICoqQmFzZWxpbmUgUk1TRSBwcm92aWRlcyBhIHJlZmVyZW5jZSB0byBzZWUgaG93IG11Y2ggaHlwZXJwYXJhbWV0ZXIgdHVuaW5nIGltcHJvdmVzIHBlcmZvcm1hbmNlLioqICANCg0KLS0tDQoNCiMjIyAqKlN0ZXAgNTogRGVmaW5lIEJheWVzaWFuIE9wdGltaXphdGlvbiBmb3IgSHlwZXJwYXJhbWV0ZXIgVHVuaW5nKioNCkluc3RlYWQgb2YgdXNpbmcgKipHcmlkIFNlYXJjaCBvciBSYW5kb20gU2VhcmNoKiosIHdlIHdpbGwgdXNlICoqQmF5ZXNpYW4gT3B0aW1pemF0aW9uKiouDQoNCiMjIyMgKipXaGF0IGlzIEJheWVzaWFuIE9wdGltaXphdGlvbj8qKg0KLSBJbnN0ZWFkIG9mIHRlc3RpbmcgYWxsIGNvbWJpbmF0aW9ucyAoR3JpZCBTZWFyY2gpIG9yIHJhbmRvbSBjb21iaW5hdGlvbnMgKFJhbmRvbSBTZWFyY2gpLCBCYXllc2lhbiBPcHRpbWl6YXRpb24gKipsZWFybnMgZnJvbSBwcmV2aW91cyB0cmlhbHMqKiB0byBmaW5kIGJldHRlciBoeXBlcnBhcmFtZXRlcnMgKipmYXN0ZXIqKi4NCi0gSXQgKipiYWxhbmNlcyBleHBsb3JhdGlvbiAmIGV4cGxvaXRhdGlvbioqOiAgDQogIC0gKipFeHBsb3JhdGlvbioqOiBUcmllcyAqKm5ldyBjb21iaW5hdGlvbnMqKiB0byBpbXByb3ZlIHRoZSBtb2RlbC4gIA0KICAtICoqRXhwbG9pdGF0aW9uKio6IEZvY3VzZXMgb24gKiphbHJlYWR5IHByb21pc2luZyB2YWx1ZXMqKiB0byByZWZpbmUgcGVyZm9ybWFuY2UuICANCg0KLS0tDQoNCiMjIyAqKlN0ZXAgNjogRGVmaW5lIHRoZSBPYmplY3RpdmUgRnVuY3Rpb24gZm9yIEJheWVzaWFuIE9wdGltaXphdGlvbioqDQpCYXllc2lhbiBPcHRpbWl6YXRpb24gbmVlZHMgYW4gKipvYmplY3RpdmUgZnVuY3Rpb24qKiB0aGF0IHJldHVybnMgYSBwZXJmb3JtYW5jZSBzY29yZSAoKipuZWdhdGl2ZSBSTVNFKiogaW4gb3VyIGNhc2UpLg0KDQpgYGBweXRob24NCiMgRGVmaW5lIHRoZSBmdW5jdGlvbiB0byBvcHRpbWl6ZQ0KZGVmIHhnYl9ldmFsdWF0ZShsZWFybmluZ19yYXRlLCBtYXhfZGVwdGgsIHN1YnNhbXBsZSwgY29sc2FtcGxlX2J5dHJlZSk6DQogICAgcGFyYW1zID0gew0KICAgICAgICAnb2JqZWN0aXZlJzogJ3JlZzpzcXVhcmVkZXJyb3InLA0KICAgICAgICAnbGVhcm5pbmdfcmF0ZSc6IGxlYXJuaW5nX3JhdGUsDQogICAgICAgICdtYXhfZGVwdGgnOiBpbnQobWF4X2RlcHRoKSwgICMgTXVzdCBiZSBpbnRlZ2VyDQogICAgICAgICdzdWJzYW1wbGUnOiBzdWJzYW1wbGUsDQogICAgICAgICdjb2xzYW1wbGVfYnl0cmVlJzogY29sc2FtcGxlX2J5dHJlZSwNCiAgICAgICAgJ2V2YWxfbWV0cmljJzogJ3Jtc2UnDQogICAgfQ0KICAgIA0KICAgICMgUGVyZm9ybSBjcm9zcy12YWxpZGF0aW9uIHRvIGV2YWx1YXRlIHBlcmZvcm1hbmNlDQogICAgc2NvcmVzID0geGdiLmN2KHBhcmFtcywgZHRyYWluLCBudW1fYm9vc3Rfcm91bmQ9MTAwLCBuZm9sZD0zLCBtZXRyaWNzPSJybXNlIiwgZWFybHlfc3RvcHBpbmdfcm91bmRzPTEwKQ0KICAgIHJldHVybiAtc2NvcmVzWyJ0ZXN0LXJtc2UtbWVhbiJdLm1pbigpICAjIFdlIG1pbmltaXplIFJNU0UsIHNvIHJldHVybiBpdHMgbmVnYXRpdmUgdmFsdWUNCmBgYA0KDQotLS0NCg0KIyMjICoqU3RlcCA3OiBTZXQgVXAgQmF5ZXNpYW4gT3B0aW1pemF0aW9uKioNCk5vdywgd2UgZGVmaW5lICoqc2VhcmNoIGJvdW5kYXJpZXMqKiBmb3IgaHlwZXJwYXJhbWV0ZXJzLg0KDQpgYGBweXRob24NCiMgRGVmaW5lIHNlYXJjaCBzcGFjZSBmb3IgaHlwZXJwYXJhbWV0ZXJzDQpwYm91bmRzID0gew0KICAgICdsZWFybmluZ19yYXRlJzogKDAuMDEsIDAuMyksDQogICAgJ21heF9kZXB0aCc6ICgzLCAxMCksDQogICAgJ3N1YnNhbXBsZSc6ICgwLjUsIDEuMCksDQogICAgJ2NvbHNhbXBsZV9ieXRyZWUnOiAoMC41LCAxLjApDQp9DQoNCiMgSW5pdGlhbGl6ZSBCYXllc2lhbiBPcHRpbWl6YXRpb24NCm9wdGltaXplciA9IEJheWVzaWFuT3B0aW1pemF0aW9uKA0KICAgIGY9eGdiX2V2YWx1YXRlLCAgIyBPYmplY3RpdmUgZnVuY3Rpb24NCiAgICBwYm91bmRzPXBib3VuZHMsICAjIFNlYXJjaCBzcGFjZQ0KICAgIHJhbmRvbV9zdGF0ZT00Mg0KKQ0KYGBgDQoNCi0tLQ0KDQojIyMgKipTdGVwIDg6IFJ1biBCYXllc2lhbiBPcHRpbWl6YXRpb24qKg0KV2Ugbm93IG9wdGltaXplIGh5cGVycGFyYW1ldGVycyBvdmVyICoqMjAgaXRlcmF0aW9ucyoqLg0KDQpgYGBweXRob24NCm9wdGltaXplci5tYXhpbWl6ZShpbml0X3BvaW50cz01LCBuX2l0ZXI9MjApICAjIDUgaW5pdGlhbCByYW5kb20gcG9pbnRzLCAyMCBvcHRpbWl6YXRpb24gc3RlcHMNCg0KIyBQcmludCB0aGUgYmVzdCBwYXJhbWV0ZXJzIGZvdW5kDQpiZXN0X3BhcmFtcyA9IG9wdGltaXplci5tYXhbInBhcmFtcyJdDQpiZXN0X3BhcmFtc1sibWF4X2RlcHRoIl0gPSBpbnQoYmVzdF9wYXJhbXNbIm1heF9kZXB0aCJdKSAgIyBFbnN1cmUgaW50ZWdlciB2YWx1ZSBmb3IgbWF4X2RlcHRoDQpwcmludCgiQmVzdCBQYXJhbWV0ZXJzIEZvdW5kOiIsIGJlc3RfcGFyYW1zKQ0KYGBgDQoNCi0tLQ0KDQojIyMgKipTdGVwIDk6IFRyYWluIFhHQm9vc3Qgd2l0aCBPcHRpbWl6ZWQgSHlwZXJwYXJhbWV0ZXJzKioNCk9uY2Ugd2UgZmluZCB0aGUgYmVzdCBwYXJhbWV0ZXJzLCB3ZSB0cmFpbiBYR0Jvb3N0IHdpdGggdGhlbS4NCg0KYGBgcHl0aG9uDQojIFRyYWluIGZpbmFsIG1vZGVsIHdpdGggb3B0aW1pemVkIHBhcmFtZXRlcnMNCm9wdGltaXplZF9tb2RlbCA9IHhnYi50cmFpbihiZXN0X3BhcmFtcywgZHRyYWluLCBudW1fYm9vc3Rfcm91bmQ9MTAwKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCnlfcHJlZF9vcHQgPSBvcHRpbWl6ZWRfbW9kZWwucHJlZGljdChkdGVzdCkNCg0KIyBDb21wdXRlIFJNU0UNCm9wdGltaXplZF9ybXNlID0gbnAuc3FydChtZWFuX3NxdWFyZWRfZXJyb3IoeV90ZXN0LCB5X3ByZWRfb3B0KSkNCnByaW50KGYiT3B0aW1pemVkIFJNU0U6IHtvcHRpbWl6ZWRfcm1zZTouNGZ9IikNCmBgYA0KDQotLS0NCg0KIyMjICoqU3RlcCAxMDogQ29tcGFyZSBCYXNlbGluZSB2cy4gT3B0aW1pemVkIE1vZGVsKioNCmBgYHB5dGhvbg0KcHJpbnQoZiJCYXNlbGluZSBSTVNFOiB7YmFzZWxpbmVfcm1zZTouNGZ9IikNCnByaW50KGYiT3B0aW1pemVkIFJNU0U6IHtvcHRpbWl6ZWRfcm1zZTouNGZ9IikNCg0KaW1wcm92ZW1lbnQgPSAoYmFzZWxpbmVfcm1zZSAtIG9wdGltaXplZF9ybXNlKSAvIGJhc2VsaW5lX3Jtc2UgKiAxMDANCnByaW50KGYiUGVyZm9ybWFuY2UgSW1wcm92ZW1lbnQ6IHtpbXByb3ZlbWVudDouMmZ9JSIpDQpgYGANCg0K8J+TjCAqKkV4cGVjdGVkIFJlc3VsdDoqKiAgDQotIE9wdGltaXplZCBSTVNFICoqc2hvdWxkIGJlIGxvd2VyKiogdGhhbiB0aGUgYmFzZWxpbmUgUk1TRS4NCi0gVGhlIG1vZGVsICoqc2hvdWxkIGdlbmVyYWxpemUgYmV0dGVyKiogdG8gdW5zZWVuIGRhdGEuDQoNCi0tLQ0KDQojIyAqKjMuIFN1bW1hcnkgJiBLZXkgVGFrZWF3YXlzKioNCiMjIyAqKktleSBUYWtlYXdheXMqKg0K4pyUICoqQmF5ZXNpYW4gT3B0aW1pemF0aW9uIGZpbmRzIGJldHRlciBoeXBlcnBhcmFtZXRlcnMgZmFzdGVyKiogdGhhbiBHcmlkIFNlYXJjaCBvciBSYW5kb20gU2VhcmNoLiAgDQrinJQgKipQZXJmb3JtYW5jZSBpbXByb3ZlbWVudCoqOiBUdW5pbmcgaHlwZXJwYXJhbWV0ZXJzIGNhbiBzaWduaWZpY2FudGx5IGxvd2VyIFJNU0UgYW5kIGltcHJvdmUgcHJlZGljdGlvbnMuICANCuKclCAqKlhHQm9vc3QgKyBCYXllc2lhbiBPcHRpbWl6YXRpb24gaXMgZWZmaWNpZW50IGZvciByZWFsLXdvcmxkIGRhdGFzZXRzKiogKGUuZy4sIGhvdXNlIHByaWNlIHByZWRpY3Rpb24pLiAgDQoNCi0tLQ0KDQojIyAqKjQuIFRob3VnaHQtUHJvdm9raW5nIFF1ZXN0aW9ucyoqDQojIyMgKioxLiBDYW4gQmF5ZXNpYW4gT3B0aW1pemF0aW9uIFJlcGxhY2UgR3JpZCBTZWFyY2ggRW50aXJlbHk/KioNCi0gV291bGQgQmF5ZXNpYW4gT3B0aW1pemF0aW9uICoqYWx3YXlzIGJlIGZhc3RlciBhbmQgbW9yZSBlZmZpY2llbnQqKiwgb3IgYXJlIHRoZXJlIGNhc2VzIHdoZXJlIEdyaWQgU2VhcmNoIG1pZ2h0IHN0aWxsIGJlIHVzZWZ1bD8NCg0KIyMjICoqMi4gSG93IERvIEZlYXR1cmUgRW5naW5lZXJpbmcgYW5kIEh5cGVycGFyYW1ldGVyIFR1bmluZyBDb21wYXJlPyoqDQotIElmIGEgbW9kZWzigJlzIHBlcmZvcm1hbmNlIGltcHJvdmVzICoqb25seSBzbGlnaHRseSoqIGFmdGVyIHR1bmluZywgc2hvdWxkIHdlICoqZm9jdXMgbW9yZSBvbiBmZWF0dXJlIGVuZ2luZWVyaW5nIGluc3RlYWQqKj8NCg0KIyMjICoqMy4gV2hhdCBBcmUgdGhlIEJlc3QgU3RvcHBpbmcgQ3JpdGVyaWEgZm9yIEJheWVzaWFuIE9wdGltaXphdGlvbj8qKg0KLSBTaG91bGQgd2UgKipsaW1pdCB0aGUgbnVtYmVyIG9mIGl0ZXJhdGlvbnMqKiB0byBwcmV2ZW50IG92ZXJmaXR0aW5nPw0KLSBIb3cgZG8gd2UgZGVjaWRlIHdoZW4gKip3ZeKAmXZlIGZvdW5kIHRoZSBiZXN0IGh5cGVycGFyYW1ldGVycyoqPw0KDQotLS0NCg==