Quick Intro to R Notebooks and R Markdown

This is an R Markdown Notebook. When you execute R code within the notebook, the results appear beneath the code.

This file was created in RStudio by going to File…New File…R Notebook.

R code needs to be in “chunks” in an R Markdown Notebook. Below is an example of an R code chunk. It makes a parabola.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter (Win/Linux) or Cmd+Shift+Return (Mac).

x <- seq(-1, 1, by = 0.01)
y <- x^2
plot(x, y, type = "l")

To hide the output, click the Expand/Collapse output button. To clear results (or an error), click the “x”.

You can also press Ctrl+Enter (Win/Linux) or Cmd+Return (Mac) to run one line of code at a time (instead of the entire chunk).

Add a new R code chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I (Win/Linux) or Cmd+Option+I (Mac).

CODE ALONG 0

Insert a new R code chunk below and type and run the code: Sys.time()

Linear Modeling with Simulated Data

Instead of using theory and formulas, let’s explore and review linear modeling using simulated data.

Below we assign to x the values 1 - 25. Then we generate y as a function of x using the formula 10 + 5*x:

x <- 1:25
y <- 10 + 5*x  # formula for a line
d <- data.frame(x, y)
plot(y ~ x, data = d)

Now let’s add some “noise” to our data by adding random draws from a Normal distribution with mean = 0 and a standard deviation = 10. The rnorm() function allows us to draw random values from a Normal distribution.

set.seed(1) ensures we all generate the same “random” data:

set.seed(1)
noise <- rnorm(n = 25, mean = 0, sd = 10)
# Add the noise to 10 + 5*x and re-draw plot
d$y <- 10 + 5*x + noise
plot(y ~ x, data = d)

Now y appears to be associated with x, but not completely determined by x.

y is the combination of a fixed part and a random part:

  1. fixed: 10 + 5*x
  2. random: rnorm(n = 25, mean = 0, sd = 10)

What if we were given this data and told to determine the process that generated it? In other words, work backwards and fill in the blanks:

  1. ___ + ___*x
  2. rnorm(n = 25, mean = 0, sd = ____)

That’s one way to think of linear modeling/regression. You have some numeric response (or dependent) variable, and you want to find the model (or the formula) that generated the data.

Traditional linear modeling/multiple regression assumes the following (among others):

  1. the formula is a weighted sum of predictors (eg, y = 10 + 5*x)
  2. the noise is a random draw from a Normal distribution with mean = 0
  3. the standard deviation of the Normal distribution is constant (eg, 10)

Linear modeling tries to recover the weights in the first assumption and the standard deviation in the third assumption.

Let’s demonstrate. Below we attempt to recover the data generating process for our data. For this we use the lm() function. We have to specify the formula for the first assumption. The 2nd and 3rd assumptions are built into lm().

The formula “y ~ x” means we think the model is “y = intercept + slope*x”. (Unless we specify otherwise, this assumes we want to estimate an intercept.) This tells lm() to take our data and find the best intercept and slope. Notice this is the correct model!

When you use lm() you’ll usually want to save the results to an object. Below I save it to “mod”. Then we view the results of the model using summary()

mod <- lm(y ~ x, data = d)
summary(mod)

Call:
lm(formula = y ~ x, data = d)

Residuals:
    Min      1Q  Median      3Q     Max 
-23.876  -4.613   2.254   5.909  14.648 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   11.135      3.999   2.784   0.0105 *  
x              5.042      0.269  18.743 1.98e-15 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 9.7 on 23 degrees of freedom
Multiple R-squared:  0.9386,    Adjusted R-squared:  0.9359 
F-statistic: 351.3 on 1 and 23 DF,  p-value: 1.981e-15

The model returns the following estimates:

  1. y = 11.135 + 5.042 * x
  2. noise = rnorm(n = 25, mean = 0, sd = 9.7)

These are pretty close to the “true” values of 10, 5, and 10 we used to generate the data.

In real life, we DO NOT KNOW the formula in part 1. The real data generation process will be far more complicated. The formula we propose will just be an approximation and may not be good.

In real life, we DO NOT KNOW if the Normality assumption or constant variance assumption of the noise is plausible.

How can we evaluate our model formula?

We could use our model estimates to generate data and see if they look similar to our original data. Run entire chunk at once and run more than once. The black points don’t change but the red ones do. That looks pretty good! Our model-generated data appears similar to our observed data.

# d$y <- 10 + 5*x + noise
d$y2 <- 11.135 + 5.042*d$x + rnorm(25, 0, 9.7)
plot(y ~ x, data = d) # original data
points(d$x, d$y2, col = "red") # simulated data

We can also compare smooth density curves of the original and model-generated data. Smooth density curves are basically smooth versions of histograms. If we have a good model, data generated by our model should have a similar distribution to the original data. Run entire chunk at once and run more than once.

hist(d$y, freq = FALSE) # freq = FALSE means area of bars sums to 1
lines(density(d$y))  # original data
d$y2 <- 11.135 + 5.042*d$x + rnorm(25, 0, 9.7)
lines(density(d$y2), col = "red")  # simulate data

This looks good as well. The distribution of our model-generated data is very similar to our observed data. You should do this more than once, say 50 times, to ensure the model consistently generates data similar to the observed data. We show one way to do that later in the workshop.

Since we think our model is “good”, we might use it to make a prediction. For example, when x = 10 what’s the expected value of y? Put another way, what’s the mean of y conditional on x = 10? We can do that with the predict() function. The interval = "confidence" arguments says return a 95% confidence interval (CI) for this mean.

predict(mod, newdata = data.frame(x = 10), interval = "confidence")
       fit     lwr      upr
1 61.55932 57.2126 65.90603

The expected mean of y when x = 10 is about 61.6 with a 95% CI of (57.2, 65.9). The CI gives us some notion of how uncertain this expected mean is. In fact it would be better to report this as, “the expected mean of y when x = 10 is about 57 to 66.”

We might also try to summarize the relationship between y and x by examining the coefficients (or weights) in the summary output. We can extract the coefficients from the summary with the coef() function:

coef(summary(mod))
             Estimate Std. Error   t value     Pr(>|t|)
(Intercept) 11.134859  3.9994866  2.784072 1.054874e-02
x            5.042446  0.2690346 18.742742 1.981382e-15

The x coefficient says that y increases by about 5 for every one-unit increase in x, give or take about 0.27. The standard error gives us some indication of the uncertainty in this estimate. We talk more about t values and p values below.

TO SUMMARIZE: This is basic linear modeling:

  1. propose and fit a model
  2. determine if the model is good and that assumptions are mostly met
  3. use the model to explain relationships and/or make predictions

CODE ALONG 1

Let’s see what happens when we fit a bad model. Below we add a new column to data frame d named z, which is a random sample of numbers on the range of -100 to 100. runif() samples numbers from a uniform distribution.

set.seed(4)
d$z <- runif(25, min = -100, max = 100)

REMINDER: Add a new R code chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I (Win/Linux) or Cmd+Option+I (Mac).

  1. Model y as a function of z using lm(y ~ z, data = d) and save to an object called mod2. View the summary. What formula does the model return? What is the estimated standard deviation of the Normally distributed noise?

  2. Use the model to simulate density histograms and compare to the original density histogram of d$y. Run the chunk several times to see how the model-generated density curve varies.

Let’s do some linear modeling with real data.

Import data

Let’s import the data we’ll be using today. The data we’ll work with is Albemarle County real estate data which was downloaded from the Office of Geographic Data Services. We’ll use a random sample of the data.

URL <- 'https://raw.githubusercontent.com/clayford/dataviz_with_ggplot2/master/alb_homes.csv'
homes <- read.csv(file = URL)
homes$hsdistrict <- factor(homes$hsdistrict)
homes$cooling <- factor(homes$cooling)

Let’s look at the first few rows:

head(homes)

Variable name definitions:

Linear Modeling with Real Estate Data

Let’s say we want to model the mean total value of a home as a function of various characteristics such as lot size, finished square feet, presence of central air, etc.

Let’s see how total value is distributed using a histogram. Notice it’s very skewed.

hist(homes$totalvalue)

We can also create a smooth density plot, which is a smooth version of a histogram:

plot(density(homes$totalvalue))

In order to model mean totalvalue of homes as a function of various characteristics, we need to propose a linear model. Unlike the previous example this is not simulated data for which we know the data generating process. How to propose a model? It helps to have some subject matter expertise.

Let’s fit a linear model using finsqft, bedrooms and lotsize. The plus (+) sign means “include” in model.

m1 <- lm(totalvalue ~ finsqft + bedroom + lotsize, data = homes)
summary(m1)

Call:
lm(formula = totalvalue ~ finsqft + bedroom + lotsize, data = homes)

Residuals:
     Min       1Q   Median       3Q      Max 
-1164152   -82296    -7690    57164  5879188 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept) -133328.25   16273.88  -8.193 3.73e-16 ***
finsqft         284.46       5.37  52.967  < 2e-16 ***
bedroom      -13218.41    5750.70  -2.299   0.0216 *  
lotsize        4268.77     219.32  19.464  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 227200 on 3021 degrees of freedom
Multiple R-squared:  0.6164,    Adjusted R-squared:  0.616 
F-statistic:  1618 on 3 and 3021 DF,  p-value: < 2.2e-16

The coef() function extracts the coefficients (or weights):

coef(m1)
 (Intercept)      finsqft      bedroom      lotsize 
-133328.2482     284.4613  -13218.4091    4268.7655 

This translates to:

totalprice = -133328.2482 + 284.4613*finsqft + -13218.4091*bedroom +
               4268.7655*lotsize`

Some naive interpretation:

Each of these interpretations assumes all other variables are held constant! So adding a bedroom to a house, without increasing the lot size or finished square feet of the house, is estimated to drop the value of a home. Does this make sense?

Is this a “good” model? Let’s simulate data from the model and compare it to our observed data. A “good” model should generate data that looks similar to the original data.

We could do this by hand:

sim_values <- -133328.2482 + 284.4613*homes$finsqft + 
  -13218.4091*homes$bedroom + 4268.7655*homes$lotsize + 
  rnorm(3025, mean = 0, sd = 227200)

An easier and faster way is to use the simulate() function which allows you to generate multiple samples. Here we generate 50 samples. Each sample will have the same number of observations as our original sample (n = 3025). Each sample value is generated using our observed values for finsqft, bedroom, and lotsize. The result is a data frame with 50 columns.

sim1 <- simulate(m1, nsim = 50)

Now let’s plot our simulated data with our observed data using smooth density plots. We use a for loop to add smooth density estimates of the 50 simulations. The syntax sim1[[i]] extracts column i as a vector. (Run entire chunk at once.)

plot(density(homes$totalvalue))
for(i in 1:50)lines(density(sim1[[i]]), col = "grey80")

(See end of notebook for how to create this plot using ggplot2 and for how to turn this into a function.)

This does not appear to be a good model. In fact some of our simulated values are negative!

Before we revise our model recall the main assumptions:

  1. totalvalue can be modeled by a weighted sum: totalvalue = Intercept + finsqft + bedrooms + lotsize
  2. noise/error is from Normal dist’n with mean 0
  3. the SD of the Normal dist’n is constant

R provides some basic diagnostic plots to assess 2 and 3. Just call plot on your model object

plot(m1)

How to interpret plots:

  1. Residuals vs Fitted: for checking constant variance; should have a horizontal line with uniform and symmetric scatter of points; if not, evidence that SD is not constant

  2. Normal Q-Q: for checking normality of residuals; points should lie close to diagonal line; if not, evidence that noise is not drawn from N(0, SD). This assumption is only really critical if you’re planning to use your model to predict exact values (instead of means) and calculate a confidence interval.

  3. Scale-Location: for checking constant variance; should have a horizontal line with uniform scatter of point; (similar to #1 but easier to detect trend in dispersion)

  4. Residuals vs Leverage: for checking for influential observations; points outside the contour lines are influential observations. Leverage is the distance from the center of all predictors. An observation with high leverage has substantial influence on the fitted value.

By default the 3 “most extreme” points are labeled by row number. 2658 appears in all four plots. It’s a really big expensive home.

homes[2658,]

These plots reveal that our assumptions of normally distributed residuals and constant variance are highly suspect. Our model is just bad.

What can we do?

Non-constant variance can be evidence of a wrong model or a very skewed response (or a bit of both). Recall that our response is quite skewed:

hist(homes$totalvalue)

When dealing with a response that is strictly positive and very skew (like dollar amounts), it is common to transform the response to a different scale. A common transformation is a log transformation. When we log transform totalvalue, the distribution looks a little more symmetric, though it’s important to note that is not an assumption of linear modeling!

hist(log(homes$totalvalue))

Let’s try modeling log-transformed totalvalue.

m2 <- lm(log(totalvalue) ~ finsqft + bedroom + lotsize, data = homes)

The diagnostic plots look better.

plot(m2)

But is this a “good model”? Is our proposed model of weighted sums good? Let’s simulate data and compare to observed data.

sim2 <- simulate(m2, nsim = 50)
plot(density(log(homes$totalvalue)))
for(i in 1:50)lines(density(sim2[[i]]), lty = 2, col = "grey80")

This doesn’t look too bad!

Let’s say we’re happy with this model. How do we interpret the coefficients? Since the response is log transformed, we interpret the coefficients as multiplicative instead of additive. Below we view the coefficients rounded to 4 decimal places.

round(coef(m2), 4)

These are proportions. To get percentages, multiply by 100.

round(coef(m2), 4) * 100

Some naive interpretations:

Remember, the interpretation assumes all other variables are held constant!

A slightly more precise estimation can be obtained by exponentiating the coefficients. Below we exponentiate using the exp() function and then round to 4 decimal places.

round(exp(coef(m2)), 4) 

For example, each additional bedroom (assuming all else held constant) increases expected total price by about 4.4%. Multiplying by 1.0439 is equivalent to adding 4.39%.

Let’s review the summary output:

summary(m2)

SUMMARY OVERVIEW

All of the p-values refer to hypothesis tests that the coefficients are 0. Many statisticians and researchers prefer to look at confidence intervals.

round(confint(m2) * 100, 4)

According to our model, each additional bedroom adds about 3% to 6% to the value of a home, assuming all else held constant.

CODE ALONG 2

  1. Insert a code chunk below and model log(totalvalue) as function of fullbath and finsqft. Call your model m3

  2. Insert a code chunk below and check the diagnostic plots

  3. How do we interpret the fullbath coefficient?

  4. Insert a code chunk below and simulate data from the model and compare to the observed totalvalue. Does this look like a good model?

Categorical predictors

Let’s add hsdistrict to the model we just fit. Does being in a particular high school district affect total value of a home? Here’s a quick tally:

table(homes$hsdistrict)

These levels are not numbers, so how does R handle this in a linear model? It creates a contrast. This is a matrix of zeroes and ones. If you have K levels, you’ll have K-1 columns. In this case we’ll have two columns: one for Monticello HS and one for Western Albemarle HS. By default R takes whatever level comes first alphabetically and makes it the baseline or reference level.

Let’s look at the default contrast, called a treatment contrast. To do this we convert the hsdistrict column to factor and then use the contrasts() function. (We don’t have to do this to add hsdistrict to our model! I’m just doing this to output the contrast “definition”)

homes$hsdistrict <- factor(homes$hsdistrict)
contrasts(homes$hsdistrict)

A model with hsdistrict will have two coefficients: Monticello and Western Albemarle

Let’s fit our new model.

m4 <- lm(log(totalvalue) ~ fullbath + finsqft + hsdistrict, data = homes)
summary(m4)

The coefficients for Monticello and Western Albemarle are in relation to Albemarle HS.

round(coef(m4) * 100, 4)

It appears that the value of a home in Western Albemarle will be about 10% higher than an equivalent home in Albemarle. Likewise it appears that the value of a home in the Monticello district will be about 7% less than an equivalent home in the Albemarle district.

CODE ALONG 3

  1. Insert a code chunk below and model log(totalvalue) as function of fullbath, finsqft and cooling. Call your model m5.

  2. What is the interpretation of cooling?

Modeling Interactions

In our model above that included hsdistrict we assumed the effects were additive. For example, it didn’t matter what high school district your home was in, the effect of fullbath or finsqft was the same. It also assumed the effect of each additional fullbath was the same regardless of how big the house was, and vice versa. This may be too simple.

Interactions allow the effects of variables to depend on other variables. Again subject matter knowledge helps with the proposal of interactions. As we’ll see interactions make your model more flexible but harder to understand.

R makes it simple to include interactions in models. Just indicate an interaction between two variables by placing a colon (:) between them. Below we include 2-way interactions. (You can have 3-way and higher interactions but they’re very difficult to interpret.)

m6 <- lm(log(totalvalue) ~ fullbath + finsqft + hsdistrict + 
           fullbath:finsqft + fullbath:hsdistrict + 
           finsqft:hsdistrict, data = homes)
summary(m6)

Interpretation is now much more difficult. We cannot directly interpret the main effects of fullbath, finsqft or hsdistrict. They interact. What’s the effect of finsqft? It depends on fullbath and hsdistrict.

Are the interactions “significant” or necessary? We can use the anova() function to evaluate this question. This runs a series of partial F-tests. Each row below is a hypothesis test. The null is the model with this predictor is the same as the model without the predictor. The anova tests below use what are called Type I sums of squares. This respects the order of the variables in the model. So…

If the null is true, the F value should be close to 1.

anova(m6)

The interaction finsqft:hsdistrict doesn’t appear to contribute much to the model given that everything else above it is in the model.

Just because an interaction is significant doesn’t necessarily mean it’s interesting or worthwhile. Nor can we infer anything about the nature of the interaction from the ANOVA table.

Effect plots can help us visualize and make sense of models with interactions. Let’s make one using the ggeffects package and talk about what it’s showing.

library(ggeffects)
plot(ggpredict(m6, terms = c("fullbath", "hsdistrict")))
# place fullbath on x-axis, group by hsdistrict

What’s the effect of fullbath? It depends. It’s more dramatic in Western Albemarle and Monticello. Of course a lot of the difference comes at extreme values of fullbath. The “ribbons” around the lines are 95% confidence intervals.

What exactly was plotted? We can see by calling ggpredict without plot

ggpredict(m6, terms = c("fullbath", "hsdistrict"))

ggpredict used our model to make totalvalue predictions for various values of fullbath in the three school districts, holding finsqft at 1828 (the median of finsqft).

We can specify our values if we like. For example, make an effect plot for 1 - 5 bathrooms and hold finsqft at 2000:

plot(ggpredict(m6, terms = c("fullbath[1:5]", "hsdistrict"), 
          condition = c(finsqft = 2000)))

What about the effects of finsqft and fullbath? This is an interaction of two numeric variables. The second variable has to serve as a grouping variable when creating an effect plot. Below we set fullbath to take values 2 - 5 and finsqft to take values of 1000 - 4000 in steps of 500.

plot(ggpredict(m6, terms = c("finsqft[1000:4000 by=500]", "fullbath[2:5]")))

The effect of finsqft seems to taper off the more fullbaths a house has. But there are few large homes with 2 full baths, and likewise, few small homes with 5 full baths. Even though the interaction is “significant” in the model, it’s clearly a very small interaction and probably not important.

CODE ALONG 4

  1. Insert a code chunk below and model log(totalvalue) as function of fullbath, finsqft, cooling, and the interaction of finsqft and cooling. Call your model m7. Is the interaction warranted?

  2. Visualize the interaction using the ggpredict function. Perhaps use [1000:4000 by=500] to set the range of finsqft on the x-axis. How notable is this interaction?

Nonlinear Effects

So far we have assumed that the relationship between a predictor and the response is linear (eg, for a 1-unit change in a predictor, the response changes by a fixed amount). That assumption can sometimes be too simple and not realistic. Fortunately there are ways to fit non-linear effects in a linear model.

Here’s a quick example of simulated non-linear data: a 2nd degree polynomial.

x <- seq(from = -10, to = 10, length.out = 100)
set.seed(3)
y <- 1.2 + 2*x + 0.9*x^2 + rnorm(100, mean = 0, sd = 10)
nl_dat <- data.frame(y, x)
plot(y ~ x, nl_dat)

Clearly a straight-line model will not work well for this data. The relationship between x and y is not adequately summarized by a straight line model.

If we wanted to try to “recover” the weights we used in simulating this data we could fit a polynomial model using the poly() function in the formula syntax:

## EXAMPLE CODE; NOT INTENED TO BE RUN
nlm1 <- lm(y ~ poly(x, degree = 2, raw = TRUE), data = nl_dat)

However, the recommended approach to fitting non-linear effects is to use natural splines instead of polynomials. Natural splines essentially allow us to fit a series of cubic polynomials connected at knots located in the range of our data.

The easiest option is to use the ns() function from the splines package, which comes installed with R. ns stands for “natural splines”. The second argument is the degrees of freedom (df). It may help to think of df as the number of times the smooth line changes directions.

Frank Harrell states in his book Regression Model Strategies that 3 to 5 df is almost always sufficient. His basic advice is to allocate more df to variables you think are more important.

Let’s see how it works with our simulated data.

library(splines)
nlm2 <- lm(y ~ ns(x, df = 2), data = nl_dat)
summary(nlm2)

The summary output is impossible to interpret. Let’s visualize the fit with an effect plot.

library(ggeffects)
plot(ggpredict(nlm2, terms = "x"), show_data = TRUE)

Let’s return to the homes data and fit a non-linear effect for finsqft using a natural spline with 5 df. Below we also include hsdistrict and lotsize and allow finsqft and hsdistrict to interact.

nlm3 <- lm(log(totalvalue) ~ ns(finsqft, 5) + lotsize + hsdistrict +
           ns(finsqft, 5):hsdistrict, 
         data = homes)

The anova function allows us to assess the non-linear effect and the interaction. Some sort of interaction between finsqft and hsdistrict appears to be present.

anova(nlm3)

The summary output is impossible to interpret.

summary(nlm3)

Effect plots are our only hope for understanding this model. Below we plot predicted totalvalue over finsqft ranging from 1000 - 3000, grouped by hsdistrict.

plot(ggpredict(nlm3, terms = c("finsqft[1000:3000 by=250]", "hsdistrict")))

The effect of finsqft on totalvalue seems more dramatic in Western Albemarle once you go beyond 1500 sq ft.

Does the model simulate data similar in distribution to the observed data?

sim4 <- simulate(nlm3, nsim = 50)
plot(density(log(homes$totalvalue)))
for(i in 1:50)lines(density(sim4[[i]]), col = "grey80")

We should still check model assumptions.

plot(nlm3)

Homes 12, 40, 963 and 1810 seem to stand out. Let’s have a look.

h <- c(12, 40, 963, 1810)
homes[h,c("totalvalue", "finsqft", "lotsize")]

Homes 12 and 40 are very low in totalvalue and the model way overpredicts their values. Home 963 has a massive totalvalue with 0 acres of lotsize. Home 1810 is on 611 acres and that value has a high leverage on its fitted value.

cbind(observed = homes$totalvalue[h], fitted = exp(fitted(nlm3)[h]))

CODE ALONG 5

  1. Insert a code chunk below and model log(totalvalue) as function of finsqft with a natural spline of 5 df, cooling, and the interaction of cooling and finsqft (natural spline of 5 df). Call your model nlm4.

  2. Use the anova function to check whether the interaction appears necessary. What do you think?

  3. Create an effect plot of finsqft by cooling. Maybe try [1000:5000 by=250] for the range of values for finsqft.

Wrap-up

This was meant to show you the basics of linear modeling in R. Hopefully you have a better grasp of how linear modeling works. Scroll down for a few more topics.

What we did today works for independent, numeric outcomes. We had one observation per home and our response was totalvalue, a number. Our models returned expected mean total value given various predictors. This is a pretty simple design.

Things get more complicated when you have, say, binary responses or multiple measures on the same subject (or home). A non-exhaustive list of other types of statistical modeling include:

References

Bonus material/topics cut for time

How does lm() work?

lm() estimates regression coefficients using ordinary least squares, or OLS. The formula for this using matrix algebra is expressed as follows:

\[\hat{\beta} = (X'X)^{-1}X'Y \]

where X is the model matrix (our predictors as they appear in the model) and Y is the dependent variable. The prime symbol ' means transpose the matrix. The -1 means take the inverse. R was developed to make calculations such as these quite easy.

Let’s revisit model m2.

formula(m2)

Here are the coefficients returned by lm():

round(coef(m2), 4)

Let’s find these using the formula above. We can use model.matrix() to get X, t() to transpose, and solve() to take the inverse. Perform matrix multiplication with %*%

X <- model.matrix(~ finsqft + bedroom + lotsize, data = homes)
Y <- log(homes$totalvalue)
B <- solve(t(X) %*% X) %*% t(X) %*% Y
round(B, 4)

While this is theoretically what lm() does, it actually uses more sophisticated methods for faster performance and protection against numeric instability. This page goes into more details if you’re interested in learning more:

https://genomicsclass.github.io/book/pages/qr_and_regression.html

ANOVA revisited

We can also use the anova() function to compare nested models. This means one model is a subset of another. For example, below we fit progressively more complicated models, building up to our model, m2: log(totalvalue) ~ finsqft + bedroom + lotsize

mod_00 <- lm(log(totalvalue) ~ 1, data = homes) # intercept-only
mod_01 <- lm(log(totalvalue) ~ finsqft, data = homes)
mod_02 <- lm(log(totalvalue) ~ finsqft + bedroom, data = homes)
mod_03 <- lm(log(totalvalue) ~ finsqft + bedroom + lotsize, data = homes)

The anova() function allows us to compare these nested models. The null hypothesis is that two models are the same, in the sense they explain the same amount of variance in the response variable, log(totalvalue). A low p-value provides evidence that the more complicated model with more variables is a better model. The usual way to use anova() to compare models is to list the smaller models first. Below are three tests:

  1. Model 2 vs Model 1
  2. Model 3 vs Model 2
  3. Model 4 vs Model 3

The end result is that model 4 is superior to the other three models.

anova(mod_00, mod_01, mod_02, mod_03)

Notice this identical to calling anova() on the full model.

anova(m2)

Another approach is using Type II sums of squares, where each variable is tested assuming all other variables are in the model. One approach to performing this test is the drop1() function. The name comes from the fact we’re dropping one variable at a time. The null hypothesis is dropping the variable from the model has no effect. A low p-value provides evidence against this hypothesis. Below each are three tests:

  1. Full model vs Full model without finsqft
  2. Full model vs Full model without bedroom
  3. Full model vs Full model without lotisze

Each test is soundly rejected. The full model is much better with all three predictors.

drop1(m2, test = "F")

This test is also implemented in the Anova() function in the car package.

# install.packages("car")
library(car)
Anova(m2)

AIC and BIC

AIC (Akaike Information Criterion) is a statistic designed to help us choose a model with the best predictive power among a group of models. The value of AIC doesn’t really have any interpretation. It’s meant to be compared to other AIC values. The lower the AIC the better. We can use the AIC() function in R to compare multiple models. For example, mod_03 seems preferable to mod_02 because the AIC value is so much smaller.

AIC(mod_02, mod_03)

AIC is the log-likelihood of the model multiplied by -2 with 2 x df added to it. The 2 x df part is a penalty. “df” is short for “degrees of freedom” and is the number of parameters estimated in the model. This includes the residual standard error. For example, mod_03 has 5 degrees of freedom because there are 4 model coefficients and the residual standard error. We call this part a “penalty” because AIC can get bigger with more coefficients.

Log-likelihood is the log-transformed likelihood. Likelihood is the joint probability of the observed data as a function of the parameters of the chosen statistical model. Imagine turning the coefficients in the model like dials on a machine and trying to find the maximum likelihood. In other words, what combination of coefficients are most likely to produce the data we observed? R does not estimate linear model coefficients using maximum likelihood, but we can calculate the log likelihood after the fact using the logLik() function:

logLik(mod_03)

We can then calculate AIC “by-hand” as follows.

rbind("mod_02" = -2*logLik(mod_02) + 2*4, 
      "mod_03" = -2*logLik(mod_03) + 2*5)

The AIC is inclined to choose overly complex models, so some researchers prefer BIC (Bayesian Information Criterion), which places a bigger penalty on the number of predictors. Again use the BIC() function in the same way.

BIC(mod_02, mod_03)

The BIC is calculated the same as the AIC but with a different penalty, log(n), where n is the number of observations. Again we can calculate “by hand”:

rbind("mod_02" = -2*logLik(mod_02) + log(nrow(homes))*4, 
      "mod_03" = -2*logLik(mod_03) + log(nrow(homes))*5)

Of course, you don’t have to choose one. You can use both AIC and BIC and report both. They will often choose the same models.

Collinearity

When predictors are highly correlated (ie, have strong linear relationships), the precision of coefficient estimates can decline. This phenomenon is often referred to as collinearity or multicollinearity. Let’s demonstrate with a toy example. First we generate two variables, x1 and x2, that are highly correlated:

x1 <- seq(1,10,length = 100)
set.seed(123)
x2 <- 1 + 2*x1 + rnorm(100, sd = 0.2)
cor(x1, x2)  # calculate correlation; perfect correlation = 1

Now we generate a new variable, y, using x1 and x2 along with some noise and fit a linear model to recover the true coefficients of 2 and 3. Notice in the pairwise scatterplot that x1 and x2 both seem associated with y in the same way.

set.seed(321)
y <- 0.5 + 2*x1 + 3*x2 + rnorm(100, sd = 10)
d_collinear <- data.frame(y, x1, x2)
pairs(d_collinear)

When we fit the model, our estimated coefficients are way off and have enormous standard errors. The “true” values are 2 and 3. The model estimates are about 12 and -2! Recall we interpret the x1 coefficient as its effect on y holding x2 constant. But since x1 and x2 are highly correlated, it’s all but impossible to estimate the effect of x1 while holding x2 constant.

mod_colinear <- lm(y ~ x1 + x2)
summary(mod_colinear)

The most common way to check for and quantify collinearity after fitting a model is calculating variance inflation factors (VIF). The details of the calculation can be found with a web search, but the basic idea is that if one of the raw VIFs is greater than 10, then we may have evidence that collinearity is influencing coefficient estimates. Fortunately this is easy to do using the vif() function in the car package. The VIFs are sky high for our contrived predictors!

library(car)
vif(mod_colinear)

The square root of the VIF can be interpreted as how much larger the standard error of the coefficient is relative to similar uncorrelated data.

vif(mod_colinear) |> sqrt()

This says the standard error for both variables is about 29 times larger than it would have been without collinearity. The best solution in this case would be to simply drop one of the variables. It appears x1 is almost completely determined by x2 and vice versa. Knowing one means you know the other. In the extreme case, when two variables are perfectly correlated, the model fitting procedure will return NA for one of the variables, as demonstrated below. Notice the message: “(1 not defined because of singularities)”

x2 <- 2*x1  # perfect correlation
summary(lm(y ~ x1 + x2))

Let’s check our m2 model where we modeled log(totalprice) as a function of finsqft, bedroom and lotsize:

vif(m2)

These VIFs look very good. We might suspect that finsqft and number of bedrooms could be highly correlated, but the VIF checks out.

One approach to addressing collinearity concerns is to use a data reduction technique such as principal components analysis (PCA). When it works, this method basically takes several variables and reduces them to one or two summary scores. This may be preferable to arbitrarily dropping variables.

For models that are intended for making predictions, collinearity is not much of a concern.

Transformation guidelines

Above we log-transformed totalvalue to help meet modeling assumptions. Recall without the log transformation our residuals were large and skewed, which is a fancy way of saying our model was a bad fit. A good fitting model should have relatively small residuals with even scatter.

A log-transformation made sense for two reasons:

  1. totalvalue was strictly positive, had a large upper bound, and covered several orders of magnitude.
  2. changes in totalvalue according to the predictors were relative (multiplicative) and not absolute (additive), which corresponds to the natural log scale.

It’s important to note that not all skewed variables need to be transformed when it comes to linear modeling. The distributional assumptions are on the residuals. However there may be times when you need to investigate transformations other than the log when it comes to modeling. These almost always take the form of a power transformation (ie, raising your variable to a power using an exponent). Powers are usually symbolized with a Greek lambda (λ). A power of 0 translates to a log transformation.

Say your variable is y. A basic palette of possible power transformations include:

The car function symbox() creates a visual assessment of which power makes the distribution reasonably symmetric. Below when we use it with totalvalue we see that the log transformation (λ = 0) does the best job of making the distribution more symmetric.

symbox(homes$totalvalue)

We can also use symbox() on a model object. For example, this produces essentially the same plot using the residuals of the model instead of totalvalue. Simply pipe the model into symbox().

lm(totalvalue ~ finsqft + bedroom + lotsize, data = homes) |>
  symbox()

A statistical “search” for the “best” power transformation can be performed with the powerTransform() function, also in the car package. The usual practice is to convert the result to the closest simple power listed above. For example, we can pipe the model result into powerTransform() and see the “best” transformation is about 0.16.

lm(totalvalue ~ finsqft + bedroom + lotsize, data = homes) |>
  powerTransform() 

0.16 is close to 0, so it makes sense to proceed with a log transformation. That greatly simplifies interpretation.

ggplot2 code for creating plot of simulated data

Here’s how to make the simulation plot using ggplot2. I find base R graphics easier for this type of plot.

sim2 <- simulate(m2, nsim = 50)
library(ggplot2)
library(tidyr)
sim2 %>% 
  pivot_longer(everything(), 
               names_to = "simulation", 
               values_to = "totalvalue") %>% 
  ggplot() +
  geom_density(mapping = aes(x = totalvalue, group = simulation),
               color = "grey80") + 
  geom_density(mapping = aes(x = log(totalvalue)),
               data = homes)  

How to make a function for simulating and plotting data for a linear model

base R

We basically copy and paste the original code in between the curly braces of the function() function. We call the function plot_sims but you can name it whatever you like. The changes are to the model name and number of simulations. We generalize those with arguments: mod and nsim. When we fit a model with lm, the data is stored with the model object by default and can be accessed as mod$model. The first column contains the model response, so we can access it with [,1] and use it to draw the density of the observed data.

plot_sims <- function(mod, nsim){
  sim <- simulate(mod, nsim = nsim)
  plot(density(mod$model[,1]))
  for(i in 1:nsim)lines(density(sim[[i]]), col = "grey80")
}
# try the function
plot_sims(mod = m2, nsim = 20)

ggplot2

Same idea as the previous function: we want to copy the original code in between the curly braces of the function() function. Except we now want to preface functions with their package name (eg, ggplot2::) so we can use the function without having the packages loaded. We also do away with the pipe %>% since it comes from yet another package (magrittr) but is accessible when tidyr is loaded. We extract the name of the response using resp <- names(mod$model)[1] and then use the use the .data pronoun (.data[[resp]]) from rlang package to use it with ggplot.

plot_sims <- function(mod, nsim){
  sim1 <- simulate(mod, nsim = nsim)
  resp <- names(mod$model)[1]
  sim1 <- tidyr::pivot_longer(sim1, everything(), 
               names_to = "simulation", 
               values_to = "totalvalue") 
  ggplot2::ggplot() +
  ggplot2::geom_density(mapping = ggplot2::aes(x = totalvalue, 
                                               group = simulation), 
                        color = "grey80", 
                        data = sim1) + 
  ggplot2::geom_density(mapping = ggplot2::aes(x = .data[[resp]]),
                        data = mod$model)  
}

plot_sims(m2, nsim = 65)

bayesplot package

The bayesplot package has a function for this called ppc_dens_overlay, but it’s a little weird to use for linear models because it was designed to be used with Bayesian models. However it’s not that hard to deploy. The first argument is simply the observed data. The second argument expects the simulations per row as opposed to per column. So we need to transpose, which we can do with the t() function. The result is a clean plot with the y-axis unlabeled since it really isn’t needed and a legend to distinguish between observed and simulated (or replicated) data.

# install.packages("bayesplot")
library(bayesplot)
ppc_dens_overlay(log(homes$totalvalue), t(sim2))

performance package

The easystats collection of packages “aims to provide a unifying and consistent framework to tame, discipline, and harness the scary R statistics and their pesky models.” https://easystats.github.io/easystats/ One of the packages is called {performance}, which provides the check_predictions() function for simulating data from a model and comparing it to the distribution of the original data. Two potential drawbacks at the time of this writing:

  1. Can be slow for large data sets
  2. log transformed data needs to be added as a variable to the data and modeled directly, as opposing to doing it on-the-fly as log(totalvalue)

Quick demo. Note the warning: “Minimum value of original data is not included in the replicated data.” This says the model is not generating data as small as the smallest value in the original data, which is 9600.

# install.packages("performance")
# install.packages("see")
library(performance)

# add log(totalvalue) to data and fit new model
homes$logtotalvalue <- log(homes$totalvalue)
m2a <- lm(logtotalvalue ~ finsqft + bedroom + lotsize, data = homes)
check_predictions(m2a)
LS0tCnRpdGxlOiAiTGluZWFyIE1vZGVsaW5nIGluIFIiCmF1dGhvcjogIkNsYXkgRm9yZCwgVVZBIExpYnJhcnkiCm91dHB1dDogaHRtbF9ub3RlYm9vawplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQotLS0KCiMjIFF1aWNrIEludHJvIHRvIFIgTm90ZWJvb2tzIGFuZCBSIE1hcmtkb3duCgpUaGlzIGlzIGFuIFIgTWFya2Rvd24gTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgUiBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSByZXN1bHRzIGFwcGVhciBiZW5lYXRoIHRoZSBjb2RlLiAgCgpUaGlzIGZpbGUgd2FzIGNyZWF0ZWQgaW4gUlN0dWRpbyBieSBnb2luZyB0byBGaWxlLi4uTmV3IEZpbGUuLi5SIE5vdGVib29rLgoKUiBjb2RlIG5lZWRzIHRvIGJlIGluICJjaHVua3MiIGluIGFuIFIgTWFya2Rvd24gTm90ZWJvb2suIEJlbG93IGlzIGFuIGV4YW1wbGUgb2YgYW4gUiBjb2RlIGNodW5rLiBJdCBtYWtlcyBhIHBhcmFib2xhLgoKVHJ5IGV4ZWN1dGluZyB0aGlzIGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqUnVuKiBidXR0b24gd2l0aGluIHRoZSBjaHVuayBvciBieSBwbGFjaW5nIHlvdXIgY3Vyc29yIGluc2lkZSBpdCBhbmQgcHJlc3NpbmcgKkN0cmwrU2hpZnQrRW50ZXIqIChXaW4vTGludXgpIG9yICpDbWQrU2hpZnQrUmV0dXJuKiAoTWFjKS4gCgpgYGB7cn0KeCA8LSBzZXEoLTEsIDEsIGJ5ID0gMC4wMSkKeSA8LSB4XjIKcGxvdCh4LCB5LCB0eXBlID0gImwiKQpgYGAKClRvIGhpZGUgdGhlIG91dHB1dCwgY2xpY2sgdGhlIEV4cGFuZC9Db2xsYXBzZSBvdXRwdXQgYnV0dG9uLiBUbyBjbGVhciByZXN1bHRzIChvciBhbiBlcnJvciksIGNsaWNrIHRoZSAieCIuIAoKWW91IGNhbiBhbHNvIHByZXNzICpDdHJsK0VudGVyKiAoV2luL0xpbnV4KSBvciAqQ21kK1JldHVybiogKE1hYykgdG8gcnVuIG9uZSBsaW5lIG9mIGNvZGUgYXQgYSB0aW1lIChpbnN0ZWFkIG9mIHRoZSBlbnRpcmUgY2h1bmspLgoKQWRkIGEgbmV3IFIgY29kZSBjaHVuayBieSBjbGlja2luZyB0aGUgKkluc2VydCBDaHVuayogYnV0dG9uIG9uIHRoZSB0b29sYmFyIG9yIGJ5IHByZXNzaW5nICpDdHJsK0FsdCtJKiAoV2luL0xpbnV4KSBvciAqQ21kK09wdGlvbitJKiAoTWFjKS4gIAoKIyMgQ09ERSBBTE9ORyAwCgpJbnNlcnQgYSBuZXcgUiBjb2RlIGNodW5rIGJlbG93IGFuZCB0eXBlIGFuZCBydW4gdGhlIGNvZGU6IGBTeXMudGltZSgpYApgYGB7cn0KCmBgYAoKCgojIyBMaW5lYXIgTW9kZWxpbmcgd2l0aCBTaW11bGF0ZWQgRGF0YQoKSW5zdGVhZCBvZiB1c2luZyB0aGVvcnkgYW5kIGZvcm11bGFzLCBsZXQncyBleHBsb3JlIGFuZCByZXZpZXcgbGluZWFyIG1vZGVsaW5nIHVzaW5nIHNpbXVsYXRlZCBkYXRhLiAKCkJlbG93IHdlIGFzc2lnbiB0byB4IHRoZSB2YWx1ZXMgMSAtIDI1LiBUaGVuIHdlIGdlbmVyYXRlIHkgYXMgYSBmdW5jdGlvbiBvZiB4IHVzaW5nIHRoZSBmb3JtdWxhIDEwICsgNSp4OgoKYGBge3J9CnggPC0gMToyNQp5IDwtIDEwICsgNSp4ICAjIGZvcm11bGEgZm9yIGEgbGluZQpkIDwtIGRhdGEuZnJhbWUoeCwgeSkKcGxvdCh5IH4geCwgZGF0YSA9IGQpCmBgYAoKCi0gMTAgaXMgdGhlIGludGVyY2VwdC4KLSA1IGlzIHRoZSBzbG9wZS4gCi0geSBpcyBjb21wbGV0ZWx5IGRldGVybWluZWQgYnkgeCAoeSA9IDEwICsgNSp4KQoKTm93IGxldCdzIGFkZCBzb21lICJub2lzZSIgdG8gb3VyIGRhdGEgYnkgYWRkaW5nIHJhbmRvbSBkcmF3cyBmcm9tIGEgTm9ybWFsCmRpc3RyaWJ1dGlvbiB3aXRoIG1lYW4gPSAwIGFuZCBhIHN0YW5kYXJkIGRldmlhdGlvbiA9IDEwLiBUaGUgYHJub3JtKClgIGZ1bmN0aW9uIGFsbG93cyB1cyB0byBkcmF3IHJhbmRvbSB2YWx1ZXMgZnJvbSBhIE5vcm1hbCBkaXN0cmlidXRpb24uCgpgc2V0LnNlZWQoMSlgIGVuc3VyZXMgd2UgYWxsIGdlbmVyYXRlIHRoZSBzYW1lICJyYW5kb20iIGRhdGE6CgpgYGB7cn0Kc2V0LnNlZWQoMSkKbm9pc2UgPC0gcm5vcm0obiA9IDI1LCBtZWFuID0gMCwgc2QgPSAxMCkKIyBBZGQgdGhlIG5vaXNlIHRvIDEwICsgNSp4IGFuZCByZS1kcmF3IHBsb3QKZCR5IDwtIDEwICsgNSp4ICsgbm9pc2UKcGxvdCh5IH4geCwgZGF0YSA9IGQpCmBgYAoKTm93IHkgYXBwZWFycyB0byBiZSBfYXNzb2NpYXRlZF8gd2l0aCB4LCBidXQgbm90IGNvbXBsZXRlbHkgZGV0ZXJtaW5lZCBieSB4LgoKeSBpcyB0aGUgY29tYmluYXRpb24gb2YgYSBmaXhlZCBwYXJ0IGFuZCBhIHJhbmRvbSBwYXJ0OgoKMS4gZml4ZWQ6IGAxMCArIDUqeGAKMi4gcmFuZG9tOiBgcm5vcm0obiA9IDI1LCBtZWFuID0gMCwgc2QgPSAxMClgCgpXaGF0IGlmIHdlIHdlcmUgZ2l2ZW4gdGhpcyBkYXRhIGFuZCB0b2xkIHRvIGRldGVybWluZSB0aGUgcHJvY2VzcyB0aGF0IGdlbmVyYXRlZCBpdD8gSW4gb3RoZXIgd29yZHMsIF93b3JrIGJhY2t3YXJkc18gYW5kIGZpbGwgaW4gdGhlIGJsYW5rczoKCjEuIF9fXyArIF9fXyp4CjIuIHJub3JtKG4gPSAyNSwgbWVhbiA9IDAsIHNkID0gX19fXykKCl9UaGF0J3Mgb25lIHdheSB0byB0aGluayBvZiBsaW5lYXIgbW9kZWxpbmcvcmVncmVzc2lvbl8uIFlvdSBoYXZlIHNvbWUgbnVtZXJpYyByZXNwb25zZSAob3IgZGVwZW5kZW50KSB2YXJpYWJsZSwgYW5kIHlvdSB3YW50IHRvIGZpbmQgdGhlIG1vZGVsIChvciB0aGUgZm9ybXVsYSkgdGhhdCBnZW5lcmF0ZWQgdGhlIGRhdGEuIAoKVHJhZGl0aW9uYWwgbGluZWFyIG1vZGVsaW5nL211bHRpcGxlIHJlZ3Jlc3Npb24gYXNzdW1lcyB0aGUgZm9sbG93aW5nIChhbW9uZyBvdGhlcnMpOgoKMS4gdGhlIGZvcm11bGEgaXMgYSBfd2VpZ2h0ZWQgc3VtXyBvZiBwcmVkaWN0b3JzIChlZywgeSA9IDEwICsgNSp4KQoyLiB0aGUgbm9pc2UgaXMgYSByYW5kb20gZHJhdyBmcm9tIGEgTm9ybWFsIGRpc3RyaWJ1dGlvbiB3aXRoIG1lYW4gPSAwCjMuIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIE5vcm1hbCBkaXN0cmlidXRpb24gaXMgY29uc3RhbnQgKGVnLCAxMCkKCkxpbmVhciBtb2RlbGluZyB0cmllcyB0byByZWNvdmVyIHRoZSB3ZWlnaHRzIGluIHRoZSBmaXJzdCBhc3N1bXB0aW9uIGFuZCB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIGluIHRoZSB0aGlyZCBhc3N1bXB0aW9uLgoKTGV0J3MgZGVtb25zdHJhdGUuIEJlbG93IHdlIGF0dGVtcHQgdG8gcmVjb3ZlciB0aGUgZGF0YSBnZW5lcmF0aW5nIHByb2Nlc3MgZm9yIG91ciBkYXRhLiBGb3IgdGhpcyB3ZSB1c2UgdGhlIGBsbSgpYCBmdW5jdGlvbi4gV2UgaGF2ZSB0byBzcGVjaWZ5IHRoZSBmb3JtdWxhIGZvciB0aGUgZmlyc3QgYXNzdW1wdGlvbi4gVGhlIDJuZCBhbmQgM3JkIGFzc3VtcHRpb25zIGFyZSBidWlsdCBpbnRvIGBsbSgpYC4KClRoZSBmb3JtdWxhICJ5IH4geCIgbWVhbnMgd2UgdGhpbmsgdGhlIG1vZGVsIGlzICJ5ID0gaW50ZXJjZXB0ICsgc2xvcGUqeCIuIChVbmxlc3Mgd2Ugc3BlY2lmeSBvdGhlcndpc2UsIHRoaXMgYXNzdW1lcyB3ZSB3YW50IHRvIGVzdGltYXRlIGFuIGludGVyY2VwdC4pIFRoaXMgdGVsbHMgYGxtKClgIHRvIHRha2Ugb3VyIGRhdGEgYW5kIGZpbmQgdGhlIGJlc3QgaW50ZXJjZXB0IGFuZCBzbG9wZS4gX05vdGljZSB0aGlzIGlzIHRoZSBjb3JyZWN0IG1vZGVsIV8KCldoZW4geW91IHVzZSBgbG0oKWAgeW91J2xsIHVzdWFsbHkgd2FudCB0byBzYXZlIHRoZSByZXN1bHRzIHRvIGFuIG9iamVjdC4gQmVsb3cgSSBzYXZlIGl0IHRvICJtb2QiLiBUaGVuIHdlIHZpZXcgdGhlIHJlc3VsdHMgb2YgdGhlIG1vZGVsIHVzaW5nIGBzdW1tYXJ5KClgCgpgYGB7cn0KbW9kIDwtIGxtKHkgfiB4LCBkYXRhID0gZCkKc3VtbWFyeShtb2QpCmBgYAoKClRoZSBtb2RlbCByZXR1cm5zIHRoZSBmb2xsb3dpbmcgZXN0aW1hdGVzOgoKMS4geSA9IDExLjEzNSArIDUuMDQyICogeAoyLiBub2lzZSA9IHJub3JtKG4gPSAyNSwgbWVhbiA9IDAsIHNkID0gOS43KQoKVGhlc2UgYXJlIHByZXR0eSBjbG9zZSB0byB0aGUgInRydWUiIHZhbHVlcyBvZiAxMCwgNSwgYW5kIDEwIHdlIHVzZWQgdG8gZ2VuZXJhdGUgdGhlIGRhdGEuICAKCioqSW4gcmVhbCBsaWZlLCB3ZSBETyBOT1QgS05PVyB0aGUgZm9ybXVsYSBpbiBwYXJ0IDEuIFRoZSByZWFsIGRhdGEgZ2VuZXJhdGlvbiBwcm9jZXNzIHdpbGwgYmUgZmFyIG1vcmUgY29tcGxpY2F0ZWQuIFRoZSBmb3JtdWxhIHdlIHByb3Bvc2Ugd2lsbCBqdXN0IGJlIGFuIGFwcHJveGltYXRpb24gYW5kIG1heSBub3QgYmUgZ29vZC4qKgoKKipJbiByZWFsIGxpZmUsIHdlIERPIE5PVCBLTk9XIGlmIHRoZSBOb3JtYWxpdHkgYXNzdW1wdGlvbiBvciBjb25zdGFudCB2YXJpYW5jZSBhc3N1bXB0aW9uIG9mIHRoZSBub2lzZSBpcyBwbGF1c2libGUuKioKCkhvdyBjYW4gd2UgZXZhbHVhdGUgb3VyIG1vZGVsIGZvcm11bGE/CgpXZSBjb3VsZCB1c2Ugb3VyIG1vZGVsIGVzdGltYXRlcyB0byBnZW5lcmF0ZSBkYXRhIGFuZCBzZWUgaWYgdGhleSBsb29rIHNpbWlsYXIgdG8gb3VyIG9yaWdpbmFsIGRhdGEuIFJ1biBlbnRpcmUgY2h1bmsgYXQgb25jZSBhbmQgcnVuIG1vcmUgdGhhbiBvbmNlLiBUaGUgYmxhY2sgcG9pbnRzIGRvbid0IGNoYW5nZSBidXQgdGhlIHJlZCBvbmVzIGRvLiBUaGF0IGxvb2tzIHByZXR0eSBnb29kISBPdXIgbW9kZWwtZ2VuZXJhdGVkIGRhdGEgYXBwZWFycyBzaW1pbGFyIHRvIG91ciBvYnNlcnZlZCBkYXRhLgoKYGBge3J9CiMgZCR5IDwtIDEwICsgNSp4ICsgbm9pc2UKZCR5MiA8LSAxMS4xMzUgKyA1LjA0MipkJHggKyBybm9ybSgyNSwgMCwgOS43KQpwbG90KHkgfiB4LCBkYXRhID0gZCkgIyBvcmlnaW5hbCBkYXRhCnBvaW50cyhkJHgsIGQkeTIsIGNvbCA9ICJyZWQiKSAjIHNpbXVsYXRlZCBkYXRhCmBgYAoKCldlIGNhbiBhbHNvIGNvbXBhcmUgX3Ntb290aCBkZW5zaXR5IGN1cnZlc18gb2YgdGhlIG9yaWdpbmFsIGFuZCBtb2RlbC1nZW5lcmF0ZWQgZGF0YS4gU21vb3RoIGRlbnNpdHkgY3VydmVzIGFyZSBiYXNpY2FsbHkgc21vb3RoIHZlcnNpb25zIG9mIGhpc3RvZ3JhbXMuIElmIHdlIGhhdmUgYSBnb29kIG1vZGVsLCBkYXRhIGdlbmVyYXRlZCBieSBvdXIgbW9kZWwgc2hvdWxkIGhhdmUgYSBzaW1pbGFyIGRpc3RyaWJ1dGlvbiB0byB0aGUgb3JpZ2luYWwgZGF0YS4gUnVuIGVudGlyZSBjaHVuayBhdCBvbmNlIGFuZCBydW4gbW9yZSB0aGFuIG9uY2UuCgpgYGB7cn0KaGlzdChkJHksIGZyZXEgPSBGQUxTRSkgIyBmcmVxID0gRkFMU0UgbWVhbnMgYXJlYSBvZiBiYXJzIHN1bXMgdG8gMQpsaW5lcyhkZW5zaXR5KGQkeSkpICAjIG9yaWdpbmFsIGRhdGEKZCR5MiA8LSAxMS4xMzUgKyA1LjA0MipkJHggKyBybm9ybSgyNSwgMCwgOS43KQpsaW5lcyhkZW5zaXR5KGQkeTIpLCBjb2wgPSAicmVkIikgICMgc2ltdWxhdGUgZGF0YQpgYGAKClRoaXMgbG9va3MgZ29vZCBhcyB3ZWxsLiBUaGUgZGlzdHJpYnV0aW9uIG9mIG91ciBtb2RlbC1nZW5lcmF0ZWQgZGF0YSBpcyB2ZXJ5IHNpbWlsYXIgdG8gb3VyIG9ic2VydmVkIGRhdGEuIFlvdSBzaG91bGQgZG8gdGhpcyBtb3JlIHRoYW4gb25jZSwgc2F5IDUwIHRpbWVzLCB0byBlbnN1cmUgdGhlIG1vZGVsIGNvbnNpc3RlbnRseSBnZW5lcmF0ZXMgZGF0YSBzaW1pbGFyIHRvIHRoZSBvYnNlcnZlZCBkYXRhLiBXZSBzaG93IG9uZSB3YXkgdG8gZG8gdGhhdCBsYXRlciBpbiB0aGUgd29ya3Nob3AuCgpTaW5jZSB3ZSB0aGluayBvdXIgbW9kZWwgaXMgImdvb2QiLCB3ZSBtaWdodCB1c2UgaXQgdG8gbWFrZSBhIHByZWRpY3Rpb24uIEZvciBleGFtcGxlLCB3aGVuIHggPSAxMCB3aGF0J3MgdGhlIGV4cGVjdGVkIHZhbHVlIG9mIHk/IFB1dCBhbm90aGVyIHdheSwgd2hhdCdzIHRoZSBfbWVhbl8gb2YgeSBjb25kaXRpb25hbCBvbiB4ID0gMTA/IFdlIGNhbiBkbyB0aGF0IHdpdGggdGhlIGBwcmVkaWN0KClgIGZ1bmN0aW9uLiAgVGhlIGBpbnRlcnZhbCA9ICJjb25maWRlbmNlImAgYXJndW1lbnRzIHNheXMgcmV0dXJuIGEgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgKENJKSBmb3IgdGhpcyBtZWFuLgoKYGBge3J9CnByZWRpY3QobW9kLCBuZXdkYXRhID0gZGF0YS5mcmFtZSh4ID0gMTApLCBpbnRlcnZhbCA9ICJjb25maWRlbmNlIikKYGBgCgpUaGUgZXhwZWN0ZWQgbWVhbiBvZiB5IHdoZW4geCA9IDEwIGlzIGFib3V0IDYxLjYgd2l0aCBhIDk1JSBDSSBvZiAoNTcuMiwgNjUuOSkuIFRoZSBDSSBnaXZlcyB1cyBzb21lIG5vdGlvbiBvZiBob3cgdW5jZXJ0YWluIHRoaXMgZXhwZWN0ZWQgbWVhbiBpcy4gSW4gZmFjdCBpdCB3b3VsZCBiZSBiZXR0ZXIgdG8gcmVwb3J0IHRoaXMgYXMsICJ0aGUgZXhwZWN0ZWQgbWVhbiBvZiB5IHdoZW4geCA9IDEwIGlzIGFib3V0IDU3IHRvIDY2LiIKCldlIG1pZ2h0IGFsc28gdHJ5IHRvIHN1bW1hcml6ZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4geSBhbmQgeCBieSBleGFtaW5pbmcgdGhlIGNvZWZmaWNpZW50cyAob3Igd2VpZ2h0cykgaW4gdGhlIHN1bW1hcnkgb3V0cHV0LiBXZSBjYW4gZXh0cmFjdCB0aGUgY29lZmZpY2llbnRzIGZyb20gdGhlIHN1bW1hcnkgd2l0aCB0aGUgYGNvZWYoKWAgZnVuY3Rpb246CgpgYGB7cn0KY29lZihzdW1tYXJ5KG1vZCkpCmBgYAoKVGhlIHggY29lZmZpY2llbnQgc2F5cyB0aGF0IHkgaW5jcmVhc2VzIGJ5IGFib3V0IDUgZm9yIGV2ZXJ5IG9uZS11bml0IGluY3JlYXNlIGluIHgsIGdpdmUgb3IgdGFrZSBhYm91dCAwLjI3LiBUaGUgc3RhbmRhcmQgZXJyb3IgZ2l2ZXMgdXMgc29tZSBpbmRpY2F0aW9uIG9mIHRoZSB1bmNlcnRhaW50eSBpbiB0aGlzIGVzdGltYXRlLiBXZSB0YWxrIG1vcmUgYWJvdXQgdCB2YWx1ZXMgYW5kIHAgdmFsdWVzIGJlbG93LgoKKipUTyBTVU1NQVJJWkU6IFRoaXMgaXMgYmFzaWMgbGluZWFyIG1vZGVsaW5nOioqCgoxLiBwcm9wb3NlIGFuZCBmaXQgYSBtb2RlbAoyLiBkZXRlcm1pbmUgaWYgdGhlIG1vZGVsIGlzIGdvb2QgYW5kIHRoYXQgYXNzdW1wdGlvbnMgYXJlIG1vc3RseSBtZXQKMy4gdXNlIHRoZSBtb2RlbCB0byBleHBsYWluIHJlbGF0aW9uc2hpcHMgYW5kL29yIG1ha2UgcHJlZGljdGlvbnMKCiMjIENPREUgQUxPTkcgMQoKTGV0J3Mgc2VlIHdoYXQgaGFwcGVucyB3aGVuIHdlIGZpdCBhIGJhZCBtb2RlbC4gQmVsb3cgd2UgYWRkIGEgbmV3IGNvbHVtbiB0byBkYXRhIGZyYW1lIGBkYCBuYW1lZCBgemAsIHdoaWNoIGlzIGEgcmFuZG9tIHNhbXBsZSBvZiBudW1iZXJzIG9uIHRoZSByYW5nZSBvZiAtMTAwIHRvIDEwMC4gYHJ1bmlmKClgIHNhbXBsZXMgbnVtYmVycyBmcm9tIGEgdW5pZm9ybSBkaXN0cmlidXRpb24uCgpgYGB7cn0Kc2V0LnNlZWQoNCkKZCR6IDwtIHJ1bmlmKDI1LCBtaW4gPSAtMTAwLCBtYXggPSAxMDApCmBgYAoKUkVNSU5ERVI6IEFkZCBhIG5ldyBSIGNvZGUgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpJbnNlcnQgQ2h1bmsqIGJ1dHRvbiBvbiB0aGUgdG9vbGJhciBvciBieSBwcmVzc2luZyAqQ3RybCtBbHQrSSogKFdpbi9MaW51eCkgb3IgKkNtZCtPcHRpb24rSSogKE1hYykuIAoKMS4gTW9kZWwgYHlgIGFzIGEgZnVuY3Rpb24gb2YgYHpgIHVzaW5nIGBsbSh5IH4geiwgZGF0YSA9IGQpYCBhbmQgc2F2ZSB0byBhbiBvYmplY3QgY2FsbGVkIGBtb2QyYC4gVmlldyB0aGUgc3VtbWFyeS4gV2hhdCBmb3JtdWxhIGRvZXMgdGhlIG1vZGVsIHJldHVybj8gV2hhdCBpcyB0aGUgZXN0aW1hdGVkIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgTm9ybWFsbHkgZGlzdHJpYnV0ZWQgbm9pc2U/CgoKCjIuIFVzZSB0aGUgbW9kZWwgdG8gc2ltdWxhdGUgZGVuc2l0eSBoaXN0b2dyYW1zIGFuZCBjb21wYXJlIHRvIHRoZSBvcmlnaW5hbCBkZW5zaXR5IGhpc3RvZ3JhbSBvZiBgZCR5YC4gUnVuIHRoZSBjaHVuayBzZXZlcmFsIHRpbWVzIHRvIHNlZSBob3cgdGhlIG1vZGVsLWdlbmVyYXRlZCBkZW5zaXR5IGN1cnZlIHZhcmllcy4gCgoKTGV0J3MgZG8gc29tZSBsaW5lYXIgbW9kZWxpbmcgd2l0aCByZWFsIGRhdGEuIAoKIyMgSW1wb3J0IGRhdGEKCkxldCdzIGltcG9ydCB0aGUgZGF0YSB3ZSdsbCBiZSB1c2luZyB0b2RheS4gVGhlIGRhdGEgd2UnbGwgd29yayB3aXRoIGlzIEFsYmVtYXJsZSBDb3VudHkgcmVhbCBlc3RhdGUgZGF0YSB3aGljaCB3YXMgZG93bmxvYWRlZCBmcm9tIHRoZSBPZmZpY2Ugb2YgR2VvZ3JhcGhpYyBEYXRhIFNlcnZpY2VzLiBXZSdsbCB1c2UgYSBfcmFuZG9tIHNhbXBsZV8gb2YgdGhlIGRhdGEuCgpgYGB7cn0KVVJMIDwtICdodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vY2xheWZvcmQvZGF0YXZpel93aXRoX2dncGxvdDIvbWFzdGVyL2FsYl9ob21lcy5jc3YnCmhvbWVzIDwtIHJlYWQuY3N2KGZpbGUgPSBVUkwpCmhvbWVzJGhzZGlzdHJpY3QgPC0gZmFjdG9yKGhvbWVzJGhzZGlzdHJpY3QpCmhvbWVzJGNvb2xpbmcgPC0gZmFjdG9yKGhvbWVzJGNvb2xpbmcpCmBgYAoKTGV0J3MgbG9vayBhdCB0aGUgZmlyc3QgZmV3IHJvd3M6CgpgYGB7cn0KaGVhZChob21lcykKYGBgCgpWYXJpYWJsZSBuYW1lIGRlZmluaXRpb25zOgoKLSAqeWVhcmJ1aWx0KjogeWVhciBob3VzZSB3YXMgYnVpbHQKLSAqZmluc3FmdCo6IHNpemUgb2YgaG91c2UgaW4gbnVtYmVyIHNxdWFyZSBmZWV0Ci0gKmNvb2xpbmcqOiAnQ2VudHJhbCBBaXInIHZlcnN1cyAnTm8gQ2VudHJhbCBBaXInCi0gKmJlZHJvb20qOiBudW1iZXIgb2YgYmVkcm9vbXMKLSAqZnVsbGJhdGgqOiBudW1iZXIgb2YgZnVsbCBiYXRocm9vbXMgKHRvaWxldCwgc2luayBhbmQgYmF0aCkKLSAqaGFsZmJhdGgqOiBudW1iZXIgb2YgaGFsZiBiYXRocm9vbXMgKHRvaWxldCBhbmQgc2luayBvbmx5KQotICpsb3RzaXplKjogc2l6ZSBvZiBsYW5kIG9uIHdoaWNoIGhvbWUgaXMgbG9jYXRlZCwgaW4gYWNyZXMKLSAqdG90YWx2YWx1ZSo6IHRvdGFsIGFzc2Vzc2VkIHZhbHVlIG9mIGhvbWUgYW5kIHByb3BlcnR5Ci0gKmVzZGlzdHJpY3QqOiB0aGUgZWxlbWVudGFyeSBzY2hvb2wgdGhlIGhvbWUgZmVlZHMgaW50bwotICptc2Rpc3RyaWN0KjogdGhlIG1pZGRsZSBzY2hvb2wgdGhlIGhvbWUgZmVlZHMgaW50bwotICpoc2Rpc3RyaWN0KjogdGhlIGhpZ2ggc2Nob29sIHRoZSBob21lIGZlZWRzIGludG8KLSAqY2Vuc3VzdHJhY3QqOiB0aGUgY2Vuc3VzIHRyYWN0IHRoZSBob21lIGlzIGxvY2F0ZWQgaW4KLSAqYWdlKjogb2YgdGhlIGhvdXNlIGluIHllYXJzIGFzIG9mIDIwMTgKLSAqY29uZGl0aW9uKjogYXNzZXNzZWQgY29uZGl0aW9uIG9mIGhvbWUgKFN1YnN0YW5kYXJkLCBQb29yLCBGYWlyLCBBdmVyYWdlLCBHb29kLCBFeGNlbGxlbnQpCi0gKmZwKjogaW5kaWNhdG9yIGlmIGhvdXNlIGhhcyBmaXJlcGxhY2UgKDA9bm8sIDE9eWVzKQoKCiMjIExpbmVhciBNb2RlbGluZyB3aXRoIFJlYWwgRXN0YXRlIERhdGEKCkxldCdzIHNheSB3ZSB3YW50IHRvIG1vZGVsIHRoZSBtZWFuICp0b3RhbCB2YWx1ZSogb2YgYSBob21lIGFzIGEgZnVuY3Rpb24gb2YgdmFyaW91cyBjaGFyYWN0ZXJpc3RpY3Mgc3VjaCBhcyBsb3Qgc2l6ZSwgZmluaXNoZWQgc3F1YXJlIGZlZXQsIHByZXNlbmNlIG9mIGNlbnRyYWwgYWlyLCBldGMuCgpMZXQncyBzZWUgaG93IHRvdGFsIHZhbHVlIGlzIGRpc3RyaWJ1dGVkIHVzaW5nIGEgaGlzdG9ncmFtLiBOb3RpY2UgaXQncyB2ZXJ5IHNrZXdlZC4KCmBgYHtyfQpoaXN0KGhvbWVzJHRvdGFsdmFsdWUpCmBgYAoKV2UgY2FuIGFsc28gY3JlYXRlIGEgc21vb3RoIGRlbnNpdHkgcGxvdCwgd2hpY2ggaXMgYSBzbW9vdGggdmVyc2lvbiBvZiBhIGhpc3RvZ3JhbToKCmBgYHtyfQpwbG90KGRlbnNpdHkoaG9tZXMkdG90YWx2YWx1ZSkpCmBgYAoKCkluIG9yZGVyIHRvIG1vZGVsIG1lYW4gdG90YWx2YWx1ZSBvZiBob21lcyBhcyBhIGZ1bmN0aW9uIG9mIHZhcmlvdXMgY2hhcmFjdGVyaXN0aWNzLCB3ZSBuZWVkIHRvIHByb3Bvc2UgYSBsaW5lYXIgbW9kZWwuIFVubGlrZSB0aGUgcHJldmlvdXMgZXhhbXBsZSB0aGlzIGlzIG5vdCBzaW11bGF0ZWQgZGF0YSBmb3Igd2hpY2ggd2Uga25vdyB0aGUgZGF0YSBnZW5lcmF0aW5nIHByb2Nlc3MuIEhvdyB0byBwcm9wb3NlIGEgbW9kZWw/IEl0IGhlbHBzIHRvIGhhdmUgc29tZSBzdWJqZWN0IG1hdHRlciBleHBlcnRpc2UuIAoKTGV0J3MgZml0IGEgbGluZWFyIG1vZGVsIHVzaW5nIGZpbnNxZnQsIGJlZHJvb21zIGFuZCBsb3RzaXplLiBUaGUgcGx1cyAoKykgc2lnbiBtZWFucyAiaW5jbHVkZSIgaW4gbW9kZWwuCgpgYGB7cn0KbTEgPC0gbG0odG90YWx2YWx1ZSB+IGZpbnNxZnQgKyBiZWRyb29tICsgbG90c2l6ZSwgZGF0YSA9IGhvbWVzKQpzdW1tYXJ5KG0xKQpgYGAKCgpUaGUgYGNvZWYoKWAgZnVuY3Rpb24gZXh0cmFjdHMgdGhlIGNvZWZmaWNpZW50cyAob3Igd2VpZ2h0cyk6CgpgYGB7cn0KY29lZihtMSkKYGBgCgpUaGlzIHRyYW5zbGF0ZXMgdG86CgpgYGAKdG90YWxwcmljZSA9IC0xMzMzMjguMjQ4MiArIDI4NC40NjEzKmZpbnNxZnQgKyAtMTMyMTguNDA5MSpiZWRyb29tICsKICAgICAgICAgICAgICAgNDI2OC43NjU1KmxvdHNpemVgCmBgYAoKU29tZSBuYWl2ZSBpbnRlcnByZXRhdGlvbjoKCi0gZWFjaCBhZGRpdGlvbmFsIGZpbmlzaGVkIHNxdWFyZSBmb290IGFkZHMgYWJvdXQgJDI4NCB0byBwcmljZQotIGVhY2ggYWRkaXRpb25hbCBiZWRyb29tIGRyb3BzIHRoZSBwcmljZSBieSAkMTMsMjE4ICg/KQotIGVhY2ggYWRkaXRpb25hbCBsb3Qgc2l6ZSBhY3JlIGFkZHMgYWJvdXQgJDQyNjggdG8gcHJpY2UKCkVhY2ggb2YgdGhlc2UgaW50ZXJwcmV0YXRpb25zIGFzc3VtZXMgX2FsbCBvdGhlciB2YXJpYWJsZXMgYXJlIGhlbGQgY29uc3RhbnRfISBTbyBhZGRpbmcgYSBiZWRyb29tIHRvIGEgaG91c2UsIHdpdGhvdXQgaW5jcmVhc2luZyB0aGUgbG90IHNpemUgb3IgZmluaXNoZWQgc3F1YXJlIGZlZXQgb2YgdGhlIGhvdXNlLCBpcyBlc3RpbWF0ZWQgdG8gZHJvcCB0aGUgdmFsdWUgb2YgYSBob21lLiBEb2VzIHRoaXMgbWFrZSBzZW5zZT8gCgpJcyB0aGlzIGEgImdvb2QiIG1vZGVsPyBMZXQncyBfc2ltdWxhdGUgZGF0YSBmcm9tIHRoZSBtb2RlbF8gYW5kIGNvbXBhcmUgaXQgdG8gb3VyIG9ic2VydmVkIGRhdGEuIEEgImdvb2QiIG1vZGVsIHNob3VsZCBnZW5lcmF0ZSBkYXRhIHRoYXQgbG9va3Mgc2ltaWxhciB0byB0aGUgb3JpZ2luYWwgZGF0YS4gCgpXZSBjb3VsZCBkbyB0aGlzIGJ5IGhhbmQ6CgpgYGB7cn0Kc2ltX3ZhbHVlcyA8LSAtMTMzMzI4LjI0ODIgKyAyODQuNDYxMypob21lcyRmaW5zcWZ0ICsgCiAgLTEzMjE4LjQwOTEqaG9tZXMkYmVkcm9vbSArIDQyNjguNzY1NSpob21lcyRsb3RzaXplICsgCiAgcm5vcm0oMzAyNSwgbWVhbiA9IDAsIHNkID0gMjI3MjAwKQpgYGAKCkFuIGVhc2llciBhbmQgZmFzdGVyIHdheSBpcyB0byB1c2UgdGhlIGBzaW11bGF0ZSgpYCBmdW5jdGlvbiB3aGljaCBhbGxvd3MgeW91IHRvIGdlbmVyYXRlIG11bHRpcGxlIHNhbXBsZXMuIEhlcmUgd2UgZ2VuZXJhdGUgNTAgc2FtcGxlcy4gRWFjaCBzYW1wbGUgd2lsbCBoYXZlIHRoZSBzYW1lIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgYXMgb3VyIG9yaWdpbmFsIHNhbXBsZSAobiA9IDMwMjUpLiBFYWNoIHNhbXBsZSB2YWx1ZSBpcyBnZW5lcmF0ZWQgdXNpbmcgb3VyIG9ic2VydmVkIHZhbHVlcyBmb3IgYGZpbnNxZnRgLCBgYmVkcm9vbWAsIGFuZCBgbG90c2l6ZWAuIFRoZSByZXN1bHQgaXMgYSBkYXRhIGZyYW1lIHdpdGggNTAgY29sdW1ucy4KCmBgYHtyfQpzaW0xIDwtIHNpbXVsYXRlKG0xLCBuc2ltID0gNTApCmBgYAoKCk5vdyBsZXQncyBwbG90IG91ciBzaW11bGF0ZWQgZGF0YSB3aXRoIG91ciBvYnNlcnZlZCBkYXRhIHVzaW5nIHNtb290aCBkZW5zaXR5IHBsb3RzLiBXZSB1c2UgYSBmb3IgbG9vcCB0byBhZGQgc21vb3RoIGRlbnNpdHkgZXN0aW1hdGVzIG9mIHRoZSA1MCBzaW11bGF0aW9ucy4gVGhlIHN5bnRheCBgc2ltMVtbaV1dYCBleHRyYWN0cyBjb2x1bW4gX2lfIGFzIGEgdmVjdG9yLiAoUnVuIGVudGlyZSBjaHVuayBhdCBvbmNlLikKCmBgYHtyfQpwbG90KGRlbnNpdHkoaG9tZXMkdG90YWx2YWx1ZSkpCmZvcihpIGluIDE6NTApbGluZXMoZGVuc2l0eShzaW0xW1tpXV0pLCBjb2wgPSAiZ3JleTgwIikKYGBgCgooU2VlIGVuZCBvZiBub3RlYm9vayBmb3IgaG93IHRvIGNyZWF0ZSB0aGlzIHBsb3QgdXNpbmcgZ2dwbG90MiBhbmQgZm9yIGhvdyB0byB0dXJuIHRoaXMgaW50byBhIGZ1bmN0aW9uLikKClRoaXMgZG9lcyBub3QgYXBwZWFyIHRvIGJlIGEgZ29vZCBtb2RlbC4gSW4gZmFjdCBzb21lIG9mIG91ciBzaW11bGF0ZWQgdmFsdWVzIGFyZSBuZWdhdGl2ZSEKCkJlZm9yZSB3ZSByZXZpc2Ugb3VyIG1vZGVsIHJlY2FsbCB0aGUgbWFpbiBhc3N1bXB0aW9uczoKCjEpIHRvdGFsdmFsdWUgY2FuIGJlIG1vZGVsZWQgYnkgYSB3ZWlnaHRlZCBzdW06IAogICB0b3RhbHZhbHVlID0gSW50ZXJjZXB0ICsgZmluc3FmdCArIGJlZHJvb21zICsgbG90c2l6ZQoyKSBub2lzZS9lcnJvciBpcyBmcm9tIE5vcm1hbCBkaXN0J24gd2l0aCBtZWFuIDAKMykgdGhlIFNEIG9mIHRoZSBOb3JtYWwgZGlzdCduIGlzIGNvbnN0YW50IAoKUiBwcm92aWRlcyBzb21lIGJhc2ljIGRpYWdub3N0aWMgcGxvdHMgdG8gYXNzZXNzIDIgYW5kIDMuIEp1c3QgY2FsbCBgcGxvdGAgb24geW91ciBtb2RlbCBvYmplY3QKCmBgYHtyfQpwbG90KG0xKQpgYGAKCkhvdyB0byBpbnRlcnByZXQgcGxvdHM6CgoxLiBSZXNpZHVhbHMgdnMgRml0dGVkOiBmb3IgY2hlY2tpbmcgY29uc3RhbnQgdmFyaWFuY2U7IHNob3VsZCBoYXZlIGEgaG9yaXpvbnRhbCBsaW5lIHdpdGggdW5pZm9ybSBhbmQgc3ltbWV0cmljIHNjYXR0ZXIgb2YgcG9pbnRzOyBpZiBub3QsIGV2aWRlbmNlIHRoYXQgU0QgaXMgbm90IGNvbnN0YW50CgoyLiBOb3JtYWwgUS1ROiBmb3IgY2hlY2tpbmcgbm9ybWFsaXR5IG9mIHJlc2lkdWFsczsgcG9pbnRzIHNob3VsZCBsaWUgY2xvc2UgdG8gZGlhZ29uYWwgbGluZTsgaWYgbm90LCBldmlkZW5jZSB0aGF0IG5vaXNlIGlzIG5vdCBkcmF3biBmcm9tIE4oMCwgU0QpLiBUaGlzIGFzc3VtcHRpb24gaXMgb25seSByZWFsbHkgY3JpdGljYWwgaWYgeW91J3JlIHBsYW5uaW5nIHRvIHVzZSB5b3VyIG1vZGVsIHRvIHByZWRpY3QgZXhhY3QgdmFsdWVzIChpbnN0ZWFkIG9mIG1lYW5zKSBhbmQgY2FsY3VsYXRlIGEgY29uZmlkZW5jZSBpbnRlcnZhbC4gCgozLiBTY2FsZS1Mb2NhdGlvbjogZm9yIGNoZWNraW5nIGNvbnN0YW50IHZhcmlhbmNlOyBzaG91bGQgaGF2ZSBhIGhvcml6b250YWwgbGluZSB3aXRoIHVuaWZvcm0gc2NhdHRlciBvZiBwb2ludDsgKHNpbWlsYXIgdG8gIzEgYnV0IGVhc2llciB0byBkZXRlY3QgdHJlbmQgaW4gZGlzcGVyc2lvbikKCjQuIFJlc2lkdWFscyB2cyBMZXZlcmFnZTogZm9yIGNoZWNraW5nIGZvciBpbmZsdWVudGlhbCBvYnNlcnZhdGlvbnM7IHBvaW50cyBvdXRzaWRlIHRoZSBjb250b3VyIGxpbmVzIGFyZSBpbmZsdWVudGlhbCBvYnNlcnZhdGlvbnMuIExldmVyYWdlIGlzIHRoZSBkaXN0YW5jZSBmcm9tIHRoZSBjZW50ZXIgb2YgYWxsIHByZWRpY3RvcnMuIEFuIG9ic2VydmF0aW9uIHdpdGggaGlnaCBsZXZlcmFnZSBoYXMgc3Vic3RhbnRpYWwgaW5mbHVlbmNlIG9uIHRoZSBmaXR0ZWQgdmFsdWUuCgpCeSBkZWZhdWx0IHRoZSAzICJtb3N0IGV4dHJlbWUiIHBvaW50cyBhcmUgbGFiZWxlZCBieSByb3cgbnVtYmVyLiAyNjU4IGFwcGVhcnMgaW4gYWxsIGZvdXIgcGxvdHMuIEl0J3MgYSByZWFsbHkgYmlnIGV4cGVuc2l2ZSBob21lLgoKYGBge3J9CmhvbWVzWzI2NTgsXQpgYGAKClRoZXNlIHBsb3RzIHJldmVhbCB0aGF0IG91ciBhc3N1bXB0aW9ucyBvZiBub3JtYWxseSBkaXN0cmlidXRlZCByZXNpZHVhbHMgYW5kIGNvbnN0YW50IHZhcmlhbmNlIGFyZSBoaWdobHkgc3VzcGVjdC4gT3VyIG1vZGVsIGlzIGp1c3QgYmFkLgoKV2hhdCBjYW4gd2UgZG8/CgpOb24tY29uc3RhbnQgdmFyaWFuY2UgY2FuIGJlIGV2aWRlbmNlIG9mIGEgd3JvbmcgbW9kZWwgb3IgYSB2ZXJ5IHNrZXdlZCByZXNwb25zZSAob3IgYSBiaXQgb2YgYm90aCkuIFJlY2FsbCB0aGF0IG91ciByZXNwb25zZSBpcyBxdWl0ZSBza2V3ZWQ6CgpgYGB7cn0KaGlzdChob21lcyR0b3RhbHZhbHVlKQpgYGAKCldoZW4gZGVhbGluZyB3aXRoIGEgcmVzcG9uc2UgdGhhdCBpcyBzdHJpY3RseSBwb3NpdGl2ZSBhbmQgdmVyeSBza2V3IChsaWtlIGRvbGxhciBhbW91bnRzKSwgaXQgaXMgY29tbW9uIHRvIHRyYW5zZm9ybSB0aGUgcmVzcG9uc2UgdG8gYSBkaWZmZXJlbnQgc2NhbGUuIEEgY29tbW9uIHRyYW5zZm9ybWF0aW9uIGlzIGEgbG9nIHRyYW5zZm9ybWF0aW9uLiBXaGVuIHdlIGxvZyB0cmFuc2Zvcm0gYHRvdGFsdmFsdWVgLCB0aGUgZGlzdHJpYnV0aW9uIGxvb2tzIGEgbGl0dGxlIG1vcmUgc3ltbWV0cmljLCB0aG91Z2ggaXQncyBpbXBvcnRhbnQgdG8gbm90ZSB0aGF0IGlzIG5vdCBhbiBhc3N1bXB0aW9uIG9mIGxpbmVhciBtb2RlbGluZyEKCmBgYHtyfQpoaXN0KGxvZyhob21lcyR0b3RhbHZhbHVlKSkKYGBgCgpMZXQncyB0cnkgbW9kZWxpbmcgbG9nLXRyYW5zZm9ybWVkIGB0b3RhbHZhbHVlYC4KCmBgYHtyfQptMiA8LSBsbShsb2codG90YWx2YWx1ZSkgfiBmaW5zcWZ0ICsgYmVkcm9vbSArIGxvdHNpemUsIGRhdGEgPSBob21lcykKYGBgCgpUaGUgZGlhZ25vc3RpYyBwbG90cyBsb29rIGJldHRlci4gCgpgYGB7cn0KcGxvdChtMikKYGBgCgpCdXQgaXMgdGhpcyBhICJnb29kIG1vZGVsIj8gSXMgb3VyIHByb3Bvc2VkIG1vZGVsIG9mIHdlaWdodGVkIHN1bXMgZ29vZD8gTGV0J3Mgc2ltdWxhdGUgZGF0YSBhbmQgY29tcGFyZSB0byBvYnNlcnZlZCBkYXRhLgoKYGBge3J9CnNpbTIgPC0gc2ltdWxhdGUobTIsIG5zaW0gPSA1MCkKcGxvdChkZW5zaXR5KGxvZyhob21lcyR0b3RhbHZhbHVlKSkpCmZvcihpIGluIDE6NTApbGluZXMoZGVuc2l0eShzaW0yW1tpXV0pLCBsdHkgPSAyLCBjb2wgPSAiZ3JleTgwIikKYGBgCgpUaGlzIGRvZXNuJ3QgbG9vayB0b28gYmFkIQoKTGV0J3Mgc2F5IHdlJ3JlIGhhcHB5IHdpdGggdGhpcyBtb2RlbC4gSG93IGRvIHdlIGludGVycHJldCB0aGUgY29lZmZpY2llbnRzPyBTaW5jZSB0aGUgcmVzcG9uc2UgaXMgbG9nIHRyYW5zZm9ybWVkLCB3ZSBpbnRlcnByZXQgdGhlIGNvZWZmaWNpZW50cyBhcyBfbXVsdGlwbGljYXRpdmUgaW5zdGVhZCBvZiBhZGRpdGl2ZV8uIEJlbG93IHdlIHZpZXcgdGhlIGNvZWZmaWNpZW50cyByb3VuZGVkIHRvIDQgZGVjaW1hbCBwbGFjZXMuCgoKYGBge3J9CnJvdW5kKGNvZWYobTIpLCA0KQpgYGAKClRoZXNlIGFyZSBwcm9wb3J0aW9ucy4gVG8gZ2V0IHBlcmNlbnRhZ2VzLCBtdWx0aXBseSBieSAxMDAuCgpgYGB7cn0Kcm91bmQoY29lZihtMiksIDQpICogMTAwCmBgYAoKU29tZSBuYWl2ZSBpbnRlcnByZXRhdGlvbnM6CgotIGVhY2ggYWRkaXRpb25hbCBmaW5pc2hlZCBzcXVhcmUgZm9vdCBpbmNyZWFzZXMgZXhwZWN0ZWQgbWVhbiBwcmljZSBieSAwLjA1JS4gT3IgbXVsdGlwbHkgYnkgMTAwIHRvIHNheSBlYWNoIGFkZGl0aW9uYWwgMTAwIGZpbmlzaGVkIHNxdWFyZSBmZWV0IGluY3JlYXNlcyBleHBlY3RlZCBtZWFuIHByaWNlIGJ5IDUlLgotIGVhY2ggYWRkaXRpb25hbCBiZWRyb29tIGluY3JlYXNlcyBleHBlY3RlZCBtZWFuIHByaWNlIGJ5IGFib3V0IDQuMyUKLSBlYWNoIGFkZGl0aW9uYWwgbG90IHNpemUgYWNyZSBpbmNyZWFzZXMgZXhwZWN0ZWQgbWVhbiBwcmljZSBieSBhYm91dCAwLjQ3JQoKUmVtZW1iZXIsIHRoZSBpbnRlcnByZXRhdGlvbiBhc3N1bWVzIF9hbGwgb3RoZXIgdmFyaWFibGVzIGFyZSBoZWxkIGNvbnN0YW50XyEgCgpBIHNsaWdodGx5IG1vcmUgcHJlY2lzZSBlc3RpbWF0aW9uIGNhbiBiZSBvYnRhaW5lZCBieSBfZXhwb25lbnRpYXRpbmdfIHRoZSBjb2VmZmljaWVudHMuIEJlbG93IHdlIGV4cG9uZW50aWF0ZSB1c2luZyB0aGUgYGV4cCgpYCBmdW5jdGlvbiBhbmQgdGhlbiByb3VuZCB0byA0IGRlY2ltYWwgcGxhY2VzLgoKYGBge3J9CnJvdW5kKGV4cChjb2VmKG0yKSksIDQpIApgYGAKCkZvciBleGFtcGxlLCBlYWNoIGFkZGl0aW9uYWwgYmVkcm9vbSAoYXNzdW1pbmcgYWxsIGVsc2UgaGVsZCBjb25zdGFudCkgaW5jcmVhc2VzIGV4cGVjdGVkIHRvdGFsIHByaWNlIGJ5IGFib3V0IDQuNCUuIE11bHRpcGx5aW5nIGJ5IDEuMDQzOSBpcyBlcXVpdmFsZW50IHRvIGFkZGluZyA0LjM5JS4KCkxldCdzIHJldmlldyB0aGUgc3VtbWFyeSBvdXRwdXQ6CgpgYGB7cn0Kc3VtbWFyeShtMikKYGBgCgoKU1VNTUFSWSBPVkVSVklFVwoKLSBSZXNpZHVhbHMgc2VjdGlvbjogcXVpY2sgYXNzZXNzbWVudCBvZiByZXNpZHVhbHMuIElkZWFsbHkgMVEvM1EgYW5kIE1pbi9NYXggd2lsbCBiZSByb3VnaGx5IGVxdWl2YWxlbnQgaW4gYWJzb2x1dGUgdmFsdWUuCi0gQ29lZmZpY2llbnRzOiBsaXN0cyB0aGUgZXN0aW1hdGVkIGNvZWZmaWNpZW50cyBhbG9uZyB3aXRoIGh5cG90aGVzaXMgdGVzdHMgZm9yIHRoZSBudWxsIHRoYXQgZWFjaCBjb2VmZmljaWVudCBpcyAwLiBFc3QvU0UgPSB0LXZhbHVlLiAKLSBSZXNpZHVhbCBzdGFuZGFyZCBlcnJvcjogZXN0aW1hdGUgb2YgdGhlIGNvbnN0YW50IHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgbm9ybWFsIGRpc3QnbiBvZiB0aGUgcmVzaWR1YWxzIChub2lzZSkKLSBkZWdyZWVzIG9mIGZyZWVkb206IHNhbXBsZSBzaXplIC0gbnVtYmVyIG9mIGNvZWZmaWNpZW50cyAoMzAyNSAtIDQpCi0gUi1zcXVhcmVkOiBwcm9wb3J0aW9uIG9mIHZhcmlhbmNlIGV4cGxhaW5lZAotIEYtc3RhdGlzdGljOiBvdmVyYWxsIHRlc3QgdGhhdCBhbGwgY29lZmZpY2llbnRzIChleGNlcHQgaW50ZXJjZXB0KSBhcmUgMC4KCkFsbCBvZiB0aGUgcC12YWx1ZXMgcmVmZXIgdG8gaHlwb3RoZXNpcyB0ZXN0cyB0aGF0IHRoZSBjb2VmZmljaWVudHMgYXJlIDAuIE1hbnkgc3RhdGlzdGljaWFucyBhbmQgcmVzZWFyY2hlcnMgcHJlZmVyIHRvIGxvb2sgYXQgY29uZmlkZW5jZSBpbnRlcnZhbHMuCgpgYGB7cn0Kcm91bmQoY29uZmludChtMikgKiAxMDAsIDQpCmBgYAoKQWNjb3JkaW5nIHRvIG91ciBtb2RlbCwgZWFjaCBhZGRpdGlvbmFsIGJlZHJvb20gYWRkcyBhYm91dCAzJSB0byA2JSB0byB0aGUgdmFsdWUgb2YgYSBob21lLCBhc3N1bWluZyBhbGwgZWxzZSBoZWxkIGNvbnN0YW50LgoKIyMgQ09ERSBBTE9ORyAyCgoxLiBJbnNlcnQgYSBjb2RlIGNodW5rIGJlbG93IGFuZCBtb2RlbCBgbG9nKHRvdGFsdmFsdWUpYCBhcyBmdW5jdGlvbiBvZiBgZnVsbGJhdGhgIGFuZCBgZmluc3FmdC5gIENhbGwgeW91ciBtb2RlbCBgbTNgCgoKMi4gSW5zZXJ0IGEgY29kZSBjaHVuayBiZWxvdyBhbmQgY2hlY2sgdGhlIGRpYWdub3N0aWMgcGxvdHMKCgoKMy4gSG93IGRvIHdlIGludGVycHJldCB0aGUgZnVsbGJhdGggY29lZmZpY2llbnQ/CgoKCjQuIEluc2VydCBhIGNvZGUgY2h1bmsgYmVsb3cgYW5kIHNpbXVsYXRlIGRhdGEgZnJvbSB0aGUgbW9kZWwgYW5kIGNvbXBhcmUgdG8gdGhlIG9ic2VydmVkIGB0b3RhbHZhbHVlYC4gRG9lcyB0aGlzIGxvb2sgbGlrZSBhIGdvb2QgbW9kZWw/CgoKCgojIyBDYXRlZ29yaWNhbCBwcmVkaWN0b3JzCgpMZXQncyBhZGQgYGhzZGlzdHJpY3RgIHRvIHRoZSBtb2RlbCB3ZSBqdXN0IGZpdC4gRG9lcyBiZWluZyBpbiBhIHBhcnRpY3VsYXIgaGlnaCBzY2hvb2wgZGlzdHJpY3QgYWZmZWN0IHRvdGFsIHZhbHVlIG9mIGEgaG9tZT8gSGVyZSdzIGEgcXVpY2sgdGFsbHk6CgpgYGB7cn0KdGFibGUoaG9tZXMkaHNkaXN0cmljdCkKYGBgCgpUaGVzZSBsZXZlbHMgYXJlIG5vdCBudW1iZXJzLCBzbyBob3cgZG9lcyBSIGhhbmRsZSB0aGlzIGluIGEgbGluZWFyIG1vZGVsPyBJdCBjcmVhdGVzIGEgX2NvbnRyYXN0Xy4gVGhpcyBpcyBhIG1hdHJpeCBvZiB6ZXJvZXMgYW5kIG9uZXMuIElmIHlvdSBoYXZlIEsgbGV2ZWxzLCB5b3UnbGwgaGF2ZSBLLTEgY29sdW1ucy4gSW4gdGhpcyBjYXNlIHdlJ2xsIGhhdmUgdHdvIGNvbHVtbnM6IG9uZSBmb3IgTW9udGljZWxsbyBIUyBhbmQgb25lIGZvciBXZXN0ZXJuIEFsYmVtYXJsZSBIUy4gQnkgZGVmYXVsdCBSIHRha2VzIHdoYXRldmVyIGxldmVsIGNvbWVzIGZpcnN0IGFscGhhYmV0aWNhbGx5IGFuZCBtYWtlcyBpdCB0aGUgX2Jhc2VsaW5lXyBvciBfcmVmZXJlbmNlXyBsZXZlbC4gCgpMZXQncyBsb29rIGF0IHRoZSBkZWZhdWx0IGNvbnRyYXN0LCBjYWxsZWQgYSBfdHJlYXRtZW50IGNvbnRyYXN0Xy4gVG8gZG8gdGhpcyB3ZSBjb252ZXJ0IHRoZSBoc2Rpc3RyaWN0IGNvbHVtbiB0byBmYWN0b3IgYW5kIHRoZW4gdXNlIHRoZSBgY29udHJhc3RzKClgIGZ1bmN0aW9uLiAoV2UgZG9uJ3QgaGF2ZSB0byBkbyB0aGlzIHRvIGFkZCBoc2Rpc3RyaWN0IHRvIG91ciBtb2RlbCEgSSdtIGp1c3QgZG9pbmcgdGhpcyB0byBvdXRwdXQgdGhlIGNvbnRyYXN0ICJkZWZpbml0aW9uIikKCmBgYHtyfQpob21lcyRoc2Rpc3RyaWN0IDwtIGZhY3Rvcihob21lcyRoc2Rpc3RyaWN0KQpjb250cmFzdHMoaG9tZXMkaHNkaXN0cmljdCkKYGBgCgpBIG1vZGVsIHdpdGggYGhzZGlzdHJpY3RgIHdpbGwgaGF2ZSB0d28gY29lZmZpY2llbnRzOiBgTW9udGljZWxsb2AgYW5kICBgV2VzdGVybiBBbGJlbWFybGVgCgotIGEgaG9tZSBpbiBBbGJlbWFybGUgSFMgZGlzdHJpY3QgZ2V0cyB0d28gemVyb2VzCi0gYSBob21lIGluIE1vbnRpY2VsbG8gSFMgZGlzdHJpY3QgZ2V0cyBhIG9uZSBpbiB0aGUgTW9udGljZWxsbyBjb2x1bW4KLSBhIGhvbWUgaW4gV2VzdGVybiBBbGJlbWFybGUgSFMgZGlzdHJpY3QgZ2V0cyBhIG9uZSBpbiB0aGUgV2VzdCBBbGIgY29sdW1uCgoKTGV0J3MgZml0IG91ciBuZXcgbW9kZWwuCgpgYGB7cn0KbTQgPC0gbG0obG9nKHRvdGFsdmFsdWUpIH4gZnVsbGJhdGggKyBmaW5zcWZ0ICsgaHNkaXN0cmljdCwgZGF0YSA9IGhvbWVzKQpzdW1tYXJ5KG00KQpgYGAKClRoZSBjb2VmZmljaWVudHMgZm9yIE1vbnRpY2VsbG8gYW5kIFdlc3Rlcm4gQWxiZW1hcmxlIGFyZSBpbiByZWxhdGlvbiB0byBBbGJlbWFybGUgSFMuIAoKYGBge3J9CnJvdW5kKGNvZWYobTQpICogMTAwLCA0KQpgYGAKCkl0IGFwcGVhcnMgdGhhdCB0aGUgdmFsdWUgb2YgYSBob21lIGluIFdlc3Rlcm4gQWxiZW1hcmxlIHdpbGwgYmUgYWJvdXQgMTAlIGhpZ2hlciB0aGFuIGFuIGVxdWl2YWxlbnQgaG9tZSBpbiBBbGJlbWFybGUuIExpa2V3aXNlIGl0IGFwcGVhcnMgdGhhdCB0aGUgdmFsdWUgb2YgYSBob21lIGluIHRoZSBNb250aWNlbGxvIGRpc3RyaWN0IHdpbGwgYmUgYWJvdXQgNyUgbGVzcyB0aGFuIGFuIGVxdWl2YWxlbnQgaG9tZSBpbiB0aGUgQWxiZW1hcmxlIGRpc3RyaWN0LiAKCiMjIENPREUgQUxPTkcgMwoKMS4gSW5zZXJ0IGEgY29kZSBjaHVuayBiZWxvdyBhbmQgbW9kZWwgYGxvZyh0b3RhbHZhbHVlKWAgYXMgZnVuY3Rpb24gb2YgYGZ1bGxiYXRoYCwgYGZpbnNxZnRgIGFuZCBgY29vbGluZy5gIENhbGwgeW91ciBtb2RlbCBgbTVgLiAKCgoKMi4gV2hhdCBpcyB0aGUgaW50ZXJwcmV0YXRpb24gb2YgY29vbGluZz8gCgoKCgojIyBNb2RlbGluZyBJbnRlcmFjdGlvbnMKCkluIG91ciBtb2RlbCBhYm92ZSB0aGF0IGluY2x1ZGVkIGBoc2Rpc3RyaWN0YCB3ZSBhc3N1bWVkIHRoZSBlZmZlY3RzIHdlcmUgX2FkZGl0aXZlXy4gRm9yIGV4YW1wbGUsIGl0IGRpZG4ndCBtYXR0ZXIgd2hhdCBoaWdoIHNjaG9vbCBkaXN0cmljdCB5b3VyIGhvbWUgd2FzIGluLCB0aGUgZWZmZWN0IG9mIGBmdWxsYmF0aGAgb3IgYGZpbnNxZnRgIHdhcyB0aGUgc2FtZS4gSXQgYWxzbyBhc3N1bWVkIHRoZSBlZmZlY3Qgb2YgZWFjaCBhZGRpdGlvbmFsIGBmdWxsYmF0aGAgd2FzIHRoZSBzYW1lIHJlZ2FyZGxlc3Mgb2YgaG93IGJpZyB0aGUgaG91c2Ugd2FzLCBhbmQgdmljZSB2ZXJzYS4gVGhpcyBtYXkgYmUgdG9vIHNpbXBsZS4gCgpJbnRlcmFjdGlvbnMgYWxsb3cgdGhlIGVmZmVjdHMgb2YgdmFyaWFibGVzIHRvIGRlcGVuZCBvbiBvdGhlciB2YXJpYWJsZXMuIEFnYWluIHN1YmplY3QgbWF0dGVyIGtub3dsZWRnZSBoZWxwcyB3aXRoIHRoZSBwcm9wb3NhbCBvZiBpbnRlcmFjdGlvbnMuIEFzIHdlJ2xsIHNlZSBpbnRlcmFjdGlvbnMgbWFrZSB5b3VyIG1vZGVsIG1vcmUgZmxleGlibGUgYnV0IGhhcmRlciB0byB1bmRlcnN0YW5kLgoKUiBtYWtlcyBpdCBzaW1wbGUgdG8gaW5jbHVkZSBpbnRlcmFjdGlvbnMgaW4gbW9kZWxzLiBKdXN0IGluZGljYXRlIGFuIGludGVyYWN0aW9uIGJldHdlZW4gdHdvIHZhcmlhYmxlcyBieSBwbGFjaW5nIGEgY29sb24gKDopIGJldHdlZW4gdGhlbS4gQmVsb3cgd2UgaW5jbHVkZSAyLXdheSBpbnRlcmFjdGlvbnMuIChZb3UgY2FuIGhhdmUgMy13YXkgYW5kIGhpZ2hlciBpbnRlcmFjdGlvbnMgYnV0IHRoZXkncmUgdmVyeSBkaWZmaWN1bHQgdG8gaW50ZXJwcmV0LikKCmBgYHtyfQptNiA8LSBsbShsb2codG90YWx2YWx1ZSkgfiBmdWxsYmF0aCArIGZpbnNxZnQgKyBoc2Rpc3RyaWN0ICsgCiAgICAgICAgICAgZnVsbGJhdGg6Zmluc3FmdCArIGZ1bGxiYXRoOmhzZGlzdHJpY3QgKyAKICAgICAgICAgICBmaW5zcWZ0OmhzZGlzdHJpY3QsIGRhdGEgPSBob21lcykKc3VtbWFyeShtNikKYGBgCgpJbnRlcnByZXRhdGlvbiBpcyBub3cgbXVjaCBtb3JlIGRpZmZpY3VsdC4gV2UgY2Fubm90IGRpcmVjdGx5IGludGVycHJldCB0aGUgX21haW4gZWZmZWN0c18gb2YgYGZ1bGxiYXRoYCwgYGZpbnNxZnRgIG9yIGBoc2Rpc3RyaWN0YC4gVGhleSBpbnRlcmFjdC4gV2hhdCdzIHRoZSBlZmZlY3Qgb2YgYGZpbnNxZnRgPyBJdCBkZXBlbmRzIG9uIGBmdWxsYmF0aGAgYW5kIGBoc2Rpc3RyaWN0YC4gCgpBcmUgdGhlIGludGVyYWN0aW9ucyAic2lnbmlmaWNhbnQiIG9yIG5lY2Vzc2FyeT8gV2UgY2FuIHVzZSB0aGUgYGFub3ZhKClgIGZ1bmN0aW9uIHRvIGV2YWx1YXRlIHRoaXMgcXVlc3Rpb24uIFRoaXMgcnVucyBhIHNlcmllcyBvZiBfcGFydGlhbCBGLXRlc3RzXy4gRWFjaCByb3cgYmVsb3cgaXMgYSBoeXBvdGhlc2lzIHRlc3QuIFRoZSBudWxsIGlzIHRoZSBtb2RlbCB3aXRoIHRoaXMgcHJlZGljdG9yIGlzIHRoZSBzYW1lIGFzIHRoZSBtb2RlbCB3aXRob3V0IHRoZSBwcmVkaWN0b3IuIFRoZSBhbm92YSB0ZXN0cyBiZWxvdyB1c2Ugd2hhdCBhcmUgY2FsbGVkIF9UeXBlIElfIHN1bXMgb2Ygc3F1YXJlcy4gVGhpcyByZXNwZWN0cyB0aGUgb3JkZXIgb2YgdGhlIHZhcmlhYmxlcyBpbiB0aGUgbW9kZWwuIFNvLi4uCgotIHRoZSBmaXJzdCB0ZXN0IGNvbXBhcmVzIGEgbW9kZWwgd2l0aCBqdXN0IGFuIGludGVyY2VwdCB0byBhIG1vZGVsIHdpdGggYW4gaW50ZXJjZXB0IGFuZCBmdWxsYmF0aC4KLSB0aGUgc2Vjb25kIHRlc3QgY29tcGFyZXMgYSBtb2RlbCB3aXRoIGFuIGludGVyY2VwdCBhbmQgZnVsbGJhdGggdG8gYSBtb2RlbCB3aXRoIGFuIGludGVyY2VwdCwgZnVsbGJhdGggYW5kIGZpbnNxZnQuCi0gQW5kIHNvIG9uLgoKSWYgdGhlIG51bGwgaXMgdHJ1ZSwgdGhlIEYgdmFsdWUgc2hvdWxkIGJlIGNsb3NlIHRvIDEuCgpgYGB7cn0KYW5vdmEobTYpCmBgYAoKVGhlIGludGVyYWN0aW9uIGBmaW5zcWZ0OmhzZGlzdHJpY3RgIGRvZXNuJ3QgYXBwZWFyIHRvIGNvbnRyaWJ1dGUgbXVjaCB0byB0aGUgbW9kZWwgZ2l2ZW4gdGhhdCBfZXZlcnl0aGluZyBlbHNlIGFib3ZlIGl0XyBpcyBpbiB0aGUgbW9kZWwuCgpKdXN0IGJlY2F1c2UgYW4gaW50ZXJhY3Rpb24gaXMgc2lnbmlmaWNhbnQgZG9lc24ndCBuZWNlc3NhcmlseSBtZWFuIGl0J3MgaW50ZXJlc3Rpbmcgb3Igd29ydGh3aGlsZS4gTm9yIGNhbiB3ZSBpbmZlciBhbnl0aGluZyBhYm91dCB0aGUgbmF0dXJlIG9mIHRoZSBpbnRlcmFjdGlvbiBmcm9tIHRoZSBBTk9WQSB0YWJsZS4KCl9FZmZlY3QgcGxvdHNfIGNhbiBoZWxwIHVzIHZpc3VhbGl6ZSBhbmQgbWFrZSBzZW5zZSBvZiBtb2RlbHMgd2l0aCBpbnRlcmFjdGlvbnMuIExldCdzIG1ha2Ugb25lIHVzaW5nIHRoZSBnZ2VmZmVjdHMgcGFja2FnZSBhbmQgdGFsayBhYm91dCB3aGF0IGl0J3Mgc2hvd2luZy4KCmBgYHtyfQpsaWJyYXJ5KGdnZWZmZWN0cykKcGxvdChnZ3ByZWRpY3QobTYsIHRlcm1zID0gYygiZnVsbGJhdGgiLCAiaHNkaXN0cmljdCIpKSkKIyBwbGFjZSBmdWxsYmF0aCBvbiB4LWF4aXMsIGdyb3VwIGJ5IGhzZGlzdHJpY3QKYGBgCgpXaGF0J3MgdGhlIGVmZmVjdCBvZiBgZnVsbGJhdGhgPyBJdCBkZXBlbmRzLiBJdCdzIG1vcmUgZHJhbWF0aWMgaW4gV2VzdGVybiBBbGJlbWFybGUgYW5kIE1vbnRpY2VsbG8uIE9mIGNvdXJzZSBhIGxvdCBvZiB0aGUgZGlmZmVyZW5jZSBjb21lcyBhdCBleHRyZW1lIHZhbHVlcyBvZiBgZnVsbGJhdGhgLiBUaGUgInJpYmJvbnMiIGFyb3VuZCB0aGUgbGluZXMgYXJlIDk1JSBjb25maWRlbmNlIGludGVydmFscy4gCgpXaGF0IGV4YWN0bHkgd2FzIHBsb3R0ZWQ/IFdlIGNhbiBzZWUgYnkgY2FsbGluZyBgZ2dwcmVkaWN0YCB3aXRob3V0IGBwbG90YAoKYGBge3J9CmdncHJlZGljdChtNiwgdGVybXMgPSBjKCJmdWxsYmF0aCIsICJoc2Rpc3RyaWN0IikpCmBgYAoKYGdncHJlZGljdGAgdXNlZCBvdXIgbW9kZWwgdG8gbWFrZSBgdG90YWx2YWx1ZWAgcHJlZGljdGlvbnMgZm9yIHZhcmlvdXMgdmFsdWVzIG9mIGBmdWxsYmF0aGAgaW4gdGhlIHRocmVlIHNjaG9vbCBkaXN0cmljdHMsIGhvbGRpbmcgZmluc3FmdCBhdCAxODI4ICh0aGUgbWVkaWFuIG9mIGZpbnNxZnQpLiAKCldlIGNhbiBzcGVjaWZ5IG91ciB2YWx1ZXMgaWYgd2UgbGlrZS4gRm9yIGV4YW1wbGUsIG1ha2UgYW4gZWZmZWN0IHBsb3QgZm9yIDEgLSA1IGJhdGhyb29tcyBhbmQgaG9sZCBgZmluc3FmdGAgYXQgMjAwMDoKCmBgYHtyfQpwbG90KGdncHJlZGljdChtNiwgdGVybXMgPSBjKCJmdWxsYmF0aFsxOjVdIiwgImhzZGlzdHJpY3QiKSwgCiAgICAgICAgICBjb25kaXRpb24gPSBjKGZpbnNxZnQgPSAyMDAwKSkpCmBgYAoKV2hhdCBhYm91dCB0aGUgZWZmZWN0cyBvZiBgZmluc3FmdGAgYW5kIGBmdWxsYmF0aGA/IFRoaXMgaXMgX2FuIGludGVyYWN0aW9uIG9mIHR3byBudW1lcmljIHZhcmlhYmxlc18uIFRoZSBzZWNvbmQgdmFyaWFibGUgaGFzIHRvIHNlcnZlIGFzIGEgZ3JvdXBpbmcgdmFyaWFibGUgd2hlbiBjcmVhdGluZyBhbiBlZmZlY3QgcGxvdC4gQmVsb3cgd2Ugc2V0IGBmdWxsYmF0aGAgdG8gdGFrZSB2YWx1ZXMgMiAtIDUgYW5kIGBmaW5zcWZ0YCB0byB0YWtlIHZhbHVlcyBvZiAxMDAwIC0gNDAwMCBpbiBzdGVwcyBvZiA1MDAuCgpgYGB7cn0KcGxvdChnZ3ByZWRpY3QobTYsIHRlcm1zID0gYygiZmluc3FmdFsxMDAwOjQwMDAgYnk9NTAwXSIsICJmdWxsYmF0aFsyOjVdIikpKQpgYGAKClRoZSBlZmZlY3Qgb2YgYGZpbnNxZnRgIHNlZW1zIHRvIHRhcGVyIG9mZiB0aGUgbW9yZSBmdWxsYmF0aHMgYSBob3VzZSBoYXMuIEJ1dCB0aGVyZSBhcmUgZmV3IGxhcmdlIGhvbWVzIHdpdGggMiBmdWxsIGJhdGhzLCBhbmQgbGlrZXdpc2UsIGZldyBzbWFsbCBob21lcyB3aXRoIDUgZnVsbCBiYXRocy4gRXZlbiB0aG91Z2ggdGhlIGludGVyYWN0aW9uIGlzICJzaWduaWZpY2FudCIgaW4gdGhlIG1vZGVsLCBpdCdzIGNsZWFybHkgYSB2ZXJ5IHNtYWxsIGludGVyYWN0aW9uIGFuZCBwcm9iYWJseSBub3QgaW1wb3J0YW50LgoKIyMgQ09ERSBBTE9ORyA0CgoxLiBJbnNlcnQgYSBjb2RlIGNodW5rIGJlbG93IGFuZCBtb2RlbCBgbG9nKHRvdGFsdmFsdWUpYCBhcyBmdW5jdGlvbiBvZiBgZnVsbGJhdGhgLCBgZmluc3FmdGAsIGBjb29saW5nYCwgYW5kIHRoZSBpbnRlcmFjdGlvbiBvZiBgZmluc3FmdGAgYW5kIGBjb29saW5nYC4gQ2FsbCB5b3VyIG1vZGVsIGBtN2AuIElzIHRoZSBpbnRlcmFjdGlvbiB3YXJyYW50ZWQ/CgoKCjIuIFZpc3VhbGl6ZSB0aGUgaW50ZXJhY3Rpb24gdXNpbmcgdGhlIGBnZ3ByZWRpY3RgIGZ1bmN0aW9uLiBQZXJoYXBzIHVzZSBgWzEwMDA6NDAwMCBieT01MDBdYCB0byBzZXQgdGhlIHJhbmdlIG9mIGBmaW5zcWZ0YCBvbiB0aGUgeC1heGlzLiBIb3cgbm90YWJsZSBpcyB0aGlzIGludGVyYWN0aW9uPwoKCgojIyBOb25saW5lYXIgRWZmZWN0cwoKU28gZmFyIHdlIGhhdmUgYXNzdW1lZCB0aGF0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBhIHByZWRpY3RvciBhbmQgdGhlIHJlc3BvbnNlIGlzIF9saW5lYXJfIChlZywgZm9yIGEgMS11bml0IGNoYW5nZSBpbiBhIHByZWRpY3RvciwgdGhlIHJlc3BvbnNlIGNoYW5nZXMgYnkgYSBmaXhlZCBhbW91bnQpLiBUaGF0IGFzc3VtcHRpb24gY2FuIHNvbWV0aW1lcyBiZSB0b28gc2ltcGxlIGFuZCBub3QgcmVhbGlzdGljLiBGb3J0dW5hdGVseSB0aGVyZSBhcmUgd2F5cyB0byBmaXQgbm9uLWxpbmVhciBlZmZlY3RzIGluIGEgbGluZWFyIG1vZGVsLgoKSGVyZSdzIGEgcXVpY2sgZXhhbXBsZSBvZiBzaW11bGF0ZWQgbm9uLWxpbmVhciBkYXRhOiBhIDJuZCBkZWdyZWUgcG9seW5vbWlhbC4KCmBgYHtyfQp4IDwtIHNlcShmcm9tID0gLTEwLCB0byA9IDEwLCBsZW5ndGgub3V0ID0gMTAwKQpzZXQuc2VlZCgzKQp5IDwtIDEuMiArIDIqeCArIDAuOSp4XjIgKyBybm9ybSgxMDAsIG1lYW4gPSAwLCBzZCA9IDEwKQpubF9kYXQgPC0gZGF0YS5mcmFtZSh5LCB4KQpwbG90KHkgfiB4LCBubF9kYXQpCmBgYAoKQ2xlYXJseSBhIHN0cmFpZ2h0LWxpbmUgbW9kZWwgd2lsbCBub3Qgd29yayB3ZWxsIGZvciB0aGlzIGRhdGEuIFRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB4IGFuZCB5IGlzIG5vdCBhZGVxdWF0ZWx5IHN1bW1hcml6ZWQgYnkgYSBzdHJhaWdodCBsaW5lIG1vZGVsLgoKSWYgd2Ugd2FudGVkIHRvIHRyeSB0byAicmVjb3ZlciIgdGhlIHdlaWdodHMgd2UgdXNlZCBpbiBzaW11bGF0aW5nIHRoaXMgZGF0YSB3ZSBjb3VsZCBmaXQgYSBwb2x5bm9taWFsIG1vZGVsIHVzaW5nIHRoZSBgcG9seSgpYCBmdW5jdGlvbiBpbiB0aGUgZm9ybXVsYSBzeW50YXg6CgoKYGBgCiMjIEVYQU1QTEUgQ09ERTsgTk9UIElOVEVORUQgVE8gQkUgUlVOCm5sbTEgPC0gbG0oeSB+IHBvbHkoeCwgZGVncmVlID0gMiwgcmF3ID0gVFJVRSksIGRhdGEgPSBubF9kYXQpCmBgYAoKSG93ZXZlciwgdGhlIHJlY29tbWVuZGVkIGFwcHJvYWNoIHRvIGZpdHRpbmcgbm9uLWxpbmVhciBlZmZlY3RzIGlzIHRvIHVzZSBfbmF0dXJhbCBzcGxpbmVzXyBpbnN0ZWFkIG9mIHBvbHlub21pYWxzLiBOYXR1cmFsIHNwbGluZXMgZXNzZW50aWFsbHkgYWxsb3cgdXMgdG8gZml0IGEgc2VyaWVzIG9mIGN1YmljIHBvbHlub21pYWxzIGNvbm5lY3RlZCBhdCBrbm90cyBsb2NhdGVkIGluIHRoZSByYW5nZSBvZiBvdXIgZGF0YS4KClRoZSBlYXNpZXN0IG9wdGlvbiBpcyB0byB1c2UgdGhlIGBucygpYCBmdW5jdGlvbiBmcm9tIHRoZSBzcGxpbmVzIHBhY2thZ2UsIHdoaWNoIGNvbWVzIGluc3RhbGxlZCB3aXRoIFIuIGBuc2Agc3RhbmRzIGZvciAibmF0dXJhbCBzcGxpbmVzIi4gVGhlIHNlY29uZCBhcmd1bWVudCBpcyB0aGUgZGVncmVlcyBvZiBmcmVlZG9tIChgZGZgKS4gSXQgbWF5IGhlbHAgdG8gdGhpbmsgb2YgYGRmYCBhcyB0aGUgbnVtYmVyIG9mIHRpbWVzIHRoZSBzbW9vdGggbGluZSBjaGFuZ2VzIGRpcmVjdGlvbnMuIAoKRnJhbmsgSGFycmVsbCBzdGF0ZXMgaW4gaGlzIGJvb2sgX1JlZ3Jlc3Npb24gTW9kZWwgU3RyYXRlZ2llc18gdGhhdCAzIHRvIDUgYGRmYCBpcyBhbG1vc3QgYWx3YXlzIHN1ZmZpY2llbnQuIEhpcyBiYXNpYyBhZHZpY2UgaXMgdG8gYWxsb2NhdGUgbW9yZSBgZGZgIHRvIHZhcmlhYmxlcyB5b3UgdGhpbmsgYXJlIG1vcmUgaW1wb3J0YW50LgoKTGV0J3Mgc2VlIGhvdyBpdCB3b3JrcyB3aXRoIG91ciBzaW11bGF0ZWQgZGF0YS4KCmBgYHtyfQpsaWJyYXJ5KHNwbGluZXMpCm5sbTIgPC0gbG0oeSB+IG5zKHgsIGRmID0gMiksIGRhdGEgPSBubF9kYXQpCnN1bW1hcnkobmxtMikKYGBgCgpUaGUgc3VtbWFyeSBvdXRwdXQgaXMgaW1wb3NzaWJsZSB0byBpbnRlcnByZXQuIExldCdzIHZpc3VhbGl6ZSB0aGUgZml0IHdpdGggYW4gZWZmZWN0IHBsb3QuCgpgYGB7cn0KbGlicmFyeShnZ2VmZmVjdHMpCnBsb3QoZ2dwcmVkaWN0KG5sbTIsIHRlcm1zID0gIngiKSwgc2hvd19kYXRhID0gVFJVRSkKYGBgCgoKTGV0J3MgcmV0dXJuIHRvIHRoZSBob21lcyBkYXRhIGFuZCBmaXQgYSBub24tbGluZWFyIGVmZmVjdCBmb3IgYGZpbnNxZnRgIHVzaW5nIGEgbmF0dXJhbCBzcGxpbmUgd2l0aCA1IGBkZmAuIEJlbG93IHdlIGFsc28gaW5jbHVkZSBgaHNkaXN0cmljdGAgYW5kIGBsb3RzaXplYCBhbmQgYWxsb3cgYGZpbnNxZnRgIGFuZCBgaHNkaXN0cmljdGAgdG8gaW50ZXJhY3QuCgpgYGB7cn0KbmxtMyA8LSBsbShsb2codG90YWx2YWx1ZSkgfiBucyhmaW5zcWZ0LCA1KSArIGxvdHNpemUgKyBoc2Rpc3RyaWN0ICsKICAgICAgICAgICBucyhmaW5zcWZ0LCA1KTpoc2Rpc3RyaWN0LCAKICAgICAgICAgZGF0YSA9IGhvbWVzKQpgYGAKClRoZSBgYW5vdmFgIGZ1bmN0aW9uIGFsbG93cyB1cyB0byBhc3Nlc3MgdGhlIG5vbi1saW5lYXIgZWZmZWN0IGFuZCB0aGUgaW50ZXJhY3Rpb24uIFNvbWUgc29ydCBvZiBpbnRlcmFjdGlvbiBiZXR3ZWVuIGZpbnNxZnQgYW5kIGhzZGlzdHJpY3QgYXBwZWFycyB0byBiZSBwcmVzZW50LgoKYGBge3J9CmFub3ZhKG5sbTMpCmBgYAoKVGhlIHN1bW1hcnkgb3V0cHV0IGlzIGltcG9zc2libGUgdG8gaW50ZXJwcmV0LgoKYGBge3J9CnN1bW1hcnkobmxtMykKCmBgYAoKCkVmZmVjdCBwbG90cyBhcmUgb3VyIG9ubHkgaG9wZSBmb3IgdW5kZXJzdGFuZGluZyB0aGlzIG1vZGVsLiBCZWxvdyB3ZSBwbG90IHByZWRpY3RlZCB0b3RhbHZhbHVlIG92ZXIgZmluc3FmdCByYW5naW5nIGZyb20gMTAwMCAtIDMwMDAsIGdyb3VwZWQgYnkgaHNkaXN0cmljdC4KCmBgYHtyfQpwbG90KGdncHJlZGljdChubG0zLCB0ZXJtcyA9IGMoImZpbnNxZnRbMTAwMDozMDAwIGJ5PTI1MF0iLCAiaHNkaXN0cmljdCIpKSkKYGBgCgpUaGUgZWZmZWN0IG9mIGBmaW5zcWZ0YCBvbiBgdG90YWx2YWx1ZWAgc2VlbXMgbW9yZSBkcmFtYXRpYyBpbiBXZXN0ZXJuIEFsYmVtYXJsZSBvbmNlIHlvdSBnbyBiZXlvbmQgMTUwMCBzcSBmdC4KCkRvZXMgdGhlIG1vZGVsIHNpbXVsYXRlIGRhdGEgc2ltaWxhciBpbiBkaXN0cmlidXRpb24gdG8gdGhlIG9ic2VydmVkIGRhdGE/CgpgYGB7cn0Kc2ltNCA8LSBzaW11bGF0ZShubG0zLCBuc2ltID0gNTApCnBsb3QoZGVuc2l0eShsb2coaG9tZXMkdG90YWx2YWx1ZSkpKQpmb3IoaSBpbiAxOjUwKWxpbmVzKGRlbnNpdHkoc2ltNFtbaV1dKSwgY29sID0gImdyZXk4MCIpCmBgYAoKV2Ugc2hvdWxkIHN0aWxsIGNoZWNrIG1vZGVsIGFzc3VtcHRpb25zLgoKYGBge3J9CnBsb3QobmxtMykKYGBgCgpIb21lcyAxMiwgNDAsIDk2MyBhbmQgMTgxMCBzZWVtIHRvIHN0YW5kIG91dC4gTGV0J3MgaGF2ZSBhIGxvb2suCgpgYGB7cn0KaCA8LSBjKDEyLCA0MCwgOTYzLCAxODEwKQpob21lc1toLGMoInRvdGFsdmFsdWUiLCAiZmluc3FmdCIsICJsb3RzaXplIildCmBgYAoKSG9tZXMgMTIgYW5kIDQwIGFyZSB2ZXJ5IGxvdyBpbiB0b3RhbHZhbHVlIGFuZCB0aGUgbW9kZWwgd2F5IG92ZXJwcmVkaWN0cyB0aGVpciB2YWx1ZXMuIEhvbWUgOTYzIGhhcyBhIG1hc3NpdmUgdG90YWx2YWx1ZSB3aXRoIDAgYWNyZXMgb2YgbG90c2l6ZS4gSG9tZSAxODEwIGlzIG9uIDYxMSBhY3JlcyBhbmQgdGhhdCB2YWx1ZSBoYXMgYSBoaWdoIGxldmVyYWdlIG9uIGl0cyBmaXR0ZWQgdmFsdWUuIAoKYGBge3J9CmNiaW5kKG9ic2VydmVkID0gaG9tZXMkdG90YWx2YWx1ZVtoXSwgZml0dGVkID0gZXhwKGZpdHRlZChubG0zKVtoXSkpCmBgYAoKIyMgQ09ERSBBTE9ORyA1CgoxLiBJbnNlcnQgYSBjb2RlIGNodW5rIGJlbG93IGFuZCBtb2RlbCBgbG9nKHRvdGFsdmFsdWUpYCBhcyBmdW5jdGlvbiBvZiBgZmluc3FmdGAgd2l0aCBhIG5hdHVyYWwgc3BsaW5lIG9mIDUgYGRmYCwgYGNvb2xpbmdgLCBhbmQgdGhlIGludGVyYWN0aW9uIG9mIGBjb29saW5nYCBhbmQgYGZpbnNxZnRgIChuYXR1cmFsIHNwbGluZSBvZiA1IGBkZmApLiBDYWxsIHlvdXIgbW9kZWwgYG5sbTRgLgoKCjIuIFVzZSB0aGUgYGFub3ZhYCBmdW5jdGlvbiB0byBjaGVjayB3aGV0aGVyIHRoZSBpbnRlcmFjdGlvbiBhcHBlYXJzIG5lY2Vzc2FyeS4gV2hhdCBkbyB5b3UgdGhpbms/CgoKMy4gQ3JlYXRlIGFuIGVmZmVjdCBwbG90IG9mIGBmaW5zcWZ0YCBieSBgY29vbGluZ2AuIE1heWJlIHRyeSBgWzEwMDA6NTAwMCBieT0yNTBdYCBmb3IgdGhlIHJhbmdlIG9mIHZhbHVlcyBmb3IgYGZpbnNxZnRgLgoKCgojIyBXcmFwLXVwCgpUaGlzIHdhcyBtZWFudCB0byBzaG93IHlvdSB0aGUgYmFzaWNzIG9mIGxpbmVhciBtb2RlbGluZyBpbiBSLiBIb3BlZnVsbHkgeW91IGhhdmUgYSBiZXR0ZXIgZ3Jhc3Agb2YgaG93IGxpbmVhciBtb2RlbGluZyB3b3Jrcy4gU2Nyb2xsIGRvd24gZm9yIGEgZmV3IG1vcmUgdG9waWNzLgoKV2hhdCB3ZSBkaWQgdG9kYXkgd29ya3MgZm9yIF9pbmRlcGVuZGVudCwgbnVtZXJpYyBvdXRjb21lc18uIFdlIGhhZCBvbmUgb2JzZXJ2YXRpb24gcGVyIGhvbWUgYW5kIG91ciByZXNwb25zZSB3YXMgYHRvdGFsdmFsdWVgLCBhIG51bWJlci4gT3VyIG1vZGVscyByZXR1cm5lZCBleHBlY3RlZCBfbWVhbl8gdG90YWwgdmFsdWUgZ2l2ZW4gdmFyaW91cyBwcmVkaWN0b3JzLiBUaGlzIGlzIGEgcHJldHR5IHNpbXBsZSBkZXNpZ24uCgpUaGluZ3MgZ2V0IG1vcmUgY29tcGxpY2F0ZWQgd2hlbiB5b3UgaGF2ZSwgc2F5LCBiaW5hcnkgcmVzcG9uc2VzIG9yIG11bHRpcGxlIG1lYXN1cmVzIG9uIHRoZSBzYW1lIHN1YmplY3QgKG9yIGhvbWUpLiBBIG5vbi1leGhhdXN0aXZlIGxpc3Qgb2Ygb3RoZXIgdHlwZXMgb2Ygc3RhdGlzdGljYWwgbW9kZWxpbmcgaW5jbHVkZToKCi0gZ2VuZXJhbGl6ZWQgbGluZWFyIG1vZGVscyAoZm9yIGJpbmFyeSBhbmQgY291bnQgcmVzcG9uc2VzKQotIG11bHRpbm9taWFsIGxvZ2l0IG1vZGVscyAoZm9yIGNhdGVnb3JpY2FsIHJlc3BvbnNlcykKLSBvcmRlcmVkIGxvZ2l0IG1vZGVscyAoZm9yIG9yZGVyZWQgY2F0ZWdvcmljYWwgcmVzcG9uc2VzKQotIG1peGVkLWVmZmVjdCBvciBsb25naXR1ZGluYWwgbGluZWFyIG1vZGVscyAoZm9yIHJlc3BvbnNlcyB3aXRoIG11bHRpcGxlIG1lYXN1cmVzIG9uIHRoZSBzYW1lIHN1YmplY3Qgb3IgY2x1c3RlcnMgb2YgcmVsYXRlZCBtZWFzdXJlcykKLSBzdXJ2aXZhbCBtb2RlbHMgKGZvciByZXNwb25zZXMgdGhhdCBtZWFzdXJlIHRpbWUgdG8gYW4gZXZlbnQpCi0gdGltZSBzZXJpZXMgbW9kZWxzIChmb3IgcmVzcG9uc2VzIHRoYXQgZXhoaWJpdCwgc2F5LCBzZWFzb25hbCB2YXJpYXRpb24gb3ZlciB0aW1lKQoKKipSZWZlcmVuY2VzKioKCi0gRmFyYXdheSwgSi4gKDIwMDUpLiBfTGluZWFyIE1vZGVscyBpbiBSXy4gTG9uZG9uOiBDaGFwbWFuICYgSGFsbC4KLSBGb3gsIEouICgyMDAyKS4gX0EgUiBhbmQgUy1QbHVzIENvbXBhbmlvbiB0byBBcHBsaWVkIFJlZ3Jlc3Npb25fLiBMb25kb246IFNhZ2UuCi0gSGFycmVsbCwgRi4gKDIwMTUpLiBfUmVncmVzc2lvbiBNb2RlbGluZyBTdHJhdGVnaWVzXyAoMm5kIGVkLikuIE5ldyBZb3JrOiBTcHJpbmdlci4KLSBLdXRuZXIsIE0uLCBldCBhbC4gKDIwMDUpLiBfQXBwbGllZCBMaW5lYXIgU3RhdGlzdGljYWwgTW9kZWxzXyAoNXRoIGVkLikuIE5ldyBZb3JrOiBNY0dyYXctSGlsbC4KLSBNYWluZG9uYWxkIEouLCBCcmF1biwgSi5XLiAoMjAxMCkuIF9EYXRhIEFuYWx5c2lzIGFuZCBHcmFwaGljcyBVc2luZyBSXyAoM3JkIGVkLikuIENhbWJyaWRnZTogQ2FtYnJpZGdlIFVuaXYgUHJlc3MuCgoKIyMgQm9udXMgbWF0ZXJpYWwvdG9waWNzIGN1dCBmb3IgdGltZQoKCgoKIyMgSG93IGRvZXMgbG0oKSB3b3JrPwoKYGxtKClgIGVzdGltYXRlcyByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyB1c2luZyBvcmRpbmFyeSBsZWFzdCBzcXVhcmVzLCBvciBPTFMuIFRoZSBmb3JtdWxhIGZvciB0aGlzIHVzaW5nIG1hdHJpeCBhbGdlYnJhIGlzIGV4cHJlc3NlZCBhcyBmb2xsb3dzOgoKJCRcaGF0e1xiZXRhfSA9IChYJ1gpXnstMX1YJ1kgJCQKCndoZXJlIFggaXMgdGhlIG1vZGVsIG1hdHJpeCAob3VyIHByZWRpY3RvcnMgYXMgdGhleSBhcHBlYXIgaW4gdGhlIG1vZGVsKSBhbmQgWSBpcyB0aGUgZGVwZW5kZW50IHZhcmlhYmxlLiBUaGUgcHJpbWUgc3ltYm9sIGAnYCBtZWFucyB0cmFuc3Bvc2UgdGhlIG1hdHJpeC4gVGhlIGAtMWAgbWVhbnMgdGFrZSB0aGUgaW52ZXJzZS4gUiB3YXMgZGV2ZWxvcGVkIHRvIG1ha2UgY2FsY3VsYXRpb25zIHN1Y2ggYXMgdGhlc2UgcXVpdGUgZWFzeS4gCgpMZXQncyByZXZpc2l0IG1vZGVsIG0yLgoKYGBge3J9CmZvcm11bGEobTIpCmBgYAoKSGVyZSBhcmUgdGhlIGNvZWZmaWNpZW50cyByZXR1cm5lZCBieSBgbG0oKWA6CgpgYGB7cn0Kcm91bmQoY29lZihtMiksIDQpCmBgYAoKTGV0J3MgZmluZCB0aGVzZSB1c2luZyB0aGUgZm9ybXVsYSBhYm92ZS4gV2UgY2FuIHVzZSBgbW9kZWwubWF0cml4KClgIHRvIGdldCBYLCBgdCgpYCB0byB0cmFuc3Bvc2UsIGFuZCBgc29sdmUoKWAgdG8gdGFrZSB0aGUgaW52ZXJzZS4gUGVyZm9ybSBtYXRyaXggbXVsdGlwbGljYXRpb24gd2l0aCBgJSolYAoKYGBge3J9ClggPC0gbW9kZWwubWF0cml4KH4gZmluc3FmdCArIGJlZHJvb20gKyBsb3RzaXplLCBkYXRhID0gaG9tZXMpClkgPC0gbG9nKGhvbWVzJHRvdGFsdmFsdWUpCkIgPC0gc29sdmUodChYKSAlKiUgWCkgJSolIHQoWCkgJSolIFkKcm91bmQoQiwgNCkKYGBgCgpXaGlsZSB0aGlzIGlzIHRoZW9yZXRpY2FsbHkgd2hhdCBgbG0oKWAgZG9lcywgaXQgYWN0dWFsbHkgdXNlcyBtb3JlIHNvcGhpc3RpY2F0ZWQgbWV0aG9kcyBmb3IgZmFzdGVyIHBlcmZvcm1hbmNlIGFuZCBwcm90ZWN0aW9uIGFnYWluc3QgbnVtZXJpYyBpbnN0YWJpbGl0eS4gVGhpcyBwYWdlIGdvZXMgaW50byBtb3JlIGRldGFpbHMgaWYgeW91J3JlIGludGVyZXN0ZWQgaW4gbGVhcm5pbmcgbW9yZToKCmh0dHBzOi8vZ2Vub21pY3NjbGFzcy5naXRodWIuaW8vYm9vay9wYWdlcy9xcl9hbmRfcmVncmVzc2lvbi5odG1sCgoKIyMgQU5PVkEgcmV2aXNpdGVkCgpXZSBjYW4gYWxzbyB1c2UgdGhlIGBhbm92YSgpYCBmdW5jdGlvbiB0byBjb21wYXJlIF9uZXN0ZWQgbW9kZWxzXy4gVGhpcyBtZWFucyBvbmUgbW9kZWwgaXMgYSBzdWJzZXQgb2YgYW5vdGhlci4gRm9yIGV4YW1wbGUsIGJlbG93IHdlIGZpdCBwcm9ncmVzc2l2ZWx5IG1vcmUgY29tcGxpY2F0ZWQgbW9kZWxzLCBidWlsZGluZyB1cCB0byBvdXIgbW9kZWwsIGBtMmA6IGBsb2codG90YWx2YWx1ZSkgfiBmaW5zcWZ0ICsgYmVkcm9vbSArIGxvdHNpemVgCgpgYGB7cn0KbW9kXzAwIDwtIGxtKGxvZyh0b3RhbHZhbHVlKSB+IDEsIGRhdGEgPSBob21lcykgIyBpbnRlcmNlcHQtb25seQptb2RfMDEgPC0gbG0obG9nKHRvdGFsdmFsdWUpIH4gZmluc3FmdCwgZGF0YSA9IGhvbWVzKQptb2RfMDIgPC0gbG0obG9nKHRvdGFsdmFsdWUpIH4gZmluc3FmdCArIGJlZHJvb20sIGRhdGEgPSBob21lcykKbW9kXzAzIDwtIGxtKGxvZyh0b3RhbHZhbHVlKSB+IGZpbnNxZnQgKyBiZWRyb29tICsgbG90c2l6ZSwgZGF0YSA9IGhvbWVzKQpgYGAKClRoZSBgYW5vdmEoKWAgZnVuY3Rpb24gYWxsb3dzIHVzIHRvIGNvbXBhcmUgdGhlc2UgbmVzdGVkIG1vZGVscy4gVGhlIG51bGwgaHlwb3RoZXNpcyBpcyB0aGF0IHR3byBtb2RlbHMgYXJlIHRoZSBzYW1lLCBpbiB0aGUgc2Vuc2UgdGhleSBleHBsYWluIHRoZSBzYW1lIGFtb3VudCBvZiB2YXJpYW5jZSBpbiB0aGUgcmVzcG9uc2UgdmFyaWFibGUsIGBsb2codG90YWx2YWx1ZSlgLiBBIGxvdyBwLXZhbHVlIHByb3ZpZGVzIGV2aWRlbmNlIHRoYXQgdGhlIG1vcmUgY29tcGxpY2F0ZWQgbW9kZWwgd2l0aCBtb3JlIHZhcmlhYmxlcyBpcyBhIGJldHRlciBtb2RlbC4gVGhlIHVzdWFsIHdheSB0byB1c2UgYGFub3ZhKClgIHRvIGNvbXBhcmUgbW9kZWxzIGlzIHRvIGxpc3QgdGhlIHNtYWxsZXIgbW9kZWxzIGZpcnN0LiBCZWxvdyBhcmUgdGhyZWUgdGVzdHM6IAoKMS4gTW9kZWwgMiB2cyBNb2RlbCAxCjIuIE1vZGVsIDMgdnMgTW9kZWwgMgozLiBNb2RlbCA0IHZzIE1vZGVsIDMKClRoZSBlbmQgcmVzdWx0IGlzIHRoYXQgbW9kZWwgNCBpcyBzdXBlcmlvciB0byB0aGUgb3RoZXIgdGhyZWUgbW9kZWxzLgoKYGBge3J9CmFub3ZhKG1vZF8wMCwgbW9kXzAxLCBtb2RfMDIsIG1vZF8wMykKYGBgCgpOb3RpY2UgdGhpcyBpZGVudGljYWwgdG8gY2FsbGluZyBgYW5vdmEoKWAgb24gdGhlIGZ1bGwgbW9kZWwuCgpgYGB7cn0KYW5vdmEobTIpCmBgYAoKQW5vdGhlciBhcHByb2FjaCBpcyB1c2luZyBUeXBlIElJIHN1bXMgb2Ygc3F1YXJlcywgd2hlcmUgZWFjaCB2YXJpYWJsZSBpcyB0ZXN0ZWQgYXNzdW1pbmcgX2FsbCBvdGhlciB2YXJpYWJsZXMgYXJlIGluIHRoZSBtb2RlbF8uIE9uZSBhcHByb2FjaCB0byBwZXJmb3JtaW5nIHRoaXMgdGVzdCBpcyB0aGUgYGRyb3AxKClgIGZ1bmN0aW9uLiBUaGUgbmFtZSBjb21lcyBmcm9tIHRoZSBmYWN0IHdlJ3JlIGRyb3BwaW5nIG9uZSB2YXJpYWJsZSBhdCBhIHRpbWUuIFRoZSBudWxsIGh5cG90aGVzaXMgaXMgZHJvcHBpbmcgdGhlIHZhcmlhYmxlIGZyb20gdGhlIG1vZGVsIGhhcyBubyBlZmZlY3QuIEEgbG93IHAtdmFsdWUgcHJvdmlkZXMgZXZpZGVuY2UgYWdhaW5zdCB0aGlzIGh5cG90aGVzaXMuIEJlbG93IGVhY2ggYXJlIHRocmVlIHRlc3RzOgoKMS4gRnVsbCBtb2RlbCB2cyBGdWxsIG1vZGVsIHdpdGhvdXQgZmluc3FmdAoyLiBGdWxsIG1vZGVsIHZzIEZ1bGwgbW9kZWwgd2l0aG91dCBiZWRyb29tCjMuIEZ1bGwgbW9kZWwgdnMgRnVsbCBtb2RlbCB3aXRob3V0IGxvdGlzemUKCkVhY2ggdGVzdCBpcyBzb3VuZGx5IHJlamVjdGVkLiBUaGUgZnVsbCBtb2RlbCBpcyBtdWNoIGJldHRlciB3aXRoIGFsbCB0aHJlZSBwcmVkaWN0b3JzLgoKYGBge3J9CmRyb3AxKG0yLCB0ZXN0ID0gIkYiKQpgYGAKClRoaXMgdGVzdCBpcyBhbHNvIGltcGxlbWVudGVkIGluIHRoZSBgQW5vdmEoKWAgZnVuY3Rpb24gaW4gdGhlIGNhciBwYWNrYWdlLgoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygiY2FyIikKbGlicmFyeShjYXIpCkFub3ZhKG0yKQpgYGAKCiMjIEFJQyBhbmQgQklDCgpBSUMgKEFrYWlrZSBJbmZvcm1hdGlvbiBDcml0ZXJpb24pIGlzIGEgc3RhdGlzdGljIGRlc2lnbmVkIHRvIGhlbHAgdXMgY2hvb3NlIGEgbW9kZWwgd2l0aCB0aGUgYmVzdCBwcmVkaWN0aXZlIHBvd2VyIGFtb25nIGEgZ3JvdXAgb2YgbW9kZWxzLiBUaGUgdmFsdWUgb2YgIEFJQyBkb2Vzbid0IHJlYWxseSBoYXZlIGFueSBpbnRlcnByZXRhdGlvbi4gSXQncyBtZWFudCB0byBiZSBjb21wYXJlZCB0byBvdGhlciBBSUMgdmFsdWVzLiBUaGUgbG93ZXIgdGhlIEFJQyB0aGUgYmV0dGVyLiBXZSBjYW4gdXNlIHRoZSBgQUlDKClgIGZ1bmN0aW9uIGluIFIgdG8gY29tcGFyZSBtdWx0aXBsZSBtb2RlbHMuIEZvciBleGFtcGxlLCBgbW9kXzAzYCBzZWVtcyBwcmVmZXJhYmxlIHRvIGBtb2RfMDJgIGJlY2F1c2UgdGhlIEFJQyB2YWx1ZSBpcyBzbyBtdWNoIHNtYWxsZXIuIAoKYGBge3J9CkFJQyhtb2RfMDIsIG1vZF8wMykKYGBgCgpBSUMgaXMgdGhlIGxvZy1saWtlbGlob29kIG9mIHRoZSBtb2RlbCBtdWx0aXBsaWVkIGJ5IC0yIHdpdGggMiB4IGRmIGFkZGVkIHRvIGl0LiBUaGUgMiB4IGRmIHBhcnQgaXMgYSBfcGVuYWx0eV8uICJkZiIgaXMgc2hvcnQgZm9yICJkZWdyZWVzIG9mIGZyZWVkb20iIGFuZCBpcyB0aGUgbnVtYmVyIG9mIHBhcmFtZXRlcnMgZXN0aW1hdGVkIGluIHRoZSBtb2RlbC4gVGhpcyBpbmNsdWRlcyB0aGUgcmVzaWR1YWwgc3RhbmRhcmQgZXJyb3IuIEZvciBleGFtcGxlLCBgbW9kXzAzYCBoYXMgNSBkZWdyZWVzIG9mIGZyZWVkb20gYmVjYXVzZSB0aGVyZSBhcmUgNCBtb2RlbCBjb2VmZmljaWVudHMgYW5kIHRoZSByZXNpZHVhbCBzdGFuZGFyZCBlcnJvci4gV2UgY2FsbCB0aGlzIHBhcnQgYSAicGVuYWx0eSIgYmVjYXVzZSBBSUMgY2FuIGdldCBiaWdnZXIgd2l0aCBtb3JlIGNvZWZmaWNpZW50cy4gCgpMb2ctbGlrZWxpaG9vZCBpcyB0aGUgbG9nLXRyYW5zZm9ybWVkIGxpa2VsaWhvb2QuIF9MaWtlbGlob29kXyBpcyB0aGUgam9pbnQgcHJvYmFiaWxpdHkgb2YgdGhlIG9ic2VydmVkIGRhdGEgYXMgYSBmdW5jdGlvbiBvZiB0aGUgcGFyYW1ldGVycyBvZiB0aGUgY2hvc2VuIHN0YXRpc3RpY2FsIG1vZGVsLiBJbWFnaW5lIHR1cm5pbmcgdGhlIGNvZWZmaWNpZW50cyBpbiB0aGUgbW9kZWwgbGlrZSBkaWFscyBvbiBhIG1hY2hpbmUgYW5kIHRyeWluZyB0byBmaW5kIHRoZSBtYXhpbXVtIGxpa2VsaWhvb2QuIEluIG90aGVyIHdvcmRzLCB3aGF0IGNvbWJpbmF0aW9uIG9mIGNvZWZmaWNpZW50cyBhcmUgbW9zdCBsaWtlbHkgdG8gcHJvZHVjZSB0aGUgZGF0YSB3ZSBvYnNlcnZlZD8gUiBkb2VzIG5vdCBlc3RpbWF0ZSBsaW5lYXIgbW9kZWwgY29lZmZpY2llbnRzIHVzaW5nIG1heGltdW0gbGlrZWxpaG9vZCwgYnV0IHdlIGNhbiBjYWxjdWxhdGUgdGhlIGxvZyBsaWtlbGlob29kIGFmdGVyIHRoZSBmYWN0IHVzaW5nIHRoZSBgbG9nTGlrKClgIGZ1bmN0aW9uOgoKYGBge3J9CmxvZ0xpayhtb2RfMDMpCmBgYAoKV2UgY2FuIHRoZW4gY2FsY3VsYXRlIEFJQyAiYnktaGFuZCIgYXMgZm9sbG93cy4KCmBgYHtyfQpyYmluZCgibW9kXzAyIiA9IC0yKmxvZ0xpayhtb2RfMDIpICsgMio0LCAKICAgICAgIm1vZF8wMyIgPSAtMipsb2dMaWsobW9kXzAzKSArIDIqNSkKYGBgCgpUaGUgQUlDIGlzIGluY2xpbmVkIHRvIGNob29zZSBvdmVybHkgY29tcGxleCBtb2RlbHMsIHNvIHNvbWUgcmVzZWFyY2hlcnMgcHJlZmVyIEJJQyAoQmF5ZXNpYW4gSW5mb3JtYXRpb24gQ3JpdGVyaW9uKSwgd2hpY2ggcGxhY2VzIGEgYmlnZ2VyIHBlbmFsdHkgb24gdGhlIG51bWJlciBvZiBwcmVkaWN0b3JzLiBBZ2FpbiB1c2UgdGhlIGBCSUMoKWAgZnVuY3Rpb24gaW4gdGhlIHNhbWUgd2F5LgoKYGBge3J9CkJJQyhtb2RfMDIsIG1vZF8wMykKYGBgCgpUaGUgQklDIGlzIGNhbGN1bGF0ZWQgdGhlIHNhbWUgYXMgdGhlIEFJQyBidXQgd2l0aCBhIGRpZmZlcmVudCBwZW5hbHR5LCBgbG9nKG4pYCwgd2hlcmUgbiBpcyB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucy4gQWdhaW4gd2UgY2FuIGNhbGN1bGF0ZSAiYnkgaGFuZCI6CgpgYGB7cn0KcmJpbmQoIm1vZF8wMiIgPSAtMipsb2dMaWsobW9kXzAyKSArIGxvZyhucm93KGhvbWVzKSkqNCwgCiAgICAgICJtb2RfMDMiID0gLTIqbG9nTGlrKG1vZF8wMykgKyBsb2cobnJvdyhob21lcykpKjUpCmBgYAoKT2YgY291cnNlLCB5b3UgZG9uJ3QgaGF2ZSB0byBjaG9vc2Ugb25lLiBZb3UgY2FuIHVzZSBib3RoIEFJQyBhbmQgQklDIGFuZCByZXBvcnQgYm90aC4gVGhleSB3aWxsIG9mdGVuIGNob29zZSB0aGUgc2FtZSBtb2RlbHMuIAoKCiMjIENvbGxpbmVhcml0eQoKV2hlbiBwcmVkaWN0b3JzIGFyZSBoaWdobHkgY29ycmVsYXRlZCAoaWUsIGhhdmUgc3Ryb25nIGxpbmVhciByZWxhdGlvbnNoaXBzKSwgdGhlIHByZWNpc2lvbiBvZiBjb2VmZmljaWVudCBlc3RpbWF0ZXMgY2FuIGRlY2xpbmUuIFRoaXMgcGhlbm9tZW5vbiBpcyBvZnRlbiByZWZlcnJlZCB0byBhcyBfY29sbGluZWFyaXR5XyBvciBfbXVsdGljb2xsaW5lYXJpdHlfLiBMZXQncyBkZW1vbnN0cmF0ZSB3aXRoIGEgdG95IGV4YW1wbGUuIEZpcnN0IHdlIGdlbmVyYXRlIHR3byB2YXJpYWJsZXMsIHgxIGFuZCB4MiwgdGhhdCBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQ6CgpgYGB7cn0KeDEgPC0gc2VxKDEsMTAsbGVuZ3RoID0gMTAwKQpzZXQuc2VlZCgxMjMpCngyIDwtIDEgKyAyKngxICsgcm5vcm0oMTAwLCBzZCA9IDAuMikKY29yKHgxLCB4MikgICMgY2FsY3VsYXRlIGNvcnJlbGF0aW9uOyBwZXJmZWN0IGNvcnJlbGF0aW9uID0gMQpgYGAKCk5vdyB3ZSBnZW5lcmF0ZSBhIG5ldyB2YXJpYWJsZSwgYHlgLCB1c2luZyBgeDFgIGFuZCBgeDJgIGFsb25nIHdpdGggc29tZSBub2lzZSBhbmQgZml0IGEgbGluZWFyIG1vZGVsIHRvIHJlY292ZXIgdGhlIHRydWUgY29lZmZpY2llbnRzIG9mIDIgYW5kIDMuIE5vdGljZSBpbiB0aGUgcGFpcndpc2Ugc2NhdHRlcnBsb3QgdGhhdCBgeDFgIGFuZCBgeDJgIGJvdGggc2VlbSBhc3NvY2lhdGVkIHdpdGggYHlgIGluIHRoZSBzYW1lIHdheS4KCmBgYHtyfQpzZXQuc2VlZCgzMjEpCnkgPC0gMC41ICsgMip4MSArIDMqeDIgKyBybm9ybSgxMDAsIHNkID0gMTApCmRfY29sbGluZWFyIDwtIGRhdGEuZnJhbWUoeSwgeDEsIHgyKQpwYWlycyhkX2NvbGxpbmVhcikKYGBgCgpXaGVuIHdlIGZpdCB0aGUgbW9kZWwsIG91ciBlc3RpbWF0ZWQgY29lZmZpY2llbnRzIGFyZSB3YXkgb2ZmIGFuZCBoYXZlIGVub3Jtb3VzIHN0YW5kYXJkIGVycm9ycy4gVGhlICJ0cnVlIiB2YWx1ZXMgYXJlIDIgYW5kIDMuIFRoZSBtb2RlbCBlc3RpbWF0ZXMgYXJlIGFib3V0IDEyIGFuZCAtMiEgUmVjYWxsIHdlIGludGVycHJldCB0aGUgYHgxYCBjb2VmZmljaWVudCBhcyBpdHMgZWZmZWN0IG9uIGB5YCBob2xkaW5nIGB4MmAgY29uc3RhbnQuIEJ1dCBzaW5jZSBgeDFgIGFuZCBgeDJgIGFyZSBoaWdobHkgY29ycmVsYXRlZCwgaXQncyBhbGwgYnV0IGltcG9zc2libGUgdG8gZXN0aW1hdGUgdGhlIGVmZmVjdCBvZiBgeDFgIHdoaWxlIGhvbGRpbmcgYHgyYCBjb25zdGFudC4KCmBgYHtyfQptb2RfY29saW5lYXIgPC0gbG0oeSB+IHgxICsgeDIpCnN1bW1hcnkobW9kX2NvbGluZWFyKQpgYGAKClRoZSBtb3N0IGNvbW1vbiB3YXkgdG8gY2hlY2sgZm9yIGFuZCBxdWFudGlmeSBjb2xsaW5lYXJpdHkgYWZ0ZXIgZml0dGluZyBhIG1vZGVsIGlzIGNhbGN1bGF0aW5nIF92YXJpYW5jZSBpbmZsYXRpb24gZmFjdG9yc18gKFZJRikuIFRoZSBkZXRhaWxzIG9mIHRoZSBjYWxjdWxhdGlvbiBjYW4gYmUgZm91bmQgd2l0aCBhIHdlYiBzZWFyY2gsIGJ1dCB0aGUgYmFzaWMgaWRlYSBpcyB0aGF0IGlmIG9uZSBvZiB0aGUgcmF3IFZJRnMgaXMgZ3JlYXRlciB0aGFuIDEwLCB0aGVuIHdlIG1heSBoYXZlIGV2aWRlbmNlIHRoYXQgY29sbGluZWFyaXR5IGlzIGluZmx1ZW5jaW5nIGNvZWZmaWNpZW50IGVzdGltYXRlcy4gRm9ydHVuYXRlbHkgdGhpcyBpcyBlYXN5IHRvIGRvIHVzaW5nIHRoZSBgdmlmKClgIGZ1bmN0aW9uIGluIHRoZSBjYXIgcGFja2FnZS4gVGhlIFZJRnMgYXJlIHNreSBoaWdoIGZvciBvdXIgY29udHJpdmVkIHByZWRpY3RvcnMhCgpgYGB7cn0KbGlicmFyeShjYXIpCnZpZihtb2RfY29saW5lYXIpCmBgYAoKVGhlIHNxdWFyZSByb290IG9mIHRoZSBWSUYgY2FuIGJlIGludGVycHJldGVkIGFzIGhvdyBtdWNoIGxhcmdlciB0aGUgc3RhbmRhcmQgZXJyb3Igb2YgdGhlIGNvZWZmaWNpZW50IGlzIHJlbGF0aXZlIHRvIHNpbWlsYXIgdW5jb3JyZWxhdGVkIGRhdGEuIAoKYGBge3J9CnZpZihtb2RfY29saW5lYXIpIHw+IHNxcnQoKQpgYGAKClRoaXMgc2F5cyB0aGUgc3RhbmRhcmQgZXJyb3IgZm9yIGJvdGggdmFyaWFibGVzIGlzIGFib3V0IDI5IHRpbWVzIGxhcmdlciB0aGFuIGl0IHdvdWxkIGhhdmUgYmVlbiB3aXRob3V0IGNvbGxpbmVhcml0eS4gVGhlIGJlc3Qgc29sdXRpb24gaW4gdGhpcyBjYXNlIHdvdWxkIGJlIHRvIHNpbXBseSBkcm9wIG9uZSBvZiB0aGUgdmFyaWFibGVzLiBJdCBhcHBlYXJzIGB4MWAgaXMgYWxtb3N0IGNvbXBsZXRlbHkgZGV0ZXJtaW5lZCBieSBgeDJgIGFuZCB2aWNlIHZlcnNhLiBLbm93aW5nIG9uZSBtZWFucyB5b3Uga25vdyB0aGUgb3RoZXIuIEluIHRoZSBleHRyZW1lIGNhc2UsIHdoZW4gdHdvIHZhcmlhYmxlcyBhcmUgcGVyZmVjdGx5IGNvcnJlbGF0ZWQsIHRoZSBtb2RlbCBmaXR0aW5nIHByb2NlZHVyZSB3aWxsIHJldHVybiBOQSBmb3Igb25lIG9mIHRoZSB2YXJpYWJsZXMsIGFzIGRlbW9uc3RyYXRlZCBiZWxvdy4gTm90aWNlIHRoZSBtZXNzYWdlOiAiKDEgbm90IGRlZmluZWQgYmVjYXVzZSBvZiBzaW5ndWxhcml0aWVzKSIKCmBgYHtyfQp4MiA8LSAyKngxICAjIHBlcmZlY3QgY29ycmVsYXRpb24Kc3VtbWFyeShsbSh5IH4geDEgKyB4MikpCmBgYAoKTGV0J3MgY2hlY2sgb3VyIGBtMmAgbW9kZWwgd2hlcmUgd2UgbW9kZWxlZCBgbG9nKHRvdGFscHJpY2UpYCBhcyBhIGZ1bmN0aW9uIG9mIGZpbnNxZnQsIGJlZHJvb20gYW5kIGxvdHNpemU6CgpgYGB7cn0KdmlmKG0yKQpgYGAKClRoZXNlIFZJRnMgbG9vayB2ZXJ5IGdvb2QuIFdlIG1pZ2h0IHN1c3BlY3QgdGhhdCBmaW5zcWZ0IGFuZCBudW1iZXIgb2YgYmVkcm9vbXMgY291bGQgYmUgaGlnaGx5IGNvcnJlbGF0ZWQsIGJ1dCB0aGUgVklGIGNoZWNrcyBvdXQuCgpPbmUgYXBwcm9hY2ggdG8gYWRkcmVzc2luZyBjb2xsaW5lYXJpdHkgY29uY2VybnMgaXMgdG8gdXNlIGEgZGF0YSByZWR1Y3Rpb24gdGVjaG5pcXVlIHN1Y2ggYXMgcHJpbmNpcGFsIGNvbXBvbmVudHMgYW5hbHlzaXMgKFBDQSkuIFdoZW4gaXQgd29ya3MsIHRoaXMgbWV0aG9kIGJhc2ljYWxseSB0YWtlcyBzZXZlcmFsIHZhcmlhYmxlcyBhbmQgcmVkdWNlcyB0aGVtIHRvIG9uZSBvciB0d28gc3VtbWFyeSBzY29yZXMuIFRoaXMgbWF5IGJlIHByZWZlcmFibGUgdG8gYXJiaXRyYXJpbHkgZHJvcHBpbmcgdmFyaWFibGVzLiAKCkZvciBtb2RlbHMgdGhhdCBhcmUgaW50ZW5kZWQgZm9yIG1ha2luZyBwcmVkaWN0aW9ucywgY29sbGluZWFyaXR5IGlzIG5vdCBtdWNoIG9mIGEgY29uY2Vybi4gCgoKIyMgVHJhbnNmb3JtYXRpb24gZ3VpZGVsaW5lcwoKQWJvdmUgd2UgbG9nLXRyYW5zZm9ybWVkIGB0b3RhbHZhbHVlYCB0byBoZWxwIG1lZXQgbW9kZWxpbmcgYXNzdW1wdGlvbnMuIFJlY2FsbCB3aXRob3V0IHRoZSBsb2cgdHJhbnNmb3JtYXRpb24gb3VyIHJlc2lkdWFscyB3ZXJlIGxhcmdlIGFuZCBza2V3ZWQsIHdoaWNoIGlzIGEgZmFuY3kgd2F5IG9mIHNheWluZyBvdXIgbW9kZWwgd2FzIGEgYmFkIGZpdC4gQSBnb29kIGZpdHRpbmcgbW9kZWwgc2hvdWxkIGhhdmUgcmVsYXRpdmVseSBzbWFsbCByZXNpZHVhbHMgd2l0aCBldmVuIHNjYXR0ZXIuIAoKQSBsb2ctdHJhbnNmb3JtYXRpb24gbWFkZSBzZW5zZSBmb3IgdHdvIHJlYXNvbnM6CgoxLiBgdG90YWx2YWx1ZWAgd2FzIHN0cmljdGx5IHBvc2l0aXZlLCBoYWQgYSBsYXJnZSB1cHBlciBib3VuZCwgYW5kIGNvdmVyZWQgc2V2ZXJhbCBvcmRlcnMgb2YgbWFnbml0dWRlLiAKMi4gY2hhbmdlcyBpbiBgdG90YWx2YWx1ZWAgYWNjb3JkaW5nIHRvIHRoZSBwcmVkaWN0b3JzIHdlcmUgcmVsYXRpdmUgKG11bHRpcGxpY2F0aXZlKSBhbmQgbm90IGFic29sdXRlIChhZGRpdGl2ZSksIHdoaWNoIGNvcnJlc3BvbmRzIHRvIHRoZSBuYXR1cmFsIGxvZyBzY2FsZS4KCkl0J3MgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCBub3QgYWxsIHNrZXdlZCB2YXJpYWJsZXMgbmVlZCB0byBiZSB0cmFuc2Zvcm1lZCB3aGVuIGl0IGNvbWVzIHRvIGxpbmVhciBtb2RlbGluZy4gVGhlIGRpc3RyaWJ1dGlvbmFsIGFzc3VtcHRpb25zIGFyZSBvbiB0aGUgcmVzaWR1YWxzLiBIb3dldmVyIHRoZXJlIG1heSBiZSB0aW1lcyB3aGVuIHlvdSBuZWVkIHRvIGludmVzdGlnYXRlIHRyYW5zZm9ybWF0aW9ucyBvdGhlciB0aGFuIHRoZSBsb2cgd2hlbiBpdCBjb21lcyB0byBtb2RlbGluZy4gVGhlc2UgYWxtb3N0IGFsd2F5cyB0YWtlIHRoZSBmb3JtIG9mIGEgX3Bvd2VyIHRyYW5zZm9ybWF0aW9uXyAoaWUsIHJhaXNpbmcgeW91ciB2YXJpYWJsZSB0byBhIHBvd2VyIHVzaW5nIGFuIGV4cG9uZW50KS4gUG93ZXJzIGFyZSB1c3VhbGx5IHN5bWJvbGl6ZWQgd2l0aCBhIEdyZWVrIGxhbWJkYSAozrspLiBBIHBvd2VyIG9mIDAgdHJhbnNsYXRlcyB0byBhIGxvZyB0cmFuc2Zvcm1hdGlvbi4KClNheSB5b3VyIHZhcmlhYmxlIGlzIGB5YC4gQSBiYXNpYyBwYWxldHRlIG9mIHBvc3NpYmxlIHBvd2VyIHRyYW5zZm9ybWF0aW9ucyBpbmNsdWRlOgoKLSDOuyA9IC0xICAgICAxL3kKLSDOuyA9IC0wLjUgICAxL3NxcnQoeSkKLSDOuyA9IDAgICAgICBsb2coeSkKLSDOuyA9IDAuNSAgICBzcXJ0KHkpCi0gzrsgPSAxICAgICAgeSAobm8gdHJhbnNmb3JtYXRpb24pCi0gzrsgPSAyICAgICAgeV4yCgpUaGUgY2FyIGZ1bmN0aW9uIGBzeW1ib3goKWAgY3JlYXRlcyBhIHZpc3VhbCBhc3Nlc3NtZW50IG9mIHdoaWNoIHBvd2VyIG1ha2VzIHRoZSBkaXN0cmlidXRpb24gcmVhc29uYWJseSBzeW1tZXRyaWMuIEJlbG93IHdoZW4gd2UgdXNlIGl0IHdpdGggYHRvdGFsdmFsdWVgIHdlIHNlZSB0aGF0IHRoZSBsb2cgdHJhbnNmb3JtYXRpb24gKM67ID0gMCkgZG9lcyB0aGUgYmVzdCBqb2Igb2YgbWFraW5nIHRoZSBkaXN0cmlidXRpb24gbW9yZSBzeW1tZXRyaWMuIAoKYGBge3J9CnN5bWJveChob21lcyR0b3RhbHZhbHVlKQpgYGAKCldlIGNhbiBhbHNvIHVzZSBgc3ltYm94KClgIG9uIGEgbW9kZWwgb2JqZWN0LiBGb3IgZXhhbXBsZSwgdGhpcyBwcm9kdWNlcyBlc3NlbnRpYWxseSB0aGUgc2FtZSBwbG90IHVzaW5nIHRoZSByZXNpZHVhbHMgb2YgdGhlIG1vZGVsIGluc3RlYWQgb2YgYHRvdGFsdmFsdWVgLiBTaW1wbHkgcGlwZSB0aGUgbW9kZWwgaW50byBgc3ltYm94KClgLgoKYGBge3J9CmxtKHRvdGFsdmFsdWUgfiBmaW5zcWZ0ICsgYmVkcm9vbSArIGxvdHNpemUsIGRhdGEgPSBob21lcykgfD4KICBzeW1ib3goKQpgYGAKCkEgc3RhdGlzdGljYWwgInNlYXJjaCIgZm9yIHRoZSAiYmVzdCIgcG93ZXIgdHJhbnNmb3JtYXRpb24gY2FuIGJlIHBlcmZvcm1lZCB3aXRoIHRoZSBgcG93ZXJUcmFuc2Zvcm0oKWAgZnVuY3Rpb24sIGFsc28gaW4gdGhlIGNhciBwYWNrYWdlLiBUaGUgdXN1YWwgcHJhY3RpY2UgaXMgdG8gY29udmVydCB0aGUgcmVzdWx0IHRvIHRoZSBjbG9zZXN0IHNpbXBsZSBwb3dlciBsaXN0ZWQgYWJvdmUuIEZvciBleGFtcGxlLCB3ZSBjYW4gcGlwZSB0aGUgbW9kZWwgcmVzdWx0IGludG8gYHBvd2VyVHJhbnNmb3JtKClgIGFuZCBzZWUgdGhlICJiZXN0IiB0cmFuc2Zvcm1hdGlvbiBpcyBhYm91dCAwLjE2LgoKYGBge3J9CmxtKHRvdGFsdmFsdWUgfiBmaW5zcWZ0ICsgYmVkcm9vbSArIGxvdHNpemUsIGRhdGEgPSBob21lcykgfD4KICBwb3dlclRyYW5zZm9ybSgpIApgYGAKCjAuMTYgaXMgY2xvc2UgdG8gMCwgc28gaXQgbWFrZXMgc2Vuc2UgdG8gcHJvY2VlZCB3aXRoIGEgbG9nIHRyYW5zZm9ybWF0aW9uLiBUaGF0IGdyZWF0bHkgc2ltcGxpZmllcyBpbnRlcnByZXRhdGlvbi4KCgojIyBnZ3Bsb3QyIGNvZGUgZm9yIGNyZWF0aW5nIHBsb3Qgb2Ygc2ltdWxhdGVkIGRhdGEKCkhlcmUncyBob3cgdG8gbWFrZSB0aGUgc2ltdWxhdGlvbiBwbG90IHVzaW5nIGdncGxvdDIuIEkgZmluZCBiYXNlIFIgZ3JhcGhpY3MgZWFzaWVyIGZvciB0aGlzIHR5cGUgb2YgcGxvdC4gCgpgYGB7cn0Kc2ltMiA8LSBzaW11bGF0ZShtMiwgbnNpbSA9IDUwKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkodGlkeXIpCnNpbTIgJT4lIAogIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIAogICAgICAgICAgICAgICBuYW1lc190byA9ICJzaW11bGF0aW9uIiwgCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ0b3RhbHZhbHVlIikgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2RlbnNpdHkobWFwcGluZyA9IGFlcyh4ID0gdG90YWx2YWx1ZSwgZ3JvdXAgPSBzaW11bGF0aW9uKSwKICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JleTgwIikgKyAKICBnZW9tX2RlbnNpdHkobWFwcGluZyA9IGFlcyh4ID0gbG9nKHRvdGFsdmFsdWUpKSwKICAgICAgICAgICAgICAgZGF0YSA9IGhvbWVzKSAgCgpgYGAKCgoKIyMgSG93IHRvIG1ha2UgYSBmdW5jdGlvbiBmb3Igc2ltdWxhdGluZyBhbmQgcGxvdHRpbmcgZGF0YSBmb3IgYSBsaW5lYXIgbW9kZWwKCioqYmFzZSBSKioKCldlIGJhc2ljYWxseSBjb3B5IGFuZCBwYXN0ZSB0aGUgb3JpZ2luYWwgY29kZSBpbiBiZXR3ZWVuIHRoZSBjdXJseSBicmFjZXMgb2YgdGhlIGBmdW5jdGlvbigpYCBmdW5jdGlvbi4gV2UgY2FsbCB0aGUgZnVuY3Rpb24gYHBsb3Rfc2ltc2AgYnV0IHlvdSBjYW4gbmFtZSBpdCB3aGF0ZXZlciB5b3UgbGlrZS4gVGhlIGNoYW5nZXMgYXJlIHRvIHRoZSBtb2RlbCBuYW1lIGFuZCBudW1iZXIgb2Ygc2ltdWxhdGlvbnMuIFdlIGdlbmVyYWxpemUgdGhvc2Ugd2l0aCBhcmd1bWVudHM6IGBtb2RgIGFuZCBgbnNpbWAuIFdoZW4gd2UgZml0IGEgbW9kZWwgd2l0aCBgbG1gLCB0aGUgZGF0YSBpcyBzdG9yZWQgd2l0aCB0aGUgbW9kZWwgb2JqZWN0IGJ5IGRlZmF1bHQgYW5kIGNhbiBiZSBhY2Nlc3NlZCBhcyBtb2QkbW9kZWwuIFRoZSBmaXJzdCBjb2x1bW4gY29udGFpbnMgdGhlIG1vZGVsIHJlc3BvbnNlLCBzbyB3ZSBjYW4gYWNjZXNzIGl0IHdpdGggYFssMV1gIGFuZCB1c2UgaXQgdG8gZHJhdyB0aGUgZGVuc2l0eSBvZiB0aGUgb2JzZXJ2ZWQgZGF0YS4KCgpgYGB7cn0KcGxvdF9zaW1zIDwtIGZ1bmN0aW9uKG1vZCwgbnNpbSl7CiAgc2ltIDwtIHNpbXVsYXRlKG1vZCwgbnNpbSA9IG5zaW0pCiAgcGxvdChkZW5zaXR5KG1vZCRtb2RlbFssMV0pKQogIGZvcihpIGluIDE6bnNpbSlsaW5lcyhkZW5zaXR5KHNpbVtbaV1dKSwgY29sID0gImdyZXk4MCIpCn0KIyB0cnkgdGhlIGZ1bmN0aW9uCnBsb3Rfc2ltcyhtb2QgPSBtMiwgbnNpbSA9IDIwKQpgYGAKCioqZ2dwbG90MioqCgpTYW1lIGlkZWEgYXMgdGhlIHByZXZpb3VzIGZ1bmN0aW9uOiB3ZSB3YW50IHRvIGNvcHkgdGhlIG9yaWdpbmFsIGNvZGUgaW4gYmV0d2VlbiB0aGUgY3VybHkgYnJhY2VzIG9mIHRoZSBgZnVuY3Rpb24oKWAgZnVuY3Rpb24uIEV4Y2VwdCB3ZSBub3cgd2FudCB0byBwcmVmYWNlIGZ1bmN0aW9ucyB3aXRoIHRoZWlyIHBhY2thZ2UgbmFtZSAoZWcsIGdncGxvdDI6Oikgc28gd2UgY2FuIHVzZSB0aGUgZnVuY3Rpb24gd2l0aG91dCBoYXZpbmcgdGhlIHBhY2thZ2VzIGxvYWRlZC4gV2UgYWxzbyBkbyBhd2F5IHdpdGggdGhlIHBpcGUgYCU+JWAgc2luY2UgaXQgY29tZXMgZnJvbSB5ZXQgYW5vdGhlciBwYWNrYWdlIChtYWdyaXR0cikgYnV0IGlzIGFjY2Vzc2libGUgd2hlbiB0aWR5ciBpcyBsb2FkZWQuIFdlIGV4dHJhY3QgdGhlIG5hbWUgb2YgdGhlIHJlc3BvbnNlIHVzaW5nIGByZXNwIDwtIG5hbWVzKG1vZCRtb2RlbClbMV1gIGFuZCB0aGVuIHVzZSB0aGUgdXNlIHRoZSBgLmRhdGFgIHByb25vdW4gKGAuZGF0YVtbcmVzcF1dYCkgZnJvbSBybGFuZyBwYWNrYWdlIHRvIHVzZSBpdCB3aXRoIGdncGxvdC4KCgpgYGB7cn0KcGxvdF9zaW1zIDwtIGZ1bmN0aW9uKG1vZCwgbnNpbSl7CiAgc2ltMSA8LSBzaW11bGF0ZShtb2QsIG5zaW0gPSBuc2ltKQogIHJlc3AgPC0gbmFtZXMobW9kJG1vZGVsKVsxXQogIHNpbTEgPC0gdGlkeXI6OnBpdm90X2xvbmdlcihzaW0xLCBldmVyeXRoaW5nKCksIAogICAgICAgICAgICAgICBuYW1lc190byA9ICJzaW11bGF0aW9uIiwgCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ0b3RhbHZhbHVlIikgCiAgZ2dwbG90Mjo6Z2dwbG90KCkgKwogIGdncGxvdDI6Omdlb21fZGVuc2l0eShtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSB0b3RhbHZhbHVlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IHNpbXVsYXRpb24pLCAKICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JleTgwIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBzaW0xKSArIAogIGdncGxvdDI6Omdlb21fZGVuc2l0eShtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbcmVzcF1dKSwKICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IG1vZCRtb2RlbCkgIAp9CgpwbG90X3NpbXMobTIsIG5zaW0gPSA2NSkKCmBgYAoKKipiYXllc3Bsb3QgcGFja2FnZSoqCgpUaGUgYmF5ZXNwbG90IHBhY2thZ2UgaGFzIGEgZnVuY3Rpb24gZm9yIHRoaXMgY2FsbGVkIGBwcGNfZGVuc19vdmVybGF5YCwgYnV0IGl0J3MgYSBsaXR0bGUgd2VpcmQgdG8gdXNlIGZvciBsaW5lYXIgbW9kZWxzIGJlY2F1c2UgaXQgd2FzIGRlc2lnbmVkIHRvIGJlIHVzZWQgd2l0aCBCYXllc2lhbiBtb2RlbHMuIEhvd2V2ZXIgaXQncyBub3QgdGhhdCBoYXJkIHRvIGRlcGxveS4gVGhlIGZpcnN0IGFyZ3VtZW50IGlzIHNpbXBseSB0aGUgb2JzZXJ2ZWQgZGF0YS4gVGhlIHNlY29uZCBhcmd1bWVudCBleHBlY3RzIHRoZSBzaW11bGF0aW9ucyBwZXIgcm93IGFzIG9wcG9zZWQgdG8gcGVyIGNvbHVtbi4gU28gd2UgbmVlZCB0byB0cmFuc3Bvc2UsIHdoaWNoIHdlIGNhbiBkbyB3aXRoIHRoZSBgdCgpYCBmdW5jdGlvbi4gVGhlIHJlc3VsdCBpcyBhIGNsZWFuIHBsb3Qgd2l0aCB0aGUgeS1heGlzIHVubGFiZWxlZCBzaW5jZSBpdCByZWFsbHkgaXNuJ3QgbmVlZGVkIGFuZCBhIGxlZ2VuZCB0byBkaXN0aW5ndWlzaCBiZXR3ZWVuIG9ic2VydmVkIGFuZCBzaW11bGF0ZWQgKG9yIHJlcGxpY2F0ZWQpIGRhdGEuIAoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygiYmF5ZXNwbG90IikKbGlicmFyeShiYXllc3Bsb3QpCnBwY19kZW5zX292ZXJsYXkobG9nKGhvbWVzJHRvdGFsdmFsdWUpLCB0KHNpbTIpKQpgYGAKCgoqKnBlcmZvcm1hbmNlIHBhY2thZ2UqKgoKVGhlIGVhc3lzdGF0cyBjb2xsZWN0aW9uIG9mIHBhY2thZ2VzICJhaW1zIHRvIHByb3ZpZGUgYSB1bmlmeWluZyBhbmQgY29uc2lzdGVudCBmcmFtZXdvcmsgdG8gdGFtZSwgZGlzY2lwbGluZSwgYW5kIGhhcm5lc3MgdGhlIHNjYXJ5IFIgc3RhdGlzdGljcyBhbmQgdGhlaXIgcGVza3kgbW9kZWxzLiIgaHR0cHM6Ly9lYXN5c3RhdHMuZ2l0aHViLmlvL2Vhc3lzdGF0cy8gT25lIG9mIHRoZSBwYWNrYWdlcyBpcyBjYWxsZWQge3BlcmZvcm1hbmNlfSwgd2hpY2ggcHJvdmlkZXMgdGhlIGBjaGVja19wcmVkaWN0aW9ucygpYCBmdW5jdGlvbiBmb3Igc2ltdWxhdGluZyBkYXRhIGZyb20gYSBtb2RlbCBhbmQgY29tcGFyaW5nIGl0IHRvIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIG9yaWdpbmFsIGRhdGEuIFR3byBwb3RlbnRpYWwgZHJhd2JhY2tzIGF0IHRoZSB0aW1lIG9mIHRoaXMgd3JpdGluZzoKCjEuIENhbiBiZSBzbG93IGZvciBsYXJnZSBkYXRhIHNldHMKMi4gbG9nIHRyYW5zZm9ybWVkIGRhdGEgbmVlZHMgdG8gYmUgYWRkZWQgYXMgYSB2YXJpYWJsZSB0byB0aGUgZGF0YSBhbmQgbW9kZWxlZCBkaXJlY3RseSwgYXMgb3Bwb3NpbmcgdG8gZG9pbmcgaXQgb24tdGhlLWZseSBhcyBgbG9nKHRvdGFsdmFsdWUpYAoKUXVpY2sgZGVtby4gTm90ZSB0aGUgd2FybmluZzogIk1pbmltdW0gdmFsdWUgb2Ygb3JpZ2luYWwgZGF0YSBpcyBub3QgaW5jbHVkZWQgaW4gdGhlIHJlcGxpY2F0ZWQgZGF0YS4iIFRoaXMgc2F5cyB0aGUgbW9kZWwgaXMgbm90IGdlbmVyYXRpbmcgZGF0YSBhcyBzbWFsbCBhcyB0aGUgc21hbGxlc3QgdmFsdWUgaW4gdGhlIG9yaWdpbmFsIGRhdGEsIHdoaWNoIGlzIDk2MDAuCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCJwZXJmb3JtYW5jZSIpCiMgaW5zdGFsbC5wYWNrYWdlcygic2VlIikKbGlicmFyeShwZXJmb3JtYW5jZSkKCiMgYWRkIGxvZyh0b3RhbHZhbHVlKSB0byBkYXRhIGFuZCBmaXQgbmV3IG1vZGVsCmhvbWVzJGxvZ3RvdGFsdmFsdWUgPC0gbG9nKGhvbWVzJHRvdGFsdmFsdWUpCm0yYSA8LSBsbShsb2d0b3RhbHZhbHVlIH4gZmluc3FmdCArIGJlZHJvb20gKyBsb3RzaXplLCBkYXRhID0gaG9tZXMpCmNoZWNrX3ByZWRpY3Rpb25zKG0yYSkKYGBgCgoK