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)
- 10 is the intercept.
- 5 is the slope.
- y is completely determined by x (y = 10 + 5*x)
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:
- fixed:
10 + 5*x
- 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:
- ___ + ___*x
- 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):
- the formula is a weighted sum of predictors (eg, y = 10 +
5*x)
- the noise is a random draw from a Normal distribution with mean =
0
- 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:
- y = 11.135 + 5.042 * x
- 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:
- propose and fit a model
- determine if the model is good and that assumptions are mostly
met
- 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).
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?
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:
- yearbuilt: year house was built
- finsqft: size of house in number square feet
- cooling: ‘Central Air’ versus ‘No Central Air’
- bedroom: number of bedrooms
- fullbath: number of full bathrooms (toilet, sink and
bath)
- halfbath: number of half bathrooms (toilet and sink
only)
- lotsize: size of land on which home is located, in
acres
- totalvalue: total assessed value of home and property
- esdistrict: the elementary school the home feeds into
- msdistrict: the middle school the home feeds into
- hsdistrict: the high school the home feeds into
- censustract: the census tract the home is located in
- age: of the house in years as of 2018
- condition: assessed condition of home (Substandard, Poor,
Fair, Average, Good, Excellent)
- fp: indicator if house has fireplace (0=no, 1=yes)
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 additional finished square foot adds about $284 to price
- each additional bedroom drops the price by $13,218 (?)
- each additional lot size acre adds about $4268 to price
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:
- totalvalue can be modeled by a weighted sum: totalvalue = Intercept
+ finsqft + bedrooms + lotsize
- noise/error is from Normal dist’n with mean 0
- 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:
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
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.
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)
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:
- each additional finished square foot increases expected mean price
by 0.05%. Or multiply by 100 to say each additional 100 finished square
feet increases expected mean price by 5%.
- each additional bedroom increases expected mean price by about
4.3%
- each additional lot size acre increases expected mean price by about
0.47%
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
- Residuals section: quick assessment of residuals. Ideally 1Q/3Q and
Min/Max will be roughly equivalent in absolute value.
- Coefficients: lists the estimated coefficients along with hypothesis
tests for the null that each coefficient is 0. Est/SE = t-value.
- Residual standard error: estimate of the constant standard deviation
of the normal dist’n of the residuals (noise)
- degrees of freedom: sample size - number of coefficients (3025 -
4)
- R-squared: proportion of variance explained
- F-statistic: overall test that all coefficients (except intercept)
are 0.
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
Insert a code chunk below and model log(totalvalue)
as function of fullbath and finsqft. Call your
model m3
Insert a code chunk below and check the diagnostic plots
How do we interpret the fullbath coefficient?
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
- a home in Albemarle HS district gets two zeroes
- a home in Monticello HS district gets a one in the Monticello
column
- a home in Western Albemarle HS district gets a one in the West Alb
column
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
Insert a code chunk below and model log(totalvalue)
as function of fullbath, finsqft and
cooling. Call your model m5.
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…
- the first test compares a model with just an intercept to a model
with an intercept and fullbath.
- the second test compares a model with an intercept and fullbath to a
model with an intercept, fullbath and finsqft.
- And so on.
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
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?
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
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.
Use the anova function to check whether the
interaction appears necessary. What do you think?
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:
- generalized linear models (for binary and count responses)
- multinomial logit models (for categorical responses)
- ordered logit models (for ordered categorical responses)
- mixed-effect or longitudinal linear models (for responses with
multiple measures on the same subject or clusters of related
measures)
- survival models (for responses that measure time to an event)
- time series models (for responses that exhibit, say, seasonal
variation over time)
References
- Faraway, J. (2005). Linear Models in R. London: Chapman
& Hall.
- Fox, J. (2002). A R and S-Plus Companion to Applied
Regression. London: Sage.
- Harrell, F. (2015). Regression Modeling Strategies (2nd
ed.). New York: Springer.
- Kutner, M., et al. (2005). Applied Linear Statistical
Models (5th ed.). New York: McGraw-Hill.
- Maindonald J., Braun, J.W. (2010). Data Analysis and Graphics
Using R (3rd ed.). Cambridge: Cambridge Univ Press.
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:
- Model 2 vs Model 1
- Model 3 vs Model 2
- 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:
- Full model vs Full model without finsqft
- Full model vs Full model without bedroom
- 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:
totalvalue was strictly positive, had a large upper
bound, and covered several orders of magnitude.
- 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:
- λ = -1 1/y
- λ = -0.5 1/sqrt(y)
- λ = 0 log(y)
- λ = 0.5 sqrt(y)
- λ = 1 y (no transformation)
- λ = 2 y^2
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:
- Can be slow for large data sets
- 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