Session 15. The logic of statistical modeling explained: optimize the Simple Linear Regression model from scratch. Understanding why statistics = learning: the concept of error minimization.

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 dive deep into theory today in an effort to understand the logic of statistical modeling by error minimization. We will talk a bit about different types of mathematical models that are being developed and used in contemporary Data Science, Machine Learning, and AI. We will distinguish between supervised and unsupervised learning on one, and then reinforcement learning on the other hand. Then we focus on supervised learning and the simple regression model again in order to understand how mathematical models are estimated from empirical data. We will demonstrate several different approaches to estimate a given model’s parameters from data and discuss why they all converge along the same lines of perspective towards one single, important insight: statistics is learning.

0. Prerequisits

Setup:

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

1. Understanding Linear Regression

1.1 Sums of Squares in Simple Linear Regression

Once again: Sepal.Length predicts Petal.Length in iris:

ggplot(data = iris,
       aes(x = Sepal.Length, 
           y = Petal.Length)) +
  geom_point(aes(x = Sepal.Length, y = Petal.Length), color = "black", size = 2) +
  geom_point(aes(x = Sepal.Length, y = Petal.Length), color = "white", size = 1.5) +
  geom_smooth(method='lm', size = .25, color = "red") +
  ggtitle("Sepal Length vs Petal Length") +
  xlab("Sepal Length") + ylab("Petal Length") + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))
`geom_smooth()` using formula 'y ~ x'

Let’s recall the form of the simple linear regression model:

\[Y = \beta_0 + \beta_1X_1 + \epsilon\]

or

\[outcome_i = model + error_i\]

where \(i\) is an index across the whole dataset (i.e. each row, each pair of Sepal.Length and Petal.Lenth, each observation). So, statistical models make errors in their predictions, of course. Then what model works the best for a given dataset? The one that minimizes the error, of course.

Take a look at the following chart:

ggplot(data = iris,
       aes(x = Sepal.Length, 
           y = Petal.Length)) +
  geom_point(aes(x = Sepal.Length, y = Petal.Length), color = "black", size = 2) +
  geom_point(aes(x = Sepal.Length, y = Petal.Length), color = "white", size = 1.5) +
  geom_hline(yintercept = mean(iris$`Petal.Length`), 
             linetype = "dashed", 
             color = "red") + 
  ggtitle("Sepal Length vs Petal Length") +
  xlab("Sepal Length") + ylab("Petal Length") + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))

The horizontal line in this chart has na intercept of 3.758, which is the mean of iris$Petal.Length, the outcome variable in our simple linear regression model. If there were not a predictor iris$Sepal.Length, the mean of \(Y\) would be our best possible guess about any new value observed on that variable. Let’s take a look at the residuals of the outcome variable from its own mean:

linFit <- iris
linFit$Petal.Length.AtMean <- linFit$Petal.Length - mean(linFit$Petal.Length)
ggplot(data = linFit,
       aes(x = Sepal.Length, 
           y = Petal.Length)) +
  geom_hline(yintercept = mean(iris$`Petal.Length`), 
             linetype = "dashed", 
             color = "red") +
  geom_segment(aes(x = Sepal.Length, 
                   y = Petal.Length, 
                   xend = Sepal.Length, 
                   yend = Petal.Length - Petal.Length.AtMean),
               color = "black", size = .2, linetype = "dashed") +
  geom_point(aes(x = Sepal.Length, y = Petal.Length), color = "black", size = 2) +
  geom_point(aes(x = Sepal.Length, y = Petal.Length), color = "white", size = 1.5) +
  geom_point(aes(x = Sepal.Length, y = mean(iris$Petal.Length)), color = "red", size = 1) +
  xlab("Sepal.Length") + ylab("Petal.Length") +
  theme_classic() +
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(strip.background = element_blank()) +
  theme(axis.text.x = element_text(size = 6)) + 
  theme(axis.text.y = element_text(size = 6)) 

What is our error, in total, if we start predicting the values of a variable from its mean alone, without any other predictor at hand? Enters the Total Sum of Squares, \(SS_T\):

ss_total <- sum((iris$Petal.Length - mean(iris$Petal.Length))^2)
print(ss_total)
[1] 464.3254

Ok, now back to the simple linear regression model Petal.Length ~ Sepal.Length:

linFit <- lm(data = iris,
             Petal.Length ~ Sepal.Length)
linFit <- data.frame(
  x = iris$Sepal.Length,
  y = iris$Petal.Length,
  predicted = linFit$fitted.values,
  residuals = linFit$residuals
)
ggplot(data = linFit,
       aes(x = x, y = y)) +
  geom_smooth(method = lm, se = F, color = "red", size = .25) +
  geom_segment(aes(x = x, 
                   y = predicted, 
                   xend = x, 
                   yend = predicted + residuals),
               color = "black", size = .2, linetype = "dashed") +
  geom_point(aes(x = x, y = y), color = "black", size = 2) +
  geom_point(aes(x = x, y = y), color = "white", size = 1.5) +
  geom_point(aes(x = x, y = predicted), color = "red", size = 1) +
  xlab("Sepal.Length") + ylab("Petal.Length") +
  theme_classic() +
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(strip.background = element_blank()) +
  theme(axis.text.x = element_text(size = 6)) + 
  theme(axis.text.y = element_text(size = 6)) 
`geom_smooth()` using formula 'y ~ x'

The Residual Sum of Squares, \(SS_R\), is the sum of squared distances from the observed data points to the model’s respective predictions:

slr_model <- lm(data = iris, 
                Petal.Length ~ Sepal.Length)
ss_residual <- sum(residuals(slr_model)^2)
print(ss_residual)
[1] 111.4592

Finally, the Model Sum of Squares, \(SS_M\).

linFit <- lm(data = iris,
             Petal.Length ~ Sepal.Length)
linFit <- data.frame(
  x = iris$Sepal.Length,
  y = iris$Petal.Length,
  predicted = linFit$fitted.values,
  meanY = mean(iris$Petal.Length)
)
ggplot(data = linFit,
       aes(x = x, y = y)) +
  geom_smooth(method = lm, se = F, color = "red", size = .25) +
  geom_hline(yintercept = mean(iris$`Petal.Length`), 
             linetype = "dashed", 
             color = "red") +
  geom_segment(aes(x = x, 
                   y = predicted, 
                   xend = x, 
                   yend = meanY),
               color = "red", size = .2, linetype = "dashed") +
  geom_point(aes(x = x, y = y), color = "black", size = 2) +
  geom_point(aes(x = x, y = y), color = "white", size = 1.5) +
  geom_point(aes(x = x, y = predicted), color = "red", size = 1) +
  xlab("Sepal.Length") + ylab("Petal.Length") +
  theme_classic() +
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(strip.background = element_blank()) +
  theme(axis.text.x = element_text(size = 6)) + 
  theme(axis.text.y = element_text(size = 6)) 
`geom_smooth()` using formula 'y ~ x'

The Model Sum of Squares, \(SS_M\), is based on the distances between the mean of the outcome and the model predictions:

ss_model <- sum((linFit$predicted - linFit$meanY)^2)
print(ss_model)
[1] 352.8662

Now:

print(ss_total - ss_residual)
[1] 352.8662

!!! The complete error present in an attempt to predict Petal.Length from its mean alone, \(SS_T\), can be thus decomposed into \(SS_R\) and \(SS_M\):

\[SS_{total} = SS_{model} + SS_{residual} = SS_T = SS_M + SS_R\] #### 1.2 \(F-test\), \(t-test\), and \(R^2\) in Simple Linear Regression

But there are more tricks waiting to be pulled, see:

slr_model <- lm(data = iris, 
                Petal.Length ~ Sepal.Length)
summary(slr_model)

Call:
lm(formula = Petal.Length ~ Sepal.Length, data = iris)

Residuals:
     Min       1Q   Median       3Q      Max 
-2.47747 -0.59072 -0.00668  0.60484  2.49512 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept)  -7.10144    0.50666  -14.02   <2e-16 ***
Sepal.Length  1.85843    0.08586   21.65   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.8678 on 148 degrees of freedom
Multiple R-squared:   0.76, Adjusted R-squared:  0.7583 
F-statistic: 468.6 on 1 and 148 DF,  p-value: < 2.2e-16

What is \(SS_M/SS_T\)?

print(ss_model/ss_total)
[1] 0.7599546

So we have: \(R^2 = SS_M/SS_T\)!

Now, remember that \(F-test\) in the output that has been mentioned in the past?

Let’s divide \(SS_M\) and \(SS_R\) by their respective degrees of freedom. For the \(df_R\) in the simple linear regression (as in other models as well) we take the number of observations minus the numbers of the parameters in the model (two, in our case: the intercept and the slope), while for the \(df_M\) we need to take only the number predictors in the model (one, in our case):

df_residual <- dim(iris)[1] - 2 # - num.obs - num.parameters
print(paste0("df_residual is: ", df_residual))
[1] "df_residual is: 148"
df_model <- 1 # - how many variables?
print(paste0("df_model is: ", df_model))
[1] "df_model is: 1"
ms_model <- ss_model/df_model
ms_residual <- ss_residual/df_residual
print(paste0("MS_model is: ", ms_model))
[1] "MS_model is: 352.866244880181"
print(paste0("MS_residual is: ", ms_residual))
[1] "MS_residual is: 0.753102399458234"

They are now called mean squares, our \(MS_M\) and \(MS_R\). Ok, now:

f_test <- ms_model/ms_residual
print(f_test)
[1] 468.5502

And know we know that

\(F = MS_{Model}/MS_{Residual} = MS_M/MS_R\). The \(F-test\) follows the \(F-distribution\) with \(df_M\) and \(df_R\) degrees of freedom:

x <- rf(100000, df1 = ms_model, df2 = ms_residual)
hist(x, 
     freq = FALSE, 
     xlim = c(0,3), 
     ylim = c(0,1),
     xlab = '', 
     main = ('F-distribution with df_1 = MS_M and df_2 = MS_R degrees of freedom (df)'), 
     cex.main=0.9)
curve(df(x, df1 = 10, df2 = 20), from = 0, to = 4, n = 5000, col= 'pink', lwd=2, add = T)

And another thing to observe:

print(f_test)
[1] 468.5502
slr_summary <- summary(slr_model)
slr_coeffs <- data.frame(slr_summary$coefficients)
print(slr_coeffs)
slr_ttest <- slr_coeffs$t.value[2]
print(slr_ttest^2)
[1] 468.5502
print(f_test)
[1] 468.5502

N.B. Complicated things have many correct interpretations and connections that exist among them. For example:

  • the best simple linear statistical is the one that maximizes the effect of the model (\(MS_M\)) while minimizing the effect of the error (\(MS_R\)), which means
  • that the best simple linear statistical model is simply the one that minimizes the error, since \(SS_T = SS_M + SS_R\), and \(SS_T\) is fixed since it only says about the dispersion of the outcome variable around its mean; then,
  • the overall effect of the model can be measured by the extent of the \(F-test\), which is essentially a ratio of two variances, \(MS_M\) and \(MS_R\), telling us how much does the model “works” beyond the error it produces, and which is a simple mathematical transformation of the
  • \(t-test\) of whether the model’s slope (\(\beta_1\)) is different from zero in which case we know that the regression line “works” because it is statistically different from a horizontal line of no correlation.

2. On Minimizing Errors in Statistical Modeling

2.1 Grid Search in the Parameter Space

I want to write a function now. The function takes the following as its inputs:

  • a dataset with one predictor and one outcome variable
  • a pair of parameters, \(/beta_0\) and \(/beta_1\),

and it returns the sum of squared errors (i.e. residuals) from the simple linear regression of the well known \(Y = \beta_0 + \beta_1X +\epsilon\) form.

Here is what I want essentially:

d <- data.frame(x = iris$Sepal.Length,
                y = iris$Petal.Length)
residuals <- summary(lm(y ~ x, data = d))$residuals
print(sum(residuals^2))
[1] 111.4592

But I do not want to use lm(): instead, I want to be able to specify \(/beta_0\) and \(/beta_1\) myself:

sse <- function(d, beta_0, beta_1) {
  predictions <- beta_0 + beta_1 * d$x
  residuals <- d$y - predictions
  return(sum(residuals^2))
}

Ok. Now, the lm() function in R can find the optimal values of the parameters \(/beta_0\) and \(/beta_1\), right?

slr_model <- lm(y ~ x, data = d)
coefficients(slr_model)
(Intercept)           x 
  -7.101443    1.858433 

Let’s test our sse() function:

beta_0_test <- coefficients(slr_model)[1]
beta_1_test <- coefficients(slr_model)[2]
sse(d = d, 
    beta_0 = beta_0_test, 
    beta_1 = beta_1_test)
[1] 111.4592

And from lm() again:

d <- data.frame(x = iris$Sepal.Length,
                y = iris$Petal.Length)
residuals <- summary(lm(y ~ x, data = d))$residuals
print(sum(residuals^2))
[1] 111.4592

So we know that our sse() works just fine.

Now: how could have determined the optimal values - the error minimizing values - of \(/beta_0\) and \(/beta_1\) without relying on lm()?

One idea is to move across the space of the parameter values in small steps and compute the model error at each point in that space, for example:

test_beta_0 <- seq(-10, 10, by = .1)
test_beta_1 <- seq(-10, 10, by = .1)
model_errors <- lapply(test_beta_0, function(x) {
  return(
    rbindlist(
      lapply(test_beta_1, function(y) {
        s <- sse(d = d, beta_0 = x, beta_1 = y)
        return(data.frame(sse = s, 
                          beta_0 = x, 
                          beta_1 = y))
    }))
  )
})
model_errors <- rbindlist(model_errors)
head(model_errors)

What would be the most optimal estimates of of \(/beta_0\) and \(/beta_1\) then?

model_errors[which.min(model_errors$sse), ]

Compare:

coefficients(slr_model)
(Intercept)           x 
  -7.101443    1.858433 

Hm, not bad?

2.2 Sample the Parameter Space

Another idea that comes to mind is the following one: why not take a uniform random sample from the Parameter Space and check out the sse() at various points defined by their \(/beta_0\) and \(/beta_1\) coordinates?

sample_parameters <- data.frame(beta_0 = runif(100000, -10, 10), 
                                beta_1 = runif(100000, -10, 10))
head(sample_parameters)

Now let’s find the model errors at each randomly sampled combination of parameters:

sample_parameters$sse <- apply(sample_parameters, 1, function(x) {
    sse(d, x[1], x[2])
})
head(sample_parameters)

And what would be the most optimal estimates of of \(/beta_0\) and \(/beta_1\) in this case?

sample_parameters[which.min(sample_parameters$sse), ]

Compare:

coefficients(slr_model)
(Intercept)           x 
  -7.101443    1.858433 

Hm?

N.B. Finding the optimal values of the model’s parameters implies some sort of search through the Paramater Space, and picking the values that minimize the model error as much as possible.

But there are better ways to do it than Grid Search or Random Sampling. And whwnever that is possible, this is what we do to our statistical learning models: we optimize them.

Please pay close attention to what exactly is happening in these procedures:

  • the dataset d is a constant, it does not change in any of the sse() function’s run;
  • the parameters \(/beta_0\) and \(/beta_1\) vary in some way (until now: grid search or random uniform sampling), and
  • the sse() function does not estimate anything - it is not lm()! - but computes the model error instead. So what are we really looking for? We are looking for a way to find the minimum of our sse() function.

2.3 Optimize the Simple Linear Regression model w. base R optim()

First we need a slight rewrite of sse() only:

sse <- function(params) {
  beta_0 <- params[1]
  beta_1 <- params[2]
  # - MODEL IS HERE:
  predictions <- beta_0 + beta_1 * d$x
  # - MODEL ENDS HERE ^^
  residuals <- d$y - predictions
  return(sum(residuals^2))
}

N.B. As the dataset d is a constant, it does not play a role of an sse() function parameter anymore.

Pick some random, initial values for \(/beta_0\) and \(/beta_1\):

beta_0_start <- runif(1, -10, 10)
beta_1_start <- runif(1, -10, 10)
print(beta_0_start)
[1] 1.237548
print(beta_1_start)
[1] -2.486866
solution <- optim(par = c(beta_0_start, beta_1_start), 
                  fn = sse, 
                  method = "Nelder-Mead",
                  lower = -Inf, upper = Inf)
print(solution$par)
[1] -7.086737  1.855748

Compare:

coefficients(slr_model)
(Intercept)           x 
  -7.101443    1.858433 

Now that looks great!

What are we really looking at here is this (first reducing the sample_parameters dataframe a bit… :-)

# - back to the old version of sse():
sse <- function(d, beta_0, beta_1) {
  predictions <- beta_0 + beta_1 * d$x
  residuals <- d$y - predictions
  return(sum(residuals^2))
}
# - sample parameters on a different range for plotting purposes
sample_parameters <- data.frame(beta_0 = runif(100000, -7.5, 7.5), 
                                beta_1 = runif(100000, -2, 2))
sample_parameters$sse <- apply(sample_parameters, 1, function(x) {
    sse(d, x[1], x[2])
})
head(sample_parameters)
plot_ly() %>% 
  add_trace(data = sample_parameters,  
            x = sample_parameters$beta_0, 
            y = sample_parameters$beta_1, 
            z = sample_parameters$sse, 
            type = "mesh3d", 
            intensity = sample_parameters$sse, 
            colorscale = "Viridis") %>% 
  layout(
    modebar = list(orientation = "v"), 
    title = "The Simple Linear Regression Error Landscape",
    scene = list(
      xaxis = list(title = "Beta 0", range = c(-7.5, 7.5)),
      yaxis = list(title = "Beta 1", range = c(-2, 2)),
      zaxis = list(title = "SSE")
    ))
sample_parameters[which.min(sample_parameters$sse), ]

Compare:

coefficients(slr_model)
(Intercept)           x 
  -7.101443    1.858433 

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


LS0tCnRpdGxlOiBJbnRybyB0byBEYXRhIFNjaWVuY2UgKE5vbi1UZWNobmljYWwgQmFja2dyb3VuZCwgUikgLSBTZXNzaW9uMTUKYXV0aG9yOgotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhywgUGhECiAgYWZmaWxpYXRpb246IERhdGFLb2xla3RpdiwgQ2hpZWYgU2NpZW50aXN0ICYgT3duZXI7IERhdGEgU2NpZW50aXN0IGZvciBXaWtpZGF0YSwgV01ERQphYnN0cmFjdDogCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgdG9jX2RlcHRoOiA1CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDUKLS0tCgohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpCgoqKioKIyBTZXNzaW9uIDE1LiBUaGUgbG9naWMgb2Ygc3RhdGlzdGljYWwgbW9kZWxpbmcgZXhwbGFpbmVkOiBvcHRpbWl6ZSB0aGUgU2ltcGxlIExpbmVhciBSZWdyZXNzaW9uIG1vZGVsIGZyb20gc2NyYXRjaC4gVW5kZXJzdGFuZGluZyB3aHkgc3RhdGlzdGljcyA9IGxlYXJuaW5nOiB0aGUgY29uY2VwdCBvZiBlcnJvciBtaW5pbWl6YXRpb24uCgoqKkZlZWRiYWNrKiogc2hvdWxkIGJlIHNlbmQgdG8gYGdvcmFuLm1pbG92YW5vdmljQGRhdGFrb2xla3Rpdi5jb21gLiAKVGhlc2Ugbm90ZWJvb2tzIGFjY29tcGFueSB0aGUgSW50cm8gdG8gRGF0YSBTY2llbmNlOiBOb24tVGVjaG5pY2FsIEJhY2tncm91bmQgY291cnNlIDIwMjAvMjEuCgoqKioKCiMjIyBXaGF0IGRvIHdlIHdhbnQgdG8gZG8gdG9kYXk/CgpXZSB3aWxsIGRpdmUgZGVlcCBpbnRvIHRoZW9yeSB0b2RheSBpbiBhbiBlZmZvcnQgdG8gdW5kZXJzdGFuZCB0aGUgbG9naWMgb2Ygc3RhdGlzdGljYWwgbW9kZWxpbmcgYnkgKmVycm9yIG1pbmltaXphdGlvbiouIFdlIHdpbGwgdGFsayBhIGJpdCBhYm91dCBkaWZmZXJlbnQgdHlwZXMgb2YgbWF0aGVtYXRpY2FsIG1vZGVscyB0aGF0IGFyZSBiZWluZyBkZXZlbG9wZWQgYW5kIHVzZWQgaW4gY29udGVtcG9yYXJ5IERhdGEgU2NpZW5jZSwgTWFjaGluZSBMZWFybmluZywgYW5kIEFJLiBXZSB3aWxsIGRpc3Rpbmd1aXNoIGJldHdlZW4gc3VwZXJ2aXNlZCBhbmQgdW5zdXBlcnZpc2VkIGxlYXJuaW5nIG9uIG9uZSwgYW5kIHRoZW4gcmVpbmZvcmNlbWVudCBsZWFybmluZyBvbiB0aGUgb3RoZXIgaGFuZC4gVGhlbiB3ZSBmb2N1cyBvbiBzdXBlcnZpc2VkIGxlYXJuaW5nIGFuZCB0aGUgc2ltcGxlIHJlZ3Jlc3Npb24gbW9kZWwgYWdhaW4gaW4gb3JkZXIgdG8gdW5kZXJzdGFuZCBob3cgbWF0aGVtYXRpY2FsIG1vZGVscyBhcmUgZXN0aW1hdGVkIGZyb20gZW1waXJpY2FsIGRhdGEuIFdlIHdpbGwgZGVtb25zdHJhdGUgc2V2ZXJhbCBkaWZmZXJlbnQgYXBwcm9hY2hlcyB0byBlc3RpbWF0ZSBhIGdpdmVuIG1vZGVsJ3MgcGFyYW1ldGVycyBmcm9tIGRhdGEgYW5kIGRpc2N1c3Mgd2h5IHRoZXkgYWxsIGNvbnZlcmdlIGFsb25nIHRoZSBzYW1lIGxpbmVzIG9mIHBlcnNwZWN0aXZlIHRvd2FyZHMgb25lIHNpbmdsZSwgaW1wb3J0YW50IGluc2lnaHQ6IHN0YXRpc3RpY3MgKmlzKiBsZWFybmluZy4KCiMjIyAwLiBQcmVyZXF1aXNpdHMKClNldHVwOgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0KZGF0YURpciA8LSBwYXN0ZTAoZ2V0d2QoKSwgIi9fZGF0YS8iKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHBsb3RseSkKYGBgCgoKIyMjIDEuIFVuZGVyc3RhbmRpbmcgTGluZWFyIFJlZ3Jlc3Npb24KCiMjIyMgMS4xIFN1bXMgb2YgU3F1YXJlcyBpbiBTaW1wbGUgTGluZWFyIFJlZ3Jlc3Npb24KCk9uY2UgYWdhaW46IGBTZXBhbC5MZW5ndGhgIHByZWRpY3RzIGBQZXRhbC5MZW5ndGhgIGluIGBpcmlzYDoKCmBgYCB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CmdncGxvdChkYXRhID0gaXJpcywKICAgICAgIGFlcyh4ID0gU2VwYWwuTGVuZ3RoLCAKICAgICAgICAgICB5ID0gUGV0YWwuTGVuZ3RoKSkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBTZXBhbC5MZW5ndGgsIHkgPSBQZXRhbC5MZW5ndGgpLCBjb2xvciA9ICJibGFjayIsIHNpemUgPSAyKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IFNlcGFsLkxlbmd0aCwgeSA9IFBldGFsLkxlbmd0aCksIGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDEuNSkgKwogIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBzaXplID0gLjI1LCBjb2xvciA9ICJyZWQiKSArCiAgZ2d0aXRsZSgiU2VwYWwgTGVuZ3RoIHZzIFBldGFsIExlbmd0aCIpICsKICB4bGFiKCJTZXBhbCBMZW5ndGgiKSArIHlsYWIoIlBldGFsIExlbmd0aCIpICsgCiAgdGhlbWVfYncoKSArIAogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUpKQpgYGAKCkxldCdzIHJlY2FsbCB0aGUgZm9ybSBvZiB0aGUgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsOgoKJCRZID0gXGJldGFfMCArIFxiZXRhXzFYXzEgKyBcZXBzaWxvbiQkCgpvcgoKJCRvdXRjb21lX2kgPSBtb2RlbCArIGVycm9yX2kkJAoKd2hlcmUgJGkkIGlzIGFuIGluZGV4IGFjcm9zcyB0aGUgd2hvbGUgZGF0YXNldCAoaS5lLiBlYWNoIHJvdywgZWFjaCBwYWlyIG9mIGBTZXBhbC5MZW5ndGhgIGFuZCBgUGV0YWwuTGVudGhgLCBlYWNoIG9ic2VydmF0aW9uKS4gU28sIHN0YXRpc3RpY2FsIG1vZGVscyBtYWtlIGVycm9ycyBpbiB0aGVpciBwcmVkaWN0aW9ucywgb2YgY291cnNlLiBUaGVuIHdoYXQgbW9kZWwgd29ya3MgdGhlIGJlc3QgZm9yIGEgZ2l2ZW4gZGF0YXNldD8gVGhlIG9uZSB0aGF0ICptaW5pbWl6ZXMgdGhlIGVycm9yKiwgb2YgY291cnNlLgoKVGFrZSBhIGxvb2sgYXQgdGhlIGZvbGxvd2luZyBjaGFydDoKCmBgYCB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CmdncGxvdChkYXRhID0gaXJpcywKICAgICAgIGFlcyh4ID0gU2VwYWwuTGVuZ3RoLCAKICAgICAgICAgICB5ID0gUGV0YWwuTGVuZ3RoKSkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBTZXBhbC5MZW5ndGgsIHkgPSBQZXRhbC5MZW5ndGgpLCBjb2xvciA9ICJibGFjayIsIHNpemUgPSAyKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IFNlcGFsLkxlbmd0aCwgeSA9IFBldGFsLkxlbmd0aCksIGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDEuNSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IG1lYW4oaXJpcyRgUGV0YWwuTGVuZ3RoYCksIAogICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgCiAgICAgICAgICAgICBjb2xvciA9ICJyZWQiKSArIAogIGdndGl0bGUoIlNlcGFsIExlbmd0aCB2cyBQZXRhbCBMZW5ndGgiKSArCiAgeGxhYigiU2VwYWwgTGVuZ3RoIikgKyB5bGFiKCJQZXRhbCBMZW5ndGgiKSArIAogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IC41KSkKYGBgCgpUaGUgaG9yaXpvbnRhbCBsaW5lIGluIHRoaXMgY2hhcnQgaGFzIG5hIGludGVyY2VwdCBvZiBgMy43NThgLCB3aGljaCBpcyB0aGUgbWVhbiBvZiBgaXJpcyRQZXRhbC5MZW5ndGhgLCB0aGUgb3V0Y29tZSB2YXJpYWJsZSBpbiBvdXIgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsLiBJZiB0aGVyZSB3ZXJlIG5vdCBhIHByZWRpY3RvciBgaXJpcyRTZXBhbC5MZW5ndGhgLCB0aGUgbWVhbiBvZiAkWSQgd291bGQgYmUgb3VyIGJlc3QgcG9zc2libGUgZ3Vlc3MgYWJvdXQgYW55IG5ldyB2YWx1ZSBvYnNlcnZlZCBvbiB0aGF0IHZhcmlhYmxlLiBMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgKnJlc2lkdWFscyBvZiB0aGUgb3V0Y29tZSB2YXJpYWJsZSBmcm9tIGl0cyBvd24gbWVhbio6CgpgYGAge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQpsaW5GaXQgPC0gaXJpcwpsaW5GaXQkUGV0YWwuTGVuZ3RoLkF0TWVhbiA8LSBsaW5GaXQkUGV0YWwuTGVuZ3RoIC0gbWVhbihsaW5GaXQkUGV0YWwuTGVuZ3RoKQpnZ3Bsb3QoZGF0YSA9IGxpbkZpdCwKICAgICAgIGFlcyh4ID0gU2VwYWwuTGVuZ3RoLCAKICAgICAgICAgICB5ID0gUGV0YWwuTGVuZ3RoKSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IG1lYW4oaXJpcyRgUGV0YWwuTGVuZ3RoYCksIAogICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgCiAgICAgICAgICAgICBjb2xvciA9ICJyZWQiKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gU2VwYWwuTGVuZ3RoLCAKICAgICAgICAgICAgICAgICAgIHkgPSBQZXRhbC5MZW5ndGgsIAogICAgICAgICAgICAgICAgICAgeGVuZCA9IFNlcGFsLkxlbmd0aCwgCiAgICAgICAgICAgICAgICAgICB5ZW5kID0gUGV0YWwuTGVuZ3RoIC0gUGV0YWwuTGVuZ3RoLkF0TWVhbiksCiAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IC4yLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IFNlcGFsLkxlbmd0aCwgeSA9IFBldGFsLkxlbmd0aCksIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDIpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gU2VwYWwuTGVuZ3RoLCB5ID0gUGV0YWwuTGVuZ3RoKSwgY29sb3IgPSAid2hpdGUiLCBzaXplID0gMS41KSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IFNlcGFsLkxlbmd0aCwgeSA9IG1lYW4oaXJpcyRQZXRhbC5MZW5ndGgpKSwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDEpICsKICB4bGFiKCJTZXBhbC5MZW5ndGgiKSArIHlsYWIoIlBldGFsLkxlbmd0aCIpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSkgKyAKICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNikpIApgYGAKV2hhdCBpcyBvdXIgZXJyb3IsIGluIHRvdGFsLCBpZiB3ZSBzdGFydCBwcmVkaWN0aW5nIHRoZSB2YWx1ZXMgb2YgYSB2YXJpYWJsZSBmcm9tIGl0cyBtZWFuIGFsb25lLCB3aXRob3V0IGFueSBvdGhlciBwcmVkaWN0b3IgYXQgaGFuZD8gRW50ZXJzIHRoZSAqVG90YWwgU3VtIG9mIFNxdWFyZXMqLCAkU1NfVCQ6CgpgYGAge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQpzc190b3RhbCA8LSBzdW0oKGlyaXMkUGV0YWwuTGVuZ3RoIC0gbWVhbihpcmlzJFBldGFsLkxlbmd0aCkpXjIpCnByaW50KHNzX3RvdGFsKQpgYGAKCk9rLCBub3cgYmFjayB0byB0aGUgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIGBQZXRhbC5MZW5ndGggfiBTZXBhbC5MZW5ndGhgOgoKYGBgIHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0KbGluRml0IDwtIGxtKGRhdGEgPSBpcmlzLAogICAgICAgICAgICAgUGV0YWwuTGVuZ3RoIH4gU2VwYWwuTGVuZ3RoKQpsaW5GaXQgPC0gZGF0YS5mcmFtZSgKICB4ID0gaXJpcyRTZXBhbC5MZW5ndGgsCiAgeSA9IGlyaXMkUGV0YWwuTGVuZ3RoLAogIHByZWRpY3RlZCA9IGxpbkZpdCRmaXR0ZWQudmFsdWVzLAogIHJlc2lkdWFscyA9IGxpbkZpdCRyZXNpZHVhbHMKKQpnZ3Bsb3QoZGF0YSA9IGxpbkZpdCwKICAgICAgIGFlcyh4ID0geCwgeSA9IHkpKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gbG0sIHNlID0gRiwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IC4yNSkgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IHgsIAogICAgICAgICAgICAgICAgICAgeSA9IHByZWRpY3RlZCwgCiAgICAgICAgICAgICAgICAgICB4ZW5kID0geCwgCiAgICAgICAgICAgICAgICAgICB5ZW5kID0gcHJlZGljdGVkICsgcmVzaWR1YWxzKSwKICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLCBzaXplID0gLjIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBnZW9tX3BvaW50KGFlcyh4ID0geCwgeSA9IHkpLCBjb2xvciA9ICJibGFjayIsIHNpemUgPSAyKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IHgsIHkgPSB5KSwgY29sb3IgPSAid2hpdGUiLCBzaXplID0gMS41KSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IHgsIHkgPSBwcmVkaWN0ZWQpLCBjb2xvciA9ICJyZWQiLCBzaXplID0gMSkgKwogIHhsYWIoIlNlcGFsLkxlbmd0aCIpICsgeWxhYigiUGV0YWwuTGVuZ3RoIikgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgdGhlbWVfYncoKSArIAogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpKSArIAogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSkgCmBgYAoKVGhlICpSZXNpZHVhbCBTdW0gb2YgU3F1YXJlcyosICAkU1NfUiQsICBpcyB0aGUgc3VtIG9mIHNxdWFyZWQgZGlzdGFuY2VzIGZyb20gdGhlIG9ic2VydmVkIGRhdGEgcG9pbnRzIHRvIHRoZSBtb2RlbCdzIHJlc3BlY3RpdmUgcHJlZGljdGlvbnM6CgpgYGAge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQpzbHJfbW9kZWwgPC0gbG0oZGF0YSA9IGlyaXMsIAogICAgICAgICAgICAgICAgUGV0YWwuTGVuZ3RoIH4gU2VwYWwuTGVuZ3RoKQpzc19yZXNpZHVhbCA8LSBzdW0ocmVzaWR1YWxzKHNscl9tb2RlbCleMikKcHJpbnQoc3NfcmVzaWR1YWwpCmBgYAoKRmluYWxseSwgdGhlICpNb2RlbCBTdW0gb2YgU3F1YXJlcyosICAkU1NfTSQuCgpgYGAge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQpsaW5GaXQgPC0gbG0oZGF0YSA9IGlyaXMsCiAgICAgICAgICAgICBQZXRhbC5MZW5ndGggfiBTZXBhbC5MZW5ndGgpCmxpbkZpdCA8LSBkYXRhLmZyYW1lKAogIHggPSBpcmlzJFNlcGFsLkxlbmd0aCwKICB5ID0gaXJpcyRQZXRhbC5MZW5ndGgsCiAgcHJlZGljdGVkID0gbGluRml0JGZpdHRlZC52YWx1ZXMsCiAgbWVhblkgPSBtZWFuKGlyaXMkUGV0YWwuTGVuZ3RoKQopCmdncGxvdChkYXRhID0gbGluRml0LAogICAgICAgYWVzKHggPSB4LCB5ID0geSkpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSBsbSwgc2UgPSBGLCBjb2xvciA9ICJyZWQiLCBzaXplID0gLjI1KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbWVhbihpcmlzJGBQZXRhbC5MZW5ndGhgKSwgCiAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCAKICAgICAgICAgICAgIGNvbG9yID0gInJlZCIpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSB4LCAKICAgICAgICAgICAgICAgICAgIHkgPSBwcmVkaWN0ZWQsIAogICAgICAgICAgICAgICAgICAgeGVuZCA9IHgsIAogICAgICAgICAgICAgICAgICAgeWVuZCA9IG1lYW5ZKSwKICAgICAgICAgICAgICAgY29sb3IgPSAicmVkIiwgc2l6ZSA9IC4yLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IHgsIHkgPSB5KSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMikgKwogIGdlb21fcG9pbnQoYWVzKHggPSB4LCB5ID0geSksIGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDEuNSkgKwogIGdlb21fcG9pbnQoYWVzKHggPSB4LCB5ID0gcHJlZGljdGVkKSwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDEpICsKICB4bGFiKCJTZXBhbC5MZW5ndGgiKSArIHlsYWIoIlBldGFsLkxlbmd0aCIpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSkgKyAKICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNikpIApgYGAKClRoZSAqTW9kZWwgU3VtIG9mIFNxdWFyZXMqLCAgJFNTX00kLCBpcyBiYXNlZCBvbiB0aGUgZGlzdGFuY2VzIGJldHdlZW4gdGhlICptZWFuIG9mIHRoZSBvdXRjb21lKiBhbmQgdGhlICptb2RlbCBwcmVkaWN0aW9ucyo6CgpgYGAge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQpzc19tb2RlbCA8LSBzdW0oKGxpbkZpdCRwcmVkaWN0ZWQgLSBsaW5GaXQkbWVhblkpXjIpCnByaW50KHNzX21vZGVsKQpgYGAKCk5vdzoKCmBgYCB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CnByaW50KHNzX3RvdGFsIC0gc3NfcmVzaWR1YWwpCmBgYAoKISEhIFRoZSBjb21wbGV0ZSBlcnJvciBwcmVzZW50IGluIGFuIGF0dGVtcHQgdG8gcHJlZGljdCBgUGV0YWwuTGVuZ3RoYCBmcm9tIGl0cyBtZWFuIGFsb25lLCAkU1NfVCQsIGNhbiBiZSB0aHVzIGRlY29tcG9zZWQgaW50byAkU1NfUiQgYW5kICRTU19NJDoKCiQkU1Nfe3RvdGFsfSA9IFNTX3ttb2RlbH0gKyBTU197cmVzaWR1YWx9ID0gU1NfVCA9IFNTX00gKyBTU19SJCQKIyMjIyAxLjIgJEYtdGVzdCQsICR0LXRlc3QkLCBhbmQgJFJeMiQgaW4gU2ltcGxlIExpbmVhciBSZWdyZXNzaW9uIAoKQnV0IHRoZXJlIGFyZSBtb3JlIHRyaWNrcyB3YWl0aW5nIHRvIGJlIHB1bGxlZCwgc2VlOgoKYGBgIHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0Kc2xyX21vZGVsIDwtIGxtKGRhdGEgPSBpcmlzLCAKICAgICAgICAgICAgICAgIFBldGFsLkxlbmd0aCB+IFNlcGFsLkxlbmd0aCkKc3VtbWFyeShzbHJfbW9kZWwpCmBgYAoKV2hhdCBpcyAkU1NfTS9TU19UJD8KCmBgYCB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CnByaW50KHNzX21vZGVsL3NzX3RvdGFsKQpgYGAKClNvIHdlIGhhdmU6ICRSXjIgPSBTU19NL1NTX1QkIQoKTm93LCByZW1lbWJlciB0aGF0ICRGLXRlc3QkIGluIHRoZSBvdXRwdXQgdGhhdCBoYXMgYmVlbiBtZW50aW9uZWQgaW4gdGhlIHBhc3Q/CgpMZXQncyBkaXZpZGUgJFNTX00kIGFuZCAkU1NfUiQgYnkgdGhlaXIgcmVzcGVjdGl2ZSBkZWdyZWVzIG9mIGZyZWVkb20uIEZvciB0aGUgJGRmX1IkIGluIHRoZSBzaW1wbGUgbGluZWFyIHJlZ3Jlc3Npb24gKGFzIGluIG90aGVyIG1vZGVscyBhcyB3ZWxsKSB3ZSB0YWtlIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIG1pbnVzIHRoZSBudW1iZXJzIG9mIHRoZSBwYXJhbWV0ZXJzIGluIHRoZSBtb2RlbCAodHdvLCBpbiBvdXIgY2FzZTogdGhlIGludGVyY2VwdCBhbmQgdGhlIHNsb3BlKSwgd2hpbGUgZm9yIHRoZSAkZGZfTSQgd2UgbmVlZCB0byB0YWtlIG9ubHkgdGhlIG51bWJlciBwcmVkaWN0b3JzIGluIHRoZSBtb2RlbCAob25lLCBpbiBvdXIgY2FzZSk6CgpgYGAge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQpkZl9yZXNpZHVhbCA8LSBkaW0oaXJpcylbMV0gLSAyICMgLSBudW0ub2JzIC0gbnVtLnBhcmFtZXRlcnMKcHJpbnQocGFzdGUwKCJkZl9yZXNpZHVhbCBpczogIiwgZGZfcmVzaWR1YWwpKQpkZl9tb2RlbCA8LSAxICMgLSBob3cgbWFueSB2YXJpYWJsZXM/CnByaW50KHBhc3RlMCgiZGZfbW9kZWwgaXM6ICIsIGRmX21vZGVsKSkKbXNfbW9kZWwgPC0gc3NfbW9kZWwvZGZfbW9kZWwKbXNfcmVzaWR1YWwgPC0gc3NfcmVzaWR1YWwvZGZfcmVzaWR1YWwKcHJpbnQocGFzdGUwKCJNU19tb2RlbCBpczogIiwgbXNfbW9kZWwpKQpwcmludChwYXN0ZTAoIk1TX3Jlc2lkdWFsIGlzOiAiLCBtc19yZXNpZHVhbCkpCmBgYAoKVGhleSBhcmUgbm93IGNhbGxlZCAqbWVhbiBzcXVhcmVzKiwgb3VyICRNU19NJCBhbmQgJE1TX1IkLiBPaywgbm93OgoKYGBgIHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0KZl90ZXN0IDwtIG1zX21vZGVsL21zX3Jlc2lkdWFsCnByaW50KGZfdGVzdCkKYGBgCgpBbmQga25vdyB3ZSBrbm93IHRoYXQgCgokRiA9IE1TX3tNb2RlbH0vTVNfe1Jlc2lkdWFsfSA9IE1TX00vTVNfUiQuIFRoZSAkRi10ZXN0JCBmb2xsb3dzIHRoZSBbJEYtZGlzdHJpYnV0aW9uJF0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRi1kaXN0cmlidXRpb24pIHdpdGggJGRmX00kIGFuZCAkZGZfUiQgZGVncmVlcyBvZiBmcmVlZG9tOgoKYGBgIHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0KeCA8LSByZigxMDAwMDAsIGRmMSA9IG1zX21vZGVsLCBkZjIgPSBtc19yZXNpZHVhbCkKaGlzdCh4LCAKICAgICBmcmVxID0gRkFMU0UsIAogICAgIHhsaW0gPSBjKDAsMyksIAogICAgIHlsaW0gPSBjKDAsMSksCiAgICAgeGxhYiA9ICcnLCAKICAgICBtYWluID0gKCdGLWRpc3RyaWJ1dGlvbiB3aXRoIGRmXzEgPSBNU19NIGFuZCBkZl8yID0gTVNfUiBkZWdyZWVzIG9mIGZyZWVkb20gKGRmKScpLCAKICAgICBjZXgubWFpbj0wLjkpCmN1cnZlKGRmKHgsIGRmMSA9IDEwLCBkZjIgPSAyMCksIGZyb20gPSAwLCB0byA9IDQsIG4gPSA1MDAwLCBjb2w9ICdwaW5rJywgbHdkPTIsIGFkZCA9IFQpCmBgYAoKQW5kIGFub3RoZXIgdGhpbmcgdG8gb2JzZXJ2ZToKCmBgYHtyIGVjaG8gPSBUfQpwcmludChmX3Rlc3QpCmBgYAoKYGBge3IgZWNobyA9IFR9CnNscl9zdW1tYXJ5IDwtIHN1bW1hcnkoc2xyX21vZGVsKQpzbHJfY29lZmZzIDwtIGRhdGEuZnJhbWUoc2xyX3N1bW1hcnkkY29lZmZpY2llbnRzKQpwcmludChzbHJfY29lZmZzKQpgYGAKCmBgYHtyIGVjaG8gPSBUfQpzbHJfdHRlc3QgPC0gc2xyX2NvZWZmcyR0LnZhbHVlWzJdCnByaW50KHNscl90dGVzdF4yKQpgYGAKCmBgYHtyIGVjaG8gPSBUfQpwcmludChmX3Rlc3QpCmBgYAoKKipOLkIuKiogQ29tcGxpY2F0ZWQgdGhpbmdzIGhhdmUgbWFueSBjb3JyZWN0IGludGVycHJldGF0aW9ucyBhbmQgY29ubmVjdGlvbnMgdGhhdCBleGlzdCBhbW9uZyB0aGVtLiBGb3IgZXhhbXBsZToKCi0gdGhlIGJlc3Qgc2ltcGxlIGxpbmVhciBzdGF0aXN0aWNhbCBpcyB0aGUgb25lIHRoYXQgbWF4aW1pemVzIHRoZSBlZmZlY3Qgb2YgdGhlIG1vZGVsICgkTVNfTSQpIHdoaWxlIG1pbmltaXppbmcgdGhlIGVmZmVjdCBvZiB0aGUgZXJyb3IgKCRNU19SJCksIHdoaWNoIG1lYW5zCi0gdGhhdCB0aGUgYmVzdCBzaW1wbGUgbGluZWFyIHN0YXRpc3RpY2FsIG1vZGVsIGlzIHNpbXBseSB0aGUgb25lIHRoYXQgbWluaW1pemVzIHRoZSBlcnJvciwgc2luY2UgJFNTX1QgPSBTU19NICsgU1NfUiQsIGFuZCAkU1NfVCQgaXMgZml4ZWQgc2luY2UgaXQgb25seSBzYXlzIGFib3V0IHRoZSBkaXNwZXJzaW9uIG9mIHRoZSBvdXRjb21lIHZhcmlhYmxlIGFyb3VuZCBpdHMgbWVhbjsgdGhlbiwKLSB0aGUgb3ZlcmFsbCBlZmZlY3Qgb2YgdGhlIG1vZGVsIGNhbiBiZSBtZWFzdXJlZCBieSB0aGUgZXh0ZW50IG9mIHRoZSAkRi10ZXN0JCwgd2hpY2ggaXMgZXNzZW50aWFsbHkgYSByYXRpbyBvZiB0d28gdmFyaWFuY2VzLCAkTVNfTSQgYW5kICRNU19SJCwgdGVsbGluZyB1cyBob3cgbXVjaCBkb2VzIHRoZSBtb2RlbCAid29ya3MiIGJleW9uZCB0aGUgZXJyb3IgaXQgcHJvZHVjZXMsIGFuZCB3aGljaCBpcyBhIHNpbXBsZSBtYXRoZW1hdGljYWwgdHJhbnNmb3JtYXRpb24gb2YgdGhlIAotICR0LXRlc3QkIG9mIHdoZXRoZXIgdGhlIG1vZGVsJ3Mgc2xvcGUgKCRcYmV0YV8xJCkgaXMgZGlmZmVyZW50IGZyb20gemVybyBpbiB3aGljaCBjYXNlIHdlIGtub3cgdGhhdCB0aGUgcmVncmVzc2lvbiBsaW5lICJ3b3JrcyIgYmVjYXVzZSBpdCBpcyBzdGF0aXN0aWNhbGx5IGRpZmZlcmVudCBmcm9tIGEgaG9yaXpvbnRhbCBsaW5lIG9mIG5vIGNvcnJlbGF0aW9uLgoKCiMjIyAyLiBPbiBNaW5pbWl6aW5nIEVycm9ycyBpbiBTdGF0aXN0aWNhbCBNb2RlbGluZwoKIyMjIyAyLjEgR3JpZCBTZWFyY2ggaW4gdGhlIFBhcmFtZXRlciBTcGFjZQoKSSB3YW50IHRvIHdyaXRlIGEgZnVuY3Rpb24gbm93LiBUaGUgZnVuY3Rpb24gdGFrZXMgdGhlIGZvbGxvd2luZyBhcyBpdHMgaW5wdXRzOgoKKyBhIGRhdGFzZXQgd2l0aCBvbmUgcHJlZGljdG9yIGFuZCBvbmUgb3V0Y29tZSB2YXJpYWJsZQorIGEgcGFpciBvZiBwYXJhbWV0ZXJzLCAkL2JldGFfMCQgYW5kICQvYmV0YV8xJCwgCgphbmQgaXQgcmV0dXJucyB0aGUgKnN1bSBvZiBzcXVhcmVkIGVycm9ycyAoaS5lLiByZXNpZHVhbHMpKiBmcm9tIHRoZSBzaW1wbGUgbGluZWFyIHJlZ3Jlc3Npb24gb2YgdGhlIHdlbGwga25vd24gJFkgPSBcYmV0YV8wICsgXGJldGFfMVggK1xlcHNpbG9uJCBmb3JtLiAKCkhlcmUgaXMgd2hhdCBJIHdhbnQgZXNzZW50aWFsbHk6CgpgYGB7ciBlY2hvID0gVH0KZCA8LSBkYXRhLmZyYW1lKHggPSBpcmlzJFNlcGFsLkxlbmd0aCwKICAgICAgICAgICAgICAgIHkgPSBpcmlzJFBldGFsLkxlbmd0aCkKcmVzaWR1YWxzIDwtIHN1bW1hcnkobG0oeSB+IHgsIGRhdGEgPSBkKSkkcmVzaWR1YWxzCnByaW50KHN1bShyZXNpZHVhbHNeMikpCmBgYAoKQnV0IEkgZG8gbm90IHdhbnQgdG8gdXNlIGBsbSgpYDogaW5zdGVhZCwgSSB3YW50IHRvIGJlIGFibGUgdG8gc3BlY2lmeSAkL2JldGFfMCQgYW5kICQvYmV0YV8xJCBteXNlbGY6CgpgYGB7ciBlY2hvID0gVH0Kc3NlIDwtIGZ1bmN0aW9uKGQsIGJldGFfMCwgYmV0YV8xKSB7CiAgcHJlZGljdGlvbnMgPC0gYmV0YV8wICsgYmV0YV8xICogZCR4CiAgcmVzaWR1YWxzIDwtIGQkeSAtIHByZWRpY3Rpb25zCiAgcmV0dXJuKHN1bShyZXNpZHVhbHNeMikpCn0KYGBgCgpPay4gTm93LCB0aGUgYGxtKClgIGZ1bmN0aW9uIGluIFIgY2FuIGZpbmQgdGhlIG9wdGltYWwgdmFsdWVzIG9mIHRoZSBwYXJhbWV0ZXJzICQvYmV0YV8wJCBhbmQgJC9iZXRhXzEkLCByaWdodD8KCmBgYHtyIGVjaG8gPSBUfQpzbHJfbW9kZWwgPC0gbG0oeSB+IHgsIGRhdGEgPSBkKQpjb2VmZmljaWVudHMoc2xyX21vZGVsKQpgYGAKCkxldCdzIHRlc3Qgb3VyIGBzc2UoKWAgZnVuY3Rpb246CgpgYGB7ciBlY2hvID0gVH0KYmV0YV8wX3Rlc3QgPC0gY29lZmZpY2llbnRzKHNscl9tb2RlbClbMV0KYmV0YV8xX3Rlc3QgPC0gY29lZmZpY2llbnRzKHNscl9tb2RlbClbMl0Kc3NlKGQgPSBkLCAKICAgIGJldGFfMCA9IGJldGFfMF90ZXN0LCAKICAgIGJldGFfMSA9IGJldGFfMV90ZXN0KQpgYGAKCkFuZCBmcm9tIGBsbSgpYCBhZ2FpbjoKCmBgYHtyIGVjaG8gPSBUfQpkIDwtIGRhdGEuZnJhbWUoeCA9IGlyaXMkU2VwYWwuTGVuZ3RoLAogICAgICAgICAgICAgICAgeSA9IGlyaXMkUGV0YWwuTGVuZ3RoKQpyZXNpZHVhbHMgPC0gc3VtbWFyeShsbSh5IH4geCwgZGF0YSA9IGQpKSRyZXNpZHVhbHMKcHJpbnQoc3VtKHJlc2lkdWFsc14yKSkKYGBgCgpTbyB3ZSBrbm93IHRoYXQgb3VyIGBzc2UoKWAgd29ya3MganVzdCBmaW5lLgoKTm93OiBob3cgY291bGQgaGF2ZSBkZXRlcm1pbmVkIHRoZSBvcHRpbWFsIHZhbHVlcyAtICp0aGUgZXJyb3IgbWluaW1pemluZyB2YWx1ZXMqIC0gb2YgJC9iZXRhXzAkIGFuZCAkL2JldGFfMSQgKndpdGhvdXQgcmVseWluZyBvbiogYGxtKClgPwoKT25lIGlkZWEgaXMgdG8gbW92ZSBhY3Jvc3MgdGhlIHNwYWNlIG9mIHRoZSBwYXJhbWV0ZXIgdmFsdWVzIGluIHNtYWxsIHN0ZXBzIGFuZCBjb21wdXRlIHRoZSBtb2RlbCBlcnJvciBhdCBlYWNoIHBvaW50IGluIHRoYXQgc3BhY2UsIGZvciBleGFtcGxlOgoKYGBge3IgZWNobyA9IFR9CnRlc3RfYmV0YV8wIDwtIHNlcSgtMTAsIDEwLCBieSA9IC4xKQp0ZXN0X2JldGFfMSA8LSBzZXEoLTEwLCAxMCwgYnkgPSAuMSkKbW9kZWxfZXJyb3JzIDwtIGxhcHBseSh0ZXN0X2JldGFfMCwgZnVuY3Rpb24oeCkgewogIHJldHVybigKICAgIHJiaW5kbGlzdCgKICAgICAgbGFwcGx5KHRlc3RfYmV0YV8xLCBmdW5jdGlvbih5KSB7CiAgICAgICAgcyA8LSBzc2UoZCA9IGQsIGJldGFfMCA9IHgsIGJldGFfMSA9IHkpCiAgICAgICAgcmV0dXJuKGRhdGEuZnJhbWUoc3NlID0gcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgYmV0YV8wID0geCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgYmV0YV8xID0geSkpCiAgICB9KSkKICApCn0pCm1vZGVsX2Vycm9ycyA8LSByYmluZGxpc3QobW9kZWxfZXJyb3JzKQpoZWFkKG1vZGVsX2Vycm9ycykKYGBgCgpXaGF0IHdvdWxkIGJlIHRoZSBtb3N0IG9wdGltYWwgZXN0aW1hdGVzIG9mIG9mICQvYmV0YV8wJCBhbmQgJC9iZXRhXzEkIHRoZW4/CgpgYGB7ciBlY2hvID0gVH0KbW9kZWxfZXJyb3JzW3doaWNoLm1pbihtb2RlbF9lcnJvcnMkc3NlKSwgXQpgYGAKCkNvbXBhcmU6CgpgYGB7ciBlY2hvID0gVH0KY29lZmZpY2llbnRzKHNscl9tb2RlbCkKYGBgCgpIbSwgbm90IGJhZD8KCiMjIyMgMi4yIFNhbXBsZSB0aGUgUGFyYW1ldGVyIFNwYWNlCgpBbm90aGVyIGlkZWEgdGhhdCBjb21lcyB0byBtaW5kIGlzIHRoZSBmb2xsb3dpbmcgb25lOiB3aHkgbm90IHRha2UgYSB1bmlmb3JtIHJhbmRvbSBzYW1wbGUgZnJvbSB0aGUgUGFyYW1ldGVyIFNwYWNlIGFuZCBjaGVjayBvdXQgdGhlIGBzc2UoKWAgYXQgdmFyaW91cyBwb2ludHMgZGVmaW5lZCBieSB0aGVpciAkL2JldGFfMCQgYW5kICQvYmV0YV8xJCBjb29yZGluYXRlcz8KCmBgYHtyIGVjaG8gPSBUfQpzYW1wbGVfcGFyYW1ldGVycyA8LSBkYXRhLmZyYW1lKGJldGFfMCA9IHJ1bmlmKDEwMDAwMCwgLTEwLCAxMCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJldGFfMSA9IHJ1bmlmKDEwMDAwMCwgLTEwLCAxMCkpCmhlYWQoc2FtcGxlX3BhcmFtZXRlcnMpCmBgYAoKTm93IGxldCdzIGZpbmQgdGhlIG1vZGVsIGVycm9ycyBhdCBlYWNoIHJhbmRvbWx5IHNhbXBsZWQgY29tYmluYXRpb24gb2YgcGFyYW1ldGVyczoKCmBgYHtyIGVjaG8gPSBUfQpzYW1wbGVfcGFyYW1ldGVycyRzc2UgPC0gYXBwbHkoc2FtcGxlX3BhcmFtZXRlcnMsIDEsIGZ1bmN0aW9uKHgpIHsKICAgIHNzZShkLCB4WzFdLCB4WzJdKQp9KQpoZWFkKHNhbXBsZV9wYXJhbWV0ZXJzKQpgYGAKCkFuZCB3aGF0IHdvdWxkIGJlIHRoZSBtb3N0IG9wdGltYWwgZXN0aW1hdGVzIG9mIG9mICQvYmV0YV8wJCBhbmQgJC9iZXRhXzEkIGluIHRoaXMgY2FzZT8KCmBgYHtyIGVjaG8gPSBUfQpzYW1wbGVfcGFyYW1ldGVyc1t3aGljaC5taW4oc2FtcGxlX3BhcmFtZXRlcnMkc3NlKSwgXQpgYGAKCkNvbXBhcmU6CgpgYGB7ciBlY2hvID0gVH0KY29lZmZpY2llbnRzKHNscl9tb2RlbCkKYGBgCgpIbT8KCioqTi5CLioqIEZpbmRpbmcgdGhlIG9wdGltYWwgdmFsdWVzIG9mIHRoZSBtb2RlbCdzIHBhcmFtZXRlcnMgaW1wbGllcyAqc29tZSogc29ydCBvZiBzZWFyY2ggdGhyb3VnaCB0aGUgUGFyYW1hdGVyIFNwYWNlLCBhbmQgcGlja2luZyB0aGUgdmFsdWVzIHRoYXQgbWluaW1pemUgdGhlIG1vZGVsIGVycm9yIGFzIG11Y2ggYXMgcG9zc2libGUuCgpCdXQgdGhlcmUgYXJlIGJldHRlciB3YXlzIHRvIGRvIGl0IHRoYW4gR3JpZCBTZWFyY2ggb3IgUmFuZG9tIFNhbXBsaW5nLiBBbmQgd2h3bmV2ZXIgdGhhdCBpcyBwb3NzaWJsZSwgdGhpcyBpcyB3aGF0IHdlIGRvIHRvIG91ciBzdGF0aXN0aWNhbCBsZWFybmluZyBtb2RlbHM6ICp3ZSBvcHRpbWl6ZSB0aGVtKi4KClBsZWFzZSBwYXkgY2xvc2UgYXR0ZW50aW9uIHRvIHdoYXQgZXhhY3RseSBpcyBoYXBwZW5pbmcgaW4gdGhlc2UgcHJvY2VkdXJlczoKCisgdGhlIGRhdGFzZXQgYGRgIGlzIGEgY29uc3RhbnQsIGl0IGRvZXMgbm90IGNoYW5nZSBpbiBhbnkgb2YgdGhlIGBzc2UoKWAgZnVuY3Rpb24ncyBydW47CisgdGhlIHBhcmFtZXRlcnMgJC9iZXRhXzAkIGFuZCAkL2JldGFfMSQgdmFyeSBpbiBzb21lIHdheSAodW50aWwgbm93OiBncmlkIHNlYXJjaCBvciByYW5kb20gdW5pZm9ybSBzYW1wbGluZyksIGFuZCAKKyB0aGUgYHNzZSgpYCBmdW5jdGlvbiAqZG9lcyBub3QgZXN0aW1hdGUgYW55dGhpbmcqIC0gaXQgaXMgbm90IGBsbSgpYCEgLSBidXQgY29tcHV0ZXMgdGhlIG1vZGVsIGVycm9yIGluc3RlYWQuIApTbyB3aGF0IGFyZSB3ZSByZWFsbHkgbG9va2luZyBmb3I/ICoqV2UgYXJlIGxvb2tpbmcgZm9yIGEgd2F5IHRvIGZpbmQgdGhlIG1pbmltdW0gb2Ygb3VyIGBzc2UoKWAgZnVuY3Rpb24uKioKCiMjIyMgMi4zIE9wdGltaXplIHRoZSBTaW1wbGUgTGluZWFyIFJlZ3Jlc3Npb24gbW9kZWwgdy4gYmFzZSBSIG9wdGltKCkKCkZpcnN0IHdlIG5lZWQgYSBzbGlnaHQgcmV3cml0ZSBvZiBgc3NlKClgIG9ubHk6CgpgYGB7ciBlY2hvID0gVH0Kc3NlIDwtIGZ1bmN0aW9uKHBhcmFtcykgewogIGJldGFfMCA8LSBwYXJhbXNbMV0KICBiZXRhXzEgPC0gcGFyYW1zWzJdCiAgIyAtIE1PREVMIElTIEhFUkU6CiAgcHJlZGljdGlvbnMgPC0gYmV0YV8wICsgYmV0YV8xICogZCR4CiAgIyAtIE1PREVMIEVORFMgSEVSRSBeXgogIHJlc2lkdWFscyA8LSBkJHkgLSBwcmVkaWN0aW9ucwogIHJldHVybihzdW0ocmVzaWR1YWxzXjIpKQp9CmBgYAoKKipOLkIuKiogQXMgdGhlIGRhdGFzZXQgYGRgIGlzIGEgY29uc3RhbnQsIGl0IGRvZXMgbm90IHBsYXkgYSByb2xlIG9mIGFuIGBzc2UoKWAgZnVuY3Rpb24gcGFyYW1ldGVyIGFueW1vcmUuCgpQaWNrIHNvbWUgcmFuZG9tLCBpbml0aWFsIHZhbHVlcyBmb3IgJC9iZXRhXzAkIGFuZCAkL2JldGFfMSQ6CgpgYGB7ciBlY2hvID0gVH0KYmV0YV8wX3N0YXJ0IDwtIHJ1bmlmKDEsIC0xMCwgMTApCmJldGFfMV9zdGFydCA8LSBydW5pZigxLCAtMTAsIDEwKQpwcmludChiZXRhXzBfc3RhcnQpCnByaW50KGJldGFfMV9zdGFydCkKYGBgCgpgYGB7ciBlY2hvID0gVH0Kc29sdXRpb24gPC0gb3B0aW0ocGFyID0gYyhiZXRhXzBfc3RhcnQsIGJldGFfMV9zdGFydCksIAogICAgICAgICAgICAgICAgICBmbiA9IHNzZSwgCiAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJOZWxkZXItTWVhZCIsCiAgICAgICAgICAgICAgICAgIGxvd2VyID0gLUluZiwgdXBwZXIgPSBJbmYpCmBgYAoKYGBge3IgZWNobyA9IFR9CnByaW50KHNvbHV0aW9uJHBhcikKYGBgCgpDb21wYXJlOgoKYGBge3IgZWNobyA9IFR9CmNvZWZmaWNpZW50cyhzbHJfbW9kZWwpCmBgYAoKTm93IHRoYXQgbG9va3MgZ3JlYXQhCgpXaGF0IGFyZSB3ZSByZWFsbHkgbG9va2luZyBhdCBoZXJlIGlzIHRoaXMgKGZpcnN0IHJlZHVjaW5nIHRoZSBgc2FtcGxlX3BhcmFtZXRlcnNgIGRhdGFmcmFtZSBhIGJpdC4uLiA6LSkKCmBgYHtyIGVjaG8gPSBUfQojIC0gYmFjayB0byB0aGUgb2xkIHZlcnNpb24gb2Ygc3NlKCk6CnNzZSA8LSBmdW5jdGlvbihkLCBiZXRhXzAsIGJldGFfMSkgewogIHByZWRpY3Rpb25zIDwtIGJldGFfMCArIGJldGFfMSAqIGQkeAogIHJlc2lkdWFscyA8LSBkJHkgLSBwcmVkaWN0aW9ucwogIHJldHVybihzdW0ocmVzaWR1YWxzXjIpKQp9CiMgLSBzYW1wbGUgcGFyYW1ldGVycyBvbiBhIGRpZmZlcmVudCByYW5nZSBmb3IgcGxvdHRpbmcgcHVycG9zZXMKc2FtcGxlX3BhcmFtZXRlcnMgPC0gZGF0YS5mcmFtZShiZXRhXzAgPSBydW5pZigxMDAwMDAsIC03LjUsIDcuNSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJldGFfMSA9IHJ1bmlmKDEwMDAwMCwgLTIsIDIpKQpzYW1wbGVfcGFyYW1ldGVycyRzc2UgPC0gYXBwbHkoc2FtcGxlX3BhcmFtZXRlcnMsIDEsIGZ1bmN0aW9uKHgpIHsKICAgIHNzZShkLCB4WzFdLCB4WzJdKQp9KQpoZWFkKHNhbXBsZV9wYXJhbWV0ZXJzKQpgYGAKCmBgYHtyIGVjaG8gPSBUfQpwbG90X2x5KCkgJT4lIAogIGFkZF90cmFjZShkYXRhID0gc2FtcGxlX3BhcmFtZXRlcnMsICAKICAgICAgICAgICAgeCA9IHNhbXBsZV9wYXJhbWV0ZXJzJGJldGFfMCwgCiAgICAgICAgICAgIHkgPSBzYW1wbGVfcGFyYW1ldGVycyRiZXRhXzEsIAogICAgICAgICAgICB6ID0gc2FtcGxlX3BhcmFtZXRlcnMkc3NlLCAKICAgICAgICAgICAgdHlwZSA9ICJtZXNoM2QiLCAKICAgICAgICAgICAgaW50ZW5zaXR5ID0gc2FtcGxlX3BhcmFtZXRlcnMkc3NlLCAKICAgICAgICAgICAgY29sb3JzY2FsZSA9ICJWaXJpZGlzIikgJT4lIAogIGxheW91dCgKICAgIG1vZGViYXIgPSBsaXN0KG9yaWVudGF0aW9uID0gInYiKSwgCiAgICB0aXRsZSA9ICJUaGUgU2ltcGxlIExpbmVhciBSZWdyZXNzaW9uIEVycm9yIExhbmRzY2FwZSIsCiAgICBzY2VuZSA9IGxpc3QoCiAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJCZXRhIDAiLCByYW5nZSA9IGMoLTcuNSwgNy41KSksCiAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJCZXRhIDEiLCByYW5nZSA9IGMoLTIsIDIpKSwKICAgICAgemF4aXMgPSBsaXN0KHRpdGxlID0gIlNTRSIpCiAgICApKQpgYGAKCgpgYGB7ciBlY2hvID0gVH0Kc2FtcGxlX3BhcmFtZXRlcnNbd2hpY2gubWluKHNhbXBsZV9wYXJhbWV0ZXJzJHNzZSksIF0KYGBgCkNvbXBhcmU6CgpgYGB7ciBlY2hvID0gVH0KY29lZmZpY2llbnRzKHNscl9tb2RlbCkKYGBgCgoKKioqCgojIyMgRnVydGhlciBSZWFkaW5ncwoKKyBbUmVncmVzc2lvbiwgYnkgRGF2aWQgTS4gTGFuZV0oaHR0cDovL29ubGluZXN0YXRib29rLmNvbS8yL3JlZ3Jlc3Npb24vcmVncmVzc2lvbi5odG1sKQorIFt7YnJvb219IHBhY2thZ2U6IFZpZ25ldHRlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvYnJvb20vdmlnbmV0dGVzL2Jyb29tLmh0bWwpCgoKIyMjIFIgTWFya2Rvd24KCisgW1IgTWFya2Rvd25dKGh0dHBzOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tLykgaXMgd2hhdCBJIGhhdmUgdXNlZCB0byBwcm9kdWNlIHRoaXMgYmVhdXRpZnVsIE5vdGVib29rLiBXZSB3aWxsIGxlYXJuIG1vcmUgYWJvdXQgaXQgbmVhciB0aGUgZW5kIG9mIHRoZSBjb3Vyc2UsIGJ1dCBpZiB5b3UgYWxyZWFkeSBmZWVsIHJlYWR5IHRvIGRpdmUgZGVlcCwgaGVyZSdzIGEgYm9vazogW1IgTWFya2Rvd246IFRoZSBEZWZpbml0aXZlIEd1aWRlLCBZaWh1aSBYaWUsIEouIEouIEFsbGFpcmUsIEdhcnJldHQgR3JvbGVtdW5kcy5dKGh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi8pIAoKKioqCkdvcmFuIFMuIE1pbG92YW5vdmnEhwoKRGF0YUtvbGVrdGl2LCAyMDIwLzIxCgpjb250YWN0OiBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tCgohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpCgoqKioKTGljZW5zZTogW0dQTHYzXShodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvZ3BsLTMuMC50eHQpClRoaXMgTm90ZWJvb2sgaXMgZnJlZSBzb2Z0d2FyZTogeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAzIG9mIHRoZSBMaWNlbnNlLCBvciAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLgpUaGlzIE5vdGVib29rIGlzIGRpc3RyaWJ1dGVkIGluIHRoZSBob3BlIHRoYXQgaXQgd2lsbCBiZSB1c2VmdWwsIGJ1dCBXSVRIT1VUIEFOWSBXQVJSQU5UWTsgd2l0aG91dCBldmVuIHRoZSBpbXBsaWVkIHdhcnJhbnR5IG9mIE1FUkNIQU5UQUJJTElUWSBvciBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgZm9yIG1vcmUgZGV0YWlscy4KWW91IHNob3VsZCBoYXZlIHJlY2VpdmVkIGEgY29weSBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgYWxvbmcgd2l0aCB0aGlzIE5vdGVib29rLiBJZiBub3QsIHNlZSA8aHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uCgoqKioKCg==