1 Introduction

When crunching data to model business decisions, we are most typically using supervised and unsupervised learning methods.

Algorithms are often grouped by similarity in terms of their function (how they work). Again, there will be no perfect classification of machine learning algorithms and there is also no way to exhaust all machine learning algorithms. We only list those most commonly used algorithms here based on similarity to keep things simple.

For illustrative purposes, I will use some examples from the well-known Introduction to Statistical Learning: with Applications in R https://www.statlearning.com/.

This note will outline the supervised learning algorithms including statistical models and those developed by the machine learning community. We will build a decision tree model for the diabetes data set in the case study.

2 Model and Instance-based Supervised Algorithms

Machine learning can be broadly categorized into two types: instance-based learning and model-based learning. Both approaches have their unique methodologies and use cases.

Instance-based Learning

Instance-based learning, also known as lazy learning, involves storing the training data and using it to make predictions directly. The algorithm does not build an explicit model but relies on the entire data set to respond to queries.

Instance-based learning algorithms compare new problem instances with instances seen in training, which have been stored in memory. Predictions are made based on the similarity between new data points and stored instances.

Among instance-based learning algorithms, K-Nearest Neighbors (K-NN) is the most common instance-based learning algorithm. It classifies a data point based on how its neighbors are classified. The ‘K’ value represents the number of neighbors to consider.

The advantages of instance-based learning algorithms are their simplicity, adaptability, and versatility. The disadvantages are storage resource, prediction speed, and noisy data sensitivity.

Model-based Learning

Model-based learning, also known as eager learning, involves building a model from the training data before making predictions. This model captures the underlying patterns in the data, which can then be used to make predictions on new data. We have already learned a few of them

  • Linear Regression: This algorithm models the relationship between a dependent variable and one or more independent variables by fitting a linear equation to the observed data.

  • Logistic Regression: A classification algorithm that models the probability of a binary outcome based on one or more predictor variables.

  • Neural Networks: Complex models inspired by the human brain, capable of capturing intricate patterns in the data through layers of interconnected nodes.

  • Decision Trees: These models use tree-like structures where nodes represent decisions based on the value of input features, leading to an output prediction. This note will discuss tree-based algorithms.

The next table compares the two categories of algorithms from different perspectives.

include_graphics("img/InstabceModel-basedLearning.jpg")
The comparison of instance-based and model-based supervised learning algorithms

The comparison of instance-based and model-based supervised learning algorithms

In the next subsections, we will provide some of the instance-based and model-based algorithms with some illustrative examples (based on small data sets) to explain the rough ideas of these algorithms.


2.1 LOESS Regression

Regression is concerned with modeling the relationship between variables that is iteratively refined using a performance measure defined based on errors in the predictions made by the model. Regression methods are a workhorse of statistics and have been the backbone of statistical machine learning. The most popular regression algorithms are:

  1. Ordinary Least Squares Regression (OLSR)

  2. Linear Regression

  3. Logistic Regression

  4. Step-wise Regression

  5. LOcally Estimated Scatter-plot Smoothing (LOESS) - a nonparametric regression.

In this subsection, we will use an example to show one of the robust distribution free single variable regression algorithm - locally weighted scatter-plot smoothing (LOESS) - using a built-in R function (without giving the details of the algorithm). Since it is a single variable regression, the performance of the performance can be easily visualized.

The LOESS regression is based on two variables Sales and Price in the data set Carseats in the book ISLR.

data("Carseats")
pander(head(Carseats))
Table continues below
Sales CompPrice Income Advertising Population Price ShelveLoc
9.5 138 73 11 276 120 Bad
11.22 111 48 16 260 83 Good
10.06 113 35 10 269 80 Medium
7.4 117 100 4 466 97 Medium
4.15 141 64 3 340 128 Bad
10.81 124 113 13 501 72 Bad
Age Education Urban US
42 17 Yes Yes
65 10 Yes Yes
59 12 Yes Yes
55 14 Yes Yes
38 13 Yes No
78 16 No Yes
lw1 = loess(Sales ~ Price, data = Carseats)
plot(Sales ~ Price, data = Carseats, pch=19, cex=0.8)
j = order(Carseats$Price)  # sort the data vector and returns the index 
                           # of the values of the original data vector
lines(Carseats$Price[j],lw1$fitted[j],col="red",lwd=3)
Figure 5. LOESS regression: Sales ~ Prices

Figure 5. LOESS regression: Sales ~ Prices

The loess() is a data-driven nonparametric regression (local polynomial regression including linear regression), in other words, the explicit model parameters in an explicitly expressed model to estimate. loess regression is analogous to single variable regression such as simple linear and nonlinear regression models.

To plot the smooth fitted regression curve, we need to use function order() the indices of the original data vector after it was ordered. For example,

x = c(3, 1, 0, 4, -5)
order(x)
[1] 5 3 2 1 4
## The above index can sort the data below
x[order(x)]
[1] -5  0  1  3  4

We can use the loess nonparametric regression model to predict as usual using the generic function predict() with an input data frame.

lw1 = loess(Sales ~ Price, data = Carseats)
predict(lw1, data.frame(Price =c(134,121)),se = TRUE)  # new data must be within 
$fit
[1] 6.751464 7.371896

$se.fit
[1] 0.2261343 0.2216638

$residual.scale
[1] 2.518948

$df
[1] 393.7244
                                                       # Existing price range

2.2 Regularized Regression

We have discussed some degree of detail in linear and logistic regression models from both classical statistics perspective and machine learning perspective in terms of model training and performance evaluation.

The following regularized regression algorithms are recently developed learning algorithms modified from classical statistics.

  • Ridge Regression

Ridge regression does not reduce the number of correlated feature variables. It brings bias to the estimated regression coefficients to reduce the impact of multi-correlated feature variables. In other words, it sacrifices the unbiasedness of the estimated regression to gain the stability of the estimation.

  • Least Absolute Shrinkage and Selection Operator (LASSO)

LASSO filters the feature variables with a small magnitude of the absolute regression coefficients. All numerical feature variables must be standardized when using LASSO regression for prediction. Since some of the feature variables will be filtered out from the model. It is considered a dimension reduction method that has become a popular tool in the machine learning community.

The following figure explains the relationships between regular least square regression, ridge regression, and LASSO.

Figure 6.  Relationship between least square, ridge, and LASSO regressions.

Figure 6. Relationship between least square, ridge, and LASSO regressions.

Because both ridge and LASSO fall into the same theoretical framework (although functioning in very different ways), Stanford statisticians developed an R library, glmnet to implement various regularized regression including both of these two regularized regression methods.


2.3 Instance-based Algorithms

First of all, observations/samples/instances all mean the same thing in machine learning.

The instance-based learning model is a decision problem with instances or examples of training data that are deemed important or required to the model. Such methods typically build up a database of example data and compare new data to the database using a similarity measure in order to find the best match and make a prediction. For this reason, instance-based methods are also called winner-take-all methods and memory-based learning (sometimes also called lazy learning. Focus is put on the representation of the stored instances and similarity measures used between instances.

The most popular instance-based algorithms are:

  • k-Nearest Neighbor (kNN)
  • Support Vector Machines (SVM)
Figure 7.  Instance-based learning algorithms: KNN and SVM.

Figure 7. Instance-based learning algorithms: KNN and SVM.

Both kNN and SVM are intuitive. The more instances the more the accuracy. ISLR (2nd edition) has case studies using R for kNN (using knn() in library {class} in section 4.7.6, starting from page 181) and SVM (using svm() in library {e1071} in sections 9.6.1 and 9.6.2, starting from page 389).


2.4 Naïve Bayes - A Bayesian Algorithm

Bayesian methods are those that explicitly apply Bayes’ Theorem for problems such as classification and regression. There several Bayesian algorithms have been developed so far. We only introduce the basic but commonly used in practice - Naïve Bayes.

The Naïve Bayes classifier is a simple probabilistic classifier that is based on the Bayes theorem but with strong assumptions regarding independence. Historically, this technique became popular with applications in email filtering, spam detection, and document categorization. Although it is often outperformed by other techniques, and despite the naïve design and oversimplified assumptions, this classifier can perform well in many complex real-world problems.

The theory behind Naïve Bayes is straightforward as depicted in the following.

Figure 14.  Naive Bayes Classifier.

Figure 14. Naive Bayes Classifier.

There are several libraries in R that have the function to implement aïve Bayes. ISLR has a lab on the application of aïve Bayes (section 4.7.5, starting from page 180) using the naiveBayes() function in R library {e1071}.


3 Decision Tree Algorithms

The Decision Tree (DT) algorithm is based on conditional probabilities. Unlike the other classification algorithms, decision trees generate rules. A rule is a conditional statement that can easily be understood by humans and easily used within a database to identify a set of records. It is easy to interpret and implement in real-world applications. Among several basic tree-based algorithms, Classification and Regression Tree (CART) is most frequently used in practice.

This subsection focuses on the basic decision tree with some technical description of steps in decision tree induction. The general structure of a decision tree algorithm is in the following example of predicting the survival of Titanic passengers.

Figure 8.  Illustration of decision tree algorithm: predicting Titanic survival.

Figure 8. Illustration of decision tree algorithm: predicting Titanic survival.

The above decision tree involves three variables: sex, age, and sibsp (sibling and spouse). We can easily convert the tree to a set of rules (conditional statements) to make a prediction of the survival status for a new incoming data point.

3.1 Structure and Technical Terms

The following diagram illustrates the basic structure of a decision tree.

Figure 9. Decision tree structure.

Figure 9. Decision tree structure.

Root Node: It represents the entire population or sample and this further gets divided into two or more homogeneous sets.

Splitting: It is a process of dividing a node into two or more sub-nodes.

Decision Node: When a sub-node splits into further sub-nodes, then it is called the decision node.

Leaf / Terminal Node: Nodes that do not split are called Leaf or Terminal Node.

Pruning: When we remove sub-nodes of a decision node, this process is called pruning. We can say the opposite process of splitting.

Branch / Sub-Tree: A subsection of the entire tree is called a branch or sub-tree.

Parent and Child Node: A node, which is divided into sub-nodes is called a parent node of sub-nodes whereas sub-nodes are the child of a parent node.


The following example based on toy data illustrates how a decision grows and how to use a decision tree to make predictions.

The toy data set is given below.

Figure 10.  Decision tree structure using a toy data.

Figure 10. Decision tree structure using a toy data.

The fully grown tree is given below (note the variable class is the binary response variable).

Figure 11.  Fully grown decision tree using a toy data.

Figure 11. Fully grown decision tree using a toy data.

DataSet = data.frame(
Age = c("Youth", "Youth", "Middle_aged", "Senior", "Senior","Senior","Middle_aged","Youth","Youth","Senior","Youth","Middle_aged",
        "Middle_aged","Senior"),
Income = c("High","High","High","Medium","Low", "Low","Low","Medium","Low","Medium","Medium","Medium","High","Medium"),
Student = c("No", "No","No","No","Yes","Yes","Yes","No","Yes","Yes","Yes",
            "No","Yes","No"),
CreditRating = c("Fair", "Excellent","Fair","Fair","Fair","Excellent",
                 "Excellent","Fair","Fair","Fair","Excellent","Excellent",
                 "Fair","Excellent"),
Class = c("No", "No", "Yes", "Yes","Yes","No","Yes", "No", "Yes","Yes",
          "Yes","Yes","Yes","No")
)
#pander(DataSet)

3.2 Decision Tree Growing - Impurity Measures

Growing a decision tree is an iterative process of splitting the feature space into some sub-spaces according to certain criteria defined based on feature variables. The predictive performance of a decision is dependent on the size of the trained tree. A small size will cause underfitting issues and a large size will result in overfitting issues.

The questions are (1) how to control the size of a decision to obtain the best performance; (2) how to select the feature variables to define the root and subsequent child nodes; (3) how to split a feature variable.

Gini index and entropy are the two popular impurity measures commonly used in decision tree induction.


3.2.1 Gini Index

  • Gini Index considers a split for each attribute (for a continuous attribute, usually considers binary split). The Gini Index measures the impurity of subgroups (D) split by a feature variable.

\[ \mbox{Gini}(D)= \sum_{i=1}^m p_i(1-p_i)= 1 - \sum_{i=1}^m p_i^2 \] Where \(p_i\) is the probability of an object that is being classified to a particular class.

For example, we calculate the Gini index using the above decision tree. The root node (age) has three child nodes. We show how to calculate the weighted Gini index of feature variable age in the following steps.

  • Weights: P(youth) = 5/14, P(middle_aged) = 4/14, P(senior) = 5/14.

  • D = Youth: \(p_1 =P(Yes) = 2/5, p_2 = P(No) = 3/5\), therefore, \({Gini}_{youth} = 1 -p_1^2 - p_2^2 = 1 - 4/25 - 9/25 = 12/25\)

  • D = Middle_aged: \(p_1 =P(Yes) = 4/4, p_2 = P(No) = 0/45\), therefore, \({Gini}_{middle_aged} = 1 -p_1^2 - p_2^2 = 1 - 16/16 - 0/16 = 0\)

  • D = Senior: \(p_1 =P(Yes) = 3/5, p_2 = P(No) = 2/5\), therefore, \({Gini}_{senior} = 1 -p_1^2 - p_2^2 = 1 - 9/25 - 4/25 = 12/25\)

The Gini index of age is given by

\[ {Gini}_{age} =\frac{5}{14}\times\frac{12}{25} + \frac{4}{14}\times 0 +\frac{5}{14}\times \frac{12}{25} = \frac{5}{14}\times \frac{24}{25} = \frac{12}{35} \approx 0.343. \]


R Function for GINI Index

GINI.calc = function(DatName, VarName, ClsName){
   # 
   freqTB0 = table(DatName[,VarName], DatName[,ClsName])
   freqTB = data.frame(NO = freqTB0[, 1], YES = freqTB0[, 2])
   freqTB$Tot = freqTB$NO + freqTB$YES
   freqTB$P1 = freqTB$NO/freqTB$Tot
   freqTB$P2 = freqTB$YES/freqTB$Tot
   freqTB$CateGINI = 1-(freqTB$P1)^2 - (freqTB$P2)^2
   freqTB$ROWPER = (freqTB$NO + freqTB$YES)/sum(freqTB$Tot)
   freqTB$ComponentGini = (freqTB$CateGINI) * (freqTB$ROWPER)
   GINI.idx = sum(freqTB$ComponentGini)
   GINI.idx
}


giniAge = GINI.calc(DatName=DataSet, VarName="Age", ClsName = "Class") 
giniIncome = GINI.calc(DatName=DataSet, VarName="Income", ClsName = "Class") 
giniStudent = GINI.calc(DatName=DataSet, VarName="Student", ClsName = "Class") 
giniCreditRating = GINI.calc(DatName=DataSet, VarName="CreditRating",
                             ClsName = "Class") 
pander(cbind(giniAge = giniAge, giniIncome = giniIncome, 
             giniStudent = giniStudent, giniCreditRating = giniCreditRating))
giniAge giniIncome giniStudent giniCreditRating
0.3429 0.4405 0.3673 0.4286

We can similarly calculate the Gini index for other feature variables in the data set. When we choose a feature variable to define the root node, we choose the feature with smallest Gini index. The Gini index is used in the classic CART algorithm and is very easy to calculate.

3.2.2 Entropy and Information Gain

Entropy is another impurity measure that is defined by

\[ E = \sum_{i = 1}^m (-p_i \log_2p_i). \] Where \(p_i\) is the same as that defined in the Gini index. We can find the entropy at the root node and each child node based on the above tree based on the toy data. A low Entropy indicates that the data labels are quite uniform.

  • root (parent) node entropy (before splitting): \(E(\mbox{D}) = -(5/14)\log_2(5/14) - (9/14)\log_2(9/14) = 0.940286\)

  • child node: youth: \(E(\mbox{D}) = -(2/5)\log_2(2/5) - (3/5)\log_2(3/5) = 0.9709506\)

  • child node: middle_aged: perfectly pure node had entropy 0.

  • child node: senior: \(E(\mbox{D}) = -(3/5)\log_3(2/5) - (2/5)\log_2(3/5) = 0.9709506\)

  • Weighted average of entropy at child nodes: \(E(\mbox{child}) = \frac{5}{14}\times 0.9709506 + \frac{4}{14}\times 0 +\frac{5}{14}\times 0.9709506 = \frac{5}{14}\times 0.9709506 \approx 0.3467681\)


  • Information Gain: \(\mbox{InfoGain} = E(\mbox{Parent Node}) - E(\mbox{Child Nodes}) = 0.940286 - 0.3467681 = 0.5935179.\)


Information gain measures whether a further split is worthwhile.

infoGain.calc = function(DatName, VarName, ClsName){
   freqTB0 = table(DatName[,VarName], DatName[,ClsName])
   freqTB = data.frame(NO = freqTB0[, 1], YES = freqTB0[, 2])
   freqTB$Tot = freqTB$NO + freqTB$YES
   freqTB$P1 = freqTB$NO/freqTB$Tot
   freqTB$P2 = freqTB$YES/freqTB$Tot
   ###
   freqTB$ROWPER = (freqTB$NO + freqTB$YES)/sum(freqTB$Tot)
   ### Delete zero cell prob to calculate the entropy
   pNO = sum(freqTB$NO)/sum(freqTB$Tot)
   pYES = sum(freqTB$YES)/sum(freqTB$Tot)
   propYES = freqTB$YES/sum(freqTB$Tot)
   ParentEnt = -pNO*log2(pNO) -pYES *log2(pYES) 
   ### entropy of child nodes
   P1 = freqTB$P1
   P2 = freqTB$P2
   logP1 = log2(freqTB$P1)
   logP2 = log2(freqTB$P2)
   logP1[which(!is.finite(logP1))] = 0
   logP2[which(!is.finite(logP2))] = 0
   ChildEnt = (-P1*logP1 - P2*logP2)
   ###
   infoGain =ParentEnt - sum(ChildEnt*propYES)
   #info.Gain = sum(freqTB$infoGain)
   list(ParentEnt = ParentEnt, ChildEnt = ChildEnt, 
        propYES = propYES, infoGain = infoGain)  
}
infoGain.calc(DatName=DataSet, VarName="Age", ClsName = "Class") 
$ParentEnt
[1] 0.940286

$ChildEnt
[1] 0.0000000 0.9709506 0.9709506

$propYES
[1] 0.2857143 0.2142857 0.1428571

$infoGain
[1] 0.5935179
entAge = infoGain.calc(DatName=DataSet, VarName="Age", ClsName = "Class")$infoGain 
entIncome = infoGain.calc(DatName=DataSet, VarName="Income", ClsName = "Class")$infoGain 
entStudent = infoGain.calc(DatName=DataSet, VarName="Student", ClsName = "Class")$infoGain 
entCreditRating = infoGain.calc(DatName=DataSet, VarName="CreditRating", ClsName = "Class")$infoGain 
pander(cbind(infoGainAge = entAge, infoGainIncome = entIncome, infoGainStudent = entStudent, infoGainCreditRating = entCreditRating))
infoGainAge infoGainIncome infoGainStudent infoGainCreditRating
0.5935 0.3612 0.4756 0.3783


3.3 Binary v.s. Multi-way Splits

In principle, trees are not restricted to binary splits but can also be grown with multi-way splits - based on the Gini index or other selection criteria. However, the (locally optimal) search for multi-way splits in numeric variables would become much more burdensome. Hence, tree algorithms often rely on the greedy forward selection of binary splits where subsequent binary splits in the same variable can also represent multi-way splits.


3.4 Boosted Trees - Ensemble Algorithms

Ensemble methods are models composed of multiple weaker models that are independently trained and whose predictions are combined in some way to make the overall prediction.

Much effort is put into what types of weak learners to combine and the ways in which to combine them. This is a very powerful class of techniques and as such is very popular.

3.4.1 Bootstrapped Aggregation (Bagging)

With the understanding of regular decisions, we can

Figure 12.  Demonstration of bootstrap aggregation algorithm.

Figure 12. Demonstration of bootstrap aggregation algorithm.

3.4.2 Random Forest

Random forest (RF) algorithms make output predictions by combining outcomes from a sequence of decision trees. Each tree is constructed independently and depends on a random vector sampled from the input data, with all the trees in the forest having the same distribution. The predictions from the forests are averaged using bootstrap aggregation and random feature selection. RF models have been demonstrated to be robust predictors for both small sample sizes and high dimensional data.

The following diagram illustrates how RF was constructed and how the decision is made based on the set of individual trees.

Figure 13.  Demonstration of random forest algorithm.

Figure 13. Demonstration of random forest algorithm.

4 Case Study - Predicting Diabetes

This is a new model that is different from logistic and neural network models. We load the analytic data set.

Pima = read.csv("https://pengdsci.github.io/STA551/w09/AnalyticPimaDiabetes.csv")[,-1]
# We use a random split approach
n = dim(Pima)[1]  # sample size
# caution: using without replacement
train.id = sample(1:n, round(0.7*n), replace = FALSE)  
train = Pima[train.id, ]    # training data
test = Pima[-train.id, ]    # testing data

4.1 rpart Library

we will rpart() to write a wrapper so we can pass the arguments of purity measures and penalty measures to construct different decision trees. The cross-validation method will be used to select the optimal decision tree as the candidate predictive to compare with the logistic model in the previous section.

Note that rpart() has a control option that allows users to set up various control parameters (so-called hyper-parameters) to allow the function to identify the optimal tree. One of those control parameters is the number of cross-validation when pruning the decision. Once xval is specified, rpart() prunes the tree automatically based on the given control parameters. This is internal k-fold cross-validation for identifying an optimal tree based on the information provided in the argument parms in which the purity measures and penalty matrix. More information can be found in the article https://cran.r-project.org/web/packages/rpart/vignettes/longintro.pdf


rpart() syntax

 tree = rpart(modelFormula,          # model formula similar to that in the logistic models
             data , 
             na.action  = na.rpart,  # By default, deleted if the outcome is missing, 
                                     # kept if features are missing
             method = "class",       # Classification form factor
             model  = FALSE,         # keep a copy of the model frame in the result? I
                  x = FALSE,         # keep a copy of the x matrix in the result.
                  y = TRUE,          # keep a copy of the dependent variable in the result. 
                                     # If missing and model is supplied this defaults to FALSE
              parms = list( # loss matrix. Penalize false positive or negative more heavily
                         loss = matrix(c(0,b,c,0), ncol = 2),  # b = FP, c = FN
                         split = purity),   # "gini" or "information"
             
             ## rpart algorithm options (These are defaults)
             control = rpart.control(
                       minsplit = 20,       # minimum number of observations required before split
                       minbucket= 10,       # minimum number of observations in any terminal node,                                                      # default = minsplit/3
                       cp  = 0.01,          # complexity parameter used as the stopping rule,  
                                            # 0.02 -> small tree
                       maxcompete  = 4,     # number of competitor splits retained in the output
                       maxsurrogate   = 5,  # number of surrogate splits retained in the output
                       usesurrogate   = 2,  # how to use surrogates in the splitting process
                       xval   = 10,         # number of cross-validations
                       surrogatestyle = 0,  # controls the selection of the best surrogate
                       maxdepth  = 30       # maximum depth of any node of the final tree)
      )


rpart() has a lot of flexibility to construct decision trees as it has user controls. It is particularly useful in applications where the costs of false positive and false negative are different.

Next, we write a wrapper so we can build different decision trees conveniently.

# arguments to pass into rpart():
# 1. data set (training /testing); 
# 2. Penalty coefficients
# 3. Impurity measure
## 
tree.builder = function(in.data, fp, fn, purity){
   tree = rpart(diabetes ~ .,                # including all features
                data = in.data, 
                na.action  = na.rpart,       # By default, deleted if the outcome is missing, 
                                             # kept if predictors are missing
                method = "class",            # Classification form factor
                model  = FALSE,
                x = FALSE,
                y = TRUE,
            parms = list( # loss matrix. Penalize false positives or negatives more heavily
                         loss = matrix(c(0, fp, fn, 0), ncol = 2, byrow = TRUE),   
                         split = purity),          # "gini" or "information"
             ## rpart algorithm options (These are defaults)
             control = rpart.control(
                        minsplit = 10,  # minimum number of observations required before split
                        minbucket= 10,  # minimum number of observations in 
                                        # any terminal node, default = minsplit/3
                        cp  = 0.01,  # complexity parameter for stopping rule, 
                                     # 0.02 -> small tree 
                       xval = 10     # number of cross-validation )
                        )
             )
  }

Using the above function, we define six different decision tree models in the following.

  • Model 1: gini.tree.11 is based on the Gini index without penalizing false positives and false negatives.

  • Model 2: info.tree.11 is based on entropy without penalizing false positives and false negatives.

  • Model 3: gini.tree.110 is based on the Gini index: the cost of false negatives is 10 times the positives.

  • Model 4: info.tree.110 is based on entropy: the cost of false negatives is 10 times the positives.

  • Model 5: gini.tree.101 is based on the Gini index: the cost of a false positive is 10 times the negatives.

  • Model 6: info.tree.101 is based on entropy: the cost of a false positive is 10 times the negatives.

The tree diagram of the above two regular decision models is given below.

## Call the tree model wrapper.
gini.tree.1.1 = tree.builder(in.data = train, fp = 1, fn = 1, purity = "gini")
info.tree.1.1 = tree.builder(in.data = train, fp = 1, fn = 1, purity = "information")
gini.tree.1.10 = tree.builder(in.data = train, fp = 1, fn = 10, purity = "gini")
info.tree.1.10 = tree.builder(in.data = train, fp = 1, fn = 10, purity = "information")
## tree plots
par(mfrow=c(1,2))
rpart.plot(gini.tree.1.1, main = "Tree with Gini index: non-penalization")
rpart.plot(info.tree.1.1, main = "Tree with entropy: non-penalization")
Figure 14. Non-penalized decision tree models using Gini index (left) and entropy (right).

Figure 14. Non-penalized decision tree models using Gini index (left) and entropy (right).

par(mfrow=c(1,2))
rpart.plot(gini.tree.1.10, main = "Tree with Gini index: penalization")
rpart.plot(info.tree.1.10, main = "Tree with entropy: penalization")
Figure 15. penalized decision tree models using Gini index (left) and entropy (right).

Figure 15. penalized decision tree models using Gini index (left) and entropy (right).

4.2 ROC for Model Selection

We built 4 different decision tree models previously. Next, we use ROC analysis to select the best among the four candidate models.

# function returning a sensitivity and specificity matrix
SenSpe = function(in.data, fp, fn, purity){
  cutoff = seq(0,1, length = 20)   # 20 cut-offs including 0 and 1. 
  model = tree.builder(in.data, fp, fn, purity) 
  ## Caution: decision tree returns both "success" and "failure" probabilities.
  ## We need only "success" probability to define sensitivity and specificity!!! 
  pred = predict(model, newdata = in.data, type = "prob") # two-column matrix.
  senspe.mtx = matrix(0, ncol = length(cutoff), nrow= 2, byrow = FALSE)
  for (i in 1:length(cutoff)){
  # CAUTION: "pos" and "neg" are values of the label in this data set!
  # The following line uses only "pos" probability: pred[, "pos"] !!!!
  pred.out =  ifelse(pred[,"pos"] >= cutoff[i], "pos", "neg")  
  TP = sum(pred.out =="pos" & in.data$diabetes == "pos")
  TN = sum(pred.out =="neg" & in.data$diabetes == "neg")
  FP = sum(pred.out =="pos" & in.data$diabetes == "neg")
  FN = sum(pred.out =="neg" & in.data$diabetes == "pos")
  senspe.mtx[1,i] = TP/(TP + FN)
  senspe.mtx[2,i] = TN/(TN + FP)
  }
  ## A better approx of ROC, need library {pROC}
  prediction = pred[, "pos"]
  category = in.data$diabetes == "pos"
  ROCobj <- roc(category, prediction)
  AUC = auc(ROCobj)
  ##
  list(senspe.mtx= senspe.mtx, AUC = round(AUC,3))
 }

The above function has three arguments for users to choose different types of decision trees including the 4 trees discussed in the previous subsection. Next, we use this function to build 6 different trees and plot their corresponding ROC curves so we can see the global performance of these tree algorithms.

giniROC11 = SenSpe(in.data = train, fp=1, fn=1, purity="gini")
infoROC11 = SenSpe(in.data = train, fp=1, fn=1, purity="information")
giniROC110 = SenSpe(in.data = train, fp=1, fn=10, purity="gini")
infoROC110 = SenSpe(in.data = train, fp=1, fn=10, purity="information")
giniROC101 = SenSpe(in.data = train, fp=10, fn=1, purity="gini")
infoROC101 = SenSpe(in.data = train, fp=10, fn=1, purity="information")

Next, we plot the ROC curves and calculate the areas under the ROC curves for Individual decision tree models.

par(pty="s")      # set up square plot through graphic parameter
colors = c("#008B8B", "#00008B",  "#8B008B",  "#8B0000",  "#8B8B00", "#8B4500")
plot(1-giniROC11$senspe.mtx[2,], giniROC11$senspe.mtx[1,], 
     type = "l", 
     xlim=c(0,1), 
     ylim=c(0,1), 
     xlab="1 - specificity: FPR", ylab="Sensitivity: TPR", 
     col = colors[1], 
     lwd = 2,
     main="ROC Curves of Decision Trees", 
     cex.main = 0.9, 
     col.main = "navy")
abline(0,1, lty = 2, col = "orchid4", lwd = 2)
lines(1-infoROC11$senspe.mtx[2,], infoROC11$senspe.mtx[1,], 
      col = colors[2], lwd = 2, lty=2)
lines(1-giniROC110$senspe.mtx[2,], giniROC110$senspe.mtx[1,],
      col = colors[3], lwd = 2)
lines(1-infoROC110$senspe.mtx[2,], infoROC110$senspe.mtx[1,], 
      col = colors[4], lwd = 2, lty=2)
lines(1-giniROC101$senspe.mtx[2,], giniROC101$senspe.mtx[1,], 
      col = colors[5], lwd = 2, lty = 4)
lines(1-infoROC101$senspe.mtx[2,], infoROC101$senspe.mtx[1,], 
      col = colors[6], lwd = 2, lty=2)
legend("bottomright", c(paste("gini.1.1,  AUC =", giniROC11$AUC), 
                        paste("info.1.1,  AUC =",infoROC11$AUC), 
                        paste("gini.1.10, AUC =",giniROC110$AUC), 
                        paste("info.1.10, AUC =",infoROC110$AUC),
                        paste("gini.10.1, AUC =",giniROC101$AUC), 
                        paste("info.10.1, AUC =",infoROC101$AUC)),
                        col=colors, 
                        lty=rep(1:2,3), lwd=rep(2,6), cex = 0.8, bty = "n")
Figure 16. Comparison of ROC curves

Figure 16. Comparison of ROC curves

The above ROC curves represent various decision trees and their corresponding AUC. The model with the largest AUC is considered the best decision tree among the existing ones.


4.3 Optimal Cut-off Score Determination

As usual, once the final model is determined, we need to find the optimal cut-off score for reporting the predictive performance of the final model with the test data. Please keep in mind the optimal cut-off determination through cross-validation must be based on the training data set.

In practical applications, one may end up with two or more final models with similar AUCs. In this case, we need to report the performance of all final models based on the test data and let clients choose one to deploy (and possibly leave the rest as challengers). For this reason, we write a function to determine the optimal cut-off for a given decision tree (based on this project) since different decision trees have their own optimal cut-off.

Optm.cutoff = function(in.data, fp, fn, purity){
  n0 = dim(in.data)[1]/5
  cutoff = seq(0,1, length = 20)               # candidate cut off prob
  ## accuracy for each candidate cut-off
  accuracy.mtx = matrix(0, ncol=20, nrow=5)    # 20 candidate cutoffs and gini.11
  ##
  for (k in 1:5){
     valid.id = ((k-1)*n0 + 1):(k*n0)
     valid.dat = in.data[valid.id,]
     train.dat = in.data[-valid.id,] 
     ## tree model
     tree.model = tree.builder(in.data, fp, fn, purity)
     ## prediction 
     pred = predict(tree.model, newdata = valid.dat, type = "prob")[,2]
     ## for-loop
     for (i in 1:20){
        ## predicted probabilities
        pc.1 = ifelse(pred > cutoff[i], "pos", "neg")
        ## accuracy
        a1 = mean(pc.1 == valid.dat$diabetes)
        accuracy.mtx[k,i] = a1
       }
      }
   avg.acc = apply(accuracy.mtx, 2, mean)
   ## plots
   n = length(avg.acc)
   idx = which(avg.acc == max(avg.acc))
   tick.label = as.character(round(cutoff,2))
   ##
   plot(1:n, avg.acc, xlab="cut-off score", ylab="average accuracy", 
        ylim=c(min(avg.acc), 1), 
        axes = FALSE,
        main=paste("5-fold CV optimal cut-off \n ",purity,"(fp, fn) = (", fp, ",", fn,")" , 
                   collapse = ""),
        cex.main = 0.9,
        col.main = "navy")
        axis(1, at=1:20, label = tick.label, las = 2)
        axis(2)
        points(idx, avg.acc[idx], pch=19, col = "red")
        segments(idx , min(avg.acc), idx , avg.acc[idx ], col = "red")
       text(idx, avg.acc[idx]+0.03, as.character(round(avg.acc[idx],4)), 
            col = "red", cex = 0.8) 
   }

For demonstration, we use the above function to calculate the optimal cut-off of 6 decision trees constructed earlier in the following.

par(mfrow=c(3,2))
Optm.cutoff(in.data = train, fp=1, fn=1, purity="gini")
Optm.cutoff(in.data = train, fp=1, fn=1, purity="information")
Optm.cutoff(in.data = train, fp=1, fn=10, purity="gini")
Optm.cutoff(in.data = train, fp=1, fn=10, purity="information")
Optm.cutoff(in.data = train, fp=10, fn=1, purity="gini")
Optm.cutoff(in.data = train, fp=10, fn=1, purity="information")
Figure 17: Plot of optimal cut-off determination

Figure 17: Plot of optimal cut-off determination

As anticipated, different trees have their own optimal cut-off. Please keep in mind that the cut-off is random (based on the randomly split training data), there may be different cut-offs in different runs. It is dependent on the tree size, sometimes, we may end up with multiple optimal cut-offs. Technically speaking, we choose any one of them for implementation. A better recommendation is to choose the average of these multiple cut-offs and the final cut-off to be used on the testing data set.

LS0tDQp0aXRsZTogIkFuIE92ZXJ2aWV3IG9mIENvbW1vbmx5IFVzZWQgU3VvZXJ2aXNlZCBNTCBBbGdvcml0aG1zIg0KYXV0aG9yOiAiQ2hlbmcgUGVuZyINCmRhdGU6ICJTVEEgNTUxIEZvdW5kYXRpb24gb2YgRGF0YSBTY2llbmNlICINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIGZpZ193aWR0aDogNg0KICAgIGZpZ19oZWlnaHQ6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2NfY29sbGFwc2VkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICB0aGVtZTogbHVtZW4NCiAgd29yZF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAga2VlcF9tZDogeWVzDQogIHBkZl9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBmaWdfd2lkdGg6IDUNCiAgICBmaWdfaGVpZ2h0OiA0DQotLS0NCg0KYGBgez1odG1sfQ0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KZGl2I1RPQyBsaSB7DQogICAgbGlzdC1zdHlsZTpub25lOw0KICAgIGJhY2tncm91bmQtaW1hZ2U6bm9uZTsNCiAgICBiYWNrZ3JvdW5kLXJlcGVhdDpub25lOw0KICAgIGJhY2tncm91bmQtcG9zaXRpb246MDsNCn0NCg0KaDEudGl0bGUgew0KICBmb250LXNpemU6IDIwcHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuZGF0ZSB7IC8qIEhlYWRlciA0IC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICBjb2xvcjogRGFya0JsdWU7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCmgxIHsgLyogSGVhZGVyIDMgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDIycHg7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IGRhcmtyZWQ7DQogICAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDIgeyAvKiBIZWFkZXIgMyAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoMyB7IC8qIEhlYWRlciAzIC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogICAgZm9udC1zaXplOiAxNnB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOw0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmg0IHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE0cHg7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IGRhcmtyZWQ7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCjwvc3R5bGU+DQpgYGANCg0KICANCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBjb2RlIGNodW5rIHNwZWNpZmllcyB3aGV0aGVyIHRoZSBSIGNvZGUsIHdhcm5pbmdzLCBhbmQgb3V0cHV0IA0KIyB3aWxsIGJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQgZmlsZXMuDQoNCmlmICghcmVxdWlyZSgiZ2dwbG90MiIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCiAgIGxpYnJhcnkoZ2dwbG90MikNCn0NCmlmICghcmVxdWlyZSgiSVNMUiIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJJU0xSIikNCiAgIGxpYnJhcnkoSVNMUikNCn0NCmlmICghcmVxdWlyZSgia25pdHIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKQ0KICAgbGlicmFyeShrbml0cikNCn0NCmlmICghcmVxdWlyZSgiSVNMUiIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJJU0xSIikNCiAgIGxpYnJhcnkoSVNMUikNCn0NCmlmICghcmVxdWlyZSgicGFuZGVyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInBhbmRlciIpDQogICBsaWJyYXJ5KHBhbmRlcikNCn0NCmlmICghcmVxdWlyZSgicnBhcnQiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicnBhcnQiKQ0KICAgbGlicmFyeShycGFydCkNCn0NCmlmICghcmVxdWlyZSgicmF0dGxlIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInJhdHRsZSIpDQogICBsaWJyYXJ5KHJhdHRsZSkNCn0NCmlmICghcmVxdWlyZSgicnBhcnQucGxvdCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJycGFydC5wbG90IikNCiAgIGxpYnJhcnkocnBhcnQucGxvdCkNCn0NCmlmICghcmVxdWlyZSgicnBhcnQucGxvdCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJycGFydC5wbG90IikNCiAgIGxpYnJhcnkocnBhcnQucGxvdCkNCn0NCmlmICghcmVxdWlyZSgiUkNvbG9yQnJld2VyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIlJDb2xvckJyZXdlciIpDQogICBsaWJyYXJ5KFJDb2xvckJyZXdlcikNCn0NCmlmICghcmVxdWlyZSgiZTEwNzEiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZTEwNzEiKQ0KICAgbGlicmFyeShlMTA3MSkNCn0NCmlmICghcmVxdWlyZSgiZ2xtbmV0IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImdsbW5ldCIpDQogICBsaWJyYXJ5KGdsbW5ldCkNCn0NCmlmICghcmVxdWlyZSgicFJPQyIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJwUk9DIikNCiAgIGxpYnJhcnkocFJPQykNCn0NCg0KIyBrbml0cjo6b3B0c19rbml0JHNldChyb290LmRpciA9ICJDOi9Vc2Vycy83NUNQRU5HL09uZURyaXZlIC0gV2VzdCBDaGVzdGVyIFVuaXZlcnNpdHkgb2YgUEEvRG9jdW1lbnRzIikNCiMga25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSAiQzpcXFNUQTQ5MFxcdzA1IikNCg0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsICAgDQogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID0gVFJVRSwgICANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BKQ0KYGBgDQoNCg0KXA0KDQojIEludHJvZHVjdGlvbg0KDQpXaGVuIGNydW5jaGluZyBkYXRhIHRvIG1vZGVsIGJ1c2luZXNzIGRlY2lzaW9ucywgd2UgYXJlIG1vc3QgdHlwaWNhbGx5IHVzaW5nIHN1cGVydmlzZWQgYW5kIHVuc3VwZXJ2aXNlZCBsZWFybmluZyBtZXRob2RzLg0KDQpBbGdvcml0aG1zIGFyZSBvZnRlbiBncm91cGVkIGJ5IHNpbWlsYXJpdHkgaW4gdGVybXMgb2YgdGhlaXIgZnVuY3Rpb24gKGhvdyB0aGV5IHdvcmspLiBBZ2FpbiwgdGhlcmUgd2lsbCBiZSBubyBwZXJmZWN0IGNsYXNzaWZpY2F0aW9uIG9mIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBhbmQgdGhlcmUgaXMgYWxzbyBubyB3YXkgdG8gZXhoYXVzdCBhbGwgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zLiBXZSBvbmx5IGxpc3QgdGhvc2UgbW9zdCBjb21tb25seSB1c2VkIGFsZ29yaXRobXMgaGVyZSBiYXNlZCBvbiBzaW1pbGFyaXR5IHRvIGtlZXAgdGhpbmdzIHNpbXBsZS4gDQoNCkZvciBpbGx1c3RyYXRpdmUgcHVycG9zZXMsIEkgd2lsbCB1c2Ugc29tZSBleGFtcGxlcyBmcm9tIHRoZSB3ZWxsLWtub3duICoqSW50cm9kdWN0aW9uIHRvIFN0YXRpc3RpY2FsIExlYXJuaW5nOiB3aXRoIEFwcGxpY2F0aW9ucyBpbiBSKiogPGh0dHBzOi8vd3d3LnN0YXRsZWFybmluZy5jb20vPi4NCg0KDQpUaGlzIG5vdGUgd2lsbCBvdXRsaW5lIHRoZSBzdXBlcnZpc2VkIGxlYXJuaW5nIGFsZ29yaXRobXMgaW5jbHVkaW5nIHN0YXRpc3RpY2FsIG1vZGVscyBhbmQgdGhvc2UgZGV2ZWxvcGVkIGJ5IHRoZSBtYWNoaW5lIGxlYXJuaW5nIGNvbW11bml0eS4gV2Ugd2lsbCBidWlsZCBhIGRlY2lzaW9uIHRyZWUgbW9kZWwgZm9yIHRoZSBkaWFiZXRlcyBkYXRhIHNldCBpbiB0aGUgY2FzZSBzdHVkeS4NCg0KDQojIE1vZGVsIGFuZCBJbnN0YW5jZS1iYXNlZCBTdXBlcnZpc2VkIEFsZ29yaXRobXMNCiANCk1hY2hpbmUgbGVhcm5pbmcgY2FuIGJlIGJyb2FkbHkgY2F0ZWdvcml6ZWQgaW50byB0d28gdHlwZXM6IGluc3RhbmNlLWJhc2VkIGxlYXJuaW5nIGFuZCBtb2RlbC1iYXNlZCBsZWFybmluZy4gQm90aCBhcHByb2FjaGVzIGhhdmUgdGhlaXIgdW5pcXVlIG1ldGhvZG9sb2dpZXMgYW5kIHVzZSBjYXNlcy4gDQoNCioqSW5zdGFuY2UtYmFzZWQgTGVhcm5pbmcqKiANCg0KSW5zdGFuY2UtYmFzZWQgbGVhcm5pbmcsIGFsc28ga25vd24gYXMgbGF6eSBsZWFybmluZywgaW52b2x2ZXMgc3RvcmluZyB0aGUgdHJhaW5pbmcgZGF0YSBhbmQgdXNpbmcgaXQgdG8gbWFrZSBwcmVkaWN0aW9ucyBkaXJlY3RseS4gVGhlIGFsZ29yaXRobSBkb2VzIG5vdCBidWlsZCBhbiBleHBsaWNpdCBtb2RlbCBidXQgcmVsaWVzIG9uIHRoZSBlbnRpcmUgZGF0YSBzZXQgdG8gcmVzcG9uZCB0byBxdWVyaWVzLg0KDQoqKkluc3RhbmNlLWJhc2VkIGxlYXJuaW5nIGFsZ29yaXRobXMqKiBjb21wYXJlIG5ldyBwcm9ibGVtIGluc3RhbmNlcyB3aXRoIGluc3RhbmNlcyBzZWVuIGluIHRyYWluaW5nLCB3aGljaCBoYXZlIGJlZW4gc3RvcmVkIGluIG1lbW9yeS4gUHJlZGljdGlvbnMgYXJlIG1hZGUgYmFzZWQgb24gdGhlIHNpbWlsYXJpdHkgYmV0d2VlbiBuZXcgZGF0YSBwb2ludHMgYW5kIHN0b3JlZCBpbnN0YW5jZXMuDQogDQpBbW9uZyAqKmluc3RhbmNlLWJhc2VkIGxlYXJuaW5nIGFsZ29yaXRobXMqKiwgKipLLU5lYXJlc3QgTmVpZ2hib3JzIChLLU5OKSoqIGlzIHRoZSBtb3N0IGNvbW1vbiBpbnN0YW5jZS1iYXNlZCBsZWFybmluZyBhbGdvcml0aG0uIEl0IGNsYXNzaWZpZXMgYSBkYXRhIHBvaW50IGJhc2VkIG9uIGhvdyBpdHMgbmVpZ2hib3JzIGFyZSBjbGFzc2lmaWVkLiBUaGUg4oCYS+KAmSB2YWx1ZSByZXByZXNlbnRzIHRoZSBudW1iZXIgb2YgbmVpZ2hib3JzIHRvIGNvbnNpZGVyLg0KIA0KVGhlICoqYWR2YW50YWdlcyBvZiBpbnN0YW5jZS1iYXNlZCBsZWFybmluZyBhbGdvcml0aG1zKiogYXJlIHRoZWlyIHNpbXBsaWNpdHksIGFkYXB0YWJpbGl0eSwgYW5kIHZlcnNhdGlsaXR5LiBUaGUgZGlzYWR2YW50YWdlcyBhcmUgc3RvcmFnZSByZXNvdXJjZSwgcHJlZGljdGlvbiBzcGVlZCwgYW5kIG5vaXN5IGRhdGEgc2Vuc2l0aXZpdHkuDQogDQogDQoqKk1vZGVsLWJhc2VkIExlYXJuaW5nKiogDQoNCk1vZGVsLWJhc2VkIGxlYXJuaW5nLCBhbHNvIGtub3duIGFzIGVhZ2VyIGxlYXJuaW5nLCBpbnZvbHZlcyBidWlsZGluZyBhIG1vZGVsIGZyb20gdGhlIHRyYWluaW5nIGRhdGEgYmVmb3JlIG1ha2luZyBwcmVkaWN0aW9ucy4gVGhpcyBtb2RlbCBjYXB0dXJlcyB0aGUgdW5kZXJseWluZyBwYXR0ZXJucyBpbiB0aGUgZGF0YSwgd2hpY2ggY2FuIHRoZW4gYmUgdXNlZCB0byBtYWtlIHByZWRpY3Rpb25zIG9uIG5ldyBkYXRhLiBXZSBoYXZlIGFscmVhZHkgbGVhcm5lZCBhIGZldyBvZiB0aGVtIA0KDQoqICoqTGluZWFyIFJlZ3Jlc3Npb24qKjogVGhpcyBhbGdvcml0aG0gbW9kZWxzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBhIGRlcGVuZGVudCB2YXJpYWJsZSBhbmQgb25lIG9yIG1vcmUgaW5kZXBlbmRlbnQgdmFyaWFibGVzIGJ5IGZpdHRpbmcgYSBsaW5lYXIgZXF1YXRpb24gdG8gdGhlIG9ic2VydmVkIGRhdGEuDQoNCiogKipMb2dpc3RpYyBSZWdyZXNzaW9uKio6IEEgY2xhc3NpZmljYXRpb24gYWxnb3JpdGhtIHRoYXQgbW9kZWxzIHRoZSBwcm9iYWJpbGl0eSBvZiBhIGJpbmFyeSBvdXRjb21lIGJhc2VkIG9uIG9uZSBvciBtb3JlIHByZWRpY3RvciB2YXJpYWJsZXMuDQoNCiogKipOZXVyYWwgTmV0d29ya3MqKjogQ29tcGxleCBtb2RlbHMgaW5zcGlyZWQgYnkgdGhlIGh1bWFuIGJyYWluLCBjYXBhYmxlIG9mIGNhcHR1cmluZyBpbnRyaWNhdGUgcGF0dGVybnMgaW4gdGhlIGRhdGEgdGhyb3VnaCBsYXllcnMgb2YgaW50ZXJjb25uZWN0ZWQgbm9kZXMuDQogDQoqICoqRGVjaXNpb24gVHJlZXMqKjogVGhlc2UgbW9kZWxzIHVzZSB0cmVlLWxpa2Ugc3RydWN0dXJlcyB3aGVyZSBub2RlcyByZXByZXNlbnQgZGVjaXNpb25zIGJhc2VkIG9uIHRoZSB2YWx1ZSBvZiBpbnB1dCBmZWF0dXJlcywgbGVhZGluZyB0byBhbiBvdXRwdXQgcHJlZGljdGlvbi4gVGhpcyBub3RlIHdpbGwgZGlzY3VzcyB0cmVlLWJhc2VkIGFsZ29yaXRobXMuIA0KDQoNClRoZSBuZXh0IHRhYmxlIGNvbXBhcmVzIHRoZSB0d28gY2F0ZWdvcmllcyBvZiBhbGdvcml0aG1zIGZyb20gZGlmZmVyZW50IHBlcnNwZWN0aXZlcy4NCg0KIA0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjkwJSIsIGZpZy5jYXA9IlRoZSBjb21wYXJpc29uIG9mIGluc3RhbmNlLWJhc2VkIGFuZCBtb2RlbC1iYXNlZCBzdXBlcnZpc2VkIGxlYXJuaW5nIGFsZ29yaXRobXMifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL0luc3RhYmNlTW9kZWwtYmFzZWRMZWFybmluZy5qcGciKQ0KYGBgDQogDQpJbiB0aGUgbmV4dCBzdWJzZWN0aW9ucywgd2Ugd2lsbCBwcm92aWRlIHNvbWUgb2YgdGhlIGluc3RhbmNlLWJhc2VkIGFuZCBtb2RlbC1iYXNlZCBhbGdvcml0aG1zIHdpdGggc29tZSBpbGx1c3RyYXRpdmUgZXhhbXBsZXMgKGJhc2VkIG9uIHNtYWxsIGRhdGEgc2V0cykgdG8gZXhwbGFpbiB0aGUgcm91Z2ggaWRlYXMgb2YgdGhlc2UgYWxnb3JpdGhtcy4gDQogDQogXA0KDQojIyBMT0VTUyBSZWdyZXNzaW9uIA0KIA0KUmVncmVzc2lvbiBpcyBjb25jZXJuZWQgd2l0aCBtb2RlbGluZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdmFyaWFibGVzIHRoYXQgaXMgaXRlcmF0aXZlbHkgcmVmaW5lZCB1c2luZyBhIHBlcmZvcm1hbmNlIG1lYXN1cmUgZGVmaW5lZCBiYXNlZCBvbiBlcnJvcnMgaW4gdGhlIHByZWRpY3Rpb25zIG1hZGUgYnkgdGhlIG1vZGVsLiBSZWdyZXNzaW9uIG1ldGhvZHMgYXJlIGEgd29ya2hvcnNlIG9mIHN0YXRpc3RpY3MgYW5kIGhhdmUgYmVlbiB0aGUgYmFja2JvbmUgb2Ygc3RhdGlzdGljYWwgbWFjaGluZSBsZWFybmluZy4gVGhlIG1vc3QgcG9wdWxhciByZWdyZXNzaW9uIGFsZ29yaXRobXMgYXJlOg0KDQoxLglPcmRpbmFyeSBMZWFzdCBTcXVhcmVzIFJlZ3Jlc3Npb24gKE9MU1IpDQoNCjIuCUxpbmVhciBSZWdyZXNzaW9uDQoNCjMuCUxvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KNC4JU3RlcC13aXNlIFJlZ3Jlc3Npb24NCg0KNS4JTE9jYWxseSBFc3RpbWF0ZWQgU2NhdHRlci1wbG90IFNtb290aGluZyAoTE9FU1MpIC0gYSBub25wYXJhbWV0cmljIHJlZ3Jlc3Npb24uDQoNCkluIHRoaXMgc3Vic2VjdGlvbiwgd2Ugd2lsbCB1c2UgYW4gZXhhbXBsZSB0byBzaG93IG9uZSBvZiB0aGUgcm9idXN0IGRpc3RyaWJ1dGlvbiBmcmVlIHNpbmdsZSB2YXJpYWJsZSByZWdyZXNzaW9uIGFsZ29yaXRobSAtIGxvY2FsbHkgd2VpZ2h0ZWQgc2NhdHRlci1wbG90IHNtb290aGluZyAoTE9FU1MpIC0gdXNpbmcgYSBidWlsdC1pbiBSIGZ1bmN0aW9uICh3aXRob3V0IGdpdmluZyB0aGUgZGV0YWlscyBvZiB0aGUgYWxnb3JpdGhtKS4gU2luY2UgaXQgaXMgYSBzaW5nbGUgdmFyaWFibGUgcmVncmVzc2lvbiwgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBwZXJmb3JtYW5jZSBjYW4gYmUgZWFzaWx5IHZpc3VhbGl6ZWQuDQoNCg0KVGhlIExPRVNTIHJlZ3Jlc3Npb24gaXMgYmFzZWQgb24gdHdvIHZhcmlhYmxlcyAqKlNhbGVzKiogYW5kICoqUHJpY2UqKiBpbiB0aGUgZGF0YSBzZXQgKkNhcnNlYXRzKiBpbiB0aGUgYm9vayAqKklTTFIqKi4NCg0KDQpgYGB7cn0NCmRhdGEoIkNhcnNlYXRzIikNCnBhbmRlcihoZWFkKENhcnNlYXRzKSkNCmBgYA0KDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5jYXA9IkZpZ3VyZSA1LiBMT0VTUyByZWdyZXNzaW9uOiBTYWxlcyB+IFByaWNlcyJ9DQpsdzEgPSBsb2VzcyhTYWxlcyB+IFByaWNlLCBkYXRhID0gQ2Fyc2VhdHMpDQpwbG90KFNhbGVzIH4gUHJpY2UsIGRhdGEgPSBDYXJzZWF0cywgcGNoPTE5LCBjZXg9MC44KQ0KaiA9IG9yZGVyKENhcnNlYXRzJFByaWNlKSAgIyBzb3J0IHRoZSBkYXRhIHZlY3RvciBhbmQgcmV0dXJucyB0aGUgaW5kZXggDQogICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9mIHRoZSB2YWx1ZXMgb2YgdGhlIG9yaWdpbmFsIGRhdGEgdmVjdG9yDQpsaW5lcyhDYXJzZWF0cyRQcmljZVtqXSxsdzEkZml0dGVkW2pdLGNvbD0icmVkIixsd2Q9MykNCmBgYA0KDQpUaGUgYGxvZXNzKClgIGlzIGEgZGF0YS1kcml2ZW4gbm9ucGFyYW1ldHJpYyByZWdyZXNzaW9uIChsb2NhbCBwb2x5bm9taWFsIHJlZ3Jlc3Npb24gaW5jbHVkaW5nIGxpbmVhciByZWdyZXNzaW9uKSwgaW4gb3RoZXIgd29yZHMsIHRoZSBleHBsaWNpdCBtb2RlbCBwYXJhbWV0ZXJzIGluIGFuIGV4cGxpY2l0bHkgZXhwcmVzc2VkIG1vZGVsIHRvIGVzdGltYXRlLiBgbG9lc3NgIHJlZ3Jlc3Npb24gaXMgYW5hbG9nb3VzIHRvIHNpbmdsZSB2YXJpYWJsZSByZWdyZXNzaW9uIHN1Y2ggYXMgc2ltcGxlIGxpbmVhciBhbmQgbm9ubGluZWFyIHJlZ3Jlc3Npb24gbW9kZWxzLg0KDQpUbyBwbG90IHRoZSBgc21vb3RoYCBmaXR0ZWQgcmVncmVzc2lvbiBjdXJ2ZSwgd2UgbmVlZCB0byB1c2UgZnVuY3Rpb24gYG9yZGVyKClgIHRoZSBpbmRpY2VzIG9mIHRoZSBvcmlnaW5hbCBkYXRhIHZlY3RvciBhZnRlciBpdCB3YXMgb3JkZXJlZC4gRm9yIGV4YW1wbGUsDQoNCmBgYHtyfQ0KeCA9IGMoMywgMSwgMCwgNCwgLTUpDQpvcmRlcih4KQ0KIyMgVGhlIGFib3ZlIGluZGV4IGNhbiBzb3J0IHRoZSBkYXRhIGJlbG93DQp4W29yZGVyKHgpXQ0KYGBgDQoNCldlIGNhbiB1c2UgdGhlIGxvZXNzIG5vbnBhcmFtZXRyaWMgcmVncmVzc2lvbiBtb2RlbCB0byBwcmVkaWN0IGFzIHVzdWFsIHVzaW5nIHRoZSBnZW5lcmljIGZ1bmN0aW9uIGBwcmVkaWN0KClgIHdpdGggYW4gaW5wdXQgZGF0YSBmcmFtZS4gDQoNCmBgYHtyfQ0KbHcxID0gbG9lc3MoU2FsZXMgfiBQcmljZSwgZGF0YSA9IENhcnNlYXRzKQ0KcHJlZGljdChsdzEsIGRhdGEuZnJhbWUoUHJpY2UgPWMoMTM0LDEyMSkpLHNlID0gVFJVRSkgICMgbmV3IGRhdGEgbXVzdCBiZSB3aXRoaW4gDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBFeGlzdGluZyBwcmljZSByYW5nZQ0KYGBgDQoNCg0KIyMgUmVndWxhcml6ZWQgUmVncmVzc2lvbg0KDQoNCldlIGhhdmUgZGlzY3Vzc2VkIHNvbWUgZGVncmVlIG9mIGRldGFpbCBpbiBsaW5lYXIgYW5kIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWxzIGZyb20gYm90aCBjbGFzc2ljYWwgc3RhdGlzdGljcyBwZXJzcGVjdGl2ZSBhbmQgbWFjaGluZSBsZWFybmluZyBwZXJzcGVjdGl2ZSBpbiB0ZXJtcyBvZiBtb2RlbCB0cmFpbmluZyBhbmQgcGVyZm9ybWFuY2UgZXZhbHVhdGlvbi4NCg0KVGhlIGZvbGxvd2luZyAqKnJlZ3VsYXJpemVkIHJlZ3Jlc3Npb24qKiBhbGdvcml0aG1zIGFyZSByZWNlbnRseSBkZXZlbG9wZWQgbGVhcm5pbmcgYWxnb3JpdGhtcyBtb2RpZmllZCBmcm9tIGNsYXNzaWNhbCBzdGF0aXN0aWNzLg0KDQoqCSoqUmlkZ2UgUmVncmVzc2lvbioqDQoNCioqUmlkZ2UgcmVncmVzc2lvbioqIGRvZXMgbm90IHJlZHVjZSB0aGUgbnVtYmVyIG9mIGNvcnJlbGF0ZWQgZmVhdHVyZSB2YXJpYWJsZXMuIEl0IGJyaW5ncyBiaWFzIHRvIHRoZSBlc3RpbWF0ZWQgcmVncmVzc2lvbiBjb2VmZmljaWVudHMgdG8gcmVkdWNlIHRoZSBpbXBhY3Qgb2YgbXVsdGktY29ycmVsYXRlZCBmZWF0dXJlIHZhcmlhYmxlcy4gSW4gb3RoZXIgd29yZHMsIGl0IHNhY3JpZmljZXMgdGhlIHVuYmlhc2VkbmVzcyBvZiB0aGUgZXN0aW1hdGVkIHJlZ3Jlc3Npb24gdG8gZ2FpbiB0aGUgc3RhYmlsaXR5IG9mIHRoZSBlc3RpbWF0aW9uLg0KDQoNCg0KKgkqKkxlYXN0IEFic29sdXRlIFNocmlua2FnZSBhbmQgU2VsZWN0aW9uIE9wZXJhdG9yIChMQVNTTykqKg0KDQoqKkxBU1NPKiogZmlsdGVycyB0aGUgZmVhdHVyZSB2YXJpYWJsZXMgd2l0aCBhIHNtYWxsIG1hZ25pdHVkZSBvZiB0aGUgYWJzb2x1dGUgcmVncmVzc2lvbiBjb2VmZmljaWVudHMuIEFsbCBudW1lcmljYWwgZmVhdHVyZSB2YXJpYWJsZXMgbXVzdCBiZSBzdGFuZGFyZGl6ZWQgd2hlbiB1c2luZyBMQVNTTyByZWdyZXNzaW9uIGZvciBwcmVkaWN0aW9uLiBTaW5jZSBzb21lIG9mIHRoZSBmZWF0dXJlIHZhcmlhYmxlcyB3aWxsIGJlIGZpbHRlcmVkIG91dCBmcm9tIHRoZSBtb2RlbC4gSXQgaXMgY29uc2lkZXJlZCBhIGRpbWVuc2lvbiByZWR1Y3Rpb24gbWV0aG9kIHRoYXQgaGFzIGJlY29tZSBhIHBvcHVsYXIgdG9vbCBpbiB0aGUgbWFjaGluZSBsZWFybmluZyBjb21tdW5pdHkuDQoNCg0KVGhlIGZvbGxvd2luZyBmaWd1cmUgZXhwbGFpbnMgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiByZWd1bGFyIGxlYXN0IHNxdWFyZSByZWdyZXNzaW9uLCByaWRnZSByZWdyZXNzaW9uLCBhbmQgTEFTU08uDQoNCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTMsIGZpZy5oZWlnaHQ9MywgZmlnLmNhcD0iRmlndXJlIDYuICBSZWxhdGlvbnNoaXAgYmV0d2VlbiBsZWFzdCBzcXVhcmUsIHJpZGdlLCBhbmQgTEFTU08gcmVncmVzc2lvbnMuIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy93MDctTEFTU08tUmlkZ2UtTFNSLmpwZyIpDQpgYGANCg0KQmVjYXVzZSBib3RoICoqcmlkZ2UqKiBhbmQgKipMQVNTTyoqIGZhbGwgaW50byB0aGUgc2FtZSB0aGVvcmV0aWNhbCBmcmFtZXdvcmsgKGFsdGhvdWdoIGZ1bmN0aW9uaW5nIGluIHZlcnkgZGlmZmVyZW50IHdheXMpLCBTdGFuZm9yZCBzdGF0aXN0aWNpYW5zIGRldmVsb3BlZCBhbiBSIGxpYnJhcnksIGBnbG1uZXRgIHRvIGltcGxlbWVudCB2YXJpb3VzIHJlZ3VsYXJpemVkIHJlZ3Jlc3Npb24gaW5jbHVkaW5nIGJvdGggb2YgdGhlc2UgdHdvIHJlZ3VsYXJpemVkIHJlZ3Jlc3Npb24gbWV0aG9kcy4NCg0KXA0KDQojIyBJbnN0YW5jZS1iYXNlZCBBbGdvcml0aG1zDQoNCkZpcnN0IG9mIGFsbCwgb2JzZXJ2YXRpb25zL3NhbXBsZXMvaW5zdGFuY2VzIGFsbCBtZWFuIHRoZSBzYW1lIHRoaW5nIGluIG1hY2hpbmUgbGVhcm5pbmcuDQoNClRoZSBpbnN0YW5jZS1iYXNlZCBsZWFybmluZyBtb2RlbCBpcyBhIGRlY2lzaW9uIHByb2JsZW0gd2l0aCBpbnN0YW5jZXMgb3IgZXhhbXBsZXMgb2YgdHJhaW5pbmcgZGF0YSB0aGF0IGFyZSBkZWVtZWQgaW1wb3J0YW50IG9yIHJlcXVpcmVkIHRvIHRoZSBtb2RlbC4gU3VjaCBtZXRob2RzIHR5cGljYWxseSBidWlsZCB1cCBhIGRhdGFiYXNlIG9mIGV4YW1wbGUgZGF0YSBhbmQgY29tcGFyZSBuZXcgZGF0YSB0byB0aGUgZGF0YWJhc2UgdXNpbmcgYSBzaW1pbGFyaXR5IG1lYXN1cmUgaW4gb3JkZXIgdG8gZmluZCB0aGUgYmVzdCBtYXRjaCBhbmQgbWFrZSBhIHByZWRpY3Rpb24uIEZvciB0aGlzIHJlYXNvbiwgaW5zdGFuY2UtYmFzZWQgbWV0aG9kcyBhcmUgYWxzbyBjYWxsZWQgd2lubmVyLXRha2UtYWxsIG1ldGhvZHMgYW5kIG1lbW9yeS1iYXNlZCBsZWFybmluZyAoc29tZXRpbWVzIGFsc28gY2FsbGVkIGxhenkgbGVhcm5pbmcuIEZvY3VzIGlzIHB1dCBvbiB0aGUgcmVwcmVzZW50YXRpb24gb2YgdGhlIHN0b3JlZCBpbnN0YW5jZXMgYW5kIHNpbWlsYXJpdHkgbWVhc3VyZXMgdXNlZCBiZXR3ZWVuIGluc3RhbmNlcy4NCg0KVGhlIG1vc3QgcG9wdWxhciBpbnN0YW5jZS1iYXNlZCBhbGdvcml0aG1zIGFyZToNCg0KKiBrLU5lYXJlc3QgTmVpZ2hib3IgKGtOTikNCiogU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgKFNWTSkNCg0KYGBge3IgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9MywgZmlnLmhlaWdodD0zLCBmaWcuY2FwPSJGaWd1cmUgNy4gIEluc3RhbmNlLWJhc2VkIGxlYXJuaW5nIGFsZ29yaXRobXM6IEtOTiBhbmQgU1ZNLiJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdzA3LUtubi1TVk0uanBnIikNCmBgYA0KDQpCb3RoIGtOTiBhbmQgU1ZNIGFyZSBpbnR1aXRpdmUuIFRoZSBtb3JlIGluc3RhbmNlcyB0aGUgbW9yZSB0aGUgYWNjdXJhY3kuICBJU0xSICgybmQgZWRpdGlvbikgaGFzIGNhc2Ugc3R1ZGllcyB1c2luZyBSIGZvciBrTk4gKHVzaW5nICoqa25uKCkqKiBpbiBsaWJyYXJ5ICoqe2NsYXNzfSoqIGluIHNlY3Rpb24gNC43LjYsIHN0YXJ0aW5nIGZyb20gcGFnZSAxODEpIGFuZCBTVk0gKHVzaW5nICoqc3ZtKCkqKiBpbiBsaWJyYXJ5ICoqe2UxMDcxfSoqIGluIHNlY3Rpb25zIDkuNi4xIGFuZCA5LjYuMiwgc3RhcnRpbmcgZnJvbSBwYWdlIDM4OSkuDQoNClwNCg0KIyMgTmEmaXVtbDt2ZSBCYXllcyAtIEEgQmF5ZXNpYW4gQWxnb3JpdGhtDQoNCkJheWVzaWFuIG1ldGhvZHMgYXJlIHRob3NlIHRoYXQgZXhwbGljaXRseSBhcHBseSBCYXllc+KAmSBUaGVvcmVtIGZvciBwcm9ibGVtcyBzdWNoIGFzIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uLiBUaGVyZSBzZXZlcmFsIEJheWVzaWFuIGFsZ29yaXRobXMgaGF2ZSBiZWVuIGRldmVsb3BlZCBzbyBmYXIuIFdlIG9ubHkgaW50cm9kdWNlIHRoZSBiYXNpYyBidXQgY29tbW9ubHkgdXNlZCBpbiBwcmFjdGljZSAtIE5hJml1bWw7dmUgQmF5ZXMuDQoNClRoZSBOYSZpdW1sO3ZlIEJheWVzIGNsYXNzaWZpZXIgaXMgYSBzaW1wbGUgcHJvYmFiaWxpc3RpYyBjbGFzc2lmaWVyIHRoYXQgaXMgYmFzZWQgb24gdGhlIEJheWVzIHRoZW9yZW0gYnV0IHdpdGggc3Ryb25nIGFzc3VtcHRpb25zIHJlZ2FyZGluZyBpbmRlcGVuZGVuY2UuIEhpc3RvcmljYWxseSwgdGhpcyB0ZWNobmlxdWUgYmVjYW1lIHBvcHVsYXIgd2l0aCBhcHBsaWNhdGlvbnMgaW4gZW1haWwgZmlsdGVyaW5nLCBzcGFtIGRldGVjdGlvbiwgYW5kIGRvY3VtZW50IGNhdGVnb3JpemF0aW9uLiBBbHRob3VnaCBpdCBpcyBvZnRlbiBvdXRwZXJmb3JtZWQgYnkgb3RoZXIgdGVjaG5pcXVlcywgYW5kIGRlc3BpdGUgdGhlIG5hJml1bWw7dmUgZGVzaWduIGFuZCBvdmVyc2ltcGxpZmllZCBhc3N1bXB0aW9ucywgdGhpcyBjbGFzc2lmaWVyIGNhbiBwZXJmb3JtIHdlbGwgaW4gbWFueSBjb21wbGV4IHJlYWwtd29ybGQgcHJvYmxlbXMuIA0KDQpUaGUgdGhlb3J5IGJlaGluZCBOYSZpdW1sO3ZlIEJheWVzIGlzIHN0cmFpZ2h0Zm9yd2FyZCBhcyBkZXBpY3RlZCBpbiB0aGUgZm9sbG93aW5nLg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTMsIGZpZy5jYXA9IkZpZ3VyZSAxNC4gIE5haXZlIEJheWVzIENsYXNzaWZpZXIuIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy93MDctTmFpdmUtQmF5ZXMuanBnIikNCmBgYA0KDQpUaGVyZSBhcmUgc2V2ZXJhbCBsaWJyYXJpZXMgaW4gUiB0aGF0IGhhdmUgdGhlIGZ1bmN0aW9uIHRvIGltcGxlbWVudCBhJml1bWw7dmUgQmF5ZXMuIElTTFIgaGFzIGEgbGFiIG9uIHRoZSBhcHBsaWNhdGlvbiBvZiBhJml1bWw7dmUgQmF5ZXMgKHNlY3Rpb24gNC43LjUsIHN0YXJ0aW5nIGZyb20gcGFnZSAxODApIHVzaW5nIHRoZSAqKm5haXZlQmF5ZXMoKSoqIGZ1bmN0aW9uIGluIFIgbGlicmFyeSAqKntlMTA3MX0qKi4NCg0KDQoNCg0KXA0KDQojIERlY2lzaW9uIFRyZWUgQWxnb3JpdGhtcw0KDQoNCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI0MCUifQ0KaWYgKGtuaXRyOjo6aXNfbGF0ZXhfb3V0cHV0KCkpIHsNCiAga25pdHI6OmFzaXNfb3V0cHV0KCdcXHVybHtodHRwczovL2dpdGh1Yi5jb20vcGVuZ2RzY2kvU1RBNTUxL2Jsb2IvbWFpbi93MDcvaW1nL3cwNy4xLUdJRnRyZWUuZ2lmfScpDQp9IGVsc2Ugew0KICBrbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1nL3cwNy4xLUdJRnRyZWUuZ2lmIikNCn0NCmBgYA0KDQoNClRoZSBEZWNpc2lvbiBUcmVlIChEVCkgYWxnb3JpdGhtIGlzIGJhc2VkIG9uIGNvbmRpdGlvbmFsIHByb2JhYmlsaXRpZXMuIFVubGlrZSB0aGUgb3RoZXIgY2xhc3NpZmljYXRpb24gYWxnb3JpdGhtcywgZGVjaXNpb24gdHJlZXMgZ2VuZXJhdGUgcnVsZXMuIEEgcnVsZSBpcyBhIGNvbmRpdGlvbmFsIHN0YXRlbWVudCB0aGF0IGNhbiBlYXNpbHkgYmUgdW5kZXJzdG9vZCBieSBodW1hbnMgYW5kIGVhc2lseSB1c2VkIHdpdGhpbiBhIGRhdGFiYXNlIHRvIGlkZW50aWZ5IGEgc2V0IG9mIHJlY29yZHMuIEl0IGlzIGVhc3kgdG8gaW50ZXJwcmV0IGFuZCBpbXBsZW1lbnQgaW4gcmVhbC13b3JsZCBhcHBsaWNhdGlvbnMuIEFtb25nIHNldmVyYWwgYmFzaWMgdHJlZS1iYXNlZCBhbGdvcml0aG1zLCBDbGFzc2lmaWNhdGlvbiBhbmQgUmVncmVzc2lvbiBUcmVlIChDQVJUKSBpcyBtb3N0IGZyZXF1ZW50bHkgdXNlZCBpbiBwcmFjdGljZS4gDQoNClRoaXMgc3Vic2VjdGlvbiBmb2N1c2VzIG9uIHRoZSBiYXNpYyBkZWNpc2lvbiB0cmVlIHdpdGggc29tZSB0ZWNobmljYWwgZGVzY3JpcHRpb24gb2Ygc3RlcHMgaW4gZGVjaXNpb24gdHJlZSBpbmR1Y3Rpb24uIFRoZSBnZW5lcmFsIHN0cnVjdHVyZSBvZiBhIGRlY2lzaW9uIHRyZWUgYWxnb3JpdGhtIGlzIGluIHRoZSBmb2xsb3dpbmcgZXhhbXBsZSBvZiBwcmVkaWN0aW5nIHRoZSBzdXJ2aXZhbCBvZiBUaXRhbmljIHBhc3NlbmdlcnMuDQoNCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTMsIGZpZy5oZWlnaHQ9MywgZmlnLmNhcD0iRmlndXJlIDguICBJbGx1c3RyYXRpb24gb2YgZGVjaXNpb24gdHJlZSBhbGdvcml0aG06IHByZWRpY3RpbmcgVGl0YW5pYyBzdXJ2aXZhbC4ifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL3cwNy1EVC1UaXRhbmljLmpwZyIpDQpgYGANCg0KVGhlIGFib3ZlIGRlY2lzaW9uIHRyZWUgaW52b2x2ZXMgdGhyZWUgdmFyaWFibGVzOiBzZXgsIGFnZSwgYW5kIHNpYnNwIChzaWJsaW5nIGFuZCBzcG91c2UpLiBXZSBjYW4gZWFzaWx5IGNvbnZlcnQgdGhlIHRyZWUgdG8gYSBzZXQgb2YgcnVsZXMgKGNvbmRpdGlvbmFsIHN0YXRlbWVudHMpIHRvIG1ha2UgYSBwcmVkaWN0aW9uIG9mIHRoZSBzdXJ2aXZhbCBzdGF0dXMgZm9yIGEgbmV3IGluY29taW5nIGRhdGEgcG9pbnQuDQoNCiMjIFN0cnVjdHVyZSBhbmQgVGVjaG5pY2FsIFRlcm1zIA0KDQpUaGUgZm9sbG93aW5nIGRpYWdyYW0gaWxsdXN0cmF0ZXMgdGhlIGJhc2ljIHN0cnVjdHVyZSBvZiBhIGRlY2lzaW9uIHRyZWUuDQoNCg0KYGBge3IgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjcwJSIsIGZpZy5jYXA9IkZpZ3VyZSA5LiBEZWNpc2lvbiB0cmVlIHN0cnVjdHVyZS4ifQ0KICBrbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1nL3cwNy4xLURlY2lzaW9uVHJlZVN0cnVjdHVyZS5qcGciKQ0KYGBgDQoNCg0KKipSb290IE5vZGUqKjogSXQgcmVwcmVzZW50cyB0aGUgZW50aXJlIHBvcHVsYXRpb24gb3Igc2FtcGxlIGFuZCB0aGlzIGZ1cnRoZXIgZ2V0cyBkaXZpZGVkIGludG8gdHdvIG9yIG1vcmUgaG9tb2dlbmVvdXMgc2V0cy4NCg0KKipTcGxpdHRpbmcqKjogSXQgaXMgYSBwcm9jZXNzIG9mIGRpdmlkaW5nIGEgbm9kZSBpbnRvIHR3byBvciBtb3JlIHN1Yi1ub2Rlcy4NCg0KKipEZWNpc2lvbiBOb2RlKio6IFdoZW4gYSBzdWItbm9kZSBzcGxpdHMgaW50byBmdXJ0aGVyIHN1Yi1ub2RlcywgdGhlbiBpdCBpcyBjYWxsZWQgdGhlIGRlY2lzaW9uIG5vZGUuDQoNCioqTGVhZiAvIFRlcm1pbmFsIE5vZGUqKjogTm9kZXMgdGhhdCBkbyBub3Qgc3BsaXQgYXJlIGNhbGxlZCBMZWFmIG9yIFRlcm1pbmFsIE5vZGUuDQoNCioqUHJ1bmluZyoqOiBXaGVuIHdlIHJlbW92ZSBzdWItbm9kZXMgb2YgYSBkZWNpc2lvbiBub2RlLCB0aGlzIHByb2Nlc3MgaXMgY2FsbGVkIHBydW5pbmcuIFdlIGNhbiBzYXkgdGhlIG9wcG9zaXRlIHByb2Nlc3Mgb2Ygc3BsaXR0aW5nLg0KDQoqKkJyYW5jaCAvIFN1Yi1UcmVlKio6IEEgc3Vic2VjdGlvbiBvZiB0aGUgZW50aXJlIHRyZWUgaXMgY2FsbGVkIGEgYnJhbmNoIG9yIHN1Yi10cmVlLg0KDQoqKlBhcmVudCBhbmQgQ2hpbGQgTm9kZSoqOiBBIG5vZGUsIHdoaWNoIGlzIGRpdmlkZWQgaW50byBzdWItbm9kZXMgaXMgY2FsbGVkIGEgcGFyZW50IG5vZGUgb2Ygc3ViLW5vZGVzIHdoZXJlYXMgc3ViLW5vZGVzIGFyZSB0aGUgY2hpbGQgb2YgYSBwYXJlbnQgbm9kZS4NCg0KDQpcDQoNClRoZSBmb2xsb3dpbmcgZXhhbXBsZSBiYXNlZCBvbiB0b3kgZGF0YSBpbGx1c3RyYXRlcyBob3cgYSBkZWNpc2lvbiBncm93cyBhbmQgaG93IHRvIHVzZSBhIGRlY2lzaW9uIHRyZWUgdG8gbWFrZSBwcmVkaWN0aW9ucy4NCg0KVGhlIHRveSBkYXRhIHNldCBpcyBnaXZlbiBiZWxvdy4NCg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTMsIGZpZy5jYXA9IkZpZ3VyZSAxMC4gIERlY2lzaW9uIHRyZWUgc3RydWN0dXJlIHVzaW5nIGEgdG95IGRhdGEuIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy93MDctRFQtVG95RGF0YS5qcGciKQ0KYGBgDQoNCg0KVGhlIGZ1bGx5IGdyb3duIHRyZWUgaXMgZ2l2ZW4gYmVsb3cgKG5vdGUgdGhlIHZhcmlhYmxlICoqY2xhc3MqKiBpcyB0aGUgYmluYXJ5IHJlc3BvbnNlIHZhcmlhYmxlKS4NCg0KYGBge3IgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9MywgZmlnLmhlaWdodD0zLCBmaWcuY2FwPSJGaWd1cmUgMTEuICBGdWxseSBncm93biBkZWNpc2lvbiB0cmVlIHVzaW5nIGEgdG95IGRhdGEuIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy93MDctRFQtVG95RXhhbXBsZS5qcGciKQ0KYGBgDQoNCg0KYGBge3J9DQpEYXRhU2V0ID0gZGF0YS5mcmFtZSgNCkFnZSA9IGMoIllvdXRoIiwgIllvdXRoIiwgIk1pZGRsZV9hZ2VkIiwgIlNlbmlvciIsICJTZW5pb3IiLCJTZW5pb3IiLCJNaWRkbGVfYWdlZCIsIllvdXRoIiwiWW91dGgiLCJTZW5pb3IiLCJZb3V0aCIsIk1pZGRsZV9hZ2VkIiwNCiAgICAgICAgIk1pZGRsZV9hZ2VkIiwiU2VuaW9yIiksDQpJbmNvbWUgPSBjKCJIaWdoIiwiSGlnaCIsIkhpZ2giLCJNZWRpdW0iLCJMb3ciLCAiTG93IiwiTG93IiwiTWVkaXVtIiwiTG93IiwiTWVkaXVtIiwiTWVkaXVtIiwiTWVkaXVtIiwiSGlnaCIsIk1lZGl1bSIpLA0KU3R1ZGVudCA9IGMoIk5vIiwgIk5vIiwiTm8iLCJObyIsIlllcyIsIlllcyIsIlllcyIsIk5vIiwiWWVzIiwiWWVzIiwiWWVzIiwNCiAgICAgICAgICAgICJObyIsIlllcyIsIk5vIiksDQpDcmVkaXRSYXRpbmcgPSBjKCJGYWlyIiwgIkV4Y2VsbGVudCIsIkZhaXIiLCJGYWlyIiwiRmFpciIsIkV4Y2VsbGVudCIsDQogICAgICAgICAgICAgICAgICJFeGNlbGxlbnQiLCJGYWlyIiwiRmFpciIsIkZhaXIiLCJFeGNlbGxlbnQiLCJFeGNlbGxlbnQiLA0KICAgICAgICAgICAgICAgICAiRmFpciIsIkV4Y2VsbGVudCIpLA0KQ2xhc3MgPSBjKCJObyIsICJObyIsICJZZXMiLCAiWWVzIiwiWWVzIiwiTm8iLCJZZXMiLCAiTm8iLCAiWWVzIiwiWWVzIiwNCiAgICAgICAgICAiWWVzIiwiWWVzIiwiWWVzIiwiTm8iKQ0KKQ0KI3BhbmRlcihEYXRhU2V0KQ0KYGBgDQoNCg0KIyMgRGVjaXNpb24gVHJlZSBHcm93aW5nIC0gSW1wdXJpdHkgTWVhc3VyZXMgDQoNCkdyb3dpbmcgYSBkZWNpc2lvbiB0cmVlIGlzIGFuIGl0ZXJhdGl2ZSBwcm9jZXNzIG9mIHNwbGl0dGluZyB0aGUgZmVhdHVyZSBzcGFjZSBpbnRvIHNvbWUgc3ViLXNwYWNlcyBhY2NvcmRpbmcgdG8gY2VydGFpbiBjcml0ZXJpYSBkZWZpbmVkIGJhc2VkIG9uIGZlYXR1cmUgdmFyaWFibGVzLiBUaGUgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBvZiBhIGRlY2lzaW9uIGlzIGRlcGVuZGVudCBvbiB0aGUgc2l6ZSBvZiB0aGUgdHJhaW5lZCB0cmVlLiBBIHNtYWxsIHNpemUgd2lsbCBjYXVzZSB1bmRlcmZpdHRpbmcgaXNzdWVzIGFuZCBhIGxhcmdlIHNpemUgd2lsbCByZXN1bHQgaW4gb3ZlcmZpdHRpbmcgaXNzdWVzLiANCg0KVGhlIHF1ZXN0aW9ucyBhcmUgKDEpIGhvdyB0byBjb250cm9sIHRoZSBzaXplIG9mIGEgZGVjaXNpb24gdG8gb2J0YWluIHRoZSBiZXN0IHBlcmZvcm1hbmNlOyAoMikgaG93IHRvIHNlbGVjdCB0aGUgZmVhdHVyZSB2YXJpYWJsZXMgdG8gZGVmaW5lIHRoZSByb290IGFuZCBzdWJzZXF1ZW50IGNoaWxkIG5vZGVzOyAoMykgaG93IHRvIHNwbGl0IGEgZmVhdHVyZSB2YXJpYWJsZS4gDQoNCioqR2luaSBpbmRleCoqIGFuZCAqKmVudHJvcHkqKiBhcmUgdGhlIHR3byBwb3B1bGFyIGltcHVyaXR5IG1lYXN1cmVzIGNvbW1vbmx5IHVzZWQgaW4gZGVjaXNpb24gdHJlZSBpbmR1Y3Rpb24uDQoNClwNCg0KIyMjIEdpbmkgSW5kZXgNCg0KKiAqKkdpbmkgSW5kZXgqKiBjb25zaWRlcnMgYSBzcGxpdCBmb3IgZWFjaCBhdHRyaWJ1dGUgKGZvciBhIGNvbnRpbnVvdXMgYXR0cmlidXRlLCB1c3VhbGx5IGNvbnNpZGVycyBiaW5hcnkgc3BsaXQpLiBUaGUgR2luaSBJbmRleCBtZWFzdXJlcyB0aGUgaW1wdXJpdHkgb2Ygc3ViZ3JvdXBzIChEKSBzcGxpdCBieSBhIGZlYXR1cmUgdmFyaWFibGUuDQoNCiQkDQpcbWJveHtHaW5pfShEKT0gXHN1bV97aT0xfV5tIHBfaSgxLXBfaSk9IDEgLSBcc3VtX3tpPTF9Xm0gcF9pXjINCiQkDQpXaGVyZSAkcF9pJCAgaXMgdGhlIHByb2JhYmlsaXR5IG9mIGFuIG9iamVjdCB0aGF0IGlzIGJlaW5nIGNsYXNzaWZpZWQgdG8gYSBwYXJ0aWN1bGFyIGNsYXNzLg0KDQoNCkZvciBleGFtcGxlLCB3ZSBjYWxjdWxhdGUgdGhlIEdpbmkgaW5kZXggdXNpbmcgdGhlIGFib3ZlIGRlY2lzaW9uIHRyZWUuIFRoZSByb290IG5vZGUgKGFnZSkgaGFzIHRocmVlIGNoaWxkIG5vZGVzLiBXZSBzaG93IGhvdyB0byBjYWxjdWxhdGUgdGhlIHdlaWdodGVkIEdpbmkgaW5kZXggb2YgZmVhdHVyZSB2YXJpYWJsZSBhZ2UgaW4gdGhlIGZvbGxvd2luZyBzdGVwcy4NCg0KKiAqKldlaWdodHMqKjogUCh5b3V0aCkgPSA1LzE0LCBQKG1pZGRsZV9hZ2VkKSA9IDQvMTQsIFAoc2VuaW9yKSA9IDUvMTQuDQoNCiogKipEID0gWW91dGgqKjogJHBfMSA9UChZZXMpID0gMi81LCBwXzIgPSBQKE5vKSA9IDMvNSQsIHRoZXJlZm9yZSwgJHtHaW5pfV97eW91dGh9ID0gMSAtcF8xXjIgLSBwXzJeMiA9IDEgLSA0LzI1IC0gOS8yNSA9IDEyLzI1JA0KDQoqICoqRCA9IE1pZGRsZV9hZ2VkKio6ICRwXzEgPVAoWWVzKSA9IDQvNCwgcF8yID0gUChObykgPSAwLzQ1JCwgdGhlcmVmb3JlLCAke0dpbml9X3ttaWRkbGVfYWdlZH0gPSAxIC1wXzFeMiAtIHBfMl4yID0gMSAtIDE2LzE2IC0gMC8xNiA9IDAkDQoNCiogKipEID0gU2VuaW9yKio6ICRwXzEgPVAoWWVzKSA9IDMvNSwgcF8yID0gUChObykgPSAyLzUkLCB0aGVyZWZvcmUsICR7R2luaX1fe3Nlbmlvcn0gPSAxIC1wXzFeMiAtIHBfMl4yID0gMSAtIDkvMjUgLSA0LzI1ID0gMTIvMjUkDQoNCg0KVGhlICoqR2luaSBpbmRleCoqIG9mIGFnZSBpcyBnaXZlbiBieSANCg0KJCQNCntHaW5pfV97YWdlfSA9XGZyYWN7NX17MTR9XHRpbWVzXGZyYWN7MTJ9ezI1fSArIFxmcmFjezR9ezE0fVx0aW1lcyAwICtcZnJhY3s1fXsxNH1cdGltZXMgXGZyYWN7MTJ9ezI1fSA9IFxmcmFjezV9ezE0fVx0aW1lcyBcZnJhY3syNH17MjV9ICA9IFxmcmFjezEyfXszNX0gXGFwcHJveCAgMC4zNDMuDQokJA0KDQpcDQoNCioqUiBGdW5jdGlvbiBmb3IgR0lOSSBJbmRleCoqDQoNCg0KYGBge3J9DQpHSU5JLmNhbGMgPSBmdW5jdGlvbihEYXROYW1lLCBWYXJOYW1lLCBDbHNOYW1lKXsNCiAgICMgDQogICBmcmVxVEIwID0gdGFibGUoRGF0TmFtZVssVmFyTmFtZV0sIERhdE5hbWVbLENsc05hbWVdKQ0KICAgZnJlcVRCID0gZGF0YS5mcmFtZShOTyA9IGZyZXFUQjBbLCAxXSwgWUVTID0gZnJlcVRCMFssIDJdKQ0KICAgZnJlcVRCJFRvdCA9IGZyZXFUQiROTyArIGZyZXFUQiRZRVMNCiAgIGZyZXFUQiRQMSA9IGZyZXFUQiROTy9mcmVxVEIkVG90DQogICBmcmVxVEIkUDIgPSBmcmVxVEIkWUVTL2ZyZXFUQiRUb3QNCiAgIGZyZXFUQiRDYXRlR0lOSSA9IDEtKGZyZXFUQiRQMSleMiAtIChmcmVxVEIkUDIpXjINCiAgIGZyZXFUQiRST1dQRVIgPSAoZnJlcVRCJE5PICsgZnJlcVRCJFlFUykvc3VtKGZyZXFUQiRUb3QpDQogICBmcmVxVEIkQ29tcG9uZW50R2luaSA9IChmcmVxVEIkQ2F0ZUdJTkkpICogKGZyZXFUQiRST1dQRVIpDQogICBHSU5JLmlkeCA9IHN1bShmcmVxVEIkQ29tcG9uZW50R2luaSkNCiAgIEdJTkkuaWR4DQp9DQpgYGANCg0KXA0KDQpgYGB7cn0NCmdpbmlBZ2UgPSBHSU5JLmNhbGMoRGF0TmFtZT1EYXRhU2V0LCBWYXJOYW1lPSJBZ2UiLCBDbHNOYW1lID0gIkNsYXNzIikgDQpnaW5pSW5jb21lID0gR0lOSS5jYWxjKERhdE5hbWU9RGF0YVNldCwgVmFyTmFtZT0iSW5jb21lIiwgQ2xzTmFtZSA9ICJDbGFzcyIpIA0KZ2luaVN0dWRlbnQgPSBHSU5JLmNhbGMoRGF0TmFtZT1EYXRhU2V0LCBWYXJOYW1lPSJTdHVkZW50IiwgQ2xzTmFtZSA9ICJDbGFzcyIpIA0KZ2luaUNyZWRpdFJhdGluZyA9IEdJTkkuY2FsYyhEYXROYW1lPURhdGFTZXQsIFZhck5hbWU9IkNyZWRpdFJhdGluZyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIENsc05hbWUgPSAiQ2xhc3MiKSANCnBhbmRlcihjYmluZChnaW5pQWdlID0gZ2luaUFnZSwgZ2luaUluY29tZSA9IGdpbmlJbmNvbWUsIA0KICAgICAgICAgICAgIGdpbmlTdHVkZW50ID0gZ2luaVN0dWRlbnQsIGdpbmlDcmVkaXRSYXRpbmcgPSBnaW5pQ3JlZGl0UmF0aW5nKSkNCmBgYA0KDQoNCldlIGNhbiBzaW1pbGFybHkgY2FsY3VsYXRlIHRoZSBHaW5pIGluZGV4IGZvciBvdGhlciBmZWF0dXJlIHZhcmlhYmxlcyBpbiB0aGUgZGF0YSBzZXQuICoqV2hlbiB3ZSBjaG9vc2UgYSBmZWF0dXJlIHZhcmlhYmxlIHRvIGRlZmluZSB0aGUgcm9vdCBub2RlLCB3ZSBjaG9vc2UgdGhlIGZlYXR1cmUgd2l0aCA8Zm9udCBjb2xvciA9ICJyZWQiPnNtYWxsZXN0IEdpbmkgaW5kZXg8L2ZvbnQ+KiouIFRoZSBHaW5pIGluZGV4IGlzIHVzZWQgaW4gdGhlIGNsYXNzaWMgQ0FSVCBhbGdvcml0aG0gYW5kIGlzIHZlcnkgZWFzeSB0byBjYWxjdWxhdGUuDQoNCg0KDQojIyMgRW50cm9weSBhbmQgSW5mb3JtYXRpb24gR2Fpbg0KDQoqKkVudHJvcHkqKiBpcyBhbm90aGVyIGltcHVyaXR5IG1lYXN1cmUgdGhhdCBpcyBkZWZpbmVkIGJ5DQoNCiQkDQpFID0gXHN1bV97aSA9IDF9Xm0gKC1wX2kgXGxvZ18ycF9pKS4NCiQkDQpXaGVyZSAkcF9pJCBpcyB0aGUgc2FtZSBhcyB0aGF0IGRlZmluZWQgaW4gdGhlIEdpbmkgaW5kZXguIFdlIGNhbiBmaW5kIHRoZSBlbnRyb3B5IGF0IHRoZSByb290IG5vZGUgYW5kIGVhY2ggY2hpbGQgbm9kZSBiYXNlZCBvbiB0aGUgYWJvdmUgdHJlZSBiYXNlZCBvbiB0aGUgdG95IGRhdGEuIEEgbG93IEVudHJvcHkgaW5kaWNhdGVzIHRoYXQgdGhlIGRhdGEgbGFiZWxzIGFyZSBxdWl0ZSB1bmlmb3JtLg0KDQoqICoqcm9vdCAocGFyZW50KSBub2RlIGVudHJvcHkgKGJlZm9yZSBzcGxpdHRpbmcpKio6ICRFKFxtYm94e0R9KSA9IC0oNS8xNClcbG9nXzIoNS8xNCkgLSAoOS8xNClcbG9nXzIoOS8xNCkgPSAwLjk0MDI4NiQNCg0KKiAqKmNoaWxkIG5vZGU6IHlvdXRoKio6ICRFKFxtYm94e0R9KSA9IC0oMi81KVxsb2dfMigyLzUpIC0gKDMvNSlcbG9nXzIoMy81KSA9IDAuOTcwOTUwNiQNCg0KKiAqKmNoaWxkIG5vZGU6IG1pZGRsZV9hZ2VkKio6IHBlcmZlY3RseSBwdXJlIG5vZGUgaGFkIGVudHJvcHkgMC4NCg0KKiAqKmNoaWxkIG5vZGU6IHNlbmlvcioqOiAkRShcbWJveHtEfSkgPSAtKDMvNSlcbG9nXzMoMi81KSAtICgyLzUpXGxvZ18yKDMvNSkgPSAwLjk3MDk1MDYkDQoNCiogKipXZWlnaHRlZCBhdmVyYWdlIG9mIGVudHJvcHkgYXQgY2hpbGQgbm9kZXMqKjogJEUoXG1ib3h7Y2hpbGR9KSA9IFxmcmFjezV9ezE0fVx0aW1lcyAwLjk3MDk1MDYgKyBcZnJhY3s0fXsxNH1cdGltZXMgMCArXGZyYWN7NX17MTR9XHRpbWVzIDAuOTcwOTUwNiA9IFxmcmFjezV9ezE0fVx0aW1lcyAwLjk3MDk1MDYgXGFwcHJveCAwLjM0Njc2ODEkDQoNClwNCg0KKiA8Zm9udCBjb2xvciA9ICJyZWQiPioqSW5mb3JtYXRpb24gR2FpbioqPC9mb250PjogJFxtYm94e0luZm9HYWlufSA9IEUoXG1ib3h7UGFyZW50IE5vZGV9KSAtIEUoXG1ib3h7Q2hpbGQgTm9kZXN9KSA9IDAuOTQwMjg2IC0gMC4zNDY3NjgxID0gMC41OTM1MTc5LiQNCg0KXA0KDQoqKkluZm9ybWF0aW9uIGdhaW4qKiBtZWFzdXJlcyB3aGV0aGVyIGEgZnVydGhlciBzcGxpdCBpcyB3b3J0aHdoaWxlLiANCg0KYGBge3J9DQppbmZvR2Fpbi5jYWxjID0gZnVuY3Rpb24oRGF0TmFtZSwgVmFyTmFtZSwgQ2xzTmFtZSl7DQogICBmcmVxVEIwID0gdGFibGUoRGF0TmFtZVssVmFyTmFtZV0sIERhdE5hbWVbLENsc05hbWVdKQ0KICAgZnJlcVRCID0gZGF0YS5mcmFtZShOTyA9IGZyZXFUQjBbLCAxXSwgWUVTID0gZnJlcVRCMFssIDJdKQ0KICAgZnJlcVRCJFRvdCA9IGZyZXFUQiROTyArIGZyZXFUQiRZRVMNCiAgIGZyZXFUQiRQMSA9IGZyZXFUQiROTy9mcmVxVEIkVG90DQogICBmcmVxVEIkUDIgPSBmcmVxVEIkWUVTL2ZyZXFUQiRUb3QNCiAgICMjIw0KICAgZnJlcVRCJFJPV1BFUiA9IChmcmVxVEIkTk8gKyBmcmVxVEIkWUVTKS9zdW0oZnJlcVRCJFRvdCkNCiAgICMjIyBEZWxldGUgemVybyBjZWxsIHByb2IgdG8gY2FsY3VsYXRlIHRoZSBlbnRyb3B5DQogICBwTk8gPSBzdW0oZnJlcVRCJE5PKS9zdW0oZnJlcVRCJFRvdCkNCiAgIHBZRVMgPSBzdW0oZnJlcVRCJFlFUykvc3VtKGZyZXFUQiRUb3QpDQogICBwcm9wWUVTID0gZnJlcVRCJFlFUy9zdW0oZnJlcVRCJFRvdCkNCiAgIFBhcmVudEVudCA9IC1wTk8qbG9nMihwTk8pIC1wWUVTICpsb2cyKHBZRVMpIA0KICAgIyMjIGVudHJvcHkgb2YgY2hpbGQgbm9kZXMNCiAgIFAxID0gZnJlcVRCJFAxDQogICBQMiA9IGZyZXFUQiRQMg0KICAgbG9nUDEgPSBsb2cyKGZyZXFUQiRQMSkNCiAgIGxvZ1AyID0gbG9nMihmcmVxVEIkUDIpDQogICBsb2dQMVt3aGljaCghaXMuZmluaXRlKGxvZ1AxKSldID0gMA0KICAgbG9nUDJbd2hpY2goIWlzLmZpbml0ZShsb2dQMikpXSA9IDANCiAgIENoaWxkRW50ID0gKC1QMSpsb2dQMSAtIFAyKmxvZ1AyKQ0KICAgIyMjDQogICBpbmZvR2FpbiA9UGFyZW50RW50IC0gc3VtKENoaWxkRW50KnByb3BZRVMpDQogICAjaW5mby5HYWluID0gc3VtKGZyZXFUQiRpbmZvR2FpbikNCiAgIGxpc3QoUGFyZW50RW50ID0gUGFyZW50RW50LCBDaGlsZEVudCA9IENoaWxkRW50LCANCiAgICAgICAgcHJvcFlFUyA9IHByb3BZRVMsIGluZm9HYWluID0gaW5mb0dhaW4pICANCn0NCmBgYA0KDQpgYGB7cn0NCmluZm9HYWluLmNhbGMoRGF0TmFtZT1EYXRhU2V0LCBWYXJOYW1lPSJBZ2UiLCBDbHNOYW1lID0gIkNsYXNzIikgDQpgYGANCmBgYHtyfQ0KZW50QWdlID0gaW5mb0dhaW4uY2FsYyhEYXROYW1lPURhdGFTZXQsIFZhck5hbWU9IkFnZSIsIENsc05hbWUgPSAiQ2xhc3MiKSRpbmZvR2FpbiANCmVudEluY29tZSA9IGluZm9HYWluLmNhbGMoRGF0TmFtZT1EYXRhU2V0LCBWYXJOYW1lPSJJbmNvbWUiLCBDbHNOYW1lID0gIkNsYXNzIikkaW5mb0dhaW4gDQplbnRTdHVkZW50ID0gaW5mb0dhaW4uY2FsYyhEYXROYW1lPURhdGFTZXQsIFZhck5hbWU9IlN0dWRlbnQiLCBDbHNOYW1lID0gIkNsYXNzIikkaW5mb0dhaW4gDQplbnRDcmVkaXRSYXRpbmcgPSBpbmZvR2Fpbi5jYWxjKERhdE5hbWU9RGF0YVNldCwgVmFyTmFtZT0iQ3JlZGl0UmF0aW5nIiwgQ2xzTmFtZSA9ICJDbGFzcyIpJGluZm9HYWluIA0KcGFuZGVyKGNiaW5kKGluZm9HYWluQWdlID0gZW50QWdlLCBpbmZvR2FpbkluY29tZSA9IGVudEluY29tZSwgaW5mb0dhaW5TdHVkZW50ID0gZW50U3R1ZGVudCwgaW5mb0dhaW5DcmVkaXRSYXRpbmcgPSBlbnRDcmVkaXRSYXRpbmcpKQ0KYGBgDQoNClwNCg0KIyMgQmluYXJ5IHYucy4gTXVsdGktd2F5IFNwbGl0cw0KDQpJbiBwcmluY2lwbGUsIHRyZWVzIGFyZSBub3QgcmVzdHJpY3RlZCB0byBiaW5hcnkgc3BsaXRzIGJ1dCBjYW4gYWxzbyBiZSBncm93biB3aXRoIG11bHRpLXdheSBzcGxpdHMgLSBiYXNlZCBvbiB0aGUgR2luaSBpbmRleCBvciBvdGhlciBzZWxlY3Rpb24gY3JpdGVyaWEuIEhvd2V2ZXIsIHRoZSAobG9jYWxseSBvcHRpbWFsKSBzZWFyY2ggZm9yIG11bHRpLXdheSBzcGxpdHMgaW4gbnVtZXJpYyB2YXJpYWJsZXMgd291bGQgYmVjb21lIG11Y2ggbW9yZSBidXJkZW5zb21lLiBIZW5jZSwgdHJlZSBhbGdvcml0aG1zIG9mdGVuIHJlbHkgb24gdGhlIGdyZWVkeSBmb3J3YXJkIHNlbGVjdGlvbiBvZiBiaW5hcnkgc3BsaXRzIHdoZXJlIHN1YnNlcXVlbnQgYmluYXJ5IHNwbGl0cyBpbiB0aGUgc2FtZSB2YXJpYWJsZSBjYW4gYWxzbyByZXByZXNlbnQgbXVsdGktd2F5IHNwbGl0cy4gDQoNClwNCg0KDQojIyBCb29zdGVkIFRyZWVzIC0gRW5zZW1ibGUgQWxnb3JpdGhtcw0KDQpFbnNlbWJsZSBtZXRob2RzIGFyZSBtb2RlbHMgY29tcG9zZWQgb2YgbXVsdGlwbGUgd2Vha2VyIG1vZGVscyB0aGF0IGFyZSBpbmRlcGVuZGVudGx5IHRyYWluZWQgYW5kIHdob3NlIHByZWRpY3Rpb25zIGFyZSBjb21iaW5lZCBpbiBzb21lIHdheSB0byBtYWtlIHRoZSBvdmVyYWxsIHByZWRpY3Rpb24uDQogDQpNdWNoIGVmZm9ydCBpcyBwdXQgaW50byB3aGF0IHR5cGVzIG9mIHdlYWsgbGVhcm5lcnMgdG8gY29tYmluZSBhbmQgdGhlIHdheXMgaW4gd2hpY2ggdG8gY29tYmluZSB0aGVtLiBUaGlzIGlzIGEgdmVyeSBwb3dlcmZ1bCBjbGFzcyBvZiB0ZWNobmlxdWVzIGFuZCBhcyBzdWNoIGlzIHZlcnkgcG9wdWxhci4NCg0KIyMjCUJvb3RzdHJhcHBlZCBBZ2dyZWdhdGlvbiAoQmFnZ2luZykNCg0KV2l0aCB0aGUgdW5kZXJzdGFuZGluZyBvZiByZWd1bGFyIGRlY2lzaW9ucywgd2UgY2FuIA0KDQoNCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTMsIGZpZy5oZWlnaHQ9MywgZmlnLmNhcD0iRmlndXJlIDEyLiAgRGVtb25zdHJhdGlvbiBvZiBib290c3RyYXAgYWdncmVnYXRpb24gYWxnb3JpdGhtLiJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdzA3LUJvb3RzdHJhcEFnZ3JlZ2F0aW9uLmpwZyIpDQpgYGANCg0KDQojIyMJUmFuZG9tIEZvcmVzdA0KDQpSYW5kb20gZm9yZXN0IChSRikgYWxnb3JpdGhtcyBtYWtlIG91dHB1dCBwcmVkaWN0aW9ucyBieSBjb21iaW5pbmcgb3V0Y29tZXMgZnJvbSBhIHNlcXVlbmNlIG9mIGRlY2lzaW9uIHRyZWVzLiBFYWNoIHRyZWUgaXMgY29uc3RydWN0ZWQgaW5kZXBlbmRlbnRseSBhbmQgZGVwZW5kcyBvbiBhIHJhbmRvbSB2ZWN0b3Igc2FtcGxlZCBmcm9tIHRoZSBpbnB1dCBkYXRhLCB3aXRoIGFsbCB0aGUgdHJlZXMgaW4gdGhlIGZvcmVzdCBoYXZpbmcgdGhlIHNhbWUgZGlzdHJpYnV0aW9uLiBUaGUgcHJlZGljdGlvbnMgZnJvbSB0aGUgZm9yZXN0cyBhcmUgYXZlcmFnZWQgdXNpbmcgYm9vdHN0cmFwIGFnZ3JlZ2F0aW9uIGFuZCByYW5kb20gZmVhdHVyZSBzZWxlY3Rpb24uIFJGIG1vZGVscyBoYXZlIGJlZW4gZGVtb25zdHJhdGVkIHRvIGJlIHJvYnVzdCBwcmVkaWN0b3JzIGZvciBib3RoIHNtYWxsIHNhbXBsZSBzaXplcyBhbmQgaGlnaCBkaW1lbnNpb25hbCBkYXRhLiANCiANClRoZSBmb2xsb3dpbmcgZGlhZ3JhbSBpbGx1c3RyYXRlcyBob3cgUkYgd2FzIGNvbnN0cnVjdGVkIGFuZCBob3cgdGhlIGRlY2lzaW9uIGlzIG1hZGUgYmFzZWQgb24gdGhlIHNldCBvZiBpbmRpdmlkdWFsIHRyZWVzLg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTMsIGZpZy5jYXA9IkZpZ3VyZSAxMy4gIERlbW9uc3RyYXRpb24gb2YgcmFuZG9tIGZvcmVzdCBhbGdvcml0aG0uIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy93MDctUmFuZG9tRm9yZXN0cy5qcGciKQ0KYGBgDQogDQogDQojIENhc2UgU3R1ZHkgLSBQcmVkaWN0aW5nIERpYWJldGVzDQogDQpUaGlzIGlzIGEgbmV3IG1vZGVsIHRoYXQgaXMgZGlmZmVyZW50IGZyb20gbG9naXN0aWMgYW5kIG5ldXJhbCBuZXR3b3JrIG1vZGVscy4gV2UgbG9hZCB0aGUgYW5hbHl0aWMgZGF0YSBzZXQuDQoNCg0KYGBge3J9DQpQaW1hID0gcmVhZC5jc3YoImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1MS93MDkvQW5hbHl0aWNQaW1hRGlhYmV0ZXMuY3N2IilbLC0xXQ0KIyBXZSB1c2UgYSByYW5kb20gc3BsaXQgYXBwcm9hY2gNCm4gPSBkaW0oUGltYSlbMV0gICMgc2FtcGxlIHNpemUNCiMgY2F1dGlvbjogdXNpbmcgd2l0aG91dCByZXBsYWNlbWVudA0KdHJhaW4uaWQgPSBzYW1wbGUoMTpuLCByb3VuZCgwLjcqbiksIHJlcGxhY2UgPSBGQUxTRSkgIA0KdHJhaW4gPSBQaW1hW3RyYWluLmlkLCBdICAgICMgdHJhaW5pbmcgZGF0YQ0KdGVzdCA9IFBpbWFbLXRyYWluLmlkLCBdICAgICMgdGVzdGluZyBkYXRhDQpgYGANCg0KIA0KIyMgYHJwYXJ0YCBMaWJyYXJ5IA0KDQp3ZSB3aWxsIGBycGFydCgpYCB0byB3cml0ZSBhIHdyYXBwZXIgc28gd2UgY2FuIHBhc3MgdGhlIGFyZ3VtZW50cyBvZiBwdXJpdHkgbWVhc3VyZXMgYW5kIHBlbmFsdHkgbWVhc3VyZXMgdG8gY29uc3RydWN0IGRpZmZlcmVudCBkZWNpc2lvbiB0cmVlcy4gVGhlIGNyb3NzLXZhbGlkYXRpb24gbWV0aG9kIHdpbGwgYmUgdXNlZCB0byBzZWxlY3QgdGhlIG9wdGltYWwgZGVjaXNpb24gdHJlZSBhcyB0aGUgY2FuZGlkYXRlIHByZWRpY3RpdmUgdG8gY29tcGFyZSB3aXRoIHRoZSBsb2dpc3RpYyBtb2RlbCBpbiB0aGUgcHJldmlvdXMgc2VjdGlvbi4NCg0KPiBOb3RlIHRoYXQgYHJwYXJ0KClgIGhhcyBhIGNvbnRyb2wgb3B0aW9uIHRoYXQgYWxsb3dzIHVzZXJzIHRvIHNldCB1cCB2YXJpb3VzIGNvbnRyb2wgcGFyYW1ldGVycyAoc28tY2FsbGVkIGh5cGVyLXBhcmFtZXRlcnMpIHRvIGFsbG93IHRoZSBmdW5jdGlvbiB0byBpZGVudGlmeSB0aGUgb3B0aW1hbCB0cmVlLiBPbmUgb2YgdGhvc2UgY29udHJvbCBwYXJhbWV0ZXJzIGlzIHRoZSBudW1iZXIgb2YgY3Jvc3MtdmFsaWRhdGlvbiB3aGVuIHBydW5pbmcgdGhlIGRlY2lzaW9uLiBPbmNlIGB4dmFsYCBpcyBzcGVjaWZpZWQsIGBycGFydCgpYCBwcnVuZXMgdGhlIHRyZWUgYXV0b21hdGljYWxseSBiYXNlZCBvbiB0aGUgZ2l2ZW4gY29udHJvbCBwYXJhbWV0ZXJzLiBUaGlzIGlzIGludGVybmFsIGstZm9sZCBjcm9zcy12YWxpZGF0aW9uIGZvciBpZGVudGlmeWluZyBhbiBvcHRpbWFsIHRyZWUgYmFzZWQgb24gdGhlIGluZm9ybWF0aW9uIHByb3ZpZGVkIGluIHRoZSBhcmd1bWVudCBgcGFybXNgIGluIHdoaWNoIHRoZSBwdXJpdHkgbWVhc3VyZXMgYW5kIHBlbmFsdHkgbWF0cml4LiBNb3JlIGluZm9ybWF0aW9uIGNhbiBiZSBmb3VuZCBpbiB0aGUgYXJ0aWNsZSA8aHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3JwYXJ0L3ZpZ25ldHRlcy9sb25naW50cm8ucGRmPg0KDQpcDQoNCioqcnBhcnQoKSoqIHN5bnRheA0KDQpgYGB7fQ0KIHRyZWUgPSBycGFydChtb2RlbEZvcm11bGEsICAgICAgICAgICMgbW9kZWwgZm9ybXVsYSBzaW1pbGFyIHRvIHRoYXQgaW4gdGhlIGxvZ2lzdGljIG1vZGVscw0KICAgICAgICAgICAgIGRhdGEgLCANCiAgICAgICAgICAgICBuYS5hY3Rpb24gID0gbmEucnBhcnQsICAjIEJ5IGRlZmF1bHQsIGRlbGV0ZWQgaWYgdGhlIG91dGNvbWUgaXMgbWlzc2luZywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBrZXB0IGlmIGZlYXR1cmVzIGFyZSBtaXNzaW5nDQogICAgICAgICAgICAgbWV0aG9kID0gImNsYXNzIiwgICAgICAgIyBDbGFzc2lmaWNhdGlvbiBmb3JtIGZhY3Rvcg0KICAgICAgICAgICAgIG1vZGVsICA9IEZBTFNFLCAgICAgICAgICMga2VlcCBhIGNvcHkgb2YgdGhlIG1vZGVsIGZyYW1lIGluIHRoZSByZXN1bHQ/IEkNCiAgICAgICAgICAgICAgICAgIHggPSBGQUxTRSwgICAgICAgICAjIGtlZXAgYSBjb3B5IG9mIHRoZSB4IG1hdHJpeCBpbiB0aGUgcmVzdWx0Lg0KICAgICAgICAgICAgICAgICAgeSA9IFRSVUUsICAgICAgICAgICMga2VlcCBhIGNvcHkgb2YgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSBpbiB0aGUgcmVzdWx0LiANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIElmIG1pc3NpbmcgYW5kIG1vZGVsIGlzIHN1cHBsaWVkIHRoaXMgZGVmYXVsdHMgdG8gRkFMU0UNCiAgICAgICAgICAgICAgcGFybXMgPSBsaXN0KCAjIGxvc3MgbWF0cml4LiBQZW5hbGl6ZSBmYWxzZSBwb3NpdGl2ZSBvciBuZWdhdGl2ZSBtb3JlIGhlYXZpbHkNCiAgICAgICAgICAgICAgICAgICAgICAgICBsb3NzID0gbWF0cml4KGMoMCxiLGMsMCksIG5jb2wgPSAyKSwgICMgYiA9IEZQLCBjID0gRk4NCiAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdCA9IHB1cml0eSksICAgIyAiZ2luaSIgb3IgImluZm9ybWF0aW9uIg0KICAgICAgICAgICAgIA0KICAgICAgICAgICAgICMjIHJwYXJ0IGFsZ29yaXRobSBvcHRpb25zIChUaGVzZSBhcmUgZGVmYXVsdHMpDQogICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgIG1pbnNwbGl0ID0gMjAsICAgICAgICMgbWluaW11bSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIHJlcXVpcmVkIGJlZm9yZSBzcGxpdA0KICAgICAgICAgICAgICAgICAgICAgICBtaW5idWNrZXQ9IDEwLCAgICAgICAjIG1pbmltdW0gbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBhbnkgdGVybWluYWwgbm9kZSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGRlZmF1bHQgPSBtaW5zcGxpdC8zDQogICAgICAgICAgICAgICAgICAgICAgIGNwICA9IDAuMDEsICAgICAgICAgICMgY29tcGxleGl0eSBwYXJhbWV0ZXIgdXNlZCBhcyB0aGUgc3RvcHBpbmcgcnVsZSwgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIDAuMDIgLT4gc21hbGwgdHJlZQ0KICAgICAgICAgICAgICAgICAgICAgICBtYXhjb21wZXRlICA9IDQsICAgICAjIG51bWJlciBvZiBjb21wZXRpdG9yIHNwbGl0cyByZXRhaW5lZCBpbiB0aGUgb3V0cHV0DQogICAgICAgICAgICAgICAgICAgICAgIG1heHN1cnJvZ2F0ZSAgID0gNSwgICMgbnVtYmVyIG9mIHN1cnJvZ2F0ZSBzcGxpdHMgcmV0YWluZWQgaW4gdGhlIG91dHB1dA0KICAgICAgICAgICAgICAgICAgICAgICB1c2VzdXJyb2dhdGUgICA9IDIsICAjIGhvdyB0byB1c2Ugc3Vycm9nYXRlcyBpbiB0aGUgc3BsaXR0aW5nIHByb2Nlc3MNCiAgICAgICAgICAgICAgICAgICAgICAgeHZhbCAgID0gMTAsICAgICAgICAgIyBudW1iZXIgb2YgY3Jvc3MtdmFsaWRhdGlvbnMNCiAgICAgICAgICAgICAgICAgICAgICAgc3Vycm9nYXRlc3R5bGUgPSAwLCAgIyBjb250cm9scyB0aGUgc2VsZWN0aW9uIG9mIHRoZSBiZXN0IHN1cnJvZ2F0ZQ0KICAgICAgICAgICAgICAgICAgICAgICBtYXhkZXB0aCAgPSAzMCAgICAgICAjIG1heGltdW0gZGVwdGggb2YgYW55IG5vZGUgb2YgdGhlIGZpbmFsIHRyZWUpDQogICAgICApDQpgYGANCg0KXA0KDQpgcnBhcnQoKWAgaGFzIGEgbG90IG9mIGZsZXhpYmlsaXR5IHRvIGNvbnN0cnVjdCBkZWNpc2lvbiB0cmVlcyBhcyBpdCBoYXMgdXNlciBjb250cm9scy4gSXQgaXMgcGFydGljdWxhcmx5IHVzZWZ1bCBpbiBhcHBsaWNhdGlvbnMgd2hlcmUgdGhlIGNvc3RzIG9mIGBmYWxzZSBwb3NpdGl2ZWAgYW5kIGBmYWxzZSBuZWdhdGl2ZWAgYXJlIGRpZmZlcmVudC4gDQoNCk5leHQsIHdlIHdyaXRlIGEgd3JhcHBlciBzbyB3ZSBjYW4gYnVpbGQgZGlmZmVyZW50IGRlY2lzaW9uIHRyZWVzIGNvbnZlbmllbnRseS4NCg0KYGBge3J9DQojIGFyZ3VtZW50cyB0byBwYXNzIGludG8gcnBhcnQoKToNCiMgMS4gZGF0YSBzZXQgKHRyYWluaW5nIC90ZXN0aW5nKTsgDQojIDIuIFBlbmFsdHkgY29lZmZpY2llbnRzDQojIDMuIEltcHVyaXR5IG1lYXN1cmUNCiMjIA0KdHJlZS5idWlsZGVyID0gZnVuY3Rpb24oaW4uZGF0YSwgZnAsIGZuLCBwdXJpdHkpew0KICAgdHJlZSA9IHJwYXJ0KGRpYWJldGVzIH4gLiwgICAgICAgICAgICAgICAgIyBpbmNsdWRpbmcgYWxsIGZlYXR1cmVzDQogICAgICAgICAgICAgICAgZGF0YSA9IGluLmRhdGEsIA0KICAgICAgICAgICAgICAgIG5hLmFjdGlvbiAgPSBuYS5ycGFydCwgICAgICAgIyBCeSBkZWZhdWx0LCBkZWxldGVkIGlmIHRoZSBvdXRjb21lIGlzIG1pc3NpbmcsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBrZXB0IGlmIHByZWRpY3RvcnMgYXJlIG1pc3NpbmcNCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2xhc3MiLCAgICAgICAgICAgICMgQ2xhc3NpZmljYXRpb24gZm9ybSBmYWN0b3INCiAgICAgICAgICAgICAgICBtb2RlbCAgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICB4ID0gRkFMU0UsDQogICAgICAgICAgICAgICAgeSA9IFRSVUUsDQogICAgICAgICAgICBwYXJtcyA9IGxpc3QoICMgbG9zcyBtYXRyaXguIFBlbmFsaXplIGZhbHNlIHBvc2l0aXZlcyBvciBuZWdhdGl2ZXMgbW9yZSBoZWF2aWx5DQogICAgICAgICAgICAgICAgICAgICAgICAgbG9zcyA9IG1hdHJpeChjKDAsIGZwLCBmbiwgMCksIG5jb2wgPSAyLCBieXJvdyA9IFRSVUUpLCAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gcHVyaXR5KSwgICAgICAgICAgIyAiZ2luaSIgb3IgImluZm9ybWF0aW9uIg0KICAgICAgICAgICAgICMjIHJwYXJ0IGFsZ29yaXRobSBvcHRpb25zIChUaGVzZSBhcmUgZGVmYXVsdHMpDQogICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICBtaW5zcGxpdCA9IDEwLCAgIyBtaW5pbXVtIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgcmVxdWlyZWQgYmVmb3JlIHNwbGl0DQogICAgICAgICAgICAgICAgICAgICAgICBtaW5idWNrZXQ9IDEwLCAgIyBtaW5pbXVtIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBhbnkgdGVybWluYWwgbm9kZSwgZGVmYXVsdCA9IG1pbnNwbGl0LzMNCiAgICAgICAgICAgICAgICAgICAgICAgIGNwICA9IDAuMDEsICAjIGNvbXBsZXhpdHkgcGFyYW1ldGVyIGZvciBzdG9wcGluZyBydWxlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIDAuMDIgLT4gc21hbGwgdHJlZSANCiAgICAgICAgICAgICAgICAgICAgICAgeHZhbCA9IDEwICAgICAjIG51bWJlciBvZiBjcm9zcy12YWxpZGF0aW9uICkNCiAgICAgICAgICAgICAgICAgICAgICAgICkNCiAgICAgICAgICAgICApDQogIH0NCmBgYA0KDQoNClVzaW5nIHRoZSBhYm92ZSBmdW5jdGlvbiwgd2UgZGVmaW5lIHNpeCBkaWZmZXJlbnQgZGVjaXNpb24gdHJlZSBtb2RlbHMgaW4gdGhlIGZvbGxvd2luZy4NCg0KKiBNb2RlbCAxOiBgZ2luaS50cmVlLjExYCBpcyBiYXNlZCBvbiB0aGUgR2luaSBpbmRleCB3aXRob3V0IHBlbmFsaXppbmcgZmFsc2UgcG9zaXRpdmVzIGFuZCBmYWxzZSBuZWdhdGl2ZXMuDQoNCiogTW9kZWwgMjogYGluZm8udHJlZS4xMWAgaXMgYmFzZWQgb24gZW50cm9weSB3aXRob3V0IHBlbmFsaXppbmcgZmFsc2UgcG9zaXRpdmVzIGFuZCBmYWxzZSBuZWdhdGl2ZXMuDQoNCiogTW9kZWwgMzogYGdpbmkudHJlZS4xMTBgIGlzIGJhc2VkIG9uIHRoZSBHaW5pIGluZGV4OiB0aGUgY29zdCBvZiBmYWxzZSBuZWdhdGl2ZXMgaXMgMTAgdGltZXMgdGhlIHBvc2l0aXZlcy4NCg0KKiBNb2RlbCA0OiBgaW5mby50cmVlLjExMGAgaXMgYmFzZWQgb24gZW50cm9weTogdGhlIGNvc3Qgb2YgZmFsc2UgbmVnYXRpdmVzIGlzIDEwIHRpbWVzIHRoZSBwb3NpdGl2ZXMuDQoNCiogTW9kZWwgNTogYGdpbmkudHJlZS4xMDFgIGlzIGJhc2VkIG9uIHRoZSBHaW5pIGluZGV4OiB0aGUgY29zdCBvZiBhIGZhbHNlIHBvc2l0aXZlIGlzIDEwIHRpbWVzIHRoZSBuZWdhdGl2ZXMuICANCg0KKiBNb2RlbCA2OiBgaW5mby50cmVlLjEwMWAgaXMgYmFzZWQgb24gZW50cm9weTogdGhlIGNvc3Qgb2YgYSBmYWxzZSBwb3NpdGl2ZSBpcyAxMCB0aW1lcyB0aGUgbmVnYXRpdmVzLiANCg0KDQpUaGUgdHJlZSBkaWFncmFtIG9mIHRoZSBhYm92ZSB0d28gcmVndWxhciBkZWNpc2lvbiBtb2RlbHMgaXMgZ2l2ZW4gYmVsb3cuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTUsIGZpZy5jYXA9IkZpZ3VyZSAxNC4gTm9uLXBlbmFsaXplZCBkZWNpc2lvbiB0cmVlIG1vZGVscyB1c2luZyBHaW5pIGluZGV4IChsZWZ0KSBhbmQgZW50cm9weSAocmlnaHQpLiJ9DQojIyBDYWxsIHRoZSB0cmVlIG1vZGVsIHdyYXBwZXIuDQpnaW5pLnRyZWUuMS4xID0gdHJlZS5idWlsZGVyKGluLmRhdGEgPSB0cmFpbiwgZnAgPSAxLCBmbiA9IDEsIHB1cml0eSA9ICJnaW5pIikNCmluZm8udHJlZS4xLjEgPSB0cmVlLmJ1aWxkZXIoaW4uZGF0YSA9IHRyYWluLCBmcCA9IDEsIGZuID0gMSwgcHVyaXR5ID0gImluZm9ybWF0aW9uIikNCmdpbmkudHJlZS4xLjEwID0gdHJlZS5idWlsZGVyKGluLmRhdGEgPSB0cmFpbiwgZnAgPSAxLCBmbiA9IDEwLCBwdXJpdHkgPSAiZ2luaSIpDQppbmZvLnRyZWUuMS4xMCA9IHRyZWUuYnVpbGRlcihpbi5kYXRhID0gdHJhaW4sIGZwID0gMSwgZm4gPSAxMCwgcHVyaXR5ID0gImluZm9ybWF0aW9uIikNCiMjIHRyZWUgcGxvdHMNCnBhcihtZnJvdz1jKDEsMikpDQpycGFydC5wbG90KGdpbmkudHJlZS4xLjEsIG1haW4gPSAiVHJlZSB3aXRoIEdpbmkgaW5kZXg6IG5vbi1wZW5hbGl6YXRpb24iKQ0KcnBhcnQucGxvdChpbmZvLnRyZWUuMS4xLCBtYWluID0gIlRyZWUgd2l0aCBlbnRyb3B5OiBub24tcGVuYWxpemF0aW9uIikNCmBgYA0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01LCBmaWcuY2FwPSJGaWd1cmUgMTUuIHBlbmFsaXplZCBkZWNpc2lvbiB0cmVlIG1vZGVscyB1c2luZyBHaW5pIGluZGV4IChsZWZ0KSBhbmQgZW50cm9weSAocmlnaHQpLiJ9DQpwYXIobWZyb3c9YygxLDIpKQ0KcnBhcnQucGxvdChnaW5pLnRyZWUuMS4xMCwgbWFpbiA9ICJUcmVlIHdpdGggR2luaSBpbmRleDogcGVuYWxpemF0aW9uIikNCnJwYXJ0LnBsb3QoaW5mby50cmVlLjEuMTAsIG1haW4gPSAiVHJlZSB3aXRoIGVudHJvcHk6IHBlbmFsaXphdGlvbiIpDQpgYGANCg0KDQojIyBST0MgZm9yIE1vZGVsIFNlbGVjdGlvbg0KDQpXZSBidWlsdCA0IGRpZmZlcmVudCBkZWNpc2lvbiB0cmVlIG1vZGVscyBwcmV2aW91c2x5LiBOZXh0LCB3ZSB1c2UgUk9DIGFuYWx5c2lzIHRvIHNlbGVjdCB0aGUgYmVzdCBhbW9uZyB0aGUgZm91ciBjYW5kaWRhdGUgbW9kZWxzLiANCg0KYGBge3J9DQojIGZ1bmN0aW9uIHJldHVybmluZyBhIHNlbnNpdGl2aXR5IGFuZCBzcGVjaWZpY2l0eSBtYXRyaXgNClNlblNwZSA9IGZ1bmN0aW9uKGluLmRhdGEsIGZwLCBmbiwgcHVyaXR5KXsNCiAgY3V0b2ZmID0gc2VxKDAsMSwgbGVuZ3RoID0gMjApICAgIyAyMCBjdXQtb2ZmcyBpbmNsdWRpbmcgMCBhbmQgMS4gDQogIG1vZGVsID0gdHJlZS5idWlsZGVyKGluLmRhdGEsIGZwLCBmbiwgcHVyaXR5KSANCiAgIyMgQ2F1dGlvbjogZGVjaXNpb24gdHJlZSByZXR1cm5zIGJvdGggInN1Y2Nlc3MiIGFuZCAiZmFpbHVyZSIgcHJvYmFiaWxpdGllcy4NCiAgIyMgV2UgbmVlZCBvbmx5ICJzdWNjZXNzIiBwcm9iYWJpbGl0eSB0byBkZWZpbmUgc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5ISEhIA0KICBwcmVkID0gcHJlZGljdChtb2RlbCwgbmV3ZGF0YSA9IGluLmRhdGEsIHR5cGUgPSAicHJvYiIpICMgdHdvLWNvbHVtbiBtYXRyaXguDQogIHNlbnNwZS5tdHggPSBtYXRyaXgoMCwgbmNvbCA9IGxlbmd0aChjdXRvZmYpLCBucm93PSAyLCBieXJvdyA9IEZBTFNFKQ0KICBmb3IgKGkgaW4gMTpsZW5ndGgoY3V0b2ZmKSl7DQogICMgQ0FVVElPTjogInBvcyIgYW5kICJuZWciIGFyZSB2YWx1ZXMgb2YgdGhlIGxhYmVsIGluIHRoaXMgZGF0YSBzZXQhDQogICMgVGhlIGZvbGxvd2luZyBsaW5lIHVzZXMgb25seSAicG9zIiBwcm9iYWJpbGl0eTogcHJlZFssICJwb3MiXSAhISEhDQogIHByZWQub3V0ID0gIGlmZWxzZShwcmVkWywicG9zIl0gPj0gY3V0b2ZmW2ldLCAicG9zIiwgIm5lZyIpICANCiAgVFAgPSBzdW0ocHJlZC5vdXQgPT0icG9zIiAmIGluLmRhdGEkZGlhYmV0ZXMgPT0gInBvcyIpDQogIFROID0gc3VtKHByZWQub3V0ID09Im5lZyIgJiBpbi5kYXRhJGRpYWJldGVzID09ICJuZWciKQ0KICBGUCA9IHN1bShwcmVkLm91dCA9PSJwb3MiICYgaW4uZGF0YSRkaWFiZXRlcyA9PSAibmVnIikNCiAgRk4gPSBzdW0ocHJlZC5vdXQgPT0ibmVnIiAmIGluLmRhdGEkZGlhYmV0ZXMgPT0gInBvcyIpDQogIHNlbnNwZS5tdHhbMSxpXSA9IFRQLyhUUCArIEZOKQ0KICBzZW5zcGUubXR4WzIsaV0gPSBUTi8oVE4gKyBGUCkNCiAgfQ0KICAjIyBBIGJldHRlciBhcHByb3ggb2YgUk9DLCBuZWVkIGxpYnJhcnkge3BST0N9DQogIHByZWRpY3Rpb24gPSBwcmVkWywgInBvcyJdDQogIGNhdGVnb3J5ID0gaW4uZGF0YSRkaWFiZXRlcyA9PSAicG9zIg0KICBST0NvYmogPC0gcm9jKGNhdGVnb3J5LCBwcmVkaWN0aW9uKQ0KICBBVUMgPSBhdWMoUk9Db2JqKQ0KICAjIw0KICBsaXN0KHNlbnNwZS5tdHg9IHNlbnNwZS5tdHgsIEFVQyA9IHJvdW5kKEFVQywzKSkNCiB9DQpgYGANCg0KVGhlIGFib3ZlIGZ1bmN0aW9uIGhhcyB0aHJlZSBhcmd1bWVudHMgZm9yIHVzZXJzIHRvIGNob29zZSBkaWZmZXJlbnQgdHlwZXMgb2YgZGVjaXNpb24gdHJlZXMgaW5jbHVkaW5nIHRoZSA0IHRyZWVzIGRpc2N1c3NlZCBpbiB0aGUgcHJldmlvdXMgc3Vic2VjdGlvbi4gTmV4dCwgd2UgdXNlIHRoaXMgZnVuY3Rpb24gdG8gYnVpbGQgNiBkaWZmZXJlbnQgdHJlZXMgYW5kIHBsb3QgdGhlaXIgY29ycmVzcG9uZGluZyBST0MgY3VydmVzIHNvIHdlIGNhbiBzZWUgdGhlIGdsb2JhbCBwZXJmb3JtYW5jZSBvZiB0aGVzZSB0cmVlIGFsZ29yaXRobXMuDQoNCg0KYGBge3J9DQpnaW5pUk9DMTEgPSBTZW5TcGUoaW4uZGF0YSA9IHRyYWluLCBmcD0xLCBmbj0xLCBwdXJpdHk9ImdpbmkiKQ0KaW5mb1JPQzExID0gU2VuU3BlKGluLmRhdGEgPSB0cmFpbiwgZnA9MSwgZm49MSwgcHVyaXR5PSJpbmZvcm1hdGlvbiIpDQpnaW5pUk9DMTEwID0gU2VuU3BlKGluLmRhdGEgPSB0cmFpbiwgZnA9MSwgZm49MTAsIHB1cml0eT0iZ2luaSIpDQppbmZvUk9DMTEwID0gU2VuU3BlKGluLmRhdGEgPSB0cmFpbiwgZnA9MSwgZm49MTAsIHB1cml0eT0iaW5mb3JtYXRpb24iKQ0KZ2luaVJPQzEwMSA9IFNlblNwZShpbi5kYXRhID0gdHJhaW4sIGZwPTEwLCBmbj0xLCBwdXJpdHk9ImdpbmkiKQ0KaW5mb1JPQzEwMSA9IFNlblNwZShpbi5kYXRhID0gdHJhaW4sIGZwPTEwLCBmbj0xLCBwdXJpdHk9ImluZm9ybWF0aW9uIikNCmBgYA0KDQpOZXh0LCB3ZSBwbG90IHRoZSBST0MgY3VydmVzIGFuZCBjYWxjdWxhdGUgdGhlIGFyZWFzIHVuZGVyIHRoZSBST0MgY3VydmVzIGZvciBJbmRpdmlkdWFsIGRlY2lzaW9uIHRyZWUgbW9kZWxzLg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTYsIGZpZy5jYXA9IkZpZ3VyZSAxNi4gQ29tcGFyaXNvbiBvZiBST0MgY3VydmVzIn0NCnBhcihwdHk9InMiKSAgICAgICMgc2V0IHVwIHNxdWFyZSBwbG90IHRocm91Z2ggZ3JhcGhpYyBwYXJhbWV0ZXINCmNvbG9ycyA9IGMoIiMwMDhCOEIiLCAiIzAwMDA4QiIsICAiIzhCMDA4QiIsICAiIzhCMDAwMCIsICAiIzhCOEIwMCIsICIjOEI0NTAwIikNCnBsb3QoMS1naW5pUk9DMTEkc2Vuc3BlLm10eFsyLF0sIGdpbmlST0MxMSRzZW5zcGUubXR4WzEsXSwgDQogICAgIHR5cGUgPSAibCIsIA0KICAgICB4bGltPWMoMCwxKSwgDQogICAgIHlsaW09YygwLDEpLCANCiAgICAgeGxhYj0iMSAtIHNwZWNpZmljaXR5OiBGUFIiLCB5bGFiPSJTZW5zaXRpdml0eTogVFBSIiwgDQogICAgIGNvbCA9IGNvbG9yc1sxXSwgDQogICAgIGx3ZCA9IDIsDQogICAgIG1haW49IlJPQyBDdXJ2ZXMgb2YgRGVjaXNpb24gVHJlZXMiLCANCiAgICAgY2V4Lm1haW4gPSAwLjksIA0KICAgICBjb2wubWFpbiA9ICJuYXZ5IikNCmFibGluZSgwLDEsIGx0eSA9IDIsIGNvbCA9ICJvcmNoaWQ0IiwgbHdkID0gMikNCmxpbmVzKDEtaW5mb1JPQzExJHNlbnNwZS5tdHhbMixdLCBpbmZvUk9DMTEkc2Vuc3BlLm10eFsxLF0sIA0KICAgICAgY29sID0gY29sb3JzWzJdLCBsd2QgPSAyLCBsdHk9MikNCmxpbmVzKDEtZ2luaVJPQzExMCRzZW5zcGUubXR4WzIsXSwgZ2luaVJPQzExMCRzZW5zcGUubXR4WzEsXSwNCiAgICAgIGNvbCA9IGNvbG9yc1szXSwgbHdkID0gMikNCmxpbmVzKDEtaW5mb1JPQzExMCRzZW5zcGUubXR4WzIsXSwgaW5mb1JPQzExMCRzZW5zcGUubXR4WzEsXSwgDQogICAgICBjb2wgPSBjb2xvcnNbNF0sIGx3ZCA9IDIsIGx0eT0yKQ0KbGluZXMoMS1naW5pUk9DMTAxJHNlbnNwZS5tdHhbMixdLCBnaW5pUk9DMTAxJHNlbnNwZS5tdHhbMSxdLCANCiAgICAgIGNvbCA9IGNvbG9yc1s1XSwgbHdkID0gMiwgbHR5ID0gNCkNCmxpbmVzKDEtaW5mb1JPQzEwMSRzZW5zcGUubXR4WzIsXSwgaW5mb1JPQzEwMSRzZW5zcGUubXR4WzEsXSwgDQogICAgICBjb2wgPSBjb2xvcnNbNl0sIGx3ZCA9IDIsIGx0eT0yKQ0KbGVnZW5kKCJib3R0b21yaWdodCIsIGMocGFzdGUoImdpbmkuMS4xLCAgQVVDID0iLCBnaW5pUk9DMTEkQVVDKSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiaW5mby4xLjEsICBBVUMgPSIsaW5mb1JPQzExJEFVQyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoImdpbmkuMS4xMCwgQVVDID0iLGdpbmlST0MxMTAkQVVDKSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiaW5mby4xLjEwLCBBVUMgPSIsaW5mb1JPQzExMCRBVUMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoImdpbmkuMTAuMSwgQVVDID0iLGdpbmlST0MxMDEkQVVDKSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiaW5mby4xMC4xLCBBVUMgPSIsaW5mb1JPQzEwMSRBVUMpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbD1jb2xvcnMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgbHR5PXJlcCgxOjIsMyksIGx3ZD1yZXAoMiw2KSwgY2V4ID0gMC44LCBidHkgPSAibiIpDQpgYGANCg0KDQoNClRoZSBhYm92ZSBST0MgY3VydmVzIHJlcHJlc2VudCB2YXJpb3VzIGRlY2lzaW9uIHRyZWVzIGFuZCB0aGVpciBjb3JyZXNwb25kaW5nIEFVQy4gVGhlIG1vZGVsIHdpdGggdGhlIGxhcmdlc3QgQVVDIGlzIGNvbnNpZGVyZWQgdGhlIGJlc3QgZGVjaXNpb24gdHJlZSBhbW9uZyB0aGUgZXhpc3Rpbmcgb25lcy4gDQoNCg0KXA0KDQojIyBPcHRpbWFsIEN1dC1vZmYgU2NvcmUgRGV0ZXJtaW5hdGlvbg0KDQpBcyB1c3VhbCwgb25jZSB0aGUgZmluYWwgbW9kZWwgaXMgZGV0ZXJtaW5lZCwgd2UgbmVlZCB0byBmaW5kIHRoZSBvcHRpbWFsIGN1dC1vZmYgc2NvcmUgZm9yIHJlcG9ydGluZyB0aGUgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBvZiB0aGUgZmluYWwgbW9kZWwgd2l0aCB0aGUgdGVzdCBkYXRhLiBQbGVhc2Uga2VlcCBpbiBtaW5kIHRoZSBvcHRpbWFsIGN1dC1vZmYgZGV0ZXJtaW5hdGlvbiB0aHJvdWdoIGNyb3NzLXZhbGlkYXRpb24gbXVzdCBiZSBiYXNlZCBvbiB0aGUgdHJhaW5pbmcgZGF0YSBzZXQuDQoNCkluIHByYWN0aWNhbCBhcHBsaWNhdGlvbnMsIG9uZSBtYXkgZW5kIHVwIHdpdGggdHdvIG9yIG1vcmUgKipmaW5hbCBtb2RlbHMqKiB3aXRoIHNpbWlsYXIgQVVDcy4gSW4gdGhpcyBjYXNlLCB3ZSBuZWVkIHRvIHJlcG9ydCB0aGUgcGVyZm9ybWFuY2Ugb2YgYWxsICpmaW5hbCBtb2RlbHMqIGJhc2VkIG9uIHRoZSB0ZXN0IGRhdGEgYW5kIGxldCBjbGllbnRzIGNob29zZSBvbmUgdG8gZGVwbG95IChhbmQgcG9zc2libHkgbGVhdmUgdGhlIHJlc3QgYXMgY2hhbGxlbmdlcnMpLiBGb3IgdGhpcyByZWFzb24sIHdlIHdyaXRlIGEgZnVuY3Rpb24gdG8gZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIGN1dC1vZmYgZm9yIGEgZ2l2ZW4gZGVjaXNpb24gdHJlZSAoYmFzZWQgb24gdGhpcyBwcm9qZWN0KSBzaW5jZSBkaWZmZXJlbnQgZGVjaXNpb24gdHJlZXMgaGF2ZSB0aGVpciBvd24gb3B0aW1hbCBjdXQtb2ZmLg0KDQoNCmBgYHtyfQ0KT3B0bS5jdXRvZmYgPSBmdW5jdGlvbihpbi5kYXRhLCBmcCwgZm4sIHB1cml0eSl7DQogIG4wID0gZGltKGluLmRhdGEpWzFdLzUNCiAgY3V0b2ZmID0gc2VxKDAsMSwgbGVuZ3RoID0gMjApICAgICAgICAgICAgICAgIyBjYW5kaWRhdGUgY3V0IG9mZiBwcm9iDQogICMjIGFjY3VyYWN5IGZvciBlYWNoIGNhbmRpZGF0ZSBjdXQtb2ZmDQogIGFjY3VyYWN5Lm10eCA9IG1hdHJpeCgwLCBuY29sPTIwLCBucm93PTUpICAgICMgMjAgY2FuZGlkYXRlIGN1dG9mZnMgYW5kIGdpbmkuMTENCiAgIyMNCiAgZm9yIChrIGluIDE6NSl7DQogICAgIHZhbGlkLmlkID0gKChrLTEpKm4wICsgMSk6KGsqbjApDQogICAgIHZhbGlkLmRhdCA9IGluLmRhdGFbdmFsaWQuaWQsXQ0KICAgICB0cmFpbi5kYXQgPSBpbi5kYXRhWy12YWxpZC5pZCxdIA0KICAgICAjIyB0cmVlIG1vZGVsDQogICAgIHRyZWUubW9kZWwgPSB0cmVlLmJ1aWxkZXIoaW4uZGF0YSwgZnAsIGZuLCBwdXJpdHkpDQogICAgICMjIHByZWRpY3Rpb24gDQogICAgIHByZWQgPSBwcmVkaWN0KHRyZWUubW9kZWwsIG5ld2RhdGEgPSB2YWxpZC5kYXQsIHR5cGUgPSAicHJvYiIpWywyXQ0KICAgICAjIyBmb3ItbG9vcA0KICAgICBmb3IgKGkgaW4gMToyMCl7DQogICAgICAgICMjIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzDQogICAgICAgIHBjLjEgPSBpZmVsc2UocHJlZCA+IGN1dG9mZltpXSwgInBvcyIsICJuZWciKQ0KICAgICAgICAjIyBhY2N1cmFjeQ0KICAgICAgICBhMSA9IG1lYW4ocGMuMSA9PSB2YWxpZC5kYXQkZGlhYmV0ZXMpDQogICAgICAgIGFjY3VyYWN5Lm10eFtrLGldID0gYTENCiAgICAgICB9DQogICAgICB9DQogICBhdmcuYWNjID0gYXBwbHkoYWNjdXJhY3kubXR4LCAyLCBtZWFuKQ0KICAgIyMgcGxvdHMNCiAgIG4gPSBsZW5ndGgoYXZnLmFjYykNCiAgIGlkeCA9IHdoaWNoKGF2Zy5hY2MgPT0gbWF4KGF2Zy5hY2MpKQ0KICAgdGljay5sYWJlbCA9IGFzLmNoYXJhY3Rlcihyb3VuZChjdXRvZmYsMikpDQogICAjIw0KICAgcGxvdCgxOm4sIGF2Zy5hY2MsIHhsYWI9ImN1dC1vZmYgc2NvcmUiLCB5bGFiPSJhdmVyYWdlIGFjY3VyYWN5IiwgDQogICAgICAgIHlsaW09YyhtaW4oYXZnLmFjYyksIDEpLCANCiAgICAgICAgYXhlcyA9IEZBTFNFLA0KICAgICAgICBtYWluPXBhc3RlKCI1LWZvbGQgQ1Ygb3B0aW1hbCBjdXQtb2ZmIFxuICIscHVyaXR5LCIoZnAsIGZuKSA9ICgiLCBmcCwgIiwiLCBmbiwiKSIgLCANCiAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIiKSwNCiAgICAgICAgY2V4Lm1haW4gPSAwLjksDQogICAgICAgIGNvbC5tYWluID0gIm5hdnkiKQ0KICAgICAgICBheGlzKDEsIGF0PTE6MjAsIGxhYmVsID0gdGljay5sYWJlbCwgbGFzID0gMikNCiAgICAgICAgYXhpcygyKQ0KICAgICAgICBwb2ludHMoaWR4LCBhdmcuYWNjW2lkeF0sIHBjaD0xOSwgY29sID0gInJlZCIpDQogICAgICAgIHNlZ21lbnRzKGlkeCAsIG1pbihhdmcuYWNjKSwgaWR4ICwgYXZnLmFjY1tpZHggXSwgY29sID0gInJlZCIpDQogICAgICAgdGV4dChpZHgsIGF2Zy5hY2NbaWR4XSswLjAzLCBhcy5jaGFyYWN0ZXIocm91bmQoYXZnLmFjY1tpZHhdLDQpKSwgDQogICAgICAgICAgICBjb2wgPSAicmVkIiwgY2V4ID0gMC44KSANCiAgIH0NCmBgYA0KDQpGb3IgZGVtb25zdHJhdGlvbiwgd2UgdXNlIHRoZSBhYm92ZSBmdW5jdGlvbiB0byBjYWxjdWxhdGUgdGhlIG9wdGltYWwgY3V0LW9mZiBvZiA2IGRlY2lzaW9uIHRyZWVzIGNvbnN0cnVjdGVkIGVhcmxpZXIgaW4gdGhlIGZvbGxvd2luZy4gDQoNCg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTgsIGZpZy5jYXA9IkZpZ3VyZSAxNzogUGxvdCBvZiBvcHRpbWFsIGN1dC1vZmYgZGV0ZXJtaW5hdGlvbiJ9DQpwYXIobWZyb3c9YygzLDIpKQ0KT3B0bS5jdXRvZmYoaW4uZGF0YSA9IHRyYWluLCBmcD0xLCBmbj0xLCBwdXJpdHk9ImdpbmkiKQ0KT3B0bS5jdXRvZmYoaW4uZGF0YSA9IHRyYWluLCBmcD0xLCBmbj0xLCBwdXJpdHk9ImluZm9ybWF0aW9uIikNCk9wdG0uY3V0b2ZmKGluLmRhdGEgPSB0cmFpbiwgZnA9MSwgZm49MTAsIHB1cml0eT0iZ2luaSIpDQpPcHRtLmN1dG9mZihpbi5kYXRhID0gdHJhaW4sIGZwPTEsIGZuPTEwLCBwdXJpdHk9ImluZm9ybWF0aW9uIikNCk9wdG0uY3V0b2ZmKGluLmRhdGEgPSB0cmFpbiwgZnA9MTAsIGZuPTEsIHB1cml0eT0iZ2luaSIpDQpPcHRtLmN1dG9mZihpbi5kYXRhID0gdHJhaW4sIGZwPTEwLCBmbj0xLCBwdXJpdHk9ImluZm9ybWF0aW9uIikNCmBgYA0KDQpBcyBhbnRpY2lwYXRlZCwgZGlmZmVyZW50IHRyZWVzIGhhdmUgdGhlaXIgb3duIG9wdGltYWwgY3V0LW9mZi4gUGxlYXNlIGtlZXAgaW4gbWluZCB0aGF0IHRoZSBjdXQtb2ZmIGlzIHJhbmRvbSAoYmFzZWQgb24gdGhlIHJhbmRvbWx5IHNwbGl0IHRyYWluaW5nIGRhdGEpLCB0aGVyZSBtYXkgYmUgZGlmZmVyZW50IGN1dC1vZmZzIGluIGRpZmZlcmVudCBydW5zLiBJdCBpcyBkZXBlbmRlbnQgb24gdGhlIHRyZWUgc2l6ZSwgc29tZXRpbWVzLCB3ZSBtYXkgZW5kIHVwIHdpdGggbXVsdGlwbGUgb3B0aW1hbCBjdXQtb2Zmcy4gVGVjaG5pY2FsbHkgc3BlYWtpbmcsIHdlIGNob29zZSBhbnkgb25lIG9mIHRoZW0gZm9yIGltcGxlbWVudGF0aW9uLiBBIGJldHRlciByZWNvbW1lbmRhdGlvbiBpcyB0byBjaG9vc2UgdGhlIGF2ZXJhZ2Ugb2YgdGhlc2UgbXVsdGlwbGUgY3V0LW9mZnMgYW5kIHRoZSBmaW5hbCBjdXQtb2ZmIHRvIGJlIHVzZWQgb24gdGhlIHRlc3RpbmcgZGF0YSBzZXQuDQoNCg==