Section 1: Boosting for the study guide.


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

  1. AdaBoost (Adaptive Boosting)
    • Assigns higher weights to misclassified points.
    • Uses an exponential loss function.
    • Works well with small decision trees.
  2. 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).
  3. 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

  1. Initialize the model with weak learners (decision trees).
  2. Compute Residuals – Calculate errors from the previous iteration.
  3. Fit a New Tree – Train the next decision tree on residuals.
  4. Optimize the Loss Function – Uses gradient descent to minimize errors.
  5. Apply Regularization – Shrinks tree complexity to avoid overfitting.
  6. 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

Feature XGBoost 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.

XGBoost Demo 1 & 2 Study Guide

1. Introduction to XGBoost Demonstration

These two demonstrations walk through real-world applications of XGBoost, covering: - Demo 1: Using XGBoost for handwritten digit classification. - Demo 2: Applying XGBoost for regression tasks.

Both demos highlight data preprocessing, model training, and evaluation.


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 8x8 pixel grayscale images of handwritten digits (0-9).

2. Steps

Step 1: Import Necessary 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

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)

Step 4: Convert Data into XGBoost’s DMatrix Format

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

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}")

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 distance to employment centers.

2. Steps

Step 1: Import Necessary 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)

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)

Step 4: Convert Data into XGBoost’s DMatrix Format

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

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}")

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.


Additional Explanations for XGBoost Demos

1. XGBoost Demo 1: Handwritten Digit Classification

Data Preparation and Visualization

  • Dataset: The sklearn.datasets.load_digits() function loads an 8x8 pixel grayscale image dataset of handwritten digits (0-9).
  • Target Variable: The target values are the actual digit labels, ranging from 0 to 9.
  • Feature Representation: The features are the pixel values of each image flattened into a 64-dimensional vector.

Splitting Data

  • The data is split into training (80%) and testing (20%) using train_test_split() to ensure that we train the model on one set of data and evaluate it on unseen data.

DMatrix Conversion

  • DMatrix: This is an internal data structure used by XGBoost for efficiency. It stores both the features and labels but also allows for optimized memory and computation handling.

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

Training the Model

  • Objective Function (multi:softmax): This is used for multi-class classification (since there are 10 classes of digits).
  • num_class: This specifies the number of classes in the classification task (10 in this case).
  • eval_metric: The evaluation metric, mlogloss, measures the accuracy of the model’s class probability predictions using logarithmic loss.
  • Boosting Rounds: The model will iterate over the data 100 times (100 rounds of boosting) to optimize the predictions.

Evaluating the Model

  • Accuracy Calculation: After training, we use model.predict(dtest) to make predictions on the test set. Then, we compare the predicted values with the true labels to compute the accuracy.

2. XGBoost Demo 2: Regression on Boston Housing Data

Data Preparation

  • Dataset: The Boston Housing Dataset contains 13 features representing aspects of housing (e.g., crime rate, number of rooms) and the target variable is the price of houses.
  • Features: Includes variables like average number of rooms per dwelling (RM), distance to employment centers (DIS), and pupil-teacher ratio in schools (PTRATIO).
  • Target Variable: The house prices in thousands of dollars.

Splitting Data

  • Like the classification demo, we split the dataset into training and test sets using train_test_split(). This ensures that the model trains on one set of data and is evaluated on unseen data.

DMatrix Conversion

  • Similar to the classification demo, we convert the training and testing data into DMatrix format to optimize the model’s performance:

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

Training the Model

  • Objective Function (reg:squarederror): For regression tasks, we use the mean squared error loss function.
  • Regularization (max_depth, learning_rate): We control the complexity of the trees using the max_depth parameter (4) and use a learning rate (learning_rate = 0.1) to control how much the model changes with each boosting round.
  • Boosting Rounds: We set the number of boosting rounds to 100. The model iteratively improves over these rounds.

Evaluating the Model

  • Root Mean Squared Error (RMSE): After making predictions with model.predict(), we calculate the RMSE to measure how well the model is predicting housing prices.
    • A lower RMSE means better predictions.

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.


2. Extracting Feature Importance in XGBoost

Once an XGBoost model is trained, you can extract feature importance scores.

Step 1: Train an XGBoost Model

import xgboost as xgb
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
import pandas as pd
import matplotlib.pyplot as plt

# Load dataset
boston = load_boston()
X, y = boston.data, boston.target
feature_names = boston.feature_names  # Column names

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

# Train XGBoost model
model = xgb.XGBRegressor(objective='reg:squarederror', n_estimators=100, max_depth=4)
model.fit(X_train, y_train)

Step 2: Retrieve and Display Feature Importance

# Get feature importance scores
importance = model.get_booster().get_score(importance_type='gain')

# Convert to DataFrame for better visualization
importance_df = pd.DataFrame({'Feature': importance.keys(), 'Importance': importance.values()})
importance_df = importance_df.sort_values(by="Importance", ascending=False)

# Plot feature importance
plt.figure(figsize=(10, 5))
plt.barh(importance_df['Feature'], importance_df['Importance'], color='blue')
plt.xlabel("Feature Importance (Gain)")
plt.ylabel("Feature Name")
plt.title("Feature Importance in XGBoost")
plt.gca().invert_yaxis()  # Flip the chart to show the most important feature at the top
plt.show()

Alternative Method Using plot_importance()

XGBoost also provides a built-in function:

xgb.plot_importance(model, importance_type='gain', max_num_features=10)
plt.show()
  • importance_type='gain': Sorts features by their information gain.
  • max_num_features=10: Limits the number of features shown.

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

Method Pros Cons
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

Final Summary

  1. Feature Importance in XGBoost helps identify which variables contribute most to predictions.
  2. Bayesian Optimization provides an intelligent approach to hyperparameter tuning.
  3. XGBoost with Bayesian Optimization outperforms traditional tuning methods.

SHAP (SHapley Additive exPlanations) and Advanced XGBoost Techniques

SHAP is a powerful method for interpreting model predictions, while early stopping and custom loss functions can enhance model performance.


1. SHAP (SHapley Additive Explanations)

1.1 What is SHAP?

SHAP provides game-theoretic explanations of machine learning model predictions. It calculates how much each feature contributes to an individual prediction and explains whether it pushes the prediction higher or lower.

Interpretability: Understand how each feature affects predictions.
Feature Influence: Identify important features for decision-making.
Works with Any Model: SHAP supports XGBoost, LightGBM, and other models.


1.2 Implementing SHAP in XGBoost

Step 1: Install SHAP

pip install shap

Step 2: Import Libraries

import shap
import xgboost as xgb
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split

Step 3: Load and Train an XGBoost Model

# Load dataset
boston = load_boston()
X, y = boston.data, boston.target
feature_names = boston.feature_names

# Train-test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Train an XGBoost model
model = xgb.XGBRegressor(objective='reg:squarederror', n_estimators=100, max_depth=4)
model.fit(X_train, y_train)

Step 4: Explain Model Predictions with SHAP

# Create SHAP explainer
explainer = shap.Explainer(model)

# Compute SHAP values
shap_values = explainer(X_test)

# Summary plot of feature importance
shap.summary_plot(shap_values, X_test, feature_names=feature_names)

🚀 SHAP summary plot shows the impact of each feature on model predictions.

Step 5: Force Plot for Individual Prediction

# Force plot for a single prediction
shap.force_plot(explainer.expected_value, shap_values[0].values, X_test[0], feature_names=feature_names)

🎯 Force plots help visualize how each feature moves the prediction higher or lower.

Step 6: SHAP Dependence Plot

# SHAP dependence plot for a feature
shap.dependence_plot("RM", shap_values.values, X_test, feature_names=feature_names)

📊 Dependence plots show how a feature interacts with the target.


2. Advanced XGBoost Techniques

2.1 Early Stopping in XGBoost

Early stopping prevents overfitting by stopping training when the model stops improving on validation data.

2.1.1 How Early Stopping Works

  1. Split data into training and validation sets.
  2. Track performance using an evaluation metric.
  3. Stop training if the metric stops improving after a certain number of rounds.

2.1.2 Implementing Early Stopping in XGBoost

# Convert to DMatrix format
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)

# Define parameters
params = {
    'objective': 'reg:squarederror',
    'learning_rate': 0.1,
    'max_depth': 4,
    'eval_metric': 'rmse'
}

# Train with early stopping
model = xgb.train(params, dtrain, num_boost_round=500, evals=[(dtest, "eval")], 
                  early_stopping_rounds=20, verbose_eval=10)
  • num_boost_round=500: Maximum trees to train.
  • early_stopping_rounds=20: Stop training if RMSE doesn’t improve for 20 consecutive rounds.

Results: Early stopping improves generalization and speeds up training.


2.2 Custom Loss Functions in XGBoost

XGBoost allows custom loss functions for specialized tasks.

2.2.1 Example: Huber Loss Function

Huber Loss combines Mean Squared Error (MSE) and Mean Absolute Error (MAE) for robust regression.

Define Custom Huber Loss

def huber_loss(preds, dtrain):
    delta = 1.0
    labels = dtrain.get_label()
    residual = preds - labels
    condition = np.abs(residual) <= delta
    gradient = np.where(condition, residual, delta * np.sign(residual))
    hessian = np.where(condition, 1, 0)
    return gradient, hessian

Train XGBoost with Huber Loss

# Define parameters
params = {
    'objective': 'reg:squarederror',
    'learning_rate': 0.1,
    'max_depth': 4
}

# Train model with custom loss
model = xgb.train(params, dtrain, num_boost_round=100, obj=huber_loss)

Why use custom loss functions? - Tailor models to specific business needs. - Improve robustness against outliers.


Final Summary

SHAP for Model Interpretation

✔ Identifies important features.
✔ Explains individual predictions.
✔ Provides global and local feature effects.

Advanced XGBoost Techniques

Early stopping prevents overfitting.
Custom loss functions allow for specialized models.


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.


4. Grid Search vs. Random Search: Which One to Use?

Scenario Use Grid Search Use Random Search
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

  1. Start with Random Search to quickly find a good range of values.
  2. 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.


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.

Hyperparameter Description Typical Range
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.

Hyperparameter Description Typical Range
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.

Hyperparameter Description Typical Range
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.

Hyperparameter Description Typical Range
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 4: Convert Data into XGBoost’s DMatrix Format

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

📌 Why use DMatrix?
- DMatrix optimizes computation for faster training and prediction.

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 4: Convert Data into XGBoost’s DMatrix Format

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

📌 Why use DMatrix?
- Improves speed and memory efficiency during training.

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

Method How It Works Pros Cons
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]
}

Step 4: Perform Grid Search

grid_search = GridSearchCV(
    estimator=xgb_model, 
    param_grid=param_grid, 
    scoring='neg_mean_squared_error', 
    cv=5, 
    verbose=1
)

# Train model
grid_search.fit(X_train, y_train)

# Print best parameters
print("Best Parameters:", grid_search.best_params_)

📌 What happens here? - Exhaustively tests all combinations from param_grid. - Uses cross-validation (cv=5) to find the best hyperparameters. - Scoring metric: neg_mean_squared_error (lower is better). - Prints the best combination of hyperparameters.


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
}

Step 3: Perform Random Search

random_search = RandomizedSearchCV(
    estimator=xgb_model,
    param_distributions=param_dist,
    n_iter=25,  # Number of random combinations to try
    scoring='neg_mean_squared_error',
    cv=5,
    verbose=1,
    random_state=42
)

# Train model
random_search.fit(X_train, y_train)

# Print best parameters
print("Best Parameters from Random Search:", random_search.best_params_)

📌 What happens here? - Randomly samples 25 different hyperparameter combinations. - Uses continuous distributions instead of predefined values. - Runs 5-fold cross-validation for each sampled combination. - Outputs the best hyperparameter combination.


4. Grid Search vs. Random Search: Which One to Use?

Scenario Use Grid Search Use Random Search
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

  1. Start with Random Search to quickly find a good range of values.
  2. 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

1. Can Feature Constraints Improve Explainability Without Sacrificing Performance?

  • If certain features shouldn’t interact due to domain constraints, how much does limiting interactions impact accuracy?
  • Would the model be more interpretable and generalizable if constraints were added?

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.

1. Dataset Selection

We will use the California Housing Prices Dataset from sklearn.datasets.
- Task: Predict house prices based on features like median income, population, and location.
- Objective: Regression problem (continuous target variable).
- Optimization Goal: Tune XGBoost hyperparameters using Bayesian Optimization for best performance.


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 4: Define XGBoost Model and Baseline Performance

Before tuning, let’s train a default XGBoost model to establish a baseline.

# Define baseline parameters
baseline_params = {
    'objective': 'reg:squarederror',
    'eval_metric': 'rmse',
    'max_depth': 6,
    'learning_rate': 0.1,
    'n_estimators': 100,
    'subsample': 0.8,
    'colsample_bytree': 0.8
}

# Train the model
baseline_model = xgb.train(baseline_params, dtrain, num_boost_round=100)

# Make predictions
y_pred = baseline_model.predict(dtest)

# Compute RMSE
baseline_rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"Baseline RMSE: {baseline_rmse:.4f}")

📌 Baseline RMSE provides a reference to see how much hyperparameter tuning improves performance.


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==