Session 20. Classification and Regression Tress (CART) w. {rpart}. Elements of Information Theory for Classification Trees. Pre-pruning and post-pruning (revisited) of Decision Trees.

Feedback should be send to goran.milovanovic@datakolektiv.com. These notebooks accompany the Intro to Data Science: Non-Technical Background course 2020/21.


What do we want to do today?

We now take a deep dive into Decision Trees in R. We will rely on the {rpart} package which encompasses some very well studied procedures to grow CART (Classification and Regression Trees). We begin by solving a regression problem tree with {rpart} and introduce the theory of CART along the way. We then proceed to solve a classification problem with a Decision Tree model and introduce the elements of Information Theory to understand the criteria used to assess the model. Enters entropy: the fundamental concept of Information Theory and among the corner stones of contemporary statistical learning. Finally we introduce the tree post-pruning procedures - essentially cross-validation across the model control parameters - and contrast it to tree post-pruning based on the complexity parameter.

0. Setup

install.packages('rpart')
install.packages ('rpart.plot')

Grab the HR_comma_sep.csv dataset from the Kaggle and place it in your _data directory for this session. We will also use the Boston Housing Dataset: BostonHousing.csv

dataDir <- paste0(getwd(), "/_data/")
library(tidyverse)
library(data.table)
library(rpart)
library(rpart.plot)

1. CART: The regression problem

We begin by loading and inspecting the Boston Housing dataset.

dataSet <- read.csv(paste0('_data/', 'BostonHousing.csv'), 
                    header = T, 
                    check.names = F,
                    stringsAsFactors = F)
head(dataSet)
glimpse(dataSet)
Rows: 506
Columns: 14
$ crim    <dbl> 0.00632, 0.02731, 0.02729, 0.03237, 0.06905, 0.02985, 0.088...
$ zn      <dbl> 18.0, 0.0, 0.0, 0.0, 0.0, 0.0, 12.5, 12.5, 12.5, 12.5, 12.5...
$ indus   <dbl> 2.31, 7.07, 7.07, 2.18, 2.18, 2.18, 7.87, 7.87, 7.87, 7.87,...
$ chas    <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
$ nox     <dbl> 0.538, 0.469, 0.469, 0.458, 0.458, 0.458, 0.524, 0.524, 0.5...
$ rm      <dbl> 6.575, 6.421, 7.185, 6.998, 7.147, 6.430, 6.012, 6.172, 5.6...
$ age     <dbl> 65.2, 78.9, 61.1, 45.8, 54.2, 58.7, 66.6, 96.1, 100.0, 85.9...
$ dis     <dbl> 4.0900, 4.9671, 4.9671, 6.0622, 6.0622, 6.0622, 5.5605, 5.9...
$ rad     <int> 1, 2, 2, 3, 3, 3, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4,...
$ tax     <int> 296, 242, 242, 222, 222, 222, 311, 311, 311, 311, 311, 311,...
$ ptratio <dbl> 15.3, 17.8, 17.8, 18.7, 18.7, 18.7, 15.2, 15.2, 15.2, 15.2,...
$ b       <dbl> 396.90, 396.90, 392.83, 394.63, 396.90, 394.12, 395.60, 396...
$ lstat   <dbl> 4.98, 9.14, 4.03, 2.94, 5.33, 5.21, 12.43, 19.15, 29.93, 17...
$ medv    <dbl> 24.0, 21.6, 34.7, 33.4, 36.2, 28.7, 22.9, 27.1, 16.5, 18.9,...

Here are the variables:

  • crim: per capita crime rate by town
  • zn: proportion of residential land zoned for lots over 25,000 sq.ft.
  • indus: proportion of non-retail business acres per town.
  • chas: Charles River dummy variable (1 if tract bounds river; 0 otherwise)
  • nox: nitric oxides concentration (parts per 10 million)
  • rm: average number of rooms per dwelling
  • age: proportion of owner-occupied units built prior to 1940
  • dis: weighted distances to five Boston employment centers
  • rad: index of accessibility to radial highways
  • tax: full-value property-tax rate per $10,000
  • ptratio: pupil-teacher ratio by town
  • b: 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
  • lstat: % lower status of the population
  • medv: Median value of owner-occupied homes in $1000’s

The medv variable is the outcome.

ggplot(dataSet, 
       aes(x = medv)) + 
  geom_histogram(fill = "blue", 
                 color = "blue") + 
  theme_bw() + 
  theme(panel.border = element_blank())

Now the {rpart} model:

regTree1 <- rpart(
  formula = medv ~ .,
  data = dataSet,
  method  = 'anova',
  control = rpart.control(cp = 0)
  )

N.B. Use the anova method to instruct {rpart} to fit a regression tree. The package will try to make an intelligent guess based on the nature of the outcome variable, however it is probably wiser to instruct it. Never mind the control = rpart.control(cp = 0) part for now.

Let’ see: use rpart.plot() to visualize the Decision Tree:

rpart.plot(regTree1,
           type = 4,
           extra = "auto")

Nothing can be seen? I know. Give me a chance I will fix this later in this Session.

The visualization works like this: wherever a decision (a split) is made, we go left for TRUE on the respective condition, and right for FALSE.

In the tree representation above, each node shows the predicted value and the percentage of cases falling under it. We will not post-prune this tree model now (as we did in the previous session); that can wait. First we need a bit of theory only to understand how trees like this are grown.

To put it in a nutshell: a Regression Decision Tree model partitions the dataset into subgroups, and then fits a constant for each case found in each subgroup. That constant is nothing more than the mean of all observations in the subgroup.

Basic regression trees partition a data set into smaller subgroups and then fit a simple constant for each observation in the subgroup. The partitioning is achieved by successive binary partitions (aka recursive partitioning) based on the different predictors. The constant to predict is based on the average values for all observations that fall in that subgroup.

The fundamental model equation is surprisingly simple then. Let’s index the subgroups (regions) as \(m = 1, 2,.., M\). Imagine we deal with \(N\) predictors in \(X\). The predicted value for any observation \(\hat{f}(X)\) is the sum of the \(c_m\) values - the region (subgroup) means multiplied by and indicator function \(I(X)\) which simply tells us if some particular observation falls into some region \(R_m\) or not (taking values of 1 and 0, respectively):

\[\hat{f}(X)=\sum_{m=1}^{M}{c_mI(X_1, X_2,.., X_N)\in{R_m}}\] But everything is easy if we already know the regions defined by splits across the predictor in the tree. But how do we learn about them? How do we grow trees?

The tree grows by a method known as binary recursive partitioning. The method is known as greedy: it begins by taking the whole dataset and splits it into two regions so as to minimize the sum of squared errors as much as possible, but once the split is made it does not affect the later splits. The process continues recursively across the newly obtained regions, each being split into two regions where sum of squared errors are minimized:

\[SSE=\Bigg\{\sum_{i\in{R_1}}(y_i-c_1)^2+\sum_{i\in{R_2}}(y_i-c_2)^2\Bigg\}\]

The recursive partitioning continues until some criterion is met (for example, the minimum allowed number of observations in a terminal node). And that is the critical part in growing a Decision Tree because complex models like them can easily overfit the data by finding smaller and smaller regions in the space spawned by the model predictors, theoretically isolating each particular value of the outcome - or very, very small subsets of their values - in highly idyosincratic regions!

Now, how do we avoid overfitting in the Decision Tree model? There are two ways to do it. The first is called pre-pruning and essentially means performing a cross-validation across the model controlling parameters interpreted as stopping rules. We will focus on pre-pruning later on in this session. Now we want to understand the **post-pruning* approach - already exemplified in Session 19 - which (just imagine) also has to do with cross-validation in the end. It is based on the value of the complexity criterion which is calculated as the tree grows in recursive partitioning, on the fly. The post-pruning solution to prevent the tree from overfitting the data is the following one (to be explained):

Instead of minimizing

\[SSE=\Bigg\{\sum_{i\in{R_1}}(y_i-c_1)^2+\sum_{i\in{R_2}}(y_i-c_2)^2\Bigg\}\]

we choose to minimize

\[SSE_{penalized}=\Bigg\{\sum_{i\in{R_1}}(y_i-c_1)^2+\sum_{i\in{R_2}}(y_i-c_2)^2\Bigg\} + \alpha|T|\]

where \(\alpha\) is the cost complexity parameter and \(|T|\) is the number of terminal nodes in the tree. Post-pruning works in the following way:

  • fit a large tree, a complex model to the data;
  • start looking for subtrees of that tree that have the minimal \(SSE_{penalized}\)
  • across a range of values for \(\alpha\); for each selected subtree
  • perform a k-fold cross-validation across \(\alpha\) and determine the cross-validation (i.e. prediction) error;
  • choose the substree with a minimal cross-validation error found on a particular value of \(\alpha\).

Let’s perform post-pruning in R. We need to take a look at the complexity parameter table which is found in regTree1$cptable field of the model object:

print(regTree1$cptable)
             CP nsplit rel error    xerror       xstd
1  0.4527442007      0 1.0000000 1.0049332 0.08317640
2  0.1711724363      1 0.5472558 0.6276144 0.05685322
3  0.0716578409      2 0.3760834 0.4216362 0.04511624
4  0.0361642808      3 0.3044255 0.3393898 0.04197179
5  0.0333692301      4 0.2682612 0.3270257 0.04341389
6  0.0266129999      5 0.2348920 0.3118824 0.04336519
7  0.0158511574      6 0.2082790 0.2828871 0.04122777
8  0.0082454484      7 0.1924279 0.2833726 0.04290846
9  0.0072653855      8 0.1841824 0.2843890 0.04467585
10 0.0069310873      9 0.1769170 0.2811362 0.04358962
11 0.0061263349     10 0.1699859 0.2777601 0.04366197
12 0.0048053197     11 0.1638596 0.2500946 0.03885811
13 0.0045609248     12 0.1590543 0.2504507 0.03885986
14 0.0039410233     13 0.1544934 0.2483725 0.03883041
15 0.0033161209     14 0.1505523 0.2493143 0.03900525
16 0.0031206492     15 0.1472362 0.2437834 0.03888541
17 0.0022459421     16 0.1441156 0.2432608 0.03877377
18 0.0022354038     18 0.1396237 0.2441498 0.03876625
19 0.0021720940     19 0.1373883 0.2432100 0.03876169
20 0.0019335691     20 0.1352162 0.2433058 0.03875133
21 0.0017169079     21 0.1332826 0.2438034 0.03874916
22 0.0014440359     22 0.1315657 0.2433717 0.03876508
23 0.0014098099     23 0.1301217 0.2447990 0.03872838
24 0.0013635419     24 0.1287119 0.2449375 0.03878009
25 0.0012778421     25 0.1273483 0.2458674 0.03877505
26 0.0012473645     26 0.1260705 0.2477517 0.03876580
27 0.0011372540     28 0.1235757 0.2482631 0.03875683
28 0.0009633247     29 0.1224385 0.2481842 0.03925403
29 0.0008486865     30 0.1214752 0.2462672 0.03925443
30 0.0007098930     31 0.1206265 0.2448268 0.03922722
31 0.0005879326     32 0.1199166 0.2450964 0.03941737
32 0.0005115280     33 0.1193287 0.2454433 0.03942573
33 0.0003794039     34 0.1188171 0.2459388 0.03942342
34 0.0003719398     35 0.1184377 0.2447076 0.03901253
35 0.0003438561     36 0.1180658 0.2448500 0.03900522
36 0.0003311450     37 0.1177219 0.2447269 0.03900653
37 0.0002274363     39 0.1170596 0.2445965 0.03905484
38 0.0001955788     40 0.1168322 0.2455211 0.03955117
39 0.0000000000     41 0.1166366 0.2454463 0.03954663

We can also visualize it with plotcp():

plotcp(regTree1)

Can you see how the value of xerror stabilizes at some point and maybe even begin to grow slightly? Remember how we used control = rpart.control(cp = 0) in our model call: we have instructed rpart() to set \(\alpha\) to 0 and produced a tree that overfits the data!

Now, pre-Pruning: look at the complexity parameter table and find the value of CP for the lowest xerror:

cptable <- as.data.frame(regTree1$cptable)
optimal_cp <- cptable$CP[which.min(cptable$xerror)]
optimalregTree1 <- prune(regTree1,
                         cp = optimal_cp)
rpart.plot(optimalregTree1,
           type = 4,
           extra = "auto")

Let’s check the situation now:

plotcp(optimalregTree1)

Well, this is way better. From the rpart() documentation:

complexity parameter. Any split that does not decrease the overall lack of fit by a factor of cp is not attempted. For instance, with anova splitting, this means that the overall R-squared must increase by cp at each step. The main role of this parameter is to save computing time by pruning off splits that are obviously not worthwhile. Essentially,the user informs the program that any split which does not improve the fit by cp will likely be pruned off by cross-validation, and that hence the program need not pursue it.

However, let’s take a look at an even simpler model. Let’s not use the cp control parameter and just let {rpart} grow the tree on its own:

regTree2 <- rpart(
  formula = medv ~ .,
  data = dataSet,
  method  = 'anova')
rpart.plot(regTree2,
           type = 4,
           extra = "auto")

Now the visualization finally helps, at least.

plotcp(regTree2)

What criteria has been used on behalf of rpart() to grow this tree?

regTree2$control$cp
[1] 0.01

Let’s compare:

optimal_cp <- .01
optimalregTree2 <- prune(regTree2,
                         cp = optimal_cp)
rpart.plot(optimalregTree2,
           type = 4,
           extra = "auto")

So please be aware of the default values of the control parameters in large and complex statistical learning models!

The \(R^2\) of the optimal model, if you are interested to learn, is:

predictions <- predict(regTree2, 
                       newdata = dataSet)
round(
  cor(predictions, dataSet$medv)^2, 
  2)
[1] 0.81

2. CART: The classification problem

Growing a Classification Tree is a little bit different than growing a Regression Tree, for one simple, obvious reason that we have already encountered in our discussions of Generalized Linear Models: namely, minimizing \(SSE\) is not an ideal goal anymore. We need some new criteria for our algorithms to decide when a new split is useful and when not. Let’s see what can be done about it.

2.1 Elements of Information Theory: enters Entropy

Imagine a tree-growing algorithm running for a two-class classification problem, ending up in a set of regions in the space spawned by the predictors. Each region potentially contains both correctly and incorrectly classified instances of the outcome. I want to measure how “pure” a region is: how well does it delineates truth from falsehood in classification.

Consider a coin toss statistical experiment with \(p = .23\) and 100 observations:

exp <- rbinom(n = 100, size = 1, prob = .23)
table(exp)
exp
 0  1 
75 25 

As expected, there are way more 0s, given the low probability of just .23 to observe `1. Of course, the probability \(p = .50\) of a fair coin would produce a different distribution of results:

exp <- rbinom(n = 100, size = 1, prob = .5)
table(exp)
exp
 0  1 
51 49 

Q. Which of these two distributions is more informative? What brings more knowledge to us: an outcome of a fair coin toss which brings about 1 and 0 with an approximatelly 50:50 distribution, or a toss of a coin with \(p = .23\)? Which outcome does bring more surprise to us?

What we can tell about the two coins: which one is more predictable. Obviously, the coin with \(p = .23\) is way more predictable than the fair coin which carries complete uncertainty about its outcome. There is somehow more structure in the uneven distribution of outcomes with \(p = .23\) than in the distribution of outcomes of a fair coin toss.

Consider the following measure:

\[H(X)=-\sum_{i = 1}^{N}P(x_i)logP(x_i)\] It is called the Shannon entropy, or the Shannon information entropy, and it was introduced by Claude Shannon in his famous 1948 paper A Mathematical Theory of Communication. Let’s compute the entropy of a coin tossing statistical experiment over a range of values of \(p\):

p <- seq(.001, .999, by = .001)
ent <- -(p*log(p) + (1-p)*log(1-p))
entFrame <- data.frame(p = p, 
                       entropy = ent)
ggplot(data = entFrame, 
       aes(x = p, y = entropy)) + 
  geom_path(size = .25, color = "blue") + 
  ggtitle("Entropy of a Coin Toss") +
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))

Look how entropy nicely describes the uncertainty of a statistical experiment: it reaches its maximum for a fair coin with \(p=.5\), and then falls symmetrically towards \(p=0\) and \(p=1\) where the outcomes of the experiment are certain. Entropy easily generalizes to more than two outcomes and is used as a fundamental measure of uncertainty in Information and Probability Theory. It is a conceptual cornerstone of many contemporary models of statistical learning.

Ask yourself: could we use entropy to measure the “purity” of a binary classification? Should our Decision Tree models maybe tend to grow trees with terminal nodes that are minimally entropic?

2.2 Growing a Classification Tree

Let’s consider the following two measures of impurity of a node in a tree that can help guide the recursive partitioning process in classification:

Gini Impurity

  • Define

\[\hat{p}_{mk} = \frac{1}{N_m}\sum_{y_i\in{R_m}}I(y_i=k)\] where \(N_m\) is the number of observations in region \(m\), \(k\) a particular class that should be predicted correctly, and \(I()\) again just an indicator function, and \(y_i\) an observation of the outcome. This is nothing else than the probability of the class \(k\) in region \(R_m\). The Gini Impurity index is then just

\[Gini\ Impurity = \sum_{k\neq{k}'}\hat{p}_{mk}\hat{p}_{mk'}=\sum_{k=1}^{K}\hat{p}_{mk}(1-\hat{p}_{mk})\]

The Gini Impurity is a measure of missclassification: it looks for the probability of an instance being incorrectly classified. Gini impurity reaches its minimum at zero when all cases in the node fall into a single target category, so it is a badness-of-fit measure (the higher it is - the worse). Gini Impurity has a deep theoretical connection to entropy which we will not go into now. Essentially, we want to guide the process of recursive partitioning of a classification trees by looking for splits that minimize Gini Impurity relative to its value in the parent nodes.

Information Gain

Define

\[IG(T,a) = H(T)-H(T|a)\]

where \(IG(T,a)\) is an Information Gain obtained from a split s in a tree, \(H(T)\) is the entropy of the parent node, and \(H(T|a)\) is the sum of entropy in the children nodes - the entropy following the split. Essentially, we want to build classification trees that maximize Information Gain in their splits.

2.3 Classification with {rpart}, again

Just to remind ourselves on how to use {rpart} for classification problems we will repeat the steps already taken in Session 19.

Consider the HR_comma_sep.csv dataset:

dataSet <- read.csv(paste0('_data/', 'HR_comma_sep.csv'), 
                    header = T, 
                    check.names = F,
                    stringsAsFactors = F)
head(dataSet)
table(dataSet$left)

    0     1 
11428  3571 

The task is to predict the value of left - whether the employee has left the company or not - from a set of predictors encompassing the following:

glimpse(dataSet)
Rows: 14,999
Columns: 10
$ satisfaction_level    <dbl> 0.38, 0.80, 0.11, 0.72, 0.37, 0.41, 0.10, 0.9...
$ last_evaluation       <dbl> 0.53, 0.86, 0.88, 0.87, 0.52, 0.50, 0.77, 0.8...
$ number_project        <int> 2, 5, 7, 5, 2, 2, 6, 5, 5, 2, 2, 6, 4, 2, 2, ...
$ average_montly_hours  <int> 157, 262, 272, 223, 159, 153, 247, 259, 224, ...
$ time_spend_company    <int> 3, 6, 4, 5, 3, 3, 4, 5, 5, 3, 3, 4, 5, 3, 3, ...
$ Work_accident         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
$ left                  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
$ promotion_last_5years <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
$ sales                 <chr> "sales", "sales", "sales", "sales", "sales", ...
$ salary                <chr> "low", "medium", "medium", "low", "low", "low...
  • satisfaction_level: a measure of employee’s level of satisfaction
  • last_evaluation: the result of a last evaluation
  • number_projects: in how many projects did the employee took part
  • average_monthly_hours: how working hours monthly on average
  • time_spend_company: for how long is the employee with us
  • Work_accident: any work accidents?
  • promotion_last_5years: did the promotion occur in the last five years?
  • sales: department (sales, accounting, hr, technical, support, management, IT, product_mng, marketing, RandD)
  • salary: salary class (low, medium, high)

Train one Decision Tree on train (N.B. method is now "class"):

# - Base Model
classTree <- rpart(left ~ ., 
                   data = dataSet, 
                   method = "class")

Visualize the model with rpart.plot():

rpart.plot(classTree)

2.4 Pre-pruning: additional ways to avoid overfitting the tree, again

Let’s take a look at the control parameters used by {rpart} to fit this classification tree:

classTree$control
$minsplit
[1] 20

$minbucket
[1] 7

$cp
[1] 0.01

$maxcompete
[1] 4

$maxsurrogate
[1] 5

$usesurrogate
[1] 2

$surrogatestyle
[1] 0

$maxdepth
[1] 30

$xval
[1] 10

Pre-pruning a Decision Tree model essentially means nothing else than tweaking a subset of important control parameters until their optimal values for the problem at hand are found. Essentially all of the following can be used to prevent the tree from becoming to complex and begin overfitting the data (as concisely explained by Sibanjan Das in his Decision Trees and Pruning in R post on DevZone):

  • maxdepth: This parameter is used to set the maximum depth of a tree. Depth is the length of the longest path from a Root node to a Leaf node. Setting this parameter will stop growing the tree when the depth is equal the value set for maxdepth.
  • minsplit: It is the minimum number of records that must exist in a node for a split to happen or be attempted. For example, we set minimum records in a split to be 5; then, a node can be further split for achieving purity when the number of records in each split node is more than 5.
  • minbucket: It is the minimum number of records that can be present in a Terminal node. For example, we set the minimum records in a node to 5, meaning that every Terminal/Leaf node should have at least five records. We should also take care of not overfitting the model by specifying this parameter. If it is set to a too-small value, like 1, we may run the risk of overfitting our model.

And how is this tweaking of the control parameters performed? You already know the answer: by cross-validating the model across a range of their values, each time performing and ROC analysis for classification as we did in Session 19!

2.5 Variable Importance

The importance of each variable in a Decision Tree model can be obtained from the variable.importance field of the model object in the following way:

importance <- 
  as.data.frame(
    sort(classTree$variable.importance, decreasing = T)
    )
colnames(importance) <- 'Importance'
print(importance)

The docs say:

An overall measure of variable importance is the sum of the goodness of split measures for each split for which it was the primary variable.


Further Readings


Goran S. Milovanović

DataKolektiv, 2020/21

contact:


License: GPLv3 This Notebook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This Notebook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this Notebook. If not, see http://www.gnu.org/licenses/.


LS0tDQp0aXRsZTogSW50cm8gdG8gRGF0YSBTY2llbmNlIChOb24tVGVjaG5pY2FsIEJhY2tncm91bmQsIFIpIC0gU2Vzc2lvbjIwDQphdXRob3I6DQotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhywgUGhEDQogIGFmZmlsaWF0aW9uOiBEYXRhS29sZWt0aXYsIENoaWVmIFNjaWVudGlzdCAmIE93bmVyOyBEYXRhIFNjaWVudGlzdCBmb3IgV2lraWRhdGEsIFdNREUNCmFic3RyYWN0OiANCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgdG9jX2RlcHRoOiA1DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDUNCi0tLQ0KDQohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpDQoNCioqKg0KIyBTZXNzaW9uIDIwLiBDbGFzc2lmaWNhdGlvbiBhbmQgUmVncmVzc2lvbiBUcmVzcyAoQ0FSVCkgdy4ge3JwYXJ0fS4gRWxlbWVudHMgb2YgSW5mb3JtYXRpb24gVGhlb3J5IGZvciBDbGFzc2lmaWNhdGlvbiBUcmVlcy4gUHJlLXBydW5pbmcgYW5kIHBvc3QtcHJ1bmluZyAocmV2aXNpdGVkKSBvZiBEZWNpc2lvbiBUcmVlcy4gIA0KDQoqKkZlZWRiYWNrKiogc2hvdWxkIGJlIHNlbmQgdG8gYGdvcmFuLm1pbG92YW5vdmljQGRhdGFrb2xla3Rpdi5jb21gLiANClRoZXNlIG5vdGVib29rcyBhY2NvbXBhbnkgdGhlIEludHJvIHRvIERhdGEgU2NpZW5jZTogTm9uLVRlY2huaWNhbCBCYWNrZ3JvdW5kIGNvdXJzZSAyMDIwLzIxLg0KDQoqKioNCg0KIyMjIFdoYXQgZG8gd2Ugd2FudCB0byBkbyB0b2RheT8NCg0KV2Ugbm93IHRha2UgYSBkZWVwIGRpdmUgaW50byBEZWNpc2lvbiBUcmVlcyBpbiBSLiBXZSB3aWxsIHJlbHkgb24gdGhlIFt7cnBhcnR9XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcnBhcnQvaW5kZXguaHRtbCkgcGFja2FnZSB3aGljaCBlbmNvbXBhc3NlcyBzb21lIHZlcnkgd2VsbCBzdHVkaWVkIHByb2NlZHVyZXMgdG8gZ3JvdyAqKkNBUlQqKiAoKkNsYXNzaWZpY2F0aW9uIGFuZCBSZWdyZXNzaW9uIFRyZWVzKikuIFdlIGJlZ2luIGJ5IHNvbHZpbmcgYSByZWdyZXNzaW9uIHByb2JsZW0gdHJlZSB3aXRoIHtycGFydH0gYW5kIGludHJvZHVjZSB0aGUgdGhlb3J5IG9mIENBUlQgYWxvbmcgdGhlIHdheS4gV2UgdGhlbiBwcm9jZWVkIHRvIHNvbHZlIGEgY2xhc3NpZmljYXRpb24gcHJvYmxlbSB3aXRoIGEgRGVjaXNpb24gVHJlZSBtb2RlbCBhbmQgaW50cm9kdWNlIHRoZSBlbGVtZW50cyBvZiBJbmZvcm1hdGlvbiBUaGVvcnkgdG8gdW5kZXJzdGFuZCB0aGUgY3JpdGVyaWEgdXNlZCB0byBhc3Nlc3MgdGhlIG1vZGVsLiBFbnRlcnMgKiplbnRyb3B5Kio6IHRoZSBmdW5kYW1lbnRhbCBjb25jZXB0IG9mIEluZm9ybWF0aW9uIFRoZW9yeSBhbmQgYW1vbmcgdGhlIGNvcm5lciBzdG9uZXMgb2YgY29udGVtcG9yYXJ5IHN0YXRpc3RpY2FsIGxlYXJuaW5nLiBGaW5hbGx5IHdlIGludHJvZHVjZSB0aGUgdHJlZSBwb3N0LXBydW5pbmcgcHJvY2VkdXJlcyAtIGVzc2VudGlhbGx5IGNyb3NzLXZhbGlkYXRpb24gYWNyb3NzIHRoZSBtb2RlbCBjb250cm9sIHBhcmFtZXRlcnMgLSBhbmQgY29udHJhc3QgaXQgdG8gdHJlZSBwb3N0LXBydW5pbmcgYmFzZWQgb24gdGhlICpjb21wbGV4aXR5IHBhcmFtZXRlciouDQoNCg0KIyMjIDAuIFNldHVwDQoNCmBgYHtyIGVjaG8gPSBULCBldmFsID0gRn0NCmluc3RhbGwucGFja2FnZXMoJ3JwYXJ0JykNCmluc3RhbGwucGFja2FnZXMgKCdycGFydC5wbG90JykNCmBgYA0KDQpHcmFiIHRoZSBgSFJfY29tbWFfc2VwLmNzdmAgZGF0YXNldCBmcm9tIHRoZSBbS2FnZ2xlXShodHRwczovL3d3dy5rYWdnbGUuY29tL2xpdWppYXFpL2hyLWNvbW1hLXNlcGNzdikgYW5kIHBsYWNlIGl0IGluIHlvdXIgYF9kYXRhYCBkaXJlY3RvcnkgZm9yIHRoaXMgc2Vzc2lvbi4gV2Ugd2lsbCBhbHNvIHVzZSB0aGUgW0Jvc3RvbiBIb3VzaW5nIERhdGFzZXQ6ICBCb3N0b25Ib3VzaW5nLmNzdl0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3NlbHZhODYvZGF0YXNldHMvbWFzdGVyL0Jvc3RvbkhvdXNpbmcuY3N2KQ0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KZGF0YURpciA8LSBwYXN0ZTAoZ2V0d2QoKSwgIi9fZGF0YS8iKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KYGBgDQoNCg0KIyMjIDEuIENBUlQ6IFRoZSByZWdyZXNzaW9uIHByb2JsZW0NCg0KV2UgYmVnaW4gYnkgbG9hZGluZyBhbmQgaW5zcGVjdGluZyB0aGUgQm9zdG9uIEhvdXNpbmcgZGF0YXNldC4NCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmRhdGFTZXQgPC0gcmVhZC5jc3YocGFzdGUwKCdfZGF0YS8nLCAnQm9zdG9uSG91c2luZy5jc3YnKSwgDQogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsIA0KICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsDQogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQ0KaGVhZChkYXRhU2V0KQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpnbGltcHNlKGRhdGFTZXQpDQpgYGANCkhlcmUgYXJlIHRoZSB2YXJpYWJsZXM6DQoNCisgKipjcmltKio6IHBlciBjYXBpdGEgY3JpbWUgcmF0ZSBieSB0b3duDQorICoqem4qKjogcHJvcG9ydGlvbiBvZiByZXNpZGVudGlhbCBsYW5kIHpvbmVkIGZvciBsb3RzIG92ZXIgMjUsMDAwIHNxLmZ0Lg0KKyAqKmluZHVzKio6IHByb3BvcnRpb24gb2Ygbm9uLXJldGFpbCBidXNpbmVzcyBhY3JlcyBwZXIgdG93bi4NCisgKipjaGFzKio6IENoYXJsZXMgUml2ZXIgZHVtbXkgdmFyaWFibGUgKDEgaWYgdHJhY3QgYm91bmRzIHJpdmVyOyAwIG90aGVyd2lzZSkNCisgKipub3gqKjogbml0cmljIG94aWRlcyBjb25jZW50cmF0aW9uIChwYXJ0cyBwZXIgMTAgbWlsbGlvbikNCisgKipybSoqOiBhdmVyYWdlIG51bWJlciBvZiByb29tcyBwZXIgZHdlbGxpbmcNCisgKiphZ2UqKjogcHJvcG9ydGlvbiBvZiBvd25lci1vY2N1cGllZCB1bml0cyBidWlsdCBwcmlvciB0byAxOTQwDQorICoqZGlzKio6IHdlaWdodGVkIGRpc3RhbmNlcyB0byBmaXZlIEJvc3RvbiBlbXBsb3ltZW50IGNlbnRlcnMNCisgKipyYWQqKjogaW5kZXggb2YgYWNjZXNzaWJpbGl0eSB0byByYWRpYWwgaGlnaHdheXMNCisgKip0YXgqKjogZnVsbC12YWx1ZSBwcm9wZXJ0eS10YXggcmF0ZSBwZXIgJDEwLDAwMA0KKyAqKnB0cmF0aW8qKjogcHVwaWwtdGVhY2hlciByYXRpbyBieSB0b3duDQorICoqYioqOiAxMDAwKEJrIC0gMC42MyleMiB3aGVyZSBCayBpcyB0aGUgcHJvcG9ydGlvbiBvZiBibGFja3MgYnkgdG93bg0KKyAqKmxzdGF0Kio6ICUgbG93ZXIgc3RhdHVzIG9mIHRoZSBwb3B1bGF0aW9uDQorICoqbWVkdioqOiBNZWRpYW4gdmFsdWUgb2Ygb3duZXItb2NjdXBpZWQgaG9tZXMgaW4gJDEwMDAncw0KDQpUaGUgYG1lZHZgIHZhcmlhYmxlIGlzIHRoZSAqb3V0Y29tZSouDQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpnZ3Bsb3QoZGF0YVNldCwgDQogICAgICAgYWVzKHggPSBtZWR2KSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJibHVlIiwgDQogICAgICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiKSArIA0KICB0aGVtZV9idygpICsgDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KTm93IHRoZSB7cnBhcnR9IG1vZGVsOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KcmVnVHJlZTEgPC0gcnBhcnQoDQogIGZvcm11bGEgPSBtZWR2IH4gLiwNCiAgZGF0YSA9IGRhdGFTZXQsDQogIG1ldGhvZCAgPSAnYW5vdmEnLA0KICBjb250cm9sID0gcnBhcnQuY29udHJvbChjcCA9IDApDQogICkNCmBgYA0KDQoqKk4uQi4qKiBVc2UgdGhlIGBhbm92YWAgbWV0aG9kIHRvIGluc3RydWN0IHtycGFydH0gdG8gZml0IGEgcmVncmVzc2lvbiB0cmVlLiBUaGUgcGFja2FnZSB3aWxsIHRyeSB0byBtYWtlIGFuIGludGVsbGlnZW50IGd1ZXNzIGJhc2VkIG9uIHRoZSBuYXR1cmUgb2YgdGhlIG91dGNvbWUgdmFyaWFibGUsIGhvd2V2ZXIgaXQgaXMgcHJvYmFibHkgd2lzZXIgdG8gaW5zdHJ1Y3QgaXQuIE5ldmVyIG1pbmQgdGhlIGBjb250cm9sID0gcnBhcnQuY29udHJvbChjcCA9IDApYCBwYXJ0IGZvciBub3cuIA0KDQpMZXQnIHNlZTogdXNlIGBycGFydC5wbG90KClgIHRvIHZpc3VhbGl6ZSB0aGUgRGVjaXNpb24gVHJlZToNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCnJwYXJ0LnBsb3QocmVnVHJlZTEsDQogICAgICAgICAgIHR5cGUgPSA0LA0KICAgICAgICAgICBleHRyYSA9ICJhdXRvIikNCmBgYA0KTm90aGluZyBjYW4gYmUgc2Vlbj8gSSBrbm93LiBHaXZlIG1lIGEgY2hhbmNlIEkgd2lsbCBmaXggdGhpcyBsYXRlciBpbiB0aGlzIFNlc3Npb24uDQoNClRoZSB2aXN1YWxpemF0aW9uIHdvcmtzIGxpa2UgdGhpczogd2hlcmV2ZXIgYSBkZWNpc2lvbiAoYSBzcGxpdCkgaXMgbWFkZSwgd2UgZ28gKipsZWZ0KiogZm9yIGBUUlVFYCBvbiB0aGUgcmVzcGVjdGl2ZSBjb25kaXRpb24sIGFuZCAqKnJpZ2h0KiogZm9yIGBGQUxTRWAuIA0KDQpJbiB0aGUgdHJlZSByZXByZXNlbnRhdGlvbiBhYm92ZSwgZWFjaCBub2RlIHNob3dzIHRoZSBwcmVkaWN0ZWQgdmFsdWUgYW5kIHRoZSBwZXJjZW50YWdlIG9mIGNhc2VzIGZhbGxpbmcgdW5kZXIgaXQuIFdlIHdpbGwgbm90IHBvc3QtcHJ1bmUgdGhpcyB0cmVlIG1vZGVsIG5vdyAoYXMgd2UgZGlkIGluIHRoZSBwcmV2aW91cyBzZXNzaW9uKTsgdGhhdCBjYW4gd2FpdC4gRmlyc3Qgd2UgbmVlZCBhIGJpdCBvZiB0aGVvcnkgb25seSB0byB1bmRlcnN0YW5kIGhvdyB0cmVlcyBsaWtlIHRoaXMgYXJlIGdyb3duLg0KDQpUbyBwdXQgaXQgaW4gYSBudXRzaGVsbDogYSBSZWdyZXNzaW9uIERlY2lzaW9uIFRyZWUgbW9kZWwgcGFydGl0aW9ucyB0aGUgZGF0YXNldCBpbnRvIHN1Ymdyb3VwcywgYW5kIHRoZW4gZml0cyBhICoqY29uc3RhbnQqKiBmb3IgZWFjaCBjYXNlIGZvdW5kIGluIGVhY2ggc3ViZ3JvdXAuIFRoYXQgY29uc3RhbnQgaXMgbm90aGluZyBtb3JlIHRoYW4gdGhlIG1lYW4gb2YgYWxsIG9ic2VydmF0aW9ucyBpbiB0aGUgc3ViZ3JvdXAuICANCg0KQmFzaWMgcmVncmVzc2lvbiB0cmVlcyBwYXJ0aXRpb24gYSBkYXRhIHNldCBpbnRvIHNtYWxsZXIgc3ViZ3JvdXBzIGFuZCB0aGVuIGZpdCBhIHNpbXBsZSBjb25zdGFudCBmb3IgZWFjaCBvYnNlcnZhdGlvbiBpbiB0aGUgc3ViZ3JvdXAuIFRoZSBwYXJ0aXRpb25pbmcgaXMgYWNoaWV2ZWQgYnkgc3VjY2Vzc2l2ZSBiaW5hcnkgcGFydGl0aW9ucyAoYWthIHJlY3Vyc2l2ZSBwYXJ0aXRpb25pbmcpIGJhc2VkIG9uIHRoZSBkaWZmZXJlbnQgcHJlZGljdG9ycy4gVGhlIGNvbnN0YW50IHRvIHByZWRpY3QgaXMgYmFzZWQgb24gKnRoZSBhdmVyYWdlIHZhbHVlcyogZm9yIGFsbCBvYnNlcnZhdGlvbnMgdGhhdCBmYWxsIGluIHRoYXQgc3ViZ3JvdXAuDQoNClRoZSBmdW5kYW1lbnRhbCBtb2RlbCBlcXVhdGlvbiBpcyBzdXJwcmlzaW5nbHkgc2ltcGxlIHRoZW4uIExldCdzIGluZGV4IHRoZSBzdWJncm91cHMgKHJlZ2lvbnMpIGFzICRtID0gMSwgMiwuLiwgTSQuIEltYWdpbmUgd2UgZGVhbCB3aXRoICROJCBwcmVkaWN0b3JzIGluICRYJC4gVGhlIHByZWRpY3RlZCB2YWx1ZSBmb3IgYW55IG9ic2VydmF0aW9uICRcaGF0e2Z9KFgpJCBpcyB0aGUgc3VtIG9mIHRoZSAkY19tJCB2YWx1ZXMgLSB0aGUgcmVnaW9uIChzdWJncm91cCkgbWVhbnMgbXVsdGlwbGllZCBieSBhbmQgaW5kaWNhdG9yIGZ1bmN0aW9uICRJKFgpJCB3aGljaCBzaW1wbHkgdGVsbHMgdXMgaWYgc29tZSBwYXJ0aWN1bGFyIG9ic2VydmF0aW9uIGZhbGxzIGludG8gc29tZSByZWdpb24gJFJfbSQgb3Igbm90ICh0YWtpbmcgdmFsdWVzIG9mIGAxYCBhbmQgYDBgLCByZXNwZWN0aXZlbHkpOiANCg0KJCRcaGF0e2Z9KFgpPVxzdW1fe209MX1ee019e2NfbUkoWF8xLCBYXzIsLi4sIFhfTilcaW57Ul9tfX0kJA0KQnV0IGV2ZXJ5dGhpbmcgaXMgZWFzeSBpZiB3ZSBhbHJlYWR5ICprbm93KiB0aGUgcmVnaW9ucyBkZWZpbmVkIGJ5IHNwbGl0cyBhY3Jvc3MgdGhlIHByZWRpY3RvciBpbiB0aGUgdHJlZS4gQnV0IGhvdyBkbyB3ZSBsZWFybiBhYm91dCB0aGVtPyBIb3cgZG8gd2UgZ3JvdyB0cmVlcz8NCg0KVGhlIHRyZWUgZ3Jvd3MgYnkgYSBtZXRob2Qga25vd24gYXMgKipiaW5hcnkgcmVjdXJzaXZlIHBhcnRpdGlvbmluZyoqLiBUaGUgbWV0aG9kIGlzIGtub3duIGFzICpncmVlZHkqOiBpdCBiZWdpbnMgYnkgdGFraW5nIHRoZSB3aG9sZSBkYXRhc2V0IGFuZCBzcGxpdHMgaXQgaW50byB0d28gcmVnaW9ucyBzbyBhcyB0byBtaW5pbWl6ZSB0aGUgc3VtIG9mIHNxdWFyZWQgZXJyb3JzIGFzIG11Y2ggYXMgcG9zc2libGUsIGJ1dCBvbmNlIHRoZSBzcGxpdCBpcyBtYWRlICppdCBkb2VzIG5vdCBhZmZlY3QqIHRoZSBsYXRlciBzcGxpdHMuIFRoZSBwcm9jZXNzIGNvbnRpbnVlcyByZWN1cnNpdmVseSBhY3Jvc3MgdGhlIG5ld2x5IG9idGFpbmVkIHJlZ2lvbnMsIGVhY2ggYmVpbmcgc3BsaXQgaW50byB0d28gcmVnaW9ucyB3aGVyZSBzdW0gb2Ygc3F1YXJlZCBlcnJvcnMgYXJlIG1pbmltaXplZDoNCg0KJCRTU0U9XEJpZ2dce1xzdW1fe2lcaW57Ul8xfX0oeV9pLWNfMSleMitcc3VtX3tpXGlue1JfMn19KHlfaS1jXzIpXjJcQmlnZ1x9JCQNCg0KVGhlIHJlY3Vyc2l2ZSBwYXJ0aXRpb25pbmcgY29udGludWVzIHVudGlsIHNvbWUgY3JpdGVyaW9uIGlzIG1ldCAoZm9yIGV4YW1wbGUsIHRoZSBtaW5pbXVtIGFsbG93ZWQgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBhIHRlcm1pbmFsIG5vZGUpLiBBbmQgdGhhdCBpcyB0aGUgY3JpdGljYWwgcGFydCBpbiBncm93aW5nIGEgRGVjaXNpb24gVHJlZSAqKmJlY2F1c2UqKiBjb21wbGV4IG1vZGVscyBsaWtlIHRoZW0gY2FuICplYXNpbHkgb3ZlcmZpdCogdGhlIGRhdGEgYnkgZmluZGluZyBzbWFsbGVyIGFuZCBzbWFsbGVyIHJlZ2lvbnMgaW4gdGhlIHNwYWNlIHNwYXduZWQgYnkgdGhlIG1vZGVsIHByZWRpY3RvcnMsIHRoZW9yZXRpY2FsbHkgaXNvbGF0aW5nIGVhY2ggcGFydGljdWxhciB2YWx1ZSBvZiB0aGUgb3V0Y29tZSAtIG9yIHZlcnksIHZlcnkgc21hbGwgc3Vic2V0cyBvZiB0aGVpciB2YWx1ZXMgLSBpbiBoaWdobHkgaWR5b3NpbmNyYXRpYyByZWdpb25zIQ0KDQpOb3csIGhvdyBkbyB3ZSBhdm9pZCBvdmVyZml0dGluZyBpbiB0aGUgRGVjaXNpb24gVHJlZSBtb2RlbD8gVGhlcmUgYXJlIHR3byB3YXlzIHRvIGRvIGl0LiBUaGUgZmlyc3QgaXMgY2FsbGVkICoqcHJlLXBydW5pbmcqKiBhbmQgZXNzZW50aWFsbHkgbWVhbnMgcGVyZm9ybWluZyBhIGNyb3NzLXZhbGlkYXRpb24gYWNyb3NzIHRoZSBtb2RlbCBjb250cm9sbGluZyBwYXJhbWV0ZXJzIGludGVycHJldGVkIGFzIHN0b3BwaW5nIHJ1bGVzLiBXZSB3aWxsIGZvY3VzIG9uIHByZS1wcnVuaW5nIGxhdGVyIG9uIGluIHRoaXMgc2Vzc2lvbi4gTm93IHdlIHdhbnQgdG8gdW5kZXJzdGFuZCB0aGUgKipwb3N0LXBydW5pbmcqIGFwcHJvYWNoIC0gYWxyZWFkeSBleGVtcGxpZmllZCBpbiBTZXNzaW9uIDE5IC0gd2hpY2ggKGp1c3QgaW1hZ2luZSkgYWxzbyBoYXMgdG8gZG8gd2l0aCBjcm9zcy12YWxpZGF0aW9uIGluIHRoZSBlbmQuIEl0IGlzIGJhc2VkIG9uIHRoZSB2YWx1ZSBvZiB0aGUgKmNvbXBsZXhpdHkgY3JpdGVyaW9uKiB3aGljaCBpcyBjYWxjdWxhdGVkIGFzIHRoZSB0cmVlIGdyb3dzIGluIHJlY3Vyc2l2ZSBwYXJ0aXRpb25pbmcsIG9uIHRoZSBmbHkuIFRoZSBwb3N0LXBydW5pbmcgc29sdXRpb24gdG8gcHJldmVudCB0aGUgdHJlZSBmcm9tIG92ZXJmaXR0aW5nIHRoZSBkYXRhIGlzIHRoZSBmb2xsb3dpbmcgb25lICh0byBiZSBleHBsYWluZWQpOg0KDQpJbnN0ZWFkIG9mIG1pbmltaXppbmcNCg0KJCRTU0U9XEJpZ2dce1xzdW1fe2lcaW57Ul8xfX0oeV9pLWNfMSleMitcc3VtX3tpXGlue1JfMn19KHlfaS1jXzIpXjJcQmlnZ1x9JCQNCg0Kd2UgY2hvb3NlIHRvIG1pbmltaXplDQoNCiQkU1NFX3twZW5hbGl6ZWR9PVxCaWdnXHtcc3VtX3tpXGlue1JfMX19KHlfaS1jXzEpXjIrXHN1bV97aVxpbntSXzJ9fSh5X2ktY18yKV4yXEJpZ2dcfSArIFxhbHBoYXxUfCQkDQoNCndoZXJlICRcYWxwaGEkIGlzIHRoZSAqY29zdCBjb21wbGV4aXR5IHBhcmFtZXRlciogYW5kICR8VHwkIGlzIHRoZSAqbnVtYmVyIG9mIHRlcm1pbmFsIG5vZGVzIGluIHRoZSB0cmVlKi4gUG9zdC1wcnVuaW5nIHdvcmtzIGluIHRoZSBmb2xsb3dpbmcgd2F5Og0KDQotIGZpdCBhIGxhcmdlIHRyZWUsIGEgY29tcGxleCBtb2RlbCB0byB0aGUgZGF0YTsNCi0gc3RhcnQgbG9va2luZyBmb3IgKnN1YnRyZWVzKiBvZiB0aGF0IHRyZWUgdGhhdCBoYXZlIHRoZSBtaW5pbWFsICRTU0Vfe3BlbmFsaXplZH0kDQotIGFjcm9zcyBhIHJhbmdlIG9mIHZhbHVlcyBmb3IgJFxhbHBoYSQ7IGZvciBlYWNoIHNlbGVjdGVkIHN1YnRyZWUNCi0gcGVyZm9ybSBhIGstZm9sZCBjcm9zcy12YWxpZGF0aW9uIGFjcm9zcyAkXGFscGhhJCBhbmQgZGV0ZXJtaW5lIHRoZSAqY3Jvc3MtdmFsaWRhdGlvbiAoaS5lLiBwcmVkaWN0aW9uKSBlcnJvcio7DQotIGNob29zZSB0aGUgc3Vic3RyZWUgd2l0aCBhIG1pbmltYWwgY3Jvc3MtdmFsaWRhdGlvbiBlcnJvciBmb3VuZCBvbiBhIHBhcnRpY3VsYXIgdmFsdWUgb2YgJFxhbHBoYSQuDQoNCkxldCdzIHBlcmZvcm0gcG9zdC1wcnVuaW5nIGluIFIuIFdlIG5lZWQgdG8gdGFrZSBhIGxvb2sgYXQgdGhlICpjb21wbGV4aXR5IHBhcmFtZXRlciB0YWJsZSogd2hpY2ggaXMgZm91bmQgaW4gYHJlZ1RyZWUxJGNwdGFibGVgIGZpZWxkIG9mIHRoZSBtb2RlbCBvYmplY3Q6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpwcmludChyZWdUcmVlMSRjcHRhYmxlKQ0KYGBgDQpXZSBjYW4gYWxzbyB2aXN1YWxpemUgaXQgd2l0aCBgcGxvdGNwKClgOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KcGxvdGNwKHJlZ1RyZWUxKQ0KYGBgDQpDYW4geW91IHNlZSBob3cgdGhlIHZhbHVlIG9mIGB4ZXJyb3JgIHN0YWJpbGl6ZXMgYXQgc29tZSBwb2ludCBhbmQgbWF5YmUgZXZlbiBiZWdpbiB0byBncm93IHNsaWdodGx5PyBSZW1lbWJlciBob3cgd2UgdXNlZCBgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woY3AgPSAwKWAgaW4gb3VyIG1vZGVsIGNhbGw6IHdlIGhhdmUgaW5zdHJ1Y3RlZCBgcnBhcnQoKWAgdG8gc2V0ICRcYWxwaGEkIHRvIGAwYCBhbmQgcHJvZHVjZWQgYSB0cmVlIHRoYXQgb3ZlcmZpdHMgdGhlIGRhdGEhDQoNCk5vdywgcHJlLVBydW5pbmc6IGxvb2sgYXQgdGhlICpjb21wbGV4aXR5IHBhcmFtZXRlciB0YWJsZSogYW5kIGZpbmQgdGhlIHZhbHVlIG9mIGBDUGAgZm9yIHRoZSBsb3dlc3QgYHhlcnJvcmA6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpjcHRhYmxlIDwtIGFzLmRhdGEuZnJhbWUocmVnVHJlZTEkY3B0YWJsZSkNCm9wdGltYWxfY3AgPC0gY3B0YWJsZSRDUFt3aGljaC5taW4oY3B0YWJsZSR4ZXJyb3IpXQ0Kb3B0aW1hbHJlZ1RyZWUxIDwtIHBydW5lKHJlZ1RyZWUxLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNwID0gb3B0aW1hbF9jcCkNCnJwYXJ0LnBsb3Qob3B0aW1hbHJlZ1RyZWUxLA0KICAgICAgICAgICB0eXBlID0gNCwNCiAgICAgICAgICAgZXh0cmEgPSAiYXV0byIpDQpgYGANCkxldCdzIGNoZWNrIHRoZSBzaXR1YXRpb24gbm93Og0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KcGxvdGNwKG9wdGltYWxyZWdUcmVlMSkNCmBgYA0KV2VsbCwgdGhpcyBpcyB3YXkgYmV0dGVyLiBGcm9tIHRoZSBgcnBhcnQoKWAgW2RvY3VtZW50YXRpb25dKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9ycGFydC92ZXJzaW9ucy80LjEtMTUvdG9waWNzL3JwYXJ0LmNvbnRyb2wpOg0KDQo+IGNvbXBsZXhpdHkgcGFyYW1ldGVyLiBBbnkgc3BsaXQgdGhhdCBkb2VzIG5vdCBkZWNyZWFzZSB0aGUgb3ZlcmFsbCBsYWNrIG9mIGZpdCBieSBhIGZhY3RvciBvZiBjcCBpcyBub3QgYXR0ZW1wdGVkLiBGb3IgaW5zdGFuY2UsIHdpdGggYW5vdmEgc3BsaXR0aW5nLCB0aGlzIG1lYW5zIHRoYXQgdGhlIG92ZXJhbGwgUi1zcXVhcmVkIG11c3QgaW5jcmVhc2UgYnkgY3AgYXQgZWFjaCBzdGVwLiBUaGUgbWFpbiByb2xlIG9mIHRoaXMgcGFyYW1ldGVyIGlzIHRvIHNhdmUgY29tcHV0aW5nIHRpbWUgYnkgcHJ1bmluZyBvZmYgc3BsaXRzIHRoYXQgYXJlIG9idmlvdXNseSBub3Qgd29ydGh3aGlsZS4gRXNzZW50aWFsbHksdGhlIHVzZXIgaW5mb3JtcyB0aGUgcHJvZ3JhbSB0aGF0IGFueSBzcGxpdCB3aGljaCBkb2VzIG5vdCBpbXByb3ZlIHRoZSBmaXQgYnkgY3Agd2lsbCBsaWtlbHkgYmUgcHJ1bmVkIG9mZiBieSBjcm9zcy12YWxpZGF0aW9uLCBhbmQgdGhhdCBoZW5jZSB0aGUgcHJvZ3JhbSBuZWVkIG5vdCBwdXJzdWUgaXQuDQoNCkhvd2V2ZXIsIGxldCdzIHRha2UgYSBsb29rIGF0IGFuIGV2ZW4gc2ltcGxlciBtb2RlbC4gTGV0J3Mgbm90IHVzZSB0aGUgYGNwYCBjb250cm9sIHBhcmFtZXRlciBhbmQganVzdCBsZXQge3JwYXJ0fSBncm93IHRoZSB0cmVlIG9uIGl0cyBvd246DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpyZWdUcmVlMiA8LSBycGFydCgNCiAgZm9ybXVsYSA9IG1lZHYgfiAuLA0KICBkYXRhID0gZGF0YVNldCwNCiAgbWV0aG9kICA9ICdhbm92YScpDQpycGFydC5wbG90KHJlZ1RyZWUyLA0KICAgICAgICAgICB0eXBlID0gNCwNCiAgICAgICAgICAgZXh0cmEgPSAiYXV0byIpDQpgYGANCk5vdyB0aGUgdmlzdWFsaXphdGlvbiBmaW5hbGx5IGhlbHBzLCBhdCBsZWFzdC4NCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCnBsb3RjcChyZWdUcmVlMikNCmBgYA0KV2hhdCBjcml0ZXJpYSBoYXMgYmVlbiB1c2VkIG9uIGJlaGFsZiBvZiBgcnBhcnQoKWAgdG8gZ3JvdyB0aGlzIHRyZWU/DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpyZWdUcmVlMiRjb250cm9sJGNwDQpgYGANCkxldCdzIGNvbXBhcmU6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpvcHRpbWFsX2NwIDwtIC4wMQ0Kb3B0aW1hbHJlZ1RyZWUyIDwtIHBydW5lKHJlZ1RyZWUyLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNwID0gb3B0aW1hbF9jcCkNCnJwYXJ0LnBsb3Qob3B0aW1hbHJlZ1RyZWUyLA0KICAgICAgICAgICB0eXBlID0gNCwNCiAgICAgICAgICAgZXh0cmEgPSAiYXV0byIpDQpgYGANClNvIHBsZWFzZSBiZSBhd2FyZSBvZiB0aGUgZGVmYXVsdCB2YWx1ZXMgb2YgdGhlIGNvbnRyb2wgcGFyYW1ldGVycyBpbiBsYXJnZSBhbmQgY29tcGxleCBzdGF0aXN0aWNhbCBsZWFybmluZyBtb2RlbHMhDQoNClRoZSAkUl4yJCBvZiB0aGUgb3B0aW1hbCBtb2RlbCwgaWYgeW91IGFyZSBpbnRlcmVzdGVkIHRvIGxlYXJuLCBpczoNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCnByZWRpY3Rpb25zIDwtIHByZWRpY3QocmVnVHJlZTIsIA0KICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gZGF0YVNldCkNCnJvdW5kKA0KICBjb3IocHJlZGljdGlvbnMsIGRhdGFTZXQkbWVkdileMiwgDQogIDIpDQpgYGANCiMjIyAyLiBDQVJUOiBUaGUgY2xhc3NpZmljYXRpb24gcHJvYmxlbQ0KDQpHcm93aW5nIGEgQ2xhc3NpZmljYXRpb24gVHJlZSBpcyBhIGxpdHRsZSBiaXQgZGlmZmVyZW50IHRoYW4gZ3Jvd2luZyBhIFJlZ3Jlc3Npb24gVHJlZSwgZm9yIG9uZSBzaW1wbGUsIG9idmlvdXMgcmVhc29uIHRoYXQgd2UgaGF2ZSBhbHJlYWR5IGVuY291bnRlcmVkIGluIG91ciBkaXNjdXNzaW9ucyBvZiBHZW5lcmFsaXplZCBMaW5lYXIgTW9kZWxzOiBuYW1lbHksIG1pbmltaXppbmcgJFNTRSQgaXMgbm90IGFuIGlkZWFsIGdvYWwgYW55bW9yZS4gV2UgbmVlZCBzb21lIG5ldyBjcml0ZXJpYSBmb3Igb3VyIGFsZ29yaXRobXMgdG8gZGVjaWRlIHdoZW4gYSBuZXcgc3BsaXQgaXMgdXNlZnVsIGFuZCB3aGVuIG5vdC4gTGV0J3Mgc2VlIHdoYXQgY2FuIGJlIGRvbmUgYWJvdXQgaXQuDQoNCiMjIyMgMi4xIEVsZW1lbnRzIG9mIEluZm9ybWF0aW9uIFRoZW9yeTogZW50ZXJzIEVudHJvcHkNCg0KSW1hZ2luZSBhIHRyZWUtZ3Jvd2luZyBhbGdvcml0aG0gcnVubmluZyBmb3IgYSB0d28tY2xhc3MgY2xhc3NpZmljYXRpb24gcHJvYmxlbSwgZW5kaW5nIHVwIGluIGEgc2V0IG9mIHJlZ2lvbnMgaW4gdGhlIHNwYWNlIHNwYXduZWQgYnkgdGhlIHByZWRpY3RvcnMuIEVhY2ggcmVnaW9uIHBvdGVudGlhbGx5IGNvbnRhaW5zIGJvdGggY29ycmVjdGx5IGFuZCBpbmNvcnJlY3RseSBjbGFzc2lmaWVkIGluc3RhbmNlcyBvZiB0aGUgb3V0Y29tZS4gSSB3YW50IHRvIG1lYXN1cmUgaG93ICJwdXJlIiBhIHJlZ2lvbiBpczogaG93IHdlbGwgZG9lcyBpdCBkZWxpbmVhdGVzIHRydXRoIGZyb20gZmFsc2Vob29kIGluIGNsYXNzaWZpY2F0aW9uLg0KDQpDb25zaWRlciBhIGNvaW4gdG9zcyBzdGF0aXN0aWNhbCBleHBlcmltZW50IHdpdGggJHAgPSAuMjMkIGFuZCAxMDAgb2JzZXJ2YXRpb25zOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KZXhwIDwtIHJiaW5vbShuID0gMTAwLCBzaXplID0gMSwgcHJvYiA9IC4yMykNCnRhYmxlKGV4cCkNCmBgYA0KQXMgZXhwZWN0ZWQsIHRoZXJlIGFyZSB3YXkgbW9yZSBgMHNgLCBnaXZlbiB0aGUgbG93IHByb2JhYmlsaXR5IG9mIGp1c3QgYC4yM2AgdG8gb2JzZXJ2ZSBgMS4gT2YgY291cnNlLCB0aGUgcHJvYmFiaWxpdHkgJHAgPSAuNTAkIG9mIGEgZmFpciBjb2luIHdvdWxkIHByb2R1Y2UgYSBkaWZmZXJlbnQgZGlzdHJpYnV0aW9uIG9mIHJlc3VsdHM6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpleHAgPC0gcmJpbm9tKG4gPSAxMDAsIHNpemUgPSAxLCBwcm9iID0gLjUpDQp0YWJsZShleHApDQpgYGANCioqUS4qKiBXaGljaCBvZiB0aGVzZSB0d28gZGlzdHJpYnV0aW9ucyBpcyBtb3JlICoqaW5mb3JtYXRpdmUqKj8gV2hhdCBicmluZ3MgbW9yZSBrbm93bGVkZ2UgdG8gdXM6IGFuIG91dGNvbWUgb2YgYSBmYWlyIGNvaW4gdG9zcyB3aGljaCBicmluZ3MgYWJvdXQgYDFgIGFuZCBgMGAgd2l0aCBhbiBhcHByb3hpbWF0ZWxseSA1MDo1MCBkaXN0cmlidXRpb24sIG9yIGEgdG9zcyBvZiBhIGNvaW4gd2l0aCAkcCA9IC4yMyQ/IFdoaWNoIG91dGNvbWUgZG9lcyBicmluZyBtb3JlICoqc3VycHJpc2UqKiB0byB1cz8gDQoNCldoYXQgd2UgY2FuIHRlbGwgYWJvdXQgdGhlIHR3byBjb2luczogd2hpY2ggb25lIGlzIG1vcmUgKipwcmVkaWN0YWJsZSoqLiBPYnZpb3VzbHksIHRoZSBjb2luIHdpdGggJHAgPSAuMjMkIGlzIHdheSBtb3JlIHByZWRpY3RhYmxlIHRoYW4gdGhlIGZhaXIgY29pbiB3aGljaCBjYXJyaWVzIGNvbXBsZXRlIHVuY2VydGFpbnR5IGFib3V0IGl0cyBvdXRjb21lLiBUaGVyZSBpcyBzb21laG93IG1vcmUgc3RydWN0dXJlIGluIHRoZSB1bmV2ZW4gZGlzdHJpYnV0aW9uIG9mIG91dGNvbWVzIHdpdGggJHAgPSAuMjMkIHRoYW4gaW4gdGhlIGRpc3RyaWJ1dGlvbiBvZiBvdXRjb21lcyBvZiBhIGZhaXIgY29pbiB0b3NzLg0KDQpDb25zaWRlciB0aGUgZm9sbG93aW5nIG1lYXN1cmU6DQoNCiQkSChYKT0tXHN1bV97aSA9IDF9XntOfVAoeF9pKWxvZ1AoeF9pKSQkDQpJdCBpcyBjYWxsZWQgdGhlICoqU2hhbm5vbiBlbnRyb3B5KiosIG9yIHRoZSAqKlNoYW5ub24gaW5mb3JtYXRpb24gZW50cm9weSoqLCBhbmQgaXQgd2FzIGludHJvZHVjZWQgYnkgW0NsYXVkZSBTaGFubm9uXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9DbGF1ZGVfU2hhbm5vbikgaW4gaGlzIGZhbW91cyAxOTQ4IHBhcGVyICoqQSBNYXRoZW1hdGljYWwgVGhlb3J5IG9mIENvbW11bmljYXRpb24qKi4gTGV0J3MgY29tcHV0ZSB0aGUgZW50cm9weSBvZiBhIGNvaW4gdG9zc2luZyBzdGF0aXN0aWNhbCBleHBlcmltZW50IG92ZXIgYSByYW5nZSBvZiB2YWx1ZXMgb2YgJHAkOiANCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCnAgPC0gc2VxKC4wMDEsIC45OTksIGJ5ID0gLjAwMSkNCmVudCA8LSAtKHAqbG9nKHApICsgKDEtcCkqbG9nKDEtcCkpDQplbnRGcmFtZSA8LSBkYXRhLmZyYW1lKHAgPSBwLCANCiAgICAgICAgICAgICAgICAgICAgICAgZW50cm9weSA9IGVudCkNCmdncGxvdChkYXRhID0gZW50RnJhbWUsIA0KICAgICAgIGFlcyh4ID0gcCwgeSA9IGVudHJvcHkpKSArIA0KICBnZW9tX3BhdGgoc2l6ZSA9IC4yNSwgY29sb3IgPSAiYmx1ZSIpICsgDQogIGdndGl0bGUoIkVudHJvcHkgb2YgYSBDb2luIFRvc3MiKSArDQogIHRoZW1lX2J3KCkgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUpKQ0KYGBgDQpMb29rIGhvdyBlbnRyb3B5IG5pY2VseSBkZXNjcmliZXMgdGhlIHVuY2VydGFpbnR5IG9mIGEgc3RhdGlzdGljYWwgZXhwZXJpbWVudDogaXQgcmVhY2hlcyBpdHMgbWF4aW11bSBmb3IgYSBmYWlyIGNvaW4gd2l0aCAkcD0uNSQsIGFuZCB0aGVuIGZhbGxzIHN5bW1ldHJpY2FsbHkgdG93YXJkcyAkcD0wJCBhbmQgJHA9MSQgd2hlcmUgdGhlIG91dGNvbWVzIG9mIHRoZSBleHBlcmltZW50IGFyZSAqKmNlcnRhaW4qKi4gRW50cm9weSBlYXNpbHkgZ2VuZXJhbGl6ZXMgdG8gbW9yZSB0aGFuIHR3byBvdXRjb21lcyBhbmQgaXMgdXNlZCBhcyBhIGZ1bmRhbWVudGFsIG1lYXN1cmUgb2YgKip1bmNlcnRhaW50eSoqIGluIEluZm9ybWF0aW9uIGFuZCBQcm9iYWJpbGl0eSBUaGVvcnkuIEl0IGlzIGEgY29uY2VwdHVhbCBjb3JuZXJzdG9uZSBvZiBtYW55IGNvbnRlbXBvcmFyeSBtb2RlbHMgb2Ygc3RhdGlzdGljYWwgbGVhcm5pbmcuDQoNCkFzayB5b3Vyc2VsZjogY291bGQgd2UgdXNlIGVudHJvcHkgdG8gbWVhc3VyZSB0aGUgInB1cml0eSIgb2YgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24/IFNob3VsZCBvdXIgRGVjaXNpb24gVHJlZSBtb2RlbHMgbWF5YmUgdGVuZCB0byBncm93IHRyZWVzIHdpdGggdGVybWluYWwgbm9kZXMgdGhhdCBhcmUgbWluaW1hbGx5IGVudHJvcGljPw0KDQojIyMjIDIuMiBHcm93aW5nIGEgQ2xhc3NpZmljYXRpb24gVHJlZQ0KDQpMZXQncyBjb25zaWRlciB0aGUgZm9sbG93aW5nIHR3byBtZWFzdXJlcyBvZiAqaW1wdXJpdHkgb2YgYSBub2RlIGluIGEgdHJlZSogdGhhdCBjYW4gaGVscCBndWlkZSB0aGUgcmVjdXJzaXZlIHBhcnRpdGlvbmluZyBwcm9jZXNzIGluIGNsYXNzaWZpY2F0aW9uOg0KDQoqKkdpbmkgSW1wdXJpdHkqKg0KDQotIERlZmluZQ0KDQokJFxoYXR7cH1fe21rfSA9IFxmcmFjezF9e05fbX1cc3VtX3t5X2lcaW57Ul9tfX1JKHlfaT1rKSQkDQp3aGVyZSAkTl9tJCBpcyB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiByZWdpb24gJG0kLCAkayQgYSBwYXJ0aWN1bGFyIGNsYXNzIHRoYXQgc2hvdWxkIGJlIHByZWRpY3RlZCBjb3JyZWN0bHksIGFuZCAkSSgpJCBhZ2FpbiBqdXN0IGFuIGluZGljYXRvciBmdW5jdGlvbiwgYW5kICR5X2kkIGFuIG9ic2VydmF0aW9uIG9mIHRoZSBvdXRjb21lLiBUaGlzIGlzIG5vdGhpbmcgZWxzZSB0aGFuIHRoZSBwcm9iYWJpbGl0eSBvZiB0aGUgY2xhc3MgJGskIGluIHJlZ2lvbiAkUl9tJC4gVGhlIEdpbmkgSW1wdXJpdHkgaW5kZXggaXMgdGhlbiBqdXN0DQoNCiQkR2luaVwgSW1wdXJpdHkgPSBcc3VtX3trXG5lcXtrfSd9XGhhdHtwfV97bWt9XGhhdHtwfV97bWsnfT1cc3VtX3trPTF9XntLfVxoYXR7cH1fe21rfSgxLVxoYXR7cH1fe21rfSkkJA0KDQpUaGUgR2luaSBJbXB1cml0eSBpcyBhIG1lYXN1cmUgb2YgbWlzc2NsYXNzaWZpY2F0aW9uOiBpdCBsb29rcyBmb3IgdGhlIHByb2JhYmlsaXR5IG9mIGFuIGluc3RhbmNlIGJlaW5nIGluY29ycmVjdGx5IGNsYXNzaWZpZWQuIEdpbmkgaW1wdXJpdHkgcmVhY2hlcyBpdHMgbWluaW11bSBhdCB6ZXJvIHdoZW4gYWxsIGNhc2VzIGluIHRoZSBub2RlIGZhbGwgaW50byBhIHNpbmdsZSB0YXJnZXQgY2F0ZWdvcnksIHNvIGl0IGlzIGEgYmFkbmVzcy1vZi1maXQgbWVhc3VyZSAodGhlIGhpZ2hlciBpdCBpcyAtIHRoZSB3b3JzZSkuIEdpbmkgSW1wdXJpdHkgaGFzIGEgZGVlcCB0aGVvcmV0aWNhbCBjb25uZWN0aW9uIHRvIGVudHJvcHkgd2hpY2ggd2Ugd2lsbCBub3QgZ28gaW50byBub3cuIEVzc2VudGlhbGx5LCB3ZSB3YW50IHRvIGd1aWRlIHRoZSBwcm9jZXNzIG9mIHJlY3Vyc2l2ZSBwYXJ0aXRpb25pbmcgb2YgYSBjbGFzc2lmaWNhdGlvbiB0cmVlcyBieSBsb29raW5nIGZvciBzcGxpdHMgdGhhdCBtaW5pbWl6ZSBHaW5pIEltcHVyaXR5IHJlbGF0aXZlIHRvIGl0cyB2YWx1ZSBpbiB0aGUgcGFyZW50IG5vZGVzLg0KDQoqKkluZm9ybWF0aW9uIEdhaW4qKg0KDQpEZWZpbmUNCg0KJCRJRyhULGEpID0gSChUKS1IKFR8YSkkJA0KDQp3aGVyZSAkSUcoVCxhKSQgaXMgYW4gKipJbmZvcm1hdGlvbiBHYWluKiogb2J0YWluZWQgZnJvbSBhIHNwbGl0ICpzKiBpbiBhIHRyZWUsICRIKFQpJCBpcyB0aGUgZW50cm9weSBvZiB0aGUgcGFyZW50IG5vZGUsIGFuZCAkSChUfGEpJCBpcyB0aGUgc3VtIG9mIGVudHJvcHkgaW4gdGhlIGNoaWxkcmVuIG5vZGVzIC0gKnRoZSBlbnRyb3B5IGZvbGxvd2luZyB0aGUgc3BsaXQqLiBFc3NlbnRpYWxseSwgd2Ugd2FudCB0byBidWlsZCBjbGFzc2lmaWNhdGlvbiB0cmVlcyB0aGF0IG1heGltaXplIEluZm9ybWF0aW9uIEdhaW4gaW4gdGhlaXIgc3BsaXRzLiANCg0KIyMjIyAyLjMgQ2xhc3NpZmljYXRpb24gd2l0aCB7cnBhcnR9LCBhZ2Fpbg0KDQpKdXN0IHRvIHJlbWluZCBvdXJzZWx2ZXMgb24gaG93IHRvIHVzZSB7cnBhcnR9IGZvciBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtcyB3ZSB3aWxsIHJlcGVhdCB0aGUgc3RlcHMgYWxyZWFkeSB0YWtlbiBpbiBTZXNzaW9uIDE5LiANCg0KQ29uc2lkZXIgdGhlIGBIUl9jb21tYV9zZXAuY3N2YCBkYXRhc2V0Og0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KZGF0YVNldCA8LSByZWFkLmNzdihwYXN0ZTAoJ19kYXRhLycsICdIUl9jb21tYV9zZXAuY3N2JyksIA0KICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCANCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLA0KICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikNCmhlYWQoZGF0YVNldCkNCmBgYA0KDQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQp0YWJsZShkYXRhU2V0JGxlZnQpDQpgYGANCg0KVGhlIHRhc2sgaXMgdG8gcHJlZGljdCB0aGUgdmFsdWUgb2YgYGxlZnRgIC0gd2hldGhlciB0aGUgZW1wbG95ZWUgaGFzIGxlZnQgdGhlIGNvbXBhbnkgb3Igbm90IC0gZnJvbSBhIHNldCBvZiBwcmVkaWN0b3JzIGVuY29tcGFzc2luZyB0aGUgZm9sbG93aW5nOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KZ2xpbXBzZShkYXRhU2V0KQ0KYGBgDQoNCi0gKipzYXRpc2ZhY3Rpb25fbGV2ZWwqKjogYSBtZWFzdXJlIG9mIGVtcGxveWVlJ3MgbGV2ZWwgb2Ygc2F0aXNmYWN0aW9uDQotICoqbGFzdF9ldmFsdWF0aW9uKio6IHRoZSByZXN1bHQgb2YgYSBsYXN0IGV2YWx1YXRpb24NCi0gKipudW1iZXJfcHJvamVjdHMqKjogaW4gaG93IG1hbnkgcHJvamVjdHMgZGlkIHRoZSBlbXBsb3llZSB0b29rIHBhcnQNCi0gKiphdmVyYWdlX21vbnRobHlfaG91cnMqKjogaG93IHdvcmtpbmcgaG91cnMgbW9udGhseSBvbiBhdmVyYWdlDQotICoqdGltZV9zcGVuZF9jb21wYW55Kio6IGZvciBob3cgbG9uZyBpcyB0aGUgZW1wbG95ZWUgd2l0aCB1cw0KLSAqKldvcmtfYWNjaWRlbnQqKjogYW55IHdvcmsgYWNjaWRlbnRzPw0KLSAqKnByb21vdGlvbl9sYXN0XzV5ZWFycyoqOiBkaWQgdGhlIHByb21vdGlvbiBvY2N1ciBpbiB0aGUgbGFzdCBmaXZlIHllYXJzPw0KLSAqKnNhbGVzKio6IGRlcGFydG1lbnQgKHNhbGVzLCBhY2NvdW50aW5nLCBociwgdGVjaG5pY2FsLCBzdXBwb3J0LCBtYW5hZ2VtZW50LCBJVCwgcHJvZHVjdF9tbmcsIG1hcmtldGluZywgUmFuZEQpDQotICoqc2FsYXJ5Kio6IHNhbGFyeSBjbGFzcyAobG93LCBtZWRpdW0sIGhpZ2gpDQoNCg0KVHJhaW4gb25lIERlY2lzaW9uIFRyZWUgb24gYHRyYWluYCAoKipOLkIuKiogYG1ldGhvZGAgaXMgbm93IGAiY2xhc3MiYCk6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQojIC0gQmFzZSBNb2RlbA0KY2xhc3NUcmVlIDwtIHJwYXJ0KGxlZnQgfiAuLCANCiAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0YVNldCwgDQogICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImNsYXNzIikNCmBgYA0KDQpWaXN1YWxpemUgdGhlIG1vZGVsIHdpdGggYHJwYXJ0LnBsb3QoKWA6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpycGFydC5wbG90KGNsYXNzVHJlZSkNCmBgYA0KDQojIyMjIDIuNCBQcmUtcHJ1bmluZzogYWRkaXRpb25hbCB3YXlzIHRvIGF2b2lkIG92ZXJmaXR0aW5nIHRoZSB0cmVlLCBhZ2FpbiANCg0KTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlICpjb250cm9sIHBhcmFtZXRlcnMqIHVzZWQgYnkge3JwYXJ0fSB0byBmaXQgdGhpcyBjbGFzc2lmaWNhdGlvbiB0cmVlOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KY2xhc3NUcmVlJGNvbnRyb2wNCmBgYA0KUHJlLXBydW5pbmcgYSBEZWNpc2lvbiBUcmVlIG1vZGVsIGVzc2VudGlhbGx5IG1lYW5zIG5vdGhpbmcgZWxzZSB0aGFuIHR3ZWFraW5nIGEgc3Vic2V0IG9mIGltcG9ydGFudCBjb250cm9sIHBhcmFtZXRlcnMgdW50aWwgdGhlaXIgb3B0aW1hbCB2YWx1ZXMgZm9yIHRoZSBwcm9ibGVtIGF0IGhhbmQgYXJlIGZvdW5kLiBFc3NlbnRpYWxseSBhbGwgb2YgdGhlIGZvbGxvd2luZyBjYW4gYmUgdXNlZCB0byBwcmV2ZW50IHRoZSB0cmVlIGZyb20gYmVjb21pbmcgdG8gY29tcGxleCBhbmQgYmVnaW4gb3ZlcmZpdHRpbmcgdGhlIGRhdGEgKGFzIGNvbmNpc2VseSBleHBsYWluZWQgYnkgW1NpYmFuamFuIERhcyBpbiBoaXMgRGVjaXNpb24gVHJlZXMgYW5kIFBydW5pbmcgaW4gUiBwb3N0IG9uIERldlpvbmVdKGh0dHBzOi8vZHpvbmUuY29tL2FydGljbGVzL2RlY2lzaW9uLXRyZWVzLWFuZC1wcnVuaW5nLWluLXIpKToNCg0KPiAtICoqbWF4ZGVwdGgqKjogVGhpcyBwYXJhbWV0ZXIgaXMgdXNlZCB0byBzZXQgdGhlIG1heGltdW0gZGVwdGggb2YgYSB0cmVlLiBEZXB0aCBpcyB0aGUgbGVuZ3RoIG9mIHRoZSBsb25nZXN0IHBhdGggZnJvbSBhIFJvb3Qgbm9kZSB0byBhIExlYWYgbm9kZS4gU2V0dGluZyB0aGlzIHBhcmFtZXRlciB3aWxsIHN0b3AgZ3Jvd2luZyB0aGUgdHJlZSB3aGVuIHRoZSBkZXB0aCBpcyBlcXVhbCB0aGUgdmFsdWUgc2V0IGZvciBtYXhkZXB0aC4NCj4gLSAqKm1pbnNwbGl0OioqIEl0IGlzIHRoZSBtaW5pbXVtIG51bWJlciBvZiByZWNvcmRzIHRoYXQgbXVzdCBleGlzdCBpbiBhIG5vZGUgZm9yIGEgc3BsaXQgdG8gaGFwcGVuIG9yIGJlIGF0dGVtcHRlZC4gRm9yIGV4YW1wbGUsIHdlIHNldCBtaW5pbXVtIHJlY29yZHMgaW4gYSBzcGxpdCB0byBiZSA1OyB0aGVuLCBhIG5vZGUgY2FuIGJlIGZ1cnRoZXIgc3BsaXQgZm9yIGFjaGlldmluZyBwdXJpdHkgd2hlbiB0aGUgbnVtYmVyIG9mIHJlY29yZHMgaW4gZWFjaCBzcGxpdCBub2RlIGlzIG1vcmUgdGhhbiA1Lg0KPiAtICoqbWluYnVja2V0OioqIEl0IGlzIHRoZSBtaW5pbXVtIG51bWJlciBvZiByZWNvcmRzIHRoYXQgY2FuIGJlIHByZXNlbnQgaW4gYSBUZXJtaW5hbCBub2RlLiBGb3IgZXhhbXBsZSwgd2Ugc2V0IHRoZSBtaW5pbXVtIHJlY29yZHMgaW4gYSBub2RlIHRvIDUsIG1lYW5pbmcgdGhhdCBldmVyeSBUZXJtaW5hbC9MZWFmIG5vZGUgc2hvdWxkIGhhdmUgYXQgbGVhc3QgZml2ZSByZWNvcmRzLiBXZSBzaG91bGQgYWxzbyB0YWtlIGNhcmUgb2Ygbm90IG92ZXJmaXR0aW5nIHRoZSBtb2RlbCBieSBzcGVjaWZ5aW5nIHRoaXMgcGFyYW1ldGVyLiBJZiBpdCBpcyBzZXQgdG8gYSB0b28tc21hbGwgdmFsdWUsIGxpa2UgMSwgd2UgbWF5IHJ1biB0aGUgcmlzayBvZiBvdmVyZml0dGluZyBvdXIgbW9kZWwuDQoNCkFuZCBob3cgaXMgdGhpcyAqdHdlYWtpbmcqIG9mIHRoZSBjb250cm9sIHBhcmFtZXRlcnMgcGVyZm9ybWVkPyBZb3UgYWxyZWFkeSBrbm93IHRoZSBhbnN3ZXI6IGJ5IGNyb3NzLXZhbGlkYXRpbmcgdGhlIG1vZGVsIGFjcm9zcyBhIHJhbmdlIG9mIHRoZWlyIHZhbHVlcywgZWFjaCB0aW1lIHBlcmZvcm1pbmcgYW5kIFJPQyBhbmFseXNpcyBmb3IgY2xhc3NpZmljYXRpb24gYXMgd2UgZGlkIGluIFNlc3Npb24gMTkhDQoNCiMjIyMgMi41IFZhcmlhYmxlIEltcG9ydGFuY2UNCg0KVGhlIGltcG9ydGFuY2Ugb2YgZWFjaCB2YXJpYWJsZSBpbiBhIERlY2lzaW9uIFRyZWUgbW9kZWwgY2FuIGJlIG9idGFpbmVkIGZyb20gdGhlIGB2YXJpYWJsZS5pbXBvcnRhbmNlYCBmaWVsZCBvZiB0aGUgbW9kZWwgb2JqZWN0IGluIHRoZSBmb2xsb3dpbmcgd2F5Og0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KaW1wb3J0YW5jZSA8LSANCiAgYXMuZGF0YS5mcmFtZSgNCiAgICBzb3J0KGNsYXNzVHJlZSR2YXJpYWJsZS5pbXBvcnRhbmNlLCBkZWNyZWFzaW5nID0gVCkNCiAgICApDQpjb2xuYW1lcyhpbXBvcnRhbmNlKSA8LSAnSW1wb3J0YW5jZScNCnByaW50KGltcG9ydGFuY2UpDQpgYGANCg0KVGhlIGRvY3Mgc2F5Og0KDQo+IEFuIG92ZXJhbGwgbWVhc3VyZSBvZiB2YXJpYWJsZSBpbXBvcnRhbmNlIGlzIHRoZSBzdW0gb2YgdGhlIGdvb2RuZXNzIG9mIHNwbGl0IG1lYXN1cmVzIGZvciBlYWNoIHNwbGl0IGZvciB3aGljaCBpdCB3YXMgdGhlIHByaW1hcnkgdmFyaWFibGUuDQoNCioqKg0KDQojIyMgRnVydGhlciBSZWFkaW5ncw0KDQorIFtUaGUgRWxlbWVudHMgb2YgU3RhdGlzdGljYWwgTGVhcm5pbmcsIEhhc3RpZSwgVC4sIFRpYnNoaXJhbmksIFIuICYgRnJpZWRtYW4sIEouLCAxMnRoIHByaW50aW5nIHdpdGggY29ycmVjdGlvbnMgYW5kIHRhYmxlIG9mIGNvbnRlbnRzLCBKYW4gMjAxNywgQ2hhcHRlciA5LjIgVHJlZS1CYXNlZCBNZXRob2RzKV0oaHR0cHM6Ly93ZWIuc3RhbmZvcmQuZWR1L35oYXN0aWUvRWxlbVN0YXRMZWFybi9wcmludGluZ3MvRVNMSUlfcHJpbnQxMl90b2MucGRmKQ0KDQorIFtBbiBJbnRyb2R1Y3Rpb24gdG8gUmVjdXJzaXZlIFBhcnRpdGlvbmluZyBVc2luZyB0aGUgUlBBUlQgUm91dGluZXMsIFRlcnJ5IE0uIFRoZXJuZWF1LCBFbGl6YWJldGggSi4gQXRraW5zb24sIE1heW8gRm91bmRhdGlvbiwgQXByaWwgMTEsIDIwMTldKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9ycGFydC92aWduZXR0ZXMvbG9uZ2ludHJvLnBkZikNCg0KKyBbQSBuaWNlLCBpbnR1aXRpdmUsIGFuZCBwcmVjaXNlIGV4cGxhbmF0aW9uIG9mIEluZm9ybWF0aW9uIEVudHJvcHkgYW5kIEluZm9ybWF0aW9uIEdhaW4gd2l0aCBleGFtcGxlczogQnJpYW4gQW1iaWVsbGkncyBibG9nXShodHRwczovL2JhbWJpZWxsaS5jb20vdGlsLzIwMTctMTAtMjItaW5mb3JtYXRpb24tZ2Fpbi8pDQoNCisgW0EgbmljZSwgaW50dWl0aXZlLCBhbmQgcHJlY2lzZSBleHBsYW5hdGlvbiBvZiBHaW5pIEltcHVyaXR5IHdpdGggZXhhbXBsZXM6IEJyaWFuIEFtYmllbGxpJ3MgYmxvZ10oaHR0cHM6Ly9iYW1iaWVsbGkuY29tL3RpbC8yMDE3LTEwLTI5LWdpbmktaW1wdXJpdHkvKQ0KDQorIFtBIHN0ZXAgYnkgc3RlcCBjYWxjdWxhdGlvbnMgb2YgSW5mb3JtYXRpb24gR2FpbiBmb3IgRGVjaXNpb24gVHJlc3MgZnJvbSBFbmdsaXNoIFdpa2lwZWRpYV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRGVjaXNpb25fdHJlZV9sZWFybmluZyNJbmZvcm1hdGlvbl9nYWluKQ0KDQoNCioqKg0KR29yYW4gUy4gTWlsb3Zhbm92acSHDQoNCkRhdGFLb2xla3RpdiwgMjAyMC8yMQ0KDQpjb250YWN0OiBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tDQoNCiFbXSguLi9faW1nL0RLX0xvZ29fMTAwLnBuZykNCg0KKioqDQpMaWNlbnNlOiBbR1BMdjNdKGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy9ncGwtMy4wLnR4dCkNClRoaXMgTm90ZWJvb2sgaXMgZnJlZSBzb2Z0d2FyZTogeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAzIG9mIHRoZSBMaWNlbnNlLCBvciAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLg0KVGhpcyBOb3RlYm9vayBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLCBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZiBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuDQpZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhbG9uZyB3aXRoIHRoaXMgTm90ZWJvb2suIElmIG5vdCwgc2VlIDxodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvPi4NCg0KKioqDQoNCg==