Session 21. Random Forests: Bagging, Out-Of-Bag (OOB) Error, Bootstrap Samples + Random Subspace Method.

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?

Today we will introduce an idea even more powerful than the Decision Tree model to solve classification and regression problems: the Random Forest model. We will rely on the R package {randomForest} to train Random Forests in both classification and regression contexts. In order to understand Random Forests we will introduce some important theoretical concepts: Bootstrap aggregating (a.k.a. Bagging), Out-of-bag (OOB) error, and Feature Bagging (a.k.a. the random subspace method or attribute bagging). We will see how these approaches in Machine Learning prevent overfitting in the training of a complex model like Random Forest.

Following our discussion of Random Forests, we will get back to a single Decision Tree and discuss the basics of its algorithm step by step on a small data set, comparing our results with the results obtained from {rpart}.

0. Setup

# install.packages('randomForest')

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

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

1. Random Forests for Classification

We begin by loading and inspecting the HR_comma_sep.csv dataset:

dataSet <- read.csv(paste0(getwd(), "/_data/HR_comma_sep.csv"),
                    header = T,
                    check.names = 1,
                    stringsAsFactors = F)
glimpse(dataSet)
Rows: 14,999
Columns: 10
$ satisfaction_level    <dbl> 0.38, 0.80, 0.11, 0.72, 0.37, 0.41, 0.10, 0.92, 0.89, 0.42, 0.45,…
$ last_evaluation       <dbl> 0.53, 0.86, 0.88, 0.87, 0.52, 0.50, 0.77, 0.85, 1.00, 0.53, 0.54,…
$ number_project        <int> 2, 5, 7, 5, 2, 2, 6, 5, 5, 2, 2, 6, 4, 2, 2, 2, 2, 4, 2, 5, 6, 2,…
$ average_montly_hours  <int> 157, 262, 272, 223, 159, 153, 247, 259, 224, 142, 135, 305, 234, …
$ time_spend_company    <int> 3, 6, 4, 5, 3, 3, 4, 5, 5, 3, 3, 4, 5, 3, 3, 3, 3, 6, 3, 5, 4, 3,…
$ Work_accident         <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,…
$ left                  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
$ promotion_last_5years <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,…
$ sales                 <chr> "sales", "sales", "sales", "sales", "sales", "sales", "sales", "s…
$ salary                <chr> "low", "medium", "medium", "low", "low", "low", "low", "low", "lo…

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

  • satisfaction_level: a measure of employee’s level of satisfaction
  • last_evaluation: the result of a last evaluation
  • number_projects: in how many projects did the employee took part
  • average_monthly_hours: how working hours monthly on average
  • time_spend_company: for how long is the employee with us
  • Work_accident: any work accidents?
  • promotion_last_5years: did the promotion occur in the last five years?
  • sales: department (sales, accounting, hr, technical, support, management, IT, product_mng, marketing, RandD)
  • salary: salary class (low, medium, high)

1.1 Random Forests: the Algorithm

There three important concepts to understand how Random Forest builds upon Decision Trees:

  • Bootstrap aggregating (Bagging). We begin with some training set for our model, \(D\). Bagging generates \(m\) new training sets, \(D_i\), \(i = 1, 2, ..., m\), by randomly sampling from \(D\) uniformly and with replacement. The samples obtained in this way are known as bootstrap samples. In Random Forests, \(m\) simpler models - Decision Trees, precisely - are fitted for each \(D_i\) bootstrap sample.

  • Out-of-bag (OOB) error. Each time a new bootstrap sample \(D_i\) is produced, some data points remain out of the bag, are not used in model training, and form the OOB set (the Out-of-bag set). The OOB error is a prediction error (remember this concept from our introduction to cross-validation?) which is computed from the OOB set, where the prediction is obtained by averaging the response (in regression) or as a majority vote (in classification) from all the trees in the forest that were not trained on that particular OOB instance.

  • The Random Subspace Method (Feature Bagging). The Random Subspace Method is a method to control for the complexity of the decision trees grown in a Random Forest model. On each new split, only a randomly chosen subset of predictors is used to find the optimal split. The Random Forests algorithm, as we will see, has a control parameter that determines how many features are randomly selected to produce a new split.

1.2 Classification w. {randomForest}

We will fit a Random Forest model to the HR_comma_sep.csv dataset with randomForest::randomForest() in R. In spite of all the precautionary measures already taken in the Random Forest model itself, we will still perform an additional outter k-fold cross-validation with 5 folds: better safe than sorry.

First, define the folds:

dataSet$ix <- sample(1:5, dim(dataSet)[1], replace = T)
table(dataSet$ix)

   1    2    3    4    5 
3116 2938 2982 2947 3016 

Perform a 5-fold cross validation for a set of Random Forests across the following control parameters:

  • ntree: the number of trees to grow
  • mtry: the number of variables to randomly sample as candidates at each split (to control Feature Bagging)
ntree <- seq(250, 1000, by = 250)
mtry <- 3:(dim(dataSet)[2]-2)
# - mtry: we do not count `left` which is an outcome
# - and `ix` which is a fold index, so we have
# - dim(dataSet)[2]-2 predictors in the model.
# - start timer:
tstart <- Sys.time()

# - iterate:
# - lapply() across ntree
rfModels <- lapply(ntree, function(nt) {
  
  # - lapply() across mtry
  mtrycv <- lapply(mtry, function(mt) {
    
    # - lapply() across folds
    cv <- lapply(unique(dataSet$ix), function(fold) {
      
      # - split training and test sets
      testIx <- fold
      trainIx <- setdiff(1:5, testIx)
      trainSet <- dataSet %>% 
        dplyr::filter(ix %in% trainIx) %>% 
        dplyr::select(-ix)
      testSet <- dataSet %>% 
        dplyr::filter(ix %in% testIx) %>%
        dplyr::select(-ix)
      
      # - `left` to factor for classification 
      # -  w. randomForest()
      trainSet$left <- as.factor(trainSet$left)
      testSet$left <- as.factor(testSet$left)
      
      # - Random Forest:
      model <- randomForest::randomForest(formula = left ~ .,
                                          data = trainSet, 
                                          ntree = nt,
                                          mtry = mt
                                          )
      # - ROC analysis:
      predictions <- predict(model, 
                             newdata = testSet)
      hit <- sum(ifelse(predictions == 1 & testSet$left == 1, 1, 0))
      hit <- hit/sum(testSet$left == 1)
      fa <- sum(ifelse(predictions == 1 & testSet$left == 0, 1, 0))
      fa <- fa/sum(testSet$left == 0)
      acc <- sum(predictions == testSet$left)
      acc <- acc/length(testSet$left)
      # - ROC output:
      return(
        data.frame(fold, hit, fa, acc)
      )
    })
    
    # - collect results from all folds:
    cv <- data.table::rbindlist(cv)
    
    # - average ROC:
    avg_roc <- data.frame(hit = mean(cv$hit),
                          fa = mean(cv$fa), 
                          acc = mean(cv$acc),
                          mtry = mt)
    return(avg_roc)
  })
  
  # - collect from all mtry values:
  mtrycv <- rbindlist(mtrycv)
  # - add ntree and out:
  mtrycv$ntree <- nt
  return(mtrycv)
  
})

# - collect all results
rfModels <- rbindlist(rfModels)
write.csv(rfModels, 
          paste0(getwd(), "/rfModels.csv"))
# - Report timing:
print(paste0("The estimation took: ", 
             difftime(Sys.time(), tstart, units = "mins"), 
             " minutes."))
[1] "The estimation took: 13.5653865853945 minutes."
print(rfModels)

Let’s inspect the CV results visually. Accuracy first:

rfModels$ntree <- factor(rfModels$ntree)
rfModels$mtry <- factor(rfModels$mtry)
ggplot(data = rfModels, 
       aes(x = mtry,
           y = acc, 
           group = ntree, 
           color = ntree,
           fill = ntree,
           label = round(acc, 2))
       ) +
  geom_path(size = .25) + 
  geom_point(size = 1.5) + 
  ggtitle("Random Forests CV: Accuracy") +
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5, size = 8))

Hit rate:

ggplot(data = rfModels, 
       aes(x = mtry,
           y = hit, 
           group = ntree, 
           color = ntree,
           fill = ntree,
           label = round(acc, 2))
       ) +
  geom_path(size = .25) + 
  geom_point(size = 1.5) + 
  ggtitle("Random Forests CV: Hit Rate") +
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5, size = 8))

False Alarm rate:

ggplot(data = rfModels, 
       aes(x = mtry,
           y = fa, 
           group = ntree, 
           color = ntree,
           fill = ntree,
           label = round(acc, 2))
       ) +
  geom_path(size = .25) + 
  geom_point(size = 1.5) + 
  ggtitle("Random Forests CV: FA Rate") +
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5, size = 8))

And pick the best model from ROC:

rfModels$diff <- rfModels$hit - rfModels$fa
w_best <- which.max(rfModels$diff)
rfModels[w_best, ]

And we can still refine the chosen model:

dataSet <- dataSet %>% 
  dplyr::select(-ix)
dataSet$left <- factor(dataSet$left)
optimal_model <- randomForest::randomForest(formula = left ~ .,
                                            data = dataSet,
                                            ntree = 250,
                                            mtry = 4)
optimal_model$importance
                      MeanDecreaseGini
satisfaction_level         2054.571142
last_evaluation             655.974267
number_project              937.381386
average_montly_hours        696.566033
time_spend_company          973.819607
Work_accident                15.990344
promotion_last_5years         2.813876
sales                        60.474408
salary                       32.918475

The cumulative OOB error up to the i-th tree can be found in the first column of the optimal_model$err.rate field:

head(optimal_model$err.rate)
            OOB          0          1
[1,] 0.02296376 0.01938534 0.03422619
[2,] 0.02106197 0.01725903 0.03296703
[3,] 0.02157001 0.01664533 0.03711871
[4,] 0.02039531 0.01418807 0.04009201
[5,] 0.01891952 0.01280932 0.03843769
[6,] 0.01734679 0.01186805 0.03476969

Let’s take a closer look at it:

oobFrame <- as.data.frame(optimal_model$err.rate)
oobFrame$ntree <- 1:dim(oobFrame[1])
ggplot(data = oobFrame, 
       aes(x = ntree,
           y = OOB)) + 
  geom_path(size = .25) + 
  geom_point(size = 1.5) + 
  ggtitle("Cumulative OOB error from the CV optimal Random Forest model") +
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5, size = 8))

We can observe how the cumulative OOB error stabilizes following the growth of a certain number of trees in the Random Forest model:

w_tree <- which.min(oobFrame$OOB)
print(w_tree)
[1] 239

And finally:

optimal_model <- randomForest::randomForest(formula = left ~ .,
                                            data = dataSet,
                                            ntree = w_tree,
                                            mtry = 4
                                            )
predictions <- predict(optimal_model, 
                       newdata = dataSet)
hit <- sum(ifelse(predictions == 1 & dataSet$left == 1, 1, 0))
hit <- hit/sum(dataSet$left == 1)
fa <- sum(ifelse(predictions == 1 & dataSet$left == 0, 1, 0))
fa <- fa/sum(dataSet$left == 0)
acc <- sum(predictions == dataSet$left)
acc <- acc/length(dataSet$left)
print(paste0("Accuracy: ", acc, "; Hit rate: ", hit, "; FA rate: ", fa))
[1] "Accuracy: 1; Hit rate: 1; FA rate: 0"

2. Random Forests for Regression

In {randomForest}, to obtain the Random Forest model for regression simply do not pronounce an outcome to be a factor. We will use the Boston Housing dataset to demonstrate.

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

Here are the variables:

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

The medv variable is the outcome.

Random Forest (no cross-validation this time):

rfRegMsodel <- randomForest::randomForest(formula = medv ~ .,
                                          data = dataSet,
                                          ntree = 1000,
                                          mtry = 7
                                          )

We can use the generic plot() function to assess how the MSE changes across ntree:

plot(rfRegMsodel)

predictions <- predict(rfRegMsodel, 
                       newdata = dataSet)
predictFrame <- data.frame(predicted_medv = predictions, 
                           observed_medv = dataSet$medv)
ggplot(data = predictFrame, 
       aes(x = predicted_medv,
           y = observed_medv)) + 
  geom_smooth(method = "lm", size = .25, color = "red") + 
  geom_point(size = 1.5, color = "black") + 
  geom_point(size = .75, color = "white") +
  ggtitle("Random Forest in Regression: Observed vs Predicted\nBoston Housing Dataset") + 
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5, size = 8))

3. A Decision Tree from scratch

N.B. We are interested in principles on which the split is decided in a Decision Tree. The following algorithm is neither a complete Decision Tree algorithm nor a fully correct implementation at all. For example, we do not deal with internal cross-validation across \(\alpha\) - the complexity parameter - at all. Second, for reasons of simplicity, we consider only binary categorical features. The aim is to demonstrate how Gini and MSE are used to compare candidate splits across categorical and numerical features, respectively.

Step one: setup + load Titanic data.

### --- setup
library(rpart)
library(rpart.plot)

### --- Titanic dataset
data(Titanic)
library(carData)
data_set <- TitanicSurvival
data_set <- data_set[complete.cases(data_set), ]
head(data_set, 20)

Let’s now train a Decision Tree to predict survived w. {rpart}:

### --- decision tree model w. rpart
dec_tree <- rpart(survived ~ .,
                  data = data_set)
rpart.plot(dec_tree)


# - predictions
predictions <- predict(dec_tree, 
                       newdata = data_set, 
                       type = "prob")
predictions <- ifelse(predictions[, 1] <= predictions[, 2],
                     "yes",
                     "no")

# - elementary ROC analysis
acc <- sum(predictions == data_set$survived)/length(data_set$survived)
print(acc)
[1] 0.8049713
hit <- sum(predictions == "yes" & data_set$survived == "yes")/sum(data_set$survived == "yes")
print(hit)
[1] 0.5620609
fa <- sum(predictions == "yes" & data_set$survived == "no")/sum(data_set$survived == "no")
print(fa)
[1] 0.02746365

Q. How is a split decided? First we will develop a function to compute Gini Impurity. We will use the nodes data.frame to test:

# - nodes data.frame to test with:
nodes <- data.frame(class_counts = c(142, 232))
print(nodes)
### --- First: a function to compute Gini Impurity
# - for classification trees
# - Gini Impurity
# - N.B. Implement Gini as 
# - # - p1*(1-p1) + p2*(1-p2) == p1 - p1^2 + p2 - p2^2 = (p1 + p2) - (p1^2+p2^2) = 1-sum(p^2)
gini_binary <- function(nodes) {
  
  # - probabilities
  p <- nodes$class_counts/sum(nodes$class_counts)

  # - Gini Impurity
  gini <- 1 - sum(p^2)
  
  return(gini)
  
}

gini_binary(nodes)
[1] 0.4710458

Now the complicated part: a function, attribute_split(), to decide a split for both numerical and categorical predictors.

attribute_split <- function(params) {
  
  # - input
  # - the data:
  data <- params[[1]]
  # - the attribute (feature, predictor) that we wish to test
  attribute <- params[[2]]
  # - the outcome variable 
  target <- params[[3]]

  # - type of attribute
  att <- which(colnames(data) == attribute)
  atype <- class(data[, att])
  
  # - switch categorical and continuous
  if (atype == "factor") {
    
    # - CATEGORICAL
    
    # - cardinality
    acard <- length(unique(data[, att]))
  
    # - possible splits
    design <- combn(unique(data[, att]), 2)
    design <- matrix(as.character(design),
                     ncol = acard)

    # - If cardinality == 2
    if (acard == 2) {
      
      # - left
      wd <- which(data[, att] == design[, 1])
      d <- data[wd, target]
      n1 <- length(d)
      td <- as.numeric(table(d))
      td <- data.frame(class_counts = td)
      gini_split_1 <- gini_binary(td)
      # - right
      wd <- which(data[, att] == design[, 2])
      d <- data[wd, target]
      n2 <- length(d)
      td <- as.numeric(table(d))
      td <- data.frame(class_counts = td)
      gini_split_2 <- gini_binary(td)
      # - Gini
      n <- n1 + n2
      g <- n1/n*gini_split_1 + n2/n*gini_split_2

      # - output
      return(list(splits = design,
                  gini = g))
      
    # - If cardinality > 2  
    } else {
      
      # - gini for all possible splits
      g <- apply(design, 2, function(x) {
        # - left
        wd <- which(data[, att] %in% x)
        d <- data[wd, target]
        n1 <- length(d)
        td <- as.numeric(table(d))
        td <- data.frame(class_counts = td)
        gini_split_1 <- gini_binary(td)
        # - right
        wd <- which(!(data[, att] %in% x))
        d <- data[wd, target]
        n2 <- length(d)
        td <- as.numeric(table(d))
        td <- data.frame(class_counts = td)
        gini_split_2 <- gini_binary(td)
        # - Gini
        n <- n1 + n2
        return(n1/n*gini_split_1 + n2/n*gini_split_2)
      })
      
      # - output
      return(list(splits = design,
                  gini = g))
      
    }
    
  } else {
    
    # - CONTINUOUS
    # - note: if CONTINUOUS
    # - we measure MSE
    
    # - lower and upper bounds
    lower_b <- min(data[, att]) + 1
    upper_b <- max(data[, att]) - 1
    
    # - objective
    min_gini <- function(split) {
      
      # - right
      wd <- which(data[, att] > split)
      d <- data[wd, target]
      n1 <- length(d)
      td <- as.numeric(table(d))
      td <- data.frame(class_counts = td)
      gini_split_1 <- gini_binary(td)
      # - left
      wd <- which(data[, att] <= split)
      d <- data[wd, target]
      n2 <- length(d)
      td <- as.numeric(table(d))
      td <- data.frame(class_counts = td)
      gini_split_2 <- gini_binary(td)
      
      n <- n1 + n2
      return(n1/n*gini_split_1 + n2/n*gini_split_2)
      
    }
    
    # - optimization: grid-search
    c <- seq(lower_b, upper_b, by = .1)
    ginies <- sapply(c, min_gini)
    
    # - output
    return(list(split = c[which.min(ginies)[1]],
                gini = ginies[which.min(ginies)]))
  
  }
  
}

Test: first split.

# - fist split:
params <- list(data_set, "sex", "survived")
attribute_split(params)
$splits
     [,1]     [,2]  
[1,] "female" "male"

$gini
[1] 0.3433076
params <- list(data_set, "passengerClass", "survived")
attribute_split(params)
$splits
     [,1]  [,2]  [,3] 
[1,] "1st" "1st" "2nd"
[2,] "2nd" "3rd" "3rd"

$gini
[1] 0.4435625 0.4824558 0.4440288
params <- list(data_set, "age", "survived")
attribute_split(params)
$split
[1] 8.0667

$gini
[1] 0.4752871

Split at sex == "male":

# - split at `sex == "male"`:
params <- list(data_set[data_set$sex=="male", ], "age", "survived")
attribute_split(params)
$split
[1] 9.0333

$gini
[1] 0.3063536

Split at sex == "female"

# - split at sex == "female"
params <- list(data_set[data_set$sex=="female", ], "age", "survived")
attribute_split(params)
$split
[1] 30.5667

$gini
[1] 0.3648965

Further Readings


Goran S. Milovanović

DataKolektiv, 2020/21

contact:


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


LS0tCnRpdGxlOiBJbnRybyB0byBEYXRhIFNjaWVuY2UgKE5vbi1UZWNobmljYWwgQmFja2dyb3VuZCwgUikgLSBTZXNzaW9uMjEKYXV0aG9yOgotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhywgUGhECiAgYWZmaWxpYXRpb246IERhdGFLb2xla3RpdiwgQ2hpZWYgU2NpZW50aXN0ICYgT3duZXI7IERhdGEgU2NpZW50aXN0IGZvciBXaWtpZGF0YSwgV01ERQphYnN0cmFjdDogCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgdG9jX2RlcHRoOiA1CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDUKLS0tCgohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpCgoqKioKIyBTZXNzaW9uIDIxLiBSYW5kb20gRm9yZXN0czogQmFnZ2luZywgT3V0LU9mLUJhZyAoT09CKSBFcnJvciwgQm9vdHN0cmFwIFNhbXBsZXMgKyBSYW5kb20gU3Vic3BhY2UgTWV0aG9kLgoKKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tYC4gClRoZXNlIG5vdGVib29rcyBhY2NvbXBhbnkgdGhlIEludHJvIHRvIERhdGEgU2NpZW5jZTogTm9uLVRlY2huaWNhbCBCYWNrZ3JvdW5kIGNvdXJzZSAyMDIwLzIxLgoKKioqCgojIyMgV2hhdCBkbyB3ZSB3YW50IHRvIGRvIHRvZGF5PwoKVG9kYXkgd2Ugd2lsbCBpbnRyb2R1Y2UgYW4gaWRlYSBldmVuIG1vcmUgcG93ZXJmdWwgdGhhbiB0aGUgRGVjaXNpb24gVHJlZSBtb2RlbCB0byBzb2x2ZSBjbGFzc2lmaWNhdGlvbiBhbmQgcmVncmVzc2lvbiBwcm9ibGVtczogKip0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbCoqLiBXZSB3aWxsIHJlbHkgb24gdGhlIFIgcGFja2FnZSBbe3JhbmRvbUZvcmVzdH1dKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yYW5kb21Gb3Jlc3QvKSB0byB0cmFpbiBSYW5kb20gRm9yZXN0cyBpbiBib3RoIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIGNvbnRleHRzLiBJbiBvcmRlciB0byB1bmRlcnN0YW5kIFJhbmRvbSBGb3Jlc3RzIHdlIHdpbGwgaW50cm9kdWNlIHNvbWUgaW1wb3J0YW50IHRoZW9yZXRpY2FsIGNvbmNlcHRzOiAqQm9vdHN0cmFwIGFnZ3JlZ2F0aW5nKiAoYS5rLmEuICpCYWdnaW5nKiksICpPdXQtb2YtYmFnIChPT0IpIGVycm9yKiwgYW5kICpGZWF0dXJlIEJhZ2dpbmcqIChhLmsuYS4gKnRoZSByYW5kb20gc3Vic3BhY2UgbWV0aG9kKiBvciAqYXR0cmlidXRlIGJhZ2dpbmcqKS4gV2Ugd2lsbCBzZWUgaG93IHRoZXNlIGFwcHJvYWNoZXMgaW4gTWFjaGluZSBMZWFybmluZyBwcmV2ZW50IG92ZXJmaXR0aW5nIGluIHRoZSB0cmFpbmluZyBvZiBhIGNvbXBsZXggbW9kZWwgbGlrZSBSYW5kb20gRm9yZXN0LgoKRm9sbG93aW5nIG91ciBkaXNjdXNzaW9uIG9mIFJhbmRvbSBGb3Jlc3RzLCB3ZSB3aWxsIGdldCBiYWNrIHRvIGEgc2luZ2xlIERlY2lzaW9uIFRyZWUgYW5kIGRpc2N1c3MgdGhlIGJhc2ljcyBvZiBpdHMgYWxnb3JpdGhtIHN0ZXAgYnkgc3RlcCBvbiBhIHNtYWxsIGRhdGEgc2V0LCBjb21wYXJpbmcgb3VyIHJlc3VsdHMgd2l0aCB0aGUgcmVzdWx0cyBvYnRhaW5lZCBmcm9tIHtycGFydH0uCgoKIyMjIDAuIFNldHVwCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IEZ9CiMgaW5zdGFsbC5wYWNrYWdlcygncmFuZG9tRm9yZXN0JykKYGBgCgpHcmFiIHRoZSBgSFJfY29tbWFfc2VwLmNzdmAgZGF0YXNldCBmcm9tIHRoZSBbS2FnZ2xlXShodHRwczovL3d3dy5rYWdnbGUuY29tL2xpdWppYXFpL2hyLWNvbW1hLXNlcGNzdikgYW5kIHBsYWNlIGl0IGluIHlvdXIgYF9kYXRhYCBkaXJlY3RvcnkgZm9yIHRoaXMgc2Vzc2lvbi4gV2Ugd2lsbCBhbHNvIHVzZSB0aGUgW0Jvc3RvbiBIb3VzaW5nIERhdGFzZXQ6ICBCb3N0b25Ib3VzaW5nLmNzdl0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3NlbHZhODYvZGF0YXNldHMvbWFzdGVyL0Jvc3RvbkhvdXNpbmcuY3N2KQoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0KZGF0YURpciA8LSBwYXN0ZTAoZ2V0d2QoKSwgIi9fZGF0YS8iKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKbGlicmFyeShnZ3JlcGVsKQpgYGAKCgojIyMgMS4gUmFuZG9tIEZvcmVzdHMgZm9yIENsYXNzaWZpY2F0aW9uCgpXZSBiZWdpbiBieSBsb2FkaW5nIGFuZCBpbnNwZWN0aW5nIHRoZSBgSFJfY29tbWFfc2VwLmNzdmAgZGF0YXNldDoKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CmRhdGFTZXQgPC0gcmVhZC5jc3YocGFzdGUwKGdldHdkKCksICIvX2RhdGEvSFJfY29tbWFfc2VwLmNzdiIpLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpnbGltcHNlKGRhdGFTZXQpCmBgYApUaGUgdGFzayBpcyB0byBwcmVkaWN0IHRoZSB2YWx1ZSBvZiBgbGVmdGAgLSB3aGV0aGVyIHRoZSBlbXBsb3llZSBoYXMgbGVmdCB0aGUgY29tcGFueSBvciBub3QgLSBmcm9tIGEgc2V0IG9mIHByZWRpY3RvcnMgZW5jb21wYXNzaW5nIHRoZSBmb2xsb3dpbmc6CgotICoqc2F0aXNmYWN0aW9uX2xldmVsKio6IGEgbWVhc3VyZSBvZiBlbXBsb3llZSdzIGxldmVsIG9mIHNhdGlzZmFjdGlvbgotICoqbGFzdF9ldmFsdWF0aW9uKio6IHRoZSByZXN1bHQgb2YgYSBsYXN0IGV2YWx1YXRpb24KLSAqKm51bWJlcl9wcm9qZWN0cyoqOiBpbiBob3cgbWFueSBwcm9qZWN0cyBkaWQgdGhlIGVtcGxveWVlIHRvb2sgcGFydAotICoqYXZlcmFnZV9tb250aGx5X2hvdXJzKio6IGhvdyB3b3JraW5nIGhvdXJzIG1vbnRobHkgb24gYXZlcmFnZQotICoqdGltZV9zcGVuZF9jb21wYW55Kio6IGZvciBob3cgbG9uZyBpcyB0aGUgZW1wbG95ZWUgd2l0aCB1cwotICoqV29ya19hY2NpZGVudCoqOiBhbnkgd29yayBhY2NpZGVudHM/Ci0gKipwcm9tb3Rpb25fbGFzdF81eWVhcnMqKjogZGlkIHRoZSBwcm9tb3Rpb24gb2NjdXIgaW4gdGhlIGxhc3QgZml2ZSB5ZWFycz8KLSAqKnNhbGVzKio6IGRlcGFydG1lbnQgKHNhbGVzLCBhY2NvdW50aW5nLCBociwgdGVjaG5pY2FsLCBzdXBwb3J0LCBtYW5hZ2VtZW50LCBJVCwgcHJvZHVjdF9tbmcsIG1hcmtldGluZywgUmFuZEQpCi0gKipzYWxhcnkqKjogc2FsYXJ5IGNsYXNzIChsb3csIG1lZGl1bSwgaGlnaCkKCgojIyMjIDEuMSBSYW5kb20gRm9yZXN0czogdGhlIEFsZ29yaXRobQoKVGhlcmUgdGhyZWUgaW1wb3J0YW50IGNvbmNlcHRzIHRvIHVuZGVyc3RhbmQgaG93IFJhbmRvbSBGb3Jlc3QgYnVpbGRzIHVwb24gRGVjaXNpb24gVHJlZXM6CgohW10oLi4vX2ltZy9TMjFfMDFfUmFuZG9tRm9yZXN0LmpwZWcpCgotICoqQm9vdHN0cmFwIGFnZ3JlZ2F0aW5nIChCYWdnaW5nKS4qKiBXZSBiZWdpbiB3aXRoIHNvbWUgdHJhaW5pbmcgc2V0IGZvciBvdXIgbW9kZWwsICREJC4gQmFnZ2luZyBnZW5lcmF0ZXMgJG0kIG5ldyB0cmFpbmluZyBzZXRzLCAkRF9pJCwgJGkgPSAxLCAyLCAuLi4sIG0kLCBieSByYW5kb21seSBzYW1wbGluZyBmcm9tICREJCB1bmlmb3JtbHkgYW5kICp3aXRoIHJlcGxhY2VtZW50Ki4gVGhlIHNhbXBsZXMgb2J0YWluZWQgaW4gdGhpcyB3YXkgYXJlIGtub3duIGFzICpib290c3RyYXAgc2FtcGxlcyouIEluIFJhbmRvbSBGb3Jlc3RzLCAkbSQgc2ltcGxlciBtb2RlbHMgLSBEZWNpc2lvbiBUcmVlcywgcHJlY2lzZWx5IC0gYXJlIGZpdHRlZCBmb3IgZWFjaCAkRF9pJCBib290c3RyYXAgc2FtcGxlLiAKCi0gKipPdXQtb2YtYmFnIChPT0IpIGVycm9yLioqIEVhY2ggdGltZSBhIG5ldyBib290c3RyYXAgc2FtcGxlICREX2kkIGlzIHByb2R1Y2VkLCBzb21lIGRhdGEgcG9pbnRzIHJlbWFpbiAqb3V0IG9mIHRoZSBiYWcqLCBhcmUgbm90IHVzZWQgaW4gbW9kZWwgdHJhaW5pbmcsIGFuZCBmb3JtIHRoZSAqKk9PQiBzZXQqKiAodGhlICpPdXQtb2YtYmFnIHNldCopLiBUaGUgT09CIGVycm9yIGlzIGEgKnByZWRpY3Rpb24gZXJyb3IqIChyZW1lbWJlciB0aGlzIGNvbmNlcHQgZnJvbSBvdXIgaW50cm9kdWN0aW9uIHRvIGNyb3NzLXZhbGlkYXRpb24/KSB3aGljaCBpcyBjb21wdXRlZCBmcm9tIHRoZSBPT0Igc2V0LCB3aGVyZSB0aGUgcHJlZGljdGlvbiBpcyBvYnRhaW5lZCBieSBhdmVyYWdpbmcgdGhlIHJlc3BvbnNlIChpbiByZWdyZXNzaW9uKSBvciBhcyBhIG1ham9yaXR5IHZvdGUgKGluIGNsYXNzaWZpY2F0aW9uKSBmcm9tIGFsbCB0aGUgdHJlZXMgaW4gdGhlIGZvcmVzdCB0aGF0IHdlcmUgbm90IHRyYWluZWQgb24gdGhhdCBwYXJ0aWN1bGFyIE9PQiBpbnN0YW5jZS4KCi0gKipUaGUgUmFuZG9tIFN1YnNwYWNlIE1ldGhvZCAoRmVhdHVyZSBCYWdnaW5nKS4qKiBUaGUgUmFuZG9tIFN1YnNwYWNlIE1ldGhvZCBpcyBhIG1ldGhvZCB0byBjb250cm9sIGZvciB0aGUgY29tcGxleGl0eSBvZiB0aGUgZGVjaXNpb24gdHJlZXMgZ3Jvd24gaW4gYSBSYW5kb20gRm9yZXN0IG1vZGVsLiAqKk9uIGVhY2ggbmV3IHNwbGl0KiosIG9ubHkgYSByYW5kb21seSBjaG9zZW4gKnN1YnNldCBvZiBwcmVkaWN0b3JzKiBpcyB1c2VkIHRvIGZpbmQgdGhlIG9wdGltYWwgc3BsaXQuIFRoZSBSYW5kb20gRm9yZXN0cyBhbGdvcml0aG0sIGFzIHdlIHdpbGwgc2VlLCBoYXMgYSBjb250cm9sIHBhcmFtZXRlciB0aGF0IGRldGVybWluZXMgaG93IG1hbnkgZmVhdHVyZXMgYXJlIHJhbmRvbWx5IHNlbGVjdGVkIHRvIHByb2R1Y2UgYSBuZXcgc3BsaXQuICAKCgojIyMjIDEuMiBDbGFzc2lmaWNhdGlvbiB3LiB7cmFuZG9tRm9yZXN0fQoKV2Ugd2lsbCBmaXQgYSBSYW5kb20gRm9yZXN0IG1vZGVsIHRvIHRoZSBgSFJfY29tbWFfc2VwLmNzdmAgZGF0YXNldCB3aXRoIGByYW5kb21Gb3Jlc3Q6OnJhbmRvbUZvcmVzdCgpYCBpbiBSLiBJbiBzcGl0ZSBvZiBhbGwgdGhlIHByZWNhdXRpb25hcnkgbWVhc3VyZXMgYWxyZWFkeSB0YWtlbiBpbiB0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbCBpdHNlbGYsIHdlIHdpbGwgc3RpbGwgcGVyZm9ybSBhbiBhZGRpdGlvbmFsICpvdXR0ZXIqIGstZm9sZCBjcm9zcy12YWxpZGF0aW9uIHdpdGggNSBmb2xkczogYmV0dGVyIHNhZmUgdGhhbiBzb3JyeS4gCgpGaXJzdCwgZGVmaW5lIHRoZSBmb2xkczoKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CmRhdGFTZXQkaXggPC0gc2FtcGxlKDE6NSwgZGltKGRhdGFTZXQpWzFdLCByZXBsYWNlID0gVCkKdGFibGUoZGF0YVNldCRpeCkKYGBgClBlcmZvcm0gYSA1LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBmb3IgYSBzZXQgb2YgUmFuZG9tIEZvcmVzdHMgYWNyb3NzIHRoZSBmb2xsb3dpbmcgY29udHJvbCBwYXJhbWV0ZXJzOgoKLSAqKm50cmVlOioqIHRoZSBudW1iZXIgb2YgdHJlZXMgdG8gZ3JvdwotICoqbXRyeToqKiB0aGUgbnVtYmVyIG9mIHZhcmlhYmxlcyB0byByYW5kb21seSBzYW1wbGUgYXMgY2FuZGlkYXRlcyBhdCBlYWNoIHNwbGl0ICh0byBjb250cm9sIEZlYXR1cmUgQmFnZ2luZykKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9Cm50cmVlIDwtIHNlcSgyNTAsIDEwMDAsIGJ5ID0gMjUwKQptdHJ5IDwtIDM6KGRpbShkYXRhU2V0KVsyXS0yKQojIC0gbXRyeTogd2UgZG8gbm90IGNvdW50IGBsZWZ0YCB3aGljaCBpcyBhbiBvdXRjb21lCiMgLSBhbmQgYGl4YCB3aGljaCBpcyBhIGZvbGQgaW5kZXgsIHNvIHdlIGhhdmUKIyAtIGRpbShkYXRhU2V0KVsyXS0yIHByZWRpY3RvcnMgaW4gdGhlIG1vZGVsLgojIC0gc3RhcnQgdGltZXI6CnRzdGFydCA8LSBTeXMudGltZSgpCgojIC0gaXRlcmF0ZToKIyAtIGxhcHBseSgpIGFjcm9zcyBudHJlZQpyZk1vZGVscyA8LSBsYXBwbHkobnRyZWUsIGZ1bmN0aW9uKG50KSB7CiAgCiAgIyAtIGxhcHBseSgpIGFjcm9zcyBtdHJ5CiAgbXRyeWN2IDwtIGxhcHBseShtdHJ5LCBmdW5jdGlvbihtdCkgewogICAgCiAgICAjIC0gbGFwcGx5KCkgYWNyb3NzIGZvbGRzCiAgICBjdiA8LSBsYXBwbHkodW5pcXVlKGRhdGFTZXQkaXgpLCBmdW5jdGlvbihmb2xkKSB7CiAgICAgIAogICAgICAjIC0gc3BsaXQgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cwogICAgICB0ZXN0SXggPC0gZm9sZAogICAgICB0cmFpbkl4IDwtIHNldGRpZmYoMTo1LCB0ZXN0SXgpCiAgICAgIHRyYWluU2V0IDwtIGRhdGFTZXQgJT4lIAogICAgICAgIGRwbHlyOjpmaWx0ZXIoaXggJWluJSB0cmFpbkl4KSAlPiUgCiAgICAgICAgZHBseXI6OnNlbGVjdCgtaXgpCiAgICAgIHRlc3RTZXQgPC0gZGF0YVNldCAlPiUgCiAgICAgICAgZHBseXI6OmZpbHRlcihpeCAlaW4lIHRlc3RJeCkgJT4lCiAgICAgICAgZHBseXI6OnNlbGVjdCgtaXgpCiAgICAgIAogICAgICAjIC0gYGxlZnRgIHRvIGZhY3RvciBmb3IgY2xhc3NpZmljYXRpb24gCiAgICAgICMgLSAgdy4gcmFuZG9tRm9yZXN0KCkKICAgICAgdHJhaW5TZXQkbGVmdCA8LSBhcy5mYWN0b3IodHJhaW5TZXQkbGVmdCkKICAgICAgdGVzdFNldCRsZWZ0IDwtIGFzLmZhY3Rvcih0ZXN0U2V0JGxlZnQpCiAgICAgIAogICAgICAjIC0gUmFuZG9tIEZvcmVzdDoKICAgICAgbW9kZWwgPC0gcmFuZG9tRm9yZXN0OjpyYW5kb21Gb3Jlc3QoZm9ybXVsYSA9IGxlZnQgfiAuLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5TZXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IG50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gbXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAjIC0gUk9DIGFuYWx5c2lzOgogICAgICBwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gdGVzdFNldCkKICAgICAgaGl0IDwtIHN1bShpZmVsc2UocHJlZGljdGlvbnMgPT0gMSAmIHRlc3RTZXQkbGVmdCA9PSAxLCAxLCAwKSkKICAgICAgaGl0IDwtIGhpdC9zdW0odGVzdFNldCRsZWZ0ID09IDEpCiAgICAgIGZhIDwtIHN1bShpZmVsc2UocHJlZGljdGlvbnMgPT0gMSAmIHRlc3RTZXQkbGVmdCA9PSAwLCAxLCAwKSkKICAgICAgZmEgPC0gZmEvc3VtKHRlc3RTZXQkbGVmdCA9PSAwKQogICAgICBhY2MgPC0gc3VtKHByZWRpY3Rpb25zID09IHRlc3RTZXQkbGVmdCkKICAgICAgYWNjIDwtIGFjYy9sZW5ndGgodGVzdFNldCRsZWZ0KQogICAgICAjIC0gUk9DIG91dHB1dDoKICAgICAgcmV0dXJuKAogICAgICAgIGRhdGEuZnJhbWUoZm9sZCwgaGl0LCBmYSwgYWNjKQogICAgICApCiAgICB9KQogICAgCiAgICAjIC0gY29sbGVjdCByZXN1bHRzIGZyb20gYWxsIGZvbGRzOgogICAgY3YgPC0gZGF0YS50YWJsZTo6cmJpbmRsaXN0KGN2KQogICAgCiAgICAjIC0gYXZlcmFnZSBST0M6CiAgICBhdmdfcm9jIDwtIGRhdGEuZnJhbWUoaGl0ID0gbWVhbihjdiRoaXQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGZhID0gbWVhbihjdiRmYSksIAogICAgICAgICAgICAgICAgICAgICAgICAgIGFjYyA9IG1lYW4oY3YkYWNjKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gbXQpCiAgICByZXR1cm4oYXZnX3JvYykKICB9KQogIAogICMgLSBjb2xsZWN0IGZyb20gYWxsIG10cnkgdmFsdWVzOgogIG10cnljdiA8LSByYmluZGxpc3QobXRyeWN2KQogICMgLSBhZGQgbnRyZWUgYW5kIG91dDoKICBtdHJ5Y3YkbnRyZWUgPC0gbnQKICByZXR1cm4obXRyeWN2KQogIAp9KQoKIyAtIGNvbGxlY3QgYWxsIHJlc3VsdHMKcmZNb2RlbHMgPC0gcmJpbmRsaXN0KHJmTW9kZWxzKQp3cml0ZS5jc3YocmZNb2RlbHMsIAogICAgICAgICAgcGFzdGUwKGdldHdkKCksICIvcmZNb2RlbHMuY3N2IikpCiMgLSBSZXBvcnQgdGltaW5nOgpwcmludChwYXN0ZTAoIlRoZSBlc3RpbWF0aW9uIHRvb2s6ICIsIAogICAgICAgICAgICAgZGlmZnRpbWUoU3lzLnRpbWUoKSwgdHN0YXJ0LCB1bml0cyA9ICJtaW5zIiksIAogICAgICAgICAgICAgIiBtaW51dGVzLiIpKQpgYGAKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CnByaW50KHJmTW9kZWxzKQpgYGAKCkxldCdzIGluc3BlY3QgdGhlIENWIHJlc3VsdHMgdmlzdWFsbHkuIEFjY3VyYWN5IGZpcnN0OgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0KcmZNb2RlbHMkbnRyZWUgPC0gZmFjdG9yKHJmTW9kZWxzJG50cmVlKQpyZk1vZGVscyRtdHJ5IDwtIGZhY3RvcihyZk1vZGVscyRtdHJ5KQpnZ3Bsb3QoZGF0YSA9IHJmTW9kZWxzLCAKICAgICAgIGFlcyh4ID0gbXRyeSwKICAgICAgICAgICB5ID0gYWNjLCAKICAgICAgICAgICBncm91cCA9IG50cmVlLCAKICAgICAgICAgICBjb2xvciA9IG50cmVlLAogICAgICAgICAgIGZpbGwgPSBudHJlZSwKICAgICAgICAgICBsYWJlbCA9IHJvdW5kKGFjYywgMikpCiAgICAgICApICsKICBnZW9tX3BhdGgoc2l6ZSA9IC4yNSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLjUpICsgCiAgZ2d0aXRsZSgiUmFuZG9tIEZvcmVzdHMgQ1Y6IEFjY3VyYWN5IikgKwogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUsIHNpemUgPSA4KSkKYGBgCgpIaXQgcmF0ZToKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CmdncGxvdChkYXRhID0gcmZNb2RlbHMsIAogICAgICAgYWVzKHggPSBtdHJ5LAogICAgICAgICAgIHkgPSBoaXQsIAogICAgICAgICAgIGdyb3VwID0gbnRyZWUsIAogICAgICAgICAgIGNvbG9yID0gbnRyZWUsCiAgICAgICAgICAgZmlsbCA9IG50cmVlLAogICAgICAgICAgIGxhYmVsID0gcm91bmQoYWNjLCAyKSkKICAgICAgICkgKwogIGdlb21fcGF0aChzaXplID0gLjI1KSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNSkgKyAKICBnZ3RpdGxlKCJSYW5kb20gRm9yZXN0cyBDVjogSGl0IFJhdGUiKSArCiAgdGhlbWVfYncoKSArIAogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSwgc2l6ZSA9IDgpKQpgYGAKRmFsc2UgQWxhcm0gcmF0ZToKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CmdncGxvdChkYXRhID0gcmZNb2RlbHMsIAogICAgICAgYWVzKHggPSBtdHJ5LAogICAgICAgICAgIHkgPSBmYSwgCiAgICAgICAgICAgZ3JvdXAgPSBudHJlZSwgCiAgICAgICAgICAgY29sb3IgPSBudHJlZSwKICAgICAgICAgICBmaWxsID0gbnRyZWUsCiAgICAgICAgICAgbGFiZWwgPSByb3VuZChhY2MsIDIpKQogICAgICAgKSArCiAgZ2VvbV9wYXRoKHNpemUgPSAuMjUpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMS41KSArIAogIGdndGl0bGUoIlJhbmRvbSBGb3Jlc3RzIENWOiBGQSBSYXRlIikgKwogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUsIHNpemUgPSA4KSkKYGBgCgpBbmQgcGljayB0aGUgYmVzdCBtb2RlbCBmcm9tIFJPQzoKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CnJmTW9kZWxzJGRpZmYgPC0gcmZNb2RlbHMkaGl0IC0gcmZNb2RlbHMkZmEKd19iZXN0IDwtIHdoaWNoLm1heChyZk1vZGVscyRkaWZmKQpyZk1vZGVsc1t3X2Jlc3QsIF0KYGBgCgpBbmQgd2UgY2FuIHN0aWxsIHJlZmluZSB0aGUgY2hvc2VuIG1vZGVsOgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0KZGF0YVNldCA8LSBkYXRhU2V0ICU+JSAKICBkcGx5cjo6c2VsZWN0KC1peCkKZGF0YVNldCRsZWZ0IDwtIGZhY3RvcihkYXRhU2V0JGxlZnQpCm9wdGltYWxfbW9kZWwgPC0gcmFuZG9tRm9yZXN0OjpyYW5kb21Gb3Jlc3QoZm9ybXVsYSA9IGxlZnQgfiAuLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRhU2V0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gMjUwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSA0KQpvcHRpbWFsX21vZGVsJGltcG9ydGFuY2UKYGBgCgpUaGUgY3VtdWxhdGl2ZSBPT0IgZXJyb3IgdXAgdG8gdGhlIGktdGggdHJlZSBjYW4gYmUgZm91bmQgaW4gdGhlIGZpcnN0IGNvbHVtbiBvZiB0aGUgYG9wdGltYWxfbW9kZWwkZXJyLnJhdGVgIGZpZWxkOgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0KaGVhZChvcHRpbWFsX21vZGVsJGVyci5yYXRlKQpgYGAKCkxldCdzIHRha2UgYSBjbG9zZXIgbG9vayBhdCBpdDoKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9Cm9vYkZyYW1lIDwtIGFzLmRhdGEuZnJhbWUob3B0aW1hbF9tb2RlbCRlcnIucmF0ZSkKb29iRnJhbWUkbnRyZWUgPC0gMTpkaW0ob29iRnJhbWVbMV0pCmdncGxvdChkYXRhID0gb29iRnJhbWUsIAogICAgICAgYWVzKHggPSBudHJlZSwKICAgICAgICAgICB5ID0gT09CKSkgKyAKICBnZW9tX3BhdGgoc2l6ZSA9IC4yNSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLjUpICsgCiAgZ2d0aXRsZSgiQ3VtdWxhdGl2ZSBPT0IgZXJyb3IgZnJvbSB0aGUgQ1Ygb3B0aW1hbCBSYW5kb20gRm9yZXN0IG1vZGVsIikgKwogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUsIHNpemUgPSA4KSkKYGBgCgpXZSBjYW4gb2JzZXJ2ZSBob3cgdGhlIGN1bXVsYXRpdmUgT09CIGVycm9yIHN0YWJpbGl6ZXMgZm9sbG93aW5nIHRoZSBncm93dGggb2YgYSBjZXJ0YWluIG51bWJlciBvZiB0cmVlcyBpbiB0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbDoKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CndfdHJlZSA8LSB3aGljaC5taW4ob29iRnJhbWUkT09CKQpwcmludCh3X3RyZWUpCmBgYAoKQW5kIGZpbmFsbHk6CgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQpvcHRpbWFsX21vZGVsIDwtIHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KGZvcm11bGEgPSBsZWZ0IH4gLiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0YVNldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IHdfdHJlZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gNAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKcHJlZGljdGlvbnMgPC0gcHJlZGljdChvcHRpbWFsX21vZGVsLCAKICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gZGF0YVNldCkKaGl0IDwtIHN1bShpZmVsc2UocHJlZGljdGlvbnMgPT0gMSAmIGRhdGFTZXQkbGVmdCA9PSAxLCAxLCAwKSkKaGl0IDwtIGhpdC9zdW0oZGF0YVNldCRsZWZ0ID09IDEpCmZhIDwtIHN1bShpZmVsc2UocHJlZGljdGlvbnMgPT0gMSAmIGRhdGFTZXQkbGVmdCA9PSAwLCAxLCAwKSkKZmEgPC0gZmEvc3VtKGRhdGFTZXQkbGVmdCA9PSAwKQphY2MgPC0gc3VtKHByZWRpY3Rpb25zID09IGRhdGFTZXQkbGVmdCkKYWNjIDwtIGFjYy9sZW5ndGgoZGF0YVNldCRsZWZ0KQpwcmludChwYXN0ZTAoIkFjY3VyYWN5OiAiLCBhY2MsICI7IEhpdCByYXRlOiAiLCBoaXQsICI7IEZBIHJhdGU6ICIsIGZhKSkKYGBgCgojIyMgMi4gUmFuZG9tIEZvcmVzdHMgZm9yIFJlZ3Jlc3Npb24KCkluIHtyYW5kb21Gb3Jlc3R9LCB0byBvYnRhaW4gdGhlIFJhbmRvbSBGb3Jlc3QgbW9kZWwgZm9yIHJlZ3Jlc3Npb24gKipzaW1wbHkgZG8gbm90IHByb25vdW5jZSBhbiBvdXRjb21lIHRvIGJlIGEgZmFjdG9yKiouCldlIHdpbGwgdXNlIHRoZSBCb3N0b24gSG91c2luZyBkYXRhc2V0IHRvIGRlbW9uc3RyYXRlLgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0KZGF0YVNldCA8LSByZWFkLmNzdihwYXN0ZTAoJ19kYXRhLycsICdCb3N0b25Ib3VzaW5nLmNzdicpLCAKICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCAKICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmhlYWQoZGF0YVNldCkKYGBgCgpIZXJlIGFyZSB0aGUgdmFyaWFibGVzOgoKKyAqKmNyaW0qKjogcGVyIGNhcGl0YSBjcmltZSByYXRlIGJ5IHRvd24KKyAqKnpuKio6IHByb3BvcnRpb24gb2YgcmVzaWRlbnRpYWwgbGFuZCB6b25lZCBmb3IgbG90cyBvdmVyIDI1LDAwMCBzcS5mdC4KKyAqKmluZHVzKio6IHByb3BvcnRpb24gb2Ygbm9uLXJldGFpbCBidXNpbmVzcyBhY3JlcyBwZXIgdG93bi4KKyAqKmNoYXMqKjogQ2hhcmxlcyBSaXZlciBkdW1teSB2YXJpYWJsZSAoMSBpZiB0cmFjdCBib3VuZHMgcml2ZXI7IDAgb3RoZXJ3aXNlKQorICoqbm94Kio6IG5pdHJpYyBveGlkZXMgY29uY2VudHJhdGlvbiAocGFydHMgcGVyIDEwIG1pbGxpb24pCisgKipybSoqOiBhdmVyYWdlIG51bWJlciBvZiByb29tcyBwZXIgZHdlbGxpbmcKKyAqKmFnZSoqOiBwcm9wb3J0aW9uIG9mIG93bmVyLW9jY3VwaWVkIHVuaXRzIGJ1aWx0IHByaW9yIHRvIDE5NDAKKyAqKmRpcyoqOiB3ZWlnaHRlZCBkaXN0YW5jZXMgdG8gZml2ZSBCb3N0b24gZW1wbG95bWVudCBjZW50ZXJzCisgKipyYWQqKjogaW5kZXggb2YgYWNjZXNzaWJpbGl0eSB0byByYWRpYWwgaGlnaHdheXMKKyAqKnRheCoqOiBmdWxsLXZhbHVlIHByb3BlcnR5LXRheCByYXRlIHBlciAkMTAsMDAwCisgKipwdHJhdGlvKio6IHB1cGlsLXRlYWNoZXIgcmF0aW8gYnkgdG93bgorICoqYioqOiAxMDAwKEJrIC0gMC42MyleMiB3aGVyZSBCayBpcyB0aGUgcHJvcG9ydGlvbiBvZiBibGFja3MgYnkgdG93bgorICoqbHN0YXQqKjogJSBsb3dlciBzdGF0dXMgb2YgdGhlIHBvcHVsYXRpb24KKyAqKm1lZHYqKjogTWVkaWFuIHZhbHVlIG9mIG93bmVyLW9jY3VwaWVkIGhvbWVzIGluICQxMDAwJ3MKClRoZSBgbWVkdmAgdmFyaWFibGUgaXMgdGhlICpvdXRjb21lKi4KClJhbmRvbSBGb3Jlc3QgKG5vIGNyb3NzLXZhbGlkYXRpb24gdGhpcyB0aW1lKToKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CnJmUmVnTXNvZGVsIDwtIHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KGZvcm11bGEgPSBtZWR2IH4gLiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFTZXQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gMTAwMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyeSA9IDcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQpgYGAKCldlIGNhbiB1c2UgdGhlIGdlbmVyaWMgYHBsb3QoKWAgZnVuY3Rpb24gdG8gYXNzZXNzIGhvdyB0aGUgTVNFIGNoYW5nZXMgYWNyb3NzIGBudHJlZWA6CgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQpwbG90KHJmUmVnTXNvZGVsKQpgYGAKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CnByZWRpY3Rpb25zIDwtIHByZWRpY3QocmZSZWdNc29kZWwsIAogICAgICAgICAgICAgICAgICAgICAgIG5ld2RhdGEgPSBkYXRhU2V0KQpwcmVkaWN0RnJhbWUgPC0gZGF0YS5mcmFtZShwcmVkaWN0ZWRfbWVkdiA9IHByZWRpY3Rpb25zLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgb2JzZXJ2ZWRfbWVkdiA9IGRhdGFTZXQkbWVkdikKZ2dwbG90KGRhdGEgPSBwcmVkaWN0RnJhbWUsIAogICAgICAgYWVzKHggPSBwcmVkaWN0ZWRfbWVkdiwKICAgICAgICAgICB5ID0gb2JzZXJ2ZWRfbWVkdikpICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2l6ZSA9IC4yNSwgY29sb3IgPSAicmVkIikgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLjUsIGNvbG9yID0gImJsYWNrIikgKyAKICBnZW9tX3BvaW50KHNpemUgPSAuNzUsIGNvbG9yID0gIndoaXRlIikgKwogIGdndGl0bGUoIlJhbmRvbSBGb3Jlc3QgaW4gUmVncmVzc2lvbjogT2JzZXJ2ZWQgdnMgUHJlZGljdGVkXG5Cb3N0b24gSG91c2luZyBEYXRhc2V0IikgKyAKICB0aGVtZV9idygpICsgCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IC41LCBzaXplID0gOCkpCmBgYAoKIyMjIDMuIEEgRGVjaXNpb24gVHJlZSBmcm9tIHNjcmF0Y2gKCioqTi5CLioqIFdlIGFyZSBpbnRlcmVzdGVkIGluIHByaW5jaXBsZXMgb24gd2hpY2ggdGhlIHNwbGl0IGlzIGRlY2lkZWQgaW4gYSBEZWNpc2lvbiBUcmVlLiBUaGUgZm9sbG93aW5nIGFsZ29yaXRobSBpcyBuZWl0aGVyIGEgY29tcGxldGUgRGVjaXNpb24gVHJlZSBhbGdvcml0aG0gbm9yIGEgZnVsbHkgY29ycmVjdCBpbXBsZW1lbnRhdGlvbiBhdCBhbGwuIEZvciBleGFtcGxlLCB3ZSBkbyBub3QgZGVhbCB3aXRoIGludGVybmFsIGNyb3NzLXZhbGlkYXRpb24gYWNyb3NzICRcYWxwaGEkIC0gdGhlIGNvbXBsZXhpdHkgcGFyYW1ldGVyIC0gYXQgYWxsLiBTZWNvbmQsIGZvciByZWFzb25zIG9mIHNpbXBsaWNpdHksIHdlIGNvbnNpZGVyIG9ubHkgYmluYXJ5IGNhdGVnb3JpY2FsIGZlYXR1cmVzLiBUaGUgYWltIGlzIHRvIGRlbW9uc3RyYXRlIGhvdyBHaW5pIGFuZCBNU0UgYXJlIHVzZWQgdG8gY29tcGFyZSBjYW5kaWRhdGUgc3BsaXRzIGFjcm9zcyBjYXRlZ29yaWNhbCBhbmQgbnVtZXJpY2FsIGZlYXR1cmVzLCByZXNwZWN0aXZlbHkuCgpTdGVwIG9uZTogc2V0dXAgKyBsb2FkIFRpdGFuaWMgZGF0YS4KCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CiMjIyAtLS0gc2V0dXAKbGlicmFyeShycGFydCkKbGlicmFyeShycGFydC5wbG90KQoKIyMjIC0tLSBUaXRhbmljIGRhdGFzZXQKZGF0YShUaXRhbmljKQpsaWJyYXJ5KGNhckRhdGEpCmRhdGFfc2V0IDwtIFRpdGFuaWNTdXJ2aXZhbApkYXRhX3NldCA8LSBkYXRhX3NldFtjb21wbGV0ZS5jYXNlcyhkYXRhX3NldCksIF0KaGVhZChkYXRhX3NldCwgMjApCmBgYAoKTGV0J3Mgbm93IHRyYWluIGEgRGVjaXNpb24gVHJlZSB0byBwcmVkaWN0IGBzdXJ2aXZlZGAgdy4ge3JwYXJ0fToKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CiMjIyAtLS0gZGVjaXNpb24gdHJlZSBtb2RlbCB3LiBycGFydApkZWNfdHJlZSA8LSBycGFydChzdXJ2aXZlZCB+IC4sCiAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRhX3NldCkKcnBhcnQucGxvdChkZWNfdHJlZSkKCiMgLSBwcmVkaWN0aW9ucwpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGRlY190cmVlLCAKICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gZGF0YV9zZXQsIAogICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAicHJvYiIpCnByZWRpY3Rpb25zIDwtIGlmZWxzZShwcmVkaWN0aW9uc1ssIDFdIDw9IHByZWRpY3Rpb25zWywgMl0sCiAgICAgICAgICAgICAgICAgICAgICJ5ZXMiLAogICAgICAgICAgICAgICAgICAgICAibm8iKQoKIyAtIGVsZW1lbnRhcnkgUk9DIGFuYWx5c2lzCmFjYyA8LSBzdW0ocHJlZGljdGlvbnMgPT0gZGF0YV9zZXQkc3Vydml2ZWQpL2xlbmd0aChkYXRhX3NldCRzdXJ2aXZlZCkKcHJpbnQoYWNjKQpoaXQgPC0gc3VtKHByZWRpY3Rpb25zID09ICJ5ZXMiICYgZGF0YV9zZXQkc3Vydml2ZWQgPT0gInllcyIpL3N1bShkYXRhX3NldCRzdXJ2aXZlZCA9PSAieWVzIikKcHJpbnQoaGl0KQpmYSA8LSBzdW0ocHJlZGljdGlvbnMgPT0gInllcyIgJiBkYXRhX3NldCRzdXJ2aXZlZCA9PSAibm8iKS9zdW0oZGF0YV9zZXQkc3Vydml2ZWQgPT0gIm5vIikKcHJpbnQoZmEpCmBgYAoqKlEuKiogSG93IGlzIGEgc3BsaXQgZGVjaWRlZD8gRmlyc3Qgd2Ugd2lsbCBkZXZlbG9wIGEgZnVuY3Rpb24gdG8gY29tcHV0ZSBHaW5pIEltcHVyaXR5LiBXZSB3aWxsIHVzZSB0aGUgYG5vZGVzYCBkYXRhLmZyYW1lIHRvIHRlc3Q6CgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQojIC0gbm9kZXMgZGF0YS5mcmFtZSB0byB0ZXN0IHdpdGg6Cm5vZGVzIDwtIGRhdGEuZnJhbWUoY2xhc3NfY291bnRzID0gYygxNDIsIDIzMikpCnByaW50KG5vZGVzKQpgYGAKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CiMjIyAtLS0gRmlyc3Q6IGEgZnVuY3Rpb24gdG8gY29tcHV0ZSBHaW5pIEltcHVyaXR5CiMgLSBmb3IgY2xhc3NpZmljYXRpb24gdHJlZXMKIyAtIEdpbmkgSW1wdXJpdHkKIyAtIE4uQi4gSW1wbGVtZW50IEdpbmkgYXMgCiMgLSAjIC0gcDEqKDEtcDEpICsgcDIqKDEtcDIpID09IHAxIC0gcDFeMiArIHAyIC0gcDJeMiA9IChwMSArIHAyKSAtIChwMV4yK3AyXjIpID0gMS1zdW0ocF4yKQpnaW5pX2JpbmFyeSA8LSBmdW5jdGlvbihub2RlcykgewogIAogICMgLSBwcm9iYWJpbGl0aWVzCiAgcCA8LSBub2RlcyRjbGFzc19jb3VudHMvc3VtKG5vZGVzJGNsYXNzX2NvdW50cykKCiAgIyAtIEdpbmkgSW1wdXJpdHkKICBnaW5pIDwtIDEgLSBzdW0ocF4yKQogIAogIHJldHVybihnaW5pKQogIAp9CgpnaW5pX2JpbmFyeShub2RlcykKYGBgCgpOb3cgdGhlIGNvbXBsaWNhdGVkIHBhcnQ6IGEgZnVuY3Rpb24sIGBhdHRyaWJ1dGVfc3BsaXQoKWAsIHRvIGRlY2lkZSBhIHNwbGl0IGZvciBib3RoIG51bWVyaWNhbCBhbmQgY2F0ZWdvcmljYWwgcHJlZGljdG9ycy4KCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CmF0dHJpYnV0ZV9zcGxpdCA8LSBmdW5jdGlvbihwYXJhbXMpIHsKICAKICAjIC0gaW5wdXQKICAjIC0gdGhlIGRhdGE6CiAgZGF0YSA8LSBwYXJhbXNbWzFdXQogICMgLSB0aGUgYXR0cmlidXRlIChmZWF0dXJlLCBwcmVkaWN0b3IpIHRoYXQgd2Ugd2lzaCB0byB0ZXN0CiAgYXR0cmlidXRlIDwtIHBhcmFtc1tbMl1dCiAgIyAtIHRoZSBvdXRjb21lIHZhcmlhYmxlIAogIHRhcmdldCA8LSBwYXJhbXNbWzNdXQoKICAjIC0gdHlwZSBvZiBhdHRyaWJ1dGUKICBhdHQgPC0gd2hpY2goY29sbmFtZXMoZGF0YSkgPT0gYXR0cmlidXRlKQogIGF0eXBlIDwtIGNsYXNzKGRhdGFbLCBhdHRdKQogIAogICMgLSBzd2l0Y2ggY2F0ZWdvcmljYWwgYW5kIGNvbnRpbnVvdXMKICBpZiAoYXR5cGUgPT0gImZhY3RvciIpIHsKICAgIAogICAgIyAtIENBVEVHT1JJQ0FMCiAgICAKICAgICMgLSBjYXJkaW5hbGl0eQogICAgYWNhcmQgPC0gbGVuZ3RoKHVuaXF1ZShkYXRhWywgYXR0XSkpCiAgCiAgICAjIC0gcG9zc2libGUgc3BsaXRzCiAgICBkZXNpZ24gPC0gY29tYm4odW5pcXVlKGRhdGFbLCBhdHRdKSwgMikKICAgIGRlc2lnbiA8LSBtYXRyaXgoYXMuY2hhcmFjdGVyKGRlc2lnbiksCiAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSBhY2FyZCkKCiAgICAjIC0gSWYgY2FyZGluYWxpdHkgPT0gMgogICAgaWYgKGFjYXJkID09IDIpIHsKICAgICAgCiAgICAgICMgLSBsZWZ0CiAgICAgIHdkIDwtIHdoaWNoKGRhdGFbLCBhdHRdID09IGRlc2lnblssIDFdKQogICAgICBkIDwtIGRhdGFbd2QsIHRhcmdldF0KICAgICAgbjEgPC0gbGVuZ3RoKGQpCiAgICAgIHRkIDwtIGFzLm51bWVyaWModGFibGUoZCkpCiAgICAgIHRkIDwtIGRhdGEuZnJhbWUoY2xhc3NfY291bnRzID0gdGQpCiAgICAgIGdpbmlfc3BsaXRfMSA8LSBnaW5pX2JpbmFyeSh0ZCkKICAgICAgIyAtIHJpZ2h0CiAgICAgIHdkIDwtIHdoaWNoKGRhdGFbLCBhdHRdID09IGRlc2lnblssIDJdKQogICAgICBkIDwtIGRhdGFbd2QsIHRhcmdldF0KICAgICAgbjIgPC0gbGVuZ3RoKGQpCiAgICAgIHRkIDwtIGFzLm51bWVyaWModGFibGUoZCkpCiAgICAgIHRkIDwtIGRhdGEuZnJhbWUoY2xhc3NfY291bnRzID0gdGQpCiAgICAgIGdpbmlfc3BsaXRfMiA8LSBnaW5pX2JpbmFyeSh0ZCkKICAgICAgIyAtIEdpbmkKICAgICAgbiA8LSBuMSArIG4yCiAgICAgIGcgPC0gbjEvbipnaW5pX3NwbGl0XzEgKyBuMi9uKmdpbmlfc3BsaXRfMgoKICAgICAgIyAtIG91dHB1dAogICAgICByZXR1cm4obGlzdChzcGxpdHMgPSBkZXNpZ24sCiAgICAgICAgICAgICAgICAgIGdpbmkgPSBnKSkKICAgICAgCiAgICAjIC0gSWYgY2FyZGluYWxpdHkgPiAyICAKICAgIH0gZWxzZSB7CiAgICAgIAogICAgICAjIC0gZ2luaSBmb3IgYWxsIHBvc3NpYmxlIHNwbGl0cwogICAgICBnIDwtIGFwcGx5KGRlc2lnbiwgMiwgZnVuY3Rpb24oeCkgewogICAgICAgICMgLSBsZWZ0CiAgICAgICAgd2QgPC0gd2hpY2goZGF0YVssIGF0dF0gJWluJSB4KQogICAgICAgIGQgPC0gZGF0YVt3ZCwgdGFyZ2V0XQogICAgICAgIG4xIDwtIGxlbmd0aChkKQogICAgICAgIHRkIDwtIGFzLm51bWVyaWModGFibGUoZCkpCiAgICAgICAgdGQgPC0gZGF0YS5mcmFtZShjbGFzc19jb3VudHMgPSB0ZCkKICAgICAgICBnaW5pX3NwbGl0XzEgPC0gZ2luaV9iaW5hcnkodGQpCiAgICAgICAgIyAtIHJpZ2h0CiAgICAgICAgd2QgPC0gd2hpY2goIShkYXRhWywgYXR0XSAlaW4lIHgpKQogICAgICAgIGQgPC0gZGF0YVt3ZCwgdGFyZ2V0XQogICAgICAgIG4yIDwtIGxlbmd0aChkKQogICAgICAgIHRkIDwtIGFzLm51bWVyaWModGFibGUoZCkpCiAgICAgICAgdGQgPC0gZGF0YS5mcmFtZShjbGFzc19jb3VudHMgPSB0ZCkKICAgICAgICBnaW5pX3NwbGl0XzIgPC0gZ2luaV9iaW5hcnkodGQpCiAgICAgICAgIyAtIEdpbmkKICAgICAgICBuIDwtIG4xICsgbjIKICAgICAgICByZXR1cm4objEvbipnaW5pX3NwbGl0XzEgKyBuMi9uKmdpbmlfc3BsaXRfMikKICAgICAgfSkKICAgICAgCiAgICAgICMgLSBvdXRwdXQKICAgICAgcmV0dXJuKGxpc3Qoc3BsaXRzID0gZGVzaWduLAogICAgICAgICAgICAgICAgICBnaW5pID0gZykpCiAgICAgIAogICAgfQogICAgCiAgfSBlbHNlIHsKICAgIAogICAgIyAtIENPTlRJTlVPVVMKICAgICMgLSBub3RlOiBpZiBDT05USU5VT1VTCiAgICAjIC0gd2UgbWVhc3VyZSBNU0UKICAgIAogICAgIyAtIGxvd2VyIGFuZCB1cHBlciBib3VuZHMKICAgIGxvd2VyX2IgPC0gbWluKGRhdGFbLCBhdHRdKSArIDEKICAgIHVwcGVyX2IgPC0gbWF4KGRhdGFbLCBhdHRdKSAtIDEKICAgIAogICAgIyAtIG9iamVjdGl2ZQogICAgbWluX2dpbmkgPC0gZnVuY3Rpb24oc3BsaXQpIHsKICAgICAgCiAgICAgICMgLSByaWdodAogICAgICB3ZCA8LSB3aGljaChkYXRhWywgYXR0XSA+IHNwbGl0KQogICAgICBkIDwtIGRhdGFbd2QsIHRhcmdldF0KICAgICAgbjEgPC0gbGVuZ3RoKGQpCiAgICAgIHRkIDwtIGFzLm51bWVyaWModGFibGUoZCkpCiAgICAgIHRkIDwtIGRhdGEuZnJhbWUoY2xhc3NfY291bnRzID0gdGQpCiAgICAgIGdpbmlfc3BsaXRfMSA8LSBnaW5pX2JpbmFyeSh0ZCkKICAgICAgIyAtIGxlZnQKICAgICAgd2QgPC0gd2hpY2goZGF0YVssIGF0dF0gPD0gc3BsaXQpCiAgICAgIGQgPC0gZGF0YVt3ZCwgdGFyZ2V0XQogICAgICBuMiA8LSBsZW5ndGgoZCkKICAgICAgdGQgPC0gYXMubnVtZXJpYyh0YWJsZShkKSkKICAgICAgdGQgPC0gZGF0YS5mcmFtZShjbGFzc19jb3VudHMgPSB0ZCkKICAgICAgZ2luaV9zcGxpdF8yIDwtIGdpbmlfYmluYXJ5KHRkKQogICAgICAKICAgICAgbiA8LSBuMSArIG4yCiAgICAgIHJldHVybihuMS9uKmdpbmlfc3BsaXRfMSArIG4yL24qZ2luaV9zcGxpdF8yKQogICAgICAKICAgIH0KICAgIAogICAgIyAtIG9wdGltaXphdGlvbjogZ3JpZC1zZWFyY2gKICAgIGMgPC0gc2VxKGxvd2VyX2IsIHVwcGVyX2IsIGJ5ID0gLjEpCiAgICBnaW5pZXMgPC0gc2FwcGx5KGMsIG1pbl9naW5pKQogICAgCiAgICAjIC0gb3V0cHV0CiAgICByZXR1cm4obGlzdChzcGxpdCA9IGNbd2hpY2gubWluKGdpbmllcylbMV1dLAogICAgICAgICAgICAgICAgZ2luaSA9IGdpbmllc1t3aGljaC5taW4oZ2luaWVzKV0pKQogIAogIH0KICAKfQpgYGAKClRlc3Q6IGZpcnN0IHNwbGl0LgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0KIyAtIGZpc3Qgc3BsaXQ6CnBhcmFtcyA8LSBsaXN0KGRhdGFfc2V0LCAic2V4IiwgInN1cnZpdmVkIikKYXR0cmlidXRlX3NwbGl0KHBhcmFtcykKcGFyYW1zIDwtIGxpc3QoZGF0YV9zZXQsICJwYXNzZW5nZXJDbGFzcyIsICJzdXJ2aXZlZCIpCmF0dHJpYnV0ZV9zcGxpdChwYXJhbXMpCnBhcmFtcyA8LSBsaXN0KGRhdGFfc2V0LCAiYWdlIiwgInN1cnZpdmVkIikKYXR0cmlidXRlX3NwbGl0KHBhcmFtcykKYGBgClNwbGl0IGF0IGBzZXggPT0gIm1hbGUiYDoKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CiMgLSBzcGxpdCBhdCBgc2V4ID09ICJtYWxlImA6CnBhcmFtcyA8LSBsaXN0KGRhdGFfc2V0W2RhdGFfc2V0JHNleD09Im1hbGUiLCBdLCAiYWdlIiwgInN1cnZpdmVkIikKYXR0cmlidXRlX3NwbGl0KHBhcmFtcykKYGBgCgpTcGxpdCBhdCBgc2V4ID09ICJmZW1hbGUiYAoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0KIyAtIHNwbGl0IGF0IHNleCA9PSAiZmVtYWxlIgpwYXJhbXMgPC0gbGlzdChkYXRhX3NldFtkYXRhX3NldCRzZXg9PSJmZW1hbGUiLCBdLCAiYWdlIiwgInN1cnZpdmVkIikKYXR0cmlidXRlX3NwbGl0KHBhcmFtcykKYGBgCgoKCioqKgoKIyMjIEZ1cnRoZXIgUmVhZGluZ3MKCisgW0JhZ2dpbmcgKEJvb3RzdHJhcCBBZ2dyZWdhdGlvbiksIGZyb20gQ29ycG9yYXRlIEZpbmFuY2UgSW5zdGl0dXRlXShodHRwczovL2NvcnBvcmF0ZWZpbmFuY2VpbnN0aXR1dGUuY29tL3Jlc291cmNlcy9rbm93bGVkZ2Uvb3RoZXIvYmFnZ2luZy1ib290c3RyYXAtYWdncmVnYXRpb24vKQoKKyBbSGFuZHMtT24gTWFjaGluZSBMZWFybmluZyB3aXRoIFIsIENoYXB0ZXIgMTAgQmFnZ2luZ10oaHR0cHM6Ly9icmFkbGV5Ym9laG1rZS5naXRodWIuaW8vSE9NTC9iYWdnaW5nLmh0bWwpCgorIFtBIHZlcnkgYmFzaWMgaW50cm9kdWN0aW9uIHRvIFJhbmRvbSBGb3Jlc3RzIHVzaW5nIFIsIGZyb20gT3hmb3JkIFByb3RlaW4gSW5mb3JtYXRpY3MgR3JvdXBdKGh0dHBzOi8vd3d3LmJsb3BpZy5jb20vYmxvZy8yMDE3LzA0L2EtdmVyeS1iYXNpYy1pbnRyb2R1Y3Rpb24tdG8tcmFuZG9tLWZvcmVzdHMtdXNpbmctci8pCgorIFtBIGdlbnRsZSBpbnRyb2R1Y3Rpb24gdG8gcmFuZG9tIGZvcmVzdHMgdXNpbmcgUiwgRWlnaHQgdG8gTGF0ZV0oaHR0cHM6Ly9laWdodDJsYXRlLndvcmRwcmVzcy5jb20vMjAxNi8wOS8yMC9hLWdlbnRsZS1pbnRyb2R1Y3Rpb24tdG8tcmFuZG9tLWZvcmVzdHMtdXNpbmctci8pCgorIFtWSURFTyBSYW5kb20gRm9yZXN0IEluIFIgfCBTaW1wbGlsZWFybl0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1IZVRUNzNXeEtJYykKCisgW1VuZGVyc3RhbmRpbmcgUmFuZG9tIEZvcmVzdDogSG93IHRoZSBBbGdvcml0aG0gV29ya3MgYW5kIFdoeSBpdCBJcyBTbyBFZmZlY3RpdmUsIFRvbnkgWWl1LCBUb3dhcmRzIERhdGEgU2NpZW5jZSwgTWVkaXVtXShodHRwczovL3Rvd2FyZHNkYXRhc2NpZW5jZS5jb20vdW5kZXJzdGFuZGluZy1yYW5kb20tZm9yZXN0LTU4MzgxZTA2MDJkMiNfPV8pCgoKCioqKgpHb3JhbiBTLiBNaWxvdmFub3ZpxIcKCkRhdGFLb2xla3RpdiwgMjAyMC8yMQoKY29udGFjdDogZ29yYW4ubWlsb3Zhbm92aWNAZGF0YWtvbGVrdGl2LmNvbQoKIVtdKC4uL19pbWcvREtfTG9nb18xMDAucG5nKQoKKioqCkxpY2Vuc2U6IFtHUEx2M10oaHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzL2dwbC0zLjAudHh0KQpUaGlzIE5vdGVib29rIGlzIGZyZWUgc29mdHdhcmU6IHlvdSBjYW4gcmVkaXN0cmlidXRlIGl0IGFuZC9vciBtb2RpZnkgaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnkgdGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiwgZWl0aGVyIHZlcnNpb24gMyBvZiB0aGUgTGljZW5zZSwgb3IgKGF0IHlvdXIgb3B0aW9uKSBhbnkgbGF0ZXIgdmVyc2lvbi4KVGhpcyBOb3RlYm9vayBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLCBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZiBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuCllvdSBzaG91bGQgaGF2ZSByZWNlaXZlZCBhIGNvcHkgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFsb25nIHdpdGggdGhpcyBOb3RlYm9vay4gSWYgbm90LCBzZWUgPGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy8+LgoKKioqCgo=