1 Motivation

Modern gradient boosting trees (GBT) is undoubtedly one of the most powerful machine learning algorithms for traditional supervised learning tasks in the recent decade. Ever since the creation of XGBoost there is a booming in different modern designs of GBT in the open source area aiming at not only model accuracy but also computational efficiency.

In this notebook we try to unbox two such powerful GBT frameworks: xgboost and lightgbm. We will focus more on the methodology rather than their APIs to deeply understand how these algorithms work and why they are so effective comparing to the vanilla version of GBT, which has been formally introduced nearly 20 years ago (Friedman 2001).

2 Gradient Boosting Machines

Before we step into the modern design of gradient boosting trees, let’s have a quick look at the traditional (vanilla) gradient boosting machines.

Gradient boosting is a model ensembling technique. It combines multiple models into one in an iterative and additive manner. Each individual model is indeed a weak learner, but jointly they end up providing powerful predictions. Mathematically speaking, the model can be expressed as:

\[ \hat{y} = F_k(x) = \sum_k f_k(x), \]

where \(k\) is number of iterations (and hence number of weak learners), \(f_k\) belongs to some model family and \(x\) is the feature vector.

A weak learner can be a model with lesser features, lesser parameters, or a simpler structure.

2.1 Functional Gradient Descent

Now if we rewrite the above model as:

\[ \begin{aligned} y &= F_k(x) + \epsilon \\ &= F_{k-1}(x) + f_k(x) + \epsilon, \end{aligned} \]

then we have:

\[ f_k(x) = y - F_{k-1}(x) - \epsilon. \]

The equation tells us that the function added at round \(k\) is fitting the model residual of the previous ensembled model (\(F_{k-1}\)). If our loss is squared error, model residual is indeed the negative gradient to the loss w.r.t. the function at the last step (ignoring the constant term):

\[ - \frac{\partial\frac{1}{2}\big[y - F_{k-1}\big]^2}{\partial F_{k-1}} = (y - F_{k-1}). \]

To generalize the idea, given any loss function \(L(y, F(x))\), to solve for the optimal function \(f_k\) to add at round \(k\) we are actually doing functional gradient descent:

\[ f_k(x) = - \frac{\partial L(y, F_{k-1}(x))}{\partial F_{k-1}(x)}. \]

The negative gradient is also referred to as pseudo-residual. When our loss function is not squared error, we no longer literally fit a model on residuals but on the pseudo-residuals.

2.2 Boosting v.s. Gradient Boosting

It can be easily confused about the name gradient boosting. As we already seen, graident boosting is indeed a gradient descent algorithm, but operating at functional space. That is, to optimize the objective function we are not looking for parameters but looking for functions. In the training phase we are iteratively searching for a function (a weak learner) to be included into our model.

Boosting is yet another different model ensembling technique, not to be confused with gradient boosting. The general idea of boosting is to also train a model iteratively. In each round the training examples will be re-weighted based on the results (losses) from the last run. Harder examples will gain higher weights so the subsequent training will shift focus onto them. The most popular boosting algorithm is probably AdaBoost, abbreviated from adaptive boosting.

Boosting is similar to gradient boosting in that both are model ensembling with additive weak learners and iterative training. But they are very different in the way they search for the optimal function at each iteration. Boosting fits a learner with examples re-weighted by loss generated from the previous ensemble. Gradient boosting instead fits a learner with pseudo-residuals: the negative functional gradient w.r.t. the loss.

2.3 Linear Model as Weak Learner

The choice of weak learner has a huge impact on the effectiveness of gradient boosting machines. Not all weak learners can gain equally from such ensemble. In this section we demonstrate a model ensemble using a weak linear learner. It turns out that gradient boosting with a linear model may do no good at all.

Let’s create some simulated data based on a simple ground-truth (non-linear) data generating process:

library(data.table)

set.seed(666)

# DGP: y = 3 x_1 + 6 x_2 -1.5 x_1x_2 + e
n <- 1000
x1 <- rnorm(n, 2, 4)
x2 <- runif(n)
e <- rnorm(n)
y <- 3 * x1 + 6 * x2 - 1.5 * x1 * x2 + e

# Train-test split.
is_test <- runif(n) < .2
D <- data.table(x1=x1, x2=x2, y=y)
head(D)

We fit a linear model with all features (consdier it as a “strong” learner):

# Fit a linear model with all features.
f <- lm(y ~ x1 + x2, data=D[!is_test])
D <- D[, yhat:=predict(f, D)]

# MSE as our loss function.
mse <- function(y, yhat) {
  mean((y - yhat)^2)
}

# Compute testing loss.
with(D[is_test], mse(y, yhat))
[1] 3.67646

Now let’s ensemble a weak learner which only takes one feature. We train the model based on gradient boosting framework. Since our loss is squared error, we can simply fit the model residual of the previous ensemble:

f1 <- lm(y ~ x1, data=D[!is_test])
D <- D[, yhat1:=predict(f1, D)]
D <- D[, resid1:=y - yhat1]

with(D[is_test], mse(y, yhat1))
[1] 4.47007
f2 <- lm(resid1 ~ x2, data=D[!is_test])
D <- D[, yhat2:=predict(f2, D)]
D <- D[, resid2:=y - yhat1 - yhat2]

with(D[is_test], mse(y, yhat1 + yhat2))
[1] 3.678013
f3 <- lm(resid2 ~ x1, data=D[!is_test])
D <- D[, yhat3:=predict(f3, D)]
D <- D[, resid3:=y - yhat1 - yhat2 - yhat3]

with(D[is_test], mse(y, yhat1 + yhat2 + yhat3))
[1] 3.676449
f4 <- lm(resid3 ~ x2, data=D[!is_test])
D <- D[, yhat4:=predict(f4, D)]
D <- D[, resid4:=y - yhat1 - yhat2 - yhat3 - yhat4]

with(D[is_test], mse(y, yhat1 + yhat2 + yhat3 + yhat4))
[1] 3.67646

As one may realize, the boosted model won’t outperform a simple linear model with all features included. Is it because of our naive built-from-scratch implementation? Not really. Let’s use xgboost–one of the state-of-the-art gradient boosting frameworks–to train a GBM with linear learner:

library(xgboost)

Dx <- xgb.DMatrix(as.matrix(D[!is_test, .(x1, x2)]), label=D[!is_test, y])
Dt <- xgb.DMatrix(as.matrix(D[is_test, .(x1, x2)]), label=D[is_test, y])

bst <- xgb.train(params=list(objective="reg:squarederror", booster="gblinear"), 
                 data=Dx, watchlist=list(test=Dt),
                 nround=50, early_stopping_rounds=5, verbose=0)

yhat <- predict(bst, as.matrix(D[is_test, .(x1, x2)]))
mse(D[is_test, y], yhat)
[1] 3.678262

The result is roughly the same.

In general, gradient boosting does not help imporving a linear model. This is by large due to the fact that GBM is an additive model. Combining linear models additively will just result in yet another linear model. Indeed, using a linear model as the base learner will have the effect of solving a full linear system (a model with all features) in an iterative and approximated manner. So at best the result is the same as a single linear model with all features.

2.4 Tree Model as Weak Learner

Based on empirical findings, the best weak learner in GBM is a tree. Most of the gradient boosting (or just boosting) implementations use tree models as their base learner.

For our simulated data, gradient boosted trees can easily outperform the linear model:

bst2 <- xgb.train(params=list(objective="reg:squarederror", booster="gbtree"), 
                  data=Dx, watchlist=list(test=Dt),
                  nround=50, early_stopping_rounds=5, verbose=0)

yhat <- predict(bst2, as.matrix(D[is_test, .(x1, x2)]))
mse(D[is_test, y], yhat)  # Smaller loss achieved.
[1] 1.432373

For the rest of the notebook we will focus our discussion on tree-based gradient boosting.

3 Tree Ensembles

Before we talk about gradient boosting trees, let’s have a brief review on tree models in general, from a single tree to tree ensembles.

Tree models are non-differentiable. Gradient-based optimizers won’t directly work on tree models. This makes them very different from other machine learning models such as neural networks.

Tree models are powerful because they are non-linear. Features are allowed to interact with each other in a sequential and loss-guided manner. And they are invariant to feature scale since a tree split is only ordinal. This simplifies data preprocessing and hence the overall pipeline.

3.1 A Single Tree

The idea of a single tree model is rather simple. The model uses a split finding algorithm to enumerate over all the possible splits on all the features. Once a best split is found based on a particular measure, the model move on to find the next split given the current node, growing the tree deeper and deeper until a certain criterion is met.

Here is a general pseudo code describing a single split algorithm in its exact form:

[Exact Tree Split Algorithm]

m: total number of features
n: total number of examples
s: score to measure the quality of a split
x_ij: the i-th example value on feature j

for feature j in 1 to m:
  sort x_j
  for example x_ij in 1 to n on feature j:
    compute s

do split using j* at x_ij* associated with maximum s

A Tree model involves multiple sequential splits to arrive at the terminal node which gives prediction. Here are several important components that must be taken care for a detailed implementation:

Feature Iteration

The algorithm is greedy. To search for the optimal split all features are traversed. The immediate consequence is the over-fitting nature of a single tree. And it is such difficulty in generalization leads to the idea of tree ensembles.

Feature Value Iteration

For each feature, a sorting operation is required to traverse over the (distinct) sorted values of that feature. The more values a feature can take the more computing resources are needed to find the best split.

Quality of Split Measure

This is probably the most important part of a tree algorithm. It directly affects the accuracy of the resulting tree. The quality score measures how good a node is splitting the examples. In the literature this is often refered to as impurity. A classical impurity measure is Gini index for classification and residual sum of squares for regression.

Depth of the Tree

How deep (how many splits) should we grow the tree? This is a hyper-parameter that can affect model generalization. For classification we can grow a tree as deep as until every node contains only one single example. That is, until no split can be further achieved. A tree that is too deep will definitely suffer from over-fitting.

A single tree is usually very bad at generalization. The only benefit is probably its interpretability since the tree structure itself is a set of human friendly decision rules. However due to its poor generalization over unseen data, such interpretability is practically of no use.

3.1.1 A Regression Tree

Continue with our simulated dataset, let’s quickly build a single regression tree and evaluate its performance on the testing set.

library(rpart)  # Recursive PARTitioning 
library(rpart.plot)

f <- rpart(y ~ x1 + x2, data=D[!is_test], method="anova", model=TRUE)
rpart.plot(f, digits=3)  # For each node the mean value and population are shown.

The testing score is worse than a linear model:

mse(D[is_test, y], predict(f, D[is_test]))
[1] 6.3468

Even though our true model is non-linear, a tree failed to outperform a linear model. Does that make sense? Our tree may be too simple to perform a task on predicting a continous value. The tree is only capable of predicting 9 distinct values, which make it difficult to compete with a linear model on a continous metric such as squared error.

Indeed it is very easy to build a more complicated (less regulated) tree to outperform the linear model:

f2 <- rpart(y ~ x1 + x2, data=D[!is_test], method="anova", model=TRUE,
            control=rpart.control(minsplit=10, cp=5e-4))
uniqueN(f2$where)  # How many terminal nodes?
[1] 33
mse(D[is_test, y], predict(f2, D[is_test]))
[1] 2.996025

Put aside regularization and model complexity, a common quality of split measure for a regression tree is the sum of squares \(SS = \sum (y_i -\bar{y})^2\). So for a given node we find a split such that the decrease in sum-of-squares \(SS_T - (SS_L + SS_R)\) is the largest. \(SS_T\) is the \(SS\) of current node, \(SS_R\) is \(SS\) of the right-hand node resulted from the split, \(SS_L\) the \(SS\) of left-hand node. Note that the prediction of a regression tree node is its mean value. Hence \(SS\) is the squared error of that node.

3.1.2 A Classification Tree

We use the old-school UCI Iris dataset to quickly demo a single classification tree.

confusion_matrix <- function(y_true, y_pred) {
  table(y_true, y_pred)
}

# Split IRIS dataset into train-test.
is_test_iris <- runif(nrow(iris)) < .3
iris_train <- iris[!is_test_iris,]
iris_test <- iris[is_test_iris,]

# Grow a single classification tree.
f <- rpart(Species ~ ., data=iris_train, method="class")
rpart.plot(f)

# Confusion matrix for the training set.
print(confusion_matrix(iris_train$Species, predict(f, iris_train, type="class")))
            y_pred
y_true       setosa versicolor virginica
  setosa         39          0         0
  versicolor      0         34         2
  virginica       0          1        33
# Confusion matrix for the testing set.
print(confusion_matrix(iris_test$Species, predict(f, iris_test, type="class")))
            y_pred
y_true       setosa versicolor virginica
  setosa         11          0         0
  versicolor      0         12         2
  virginica       0          2        14

A single classification tree model can perform both well in training and testing set for Iris. Since the dataset is a bit too trivial. In any real world application, a single tree is usually not enough to solve the problem. And this is where we need more trees.

3.2 From One Tree to Many Trees

To overcome the shortfall of a single tree, ensembling technique is used to combine multiple trees into one single model. There are two very successful ensembling techniques used for tree models: bagging and boosting. The general idea is to combine many simple (hence weak) trees as a tree committee for final prediction. It turns out that the variability in many simple/weak and especially non-linear learners can jointly outperform a single (and usually over-fitting) complicated/strong learner.

3.2.1 Bagging Ensembles

The most well-known bagging tree model is Random Forest (RF hereafter). In each single tree in RF the split algorithm is modified to only iterate over a subsample of all features. This reduces the ability of any single tree to over-fit the training data and create diversity among different trees. RF also use bootstrap sampling on the training dataset when doing feature value iteration. Rather than searching split on full training set it only searches on a set of bootstrapped samples. Some model variants even perform only a small number of random splits (instead of finding the best split with a full scan) on the feature value iteration phase.

Although it is claimed in theory that RF is strong against over-fitting due to both feature (column) subsampling and bootstrap (row) sampling, in reality RF can still easily over-fit without proper tunning.

One obvious advantage of RF, in terms of computational efficiency, is that it is embarrassingly parallel. Most modern implementations of RF supports single-machine cpu parallel or even distributed computing.

3.2.2 Boosting Ensembles

Another ensembling technique which has been proven to be even more power empirically is boosting. We have already discussed the difference between boosting and gradient boosting. Here we specifically refer to gradient boosting since it usually gives better results and it is our main topic in this notebook.

Unlike RF which trains multiple trees independently and aggregates the results, gradient boosting trees (GBT hereafter) adopt an additive training procedure, so each tree is iteratively added into the model.

Similar to RF or any other tree ensembling, each individual tree is a very weak learner. But jointly they become very good at generalization. Boosting trees indeed have achieved quite some of the state-of-the-art performance in real world applications.

Even though GBT has been introduced in the literature for almost 20 years already, it is the recent 5 years that several very powerful open source implementations have emerged to really shape the practitioners’ choice of toolbox. In the following sections we’d like to deep-dive into two of them: xgboost and lightgbm. We will focus especially on their novelties in bettering GBT to the extreme.

4 XGBoost

Chen and Guestrin (2016) open source the xgboost package, probably one of the most influential GBT framework to date. It also comes with the most variety of langauge API support: Python, R, Julia, Java, …, etc. It is also more or less integrated with distributed computing platform such as Spark, and cloud solution such as GCP and AWS.

print(packageVersion("xgboost"))
[1] '0.90.0.2'

4.1 Regularization on Impurity

The name xgboost, though, actually refers to the engineering goal to push the limit of computations resources for boosted tree algorithms. Which is the reason why many people use xgboost. For model, it might be more suitable to be called as regularized gradient boosting.1

One important and novel feature of XGBoost is to introduce regularization in the impurity measure. Before this, most tree ensembles focus on using traditional way of regularization, such as pruning, subsampling, or limiting number of leaves.

In xgboost the formulation of the objective function makes its tree ensembles extremely flexible. Loss can be plug-and-played as well as evaluation metrics. In general, the model has the following cost function:

\[ \text{Cost} = \sum_{i=1}^n L(y_i, \hat{y_i}) + \frac{1}{2}\lambda\vert\vert W \vert\vert^2 + \alpha\vert\vert W\vert\vert, \]

where \(L(y_i, \hat{y_i})\) is a per-instance loss, \(W\) is the weights of all tree leaves, \(\lambda\) and \(\alpha\) are L2 and L1 regularization term, respectively.

(By default xgboost has \(\lambda = 1\) and \(\alpha = 0\).)

The cost function indeed looks no different than any other differentiable machine learning model we already know. What makes it novel is that xgboost uses the cost as the impurity measure to implement the split finding algorithm in a tree, which is not differentiable. But how?

4.2 Additive Training

For convenience let’s re-state the gradient boosting model function here, with a bit more detailed notations:

\[ \begin{aligned} \hat{y}^{(k)} = F_k(x) &= \sum_k f_k(x) \\ &= \sum_{t=1}^{k-1}f_t(x) + f_k(x), \end{aligned} \]

where \(\hat{y}^{(k)}\) is the model prediction for model at round \(k\).

Define the loss function (without regularization term for now for simplicity) at round \(k\) for \(i = 1, ..., n\) examples to be:

\[ \begin{aligned} \text{Loss} &= \sum_{i=1}^n L(y_i, \hat{y_i}^{(k)}) \\ &= \sum_{i=1}^n L(y_i, \hat{y_i}^{(k-1)} + f_k(x_i)). \end{aligned} \]

Here \(\hat{y_i}^{(k-1)}\) is purely determined by model already trained in the previous round, we only care about picking up a tree model \(f_k(x_i)\) which can further reduce the loss the most.

When the loss is a squared error, the function is somewhat trackable:

\[ \begin{aligned} \text{MSE-Loss} &= \frac{1}{n}\sum_{i=1}^n (y_i - \hat{y_i}^{(k)})^2 \\ &= \frac{1}{n}\sum_{i=1}^n \bigg[ \underbrace{(y_i - \hat{y}^{(k-1)})^2}_\text{Constant} - 2(y_i - \hat{y_i}^{(k-1)})f_k(x_i) + f_k(x_i)^2\bigg] \\ &= \frac{1}{n}\sum_{i=1}^n \bigg[ - 2\underbrace{(y_i - \hat{y_i}^{(k-1)}}_\text{Residual at k-1})f_k(x_i) + f_k(x_i)^2\bigg] + \text{Constant}. \end{aligned} \]

But when the loss is, say, a cross-entropy loss (which is fairly common), the function becomes much harder to deal with.

4.3 Second-Order Loss Approximation

In order to allow a general plug-and-play framework for different loss functions, instead of solving the exact functional form of a particular loss, xgboost approximates the loss with a second-order Taylor series expansion.

The 2nd-order expansion of a function \(L(x)\) at point \(x_0\) can be written as:

\[ L(x) \approx L(x_0) + L'(x_0)(x - x_0) + \frac{1}{2}L''(x_0)(x - x_0)^2. \]

Now define:

\[ x - x_0 = a, \]

we then have:

\[ L(x_0 + a) \approx L(x_0) + L'(x_0)a + \frac{1}{2}L''(x_0)a^2, \]

or simply:

\[ L(x + a) \approx L(x) + L'(x)a + \frac{1}{2}L''(x)a^2. \]

Use the form we can re-write our loss function:

\[ \begin{aligned} \text{Loss} &= \sum_{i=1}^n L(y_i, \hat{y_i}^{(k)}) \\ &= \sum_{i=1}^n L(y_i, \hat{y_i}^{(k-1)} + f_k(x_i)), \\ \text{Approx. Loss} &= \sum_{i=1}^n \Bigg[ \underbrace{ \vphantom{\frac{\partial L(y_i, \hat{y_i}^{(k-1)})}{\partial \hat{y_i}^{(k-1)}}} L(y_i, \hat{y_i}^{(k-1)})}_\text{Constant} + \underbrace{\frac{\partial L(y_i, \hat{y_i}^{(k-1)})}{\partial \hat{y_i}^{(k-1)}}}_{g_i}f_k(x_i) + \underbrace{\frac{\partial^2 L(y_i, \hat{y_i}^{(k-1)})}{\partial^2 \hat{y_i}^{(k-1)}}}_{h_i}f_k(x_i)^2 \Bigg] \\ &= \sum_{i=1}^n \Bigg[ g_if_k(x_i) + h_if_k(x_i)^2 \Bigg] + \text{Constant}. \end{aligned} \]

We follow the author’s notation on the first order term as \(g_i\) (g for gradient) and the second order term as \(h_i\) (h for Hessian). Together with the regularization term it becomes the quality of split measure which ultimately guides the model to pick up the next learner \(f_k\) at round \(k\).

Essentially XGBoost employs a functional gradient descent up to the second-order derivative. This makes it more precise in finding the next best learner.

Note that the value of \(g_i\) and \(h_i\) both remain the same for the current round, meaning that the computing cost is largely reduced when we are evaluating among different tree splits. This is one of the reason why XGBoost is so much faster than any other vanilla implementation of GBT.

4.4 Tree Split Optimization

Now let’s formally parameterize a tree model \(f_k(x_i)\) with \(T\) terminal nodes (leaves), \(w_j\) denotes the weight for \(j\)-th leaf, and \(I_j\) the set of examples assigned to leaf \(j\). The approximated loss (with regularization term this time) at round \(k\) can then be re-written in a group-by-leaf summation:

\[ \begin{aligned} Cost^k &= \sum_{i=1}^n \Bigg[ g_if_k(x_i) + h_if_k(x_i)^2 \Bigg] + \frac{1}{2}\lambda\vert\vert W \vert\vert^2 + \alpha\vert\vert W\vert\vert \\ &= \sum_{j=1}^T \Bigg[ \sum_{i \in I_j} g_iw_j + \frac{1}{2}\sum_{i \in I_j}h_iw_j^2 \Bigg] + \frac{1}{2}\lambda\sum_{j=1}^T w_j^2 + \alpha\sum_{j=1}^T w_j \\ &= \sum_{j=1}^T \Bigg[ \sum_{i \in I_j} g_iw_j + \frac{1}{2}(\sum_{i \in I_j}h_i + \lambda)w_j^2 + \alpha\vert w_j\vert \Bigg]. \end{aligned} \]

Given the cost function we can solve for optimal weight \(w_j^*\) minimizing the cost with a first-order-condition:

\[ \frac{\partial Cost^k}{\partial w_j} = 0, \]

which gives:

\[ \sum_{i \in I_j} g_i + (\sum_{i \in I_j}h_i + \lambda)w_j + \alpha\frac{w_j}{\vert w_j\vert} = 0. \]

For a null L1 penalty (\(\alpha = 0\)) the solution is simply:

\[ \begin{aligned} w_j^* &= - \frac{\sum_{i \in I_j} g_i}{\sum_{i \in I_j}h_i + \lambda}, \\ Cost^{k*} &= - \frac{1}{2} \sum_{j=1}^T \frac{\big(\sum_{i \in I_j} g_i\big)^2}{\sum_{i \in I_j}h_i + \lambda}. \end{aligned} \]

Now we have solved the weights that minimize the cost once \(T\) is determined. We will also need the optimized cost to determine the best split. That is, the quality of split measure. The best split should result in two nodes such that the total cost of two nodes is reduced the most from that of their parent node.

A learning rate can be applied to the optimal weights at each iteration, making the ensembler more conservative in updating itself.

Here is a pseudo code describing the core algorithm:

[XGBoost Additive Training Algorithm]

F: initial model
M: total number of features
N: total number of examples
K: total number of iterations (tree models)
T: total number of leaves
g_ik: g_i for example i at round k
h_ik: h_i for example i at round k
g_ijk: g_i for example i at round k assigned to leaf j
h_ijk: h_i for example i at round k assigned to leaf j
cost: the score at leaf j, sum(g_ijk)^2 / (sum(h_ijk) + lambda)
x_im: the i-th example value on feature m

for round k in 1 to K:
  while number of split < T:
    compute g_ik, h_ik for all i
    for feature j in 1 to m:
      sort x_j
      for example x_im in i = 1 to n on feature m:
        split by x_im into left and right leaves
        compute sum of cost on left and right leaves
    find best split using feature m* at x_im* associated with minimum cost
  update model F <- F + tree learned

4.4.1 Common Loss Functions

xgboost already comes with several loss functions built-in. We can also define our custom loss easily by just implementing the first (\(g\)) and second order (\(h\)) gradients of that loss w.r.t. model output. Here we simply review the two mostly used loss functions.

Squared Loss

Deriving \(g\) and \(h\) for a squared loss is trivial:

\[ \begin{aligned} g_i &= \frac{\partial(y_i - \hat{y}_i^{(k-1)})^2}{\partial\hat{y}_i^{(k-1)}} = 2(\hat{y}_i^{(k-1)} - y_i), \\ h_i &= \frac{\partial^2(y_i - \hat{y}^{(k-1)})^2}{\partial^2\hat{y}^{(k-1)}} = 2. \end{aligned} \]

We can ignore constant so the gradient \(g_i\) is simply \(\hat{y} - y\) and the hessian is unity.

Let’s use R’s symbolic derivatives to verify our derivation:

dy2y <- deriv3(~ (y - yhat)^2, c("yhat"))

yhat <- rnorm(5)
y <- rnorm(5)

2*(yhat - y)  # Gradient by hands.
[1]  3.187627  4.822539  4.377736 -4.599785  2.648223
eval(dy2y)  # By symbolic derivatives.
[1] 2.540241 5.814221 4.791143 5.289506 1.753271
attr(,"gradient")
          yhat
[1,]  3.187627
[2,]  4.822539
[3,]  4.377736
[4,] -4.599785
[5,]  2.648223
attr(,"hessian")
, , yhat

     yhat
[1,]    2
[2,]    2
[3,]    2
[4,]    2
[5,]    2

Cross-Entropy Loss

A subtle details must be taken care for cross-entropy loss. Intuitively we’d like to just take the derivative w.r.t. the model output probability:

\[ \begin{aligned} g_i &= \frac{\partial\big[- y_i\ln\hat{y}_i^{(k-1)} - (1 - y_i)\ln(1 - \hat{y}_i^{(k-1)})\big]} {\partial\hat{y}_i^{(k-1)}} = \frac{\hat{y}_i^{(k-1)} - y_i}{(1 - \hat{y}_i^{(k-1)})\hat{y}_i^{(k-1)}}, \\ h_i &= \frac{\partial^2\big[- y_i\ln\hat{y}_i^{(k-1)} - (1 - y_i)\ln(1 - \hat{y}_i^{(k-1)})\big]} {\partial^2\hat{y}_i^{(k-1)}} = \frac{1 - y_i}{(1 - \hat{y}_i^{(k-1)})^2} + \frac{y_i}{(\hat{y}_i^{(k-1)})^2}. \end{aligned} \]

Again use symbolic differentiation to verify our derivation:

# Demonstrate symbolic derivatives.
dq2q <- deriv3(~ - (y*log(q) + (1-y)*log(1 - q)), c("q"))
y <- sample(c(0, 1), 5, replace=TRUE)
q <- runif(5)

(1 - y)/(1 - q) - y / q  # Gradient by hands.
[1]  3.092679 -1.506702  1.476308 -1.771791  1.126489
(1 - y)/(1-q)^2 + y / q^2  # Hessian by hands.
[1] 9.564663 2.270150 2.179485 3.139244 1.268978
eval(dq2q)  # By symbolic derivatives.
[1] 1.1290377 0.4099229 0.3895444 0.5719910 0.1191058
attr(,"gradient")
             q
[1,]  3.092679
[2,] -1.506702
[3,]  1.476308
[4,] -1.771791
[5,]  1.126489
attr(,"hessian")
, , q

            q
[1,] 9.564663
[2,] 2.270150
[3,] 2.179485
[4,] 3.139244
[5,] 1.268978

The above derivation assumes \(\hat{y}\) is the sigmoid model output (probability). While actually in xgboost’s implementation for logistic regression the term \(\hat{y}\) is before sigmoid transformation (what the auther refers to as margin). So the model weights are raw scores (logits) instead of probabilities. This makes sense since the model is additive. It is better to do the add operation before the final sigmoid transformation of the ensembler. That is, each tree predicts raw scores and the final model ensemble is the sigmoid of the sum of all the raw scores (from the assigned leaves). This also largely simplifies the derivatives because now what we will be doing is:

\[ g_i = \frac{\partial L}{\partial t}\frac{\partial t}{\partial\hat{y}}, \]

where \(t\) is the sigmoid \(t(\hat{y}) = \frac{1}{1 + \exp(-\hat{y})}\).

Based on the fact that \(\frac{\partial t}{\partial \hat{y}} = t(1 - t)\), we finally have (simplying \(\hat{y}_i^{(k-1)}\) as \(\hat{y}\) to save typings):

\[ \begin{aligned} g_i &= \frac{t - y_i}{t(1 - t)} \times t(1-t) \\ &= t(\hat{y}) - y_i, \\ h_i &= t(\hat{y})(1 - t(\hat{y})). \end{aligned} \]

4.4.2 Exercise: Implement a Single XGB Split

To verify our understanding, as an exercise we built a single tree with just one split (a decision stump). And we comapre the result of our implementation to that of xgboost to see if they agree with each other.

lambd <- 1  # L2 regularization.
all_features <- c("x1", "x2")

cost <- function(df) {
  # The XGB optimal cost based on 2nd-order Taylor expansion.
  - sum(df$g)^2 / (2*(sum(df$h) + lambd))
}

cost_on_split <- function(D, split_ind) {
  # Split the data into left and right child nodes.
  DL <- D[1:split_ind, .(g, h)]
  DR <- D[(split_ind + 1):nrow(D), .(g, h)]
  cost(DL) + cost(DR)
}

feature_iteration <- function(D, all_features) {
  feature_best_costs <- list()
  best_split_ind <- list()
  best_split_val <- list()
  for ( x in all_features ) {
    # Sort examples by x.
    setorderv(D, x)

    # Feature value iteration.
    split_cost <- rep(NA_real_, nrow(D) - 1)
    for ( i in seq_len(nrow(D) - 1) ) {
      split_cost[i] <- cost_on_split(D, i)
    }
    feature_best_costs[[x]] <- min(split_cost)
    best_split_ind[[x]] <- which.min(split_cost)
    best_split_val[[x]] <- D[[x]][best_split_ind[[x]]]
  }

  # Determine which feature gains the largest reduction in cost.
  split_col <- names(which.min(feature_best_costs))
  list(split_col=split_col,
       split_ind=best_split_ind[[split_col]],
       split_val=best_split_val[[split_col]])
}


y <- D$y[!is_test]
dy2y <- deriv3(~ (y - yhat)^2, c("yhat"))
yhat <- .5  # This is the initial prediction used by xgboost.
dy <- eval(dy2y)
D <- D[!is_test, g:=attr(dy, "gradient")[,"yhat"]]
D <- D[!is_test, h:=attr(dy, "hessian")[,"yhat","yhat"]]

# Feature iteration on the root split.
r <- feature_iteration(D[!is_test], all_features)
print(r)
$split_col
[1] "x1"

$split_ind
[1] 394

$split_val
[1] 1.821115
# Verify the result with a real xgboost implementation.
bst3 <- xgb.train(params=list(objective="reg:squarederror", max_depth=1),
                  data=Dx, nround=1)
xgb.model.dt.tree(model=bst3)

So the optimal split at root node from our toy implementation agrees with that of xgboost. Note that the split value reported by xgboost does not correspond to an actual value in the feature. It is a virtual value just for splitting purpose. Indeed it is the average value between the closest two values at the split. To check this:

D2 <- D[!is_test]
setorder(D2, x1)
print(mean(D2[r$split_ind:(r$split_ind + 1), x1]))
[1] 1.826493

4.4.3 Parallelization

Unlike bagging trees which are embarassingly parallel, boosting trees are trained iteratively, which makes them harder to parallelize. We can, however, still parallelize the training process, not at tree-level but at feature-level.

Since the algorithm needs to traverse over all (or a subset of) features to find the best split, feature iteration is readily parallelable. All modern GBT libraries support this kind of parallelization.

4.4.4 Comparing to Vanilla GBT

In vanilla gradient boosting at each iteration we fit a tree to the pseudo residuals, i.e., negative gradient of loss function w.r.t. model output. The tree (or any other base learner) is learning from the pseudo residuals with its own loss. Such loss is local and may not reflect the global objective we want to optimize. XGBoost incorporates that global objective (with regularization) into each iteration, making the base learner more relevant to our end goal.

The second-order approximation of global objective further reduces the computing cost by converting the loss computation into a group-by-leaf sum operation, with all gradients pre-computed at the beginning of each iteration.

Notice that in a vanilla GBT we need to calculate the gradients to get the pseudo-residuals, and the base learners need to calculate the gradients of their own objective function (or calculate the impurity measure) in order to do optimization. XGBoost hence is much faster than vanilla GBT in training.

4.5 Approximated Tree Split

The exact split finding algorithm may not be scalable in large applications. So instead xgboost supports an approximated split algorithm described in the following pseudo code:2

[Approximated Tree Split Algorithm]

m: total number of features
s: score to measure the quality of a split
eps: percentile increment, dividing feature roughly into 1/eps buckets
x_pj: the p-th example percentile value on feature j

for feature j in 1 to m:
  divided feature j into buckets based on eps
  aggregate s for each bucket

for feature j in 1 to m:
  for all value x_pj on feature j:
    compute s based on bucketed aggregation

do split using j* at x_pj* associated with maximum s

The idea is to split only at percentile value, introducing one additional parameter called sketch_eps (default at 0.03 in xgboost) to control the granularity of the buckets. So the number of search for each continous variable will be capped roughly at 1/sketch_eps instead of the number of distinct values, which can be huge.

By default in xgboost the bucketization is done only once at the model initialization phase in each iteration. All subsequent splits are performed on the same set of buckets for each feature. It is also possible to re-propose new buckets after each optimal split, as pointed out in the original paper, but such control is not yet open to end user in xgboost interface as of 2019-12-27.

More specifically, in xgboost the parameter tree_method controls the type of splitting algorithm:

  • tree_method="exact": The exact splitting algorithm by default when dataset is small
  • tree_method="approx": The approximated splitting algorithm
  • tree_method="hist": The histogram tree approach (refer to LightGBM section for details)

4.6 Sparsity Awareness: Split on Missing

One novelty in xgboost is its capability of dealing with missing values. It solves the missing value problem by learning a default branch (either the left or the right child node) for each node in a split. During training, in each node the missing value can go to either left or right branch. Quality score for both branches are now computed based on the assumption that missing values should go to the other branch. In the end the split algorithm returns not only optimal split but also optimal default branch for missing value at each node.

xgboost is indeed not the first tree model to handle missing values in its own way. For example, rpart also handles missing value in a particular manner. In rpart, each split will come with additional surrogate variables to handle cases where the split feature value is missing at inference time. A surrogate variable is a feature other than the best split feature but used to classify the resulting child nodes passing a minimum quality criterion. For more details readers may refer to Therneau and Atkinson (2019).

Comparing to rpart’s surrogate variable approach, which increases computing cost, xgboost’s approach is more computationally efficient and seems to also result in better model accuracy.

4.7 Random Forest via XGBoost

xgboost is a general framework for model (especially tree) ensemblers. Since there is no rule stopping us from building more than one tree at each iteration, we can effectively grow multiple trees with only one round to train a random forest with xgboost. Even more, we can train a boosting random forest by growing multiple trees and with multiple iterations. That is, we use RF as our base learner in a gradient boosting machine.

5 LightGBM

Ke et al. (2017) open source the lightgbm package, yet another powerful GBT framework with its own remarkable novel implementation.3

library(lightgbm)
packageVersion("lightgbm")
[1] '2.3.2'

Here is a simple training task using lightgbm on our simulated data:

Dx2 <- lgb.Dataset(as.matrix(D[!is_test, .(x1, x2)]), label=D[!is_test, y])
Dt2 <- lgb.Dataset(as.matrix(D[is_test, .(x1, x2)]), label=D[is_test, y])

# We set the parameters such that lgb behaves like xgb's default for common parameters.
lgbst <- lgb.train(params=list(objective="regression", boosting="gbdt", lambda_l2=1,
                               min_sum_hessian_in_leaf=1, min_data_in_leaf=0),
                   data=Dx2, valids=list(test=Dt2),
                   num_iterations=100, early_stopping_rounds=5, verbose=0)

yhat <- predict(lgbst, as.matrix(D[is_test, .(x1, x2)]))
mse(D[is_test, y], yhat)
[1] 1.360733

In general lightgbm runs even faster than xgboost in training, but with comparable model accuracy. This is achieved by a combination of different strategies (some of them novel). We will walk-through each of them in the following sections.

5.1 Histogram Tree (Quantization)

lightgbm uses histogram-based tree split algorithm.4

Instead of splitting at raw feature values histogram-based approach bucketizes continous features into discrete bins, forming a limited number of feature bins (in lightgbm this is default max_bin=255) and then split on those bins. This largely reduces the complexity from \(O(n)\) to \(O(\text{max_bin})\) during split finding for a feature.5

5.1.1 Greedy Equal-Density Binning

There are two strategies in binning: equal bin length (Li, Wu, and Burges 2008) or equal bin density. lightgbm adopts the latter. So number of data points will be roughly the same for most bins. This effectively transforms feature distribution into a uniform one when finding the best split.

lightgbm doesn’t use the entire training dataset to construct the boundaries of bins. Instead it use a random subset (only applicable when data size is huge enough). This random sample size can be controlled by parameter bin_construct_sample_cnt=200000. Additionally, min_data_in_bin=3 can be used to control minimal number of data inside one bin.

Note that this approach is different from the percentile splitting we’ve discussed for approximated tree split implemented in xgboost:

The existing approximate splitting method in xgboost also buckets continuous features into discrete bins to speed up training. The approx method generates a new set of bins for each iteration, whereas the hist method re-uses the bins over multiple iterations.6

5.1.2 Histogram Subtraction Trick

To calulate the information gain from a split we need to calculate the cost for the resulting two nodes. Remember that a cost of a single node \(j\) is:

\[ \text{Cost of Node j} = - \frac{1}{2} \frac{\big(\sum_{i \in I_j} g_i\big)^2}{\sum_{i \in I_j}h_i + \lambda}. \]

A key observation here is that what we need is simply summation of gradients inside the node. Since an example is assigned to either left (\(I_j^L\)) or right (\(I_j^R\)) child after a split, apparently we have:

\[ \begin{aligned} \sum_{i \in I_j} g_i &= \sum_{i \in I_j^L} g_i + \sum_{i \in I_j^R} g_i, \\ \sum_{i \in I_j} h_i &= \sum_{i \in I_j^L} h_i + \sum_{i \in I_j^R} h_i. \end{aligned} \]

This means that when calculating the total gain of a split, we only need to calculate the cost of one node. And the cost of the other node can be obtained by first substracting the gradient sums from that of the parent, then squaring the first-order gradient sum before pluging into the cost function to arrive at the final cost of the other node. To save even more resources, we can always choose to calculate on the smaller child node. This is one key factor why lightgbm can run even faster than xgboost’s default or the approximated tree method.

5.1.3 Speed-Accuracy Tradeoff

In the original paper it is demonstrated with experiments that a total number of 255 bins (plus one bin specifically for zeroes) can perform very well. We can always choose more bins for potentially better accuracy, but at the price of slowering down the training speed.

We can use a single decision stump to easily see the effect of max_bin. First let’s run with the default setting:

lgbst2 <- lgb.train(params=list(objective="regression", boosting="gbdt", lambda_l2=1,
                                num_leaves=2),
                    data=Dx2, num_iterations=1, verbose=0)

lgb.model.dt.tree(model=lgbst2)[]

The optimal root split coincide with xgboost and also our toy implementation. Note that we have far more than max_bin=255 distinct values in our splitting variable:

distinct_cnt <- uniqueN(D[!is_test, x1])
print(distinct_cnt)
[1] 830

Instead of performing 829 splits LightGBM only performs 255 splits to locate the same optimal split.

Now let’s reduce the maximal number of bins to a rather small number:

# We need to re-construct the dataset since lgb caches the bins in dataset.
Dx2 <- lgb.Dataset(as.matrix(D[!is_test, .(x1, x2)]), label=D[!is_test, y])
lgbst3 <- lgb.train(params=list(objective="regression", boosting="gbdt", lambda_l2=1,
                                num_leaves=2, max_bin=10),
                    data=Dx2, num_iterations=1, verbose=0)

lgb.model.dt.tree(model=lgbst3)[]

By lowering down max_bin now we end up with a sub-optimal split, with smaller gain for the split.

5.1.4 Handling Sparsity

Histogram tree cannot be fully optimized for sparse data though. Since no matter a feature value is zero or not, its bin value needs to be accessed. As a result, to speed up training runtime there must be other strategies targeted at sparsity optimization.

5.2 Gradient-Based One-Side Sampling

To reduce the training time one idea is to reduce number of splits in finding the best split. Without any approximation or quantization, for each feature the algorithm needs to traverse over all distinct feature values to find the best split. This can be prohibitively slow for large applications. xgboost tackles this with its approximated tree split using only percentiles as split value. lightgbm has several different strategies combined together. The histogram tree method we just discussed is one of them. Another such strategy is called Gradient-Based One-Side Sampling (GOSS).

When finding the best split, GOSS down-samples the examples with their absolute gradient sizes as sampling weights. It is based on the fact that examples with smaller gradients generally means they are already well-trained (and hence the error is smaller). Examples with larger gradients, instead, should be the focus for the model to imporve further. The concept is borrowed from AdaBoost, where examples are re-weighted at each boosting round to guide the model on learning the harder examples.

GOSS keeps examples with large gradients and only do sampling on examples with small gradients. Since this will screw up the distribution, which in turn hurts the model’s ability to learn, GOSS compensates such effect by applying a constant factor \(\frac{1 - a}{b}\) to sampled examples with small gradient to “re-weight” the data back to its original distribution. Here \(a\) is the top percentage of examples to keep, sorted descendingly by gradients, and \(b\) the sampling rate for the rest of the examples (with smaller gradients than those top ones). The authors in their paper provide the theoretical ground to support that GOSS can approximate well when the data size is huge.

To use GOSS in lightgbm we need to specifiy the parameter boosting="goss". By default booster="gbdt" is just the modern GBT with leaf-wise growing histogram trees.

5.3 Exclusive Feature Bundling

GOSS deals with data in long size. What about very wide (high dimentionality) data? In such case, lightgbm adopts a strategy to group together features that rarely co-exist into single feature and do the split scan. This is referred to as Exclusive Feature Bundling, or EFB.

lightgbm postulates EFB as a graph coloring problem by taking features as vertices and adding edges for every two features if they are not mutually exclusive. To speed up even more, it introduces a certain level of “pollution” by allowing features that are not 100% mutually exclusive to be also bundled together. This can be controlled by parameter max_conflict_rate=0. The higher the rate the more compromises for the model accuracy.

EFB can be enabled by parameter enable_bundle (and by default it is on already).

5.4 Leaf-Wise Tree Split

Leaf-wise tree split is also called best-first split (Shi 2007). Classical trees will grow their nodes in a depth-first order. At each layer all nodes are splitted in order to construct the next layer. The final tree layout can be asymmetric due to post-training pruning or other regularization techniques, but the grow policy is to always go deeper first for each node at the same level.

A leaf-wise growing tree doesn’t always split every node at the same layer down to the next layer. Instead, it chooses the node that can result in the best gain for every split. Since one of the resulting two additional nodes at current split may result in the next best split, the tree may well grow horizontally (leaf-wise) instead of vertically (depth-wise).

Emprically, a single leaf-wise growing tree tend to over-fit more than a depth-wise growing tree. But as a week learner (with shallower structure) in gradient boosting it can outperform the depth-wise approach. To further regularize the tree, lightgbm also has max_depth to control the depth of the tree, even though it is growing leaf-wise.

5.5 Optimal Categorical Encoding

Usually we use one-hot encoding to represent categorical variables. To be memory-friendly, such data input is usually a sparse matrix. But when a categorical is highly skewed (high cardinality), it will hurt the performance of a tree learner.

Here is a direct quote from lightgbm ’s official doc:

Instead of one-hot encoding, the optimal solution is to split on a categorical feature by partitioning its categories into 2 subsets. If the feature has k categories, there are 2^(k-1) - 1 possible partitions. But there is an efficient solution for regression trees. It needs about O(k * log(k)) to find the optimal partition. The basic idea is to sort the categories according to the training objective at each split. More specifically, LightGBM sorts the histogram (for a categorical feature) according to its accumulated values (sum_gradient / sum_hessian) and then finds the best split on the sorted histogram.

Readers interested can refer to Fisher (1958) for the details of the solution to optimal partition in a categorical variable.

5.6 Parallel Learning

We’ve mentioned that GBT is a iterative training algorithm so theoretically the parallelization must be done at a level other than the base learner. In lightgbm parallelization is seriously implemented into different such levels, with support of both multi-threading or multi-machine runtime.

(By default tree_learner=serial so there is no parallelization at learning.)

5.6.1 Feature Parallel

tree_learner=feature

This is the most straightforward parallelization we can think of. Data is partitioned vertically so each thread will only inspect a subset of features. Best split is found simultaneously among those features to get the local best splits, then a comminucation overhead is required to arrive at the global best. The speedup is limited if our data is not wide but instead very long.

5.6.2 Data Parallel

tree_learner=data

Instead of vertically parallelization, lightgbm also supports horizontal parallelization. Local gradient histograms will be constructed for each partition and then merged to a global histogram for finding the best split.

5.6.3 Voting Parallel

tree_learner=voting

Meng et al. (2016) propose the idea of parallel voting tree. The general idea can be summarized into the following flow:

  1. Partition data according to multiple threads/machines
  2. In each partition a local voting is done to propose top-k features for the best split
  3. Top-2k features are collectd based on all local votes
  4. Full data for the top-2k is collected and a global best split is searched within them

Such parallel voting strategy has limited communication overhead but can approximate very good in accuracy. In lightgbm, by default when doing parallel voting we have top_k=20.

In general, since lightgbm is already lightning fast, only very huge data will we need to resort to parallel voting.

6 XGBoost v.s. LightGBM

xgboost and lightgbm have a lot in common, even their APIs (in both Python and R) are highly similar to each other. They use the same global loss function with 2nd-order approximation and regularization to optimize the model ensembler. They allow plug-and-play custom losses and evaluation metrics. They handle missing values in the same way by learning a default branch at each split, …

Still, there are subtle differences we should be aware of. In this section we discuss several important differences (out of commonality) in the two.

6.1 On Tree Type

As a matter of fact, since histogram-based approach together with leaf-wise growth is so powerful, after lightgbm’s entering into the commuity, xgboost also quickly catched up with the same implementation. So in xgboost we can set the following parameter

tree_method="hist"
grow_policy="lossguide"

to train a XGBoost model with leaf-wise growing histogram-based trees, just like LightGBM.

6.2 On Base Learner

lightgbm only supports tree learner (using the boosting parameter) while xgboost also supports linear learners (using the booster parameter).

In xgboost two linear learners are supported: A simple linear regressor and a generalized linear model with tweedie distribution.

For tree learner other than the regression tree they both supports two additional tree models:

  1. Random Forest
  2. DART (Rashmi and Gilad-Bachrach 2015)

One notable difference in RF is that while xgboost allows training a boosted RF, lightgbm only supports training a regular RF (there is no boosting involved).

6.3 On Hyperparameters

There are tons of hyperparameters in modern GBT models we discussed in this notebook. Here we only walk-through those important and non-trivial ones.

6.3.1 Regularization Parameters

6.3.1.1 L1/L2 Penalties

Both packages implement the same penalties on cost function. Only the names and default values are different. In lightgbm L1/L2 penalties are defined by:

lambda_l1=0
lambda_l2=0

And in xgboost they are:

alpha=0
lambda=1

6.3.1.2 Leaf Size

One way to regularize a tree model is to limit the size of its terminal node (leaf). In lightgbm this can be done by either limiting the minimum number of examples or limiting the minimum sum of hessian (\(\sum_{i \in I_j}h_i\) for node \(j\)). Here are the default values:

min_data_in_leaf=20
min_sum_hessian_in_leaf=1e-3

In lightgbm an improper value for this parameter usually results in the following warning message during training runtime:

[LightGBM] [Warning] No further splits with positive gain, best gain: -inf

In such case, try to lower down the value since the algorithm is complaining about not able to split further due to reaching the minimum limits of node size, given the targeted number of leaves. Of course another cure is to reduce tree complexity by decreasing num_leaves.

In xgboost to control leaf size we can only limit on hessian but not the exact example counts:

min_child_weight=1

Remember that in the case of a squared loss the hessian is indeed unity (after normalization), so the hessian sum will be the same as example counts.

Notice the difference in the default values between lightgbm and xgboost. The former has less tolerance on over-fitting (more regularized).

6.3.1.3 Tree Size

A larger tree has more leaves and hence more complicated. More leaves also means more interaction among variables, which in turn controls the degree of non-linearity in the model.7

Since lightgbm grows a tree leaf-wise, there are two parameters to control tree size:

max_depth=-1
num_leaves=31

num_leaves is the main factor, and max_depth supplements it.

In xgboost one can choose to grow a tree either depth-wise (the default) or leaf-wise. For depth-wise tree there is only one relevant parameter:

max_depth=6

For leaf-wise growing tree to control number of leaves:

max_leaves=0

For a leaf-wise tree to grow as many leaves as a depth-wise tree, the former will usually get deeper than the latter. This is why to simultaneously control the number of leaves and tree depth is a good strategy. Be aware that by default lightgbm doesn’t limit tree depth at all.

6.3.1.4 Sub-Sampling

Both libraries support column sampling and row sampling. In lightgbm we have:

bagging_freq=0
bagging_fraction=1
feature_fraction=1
feature_fraction_bynode=1

Note that bagging_freq controls how many iterations to re-do the bagging. So bagging_freq=1 will do row sampling at the start of every iteration. We can even subsample only positive or negative examples (for binary classification), by using pos_bagging_fraction or neg_bagging_fraction. This can be handy when dealing with imbalanced dataset.

For feature sampling, feature_fraction controls the sampling at the beginning of each iteration, while feature_fraction_bynode controls the sampling at each tree node. The latter is in general more regularized since the model training is subject to stronger randomness.

In xgboost we have the following relevant parameters:

subsample=1
colsample_bytree=1
colsample_bylevel=1
colsample_bynode=1

It has less control over bagging, only one parameter subsample is used and the bagging is done at the beginning of each iteration. While it has more control over column sampling. Besides column sampling at iteration or node-level, it also supports column sampling at tree-depth-level.

Note that in both libraries the bagging is done without replacement. So none of them implements bootstrap bagging, which is proposed by the original random forest paper.

6.4 On Imbalanced Data

6.4.1 Class Re-Weighting

Simply based on hyperparameters there are two ways to handle imbalanced dataset in a binary classification task. The first approach is to re-weight the examples. Both xgboost and lightgbm has the parameter for this purpose:

scale_pos_weight=1

To re-balance the distribution to 50-50 the weight should be:

number of negative samples / number of positive samples

This will help the model in its general ranking performance (so metrics such as AUC will improve a lot). For lightgbm there is also another convenient boolean parameter just to do this: is_unbalance.

How does the weighting factor affect our model training? It is directly through the gradients. The scale factor will simply be used as a multiplier on both gradient \(g_i\) and hessian \(h_i\) for that example, provided that the example \(i\) is a positive example.

In xgboost’ source code it is:

_out_gpair[_idx] = GradientPair(Loss::FirstOrderGradient(p, label) * w,
                                Loss::SecondOrderGradient(p, label) * w);

where w is the scale factor (after considering also user-supplied custom weights for each example).

And in lightgbm’s source code it is:

const double response = -label * sigmoid_ / (1.0f + std::exp(label * sigmoid_ * score[i]));
const double abs_response = fabs(response);
gradients[i] = static_cast<score_t>(response * label_weight  * weights_[i]);
hessians[i] = static_cast<score_t>(abs_response * (sigmoid_ - abs_response) * label_weight * weights_[i]);

where label is 1 for positive and -1 for negative, label_weight is the scale factor, sigmoid_ is the scale factor for sigmoid function (default at 1), and weights_ is user-supplied custom weights.

6.4.2 Margin Clipping

But since any non-unity weight also screws up the distribution of our training data, the model will not be able to estimate the class probability correctly. In case that estimating the correct probability is relevant, we have another parameter that may help, which is also shared by the two libraries:

max_delta_step=0

Here is a quote from xgboost’s official document on this parameter:

Usually this parameter is not needed, but it might help in logistic regression when class is extremely imbalanced. Set it to value of 1-10 might help control the update.

Technically, max_delta_step cap the leaf weight (base model logit output or margin) learnt from each iteration. Not that this is different from learning rate which only applies a fraction of the update. max_delta_step effectively limits the absolute update amount. And learning rate will still apply to the capped update.

This is a bit similar to gradient clipping in gradient descent, where we cap the gradient update to a hard limit to avoid aggressive update.

6.5 On Categorical Encoding

One important user-facing difference is lightgbm’s special support for categorical features without one-hot-encoding. This is probably one of the most handy function in modern machine learning libraries. It not only improves the model accuracy but also simplifies data pipeline and hence reduces engineering efforts.

For xgboost categoricals must be either one-hot encoded or converted to integers with an ordinal assumption. Empirically, even though the feature is not ordinal, sometimes converting it into integers still work pretty well in terms of accuracy, adding that the training runtime can be reduced a lot and data pipeline simplified.

To demonstrate the performance gain of the optimal categorical encoding in LightGBM, let’s simulate some data. Here we use the same data generating process previously setup, but with one more categorical variable that enter linearly into the model.

# Create simulated data for categorical experiment.
library(Matrix)

n <- 10000
z1 <- rnorm(n, 2, 4)
z2 <- runif(n)
e <- rnorm(n)

# Create one categorical with moderate cardinality and skewed distribution.
categorical <- sample(1:20, size=n, replace=TRUE, prob=1.3^(seq(2, 21)))
table(categorical)
categorical
   1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16 
  16   21   30   34   53   59   62  105  124  180  198  294  346  509  602  788 
  17   18   19   20 
1017 1445 1837 2280 
sparse_cat <- t(fac2sparse(factor(categorical)))  # One-hot encode.
stopifnot(all.equal(as.numeric(table(categorical)), 
                    colSums(sparse_cat), check.attributes=FALSE))

# Random effects on categories.
cat_beta <- rnorm(ncol(sparse_cat))

# DGP: g = 3 z_1 + 6 z_2 -1.5 z_1z_2 + categorical dummy effects + e
g <- 3 * z1 + 6 * z2 - 1.5 * z1 * z2 + sparse_cat %*% cat_beta + e
g <- g[,1]  # Strip redundant dim.

# Prepare model design matrix.
in_mat <- cbind(z1, z2, sparse_cat)
head(in_mat)  # Training data in sparse matrix format.
6 x 22 sparse Matrix of class "dgCMatrix"
   [[ suppressing 22 column names 'z1', 'z2', '1' ... ]]
                                                                   
[1,] -1.6390872 0.006529306 . . . . . . . . . . . . 1 . . . . . . .
[2,] -4.3118135 0.694535395 . . . . . . . . . . . . . . . . . 1 . .
[3,]  1.5151302 0.992225272 . . . . . . . . . . . 1 . . . . . . . .
[4,]  3.5821921 0.705857143 . . . . . . . . . . . . . . . . . . 1 .
[5,] -0.5556953 0.787081922 . . . . . . . . . . . . . . . . . 1 . .
[6,]  1.0538218 0.330773519 . . . . . . . . . . . . . . . . . 1 . .
in_dt <- data.table(z1=z1, z2=z2, cat=categorical)
head(in_dt)  # Training data in tabular format.
# Train-test split.
is_test_g <- runif(n) < .2

Now train a XGBoost model with one-hot-encoded sparse matrix as input. To be fair, we setup the parameters such that xgboost will also grow a leaf-wise histogram tree as its base learner.

run_xgb <- function(v=1) {
  xgb_dtrain <- xgb.DMatrix(in_mat[!is_test_g,], label=g[!is_test_g])
  xgb_dtest <- xgb.DMatrix(in_mat[is_test_g,], label=g[is_test_g])

  # We set the parameters such that xgb use lgb-style leaf-wise histogram tree.
  xgb.train(params=list(objective="reg:squarederror", booster="gbtree",
                        eta=.3, max_leaves=7, max_depth=3, nthreads=1,
                        tree_method="hist", grow_policy="lossguide"),
            data=xgb_dtrain, watchlist=list(test=xgb_dtest),
            nround=50, early_stopping_rounds=5, verbose=v, print_every_n=10)
}

invisible(run_xgb())
[1] test-rmse:8.549810 
Will train until test_rmse hasn't improved in 5 rounds.

[11]    test-rmse:1.565751 
[21]    test-rmse:1.313399 
[31]    test-rmse:1.241104 
[41]    test-rmse:1.219501 
[50]    test-rmse:1.196451 

Now it’s LightGBM’s turn. We directly feed a data matrix with only 3 columns, one of it being categorical but represented in integer, without the ordinal assumption by specifying explicitly in categorical_feature argument to the model training call.

run_lgb <- function(v=1) {
  lgb_dtrain <- lgb.Dataset(as.matrix(in_dt[!is_test_g]), label=g[!is_test_g],
                            colnames=names(in_dt), categorical_feature="cat")
  lgb_dtest <- lgb.Dataset(as.matrix(in_dt[is_test_g]), label=g[is_test_g],
                           colnames=names(in_dt), categorical_feature="cat")

  # We set the parameters such that lgb behaves like xgb's default for common parameters.
  lgb.train(params=list(objective="regression", boosting="gbdt",
                        lambda_l2=1, learning_rate=.3, metric="rmse",
                        num_leaves=7, max_depth=3, num_threads=1,
                        min_sum_hessian_in_leaf=1, min_data_in_leaf=0),
            data=lgb_dtrain, valids=list(test=lgb_dtest),
            num_iterations=50, early_stopping_rounds=5, verbose=v, eval_freq=10)
}

invisible(run_lgb())
[1]:    test's rmse:6.84807 
[11]:   test's rmse:1.43646 
[21]:   test's rmse:1.21961 
[31]:   test's rmse:1.17573 
[41]:   test's rmse:1.14199 
[50]:   test's rmse:1.12623 

In our specific parameter setup LightGBM outperforms XGBoost. But it is minor since we didn’t tune any of the models seriously. There are already lots of benchmarks on the internet with different experiments and dataset, the results are in general a mixture. We consider two models comparable in terms of accuracy. Here we only care about the performance gap resulted from a different treatment of categorical variables.

Now we benchmark the model training runtime:

library(microbenchmark)

print(microbenchmark(
  xgb=run_xgb(v=0),
  lgb=run_lgb(v=0),
  times=10
))
Unit: milliseconds
 expr      min       lq      mean   median       uq      max neval
  xgb 788.7158 790.1674 796.14318 792.5025 800.2261 817.4937    10
  lgb  26.1314  28.3054  29.80156  28.9952  29.4064  38.3660    10

Clearly LightGBM is much faster than XGBoost with its optimal categorical encoding scheme. Not to mention it also saves our ass from preparing a one-hot encoder for our data.

7 Concluding Remarks

Modern gradient boosting trees are powerful machine learning models for lots of real world applications. In this notebook we review thoroughly two of such frameworks in the open source community: xgboost and lightgbm. We choose them not only because they are both implemented with very high engineering quality, but also because they are both extremely popular and wdiely adopted among data science practitioners.

For such a tool that is so widely spread, we feel there is a need to demystify why it is so effective by understanding how they are different from traditional or vanilla gradient boosting machines. We focus more on the technical details and their novelties in bettering the GBT family. We hope the notebook can help practitioners better understand their tool at hand, so they can utilize it with its maximum potential.

8 References

Chen, Tianqi, and Carlos Guestrin. 2016. “XGBoost: A Scalable Tree Boosting System.” In Proceedings of the 22nd Acm Sigkdd International Conference on Knowledge Discovery and Data Mining, 785–94. KDD ’16. New York, NY, USA: ACM. https://doi.org/10.1145/2939672.2939785.

Fisher, Walter D. 1958. “On Grouping for Maximum Homogeneity.” Journal of the American Statistical Association 53 (284): 789–98.

Friedman, Jerome H. 2001. “Greedy Function Approximation: A Gradient Boosting Machine.” Annals of Statistics, 1189–1232.

Ke, Guolin, Qi Meng, Thomas Finley, Taifeng Wang, Wei Chen, Weidong Ma, Qiwei Ye, and Tie-Yan Liu. 2017. “Lightgbm: A Highly Efficient Gradient Boosting Decision Tree.” In Advances in Neural Information Processing Systems, 3146–54.

Li, Ping, Qiang Wu, and Christopher J Burges. 2008. “Mcrank: Learning to Rank Using Multiple Classification and Gradient Boosting.” In Advances in Neural Information Processing Systems, 897–904.

Meng, Qi, Guolin Ke, Taifeng Wang, Wei Chen, Qiwei Ye, Zhi-Ming Ma, and Tie-Yan Liu. 2016. “A Communication-Efficient Parallel Algorithm for Decision Tree.” In Advances in Neural Information Processing Systems 29, edited by D. D. Lee, M. Sugiyama, U. V. Luxburg, I. Guyon, and R. Garnett, 1279–87. Curran Associates, Inc. http://papers.nips.cc/paper/6381-a-communication-efficient-parallel-algorithm-for-decision-tree.pdf.

Rashmi, Korlakai Vinayak, and Ran Gilad-Bachrach. 2015. “DART: Dropouts Meet Multiple Additive Regression Trees.” In.

Shi, Haijian. 2007. “Best-First Decision Tree Learning.” PhD thesis, The University of Waikato.

Therneau, Terry, and Beth Atkinson. 2019. Rpart: Recursive Partitioning and Regression Trees. https://CRAN.R-project.org/package=rpart.


  1. Quoted from the creator of xgboost: https://www.quora.com/What-is-the-difference-between-the-R-gbm-gradient-boosting-machine-and-xgboost-extreme-gradient-boosting↩︎

  2. Approximated split search is not particularly novel in XGBoost. It has been well studied in other academic works.↩︎

  3. As of 2019-12-27 lightgbm is not yet available on CRAN for its R wrapper. Please refer to the official instruction to install it separately.↩︎

  4. Histogram tree is not particularly novel in lightgbm. The approach has been proposed for quite long time in the literature and there are also GBT libraries already implemented the approach.↩︎

  5. The complexity of building the histogram itself is still \(O(n)\) but the operation is simple and negligible comparing to the split finding.↩︎

  6. https://github.com/dmlc/xgboost/issues/1950↩︎

  7. When a subsequent split is conditioning on a previous split on another variable, we consider this as variable interaction.↩︎

LS0tCnRpdGxlOiAiRGVteXN0aWZ5IE1vZGVybiBHcmFkaWVudCBCb29zdGluZyBUcmVlcyIKc3VidGl0bGU6ICJGcm9tIFRoZW9yeSB0byBIYW5kcy1PbiBFeGFtcGxlcyIKYXV0aG9yOgotIG5hbWU6IEt5bGUgQ2h1bmcKICBhZmZpbGlhdGlvbjoKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJWIgJVknKWAgTGFzdCBVcGRhdGVkICgyNyBEZWMgMjAxOSBGaXJzdCBVcGxvYWRlZCkiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBwYXBlcgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGluY2x1ZGVzOgogICAgICBpbl9oZWFkZXI6IC90bXAvbWV0YV9oZWFkZXIuaHRtbAogIGNvZGVfZG93bmxvYWQ6IHRydWUKYmlibGlvZ3JhcGh5OiBnYnQuYmliCmxpbmstY2l0YXRpb25zOiB5ZXMKYWJzdHJhY3Q6IHwKICBNb2Rlcm4gZ3JhZGllbnQgYm9vc3RpbmcgdHJlZXMgKEdCVCkgaXMgdW5kb3VidGVkbHkgb25lIG9mIHRoZSBtb3N0IHBvd2VyZnVsIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBmb3IgdHJhZGl0aW9uYWwgc3VwZXJ2aXNlZCBsZWFybmluZyB0YXNrcyBpbiB0aGUgcmVjZW50IGRlY2FkZS4gSW4gdGhpcyBub3RlYm9vayB3ZSB0cnkgdG8gdW5ib3ggdHdvIHN1Y2ggcG93ZXJmdWwgR0JUIGZyYW1ld29ya3M6IGB4Z2Jvb3N0YCBhbmQgYGxpZ2h0Z2JtYC4gV2Ugd2lsbCBmb2N1cyBtb3JlIG9uIHRoZSBtZXRob2RvbG9neSByYXRoZXIgdGhhbiB0aGVpciBBUElzIHRvIGRlZXBseSB1bmRlcnN0YW5kIGhvdyB0aGVzZSBhbGdvcml0aG1zIHdvcmsgYW5kIHdoeSB0aGV5IGFyZSBzbyBlZmZlY3RpdmUgY29tcGFyaW5nIHRvIHRoZSB2YW5pbGxhIHZlcnNpb24gb2YgR0JULCB3aGljaCBoYXMgYmVlbiBmb3JtYWxseSBpbnRyb2R1Y2VkIG5lYXJseSAyMCB5ZWFycyBhZ28uCi0tLQoKYGBge3IgbWV0YSwgaW5jbHVkZT1GQUxTRX0KbWV0YV9oZWFkZXJfZmlsZSA8LSBmaWxlKCIvdG1wL21ldGFfaGVhZGVyLmh0bWwiKQoKIyBBZGQgb3BlbiBncmFwaCBtZXRhLgptZXRhIDwtIGMoCiAgJzxtZXRhIG5hbWU9ImF1dGhvciIgY29udGVudD0iS3lsZSBDaHVuZyI+JywKICAnPG1ldGEgcHJvcGVydHk9Im9nOnRpdGxlIiBjb250ZW50PSJEZW15c3RpZnkgTW9kZXJuIEdyYWRpZW50IEJvb3N0aW5nIFRyZWVzOiBGcm9tIFRoZW9yeSB0byBIYW5kcy1PbiBFeGFtcGxlcyI+JywKICAnPG1ldGEgcHJvcGVydHk9Im9nOnR5cGUiIGNvbnRlbnQ9ImFydGljbGUiPicsCiAgJzxtZXRhIHByb3BlcnR5PSJvZzp1cmwiIGNvbnRlbnQ9Imh0dHBzOi8vZXZlcmRhcmsuZ2l0aHViLmlvL2s5L25vdGVib29rcy9tbC9ncmFkaWVudF9ib29zdGluZy9nYnQubmIuaHRtbCI+JywKICAnPG1ldGEgcHJvcGVydHk9Im9nOmltYWdlIiBjb250ZW50PSJodHRwczovL2V2ZXJkYXJrLmdpdGh1Yi5pby9rOS9hc3NldHMvYW5kcm9pZGlmeS5qcGciPicsCiAgJzxtZXRhIHByb3BlcnR5PSJvZzpkZXNjcmlwdGlvbiIgY29udGVudD0iQSBkYXRhIHNjaWVuY2Ugbm90ZWJvb2sgYWJvdXQgZ3JhZGllbnQgYm9vc3RpbmcgdHJlZXMuIj4nCikKY29udGVudHMgPC0gbWV0YQoKIyBBZGQgR2l0aHViIGNvcm5lci4KZ2l0aHViX2Nvcm5lcl9zdmcgPC0gIi4uLy4uLy4uL2Fzc2V0cy9naXRodWJfY29ybmVyLmh0bWwiCmdpdGh1Yl9jb3JuZXJfY29uZiA8LSBsaXN0KGdpdGh1Yl9saW5rPSJodHRwczovL2dpdGh1Yi5jb20vZXZlcmRhcmsvazkvdHJlZS9tYXN0ZXIvbm90ZWJvb2tzL21sL2dyYWRpZW50X2Jvb3N0aW5nIikKY29udGVudHMgPC0gYyhjb250ZW50cywgc3RyaW5ncjo6c3RyX2ludGVycChyZWFkTGluZXMoZ2l0aHViX2Nvcm5lcl9zdmcpLCBnaXRodWJfY29ybmVyX2NvbmYpKQp3cml0ZUxpbmVzKGNvbnRlbnRzLCBtZXRhX2hlYWRlcl9maWxlKQoKY2xvc2UobWV0YV9oZWFkZXJfZmlsZSkKYGBgCgojIE1vdGl2YXRpb24KCk1vZGVybiBncmFkaWVudCBib29zdGluZyB0cmVlcyAoR0JUKSBpcyB1bmRvdWJ0ZWRseSBvbmUgb2YgdGhlIG1vc3QgcG93ZXJmdWwgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIGZvciB0cmFkaXRpb25hbCBzdXBlcnZpc2VkIGxlYXJuaW5nIHRhc2tzIGluIHRoZSByZWNlbnQgZGVjYWRlLgpFdmVyIHNpbmNlIHRoZSBjcmVhdGlvbiBvZiBYR0Jvb3N0IHRoZXJlIGlzIGEgYm9vbWluZyBpbiBkaWZmZXJlbnQgbW9kZXJuIGRlc2lnbnMgb2YgR0JUIGluIHRoZSBvcGVuIHNvdXJjZSBhcmVhIGFpbWluZyBhdCBub3Qgb25seSBtb2RlbCBhY2N1cmFjeSBidXQgYWxzbyBjb21wdXRhdGlvbmFsIGVmZmljaWVuY3kuCgpJbiB0aGlzIG5vdGVib29rIHdlIHRyeSB0byB1bmJveCB0d28gc3VjaCBwb3dlcmZ1bCBHQlQgZnJhbWV3b3JrczogYHhnYm9vc3RgIGFuZCBgbGlnaHRnYm1gLgpXZSB3aWxsIGZvY3VzIG1vcmUgb24gdGhlIG1ldGhvZG9sb2d5IHJhdGhlciB0aGFuIHRoZWlyIEFQSXMgdG8gZGVlcGx5IHVuZGVyc3RhbmQgaG93IHRoZXNlIGFsZ29yaXRobXMgd29yayBhbmQgd2h5IHRoZXkgYXJlIHNvIGVmZmVjdGl2ZSBjb21wYXJpbmcgdG8gdGhlIHZhbmlsbGEgdmVyc2lvbiBvZiBHQlQsCndoaWNoIGhhcyBiZWVuIGZvcm1hbGx5IGludHJvZHVjZWQgbmVhcmx5IDIwIHllYXJzIGFnbyBbQGZyaWVkbWFuMjAwMWdyZWVkeV0uCgojIEdyYWRpZW50IEJvb3N0aW5nIE1hY2hpbmVzCgpCZWZvcmUgd2Ugc3RlcCBpbnRvIHRoZSBtb2Rlcm4gZGVzaWduIG9mIGdyYWRpZW50IGJvb3N0aW5nIHRyZWVzLApsZXQncyBoYXZlIGEgcXVpY2sgbG9vayBhdCB0aGUgdHJhZGl0aW9uYWwgKHZhbmlsbGEpIGdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmVzLgoKR3JhZGllbnQgYm9vc3RpbmcgaXMgYSBtb2RlbCBlbnNlbWJsaW5nIHRlY2huaXF1ZS4KSXQgY29tYmluZXMgbXVsdGlwbGUgbW9kZWxzIGludG8gb25lIGluIGFuICppdGVyYXRpdmUgYW5kIGFkZGl0aXZlKiBtYW5uZXIuCkVhY2ggaW5kaXZpZHVhbCBtb2RlbCBpcyBpbmRlZWQgYSB3ZWFrIGxlYXJuZXIsCmJ1dCBqb2ludGx5IHRoZXkgZW5kIHVwIHByb3ZpZGluZyBwb3dlcmZ1bCBwcmVkaWN0aW9ucy4KTWF0aGVtYXRpY2FsbHkgc3BlYWtpbmcsCnRoZSBtb2RlbCBjYW4gYmUgZXhwcmVzc2VkIGFzOgoKJCQKXGhhdHt5fSA9IEZfayh4KSA9IFxzdW1fayBmX2soeCksCiQkCgp3aGVyZSAkayQgaXMgbnVtYmVyIG9mIGl0ZXJhdGlvbnMgKGFuZCBoZW5jZSBudW1iZXIgb2Ygd2VhayBsZWFybmVycyksCiRmX2skIGJlbG9uZ3MgdG8gc29tZSBtb2RlbCBmYW1pbHkgYW5kICR4JCBpcyB0aGUgZmVhdHVyZSB2ZWN0b3IuCgpBIHdlYWsgbGVhcm5lciBjYW4gYmUgYSBtb2RlbCB3aXRoIGxlc3NlciBmZWF0dXJlcywKbGVzc2VyIHBhcmFtZXRlcnMsCm9yIGEgc2ltcGxlciBzdHJ1Y3R1cmUuCgojIyBGdW5jdGlvbmFsIEdyYWRpZW50IERlc2NlbnQKCk5vdyBpZiB3ZSByZXdyaXRlIHRoZSBhYm92ZSBtb2RlbCBhczoKCiQkClxiZWdpbnthbGlnbmVkfQp5CiY9IEZfayh4KSArIFxlcHNpbG9uIFxcCiY9IEZfe2stMX0oeCkgKyBmX2soeCkgKyBcZXBzaWxvbiwKXGVuZHthbGlnbmVkfQokJAoKdGhlbiB3ZSBoYXZlOgoKJCQKZl9rKHgpID0geSAtIEZfe2stMX0oeCkgLSBcZXBzaWxvbi4KJCQKClRoZSBlcXVhdGlvbiB0ZWxscyB1cyB0aGF0IHRoZSBmdW5jdGlvbiBhZGRlZCBhdCByb3VuZCAkayQgaXMgZml0dGluZyB0aGUgKm1vZGVsIHJlc2lkdWFsKiBvZiB0aGUgcHJldmlvdXMgZW5zZW1ibGVkIG1vZGVsICgkRl97ay0xfSQpLgpJZiBvdXIgbG9zcyBpcyBzcXVhcmVkIGVycm9yLAptb2RlbCByZXNpZHVhbCBpcyBpbmRlZWQgdGhlIG5lZ2F0aXZlIGdyYWRpZW50IHRvIHRoZSBsb3NzIHcuci50LiB0aGUgZnVuY3Rpb24gYXQgdGhlIGxhc3Qgc3RlcCAoaWdub3JpbmcgdGhlIGNvbnN0YW50IHRlcm0pOgoKJCQKLSBcZnJhY3tccGFydGlhbFxmcmFjezF9ezJ9XGJpZ1t5IC0gRl97ay0xfVxiaWddXjJ9e1xwYXJ0aWFsIEZfe2stMX19ID0gKHkgLSBGX3trLTF9KS4KJCQKClRvIGdlbmVyYWxpemUgdGhlIGlkZWEsCmdpdmVuIGFueSBsb3NzIGZ1bmN0aW9uICRMKHksIEYoeCkpJCwKdG8gc29sdmUgZm9yIHRoZSBvcHRpbWFsIGZ1bmN0aW9uICRmX2skIHRvIGFkZCBhdCByb3VuZCAkayQgd2UgYXJlIGFjdHVhbGx5IGRvaW5nICpmdW5jdGlvbmFsIGdyYWRpZW50IGRlc2NlbnQqOgoKJCQKZl9rKHgpID0gLSBcZnJhY3tccGFydGlhbCBMKHksIEZfe2stMX0oeCkpfXtccGFydGlhbCBGX3trLTF9KHgpfS4KJCQKClRoZSBuZWdhdGl2ZSBncmFkaWVudCBpcyBhbHNvIHJlZmVycmVkIHRvIGFzICpwc2V1ZG8tcmVzaWR1YWwqLgpXaGVuIG91ciBsb3NzIGZ1bmN0aW9uIGlzIG5vdCBzcXVhcmVkIGVycm9yLAp3ZSBubyBsb25nZXIgbGl0ZXJhbGx5IGZpdCBhIG1vZGVsIG9uIHJlc2lkdWFscyBidXQgb24gdGhlIHBzZXVkby1yZXNpZHVhbHMuCgojIyBCb29zdGluZyB2LnMuIEdyYWRpZW50IEJvb3N0aW5nCgpJdCBjYW4gYmUgZWFzaWx5IGNvbmZ1c2VkIGFib3V0IHRoZSBuYW1lICpncmFkaWVudCBib29zdGluZyouCkFzIHdlIGFscmVhZHkgc2VlbiwKZ3JhaWRlbnQgYm9vc3RpbmcgaXMgaW5kZWVkIGEgZ3JhZGllbnQgZGVzY2VudCBhbGdvcml0aG0sCmJ1dCBvcGVyYXRpbmcgYXQgZnVuY3Rpb25hbCBzcGFjZS4KVGhhdCBpcywKdG8gb3B0aW1pemUgdGhlIG9iamVjdGl2ZSBmdW5jdGlvbiB3ZSBhcmUgbm90IGxvb2tpbmcgZm9yIHBhcmFtZXRlcnMgYnV0IGxvb2tpbmcgZm9yIGZ1bmN0aW9ucy4KSW4gdGhlIHRyYWluaW5nIHBoYXNlIHdlIGFyZSBpdGVyYXRpdmVseSBzZWFyY2hpbmcgZm9yIGEgZnVuY3Rpb24gKGEgd2VhayBsZWFybmVyKSB0byBiZSBpbmNsdWRlZCBpbnRvIG91ciBtb2RlbC4KCipCb29zdGluZyogaXMgeWV0IGFub3RoZXIgZGlmZmVyZW50IG1vZGVsIGVuc2VtYmxpbmcgdGVjaG5pcXVlLApub3QgdG8gYmUgY29uZnVzZWQgd2l0aCBncmFkaWVudCBib29zdGluZy4KVGhlIGdlbmVyYWwgaWRlYSBvZiBib29zdGluZyBpcyB0byBhbHNvIHRyYWluIGEgbW9kZWwgaXRlcmF0aXZlbHkuCkluIGVhY2ggcm91bmQgdGhlIHRyYWluaW5nIGV4YW1wbGVzIHdpbGwgYmUgcmUtd2VpZ2h0ZWQgYmFzZWQgb24gdGhlIHJlc3VsdHMgKGxvc3NlcykgZnJvbSB0aGUgbGFzdCBydW4uCkhhcmRlciBleGFtcGxlcyB3aWxsIGdhaW4gaGlnaGVyIHdlaWdodHMgc28gdGhlIHN1YnNlcXVlbnQgdHJhaW5pbmcgd2lsbCBzaGlmdCBmb2N1cyBvbnRvIHRoZW0uClRoZSBtb3N0IHBvcHVsYXIgYm9vc3RpbmcgYWxnb3JpdGhtIGlzIHByb2JhYmx5IFtBZGFCb29zdF0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQWRhQm9vc3QpLAphYmJyZXZpYXRlZCBmcm9tIGFkYXB0aXZlIGJvb3N0aW5nLgoKQm9vc3RpbmcgaXMgc2ltaWxhciB0byBncmFkaWVudCBib29zdGluZyBpbiB0aGF0IGJvdGggYXJlIG1vZGVsIGVuc2VtYmxpbmcgd2l0aCBhZGRpdGl2ZSB3ZWFrIGxlYXJuZXJzIGFuZCBpdGVyYXRpdmUgdHJhaW5pbmcuCkJ1dCB0aGV5IGFyZSB2ZXJ5IGRpZmZlcmVudCBpbiB0aGUgd2F5IHRoZXkgc2VhcmNoIGZvciB0aGUgb3B0aW1hbCBmdW5jdGlvbiBhdCBlYWNoIGl0ZXJhdGlvbi4KQm9vc3RpbmcgZml0cyBhIGxlYXJuZXIgd2l0aCBleGFtcGxlcyByZS13ZWlnaHRlZCBieSBsb3NzIGdlbmVyYXRlZCBmcm9tIHRoZSBwcmV2aW91cyBlbnNlbWJsZS4KR3JhZGllbnQgYm9vc3RpbmcgaW5zdGVhZCBmaXRzIGEgbGVhcm5lciB3aXRoIHBzZXVkby1yZXNpZHVhbHM6CnRoZSBuZWdhdGl2ZSBmdW5jdGlvbmFsIGdyYWRpZW50IHcuci50LiB0aGUgbG9zcy4KCiMjIExpbmVhciBNb2RlbCBhcyBXZWFrIExlYXJuZXIKClRoZSBjaG9pY2Ugb2Ygd2VhayBsZWFybmVyIGhhcyBhIGh1Z2UgaW1wYWN0IG9uIHRoZSBlZmZlY3RpdmVuZXNzIG9mIGdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmVzLgpOb3QgYWxsIHdlYWsgbGVhcm5lcnMgY2FuIGdhaW4gZXF1YWxseSBmcm9tIHN1Y2ggZW5zZW1ibGUuCkluIHRoaXMgc2VjdGlvbiB3ZSBkZW1vbnN0cmF0ZSBhIG1vZGVsIGVuc2VtYmxlIHVzaW5nIGEgd2VhayBsaW5lYXIgbGVhcm5lci4KSXQgdHVybnMgb3V0IHRoYXQgZ3JhZGllbnQgYm9vc3Rpbmcgd2l0aCBhIGxpbmVhciBtb2RlbCBtYXkgZG8gbm8gZ29vZCBhdCBhbGwuCgpMZXQncyBjcmVhdGUgc29tZSBzaW11bGF0ZWQgZGF0YSBiYXNlZCBvbiBhIHNpbXBsZSBncm91bmQtdHJ1dGggKG5vbi1saW5lYXIpIGRhdGEgZ2VuZXJhdGluZyBwcm9jZXNzOgoKYGBge3Igc2ltX2RhdGF9CmxpYnJhcnkoZGF0YS50YWJsZSkKCnNldC5zZWVkKDY2NikKCiMgREdQOiB5ID0gMyB4XzEgKyA2IHhfMiAtMS41IHhfMXhfMiArIGUKbiA8LSAxMDAwCngxIDwtIHJub3JtKG4sIDIsIDQpCngyIDwtIHJ1bmlmKG4pCmUgPC0gcm5vcm0obikKeSA8LSAzICogeDEgKyA2ICogeDIgLSAxLjUgKiB4MSAqIHgyICsgZQoKIyBUcmFpbi10ZXN0IHNwbGl0Lgppc190ZXN0IDwtIHJ1bmlmKG4pIDwgLjIKRCA8LSBkYXRhLnRhYmxlKHgxPXgxLCB4Mj14MiwgeT15KQpoZWFkKEQpCmBgYAoKV2UgZml0IGEgbGluZWFyIG1vZGVsIHdpdGggYWxsIGZlYXR1cmVzIChjb25zZGllciBpdCBhcyBhICJzdHJvbmciIGxlYXJuZXIpOgoKYGBge3Igc2ltX2RhdGFfZml0fQojIEZpdCBhIGxpbmVhciBtb2RlbCB3aXRoIGFsbCBmZWF0dXJlcy4KZiA8LSBsbSh5IH4geDEgKyB4MiwgZGF0YT1EWyFpc190ZXN0XSkKRCA8LSBEWywgeWhhdDo9cHJlZGljdChmLCBEKV0KCiMgTVNFIGFzIG91ciBsb3NzIGZ1bmN0aW9uLgptc2UgPC0gZnVuY3Rpb24oeSwgeWhhdCkgewogIG1lYW4oKHkgLSB5aGF0KV4yKQp9CgojIENvbXB1dGUgdGVzdGluZyBsb3NzLgp3aXRoKERbaXNfdGVzdF0sIG1zZSh5LCB5aGF0KSkKYGBgCgpOb3cgbGV0J3MgZW5zZW1ibGUgYSB3ZWFrIGxlYXJuZXIgd2hpY2ggb25seSB0YWtlcyBvbmUgZmVhdHVyZS4KV2UgdHJhaW4gdGhlIG1vZGVsIGJhc2VkIG9uIGdyYWRpZW50IGJvb3N0aW5nIGZyYW1ld29yay4KU2luY2Ugb3VyIGxvc3MgaXMgc3F1YXJlZCBlcnJvciwKd2UgY2FuIHNpbXBseSBmaXQgdGhlIG1vZGVsIHJlc2lkdWFsIG9mIHRoZSBwcmV2aW91cyBlbnNlbWJsZToKCmBgYHtyIGdibV9saW5lYXJfbGVhcm5lcn0KZjEgPC0gbG0oeSB+IHgxLCBkYXRhPURbIWlzX3Rlc3RdKQpEIDwtIERbLCB5aGF0MTo9cHJlZGljdChmMSwgRCldCkQgPC0gRFssIHJlc2lkMTo9eSAtIHloYXQxXQoKd2l0aChEW2lzX3Rlc3RdLCBtc2UoeSwgeWhhdDEpKQoKZjIgPC0gbG0ocmVzaWQxIH4geDIsIGRhdGE9RFshaXNfdGVzdF0pCkQgPC0gRFssIHloYXQyOj1wcmVkaWN0KGYyLCBEKV0KRCA8LSBEWywgcmVzaWQyOj15IC0geWhhdDEgLSB5aGF0Ml0KCndpdGgoRFtpc190ZXN0XSwgbXNlKHksIHloYXQxICsgeWhhdDIpKQoKZjMgPC0gbG0ocmVzaWQyIH4geDEsIGRhdGE9RFshaXNfdGVzdF0pCkQgPC0gRFssIHloYXQzOj1wcmVkaWN0KGYzLCBEKV0KRCA8LSBEWywgcmVzaWQzOj15IC0geWhhdDEgLSB5aGF0MiAtIHloYXQzXQoKd2l0aChEW2lzX3Rlc3RdLCBtc2UoeSwgeWhhdDEgKyB5aGF0MiArIHloYXQzKSkKCmY0IDwtIGxtKHJlc2lkMyB+IHgyLCBkYXRhPURbIWlzX3Rlc3RdKQpEIDwtIERbLCB5aGF0NDo9cHJlZGljdChmNCwgRCldCkQgPC0gRFssIHJlc2lkNDo9eSAtIHloYXQxIC0geWhhdDIgLSB5aGF0MyAtIHloYXQ0XQoKd2l0aChEW2lzX3Rlc3RdLCBtc2UoeSwgeWhhdDEgKyB5aGF0MiArIHloYXQzICsgeWhhdDQpKQpgYGAKCkFzIG9uZSBtYXkgcmVhbGl6ZSwKdGhlIGJvb3N0ZWQgbW9kZWwgd29uJ3Qgb3V0cGVyZm9ybSBhIHNpbXBsZSBsaW5lYXIgbW9kZWwgd2l0aCBhbGwgZmVhdHVyZXMgaW5jbHVkZWQuCklzIGl0IGJlY2F1c2Ugb2Ygb3VyIG5haXZlIGJ1aWx0LWZyb20tc2NyYXRjaCBpbXBsZW1lbnRhdGlvbj8KTm90IHJlYWxseS4KTGV0J3MgdXNlIGB4Z2Jvb3N0YC0tb25lIG9mIHRoZSBzdGF0ZS1vZi10aGUtYXJ0IGdyYWRpZW50IGJvb3N0aW5nIGZyYW1ld29ya3MtLXRvIHRyYWluIGEgR0JNIHdpdGggbGluZWFyIGxlYXJuZXI6CgpgYGB7ciB4Z2JfbGluZWFyX2xlYXJuZXJ9CmxpYnJhcnkoeGdib29zdCkKCkR4IDwtIHhnYi5ETWF0cml4KGFzLm1hdHJpeChEWyFpc190ZXN0LCAuKHgxLCB4MildKSwgbGFiZWw9RFshaXNfdGVzdCwgeV0pCkR0IDwtIHhnYi5ETWF0cml4KGFzLm1hdHJpeChEW2lzX3Rlc3QsIC4oeDEsIHgyKV0pLCBsYWJlbD1EW2lzX3Rlc3QsIHldKQoKYnN0IDwtIHhnYi50cmFpbihwYXJhbXM9bGlzdChvYmplY3RpdmU9InJlZzpzcXVhcmVkZXJyb3IiLCBib29zdGVyPSJnYmxpbmVhciIpLCAKICAgICAgICAgICAgICAgICBkYXRhPUR4LCB3YXRjaGxpc3Q9bGlzdCh0ZXN0PUR0KSwKICAgICAgICAgICAgICAgICBucm91bmQ9NTAsIGVhcmx5X3N0b3BwaW5nX3JvdW5kcz01LCB2ZXJib3NlPTApCgp5aGF0IDwtIHByZWRpY3QoYnN0LCBhcy5tYXRyaXgoRFtpc190ZXN0LCAuKHgxLCB4MildKSkKbXNlKERbaXNfdGVzdCwgeV0sIHloYXQpCmBgYAoKVGhlIHJlc3VsdCBpcyByb3VnaGx5IHRoZSBzYW1lLgoKSW4gZ2VuZXJhbCwKZ3JhZGllbnQgYm9vc3RpbmcgZG9lcyBub3QgaGVscCBpbXBvcnZpbmcgYSBsaW5lYXIgbW9kZWwuClRoaXMgaXMgYnkgbGFyZ2UgZHVlIHRvIHRoZSBmYWN0IHRoYXQgR0JNIGlzIGFuIGFkZGl0aXZlIG1vZGVsLgpDb21iaW5pbmcgbGluZWFyIG1vZGVscyBhZGRpdGl2ZWx5IHdpbGwganVzdCByZXN1bHQgaW4geWV0IGFub3RoZXIgbGluZWFyIG1vZGVsLgpJbmRlZWQsCnVzaW5nIGEgbGluZWFyIG1vZGVsIGFzIHRoZSBiYXNlIGxlYXJuZXIgd2lsbCBoYXZlIHRoZSBlZmZlY3Qgb2Ygc29sdmluZyBhIGZ1bGwgbGluZWFyIHN5c3RlbSAoYSBtb2RlbCB3aXRoIGFsbCBmZWF0dXJlcykgaW4gYW4gaXRlcmF0aXZlIGFuZCBhcHByb3hpbWF0ZWQgbWFubmVyLgpTbyBhdCBiZXN0IHRoZSByZXN1bHQgaXMgdGhlIHNhbWUgYXMgYSBzaW5nbGUgbGluZWFyIG1vZGVsIHdpdGggYWxsIGZlYXR1cmVzLgoKIyMgVHJlZSBNb2RlbCBhcyBXZWFrIExlYXJuZXIKCkJhc2VkIG9uIGVtcGlyaWNhbCBmaW5kaW5ncywKdGhlIGJlc3Qgd2VhayBsZWFybmVyIGluIEdCTSBpcyBhIHRyZWUuCk1vc3Qgb2YgdGhlIGdyYWRpZW50IGJvb3N0aW5nIChvciBqdXN0IGJvb3N0aW5nKSBpbXBsZW1lbnRhdGlvbnMgdXNlIHRyZWUgbW9kZWxzIGFzIHRoZWlyIGJhc2UgbGVhcm5lci4KCkZvciBvdXIgc2ltdWxhdGVkIGRhdGEsCmdyYWRpZW50IGJvb3N0ZWQgdHJlZXMgY2FuIGVhc2lseSBvdXRwZXJmb3JtIHRoZSBsaW5lYXIgbW9kZWw6CgpgYGB7ciB4Z2JfdHJlZV9sZWFybmVyfQpic3QyIDwtIHhnYi50cmFpbihwYXJhbXM9bGlzdChvYmplY3RpdmU9InJlZzpzcXVhcmVkZXJyb3IiLCBib29zdGVyPSJnYnRyZWUiKSwgCiAgICAgICAgICAgICAgICAgIGRhdGE9RHgsIHdhdGNobGlzdD1saXN0KHRlc3Q9RHQpLAogICAgICAgICAgICAgICAgICBucm91bmQ9NTAsIGVhcmx5X3N0b3BwaW5nX3JvdW5kcz01LCB2ZXJib3NlPTApCgp5aGF0IDwtIHByZWRpY3QoYnN0MiwgYXMubWF0cml4KERbaXNfdGVzdCwgLih4MSwgeDIpXSkpCm1zZShEW2lzX3Rlc3QsIHldLCB5aGF0KSAgIyBTbWFsbGVyIGxvc3MgYWNoaWV2ZWQuCmBgYAoKRm9yIHRoZSByZXN0IG9mIHRoZSBub3RlYm9vayB3ZSB3aWxsIGZvY3VzIG91ciBkaXNjdXNzaW9uIG9uIHRyZWUtYmFzZWQgZ3JhZGllbnQgYm9vc3RpbmcuCgojIFRyZWUgRW5zZW1ibGVzCgpCZWZvcmUgd2UgdGFsayBhYm91dCBncmFkaWVudCBib29zdGluZyB0cmVlcywKbGV0J3MgaGF2ZSBhIGJyaWVmIHJldmlldyBvbiB0cmVlIG1vZGVscyBpbiBnZW5lcmFsLApmcm9tIGEgc2luZ2xlIHRyZWUgdG8gdHJlZSBlbnNlbWJsZXMuCgpUcmVlIG1vZGVscyBhcmUgbm9uLWRpZmZlcmVudGlhYmxlLgpHcmFkaWVudC1iYXNlZCBvcHRpbWl6ZXJzIHdvbid0IGRpcmVjdGx5IHdvcmsgb24gdHJlZSBtb2RlbHMuClRoaXMgbWFrZXMgdGhlbSB2ZXJ5IGRpZmZlcmVudCBmcm9tIG90aGVyIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHN1Y2ggYXMgbmV1cmFsIG5ldHdvcmtzLgoKVHJlZSBtb2RlbHMgYXJlIHBvd2VyZnVsIGJlY2F1c2UgdGhleSBhcmUgbm9uLWxpbmVhci4KRmVhdHVyZXMgYXJlIGFsbG93ZWQgdG8gaW50ZXJhY3Qgd2l0aCBlYWNoIG90aGVyIGluIGEgc2VxdWVudGlhbCBhbmQgbG9zcy1ndWlkZWQgbWFubmVyLgpBbmQgdGhleSBhcmUgaW52YXJpYW50IHRvIGZlYXR1cmUgc2NhbGUgc2luY2UgYSB0cmVlIHNwbGl0IGlzIG9ubHkgb3JkaW5hbC4KVGhpcyBzaW1wbGlmaWVzIGRhdGEgcHJlcHJvY2Vzc2luZyBhbmQgaGVuY2UgdGhlIG92ZXJhbGwgcGlwZWxpbmUuCgojIyBBIFNpbmdsZSBUcmVlCgpUaGUgaWRlYSBvZiBhIHNpbmdsZSB0cmVlIG1vZGVsIGlzIHJhdGhlciBzaW1wbGUuClRoZSBtb2RlbCB1c2VzIGEgc3BsaXQgZmluZGluZyBhbGdvcml0aG0gdG8gZW51bWVyYXRlIG92ZXIgYWxsIHRoZSBwb3NzaWJsZSBzcGxpdHMgb24gYWxsIHRoZSBmZWF0dXJlcy4KT25jZSBhIGJlc3Qgc3BsaXQgaXMgZm91bmQgYmFzZWQgb24gYSBwYXJ0aWN1bGFyIG1lYXN1cmUsCnRoZSBtb2RlbCBtb3ZlIG9uIHRvIGZpbmQgdGhlIG5leHQgc3BsaXQgZ2l2ZW4gdGhlIGN1cnJlbnQgbm9kZSwKZ3Jvd2luZyB0aGUgdHJlZSBkZWVwZXIgYW5kIGRlZXBlciB1bnRpbCBhIGNlcnRhaW4gY3JpdGVyaW9uIGlzIG1ldC4KCkhlcmUgaXMgYSBnZW5lcmFsIHBzZXVkbyBjb2RlIGRlc2NyaWJpbmcgYSBzaW5nbGUgc3BsaXQgYWxnb3JpdGhtIGluIGl0cyAqZXhhY3QqIGZvcm06CgpgYGAKW0V4YWN0IFRyZWUgU3BsaXQgQWxnb3JpdGhtXQoKbTogdG90YWwgbnVtYmVyIG9mIGZlYXR1cmVzCm46IHRvdGFsIG51bWJlciBvZiBleGFtcGxlcwpzOiBzY29yZSB0byBtZWFzdXJlIHRoZSBxdWFsaXR5IG9mIGEgc3BsaXQKeF9pajogdGhlIGktdGggZXhhbXBsZSB2YWx1ZSBvbiBmZWF0dXJlIGoKCmZvciBmZWF0dXJlIGogaW4gMSB0byBtOgogIHNvcnQgeF9qCiAgZm9yIGV4YW1wbGUgeF9paiBpbiAxIHRvIG4gb24gZmVhdHVyZSBqOgogICAgY29tcHV0ZSBzCgpkbyBzcGxpdCB1c2luZyBqKiBhdCB4X2lqKiBhc3NvY2lhdGVkIHdpdGggbWF4aW11bSBzCmBgYAoKQSBUcmVlIG1vZGVsIGludm9sdmVzIG11bHRpcGxlICpzZXF1ZW50aWFsKiBzcGxpdHMgdG8gYXJyaXZlIGF0IHRoZSB0ZXJtaW5hbCBub2RlIHdoaWNoIGdpdmVzIHByZWRpY3Rpb24uCkhlcmUgYXJlIHNldmVyYWwgaW1wb3J0YW50IGNvbXBvbmVudHMgdGhhdCBtdXN0IGJlIHRha2VuIGNhcmUgZm9yIGEgZGV0YWlsZWQgaW1wbGVtZW50YXRpb246CgojIyMjIEZlYXR1cmUgSXRlcmF0aW9uIHstfQoKVGhlIGFsZ29yaXRobSBpcyBncmVlZHkuClRvIHNlYXJjaCBmb3IgdGhlIG9wdGltYWwgc3BsaXQgYWxsIGZlYXR1cmVzIGFyZSB0cmF2ZXJzZWQuClRoZSBpbW1lZGlhdGUgY29uc2VxdWVuY2UgaXMgdGhlIG92ZXItZml0dGluZyBuYXR1cmUgb2YgYSBzaW5nbGUgdHJlZS4KQW5kIGl0IGlzIHN1Y2ggZGlmZmljdWx0eSBpbiBnZW5lcmFsaXphdGlvbiBsZWFkcyB0byB0aGUgaWRlYSBvZiB0cmVlIGVuc2VtYmxlcy4KCiMjIyMgRmVhdHVyZSBWYWx1ZSBJdGVyYXRpb24gey19CgpGb3IgZWFjaCBmZWF0dXJlLAphIHNvcnRpbmcgb3BlcmF0aW9uIGlzIHJlcXVpcmVkIHRvIHRyYXZlcnNlIG92ZXIgdGhlIChkaXN0aW5jdCkgc29ydGVkIHZhbHVlcyBvZiB0aGF0IGZlYXR1cmUuClRoZSBtb3JlIHZhbHVlcyBhIGZlYXR1cmUgY2FuIHRha2UgdGhlIG1vcmUgY29tcHV0aW5nIHJlc291cmNlcyBhcmUgbmVlZGVkIHRvIGZpbmQgdGhlIGJlc3Qgc3BsaXQuCgojIyMjIFF1YWxpdHkgb2YgU3BsaXQgTWVhc3VyZSB7LX0KClRoaXMgaXMgcHJvYmFibHkgdGhlIG1vc3QgaW1wb3J0YW50IHBhcnQgb2YgYSB0cmVlIGFsZ29yaXRobS4KSXQgZGlyZWN0bHkgYWZmZWN0cyB0aGUgYWNjdXJhY3kgb2YgdGhlIHJlc3VsdGluZyB0cmVlLgpUaGUgcXVhbGl0eSBzY29yZSBtZWFzdXJlcyBob3cgZ29vZCBhIG5vZGUgaXMgc3BsaXR0aW5nIHRoZSBleGFtcGxlcy4KSW4gdGhlIGxpdGVyYXR1cmUgdGhpcyBpcyBvZnRlbiByZWZlcmVkIHRvIGFzICppbXB1cml0eSouCkEgY2xhc3NpY2FsIGltcHVyaXR5IG1lYXN1cmUgaXMgR2luaSBpbmRleCBmb3IgY2xhc3NpZmljYXRpb24gYW5kIHJlc2lkdWFsIHN1bSBvZiBzcXVhcmVzIGZvciByZWdyZXNzaW9uLgoKIyMjIyBEZXB0aCBvZiB0aGUgVHJlZSB7LX0KCkhvdyBkZWVwIChob3cgbWFueSBzcGxpdHMpIHNob3VsZCB3ZSBncm93IHRoZSB0cmVlPwpUaGlzIGlzIGEgaHlwZXItcGFyYW1ldGVyIHRoYXQgY2FuIGFmZmVjdCBtb2RlbCBnZW5lcmFsaXphdGlvbi4KRm9yIGNsYXNzaWZpY2F0aW9uIHdlIGNhbiBncm93IGEgdHJlZSBhcyBkZWVwIGFzIHVudGlsIGV2ZXJ5IG5vZGUgY29udGFpbnMgb25seSBvbmUgc2luZ2xlIGV4YW1wbGUuClRoYXQgaXMsCnVudGlsIG5vIHNwbGl0IGNhbiBiZSBmdXJ0aGVyIGFjaGlldmVkLgpBIHRyZWUgdGhhdCBpcyB0b28gZGVlcCB3aWxsIGRlZmluaXRlbHkgc3VmZmVyIGZyb20gb3Zlci1maXR0aW5nLgoKQSBzaW5nbGUgdHJlZSBpcyB1c3VhbGx5IHZlcnkgYmFkIGF0IGdlbmVyYWxpemF0aW9uLgpUaGUgb25seSBiZW5lZml0IGlzIHByb2JhYmx5IGl0cyAqaW50ZXJwcmV0YWJpbGl0eSogc2luY2UgdGhlIHRyZWUgc3RydWN0dXJlIGl0c2VsZiBpcyBhIHNldCBvZiBodW1hbiBmcmllbmRseSBkZWNpc2lvbiBydWxlcy4KSG93ZXZlciBkdWUgdG8gaXRzIHBvb3IgZ2VuZXJhbGl6YXRpb24gb3ZlciB1bnNlZW4gZGF0YSwKc3VjaCBpbnRlcnByZXRhYmlsaXR5IGlzIHByYWN0aWNhbGx5IG9mIG5vIHVzZS4KCiMjIyBBIFJlZ3Jlc3Npb24gVHJlZQoKQ29udGludWUgd2l0aCBvdXIgc2ltdWxhdGVkIGRhdGFzZXQsCmxldCdzIHF1aWNrbHkgYnVpbGQgYSBzaW5nbGUgcmVncmVzc2lvbiB0cmVlIGFuZCBldmFsdWF0ZSBpdHMgcGVyZm9ybWFuY2Ugb24gdGhlIHRlc3Rpbmcgc2V0LgoKYGBge3Igc2ltX2RhdGFfdHJlZV90cmFpbn0KbGlicmFyeShycGFydCkgICMgUmVjdXJzaXZlIFBBUlRpdGlvbmluZyAKbGlicmFyeShycGFydC5wbG90KQoKZiA8LSBycGFydCh5IH4geDEgKyB4MiwgZGF0YT1EWyFpc190ZXN0XSwgbWV0aG9kPSJhbm92YSIsIG1vZGVsPVRSVUUpCnJwYXJ0LnBsb3QoZiwgZGlnaXRzPTMpICAjIEZvciBlYWNoIG5vZGUgdGhlIG1lYW4gdmFsdWUgYW5kIHBvcHVsYXRpb24gYXJlIHNob3duLgpgYGAKClRoZSB0ZXN0aW5nIHNjb3JlIGlzIHdvcnNlIHRoYW4gYSBsaW5lYXIgbW9kZWw6CgpgYGB7ciBzaW1fZGF0YV90cmVlX2V2YWx9Cm1zZShEW2lzX3Rlc3QsIHldLCBwcmVkaWN0KGYsIERbaXNfdGVzdF0pKQpgYGAKCkV2ZW4gdGhvdWdoIG91ciB0cnVlIG1vZGVsIGlzIG5vbi1saW5lYXIsCmEgdHJlZSBmYWlsZWQgdG8gb3V0cGVyZm9ybSBhIGxpbmVhciBtb2RlbC4KRG9lcyB0aGF0IG1ha2Ugc2Vuc2U/Ck91ciB0cmVlIG1heSBiZSB0b28gc2ltcGxlIHRvIHBlcmZvcm0gYSB0YXNrIG9uIHByZWRpY3RpbmcgYSBjb250aW5vdXMgdmFsdWUuClRoZSB0cmVlIGlzIG9ubHkgY2FwYWJsZSBvZiBwcmVkaWN0aW5nIDkgZGlzdGluY3QgdmFsdWVzLAp3aGljaCBtYWtlIGl0IGRpZmZpY3VsdCB0byBjb21wZXRlIHdpdGggYSBsaW5lYXIgbW9kZWwgb24gYSBjb250aW5vdXMgbWV0cmljIHN1Y2ggYXMgc3F1YXJlZCBlcnJvci4KCkluZGVlZCBpdCBpcyB2ZXJ5IGVhc3kgdG8gYnVpbGQgYSBtb3JlIGNvbXBsaWNhdGVkIChsZXNzIHJlZ3VsYXRlZCkgdHJlZSB0byBvdXRwZXJmb3JtIHRoZSBsaW5lYXIgbW9kZWw6CgpgYGB7ciBzaW1fZGF0YV9iZXR0ZXJfdHJlZX0KZjIgPC0gcnBhcnQoeSB+IHgxICsgeDIsIGRhdGE9RFshaXNfdGVzdF0sIG1ldGhvZD0iYW5vdmEiLCBtb2RlbD1UUlVFLAogICAgICAgICAgICBjb250cm9sPXJwYXJ0LmNvbnRyb2wobWluc3BsaXQ9MTAsIGNwPTVlLTQpKQp1bmlxdWVOKGYyJHdoZXJlKSAgIyBIb3cgbWFueSB0ZXJtaW5hbCBub2Rlcz8KbXNlKERbaXNfdGVzdCwgeV0sIHByZWRpY3QoZjIsIERbaXNfdGVzdF0pKQpgYGAKClB1dCBhc2lkZSByZWd1bGFyaXphdGlvbiBhbmQgbW9kZWwgY29tcGxleGl0eSwKYSBjb21tb24gcXVhbGl0eSBvZiBzcGxpdCBtZWFzdXJlIGZvciBhIHJlZ3Jlc3Npb24gdHJlZSBpcyB0aGUgc3VtIG9mIHNxdWFyZXMgJFNTID0gXHN1bSAoeV9pIC1cYmFye3l9KV4yJC4KU28gZm9yIGEgZ2l2ZW4gbm9kZSB3ZSBmaW5kIGEgc3BsaXQgc3VjaCB0aGF0IHRoZSBkZWNyZWFzZSBpbiBzdW0tb2Ytc3F1YXJlcyAkU1NfVCAtIChTU19MICsgU1NfUikkIGlzIHRoZSBsYXJnZXN0LgokU1NfVCQgaXMgdGhlICRTUyQgb2YgY3VycmVudCBub2RlLAokU1NfUiQgaXMgJFNTJCBvZiB0aGUgcmlnaHQtaGFuZCBub2RlIHJlc3VsdGVkIGZyb20gdGhlIHNwbGl0LAokU1NfTCQgdGhlICRTUyQgb2YgbGVmdC1oYW5kIG5vZGUuCk5vdGUgdGhhdCB0aGUgcHJlZGljdGlvbiBvZiBhIHJlZ3Jlc3Npb24gdHJlZSBub2RlIGlzIGl0cyBtZWFuIHZhbHVlLgpIZW5jZSAkU1MkIGlzIHRoZSBzcXVhcmVkIGVycm9yIG9mIHRoYXQgbm9kZS4KCiMjIyBBIENsYXNzaWZpY2F0aW9uIFRyZWUKCldlIHVzZSB0aGUgb2xkLXNjaG9vbCBbVUNJIElyaXNdKGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9kYXRhc2V0cy9pcmlzKSBkYXRhc2V0IHRvIHF1aWNrbHkgZGVtbyBhIHNpbmdsZSBjbGFzc2lmaWNhdGlvbiB0cmVlLgoKYGBge3IgcnBhcnRfdHJhaW59CmNvbmZ1c2lvbl9tYXRyaXggPC0gZnVuY3Rpb24oeV90cnVlLCB5X3ByZWQpIHsKICB0YWJsZSh5X3RydWUsIHlfcHJlZCkKfQoKIyBTcGxpdCBJUklTIGRhdGFzZXQgaW50byB0cmFpbi10ZXN0Lgppc190ZXN0X2lyaXMgPC0gcnVuaWYobnJvdyhpcmlzKSkgPCAuMwppcmlzX3RyYWluIDwtIGlyaXNbIWlzX3Rlc3RfaXJpcyxdCmlyaXNfdGVzdCA8LSBpcmlzW2lzX3Rlc3RfaXJpcyxdCgojIEdyb3cgYSBzaW5nbGUgY2xhc3NpZmljYXRpb24gdHJlZS4KZiA8LSBycGFydChTcGVjaWVzIH4gLiwgZGF0YT1pcmlzX3RyYWluLCBtZXRob2Q9ImNsYXNzIikKcnBhcnQucGxvdChmKQpgYGAKCmBgYHtyIHJwYXJ0X2V2YWxfdHJhaW59CiMgQ29uZnVzaW9uIG1hdHJpeCBmb3IgdGhlIHRyYWluaW5nIHNldC4KcHJpbnQoY29uZnVzaW9uX21hdHJpeChpcmlzX3RyYWluJFNwZWNpZXMsIHByZWRpY3QoZiwgaXJpc190cmFpbiwgdHlwZT0iY2xhc3MiKSkpCmBgYAoKYGBge3IgcnBhcnRfZXZhbF90ZXN0fQojIENvbmZ1c2lvbiBtYXRyaXggZm9yIHRoZSB0ZXN0aW5nIHNldC4KcHJpbnQoY29uZnVzaW9uX21hdHJpeChpcmlzX3Rlc3QkU3BlY2llcywgcHJlZGljdChmLCBpcmlzX3Rlc3QsIHR5cGU9ImNsYXNzIikpKQpgYGAKCkEgc2luZ2xlIGNsYXNzaWZpY2F0aW9uIHRyZWUgbW9kZWwgY2FuIHBlcmZvcm0gYm90aCB3ZWxsIGluIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldCBmb3IgSXJpcy4KU2luY2UgdGhlIGRhdGFzZXQgaXMgYSBiaXQgdG9vIHRyaXZpYWwuCkluIGFueSByZWFsIHdvcmxkIGFwcGxpY2F0aW9uLAphIHNpbmdsZSB0cmVlIGlzIHVzdWFsbHkgbm90IGVub3VnaCB0byBzb2x2ZSB0aGUgcHJvYmxlbS4KQW5kIHRoaXMgaXMgd2hlcmUgd2UgbmVlZCAqbW9yZSogdHJlZXMuCgojIyBGcm9tIE9uZSBUcmVlIHRvIE1hbnkgVHJlZXMKClRvIG92ZXJjb21lIHRoZSBzaG9ydGZhbGwgb2YgYSBzaW5nbGUgdHJlZSwKZW5zZW1ibGluZyB0ZWNobmlxdWUgaXMgdXNlZCB0byBjb21iaW5lIG11bHRpcGxlIHRyZWVzIGludG8gb25lIHNpbmdsZSBtb2RlbC4KVGhlcmUgYXJlIHR3byB2ZXJ5IHN1Y2Nlc3NmdWwgZW5zZW1ibGluZyB0ZWNobmlxdWVzIHVzZWQgZm9yIHRyZWUgbW9kZWxzOgpiYWdnaW5nIGFuZCBib29zdGluZy4KVGhlIGdlbmVyYWwgaWRlYSBpcyB0byBjb21iaW5lIG1hbnkgc2ltcGxlIChoZW5jZSB3ZWFrKSB0cmVlcyBhcyBhIHRyZWUgY29tbWl0dGVlIGZvciBmaW5hbCBwcmVkaWN0aW9uLgpJdCB0dXJucyBvdXQgdGhhdCB0aGUgdmFyaWFiaWxpdHkgaW4gbWFueSBzaW1wbGUvd2VhayBhbmQgZXNwZWNpYWxseSBub24tbGluZWFyIGxlYXJuZXJzIGNhbiBqb2ludGx5IG91dHBlcmZvcm0gYSBzaW5nbGUgKGFuZCB1c3VhbGx5IG92ZXItZml0dGluZykgY29tcGxpY2F0ZWQvc3Ryb25nIGxlYXJuZXIuCgojIyMgQmFnZ2luZyBFbnNlbWJsZXMKClRoZSBtb3N0IHdlbGwta25vd24gYmFnZ2luZyB0cmVlIG1vZGVsIGlzIFJhbmRvbSBGb3Jlc3QgKFJGIGhlcmVhZnRlcikuCkluIGVhY2ggc2luZ2xlIHRyZWUgaW4gUkYgdGhlIHNwbGl0IGFsZ29yaXRobSBpcyBtb2RpZmllZCB0byBvbmx5IGl0ZXJhdGUgb3ZlciBhIHN1YnNhbXBsZSBvZiBhbGwgZmVhdHVyZXMuClRoaXMgcmVkdWNlcyB0aGUgYWJpbGl0eSBvZiBhbnkgc2luZ2xlIHRyZWUgdG8gb3Zlci1maXQgdGhlIHRyYWluaW5nIGRhdGEgYW5kIGNyZWF0ZSBkaXZlcnNpdHkgYW1vbmcgZGlmZmVyZW50IHRyZWVzLgpSRiBhbHNvIHVzZSBib29zdHJhcCBzYW1wbGluZyBvbiB0aGUgdHJhaW5pbmcgZGF0YXNldCB3aGVuIGRvaW5nIGZlYXR1cmUgdmFsdWUgaXRlcmF0aW9uLgpSYXRoZXIgdGhhbiBzZWFyY2hpbmcgc3BsaXQgb24gZnVsbCB0cmFpbmluZyBzZXQgaXQgb25seSBzZWFyY2hlcyBvbiBhIHNldCBvZiBib29zdHJhcHBlZCBzYW1wbGVzLgpTb21lIG1vZGVsIHZhcmlhbnRzIGV2ZW4gcGVyZm9ybSBvbmx5IGEgc21hbGwgbnVtYmVyIG9mIHJhbmRvbSBzcGxpdHMgKGluc3RlYWQgb2YgZmluZGluZyB0aGUgYmVzdCBzcGxpdCB3aXRoIGEgZnVsbCBzY2FuKSBvbiB0aGUgZmVhdHVyZSB2YWx1ZSBpdGVyYXRpb24gcGhhc2UuCgpBbHRob3VnaCBpdCBpcyBjbGFpbWVkIGluIHRoZW9yeSB0aGF0IFJGIGlzIHN0cm9uZyBhZ2FpbnN0IG92ZXItZml0dGluZyBkdWUgdG8gYm90aCBmZWF0dXJlIChjb2x1bW4pIHN1YnNhbXBsaW5nIGFuZCBib29zdHJhcCAocm93KSBzYW1wbGluZywKaW4gcmVhbGl0eSBSRiBjYW4gc3RpbGwgZWFzaWx5IG92ZXItZml0IHdpdGhvdXQgcHJvcGVyIHR1bm5pbmcuCgpPbmUgb2J2aW91cyBhZHZhbnRhZ2Ugb2YgUkYsCmluIHRlcm1zIG9mIGNvbXB1dGF0aW9uYWwgZWZmaWNpZW5jeSwKaXMgdGhhdCBpdCBpcyBbZW1iYXJyYXNzaW5nbHkgcGFyYWxsZWxdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0VtYmFycmFzc2luZ2x5X3BhcmFsbGVsKS4KTW9zdCBtb2Rlcm4gaW1wbGVtZW50YXRpb25zIG9mIFJGIHN1cHBvcnRzIHNpbmdsZS1tYWNoaW5lIGNwdSBwYXJhbGxlbCBvciBldmVuIGRpc3RyaWJ1dGVkIGNvbXB1dGluZy4KCiMjIyBCb29zdGluZyBFbnNlbWJsZXMKCkFub3RoZXIgZW5zZW1ibGluZyB0ZWNobmlxdWUgd2hpY2ggaGFzIGJlZW4gcHJvdmVuIHRvIGJlIGV2ZW4gbW9yZSBwb3dlciBlbXBpcmljYWxseSBpcyBib29zdGluZy4KV2UgaGF2ZSBhbHJlYWR5IGRpc2N1c3NlZCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGJvb3N0aW5nIGFuZCBncmFkaWVudCBib29zdGluZy4KSGVyZSB3ZSBzcGVjaWZpY2FsbHkgcmVmZXIgdG8gZ3JhZGllbnQgYm9vc3Rpbmcgc2luY2UgaXQgdXN1YWxseSBnaXZlcyBiZXR0ZXIgcmVzdWx0cyBhbmQgaXQgaXMgb3VyIG1haW4gdG9waWMgaW4gdGhpcyBub3RlYm9vay4KClVubGlrZSBSRiB3aGljaCB0cmFpbnMgbXVsdGlwbGUgdHJlZXMgaW5kZXBlbmRlbnRseSBhbmQgYWdncmVnYXRlcyB0aGUgcmVzdWx0cywKZ3JhZGllbnQgYm9vc3RpbmcgdHJlZXMgKEdCVCBoZXJlYWZ0ZXIpIGFkb3B0IGFuIGFkZGl0aXZlIHRyYWluaW5nIHByb2NlZHVyZSwKc28gZWFjaCB0cmVlIGlzIGl0ZXJhdGl2ZWx5IGFkZGVkIGludG8gdGhlIG1vZGVsLgoKU2ltaWxhciB0byBSRiBvciBhbnkgb3RoZXIgdHJlZSBlbnNlbWJsaW5nLAplYWNoIGluZGl2aWR1YWwgdHJlZSBpcyBhIHZlcnkgd2VhayBsZWFybmVyLgpCdXQgam9pbnRseSB0aGV5IGJlY29tZSB2ZXJ5IGdvb2QgYXQgZ2VuZXJhbGl6YXRpb24uCkJvb3N0aW5nIHRyZWVzIGluZGVlZCBoYXZlIGFjaGlldmVkIHF1aXRlIHNvbWUgb2YgdGhlIHN0YXRlLW9mLXRoZS1hcnQgcGVyZm9ybWFuY2UgaW4gcmVhbCB3b3JsZCBhcHBsaWNhdGlvbnMuCgpFdmVuIHRob3VnaCBHQlQgaGFzIGJlZW4gaW50cm9kdWNlZCBpbiB0aGUgbGl0ZXJhdHVyZSBmb3IgYWxtb3N0IDIwIHllYXJzIGFscmVhZHksCml0IGlzIHRoZSByZWNlbnQgNSB5ZWFycyB0aGF0IHNldmVyYWwgdmVyeSBwb3dlcmZ1bCBvcGVuIHNvdXJjZSBpbXBsZW1lbnRhdGlvbnMgaGF2ZSBlbWVyZ2VkIHRvIHJlYWxseSBzaGFwZSB0aGUgcHJhY3RpdGlvbmVycycgY2hvaWNlIG9mIHRvb2xib3guCkluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMgd2UnZCBsaWtlIHRvIGRlZXAtZGl2ZSBpbnRvIHR3byBvZiB0aGVtOgpgeGdib29zdGAgYW5kIGBsaWdodGdibWAuCldlIHdpbGwgZm9jdXMgZXNwZWNpYWxseSBvbiB0aGVpciBub3ZlbHRpZXMgaW4gYmV0dGVyaW5nIEdCVCB0byB0aGUgZXh0cmVtZS4KCiMgWEdCb29zdAoKQENoZW4yMDE2IG9wZW4gc291cmNlIHRoZSBbYHhnYm9vc3RgXShodHRwczovL2dpdGh1Yi5jb20vZG1sYy94Z2Jvb3N0KSBwYWNrYWdlLApwcm9iYWJseSBvbmUgb2YgdGhlIG1vc3QgaW5mbHVlbnRpYWwgR0JUIGZyYW1ld29yayB0byBkYXRlLgpJdCBhbHNvIGNvbWVzIHdpdGggdGhlIG1vc3QgdmFyaWV0eSBvZiBsYW5nYXVnZSBBUEkgc3VwcG9ydDoKUHl0aG9uLCBSLCBKdWxpYSwgSmF2YSwgLi4uLCBldGMuCkl0IGlzIGFsc28gbW9yZSBvciBsZXNzIGludGVncmF0ZWQgd2l0aCBkaXN0cmlidXRlZCBjb21wdXRpbmcgcGxhdGZvcm0gc3VjaCBhcyBTcGFyaywKYW5kIGNsb3VkIHNvbHV0aW9uIHN1Y2ggYXMgR0NQIGFuZCBBV1MuCgpgYGB7ciB4Z2JfdmVyfQpwcmludChwYWNrYWdlVmVyc2lvbigieGdib29zdCIpKQpgYGAKCiMjIFJlZ3VsYXJpemF0aW9uIG9uIEltcHVyaXR5Cgo+IFRoZSBuYW1lIHhnYm9vc3QsIHRob3VnaCwgYWN0dWFsbHkgcmVmZXJzIHRvIHRoZSBlbmdpbmVlcmluZyBnb2FsIHRvIHB1c2ggdGhlIGxpbWl0IG9mIGNvbXB1dGF0aW9ucyByZXNvdXJjZXMgZm9yIGJvb3N0ZWQgdHJlZSBhbGdvcml0aG1zLgpXaGljaCBpcyB0aGUgcmVhc29uIHdoeSBtYW55IHBlb3BsZSB1c2UgeGdib29zdC4KRm9yIG1vZGVsLCBpdCBtaWdodCBiZSBtb3JlIHN1aXRhYmxlIHRvIGJlIGNhbGxlZCBhcyByZWd1bGFyaXplZCBncmFkaWVudCBib29zdGluZy4KXltRdW90ZWQgZnJvbSB0aGUgY3JlYXRvciBvZiB4Z2Jvb3N0OiBodHRwczovL3d3dy5xdW9yYS5jb20vV2hhdC1pcy10aGUtZGlmZmVyZW5jZS1iZXR3ZWVuLXRoZS1SLWdibS1ncmFkaWVudC1ib29zdGluZy1tYWNoaW5lLWFuZC14Z2Jvb3N0LWV4dHJlbWUtZ3JhZGllbnQtYm9vc3RpbmddCgpPbmUgaW1wb3J0YW50IGFuZCBub3ZlbCBmZWF0dXJlIG9mIFhHQm9vc3QgaXMgdG8gaW50cm9kdWNlIHJlZ3VsYXJpemF0aW9uIGluIHRoZSBpbXB1cml0eSBtZWFzdXJlLgpCZWZvcmUgdGhpcywKbW9zdCB0cmVlIGVuc2VtYmxlcyBmb2N1cyBvbiB1c2luZyB0cmFkaXRpb25hbCB3YXkgb2YgcmVndWxhcml6YXRpb24sCnN1Y2ggYXMgcHJ1bmluZywgc3Vic2FtcGxpbmcsIG9yIGxpbWl0aW5nIG51bWJlciBvZiBsZWF2ZXMuCgpJbiBgeGdib29zdGAgdGhlIGZvcm11bGF0aW9uIG9mIHRoZSBvYmplY3RpdmUgZnVuY3Rpb24gbWFrZXMgaXRzIHRyZWUgZW5zZW1ibGVzIGV4dHJlbWVseSBmbGV4aWJsZS4KTG9zcyBjYW4gYmUgcGx1Zy1hbmQtcGxheWVkIGFzIHdlbGwgYXMgZXZhbHVhdGlvbiBtZXRyaWNzLgpJbiBnZW5lcmFsLAp0aGUgbW9kZWwgaGFzIHRoZSBmb2xsb3dpbmcgY29zdCBmdW5jdGlvbjoKCiQkClx0ZXh0e0Nvc3R9ID0gXHN1bV97aT0xfV5uIEwoeV9pLCBcaGF0e3lfaX0pICsKXGZyYWN7MX17Mn1cbGFtYmRhXHZlcnRcdmVydCBXIFx2ZXJ0XHZlcnReMiArClxhbHBoYVx2ZXJ0XHZlcnQgV1x2ZXJ0XHZlcnQsCiQkCgp3aGVyZSAkTCh5X2ksIFxoYXR7eV9pfSkkIGlzIGEgcGVyLWluc3RhbmNlIGxvc3MsCiRXJCBpcyB0aGUgd2VpZ2h0cyBvZiBhbGwgdHJlZSBsZWF2ZXMsCiRcbGFtYmRhJCBhbmQgJFxhbHBoYSQgYXJlIEwyIGFuZCBMMSByZWd1bGFyaXphdGlvbiB0ZXJtLApyZXNwZWN0aXZlbHkuCgooQnkgZGVmYXVsdCBgeGdib29zdGAgaGFzICRcbGFtYmRhID0gMSQgYW5kICRcYWxwaGEgPSAwJC4pCgpUaGUgY29zdCBmdW5jdGlvbiBpbmRlZWQgbG9va3Mgbm8gZGlmZmVyZW50IHRoYW4gYW55IG90aGVyIGRpZmZlcmVudGlhYmxlIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwgd2UgYWxyZWFkeSBrbm93LgpXaGF0IG1ha2VzIGl0IG5vdmVsIGlzIHRoYXQgYHhnYm9vc3RgIHVzZXMgdGhlIGNvc3QgYXMgdGhlIGltcHVyaXR5IG1lYXN1cmUgdG8gaW1wbGVtZW50IHRoZSBzcGxpdCBmaW5kaW5nIGFsZ29yaXRobSBpbiBhIHRyZWUsCndoaWNoIGlzIG5vdCBkaWZmZXJlbnRpYWJsZS4KQnV0IGhvdz8KCiMjIEFkZGl0aXZlIFRyYWluaW5nCgpGb3IgY29udmVuaWVuY2UgbGV0J3MgcmUtc3RhdGUgdGhlIGdyYWRpZW50IGJvb3N0aW5nIG1vZGVsIGZ1bmN0aW9uIGhlcmUsCndpdGggYSBiaXQgbW9yZSBkZXRhaWxlZCBub3RhdGlvbnM6CgokJApcYmVnaW57YWxpZ25lZH0KXGhhdHt5fV57KGspfSA9IEZfayh4KQomPSBcc3VtX2sgZl9rKHgpIFxcCiY9IFxzdW1fe3Q9MX1ee2stMX1mX3QoeCkgKyBmX2soeCksClxlbmR7YWxpZ25lZH0KJCQKCndoZXJlICRcaGF0e3l9Xnsoayl9JCBpcyB0aGUgbW9kZWwgcHJlZGljdGlvbiBmb3IgbW9kZWwgYXQgcm91bmQgJGskLgoKRGVmaW5lIHRoZSBsb3NzIGZ1bmN0aW9uICh3aXRob3V0IHJlZ3VsYXJpemF0aW9uIHRlcm0gZm9yIG5vdyBmb3Igc2ltcGxpY2l0eSkgYXQgcm91bmQgJGskIGZvciAkaSA9IDEsIC4uLiwgbiQgZXhhbXBsZXMgdG8gYmU6CgokJApcYmVnaW57YWxpZ25lZH0KXHRleHR7TG9zc30gJj0gXHN1bV97aT0xfV5uIEwoeV9pLCBcaGF0e3lfaX1eeyhrKX0pIFxcCiY9IFxzdW1fe2k9MX1ebiBMKHlfaSwgXGhhdHt5X2l9Xnsoay0xKX0gKyBmX2soeF9pKSkuClxlbmR7YWxpZ25lZH0KJCQKCkhlcmUgJFxoYXR7eV9pfV57KGstMSl9JCBpcyBwdXJlbHkgZGV0ZXJtaW5lZCBieSBtb2RlbCBhbHJlYWR5IHRyYWluZWQgaW4gdGhlIHByZXZpb3VzIHJvdW5kLAp3ZSBvbmx5IGNhcmUgYWJvdXQgcGlja2luZyB1cCBhIHRyZWUgbW9kZWwgJGZfayh4X2kpJCB3aGljaCBjYW4gZnVydGhlciByZWR1Y2UgdGhlIGxvc3MgdGhlIG1vc3QuCgpXaGVuIHRoZSBsb3NzIGlzIGEgc3F1YXJlZCBlcnJvciwKdGhlIGZ1bmN0aW9uIGlzIHNvbWV3aGF0IHRyYWNrYWJsZToKCiQkClxiZWdpbnthbGlnbmVkfQpcdGV4dHtNU0UtTG9zc30gJj0gXGZyYWN7MX17bn1cc3VtX3tpPTF9Xm4gKHlfaSAtIFxoYXR7eV9pfV57KGspfSleMiBcXAomPSBcZnJhY3sxfXtufVxzdW1fe2k9MX1ebiBcYmlnZ1sKXHVuZGVyYnJhY2V7KHlfaSAtIFxoYXR7eX1eeyhrLTEpfSleMn1fXHRleHR7Q29uc3RhbnR9Ci0gMih5X2kgLSBcaGF0e3lfaX1eeyhrLTEpfSlmX2soeF9pKQorIGZfayh4X2kpXjJcYmlnZ10gXFwKJj0gXGZyYWN7MX17bn1cc3VtX3tpPTF9Xm4gXGJpZ2dbCi0gMlx1bmRlcmJyYWNleyh5X2kgLSBcaGF0e3lfaX1eeyhrLTEpfX1fXHRleHR7UmVzaWR1YWwgYXQgay0xfSlmX2soeF9pKQorIGZfayh4X2kpXjJcYmlnZ10gKyBcdGV4dHtDb25zdGFudH0uClxlbmR7YWxpZ25lZH0KJCQKCkJ1dCB3aGVuIHRoZSBsb3NzIGlzLCBzYXksIGEgY3Jvc3MtZW50cm9weSBsb3NzICh3aGljaCBpcyBmYWlybHkgY29tbW9uKSwKdGhlIGZ1bmN0aW9uIGJlY29tZXMgbXVjaCBoYXJkZXIgdG8gZGVhbCB3aXRoLgoKIyMgU2Vjb25kLU9yZGVyIExvc3MgQXBwcm94aW1hdGlvbgoKSW4gb3JkZXIgdG8gYWxsb3cgYSBnZW5lcmFsIHBsdWctYW5kLXBsYXkgZnJhbWV3b3JrIGZvciBkaWZmZXJlbnQgbG9zcyBmdW5jdGlvbnMsCmluc3RlYWQgb2Ygc29sdmluZyB0aGUgZXhhY3QgZnVuY3Rpb25hbCBmb3JtIG9mIGEgcGFydGljdWxhciBsb3NzLApgeGdib29zdGAgYXBwcm94aW1hdGVzIHRoZSBsb3NzIHdpdGggYSBzZWNvbmQtb3JkZXIgW1RheWxvciBzZXJpZXNdKChodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9UYXlsb3Jfc2VyaWVzKSkgZXhwYW5zaW9uLgoKVGhlIDJuZC1vcmRlciBleHBhbnNpb24gb2YgYSBmdW5jdGlvbiAkTCh4KSQgYXQgcG9pbnQgJHhfMCQgY2FuIGJlIHdyaXR0ZW4gYXM6CgokJApMKHgpIFxhcHByb3ggTCh4XzApICsgTCcoeF8wKSh4IC0geF8wKSArIFxmcmFjezF9ezJ9TCcnKHhfMCkoeCAtIHhfMCleMi4KJCQKCk5vdyBkZWZpbmU6CgokJAp4IC0geF8wID0gYSwKJCQKCndlIHRoZW4gaGF2ZToKCiQkCkwoeF8wICsgYSkgXGFwcHJveCBMKHhfMCkgKyBMJyh4XzApYSArIFxmcmFjezF9ezJ9TCcnKHhfMClhXjIsCiQkCgpvciBzaW1wbHk6CgokJApMKHggKyBhKSBcYXBwcm94IEwoeCkgKyBMJyh4KWEgKyBcZnJhY3sxfXsyfUwnJyh4KWFeMi4KJCQKClVzZSB0aGUgZm9ybSB3ZSBjYW4gcmUtd3JpdGUgb3VyIGxvc3MgZnVuY3Rpb246CgokJApcYmVnaW57YWxpZ25lZH0KXHRleHR7TG9zc30KJj0gXHN1bV97aT0xfV5uIEwoeV9pLCBcaGF0e3lfaX1eeyhrKX0pIFxcCiY9IFxzdW1fe2k9MX1ebgpMKHlfaSwgXGhhdHt5X2l9Xnsoay0xKX0gKyBmX2soeF9pKSksIFxcClx0ZXh0e0FwcHJveC4gTG9zc30gJj0gXHN1bV97aT0xfV5uClxCaWdnWwpcdW5kZXJicmFjZXsKXHZwaGFudG9te1xmcmFje1xwYXJ0aWFsIEwoeV9pLCBcaGF0e3lfaX1eeyhrLTEpfSl9e1xwYXJ0aWFsIFxoYXR7eV9pfV57KGstMSl9fX0KTCh5X2ksIFxoYXR7eV9pfV57KGstMSl9KX1fXHRleHR7Q29uc3RhbnR9ICsKXHVuZGVyYnJhY2V7XGZyYWN7XHBhcnRpYWwgTCh5X2ksIFxoYXR7eV9pfV57KGstMSl9KX17XHBhcnRpYWwgXGhhdHt5X2l9Xnsoay0xKX19fV97Z19pfWZfayh4X2kpICsKXHVuZGVyYnJhY2V7XGZyYWN7XHBhcnRpYWxeMiBMKHlfaSwgXGhhdHt5X2l9Xnsoay0xKX0pfXtccGFydGlhbF4yIFxoYXR7eV9pfV57KGstMSl9fX1fe2hfaX1mX2soeF9pKV4yClxCaWdnXSBcXAomPSBcc3VtX3tpPTF9Xm4KXEJpZ2dbCmdfaWZfayh4X2kpICsKaF9pZl9rKHhfaSleMgpcQmlnZ10gKyBcdGV4dHtDb25zdGFudH0uClxlbmR7YWxpZ25lZH0KJCQKCldlIGZvbGxvdyB0aGUgYXV0aG9yJ3Mgbm90YXRpb24gb24gdGhlIGZpcnN0IG9yZGVyIHRlcm0gYXMgJGdfaSQgKGcgZm9yIGdyYWRpZW50KSBhbmQgdGhlIHNlY29uZCBvcmRlciB0ZXJtIGFzICRoX2kkIChoIGZvciBbSGVzc2lhbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvSGVzc2lhbl9tYXRyaXgpKS4KVG9nZXRoZXIgd2l0aCB0aGUgcmVndWxhcml6YXRpb24gdGVybSBpdCBiZWNvbWVzIHRoZSBxdWFsaXR5IG9mIHNwbGl0IG1lYXN1cmUgd2hpY2ggdWx0aW1hdGVseSBndWlkZXMgdGhlIG1vZGVsIHRvIHBpY2sgdXAgdGhlIG5leHQgbGVhcm5lciAkZl9rJCBhdCByb3VuZCAkayQuCgpFc3NlbnRpYWxseSBYR0Jvb3N0IGVtcGxveXMgYSBmdW5jdGlvbmFsIGdyYWRpZW50IGRlc2NlbnQgdXAgdG8gdGhlIHNlY29uZC1vcmRlciBkZXJpdmF0aXZlLgpUaGlzIG1ha2VzIGl0IG1vcmUgcHJlY2lzZSBpbiBmaW5kaW5nIHRoZSBuZXh0IGJlc3QgbGVhcm5lci4KCk5vdGUgdGhhdCB0aGUgdmFsdWUgb2YgJGdfaSQgYW5kICRoX2kkIGJvdGggcmVtYWluIHRoZSBzYW1lIGZvciB0aGUgY3VycmVudCByb3VuZCwKbWVhbmluZyB0aGF0IHRoZSBjb21wdXRpbmcgY29zdCBpcyBsYXJnZWx5IHJlZHVjZWQgd2hlbiB3ZSBhcmUgZXZhbHVhdGluZyBhbW9uZyBkaWZmZXJlbnQgdHJlZSBzcGxpdHMuClRoaXMgaXMgb25lIG9mIHRoZSByZWFzb24gd2h5IFhHQm9vc3QgaXMgc28gbXVjaCBmYXN0ZXIgdGhhbiBhbnkgb3RoZXIgdmFuaWxsYSBpbXBsZW1lbnRhdGlvbiBvZiBHQlQuCgojIyBUcmVlIFNwbGl0IE9wdGltaXphdGlvbgoKTm93IGxldCdzIGZvcm1hbGx5IHBhcmFtZXRlcml6ZSBhIHRyZWUgbW9kZWwgJGZfayh4X2kpJCB3aXRoICRUJCB0ZXJtaW5hbCBub2RlcyAobGVhdmVzKSwKJHdfaiQgZGVub3RlcyB0aGUgd2VpZ2h0IGZvciAkaiQtdGggbGVhZiwKYW5kICRJX2okIHRoZSBzZXQgb2YgZXhhbXBsZXMgYXNzaWduZWQgdG8gbGVhZiAkaiQuClRoZSBhcHByb3hpbWF0ZWQgbG9zcyAod2l0aCByZWd1bGFyaXphdGlvbiB0ZXJtIHRoaXMgdGltZSkgYXQgcm91bmQgJGskIGNhbiB0aGVuIGJlIHJlLXdyaXR0ZW4gaW4gYSAqZ3JvdXAtYnktbGVhZiogc3VtbWF0aW9uOgoKJCQKXGJlZ2lue2FsaWduZWR9CkNvc3ReayAmPQpcc3VtX3tpPTF9Xm4KXEJpZ2dbCmdfaWZfayh4X2kpICsgaF9pZl9rKHhfaSleMgpcQmlnZ10gKwpcZnJhY3sxfXsyfVxsYW1iZGFcdmVydFx2ZXJ0IFcgXHZlcnRcdmVydF4yICsKXGFscGhhXHZlcnRcdmVydCBXXHZlcnRcdmVydCBcXAomPSBcc3VtX3tqPTF9XlQKXEJpZ2dbClxzdW1fe2kgXGluIElfan0gZ19pd19qICsgXGZyYWN7MX17Mn1cc3VtX3tpIFxpbiBJX2p9aF9pd19qXjIKXEJpZ2ddICsKXGZyYWN7MX17Mn1cbGFtYmRhXHN1bV97aj0xfV5UIHdfal4yICsgXGFscGhhXHN1bV97aj0xfV5UIHdfaiBcXAomPSBcc3VtX3tqPTF9XlQKXEJpZ2dbClxzdW1fe2kgXGluIElfan0gZ19pd19qICsgXGZyYWN7MX17Mn0oXHN1bV97aSBcaW4gSV9qfWhfaSArIFxsYW1iZGEpd19qXjIgKyBcYWxwaGFcdmVydCB3X2pcdmVydApcQmlnZ10uClxlbmR7YWxpZ25lZH0KJCQKCkdpdmVuIHRoZSBjb3N0IGZ1bmN0aW9uIHdlIGNhbiBzb2x2ZSBmb3Igb3B0aW1hbCB3ZWlnaHQgJHdfal4qJCBtaW5pbWl6aW5nIHRoZSBjb3N0IHdpdGggYSBmaXJzdC1vcmRlci1jb25kaXRpb246CgokJApcZnJhY3tccGFydGlhbCBDb3N0Xmt9e1xwYXJ0aWFsIHdfan0gPSAwLAokJAoKd2hpY2ggZ2l2ZXM6CgokJApcc3VtX3tpIFxpbiBJX2p9IGdfaSArIChcc3VtX3tpIFxpbiBJX2p9aF9pICsgXGxhbWJkYSl3X2ogKyBcYWxwaGFcZnJhY3t3X2p9e1x2ZXJ0IHdfalx2ZXJ0fSA9IDAuCiQkCgpGb3IgYSBudWxsIEwxIHBlbmFsdHkgKCRcYWxwaGEgPSAwJCkgdGhlIHNvbHV0aW9uIGlzIHNpbXBseToKCiQkClxiZWdpbnthbGlnbmVkfQp3X2peKiAmPSAtIFxmcmFje1xzdW1fe2kgXGluIElfan0gZ19pfXtcc3VtX3tpIFxpbiBJX2p9aF9pICsgXGxhbWJkYX0sIFxcCkNvc3Ree2sqfSAmPSAtIFxmcmFjezF9ezJ9IFxzdW1fe2o9MX1eVCBcZnJhY3tcYmlnKFxzdW1fe2kgXGluIElfan0gZ19pXGJpZyleMn17XHN1bV97aSBcaW4gSV9qfWhfaSArIFxsYW1iZGF9LgpcZW5ke2FsaWduZWR9CiQkCgpOb3cgd2UgaGF2ZSBzb2x2ZWQgdGhlIHdlaWdodHMgdGhhdCBtaW5pbWl6ZSB0aGUgY29zdCBvbmNlICRUJCBpcyBkZXRlcm1pbmVkLgpXZSB3aWxsIGFsc28gbmVlZCB0aGUgb3B0aW1pemVkIGNvc3QgdG8gZGV0ZXJtaW5lIHRoZSBiZXN0IHNwbGl0LgpUaGF0IGlzLAp0aGUgcXVhbGl0eSBvZiBzcGxpdCBtZWFzdXJlLgpUaGUgYmVzdCBzcGxpdCBzaG91bGQgcmVzdWx0IGluIHR3byBub2RlcyBzdWNoIHRoYXQgdGhlIHRvdGFsIGNvc3Qgb2YgdHdvIG5vZGVzIGlzIHJlZHVjZWQgdGhlIG1vc3QgZnJvbSB0aGF0IG9mIHRoZWlyIHBhcmVudCBub2RlLgoKQSBsZWFybmluZyByYXRlIGNhbiBiZSBhcHBsaWVkIHRvIHRoZSBvcHRpbWFsIHdlaWdodHMgYXQgZWFjaCBpdGVyYXRpb24sCm1ha2luZyB0aGUgZW5zZW1ibGVyIG1vcmUgY29uc2VydmF0aXZlIGluIHVwZGF0aW5nIGl0c2VsZi4KCkhlcmUgaXMgYSBwc2V1ZG8gY29kZSBkZXNjcmliaW5nIHRoZSBjb3JlIGFsZ29yaXRobToKCmBgYApbWEdCb29zdCBBZGRpdGl2ZSBUcmFpbmluZyBBbGdvcml0aG1dCgpGOiBpbml0aWFsIG1vZGVsCk06IHRvdGFsIG51bWJlciBvZiBmZWF0dXJlcwpOOiB0b3RhbCBudW1iZXIgb2YgZXhhbXBsZXMKSzogdG90YWwgbnVtYmVyIG9mIGl0ZXJhdGlvbnMgKHRyZWUgbW9kZWxzKQpUOiB0b3RhbCBudW1iZXIgb2YgbGVhdmVzCmdfaWs6IGdfaSBmb3IgZXhhbXBsZSBpIGF0IHJvdW5kIGsKaF9pazogaF9pIGZvciBleGFtcGxlIGkgYXQgcm91bmQgawpnX2lqazogZ19pIGZvciBleGFtcGxlIGkgYXQgcm91bmQgayBhc3NpZ25lZCB0byBsZWFmIGoKaF9pams6IGhfaSBmb3IgZXhhbXBsZSBpIGF0IHJvdW5kIGsgYXNzaWduZWQgdG8gbGVhZiBqCmNvc3Q6IHRoZSBzY29yZSBhdCBsZWFmIGosIHN1bShnX2lqayleMiAvIChzdW0oaF9pamspICsgbGFtYmRhKQp4X2ltOiB0aGUgaS10aCBleGFtcGxlIHZhbHVlIG9uIGZlYXR1cmUgbQoKZm9yIHJvdW5kIGsgaW4gMSB0byBLOgogIHdoaWxlIG51bWJlciBvZiBzcGxpdCA8IFQ6CiAgICBjb21wdXRlIGdfaWssIGhfaWsgZm9yIGFsbCBpCiAgICBmb3IgZmVhdHVyZSBqIGluIDEgdG8gbToKICAgICAgc29ydCB4X2oKICAgICAgZm9yIGV4YW1wbGUgeF9pbSBpbiBpID0gMSB0byBuIG9uIGZlYXR1cmUgbToKICAgICAgICBzcGxpdCBieSB4X2ltIGludG8gbGVmdCBhbmQgcmlnaHQgbGVhdmVzCiAgICAgICAgY29tcHV0ZSBzdW0gb2YgY29zdCBvbiBsZWZ0IGFuZCByaWdodCBsZWF2ZXMKICAgIGZpbmQgYmVzdCBzcGxpdCB1c2luZyBmZWF0dXJlIG0qIGF0IHhfaW0qIGFzc29jaWF0ZWQgd2l0aCBtaW5pbXVtIGNvc3QKICB1cGRhdGUgbW9kZWwgRiA8LSBGICsgdHJlZSBsZWFybmVkCmBgYAoKIyMjIENvbW1vbiBMb3NzIEZ1bmN0aW9ucwoKYHhnYm9vc3RgIGFscmVhZHkgY29tZXMgd2l0aCBzZXZlcmFsIGxvc3MgZnVuY3Rpb25zIGJ1aWx0LWluLgpXZSBjYW4gYWxzbyBkZWZpbmUgb3VyIGN1c3RvbSBsb3NzIGVhc2lseSBieSBqdXN0IGltcGxlbWVudGluZyB0aGUgZmlyc3QgKCRnJCkgYW5kIHNlY29uZCBvcmRlciAoJGgkKSBncmFkaWVudHMgb2YgdGhhdCBsb3NzIHcuci50LiBtb2RlbCBvdXRwdXQuCkhlcmUgd2Ugc2ltcGx5IHJldmlldyB0aGUgdHdvIG1vc3RseSB1c2VkIGxvc3MgZnVuY3Rpb25zLgoKIyMjIyBTcXVhcmVkIExvc3Mgey19CgpEZXJpdmluZyAkZyQgYW5kICRoJCBmb3IgYSBzcXVhcmVkIGxvc3MgaXMgdHJpdmlhbDoKCiQkClxiZWdpbnthbGlnbmVkfQpnX2kgJj0gXGZyYWN7XHBhcnRpYWwoeV9pIC0gXGhhdHt5fV9pXnsoay0xKX0pXjJ9e1xwYXJ0aWFsXGhhdHt5fV9pXnsoay0xKX19ID0gMihcaGF0e3l9X2leeyhrLTEpfSAtIHlfaSksIFxcCmhfaSAmPSBcZnJhY3tccGFydGlhbF4yKHlfaSAtIFxoYXR7eX1eeyhrLTEpfSleMn17XHBhcnRpYWxeMlxoYXR7eX1eeyhrLTEpfX0gPSAyLgpcZW5ke2FsaWduZWR9CiQkCgpXZSBjYW4gaWdub3JlIGNvbnN0YW50IHNvIHRoZSBncmFkaWVudCAkZ19pJCBpcyBzaW1wbHkgJFxoYXR7eX0gLSB5JCBhbmQgdGhlIGhlc3NpYW4gaXMgdW5pdHkuCgpMZXQncyB1c2UgUidzICpzeW1ib2xpYyBkZXJpdmF0aXZlcyogdG8gdmVyaWZ5IG91ciBkZXJpdmF0aW9uOgoKYGBge3Igc3ltYm9saWNfZGVyaXZfc3F1YXJlZF9sb3NzfQpkeTJ5IDwtIGRlcml2Myh+ICh5IC0geWhhdCleMiwgYygieWhhdCIpKQoKeWhhdCA8LSBybm9ybSg1KQp5IDwtIHJub3JtKDUpCgoyKih5aGF0IC0geSkgICMgR3JhZGllbnQgYnkgaGFuZHMuCgpldmFsKGR5MnkpICAjIEJ5IHN5bWJvbGljIGRlcml2YXRpdmVzLgpgYGAKCiMjIyMgQ3Jvc3MtRW50cm9weSBMb3NzIHstfQoKQSBzdWJ0bGUgZGV0YWlscyBtdXN0IGJlIHRha2VuIGNhcmUgZm9yIGNyb3NzLWVudHJvcHkgbG9zcy4KSW50dWl0aXZlbHkgd2UnZCBsaWtlIHRvIGp1c3QgdGFrZSB0aGUgZGVyaXZhdGl2ZSB3LnIudC4gdGhlIG1vZGVsIG91dHB1dCBwcm9iYWJpbGl0eToKCiQkClxiZWdpbnthbGlnbmVkfQpnX2kgJj0gXGZyYWN7XHBhcnRpYWxcYmlnWy0geV9pXGxuXGhhdHt5fV9pXnsoay0xKX0gLSAoMSAtIHlfaSlcbG4oMSAtIFxoYXR7eX1faV57KGstMSl9KVxiaWddfQp7XHBhcnRpYWxcaGF0e3l9X2leeyhrLTEpfX0gPQpcZnJhY3tcaGF0e3l9X2leeyhrLTEpfSAtIHlfaX17KDEgLSBcaGF0e3l9X2leeyhrLTEpfSlcaGF0e3l9X2leeyhrLTEpfX0sIFxcCmhfaSAmPSBcZnJhY3tccGFydGlhbF4yXGJpZ1stIHlfaVxsblxoYXR7eX1faV57KGstMSl9IC0gKDEgLSB5X2kpXGxuKDEgLSBcaGF0e3l9X2leeyhrLTEpfSlcYmlnXX0Ke1xwYXJ0aWFsXjJcaGF0e3l9X2leeyhrLTEpfX0gPSBcZnJhY3sxIC0geV9pfXsoMSAtIFxoYXR7eX1faV57KGstMSl9KV4yfSArIFxmcmFje3lfaX17KFxoYXR7eX1faV57KGstMSl9KV4yfS4KXGVuZHthbGlnbmVkfQokJAoKQWdhaW4gdXNlIHN5bWJvbGljIGRpZmZlcmVudGlhdGlvbiB0byB2ZXJpZnkgb3VyIGRlcml2YXRpb246CgpgYGB7ciBzeW1ib2xpY19kZXJpdl9sb2dfbG9zc30KIyBEZW1vbnN0cmF0ZSBzeW1ib2xpYyBkZXJpdmF0aXZlcy4KZHEycSA8LSBkZXJpdjMofiAtICh5KmxvZyhxKSArICgxLXkpKmxvZygxIC0gcSkpLCBjKCJxIikpCnkgPC0gc2FtcGxlKGMoMCwgMSksIDUsIHJlcGxhY2U9VFJVRSkKcSA8LSBydW5pZig1KQoKKDEgLSB5KS8oMSAtIHEpIC0geSAvIHEgICMgR3JhZGllbnQgYnkgaGFuZHMuCgooMSAtIHkpLygxLXEpXjIgKyB5IC8gcV4yICAjIEhlc3NpYW4gYnkgaGFuZHMuCgpldmFsKGRxMnEpICAjIEJ5IHN5bWJvbGljIGRlcml2YXRpdmVzLgpgYGAKClRoZSBhYm92ZSBkZXJpdmF0aW9uIGFzc3VtZXMgJFxoYXR7eX0kIGlzIHRoZSBzaWdtb2lkIG1vZGVsIG91dHB1dCAocHJvYmFiaWxpdHkpLgpXaGlsZSBhY3R1YWxseSBpbiBgeGdib29zdGAncyBpbXBsZW1lbnRhdGlvbiBmb3IgbG9naXN0aWMgcmVncmVzc2lvbiB0aGUgdGVybSAkXGhhdHt5fSQgaXMgKmJlZm9yZSogc2lnbW9pZCB0cmFuc2Zvcm1hdGlvbiAod2hhdCB0aGUgYXV0aGVyIHJlZmVycyB0byBhcyAqbWFyZ2luKikuClNvIHRoZSBtb2RlbCB3ZWlnaHRzIGFyZSByYXcgc2NvcmVzICgqbG9naXRzKikgaW5zdGVhZCBvZiBwcm9iYWJpbGl0aWVzLgpUaGlzIG1ha2VzIHNlbnNlIHNpbmNlIHRoZSBtb2RlbCBpcyBhZGRpdGl2ZS4KSXQgaXMgYmV0dGVyIHRvIGRvIHRoZSBhZGQgb3BlcmF0aW9uIGJlZm9yZSB0aGUgZmluYWwgc2lnbW9pZCB0cmFuc2Zvcm1hdGlvbiBvZiB0aGUgZW5zZW1ibGVyLgpUaGF0IGlzLAplYWNoIHRyZWUgcHJlZGljdHMgcmF3IHNjb3JlcyBhbmQgdGhlIGZpbmFsIG1vZGVsIGVuc2VtYmxlIGlzIHRoZSBzaWdtb2lkIG9mIHRoZSBzdW0gb2YgYWxsIHRoZSByYXcgc2NvcmVzIChmcm9tIHRoZSBhc3NpZ25lZCBsZWF2ZXMpLgpUaGlzIGFsc28gbGFyZ2VseSBzaW1wbGlmaWVzIHRoZSBkZXJpdmF0aXZlcyBiZWNhdXNlIG5vdyB3aGF0IHdlIHdpbGwgYmUgZG9pbmcgaXM6CgokJApnX2kgPSBcZnJhY3tccGFydGlhbCBMfXtccGFydGlhbCB0fVxmcmFje1xwYXJ0aWFsIHR9e1xwYXJ0aWFsXGhhdHt5fX0sCiQkCgp3aGVyZSAkdCQgaXMgdGhlIHNpZ21vaWQgJHQoXGhhdHt5fSkgPSBcZnJhY3sxfXsxICsgXGV4cCgtXGhhdHt5fSl9JC4KCkJhc2VkIG9uIHRoZSBmYWN0IHRoYXQgJFxmcmFje1xwYXJ0aWFsIHR9e1xwYXJ0aWFsIFxoYXR7eX19ID0gdCgxIC0gdCkkLAp3ZSBmaW5hbGx5IGhhdmUgKHNpbXBseWluZyAkXGhhdHt5fV9pXnsoay0xKX0kIGFzICRcaGF0e3l9JCB0byBzYXZlIHR5cGluZ3MpOgoKJCQKXGJlZ2lue2FsaWduZWR9CmdfaSAmPSBcZnJhY3t0IC0geV9pfXt0KDEgLSB0KX0gXHRpbWVzIHQoMS10KSBcXAomPSB0KFxoYXR7eX0pIC0geV9pLCBcXApoX2kgJj0gdChcaGF0e3l9KSgxIC0gdChcaGF0e3l9KSkuClxlbmR7YWxpZ25lZH0KJCQKCiMjIyBFeGVyY2lzZTogSW1wbGVtZW50IGEgU2luZ2xlIFhHQiBTcGxpdAoKVG8gdmVyaWZ5IG91ciB1bmRlcnN0YW5kaW5nLAphcyBhbiBleGVyY2lzZSB3ZSBidWlsdCBhIHNpbmdsZSB0cmVlIHdpdGgganVzdCBvbmUgc3BsaXQgKGEgKmRlY2lzaW9uIHN0dW1wKikuCkFuZCB3ZSBjb21hcHJlIHRoZSByZXN1bHQgb2Ygb3VyIGltcGxlbWVudGF0aW9uIHRvIHRoYXQgb2YgYHhnYm9vc3RgIHRvIHNlZSBpZiB0aGV5IGFncmVlIHdpdGggZWFjaCBvdGhlci4KCmBgYHtyIHhnYl9vbmVfc3BsaXRfZnJvbV9zY3JhdGNofQpsYW1iZCA8LSAxICAjIEwyIHJlZ3VsYXJpemF0aW9uLgphbGxfZmVhdHVyZXMgPC0gYygieDEiLCAieDIiKQoKY29zdCA8LSBmdW5jdGlvbihkZikgewogICMgVGhlIFhHQiBvcHRpbWFsIGNvc3QgYmFzZWQgb24gMm5kLW9yZGVyIFRheWxvciBleHBhbnNpb24uCiAgLSBzdW0oZGYkZyleMiAvICgyKihzdW0oZGYkaCkgKyBsYW1iZCkpCn0KCmNvc3Rfb25fc3BsaXQgPC0gZnVuY3Rpb24oRCwgc3BsaXRfaW5kKSB7CiAgIyBTcGxpdCB0aGUgZGF0YSBpbnRvIGxlZnQgYW5kIHJpZ2h0IGNoaWxkIG5vZGVzLgogIERMIDwtIERbMTpzcGxpdF9pbmQsIC4oZywgaCldCiAgRFIgPC0gRFsoc3BsaXRfaW5kICsgMSk6bnJvdyhEKSwgLihnLCBoKV0KICBjb3N0KERMKSArIGNvc3QoRFIpCn0KCmZlYXR1cmVfaXRlcmF0aW9uIDwtIGZ1bmN0aW9uKEQsIGFsbF9mZWF0dXJlcykgewogIGZlYXR1cmVfYmVzdF9jb3N0cyA8LSBsaXN0KCkKICBiZXN0X3NwbGl0X2luZCA8LSBsaXN0KCkKICBiZXN0X3NwbGl0X3ZhbCA8LSBsaXN0KCkKICBmb3IgKCB4IGluIGFsbF9mZWF0dXJlcyApIHsKICAgICMgU29ydCBleGFtcGxlcyBieSB4LgogICAgc2V0b3JkZXJ2KEQsIHgpCgogICAgIyBGZWF0dXJlIHZhbHVlIGl0ZXJhdGlvbi4KICAgIHNwbGl0X2Nvc3QgPC0gcmVwKE5BX3JlYWxfLCBucm93KEQpIC0gMSkKICAgIGZvciAoIGkgaW4gc2VxX2xlbihucm93KEQpIC0gMSkgKSB7CiAgICAgIHNwbGl0X2Nvc3RbaV0gPC0gY29zdF9vbl9zcGxpdChELCBpKQogICAgfQogICAgZmVhdHVyZV9iZXN0X2Nvc3RzW1t4XV0gPC0gbWluKHNwbGl0X2Nvc3QpCiAgICBiZXN0X3NwbGl0X2luZFtbeF1dIDwtIHdoaWNoLm1pbihzcGxpdF9jb3N0KQogICAgYmVzdF9zcGxpdF92YWxbW3hdXSA8LSBEW1t4XV1bYmVzdF9zcGxpdF9pbmRbW3hdXV0KICB9CgogICMgRGV0ZXJtaW5lIHdoaWNoIGZlYXR1cmUgZ2FpbnMgdGhlIGxhcmdlc3QgcmVkdWN0aW9uIGluIGNvc3QuCiAgc3BsaXRfY29sIDwtIG5hbWVzKHdoaWNoLm1pbihmZWF0dXJlX2Jlc3RfY29zdHMpKQogIGxpc3Qoc3BsaXRfY29sPXNwbGl0X2NvbCwKICAgICAgIHNwbGl0X2luZD1iZXN0X3NwbGl0X2luZFtbc3BsaXRfY29sXV0sCiAgICAgICBzcGxpdF92YWw9YmVzdF9zcGxpdF92YWxbW3NwbGl0X2NvbF1dKQp9CgoKeSA8LSBEJHlbIWlzX3Rlc3RdCmR5MnkgPC0gZGVyaXYzKH4gKHkgLSB5aGF0KV4yLCBjKCJ5aGF0IikpCnloYXQgPC0gLjUgICMgVGhpcyBpcyB0aGUgaW5pdGlhbCBwcmVkaWN0aW9uIHVzZWQgYnkgeGdib29zdC4KZHkgPC0gZXZhbChkeTJ5KQpEIDwtIERbIWlzX3Rlc3QsIGc6PWF0dHIoZHksICJncmFkaWVudCIpWywieWhhdCJdXQpEIDwtIERbIWlzX3Rlc3QsIGg6PWF0dHIoZHksICJoZXNzaWFuIilbLCJ5aGF0IiwieWhhdCJdXQoKIyBGZWF0dXJlIGl0ZXJhdGlvbiBvbiB0aGUgcm9vdCBzcGxpdC4KciA8LSBmZWF0dXJlX2l0ZXJhdGlvbihEWyFpc190ZXN0XSwgYWxsX2ZlYXR1cmVzKQpwcmludChyKQpgYGAKCmBgYHtyIHhnYl90cmVlX2xlYXJuZXJfdmVyaWZ5fQojIFZlcmlmeSB0aGUgcmVzdWx0IHdpdGggYSByZWFsIHhnYm9vc3QgaW1wbGVtZW50YXRpb24uCmJzdDMgPC0geGdiLnRyYWluKHBhcmFtcz1saXN0KG9iamVjdGl2ZT0icmVnOnNxdWFyZWRlcnJvciIsIG1heF9kZXB0aD0xKSwKICAgICAgICAgICAgICAgICAgZGF0YT1EeCwgbnJvdW5kPTEpCnhnYi5tb2RlbC5kdC50cmVlKG1vZGVsPWJzdDMpCmBgYAoKU28gdGhlIG9wdGltYWwgc3BsaXQgYXQgcm9vdCBub2RlIGZyb20gb3VyIHRveSBpbXBsZW1lbnRhdGlvbiBhZ3JlZXMgd2l0aCB0aGF0IG9mIGB4Z2Jvb3N0YC4KTm90ZSB0aGF0IHRoZSBzcGxpdCB2YWx1ZSByZXBvcnRlZCBieSBgeGdib29zdGAgZG9lcyBub3QgY29ycmVzcG9uZCB0byBhbiBhY3R1YWwgdmFsdWUgaW4gdGhlIGZlYXR1cmUuCkl0IGlzIGEgdmlydHVhbCB2YWx1ZSBqdXN0IGZvciBzcGxpdHRpbmcgcHVycG9zZS4KSW5kZWVkIGl0IGlzIHRoZSBhdmVyYWdlIHZhbHVlIGJldHdlZW4gdGhlIGNsb3Nlc3QgdHdvIHZhbHVlcyBhdCB0aGUgc3BsaXQuClRvIGNoZWNrIHRoaXM6CgpgYGB7ciBjaGVja19zcGxpdF92YWx9CkQyIDwtIERbIWlzX3Rlc3RdCnNldG9yZGVyKEQyLCB4MSkKcHJpbnQobWVhbihEMltyJHNwbGl0X2luZDoociRzcGxpdF9pbmQgKyAxKSwgeDFdKSkKYGBgCgojIyMgUGFyYWxsZWxpemF0aW9uCgpVbmxpa2UgYmFnZ2luZyB0cmVlcyB3aGljaCBhcmUgZW1iYXJhc3NpbmdseSBwYXJhbGxlbCwKYm9vc3RpbmcgdHJlZXMgYXJlIHRyYWluZWQgaXRlcmF0aXZlbHksCndoaWNoIG1ha2VzIHRoZW0gaGFyZGVyIHRvIHBhcmFsbGVsaXplLgpXZSBjYW4sIGhvd2V2ZXIsIHN0aWxsIHBhcmFsbGVsaXplIHRoZSB0cmFpbmluZyBwcm9jZXNzLApub3QgYXQgdHJlZS1sZXZlbCBidXQgYXQgZmVhdHVyZS1sZXZlbC4KClNpbmNlIHRoZSBhbGdvcml0aG0gbmVlZHMgdG8gdHJhdmVyc2Ugb3ZlciBhbGwgKG9yIGEgc3Vic2V0IG9mKSBmZWF0dXJlcyB0byBmaW5kIHRoZSBiZXN0IHNwbGl0LApmZWF0dXJlIGl0ZXJhdGlvbiBpcyByZWFkaWx5IHBhcmFsbGVsYWJsZS4KQWxsIG1vZGVybiBHQlQgbGlicmFyaWVzIHN1cHBvcnQgdGhpcyBraW5kIG9mIHBhcmFsbGVsaXphdGlvbi4KCiMjIyBDb21wYXJpbmcgdG8gVmFuaWxsYSBHQlQKCkluIHZhbmlsbGEgZ3JhZGllbnQgYm9vc3RpbmcgYXQgZWFjaCBpdGVyYXRpb24gd2UgZml0IGEgdHJlZSB0byB0aGUgcHNldWRvIHJlc2lkdWFscywKaS5lLiwgbmVnYXRpdmUgZ3JhZGllbnQgb2YgbG9zcyBmdW5jdGlvbiB3LnIudC4gbW9kZWwgb3V0cHV0LgpUaGUgdHJlZSAob3IgYW55IG90aGVyIGJhc2UgbGVhcm5lcikgaXMgbGVhcm5pbmcgZnJvbSB0aGUgcHNldWRvIHJlc2lkdWFscyB3aXRoIGl0cyBvd24gbG9zcy4KU3VjaCBsb3NzIGlzIGxvY2FsIGFuZCBtYXkgbm90IHJlZmxlY3QgdGhlIGdsb2JhbCBvYmplY3RpdmUgd2Ugd2FudCB0byBvcHRpbWl6ZS4KWEdCb29zdCBpbmNvcnBvcmF0ZXMgdGhhdCBnbG9iYWwgb2JqZWN0aXZlICh3aXRoIHJlZ3VsYXJpemF0aW9uKSBpbnRvIGVhY2ggaXRlcmF0aW9uLAptYWtpbmcgdGhlIGJhc2UgbGVhcm5lciBtb3JlIHJlbGV2YW50IHRvIG91ciBlbmQgZ29hbC4KClRoZSBzZWNvbmQtb3JkZXIgYXBwcm94aW1hdGlvbiBvZiBnbG9iYWwgb2JqZWN0aXZlIGZ1cnRoZXIgcmVkdWNlcyB0aGUgY29tcHV0aW5nIGNvc3QgYnkgY29udmVydGluZyB0aGUgbG9zcyBjb21wdXRhdGlvbiBpbnRvIGEgZ3JvdXAtYnktbGVhZiBzdW0gb3BlcmF0aW9uLAp3aXRoIGFsbCBncmFkaWVudHMgcHJlLWNvbXB1dGVkIGF0IHRoZSBiZWdpbm5pbmcgb2YgZWFjaCBpdGVyYXRpb24uCgpOb3RpY2UgdGhhdCBpbiBhIHZhbmlsbGEgR0JUIHdlIG5lZWQgdG8gY2FsY3VsYXRlIHRoZSBncmFkaWVudHMgdG8gZ2V0IHRoZSBwc2V1ZG8tcmVzaWR1YWxzLAphbmQgdGhlIGJhc2UgbGVhcm5lcnMgbmVlZCB0byBjYWxjdWxhdGUgdGhlIGdyYWRpZW50cyBvZiB0aGVpciBvd24gb2JqZWN0aXZlIGZ1bmN0aW9uIChvciBjYWxjdWxhdGUgdGhlIGltcHVyaXR5IG1lYXN1cmUpIGluIG9yZGVyIHRvIGRvIG9wdGltaXphdGlvbi4KWEdCb29zdCBoZW5jZSBpcyBtdWNoIGZhc3RlciB0aGFuIHZhbmlsbGEgR0JUIGluIHRyYWluaW5nLgoKIyMgQXBwcm94aW1hdGVkIFRyZWUgU3BsaXQKClRoZSBleGFjdCBzcGxpdCBmaW5kaW5nIGFsZ29yaXRobSBtYXkgbm90IGJlIHNjYWxhYmxlIGluIGxhcmdlIGFwcGxpY2F0aW9ucy4KU28gaW5zdGVhZCBgeGdib29zdGAgc3VwcG9ydHMgYW4gYXBwcm94aW1hdGVkIHNwbGl0IGFsZ29yaXRobSBkZXNjcmliZWQgaW4gdGhlIGZvbGxvd2luZyBwc2V1ZG8gY29kZToKXltBcHByb3hpbWF0ZWQgc3BsaXQgc2VhcmNoIGlzIG5vdCBwYXJ0aWN1bGFybHkgbm92ZWwgaW4gWEdCb29zdC4KSXQgaGFzIGJlZW4gd2VsbCBzdHVkaWVkIGluIG90aGVyIGFjYWRlbWljIHdvcmtzLl0KCmBgYApbQXBwcm94aW1hdGVkIFRyZWUgU3BsaXQgQWxnb3JpdGhtXQoKbTogdG90YWwgbnVtYmVyIG9mIGZlYXR1cmVzCnM6IHNjb3JlIHRvIG1lYXN1cmUgdGhlIHF1YWxpdHkgb2YgYSBzcGxpdAplcHM6IHBlcmNlbnRpbGUgaW5jcmVtZW50LCBkaXZpZGluZyBmZWF0dXJlIHJvdWdobHkgaW50byAxL2VwcyBidWNrZXRzCnhfcGo6IHRoZSBwLXRoIGV4YW1wbGUgcGVyY2VudGlsZSB2YWx1ZSBvbiBmZWF0dXJlIGoKCmZvciBmZWF0dXJlIGogaW4gMSB0byBtOgogIGRpdmlkZWQgZmVhdHVyZSBqIGludG8gYnVja2V0cyBiYXNlZCBvbiBlcHMKICBhZ2dyZWdhdGUgcyBmb3IgZWFjaCBidWNrZXQKCmZvciBmZWF0dXJlIGogaW4gMSB0byBtOgogIGZvciBhbGwgdmFsdWUgeF9waiBvbiBmZWF0dXJlIGo6CiAgICBjb21wdXRlIHMgYmFzZWQgb24gYnVja2V0ZWQgYWdncmVnYXRpb24KCmRvIHNwbGl0IHVzaW5nIGoqIGF0IHhfcGoqIGFzc29jaWF0ZWQgd2l0aCBtYXhpbXVtIHMKYGBgCgpUaGUgaWRlYSBpcyB0byBzcGxpdCBvbmx5IGF0IHBlcmNlbnRpbGUgdmFsdWUsCmludHJvZHVjaW5nIG9uZSBhZGRpdGlvbmFsIHBhcmFtZXRlciBjYWxsZWQgYHNrZXRjaF9lcHNgIChkZWZhdWx0IGF0IDAuMDMgaW4gYHhnYm9vc3RgKSB0byBjb250cm9sIHRoZSBncmFudWxhcml0eSBvZiB0aGUgYnVja2V0cy4KU28gdGhlIG51bWJlciBvZiBzZWFyY2ggZm9yIGVhY2ggY29udGlub3VzIHZhcmlhYmxlIHdpbGwgYmUgY2FwcGVkIHJvdWdobHkgYXQgYDEvc2tldGNoX2Vwc2AgaW5zdGVhZCBvZiB0aGUgbnVtYmVyIG9mIGRpc3RpbmN0IHZhbHVlcywKd2hpY2ggY2FuIGJlIGh1Z2UuCgpCeSBkZWZhdWx0IGluIGB4Z2Jvb3N0YCB0aGUgYnVja2V0aXphdGlvbiBpcyBkb25lIG9ubHkgb25jZSBhdCB0aGUgbW9kZWwgaW5pdGlhbGl6YXRpb24gcGhhc2UgaW4gZWFjaCBpdGVyYXRpb24uCkFsbCBzdWJzZXF1ZW50IHNwbGl0cyBhcmUgcGVyZm9ybWVkIG9uIHRoZSBzYW1lIHNldCBvZiBidWNrZXRzIGZvciBlYWNoIGZlYXR1cmUuCkl0IGlzIGFsc28gcG9zc2libGUgdG8gcmUtcHJvcG9zZSBuZXcgYnVja2V0cyBhZnRlciBlYWNoIG9wdGltYWwgc3BsaXQsCmFzIHBvaW50ZWQgb3V0IGluIHRoZSBvcmlnaW5hbCBwYXBlciwKYnV0IHN1Y2ggY29udHJvbCBpcyBub3QgeWV0IG9wZW4gdG8gZW5kIHVzZXIgaW4gYHhnYm9vc3RgIGludGVyZmFjZSBhcyBvZiBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVZLSVtLSVkJylgLgoKTW9yZSBzcGVjaWZpY2FsbHksCmluIGB4Z2Jvb3N0YCB0aGUgcGFyYW1ldGVyIGB0cmVlX21ldGhvZGAgY29udHJvbHMgdGhlIHR5cGUgb2Ygc3BsaXR0aW5nIGFsZ29yaXRobToKCisgYHRyZWVfbWV0aG9kPSJleGFjdCJgOiBUaGUgZXhhY3Qgc3BsaXR0aW5nIGFsZ29yaXRobSBieSBkZWZhdWx0IHdoZW4gZGF0YXNldCBpcyBzbWFsbAorIGB0cmVlX21ldGhvZD0iYXBwcm94ImA6IFRoZSBhcHByb3hpbWF0ZWQgc3BsaXR0aW5nIGFsZ29yaXRobQorIGB0cmVlX21ldGhvZD0iaGlzdCJgOiBUaGUgaGlzdG9ncmFtIHRyZWUgYXBwcm9hY2ggKHJlZmVyIHRvIFtMaWdodEdCTSBzZWN0aW9uXSgjbGdiKSBmb3IgZGV0YWlscykKCiMjIFNwYXJzaXR5IEF3YXJlbmVzczogU3BsaXQgb24gTWlzc2luZwoKT25lIG5vdmVsdHkgaW4gYHhnYm9vc3RgIGlzIGl0cyBjYXBhYmlsaXR5IG9mIGRlYWxpbmcgd2l0aCBtaXNzaW5nIHZhbHVlcy4KSXQgc29sdmVzIHRoZSBtaXNzaW5nIHZhbHVlIHByb2JsZW0gYnkgbGVhcm5pbmcgYSBkZWZhdWx0IGJyYW5jaCAoZWl0aGVyIHRoZSBsZWZ0IG9yIHRoZSByaWdodCBjaGlsZCBub2RlKSBmb3IgZWFjaCBub2RlIGluIGEgc3BsaXQuCkR1cmluZyB0cmFpbmluZywKaW4gZWFjaCBub2RlIHRoZSBtaXNzaW5nIHZhbHVlIGNhbiBnbyB0byBlaXRoZXIgbGVmdCBvciByaWdodCBicmFuY2guClF1YWxpdHkgc2NvcmUgZm9yIGJvdGggYnJhbmNoZXMgYXJlIG5vdyBjb21wdXRlZCBiYXNlZCBvbiB0aGUgYXNzdW1wdGlvbiB0aGF0IG1pc3NpbmcgdmFsdWVzIHNob3VsZCBnbyB0byB0aGUgb3RoZXIgYnJhbmNoLgpJbiB0aGUgZW5kIHRoZSBzcGxpdCBhbGdvcml0aG0gcmV0dXJucyBub3Qgb25seSBvcHRpbWFsIHNwbGl0IGJ1dCBhbHNvIG9wdGltYWwgZGVmYXVsdCBicmFuY2ggZm9yIG1pc3NpbmcgdmFsdWUgYXQgZWFjaCBub2RlLgoKYHhnYm9vc3RgIGlzIGluZGVlZCBub3QgdGhlIGZpcnN0IHRyZWUgbW9kZWwgdG8gaGFuZGxlIG1pc3NpbmcgdmFsdWVzIGluIGl0cyBvd24gd2F5LgpGb3IgZXhhbXBsZSwKYHJwYXJ0YCBhbHNvIGhhbmRsZXMgbWlzc2luZyB2YWx1ZSBpbiBhIHBhcnRpY3VsYXIgbWFubmVyLgpJbiBgcnBhcnRgLAplYWNoIHNwbGl0IHdpbGwgY29tZSB3aXRoIGFkZGl0aW9uYWwgc3Vycm9nYXRlIHZhcmlhYmxlcyB0byBoYW5kbGUgY2FzZXMgd2hlcmUgdGhlIHNwbGl0IGZlYXR1cmUgdmFsdWUgaXMgbWlzc2luZyBhdCBpbmZlcmVuY2UgdGltZS4KQSBzdXJyb2dhdGUgdmFyaWFibGUgaXMgYSBmZWF0dXJlIG90aGVyIHRoYW4gdGhlIGJlc3Qgc3BsaXQgZmVhdHVyZSBidXQgdXNlZCB0byBjbGFzc2lmeSB0aGUgcmVzdWx0aW5nIGNoaWxkIG5vZGVzIHBhc3NpbmcgYSBtaW5pbXVtIHF1YWxpdHkgY3JpdGVyaW9uLgpGb3IgbW9yZSBkZXRhaWxzIHJlYWRlcnMgbWF5IHJlZmVyIHRvIEBycGFydC4KCkNvbXBhcmluZyB0byBgcnBhcnRgJ3Mgc3Vycm9nYXRlIHZhcmlhYmxlIGFwcHJvYWNoLAp3aGljaCBpbmNyZWFzZXMgY29tcHV0aW5nIGNvc3QsCmB4Z2Jvb3N0YCdzIGFwcHJvYWNoIGlzIG1vcmUgY29tcHV0YXRpb25hbGx5IGVmZmljaWVudCBhbmQgc2VlbXMgdG8gYWxzbyByZXN1bHQgaW4gYmV0dGVyIG1vZGVsIGFjY3VyYWN5LgoKIyMgUmFuZG9tIEZvcmVzdCB2aWEgWEdCb29zdAoKYHhnYm9vc3RgIGlzIGEgZ2VuZXJhbCBmcmFtZXdvcmsgZm9yIG1vZGVsIChlc3BlY2lhbGx5IHRyZWUpIGVuc2VtYmxlcnMuClNpbmNlIHRoZXJlIGlzIG5vIHJ1bGUgc3RvcHBpbmcgdXMgZnJvbSBidWlsZGluZyBtb3JlIHRoYW4gb25lIHRyZWUgYXQgZWFjaCBpdGVyYXRpb24sCndlIGNhbiBlZmZlY3RpdmVseSBncm93IG11bHRpcGxlIHRyZWVzIHdpdGggb25seSBvbmUgcm91bmQgdG8gdHJhaW4gYSByYW5kb20gZm9yZXN0IHdpdGggYHhnYm9vc3RgLgpFdmVuIG1vcmUsCndlIGNhbiB0cmFpbiBhICpib29zdGluZyByYW5kb20gZm9yZXN0KiBieSBncm93aW5nIG11bHRpcGxlIHRyZWVzIGFuZCB3aXRoIG11bHRpcGxlIGl0ZXJhdGlvbnMuClRoYXQgaXMsCndlIHVzZSBSRiBhcyBvdXIgYmFzZSBsZWFybmVyIGluIGEgZ3JhZGllbnQgYm9vc3RpbmcgbWFjaGluZS4KCiMgTGlnaHRHQk0geyNsZ2J9CgpAa2UyMDE3bGlnaHRnYm0gb3BlbiBzb3VyY2UgdGhlIFtgbGlnaHRnYm1gXShodHRwczovL2dpdGh1Yi5jb20vbWljcm9zb2Z0L0xpZ2h0R0JNKSBwYWNrYWdlLAp5ZXQgYW5vdGhlciBwb3dlcmZ1bCBHQlQgZnJhbWV3b3JrIHdpdGggaXRzIG93biByZW1hcmthYmxlIG5vdmVsIGltcGxlbWVudGF0aW9uLgpeW0FzIG9mIGByIGZvcm1hdChTeXMudGltZSgpLCAnJVktJW0tJWQnKWAgYGxpZ2h0Z2JtYCBpcyBub3QgeWV0IGF2YWlsYWJsZSBvbiBbQ1JBTl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcpIGZvciBpdHMgUiB3cmFwcGVyLgpQbGVhc2UgcmVmZXIgdG8gdGhlIFtvZmZpY2lhbCBpbnN0cnVjdGlvbl0oaHR0cHM6Ly9naXRodWIuY29tL21pY3Jvc29mdC9MaWdodEdCTS90cmVlL21hc3Rlci9SLXBhY2thZ2UpIHRvIGluc3RhbGwgaXQgc2VwYXJhdGVseS5dCgpgYGB7ciBpbXBvcnRfbGdiLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KGxpZ2h0Z2JtKQpwYWNrYWdlVmVyc2lvbigibGlnaHRnYm0iKQpgYGAKCkhlcmUgaXMgYSBzaW1wbGUgdHJhaW5pbmcgdGFzayB1c2luZyBgbGlnaHRnYm1gIG9uIG91ciBzaW11bGF0ZWQgZGF0YToKCmBgYHtyIGxnYl90cmVlX2xlYXJuZXJ9CkR4MiA8LSBsZ2IuRGF0YXNldChhcy5tYXRyaXgoRFshaXNfdGVzdCwgLih4MSwgeDIpXSksIGxhYmVsPURbIWlzX3Rlc3QsIHldKQpEdDIgPC0gbGdiLkRhdGFzZXQoYXMubWF0cml4KERbaXNfdGVzdCwgLih4MSwgeDIpXSksIGxhYmVsPURbaXNfdGVzdCwgeV0pCgojIFdlIHNldCB0aGUgcGFyYW1ldGVycyBzdWNoIHRoYXQgbGdiIGJlaGF2ZXMgbGlrZSB4Z2IncyBkZWZhdWx0IGZvciBjb21tb24gcGFyYW1ldGVycy4KbGdic3QgPC0gbGdiLnRyYWluKHBhcmFtcz1saXN0KG9iamVjdGl2ZT0icmVncmVzc2lvbiIsIGJvb3N0aW5nPSJnYmR0IiwgbGFtYmRhX2wyPTEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fc3VtX2hlc3NpYW5faW5fbGVhZj0xLCBtaW5fZGF0YV9pbl9sZWFmPTApLAogICAgICAgICAgICAgICAgICAgZGF0YT1EeDIsIHZhbGlkcz1saXN0KHRlc3Q9RHQyKSwKICAgICAgICAgICAgICAgICAgIG51bV9pdGVyYXRpb25zPTEwMCwgZWFybHlfc3RvcHBpbmdfcm91bmRzPTUsIHZlcmJvc2U9MCkKCnloYXQgPC0gcHJlZGljdChsZ2JzdCwgYXMubWF0cml4KERbaXNfdGVzdCwgLih4MSwgeDIpXSkpCm1zZShEW2lzX3Rlc3QsIHldLCB5aGF0KQpgYGAKCkluIGdlbmVyYWwgYGxpZ2h0Z2JtYCBydW5zIGV2ZW4gZmFzdGVyIHRoYW4gYHhnYm9vc3RgIGluIHRyYWluaW5nLApidXQgd2l0aCBjb21wYXJhYmxlIG1vZGVsIGFjY3VyYWN5LgpUaGlzIGlzIGFjaGlldmVkIGJ5IGEgY29tYmluYXRpb24gb2YgZGlmZmVyZW50IHN0cmF0ZWdpZXMgKHNvbWUgb2YgdGhlbSBub3ZlbCkuCldlIHdpbGwgd2Fsay10aHJvdWdoIGVhY2ggb2YgdGhlbSBpbiB0aGUgZm9sbG93aW5nIHNlY3Rpb25zLgoKIyMgSGlzdG9ncmFtIFRyZWUgKFF1YW50aXphdGlvbikKCmBsaWdodGdibWAgdXNlcyBoaXN0b2dyYW0tYmFzZWQgdHJlZSBzcGxpdCBhbGdvcml0aG0uCl5bSGlzdG9ncmFtIHRyZWUgaXMgbm90IHBhcnRpY3VsYXJseSBub3ZlbCBpbiBgbGlnaHRnYm1gLgpUaGUgYXBwcm9hY2ggaGFzIGJlZW4gcHJvcG9zZWQgZm9yIHF1aXRlIGxvbmcgdGltZSBpbiB0aGUgbGl0ZXJhdHVyZSBhbmQgdGhlcmUgYXJlIGFsc28gR0JUIGxpYnJhcmllcyBhbHJlYWR5IGltcGxlbWVudGVkIHRoZSBhcHByb2FjaC5dCgpJbnN0ZWFkIG9mIHNwbGl0dGluZyBhdCByYXcgZmVhdHVyZSB2YWx1ZXMgaGlzdG9ncmFtLWJhc2VkIGFwcHJvYWNoIGJ1Y2tldGl6ZXMgY29udGlub3VzIGZlYXR1cmVzIGludG8gZGlzY3JldGUgYmlucywKZm9ybWluZyBhIGxpbWl0ZWQgbnVtYmVyIG9mIGZlYXR1cmUgYmlucyAoaW4gYGxpZ2h0Z2JtYCB0aGlzIGlzIGRlZmF1bHQgYG1heF9iaW49MjU1YCkgYW5kIHRoZW4gc3BsaXQgb24gdGhvc2UgYmlucy4KVGhpcyBsYXJnZWx5IHJlZHVjZXMgdGhlIGNvbXBsZXhpdHkgZnJvbSAkTyhuKSQgdG8gJE8oXHRleHR7bWF4X2Jpbn0pJCBkdXJpbmcgc3BsaXQgZmluZGluZyBmb3IgYSBmZWF0dXJlLgpeW1RoZSBjb21wbGV4aXR5IG9mIGJ1aWxkaW5nIHRoZSBoaXN0b2dyYW0gaXRzZWxmIGlzIHN0aWxsICRPKG4pJCBidXQgdGhlIG9wZXJhdGlvbiBpcyBzaW1wbGUgYW5kIG5lZ2xpZ2libGUgY29tcGFyaW5nIHRvIHRoZSBzcGxpdCBmaW5kaW5nLl0KCiMjIyBHcmVlZHkgRXF1YWwtRGVuc2l0eSBCaW5uaW5nCgpUaGVyZSBhcmUgdHdvIHN0cmF0ZWdpZXMgaW4gYmlubmluZzoKZXF1YWwgYmluIGxlbmd0aCBbQGxpMjAwOG1jcmFua10gb3IgZXF1YWwgYmluIGRlbnNpdHkuCmBsaWdodGdibWAgYWRvcHRzIHRoZSBsYXR0ZXIuClNvIG51bWJlciBvZiBkYXRhIHBvaW50cyB3aWxsIGJlIHJvdWdobHkgdGhlIHNhbWUgZm9yIG1vc3QgYmlucy4KVGhpcyBlZmZlY3RpdmVseSB0cmFuc2Zvcm1zIGZlYXR1cmUgZGlzdHJpYnV0aW9uIGludG8gYSB1bmlmb3JtIG9uZSB3aGVuIGZpbmRpbmcgdGhlIGJlc3Qgc3BsaXQuCgpgbGlnaHRnYm1gIGRvZXNuJ3QgdXNlIHRoZSBlbnRpcmUgdHJhaW5pbmcgZGF0YXNldCB0byBjb25zdHJ1Y3QgdGhlIGJvdW5kYXJpZXMgb2YgYmlucy4KSW5zdGVhZCBpdCB1c2UgYSByYW5kb20gc3Vic2V0IChvbmx5IGFwcGxpY2FibGUgd2hlbiBkYXRhIHNpemUgaXMgaHVnZSBlbm91Z2gpLgpUaGlzIHJhbmRvbSBzYW1wbGUgc2l6ZSBjYW4gYmUgY29udHJvbGxlZCBieSBwYXJhbWV0ZXIgYGJpbl9jb25zdHJ1Y3Rfc2FtcGxlX2NudD0yMDAwMDBgLgpBZGRpdGlvbmFsbHksCmBtaW5fZGF0YV9pbl9iaW49M2AgY2FuIGJlIHVzZWQgdG8gY29udHJvbCBtaW5pbWFsIG51bWJlciBvZiBkYXRhIGluc2lkZSBvbmUgYmluLgoKTm90ZSB0aGF0IHRoaXMgYXBwcm9hY2ggaXMgZGlmZmVyZW50IGZyb20gdGhlIHBlcmNlbnRpbGUgc3BsaXR0aW5nIHdlJ3ZlIGRpc2N1c3NlZCBmb3IgYXBwcm94aW1hdGVkIHRyZWUgc3BsaXQgaW1wbGVtZW50ZWQgaW4gYHhnYm9vc3RgOgoKPiBUaGUgZXhpc3RpbmcgYXBwcm94aW1hdGUgc3BsaXR0aW5nIG1ldGhvZCBpbiBgeGdib29zdGAgYWxzbyBidWNrZXRzIGNvbnRpbnVvdXMgZmVhdHVyZXMgaW50byBkaXNjcmV0ZSBiaW5zIHRvIHNwZWVkIHVwIHRyYWluaW5nLgpUaGUgYGFwcHJveGAgbWV0aG9kIGdlbmVyYXRlcyBhIG5ldyBzZXQgb2YgYmlucyBmb3IgZWFjaCBpdGVyYXRpb24sCndoZXJlYXMgdGhlIGBoaXN0YCBtZXRob2QgcmUtdXNlcyB0aGUgYmlucyBvdmVyIG11bHRpcGxlIGl0ZXJhdGlvbnMuCl5baHR0cHM6Ly9naXRodWIuY29tL2RtbGMveGdib29zdC9pc3N1ZXMvMTk1MF0KCiMjIyBIaXN0b2dyYW0gU3VidHJhY3Rpb24gVHJpY2sKClRvIGNhbHVsYXRlIHRoZSBpbmZvcm1hdGlvbiBnYWluIGZyb20gYSBzcGxpdCB3ZSBuZWVkIHRvIGNhbGN1bGF0ZSB0aGUgY29zdCBmb3IgdGhlIHJlc3VsdGluZyB0d28gbm9kZXMuClJlbWVtYmVyIHRoYXQgYSBjb3N0IG9mIGEgc2luZ2xlIG5vZGUgJGokIGlzOgoKJCQKXHRleHR7Q29zdCBvZiBOb2RlIGp9ID0gLSBcZnJhY3sxfXsyfSBcZnJhY3tcYmlnKFxzdW1fe2kgXGluIElfan0gZ19pXGJpZyleMn17XHN1bV97aSBcaW4gSV9qfWhfaSArIFxsYW1iZGF9LgokJAoKQSBrZXkgb2JzZXJ2YXRpb24gaGVyZSBpcyB0aGF0IHdoYXQgd2UgbmVlZCBpcyBzaW1wbHkgc3VtbWF0aW9uIG9mIGdyYWRpZW50cyBpbnNpZGUgdGhlIG5vZGUuClNpbmNlIGFuIGV4YW1wbGUgaXMgYXNzaWduZWQgdG8gZWl0aGVyIGxlZnQgKCRJX2peTCQpIG9yIHJpZ2h0ICgkSV9qXlIkKSBjaGlsZCBhZnRlciBhIHNwbGl0LAphcHBhcmVudGx5IHdlIGhhdmU6CgokJApcYmVnaW57YWxpZ25lZH0KXHN1bV97aSBcaW4gSV9qfSBnX2kgJj0gXHN1bV97aSBcaW4gSV9qXkx9IGdfaSArIFxzdW1fe2kgXGluIElfal5SfSBnX2ksIFxcClxzdW1fe2kgXGluIElfan0gaF9pICY9IFxzdW1fe2kgXGluIElfal5MfSBoX2kgKyBcc3VtX3tpIFxpbiBJX2peUn0gaF9pLgpcZW5ke2FsaWduZWR9CiQkCgpUaGlzIG1lYW5zIHRoYXQgd2hlbiBjYWxjdWxhdGluZyB0aGUgdG90YWwgZ2FpbiBvZiBhIHNwbGl0LAp3ZSBvbmx5IG5lZWQgdG8gY2FsY3VsYXRlIHRoZSBjb3N0IG9mIG9uZSBub2RlLgpBbmQgdGhlIGNvc3Qgb2YgdGhlIG90aGVyIG5vZGUgY2FuIGJlIG9idGFpbmVkIGJ5IGZpcnN0IHN1YnN0cmFjdGluZyB0aGUgZ3JhZGllbnQgc3VtcyBmcm9tIHRoYXQgb2YgdGhlIHBhcmVudCwKdGhlbiBzcXVhcmluZyB0aGUgZmlyc3Qtb3JkZXIgZ3JhZGllbnQgc3VtIGJlZm9yZSBwbHVnaW5nIGludG8gdGhlIGNvc3QgZnVuY3Rpb24gdG8gYXJyaXZlIGF0IHRoZSBmaW5hbCBjb3N0IG9mIHRoZSBvdGhlciBub2RlLgpUbyBzYXZlIGV2ZW4gbW9yZSByZXNvdXJjZXMsCndlIGNhbiBhbHdheXMgY2hvb3NlIHRvIGNhbGN1bGF0ZSBvbiB0aGUgc21hbGxlciBjaGlsZCBub2RlLgpUaGlzIGlzIG9uZSBrZXkgZmFjdG9yIHdoeSBgbGlnaHRnYm1gIGNhbiBydW4gZXZlbiBmYXN0ZXIgdGhhbiBgeGdib29zdGAncyBkZWZhdWx0IG9yIHRoZSBhcHByb3hpbWF0ZWQgdHJlZSBtZXRob2QuCgojIyMgU3BlZWQtQWNjdXJhY3kgVHJhZGVvZmYKCkluIHRoZSBvcmlnaW5hbCBwYXBlciBpdCBpcyBkZW1vbnN0cmF0ZWQgd2l0aCBleHBlcmltZW50cyB0aGF0IGEgdG90YWwgbnVtYmVyIG9mIDI1NSBiaW5zIChwbHVzIG9uZSBiaW4gc3BlY2lmaWNhbGx5IGZvciB6ZXJvZXMpIGNhbiBwZXJmb3JtIHZlcnkgd2VsbC4KV2UgY2FuIGFsd2F5cyBjaG9vc2UgbW9yZSBiaW5zIGZvciBwb3RlbnRpYWxseSBiZXR0ZXIgYWNjdXJhY3ksCmJ1dCBhdCB0aGUgcHJpY2Ugb2Ygc2xvd2VyaW5nIGRvd24gdGhlIHRyYWluaW5nIHNwZWVkLgoKV2UgY2FuIHVzZSBhIHNpbmdsZSBkZWNpc2lvbiBzdHVtcCB0byBlYXNpbHkgc2VlIHRoZSBlZmZlY3Qgb2YgYG1heF9iaW5gLgpGaXJzdCBsZXQncyBydW4gd2l0aCB0aGUgZGVmYXVsdCBzZXR0aW5nOgoKYGBge3IgbGdiX2RlY2lzaW9uX3N0dW1wfQpsZ2JzdDIgPC0gbGdiLnRyYWluKHBhcmFtcz1saXN0KG9iamVjdGl2ZT0icmVncmVzc2lvbiIsIGJvb3N0aW5nPSJnYmR0IiwgbGFtYmRhX2wyPTEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX2xlYXZlcz0yKSwKICAgICAgICAgICAgICAgICAgICBkYXRhPUR4MiwgbnVtX2l0ZXJhdGlvbnM9MSwgdmVyYm9zZT0wKQoKbGdiLm1vZGVsLmR0LnRyZWUobW9kZWw9bGdic3QyKVtdCmBgYAoKVGhlIG9wdGltYWwgcm9vdCBzcGxpdCBjb2luY2lkZSB3aXRoIGB4Z2Jvb3N0YCBhbmQgYWxzbyBvdXIgdG95IGltcGxlbWVudGF0aW9uLgpOb3RlIHRoYXQgd2UgaGF2ZSBmYXIgbW9yZSB0aGFuIGBtYXhfYmluPTI1NWAgZGlzdGluY3QgdmFsdWVzIGluIG91ciBzcGxpdHRpbmcgdmFyaWFibGU6CgpgYGB7ciBkaXN0aW5jdF9jb3VudH0KZGlzdGluY3RfY250IDwtIHVuaXF1ZU4oRFshaXNfdGVzdCwgeDFdKQpwcmludChkaXN0aW5jdF9jbnQpCmBgYAoKSW5zdGVhZCBvZiBwZXJmb3JtaW5nIGByIGRpc3RpbmN0X2NudCAtIDFgIHNwbGl0cyBMaWdodEdCTSBvbmx5IHBlcmZvcm1zIDI1NSBzcGxpdHMgdG8gbG9jYXRlIHRoZSBzYW1lIG9wdGltYWwgc3BsaXQuCgpOb3cgbGV0J3MgcmVkdWNlIHRoZSBtYXhpbWFsIG51bWJlciBvZiBiaW5zIHRvIGEgcmF0aGVyIHNtYWxsIG51bWJlcjoKCmBgYHtyIGxnYl9kZWNpc2lvbl9zdHVtcF9sYXJnZV9iaW59CiMgV2UgbmVlZCB0byByZS1jb25zdHJ1Y3QgdGhlIGRhdGFzZXQgc2luY2UgbGdiIGNhY2hlcyB0aGUgYmlucyBpbiBkYXRhc2V0LgpEeDIgPC0gbGdiLkRhdGFzZXQoYXMubWF0cml4KERbIWlzX3Rlc3QsIC4oeDEsIHgyKV0pLCBsYWJlbD1EWyFpc190ZXN0LCB5XSkKbGdic3QzIDwtIGxnYi50cmFpbihwYXJhbXM9bGlzdChvYmplY3RpdmU9InJlZ3Jlc3Npb24iLCBib29zdGluZz0iZ2JkdCIsIGxhbWJkYV9sMj0xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9sZWF2ZXM9MiwgbWF4X2Jpbj0xMCksCiAgICAgICAgICAgICAgICAgICAgZGF0YT1EeDIsIG51bV9pdGVyYXRpb25zPTEsIHZlcmJvc2U9MCkKCmxnYi5tb2RlbC5kdC50cmVlKG1vZGVsPWxnYnN0MylbXQpgYGAKCkJ5IGxvd2VyaW5nIGRvd24gYG1heF9iaW5gIG5vdyB3ZSBlbmQgdXAgd2l0aCBhIHN1Yi1vcHRpbWFsIHNwbGl0LAp3aXRoIHNtYWxsZXIgZ2FpbiBmb3IgdGhlIHNwbGl0LgoKIyMjIEhhbmRsaW5nIFNwYXJzaXR5CgpIaXN0b2dyYW0gdHJlZSBjYW5ub3QgYmUgZnVsbHkgb3B0aW1pemVkIGZvciBzcGFyc2UgZGF0YSB0aG91Z2guClNpbmNlIG5vIG1hdHRlciBhIGZlYXR1cmUgdmFsdWUgaXMgemVybyBvciBub3QsCml0cyBiaW4gdmFsdWUgbmVlZHMgdG8gYmUgYWNjZXNzZWQuCkFzIGEgcmVzdWx0LAp0byBzcGVlZCB1cCB0cmFpbmluZyBydW50aW1lIHRoZXJlIG11c3QgYmUgb3RoZXIgc3RyYXRlZ2llcyB0YXJnZXRlZCBhdCBzcGFyc2l0eSBvcHRpbWl6YXRpb24uCgojIyBHcmFkaWVudC1CYXNlZCBPbmUtU2lkZSBTYW1wbGluZwoKVG8gcmVkdWNlIHRoZSB0cmFpbmluZyB0aW1lIG9uZSBpZGVhIGlzIHRvIHJlZHVjZSBudW1iZXIgb2Ygc3BsaXRzIGluIGZpbmRpbmcgdGhlIGJlc3Qgc3BsaXQuCldpdGhvdXQgYW55IGFwcHJveGltYXRpb24gb3IgcXVhbnRpemF0aW9uLApmb3IgZWFjaCBmZWF0dXJlIHRoZSBhbGdvcml0aG0gbmVlZHMgdG8gdHJhdmVyc2Ugb3ZlciBhbGwgZGlzdGluY3QgZmVhdHVyZSB2YWx1ZXMgdG8gZmluZCB0aGUgYmVzdCBzcGxpdC4KVGhpcyBjYW4gYmUgcHJvaGliaXRpdmVseSBzbG93IGZvciBsYXJnZSBhcHBsaWNhdGlvbnMuCmB4Z2Jvb3N0YCB0YWNrbGVzIHRoaXMgd2l0aCBpdHMgYXBwcm94aW1hdGVkIHRyZWUgc3BsaXQgdXNpbmcgb25seSBwZXJjZW50aWxlcyBhcyBzcGxpdCB2YWx1ZS4KYGxpZ2h0Z2JtYCBoYXMgc2V2ZXJhbCBkaWZmZXJlbnQgc3RyYXRlZ2llcyBjb21iaW5lZCB0b2dldGhlci4KVGhlIGhpc3RvZ3JhbSB0cmVlIG1ldGhvZCB3ZSBqdXN0IGRpc2N1c3NlZCBpcyBvbmUgb2YgdGhlbS4KQW5vdGhlciBzdWNoIHN0cmF0ZWd5IGlzIGNhbGxlZCBHcmFkaWVudC1CYXNlZCBPbmUtU2lkZSBTYW1wbGluZyAoR09TUykuCgpXaGVuIGZpbmRpbmcgdGhlIGJlc3Qgc3BsaXQsCkdPU1MgZG93bi1zYW1wbGVzIHRoZSBleGFtcGxlcyB3aXRoIHRoZWlyIGFic29sdXRlIGdyYWRpZW50IHNpemVzIGFzIHNhbXBsaW5nIHdlaWdodHMuCkl0IGlzIGJhc2VkIG9uIHRoZSBmYWN0IHRoYXQgZXhhbXBsZXMgd2l0aCBzbWFsbGVyIGdyYWRpZW50cyBnZW5lcmFsbHkgbWVhbnMgdGhleSBhcmUgYWxyZWFkeSB3ZWxsLXRyYWluZWQgKGFuZCBoZW5jZSB0aGUgZXJyb3IgaXMgc21hbGxlcikuCkV4YW1wbGVzIHdpdGggbGFyZ2VyIGdyYWRpZW50cywKaW5zdGVhZCwKc2hvdWxkIGJlIHRoZSBmb2N1cyBmb3IgdGhlIG1vZGVsIHRvIGltcG9ydmUgZnVydGhlci4KVGhlIGNvbmNlcHQgaXMgYm9ycm93ZWQgZnJvbSBBZGFCb29zdCwKd2hlcmUgZXhhbXBsZXMgYXJlIHJlLXdlaWdodGVkIGF0IGVhY2ggYm9vc3Rpbmcgcm91bmQgdG8gZ3VpZGUgdGhlIG1vZGVsIG9uIGxlYXJuaW5nIHRoZSBoYXJkZXIgZXhhbXBsZXMuCgpHT1NTIGtlZXBzIGV4YW1wbGVzIHdpdGggbGFyZ2UgZ3JhZGllbnRzIGFuZCBvbmx5IGRvIHNhbXBsaW5nIG9uIGV4YW1wbGVzIHdpdGggc21hbGwgZ3JhZGllbnRzLgpTaW5jZSB0aGlzIHdpbGwgc2NyZXcgdXAgdGhlIGRpc3RyaWJ1dGlvbiwKd2hpY2ggaW4gdHVybiBodXJ0cyB0aGUgbW9kZWwncyBhYmlsaXR5IHRvIGxlYXJuLApHT1NTIGNvbXBlbnNhdGVzIHN1Y2ggZWZmZWN0IGJ5IGFwcGx5aW5nIGEgY29uc3RhbnQgZmFjdG9yICRcZnJhY3sxIC0gYX17Yn0kIHRvIHNhbXBsZWQgZXhhbXBsZXMgd2l0aCBzbWFsbCBncmFkaWVudCB0byAicmUtd2VpZ2h0IiB0aGUgZGF0YSBiYWNrIHRvIGl0cyBvcmlnaW5hbCBkaXN0cmlidXRpb24uCkhlcmUgJGEkIGlzIHRoZSB0b3AgcGVyY2VudGFnZSBvZiBleGFtcGxlcyB0byBrZWVwLApzb3J0ZWQgZGVzY2VuZGluZ2x5IGJ5IGdyYWRpZW50cywKYW5kICRiJCB0aGUgc2FtcGxpbmcgcmF0ZSBmb3IgdGhlIHJlc3Qgb2YgdGhlIGV4YW1wbGVzICh3aXRoIHNtYWxsZXIgZ3JhZGllbnRzIHRoYW4gdGhvc2UgdG9wIG9uZXMpLgpUaGUgYXV0aG9ycyBpbiB0aGVpciBwYXBlciBwcm92aWRlIHRoZSB0aGVvcmV0aWNhbCBncm91bmQgdG8gc3VwcG9ydCB0aGF0IEdPU1MgY2FuIGFwcHJveGltYXRlIHdlbGwgd2hlbiB0aGUgZGF0YSBzaXplIGlzIGh1Z2UuCgpUbyB1c2UgR09TUyBpbiBgbGlnaHRnYm1gIHdlIG5lZWQgdG8gc3BlY2lmaXkgdGhlIHBhcmFtZXRlciBgYm9vc3Rpbmc9Imdvc3MiYC4KQnkgZGVmYXVsdCBgYm9vc3Rlcj0iZ2JkdCJgIGlzIGp1c3QgdGhlIG1vZGVybiBHQlQgd2l0aCBsZWFmLXdpc2UgZ3Jvd2luZyBoaXN0b2dyYW0gdHJlZXMuCgojIyBFeGNsdXNpdmUgRmVhdHVyZSBCdW5kbGluZwoKR09TUyBkZWFscyB3aXRoIGRhdGEgaW4gbG9uZyBzaXplLgpXaGF0IGFib3V0IHZlcnkgd2lkZSAoaGlnaCBkaW1lbnRpb25hbGl0eSkgZGF0YT8KSW4gc3VjaCBjYXNlLApgbGlnaHRnYm1gIGFkb3B0cyBhIHN0cmF0ZWd5IHRvIGdyb3VwIHRvZ2V0aGVyIGZlYXR1cmVzIHRoYXQgcmFyZWx5IGNvLWV4aXN0IGludG8gc2luZ2xlIGZlYXR1cmUgYW5kIGRvIHRoZSBzcGxpdCBzY2FuLgpUaGlzIGlzIHJlZmVycmVkIHRvIGFzIEV4Y2x1c2l2ZSBGZWF0dXJlIEJ1bmRsaW5nLCBvciBFRkIuCgpgbGlnaHRnYm1gIHBvc3R1bGF0ZXMgRUZCIGFzIGEgW2dyYXBoIGNvbG9yaW5nIHByb2JsZW1dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0dyYXBoX2NvbG9yaW5nKSBieSB0YWtpbmcgZmVhdHVyZXMgYXMgdmVydGljZXMgYW5kIGFkZGluZyBlZGdlcyBmb3IgZXZlcnkgdHdvIGZlYXR1cmVzIGlmIHRoZXkgYXJlIG5vdCBtdXR1YWxseSBleGNsdXNpdmUuClRvIHNwZWVkIHVwIGV2ZW4gbW9yZSwKaXQgaW50cm9kdWNlcyBhIGNlcnRhaW4gbGV2ZWwgb2YgInBvbGx1dGlvbiIgYnkgYWxsb3dpbmcgZmVhdHVyZXMgdGhhdCBhcmUgbm90IDEwMCUgbXV0dWFsbHkgZXhjbHVzaXZlIHRvIGJlIGFsc28gYnVuZGxlZCB0b2dldGhlci4KVGhpcyBjYW4gYmUgY29udHJvbGxlZCBieSBwYXJhbWV0ZXIgYG1heF9jb25mbGljdF9yYXRlPTBgLgpUaGUgaGlnaGVyIHRoZSByYXRlIHRoZSBtb3JlIGNvbXByb21pc2VzIGZvciB0aGUgbW9kZWwgYWNjdXJhY3kuCgpFRkIgY2FuIGJlIGVuYWJsZWQgYnkgcGFyYW1ldGVyIGBlbmFibGVfYnVuZGxlYCAoYW5kIGJ5IGRlZmF1bHQgaXQgaXMgb24gYWxyZWFkeSkuCgojIyBMZWFmLVdpc2UgVHJlZSBTcGxpdAoKTGVhZi13aXNlIHRyZWUgc3BsaXQgaXMgYWxzbyBjYWxsZWQgYmVzdC1maXJzdCBzcGxpdCBbQHNoaTIwMDdiZXN0XS4KQ2xhc3NpY2FsIHRyZWVzIHdpbGwgZ3JvdyB0aGVpciBub2RlcyBpbiBhIGRlcHRoLWZpcnN0IG9yZGVyLgpBdCBlYWNoIGxheWVyIGFsbCBub2RlcyBhcmUgc3BsaXR0ZWQgaW4gb3JkZXIgdG8gY29uc3RydWN0IHRoZSBuZXh0IGxheWVyLgpUaGUgZmluYWwgdHJlZSBsYXlvdXQgY2FuIGJlIGFzeW1tZXRyaWMgZHVlIHRvIHBvc3QtdHJhaW5pbmcgcHJ1bmluZyBvciBvdGhlciByZWd1bGFyaXphdGlvbiB0ZWNobmlxdWVzLApidXQgdGhlIGdyb3cgcG9saWN5IGlzIHRvIGFsd2F5cyBnbyBkZWVwZXIgZmlyc3QgZm9yIGVhY2ggbm9kZSBhdCB0aGUgc2FtZSBsZXZlbC4KCkEgbGVhZi13aXNlIGdyb3dpbmcgdHJlZSBkb2Vzbid0IGFsd2F5cyBzcGxpdCBldmVyeSBub2RlIGF0IHRoZSBzYW1lIGxheWVyIGRvd24gdG8gdGhlIG5leHQgbGF5ZXIuCkluc3RlYWQsCml0IGNob29zZXMgdGhlIG5vZGUgdGhhdCBjYW4gcmVzdWx0IGluIHRoZSBiZXN0IGdhaW4gZm9yIGV2ZXJ5IHNwbGl0LgpTaW5jZSBvbmUgb2YgdGhlIHJlc3VsdGluZyB0d28gYWRkaXRpb25hbCBub2RlcyBhdCBjdXJyZW50IHNwbGl0IG1heSByZXN1bHQgaW4gdGhlIG5leHQgYmVzdCBzcGxpdCwKdGhlIHRyZWUgbWF5IHdlbGwgZ3JvdyBob3Jpem9udGFsbHkgKGxlYWYtd2lzZSkgaW5zdGVhZCBvZiB2ZXJ0aWNhbGx5IChkZXB0aC13aXNlKS4KCkVtcHJpY2FsbHksCmEgc2luZ2xlIGxlYWYtd2lzZSBncm93aW5nIHRyZWUgdGVuZCB0byBvdmVyLWZpdCBtb3JlIHRoYW4gYSBkZXB0aC13aXNlIGdyb3dpbmcgdHJlZS4KQnV0IGFzIGEgd2VlayBsZWFybmVyICh3aXRoIHNoYWxsb3dlciBzdHJ1Y3R1cmUpIGluIGdyYWRpZW50IGJvb3N0aW5nIGl0IGNhbiBvdXRwZXJmb3JtIHRoZSBkZXB0aC13aXNlIGFwcHJvYWNoLgpUbyBmdXJ0aGVyIHJlZ3VsYXJpemUgdGhlIHRyZWUsCmBsaWdodGdibWAgYWxzbyBoYXMgYG1heF9kZXB0aGAgdG8gY29udHJvbCB0aGUgZGVwdGggb2YgdGhlIHRyZWUsCmV2ZW4gdGhvdWdoIGl0IGlzIGdyb3dpbmcgbGVhZi13aXNlLgoKIyMgT3B0aW1hbCBDYXRlZ29yaWNhbCBFbmNvZGluZwoKVXN1YWxseSB3ZSB1c2Ugb25lLWhvdCBlbmNvZGluZyB0byByZXByZXNlbnQgY2F0ZWdvcmljYWwgdmFyaWFibGVzLgpUbyBiZSBtZW1vcnktZnJpZW5kbHksCnN1Y2ggZGF0YSBpbnB1dCBpcyB1c3VhbGx5IGEgc3BhcnNlIG1hdHJpeC4KQnV0IHdoZW4gYSBjYXRlZ29yaWNhbCBpcyBoaWdobHkgc2tld2VkIChoaWdoIGNhcmRpbmFsaXR5KSwKaXQgd2lsbCBodXJ0IHRoZSBwZXJmb3JtYW5jZSBvZiBhIHRyZWUgbGVhcm5lci4KCkhlcmUgaXMgYSBkaXJlY3QgcXVvdGUgZnJvbSBgbGlnaHRnYm1gICdzIG9mZmljaWFsIGRvYzoKCj5JbnN0ZWFkIG9mIG9uZS1ob3QgZW5jb2RpbmcsCnRoZSBvcHRpbWFsIHNvbHV0aW9uIGlzIHRvIHNwbGl0IG9uIGEgY2F0ZWdvcmljYWwgZmVhdHVyZSBieSBwYXJ0aXRpb25pbmcgaXRzIGNhdGVnb3JpZXMgaW50byAyIHN1YnNldHMuCklmIHRoZSBmZWF0dXJlIGhhcyBrIGNhdGVnb3JpZXMsCnRoZXJlIGFyZSBgMl4oay0xKSAtIDFgIHBvc3NpYmxlIHBhcnRpdGlvbnMuCkJ1dCB0aGVyZSBpcyBhbiBlZmZpY2llbnQgc29sdXRpb24gZm9yIHJlZ3Jlc3Npb24gdHJlZXMuCkl0IG5lZWRzIGFib3V0IGBPKGsgKiBsb2coaykpYCB0byBmaW5kIHRoZSBvcHRpbWFsIHBhcnRpdGlvbi4KVGhlIGJhc2ljIGlkZWEgaXMgdG8gc29ydCB0aGUgY2F0ZWdvcmllcyBhY2NvcmRpbmcgdG8gdGhlIHRyYWluaW5nIG9iamVjdGl2ZSBhdCBlYWNoIHNwbGl0LiBNb3JlIHNwZWNpZmljYWxseSwKTGlnaHRHQk0gc29ydHMgdGhlIGhpc3RvZ3JhbSAoZm9yIGEgY2F0ZWdvcmljYWwgZmVhdHVyZSkgYWNjb3JkaW5nIHRvIGl0cyBhY2N1bXVsYXRlZCB2YWx1ZXMgKGBzdW1fZ3JhZGllbnRgIC8gYHN1bV9oZXNzaWFuYCkgYW5kIHRoZW4gZmluZHMgdGhlIGJlc3Qgc3BsaXQgb24gdGhlIHNvcnRlZCBoaXN0b2dyYW0uCgpSZWFkZXJzIGludGVyZXN0ZWQgY2FuIHJlZmVyIHRvIEBmaXNoZXIxOTU4Z3JvdXBpbmcgZm9yIHRoZSBkZXRhaWxzIG9mIHRoZSBzb2x1dGlvbiB0byBvcHRpbWFsIHBhcnRpdGlvbiBpbiBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlLgoKIyMgUGFyYWxsZWwgTGVhcm5pbmcKCldlJ3ZlIG1lbnRpb25lZCB0aGF0IEdCVCBpcyBhIGl0ZXJhdGl2ZSB0cmFpbmluZyBhbGdvcml0aG0gc28gdGhlb3JldGljYWxseSB0aGUgcGFyYWxsZWxpemF0aW9uIG11c3QgYmUgZG9uZSBhdCBhIGxldmVsIG90aGVyIHRoYW4gdGhlIGJhc2UgbGVhcm5lci4KSW4gYGxpZ2h0Z2JtYCBwYXJhbGxlbGl6YXRpb24gaXMgc2VyaW91c2x5IGltcGxlbWVudGVkIGludG8gZGlmZmVyZW50IHN1Y2ggbGV2ZWxzLAp3aXRoIHN1cHBvcnQgb2YgYm90aCBtdWx0aS10aHJlYWRpbmcgb3IgbXVsdGktbWFjaGluZSBydW50aW1lLgoKKEJ5IGRlZmF1bHQgYHRyZWVfbGVhcm5lcj1zZXJpYWxgIHNvIHRoZXJlIGlzIG5vIHBhcmFsbGVsaXphdGlvbiBhdCBsZWFybmluZy4pCgojIyMgRmVhdHVyZSBQYXJhbGxlbAoKYGBgCnRyZWVfbGVhcm5lcj1mZWF0dXJlCmBgYAoKVGhpcyBpcyB0aGUgbW9zdCBzdHJhaWdodGZvcndhcmQgcGFyYWxsZWxpemF0aW9uIHdlIGNhbiB0aGluayBvZi4KRGF0YSBpcyBwYXJ0aXRpb25lZCB2ZXJ0aWNhbGx5IHNvIGVhY2ggdGhyZWFkIHdpbGwgb25seSBpbnNwZWN0IGEgc3Vic2V0IG9mIGZlYXR1cmVzLgpCZXN0IHNwbGl0IGlzIGZvdW5kIHNpbXVsdGFuZW91c2x5IGFtb25nIHRob3NlIGZlYXR1cmVzIHRvIGdldCB0aGUgbG9jYWwgYmVzdCBzcGxpdHMsCnRoZW4gYSBjb21taW51Y2F0aW9uIG92ZXJoZWFkIGlzIHJlcXVpcmVkIHRvIGFycml2ZSBhdCB0aGUgZ2xvYmFsIGJlc3QuClRoZSBzcGVlZHVwIGlzIGxpbWl0ZWQgaWYgb3VyIGRhdGEgaXMgbm90IHdpZGUgYnV0IGluc3RlYWQgdmVyeSBsb25nLgoKIyMjIERhdGEgUGFyYWxsZWwKCmBgYAp0cmVlX2xlYXJuZXI9ZGF0YQpgYGAKCkluc3RlYWQgb2YgdmVydGljYWxseSBwYXJhbGxlbGl6YXRpb24sCmBsaWdodGdibWAgYWxzbyBzdXBwb3J0cyBob3Jpem9udGFsIHBhcmFsbGVsaXphdGlvbi4KTG9jYWwgZ3JhZGllbnQgaGlzdG9ncmFtcyB3aWxsIGJlIGNvbnN0cnVjdGVkIGZvciBlYWNoIHBhcnRpdGlvbiBhbmQgdGhlbiBtZXJnZWQgdG8gYSBnbG9iYWwgaGlzdG9ncmFtIGZvciBmaW5kaW5nIHRoZSBiZXN0IHNwbGl0LgoKIyMjIFZvdGluZyBQYXJhbGxlbAoKYGBgCnRyZWVfbGVhcm5lcj12b3RpbmcKYGBgCgpATklQUzIwMTZfNjM4MSBwcm9wb3NlIHRoZSBpZGVhIG9mIHBhcmFsbGVsIHZvdGluZyB0cmVlLgpUaGUgZ2VuZXJhbCBpZGVhIGNhbiBiZSBzdW1tYXJpemVkIGludG8gdGhlIGZvbGxvd2luZyBmbG93OgoKMS4gUGFydGl0aW9uIGRhdGEgYWNjb3JkaW5nIHRvIG11bHRpcGxlIHRocmVhZHMvbWFjaGluZXMKMi4gSW4gZWFjaCBwYXJ0aXRpb24gYSBsb2NhbCB2b3RpbmcgaXMgZG9uZSB0byBwcm9wb3NlIHRvcC1rIGZlYXR1cmVzIGZvciB0aGUgYmVzdCBzcGxpdAozLiBUb3AtMmsgZmVhdHVyZXMgYXJlIGNvbGxlY3RkIGJhc2VkIG9uIGFsbCBsb2NhbCB2b3Rlcwo0LiBGdWxsIGRhdGEgZm9yIHRoZSB0b3AtMmsgaXMgY29sbGVjdGVkIGFuZCBhIGdsb2JhbCBiZXN0IHNwbGl0IGlzIHNlYXJjaGVkIHdpdGhpbiB0aGVtCgpTdWNoIHBhcmFsbGVsIHZvdGluZyBzdHJhdGVneSBoYXMgbGltaXRlZCBjb21tdW5pY2F0aW9uIG92ZXJoZWFkIGJ1dCBjYW4gYXBwcm94aW1hdGUgdmVyeSBnb29kIGluIGFjY3VyYWN5LgpJbiBgbGlnaHRnYm1gLApieSBkZWZhdWx0IHdoZW4gZG9pbmcgcGFyYWxsZWwgdm90aW5nIHdlIGhhdmUgYHRvcF9rPTIwYC4KCkluIGdlbmVyYWwsCnNpbmNlIGBsaWdodGdibWAgaXMgYWxyZWFkeSBsaWdodG5pbmcgZmFzdCwKb25seSB2ZXJ5IGh1Z2UgZGF0YSB3aWxsIHdlIG5lZWQgdG8gcmVzb3J0IHRvIHBhcmFsbGVsIHZvdGluZy4KCiMgWEdCb29zdCB2LnMuIExpZ2h0R0JNCgpgeGdib29zdGAgYW5kIGBsaWdodGdibWAgaGF2ZSBhIGxvdCBpbiBjb21tb24sCmV2ZW4gdGhlaXIgQVBJcyAoaW4gYm90aCBQeXRob24gYW5kIFIpIGFyZSBoaWdobHkgc2ltaWxhciB0byBlYWNoIG90aGVyLgpUaGV5IHVzZSB0aGUgc2FtZSBnbG9iYWwgbG9zcyBmdW5jdGlvbiB3aXRoIDJuZC1vcmRlciBhcHByb3hpbWF0aW9uIGFuZCByZWd1bGFyaXphdGlvbiB0byBvcHRpbWl6ZSB0aGUgbW9kZWwgZW5zZW1ibGVyLgpUaGV5IGFsbG93IHBsdWctYW5kLXBsYXkgY3VzdG9tIGxvc3NlcyBhbmQgZXZhbHVhdGlvbiBtZXRyaWNzLgpUaGV5IGhhbmRsZSBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgc2FtZSB3YXkgYnkgbGVhcm5pbmcgYSBkZWZhdWx0IGJyYW5jaCBhdCBlYWNoIHNwbGl0LCAuLi4KClN0aWxsLAp0aGVyZSBhcmUgc3VidGxlIGRpZmZlcmVuY2VzIHdlIHNob3VsZCBiZSBhd2FyZSBvZi4KSW4gdGhpcyBzZWN0aW9uIHdlIGRpc2N1c3Mgc2V2ZXJhbCBpbXBvcnRhbnQgZGlmZmVyZW5jZXMgKG91dCBvZiBjb21tb25hbGl0eSkgaW4gdGhlIHR3by4KCiMjIE9uIFRyZWUgVHlwZQoKQXMgYSBtYXR0ZXIgb2YgZmFjdCwKc2luY2UgaGlzdG9ncmFtLWJhc2VkIGFwcHJvYWNoIHRvZ2V0aGVyIHdpdGggbGVhZi13aXNlIGdyb3d0aCBpcyBzbyBwb3dlcmZ1bCwKYWZ0ZXIgYGxpZ2h0Z2JtYCdzIGVudGVyaW5nIGludG8gdGhlIGNvbW11aXR5LApgeGdib29zdGAgYWxzbyBxdWlja2x5IGNhdGNoZWQgdXAgd2l0aCB0aGUgc2FtZSBpbXBsZW1lbnRhdGlvbi4KU28gaW4gYHhnYm9vc3RgIHdlIGNhbiBzZXQgdGhlIGZvbGxvd2luZyBwYXJhbWV0ZXIKCmBgYAp0cmVlX21ldGhvZD0iaGlzdCIKZ3Jvd19wb2xpY3k9Imxvc3NndWlkZSIKYGBgCgp0byB0cmFpbiBhIFhHQm9vc3QgbW9kZWwgd2l0aCBsZWFmLXdpc2UgZ3Jvd2luZyBoaXN0b2dyYW0tYmFzZWQgdHJlZXMsCmp1c3QgbGlrZSBMaWdodEdCTS4KCiMjIE9uIEJhc2UgTGVhcm5lcgoKYGxpZ2h0Z2JtYCBvbmx5IHN1cHBvcnRzIHRyZWUgbGVhcm5lciAodXNpbmcgdGhlIGBib29zdGluZ2AgcGFyYW1ldGVyKSB3aGlsZSBgeGdib29zdGAgYWxzbyBzdXBwb3J0cyBsaW5lYXIgbGVhcm5lcnMgKHVzaW5nIHRoZSBgYm9vc3RlcmAgcGFyYW1ldGVyKS4KCkluIGB4Z2Jvb3N0YCB0d28gbGluZWFyIGxlYXJuZXJzIGFyZSBzdXBwb3J0ZWQ6CkEgc2ltcGxlIGxpbmVhciByZWdyZXNzb3IgYW5kIGEgZ2VuZXJhbGl6ZWQgbGluZWFyIG1vZGVsIHdpdGggW3R3ZWVkaWUgZGlzdHJpYnV0aW9uXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Ud2VlZGllX2Rpc3RyaWJ1dGlvbikuCgpGb3IgdHJlZSBsZWFybmVyIG90aGVyIHRoYW4gdGhlIHJlZ3Jlc3Npb24gdHJlZSB0aGV5IGJvdGggc3VwcG9ydHMgdHdvIGFkZGl0aW9uYWwgdHJlZSBtb2RlbHM6CgoxLiBSYW5kb20gRm9yZXN0CjIuIERBUlQgW0ByYXNobWkyMDE1ZGFydF0KCk9uZSBub3RhYmxlIGRpZmZlcmVuY2UgaW4gUkYgaXMgdGhhdCB3aGlsZSBgeGdib29zdGAgYWxsb3dzIHRyYWluaW5nIGEgYm9vc3RlZCBSRiwKYGxpZ2h0Z2JtYCBvbmx5IHN1cHBvcnRzIHRyYWluaW5nIGEgcmVndWxhciBSRiAodGhlcmUgaXMgbm8gYm9vc3RpbmcgaW52b2x2ZWQpLgoKIyMgT24gSHlwZXJwYXJhbWV0ZXJzCgpUaGVyZSBhcmUgdG9ucyBvZiBoeXBlcnBhcmFtZXRlcnMgaW4gbW9kZXJuIEdCVCBtb2RlbHMgd2UgZGlzY3Vzc2VkIGluIHRoaXMgbm90ZWJvb2suCkhlcmUgd2Ugb25seSB3YWxrLXRocm91Z2ggdGhvc2UgaW1wb3J0YW50IGFuZCBub24tdHJpdmlhbCBvbmVzLgoKIyMjIFJlZ3VsYXJpemF0aW9uIFBhcmFtZXRlcnMKCiMjIyMgTDEvTDIgUGVuYWx0aWVzCgpCb3RoIHBhY2thZ2VzIGltcGxlbWVudCB0aGUgc2FtZSBwZW5hbHRpZXMgb24gY29zdCBmdW5jdGlvbi4KT25seSB0aGUgbmFtZXMgYW5kIGRlZmF1bHQgdmFsdWVzIGFyZSBkaWZmZXJlbnQuCkluIGBsaWdodGdibWAgTDEvTDIgcGVuYWx0aWVzIGFyZSBkZWZpbmVkIGJ5OgoKYGBgCmxhbWJkYV9sMT0wCmxhbWJkYV9sMj0wCmBgYAoKQW5kIGluIGB4Z2Jvb3N0YCB0aGV5IGFyZToKCmBgYAphbHBoYT0wCmxhbWJkYT0xCmBgYAoKIyMjIyBMZWFmIFNpemUKCk9uZSB3YXkgdG8gcmVndWxhcml6ZSBhIHRyZWUgbW9kZWwgaXMgdG8gbGltaXQgdGhlIHNpemUgb2YgaXRzIHRlcm1pbmFsIG5vZGUgKGxlYWYpLgpJbiBgbGlnaHRnYm1gIHRoaXMgY2FuIGJlIGRvbmUgYnkgZWl0aGVyIGxpbWl0aW5nIHRoZSBtaW5pbXVtIG51bWJlciBvZiBleGFtcGxlcyBvciBsaW1pdGluZyB0aGUgbWluaW11bSBzdW0gb2YgaGVzc2lhbiAoJFxzdW1fe2kgXGluIElfan1oX2kkIGZvciBub2RlICRqJCkuCkhlcmUgYXJlIHRoZSBkZWZhdWx0IHZhbHVlczoKCmBgYAptaW5fZGF0YV9pbl9sZWFmPTIwCm1pbl9zdW1faGVzc2lhbl9pbl9sZWFmPTFlLTMKYGBgCgpJbiBgbGlnaHRnYm1gIGFuIGltcHJvcGVyIHZhbHVlIGZvciB0aGlzIHBhcmFtZXRlciB1c3VhbGx5IHJlc3VsdHMgaW4gdGhlIGZvbGxvd2luZyB3YXJuaW5nIG1lc3NhZ2UgZHVyaW5nIHRyYWluaW5nIHJ1bnRpbWU6CgpgYGAKW0xpZ2h0R0JNXSBbV2FybmluZ10gTm8gZnVydGhlciBzcGxpdHMgd2l0aCBwb3NpdGl2ZSBnYWluLCBiZXN0IGdhaW46IC1pbmYKYGBgCgpJbiBzdWNoIGNhc2UsCnRyeSB0byBsb3dlciBkb3duIHRoZSB2YWx1ZSBzaW5jZSB0aGUgYWxnb3JpdGhtIGlzIGNvbXBsYWluaW5nIGFib3V0IG5vdCBhYmxlIHRvIHNwbGl0IGZ1cnRoZXIgZHVlIHRvIHJlYWNoaW5nIHRoZSBtaW5pbXVtIGxpbWl0cyBvZiBub2RlIHNpemUsCmdpdmVuIHRoZSB0YXJnZXRlZCBudW1iZXIgb2YgbGVhdmVzLgpPZiBjb3Vyc2UgYW5vdGhlciBjdXJlIGlzIHRvIHJlZHVjZSB0cmVlIGNvbXBsZXhpdHkgYnkgZGVjcmVhc2luZyBgbnVtX2xlYXZlc2AuCgpJbiBgeGdib29zdGAgdG8gY29udHJvbCBsZWFmIHNpemUgd2UgY2FuIG9ubHkgbGltaXQgb24gaGVzc2lhbiBidXQgbm90IHRoZSBleGFjdCBleGFtcGxlIGNvdW50czoKCmBgYAptaW5fY2hpbGRfd2VpZ2h0PTEKYGBgCgpSZW1lbWJlciB0aGF0IGluIHRoZSBjYXNlIG9mIGEgc3F1YXJlZCBsb3NzIHRoZSBoZXNzaWFuIGlzIGluZGVlZCB1bml0eSAoYWZ0ZXIgbm9ybWFsaXphdGlvbiksCnNvIHRoZSBoZXNzaWFuIHN1bSB3aWxsIGJlIHRoZSBzYW1lIGFzIGV4YW1wbGUgY291bnRzLgoKTm90aWNlIHRoZSBkaWZmZXJlbmNlIGluIHRoZSBkZWZhdWx0IHZhbHVlcyBiZXR3ZWVuIGBsaWdodGdibWAgYW5kIGB4Z2Jvb3N0YC4KVGhlIGZvcm1lciBoYXMgbGVzcyB0b2xlcmFuY2Ugb24gb3Zlci1maXR0aW5nIChtb3JlIHJlZ3VsYXJpemVkKS4KCiMjIyMgVHJlZSBTaXplCgpBIGxhcmdlciB0cmVlIGhhcyBtb3JlIGxlYXZlcyBhbmQgaGVuY2UgbW9yZSBjb21wbGljYXRlZC4KTW9yZSBsZWF2ZXMgYWxzbyBtZWFucyBtb3JlIGludGVyYWN0aW9uIGFtb25nIHZhcmlhYmxlcywKd2hpY2ggaW4gdHVybiBjb250cm9scyB0aGUgZGVncmVlIG9mIG5vbi1saW5lYXJpdHkgaW4gdGhlIG1vZGVsLgpeW1doZW4gYSBzdWJzZXF1ZW50IHNwbGl0IGlzIGNvbmRpdGlvbmluZyBvbiBhIHByZXZpb3VzIHNwbGl0IG9uIGFub3RoZXIgdmFyaWFibGUsCndlIGNvbnNpZGVyIHRoaXMgYXMgdmFyaWFibGUgaW50ZXJhY3Rpb24uXQoKU2luY2UgYGxpZ2h0Z2JtYCBncm93cyBhIHRyZWUgbGVhZi13aXNlLAp0aGVyZSBhcmUgdHdvIHBhcmFtZXRlcnMgdG8gY29udHJvbCB0cmVlIHNpemU6CgpgYGAKbWF4X2RlcHRoPS0xCm51bV9sZWF2ZXM9MzEKYGBgCgpgbnVtX2xlYXZlc2AgaXMgdGhlIG1haW4gZmFjdG9yLAphbmQgYG1heF9kZXB0aGAgc3VwcGxlbWVudHMgaXQuCgoKSW4gYHhnYm9vc3RgIG9uZSBjYW4gY2hvb3NlIHRvIGdyb3cgYSB0cmVlIGVpdGhlciBkZXB0aC13aXNlICh0aGUgZGVmYXVsdCkgb3IgbGVhZi13aXNlLgpGb3IgZGVwdGgtd2lzZSB0cmVlIHRoZXJlIGlzIG9ubHkgb25lIHJlbGV2YW50IHBhcmFtZXRlcjoKCmBgYAptYXhfZGVwdGg9NgpgYGAKCkZvciBsZWFmLXdpc2UgZ3Jvd2luZyB0cmVlIHRvIGNvbnRyb2wgbnVtYmVyIG9mIGxlYXZlczoKCmBgYAptYXhfbGVhdmVzPTAKYGBgCgpGb3IgYSBsZWFmLXdpc2UgdHJlZSB0byBncm93IGFzIG1hbnkgbGVhdmVzIGFzIGEgZGVwdGgtd2lzZSB0cmVlLAp0aGUgZm9ybWVyIHdpbGwgdXN1YWxseSBnZXQgZGVlcGVyIHRoYW4gdGhlIGxhdHRlci4KVGhpcyBpcyB3aHkgdG8gc2ltdWx0YW5lb3VzbHkgY29udHJvbCB0aGUgbnVtYmVyIG9mIGxlYXZlcyBhbmQgdHJlZSBkZXB0aCBpcyBhIGdvb2Qgc3RyYXRlZ3kuCkJlIGF3YXJlIHRoYXQgYnkgZGVmYXVsdCBgbGlnaHRnYm1gIGRvZXNuJ3QgbGltaXQgdHJlZSBkZXB0aCBhdCBhbGwuCgojIyMjIFN1Yi1TYW1wbGluZwoKQm90aCBsaWJyYXJpZXMgc3VwcG9ydCBjb2x1bW4gc2FtcGxpbmcgYW5kIHJvdyBzYW1wbGluZy4KSW4gYGxpZ2h0Z2JtYCB3ZSBoYXZlOgoKYGBgCmJhZ2dpbmdfZnJlcT0wCmJhZ2dpbmdfZnJhY3Rpb249MQpmZWF0dXJlX2ZyYWN0aW9uPTEKZmVhdHVyZV9mcmFjdGlvbl9ieW5vZGU9MQpgYGAKCk5vdGUgdGhhdCBgYmFnZ2luZ19mcmVxYCBjb250cm9scyBob3cgbWFueSBpdGVyYXRpb25zIHRvIHJlLWRvIHRoZSBiYWdnaW5nLgpTbyBgYmFnZ2luZ19mcmVxPTFgIHdpbGwgZG8gcm93IHNhbXBsaW5nIGF0IHRoZSBzdGFydCBvZiBldmVyeSBpdGVyYXRpb24uCldlIGNhbiBldmVuIHN1YnNhbXBsZSBvbmx5IHBvc2l0aXZlIG9yIG5lZ2F0aXZlIGV4YW1wbGVzIChmb3IgYmluYXJ5IGNsYXNzaWZpY2F0aW9uKSwKYnkgdXNpbmcgYHBvc19iYWdnaW5nX2ZyYWN0aW9uYCBvciBgbmVnX2JhZ2dpbmdfZnJhY3Rpb25gLgpUaGlzIGNhbiBiZSBoYW5keSB3aGVuIGRlYWxpbmcgd2l0aCBpbWJhbGFuY2VkIGRhdGFzZXQuCgpGb3IgZmVhdHVyZSBzYW1wbGluZywKYGZlYXR1cmVfZnJhY3Rpb25gIGNvbnRyb2xzIHRoZSBzYW1wbGluZyBhdCB0aGUgYmVnaW5uaW5nIG9mIGVhY2ggaXRlcmF0aW9uLAp3aGlsZSBgZmVhdHVyZV9mcmFjdGlvbl9ieW5vZGVgIGNvbnRyb2xzIHRoZSBzYW1wbGluZyBhdCBlYWNoIHRyZWUgbm9kZS4KVGhlIGxhdHRlciBpcyBpbiBnZW5lcmFsIG1vcmUgcmVndWxhcml6ZWQgc2luY2UgdGhlIG1vZGVsIHRyYWluaW5nIGlzIHN1YmplY3QgdG8gc3Ryb25nZXIgcmFuZG9tbmVzcy4KCkluIGB4Z2Jvb3N0YCB3ZSBoYXZlIHRoZSBmb2xsb3dpbmcgcmVsZXZhbnQgcGFyYW1ldGVyczoKCmBgYApzdWJzYW1wbGU9MQpjb2xzYW1wbGVfYnl0cmVlPTEKY29sc2FtcGxlX2J5bGV2ZWw9MQpjb2xzYW1wbGVfYnlub2RlPTEKYGBgCgpJdCBoYXMgbGVzcyBjb250cm9sIG92ZXIgYmFnZ2luZywKb25seSBvbmUgcGFyYW1ldGVyIGBzdWJzYW1wbGVgIGlzIHVzZWQgYW5kIHRoZSBiYWdnaW5nIGlzIGRvbmUgYXQgdGhlIGJlZ2lubmluZyBvZiBlYWNoIGl0ZXJhdGlvbi4KV2hpbGUgaXQgaGFzIG1vcmUgY29udHJvbCBvdmVyIGNvbHVtbiBzYW1wbGluZy4KQmVzaWRlcyBjb2x1bW4gc2FtcGxpbmcgYXQgaXRlcmF0aW9uIG9yIG5vZGUtbGV2ZWwsCml0IGFsc28gc3VwcG9ydHMgY29sdW1uIHNhbXBsaW5nIGF0IHRyZWUtZGVwdGgtbGV2ZWwuCgpOb3RlIHRoYXQgaW4gYm90aCBsaWJyYXJpZXMgdGhlIGJhZ2dpbmcgaXMgZG9uZSAqd2l0aG91dCByZXBsYWNlbWVudCouClNvIG5vbmUgb2YgdGhlbSBpbXBsZW1lbnRzIGJvb3N0cmFwIGJhZ2dpbmcsCndoaWNoIGlzIHByb3Bvc2VkIGJ5IHRoZSBvcmlnaW5hbCByYW5kb20gZm9yZXN0IHBhcGVyLgoKIyMgT24gSW1iYWxhbmNlZCBEYXRhCgojIyMgQ2xhc3MgUmUtV2VpZ2h0aW5nCgpTaW1wbHkgYmFzZWQgb24gaHlwZXJwYXJhbWV0ZXJzIHRoZXJlIGFyZSB0d28gd2F5cyB0byBoYW5kbGUgaW1iYWxhbmNlZCBkYXRhc2V0IGluIGEgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHRhc2suClRoZSBmaXJzdCBhcHByb2FjaCBpcyB0byByZS13ZWlnaHQgdGhlIGV4YW1wbGVzLgpCb3RoIGB4Z2Jvb3N0YCBhbmQgYGxpZ2h0Z2JtYCBoYXMgdGhlIHBhcmFtZXRlciBmb3IgdGhpcyBwdXJwb3NlOgoKYGBgCnNjYWxlX3Bvc193ZWlnaHQ9MQpgYGAKClRvIHJlLWJhbGFuY2UgdGhlIGRpc3RyaWJ1dGlvbiB0byA1MC01MCB0aGUgd2VpZ2h0IHNob3VsZCBiZToKCmBgYApudW1iZXIgb2YgbmVnYXRpdmUgc2FtcGxlcyAvIG51bWJlciBvZiBwb3NpdGl2ZSBzYW1wbGVzCmBgYAoKVGhpcyB3aWxsIGhlbHAgdGhlIG1vZGVsIGluIGl0cyBnZW5lcmFsIHJhbmtpbmcgcGVyZm9ybWFuY2UgKHNvIG1ldHJpY3Mgc3VjaCBhcyBBVUMgd2lsbCBpbXByb3ZlIGEgbG90KS4KRm9yIGBsaWdodGdibWAgdGhlcmUgaXMgYWxzbyBhbm90aGVyIGNvbnZlbmllbnQgYm9vbGVhbiBwYXJhbWV0ZXIganVzdCB0byBkbyB0aGlzOgpgaXNfdW5iYWxhbmNlYC4KCkhvdyBkb2VzIHRoZSB3ZWlnaHRpbmcgZmFjdG9yIGFmZmVjdCBvdXIgbW9kZWwgdHJhaW5pbmc/Ckl0IGlzIGRpcmVjdGx5IHRocm91Z2ggdGhlIGdyYWRpZW50cy4KVGhlIHNjYWxlIGZhY3RvciB3aWxsIHNpbXBseSBiZSB1c2VkIGFzIGEgbXVsdGlwbGllciBvbiAqYm90aCogZ3JhZGllbnQgJGdfaSQgYW5kIGhlc3NpYW4gJGhfaSQgZm9yIHRoYXQgZXhhbXBsZSwKcHJvdmlkZWQgdGhhdCB0aGUgZXhhbXBsZSAkaSQgaXMgYSBwb3NpdGl2ZSBleGFtcGxlLgoKSW4gYHhnYm9vc3RgJyBzb3VyY2UgY29kZSBpdCBpczoKCmBgYGNwcApfb3V0X2dwYWlyW19pZHhdID0gR3JhZGllbnRQYWlyKExvc3M6OkZpcnN0T3JkZXJHcmFkaWVudChwLCBsYWJlbCkgKiB3LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIExvc3M6OlNlY29uZE9yZGVyR3JhZGllbnQocCwgbGFiZWwpICogdyk7CmBgYAoKd2hlcmUgYHdgIGlzIHRoZSBzY2FsZSBmYWN0b3IgKGFmdGVyIGNvbnNpZGVyaW5nIGFsc28gdXNlci1zdXBwbGllZCBjdXN0b20gd2VpZ2h0cyBmb3IgZWFjaCBleGFtcGxlKS4KCkFuZCBpbiBgbGlnaHRnYm1gJ3Mgc291cmNlIGNvZGUgaXQgaXM6CgpgYGBjcHAKY29uc3QgZG91YmxlIHJlc3BvbnNlID0gLWxhYmVsICogc2lnbW9pZF8gLyAoMS4wZiArIHN0ZDo6ZXhwKGxhYmVsICogc2lnbW9pZF8gKiBzY29yZVtpXSkpOwpjb25zdCBkb3VibGUgYWJzX3Jlc3BvbnNlID0gZmFicyhyZXNwb25zZSk7CmdyYWRpZW50c1tpXSA9IHN0YXRpY19jYXN0PHNjb3JlX3Q+KHJlc3BvbnNlICogbGFiZWxfd2VpZ2h0ICAqIHdlaWdodHNfW2ldKTsKaGVzc2lhbnNbaV0gPSBzdGF0aWNfY2FzdDxzY29yZV90PihhYnNfcmVzcG9uc2UgKiAoc2lnbW9pZF8gLSBhYnNfcmVzcG9uc2UpICogbGFiZWxfd2VpZ2h0ICogd2VpZ2h0c19baV0pOwpgYGAKCndoZXJlIGBsYWJlbGAgaXMgMSBmb3IgcG9zaXRpdmUgYW5kIC0xIGZvciBuZWdhdGl2ZSwKYGxhYmVsX3dlaWdodGAgaXMgdGhlIHNjYWxlIGZhY3RvciwKYHNpZ21vaWRfYCBpcyB0aGUgc2NhbGUgZmFjdG9yIGZvciBzaWdtb2lkIGZ1bmN0aW9uIChkZWZhdWx0IGF0IDEpLAphbmQgIGB3ZWlnaHRzX2AgaXMgdXNlci1zdXBwbGllZCBjdXN0b20gd2VpZ2h0cy4KCiMjIyBNYXJnaW4gQ2xpcHBpbmcKCkJ1dCBzaW5jZSBhbnkgbm9uLXVuaXR5IHdlaWdodCBhbHNvIHNjcmV3cyB1cCB0aGUgZGlzdHJpYnV0aW9uIG9mIG91ciB0cmFpbmluZyBkYXRhLAp0aGUgbW9kZWwgd2lsbCBub3QgYmUgYWJsZSB0byBlc3RpbWF0ZSB0aGUgY2xhc3MgcHJvYmFiaWxpdHkgY29ycmVjdGx5LgpJbiBjYXNlIHRoYXQgZXN0aW1hdGluZyB0aGUgY29ycmVjdCBwcm9iYWJpbGl0eSBpcyByZWxldmFudCwKd2UgaGF2ZSBhbm90aGVyIHBhcmFtZXRlciB0aGF0IG1heSBoZWxwLAp3aGljaCBpcyBhbHNvIHNoYXJlZCBieSB0aGUgdHdvIGxpYnJhcmllczoKCmBgYAptYXhfZGVsdGFfc3RlcD0wCmBgYAoKSGVyZSBpcyBhIHF1b3RlIGZyb20gYHhnYm9vc3RgJ3Mgb2ZmaWNpYWwgZG9jdW1lbnQgb24gdGhpcyBwYXJhbWV0ZXI6Cgo+IFVzdWFsbHkgdGhpcyBwYXJhbWV0ZXIgaXMgbm90IG5lZWRlZCwKYnV0IGl0IG1pZ2h0IGhlbHAgaW4gbG9naXN0aWMgcmVncmVzc2lvbiB3aGVuIGNsYXNzIGlzIGV4dHJlbWVseSBpbWJhbGFuY2VkLgpTZXQgaXQgdG8gdmFsdWUgb2YgMS0xMCBtaWdodCBoZWxwIGNvbnRyb2wgdGhlIHVwZGF0ZS4KClRlY2huaWNhbGx5LApgbWF4X2RlbHRhX3N0ZXBgIGNhcCB0aGUgbGVhZiB3ZWlnaHQgKGJhc2UgbW9kZWwgbG9naXQgb3V0cHV0IG9yICptYXJnaW4qKSBsZWFybnQgZnJvbSBlYWNoIGl0ZXJhdGlvbi4KTm90IHRoYXQgdGhpcyBpcyBkaWZmZXJlbnQgZnJvbSBsZWFybmluZyByYXRlIHdoaWNoIG9ubHkgYXBwbGllcyBhIGZyYWN0aW9uIG9mIHRoZSB1cGRhdGUuCmBtYXhfZGVsdGFfc3RlcGAgZWZmZWN0aXZlbHkgbGltaXRzIHRoZSBhYnNvbHV0ZSB1cGRhdGUgYW1vdW50LgpBbmQgbGVhcm5pbmcgcmF0ZSB3aWxsIHN0aWxsIGFwcGx5IHRvIHRoZSBjYXBwZWQgdXBkYXRlLgoKVGhpcyBpcyBhIGJpdCBzaW1pbGFyIHRvICpncmFkaWVudCBjbGlwcGluZyogaW4gZ3JhZGllbnQgZGVzY2VudCwKd2hlcmUgd2UgY2FwIHRoZSBncmFkaWVudCB1cGRhdGUgdG8gYSBoYXJkIGxpbWl0IHRvIGF2b2lkIGFnZ3Jlc3NpdmUgdXBkYXRlLgoKIyMgT24gQ2F0ZWdvcmljYWwgRW5jb2RpbmcKCk9uZSBpbXBvcnRhbnQgdXNlci1mYWNpbmcgZGlmZmVyZW5jZSBpcyBgbGlnaHRnYm1gJ3Mgc3BlY2lhbCBzdXBwb3J0IGZvciBjYXRlZ29yaWNhbCBmZWF0dXJlcyAqd2l0aG91dCogb25lLWhvdC1lbmNvZGluZy4KVGhpcyBpcyBwcm9iYWJseSBvbmUgb2YgdGhlIG1vc3QgaGFuZHkgZnVuY3Rpb24gaW4gbW9kZXJuIG1hY2hpbmUgbGVhcm5pbmcgbGlicmFyaWVzLgpJdCBub3Qgb25seSBpbXByb3ZlcyB0aGUgbW9kZWwgYWNjdXJhY3kgYnV0IGFsc28gc2ltcGxpZmllcyBkYXRhIHBpcGVsaW5lIGFuZCBoZW5jZSByZWR1Y2VzIGVuZ2luZWVyaW5nIGVmZm9ydHMuCgpGb3IgYHhnYm9vc3RgIGNhdGVnb3JpY2FscyBtdXN0IGJlIGVpdGhlciBvbmUtaG90IGVuY29kZWQgb3IgY29udmVydGVkIHRvIGludGVnZXJzIHdpdGggYW4gb3JkaW5hbCBhc3N1bXB0aW9uLgpFbXBpcmljYWxseSwKZXZlbiB0aG91Z2ggdGhlIGZlYXR1cmUgaXMgbm90IG9yZGluYWwsCnNvbWV0aW1lcyBjb252ZXJ0aW5nIGl0IGludG8gaW50ZWdlcnMgc3RpbGwgd29yayBwcmV0dHkgd2VsbCBpbiB0ZXJtcyBvZiBhY2N1cmFjeSwKYWRkaW5nIHRoYXQgdGhlIHRyYWluaW5nIHJ1bnRpbWUgY2FuIGJlIHJlZHVjZWQgYSBsb3QgYW5kIGRhdGEgcGlwZWxpbmUgc2ltcGxpZmllZC4KClRvIGRlbW9uc3RyYXRlIHRoZSBwZXJmb3JtYW5jZSBnYWluIG9mIHRoZSBvcHRpbWFsIGNhdGVnb3JpY2FsIGVuY29kaW5nIGluIExpZ2h0R0JNLApsZXQncyBzaW11bGF0ZSBzb21lIGRhdGEuCkhlcmUgd2UgdXNlIHRoZSBzYW1lIGRhdGEgZ2VuZXJhdGluZyBwcm9jZXNzIHByZXZpb3VzbHkgc2V0dXAsCmJ1dCB3aXRoIG9uZSBtb3JlIGNhdGVnb3JpY2FsIHZhcmlhYmxlIHRoYXQgZW50ZXIgbGluZWFybHkgaW50byB0aGUgbW9kZWwuCgpgYGB7ciBzaW1fZGF0YV9mb3JfY2F0ZWdvcmljYWxfZXhwfQojIENyZWF0ZSBzaW11bGF0ZWQgZGF0YSBmb3IgY2F0ZWdvcmljYWwgZXhwZXJpbWVudC4KbGlicmFyeShNYXRyaXgpCgpuIDwtIDEwMDAwCnoxIDwtIHJub3JtKG4sIDIsIDQpCnoyIDwtIHJ1bmlmKG4pCmUgPC0gcm5vcm0obikKCiMgQ3JlYXRlIG9uZSBjYXRlZ29yaWNhbCB3aXRoIG1vZGVyYXRlIGNhcmRpbmFsaXR5IGFuZCBza2V3ZWQgZGlzdHJpYnV0aW9uLgpjYXRlZ29yaWNhbCA8LSBzYW1wbGUoMToyMCwgc2l6ZT1uLCByZXBsYWNlPVRSVUUsIHByb2I9MS4zXihzZXEoMiwgMjEpKSkKdGFibGUoY2F0ZWdvcmljYWwpCgpzcGFyc2VfY2F0IDwtIHQoZmFjMnNwYXJzZShmYWN0b3IoY2F0ZWdvcmljYWwpKSkgICMgT25lLWhvdCBlbmNvZGUuCnN0b3BpZm5vdChhbGwuZXF1YWwoYXMubnVtZXJpYyh0YWJsZShjYXRlZ29yaWNhbCkpLCAKICAgICAgICAgICAgICAgICAgICBjb2xTdW1zKHNwYXJzZV9jYXQpLCBjaGVjay5hdHRyaWJ1dGVzPUZBTFNFKSkKCiMgUmFuZG9tIGVmZmVjdHMgb24gY2F0ZWdvcmllcy4KY2F0X2JldGEgPC0gcm5vcm0obmNvbChzcGFyc2VfY2F0KSkKCiMgREdQOiBnID0gMyB6XzEgKyA2IHpfMiAtMS41IHpfMXpfMiArIGNhdGVnb3JpY2FsIGR1bW15IGVmZmVjdHMgKyBlCmcgPC0gMyAqIHoxICsgNiAqIHoyIC0gMS41ICogejEgKiB6MiArIHNwYXJzZV9jYXQgJSolIGNhdF9iZXRhICsgZQpnIDwtIGdbLDFdICAjIFN0cmlwIHJlZHVuZGFudCBkaW0uCgojIFByZXBhcmUgbW9kZWwgZGVzaWduIG1hdHJpeC4KaW5fbWF0IDwtIGNiaW5kKHoxLCB6Miwgc3BhcnNlX2NhdCkKaGVhZChpbl9tYXQpICAjIFRyYWluaW5nIGRhdGEgaW4gc3BhcnNlIG1hdHJpeCBmb3JtYXQuCgppbl9kdCA8LSBkYXRhLnRhYmxlKHoxPXoxLCB6Mj16MiwgY2F0PWNhdGVnb3JpY2FsKQpoZWFkKGluX2R0KSAgIyBUcmFpbmluZyBkYXRhIGluIHRhYnVsYXIgZm9ybWF0LgoKIyBUcmFpbi10ZXN0IHNwbGl0Lgppc190ZXN0X2cgPC0gcnVuaWYobikgPCAuMgpgYGAKCk5vdyB0cmFpbiBhIFhHQm9vc3QgbW9kZWwgd2l0aCBvbmUtaG90LWVuY29kZWQgc3BhcnNlIG1hdHJpeCBhcyBpbnB1dC4KVG8gYmUgZmFpciwKd2Ugc2V0dXAgdGhlIHBhcmFtZXRlcnMgc3VjaCB0aGF0IGB4Z2Jvb3N0YCB3aWxsIGFsc28gZ3JvdyBhIGxlYWYtd2lzZSBoaXN0b2dyYW0gdHJlZSBhcyBpdHMgYmFzZSBsZWFybmVyLgoKYGBge3IgY2F0ZWdvcmljYWxfZXhwX3hnYn0KcnVuX3hnYiA8LSBmdW5jdGlvbih2PTEpIHsKICB4Z2JfZHRyYWluIDwtIHhnYi5ETWF0cml4KGluX21hdFshaXNfdGVzdF9nLF0sIGxhYmVsPWdbIWlzX3Rlc3RfZ10pCiAgeGdiX2R0ZXN0IDwtIHhnYi5ETWF0cml4KGluX21hdFtpc190ZXN0X2csXSwgbGFiZWw9Z1tpc190ZXN0X2ddKQoKICAjIFdlIHNldCB0aGUgcGFyYW1ldGVycyBzdWNoIHRoYXQgeGdiIHVzZSBsZ2Itc3R5bGUgbGVhZi13aXNlIGhpc3RvZ3JhbSB0cmVlLgogIHhnYi50cmFpbihwYXJhbXM9bGlzdChvYmplY3RpdmU9InJlZzpzcXVhcmVkZXJyb3IiLCBib29zdGVyPSJnYnRyZWUiLAogICAgICAgICAgICAgICAgICAgICAgICBldGE9LjMsIG1heF9sZWF2ZXM9NywgbWF4X2RlcHRoPTMsIG50aHJlYWRzPTEsCiAgICAgICAgICAgICAgICAgICAgICAgIHRyZWVfbWV0aG9kPSJoaXN0IiwgZ3Jvd19wb2xpY3k9Imxvc3NndWlkZSIpLAogICAgICAgICAgICBkYXRhPXhnYl9kdHJhaW4sIHdhdGNobGlzdD1saXN0KHRlc3Q9eGdiX2R0ZXN0KSwKICAgICAgICAgICAgbnJvdW5kPTUwLCBlYXJseV9zdG9wcGluZ19yb3VuZHM9NSwgdmVyYm9zZT12LCBwcmludF9ldmVyeV9uPTEwKQp9CgppbnZpc2libGUocnVuX3hnYigpKQpgYGAKCk5vdyBpdCdzIExpZ2h0R0JNJ3MgdHVybi4KV2UgZGlyZWN0bHkgZmVlZCBhIGRhdGEgbWF0cml4IHdpdGggb25seSAzIGNvbHVtbnMsCm9uZSBvZiBpdCBiZWluZyBjYXRlZ29yaWNhbCBidXQgcmVwcmVzZW50ZWQgaW4gaW50ZWdlciwKd2l0aG91dCB0aGUgb3JkaW5hbCBhc3N1bXB0aW9uIGJ5IHNwZWNpZnlpbmcgZXhwbGljaXRseSBpbiBgY2F0ZWdvcmljYWxfZmVhdHVyZWAgYXJndW1lbnQgdG8gdGhlIG1vZGVsIHRyYWluaW5nIGNhbGwuCgpgYGB7ciBjYXRlZ29yaWNhbF9leHBfbGdifQpydW5fbGdiIDwtIGZ1bmN0aW9uKHY9MSkgewogIGxnYl9kdHJhaW4gPC0gbGdiLkRhdGFzZXQoYXMubWF0cml4KGluX2R0WyFpc190ZXN0X2ddKSwgbGFiZWw9Z1shaXNfdGVzdF9nXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG5hbWVzPW5hbWVzKGluX2R0KSwgY2F0ZWdvcmljYWxfZmVhdHVyZT0iY2F0IikKICBsZ2JfZHRlc3QgPC0gbGdiLkRhdGFzZXQoYXMubWF0cml4KGluX2R0W2lzX3Rlc3RfZ10pLCBsYWJlbD1nW2lzX3Rlc3RfZ10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG5hbWVzPW5hbWVzKGluX2R0KSwgY2F0ZWdvcmljYWxfZmVhdHVyZT0iY2F0IikKCiAgIyBXZSBzZXQgdGhlIHBhcmFtZXRlcnMgc3VjaCB0aGF0IGxnYiBiZWhhdmVzIGxpa2UgeGdiJ3MgZGVmYXVsdCBmb3IgY29tbW9uIHBhcmFtZXRlcnMuCiAgbGdiLnRyYWluKHBhcmFtcz1saXN0KG9iamVjdGl2ZT0icmVncmVzc2lvbiIsIGJvb3N0aW5nPSJnYmR0IiwKICAgICAgICAgICAgICAgICAgICAgICAgbGFtYmRhX2wyPTEsIGxlYXJuaW5nX3JhdGU9LjMsIG1ldHJpYz0icm1zZSIsCiAgICAgICAgICAgICAgICAgICAgICAgIG51bV9sZWF2ZXM9NywgbWF4X2RlcHRoPTMsIG51bV90aHJlYWRzPTEsCiAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9zdW1faGVzc2lhbl9pbl9sZWFmPTEsIG1pbl9kYXRhX2luX2xlYWY9MCksCiAgICAgICAgICAgIGRhdGE9bGdiX2R0cmFpbiwgdmFsaWRzPWxpc3QodGVzdD1sZ2JfZHRlc3QpLAogICAgICAgICAgICBudW1faXRlcmF0aW9ucz01MCwgZWFybHlfc3RvcHBpbmdfcm91bmRzPTUsIHZlcmJvc2U9diwgZXZhbF9mcmVxPTEwKQp9CgppbnZpc2libGUocnVuX2xnYigpKQpgYGAKCkluIG91ciBzcGVjaWZpYyBwYXJhbWV0ZXIgc2V0dXAgTGlnaHRHQk0gb3V0cGVyZm9ybXMgWEdCb29zdC4KQnV0IGl0IGlzIG1pbm9yIHNpbmNlIHdlIGRpZG4ndCB0dW5lIGFueSBvZiB0aGUgbW9kZWxzIHNlcmlvdXNseS4KVGhlcmUgYXJlIGFscmVhZHkgbG90cyBvZiBiZW5jaG1hcmtzIG9uIHRoZSBpbnRlcm5ldCB3aXRoIGRpZmZlcmVudCBleHBlcmltZW50cyBhbmQgZGF0YXNldCwKdGhlIHJlc3VsdHMgYXJlIGluIGdlbmVyYWwgYSBtaXh0dXJlLgpXZSBjb25zaWRlciB0d28gbW9kZWxzIGNvbXBhcmFibGUgaW4gdGVybXMgb2YgYWNjdXJhY3kuCkhlcmUgd2Ugb25seSBjYXJlIGFib3V0IHRoZSBwZXJmb3JtYW5jZSBnYXAgcmVzdWx0ZWQgZnJvbSBhIGRpZmZlcmVudCB0cmVhdG1lbnQgb2YgY2F0ZWdvcmljYWwgdmFyaWFibGVzLgoKTm93IHdlIGJlbmNobWFyayB0aGUgbW9kZWwgdHJhaW5pbmcgcnVudGltZToKCmBgYHtyIGJlbmNobWFya19jYXR9CmxpYnJhcnkobWljcm9iZW5jaG1hcmspCgpwcmludChtaWNyb2JlbmNobWFyaygKICB4Z2I9cnVuX3hnYih2PTApLAogIGxnYj1ydW5fbGdiKHY9MCksCiAgdGltZXM9MTAKKSkKYGBgCgpDbGVhcmx5IExpZ2h0R0JNIGlzIG11Y2ggZmFzdGVyIHRoYW4gWEdCb29zdCB3aXRoIGl0cyBvcHRpbWFsIGNhdGVnb3JpY2FsIGVuY29kaW5nIHNjaGVtZS4KTm90IHRvIG1lbnRpb24gaXQgYWxzbyBzYXZlcyBvdXIgYXNzIGZyb20gcHJlcGFyaW5nIGEgb25lLWhvdCBlbmNvZGVyIGZvciBvdXIgZGF0YS4KCiMgQ29uY2x1ZGluZyBSZW1hcmtzCgpNb2Rlcm4gZ3JhZGllbnQgYm9vc3RpbmcgdHJlZXMgYXJlIHBvd2VyZnVsIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIGZvciBsb3RzIG9mIHJlYWwgd29ybGQgYXBwbGljYXRpb25zLgpJbiB0aGlzIG5vdGVib29rIHdlIHJldmlldyB0aG9yb3VnaGx5IHR3byBvZiBzdWNoIGZyYW1ld29ya3MgaW4gdGhlIG9wZW4gc291cmNlIGNvbW11bml0eToKYHhnYm9vc3RgIGFuZCBgbGlnaHRnYm1gLgpXZSBjaG9vc2UgdGhlbSBub3Qgb25seSBiZWNhdXNlIHRoZXkgYXJlIGJvdGggaW1wbGVtZW50ZWQgd2l0aCB2ZXJ5IGhpZ2ggZW5naW5lZXJpbmcgcXVhbGl0eSwKYnV0IGFsc28gYmVjYXVzZSB0aGV5IGFyZSBib3RoIGV4dHJlbWVseSBwb3B1bGFyIGFuZCB3ZGllbHkgYWRvcHRlZCBhbW9uZyBkYXRhIHNjaWVuY2UgcHJhY3RpdGlvbmVycy4KCkZvciBzdWNoIGEgdG9vbCB0aGF0IGlzIHNvIHdpZGVseSBzcHJlYWQsCndlIGZlZWwgdGhlcmUgaXMgYSBuZWVkIHRvIGRlbXlzdGlmeSB3aHkgaXQgaXMgc28gZWZmZWN0aXZlIGJ5IHVuZGVyc3RhbmRpbmcgaG93IHRoZXkgYXJlIGRpZmZlcmVudCBmcm9tIHRyYWRpdGlvbmFsIG9yIHZhbmlsbGEgZ3JhZGllbnQgYm9vc3RpbmcgbWFjaGluZXMuCldlIGZvY3VzIG1vcmUgb24gdGhlIHRlY2huaWNhbCBkZXRhaWxzIGFuZCB0aGVpciBub3ZlbHRpZXMgaW4gYmV0dGVyaW5nIHRoZSBHQlQgZmFtaWx5LgpXZSBob3BlIHRoZSBub3RlYm9vayBjYW4gaGVscCBwcmFjdGl0aW9uZXJzIGJldHRlciB1bmRlcnN0YW5kIHRoZWlyIHRvb2wgYXQgaGFuZCwKc28gdGhleSBjYW4gdXRpbGl6ZSBpdCB3aXRoIGl0cyBtYXhpbXVtIHBvdGVudGlhbC4KCiMgUmVmZXJlbmNlcwo=