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")
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.
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:
Ordinary Least Squares Regression (OLSR)
Linear Regression
Logistic Regression
Step-wise Regression
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
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 |
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)
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
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 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.
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.
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)
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).
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.
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}.
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.
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.
Structure and
Technical Terms
The following diagram illustrates the basic structure of a decision
tree.
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.
The fully grown tree is given below (note the variable
class is the binary response variable).
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)
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.
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))
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.
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))
0.5935 |
0.3612 |
0.4756 |
0.3783 |
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.
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.
Bootstrapped
Aggregation (Bagging)
With the understanding of regular decisions, we can
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.
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
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")
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")
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")
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.
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")
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==