Session 18. Generalized Linear Models III. Poisson regression. Negative binomial regression. Cross-validation in Regression problems.

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


What do we want to do today?

We will complete our journey into the world of Generalized Linear Models (GLMs) by discussing the application of the Poisson regression for count data and its generalization known as Negative bionomial regression which can help save the day when Poisson regression fails. We will then introduce cross-validation: a very important and extremely useful model selection procedure. In this Session we will discuss two forms of cross-validation (CV): the Leave-Out-One-CV (LOOCV) and the K-fold CV.

0. Setup

Grab the fHH1.csv dataset from the proback/BeyondMLR.

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

1. Poisson Regression

1.1 The Poisson Regression Model

As in all GLMs in general, the problem to be solved is how to apply the Linear Model in a situation where our data fail to satisfy its assumptions. The Poisson regression deals with the situation where our observations - the outcome variable - are counts per some unit of time or space. Remember the Poisson distribution, introduced earlier in our sessions, that is used to model the frequency of occurrences in a given time interval (or spatial area as well)? Its probability density is given by:

\[f(k; \lambda) = P(X = k) = \frac{\lambda^ke^{-\lambda}}{k!}\] where \(k\) is the number of occurrences, \(k=0,1,2,...\), \(e\) is the Euler number \(e=2.7182...\), and \(\lambda\) is the only distribution parameter which represents the Poisson distribution mean and variance at the same time, \(\lambda = E(x) = Var(x)\).

To introduce the crucial model assumption precisely before we derive the model, Poisson regression assumes the response variable \(Y\) has a Poisson distribution, and assumes the logarithm of its expected value can be modeled by a linear combination of (a) predictors and (b) coefficients (that need to be estimated, of course). More precisely, the Poisson regression models \(\lambda_i\), the average number of occurrences of a phenomenon, as a function of one or more predictors. For example, we could consider the average number of car accidents in some state S in year Y as a function of a specific set of regulations that were in force in that state in that year. Let’s take a closer look at the model:

\[log(\lambda_i) = \beta_0 + \beta_1x_i\]

or more generally

\[log(\lambda) = \beta_0 + \beta_1X\]

while assuming that where the observed values\(Y_i\) follow a Poisson distribution with \(\lambda = \lambda_i\) for a given \(x_i\). For each observation then we could have a different value of \(\lambda\) depending on a particular value of the predictor \(X\). The model is easily expanded to encompass more predictors:

\[log(\lambda) = \beta_0 + \beta_1X_1 + \beta_2X_2 + ... + \beta_nX_n\]

The model assumptions are:

  • Poisson outcome: the outcome is a count per unit of time or space and follows a Poisson distribution;
  • independence: all observations are independent of one another;
  • the mean is equal to the variance: simply, because by definition we know that the mean of a Poisson variable must be equal to its variance; and
  • Linearity: as in all linear models, of course, and specifically here the log of the mean rate \(log(\lambda)\) must be a linear combination of \(X\).

1.2 Poisson Regression in R

We will the Household Size in the Philippines case study from this excellent book on applied regression models in R: Beyond Multiple Linear Regression: Applied Generalized Linear Models and Multilevel Models in R, Paul Roback and Julie Legler.

fHH1 <- fread(paste0(getwd(), "/_data/fHH1.csv"))
str(fHH1)
Classes ‘data.table’ and 'data.frame':  1500 obs. of  6 variables:
 $ V1      : int  1 2 3 4 5 6 7 8 9 10 ...
 $ location: chr  "CentralLuzon" "MetroManila" "DavaoRegion" "Visayas" ...
 $ age     : int  65 75 54 49 74 59 54 41 50 59 ...
 $ total   : int  0 3 4 3 3 6 5 5 6 4 ...
 $ numLT5  : int  0 0 0 0 0 0 0 0 0 0 ...
 $ roof    : chr  "Predominantly Strong Material" "Predominantly Strong Material" "Predominantly Strong Material" "Predominantly Strong Material" ...
 - attr(*, ".internal.selfref")=<externalptr> 

What we would like to do is to be able to predict the total variable, which represents the number of people living in a household (other than the head of the household), from the following covariates:

  • location: where the house is located (regions in the Philippines, whose The Philippine Statistics Authority (PSA) spearheads from the Family Income and Expenditure Survey (FIES) are the source of this dataset);
  • age: the age of the head of household;
  • numLT5: the number of people in the household under 5 years of age;
  • roof: the type of roof in the household (either Predominantly Light/Salvaged Material, or Predominantly Strong Material: stronger material can sometimes be used as a proxy for greater wealth).

Let’s take a look at the probability distribution of the outcome variable:

ggplot(data = fHH1, 
       aes(x = total)) + 
  geom_bar(stat = 'count',
           colour = "black",
           fill = "aliceblue",
           alpha = .75,
           ) + 
  theme_bw() + 
  theme(panel.border = element_blank())

We have two integer and two categorical predictors. Let’s have a look at them, categorical predictors first:

ggplot(data = fHH1, 
       aes(x = location)) + 
  geom_bar(stat = 'count',
           colour = "black",
           fill = "indianred",
           alpha = .75,
           ) + 
  theme_bw() + 
  theme(panel.border = element_blank())

as.data.frame(table(fHH1$roof))

Now the numbers:

ggplot(data = fHH1, 
       aes(x = age)) + 
  geom_bar(stat = 'count',
           colour = "black",
           fill = "green",
           alpha = .75,
           ) + 
  theme_bw() + 
  theme(panel.border = element_blank())

ggplot(data = fHH1, 
       aes(x = numLT5)) + 
  geom_bar(stat = 'count',
           colour = "black",
           fill = "green",
           alpha = .75,
           ) + 
  theme_bw() + 
  theme(panel.border = element_blank())

We need to test the assumption that the outcome variable follows a Poisson distribution. While it is almost always very difficult to say what distribution does a variable follows in empirical problems, we can test the relationship between the outcome mean and variance: they should be equal, right?

meanVar <- fHH1 %>%
  dplyr::select(age, total) %>% 
  dplyr::mutate(ints = cut(age, breaks = 15)) %>% 
  dplyr::group_by(ints) %>% 
  dplyr::summarise(mean = mean(total),
                   var = var(total),
                   n = n())
ggplot(meanVar, 
       aes(x = mean, y = var)) + 
  geom_smooth(method = "lm", size = .25) + 
  geom_point(size = 2) +
  geom_point(size = 1.5, color = "white") +
  theme_bw() + 
  theme(panel.border = element_blank())

What I really did is to cut() - a handy {dplyr} function indeed! - the age variable in 15 intervals and plot mean against the variance of the values of the outcome in the age intervals that I have obtained.

Another trick that we can pool is to perform a non-parametric bootstrap and see if the obtained means and variances are correlated:

outcome <- data.frame(total = fHH1$total)
meanVar <- lapply(1:1000, function(x) {
  s <- sample_n(outcome,
                size = dim(outcome)[1],
                replace = TRUE)
  return(
    data.frame(mean = mean(s$total), 
               var = var(s$total))
  )
})
meanVar <- rbindlist(meanVar)
ggplot(meanVar, 
       aes(x = mean, y = var)) + 
  geom_smooth(method = "lm", size = .25) + 
  geom_point(size = 2) +
  geom_point(size = 1.5, color = "white") +
  theme_bw() + 
  theme(panel.border = element_blank())

What this exercise shows is that the mean and the variance of total seem to be correlated to a degree, but carefully observe how variances exceed the respective bootstrap sample means.

Now, the Poisson regression model:

poiss_model = glm(total ~ age,
                 family = "poisson",
                 data = fHH1)
summary(poiss_model)

Call:
glm(formula = total ~ age, family = "poisson", data = fHH1)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.9079  -0.9637  -0.2155   0.6092   4.9561  

Coefficients:
              Estimate Std. Error z value Pr(>|z|)    
(Intercept)  1.5499422  0.0502754  30.829  < 2e-16 ***
age         -0.0047059  0.0009363  -5.026 5.01e-07 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for poisson family taken to be 1)

    Null deviance: 2362.5  on 1499  degrees of freedom
Residual deviance: 2337.1  on 1498  degrees of freedom
AIC: 6714

Number of Fisher Scoring iterations: 5

The obtain the model coefficients (with Exponentiation, of course - remember the \(log(\lambda_i)\) transform of the outcome in the model):

exp(coef(poiss_model))
(Intercept)         age 
  4.7111980   0.9953052 

The model log-likelihood and the Akaike Information Criterion (AIC) are:

logLik(poiss_model)
'log Lik.' -3354.984 (df=2)
poiss_model$aic
[1] 6713.968

And the confidence intervals for model coefficients are again obtained from confint():

confint(poiss_model)
                   2.5 %       97.5 %
(Intercept)  1.451170100  1.648249185
age         -0.006543163 -0.002872717

Now the full model:

poiss_model_full = glm(total ~ location + age + numLT5 + roof,
                      family = "poisson",
                      data = fHH1)
summary(poiss_model_full)

Call:
glm(formula = total ~ location + age + numLT5 + roof, family = "poisson", 
    data = fHH1)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.5315  -0.7207  -0.0900   0.6103   3.8415  

Coefficients:
                                    Estimate Std. Error z value Pr(>|z|)    
(Intercept)                        1.0613397  0.0750613  14.140   <2e-16 ***
locationDavaoRegion               -0.0459257  0.0538820  -0.852    0.394    
locationIlocosRegion               0.0090312  0.0527603   0.171    0.864    
locationMetroManila                0.0381394  0.0472689   0.807    0.420    
locationVisayas                    0.0486360  0.0422648   1.151    0.250    
age                               -0.0002257  0.0009587  -0.235    0.814    
numLT5                             0.3643417  0.0165647  21.995   <2e-16 ***
roofPredominantly Strong Material  0.0597142  0.0437171   1.366    0.172    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for poisson family taken to be 1)

    Null deviance: 2362.5  on 1499  degrees of freedom
Residual deviance: 1897.9  on 1492  degrees of freedom
AIC: 6286.8

Number of Fisher Scoring iterations: 5

And let’s take a look at the full model coefficients:

exp(coef(poiss_model_full))
                      (Intercept)               locationDavaoRegion              locationIlocosRegion 
                        2.8902405                         0.9551130                         1.0090721 
              locationMetroManila                   locationVisayas                               age 
                        1.0388760                         1.0498382                         0.9997744 
                           numLT5 roofPredominantly Strong Material 
                        1.4395661                         1.0615331 

Compare the AICs of the model encompassing age only and the full model:

poiss_model_full$aic
[1] 6286.755
poiss_model$aic
[1] 6713.968

And we can see that the full model performs somewhat better, as (maybe) expected.

2. Overdispersion and the Negative Binomial Regression

Remember how we have discovered that variances in total - the outcome for the model in the fHH1 dataset - seems to be larger than its variance?

Overdispersion describes the observation that variation is higher than would be expected. Some distributions do not have a parameter to fit variability of the observation. For example, the normal distribution does that through the parameter \(\sigma\) (i.e. the standard deviation of the model), which is constant in a typical regression. In contrast, the Poisson distribution has no such parameter, and in fact the variance increases with the mean (i.e. the variance and the mean have the same value). In this latter case, for an expected value of \(E(y)= 5\), we also expect that the variance of observed data points is \(5\). But what if it is not? What if the observed variance is much higher, i.e. if the data are overdispersed? Source: Introduction: what is overdispersion? From: Advice for Problems in Environmental Statistics (APES) of the Department of Biometry and Environmental System Analysis at the University of Freiburg, and the Professorship for Theoretical Ecology at the University of Regensburg

One way to test whether overdispersion is present is to compute the dispersion parameter. It is obtained by dividing the model deviance with the respective number of degrees of freedom (which is \(n-p\) in this case, \(n\) being the number of observations and \(p\) the number of parameters). If no overdispersion is present the value of the dispersion parameter should be close to 1. Let’s see:

deviance <- poiss_model_full$deviance
n <- dim(fHH1)[1]
p <- length(poiss_model_full$coefficients) 
df = n - p
dispersionParameter <- deviance/df
print(dispersionParameter)
[1] 1.272035

Ok, and what do we do then? We need a model less constrained than the Poisson regression!

For example, Poisson regression analysis is commonly used to model count data. If overdispersion is a feature, an alternative model with additional free parameters may provide a better fit. In the case of count data, a Poisson mixture model like the negative binomial distribution can be proposed instead, in which the mean of the Poisson distribution can itself be thought of as a random variable drawn – in this case – from the gamma distribution thereby introducing an additional free parameter (note the resulting negative binomial distribution is completely characterized by two parameters). Source: Overdispersion, From Wikipedia, the free encyclopedia

In **Negative Binomial Regression* we generate a value of \(\lambda\) for each observation from a Gamma distribution and then generate a count using a Poisson distribution with the generated value of \(\lambda\). Mathematically, with this process we arrive at the Gamma-Poisson mixture which is described by a Negative Binomial Distribution which has two parameters (one more than the simpler Poisson). Because we introduce that one more parameter the situation is way more flexible and the counts can be more dispersed than it would be expected for observations based on a Poisson with rate \(\lambda\) alone.

library(MASS)
nb_model <- glm.nb(total ~ location + age + numLT5 + roof,
                   data = fHH1)
summary(nb_model)

Call:
glm.nb(formula = total ~ location + age + numLT5 + roof, data = fHH1, 
    init.theta = 24.74987389, link = log)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.4636  -0.6913  -0.0850   0.5678   3.3832  

Coefficients:
                                   Estimate Std. Error z value Pr(>|z|)    
(Intercept)                        1.080240   0.080844  13.362   <2e-16 ***
locationDavaoRegion               -0.049495   0.057712  -0.858    0.391    
locationIlocosRegion               0.008724   0.056592   0.154    0.877    
locationMetroManila                0.039579   0.050658   0.781    0.435    
locationVisayas                    0.046035   0.045308   1.016    0.310    
age                               -0.000590   0.001033  -0.571    0.568    
numLT5                             0.367798   0.018586  19.789   <2e-16 ***
roofPredominantly Strong Material  0.059352   0.047098   1.260    0.208    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for Negative Binomial(24.7499) family taken to be 1)

    Null deviance: 2076.9  on 1499  degrees of freedom
Residual deviance: 1678.0  on 1492  degrees of freedom
AIC: 6273.4

Number of Fisher Scoring iterations: 1

              Theta:  24.75 
          Std. Err.:  7.04 

 2 x log-likelihood:  -6255.41 

N.B. Lower value of \(\theta\) implies larger overdispersion.

3. Cross-Validation

When we decide to derive a model of some phenomenon in mathematical statistics what we typically do is to draw a sample of relevant observations and predictors and the train the model on the sampled data. As we have seen in the course of our previous sessions, the question of generalization is of crucial importance. We are not looking for models with significant coefficients and high goodnes-of-fit (GoF) measures (such as \(R^2\), for example) or low badness-of-fit measures (such as \(AIC\)), but for models that will guarantee to generalize beyond the data that we have used to train them. Thus, we are very much interested in the standard errors of the model coefficients: they let us know how widely would they vary if we were to sample the data over and over again from the respective population.

There is one serious problem to face here. Since we have one sample of data at our disposal, we can easily imagine how all following samples would vary in respect to it. Since all samples come from the same population we can safely assume that some order, some structure, some invariant information will be present in all of them. However, we also know that each sample will be idiosyncratic up to some level. And we manage to fit a great model to the sample of data only we will be probably start to explain that idiosyncratic aspect of the sample too: a part of information contained in the data that is a consequence of the sampling procedure and not in any way a general pattern present in the population and thus probably not present in each or a majority of all other samples that we could have drawn from it. This problem is called overfitting and needs to be handled very carefully in Data Science.

Cross-validation (CV) is one method to deal with the possibility of overfitting. In all forms of CV the idea is basically the same: train (i.e. estimate) the model on a subset (or subsets) of data and test the model on the remaining “unseen” part (or parts) of it. We will introduce two basic CV approaches here: the Leave-Out-One-CV and the K-fold CV.

3.1 LOOCV

I will use iris to demonstrate the LOOCV procedure. We want to compare two models of Sepal.Width:

  • first we predict the outcome from Sepal.Length and Petal.Length only, and then
  • we predict the outcome from Sepal.Length, Petal.Length, and Petal.Width.

The LOOCV procedure is used when the dataset is really small - which iris definitely is.

Here is the LOOCV approach:

  • remove one single observation from the dataset;
  • estimate the model from the remaining data;
  • predict the observation that was left out;
  • compute the squared prediction error (data point - prediction)^2);
  • compute the mean squared prediction error (MSPE) following the removal/fit/predict step for each data point.

The selected model would be the one with the lower mean prediction error, of course. R:

head(iris)

Compute the MSPE for the simpler model first:

dataSet <- dplyr::select(iris, 
                         Sepal.Length, Petal.Length, Sepal.Width)
mspe1 <- sapply(1:dim(dataSet)[1], function(x) {
  fitData <- dataSet[-x, ]
  leftOut <- dataSet[x, ]
  model <- lm(Sepal.Width ~ Sepal.Length + Petal.Length, 
              data = fitData)
  prediction <- predict(model, 
                        newdata = leftOut[, c('Sepal.Length', 'Petal.Length')])
  spe <- (leftOut$Sepal.Width - prediction)^2
})
print(mean(mspe1))
[1] 0.1069126

And now compute the MSPE for the model encompassing the additional predictor:

dataSet <- dplyr::select(iris, 
                         Sepal.Length, Petal.Length, Petal.Width, Sepal.Width)
mspe2 <- sapply(1:dim(dataSet)[1], function(x) {
  fitData <- dataSet[-x, ]
  leftOut <- dataSet[x, ]
  model <- lm(Sepal.Width ~ Sepal.Length + Petal.Length + Petal.Width, 
              data = fitData)
  prediction <- predict(model, 
                        newdata = leftOut[, c('Sepal.Length', 'Petal.Length', 'Petal.Width')])
  spe <- (leftOut$Sepal.Width - prediction)^2
})
print(mean(mspe2))
[1] 0.09509893

3.2 K-Fold CV

In K-fold CV we randomly place all available observations in K folds (i.e. groups). If we decide to go for three groups, for example, we proceed in the following way:

  • randomly assign the observations into the three groups A, B, and C;
  • fit the model on A+B data and predict the outcome from the left out group C, then compute the MSPE;
  • fit the model on A+C data and predict the outcome from the left out group B, then compute the MSPE;
  • fit the model on C+B data and predict the outcome from the left out group A, then compute the MSPE;
  • compute the mean of the MSPEs from the previous steps; finally,
  • select the model with the lowest mean prediction error.

For the simpler Sepal.Width ~ Sepal.Length + Petal.Length model first:

iris$Species <- NULL
iris$fold <- sample(1:3, 150, replace = TRUE)
table(iris$fold)

 1  2  3 
59 39 52 

N.B. The folds should be of approximately the same size at least. I could have opted for 50/50/50 as well. However:

dataSet <- dplyr::select(iris, 
                         Sepal.Length, Petal.Length, Sepal.Width, fold)
mspe1 <- sapply(1:3, function(x) {
  fitData <- dataSet[dataSet$fold != x, ]
  leftOut <- dataSet[dataSet$fold == x, ]
  model <- lm(Sepal.Width ~ Sepal.Length + Petal.Length, 
              data = fitData)
  prediction <- predict(model, 
                        newdata = leftOut[, c('Sepal.Length', 'Petal.Length')])
  spe <- mean((leftOut$Sepal.Width - prediction)^2)
})
print(mspe1)
[1] 0.09788912 0.09628600 0.12544086

And the mean of MSPEs for the simpler model is:

print(mean(mspe1))
[1] 0.1065387

Now for the model encompassing Petal.Width too:

dataSet <- dplyr::select(iris, 
                         Sepal.Length, Petal.Length, Petal.Width, Sepal.Width, fold)
mspe2 <- sapply(1:3, function(x) {
  fitData <- dataSet[dataSet$fold != x, ]
  leftOut <- dataSet[dataSet$fold == x, ]
  model <- lm(Sepal.Width ~ Sepal.Length + Petal.Length + Petal.Width, 
              data = fitData)
  prediction <- predict(model, 
                        newdata = leftOut[, c('Sepal.Length', 'Petal.Length', 'Petal.Width')])
  spe <- mean((leftOut$Sepal.Width - prediction)^2)
})
print(mspe2)
[1] 0.08141191 0.09419004 0.10977537

And the mean MSPE from three folds is:

print(mean(mspe2))
[1] 0.09512577

N.B. Cross-validation procedures do not have to be based on MSPE. Other criteria like AIC or log-likelihood are used as well. For classification problems, the ROC analysis for each prediction can be used to select the best available model.


R Markdown


Goran S. Milovanović

DataKolektiv, 2020/21

contact:


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


LS0tDQp0aXRsZTogSW50cm8gdG8gRGF0YSBTY2llbmNlIChOb24tVGVjaG5pY2FsIEJhY2tncm91bmQsIFIpIC0gU2Vzc2lvbjE4DQphdXRob3I6DQotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhywgUGhEDQogIGFmZmlsaWF0aW9uOiBEYXRhS29sZWt0aXYsIENoaWVmIFNjaWVudGlzdCAmIE93bmVyOyBEYXRhIFNjaWVudGlzdCBmb3IgV2lraWRhdGEsIFdNREUNCmFic3RyYWN0OiANCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgdG9jX2RlcHRoOiA1DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDUNCi0tLQ0KDQohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpDQoNCioqKg0KIyBTZXNzaW9uIDE4LiBHZW5lcmFsaXplZCBMaW5lYXIgTW9kZWxzIElJSS4gUG9pc3NvbiByZWdyZXNzaW9uLiBOZWdhdGl2ZSBiaW5vbWlhbCByZWdyZXNzaW9uLiBDcm9zcy12YWxpZGF0aW9uIGluIFJlZ3Jlc3Npb24gcHJvYmxlbXMuDQoNCioqRmVlZGJhY2sqKiBzaG91bGQgYmUgc2VuZCB0byBgZ29yYW4ubWlsb3Zhbm92aWNAZGF0YWtvbGVrdGl2LmNvbWAuIA0KVGhlc2Ugbm90ZWJvb2tzIGFjY29tcGFueSB0aGUgSW50cm8gdG8gRGF0YSBTY2llbmNlOiBOb24tVGVjaG5pY2FsIEJhY2tncm91bmQgY291cnNlIDIwMjAvMjEuDQoNCioqKg0KDQojIyMgV2hhdCBkbyB3ZSB3YW50IHRvIGRvIHRvZGF5Pw0KDQpXZSB3aWxsIGNvbXBsZXRlIG91ciBqb3VybmV5IGludG8gdGhlIHdvcmxkIG9mIEdlbmVyYWxpemVkIExpbmVhciBNb2RlbHMgKEdMTXMpIGJ5IGRpc2N1c3NpbmcgdGhlIGFwcGxpY2F0aW9uIG9mIHRoZSAqUG9pc3NvbiByZWdyZXNzaW9uKiBmb3IgY291bnQgZGF0YSBhbmQgaXRzIGdlbmVyYWxpemF0aW9uIGtub3duIGFzICpOZWdhdGl2ZSBiaW9ub21pYWwgcmVncmVzc2lvbiogd2hpY2ggY2FuIGhlbHAgc2F2ZSB0aGUgZGF5IHdoZW4gUG9pc3NvbiByZWdyZXNzaW9uIGZhaWxzLiBXZSB3aWxsIHRoZW4gaW50cm9kdWNlICpjcm9zcy12YWxpZGF0aW9uKjogYSB2ZXJ5IGltcG9ydGFudCBhbmQgZXh0cmVtZWx5IHVzZWZ1bCBtb2RlbCBzZWxlY3Rpb24gcHJvY2VkdXJlLiBJbiB0aGlzIFNlc3Npb24gd2Ugd2lsbCBkaXNjdXNzIHR3byBmb3JtcyBvZiBjcm9zcy12YWxpZGF0aW9uIChDVik6IHRoZSAqTGVhdmUtT3V0LU9uZS1DViAoTE9PQ1YpKiBhbmQgdGhlICpLLWZvbGQgQ1YqLg0KDQojIyMgMC4gU2V0dXANCg0KR3JhYiB0aGUgYGZISDEuY3N2YCBkYXRhc2V0IGZyb20gdGhlIFtwcm9iYWNrL0JleW9uZE1MUl0oaHR0cHM6Ly9naXRodWIuY29tL3Byb2JhY2svQmV5b25kTUxSL3RyZWUvbWFzdGVyL2RhdGEpLg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KZGF0YURpciA8LSBwYXN0ZTAoZ2V0d2QoKSwgIi9fZGF0YS8iKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KGNhcikNCmxpYnJhcnkoZ2dyZXBlbCkNCmBgYA0KDQoNCiMjIyAxLiBQb2lzc29uIFJlZ3Jlc3Npb24NCg0KIyMjIyAxLjEgVGhlIFBvaXNzb24gUmVncmVzc2lvbiBNb2RlbA0KDQpBcyBpbiBhbGwgR0xNcyBpbiBnZW5lcmFsLCB0aGUgcHJvYmxlbSB0byBiZSBzb2x2ZWQgaXMgaG93IHRvIGFwcGx5IHRoZSBMaW5lYXIgTW9kZWwgaW4gYSBzaXR1YXRpb24gd2hlcmUgb3VyIGRhdGEgZmFpbCB0byBzYXRpc2Z5IGl0cyBhc3N1bXB0aW9ucy4gVGhlICpQb2lzc29uIHJlZ3Jlc3Npb24qIGRlYWxzIHdpdGggdGhlIHNpdHVhdGlvbiB3aGVyZSBvdXIgb2JzZXJ2YXRpb25zIC0gdGhlIG91dGNvbWUgdmFyaWFibGUgLSBhcmUgKmNvdW50cyBwZXIgc29tZSB1bml0IG9mIHRpbWUgb3Igc3BhY2UqLiBSZW1lbWJlciB0aGUgUG9pc3NvbiBkaXN0cmlidXRpb24sIGludHJvZHVjZWQgZWFybGllciBpbiBvdXIgc2Vzc2lvbnMsIHRoYXQgaXMgdXNlZCB0byBtb2RlbCB0aGUgZnJlcXVlbmN5IG9mIG9jY3VycmVuY2VzIGluIGEgZ2l2ZW4gdGltZSBpbnRlcnZhbCAob3Igc3BhdGlhbCBhcmVhIGFzIHdlbGwpPyBJdHMgcHJvYmFiaWxpdHkgZGVuc2l0eSBpcyBnaXZlbiBieToNCg0KJCRmKGs7IFxsYW1iZGEpID0gUChYID0gaykgPSBcZnJhY3tcbGFtYmRhXmtlXnstXGxhbWJkYX19e2shfSQkDQp3aGVyZSAkayQgaXMgdGhlIG51bWJlciBvZiBvY2N1cnJlbmNlcywgJGs9MCwxLDIsLi4uJCwgJGUkIGlzIHRoZSBFdWxlciBudW1iZXIgJGU9Mi43MTgyLi4uJCwgYW5kICRcbGFtYmRhJCBpcyB0aGUgb25seSBkaXN0cmlidXRpb24gcGFyYW1ldGVyIHdoaWNoIHJlcHJlc2VudHMgdGhlIFBvaXNzb24gZGlzdHJpYnV0aW9uICptZWFuIGFuZCB2YXJpYW5jZSogYXQgdGhlIHNhbWUgdGltZSwgJFxsYW1iZGEgPSBFKHgpID0gVmFyKHgpJC4NCg0KVG8gaW50cm9kdWNlIHRoZSBjcnVjaWFsIG1vZGVsIGFzc3VtcHRpb24gcHJlY2lzZWx5IGJlZm9yZSB3ZSBkZXJpdmUgdGhlIG1vZGVsLCBQb2lzc29uIHJlZ3Jlc3Npb24gYXNzdW1lcyB0aGUgcmVzcG9uc2UgdmFyaWFibGUgJFkkIGhhcyBhIFBvaXNzb24gZGlzdHJpYnV0aW9uLCBhbmQgYXNzdW1lcyB0aGUgKmxvZ2FyaXRobSBvZiBpdHMgZXhwZWN0ZWQgdmFsdWUqIGNhbiBiZSBtb2RlbGVkIGJ5IGEgbGluZWFyIGNvbWJpbmF0aW9uIG9mIChhKSBwcmVkaWN0b3JzIGFuZCAoYikgY29lZmZpY2llbnRzICh0aGF0IG5lZWQgdG8gYmUgZXN0aW1hdGVkLCBvZiBjb3Vyc2UpLiBNb3JlIHByZWNpc2VseSwgdGhlIFBvaXNzb24gcmVncmVzc2lvbiBtb2RlbHMgJFxsYW1iZGFfaSQsIHRoZSBhdmVyYWdlIG51bWJlciBvZiBvY2N1cnJlbmNlcyBvZiBhIHBoZW5vbWVub24sIGFzIGEgZnVuY3Rpb24gb2Ygb25lIG9yIG1vcmUgcHJlZGljdG9ycy4gRm9yIGV4YW1wbGUsIHdlIGNvdWxkIGNvbnNpZGVyIHRoZSBhdmVyYWdlIG51bWJlciBvZiBjYXIgYWNjaWRlbnRzIGluIHNvbWUgc3RhdGUgKipTKiogaW4geWVhciAqKlkqKiBhcyBhIGZ1bmN0aW9uIG9mIGEgc3BlY2lmaWMgc2V0IG9mIHJlZ3VsYXRpb25zIHRoYXQgd2VyZSBpbiBmb3JjZSBpbiB0aGF0IHN0YXRlIGluIHRoYXQgeWVhci4gTGV0J3MgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBtb2RlbDoNCg0KJCRsb2coXGxhbWJkYV9pKSA9IFxiZXRhXzAgKyBcYmV0YV8xeF9pJCQNCg0Kb3IgbW9yZSBnZW5lcmFsbHkNCg0KJCRsb2coXGxhbWJkYSkgPSBcYmV0YV8wICsgXGJldGFfMVgkJA0KDQp3aGlsZSBhc3N1bWluZyB0aGF0IHdoZXJlIHRoZSBvYnNlcnZlZCB2YWx1ZXMkWV9pJCBmb2xsb3cgYSBQb2lzc29uIGRpc3RyaWJ1dGlvbiB3aXRoICRcbGFtYmRhID0gXGxhbWJkYV9pJCBmb3IgYSBnaXZlbiAkeF9pJC4gRm9yIGVhY2ggb2JzZXJ2YXRpb24gdGhlbiB3ZSBjb3VsZCBoYXZlIGEgZGlmZmVyZW50IHZhbHVlIG9mICRcbGFtYmRhJCBkZXBlbmRpbmcgb24gYSBwYXJ0aWN1bGFyIHZhbHVlIG9mIHRoZSBwcmVkaWN0b3IgJFgkLiBUaGUgbW9kZWwgaXMgZWFzaWx5IGV4cGFuZGVkIHRvIGVuY29tcGFzcyBtb3JlIHByZWRpY3RvcnM6DQoNCiQkbG9nKFxsYW1iZGEpID0gXGJldGFfMCArIFxiZXRhXzFYXzEgKyBcYmV0YV8yWF8yICsgLi4uICsgXGJldGFfblhfbiQkDQoNClRoZSBtb2RlbCBhc3N1bXB0aW9ucyBhcmU6DQoNCi0gKipQb2lzc29uIG91dGNvbWU6KiogdGhlIG91dGNvbWUgaXMgYSBjb3VudCBwZXIgdW5pdCBvZiB0aW1lIG9yIHNwYWNlIGFuZCBmb2xsb3dzIGEgUG9pc3NvbiBkaXN0cmlidXRpb247DQotICoqaW5kZXBlbmRlbmNlOioqIGFsbCBvYnNlcnZhdGlvbnMgYXJlIGluZGVwZW5kZW50IG9mIG9uZSBhbm90aGVyOyANCi0gKip0aGUgbWVhbiBpcyBlcXVhbCB0byB0aGUgdmFyaWFuY2U6Kiogc2ltcGx5LCBiZWNhdXNlIGJ5IGRlZmluaXRpb24gd2Uga25vdyB0aGF0IHRoZSBtZWFuIG9mIGEgUG9pc3NvbiB2YXJpYWJsZSBtdXN0IGJlIGVxdWFsIHRvIGl0cyB2YXJpYW5jZTsgYW5kDQotICoqTGluZWFyaXR5OioqIGFzIGluIGFsbCBsaW5lYXIgbW9kZWxzLCBvZiBjb3Vyc2UsIGFuZCBzcGVjaWZpY2FsbHkgaGVyZSB0aGUgbG9nIG9mIHRoZSBtZWFuIHJhdGUgJGxvZyhcbGFtYmRhKSQgbXVzdCBiZSBhIGxpbmVhciBjb21iaW5hdGlvbiBvZiAkWCQuDQoNCg0KIyMjIyAxLjIgUG9pc3NvbiBSZWdyZXNzaW9uIGluIFINCg0KV2Ugd2lsbCB0aGUgW0hvdXNlaG9sZCBTaXplIGluIHRoZSBQaGlsaXBwaW5lc10oaHR0cHM6Ly9ib29rZG93bi5vcmcvcm9iYWNrL2Jvb2tkb3duLUJleW9uZE1MUi9jaC1wb2lzc29ucmVnLmh0bWwpIGNhc2Ugc3R1ZHkgZnJvbSB0aGlzIGV4Y2VsbGVudCBib29rIG9uIGFwcGxpZWQgcmVncmVzc2lvbiBtb2RlbHMgaW4gUjogW0JleW9uZCBNdWx0aXBsZSBMaW5lYXIgUmVncmVzc2lvbjogQXBwbGllZCBHZW5lcmFsaXplZCBMaW5lYXIgTW9kZWxzIGFuZCBNdWx0aWxldmVsIE1vZGVscyBpbiBSLCBQYXVsIFJvYmFjayBhbmQgSnVsaWUgTGVnbGVyXShodHRwczovL2Jvb2tkb3duLm9yZy9yb2JhY2svYm9va2Rvd24tQmV5b25kTUxSLykuDQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gVCwgd2FybmluZyA9IEZ9DQpmSEgxIDwtIGZyZWFkKHBhc3RlMChnZXR3ZCgpLCAiL19kYXRhL2ZISDEuY3N2IikpDQpzdHIoZkhIMSkNCmBgYA0KDQpXaGF0IHdlIHdvdWxkIGxpa2UgdG8gZG8gaXMgdG8gYmUgYWJsZSB0byBwcmVkaWN0IHRoZSBgdG90YWxgIHZhcmlhYmxlLCB3aGljaCByZXByZXNlbnRzIHRoZSBudW1iZXIgb2YgcGVvcGxlIGxpdmluZyBpbiBhIGhvdXNlaG9sZCAob3RoZXIgdGhhbiB0aGUgaGVhZCBvZiB0aGUgaG91c2Vob2xkKSwgZnJvbSB0aGUgZm9sbG93aW5nIGNvdmFyaWF0ZXM6DQoNCi0gYGxvY2F0aW9uYDogd2hlcmUgdGhlIGhvdXNlIGlzIGxvY2F0ZWQgKHJlZ2lvbnMgaW4gdGhlIFBoaWxpcHBpbmVzLCB3aG9zZSBUaGUgUGhpbGlwcGluZSBTdGF0aXN0aWNzIEF1dGhvcml0eSAoUFNBKSBzcGVhcmhlYWRzIGZyb20gdGhlIEZhbWlseSBJbmNvbWUgYW5kIEV4cGVuZGl0dXJlIFN1cnZleSAoRklFUykgYXJlIHRoZSBzb3VyY2Ugb2YgdGhpcyBkYXRhc2V0KTsNCi0gYGFnZWA6IHRoZSBhZ2Ugb2YgdGhlIGhlYWQgb2YgaG91c2Vob2xkOw0KLSBgbnVtTFQ1YDogdGhlIG51bWJlciBvZiBwZW9wbGUgaW4gdGhlIGhvdXNlaG9sZCB1bmRlciA1IHllYXJzIG9mIGFnZTsNCi0gYHJvb2ZgOiB0aGUgdHlwZSBvZiByb29mIGluIHRoZSBob3VzZWhvbGQgKGVpdGhlciBQcmVkb21pbmFudGx5IExpZ2h0L1NhbHZhZ2VkIE1hdGVyaWFsLCBvciBQcmVkb21pbmFudGx5IFN0cm9uZyBNYXRlcmlhbDogc3Ryb25nZXIgbWF0ZXJpYWwgY2FuIHNvbWV0aW1lcyBiZSB1c2VkIGFzIGEgcHJveHkgZm9yIGdyZWF0ZXIgd2VhbHRoKS4NCg0KTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbiBvZiB0aGUgb3V0Y29tZSB2YXJpYWJsZToNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmdncGxvdChkYXRhID0gZkhIMSwgDQogICAgICAgYWVzKHggPSB0b3RhbCkpICsgDQogIGdlb21fYmFyKHN0YXQgPSAnY291bnQnLA0KICAgICAgICAgICBjb2xvdXIgPSAiYmxhY2siLA0KICAgICAgICAgICBmaWxsID0gImFsaWNlYmx1ZSIsDQogICAgICAgICAgIGFscGhhID0gLjc1LA0KICAgICAgICAgICApICsgDQogIHRoZW1lX2J3KCkgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQpXZSBoYXZlIHR3byBpbnRlZ2VyIGFuZCB0d28gY2F0ZWdvcmljYWwgcHJlZGljdG9ycy4gTGV0J3MgaGF2ZSBhIGxvb2sgYXQgdGhlbSwgY2F0ZWdvcmljYWwgcHJlZGljdG9ycyBmaXJzdDoNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmdncGxvdChkYXRhID0gZkhIMSwgDQogICAgICAgYWVzKHggPSBsb2NhdGlvbikpICsgDQogIGdlb21fYmFyKHN0YXQgPSAnY291bnQnLA0KICAgICAgICAgICBjb2xvdXIgPSAiYmxhY2siLA0KICAgICAgICAgICBmaWxsID0gImluZGlhbnJlZCIsDQogICAgICAgICAgIGFscGhhID0gLjc1LA0KICAgICAgICAgICApICsgDQogIHRoZW1lX2J3KCkgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KYXMuZGF0YS5mcmFtZSh0YWJsZShmSEgxJHJvb2YpKQ0KYGBgDQoNCk5vdyB0aGUgbnVtYmVyczoNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmdncGxvdChkYXRhID0gZkhIMSwgDQogICAgICAgYWVzKHggPSBhZ2UpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gJ2NvdW50JywNCiAgICAgICAgICAgY29sb3VyID0gImJsYWNrIiwNCiAgICAgICAgICAgZmlsbCA9ICJncmVlbiIsDQogICAgICAgICAgIGFscGhhID0gLjc1LA0KICAgICAgICAgICApICsgDQogIHRoZW1lX2J3KCkgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KZ2dwbG90KGRhdGEgPSBmSEgxLCANCiAgICAgICBhZXMoeCA9IG51bUxUNSkpICsgDQogIGdlb21fYmFyKHN0YXQgPSAnY291bnQnLA0KICAgICAgICAgICBjb2xvdXIgPSAiYmxhY2siLA0KICAgICAgICAgICBmaWxsID0gImdyZWVuIiwNCiAgICAgICAgICAgYWxwaGEgPSAuNzUsDQogICAgICAgICAgICkgKyANCiAgdGhlbWVfYncoKSArIA0KICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpDQpgYGANCldlIG5lZWQgdG8gdGVzdCB0aGUgYXNzdW1wdGlvbiB0aGF0IHRoZSBvdXRjb21lIHZhcmlhYmxlIGZvbGxvd3MgYSBQb2lzc29uIGRpc3RyaWJ1dGlvbi4gV2hpbGUgaXQgaXMgYWxtb3N0IGFsd2F5cyB2ZXJ5IGRpZmZpY3VsdCB0byBzYXkgd2hhdCBkaXN0cmlidXRpb24gZG9lcyBhIHZhcmlhYmxlIGZvbGxvd3MgaW4gZW1waXJpY2FsIHByb2JsZW1zLCB3ZSBjYW4gdGVzdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIG91dGNvbWUgbWVhbiBhbmQgdmFyaWFuY2U6IHRoZXkgc2hvdWxkIGJlIGVxdWFsLCByaWdodD8NCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCm1lYW5WYXIgPC0gZkhIMSAlPiUNCiAgZHBseXI6OnNlbGVjdChhZ2UsIHRvdGFsKSAlPiUgDQogIGRwbHlyOjptdXRhdGUoaW50cyA9IGN1dChhZ2UsIGJyZWFrcyA9IDE1KSkgJT4lIA0KICBkcGx5cjo6Z3JvdXBfYnkoaW50cykgJT4lIA0KICBkcGx5cjo6c3VtbWFyaXNlKG1lYW4gPSBtZWFuKHRvdGFsKSwNCiAgICAgICAgICAgICAgICAgICB2YXIgPSB2YXIodG90YWwpLA0KICAgICAgICAgICAgICAgICAgIG4gPSBuKCkpDQpnZ3Bsb3QobWVhblZhciwgDQogICAgICAgYWVzKHggPSBtZWFuLCB5ID0gdmFyKSkgKyANCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2l6ZSA9IC4yNSkgKyANCiAgZ2VvbV9wb2ludChzaXplID0gMikgKw0KICBnZW9tX3BvaW50KHNpemUgPSAxLjUsIGNvbG9yID0gIndoaXRlIikgKw0KICB0aGVtZV9idygpICsgDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KV2hhdCBJIHJlYWxseSBkaWQgaXMgdG8gYGN1dCgpYCAtIGEgaGFuZHkge2RwbHlyfSBmdW5jdGlvbiBpbmRlZWQhIC0gdGhlIGBhZ2VgIHZhcmlhYmxlIGluIDE1IGludGVydmFscyBhbmQgcGxvdCBtZWFuIGFnYWluc3QgdGhlIHZhcmlhbmNlIG9mIHRoZSB2YWx1ZXMgb2YgdGhlIG91dGNvbWUgaW4gdGhlIGFnZSBpbnRlcnZhbHMgdGhhdCBJIGhhdmUgb2J0YWluZWQuDQoNCkFub3RoZXIgdHJpY2sgdGhhdCB3ZSBjYW4gcG9vbCBpcyB0byBwZXJmb3JtIGEgKipub24tcGFyYW1ldHJpYyBib290c3RyYXAqKiBhbmQgc2VlIGlmIHRoZSBvYnRhaW5lZCBtZWFucyBhbmQgdmFyaWFuY2VzIGFyZSBjb3JyZWxhdGVkOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0Kb3V0Y29tZSA8LSBkYXRhLmZyYW1lKHRvdGFsID0gZkhIMSR0b3RhbCkNCm1lYW5WYXIgPC0gbGFwcGx5KDE6MTAwMCwgZnVuY3Rpb24oeCkgew0KICBzIDwtIHNhbXBsZV9uKG91dGNvbWUsDQogICAgICAgICAgICAgICAgc2l6ZSA9IGRpbShvdXRjb21lKVsxXSwNCiAgICAgICAgICAgICAgICByZXBsYWNlID0gVFJVRSkNCiAgcmV0dXJuKA0KICAgIGRhdGEuZnJhbWUobWVhbiA9IG1lYW4ocyR0b3RhbCksIA0KICAgICAgICAgICAgICAgdmFyID0gdmFyKHMkdG90YWwpKQ0KICApDQp9KQ0KbWVhblZhciA8LSByYmluZGxpc3QobWVhblZhcikNCmdncGxvdChtZWFuVmFyLCANCiAgICAgICBhZXMoeCA9IG1lYW4sIHkgPSB2YXIpKSArIA0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzaXplID0gLjI1KSArIA0KICBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNSwgY29sb3IgPSAid2hpdGUiKSArDQogIHRoZW1lX2J3KCkgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQpXaGF0IHRoaXMgZXhlcmNpc2Ugc2hvd3MgaXMgdGhhdCB0aGUgbWVhbiBhbmQgdGhlIHZhcmlhbmNlIG9mIGB0b3RhbGAgc2VlbSB0byBiZSBjb3JyZWxhdGVkIHRvIGEgZGVncmVlLCBidXQgY2FyZWZ1bGx5IG9ic2VydmUgaG93IHZhcmlhbmNlcyBleGNlZWQgdGhlIHJlc3BlY3RpdmUgYm9vdHN0cmFwIHNhbXBsZSBtZWFucy4NCg0KTm93LCB0aGUgUG9pc3NvbiByZWdyZXNzaW9uIG1vZGVsOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KcG9pc3NfbW9kZWwgPSBnbG0odG90YWwgfiBhZ2UsDQogICAgICAgICAgICAgICAgIGZhbWlseSA9ICJwb2lzc29uIiwNCiAgICAgICAgICAgICAgICAgZGF0YSA9IGZISDEpDQpzdW1tYXJ5KHBvaXNzX21vZGVsKQ0KYGBgDQoNClRoZSBvYnRhaW4gdGhlIG1vZGVsIGNvZWZmaWNpZW50cyAod2l0aCBFeHBvbmVudGlhdGlvbiwgb2YgY291cnNlIC0gcmVtZW1iZXIgdGhlICRsb2coXGxhbWJkYV9pKSQgdHJhbnNmb3JtIG9mIHRoZSBvdXRjb21lIGluIHRoZSBtb2RlbCk6IA0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KZXhwKGNvZWYocG9pc3NfbW9kZWwpKQ0KYGBgDQpUaGUgbW9kZWwgbG9nLWxpa2VsaWhvb2QgYW5kIHRoZSBBa2Fpa2UgSW5mb3JtYXRpb24gQ3JpdGVyaW9uIChBSUMpIGFyZToNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmxvZ0xpayhwb2lzc19tb2RlbCkNCmBgYA0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KcG9pc3NfbW9kZWwkYWljDQpgYGANCkFuZCB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbHMgZm9yIG1vZGVsIGNvZWZmaWNpZW50cyBhcmUgYWdhaW4gb2J0YWluZWQgZnJvbSBgY29uZmludCgpYDoNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmNvbmZpbnQocG9pc3NfbW9kZWwpDQpgYGANCg0KTm93IHRoZSBmdWxsIG1vZGVsOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KcG9pc3NfbW9kZWxfZnVsbCA9IGdsbSh0b3RhbCB+IGxvY2F0aW9uICsgYWdlICsgbnVtTFQ1ICsgcm9vZiwNCiAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSAicG9pc3NvbiIsDQogICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGZISDEpDQpzdW1tYXJ5KHBvaXNzX21vZGVsX2Z1bGwpDQpgYGANCg0KQW5kIGxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBmdWxsIG1vZGVsIGNvZWZmaWNpZW50czoNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmV4cChjb2VmKHBvaXNzX21vZGVsX2Z1bGwpKQ0KYGBgDQpDb21wYXJlIHRoZSBBSUNzIG9mIHRoZSBtb2RlbCBlbmNvbXBhc3NpbmcgYGFnZWAgb25seSBhbmQgdGhlIGZ1bGwgbW9kZWw6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpwb2lzc19tb2RlbF9mdWxsJGFpYw0KYGBgDQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpwb2lzc19tb2RlbCRhaWMNCmBgYA0KQW5kIHdlIGNhbiBzZWUgdGhhdCB0aGUgZnVsbCBtb2RlbCBwZXJmb3JtcyBzb21ld2hhdCBiZXR0ZXIsIGFzIChtYXliZSkgZXhwZWN0ZWQuDQoNCiMjIyAyLiBPdmVyZGlzcGVyc2lvbiBhbmQgdGhlIE5lZ2F0aXZlIEJpbm9taWFsIFJlZ3Jlc3Npb24NCg0KUmVtZW1iZXIgaG93IHdlIGhhdmUgZGlzY292ZXJlZCB0aGF0IHZhcmlhbmNlcyBpbiBgdG90YWxgIC0gdGhlIG91dGNvbWUgZm9yIHRoZSBtb2RlbCBpbiB0aGUgYGZISDFgIGRhdGFzZXQgLSBzZWVtcyB0byBiZSBsYXJnZXIgdGhhbiBpdHMgdmFyaWFuY2U/IA0KDQo+IE92ZXJkaXNwZXJzaW9uIGRlc2NyaWJlcyB0aGUgb2JzZXJ2YXRpb24gdGhhdCB2YXJpYXRpb24gaXMgaGlnaGVyIHRoYW4gd291bGQgYmUgZXhwZWN0ZWQuIFNvbWUgZGlzdHJpYnV0aW9ucyBkbyBub3QgaGF2ZSBhIHBhcmFtZXRlciB0byBmaXQgdmFyaWFiaWxpdHkgb2YgdGhlIG9ic2VydmF0aW9uLiBGb3IgZXhhbXBsZSwgdGhlIG5vcm1hbCBkaXN0cmlidXRpb24gZG9lcyB0aGF0IHRocm91Z2ggdGhlIHBhcmFtZXRlciAkXHNpZ21hJCAoaS5lLiB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoZSBtb2RlbCksIHdoaWNoIGlzIGNvbnN0YW50IGluIGEgdHlwaWNhbCByZWdyZXNzaW9uLiBJbiBjb250cmFzdCwgdGhlIFBvaXNzb24gZGlzdHJpYnV0aW9uIGhhcyBubyBzdWNoIHBhcmFtZXRlciwgYW5kIGluIGZhY3QgdGhlIHZhcmlhbmNlIGluY3JlYXNlcyB3aXRoIHRoZSBtZWFuIChpLmUuIHRoZSB2YXJpYW5jZSBhbmQgdGhlIG1lYW4gaGF2ZSB0aGUgc2FtZSB2YWx1ZSkuIEluIHRoaXMgbGF0dGVyIGNhc2UsIGZvciBhbiBleHBlY3RlZCB2YWx1ZSBvZiAkRSh5KT0gNSQsIHdlIGFsc28gZXhwZWN0IHRoYXQgdGhlIHZhcmlhbmNlIG9mIG9ic2VydmVkIGRhdGEgcG9pbnRzIGlzICQ1JC4gQnV0IHdoYXQgaWYgaXQgaXMgbm90PyBXaGF0IGlmIHRoZSBvYnNlcnZlZCB2YXJpYW5jZSBpcyBtdWNoIGhpZ2hlciwgaS5lLiBpZiB0aGUgZGF0YSBhcmUgb3ZlcmRpc3BlcnNlZD8gU291cmNlOiBbSW50cm9kdWN0aW9uOiB3aGF0IGlzIG92ZXJkaXNwZXJzaW9uPyBGcm9tOiBBZHZpY2UgZm9yIFByb2JsZW1zIGluIEVudmlyb25tZW50YWwgU3RhdGlzdGljcyAoQVBFUykgb2YgdGhlIERlcGFydG1lbnQgb2YgQmlvbWV0cnkgYW5kIEVudmlyb25tZW50YWwgU3lzdGVtIEFuYWx5c2lzIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEZyZWlidXJnLCBhbmQgdGhlIFByb2Zlc3NvcnNoaXAgZm9yIFRoZW9yZXRpY2FsIEVjb2xvZ3kgYXQgdGhlIFVuaXZlcnNpdHkgb2YgUmVnZW5zYnVyZ10oaHR0cDovL2Jpb21ldHJ5LmdpdGh1Yi5pby9BUEVTLy9MZWN0dXJlTm90ZXMvMjAxNi1KQUdTL092ZXJkaXNwZXJzaW9uL092ZXJkaXNwZXJzaW9uSkFHUy5odG1sKQ0KDQpPbmUgd2F5IHRvIHRlc3Qgd2hldGhlciBvdmVyZGlzcGVyc2lvbiBpcyBwcmVzZW50IGlzIHRvIGNvbXB1dGUgdGhlICoqZGlzcGVyc2lvbiBwYXJhbWV0ZXIqKi4gSXQgaXMgb2J0YWluZWQgYnkgZGl2aWRpbmcgdGhlIG1vZGVsIGRldmlhbmNlIHdpdGggdGhlIHJlc3BlY3RpdmUgbnVtYmVyIG9mIGRlZ3JlZXMgb2YgZnJlZWRvbSAod2hpY2ggaXMgJG4tcCQgaW4gdGhpcyBjYXNlLCAkbiQgYmVpbmcgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgYW5kICRwJCB0aGUgbnVtYmVyIG9mIHBhcmFtZXRlcnMpLiBJZiBubyBvdmVyZGlzcGVyc2lvbiBpcyBwcmVzZW50IHRoZSB2YWx1ZSBvZiB0aGUgZGlzcGVyc2lvbiBwYXJhbWV0ZXIgc2hvdWxkIGJlIGNsb3NlIHRvIDEuIExldCdzIHNlZToNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmRldmlhbmNlIDwtIHBvaXNzX21vZGVsX2Z1bGwkZGV2aWFuY2UNCm4gPC0gZGltKGZISDEpWzFdDQpwIDwtIGxlbmd0aChwb2lzc19tb2RlbF9mdWxsJGNvZWZmaWNpZW50cykgDQpkZiA9IG4gLSBwDQpkaXNwZXJzaW9uUGFyYW1ldGVyIDwtIGRldmlhbmNlL2RmDQpwcmludChkaXNwZXJzaW9uUGFyYW1ldGVyKQ0KYGBgDQpPaywgYW5kIHdoYXQgZG8gd2UgZG8gdGhlbj8gV2UgbmVlZCBhIG1vZGVsIGxlc3MgY29uc3RyYWluZWQgdGhhbiB0aGUgUG9pc3NvbiByZWdyZXNzaW9uIQ0KDQo+IEZvciBleGFtcGxlLCBQb2lzc29uIHJlZ3Jlc3Npb24gYW5hbHlzaXMgaXMgY29tbW9ubHkgdXNlZCB0byBtb2RlbCBjb3VudCBkYXRhLiBJZiBvdmVyZGlzcGVyc2lvbiBpcyBhIGZlYXR1cmUsIGFuIGFsdGVybmF0aXZlIG1vZGVsIHdpdGggYWRkaXRpb25hbCBmcmVlIHBhcmFtZXRlcnMgbWF5IHByb3ZpZGUgYSBiZXR0ZXIgZml0LiBJbiB0aGUgY2FzZSBvZiBjb3VudCBkYXRhLCBhIFBvaXNzb24gbWl4dHVyZSBtb2RlbCBsaWtlIHRoZSBuZWdhdGl2ZSBiaW5vbWlhbCBkaXN0cmlidXRpb24gY2FuIGJlIHByb3Bvc2VkIGluc3RlYWQsIGluIHdoaWNoIHRoZSBtZWFuIG9mIHRoZSBQb2lzc29uIGRpc3RyaWJ1dGlvbiBjYW4gaXRzZWxmIGJlIHRob3VnaHQgb2YgYXMgYSByYW5kb20gdmFyaWFibGUgZHJhd24g4oCTIGluIHRoaXMgY2FzZSDigJMgZnJvbSB0aGUgZ2FtbWEgZGlzdHJpYnV0aW9uIHRoZXJlYnkgaW50cm9kdWNpbmcgYW4gYWRkaXRpb25hbCBmcmVlIHBhcmFtZXRlciAobm90ZSB0aGUgcmVzdWx0aW5nIG5lZ2F0aXZlIGJpbm9taWFsIGRpc3RyaWJ1dGlvbiBpcyBjb21wbGV0ZWx5IGNoYXJhY3Rlcml6ZWQgYnkgdHdvIHBhcmFtZXRlcnMpLiBTb3VyY2U6IFtPdmVyZGlzcGVyc2lvbiwgRnJvbSBXaWtpcGVkaWEsIHRoZSBmcmVlIGVuY3ljbG9wZWRpYQ0KXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9PdmVyZGlzcGVyc2lvbikNCg0KSW4gKipOZWdhdGl2ZSBCaW5vbWlhbCBSZWdyZXNzaW9uKiB3ZSBnZW5lcmF0ZSBhIHZhbHVlIG9mICRcbGFtYmRhJCBmb3IgZWFjaCBvYnNlcnZhdGlvbiBmcm9tIGEgW0dhbW1hIGRpc3RyaWJ1dGlvbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvR2FtbWFfZGlzdHJpYnV0aW9uKSBhbmQgdGhlbiBnZW5lcmF0ZSBhIGNvdW50IHVzaW5nIGEgUG9pc3NvbiBkaXN0cmlidXRpb24gd2l0aCB0aGUgZ2VuZXJhdGVkIHZhbHVlIG9mICRcbGFtYmRhJC4gTWF0aGVtYXRpY2FsbHksIHdpdGggdGhpcyBwcm9jZXNzIHdlIGFycml2ZSBhdCB0aGUgKkdhbW1hLVBvaXNzb24gbWl4dHVyZSogd2hpY2ggaXMgZGVzY3JpYmVkIGJ5IGEgW05lZ2F0aXZlIEJpbm9taWFsIERpc3RyaWJ1dGlvbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTmVnYXRpdmVfYmlub21pYWxfZGlzdHJpYnV0aW9uKSB3aGljaCBoYXMgdHdvIHBhcmFtZXRlcnMgKG9uZSBtb3JlIHRoYW4gdGhlIHNpbXBsZXIgUG9pc3NvbikuIEJlY2F1c2Ugd2UgaW50cm9kdWNlIHRoYXQgb25lIG1vcmUgcGFyYW1ldGVyIHRoZSBzaXR1YXRpb24gaXMgd2F5IG1vcmUgZmxleGlibGUgYW5kIHRoZSBjb3VudHMgY2FuIGJlIG1vcmUgZGlzcGVyc2VkIHRoYW4gaXQgd291bGQgYmUgZXhwZWN0ZWQgZm9yIG9ic2VydmF0aW9ucyBiYXNlZCBvbiBhIFBvaXNzb24gd2l0aCByYXRlICRcbGFtYmRhJCBhbG9uZS4NCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmxpYnJhcnkoTUFTUykNCm5iX21vZGVsIDwtIGdsbS5uYih0b3RhbCB+IGxvY2F0aW9uICsgYWdlICsgbnVtTFQ1ICsgcm9vZiwNCiAgICAgICAgICAgICAgICAgICBkYXRhID0gZkhIMSkNCnN1bW1hcnkobmJfbW9kZWwpDQpgYGANCg0KKipOLkIuKiogTG93ZXIgdmFsdWUgb2YgJFx0aGV0YSQgaW1wbGllcyBsYXJnZXIgb3ZlcmRpc3BlcnNpb24uDQoNCiMjIyAzLiBDcm9zcy1WYWxpZGF0aW9uDQoNCldoZW4gd2UgZGVjaWRlIHRvIGRlcml2ZSBhIG1vZGVsIG9mIHNvbWUgcGhlbm9tZW5vbiBpbiBtYXRoZW1hdGljYWwgc3RhdGlzdGljcyB3aGF0IHdlIHR5cGljYWxseSBkbyBpcyB0byBkcmF3IGEgc2FtcGxlIG9mIHJlbGV2YW50IG9ic2VydmF0aW9ucyBhbmQgcHJlZGljdG9ycyBhbmQgdGhlIHRyYWluIHRoZSBtb2RlbCBvbiB0aGUgc2FtcGxlZCBkYXRhLiBBcyB3ZSBoYXZlIHNlZW4gaW4gdGhlIGNvdXJzZSBvZiBvdXIgcHJldmlvdXMgc2Vzc2lvbnMsIHRoZSBxdWVzdGlvbiBvZiAqKmdlbmVyYWxpemF0aW9uKiogaXMgb2YgY3J1Y2lhbCBpbXBvcnRhbmNlLiBXZSBhcmUgbm90IGxvb2tpbmcgZm9yIG1vZGVscyB3aXRoIHNpZ25pZmljYW50IGNvZWZmaWNpZW50cyBhbmQgaGlnaCAqKmdvb2RuZXMtb2YtZml0IChHb0YpKiogbWVhc3VyZXMgKHN1Y2ggYXMgJFJeMiQsIGZvciBleGFtcGxlKSBvciBsb3cgKipiYWRuZXNzLW9mLWZpdCoqIG1lYXN1cmVzIChzdWNoIGFzICRBSUMkKSwgYnV0IGZvciBtb2RlbHMgdGhhdCB3aWxsIGd1YXJhbnRlZSB0byBnZW5lcmFsaXplIGJleW9uZCB0aGUgZGF0YSB0aGF0IHdlIGhhdmUgdXNlZCB0byB0cmFpbiB0aGVtLiBUaHVzLCB3ZSBhcmUgdmVyeSBtdWNoIGludGVyZXN0ZWQgaW4gdGhlIHN0YW5kYXJkIGVycm9ycyBvZiB0aGUgbW9kZWwgY29lZmZpY2llbnRzOiB0aGV5IGxldCB1cyBrbm93IGhvdyB3aWRlbHkgd291bGQgdGhleSB2YXJ5IGlmIHdlIHdlcmUgdG8gc2FtcGxlIHRoZSBkYXRhIG92ZXIgYW5kIG92ZXIgYWdhaW4gZnJvbSB0aGUgcmVzcGVjdGl2ZSBwb3B1bGF0aW9uLg0KDQpUaGVyZSBpcyBvbmUgc2VyaW91cyBwcm9ibGVtIHRvIGZhY2UgaGVyZS4gU2luY2Ugd2UgaGF2ZSBvbmUgc2FtcGxlIG9mIGRhdGEgYXQgb3VyIGRpc3Bvc2FsLCB3ZSBjYW4gZWFzaWx5IGltYWdpbmUgaG93IGFsbCBmb2xsb3dpbmcgc2FtcGxlcyB3b3VsZCB2YXJ5IGluIHJlc3BlY3QgdG8gaXQuIFNpbmNlIGFsbCBzYW1wbGVzIGNvbWUgZnJvbSB0aGUgc2FtZSBwb3B1bGF0aW9uIHdlIGNhbiBzYWZlbHkgYXNzdW1lIHRoYXQgc29tZSBvcmRlciwgc29tZSBzdHJ1Y3R1cmUsIHNvbWUgaW52YXJpYW50IGluZm9ybWF0aW9uIHdpbGwgYmUgcHJlc2VudCBpbiBhbGwgb2YgdGhlbS4gSG93ZXZlciwgd2UgYWxzbyBrbm93IHRoYXQgZWFjaCBzYW1wbGUgd2lsbCBiZSAqaWRpb3N5bmNyYXRpYyogdXAgdG8gc29tZSBsZXZlbC4gQW5kIHdlIG1hbmFnZSB0byBmaXQgYSBncmVhdCBtb2RlbCB0byB0aGUgc2FtcGxlIG9mIGRhdGEgb25seSB3ZSB3aWxsIGJlIHByb2JhYmx5IHN0YXJ0IHRvIGV4cGxhaW4gdGhhdCBpZGlvc3luY3JhdGljIGFzcGVjdCBvZiB0aGUgc2FtcGxlIHRvbzogYSBwYXJ0IG9mIGluZm9ybWF0aW9uIGNvbnRhaW5lZCBpbiB0aGUgZGF0YSB0aGF0IGlzIGEgY29uc2VxdWVuY2Ugb2YgdGhlIHNhbXBsaW5nIHByb2NlZHVyZSBhbmQgbm90IGluIGFueSB3YXkgYSBnZW5lcmFsIHBhdHRlcm4gcHJlc2VudCBpbiB0aGUgcG9wdWxhdGlvbiBhbmQgdGh1cyBwcm9iYWJseSBub3QgcHJlc2VudCBpbiBlYWNoIG9yIGEgbWFqb3JpdHkgb2YgYWxsIG90aGVyIHNhbXBsZXMgdGhhdCB3ZSBjb3VsZCBoYXZlIGRyYXduIGZyb20gaXQuIFRoaXMgcHJvYmxlbSBpcyBjYWxsZWQgKipvdmVyZml0dGluZyoqIGFuZCBuZWVkcyB0byBiZSBoYW5kbGVkIHZlcnkgY2FyZWZ1bGx5IGluIERhdGEgU2NpZW5jZS4NCg0KKipDcm9zcy12YWxpZGF0aW9uIChDVikqKiBpcyBvbmUgbWV0aG9kIHRvIGRlYWwgd2l0aCB0aGUgcG9zc2liaWxpdHkgb2Ygb3ZlcmZpdHRpbmcuIEluIGFsbCBmb3JtcyBvZiBDViB0aGUgaWRlYSBpcyBiYXNpY2FsbHkgdGhlIHNhbWU6IHRyYWluIChpLmUuIGVzdGltYXRlKSB0aGUgbW9kZWwgb24gYSBzdWJzZXQgKG9yIHN1YnNldHMpIG9mIGRhdGEgYW5kIHRlc3QgdGhlIG1vZGVsIG9uIHRoZSByZW1haW5pbmcgInVuc2VlbiIgcGFydCAob3IgcGFydHMpIG9mIGl0LiBXZSB3aWxsIGludHJvZHVjZSB0d28gYmFzaWMgQ1YgYXBwcm9hY2hlcyBoZXJlOiB0aGUgKipMZWF2ZS1PdXQtT25lLUNWKiogYW5kIHRoZSAqKkstZm9sZCBDVioqLg0KDQojIyMjIDMuMSBMT09DVg0KDQpJIHdpbGwgdXNlIGBpcmlzYCB0byBkZW1vbnN0cmF0ZSB0aGUgTE9PQ1YgcHJvY2VkdXJlLiBXZSB3YW50IHRvIGNvbXBhcmUgdHdvIG1vZGVscyBvZiBgU2VwYWwuV2lkdGhgOg0KDQotIGZpcnN0IHdlIHByZWRpY3QgdGhlIG91dGNvbWUgZnJvbSBgU2VwYWwuTGVuZ3RoYCBhbmQgYFBldGFsLkxlbmd0aGAgb25seSwgYW5kIHRoZW4NCi0gd2UgcHJlZGljdCB0aGUgb3V0Y29tZSBmcm9tIGBTZXBhbC5MZW5ndGhgLCBgUGV0YWwuTGVuZ3RoYCwgYW5kIGBQZXRhbC5XaWR0aGAuDQoNClRoZSBMT09DViBwcm9jZWR1cmUgaXMgdXNlZCB3aGVuIHRoZSBkYXRhc2V0IGlzIHJlYWxseSBzbWFsbCAtIHdoaWNoIGBpcmlzYCBkZWZpbml0ZWx5IGlzLg0KDQpIZXJlIGlzIHRoZSBMT09DViBhcHByb2FjaDoNCg0KLSByZW1vdmUgb25lIHNpbmdsZSBvYnNlcnZhdGlvbiBmcm9tIHRoZSBkYXRhc2V0Ow0KLSBlc3RpbWF0ZSB0aGUgbW9kZWwgZnJvbSB0aGUgcmVtYWluaW5nIGRhdGE7DQotIHByZWRpY3QgdGhlIG9ic2VydmF0aW9uIHRoYXQgd2FzIGxlZnQgb3V0Ow0KLSBjb21wdXRlIHRoZSAqc3F1YXJlZCBwcmVkaWN0aW9uIGVycm9yKiAoYGRhdGEgcG9pbnQgLSBwcmVkaWN0aW9uKV4yYCk7DQotIGNvbXB1dGUgdGhlICptZWFuIHNxdWFyZWQgcHJlZGljdGlvbiBlcnJvciAoTVNQRSkqIGZvbGxvd2luZyB0aGUgcmVtb3ZhbC9maXQvcHJlZGljdCBzdGVwIGZvciBlYWNoIGRhdGEgcG9pbnQuDQoNClRoZSBzZWxlY3RlZCBtb2RlbCB3b3VsZCBiZSB0aGUgb25lIHdpdGggdGhlIGxvd2VyIG1lYW4gcHJlZGljdGlvbiBlcnJvciwgb2YgY291cnNlLiBSOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KaGVhZChpcmlzKQ0KYGBgDQoNCkNvbXB1dGUgdGhlIE1TUEUgZm9yIHRoZSBzaW1wbGVyIG1vZGVsIGZpcnN0Og0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KZGF0YVNldCA8LSBkcGx5cjo6c2VsZWN0KGlyaXMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoLCBTZXBhbC5XaWR0aCkNCm1zcGUxIDwtIHNhcHBseSgxOmRpbShkYXRhU2V0KVsxXSwgZnVuY3Rpb24oeCkgew0KICBmaXREYXRhIDwtIGRhdGFTZXRbLXgsIF0NCiAgbGVmdE91dCA8LSBkYXRhU2V0W3gsIF0NCiAgbW9kZWwgPC0gbG0oU2VwYWwuV2lkdGggfiBTZXBhbC5MZW5ndGggKyBQZXRhbC5MZW5ndGgsIA0KICAgICAgICAgICAgICBkYXRhID0gZml0RGF0YSkNCiAgcHJlZGljdGlvbiA8LSBwcmVkaWN0KG1vZGVsLCANCiAgICAgICAgICAgICAgICAgICAgICAgIG5ld2RhdGEgPSBsZWZ0T3V0WywgYygnU2VwYWwuTGVuZ3RoJywgJ1BldGFsLkxlbmd0aCcpXSkNCiAgc3BlIDwtIChsZWZ0T3V0JFNlcGFsLldpZHRoIC0gcHJlZGljdGlvbileMg0KfSkNCnByaW50KG1lYW4obXNwZTEpKQ0KYGBgDQoNCkFuZCBub3cgY29tcHV0ZSB0aGUgTVNQRSBmb3IgdGhlIG1vZGVsIGVuY29tcGFzc2luZyB0aGUgYWRkaXRpb25hbCBwcmVkaWN0b3I6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpkYXRhU2V0IDwtIGRwbHlyOjpzZWxlY3QoaXJpcywgDQogICAgICAgICAgICAgICAgICAgICAgICAgU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgsIFBldGFsLldpZHRoLCBTZXBhbC5XaWR0aCkNCm1zcGUyIDwtIHNhcHBseSgxOmRpbShkYXRhU2V0KVsxXSwgZnVuY3Rpb24oeCkgew0KICBmaXREYXRhIDwtIGRhdGFTZXRbLXgsIF0NCiAgbGVmdE91dCA8LSBkYXRhU2V0W3gsIF0NCiAgbW9kZWwgPC0gbG0oU2VwYWwuV2lkdGggfiBTZXBhbC5MZW5ndGggKyBQZXRhbC5MZW5ndGggKyBQZXRhbC5XaWR0aCwgDQogICAgICAgICAgICAgIGRhdGEgPSBmaXREYXRhKQ0KICBwcmVkaWN0aW9uIDwtIHByZWRpY3QobW9kZWwsIA0KICAgICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IGxlZnRPdXRbLCBjKCdTZXBhbC5MZW5ndGgnLCAnUGV0YWwuTGVuZ3RoJywgJ1BldGFsLldpZHRoJyldKQ0KICBzcGUgPC0gKGxlZnRPdXQkU2VwYWwuV2lkdGggLSBwcmVkaWN0aW9uKV4yDQp9KQ0KcHJpbnQobWVhbihtc3BlMikpDQpgYGANCg0KIyMjIyAzLjIgSy1Gb2xkIENWDQoNCkluIEstZm9sZCBDViB3ZSByYW5kb21seSBwbGFjZSBhbGwgYXZhaWxhYmxlIG9ic2VydmF0aW9ucyBpbiBLICpmb2xkcyogKGkuZS4gZ3JvdXBzKS4gSWYgd2UgZGVjaWRlIHRvIGdvIGZvciB0aHJlZSBncm91cHMsIGZvciBleGFtcGxlLCB3ZSBwcm9jZWVkIGluIHRoZSBmb2xsb3dpbmcgd2F5Og0KDQotIHJhbmRvbWx5IGFzc2lnbiB0aGUgb2JzZXJ2YXRpb25zIGludG8gdGhlIHRocmVlIGdyb3VwcyBBLCBCLCBhbmQgQzsNCi0gZml0IHRoZSBtb2RlbCBvbiBBK0IgZGF0YSBhbmQgcHJlZGljdCB0aGUgb3V0Y29tZSBmcm9tIHRoZSBsZWZ0IG91dCBncm91cCBDLCB0aGVuIGNvbXB1dGUgdGhlIE1TUEU7DQotIGZpdCB0aGUgbW9kZWwgb24gQStDIGRhdGEgYW5kIHByZWRpY3QgdGhlIG91dGNvbWUgZnJvbSB0aGUgbGVmdCBvdXQgZ3JvdXAgQiwgdGhlbiBjb21wdXRlIHRoZSBNU1BFOw0KLSBmaXQgdGhlIG1vZGVsIG9uIEMrQiBkYXRhIGFuZCBwcmVkaWN0IHRoZSBvdXRjb21lIGZyb20gdGhlIGxlZnQgb3V0IGdyb3VwIEEsIHRoZW4gY29tcHV0ZSB0aGUgTVNQRTsNCi0gY29tcHV0ZSB0aGUgbWVhbiBvZiB0aGUgTVNQRXMgZnJvbSB0aGUgcHJldmlvdXMgc3RlcHM7IGZpbmFsbHksDQotIHNlbGVjdCB0aGUgbW9kZWwgd2l0aCB0aGUgbG93ZXN0IG1lYW4gcHJlZGljdGlvbiBlcnJvci4NCg0KRm9yIHRoZSBzaW1wbGVyIGBTZXBhbC5XaWR0aCB+IFNlcGFsLkxlbmd0aCArIFBldGFsLkxlbmd0aGAgbW9kZWwgZmlyc3Q6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQppcmlzJFNwZWNpZXMgPC0gTlVMTA0KaXJpcyRmb2xkIDwtIHNhbXBsZSgxOjMsIDE1MCwgcmVwbGFjZSA9IFRSVUUpDQp0YWJsZShpcmlzJGZvbGQpDQpgYGANCioqTi5CLioqIFRoZSBmb2xkcyBzaG91bGQgYmUgb2YgYXBwcm94aW1hdGVseSB0aGUgc2FtZSBzaXplIGF0IGxlYXN0LiBJIGNvdWxkIGhhdmUgb3B0ZWQgZm9yIDUwLzUwLzUwIGFzIHdlbGwuIEhvd2V2ZXI6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpkYXRhU2V0IDwtIGRwbHlyOjpzZWxlY3QoaXJpcywgDQogICAgICAgICAgICAgICAgICAgICAgICAgU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgsIFNlcGFsLldpZHRoLCBmb2xkKQ0KbXNwZTEgPC0gc2FwcGx5KDE6MywgZnVuY3Rpb24oeCkgew0KICBmaXREYXRhIDwtIGRhdGFTZXRbZGF0YVNldCRmb2xkICE9IHgsIF0NCiAgbGVmdE91dCA8LSBkYXRhU2V0W2RhdGFTZXQkZm9sZCA9PSB4LCBdDQogIG1vZGVsIDwtIGxtKFNlcGFsLldpZHRoIH4gU2VwYWwuTGVuZ3RoICsgUGV0YWwuTGVuZ3RoLCANCiAgICAgICAgICAgICAgZGF0YSA9IGZpdERhdGEpDQogIHByZWRpY3Rpb24gPC0gcHJlZGljdChtb2RlbCwgDQogICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gbGVmdE91dFssIGMoJ1NlcGFsLkxlbmd0aCcsICdQZXRhbC5MZW5ndGgnKV0pDQogIHNwZSA8LSBtZWFuKChsZWZ0T3V0JFNlcGFsLldpZHRoIC0gcHJlZGljdGlvbileMikNCn0pDQpwcmludChtc3BlMSkNCmBgYA0KQW5kIHRoZSBtZWFuIG9mIE1TUEVzIGZvciB0aGUgc2ltcGxlciBtb2RlbCBpczoNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCnByaW50KG1lYW4obXNwZTEpKQ0KYGBgDQpOb3cgZm9yIHRoZSBtb2RlbCBlbmNvbXBhc3NpbmcgYFBldGFsLldpZHRoYCB0b286DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpkYXRhU2V0IDwtIGRwbHlyOjpzZWxlY3QoaXJpcywgDQogICAgICAgICAgICAgICAgICAgICAgICAgU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgsIFBldGFsLldpZHRoLCBTZXBhbC5XaWR0aCwgZm9sZCkNCm1zcGUyIDwtIHNhcHBseSgxOjMsIGZ1bmN0aW9uKHgpIHsNCiAgZml0RGF0YSA8LSBkYXRhU2V0W2RhdGFTZXQkZm9sZCAhPSB4LCBdDQogIGxlZnRPdXQgPC0gZGF0YVNldFtkYXRhU2V0JGZvbGQgPT0geCwgXQ0KICBtb2RlbCA8LSBsbShTZXBhbC5XaWR0aCB+IFNlcGFsLkxlbmd0aCArIFBldGFsLkxlbmd0aCArIFBldGFsLldpZHRoLCANCiAgICAgICAgICAgICAgZGF0YSA9IGZpdERhdGEpDQogIHByZWRpY3Rpb24gPC0gcHJlZGljdChtb2RlbCwgDQogICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gbGVmdE91dFssIGMoJ1NlcGFsLkxlbmd0aCcsICdQZXRhbC5MZW5ndGgnLCAnUGV0YWwuV2lkdGgnKV0pDQogIHNwZSA8LSBtZWFuKChsZWZ0T3V0JFNlcGFsLldpZHRoIC0gcHJlZGljdGlvbileMikNCn0pDQpwcmludChtc3BlMikNCmBgYA0KDQpBbmQgdGhlIG1lYW4gTVNQRSBmcm9tIHRocmVlIGZvbGRzIGlzOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KcHJpbnQobWVhbihtc3BlMikpDQpgYGANCioqTi5CLioqIENyb3NzLXZhbGlkYXRpb24gcHJvY2VkdXJlcyBkbyBub3QgaGF2ZSB0byBiZSBiYXNlZCBvbiBNU1BFLiBPdGhlciBjcml0ZXJpYSBsaWtlIEFJQyBvciBsb2ctbGlrZWxpaG9vZCBhcmUgdXNlZCBhcyB3ZWxsLiBGb3IgY2xhc3NpZmljYXRpb24gcHJvYmxlbXMsIHRoZSBST0MgYW5hbHlzaXMgZm9yIGVhY2ggcHJlZGljdGlvbiBjYW4gYmUgdXNlZCB0byBzZWxlY3QgdGhlIGJlc3QgYXZhaWxhYmxlIG1vZGVsLg0KDQoNCioqKg0KDQojIyMgRnVydGhlciBSZWFkaW5ncw0KDQorIFtCZXlvbmQgTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb246IEFwcGxpZWQgR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVscyBhbmQgTXVsdGlsZXZlbCBNb2RlbHMgaW4gUiwgUGF1bCBSb2JhY2sgYW5kIEp1bGllIExlZ2xlciwgSmFudWFyeSAyNiwgMjAyMSwgQ2hhcHRlciA0IFBvaXNzb24gUmVncmVzc2lvbl0oaHR0cHM6Ly9ib29rZG93bi5vcmcvcm9iYWNrL2Jvb2tkb3duLUJleW9uZE1MUi9jaC1wb2lzc29ucmVnLmh0bWwpDQoNCisgW0ltcHJvdmUgWW91ciBNb2RlbCBQZXJmb3JtYW5jZSB1c2luZyBDcm9zcyBWYWxpZGF0aW9uIChpbiBQeXRob24gYW5kIFIpIGZyb20gQW5hbHl0aWNzIFZpZGh5YV0oaHR0cHM6Ly93d3cuYW5hbHl0aWNzdmlkaHlhLmNvbS9ibG9nLzIwMTgvMDUvaW1wcm92ZS1tb2RlbC1wZXJmb3JtYW5jZS1jcm9zcy12YWxpZGF0aW9uLWluLXB5dGhvbi1yKQ0KDQoNCiMjIyBSIE1hcmtkb3duDQoNCisgW1IgTWFya2Rvd25dKGh0dHBzOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tLykgaXMgd2hhdCBJIGhhdmUgdXNlZCB0byBwcm9kdWNlIHRoaXMgYmVhdXRpZnVsIE5vdGVib29rLiBXZSB3aWxsIGxlYXJuIG1vcmUgYWJvdXQgaXQgbmVhciB0aGUgZW5kIG9mIHRoZSBjb3Vyc2UsIGJ1dCBpZiB5b3UgYWxyZWFkeSBmZWVsIHJlYWR5IHRvIGRpdmUgZGVlcCwgaGVyZSdzIGEgYm9vazogW1IgTWFya2Rvd246IFRoZSBEZWZpbml0aXZlIEd1aWRlLCBZaWh1aSBYaWUsIEouIEouIEFsbGFpcmUsIEdhcnJldHQgR3JvbGVtdW5kcy5dKGh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi8pIA0KDQoqKioNCkdvcmFuIFMuIE1pbG92YW5vdmnEhw0KDQpEYXRhS29sZWt0aXYsIDIwMjAvMjENCg0KY29udGFjdDogZ29yYW4ubWlsb3Zhbm92aWNAZGF0YWtvbGVrdGl2LmNvbQ0KDQohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpDQoNCioqKg0KTGljZW5zZTogW0dQTHYzXShodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvZ3BsLTMuMC50eHQpDQpUaGlzIE5vdGVib29rIGlzIGZyZWUgc29mdHdhcmU6IHlvdSBjYW4gcmVkaXN0cmlidXRlIGl0IGFuZC9vciBtb2RpZnkgaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnkgdGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiwgZWl0aGVyIHZlcnNpb24gMyBvZiB0aGUgTGljZW5zZSwgb3IgKGF0IHlvdXIgb3B0aW9uKSBhbnkgbGF0ZXIgdmVyc2lvbi4NClRoaXMgTm90ZWJvb2sgaXMgZGlzdHJpYnV0ZWQgaW4gdGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCwgYnV0IFdJVEhPVVQgQU5ZIFdBUlJBTlRZOyB3aXRob3V0IGV2ZW4gdGhlIGltcGxpZWQgd2FycmFudHkgb2YgTUVSQ0hBTlRBQklMSVRZIG9yIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFLiAgU2VlIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLg0KWW91IHNob3VsZCBoYXZlIHJlY2VpdmVkIGEgY29weSBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgYWxvbmcgd2l0aCB0aGlzIE5vdGVib29rLiBJZiBub3QsIHNlZSA8aHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uDQoNCioqKg0KDQo=