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.

2 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)

3 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:

  1. Validate the results on the current set
  2. 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)
  3. 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.

4 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.

4.1 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()
  .. )

4.2 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.

4.2.1 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

4.2.2 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

4.3 Accuracy and Signal Detection Theory

4.3.1 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.

4.3.2 Running stats

Let’s do some stats on this

Yes No Total
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

4.3.2.1 Accuracy rate

Accuracy is simply the sum of true positives and true negatives divided by the total.

[1] 0.9090909

4.3.2.2 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

4.3.2.3 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

4.3.2.4 False positive rate

Let us ask the following question: When the stimulus = no, how many times the response = yes?

[1] 0.1666667

4.3.2.5 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

4.3.2.6 Precision

Let us ask the following question: When the subject responds yes, how many times is (s)he correct?

[1] 0.9090909

4.3.2.7 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:

  1. 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
  2. beta or the bias criterion. Between 0-1: lower = increase in “yes” responses
  3. Aprime or estimate of discriminability. Between 0-1: 1 = good discrimination; 0 is at chance
  4. bppd or the “b prime prime d”. Between -1 and 1: 0 = no bias, negative = tendency to respond “yes”, positive = tendency to respond “no”)
  5. 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

4.3.3 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.

4.3.4 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.

4.4 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.

4.4.1 Load and summarise


-- Column specification ----------------------------------------------
cols(
  .default = col_double(),
  context = col_character()
)
i Use `spec()` for the full column specifications.

4.4.2 First predictive model

4.4.2.1 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

4.4.2.2 GLM as a classification tool

4.4.2.2.1 Model specification

We run a GLM with context as our outcome, and Z2-Z1 as our predictor. We want to evaluate whether the two classes can be separated when using the acoustic metric Z2-Z1. Context has two levels, and this will be considered as a binomial distribution.


Call:
glm(formula = context ~ Z2mnZ1, family = binomial, data = .)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.2879  -1.1358  -0.8703   1.1538   1.4998  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept)  0.50112    0.23036   2.175 0.029605 *  
Z2mnZ1      -0.12281    0.03621  -3.391 0.000696 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 552.89  on 401  degrees of freedom
Residual deviance: 541.01  on 400  degrees of freedom
AIC: 545.01

Number of Fisher Scoring iterations: 4
4.4.2.2.2 Plogis

The result above shows that when moving from the non-guttural (intercept), a unit increase (i.e., guttural) yields a statistically significant decrease in the logodds associated with Z2-Z1. We can evaluate this further from a classification point of view, using plogis.

[1] 0.622722
[1] 0.5934644

This shows that Z2-Z1 is able to explain the difference in the guttural class with an accuracy of 59%. Let’s continue with this model further.

4.4.2.2.3 Model predictions

As above, we obtain predictions from the model. Because we are using a numeric predictor, we need to assign a threshold for the predict function. The threshold can be thought of as telling the predict function to assign any predictions lower than 50% to one group, and any higher to another.

               
pred.glm.Z2mnZ1 Non-Guttural Guttural
   Non-Guttural          167       75
   Guttural               55      105
Setting levels: control = Non-Guttural, case = Guttural
Setting direction: controls < cases

Call:
roc.default(response = dfPharV2$context, predictor = as.numeric(pred.glm.Z2mnZ1))

Data: as.numeric(pred.glm.Z2mnZ1) in 222 controls (dfPharV2$context Non-Guttural) < 180 cases (dfPharV2$context Guttural).
Area under the curve: 0.6678

The model above was able to explain the difference between the two classes with an accuracy of 67.7%. It has a slightly low specificity (0.58) to detect gutturals, but a flighty high sensitivity (0.75) to reject the non-gutturals. Looking at the confusion matrix, we observe that both groups were relatively accurately identified, but we have relatively large errors (or confusions). The AUC is at 0.67, which is not too high.

Let’s continue with GLM to evaluate it further. We start by running a correlation test to evaluate issues with GLM.

4.4.3 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.

4.4.3.1 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 

4.4.3.2 Plots to visualise the data

As we see from the plot, Z3-Z2 is higher in the guttural, whereas A2*-A3* is lower.

4.4.3.3 GLM on correlated data

Let’s run a logistic regression as a classification tool to predict the context as a function of each predictor separately and then combined.


Call:
glm(formula = context ~ Z3mnZ2, family = binomial, data = .)

Deviance Residuals: 
   Min      1Q  Median      3Q     Max  
-1.405  -1.061  -0.872   1.180   1.559  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept) -0.99451    0.23061  -4.313 1.61e-05 ***
Z3mnZ2       0.17440    0.04553   3.831 0.000128 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 552.89  on 401  degrees of freedom
Residual deviance: 537.74  on 400  degrees of freedom
AIC: 541.74

Number of Fisher Scoring iterations: 4

Call:
glm(formula = context ~ A2mnA3, family = binomial, data = .)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.5858  -1.0416  -0.6598   1.0684   2.0105  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept) -0.98961    0.16632  -5.950 2.68e-09 ***
A2mnA3      -0.06973    0.01122  -6.215 5.12e-10 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 552.89  on 401  degrees of freedom
Residual deviance: 508.51  on 400  degrees of freedom
AIC: 512.51

Number of Fisher Scoring iterations: 4

Call:
glm(formula = context ~ Z3mnZ2 + A2mnA3, family = binomial, data = .)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.6782  -1.0231  -0.6124   1.0639   2.1936  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept) -0.10996    0.27830  -0.395 0.692754    
Z3mnZ2      -0.39266    0.10201  -3.849 0.000118 ***
A2mnA3      -0.14930    0.02418  -6.175 6.61e-10 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 552.89  on 401  degrees of freedom
Residual deviance: 492.50  on 399  degrees of freedom
AIC: 498.5

Number of Fisher Scoring iterations: 4

When looking at the three models above, it is clear that the logodd value for Z3-Z2 is positive, whereas it is negative for A2*-A3*. When adding the two predictors together, there is clear suppression: the coefficients for both predictors are now negative. The relatively high correlation between the predictors affected the coefficients and changed the direction of the slope; collinearity is harmful for any regression analysis.

In the following, we introduce decision trees followed by Random Forest as a way to deal with collinearity and to make sense of multivariate predictors.

5 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:

  1. Test global null hypothesis of independence between predictors and outcome.
  2. Select the predictor with the strongest association with the outcome measured based on a multiplicity adjusted p-values with Bonferroni correction
  3. Implement a binary split in the selected input variable.
  4. 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.

5.1 Individual trees

5.1.1 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.

5.2 Model 1

5.2.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.

5.2.2 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

5.3 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.

5.4 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.

5.4.1 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!

6 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:

  1. randomForest: The original implementation of Random Forests.
  2. party and partykit: using conditional inference trees as base learners
  3. 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.

6.1 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..

6.1.1 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:

  1. ntree = 100 = number of trees to grow. Default = 500.
  2. 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.

  1. replace = FALSE = Use subsampling with or without replacement. Default is FALSE, i.e., use subsets of the data without replacing these.
  2. fraction = 0.632 = Use 63.2% of the data in each split.

6.1.2 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 

6.1.3 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:

  1. Simple permutation tests (conditional = FALSE)
  2. 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.

6.1.4 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.

6.2 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.

6.2.1 Model specification

In the model below specification below, there are already a few options we are familiar with, with additional ones described below:

  1. num.tree = Number of trees to grow. We use the default value
  2. mtry = Number of predictors to use. Default = floor(sqrt(Variables)). For compatibility with party, we use round(sqrt(23))
  3. replace = FALSE = Use subsampling with or without replacement. Default replace = TRUE, i.e., is with replacement.
  4. sample.fraction = 0.632 = Use 63.2% of the data in each split. Default is full dataset, i.e., sample.fraction = 1
  5. importance = "permutation" = Compute variable importance scores via permutation tests
  6. 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.
  7. splitrule = "extratrees" = rule used for splitting trees.
  8. 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%.

6.2.2 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.

6.2.2.2 Model specification

We use the same model specification as above, except from using the training set and saving the forest (with write.forest = TRUE).

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%.

6.2.2.3 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.

6.2.2.4 Variable Importance Scores

6.2.2.4.1 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.

6.2.2.4.2 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

6.3 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.

7 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.

7.1 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>

7.2 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!!

7.3 Model Specification

Within the model specification, we need to specify multiple options:

  1. A recipe: This is the recipe and is related to any data processing one wants to apply on the data.
  2. An engine: We need to specify the engine to use. Here we want to run a Random Forest.
  3. A tuning: Here we can tune our engine
  4. A workflow: here we specify the various steps of the workflow

7.3.1 Recipe

When defining the recipe, you need to think of the type of “transformations” you will apply to your data.

  1. 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)
  2. 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).
  3. 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.

7.3.3 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.

7.3.4 Workflow

Now we define the workflow adding the recipe and the model.

7.3.5 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:

  1. 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 

7.3.6 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

7.4 Results

For the results, we can obtain various metrics on the training and testing sets.

7.4.2 Predictions testing set

7.4.2.1 Overall

[[1]]
NA

7.4.5 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.

8 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