1 Introduction

In the previous module, we introduced two types of regression models in statistics that are formulated based on several assumptions such as iid (identically independent distribution), normal distribution or binomial distribution of the response variable, the large sample size for using the central limit theorem to define different performance metrics, etc. With these assumptions, we can define different methods to assess the performance of models and identify the best models for implementation.

In data science projects, it is quite often that data generation processes do not meet the requirements for many classical models and procedures. We need to use appropriate performance metrics to assess the goodness of the model/algorithm without having strict assumptions and make sure the model will be consistently reliable across new, unseen data sets.

The majority of performance metrics in data science are defined directly based on either the estimated error of the response or some type of ‘distance’. In predictive modeling, most of the performance measures are defined with predictive error probabilities. We will introduce one of the most commonly used global predictive performance measures - the ROC (receiver operating characteristics) curve and its components.

In this module, we will discuss the basics of K-Fold Cross-Validation and compare it to some simpler methods. Some commonly used performance measures for predictive modeling such as sensitivity, specificity, false positive rate, false negative rate, ROC curve, and the area of the ROC curve (AUC) will also be introduced and illustrated with some simple examples.

2 Review of Linear and Logistic Models

In classical statistical modeling, we select candidate models based on exploratory data analysis and visual analysis. The candidate models are then fit to the analytic data set. Since all models have some sort of assumptions (think about linear regression and logistic and Poisson regression models), and then carry out model diagnostic analyses to identify potential violations of the model assumption. Of course, we assume that the data represent the population. This modeling process is depicted in the following diagram.

Classical statistical modeling process.

Classical statistical modeling process.

Recall the process of the following two representative statistical models.

2.1 Linear Regression Model

First, we look at the process of how to build a linear regression model for a data set.

  • Explicit and Implicit Assumptions. The following are some of the assumptions about a normal-based linear regression model:

    • Predictor (feature) variables are (linearly or curve-linearly) correlated to the response variable (also called label in machine learning terms);
    • Predictor variables themselves are NOT highly linearly correlated (i.e., no serious collinearity);
    • The response variable is normally distributed;
    • The variance of the response variable is constant;
    • There are no outlier (influential) observations;
    • information on all relevant predictor variables is available in the data (i.e., no important predictor is missing);
  • Model Fitting (Parameter Estimating). The least-square estimator (LSE), which is equivalent to the maximum likelihood estimator (MLE) when the response variable is assumed to be a normal distribution, can be used to estimate the regression coefficients.

  • Model Selection and Diagnostics. Since several implicit and explicit assumptions have been assumed underlying the linear regression, different sets of diagnostic measures were developed to detect different potential violations of the model.

    • global goodness-of-fit: R2, AIC and SBC (requires normality assumption of the response variable), MSE, etc.
    • local goodness-of-fit/model selection: R, F-test (need normality and equal variance of the response variable), t-test, likelihood ratio test, Mallow’s Cp, etc.
    • normality: QQ-plot and probability plot for a visual check, goodness-of-fit tests such as Anderson-Darling, Cramer-von Miss, Kolmogorov-Smirnov, Shapiro-Wilks, and Pearson’s chi-square tests, etc.
    • detecting outliers/influential observations: leverage point-hat matrix, DFITT - defined based on leave-one-out resampling method, cook’s distance, (scaled) residual plots, etc.
    • verifying constant variance: F-test (requires normality), Brown-Forsythe test (nonparametric), Breusch-Pagan Test (also nonparametric), Bartlett’s Test (requires normality), etc.
    • detecting collinearity: Variance inflation factor (VIF) for data-based and structural collinearity.
    • detecting mission of determinant variable:

2.2 Logistic Regression Model

For the binary logistic model, we also follow the same steps to identify the final model. For example, the well-known binary logistic regression modeling follows similar steps:

  • Assumptions: Unlike the linear regression model which has a strong assumption of normality, the logistic regression model assumes the following

    • binomial distribution: The dependent variable is binary.
    • independence: The logistic regression requires the observations to be independent of each other.
    • collinearity: The logistic regression requires there to be little or no multicollinearity among the independent variables.
    • linearity: The logistic regression assumes linearity of independent variables and the log odds of the event of interest.
    • large sample size: The logistic regression typically requires a large sample size. A general guideline is that you need a minimum of 10 observations with the least frequent outcome for each independent variable in your model.
    • mis-specification: No important variables are omitted. No extraneous variables are included.
    • measurement error: The independent variables are measured without error.
  • Model Fitting: The coefficients of the logistic regression model are estimated using a maximum likelihood estimator. Note LSE cannot be estimated for the logistic regression model.

  • Model Selection and Diagnostics: Unlike the normal linear regression model, there are a few diagnostic methods one can use in logistic regression models.

    • misspecification: link test (large sample test);
    • goodness-of-fit: log-likelihood chi-square and pseudo-R-square; Hosmer-Lemeshow’s lack of fit test AIC and SBC.
    • multi-collinearity: VIF
    • influential points: Cook’s distance, DBETA, deviance residuals


2.3 Predictive Performance Measures

Regression models can be used for either predictive or association analysis. Different performance metrics have been defined to assess the predictive power of regression models. We list some of the error-based measures in the following.

2.3.1 Linear Regression

When evaluating the predictive performance of linear regression models, several key metrics based on predictive error (residuals) are commonly used. Let \(\{\hat{y}_1, \hat{y}_2, \cdots, \hat{y}_k\}\) be the corresponding predicted values of \(\{y_1, y_2, \cdots, y_k \}\).

Mean Absolute Error (MAE) is defined by

\[ MAE = \frac{\sum_{i=1}^k |\hat{y}_i-y_i|}{k} \]

This measures the average magnitude of the errors in predictions, without considering their direction. It is the average of the absolute differences between predicted and actual values. MAE is less sensitive to outliers compared to other metrics.

Mean Squared Error (MSE) is defined by

\[ MSE = \frac{\sum_{i=1}^k (\hat{y}_i-y_i)^2}{k} \]

This measures the average of the squares of the errors. It gives a higher weight to larger errors, making it useful for identifying models that have large prediction errors.

Root Mean Squared Error (RMSE) is defined by

\[ RMSE = \sqrt{\frac{\sum_{i=1}^k (\hat{y}_i-y_i)^2}{k}} \]

This is the square root of the MSE. It provides an error value in the same units as the dependent variable, making it easier to interpret.


2.3.2 Logistic Regression

Recall that the logistic regression model.

\[ P(Y = \text{success}) = \frac{\exp(\beta_0 + \beta_1 x_1 + \cdots + \beta_k x_k)}{1+\exp(\beta_0 + \beta_1 x_1 + \cdots + \beta_k x_k)}. \]

The model predicts the probability of success but not the value of \(Y\) (“success” or “failure”). In order to predict “success” or “failure”, we need to have a cut-off probability. The key predictive performance metrics are defined based on the predicted values of \(Y\) (“success” or “failure”) and the true values of \(Y\) which is tabulated in the following confusion matrix

Confusion matrix of binary logistic predictive models

Confusion matrix of binary logistic predictive models

Several key metrics that are commonly used are based on the above confusion matrix.

True Positive Rate (TPR): The proportion of correctly predicted positive among all true POSITIVES. True Positive Rate is also called the sensitivity of the model which is a conditional probability \(P(\text{predicted true}| \text{actual true})\). The sample TPR is given by

\[ \text{True Positive Rate} = \frac{TP}{TP + FN} \]

False Positive Rate (FPR): The proportion of incorrectly predicted positive among all true NEGATIVES. is a conditional probability \(P(\text{predicted true}| \text{actual negative})\). The sample TPR is given by

\[ \text{False Positive Rate} = \frac{FP}{FP + TN} \]

True Negative Rate (also called Specificity) is the probability that an actual negative will test negative. That is, \(P(\text{Predicted Negative}|\text{Actual Negative})\). The sample true negative rate (specificity) is given by

\[ \text{True Ngative Rate} = \frac{TN}{TN+FP}. \]

Apparently, \(\text{false positove rate} = 1 - \text{specificity}\).

Accuracy: The proportion of correctly predicted instances out of the total instances.

\[ \text{accuracy} = \frac{TP + TN}{TP + TN + FP + FN} \]

Precision and Recall: Precision measures the proportion of true positive predictions among all positive predictions, while recall (or sensitivity) measures the proportion of true positives identified out of all actual positives.

\[ \text{precision} = \frac{TP}{TP+ FP} \ \ \text{and} \ \ \text{recall} = \frac{TP + TN}{TP + FN} \]

F1 Score: The harmonic mean of precision and recall, providing a balance between the two.

\[ \text{F1 Score} = 2\times \frac{\text{precision} \times \text{recall} }{\text{precision} + \text{recall}} \]

2.3.3 ROC Curve

The above performance metrics are defined based on the confusion matrix. However, a confusion matrix is defined based on the choice of the cut-off predictive scores. In logistic predictive modeling, the predictive scores are the probabilities of observing the target category label (either success or failure if the binary response with these two character values) in the training model.

Since the success probability is between 0 and 1, we can try different cut-off probabilities to construct different confusion matrices and calculate the corresponding sensitivity and specificity measures. We can use these pairs of sensitivity and specificity at different cut-off probabilities. The plot of sensitivity against (1-specificity) is called Receiver Operating Characteristic (ROC) curve.

For example, if we have a predictive model such as logistic regression to predict whether an animal is a dog or cat based on a data set with some given features related to cats and animals. The following animated graph shows that the coordinates of each point on the ROC curve are determined by a confusion matrix (formed by a cut-off probability).

ROC curve demonstration

ROC curve demonstration

The next animated graph shows the prediction error-based performance at each given cut-off probability.

Animated ROC curve.

Animated ROC curve.

When comparing two more classification models, ROC curves provide an intuitive visual comparison of the global performance of the underlying models. However, if two ROC curves of two underlying classification models intersect, sometimes it is difficult to compare the global performance of the two underlying models through visual representations. The following figure shows the ROC curves of two hypothetical classification models. It is hard to check the performance of the two models.

Using ROC for model selection.

Using ROC for model selection.

In the above case, we can use the areas under the ROC curves to compare the global performance of the two underlying models. The Area Under the Curve (AUC) provides a numerical measure of the overall performance of a classification model. In the next example, we will use an example to demonstrate how to construct an ROC curve of a logistic predictive model and its AUC using an R function.

2.4 Example of Fraud Detection

This data set was extracted from a real-world data set. The data set contains a fraud index (calculated from real fraud data) and fraud status (in the character format). We will use a data set to build a logistic regression and construct an ROC curve.

fraud.data = read.csv("https://pengdsci.github.io/datasets/FraudIndex/fraudidx.csv")[,-1]
## encode status variable: bad = 1 and good = 0
good.id = which(fraud.data$status == "good ") 
bad.id = which(fraud.data$status == "fraud")
##
fraud.data$fraud.status = 0
fraud.data$fraud.status[bad.id] = 1
cut.off.seq = seq(0, 1, length = 100)
sensitivity.vec = NULL
specificity.vec = NULL
### 
logit.model = glm(fraud.status ~ index, family = binomial(link = logit), data = fraud.data)
newdata = data.frame(index= fraud.data$index)
pred.prob.train = predict.glm(logit.model, newdata, type = "response")
for (i in 1:100){
  fraud.data$train.status = as.numeric(pred.prob.train > cut.off.seq[i])
### components for defining various measures
TN = sum(fraud.data$train.status == 0 & fraud.data$fraud.status == 0) # true negative
FN = sum(fraud.data$train.status == 0 & fraud.data$fraud.status == 1) # false negative
FP = sum(fraud.data$train.status == 1 & fraud.data$fraud.status == 0) # false positive
TP = sum(fraud.data$train.status == 1 & fraud.data$fraud.status == 1) # true positive
###
sensitivity.vec[i] = TP / (TP + FN)
specificity.vec[i] = TN / (TN + FP)
}
one.minus.spec = 1 - specificity.vec
sens.vec = sensitivity.vec
## we next use library {pROC}
## to calculate AUC
  prediction = pred.prob.train
  category = fraud.data$fraud.status == 1
  ROCobj <- roc(category, prediction)
  AUC = round(auc(ROCobj),4)
####
par(pty = "s")   # make a square figure
plot(one.minus.spec, sens.vec, type = "l", xlim = c(0,1), ylim = c(0,1),
     xlab ="1 - specificity",
     ylab = "sensitivity",
     main = "ROC curve of Logistic Fraud Model",
     lwd = 2,
     col = "blue", )
segments(0,0,1,1, col = "red", lty = 2, lwd = 2)
#AUC = round(sum(sens.vec*(one.minus.spec[-101]-one.minus.spec[-1])),4)
text(0.8, 0.3, paste("AUC = ", AUC), col = "blue", cex = 0.8)
ROC Curve of logistic fraud model

ROC Curve of logistic fraud model

ROC curves and AUCs are used to compare the performance of two predictive models. The bigger the AUC, the better the predictive model. If a single predictive model is used, the AUC measures the predictive power of the model. In general, an AUC of 0.5 suggests no discrimination (i.e., ability to diagnose patients with and without the disease or condition based on the test), 0.7 to 0.8 is considered acceptable, 0.8 to 0.9 is considered excellent, and more than 0.9 is considered outstanding.

The AUC of the above logistic predictive model is bigger than 0.9 which indicates the model has an outstanding predictive power.


3 Data Splitting Types

One of the fundamental differences between statistical and machine learning modeling is that both fields use different model-building strategies. In classical statistical modeling, modeling building and evaluation are based on the same data set. However, in data science, the data set is partitioned into disjoint subsets for modeling training (building), validation, and testing, respectively. With data splitting, the model-building process will relax the assumptions used in the classical statistical analysis.

This section outlines data splitting types that are commonly used in practice.

3.1 Two-way Splitting

Train-Test Split: The data set is divided into two sets: a training set and a testing set. Typically, 70-80% of the data is used for training, and 20-30% is used for testing.

Training-testing split: two-split

Training-testing split: two-split


Training Data

The training data set is the sample of data used to fit the model. In other words, the training data teaches the model how it’s supposed to learn and think. To do this, training data is often presented in pairs: predictor variables (feature variables) and a response variable (also called a label).

Training data is the first set of data the model/algorithm is exposed to. During each stage of training, the model will be fit to the training data and estimate the parameters (also called weight in some machine learning algorithms such as neural networks).

Because the training data set is used to estimate the parameters (i.e., teaching the algorithm), it requires a certain amount of information to make the algorithms and models reliable. It makes up between 60% and 80% of the total data.


Test Data

The test data set is a sample of data used to provide an unbiased evaluation of a final model fit on the training data set or to test the model. Put more simply, test data is a set of unlabeled inputs (i.e., the response value is removed from the data) that test whether the model is producing the correct outputs in the real world.

The key difference between a validation data set and a test data set is that the validation data set is used during model configuration, while the test data set is reserved to evaluate the final model.

Test data is about 20% of the total data and should be completely separate from the training data — which our model should know very well by this point.

Example

In classical statistics, we build and identify the final model using the entire data set and report the performance of the final using the same entire data set because we made strong assumptions about models. For example, in the linear regression, we use the training data set to estimate the coefficients of linear regression model \(y = \beta_0+\beta_1 x_1 + \beta_2 x_2 + \cdots + \beta_k x_k\) and obtain the following fitted model

\[ y = \hat{\beta}_0+\hat{\beta}_1 x_1 + \hat{\beta}_2 x_2 + \cdots + \hat{\beta}_k x_k \]

We use the above-estimated model and the \(x\)-values in the testing data set to predict the corresponding \(y\)-value. Then we can estimate the true bias.

3.2 Three-way Splitting

Train-Validation-Test Split: The data set is split into three subsets: training, validation, and testing. The training set is used to train the model, the validation set is used to tune hyper-parameters and validate performance during training, and the testing set is used for final evaluation.

Training-validating-testing split: three-way split

Training-validating-testing split: three-way split

Validation Data

The validation data set is a sample of data held back from training the model. This data set provides an unbiased evaluation of a model fit on the training data set while tuning model hyperparameters or calculating a performance measure for model selection. In more basic terms, validation data is an unused portion of your training data and helps determine if the initial model is accurate.

Illustrative example of three-way data split

Illustrative example of three-way data split

A model hyperparameter is a configuration that is external to the model and whose value cannot be estimated from data.

  • They are often used in processes to help estimate model parameters.
  • They are often specified by the practitioner.
  • They can often be set using heuristics.
  • They are often tuned for a given predictive modeling problem.

We don’t know the best value for a model hyperparameter on a given problem. We may use rules of thumb, copy values used on other problems, or search for the best value by trial and error. For example, in predicting labels in the logistic regression models, we need to use an optimal cut-off probability to achieve the best accuracy. The cut-off probability is the hyper-parameter.

3.3 Overfitting and Underfitting

Overfitting and underfitting are common issues in data science (including statistics) that affect a model’s performance.

include_graphics("img/OverfittingUnderfitting.png")
Illustration of overfitting and underfitting

Illustration of overfitting and underfitting

Overfitting occurs when a model learns the training data too well (high accuracy), including its noise and outliers. This makes the model perform exceptionally well on training data but poorly on new, unseen data (poor accuracy). Different reasons may cause overfitting. For example, model complexity such as too many features and insufficient training data. In practice, we can use the cross-validation method to detect overfitting and fix the issues by simplifying the models and increasing the volume of the training data set.

Underfitting happens when a model is too simple to capture the underlying patterns in the data. It performs poorly on both training and new data. An underfitting model results in low accuracy on both training and validation/test data. Underfitting can also be detected by cross-validation. The remedies for improving the underfitting issue are to increase modeling complexity by adding more features and reducing regularization (restriction).

3.4 Splitting Methods

There are various methods of splitting data sets. The right approach for data splitting and the optimal split ratio both depend on several factors, including the use case, amount of data, quality of data, and the number of hyper-parameters.

The most common approach for dividing a data set is random sampling. As the name suggests, the method involves shuffling the data set and randomly assigning samples to training, validation, or test sets according to predetermined ratios. With class-balanced data sets, random sampling ensures the split is unbiased.

However, random sampling is not the correct approach with imbalanced data sets (the imbalanced class ratio is usually less than 1% ). When the data consists of skewed class proportions, random sampling will almost certainly create a bias in the model. In this case, we need to use stratified sampling in which the random sampling method is utilized in the major and minor categories respectively according to the predetermined ratios. The final training, validating, and testing data sets are formed using the corresponding stratified samples.

Caution: Prediction/classification problems based on imbalanced data are challenging in practice. Some special methods in either sampling or modeling are needed to handle the imbalanced category issue. In this course, we only focus on balanced data.

4 Cross-Validation

Different cross-validation methods are running in practice. We will discuss the K-fold CV in detail and work on an example to illustrate the steps for implementing the procedure. Other cross-validation methods will also be listed for reference at the end of this section.

4.1 K-Fold Cross-Validation

K-fold Cross Validation: The data set is divided into (\(k\)) equally sized folds. The model is trained and evaluated (\(k\)) times, each time using (\(k-1\)) folds for training and 1 fold for validation/testing. This helps in obtaining more robust performance estimates. This note will focus on \(K\)-fold cross-validation and its applications.

K-fold cross validation split

K-fold cross validation split

K-Fold Cross-Validation partitions the data set into multiple subsets to iteratively train and test the model, the Train-Test Split method divides the data set into just two parts: one for training and the other for testing. The Train-Test Split method is simple and quick to implement. It provides a more robust and reliable performance estimate because it reduces the impact of data variability. By using multiple training and testing cycles, it minimizes the risk of overfitting to a particular data split. This method also ensures that every data point is used for both training and validation, which results in a more comprehensive evaluation of the model’s performance.

The fold number K is considered as a hyper-parameter which can be tuned using validation data. In practice, it is rarely tuned to save computational resources. The common practice is to choose a number between 5 and 10. In the next subsection, we use 5-fold cross-validation for model selection in linear regression using performance measure MSE.

4.2 5-Fold Cross-Validation

The general 5-fold scheme is depicted in the following.

include_graphics("img/FiveFoldCV.png")
Five-fold Cross-validation scheme

Five-fold Cross-validation scheme

Next, we use an example to illustrate the steps for 5-fold cross-validation.

Example: Palmer Penguins Dataset contains 344 penguins from 3 different species collected from 3 islands in the Palmer Archipelago, Antarctica. In this example, we only use four numerical variables: Culmen Depth (mm), Culmen (mm), flipper length (mm), and body mass (g), to implement cross-validation for model selection in linear regression.

include_graphics("img/penguinSize.png")
Illustration of penguin size measures

Illustration of penguin size measures

We will build two linear regression models and then use a 5-fold cross-validation method to select the one with better performance (smaller MSE). The two models are defined in the following.

\[ \text{ M1: } \ \ \text{BodyMass}= \beta_0 + \beta_1 \text{CulmenLength} + \beta_2 \text{CulmenDepth} + \epsilon \]

and

\[ \text{ M2: } \ \ \text{BodyMass}= \alpha_0 + \alpha_1 \text{CulmenLength} + \alpha_2 \text{CulmenDepth} +\alpha_3 \text{FlipperLenth} + \psi, \] where \(\epsilon \sim N(0, \sigma^2)\) and \(\psi \sim N(0, \delta^2)\).

We use 80%-20% splitting ratio to randomly split into training and test data sets. We 5-fold splitting scheme in the training data. We build the above two models with 4 combined training folds and calculate the MSE based on the validation fold. Under this 5-fold cross-validation, each of the two models will have 5 performance measures (MSE), we calculate the mean of MSE of the two models respectively and the model with the smaller mean MSE is the one to be the final model to report (implement). The following figure depicts this process.

include_graphics("img/5foldCV.png")
The process 5-fold cross-validation for model selection with MSE

The process 5-fold cross-validation for model selection with MSE

4.2.1 Cross-validation with Linear Regression

We implement cross-validation using the penguin data to assess the performance of linear regression models.

penguin = read.csv("PenguinsSize.csv", header = TRUE)
pairs(penguin[, 3:6])
Pairwise scatter plot showing the correlations among the linear correlation between variables.

Pairwise scatter plot showing the correlations among the linear correlation between variables.

The basic steps are outlined in the following:

  • Two-way data splitting split the entire sample into training data (80%) and testing data (20%).

  • Creating 5-fold data: randomly split the training data into 5 folds (with approximately equal size).

  • Performing cross-validation: fit the two models to the 5 different combinations of combined training folds and evaluate the performance based on the corresponding validation folds.

  • Averaging MSE: take the average of the MSE of the two models respectively and report them for model selection.

The R code for implementing the above 5-fold cross-validation is given by

penguin = na.omit(penguin)  # delete all records with missing values
##
n = dim(penguin)[1]     # sample size
obs.ID = 1:n            # randomize obs ID
n.train = round(0.8*n)  # training data size

shuffled.id = sample(obs.ID, 
                          n, 
                          replace = FALSE)  # randomizing the observation IDs 
shuffled.penguin = na.omit(penguin[shuffled.id, ])   # randomizing the data set
# Two-way split for training and testing data sets
train.data = shuffled.penguin[1:n.train,]         # training data
test.data = shuffled.penguin[(n.train + 1):n, ]   # testing data
##
n.fold = round(n.train/5)-1    # size of 5-fold splitting, -1 to guarantee that
                               # 5*foldsize <= train.data size
## 5-fold cross-validation: iterative process with a for-loop
##
MSE.M1 = rep(0,5)
MSE.M2 = rep(0,5)
##
for (i in 1:5){
  valid.id = ((i-1)*n.fold+1):(i*n.fold)    # obs ID for validating fold 
  cross.train = train.data[-valid.id, ]
  cross.valid = train.data[valid.id,]
  ## Building the two models
  M1 = lm(bodyMass ~ culmenLength + culmenDepth, data  = cross.train)
  M2 = lm(bodyMass ~ culmenLength + culmenDepth + flipperLength, data  = cross.train)
  ## Predicting bodyMass with validate fold
  predM1 = predict(M1, newdata = cross.valid)
  predM2 = predict(M2, newdata = cross.valid)
  ##
  MSE.M1[i] = mean((predM1 - cross.valid$bodyMass)^2)
  MSE.M2[i] = mean((predM2 - cross.valid$bodyMass)^2)
 }
 MSE.1 = mean(MSE.M1)
 MSE.2 = mean(MSE.M2)
 ### Visualize the MSE 
 plot(1:5, MSE.M1, type = "l", 
      xlim =c(0,6),
      ylim = c(100000, 50000+max(MSE.M1)),
      ylab = "MSE (g)",
      xlab = "Iterations 1 to 5",
      lwd = 2,
      col = "navy",
      main = "Performance Evaluation via 5-fold Cross Validation",
      cex.main = 0.8)
 points(1:5, MSE.M1, pch = 19, col = "navy")
 lines(1:5, MSE.M2, lwd = 2, col = "darkred")
 points(1:5, MSE.M2, pch = 19, col = "darkred")
 legend("topright", c(paste("M1 Avg. MSE: ", round(MSE.1)), paste("M2 Avg. MSE: ",round(MSE.2))),
        col=c("navy", "darkred"),
        pch = rep(19,2), 
        lwd = rep(2,2),
        cex = 0.6,
        bty = "n")
Cross-validation for evaluating predictive performance of linear regression models

Cross-validation for evaluating predictive performance of linear regression models

The above figure indicates that model 2 (M2) has a smaller average fold-wise MSE. This means that model M2 has a better predictive performance. Therefore, M2 is recommended for practical prediction.

In real-world applications, the actual performance of the recommended model should also be reported to the clients based on the test data that has not been used yet. The common practice is to refit the recommended model to the original entire training data to update the regression coefficients and then use this refitted model to evaluate the predetermined performance measure (in our case MSE) based on the testing data. The following code shows this step.

M2.final = lm(bodyMass ~ culmenLength + culmenDepth + flipperLength, data  = train.data)
predM2.final = predict(M2.final, newdata = test.data)
MSE.M2.final = mean((predM2.final - test.data$bodyMass)^2)
MSE.M2.final
[1] 188408.2

The actual performance of the recommended model has mean squared error (MSE) is shown above.

Remarks: There are different metrics to measure different types of performances. In this example, we are interested in predictive performance, the mean squared error was used. If we want to evaluate the global goodness-of-fit, we may use the following measures.

  • R-squared (\(R^2\)): This is the most common measure. It represents the proportion of the variance in the dependent variable that is predictable from the independent variables. An R² value closer to 1 indicates a better fit.

  • Adjusted R-squared: This adjusts the R² value based on the number of predictors in the model. It is useful when comparing models with a different number of predictors, as it penalizes for adding variables that do not improve the model significantly.

  • F-statistic: This tests the overall significance of the model. It compares the model with no predictors (intercept-only model) to the specified model. A higher F-statistic indicates that the model provides a better fit than a model with no predictors.

  • Akaike Information Criterion (AIC) and Bayesian Information Criterion (BIC): These criteria are used for model selection. They balance model fit and complexity, penalizing models with more parameters. Lower values of AIC and BIC indicate a better model.

4.2.2 Cross-validation in Logistic Regression

We still use the fraud data to build a logistic regression model using cross-validation and testing methods. We first randomly split the data into 70% and 30%. The 30% of the data will be used for testing purposes. The 70% will be used for training and validation. The performance metric to be used is the AUC. The average of the validated AUC will be compared with the AUC calculated from the testing data.

We use 10-fold cross-validation in this example. The summary of the validated AUCs is given below. Instead of calculating specificity and sensitivity from scratch, we extract these two metrics from roc() directly in this cross-validation.

fraud.data = read.csv("https://pengdsci.github.io/datasets/FraudIndex/fraudidx.csv")[,-1]
## encode status variable: bad = 1 and good = 0
good.id = which(fraud.data$status == " good") 
bad.id = which(fraud.data$status == "fraud")
##
fraud.data$fraud.status = 0
fraud.data$fraud.status[bad.id] = 1
nn = dim(fraud.data)[1]
train.id = sample(1:nn, round(nn*0.7), replace = FALSE) 
trainDat = fraud.data[train.id,]
testDat = fraud.data[-train.id,]
## each fold has 2326 observations
##
AUC.vec = rep(0,5)
color=c("#002642","#840032","#432534", "#c44900", "#0b7a75")
##
for(i in 1:5){
  ID = ((i-1)*4652+1):(i*4652)
  valid = as.data.frame(trainDat[ID,])
  train = as.data.frame(trainDat[-ID,])
  ##
  logit.model = glm(fraud.status ~ index, family = binomial(link = logit), data = train)
  newdata = data.frame(index= valid$index)
  pred.prob.train = predict.glm(logit.model, newdata, type = "response")
  ## we next use library {pROC}
  ## to calculate AUC
  prediction = pred.prob.train
  category = valid$fraud.status == 1
  ROCobj <- roc(category, prediction)
  AUC.vec[i] = round(auc(ROCobj),4)
  if(i==1){
    plot((1-ROCobj$specificities),ROCobj$sensitivities,
         type="l",
         main="5-fold Cross-validated ROC",
         col=color[i], 
         xlab="1-specificty",
         ylab="sensitivity",
         lwd=1, 
         lty=1)
        segments(0,0,1,1, lty=2, col="red")
    } else{
    lines((1-ROCobj$specificities),ROCobj$sensitivities, col=color[i], lwd=1)
    }
  }

summary(AUC.vec)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.9157  0.9300  0.9301  0.9281  0.9313  0.9333 

We next calculate the AUC based on the hold-out testing data. We will use

  ### Caution, the model used to calculate the AUC must be based on the entire
  ### training data
  logit.model = glm(fraud.status ~ index, family = binomial(link = logit), data = trainDat)
  ###
  newdata = data.frame(index= testDat$index)
  pred.prob.train = predict.glm(logit.model, newdata, type = "response")
  ## we next use library {pROC}
  ## to calculate AUC
  prediction = pred.prob.train
  category = testDat$fraud.status == 1
  ROCobj <- roc(category, prediction)
  testAUC= round(auc(ROCobj),4)
  c(testAUC = testAUC)
testAUC 
 0.9254 

4.3 Additional Comments of K-Fold CV

In K-Fold Cross-Validation, \(K\) represents the number of groups into which the data set is divided. This number determines how many rounds of testing the model undergoes, ensuring each segment is used as a testing set once. This is a hyper-parameter that can be tuned to produce the optimal performance. However, some heuristics can be used in practice to save some model training effort.

A smaller K (e.g., 3-5) might be faster but could yield less reliable estimates as each fold represents a larger portion of the dataset, potentially missing out on diverse data scenarios. A larger K (e.g., 10) provides a more detailed evaluation at the cost of increased computation. A common starting point is K=5 or K=10, which are often enough to get a reliable estimate without excessive computation.

  • \(K = 2\) or \(3\): These choices can be beneficial when computational resources are limited or when a quicker evaluation is needed. They reduce the number of training cycles, thus saving time and computational power while still providing a reasonable estimate of model performance.

  • \(K = 5\) or \(10\): Choosing \(K = 5\) or \(K = 10\) are popular choices because they provide a good balance between computational efficiency and model performance estimation.

  • \(K = 20\): Using a larger value of K can provide a more detailed performance evaluation. However, it increases the computational burden and might result in higher variance if the subsets are too small.

Importance of shuffling data in K-Fold Cross-Validation

Shuffling the data in K-Fold Cross-Validation is highly recommended to enhance the validity of model evaluation. Shuffling breaks any inherent order in the data set that could introduce bias during the validation process. This ensures that each fold is representative of the entire data set, which is crucial for assessing how well the model generalizes to new data. However, it’s important to avoid shuffling in cases where the sequence of data points is meaningful, such as with time-series data, to preserve the integrity of the learning process.

Ensuring reproducibility in K-Fold Cross-Validation

Ensuring that the results of K-Fold Cross-Validation are reproducible is crucial for verifying model stability and performance. This can be achieved by setting the random_state parameter, which ensures consistent shuffling of data across different runs, allowing for identical data splits and thus, reproducible results.

4.4 Other Cross-Validation Methods

In addition to the K-fold cross-validation, the table below provides a comprehensive comparison of the most popular various cross-validation methods, highlighting their unique features and best use cases to guide data science practitioners in selecting the most effective technique for specific modeling needs.

  • Stratified K-Fold Cross-Validation maintains the same proportion of class labels in each fold as the original data set. It is great for classification tasks with imbalanced classes to maintain group proportions.

  • Leave-One-Out Cross-Validation (LOOCV) is good for both regression and classification. Each data point is used once as a test set, with the rest as training. It is especially useful for small data sets to maximize training data, though computationally intensive.

  • Leave-P-Out Cross-Validation is good for both regression and classification. It is similar to LOOCV, but leaves out \(p\) data points for the test set. It is ideal for small data sets to test how changes in the data samples affect model stability.

  • Group K-Fold Cross-Validation is good for both regression and classification with groups. This method ensures no group is in both training and test sets, which is useful when data points are not independent. It is appropriate for data sets with logical groupings to test performance on independent groups.

  • Stratified Group K-Fold Cross-Validation is primarily for classification with grouped data. It combines stratification and group integrity, ensuring that groups are not split across folds. It is useful for grouped and imbalanced data sets to maintain both class and group integrity.

K-Fold Cross-Validation is a powerful tool for evaluating machine learning models. It’s better than the simple Train-Test Split because it tests the model on various parts of the data. It helps data practitioners trust that the selected final model will work well on unseen data too.

5 Optimal Cut-off Probability.

The optimal point of the ROC curve is a point on the ROC curve that produces the desired sensitivity (true positive rate - TPR) and specificity (true negative rate - TNR). It is dependent on specific applications. For example, in clinical studies, a false negative (opposite of true negative) costs a lot more than a false positive (opposite of true positive). In credit card fraud detection, a high false positive rate (FPR) could cost a lot more than a high false negative rate (FNR) due to financial regulations that require reviewing all reported suspicious fraudulent transactions, hence, causing high operational costs. No matter what optimal point is defined, there is a corresponding cut-off probability that will be used to predict the value of the response.

As an illustrative example (that may not be practically meaningful), we want the cut-off probability such that the specificity is closest to the sensitivity. We will use the same fraud data set in the following code. Note that the cut-off probability search must be based on the training data set.

fraud.data = read.csv("https://pengdsci.github.io/datasets/FraudIndex/fraudidx.csv")[,-1]
## encode status variable: bad = 1 and good = 0
good.id = which(fraud.data$status == " good") 
bad.id = which(fraud.data$status == "fraud")
##
fraud.data$fraud.status = 0
fraud.data$fraud.status[bad.id] = 1
nn = dim(fraud.data)[1]
train.id = sample(1:nn, round(nn*0.7), replace = FALSE) 
trainDat = fraud.data[train.id,]
testDat = fraud.data[-train.id,]
##
logit.model = glm(fraud.status ~ index, family = binomial(link = logit), data = trainDat)
###
newdata = data.frame(index= testDat$index)
pred.prob.train = predict.glm(logit.model, newdata, type = "response")
## we next use library {pROC}
## to calculate AUC
prediction = pred.prob.train
category = testDat$fraud.status == 1
ROCobj <- roc(category, prediction)
## 
sen = ROCobj$sensitivities
spe = ROCobj$specificities
SenMinusSpe = abs(sen-spe)
minID = which(SenMinusSpe == min(SenMinusSpe))
cut.off.prob = ROCobj$thresholds
###
plot((1-spe), sen, main = "Optimal ROC Point",
     type = "l",
     ylab = "sensitivity",
     xlab = "1 - specificity")
segments(0, 0, 1, 1, lty = 2, col = "red", lwd = 2)
segments((1-spe[minID]), 1-spe[minID], (1-spe[minID]), sen[minID], col="purple")
points((1-spe[minID]), sen[minID], pch=19, col = "darkred", cex = 1.7)
points((1-spe[minID]), sen[minID], pch=19, col = "gold", cex = 1.2)
text(0.4, sen[minID], paste("(",round(spe[minID],5), ", ", round(sen[minID],5), ")"), cex = 0.8, col = "blue")

The above figure shows the optimal point on the ROC curve that meets the requirements of sensitivity and specificity. For other optimal points such as the point that maximizes the accuracy of the prediction, we can use the same process to search the cut-off probability.

LS0tDQp0aXRsZTogJ0Nyb3NzIFZhbGlkYXRpb24gYW5kIENvbmNlcHRzIG9mIFJPQyBBbmFseXNpcycNCmF1dGhvcjogIiBDaGVuZyBQZW5nIg0KZGF0ZTogIiBTVEEgNTExIC0gRm91ZGF0aW9ucyBvZiBEYXRhIFNjaWVuY2UiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRvY19jb2xsYXBzZWQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHNtb290aF9zY3JvbGw6IHllcw0KICAgIHRoZW1lOiBsdW1lbg0KICB3b3JkX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICBrZWVwX21kOiB5ZXMNCiAgcGRmX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIGZpZ193aWR0aDogMw0KICAgIGZpZ19oZWlnaHQ6IDMNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQoNCmBgYHs9aHRtbH0NCg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KLyogQ2FzY2FkaW5nIFN0eWxlIFNoZWV0cyAoQ1NTKSBpcyBhIHN0eWxlc2hlZXQgbGFuZ3VhZ2UgdXNlZCB0byBkZXNjcmliZSB0aGUgcHJlc2VudGF0aW9uIG9mIGEgZG9jdW1lbnQgd3JpdHRlbiBpbiBIVE1MIG9yIFhNTC4gaXQgaXMgYSBzaW1wbGUgbWVjaGFuaXNtIGZvciBhZGRpbmcgc3R5bGUgKGUuZy4sIGZvbnRzLCBjb2xvcnMsIHNwYWNpbmcpIHRvIFdlYiBkb2N1bWVudHMuICovDQoNCmgxLnRpdGxlIHsgIC8qIFRpdGxlIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiB0aGUgcmVwb3J0IHRpdGxlICovDQogIGZvbnQtc2l6ZTogMjRweDsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGNvbG9yOiBEYXJrUmVkOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCn0NCmg0LmF1dGhvciB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgYXV0aG9ycyAgKi8NCiAgZm9udC1zaXplOiAyMHB4Ow0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCmg0LmRhdGUgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIHRoZSBkYXRlICAqLw0KICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgxIHsgLyogSGVhZGVyIDEgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAxIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAyMnB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQpoMiB7IC8qIEhlYWRlciAyIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMiBzZWN0aW9uIHRpdGxlICovDQogICAgZm9udC1zaXplOiAyMHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KDQpoMyB7IC8qIEhlYWRlciAzIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCAzIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoNCB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCA0IHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmJvZHkgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCi5oaWdobGlnaHRtZSB7IGJhY2tncm91bmQtY29sb3I6eWVsbG93OyB9DQoNCnAgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCjwvc3R5bGU+DQpgYGANCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQojIGNvZGUgY2h1bmsgc3BlY2lmaWVzIHdoZXRoZXIgdGhlIFIgY29kZSwgd2FybmluZ3MsIGFuZCBvdXRwdXQgDQojIHdpbGwgYmUgaW5jbHVkZWQgaW4gdGhlIG91dHB1dCBmaWxlcy4NCmlmICghcmVxdWlyZSgia25pdHIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKQ0KICAgbGlicmFyeShrbml0cikNCn0NCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCn0NCmlmICghcmVxdWlyZSgiR0dhbGx5IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIkdHYWxseSIpDQpsaWJyYXJ5KEdHYWxseSkNCn0NCmlmICghcmVxdWlyZSgicFJPQyIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJwUk9DIikNCmxpYnJhcnkocFJPQykNCn0NCg0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAgICAgICMgaW5jbHVkZSBjb2RlIGNodW5rIGluIHRoZSBvdXRwdXQgZmlsZQ0KICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSwgICMgc29tZXRpbWVzLCB5b3UgY29kZSBtYXkgcHJvZHVjZSB3YXJuaW5nIG1lc3NhZ2VzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgeW91IGNhbiBjaG9vc2UgdG8gaW5jbHVkZSB0aGUgd2FybmluZyBtZXNzYWdlcyBpbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgdGhlIG91dHB1dCBmaWxlLiANCiAgICAgICAgICAgICAgICAgICAgICByZXN1bHRzID0gVFJVRSwgICAjIHlvdSBjYW4gYWxzbyBkZWNpZGUgd2hldGhlciB0byBpbmNsdWRlIHRoZSBvdXRwdXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGluIHRoZSBvdXRwdXQgZmlsZS4NCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BDQogICAgICAgICAgICAgICAgICAgICAgKSAgDQpgYGANCg0KDQpcDQoNCiMgSW50cm9kdWN0aW9uDQoNCkluIHRoZSBwcmV2aW91cyBtb2R1bGUsIHdlIGludHJvZHVjZWQgdHdvIHR5cGVzIG9mIHJlZ3Jlc3Npb24gbW9kZWxzIGluIHN0YXRpc3RpY3MgdGhhdCBhcmUgZm9ybXVsYXRlZCBiYXNlZCBvbiBzZXZlcmFsIGFzc3VtcHRpb25zIHN1Y2ggYXMgPGZvbnQgY29sb3IgPSAicmVkIj4qKlxjb2xvcntyZWR9aWlkKio8L2ZvbnQ+ICg8Zm9udCBjb2xvciA9ICJyZWQiPioqXGNvbG9ye3JlZH1pKio8L2ZvbnQ+ZGVudGljYWxseSA8Zm9udCBjb2xvciA9ICJyZWQiPioqXGNvbG9ye3JlZH1pKio8L2ZvbnQ+bmRlcGVuZGVudCA8Zm9udCBjb2xvciA9ICJyZWQiPioqXGNvbG9ye3JlZH1kKio8L2ZvbnQ+aXN0cmlidXRpb24pLCBub3JtYWwgZGlzdHJpYnV0aW9uIG9yIGJpbm9taWFsIGRpc3RyaWJ1dGlvbiBvZiB0aGUgcmVzcG9uc2UgdmFyaWFibGUsIHRoZSBsYXJnZSBzYW1wbGUgc2l6ZSBmb3IgdXNpbmcgdGhlIGNlbnRyYWwgbGltaXQgdGhlb3JlbSB0byBkZWZpbmUgZGlmZmVyZW50IHBlcmZvcm1hbmNlIG1ldHJpY3MsIGV0Yy4gV2l0aCB0aGVzZSBhc3N1bXB0aW9ucywgd2UgY2FuIGRlZmluZSBkaWZmZXJlbnQgbWV0aG9kcyB0byBhc3Nlc3MgdGhlIHBlcmZvcm1hbmNlIG9mIG1vZGVscyBhbmQgaWRlbnRpZnkgdGhlIGJlc3QgbW9kZWxzIGZvciBpbXBsZW1lbnRhdGlvbi4NCg0KSW4gZGF0YSBzY2llbmNlIHByb2plY3RzLCBpdCBpcyBxdWl0ZSBvZnRlbiB0aGF0IGRhdGEgZ2VuZXJhdGlvbiBwcm9jZXNzZXMgZG8gbm90IG1lZXQgdGhlIHJlcXVpcmVtZW50cyBmb3IgbWFueSBjbGFzc2ljYWwgbW9kZWxzIGFuZCBwcm9jZWR1cmVzLiBXZSBuZWVkIHRvIHVzZSAqKmFwcHJvcHJpYXRlIHBlcmZvcm1hbmNlIG1ldHJpY3MqKiB0byBhc3Nlc3MgdGhlIGdvb2RuZXNzIG9mIHRoZSBtb2RlbC9hbGdvcml0aG0gd2l0aG91dCBoYXZpbmcgc3RyaWN0IGFzc3VtcHRpb25zIGFuZCBtYWtlIHN1cmUgdGhlIG1vZGVsIHdpbGwgYmUgY29uc2lzdGVudGx5IHJlbGlhYmxlIGFjcm9zcyBuZXcsIHVuc2VlbiBkYXRhIHNldHMuDQoNClRoZSBtYWpvcml0eSBvZiBwZXJmb3JtYW5jZSBtZXRyaWNzIGluIGRhdGEgc2NpZW5jZSBhcmUgZGVmaW5lZCBkaXJlY3RseSBiYXNlZCBvbiBlaXRoZXIgdGhlIGVzdGltYXRlZCBlcnJvciBvZiB0aGUgcmVzcG9uc2Ugb3Igc29tZSB0eXBlIG9mICdkaXN0YW5jZScuIEluIHByZWRpY3RpdmUgbW9kZWxpbmcsIG1vc3Qgb2YgdGhlIHBlcmZvcm1hbmNlIG1lYXN1cmVzIGFyZSBkZWZpbmVkIHdpdGggcHJlZGljdGl2ZSBlcnJvciBwcm9iYWJpbGl0aWVzLiBXZSB3aWxsIGludHJvZHVjZSBvbmUgb2YgdGhlIG1vc3QgY29tbW9ubHkgdXNlZCBnbG9iYWwgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBtZWFzdXJlcyAtIHRoZSBST0MgKHJlY2VpdmVyIG9wZXJhdGluZyBjaGFyYWN0ZXJpc3RpY3MpIGN1cnZlIGFuZCBpdHMgY29tcG9uZW50cy4NCg0KSW4gdGhpcyBtb2R1bGUsIHdlIHdpbGwgZGlzY3VzcyB0aGUgYmFzaWNzIG9mICoqSy1Gb2xkIENyb3NzLVZhbGlkYXRpb24qKiBhbmQgY29tcGFyZSBpdCB0byBzb21lIHNpbXBsZXIgbWV0aG9kcy4gU29tZSBjb21tb25seSB1c2VkIHBlcmZvcm1hbmNlIG1lYXN1cmVzIGZvciBwcmVkaWN0aXZlIG1vZGVsaW5nIHN1Y2ggYXMgc2Vuc2l0aXZpdHksIHNwZWNpZmljaXR5LCBmYWxzZSBwb3NpdGl2ZSByYXRlLCBmYWxzZSBuZWdhdGl2ZSByYXRlLCBST0MgY3VydmUsIGFuZCB0aGUgYXJlYSBvZiB0aGUgUk9DIGN1cnZlIChBVUMpIHdpbGwgYWxzbyBiZSBpbnRyb2R1Y2VkIGFuZCBpbGx1c3RyYXRlZCB3aXRoIHNvbWUgc2ltcGxlIGV4YW1wbGVzLg0KDQoNCiMgUmV2aWV3IG9mIExpbmVhciBhbmQgTG9naXN0aWMgTW9kZWxzDQoNCkluIGNsYXNzaWNhbCBzdGF0aXN0aWNhbCBtb2RlbGluZywgd2Ugc2VsZWN0IGNhbmRpZGF0ZSBtb2RlbHMgYmFzZWQgb24gZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyBhbmQgdmlzdWFsIGFuYWx5c2lzLiBUaGUgY2FuZGlkYXRlIG1vZGVscyBhcmUgdGhlbiBmaXQgdG8gdGhlIGFuYWx5dGljIGRhdGEgc2V0LiBTaW5jZSBhbGwgbW9kZWxzIGhhdmUgc29tZSBzb3J0IG9mIGFzc3VtcHRpb25zICh0aGluayBhYm91dCBsaW5lYXIgcmVncmVzc2lvbiBhbmQgbG9naXN0aWMgYW5kIFBvaXNzb24gcmVncmVzc2lvbiBtb2RlbHMpLCAgYW5kIHRoZW4gY2Fycnkgb3V0IG1vZGVsIGRpYWdub3N0aWMgYW5hbHlzZXMgdG8gaWRlbnRpZnkgcG90ZW50aWFsIHZpb2xhdGlvbnMgb2YgdGhlIG1vZGVsIGFzc3VtcHRpb24uIE9mIGNvdXJzZSwgd2UgYXNzdW1lIHRoYXQgdGhlIGRhdGEgcmVwcmVzZW50IHRoZSBwb3B1bGF0aW9uLiBUaGlzIG1vZGVsaW5nIHByb2Nlc3MgaXMgZGVwaWN0ZWQgaW4gdGhlIGZvbGxvd2luZyBkaWFncmFtLg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iNzAlIiwgZmlnLmNhcD0iQ2xhc3NpY2FsIHN0YXRpc3RpY2FsIG1vZGVsaW5nIHByb2Nlc3MuIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy93MDctU3RhdE1vZGVsUHJvY2Vzcy5qcGciKQ0KYGBgDQoNClJlY2FsbCB0aGUgcHJvY2VzcyBvZiB0aGUgZm9sbG93aW5nIHR3byByZXByZXNlbnRhdGl2ZSBzdGF0aXN0aWNhbCBtb2RlbHMuDQoNCiMjIExpbmVhciBSZWdyZXNzaW9uIE1vZGVsDQoNCkZpcnN0LCB3ZSBsb29rIGF0IHRoZSBwcm9jZXNzIG9mIGhvdyB0byBidWlsZCBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIGZvciBhIGRhdGEgc2V0Lg0KDQoqICoqRXhwbGljaXQgYW5kIEltcGxpY2l0IEFzc3VtcHRpb25zKiouIFRoZSBmb2xsb3dpbmcgYXJlIHNvbWUgb2YgdGhlIGFzc3VtcHRpb25zIGFib3V0IGEgbm9ybWFsLWJhc2VkIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsOg0KDQogICAtIFByZWRpY3RvciAoZmVhdHVyZSkgdmFyaWFibGVzIGFyZSAobGluZWFybHkgb3IgY3VydmUtbGluZWFybHkpIGNvcnJlbGF0ZWQgdG8gdGhlIHJlc3BvbnNlIHZhcmlhYmxlIChhbHNvIGNhbGxlZCBsYWJlbCBpbiBtYWNoaW5lIGxlYXJuaW5nIHRlcm1zKTsNCiAgIC0gUHJlZGljdG9yIHZhcmlhYmxlcyB0aGVtc2VsdmVzIGFyZSBOT1QgaGlnaGx5IGxpbmVhcmx5IGNvcnJlbGF0ZWQgKGkuZS4sIG5vIHNlcmlvdXMgY29sbGluZWFyaXR5KTsNCiAgIC0gVGhlIHJlc3BvbnNlIHZhcmlhYmxlIGlzIG5vcm1hbGx5IGRpc3RyaWJ1dGVkOw0KICAgLSBUaGUgdmFyaWFuY2Ugb2YgdGhlIHJlc3BvbnNlIHZhcmlhYmxlIGlzIGNvbnN0YW50Ow0KICAgLSBUaGVyZSBhcmUgbm8gb3V0bGllciAoaW5mbHVlbnRpYWwpIG9ic2VydmF0aW9uczsNCiAgIC0gaW5mb3JtYXRpb24gb24gYWxsIHJlbGV2YW50IHByZWRpY3RvciB2YXJpYWJsZXMgaXMgYXZhaWxhYmxlIGluIHRoZSBkYXRhIChpLmUuLCBubyBpbXBvcnRhbnQgcHJlZGljdG9yIGlzIG1pc3NpbmcpOw0KDQoNCiogKipNb2RlbCBGaXR0aW5nIChQYXJhbWV0ZXIgRXN0aW1hdGluZykqKi4gVGhlIGxlYXN0LXNxdWFyZSBlc3RpbWF0b3IgKExTRSksIHdoaWNoIGlzIGVxdWl2YWxlbnQgdG8gdGhlIG1heGltdW0gbGlrZWxpaG9vZCBlc3RpbWF0b3IgKE1MRSkgd2hlbiB0aGUgcmVzcG9uc2UgdmFyaWFibGUgaXMgYXNzdW1lZCB0byBiZSBhIG5vcm1hbCBkaXN0cmlidXRpb24sIGNhbiBiZSB1c2VkIHRvIGVzdGltYXRlIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cy4gIA0KDQoqICoqTW9kZWwgU2VsZWN0aW9uIGFuZCBEaWFnbm9zdGljcyoqLiBTaW5jZSBzZXZlcmFsIGltcGxpY2l0IGFuZCBleHBsaWNpdCBhc3N1bXB0aW9ucyBoYXZlIGJlZW4gYXNzdW1lZCB1bmRlcmx5aW5nIHRoZSBsaW5lYXIgcmVncmVzc2lvbiwgZGlmZmVyZW50IHNldHMgb2YgZGlhZ25vc3RpYyBtZWFzdXJlcyB3ZXJlIGRldmVsb3BlZCB0byBkZXRlY3QgZGlmZmVyZW50IHBvdGVudGlhbCB2aW9sYXRpb25zIG9mIHRoZSBtb2RlbC4NCg0KICAtIDxiPipnbG9iYWwgZ29vZG5lc3Mtb2YtZml0KjwvYj46IFI8c3VwPjI8L3N1cD4sIEFJQyBhbmQgU0JDIChyZXF1aXJlcyBub3JtYWxpdHkgYXNzdW1wdGlvbiBvZiB0aGUgcmVzcG9uc2UgdmFyaWFibGUpLCBNU0UsIGV0Yy4NCiAgLSA8Yj4qbG9jYWwgZ29vZG5lc3Mtb2YtZml0L21vZGVsIHNlbGVjdGlvbio8L2I+OiBSLCBGLXRlc3QgKG5lZWQgbm9ybWFsaXR5IGFuZCBlcXVhbCB2YXJpYW5jZSBvZiB0aGUgcmVzcG9uc2UgdmFyaWFibGUpLCB0LXRlc3QsIGxpa2VsaWhvb2QgcmF0aW8gdGVzdCwgTWFsbG93J3MgQzxzdWI+cDwvc3ViPiwgZXRjLg0KICAtIDxiPipub3JtYWxpdHkqPC9iPjogUVEtcGxvdCBhbmQgcHJvYmFiaWxpdHkgcGxvdCBmb3IgYSB2aXN1YWwgY2hlY2ssIGdvb2RuZXNzLW9mLWZpdCB0ZXN0cyBzdWNoIGFzIEFuZGVyc29uLURhcmxpbmcsIENyYW1lci12b24gTWlzcywgS29sbW9nb3Jvdi1TbWlybm92LCBTaGFwaXJvLVdpbGtzLCBhbmQgUGVhcnNvbidzIGNoaS1zcXVhcmUgdGVzdHMsIGV0Yy4gDQogIC0gPGI+KmRldGVjdGluZyBvdXRsaWVycy9pbmZsdWVudGlhbCBvYnNlcnZhdGlvbnMqPC9iPjogbGV2ZXJhZ2UgcG9pbnQtaGF0IG1hdHJpeCwgREZJVFQgLSBkZWZpbmVkIGJhc2VkIG9uIGxlYXZlLW9uZS1vdXQgcmVzYW1wbGluZyBtZXRob2QsIGNvb2sncyBkaXN0YW5jZSwgKHNjYWxlZCkgcmVzaWR1YWwgcGxvdHMsIGV0Yy4NCiAgLSA8Yj4qdmVyaWZ5aW5nIGNvbnN0YW50IHZhcmlhbmNlKjwvYj46IEYtdGVzdCAocmVxdWlyZXMgbm9ybWFsaXR5KSwgQnJvd24tRm9yc3l0aGUgdGVzdCAobm9ucGFyYW1ldHJpYyksIEJyZXVzY2gtUGFnYW4gVGVzdCAoYWxzbyBub25wYXJhbWV0cmljKSwgQmFydGxldHQncyBUZXN0IChyZXF1aXJlcyBub3JtYWxpdHkpLCBldGMuDQogIC0gPGI+KmRldGVjdGluZyBjb2xsaW5lYXJpdHkqPC9iPjogVmFyaWFuY2UgaW5mbGF0aW9uIGZhY3RvciAoVklGKSBmb3IgZGF0YS1iYXNlZCBhbmQgc3RydWN0dXJhbCBjb2xsaW5lYXJpdHkuDQogIC0gPGI+KmRldGVjdGluZyBtaXNzaW9uIG9mIGRldGVybWluYW50IHZhcmlhYmxlKjwvYj46IA0KDQojIyBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsDQoNCkZvciB0aGUgYmluYXJ5IGxvZ2lzdGljIG1vZGVsLCB3ZSBhbHNvIGZvbGxvdyB0aGUgc2FtZSBzdGVwcyB0byBpZGVudGlmeSB0aGUgZmluYWwgbW9kZWwuIEZvciBleGFtcGxlLCB0aGUgd2VsbC1rbm93biBiaW5hcnkgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbGluZyBmb2xsb3dzIHNpbWlsYXIgc3RlcHM6DQoNCiogKipBc3N1bXB0aW9ucyoqOiBVbmxpa2UgdGhlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHdoaWNoIGhhcyBhIHN0cm9uZyBhc3N1bXB0aW9uIG9mIG5vcm1hbGl0eSwgdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgYXNzdW1lcyB0aGUgZm9sbG93aW5nDQoNCiAgLSA8Yj4qYmlub21pYWwgZGlzdHJpYnV0aW9uKjwvYj46IFRoZSBkZXBlbmRlbnQgdmFyaWFibGUgaXMgYmluYXJ5Lg0KICAtIDxiPippbmRlcGVuZGVuY2UqPC9iPjogVGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gcmVxdWlyZXMgdGhlIG9ic2VydmF0aW9ucyB0byBiZSBpbmRlcGVuZGVudCBvZiBlYWNoIG90aGVyLg0KICAtIDxiPipjb2xsaW5lYXJpdHkqPC9iPjogVGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gcmVxdWlyZXMgdGhlcmUgdG8gYmUgbGl0dGxlIG9yIG5vIG11bHRpY29sbGluZWFyaXR5IGFtb25nIHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMuDQogIC0gPGI+KmxpbmVhcml0eSo8L2I+OiBUaGUgbG9naXN0aWMgcmVncmVzc2lvbiBhc3N1bWVzIGxpbmVhcml0eSBvZiBpbmRlcGVuZGVudCB2YXJpYWJsZXMgYW5kIHRoZSBsb2cgb2RkcyBvZiB0aGUgZXZlbnQgb2YgaW50ZXJlc3QuDQogIC0gPGI+KmxhcmdlIHNhbXBsZSBzaXplKjwvYj46IFRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIHR5cGljYWxseSByZXF1aXJlcyBhIGxhcmdlIHNhbXBsZSBzaXplLiBBIGdlbmVyYWwgZ3VpZGVsaW5lIGlzIHRoYXQgeW91IG5lZWQgYSBtaW5pbXVtIG9mIDEwIG9ic2VydmF0aW9ucyB3aXRoIHRoZSBsZWFzdCBmcmVxdWVudCBvdXRjb21lIGZvciBlYWNoIGluZGVwZW5kZW50IHZhcmlhYmxlIGluIHlvdXIgbW9kZWwuIA0KICAtIDxiPiptaXMtc3BlY2lmaWNhdGlvbio8L2I+OiBObyBpbXBvcnRhbnQgdmFyaWFibGVzIGFyZSBvbWl0dGVkLiBObyBleHRyYW5lb3VzIHZhcmlhYmxlcyBhcmUgaW5jbHVkZWQuDQogIC0gPGI+Km1lYXN1cmVtZW50IGVycm9yKjwvYj46IFRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMgYXJlIG1lYXN1cmVkIHdpdGhvdXQgZXJyb3IuDQoNCiogKipNb2RlbCBGaXR0aW5nKio6IFRoZSBjb2VmZmljaWVudHMgb2YgdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgYXJlIGVzdGltYXRlZCB1c2luZyBhIG1heGltdW0gbGlrZWxpaG9vZCBlc3RpbWF0b3IuIE5vdGUgTFNFIGNhbm5vdCBiZSBlc3RpbWF0ZWQgZm9yIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLg0KDQoqICoqTW9kZWwgU2VsZWN0aW9uIGFuZCBEaWFnbm9zdGljcyoqOiBVbmxpa2UgdGhlIG5vcm1hbCBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCwgdGhlcmUgYXJlIGEgZmV3IGRpYWdub3N0aWMgbWV0aG9kcyBvbmUgY2FuIHVzZSBpbiBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVscy4NCg0KICAtIDxiPiptaXNzcGVjaWZpY2F0aW9uKjwvYj46IGxpbmsgdGVzdCAobGFyZ2Ugc2FtcGxlIHRlc3QpOw0KICAtIDxiPipnb29kbmVzcy1vZi1maXQqPC9iPjogbG9nLWxpa2VsaWhvb2QgY2hpLXNxdWFyZSBhbmQgcHNldWRvLVItc3F1YXJlOyBIb3NtZXItTGVtZXNob3cncyBsYWNrIG9mIGZpdCB0ZXN0IEFJQyBhbmQgU0JDLg0KICAtIDxiPiptdWx0aS1jb2xsaW5lYXJpdHkqPC9iPjogVklGDQogIC0gPGI+KmluZmx1ZW50aWFsIHBvaW50cyo8L2I+OiBDb29rJ3MgZGlzdGFuY2UsIERCRVRBLCBkZXZpYW5jZSByZXNpZHVhbHMNCg0KXA0KDQojIyBQcmVkaWN0aXZlIFBlcmZvcm1hbmNlIE1lYXN1cmVzDQoNClJlZ3Jlc3Npb24gbW9kZWxzIGNhbiBiZSB1c2VkIGZvciBlaXRoZXIgcHJlZGljdGl2ZSBvciBhc3NvY2lhdGlvbiBhbmFseXNpcy4gRGlmZmVyZW50IHBlcmZvcm1hbmNlIG1ldHJpY3MgaGF2ZSBiZWVuIGRlZmluZWQgdG8gYXNzZXNzIHRoZSBwcmVkaWN0aXZlIHBvd2VyIG9mIHJlZ3Jlc3Npb24gbW9kZWxzLiBXZSBsaXN0IHNvbWUgb2YgdGhlIGVycm9yLWJhc2VkIG1lYXN1cmVzIGluIHRoZSBmb2xsb3dpbmcuDQoNCg0KIyMjIExpbmVhciBSZWdyZXNzaW9uDQoNCldoZW4gZXZhbHVhdGluZyB0aGUgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBvZiBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbHMsIHNldmVyYWwga2V5IG1ldHJpY3MgYmFzZWQgb24gcHJlZGljdGl2ZSBlcnJvciAoKipyZXNpZHVhbHMqKikgYXJlIGNvbW1vbmx5IHVzZWQuIExldCAkXHtcaGF0e3l9XzEsIFxoYXR7eX1fMiwgXGNkb3RzLCBcaGF0e3l9X2tcfSQgYmUgdGhlIGNvcnJlc3BvbmRpbmcgcHJlZGljdGVkIHZhbHVlcyBvZiAkXHt5XzEsIHlfMiwgXGNkb3RzLCB5X2sgXH0kLg0KDQoNCioqTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSoqIGlzIGRlZmluZWQgYnkNCg0KJCQNCk1BRSA9IFxmcmFje1xzdW1fe2k9MX1eayB8XGhhdHt5fV9pLXlfaXx9e2t9DQokJA0KDQoNClRoaXMgbWVhc3VyZXMgdGhlIGF2ZXJhZ2UgbWFnbml0dWRlIG9mIHRoZSBlcnJvcnMgaW4gcHJlZGljdGlvbnMsIHdpdGhvdXQgY29uc2lkZXJpbmcgdGhlaXIgZGlyZWN0aW9uLiBJdCBpcyB0aGUgYXZlcmFnZSBvZiB0aGUgYWJzb2x1dGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBwcmVkaWN0ZWQgYW5kIGFjdHVhbCB2YWx1ZXMuIE1BRSBpcyBsZXNzIHNlbnNpdGl2ZSB0byBvdXRsaWVycyBjb21wYXJlZCB0byBvdGhlciBtZXRyaWNzLg0KDQoNCioqTWVhbiBTcXVhcmVkIEVycm9yIChNU0UpKiogaXMgZGVmaW5lZCBieQ0KDQokJA0KTVNFID0gXGZyYWN7XHN1bV97aT0xfV5rIChcaGF0e3l9X2kteV9pKV4yfXtrfQ0KJCQNCg0KVGhpcyBtZWFzdXJlcyB0aGUgYXZlcmFnZSBvZiB0aGUgc3F1YXJlcyBvZiB0aGUgZXJyb3JzLiBJdCBnaXZlcyBhIGhpZ2hlciB3ZWlnaHQgdG8gbGFyZ2VyIGVycm9ycywgbWFraW5nIGl0IHVzZWZ1bCBmb3IgaWRlbnRpZnlpbmcgbW9kZWxzIHRoYXQgaGF2ZSBsYXJnZSBwcmVkaWN0aW9uIGVycm9ycy4NCg0KDQoqKlJvb3QgTWVhbiBTcXVhcmVkIEVycm9yIChSTVNFKSoqIGlzIGRlZmluZWQgYnkNCg0KJCQNClJNU0UgPSBcc3FydHtcZnJhY3tcc3VtX3tpPTF9XmsgKFxoYXR7eX1faS15X2kpXjJ9e2t9fQ0KJCQNCg0KVGhpcyBpcyB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIE1TRS4gSXQgcHJvdmlkZXMgYW4gZXJyb3IgdmFsdWUgaW4gdGhlIHNhbWUgdW5pdHMgYXMgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSwgbWFraW5nIGl0IGVhc2llciB0byBpbnRlcnByZXQuDQoNClwNCg0KIyMjIExvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KUmVjYWxsIHRoYXQgdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwuDQoNCiQkDQpQKFkgPSBcdGV4dHtzdWNjZXNzfSkgPSBcZnJhY3tcZXhwKFxiZXRhXzAgKyBcYmV0YV8xIHhfMSArIFxjZG90cyArIFxiZXRhX2sgeF9rKX17MStcZXhwKFxiZXRhXzAgKyBcYmV0YV8xIHhfMSArIFxjZG90cyArIFxiZXRhX2sgeF9rKX0uDQokJA0KDQpUaGUgbW9kZWwgcHJlZGljdHMgdGhlICoqcHJvYmFiaWxpdHkgb2Ygc3VjY2VzcyoqIGJ1dCBub3QgdGhlIHZhbHVlIG9mICRZJCAoInN1Y2Nlc3MiIG9yICJmYWlsdXJlIikuIEluIG9yZGVyIHRvIHByZWRpY3QgInN1Y2Nlc3MiIG9yICJmYWlsdXJlIiwgd2UgbmVlZCB0byBoYXZlIGEgKipjdXQtb2ZmIHByb2JhYmlsaXR5KiouIFRoZSBrZXkgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBtZXRyaWNzIGFyZSBkZWZpbmVkIGJhc2VkIG9uIHRoZSAqKnByZWRpY3RlZCB2YWx1ZXMqKiBvZiAkWSQgKCJzdWNjZXNzIiBvciAiZmFpbHVyZSIpIGFuZCB0aGUgKip0cnVlIHZhbHVlcyoqIG9mICRZJCB3aGljaCBpcyB0YWJ1bGF0ZWQgaW4gdGhlIGZvbGxvd2luZyAqKmNvbmZ1c2lvbiBtYXRyaXgqKg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iNTAlIiwgZmlnLmNhcD0iQ29uZnVzaW9uIG1hdHJpeCBvZiBiaW5hcnkgbG9naXN0aWMgcHJlZGljdGl2ZSBtb2RlbHMifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL0NvbmZ1c2lvbk1hdHJpeC5wbmciKQ0KYGBgDQoNCg0KU2V2ZXJhbCBrZXkgbWV0cmljcyB0aGF0IGFyZSBjb21tb25seSB1c2VkIGFyZSBiYXNlZCBvbiB0aGUgYWJvdmUgY29uZnVzaW9uIG1hdHJpeC4NCg0KKipUcnVlIFBvc2l0aXZlIFJhdGUgKFRQUikqKjogVGhlIHByb3BvcnRpb24gb2YgKipjb3JyZWN0bHkqKiBwcmVkaWN0ZWQgcG9zaXRpdmUgYW1vbmcgKiphbGwgdHJ1ZSBQT1NJVElWRVMqKi4gKipUcnVlIFBvc2l0aXZlIFJhdGUqKiBpcyBhbHNvIGNhbGxlZCB0aGUgc2Vuc2l0aXZpdHkgb2YgdGhlIG1vZGVsIHdoaWNoIGlzIGEgY29uZGl0aW9uYWwgcHJvYmFiaWxpdHkgJFAoXHRleHR7cHJlZGljdGVkIHRydWV9fCBcdGV4dHthY3R1YWwgdHJ1ZX0pJC4gVGhlIHNhbXBsZSBUUFIgaXMgZ2l2ZW4gYnkNCg0KJCQNClx0ZXh0e1RydWUgUG9zaXRpdmUgUmF0ZX0gPSBcZnJhY3tUUH17VFAgKyBGTn0NCiQkDQoNCioqRmFsc2UgUG9zaXRpdmUgUmF0ZSAoRlBSKSoqOiBUaGUgcHJvcG9ydGlvbiBvZiAqKmluY29ycmVjdGx5KiogcHJlZGljdGVkIHBvc2l0aXZlIGFtb25nICoqYWxsIHRydWUgTkVHQVRJVkVTKiouIGlzIGEgY29uZGl0aW9uYWwgcHJvYmFiaWxpdHkgJFAoXHRleHR7cHJlZGljdGVkIHRydWV9fCBcdGV4dHthY3R1YWwgbmVnYXRpdmV9KSQuIFRoZSBzYW1wbGUgVFBSIGlzIGdpdmVuIGJ5DQoNCiQkDQpcdGV4dHtGYWxzZSBQb3NpdGl2ZSBSYXRlfSA9IFxmcmFje0ZQfXtGUCArIFROfQ0KJCQNCg0KKipUcnVlIE5lZ2F0aXZlIFJhdGUgKGFsc28gY2FsbGVkIFNwZWNpZmljaXR5KSoqIGlzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGFuIGFjdHVhbCBuZWdhdGl2ZSB3aWxsIHRlc3QgbmVnYXRpdmUuIFRoYXQgaXMsICRQKFx0ZXh0e1ByZWRpY3RlZCBOZWdhdGl2ZX18XHRleHR7QWN0dWFsIE5lZ2F0aXZlfSkkLiBUaGUgc2FtcGxlICoqdHJ1ZSBuZWdhdGl2ZSByYXRlIChzcGVjaWZpY2l0eSkqKiBpcyBnaXZlbiBieQ0KDQokJA0KXHRleHR7VHJ1ZSBOZ2F0aXZlIFJhdGV9ID0gXGZyYWN7VE59e1ROK0ZQfS4NCiQkDQoNCkFwcGFyZW50bHksICRcdGV4dHtmYWxzZSBwb3NpdG92ZSByYXRlfSA9IDEgLSBcdGV4dHtzcGVjaWZpY2l0eX0kLg0KDQoNCioqQWNjdXJhY3kqKjogVGhlIHByb3BvcnRpb24gb2YgY29ycmVjdGx5IHByZWRpY3RlZCBpbnN0YW5jZXMgb3V0IG9mIHRoZSB0b3RhbCBpbnN0YW5jZXMuDQoNCiQkDQpcdGV4dHthY2N1cmFjeX0gPSBcZnJhY3tUUCArIFROfXtUUCArIFROICsgRlAgKyBGTn0NCiQkDQoNCioqUHJlY2lzaW9uIGFuZCBSZWNhbGwqKjogUHJlY2lzaW9uIG1lYXN1cmVzIHRoZSBwcm9wb3J0aW9uIG9mIHRydWUgcG9zaXRpdmUgcHJlZGljdGlvbnMgYW1vbmcgYWxsIHBvc2l0aXZlIHByZWRpY3Rpb25zLCB3aGlsZSByZWNhbGwgKG9yIHNlbnNpdGl2aXR5KSBtZWFzdXJlcyB0aGUgcHJvcG9ydGlvbiBvZiB0cnVlIHBvc2l0aXZlcyBpZGVudGlmaWVkIG91dCBvZiBhbGwgYWN0dWFsIHBvc2l0aXZlcy4NCg0KJCQNClx0ZXh0e3ByZWNpc2lvbn0gPSBcZnJhY3tUUH17VFArIEZQfSAgXCBcIFx0ZXh0e2FuZH0gXCBcICBcdGV4dHtyZWNhbGx9ID0gXGZyYWN7VFAgKyBUTn17VFAgKyBGTn0NCiQkDQoNCg0KKipGMSBTY29yZSoqOiBUaGUgaGFybW9uaWMgbWVhbiBvZiBwcmVjaXNpb24gYW5kIHJlY2FsbCwgcHJvdmlkaW5nIGEgYmFsYW5jZSBiZXR3ZWVuIHRoZSB0d28uDQoNCiQkDQpcdGV4dHtGMSBTY29yZX0gPSAyXHRpbWVzIFxmcmFje1x0ZXh0e3ByZWNpc2lvbn0gXHRpbWVzIFx0ZXh0e3JlY2FsbH0gfXtcdGV4dHtwcmVjaXNpb259ICsgXHRleHR7cmVjYWxsfX0NCiQkDQoNCiMjIyBST0MgQ3VydmUNCg0KVGhlIGFib3ZlIHBlcmZvcm1hbmNlIG1ldHJpY3MgYXJlIGRlZmluZWQgYmFzZWQgb24gdGhlIGNvbmZ1c2lvbiBtYXRyaXguIEhvd2V2ZXIsIGEgY29uZnVzaW9uIG1hdHJpeCBpcyBkZWZpbmVkIGJhc2VkIG9uIHRoZSBjaG9pY2Ugb2YgdGhlIGN1dC1vZmYgcHJlZGljdGl2ZSBzY29yZXMuIEluIGxvZ2lzdGljIHByZWRpY3RpdmUgbW9kZWxpbmcsIHRoZSBwcmVkaWN0aXZlIHNjb3JlcyBhcmUgdGhlIHByb2JhYmlsaXRpZXMgb2Ygb2JzZXJ2aW5nIHRoZSB0YXJnZXQgY2F0ZWdvcnkgbGFiZWwgKGVpdGhlciAqKnN1Y2Nlc3MqKiBvciAqKmZhaWx1cmUqKiBpZiB0aGUgYmluYXJ5IHJlc3BvbnNlIHdpdGggdGhlc2UgdHdvIGNoYXJhY3RlciB2YWx1ZXMpIGluIHRoZSB0cmFpbmluZyBtb2RlbC4gIA0KDQpTaW5jZSB0aGUgKipzdWNjZXNzIHByb2JhYmlsaXR5KiogaXMgYmV0d2VlbiAwIGFuZCAxLCB3ZSBjYW4gdHJ5IGRpZmZlcmVudCBjdXQtb2ZmIHByb2JhYmlsaXRpZXMgdG8gY29uc3RydWN0IGRpZmZlcmVudCBjb25mdXNpb24gbWF0cmljZXMgYW5kIGNhbGN1bGF0ZSB0aGUgY29ycmVzcG9uZGluZyBzZW5zaXRpdml0eSBhbmQgc3BlY2lmaWNpdHkgbWVhc3VyZXMuIFdlIGNhbiB1c2UgdGhlc2UgcGFpcnMgb2Ygc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5IGF0IGRpZmZlcmVudCBjdXQtb2ZmIHByb2JhYmlsaXRpZXMuIFRoZSBwbG90IG9mIHNlbnNpdGl2aXR5IGFnYWluc3QgKDEtc3BlY2lmaWNpdHkpIGlzIGNhbGxlZCAqKlJlY2VpdmVyIE9wZXJhdGluZyBDaGFyYWN0ZXJpc3RpYyAoUk9DKSoqIGN1cnZlLg0KDQoNCkZvciBleGFtcGxlLCBpZiB3ZSBoYXZlIGEgcHJlZGljdGl2ZSBtb2RlbCBzdWNoIGFzIGxvZ2lzdGljIHJlZ3Jlc3Npb24gdG8gcHJlZGljdCB3aGV0aGVyIGFuIGFuaW1hbCBpcyBhIGRvZyBvciBjYXQgYmFzZWQgb24gYSBkYXRhIHNldCB3aXRoIHNvbWUgZ2l2ZW4gZmVhdHVyZXMgcmVsYXRlZCB0byBjYXRzIGFuZCBhbmltYWxzLiBUaGUgZm9sbG93aW5nIGFuaW1hdGVkIGdyYXBoIHNob3dzIHRoYXQgdGhlIGNvb3JkaW5hdGVzIG9mIGVhY2ggcG9pbnQgb24gdGhlIFJPQyBjdXJ2ZSBhcmUgZGV0ZXJtaW5lZCBieSBhIGNvbmZ1c2lvbiBtYXRyaXggKGZvcm1lZCBieSBhIGN1dC1vZmYgcHJvYmFiaWxpdHkpLiAgICANCg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ24gPSJjZW50ZXIiLCAgb3V0LndpZHRoID0gJzg1JScsIGZpZy5jYXA9IlJPQyBjdXJ2ZSBkZW1vbnN0cmF0aW9uIn0NCmlmIChrbml0cjo6OmlzX2xhdGV4X291dHB1dCgpKSB7DQogIGtuaXRyOjphc2lzX291dHB1dCgnXFx1cmx7aHR0cHM6Ly9wZW5nZHNjaS5naXRodWIuaW8vU1RBNTUxL3cwNy9pbWcvcm9jLmdpZn0nKQ0KfSBlbHNlIHsNCiAga25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltZy9yb2MuZ2lmIikNCn0NCmBgYA0KDQpUaGUgbmV4dCBhbmltYXRlZCBncmFwaCBzaG93cyB0aGUgcHJlZGljdGlvbiBlcnJvci1iYXNlZCBwZXJmb3JtYW5jZSBhdCBlYWNoIGdpdmVuIGN1dC1vZmYgcHJvYmFiaWxpdHkuDQoNCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI3MCUiLCBvdXQuaGVpZ2h0PSI3MCUiLCBmaWcuY2FwPSJBbmltYXRlZCBST0MgY3VydmUuIn0NCmlmIChrbml0cjo6OmlzX2xhdGV4X291dHB1dCgpKSB7DQogIGtuaXRyOjphc2lzX291dHB1dCgnXFx1cmx7aHR0cHM6Ly9naXRodWIuY29tL3Blbmdkc2NpL1NUQTU1MS9ibG9iL21haW4vdzA2L2ltZy93MDYtQW5pbWF0ZWQtUk9DLmdpZn0nKQ0KfSBlbHNlIHsNCiAga25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltZy93MDYtQW5pbWF0ZWQtUk9DLmdpZiIpDQp9DQpgYGANCg0KDQoNCldoZW4gY29tcGFyaW5nIHR3byBtb3JlIGNsYXNzaWZpY2F0aW9uIG1vZGVscywgUk9DIGN1cnZlcyBwcm92aWRlIGFuIGludHVpdGl2ZSB2aXN1YWwgY29tcGFyaXNvbiBvZiB0aGUgZ2xvYmFsIHBlcmZvcm1hbmNlIG9mIHRoZSB1bmRlcmx5aW5nIG1vZGVscy4gSG93ZXZlciwgaWYgdHdvIFJPQyBjdXJ2ZXMgb2YgdHdvIHVuZGVybHlpbmcgY2xhc3NpZmljYXRpb24gbW9kZWxzIGludGVyc2VjdCwgc29tZXRpbWVzIGl0IGlzIGRpZmZpY3VsdCB0byBjb21wYXJlIHRoZSBnbG9iYWwgcGVyZm9ybWFuY2Ugb2YgdGhlIHR3byB1bmRlcmx5aW5nIG1vZGVscyB0aHJvdWdoIHZpc3VhbCByZXByZXNlbnRhdGlvbnMuIFRoZSBmb2xsb3dpbmcgZmlndXJlIHNob3dzIHRoZSBST0MgY3VydmVzIG9mIHR3byBoeXBvdGhldGljYWwgY2xhc3NpZmljYXRpb24gbW9kZWxzLiBJdCBpcyBoYXJkIHRvIGNoZWNrIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgdHdvIG1vZGVscy4NCg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iNzAlIiwgZmlnLmNhcD0iVXNpbmcgUk9DIGZvciBtb2RlbCBzZWxlY3Rpb24uIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy93MDYtRXF1YWwtQVVDLVJPQy5qcGciKQ0KYGBgDQoNCkluIHRoZSBhYm92ZSBjYXNlLCB3ZSBjYW4gdXNlIHRoZSBhcmVhcyB1bmRlciB0aGUgUk9DIGN1cnZlcyB0byBjb21wYXJlIHRoZSBnbG9iYWwgcGVyZm9ybWFuY2Ugb2YgdGhlIHR3byB1bmRlcmx5aW5nIG1vZGVscy4gVGhlIEFyZWEgVW5kZXIgdGhlIEN1cnZlIChBVUMpIHByb3ZpZGVzIGEgbnVtZXJpY2FsIG1lYXN1cmUgb2YgdGhlIG92ZXJhbGwgcGVyZm9ybWFuY2Ugb2YgYSBjbGFzc2lmaWNhdGlvbiBtb2RlbC4gSW4gdGhlIG5leHQgZXhhbXBsZSwgd2Ugd2lsbCB1c2UgYW4gZXhhbXBsZSB0byBkZW1vbnN0cmF0ZSBob3cgdG8gY29uc3RydWN0IGFuIFJPQyBjdXJ2ZSBvZiBhIGxvZ2lzdGljIHByZWRpY3RpdmUgbW9kZWwgYW5kIGl0cyBBVUMgdXNpbmcgYW4gUiBmdW5jdGlvbi4NCg0KDQojIyBFeGFtcGxlIG9mIEZyYXVkIERldGVjdGlvbg0KDQpUaGlzIGRhdGEgc2V0IHdhcyBleHRyYWN0ZWQgZnJvbSBhIHJlYWwtd29ybGQgZGF0YSBzZXQuIFRoZSBkYXRhIHNldCBjb250YWlucyBhIGZyYXVkIGluZGV4IChjYWxjdWxhdGVkIGZyb20gcmVhbCBmcmF1ZCBkYXRhKSBhbmQgZnJhdWQgc3RhdHVzIChpbiB0aGUgY2hhcmFjdGVyIGZvcm1hdCkuIFdlIHdpbGwgdXNlIGEgZGF0YSBzZXQgdG8gYnVpbGQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIGFuZCBjb25zdHJ1Y3QgYW4gUk9DIGN1cnZlLg0KDQoNCmBgYHtyICBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTQsIGZpZy5jYXA9IlJPQyBDdXJ2ZSBvZiBsb2dpc3RpYyBmcmF1ZCBtb2RlbCJ9DQpmcmF1ZC5kYXRhID0gcmVhZC5jc3YoImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL2RhdGFzZXRzL0ZyYXVkSW5kZXgvZnJhdWRpZHguY3N2IilbLC0xXQ0KIyMgZW5jb2RlIHN0YXR1cyB2YXJpYWJsZTogYmFkID0gMSBhbmQgZ29vZCA9IDANCmdvb2QuaWQgPSB3aGljaChmcmF1ZC5kYXRhJHN0YXR1cyA9PSAiZ29vZCAiKSANCmJhZC5pZCA9IHdoaWNoKGZyYXVkLmRhdGEkc3RhdHVzID09ICJmcmF1ZCIpDQojIw0KZnJhdWQuZGF0YSRmcmF1ZC5zdGF0dXMgPSAwDQpmcmF1ZC5kYXRhJGZyYXVkLnN0YXR1c1tiYWQuaWRdID0gMQ0KY3V0Lm9mZi5zZXEgPSBzZXEoMCwgMSwgbGVuZ3RoID0gMTAwKQ0Kc2Vuc2l0aXZpdHkudmVjID0gTlVMTA0Kc3BlY2lmaWNpdHkudmVjID0gTlVMTA0KIyMjIA0KbG9naXQubW9kZWwgPSBnbG0oZnJhdWQuc3RhdHVzIH4gaW5kZXgsIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSBsb2dpdCksIGRhdGEgPSBmcmF1ZC5kYXRhKQ0KbmV3ZGF0YSA9IGRhdGEuZnJhbWUoaW5kZXg9IGZyYXVkLmRhdGEkaW5kZXgpDQpwcmVkLnByb2IudHJhaW4gPSBwcmVkaWN0LmdsbShsb2dpdC5tb2RlbCwgbmV3ZGF0YSwgdHlwZSA9ICJyZXNwb25zZSIpDQpmb3IgKGkgaW4gMToxMDApew0KICBmcmF1ZC5kYXRhJHRyYWluLnN0YXR1cyA9IGFzLm51bWVyaWMocHJlZC5wcm9iLnRyYWluID4gY3V0Lm9mZi5zZXFbaV0pDQojIyMgY29tcG9uZW50cyBmb3IgZGVmaW5pbmcgdmFyaW91cyBtZWFzdXJlcw0KVE4gPSBzdW0oZnJhdWQuZGF0YSR0cmFpbi5zdGF0dXMgPT0gMCAmIGZyYXVkLmRhdGEkZnJhdWQuc3RhdHVzID09IDApICMgdHJ1ZSBuZWdhdGl2ZQ0KRk4gPSBzdW0oZnJhdWQuZGF0YSR0cmFpbi5zdGF0dXMgPT0gMCAmIGZyYXVkLmRhdGEkZnJhdWQuc3RhdHVzID09IDEpICMgZmFsc2UgbmVnYXRpdmUNCkZQID0gc3VtKGZyYXVkLmRhdGEkdHJhaW4uc3RhdHVzID09IDEgJiBmcmF1ZC5kYXRhJGZyYXVkLnN0YXR1cyA9PSAwKSAjIGZhbHNlIHBvc2l0aXZlDQpUUCA9IHN1bShmcmF1ZC5kYXRhJHRyYWluLnN0YXR1cyA9PSAxICYgZnJhdWQuZGF0YSRmcmF1ZC5zdGF0dXMgPT0gMSkgIyB0cnVlIHBvc2l0aXZlDQojIyMNCnNlbnNpdGl2aXR5LnZlY1tpXSA9IFRQIC8gKFRQICsgRk4pDQpzcGVjaWZpY2l0eS52ZWNbaV0gPSBUTiAvIChUTiArIEZQKQ0KfQ0Kb25lLm1pbnVzLnNwZWMgPSAxIC0gc3BlY2lmaWNpdHkudmVjDQpzZW5zLnZlYyA9IHNlbnNpdGl2aXR5LnZlYw0KIyMgd2UgbmV4dCB1c2UgbGlicmFyeSB7cFJPQ30NCiMjIHRvIGNhbGN1bGF0ZSBBVUMNCiAgcHJlZGljdGlvbiA9IHByZWQucHJvYi50cmFpbg0KICBjYXRlZ29yeSA9IGZyYXVkLmRhdGEkZnJhdWQuc3RhdHVzID09IDENCiAgUk9Db2JqIDwtIHJvYyhjYXRlZ29yeSwgcHJlZGljdGlvbikNCiAgQVVDID0gcm91bmQoYXVjKFJPQ29iaiksNCkNCiMjIyMNCnBhcihwdHkgPSAicyIpICAgIyBtYWtlIGEgc3F1YXJlIGZpZ3VyZQ0KcGxvdChvbmUubWludXMuc3BlYywgc2Vucy52ZWMsIHR5cGUgPSAibCIsIHhsaW0gPSBjKDAsMSksIHlsaW0gPSBjKDAsMSksDQogICAgIHhsYWIgPSIxIC0gc3BlY2lmaWNpdHkiLA0KICAgICB5bGFiID0gInNlbnNpdGl2aXR5IiwNCiAgICAgbWFpbiA9ICJST0MgY3VydmUgb2YgTG9naXN0aWMgRnJhdWQgTW9kZWwiLA0KICAgICBsd2QgPSAyLA0KICAgICBjb2wgPSAiYmx1ZSIsICkNCnNlZ21lbnRzKDAsMCwxLDEsIGNvbCA9ICJyZWQiLCBsdHkgPSAyLCBsd2QgPSAyKQ0KI0FVQyA9IHJvdW5kKHN1bShzZW5zLnZlYyoob25lLm1pbnVzLnNwZWNbLTEwMV0tb25lLm1pbnVzLnNwZWNbLTFdKSksNCkNCnRleHQoMC44LCAwLjMsIHBhc3RlKCJBVUMgPSAiLCBBVUMpLCBjb2wgPSAiYmx1ZSIsIGNleCA9IDAuOCkNCmBgYA0KDQpST0MgY3VydmVzIGFuZCBBVUNzIGFyZSB1c2VkIHRvIGNvbXBhcmUgdGhlIHBlcmZvcm1hbmNlIG9mIHR3byBwcmVkaWN0aXZlIG1vZGVscy4gVGhlIGJpZ2dlciB0aGUgQVVDLCB0aGUgYmV0dGVyIHRoZSBwcmVkaWN0aXZlIG1vZGVsLiBJZiBhIHNpbmdsZSBwcmVkaWN0aXZlIG1vZGVsIGlzIHVzZWQsIHRoZSBBVUMgbWVhc3VyZXMgdGhlIHByZWRpY3RpdmUgcG93ZXIgb2YgdGhlIG1vZGVsLiBJbiBnZW5lcmFsLCBhbiBBVUMgb2YgMC41IHN1Z2dlc3RzIG5vIGRpc2NyaW1pbmF0aW9uIChpLmUuLCBhYmlsaXR5IHRvIGRpYWdub3NlIHBhdGllbnRzIHdpdGggYW5kIHdpdGhvdXQgdGhlIGRpc2Vhc2Ugb3IgY29uZGl0aW9uIGJhc2VkIG9uIHRoZSB0ZXN0KSwgMC43IHRvIDAuOCBpcyBjb25zaWRlcmVkIGFjY2VwdGFibGUsIDAuOCB0byAwLjkgaXMgY29uc2lkZXJlZCBleGNlbGxlbnQsIGFuZCBtb3JlIHRoYW4gMC45IGlzIGNvbnNpZGVyZWQgb3V0c3RhbmRpbmcuIA0KDQpUaGUgQVVDIG9mIHRoZSBhYm92ZSBsb2dpc3RpYyBwcmVkaWN0aXZlIG1vZGVsIGlzIGJpZ2dlciB0aGFuIDAuOSB3aGljaCBpbmRpY2F0ZXMgdGhlIG1vZGVsIGhhcyBhbiBvdXRzdGFuZGluZyBwcmVkaWN0aXZlIHBvd2VyLg0KDQoNClwNCg0KIyBEYXRhIFNwbGl0dGluZyBUeXBlcw0KDQpPbmUgb2YgdGhlIGZ1bmRhbWVudGFsIGRpZmZlcmVuY2VzIGJldHdlZW4gc3RhdGlzdGljYWwgYW5kIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxpbmcgaXMgdGhhdCBib3RoIGZpZWxkcyB1c2UgZGlmZmVyZW50IG1vZGVsLWJ1aWxkaW5nIHN0cmF0ZWdpZXMuIEluIGNsYXNzaWNhbCBzdGF0aXN0aWNhbCBtb2RlbGluZywgbW9kZWxpbmcgYnVpbGRpbmcgYW5kIGV2YWx1YXRpb24gYXJlIGJhc2VkIG9uIHRoZSBzYW1lIGRhdGEgc2V0LiBIb3dldmVyLCBpbiBkYXRhIHNjaWVuY2UsIHRoZSBkYXRhIHNldCBpcyBwYXJ0aXRpb25lZCBpbnRvIGRpc2pvaW50IHN1YnNldHMgZm9yIG1vZGVsaW5nIHRyYWluaW5nIChidWlsZGluZyksIHZhbGlkYXRpb24sIGFuZCB0ZXN0aW5nLCByZXNwZWN0aXZlbHkuIFdpdGggZGF0YSBzcGxpdHRpbmcsIHRoZSBtb2RlbC1idWlsZGluZyBwcm9jZXNzIHdpbGwgcmVsYXggdGhlIGFzc3VtcHRpb25zIHVzZWQgaW4gdGhlIGNsYXNzaWNhbCBzdGF0aXN0aWNhbCBhbmFseXNpcy4NCg0KVGhpcyBzZWN0aW9uIG91dGxpbmVzICoqZGF0YSBzcGxpdHRpbmcqKiB0eXBlcyB0aGF0IGFyZSBjb21tb25seSB1c2VkIGluIHByYWN0aWNlLg0KDQoNCiMjIFR3by13YXkgU3BsaXR0aW5nDQoNCioqVHJhaW4tVGVzdCBTcGxpdCoqOiBUaGUgZGF0YSBzZXQgaXMgZGl2aWRlZCBpbnRvIHR3byBzZXRzOiBhIHRyYWluaW5nIHNldCBhbmQgYSB0ZXN0aW5nIHNldC4gVHlwaWNhbGx5LCA3MC04MCUgb2YgdGhlIGRhdGEgaXMgdXNlZCBmb3IgdHJhaW5pbmcsIGFuZCAyMC0zMCUgaXMgdXNlZCBmb3IgdGVzdGluZy4NCg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iNjUlIiwgZmlnLmNhcD0iVHJhaW5pbmctdGVzdGluZyBzcGxpdDogdHdvLXNwbGl0In0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy90cmFpbi10ZXN0LXNwbGl0LnBuZyIpDQpgYGANCg0KXA0KDQoqKlRyYWluaW5nIERhdGEqKg0KDQpUaGUgdHJhaW5pbmcgZGF0YSBzZXQgaXMgdGhlIHNhbXBsZSBvZiBkYXRhIHVzZWQgdG8gZml0IHRoZSBtb2RlbC4gSW4gb3RoZXIgd29yZHMsIHRoZSB0cmFpbmluZyBkYXRhIHRlYWNoZXMgdGhlIG1vZGVsIGhvdyBpdOKAmXMgc3VwcG9zZWQgdG8gbGVhcm4gYW5kIHRoaW5rLiBUbyBkbyB0aGlzLCB0cmFpbmluZyBkYXRhIGlzIG9mdGVuIHByZXNlbnRlZCBpbiBwYWlyczogcHJlZGljdG9yIHZhcmlhYmxlcyAoZmVhdHVyZSB2YXJpYWJsZXMpIGFuZCBhIHJlc3BvbnNlIHZhcmlhYmxlIChhbHNvIGNhbGxlZCBhIGxhYmVsKS4NCg0KVHJhaW5pbmcgZGF0YSBpcyB0aGUgZmlyc3Qgc2V0IG9mIGRhdGEgdGhlIG1vZGVsL2FsZ29yaXRobSBpcyBleHBvc2VkIHRvLiBEdXJpbmcgZWFjaCBzdGFnZSBvZiB0cmFpbmluZywgdGhlIG1vZGVsIHdpbGwgYmUgZml0IHRvIHRoZSB0cmFpbmluZyBkYXRhIGFuZCBlc3RpbWF0ZSB0aGUgcGFyYW1ldGVycyAoYWxzbyBjYWxsZWQgd2VpZ2h0IGluIHNvbWUgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIHN1Y2ggYXMgbmV1cmFsIG5ldHdvcmtzKS4NCg0KQmVjYXVzZSB0aGUgdHJhaW5pbmcgZGF0YSBzZXQgaXMgdXNlZCB0byBlc3RpbWF0ZSB0aGUgcGFyYW1ldGVycyAoaS5lLiwgdGVhY2hpbmcgdGhlIGFsZ29yaXRobSksIGl0IHJlcXVpcmVzIGEgY2VydGFpbiBhbW91bnQgb2YgaW5mb3JtYXRpb24gdG8gbWFrZSB0aGUgYWxnb3JpdGhtcyBhbmQgbW9kZWxzIHJlbGlhYmxlLiBJdCBtYWtlcyB1cCBiZXR3ZWVuIDYwJSBhbmQgODAlIG9mIHRoZSB0b3RhbCBkYXRhLg0KDQpcDQoNCioqVGVzdCBEYXRhKioNCg0KVGhlIHRlc3QgZGF0YSBzZXQgaXMgYSBzYW1wbGUgb2YgZGF0YSB1c2VkIHRvIHByb3ZpZGUgYW4gdW5iaWFzZWQgZXZhbHVhdGlvbiBvZiBhIGZpbmFsIG1vZGVsIGZpdCBvbiB0aGUgdHJhaW5pbmcgZGF0YSBzZXQgb3IgdG8gdGVzdCB0aGUgbW9kZWwuIFB1dCBtb3JlIHNpbXBseSwgdGVzdCBkYXRhIGlzIGEgc2V0IG9mICoqdW5sYWJlbGVkIGlucHV0cyoqIChpLmUuLCB0aGUgcmVzcG9uc2UgdmFsdWUgaXMgcmVtb3ZlZCBmcm9tIHRoZSBkYXRhKSB0aGF0IHRlc3Qgd2hldGhlciB0aGUgbW9kZWwgaXMgcHJvZHVjaW5nIHRoZSBjb3JyZWN0IG91dHB1dHMgaW4gdGhlIHJlYWwgd29ybGQuDQoNClRoZSBrZXkgZGlmZmVyZW5jZSBiZXR3ZWVuIGEgdmFsaWRhdGlvbiBkYXRhIHNldCBhbmQgYSB0ZXN0IGRhdGEgc2V0IGlzIHRoYXQgdGhlIHZhbGlkYXRpb24gZGF0YSBzZXQgaXMgdXNlZCBkdXJpbmcgbW9kZWwgY29uZmlndXJhdGlvbiwgd2hpbGUgdGhlIHRlc3QgZGF0YSBzZXQgaXMgcmVzZXJ2ZWQgdG8gZXZhbHVhdGUgdGhlIGZpbmFsIG1vZGVsLg0KDQpUZXN0IGRhdGEgaXMgYWJvdXQgMjAlIG9mIHRoZSB0b3RhbCBkYXRhIGFuZCBzaG91bGQgYmUgY29tcGxldGVseSBzZXBhcmF0ZSBmcm9tIHRoZSB0cmFpbmluZyBkYXRhIOKAlCB3aGljaCBvdXIgbW9kZWwgc2hvdWxkIGtub3cgdmVyeSB3ZWxsIGJ5IHRoaXMgcG9pbnQuDQoNCg0KDQoqKkV4YW1wbGUqKg0KDQoNCkluIGNsYXNzaWNhbCBzdGF0aXN0aWNzLCB3ZSBidWlsZCBhbmQgaWRlbnRpZnkgdGhlIGZpbmFsIG1vZGVsIHVzaW5nIHRoZSBlbnRpcmUgZGF0YSBzZXQgYW5kIHJlcG9ydCB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIGZpbmFsIHVzaW5nIHRoZSBzYW1lIGVudGlyZSBkYXRhIHNldCBiZWNhdXNlIHdlIG1hZGUgc3Ryb25nIGFzc3VtcHRpb25zIGFib3V0IG1vZGVscy4gRm9yIGV4YW1wbGUsIGluIHRoZSBsaW5lYXIgcmVncmVzc2lvbiwgd2UgdXNlIHRoZSB0cmFpbmluZyBkYXRhIHNldCB0byBlc3RpbWF0ZSB0aGUgY29lZmZpY2llbnRzIG9mIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsICR5ID0gXGJldGFfMCtcYmV0YV8xIHhfMSArIFxiZXRhXzIgeF8yICsgXGNkb3RzICsgXGJldGFfayB4X2skIGFuZCBvYnRhaW4gdGhlIGZvbGxvd2luZyBmaXR0ZWQgbW9kZWwNCg0KJCQNCnkgPSBcaGF0e1xiZXRhfV8wK1xoYXR7XGJldGF9XzEgeF8xICsgXGhhdHtcYmV0YX1fMiB4XzIgKyBcY2RvdHMgKyBcaGF0e1xiZXRhfV9rIHhfaw0KJCQNCg0KV2UgdXNlIHRoZSBhYm92ZS1lc3RpbWF0ZWQgbW9kZWwgYW5kIHRoZSAkeCQtdmFsdWVzIGluIHRoZSB0ZXN0aW5nIGRhdGEgc2V0IHRvIHByZWRpY3QgdGhlIGNvcnJlc3BvbmRpbmcgJHkkLXZhbHVlLiBUaGVuIHdlIGNhbiBlc3RpbWF0ZSB0aGUgdHJ1ZSBiaWFzLiANCg0KDQoNCg0KDQoNCiMjIFRocmVlLXdheSBTcGxpdHRpbmcNCg0KKipUcmFpbi1WYWxpZGF0aW9uLVRlc3QgU3BsaXQqKjogVGhlIGRhdGEgc2V0IGlzIHNwbGl0IGludG8gdGhyZWUgc3Vic2V0czogdHJhaW5pbmcsIHZhbGlkYXRpb24sIGFuZCB0ZXN0aW5nLiBUaGUgdHJhaW5pbmcgc2V0IGlzIHVzZWQgdG8gdHJhaW4gdGhlIG1vZGVsLCB0aGUgKnZhbGlkYXRpb24qIHNldCBpcyB1c2VkIHRvIHR1bmUgKipoeXBlci1wYXJhbWV0ZXJzKiogYW5kIHZhbGlkYXRlIHBlcmZvcm1hbmNlIGR1cmluZyB0cmFpbmluZywgYW5kIHRoZSB0ZXN0aW5nIHNldCBpcyB1c2VkIGZvciBmaW5hbCBldmFsdWF0aW9uLg0KDQoNCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI2NSUiLCBmaWcuY2FwPSJUcmFpbmluZy12YWxpZGF0aW5nLXRlc3Rpbmcgc3BsaXQ6IHRocmVlLXdheSBzcGxpdCJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdHJhaW4tdmFsaWQtdGVzdC1zcGxpdC5wbmciKQ0KYGBgDQoNCg0KDQoqKlZhbGlkYXRpb24gRGF0YSoqDQoNClRoZSB2YWxpZGF0aW9uIGRhdGEgc2V0IGlzIGEgc2FtcGxlIG9mIGRhdGEgaGVsZCBiYWNrIGZyb20gdHJhaW5pbmcgdGhlIG1vZGVsLiBUaGlzIGRhdGEgc2V0IHByb3ZpZGVzIGFuIHVuYmlhc2VkIGV2YWx1YXRpb24gb2YgYSBtb2RlbCBmaXQgb24gdGhlIHRyYWluaW5nIGRhdGEgc2V0IHdoaWxlIHR1bmluZyBtb2RlbCBoeXBlcnBhcmFtZXRlcnMgb3IgY2FsY3VsYXRpbmcgYSBwZXJmb3JtYW5jZSBtZWFzdXJlIGZvciBtb2RlbCBzZWxlY3Rpb24uIEluIG1vcmUgYmFzaWMgdGVybXMsIHZhbGlkYXRpb24gZGF0YSBpcyBhbiB1bnVzZWQgcG9ydGlvbiBvZiB5b3VyIHRyYWluaW5nIGRhdGEgYW5kIGhlbHBzIGRldGVybWluZSBpZiB0aGUgaW5pdGlhbCBtb2RlbCBpcyBhY2N1cmF0ZS4NCg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iODUlIiwgZmlnLmNhcD0iSWxsdXN0cmF0aXZlIGV4YW1wbGUgb2YgdGhyZWUtd2F5IGRhdGEgc3BsaXQifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL0NWLU1TRS1EZW1vLnBuZyIpDQpgYGANCg0KDQpBIG1vZGVsICoqaHlwZXJwYXJhbWV0ZXIqKiBpcyBhIGNvbmZpZ3VyYXRpb24gdGhhdCBpcyBleHRlcm5hbCB0byB0aGUgbW9kZWwgYW5kIHdob3NlIHZhbHVlIGNhbm5vdCBiZSBlc3RpbWF0ZWQgZnJvbSBkYXRhLg0KDQoqIFRoZXkgYXJlIG9mdGVuIHVzZWQgaW4gcHJvY2Vzc2VzIHRvIGhlbHAgZXN0aW1hdGUgbW9kZWwgcGFyYW1ldGVycy4NCiogVGhleSBhcmUgb2Z0ZW4gc3BlY2lmaWVkIGJ5IHRoZSBwcmFjdGl0aW9uZXIuDQoqIFRoZXkgY2FuIG9mdGVuIGJlIHNldCB1c2luZyBoZXVyaXN0aWNzLg0KKiBUaGV5IGFyZSBvZnRlbiB0dW5lZCBmb3IgYSBnaXZlbiBwcmVkaWN0aXZlIG1vZGVsaW5nIHByb2JsZW0uDQoNCldlIGRvbid0IGtub3cgdGhlIGJlc3QgdmFsdWUgZm9yIGEgbW9kZWwgaHlwZXJwYXJhbWV0ZXIgb24gYSBnaXZlbiBwcm9ibGVtLiBXZSBtYXkgdXNlIHJ1bGVzIG9mIHRodW1iLCBjb3B5IHZhbHVlcyB1c2VkIG9uIG90aGVyIHByb2JsZW1zLCBvciBzZWFyY2ggZm9yIHRoZSBiZXN0IHZhbHVlIGJ5IHRyaWFsIGFuZCBlcnJvci4gRm9yIGV4YW1wbGUsIGluIHByZWRpY3RpbmcgbGFiZWxzIGluIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVscywgd2UgbmVlZCB0byB1c2UgYW4gb3B0aW1hbCBjdXQtb2ZmIHByb2JhYmlsaXR5IHRvIGFjaGlldmUgdGhlIGJlc3QgYWNjdXJhY3kuIFRoZSAqKmN1dC1vZmYgcHJvYmFiaWxpdHkqKiBpcyB0aGUgaHlwZXItcGFyYW1ldGVyLiANCg0KDQojIyBPdmVyZml0dGluZyBhbmQgVW5kZXJmaXR0aW5nDQoNCk92ZXJmaXR0aW5nIGFuZCB1bmRlcmZpdHRpbmcgYXJlIGNvbW1vbiBpc3N1ZXMgaW4gZGF0YSBzY2llbmNlIChpbmNsdWRpbmcgc3RhdGlzdGljcykgdGhhdCBhZmZlY3QgYSBtb2RlbOKAmXMgcGVyZm9ybWFuY2UuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LmhlaWdodD0iMjUlIiwgZmlnLmNhcD0iSWxsdXN0cmF0aW9uIG9mIG92ZXJmaXR0aW5nIGFuZCB1bmRlcmZpdHRpbmcifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL092ZXJmaXR0aW5nVW5kZXJmaXR0aW5nLnBuZyIpDQpgYGANCg0KDQoqKk92ZXJmaXR0aW5nKiogb2NjdXJzIHdoZW4gYSBtb2RlbCBsZWFybnMgdGhlIHRyYWluaW5nIGRhdGEgdG9vIHdlbGwgKGhpZ2ggYWNjdXJhY3kpLCBpbmNsdWRpbmcgaXRzIG5vaXNlIGFuZCBvdXRsaWVycy4gVGhpcyBtYWtlcyB0aGUgbW9kZWwgcGVyZm9ybSBleGNlcHRpb25hbGx5IHdlbGwgb24gdHJhaW5pbmcgZGF0YSBidXQgcG9vcmx5IG9uIG5ldywgdW5zZWVuIGRhdGEgKHBvb3IgYWNjdXJhY3kpLiBEaWZmZXJlbnQgcmVhc29ucyBtYXkgY2F1c2Ugb3ZlcmZpdHRpbmcuIEZvciBleGFtcGxlLCBtb2RlbCBjb21wbGV4aXR5IHN1Y2ggYXMgdG9vIG1hbnkgZmVhdHVyZXMgYW5kIGluc3VmZmljaWVudCB0cmFpbmluZyBkYXRhLiBJbiBwcmFjdGljZSwgd2UgY2FuIHVzZSB0aGUgY3Jvc3MtdmFsaWRhdGlvbiBtZXRob2QgdG8gZGV0ZWN0IG92ZXJmaXR0aW5nIGFuZCBmaXggdGhlIGlzc3VlcyBieSBzaW1wbGlmeWluZyB0aGUgbW9kZWxzIGFuZCBpbmNyZWFzaW5nIHRoZSB2b2x1bWUgb2YgdGhlIHRyYWluaW5nIGRhdGEgc2V0LiAgIA0KDQoqKlVuZGVyZml0dGluZyoqIGhhcHBlbnMgd2hlbiBhIG1vZGVsIGlzIHRvbyBzaW1wbGUgdG8gY2FwdHVyZSB0aGUgdW5kZXJseWluZyBwYXR0ZXJucyBpbiB0aGUgZGF0YS4gSXQgcGVyZm9ybXMgcG9vcmx5IG9uIGJvdGggdHJhaW5pbmcgYW5kIG5ldyBkYXRhLiBBbiB1bmRlcmZpdHRpbmcgbW9kZWwgcmVzdWx0cyBpbiBsb3cgYWNjdXJhY3kgb24gYm90aCB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbi90ZXN0IGRhdGEuIFVuZGVyZml0dGluZyBjYW4gYWxzbyBiZSBkZXRlY3RlZCBieSBjcm9zcy12YWxpZGF0aW9uLiBUaGUgcmVtZWRpZXMgZm9yIGltcHJvdmluZyB0aGUgdW5kZXJmaXR0aW5nIGlzc3VlIGFyZSB0byBpbmNyZWFzZSBtb2RlbGluZyBjb21wbGV4aXR5IGJ5IGFkZGluZyBtb3JlIGZlYXR1cmVzIGFuZCByZWR1Y2luZyByZWd1bGFyaXphdGlvbiAocmVzdHJpY3Rpb24pLg0KDQojIyBTcGxpdHRpbmcgTWV0aG9kcw0KDQpUaGVyZSBhcmUgdmFyaW91cyBtZXRob2RzIG9mIHNwbGl0dGluZyBkYXRhIHNldHMuIFRoZSByaWdodCBhcHByb2FjaCBmb3IgZGF0YSBzcGxpdHRpbmcgYW5kIHRoZSBvcHRpbWFsIHNwbGl0IHJhdGlvIGJvdGggZGVwZW5kIG9uIHNldmVyYWwgZmFjdG9ycywgaW5jbHVkaW5nIHRoZSB1c2UgY2FzZSwgYW1vdW50IG9mIGRhdGEsIHF1YWxpdHkgb2YgZGF0YSwgYW5kIHRoZSBudW1iZXIgb2YgKipoeXBlci1wYXJhbWV0ZXJzKiouIA0KDQpUaGUgbW9zdCBjb21tb24gYXBwcm9hY2ggZm9yIGRpdmlkaW5nIGEgZGF0YSBzZXQgaXMgKipyYW5kb20gc2FtcGxpbmcqKi4gQXMgdGhlIG5hbWUgc3VnZ2VzdHMsIHRoZSBtZXRob2QgaW52b2x2ZXMgc2h1ZmZsaW5nIHRoZSBkYXRhIHNldCBhbmQgcmFuZG9tbHkgYXNzaWduaW5nIHNhbXBsZXMgdG8gdHJhaW5pbmcsIHZhbGlkYXRpb24sIG9yIHRlc3Qgc2V0cyBhY2NvcmRpbmcgdG8gcHJlZGV0ZXJtaW5lZCByYXRpb3MuIFdpdGggKipjbGFzcy1iYWxhbmNlZCoqIGRhdGEgc2V0cywgcmFuZG9tIHNhbXBsaW5nIGVuc3VyZXMgdGhlIHNwbGl0IGlzIHVuYmlhc2VkLiANCg0KSG93ZXZlciwgKipyYW5kb20gc2FtcGxpbmcqKiBpcyBub3QgdGhlIGNvcnJlY3QgYXBwcm9hY2ggd2l0aCBpbWJhbGFuY2VkIGRhdGEgc2V0cyAoPGZvbnQgY29sb3IgPSAicmVkIj4qXGNvbG9ye2JsdWV9dGhlIGltYmFsYW5jZWQgY2xhc3MgcmF0aW8gaXMgdXN1YWxseSBsZXNzIHRoYW4gMSUqIDwvZm9udD4pLiBXaGVuIHRoZSBkYXRhIGNvbnNpc3RzIG9mIHNrZXdlZCBjbGFzcyBwcm9wb3J0aW9ucywgcmFuZG9tIHNhbXBsaW5nIHdpbGwgYWxtb3N0IGNlcnRhaW5seSBjcmVhdGUgYSBiaWFzIGluIHRoZSBtb2RlbC4gSW4gdGhpcyBjYXNlLCB3ZSBuZWVkIHRvIHVzZSAqKnN0cmF0aWZpZWQgc2FtcGxpbmcqKiBpbiB3aGljaCB0aGUgKipyYW5kb20gc2FtcGxpbmcqKiBtZXRob2QgaXMgdXRpbGl6ZWQgaW4gdGhlIG1ham9yIGFuZCBtaW5vciBjYXRlZ29yaWVzIHJlc3BlY3RpdmVseSBhY2NvcmRpbmcgdG8gdGhlIHByZWRldGVybWluZWQgcmF0aW9zLiBUaGUgZmluYWwgdHJhaW5pbmcsIHZhbGlkYXRpbmcsIGFuZCB0ZXN0aW5nIGRhdGEgc2V0cyBhcmUgZm9ybWVkIHVzaW5nIHRoZSBjb3JyZXNwb25kaW5nIHN0cmF0aWZpZWQgc2FtcGxlcy4gIA0KDQo8Zm9udCBjb2xvciA9ICJyZWQiPioqXGNvbG9ye3JlZH1DYXV0aW9uOioqPC9mb250PiA8Zm9udCBjb2xvciA9ICJibHVlIj4qXGNvbG9ye2JsdWV9UHJlZGljdGlvbi9jbGFzc2lmaWNhdGlvbiBwcm9ibGVtcyBiYXNlZCBvbiBpbWJhbGFuY2VkIGRhdGEgYXJlIGNoYWxsZW5naW5nIGluIHByYWN0aWNlLiBTb21lIHNwZWNpYWwgbWV0aG9kcyBpbiBlaXRoZXIgc2FtcGxpbmcgb3IgbW9kZWxpbmcgYXJlIG5lZWRlZCB0byBoYW5kbGUgdGhlIGltYmFsYW5jZWQgY2F0ZWdvcnkgaXNzdWUuIEluIHRoaXMgY291cnNlLCB3ZSBvbmx5IGZvY3VzIG9uIGJhbGFuY2VkIGRhdGEuKjwvZm9udD4NCg0KDQoNCiMgQ3Jvc3MtVmFsaWRhdGlvbg0KDQpEaWZmZXJlbnQgY3Jvc3MtdmFsaWRhdGlvbiBtZXRob2RzIGFyZSBydW5uaW5nIGluIHByYWN0aWNlLiBXZSB3aWxsIGRpc2N1c3MgdGhlIEstZm9sZCBDViBpbiBkZXRhaWwgYW5kIHdvcmsgb24gYW4gZXhhbXBsZSB0byBpbGx1c3RyYXRlIHRoZSBzdGVwcyBmb3IgaW1wbGVtZW50aW5nIHRoZSBwcm9jZWR1cmUuIE90aGVyIGNyb3NzLXZhbGlkYXRpb24gbWV0aG9kcyB3aWxsIGFsc28gYmUgbGlzdGVkIGZvciByZWZlcmVuY2UgYXQgdGhlIGVuZCBvZiB0aGlzIHNlY3Rpb24uDQoNCg0KIyMgSy1Gb2xkIENyb3NzLVZhbGlkYXRpb24NCg0KKipLLWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbioqOiBUaGUgZGF0YSBzZXQgaXMgZGl2aWRlZCBpbnRvICgkayQpICoqZXF1YWxseSBzaXplZCoqIGZvbGRzLiBUaGUgbW9kZWwgaXMgdHJhaW5lZCBhbmQgZXZhbHVhdGVkICgkayQpIHRpbWVzLCBlYWNoIHRpbWUgdXNpbmcgKCRrLTEkKSBmb2xkcyBmb3IgdHJhaW5pbmcgYW5kIDEgZm9sZCBmb3IgdmFsaWRhdGlvbi90ZXN0aW5nLiBUaGlzIGhlbHBzIGluIG9idGFpbmluZyBtb3JlIHJvYnVzdCBwZXJmb3JtYW5jZSBlc3RpbWF0ZXMuICoqVGhpcyBub3RlIHdpbGwgZm9jdXMgb24gJEskLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBhbmQgaXRzIGFwcGxpY2F0aW9ucy4qKg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iNjUlIiwgZmlnLmNhcD0iSy1mb2xkIGNyb3NzIHZhbGlkYXRpb24gc3BsaXQifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL0stRm9sZC1DVi5wbmciKQ0KYGBgDQoNCioqSy1Gb2xkIENyb3NzLVZhbGlkYXRpb24qKiBwYXJ0aXRpb25zIHRoZSBkYXRhIHNldCBpbnRvIG11bHRpcGxlIHN1YnNldHMgdG8gaXRlcmF0aXZlbHkgdHJhaW4gYW5kIHRlc3QgdGhlIG1vZGVsLCB0aGUgVHJhaW4tVGVzdCBTcGxpdCBtZXRob2QgZGl2aWRlcyB0aGUgZGF0YSBzZXQgaW50byBqdXN0IHR3byBwYXJ0czogb25lIGZvciB0cmFpbmluZyBhbmQgdGhlIG90aGVyIGZvciB0ZXN0aW5nLiBUaGUgVHJhaW4tVGVzdCBTcGxpdCBtZXRob2QgaXMgc2ltcGxlIGFuZCBxdWljayB0byBpbXBsZW1lbnQuIEl0IHByb3ZpZGVzIGEgbW9yZSByb2J1c3QgYW5kIHJlbGlhYmxlIHBlcmZvcm1hbmNlIGVzdGltYXRlIGJlY2F1c2UgaXQgcmVkdWNlcyB0aGUgaW1wYWN0IG9mIGRhdGEgdmFyaWFiaWxpdHkuIEJ5IHVzaW5nIG11bHRpcGxlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGN5Y2xlcywgaXQgbWluaW1pemVzIHRoZSByaXNrIG9mIG92ZXJmaXR0aW5nIHRvIGEgcGFydGljdWxhciBkYXRhIHNwbGl0LiBUaGlzIG1ldGhvZCBhbHNvIGVuc3VyZXMgdGhhdCBldmVyeSBkYXRhIHBvaW50IGlzIHVzZWQgZm9yIGJvdGggdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24sIHdoaWNoIHJlc3VsdHMgaW4gYSBtb3JlIGNvbXByZWhlbnNpdmUgZXZhbHVhdGlvbiBvZiB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZS4gDQogDQpUaGUgZm9sZCBudW1iZXIgKipLKiogaXMgY29uc2lkZXJlZCBhcyBhIGh5cGVyLXBhcmFtZXRlciB3aGljaCBjYW4gYmUgdHVuZWQgdXNpbmcgdmFsaWRhdGlvbiBkYXRhLiBJbiBwcmFjdGljZSwgaXQgaXMgcmFyZWx5IHR1bmVkIHRvIHNhdmUgY29tcHV0YXRpb25hbCByZXNvdXJjZXMuIFRoZSBjb21tb24gcHJhY3RpY2UgaXMgdG8gY2hvb3NlIGEgbnVtYmVyIGJldHdlZW4gNSBhbmQgMTAuIEluIHRoZSBuZXh0IHN1YnNlY3Rpb24sIHdlIHVzZSA1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBmb3IgbW9kZWwgc2VsZWN0aW9uIGluIGxpbmVhciByZWdyZXNzaW9uIHVzaW5nIHBlcmZvcm1hbmNlIG1lYXN1cmUgTVNFLg0KDQoNCiMjIDUtRm9sZCBDcm9zcy1WYWxpZGF0aW9uDQoNClRoZSBnZW5lcmFsIDUtZm9sZCBzY2hlbWUgaXMgZGVwaWN0ZWQgaW4gdGhlIGZvbGxvd2luZy4NCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjcwJSIsIGZpZy5jYXA9IkZpdmUtZm9sZCBDcm9zcy12YWxpZGF0aW9uIHNjaGVtZSJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvRml2ZUZvbGRDVi5wbmciKQ0KYGBgDQoNCk5leHQsIHdlIHVzZSBhbiBleGFtcGxlIHRvIGlsbHVzdHJhdGUgdGhlIHN0ZXBzIGZvciA1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbi4NCg0KKipFeGFtcGxlKio6ICoqUGFsbWVyIFBlbmd1aW5zIERhdGFzZXQqKiBjb250YWlucyAzNDQgcGVuZ3VpbnMgZnJvbSAzIGRpZmZlcmVudCBzcGVjaWVzIGNvbGxlY3RlZCBmcm9tIDMgaXNsYW5kcyBpbiB0aGUgUGFsbWVyIEFyY2hpcGVsYWdvLCBBbnRhcmN0aWNhLiBJbiB0aGlzIGV4YW1wbGUsIHdlIG9ubHkgdXNlIGZvdXIgbnVtZXJpY2FsIHZhcmlhYmxlczogQ3VsbWVuIERlcHRoIChtbSksIEN1bG1lbiAobW0pLCBmbGlwcGVyIGxlbmd0aCAobW0pLCBhbmQgYm9keSBtYXNzIChnKSwgdG8gaW1wbGVtZW50IGNyb3NzLXZhbGlkYXRpb24gZm9yIG1vZGVsIHNlbGVjdGlvbiBpbiBsaW5lYXIgcmVncmVzc2lvbi4gDQoNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQuaGVpZ2h0PSI0NSUiLCBmaWcuY2FwPSJJbGx1c3RyYXRpb24gb2YgcGVuZ3VpbiBzaXplIG1lYXN1cmVzIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy9wZW5ndWluU2l6ZS5wbmciKQ0KYGBgDQoNCg0KV2Ugd2lsbCBidWlsZCB0d28gbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWxzIGFuZCB0aGVuIHVzZSBhIDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uIG1ldGhvZCB0byBzZWxlY3QgdGhlIG9uZSB3aXRoIGJldHRlciBwZXJmb3JtYW5jZSAoc21hbGxlciBNU0UpLiBUaGUgdHdvIG1vZGVscyBhcmUgZGVmaW5lZCBpbiB0aGUgZm9sbG93aW5nLg0KDQokJA0KXHRleHR7IE0xOiB9IFwgXCBcdGV4dHtCb2R5TWFzc309IFxiZXRhXzAgKyBcYmV0YV8xIFx0ZXh0e0N1bG1lbkxlbmd0aH0gKyBcYmV0YV8yIFx0ZXh0e0N1bG1lbkRlcHRofSArIFxlcHNpbG9uIA0KJCQNCg0KYW5kDQoNCiQkDQpcdGV4dHsgTTI6IH0gXCBcIFx0ZXh0e0JvZHlNYXNzfT0gXGFscGhhXzAgKyBcYWxwaGFfMSBcdGV4dHtDdWxtZW5MZW5ndGh9ICsgXGFscGhhXzIgXHRleHR7Q3VsbWVuRGVwdGh9ICtcYWxwaGFfMyBcdGV4dHtGbGlwcGVyTGVudGh9ICsgXHBzaSwNCiQkDQp3aGVyZSAkXGVwc2lsb24gXHNpbSBOKDAsIFxzaWdtYV4yKSQgYW5kICRccHNpIFxzaW0gTigwLCBcZGVsdGFeMikkLiANCg0KV2UgdXNlIDgwJS0yMCUgc3BsaXR0aW5nIHJhdGlvIHRvIHJhbmRvbWx5IHNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLiBXZSA1LWZvbGQgc3BsaXR0aW5nIHNjaGVtZSBpbiB0aGUgdHJhaW5pbmcgZGF0YS4gV2UgYnVpbGQgdGhlIGFib3ZlIHR3byBtb2RlbHMgKip3aXRoIDQgY29tYmluZWQgdHJhaW5pbmcgZm9sZHMqKiBhbmQgY2FsY3VsYXRlIHRoZSBNU0UgYmFzZWQgb24gdGhlICoqdmFsaWRhdGlvbiBmb2xkKiouIFVuZGVyIHRoaXMgNS1mb2xkIGNyb3NzLXZhbGlkYXRpb24sIGVhY2ggb2YgdGhlIHR3byBtb2RlbHMgd2lsbCBoYXZlIDUgcGVyZm9ybWFuY2UgbWVhc3VyZXMgKE1TRSksIHdlIGNhbGN1bGF0ZSB0aGUgbWVhbiBvZiBNU0Ugb2YgdGhlIHR3byBtb2RlbHMgcmVzcGVjdGl2ZWx5IGFuZCB0aGUgbW9kZWwgd2l0aCB0aGUgc21hbGxlciBtZWFuIE1TRSBpcyB0aGUgb25lIHRvIGJlIHRoZSBmaW5hbCBtb2RlbCB0byByZXBvcnQgKGltcGxlbWVudCkuIFRoZSBmb2xsb3dpbmcgZmlndXJlIGRlcGljdHMgdGhpcyBwcm9jZXNzLg0KDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI3MCUiLCBmaWcuY2FwPSJUaGUgcHJvY2VzcyA1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBmb3IgbW9kZWwgc2VsZWN0aW9uIHdpdGggTVNFIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy81Zm9sZENWLnBuZyIpDQpgYGANCg0KIyMjIENyb3NzLXZhbGlkYXRpb24gd2l0aCBMaW5lYXIgUmVncmVzc2lvbg0KDQpXZSBpbXBsZW1lbnQgY3Jvc3MtdmFsaWRhdGlvbiB1c2luZyB0aGUgKnBlbmd1aW4qIGRhdGEgdG8gYXNzZXNzIHRoZSBwZXJmb3JtYW5jZSBvZiBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbHMuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9NSwgZmlnLmNhcD0iUGFpcndpc2Ugc2NhdHRlciBwbG90IHNob3dpbmcgdGhlIGNvcnJlbGF0aW9ucyBhbW9uZyB0aGUgbGluZWFyIGNvcnJlbGF0aW9uIGJldHdlZW4gdmFyaWFibGVzLiJ9DQpwZW5ndWluID0gcmVhZC5jc3YoIlBlbmd1aW5zU2l6ZS5jc3YiLCBoZWFkZXIgPSBUUlVFKQ0KcGFpcnMocGVuZ3VpblssIDM6Nl0pDQpgYGANCg0KVGhlIGJhc2ljIHN0ZXBzIGFyZSBvdXRsaW5lZCBpbiB0aGUgZm9sbG93aW5nOg0KDQoqICoqVHdvLXdheSBkYXRhIHNwbGl0dGluZyoqIHNwbGl0IHRoZSBlbnRpcmUgc2FtcGxlIGludG8gdHJhaW5pbmcgZGF0YSAoODAlKSBhbmQgdGVzdGluZyBkYXRhICgyMCUpLg0KDQoqICoqQ3JlYXRpbmcgNS1mb2xkIGRhdGEqKjogcmFuZG9tbHkgc3BsaXQgdGhlIHRyYWluaW5nIGRhdGEgaW50byA1IGZvbGRzICh3aXRoIGFwcHJveGltYXRlbHkgZXF1YWwgc2l6ZSkuDQoNCiogKipQZXJmb3JtaW5nIGNyb3NzLXZhbGlkYXRpb24qKjogZml0IHRoZSB0d28gbW9kZWxzIHRvIHRoZSA1IGRpZmZlcmVudCBjb21iaW5hdGlvbnMgb2YgY29tYmluZWQgdHJhaW5pbmcgZm9sZHMgYW5kIGV2YWx1YXRlIHRoZSBwZXJmb3JtYW5jZSBiYXNlZCBvbiB0aGUgY29ycmVzcG9uZGluZyB2YWxpZGF0aW9uIGZvbGRzLg0KDQoqICoqQXZlcmFnaW5nIE1TRSoqOiB0YWtlIHRoZSBhdmVyYWdlIG9mIHRoZSBNU0Ugb2YgdGhlIHR3byBtb2RlbHMgcmVzcGVjdGl2ZWx5IGFuZCByZXBvcnQgdGhlbSBmb3IgbW9kZWwgc2VsZWN0aW9uLg0KDQpUaGUgUiBjb2RlIGZvciBpbXBsZW1lbnRpbmcgdGhlIGFib3ZlIDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uIGlzIGdpdmVuIGJ5DQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NCwgZmlnLmNhcD0iQ3Jvc3MtdmFsaWRhdGlvbiBmb3IgZXZhbHVhdGluZyBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlIG9mIGxpbmVhciByZWdyZXNzaW9uIG1vZGVscyJ9DQpwZW5ndWluID0gbmEub21pdChwZW5ndWluKSAgIyBkZWxldGUgYWxsIHJlY29yZHMgd2l0aCBtaXNzaW5nIHZhbHVlcw0KIyMNCm4gPSBkaW0ocGVuZ3VpbilbMV0gICAgICMgc2FtcGxlIHNpemUNCm9icy5JRCA9IDE6biAgICAgICAgICAgICMgcmFuZG9taXplIG9icyBJRA0Kbi50cmFpbiA9IHJvdW5kKDAuOCpuKSAgIyB0cmFpbmluZyBkYXRhIHNpemUNCg0Kc2h1ZmZsZWQuaWQgPSBzYW1wbGUob2JzLklELCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgbiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2UgPSBGQUxTRSkgICMgcmFuZG9taXppbmcgdGhlIG9ic2VydmF0aW9uIElEcyANCnNodWZmbGVkLnBlbmd1aW4gPSBuYS5vbWl0KHBlbmd1aW5bc2h1ZmZsZWQuaWQsIF0pICAgIyByYW5kb21pemluZyB0aGUgZGF0YSBzZXQNCiMgVHdvLXdheSBzcGxpdCBmb3IgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YSBzZXRzDQp0cmFpbi5kYXRhID0gc2h1ZmZsZWQucGVuZ3VpblsxOm4udHJhaW4sXSAgICAgICAgICMgdHJhaW5pbmcgZGF0YQ0KdGVzdC5kYXRhID0gc2h1ZmZsZWQucGVuZ3Vpblsobi50cmFpbiArIDEpOm4sIF0gICAjIHRlc3RpbmcgZGF0YQ0KIyMNCm4uZm9sZCA9IHJvdW5kKG4udHJhaW4vNSktMSAgICAjIHNpemUgb2YgNS1mb2xkIHNwbGl0dGluZywgLTEgdG8gZ3VhcmFudGVlIHRoYXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIDUqZm9sZHNpemUgPD0gdHJhaW4uZGF0YSBzaXplDQojIyA1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbjogaXRlcmF0aXZlIHByb2Nlc3Mgd2l0aCBhIGZvci1sb29wDQojIw0KTVNFLk0xID0gcmVwKDAsNSkNCk1TRS5NMiA9IHJlcCgwLDUpDQojIw0KZm9yIChpIGluIDE6NSl7DQogIHZhbGlkLmlkID0gKChpLTEpKm4uZm9sZCsxKTooaSpuLmZvbGQpICAgICMgb2JzIElEIGZvciB2YWxpZGF0aW5nIGZvbGQgDQogIGNyb3NzLnRyYWluID0gdHJhaW4uZGF0YVstdmFsaWQuaWQsIF0NCiAgY3Jvc3MudmFsaWQgPSB0cmFpbi5kYXRhW3ZhbGlkLmlkLF0NCiAgIyMgQnVpbGRpbmcgdGhlIHR3byBtb2RlbHMNCiAgTTEgPSBsbShib2R5TWFzcyB+IGN1bG1lbkxlbmd0aCArIGN1bG1lbkRlcHRoLCBkYXRhICA9IGNyb3NzLnRyYWluKQ0KICBNMiA9IGxtKGJvZHlNYXNzIH4gY3VsbWVuTGVuZ3RoICsgY3VsbWVuRGVwdGggKyBmbGlwcGVyTGVuZ3RoLCBkYXRhICA9IGNyb3NzLnRyYWluKQ0KICAjIyBQcmVkaWN0aW5nIGJvZHlNYXNzIHdpdGggdmFsaWRhdGUgZm9sZA0KICBwcmVkTTEgPSBwcmVkaWN0KE0xLCBuZXdkYXRhID0gY3Jvc3MudmFsaWQpDQogIHByZWRNMiA9IHByZWRpY3QoTTIsIG5ld2RhdGEgPSBjcm9zcy52YWxpZCkNCiAgIyMNCiAgTVNFLk0xW2ldID0gbWVhbigocHJlZE0xIC0gY3Jvc3MudmFsaWQkYm9keU1hc3MpXjIpDQogIE1TRS5NMltpXSA9IG1lYW4oKHByZWRNMiAtIGNyb3NzLnZhbGlkJGJvZHlNYXNzKV4yKQ0KIH0NCiBNU0UuMSA9IG1lYW4oTVNFLk0xKQ0KIE1TRS4yID0gbWVhbihNU0UuTTIpDQogIyMjIFZpc3VhbGl6ZSB0aGUgTVNFIA0KIHBsb3QoMTo1LCBNU0UuTTEsIHR5cGUgPSAibCIsIA0KICAgICAgeGxpbSA9YygwLDYpLA0KICAgICAgeWxpbSA9IGMoMTAwMDAwLCA1MDAwMCttYXgoTVNFLk0xKSksDQogICAgICB5bGFiID0gIk1TRSAoZykiLA0KICAgICAgeGxhYiA9ICJJdGVyYXRpb25zIDEgdG8gNSIsDQogICAgICBsd2QgPSAyLA0KICAgICAgY29sID0gIm5hdnkiLA0KICAgICAgbWFpbiA9ICJQZXJmb3JtYW5jZSBFdmFsdWF0aW9uIHZpYSA1LWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbiIsDQogICAgICBjZXgubWFpbiA9IDAuOCkNCiBwb2ludHMoMTo1LCBNU0UuTTEsIHBjaCA9IDE5LCBjb2wgPSAibmF2eSIpDQogbGluZXMoMTo1LCBNU0UuTTIsIGx3ZCA9IDIsIGNvbCA9ICJkYXJrcmVkIikNCiBwb2ludHMoMTo1LCBNU0UuTTIsIHBjaCA9IDE5LCBjb2wgPSAiZGFya3JlZCIpDQogbGVnZW5kKCJ0b3ByaWdodCIsIGMocGFzdGUoIk0xIEF2Zy4gTVNFOiAiLCByb3VuZChNU0UuMSkpLCBwYXN0ZSgiTTIgQXZnLiBNU0U6ICIscm91bmQoTVNFLjIpKSksDQogICAgICAgIGNvbD1jKCJuYXZ5IiwgImRhcmtyZWQiKSwNCiAgICAgICAgcGNoID0gcmVwKDE5LDIpLCANCiAgICAgICAgbHdkID0gcmVwKDIsMiksDQogICAgICAgIGNleCA9IDAuNiwNCiAgICAgICAgYnR5ID0gIm4iKQ0KYGBgDQoNClRoZSBhYm92ZSBmaWd1cmUgaW5kaWNhdGVzIHRoYXQgbW9kZWwgMiAoTTIpIGhhcyBhIHNtYWxsZXIgYXZlcmFnZSBmb2xkLXdpc2UgTVNFLiBUaGlzIG1lYW5zIHRoYXQgbW9kZWwgTTIgaGFzIGEgYmV0dGVyIHByZWRpY3RpdmUgcGVyZm9ybWFuY2UuIFRoZXJlZm9yZSwgTTIgaXMgcmVjb21tZW5kZWQgZm9yIHByYWN0aWNhbCBwcmVkaWN0aW9uLiANCg0KSW4gcmVhbC13b3JsZCBhcHBsaWNhdGlvbnMsIHRoZSBhY3R1YWwgcGVyZm9ybWFuY2Ugb2YgdGhlIHJlY29tbWVuZGVkIG1vZGVsIHNob3VsZCBhbHNvIGJlIHJlcG9ydGVkIHRvIHRoZSBjbGllbnRzIGJhc2VkIG9uIHRoZSB0ZXN0IGRhdGEgdGhhdCBoYXMgbm90IGJlZW4gdXNlZCB5ZXQuIFRoZSBjb21tb24gcHJhY3RpY2UgaXMgdG8gcmVmaXQgdGhlIHJlY29tbWVuZGVkIG1vZGVsIHRvIHRoZSAqKm9yaWdpbmFsIGVudGlyZSB0cmFpbmluZyBkYXRhKiogdG8gdXBkYXRlIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyBhbmQgdGhlbiB1c2UgdGhpcyByZWZpdHRlZCBtb2RlbCB0byBldmFsdWF0ZSB0aGUgcHJlZGV0ZXJtaW5lZCBwZXJmb3JtYW5jZSBtZWFzdXJlIChpbiBvdXIgY2FzZSBNU0UpIGJhc2VkIG9uIHRoZSB0ZXN0aW5nIGRhdGEuIFRoZSBmb2xsb3dpbmcgY29kZSBzaG93cyB0aGlzIHN0ZXAuDQoNCg0KYGBge3J9DQpNMi5maW5hbCA9IGxtKGJvZHlNYXNzIH4gY3VsbWVuTGVuZ3RoICsgY3VsbWVuRGVwdGggKyBmbGlwcGVyTGVuZ3RoLCBkYXRhICA9IHRyYWluLmRhdGEpDQpwcmVkTTIuZmluYWwgPSBwcmVkaWN0KE0yLmZpbmFsLCBuZXdkYXRhID0gdGVzdC5kYXRhKQ0KTVNFLk0yLmZpbmFsID0gbWVhbigocHJlZE0yLmZpbmFsIC0gdGVzdC5kYXRhJGJvZHlNYXNzKV4yKQ0KTVNFLk0yLmZpbmFsDQpgYGANCg0KVGhlIGFjdHVhbCBwZXJmb3JtYW5jZSBvZiB0aGUgcmVjb21tZW5kZWQgbW9kZWwgaGFzIG1lYW4gc3F1YXJlZCBlcnJvciAoTVNFKSBpcyBzaG93biBhYm92ZS4NCg0KKipSZW1hcmtzKio6IFRoZXJlIGFyZSBkaWZmZXJlbnQgbWV0cmljcyB0byBtZWFzdXJlIGRpZmZlcmVudCB0eXBlcyBvZiBwZXJmb3JtYW5jZXMuIEluIHRoaXMgZXhhbXBsZSwgd2UgYXJlIGludGVyZXN0ZWQgaW4gcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSwgdGhlIG1lYW4gc3F1YXJlZCBlcnJvciB3YXMgdXNlZC4gSWYgd2Ugd2FudCB0byBldmFsdWF0ZSB0aGUgZ2xvYmFsIGdvb2RuZXNzLW9mLWZpdCwgd2UgbWF5IHVzZSB0aGUgZm9sbG93aW5nIG1lYXN1cmVzLg0KDQoqICoqUi1zcXVhcmVkICgkUl4yJCkqKjogVGhpcyBpcyB0aGUgbW9zdCBjb21tb24gbWVhc3VyZS4gSXQgcmVwcmVzZW50cyB0aGUgcHJvcG9ydGlvbiBvZiB0aGUgdmFyaWFuY2UgaW4gdGhlIGRlcGVuZGVudCB2YXJpYWJsZSB0aGF0IGlzIHByZWRpY3RhYmxlIGZyb20gdGhlIGluZGVwZW5kZW50IHZhcmlhYmxlcy4gQW4gUsKyIHZhbHVlIGNsb3NlciB0byAxIGluZGljYXRlcyBhIGJldHRlciBmaXQuDQoNCiogKipBZGp1c3RlZCBSLXNxdWFyZWQqKjogVGhpcyBhZGp1c3RzIHRoZSBSwrIgdmFsdWUgYmFzZWQgb24gdGhlIG51bWJlciBvZiBwcmVkaWN0b3JzIGluIHRoZSBtb2RlbC4gSXQgaXMgdXNlZnVsIHdoZW4gY29tcGFyaW5nIG1vZGVscyB3aXRoIGEgZGlmZmVyZW50IG51bWJlciBvZiBwcmVkaWN0b3JzLCBhcyBpdCBwZW5hbGl6ZXMgZm9yIGFkZGluZyB2YXJpYWJsZXMgdGhhdCBkbyBub3QgaW1wcm92ZSB0aGUgbW9kZWwgc2lnbmlmaWNhbnRseS4NCg0KKiAqKkYtc3RhdGlzdGljKio6IFRoaXMgdGVzdHMgdGhlIG92ZXJhbGwgc2lnbmlmaWNhbmNlIG9mIHRoZSBtb2RlbC4gSXQgY29tcGFyZXMgdGhlIG1vZGVsIHdpdGggbm8gcHJlZGljdG9ycyAoaW50ZXJjZXB0LW9ubHkgbW9kZWwpIHRvIHRoZSBzcGVjaWZpZWQgbW9kZWwuIEEgaGlnaGVyIEYtc3RhdGlzdGljIGluZGljYXRlcyB0aGF0IHRoZSBtb2RlbCBwcm92aWRlcyBhIGJldHRlciBmaXQgdGhhbiBhIG1vZGVsIHdpdGggbm8gcHJlZGljdG9ycy4NCg0KKiAqKkFrYWlrZSBJbmZvcm1hdGlvbiBDcml0ZXJpb24gKEFJQykgYW5kIEJheWVzaWFuIEluZm9ybWF0aW9uIENyaXRlcmlvbiAoQklDKSoqOiBUaGVzZSBjcml0ZXJpYSBhcmUgdXNlZCBmb3IgbW9kZWwgc2VsZWN0aW9uLiBUaGV5IGJhbGFuY2UgbW9kZWwgZml0IGFuZCBjb21wbGV4aXR5LCBwZW5hbGl6aW5nIG1vZGVscyB3aXRoIG1vcmUgcGFyYW1ldGVycy4gTG93ZXIgdmFsdWVzIG9mIEFJQyBhbmQgQklDIGluZGljYXRlIGEgYmV0dGVyIG1vZGVsLg0KDQoNCg0KIyMjIENyb3NzLXZhbGlkYXRpb24gaW4gTG9naXN0aWMgUmVncmVzc2lvbg0KDQpXZSBzdGlsbCB1c2UgdGhlIGZyYXVkIGRhdGEgdG8gYnVpbGQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIGNyb3NzLXZhbGlkYXRpb24gYW5kIHRlc3RpbmcgbWV0aG9kcy4gV2UgZmlyc3QgcmFuZG9tbHkgc3BsaXQgdGhlIGRhdGEgaW50byA3MCUgYW5kIDMwJS4gVGhlIDMwJSBvZiB0aGUgZGF0YSB3aWxsIGJlIHVzZWQgZm9yIHRlc3RpbmcgcHVycG9zZXMuIFRoZSA3MCUgd2lsbCBiZSB1c2VkIGZvciB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbi4gVGhlIHBlcmZvcm1hbmNlIG1ldHJpYyB0byBiZSB1c2VkIGlzIHRoZSBBVUMuIFRoZSBhdmVyYWdlIG9mIHRoZSB2YWxpZGF0ZWQgQVVDIHdpbGwgYmUgY29tcGFyZWQgd2l0aCB0aGUgQVVDIGNhbGN1bGF0ZWQgZnJvbSB0aGUgdGVzdGluZyBkYXRhLg0KDQpXZSB1c2UgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uIGluIHRoaXMgZXhhbXBsZS4gVGhlIHN1bW1hcnkgb2YgdGhlIHZhbGlkYXRlZCBBVUNzIGlzIGdpdmVuIGJlbG93LiBJbnN0ZWFkIG9mIGNhbGN1bGF0aW5nIHNwZWNpZmljaXR5IGFuZCBzZW5zaXRpdml0eSBmcm9tIHNjcmF0Y2gsIHdlIGV4dHJhY3QgdGhlc2UgdHdvIG1ldHJpY3MgZnJvbSBgcm9jKClgIGRpcmVjdGx5IGluIHRoaXMgY3Jvc3MtdmFsaWRhdGlvbi4NCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQ0KZnJhdWQuZGF0YSA9IHJlYWQuY3N2KCJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy9GcmF1ZEluZGV4L2ZyYXVkaWR4LmNzdiIpWywtMV0NCiMjIGVuY29kZSBzdGF0dXMgdmFyaWFibGU6IGJhZCA9IDEgYW5kIGdvb2QgPSAwDQpnb29kLmlkID0gd2hpY2goZnJhdWQuZGF0YSRzdGF0dXMgPT0gIiBnb29kIikgDQpiYWQuaWQgPSB3aGljaChmcmF1ZC5kYXRhJHN0YXR1cyA9PSAiZnJhdWQiKQ0KIyMNCmZyYXVkLmRhdGEkZnJhdWQuc3RhdHVzID0gMA0KZnJhdWQuZGF0YSRmcmF1ZC5zdGF0dXNbYmFkLmlkXSA9IDENCm5uID0gZGltKGZyYXVkLmRhdGEpWzFdDQp0cmFpbi5pZCA9IHNhbXBsZSgxOm5uLCByb3VuZChubiowLjcpLCByZXBsYWNlID0gRkFMU0UpIA0KdHJhaW5EYXQgPSBmcmF1ZC5kYXRhW3RyYWluLmlkLF0NCnRlc3REYXQgPSBmcmF1ZC5kYXRhWy10cmFpbi5pZCxdDQojIyBlYWNoIGZvbGQgaGFzIDIzMjYgb2JzZXJ2YXRpb25zDQojIw0KQVVDLnZlYyA9IHJlcCgwLDUpDQpjb2xvcj1jKCIjMDAyNjQyIiwiIzg0MDAzMiIsIiM0MzI1MzQiLCAiI2M0NDkwMCIsICIjMGI3YTc1IikNCiMjDQpmb3IoaSBpbiAxOjUpew0KICBJRCA9ICgoaS0xKSo0NjUyKzEpOihpKjQ2NTIpDQogIHZhbGlkID0gYXMuZGF0YS5mcmFtZSh0cmFpbkRhdFtJRCxdKQ0KICB0cmFpbiA9IGFzLmRhdGEuZnJhbWUodHJhaW5EYXRbLUlELF0pDQogICMjDQogIGxvZ2l0Lm1vZGVsID0gZ2xtKGZyYXVkLnN0YXR1cyB+IGluZGV4LCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gbG9naXQpLCBkYXRhID0gdHJhaW4pDQogIG5ld2RhdGEgPSBkYXRhLmZyYW1lKGluZGV4PSB2YWxpZCRpbmRleCkNCiAgcHJlZC5wcm9iLnRyYWluID0gcHJlZGljdC5nbG0obG9naXQubW9kZWwsIG5ld2RhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQ0KICAjIyB3ZSBuZXh0IHVzZSBsaWJyYXJ5IHtwUk9DfQ0KICAjIyB0byBjYWxjdWxhdGUgQVVDDQogIHByZWRpY3Rpb24gPSBwcmVkLnByb2IudHJhaW4NCiAgY2F0ZWdvcnkgPSB2YWxpZCRmcmF1ZC5zdGF0dXMgPT0gMQ0KICBST0NvYmogPC0gcm9jKGNhdGVnb3J5LCBwcmVkaWN0aW9uKQ0KICBBVUMudmVjW2ldID0gcm91bmQoYXVjKFJPQ29iaiksNCkNCiAgaWYoaT09MSl7DQogICAgcGxvdCgoMS1ST0NvYmokc3BlY2lmaWNpdGllcyksUk9Db2JqJHNlbnNpdGl2aXRpZXMsDQogICAgICAgICB0eXBlPSJsIiwNCiAgICAgICAgIG1haW49IjUtZm9sZCBDcm9zcy12YWxpZGF0ZWQgUk9DIiwNCiAgICAgICAgIGNvbD1jb2xvcltpXSwgDQogICAgICAgICB4bGFiPSIxLXNwZWNpZmljdHkiLA0KICAgICAgICAgeWxhYj0ic2Vuc2l0aXZpdHkiLA0KICAgICAgICAgbHdkPTEsIA0KICAgICAgICAgbHR5PTEpDQogICAgICAgIHNlZ21lbnRzKDAsMCwxLDEsIGx0eT0yLCBjb2w9InJlZCIpDQogICAgfSBlbHNlew0KICAgIGxpbmVzKCgxLVJPQ29iaiRzcGVjaWZpY2l0aWVzKSxST0NvYmokc2Vuc2l0aXZpdGllcywgY29sPWNvbG9yW2ldLCBsd2Q9MSkNCiAgICB9DQogIH0NCnN1bW1hcnkoQVVDLnZlYykNCmBgYA0KDQpXZSBuZXh0IGNhbGN1bGF0ZSB0aGUgQVVDIGJhc2VkIG9uIHRoZSBob2xkLW91dCB0ZXN0aW5nIGRhdGEuIFdlIHdpbGwgdXNlDQoNCmBgYHtyfQ0KICAjIyMgQ2F1dGlvbiwgdGhlIG1vZGVsIHVzZWQgdG8gY2FsY3VsYXRlIHRoZSBBVUMgbXVzdCBiZSBiYXNlZCBvbiB0aGUgZW50aXJlDQogICMjIyB0cmFpbmluZyBkYXRhDQogIGxvZ2l0Lm1vZGVsID0gZ2xtKGZyYXVkLnN0YXR1cyB+IGluZGV4LCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gbG9naXQpLCBkYXRhID0gdHJhaW5EYXQpDQogICMjIw0KICBuZXdkYXRhID0gZGF0YS5mcmFtZShpbmRleD0gdGVzdERhdCRpbmRleCkNCiAgcHJlZC5wcm9iLnRyYWluID0gcHJlZGljdC5nbG0obG9naXQubW9kZWwsIG5ld2RhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQ0KICAjIyB3ZSBuZXh0IHVzZSBsaWJyYXJ5IHtwUk9DfQ0KICAjIyB0byBjYWxjdWxhdGUgQVVDDQogIHByZWRpY3Rpb24gPSBwcmVkLnByb2IudHJhaW4NCiAgY2F0ZWdvcnkgPSB0ZXN0RGF0JGZyYXVkLnN0YXR1cyA9PSAxDQogIFJPQ29iaiA8LSByb2MoY2F0ZWdvcnksIHByZWRpY3Rpb24pDQogIHRlc3RBVUM9IHJvdW5kKGF1YyhST0NvYmopLDQpDQogIGModGVzdEFVQyA9IHRlc3RBVUMpDQpgYGANCg0KDQojIyBBZGRpdGlvbmFsIENvbW1lbnRzIG9mIEstRm9sZCBDVg0KDQpJbiBLLUZvbGQgQ3Jvc3MtVmFsaWRhdGlvbiwgJEskIHJlcHJlc2VudHMgdGhlIG51bWJlciBvZiBncm91cHMgaW50byB3aGljaCB0aGUgZGF0YSBzZXQgaXMgZGl2aWRlZC4gVGhpcyBudW1iZXIgZGV0ZXJtaW5lcyBob3cgbWFueSByb3VuZHMgb2YgdGVzdGluZyB0aGUgbW9kZWwgdW5kZXJnb2VzLCBlbnN1cmluZyBlYWNoIHNlZ21lbnQgaXMgdXNlZCBhcyBhIHRlc3Rpbmcgc2V0IG9uY2UuIFRoaXMgaXMgYSBoeXBlci1wYXJhbWV0ZXIgdGhhdCBjYW4gYmUgdHVuZWQgdG8gcHJvZHVjZSB0aGUgb3B0aW1hbCBwZXJmb3JtYW5jZS4gSG93ZXZlciwgc29tZSBoZXVyaXN0aWNzIGNhbiBiZSB1c2VkIGluIHByYWN0aWNlIHRvIHNhdmUgc29tZSBtb2RlbCB0cmFpbmluZyBlZmZvcnQuDQoNCkEgc21hbGxlciBLIChlLmcuLCAzLTUpIG1pZ2h0IGJlIGZhc3RlciBidXQgY291bGQgeWllbGQgbGVzcyByZWxpYWJsZSBlc3RpbWF0ZXMgYXMgZWFjaCBmb2xkIHJlcHJlc2VudHMgYSBsYXJnZXIgcG9ydGlvbiBvZiB0aGUgZGF0YXNldCwgcG90ZW50aWFsbHkgbWlzc2luZyBvdXQgb24gZGl2ZXJzZSBkYXRhIHNjZW5hcmlvcy4gQSBsYXJnZXIgSyAoZS5nLiwgMTApIHByb3ZpZGVzIGEgbW9yZSBkZXRhaWxlZCBldmFsdWF0aW9uIGF0IHRoZSBjb3N0IG9mIGluY3JlYXNlZCBjb21wdXRhdGlvbi4gQSBjb21tb24gc3RhcnRpbmcgcG9pbnQgaXMgSz01IG9yIEs9MTAsIHdoaWNoIGFyZSBvZnRlbiBlbm91Z2ggdG8gZ2V0IGEgcmVsaWFibGUgZXN0aW1hdGUgd2l0aG91dCBleGNlc3NpdmUgY29tcHV0YXRpb24uDQoNCg0KKiAqKiRLID0gMiQgb3IgJDMkKio6IFRoZXNlIGNob2ljZXMgY2FuIGJlIGJlbmVmaWNpYWwgd2hlbiBjb21wdXRhdGlvbmFsIHJlc291cmNlcyBhcmUgbGltaXRlZCBvciB3aGVuIGEgcXVpY2tlciBldmFsdWF0aW9uIGlzIG5lZWRlZC4gVGhleSByZWR1Y2UgdGhlIG51bWJlciBvZiB0cmFpbmluZyBjeWNsZXMsIHRodXMgc2F2aW5nIHRpbWUgYW5kIGNvbXB1dGF0aW9uYWwgcG93ZXIgd2hpbGUgc3RpbGwgcHJvdmlkaW5nIGEgcmVhc29uYWJsZSBlc3RpbWF0ZSBvZiBtb2RlbCBwZXJmb3JtYW5jZS4gDQoNCiogKiokSyA9IDUkIG9yICQxMCQqKjogQ2hvb3NpbmcgJEsgPSA1JCBvciAkSyA9IDEwJCBhcmUgcG9wdWxhciBjaG9pY2VzIGJlY2F1c2UgdGhleSBwcm92aWRlIGEgZ29vZCBiYWxhbmNlIGJldHdlZW4gY29tcHV0YXRpb25hbCBlZmZpY2llbmN5IGFuZCBtb2RlbCBwZXJmb3JtYW5jZSBlc3RpbWF0aW9uLiANCg0KKiAqKiRLID0gMjAkKio6IFVzaW5nIGEgbGFyZ2VyIHZhbHVlIG9mIEsgY2FuIHByb3ZpZGUgYSBtb3JlIGRldGFpbGVkIHBlcmZvcm1hbmNlIGV2YWx1YXRpb24uIEhvd2V2ZXIsIGl0IGluY3JlYXNlcyB0aGUgY29tcHV0YXRpb25hbCBidXJkZW4gYW5kIG1pZ2h0IHJlc3VsdCBpbiBoaWdoZXIgdmFyaWFuY2UgaWYgdGhlIHN1YnNldHMgYXJlIHRvbyBzbWFsbC4NCg0KDQoqKkltcG9ydGFuY2Ugb2Ygc2h1ZmZsaW5nIGRhdGEgaW4gSy1Gb2xkIENyb3NzLVZhbGlkYXRpb24qKg0KDQpTaHVmZmxpbmcgdGhlIGRhdGEgaW4gSy1Gb2xkIENyb3NzLVZhbGlkYXRpb24gaXMgaGlnaGx5IHJlY29tbWVuZGVkIHRvIGVuaGFuY2UgdGhlIHZhbGlkaXR5IG9mIG1vZGVsIGV2YWx1YXRpb24uIFNodWZmbGluZyBicmVha3MgYW55IGluaGVyZW50IG9yZGVyIGluIHRoZSBkYXRhIHNldCB0aGF0IGNvdWxkIGludHJvZHVjZSBiaWFzIGR1cmluZyB0aGUgdmFsaWRhdGlvbiBwcm9jZXNzLiBUaGlzIGVuc3VyZXMgdGhhdCBlYWNoIGZvbGQgaXMgcmVwcmVzZW50YXRpdmUgb2YgdGhlIGVudGlyZSBkYXRhIHNldCwgd2hpY2ggaXMgY3J1Y2lhbCBmb3IgYXNzZXNzaW5nIGhvdyB3ZWxsIHRoZSBtb2RlbCBnZW5lcmFsaXplcyB0byBuZXcgZGF0YS4gSG93ZXZlciwgaXTigJlzIGltcG9ydGFudCB0byBhdm9pZCBzaHVmZmxpbmcgaW4gY2FzZXMgd2hlcmUgdGhlIHNlcXVlbmNlIG9mIGRhdGEgcG9pbnRzIGlzIG1lYW5pbmdmdWwsIHN1Y2ggYXMgd2l0aCB0aW1lLXNlcmllcyBkYXRhLCB0byBwcmVzZXJ2ZSB0aGUgaW50ZWdyaXR5IG9mIHRoZSBsZWFybmluZyBwcm9jZXNzLiANCg0KKipFbnN1cmluZyByZXByb2R1Y2liaWxpdHkgaW4gSy1Gb2xkIENyb3NzLVZhbGlkYXRpb24qKg0KDQpFbnN1cmluZyB0aGF0IHRoZSByZXN1bHRzIG9mIEstRm9sZCBDcm9zcy1WYWxpZGF0aW9uIGFyZSByZXByb2R1Y2libGUgaXMgY3J1Y2lhbCBmb3IgdmVyaWZ5aW5nIG1vZGVsIHN0YWJpbGl0eSBhbmQgcGVyZm9ybWFuY2UuIFRoaXMgY2FuIGJlIGFjaGlldmVkIGJ5IHNldHRpbmcgdGhlIHJhbmRvbV9zdGF0ZSBwYXJhbWV0ZXIsIHdoaWNoIGVuc3VyZXMgY29uc2lzdGVudCBzaHVmZmxpbmcgb2YgZGF0YSBhY3Jvc3MgZGlmZmVyZW50IHJ1bnMsIGFsbG93aW5nIGZvciBpZGVudGljYWwgZGF0YSBzcGxpdHMgYW5kIHRodXMsIHJlcHJvZHVjaWJsZSByZXN1bHRzLiAgDQoNCg0KIyMgT3RoZXIgQ3Jvc3MtVmFsaWRhdGlvbiBNZXRob2RzDQoNCkluIGFkZGl0aW9uIHRvIHRoZSBLLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiwgdGhlIHRhYmxlIGJlbG93IHByb3ZpZGVzIGEgY29tcHJlaGVuc2l2ZSBjb21wYXJpc29uIG9mIHRoZSBtb3N0IHBvcHVsYXIgdmFyaW91cyBjcm9zcy12YWxpZGF0aW9uIG1ldGhvZHMsIGhpZ2hsaWdodGluZyB0aGVpciB1bmlxdWUgZmVhdHVyZXMgYW5kIGJlc3QgdXNlIGNhc2VzIHRvIGd1aWRlIGRhdGEgc2NpZW5jZSBwcmFjdGl0aW9uZXJzIGluIHNlbGVjdGluZyB0aGUgbW9zdCBlZmZlY3RpdmUgdGVjaG5pcXVlIGZvciBzcGVjaWZpYyBtb2RlbGluZyBuZWVkcy4NCg0KKiAqKlN0cmF0aWZpZWQgSy1Gb2xkIENyb3NzLVZhbGlkYXRpb24qKgltYWludGFpbnMgdGhlIHNhbWUgcHJvcG9ydGlvbiBvZiBjbGFzcyBsYWJlbHMgaW4gZWFjaCBmb2xkIGFzIHRoZSBvcmlnaW5hbCBkYXRhIHNldC4JSXQgaXMgZ3JlYXQgZm9yIGNsYXNzaWZpY2F0aW9uIHRhc2tzIHdpdGggaW1iYWxhbmNlZCBjbGFzc2VzIHRvIG1haW50YWluIGdyb3VwIHByb3BvcnRpb25zLg0KDQoqICoqTGVhdmUtT25lLU91dCBDcm9zcy1WYWxpZGF0aW9uIChMT09DVikqKiBpcyBnb29kIGZvciBib3RoIHJlZ3Jlc3Npb24gYW5kIGNsYXNzaWZpY2F0aW9uLiAJRWFjaCBkYXRhIHBvaW50IGlzIHVzZWQgb25jZSBhcyBhIHRlc3Qgc2V0LCB3aXRoIHRoZSByZXN0IGFzIHRyYWluaW5nLglJdCBpcyBlc3BlY2lhbGx5IHVzZWZ1bCBmb3Igc21hbGwgZGF0YSBzZXRzIHRvIG1heGltaXplIHRyYWluaW5nIGRhdGEsIHRob3VnaCBjb21wdXRhdGlvbmFsbHkgaW50ZW5zaXZlLg0KDQoqICoqTGVhdmUtUC1PdXQgQ3Jvc3MtVmFsaWRhdGlvbioqIGlzIGdvb2QgZm9yIGJvdGggcmVncmVzc2lvbiBhbmQgY2xhc3NpZmljYXRpb24uCUl0IGlzIHNpbWlsYXIgdG8gKipMT09DVioqLCBidXQgbGVhdmVzIG91dCAkcCQgZGF0YSBwb2ludHMgZm9yIHRoZSB0ZXN0IHNldC4JSXQgaXMgaWRlYWwgZm9yIHNtYWxsIGRhdGEgc2V0cyB0byB0ZXN0IGhvdyBjaGFuZ2VzIGluIHRoZSBkYXRhIHNhbXBsZXMgYWZmZWN0IG1vZGVsIHN0YWJpbGl0eS4NCg0KKiAqKkdyb3VwIEstRm9sZCBDcm9zcy1WYWxpZGF0aW9uKiogaXMgZ29vZCBmb3IgYm90aCByZWdyZXNzaW9uIGFuZCBjbGFzc2lmaWNhdGlvbiB3aXRoIGdyb3Vwcy4gVGhpcyBtZXRob2QgZW5zdXJlcyBubyBncm91cCBpcyBpbiBib3RoIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMsIHdoaWNoIGlzIHVzZWZ1bCB3aGVuIGRhdGEgcG9pbnRzIGFyZSBub3QgaW5kZXBlbmRlbnQuIEl0IGlzIGFwcHJvcHJpYXRlIGZvciBkYXRhIHNldHMgd2l0aCBsb2dpY2FsIGdyb3VwaW5ncyB0byB0ZXN0IHBlcmZvcm1hbmNlIG9uIGluZGVwZW5kZW50IGdyb3Vwcy4NCg0KKiAqKlN0cmF0aWZpZWQgR3JvdXAgSy1Gb2xkIENyb3NzLVZhbGlkYXRpb24qKiBpcyBwcmltYXJpbHkgZm9yIGNsYXNzaWZpY2F0aW9uIHdpdGggZ3JvdXBlZCBkYXRhLiBJdCBjb21iaW5lcyBzdHJhdGlmaWNhdGlvbiBhbmQgZ3JvdXAgaW50ZWdyaXR5LCBlbnN1cmluZyB0aGF0IGdyb3VwcyBhcmUgbm90IHNwbGl0IGFjcm9zcyBmb2xkcy4gSXQgaXMgdXNlZnVsIGZvciBncm91cGVkIGFuZCBpbWJhbGFuY2VkIGRhdGEgc2V0cyB0byBtYWludGFpbiBib3RoIGNsYXNzIGFuZCBncm91cCBpbnRlZ3JpdHkuDQoNCg0KSy1Gb2xkIENyb3NzLVZhbGlkYXRpb24gaXMgYSBwb3dlcmZ1bCB0b29sIGZvciBldmFsdWF0aW5nIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLiBJdCdzIGJldHRlciB0aGFuIHRoZSBzaW1wbGUgVHJhaW4tVGVzdCBTcGxpdCBiZWNhdXNlIGl0IHRlc3RzIHRoZSBtb2RlbCBvbiB2YXJpb3VzIHBhcnRzIG9mIHRoZSBkYXRhLiBJdCBoZWxwcyBkYXRhIHByYWN0aXRpb25lcnMgdHJ1c3QgdGhhdCB0aGUgc2VsZWN0ZWQgZmluYWwgbW9kZWwgd2lsbCB3b3JrIHdlbGwgb24gdW5zZWVuIGRhdGEgdG9vLg0KDQoNCg0KIyBPcHRpbWFsIEN1dC1vZmYgUHJvYmFiaWxpdHkuDQoNClRoZSBvcHRpbWFsIHBvaW50IG9mIHRoZSBST0MgY3VydmUgaXMgYSBwb2ludCBvbiB0aGUgUk9DIGN1cnZlIHRoYXQgcHJvZHVjZXMgdGhlIGRlc2lyZWQgc2Vuc2l0aXZpdHkgKHRydWUgcG9zaXRpdmUgcmF0ZSAtIFRQUikgYW5kIHNwZWNpZmljaXR5ICh0cnVlIG5lZ2F0aXZlIHJhdGUgLSBUTlIpLiBJdCBpcyBkZXBlbmRlbnQgb24gc3BlY2lmaWMgYXBwbGljYXRpb25zLiBGb3IgZXhhbXBsZSwgaW4gY2xpbmljYWwgc3R1ZGllcywgYSBgZmFsc2UgbmVnYXRpdmVgIChvcHBvc2l0ZSBvZiB0cnVlIG5lZ2F0aXZlKSBjb3N0cyBhIGxvdCBtb3JlIHRoYW4gYSBgZmFsc2UgcG9zaXRpdmVgIChvcHBvc2l0ZSBvZiB0cnVlIHBvc2l0aXZlKS4gSW4gY3JlZGl0IGNhcmQgZnJhdWQgZGV0ZWN0aW9uLCBhIGhpZ2ggYGZhbHNlIHBvc2l0aXZlIHJhdGVgIChGUFIpIGNvdWxkIGNvc3QgYSBsb3QgbW9yZSB0aGFuIGEgaGlnaCBgZmFsc2UgbmVnYXRpdmUgcmF0ZWAgKEZOUikgZHVlIHRvIGZpbmFuY2lhbCByZWd1bGF0aW9ucyB0aGF0IHJlcXVpcmUgcmV2aWV3aW5nIGFsbCByZXBvcnRlZCBzdXNwaWNpb3VzIGZyYXVkdWxlbnQgdHJhbnNhY3Rpb25zLCBoZW5jZSwgY2F1c2luZyBoaWdoIG9wZXJhdGlvbmFsIGNvc3RzLiBObyBtYXR0ZXIgd2hhdCBvcHRpbWFsIHBvaW50IGlzIGRlZmluZWQsIHRoZXJlIGlzIGEgY29ycmVzcG9uZGluZyBjdXQtb2ZmIHByb2JhYmlsaXR5IHRoYXQgd2lsbCBiZSB1c2VkIHRvIHByZWRpY3QgdGhlIHZhbHVlIG9mIHRoZSByZXNwb25zZS4NCg0KQXMgYW4gaWxsdXN0cmF0aXZlIGV4YW1wbGUgKHRoYXQgbWF5IG5vdCBiZSBwcmFjdGljYWxseSBtZWFuaW5nZnVsKSwgd2Ugd2FudCB0aGUgY3V0LW9mZiBwcm9iYWJpbGl0eSBzdWNoIHRoYXQgdGhlIHNwZWNpZmljaXR5IGlzIGNsb3Nlc3QgdG8gdGhlIHNlbnNpdGl2aXR5LiBXZSB3aWxsIHVzZSB0aGUgc2FtZSBmcmF1ZCBkYXRhIHNldCBpbiB0aGUgZm9sbG93aW5nIGNvZGUuIE5vdGUgdGhhdCB0aGUgKipjdXQtb2ZmIHByb2JhYmlsaXR5IHNlYXJjaCoqIG11c3QgYmUgYmFzZWQgb24gdGhlIHRyYWluaW5nIGRhdGEgc2V0Lg0KDQpgYGB7ciAgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQ0KZnJhdWQuZGF0YSA9IHJlYWQuY3N2KCJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy9GcmF1ZEluZGV4L2ZyYXVkaWR4LmNzdiIpWywtMV0NCiMjIGVuY29kZSBzdGF0dXMgdmFyaWFibGU6IGJhZCA9IDEgYW5kIGdvb2QgPSAwDQpnb29kLmlkID0gd2hpY2goZnJhdWQuZGF0YSRzdGF0dXMgPT0gIiBnb29kIikgDQpiYWQuaWQgPSB3aGljaChmcmF1ZC5kYXRhJHN0YXR1cyA9PSAiZnJhdWQiKQ0KIyMNCmZyYXVkLmRhdGEkZnJhdWQuc3RhdHVzID0gMA0KZnJhdWQuZGF0YSRmcmF1ZC5zdGF0dXNbYmFkLmlkXSA9IDENCm5uID0gZGltKGZyYXVkLmRhdGEpWzFdDQp0cmFpbi5pZCA9IHNhbXBsZSgxOm5uLCByb3VuZChubiowLjcpLCByZXBsYWNlID0gRkFMU0UpIA0KdHJhaW5EYXQgPSBmcmF1ZC5kYXRhW3RyYWluLmlkLF0NCnRlc3REYXQgPSBmcmF1ZC5kYXRhWy10cmFpbi5pZCxdDQojIw0KbG9naXQubW9kZWwgPSBnbG0oZnJhdWQuc3RhdHVzIH4gaW5kZXgsIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSBsb2dpdCksIGRhdGEgPSB0cmFpbkRhdCkNCiMjIw0KbmV3ZGF0YSA9IGRhdGEuZnJhbWUoaW5kZXg9IHRlc3REYXQkaW5kZXgpDQpwcmVkLnByb2IudHJhaW4gPSBwcmVkaWN0LmdsbShsb2dpdC5tb2RlbCwgbmV3ZGF0YSwgdHlwZSA9ICJyZXNwb25zZSIpDQojIyB3ZSBuZXh0IHVzZSBsaWJyYXJ5IHtwUk9DfQ0KIyMgdG8gY2FsY3VsYXRlIEFVQw0KcHJlZGljdGlvbiA9IHByZWQucHJvYi50cmFpbg0KY2F0ZWdvcnkgPSB0ZXN0RGF0JGZyYXVkLnN0YXR1cyA9PSAxDQpST0NvYmogPC0gcm9jKGNhdGVnb3J5LCBwcmVkaWN0aW9uKQ0KIyMgDQpzZW4gPSBST0NvYmokc2Vuc2l0aXZpdGllcw0Kc3BlID0gUk9Db2JqJHNwZWNpZmljaXRpZXMNClNlbk1pbnVzU3BlID0gYWJzKHNlbi1zcGUpDQptaW5JRCA9IHdoaWNoKFNlbk1pbnVzU3BlID09IG1pbihTZW5NaW51c1NwZSkpDQpjdXQub2ZmLnByb2IgPSBST0NvYmokdGhyZXNob2xkcw0KIyMjDQpwbG90KCgxLXNwZSksIHNlbiwgbWFpbiA9ICJPcHRpbWFsIFJPQyBQb2ludCIsDQogICAgIHR5cGUgPSAibCIsDQogICAgIHlsYWIgPSAic2Vuc2l0aXZpdHkiLA0KICAgICB4bGFiID0gIjEgLSBzcGVjaWZpY2l0eSIpDQpzZWdtZW50cygwLCAwLCAxLCAxLCBsdHkgPSAyLCBjb2wgPSAicmVkIiwgbHdkID0gMikNCnNlZ21lbnRzKCgxLXNwZVttaW5JRF0pLCAxLXNwZVttaW5JRF0sICgxLXNwZVttaW5JRF0pLCBzZW5bbWluSURdLCBjb2w9InB1cnBsZSIpDQpwb2ludHMoKDEtc3BlW21pbklEXSksIHNlblttaW5JRF0sIHBjaD0xOSwgY29sID0gImRhcmtyZWQiLCBjZXggPSAxLjcpDQpwb2ludHMoKDEtc3BlW21pbklEXSksIHNlblttaW5JRF0sIHBjaD0xOSwgY29sID0gImdvbGQiLCBjZXggPSAxLjIpDQp0ZXh0KDAuNCwgc2VuW21pbklEXSwgcGFzdGUoIigiLHJvdW5kKHNwZVttaW5JRF0sNSksICIsICIsIHJvdW5kKHNlblttaW5JRF0sNSksICIpIiksIGNleCA9IDAuOCwgY29sID0gImJsdWUiKQ0KYGBgDQoNClRoZSBhYm92ZSBmaWd1cmUgc2hvd3MgdGhlIG9wdGltYWwgcG9pbnQgb24gdGhlIFJPQyBjdXJ2ZSB0aGF0IG1lZXRzIHRoZSByZXF1aXJlbWVudHMgb2Ygc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5LiBGb3Igb3RoZXIgb3B0aW1hbCBwb2ludHMgc3VjaCBhcyB0aGUgcG9pbnQgdGhhdCBtYXhpbWl6ZXMgdGhlIGFjY3VyYWN5IG9mIHRoZSBwcmVkaWN0aW9uLCB3ZSBjYW4gdXNlIHRoZSBzYW1lIHByb2Nlc3MgdG8gc2VhcmNoIHRoZSBjdXQtb2ZmIHByb2JhYmlsaXR5Lg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0K