library(tidyverse)
library(broom)
library(directlabels)
# For non-Bayesian multilevel modeling
#library(lme4)
library(lmerTest) # use lmerTest, which is like lme4, but also provides p-values for fixed effects (albeit a somewhat controversial approach...)
# For Bayesian multilevel modeling
library(rstan)
options(mc.cores = parallel::detectCores()) # Allows rstan to run on multiple cores
rstan_options(auto_write = TRUE)
library(rethinking)
# Packages for timing
library(beepr)
library(tictoc)
Now that we’re ready to launch into multilevel modeling, let’s revisit the concept of “pooling”. In this example, we’ve been trying to estimate an Minnesota public employees’s hourly compensation rates. We can illustrate pooling at play by performing a simple thought experiment with this particular modeling task in mind. Let’s say, for example, that we want to throw an “employee fun day” for Minnesota state employees. (Yay–they deserve it!) While everyone’s out having fun, we want to use that opportunity to get a “lay of the land” about how employees are compensated. The goal is to arrange our employees in a way that makes it easy to see/understand meaningful patterns in their compensation rates. (Note: In this example, we’re pretending we don’t have direct access to HR records about compensation rates–we need to deduce the trends from our observations during the “employee fun day”.)
One approach would be to invite everyone to one giant pool party, where everyone’s forced to show up and swim together. To accommodate this, you’re going to need a pretty large pool, and you’re going to throw your IT professionals right next to your corrections officers, and your nurses in next to your court clerks. With this approach, you can still get a general sense of the overall population trends, but this makes it hard to sort out the nuanced distinctions between these groups’ compensation levels. This concept is called complete pooling.
Another approach would be to go to a different extreme: instead of throwing a pool party, we could throw a bunch of separate office parties. This concept is called no pooling. At each of these parties, everyone will celebrate only with other members of their same job class, and no one is allowed to mingle with anyone outside of their immediate group. This would make the distinctions between groups very clear. Unfortunately, it would be both cumbersome and potentially misleading to try to deduce broader population trends from all of these separate office parties. For example, the state has only one “Judicial Mail Clerk”, one “Music Therapist”, and one “Seed Potato Specialist”. It would be tricky to try to visit each and every one of these individuals at their one-off office parties! Beyond that, you could run into idiosyncratic situations that could skew your perspective. For example, you might discover that Minnesota’s seed potato specialist just happens to be the world’s leading expert on seed potatoes, and is compensated accordingly. This is great news for Minnesota! But you wouldn’t want to let this outlier be the only input into your compensation model for Minnesota seed potato specialists into the future. Because, when the current expert retires and the next seed potato specialist is hired, they aren’t likely to be nearly as qualified–or as well compensated.
The final approach–the partial pooling approach–is like celebrating our employee fun day at a waterpark. At a waterpark, there’s plenty of room to spread out and cluster. You’ll find that employees naturally tend to mingle with their officemates across different areas of the waterpark. If you were to walk around to the different employee clusters, you could easily take a quick “straw poll” in each group and get a sense of how much their compensation rates vary within the cluster. And after you have sampled several clusters, you could start to get a good feel for how compensation rates vary across clusters. By the end, you’d have a pretty reasonable estimate of the compensation trends–both for the population as a whole, and for each cluster.
Multilevel modeling is like a day at the waterpark. (See: McElreath, p. 408 for a more complete discussion of “pooling” concepts.) Let’s dive in!
Run a (non-Bayesian) varying intercepts model (lme4)
Let’s start from the key assumption that much of the difference in compensation rates likely has to do with which job each employee is performing. We certainly don’t expect each job to have the same starting salary (intercept), so let’s account for that by fitting a multilevel model where the intercept is allowed to vary based on job class.
When running a varying intercepts model using the lme4 package, you’ll use model formula syntax that is relatively similar to what you’re probably already used to from other R packages. Looking at the formula below, you’ll see that the one difference is the (1|JOB_FACTOR) component of the model. The 1| indicates that we want to add a varying intercept, with |JOB_FACTOR as the grouping across which we want to allow the intercepts to vary.
model2_lmer <- lmer(COMP_RATE_STND_HOURLY ~ YRS_SINCE_ORIGINAL_HIRE + (1|JOB_FACTOR), # model formula
data = hr_2019_top_25_job_classes,
REML = FALSE) # set REML = FALSE to use log-likelihood optimization and get AIC/deviance values in output
summary(model2_lmer)
Linear mixed model fit by maximum likelihood . t-tests use Satterthwaite's method ['lmerModLmerTest']
Formula: COMP_RATE_STND_HOURLY ~ YRS_SINCE_ORIGINAL_HIRE + (1 | JOB_FACTOR)
Data: hr_2019_top_25_job_classes
AIC BIC logLik deviance df.resid
81556.7 81587.6 -40774.3 81548.7 16711
Scaled residuals:
Min 1Q Median 3Q Max
-11.4715 -0.5367 0.0417 0.6118 6.9646
Random effects:
Groups Name Variance Std.Dev.
JOB_FACTOR (Intercept) 88.820 9.424
Residual 7.596 2.756
Number of obs: 16715, groups: JOB_FACTOR, 25
Fixed effects:
Estimate Std. Error df t value Pr(>|t|)
(Intercept) 27.397727 1.885208 25.009188 14.53 0.000000000000106 ***
YRS_SINCE_ORIGINAL_HIRE 0.167208 0.002196 16690.616574 76.14 < 0.0000000000000002 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Correlation of Fixed Effects:
(Intr)
YRS_SINCE_O -0.014
The model summary separates the explained variance into two concepts: fixed effects vs. random effects. Looking first at the “fixed effects”, we notice that the estimate for the intercept is ~$27, representing the population mean hourly compensation rate. When we look at the number of years since original hire (YRS_SINCE_ORIGINAL_HIRE), the estimate for the slope of is 0.167. This indicates that the population average increase in hourly compensation rate for each additional year worked at the state is ~16-17 cents per year. Now, looking at the “random effects”, we see that the job class (JOB_FACTOR) accounts for a lot of the additional variance in hourly compensation rates. There is also still some residual variance left over (~7.59) that is not explained by the model.
And what exactly does this look like? Here’s a visual to try to illustrate what it’s doing. The slope of the relationship between years worked and hourly compensation rate is still fixed to be the same across all job classes, but now the different job classes each have their own separate intercepts:
rand <- as.data.frame(ranef(model2_lmer))
coeff_df <- rand %>%
mutate(
pop_intercept = fixef(model2_lmer)['(Intercept)'],
pop_slope = fixef(model2_lmer)['YRS_SINCE_ORIGINAL_HIRE']
)
x <- as.data.frame(seq(0, 30, 1)) # set up x axis data to visualize 30 years
names(x) <- "YRS_SINCE_ORIGINAL_HIRE"
viz_df <- crossing(coeff_df, x)
viz_df <- viz_df %>%
mutate(
middle = pop_intercept + condval + YRS_SINCE_ORIGINAL_HIRE * pop_slope,
lower_bound = pop_intercept + condval + YRS_SINCE_ORIGINAL_HIRE * pop_slope + condsd * 2, # 95% CI
upper_bound = pop_intercept + condval + YRS_SINCE_ORIGINAL_HIRE * pop_slope - condsd * 2
)
ggplot(viz_df, aes(x=YRS_SINCE_ORIGINAL_HIRE, y=middle, col=grp, label=grp)) +
geom_smooth(aes(ymin = lower_bound, ymax = upper_bound), stat = "identity") +
geom_dl(aes(label=grp, color=grp), method = list("last.points")) +
xlim(0, 50) +
ylim(0, 60) +
theme(legend.position = "none")

Compare varying intercepts approach to running a glm with a factor variable
If you’re like me, you may be saying to yourself at this point: “No big deal! This doesn’t really seem any different from simply running a basic linear model and adding the job class as a factor variable.” But as it turns out, our multilevel model is a little different. Because the varying intercepts model we fit above is a “waterpark party”. (Remember our ‘pooling’ metaphor?) And fitting a glm model with job class as a factor variable is like throwing an “office party”.
To help illustrate this difference, we’re going to need to expand the dataset a bit. In addition to the “top 25” job classes we’ve been looking at so far, we’re going to add an additional set of job classes that are much smaller. Each of these job classes contain fewer than 5 employees, and we will include them in order to be able to illustrate the concept of “shrinkage”:
# Add some additional, smaller job classes for illustration purposes later on
top_25_plus_some_smaller_job_classes <- c(top_25_job_classes, "Public Defense Data Entry Cl", "Jud Library Asst I", "Service Worker", "Peer Specialist", "Pollution Cont Technician", "Asst Dir Mn State Lottery", "Asst Commr Pollution Control", "Epidemiologist Program Manager", "Governor")
hr_2019_top_25_job_classes_plus <- hr_2019 %>%
filter(JOB_TITLE %in% top_25_plus_some_smaller_job_classes)
hr_2019_top_25_job_classes_plus$JOB_FACTOR <- relevel(as.factor(hr_2019_top_25_job_classes_plus$JOB_TITLE), ref="State Patrol Trooper")
Now, let’s run two models on this expanded dataset–one multilevel model using lmer and one linear model using glm:
model2_lmer_top25plus <- lmer(COMP_RATE_STND_HOURLY ~ YRS_SINCE_ORIGINAL_HIRE + (1|JOB_FACTOR), data = hr_2019_top_25_job_classes_plus, REML = FALSE)
model2_glm_top25plus <- glm(COMP_RATE_STND_HOURLY ~ YRS_SINCE_ORIGINAL_HIRE + JOB_FACTOR, data = hr_2019_top_25_job_classes_plus, family = "gaussian")
Now, we can do a little work to visualize the coefficients from each model in a side-by-side comparison. You’ll notice that, for the 25 larger job classes we examined, there doesn’t seem to be much difference in the coefficient estimates. Where things start to matter is for the smaller job classes we included in the model. For these job classes, where we had relatively fewer individuals in each class, the varying intercepts model is shrinking their coefficient estimates towards the middle. It’s using information from the “grand” population mean to adjust our coefficient estimates for these smaller groups to make them less extreme! This phenomenon of shrinking parameter estimates towards the population mean is a built-in benefit of multilevel modeling. The model is performing a delicate balancing act between overall population trends and the variance within and across groups. For smaller groups, the “grand mean” of the population plays a bigger role in the final coefficient estimates than it does for larger groups.
# Extract the group coefficients from the varying intercepts model
group_coeffs_lmer <- as.data.frame(ranef(model2_lmer_top25plus)) %>%
rename(lmer_coeff = condval)
# Extract and clean the group coefficients from the glm model
group_coeffs_glm <- as.data.frame(model2_glm_top25plus$coefficients)
names(group_coeffs_glm) <- "Coefficient"
group_coeffs_glm <- group_coeffs_glm %>%
rownames_to_column("grp") %>%
filter(grp != c('(Intercept)', 'YRS_SINCE_ORIGINAL_HIRE')) %>%
mutate(grp = gsub("JOB_FACTOR", "", grp))
group_coeffs_glm <- rbind(group_coeffs_glm, list("State Patrol Trooper", 0.0))
group_coeffs_glm <- group_coeffs_glm %>%
mutate(glm_coeff = Coefficient - mean(Coefficient)) # center the coefficients from the glm at zero (instead of around "State Patrol Trooper"), so we can compare them to the how the random effects coefficients (which are zero-centered by default)
inner_join(group_coeffs_lmer, group_coeffs_glm, by=c("grp")) %>%
ggplot(., aes(y=reorder(grp, lmer_coeff), lmer_coeff = lmer_coeff, glm_coeff = glm_coeff)) +
geom_point(aes(x=glm_coeff, col="glm_coeff")) +
geom_point(aes(x=lmer_coeff, col="lmer_coeff")) +
scale_color_manual(name = "", values = c("#fdb924", "#8b2323")) +
xlab("coefficients") +
ylab("job class")

Run a Bayesian varying intercepts model (rethinking)
Okay, now it’s time to get Bayesian. Let’s return to the original dataset–the top 25 job classes–and we’ll tackle the exact same varying intercepts modeling task as above. The only difference this time is that we get to set some priors! To create a varying intercepts formula, we need to add a term–a[JOB_INDEX]–to represent these intercepts, which are allowed to vary based on the JOB_INDEX value. We also add one additional prior, a_sigma, to represent our expectations about the distribution for the standard deviation of the mean hourly starting salary across groups.
Note that we’re switching to using the JOB_INDEX version of the variable, rather than the JOB_FACTOR version. This is because rethinking suggests that index variables are preferable to factors for its Bayesian fitting approach, because an index variable allows all distinct groups present in the variable to be assigned an explicit prior (McElreath, p. 155).
fit_model2_bayes <- function(){
# Prep the data columns we want to pass the the model
dat_list <- list(
COMP_RATE_STND_HOURLY = hr_2019_top_25_job_classes$COMP_RATE_STND_HOURLY,
JOB_INDEX = hr_2019_top_25_job_classes$JOB_INDEX,
YRS_SINCE_ORIGINAL_HIRE = hr_2019_top_25_job_classes$YRS_SINCE_ORIGINAL_HIRE
)
tic("Running model 2 Bayesian")
model2_bayes <- ulam(
alist(
COMP_RATE_STND_HOURLY ~ dnorm( mu , sigma ),
mu <- Intercept + a[JOB_INDEX] + b_YRS_SINCE_ORIGINAL_HIRE*YRS_SINCE_ORIGINAL_HIRE,
Intercept ~ dnorm(30, 10),
sigma ~ dcauchy(0, 20),
# fixed effects priors
b_YRS_SINCE_ORIGINAL_HIRE ~ dnorm(0.675, 1.5),
# multilevel adaptive priors
a[JOB_INDEX] ~ dnorm(0, a_sigma), # prior for mean hourly starting salary of each group (after accounting for the "grand intercept" for the population as a whole): a normal distribution centered around 0
# hyper-priors
a_sigma ~ dcauchy(0, 50) # prior for the standard deviation of the mean hourly starting salary across groups
),
data = dat_list,
chains = 1,
log_lik = TRUE
)
toc() # stop the timer
beepr::beep() # emit a beep so we can come back from whatever else we were doing
saveRDS(model2_bayes, "./models/model2_bayes.rds")
return(model2_bayes)
}
# If we have already fit the model and have saved the results to disk, do not re-fit it! (This takes a long time.)
# Simply load the model from disk...
if(file.exists("./models/model2_bayes.rds")) {
cat("Model has already been fit! Loading results from disk.")
model2_bayes <- readRDS("./models/model2_bayes.rds")
} else {
# If we have not yet fit the model, go ahead and fit it and save the results to disk
cat("Model has not yet been fit. Fitting model now. Please be patient.")
model2_bayes <- fit_model2_bayes()
}
Model has already been fit! Loading results from disk.
We can view the model results:
precis(model2_bayes, depth=2)
plot(precis(model2_bayes, depth=2), main="All Posterior Params")

plot(precis(model2_bayes, depth=2, pars="a"), labels=JOB_LABELS, main="Job Intercepts")

Now, for the sake of illustration, let’s run the same model again–this time with a more extreme prior for the standard deviation across job classes:
fit_model2_bayes_extreme_priors <- function() {
# Prep the data columns we want to pass the the model
dat_list <- list(
COMP_RATE_STND_HOURLY = hr_2019_top_25_job_classes$COMP_RATE_STND_HOURLY,
JOB_INDEX = hr_2019_top_25_job_classes$JOB_INDEX,
YRS_SINCE_ORIGINAL_HIRE = hr_2019_top_25_job_classes$YRS_SINCE_ORIGINAL_HIRE
)
tic("Running model 2 Bayesian w/ extreme priors")
model2_bayes_extreme_priors <- ulam(
alist(
COMP_RATE_STND_HOURLY ~ dnorm( mu , sigma ),
mu <- Intercept + a[JOB_INDEX] + b_YRS_SINCE_ORIGINAL_HIRE*YRS_SINCE_ORIGINAL_HIRE,
Intercept ~ dnorm(30, 10),
sigma ~ dcauchy(0, 30),
# fixed effects priors
b_YRS_SINCE_ORIGINAL_HIRE ~ dnorm(0.675, 1.5),
# multilevel adaptive priors
a[JOB_INDEX] ~ dnorm(0, a_sigma),
# hyper-priors
a_sigma ~ dcauchy(0, 0.00000000000000001) # <-- make this prior more extreme
),
data = dat_list,
chains = 1,
log_lik = TRUE
)
toc() # stop the timer
beepr::beep() # emit a beep so we can come back from whatever else we were doing
saveRDS(model2_bayes_extreme_priors, "./models/model2_bayes_extreme_priors.rds")
return(model2_bayes_extreme_priors)
}
if(file.exists("./models/model2_bayes_extreme_priors.rds")) {
cat("Model has already been fit! Loading results from disk.")
model2_bayes_extreme_priors <- readRDS("./models/model2_bayes_extreme_priors.rds")
} else {
cat("Model has not yet been fit. Fitting model now. Please be patient.")
model2_bayes_extreme_priors <- fit_model2_bayes_extreme_priors()
}
Model has already been fit! Loading results from disk.
And view the model results:
precis(model2_bayes_extreme_priors, depth=2)
plot(precis(model2_bayes_extreme_priors, depth=2), main="All Posterior Params")

plot(precis(model2_bayes_extreme_priors, depth=2, pars="a"), labels=JOB_LABELS, main="Job Intercepts")

Compare the Bayesian models
You’ll notice that the two Bayesian models here have different pWAIC values. This is because the pWAIC value value also takes into account the regularizing work performed by the prior to shrink all of our group intercepts towards the mean (McElreath, p. 404). More regularization results in a lower effective parameter value. In the model with extreme priors, we somewhat thwarted the regularizing abilities of the model. The intercepts weren’t able to be shrunk as much toward the mean, and as a result, the number of effective parameters increased slightly.
compare(model2_bayes, model2_bayes_extreme_priors)
LS0tCnRpdGxlOiAiTU4gUHVibGljIFNhbGFyeSBEYXRhIC0gTXVsdGlsZXZlbCBNb2RlbHMiCnN1YnRpdGxlOiAiUGFydCAyOiBWYXJ5aW5nIGludGVyY2VwdHMiCmF1dGhvcjogIkFsaXNvbiBMaW5rIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkoZGlyZWN0bGFiZWxzKQoKIyBGb3Igbm9uLUJheWVzaWFuIG11bHRpbGV2ZWwgbW9kZWxpbmcKI2xpYnJhcnkobG1lNCkKbGlicmFyeShsbWVyVGVzdCkgIyB1c2UgbG1lclRlc3QsIHdoaWNoIGlzIGxpa2UgbG1lNCwgYnV0IGFsc28gcHJvdmlkZXMgcC12YWx1ZXMgZm9yIGZpeGVkIGVmZmVjdHMgKGFsYmVpdCBhIHNvbWV3aGF0IGNvbnRyb3ZlcnNpYWwgYXBwcm9hY2guLi4pCgojIEZvciBCYXllc2lhbiBtdWx0aWxldmVsIG1vZGVsaW5nCmxpYnJhcnkocnN0YW4pCm9wdGlvbnMobWMuY29yZXMgPSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSkgIyBBbGxvd3MgcnN0YW4gdG8gcnVuIG9uIG11bHRpcGxlIGNvcmVzCnJzdGFuX29wdGlvbnMoYXV0b193cml0ZSA9IFRSVUUpCmxpYnJhcnkocmV0aGlua2luZykgCgojIFBhY2thZ2VzIGZvciB0aW1pbmcgCmxpYnJhcnkoYmVlcHIpCmxpYnJhcnkodGljdG9jKQpgYGAKCgpOb3cgdGhhdCB3ZSdyZSByZWFkeSB0byBsYXVuY2ggaW50byBtdWx0aWxldmVsIG1vZGVsaW5nLCBsZXQncyByZXZpc2l0IHRoZSBjb25jZXB0IG9mICJwb29saW5nIi4gIEluIHRoaXMgZXhhbXBsZSwgd2UndmUgYmVlbiB0cnlpbmcgdG8gZXN0aW1hdGUgYW4gTWlubmVzb3RhIHB1YmxpYyBlbXBsb3llZXMncyBob3VybHkgY29tcGVuc2F0aW9uIHJhdGVzLiAgV2UgY2FuIGlsbHVzdHJhdGUgcG9vbGluZyBhdCBwbGF5IGJ5IHBlcmZvcm1pbmcgYSBzaW1wbGUgdGhvdWdodCBleHBlcmltZW50IHdpdGggdGhpcyBwYXJ0aWN1bGFyIG1vZGVsaW5nIHRhc2sgaW4gbWluZC4gIExldCdzIHNheSwgZm9yIGV4YW1wbGUsIHRoYXQgd2Ugd2FudCB0byB0aHJvdyBhbiAiZW1wbG95ZWUgZnVuIGRheSIgZm9yIE1pbm5lc290YSBzdGF0ZSBlbXBsb3llZXMuICAoWWF5LS10aGV5IGRlc2VydmUgaXQhKSAgV2hpbGUgZXZlcnlvbmUncyBvdXQgaGF2aW5nIGZ1biwgd2Ugd2FudCB0byB1c2UgdGhhdCBvcHBvcnR1bml0eSB0byBnZXQgYSAibGF5IG9mIHRoZSBsYW5kIiBhYm91dCBob3cgZW1wbG95ZWVzIGFyZSBjb21wZW5zYXRlZC4gIFRoZSBnb2FsIGlzIHRvIGFycmFuZ2Ugb3VyIGVtcGxveWVlcyBpbiBhIHdheSB0aGF0IG1ha2VzIGl0IGVhc3kgdG8gc2VlL3VuZGVyc3RhbmQgbWVhbmluZ2Z1bCBwYXR0ZXJucyBpbiB0aGVpciBjb21wZW5zYXRpb24gcmF0ZXMuICAoTm90ZTogSW4gdGhpcyBleGFtcGxlLCB3ZSdyZSBwcmV0ZW5kaW5nIHdlIGRvbid0IGhhdmUgZGlyZWN0IGFjY2VzcyB0byBIUiByZWNvcmRzIGFib3V0IGNvbXBlbnNhdGlvbiByYXRlcy0td2UgbmVlZCB0byBkZWR1Y2UgdGhlIHRyZW5kcyBmcm9tIG91ciBvYnNlcnZhdGlvbnMgZHVyaW5nIHRoZSAiZW1wbG95ZWUgZnVuIGRheSIuKQoKT25lIGFwcHJvYWNoIHdvdWxkIGJlIHRvIGludml0ZSBldmVyeW9uZSB0byBvbmUgZ2lhbnQgcG9vbCBwYXJ0eSwgd2hlcmUgZXZlcnlvbmUncyBmb3JjZWQgdG8gc2hvdyB1cCBhbmQgc3dpbSB0b2dldGhlci4gIFRvIGFjY29tbW9kYXRlIHRoaXMsIHlvdSdyZSBnb2luZyB0byBuZWVkIGEgcHJldHR5IGxhcmdlIHBvb2wsIGFuZCB5b3UncmUgZ29pbmcgdG8gdGhyb3cgeW91ciBJVCBwcm9mZXNzaW9uYWxzIHJpZ2h0IG5leHQgdG8geW91ciBjb3JyZWN0aW9ucyBvZmZpY2VycywgYW5kIHlvdXIgbnVyc2VzIGluIG5leHQgdG8geW91ciBjb3VydCBjbGVya3MuICBXaXRoIHRoaXMgYXBwcm9hY2gsIHlvdSBjYW4gc3RpbGwgZ2V0IGEgZ2VuZXJhbCBzZW5zZSBvZiB0aGUgb3ZlcmFsbCBwb3B1bGF0aW9uIHRyZW5kcywgYnV0IHRoaXMgbWFrZXMgaXQgaGFyZCB0byBzb3J0IG91dCB0aGUgbnVhbmNlZCBkaXN0aW5jdGlvbnMgYmV0d2VlbiB0aGVzZSBncm91cHMnIGNvbXBlbnNhdGlvbiBsZXZlbHMuICBUaGlzIGNvbmNlcHQgaXMgY2FsbGVkICoqY29tcGxldGUgcG9vbGluZyoqLgoKIVtjcm93ZGVkIHBvb2xdKGltZy9wb29sLmpwZykKCkFub3RoZXIgYXBwcm9hY2ggd291bGQgYmUgdG8gZ28gdG8gYSBkaWZmZXJlbnQgZXh0cmVtZTogaW5zdGVhZCBvZiB0aHJvd2luZyBhIHBvb2wgcGFydHksIHdlIGNvdWxkIHRocm93IGEgYnVuY2ggb2Ygc2VwYXJhdGUgb2ZmaWNlIHBhcnRpZXMuICBUaGlzIGNvbmNlcHQgaXMgY2FsbGVkICoqbm8gcG9vbGluZyoqLiAgQXQgZWFjaCBvZiB0aGVzZSBwYXJ0aWVzLCBldmVyeW9uZSB3aWxsIGNlbGVicmF0ZSBvbmx5IHdpdGggb3RoZXIgbWVtYmVycyBvZiB0aGVpciBzYW1lIGpvYiBjbGFzcywgYW5kIG5vIG9uZSBpcyBhbGxvd2VkIHRvIG1pbmdsZSB3aXRoIGFueW9uZSBvdXRzaWRlIG9mIHRoZWlyIGltbWVkaWF0ZSBncm91cC4gIFRoaXMgd291bGQgbWFrZSB0aGUgZGlzdGluY3Rpb25zIGJldHdlZW4gZ3JvdXBzIHZlcnkgY2xlYXIuICBVbmZvcnR1bmF0ZWx5LCBpdCB3b3VsZCBiZSBib3RoIGN1bWJlcnNvbWUgYW5kIHBvdGVudGlhbGx5IG1pc2xlYWRpbmcgdG8gdHJ5IHRvIGRlZHVjZSBicm9hZGVyIHBvcHVsYXRpb24gdHJlbmRzIGZyb20gYWxsIG9mIHRoZXNlIHNlcGFyYXRlIG9mZmljZSBwYXJ0aWVzLiAgRm9yIGV4YW1wbGUsIHRoZSBzdGF0ZSBoYXMgb25seSBvbmUgIkp1ZGljaWFsIE1haWwgQ2xlcmsiLCBvbmUgIk11c2ljIFRoZXJhcGlzdCIsIGFuZCBvbmUgIlNlZWQgUG90YXRvIFNwZWNpYWxpc3QiLiAgSXQgd291bGQgYmUgdHJpY2t5IHRvIHRyeSB0byB2aXNpdCBlYWNoIGFuZCBldmVyeSBvbmUgb2YgdGhlc2UgaW5kaXZpZHVhbHMgYXQgdGhlaXIgb25lLW9mZiBvZmZpY2UgcGFydGllcyEgIEJleW9uZCB0aGF0LCB5b3UgY291bGQgcnVuIGludG8gaWRpb3N5bmNyYXRpYyBzaXR1YXRpb25zIHRoYXQgY291bGQgc2tldyB5b3VyIHBlcnNwZWN0aXZlLiAgRm9yIGV4YW1wbGUsIHlvdSBtaWdodCBkaXNjb3ZlciB0aGF0IE1pbm5lc290YSdzIHNlZWQgcG90YXRvIHNwZWNpYWxpc3QganVzdCBoYXBwZW5zIHRvIGJlIHRoZSB3b3JsZCdzIGxlYWRpbmcgZXhwZXJ0IG9uIHNlZWQgcG90YXRvZXMsIGFuZCBpcyBjb21wZW5zYXRlZCBhY2NvcmRpbmdseS4gIFRoaXMgaXMgZ3JlYXQgbmV3cyBmb3IgTWlubmVzb3RhISAgQnV0IHlvdSB3b3VsZG4ndCB3YW50IHRvIGxldCB0aGlzIG91dGxpZXIgYmUgdGhlIG9ubHkgaW5wdXQgaW50byB5b3VyIGNvbXBlbnNhdGlvbiBtb2RlbCBmb3IgTWlubmVzb3RhIHNlZWQgcG90YXRvIHNwZWNpYWxpc3RzIGludG8gdGhlIGZ1dHVyZS4gIEJlY2F1c2UsIHdoZW4gdGhlIGN1cnJlbnQgZXhwZXJ0IHJldGlyZXMgYW5kIHRoZSBfbmV4dF8gc2VlZCBwb3RhdG8gc3BlY2lhbGlzdCBpcyBoaXJlZCwgdGhleSBhcmVuJ3QgbGlrZWx5IHRvIGJlIG5lYXJseSBhcyBxdWFsaWZpZWQtLW9yIGFzIHdlbGwgY29tcGVuc2F0ZWQuCgohW29mZmljZSBwYXJ0eV0oaW1nL29mZmljZV9wYXJ0eS5qcGcpCgpUaGUgZmluYWwgYXBwcm9hY2gtLXRoZSAqKnBhcnRpYWwgcG9vbGluZyoqIGFwcHJvYWNoLS1pcyBsaWtlIGNlbGVicmF0aW5nIG91ciBlbXBsb3llZSBmdW4gZGF5IGF0IGEgd2F0ZXJwYXJrLiAgQXQgYSB3YXRlcnBhcmssIHRoZXJlJ3MgcGxlbnR5IG9mIHJvb20gdG8gc3ByZWFkIG91dCBhbmQgY2x1c3Rlci4gIFlvdSdsbCBmaW5kIHRoYXQgZW1wbG95ZWVzIG5hdHVyYWxseSB0ZW5kIHRvIG1pbmdsZSB3aXRoIHRoZWlyIG9mZmljZW1hdGVzIGFjcm9zcyBkaWZmZXJlbnQgYXJlYXMgb2YgdGhlIHdhdGVycGFyay4gIElmIHlvdSB3ZXJlIHRvIHdhbGsgYXJvdW5kIHRvIHRoZSBkaWZmZXJlbnQgZW1wbG95ZWUgY2x1c3RlcnMsIHlvdSBjb3VsZCBlYXNpbHkgdGFrZSBhIHF1aWNrICJzdHJhdyBwb2xsIiBpbiBlYWNoIGdyb3VwIGFuZCBnZXQgYSBzZW5zZSBvZiBob3cgbXVjaCB0aGVpciBjb21wZW5zYXRpb24gcmF0ZXMgdmFyeSB3aXRoaW4gdGhlIGNsdXN0ZXIuICBBbmQgYWZ0ZXIgeW91IGhhdmUgc2FtcGxlZCBzZXZlcmFsIGNsdXN0ZXJzLCB5b3UgY291bGQgc3RhcnQgdG8gZ2V0IGEgZ29vZCBmZWVsIGZvciBob3cgY29tcGVuc2F0aW9uIHJhdGVzIHZhcnkgX2Fjcm9zc18gY2x1c3RlcnMuICBCeSB0aGUgZW5kLCB5b3UnZCBoYXZlIGEgcHJldHR5IHJlYXNvbmFibGUgZXN0aW1hdGUgb2YgdGhlIGNvbXBlbnNhdGlvbiB0cmVuZHMtLWJvdGggZm9yIHRoZSBwb3B1bGF0aW9uIGFzIGEgd2hvbGUsIGFuZCBmb3IgZWFjaCBjbHVzdGVyLgoKIVt3YXRlcnBhcmtdKGltZy93YXRlcnBhcmsuanBnKQoKCk11bHRpbGV2ZWwgbW9kZWxpbmcgaXMgbGlrZSBhIGRheSBhdCB0aGUgd2F0ZXJwYXJrLiAgKFNlZTogTWNFbHJlYXRoLCBwLiA0MDggZm9yIGEgbW9yZSBjb21wbGV0ZSBkaXNjdXNzaW9uIG9mICJwb29saW5nIiBjb25jZXB0cy4pICBMZXQncyBkaXZlIGluIQoKCiMjIFJ1biBhIChub24tQmF5ZXNpYW4pIHZhcnlpbmcgaW50ZXJjZXB0cyBtb2RlbCAoYGxtZTRgKQoKTGV0J3Mgc3RhcnQgZnJvbSB0aGUga2V5IGFzc3VtcHRpb24gdGhhdCBtdWNoIG9mIHRoZSBkaWZmZXJlbmNlIGluIGNvbXBlbnNhdGlvbiByYXRlcyBsaWtlbHkgaGFzIHRvIGRvIHdpdGggd2hpY2ggam9iIGVhY2ggZW1wbG95ZWUgaXMgcGVyZm9ybWluZy4gIFdlIGNlcnRhaW5seSBkb24ndCBleHBlY3QgZWFjaCBqb2IgdG8gaGF2ZSB0aGUgc2FtZSBzdGFydGluZyBzYWxhcnkgKGludGVyY2VwdCksIHNvIGxldCdzIGFjY291bnQgZm9yIHRoYXQgYnkgZml0dGluZyBhIG11bHRpbGV2ZWwgbW9kZWwgd2hlcmUgdGhlIGludGVyY2VwdCBpcyBhbGxvd2VkIHRvIHZhcnkgYmFzZWQgb24gam9iIGNsYXNzLgoKV2hlbiBydW5uaW5nIGEgdmFyeWluZyBpbnRlcmNlcHRzIG1vZGVsIHVzaW5nIHRoZSBgbG1lNGAgcGFja2FnZSwgeW91J2xsIHVzZSBtb2RlbCBmb3JtdWxhIHN5bnRheCB0aGF0IGlzIHJlbGF0aXZlbHkgc2ltaWxhciB0byB3aGF0IHlvdSdyZSBwcm9iYWJseSBhbHJlYWR5IHVzZWQgdG8gZnJvbSBvdGhlciBSIHBhY2thZ2VzLiAgTG9va2luZyBhdCB0aGUgZm9ybXVsYSBiZWxvdywgeW91J2xsIHNlZSB0aGF0IHRoZSBvbmUgZGlmZmVyZW5jZSBpcyB0aGUgYCgxfEpPQl9GQUNUT1IpYCBjb21wb25lbnQgb2YgdGhlIG1vZGVsLiAgVGhlIGAxfGAgaW5kaWNhdGVzIHRoYXQgd2Ugd2FudCB0byBhZGQgYSB2YXJ5aW5nIF9pbnRlcmNlcHRfLCB3aXRoIGB8Sk9CX0ZBQ1RPUmAgYXMgdGhlIGdyb3VwaW5nIGFjcm9zcyB3aGljaCB3ZSB3YW50IHRvIGFsbG93IHRoZSBpbnRlcmNlcHRzIHRvIHZhcnkuCgpgYGB7ciBtb2RlbF92YXJfaW50X2xtZXJ9Cm1vZGVsMl9sbWVyIDwtIGxtZXIoQ09NUF9SQVRFX1NUTkRfSE9VUkxZIH4gWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUgKyAoMXxKT0JfRkFDVE9SKSwgIyBtb2RlbCBmb3JtdWxhCiAgICAgICAgICAgICAgIGRhdGEgPSBocl8yMDE5X3RvcF8yNV9qb2JfY2xhc3NlcywgCiAgICAgICAgICAgICAgIFJFTUwgPSBGQUxTRSkgIyBzZXQgUkVNTCA9IEZBTFNFIHRvIHVzZSBsb2ctbGlrZWxpaG9vZCBvcHRpbWl6YXRpb24gYW5kIGdldCBBSUMvZGV2aWFuY2UgdmFsdWVzIGluIG91dHB1dAoKc3VtbWFyeShtb2RlbDJfbG1lcikKYGBgCgpUaGUgbW9kZWwgc3VtbWFyeSBzZXBhcmF0ZXMgdGhlIGV4cGxhaW5lZCB2YXJpYW5jZSBpbnRvIHR3byBjb25jZXB0czogKipmaXhlZCBlZmZlY3RzKiogdnMuICoqcmFuZG9tIGVmZmVjdHMqKi4gIExvb2tpbmcgZmlyc3QgYXQgdGhlICJmaXhlZCBlZmZlY3RzIiwgd2Ugbm90aWNlIHRoYXQgdGhlIGVzdGltYXRlIGZvciB0aGUgaW50ZXJjZXB0IGlzIH5cJDI3LCByZXByZXNlbnRpbmcgdGhlIHBvcHVsYXRpb24gbWVhbiBob3VybHkgY29tcGVuc2F0aW9uIHJhdGUuICBXaGVuIHdlIGxvb2sgYXQgdGhlIG51bWJlciBvZiB5ZWFycyBzaW5jZSBvcmlnaW5hbCBoaXJlIChZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRSksIHRoZSBlc3RpbWF0ZSBmb3IgdGhlIHNsb3BlIG9mIGlzIDAuMTY3LiAgVGhpcyBpbmRpY2F0ZXMgdGhhdCB0aGUgcG9wdWxhdGlvbiBhdmVyYWdlIGluY3JlYXNlIGluIGhvdXJseSBjb21wZW5zYXRpb24gcmF0ZSBmb3IgZWFjaCBhZGRpdGlvbmFsIHllYXIgd29ya2VkIGF0IHRoZSBzdGF0ZSBpcyB+MTYtMTcgY2VudHMgcGVyIHllYXIuICBOb3csIGxvb2tpbmcgYXQgdGhlICJyYW5kb20gZWZmZWN0cyIsIHdlIHNlZSB0aGF0IHRoZSBqb2IgY2xhc3MgKEpPQl9GQUNUT1IpIGFjY291bnRzIGZvciBfYSBsb3RfIG9mIHRoZSBhZGRpdGlvbmFsIHZhcmlhbmNlIGluIGhvdXJseSBjb21wZW5zYXRpb24gcmF0ZXMuICBUaGVyZSBpcyBhbHNvIHN0aWxsIHNvbWUgcmVzaWR1YWwgdmFyaWFuY2UgbGVmdCBvdmVyICh+Ny41OSkgdGhhdCBpcyBub3QgZXhwbGFpbmVkIGJ5IHRoZSBtb2RlbC4KCkFuZCB3aGF0IGV4YWN0bHkgZG9lcyB0aGlzIGxvb2sgbGlrZT8gIEhlcmUncyBhIHZpc3VhbCB0byB0cnkgdG8gaWxsdXN0cmF0ZSB3aGF0IGl0J3MgZG9pbmcuICBUaGUgc2xvcGUgb2YgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHllYXJzIHdvcmtlZCBhbmQgaG91cmx5IGNvbXBlbnNhdGlvbiByYXRlIGlzIHN0aWxsIGZpeGVkIHRvIGJlIHRoZSBzYW1lIGFjcm9zcyBhbGwgam9iIGNsYXNzZXMsIGJ1dCBub3cgdGhlIGRpZmZlcmVudCBqb2IgY2xhc3NlcyBlYWNoIGhhdmUgdGhlaXIgb3duIHNlcGFyYXRlIGludGVyY2VwdHM6CgpgYGB7ciB2aXpfdmFyX2ludH0KcmFuZCA8LSBhcy5kYXRhLmZyYW1lKHJhbmVmKG1vZGVsMl9sbWVyKSkKCmNvZWZmX2RmIDwtIHJhbmQgJT4lCiAgbXV0YXRlKAogICAgcG9wX2ludGVyY2VwdCA9IGZpeGVmKG1vZGVsMl9sbWVyKVsnKEludGVyY2VwdCknXSwKICAgIHBvcF9zbG9wZSA9IGZpeGVmKG1vZGVsMl9sbWVyKVsnWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUnXQogICkgCgp4IDwtIGFzLmRhdGEuZnJhbWUoc2VxKDAsIDMwLCAxKSkgIyBzZXQgdXAgeCBheGlzIGRhdGEgdG8gdmlzdWFsaXplIDMwIHllYXJzCm5hbWVzKHgpIDwtICJZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRSIKCnZpel9kZiA8LSBjcm9zc2luZyhjb2VmZl9kZiwgeCkKCnZpel9kZiA8LSB2aXpfZGYgJT4lCiAgbXV0YXRlKAogICAgbWlkZGxlID0gcG9wX2ludGVyY2VwdCArIGNvbmR2YWwgKyBZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRSAqIHBvcF9zbG9wZSwKICAgIGxvd2VyX2JvdW5kID0gcG9wX2ludGVyY2VwdCArIGNvbmR2YWwgKyBZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRSAqIHBvcF9zbG9wZSArIGNvbmRzZCAqIDIsICMgOTUlIENJCiAgICB1cHBlcl9ib3VuZCA9IHBvcF9pbnRlcmNlcHQgKyBjb25kdmFsICsgWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUgKiBwb3Bfc2xvcGUgLSBjb25kc2QgKiAyCiAgKQoKZ2dwbG90KHZpel9kZiwgYWVzKHg9WVJTX1NJTkNFX09SSUdJTkFMX0hJUkUsIHk9bWlkZGxlLCBjb2w9Z3JwLCBsYWJlbD1ncnApKSArCiAgZ2VvbV9zbW9vdGgoYWVzKHltaW4gPSBsb3dlcl9ib3VuZCwgeW1heCA9IHVwcGVyX2JvdW5kKSwgc3RhdCA9ICJpZGVudGl0eSIpICsKICBnZW9tX2RsKGFlcyhsYWJlbD1ncnAsIGNvbG9yPWdycCksIG1ldGhvZCA9IGxpc3QoImxhc3QucG9pbnRzIikpICsKICB4bGltKDAsIDUwKSArCiAgeWxpbSgwLCA2MCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgoKIyMjIENvbXBhcmUgdmFyeWluZyBpbnRlcmNlcHRzIGFwcHJvYWNoIHRvIHJ1bm5pbmcgYSBgZ2xtYCB3aXRoIGEgZmFjdG9yIHZhcmlhYmxlCgpJZiB5b3UncmUgbGlrZSBtZSwgeW91IG1heSBiZSBzYXlpbmcgdG8geW91cnNlbGYgYXQgdGhpcyBwb2ludDogIk5vIGJpZyBkZWFsISBUaGlzIGRvZXNuJ3QgcmVhbGx5IHNlZW0gYW55IGRpZmZlcmVudCBmcm9tIHNpbXBseSBydW5uaW5nIGEgYmFzaWMgbGluZWFyIG1vZGVsIGFuZCBhZGRpbmcgdGhlIGpvYiBjbGFzcyBhcyBhIGZhY3RvciB2YXJpYWJsZS4iICBCdXQgYXMgaXQgdHVybnMgb3V0LCBvdXIgbXVsdGlsZXZlbCBtb2RlbCBfaXNfIGEgbGl0dGxlIGRpZmZlcmVudC4gIEJlY2F1c2UgdGhlIHZhcnlpbmcgaW50ZXJjZXB0cyBtb2RlbCB3ZSBmaXQgYWJvdmUgaXMgYSAid2F0ZXJwYXJrIHBhcnR5Ii4gIChSZW1lbWJlciBvdXIgJ3Bvb2xpbmcnIG1ldGFwaG9yPykgIEFuZCBmaXR0aW5nIGEgZ2xtIG1vZGVsIHdpdGggam9iIGNsYXNzIGFzIGEgZmFjdG9yIHZhcmlhYmxlIGlzIGxpa2UgdGhyb3dpbmcgYW4gIm9mZmljZSBwYXJ0eSIuICAKClRvIGhlbHAgaWxsdXN0cmF0ZSB0aGlzIGRpZmZlcmVuY2UsIHdlJ3JlIGdvaW5nIHRvIG5lZWQgdG8gZXhwYW5kIHRoZSBkYXRhc2V0IGEgYml0LiAgSW4gYWRkaXRpb24gdG8gdGhlICJ0b3AgMjUiIGpvYiBjbGFzc2VzIHdlJ3ZlIGJlZW4gbG9va2luZyBhdCBzbyBmYXIsIHdlJ3JlIGdvaW5nIHRvIGFkZCBhbiBhZGRpdGlvbmFsIHNldCBvZiBqb2IgY2xhc3NlcyB0aGF0IGFyZSBtdWNoIHNtYWxsZXIuICBFYWNoIG9mIHRoZXNlIGpvYiBjbGFzc2VzIGNvbnRhaW4gZmV3ZXIgdGhhbiA1IGVtcGxveWVlcywgYW5kIHdlIHdpbGwgaW5jbHVkZSB0aGVtIGluIG9yZGVyIHRvIGJlIGFibGUgdG8gaWxsdXN0cmF0ZSB0aGUgY29uY2VwdCBvZiAic2hyaW5rYWdlIjoKCmBgYHtyIGZpbHRlcl9qb2JfY2xhc3Nlc19wbHVzfQojIEFkZCBzb21lIGFkZGl0aW9uYWwsIHNtYWxsZXIgam9iIGNsYXNzZXMgZm9yIGlsbHVzdHJhdGlvbiBwdXJwb3NlcyBsYXRlciBvbgp0b3BfMjVfcGx1c19zb21lX3NtYWxsZXJfam9iX2NsYXNzZXMgPC0gYyh0b3BfMjVfam9iX2NsYXNzZXMsICJQdWJsaWMgRGVmZW5zZSBEYXRhIEVudHJ5IENsIiwgIkp1ZCBMaWJyYXJ5IEFzc3QgSSIsICJTZXJ2aWNlIFdvcmtlciIsICJQZWVyIFNwZWNpYWxpc3QiLCAiUG9sbHV0aW9uIENvbnQgVGVjaG5pY2lhbiIsICJBc3N0IERpciBNbiBTdGF0ZSBMb3R0ZXJ5IiwgIkFzc3QgQ29tbXIgUG9sbHV0aW9uIENvbnRyb2wiLCAiRXBpZGVtaW9sb2dpc3QgUHJvZ3JhbSBNYW5hZ2VyIiwgIkdvdmVybm9yIikKCmhyXzIwMTlfdG9wXzI1X2pvYl9jbGFzc2VzX3BsdXMgPC0gaHJfMjAxOSAlPiUKICBmaWx0ZXIoSk9CX1RJVExFICVpbiUgdG9wXzI1X3BsdXNfc29tZV9zbWFsbGVyX2pvYl9jbGFzc2VzKQoKaHJfMjAxOV90b3BfMjVfam9iX2NsYXNzZXNfcGx1cyRKT0JfRkFDVE9SIDwtIHJlbGV2ZWwoYXMuZmFjdG9yKGhyXzIwMTlfdG9wXzI1X2pvYl9jbGFzc2VzX3BsdXMkSk9CX1RJVExFKSwgcmVmPSJTdGF0ZSBQYXRyb2wgVHJvb3BlciIpCmBgYAoKTm93LCBsZXQncyBydW4gdHdvIG1vZGVscyBvbiB0aGlzIGV4cGFuZGVkIGRhdGFzZXQtLW9uZSBtdWx0aWxldmVsIG1vZGVsIHVzaW5nIGBsbWVyYCBhbmQgb25lIGxpbmVhciBtb2RlbCB1c2luZyBgZ2xtYDoKCmBgYHtyIHJ1bl9sbWVyX2dsbV9jb21wYXJlfQptb2RlbDJfbG1lcl90b3AyNXBsdXMgPC0gbG1lcihDT01QX1JBVEVfU1RORF9IT1VSTFkgfiBZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRSArICgxfEpPQl9GQUNUT1IpLCBkYXRhID0gaHJfMjAxOV90b3BfMjVfam9iX2NsYXNzZXNfcGx1cywgUkVNTCA9IEZBTFNFKQoKbW9kZWwyX2dsbV90b3AyNXBsdXMgPC0gZ2xtKENPTVBfUkFURV9TVE5EX0hPVVJMWSB+IFlSU19TSU5DRV9PUklHSU5BTF9ISVJFICsgSk9CX0ZBQ1RPUiwgZGF0YSA9IGhyXzIwMTlfdG9wXzI1X2pvYl9jbGFzc2VzX3BsdXMsIGZhbWlseSA9ICJnYXVzc2lhbiIpCmBgYAoKTm93LCB3ZSBjYW4gZG8gYSBsaXR0bGUgd29yayB0byB2aXN1YWxpemUgdGhlIGNvZWZmaWNpZW50cyBmcm9tIGVhY2ggbW9kZWwgaW4gYSBzaWRlLWJ5LXNpZGUgY29tcGFyaXNvbi4gIFlvdSdsbCBub3RpY2UgdGhhdCwgZm9yIHRoZSAyNSBsYXJnZXIgam9iIGNsYXNzZXMgd2UgZXhhbWluZWQsIHRoZXJlIGRvZXNuJ3Qgc2VlbSB0byBiZSBtdWNoIGRpZmZlcmVuY2UgaW4gdGhlIGNvZWZmaWNpZW50IGVzdGltYXRlcy4gIFdoZXJlIHRoaW5ncyBzdGFydCB0byBtYXR0ZXIgaXMgZm9yIHRoZSBfc21hbGxlcl8gam9iIGNsYXNzZXMgd2UgaW5jbHVkZWQgaW4gdGhlIG1vZGVsLiAgRm9yIHRoZXNlIGpvYiBjbGFzc2VzLCB3aGVyZSB3ZSBoYWQgcmVsYXRpdmVseSBmZXdlciBpbmRpdmlkdWFscyBpbiBlYWNoIGNsYXNzLCB0aGUgdmFyeWluZyBpbnRlcmNlcHRzIG1vZGVsIGlzICoqc2hyaW5raW5nKiogdGhlaXIgY29lZmZpY2llbnQgZXN0aW1hdGVzIHRvd2FyZHMgdGhlIG1pZGRsZS4gIEl0J3MgdXNpbmcgaW5mb3JtYXRpb24gZnJvbSB0aGUgImdyYW5kIiBwb3B1bGF0aW9uIG1lYW4gdG8gYWRqdXN0IG91ciBjb2VmZmljaWVudCBlc3RpbWF0ZXMgZm9yIHRoZXNlIHNtYWxsZXIgZ3JvdXBzIHRvIG1ha2UgdGhlbSBsZXNzIGV4dHJlbWUhICBUaGlzIHBoZW5vbWVub24gb2YgKipzaHJpbmtpbmcqKiBwYXJhbWV0ZXIgZXN0aW1hdGVzIHRvd2FyZHMgdGhlIHBvcHVsYXRpb24gbWVhbiBpcyBhIGJ1aWx0LWluIGJlbmVmaXQgb2YgbXVsdGlsZXZlbCBtb2RlbGluZy4gIFRoZSBtb2RlbCBpcyBwZXJmb3JtaW5nIGEgZGVsaWNhdGUgYmFsYW5jaW5nIGFjdCBiZXR3ZWVuIG92ZXJhbGwgcG9wdWxhdGlvbiB0cmVuZHMgYW5kIHRoZSB2YXJpYW5jZSB3aXRoaW4gYW5kIGFjcm9zcyBncm91cHMuICBGb3Igc21hbGxlciBncm91cHMsIHRoZSAiZ3JhbmQgbWVhbiIgb2YgdGhlIHBvcHVsYXRpb24gcGxheXMgYSBiaWdnZXIgcm9sZSBpbiB0aGUgZmluYWwgY29lZmZpY2llbnQgZXN0aW1hdGVzIHRoYW4gaXQgZG9lcyBmb3IgbGFyZ2VyIGdyb3Vwcy4KCmBgYHtyIGV4dHJhY3RfY29lZmZzX2xtZXJfZ2xtLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojIEV4dHJhY3QgdGhlIGdyb3VwIGNvZWZmaWNpZW50cyBmcm9tIHRoZSB2YXJ5aW5nIGludGVyY2VwdHMgbW9kZWwKZ3JvdXBfY29lZmZzX2xtZXIgPC0gYXMuZGF0YS5mcmFtZShyYW5lZihtb2RlbDJfbG1lcl90b3AyNXBsdXMpKSAlPiUKICByZW5hbWUobG1lcl9jb2VmZiA9IGNvbmR2YWwpCgojIEV4dHJhY3QgYW5kIGNsZWFuIHRoZSBncm91cCBjb2VmZmljaWVudHMgZnJvbSB0aGUgZ2xtIG1vZGVsCmdyb3VwX2NvZWZmc19nbG0gPC0gYXMuZGF0YS5mcmFtZShtb2RlbDJfZ2xtX3RvcDI1cGx1cyRjb2VmZmljaWVudHMpCm5hbWVzKGdyb3VwX2NvZWZmc19nbG0pIDwtICJDb2VmZmljaWVudCIKCmdyb3VwX2NvZWZmc19nbG0gPC0gZ3JvdXBfY29lZmZzX2dsbSAlPiUgCiAgcm93bmFtZXNfdG9fY29sdW1uKCJncnAiKSAlPiUgCiAgZmlsdGVyKGdycCAhPSBjKCcoSW50ZXJjZXB0KScsICdZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRScpKSAlPiUKICBtdXRhdGUoZ3JwID0gZ3N1YigiSk9CX0ZBQ1RPUiIsICIiLCBncnApKQoKZ3JvdXBfY29lZmZzX2dsbSA8LSByYmluZChncm91cF9jb2VmZnNfZ2xtLCBsaXN0KCJTdGF0ZSBQYXRyb2wgVHJvb3BlciIsIDAuMCkpCgpncm91cF9jb2VmZnNfZ2xtIDwtIGdyb3VwX2NvZWZmc19nbG0gJT4lCiAgbXV0YXRlKGdsbV9jb2VmZiA9IENvZWZmaWNpZW50IC0gbWVhbihDb2VmZmljaWVudCkpICMgY2VudGVyIHRoZSBjb2VmZmljaWVudHMgZnJvbSB0aGUgZ2xtIGF0IHplcm8gKGluc3RlYWQgb2YgYXJvdW5kICJTdGF0ZSBQYXRyb2wgVHJvb3BlciIpLCBzbyB3ZSBjYW4gY29tcGFyZSB0aGVtIHRvIHRoZSBob3cgdGhlIHJhbmRvbSBlZmZlY3RzIGNvZWZmaWNpZW50cyAod2hpY2ggYXJlIHplcm8tY2VudGVyZWQgYnkgZGVmYXVsdCkKYGBgCgpgYGB7ciB2aXpfY29lZmZfc2hyaW5rYWdlLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01LCBmaWcuYXNwPTAuNn0KaW5uZXJfam9pbihncm91cF9jb2VmZnNfbG1lciwgZ3JvdXBfY29lZmZzX2dsbSwgYnk9YygiZ3JwIikpICU+JQogIGdncGxvdCguLCBhZXMoeT1yZW9yZGVyKGdycCwgbG1lcl9jb2VmZiksIGxtZXJfY29lZmYgPSBsbWVyX2NvZWZmLCBnbG1fY29lZmYgPSBnbG1fY29lZmYpKSArCiAgZ2VvbV9wb2ludChhZXMoeD1nbG1fY29lZmYsIGNvbD0iZ2xtX2NvZWZmIikpICsKICBnZW9tX3BvaW50KGFlcyh4PWxtZXJfY29lZmYsIGNvbD0ibG1lcl9jb2VmZiIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWUgPSAiIiwgdmFsdWVzID0gYygiI2ZkYjkyNCIsICIjOGIyMzIzIikpICsKICB4bGFiKCJjb2VmZmljaWVudHMiKSArCiAgeWxhYigiam9iIGNsYXNzIikKYGBgCgoKIyMgUnVuIGEgQmF5ZXNpYW4gdmFyeWluZyBpbnRlcmNlcHRzIG1vZGVsIChgcmV0aGlua2luZ2ApCgpPa2F5LCBub3cgaXQncyB0aW1lIHRvIGdldCBCYXllc2lhbi4gIExldCdzIHJldHVybiB0byB0aGUgb3JpZ2luYWwgZGF0YXNldC0tdGhlIHRvcCAyNSBqb2IgY2xhc3Nlcy0tYW5kIHdlJ2xsIHRhY2tsZSB0aGUgZXhhY3Qgc2FtZSB2YXJ5aW5nIGludGVyY2VwdHMgbW9kZWxpbmcgdGFzayBhcyBhYm92ZS4gIFRoZSBvbmx5IGRpZmZlcmVuY2UgdGhpcyB0aW1lIGlzIHRoYXQgd2UgZ2V0IHRvIHNldCBzb21lIHByaW9ycyEgIFRvIGNyZWF0ZSBhIHZhcnlpbmcgaW50ZXJjZXB0cyBmb3JtdWxhLCB3ZSBuZWVkIHRvIGFkZCBhIHRlcm0tLWBhW0pPQl9JTkRFWF1gLS10byByZXByZXNlbnQgdGhlc2UgaW50ZXJjZXB0cywgd2hpY2ggYXJlIGFsbG93ZWQgdG8gdmFyeSBiYXNlZCBvbiB0aGUgSk9CX0lOREVYIHZhbHVlLiAgV2UgYWxzbyBhZGQgb25lIGFkZGl0aW9uYWwgcHJpb3IsIGBhX3NpZ21hYCwgdG8gcmVwcmVzZW50IG91ciBleHBlY3RhdGlvbnMgYWJvdXQgdGhlIGRpc3RyaWJ1dGlvbiBmb3IgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgbWVhbiBob3VybHkgc3RhcnRpbmcgc2FsYXJ5IGFjcm9zcyBncm91cHMuCgpOb3RlIHRoYXQgd2UncmUgc3dpdGNoaW5nIHRvIHVzaW5nIHRoZSBgSk9CX0lOREVYYCB2ZXJzaW9uIG9mIHRoZSB2YXJpYWJsZSwgcmF0aGVyIHRoYW4gdGhlIGBKT0JfRkFDVE9SYCB2ZXJzaW9uLiAgVGhpcyBpcyBiZWNhdXNlIGByZXRoaW5raW5nYCBzdWdnZXN0cyB0aGF0IGluZGV4IHZhcmlhYmxlcyBhcmUgcHJlZmVyYWJsZSB0byBmYWN0b3JzIGZvciBpdHMgQmF5ZXNpYW4gZml0dGluZyBhcHByb2FjaCwgYmVjYXVzZSBhbiBpbmRleCB2YXJpYWJsZSBhbGxvd3MgYWxsIGRpc3RpbmN0IGdyb3VwcyBwcmVzZW50IGluIHRoZSB2YXJpYWJsZSB0byBiZSBhc3NpZ25lZCBhbiBleHBsaWNpdCBwcmlvciAoTWNFbHJlYXRoLCBwLiAxNTUpLgoKYGBge3IgbW9kZWxfdmFyX2ludF9iYXllc30KZml0X21vZGVsMl9iYXllcyA8LSBmdW5jdGlvbigpewogIAogICMgUHJlcCB0aGUgZGF0YSBjb2x1bW5zIHdlIHdhbnQgdG8gcGFzcyB0aGUgdGhlIG1vZGVsCiAgZGF0X2xpc3QgPC0gbGlzdCgKICAgIENPTVBfUkFURV9TVE5EX0hPVVJMWSA9IGhyXzIwMTlfdG9wXzI1X2pvYl9jbGFzc2VzJENPTVBfUkFURV9TVE5EX0hPVVJMWSwKICAgIEpPQl9JTkRFWCA9IGhyXzIwMTlfdG9wXzI1X2pvYl9jbGFzc2VzJEpPQl9JTkRFWCwKICAgIFlSU19TSU5DRV9PUklHSU5BTF9ISVJFID0gaHJfMjAxOV90b3BfMjVfam9iX2NsYXNzZXMkWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUKICApCiAgCiAgdGljKCJSdW5uaW5nIG1vZGVsIDIgQmF5ZXNpYW4iKQogIG1vZGVsMl9iYXllcyA8LSB1bGFtKAogICAgYWxpc3QoCiAgICAgIENPTVBfUkFURV9TVE5EX0hPVVJMWSB+IGRub3JtKCBtdSAsIHNpZ21hICksCiAgICAgIG11IDwtIEludGVyY2VwdCArIGFbSk9CX0lOREVYXSArIGJfWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUqWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUsCiAgICAgIAogICAgICBJbnRlcmNlcHQgfiBkbm9ybSgzMCwgMTApLAogICAgICBzaWdtYSB+IGRjYXVjaHkoMCwgMjApLAogICAgICAKICAgICAgIyBmaXhlZCBlZmZlY3RzIHByaW9ycwogICAgICBiX1lSU19TSU5DRV9PUklHSU5BTF9ISVJFIH4gZG5vcm0oMC42NzUsIDEuNSksCiAgICAgIAogICAgICAjIG11bHRpbGV2ZWwgYWRhcHRpdmUgcHJpb3JzCiAgICAgIGFbSk9CX0lOREVYXSB+IGRub3JtKDAsIGFfc2lnbWEpLCAjIHByaW9yIGZvciBtZWFuIGhvdXJseSBzdGFydGluZyBzYWxhcnkgb2YgZWFjaCBncm91cCAoYWZ0ZXIgYWNjb3VudGluZyBmb3IgdGhlICJncmFuZCBpbnRlcmNlcHQiIGZvciB0aGUgcG9wdWxhdGlvbiBhcyBhIHdob2xlKTogYSBub3JtYWwgZGlzdHJpYnV0aW9uIGNlbnRlcmVkIGFyb3VuZCAwCiAgICAgIAogICAgICAjIGh5cGVyLXByaW9ycwogICAgICBhX3NpZ21hIH4gZGNhdWNoeSgwLCA1MCkgIyBwcmlvciBmb3IgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgbWVhbiBob3VybHkgc3RhcnRpbmcgc2FsYXJ5IGFjcm9zcyBncm91cHMKICAgICksCiAgICBkYXRhID0gZGF0X2xpc3QsIAogICAgY2hhaW5zID0gMSwKICAgIGxvZ19saWsgPSBUUlVFCiAgKQogIAogIHRvYygpICMgc3RvcCB0aGUgdGltZXIKICBiZWVwcjo6YmVlcCgpICMgZW1pdCBhIGJlZXAgc28gd2UgY2FuIGNvbWUgYmFjayBmcm9tIHdoYXRldmVyIGVsc2Ugd2Ugd2VyZSBkb2luZwogIAogIHNhdmVSRFMobW9kZWwyX2JheWVzLCAiLi9tb2RlbHMvbW9kZWwyX2JheWVzLnJkcyIpCiAgcmV0dXJuKG1vZGVsMl9iYXllcykKfQoKIyBJZiB3ZSBoYXZlIGFscmVhZHkgZml0IHRoZSBtb2RlbCBhbmQgaGF2ZSBzYXZlZCB0aGUgcmVzdWx0cyB0byBkaXNrLCBkbyBub3QgcmUtZml0IGl0ISAoVGhpcyB0YWtlcyBhIGxvbmcgdGltZS4pCiMgU2ltcGx5IGxvYWQgdGhlIG1vZGVsIGZyb20gZGlzay4uLgppZihmaWxlLmV4aXN0cygiLi9tb2RlbHMvbW9kZWwyX2JheWVzLnJkcyIpKSB7CiAgY2F0KCJNb2RlbCBoYXMgYWxyZWFkeSBiZWVuIGZpdCEgIExvYWRpbmcgcmVzdWx0cyBmcm9tIGRpc2suIikKICBtb2RlbDJfYmF5ZXMgPC0gcmVhZFJEUygiLi9tb2RlbHMvbW9kZWwyX2JheWVzLnJkcyIpCn0gZWxzZSB7IAogICMgSWYgd2UgaGF2ZSBub3QgeWV0IGZpdCB0aGUgbW9kZWwsIGdvIGFoZWFkIGFuZCBmaXQgaXQgYW5kIHNhdmUgdGhlIHJlc3VsdHMgdG8gZGlzawogIGNhdCgiTW9kZWwgaGFzIG5vdCB5ZXQgYmVlbiBmaXQuIEZpdHRpbmcgbW9kZWwgbm93LiBQbGVhc2UgYmUgcGF0aWVudC4iKQogIG1vZGVsMl9iYXllcyA8LSBmaXRfbW9kZWwyX2JheWVzKCkKfQpgYGAKCldlIGNhbiB2aWV3IHRoZSBtb2RlbCByZXN1bHRzOgoKYGBge3J9CnByZWNpcyhtb2RlbDJfYmF5ZXMsIGRlcHRoPTIpCnBsb3QocHJlY2lzKG1vZGVsMl9iYXllcywgZGVwdGg9MiksIG1haW49IkFsbCBQb3N0ZXJpb3IgUGFyYW1zIikKcGxvdChwcmVjaXMobW9kZWwyX2JheWVzLCBkZXB0aD0yLCBwYXJzPSJhIiksIGxhYmVscz1KT0JfTEFCRUxTLCBtYWluPSJKb2IgSW50ZXJjZXB0cyIpCmBgYAoKTm93LCBmb3IgdGhlIHNha2Ugb2YgaWxsdXN0cmF0aW9uLCBsZXQncyBydW4gdGhlIHNhbWUgbW9kZWwgYWdhaW4tLXRoaXMgdGltZSB3aXRoIGEgbW9yZSBleHRyZW1lIHByaW9yIGZvciB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIGFjcm9zcyBqb2IgY2xhc3NlczoKCmBgYHtyIG1vZGVsX3Zhcl9pbnRfYmF5ZXNfZXh0cmVtZV9wcmlvcnN9CmZpdF9tb2RlbDJfYmF5ZXNfZXh0cmVtZV9wcmlvcnMgPC0gZnVuY3Rpb24oKSB7CgogICMgUHJlcCB0aGUgZGF0YSBjb2x1bW5zIHdlIHdhbnQgdG8gcGFzcyB0aGUgdGhlIG1vZGVsCiAgZGF0X2xpc3QgPC0gbGlzdCgKICAgIENPTVBfUkFURV9TVE5EX0hPVVJMWSA9IGhyXzIwMTlfdG9wXzI1X2pvYl9jbGFzc2VzJENPTVBfUkFURV9TVE5EX0hPVVJMWSwKICAgIEpPQl9JTkRFWCA9IGhyXzIwMTlfdG9wXzI1X2pvYl9jbGFzc2VzJEpPQl9JTkRFWCwKICAgIFlSU19TSU5DRV9PUklHSU5BTF9ISVJFID0gaHJfMjAxOV90b3BfMjVfam9iX2NsYXNzZXMkWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUKICApCiAgCiAgdGljKCJSdW5uaW5nIG1vZGVsIDIgQmF5ZXNpYW4gdy8gZXh0cmVtZSBwcmlvcnMiKQogIG1vZGVsMl9iYXllc19leHRyZW1lX3ByaW9ycyA8LSB1bGFtKAogICAgYWxpc3QoCiAgICAgIENPTVBfUkFURV9TVE5EX0hPVVJMWSB+IGRub3JtKCBtdSAsIHNpZ21hICksCiAgICAgIG11IDwtIEludGVyY2VwdCArIGFbSk9CX0lOREVYXSArIGJfWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUqWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUsCiAgICAgIAogICAgICBJbnRlcmNlcHQgfiBkbm9ybSgzMCwgMTApLAogICAgICBzaWdtYSB+IGRjYXVjaHkoMCwgMzApLAogICAgICAKICAgICAgIyBmaXhlZCBlZmZlY3RzIHByaW9ycwogICAgICBiX1lSU19TSU5DRV9PUklHSU5BTF9ISVJFIH4gZG5vcm0oMC42NzUsIDEuNSksCiAgICAgIAogICAgICAjIG11bHRpbGV2ZWwgYWRhcHRpdmUgcHJpb3JzCiAgICAgIGFbSk9CX0lOREVYXSB+IGRub3JtKDAsIGFfc2lnbWEpLAogICAgICAKICAgICAgIyBoeXBlci1wcmlvcnMKICAgICAgYV9zaWdtYSB+IGRjYXVjaHkoMCwgMC4wMDAwMDAwMDAwMDAwMDAwMSkgIyA8LS0gbWFrZSB0aGlzIHByaW9yIG1vcmUgZXh0cmVtZQogICAgKSwKICAgIGRhdGEgPSBkYXRfbGlzdCwgCiAgICBjaGFpbnMgPSAxLAogICAgbG9nX2xpayA9IFRSVUUKICApCiAgCiAgdG9jKCkgIyBzdG9wIHRoZSB0aW1lcgogIGJlZXByOjpiZWVwKCkgIyBlbWl0IGEgYmVlcCBzbyB3ZSBjYW4gY29tZSBiYWNrIGZyb20gd2hhdGV2ZXIgZWxzZSB3ZSB3ZXJlIGRvaW5nCiAgCiAgc2F2ZVJEUyhtb2RlbDJfYmF5ZXNfZXh0cmVtZV9wcmlvcnMsICIuL21vZGVscy9tb2RlbDJfYmF5ZXNfZXh0cmVtZV9wcmlvcnMucmRzIikKICByZXR1cm4obW9kZWwyX2JheWVzX2V4dHJlbWVfcHJpb3JzKQp9CiAgCmlmKGZpbGUuZXhpc3RzKCIuL21vZGVscy9tb2RlbDJfYmF5ZXNfZXh0cmVtZV9wcmlvcnMucmRzIikpIHsKICBjYXQoIk1vZGVsIGhhcyBhbHJlYWR5IGJlZW4gZml0ISAgTG9hZGluZyByZXN1bHRzIGZyb20gZGlzay4iKQogIG1vZGVsMl9iYXllc19leHRyZW1lX3ByaW9ycyA8LSByZWFkUkRTKCIuL21vZGVscy9tb2RlbDJfYmF5ZXNfZXh0cmVtZV9wcmlvcnMucmRzIikKfSBlbHNlIHsgCiAgY2F0KCJNb2RlbCBoYXMgbm90IHlldCBiZWVuIGZpdC4gRml0dGluZyBtb2RlbCBub3cuIFBsZWFzZSBiZSBwYXRpZW50LiIpCiAgbW9kZWwyX2JheWVzX2V4dHJlbWVfcHJpb3JzIDwtIGZpdF9tb2RlbDJfYmF5ZXNfZXh0cmVtZV9wcmlvcnMoKQp9CmBgYAoKQW5kIHZpZXcgdGhlIG1vZGVsIHJlc3VsdHM6CgpgYGB7cn0KcHJlY2lzKG1vZGVsMl9iYXllc19leHRyZW1lX3ByaW9ycywgZGVwdGg9MikKcGxvdChwcmVjaXMobW9kZWwyX2JheWVzX2V4dHJlbWVfcHJpb3JzLCBkZXB0aD0yKSwgbWFpbj0iQWxsIFBvc3RlcmlvciBQYXJhbXMiKQpwbG90KHByZWNpcyhtb2RlbDJfYmF5ZXNfZXh0cmVtZV9wcmlvcnMsIGRlcHRoPTIsIHBhcnM9ImEiKSwgbGFiZWxzPUpPQl9MQUJFTFMsIG1haW49IkpvYiBJbnRlcmNlcHRzIikKYGBgCgoKIyMjIENvbXBhcmUgdGhlIEJheWVzaWFuIG1vZGVscwoKWW91J2xsIG5vdGljZSB0aGF0IHRoZSB0d28gQmF5ZXNpYW4gbW9kZWxzIGhlcmUgaGF2ZSBkaWZmZXJlbnQgcFdBSUMgdmFsdWVzLiAgVGhpcyBpcyBiZWNhdXNlIHRoZSBwV0FJQyB2YWx1ZSB2YWx1ZSBhbHNvIHRha2VzIGludG8gYWNjb3VudCB0aGUgcmVndWxhcml6aW5nIHdvcmsgcGVyZm9ybWVkIGJ5IHRoZSBwcmlvciB0byBzaHJpbmsgYWxsIG9mIG91ciBncm91cCBpbnRlcmNlcHRzIHRvd2FyZHMgdGhlIG1lYW4gKE1jRWxyZWF0aCwgcC4gNDA0KS4gIE1vcmUgcmVndWxhcml6YXRpb24gcmVzdWx0cyBpbiBhIGxvd2VyIGVmZmVjdGl2ZSBwYXJhbWV0ZXIgdmFsdWUuICBJbiB0aGUgbW9kZWwgd2l0aCBleHRyZW1lIHByaW9ycywgd2Ugc29tZXdoYXQgdGh3YXJ0ZWQgdGhlIHJlZ3VsYXJpemluZyBhYmlsaXRpZXMgb2YgdGhlIG1vZGVsLiAgVGhlIGludGVyY2VwdHMgd2VyZW4ndCBhYmxlIHRvIGJlIHNocnVuayBhcyBtdWNoIHRvd2FyZCB0aGUgbWVhbiwgYW5kIGFzIGEgcmVzdWx0LCB0aGUgbnVtYmVyIG9mIGVmZmVjdGl2ZSBwYXJhbWV0ZXJzIGluY3JlYXNlZCBzbGlnaHRseS4KCmBgYHtyIGNvbXBhcmVfYmF5ZXNfbW9kZWxzfQpjb21wYXJlKG1vZGVsMl9iYXllcywgbW9kZWwyX2JheWVzX2V4dHJlbWVfcHJpb3JzKQpgYGAKCgo=