This notebook provides details of how to grow Random Forests for phonetic data. This is not exclusive to phonetic data and can be used for any other type of data.
We will start by looking at the basics of predictive modelling, by looking at logistic regression as a classification tool and use some notions from the Signal Detection Theory (accuracy, sensitivity, specificity, recall, dprime, AUC). We then look at some issues with logistic regression related to multicollinearity. We introduce then decision trees to understand how they work, before attempting to replicate how Random Forests work. We then grow our first Random Forests and try to capture as much information as possible from it. At the end, we look at the approach advocated by tidymodels
. At the end of the session, participants will be able to grow a Random Forest based on their own data.
Loading packages
## Use the code below to check if you have all required packages installed. If some are not installed already, the code below will install these. If you have all packages installed, then the second line of code will lad them.
requiredPackages = c('tidyverse', 'broom', 'knitr', 'corrplot', 'psycho', 'PresenceAbsence', 'party', 'ranger', 'tidymodels', 'pROC', 'varImp', 'lattice', 'vip', 'doFuture', 'doRNG', 'parallelly')
for(p in requiredPackages){
if(!require(p,character.only = TRUE)) install.packages(p)
library(p,character.only = TRUE)
}
Declare parallel computing
R
is a powerful software. It is designed by default to only use one core on your machine. As you know, any laptop/computer comes with at least two cores (if not more). Mine has 4 physical cores with multihtreading (i.e., a total of 8 logical cores). If I am to run a code that requires heavy computations, I will never ever use one core (as it will take ages to run). This is where the additional power of R is needed.
We can use any parallel computing package. I use the doFuture
package as it is one of the best backend parallel computing packages. I am declaring parallel computing below using one core only, but on your machine, you can use all cores
Number of cores available for model calculations set to 2.
[1] 2
Socket cluster with 2 nodes where 2 nodes are on host ‘localhost’ (R version 4.0.3 (2020-10-10), platform x86_64-w64-mingw32)
Introduction
The majority of research in phonetics and linguistics looks at traditional statistical techniques to evaluate group differences, using either frequentist or Bayesian frameworks. What is important in these frameworks is to assess whether a particular outcome can be explained by the potential differences that exist in a predictor. For example, we may want to examine if the differences observed in F2 frequency at the vowel midpoint can be explained by place of articulation, or age or gender. For this we use a linear regression (with or without mixed effects modelling) with the following formula: lm(F2 ~ place + age + gender)
.
This approach can potentially explain the differences associated with the predictors, i.e., we evaluate their influence on the outcome. But let’s ask the following question: can the observed differences on F2 frequency at the vowel midpoint be predictive of group differences associated with gender? Let’s put it differently: can we predict gender differences based on F2 frequencies? What would happen in the future? This is where predictive modelling comes to the rescue.
Predictive modelling is a statistical technique that uses either traditional or machine learning approaches to evaluate group difference and how they inform group separation. They can be used to predict changes either on the current data or in the future. If you build a predictive model based on pre-existing data, you can use the model’s predictions to:
- Validate the results on the current set
- Predict results on new ranges (e.g., if you have results on age group 30-50, in theory, you could predict the patterns for age 20 or 70)
- Predict results on new data, either on the testing set or any new data you obtain in the future.
To understand this further, we will start with a simple Generalised Linear Model predicting a perception experiment on grammaticality judgement, before moving to Decision Trees and Random Forests.
Generalised Linear Model
Here we will look at an example when the outcome is binary. This simulated data is structured as follows. We asked one participant to listen to 165 sentences, and to judge whether these are “grammatical” or “ungrammatical”. There were 105 sentences that were “grammatical” and 60 “ungrammatical”. This fictitious example can apply in any other situation. Let’s think Geography: 165 lands: 105 “flat” and 60 “non-flat”, etc. This applies to any case where you need to “categorise” the outcome into two groups.
Load and summaries
Let’s load in the data and do some basic summaries
-- Column specification ----------------------------------------------
cols(
grammaticality = col_character(),
response = col_character()
)
spec_tbl_df [165 x 2] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
$ grammaticality: chr [1:165] "grammatical" "grammatical" "grammatical" "grammatical" ...
$ response : chr [1:165] "yes" "yes" "yes" "yes" ...
- attr(*, "spec")=
.. cols(
.. grammaticality = col_character(),
.. response = col_character()
.. )
Normal GLM
Let’s run a first GLM (Generalised Linear Model). A GLM uses a special family “binomial” as it assumes the outcome has a binomial distribution. In general, results from a Logistic Regression are close to what we get from SDT (see above).
To run the results, we will change the reference level for both response and grammaticality. The basic assumption about GLM is that we start with our reference level being the “no” responses to the “ungrammatical” category. Any changes to this reference will be seen in the coefficients as “yes” responses to the “grammatical” category.
Model estimation and results
The results below show the logodds for our model.
response
grammaticality no yes
ungrammatical 50 10
grammatical 5 100
Call:
glm(formula = response ~ grammaticality, family = binomial, data = .)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.4676 -0.6039 0.3124 0.3124 1.8930
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -1.6094 0.3464 -4.646 3.38e-06 ***
grammaticalitygrammatical 4.6052 0.5744 8.017 1.08e-15 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 210.050 on 164 degrees of freedom
Residual deviance: 94.271 on 163 degrees of freedom
AIC: 98.271
Number of Fisher Scoring iterations: 5
The results show that for one unit increase in the response (i.e., from no to yes), the logodds of being “grammatical” is increased by 4.6051702 (the intercept shows that when the response is “no”, the logodds are -1.6094379). The actual logodds for the response “yes” to grammatical is 2.9957323
LogOdds to proportions
If you want to talk about the percentage “accuracy” of our model, then we can transform our loggodds into proportions. This shows that the proportion of “grammatical” receiving a “yes” response increases by 95%
[1] 0.1666667
[1] 0.952381
Plotting
grammatical <- grammatical %>%
mutate(prob = predict(mdl.glm, type = "response"))
grammatical %>%
ggplot(aes(x = as.numeric(grammaticality), y = prob)) +
geom_point() +
geom_smooth(method = "glm",
method.args = list(family = "binomial"), se = T) +
theme_bw(base_size = 20) +
labs(y = "Probability", x = "") +
coord_cartesian(ylim = c(0, 1), xlim = c(2.05, 0.95), expand = FALSE) +
scale_x_discrete(limits = c("UGramm", "Gramm"))
Accuracy and Signal Detection Theory
Rationale
We are generally interested in performance, i.e., whether the we have “accurately” categorised the outcome or not and at the same time want to evaluate our biases in responses. When deciding on categories, we are usually biased in our selection.
Let’s ask the question: How many of you have a Mac laptop and how many a Windows laptop? For those with a Mac, what was the main reason for choosing it? Are you biased in anyway by your decision?
If we go back to our grammatical example above, we want to evaluate if the participant was biased in anyway while responding. To correct for these biases, we use some variants from Signal Detection Theory to obtain the true estimates without being influenced by the biases.
Running stats
Let’s do some stats on this
Grammatical (Yes Actual) |
TP = 100 |
FN = 5 |
(Yes Actual) 105 |
Ungrammatical (No Actual) |
FP = 10 |
TN = 50 |
(No Actual) 60 |
Total |
(Yes Response) 110 |
(No Response) 55 |
165 |
TP = True Positive (Hit); FP = False Positive; FN = False Negative; TN = True Negative
[1] 100
[1] 5
[1] 10
[1] 50
[1] 165
Accuracy rate
Accuracy is simply the sum of true positives and true negatives divided by the total.
[1] 0.9090909
Error rate
The error rate can be computed as 1-accuracy or sum of false positives and false negatives divided by the total.
[1] 0.09090909
Specificity
Let us ask the following question: When the stimulus = yes, how many times the response = yes?
Here we quantify the true positive rate or specificity.
[1] 0.952381
False positive rate
Let us ask the following question: When the stimulus = no, how many times the response = yes?
[1] 0.1666667
Sensitivity
Sensitivity quantify the rejection rate and evaluates the sensitivity of the mode.
Let us ask the following question: When the stimulus = no, how many times the response = no?
[1] 0.8333333
Precision
Let us ask the following question: When the subject responds yes
, how many times is (s)he correct?
[1] 0.9090909
DPrime and other SDT metrics
We use the library psycho
to obtain SDT metrics.
One of the most important metrics is dprime
. This is usually used in perception experiments as it allows to take into account both sensitivity and specificity. This means, it allows to correct for biases in estimation.
The various metrics computed are:
dprime
or the sensitivity index. Between -6 to +6. positive = accurate detection; +6 = maximum detection; 0 no detection; negative = more noise (i.e., “no” responses) in the data
beta
or the bias criterion. Between 0-1: lower = increase in “yes” responses
Aprime
or estimate of discriminability. Between 0-1: 1 = good discrimination; 0 is at chance
bppd
or the “b prime prime d”. Between -1 and 1: 0 = no bias, negative = tendency to respond “yes”, positive = tendency to respond “no”)
c
or the index of bias. equals to SD.
For more details, see link
From our perspective here, the most important is d-prime (and the other sespetivity/specificity metrics). This is modelling the difference between the rate of “True Positive” responses and “False Positive” responses in standard unit (or z-scores). The formula can be written as:
d' (d prime) = Z(True Positive Rate) - Z(False Positive Rate)
$dprime
[1] 2.635813
$beta
[1] 0.3970026
$aprime
[1] 0.9419643
$bppd
[1] -0.5076923
$c
[1] -0.3504848
GLM and d prime
The values obtained here match those obtained from SDT. For d prime, the difference stems from the use of the logit variant of the Binomial family. By using a probit variant, one obtains the same values (see here for more details). A probit variant models the z-score differences in the outcome and is evaluated in change in 1-standard unit. This is modelling the change from “ungrammatical” “no” responses into “grammatical” “yes” responses in z-scores. The same conceptual underpinnings of d-prime from Signal Detection Theory.
[1] 2.635813
grammaticalityungrammatical
2.635813
The logit variant of the GLM reports the coefficients for the True Positive Rate or Specificity; the probit variant reports on dprime, or the difference between the True Positive responses and the False Positive responses in standard units.
GLM as a classification tool
The code below demonstrates the links between our GLM model and what we had obtained above from SDT. The predictions’ table shows that our GLM was successful at obtaining prediction that are identical to our initial data setup. Look at the table here and the table above. Once we have created our table of outcome, we can compute percent correct, the specificity, the sensitivity, etc. This yields the actual value with the SD that is related to variations in responses.
pred.gramm
grammatical ungrammatical
yes 100 10
no 5 50
Setting levels: control = grammatical, case = ungrammatical
Setting direction: controls < cases
Call:
roc.default(response = grammatical$grammaticality, predictor = pred.gramm)
Data: pred.gramm in 105 controls (grammatical$grammaticality grammatical) < 60 cases (grammatical$grammaticality ungrammatical).
Area under the curve: 1
If you look at the results from SDT above, these results are the same as the following
Accuracy: (TP+TN)/Total (0.9090909)
True Positive Rate (or Specificity) TP/(TP+FN) (0.952381)
True Negative Rate (or Sensitivity) TN/(FP+TN) (0.8333333)
The Area Under the Curve (AUC) is usually used to assess the model’s performance. As you see, we use the sensitivity on the y-axis and the inverse of specificity (1-specificity) on the x-axis. The AUC for this data is 1, i.e., perfect predictions and if you look at the AUC plot, it shows a perfect accuracy and detection. This is usually rare but can be obtained. Recall that this is a made-up dataset for demonstration only!
Now lets continue with a real dataset.
Issues with GLM
Let’s look at a new dataset that comes from phonetic research. This dataset is from my current work on the phonetic basis of the guttural natural class in Levantine Arabic
. Acoustic analyses were conducted on multiple portions of the VCV sequence, and we report here the averaged results on the first half of V2. Acoustic metrics were obtained via VoiceSauce: various acoustic metrics of supralaryngeal (bark difference) and laryngeal (voice quality via amplitude harmonic differences, noise, energy) were obtained. A total of 23 different acoustic metrics were obtained from 10 participants. Here, we use the data from the first two participants for demonstration purposes (one male and one female). The grouping factor of interest is context with two levels: guttural
vs non-guttural
. Gutturals include uvular, pharyngealised and pharyngeal consonants; non-gutturals include coronal plain, velar and glottal consonants.
The aim of the study was to evaluate whether the combination of the acoustic metrics provides support for a difference between the two classes of gutturals vs non-gutturals.
Load and summarise
-- Column specification ----------------------------------------------
cols(
.default = col_double(),
context = col_character()
)
i Use `spec()` for the full column specifications.
First predictive model
Linear model
Let’s start by a simple linear model to evaluate relationship between a particular measure Z2-Z1
and the context.
Call:
lm(formula = Z2mnZ1 ~ context, data = .)
Residuals:
Min 1Q Median 3Q Max
-4.301 -2.185 -1.383 3.507 4.994
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 6.2964 0.1903 33.094 < 2e-16 ***
contextGuttural -0.9849 0.2843 -3.464 0.00059 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 2.835 on 400 degrees of freedom
Multiple R-squared: 0.02912, Adjusted R-squared: 0.02669
F-statistic: 12 on 1 and 400 DF, p-value: 0.0005902
What this result is telling us is that there is a statistically significant decrease in Z2-Z1
in the guttural class when compared with the non-guttural (ignoring the fact that we need mixed modelling to evaluate the results). Now let’s ask the question: with this linear model, can you predict percentage separation between the two classes? Are you able to tell if the differences observed between the two classes are meaningful? This is where we need to change our way of looking at the data. Let’s look at GLM as a classification tool as above
Correlation tests
The correlation plot above shows which predictors are correlated with each other (positively or negatively). They are organised by hierarchical clustering that identifies clusters of predictors correlated with each other.
Correlation tests
Below, we look at two predictors that are correlated with each other: Z3-Z2
(F3-F2 in Bark) and A2*-A3*
(normalised amplitude differences between harmonics closest to F2 and F3). The results of the correlation test shows the two predictors to negatively correlate with each other at a rate of -0.87.
Pearson's product-moment correlation
data: dfPharV2$Z3mnZ2 and dfPharV2$A2mnA3
t = -35.864, df = 400, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
-0.8947506 -0.8480066
sample estimates:
cor
-0.8733751
Plots to visualise the data
As we see from the plot, Z3-Z2
is higher in the guttural, whereas A2*-A3*
is lower.
Decision Trees
Decision trees are a statistical tool that uses the combination of predictors to identify patterns in the data and provides classification accuracy for the model.
The decision tree used is based on conditional inference trees
that looks at each predictor and splits the data into multiple nodes (branches) through recursive partitioning in a tree-structured regression model
. Each node is also split into leaves (difference between levels of outcome).
Decision trees via ctree
does the following:
- Test global null hypothesis of independence between predictors and outcome.
- Select the predictor with the strongest association with the outcome measured based on a multiplicity adjusted p-values with Bonferroni correction
- Implement a binary split in the selected input variable.
- Recursively repeat steps 1), 2). and 3).
Let’s see this in an example using the same dataset. To understand what the decision tree is doing, we will dissect it, by creating one tree with one predictor and move to the next.
Individual trees
Tree 1
Conditional inference tree with 4 terminal nodes
Response: context
Input: Z2mnZ1
Number of observations: 402
1) Z2mnZ1 <= 9.551456; criterion = 0.999, statistic = 11.678
2) Z2mnZ1 <= 6.779068; criterion = 1, statistic = 12.368
3) Z2mnZ1 <= 4.004879; criterion = 1, statistic = 56.773
4)* weights = 157
3) Z2mnZ1 > 4.004879
5)* weights = 106
2) Z2mnZ1 > 6.779068
6)* weights = 64
1) Z2mnZ1 > 9.551456
7)* weights = 75
How to interpret this figure? Let’s look at mean values and a plot for this variable. This is the difference between F2
and F1
using the bark scale. Because gutturals are produced within the pharynx (regardless of where), the predictions is that a high F1
and a low F2
will be the acoustic correlates related to this constriction location. The closeness between these formants yields a lower Z2-Z1
. Hence, the prediction is as follow: the smaller the difference, the more pharyngeal-like constriction these consonants have (all else being equal!). Let’s compute the mean/median and plot the difference between the two contexts.
The table above reports the mean and median of Z2-Z1
for both levels of context and the plots show the difference between the two. We have a total of 180 cases in the guttural
, and 222 in the non-guttural
. When considering the conditional inference tree output, various splits were obtained. The first is any value higher than 9.55 being assigned to the non-guttural
class (around 98% of 75 cases) Then, with anything lower than 9.55, a second split was obtained. A threshold of 6.78: higher assigned to guttural
(around 98% of 64 cases), lower, were split again with a threshold of 4 Bark. A third split was obtained: values lower of equal to 4 Bark are assigned to the guttural
(around 70% of 157 cases) and values higher than 4 Barks assigned to the non-guttural
(around 90% of 106 cases).
Dissecting the tree like this allows interpretation of the output. In this example, this is quite a complex case and ctree
allowed to fine tune the different patterns seen with Now let’s look at the full dataset to make sense of the combination of predictors to the difference.
Model 1
Model specification
Conditional inference tree with 8 terminal nodes
Response: context
Inputs: CPP, Energy, H1A1c, H1A2c, H1A3c, H1H2c, H2H4c, H2KH5Kc, H42Kc, HNR05, HNR15, HNR25, HNR35, SHR, soe, Z1mnZ0, Z2mnZ1, Z3mnZ2, Z4mnZ3, F0Bark, A1mnA2, A1mnA3, A2mnA3
Number of observations: 402
1) A2mnA3 <= -13.78; criterion = 1, statistic = 42.329
2) Z4mnZ3 <= 1.592125; criterion = 1, statistic = 40.991
3) H2H4c <= -8.396333; criterion = 0.993, statistic = 13.141
4)* weights = 8
3) H2H4c > -8.396333
5)* weights = 100
2) Z4mnZ3 > 1.592125
6) Energy <= 2.8295; criterion = 0.999, statistic = 16.923
7)* weights = 25
6) Energy > 2.8295
8)* weights = 10
1) A2mnA3 > -13.78
9) H1H2c <= 10.27167; criterion = 0.953, statistic = 9.458
10) SHR <= 0.1566667; criterion = 1, statistic = 18.337
11)* weights = 99
10) SHR > 0.1566667
12) H1H2c <= 0.7411667; criterion = 0.972, statistic = 10.449
13)* weights = 103
12) H1H2c > 0.7411667
14)* weights = 30
9) H1H2c > 10.27167
15)* weights = 27
How to interpret this complex decision tree?
Let’s obtain the median value for each predictor grouped by context. Discuss some of the patterns.
We started with context
as our outcome, and all 23 acoustic measures as predictors. A total of 8 terminal nodes were identified with multiple binary splits in their leaves, allowing separation of the two categories. Looking specifically at the output, we observe a few things.
The first node was based on A2*-A3*
, detecting a difference between non-gutturals and gutturals. For the first binary split, a threshold of -13.78 Bark was used (mean non guttural = -7.86; mean guttural = -14.58), then for values lower of equal to this threshold, a second split was performed using Z4-Z3
(mean non guttural = 1.67; mean guttural = 1.43) with any value smaller and equal to 1.59, then another binary split using H2*-H4*
, etc…
Once done, the ctree
provides multiple binary splits into guttural or non-guttural.
Any possible issues/interesting patterns you can identify? Look at the interactions between predictors.
Predictions from the full model
Let’s obtain some predictions from the model and evaluate how successful it is in dealing with the data.
pred.ctree Non-Guttural Guttural
Non-Guttural 194 41
Guttural 28 139
Setting levels: control = Non-Guttural, case = Guttural
Setting direction: controls < cases
Call:
roc.default(response = dfPharV2$context, predictor = as.numeric(pred.ctree))
Data: as.numeric(pred.ctree) in 222 controls (dfPharV2$context Non-Guttural) < 180 cases (dfPharV2$context Guttural).
Area under the curve: 0.823
This full model has a classification accuracy of 82.8%.This is not bad!! It has a relatively moderate specificity at 0.77 (at detecting the gutturals) but a high sensitivity at 0.87 (at detecting the non-gutturals). The ROC curve shows the relationship between the two with an AUC of 0.823
Up to you..
Try and fit two decision trees, the first on bark difference metrics and the second on voice quality metrics. Use the tidyverse
approach to 1) select predictors and 2) to run ctree.
Random selection
One important issue is that the trees we grew above are biased. They are based on the full dataset, which means they are very likely to overfit the data. We did not add any random selection and we only grew one tree each time. If you think about it, is it possible that we obtained such results simply by chance?
What if we add some randomness in the process of creating a conditional inference tree?
We change a small option in ctree
to allow for random selection of variables, to mimic what Random Forests will do. We use controls
to specify mtry = 5
, which is the rounded square root of number of predictors.
Model 2
pred.ctree1 Non-Guttural Guttural
Non-Guttural 101 10
Guttural 121 170
Setting levels: control = Non-Guttural, case = Guttural
Setting direction: controls < cases
Call:
roc.default(response = dfPharV2$context, predictor = as.numeric(pred.ctree1))
Data: as.numeric(pred.ctree1) in 222 controls (dfPharV2$context Non-Guttural) < 180 cases (dfPharV2$context Guttural).
Area under the curve: 0.6997
Can you compare results between you and discuss what is going on?
When adding one random selection process to our ctree
, we allow it to obtain more robust predictions. We could even go further and grow multiple small trees with a portion of datapoints (e.g., 100 rows, 200 rows). When doing these multiple random selections, you are growing multiple trees that are decorrelated from each other. These become independent trees and one can combine the results of these trees to come with clear predictions.
This is how Random Forests work. You would start from a dataset, then grow multiple trees, vary number of observations used (nrow), and number of predictors used (mtry), adjust branches, and depth of nodes and at the end, combine the results in a forest. You can also run permutation tests to evaluate contributions of each predictor to the outcome. This is the beauty of Random Forests. They do all of these steps automatically at once for you!
Random Forests
As their name indicate, a Random Forest is a forest of trees implemented through bagging ensemble algorithms. Each tree has multiple branches (nodes), and will provide predictions based on recursive partitioning of the data. Then using the predictions from the multiple grown trees, Random Forests will create averaged
predictions and come up with prediction accuracy, etc.
There are multiple packages that one can use to grow Random Forests:
randomForest
: The original implementation of Random Forests.
party
and partykit
: using conditional inference trees as base learners
ranger
: a reimplementation of Random Forests; faster and more flexible than original implementation
The first implementation of Random Forests is widely used in research. One of the issues in this first implementation is that it favoured specific types of predictors (e.g., categorical predictors, predictors with multiple cut-offs, etc). Random Forests grown via Conditional Inference Trees as implemented in party
guard against this bias, but they are computationally demanding. Random Forests grown via permutation tests as implemented in ranger
speed up the computations and can mimic the unbiased selection process.
Party
Random Forests grown via conditional inference trees, are different from the original implementation. They offer an unbiased selection process that guards against overfitting of the data. There are various points we need to consider in growing the forest, including number of trees and predictors to use each time. Let us run our first Random Forest via conditional inference trees. To make sure the code runs as fast as it can, we use a very low number of trees: only 100 It is well known that the more trees you grow, the more confidence you have in the results, as model estimation will be more stable. In this example, I would easily go with 500 trees..
Model specification
To grow the forest, we use the function cforest
. We use all of the dataset for the moment. We need to specify a few options within controls:
ntree = 100
= number of trees to grow. Default = 500.
mtry = round(sqrt(23))
: number of predictors to use each time. Default is 5, but specifying it is advised to account for the structure of the data
By default, cforest_unbiased
has two additional important options that are used for an unbiased selection process. WARNING: you should not change these unless you know what you are doing. Also, by default, the data are split into a training and a testing set. The training is equal to 2/3s of the data; the testing is 1/3.
replace = FALSE
= Use subsampling with or without replacement. Default is FALSE
, i.e., use subsets of the data without replacing these.
fraction = 0.632
= Use 63.2% of the data in each split.
Predictions
To obtain predictions from the model, we use the predict
function and add OOB = TRUE
. This uses the out-of-bag sample (i.e., 1/3 of the data).
pred.cforest Non-Guttural Guttural
Non-Guttural 204 40
Guttural 18 140
Setting levels: control = Non-Guttural, case = Guttural
Setting direction: controls < cases
Call:
roc.default(response = dfPharV2$context, predictor = as.numeric(pred.cforest))
Data: as.numeric(pred.cforest) in 222 controls (dfPharV2$context Non-Guttural) < 180 cases (dfPharV2$context Guttural).
Area under the curve: 0.8483
Compared with the 82.8% classification accuracy we obtained using ctree
using our full dataset above (model 1), here we obtain 85.5% with an 2.7% increase. Compared with the 67.4% from model 2 from ctree
with random selection of predictors, we have an 18.1% increase in classification accuracy!
We could test whether there is statistically significant difference between our ctree
and cforest
models. Using the ROC curves, the roc.test
conducts a non-parametric Z test of significance on the correlated ROC curves. The results show a statistically significant improvement using the cforest
model. This is normal because we are growing 100 different trees, with random selection of both predictors and samples and provide an averaged
prediction.
DeLong's test for two correlated ROC curves
data: roc.ctree and roc.cforest
Z = -1.1556, p-value = 0.2478
alternative hypothesis: true difference in AUC is not equal to 0
sample estimates:
AUC of roc1 AUC of roc2
0.8230480 0.8483483
DeLong's test for two correlated ROC curves
data: roc.ctree1 and roc.cforest
Z = -6.3835, p-value = 1.731e-10
alternative hypothesis: true difference in AUC is not equal to 0
sample estimates:
AUC of roc1 AUC of roc2
0.6996997 0.8483483
Variable Importance Scores
One important feature in ctree
was to show which predictor was used first is splitting the data, which was then followed by the other predictors. We use a similar functionality with cforest
to obtain variable importance scores to pinpoint strong
and weak
predictors.
There are two ways to obtain this:
- Simple permutation tests (conditional = FALSE)
- Conditional permutation tests (conditional = TRUE)
The former is generally comparable across packages and provides a normal permutation test; the latter runs a permutation test on a grid defined by the correlation matrix and corrects for possible collinearity. This is similar to a regression analysis, but looks at both main effects and interactions.
You could use the normal varimp
as implemented in party
. This uses mean decrease in accuracy scores. We will use variable importance scores via an AUC based permutation tests as this uses both accuracy and errors in the model, using varImpAUC
from the varImp
package.
DANGER ZONE: using conditional permutation test requires a lot of RAMs, unless you have access to a cluster, and/or a lot of RAMs, do not attempt running it. We will run the non-conditional version here for demonstration.
Warning in for (per in 1:nperm) { :
closing unused connection 4 (<-JalalXPS15:11119)
Warning in for (per in 1:nperm) { :
closing unused connection 3 (<-JalalXPS15:11119)
The Variable Importance Scores via non-conditional permutation tests showed that A2*-A3*
(i.e., energy in mid-high frequencies around F2 and F3) is the most important variable at explaining the difference between gutturals and non-gutturals, followed by Z4-Z3
(pharyngeal constriction), H1*-A3*
(energy in mid-high frequency component), Z2-Z1
(degree of compactness), Z3-Z2
(spectral divergence), H1*-A2
(energy in mid frequency component) and Z1-Z0
(degree of openness). All other predictors contribute to the contrast but to varying degrees (from H1*-H2*
to H1*-A1*
). The last 5 predictors are the least important and and the CPP has a 0 mean decrease in accuracy and can even be ignored.
Conclusion
The party
package is powerful at growing Random Forests via conditional Inference trees, but is computationally prohibitive when increasing number of trees and using conditional permutation tests of variable importance scores. We look next at the package ranger
due to its speed in computation and flexibility.
Ranger
The ranger
package proposes a reimplementation of the original Random Forests algorithms, written in C++ and allows for parallel computing. It offers more flexibility in terms of model specification.
Model specification
In the model below specification below, there are already a few options we are familiar with, with additional ones described below:
num.tree
= Number of trees to grow. We use the default value
mtry
= Number of predictors to use. Default = floor(sqrt(Variables))
. For compatibility with party
, we use round(sqrt(23))
replace = FALSE
= Use subsampling with or without replacement. Default replace = TRUE
, i.e., is with replacement.
sample.fraction = 0.632
= Use 63.2% of the data in each split. Default is full dataset, i.e., sample.fraction = 1
importance = "permutation"
= Compute variable importance scores via permutation tests
scale.permutation.importance = FALSE
= whether to scale variable importance scores to be out of 100%. Default is TRUE. This is likely to introduce biases in variable importance estimation.
splitrule = "extratrees"
= rule used for splitting trees.
num.threads
= allow for parallel computing. Here we only specify 1 thread, but can use all thread on your computer (or cluster).
We use options 2-7 to make sure we have an unbiased selection process with ranger
. You can try on your own running the model below by using the defaults to see how the rate of classification increases more, but with the caveat that it has a biased selection process.
Ranger result
Call:
ranger(context ~ ., data = ., num.trees = 500, mtry = round(sqrt(23)), replace = FALSE, sample.fraction = 0.632, importance = "permutation", scale.permutation.importance = FALSE, splitrule = "extratrees", num.threads = 1)
Type: Classification
Number of trees: 500
Sample size: 402
Number of independent variables: 23
Mtry: 5
Target node size: 1
Variable importance mode: permutation
Splitrule: extratrees
Number of random splits: 1
OOB prediction error: 8.21 %
Results of our Random Forest shows an OOB (Out-Of-Bag) error rate of 8.2%, i.e., an accuracy of 91.8%.
Going further
Unfortunately, when growing a tree with ranger
, we cannot use predictions from the OOB sample as there is no comparable options to do so on the predictions. We need to hard-code this. We split the data into a training and a testing sets. The training will be on 2/3s of the data; the testing is on the remaining 1/3.
Create a training and a testing set
Model specification
We use the same model specification as above, except from using the training set and saving the forest (with write.forest = TRUE
).
set.seed(123456)
mdl.ranger2 <- gutt.train %>%
ranger(context ~ ., data = ., num.trees = 500, mtry = round(sqrt(23)),
replace = FALSE, sample.fraction = 0.632,
importance = "permutation", scale.permutation.importance = FALSE,
splitrule = "extratrees", num.threads = 1, write.forest = TRUE)
mdl.ranger2
Ranger result
Call:
ranger(context ~ ., data = ., num.trees = 500, mtry = round(sqrt(23)), replace = FALSE, sample.fraction = 0.632, importance = "permutation", scale.permutation.importance = FALSE, splitrule = "extratrees", num.threads = 1, write.forest = TRUE)
Type: Classification
Number of trees: 500
Sample size: 268
Number of independent variables: 23
Mtry: 5
Target node size: 1
Variable importance mode: permutation
Splitrule: extratrees
Number of random splits: 1
OOB prediction error: 9.33 %
With the training set, we have an OOB error rate of 9.3%; i.e., an accuracy rate of 90.7%.
Predictions
For the predictions, we use the testing set as a validation set. This is to be considered as a true reflection of the model. This is unseen data not used in the training set.
Non-Guttural Guttural
Non-Guttural 70 15
Guttural 3 46
Setting levels: control = Non-Guttural, case = Guttural
Setting direction: controls < cases
Call:
roc.default(response = gutt.test$context, predictor = as.numeric(pred.ranger2$predictions))
Data: as.numeric(pred.ranger2$predictions) in 73 controls (gutt.test$context Non-Guttural) < 61 cases (gutt.test$context Guttural).
Area under the curve: 0.8565
The classification rate based on the testing set is 86.6%. This is comparable to the one we obtained with cforest
. The changes in the settings allow for similarities in the predictions obtained from both party
and ranger
.
Variable Importance Scores
Default
For the variable importance scores, we obtain them from either the training set or the full model above.
There are similarities between cforest
and ranger
, with minor differences. Z2-Z1
is the best predictor at explaining the differences between gutturals and non-gutturals with ranger
followed by Z3-Z2
and then A2*-A3*
, (reverse with cforest
!). The order of the additional predictors is sightly different between the two models. This is expected as the cforest
model only used 100 trees, whereas the ranger
model used 500 trees.
A clear difference between the packages party
and ranger
is that the former allows for conditional permutation tests for variable importance scores; this is absent from ranger
. However, there is a debate in the literature on whether correlated data are harmful within Random Forests. It is clear that how Random Forests work, i.e., the randomness in the selection process in number of data points, predictors, splitting rules, etc. allow the trees to be decorrelated from each other. Hence, the conditional permutation tests may not be required. But what they offer is to condition variable importance scores on each other (based on correlation tests) to mimic what a multiple regression analysis does (but without suffering from suppression!). Strong predictors will show major contribution, while weak ones will be squashed giving them extremely low (or even negative) scores. Within ranger
, it is possible to evaluate this by estimating p values associated with each variable importance.We use the altman
method. See documentation for more details.
DANGER ZONE: This requires heavy computations. Use with all cores on your machine or in the cluster. Recommendations are to use a minimum of 100 permutations or more, i.e., num.permutations = 100
. Here, we only use 20 to show the output.
With p values
importance pvalue
CPP 0.002727273 0.14285714
Energy 0.015535354 0.04761905
H1A1c 0.014929293 0.04761905
H1A2c 0.029979798 0.04761905
H1A3c 0.035979798 0.04761905
H1H2c 0.014303030 0.04761905
H2H4c 0.010060606 0.04761905
H2KH5Kc 0.013434343 0.04761905
H42Kc 0.013434343 0.04761905
HNR05 0.007939394 0.04761905
HNR15 0.013838384 0.04761905
HNR25 0.013030303 0.04761905
HNR35 0.014222222 0.04761905
SHR 0.013313131 0.04761905
soe 0.008969697 0.04761905
Z1mnZ0 0.028505051 0.04761905
Z2mnZ1 0.066808081 0.04761905
Z3mnZ2 0.043232323 0.04761905
Z4mnZ3 0.039858586 0.04761905
F0Bark 0.009838384 0.04761905
A1mnA2 0.015070707 0.04761905
A1mnA3 0.027696970 0.04761905
A2mnA3 0.041313131 0.04761905
Of course, the output above shows variable p values. The lowest is at 0.048 for all predictors; one at 0.14 for CPP. Recall that CPP received the lowest variable importance score within ranger
and cforest
. If you increase permutations to 100 or 200, you will get more confidence in your results and can report the p values
Up to you!
Following what we have done above with decision trees, run a Random Forest either with party
or ranger
on formants or voice quality. Discuss with your peers the results and we can discuss any issues.. You need to 1) select variables and 2) run the code. Don’t forget, you can simply copy and paste the code, but it is best to try to recall the call functions.
In the next part, we look at the tidymodels
and introduce their philosophy.
Random forests with Tidymodels
The tidymodels
are a bundle of packages used to streamline and simplify the use of machine learning. The tidymodels
are not restricted to Random Forests, and you can even use them to run simple linear models, logistic regressions, PCA, Random Forests, Deep Learning, etc.
The tidymodels
’ philosophy is to separate data processing on the training and testing sets, and use of a workflow. Below, is an full example of how one can run Random Forests with via ranger
using the tidymodels
.
Training and testing sets
We start by creating a training and a testing set using the function initial_split
. Using strata = context
allows the model to split the data taking into account its structure and splits the data according to proportions of each group.
<Analysis/Assess/Total>
<270/132/402>
Set for cross-validation
We can (if we want to), create a 10-folds cross-validation on the training set. This allows to fine tune the training by obtaining the forest with the highest accuracy. This is a clear difference with ranger
. While it is not impossible to hard code that, tidymodels
simplify it for us!!
Model Specification
Within the model specification, we need to specify multiple options:
- A
recipe
: This is the recipe and is related to any data processing one wants to apply on the data.
- An
engine
: We need to specify the engine
to use. Here we want to run a Random Forest.
- A
tuning
: Here we can tune our engine
- A
workflow
: here we specify the various steps of the workflow
Recipe
When defining the recipe, you need to think of the type of “transformations” you will apply to your data.
- Z-scoring is the first thing that comes to mind. When you z-score the data, you are allowing all strong and weak predictors to be considered equally by the model. This is important as some of our predictors have very large differences related to the levels of context and have different measurement scales. We could have applied it above, but we need to make sure to apply it separately on both training and testing sets (otherwise, our model suffers from data leakage)
- If you have any missing data, you can use central imputations to fill in missing data (random forests do not like missing data, though they can work with them).
- You can apply PCA on all your predictors to remove collinearity before running random forests. This is a great option to consider, but adds more complexity to your model. 4.Finally, if you have categorical predictors, you can transform them into dummy variables using
step_dummy()
: 1s and 2s for binary; or use one-hot-encoding step_dummy(predictor, one_hot = TRUE)
See documentations of tidymodels
for what you can apply!!
Once we have prepared the recipe
, we can bake it
to see the changes applied to it.
Setting the engine
We set the engine here as a rand_forest
. We specify a classification mode. Then, we set an engine with engine specific parameters.
set.seed(123456)
engine_tidym <- rand_forest(
mode = "classification",
## to tune both mtry and trees, uncomment the two lines below and comment the next two lines
##mtry %>% tune(),
##trees %>% tune(),
mtry = round(sqrt(23)),
trees = 500,
min_n = 1
) %>%
set_engine("ranger", importance = "permutation", sample.fraction = 0.632,
replace = FALSE, write.forest = T, splitrule = "extratrees",
scale.permutation.importance = FALSE) # we add engine specific settings
Settings for tuning
If we want to tune the model, then uncomment the lines below. It is important to use an mtry that hovers around the round(sqrt(Variables)). If you use all available variables, then your forest is biased as it is able to see all predictors. For number of trees, low numbers are not great, as you can easily underfit the data and not produce meaningful results. Large numbers are fine and Random Forests do not overfit (in theory).
The full dataset has around 2000 observations, and 23 predictors (well even more, but let’s ignore it for the moment). I tuned mtry
to be between 4 and 6, and trees
to be between 1000 and 5000 in a 30 step increment. In total, with a 10-folds cross validation, I grew 30 random forests on each fold for a total of 300 Random Forests on the training set!!! This of course will take a loooooong time to compute on your computer if using one thread. So use parallel computing or a cluster. When running in the cluster with 20 cores, each with 11GB RAMs, and it took around 260.442 seconds to run with 220GB RAMS! Of course, with smaller RAMs and number of cores, the code will still run butwill take longer.
Workflow
Now we define the workflow adding the recipe
and the model
.
Tuning and running model
Here we run the model starting with the workflow, the cross-validation sample, the tuning parameters and asking for specific metrics. Below, we receive one warning:
- We did not define any tuning, and receive a warning about it.
Above, we added an option to suppress any warning messages from doFuture
. If you do not use that option, you may receive a warning that some threads are not using correct random seeds. This is a false-positive well known to the developers. For more details, see this link, but also their github page; so you can ignore the warning.
No tuning parameters have been detected, performance will be evaluated using the resamples with no tuning. Did you want to [tune()] parameters?
user system elapsed
0.39 0.05 10.88
# Tuning results
# 10-fold cross-validation using stratification
Finalise model
We obtain the best performing model from cross-validation, then finalise the workflow by predicting the results on the testing set and obtain the results of the best performing model
Results
For the results, we can obtain various metrics on the training and testing sets.
Cross-validation on training set
Specificity
[1] 0.8358974
Confusion Matrix training set
Variable Importance
Best 10
All predictors
Gains curves
This is an interesting features that show how much is gained when looking at various portions of the data. We see a gradual increase in the values. When 50% of the data were tested, around 83% of the results within the non-guttural class were already identified. The more testing was performed, the more confidence in the results there are and then when 84.96% of the data were tested, 100% of the cases were found.
ROC Curves
session info
R version 4.0.3 (2020-10-10)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19041)
Matrix products: default
Random number generation:
RNG: L'Ecuyer-CMRG
Normal: Inversion
Sample: Rejection
locale:
[1] LC_COLLATE=English_United Kingdom.1252
[2] LC_CTYPE=English_United Kingdom.1252
[3] LC_MONETARY=English_United Kingdom.1252
[4] LC_NUMERIC=C
[5] LC_TIME=English_United Kingdom.1252
attached base packages:
[1] stats4 grid stats graphics grDevices utils
[7] datasets methods base
other attached packages:
[1] parallelly_1.23.0 doRNG_1.8.2 rngtools_1.5
[4] doFuture_0.12.0 future_1.21.0 foreach_1.5.1
[7] vip_0.3.2 lattice_0.20-41 varImp_0.4
[10] measures_0.3 pROC_1.17.0.1 yardstick_0.0.7
[13] workflows_0.2.1 tune_0.1.3 rsample_0.0.9
[16] recipes_0.1.15 parsnip_0.1.5 modeldata_0.1.0
[19] infer_0.5.4 dials_0.0.9 scales_1.1.1
[22] tidymodels_0.1.2 ranger_0.12.1 party_1.3-7
[25] strucchange_1.5-2 sandwich_3.0-0 zoo_1.8-8
[28] modeltools_0.2-23 mvtnorm_1.1-1 PresenceAbsence_1.1.9
[31] psycho_0.6.1 corrplot_0.84 knitr_1.31
[34] broom_0.7.5 forcats_0.5.1 stringr_1.4.0
[37] dplyr_1.0.5 purrr_0.3.4 readr_1.4.0
[40] tidyr_1.1.3 tibble_3.1.0 ggplot2_3.3.3
[43] tidyverse_1.3.0
loaded via a namespace (and not attached):
[1] TH.data_1.0-10 colorspace_2.0-0 ellipsis_0.3.1
[4] class_7.3-18 rio_0.5.26 fs_1.5.0
[7] rstudioapi_0.13 farver_2.1.0 listenv_0.8.0
[10] furrr_0.2.2 prodlim_2019.11.13 fansi_0.4.2
[13] lubridate_1.7.10 coin_1.4-1 xml2_1.3.2
[16] codetools_0.2-18 splines_4.0.3 libcoin_1.0-8
[19] jsonlite_1.7.2 dbplyr_2.1.0 compiler_4.0.3
[22] httr_1.4.2 backports_1.2.1 assertthat_0.2.1
[25] Matrix_1.3-2 cli_2.3.1 htmltools_0.5.1.1
[28] tools_4.0.3 gtable_0.3.0 glue_1.4.2
[31] Rcpp_1.0.6 carData_3.0-4 cellranger_1.1.0
[34] DiceDesign_1.9 vctrs_0.3.6 nlme_3.1-152
[37] iterators_1.0.13 timeDate_3043.102 xfun_0.21
[40] gower_0.2.2 globals_0.14.0 openxlsx_4.2.3
[43] rvest_0.3.6 lifecycle_1.0.0 MASS_7.3-53.1
[46] ipred_0.9-10 hms_1.0.0 parallel_4.0.3
[49] yaml_2.2.1 curl_4.3 gridExtra_2.3
[52] rpart_4.1-15 stringi_1.5.3 lhs_1.1.1
[55] hardhat_0.1.5 zip_2.1.1 lava_1.6.8.1
[58] rlang_0.4.10 pkgconfig_2.0.3 matrixStats_0.58.0
[61] evaluate_0.14 labeling_0.4.2 tidyselect_1.1.0
[64] plyr_1.8.6 magrittr_2.0.1 R6_2.5.0
[67] generics_0.1.0 multcomp_1.4-16 DBI_1.1.1
[70] mgcv_1.8-34 pillar_1.5.1 haven_2.3.1
[73] foreign_0.8-81 withr_2.4.1 survival_3.2-7
[76] abind_1.4-5 nnet_7.3-15 modelr_0.1.8
[79] crayon_1.4.1 car_3.0-10 utf8_1.1.4
[82] rmarkdown_2.7 readxl_1.3.1 data.table_1.14.0
[85] reprex_1.0.0 digest_0.6.27 GPfit_1.0-8
[88] munsell_0.5.0
LS0tDQp0aXRsZTogIkludHJvZHVjdGlvbiB0byBSYW5kb20gRm9yZXN0cyINCmF1dGhvcjogDQogIG5hbWU6ICJKYWxhbCBBbC1UYW1pbWkiDQogIGFmZmlsaWF0aW9uOiAiTmV3Y2FzdGxlIFVuaXZlcnNpdHkiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgaGlnaGxpZ2h0OiBweWdtZW50cw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2RlcHRoOiA2DQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiB0cnVlDQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogIA0KLS0tDQoNClRoaXMgbm90ZWJvb2sgcHJvdmlkZXMgZGV0YWlscyBvZiBob3cgdG8gZ3JvdyBSYW5kb20gRm9yZXN0cyBmb3IgcGhvbmV0aWMgZGF0YS4gVGhpcyBpcyBub3QgZXhjbHVzaXZlIHRvIHBob25ldGljIGRhdGEgYW5kIGNhbiBiZSB1c2VkIGZvciBhbnkgb3RoZXIgdHlwZSBvZiBkYXRhLiANCg0KV2Ugd2lsbCBzdGFydCBieSBsb29raW5nIGF0IHRoZSBiYXNpY3Mgb2YgcHJlZGljdGl2ZSBtb2RlbGxpbmcsIGJ5IGxvb2tpbmcgYXQgbG9naXN0aWMgcmVncmVzc2lvbiBhcyBhIGNsYXNzaWZpY2F0aW9uIHRvb2wgYW5kIHVzZSBzb21lIG5vdGlvbnMgZnJvbSB0aGUgU2lnbmFsIERldGVjdGlvbiBUaGVvcnkgKGFjY3VyYWN5LCBzZW5zaXRpdml0eSwgc3BlY2lmaWNpdHksIHJlY2FsbCwgZHByaW1lLCBBVUMpLiBXZSB0aGVuIGxvb2sgYXQgc29tZSBpc3N1ZXMgd2l0aCBsb2dpc3RpYyByZWdyZXNzaW9uIHJlbGF0ZWQgdG8gbXVsdGljb2xsaW5lYXJpdHkuIFdlIGludHJvZHVjZSB0aGVuIGRlY2lzaW9uIHRyZWVzIHRvIHVuZGVyc3RhbmQgaG93IHRoZXkgd29yaywgYmVmb3JlIGF0dGVtcHRpbmcgdG8gcmVwbGljYXRlIGhvdyBSYW5kb20gRm9yZXN0cyB3b3JrLiBXZSB0aGVuIGdyb3cgb3VyIGZpcnN0IFJhbmRvbSBGb3Jlc3RzIGFuZCB0cnkgdG8gY2FwdHVyZSBhcyBtdWNoIGluZm9ybWF0aW9uIGFzIHBvc3NpYmxlIGZyb20gaXQuIEF0IHRoZSBlbmQsIHdlIGxvb2sgYXQgdGhlIGFwcHJvYWNoIGFkdm9jYXRlZCBieSAgYHRpZHltb2RlbHNgLg0KQXQgdGhlIGVuZCBvZiB0aGUgc2Vzc2lvbiwgcGFydGljaXBhbnRzIHdpbGwgYmUgYWJsZSB0byBncm93IGEgUmFuZG9tIEZvcmVzdCBiYXNlZCBvbiB0aGVpciBvd24gZGF0YS4gDQoNCg0KDQojIExvYWRpbmcgcGFja2FnZXMgDQoNCg0KYGBge3Igd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9DQojIyBVc2UgdGhlIGNvZGUgYmVsb3cgdG8gY2hlY2sgaWYgeW91IGhhdmUgYWxsIHJlcXVpcmVkIHBhY2thZ2VzIGluc3RhbGxlZC4gSWYgc29tZSBhcmUgbm90IGluc3RhbGxlZCBhbHJlYWR5LCB0aGUgY29kZSBiZWxvdyB3aWxsIGluc3RhbGwgdGhlc2UuIElmIHlvdSBoYXZlIGFsbCBwYWNrYWdlcyBpbnN0YWxsZWQsIHRoZW4gdGhlIHNlY29uZCBsaW5lIG9mIGNvZGUgd2lsbCBsYWQgdGhlbS4NCg0KcmVxdWlyZWRQYWNrYWdlcyA9IGMoJ3RpZHl2ZXJzZScsICdicm9vbScsICdrbml0cicsICdjb3JycGxvdCcsICdwc3ljaG8nLCAnUHJlc2VuY2VBYnNlbmNlJywgJ3BhcnR5JywgJ3JhbmdlcicsICd0aWR5bW9kZWxzJywgJ3BST0MnLCAndmFySW1wJywgJ2xhdHRpY2UnLCAndmlwJywgJ2RvRnV0dXJlJywgJ2RvUk5HJywgJ3BhcmFsbGVsbHknKQ0KZm9yKHAgaW4gcmVxdWlyZWRQYWNrYWdlcyl7DQogIGlmKCFyZXF1aXJlKHAsY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkgaW5zdGFsbC5wYWNrYWdlcyhwKQ0KICBsaWJyYXJ5KHAsY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQ0KfQ0KDQoNCmBgYA0KDQojIERlY2xhcmUgcGFyYWxsZWwgY29tcHV0aW5nDQoNCmBSYCBpcyBhIHBvd2VyZnVsIHNvZnR3YXJlLiBJdCBpcyBkZXNpZ25lZCBieSBkZWZhdWx0IHRvIG9ubHkgdXNlIG9uZSBjb3JlIG9uIHlvdXIgbWFjaGluZS4gQXMgeW91IGtub3csIGFueSBsYXB0b3AvY29tcHV0ZXIgY29tZXMgd2l0aCBhdCBsZWFzdCB0d28gY29yZXMgKGlmIG5vdCBtb3JlKS4gTWluZSBoYXMgNCBwaHlzaWNhbCBjb3JlcyB3aXRoIG11bHRpaHRyZWFkaW5nIChpLmUuLCBhIHRvdGFsIG9mIDggbG9naWNhbCBjb3JlcykuIElmIEkgYW0gdG8gcnVuIGEgY29kZSB0aGF0IHJlcXVpcmVzIGhlYXZ5IGNvbXB1dGF0aW9ucywgSSB3aWxsIG5ldmVyIGV2ZXIgdXNlIG9uZSBjb3JlIChhcyBpdCB3aWxsIHRha2UgYWdlcyB0byBydW4pLiBUaGlzIGlzIHdoZXJlIHRoZSBhZGRpdGlvbmFsIHBvd2VyIG9mIFIgaXMgbmVlZGVkLiANCg0KV2UgY2FuIHVzZSBhbnkgcGFyYWxsZWwgY29tcHV0aW5nIHBhY2thZ2UuIEkgdXNlIHRoZSBgZG9GdXR1cmVgIHBhY2thZ2UgYXMgaXQgaXMgb25lIG9mIHRoZSBiZXN0IGJhY2tlbmQgcGFyYWxsZWwgY29tcHV0aW5nIHBhY2thZ2VzLiBJIGFtIGRlY2xhcmluZyBwYXJhbGxlbCBjb21wdXRpbmcgYmVsb3cgdXNpbmcgb25lIGNvcmUgb25seSwgYnV0IG9uIHlvdXIgbWFjaGluZSwgeW91IGNhbiB1c2UgYWxsIGNvcmVzDQoNCmBgYHtyfQ0KIyMgVG8gZGVmaW5lIHBhcmFsbGVsIGNvbXB1dGluZyBvbiB5b3VyIG1hY2hpbmUsIHVzZSB0aGUgcGFja2FnZSBkb0Z1dHVyZS4gVGhlIGNvZGUgYmVsb3cgc2hvd3MgaG93IHlvdSBjb3VsZCBkZWZpbmUgaXQgb24geW91ciBtYWNoaW5lLg0KIyAjRGVjbGFyZSBwYXJhbGxlbCBjb21wdXRpbmcgDQojIyBuY29yZXMgPC0gYXZhaWxhYmxlQ29yZXMoKQ0KbmNvcmVzIDwtIDINCmNhdChwYXN0ZTAoIk51bWJlciBvZiBjb3JlcyBhdmFpbGFibGUgZm9yIG1vZGVsIGNhbGN1bGF0aW9ucyBzZXQgdG8gIiwgbmNvcmVzLCAiLiIpKQ0KcmVnaXN0ZXJEb0Z1dHVyZSgpDQojY2wgPC0gcGFyYWxsZWw6Om1ha2VDbHVzdGVyKG5jb3JlcykNCmNsIDwtIHBhcmFsbGVsbHk6Om1ha2VDbHVzdGVyUFNPQ0sobmNvcmVzKQ0KcGxhbihjbHVzdGVyLCB3b3JrZXJzID0gY2wpDQpuY29yZXMNCmNsDQoNCiMgYmVsb3cgd2UgcmVnaXN0ZXIgb3VyIHJhbmRvbSBudW1iZXIgZ2VuZXJhdG9yLiBUaGlzIHdpbGwgbW9zdGx5IGJlIHVzZWQgd2l0aGluIHRoZSB0aWR5bW9kZWxzIGJlbG93LiBUaGlzIGFsbG93cyByZXBsaWNhdGlvbiBvZiB0aGUgcmVzdWx0cw0Kc2V0LnNlZWQoMTIzNDU2KQ0KIyBiZWxvdyB0byBzdXBwcmVzcyBhbnl3YXJuaW5ncyBmcm9tIGRvRnV0dXJlDQpvcHRpb25zKGRvRnV0dXJlLnJuZy5vbk1pc3VzZSA9ICJpZ25vcmUiKQ0KYGBgDQoNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KVGhlIG1ham9yaXR5IG9mIHJlc2VhcmNoIGluIHBob25ldGljcyBhbmQgbGluZ3Vpc3RpY3MgbG9va3MgYXQgdHJhZGl0aW9uYWwgc3RhdGlzdGljYWwgdGVjaG5pcXVlcyB0byBldmFsdWF0ZSBncm91cCBkaWZmZXJlbmNlcywgdXNpbmcgZWl0aGVyIGZyZXF1ZW50aXN0IG9yIEJheWVzaWFuIGZyYW1ld29ya3MuIFdoYXQgaXMgaW1wb3J0YW50IGluIHRoZXNlIGZyYW1ld29ya3MgaXMgdG8gYXNzZXNzIHdoZXRoZXIgYSBwYXJ0aWN1bGFyIG91dGNvbWUgY2FuIGJlIGV4cGxhaW5lZCBieSB0aGUgcG90ZW50aWFsIGRpZmZlcmVuY2VzIHRoYXQgZXhpc3QgaW4gYSBwcmVkaWN0b3IuIEZvciBleGFtcGxlLCB3ZSBtYXkgd2FudCB0byBleGFtaW5lIGlmIHRoZSBkaWZmZXJlbmNlcyBvYnNlcnZlZCBpbiBGMiBmcmVxdWVuY3kgYXQgdGhlIHZvd2VsIG1pZHBvaW50IGNhbiBiZSBleHBsYWluZWQgYnkgcGxhY2Ugb2YgYXJ0aWN1bGF0aW9uLCBvciBhZ2Ugb3IgZ2VuZGVyLiBGb3IgdGhpcyB3ZSB1c2UgYSBsaW5lYXIgcmVncmVzc2lvbiAod2l0aCBvciB3aXRob3V0IG1peGVkIGVmZmVjdHMgbW9kZWxsaW5nKSB3aXRoIHRoZSBmb2xsb3dpbmcgZm9ybXVsYTogYGxtKEYyIH4gcGxhY2UgKyBhZ2UgKyBnZW5kZXIpYC4gDQoNClRoaXMgYXBwcm9hY2ggY2FuIHBvdGVudGlhbGx5IGV4cGxhaW4gdGhlIGRpZmZlcmVuY2VzIGFzc29jaWF0ZWQgd2l0aCB0aGUgcHJlZGljdG9ycywgaS5lLiwgd2UgZXZhbHVhdGUgdGhlaXIgaW5mbHVlbmNlIG9uIHRoZSBvdXRjb21lLiBCdXQgbGV0J3MgYXNrIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb246IGNhbiB0aGUgb2JzZXJ2ZWQgZGlmZmVyZW5jZXMgb24gRjIgZnJlcXVlbmN5IGF0IHRoZSB2b3dlbCBtaWRwb2ludCBiZSBwcmVkaWN0aXZlIG9mIGdyb3VwIGRpZmZlcmVuY2VzIGFzc29jaWF0ZWQgd2l0aCBnZW5kZXI/IExldCdzIHB1dCBpdCBkaWZmZXJlbnRseTogY2FuIHdlIHByZWRpY3QgZ2VuZGVyIGRpZmZlcmVuY2VzIGJhc2VkIG9uIEYyIGZyZXF1ZW5jaWVzPyBXaGF0IHdvdWxkIGhhcHBlbiBpbiB0aGUgZnV0dXJlPyBUaGlzIGlzIHdoZXJlIHByZWRpY3RpdmUgbW9kZWxsaW5nIGNvbWVzIHRvIHRoZSByZXNjdWUuDQoNClByZWRpY3RpdmUgbW9kZWxsaW5nIGlzIGEgc3RhdGlzdGljYWwgdGVjaG5pcXVlIHRoYXQgdXNlcyBlaXRoZXIgdHJhZGl0aW9uYWwgb3IgbWFjaGluZSBsZWFybmluZyBhcHByb2FjaGVzIHRvIGV2YWx1YXRlIGdyb3VwIGRpZmZlcmVuY2UgYW5kIGhvdyB0aGV5IGluZm9ybSBncm91cCBzZXBhcmF0aW9uLiBUaGV5IGNhbiBiZSB1c2VkIHRvIHByZWRpY3QgY2hhbmdlcyBlaXRoZXIgb24gdGhlIGN1cnJlbnQgZGF0YSBvciBpbiB0aGUgZnV0dXJlLiBJZiB5b3UgYnVpbGQgYSBwcmVkaWN0aXZlIG1vZGVsIGJhc2VkIG9uIHByZS1leGlzdGluZyBkYXRhLCB5b3UgY2FuIHVzZSB0aGUgbW9kZWwncyBwcmVkaWN0aW9ucyB0bzoNCg0KMS4gVmFsaWRhdGUgdGhlIHJlc3VsdHMgb24gdGhlIGN1cnJlbnQgc2V0DQoyLiBQcmVkaWN0IHJlc3VsdHMgb24gbmV3IHJhbmdlcyAoZS5nLiwgaWYgeW91IGhhdmUgcmVzdWx0cyBvbiBhZ2UgZ3JvdXAgMzAtNTAsIGluIHRoZW9yeSwgeW91IGNvdWxkIHByZWRpY3QgdGhlIHBhdHRlcm5zIGZvciBhZ2UgMjAgb3IgNzApDQozLiBQcmVkaWN0IHJlc3VsdHMgb24gbmV3IGRhdGEsIGVpdGhlciBvbiB0aGUgdGVzdGluZyBzZXQgb3IgYW55IG5ldyBkYXRhIHlvdSBvYnRhaW4gaW4gdGhlIGZ1dHVyZS4gDQoNCg0KVG8gdW5kZXJzdGFuZCB0aGlzIGZ1cnRoZXIsIHdlIHdpbGwgc3RhcnQgd2l0aCBhIHNpbXBsZSBHZW5lcmFsaXNlZCBMaW5lYXIgTW9kZWwgcHJlZGljdGluZyBhIHBlcmNlcHRpb24gZXhwZXJpbWVudCBvbiBncmFtbWF0aWNhbGl0eSBqdWRnZW1lbnQsIGJlZm9yZSBtb3ZpbmcgdG8gRGVjaXNpb24gVHJlZXMgYW5kIFJhbmRvbSBGb3Jlc3RzLiANCg0KIyBHZW5lcmFsaXNlZCBMaW5lYXIgTW9kZWwNCg0KSGVyZSB3ZSB3aWxsIGxvb2sgYXQgYW4gZXhhbXBsZSB3aGVuIHRoZSBvdXRjb21lIGlzIGJpbmFyeS4gVGhpcyBzaW11bGF0ZWQgZGF0YSBpcyBzdHJ1Y3R1cmVkIGFzIGZvbGxvd3MuIFdlIGFza2VkIG9uZSBwYXJ0aWNpcGFudCB0byBsaXN0ZW4gdG8gMTY1IHNlbnRlbmNlcywgYW5kIHRvIGp1ZGdlIHdoZXRoZXIgdGhlc2UgYXJlICJncmFtbWF0aWNhbCIgb3IgInVuZ3JhbW1hdGljYWwiLiBUaGVyZSB3ZXJlIDEwNSBzZW50ZW5jZXMgdGhhdCB3ZXJlICJncmFtbWF0aWNhbCIgYW5kIDYwICJ1bmdyYW1tYXRpY2FsIi4gVGhpcyBmaWN0aXRpb3VzIGV4YW1wbGUgY2FuIGFwcGx5IGluIGFueSBvdGhlciBzaXR1YXRpb24uIExldCdzIHRoaW5rIEdlb2dyYXBoeTogMTY1IGxhbmRzOiAxMDUgImZsYXQiIGFuZCA2MCAibm9uLWZsYXQiLCBldGMuIFRoaXMgYXBwbGllcyB0byBhbnkgY2FzZSB3aGVyZSB5b3UgbmVlZCB0byAiY2F0ZWdvcmlzZSIgdGhlIG91dGNvbWUgaW50byB0d28gZ3JvdXBzLiANCg0KIyMgTG9hZCBhbmQgc3VtbWFyaWVzDQoNCkxldCdzIGxvYWQgaW4gdGhlIGRhdGEgYW5kIGRvIHNvbWUgYmFzaWMgc3VtbWFyaWVzDQoNCmBgYHtyfQ0KZ3JhbW1hdGljYWwgPC0gcmVhZF9jc3YoImdyYW1tYXRpY2FsLmNzdiIpDQpncmFtbWF0aWNhbA0Kc3RyKGdyYW1tYXRpY2FsKQ0KaGVhZChncmFtbWF0aWNhbCkNCmBgYA0KDQojIyBOb3JtYWwgR0xNDQoNCkxldCdzIHJ1biBhIGZpcnN0IEdMTSAoR2VuZXJhbGlzZWQgTGluZWFyIE1vZGVsKS4gQSBHTE0gdXNlcyBhIHNwZWNpYWwgZmFtaWx5ICJiaW5vbWlhbCIgYXMgaXQgYXNzdW1lcyB0aGUgb3V0Y29tZSBoYXMgYSBiaW5vbWlhbCBkaXN0cmlidXRpb24uIEluIGdlbmVyYWwsIHJlc3VsdHMgZnJvbSBhIExvZ2lzdGljIFJlZ3Jlc3Npb24gYXJlIGNsb3NlIHRvIHdoYXQgd2UgZ2V0IGZyb20gU0RUIChzZWUgYWJvdmUpLg0KDQpUbyBydW4gdGhlIHJlc3VsdHMsIHdlIHdpbGwgY2hhbmdlIHRoZSByZWZlcmVuY2UgbGV2ZWwgZm9yIGJvdGggcmVzcG9uc2UgYW5kIGdyYW1tYXRpY2FsaXR5LiBUaGUgYmFzaWMgYXNzdW1wdGlvbiBhYm91dCBHTE0gaXMgdGhhdCB3ZSBzdGFydCB3aXRoIG91ciByZWZlcmVuY2UgbGV2ZWwgYmVpbmcgdGhlICJubyIgcmVzcG9uc2VzIHRvIHRoZSAidW5ncmFtbWF0aWNhbCIgY2F0ZWdvcnkuIEFueSBjaGFuZ2VzIHRvIHRoaXMgcmVmZXJlbmNlIHdpbGwgYmUgc2VlbiBpbiB0aGUgY29lZmZpY2llbnRzIGFzICJ5ZXMiIHJlc3BvbnNlcyB0byB0aGUgImdyYW1tYXRpY2FsIiBjYXRlZ29yeS4NCg0KIyMjIE1vZGVsIGVzdGltYXRpb24gYW5kIHJlc3VsdHMNCg0KVGhlIHJlc3VsdHMgYmVsb3cgc2hvdyB0aGUgbG9nb2RkcyBmb3Igb3VyIG1vZGVsLiANCg0KYGBge3J9DQpncmFtbWF0aWNhbCA8LSBncmFtbWF0aWNhbCAlPiUgDQogIG11dGF0ZShyZXNwb25zZSA9IGZhY3RvcihyZXNwb25zZSwgbGV2ZWxzID0gYygibm8iLCAieWVzIikpLA0KICAgICAgICAgZ3JhbW1hdGljYWxpdHkgPSBmYWN0b3IoZ3JhbW1hdGljYWxpdHksIGxldmVscyA9IGMoInVuZ3JhbW1hdGljYWwiLCAiZ3JhbW1hdGljYWwiKSkpDQoNCmdyYW1tYXRpY2FsICU+JSANCiAgZ3JvdXBfYnkoZ3JhbW1hdGljYWxpdHksIHJlc3BvbnNlKSAlPiUgDQogIHRhYmxlKCkNCg0KbWRsLmdsbSA8LSBncmFtbWF0aWNhbCAlPiUgDQogIGdsbShyZXNwb25zZSB+IGdyYW1tYXRpY2FsaXR5LCBkYXRhID0gLiwgZmFtaWx5ID0gYmlub21pYWwpDQpzdW1tYXJ5KG1kbC5nbG0pDQoNCnRpZHkobWRsLmdsbSkgJT4lIA0KICBzZWxlY3QodGVybSwgZXN0aW1hdGUpICU+JSANCiAgbXV0YXRlKGVzdGltYXRlID0gcm91bmQoZXN0aW1hdGUsIDMpKQ0KIyB0byBvbmx5IGdldCB0aGUgY29lZmZpY2llbnRzDQpteWNvZWYgPC0gdGlkeShtZGwuZ2xtKSAlPiUgcHVsbChlc3RpbWF0ZSkNCmBgYA0KDQoNClRoZSByZXN1bHRzIHNob3cgdGhhdCBmb3Igb25lIHVuaXQgaW5jcmVhc2UgaW4gdGhlIHJlc3BvbnNlIChpLmUuLCBmcm9tIG5vIHRvIHllcyksIHRoZSBsb2dvZGRzIG9mIGJlaW5nICJncmFtbWF0aWNhbCIgaXMgaW5jcmVhc2VkIGJ5IGByIG15Y29lZlsyXWAgKHRoZSBpbnRlcmNlcHQgc2hvd3MgdGhhdCB3aGVuIHRoZSByZXNwb25zZSBpcyAibm8iLCB0aGUgbG9nb2RkcyBhcmUgYHIgbXljb2VmWzFdYCkuIFRoZSBhY3R1YWwgbG9nb2RkcyBmb3IgdGhlIHJlc3BvbnNlICJ5ZXMiIHRvIGdyYW1tYXRpY2FsIGlzIGByIG15Y29lZlsxXSArIG15Y29lZlsyXWAgDQoNCg0KIyMjIExvZ09kZHMgdG8gcHJvcG9ydGlvbnMNCg0KSWYgeW91IHdhbnQgdG8gdGFsayBhYm91dCB0aGUgcGVyY2VudGFnZSAiYWNjdXJhY3kiIG9mIG91ciBtb2RlbCwgdGhlbiB3ZSBjYW4gdHJhbnNmb3JtIG91ciBsb2dnb2RkcyBpbnRvIHByb3BvcnRpb25zLiBUaGlzIHNob3dzIHRoYXQgdGhlIHByb3BvcnRpb24gb2YgImdyYW1tYXRpY2FsIiByZWNlaXZpbmcgYSAieWVzIiByZXNwb25zZSBpbmNyZWFzZXMgYnkgOTUlIA0KDQpgYGB7cn0NCnBsb2dpcyhteWNvZWZbMV0pDQpwbG9naXMobXljb2VmWzFdICsgbXljb2VmWzJdKQ0KYGBgDQoNCiMjIyBQbG90dGluZw0KDQpgYGB7cn0NCmdyYW1tYXRpY2FsIDwtIGdyYW1tYXRpY2FsICU+JSANCiAgbXV0YXRlKHByb2IgPSBwcmVkaWN0KG1kbC5nbG0sIHR5cGUgPSAicmVzcG9uc2UiKSkNCmdyYW1tYXRpY2FsICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gYXMubnVtZXJpYyhncmFtbWF0aWNhbGl0eSksIHkgPSBwcm9iKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAiZ2xtIiwgDQogICAgICAgICAgICAgIG1ldGhvZC5hcmdzID0gbGlzdChmYW1pbHkgPSAiYmlub21pYWwiKSwgc2UgPSBUKSArIA0KICB0aGVtZV9idyhiYXNlX3NpemUgPSAyMCkgKw0KICBsYWJzKHkgPSAiUHJvYmFiaWxpdHkiLCB4ID0gIiIpICsNCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDAsIDEpLCB4bGltID0gYygyLjA1LCAwLjk1KSwgZXhwYW5kID0gRkFMU0UpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHMgPSBjKCJVR3JhbW0iLCAiR3JhbW0iKSkNCmBgYA0KDQojIyBBY2N1cmFjeSBhbmQgU2lnbmFsIERldGVjdGlvbiBUaGVvcnkNCg0KIyMjIFJhdGlvbmFsZQ0KDQpXZSBhcmUgZ2VuZXJhbGx5IGludGVyZXN0ZWQgaW4gcGVyZm9ybWFuY2UsIGkuZS4sIHdoZXRoZXIgdGhlIHdlIGhhdmUgImFjY3VyYXRlbHkiIGNhdGVnb3Jpc2VkIHRoZSBvdXRjb21lIG9yIG5vdCBhbmQgYXQgdGhlIHNhbWUgdGltZSB3YW50IHRvIGV2YWx1YXRlIG91ciBiaWFzZXMgaW4gcmVzcG9uc2VzLiBXaGVuIGRlY2lkaW5nIG9uIGNhdGVnb3JpZXMsIHdlIGFyZSB1c3VhbGx5IGJpYXNlZCBpbiBvdXIgc2VsZWN0aW9uLiANCg0KTGV0J3MgYXNrIHRoZSBxdWVzdGlvbjogSG93IG1hbnkgb2YgeW91IGhhdmUgYSBNYWMgbGFwdG9wIGFuZCBob3cgbWFueSBhIFdpbmRvd3MgbGFwdG9wPyBGb3IgdGhvc2Ugd2l0aCBhIE1hYywgd2hhdCB3YXMgdGhlIG1haW4gcmVhc29uIGZvciBjaG9vc2luZyBpdD8gQXJlIHlvdSBiaWFzZWQgaW4gYW55d2F5IGJ5IHlvdXIgZGVjaXNpb24/IA0KDQpJZiB3ZSBnbyBiYWNrIHRvIG91ciBncmFtbWF0aWNhbCBleGFtcGxlIGFib3ZlLCB3ZSB3YW50IHRvIGV2YWx1YXRlIGlmIHRoZSBwYXJ0aWNpcGFudCB3YXMgYmlhc2VkIGluIGFueXdheSB3aGlsZSByZXNwb25kaW5nLiBUbyBjb3JyZWN0IGZvciB0aGVzZSBiaWFzZXMsIHdlIHVzZSBzb21lIHZhcmlhbnRzIGZyb20gU2lnbmFsIERldGVjdGlvbiBUaGVvcnkgdG8gb2J0YWluIHRoZSB0cnVlIGVzdGltYXRlcyB3aXRob3V0IGJlaW5nIGluZmx1ZW5jZWQgYnkgdGhlIGJpYXNlcy4gDQoNCiMjIyBSdW5uaW5nIHN0YXRzDQoNCkxldCdzIGRvIHNvbWUgc3RhdHMgb24gdGhpcyANCg0KfCAgfCBZZXMgfCBObyB8IFRvdGFsIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS18DQp8IEdyYW1tYXRpY2FsIChZZXMgQWN0dWFsKSB8IFRQID0gMTAwIHwgRk4gPSA1IHwgKFllcyBBY3R1YWwpIDEwNSB8DQp8IFVuZ3JhbW1hdGljYWwgKE5vIEFjdHVhbCkgIHwgRlAgPSAxMCB8IFROID0gNTAgfCAoTm8gQWN0dWFsKSA2MCB8DQp8IFRvdGFsIHwgKFllcyBSZXNwb25zZSkgMTEwIHwgKE5vIFJlc3BvbnNlKSA1NSB8IDE2NSB8DQoNCg0KVFAgPSBUcnVlIFBvc2l0aXZlIChIaXQpOyBGUCA9IEZhbHNlIFBvc2l0aXZlOyBGTiA9IEZhbHNlIE5lZ2F0aXZlOyBUTiA9IFRydWUgTmVnYXRpdmUNCg0KDQpgYGB7cn0NCmdyYW1tYXRpY2FsIDwtIGdyYW1tYXRpY2FsICU+JSANCiAgbXV0YXRlKHJlc3BvbnNlID0gZmFjdG9yKHJlc3BvbnNlLCBsZXZlbHMgPSBjKCJ5ZXMiLCAibm8iKSksDQogICAgICAgICBncmFtbWF0aWNhbGl0eSA9IGZhY3RvcihncmFtbWF0aWNhbGl0eSwgbGV2ZWxzID0gYygiZ3JhbW1hdGljYWwiLCAidW5ncmFtbWF0aWNhbCIpKSkNCg0KIyMgVFAgPSBUcnVlIFBvc2l0aXZlIChIaXQpOyBGUCA9IEZhbHNlIFBvc2l0aXZlOyBGTiA9IEZhbHNlIE5lZ2F0aXZlOyBUTiA9IFRydWUgTmVnYXRpdmUNCg0KDQpUUCA8LSBucm93KGdyYW1tYXRpY2FsICU+JSANCiAgICAgICAgICAgICBmaWx0ZXIoZ3JhbW1hdGljYWxpdHkgPT0gImdyYW1tYXRpY2FsIiAmDQogICAgICAgICAgICAgICAgICAgICAgcmVzcG9uc2UgPT0gInllcyIpKQ0KRk4gPC0gbnJvdyhncmFtbWF0aWNhbCAlPiUgDQogICAgICAgICAgICAgZmlsdGVyKGdyYW1tYXRpY2FsaXR5ID09ICJncmFtbWF0aWNhbCIgJg0KICAgICAgICAgICAgICAgICAgICAgIHJlc3BvbnNlID09ICJubyIpKQ0KRlAgPC0gbnJvdyhncmFtbWF0aWNhbCAlPiUgDQogICAgICAgICAgICAgZmlsdGVyKGdyYW1tYXRpY2FsaXR5ID09ICJ1bmdyYW1tYXRpY2FsIiAmDQogICAgICAgICAgICAgICAgICAgICAgcmVzcG9uc2UgPT0gInllcyIpKQ0KVE4gPC0gbnJvdyhncmFtbWF0aWNhbCAlPiUgDQogICAgICAgICAgICAgZmlsdGVyKGdyYW1tYXRpY2FsaXR5ID09ICJ1bmdyYW1tYXRpY2FsIiAmDQogICAgICAgICAgICAgICAgICAgICAgcmVzcG9uc2UgPT0gIm5vIikpDQpUUA0KRk4NCkZQDQpUTg0KDQpUb3RhbCA8LSBucm93KGdyYW1tYXRpY2FsKQ0KVG90YWwNCmBgYA0KDQojIyMjIEFjY3VyYWN5IHJhdGUNCg0KQWNjdXJhY3kgaXMgc2ltcGx5IHRoZSBzdW0gb2YgdHJ1ZSBwb3NpdGl2ZXMgYW5kIHRydWUgbmVnYXRpdmVzIGRpdmlkZWQgYnkgdGhlIHRvdGFsLiANCg0KYGBge3J9DQooVFArVE4pL1RvdGFsDQpgYGANCg0KIyMjIyBFcnJvciByYXRlDQoNClRoZSBlcnJvciByYXRlIGNhbiBiZSBjb21wdXRlZCBhcyAxLWFjY3VyYWN5IG9yIHN1bSBvZiBmYWxzZSBwb3NpdGl2ZXMgYW5kIGZhbHNlIG5lZ2F0aXZlcyBkaXZpZGVkIGJ5IHRoZSB0b3RhbC4NCg0KDQpgYGB7cn0NCihGUCtGTikvVG90YWwgIyBlcnJvciwgYWxzbyAxLWFjY3VyYWN5DQpgYGANCg0KIyMjIyBTcGVjaWZpY2l0eSANCg0KTGV0IHVzIGFzayB0aGUgZm9sbG93aW5nIHF1ZXN0aW9uOiBXaGVuIHRoZSAqKnN0aW11bHVzID0geWVzKiosIGhvdyBtYW55IHRpbWVzIHRoZSAqKnJlc3BvbnNlID0geWVzKio/IA0KDQpIZXJlIHdlIHF1YW50aWZ5IHRoZSB0cnVlIHBvc2l0aXZlIHJhdGUgb3Igc3BlY2lmaWNpdHkuIA0KDQpgYGB7cn0NClRQLyhUUCtGTikgIyBhbHNvIFRydWUgUG9zaXRpdmUgUmF0ZSBvciBTcGVjaWZpY2l0eQ0KYGBgDQoNCiMjIyMgRmFsc2UgcG9zaXRpdmUgcmF0ZQ0KDQpMZXQgdXMgYXNrIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb246IFdoZW4gdGhlICoqc3RpbXVsdXMgPSBubyoqLCBob3cgbWFueSB0aW1lcyB0aGUgKipyZXNwb25zZSA9IHllcyoqPyANCg0KYGBge3J9DQpGUC8oRlArVE4pICMgRmFsc2UgUG9zaXRpdmUgUmF0ZQ0KYGBgDQoNCiMjIyMgU2Vuc2l0aXZpdHkgDQoNClNlbnNpdGl2aXR5IHF1YW50aWZ5IHRoZSByZWplY3Rpb24gcmF0ZSBhbmQgZXZhbHVhdGVzIHRoZSBzZW5zaXRpdml0eSBvZiB0aGUgbW9kZS4gDQoNCkxldCB1cyBhc2sgdGhlIGZvbGxvd2luZyBxdWVzdGlvbjogV2hlbiB0aGUgKipzdGltdWx1cyA9IG5vKiosIGhvdyBtYW55IHRpbWVzIHRoZSAqKnJlc3BvbnNlID0gbm8qKj8NCg0KYGBge3J9DQpUTi8oRlArVE4pICMgVHJ1ZSBOZWdhdGl2ZSBSYXRlIG9yIFNlbnNpdGl2aXR5IA0KYGBgDQoNCiMjIyMgUHJlY2lzaW9uDQoNCkxldCB1cyBhc2sgdGhlIGZvbGxvd2luZyBxdWVzdGlvbjogV2hlbiB0aGUgc3ViamVjdCByZXNwb25kcyBgeWVzYCwgaG93IG1hbnkgdGltZXMgaXMgKHMpaGUgY29ycmVjdD8NCg0KYGBge3J9DQpUUC8oVFArRlApICMgcHJlY2lzaW9uDQpgYGANCg0KIyMjIyBEUHJpbWUgYW5kIG90aGVyIFNEVCBtZXRyaWNzDQoNCldlIHVzZSB0aGUgbGlicmFyeSBgcHN5Y2hvYCB0byBvYnRhaW4gU0RUIG1ldHJpY3MuIA0KDQpPbmUgb2YgdGhlIG1vc3QgaW1wb3J0YW50IG1ldHJpY3MgaXMgYGRwcmltZWAuIFRoaXMgaXMgdXN1YWxseSB1c2VkIGluIHBlcmNlcHRpb24gZXhwZXJpbWVudHMgYXMgaXQgYWxsb3dzIHRvIHRha2UgaW50byBhY2NvdW50IGJvdGggc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5LiBUaGlzIG1lYW5zLCBpdCBhbGxvd3MgdG8gY29ycmVjdCBmb3IgYmlhc2VzIGluIGVzdGltYXRpb24uIA0KDQpUaGUgdmFyaW91cyBtZXRyaWNzIGNvbXB1dGVkIGFyZToNCg0KMS4gYGRwcmltZWAgb3IgdGhlIHNlbnNpdGl2aXR5IGluZGV4LiBCZXR3ZWVuIC02IHRvICs2LiBwb3NpdGl2ZSA9IGFjY3VyYXRlIGRldGVjdGlvbjsgKzYgPSBtYXhpbXVtIGRldGVjdGlvbjsgMCBubyBkZXRlY3Rpb247IG5lZ2F0aXZlID0gbW9yZSBub2lzZSAoaS5lLiwgIm5vIiByZXNwb25zZXMpIGluIHRoZSBkYXRhDQoyLiBgYmV0YWAgb3IgdGhlIGJpYXMgY3JpdGVyaW9uLiBCZXR3ZWVuIDAtMTogbG93ZXIgPSBpbmNyZWFzZSBpbiAieWVzIiByZXNwb25zZXMNCjMuIGBBcHJpbWVgIG9yIGVzdGltYXRlIG9mIGRpc2NyaW1pbmFiaWxpdHkuIEJldHdlZW4gMC0xOiAxID0gZ29vZCBkaXNjcmltaW5hdGlvbjsgMCBpcyBhdCBjaGFuY2UNCjQuIGBicHBkYCBvciB0aGUgImIgcHJpbWUgcHJpbWUgZCIuIEJldHdlZW4gLTEgYW5kIDE6IDAgPSBubyBiaWFzLCBuZWdhdGl2ZSA9IHRlbmRlbmN5IHRvIHJlc3BvbmQgInllcyIsIHBvc2l0aXZlID0gdGVuZGVuY3kgdG8gcmVzcG9uZCAibm8iKQ0KNS4gYGNgIG9yIHRoZSBpbmRleCBvZiBiaWFzLiBlcXVhbHMgdG8gU0QuDQoNCkZvciBtb3JlIGRldGFpbHMsIHNlZSBbbGlua10oaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vY29tcHV0ZS1zaWduYWwtZGV0ZWN0aW9uLXRoZW9yeS1pbmRpY2VzLXdpdGgtci9hbXAvKSANCg0KRnJvbSBvdXIgcGVyc3BlY3RpdmUgaGVyZSwgdGhlIG1vc3QgaW1wb3J0YW50IGlzIGQtcHJpbWUgKGFuZCB0aGUgb3RoZXIgc2VzcGV0aXZpdHkvc3BlY2lmaWNpdHkgbWV0cmljcykuIA0KVGhpcyBpcyBtb2RlbGxpbmcgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgcmF0ZSBvZiAiVHJ1ZSBQb3NpdGl2ZSIgcmVzcG9uc2VzIGFuZCAiRmFsc2UgUG9zaXRpdmUiIHJlc3BvbnNlcyBpbiBzdGFuZGFyZCB1bml0IChvciB6LXNjb3JlcykuIFRoZSBmb3JtdWxhIGNhbiBiZSB3cml0dGVuIGFzOg0KDQpgZCcgKGQgcHJpbWUpID0gWihUcnVlIFBvc2l0aXZlIFJhdGUpIC0gWihGYWxzZSBQb3NpdGl2ZSBSYXRlKWANCg0KYGBge3J9DQpwc3ljaG86OmRwcmltZShUUCwgRlAsIEZOLCBUTiwgDQogICAgICAgICAgICAgICBuX3RhcmdldHMgPSBUUCtGTiwgDQogICAgICAgICAgICAgICBuX2Rpc3RyYWN0b3JzID0gRlArVE4sDQogICAgICAgICAgICAgICBhZGp1c3QgPSBGQUxTRSkNCg0KYGBgDQoNCg0KIyMjIEdMTSBhbmQgZCBwcmltZQ0KDQpUaGUgdmFsdWVzIG9idGFpbmVkIGhlcmUgbWF0Y2ggdGhvc2Ugb2J0YWluZWQgZnJvbSBTRFQuIEZvciBkIHByaW1lLCB0aGUgZGlmZmVyZW5jZSBzdGVtcyBmcm9tIHRoZSB1c2Ugb2YgdGhlIGxvZ2l0IHZhcmlhbnQgb2YgdGhlIEJpbm9taWFsIGZhbWlseS4gQnkgdXNpbmcgYSBwcm9iaXQgdmFyaWFudCwgb25lIG9idGFpbnMgdGhlIHNhbWUgdmFsdWVzIChbc2VlIGhlcmVdKGh0dHBzOi8vc3RhdHMuaWRyZS51Y2xhLmVkdS9yL2RhZS9wcm9iaXQtcmVncmVzc2lvbi8pIGZvciBtb3JlIGRldGFpbHMpLiBBIHByb2JpdCB2YXJpYW50IG1vZGVscyB0aGUgei1zY29yZSBkaWZmZXJlbmNlcyBpbiB0aGUgb3V0Y29tZSBhbmQgaXMgZXZhbHVhdGVkIGluIGNoYW5nZSBpbiAxLXN0YW5kYXJkIHVuaXQuIFRoaXMgaXMgbW9kZWxsaW5nIHRoZSBjaGFuZ2UgZnJvbSAidW5ncmFtbWF0aWNhbCIgIm5vIiByZXNwb25zZXMgaW50byAiZ3JhbW1hdGljYWwiICJ5ZXMiIHJlc3BvbnNlcyBpbiB6LXNjb3Jlcy4gVGhlIHNhbWUgY29uY2VwdHVhbCB1bmRlcnBpbm5pbmdzIG9mIGQtcHJpbWUgZnJvbSBTaWduYWwgRGV0ZWN0aW9uIFRoZW9yeS4NCg0KYGBge3J9DQojIyBkIHByaW1lDQpwc3ljaG86OmRwcmltZShUUCwgRlAsIEZOLCBUTiwgDQogICAgICAgICAgICAgICBuX3RhcmdldHMgPSBUUCtGTiwgDQogICAgICAgICAgICAgICBuX2Rpc3RyYWN0b3JzID0gRlArVE4sDQogICAgICAgICAgICAgICBhZGp1c3QgPSBGQUxTRSkkZHByaW1lDQoNCiMjIEdMTSB3aXRoIHByb2JpdA0KY29lZihnbG0ocmVzcG9uc2UgfiBncmFtbWF0aWNhbGl0eSwgZGF0YSA9IGdyYW1tYXRpY2FsLCBmYW1pbHkgPSBiaW5vbWlhbChwcm9iaXQpKSlbMl0NCg0KYGBgDQoNCg0KVGhlIGxvZ2l0IHZhcmlhbnQgb2YgdGhlIEdMTSByZXBvcnRzIHRoZSBjb2VmZmljaWVudHMgZm9yIHRoZSBUcnVlIFBvc2l0aXZlIFJhdGUgb3IgU3BlY2lmaWNpdHk7IHRoZSBwcm9iaXQgdmFyaWFudCByZXBvcnRzIG9uIGRwcmltZSwgb3IgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgVHJ1ZSBQb3NpdGl2ZSByZXNwb25zZXMgYW5kIHRoZSBGYWxzZSBQb3NpdGl2ZSByZXNwb25zZXMgaW4gc3RhbmRhcmQgdW5pdHMuDQoNCg0KIyMjIEdMTSBhcyBhIGNsYXNzaWZpY2F0aW9uIHRvb2wNCg0KVGhlIGNvZGUgYmVsb3cgZGVtb25zdHJhdGVzIHRoZSBsaW5rcyBiZXR3ZWVuIG91ciBHTE0gbW9kZWwgYW5kIHdoYXQgd2UgaGFkIG9idGFpbmVkIGFib3ZlIGZyb20gU0RULiBUaGUgcHJlZGljdGlvbnMnIHRhYmxlIHNob3dzIHRoYXQgb3VyIEdMTSB3YXMgc3VjY2Vzc2Z1bCBhdCBvYnRhaW5pbmcgcHJlZGljdGlvbiB0aGF0IGFyZSBpZGVudGljYWwgdG8gb3VyIGluaXRpYWwgZGF0YSBzZXR1cC4gTG9vayBhdCB0aGUgdGFibGUgaGVyZSBhbmQgdGhlIHRhYmxlIGFib3ZlLiBPbmNlIHdlIGhhdmUgY3JlYXRlZCBvdXIgdGFibGUgb2Ygb3V0Y29tZSwgd2UgY2FuIGNvbXB1dGUgcGVyY2VudCBjb3JyZWN0LCB0aGUgc3BlY2lmaWNpdHksIHRoZSBzZW5zaXRpdml0eSwgZXRjLiBUaGlzIHlpZWxkcyB0aGUgYWN0dWFsIHZhbHVlIHdpdGggdGhlIFNEIHRoYXQgaXMgcmVsYXRlZCB0byB2YXJpYXRpb25zIGluIHJlc3BvbnNlcy4gDQoNCmBgYHtyfQ0KbWRsLmdsbS5DIDwtIGdyYW1tYXRpY2FsICU+JSANCiAgZ2xtKHJlc3BvbnNlIH4gZ3JhbW1hdGljYWxpdHksIGRhdGEgPSAuLCBmYW1pbHkgPSBiaW5vbWlhbCkNCnByZWQuZ3JhbW0gPC0gcHJlZGljdChtZGwuZ2xtLkMsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQp0YmwuZ2xtIDwtIHRhYmxlKGdyYW1tYXRpY2FsJHJlc3BvbnNlLCBwcmVkLmdyYW1tKQ0KY29sbmFtZXModGJsLmdsbSkgPC0gYygiZ3JhbW1hdGljYWwiLCAidW5ncmFtbWF0aWNhbCIpDQp0YmwuZ2xtDQojIGZyb20gUHJlc2VuY2VBYnNlbmNlDQpQcmVzZW5jZUFic2VuY2U6OnBjYyh0YmwuZ2xtKQ0KUHJlc2VuY2VBYnNlbmNlOjpzcGVjaWZpY2l0eSh0YmwuZ2xtKQ0KUHJlc2VuY2VBYnNlbmNlOjpzZW5zaXRpdml0eSh0YmwuZ2xtKQ0KDQpyb2MuZ3JhbW0gPC0gcFJPQzo6cm9jKGdyYW1tYXRpY2FsJGdyYW1tYXRpY2FsaXR5LCBwcmVkLmdyYW1tKQ0Kcm9jLmdyYW1tDQpwUk9DOjpwbG90LnJvYyhyb2MuZ3JhbW0sIGxlZ2FjeS5heGVzID0gVFJVRSkNCmBgYA0KDQpJZiB5b3UgbG9vayBhdCB0aGUgcmVzdWx0cyBmcm9tIFNEVCBhYm92ZSwgdGhlc2UgcmVzdWx0cyBhcmUgdGhlIHNhbWUgYXMNCnRoZSBmb2xsb3dpbmcNCg0KQWNjdXJhY3k6IChUUCtUTikvVG90YWwgKGByIChUUCtUTikvVG90YWxgKSANCg0KVHJ1ZSBQb3NpdGl2ZSBSYXRlIChvciBTcGVjaWZpY2l0eSkgVFAvKFRQK0ZOKSAoYHIgVFAvKFRQK0ZOKWApDQoNClRydWUgTmVnYXRpdmUgUmF0ZSAob3IgU2Vuc2l0aXZpdHkpIFROLyhGUCtUTikgKGByIFROLyhGUCtUTilgKSANCg0KVGhlIEFyZWEgVW5kZXIgdGhlIEN1cnZlIChBVUMpIGlzIHVzdWFsbHkgdXNlZCB0byBhc3Nlc3MgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UuIEFzIHlvdSBzZWUsIHdlIHVzZSB0aGUgc2Vuc2l0aXZpdHkgb24gdGhlIHktYXhpcyBhbmQgdGhlIGludmVyc2Ugb2Ygc3BlY2lmaWNpdHkgKDEtc3BlY2lmaWNpdHkpIG9uIHRoZSB4LWF4aXMuIFRoZSBBVUMgZm9yIHRoaXMgZGF0YSBpcyAxLCBpLmUuLCBwZXJmZWN0IHByZWRpY3Rpb25zIGFuZCBpZiB5b3UgbG9vayBhdCB0aGUgQVVDIHBsb3QsIGl0IHNob3dzIGEgcGVyZmVjdCBhY2N1cmFjeSBhbmQgZGV0ZWN0aW9uLiBUaGlzIGlzIHVzdWFsbHkgcmFyZSBidXQgY2FuIGJlIG9idGFpbmVkLiBSZWNhbGwgdGhhdCB0aGlzIGlzIGEgbWFkZS11cCBkYXRhc2V0IGZvciBkZW1vbnN0cmF0aW9uIG9ubHkhIA0KDQpOb3cgbGV0cyBjb250aW51ZSB3aXRoIGEgcmVhbCBkYXRhc2V0LiANCg0KIyMgSXNzdWVzIHdpdGggR0xNDQoNCkxldCdzIGxvb2sgYXQgYSBuZXcgZGF0YXNldCB0aGF0IGNvbWVzIGZyb20gcGhvbmV0aWMgcmVzZWFyY2guIFRoaXMgZGF0YXNldCBpcyBmcm9tIG15IGN1cnJlbnQgd29yayBvbiB0aGUgYHBob25ldGljIGJhc2lzIG9mIHRoZSBndXR0dXJhbCBuYXR1cmFsIGNsYXNzIGluIExldmFudGluZSBBcmFiaWNgLiBBY291c3RpYyBhbmFseXNlcyB3ZXJlIGNvbmR1Y3RlZCBvbiBtdWx0aXBsZSBwb3J0aW9ucyBvZiB0aGUgVkNWIHNlcXVlbmNlLCBhbmQgd2UgcmVwb3J0IGhlcmUgdGhlIGF2ZXJhZ2VkIHJlc3VsdHMgb24gdGhlIGZpcnN0IGhhbGYgb2YgVjIuIEFjb3VzdGljIG1ldHJpY3Mgd2VyZSBvYnRhaW5lZCB2aWEgVm9pY2VTYXVjZTogdmFyaW91cyBhY291c3RpYyBtZXRyaWNzIG9mIHN1cHJhbGFyeW5nZWFsIChiYXJrIGRpZmZlcmVuY2UpIGFuZCBsYXJ5bmdlYWwgKHZvaWNlIHF1YWxpdHkgdmlhIGFtcGxpdHVkZSBoYXJtb25pYyBkaWZmZXJlbmNlcywgbm9pc2UsIGVuZXJneSkgd2VyZSBvYnRhaW5lZC4gQSB0b3RhbCBvZiAyMyBkaWZmZXJlbnQgYWNvdXN0aWMgbWV0cmljcyB3ZXJlIG9idGFpbmVkIGZyb20gMTAgcGFydGljaXBhbnRzLiBIZXJlLCB3ZSB1c2UgdGhlIGRhdGEgZnJvbSB0aGUgZmlyc3QgdHdvIHBhcnRpY2lwYW50cyBmb3IgZGVtb25zdHJhdGlvbiBwdXJwb3NlcyAob25lIG1hbGUgYW5kIG9uZSBmZW1hbGUpLiBUaGUgZ3JvdXBpbmcgZmFjdG9yIG9mIGludGVyZXN0IGlzIGNvbnRleHQgd2l0aCB0d28gbGV2ZWxzOiBgZ3V0dHVyYWxgIHZzIGBub24tZ3V0dHVyYWxgLiBHdXR0dXJhbHMgaW5jbHVkZSAgdXZ1bGFyLCBwaGFyeW5nZWFsaXNlZCBhbmQgcGhhcnluZ2VhbCBjb25zb25hbnRzOyBub24tZ3V0dHVyYWxzIGluY2x1ZGUgY29yb25hbCBwbGFpbiwgdmVsYXIgYW5kIGdsb3R0YWwgY29uc29uYW50cy4gIA0KDQpUaGUgYWltIG9mIHRoZSBzdHVkeSB3YXMgdG8gZXZhbHVhdGUgd2hldGhlciB0aGUgY29tYmluYXRpb24gb2YgdGhlIGFjb3VzdGljIG1ldHJpY3MgcHJvdmlkZXMgc3VwcG9ydCBmb3IgYSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHR3byBjbGFzc2VzIG9mIGd1dHR1cmFscyB2cyBub24tZ3V0dHVyYWxzLiANCg0KIyMjIExvYWQgYW5kIHN1bW1hcmlzZQ0KDQpgYGB7cn0NCmRmUGhhclYyIDwtIHJlYWRfY3N2KCJkZlBoYXJWMi5jc3YiKQ0KZGZQaGFyVjINCmBgYA0KDQoNCiMjIyBGaXJzdCBwcmVkaWN0aXZlIG1vZGVsDQoNCiMjIyMgTGluZWFyIG1vZGVsDQoNCkxldCdzIHN0YXJ0IGJ5IGEgc2ltcGxlIGxpbmVhciBtb2RlbCB0byBldmFsdWF0ZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBhIHBhcnRpY3VsYXIgbWVhc3VyZSBgWjItWjFgIGFuZCB0aGUgY29udGV4dC4NCg0KYGBge3J9DQpkZlBoYXJWMiA8LSBkZlBoYXJWMiAlPiUgDQogIG11dGF0ZShjb250ZXh0ID0gZmFjdG9yKGNvbnRleHQsIGxldmVscyA9IGMoIk5vbi1HdXR0dXJhbCIsICJHdXR0dXJhbCIpKSkNCg0KZGZQaGFyVjIgJT4lIA0KICBsbShaMm1uWjEgfiBjb250ZXh0LCBkYXRhID0gLikgJT4lIA0KICBzdW1tYXJ5KCkNCmBgYA0KDQoNCldoYXQgdGhpcyByZXN1bHQgaXMgdGVsbGluZyB1cyBpcyB0aGF0IHRoZXJlIGlzIGEgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBkZWNyZWFzZSBpbiBgWjItWjFgIGluIHRoZSBndXR0dXJhbCBjbGFzcyB3aGVuIGNvbXBhcmVkIHdpdGggdGhlIG5vbi1ndXR0dXJhbCAoaWdub3JpbmcgdGhlIGZhY3QgdGhhdCB3ZSBuZWVkIG1peGVkIG1vZGVsbGluZyB0byBldmFsdWF0ZSB0aGUgcmVzdWx0cykuIE5vdyBsZXQncyBhc2sgdGhlIHF1ZXN0aW9uOiB3aXRoIHRoaXMgbGluZWFyIG1vZGVsLCBjYW4geW91IHByZWRpY3QgcGVyY2VudGFnZSBzZXBhcmF0aW9uIGJldHdlZW4gdGhlIHR3byBjbGFzc2VzPyBBcmUgeW91IGFibGUgdG8gdGVsbCBpZiB0aGUgZGlmZmVyZW5jZXMgb2JzZXJ2ZWQgYmV0d2VlbiB0aGUgdHdvIGNsYXNzZXMgYXJlIG1lYW5pbmdmdWw/IFRoaXMgaXMgd2hlcmUgd2UgbmVlZCB0byBjaGFuZ2Ugb3VyIHdheSBvZiBsb29raW5nIGF0IHRoZSBkYXRhLiBMZXQncyBsb29rIGF0IEdMTSBhcyBhIGNsYXNzaWZpY2F0aW9uIHRvb2wgYXMgYWJvdmUgDQoNCg0KIyMjIyBHTE0gYXMgYSBjbGFzc2lmaWNhdGlvbiB0b29sDQoNCiMjIyMjIE1vZGVsIHNwZWNpZmljYXRpb24NCg0KV2UgcnVuIGEgR0xNIHdpdGggYGNvbnRleHRgIGFzIG91ciBvdXRjb21lLCBhbmQgYFoyLVoxYCBhcyBvdXIgcHJlZGljdG9yLiBXZSB3YW50IHRvIGV2YWx1YXRlIHdoZXRoZXIgdGhlIHR3byBjbGFzc2VzIGNhbiBiZSBzZXBhcmF0ZWQgd2hlbiB1c2luZyB0aGUgYWNvdXN0aWMgbWV0cmljIGBaMi1aMWAuIENvbnRleHQgaGFzIHR3byBsZXZlbHMsIGFuZCB0aGlzIHdpbGwgYmUgY29uc2lkZXJlZCBhcyBhIGJpbm9taWFsIGRpc3RyaWJ1dGlvbi4gDQoNCg0KYGBge3J9DQptZGwuZ2xtLloybW5aMSA8LSBkZlBoYXJWMiAlPiUgDQogIGdsbShjb250ZXh0IH4gWjJtbloxLCBkYXRhID0gLiwgZmFtaWx5ID0gYmlub21pYWwpDQpzdW1tYXJ5KG1kbC5nbG0uWjJtbloxKQ0KdGlkeShtZGwuZ2xtLloybW5aMSkgJT4lIA0KICBzZWxlY3QodGVybSwgZXN0aW1hdGUpICU+JSANCiAgbXV0YXRlKGVzdGltYXRlID0gcm91bmQoZXN0aW1hdGUsIDMpKQ0KIyB0byBvbmx5IGdldCB0aGUgY29lZmZpY2llbnRzDQpteWNvZWYyIDwtIHRpZHkobWRsLmdsbS5aMm1uWjEpICU+JSBwdWxsKGVzdGltYXRlKQ0KYGBgDQoNCiMjIyMjIFBsb2dpcw0KDQpUaGUgcmVzdWx0IGFib3ZlIHNob3dzIHRoYXQgd2hlbiBtb3ZpbmcgZnJvbSB0aGUgYG5vbi1ndXR0dXJhbGAgKGludGVyY2VwdCksIGEgdW5pdCBpbmNyZWFzZSAoaS5lLiwgYGd1dHR1cmFsYCkgeWllbGRzIGEgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBkZWNyZWFzZSBpbiB0aGUgbG9nb2RkcyBhc3NvY2lhdGVkIHdpdGggYFoyLVoxYC4gV2UgY2FuIGV2YWx1YXRlIHRoaXMgZnVydGhlciBmcm9tIGEgY2xhc3NpZmljYXRpb24gcG9pbnQgb2YgdmlldywgdXNpbmcgYHBsb2dpc2AuDQoNCg0KYGBge3J9DQojIG5vbi1ndXR0dXJhbA0KcGxvZ2lzKG15Y29lZjJbMV0pDQojZ3V0dHVyYWwNCnBsb2dpcyhteWNvZWYyWzFdICsgbXljb2VmMlsyXSkNCmBgYA0KDQoNClRoaXMgc2hvd3MgdGhhdCBgWjItWjFgIGlzIGFibGUgdG8gZXhwbGFpbiB0aGUgZGlmZmVyZW5jZSBpbiB0aGUgYGd1dHR1cmFsYCBjbGFzcyB3aXRoIGFuIGFjY3VyYWN5IG9mIDU5JS4gTGV0J3MgY29udGludWUgd2l0aCB0aGlzIG1vZGVsIGZ1cnRoZXIuDQoNCiMjIyMjIE1vZGVsIHByZWRpY3Rpb25zDQoNCkFzIGFib3ZlLCB3ZSBvYnRhaW4gcHJlZGljdGlvbnMgZnJvbSB0aGUgbW9kZWwuIEJlY2F1c2Ugd2UgYXJlIHVzaW5nIGEgbnVtZXJpYyBwcmVkaWN0b3IsIHdlIG5lZWQgdG8gYXNzaWduIGEgdGhyZXNob2xkIGZvciB0aGUgcHJlZGljdCBmdW5jdGlvbi4gVGhlIHRocmVzaG9sZCBjYW4gYmUgdGhvdWdodCBvZiBhcyB0ZWxsaW5nIHRoZSBwcmVkaWN0IGZ1bmN0aW9uIHRvIGFzc2lnbiBhbnkgcHJlZGljdGlvbnMgbG93ZXIgdGhhbiA1MCUgdG8gb25lIGdyb3VwLCBhbmQgYW55IGhpZ2hlciB0byBhbm90aGVyLiANCg0KYGBge3J9DQpwcmVkLmdsbS5aMm1uWjEgPC0gcHJlZGljdChtZGwuZ2xtLloybW5aMSwgdHlwZSA9ICJyZXNwb25zZSIpPjAuNQ0KDQp0YmwuZ2xtLloybW5aMSA8LSB0YWJsZShwcmVkLmdsbS5aMm1uWjEsIGRmUGhhclYyJGNvbnRleHQpDQpyb3duYW1lcyh0YmwuZ2xtLloybW5aMSkgPC0gYygiTm9uLUd1dHR1cmFsIiwgIkd1dHR1cmFsIikNCnRibC5nbG0uWjJtbloxDQojIGZyb20gUHJlc2VuY2VBYnNlbmNlDQpQcmVzZW5jZUFic2VuY2U6OnBjYyh0YmwuZ2xtLloybW5aMSkNClByZXNlbmNlQWJzZW5jZTo6c3BlY2lmaWNpdHkodGJsLmdsbS5aMm1uWjEpDQpQcmVzZW5jZUFic2VuY2U6OnNlbnNpdGl2aXR5KHRibC5nbG0uWjJtbloxKQ0KDQpyb2MuZ2xtLloybW5aMSA8LSBwUk9DOjpyb2MoZGZQaGFyVjIkY29udGV4dCwgYXMubnVtZXJpYyhwcmVkLmdsbS5aMm1uWjEpKQ0Kcm9jLmdsbS5aMm1uWjENCnBST0M6OnBsb3Qucm9jKHJvYy5nbG0uWjJtbloxLCBsZWdhY3kuYXhlcyA9IFRSVUUpDQpgYGANCg0KDQpUaGUgbW9kZWwgYWJvdmUgd2FzIGFibGUgdG8gZXhwbGFpbiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB0d28gY2xhc3NlcyB3aXRoIGFuIGFjY3VyYWN5IG9mIDY3LjclLiBJdCBoYXMgYSBzbGlnaHRseSBsb3cgc3BlY2lmaWNpdHkgKDAuNTgpIHRvIGRldGVjdCBgZ3V0dHVyYWxzYCwgYnV0IGEgZmxpZ2h0eSBoaWdoIHNlbnNpdGl2aXR5ICgwLjc1KSB0byByZWplY3QgdGhlIGBub24tZ3V0dHVyYWxzYC4gTG9va2luZyBhdCB0aGUgY29uZnVzaW9uIG1hdHJpeCwgd2Ugb2JzZXJ2ZSB0aGF0IGJvdGggZ3JvdXBzIHdlcmUgcmVsYXRpdmVseSBhY2N1cmF0ZWx5IGlkZW50aWZpZWQsIGJ1dCB3ZSBoYXZlIHJlbGF0aXZlbHkgbGFyZ2UgZXJyb3JzIChvciBjb25mdXNpb25zKS4gVGhlIEFVQyBpcyBhdCAwLjY3LCB3aGljaCBpcyBub3QgdG9vIGhpZ2guIA0KDQpMZXQncyBjb250aW51ZSB3aXRoIEdMTSB0byBldmFsdWF0ZSBpdCBmdXJ0aGVyLiBXZSBzdGFydCBieSBydW5uaW5nIGEgY29ycmVsYXRpb24gdGVzdCB0byBldmFsdWF0ZSBpc3N1ZXMgd2l0aCBHTE0uDQoNCiMjIyBDb3JyZWxhdGlvbiB0ZXN0cw0KDQpgYGB7cn0NCmNvcnIgPC0gY29yKGRmUGhhclYyWy0xXSkNCmNvbCA8LSBjb2xvclJhbXBQYWxldHRlKGMoInJlZCIsICJ3aGl0ZSIsICJibHVlIikpKDIwKQ0KY29ycnBsb3QoY29yciwgdHlwZSA9ICJ1cHBlciIsIG9yZGVyID0gImhjbHVzdCIsIGNvbCA9IGNvbCkNCmBgYA0KDQoNClRoZSBjb3JyZWxhdGlvbiBwbG90IGFib3ZlIHNob3dzIHdoaWNoIHByZWRpY3RvcnMgYXJlIGNvcnJlbGF0ZWQgd2l0aCBlYWNoIG90aGVyIChwb3NpdGl2ZWx5IG9yIG5lZ2F0aXZlbHkpLiBUaGV5IGFyZSBvcmdhbmlzZWQgYnkgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgdGhhdCBpZGVudGlmaWVzIGNsdXN0ZXJzIG9mIHByZWRpY3RvcnMgY29ycmVsYXRlZCB3aXRoIGVhY2ggb3RoZXIuIA0KDQojIyMjIENvcnJlbGF0aW9uIHRlc3RzDQoNCg0KQmVsb3csIHdlIGxvb2sgYXQgdHdvIHByZWRpY3RvcnMgdGhhdCBhcmUgY29ycmVsYXRlZCB3aXRoIGVhY2ggb3RoZXI6ICBgWjMtWjJgIChGMy1GMiBpbiBCYXJrKSBhbmQgYEEyKi1BMypgIChub3JtYWxpc2VkIGFtcGxpdHVkZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGhhcm1vbmljcyBjbG9zZXN0IHRvIEYyIGFuZCBGMykuIFRoZSByZXN1bHRzIG9mIHRoZSBjb3JyZWxhdGlvbiB0ZXN0IHNob3dzIHRoZSB0d28gcHJlZGljdG9ycyB0byBuZWdhdGl2ZWx5IGNvcnJlbGF0ZSB3aXRoIGVhY2ggb3RoZXIgYXQgYSByYXRlIG9mIC0wLjg3Lg0KDQoNCg0KYGBge3J9DQpjb3IudGVzdChkZlBoYXJWMiRaM21uWjIsIGRmUGhhclYyJEEybW5BMykNCmBgYA0KDQoNCiMjIyMgUGxvdHMgdG8gdmlzdWFsaXNlIHRoZSBkYXRhDQoNCmBgYHtyfQ0KZGZQaGFyVjIgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBjb250ZXh0LCB5ID0gWjNtbloyKSkgKyANCiAgZ2VvbV9ib3hwbG90KCkNCg0KZGZQaGFyVjIgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBjb250ZXh0LCB5ID0gQTJtbkEzKSkgKyANCiAgZ2VvbV9ib3hwbG90KCkgIA0KYGBgDQoNCg0KDQpBcyB3ZSBzZWUgZnJvbSB0aGUgcGxvdCwgYFozLVoyYCBpcyBoaWdoZXIgaW4gdGhlIGd1dHR1cmFsLCB3aGVyZWFzICBgQTIqLUEzKmAgaXMgbG93ZXIuIA0KDQojIyMjIEdMTSBvbiBjb3JyZWxhdGVkIGRhdGENCg0KDQpMZXQncyBydW4gYSBsb2dpc3RpYyByZWdyZXNzaW9uIGFzIGEgY2xhc3NpZmljYXRpb24gdG9vbCB0byBwcmVkaWN0IHRoZSAgY29udGV4dCBhcyBhIGZ1bmN0aW9uIG9mIGVhY2ggcHJlZGljdG9yIHNlcGFyYXRlbHkgYW5kIHRoZW4gY29tYmluZWQuIA0KDQpgYGB7cn0NCmRmUGhhclYyICU+JSBnbG0oY29udGV4dCB+IFozbW5aMiwgZGF0YSA9IC4sIGZhbWlseSA9IGJpbm9taWFsKSAlPiUgc3VtbWFyeSgpDQoNCmRmUGhhclYyICU+JSBnbG0oY29udGV4dCB+IEEybW5BMywgZGF0YSA9IC4sIGZhbWlseSA9IGJpbm9taWFsKSAlPiUgc3VtbWFyeSgpDQoNCmRmUGhhclYyICU+JSBnbG0oY29udGV4dCB+IFozbW5aMiArIEEybW5BMywgZGF0YSA9IC4sIGZhbWlseSA9IGJpbm9taWFsKSAlPiUgc3VtbWFyeSgpDQpgYGANCg0KDQoNCldoZW4gbG9va2luZyBhdCB0aGUgdGhyZWUgbW9kZWxzIGFib3ZlLCBpdCBpcyBjbGVhciB0aGF0IHRoZSBsb2dvZGQgdmFsdWUgZm9yIGBaMy1aMmAgaXMgcG9zaXRpdmUsIHdoZXJlYXMgaXQgaXMgbmVnYXRpdmUgZm9yIGBBMiotQTMqYC4gV2hlbiBhZGRpbmcgdGhlIHR3byBwcmVkaWN0b3JzIHRvZ2V0aGVyLCB0aGVyZSBpcyBjbGVhciAqKnN1cHByZXNzaW9uKio6IHRoZSBjb2VmZmljaWVudHMgZm9yIGJvdGggcHJlZGljdG9ycyBhcmUgbm93IG5lZ2F0aXZlLiBUaGUgcmVsYXRpdmVseSBoaWdoIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIHByZWRpY3RvcnMgYWZmZWN0ZWQgdGhlIGNvZWZmaWNpZW50cyBhbmQgY2hhbmdlZCB0aGUgZGlyZWN0aW9uIG9mIHRoZSBzbG9wZTsgY29sbGluZWFyaXR5IGlzIGhhcm1mdWwgZm9yIGFueSByZWdyZXNzaW9uIGFuYWx5c2lzLiANCg0KSW4gdGhlIGZvbGxvd2luZywgd2UgaW50cm9kdWNlIGRlY2lzaW9uIHRyZWVzIGZvbGxvd2VkIGJ5IFJhbmRvbSBGb3Jlc3QgYXMgYSB3YXkgdG8gZGVhbCB3aXRoIGNvbGxpbmVhcml0eSBhbmQgdG8gbWFrZSBzZW5zZSBvZiBtdWx0aXZhcmlhdGUgcHJlZGljdG9ycy4gDQoNCiMgRGVjaXNpb24gVHJlZXMgDQoNCkRlY2lzaW9uIHRyZWVzIGFyZSBhIHN0YXRpc3RpY2FsIHRvb2wgdGhhdCB1c2VzIHRoZSBjb21iaW5hdGlvbiBvZiBwcmVkaWN0b3JzIHRvIGlkZW50aWZ5IHBhdHRlcm5zIGluIHRoZSBkYXRhIGFuZCBwcm92aWRlcyBjbGFzc2lmaWNhdGlvbiBhY2N1cmFjeSBmb3IgdGhlIG1vZGVsLiANCg0KVGhlIGRlY2lzaW9uIHRyZWUgdXNlZCBpcyBiYXNlZCBvbiBgY29uZGl0aW9uYWwgaW5mZXJlbmNlIHRyZWVzYCB0aGF0IGxvb2tzIGF0IGVhY2ggcHJlZGljdG9yIGFuZCBzcGxpdHMgdGhlIGRhdGEgaW50byBtdWx0aXBsZSBub2RlcyAoYnJhbmNoZXMpIHRocm91Z2ggcmVjdXJzaXZlIHBhcnRpdGlvbmluZyBpbiBhIGB0cmVlLXN0cnVjdHVyZWQgcmVncmVzc2lvbiBtb2RlbGAuIEVhY2ggbm9kZSBpcyBhbHNvIHNwbGl0IGludG8gbGVhdmVzIChkaWZmZXJlbmNlIGJldHdlZW4gbGV2ZWxzIG9mIG91dGNvbWUpLg0KDQpEZWNpc2lvbiB0cmVlcyB2aWEgYGN0cmVlYCBkb2VzIHRoZSBmb2xsb3dpbmc6IA0KDQoxLiBUZXN0IGdsb2JhbCBudWxsIGh5cG90aGVzaXMgb2YgaW5kZXBlbmRlbmNlIGJldHdlZW4gcHJlZGljdG9ycyBhbmQgb3V0Y29tZS4gDQoyLiBTZWxlY3QgdGhlIHByZWRpY3RvciB3aXRoIHRoZSBzdHJvbmdlc3QgYXNzb2NpYXRpb24gd2l0aCB0aGUgb3V0Y29tZSBtZWFzdXJlZCBiYXNlZCBvbiBhIG11bHRpcGxpY2l0eSBhZGp1c3RlZCBwLXZhbHVlcyB3aXRoIEJvbmZlcnJvbmkgY29ycmVjdGlvbg0KMy4gSW1wbGVtZW50IGEgYmluYXJ5IHNwbGl0IGluIHRoZSBzZWxlY3RlZCBpbnB1dCB2YXJpYWJsZS4gDQo0LiBSZWN1cnNpdmVseSByZXBlYXQgc3RlcHMgMSksIDIpLiBhbmQgMykuDQoNCkxldCdzIHNlZSB0aGlzIGluIGFuIGV4YW1wbGUgdXNpbmcgdGhlIHNhbWUgZGF0YXNldC4gVG8gdW5kZXJzdGFuZCB3aGF0IHRoZSBkZWNpc2lvbiB0cmVlIGlzIGRvaW5nLCB3ZSB3aWxsIGRpc3NlY3QgaXQsIGJ5IGNyZWF0aW5nIG9uZSB0cmVlIHdpdGggb25lIHByZWRpY3RvciBhbmQgbW92ZSB0byB0aGUgbmV4dC4NCg0KDQojIyBJbmRpdmlkdWFsIHRyZWVzDQoNCiMjIyBUcmVlIDENCg0KYGBge3J9DQojIyBmcm9tIHRoZSBwYWNrYWdlIHBhcnR5DQpzZXQuc2VlZCgxMjM0NTYpDQp0cmVlMSA8LSBkZlBoYXJWMiAlPiUgDQogIGN0cmVlKA0KICAgIGNvbnRleHQgfiBaMm1uWjEsIA0KICAgIGRhdGEgPSAuKQ0KcHJpbnQodHJlZTEpDQpwbG90KHRyZWUxLCBtYWluID0gIkNvbmRpdGlvbmFsIEluZmVyZW5jZSBUcmVlICIpDQpgYGANCg0KDQpIb3cgdG8gaW50ZXJwcmV0IHRoaXMgZmlndXJlPyBMZXQncyBsb29rIGF0IG1lYW4gdmFsdWVzIGFuZCBhIHBsb3QgZm9yIHRoaXMgdmFyaWFibGUuIFRoaXMgaXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBgRjJgIGFuZCBgRjFgIHVzaW5nIHRoZSBiYXJrIHNjYWxlLiBCZWNhdXNlIGd1dHR1cmFscyBhcmUgcHJvZHVjZWQgd2l0aGluIHRoZSBwaGFyeW54IChyZWdhcmRsZXNzIG9mIHdoZXJlKSwgdGhlIHByZWRpY3Rpb25zIGlzIHRoYXQgYSBoaWdoIGBGMWAgYW5kIGEgbG93IGBGMmAgd2lsbCBiZSB0aGUgYWNvdXN0aWMgY29ycmVsYXRlcyByZWxhdGVkIHRvIHRoaXMgY29uc3RyaWN0aW9uIGxvY2F0aW9uLiBUaGUgY2xvc2VuZXNzIGJldHdlZW4gdGhlc2UgZm9ybWFudHMgeWllbGRzIGEgbG93ZXIgYFoyLVoxYC4gSGVuY2UsIHRoZSBwcmVkaWN0aW9uIGlzIGFzIGZvbGxvdzogdGhlIHNtYWxsZXIgdGhlIGRpZmZlcmVuY2UsIHRoZSBtb3JlIHBoYXJ5bmdlYWwtbGlrZSBjb25zdHJpY3Rpb24gdGhlc2UgY29uc29uYW50cyBoYXZlIChhbGwgZWxzZSBiZWluZyBlcXVhbCEpLiBMZXQncyBjb21wdXRlIHRoZSBtZWFuL21lZGlhbiBhbmQgcGxvdCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB0d28gY29udGV4dHMuDQoNCmBgYHtyfQ0KZGZQaGFyVjIgJT4lIA0KICBncm91cF9ieShjb250ZXh0KSAlPiUgDQogIHN1bW1hcmlzZShtZWFuID0gbWVhbihaMm1uWjEpLA0KICAgICAgICAgICAgbWVkaWFuID0gbWVkaWFuKFoybW5aMSksIA0KICAgICAgICAgICAgY291bnQgPSBuKCkpDQoNCmRmUGhhclYyICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gY29udGV4dCwgeSA9IFoybW5aMSkpICsgDQogIGdlb21fYm94cGxvdCgpICANCmBgYA0KDQoNClRoZSB0YWJsZSBhYm92ZSByZXBvcnRzIHRoZSBtZWFuIGFuZCBtZWRpYW4gb2YgYFoyLVoxYCBmb3IgYm90aCBsZXZlbHMgb2YgY29udGV4dCBhbmQgdGhlIHBsb3RzIHNob3cgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvLiBXZSBoYXZlIGEgdG90YWwgb2YgMTgwIGNhc2VzIGluIHRoZSBgZ3V0dHVyYWxgLCBhbmQgMjIyIGluIHRoZSBgbm9uLWd1dHR1cmFsYC4gDQpXaGVuIGNvbnNpZGVyaW5nIHRoZSBjb25kaXRpb25hbCBpbmZlcmVuY2UgdHJlZSBvdXRwdXQsIHZhcmlvdXMgc3BsaXRzIHdlcmUgb2J0YWluZWQuIA0KVGhlIGZpcnN0IGlzIGFueSB2YWx1ZSBoaWdoZXIgdGhhbiA5LjU1IGJlaW5nIGFzc2lnbmVkIHRvIHRoZSBgbm9uLWd1dHR1cmFsYCBjbGFzcyAoYXJvdW5kIDk4JSBvZiA3NSBjYXNlcykNClRoZW4sIHdpdGggYW55dGhpbmcgbG93ZXIgdGhhbiA5LjU1LCBhIHNlY29uZCBzcGxpdCB3YXMgb2J0YWluZWQuIEEgdGhyZXNob2xkIG9mIDYuNzg6IGhpZ2hlciBhc3NpZ25lZCB0byBgZ3V0dHVyYWxgIChhcm91bmQgOTglIG9mIDY0IGNhc2VzKSwgbG93ZXIsIHdlcmUgc3BsaXQgYWdhaW4gd2l0aCBhIHRocmVzaG9sZCBvZiA0IEJhcmsuIEEgdGhpcmQgc3BsaXQgd2FzIG9idGFpbmVkOiB2YWx1ZXMgbG93ZXIgb2YgZXF1YWwgdG8gNCBCYXJrIGFyZSBhc3NpZ25lZCB0byB0aGUgYGd1dHR1cmFsYCAoYXJvdW5kIDcwJSBvZiAxNTcgY2FzZXMpIGFuZCB2YWx1ZXMgaGlnaGVyIHRoYW4gNCBCYXJrcyBhc3NpZ25lZCB0byB0aGUgYG5vbi1ndXR0dXJhbGAgKGFyb3VuZCA5MCUgb2YgMTA2IGNhc2VzKS4NCg0KRGlzc2VjdGluZyB0aGUgdHJlZSBsaWtlIHRoaXMgYWxsb3dzIGludGVycHJldGF0aW9uIG9mIHRoZSBvdXRwdXQuIEluIHRoaXMgZXhhbXBsZSwgdGhpcyBpcyBxdWl0ZSBhIGNvbXBsZXggY2FzZSBhbmQgYGN0cmVlYCBhbGxvd2VkIHRvIGZpbmUgdHVuZSB0aGUgZGlmZmVyZW50IHBhdHRlcm5zIHNlZW4gd2l0aCANCk5vdyBsZXQncyBsb29rIGF0IHRoZSBmdWxsIGRhdGFzZXQgdG8gbWFrZSBzZW5zZSBvZiB0aGUgY29tYmluYXRpb24gb2YgcHJlZGljdG9ycyB0byB0aGUgZGlmZmVyZW5jZS4gDQoNCiMjIE1vZGVsIDENCg0KIyMjIE1vZGVsIHNwZWNpZmljYXRpb24NCg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NTYpDQpmaXQgPC0gZGZQaGFyVjIgICU+JSANCiAgY3RyZWUoDQogICAgY29udGV4dCB+IC4sIA0KICAgIGRhdGEgPSAuKQ0KcHJpbnQoZml0KQ0KcGxvdChmaXQsIG1haW4gPSAiQ29uZGl0aW9uYWwgSW5mZXJlbmNlIFRyZWUiKQ0KYGBgDQoNCg0KSG93IHRvIGludGVycHJldCB0aGlzIGNvbXBsZXggZGVjaXNpb24gdHJlZT8gDQoNCkxldCdzIG9idGFpbiB0aGUgbWVkaWFuIHZhbHVlIGZvciBlYWNoIHByZWRpY3RvciBncm91cGVkIGJ5IGNvbnRleHQuIERpc2N1c3Mgc29tZSBvZiB0aGUgcGF0dGVybnMuIA0KDQpgYGB7cn0NCmRmUGhhclYyICU+JSANCiAgZ3JvdXBfYnkoY29udGV4dCkgJT4lIA0KICBzdW1tYXJpemVfYWxsKGxpc3QobWVhbiA9IG1lYW4pKQ0KYGBgDQoNCg0KDQpXZSBzdGFydGVkIHdpdGggYGNvbnRleHRgIGFzIG91ciBvdXRjb21lLCBhbmQgYWxsIDIzIGFjb3VzdGljIG1lYXN1cmVzIGFzIHByZWRpY3RvcnMuIEEgdG90YWwgb2YgOCB0ZXJtaW5hbCBub2RlcyB3ZXJlIGlkZW50aWZpZWQgd2l0aCBtdWx0aXBsZSBiaW5hcnkgc3BsaXRzIGluIHRoZWlyIGxlYXZlcywgYWxsb3dpbmcgc2VwYXJhdGlvbiBvZiB0aGUgdHdvIGNhdGVnb3JpZXMuIExvb2tpbmcgc3BlY2lmaWNhbGx5IGF0IHRoZSBvdXRwdXQsIHdlIG9ic2VydmUgYSBmZXcgdGhpbmdzLg0KDQpUaGUgZmlyc3Qgbm9kZSB3YXMgYmFzZWQgb24gYEEyKi1BMypgLCBkZXRlY3RpbmcgYSBkaWZmZXJlbmNlIGJldHdlZW4gbm9uLWd1dHR1cmFscyBhbmQgZ3V0dHVyYWxzLiBGb3IgdGhlIGZpcnN0IGJpbmFyeSBzcGxpdCwgYSB0aHJlc2hvbGQgb2YgLTEzLjc4IEJhcmsgd2FzIHVzZWQgKG1lYW4gbm9uIGd1dHR1cmFsID0gLTcuODY7IG1lYW4gZ3V0dHVyYWwgPSAtMTQuNTgpLCB0aGVuIGZvciB2YWx1ZXMgbG93ZXIgb2YgZXF1YWwgdG8gdGhpcyB0aHJlc2hvbGQsIGEgc2Vjb25kIHNwbGl0IHdhcyBwZXJmb3JtZWQgdXNpbmcgYFo0LVozYCAobWVhbiBub24gZ3V0dHVyYWwgPSAxLjY3OyBtZWFuIGd1dHR1cmFsID0gMS40Mykgd2l0aCBhbnkgdmFsdWUgc21hbGxlciBhbmQgZXF1YWwgdG8gMS41OSwgdGhlbiBhbm90aGVyIGJpbmFyeSBzcGxpdCB1c2luZyBgSDIqLUg0KmAsIGV0Yy4uLg0KDQpPbmNlIGRvbmUsIHRoZSBgY3RyZWVgIHByb3ZpZGVzIG11bHRpcGxlIGJpbmFyeSBzcGxpdHMgaW50byBndXR0dXJhbCBvciBub24tZ3V0dHVyYWwuIA0KDQpBbnkgcG9zc2libGUgaXNzdWVzL2ludGVyZXN0aW5nIHBhdHRlcm5zIHlvdSBjYW4gaWRlbnRpZnk/IExvb2sgYXQgdGhlIGludGVyYWN0aW9ucyBiZXR3ZWVuIHByZWRpY3RvcnMuIA0KDQoNCiMjIyBQcmVkaWN0aW9ucyBmcm9tIHRoZSBmdWxsIG1vZGVsDQoNCkxldCdzIG9idGFpbiBzb21lIHByZWRpY3Rpb25zIGZyb20gdGhlIG1vZGVsIGFuZCBldmFsdWF0ZSBob3cgc3VjY2Vzc2Z1bCBpdCBpcyBpbiBkZWFsaW5nIHdpdGggdGhlIGRhdGEuIA0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMzQ1NikNCnByZWQuY3RyZWUgPC0gcHJlZGljdChmaXQpDQp0YmwuY3RyZWUgPC0gdGFibGUocHJlZC5jdHJlZSwgZGZQaGFyVjIkY29udGV4dCkNCnRibC5jdHJlZQ0KUHJlc2VuY2VBYnNlbmNlOjpwY2ModGJsLmN0cmVlKQ0KUHJlc2VuY2VBYnNlbmNlOjpzcGVjaWZpY2l0eSh0YmwuY3RyZWUpDQpQcmVzZW5jZUFic2VuY2U6OnNlbnNpdGl2aXR5KHRibC5jdHJlZSkNCg0Kcm9jLmN0cmVlIDwtIHBST0M6OnJvYyhkZlBoYXJWMiRjb250ZXh0LCBhcy5udW1lcmljKHByZWQuY3RyZWUpKQ0Kcm9jLmN0cmVlDQpwUk9DOjpwbG90LnJvYyhyb2MuY3RyZWUsIGxlZ2FjeS5heGVzID0gVFJVRSkNCmBgYA0KDQpUaGlzIGZ1bGwgbW9kZWwgaGFzIGEgY2xhc3NpZmljYXRpb24gYWNjdXJhY3kgb2YgODIuOCUuVGhpcyBpcyBub3QgYmFkISEgSXQgaGFzIGEgcmVsYXRpdmVseSBtb2RlcmF0ZSBzcGVjaWZpY2l0eSBhdCAwLjc3IChhdCBkZXRlY3RpbmcgdGhlIGd1dHR1cmFscykgYnV0IGEgaGlnaCBzZW5zaXRpdml0eSBhdCAwLjg3IChhdCBkZXRlY3RpbmcgdGhlIG5vbi1ndXR0dXJhbHMpLiBUaGUgUk9DIGN1cnZlIHNob3dzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgdHdvIHdpdGggYW4gQVVDIG9mIDAuODIzDQoNCg0KIyMgVXAgdG8geW91Li4NCg0KVHJ5IGFuZCBmaXQgdHdvIGRlY2lzaW9uIHRyZWVzLCB0aGUgZmlyc3Qgb24gYmFyayBkaWZmZXJlbmNlIG1ldHJpY3MgYW5kIHRoZSBzZWNvbmQgb24gdm9pY2UgcXVhbGl0eSBtZXRyaWNzLiBVc2UgdGhlIGB0aWR5dmVyc2VgIGFwcHJvYWNoIHRvIDEpIHNlbGVjdCBwcmVkaWN0b3JzIGFuZCAyKSB0byBydW4gY3RyZWUuIA0KDQoNCmBgYHtyfQ0KIyBmb3JtYW50cw0KYGBgDQoNCg0KDQoNCg0KYGBge3J9DQojIFZvaWNlIHF1YWxpdHkNCmBgYA0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQojIyBSYW5kb20gc2VsZWN0aW9uDQoNCk9uZSBpbXBvcnRhbnQgaXNzdWUgaXMgdGhhdCB0aGUgdHJlZXMgd2UgZ3JldyBhYm92ZSBhcmUgYmlhc2VkLiBUaGV5IGFyZSBiYXNlZCBvbiB0aGUgZnVsbCBkYXRhc2V0LCB3aGljaCBtZWFucyB0aGV5IGFyZSB2ZXJ5IGxpa2VseSB0byBvdmVyZml0IHRoZSBkYXRhLiBXZSBkaWQgbm90IGFkZCBhbnkgcmFuZG9tIHNlbGVjdGlvbiBhbmQgd2Ugb25seSBncmV3IG9uZSB0cmVlIGVhY2ggdGltZS4gSWYgeW91IHRoaW5rIGFib3V0IGl0LCBpcyBpdCBwb3NzaWJsZSB0aGF0IHdlIG9idGFpbmVkIHN1Y2ggcmVzdWx0cyBzaW1wbHkgYnkgY2hhbmNlPyANCg0KV2hhdCBpZiB3ZSBhZGQgc29tZSByYW5kb21uZXNzIGluIHRoZSBwcm9jZXNzIG9mIGNyZWF0aW5nIGEgY29uZGl0aW9uYWwgaW5mZXJlbmNlIHRyZWU/DQoNCg0KV2UgY2hhbmdlIGEgc21hbGwgb3B0aW9uIGluIGBjdHJlZWAgdG8gYWxsb3cgZm9yIHJhbmRvbSBzZWxlY3Rpb24gb2YgdmFyaWFibGVzLCB0byBtaW1pYyB3aGF0IFJhbmRvbSBGb3Jlc3RzIHdpbGwgZG8uIFdlIHVzZSBgY29udHJvbHNgIHRvIHNwZWNpZnkgYG10cnkgPSA1YCwgd2hpY2ggaXMgdGhlIHJvdW5kZWQgc3F1YXJlIHJvb3Qgb2YgbnVtYmVyIG9mIHByZWRpY3RvcnMuIA0KDQoNCiMjIyBNb2RlbCAyDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNDU2KQ0KZml0MSA8LSBkZlBoYXJWMiAgJT4lIA0KICBjdHJlZSgNCiAgICBjb250ZXh0IH4gLiwgDQogICAgZGF0YSA9IC4sDQogICAgY29udHJvbHMgPSBjdHJlZV9jb250cm9sKG10cnkgPSA1KSkNCnBsb3QoZml0MSwgbWFpbiA9ICJDb25kaXRpb25hbCBJbmZlcmVuY2UgVHJlZSIpDQpwcmVkLmN0cmVlMSA8LSBwcmVkaWN0KGZpdDEpDQp0YmwuY3RyZWUxIDwtIHRhYmxlKHByZWQuY3RyZWUxLCBkZlBoYXJWMiRjb250ZXh0KQ0KdGJsLmN0cmVlMQ0KUHJlc2VuY2VBYnNlbmNlOjpwY2ModGJsLmN0cmVlMSkNClByZXNlbmNlQWJzZW5jZTo6c3BlY2lmaWNpdHkodGJsLmN0cmVlMSkNClByZXNlbmNlQWJzZW5jZTo6c2Vuc2l0aXZpdHkodGJsLmN0cmVlMSkNCg0Kcm9jLmN0cmVlMSA8LSBwUk9DOjpyb2MoZGZQaGFyVjIkY29udGV4dCwgYXMubnVtZXJpYyhwcmVkLmN0cmVlMSkpDQpyb2MuY3RyZWUxDQpwUk9DOjpwbG90LnJvYyhyb2MuY3RyZWUxLCBsZWdhY3kuYXhlcyA9IFRSVUUpDQpgYGANCg0KDQpDYW4geW91IGNvbXBhcmUgcmVzdWx0cyBiZXR3ZWVuIHlvdSBhbmQgZGlzY3VzcyB3aGF0IGlzIGdvaW5nIG9uPyANCg0KV2hlbiBhZGRpbmcgb25lIHJhbmRvbSBzZWxlY3Rpb24gcHJvY2VzcyB0byBvdXIgYGN0cmVlYCwgd2UgYWxsb3cgaXQgdG8gb2J0YWluIG1vcmUgcm9idXN0IHByZWRpY3Rpb25zLiBXZSBjb3VsZCBldmVuIGdvIGZ1cnRoZXIgYW5kIGdyb3cgbXVsdGlwbGUgc21hbGwgdHJlZXMgd2l0aCBhIHBvcnRpb24gb2YgZGF0YXBvaW50cyAoZS5nLiwgMTAwIHJvd3MsIDIwMCByb3dzKS4gV2hlbiBkb2luZyB0aGVzZSBtdWx0aXBsZSByYW5kb20gc2VsZWN0aW9ucywgeW91IGFyZSBncm93aW5nIG11bHRpcGxlIHRyZWVzIHRoYXQgYXJlIGRlY29ycmVsYXRlZCBmcm9tIGVhY2ggb3RoZXIuIFRoZXNlIGJlY29tZSBpbmRlcGVuZGVudCB0cmVlcyBhbmQgb25lIGNhbiBjb21iaW5lIHRoZSByZXN1bHRzIG9mIHRoZXNlIHRyZWVzIHRvIGNvbWUgd2l0aCBjbGVhciBwcmVkaWN0aW9ucy4gDQoNClRoaXMgaXMgaG93IFJhbmRvbSBGb3Jlc3RzIHdvcmsuIFlvdSB3b3VsZCBzdGFydCBmcm9tIGEgZGF0YXNldCwgdGhlbiBncm93IG11bHRpcGxlIHRyZWVzLCB2YXJ5IG51bWJlciBvZiBvYnNlcnZhdGlvbnMgdXNlZCAobnJvdyksIGFuZCBudW1iZXIgb2YgcHJlZGljdG9ycyB1c2VkIChtdHJ5KSwgYWRqdXN0IGJyYW5jaGVzLCBhbmQgZGVwdGggb2Ygbm9kZXMgYW5kIGF0IHRoZSBlbmQsIGNvbWJpbmUgdGhlIHJlc3VsdHMgaW4gYSBmb3Jlc3QuIFlvdSBjYW4gYWxzbyBydW4gcGVybXV0YXRpb24gdGVzdHMgdG8gZXZhbHVhdGUgY29udHJpYnV0aW9ucyBvZiBlYWNoIHByZWRpY3RvciB0byB0aGUgb3V0Y29tZS4gVGhpcyBpcyB0aGUgYmVhdXR5IG9mIFJhbmRvbSBGb3Jlc3RzLiBUaGV5IGRvIGFsbCBvZiB0aGVzZSBzdGVwcyBhdXRvbWF0aWNhbGx5IGF0IG9uY2UgZm9yIHlvdSEgDQoNCg0KIyBSYW5kb20gRm9yZXN0cw0KDQpBcyB0aGVpciBuYW1lIGluZGljYXRlLCBhIFJhbmRvbSBGb3Jlc3QgaXMgYSBmb3Jlc3Qgb2YgdHJlZXMgaW1wbGVtZW50ZWQgdGhyb3VnaCBiYWdnaW5nIGVuc2VtYmxlIGFsZ29yaXRobXMuIEVhY2ggdHJlZSBoYXMgbXVsdGlwbGUgYnJhbmNoZXMgKG5vZGVzKSwgYW5kIHdpbGwgcHJvdmlkZSBwcmVkaWN0aW9ucyBiYXNlZCBvbiByZWN1cnNpdmUgcGFydGl0aW9uaW5nIG9mIHRoZSBkYXRhLiBUaGVuIHVzaW5nIHRoZSBwcmVkaWN0aW9ucyBmcm9tIHRoZSBtdWx0aXBsZSBncm93biB0cmVlcywgUmFuZG9tIEZvcmVzdHMgd2lsbCBjcmVhdGUgYGF2ZXJhZ2VkYCBwcmVkaWN0aW9ucyBhbmQgY29tZSB1cCB3aXRoIHByZWRpY3Rpb24gYWNjdXJhY3ksIGV0Yy4gDQoNClRoZXJlIGFyZSBtdWx0aXBsZSBwYWNrYWdlcyB0aGF0IG9uZSBjYW4gdXNlIHRvIGdyb3cgUmFuZG9tIEZvcmVzdHM6DQoNCjEuIGByYW5kb21Gb3Jlc3RgOiBUaGUgb3JpZ2luYWwgaW1wbGVtZW50YXRpb24gb2YgUmFuZG9tIEZvcmVzdHMuDQoyLiBgcGFydHlgIGFuZCBgcGFydHlraXRgOiB1c2luZyBjb25kaXRpb25hbCBpbmZlcmVuY2UgdHJlZXMgYXMgYmFzZSBsZWFybmVycw0KMy4gYHJhbmdlcmA6IGEgcmVpbXBsZW1lbnRhdGlvbiBvZiBSYW5kb20gRm9yZXN0czsgZmFzdGVyIGFuZCBtb3JlIGZsZXhpYmxlIHRoYW4gb3JpZ2luYWwgaW1wbGVtZW50YXRpb24NCg0KVGhlIGZpcnN0IGltcGxlbWVudGF0aW9uIG9mIFJhbmRvbSBGb3Jlc3RzIGlzIHdpZGVseSB1c2VkIGluIHJlc2VhcmNoLiBPbmUgb2YgdGhlIGlzc3VlcyBpbiB0aGlzIGZpcnN0IGltcGxlbWVudGF0aW9uIGlzIHRoYXQgaXQgZmF2b3VyZWQgc3BlY2lmaWMgdHlwZXMgb2YgcHJlZGljdG9ycyAoZS5nLiwgY2F0ZWdvcmljYWwgcHJlZGljdG9ycywgcHJlZGljdG9ycyB3aXRoIG11bHRpcGxlIGN1dC1vZmZzLCBldGMpLiBSYW5kb20gRm9yZXN0cyBncm93biB2aWEgQ29uZGl0aW9uYWwgSW5mZXJlbmNlIFRyZWVzIGFzIGltcGxlbWVudGVkIGluIGBwYXJ0eWAgZ3VhcmQgYWdhaW5zdCB0aGlzIGJpYXMsIGJ1dCB0aGV5IGFyZSBjb21wdXRhdGlvbmFsbHkgZGVtYW5kaW5nLiBSYW5kb20gRm9yZXN0cyBncm93biB2aWEgcGVybXV0YXRpb24gdGVzdHMgYXMgaW1wbGVtZW50ZWQgaW4gYHJhbmdlcmAgc3BlZWQgdXAgdGhlIGNvbXB1dGF0aW9ucyBhbmQgY2FuIG1pbWljIHRoZSB1bmJpYXNlZCBzZWxlY3Rpb24gcHJvY2Vzcy4gDQoNCiMjIFBhcnR5DQoNClJhbmRvbSBGb3Jlc3RzIGdyb3duIHZpYSBjb25kaXRpb25hbCBpbmZlcmVuY2UgdHJlZXMsIGFyZSBkaWZmZXJlbnQgZnJvbSB0aGUgb3JpZ2luYWwgaW1wbGVtZW50YXRpb24uIFRoZXkgb2ZmZXIgYW4gdW5iaWFzZWQgc2VsZWN0aW9uIHByb2Nlc3MgdGhhdCBndWFyZHMgYWdhaW5zdCBvdmVyZml0dGluZyBvZiB0aGUgZGF0YS4gVGhlcmUgYXJlIHZhcmlvdXMgcG9pbnRzIHdlIG5lZWQgdG8gY29uc2lkZXIgaW4gZ3Jvd2luZyB0aGUgZm9yZXN0LCBpbmNsdWRpbmcgbnVtYmVyIG9mIHRyZWVzIGFuZCBwcmVkaWN0b3JzIHRvIHVzZSBlYWNoIHRpbWUuIExldCB1cyBydW4gb3VyIGZpcnN0IFJhbmRvbSBGb3Jlc3QgdmlhIGNvbmRpdGlvbmFsIGluZmVyZW5jZSB0cmVlcy4gVG8gbWFrZSBzdXJlIHRoZSBjb2RlIHJ1bnMgYXMgZmFzdCBhcyBpdCBjYW4sIHdlIHVzZSBhIHZlcnkgbG93IG51bWJlciBvZiB0cmVlczogb25seSAxMDAgSXQgaXMgd2VsbCBrbm93biB0aGF0IHRoZSBtb3JlIHRyZWVzIHlvdSBncm93LCB0aGUgbW9yZSBjb25maWRlbmNlIHlvdSBoYXZlIGluIHRoZSByZXN1bHRzLCBhcyBtb2RlbCBlc3RpbWF0aW9uIHdpbGwgYmUgbW9yZSBzdGFibGUuIEluIHRoaXMgZXhhbXBsZSwgSSB3b3VsZCBlYXNpbHkgZ28gd2l0aCA1MDAgdHJlZXMuLg0KDQojIyMgTW9kZWwgc3BlY2lmaWNhdGlvbg0KDQpUbyBncm93IHRoZSBmb3Jlc3QsIHdlIHVzZSB0aGUgZnVuY3Rpb24gYGNmb3Jlc3RgLiBXZSB1c2UgYWxsIG9mIHRoZSBkYXRhc2V0IGZvciB0aGUgbW9tZW50LiBXZSBuZWVkIHRvIHNwZWNpZnkgYSBmZXcgb3B0aW9ucyB3aXRoaW4gY29udHJvbHM6IA0KDQoxLiBgbnRyZWUgPSAxMDBgID0gbnVtYmVyIG9mIHRyZWVzIHRvIGdyb3cuIERlZmF1bHQgPSA1MDAuDQoyLiBgbXRyeSA9IHJvdW5kKHNxcnQoMjMpKWA6IG51bWJlciBvZiBwcmVkaWN0b3JzIHRvIHVzZSBlYWNoIHRpbWUuIERlZmF1bHQgaXMgNSwgYnV0IHNwZWNpZnlpbmcgaXQgaXMgYWR2aXNlZCB0byBhY2NvdW50IGZvciB0aGUgc3RydWN0dXJlIG9mIHRoZSBkYXRhDQoNCkJ5IGRlZmF1bHQsIGBjZm9yZXN0X3VuYmlhc2VkYCBoYXMgdHdvIGFkZGl0aW9uYWwgaW1wb3J0YW50IG9wdGlvbnMgdGhhdCBhcmUgdXNlZCBmb3IgYW4gdW5iaWFzZWQgc2VsZWN0aW9uIHByb2Nlc3MuICoqV0FSTklORyoqOiB5b3Ugc2hvdWxkIG5vdCBjaGFuZ2UgdGhlc2UgdW5sZXNzIHlvdSBrbm93IHdoYXQgeW91IGFyZSBkb2luZy4gQWxzbywgYnkgZGVmYXVsdCwgdGhlIGRhdGEgYXJlIHNwbGl0IGludG8gYSB0cmFpbmluZyBhbmQgYSB0ZXN0aW5nIHNldC4gVGhlIHRyYWluaW5nIGlzIGVxdWFsIHRvIDIvM3Mgb2YgdGhlIGRhdGE7IHRoZSB0ZXN0aW5nIGlzIDEvMy4NCg0KMS4gYHJlcGxhY2UgPSBGQUxTRWAgPSBVc2Ugc3Vic2FtcGxpbmcgd2l0aCBvciB3aXRob3V0IHJlcGxhY2VtZW50LiBEZWZhdWx0IGlzIGBGQUxTRWAsIGkuZS4sIHVzZSBzdWJzZXRzIG9mIHRoZSBkYXRhIHdpdGhvdXQgcmVwbGFjaW5nIHRoZXNlLiAgDQoyLiBgZnJhY3Rpb24gPSAwLjYzMmAgPSBVc2UgNjMuMiUgb2YgdGhlIGRhdGEgaW4gZWFjaCBzcGxpdC4gDQoNCg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NTYpDQptZGwuY2ZvcmVzdCA8LSBkZlBoYXJWMiAlPiUgDQogIGNmb3Jlc3QoY29udGV4dCB+IC4sIGRhdGEgPSAuLCANCiAgICAgICAgICBjb250cm9scyA9IGNmb3Jlc3RfdW5iaWFzZWQobnRyZWUgPSAxMDAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gcm91bmQoc3FydCgyMykpKSkNCmBgYA0KDQoNCiMjIyBQcmVkaWN0aW9ucw0KDQpUbyBvYnRhaW4gcHJlZGljdGlvbnMgZnJvbSB0aGUgbW9kZWwsIHdlIHVzZSB0aGUgYHByZWRpY3RgIGZ1bmN0aW9uIGFuZCBhZGQgYE9PQiA9IFRSVUVgLiBUaGlzIHVzZXMgdGhlIG91dC1vZi1iYWcgc2FtcGxlIChpLmUuLCAxLzMgb2YgdGhlIGRhdGEpLiANCg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NTYpDQpwcmVkLmNmb3Jlc3QgPC0gcHJlZGljdChtZGwuY2ZvcmVzdCwgT09CID0gVFJVRSkNCnRibC5jZm9yZXN0IDwtIHRhYmxlKHByZWQuY2ZvcmVzdCwgZGZQaGFyVjIkY29udGV4dCkNCnRibC5jZm9yZXN0DQpQcmVzZW5jZUFic2VuY2U6OnBjYyh0YmwuY2ZvcmVzdCkNClByZXNlbmNlQWJzZW5jZTo6c3BlY2lmaWNpdHkodGJsLmNmb3Jlc3QpDQpQcmVzZW5jZUFic2VuY2U6OnNlbnNpdGl2aXR5KHRibC5jZm9yZXN0KQ0KDQpyb2MuY2ZvcmVzdCA8LSBwUk9DOjpyb2MoZGZQaGFyVjIkY29udGV4dCwgYXMubnVtZXJpYyhwcmVkLmNmb3Jlc3QpKQ0Kcm9jLmNmb3Jlc3QNCnBST0M6OnBsb3Qucm9jKHJvYy5jZm9yZXN0LCBsZWdhY3kuYXhlcyA9IFRSVUUpDQpgYGANCg0KQ29tcGFyZWQgd2l0aCB0aGUgODIuOCUgY2xhc3NpZmljYXRpb24gYWNjdXJhY3kgd2Ugb2J0YWluZWQgdXNpbmcgYGN0cmVlYCB1c2luZyBvdXIgZnVsbCBkYXRhc2V0IGFib3ZlIChtb2RlbCAxKSwgaGVyZSB3ZSBvYnRhaW4gODUuNSUgd2l0aCBhbiAyLjclIGluY3JlYXNlLiBDb21wYXJlZCB3aXRoIHRoZSA2Ny40JSBmcm9tIG1vZGVsIDIgZnJvbSBgY3RyZWVgIHdpdGggcmFuZG9tIHNlbGVjdGlvbiBvZiBwcmVkaWN0b3JzLCB3ZSBoYXZlIGFuIDE4LjElIGluY3JlYXNlIGluIGNsYXNzaWZpY2F0aW9uIGFjY3VyYWN5IQ0KDQpXZSBjb3VsZCB0ZXN0IHdoZXRoZXIgdGhlcmUgaXMgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGJldHdlZW4gb3VyIGBjdHJlZWAgYW5kIGBjZm9yZXN0YCBtb2RlbHMuIFVzaW5nIHRoZSBST0MgY3VydmVzLCB0aGUgYHJvYy50ZXN0YCBjb25kdWN0cyBhIG5vbi1wYXJhbWV0cmljIFogdGVzdCBvZiBzaWduaWZpY2FuY2Ugb24gdGhlIGNvcnJlbGF0ZWQgUk9DIGN1cnZlcy4gVGhlIHJlc3VsdHMgc2hvdyBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgaW1wcm92ZW1lbnQgdXNpbmcgdGhlIGBjZm9yZXN0YCBtb2RlbC4gVGhpcyBpcyBub3JtYWwgYmVjYXVzZSB3ZSBhcmUgZ3Jvd2luZyAxMDAgZGlmZmVyZW50IHRyZWVzLCB3aXRoIHJhbmRvbSBzZWxlY3Rpb24gb2YgYm90aCBwcmVkaWN0b3JzIGFuZCBzYW1wbGVzIGFuZCBwcm92aWRlIGFuIGBhdmVyYWdlZGAgcHJlZGljdGlvbi4gDQoNCmBgYHtyfQ0KcFJPQzo6cm9jLnRlc3Qocm9jLmN0cmVlLCByb2MuY2ZvcmVzdCkNCnBST0M6OnJvYy50ZXN0KHJvYy5jdHJlZTEsIHJvYy5jZm9yZXN0KQ0KYGBgDQoNCg0KIyMjIFZhcmlhYmxlIEltcG9ydGFuY2UgU2NvcmVzDQoNCk9uZSBpbXBvcnRhbnQgZmVhdHVyZSBpbiBgY3RyZWVgIHdhcyB0byBzaG93IHdoaWNoIHByZWRpY3RvciB3YXMgdXNlZCBmaXJzdCBpcyBzcGxpdHRpbmcgdGhlIGRhdGEsIHdoaWNoIHdhcyB0aGVuIGZvbGxvd2VkIGJ5IHRoZSBvdGhlciBwcmVkaWN0b3JzLiBXZSB1c2UgYSBzaW1pbGFyIGZ1bmN0aW9uYWxpdHkgd2l0aCBgY2ZvcmVzdGAgdG8gb2J0YWluIHZhcmlhYmxlIGltcG9ydGFuY2Ugc2NvcmVzIHRvIHBpbnBvaW50IGBzdHJvbmdgIGFuZCBgd2Vha2AgcHJlZGljdG9ycy4gDQoNClRoZXJlIGFyZSB0d28gd2F5cyB0byBvYnRhaW4gdGhpczoNCg0KMS4gU2ltcGxlIHBlcm11dGF0aW9uIHRlc3RzIChjb25kaXRpb25hbCA9IEZBTFNFKQ0KMi4gQ29uZGl0aW9uYWwgcGVybXV0YXRpb24gdGVzdHMgKGNvbmRpdGlvbmFsID0gVFJVRSkNCg0KVGhlIGZvcm1lciBpcyBnZW5lcmFsbHkgY29tcGFyYWJsZSBhY3Jvc3MgcGFja2FnZXMgYW5kIHByb3ZpZGVzIGEgbm9ybWFsIHBlcm11dGF0aW9uIHRlc3Q7IHRoZSBsYXR0ZXIgcnVucyBhIHBlcm11dGF0aW9uIHRlc3Qgb24gYSBncmlkIGRlZmluZWQgYnkgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeCBhbmQgY29ycmVjdHMgZm9yIHBvc3NpYmxlIGNvbGxpbmVhcml0eS4gVGhpcyBpcyBzaW1pbGFyIHRvIGEgcmVncmVzc2lvbiBhbmFseXNpcywgYnV0IGxvb2tzIGF0IGJvdGggbWFpbiBlZmZlY3RzIGFuZCBpbnRlcmFjdGlvbnMuIA0KDQpZb3UgY291bGQgdXNlIHRoZSBub3JtYWwgYHZhcmltcGAgYXMgaW1wbGVtZW50ZWQgaW4gYHBhcnR5YC4gVGhpcyB1c2VzIG1lYW4gZGVjcmVhc2UgaW4gYWNjdXJhY3kgc2NvcmVzLiBXZSB3aWxsIHVzZSB2YXJpYWJsZSBpbXBvcnRhbmNlIHNjb3JlcyB2aWEgYW4gQVVDIGJhc2VkIHBlcm11dGF0aW9uIHRlc3RzIGFzIHRoaXMgdXNlcyBib3RoIGFjY3VyYWN5IGFuZCBlcnJvcnMgaW4gdGhlIG1vZGVsLCB1c2luZyBgdmFySW1wQVVDYCBmcm9tIHRoZSBgdmFySW1wYCBwYWNrYWdlLg0KDQoqKkRBTkdFUiBaT05FKio6IHVzaW5nIGNvbmRpdGlvbmFsIHBlcm11dGF0aW9uIHRlc3QgcmVxdWlyZXMgYSBsb3Qgb2YgUkFNcywgdW5sZXNzIHlvdSBoYXZlIGFjY2VzcyB0byBhIGNsdXN0ZXIsIGFuZC9vciBhIGxvdCBvZiBSQU1zLCBkbyBub3QgYXR0ZW1wdCBydW5uaW5nIGl0LiBXZSB3aWxsIHJ1biB0aGUgbm9uLWNvbmRpdGlvbmFsIHZlcnNpb24gaGVyZSBmb3IgZGVtb25zdHJhdGlvbi4NCg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMzQ1NikNClZhckltcC5jZm9yZXN0IDwtIHZhckltcDo6dmFySW1wQVVDKG1kbC5jZm9yZXN0LCBjb25kaXRpb25hbCA9IEZBTFNFKQ0KbGF0dGljZTo6YmFyY2hhcnQoc29ydChWYXJJbXAuY2ZvcmVzdCkpDQpgYGANCg0KDQpUaGUgVmFyaWFibGUgSW1wb3J0YW5jZSBTY29yZXMgdmlhIG5vbi1jb25kaXRpb25hbCBwZXJtdXRhdGlvbiB0ZXN0cyBzaG93ZWQgdGhhdCBgQTIqLUEzKmAgKGkuZS4sIGVuZXJneSBpbiBtaWQtaGlnaCBmcmVxdWVuY2llcyBhcm91bmQgRjIgYW5kIEYzKSBpcyB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGUgYXQgZXhwbGFpbmluZyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGd1dHR1cmFscyBhbmQgbm9uLWd1dHR1cmFscywgZm9sbG93ZWQgYnkgYFo0LVozYCAocGhhcnluZ2VhbCBjb25zdHJpY3Rpb24pLCBgSDEqLUEzKmAgKGVuZXJneSBpbiBtaWQtaGlnaCBmcmVxdWVuY3kgY29tcG9uZW50KSwgYFoyLVoxYCAoZGVncmVlIG9mIGNvbXBhY3RuZXNzKSwgYFozLVoyYCAoc3BlY3RyYWwgZGl2ZXJnZW5jZSksIGBIMSotQTJgIChlbmVyZ3kgaW4gbWlkIGZyZXF1ZW5jeSBjb21wb25lbnQpIGFuZCBgWjEtWjBgIChkZWdyZWUgb2Ygb3Blbm5lc3MpLiBBbGwgb3RoZXIgcHJlZGljdG9ycyBjb250cmlidXRlIHRvIHRoZSBjb250cmFzdCBidXQgdG8gdmFyeWluZyBkZWdyZWVzIChmcm9tIGBIMSotSDIqYCB0byBgSDEqLUExKmApLiBUaGUgbGFzdCA1IHByZWRpY3RvcnMgYXJlIHRoZSBsZWFzdCBpbXBvcnRhbnQgYW5kIGFuZCB0aGUgQ1BQIGhhcyBhIDAgbWVhbiBkZWNyZWFzZSBpbiBhY2N1cmFjeSBhbmQgY2FuIGV2ZW4gYmUgaWdub3JlZC4gDQoNCg0KIyMjIENvbmNsdXNpb24NCg0KVGhlIGBwYXJ0eWAgcGFja2FnZSBpcyBwb3dlcmZ1bCBhdCBncm93aW5nIFJhbmRvbSBGb3Jlc3RzIHZpYSBjb25kaXRpb25hbCBJbmZlcmVuY2UgdHJlZXMsIGJ1dCBpcyBjb21wdXRhdGlvbmFsbHkgcHJvaGliaXRpdmUgd2hlbiBpbmNyZWFzaW5nIG51bWJlciBvZiB0cmVlcyBhbmQgdXNpbmcgY29uZGl0aW9uYWwgcGVybXV0YXRpb24gdGVzdHMgb2YgdmFyaWFibGUgaW1wb3J0YW5jZSBzY29yZXMuIFdlIGxvb2sgbmV4dCBhdCB0aGUgcGFja2FnZSBgcmFuZ2VyYCBkdWUgdG8gaXRzIHNwZWVkIGluIGNvbXB1dGF0aW9uIGFuZCBmbGV4aWJpbGl0eS4gDQoNCiMjIFJhbmdlcg0KDQpUaGUgYHJhbmdlcmAgcGFja2FnZSBwcm9wb3NlcyBhIHJlaW1wbGVtZW50YXRpb24gb2YgdGhlIG9yaWdpbmFsIFJhbmRvbSBGb3Jlc3RzIGFsZ29yaXRobXMsIHdyaXR0ZW4gaW4gQysrIGFuZCBhbGxvd3MgZm9yIHBhcmFsbGVsIGNvbXB1dGluZy4gSXQgb2ZmZXJzIG1vcmUgZmxleGliaWxpdHkgaW4gdGVybXMgb2YgbW9kZWwgc3BlY2lmaWNhdGlvbi4gDQoNCiMjIyBNb2RlbCBzcGVjaWZpY2F0aW9uDQoNCkluIHRoZSBtb2RlbCBiZWxvdyBzcGVjaWZpY2F0aW9uIGJlbG93LCB0aGVyZSBhcmUgYWxyZWFkeSBhIGZldyBvcHRpb25zIHdlIGFyZSBmYW1pbGlhciB3aXRoLCB3aXRoIGFkZGl0aW9uYWwgb25lcyBkZXNjcmliZWQgYmVsb3c6DQoNCjEuIGBudW0udHJlZWAgPSBOdW1iZXIgb2YgdHJlZXMgdG8gZ3Jvdy4gV2UgdXNlIHRoZSBkZWZhdWx0IHZhbHVlDQoyLiBgbXRyeWAgPSBOdW1iZXIgb2YgcHJlZGljdG9ycyB0byB1c2UuIERlZmF1bHQgPSBgZmxvb3Ioc3FydChWYXJpYWJsZXMpKWAuIEZvciBjb21wYXRpYmlsaXR5IHdpdGggYHBhcnR5YCwgd2UgdXNlIGByb3VuZChzcXJ0KDIzKSlgDQozLiBgcmVwbGFjZSA9IEZBTFNFYCA9IFVzZSBzdWJzYW1wbGluZyB3aXRoIG9yIHdpdGhvdXQgcmVwbGFjZW1lbnQuIERlZmF1bHQgYHJlcGxhY2UgPSBUUlVFYCwgaS5lLiwgaXMgKip3aXRoKiogcmVwbGFjZW1lbnQuIA0KNC4gYHNhbXBsZS5mcmFjdGlvbiA9IDAuNjMyYCA9IFVzZSA2My4yJSBvZiB0aGUgZGF0YSBpbiBlYWNoIHNwbGl0LiBEZWZhdWx0IGlzIGZ1bGwgZGF0YXNldCwgaS5lLiwgYHNhbXBsZS5mcmFjdGlvbiA9IDFgDQo1LiBgaW1wb3J0YW5jZSA9ICJwZXJtdXRhdGlvbiJgID0gQ29tcHV0ZSB2YXJpYWJsZSBpbXBvcnRhbmNlIHNjb3JlcyB2aWEgcGVybXV0YXRpb24gdGVzdHMNCjYuIGBzY2FsZS5wZXJtdXRhdGlvbi5pbXBvcnRhbmNlID0gRkFMU0VgID0gd2hldGhlciB0byBzY2FsZSB2YXJpYWJsZSBpbXBvcnRhbmNlIHNjb3JlcyB0byBiZSBvdXQgb2YgMTAwJS4gRGVmYXVsdCBpcyBUUlVFLiBUaGlzIGlzIGxpa2VseSB0byBpbnRyb2R1Y2UgYmlhc2VzIGluIHZhcmlhYmxlIGltcG9ydGFuY2UgZXN0aW1hdGlvbi4gDQo3LiBgc3BsaXRydWxlID0gImV4dHJhdHJlZXMiYCA9IHJ1bGUgdXNlZCBmb3Igc3BsaXR0aW5nIHRyZWVzLiANCjguIGBudW0udGhyZWFkc2AgPSBhbGxvdyBmb3IgcGFyYWxsZWwgY29tcHV0aW5nLiBIZXJlIHdlIG9ubHkgc3BlY2lmeSAxIHRocmVhZCwgYnV0IGNhbiB1c2UgYWxsIHRocmVhZCBvbiB5b3VyIGNvbXB1dGVyIChvciBjbHVzdGVyKS4NCg0KDQpXZSB1c2Ugb3B0aW9ucyAyLTcgdG8gbWFrZSBzdXJlIHdlIGhhdmUgYW4gdW5iaWFzZWQgc2VsZWN0aW9uIHByb2Nlc3Mgd2l0aCBgcmFuZ2VyYC4gWW91IGNhbiB0cnkgb24geW91ciBvd24gcnVubmluZyB0aGUgbW9kZWwgYmVsb3cgYnkgdXNpbmcgdGhlIGRlZmF1bHRzIHRvIHNlZSBob3cgdGhlIHJhdGUgb2YgY2xhc3NpZmljYXRpb24gaW5jcmVhc2VzIG1vcmUsIGJ1dCB3aXRoIHRoZSBjYXZlYXQgdGhhdCBpdCBoYXMgYSBiaWFzZWQgc2VsZWN0aW9uIHByb2Nlc3MuIA0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMzQ1NikNCm1kbC5yYW5nZXIgPC0gZGZQaGFyVjIgJT4lIA0KICByYW5nZXIoY29udGV4dCB+IC4sIGRhdGEgPSAuLCBudW0udHJlZXMgPSA1MDAsIG10cnkgPSByb3VuZChzcXJ0KDIzKSksDQogICAgICAgICByZXBsYWNlID0gRkFMU0UsIHNhbXBsZS5mcmFjdGlvbiA9IDAuNjMyLCANCiAgICAgICAgIGltcG9ydGFuY2UgPSAicGVybXV0YXRpb24iLCBzY2FsZS5wZXJtdXRhdGlvbi5pbXBvcnRhbmNlID0gRkFMU0UsDQogICAgICAgICBzcGxpdHJ1bGUgPSAiZXh0cmF0cmVlcyIsIG51bS50aHJlYWRzID0gMSkNCm1kbC5yYW5nZXINCmBgYA0KDQoNClJlc3VsdHMgb2Ygb3VyIFJhbmRvbSBGb3Jlc3Qgc2hvd3MgYW4gT09CIChPdXQtT2YtQmFnKSBlcnJvciByYXRlIG9mIDguMiUsIGkuZS4sIGFuIGFjY3VyYWN5IG9mIDkxLjglLg0KDQojIyMgR29pbmcgZnVydGhlcg0KDQpVbmZvcnR1bmF0ZWx5LCB3aGVuIGdyb3dpbmcgYSB0cmVlIHdpdGggYHJhbmdlcmAsIHdlIGNhbm5vdCB1c2UgcHJlZGljdGlvbnMgZnJvbSB0aGUgT09CIHNhbXBsZSBhcyB0aGVyZSBpcyBubyBjb21wYXJhYmxlIG9wdGlvbnMgdG8gZG8gc28gb24gdGhlIHByZWRpY3Rpb25zLiBXZSBuZWVkIHRvIGhhcmQtY29kZSB0aGlzLiBXZSBzcGxpdCB0aGUgZGF0YSBpbnRvIGEgdHJhaW5pbmcgYW5kIGEgdGVzdGluZyBzZXRzLiBUaGUgdHJhaW5pbmcgd2lsbCBiZSBvbiAyLzNzIG9mIHRoZSBkYXRhOyB0aGUgdGVzdGluZyBpcyBvbiB0aGUgcmVtYWluaW5nIDEvMy4gDQoNCiMjIyMgQ3JlYXRlIGEgdHJhaW5pbmcgYW5kIGEgdGVzdGluZyBzZXQNCg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NTYpDQp0cmFpbi5pZHggPC0gc2FtcGxlKG5yb3coZGZQaGFyVjIpLCAyLzMgKiBucm93KGRmUGhhclYyKSkNCmd1dHQudHJhaW4gPC0gZGZQaGFyVjJbdHJhaW4uaWR4LCBdDQpndXR0LnRlc3QgPC0gZGZQaGFyVjJbLXRyYWluLmlkeCwgXQ0KYGBgDQoNCg0KIyMjIyBNb2RlbCBzcGVjaWZpY2F0aW9uDQoNCldlIHVzZSB0aGUgc2FtZSBtb2RlbCBzcGVjaWZpY2F0aW9uIGFzIGFib3ZlLCBleGNlcHQgZnJvbSB1c2luZyB0aGUgdHJhaW5pbmcgc2V0IGFuZCBzYXZpbmcgdGhlIGZvcmVzdCAod2l0aCBgd3JpdGUuZm9yZXN0ID0gVFJVRWApLg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMzQ1NikNCm1kbC5yYW5nZXIyIDwtIGd1dHQudHJhaW4gJT4lIA0KICByYW5nZXIoY29udGV4dCB+IC4sIGRhdGEgPSAuLCBudW0udHJlZXMgPSA1MDAsIG10cnkgPSByb3VuZChzcXJ0KDIzKSksDQogICAgICAgICByZXBsYWNlID0gRkFMU0UsIHNhbXBsZS5mcmFjdGlvbiA9IDAuNjMyLCANCiAgICAgICAgIGltcG9ydGFuY2UgPSAicGVybXV0YXRpb24iLCBzY2FsZS5wZXJtdXRhdGlvbi5pbXBvcnRhbmNlID0gRkFMU0UsDQogICAgICAgICBzcGxpdHJ1bGUgPSAiZXh0cmF0cmVlcyIsIG51bS50aHJlYWRzID0gMSwgd3JpdGUuZm9yZXN0ID0gVFJVRSkNCm1kbC5yYW5nZXIyDQpgYGANCg0KV2l0aCB0aGUgdHJhaW5pbmcgc2V0LCB3ZSBoYXZlIGFuIE9PQiBlcnJvciByYXRlIG9mIDkuMyU7IGkuZS4sIGFuIGFjY3VyYWN5IHJhdGUgb2YgOTAuNyUuDQoNCiMjIyMgUHJlZGljdGlvbnMNCiANCkZvciB0aGUgcHJlZGljdGlvbnMsIHdlIHVzZSB0aGUgdGVzdGluZyBzZXQgYXMgYSB2YWxpZGF0aW9uIHNldC4gVGhpcyBpcyB0byBiZSBjb25zaWRlcmVkIGFzIGEgdHJ1ZSByZWZsZWN0aW9uIG9mIHRoZSBtb2RlbC4gVGhpcyBpcyB1bnNlZW4gZGF0YSBub3QgdXNlZCBpbiB0aGUgdHJhaW5pbmcgc2V0Lg0KDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNDU2KQ0KcHJlZC5yYW5nZXIyIDwtIHByZWRpY3QobWRsLnJhbmdlcjIsIGRhdGEgPSBndXR0LnRlc3QpDQp0YmwucmFuZ2VyMiA8LSB0YWJsZShwcmVkLnJhbmdlcjIkcHJlZGljdGlvbnMsIGd1dHQudGVzdCRjb250ZXh0KQ0KdGJsLnJhbmdlcjINClByZXNlbmNlQWJzZW5jZTo6cGNjKHRibC5yYW5nZXIyKQ0KUHJlc2VuY2VBYnNlbmNlOjpzcGVjaWZpY2l0eSh0YmwucmFuZ2VyMikNClByZXNlbmNlQWJzZW5jZTo6c2Vuc2l0aXZpdHkodGJsLnJhbmdlcjIpDQoNCnJvYy5yYW5nZXIgPC0gcFJPQzo6cm9jKGd1dHQudGVzdCRjb250ZXh0LCBhcy5udW1lcmljKHByZWQucmFuZ2VyMiRwcmVkaWN0aW9ucykpDQpyb2MucmFuZ2VyDQpwUk9DOjpwbG90LnJvYyhyb2MucmFuZ2VyLCBsZWdhY3kuYXhlcyA9IFRSVUUpDQpgYGANCg0KDQpUaGUgY2xhc3NpZmljYXRpb24gcmF0ZSBiYXNlZCBvbiB0aGUgdGVzdGluZyBzZXQgaXMgODYuNiUuIFRoaXMgaXMgY29tcGFyYWJsZSB0byB0aGUgb25lIHdlIG9idGFpbmVkIHdpdGggYGNmb3Jlc3RgLiBUaGUgY2hhbmdlcyBpbiB0aGUgc2V0dGluZ3MgYWxsb3cgZm9yIHNpbWlsYXJpdGllcyBpbiB0aGUgcHJlZGljdGlvbnMgb2J0YWluZWQgZnJvbSBib3RoIGBwYXJ0eWAgYW5kIGByYW5nZXJgLiANCg0KIyMjIyBWYXJpYWJsZSBJbXBvcnRhbmNlIFNjb3Jlcw0KDQojIyMjIyBEZWZhdWx0DQoNCkZvciB0aGUgdmFyaWFibGUgaW1wb3J0YW5jZSBzY29yZXMsIHdlIG9idGFpbiB0aGVtIGZyb20gZWl0aGVyIHRoZSB0cmFpbmluZyBzZXQgb3IgdGhlIGZ1bGwgbW9kZWwgYWJvdmUuDQoNCg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NTYpDQpsYXR0aWNlOjpiYXJjaGFydChzb3J0KG1kbC5yYW5nZXIyJHZhcmlhYmxlLmltcG9ydGFuY2UpLCBtYWluID0gIlZhcmlhYmxlIEltcG9ydGFuY2Ugc2NvcmVzIC0gdHJhaW5pbmcgc2V0IikNCmxhdHRpY2U6OmJhcmNoYXJ0KHNvcnQobWRsLnJhbmdlciR2YXJpYWJsZS5pbXBvcnRhbmNlKSwgbWFpbiA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIHNjb3JlcyAtIGZ1bGwgc2V0IikNCmBgYA0KDQoNClRoZXJlIGFyZSBzaW1pbGFyaXRpZXMgYmV0d2VlbiBgY2ZvcmVzdGAgYW5kIGByYW5nZXJgLCB3aXRoIG1pbm9yIGRpZmZlcmVuY2VzLiBgWjItWjFgIGlzIHRoZSBiZXN0IHByZWRpY3RvciBhdCBleHBsYWluaW5nIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGd1dHR1cmFscyBhbmQgbm9uLWd1dHR1cmFscyB3aXRoIGByYW5nZXJgIGZvbGxvd2VkIGJ5IGBaMy1aMmAgYW5kIHRoZW4gYEEyKi1BMypgLCAocmV2ZXJzZSB3aXRoIGBjZm9yZXN0YCEpLiBUaGUgb3JkZXIgb2YgdGhlIGFkZGl0aW9uYWwgcHJlZGljdG9ycyBpcyBzaWdodGx5IGRpZmZlcmVudCBiZXR3ZWVuIHRoZSB0d28gbW9kZWxzLiBUaGlzIGlzIGV4cGVjdGVkIGFzIHRoZSBgY2ZvcmVzdGAgbW9kZWwgb25seSB1c2VkIDEwMCB0cmVlcywgd2hlcmVhcyB0aGUgYHJhbmdlcmAgbW9kZWwgdXNlZCA1MDAgdHJlZXMuDQoNCg0KQSBjbGVhciBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHBhY2thZ2VzIGBwYXJ0eWAgYW5kIGByYW5nZXJgIGlzIHRoYXQgdGhlIGZvcm1lciBhbGxvd3MgZm9yIGNvbmRpdGlvbmFsIHBlcm11dGF0aW9uIHRlc3RzIGZvciB2YXJpYWJsZSBpbXBvcnRhbmNlIHNjb3JlczsgdGhpcyBpcyBhYnNlbnQgZnJvbSBgcmFuZ2VyYC4gSG93ZXZlciwgdGhlcmUgaXMgYSBkZWJhdGUgaW4gdGhlIGxpdGVyYXR1cmUgb24gd2hldGhlciBjb3JyZWxhdGVkIGRhdGEgYXJlIGhhcm1mdWwgd2l0aGluIFJhbmRvbSBGb3Jlc3RzLiBJdCBpcyBjbGVhciB0aGF0IGhvdyBSYW5kb20gRm9yZXN0cyB3b3JrLCBpLmUuLCB0aGUgcmFuZG9tbmVzcyBpbiB0aGUgc2VsZWN0aW9uIHByb2Nlc3MgaW4gbnVtYmVyIG9mIGRhdGEgcG9pbnRzLCBwcmVkaWN0b3JzLCBzcGxpdHRpbmcgcnVsZXMsIGV0Yy4gYWxsb3cgdGhlIHRyZWVzIHRvIGJlIGRlY29ycmVsYXRlZCBmcm9tIGVhY2ggb3RoZXIuIEhlbmNlLCB0aGUgY29uZGl0aW9uYWwgcGVybXV0YXRpb24gdGVzdHMgbWF5IG5vdCBiZSByZXF1aXJlZC4gQnV0IHdoYXQgdGhleSBvZmZlciBpcyB0byBjb25kaXRpb24gdmFyaWFibGUgaW1wb3J0YW5jZSBzY29yZXMgb24gZWFjaCBvdGhlciAoYmFzZWQgb24gY29ycmVsYXRpb24gdGVzdHMpIHRvIG1pbWljIHdoYXQgYSBtdWx0aXBsZSByZWdyZXNzaW9uIGFuYWx5c2lzIGRvZXMgKGJ1dCB3aXRob3V0IHN1ZmZlcmluZyBmcm9tIHN1cHByZXNzaW9uISkuIFN0cm9uZyBwcmVkaWN0b3JzIHdpbGwgc2hvdyBtYWpvciBjb250cmlidXRpb24sIHdoaWxlIHdlYWsgb25lcyB3aWxsIGJlIHNxdWFzaGVkIGdpdmluZyB0aGVtIGV4dHJlbWVseSBsb3cgKG9yIGV2ZW4gbmVnYXRpdmUpIHNjb3Jlcy4gV2l0aGluIGByYW5nZXJgLCBpdCBpcyBwb3NzaWJsZSB0byBldmFsdWF0ZSB0aGlzIGJ5IGVzdGltYXRpbmcgcCB2YWx1ZXMgYXNzb2NpYXRlZCB3aXRoIGVhY2ggdmFyaWFibGUgaW1wb3J0YW5jZS5XZSB1c2UgdGhlIGBhbHRtYW5gIG1ldGhvZC4gU2VlIGRvY3VtZW50YXRpb24gZm9yIG1vcmUgZGV0YWlscy4gDQoNCioqREFOR0VSIFpPTkUqKjogVGhpcyByZXF1aXJlcyBoZWF2eSBjb21wdXRhdGlvbnMuIFVzZSB3aXRoIGFsbCBjb3JlcyBvbiB5b3VyIG1hY2hpbmUgb3IgaW4gdGhlIGNsdXN0ZXIuIFJlY29tbWVuZGF0aW9ucyBhcmUgdG8gdXNlIGEgbWluaW11bSBvZiAxMDAgcGVybXV0YXRpb25zIG9yIG1vcmUsIGkuZS4sIGBudW0ucGVybXV0YXRpb25zID0gMTAwYC4gSGVyZSwgd2Ugb25seSB1c2UgMjAgdG8gc2hvdyB0aGUgb3V0cHV0Lg0KDQojIyMjIyBXaXRoIHAgdmFsdWVzDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNDU2KQ0KVmFySW1wLnB2YWwgPC0gaW1wb3J0YW5jZV9wdmFsdWVzKG1kbC5yYW5nZXIyLCBtZXRob2QgPSAiYWx0bWFubiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtLnBlcm11dGF0aW9ucyA9IDIwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtdWxhID0gY29udGV4dCB+IC4sIGRhdGEgPSBndXR0LnRyYWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bS50aHJlYWRzID0gMikNClZhckltcC5wdmFsDQpgYGANCg0KDQpPZiBjb3Vyc2UsIHRoZSBvdXRwdXQgYWJvdmUgc2hvd3MgdmFyaWFibGUgcCB2YWx1ZXMuIFRoZSBsb3dlc3QgaXMgYXQgMC4wNDggZm9yIGFsbCBwcmVkaWN0b3JzOyBvbmUgYXQgMC4xNCBmb3IgQ1BQLiBSZWNhbGwgdGhhdCBDUFAgcmVjZWl2ZWQgdGhlIGxvd2VzdCB2YXJpYWJsZSBpbXBvcnRhbmNlIHNjb3JlIHdpdGhpbiBgcmFuZ2VyYCBhbmQgYGNmb3Jlc3RgLiBJZiB5b3UgaW5jcmVhc2UgcGVybXV0YXRpb25zIHRvIDEwMCBvciAyMDAsIHlvdSB3aWxsIGdldCBtb3JlIGNvbmZpZGVuY2UgaW4geW91ciByZXN1bHRzIGFuZCBjYW4gcmVwb3J0IHRoZSBwIHZhbHVlcw0KDQoNCg0KIyMgVXAgdG8geW91IQ0KDQpGb2xsb3dpbmcgd2hhdCB3ZSBoYXZlIGRvbmUgYWJvdmUgd2l0aCBkZWNpc2lvbiB0cmVlcywgcnVuIGEgUmFuZG9tIEZvcmVzdCBlaXRoZXIgd2l0aCBgcGFydHlgIG9yIGByYW5nZXJgIG9uIGZvcm1hbnRzIG9yIHZvaWNlIHF1YWxpdHkuIERpc2N1c3Mgd2l0aCB5b3VyIHBlZXJzIHRoZSByZXN1bHRzIGFuZCB3ZSBjYW4gZGlzY3VzcyBhbnkgaXNzdWVzLi4gWW91IG5lZWQgdG8gMSkgc2VsZWN0IHZhcmlhYmxlcyBhbmQgMikgcnVuIHRoZSBjb2RlLiBEb24ndCBmb3JnZXQsIHlvdSBjYW4gc2ltcGx5IGNvcHkgYW5kIHBhc3RlIHRoZSBjb2RlLCBidXQgaXQgaXMgYmVzdCB0byB0cnkgdG8gcmVjYWxsIHRoZSBjYWxsIGZ1bmN0aW9ucy4gDQoNCg0KDQpgYGB7cn0NCiMgZm9ybWFudHMNCmBgYA0KDQoNCg0KDQoNCmBgYHtyfQ0KIyBWb2ljZSBxdWFsaXR5DQpgYGANCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCkluIHRoZSBuZXh0IHBhcnQsIHdlIGxvb2sgYXQgdGhlIGB0aWR5bW9kZWxzYCBhbmQgaW50cm9kdWNlIHRoZWlyIHBoaWxvc29waHkuIA0KDQojIFJhbmRvbSBmb3Jlc3RzIHdpdGggVGlkeW1vZGVscw0KDQpUaGUgYHRpZHltb2RlbHNgIGFyZSBhIGJ1bmRsZSBvZiBwYWNrYWdlcyB1c2VkIHRvIHN0cmVhbWxpbmUgYW5kIHNpbXBsaWZ5IHRoZSB1c2Ugb2YgbWFjaGluZSBsZWFybmluZy4gVGhlIGB0aWR5bW9kZWxzYCBhcmUgbm90IHJlc3RyaWN0ZWQgdG8gUmFuZG9tIEZvcmVzdHMsIGFuZCB5b3UgY2FuIGV2ZW4gdXNlIHRoZW0gdG8gcnVuIHNpbXBsZSBsaW5lYXIgbW9kZWxzLCBsb2dpc3RpYyByZWdyZXNzaW9ucywgUENBLCBSYW5kb20gRm9yZXN0cywgRGVlcCBMZWFybmluZywgZXRjLg0KDQpUaGUgYHRpZHltb2RlbHNgJyBwaGlsb3NvcGh5IGlzIHRvIHNlcGFyYXRlIGRhdGEgcHJvY2Vzc2luZyBvbiB0aGUgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cywgYW5kIHVzZSBvZiBhIHdvcmtmbG93LiBCZWxvdywgaXMgYW4gZnVsbCBleGFtcGxlIG9mIGhvdyBvbmUgY2FuIHJ1biBSYW5kb20gRm9yZXN0cyB3aXRoIHZpYSBgcmFuZ2VyYCB1c2luZyB0aGUgYHRpZHltb2RlbHNgLg0KDQojIyBUcmFpbmluZyBhbmQgdGVzdGluZyBzZXRzDQoNCldlIHN0YXJ0IGJ5IGNyZWF0aW5nIGEgdHJhaW5pbmcgYW5kIGEgdGVzdGluZyBzZXQgdXNpbmcgdGhlIGZ1bmN0aW9uIGBpbml0aWFsX3NwbGl0YC4gVXNpbmcgYHN0cmF0YSA9IGNvbnRleHRgIGFsbG93cyB0aGUgbW9kZWwgdG8gc3BsaXQgdGhlIGRhdGEgdGFraW5nIGludG8gYWNjb3VudCBpdHMgc3RydWN0dXJlIGFuZCBzcGxpdHMgdGhlIGRhdGEgYWNjb3JkaW5nIHRvIHByb3BvcnRpb25zIG9mIGVhY2ggZ3JvdXAuIA0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMzQ1NikNCnRyYWluX3Rlc3Rfc3BsaXQgPC0NCiAgaW5pdGlhbF9zcGxpdCgNCiAgICBkYXRhID0gZGZQaGFyVjIsDQogICAgc3RyYXRhID0gImNvbnRleHQiLA0KICAgIHByb3AgPSAwLjY2NykgDQp0cmFpbl90ZXN0X3NwbGl0DQp0cmFpbl90YmwgPC0gdHJhaW5fdGVzdF9zcGxpdCAlPiUgdHJhaW5pbmcoKSANCnRlc3RfdGJsICA8LSB0cmFpbl90ZXN0X3NwbGl0ICU+JSB0ZXN0aW5nKCkNCmBgYA0KDQoNCiMjIFNldCBmb3IgY3Jvc3MtdmFsaWRhdGlvbg0KDQpXZSBjYW4gKGlmIHdlIHdhbnQgdG8pLCBjcmVhdGUgYSAxMC1mb2xkcyBjcm9zcy12YWxpZGF0aW9uIG9uIHRoZSB0cmFpbmluZyBzZXQuIFRoaXMgYWxsb3dzIHRvIGZpbmUgdHVuZSB0aGUgdHJhaW5pbmcgYnkgb2J0YWluaW5nIHRoZSBmb3Jlc3Qgd2l0aCB0aGUgaGlnaGVzdCBhY2N1cmFjeS4gVGhpcyBpcyBhIGNsZWFyIGRpZmZlcmVuY2Ugd2l0aCBgcmFuZ2VyYC4gV2hpbGUgaXQgaXMgbm90IGltcG9zc2libGUgdG8gaGFyZCBjb2RlIHRoYXQsIGB0aWR5bW9kZWxzYCBzaW1wbGlmeSBpdCBmb3IgdXMhIQ0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMzQ1NikNCnRyYWluX2N2IDwtIHZmb2xkX2N2KHRyYWluX3RibCwgdiA9IDEwLCBzdHJhdGEgPSAiY29udGV4dCIpDQpgYGANCg0KIyMgTW9kZWwgU3BlY2lmaWNhdGlvbg0KDQpXaXRoaW4gdGhlIG1vZGVsIHNwZWNpZmljYXRpb24sIHdlIG5lZWQgdG8gc3BlY2lmeSBtdWx0aXBsZSBvcHRpb25zOg0KDQoxLiBBIGByZWNpcGVgOiBUaGlzIGlzIHRoZSByZWNpcGUgYW5kIGlzIHJlbGF0ZWQgdG8gYW55IGRhdGEgcHJvY2Vzc2luZyBvbmUgd2FudHMgdG8gYXBwbHkgb24gdGhlIGRhdGEuDQoyLiBBbiBgZW5naW5lYDogV2UgbmVlZCB0byBzcGVjaWZ5IHRoZSBgZW5naW5lYCB0byB1c2UuIEhlcmUgd2Ugd2FudCB0byBydW4gYSBSYW5kb20gRm9yZXN0Lg0KMy4gQSBgdHVuaW5nYDogSGVyZSB3ZSBjYW4gdHVuZSBvdXIgZW5naW5lDQo0LiBBIGB3b3JrZmxvd2A6IGhlcmUgd2Ugc3BlY2lmeSB0aGUgdmFyaW91cyBzdGVwcyBvZiB0aGUgd29ya2Zsb3cNCg0KDQojIyMgUmVjaXBlDQoNCldoZW4gZGVmaW5pbmcgdGhlIHJlY2lwZSwgeW91IG5lZWQgdG8gdGhpbmsgb2YgdGhlIHR5cGUgb2YgInRyYW5zZm9ybWF0aW9ucyIgeW91IHdpbGwgYXBwbHkgdG8geW91ciBkYXRhLiANCg0KMS4gWi1zY29yaW5nIGlzIHRoZSBmaXJzdCB0aGluZyB0aGF0IGNvbWVzIHRvIG1pbmQuIFdoZW4geW91IHotc2NvcmUgdGhlIGRhdGEsIHlvdSBhcmUgYWxsb3dpbmcgYWxsIHN0cm9uZyBhbmQgd2VhayBwcmVkaWN0b3JzIHRvIGJlIGNvbnNpZGVyZWQgZXF1YWxseSBieSB0aGUgbW9kZWwuIFRoaXMgaXMgaW1wb3J0YW50IGFzIHNvbWUgb2Ygb3VyIHByZWRpY3RvcnMgaGF2ZSB2ZXJ5IGxhcmdlIGRpZmZlcmVuY2VzIHJlbGF0ZWQgdG8gdGhlIGxldmVscyBvZiBjb250ZXh0IGFuZCBoYXZlIGRpZmZlcmVudCBtZWFzdXJlbWVudCBzY2FsZXMuIFdlIGNvdWxkIGhhdmUgYXBwbGllZCBpdCBhYm92ZSwgYnV0IHdlIG5lZWQgdG8gbWFrZSBzdXJlIHRvIGFwcGx5IGl0IHNlcGFyYXRlbHkgb24gYm90aCB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzIChvdGhlcndpc2UsIG91ciBtb2RlbCBzdWZmZXJzIGZyb20gZGF0YSBsZWFrYWdlKQ0KMi4gSWYgeW91IGhhdmUgYW55IG1pc3NpbmcgZGF0YSwgeW91IGNhbiB1c2UgY2VudHJhbCBpbXB1dGF0aW9ucyB0byBmaWxsIGluIG1pc3NpbmcgZGF0YSAocmFuZG9tIGZvcmVzdHMgZG8gbm90IGxpa2UgbWlzc2luZyBkYXRhLCB0aG91Z2ggdGhleSBjYW4gd29yayB3aXRoIHRoZW0pLiANCjMuIFlvdSBjYW4gYXBwbHkgUENBIG9uIGFsbCB5b3VyIHByZWRpY3RvcnMgdG8gcmVtb3ZlIGNvbGxpbmVhcml0eSBiZWZvcmUgcnVubmluZyByYW5kb20gZm9yZXN0cy4gVGhpcyBpcyBhIGdyZWF0IG9wdGlvbiB0byBjb25zaWRlciwgYnV0IGFkZHMgbW9yZSBjb21wbGV4aXR5IHRvIHlvdXIgbW9kZWwuIA0KNC5GaW5hbGx5LCBpZiB5b3UgaGF2ZSBjYXRlZ29yaWNhbCBwcmVkaWN0b3JzLCB5b3UgY2FuIHRyYW5zZm9ybSB0aGVtIGludG8gZHVtbXkgdmFyaWFibGVzIHVzaW5nIGBzdGVwX2R1bW15KClgOiAxcyBhbmQgMnMgZm9yIGJpbmFyeTsgb3IgdXNlIG9uZS1ob3QtZW5jb2RpbmcgYHN0ZXBfZHVtbXkocHJlZGljdG9yLCBvbmVfaG90ID0gVFJVRSlgDQoNClNlZSBkb2N1bWVudGF0aW9ucyBvZiBgdGlkeW1vZGVsc2AgZm9yIHdoYXQgeW91IGNhbiBhcHBseSEhDQoNCg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NTYpDQpyZWNpcGUgPC0gIA0KICB0cmFpbl90YmwgJT4lDQogIHJlY2lwZShjb250ZXh0IH4gLikgJT4lDQogIHN0ZXBfY2VudGVyKGFsbF9wcmVkaWN0b3JzKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lDQogIHN0ZXBfc2NhbGUoYWxsX3ByZWRpY3RvcnMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUgDQogIHByZXAoKQ0KDQp0cmFpbkRhdGFfYmFrZWQgPC0gYmFrZShyZWNpcGUsIG5ld19kYXRhID0gdHJhaW5fdGJsKSAjIGNvbnZlcnQgdG8gdGhlIHRyYWluIGRhdGEgdG8gdGhlIG5ld2x5IGltcHV0ZWQgZGF0YQ0KdHJhaW5EYXRhX2Jha2VkDQoNCmBgYA0KDQpPbmNlIHdlIGhhdmUgcHJlcGFyZWQgdGhlIGByZWNpcGVgLCB3ZSBjYW4gYGJha2UgaXRgIHRvIHNlZSB0aGUgY2hhbmdlcyBhcHBsaWVkIHRvIGl0Lg0KDQojIyMgU2V0dGluZyB0aGUgZW5naW5lDQoNCldlIHNldCB0aGUgZW5naW5lIGhlcmUgYXMgYSBgcmFuZF9mb3Jlc3RgLiBXZSBzcGVjaWZ5IGEgY2xhc3NpZmljYXRpb24gbW9kZS4gVGhlbiwgd2Ugc2V0IGFuIGVuZ2luZSB3aXRoIGVuZ2luZSBzcGVjaWZpYyBwYXJhbWV0ZXJzLiANCg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NTYpDQplbmdpbmVfdGlkeW0gPC0gcmFuZF9mb3Jlc3QoDQogICAgbW9kZSA9ICJjbGFzc2lmaWNhdGlvbiIsDQogICAgIyMgdG8gdHVuZSBib3RoIG10cnkgYW5kIHRyZWVzLCB1bmNvbW1lbnQgdGhlIHR3byBsaW5lcyBiZWxvdyBhbmQgY29tbWVudCB0aGUgbmV4dCB0d28gbGluZXMNCiAgICAjI210cnkgJT4lIHR1bmUoKSwNCiAgICAjI3RyZWVzICU+JSB0dW5lKCksDQogICAgbXRyeSA9IHJvdW5kKHNxcnQoMjMpKSwNCiAgICB0cmVlcyA9IDUwMCwNCiAgICBtaW5fbiA9IDENCiAgKSAlPiUgDQogIHNldF9lbmdpbmUoInJhbmdlciIsIGltcG9ydGFuY2UgPSAicGVybXV0YXRpb24iLCBzYW1wbGUuZnJhY3Rpb24gPSAwLjYzMiwNCiAgICAgICAgICAgICByZXBsYWNlID0gRkFMU0UsIHdyaXRlLmZvcmVzdCA9IFQsIHNwbGl0cnVsZSA9ICJleHRyYXRyZWVzIiwNCiAgICAgICAgICAgICBzY2FsZS5wZXJtdXRhdGlvbi5pbXBvcnRhbmNlID0gRkFMU0UpICMgd2UgYWRkIGVuZ2luZSBzcGVjaWZpYyBzZXR0aW5ncw0KYGBgDQoNCiMjIyBTZXR0aW5ncyBmb3IgdHVuaW5nDQoNCklmIHdlIHdhbnQgdG8gdHVuZSB0aGUgbW9kZWwsIHRoZW4gdW5jb21tZW50IHRoZSBsaW5lcyBiZWxvdy4gSXQgaXMgaW1wb3J0YW50IHRvIHVzZSBhbiBtdHJ5IHRoYXQgaG92ZXJzIGFyb3VuZCB0aGUgcm91bmQoc3FydChWYXJpYWJsZXMpKS4gSWYgeW91IHVzZSBhbGwgYXZhaWxhYmxlIHZhcmlhYmxlcywgdGhlbiB5b3VyIGZvcmVzdCBpcyBiaWFzZWQgYXMgaXQgaXMgYWJsZSB0byBzZWUgYWxsIHByZWRpY3RvcnMuIEZvciBudW1iZXIgb2YgdHJlZXMsIGxvdyBudW1iZXJzIGFyZSBub3QgZ3JlYXQsIGFzIHlvdSBjYW4gZWFzaWx5IHVuZGVyZml0IHRoZSBkYXRhIGFuZCBub3QgcHJvZHVjZSBtZWFuaW5nZnVsIHJlc3VsdHMuIExhcmdlIG51bWJlcnMgYXJlIGZpbmUgYW5kIFJhbmRvbSBGb3Jlc3RzIGRvIG5vdCBvdmVyZml0IChpbiB0aGVvcnkpLiANCg0KVGhlIGZ1bGwgZGF0YXNldCBoYXMgYXJvdW5kIDIwMDAgb2JzZXJ2YXRpb25zLCBhbmQgMjMgcHJlZGljdG9ycyAod2VsbCBldmVuIG1vcmUsIGJ1dCBsZXQncyBpZ25vcmUgaXQgZm9yIHRoZSBtb21lbnQpLiBJIHR1bmVkIGBtdHJ5YCB0byBiZSBiZXR3ZWVuIDQgYW5kIDYsIGFuZCBgdHJlZXNgIHRvIGJlIGJldHdlZW4gMTAwMCBhbmQgNTAwMCBpbiBhIDMwIHN0ZXAgaW5jcmVtZW50LiBJbiB0b3RhbCwgd2l0aCBhIDEwLWZvbGRzIGNyb3NzIHZhbGlkYXRpb24sIEkgZ3JldyAzMCByYW5kb20gZm9yZXN0cyBvbiBlYWNoIGZvbGQgZm9yIGEgdG90YWwgb2YgMzAwIFJhbmRvbSBGb3Jlc3RzIG9uIHRoZSB0cmFpbmluZyBzZXQhISEgVGhpcyBvZiBjb3Vyc2Ugd2lsbCB0YWtlIGEgbG9vb29vb25nIHRpbWUgdG8gY29tcHV0ZSBvbiB5b3VyIGNvbXB1dGVyIGlmIHVzaW5nIG9uZSB0aHJlYWQuIFNvIHVzZSBwYXJhbGxlbCBjb21wdXRpbmcgb3IgYSBjbHVzdGVyLiBXaGVuIHJ1bm5pbmcgaW4gdGhlIGNsdXN0ZXIgd2l0aCAyMCBjb3JlcywgZWFjaCB3aXRoIDExR0IgUkFNcywgYW5kIGl0IHRvb2sgYXJvdW5kIDI2MC40NDIgc2Vjb25kcyB0byBydW4gd2l0aCAyMjBHQiBSQU1TISBPZiBjb3Vyc2UsIHdpdGggc21hbGxlciBSQU1zIGFuZCBudW1iZXIgb2YgY29yZXMsIHRoZSBjb2RlIHdpbGwgc3RpbGwgcnVuIGJ1dHdpbGwgdGFrZSBsb25nZXIuIA0KDQoNCmBgYHtyfQ0KIyMgc2V0LnNlZWQoMTIzNDU2KQ0KIyNncmlkeV90aWR5bSA8LSBncmlkX3JhbmRvbSgNCiMjICBtdHJ5KCkgJT4lIHJhbmdlX3NldChjKDQsIDYpKSwNCiMjICB0cmVlcygpICU+JSByYW5nZV9zZXQoYygxMDAwLCA1MDAwKSksDQojIyAgc2l6ZSA9IDMwDQojIyAgKQ0KYGBgDQoNCiMjIyBXb3JrZmxvdw0KDQpOb3cgd2UgZGVmaW5lIHRoZSB3b3JrZmxvdyBhZGRpbmcgdGhlIGByZWNpcGVgIGFuZCB0aGUgYG1vZGVsYC4NCg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NTYpDQp3a2ZsX3RpZHltIDwtIHdvcmtmbG93KCkgJT4lIA0KICBhZGRfcmVjaXBlKHJlY2lwZSkgJT4lIA0KICBhZGRfbW9kZWwoZW5naW5lX3RpZHltKQ0KYGBgDQoNCiMjIyBUdW5pbmcgYW5kIHJ1bm5pbmcgbW9kZWwNCg0KSGVyZSB3ZSBydW4gdGhlIG1vZGVsIHN0YXJ0aW5nIHdpdGggdGhlIHdvcmtmbG93LCB0aGUgY3Jvc3MtdmFsaWRhdGlvbiBzYW1wbGUsIHRoZSB0dW5pbmcgcGFyYW1ldGVycyBhbmQgYXNraW5nIGZvciBzcGVjaWZpYyBtZXRyaWNzLiBCZWxvdywgd2UgcmVjZWl2ZSBvbmUgd2FybmluZzoNCg0KMS4gV2UgZGlkIG5vdCBkZWZpbmUgYW55IHR1bmluZywgYW5kIHJlY2VpdmUgYSB3YXJuaW5nIGFib3V0IGl0Lg0KDQpBYm92ZSwgd2UgYWRkZWQgYW4gb3B0aW9uIHRvIHN1cHByZXNzIGFueSB3YXJuaW5nIG1lc3NhZ2VzIGZyb20gYGRvRnV0dXJlYC4gSWYgeW91IGRvIG5vdCB1c2UgdGhhdCBvcHRpb24sIHlvdSBtYXkgcmVjZWl2ZSBhIHdhcm5pbmcgdGhhdCBzb21lIHRocmVhZHMgYXJlIG5vdCB1c2luZyBjb3JyZWN0IHJhbmRvbSBzZWVkcy4gVGhpcyBpcyBhIGZhbHNlLXBvc2l0aXZlIHdlbGwga25vd24gdG8gdGhlIGRldmVsb3BlcnMuIEZvciBtb3JlIGRldGFpbHMsIHNlZSBbdGhpcyBsaW5rXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2NvbnRyb2xfZ3JpZC5odG1sKSwgYnV0IGFsc28gdGhlaXIgZ2l0aHViIHBhZ2U7IHNvIHlvdSBjYW4gaWdub3JlIHRoZSB3YXJuaW5nLiANCg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NTYpDQpzeXN0ZW0udGltZShncmlkX3RpZHltIDwtIA0KICB0dW5lX2dyaWQod2tmbF90aWR5bSwgDQogICAgICAgICAgICByZXNhbXBsZXMgPSB0cmFpbl9jdiwNCiAgICAgICAgICAgICMjIGlmIHR1bmluZyBtdHJ5IGFuZCB0cmVlcywgdW5jb21tZW50IHRoZSBsaW5lIGJlbG93DQogICAgICAgICAgICAjZ3JpZCA9IGdyaWR5X3RpZHltLA0KICAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpY19zZXQoYWNjdXJhY3ksIHJvY19hdWMsIHNlbnMsIHNwZWMpLA0KICAgICAgICAgICAgY29udHJvbCA9IGNvbnRyb2xfZ3JpZChzYXZlX3ByZWQgPSBUUlVFLCBwYXJhbGxlbF9vdmVyID0gInJlc2FtcGxlcyIpKQ0KKQ0KcHJpbnQoZ3JpZF90aWR5bSkNCmBgYA0KDQoNCiMjIyBGaW5hbGlzZSBtb2RlbA0KDQpXZSBvYnRhaW4gdGhlIGJlc3QgcGVyZm9ybWluZyBtb2RlbCBmcm9tIGNyb3NzLXZhbGlkYXRpb24sIHRoZW4gZmluYWxpc2UgdGhlIHdvcmtmbG93IGJ5IHByZWRpY3RpbmcgdGhlIHJlc3VsdHMgb24gdGhlIHRlc3Rpbmcgc2V0IGFuZCBvYnRhaW4gdGhlIHJlc3VsdHMgb2YgdGhlIGJlc3QgcGVyZm9ybWluZyBtb2RlbA0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMzQ1NikNCmNvbGxlY3RfbWV0cmljcyhncmlkX3RpZHltKQ0KZ3JpZF90aWR5bV9iZXN0IDwtIHNlbGVjdF9iZXN0KGdyaWRfdGlkeW0sIG1ldHJpYyA9ICJyb2NfYXVjIikNCmdyaWRfdGlkeW1fYmVzdA0Kd2tmbF90aWR5bV9iZXN0IDwtIGZpbmFsaXplX3dvcmtmbG93KHdrZmxfdGlkeW0sIGdyaWRfdGlkeW1fYmVzdCkNCndrZmxfdGlkeW1fZmluYWwgPC0gbGFzdF9maXQod2tmbF90aWR5bV9iZXN0LCBzcGxpdCA9IHRyYWluX3Rlc3Rfc3BsaXQpDQpgYGANCg0KIyMgUmVzdWx0cw0KDQpGb3IgdGhlIHJlc3VsdHMsIHdlIGNhbiBvYnRhaW4gdmFyaW91cyBtZXRyaWNzIG9uIHRoZSB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzLiANCg0KIyMjIENyb3NzLXZhbGlkYXRpb24gb24gdHJhaW5pbmcgc2V0DQoNCiMjIyMgQWNjdXJhY3kNCg0KYGBge3J9DQpwZXJjZW50KHNob3dfYmVzdChncmlkX3RpZHltLCBtZXRyaWMgPSAiYWNjdXJhY3kiLCBuID0gMSkkbWVhbikNCmBgYA0KDQojIyMjIFJPQy1BVUMNCg0KYGBge3J9DQojIENyb3NzLXZhbGlkYXRlZCB0cmFpbmluZyBwZXJmb3JtYW5jZQ0Kc2hvd19iZXN0KGdyaWRfdGlkeW0sIG1ldHJpYyA9ICJyb2NfYXVjIiwgbiA9IDEpJG1lYW4NCmBgYA0KDQoNCiMjIyMgU2Vuc2l0aXZpdHkNCg0KYGBge3J9DQpzaG93X2Jlc3QoZ3JpZF90aWR5bSwgbWV0cmljID0gInNlbnMiLCBuID0gMSkkbWVhbg0KYGBgDQoNCiMjIyMgU3BlY2lmaWNpdHkNCg0KYGBge3J9DQpzaG93X2Jlc3QoZ3JpZF90aWR5bSwgbWV0cmljID0gInNwZWMiLCBuID0gMSkkbWVhbg0KYGBgDQoNCiMjIyBQcmVkaWN0aW9ucyB0ZXN0aW5nIHNldA0KDQojIyMjIE92ZXJhbGwNCg0KYGBge3J9DQp3a2ZsX3RpZHltX2ZpbmFsJC5tZXRyaWNzDQpgYGANCg0KIyMjIyBBY2N1cmFjeQ0KDQpgYGB7cn0NCiNhY2N1cmFjeQ0KcGVyY2VudCh3a2ZsX3RpZHltX2ZpbmFsJC5tZXRyaWNzW1sxXV0kLmVzdGltYXRlW1sxXV0pDQpgYGANCg0KIyMjIyBST0MtQVVDDQoNCg0KYGBge3J9DQojcm9jLWF1Yw0Kd2tmbF90aWR5bV9maW5hbCQubWV0cmljc1tbMV1dJC5lc3RpbWF0ZVtbMl1dDQpgYGANCg0KIyMjIENvbmZ1c2lvbiBNYXRyaXggdHJhaW5pbmcgc2V0DQoNCmBgYHtyIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gOH0NCndrZmxfdGlkeW1fZmluYWwkLnByZWRpY3Rpb25zW1sxXV0gJT4lDQogIGNvbmZfbWF0KGNvbnRleHQsIC5wcmVkX2NsYXNzKSAlPiUNCiAgcGx1Y2soMSkgJT4lDQogIGFzX3RpYmJsZSgpICU+JQ0KICBncm91cF9ieShUcnV0aCkgJT4lICMgZ3JvdXAgYnkgVHJ1dGggdG8gY29tcHV0ZSBwZXJjZW50YWdlcw0KICBtdXRhdGUocHJvcCA9cGVyY2VudChwcm9wLnRhYmxlKG4pKSkgJT4lICMgY2FsY3VsYXRlIHBlcmNlbnRhZ2VzIHJvdy13aXNlDQogIGdncGxvdChhZXMoUHJlZGljdGlvbiwgVHJ1dGgsIGFscGhhID0gcHJvcCkpICsNCiAgZ2VvbV90aWxlKHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHByb3ApLCBjb2xvdXIgPSAid2hpdGUiLCBhbHBoYSA9IDEsIHNpemUgPSA4KQ0KYGBgDQoNCg0KIyMjIFZhcmlhYmxlIEltcG9ydGFuY2UNCg0KIyMjIyBCZXN0IDEwDQoNCmBgYHtyfQ0KdmlwKHB1bGxfd29ya2Zsb3dfZml0KHdrZmxfdGlkeW1fZmluYWwkLndvcmtmbG93W1sxXV0pKQ0KYGBgDQoNCg0KIyMjIyBBbGwgcHJlZGljdG9ycw0KDQoNCmBgYHtyfQ0KdmlwKHB1bGxfd29ya2Zsb3dfZml0KHdrZmxfdGlkeW1fZmluYWwkLndvcmtmbG93W1sxXV0pLCBudW1fZmVhdHVyZXMgPSAyMykNCmBgYA0KDQoNCiMjIyBHYWlucyBjdXJ2ZXMNCg0KVGhpcyBpcyBhbiBpbnRlcmVzdGluZyBmZWF0dXJlcyB0aGF0IHNob3cgaG93IG11Y2ggaXMgZ2FpbmVkIHdoZW4gbG9va2luZyBhdCB2YXJpb3VzIHBvcnRpb25zIG9mIHRoZSBkYXRhLiBXZSBzZWUgYSBncmFkdWFsIGluY3JlYXNlIGluIHRoZSB2YWx1ZXMuIFdoZW4gNTAlIG9mIHRoZSBkYXRhIHdlcmUgdGVzdGVkLCBhcm91bmQgODMlIG9mIHRoZSByZXN1bHRzIHdpdGhpbiB0aGUgbm9uLWd1dHR1cmFsIGNsYXNzIHdlcmUgYWxyZWFkeSBpZGVudGlmaWVkLiBUaGUgbW9yZSB0ZXN0aW5nIHdhcyBwZXJmb3JtZWQsIHRoZSBtb3JlIGNvbmZpZGVuY2UgaW4gdGhlIHJlc3VsdHMgdGhlcmUgYXJlIGFuZCB0aGVuIHdoZW4gODQuOTYlIG9mIHRoZSBkYXRhIHdlcmUgdGVzdGVkLCAxMDAlIG9mIHRoZSBjYXNlcyB3ZXJlIGZvdW5kLiANCg0KYGBge3J9DQp3a2ZsX3RpZHltX2ZpbmFsJC5wcmVkaWN0aW9uc1tbMV1dICU+JQ0KICBnYWluX2N1cnZlKGNvbnRleHQsIGAucHJlZF9Ob24tR3V0dHVyYWxgKSAlPiUNCiAgYXV0b3Bsb3QoKSANCmBgYCAgICAgICANCg0KIyMjIFJPQyBDdXJ2ZXMNCg0KYGBge3J9DQp3a2ZsX3RpZHltX2ZpbmFsJC5wcmVkaWN0aW9uc1tbMV1dICU+JQ0KICByb2NfY3VydmUoY29udGV4dCwgYC5wcmVkX05vbi1HdXR0dXJhbGApICU+JQ0KICBhdXRvcGxvdCgpIA0KYGBgIA0KDQoNCg0KIyBzZXNzaW9uIGluZm8NCg0KYGBge3Igd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9DQpzZXNzaW9uSW5mbygpDQpgYGANCg0K