Setup
library(knitr)
opts_chunk$set(comment = NA,
message = FALSE,
warning = FALSE)
options(max.print="250")
opts_knit$set(width=75)
library(skimr)
library(tableone)
library(broom)
library(Epi)
library(survival)
library(Matching)
library(cobalt)
library(lme4)
library(twang)
library(survey)
library(rbounds)
library(tidyverse)
## Note that we will also use the broom.mixed package
## but we won't load it here
decim <- function(x, k) format(round(x, k), nsmall=k)
theme_set(theme_bw())
The Data Set
The Data Set is 100% fictional, and is available as toy.csv
on the course website.
- It contains data on 400 subjects (140 treated and 260 controls) on treatment status, six covariates, and three outcomes, with no missing observations anywhere.
- We assume that a logical argument suggests that the square of
covA
, as well as the interactions of covB
with covC
and with covD
should be related to treatment assignment, and thus should be included in our propensity model.
- Our objective is to estimate the average causal effect of treatment (as compared to control) on each of the three outcomes, without propensity adjustment, and then with propensity matching, subclassification, weighting and regression adjustment using the propensity score.
toy <- read_csv("data/toy.csv") %>%
type.convert(as.is = FALSE) %>%
mutate(subject = as.character(subject))
toy
# A tibble: 400 x 11
subject treated covA covB covC covD covE covF out1.cost out2.event
<chr> <int> <dbl> <int> <dbl> <dbl> <int> <fct> <int> <fct>
1 T_001 0 4.13 0 10 8.2 14 2-Middle 34 No
2 T_002 1 4.58 1 8.69 10.1 13 1-Low 63 No
3 T_003 0 1.28 0 11.8 5.6 14 1-Low 61 No
4 T_004 0 3.11 0 10.9 10.9 10 1-Low 34 No
5 T_005 1 3.31 0 10.5 9 14 3-High 38 No
6 T_006 0 4.08 0 13.9 10 5 3-High 51 No
7 T_007 0 3.86 1 13 6.1 10 3-High 53 Yes
8 T_008 0 2.58 0 12.6 5.2 4 2-Middle 53 Yes
9 T_009 0 3.46 0 10.1 8.8 10 1-Low 61 Yes
10 T_010 0 3.11 1 13.3 4.8 10 1-Low 28 No
# ... with 390 more rows, and 1 more variable: out3.time <int>
The Codebook for the toy
data
toy.codebook <- base::data.frame(
Variable = dput(names(toy)),
Type = c("Subject ID", "2-level categorical (0/1)", "Quantitative (2 decimal places)",
"2-level categorical (0/1)", "Quantitative (1 decimal place)",
"Quantitative (1 decimal place)", "Integer",
"3-level ordinal factor", "Quantitative outcome",
"Binary outcome (did event occur?)", "Time to event outcome"),
Notes = c("labels are T_001 to T_400", "0 = control, 1 = treated",
"reasonable values range from 0 to 6", "0 = no, 1 = yes",
"plausible range 3-20", "plausible range 3-20", "plausible range 3-20",
"1 = Low, 2 = Middle, 3 = High",
"typical values 10-100", "Yes/No (note: event is bad)",
"Time before event is observed or subject exits study (censored), range is 76-154 weeks"))
c("subject", "treated", "covA", "covB", "covC", "covD", "covE",
"covF", "out1.cost", "out2.event", "out3.time")
toy.codebook
Variable Type
1 subject Subject ID
2 treated 2-level categorical (0/1)
3 covA Quantitative (2 decimal places)
4 covB 2-level categorical (0/1)
5 covC Quantitative (1 decimal place)
6 covD Quantitative (1 decimal place)
7 covE Integer
8 covF 3-level ordinal factor
9 out1.cost Quantitative outcome
10 out2.event Binary outcome (did event occur?)
11 out3.time Time to event outcome
Notes
1 labels are T_001 to T_400
2 0 = control, 1 = treated
3 reasonable values range from 0 to 6
4 0 = no, 1 = yes
5 plausible range 3-20
6 plausible range 3-20
7 plausible range 3-20
8 1 = Low, 2 = Middle, 3 = High
9 typical values 10-100
10 Yes/No (note: event is bad)
11 Time before event is observed or subject exits study (censored), range is 76-154 weeks
With regard to the out3.time
variable, subjects with out2.event
= No were censored, so that out2.event
= Yes indicates an observed event.
“Skimmed” Summaries, within treatment groups
toy %>% group_by(treated) %>% skim_without_charts(-subject)
Data summary
Name |
Piped data |
Number of rows |
400 |
Number of columns |
11 |
_______________________ |
|
Column type frequency: |
|
factor |
2 |
numeric |
7 |
________________________ |
|
Group variables |
treated |
Variable type: factor
covF |
0 |
0 |
1 |
FALSE |
3 |
1-L: 118, 2-M: 98, 3-H: 44 |
covF |
1 |
0 |
1 |
FALSE |
3 |
2-M: 54, 3-H: 48, 1-L: 38 |
out2.event |
0 |
0 |
1 |
FALSE |
2 |
No: 154, Yes: 106 |
out2.event |
1 |
0 |
1 |
FALSE |
2 |
Yes: 82, No: 58 |
Variable type: numeric
covA |
0 |
0 |
1 |
3.00 |
1.09 |
0.20 |
2.51 |
3.08 |
3.84 |
5.35 |
covA |
1 |
0 |
1 |
3.16 |
1.14 |
0.65 |
2.45 |
3.29 |
4.16 |
5.05 |
covB |
0 |
0 |
1 |
0.30 |
0.46 |
0.00 |
0.00 |
0.00 |
1.00 |
1.00 |
covB |
1 |
0 |
1 |
0.51 |
0.50 |
0.00 |
0.00 |
1.00 |
1.00 |
1.00 |
covC |
0 |
0 |
1 |
10.60 |
2.05 |
5.56 |
9.24 |
10.60 |
12.33 |
14.44 |
covC |
1 |
0 |
1 |
9.62 |
1.87 |
5.96 |
8.17 |
9.58 |
10.80 |
13.94 |
covD |
0 |
0 |
1 |
8.65 |
2.21 |
2.80 |
7.20 |
9.05 |
10.30 |
12.80 |
covD |
1 |
0 |
1 |
9.16 |
2.08 |
3.20 |
7.65 |
9.35 |
10.80 |
14.50 |
covE |
0 |
0 |
1 |
11.30 |
3.42 |
4.00 |
9.00 |
11.00 |
13.25 |
19.00 |
covE |
1 |
0 |
1 |
9.77 |
2.84 |
4.00 |
8.00 |
9.00 |
12.00 |
16.00 |
out1.cost |
0 |
0 |
1 |
47.01 |
12.39 |
20.00 |
38.00 |
47.00 |
54.00 |
84.00 |
out1.cost |
1 |
0 |
1 |
56.64 |
16.56 |
20.00 |
45.00 |
56.50 |
72.25 |
84.00 |
out3.time |
0 |
0 |
1 |
109.85 |
12.61 |
79.00 |
101.00 |
110.00 |
118.25 |
154.00 |
out3.time |
1 |
0 |
1 |
102.71 |
11.99 |
76.00 |
95.00 |
101.00 |
110.00 |
136.00 |
Table 1
factorlist <- c("covB", "covF", "out2.event")
CreateTableOne(data = toy,
vars = dput(names(select(toy, -subject, -treated))),
strata = "treated", factorVars = factorlist)
c("covA", "covB", "covC", "covD", "covE", "covF", "out1.cost",
"out2.event", "out3.time")
Stratified by treated
0 1 p test
n 260 140
covA (mean (SD)) 3.00 (1.09) 3.16 (1.14) 0.170
covB = 1 (%) 77 (29.6) 72 (51.4) <0.001
covC (mean (SD)) 10.60 (2.05) 9.62 (1.87) <0.001
covD (mean (SD)) 8.65 (2.21) 9.16 (2.08) 0.025
covE (mean (SD)) 11.30 (3.42) 9.77 (2.84) <0.001
covF (%) <0.001
1-Low 118 (45.4) 38 (27.1)
2-Middle 98 (37.7) 54 (38.6)
3-High 44 (16.9) 48 (34.3)
out1.cost (mean (SD)) 47.01 (12.39) 56.64 (16.56) <0.001
out2.event = Yes (%) 106 (40.8) 82 (58.6) 0.001
out3.time (mean (SD)) 109.85 (12.61) 102.71 (11.99) <0.001
Data Management and Cleanup
Range Checks for Quantitative (continuous) Variables
Checking and cleaning the quantitative variables is pretty straightforward - the main thing I’ll do at this stage is check the ranges of values shown to ensure that they match up with what I’m expecting. Here, all of the quantitative variables have values that fall within the “permissible” range described by my codebook, so we’ll assume that for the moment, we’re OK on subject
(just a meaningless code, really), covA
, covC
, covD
, covE
, out1.cost
and out3.time
, and we see no missingness.
Restating Categorical Information in Helpful Ways
The cleanup of the toy data focuses, as it usually does, on variables that contain categories of information, rather than simple counts or measures, represented in quantitative variables.
Re-expressing Binary Variables as Numbers and Factors
We have three binary variables (treated
, covB
and out2.event
). A major issue in developing these variables is to ensure that the direction of resulting odds ratios and risk differences are consistent and that cross-tabulations are in standard epidemiological format.
It will be useful to define binary variables in two ways:
- as a numeric indicator variable taking on the values 0 (meaning “not having the characteristic being studied”) or 1 (meaning “having the characteristic being studied”)
- as a text factor - with the levels of our key exposure and outcomes arranged so that “having the characteristic” precedes “not having the characteristic” in R when you create a table, but the covariates should still be No/Yes.
So what do we currently have? From the output below, it looks like treated
and covB
are numeric, 0/1 variables, while out2.event
is a factor with levels “No” and then “Yes”
toy %>% select(treated, covB, out2.event) %>% summary()
treated covB out2.event
Min. :0.00 Min. :0.0000 No :212
1st Qu.:0.00 1st Qu.:0.0000 Yes:188
Median :0.00 Median :0.0000
Mean :0.35 Mean :0.3725
3rd Qu.:1.00 3rd Qu.:1.0000
Max. :1.00 Max. :1.0000
So, we’ll create factors for treated
and covB
:
toy$treated_f <- factor(toy$treated, levels = c(1,0),
labels = c("Treated", "Control"))
toy$covB_f <- factor(toy$covB, levels = c(0,1),
labels = c("No B", "Has B"))
For out2.event
, on the other hand, we don’t have either quite the way we might want it. As you see in the summary output, we have two codes for out2.event
- either No or Yes, in that order. But we want Yes to precede No (and I’d like a more meaningful name). So I redefine the factor variable, as follows.
toy$out2_f <- factor(toy$out2.event, levels = c("Yes","No"),
labels = c("Event","No Event"))
To obtain a numerical (0 or 1) version of out2.event
we can use R’s as.numeric
function - the problem is that this produces values of 1 (for No) and 2 (for Yes), rather than 0 and 1. So, I simply subtract 1 from the result, and we get what we need.
toy$out2 <- as.numeric(toy$out2.event) - 1
Testing Your Code - Sanity Checks
Before I move on, I’ll do a series of sanity checks to make sure that our new variables are defined as we want them, by producing a series of small tables comparing the new variables to those originally included in the data set.
toy %>% count(treated, treated_f)
# A tibble: 2 x 3
treated treated_f n
<int> <fct> <int>
1 0 Control 260
2 1 Treated 140
toy %>% count(covB, covB_f)
# A tibble: 2 x 3
covB covB_f n
<int> <fct> <int>
1 0 No B 251
2 1 Has B 149
toy %>% count(out2.event, out2_f, out2)
# A tibble: 2 x 4
out2.event out2_f out2 n
<fct> <fct> <dbl> <int>
1 No No Event 0 212
2 Yes Event 1 188
Everything looks OK:
treated_f
correctly captures the information in treated
, with the label Treated above the label Control in the rows of the table, facilitating standard epidemiological format.
covB_f
also correctly captures the covB
information, placing “Has B” last.
out2_f
correctly captures and re-orders the labels from the original out2.event
out2
shows the data correctly (as compared to the original out2.event
) with 0-1 coding.
Dealing with Variables including More than Two Categories
When we have a multi-categorical (more than two categories) variable, like covF
, we will want to have
- both a text version of the variable with sensibly ordered levels, as a factor in R, as well as
- a series of numeric indicator variables (taking the values 0 or 1) for the individual levels.
toy %>% count(covF)
# A tibble: 3 x 2
covF n
<fct> <int>
1 1-Low 156
2 2-Middle 152
3 3-High 92
From the summary
output, we can see that we’re all set for the text version of covF
, as what we have currently is a factor with three levels, labeled 1-Low, 2-Middle and 3-High. This list of variables should work out well for us, as it preserves the ordering in a table and permits us to see the names, too. If we’d used just Low, Middle and High, then when R sorted a table into alphabetical order, we’d have High, then Low, then Middle - not ideal.
Preparing Indicator Variables for covF
So, all we need to do for covF
is prepare indicator variables. We can either do this for all levels, or select one as the baseline, and do the rest. Here, I’ll show them all.
toy <- toy %>%
mutate(covF.Low = as.numeric(covF == "1-Low"),
covF.Middle = as.numeric(covF == "2-Middle"),
covF.High = as.numeric(covF == "3-High"))
And now, some more sanity checks for the covF
information:
toy %>% count(covF, covF.High, covF.Middle, covF.Low)
# A tibble: 3 x 5
covF covF.High covF.Middle covF.Low n
<fct> <dbl> <dbl> <dbl> <int>
1 1-Low 0 0 1 156
2 2-Middle 0 1 0 152
3 3-High 1 0 0 92
Data Set After Cleaning
Skim, within Treatment Groups
toy %>% select(treated_f, covA, covB, covC, covD, covE,
covF, Asqr, BC, BD, out1.cost, out2, out3.time) %>%
group_by(treated_f) %>%
skim_without_charts()
Data summary
Name |
Piped data |
Number of rows |
400 |
Number of columns |
13 |
_______________________ |
|
Column type frequency: |
|
factor |
1 |
numeric |
11 |
________________________ |
|
Group variables |
treated_f |
Variable type: factor
covF |
Treated |
0 |
1 |
FALSE |
3 |
2-M: 54, 3-H: 48, 1-L: 38 |
covF |
Control |
0 |
1 |
FALSE |
3 |
1-L: 118, 2-M: 98, 3-H: 44 |
Variable type: numeric
covA |
Treated |
0 |
1 |
3.16 |
1.14 |
0.65 |
2.45 |
3.29 |
4.16 |
5.05 |
covA |
Control |
0 |
1 |
3.00 |
1.09 |
0.20 |
2.51 |
3.08 |
3.84 |
5.35 |
covB |
Treated |
0 |
1 |
0.51 |
0.50 |
0.00 |
0.00 |
1.00 |
1.00 |
1.00 |
covB |
Control |
0 |
1 |
0.30 |
0.46 |
0.00 |
0.00 |
0.00 |
1.00 |
1.00 |
covC |
Treated |
0 |
1 |
9.62 |
1.87 |
5.96 |
8.17 |
9.58 |
10.80 |
13.94 |
covC |
Control |
0 |
1 |
10.60 |
2.05 |
5.56 |
9.24 |
10.60 |
12.33 |
14.44 |
covD |
Treated |
0 |
1 |
9.16 |
2.08 |
3.20 |
7.65 |
9.35 |
10.80 |
14.50 |
covD |
Control |
0 |
1 |
8.65 |
2.21 |
2.80 |
7.20 |
9.05 |
10.30 |
12.80 |
covE |
Treated |
0 |
1 |
9.77 |
2.84 |
4.00 |
8.00 |
9.00 |
12.00 |
16.00 |
covE |
Control |
0 |
1 |
11.30 |
3.42 |
4.00 |
9.00 |
11.00 |
13.25 |
19.00 |
Asqr |
Treated |
0 |
1 |
11.30 |
6.74 |
0.42 |
6.00 |
10.82 |
17.26 |
25.50 |
Asqr |
Control |
0 |
1 |
10.22 |
6.01 |
0.04 |
6.30 |
9.49 |
14.78 |
28.62 |
BC |
Treated |
0 |
1 |
4.95 |
5.02 |
0.00 |
0.00 |
6.43 |
9.69 |
13.70 |
BC |
Control |
0 |
1 |
2.99 |
4.78 |
0.00 |
0.00 |
0.00 |
7.38 |
14.24 |
BD |
Treated |
0 |
1 |
4.52 |
4.66 |
0.00 |
0.00 |
4.25 |
9.20 |
12.20 |
BD |
Control |
0 |
1 |
2.44 |
3.93 |
0.00 |
0.00 |
0.00 |
6.10 |
12.50 |
out1.cost |
Treated |
0 |
1 |
56.64 |
16.56 |
20.00 |
45.00 |
56.50 |
72.25 |
84.00 |
out1.cost |
Control |
0 |
1 |
47.01 |
12.39 |
20.00 |
38.00 |
47.00 |
54.00 |
84.00 |
out2 |
Treated |
0 |
1 |
0.59 |
0.49 |
0.00 |
0.00 |
1.00 |
1.00 |
1.00 |
out2 |
Control |
0 |
1 |
0.41 |
0.49 |
0.00 |
0.00 |
0.00 |
1.00 |
1.00 |
out3.time |
Treated |
0 |
1 |
102.71 |
11.99 |
76.00 |
95.00 |
101.00 |
110.00 |
136.00 |
out3.time |
Control |
0 |
1 |
109.85 |
12.61 |
79.00 |
101.00 |
110.00 |
118.25 |
154.00 |
Table 1
Note that the factors I created for the out2
outcome are not well ordered for a Table 1, but are well ordered for other tables we’ll fit later. So, in this case, I’ll use the numeric version of the out2
outcome, but the new factor representations of covB
and treated
.
varlist = c("covA", "covB_f", "covC", "covD", "covE", "covF",
"Asqr", "BC", "BD", "out1.cost", "out2", "out3.time")
factorlist = c("covB_f", "covF", "out2")
CreateTableOne(vars = varlist, strata = "treated_f",
data = toy, factorVars = factorlist)
Stratified by treated_f
Treated Control p test
n 140 260
covA (mean (SD)) 3.16 (1.14) 3.00 (1.09) 0.170
covB_f = Has B (%) 72 (51.4) 77 (29.6) <0.001
covC (mean (SD)) 9.62 (1.87) 10.60 (2.05) <0.001
covD (mean (SD)) 9.16 (2.08) 8.65 (2.21) 0.025
covE (mean (SD)) 9.77 (2.84) 11.30 (3.42) <0.001
covF (%) <0.001
1-Low 38 (27.1) 118 (45.4)
2-Middle 54 (38.6) 98 (37.7)
3-High 48 (34.3) 44 (16.9)
Asqr (mean (SD)) 11.30 (6.74) 10.22 (6.01) 0.101
BC (mean (SD)) 4.95 (5.02) 2.99 (4.78) <0.001
BD (mean (SD)) 4.52 (4.66) 2.44 (3.93) <0.001
out1.cost (mean (SD)) 56.64 (16.56) 47.01 (12.39) <0.001
out2 = 1 (%) 82 (58.6) 106 (40.8) 0.001
out3.time (mean (SD)) 102.71 (11.99) 109.85 (12.61) <0.001
The 13 Tasks We’ll Tackle in this Example
- Ignoring the covariate information, what is the unadjusted point estimate (and 95% confidence interval) for the effect of the treatment on each of the three outcomes (
out1.cost
, out2.event
, and out3.time
)?
- Assume that theory suggests that the square of
covA
, as well as the interactions of covB
with covC
and covB
with covD
should be related to treatment assignment. Fit a propensity score model to the data, using the six covariates (A-F) and the three transformations (A2, and the B-C and B-D interactions.) Plot the resulting propensity scores, by treatment group, in an attractive and useful way.
- Use Rubin’s Rules to assess the overlap of the propensity scores and the individual covariates prior to the use of any propensity score adjustments.
- Use 1:1 greedy matching to match all 140 treated subjects to control subjects without replacement on the basis of the linear propensity for treatment. Evaluate the degree of covariate imbalance before and after propensity matching for each of the six covariates, and present the pre- and post-match standardized differences and variance ratios for the covariates, as well as the square term and interactions, as well as both the raw and linear propensity score in appropriate plots. Now, build a new data frame containing the propensity-matched sample, and use it to first check Rubin’s Rules after matching.
- Now, use the matched sample data set to evaluate the treatment’s average causal effect on each of the three outcomes. In each case, specify a point estimate (and associated 95% confidence interval) for the effect of being treated (as compared to being a control subject) on the outcome. Compare your results to the automatic versions reported by the Matching package when you include the outcome in the matching process.
- Now, instead of matching, instead classify the subjects into quintiles by the raw propensity score. Display the balance in terms of standardized differences by quintile for the covariates, their transformations, and the propensity score in an appropriate table or plot(s). Are you satisfied?
- Regardless of your answer to the previous question, use the propensity score quintile subclassification approach to find a point estimate (and 95% confidence interval) for the effect of the treatment on each outcome.
- Now using a reasonable propensity score weighting strategy, assess the balance of each covariate, the transformations and the linear propensity score prior to and after propensity weighting. Is the balance after weighting satisfactory?
- Using propensity score weighting to evaluate the treatment’s effect, developing a point estimate and 95% CI for the average causal effect of treatment on each outcome.
- Finally, use direct adjustment for the linear propensity score on the entire sample to evaluate the treatment’s effect, developing a point estimate and 95% CI for each outcome.
- Now, try a double robust approach. Weight, then adjust for linear propensity score.
- Compare your conclusions about the average causal effect obtained in the following six ways to each other. What happens and why? Which of these methods seems most appropriate given the available information?
- without propensity adjustment,
- after propensity matching,
- after propensity score subclassification,
- after propensity score weighting,
- after adjusting for the propensity score directly, and
- after weighting then adjusting for the PS, to each other.
- Perform a sensitivity analysis for your matched samples analysis and the first outcome (
out1.cost
) if it turns out to show a statistically significant treatment effect.
Task 1. Ignoring covariates, estimate the effect of treatment vs. control on…
Outcome 1 (a continuous outcome)
Our first outcome describes a quantitative measure, cost, and we’re asking what the effect of treatment
as compared to control
is on that outcome. Starting with brief numerical summaries:
toy %>%
group_by(treated_f) %>%
skim_without_charts(out1.cost)
Data summary
Name |
Piped data |
Number of rows |
400 |
Number of columns |
21 |
_______________________ |
|
Column type frequency: |
|
numeric |
1 |
________________________ |
|
Group variables |
treated_f |
Variable type: numeric
out1.cost |
Treated |
0 |
1 |
56.64 |
16.56 |
20 |
45 |
56.5 |
72.25 |
84 |
out1.cost |
Control |
0 |
1 |
47.01 |
12.39 |
20 |
38 |
47.0 |
54.00 |
84 |
It looks like the Treated group has higher costs than the Control group. To model this, we could use a linear regression model to obtain a point estimate and 95% confidence interval. Here, I prefer to use the numeric version of the treated
variable, with 0 = “control” and 1 = “treated”.
unadj.out1 <- lm(out1.cost ~ treated, data=toy)
summary(unadj.out1); confint(unadj.out1, level = 0.95) ## provides treated effect and CI estimates
Call:
lm(formula = out1.cost ~ treated, data = toy)
Residuals:
Min 1Q Median 3Q Max
-36.643 -11.008 -0.008 9.084 36.992
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 47.0077 0.8673 54.202 < 2e-16 ***
treated 9.6352 1.4659 6.573 1.55e-10 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 13.98 on 398 degrees of freedom
Multiple R-squared: 0.09791, Adjusted R-squared: 0.09565
F-statistic: 43.2 on 1 and 398 DF, p-value: 1.553e-10
2.5 % 97.5 %
(Intercept) 45.302702 48.71268
treated 6.753205 12.51713
We can store these results in a data frame, with the tidy
function from the broom
package.
tidy(unadj.out1, conf.int = TRUE, conf.level = 0.95)
# A tibble: 2 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) 47.0 0.867 54.2 7.71e-186 45.3 48.7
2 treated 9.64 1.47 6.57 1.55e- 10 6.75 12.5
res_unadj_1 <- tidy(unadj.out1, conf.int = TRUE, conf.level = 0.95) %>%
filter(term == "treated")
res_unadj_1
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 9.64 1.47 6.57 1.55e-10 6.75 12.5
Our unadjusted treatment effect estimate is a difference of 9.64 in cost, with 95% confidence interval (6.75, 12.52).
Outcome 2 (a binary outcome)
Using a 2x2 table in standard epidemiological format
Thanks to our preliminary cleanup, it’s relatively easy to obtain a table in standard epidemiological format comparing treated to control subjects in terms of out2
:
table(toy$treated_f, toy$out2_f)
Event No Event
Treated 82 58
Control 106 154
Note that the exposure is in the rows, with “Having the Exposure” or “Treated” at the top, and the outcome is in the columns, with “Yes” or “Outcome Occurred” or “Event Occurred” on the left, so that the top left cell count describes people that had both the exposure and the outcome. That’s standard epidemiological format, just what we need for the twoby2
function in the Epi
package.
temp <- twoby2(table(toy$treated_f, toy$out2_f))
2 by 2 table analysis:
------------------------------------------------------
Outcome : Event
Comparing : Treated vs. Control
Event No Event P(Event) 95% conf. interval
Treated 82 58 0.5857 0.5025 0.6643
Control 106 154 0.4077 0.3496 0.4685
95% conf. interval
Relative Risk: 1.4367 1.1737 1.7586
Sample Odds Ratio: 2.0540 1.3530 3.1181
Conditional MLE Odds Ratio: 2.0502 1.3248 3.1884
Probability difference: 0.1780 0.0754 0.2754
Exact P-value: 0.0008
Asymptotic P-value: 0.0007
------------------------------------------------------
Eventually, we will be interested in at least two measures - the odds ratio and the risk (probability) difference estimates, and their respective confidence intervals.
The risk difference is shown as the Probability difference here. Let’s save it to a data frame, and then we’ll save the (sample) odds ratio information to another data frame.
res_unadj_2_riskdiff <- tibble(out = "out2.event",
risk.diff = temp$measures[4,1],
conf.low = temp$measures[4,2],
conf.high = temp$measures[4,3])
res_unadj_2_oddsratio <- tibble(out = "out2.event",
odds.ratio = temp$measures[2,1],
conf.low = temp$measures[2,2],
conf.high = temp$measures[2,3])
res_unadj_2_riskdiff
# A tibble: 1 x 4
out risk.diff conf.low conf.high
<chr> <dbl> <dbl> <dbl>
1 out2.event 0.178 0.0754 0.275
res_unadj_2_oddsratio
# A tibble: 1 x 4
out odds.ratio conf.low conf.high
<chr> <dbl> <dbl> <dbl>
1 out2.event 2.05 1.35 3.12
- For a difference in risk, our unadjusted treatment effect estimate is an difference of 17.8 percentage points as compared to control, with 95% CI of (7.5, 27.5) percentage points.
- For an odds ratio, our unadjusted treatment effect estimate is an odds ratio of 2.05 (95% CI = 1.35, 3.12) for the event occurring with treatment as compared to control.
Using a logistic regression model
For the odds ratio estimate, we can use a simple logistic regression model to estimate the unadjusted treatment effect, resulting in essentially the same answer. We’ll use the numerical (0/1) format to represent binary information, as follows.
unadj.out2 <- glm(out2 ~ treated, data=toy, family=binomial())
summary(unadj.out2)
Call:
glm(formula = out2 ~ treated, family = binomial(), data = toy)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.328 -1.023 -1.023 1.340 1.340
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.3735 0.1262 -2.960 0.003080 **
treated 0.7198 0.2130 3.379 0.000726 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 553.08 on 399 degrees of freedom
Residual deviance: 541.47 on 398 degrees of freedom
AIC: 545.47
Number of Fisher Scoring iterations: 4
exp(coef(unadj.out2)) # produces odds ratio estimate
(Intercept) treated
0.6883117 2.0540013
exp(confint(unadj.out2)) # produces 95% CI for odds ratio
2.5 % 97.5 %
(Intercept) 0.5362913 0.8800944
treated 1.3561085 3.1283210
And, again, we can use the tidy
function in the broom
package to build a tibble of the key parts of the output. Note that by including the exponentiate = TRUE
command, our results in the treated
row describe the odds ratio, rather than the log odds.
tidy(unadj.out2, conf.int = TRUE, exponentiate = TRUE)
# A tibble: 2 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) 0.688 0.126 -2.96 0.00308 0.536 0.880
2 treated 2.05 0.213 3.38 0.000726 1.36 3.13
res_unadj_2_or <- tidy(unadj.out2, conf.int = TRUE,
conf.level = 0.95, exponentiate = TRUE) %>%
filter(term == "treated")
res_unadj_2_or
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 2.05 0.213 3.38 0.000726 1.36 3.13
- Our odds ratio estimate is 2.05, with 95% confidence interval ranging from 1.36 to 3.13.
- For practical purposes, the odds ratio and 95% confidence interval obtained here matches the methodology for the
twoby2
function. The approach implemented in the twoby2
function produces slightly less conservative (i.e. narrower) confidence intervals for the effect estimate than does the approach used in the logistic regression model.
Outcome 3 (a time-to-event outcome with right censoring)
Our out3.time
variable is a variable indicating the time before the event described in out2
occurred. This happened to 188 of the 400 subjects in the data set. For the other 212 subjects who left the study before their event occurred, we have the time before censoring. We can see the results of this censoring in the survival object describing each treatment group.
Here, for instance, is the survival object for the treated subjects - the first subject listed here is censored - had the event at some point after 106 weeks (106+) but we don’t know precisely when after 106 weeks.
Surv(toy$out3.time, toy$out2.event == "Yes")[toy$treated == 1]
[1] 106+ 96+ 96 99+ 99 108+ 124 116+ 101+ 110 80+ 94 99 126 93
[16] 93+ 104 125 102 87 99 102+ 101+ 101 83 94 107 130+ 112+ 111
[31] 95 96 80+ 89 110 116+ 108+ 118 95 125+ 104 103 112+ 115+ 90
[46] 110+ 105+ 113+ 136+ 105 96+ 126+ 108+ 96 116+ 99 96 108+ 109 114
[61] 112 108+ 115 112+ 100 115+ 114+ 109 127+ 100 85 110 115 117 88
[76] 91 78+ 104+ 96+ 100+ 108+ 107+ 116 91 88 127+ 99 96+ 87 120+
[91] 108 99 87 101 106+ 97 128 100 94 94 89 102 96 76 99+
[106] 93 93 110 96+ 95 97 104 94 114+ 97+ 95 103+ 100+ 100 91
[121] 110+ 119 112+ 98 102+ 103 118+ 89 98+ 79 101+ 85 109+ 87 92
[136] 79+ 108+ 102 85 119+
- To see the controls, we could use
Surv(toy$out3.time, toy$out2.event=="Yes")[toy$treated==0]
To deal with the right censoring, we’ll use the survival
package to fit a simple unadjusted Cox proportional hazards model to assess the relative hazard of having the event at a particular time point among treated subjects as compared to controls.
unadj.out3 <- coxph(Surv(out3.time, out2.event=="Yes") ~ treated, data=toy)
summary(unadj.out3) ## exp(coef) section indicates relative risk estimate and 95% CI
Call:
coxph(formula = Surv(out3.time, out2.event == "Yes") ~ treated,
data = toy)
n= 400, number of events= 188
coef exp(coef) se(coef) z Pr(>|z|)
treated 0.7737 2.1677 0.1489 5.196 2.04e-07 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
exp(coef) exp(-coef) lower .95 upper .95
treated 2.168 0.4613 1.619 2.902
Concordance= 0.6 (se = 0.019 )
Likelihood ratio test= 25.63 on 1 df, p=4e-07
Wald test = 27 on 1 df, p=2e-07
Score (logrank) test = 28.3 on 1 df, p=1e-07
The relative hazard rate is shown in the exp(coef)
section of the output.
Yes, you can tidy this model, as well, using the broom
package.
res_unadj_3 <- tidy(unadj.out3, exponentiate = TRUE,
conf.int = TRUE) %>%
filter(term == "treated")
res_unadj_3
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 2.17 0.149 5.20 0.000000204 1.62 2.90
And so, our estimate can be saved, as we’ve done previously.
- The relative hazard rate estimate is 2.17, with 95% confidence interval ranging from 1.62 to 2.90. Our unadjusted treatment model suggests that the hazard of the outcome is significantly larger (at a 5% significance level) in the treated group than in the control group.
It’s wise, whenever fitting a Cox proportional hazards model, to assess the proportional hazards assumption. One way to do this is to run a simple test in R - from which we can obtain a plot, if we like. The idea is for the plot to show no clear patterns over time, and look pretty much like a horizontal line, while we would like the test to be non-significant - if that’s the case, our proportional hazards assumption is likely OK.
cox.zph(unadj.out3)
chisq df p
treated 1.19 1 0.27
GLOBAL 1.19 1 0.27
plot(cox.zph(unadj.out3), var="treated")

If the proportional hazards assumption is clearly violated (here it isn’t), call a statistician.
Unadjusted Estimates of Treatment Effect on Outcomes
So, our unadjusted average treatment effect estimates (in each case comparing treated subjects to control subjects) are thus:
No covariate adjustment |
9.64 |
0.178 |
2.05 |
2.17 |
(unadjusted) |
(6.75, 12.52) |
(0.075, 0.275) |
(1.36, 3.13) |
(1.62, 2.90) |
Task 2. Fit the propensity score model, then plot the PS-treatment relationship
I’ll use a logistic regression model
psmodel <- glm(treated ~ covA + covB + covC + covD + covE + covF +
Asqr + BC + BD, family=binomial(), data=toy)
summary(psmodel)
Call:
glm(formula = treated ~ covA + covB + covC + covD + covE + covF +
Asqr + BC + BD, family = binomial(), data = toy)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.7156 -0.8403 -0.5277 1.0302 1.9581
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 2.55195 1.54168 1.655 0.09786 .
covA -0.31630 0.45711 -0.692 0.48896
covB -1.64510 1.85012 -0.889 0.37390
covC -0.26162 0.08627 -3.033 0.00243 **
covD 0.06869 0.07988 0.860 0.38986
covE -0.15560 0.03943 -3.947 7.93e-05 ***
covF2-Middle 0.23060 0.27497 0.839 0.40167
covF3-High 0.90026 0.30555 2.946 0.00322 **
Asqr 0.07081 0.08095 0.875 0.38169
BC 0.22538 0.12432 1.813 0.06984 .
BD 0.04450 0.11894 0.374 0.70829
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 517.96 on 399 degrees of freedom
Residual deviance: 444.25 on 389 degrees of freedom
AIC: 466.25
Number of Fisher Scoring iterations: 4
Having fit the model, my first step will be to save the raw and linear propensity score values to the main toy example tibble.
toy$ps <- psmodel$fitted
toy$linps <- psmodel$linear.predictors
Comparing the Distribution of Propensity Score Across the Two Treatment Groups
Now, I can use these saved values to assess the propensity model.
toy %>% group_by(treated_f) %>% skim_without_charts(ps, linps)
Data summary
Name |
Piped data |
Number of rows |
400 |
Number of columns |
23 |
_______________________ |
|
Column type frequency: |
|
numeric |
2 |
________________________ |
|
Group variables |
treated_f |
Variable type: numeric
ps |
Treated |
0 |
1 |
0.46 |
0.18 |
0.15 |
0.31 |
0.46 |
0.61 |
0.83 |
ps |
Control |
0 |
1 |
0.29 |
0.18 |
0.03 |
0.14 |
0.24 |
0.40 |
0.77 |
linps |
Treated |
0 |
1 |
-0.19 |
0.80 |
-1.76 |
-0.82 |
-0.15 |
0.43 |
1.61 |
linps |
Control |
0 |
1 |
-1.08 |
1.01 |
-3.33 |
-1.80 |
-1.14 |
-0.38 |
1.21 |
The simplest plot is probably a boxplot, but it’s not very granular.
ggplot(toy, aes(x = treated_f, y = ps)) +
geom_boxplot()

ggplot(toy, aes(x = treated_f, y = ps, color = treated_f)) +
geom_boxplot() +
geom_jitter(width = 0.1) +
guides(color = FALSE)

I’d rather get a fancier plot to compare the distributions of the propensity score across the two treatment groups, perhaps using a smoothed density estimate, as shown below. Here, I’ll show the distributions of the linear propensity score, the log odds of treatment.
ggplot(toy, aes(x = linps, fill = treated_f)) +
geom_density(alpha = 0.3)

We see a fair amount of overlap across the two treatment groups. I’ll use Rubin’s Rules in the next section to help assess the amount of overlap at this point, before any adjustments for the propensity score.
Task 3. Rubin’s Rules to Check Overlap Before Propensity Adjustment
In his 2001 article about using propensity scores to design studies, as applied to studies of the causal effects of the conduct of the tobacco industry on medical expenditures, Donald Rubin proposed three “rules” for assessing the overlap / balance of covariates appropriately before and after propensity adjustment. Before an outcome is evaluated using a regression analysis (perhaps supplemented by a propensity score adjustment through matching, weighting, subclassification or even direct adjustment), there are three checks that should be performed.
When we do a propensity score analysis, it will be helpful to perform these checks as soon as the propensity model has been estimated, even before any adjustments take place, to see how well the distributions of covariates overlap. After using the propensity score, we hope to see these checks meet the standards below. In what follows, I will describe each standard, and demonstrate its evaluation using the propensity score model we just fit, and looking at the original toy
data set, without applying the propensity score in any way to do adjustments.
Rubin’s Rule 1
Rubin’s Rule 1 states that the absolute value of the standardized difference of the linear propensity score, comparing the treated group to the control group, should be close to 0, ideally below 10%, and in any case less than 50%. If so, we may move on to Rule 2.
To evaluate this rule in the toy example, we’ll run the following code to place the right value into a variable called rubin1.unadj
(for Rubin’s Rule 1, unadjusted).
rubin1.unadj <- with(toy,
abs(100*(mean(linps[treated==1])-mean(linps[treated==0]))/sd(linps)))
rubin1.unadj
[1] 85.85784
What this does is calculate the (absolute value of the) standardized difference of the linear propensity score comparing treated subjects to control subjects.
- We want this value to be close to 0, and certainly less than 50 in order to push forward to outcomes analysis without further adjustment for the propensity score.
- Clearly, here, with a value above 50%, we can’t justify simply running an unadjusted regression model, be it a linear, logistic or Cox model - we’ve got observed selection bias, and need to actually apply the propensity score somehow in order to account for this.
- So, we’ll need to match, subclassify, weight or directly adjust for propensity here.
Since we’ve failed Rubin’s 1st Rule, in some sense, we’re done checking the rules, because we clearly need to further adjust for observed selection bias - there’s no need to prove that further through checking Rubin’s 2nd and 3rd rules. But we’ll do it here to show what’s involved.
Rubin’s Rule 2
Rubin’s Rule 2 states that the ratio of the variance of the linear propensity score in the treated group to the variance of the linear propensity score in the control group should be close to 1, ideally between 4/5 and 5/4, but certainly not very close to or exceeding 1/2 and 2. If so, we may move on to Rule 3.
To evaluate this rule in the toy example, we’ll run the following code to place the right value into a variable called rubin2.unadj
(for Rubin’s Rule 2, unadjusted).
rubin2.unadj <-with(toy, var(linps[treated==1])/var(linps[treated==0]))
rubin2.unadj
[1] 0.6274233
This is the ratio of variances of the linear propensity score comparing treated subjects to control subjects. We want this value to be close to 1, and certainly between 0.5 and 2. In this case, we pass Rule 2, if just barely.
Rubin’s Rule 3
For Rubin’s Rule 3, we begin by calculating regression residuals for each covariate of interest (usually, each of those included in the propensity model) regressed on a single predictor - the linear propensity score. We then look to see if the ratio of the variance of the residuals of this model for the treatment group divided by the variance of the residuals of this model for the control group is close to 1. Again, ideally this will fall between 4/5 and 5/4 for each covariate, but certainly between 1/2 and 2. If so, then the use of regression models seems well justified.
To evaluate Rubin’s 3rd Rule, we’ll create a little function to help us do the calculations.
## General function rubin3 to help calculate Rubin's Rule 3
rubin3 <- function(data, covlist, linps) {
covlist2 <- as.matrix(covlist)
res <- NA
for(i in 1:ncol(covlist2)) {
cov <- as.numeric(covlist2[,i])
num <- var(resid(lm(cov ~ data$linps))[data$exposure == 1])
den <- var(resid(lm(cov ~ data$linps))[data$exposure == 0])
res[i] <- decim(num/den, 3)
}
final <- tibble(name = names(covlist), resid.var.ratio = as.numeric(res))
return(final)
}
Now, then, applying the rule to our sample prior to propensity score adjustment, we get the following result. Note that I’m using the indicator variable forms for the covF
information.
cov.sub <- toy %>% select(covA, covB, covC, covD, covE,
covF.Middle, covF.High, Asqr, BC, BD)
toy$exposure <- toy$treated
rubin3.unadj <- rubin3(data = toy, covlist = cov.sub, linps = linps)
rubin3.unadj
# A tibble: 10 x 2
name resid.var.ratio
<chr> <dbl>
1 covA 1.08
2 covB 1.49
3 covC 0.971
4 covD 1.00
5 covE 0.717
6 covF.Middle 1.03
7 covF.High 1.48
8 Asqr 1.25
9 BC 1.32
10 BD 1.76
Some of these covariates look to have residual variance ratios near 1, while others are further away, but all are within the (0.5, 2.0) range. So we’d pass Rule 3 here, although we’d clearly like to see some covariates (A and E, in particular) with ratios closer to 1.
A Cleveland Dot Chart of the Rubin’s Rule 3 Results
ggplot(rubin3.unadj, aes(x = resid.var.ratio, y = reorder(name, resid.var.ratio))) +
geom_point(col = "blue", size = 2) +
theme_bw() +
xlim(0.5, 2.0) +
geom_vline(aes(xintercept = 1)) +
geom_vline(aes(xintercept = 4/5), linetype = "dashed", col = "red") +
geom_vline(aes(xintercept = 5/4), linetype = "dashed", col = "red") +
labs(x = "Residual Variance Ratio", y = "")

We see values outside the 4/5 and 5/4 lines, but nothing falls outside (0.5, 2).
Task 4. Use 1:1 greedy matching on the linear PS, then check post-match balance
As requested, we’ll do 1:1 greedy matching on the linear propensity score without replacement and breaking ties randomly. Note that we use replace = FALSE
and ties=FALSE
here. To start, we won’t include an outcome variable in our call to the Match
function within the Matching
package We’ll wind up with a match including 140 treated and 140 control subjects.
X <- toy$linps ## matching on the linear propensity score
Tr <- as.logical(toy$treated)
match1 <- Match(Tr=Tr, X=X, M = 1, replace=FALSE, ties=FALSE)
summary(match1)
Estimate... 0
SE......... 0
T-stat..... NaN
p.val...... NA
Original number of observations.............. 400
Original number of treated obs............... 140
Matched number of observations............... 140
Matched number of observations (unweighted). 140
Alternative Matching Strategies.
There are many other strategies available for doing matching in R, many of whom are supported by other packages we’ll use, like cobalt
. Our focus in this toy problem is 1:1 greedy matching using the Matching
package, but we’ll also briefly mention a couple of other approaches available in that same package.
Specifically, if we wanted to do matching with replacement, we would use replace = TRUE
(which is actually the default choice) and maintain ties = FALSE
.
If we wanted to do 1:2 rather than 1:1 matching with the Match
function, we would change M = 1
to M = 2
.
- Suppose you run a 1:1 propensity match with replacement. You should want to know:
- how many treated subjects are in your matched sample, and
- how many control subjects are in your matched sample
If you run a match using match_with_replacement <- Match(Y, Tr, X, M=1, ties = FALSE)
then these answers come from n_distinct(match_with_replacement$index.treated)
and n_distinct(match_with_replacement$index.control)
, respectively. This method works for 1:2 matches, too.
- When matching with replacement using the
Match
function from the Matching package, you should always indicate ties = FALSE
.
- So,
match2 <- Match(Tr=Tr, X=X, M = 1, ties=FALSE)
is OK, but match2 <- Match(Tr=Tr, X=X, M = 1)
is not.
- You’ll know it worked properly if you run
n_distinct(match2$index.treated)
and n_distinct(match2$index.control)
and the control group size is no larger than the treated group size.
- The conclusion: Regardless of whether you’re doing with or without replacement, run
Match
using ties = FALSE
.
Balance Assessment (Semi-Automated)
OK, back to our 1:1 greedy match without replacement. Next, we’ll assess the balance imposed by this greedy match on our covariates, and their transformations (A
^2 and B*C
and B*D
) as well as the raw and linear propensity scores. The default output from the MatchBalance
function is extensive…
set.seed(5001)
mb1 <- MatchBalance(treated ~ covA + covB + covC + covD + covE + covF +
Asqr + BC + BD + ps + linps, data=toy,
match.out = match1, nboots=500)
***** (V1) covA *****
Before Matching After Matching
mean treatment........ 3.1646 3.1646
mean control.......... 3.0046 3.0732
std mean diff......... 14.051 8.0251
mean raw eQQ diff..... 0.19193 0.15921
med raw eQQ diff..... 0.21 0.16
max raw eQQ diff..... 0.58 0.58
mean eCDF diff........ 0.047314 0.038047
med eCDF diff........ 0.035165 0.035714
max eCDF diff........ 0.11868 0.1
var ratio (Tr/Co)..... 1.0837 1.0039
T-test p-value........ 0.1753 0.49932
KS Bootstrap p-value.. 0.136 0.452
KS Naive p-value...... 0.154 0.48581
KS Statistic.......... 0.11868 0.1
***** (V2) covB *****
Before Matching After Matching
mean treatment........ 0.51429 0.51429
mean control.......... 0.29615 0.44286
std mean diff......... 43.488 14.24
mean raw eQQ diff..... 0.22143 0.071429
med raw eQQ diff..... 0 0
max raw eQQ diff..... 1 1
mean eCDF diff........ 0.10907 0.035714
med eCDF diff........ 0.10907 0.035714
max eCDF diff........ 0.21813 0.071429
var ratio (Tr/Co)..... 1.2023 1.0124
T-test p-value........ 2.6605e-05 0.15656
***** (V3) covC *****
Before Matching After Matching
mean treatment........ 9.6238 9.6238
mean control.......... 10.596 9.7243
std mean diff......... -51.896 -5.3669
mean raw eQQ diff..... 0.9755 0.1705
med raw eQQ diff..... 0.975 0.11
max raw eQQ diff..... 1.64 0.8
mean eCDF diff........ 0.12933 0.02018
med eCDF diff........ 0.13297 0.021429
max eCDF diff........ 0.24066 0.064286
var ratio (Tr/Co)..... 0.83836 0.91311
T-test p-value........ 2.582e-06 0.64407
KS Bootstrap p-value.. < 2.22e-16 0.926
KS Naive p-value...... 5.2867e-05 0.93449
KS Statistic.......... 0.24066 0.064286
***** (V4) covD *****
Before Matching After Matching
mean treatment........ 9.1593 9.1593
mean control.......... 8.6469 9.2464
std mean diff......... 24.595 -4.1832
mean raw eQQ diff..... 0.54071 0.20857
med raw eQQ diff..... 0.5 0.1
max raw eQQ diff..... 1.8 1.7
mean eCDF diff........ 0.051117 0.020819
med eCDF diff........ 0.054945 0.021429
max eCDF diff........ 0.11648 0.064286
var ratio (Tr/Co)..... 0.8872 1.136
T-test p-value........ 0.022381 0.71873
KS Bootstrap p-value.. 0.124 0.896
KS Naive p-value...... 0.16916 0.93449
KS Statistic.......... 0.11648 0.064286
***** (V5) covE *****
Before Matching After Matching
mean treatment........ 9.7714 9.7714
mean control.......... 11.3 10.107
std mean diff......... -53.833 -11.823
mean raw eQQ diff..... 1.5143 0.47857
med raw eQQ diff..... 2 0
max raw eQQ diff..... 4 2
mean eCDF diff........ 0.095673 0.036813
med eCDF diff........ 0.074725 0.0071429
max eCDF diff........ 0.22473 0.13571
var ratio (Tr/Co)..... 0.68813 1.1147
T-test p-value........ 2.7506e-06 0.26683
KS Bootstrap p-value.. < 2.22e-16 0.076
KS Naive p-value...... 0.00020385 0.1517
KS Statistic.......... 0.22473 0.13571
***** (V6) covF2-Middle *****
Before Matching After Matching
mean treatment........ 0.38571 0.38571
mean control.......... 0.37692 0.45714
std mean diff......... 1.7996 -14.622
mean raw eQQ diff..... 0.0071429 0.071429
med raw eQQ diff..... 0 0
max raw eQQ diff..... 1 1
mean eCDF diff........ 0.0043956 0.035714
med eCDF diff........ 0.0043956 0.035714
max eCDF diff........ 0.0087912 0.071429
var ratio (Tr/Co)..... 1.0122 0.95477
T-test p-value........ 0.86353 0.18084
***** (V7) covF3-High *****
Before Matching After Matching
mean treatment........ 0.34286 0.34286
mean control.......... 0.16923 0.24286
std mean diff......... 36.448 20.992
mean raw eQQ diff..... 0.17143 0.1
med raw eQQ diff..... 0 0
max raw eQQ diff..... 1 1
mean eCDF diff........ 0.086813 0.05
med eCDF diff........ 0.086813 0.05
max eCDF diff........ 0.17363 0.1
var ratio (Tr/Co)..... 1.6079 1.2253
T-test p-value........ 0.00023805 0.025801
***** (V8) Asqr *****
Before Matching After Matching
mean treatment........ 11.301 11.301
mean control.......... 10.219 10.726
std mean diff......... 16.05 8.5251
mean raw eQQ diff..... 1.2406 0.91933
med raw eQQ diff..... 1.266 0.83225
max raw eQQ diff..... 3.2912 3.12
mean eCDF diff........ 0.047314 0.038047
med eCDF diff........ 0.035165 0.035714
max eCDF diff........ 0.11868 0.1
var ratio (Tr/Co)..... 1.2571 1.174
T-test p-value........ 0.11328 0.45513
KS Bootstrap p-value.. 0.136 0.452
KS Naive p-value...... 0.154 0.48581
KS Statistic.......... 0.11868 0.1
***** (V9) BC *****
Before Matching After Matching
mean treatment........ 4.9519 4.9519
mean control.......... 2.9916 4.414
std mean diff......... 39.082 10.724
mean raw eQQ diff..... 2.0337 0.77436
med raw eQQ diff..... 0.055 0.005
max raw eQQ diff..... 9.5 7.2
mean eCDF diff........ 0.089824 0.04009
med eCDF diff........ 0.066484 0.035714
max eCDF diff........ 0.23736 0.10714
var ratio (Tr/Co)..... 1.1009 0.93057
T-test p-value........ 0.00018579 0.31649
KS Bootstrap p-value.. < 2.22e-16 0.256
KS Naive p-value...... 7.0428e-05 0.39769
KS Statistic.......... 0.23736 0.10714
***** (V10) BD *****
Before Matching After Matching
mean treatment........ 4.52 4.52
mean control.......... 2.4404 3.7864
std mean diff......... 44.618 15.739
mean raw eQQ diff..... 2.0993 0.73786
med raw eQQ diff..... 0.65 0.15
max raw eQQ diff..... 8.5 6.2
mean eCDF diff........ 0.14507 0.05533
med eCDF diff........ 0.17527 0.057143
max eCDF diff........ 0.22308 0.1
var ratio (Tr/Co)..... 1.4089 1.0969
T-test p-value........ 1.0928e-05 0.10232
KS Bootstrap p-value.. < 2.22e-16 0.322
KS Naive p-value...... 0.00023316 0.48581
KS Statistic.......... 0.22308 0.1
***** (V11) ps *****
Before Matching After Matching
mean treatment........ 0.45945 0.45945
mean control.......... 0.29107 0.41399
std mean diff......... 93.884 25.348
mean raw eQQ diff..... 0.16923 0.045728
med raw eQQ diff..... 0.17888 0.055288
max raw eQQ diff..... 0.2476 0.098859
mean eCDF diff........ 0.24865 0.074643
med eCDF diff........ 0.26429 0.067857
max eCDF diff........ 0.39341 0.17143
var ratio (Tr/Co)..... 0.94368 1.224
T-test p-value........ < 2.22e-16 2.6993e-07
KS Bootstrap p-value.. < 2.22e-16 0.042
KS Naive p-value...... 1.1692e-12 0.032675
KS Statistic.......... 0.39341 0.17143
***** (V12) linps *****
Before Matching After Matching
mean treatment........ -0.18896 -0.18896
mean control.......... -1.0761 -0.38324
std mean diff......... 110.7 24.243
mean raw eQQ diff..... 0.89465 0.1959
med raw eQQ diff..... 0.9187 0.23821
max raw eQQ diff..... 1.5824 0.47847
mean eCDF diff........ 0.24865 0.074643
med eCDF diff........ 0.26429 0.067857
max eCDF diff........ 0.39341 0.17143
var ratio (Tr/Co)..... 0.62742 1.2399
T-test p-value........ < 2.22e-16 2.6488e-07
KS Bootstrap p-value.. < 2.22e-16 0.042
KS Naive p-value...... 1.1692e-12 0.032675
KS Statistic.......... 0.39341 0.17143
Before Matching Minimum p.value: < 2.22e-16
Variable Name(s): covC covE BC BD ps linps Number(s): 3 5 9 10 11 12
After Matching Minimum p.value: 2.6488e-07
Variable Name(s): linps Number(s): 12
The cobalt
package has some promising tools for taking this sort of output and turning it into something useful. We’ll look at that approach soon. For now, some old-school stuff…
Extracting, Tabulating Standardized Differences (without cobalt
)
We’ll start by naming the covariates that the MatchBalance
output contains…
covnames <- c("covA", "covB", "covC", "covD", "covE",
"covF - Middle", "covF - High",
"A^2","B*C", "B*D", "raw PS", "linear PS")
The next step is to extract the standardized differences (using the pooled denominator to estimate, rather than the treatment-only denominator used in the main output above.)
pre.szd <- NULL; post.szd <- NULL
for(i in 1:length(covnames)) {
pre.szd[i] <- mb1$BeforeMatching[[i]]$sdiff.pooled
post.szd[i] <- mb1$AfterMatching[[i]]$sdiff.pooled
}
Now, we can build a table of the standardized differences:
match_szd <- tibble(covnames, pre.szd, post.szd, row.names=covnames)
print(match_szd, digits=3)
# A tibble: 12 x 4
covnames pre.szd post.szd row.names
<chr> <dbl> <dbl> <chr>
1 covA 14.3 8.03 covA
2 covB 45.4 14.2 covB
3 covC -49.6 -5.37 covC
4 covD 23.8 -4.18 covD
5 covE -48.6 -11.8 covE
6 covF - Middle 1.81 -14.6 covF - Middle
7 covF - High 40.5 21.0 covF - High
8 A^2 16.9 8.53 A^2
9 B*C 40.0 10.7 B*C
10 B*D 48.3 15.7 B*D
11 raw PS 92.5 25.3 raw PS
12 linear PS 97.2 24.2 linear PS
And then, we could plot these, or their absolute values. Here’s what that looks like.
A Love Plot describing Standardized Differences Before/After Matching (without cobalt
)
ggplot(match_szd, aes(x = pre.szd, y = reorder(covnames, pre.szd))) +
geom_point(col = "black", size = 3, pch = 1) +
geom_point(aes(x = post.szd, y = reorder(covnames, pre.szd)),
size = 3, col = "blue") +
theme_bw() +
geom_vline(aes(xintercept = 0)) +
geom_vline(aes(xintercept = 10), linetype = "dashed", col = "red") +
geom_vline(aes(xintercept = -10), linetype = "dashed", col = "red") +
labs(x = "Standardized Difference (%)", y = "")

Using cobalt
to build a “Love Plot” after Matching
b <- bal.tab(match1,
treated ~ covA + covB + covC + covD +
covE + covF + Asqr + BC + BD + ps + linps,
data=toy, stats = c("m", "v"), un = TRUE,
quick = FALSE)
b
Balance Measures
Type Diff.Un V.Ratio.Un Diff.Adj V.Ratio.Adj
covA Contin. 0.1405 1.0837 0.0803 1.0039
covB Binary 0.2181 . 0.0714 .
covC Contin. -0.5190 0.8384 -0.0537 0.9131
covD Contin. 0.2460 0.8872 -0.0418 1.1360
covE Contin. -0.5383 0.6881 -0.1182 1.1147
covF_1-Low Binary -0.1824 . -0.0286 .
covF_2-Middle Binary 0.0088 . -0.0714 .
covF_3-High Binary 0.1736 . 0.1000 .
Asqr Contin. 0.1605 1.2571 0.0853 1.1740
BC Contin. 0.3908 1.1009 0.1072 0.9306
BD Contin. 0.4462 1.4089 0.1574 1.0969
ps Contin. 0.9388 0.9437 0.2535 1.2240
linps Contin. 1.1070 0.6274 0.2424 1.2399
Sample sizes
Control Treated
All 260 140
Matched 140 140
Unmatched 120 0
Building a Plot of Standardized Differences, with cobalt
p <- love.plot(b, threshold = .1, size = 3,
var.order = "unadjusted",
title = "Standardized Differences and 1:1 Matching")
p + theme_bw()

Building a Plot of Variance Ratios, with cobalt
p <- love.plot(b, stat = "v",
threshold = 1.25, size = 3,
var.order = "unadjusted",
title = "Variance Ratios and 1:1 Matching")
p + theme_bw()

Creating a New Data Frame, Containing the Matched Sample (without cobalt
)
Now, we build a new matched sample data frame in order to do some of the analyses to come. This will contain only the 280 matched subjects (140 treated and 140 control).
matches <- factor(rep(match1$index.treated, 2))
toy.matchedsample <- cbind(matches, toy[c(match1$index.control, match1$index.treated),])
Some sanity checks:
toy.matchedsample %>% count(treated_f)
treated_f n
1 Treated 140
2 Control 140
head(toy.matchedsample)
matches subject treated covA covB covC covD covE covF out1.cost
1 2 T_260 0 3.08 1 10.30 9.4 10 1-Low 42
2 5 T_138 0 3.84 0 9.82 9.0 10 1-Low 58
3 11 T_190 0 2.86 0 7.50 12.0 5 3-High 39
4 14 T_235 0 3.87 1 10.20 9.5 7 2-Middle 51
5 15 T_297 0 4.01 0 9.00 12.7 13 2-Middle 49
6 17 T_261 0 5.35 1 5.56 10.3 10 2-Middle 82
out2.event out3.time treated_f covB_f out2_f out2 covF.Low covF.Middle
1 No 127 Control Has B No Event 0 1 0
2 Yes 92 Control No B Event 1 1 0
3 No 105 Control No B No Event 0 0 0
4 No 108 Control Has B No Event 0 0 1
5 No 111 Control No B No Event 0 0 1
6 Yes 114 Control Has B Event 1 0 1
covF.High Asqr BC BD ps linps exposure
1 0 9.4864 10.30 9.4 0.4351701 -0.2607876 0
2 0 14.7456 0.00 0.0 0.2450288 -1.1253040 0
3 1 8.1796 0.00 0.0 0.7704718 1.2109770 0
4 0 14.9769 10.20 9.5 0.6434764 0.5904848 0
5 0 16.0801 0.00 0.0 0.2989950 -0.8520883 0
6 0 28.6225 5.56 10.3 0.7069386 0.8805615 0
Rubin’s Rules to Check Balance After Matching
Rubin’s Rule 1
Rubin’s Rule 1 states that the absolute value of the standardized difference of the linear propensity score, comparing the treated group to the control group, should be close to 0, ideally below 10%, and in any case less than 50%. If so, we may move on to Rule 2.
Recall that our result without propensity matching (or any other adjustment) was
rubin1.unadj
[1] 85.85784
To run this for our matched sample, we use:
rubin1.match <- with(toy.matchedsample,
abs(100*(mean(linps[treated==1])-mean(linps[treated==0]))/sd(linps)))
rubin1.match
[1] 25.34702
Here, we’ve at least got this value down below 50%, so we would pass Rule 1, although perhaps a different propensity score adjustment (perhaps by weighting or subclassification, or using a different matching approach) might improve this result by getting it closer to 0.
Rubin’s Rule 2
Rubin’s Rule 2 states that the ratio of the variance of the linear propensity score in the treated group to the variance of the linear propensity score in the control group should be close to 1, ideally between 4/5 and 5/4, but certainly not very close to or exceeding 1/2 and 2. If so, we may move on to Rule 3.
Recall that our result without propensity matching (or any other adjustment) was
rubin2.unadj
[1] 0.6274233
To run this for our matched sample, we use:
rubin2.match <- with(toy.matchedsample, var(linps[treated==1])/var(linps[treated==0]))
rubin2.match
[1] 1.239919
This is moderately promising - a substantial improvement over our unadjusted result, and now, just barely within our desired range of 4/5 to 5/4, and clearly within 1/2 to 2.
We pass Rule 2, as well.
Rubin’s Rule 3
For Rubin’s Rule 3, we begin by calculating regression residuals for each covariate of interest (usually, each of those included in the propensity model) regressed on a single predictor - the linear propensity score. We then look to see if the ratio of the variance of the residuals of this model for the treatment group divided by the variance of the residuals of this model for the control group is close to 1. Again, ideally this will fall between 4/5 and 5/4 for each covariate, but certainly between 1/2 and 2. If so, then the use of regression models seems well justified.
Recall that our result without propensity matching (or any other adjustment) was
rubin3.unadj
# A tibble: 10 x 2
name resid.var.ratio
<chr> <dbl>
1 covA 1.08
2 covB 1.49
3 covC 0.971
4 covD 1.00
5 covE 0.717
6 covF.Middle 1.03
7 covF.High 1.48
8 Asqr 1.25
9 BC 1.32
10 BD 1.76
After propensity matching, we use this code to assess Rubin’s 3rd Rule in our matched sample.
cov.sub <- dplyr::select(toy.matchedsample,
covA, covB, covC, covD, covE,
covF.Middle, covF.High, Asqr, BC, BD)
toy.matchedsample$exposure <- toy.matchedsample$treated
rubin3.matched <- rubin3(data = toy.matchedsample, covlist = cov.sub, linps = linps)
rubin3.matched
# A tibble: 10 x 2
name resid.var.ratio
<chr> <dbl>
1 covA 1.00
2 covB 1.19
3 covC 0.804
4 covD 1.14
5 covE 0.954
6 covF.Middle 0.927
7 covF.High 1.24
8 Asqr 1.18
9 BC 1.02
10 BD 1.30
It looks like the results are basically unchanged, except that covF.High
is improved. The dotplot of these results comparing pre- to post-matching is shown below.
A Cleveland Dot Chart of the Rubin’s Rule 3 Results Pre vs. Post-Match
rubin3.both <- bind_rows(rubin3.unadj, rubin3.matched)
rubin3.both$source <- c(rep("Unmatched",10), rep("Matched", 10))
ggplot(rubin3.both, aes(x = resid.var.ratio, y = name, col = source)) +
geom_point(size = 3) +
theme_bw() +
xlim(0.5, 2.0) +
geom_vline(aes(xintercept = 1)) +
geom_vline(aes(xintercept = 4/5), linetype = "dashed", col = "red") +
geom_vline(aes(xintercept = 5/4), linetype = "dashed", col = "red") +
labs(x = "Residual Variance Ratio", y = "")

Some improvement to report, overall.
Task 5. After matching, estimate the causal effect of treatment on …
Outcome 1 (a continuous outcome)
Approach 1. Automated Approach from the Matching package - ATT Estimate
First, we’ll look at the essentially automatic answer which can be obtained when using the Matching
package and inserting an outcome Y. For a continuous outcome, this is often a reasonable approach.
X <- toy$linps ## matching on the linear propensity score
Tr <- as.logical(toy$treated)
Y <- toy$out1.cost
match1.out1 <- Match(Y=Y, Tr=Tr, X=X, M = 1, replace=FALSE, ties=FALSE)
summary(match1.out1)
Estimate... 9.7786
SE......... 1.6137
T-stat..... 6.0599
p.val...... 1.3622e-09
Original number of observations.............. 400
Original number of treated obs............... 140
Matched number of observations............... 140
Matched number of observations (unweighted). 140
The estimate is 9.78 with standard error 1.61. We can obtain an approximate 95% confidence interval by adding and subtracting 1.96 times (or just double) the standard error (SE) to the point estimate, 9.78. Here, using the 1.96 figure, that would yields an approximate 95% CI of (6.62, 12.94).
Approach 2. Automated Approach from the Matching package - ATE Estimate
match1.out1.ATE <- Match(Y=Y, Tr=Tr, X=X, M = 1, replace=FALSE, ties=FALSE, estimand="ATE")
summary(match1.out1.ATE)
Estimate... 9.8321
SE......... 1.1482
T-stat..... 8.5634
p.val...... < 2.22e-16
Original number of observations.............. 400
Original number of treated obs............... 140
Matched number of observations............... 280
Matched number of observations (unweighted). 280
And our 95% CI for this ATE estimate would be 9.83 \(\pm\) 1.96(1.15), or (7.58, 12.08), but we’ll stick with the ATT estimate for now.
ATT vs. ATE: Definitions
- Informally, the average treatment effect on the treated (ATT) estimate describes the difference in potential outcomes (between treated and untreated subjects) summarized across the population of people who actually received the treatment.
- In our initial match, we identified a unique and nicely matched control patient for each of the 140 people in the treated group. We have a 1:1 match on the treated, and thus can describe subjects across that set of treated patients reasonably well.
- On the other hand the average treatment effect (ATE) refers to the difference in potential outcomes summarized across the entire population, including those who did not receive the treatment.
- In our ATE match, we have less success, in part because if we match to the treated patients in a 1:1 way, we’ll have an additional 120 unmatched control patients, about whom we can describe results only vaguely. We could consider matching up control patients to treated patients, perhaps combined with a willingness to re-use some of the treated patients to get a better estimate across the whole population.
Approach 3. Mirroring the Paired T test in a Regression Model
We can mirror the paired t test result in a regression model that treats the match identifier as a fixed factor in a linear model, as follows. This takes the pairing into account, but treating pairing as a fixed, rather than random, factor, isn’t really satisfactory as a solution, although it does match the paired t test.
adj.m.out1 <- lm(out1.cost ~ treated + factor(matches), data=toy.matchedsample)
adj.m.out1.tidy <- tidy(adj.m.out1, conf.int = TRUE) %>%
filter(term == "treated")
adj.m.out1.tidy
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 9.80 1.63 6.03 0.0000000140 6.59 13.0
So, this regression approach produces an estimate that is exactly the same as the paired t test, but this isn’t something I’m completely comfortable with.
Approach 4. A Mixed Model to account for 1:1 Matching
What I think of as a more appropriate result comes from a mixed model where the matches are treated as a random factor, but the treatment group is treated as a fixed factor. This is developed like this, using the lme4
package. Note that we have to create a factor variable to represent the matches, since that’s the only thing that lme4
understands.
toy.matchedsample$matches.f <- as.factor(toy.matchedsample$matches)
## Need to use matches as a factor in R here
matched_mixedmodel.out1 <- lmer(out1.cost ~ treated + (1 | matches.f), data=toy.matchedsample)
summary(matched_mixedmodel.out1); confint(matched_mixedmodel.out1)
Linear mixed model fit by REML ['lmerMod']
Formula: out1.cost ~ treated + (1 | matches.f)
Data: toy.matchedsample
REML criterion at convergence: 2297.1
Scaled residuals:
Min 1Q Median 3Q Max
-2.43583 -0.69061 -0.01882 0.63377 2.17809
Random effects:
Groups Name Variance Std.Dev.
matches.f (Intercept) 37.35 6.112
Residual 184.87 13.597
Number of obs: 280, groups: matches.f, 140
Fixed effects:
Estimate Std. Error t value
(Intercept) 46.843 1.260 37.18
treated 9.800 1.625 6.03
Correlation of Fixed Effects:
(Intr)
treated -0.645
2.5 % 97.5 %
.sig01 0.6245323 8.793725
.sigma 12.1037702 15.304204
(Intercept) 44.3736429 49.312071
treated 6.6043133 12.995687
The tidy
approach from broom
doesn’t work with a linear mixed model, but the broom.mixed
package version of tidy
does, so we have:
res_matched_1 <- broom.mixed::tidy(matched_mixedmodel.out1,
conf.int = T, conf.level = 0.95) %>%
filter(term == "treated")
res_matched_1
# A tibble: 1 x 8
effect group term estimate std.error statistic conf.low conf.high
<chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 fixed <NA> treated 9.8 1.63 6.03 6.61 13.0
Our estimate is 9.80, with 95% CI ranging from 6.61 to 12.99.
Practically, does any of this matter in this example?
Not much in this example, no, as long as you stick to ATT approaches.
“Automated” ATT via Match |
9.78 |
1.61 |
(6.62, 12.94) |
Linear Model (pairs as fixed factor) |
9.80 |
1.63 |
(6.59, 13.01) |
Mixed Model (pairs as random factor) |
9.80 |
1.63 |
(6.61, 12.99) |
Outcome 2 (a binary outcome)
Approach 1. Automated Approach from the Matching package (ATT)
First, we’ll look at the essentially automatic answer which can be obtained when using the Matching
package and inserting an outcome Y. For a binary outcome, this is often a reasonable approach, especially if you don’t wish to adjust for any other covariate, and the result will be expressed as a risk difference, rather than as a relative risk or odds ratio. Note that I have used the 0-1 version of Outcome 2, rather than a factor version. The estimate produced is the difference in risk associated with out2
= 1 (Treated subjects) minus out2
= 0 (Controls.)
X <- toy$linps ## matching on the linear propensity score
Tr <- as.logical(toy$treated)
Y <- toy$out2
match1_out2 <- Match(Y=Y, Tr=Tr, X=X, M = 1, replace=FALSE, ties=FALSE)
summary(match1_out2)
Estimate... 0.13571
SE......... 0.06162
T-stat..... 2.2024
p.val...... 0.027634
Original number of observations.............. 400
Original number of treated obs............... 140
Matched number of observations............... 140
Matched number of observations (unweighted). 140
As in the continuous case, we obtain an approximate 95% confidence interval by adding and subtracting 1.96 times (or just double) the standard error (SE) to the point estimate. The estimated effect on the risk difference is 0.136 with standard error 0.062 and 95% CI (0.015, 0.256).
Approach 2. Using the matched sample to perform a conditional logistic regression
Since we have the matched sample available, we can simply perform a conditional logistic regression to estimate the treatment effect in terms of a log odds ratio (or, after exponentiating, an odds ratio.) Again, I use the 0/1 version of both the outcome and treatment indicator. The key modeling function clogit
is part of the survival
package.
adj.m.out2 <- clogit(out2 ~ treated + strata(matches), data=toy.matchedsample)
summary(adj.m.out2)
Call:
coxph(formula = Surv(rep(1, 280L), out2) ~ treated + strata(matches),
data = toy.matchedsample, method = "exact")
n= 280, number of events= 144
coef exp(coef) se(coef) z Pr(>|z|)
treated 0.5245 1.6897 0.2343 2.239 0.0252 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
exp(coef) exp(-coef) lower .95 upper .95
treated 1.69 0.5918 1.068 2.674
Concordance= 0.628 (se = 0.077 )
Likelihood ratio test= 5.19 on 1 df, p=0.02
Wald test = 5.01 on 1 df, p=0.03
Score (logrank) test = 5.13 on 1 df, p=0.02
The odds ratio in the exp(coef)
section above is the average causal effect estimate - it describes the odds of having an event (out2
) occur associated with being a treated subject, as compared to the odds of the event when a control subject.
I tidied this, as follows, with conf.int = TRUE
, and got …
adj.m.out2_tidy <- tidy(adj.m.out2, exponentiate = TRUE,
conf.int = TRUE)
adj.m.out2_tidy
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.69 0.234 2.24 0.0252 1.07 2.67
Our point estimate is 1.69, with standard error 0.23, and 95% CI ranging from 1.07 to 2.67.
- I’ll use this conditional logistic regression approach to summarize the findings with regard to an odds ratio in my summary of matching results to come.
Outcome 3 (a time-to-event outcome)
Approach 1. Automated Approach from the Matching package
Again, we’ll start by thinking about the essentially automatic answer which can be obtained when using the Match
function. The problem here is that this approach doesn’t take into account the right censoring at all, and assumes that all of the specified times in Outcome 3 are observed. This causes the result (or the ATE version) to not make sense, given what we know about the data. So I don’t recommend you use this approach when dealing with a time-to-event outcome.
And as a result, I won’t even show it here.
Approach 2. A stratified Cox proportional hazards model
Since we have the matched sample, we can use a stratified Cox proportional hazards model to compare the treatment groups on our time-to-event outcome, while accounting for the matched pairs. The main results will be a relative hazard rate estimate, with 95% CI. Again, I use the 0/1 numeric version of the event indicator (out2
), and of the treatment indicator (treated
) here.
adj.m.out3 <- coxph(Surv(out3.time, out2) ~ treated + strata(matches),
data=toy.matchedsample)
summary(adj.m.out3)
Call:
coxph(formula = Surv(out3.time, out2) ~ treated + strata(matches),
data = toy.matchedsample)
n= 280, number of events= 144
coef exp(coef) se(coef) z Pr(>|z|)
treated 0.6306 1.8788 0.2155 2.927 0.00343 **
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
exp(coef) exp(-coef) lower .95 upper .95
treated 1.879 0.5323 1.232 2.866
Concordance= 0.653 (se = 0.069 )
Likelihood ratio test= 9 on 1 df, p=0.003
Wald test = 8.56 on 1 df, p=0.003
Score (logrank) test = 8.85 on 1 df, p=0.003
I tidied this with …
adj.m.out3_tidy <- tidy(adj.m.out3, exponentiate = TRUE,
conf.int = TRUE)
adj.m.out3_tidy
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.88 0.215 2.93 0.00343 1.23 2.87
Our point estimate for the relative hazard rate (from the exp(coef)
section of the summary output) is 1.88, with standard error 0.22, and 95% CI ranging from 1.23 to 2.87.
Checking the proportional hazards assumption looks all right.
cox.zph(adj.m.out3) # Quick check for proportional hazards assumption
chisq df p
treated 0.256 1 0.61
GLOBAL 0.256 1 0.61
plot(cox.zph(adj.m.out3), var="treated")

Results So Far (After Propensity Matching)
So, here’s our summary again, now incorporating both our unadjusted results and the results after matching. Automated results and my favorite of our various non-automated approaches are shown. Note that I’ve left out the “automated” approach for a time-to-event outcome entirely, so as to discourage you from using it.
No covariate adjustment |
9.64 |
0.178 |
2.05 |
2.17 |
(unadjusted) |
(6.75, 12.52) |
(0.075, 0.275) |
(1.36, 3.13) |
(1.62, 2.90) |
After 1:1 PS Match |
9.78 |
0.136 |
N/A |
N/A |
(Match : Automated) |
(6.62, 12.94) |
(0.015, 0.256) |
N/A |
N/A |
After 1:1 PS Match |
9.80 |
N/A |
1.69 |
1.88 |
(“Regression” Models) |
(6.61, 12.99) |
N/A |
(1.07, 2.67) |
(1.23, 2.87) |
Task 6. Subclassify by PS quintile, then display post-subclassification balance
First, we divide the data by the propensity score into 5 strata of equal size using the cut2
function from the Hmisc
package. Then we create a quintile
variable which specifies 1 = lowest propensity scores to 5 = highest.
toy$stratum <- Hmisc::cut2(toy$ps, g=5)
toy %>% group_by(stratum) %>% skim_without_charts(ps) ## sanity check
Data summary
Name |
Piped data |
Number of rows |
400 |
Number of columns |
25 |
_______________________ |
|
Column type frequency: |
|
numeric |
1 |
________________________ |
|
Group variables |
stratum |
Variable type: numeric
ps |
[0.0345,0.170) |
0 |
1 |
0.10 |
0.04 |
0.03 |
0.07 |
0.10 |
0.13 |
0.17 |
ps |
[0.1698,0.259) |
0 |
1 |
0.21 |
0.02 |
0.17 |
0.20 |
0.22 |
0.24 |
0.26 |
ps |
[0.2588,0.386) |
0 |
1 |
0.31 |
0.04 |
0.26 |
0.29 |
0.31 |
0.35 |
0.38 |
ps |
[0.3861,0.545) |
0 |
1 |
0.46 |
0.05 |
0.39 |
0.43 |
0.46 |
0.49 |
0.54 |
ps |
[0.5453,0.834] |
0 |
1 |
0.66 |
0.07 |
0.55 |
0.60 |
0.65 |
0.71 |
0.83 |
toy$quintile <- factor(toy$stratum, labels=1:5)
toy %>% count(stratum, quintile) ## sanity check
# A tibble: 5 x 3
stratum quintile n
<fct> <fct> <int>
1 [0.0345,0.170) 1 80
2 [0.1698,0.259) 2 80
3 [0.2588,0.386) 3 80
4 [0.3861,0.545) 4 80
5 [0.5453,0.834] 5 80
Check Balance and Propensity Score Overlap in Each Quintile
We want to check the balance and propensity score overlap for each stratum (quintile.) I’ll start with a set of faceted, jittered plots to look at overlap.
ggplot(toy, aes(x = treated_f, y = round(ps,2), group = quintile, color = treated_f)) +
geom_jitter(width = 0.2) +
guides(color = FALSE) +
facet_wrap(~ quintile) +
labs(x = "", y = "Propensity for Treatment",
title = "Quintile Subclassification in the Toy Example")

It can be helpful to know how many observations (by exposure group) are in each quintile.
toy %>% count(quintile, treated_f)
# A tibble: 10 x 3
quintile treated_f n
<fct> <fct> <int>
1 1 Treated 4
2 1 Control 76
3 2 Treated 20
4 2 Control 60
5 3 Treated 27
6 3 Control 53
7 4 Treated 39
8 4 Control 41
9 5 Treated 50
10 5 Control 30
With only 4 “treated” subjects in Quintile 1, I am concerned that we won’t be able to do much there to create balance.
The overlap may show a little better in the plot if you free up the y axes…
ggplot(toy, aes(x = treated_f, y = round(ps,2), group = quintile, color = treated_f)) +
geom_jitter(width = 0.2) +
guides(color = FALSE) +
facet_wrap(~ quintile, scales = "free_y") +
labs(x = "", y = "Propensity for Treatment",
title = "Quintile Subclassification in the Toy Example")

Creating a Standardized Difference Calculation Function
We’ll need to be able to calculate standardized differences in this situation so I’ve created a simple szd
function to do this - using the average denominator method.
szd <- function(covlist, g) {
covlist2 <- as.matrix(covlist)
g <- as.factor(g)
res <- NA
for(i in 1:ncol(covlist2)) {
cov <- as.numeric(covlist2[,i])
num <- 100*diff(tapply(cov, g, mean, na.rm=TRUE))
den <- sqrt(mean(tapply(cov, g, var, na.rm=TRUE)))
res[i] <- round(num/den,2)
}
names(res) <- names(covlist)
res
}
Creating the Five Subsamples, by PS Quintile
Next, we split the complete sample into the five quintiles.
## Divide the sample into the five quintiles
quin1 <- filter(toy, quintile==1)
quin2 <- filter(toy, quintile==2)
quin3 <- filter(toy, quintile==3)
quin4 <- filter(toy, quintile==4)
quin5 <- filter(toy, quintile==5)
Standardized Differences in Each Quintile, and Overall
Now, we’ll calculate the standardized differences for each covariate (note that we’re picking up two of the indicators for our multi-categorical covF
) within each quintile, as well as overall.
covs <- c("covA", "covB", "covC", "covD", "covE", "covF.Middle",
"covF.High", "Asqr","BC", "BD", "ps", "linps")
d.q1 <- szd(quin1[covs], quin1$treated)
d.q2 <- szd(quin2[covs], quin2$treated)
d.q3 <- szd(quin3[covs], quin3$treated)
d.q4 <- szd(quin4[covs], quin4$treated)
d.q5 <- szd(quin5[covs], quin5$treated)
d.all <- szd(toy[covs], toy$treated)
toy.szd <- tibble(covs, Overall = d.all, Q1 = d.q1, Q2 = d.q2, Q3 = d.q3, Q4 = d.q4, Q5 = d.q5)
toy.szd <- gather(toy.szd, "quint", "sz.diff", 2:7)
toy.szd
# A tibble: 72 x 3
covs quint sz.diff
<chr> <chr> <dbl>
1 covA Overall 14.3
2 covB Overall 45.4
3 covC Overall -49.6
4 covD Overall 23.8
5 covE Overall -48.6
6 covF.Middle Overall 1.81
7 covF.High Overall 40.5
8 Asqr Overall 16.9
9 BC Overall 40.0
10 BD Overall 48.3
# ... with 62 more rows
Plotting the Standardized Differences
ggplot(toy.szd, aes(x = sz.diff, y = reorder(covs, -sz.diff), group = quint)) +
geom_point() +
geom_vline(xintercept = 0) +
geom_vline(xintercept = c(-10,10), linetype = "dashed", col = "blue") +
facet_wrap(~ quint) +
labs(x = "Standardized Difference, %", y = "",
title = "Comparing Standardized Differences by PS Quintile",
subtitle = "The toy example")

ggplot(toy.szd, aes(x = abs(sz.diff), y = covs, group = quint)) +
geom_point() +
geom_vline(xintercept = 0) +
geom_vline(xintercept = 10, linetype = "dashed", col = "blue") +
facet_wrap(~ quint) +
labs(x = "|Standardized Difference|, %", y = "",
title = "Absolute Standardized Differences by PS Quintile",
subtitle = "The toy example")

Checking Rubin’s Rules Post-Subclassification
Rubin’s Rule 1
As a reminder, prior to adjustment, Rubin’s Rule 1 for the toy
example was:
rubin1.unadj <- with(toy,
abs(100*(mean(linps[treated==1]) -
mean(linps[treated==0]))/sd(linps)))
rubin1.unadj
[1] 85.85784
After propensity score subclassification, we can obtain the same summary within each of the five quintiles…
rubin1.q1 <- with(quin1, abs(100*(mean(linps[treated==1]) -
mean(linps[treated==0]))/sd(linps)))
rubin1.q2 <- with(quin2, abs(100*(mean(linps[treated==1]) -
mean(linps[treated==0]))/sd(linps)))
rubin1.q3 <- with(quin3, abs(100*(mean(linps[treated==1]) -
mean(linps[treated==0]))/sd(linps)))
rubin1.q4 <- with(quin4, abs(100*(mean(linps[treated==1]) -
mean(linps[treated==0]))/sd(linps)))
rubin1.q5 <- with(quin5, abs(100*(mean(linps[treated==1]) -
mean(linps[treated==0]))/sd(linps)))
rubin1.sub <- c(rubin1.q1, rubin1.q2, rubin1.q3, rubin1.q4, rubin1.q5)
names(rubin1.sub)=c("Q1", "Q2", "Q3", "Q4", "Q5")
rubin1.sub
Q1 Q2 Q3 Q4 Q5
125.831381 14.775967 22.061011 4.384187 0.176807
It was always a long shot that subclassification alone would reduce all of these values below 10%, but I had hoped to get them all below 50%. With only 4 “treated” subjects in Quintile 1, though, the task was too tough.
Rubin’s Rule 2
As a reminder, prior to adjustment, Rubin’s Rule 2 for the toy
example was:
rubin2.unadj <- with(toy, var(linps[treated==1])/var(linps[treated==0]))
rubin2.unadj
[1] 0.6274233
After Subclassification, we can obtain the same summary within each of the five quintiles…
rubin2.q1 <- with(quin1, var(linps[treated==1])/var(linps[treated==0]))
rubin2.q2 <- with(quin2, var(linps[treated==1])/var(linps[treated==0]))
rubin2.q3 <- with(quin3, var(linps[treated==1])/var(linps[treated==0]))
rubin2.q4 <- with(quin4, var(linps[treated==1])/var(linps[treated==0]))
rubin2.q5 <- with(quin5, var(linps[treated==1])/var(linps[treated==0]))
rubin2.sub <- c(rubin2.q1, rubin2.q2, rubin2.q3, rubin2.q4, rubin2.q5)
names(rubin2.sub)=c("Q1", "Q2", "Q3", "Q4", "Q5")
rubin2.sub
Q1 Q2 Q3 Q4 Q5
0.006547378 2.170717727 1.054126217 0.925867014 1.600734926
Some of these variance ratios are actually a bit further from 1 than the full data set. Again, with a small sample size like this, subclassification looks like a weak choice. At most, three of the quintiles (3-4 and maybe 5) show OK variance ratios after propensity score subclassification.
Rubin’s Rule 3
Prior to propensity adjustment, recall that Rubin’s Rule 3 summaries were:
covs <- c("covA", "covB", "covC", "covD", "covE",
"covF.Middle", "covF.High", "Asqr","BC", "BD")
rubin3.unadj <- rubin3(data=toy, covlist=toy[covs])
After subclassification, then, Rubin’s Rule 3 summaries within each quintile are:
rubin3.q1 <- rubin3(data=quin1, covlist=quin1[covs])
rubin3.q2 <- rubin3(data=quin2, covlist=quin2[covs])
rubin3.q3 <- rubin3(data=quin3, covlist=quin3[covs])
rubin3.q4 <- rubin3(data=quin4, covlist=quin4[covs])
rubin3.q5 <- rubin3(data=quin5, covlist=quin5[covs])
toy.rubin3 <- tibble(covs, All = rubin3.unadj$resid.var.ratio,
Q1 = rubin3.q1$resid.var.ratio,
Q2 = rubin3.q2$resid.var.ratio,
Q3 = rubin3.q3$resid.var.ratio,
Q4 = rubin3.q4$resid.var.ratio,
Q5 = rubin3.q5$resid.var.ratio)
toy.rubin3 <- gather(toy.rubin3, "quint", "rubin3", 2:7)
ggplot(toy.rubin3, aes(x = rubin3, y = covs, group = quint)) +
geom_point() +
geom_vline(xintercept = 1) +
geom_vline(xintercept = c(0.8, 1.25), linetype = "dashed", col = "blue") +
geom_vline(xintercept = c(0.5, 2), col = "red") +
facet_wrap(~ quint) +
labs(x = "Residual Variance Ratio", y = "",
title = "Residual Variance Ratios by PS Quintile",
subtitle = "Rubin's Rule 3: The toy example")

Most of the residual variance ratios are in the range of (0.5, 2) in quintiles 2-5, with the exception of the covF.high
indicator in Quintile 2. Quintile 1 is certainly problematic in this regard.
Task 7. After subclassifying, what is the estimated average treatment effect?
… on Outcome 1 [a continuous outcome]
First, we’ll find the estimated average causal effect (and standard error) within each quintile via linear regression.
quin1.out1 <- lm(out1.cost ~ treated, data=quin1)
quin2.out1 <- lm(out1.cost ~ treated, data=quin2)
quin3.out1 <- lm(out1.cost ~ treated, data=quin3)
quin4.out1 <- lm(out1.cost ~ treated, data=quin4)
quin5.out1 <- lm(out1.cost ~ treated, data=quin5)
coef(summary(quin1.out1)); coef(summary(quin2.out1)); coef(summary(quin3.out1)); coef(summary(quin4.out1)); coef(summary(quin5.out1))
Estimate Std. Error t value Pr(>|t|)
(Intercept) 46.763158 1.283162 36.443677 9.663430e-51
treated -4.013158 5.738477 -0.699342 4.864186e-01
Estimate Std. Error t value Pr(>|t|)
(Intercept) 45.5 1.445801 31.47043 4.383196e-46
treated 7.6 2.891603 2.62830 1.033042e-02
Estimate Std. Error t value Pr(>|t|)
(Intercept) 45.000000 1.804463 24.938163 6.523421e-39
treated 8.444444 3.106069 2.718691 8.074096e-03
Estimate Std. Error t value Pr(>|t|)
(Intercept) 48.097561 2.775464 17.329555 1.814301e-28
treated 9.287054 3.975103 2.336306 2.204426e-02
Estimate Std. Error t value Pr(>|t|)
(Intercept) 52.70 2.681145 19.655781 5.998878e-32
treated 7.62 3.391410 2.246853 2.747662e-02
Just looking at these results, it doesn’t look like combining quintile 1 with the others is a good idea. I’ll do it here, to show the general idea, but I’m not satisfied with the results. There is certainly a cleverer way to accomplish this using the broom
package, or maybe a little programming with purrr
.
Next, we find the mean of the five quintile-specific estimated regression coefficients
est.st <- (coef(quin1.out1)[2] + coef(quin2.out1)[2] + coef(quin3.out1)[2] +
coef(quin4.out1)[2] + coef(quin5.out1)[2])/5
est.st
treated
5.787668
To get the combined standard error estimate, we do the following:
se.q1 <- summary(quin1.out1)$coefficients[2,2]
se.q2 <- summary(quin2.out1)$coefficients[2,2]
se.q3 <- summary(quin3.out1)$coefficients[2,2]
se.q4 <- summary(quin4.out1)$coefficients[2,2]
se.q5 <- summary(quin5.out1)$coefficients[2,2]
se.st <- sqrt((se.q1^2 + se.q2^2 + se.q3^2 + se.q4^2 + se.q5^2)*(1/25))
se.st
[1] 1.769093
The resulting 95% confidence Interval for the average causal treatment effect is then:
strat.result1 <- tibble(estimate = est.st,
conf.low = est.st - 1.96*se.st,
conf.high = est.st + 1.96*se.st)
strat.result1
# A tibble: 1 x 3
estimate conf.low conf.high
<dbl> <dbl> <dbl>
1 5.79 2.32 9.26
Again, I don’t trust this estimate in this setting because the balance (especially in Quintile 1) is too weak.
… on Outcome 2 [a binary outcome]
First, we find the estimated average causal effect (and standard error) within each quintile via logistic regression:
quin1.out2 <- glm(out2 ~ treated, data=quin1, family=binomial())
quin2.out2 <- glm(out2 ~ treated, data=quin2, family=binomial())
quin3.out2 <- glm(out2 ~ treated, data=quin3, family=binomial())
quin4.out2 <- glm(out2 ~ treated, data=quin4, family=binomial())
quin5.out2 <- glm(out2 ~ treated, data=quin5, family=binomial())
coef(summary(quin1.out2)); coef(summary(quin2.out2)); coef(summary(quin3.out2)); coef(summary(quin4.out2)); coef(summary(quin5.out2))
Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.8347977 0.2496921 -3.3433088 0.0008278571
treated 0.8347977 1.0307018 0.8099314 0.4179796183
Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.3364722 0.2618615 -1.284925 0.1988186
treated 1.1837701 0.5537747 2.137638 0.0325461
Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.1892420 0.2759519 -0.685779 0.49285245
treated 0.8823892 0.4927637 1.790694 0.07334233
Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.3448405 0.3170019 -1.087818 0.2766753
treated 0.6026696 0.4525133 1.331827 0.1829169
Estimate Std. Error z value Pr(>|z|)
(Intercept) 0.2682640 0.3684322 0.7281230 0.4665383
treated -0.1882213 0.4646186 -0.4051092 0.6853973
Next, we find the mean of the five quintile-specific estimated logistic regression coefficients
est.st <- (coef(quin1.out2)[2] + coef(quin2.out2)[2] + coef(quin3.out2)[2] +
coef(quin4.out2)[2] + coef(quin5.out2)[2])/5
est.st ## this is the estimated log odds ratio
treated
0.6630811
## And we exponentiate this to get the overall odds ratio estimate
exp(est.st)
treated
1.940763
To get the combined standard error estimate across the five quintiles, we do the following:
se.q1 <- summary(quin1.out2)$coefficients[2,2]
se.q2 <- summary(quin2.out2)$coefficients[2,2]
se.q3 <- summary(quin3.out2)$coefficients[2,2]
se.q4 <- summary(quin4.out2)$coefficients[2,2]
se.q5 <- summary(quin5.out2)$coefficients[2,2]
se.st <- sqrt((se.q1^2 + se.q2^2 + se.q3^2 + se.q4^2 + se.q5^2)*(1/25))
se.st
[1] 0.2851293
## Of course, this standard error is also on the log odds ratio scale
Now, we obtain a 95% Confidence Interval for the Average Causal Effect of our treatment (as an Odds Ratio) through combination and exponentiation, as follows:
strat.result2 <- tibble(estimate = exp(est.st),
conf.low = exp(est.st - 1.96*se.st),
conf.high = exp(est.st + 1.96*se.st))
strat.result2
# A tibble: 1 x 3
estimate conf.low conf.high
<dbl> <dbl> <dbl>
1 1.94 1.11 3.39
… on Outcome 3 [a time to event]
Subjects with out2.event
= “Yes” are truly observed events, while those with out2.event
== “No” are censored before an event can happen to them.
The Cox model comparing treated to control, stratifying on quintile, is…
adj.s.out3 <- coxph(Surv(out3.time, out2) ~ treated + strata(quintile), data=toy)
summary(adj.s.out3) ## exp(coef) gives relative hazard associated with treatment
Call:
coxph(formula = Surv(out3.time, out2) ~ treated + strata(quintile),
data = toy)
n= 400, number of events= 188
coef exp(coef) se(coef) z Pr(>|z|)
treated 0.6817 1.9772 0.1718 3.968 7.25e-05 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
exp(coef) exp(-coef) lower .95 upper .95
treated 1.977 0.5058 1.412 2.769
Concordance= 0.582 (se = 0.019 )
Likelihood ratio test= 15.66 on 1 df, p=8e-05
Wald test = 15.74 on 1 df, p=7e-05
Score (logrank) test = 16.13 on 1 df, p=6e-05
strat.result3 <- tidy(adj.s.out3, exponentiate = TRUE, conf.int = TRUE)
strat.result3
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.98 0.172 3.97 0.0000725 1.41 2.77
Checking the Proportional Hazards Assumption
The proportional hazards assumption may be problematic.
cox.zph(adj.s.out3) ## checking the proportional hazards assumption
chisq df p
treated 3.57 1 0.059
GLOBAL 3.57 1 0.059
plot(cox.zph(adj.s.out3), var="treated")

Results So Far (After Matching and Subclassification)
These subclassification results describe the average treatment effect, while the previous analyses we have completed describe the average treatment effect on the treated. This is one reason for the meaningful difference between the estimates. Another reason is that the balance on observed covariates is much worse after stratification in some quintiles, especially Quintile 1.
No covariate adjustment |
9.64 |
0.178 |
2.05 |
2.17 |
(unadjusted) |
(6.75, 12.52) |
(0.075, 0.275) |
(1.36, 3.13) |
(1.62, 2.90) |
After 1:1 PS Match |
9.78 |
0.136 |
N/A |
N/A |
(Match : Automated) |
(6.62, 12.94) |
(0.015, 0.256) |
N/A |
N/A |
After 1:1 PS Match |
9.80 |
N/A |
1.69 |
1.88 |
(“Regression” Models) |
(6.61, 12.99) |
N/A |
(1.07, 2.67) |
(1.23, 2.87) |
After PS Subclassification |
5.79 |
N/A |
1.94 |
1.98 |
(“Regression” models, ATE) |
(2.32, 9.26) |
N/A |
(1.11, 9.26) |
(1.41, 2.77) |
Task 8. Execute weighting by the inverse PS, then assess covariate balance
ATT approach: Weight treated subjects as 1; control subjects as ps/(1-ps)
toy$wts1 <- ifelse(toy$treated==1, 1, toy$ps/(1-toy$ps))
Here is a plot of the resulting ATT (average treatment effect on the treated) weights:
ggplot(toy, aes(x = ps, y = wts1, color = treated_f)) +
geom_point() +
guides(color = FALSE) +
facet_wrap(~ treated_f) +
labs(x = "Estimated Propensity for Treatment",
y = "ATT weights for the toy example",
title = "ATT weighting structure: Toy example")

ATE Approach: Weight treated subjects by 1/ps; Control subjects by 1/(1-PS)
toy$wts2 <- ifelse(toy$treated==1, 1/toy$ps, 1/(1-toy$ps))
Here’s a plot of the ATE (average treatment effect) weights…
ggplot(toy, aes(x = ps, y = wts2, color = treated_f)) +
geom_point() +
guides(color = FALSE) +
facet_wrap(~ treated_f) +
labs(x = "Estimated Propensity for Treatment",
y = "ATE weights for the toy example",
title = "ATE weighting structure: Toy example")

Assessing Balance after Weighting
The twang
package provides several functions for assessing balance after weighting, in addition to actually doing the weighting using more complex propensity models. For this example, we’ll demonstrate balance assessment for our two (relatively simple) weighting schemes. In other examples, we’ll use twang
to do more complete weighting work.
Reminder of ATT vs. ATE Definitions
- Informally, the average treatment effect on the treated (ATT) estimate describes the difference in potential outcomes (between treated and untreated subjects) summarized across the population of people who actually received the treatment. This is usually the estimate we work with in making causal estimates from observational studies.
- On the other hand, the average treatment effect (ATE) refers to the difference in potential outcomes summarized across the entire population, including those who did not receive the treatment.
For ATT weights (wts1
)
toy_df <- base::data.frame(toy) # twang doesn't react well to tibbles
covlist <- c("covA", "covB", "covC", "covD", "covE", "covF", "Asqr","BC", "BD", "ps", "linps")
# for ATT weights
bal.wts1 <- dx.wts(x=toy_df$wts1, data=toy_df, vars=covlist,
treat.var="treated", estimand="ATT")
bal.wts1
type n.treat n.ctrl ess.treat ess.ctrl max.es mean.es max.ks
1 unw 140 260 140 260.0000 1.1070181 0.43969555 0.3934066
2 140 260 140 117.3756 0.1197246 0.05601621 0.1295878
mean.ks iter
1 0.20380389 NA
2 0.06990453 NA
bal.table(bal.wts1)
$unw
tx.mn tx.sd ct.mn ct.sd std.eff.sz stat p ks ks.pval
covA 3.165 1.138 3.005 1.094 0.141 1.361 0.174 0.119 0.141
covB 0.514 0.502 0.296 0.457 0.435 4.284 0.000 0.218 0.000
covC 9.624 1.873 10.596 2.045 -0.519 -4.800 0.000 0.241 0.000
covD 9.159 2.083 8.647 2.212 0.246 2.300 0.022 0.116 0.155
covE 9.771 2.839 11.300 3.423 -0.538 -4.779 0.000 0.225 0.000
covF:1-Low 0.271 0.445 0.454 0.498 -0.410 9.831 0.000 0.182 0.000
covF:2-Middle 0.386 0.487 0.377 0.485 0.018 NA NA 0.009 0.000
covF:3-High 0.343 0.475 0.169 0.375 0.366 NA NA 0.174 0.000
Asqr 11.301 6.743 10.219 6.014 0.161 1.592 0.112 0.119 0.141
BC 4.952 5.016 2.992 4.781 0.391 3.796 0.000 0.237 0.000
BD 4.520 4.661 2.440 3.927 0.446 4.499 0.000 0.223 0.000
ps 0.459 0.179 0.291 0.185 0.939 8.879 0.000 0.393 0.000
linps -0.189 0.801 -1.076 1.012 1.107 9.624 0.000 0.393 0.000
[[2]]
tx.mn tx.sd ct.mn ct.sd std.eff.sz stat p ks ks.pval
covA 3.165 1.138 3.187 1.133 -0.020 -0.155 0.877 0.079 0.784
covB 0.514 0.502 0.556 0.498 -0.082 -0.675 0.500 0.041 1.000
covC 9.624 1.873 9.550 2.206 0.039 0.281 0.778 0.083 0.735
covD 9.159 2.083 9.212 1.997 -0.025 -0.212 0.833 0.062 0.950
covE 9.771 2.839 9.750 2.834 0.008 0.063 0.950 0.078 0.792
covF:1-Low 0.271 0.445 0.229 0.420 0.096 0.334 0.706 0.043 0.706
covF:2-Middle 0.386 0.487 0.409 0.492 -0.048 NA NA 0.023 0.706
covF:3-High 0.343 0.475 0.362 0.481 -0.041 NA NA 0.020 0.706
Asqr 11.301 6.743 11.435 6.459 -0.020 -0.157 0.876 0.079 0.784
BC 4.952 5.016 5.281 5.050 -0.066 -0.542 0.588 0.062 0.949
BD 4.520 4.661 4.860 4.574 -0.073 -0.588 0.557 0.081 0.762
ps 0.459 0.179 0.481 0.195 -0.120 -0.914 0.361 0.130 0.209
linps -0.189 0.801 -0.116 0.904 -0.091 -0.696 0.487 0.130 0.209
The std.eff.sz
shows the standardized difference, but as a proportion, rather than as a percentage. We’ll create a data frame (tibble) so we can plot the data more easily.
bal.before.wts1 <- bal.table(bal.wts1)[1]
bal.after.wts1 <- bal.table(bal.wts1)[2]
balance.att.weights <- base::data.frame(names = rownames(bal.before.wts1$unw),
pre.weighting = 100*bal.before.wts1$unw$std.eff.sz,
ATT.weighted = 100*bal.after.wts1[[1]]$std.eff.sz)
balance.att.weights <- gather(balance.att.weights, timing, szd, 2:3)
OK - here is the plot of standardized differences before and after ATT weighting.
ggplot(balance.att.weights, aes(x = szd, y = reorder(names, szd), color = timing)) +
geom_point(size = 3) +
geom_vline(xintercept = 0) +
geom_vline(xintercept = c(-10,10), linetype = "dashed", col = "blue") +
labs(x = "Standardized Difference", y = "",
title = "Standardized Difference before and after ATT Weighting",
subtitle = "The toy example")

For ATE weights (wts2
)
bal.wts2 <- dx.wts(x=toy_df$wts2, data=toy_df, vars=covlist,
treat.var="treated", estimand="ATE")
bal.wts2
type n.treat n.ctrl ess.treat ess.ctrl max.es mean.es max.ks
1 unw 140 260 140.0000 260.000 0.8585784 0.40976413 0.3934066
2 140 260 111.5654 224.749 0.1646821 0.07619241 0.1876510
mean.ks iter
1 0.20380389 NA
2 0.08075449 NA
bal.table(bal.wts2)
$unw
tx.mn tx.sd ct.mn ct.sd std.eff.sz stat p ks ks.pval
covA 3.165 1.138 3.005 1.094 0.144 1.361 0.174 0.119 0.141
covB 0.514 0.502 0.296 0.457 0.451 4.284 0.000 0.218 0.000
covC 9.624 1.873 10.596 2.045 -0.477 -4.800 0.000 0.241 0.000
covD 9.159 2.083 8.647 2.212 0.235 2.300 0.022 0.116 0.155
covE 9.771 2.839 11.300 3.423 -0.462 -4.779 0.000 0.225 0.000
covF:1-Low 0.271 0.445 0.454 0.498 -0.374 9.831 0.000 0.182 0.000
covF:2-Middle 0.386 0.487 0.377 0.485 0.018 NA NA 0.009 0.000
covF:3-High 0.343 0.475 0.169 0.375 0.413 NA NA 0.174 0.000
Asqr 11.301 6.743 10.219 6.014 0.172 1.592 0.112 0.119 0.141
BC 4.952 5.016 2.992 4.781 0.396 3.796 0.000 0.237 0.000
BD 4.520 4.661 2.440 3.927 0.483 4.499 0.000 0.223 0.000
ps 0.459 0.179 0.291 0.185 0.844 8.879 0.000 0.393 0.000
linps -0.189 0.801 -1.076 1.012 0.859 9.624 0.000 0.393 0.000
[[2]]
tx.mn tx.sd ct.mn ct.sd std.eff.sz stat p ks ks.pval
covA 3.146 1.105 3.070 1.111 0.068 0.602 0.548 0.082 0.666
covB 0.415 0.495 0.389 0.489 0.053 0.456 0.649 0.026 1.000
covC 10.033 1.894 10.220 2.164 -0.092 -0.791 0.429 0.113 0.273
covD 9.125 2.261 8.850 2.154 0.126 1.027 0.305 0.118 0.226
covE 10.442 2.949 10.743 3.309 -0.091 -0.847 0.398 0.084 0.627
covF:1-Low 0.345 0.475 0.373 0.484 -0.057 0.146 0.864 0.028 0.864
covF:2-Middle 0.396 0.489 0.388 0.487 0.016 NA NA 0.008 0.864
covF:3-High 0.259 0.438 0.239 0.426 0.048 NA NA 0.020 0.864
Asqr 11.111 6.583 10.656 6.205 0.072 0.610 0.543 0.082 0.666
BC 4.076 5.009 3.814 5.002 0.053 0.459 0.646 0.068 0.849
BD 3.550 4.470 3.310 4.330 0.056 0.478 0.633 0.045 0.996
ps 0.378 0.176 0.359 0.209 0.093 0.823 0.411 0.188 0.009
linps -0.561 0.806 -0.731 1.078 0.165 1.569 0.118 0.188 0.009
bal.before.wts2 <- bal.table(bal.wts2)[1]
bal.after.wts2 <- bal.table(bal.wts2)[2]
balance.ate.weights <- base::data.frame(names = rownames(bal.before.wts2$unw),
pre.weighting = 100*bal.before.wts2$unw$std.eff.sz,
ATE.weighted = 100*bal.after.wts2[[1]]$std.eff.sz)
balance.ate.weights <- gather(balance.ate.weights, timing, szd, 2:3)
Here is the plot of standardized differences before and after ATE weighting.
ggplot(balance.ate.weights, aes(x = szd, y = reorder(names, szd), color = timing)) +
geom_point(size = 3) +
geom_vline(xintercept = 0) +
geom_vline(xintercept = c(-10,10), linetype = "dashed", col = "blue") +
labs(x = "Standardized Difference", y = "",
title = "Standardized Difference before and after ATE Weighting",
subtitle = "The toy example")

Rubin’s Rules after ATT weighting
For our weighted sample, our summary statistic for Rules 1 and 2 may be found from the bal.table
output.
Rubin’s Rule 1
We can read off the standardized effect size after weighting for the linear propensity score as -0.091. Multiplying by 100, we get 9.1%, so we would pass Rule 1.
Rubin’s Rule 2
We can read off the standard deviations within the treated and control groups. We can then square each, to get the relevant variances, then take the ratio of those variances. Here, we have standard deviations of the linear propensity score after weighting of 0.801 in the treated group and 0.904 in the control group. 0.801^2 / 0.904^2 = 0.7851, which is just outside our desired range of 4/5 to 5/4, as well as clearly within 1/2 to 2. Arguably, we can pass Rule 2, also. But I’ll be interested to see if twang
can do better.
Rubin’s Rule 3
Rubin’s Rule 3 requires some more substantial manipulation of the data. I’ll skip that here.
Rubin’s Rules after ATE weighting
Again, our summary statistic for Rules 1 and 2 may be found from the bal.table
output.
Rubin’s Rule 1
The standardized effect size after ATE weighting for the linear propensity score is 0.177. Multiplying by 100, we get 17.7%, so we would pass Rule 1.
Rubin’s Rule 2
We can read off the standard deviations within the treated and control groups from the ATE weights, then square to get the variances, then take the ratio. Here, we have 0.806^2 / 1.078^2 = 0.559, which is not within our desired range of 4/5 to 5/4, but is between 0.5 and 2. Arguably, we pass Rule 2, also. But I’ll be interested to see if twang
can do better.
Rubin’s Rule 3
Again, for now, I’m skipping Rubin’s Rule 3 after weighting.
Using TWANG for Alternative PS Estimation and ATT Weighting
Here, I’ll demonstrate the use of the the twang
package’s functions to fit the propensity model and then perform ATT weighting, mostly using default options.
Estimate the Propensity Score using Generalized Boosted Regression, and then perfom ATT Weighting
We can directly use the twang
(toolkit for weighting and analysis of nonequivalent groups) package to weight our results, and even to re-estimate the propensity score using generalized boosted regression rather than a logistic regression model. The twang
vignette is very helpful and found at this link.
To begin, we’ll estimate the propensity score using the twang
function ps
. This uses a generalized boosted regression approach to estimate the propensity score and produce material for checking balance.
# Recall that twang does not play well with tibbles,
# so we have to use the data frame version of the toy object
ps.toy <- ps(treated ~ covA + covB + covC + covD + covE + covF +
Asqr + BC + BD,
data = toy_df,
n.trees = 3000,
interaction.depth = 2,
stop.method = c("es.mean"),
estimand = "ATT",
verbose = FALSE)
Did we let the simulations run long enough to stabilize estimates?
plot(ps.toy)

What is the effective sample size of our weighted results?
summary(ps.toy)
n.treat n.ctrl ess.treat ess.ctrl max.es mean.es max.ks
unw 140 260 140 260.0000 0.53833327 0.33365329 0.24065934
es.mean.ATT 140 260 140 107.1175 0.08192421 0.04338572 0.07831191
max.ks.p mean.ks iter
unw NA 0.16933067 NA
es.mean.ATT NA 0.04627681 1128
How is the balance?
plot(ps.toy, plots = 2)

plot(ps.toy, plots = 3)

Assessing Balance with cobalt
b2 <- bal.tab(ps.toy, full.stop.method = "es.mean.att",
stats = c("m", "v"), un = TRUE)
b2
Call
ps.fast(formula = formula, data = data, params = params, n.trees = nrounds,
interaction.depth = max_depth, shrinkage = eta, bag.fraction = subsample,
n.minobsinnode = min_child_weight, perm.test.iters = perm.test.iters,
print.level = print.level, verbose = verbose, estimand = estimand,
stop.method = stop.method, sampw = sampw, multinom = multinom,
ks.exact = ks.exact, version = version, tree_method = tree_method,
n.keep = n.keep, n.grid = n.grid, keep.data = keep.data)
Balance Measures
Type Diff.Un V.Ratio.Un Diff.Adj V.Ratio.Adj
prop.score Distance 1.9261 1.2314 0.9594 0.7442
covA Contin. 0.1405 1.0837 0.0558 1.0552
covB Binary 0.2181 . 0.0228 .
covC Contin. -0.5190 0.8384 -0.0544 0.9175
covD Contin. 0.2460 0.8872 -0.0495 1.0667
covE Contin. -0.5383 0.6881 -0.0819 0.8857
covF_1-Low Binary -0.1824 . 0.0073 .
covF_2-Middle Binary 0.0088 . -0.0075 .
covF_3-High Binary 0.1736 . 0.0002 .
Asqr Contin. 0.1605 1.2571 0.0694 1.1293
BC Contin. 0.3908 1.1009 0.0593 1.0168
BD Contin. 0.4462 1.4089 0.0292 0.9898
Effective sample sizes
Control Treated
Unadjusted 260. 140
Adjusted 107.12 140
Semi-Automated Love plot of Standardized Differences
p <- love.plot(b2,
threshold = .1, size = 3,
title = "Standardized Diffs and TWANG ATT weighting")
p + theme_bw()

Semi-Automated Love plot of Variance Ratios
p <- love.plot(b2, stat = "v",
threshold = 1.25, size = 3,
title = "Variance Ratios: TWANG ATT weighting")
p + theme_bw()

Task 9. After weighting, what is the estimated average causal effect of treatment?
… on Outcome 1 [a continuous outcome]
with ATT weights
The relevant regression approach uses the svydesign
and svyglm
functions from the survey
package.
toywt1.design <- svydesign(ids=~1, weights=~wts1, data=toy) # using ATT weights
adjout1.wt1 <- svyglm(out1.cost ~ treated, design=toywt1.design)
wt_att_results1 <- tidy(adjout1.wt1, conf.int = TRUE) %>% filter(term == "treated")
wt_att_results1
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 7.70 1.88 4.10 0.0000495 4.01 11.4
with ATE weights
toywt2.design <- svydesign(ids=~1, weights=~wts2, data=toy) # using ATE weights
adjout1.wt2 <- svyglm(out1.cost ~ treated, design=toywt2.design)
wt_ate_results1 <- tidy(adjout1.wt2, conf.int = TRUE) %>% filter(term == "treated")
wt_ate_results1
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 7.44 1.68 4.44 0.0000119 4.14 10.7
with TWANG ATT weights
toywt3.design <- svydesign(ids=~1,
weights=~get.weights(ps.toy,
stop.method = "es.mean"),
data=toy) # using twang ATT weights
adjout1.wt3 <- svyglm(out1.cost ~ treated, design=toywt3.design)
wt_twangatt_results1 <- tidy(adjout1.wt3, conf.int = TRUE) %>% filter(term == "treated")
wt_twangatt_results1
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 8.40 1.73 4.87 0.00000165 5.00 11.8
… on Outcome 2 [a binary outcome]
For a binary outcome, we build the outcome model using the quasibinomial, rather than the usual binomial family. We use the same svydesign
information as we built for outcome 1.
Using ATT weights
adjout2.wt1 <- svyglm(out2 ~ treated, design=toywt1.design, family=quasibinomial())
wt_att_results2 <- tidy(adjout2.wt1, conf.int = TRUE, exponentiate = TRUE) %>%
filter(term == "treated")
wt_att_results2
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.59 0.253 1.83 0.0683 0.966 2.61
Using ATE weights
adjout2.wt2 <- svyglm(out2.event ~ treated, design=toywt2.design, family=quasibinomial())
wt_ate_results2 <- tidy(adjout2.wt2, conf.int = TRUE, exponentiate = TRUE) %>%
filter(term == "treated")
wt_ate_results2
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 2.06 0.236 3.07 0.00228 1.30 3.28
with TWANG ATT weights
adjout2.wt3 <- svyglm(out2 ~ treated, design=toywt3.design,
family=quasibinomial())
wt_twangatt_results2 <- tidy(adjout2.wt3, conf.int = TRUE, exponentiate = TRUE) %>%
filter(term == "treated")
wt_twangatt_results2
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.57 0.259 1.74 0.0822 0.944 2.61
… on Outcome 3 [a time to event]
As before, subjects with out2.event
= “Yes” are truly observed events, while those with out2.event
== “No” are censored before an event can happen to them.
Using ATT weights
The Cox model comparing treated to control, weighting by ATT weights (wts1
), is…
adjout3.wt1 <- coxph(Surv(out3.time, out2) ~ treated, data=toy, weights=wts1)
wt_att_results3 <- tidy(adjout3.wt1, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term == "treated")
wt_att_results3
# A tibble: 1 x 8
term estimate std.error robust.se statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.76 0.165 0.168 3.34 0.000831 1.26 2.44
The exp(coef)
output gives the relative hazard of the event comparing treated subjects to control subjects.
And here’s the check of the proportional hazards assumption…
cox.zph(adjout3.wt1); plot(cox.zph(adjout3.wt1), var="treated")
chisq df p
treated 2.44 1 0.12
GLOBAL 2.44 1 0.12

Using ATE weights
adjout3.wt2 <- coxph(Surv(out3.time, out2) ~ treated, data=toy, weights=wts2)
wt_ate_results3 <- tidy(adjout3.wt2, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term == "treated")
wt_ate_results3
# A tibble: 1 x 8
term estimate std.error robust.se statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 2.22 0.102 0.155 5.13 0.000000291 1.64 3.01
And here’s the check of the proportional hazards assumption…
cox.zph(adjout3.wt2); plot(cox.zph(adjout3.wt2), var="treated")
chisq df p
treated 5.48 1 0.019
GLOBAL 5.48 1 0.019

with TWANG ATT weights
wts3 <- get.weights(ps.toy, stop.method = "es.mean")
adjout3.wt3 <- coxph(Surv(out3.time, out2) ~ treated, data=toy, weights=wts3)
wt_twangatt_results3 <- tidy(adjout3.wt3, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term == "treated")
wt_twangatt_results3
# A tibble: 1 x 8
term estimate std.error robust.se statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.76 0.183 0.173 3.25 0.00116 1.25 2.47
Results So Far (After Matching, Subclassification and Weighting)
No covariate adjustment |
9.64 |
0.178 |
2.05 |
2.17 |
(unadjusted) |
(6.75, 12.52) |
(0.075, 0.275) |
(1.36, 3.13) |
(1.62, 2.90) |
After 1:1 PS Match |
9.78 |
0.136 |
N/A |
N/A |
(Match : Automated) |
(6.62, 12.94) |
(0.015, 0.256) |
N/A |
N/A |
After 1:1 PS Match |
9.80 |
N/A |
1.69 |
1.88 |
(“Regression” Models) |
(6.61, 12.99) |
N/A |
(1.07, 2.67) |
(1.23, 2.87) |
After PS Subclassification |
5.79 |
N/A |
1.94 |
1.98 |
(“Regression” models, ATE) |
(2.32, 9.26) |
N/A |
(1.11, 9.26) |
(1.41, 2.77) |
ATT Weighting |
7.70 |
N/A |
1.59 |
1.76 |
(ATT) |
(4.01, 11.39) |
N/A |
(0.97, 2.61) |
(1.26, 2.44) |
ATE Weighting |
7.44 |
N/A |
2.06 |
2.22 |
(ATE) |
(4.14, 10.74) |
N/A |
(1.30, 3.28) |
(1.64, 3.01) |
twang ATT weights |
8.40 |
N/A |
1.57 |
1.76 |
(ATT) |
(5.00, 11.79) |
N/A |
(0.94, 2.61) |
(1.25, 2.47) |
Task 10. After direct adjustment for the linear PS, what is the estimated average causal treatment effect?
… on Outcome 1 [a continuous outcome]
Here, we fit a linear regression model with linps
added as a covariate.
adj.reg.out1 <- lm(out1.cost ~ treated + linps, data=toy)
adj_out1 <- tidy(adj.reg.out1, conf.int = TRUE) %>% filter(term == "treated")
adj_out1
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 7.99 1.60 5.01 0.000000833 4.86 11.1
… on Outcome 2 [a binary outcome]
Here, fit a logistic regression with linps
added as a covariate
adj.reg.out2 <- glm(out2 ~ treated + linps, data=toy, family=binomial())
adj_out2 <- tidy(adj.reg.out2, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term == "treated")
adj_out2
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.80 0.232 2.53 0.0113 1.14 2.85
… on Outcome 3 [a time-to-event outcome]
Again, subjects with out2.event
No are right-censored, those with Yes for out2.event
have their times to event observed.
We fit a Cox proportional hazards model predicting time to event (with event=Yes indicating non-censored cases) based on treatment group (treated) and now also the linear propensity score.
adj.reg.out3 <- coxph(Surv(out3.time, out2) ~ treated + linps, data=toy)
adj_out3 <- tidy(adj.reg.out2, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term == "treated")
adj_out3
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.80 0.232 2.53 0.0113 1.14 2.85
The exp(coef)
section of the summary
for this model indicates the relative hazard estimates and associated 95% CI.
Check proportional hazards assumption
Here’s the check of the proportional hazards assumption.
cox.zph(adj.reg.out3)
chisq df p
treated 1.220 1 0.27
linps 0.356 1 0.55
GLOBAL 2.737 2 0.25
plot(cox.zph(adj.reg.out3), var="treated")

plot(cox.zph(adj.reg.out3), var="linps")

Results So Far (After Matching, Subclassification, Weighting, Adjustment)
No covariate adjustment |
9.64 |
0.178 |
2.05 |
2.17 |
(unadjusted) |
(6.75, 12.52) |
(0.075, 0.275) |
(1.36, 3.13) |
(1.62, 2.90) |
After 1:1 PS Match |
9.78 |
0.136 |
N/A |
N/A |
(Match : Automated) |
(6.62, 12.94) |
(0.015, 0.256) |
N/A |
N/A |
After 1:1 PS Match |
9.80 |
N/A |
1.69 |
1.88 |
(“Regression” Models) |
(6.61, 12.99) |
N/A |
(1.07, 2.67) |
(1.23, 2.87) |
After PS Subclassification |
5.79 |
N/A |
1.94 |
1.98 |
(“Regression” models, ATE) |
(2.32, 9.26) |
N/A |
(1.11, 9.26) |
(1.41, 2.77) |
ATT Weighting |
7.70 |
N/A |
1.59 |
1.76 |
(ATT) |
(4.01, 11.39) |
N/A |
(0.97, 2.61) |
(1.26, 2.44) |
ATE Weighting |
7.44 |
N/A |
2.06 |
2.22 |
(ATE) |
(4.14, 10.74) |
N/A |
(1.30, 3.28) |
(1.64, 3.01) |
twang ATT weights |
8.40 |
N/A |
1.57 |
1.76 |
(ATT) |
(5.00, 11.79) |
N/A |
(0.94, 2.61) |
(1.25, 2.47) |
Direct Adjustment |
7.99 |
N/A |
1.80 |
1.80 |
(with linps , ATT) |
(4.86, 11.13) |
N/A |
(1.14, 2.85) |
(1.14, 2.85) |
Task 11. “Double Robust” Approach - Weighting + Adjustment, what is the estimated average causal effect of treatment?
This approach is essentially identical to the weighting analyses done in Task 9. The only change is to add linps
to treated
in the outcome models.
… on Outcome 1 [a continuous outcome]
with ATT weights
The relevant regression approach uses the svydesign
and svyglm
functions from the survey
package.
toywt1.design <- svydesign(ids=~1, weights=~wts1, data=toy) # using ATT weights
dr.out1.wt1 <- svyglm(out1.cost ~ treated + linps, design=toywt1.design)
dr_att_out1 <- tidy(dr.out1.wt1, conf.int = TRUE) %>% filter(term == "treated")
dr_att_out1
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 7.91 1.84 4.29 0.0000221 4.29 11.5
with ATE weights
toywt2.design <- svydesign(ids=~1, weights=~wts2, data=toy) # using ATE weights
dr.out1.wt2 <- svyglm(out1.cost ~ treated + linps, design=toywt2.design)
dr_ate_out1 <- tidy(dr.out1.wt2, conf.int = TRUE) %>% filter(term == "treated")
dr_ate_out1
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 7.01 1.69 4.15 0.0000414 3.69 10.3
with twang
based ATT weights
wts3 <- get.weights(ps.toy, stop.method = "es.mean")
toywt3.design <- svydesign(ids=~1, weights=~wts3, data=toy) # twang ATT weights
dr.out1.wt3 <- svyglm(out1.cost ~ treated + linps, design=toywt3.design)
dr_twangatt_out1 <- tidy(dr.out1.wt3, conf.int = TRUE) %>% filter(term == "treated")
dr_twangatt_out1
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 8.14 1.72 4.72 0.00000330 4.75 11.5
… on Outcome 2 [a binary outcome]
For a binary outcome, we build the outcome model using the quasibinomial, rather than the usual binomial family. We use the same svydesign
information as we built for outcome 1.
Using ATT weights
dr.out2.wt1 <- svyglm(out2 ~ treated + linps, design=toywt1.design,
family=quasibinomial())
dr_att_out2 <- tidy(dr.out2.wt1, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term == "treated")
dr_att_out2
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.59 0.249 1.86 0.0639 0.974 2.59
Using ATE weights
dr.out2.wt2 <- svyglm(out2.event ~ treated + linps, design=toywt2.design,
family=quasibinomial())
dr_ate_out2 <- tidy(dr.out2.wt2, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term == "treated")
dr_ate_out2
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 2.03 0.243 2.91 0.00379 1.26 3.28
Using twang
ATT weights
dr.out2.wt3 <- svyglm(out2 ~ treated + linps, design=toywt3.design,
family=quasibinomial())
dr_twangatt_out2 <- tidy(dr.out2.wt3, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term == "treated")
dr_twangatt_out2
# A tibble: 1 x 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.58 0.265 1.74 0.0828 0.942 2.67
… on Outcome 3 [a time to event]
As before, subjects with out2.event
= “Yes” are truly observed events, while those with out2.event
== “No” are censored before an event can happen to them.
Using ATT weights
The Cox model comparing treated to control, weighting by ATT weights (wts1
), is…
dr.out3.wt1 <- coxph(Surv(out3.time, out2) ~ treated + linps, data=toy, weights=wts1)
dr_att_out3 <- tidy(dr.out3.wt1, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term == "treated")
dr_att_out3
# A tibble: 1 x 8
term estimate std.error robust.se statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.76 0.165 0.171 3.30 0.000950 1.26 2.46
The exp(coef)
output gives the relative hazard of the event comparing treated subjects to control subjects.
And here’s the check of the proportional hazards assumption…
cox.zph(dr.out3.wt1); plot(cox.zph(dr.out3.wt1), var="treated")
chisq df p
treated 2.40 1 0.121
linps 3.74 1 0.053
GLOBAL 6.46 2 0.039

Using ATE weights
dr.out3.wt2 <- coxph(Surv(out3.time, out2) ~ treated + linps, data=toy, weights=wts2)
dr_ate_out3 <- tidy(dr.out3.wt2, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term == "treated")
dr_ate_out3
# A tibble: 1 x 8
term estimate std.error robust.se statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 2.22 0.104 0.172 4.65 0.00000332 1.59 3.11
And here’s the check of the proportional hazards assumption…
cox.zph(dr.out3.wt2); plot(cox.zph(dr.out3.wt2), var="treated")
chisq df p
treated 5.47 1 0.0194
linps 5.12 1 0.0237
GLOBAL 13.08 2 0.0014

Using twang
ATT weights
dr.out3.wt3 <- coxph(Surv(out3.time, out2) ~ treated + linps,
data=toy, weights=wts3)
dr_twangatt_out3 <- tidy(dr.out3.wt3, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term == "treated")
dr_twangatt_out3
# A tibble: 1 x 8
term estimate std.error robust.se statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 treated 1.80 0.185 0.184 3.20 0.00139 1.25 2.58
The exp(coef)
output gives the relative hazard of the event comparing treated subjects to control subjects.
And here’s the check of the proportional hazards assumption…
cox.zph(dr.out3.wt3); plot(cox.zph(dr.out3.wt3), var="treated")
chisq df p
treated 0.645 1 0.42
linps 2.111 1 0.15
GLOBAL 3.245 2 0.20

Task 12. Results
Treatment Effect Estimates
We now can build the table of all of the outcome results we’ve obtained here.
No covariate adjustment |
9.64 |
0.178 |
2.05 |
2.17 |
(unadjusted) |
(6.75, 12.52) |
(0.075, 0.275) |
(1.36, 3.13) |
(1.62, 2.90) |
After 1:1 PS Match |
9.78 |
0.136 |
N/A |
N/A |
(Match : Automated) |
(6.62, 12.94) |
(0.015, 0.256) |
N/A |
N/A |
After 1:1 PS Match |
9.80 |
N/A |
1.69 |
1.88 |
(“Regression” Models) |
(6.61, 12.99) |
N/A |
(1.07, 2.67) |
(1.23, 2.87) |
After PS Subclassification |
5.79 |
N/A |
1.94 |
1.98 |
(“Regression” models, ATE) |
(2.32, 9.26) |
N/A |
(1.11, 9.26) |
(1.41, 2.77) |
ATT Weighting |
7.70 |
N/A |
1.59 |
1.76 |
(ATT) |
(4.01, 11.39) |
N/A |
(0.97, 2.61) |
(1.26, 2.44) |
ATE Weighting |
7.44 |
N/A |
2.06 |
2.22 |
(ATE) |
(4.14, 10.74) |
N/A |
(1.30, 3.28) |
(1.64, 3.01) |
twang ATT weights |
8.40 |
N/A |
1.57 |
1.76 |
(ATT) |
(5.00, 11.79) |
N/A |
(0.94, 2.61) |
(1.25, 2.47) |
Direct Adjustment |
7.99 |
N/A |
1.80 |
1.80 |
(with linps , ATT) |
(4.86, 11.13) |
N/A |
(1.14, 2.85) |
(1.14, 2.85) |
Double Robust |
7.91 |
N/A |
1.59 |
1.76 |
(ATT wts + adj.) |
(4.29, 11.54) |
N/A |
(0.97, 2.59) |
(1.26, 2.46) |
Double Robust |
7.01 |
N/A |
2.03 |
2.22 |
(ATE wts + adj.) |
(3.69, 10.33) |
N/A |
(1.26, 3.28) |
(1.59, 3.11) |
Double Robust |
8.14 |
N/A |
1.58 |
1.80 |
(twang ATT wts + adj.) |
(4.75, 11.53) |
N/A |
(0.94, 2.67) |
(1.25, 2.58) |
So, with the exception of the subclassification approach (which was problematic in terms of observed covariate balance) we observe significant results (indicating higher costs with the treatment, and higher likelihood of experiencing the event, and increased hazard of event occurrence) for every adjustment approach.
Quality of Balance: Standardized Differences and Variance Ratios
We’re looking at the balance across the following 10 covariates and transformations here: covA, covB, covC, covD, covE, covF[middle], covF[high], A squared, BxC
and BxD
, as well as the raw and linear propensity scores …
Most Desirable Values |
Between -10 and +10 |
Between 0.8 and 1.25 |
No Adjustments |
-50 to 97 |
0.63 to 1.61 |
1:1 Propensity Matching |
-15 to 25 |
0.91 to 1.24 |
Subclassification Quintile 1 |
-161 to 212 |
not calculated above |
Quintile 2 |
-32 to 71 |
not calculated above |
Quintile 3 |
-33 to 60 |
not calculated above |
Quintile 4 |
-32 to 10 |
not calculated above |
Quintile 5 |
-32 to 17 |
not calculated above |
Propensity Weighting, ATT |
-12 to 10 |
0.72 to 1.12 |
Propensity Weighting, ATE |
-9 to 16 |
0.56 to 1.13 |
Quality of Balance: Rubin’s Rules
“Pass” Range, per Rubin |
0 to 50 |
0.5 to 2.0 |
0.5 to 2.0 |
No Adjustments |
85.9 |
0.63 |
0.72 to 1.76 |
1:1 Propensity Matching |
25.3 |
1.24 |
0.80 to 1.30 |
Subclassification: Quintile 1 |
125.8 |
0.01 |
0.00 to 0.96 |
Quintile 2 |
14.8 |
2.17 |
0.42 to 11.33 |
Quintile 3 |
22.1 |
1.05 |
0.41 to 2.20 |
Quintile 4 |
4.4 |
0.93 |
0.56 to 1.23 |
Quintile 5 |
0.2 |
1.60 |
0.56 to 1.46 |
Propensity Weighting, ATT |
-9.1 |
0.79 |
Not evaluated |
Propensity Weighting, ATE |
16.5 |
0.56 |
Not evaluated |
Clearly, the matching and propensity weighting show improvement over the initial (no adjustments) results, although neither is completely satisfactory in terms of all covariates. In practice, I would be comfortable with either a 1:1 match or a weighting approach, I think. It isn’t likely that the subclassification will get us anywhere useful in terms of balance. Rubin’s Rule 3 could also be applied after weighting on the propensity score.
What is a Sensitivity Analysis for Matched Samples?
We’ll study a formal sensitivity analysis approach for matched samples. Note well that this specific approach is appropriate only when we have
- a statistically significant conclusion
- from a matched samples analysis using the propensity score.
The Sensitivity Parameter, \(\Gamma\)
Suppose we have two units (subjects, patients), say, \(j\) and \(k\), with the same observed covariate values x but different probabilities \(p\) of treatment assignment (possibly due to some unobserved covariate), so that x\(_j\) = x\(_k\) but that possibly \(p_j \neq p_k\).
Units \(j\) and \(k\) might be matched to form a matched pair in our attempt to control overt bias due to the covariates x.
- The odds that units \(j\) and \(k\) receive the treatment are, respectively, \(\frac{p_j}{1 - p_j}\) and \(\frac{p_k}{1 - p_k}\), and the odds ratio is thus the ratio of these odds.
Imagine that we knew that this odds ratio for units with the same x was at most some number \(\Gamma\), so that \(\Gamma \geq 1\). That is,
\[
\frac{1}{\Gamma} \leq \frac{p_j(1 - p_j)}{p_k(1 - p_k)} \leq \Gamma
\]
We call \(\Gamma\) the sensitivity parameter, and it is the basis for our sensitivity analyses.
- If \(\Gamma = 1\), then \(p_j = p_k\) whenever x\(_j\) = x\(_k\), so the study would be free of hidden bias, and standard statistical methods designed for randomized trials would apply.
If \(\Gamma = 2\), then two units who appear similar in that they have the same set of observed covariates x, could differ in their odds of receiving the treatment by as much as a factor of 2, so that one could be twice as likely as the other to receive the treatment.
So \(\Gamma\) is a value between 1 and \(\infty\) where the size of \(\Gamma\) indicates the degree of a departure from a study free of hidden bias.
Interpreting the Sensitivity Parameter, \(\Gamma\)
Again, \(\Gamma\) is a measure of the degree of departure from a study that is free of hidden bias.
A sensitivity analysis will consider possible values of \(\Gamma\) and show how the inference for our outcomes might change under different levels of hidden bias, as indexed by \(\Gamma\).
- A study is sensitive if values of \(\Gamma\) close to 1 could lead to inferences that are very different from those obtained assuming the study is free of hidden bias.
- A study is insensitive (a good thing here) if extreme values of \(\Gamma\) are required to alter the inference.
When we perform this sort of sensitivity analysis, we will specify different levels of hidden bias (different \(\Gamma\) values) and see how large a \(\Gamma\) we can have while still retaining the fundamental conclusions of the matched outcomes analysis.
Task 13. Sensitivity Analysis for Matched Samples, Outcome 1, using rbounds
In our matched sample analysis, for outcome 1 (cost) in the toy example, we saw a statistically significant result. A formal sensitivity analysis is called for, as a result, and we will accomplish one for this quantitative outcome, using the rbounds
package.
The rbounds
package is designed to work with the output from Matching
, and can calculate Rosenbaum sensitivity bounds for the treatment effect, which help us understand the impact of hidden bias needed to invalidate our significant conclusions from the matched samples analysis.
Rosenbaum Bounds for the Wilcoxon Signed Rank test (Quantitative outcome)
We have already used the Match function from the Matching package to develop a matched sample. Given this, we need only run the psens
function from the rbounds
package to obtain sensitivity results.
X <- toy$linps ## matching on the linear propensity score
Tr <- as.logical(toy$treated)
Y <- toy$out1.cost
match1 <- Match(Tr=Tr, X=X, Y = Y, M = 1, replace=FALSE, ties=FALSE)
summary(match1)
Estimate... 9.7857
SE......... 1.6131
T-stat..... 6.0664
p.val...... 1.3083e-09
Original number of observations.............. 400
Original number of treated obs............... 140
Matched number of observations............... 140
Matched number of observations (unweighted). 140
psens(match1, Gamma = 5, GammaInc = 0.25)
Rosenbaum Sensitivity Test for Wilcoxon Signed Rank P-Value
Unconfounded estimate .... 0
Gamma Lower bound Upper bound
1.00 0 0.0000
1.25 0 0.0000
1.50 0 0.0003
1.75 0 0.0030
2.00 0 0.0160
2.25 0 0.0524
2.50 0 0.1232
2.75 0 0.2286
3.00 0 0.3574
3.25 0 0.4927
3.50 0 0.6190
3.75 0 0.7265
4.00 0 0.8114
4.25 0 0.8745
4.50 0 0.9190
4.75 0 0.9491
5.00 0 0.9688
Note: Gamma is Odds of Differential Assignment To
Treatment Due to Unobserved Factors
If the study were free of hidden bias, that is, if \(\Gamma = 1\), then there would be strong evidence that the treated patients had higher costs, and the specific Wilcoxon signed rank test we’re looking at here shows a \(p\) value < 0.0001. The sensitivity analysis we’ll conduct now asks how this conclusion might be changed by hidden biases of various magnitudes, depending on the significance level we plan to use in our test.
Specifying The Threshold \(\Gamma\) value
From the output above, find the \(\Gamma\) value where the upper bound for our \(p\) value slips from “statistically significant” to “not significant” territory.
- We’re doing a two-tailed test, with a 95% confidence level, so the \(\Gamma\) statistic for this situation is between 2.0 and 2.25, since that is the point where the upper bound for the p value crosses the threshold of \(\alpha/2 = 0.025\).
So this study’s conclusion (that treated patients had significantly higher costs) would still hold even in the face of a hidden bias with \(\Gamma = 2\), but not with \(\Gamma = 2.25\).
The tipping point for the sensitivity parameter is a little over 2.0. To explain away the observed association between treatment and this outcome (cost), a hidden bias or unobserved covariate would need to increase the odds of treatment by more than a factor of \(\Gamma = 2\).
Returning to the output:
- If instead we were doing a one-tailed test with a 90% confidence level, then the \(\Gamma\) statistic would be between 2.25 and 2.50, since that is where the upper bound for the p value crosses \(\alpha = 0.10\).
Interpreting \(\Gamma\) appropriately
\(\Gamma\) tells you only how big a bias is needed to change the answer. By itself, it says NOTHING about the likelihood that a bias of that size is present in your study, except that, of course, smaller biases hide more effectively than large ones, on average.
- In some settings, we’ll think of \(\Gamma\) in terms of small (< 1.5), modest (1.5 - 2.5), moderate (2.5 - 4) and large (> 4) hidden bias requirements. But these are completely arbitrary distinctions, and I can provide no good argument for their use.
The only defense against hidden bias affecting your conclusions is to try to reduce the potential for hidden bias in the first place. We work on this via careful design of observational studies, especially by including as many different dimensions of the selection problem as possible in your propensity model.
Alternative Descriptions of \(\Gamma\)
As we see in Chapter 9 of Rosenbaum’s Observation and Experiment, we can describe a \(\Gamma\) = 2 as being equivalent to a range of potential values of \(\Theta_p\) from 0.33 to 0.67, and values of \(\Lambda = 3\) and \(\Delta = 5\). \(\Theta_p\) provides an estimate of the chance that the first person in a pair is the treated subject. \(\Lambda\) and \(\Delta\) refer to the amplification of sensitivity analysis, with reference to a spurious associated between treatment received and outcome observed in the absence of a treatment effect. The odds that the first person in a pair is treated rather than control is bounded by \(\Lambda\) and \(1/\Lambda\). The parameter \(\Delta\) defines the odds that the paired difference in outcomes is greater than 0 (as compared to less than 0) if there is in fact no treatment effect.
An Alternate Approach - the Hodges-Lehman estimate
hlsens(match1)
Rosenbaum Sensitivity Test for Hodges-Lehmann Point Estimate
Unconfounded estimate .... 10
Gamma Lower bound Upper bound
1 10.00000 10.0
2 4.00000 16.1
3 0.49998 19.1
4 -1.50000 21.6
5 -3.50000 23.1
6 -5.00000 24.6
Note: Gamma is Odds of Differential Assignment To
Treatment Due to Unobserved Factors
If the \(\Gamma\) value is 2.0, then this implies that the Hodges-Lehmann estimate might be as low as 4 or as high as 16.1 (it is 10.0 in the absence of hidden bias in this case - when \(\Gamma\) = 0.)
What about other types of outcomes?
The rbounds
package can evaluate binary outcomes using the binarysens
and Fishersens
functions.
Survival outcomes can be assessed, too, but not, I believe, using rbounds
unless there is no censoring. Some time back, I built a spreadsheet for this task, which I’ll be happy to share.
What about when we match 1:2 or 1:3 instead of 1:1?
The mcontrol
function in the rbounds
package can be helpful in such a setting.
Wrapup
If you run this script, you’ll wind up with a version of the toy
tibble that contains 400 observations on 28 variables, along with a toy.codebook
list.
You’ll also have two new functions, called szd
and rubin3
, that, with some modification, may be useful elsewhere.
To drop everything else in the global environment created by this Markdown file, run the code that follows.
rm(list = c("adj.m.out1", "adj.m.out1.tidy", "adj.m.out2", "adj.m.out2_tidy",
"adj.m.out3", "adj.m.out3_tidy", "adj.reg.out1", "adj.reg.out2",
"adj.reg.out3", "adj.s.out3", "adj_out1", "adj_out2", "adj_out3",
"adjout1.wt1", "adjout1.wt2", "adjout1.wt3", "adjout2.wt1", "adjout2.wt2",
"adjout2.wt3", "adjout3.wt1", "adjout3.wt2", "adjout3.wt3", "alert",
"b", "bal.after.wts1", "bal.after.wts2", "bal.before.wts1", "bal.before.wts2",
"bal.wts1", "bal.wts2", "balance.ate.weights", "balance.att.weights",
"cov.sub", "covlist", "covnames", "covs", "d.all", "d.q1", "d.q2",
"d.q3", "d.q4", "d.q5", "decim", "dr.out1.wt1", "dr.out1.wt2",
"dr.out1.wt3", "dr.out2.wt1", "dr.out2.wt2", "dr.out2.wt3", "dr.out3.wt1",
"dr.out3.wt2", "dr.out3.wt3", "dr_ate_out1", "dr_ate_out2", "dr_ate_out3",
"dr_att_out1", "dr_att_out2", "dr_att_out3", "dr_twangatt_out1",
"dr_twangatt_out2", "dr_twangatt_out3", "est.st", "factorlist",
"i", "match_szd", "match_vrat", "match1", "match1.out1", "match1.out1.ATE",
"match1_out2", "matched_mixedmodel.out1", "matches", "mb1", "p",
"post.szd", "post.vratio", "pre.szd", "pre.vratio", "ps.toy",
"psmodel", "quin1", "quin1.out1", "quin1.out2", "quin2", "quin2.out1",
"quin2.out2", "quin3", "quin3.out1", "quin3.out2", "quin4", "quin4.out1",
"quin4.out2", "quin5", "quin5.out1", "quin5.out2", "res_matched_1",
"res_unadj_1", "res_unadj_2_oddsratio", "res_unadj_2_or", "res_unadj_2_riskdiff",
"res_unadj_3", "rubin1.match", "rubin1.q1", "rubin1.q2", "rubin1.q3",
"rubin1.q4", "rubin1.q5", "rubin1.sub", "rubin1.unadj", "rubin2.match",
"rubin2.q1", "rubin2.q2", "rubin2.q3", "rubin2.q4", "rubin2.q5",
"rubin2.sub", "rubin2.unadj", "rubin3.both", "rubin3.matched",
"rubin3.q1", "rubin3.q2", "rubin3.q3", "rubin3.q4", "rubin3.q5",
"rubin3.unadj", "se.q1", "se.q2", "se.q3", "se.q4", "se.q5",
"se.st", "strat.result1", "strat.result2", "strat.result3",
"temp", "toy.matchedsample", "toy.rubin3",
"toy.szd", "toy_df", "toywt1.design", "toywt2.design", "toywt3.design",
"Tr", "unadj.out1", "unadj.out2", "unadj.out3", "varlist", "wt_ate_results1",
"wt_ate_results2", "wt_ate_results3", "wt_att_results1", "wt_att_results2",
"wt_att_results3", "wt_twangatt_results1", "wt_twangatt_results2",
"wt_twangatt_results3", "wts3", "X", "Y"))
LS0tDQp0aXRsZTogIlRoZSB0b3kgRXhhbXBsZSINCmF1dGhvcjogIlRob21hcyBFLiBMb3ZlLCBQaC5ELiINCmRhdGU6ICdWZXJzaW9uOiBgciBTeXMuRGF0ZSgpYCcNCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICAgIHRoZW1lOiBwYXBlcg0KICAgICAgaGlnaGxpZ2h0OiB0ZXh0bWF0ZQ0KICAgICAgdG9jOiBUUlVFDQogICAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICAgIG51bWJlcl9zZWN0aW9uczogVFJVRQ0KICAgICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQotLS0NCg0KIyBTZXR1cCANCg0KYGBge3IgcGFja2FnZXMsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KbGlicmFyeShrbml0cikNCm9wdHNfY2h1bmskc2V0KGNvbW1lbnQgPSBOQSwNCiAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSkNCm9wdGlvbnMobWF4LnByaW50PSIyNTAiKQ0Kb3B0c19rbml0JHNldCh3aWR0aD03NSkNCg0KbGlicmFyeShza2ltcikNCmxpYnJhcnkodGFibGVvbmUpDQpsaWJyYXJ5KGJyb29tKQ0KbGlicmFyeShFcGkpDQpsaWJyYXJ5KHN1cnZpdmFsKQ0KbGlicmFyeShNYXRjaGluZykNCmxpYnJhcnkoY29iYWx0KQ0KbGlicmFyeShsbWU0KQ0KbGlicmFyeSh0d2FuZykNCmxpYnJhcnkoc3VydmV5KQ0KbGlicmFyeShyYm91bmRzKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCiMjIE5vdGUgdGhhdCB3ZSB3aWxsIGFsc28gdXNlIHRoZSBicm9vbS5taXhlZCBwYWNrYWdlDQojIyBidXQgd2Ugd29uJ3QgbG9hZCBpdCBoZXJlDQoNCmRlY2ltIDwtIGZ1bmN0aW9uKHgsIGspIGZvcm1hdChyb3VuZCh4LCBrKSwgbnNtYWxsPWspDQoNCnRoZW1lX3NldCh0aGVtZV9idygpKQ0KYGBgDQoNCiMjIFRoZSBEYXRhIFNldCANCg0KVGhlIERhdGEgU2V0IGlzIDEwMCUgZmljdGlvbmFsLCBhbmQgaXMgYXZhaWxhYmxlIGFzIGB0b3kuY3N2YCBvbiB0aGUgY291cnNlIHdlYnNpdGUuIA0KDQotIEl0IGNvbnRhaW5zIGRhdGEgb24gNDAwIHN1YmplY3RzICgxNDAgdHJlYXRlZCBhbmQgMjYwIGNvbnRyb2xzKSBvbiB0cmVhdG1lbnQgc3RhdHVzLCBzaXggY292YXJpYXRlcywgYW5kIHRocmVlIG91dGNvbWVzLCB3aXRoIG5vIG1pc3Npbmcgb2JzZXJ2YXRpb25zIGFueXdoZXJlLiANCi0gV2UgYXNzdW1lIHRoYXQgYSBsb2dpY2FsIGFyZ3VtZW50IHN1Z2dlc3RzIHRoYXQgdGhlIHNxdWFyZSBvZiBgY292QWAsIGFzIHdlbGwgYXMgdGhlIGludGVyYWN0aW9ucyBvZiBgY292QmAgd2l0aCBgY292Q2AgYW5kIHdpdGggYGNvdkRgIHNob3VsZCBiZSByZWxhdGVkIHRvIHRyZWF0bWVudCBhc3NpZ25tZW50LCBhbmQgdGh1cyBzaG91bGQgYmUgaW5jbHVkZWQgaW4gb3VyIHByb3BlbnNpdHkgbW9kZWwuDQotIE91ciBvYmplY3RpdmUgaXMgdG8gZXN0aW1hdGUgdGhlIGF2ZXJhZ2UgY2F1c2FsIGVmZmVjdCBvZiB0cmVhdG1lbnQgKGFzIGNvbXBhcmVkIHRvIGNvbnRyb2wpIG9uIGVhY2ggb2YgdGhlIHRocmVlIG91dGNvbWVzLCB3aXRob3V0IHByb3BlbnNpdHkgYWRqdXN0bWVudCwgYW5kIHRoZW4gd2l0aCBwcm9wZW5zaXR5IG1hdGNoaW5nLCBzdWJjbGFzc2lmaWNhdGlvbiwgd2VpZ2h0aW5nIGFuZCByZWdyZXNzaW9uIGFkanVzdG1lbnQgdXNpbmcgdGhlIHByb3BlbnNpdHkgc2NvcmUuDQoNCmBgYHtyIGxvYWRfdG95fQ0KdG95IDwtIHJlYWRfY3N2KCJkYXRhL3RveS5jc3YiKSAlPiUNCiAgdHlwZS5jb252ZXJ0KGFzLmlzID0gRkFMU0UpICU+JQ0KICBtdXRhdGUoc3ViamVjdCA9IGFzLmNoYXJhY3RlcihzdWJqZWN0KSkNCg0KdG95DQpgYGANCg0KIyMgVGhlIENvZGVib29rIGZvciB0aGUgYHRveWAgZGF0YQ0KDQpgYGB7cn0NCnRveS5jb2RlYm9vayA8LSBiYXNlOjpkYXRhLmZyYW1lKA0KICAgIFZhcmlhYmxlID0gZHB1dChuYW1lcyh0b3kpKSwNCiAgICBUeXBlID0gYygiU3ViamVjdCBJRCIsICIyLWxldmVsIGNhdGVnb3JpY2FsICgwLzEpIiwgIlF1YW50aXRhdGl2ZSAoMiBkZWNpbWFsIHBsYWNlcykiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICIyLWxldmVsIGNhdGVnb3JpY2FsICgwLzEpIiwgIlF1YW50aXRhdGl2ZSAoMSBkZWNpbWFsIHBsYWNlKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIlF1YW50aXRhdGl2ZSAoMSBkZWNpbWFsIHBsYWNlKSIsICJJbnRlZ2VyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiMy1sZXZlbCBvcmRpbmFsIGZhY3RvciIsICJRdWFudGl0YXRpdmUgb3V0Y29tZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIkJpbmFyeSBvdXRjb21lIChkaWQgZXZlbnQgb2NjdXI/KSIsICJUaW1lIHRvIGV2ZW50IG91dGNvbWUiKSwNCiAgICBOb3RlcyA9IGMoImxhYmVscyBhcmUgVF8wMDEgdG8gVF80MDAiLCAiMCA9IGNvbnRyb2wsIDEgPSB0cmVhdGVkIiwgDQogICAgICAgICAgICAgICJyZWFzb25hYmxlIHZhbHVlcyByYW5nZSBmcm9tIDAgdG8gNiIsICIwID0gbm8sIDEgPSB5ZXMiLA0KICAgICAgICAgICAgICAicGxhdXNpYmxlIHJhbmdlIDMtMjAiLCAicGxhdXNpYmxlIHJhbmdlIDMtMjAiLCAicGxhdXNpYmxlIHJhbmdlIDMtMjAiLCANCiAgICAgICAgICAgICAgIjEgPSBMb3csIDIgPSBNaWRkbGUsIDMgPSBIaWdoIiwNCiAgICAgICAgICAgICAgInR5cGljYWwgdmFsdWVzIDEwLTEwMCIsICJZZXMvTm8gKG5vdGU6IGV2ZW50IGlzIGJhZCkiLCANCiAgICAgICAgICAgICAgIlRpbWUgYmVmb3JlIGV2ZW50IGlzIG9ic2VydmVkIG9yIHN1YmplY3QgZXhpdHMgc3R1ZHkgKGNlbnNvcmVkKSwgcmFuZ2UgaXMgNzYtMTU0IHdlZWtzIikpDQoNCnRveS5jb2RlYm9vaw0KYGBgDQoNCldpdGggcmVnYXJkIHRvIHRoZSBgb3V0My50aW1lYCB2YXJpYWJsZSwgc3ViamVjdHMgd2l0aCBgb3V0Mi5ldmVudGAgPSBObyB3ZXJlIGNlbnNvcmVkLCBzbyB0aGF0IGBvdXQyLmV2ZW50YCA9IFllcyBpbmRpY2F0ZXMgYW4gb2JzZXJ2ZWQgZXZlbnQuDQoNCiMjICJTa2ltbWVkIiBTdW1tYXJpZXMsIHdpdGhpbiB0cmVhdG1lbnQgZ3JvdXBzDQoNCmBgYHtyfQ0KdG95ICU+JSBncm91cF9ieSh0cmVhdGVkKSAlPiUgc2tpbV93aXRob3V0X2NoYXJ0cygtc3ViamVjdCkNCmBgYA0KDQojIyBUYWJsZSAxIA0KDQpgYGB7cn0NCmZhY3Rvcmxpc3QgPC0gYygiY292QiIsICJjb3ZGIiwgIm91dDIuZXZlbnQiKQ0KDQpDcmVhdGVUYWJsZU9uZShkYXRhID0gdG95LA0KICAgIHZhcnMgPSBkcHV0KG5hbWVzKHNlbGVjdCh0b3ksIC1zdWJqZWN0LCAtdHJlYXRlZCkpKSwgDQogICAgc3RyYXRhID0gInRyZWF0ZWQiLCBmYWN0b3JWYXJzID0gZmFjdG9ybGlzdCkNCmBgYA0KDQojIERhdGEgTWFuYWdlbWVudCBhbmQgQ2xlYW51cA0KDQojIyBSYW5nZSBDaGVja3MgZm9yIFF1YW50aXRhdGl2ZSAoY29udGludW91cykgVmFyaWFibGVzDQoNCkNoZWNraW5nIGFuZCBjbGVhbmluZyB0aGUgcXVhbnRpdGF0aXZlIHZhcmlhYmxlcyBpcyBwcmV0dHkgc3RyYWlnaHRmb3J3YXJkIC0gdGhlIG1haW4gdGhpbmcgSSdsbCBkbyBhdCB0aGlzIHN0YWdlIGlzIGNoZWNrIHRoZSByYW5nZXMgb2YgdmFsdWVzIHNob3duIHRvIGVuc3VyZSB0aGF0IHRoZXkgbWF0Y2ggdXAgd2l0aCB3aGF0IEknbSBleHBlY3RpbmcuIEhlcmUsIGFsbCBvZiB0aGUgcXVhbnRpdGF0aXZlIHZhcmlhYmxlcyBoYXZlIHZhbHVlcyB0aGF0IGZhbGwgd2l0aGluIHRoZSAicGVybWlzc2libGUiIHJhbmdlIGRlc2NyaWJlZCBieSBteSBjb2RlYm9vaywgc28gd2UnbGwgYXNzdW1lIHRoYXQgZm9yIHRoZSBtb21lbnQsIHdlJ3JlIE9LIG9uIGBzdWJqZWN0YCAoanVzdCBhIG1lYW5pbmdsZXNzIGNvZGUsIHJlYWxseSksIGBjb3ZBYCwgYGNvdkNgLCBgY292RGAsIGBjb3ZFYCwgYG91dDEuY29zdGAgYW5kIGBvdXQzLnRpbWVgLCBhbmQgd2Ugc2VlIG5vIG1pc3NpbmduZXNzLg0KDQojIyBSZXN0YXRpbmcgQ2F0ZWdvcmljYWwgSW5mb3JtYXRpb24gaW4gSGVscGZ1bCBXYXlzDQoNClRoZSBjbGVhbnVwIG9mIHRoZSB0b3kgZGF0YSBmb2N1c2VzLCBhcyBpdCB1c3VhbGx5IGRvZXMsIG9uIHZhcmlhYmxlcyB0aGF0IGNvbnRhaW4gKipjYXRlZ29yaWVzKiogb2YgaW5mb3JtYXRpb24sIHJhdGhlciB0aGFuIHNpbXBsZSBjb3VudHMgb3IgbWVhc3VyZXMsIHJlcHJlc2VudGVkIGluIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMuIA0KDQojIyMgUmUtZXhwcmVzc2luZyBCaW5hcnkgVmFyaWFibGVzIGFzIE51bWJlcnMgYW5kIEZhY3RvcnMNCg0KV2UgaGF2ZSB0aHJlZSBiaW5hcnkgdmFyaWFibGVzIChgdHJlYXRlZGAsIGBjb3ZCYCBhbmQgYG91dDIuZXZlbnRgKS4gQSBtYWpvciBpc3N1ZSBpbiBkZXZlbG9waW5nIHRoZXNlIHZhcmlhYmxlcyBpcyB0byBlbnN1cmUgdGhhdCB0aGUgZGlyZWN0aW9uIG9mIHJlc3VsdGluZyBvZGRzIHJhdGlvcyBhbmQgcmlzayBkaWZmZXJlbmNlcyBhcmUgY29uc2lzdGVudCBhbmQgdGhhdCBjcm9zcy10YWJ1bGF0aW9ucyBhcmUgaW4gc3RhbmRhcmQgZXBpZGVtaW9sb2dpY2FsIGZvcm1hdC4gDQoNCkl0IHdpbGwgYmUgdXNlZnVsIHRvIGRlZmluZSBiaW5hcnkgdmFyaWFibGVzIGluIHR3byB3YXlzOiANCg0KLSBhcyBhIG51bWVyaWMgaW5kaWNhdG9yIHZhcmlhYmxlIHRha2luZyBvbiB0aGUgdmFsdWVzIDAgKG1lYW5pbmcgIm5vdCBoYXZpbmcgdGhlIGNoYXJhY3RlcmlzdGljIGJlaW5nIHN0dWRpZWQiKSBvciAxIChtZWFuaW5nICJoYXZpbmcgdGhlIGNoYXJhY3RlcmlzdGljIGJlaW5nIHN0dWRpZWQiKSANCi0gYXMgYSB0ZXh0IGZhY3RvciAtIHdpdGggdGhlIGxldmVscyBvZiBvdXIga2V5IGV4cG9zdXJlIGFuZCBvdXRjb21lcyBhcnJhbmdlZCBzbyB0aGF0ICJoYXZpbmcgdGhlIGNoYXJhY3RlcmlzdGljIiBwcmVjZWRlcyAibm90IGhhdmluZyB0aGUgY2hhcmFjdGVyaXN0aWMiIGluIFIgd2hlbiB5b3UgY3JlYXRlIGEgdGFibGUsIGJ1dCB0aGUgY292YXJpYXRlcyBzaG91bGQgc3RpbGwgYmUgTm8vWWVzLg0KDQpTbyB3aGF0IGRvIHdlIGN1cnJlbnRseSBoYXZlPyBGcm9tIHRoZSBvdXRwdXQgYmVsb3csIGl0IGxvb2tzIGxpa2UgYHRyZWF0ZWRgIGFuZCBgY292QmAgYXJlIG51bWVyaWMsIDAvMSB2YXJpYWJsZXMsIHdoaWxlIGBvdXQyLmV2ZW50YCBpcyBhIGZhY3RvciB3aXRoIGxldmVscyAiTm8iIGFuZCB0aGVuICJZZXMiDQoNCmBgYHtyfQ0KdG95ICU+JSBzZWxlY3QodHJlYXRlZCwgY292Qiwgb3V0Mi5ldmVudCkgJT4lIHN1bW1hcnkoKQ0KYGBgDQoNClNvLCB3ZSdsbCBjcmVhdGUgZmFjdG9ycyBmb3IgYHRyZWF0ZWRgIGFuZCBgY292QmA6DQoNCmBgYHtyfQ0KdG95JHRyZWF0ZWRfZiA8LSBmYWN0b3IodG95JHRyZWF0ZWQsIGxldmVscyA9IGMoMSwwKSwgDQogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJUcmVhdGVkIiwgIkNvbnRyb2wiKSkNCnRveSRjb3ZCX2YgPC0gZmFjdG9yKHRveSRjb3ZCLCBsZXZlbHMgPSBjKDAsMSksIA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiTm8gQiIsICJIYXMgQiIpKQ0KYGBgDQoNCkZvciBgb3V0Mi5ldmVudGAsIG9uIHRoZSBvdGhlciBoYW5kLCB3ZSBkb24ndCBoYXZlIGVpdGhlciBxdWl0ZSB0aGUgd2F5IHdlIG1pZ2h0IHdhbnQgaXQuIEFzIHlvdSBzZWUgaW4gdGhlIHN1bW1hcnkgb3V0cHV0LCB3ZSBoYXZlIHR3byBjb2RlcyBmb3IgYG91dDIuZXZlbnRgIC0gZWl0aGVyIE5vIG9yIFllcywgaW4gdGhhdCBvcmRlci4gQnV0IHdlIHdhbnQgWWVzIHRvIHByZWNlZGUgTm8gKGFuZCBJJ2QgbGlrZSBhIG1vcmUgbWVhbmluZ2Z1bCBuYW1lKS4gU28gSSByZWRlZmluZSB0aGUgZmFjdG9yIHZhcmlhYmxlLCBhcyBmb2xsb3dzLg0KDQpgYGB7cn0NCnRveSRvdXQyX2YgPC0gZmFjdG9yKHRveSRvdXQyLmV2ZW50LCBsZXZlbHMgPSBjKCJZZXMiLCJObyIpLCANCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkV2ZW50IiwiTm8gRXZlbnQiKSkNCmBgYA0KDQpUbyBvYnRhaW4gYSBudW1lcmljYWwgKDAgb3IgMSkgdmVyc2lvbiBvZiBgb3V0Mi5ldmVudGAgd2UgY2FuIHVzZSBSJ3MgYGFzLm51bWVyaWNgIGZ1bmN0aW9uIC0gdGhlIHByb2JsZW0gaXMgdGhhdCB0aGlzIHByb2R1Y2VzIHZhbHVlcyBvZiAxIChmb3IgTm8pIGFuZCAyIChmb3IgWWVzKSwgcmF0aGVyIHRoYW4gMCBhbmQgMS4gU28sIEkgc2ltcGx5IHN1YnRyYWN0IDEgZnJvbSB0aGUgcmVzdWx0LCBhbmQgd2UgZ2V0IHdoYXQgd2UgbmVlZC4NCg0KYGBge3J9DQp0b3kkb3V0MiA8LSBhcy5udW1lcmljKHRveSRvdXQyLmV2ZW50KSAtIDENCmBgYA0KDQojIyMgVGVzdGluZyBZb3VyIENvZGUgLSBTYW5pdHkgQ2hlY2tzDQoNCkJlZm9yZSBJIG1vdmUgb24sIEknbGwgZG8gYSBzZXJpZXMgb2Ygc2FuaXR5IGNoZWNrcyB0byBtYWtlIHN1cmUgdGhhdCBvdXIgbmV3IHZhcmlhYmxlcyBhcmUgZGVmaW5lZCBhcyB3ZSB3YW50IHRoZW0sIGJ5IHByb2R1Y2luZyBhIHNlcmllcyBvZiBzbWFsbCB0YWJsZXMgY29tcGFyaW5nIHRoZSBuZXcgdmFyaWFibGVzIHRvIHRob3NlIG9yaWdpbmFsbHkgaW5jbHVkZWQgaW4gdGhlIGRhdGEgc2V0Lg0KDQpgYGB7cn0NCnRveSAlPiUgY291bnQodHJlYXRlZCwgdHJlYXRlZF9mKQ0KDQp0b3kgJT4lIGNvdW50KGNvdkIsIGNvdkJfZikNCg0KdG95ICU+JSBjb3VudChvdXQyLmV2ZW50LCBvdXQyX2YsIG91dDIpDQpgYGANCg0KRXZlcnl0aGluZyBsb29rcyBPSzoNCg0KLSBgdHJlYXRlZF9mYCBjb3JyZWN0bHkgY2FwdHVyZXMgdGhlIGluZm9ybWF0aW9uIGluIGB0cmVhdGVkYCwgd2l0aCB0aGUgbGFiZWwgVHJlYXRlZCBhYm92ZSB0aGUgbGFiZWwgQ29udHJvbCBpbiB0aGUgcm93cyBvZiB0aGUgdGFibGUsIGZhY2lsaXRhdGluZyBzdGFuZGFyZCBlcGlkZW1pb2xvZ2ljYWwgZm9ybWF0Lg0KLSBgY292Ql9mYCBhbHNvIGNvcnJlY3RseSBjYXB0dXJlcyB0aGUgYGNvdkJgIGluZm9ybWF0aW9uLCBwbGFjaW5nICJIYXMgQiIgbGFzdC4NCi0gYG91dDJfZmAgY29ycmVjdGx5IGNhcHR1cmVzIGFuZCByZS1vcmRlcnMgdGhlIGxhYmVscyBmcm9tIHRoZSBvcmlnaW5hbCBgb3V0Mi5ldmVudGANCi0gYG91dDJgIHNob3dzIHRoZSBkYXRhIGNvcnJlY3RseSAoYXMgY29tcGFyZWQgdG8gdGhlIG9yaWdpbmFsIGBvdXQyLmV2ZW50YCkgd2l0aCAwLTEgY29kaW5nLg0KDQojIyBEZWFsaW5nIHdpdGggVmFyaWFibGVzIGluY2x1ZGluZyBNb3JlIHRoYW4gVHdvIENhdGVnb3JpZXMNCg0KV2hlbiB3ZSBoYXZlIGEgbXVsdGktY2F0ZWdvcmljYWwgKG1vcmUgdGhhbiB0d28gY2F0ZWdvcmllcykgdmFyaWFibGUsIGxpa2UgYGNvdkZgLCB3ZSB3aWxsIHdhbnQgdG8gaGF2ZQ0KDQotIGJvdGggYSB0ZXh0IHZlcnNpb24gb2YgdGhlIHZhcmlhYmxlIHdpdGggc2Vuc2libHkgb3JkZXJlZCBsZXZlbHMsIGFzIGEgZmFjdG9yIGluIFIsIGFzIHdlbGwgYXMgDQotIGEgc2VyaWVzIG9mIG51bWVyaWMgaW5kaWNhdG9yIHZhcmlhYmxlcyAodGFraW5nIHRoZSB2YWx1ZXMgMCBvciAxKSBmb3IgdGhlIGluZGl2aWR1YWwgbGV2ZWxzLg0KDQpgYGB7cn0NCnRveSAlPiUgY291bnQoY292RikNCmBgYA0KDQpGcm9tIHRoZSBgc3VtbWFyeWAgb3V0cHV0LCB3ZSBjYW4gc2VlIHRoYXQgd2UncmUgYWxsIHNldCBmb3IgdGhlIHRleHQgdmVyc2lvbiBvZiBgY292RmAsIGFzIHdoYXQgd2UgaGF2ZSBjdXJyZW50bHkgaXMgYSBmYWN0b3Igd2l0aCB0aHJlZSBsZXZlbHMsIGxhYmVsZWQgMS1Mb3csIDItTWlkZGxlIGFuZCAzLUhpZ2guIFRoaXMgbGlzdCBvZiB2YXJpYWJsZXMgc2hvdWxkIHdvcmsgb3V0IHdlbGwgZm9yIHVzLCBhcyBpdCBwcmVzZXJ2ZXMgdGhlIG9yZGVyaW5nIGluIGEgdGFibGUgYW5kIHBlcm1pdHMgdXMgdG8gc2VlIHRoZSBuYW1lcywgdG9vLiBJZiB3ZSdkIHVzZWQganVzdCBMb3csIE1pZGRsZSBhbmQgSGlnaCwgdGhlbiB3aGVuIFIgc29ydGVkIGEgdGFibGUgaW50byBhbHBoYWJldGljYWwgb3JkZXIsIHdlJ2QgaGF2ZSBIaWdoLCB0aGVuIExvdywgdGhlbiBNaWRkbGUgLSBub3QgaWRlYWwuDQoNCiMjIyBQcmVwYXJpbmcgSW5kaWNhdG9yIFZhcmlhYmxlcyBmb3IgYGNvdkZgDQoNClNvLCBhbGwgd2UgbmVlZCB0byBkbyBmb3IgYGNvdkZgIGlzIHByZXBhcmUgaW5kaWNhdG9yIHZhcmlhYmxlcy4gV2UgY2FuIGVpdGhlciBkbyB0aGlzIGZvciBhbGwgbGV2ZWxzLCBvciBzZWxlY3Qgb25lIGFzIHRoZSBiYXNlbGluZSwgYW5kIGRvIHRoZSByZXN0LiBIZXJlLCBJJ2xsIHNob3cgdGhlbSBhbGwuDQoNCmBgYHtyfQ0KdG95IDwtIHRveSAlPiUNCiAgICBtdXRhdGUoY292Ri5Mb3cgPSBhcy5udW1lcmljKGNvdkYgPT0gIjEtTG93IiksDQogICAgICAgICAgIGNvdkYuTWlkZGxlID0gYXMubnVtZXJpYyhjb3ZGID09ICIyLU1pZGRsZSIpLA0KICAgICAgICAgICBjb3ZGLkhpZ2ggPSBhcy5udW1lcmljKGNvdkYgPT0gIjMtSGlnaCIpKQ0KYGBgDQoNCkFuZCBub3csIHNvbWUgbW9yZSBzYW5pdHkgY2hlY2tzIGZvciB0aGUgYGNvdkZgIGluZm9ybWF0aW9uOg0KDQpgYGB7cn0NCnRveSAlPiUgY291bnQoY292RiwgY292Ri5IaWdoLCBjb3ZGLk1pZGRsZSwgY292Ri5Mb3cpDQpgYGANCg0KIyMgQ3JlYXRpbmcgdGhlIFRyYW5zZm9ybWF0aW9uIGFuZCBQcm9kdWN0IFRlcm1zDQoNClJlbWVtYmVyIHRoYXQgd2UgaGF2ZSByZWFzb24gdG8gYmVsaWV2ZSB0aGF0IHRoZSBzcXVhcmUgb2YgYGNvdkFgIGFzIHdlbGwgYXMgdGhlIGludGVyYWN0aW9uIG9mIGBjb3ZCYCB3aXRoIGBjb3ZDYCBhbmQgYWxzbyBgY292QmAgd2l0aCBgY292RGAgd2lsbCBoYXZlIGFuIGltcGFjdCBvbiB0cmVhdG1lbnQgYXNzaWdubWVudC4gSXQgd2lsbCBiZSB1c2VmdWwgdG8gaGF2ZSB0aGVzZSB0cmFuc2Zvcm1hdGlvbnMgaW4gb3VyIGRhdGEgc2V0IGZvciBtb2RlbGluZyBhbmQgc3VtbWFyaXppbmcuIEkgd2lsbCB1c2UgYGNvdkJgIGluIGl0cyBudW1lcmljICgwLDEpIGZvcm0gKHJhdGhlciB0aGFuIGFzIGEgZmFjdG9yIC0gYGNvdkIuZmApIHdoZW4gY3JlYXRpbmcgcHJvZHVjdCB0ZXJtcywgYXMgc2hvd24gYmVsb3cuDQoNCmBgYHtyfQ0KdG95IDwtIHRveSAlPiUNCiAgICBtdXRhdGUoQXNxciA9IGNvdkFeMiwNCiAgICAgICAgICAgQkMgPSBjb3ZCKmNvdkMsDQogICAgICAgICAgIEJEID0gY292Qipjb3ZEKQ0KYGBgDQoNCiMgRGF0YSBTZXQgQWZ0ZXIgQ2xlYW5pbmcgey50YWJzZXR9DQoNCiMjIFNraW0sIHdpdGhpbiBUcmVhdG1lbnQgR3JvdXBzDQoNCmBgYHtyfQ0KdG95ICU+JSBzZWxlY3QodHJlYXRlZF9mLCBjb3ZBLCBjb3ZCLCBjb3ZDLCBjb3ZELCBjb3ZFLCANCiAgICAgICAgICAgICAgIGNvdkYsIEFzcXIsIEJDLCBCRCwgb3V0MS5jb3N0LCBvdXQyLCBvdXQzLnRpbWUpICU+JQ0KICAgIGdyb3VwX2J5KHRyZWF0ZWRfZikgJT4lDQogICAgc2tpbV93aXRob3V0X2NoYXJ0cygpDQpgYGANCg0KIyMgVGFibGUgMQ0KDQpOb3RlIHRoYXQgdGhlIGZhY3RvcnMgSSBjcmVhdGVkIGZvciB0aGUgYG91dDJgIG91dGNvbWUgYXJlIG5vdCB3ZWxsIG9yZGVyZWQgZm9yIGEgVGFibGUgMSwgYnV0IGFyZSB3ZWxsIG9yZGVyZWQgZm9yIG90aGVyIHRhYmxlcyB3ZSdsbCBmaXQgbGF0ZXIuIFNvLCBpbiB0aGlzIGNhc2UsIEknbGwgdXNlIHRoZSBudW1lcmljIHZlcnNpb24gb2YgdGhlIGBvdXQyYCBvdXRjb21lLCBidXQgdGhlIG5ldyBmYWN0b3IgcmVwcmVzZW50YXRpb25zIG9mIGBjb3ZCYCBhbmQgYHRyZWF0ZWRgLg0KDQpgYGB7cn0NCnZhcmxpc3QgPSBjKCJjb3ZBIiwgImNvdkJfZiIsICJjb3ZDIiwgImNvdkQiLCAiY292RSIsICJjb3ZGIiwgDQogICAgICAgICAgICAiQXNxciIsICJCQyIsICJCRCIsICJvdXQxLmNvc3QiLCAib3V0MiIsICJvdXQzLnRpbWUiKQ0KZmFjdG9ybGlzdCA9IGMoImNvdkJfZiIsICJjb3ZGIiwgIm91dDIiKQ0KQ3JlYXRlVGFibGVPbmUodmFycyA9IHZhcmxpc3QsIHN0cmF0YSA9ICJ0cmVhdGVkX2YiLCANCiAgICAgICAgICAgICAgIGRhdGEgPSB0b3ksIGZhY3RvclZhcnMgPSBmYWN0b3JsaXN0KQ0KYGBgDQoNCg0KIyBUaGUgMTMgVGFza3MgV2UnbGwgVGFja2xlIGluIHRoaXMgRXhhbXBsZQ0KDQoxLiBJZ25vcmluZyB0aGUgY292YXJpYXRlIGluZm9ybWF0aW9uLCB3aGF0IGlzIHRoZSB1bmFkanVzdGVkIHBvaW50IGVzdGltYXRlIChhbmQgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWwpIGZvciB0aGUgZWZmZWN0IG9mIHRoZSB0cmVhdG1lbnQgb24gZWFjaCBvZiB0aGUgdGhyZWUgb3V0Y29tZXMgKGBvdXQxLmNvc3RgLCBgb3V0Mi5ldmVudGAsIGFuZCBgb3V0My50aW1lYCk/DQoyLiBBc3N1bWUgdGhhdCB0aGVvcnkgc3VnZ2VzdHMgdGhhdCB0aGUgc3F1YXJlIG9mIGBjb3ZBYCwgYXMgd2VsbCBhcyB0aGUgaW50ZXJhY3Rpb25zIG9mIGBjb3ZCYCB3aXRoIGBjb3ZDYCBhbmQgYGNvdkJgIHdpdGggYGNvdkRgIHNob3VsZCBiZSByZWxhdGVkIHRvIHRyZWF0bWVudCBhc3NpZ25tZW50LiBGaXQgYSBwcm9wZW5zaXR5IHNjb3JlIG1vZGVsIHRvIHRoZSBkYXRhLCB1c2luZyB0aGUgc2l4IGNvdmFyaWF0ZXMgKEEtRikgYW5kIHRoZSB0aHJlZSB0cmFuc2Zvcm1hdGlvbnMgKEFeMl4sIGFuZCB0aGUgQi1DIGFuZCBCLUQgaW50ZXJhY3Rpb25zLikgUGxvdCB0aGUgcmVzdWx0aW5nIHByb3BlbnNpdHkgc2NvcmVzLCBieSB0cmVhdG1lbnQgZ3JvdXAsIGluIGFuIGF0dHJhY3RpdmUgYW5kIHVzZWZ1bCB3YXkuDQozLiBVc2UgUnViaW4ncyBSdWxlcyB0byBhc3Nlc3MgdGhlIG92ZXJsYXAgb2YgdGhlIHByb3BlbnNpdHkgc2NvcmVzIGFuZCB0aGUgaW5kaXZpZHVhbCBjb3ZhcmlhdGVzIHByaW9yIHRvIHRoZSB1c2Ugb2YgYW55IHByb3BlbnNpdHkgc2NvcmUgYWRqdXN0bWVudHMuDQo0LiBVc2UgMToxIGdyZWVkeSBtYXRjaGluZyB0byBtYXRjaCBhbGwgMTQwIHRyZWF0ZWQgc3ViamVjdHMgIHRvIGNvbnRyb2wgc3ViamVjdHMgd2l0aG91dCByZXBsYWNlbWVudCBvbiB0aGUgYmFzaXMgb2YgdGhlIGxpbmVhciBwcm9wZW5zaXR5IGZvciB0cmVhdG1lbnQuIEV2YWx1YXRlIHRoZSBkZWdyZWUgb2YgY292YXJpYXRlIGltYmFsYW5jZSBiZWZvcmUgYW5kIGFmdGVyIHByb3BlbnNpdHkgbWF0Y2hpbmcgZm9yIGVhY2ggb2YgdGhlIHNpeCBjb3ZhcmlhdGVzLCBhbmQgcHJlc2VudCB0aGUgcHJlLSBhbmQgcG9zdC1tYXRjaCBzdGFuZGFyZGl6ZWQgZGlmZmVyZW5jZXMgYW5kIHZhcmlhbmNlIHJhdGlvcyBmb3IgdGhlIGNvdmFyaWF0ZXMsIGFzIHdlbGwgYXMgdGhlIHNxdWFyZSB0ZXJtIGFuZCBpbnRlcmFjdGlvbnMsIGFzIHdlbGwgYXMgYm90aCB0aGUgcmF3IGFuZCBsaW5lYXIgcHJvcGVuc2l0eSBzY29yZSBpbiBhcHByb3ByaWF0ZSBwbG90cy4gTm93LCBidWlsZCBhIG5ldyBkYXRhIGZyYW1lIGNvbnRhaW5pbmcgdGhlIHByb3BlbnNpdHktbWF0Y2hlZCBzYW1wbGUsIGFuZCB1c2UgaXQgdG8gZmlyc3QgY2hlY2sgUnViaW4ncyBSdWxlcyBhZnRlciBtYXRjaGluZy4NCjUuIE5vdywgdXNlIHRoZSBtYXRjaGVkIHNhbXBsZSBkYXRhIHNldCB0byBldmFsdWF0ZSB0aGUgdHJlYXRtZW50J3MgYXZlcmFnZSBjYXVzYWwgZWZmZWN0IG9uIGVhY2ggb2YgdGhlIHRocmVlIG91dGNvbWVzLiBJbiBlYWNoIGNhc2UsIHNwZWNpZnkgYSBwb2ludCBlc3RpbWF0ZSAoYW5kIGFzc29jaWF0ZWQgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWwpIGZvciB0aGUgZWZmZWN0IG9mIGJlaW5nIHRyZWF0ZWQgKGFzIGNvbXBhcmVkIHRvIGJlaW5nIGEgY29udHJvbCBzdWJqZWN0KSBvbiB0aGUgb3V0Y29tZS4gQ29tcGFyZSB5b3VyIHJlc3VsdHMgdG8gdGhlIGF1dG9tYXRpYyB2ZXJzaW9ucyByZXBvcnRlZCBieSB0aGUgTWF0Y2hpbmcgcGFja2FnZSB3aGVuIHlvdSBpbmNsdWRlIHRoZSBvdXRjb21lIGluIHRoZSBtYXRjaGluZyBwcm9jZXNzLg0KNi4gTm93LCBpbnN0ZWFkIG9mIG1hdGNoaW5nLCBpbnN0ZWFkIGNsYXNzaWZ5IHRoZSBzdWJqZWN0cyBpbnRvIHF1aW50aWxlcyBieSB0aGUgcmF3IHByb3BlbnNpdHkgc2NvcmUuIERpc3BsYXkgdGhlIGJhbGFuY2UgaW4gdGVybXMgb2Ygc3RhbmRhcmRpemVkIGRpZmZlcmVuY2VzIGJ5IHF1aW50aWxlIGZvciB0aGUgY292YXJpYXRlcywgdGhlaXIgdHJhbnNmb3JtYXRpb25zLCBhbmQgdGhlIHByb3BlbnNpdHkgc2NvcmUgaW4gYW4gYXBwcm9wcmlhdGUgdGFibGUgb3IgcGxvdChzKS4gQXJlIHlvdSBzYXRpc2ZpZWQ/IA0KNy4gUmVnYXJkbGVzcyBvZiB5b3VyIGFuc3dlciB0byB0aGUgcHJldmlvdXMgcXVlc3Rpb24sIHVzZSB0aGUgcHJvcGVuc2l0eSBzY29yZSBxdWludGlsZSBzdWJjbGFzc2lmaWNhdGlvbiBhcHByb2FjaCB0byBmaW5kIGEgcG9pbnQgZXN0aW1hdGUgKGFuZCA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbCkgZm9yIHRoZSBlZmZlY3Qgb2YgdGhlIHRyZWF0bWVudCBvbiBlYWNoIG91dGNvbWUuIA0KOC4gTm93IHVzaW5nIGEgcmVhc29uYWJsZSBwcm9wZW5zaXR5IHNjb3JlIHdlaWdodGluZyBzdHJhdGVneSwgYXNzZXNzIHRoZSBiYWxhbmNlIG9mIGVhY2ggY292YXJpYXRlLCB0aGUgdHJhbnNmb3JtYXRpb25zIGFuZCB0aGUgbGluZWFyIHByb3BlbnNpdHkgc2NvcmUgcHJpb3IgdG8gYW5kIGFmdGVyIHByb3BlbnNpdHkgd2VpZ2h0aW5nLiBJcyB0aGUgYmFsYW5jZSBhZnRlciB3ZWlnaHRpbmcgc2F0aXNmYWN0b3J5Pw0KOS4gVXNpbmcgcHJvcGVuc2l0eSBzY29yZSB3ZWlnaHRpbmcgdG8gZXZhbHVhdGUgdGhlIHRyZWF0bWVudCdzIGVmZmVjdCwgZGV2ZWxvcGluZyBhIHBvaW50IGVzdGltYXRlIGFuZCA5NSUgQ0kgZm9yIHRoZSBhdmVyYWdlIGNhdXNhbCBlZmZlY3Qgb2YgdHJlYXRtZW50IG9uIGVhY2ggb3V0Y29tZS4NCjEwLiBGaW5hbGx5LCB1c2UgZGlyZWN0IGFkanVzdG1lbnQgZm9yIHRoZSBsaW5lYXIgcHJvcGVuc2l0eSBzY29yZSBvbiB0aGUgZW50aXJlIHNhbXBsZSB0byBldmFsdWF0ZSB0aGUgdHJlYXRtZW50J3MgZWZmZWN0LCBkZXZlbG9waW5nIGEgcG9pbnQgZXN0aW1hdGUgYW5kIDk1JSBDSSBmb3IgZWFjaCBvdXRjb21lLg0KMTEuIE5vdywgdHJ5IGEgZG91YmxlIHJvYnVzdCBhcHByb2FjaC4gV2VpZ2h0LCB0aGVuIGFkanVzdCBmb3IgbGluZWFyIHByb3BlbnNpdHkgc2NvcmUuDQoxMi4gQ29tcGFyZSB5b3VyIGNvbmNsdXNpb25zIGFib3V0IHRoZSBhdmVyYWdlIGNhdXNhbCBlZmZlY3Qgb2J0YWluZWQgaW4gdGhlIGZvbGxvd2luZyBzaXggd2F5cyB0byBlYWNoIG90aGVyLiBXaGF0IGhhcHBlbnMgYW5kIHdoeT8gV2hpY2ggb2YgdGhlc2UgbWV0aG9kcyBzZWVtcyBtb3N0IGFwcHJvcHJpYXRlIGdpdmVuIHRoZSBhdmFpbGFibGUgaW5mb3JtYXRpb24/DQogICAgKyB3aXRob3V0IHByb3BlbnNpdHkgYWRqdXN0bWVudCwgDQogICAgKyBhZnRlciBwcm9wZW5zaXR5IG1hdGNoaW5nLCANCiAgICArIGFmdGVyIHByb3BlbnNpdHkgc2NvcmUgc3ViY2xhc3NpZmljYXRpb24sIA0KICAgICsgYWZ0ZXIgcHJvcGVuc2l0eSBzY29yZSB3ZWlnaHRpbmcsIA0KICAgICsgYWZ0ZXIgYWRqdXN0aW5nIGZvciB0aGUgcHJvcGVuc2l0eSBzY29yZSBkaXJlY3RseSwgYW5kIA0KICAgICsgYWZ0ZXIgd2VpZ2h0aW5nIHRoZW4gYWRqdXN0aW5nIGZvciB0aGUgUFMsIHRvIGVhY2ggb3RoZXIuICANCjEzLiBQZXJmb3JtIGEgc2Vuc2l0aXZpdHkgYW5hbHlzaXMgZm9yIHlvdXIgbWF0Y2hlZCBzYW1wbGVzIGFuYWx5c2lzIGFuZCB0aGUgZmlyc3Qgb3V0Y29tZSAoYG91dDEuY29zdGApIGlmIGl0IHR1cm5zIG91dCB0byBzaG93IGEgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCB0cmVhdG1lbnQgZWZmZWN0Lg0KDQojIFRhc2sgMS4gSWdub3JpbmcgY292YXJpYXRlcywgZXN0aW1hdGUgdGhlIGVmZmVjdCBvZiB0cmVhdG1lbnQgdnMuIGNvbnRyb2wgb24uLi4NCg0KIyMgT3V0Y29tZSAxIChhIGNvbnRpbnVvdXMgb3V0Y29tZSkNCg0KT3VyIGZpcnN0IG91dGNvbWUgZGVzY3JpYmVzIGEgcXVhbnRpdGF0aXZlIG1lYXN1cmUsIGNvc3QsIGFuZCB3ZSdyZSBhc2tpbmcgd2hhdCB0aGUgZWZmZWN0IG9mIGB0cmVhdG1lbnRgIGFzIGNvbXBhcmVkIHRvIGBjb250cm9sYCBpcyBvbiB0aGF0IG91dGNvbWUuIFN0YXJ0aW5nIHdpdGggYnJpZWYgbnVtZXJpY2FsIHN1bW1hcmllczoNCg0KYGBge3J9DQp0b3kgJT4lDQogICAgZ3JvdXBfYnkodHJlYXRlZF9mKSAlPiUNCiAgICBza2ltX3dpdGhvdXRfY2hhcnRzKG91dDEuY29zdCkNCmBgYA0KDQpJdCBsb29rcyBsaWtlIHRoZSBUcmVhdGVkIGdyb3VwIGhhcyBoaWdoZXIgY29zdHMgdGhhbiB0aGUgQ29udHJvbCBncm91cC4gVG8gbW9kZWwgdGhpcywgd2UgY291bGQgdXNlIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdG8gb2J0YWluIGEgcG9pbnQgZXN0aW1hdGUgYW5kIDk1JSBjb25maWRlbmNlIGludGVydmFsLiBIZXJlLCBJIHByZWZlciB0byB1c2UgdGhlIG51bWVyaWMgdmVyc2lvbiBvZiB0aGUgYHRyZWF0ZWRgIHZhcmlhYmxlLCB3aXRoIDAgPSAiY29udHJvbCIgYW5kIDEgPSAidHJlYXRlZCIuDQoNCmBgYHtyfQ0KdW5hZGoub3V0MSA8LSBsbShvdXQxLmNvc3QgfiB0cmVhdGVkLCBkYXRhPXRveSkNCnN1bW1hcnkodW5hZGoub3V0MSk7IGNvbmZpbnQodW5hZGoub3V0MSwgbGV2ZWwgPSAwLjk1KSAjIyBwcm92aWRlcyB0cmVhdGVkIGVmZmVjdCBhbmQgQ0kgZXN0aW1hdGVzDQpgYGANCg0KV2UgY2FuIHN0b3JlIHRoZXNlIHJlc3VsdHMgaW4gYSBkYXRhIGZyYW1lLCB3aXRoIHRoZSBgdGlkeWAgZnVuY3Rpb24gZnJvbSB0aGUgYGJyb29tYCBwYWNrYWdlLg0KDQpgYGB7cn0NCnRpZHkodW5hZGoub3V0MSwgY29uZi5pbnQgPSBUUlVFLCBjb25mLmxldmVsID0gMC45NSkNCmBgYA0KDQpgYGB7cn0NCnJlc191bmFkal8xIDwtIHRpZHkodW5hZGoub3V0MSwgY29uZi5pbnQgPSBUUlVFLCBjb25mLmxldmVsID0gMC45NSkgJT4lDQogICAgZmlsdGVyKHRlcm0gPT0gInRyZWF0ZWQiKQ0KDQpyZXNfdW5hZGpfMQ0KYGBgDQoNCk91ciB1bmFkanVzdGVkIHRyZWF0bWVudCBlZmZlY3QgZXN0aW1hdGUgaXMgYSBkaWZmZXJlbmNlIG9mIGByIGRlY2ltKHJlc191bmFkal8xJGVzdGltYXRlLDIpYCBpbiBjb3N0LCB3aXRoIDk1JSBjb25maWRlbmNlIGludGVydmFsIChgciBkZWNpbShyZXNfdW5hZGpfMSRjb25mLmxvdywyKWAsIGByIGRlY2ltKHJlc191bmFkal8xJGNvbmYuaGlnaCwyKWApLg0KDQojIyBPdXRjb21lIDIgKGEgYmluYXJ5IG91dGNvbWUpDQoNCiMjIyBVc2luZyBhIDJ4MiB0YWJsZSBpbiBzdGFuZGFyZCBlcGlkZW1pb2xvZ2ljYWwgZm9ybWF0DQoNClRoYW5rcyB0byBvdXIgcHJlbGltaW5hcnkgY2xlYW51cCwgaXQncyByZWxhdGl2ZWx5IGVhc3kgdG8gb2J0YWluIGEgdGFibGUgaW4gc3RhbmRhcmQgZXBpZGVtaW9sb2dpY2FsIGZvcm1hdCBjb21wYXJpbmcgdHJlYXRlZCB0byBjb250cm9sIHN1YmplY3RzIGluIHRlcm1zIG9mIGBvdXQyYDoNCg0KYGBge3J9DQp0YWJsZSh0b3kkdHJlYXRlZF9mLCB0b3kkb3V0Ml9mKQ0KYGBgDQoNCk5vdGUgdGhhdCB0aGUgZXhwb3N1cmUgaXMgaW4gdGhlIHJvd3MsIHdpdGggIkhhdmluZyB0aGUgRXhwb3N1cmUiIG9yICJUcmVhdGVkIiBhdCB0aGUgdG9wLCBhbmQgdGhlIG91dGNvbWUgaXMgaW4gdGhlIGNvbHVtbnMsIHdpdGggIlllcyIgb3IgIk91dGNvbWUgT2NjdXJyZWQiIG9yICJFdmVudCBPY2N1cnJlZCIgb24gdGhlIGxlZnQsIHNvIHRoYXQgdGhlIHRvcCBsZWZ0IGNlbGwgY291bnQgZGVzY3JpYmVzIHBlb3BsZSB0aGF0IGhhZCBib3RoIHRoZSBleHBvc3VyZSBhbmQgdGhlIG91dGNvbWUuIFRoYXQncyAqc3RhbmRhcmQgZXBpZGVtaW9sb2dpY2FsIGZvcm1hdCosIGp1c3Qgd2hhdCB3ZSBuZWVkIGZvciB0aGUgYHR3b2J5MmAgZnVuY3Rpb24gaW4gdGhlIGBFcGlgIHBhY2thZ2UuDQoNCmBgYHtyfQ0KdGVtcCA8LSB0d29ieTIodGFibGUodG95JHRyZWF0ZWRfZiwgdG95JG91dDJfZikpDQpgYGANCg0KRXZlbnR1YWxseSwgd2Ugd2lsbCBiZSBpbnRlcmVzdGVkIGluIGF0IGxlYXN0IHR3byBtZWFzdXJlcyAtIHRoZSBvZGRzIHJhdGlvIGFuZCB0aGUgcmlzayAocHJvYmFiaWxpdHkpIGRpZmZlcmVuY2UgZXN0aW1hdGVzLCBhbmQgdGhlaXIgcmVzcGVjdGl2ZSBjb25maWRlbmNlIGludGVydmFscy4NCg0KVGhlIHJpc2sgZGlmZmVyZW5jZSBpcyBzaG93biBhcyB0aGUgUHJvYmFiaWxpdHkgZGlmZmVyZW5jZSBoZXJlLiBMZXQncyBzYXZlIGl0IHRvIGEgZGF0YSBmcmFtZSwgYW5kIHRoZW4gd2UnbGwgc2F2ZSB0aGUgKHNhbXBsZSkgb2RkcyByYXRpbyBpbmZvcm1hdGlvbiB0byBhbm90aGVyIGRhdGEgZnJhbWUuDQoNCmBgYHtyfQ0KcmVzX3VuYWRqXzJfcmlza2RpZmYgPC0gdGliYmxlKG91dCA9ICJvdXQyLmV2ZW50IiwNCiAgICAgICAgIHJpc2suZGlmZiA9IHRlbXAkbWVhc3VyZXNbNCwxXSwNCiAgICAgICAgIGNvbmYubG93ID0gdGVtcCRtZWFzdXJlc1s0LDJdLA0KICAgICAgICAgY29uZi5oaWdoID0gdGVtcCRtZWFzdXJlc1s0LDNdKQ0KDQpyZXNfdW5hZGpfMl9vZGRzcmF0aW8gPC0gdGliYmxlKG91dCA9ICJvdXQyLmV2ZW50IiwNCiAgICAgICAgIG9kZHMucmF0aW8gPSB0ZW1wJG1lYXN1cmVzWzIsMV0sDQogICAgICAgICBjb25mLmxvdyA9IHRlbXAkbWVhc3VyZXNbMiwyXSwNCiAgICAgICAgIGNvbmYuaGlnaCA9IHRlbXAkbWVhc3VyZXNbMiwzXSkNCg0KcmVzX3VuYWRqXzJfcmlza2RpZmYNCnJlc191bmFkal8yX29kZHNyYXRpbw0KYGBgDQoNCi0gRm9yIGEgKmRpZmZlcmVuY2UgaW4gcmlzayosIG91ciB1bmFkanVzdGVkIHRyZWF0bWVudCBlZmZlY3QgZXN0aW1hdGUgaXMgYW4gZGlmZmVyZW5jZSBvZiBgciBkZWNpbSgxMDAgKiByZXNfdW5hZGpfMl9yaXNrZGlmZiRyaXNrLmRpZmYsIDEpYCBwZXJjZW50YWdlIHBvaW50cyBhcyBjb21wYXJlZCB0byBjb250cm9sLCB3aXRoIDk1JSBDSSBvZiAoYHIgZGVjaW0oMTAwICogcmVzX3VuYWRqXzJfcmlza2RpZmYkY29uZi5sb3csIDEpYCwgYHIgZGVjaW0oMTAwICogcmVzX3VuYWRqXzJfcmlza2RpZmYkY29uZi5oaWdoLCAxKWApIHBlcmNlbnRhZ2UgcG9pbnRzLg0KLSBGb3IgYW4gKm9kZHMgcmF0aW8qLCBvdXIgdW5hZGp1c3RlZCB0cmVhdG1lbnQgZWZmZWN0IGVzdGltYXRlIGlzIGFuIG9kZHMgcmF0aW8gb2YgYHIgZGVjaW0ocmVzX3VuYWRqXzJfb2Rkc3JhdGlvJG9kZHMucmF0aW8sIDIpYCAoOTUlIENJID0gYHIgZGVjaW0ocmVzX3VuYWRqXzJfb2Rkc3JhdGlvJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHJlc191bmFkal8yX29kZHNyYXRpbyRjb25mLmhpZ2gsIDIpYCkgZm9yIHRoZSBldmVudCBvY2N1cnJpbmcgd2l0aCB0cmVhdG1lbnQgYXMgY29tcGFyZWQgdG8gY29udHJvbC4gDQoNCiMjIyBVc2luZyBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwNCg0KRm9yIHRoZSBvZGRzIHJhdGlvIGVzdGltYXRlLCB3ZSBjYW4gdXNlIGEgc2ltcGxlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgdG8gZXN0aW1hdGUgdGhlIHVuYWRqdXN0ZWQgdHJlYXRtZW50IGVmZmVjdCwgcmVzdWx0aW5nIGluIGVzc2VudGlhbGx5IHRoZSBzYW1lIGFuc3dlci4gV2UnbGwgdXNlIHRoZSBudW1lcmljYWwgKDAvMSkgZm9ybWF0IHRvIHJlcHJlc2VudCBiaW5hcnkgaW5mb3JtYXRpb24sIGFzIGZvbGxvd3MuDQoNCmBgYHtyfQ0KdW5hZGoub3V0MiA8LSBnbG0ob3V0MiB+IHRyZWF0ZWQsIGRhdGE9dG95LCBmYW1pbHk9Ymlub21pYWwoKSkNCg0Kc3VtbWFyeSh1bmFkai5vdXQyKQ0KDQpleHAoY29lZih1bmFkai5vdXQyKSkgIyBwcm9kdWNlcyBvZGRzIHJhdGlvIGVzdGltYXRlDQpleHAoY29uZmludCh1bmFkai5vdXQyKSkgIyBwcm9kdWNlcyA5NSUgQ0kgZm9yIG9kZHMgcmF0aW8NCmBgYA0KDQpBbmQsIGFnYWluLCB3ZSBjYW4gdXNlIHRoZSBgdGlkeWAgZnVuY3Rpb24gaW4gdGhlIGBicm9vbWAgcGFja2FnZSB0byBidWlsZCBhIHRpYmJsZSBvZiB0aGUga2V5IHBhcnRzIG9mIHRoZSBvdXRwdXQuIE5vdGUgdGhhdCBieSBpbmNsdWRpbmcgdGhlIGBleHBvbmVudGlhdGUgPSBUUlVFYCBjb21tYW5kLCBvdXIgcmVzdWx0cyBpbiB0aGUgYHRyZWF0ZWRgIHJvdyBkZXNjcmliZSB0aGUgb2RkcyByYXRpbywgcmF0aGVyIHRoYW4gdGhlIGxvZyBvZGRzLg0KDQpgYGB7cn0NCnRpZHkodW5hZGoub3V0MiwgY29uZi5pbnQgPSBUUlVFLCBleHBvbmVudGlhdGUgPSBUUlVFKQ0KYGBgDQoNCmBgYHtyfQ0KcmVzX3VuYWRqXzJfb3IgPC0gdGlkeSh1bmFkai5vdXQyLCBjb25mLmludCA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICBjb25mLmxldmVsID0gMC45NSwgZXhwb25lbnRpYXRlID0gVFJVRSkgJT4lDQogICAgZmlsdGVyKHRlcm0gPT0gInRyZWF0ZWQiKQ0KDQpyZXNfdW5hZGpfMl9vcg0KYGBgDQoNCi0gT3VyIG9kZHMgcmF0aW8gZXN0aW1hdGUgaXMgYHIgZGVjaW0ocmVzX3VuYWRqXzJfb3IkZXN0aW1hdGUsIDIpYCwgd2l0aCA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbCByYW5naW5nIGZyb20gYHIgZGVjaW0ocmVzX3VuYWRqXzJfb3IkY29uZi5sb3csIDIpYCB0byBgciBkZWNpbShyZXNfdW5hZGpfMl9vciRjb25mLmhpZ2gsIDIpYC4NCi0gRm9yIHByYWN0aWNhbCBwdXJwb3NlcywgdGhlIG9kZHMgcmF0aW8gYW5kIDk1JSBjb25maWRlbmNlIGludGVydmFsIG9idGFpbmVkIGhlcmUgbWF0Y2hlcyB0aGUgbWV0aG9kb2xvZ3kgZm9yIHRoZSBgdHdvYnkyYCBmdW5jdGlvbi4gVGhlIGFwcHJvYWNoIGltcGxlbWVudGVkIGluIHRoZSBgdHdvYnkyYCBmdW5jdGlvbiBwcm9kdWNlcyBzbGlnaHRseSBsZXNzIGNvbnNlcnZhdGl2ZSAoaS5lLiBuYXJyb3dlcikgY29uZmlkZW5jZSBpbnRlcnZhbHMgZm9yIHRoZSBlZmZlY3QgZXN0aW1hdGUgdGhhbiBkb2VzIHRoZSBhcHByb2FjaCB1c2VkIGluIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLg0KDQojIyBPdXRjb21lIDMgKGEgdGltZS10by1ldmVudCBvdXRjb21lIHdpdGggcmlnaHQgY2Vuc29yaW5nKQ0KDQpPdXIgYG91dDMudGltZWAgdmFyaWFibGUgaXMgYSB2YXJpYWJsZSBpbmRpY2F0aW5nIHRoZSB0aW1lIGJlZm9yZSB0aGUgZXZlbnQgZGVzY3JpYmVkIGluIGBvdXQyYCBvY2N1cnJlZC4gVGhpcyBoYXBwZW5lZCB0byBgciBzdW0odG95JG91dDIpYCBvZiB0aGUgYHIgbnJvdyh0b3kpYCBzdWJqZWN0cyBpbiB0aGUgZGF0YSBzZXQuIEZvciB0aGUgb3RoZXIgYHIgc3VtKHRveSRvdXQyID09IDApYCBzdWJqZWN0cyB3aG8gbGVmdCB0aGUgc3R1ZHkgYmVmb3JlIHRoZWlyIGV2ZW50IG9jY3VycmVkLCB3ZSBoYXZlIHRoZSB0aW1lIGJlZm9yZSBjZW5zb3JpbmcuIFdlIGNhbiBzZWUgdGhlIHJlc3VsdHMgb2YgdGhpcyBjZW5zb3JpbmcgaW4gdGhlIHN1cnZpdmFsIG9iamVjdCBkZXNjcmliaW5nIGVhY2ggdHJlYXRtZW50IGdyb3VwLiANCg0KSGVyZSwgZm9yIGluc3RhbmNlLCBpcyB0aGUgc3Vydml2YWwgb2JqZWN0IGZvciB0aGUgKnRyZWF0ZWQqIHN1YmplY3RzIC0gdGhlIGZpcnN0IHN1YmplY3QgbGlzdGVkIGhlcmUgaXMgY2Vuc29yZWQgLSBoYWQgdGhlIGV2ZW50IGF0IHNvbWUgcG9pbnQgYWZ0ZXIgMTA2IHdlZWtzICgxMDYrKSBidXQgd2UgZG9uJ3Qga25vdyBwcmVjaXNlbHkgd2hlbiBhZnRlciAxMDYgd2Vla3MuDQoNCmBgYHtyfQ0KU3Vydih0b3kkb3V0My50aW1lLCB0b3kkb3V0Mi5ldmVudCA9PSAiWWVzIilbdG95JHRyZWF0ZWQgPT0gMV0NCmBgYA0KDQotIFRvIHNlZSB0aGUgY29udHJvbHMsIHdlIGNvdWxkIHVzZSBgU3Vydih0b3kkb3V0My50aW1lLCB0b3kkb3V0Mi5ldmVudD09IlllcyIpW3RveSR0cmVhdGVkPT0wXWANCg0KVG8gZGVhbCB3aXRoIHRoZSByaWdodCBjZW5zb3JpbmcsIHdlJ2xsIHVzZSB0aGUgYHN1cnZpdmFsYCBwYWNrYWdlIHRvIGZpdCBhIHNpbXBsZSB1bmFkanVzdGVkIENveCBwcm9wb3J0aW9uYWwgaGF6YXJkcyBtb2RlbCB0byBhc3Nlc3MgdGhlIHJlbGF0aXZlIGhhemFyZCBvZiBoYXZpbmcgdGhlIGV2ZW50IGF0IGEgcGFydGljdWxhciB0aW1lIHBvaW50IGFtb25nIHRyZWF0ZWQgc3ViamVjdHMgYXMgY29tcGFyZWQgdG8gY29udHJvbHMuDQoNCmBgYHtyfQ0KdW5hZGoub3V0MyA8LSBjb3hwaChTdXJ2KG91dDMudGltZSwgb3V0Mi5ldmVudD09IlllcyIpIH4gdHJlYXRlZCwgZGF0YT10b3kpDQpzdW1tYXJ5KHVuYWRqLm91dDMpICMjIGV4cChjb2VmKSBzZWN0aW9uIGluZGljYXRlcyByZWxhdGl2ZSByaXNrIGVzdGltYXRlIGFuZCA5NSUgQ0kNCmBgYA0KDQpUaGUgcmVsYXRpdmUgaGF6YXJkIHJhdGUgaXMgc2hvd24gaW4gdGhlIGBleHAoY29lZilgIHNlY3Rpb24gb2YgdGhlIG91dHB1dC4gDQoNClllcywgeW91IGNhbiB0aWR5IHRoaXMgbW9kZWwsIGFzIHdlbGwsIHVzaW5nIHRoZSBgYnJvb21gIHBhY2thZ2UuDQoNCmBgYHtyfQ0KcmVzX3VuYWRqXzMgPC0gdGlkeSh1bmFkai5vdXQzLCBleHBvbmVudGlhdGUgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICBjb25mLmludCA9IFRSVUUpICU+JQ0KICAgIGZpbHRlcih0ZXJtID09ICJ0cmVhdGVkIikNCnJlc191bmFkal8zDQpgYGANCg0KQW5kIHNvLCBvdXIgZXN0aW1hdGUgY2FuIGJlIHNhdmVkLCBhcyB3ZSd2ZSBkb25lIHByZXZpb3VzbHkuIA0KDQotIFRoZSByZWxhdGl2ZSBoYXphcmQgcmF0ZSBlc3RpbWF0ZSBpcyBgciBkZWNpbShyZXNfdW5hZGpfMyRlc3RpbWF0ZSwgMilgLCB3aXRoIDk1JSBjb25maWRlbmNlIGludGVydmFsIHJhbmdpbmcgZnJvbSBgciBkZWNpbShyZXNfdW5hZGpfMyRjb25mLmxvdywgMilgIHRvIGByIGRlY2ltKHJlc191bmFkal8zJGNvbmYuaGlnaCwgMilgLiBPdXIgdW5hZGp1c3RlZCB0cmVhdG1lbnQgbW9kZWwgc3VnZ2VzdHMgdGhhdCB0aGUgaGF6YXJkIG9mIHRoZSBvdXRjb21lIGlzIHNpZ25pZmljYW50bHkgbGFyZ2VyIChhdCBhIDUlIHNpZ25pZmljYW5jZSBsZXZlbCkgaW4gdGhlIHRyZWF0ZWQgZ3JvdXAgdGhhbiBpbiB0aGUgY29udHJvbCBncm91cC4gDQoNCkl0J3Mgd2lzZSwgd2hlbmV2ZXIgZml0dGluZyBhIENveCBwcm9wb3J0aW9uYWwgaGF6YXJkcyBtb2RlbCwgdG8gYXNzZXNzIHRoZSBwcm9wb3J0aW9uYWwgaGF6YXJkcyBhc3N1bXB0aW9uLiBPbmUgd2F5IHRvIGRvIHRoaXMgaXMgdG8gcnVuIGEgc2ltcGxlIHRlc3QgaW4gUiAtIGZyb20gd2hpY2ggd2UgY2FuIG9idGFpbiBhIHBsb3QsIGlmIHdlIGxpa2UuIFRoZSBpZGVhIGlzIGZvciB0aGUgcGxvdCB0byBzaG93IG5vIGNsZWFyIHBhdHRlcm5zIG92ZXIgdGltZSwgYW5kIGxvb2sgcHJldHR5IG11Y2ggbGlrZSBhIGhvcml6b250YWwgbGluZSwgd2hpbGUgd2Ugd291bGQgbGlrZSB0aGUgdGVzdCB0byBiZSBub24tc2lnbmlmaWNhbnQgLSBpZiB0aGF0J3MgdGhlIGNhc2UsIG91ciBwcm9wb3J0aW9uYWwgaGF6YXJkcyBhc3N1bXB0aW9uIGlzIGxpa2VseSBPSy4NCg0KYGBge3J9DQpjb3guenBoKHVuYWRqLm91dDMpDQpwbG90KGNveC56cGgodW5hZGoub3V0MyksIHZhcj0idHJlYXRlZCIpDQpgYGANCg0KSWYgdGhlIHByb3BvcnRpb25hbCBoYXphcmRzIGFzc3VtcHRpb24gaXMgY2xlYXJseSB2aW9sYXRlZCAoaGVyZSBpdCBpc24ndCksIGNhbGwgYSBzdGF0aXN0aWNpYW4uDQoNCiMjIFVuYWRqdXN0ZWQgRXN0aW1hdGVzIG9mIFRyZWF0bWVudCBFZmZlY3Qgb24gT3V0Y29tZXMNCg0KU28sIG91ciB1bmFkanVzdGVkIGF2ZXJhZ2UgdHJlYXRtZW50IGVmZmVjdCBlc3RpbWF0ZXMgKGluIGVhY2ggY2FzZSBjb21wYXJpbmcgdHJlYXRlZCBzdWJqZWN0cyB0byBjb250cm9sIHN1YmplY3RzKSBhcmUgdGh1czoNCg0KRXN0LiBUcmVhdG1lbnQgRWZmZWN0ICg5NSUgQ0kpIHwgT3V0Y29tZSAxIChDb3N0IGRpZmYuKSB8IE91dGNvbWUgMiAoUmlzayBkaWZmLikgfCBPdXRjb21lIDIgKE9kZHMgUmF0aW8pIHwgT3V0Y29tZSAzIChSZWxhdGl2ZSBIYXphcmQgUmF0ZSkNCi0tLS0tLS0tLS0tLS0tLS06IHwgLS0tLS0tLS0tLS06IHwgLS0tLS0tLS0tLS06IHwgLS0tLS0tLS0tLS06IHwgLS0tLS0tLS0tLS06IA0KTm8gY292YXJpYXRlIGFkanVzdG1lbnQgfCAqKmByIGRlY2ltKHJlc191bmFkal8xJGVzdGltYXRlLDIpYCoqIHwgKipgciBkZWNpbShyZXNfdW5hZGpfMl9yaXNrZGlmZiRyaXNrLmRpZmYsIDMpYCoqIHwgKipgciBkZWNpbShyZXNfdW5hZGpfMl9vciRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKHJlc191bmFkal8zJGVzdGltYXRlLCAyKWAqKiANCih1bmFkanVzdGVkKSB8IChgciBkZWNpbShyZXNfdW5hZGpfMSRjb25mLmxvdywyKWAsIGByIGRlY2ltKHJlc191bmFkal8xJGNvbmYuaGlnaCwyKWApIHwgKGByIGRlY2ltKHJlc191bmFkal8yX3Jpc2tkaWZmJGNvbmYubG93LCAzKWAsIGByIGRlY2ltKHJlc191bmFkal8yX3Jpc2tkaWZmJGNvbmYuaGlnaCwgMylgKSB8IChgciBkZWNpbShyZXNfdW5hZGpfMl9vciRjb25mLmxvdywgMilgLCBgciBkZWNpbShyZXNfdW5hZGpfMl9vciRjb25mLmhpZ2gsIDIpYCkgfCAoYHIgZGVjaW0ocmVzX3VuYWRqXzMkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0ocmVzX3VuYWRqXzMkY29uZi5oaWdoLCAyKWApDQoNCiMgVGFzayAyLiBGaXQgdGhlIHByb3BlbnNpdHkgc2NvcmUgbW9kZWwsIHRoZW4gcGxvdCB0aGUgUFMtdHJlYXRtZW50IHJlbGF0aW9uc2hpcA0KDQpJJ2xsIHVzZSBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwNCg0KYGBge3J9DQpwc21vZGVsIDwtIGdsbSh0cmVhdGVkIH4gY292QSArIGNvdkIgKyBjb3ZDICsgY292RCArIGNvdkUgKyBjb3ZGICsgDQogICAgICAgICAgICAgICAgICAgQXNxciArIEJDICsgQkQsIGZhbWlseT1iaW5vbWlhbCgpLCBkYXRhPXRveSkNCnN1bW1hcnkocHNtb2RlbCkNCmBgYA0KDQpIYXZpbmcgZml0IHRoZSBtb2RlbCwgbXkgZmlyc3Qgc3RlcCB3aWxsIGJlIHRvIHNhdmUgdGhlIHJhdyBhbmQgbGluZWFyIHByb3BlbnNpdHkgc2NvcmUgdmFsdWVzIHRvIHRoZSBtYWluIHRveSBleGFtcGxlIHRpYmJsZS4NCg0KYGBge3J9DQp0b3kkcHMgPC0gcHNtb2RlbCRmaXR0ZWQNCnRveSRsaW5wcyA8LSBwc21vZGVsJGxpbmVhci5wcmVkaWN0b3JzDQpgYGANCg0KIyMgQ29tcGFyaW5nIHRoZSBEaXN0cmlidXRpb24gb2YgUHJvcGVuc2l0eSBTY29yZSBBY3Jvc3MgdGhlIFR3byBUcmVhdG1lbnQgR3JvdXBzDQoNCk5vdywgSSBjYW4gdXNlIHRoZXNlIHNhdmVkIHZhbHVlcyB0byBhc3Nlc3MgdGhlIHByb3BlbnNpdHkgbW9kZWwuDQoNCmBgYHtyfQ0KdG95ICU+JSBncm91cF9ieSh0cmVhdGVkX2YpICU+JSBza2ltX3dpdGhvdXRfY2hhcnRzKHBzLCBsaW5wcykNCmBgYA0KDQpUaGUgc2ltcGxlc3QgcGxvdCBpcyBwcm9iYWJseSBhIGJveHBsb3QsIGJ1dCBpdCdzIG5vdCB2ZXJ5IGdyYW51bGFyLg0KDQpgYGB7cn0NCmdncGxvdCh0b3ksIGFlcyh4ID0gdHJlYXRlZF9mLCB5ID0gcHMpKSArDQogICAgZ2VvbV9ib3hwbG90KCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdCh0b3ksIGFlcyh4ID0gdHJlYXRlZF9mLCB5ID0gcHMsIGNvbG9yID0gdHJlYXRlZF9mKSkgKyANCiAgICBnZW9tX2JveHBsb3QoKSArDQogICAgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjEpICsgDQogICAgZ3VpZGVzKGNvbG9yID0gRkFMU0UpDQpgYGANCg0KSSdkIHJhdGhlciBnZXQgYSBmYW5jaWVyIHBsb3QgdG8gY29tcGFyZSB0aGUgZGlzdHJpYnV0aW9ucyBvZiB0aGUgcHJvcGVuc2l0eSBzY29yZSBhY3Jvc3MgdGhlIHR3byB0cmVhdG1lbnQgZ3JvdXBzLCBwZXJoYXBzIHVzaW5nIGEgc21vb3RoZWQgZGVuc2l0eSBlc3RpbWF0ZSwgYXMgc2hvd24gYmVsb3cuIEhlcmUsIEknbGwgc2hvdyB0aGUgZGlzdHJpYnV0aW9ucyBvZiB0aGUgbGluZWFyIHByb3BlbnNpdHkgc2NvcmUsIHRoZSBsb2cgb2RkcyBvZiB0cmVhdG1lbnQuDQoNCmBgYHtyfQ0KZ2dwbG90KHRveSwgYWVzKHggPSBsaW5wcywgZmlsbCA9IHRyZWF0ZWRfZikpICsNCiAgICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjMpDQpgYGANCg0KV2Ugc2VlIGEgZmFpciBhbW91bnQgb2Ygb3ZlcmxhcCBhY3Jvc3MgdGhlIHR3byB0cmVhdG1lbnQgZ3JvdXBzLiBJJ2xsIHVzZSBSdWJpbidzIFJ1bGVzIGluIHRoZSBuZXh0IHNlY3Rpb24gdG8gaGVscCBhc3Nlc3MgdGhlIGFtb3VudCBvZiBvdmVybGFwIGF0IHRoaXMgcG9pbnQsIGJlZm9yZSBhbnkgYWRqdXN0bWVudHMgZm9yIHRoZSBwcm9wZW5zaXR5IHNjb3JlLg0KDQojIFRhc2sgMy4gUnViaW4ncyBSdWxlcyB0byBDaGVjayBPdmVybGFwIEJlZm9yZSBQcm9wZW5zaXR5IEFkanVzdG1lbnQNCg0KSW4gaGlzIDIwMDEgYXJ0aWNsZVteMV0gYWJvdXQgdXNpbmcgcHJvcGVuc2l0eSBzY29yZXMgdG8gZGVzaWduIHN0dWRpZXMsIGFzIGFwcGxpZWQgdG8gc3R1ZGllcyBvZiB0aGUgY2F1c2FsIGVmZmVjdHMgb2YgdGhlIGNvbmR1Y3Qgb2YgdGhlIHRvYmFjY28gaW5kdXN0cnkgb24gbWVkaWNhbCBleHBlbmRpdHVyZXMsIERvbmFsZCBSdWJpbiBwcm9wb3NlZCB0aHJlZSAicnVsZXMiIGZvciBhc3Nlc3NpbmcgdGhlIG92ZXJsYXAgLyBiYWxhbmNlIG9mIGNvdmFyaWF0ZXMgYXBwcm9wcmlhdGVseSBiZWZvcmUgYW5kIGFmdGVyIHByb3BlbnNpdHkgYWRqdXN0bWVudC4gIEJlZm9yZSBhbiBvdXRjb21lIGlzIGV2YWx1YXRlZCB1c2luZyBhIHJlZ3Jlc3Npb24gYW5hbHlzaXMgKHBlcmhhcHMgc3VwcGxlbWVudGVkIGJ5IGEgcHJvcGVuc2l0eSBzY29yZSBhZGp1c3RtZW50IHRocm91Z2ggbWF0Y2hpbmcsIHdlaWdodGluZywgc3ViY2xhc3NpZmljYXRpb24gb3IgZXZlbiBkaXJlY3QgYWRqdXN0bWVudCksIHRoZXJlIGFyZSB0aHJlZSBjaGVja3MgdGhhdCBzaG91bGQgYmUgcGVyZm9ybWVkLg0KDQpXaGVuIHdlIGRvIGEgcHJvcGVuc2l0eSBzY29yZSBhbmFseXNpcywgaXQgd2lsbCBiZSBoZWxwZnVsIHRvIHBlcmZvcm0gdGhlc2UgY2hlY2tzIGFzIHNvb24gYXMgdGhlIHByb3BlbnNpdHkgbW9kZWwgaGFzIGJlZW4gZXN0aW1hdGVkLCBldmVuIGJlZm9yZSBhbnkgYWRqdXN0bWVudHMgdGFrZSBwbGFjZSwgdG8gc2VlIGhvdyB3ZWxsIHRoZSBkaXN0cmlidXRpb25zIG9mIGNvdmFyaWF0ZXMgb3ZlcmxhcC4gQWZ0ZXIgdXNpbmcgdGhlIHByb3BlbnNpdHkgc2NvcmUsIHdlIGhvcGUgdG8gc2VlIHRoZXNlIGNoZWNrcyBtZWV0IHRoZSBzdGFuZGFyZHMgYmVsb3cuIEluIHdoYXQgZm9sbG93cywgSSB3aWxsIGRlc2NyaWJlIGVhY2ggc3RhbmRhcmQsIGFuZCBkZW1vbnN0cmF0ZSBpdHMgZXZhbHVhdGlvbiB1c2luZyB0aGUgcHJvcGVuc2l0eSBzY29yZSBtb2RlbCB3ZSBqdXN0IGZpdCwgYW5kIGxvb2tpbmcgYXQgdGhlIG9yaWdpbmFsIGB0b3lgIGRhdGEgc2V0LCB3aXRob3V0IGFwcGx5aW5nIHRoZSBwcm9wZW5zaXR5IHNjb3JlIGluIGFueSB3YXkgdG8gZG8gYWRqdXN0bWVudHMuDQoNCiMjIFJ1YmluJ3MgUnVsZSAxDQoNClJ1YmluJ3MgUnVsZSAxIHN0YXRlcyB0aGF0IHRoZSBhYnNvbHV0ZSB2YWx1ZSBvZiB0aGUgc3RhbmRhcmRpemVkIGRpZmZlcmVuY2Ugb2YgdGhlIGxpbmVhciBwcm9wZW5zaXR5IHNjb3JlLCBjb21wYXJpbmcgdGhlIHRyZWF0ZWQgZ3JvdXAgdG8gdGhlIGNvbnRyb2wgZ3JvdXAsIHNob3VsZCBiZSBjbG9zZSB0byAwLCBpZGVhbGx5IGJlbG93IDEwJSwgYW5kIGluIGFueSBjYXNlIGxlc3MgdGhhbiA1MCUuIElmIHNvLCB3ZSBtYXkgbW92ZSBvbiB0byBSdWxlIDIuDQoNClRvIGV2YWx1YXRlIHRoaXMgcnVsZSBpbiB0aGUgdG95IGV4YW1wbGUsIHdlJ2xsIHJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gcGxhY2UgdGhlIHJpZ2h0IHZhbHVlIGludG8gYSB2YXJpYWJsZSBjYWxsZWQgYHJ1YmluMS51bmFkamAgKGZvciBSdWJpbidzIFJ1bGUgMSwgdW5hZGp1c3RlZCkuDQoNCmBgYHtyfQ0KcnViaW4xLnVuYWRqIDwtIHdpdGgodG95LA0KICAgICBhYnMoMTAwKihtZWFuKGxpbnBzW3RyZWF0ZWQ9PTFdKS1tZWFuKGxpbnBzW3RyZWF0ZWQ9PTBdKSkvc2QobGlucHMpKSkNCnJ1YmluMS51bmFkag0KYGBgDQoNCldoYXQgdGhpcyBkb2VzIGlzIGNhbGN1bGF0ZSB0aGUgKGFic29sdXRlIHZhbHVlIG9mIHRoZSkgc3RhbmRhcmRpemVkIGRpZmZlcmVuY2Ugb2YgdGhlIGxpbmVhciBwcm9wZW5zaXR5IHNjb3JlIGNvbXBhcmluZyB0cmVhdGVkIHN1YmplY3RzIHRvIGNvbnRyb2wgc3ViamVjdHMuIA0KDQotIFdlIHdhbnQgdGhpcyB2YWx1ZSB0byBiZSBjbG9zZSB0byAwLCBhbmQgY2VydGFpbmx5IGxlc3MgdGhhbiA1MCBpbiBvcmRlciB0byBwdXNoIGZvcndhcmQgdG8gb3V0Y29tZXMgYW5hbHlzaXMgd2l0aG91dCBmdXJ0aGVyIGFkanVzdG1lbnQgZm9yIHRoZSBwcm9wZW5zaXR5IHNjb3JlLiANCi0gQ2xlYXJseSwgaGVyZSwgd2l0aCBhIHZhbHVlIGFib3ZlIDUwJSwgd2UgY2FuJ3QganVzdGlmeSBzaW1wbHkgcnVubmluZyBhbiB1bmFkanVzdGVkIHJlZ3Jlc3Npb24gbW9kZWwsIGJlIGl0IGEgbGluZWFyLCBsb2dpc3RpYyBvciBDb3ggbW9kZWwgLSB3ZSd2ZSBnb3Qgb2JzZXJ2ZWQgc2VsZWN0aW9uIGJpYXMsIGFuZCBuZWVkIHRvIGFjdHVhbGx5IGFwcGx5IHRoZSBwcm9wZW5zaXR5IHNjb3JlIHNvbWVob3cgaW4gb3JkZXIgdG8gYWNjb3VudCBmb3IgdGhpcy4gDQotIFNvLCB3ZSdsbCBuZWVkIHRvIG1hdGNoLCBzdWJjbGFzc2lmeSwgd2VpZ2h0IG9yIGRpcmVjdGx5IGFkanVzdCBmb3IgcHJvcGVuc2l0eSBoZXJlLg0KDQpTaW5jZSB3ZSd2ZSBmYWlsZWQgUnViaW4ncyAxc3QgUnVsZSwgaW4gc29tZSBzZW5zZSwgd2UncmUgZG9uZSBjaGVja2luZyB0aGUgcnVsZXMsIGJlY2F1c2Ugd2UgY2xlYXJseSBuZWVkIHRvIGZ1cnRoZXIgYWRqdXN0IGZvciBvYnNlcnZlZCBzZWxlY3Rpb24gYmlhcyAtIHRoZXJlJ3Mgbm8gbmVlZCB0byBwcm92ZSB0aGF0IGZ1cnRoZXIgdGhyb3VnaCBjaGVja2luZyBSdWJpbidzIDJuZCBhbmQgM3JkIHJ1bGVzLiBCdXQgd2UnbGwgZG8gaXQgaGVyZSB0byBzaG93IHdoYXQncyBpbnZvbHZlZC4NCg0KW14xXTogUnViaW4gREIgMjAwMSBVc2luZyBQcm9wZW5zaXR5IFNjb3JlcyB0byBIZWxwIERlc2lnbiBPYnNlcnZhdGlvbmFsIFN0dWRpZXM6IEFwcGxpY2F0aW9uIHRvIHRoZSBUb2JhY2NvIExpdGlnYXRpb24uICpIZWFsdGggU2VydmljZXMgJiBPdXRjb21lcyBSZXNlYXJjaCBNZXRob2RvbG9neSogMjogMTY5LTE4OC4NCg0KIyMgUnViaW4ncyBSdWxlIDINCg0KUnViaW4ncyBSdWxlIDIgc3RhdGVzIHRoYXQgdGhlIHJhdGlvIG9mIHRoZSB2YXJpYW5jZSBvZiB0aGUgbGluZWFyIHByb3BlbnNpdHkgc2NvcmUgaW4gdGhlIHRyZWF0ZWQgZ3JvdXAgdG8gdGhlIHZhcmlhbmNlIG9mIHRoZSBsaW5lYXIgcHJvcGVuc2l0eSBzY29yZSBpbiB0aGUgY29udHJvbCBncm91cCBzaG91bGQgYmUgY2xvc2UgdG8gMSwgaWRlYWxseSBiZXR3ZWVuIDQvNSBhbmQgNS80LCBidXQgY2VydGFpbmx5IG5vdCB2ZXJ5IGNsb3NlIHRvIG9yIGV4Y2VlZGluZyAxLzIgYW5kIDIuIElmIHNvLCB3ZSBtYXkgbW92ZSBvbiB0byBSdWxlIDMuDQoNClRvIGV2YWx1YXRlIHRoaXMgcnVsZSBpbiB0aGUgdG95IGV4YW1wbGUsIHdlJ2xsIHJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gcGxhY2UgdGhlIHJpZ2h0IHZhbHVlIGludG8gYSB2YXJpYWJsZSBjYWxsZWQgYHJ1YmluMi51bmFkamAgKGZvciBSdWJpbidzIFJ1bGUgMiwgdW5hZGp1c3RlZCkuDQoNCmBgYHtyfQ0KcnViaW4yLnVuYWRqIDwtd2l0aCh0b3ksIHZhcihsaW5wc1t0cmVhdGVkPT0xXSkvdmFyKGxpbnBzW3RyZWF0ZWQ9PTBdKSkNCnJ1YmluMi51bmFkag0KYGBgDQoNClRoaXMgaXMgdGhlIHJhdGlvIG9mIHZhcmlhbmNlcyBvZiB0aGUgbGluZWFyIHByb3BlbnNpdHkgc2NvcmUgY29tcGFyaW5nIHRyZWF0ZWQgc3ViamVjdHMgdG8gY29udHJvbCBzdWJqZWN0cy4gV2Ugd2FudCB0aGlzIHZhbHVlIHRvIGJlIGNsb3NlIHRvIDEsIGFuZCBjZXJ0YWlubHkgYmV0d2VlbiAwLjUgYW5kIDIuIEluIHRoaXMgY2FzZSwgd2UgcGFzcyBSdWxlIDIsIGlmIGp1c3QgYmFyZWx5Lg0KDQojIyBSdWJpbidzIFJ1bGUgMw0KDQpGb3IgUnViaW4ncyBSdWxlIDMsIHdlIGJlZ2luIGJ5IGNhbGN1bGF0aW5nIHJlZ3Jlc3Npb24gcmVzaWR1YWxzIGZvciBlYWNoIGNvdmFyaWF0ZSBvZiBpbnRlcmVzdCAodXN1YWxseSwgZWFjaCBvZiB0aG9zZSBpbmNsdWRlZCBpbiB0aGUgcHJvcGVuc2l0eSBtb2RlbCkgcmVncmVzc2VkIG9uIGEgc2luZ2xlIHByZWRpY3RvciAtIHRoZSBsaW5lYXIgcHJvcGVuc2l0eSBzY29yZS4gV2UgdGhlbiBsb29rIHRvIHNlZSBpZiB0aGUgcmF0aW8gb2YgdGhlIHZhcmlhbmNlIG9mIHRoZSByZXNpZHVhbHMgb2YgdGhpcyBtb2RlbCBmb3IgdGhlIHRyZWF0bWVudCBncm91cCBkaXZpZGVkIGJ5IHRoZSB2YXJpYW5jZSBvZiB0aGUgcmVzaWR1YWxzIG9mIHRoaXMgbW9kZWwgZm9yIHRoZSBjb250cm9sIGdyb3VwIGlzIGNsb3NlIHRvIDEuIEFnYWluLCBpZGVhbGx5IHRoaXMgd2lsbCBmYWxsIGJldHdlZW4gNC81IGFuZCA1LzQgZm9yIGVhY2ggY292YXJpYXRlLCBidXQgY2VydGFpbmx5IGJldHdlZW4gMS8yIGFuZCAyLiBJZiBzbywgdGhlbiB0aGUgdXNlIG9mIHJlZ3Jlc3Npb24gbW9kZWxzIHNlZW1zIHdlbGwganVzdGlmaWVkLg0KDQpUbyBldmFsdWF0ZSBSdWJpbidzIDNyZCBSdWxlLCB3ZSdsbCBjcmVhdGUgYSBsaXR0bGUgZnVuY3Rpb24gdG8gaGVscCB1cyBkbyB0aGUgY2FsY3VsYXRpb25zLg0KDQpgYGB7ciBydWJpbjMgZnVuY3Rpb259DQojIyBHZW5lcmFsIGZ1bmN0aW9uIHJ1YmluMyB0byBoZWxwIGNhbGN1bGF0ZSBSdWJpbidzIFJ1bGUgMw0KcnViaW4zIDwtIGZ1bmN0aW9uKGRhdGEsIGNvdmxpc3QsIGxpbnBzKSB7DQogIGNvdmxpc3QyIDwtIGFzLm1hdHJpeChjb3ZsaXN0KQ0KICByZXMgPC0gTkENCiAgZm9yKGkgaW4gMTpuY29sKGNvdmxpc3QyKSkgew0KICAgIGNvdiA8LSBhcy5udW1lcmljKGNvdmxpc3QyWyxpXSkNCiAgICBudW0gPC0gdmFyKHJlc2lkKGxtKGNvdiB+IGRhdGEkbGlucHMpKVtkYXRhJGV4cG9zdXJlID09IDFdKQ0KICAgIGRlbiA8LSB2YXIocmVzaWQobG0oY292IH4gZGF0YSRsaW5wcykpW2RhdGEkZXhwb3N1cmUgPT0gMF0pDQogICAgcmVzW2ldIDwtIGRlY2ltKG51bS9kZW4sIDMpDQogIH0NCiAgZmluYWwgPC0gdGliYmxlKG5hbWUgPSBuYW1lcyhjb3ZsaXN0KSwgcmVzaWQudmFyLnJhdGlvID0gYXMubnVtZXJpYyhyZXMpKQ0KICByZXR1cm4oZmluYWwpDQp9DQpgYGANCg0KTm93LCB0aGVuLCBhcHBseWluZyB0aGUgcnVsZSB0byBvdXIgc2FtcGxlIHByaW9yIHRvIHByb3BlbnNpdHkgc2NvcmUgYWRqdXN0bWVudCwgd2UgZ2V0IHRoZSBmb2xsb3dpbmcgcmVzdWx0LiBOb3RlIHRoYXQgSSdtIHVzaW5nIHRoZSBpbmRpY2F0b3IgdmFyaWFibGUgZm9ybXMgZm9yIHRoZSBgY292RmAgaW5mb3JtYXRpb24uDQoNCmBgYHtyfQ0KY292LnN1YiA8LSB0b3kgJT4lIHNlbGVjdChjb3ZBLCBjb3ZCLCBjb3ZDLCBjb3ZELCBjb3ZFLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNvdkYuTWlkZGxlLCBjb3ZGLkhpZ2gsIEFzcXIsIEJDLCBCRCkNCg0KdG95JGV4cG9zdXJlIDwtIHRveSR0cmVhdGVkDQoNCnJ1YmluMy51bmFkaiA8LSBydWJpbjMoZGF0YSA9IHRveSwgY292bGlzdCA9IGNvdi5zdWIsIGxpbnBzID0gbGlucHMpDQpydWJpbjMudW5hZGoNCmBgYA0KDQpTb21lIG9mIHRoZXNlIGNvdmFyaWF0ZXMgbG9vayB0byBoYXZlIHJlc2lkdWFsIHZhcmlhbmNlIHJhdGlvcyBuZWFyIDEsIHdoaWxlIG90aGVycyBhcmUgZnVydGhlciBhd2F5LCBidXQgYWxsIGFyZSB3aXRoaW4gdGhlICgwLjUsIDIuMCkgcmFuZ2UuIFNvIHdlJ2QgcGFzcyBSdWxlIDMgaGVyZSwgYWx0aG91Z2ggd2UnZCBjbGVhcmx5IGxpa2UgdG8gc2VlIHNvbWUgY292YXJpYXRlcyAoQSBhbmQgRSwgaW4gcGFydGljdWxhcikgd2l0aCByYXRpb3MgY2xvc2VyIHRvIDEuDQoNCiMjIyBBIENsZXZlbGFuZCBEb3QgQ2hhcnQgb2YgdGhlIFJ1YmluJ3MgUnVsZSAzIFJlc3VsdHMNCg0KYGBge3IgcnViaW4zX2NoYXJ0fQ0KZ2dwbG90KHJ1YmluMy51bmFkaiwgYWVzKHggPSByZXNpZC52YXIucmF0aW8sIHkgPSByZW9yZGVyKG5hbWUsIHJlc2lkLnZhci5yYXRpbykpKSArDQogICAgZ2VvbV9wb2ludChjb2wgPSAiYmx1ZSIsIHNpemUgPSAyKSArIA0KICAgIHRoZW1lX2J3KCkgKw0KICAgIHhsaW0oMC41LCAyLjApICsNCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gMSkpICsNCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gNC81KSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sID0gInJlZCIpICsNCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gNS80KSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sID0gInJlZCIpICsNCiAgbGFicyh4ID0gIlJlc2lkdWFsIFZhcmlhbmNlIFJhdGlvIiwgeSA9ICIiKSANCmBgYA0KDQpXZSBzZWUgdmFsdWVzIG91dHNpZGUgdGhlIDQvNSBhbmQgNS80IGxpbmVzLCBidXQgbm90aGluZyBmYWxscyBvdXRzaWRlICgwLjUsIDIpLg0KDQojIFRhc2sgNC4gVXNlIDE6MSBncmVlZHkgbWF0Y2hpbmcgb24gdGhlIGxpbmVhciBQUywgdGhlbiBjaGVjayBwb3N0LW1hdGNoIGJhbGFuY2UNCg0KQXMgcmVxdWVzdGVkLCB3ZSdsbCBkbyAxOjEgZ3JlZWR5IG1hdGNoaW5nIG9uIHRoZSBsaW5lYXIgcHJvcGVuc2l0eSBzY29yZSB3aXRob3V0IHJlcGxhY2VtZW50IGFuZCBicmVha2luZyB0aWVzIHJhbmRvbWx5LiBOb3RlIHRoYXQgd2UgdXNlIGByZXBsYWNlID0gRkFMU0VgIGFuZCBgdGllcz1GQUxTRWAgaGVyZS4gVG8gc3RhcnQsIHdlIHdvbid0IGluY2x1ZGUgYW4gb3V0Y29tZSB2YXJpYWJsZSBpbiBvdXIgY2FsbCB0byB0aGUgYE1hdGNoYCBmdW5jdGlvbiB3aXRoaW4gdGhlIGBNYXRjaGluZ2AgcGFja2FnZSBXZSdsbCB3aW5kIHVwIHdpdGggYSBtYXRjaCBpbmNsdWRpbmcgMTQwIHRyZWF0ZWQgYW5kIDE0MCBjb250cm9sIHN1YmplY3RzLiANCg0KYGBge3J9DQpYIDwtIHRveSRsaW5wcyAjIyBtYXRjaGluZyBvbiB0aGUgbGluZWFyIHByb3BlbnNpdHkgc2NvcmUNClRyIDwtIGFzLmxvZ2ljYWwodG95JHRyZWF0ZWQpDQptYXRjaDEgPC0gTWF0Y2goVHI9VHIsIFg9WCwgTSA9IDEsIHJlcGxhY2U9RkFMU0UsIHRpZXM9RkFMU0UpDQpzdW1tYXJ5KG1hdGNoMSkNCmBgYA0KDQojIyMgQWx0ZXJuYXRpdmUgTWF0Y2hpbmcgU3RyYXRlZ2llcy4NCg0KVGhlcmUgYXJlIG1hbnkgb3RoZXIgc3RyYXRlZ2llcyBhdmFpbGFibGUgZm9yIGRvaW5nIG1hdGNoaW5nIGluIFIsIG1hbnkgb2Ygd2hvbSBhcmUgc3VwcG9ydGVkIGJ5IG90aGVyIHBhY2thZ2VzIHdlJ2xsIHVzZSwgbGlrZSBgY29iYWx0YC4gT3VyIGZvY3VzIGluIHRoaXMgdG95IHByb2JsZW0gaXMgMToxIGdyZWVkeSBtYXRjaGluZyB1c2luZyB0aGUgYE1hdGNoaW5nYCBwYWNrYWdlLCBidXQgd2UnbGwgYWxzbyBicmllZmx5IG1lbnRpb24gYSBjb3VwbGUgb2Ygb3RoZXIgYXBwcm9hY2hlcyBhdmFpbGFibGUgaW4gdGhhdCBzYW1lIHBhY2thZ2UuDQoNClNwZWNpZmljYWxseSwgaWYgd2Ugd2FudGVkIHRvIGRvIG1hdGNoaW5nIHdpdGggcmVwbGFjZW1lbnQsIHdlIHdvdWxkIHVzZSBgcmVwbGFjZSA9IFRSVUVgICh3aGljaCBpcyBhY3R1YWxseSB0aGUgZGVmYXVsdCBjaG9pY2UpIGFuZCBtYWludGFpbiBgdGllcyA9IEZBTFNFYC4gDQoNCklmIHdlIHdhbnRlZCB0byBkbyAxOjIgcmF0aGVyIHRoYW4gMToxIG1hdGNoaW5nIHdpdGggdGhlIGBNYXRjaGAgZnVuY3Rpb24sIHdlIHdvdWxkIGNoYW5nZSBgTSA9IDFgIHRvIGBNID0gMmAuDQoNCjEuIFN1cHBvc2UgeW91IHJ1biBhIDE6MSBwcm9wZW5zaXR5IG1hdGNoICoqd2l0aCByZXBsYWNlbWVudCoqLiBZb3Ugc2hvdWxkIHdhbnQgdG8ga25vdzoNCiAgICAtIGhvdyBtYW55IHRyZWF0ZWQgc3ViamVjdHMgYXJlIGluIHlvdXIgbWF0Y2hlZCBzYW1wbGUsIGFuZCANCiAgICAtIGhvdyBtYW55IGNvbnRyb2wgc3ViamVjdHMgYXJlIGluIHlvdXIgbWF0Y2hlZCBzYW1wbGUNCg0KSWYgeW91IHJ1biBhIG1hdGNoIHVzaW5nIGBtYXRjaF93aXRoX3JlcGxhY2VtZW50IDwtIE1hdGNoKFksIFRyLCBYLCBNPTEsIHRpZXMgPSBGQUxTRSlgIHRoZW4gdGhlc2UgYW5zd2VycyBjb21lIGZyb20gYG5fZGlzdGluY3QobWF0Y2hfd2l0aF9yZXBsYWNlbWVudCRpbmRleC50cmVhdGVkKWAgYW5kIGBuX2Rpc3RpbmN0KG1hdGNoX3dpdGhfcmVwbGFjZW1lbnQkaW5kZXguY29udHJvbClgLCByZXNwZWN0aXZlbHkuIFRoaXMgbWV0aG9kIHdvcmtzIGZvciAxOjIgbWF0Y2hlcywgdG9vLg0KDQoyLiBXaGVuIG1hdGNoaW5nIHdpdGggcmVwbGFjZW1lbnQgdXNpbmcgdGhlIGBNYXRjaGAgZnVuY3Rpb24gZnJvbSB0aGUgTWF0Y2hpbmcgcGFja2FnZSwgeW91IHNob3VsZCBhbHdheXMgaW5kaWNhdGUgYHRpZXMgPSBGQUxTRWAuDQogICAgLSBTbywgYG1hdGNoMiA8LSBNYXRjaChUcj1UciwgWD1YLCBNID0gMSwgdGllcz1GQUxTRSlgIGlzIE9LLCBidXQgYG1hdGNoMiA8LSBNYXRjaChUcj1UciwgWD1YLCBNID0gMSlgIGlzIG5vdC4gDQogICAgLSBZb3UnbGwga25vdyBpdCB3b3JrZWQgcHJvcGVybHkgaWYgeW91IHJ1biBgbl9kaXN0aW5jdChtYXRjaDIkaW5kZXgudHJlYXRlZClgIGFuZCBgbl9kaXN0aW5jdChtYXRjaDIkaW5kZXguY29udHJvbClgIGFuZCB0aGUgY29udHJvbCBncm91cCBzaXplIGlzIG5vIGxhcmdlciB0aGFuIHRoZSB0cmVhdGVkIGdyb3VwIHNpemUuIA0KICAgIC0gVGhlIGNvbmNsdXNpb246IFJlZ2FyZGxlc3Mgb2Ygd2hldGhlciB5b3UncmUgZG9pbmcgd2l0aCBvciB3aXRob3V0IHJlcGxhY2VtZW50LCBydW4gYE1hdGNoYCB1c2luZyBgdGllcyA9IEZBTFNFYC4NCg0KDQojIyBCYWxhbmNlIEFzc2Vzc21lbnQgKFNlbWktQXV0b21hdGVkKQ0KDQpPSywgYmFjayB0byBvdXIgMToxIGdyZWVkeSBtYXRjaCB3aXRob3V0IHJlcGxhY2VtZW50LiBOZXh0LCB3ZSdsbCBhc3Nlc3MgdGhlIGJhbGFuY2UgaW1wb3NlZCBieSB0aGlzIGdyZWVkeSBtYXRjaCBvbiBvdXIgY292YXJpYXRlcywgYW5kIHRoZWlyIHRyYW5zZm9ybWF0aW9ucyAoYEFgXjIgYW5kIGBCKkNgIGFuZCBgQipEYCkgYXMgd2VsbCBhcyB0aGUgcmF3IGFuZCBsaW5lYXIgcHJvcGVuc2l0eSBzY29yZXMuIFRoZSBkZWZhdWx0IG91dHB1dCBmcm9tIHRoZSBgTWF0Y2hCYWxhbmNlYCBmdW5jdGlvbiBpcyBleHRlbnNpdmUuLi4NCg0KYGBge3J9DQpzZXQuc2VlZCg1MDAxKQ0KbWIxIDwtIE1hdGNoQmFsYW5jZSh0cmVhdGVkIH4gY292QSArIGNvdkIgKyBjb3ZDICsgY292RCArIGNvdkUgKyBjb3ZGICsgDQogICAgICAgICAgICAgICAgICAgICAgICBBc3FyICsgQkMgKyBCRCArIHBzICsgbGlucHMsIGRhdGE9dG95LCANCiAgICAgICAgICAgICAgICAgICAgbWF0Y2gub3V0ID0gbWF0Y2gxLCBuYm9vdHM9NTAwKQ0KYGBgDQoNClRoZSBgY29iYWx0YCBwYWNrYWdlIGhhcyBzb21lIHByb21pc2luZyB0b29scyBmb3IgdGFraW5nIHRoaXMgc29ydCBvZiBvdXRwdXQgYW5kIHR1cm5pbmcgaXQgaW50byBzb21ldGhpbmcgdXNlZnVsLiBXZSdsbCBsb29rIGF0IHRoYXQgYXBwcm9hY2ggc29vbi4gRm9yIG5vdywgc29tZSBvbGQtc2Nob29sIHN0dWZmLi4uDQoNCiMjIEV4dHJhY3RpbmcsIFRhYnVsYXRpbmcgU3RhbmRhcmRpemVkIERpZmZlcmVuY2VzICh3aXRob3V0IGBjb2JhbHRgKQ0KDQpXZSdsbCBzdGFydCBieSBuYW1pbmcgdGhlIGNvdmFyaWF0ZXMgdGhhdCB0aGUgYE1hdGNoQmFsYW5jZWAgb3V0cHV0IGNvbnRhaW5zLi4uDQoNCmBgYHtyfQ0KY292bmFtZXMgPC0gYygiY292QSIsICJjb3ZCIiwgImNvdkMiLCAiY292RCIsICJjb3ZFIiwgDQogICAgICAgICAgICAgICJjb3ZGIC0gTWlkZGxlIiwgImNvdkYgLSBIaWdoIiwgDQogICAgICAgICAgICAgICJBXjIiLCJCKkMiLCAiQipEIiwgInJhdyBQUyIsICJsaW5lYXIgUFMiKQ0KYGBgDQoNClRoZSBuZXh0IHN0ZXAgaXMgdG8gZXh0cmFjdCB0aGUgc3RhbmRhcmRpemVkIGRpZmZlcmVuY2VzICh1c2luZyB0aGUgcG9vbGVkIGRlbm9taW5hdG9yIHRvIGVzdGltYXRlLCByYXRoZXIgdGhhbiB0aGUgdHJlYXRtZW50LW9ubHkgZGVub21pbmF0b3IgdXNlZCBpbiB0aGUgbWFpbiBvdXRwdXQgYWJvdmUuKQ0KDQpgYGB7cn0NCnByZS5zemQgPC0gTlVMTDsgcG9zdC5zemQgPC0gTlVMTA0KZm9yKGkgaW4gMTpsZW5ndGgoY292bmFtZXMpKSB7DQogIHByZS5zemRbaV0gPC0gbWIxJEJlZm9yZU1hdGNoaW5nW1tpXV0kc2RpZmYucG9vbGVkDQogIHBvc3Quc3pkW2ldIDwtIG1iMSRBZnRlck1hdGNoaW5nW1tpXV0kc2RpZmYucG9vbGVkDQp9DQpgYGANCg0KTm93LCB3ZSBjYW4gYnVpbGQgYSB0YWJsZSBvZiB0aGUgc3RhbmRhcmRpemVkIGRpZmZlcmVuY2VzOg0KDQpgYGB7cn0NCm1hdGNoX3N6ZCA8LSB0aWJibGUoY292bmFtZXMsIHByZS5zemQsIHBvc3Quc3pkLCByb3cubmFtZXM9Y292bmFtZXMpDQpwcmludChtYXRjaF9zemQsIGRpZ2l0cz0zKQ0KYGBgDQoNCkFuZCB0aGVuLCB3ZSBjb3VsZCBwbG90IHRoZXNlLCBvciB0aGVpciBhYnNvbHV0ZSB2YWx1ZXMuIEhlcmUncyB3aGF0IHRoYXQgbG9va3MgbGlrZS4NCg0KIyMgQSBMb3ZlIFBsb3QgZGVzY3JpYmluZyBTdGFuZGFyZGl6ZWQgRGlmZmVyZW5jZXMgQmVmb3JlL0FmdGVyIE1hdGNoaW5nICh3aXRob3V0IGBjb2JhbHRgKQ0KDQpgYGB7cn0NCmdncGxvdChtYXRjaF9zemQsIGFlcyh4ID0gcHJlLnN6ZCwgeSA9IHJlb3JkZXIoY292bmFtZXMsIHByZS5zemQpKSkgKw0KICAgIGdlb21fcG9pbnQoY29sID0gImJsYWNrIiwgc2l6ZSA9IDMsIHBjaCA9IDEpICsgDQogICAgZ2VvbV9wb2ludChhZXMoeCA9IHBvc3Quc3pkLCB5ID0gcmVvcmRlcihjb3ZuYW1lcywgcHJlLnN6ZCkpLCANCiAgICAgICAgICAgICAgIHNpemUgPSAzLCBjb2wgPSAiYmx1ZSIpICsNCiAgICB0aGVtZV9idygpICsNCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gMCkpICsNCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gMTApLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2wgPSAicmVkIikgKw0KICAgIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSAtMTApLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2wgPSAicmVkIikgKw0KICAgIGxhYnMoeCA9ICJTdGFuZGFyZGl6ZWQgRGlmZmVyZW5jZSAoJSkiLCB5ID0gIiIpIA0KYGBgDQoNCiMjIFVzaW5nIGBjb2JhbHRgIHRvIGJ1aWxkIGEgIkxvdmUgUGxvdCIgYWZ0ZXIgTWF0Y2hpbmcNCg0KYGBge3IgfQ0KYiA8LSBiYWwudGFiKG1hdGNoMSwgDQogICAgICAgICAgICAgdHJlYXRlZCB+IGNvdkEgKyBjb3ZCICsgY292QyArIGNvdkQgKyANCiAgICAgICAgICAgICAgIGNvdkUgKyBjb3ZGICsgQXNxciArIEJDICsgQkQgKyBwcyArIGxpbnBzLCANCiAgICAgICAgICAgICBkYXRhPXRveSwgc3RhdHMgPSBjKCJtIiwgInYiKSwgdW4gPSBUUlVFLA0KICAgICAgICAgICAgIHF1aWNrID0gRkFMU0UpDQpiDQpgYGANCg0KIyMjIEJ1aWxkaW5nIGEgUGxvdCBvZiBTdGFuZGFyZGl6ZWQgRGlmZmVyZW5jZXMsIHdpdGggYGNvYmFsdGANCg0KYGBge3IgfQ0KcCA8LSBsb3ZlLnBsb3QoYiwgdGhyZXNob2xkID0gLjEsIHNpemUgPSAzLA0KICAgICAgICAgICAgICAgdmFyLm9yZGVyID0gInVuYWRqdXN0ZWQiLA0KICAgICAgICAgICAgICAgdGl0bGUgPSAiU3RhbmRhcmRpemVkIERpZmZlcmVuY2VzIGFuZCAxOjEgTWF0Y2hpbmciKQ0KcCArIHRoZW1lX2J3KCkNCmBgYA0KDQojIyMgQnVpbGRpbmcgYSBQbG90IG9mIFZhcmlhbmNlIFJhdGlvcywgd2l0aCBgY29iYWx0YA0KDQpgYGB7ciB9DQpwIDwtIGxvdmUucGxvdChiLCBzdGF0ID0gInYiLA0KICAgICAgICAgICAgICAgdGhyZXNob2xkID0gMS4yNSwgc2l6ZSA9IDMsDQogICAgICAgICAgICAgICB2YXIub3JkZXIgPSAidW5hZGp1c3RlZCIsDQogICAgICAgICAgICAgICB0aXRsZSA9ICJWYXJpYW5jZSBSYXRpb3MgYW5kIDE6MSBNYXRjaGluZyIpDQpwICsgdGhlbWVfYncoKQ0KYGBgDQoNCiMjIEV4dHJhY3RpbmcsIFRhYnVsYXRpbmcgVmFyaWFuY2UgUmF0aW9zICh3aXRob3V0IGBjb2JhbHRgKQ0KDQpOZXh0LCB3ZSBleHRyYWN0IHRoZSB2YXJpYW5jZSByYXRpb3MsIGFuZCBidWlsZCBhIHRhYmxlLg0KDQpgYGB7cn0NCnByZS52cmF0aW8gPC0gTlVMTDsgcG9zdC52cmF0aW8gPC0gTlVMTA0KZm9yKGkgaW4gMTpsZW5ndGgoY292bmFtZXMpKSB7DQogIHByZS52cmF0aW9baV0gPC0gbWIxJEJlZm9yZU1hdGNoaW5nW1tpXV0kdmFyLnJhdGlvDQogIHBvc3QudnJhdGlvW2ldIDwtIG1iMSRBZnRlck1hdGNoaW5nW1tpXV0kdmFyLnJhdGlvDQp9DQoNCiMjIFRhYmxlIG9mIFZhcmlhbmNlIFJhdGlvcw0KbWF0Y2hfdnJhdCA8LSB0aWJibGUobmFtZXMgPSBjb3ZuYW1lcywgcHJlLnZyYXRpbywgcG9zdC52cmF0aW8sIHJvdy5uYW1lcz1jb3ZuYW1lcykNCnByaW50KG1hdGNoX3ZyYXQsIGRpZ2l0cz0yKQ0KYGBgDQoNCiMjIENyZWF0aW5nIGEgTmV3IERhdGEgRnJhbWUsIENvbnRhaW5pbmcgdGhlIE1hdGNoZWQgU2FtcGxlICh3aXRob3V0IGBjb2JhbHRgKQ0KDQpOb3csIHdlIGJ1aWxkIGEgbmV3IG1hdGNoZWQgc2FtcGxlIGRhdGEgZnJhbWUgaW4gb3JkZXIgdG8gZG8gc29tZSBvZiB0aGUgYW5hbHlzZXMgdG8gY29tZS4gVGhpcyB3aWxsIGNvbnRhaW4gb25seSB0aGUgMjgwIG1hdGNoZWQgc3ViamVjdHMgKDE0MCB0cmVhdGVkIGFuZCAxNDAgY29udHJvbCkuDQoNCmBgYHtyfQ0KbWF0Y2hlcyA8LSBmYWN0b3IocmVwKG1hdGNoMSRpbmRleC50cmVhdGVkLCAyKSkNCnRveS5tYXRjaGVkc2FtcGxlIDwtIGNiaW5kKG1hdGNoZXMsIHRveVtjKG1hdGNoMSRpbmRleC5jb250cm9sLCBtYXRjaDEkaW5kZXgudHJlYXRlZCksXSkNCmBgYA0KDQpTb21lIHNhbml0eSBjaGVja3M6DQoNCmBgYHtyfQ0KdG95Lm1hdGNoZWRzYW1wbGUgJT4lIGNvdW50KHRyZWF0ZWRfZikNCg0KaGVhZCh0b3kubWF0Y2hlZHNhbXBsZSkNCmBgYA0KDQojIyBSdWJpbidzIFJ1bGVzIHRvIENoZWNrIEJhbGFuY2UgQWZ0ZXIgTWF0Y2hpbmcNCg0KIyMjIFJ1YmluJ3MgUnVsZSAxDQoNClJ1YmluJ3MgUnVsZSAxIHN0YXRlcyB0aGF0IHRoZSBhYnNvbHV0ZSB2YWx1ZSBvZiB0aGUgc3RhbmRhcmRpemVkIGRpZmZlcmVuY2Ugb2YgdGhlIGxpbmVhciBwcm9wZW5zaXR5IHNjb3JlLCBjb21wYXJpbmcgdGhlIHRyZWF0ZWQgZ3JvdXAgdG8gdGhlIGNvbnRyb2wgZ3JvdXAsIHNob3VsZCBiZSBjbG9zZSB0byAwLCBpZGVhbGx5IGJlbG93IDEwJSwgYW5kIGluIGFueSBjYXNlIGxlc3MgdGhhbiA1MCUuIElmIHNvLCB3ZSBtYXkgbW92ZSBvbiB0byBSdWxlIDIuDQoNClJlY2FsbCB0aGF0IG91ciByZXN1bHQgd2l0aG91dCBwcm9wZW5zaXR5IG1hdGNoaW5nIChvciBhbnkgb3RoZXIgYWRqdXN0bWVudCkgd2FzIA0KDQpgYGB7cn0NCnJ1YmluMS51bmFkag0KYGBgDQoNClRvIHJ1biB0aGlzIGZvciBvdXIgbWF0Y2hlZCBzYW1wbGUsIHdlIHVzZToNCg0KYGBge3J9DQpydWJpbjEubWF0Y2ggPC0gd2l0aCh0b3kubWF0Y2hlZHNhbXBsZSwNCiAgICAgIGFicygxMDAqKG1lYW4obGlucHNbdHJlYXRlZD09MV0pLW1lYW4obGlucHNbdHJlYXRlZD09MF0pKS9zZChsaW5wcykpKQ0KcnViaW4xLm1hdGNoDQpgYGANCg0KSGVyZSwgd2UndmUgYXQgbGVhc3QgZ290IHRoaXMgdmFsdWUgZG93biBiZWxvdyA1MFwlLCBzbyB3ZSB3b3VsZCBwYXNzIFJ1bGUgMSwgYWx0aG91Z2ggcGVyaGFwcyBhIGRpZmZlcmVudCBwcm9wZW5zaXR5IHNjb3JlIGFkanVzdG1lbnQgKHBlcmhhcHMgYnkgd2VpZ2h0aW5nIG9yIHN1YmNsYXNzaWZpY2F0aW9uLCBvciB1c2luZyBhIGRpZmZlcmVudCBtYXRjaGluZyBhcHByb2FjaCkgbWlnaHQgaW1wcm92ZSB0aGlzIHJlc3VsdCBieSBnZXR0aW5nIGl0IGNsb3NlciB0byAwLg0KDQojIyMgUnViaW4ncyBSdWxlIDINCg0KUnViaW4ncyBSdWxlIDIgc3RhdGVzIHRoYXQgdGhlIHJhdGlvIG9mIHRoZSB2YXJpYW5jZSBvZiB0aGUgbGluZWFyIHByb3BlbnNpdHkgc2NvcmUgaW4gdGhlIHRyZWF0ZWQgZ3JvdXAgdG8gdGhlIHZhcmlhbmNlIG9mIHRoZSBsaW5lYXIgcHJvcGVuc2l0eSBzY29yZSBpbiB0aGUgY29udHJvbCBncm91cCBzaG91bGQgYmUgY2xvc2UgdG8gMSwgaWRlYWxseSBiZXR3ZWVuIDQvNSBhbmQgNS80LCBidXQgY2VydGFpbmx5IG5vdCB2ZXJ5IGNsb3NlIHRvIG9yIGV4Y2VlZGluZyAxLzIgYW5kIDIuIElmIHNvLCB3ZSBtYXkgbW92ZSBvbiB0byBSdWxlIDMuDQoNClJlY2FsbCB0aGF0IG91ciByZXN1bHQgd2l0aG91dCBwcm9wZW5zaXR5IG1hdGNoaW5nIChvciBhbnkgb3RoZXIgYWRqdXN0bWVudCkgd2FzIA0KDQpgYGB7cn0NCnJ1YmluMi51bmFkag0KYGBgDQoNClRvIHJ1biB0aGlzIGZvciBvdXIgbWF0Y2hlZCBzYW1wbGUsIHdlIHVzZToNCg0KYGBge3J9DQpydWJpbjIubWF0Y2ggPC0gd2l0aCh0b3kubWF0Y2hlZHNhbXBsZSwgdmFyKGxpbnBzW3RyZWF0ZWQ9PTFdKS92YXIobGlucHNbdHJlYXRlZD09MF0pKQ0KcnViaW4yLm1hdGNoDQpgYGANCg0KVGhpcyBpcyBtb2RlcmF0ZWx5IHByb21pc2luZyAtIGEgc3Vic3RhbnRpYWwgaW1wcm92ZW1lbnQgb3ZlciBvdXIgdW5hZGp1c3RlZCByZXN1bHQsIGFuZCBub3csIGp1c3QgYmFyZWx5IHdpdGhpbiBvdXIgZGVzaXJlZCByYW5nZSBvZiA0LzUgdG8gNS80LCBhbmQgY2xlYXJseSB3aXRoaW4gMS8yIHRvIDIuIA0KDQpXZSBwYXNzIFJ1bGUgMiwgYXMgd2VsbC4NCg0KIyMjIFJ1YmluJ3MgUnVsZSAzDQoNCkZvciBSdWJpbidzIFJ1bGUgMywgd2UgYmVnaW4gYnkgY2FsY3VsYXRpbmcgcmVncmVzc2lvbiByZXNpZHVhbHMgZm9yIGVhY2ggY292YXJpYXRlIG9mIGludGVyZXN0ICh1c3VhbGx5LCBlYWNoIG9mIHRob3NlIGluY2x1ZGVkIGluIHRoZSBwcm9wZW5zaXR5IG1vZGVsKSByZWdyZXNzZWQgb24gYSBzaW5nbGUgcHJlZGljdG9yIC0gdGhlIGxpbmVhciBwcm9wZW5zaXR5IHNjb3JlLiBXZSB0aGVuIGxvb2sgdG8gc2VlIGlmIHRoZSByYXRpbyBvZiB0aGUgdmFyaWFuY2Ugb2YgdGhlIHJlc2lkdWFscyBvZiB0aGlzIG1vZGVsIGZvciB0aGUgdHJlYXRtZW50IGdyb3VwIGRpdmlkZWQgYnkgdGhlIHZhcmlhbmNlIG9mIHRoZSByZXNpZHVhbHMgb2YgdGhpcyBtb2RlbCBmb3IgdGhlIGNvbnRyb2wgZ3JvdXAgaXMgY2xvc2UgdG8gMS4gQWdhaW4sIGlkZWFsbHkgdGhpcyB3aWxsIGZhbGwgYmV0d2VlbiA0LzUgYW5kIDUvNCBmb3IgZWFjaCBjb3ZhcmlhdGUsIGJ1dCBjZXJ0YWlubHkgYmV0d2VlbiAxLzIgYW5kIDIuIElmIHNvLCB0aGVuIHRoZSB1c2Ugb2YgcmVncmVzc2lvbiBtb2RlbHMgc2VlbXMgd2VsbCBqdXN0aWZpZWQuDQoNClJlY2FsbCB0aGF0IG91ciByZXN1bHQgd2l0aG91dCBwcm9wZW5zaXR5IG1hdGNoaW5nIChvciBhbnkgb3RoZXIgYWRqdXN0bWVudCkgd2FzIA0KDQpgYGB7cn0NCnJ1YmluMy51bmFkag0KYGBgDQoNCkFmdGVyIHByb3BlbnNpdHkgbWF0Y2hpbmcsIHdlIHVzZSB0aGlzIGNvZGUgdG8gYXNzZXNzIFJ1YmluJ3MgM3JkIFJ1bGUgaW4gb3VyIG1hdGNoZWQgc2FtcGxlLg0KDQpgYGB7cn0NCmNvdi5zdWIgPC0gZHBseXI6OnNlbGVjdCh0b3kubWF0Y2hlZHNhbXBsZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBjb3ZBLCBjb3ZCLCBjb3ZDLCBjb3ZELCBjb3ZFLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNvdkYuTWlkZGxlLCBjb3ZGLkhpZ2gsIEFzcXIsIEJDLCBCRCkNCg0KdG95Lm1hdGNoZWRzYW1wbGUkZXhwb3N1cmUgPC0gdG95Lm1hdGNoZWRzYW1wbGUkdHJlYXRlZA0KDQpydWJpbjMubWF0Y2hlZCA8LSBydWJpbjMoZGF0YSA9IHRveS5tYXRjaGVkc2FtcGxlLCBjb3ZsaXN0ID0gY292LnN1YiwgbGlucHMgPSBsaW5wcykNCg0KcnViaW4zLm1hdGNoZWQNCmBgYA0KDQpJdCBsb29rcyBsaWtlIHRoZSByZXN1bHRzIGFyZSBiYXNpY2FsbHkgdW5jaGFuZ2VkLCBleGNlcHQgdGhhdCBgY292Ri5IaWdoYCBpcyBpbXByb3ZlZC4gVGhlIGRvdHBsb3Qgb2YgdGhlc2UgcmVzdWx0cyBjb21wYXJpbmcgcHJlLSB0byBwb3N0LW1hdGNoaW5nIGlzIHNob3duIGJlbG93Lg0KDQojIyMgQSBDbGV2ZWxhbmQgRG90IENoYXJ0IG9mIHRoZSBSdWJpbidzIFJ1bGUgMyBSZXN1bHRzIFByZSB2cy4gUG9zdC1NYXRjaA0KDQpgYGB7ciBydWJpbjMgZG90IGNoYXJ0IHByZSBhbmQgcG9zdCBtYXRjaH0NCnJ1YmluMy5ib3RoIDwtIGJpbmRfcm93cyhydWJpbjMudW5hZGosIHJ1YmluMy5tYXRjaGVkKQ0KcnViaW4zLmJvdGgkc291cmNlIDwtIGMocmVwKCJVbm1hdGNoZWQiLDEwKSwgcmVwKCJNYXRjaGVkIiwgMTApKQ0KDQpnZ3Bsb3QocnViaW4zLmJvdGgsIGFlcyh4ID0gcmVzaWQudmFyLnJhdGlvLCB5ID0gbmFtZSwgY29sID0gc291cmNlKSkgKw0KICAgIGdlb21fcG9pbnQoc2l6ZSA9IDMpICsgDQogICAgdGhlbWVfYncoKSArDQogICAgeGxpbSgwLjUsIDIuMCkgKw0KICAgIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSAxKSkgKw0KICAgIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSA0LzUpLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2wgPSAicmVkIikgKw0KICAgIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSA1LzQpLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2wgPSAicmVkIikgKw0KICBsYWJzKHggPSAiUmVzaWR1YWwgVmFyaWFuY2UgUmF0aW8iLCB5ID0gIiIpIA0KYGBgDQoNClNvbWUgaW1wcm92ZW1lbnQgdG8gcmVwb3J0LCBvdmVyYWxsLg0KDQojIFRhc2sgNS4gQWZ0ZXIgbWF0Y2hpbmcsIGVzdGltYXRlIHRoZSBjYXVzYWwgZWZmZWN0IG9mIHRyZWF0bWVudCBvbiAuLi4NCg0KIyMgT3V0Y29tZSAxIChhIGNvbnRpbnVvdXMgb3V0Y29tZSkNCg0KIyMjIEFwcHJvYWNoIDEuIEF1dG9tYXRlZCBBcHByb2FjaCBmcm9tIHRoZSBNYXRjaGluZyBwYWNrYWdlIC0gQVRUIEVzdGltYXRlDQoNCkZpcnN0LCB3ZSdsbCBsb29rIGF0IHRoZSBlc3NlbnRpYWxseSBhdXRvbWF0aWMgYW5zd2VyIHdoaWNoIGNhbiBiZSBvYnRhaW5lZCB3aGVuIHVzaW5nIHRoZSBgTWF0Y2hpbmdgIHBhY2thZ2UgYW5kIGluc2VydGluZyBhbiBvdXRjb21lIFkuIEZvciBhIGNvbnRpbnVvdXMgb3V0Y29tZSwgdGhpcyBpcyBvZnRlbiBhIHJlYXNvbmFibGUgYXBwcm9hY2guDQoNCmBgYHtyfQ0KWCA8LSB0b3kkbGlucHMgIyMgbWF0Y2hpbmcgb24gdGhlIGxpbmVhciBwcm9wZW5zaXR5IHNjb3JlDQpUciA8LSBhcy5sb2dpY2FsKHRveSR0cmVhdGVkKQ0KWSA8LSB0b3kkb3V0MS5jb3N0DQptYXRjaDEub3V0MSA8LSBNYXRjaChZPVksIFRyPVRyLCBYPVgsIE0gPSAxLCByZXBsYWNlPUZBTFNFLCB0aWVzPUZBTFNFKQ0Kc3VtbWFyeShtYXRjaDEub3V0MSkNCmBgYA0KDQpUaGUgZXN0aW1hdGUgaXMgYHIgZGVjaW0obWF0Y2gxLm91dDEkZXN0Lm5vYWRqLCAyKWAgd2l0aCBzdGFuZGFyZCBlcnJvciBgciBkZWNpbShtYXRjaDEub3V0MSRzZS5zdGFuZGFyZCwyKWAuIFdlIGNhbiBvYnRhaW4gYW4gYXBwcm94aW1hdGUgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgYnkgYWRkaW5nIGFuZCBzdWJ0cmFjdGluZyAxLjk2IHRpbWVzIChvciBqdXN0IGRvdWJsZSkgdGhlIHN0YW5kYXJkIGVycm9yIChTRSkgdG8gdGhlIHBvaW50IGVzdGltYXRlLCBgciBkZWNpbShtYXRjaDEub3V0MSRlc3Qubm9hZGosIDIpYC4gSGVyZSwgdXNpbmcgdGhlIDEuOTYgZmlndXJlLCB0aGF0IHdvdWxkIHlpZWxkcyBhbiBhcHByb3hpbWF0ZSA5NSUgQ0kgb2YgKGByIGRlY2ltKG1hdGNoMS5vdXQxJGVzdC5ub2FkaiAtIDEuOTYqbWF0Y2gxLm91dDEkc2Uuc3RhbmRhcmQsIDIpYCwgYHIgZGVjaW0obWF0Y2gxLm91dDEkZXN0Lm5vYWRqICsgMS45NiptYXRjaDEub3V0MSRzZS5zdGFuZGFyZCwgMilgKS4NCg0KIyMjIEFwcHJvYWNoIDIuIEF1dG9tYXRlZCBBcHByb2FjaCBmcm9tIHRoZSBNYXRjaGluZyBwYWNrYWdlIC0gQVRFIEVzdGltYXRlDQoNCmBgYHtyfQ0KbWF0Y2gxLm91dDEuQVRFIDwtIE1hdGNoKFk9WSwgVHI9VHIsIFg9WCwgTSA9IDEsIHJlcGxhY2U9RkFMU0UsIHRpZXM9RkFMU0UsIGVzdGltYW5kPSJBVEUiKQ0Kc3VtbWFyeShtYXRjaDEub3V0MS5BVEUpDQpgYGANCg0KQW5kIG91ciA5NSUgQ0kgZm9yIHRoaXMgQVRFIGVzdGltYXRlIHdvdWxkIGJlIGByIGRlY2ltKG1hdGNoMS5vdXQxLkFURSRlc3Qubm9hZGosIDIpYCAkXHBtJCAxLjk2KGByIGRlY2ltKG1hdGNoMS5vdXQxLkFURSRzZS5zdGFuZGFyZCwgMilgKSwgb3IgKGByIGRlY2ltKG1hdGNoMS5vdXQxLkFURSRlc3Qubm9hZGogLSAxLjk2Km1hdGNoMS5vdXQxLkFURSRzZS5zdGFuZGFyZCwgMilgLCBgciBkZWNpbShtYXRjaDEub3V0MS5BVEUkZXN0Lm5vYWRqICsgMS45NiptYXRjaDEub3V0MS5BVEUkc2Uuc3RhbmRhcmQsIDIpYCksIGJ1dCB3ZSdsbCBzdGljayB3aXRoIHRoZSBBVFQgZXN0aW1hdGUgZm9yIG5vdy4NCg0KIyMjIEFUVCB2cy4gQVRFOiBEZWZpbml0aW9ucw0KDQotIEluZm9ybWFsbHksIHRoZSAqKmF2ZXJhZ2UgdHJlYXRtZW50IGVmZmVjdCBvbiB0aGUgdHJlYXRlZCoqIChBVFQpIGVzdGltYXRlIGRlc2NyaWJlcyB0aGUgZGlmZmVyZW5jZSBpbiBwb3RlbnRpYWwgb3V0Y29tZXMgKGJldHdlZW4gdHJlYXRlZCBhbmQgdW50cmVhdGVkIHN1YmplY3RzKSBzdW1tYXJpemVkIGFjcm9zcyB0aGUgcG9wdWxhdGlvbiBvZiBwZW9wbGUgd2hvIGFjdHVhbGx5IHJlY2VpdmVkIHRoZSB0cmVhdG1lbnQuIA0KICAgICsgSW4gb3VyIGluaXRpYWwgbWF0Y2gsIHdlIGlkZW50aWZpZWQgYSB1bmlxdWUgYW5kIG5pY2VseSBtYXRjaGVkIGNvbnRyb2wgcGF0aWVudCBmb3IgZWFjaCBvZiB0aGUgMTQwIHBlb3BsZSBpbiB0aGUgdHJlYXRlZCBncm91cC4gV2UgaGF2ZSBhIDE6MSBtYXRjaCBvbiB0aGUgdHJlYXRlZCwgYW5kIHRodXMgY2FuIGRlc2NyaWJlIHN1YmplY3RzIGFjcm9zcyB0aGF0IHNldCBvZiB0cmVhdGVkIHBhdGllbnRzIHJlYXNvbmFibHkgd2VsbC4NCi0gT24gdGhlIG90aGVyIGhhbmQgdGhlICoqYXZlcmFnZSB0cmVhdG1lbnQgZWZmZWN0KiogKEFURSkgcmVmZXJzIHRvIHRoZSBkaWZmZXJlbmNlIGluIHBvdGVudGlhbCBvdXRjb21lcyBzdW1tYXJpemVkIGFjcm9zcyB0aGUgZW50aXJlIHBvcHVsYXRpb24sIGluY2x1ZGluZyB0aG9zZSB3aG8gZGlkIG5vdCByZWNlaXZlIHRoZSB0cmVhdG1lbnQuICANCiAgICArIEluIG91ciBBVEUgbWF0Y2gsIHdlIGhhdmUgbGVzcyBzdWNjZXNzLCBpbiBwYXJ0IGJlY2F1c2UgaWYgd2UgbWF0Y2ggdG8gdGhlIHRyZWF0ZWQgcGF0aWVudHMgaW4gYSAxOjEgd2F5LCB3ZSdsbCBoYXZlIGFuIGFkZGl0aW9uYWwgMTIwIHVubWF0Y2hlZCBjb250cm9sIHBhdGllbnRzLCBhYm91dCB3aG9tIHdlIGNhbiBkZXNjcmliZSByZXN1bHRzIG9ubHkgdmFndWVseS4gV2UgY291bGQgY29uc2lkZXIgbWF0Y2hpbmcgdXAgY29udHJvbCBwYXRpZW50cyB0byB0cmVhdGVkIHBhdGllbnRzLCBwZXJoYXBzIGNvbWJpbmVkIHdpdGggYSB3aWxsaW5nbmVzcyB0byByZS11c2Ugc29tZSBvZiB0aGUgdHJlYXRlZCBwYXRpZW50cyB0byBnZXQgYSBiZXR0ZXIgZXN0aW1hdGUgYWNyb3NzIHRoZSB3aG9sZSBwb3B1bGF0aW9uLg0KDQojIyMgQXBwcm9hY2ggMy4gTWlycm9yaW5nIHRoZSBQYWlyZWQgVCB0ZXN0IGluIGEgUmVncmVzc2lvbiBNb2RlbA0KDQpXZSBjYW4gbWlycm9yIHRoZSBwYWlyZWQgdCB0ZXN0IHJlc3VsdCBpbiBhIHJlZ3Jlc3Npb24gbW9kZWwgdGhhdCB0cmVhdHMgdGhlIG1hdGNoIGlkZW50aWZpZXIgYXMgYSBmaXhlZCBmYWN0b3IgaW4gYSBsaW5lYXIgbW9kZWwsIGFzIGZvbGxvd3MuIFRoaXMgdGFrZXMgdGhlIHBhaXJpbmcgaW50byBhY2NvdW50LCBidXQgdHJlYXRpbmcgcGFpcmluZyBhcyBhIGZpeGVkLCByYXRoZXIgdGhhbiByYW5kb20sIGZhY3RvciwgaXNuJ3QgcmVhbGx5IHNhdGlzZmFjdG9yeSBhcyBhIHNvbHV0aW9uLCBhbHRob3VnaCBpdCBkb2VzIG1hdGNoIHRoZSBwYWlyZWQgdCB0ZXN0Lg0KDQpgYGB7cn0NCmFkai5tLm91dDEgPC0gbG0ob3V0MS5jb3N0IH4gdHJlYXRlZCArIGZhY3RvcihtYXRjaGVzKSwgZGF0YT10b3kubWF0Y2hlZHNhbXBsZSkgDQoNCmFkai5tLm91dDEudGlkeSA8LSB0aWR5KGFkai5tLm91dDEsIGNvbmYuaW50ID0gVFJVRSkgJT4lIA0KICAgIGZpbHRlcih0ZXJtID09ICJ0cmVhdGVkIikNCg0KYWRqLm0ub3V0MS50aWR5DQpgYGANCg0KU28sIHRoaXMgcmVncmVzc2lvbiBhcHByb2FjaCBwcm9kdWNlcyBhbiBlc3RpbWF0ZSB0aGF0IGlzIGV4YWN0bHkgdGhlIHNhbWUgYXMgdGhlIHBhaXJlZCB0IHRlc3RbXjJdLCBidXQgdGhpcyBpc24ndCBzb21ldGhpbmcgSSdtIGNvbXBsZXRlbHkgY29tZm9ydGFibGUgd2l0aC4NCg0KW14yXTogSSdsbCBsZWF2ZSBjaGVja2luZyB0aGF0IHRoaXMgaXMgdHJ1ZSBhcyBhbiBleGVyY2lzZSBmb3IgdGhlIGN1cmlvdXMuDQoNCiMjIyBBcHByb2FjaCA0LiBBIE1peGVkIE1vZGVsIHRvIGFjY291bnQgZm9yIDE6MSBNYXRjaGluZw0KDQpXaGF0IEkgdGhpbmsgb2YgYXMgYSBtb3JlIGFwcHJvcHJpYXRlIHJlc3VsdCBjb21lcyBmcm9tIGEgbWl4ZWQgbW9kZWwgd2hlcmUgdGhlIG1hdGNoZXMgYXJlIHRyZWF0ZWQgYXMgYSByYW5kb20gZmFjdG9yLCBidXQgdGhlIHRyZWF0bWVudCBncm91cCBpcyB0cmVhdGVkIGFzIGEgZml4ZWQgZmFjdG9yLiBUaGlzIGlzIGRldmVsb3BlZCBsaWtlIHRoaXMsIHVzaW5nIHRoZSBgbG1lNGAgcGFja2FnZS4gTm90ZSB0aGF0IHdlIGhhdmUgdG8gY3JlYXRlIGEgZmFjdG9yIHZhcmlhYmxlIHRvIHJlcHJlc2VudCB0aGUgbWF0Y2hlcywgc2luY2UgdGhhdCdzIHRoZSBvbmx5IHRoaW5nIHRoYXQgYGxtZTRgIHVuZGVyc3RhbmRzLg0KDQpgYGB7cn0NCnRveS5tYXRjaGVkc2FtcGxlJG1hdGNoZXMuZiA8LSBhcy5mYWN0b3IodG95Lm1hdGNoZWRzYW1wbGUkbWF0Y2hlcykgDQojIyBOZWVkIHRvIHVzZSBtYXRjaGVzIGFzIGEgZmFjdG9yIGluIFIgaGVyZQ0KDQptYXRjaGVkX21peGVkbW9kZWwub3V0MSA8LSBsbWVyKG91dDEuY29zdCB+IHRyZWF0ZWQgKyAoMSB8IG1hdGNoZXMuZiksIGRhdGE9dG95Lm1hdGNoZWRzYW1wbGUpDQpzdW1tYXJ5KG1hdGNoZWRfbWl4ZWRtb2RlbC5vdXQxKTsgY29uZmludChtYXRjaGVkX21peGVkbW9kZWwub3V0MSkNCmBgYA0KDQpUaGUgYHRpZHlgIGFwcHJvYWNoIGZyb20gYGJyb29tYCBkb2Vzbid0IHdvcmsgd2l0aCBhIGxpbmVhciBtaXhlZCBtb2RlbCwgYnV0IHRoZSBgYnJvb20ubWl4ZWRgIHBhY2thZ2UgdmVyc2lvbiBvZiBgdGlkeWAgZG9lcywgc28gd2UgaGF2ZToNCg0KYGBge3J9DQpyZXNfbWF0Y2hlZF8xIDwtIGJyb29tLm1peGVkOjp0aWR5KG1hdGNoZWRfbWl4ZWRtb2RlbC5vdXQxLCANCiAgICAgICAgICAgICAgICAgICAgICBjb25mLmludCA9IFQsIGNvbmYubGV2ZWwgPSAwLjk1KSAlPiUgDQogICAgZmlsdGVyKHRlcm0gPT0gInRyZWF0ZWQiKQ0KDQpyZXNfbWF0Y2hlZF8xDQpgYGANCg0KT3VyIGVzdGltYXRlIGlzIGByIGRlY2ltKHJlc19tYXRjaGVkXzEkZXN0aW1hdGUsIDIpYCwgd2l0aCA5NSUgQ0kgcmFuZ2luZyBmcm9tIGByIGRlY2ltKHJlc19tYXRjaGVkXzEkY29uZi5sb3csIDIpYCB0byBgciBkZWNpbShyZXNfbWF0Y2hlZF8xJGNvbmYuaGlnaCwgMilgLg0KDQojIyMgUHJhY3RpY2FsbHksIGRvZXMgYW55IG9mIHRoaXMgbWF0dGVyIGluIHRoaXMgZXhhbXBsZT8NCg0KTm90IG11Y2ggaW4gdGhpcyBleGFtcGxlLCBubywgYXMgbG9uZyBhcyB5b3Ugc3RpY2sgdG8gQVRUIGFwcHJvYWNoZXMuDQoNCkFwcHJvYWNoIHwgRWZmZWN0IEVzdGltYXRlIHwgU3RhbmRhcmQgRXJyb3IgfCA5NSUgQ0kNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS06IHwgLS0tLS0tLS0tOiB8IC0tLS0tLS0tOiB8IC0tLS0tLS0tLS0tLS0tLQ0KIkF1dG9tYXRlZCIgQVRUIHZpYSBgTWF0Y2hgIHwgYHIgZGVjaW0obWF0Y2gxLm91dDEkZXN0Lm5vYWRqLCAyKWAgfCBgciBkZWNpbShtYXRjaDEub3V0MSRzZS5zdGFuZGFyZCwyKWAgfCAoYHIgZGVjaW0obWF0Y2gxLm91dDEkZXN0Lm5vYWRqIC0gMS45NiptYXRjaDEub3V0MSRzZS5zdGFuZGFyZCwgMilgLCBgciBkZWNpbShtYXRjaDEub3V0MSRlc3Qubm9hZGogKyAxLjk2Km1hdGNoMS5vdXQxJHNlLnN0YW5kYXJkLCAyKWApDQpMaW5lYXIgTW9kZWwgKHBhaXJzIGFzIGZpeGVkIGZhY3RvcikgfCBgciBkZWNpbShhZGoubS5vdXQxLnRpZHkkZXN0aW1hdGUsIDIpYCB8IGByIGRlY2ltKGFkai5tLm91dDEudGlkeSRzdGQuZXJyb3IsIDIpYCB8IChgciBkZWNpbShhZGoubS5vdXQxLnRpZHkkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oYWRqLm0ub3V0MS50aWR5JGNvbmYuaGlnaCwgMilgKQ0KTWl4ZWQgTW9kZWwgKHBhaXJzIGFzIHJhbmRvbSBmYWN0b3IpIHwgYHIgZGVjaW0ocmVzX21hdGNoZWRfMSRlc3RpbWF0ZSwgMilgIHwgYHIgZGVjaW0ocmVzX21hdGNoZWRfMSRzdGQuZXJyb3IsIDIpYCB8IChgciBkZWNpbShyZXNfbWF0Y2hlZF8xJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHJlc19tYXRjaGVkXzEkY29uZi5oaWdoLCAyKWApDQoNCg0KIyMgT3V0Y29tZSAyIChhIGJpbmFyeSBvdXRjb21lKQ0KDQojIyMgQXBwcm9hY2ggMS4gQXV0b21hdGVkIEFwcHJvYWNoIGZyb20gdGhlIE1hdGNoaW5nIHBhY2thZ2UgKEFUVCkNCg0KRmlyc3QsIHdlJ2xsIGxvb2sgYXQgdGhlIGVzc2VudGlhbGx5IGF1dG9tYXRpYyBhbnN3ZXIgd2hpY2ggY2FuIGJlIG9idGFpbmVkIHdoZW4gdXNpbmcgdGhlIGBNYXRjaGluZ2AgcGFja2FnZSBhbmQgaW5zZXJ0aW5nIGFuIG91dGNvbWUgWS4gRm9yIGEgYmluYXJ5IG91dGNvbWUsIHRoaXMgaXMgb2Z0ZW4gYSByZWFzb25hYmxlIGFwcHJvYWNoLCBlc3BlY2lhbGx5IGlmIHlvdSBkb24ndCB3aXNoIHRvIGFkanVzdCBmb3IgYW55IG90aGVyIGNvdmFyaWF0ZSwgYW5kIHRoZSByZXN1bHQgd2lsbCBiZSBleHByZXNzZWQgYXMgYSByaXNrIGRpZmZlcmVuY2UsIHJhdGhlciB0aGFuIGFzIGEgcmVsYXRpdmUgcmlzayBvciBvZGRzIHJhdGlvLiBOb3RlIHRoYXQgSSBoYXZlIHVzZWQgdGhlIDAtMSB2ZXJzaW9uIG9mIE91dGNvbWUgMiwgcmF0aGVyIHRoYW4gYSBmYWN0b3IgdmVyc2lvbi4gVGhlIGVzdGltYXRlIHByb2R1Y2VkIGlzIHRoZSBkaWZmZXJlbmNlIGluIHJpc2sgYXNzb2NpYXRlZCB3aXRoIGBvdXQyYCA9IDEgKFRyZWF0ZWQgc3ViamVjdHMpIG1pbnVzIGBvdXQyYCA9IDAgKENvbnRyb2xzLikNCg0KYGBge3J9DQpYIDwtIHRveSRsaW5wcyAjIyBtYXRjaGluZyBvbiB0aGUgbGluZWFyIHByb3BlbnNpdHkgc2NvcmUNClRyIDwtIGFzLmxvZ2ljYWwodG95JHRyZWF0ZWQpDQpZIDwtIHRveSRvdXQyDQptYXRjaDFfb3V0MiA8LSBNYXRjaChZPVksIFRyPVRyLCBYPVgsIE0gPSAxLCByZXBsYWNlPUZBTFNFLCB0aWVzPUZBTFNFKQ0Kc3VtbWFyeShtYXRjaDFfb3V0MikNCmBgYA0KDQpBcyBpbiB0aGUgY29udGludW91cyBjYXNlLCB3ZSBvYnRhaW4gYW4gYXBwcm94aW1hdGUgOTVcJSBjb25maWRlbmNlIGludGVydmFsIGJ5IGFkZGluZyBhbmQgc3VidHJhY3RpbmcgMS45NiB0aW1lcyAob3IganVzdCBkb3VibGUpIHRoZSBzdGFuZGFyZCBlcnJvciAoU0UpIHRvIHRoZSBwb2ludCBlc3RpbWF0ZS4gVGhlIGVzdGltYXRlZCBlZmZlY3Qgb24gdGhlIHJpc2sgZGlmZmVyZW5jZSBpcyBgciBkZWNpbShtYXRjaDFfb3V0MiRlc3Qubm9hZGosIDMpYCB3aXRoIHN0YW5kYXJkIGVycm9yIGByIGRlY2ltKG1hdGNoMV9vdXQyJHNlLnN0YW5kYXJkLCAzKWAgYW5kIDk1JSBDSSAoYHIgZGVjaW0obWF0Y2gxX291dDIkZXN0Lm5vYWRqIC0gMS45NiptYXRjaDFfb3V0MiRzZS5zdGFuZGFyZCwgMylgLCBgciBkZWNpbShtYXRjaDFfb3V0MiRlc3Qubm9hZGogKyAxLjk2Km1hdGNoMV9vdXQyJHNlLnN0YW5kYXJkLCAzKWApLg0KDQojIyMgQXBwcm9hY2ggMi4gVXNpbmcgdGhlIG1hdGNoZWQgc2FtcGxlIHRvIHBlcmZvcm0gYSBjb25kaXRpb25hbCBsb2dpc3RpYyByZWdyZXNzaW9uDQoNClNpbmNlIHdlIGhhdmUgdGhlIG1hdGNoZWQgc2FtcGxlIGF2YWlsYWJsZSwgd2UgY2FuIHNpbXBseSBwZXJmb3JtIGEgY29uZGl0aW9uYWwgbG9naXN0aWMgcmVncmVzc2lvbiB0byBlc3RpbWF0ZSB0aGUgdHJlYXRtZW50IGVmZmVjdCBpbiB0ZXJtcyBvZiBhIGxvZyBvZGRzIHJhdGlvIChvciwgYWZ0ZXIgZXhwb25lbnRpYXRpbmcsIGFuIG9kZHMgcmF0aW8uKSBBZ2FpbiwgSSB1c2UgdGhlIDAvMSB2ZXJzaW9uIG9mIGJvdGggdGhlIG91dGNvbWUgYW5kIHRyZWF0bWVudCBpbmRpY2F0b3IuIFRoZSBrZXkgbW9kZWxpbmcgZnVuY3Rpb24gYGNsb2dpdGAgaXMgcGFydCBvZiB0aGUgYHN1cnZpdmFsYCBwYWNrYWdlLg0KDQpgYGB7cn0NCmFkai5tLm91dDIgPC0gY2xvZ2l0KG91dDIgfiB0cmVhdGVkICsgc3RyYXRhKG1hdGNoZXMpLCBkYXRhPXRveS5tYXRjaGVkc2FtcGxlKQ0Kc3VtbWFyeShhZGoubS5vdXQyKQ0KYGBgDQoNClRoZSBvZGRzIHJhdGlvIGluIHRoZSBgZXhwKGNvZWYpYCBzZWN0aW9uIGFib3ZlIGlzIHRoZSBhdmVyYWdlIGNhdXNhbCBlZmZlY3QgZXN0aW1hdGUgLSBpdCBkZXNjcmliZXMgdGhlIG9kZHMgb2YgaGF2aW5nIGFuIGV2ZW50IChgb3V0MmApIG9jY3VyIGFzc29jaWF0ZWQgd2l0aCBiZWluZyBhIHRyZWF0ZWQgc3ViamVjdCwgYXMgY29tcGFyZWQgdG8gdGhlIG9kZHMgb2YgdGhlIGV2ZW50IHdoZW4gYSBjb250cm9sIHN1YmplY3QuIA0KDQpJIHRpZGllZCB0aGlzLCBhcyBmb2xsb3dzLCB3aXRoIGBjb25mLmludCA9IFRSVUVgLCBhbmQgZ290IC4uLg0KDQpgYGB7cn0NCmFkai5tLm91dDJfdGlkeSA8LSB0aWR5KGFkai5tLm91dDIsIGV4cG9uZW50aWF0ZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICBjb25mLmludCA9IFRSVUUpDQoNCmFkai5tLm91dDJfdGlkeQ0KYGBgDQoNCk91ciBwb2ludCBlc3RpbWF0ZSBpcyBgciBkZWNpbShhZGoubS5vdXQyX3RpZHkkZXN0aW1hdGUsIDIpYCwgd2l0aCBzdGFuZGFyZCBlcnJvciBgciBkZWNpbShhZGoubS5vdXQyX3RpZHkkc3RkLmVycm9yLCAyKWAsIGFuZCA5NSUgQ0kgcmFuZ2luZyBmcm9tIGByIGRlY2ltKGFkai5tLm91dDJfdGlkeSRjb25mLmxvdywgMilgIHRvIGByIGRlY2ltKGFkai5tLm91dDJfdGlkeSRjb25mLmhpZ2gsIDIpYC4NCg0KLSBJJ2xsIHVzZSB0aGlzIGNvbmRpdGlvbmFsIGxvZ2lzdGljIHJlZ3Jlc3Npb24gYXBwcm9hY2ggdG8gc3VtbWFyaXplIHRoZSBmaW5kaW5ncyB3aXRoIHJlZ2FyZCB0byBhbiBvZGRzIHJhdGlvIGluIG15IHN1bW1hcnkgb2YgbWF0Y2hpbmcgcmVzdWx0cyB0byBjb21lLg0KDQojIyBPdXRjb21lIDMgKGEgdGltZS10by1ldmVudCBvdXRjb21lKQ0KDQojIyMgQXBwcm9hY2ggMS4gQXV0b21hdGVkIEFwcHJvYWNoIGZyb20gdGhlIE1hdGNoaW5nIHBhY2thZ2UNCg0KQWdhaW4sIHdlJ2xsIHN0YXJ0IGJ5IHRoaW5raW5nIGFib3V0IHRoZSBlc3NlbnRpYWxseSBhdXRvbWF0aWMgYW5zd2VyIHdoaWNoIGNhbiBiZSBvYnRhaW5lZCB3aGVuIHVzaW5nIHRoZSBgTWF0Y2hgIGZ1bmN0aW9uLiBUaGUgcHJvYmxlbSBoZXJlIGlzIHRoYXQgdGhpcyBhcHByb2FjaCBkb2Vzbid0IHRha2UgaW50byBhY2NvdW50IHRoZSByaWdodCBjZW5zb3JpbmcgYXQgYWxsLCBhbmQgYXNzdW1lcyB0aGF0IGFsbCBvZiB0aGUgc3BlY2lmaWVkIHRpbWVzIGluIE91dGNvbWUgMyBhcmUgb2JzZXJ2ZWQuIFRoaXMgY2F1c2VzIHRoZSByZXN1bHQgKG9yIHRoZSBBVEUgdmVyc2lvbikgdG8gbm90IG1ha2Ugc2Vuc2UsIGdpdmVuIHdoYXQgd2Uga25vdyBhYm91dCB0aGUgZGF0YS4gU28gSSBkb24ndCByZWNvbW1lbmQgeW91IHVzZSB0aGlzIGFwcHJvYWNoIHdoZW4gZGVhbGluZyB3aXRoIGEgdGltZS10by1ldmVudCBvdXRjb21lLg0KDQpBbmQgYXMgYSByZXN1bHQsIEkgd29uJ3QgZXZlbiBzaG93IGl0IGhlcmUuDQoNCiMjIyBBcHByb2FjaCAyLiBBIHN0cmF0aWZpZWQgQ294IHByb3BvcnRpb25hbCBoYXphcmRzIG1vZGVsDQoNClNpbmNlIHdlIGhhdmUgdGhlIG1hdGNoZWQgc2FtcGxlLCB3ZSBjYW4gdXNlIGEgc3RyYXRpZmllZCBDb3ggcHJvcG9ydGlvbmFsIGhhemFyZHMgbW9kZWwgdG8gY29tcGFyZSB0aGUgdHJlYXRtZW50IGdyb3VwcyBvbiBvdXIgdGltZS10by1ldmVudCBvdXRjb21lLCB3aGlsZSBhY2NvdW50aW5nIGZvciB0aGUgbWF0Y2hlZCBwYWlycy4gVGhlIG1haW4gcmVzdWx0cyB3aWxsIGJlIGEgcmVsYXRpdmUgaGF6YXJkIHJhdGUgZXN0aW1hdGUsIHdpdGggOTUlIENJLiBBZ2FpbiwgSSB1c2UgdGhlIDAvMSBudW1lcmljIHZlcnNpb24gb2YgdGhlIGV2ZW50IGluZGljYXRvciAoYG91dDJgKSwgYW5kIG9mIHRoZSB0cmVhdG1lbnQgaW5kaWNhdG9yIChgdHJlYXRlZGApIGhlcmUuDQoNCmBgYHtyfQ0KYWRqLm0ub3V0MyA8LSBjb3hwaChTdXJ2KG91dDMudGltZSwgb3V0MikgfiB0cmVhdGVkICsgc3RyYXRhKG1hdGNoZXMpLA0KICAgICAgICAgICAgICAgICAgICBkYXRhPXRveS5tYXRjaGVkc2FtcGxlKQ0Kc3VtbWFyeShhZGoubS5vdXQzKQ0KYGBgDQoNCkkgdGlkaWVkIHRoaXMgd2l0aCAuLi4NCg0KYGBge3J9DQphZGoubS5vdXQzX3RpZHkgPC0gdGlkeShhZGoubS5vdXQzLCBleHBvbmVudGlhdGUgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgY29uZi5pbnQgPSBUUlVFKQ0KDQphZGoubS5vdXQzX3RpZHkNCmBgYA0KDQpPdXIgcG9pbnQgZXN0aW1hdGUgZm9yIHRoZSByZWxhdGl2ZSBoYXphcmQgcmF0ZSAoZnJvbSB0aGUgYGV4cChjb2VmKWAgc2VjdGlvbiBvZiB0aGUgc3VtbWFyeSBvdXRwdXQpIGlzIGByIGRlY2ltKGFkai5tLm91dDNfdGlkeSRlc3RpbWF0ZSwgMilgLCB3aXRoIHN0YW5kYXJkIGVycm9yIGByIGRlY2ltKGFkai5tLm91dDNfdGlkeSRzdGQuZXJyb3IsIDIpYCwgYW5kIDk1JSBDSSByYW5naW5nIGZyb20gYHIgZGVjaW0oYWRqLm0ub3V0M190aWR5JGNvbmYubG93LCAyKWAgdG8gYHIgZGVjaW0oYWRqLm0ub3V0M190aWR5JGNvbmYuaGlnaCwgMilgLg0KDQpDaGVja2luZyB0aGUgcHJvcG9ydGlvbmFsIGhhemFyZHMgYXNzdW1wdGlvbiBsb29rcyBhbGwgcmlnaHQuDQoNCmBgYHtyfQ0KY294LnpwaChhZGoubS5vdXQzKSAjIFF1aWNrIGNoZWNrIGZvciBwcm9wb3J0aW9uYWwgaGF6YXJkcyBhc3N1bXB0aW9uDQpwbG90KGNveC56cGgoYWRqLm0ub3V0MyksIHZhcj0idHJlYXRlZCIpDQpgYGANCg0KIyMgUmVzdWx0cyBTbyBGYXIgKEFmdGVyIFByb3BlbnNpdHkgTWF0Y2hpbmcpDQoNClNvLCBoZXJlJ3Mgb3VyIHN1bW1hcnkgYWdhaW4sIG5vdyBpbmNvcnBvcmF0aW5nIGJvdGggb3VyIHVuYWRqdXN0ZWQgcmVzdWx0cyBhbmQgdGhlIHJlc3VsdHMgYWZ0ZXIgbWF0Y2hpbmcuIEF1dG9tYXRlZCByZXN1bHRzIGFuZCBteSBmYXZvcml0ZSBvZiBvdXIgdmFyaW91cyBub24tYXV0b21hdGVkIGFwcHJvYWNoZXMgYXJlIHNob3duLiBOb3RlIHRoYXQgSSd2ZSBsZWZ0IG91dCB0aGUgImF1dG9tYXRlZCIgYXBwcm9hY2ggZm9yIGEgdGltZS10by1ldmVudCBvdXRjb21lIGVudGlyZWx5LCBzbyBhcyB0byBkaXNjb3VyYWdlIHlvdSBmcm9tIHVzaW5nIGl0LiANCg0KRXN0LiBUcmVhdG1lbnQgRWZmZWN0ICg5NSUgQ0kpIHwgT3V0Y29tZSAxIChDb3N0IGRpZmYuKSB8IE91dGNvbWUgMiAoUmlzayBkaWZmLikgfCBPdXRjb21lIDIgKE9kZHMgUmF0aW8pIHwgT3V0Y29tZSAzIChSZWxhdGl2ZSBIYXphcmQgUmF0ZSkNCi0tLS0tLS0tLS0tLS0tLS06IHwgLS0tLS0tLS0tLS06IHwgLS0tLS0tLS0tLS06IHwgLS0tLS0tLS0tLS06IHwgLS0tLS0tLS0tLS06IA0KTm8gY292YXJpYXRlIGFkanVzdG1lbnQgfCAqKmByIGRlY2ltKHJlc191bmFkal8xJGVzdGltYXRlLDIpYCoqIHwgKipgciBkZWNpbShyZXNfdW5hZGpfMl9yaXNrZGlmZiRyaXNrLmRpZmYsIDMpYCoqIHwgKipgciBkZWNpbShyZXNfdW5hZGpfMl9vciRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKHJlc191bmFkal8zJGVzdGltYXRlLCAyKWAqKiANCih1bmFkanVzdGVkKSB8IChgciBkZWNpbShyZXNfdW5hZGpfMSRjb25mLmxvdywyKWAsIGByIGRlY2ltKHJlc191bmFkal8xJGNvbmYuaGlnaCwyKWApIHwgKGByIGRlY2ltKHJlc191bmFkal8yX3Jpc2tkaWZmJGNvbmYubG93LCAzKWAsIGByIGRlY2ltKHJlc191bmFkal8yX3Jpc2tkaWZmJGNvbmYuaGlnaCwgMylgKSB8IChgciBkZWNpbShyZXNfdW5hZGpfMl9vciRjb25mLmxvdywgMilgLCBgciBkZWNpbShyZXNfdW5hZGpfMl9vciRjb25mLmhpZ2gsIDIpYCkgfCAoYHIgZGVjaW0ocmVzX3VuYWRqXzMkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0ocmVzX3VuYWRqXzMkY29uZi5oaWdoLCAyKWApDQpBZnRlciAxOjEgUFMgTWF0Y2ggfCAqKmByIGRlY2ltKG1hdGNoMS5vdXQxJGVzdC5ub2FkaiwgMilgKiogfCAqKmByIGRlY2ltKG1hdGNoMV9vdXQyJGVzdC5ub2FkaiwgMylgKiogfCBOL0EgfCBOL0EgDQooYE1hdGNoYDogQXV0b21hdGVkKSB8IChgciBkZWNpbShtYXRjaDEub3V0MSRlc3Qubm9hZGogLSAxLjk2Km1hdGNoMS5vdXQxJHNlLnN0YW5kYXJkLCAyKWAsIGByIGRlY2ltKG1hdGNoMS5vdXQxJGVzdC5ub2FkaiArIDEuOTYqbWF0Y2gxLm91dDEkc2Uuc3RhbmRhcmQsIDIpYCkgfCAoYHIgZGVjaW0obWF0Y2gxX291dDIkZXN0Lm5vYWRqIC0gMS45NiptYXRjaDFfb3V0MiRzZS5zdGFuZGFyZCwgMylgLCBgciBkZWNpbShtYXRjaDFfb3V0MiRlc3Qubm9hZGogKyAxLjk2Km1hdGNoMV9vdXQyJHNlLnN0YW5kYXJkLCAzKWApIHwgTi9BIHwgTi9BDQpBZnRlciAxOjEgUFMgTWF0Y2ggfCAqKmByIGRlY2ltKHJlc19tYXRjaGVkXzEkZXN0aW1hdGUsIDIpYCoqIHwgTi9BIHwgKipgciBkZWNpbShhZGoubS5vdXQyX3RpZHkkZXN0aW1hdGUsIDIpYCoqIHwgKipgciBkZWNpbShhZGoubS5vdXQzX3RpZHkkZXN0aW1hdGUsIDIpYCoqDQooIlJlZ3Jlc3Npb24iIE1vZGVscykgfCAoYHIgZGVjaW0ocmVzX21hdGNoZWRfMSRjb25mLmxvdywgMilgLCBgciBkZWNpbShyZXNfbWF0Y2hlZF8xJGNvbmYuaGlnaCwgMilgKSB8IE4vQSB8IChgciBkZWNpbShhZGoubS5vdXQyX3RpZHkkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oYWRqLm0ub3V0Ml90aWR5JGNvbmYuaGlnaCwgMilgKSB8IChgciBkZWNpbShhZGoubS5vdXQzX3RpZHkkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oYWRqLm0ub3V0M190aWR5JGNvbmYuaGlnaCwgMilgKQ0KDQojIFRhc2sgNi4gU3ViY2xhc3NpZnkgYnkgUFMgcXVpbnRpbGUsIHRoZW4gZGlzcGxheSBwb3N0LXN1YmNsYXNzaWZpY2F0aW9uIGJhbGFuY2UNCg0KRmlyc3QsIHdlIGRpdmlkZSB0aGUgZGF0YSBieSB0aGUgcHJvcGVuc2l0eSBzY29yZSBpbnRvIDUgc3RyYXRhIG9mIGVxdWFsIHNpemUgdXNpbmcgdGhlIGBjdXQyYCBmdW5jdGlvbiBmcm9tIHRoZSBgSG1pc2NgIHBhY2thZ2UuIFRoZW4gd2UgY3JlYXRlIGEgYHF1aW50aWxlYCB2YXJpYWJsZSB3aGljaCBzcGVjaWZpZXMgMSA9IGxvd2VzdCBwcm9wZW5zaXR5IHNjb3JlcyB0byA1ID0gaGlnaGVzdC4NCg0KYGBge3J9DQp0b3kkc3RyYXR1bSA8LSBIbWlzYzo6Y3V0Mih0b3kkcHMsIGc9NSkNCg0KdG95ICU+JSBncm91cF9ieShzdHJhdHVtKSAlPiUgc2tpbV93aXRob3V0X2NoYXJ0cyhwcykgIyMgc2FuaXR5IGNoZWNrDQpgYGANCg0KYGBge3J9DQp0b3kkcXVpbnRpbGUgPC0gZmFjdG9yKHRveSRzdHJhdHVtLCBsYWJlbHM9MTo1KQ0KDQp0b3kgJT4lIGNvdW50KHN0cmF0dW0sIHF1aW50aWxlKSAjIyBzYW5pdHkgY2hlY2sNCmBgYA0KDQojIyBDaGVjayBCYWxhbmNlIGFuZCBQcm9wZW5zaXR5IFNjb3JlIE92ZXJsYXAgaW4gRWFjaCBRdWludGlsZQ0KDQpXZSB3YW50IHRvIGNoZWNrIHRoZSBiYWxhbmNlIGFuZCBwcm9wZW5zaXR5IHNjb3JlIG92ZXJsYXAgZm9yIGVhY2ggc3RyYXR1bSAocXVpbnRpbGUuKSBJJ2xsIHN0YXJ0IHdpdGggYSBzZXQgb2YgZmFjZXRlZCwgaml0dGVyZWQgcGxvdHMgdG8gbG9vayBhdCBvdmVybGFwLg0KDQpgYGB7cn0NCmdncGxvdCh0b3ksIGFlcyh4ID0gdHJlYXRlZF9mLCB5ID0gcm91bmQocHMsMiksIGdyb3VwID0gcXVpbnRpbGUsIGNvbG9yID0gdHJlYXRlZF9mKSkgKw0KICAgIGdlb21faml0dGVyKHdpZHRoID0gMC4yKSArDQogICAgZ3VpZGVzKGNvbG9yID0gRkFMU0UpICsNCiAgICBmYWNldF93cmFwKH4gcXVpbnRpbGUpICsNCiAgICBsYWJzKHggPSAiIiwgeSA9ICJQcm9wZW5zaXR5IGZvciBUcmVhdG1lbnQiLCANCiAgICAgICAgIHRpdGxlID0gIlF1aW50aWxlIFN1YmNsYXNzaWZpY2F0aW9uIGluIHRoZSBUb3kgRXhhbXBsZSIpDQpgYGANCg0KSXQgY2FuIGJlIGhlbHBmdWwgdG8ga25vdyBob3cgbWFueSBvYnNlcnZhdGlvbnMgKGJ5IGV4cG9zdXJlIGdyb3VwKSBhcmUgaW4gZWFjaCBxdWludGlsZS4NCg0KYGBge3J9DQp0b3kgJT4lIGNvdW50KHF1aW50aWxlLCB0cmVhdGVkX2YpDQpgYGANCg0KV2l0aCBvbmx5IDQgInRyZWF0ZWQiIHN1YmplY3RzIGluIFF1aW50aWxlIDEsIEkgYW0gY29uY2VybmVkIHRoYXQgd2Ugd29uJ3QgYmUgYWJsZSB0byBkbyBtdWNoIHRoZXJlIHRvIGNyZWF0ZSBiYWxhbmNlLg0KDQpUaGUgb3ZlcmxhcCBtYXkgc2hvdyBhIGxpdHRsZSBiZXR0ZXIgaW4gdGhlIHBsb3QgaWYgeW91IGZyZWUgdXAgdGhlIHkgYXhlcy4uLg0KDQpgYGB7cn0NCmdncGxvdCh0b3ksIGFlcyh4ID0gdHJlYXRlZF9mLCB5ID0gcm91bmQocHMsMiksIGdyb3VwID0gcXVpbnRpbGUsIGNvbG9yID0gdHJlYXRlZF9mKSkgKw0KICAgIGdlb21faml0dGVyKHdpZHRoID0gMC4yKSArDQogICAgZ3VpZGVzKGNvbG9yID0gRkFMU0UpICsNCiAgICBmYWNldF93cmFwKH4gcXVpbnRpbGUsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogICAgbGFicyh4ID0gIiIsIHkgPSAiUHJvcGVuc2l0eSBmb3IgVHJlYXRtZW50IiwgDQogICAgICAgICB0aXRsZSA9ICJRdWludGlsZSBTdWJjbGFzc2lmaWNhdGlvbiBpbiB0aGUgVG95IEV4YW1wbGUiKQ0KYGBgDQoNCiMjIENyZWF0aW5nIGEgU3RhbmRhcmRpemVkIERpZmZlcmVuY2UgQ2FsY3VsYXRpb24gRnVuY3Rpb24NCg0KV2UnbGwgbmVlZCB0byBiZSBhYmxlIHRvIGNhbGN1bGF0ZSBzdGFuZGFyZGl6ZWQgZGlmZmVyZW5jZXMgaW4gdGhpcyBzaXR1YXRpb24gc28gSSd2ZSBjcmVhdGVkIGEgc2ltcGxlIGBzemRgIGZ1bmN0aW9uIHRvIGRvIHRoaXMgLSB1c2luZyB0aGUgYXZlcmFnZSBkZW5vbWluYXRvciBtZXRob2QuDQoNCmBgYHtyfQ0Kc3pkIDwtIGZ1bmN0aW9uKGNvdmxpc3QsIGcpIHsNCiAgY292bGlzdDIgPC0gYXMubWF0cml4KGNvdmxpc3QpDQogIGcgPC0gYXMuZmFjdG9yKGcpDQogIHJlcyA8LSBOQQ0KICBmb3IoaSBpbiAxOm5jb2woY292bGlzdDIpKSB7DQogICAgY292IDwtIGFzLm51bWVyaWMoY292bGlzdDJbLGldKQ0KICAgIG51bSA8LSAxMDAqZGlmZih0YXBwbHkoY292LCBnLCBtZWFuLCBuYS5ybT1UUlVFKSkNCiAgICBkZW4gPC0gc3FydChtZWFuKHRhcHBseShjb3YsIGcsIHZhciwgbmEucm09VFJVRSkpKQ0KICAgIHJlc1tpXSA8LSByb3VuZChudW0vZGVuLDIpDQogIH0NCiAgbmFtZXMocmVzKSA8LSBuYW1lcyhjb3ZsaXN0KSAgIA0KICByZXMNCn0NCmBgYA0KDQoNCiMjIENyZWF0aW5nIHRoZSBGaXZlIFN1YnNhbXBsZXMsIGJ5IFBTIFF1aW50aWxlDQoNCk5leHQsIHdlIHNwbGl0IHRoZSBjb21wbGV0ZSBzYW1wbGUgaW50byB0aGUgZml2ZSBxdWludGlsZXMuDQoNCmBgYHtyfQ0KIyMgRGl2aWRlIHRoZSBzYW1wbGUgaW50byB0aGUgZml2ZSBxdWludGlsZXMNCnF1aW4xIDwtIGZpbHRlcih0b3ksIHF1aW50aWxlPT0xKQ0KcXVpbjIgPC0gZmlsdGVyKHRveSwgcXVpbnRpbGU9PTIpDQpxdWluMyA8LSBmaWx0ZXIodG95LCBxdWludGlsZT09MykNCnF1aW40IDwtIGZpbHRlcih0b3ksIHF1aW50aWxlPT00KQ0KcXVpbjUgPC0gZmlsdGVyKHRveSwgcXVpbnRpbGU9PTUpDQpgYGANCg0KIyMgU3RhbmRhcmRpemVkIERpZmZlcmVuY2VzIGluIEVhY2ggUXVpbnRpbGUsIGFuZCBPdmVyYWxsDQoNCk5vdywgd2UnbGwgY2FsY3VsYXRlIHRoZSBzdGFuZGFyZGl6ZWQgZGlmZmVyZW5jZXMgZm9yIGVhY2ggY292YXJpYXRlIChub3RlIHRoYXQgd2UncmUgcGlja2luZyB1cCB0d28gb2YgdGhlIGluZGljYXRvcnMgZm9yIG91ciBtdWx0aS1jYXRlZ29yaWNhbCBgY292RmApIHdpdGhpbiBlYWNoIHF1aW50aWxlLCBhcyB3ZWxsIGFzIG92ZXJhbGwuDQoNCmBgYHtyfQ0KY292cyA8LSBjKCJjb3ZBIiwgImNvdkIiLCAiY292QyIsICJjb3ZEIiwgImNvdkUiLCAiY292Ri5NaWRkbGUiLCANCiAgICAgICAgICAiY292Ri5IaWdoIiwgIkFzcXIiLCJCQyIsICJCRCIsICJwcyIsICJsaW5wcyIpDQpkLnExIDwtIHN6ZChxdWluMVtjb3ZzXSwgcXVpbjEkdHJlYXRlZCkNCmQucTIgPC0gc3pkKHF1aW4yW2NvdnNdLCBxdWluMiR0cmVhdGVkKQ0KZC5xMyA8LSBzemQocXVpbjNbY292c10sIHF1aW4zJHRyZWF0ZWQpDQpkLnE0IDwtIHN6ZChxdWluNFtjb3ZzXSwgcXVpbjQkdHJlYXRlZCkNCmQucTUgPC0gc3pkKHF1aW41W2NvdnNdLCBxdWluNSR0cmVhdGVkKQ0KZC5hbGwgPC0gc3pkKHRveVtjb3ZzXSwgdG95JHRyZWF0ZWQpDQoNCnRveS5zemQgPC0gdGliYmxlKGNvdnMsIE92ZXJhbGwgPSBkLmFsbCwgUTEgPSBkLnExLCBRMiA9IGQucTIsIFEzID0gZC5xMywgUTQgPSBkLnE0LCBRNSA9IGQucTUpDQp0b3kuc3pkIDwtIGdhdGhlcih0b3kuc3pkLCAicXVpbnQiLCAic3ouZGlmZiIsIDI6NykNCnRveS5zemQNCmBgYA0KDQojIyBQbG90dGluZyB0aGUgU3RhbmRhcmRpemVkIERpZmZlcmVuY2VzDQoNCmBgYHtyfQ0KZ2dwbG90KHRveS5zemQsIGFlcyh4ID0gc3ouZGlmZiwgeSA9IHJlb3JkZXIoY292cywgLXN6LmRpZmYpLCBncm91cCA9IHF1aW50KSkgKyANCiAgICBnZW9tX3BvaW50KCkgKw0KICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDApICsNCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKC0xMCwxMCksIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbCA9ICJibHVlIikgKw0KICAgIGZhY2V0X3dyYXAofiBxdWludCkgKw0KICAgIGxhYnMoeCA9ICJTdGFuZGFyZGl6ZWQgRGlmZmVyZW5jZSwgJSIsIHkgPSAiIiwNCiAgICAgICAgIHRpdGxlID0gIkNvbXBhcmluZyBTdGFuZGFyZGl6ZWQgRGlmZmVyZW5jZXMgYnkgUFMgUXVpbnRpbGUiLA0KICAgICAgICAgc3VidGl0bGUgPSAiVGhlIHRveSBleGFtcGxlIikNCmBgYA0KDQoNCmBgYHtyfQ0KZ2dwbG90KHRveS5zemQsIGFlcyh4ID0gYWJzKHN6LmRpZmYpLCB5ID0gY292cywgZ3JvdXAgPSBxdWludCkpICsgDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMTAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbCA9ICJibHVlIikgKw0KICAgIGZhY2V0X3dyYXAofiBxdWludCkgKw0KICAgIGxhYnMoeCA9ICJ8U3RhbmRhcmRpemVkIERpZmZlcmVuY2V8LCAlIiwgeSA9ICIiLA0KICAgICAgICAgdGl0bGUgPSAiQWJzb2x1dGUgU3RhbmRhcmRpemVkIERpZmZlcmVuY2VzIGJ5IFBTIFF1aW50aWxlIiwNCiAgICAgICAgIHN1YnRpdGxlID0gIlRoZSB0b3kgZXhhbXBsZSIpDQpgYGANCg0KIyMgQ2hlY2tpbmcgUnViaW4ncyBSdWxlcyBQb3N0LVN1YmNsYXNzaWZpY2F0aW9uDQoNCiMjIyBSdWJpbidzIFJ1bGUgMQ0KDQpBcyBhIHJlbWluZGVyLCBwcmlvciB0byBhZGp1c3RtZW50LCBSdWJpbidzIFJ1bGUgMSBmb3IgdGhlIGB0b3lgIGV4YW1wbGUgd2FzOg0KDQpgYGB7cn0NCnJ1YmluMS51bmFkaiA8LSB3aXRoKHRveSwNCiAgICAgICAgICAgICAgICAgICAgIGFicygxMDAqKG1lYW4obGlucHNbdHJlYXRlZD09MV0pIC0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWFuKGxpbnBzW3RyZWF0ZWQ9PTBdKSkvc2QobGlucHMpKSkNCnJ1YmluMS51bmFkag0KYGBgDQoNCkFmdGVyIHByb3BlbnNpdHkgc2NvcmUgc3ViY2xhc3NpZmljYXRpb24sIHdlIGNhbiBvYnRhaW4gdGhlIHNhbWUgc3VtbWFyeSB3aXRoaW4gZWFjaCBvZiB0aGUgZml2ZSBxdWludGlsZXMuLi4NCg0KYGBge3J9DQpydWJpbjEucTEgPC0gd2l0aChxdWluMSwgYWJzKDEwMCoobWVhbihsaW5wc1t0cmVhdGVkPT0xXSkgLSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbihsaW5wc1t0cmVhdGVkPT0wXSkpL3NkKGxpbnBzKSkpDQpydWJpbjEucTIgPC0gd2l0aChxdWluMiwgYWJzKDEwMCoobWVhbihsaW5wc1t0cmVhdGVkPT0xXSkgLSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbihsaW5wc1t0cmVhdGVkPT0wXSkpL3NkKGxpbnBzKSkpDQpydWJpbjEucTMgPC0gd2l0aChxdWluMywgYWJzKDEwMCoobWVhbihsaW5wc1t0cmVhdGVkPT0xXSkgLSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbihsaW5wc1t0cmVhdGVkPT0wXSkpL3NkKGxpbnBzKSkpDQpydWJpbjEucTQgPC0gd2l0aChxdWluNCwgYWJzKDEwMCoobWVhbihsaW5wc1t0cmVhdGVkPT0xXSkgLSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbihsaW5wc1t0cmVhdGVkPT0wXSkpL3NkKGxpbnBzKSkpDQpydWJpbjEucTUgPC0gd2l0aChxdWluNSwgYWJzKDEwMCoobWVhbihsaW5wc1t0cmVhdGVkPT0xXSkgLSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbihsaW5wc1t0cmVhdGVkPT0wXSkpL3NkKGxpbnBzKSkpDQoNCnJ1YmluMS5zdWIgPC0gYyhydWJpbjEucTEsIHJ1YmluMS5xMiwgcnViaW4xLnEzLCBydWJpbjEucTQsIHJ1YmluMS5xNSkNCm5hbWVzKHJ1YmluMS5zdWIpPWMoIlExIiwgIlEyIiwgIlEzIiwgIlE0IiwgIlE1IikNCg0KcnViaW4xLnN1Yg0KYGBgDQoNCkl0IHdhcyBhbHdheXMgYSBsb25nIHNob3QgdGhhdCBzdWJjbGFzc2lmaWNhdGlvbiBhbG9uZSB3b3VsZCByZWR1Y2UgYWxsIG9mIHRoZXNlIHZhbHVlcyBiZWxvdyAxMCUsIGJ1dCBJIGhhZCBob3BlZCB0byBnZXQgdGhlbSBhbGwgYmVsb3cgNTAlLiBXaXRoIG9ubHkgNCAidHJlYXRlZCIgc3ViamVjdHMgaW4gUXVpbnRpbGUgMSwgdGhvdWdoLCB0aGUgdGFzayB3YXMgdG9vIHRvdWdoLg0KDQojIyMgUnViaW4ncyBSdWxlIDINCg0KQXMgYSByZW1pbmRlciwgcHJpb3IgdG8gYWRqdXN0bWVudCwgUnViaW4ncyBSdWxlIDIgZm9yIHRoZSBgdG95YCBleGFtcGxlIHdhczoNCg0KYGBge3J9DQpydWJpbjIudW5hZGogPC0gd2l0aCh0b3ksIHZhcihsaW5wc1t0cmVhdGVkPT0xXSkvdmFyKGxpbnBzW3RyZWF0ZWQ9PTBdKSkNCnJ1YmluMi51bmFkag0KYGBgDQoNCkFmdGVyIFN1YmNsYXNzaWZpY2F0aW9uLCB3ZSBjYW4gb2J0YWluIHRoZSBzYW1lIHN1bW1hcnkgd2l0aGluIGVhY2ggb2YgdGhlIGZpdmUgcXVpbnRpbGVzLi4uDQoNCmBgYHtyfQ0KcnViaW4yLnExIDwtIHdpdGgocXVpbjEsIHZhcihsaW5wc1t0cmVhdGVkPT0xXSkvdmFyKGxpbnBzW3RyZWF0ZWQ9PTBdKSkNCnJ1YmluMi5xMiA8LSB3aXRoKHF1aW4yLCB2YXIobGlucHNbdHJlYXRlZD09MV0pL3ZhcihsaW5wc1t0cmVhdGVkPT0wXSkpDQpydWJpbjIucTMgPC0gd2l0aChxdWluMywgdmFyKGxpbnBzW3RyZWF0ZWQ9PTFdKS92YXIobGlucHNbdHJlYXRlZD09MF0pKQ0KcnViaW4yLnE0IDwtIHdpdGgocXVpbjQsIHZhcihsaW5wc1t0cmVhdGVkPT0xXSkvdmFyKGxpbnBzW3RyZWF0ZWQ9PTBdKSkNCnJ1YmluMi5xNSA8LSB3aXRoKHF1aW41LCB2YXIobGlucHNbdHJlYXRlZD09MV0pL3ZhcihsaW5wc1t0cmVhdGVkPT0wXSkpDQoNCnJ1YmluMi5zdWIgPC0gYyhydWJpbjIucTEsIHJ1YmluMi5xMiwgcnViaW4yLnEzLCBydWJpbjIucTQsIHJ1YmluMi5xNSkNCm5hbWVzKHJ1YmluMi5zdWIpPWMoIlExIiwgIlEyIiwgIlEzIiwgIlE0IiwgIlE1IikNCg0KcnViaW4yLnN1Yg0KYGBgDQoNClNvbWUgb2YgdGhlc2UgdmFyaWFuY2UgcmF0aW9zIGFyZSBhY3R1YWxseSBhIGJpdCBmdXJ0aGVyIGZyb20gMSB0aGFuIHRoZSBmdWxsIGRhdGEgc2V0LiBBZ2Fpbiwgd2l0aCBhIHNtYWxsIHNhbXBsZSBzaXplIGxpa2UgdGhpcywgc3ViY2xhc3NpZmljYXRpb24gbG9va3MgbGlrZSBhIHdlYWsgY2hvaWNlLiBBdCBtb3N0LCB0aHJlZSBvZiB0aGUgcXVpbnRpbGVzICgzLTQgYW5kIG1heWJlIDUpIHNob3cgT0sgdmFyaWFuY2UgcmF0aW9zIGFmdGVyIHByb3BlbnNpdHkgc2NvcmUgc3ViY2xhc3NpZmljYXRpb24uDQoNCiMjIyBSdWJpbidzIFJ1bGUgMw0KDQpQcmlvciB0byBwcm9wZW5zaXR5IGFkanVzdG1lbnQsIHJlY2FsbCB0aGF0IFJ1YmluJ3MgUnVsZSAzIHN1bW1hcmllcyB3ZXJlOg0KDQpgYGB7cn0NCmNvdnMgPC0gYygiY292QSIsICJjb3ZCIiwgImNvdkMiLCAiY292RCIsICJjb3ZFIiwgDQogICAgICAgICAgImNvdkYuTWlkZGxlIiwgImNvdkYuSGlnaCIsICJBc3FyIiwiQkMiLCAiQkQiKQ0KcnViaW4zLnVuYWRqIDwtIHJ1YmluMyhkYXRhPXRveSwgY292bGlzdD10b3lbY292c10pDQpgYGANCg0KQWZ0ZXIgc3ViY2xhc3NpZmljYXRpb24sIHRoZW4sIFJ1YmluJ3MgUnVsZSAzIHN1bW1hcmllcyB3aXRoaW4gZWFjaCBxdWludGlsZSBhcmU6DQoNCmBgYHtyfQ0KcnViaW4zLnExIDwtIHJ1YmluMyhkYXRhPXF1aW4xLCBjb3ZsaXN0PXF1aW4xW2NvdnNdKQ0KcnViaW4zLnEyIDwtIHJ1YmluMyhkYXRhPXF1aW4yLCBjb3ZsaXN0PXF1aW4yW2NvdnNdKQ0KcnViaW4zLnEzIDwtIHJ1YmluMyhkYXRhPXF1aW4zLCBjb3ZsaXN0PXF1aW4zW2NvdnNdKQ0KcnViaW4zLnE0IDwtIHJ1YmluMyhkYXRhPXF1aW40LCBjb3ZsaXN0PXF1aW40W2NvdnNdKQ0KcnViaW4zLnE1IDwtIHJ1YmluMyhkYXRhPXF1aW41LCBjb3ZsaXN0PXF1aW41W2NvdnNdKQ0KDQp0b3kucnViaW4zIDwtIHRpYmJsZShjb3ZzLCBBbGwgPSBydWJpbjMudW5hZGokcmVzaWQudmFyLnJhdGlvLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBRMSA9IHJ1YmluMy5xMSRyZXNpZC52YXIucmF0aW8sIA0KICAgICAgICAgICAgICAgICAgICAgICAgIFEyID0gcnViaW4zLnEyJHJlc2lkLnZhci5yYXRpbywgDQogICAgICAgICAgICAgICAgICAgICAgICAgUTMgPSBydWJpbjMucTMkcmVzaWQudmFyLnJhdGlvLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBRNCA9IHJ1YmluMy5xNCRyZXNpZC52YXIucmF0aW8sIA0KICAgICAgICAgICAgICAgICAgICAgICAgIFE1ID0gcnViaW4zLnE1JHJlc2lkLnZhci5yYXRpbykNCg0KdG95LnJ1YmluMyA8LSBnYXRoZXIodG95LnJ1YmluMywgInF1aW50IiwgInJ1YmluMyIsIDI6NykNCmBgYA0KDQoNCmBgYHtyfQ0KZ2dwbG90KHRveS5ydWJpbjMsIGFlcyh4ID0gcnViaW4zLCB5ID0gY292cywgZ3JvdXAgPSBxdWludCkpICsgDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAxKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygwLjgsIDEuMjUpLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2wgPSAiYmx1ZSIpICsNCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKDAuNSwgMiksIGNvbCA9ICJyZWQiKSArDQogICAgZmFjZXRfd3JhcCh+IHF1aW50KSArDQogICAgbGFicyh4ID0gIlJlc2lkdWFsIFZhcmlhbmNlIFJhdGlvIiwgeSA9ICIiLA0KICAgICAgICAgdGl0bGUgPSAiUmVzaWR1YWwgVmFyaWFuY2UgUmF0aW9zIGJ5IFBTIFF1aW50aWxlIiwNCiAgICAgICAgIHN1YnRpdGxlID0gIlJ1YmluJ3MgUnVsZSAzOiBUaGUgdG95IGV4YW1wbGUiKQ0KYGBgDQoNCk1vc3Qgb2YgdGhlIHJlc2lkdWFsIHZhcmlhbmNlIHJhdGlvcyBhcmUgaW4gdGhlIHJhbmdlIG9mICgwLjUsIDIpIGluIHF1aW50aWxlcyAyLTUsIHdpdGggdGhlIGV4Y2VwdGlvbiBvZiB0aGUgYGNvdkYuaGlnaGAgaW5kaWNhdG9yIGluIFF1aW50aWxlIDIuIFF1aW50aWxlIDEgaXMgY2VydGFpbmx5IHByb2JsZW1hdGljIGluIHRoaXMgcmVnYXJkLg0KDQojIFRhc2sgNy4gQWZ0ZXIgc3ViY2xhc3NpZnlpbmcsIHdoYXQgaXMgdGhlIGVzdGltYXRlZCBhdmVyYWdlIHRyZWF0bWVudCBlZmZlY3Q/DQoNCiMjIC4uLiBvbiBPdXRjb21lIDEgW2EgY29udGludW91cyBvdXRjb21lXQ0KDQpGaXJzdCwgd2UnbGwgZmluZCB0aGUgZXN0aW1hdGVkIGF2ZXJhZ2UgY2F1c2FsIGVmZmVjdCAoYW5kIHN0YW5kYXJkIGVycm9yKSB3aXRoaW4gZWFjaCBxdWludGlsZSB2aWEgbGluZWFyIHJlZ3Jlc3Npb24uDQoNCmBgYHtyfQ0KcXVpbjEub3V0MSA8LSBsbShvdXQxLmNvc3QgfiB0cmVhdGVkLCBkYXRhPXF1aW4xKQ0KcXVpbjIub3V0MSA8LSBsbShvdXQxLmNvc3QgfiB0cmVhdGVkLCBkYXRhPXF1aW4yKQ0KcXVpbjMub3V0MSA8LSBsbShvdXQxLmNvc3QgfiB0cmVhdGVkLCBkYXRhPXF1aW4zKQ0KcXVpbjQub3V0MSA8LSBsbShvdXQxLmNvc3QgfiB0cmVhdGVkLCBkYXRhPXF1aW40KQ0KcXVpbjUub3V0MSA8LSBsbShvdXQxLmNvc3QgfiB0cmVhdGVkLCBkYXRhPXF1aW41KQ0KDQpjb2VmKHN1bW1hcnkocXVpbjEub3V0MSkpOyBjb2VmKHN1bW1hcnkocXVpbjIub3V0MSkpOyBjb2VmKHN1bW1hcnkocXVpbjMub3V0MSkpOyBjb2VmKHN1bW1hcnkocXVpbjQub3V0MSkpOyBjb2VmKHN1bW1hcnkocXVpbjUub3V0MSkpDQpgYGANCg0KSnVzdCBsb29raW5nIGF0IHRoZXNlIHJlc3VsdHMsIGl0IGRvZXNuJ3QgbG9vayBsaWtlIGNvbWJpbmluZyBxdWludGlsZSAxIHdpdGggdGhlIG90aGVycyBpcyBhIGdvb2QgaWRlYS4gSSdsbCBkbyBpdCBoZXJlLCB0byBzaG93IHRoZSBnZW5lcmFsIGlkZWEsIGJ1dCBJJ20gbm90IHNhdGlzZmllZCB3aXRoIHRoZSByZXN1bHRzLiBUaGVyZSBpcyBjZXJ0YWlubHkgYSBjbGV2ZXJlciB3YXkgdG8gYWNjb21wbGlzaCB0aGlzIHVzaW5nIHRoZSBgYnJvb21gIHBhY2thZ2UsIG9yIG1heWJlIGEgbGl0dGxlIHByb2dyYW1taW5nIHdpdGggYHB1cnJyYC4NCg0KTmV4dCwgd2UgZmluZCB0aGUgbWVhbiBvZiB0aGUgZml2ZSBxdWludGlsZS1zcGVjaWZpYyBlc3RpbWF0ZWQgcmVncmVzc2lvbiBjb2VmZmljaWVudHMNCg0KYGBge3J9DQplc3Quc3QgPC0gKGNvZWYocXVpbjEub3V0MSlbMl0gKyBjb2VmKHF1aW4yLm91dDEpWzJdICsgY29lZihxdWluMy5vdXQxKVsyXSArDQogICAgICAgICAgICAgICBjb2VmKHF1aW40Lm91dDEpWzJdICsgY29lZihxdWluNS5vdXQxKVsyXSkvNQ0KZXN0LnN0DQpgYGANCg0KVG8gZ2V0IHRoZSBjb21iaW5lZCBzdGFuZGFyZCBlcnJvciBlc3RpbWF0ZSwgd2UgZG8gdGhlIGZvbGxvd2luZzoNCg0KYGBge3J9DQpzZS5xMSA8LSBzdW1tYXJ5KHF1aW4xLm91dDEpJGNvZWZmaWNpZW50c1syLDJdDQpzZS5xMiA8LSBzdW1tYXJ5KHF1aW4yLm91dDEpJGNvZWZmaWNpZW50c1syLDJdDQpzZS5xMyA8LSBzdW1tYXJ5KHF1aW4zLm91dDEpJGNvZWZmaWNpZW50c1syLDJdDQpzZS5xNCA8LSBzdW1tYXJ5KHF1aW40Lm91dDEpJGNvZWZmaWNpZW50c1syLDJdDQpzZS5xNSA8LSBzdW1tYXJ5KHF1aW41Lm91dDEpJGNvZWZmaWNpZW50c1syLDJdDQoNCnNlLnN0IDwtIHNxcnQoKHNlLnExXjIgKyBzZS5xMl4yICsgc2UucTNeMiArIHNlLnE0XjIgKyBzZS5xNV4yKSooMS8yNSkpDQpzZS5zdA0KYGBgDQoNClRoZSByZXN1bHRpbmcgOTUlIGNvbmZpZGVuY2UgSW50ZXJ2YWwgZm9yIHRoZSBhdmVyYWdlIGNhdXNhbCB0cmVhdG1lbnQgZWZmZWN0IGlzIHRoZW46DQoNCmBgYHtyfQ0Kc3RyYXQucmVzdWx0MSA8LSB0aWJibGUoZXN0aW1hdGUgPSBlc3Quc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uZi5sb3cgPSBlc3Quc3QgLSAxLjk2KnNlLnN0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbmYuaGlnaCA9IGVzdC5zdCArIDEuOTYqc2Uuc3QpDQpzdHJhdC5yZXN1bHQxDQpgYGANCg0KQWdhaW4sIEkgZG9uJ3QgdHJ1c3QgdGhpcyBlc3RpbWF0ZSBpbiB0aGlzIHNldHRpbmcgYmVjYXVzZSB0aGUgYmFsYW5jZSAoZXNwZWNpYWxseSBpbiBRdWludGlsZSAxKSBpcyB0b28gd2Vhay4NCg0KIyMgLi4uIG9uIE91dGNvbWUgMiBbYSBiaW5hcnkgb3V0Y29tZV0NCg0KRmlyc3QsIHdlIGZpbmQgdGhlIGVzdGltYXRlZCBhdmVyYWdlIGNhdXNhbCBlZmZlY3QgKGFuZCBzdGFuZGFyZCBlcnJvcikgd2l0aGluIGVhY2ggcXVpbnRpbGUgdmlhIGxvZ2lzdGljIHJlZ3Jlc3Npb246DQoNCmBgYHtyfQ0KcXVpbjEub3V0MiA8LSBnbG0ob3V0MiB+IHRyZWF0ZWQsIGRhdGE9cXVpbjEsIGZhbWlseT1iaW5vbWlhbCgpKQ0KcXVpbjIub3V0MiA8LSBnbG0ob3V0MiB+IHRyZWF0ZWQsIGRhdGE9cXVpbjIsIGZhbWlseT1iaW5vbWlhbCgpKQ0KcXVpbjMub3V0MiA8LSBnbG0ob3V0MiB+IHRyZWF0ZWQsIGRhdGE9cXVpbjMsIGZhbWlseT1iaW5vbWlhbCgpKQ0KcXVpbjQub3V0MiA8LSBnbG0ob3V0MiB+IHRyZWF0ZWQsIGRhdGE9cXVpbjQsIGZhbWlseT1iaW5vbWlhbCgpKQ0KcXVpbjUub3V0MiA8LSBnbG0ob3V0MiB+IHRyZWF0ZWQsIGRhdGE9cXVpbjUsIGZhbWlseT1iaW5vbWlhbCgpKQ0KDQpjb2VmKHN1bW1hcnkocXVpbjEub3V0MikpOyBjb2VmKHN1bW1hcnkocXVpbjIub3V0MikpOyBjb2VmKHN1bW1hcnkocXVpbjMub3V0MikpOyBjb2VmKHN1bW1hcnkocXVpbjQub3V0MikpOyBjb2VmKHN1bW1hcnkocXVpbjUub3V0MikpDQpgYGANCg0KTmV4dCwgd2UgZmluZCB0aGUgbWVhbiBvZiB0aGUgZml2ZSBxdWludGlsZS1zcGVjaWZpYyBlc3RpbWF0ZWQgbG9naXN0aWMgcmVncmVzc2lvbiBjb2VmZmljaWVudHMNCg0KYGBge3J9DQplc3Quc3QgPC0gKGNvZWYocXVpbjEub3V0MilbMl0gKyBjb2VmKHF1aW4yLm91dDIpWzJdICsgY29lZihxdWluMy5vdXQyKVsyXSArDQogICAgICAgICAgICAgICBjb2VmKHF1aW40Lm91dDIpWzJdICsgY29lZihxdWluNS5vdXQyKVsyXSkvNQ0KZXN0LnN0ICMjIHRoaXMgaXMgdGhlIGVzdGltYXRlZCBsb2cgb2RkcyByYXRpbw0KDQojIyBBbmQgd2UgZXhwb25lbnRpYXRlIHRoaXMgdG8gZ2V0IHRoZSBvdmVyYWxsIG9kZHMgcmF0aW8gZXN0aW1hdGUNCmV4cChlc3Quc3QpDQpgYGANCg0KVG8gZ2V0IHRoZSBjb21iaW5lZCBzdGFuZGFyZCBlcnJvciBlc3RpbWF0ZSBhY3Jvc3MgdGhlIGZpdmUgcXVpbnRpbGVzLCB3ZSBkbyB0aGUgZm9sbG93aW5nOg0KDQpgYGB7cn0NCnNlLnExIDwtIHN1bW1hcnkocXVpbjEub3V0MikkY29lZmZpY2llbnRzWzIsMl0NCnNlLnEyIDwtIHN1bW1hcnkocXVpbjIub3V0MikkY29lZmZpY2llbnRzWzIsMl0NCnNlLnEzIDwtIHN1bW1hcnkocXVpbjMub3V0MikkY29lZmZpY2llbnRzWzIsMl0NCnNlLnE0IDwtIHN1bW1hcnkocXVpbjQub3V0MikkY29lZmZpY2llbnRzWzIsMl0NCnNlLnE1IDwtIHN1bW1hcnkocXVpbjUub3V0MikkY29lZmZpY2llbnRzWzIsMl0NCnNlLnN0IDwtIHNxcnQoKHNlLnExXjIgKyBzZS5xMl4yICsgc2UucTNeMiArIHNlLnE0XjIgKyBzZS5xNV4yKSooMS8yNSkpDQpzZS5zdA0KIyMgT2YgY291cnNlLCB0aGlzIHN0YW5kYXJkIGVycm9yIGlzIGFsc28gb24gdGhlIGxvZyBvZGRzIHJhdGlvIHNjYWxlDQpgYGANCg0KTm93LCB3ZSBvYnRhaW4gYSA5NSUgQ29uZmlkZW5jZSBJbnRlcnZhbCBmb3IgdGhlIEF2ZXJhZ2UgQ2F1c2FsIEVmZmVjdCBvZiBvdXIgdHJlYXRtZW50IChhcyBhbiBPZGRzIFJhdGlvKSB0aHJvdWdoIGNvbWJpbmF0aW9uIGFuZCBleHBvbmVudGlhdGlvbiwgYXMgZm9sbG93czoNCg0KYGBge3J9DQpzdHJhdC5yZXN1bHQyIDwtIHRpYmJsZShlc3RpbWF0ZSA9IGV4cChlc3Quc3QpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbmYubG93ID0gZXhwKGVzdC5zdCAtIDEuOTYqc2Uuc3QpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbmYuaGlnaCA9IGV4cChlc3Quc3QgKyAxLjk2KnNlLnN0KSkNCnN0cmF0LnJlc3VsdDINCmBgYA0KDQojIyAuLi4gb24gT3V0Y29tZSAzIFthIHRpbWUgdG8gZXZlbnRdDQoNClN1YmplY3RzIHdpdGggYG91dDIuZXZlbnRgID0gIlllcyIgYXJlIHRydWx5IG9ic2VydmVkIGV2ZW50cywgd2hpbGUgdGhvc2Ugd2l0aCBgb3V0Mi5ldmVudGAgPT0gIk5vIiBhcmUgY2Vuc29yZWQgYmVmb3JlIGFuIGV2ZW50IGNhbiBoYXBwZW4gdG8gdGhlbS4NCg0KVGhlIENveCBtb2RlbCBjb21wYXJpbmcgdHJlYXRlZCB0byBjb250cm9sLCBzdHJhdGlmeWluZyBvbiBxdWludGlsZSwgaXMuLi4NCg0KYGBge3J9DQphZGoucy5vdXQzIDwtIGNveHBoKFN1cnYob3V0My50aW1lLCBvdXQyKSB+IHRyZWF0ZWQgKyBzdHJhdGEocXVpbnRpbGUpLCBkYXRhPXRveSkNCnN1bW1hcnkoYWRqLnMub3V0MykgIyMgZXhwKGNvZWYpIGdpdmVzIHJlbGF0aXZlIGhhemFyZCBhc3NvY2lhdGVkIHdpdGggdHJlYXRtZW50DQoNCnN0cmF0LnJlc3VsdDMgPC0gdGlkeShhZGoucy5vdXQzLCBleHBvbmVudGlhdGUgPSBUUlVFLCBjb25mLmludCA9IFRSVUUpDQoNCnN0cmF0LnJlc3VsdDMNCmBgYA0KDQojIyMgQ2hlY2tpbmcgdGhlIFByb3BvcnRpb25hbCBIYXphcmRzIEFzc3VtcHRpb24NCg0KVGhlIHByb3BvcnRpb25hbCBoYXphcmRzIGFzc3VtcHRpb24gbWF5IGJlIHByb2JsZW1hdGljLg0KDQpgYGB7cn0NCmNveC56cGgoYWRqLnMub3V0MykgIyMgY2hlY2tpbmcgdGhlIHByb3BvcnRpb25hbCBoYXphcmRzIGFzc3VtcHRpb24NCnBsb3QoY294LnpwaChhZGoucy5vdXQzKSwgdmFyPSJ0cmVhdGVkIikNCmBgYA0KDQojIyBSZXN1bHRzIFNvIEZhciAoQWZ0ZXIgTWF0Y2hpbmcgYW5kIFN1YmNsYXNzaWZpY2F0aW9uKQ0KDQpUaGVzZSBzdWJjbGFzc2lmaWNhdGlvbiByZXN1bHRzIGRlc2NyaWJlIHRoZSBhdmVyYWdlIHRyZWF0bWVudCBlZmZlY3QsIHdoaWxlIHRoZSBwcmV2aW91cyBhbmFseXNlcyB3ZSBoYXZlIGNvbXBsZXRlZCBkZXNjcmliZSB0aGUgYXZlcmFnZSB0cmVhdG1lbnQgZWZmZWN0IG9uIHRoZSB0cmVhdGVkLiBUaGlzIGlzIG9uZSByZWFzb24gZm9yIHRoZSBtZWFuaW5nZnVsIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgZXN0aW1hdGVzLiBBbm90aGVyIHJlYXNvbiBpcyB0aGF0IHRoZSBiYWxhbmNlIG9uIG9ic2VydmVkIGNvdmFyaWF0ZXMgaXMgbXVjaCB3b3JzZSBhZnRlciBzdHJhdGlmaWNhdGlvbiBpbiBzb21lIHF1aW50aWxlcywgZXNwZWNpYWxseSBRdWludGlsZSAxLg0KDQpFc3QuIFRyZWF0bWVudCBFZmZlY3QgKDk1JSBDSSkgfCBPdXRjb21lIDEgKENvc3QgZGlmZi4pIHwgT3V0Y29tZSAyIChSaXNrIGRpZmYuKSB8IE91dGNvbWUgMiAoT2RkcyBSYXRpbykgfCBPdXRjb21lIDMgKFJlbGF0aXZlIEhhemFyZCBSYXRlKQ0KLS0tLS0tLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogDQpObyBjb3ZhcmlhdGUgYWRqdXN0bWVudCB8ICoqYHIgZGVjaW0ocmVzX3VuYWRqXzEkZXN0aW1hdGUsMilgKiogfCAqKmByIGRlY2ltKHJlc191bmFkal8yX3Jpc2tkaWZmJHJpc2suZGlmZiwgMylgKiogfCAqKmByIGRlY2ltKHJlc191bmFkal8yX29yJGVzdGltYXRlLCAyKWAqKiB8ICoqYHIgZGVjaW0ocmVzX3VuYWRqXzMkZXN0aW1hdGUsIDIpYCoqIA0KKHVuYWRqdXN0ZWQpIHwgKGByIGRlY2ltKHJlc191bmFkal8xJGNvbmYubG93LDIpYCwgYHIgZGVjaW0ocmVzX3VuYWRqXzEkY29uZi5oaWdoLDIpYCkgfCAoYHIgZGVjaW0ocmVzX3VuYWRqXzJfcmlza2RpZmYkY29uZi5sb3csIDMpYCwgYHIgZGVjaW0ocmVzX3VuYWRqXzJfcmlza2RpZmYkY29uZi5oaWdoLCAzKWApIHwgKGByIGRlY2ltKHJlc191bmFkal8yX29yJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHJlc191bmFkal8yX29yJGNvbmYuaGlnaCwgMilgKSB8IChgciBkZWNpbShyZXNfdW5hZGpfMyRjb25mLmxvdywgMilgLCBgciBkZWNpbShyZXNfdW5hZGpfMyRjb25mLmhpZ2gsIDIpYCkNCkFmdGVyIDE6MSBQUyBNYXRjaCB8ICoqYHIgZGVjaW0obWF0Y2gxLm91dDEkZXN0Lm5vYWRqLCAyKWAqKiB8ICoqYHIgZGVjaW0obWF0Y2gxX291dDIkZXN0Lm5vYWRqLCAzKWAqKiB8IE4vQSB8IE4vQSANCihgTWF0Y2hgOiBBdXRvbWF0ZWQpIHwgKGByIGRlY2ltKG1hdGNoMS5vdXQxJGVzdC5ub2FkaiAtIDEuOTYqbWF0Y2gxLm91dDEkc2Uuc3RhbmRhcmQsIDIpYCwgYHIgZGVjaW0obWF0Y2gxLm91dDEkZXN0Lm5vYWRqICsgMS45NiptYXRjaDEub3V0MSRzZS5zdGFuZGFyZCwgMilgKSB8IChgciBkZWNpbShtYXRjaDFfb3V0MiRlc3Qubm9hZGogLSAxLjk2Km1hdGNoMV9vdXQyJHNlLnN0YW5kYXJkLCAzKWAsIGByIGRlY2ltKG1hdGNoMV9vdXQyJGVzdC5ub2FkaiArIDEuOTYqbWF0Y2gxX291dDIkc2Uuc3RhbmRhcmQsIDMpYCkgfCBOL0EgfCBOL0ENCkFmdGVyIDE6MSBQUyBNYXRjaCB8ICoqYHIgZGVjaW0ocmVzX21hdGNoZWRfMSRlc3RpbWF0ZSwgMilgKiogfCBOL0EgfCAqKmByIGRlY2ltKGFkai5tLm91dDJfdGlkeSRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKGFkai5tLm91dDNfdGlkeSRlc3RpbWF0ZSwgMilgKioNCigiUmVncmVzc2lvbiIgTW9kZWxzKSB8IChgciBkZWNpbShyZXNfbWF0Y2hlZF8xJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHJlc19tYXRjaGVkXzEkY29uZi5oaWdoLCAyKWApIHwgTi9BIHwgKGByIGRlY2ltKGFkai5tLm91dDJfdGlkeSRjb25mLmxvdywgMilgLCBgciBkZWNpbShhZGoubS5vdXQyX3RpZHkkY29uZi5oaWdoLCAyKWApIHwgKGByIGRlY2ltKGFkai5tLm91dDNfdGlkeSRjb25mLmxvdywgMilgLCBgciBkZWNpbShhZGoubS5vdXQzX3RpZHkkY29uZi5oaWdoLCAyKWApDQpBZnRlciBQUyBTdWJjbGFzc2lmaWNhdGlvbiB8ICoqYHIgZGVjaW0oc3RyYXQucmVzdWx0MSRlc3RpbWF0ZSwgMilgKiogfCBOL0EgfCAqKmByIGRlY2ltKHN0cmF0LnJlc3VsdDIkZXN0aW1hdGUsIDIpYCoqIHwgKipgciBkZWNpbShzdHJhdC5yZXN1bHQzJGVzdGltYXRlLCAyKWAqKg0KKCJSZWdyZXNzaW9uIiBtb2RlbHMsIEFURSkgfCAoYHIgZGVjaW0oc3RyYXQucmVzdWx0MSRjb25mLmxvdywgMilgLCBgciBkZWNpbShzdHJhdC5yZXN1bHQxJGNvbmYuaGlnaCwgMilgKSB8IE4vQSB8IChgciBkZWNpbShzdHJhdC5yZXN1bHQyJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHN0cmF0LnJlc3VsdDEkY29uZi5oaWdoLCAyKWApIHwgKGByIGRlY2ltKHN0cmF0LnJlc3VsdDMkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oc3RyYXQucmVzdWx0MyRjb25mLmhpZ2gsIDIpYCkNCg0KIyBUYXNrIDguIEV4ZWN1dGUgd2VpZ2h0aW5nIGJ5IHRoZSBpbnZlcnNlIFBTLCB0aGVuIGFzc2VzcyBjb3ZhcmlhdGUgYmFsYW5jZQ0KDQojIyBBVFQgYXBwcm9hY2g6IFdlaWdodCB0cmVhdGVkIHN1YmplY3RzIGFzIDE7IGNvbnRyb2wgc3ViamVjdHMgYXMgcHMvKDEtcHMpDQoNCmBgYHtyfQ0KdG95JHd0czEgPC0gaWZlbHNlKHRveSR0cmVhdGVkPT0xLCAxLCB0b3kkcHMvKDEtdG95JHBzKSkNCmBgYA0KDQpIZXJlIGlzIGEgcGxvdCBvZiB0aGUgcmVzdWx0aW5nIEFUVCAoYXZlcmFnZSB0cmVhdG1lbnQgZWZmZWN0IG9uIHRoZSB0cmVhdGVkKSB3ZWlnaHRzOg0KDQpgYGB7cn0NCmdncGxvdCh0b3ksIGFlcyh4ID0gcHMsIHkgPSB3dHMxLCBjb2xvciA9IHRyZWF0ZWRfZikpICsNCiAgICBnZW9tX3BvaW50KCkgKyANCiAgICBndWlkZXMoY29sb3IgPSBGQUxTRSkgKw0KICAgIGZhY2V0X3dyYXAofiB0cmVhdGVkX2YpICsNCiAgICBsYWJzKHggPSAiRXN0aW1hdGVkIFByb3BlbnNpdHkgZm9yIFRyZWF0bWVudCIsDQogICAgICAgICB5ID0gIkFUVCB3ZWlnaHRzIGZvciB0aGUgdG95IGV4YW1wbGUiLA0KICAgICAgICAgdGl0bGUgPSAiQVRUIHdlaWdodGluZyBzdHJ1Y3R1cmU6IFRveSBleGFtcGxlIikNCmBgYA0KDQoNCiMjIEFURSBBcHByb2FjaDogV2VpZ2h0IHRyZWF0ZWQgc3ViamVjdHMgYnkgMS9wczsgQ29udHJvbCBzdWJqZWN0cyBieSAxLygxLVBTKQ0KDQpgYGB7cn0NCnRveSR3dHMyIDwtIGlmZWxzZSh0b3kkdHJlYXRlZD09MSwgMS90b3kkcHMsIDEvKDEtdG95JHBzKSkNCmBgYA0KDQpIZXJlJ3MgYSBwbG90IG9mIHRoZSBBVEUgKGF2ZXJhZ2UgdHJlYXRtZW50IGVmZmVjdCkgd2VpZ2h0cy4uLg0KDQpgYGB7cn0NCmdncGxvdCh0b3ksIGFlcyh4ID0gcHMsIHkgPSB3dHMyLCBjb2xvciA9IHRyZWF0ZWRfZikpICsNCiAgICBnZW9tX3BvaW50KCkgKyANCiAgICBndWlkZXMoY29sb3IgPSBGQUxTRSkgKw0KICAgIGZhY2V0X3dyYXAofiB0cmVhdGVkX2YpICsNCiAgICBsYWJzKHggPSAiRXN0aW1hdGVkIFByb3BlbnNpdHkgZm9yIFRyZWF0bWVudCIsDQogICAgICAgICB5ID0gIkFURSB3ZWlnaHRzIGZvciB0aGUgdG95IGV4YW1wbGUiLA0KICAgICAgICAgdGl0bGUgPSAiQVRFIHdlaWdodGluZyBzdHJ1Y3R1cmU6IFRveSBleGFtcGxlIikNCmBgYA0KDQojIyBBc3Nlc3NpbmcgQmFsYW5jZSBhZnRlciBXZWlnaHRpbmcNCg0KVGhlIGB0d2FuZ2AgcGFja2FnZSBwcm92aWRlcyBzZXZlcmFsIGZ1bmN0aW9ucyBmb3IgYXNzZXNzaW5nIGJhbGFuY2UgYWZ0ZXIgd2VpZ2h0aW5nLCBpbiBhZGRpdGlvbiB0byBhY3R1YWxseSBkb2luZyB0aGUgd2VpZ2h0aW5nIHVzaW5nIG1vcmUgY29tcGxleCBwcm9wZW5zaXR5IG1vZGVscy4gRm9yIHRoaXMgZXhhbXBsZSwgd2UnbGwgZGVtb25zdHJhdGUgYmFsYW5jZSBhc3Nlc3NtZW50IGZvciBvdXIgdHdvIChyZWxhdGl2ZWx5IHNpbXBsZSkgd2VpZ2h0aW5nIHNjaGVtZXMuIEluIG90aGVyIGV4YW1wbGVzLCB3ZSdsbCB1c2UgYHR3YW5nYCB0byBkbyBtb3JlIGNvbXBsZXRlIHdlaWdodGluZyB3b3JrLg0KDQojIyMgUmVtaW5kZXIgb2YgQVRUIHZzLiBBVEUgRGVmaW5pdGlvbnMNCg0KLSBJbmZvcm1hbGx5LCB0aGUgKiphdmVyYWdlIHRyZWF0bWVudCBlZmZlY3Qgb24gdGhlIHRyZWF0ZWQqKiAoQVRUKSBlc3RpbWF0ZSBkZXNjcmliZXMgdGhlIGRpZmZlcmVuY2UgaW4gcG90ZW50aWFsIG91dGNvbWVzIChiZXR3ZWVuIHRyZWF0ZWQgYW5kIHVudHJlYXRlZCBzdWJqZWN0cykgc3VtbWFyaXplZCBhY3Jvc3MgdGhlIHBvcHVsYXRpb24gb2YgcGVvcGxlIHdobyBhY3R1YWxseSByZWNlaXZlZCB0aGUgdHJlYXRtZW50LiBUaGlzIGlzIHVzdWFsbHkgdGhlIGVzdGltYXRlIHdlIHdvcmsgd2l0aCBpbiBtYWtpbmcgY2F1c2FsIGVzdGltYXRlcyBmcm9tIG9ic2VydmF0aW9uYWwgc3R1ZGllcy4NCi0gT24gdGhlIG90aGVyIGhhbmQsIHRoZSAqKmF2ZXJhZ2UgdHJlYXRtZW50IGVmZmVjdCoqIChBVEUpIHJlZmVycyB0byB0aGUgZGlmZmVyZW5jZSBpbiBwb3RlbnRpYWwgb3V0Y29tZXMgc3VtbWFyaXplZCBhY3Jvc3MgdGhlIGVudGlyZSBwb3B1bGF0aW9uLCBpbmNsdWRpbmcgdGhvc2Ugd2hvIGRpZCBub3QgcmVjZWl2ZSB0aGUgdHJlYXRtZW50LiAgDQoNCg0KIyMjIEZvciBBVFQgd2VpZ2h0cyAoYHd0czFgKQ0KDQpgYGB7cn0NCnRveV9kZiA8LSBiYXNlOjpkYXRhLmZyYW1lKHRveSkgIyB0d2FuZyBkb2Vzbid0IHJlYWN0IHdlbGwgdG8gdGliYmxlcw0KDQpjb3ZsaXN0IDwtIGMoImNvdkEiLCAiY292QiIsICJjb3ZDIiwgImNvdkQiLCAiY292RSIsICJjb3ZGIiwgIkFzcXIiLCJCQyIsICJCRCIsICJwcyIsICJsaW5wcyIpDQoNCiMgZm9yIEFUVCB3ZWlnaHRzDQpiYWwud3RzMSA8LSBkeC53dHMoeD10b3lfZGYkd3RzMSwgZGF0YT10b3lfZGYsIHZhcnM9Y292bGlzdCwgDQogICAgICAgICAgICAgICAgICAgdHJlYXQudmFyPSJ0cmVhdGVkIiwgZXN0aW1hbmQ9IkFUVCIpDQpiYWwud3RzMQ0KYmFsLnRhYmxlKGJhbC53dHMxKQ0KYGBgDQoNClRoZSBgc3RkLmVmZi5zemAgc2hvd3MgdGhlIHN0YW5kYXJkaXplZCBkaWZmZXJlbmNlLCBidXQgYXMgYSBwcm9wb3J0aW9uLCByYXRoZXIgdGhhbiBhcyBhIHBlcmNlbnRhZ2UuIFdlJ2xsIGNyZWF0ZSBhIGRhdGEgZnJhbWUgKHRpYmJsZSkgc28gd2UgY2FuIHBsb3QgdGhlIGRhdGEgbW9yZSBlYXNpbHkuDQoNCmBgYHtyIH0NCmJhbC5iZWZvcmUud3RzMSA8LSBiYWwudGFibGUoYmFsLnd0czEpWzFdDQpiYWwuYWZ0ZXIud3RzMSA8LSBiYWwudGFibGUoYmFsLnd0czEpWzJdDQoNCmJhbGFuY2UuYXR0LndlaWdodHMgPC0gYmFzZTo6ZGF0YS5mcmFtZShuYW1lcyA9IHJvd25hbWVzKGJhbC5iZWZvcmUud3RzMSR1bncpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZS53ZWlnaHRpbmcgPSAxMDAqYmFsLmJlZm9yZS53dHMxJHVudyRzdGQuZWZmLnN6LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFUVC53ZWlnaHRlZCA9IDEwMCpiYWwuYWZ0ZXIud3RzMVtbMV1dJHN0ZC5lZmYuc3opDQpiYWxhbmNlLmF0dC53ZWlnaHRzIDwtIGdhdGhlcihiYWxhbmNlLmF0dC53ZWlnaHRzLCB0aW1pbmcsIHN6ZCwgMjozKQ0KYGBgDQoNCk9LIC0gaGVyZSBpcyB0aGUgcGxvdCBvZiBzdGFuZGFyZGl6ZWQgZGlmZmVyZW5jZXMgYmVmb3JlIGFuZCBhZnRlciBBVFQgd2VpZ2h0aW5nLg0KDQpgYGB7cn0NCmdncGxvdChiYWxhbmNlLmF0dC53ZWlnaHRzLCBhZXMoeCA9IHN6ZCwgeSA9IHJlb3JkZXIobmFtZXMsIHN6ZCksIGNvbG9yID0gdGltaW5nKSkgKw0KICAgIGdlb21fcG9pbnQoc2l6ZSA9IDMpICsgDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCkgKw0KICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoLTEwLDEwKSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sID0gImJsdWUiKSArDQogICAgbGFicyh4ID0gIlN0YW5kYXJkaXplZCBEaWZmZXJlbmNlIiwgeSA9ICIiLCANCiAgICAgICAgIHRpdGxlID0gIlN0YW5kYXJkaXplZCBEaWZmZXJlbmNlIGJlZm9yZSBhbmQgYWZ0ZXIgQVRUIFdlaWdodGluZyIsDQogICAgICAgICBzdWJ0aXRsZSA9ICJUaGUgdG95IGV4YW1wbGUiKSANCmBgYA0KDQoNCiMjIyBGb3IgQVRFIHdlaWdodHMgKGB3dHMyYCkNCg0KYGBge3J9DQpiYWwud3RzMiA8LSBkeC53dHMoeD10b3lfZGYkd3RzMiwgZGF0YT10b3lfZGYsIHZhcnM9Y292bGlzdCwgDQogICAgICAgICAgICAgICAgICAgdHJlYXQudmFyPSJ0cmVhdGVkIiwgZXN0aW1hbmQ9IkFURSIpDQpiYWwud3RzMg0KYmFsLnRhYmxlKGJhbC53dHMyKQ0KYGBgDQoNCmBgYHtyIH0NCmJhbC5iZWZvcmUud3RzMiA8LSBiYWwudGFibGUoYmFsLnd0czIpWzFdDQpiYWwuYWZ0ZXIud3RzMiA8LSBiYWwudGFibGUoYmFsLnd0czIpWzJdDQoNCmJhbGFuY2UuYXRlLndlaWdodHMgPC0gYmFzZTo6ZGF0YS5mcmFtZShuYW1lcyA9IHJvd25hbWVzKGJhbC5iZWZvcmUud3RzMiR1bncpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZS53ZWlnaHRpbmcgPSAxMDAqYmFsLmJlZm9yZS53dHMyJHVudyRzdGQuZWZmLnN6LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFURS53ZWlnaHRlZCA9IDEwMCpiYWwuYWZ0ZXIud3RzMltbMV1dJHN0ZC5lZmYuc3opDQpiYWxhbmNlLmF0ZS53ZWlnaHRzIDwtIGdhdGhlcihiYWxhbmNlLmF0ZS53ZWlnaHRzLCB0aW1pbmcsIHN6ZCwgMjozKQ0KYGBgDQoNCkhlcmUgaXMgdGhlIHBsb3Qgb2Ygc3RhbmRhcmRpemVkIGRpZmZlcmVuY2VzIGJlZm9yZSBhbmQgYWZ0ZXIgQVRFIHdlaWdodGluZy4NCg0KYGBge3J9DQpnZ3Bsb3QoYmFsYW5jZS5hdGUud2VpZ2h0cywgYWVzKHggPSBzemQsIHkgPSByZW9yZGVyKG5hbWVzLCBzemQpLCBjb2xvciA9IHRpbWluZykpICsNCiAgICBnZW9tX3BvaW50KHNpemUgPSAzKSArIA0KICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDApICsNCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKC0xMCwxMCksIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbCA9ICJibHVlIikgKw0KICAgIGxhYnMoeCA9ICJTdGFuZGFyZGl6ZWQgRGlmZmVyZW5jZSIsIHkgPSAiIiwgDQogICAgICAgICB0aXRsZSA9ICJTdGFuZGFyZGl6ZWQgRGlmZmVyZW5jZSBiZWZvcmUgYW5kIGFmdGVyIEFURSBXZWlnaHRpbmciLA0KICAgICAgICAgc3VidGl0bGUgPSAiVGhlIHRveSBleGFtcGxlIikgDQpgYGANCg0KIyMgUnViaW4ncyBSdWxlcyBhZnRlciBBVFQgd2VpZ2h0aW5nDQoNCkZvciBvdXIgd2VpZ2h0ZWQgc2FtcGxlLCBvdXIgc3VtbWFyeSBzdGF0aXN0aWMgZm9yIFJ1bGVzIDEgYW5kIDIgbWF5IGJlIGZvdW5kIGZyb20gdGhlDQpgYmFsLnRhYmxlYCBvdXRwdXQuDQoNCiMjIyBSdWJpbidzIFJ1bGUgMQ0KDQpXZSBjYW4gcmVhZCBvZmYgdGhlIHN0YW5kYXJkaXplZCBlZmZlY3Qgc2l6ZSBhZnRlciB3ZWlnaHRpbmcgZm9yIHRoZSBsaW5lYXIgcHJvcGVuc2l0eQ0Kc2NvcmUgYXMgLTAuMDkxLiBNdWx0aXBseWluZyBieSAxMDAsIHdlIGdldCA5LjElLCBzbyB3ZSB3b3VsZCBwYXNzIFJ1bGUgMS4NCg0KIyMjIFJ1YmluJ3MgUnVsZSAyDQoNCldlIGNhbiByZWFkIG9mZiB0aGUgc3RhbmRhcmQgZGV2aWF0aW9ucyB3aXRoaW4gdGhlIHRyZWF0ZWQgYW5kIGNvbnRyb2wgZ3JvdXBzLiBXZSBjYW4NCnRoZW4gc3F1YXJlIGVhY2gsIHRvIGdldCB0aGUgcmVsZXZhbnQgdmFyaWFuY2VzLCB0aGVuIHRha2UgdGhlIHJhdGlvIG9mIHRob3NlIHZhcmlhbmNlcy4NCkhlcmUsIHdlIGhhdmUgc3RhbmRhcmQgZGV2aWF0aW9ucyBvZiB0aGUgbGluZWFyIHByb3BlbnNpdHkgc2NvcmUgYWZ0ZXIgd2VpZ2h0aW5nIG9mIDAuODAxIGluIHRoZSB0cmVhdGVkIGdyb3VwIGFuZCAwLjkwNCBpbiB0aGUgY29udHJvbCBncm91cC4gMC44MDFeMiAvIDAuOTA0XjIgPSAwLjc4NTEsIHdoaWNoIGlzIGp1c3Qgb3V0c2lkZSBvdXIgZGVzaXJlZCByYW5nZSBvZiA0LzUgdG8gNS80LCBhcyB3ZWxsIGFzIGNsZWFybHkgd2l0aGluIDEvMiB0byAyLiBBcmd1YWJseSwgd2UgY2FuIHBhc3MgUnVsZSAyLCBhbHNvLiBCdXQgSSdsbCBiZSBpbnRlcmVzdGVkIHRvIHNlZSBpZiBgdHdhbmdgIGNhbiBkbyBiZXR0ZXIuDQoNCiMjIyBSdWJpbidzIFJ1bGUgMw0KDQpSdWJpbidzIFJ1bGUgMyByZXF1aXJlcyBzb21lIG1vcmUgc3Vic3RhbnRpYWwgbWFuaXB1bGF0aW9uIG9mIHRoZSBkYXRhLiBJJ2xsIHNraXAgdGhhdCBoZXJlLg0KDQojIyBSdWJpbidzIFJ1bGVzIGFmdGVyIEFURSB3ZWlnaHRpbmcNCg0KQWdhaW4sIG91ciBzdW1tYXJ5IHN0YXRpc3RpYyBmb3IgUnVsZXMgMSBhbmQgMiBtYXkgYmUgZm91bmQgZnJvbSB0aGUgYGJhbC50YWJsZWAgb3V0cHV0Lg0KDQojIyMgUnViaW4ncyBSdWxlIDENCg0KVGhlIHN0YW5kYXJkaXplZCBlZmZlY3Qgc2l6ZSBhZnRlciBBVEUgd2VpZ2h0aW5nIGZvciB0aGUgbGluZWFyIHByb3BlbnNpdHkgc2NvcmUgaXMgMC4xNzcuIE11bHRpcGx5aW5nIGJ5IDEwMCwgd2UgZ2V0IDE3LjclLCBzbyB3ZSB3b3VsZCBwYXNzIFJ1bGUgMS4NCg0KIyMjIFJ1YmluJ3MgUnVsZSAyDQoNCldlIGNhbiByZWFkIG9mZiB0aGUgc3RhbmRhcmQgZGV2aWF0aW9ucyB3aXRoaW4gdGhlIHRyZWF0ZWQgYW5kIGNvbnRyb2wgZ3JvdXBzIGZyb20gdGhlIEFURSB3ZWlnaHRzLCB0aGVuIHNxdWFyZSB0byBnZXQgdGhlIHZhcmlhbmNlcywgdGhlbiB0YWtlIHRoZSByYXRpby4gSGVyZSwgd2UgaGF2ZSAwLjgwNl4yIC8gMS4wNzheMiA9IDAuNTU5LCB3aGljaCBpcyBub3Qgd2l0aGluIG91ciBkZXNpcmVkIHJhbmdlIG9mIDQvNSB0byA1LzQsIGJ1dCBpcyBiZXR3ZWVuIDAuNSBhbmQgMi4gQXJndWFibHksIHdlIHBhc3MgUnVsZSAyLCBhbHNvLiBCdXQgSSdsbCBiZSBpbnRlcmVzdGVkIHRvIHNlZSBpZiBgdHdhbmdgIGNhbiBkbyBiZXR0ZXIuDQoNCiMjIyBSdWJpbidzIFJ1bGUgMw0KDQpBZ2FpbiwgZm9yIG5vdywgSSdtIHNraXBwaW5nIFJ1YmluJ3MgUnVsZSAzIGFmdGVyIHdlaWdodGluZy4NCg0KIyBVc2luZyBUV0FORyBmb3IgQWx0ZXJuYXRpdmUgUFMgRXN0aW1hdGlvbiBhbmQgQVRUIFdlaWdodGluZw0KDQpIZXJlLCBJJ2xsIGRlbW9uc3RyYXRlIHRoZSB1c2Ugb2YgdGhlIHRoZSBgdHdhbmdgIHBhY2thZ2UncyBmdW5jdGlvbnMgdG8gZml0IHRoZSBwcm9wZW5zaXR5IG1vZGVsIGFuZCB0aGVuIHBlcmZvcm0gQVRUIHdlaWdodGluZywgbW9zdGx5IHVzaW5nIGRlZmF1bHQgb3B0aW9ucy4NCg0KIyMgRXN0aW1hdGUgdGhlIFByb3BlbnNpdHkgU2NvcmUgdXNpbmcgR2VuZXJhbGl6ZWQgQm9vc3RlZCBSZWdyZXNzaW9uLCBhbmQgdGhlbiBwZXJmb20gQVRUIFdlaWdodGluZw0KDQpXZSBjYW4gZGlyZWN0bHkgdXNlIHRoZSBgdHdhbmdgICgqKnQqKm9vbGtpdCBmb3IgKip3KiplaWdodGluZyBhbmQgKiphKipuYWx5c2lzIG9mICoqbioqb25lcXVpdmFsZW50ICoqZyoqcm91cHMpIHBhY2thZ2UgdG8gd2VpZ2h0IG91ciByZXN1bHRzLCBhbmQgZXZlbiB0byByZS1lc3RpbWF0ZSB0aGUgcHJvcGVuc2l0eSBzY29yZSB1c2luZyBnZW5lcmFsaXplZCBib29zdGVkIHJlZ3Jlc3Npb24gcmF0aGVyIHRoYW4gYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLiBUaGUgYHR3YW5nYCB2aWduZXR0ZSBpcyB2ZXJ5IGhlbHBmdWwgYW5kIGZvdW5kIGF0IFt0aGlzIGxpbmtdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy90d2FuZy92aWduZXR0ZXMvdHdhbmcucGRmKS4NCg0KVG8gYmVnaW4sIHdlJ2xsIGVzdGltYXRlIHRoZSBwcm9wZW5zaXR5IHNjb3JlIHVzaW5nIHRoZSBgdHdhbmdgIGZ1bmN0aW9uIGBwc2AuIFRoaXMgdXNlcyBhICpnZW5lcmFsaXplZCBib29zdGVkIHJlZ3Jlc3Npb24qIGFwcHJvYWNoIHRvIGVzdGltYXRlIHRoZSBwcm9wZW5zaXR5IHNjb3JlIGFuZCBwcm9kdWNlIG1hdGVyaWFsIGZvciBjaGVja2luZyBiYWxhbmNlLg0KDQpgYGB7ciwgd2FybmluZyA9IEZBTFNFfQ0KIyBSZWNhbGwgdGhhdCB0d2FuZyBkb2VzIG5vdCBwbGF5IHdlbGwgd2l0aCB0aWJibGVzLA0KIyBzbyB3ZSBoYXZlIHRvIHVzZSB0aGUgZGF0YSBmcmFtZSB2ZXJzaW9uIG9mIHRoZSB0b3kgb2JqZWN0DQoNCnBzLnRveSA8LSBwcyh0cmVhdGVkIH4gY292QSArIGNvdkIgKyBjb3ZDICsgY292RCArIGNvdkUgKyBjb3ZGICsgDQogICAgICAgICAgICAgICAgIEFzcXIgKyBCQyArIEJELA0KICAgICAgICAgICAgIGRhdGEgPSB0b3lfZGYsDQogICAgICAgICAgICAgbi50cmVlcyA9IDMwMDAsDQogICAgICAgICAgICAgaW50ZXJhY3Rpb24uZGVwdGggPSAyLA0KICAgICAgICAgICAgIHN0b3AubWV0aG9kID0gYygiZXMubWVhbiIpLA0KICAgICAgICAgICAgIGVzdGltYW5kID0gIkFUVCIsDQogICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFKQ0KYGBgDQoNCiMjIyBEaWQgd2UgbGV0IHRoZSBzaW11bGF0aW9ucyBydW4gbG9uZyBlbm91Z2ggdG8gc3RhYmlsaXplIGVzdGltYXRlcz8NCg0KYGBge3J9DQpwbG90KHBzLnRveSkNCmBgYA0KDQojIyMgV2hhdCBpcyB0aGUgZWZmZWN0aXZlIHNhbXBsZSBzaXplIG9mIG91ciB3ZWlnaHRlZCByZXN1bHRzPw0KDQpgYGB7cn0NCnN1bW1hcnkocHMudG95KQ0KYGBgDQoNCiMjIyBIb3cgaXMgdGhlIGJhbGFuY2U/DQoNCmBgYHtyfQ0KcGxvdChwcy50b3ksIHBsb3RzID0gMikNCmBgYA0KDQpgYGB7cn0NCnBsb3QocHMudG95LCBwbG90cyA9IDMpDQpgYGANCg0KIyMjIEFzc2Vzc2luZyBCYWxhbmNlIHdpdGggYGNvYmFsdGANCg0KYGBge3J9DQpiMiA8LSBiYWwudGFiKHBzLnRveSwgZnVsbC5zdG9wLm1ldGhvZCA9ICJlcy5tZWFuLmF0dCIsIA0KICAgICAgICBzdGF0cyA9IGMoIm0iLCAidiIpLCB1biA9IFRSVUUpDQoNCmIyDQpgYGANCg0KIyMgU2VtaS1BdXRvbWF0ZWQgTG92ZSBwbG90IG9mIFN0YW5kYXJkaXplZCBEaWZmZXJlbmNlcw0KDQpgYGB7cn0NCnAgPC0gbG92ZS5wbG90KGIyLCANCiAgICAgICAgICAgICAgIHRocmVzaG9sZCA9IC4xLCBzaXplID0gMywgDQogICAgICAgICAgICAgICB0aXRsZSA9ICJTdGFuZGFyZGl6ZWQgRGlmZnMgYW5kIFRXQU5HIEFUVCB3ZWlnaHRpbmciKQ0KcCArIHRoZW1lX2J3KCkNCmBgYA0KDQojIyBTZW1pLUF1dG9tYXRlZCBMb3ZlIHBsb3Qgb2YgVmFyaWFuY2UgUmF0aW9zDQoNCmBgYHtyfQ0KcCA8LSBsb3ZlLnBsb3QoYjIsIHN0YXQgPSAidiIsDQogICAgICAgICAgICAgICB0aHJlc2hvbGQgPSAxLjI1LCBzaXplID0gMywgDQogICAgICAgICAgICAgICB0aXRsZSA9ICJWYXJpYW5jZSBSYXRpb3M6IFRXQU5HIEFUVCB3ZWlnaHRpbmciKQ0KcCArIHRoZW1lX2J3KCkNCmBgYA0KDQojIFRhc2sgOS4gQWZ0ZXIgd2VpZ2h0aW5nLCB3aGF0IGlzIHRoZSBlc3RpbWF0ZWQgYXZlcmFnZSBjYXVzYWwgZWZmZWN0IG9mIHRyZWF0bWVudD8NCg0KIyMgLi4uIG9uIE91dGNvbWUgMSBbYSBjb250aW51b3VzIG91dGNvbWVdDQoNCiMjIyB3aXRoIEFUVCB3ZWlnaHRzDQoNClRoZSByZWxldmFudCByZWdyZXNzaW9uIGFwcHJvYWNoIHVzZXMgdGhlIGBzdnlkZXNpZ25gIGFuZCBgc3Z5Z2xtYCBmdW5jdGlvbnMgZnJvbSB0aGUgYHN1cnZleWAgcGFja2FnZS4NCg0KYGBge3J9DQp0b3l3dDEuZGVzaWduIDwtIHN2eWRlc2lnbihpZHM9fjEsIHdlaWdodHM9fnd0czEsIGRhdGE9dG95KSAjIHVzaW5nIEFUVCB3ZWlnaHRzDQoNCmFkam91dDEud3QxIDwtIHN2eWdsbShvdXQxLmNvc3QgfiB0cmVhdGVkLCBkZXNpZ249dG95d3QxLmRlc2lnbikNCg0Kd3RfYXR0X3Jlc3VsdHMxIDwtIHRpZHkoYWRqb3V0MS53dDEsIGNvbmYuaW50ID0gVFJVRSkgJT4lIGZpbHRlcih0ZXJtID09ICJ0cmVhdGVkIikNCg0Kd3RfYXR0X3Jlc3VsdHMxDQpgYGANCg0KIyMjIHdpdGggQVRFIHdlaWdodHMNCg0KYGBge3J9DQp0b3l3dDIuZGVzaWduIDwtIHN2eWRlc2lnbihpZHM9fjEsIHdlaWdodHM9fnd0czIsIGRhdGE9dG95KSAjIHVzaW5nIEFURSB3ZWlnaHRzDQoNCmFkam91dDEud3QyIDwtIHN2eWdsbShvdXQxLmNvc3QgfiB0cmVhdGVkLCBkZXNpZ249dG95d3QyLmRlc2lnbikNCnd0X2F0ZV9yZXN1bHRzMSA8LSB0aWR5KGFkam91dDEud3QyLCBjb25mLmludCA9IFRSVUUpICU+JSBmaWx0ZXIodGVybSA9PSAidHJlYXRlZCIpDQoNCnd0X2F0ZV9yZXN1bHRzMQ0KYGBgDQoNCiMjIyB3aXRoIFRXQU5HIEFUVCB3ZWlnaHRzDQoNCmBgYHtyfQ0KdG95d3QzLmRlc2lnbiA8LSBzdnlkZXNpZ24oaWRzPX4xLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodHM9fmdldC53ZWlnaHRzKHBzLnRveSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdG9wLm1ldGhvZCA9ICJlcy5tZWFuIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhPXRveSkgIyB1c2luZyB0d2FuZyBBVFQgd2VpZ2h0cw0KDQphZGpvdXQxLnd0MyA8LSBzdnlnbG0ob3V0MS5jb3N0IH4gdHJlYXRlZCwgZGVzaWduPXRveXd0My5kZXNpZ24pDQp3dF90d2FuZ2F0dF9yZXN1bHRzMSA8LSB0aWR5KGFkam91dDEud3QzLCBjb25mLmludCA9IFRSVUUpICU+JSBmaWx0ZXIodGVybSA9PSAidHJlYXRlZCIpDQoNCnd0X3R3YW5nYXR0X3Jlc3VsdHMxDQpgYGANCg0KDQojIyAuLi4gb24gT3V0Y29tZSAyIFthIGJpbmFyeSBvdXRjb21lXQ0KDQpGb3IgYSBiaW5hcnkgb3V0Y29tZSwgd2UgYnVpbGQgdGhlIG91dGNvbWUgbW9kZWwgdXNpbmcgdGhlIHF1YXNpYmlub21pYWwsIHJhdGhlciB0aGFuIHRoZSB1c3VhbCBiaW5vbWlhbCBmYW1pbHkuIFdlIHVzZSB0aGUgc2FtZSBgc3Z5ZGVzaWduYCBpbmZvcm1hdGlvbiBhcyB3ZSBidWlsdCBmb3Igb3V0Y29tZSAxLg0KDQojIyMgVXNpbmcgQVRUIHdlaWdodHMNCg0KYGBge3J9DQphZGpvdXQyLnd0MSA8LSBzdnlnbG0ob3V0MiB+IHRyZWF0ZWQsIGRlc2lnbj10b3l3dDEuZGVzaWduLCBmYW1pbHk9cXVhc2liaW5vbWlhbCgpKQ0KDQp3dF9hdHRfcmVzdWx0czIgPC0gdGlkeShhZGpvdXQyLnd0MSwgY29uZi5pbnQgPSBUUlVFLCBleHBvbmVudGlhdGUgPSBUUlVFKSAlPiUgDQogICAgZmlsdGVyKHRlcm0gPT0gInRyZWF0ZWQiKQ0KDQp3dF9hdHRfcmVzdWx0czINCmBgYA0KDQojIyMgVXNpbmcgQVRFIHdlaWdodHMNCg0KYGBge3J9DQphZGpvdXQyLnd0MiA8LSBzdnlnbG0ob3V0Mi5ldmVudCB+IHRyZWF0ZWQsIGRlc2lnbj10b3l3dDIuZGVzaWduLCBmYW1pbHk9cXVhc2liaW5vbWlhbCgpKQ0KDQp3dF9hdGVfcmVzdWx0czIgPC0gdGlkeShhZGpvdXQyLnd0MiwgY29uZi5pbnQgPSBUUlVFLCBleHBvbmVudGlhdGUgPSBUUlVFKSAlPiUgDQogICAgZmlsdGVyKHRlcm0gPT0gInRyZWF0ZWQiKQ0KDQp3dF9hdGVfcmVzdWx0czINCmBgYA0KDQojIyMgd2l0aCBUV0FORyBBVFQgd2VpZ2h0cw0KDQpgYGB7cn0NCmFkam91dDIud3QzIDwtIHN2eWdsbShvdXQyIH4gdHJlYXRlZCwgZGVzaWduPXRveXd0My5kZXNpZ24sDQogICAgICAgICAgICAgICAgICAgICAgZmFtaWx5PXF1YXNpYmlub21pYWwoKSkNCg0Kd3RfdHdhbmdhdHRfcmVzdWx0czIgPC0gdGlkeShhZGpvdXQyLnd0MywgY29uZi5pbnQgPSBUUlVFLCBleHBvbmVudGlhdGUgPSBUUlVFKSAlPiUgDQogICAgZmlsdGVyKHRlcm0gPT0gInRyZWF0ZWQiKQ0KDQp3dF90d2FuZ2F0dF9yZXN1bHRzMg0KYGBgDQoNCiMjIC4uLiBvbiBPdXRjb21lIDMgW2EgdGltZSB0byBldmVudF0NCg0KQXMgYmVmb3JlLCBzdWJqZWN0cyB3aXRoIGBvdXQyLmV2ZW50YCA9ICJZZXMiIGFyZSB0cnVseSBvYnNlcnZlZCBldmVudHMsIHdoaWxlIHRob3NlIHdpdGggYG91dDIuZXZlbnRgID09ICJObyIgYXJlIGNlbnNvcmVkIGJlZm9yZSBhbiBldmVudCBjYW4gaGFwcGVuIHRvIHRoZW0uIA0KDQojIyMgVXNpbmcgQVRUIHdlaWdodHMNCg0KVGhlIENveCBtb2RlbCBjb21wYXJpbmcgdHJlYXRlZCB0byBjb250cm9sLCB3ZWlnaHRpbmcgYnkgQVRUIHdlaWdodHMgKGB3dHMxYCksIGlzLi4uDQoNCmBgYHtyfQ0KYWRqb3V0My53dDEgPC0gY294cGgoU3VydihvdXQzLnRpbWUsIG91dDIpIH4gdHJlYXRlZCwgZGF0YT10b3ksIHdlaWdodHM9d3RzMSkNCnd0X2F0dF9yZXN1bHRzMyA8LSB0aWR5KGFkam91dDMud3QxLCBleHBvbmVudGlhdGUgPSBUUlVFLCBjb25mLmludCA9IFRSVUUpICU+JSANCiAgICBmaWx0ZXIodGVybSA9PSAidHJlYXRlZCIpDQoNCnd0X2F0dF9yZXN1bHRzMw0KYGBgDQoNClRoZSBgZXhwKGNvZWYpYCBvdXRwdXQgZ2l2ZXMgdGhlIHJlbGF0aXZlIGhhemFyZCBvZiB0aGUgZXZlbnQgY29tcGFyaW5nIHRyZWF0ZWQgc3ViamVjdHMgdG8gY29udHJvbCBzdWJqZWN0cy4NCg0KQW5kIGhlcmUncyB0aGUgY2hlY2sgb2YgdGhlIHByb3BvcnRpb25hbCBoYXphcmRzIGFzc3VtcHRpb24uLi4NCg0KYGBge3J9DQpjb3guenBoKGFkam91dDMud3QxKTsgcGxvdChjb3guenBoKGFkam91dDMud3QxKSwgdmFyPSJ0cmVhdGVkIikNCmBgYA0KDQojIyMgVXNpbmcgQVRFIHdlaWdodHMNCg0KYGBge3J9DQphZGpvdXQzLnd0MiA8LSBjb3hwaChTdXJ2KG91dDMudGltZSwgb3V0MikgfiB0cmVhdGVkLCBkYXRhPXRveSwgd2VpZ2h0cz13dHMyKQ0Kd3RfYXRlX3Jlc3VsdHMzIDwtIHRpZHkoYWRqb3V0My53dDIsIGV4cG9uZW50aWF0ZSA9IFRSVUUsIGNvbmYuaW50ID0gVFJVRSkgJT4lIA0KICAgIGZpbHRlcih0ZXJtID09ICJ0cmVhdGVkIikNCg0Kd3RfYXRlX3Jlc3VsdHMzDQpgYGANCg0KQW5kIGhlcmUncyB0aGUgY2hlY2sgb2YgdGhlIHByb3BvcnRpb25hbCBoYXphcmRzIGFzc3VtcHRpb24uLi4NCg0KYGBge3J9DQpjb3guenBoKGFkam91dDMud3QyKTsgcGxvdChjb3guenBoKGFkam91dDMud3QyKSwgdmFyPSJ0cmVhdGVkIikNCmBgYA0KDQojIyMgd2l0aCBUV0FORyBBVFQgd2VpZ2h0cw0KDQpgYGB7cn0NCnd0czMgPC0gZ2V0LndlaWdodHMocHMudG95LCBzdG9wLm1ldGhvZCA9ICJlcy5tZWFuIikNCg0KYWRqb3V0My53dDMgPC0gY294cGgoU3VydihvdXQzLnRpbWUsIG91dDIpIH4gdHJlYXRlZCwgZGF0YT10b3ksIHdlaWdodHM9d3RzMykNCnd0X3R3YW5nYXR0X3Jlc3VsdHMzIDwtIHRpZHkoYWRqb3V0My53dDMsIGV4cG9uZW50aWF0ZSA9IFRSVUUsIGNvbmYuaW50ID0gVFJVRSkgJT4lIA0KICAgIGZpbHRlcih0ZXJtID09ICJ0cmVhdGVkIikNCg0Kd3RfdHdhbmdhdHRfcmVzdWx0czMNCmBgYA0KDQoNCiMjIFJlc3VsdHMgU28gRmFyIChBZnRlciBNYXRjaGluZywgU3ViY2xhc3NpZmljYXRpb24gYW5kIFdlaWdodGluZykNCg0KRXN0LiBUcmVhdG1lbnQgRWZmZWN0ICg5NSUgQ0kpIHwgT3V0Y29tZSAxIChDb3N0IGRpZmYuKSB8IE91dGNvbWUgMiAoUmlzayBkaWZmLikgfCBPdXRjb21lIDIgKE9kZHMgUmF0aW8pIHwgT3V0Y29tZSAzIChSZWwuIEhSKQ0KLS0tLS0tLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogDQpObyBjb3ZhcmlhdGUgYWRqdXN0bWVudCB8ICoqYHIgZGVjaW0ocmVzX3VuYWRqXzEkZXN0aW1hdGUsMilgKiogfCAqKmByIGRlY2ltKHJlc191bmFkal8yX3Jpc2tkaWZmJHJpc2suZGlmZiwgMylgKiogfCAqKmByIGRlY2ltKHJlc191bmFkal8yX29yJGVzdGltYXRlLCAyKWAqKiB8ICoqYHIgZGVjaW0ocmVzX3VuYWRqXzMkZXN0aW1hdGUsIDIpYCoqIA0KKHVuYWRqdXN0ZWQpIHwgKGByIGRlY2ltKHJlc191bmFkal8xJGNvbmYubG93LDIpYCwgYHIgZGVjaW0ocmVzX3VuYWRqXzEkY29uZi5oaWdoLDIpYCkgfCAoYHIgZGVjaW0ocmVzX3VuYWRqXzJfcmlza2RpZmYkY29uZi5sb3csIDMpYCwgYHIgZGVjaW0ocmVzX3VuYWRqXzJfcmlza2RpZmYkY29uZi5oaWdoLCAzKWApIHwgKGByIGRlY2ltKHJlc191bmFkal8yX29yJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHJlc191bmFkal8yX29yJGNvbmYuaGlnaCwgMilgKSB8IChgciBkZWNpbShyZXNfdW5hZGpfMyRjb25mLmxvdywgMilgLCBgciBkZWNpbShyZXNfdW5hZGpfMyRjb25mLmhpZ2gsIDIpYCkNCkFmdGVyIDE6MSBQUyBNYXRjaCB8ICoqYHIgZGVjaW0obWF0Y2gxLm91dDEkZXN0Lm5vYWRqLCAyKWAqKiB8ICoqYHIgZGVjaW0obWF0Y2gxX291dDIkZXN0Lm5vYWRqLCAzKWAqKiB8IE4vQSB8IE4vQSANCihgTWF0Y2hgOiBBdXRvbWF0ZWQpIHwgKGByIGRlY2ltKG1hdGNoMS5vdXQxJGVzdC5ub2FkaiAtIDEuOTYqbWF0Y2gxLm91dDEkc2Uuc3RhbmRhcmQsIDIpYCwgYHIgZGVjaW0obWF0Y2gxLm91dDEkZXN0Lm5vYWRqICsgMS45NiptYXRjaDEub3V0MSRzZS5zdGFuZGFyZCwgMilgKSB8IChgciBkZWNpbShtYXRjaDFfb3V0MiRlc3Qubm9hZGogLSAxLjk2Km1hdGNoMV9vdXQyJHNlLnN0YW5kYXJkLCAzKWAsIGByIGRlY2ltKG1hdGNoMV9vdXQyJGVzdC5ub2FkaiArIDEuOTYqbWF0Y2gxX291dDIkc2Uuc3RhbmRhcmQsIDMpYCkgfCBOL0EgfCBOL0ENCkFmdGVyIDE6MSBQUyBNYXRjaCB8ICoqYHIgZGVjaW0ocmVzX21hdGNoZWRfMSRlc3RpbWF0ZSwgMilgKiogfCBOL0EgfCAqKmByIGRlY2ltKGFkai5tLm91dDJfdGlkeSRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKGFkai5tLm91dDNfdGlkeSRlc3RpbWF0ZSwgMilgKioNCigiUmVncmVzc2lvbiIgTW9kZWxzKSB8IChgciBkZWNpbShyZXNfbWF0Y2hlZF8xJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHJlc19tYXRjaGVkXzEkY29uZi5oaWdoLCAyKWApIHwgTi9BIHwgKGByIGRlY2ltKGFkai5tLm91dDJfdGlkeSRjb25mLmxvdywgMilgLCBgciBkZWNpbShhZGoubS5vdXQyX3RpZHkkY29uZi5oaWdoLCAyKWApIHwgKGByIGRlY2ltKGFkai5tLm91dDNfdGlkeSRjb25mLmxvdywgMilgLCBgciBkZWNpbShhZGoubS5vdXQzX3RpZHkkY29uZi5oaWdoLCAyKWApDQpBZnRlciBQUyBTdWJjbGFzc2lmaWNhdGlvbiB8ICoqYHIgZGVjaW0oc3RyYXQucmVzdWx0MSRlc3RpbWF0ZSwgMilgKiogfCBOL0EgfCAqKmByIGRlY2ltKHN0cmF0LnJlc3VsdDIkZXN0aW1hdGUsIDIpYCoqIHwgKipgciBkZWNpbShzdHJhdC5yZXN1bHQzJGVzdGltYXRlLCAyKWAqKg0KKCJSZWdyZXNzaW9uIiBtb2RlbHMsIEFURSkgfCAoYHIgZGVjaW0oc3RyYXQucmVzdWx0MSRjb25mLmxvdywgMilgLCBgciBkZWNpbShzdHJhdC5yZXN1bHQxJGNvbmYuaGlnaCwgMilgKSB8IE4vQSB8IChgciBkZWNpbShzdHJhdC5yZXN1bHQyJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHN0cmF0LnJlc3VsdDEkY29uZi5oaWdoLCAyKWApIHwgKGByIGRlY2ltKHN0cmF0LnJlc3VsdDMkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oc3RyYXQucmVzdWx0MyRjb25mLmhpZ2gsIDIpYCkNCkFUVCBXZWlnaHRpbmcgfCAqKmByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMSRlc3RpbWF0ZSwgMilgKiogfCBOL0EgfCAqKmByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMiRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMyRlc3RpbWF0ZSwgMilgKioNCihBVFQpIHwgKGByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMSRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF9hdHRfcmVzdWx0czEkY29uZi5oaWdoLCAyKWApIHwgTi9BIHwgKGByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMiRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF9hdHRfcmVzdWx0czIkY29uZi5oaWdoLCAyKWApIHwgKGByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMyRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF9hdHRfcmVzdWx0czMkY29uZi5oaWdoLCAyKWApDQpBVEUgV2VpZ2h0aW5nIHwgKipgciBkZWNpbSh3dF9hdGVfcmVzdWx0czEkZXN0aW1hdGUsIDIpYCoqIHwgTi9BIHwgKipgciBkZWNpbSh3dF9hdGVfcmVzdWx0czIkZXN0aW1hdGUsIDIpYCoqIHwgKipgciBkZWNpbSh3dF9hdGVfcmVzdWx0czMkZXN0aW1hdGUsIDIpYCoqDQooQVRFKSB8IChgciBkZWNpbSh3dF9hdGVfcmVzdWx0czEkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0od3RfYXRlX3Jlc3VsdHMxJGNvbmYuaGlnaCwgMilgKSB8IE4vQSB8IChgciBkZWNpbSh3dF9hdGVfcmVzdWx0czIkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0od3RfYXRlX3Jlc3VsdHMyJGNvbmYuaGlnaCwgMilgKSB8IChgciBkZWNpbSh3dF9hdGVfcmVzdWx0czMkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0od3RfYXRlX3Jlc3VsdHMzJGNvbmYuaGlnaCwgMilgKQ0KYHR3YW5nYCBBVFQgd2VpZ2h0cyB8ICoqYHIgZGVjaW0od3RfdHdhbmdhdHRfcmVzdWx0czEkZXN0aW1hdGUsIDIpYCoqIHwgTi9BIHwgKipgciBkZWNpbSh3dF90d2FuZ2F0dF9yZXN1bHRzMiRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKHd0X3R3YW5nYXR0X3Jlc3VsdHMzJGVzdGltYXRlLCAyKWAqKg0KKEFUVCkgfCAoYHIgZGVjaW0od3RfdHdhbmdhdHRfcmVzdWx0czEkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0od3RfdHdhbmdhdHRfcmVzdWx0czEkY29uZi5oaWdoLCAyKWApIHwgTi9BIHwgKGByIGRlY2ltKHd0X3R3YW5nYXR0X3Jlc3VsdHMyJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHd0X3R3YW5nYXR0X3Jlc3VsdHMyJGNvbmYuaGlnaCwgMilgKSB8IChgciBkZWNpbSh3dF90d2FuZ2F0dF9yZXN1bHRzMyRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF90d2FuZ2F0dF9yZXN1bHRzMyRjb25mLmhpZ2gsIDIpYCkNCg0KIyBUYXNrIDEwLiBBZnRlciBkaXJlY3QgYWRqdXN0bWVudCBmb3IgdGhlIGxpbmVhciBQUywgd2hhdCBpcyB0aGUgZXN0aW1hdGVkIGF2ZXJhZ2UgIGNhdXNhbCB0cmVhdG1lbnQgZWZmZWN0Pw0KDQojIyAuLi4gb24gT3V0Y29tZSAxIFthIGNvbnRpbnVvdXMgb3V0Y29tZV0NCg0KSGVyZSwgd2UgZml0IGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCBgbGlucHNgIGFkZGVkIGFzIGEgY292YXJpYXRlLg0KDQpgYGB7cn0NCmFkai5yZWcub3V0MSA8LSBsbShvdXQxLmNvc3QgfiB0cmVhdGVkICsgbGlucHMsIGRhdGE9dG95KQ0KDQphZGpfb3V0MSA8LSB0aWR5KGFkai5yZWcub3V0MSwgY29uZi5pbnQgPSBUUlVFKSAlPiUgZmlsdGVyKHRlcm0gPT0gInRyZWF0ZWQiKQ0KDQphZGpfb3V0MQ0KYGBgDQoNCiMjIC4uLiBvbiBPdXRjb21lIDIgW2EgYmluYXJ5IG91dGNvbWVdDQoNCkhlcmUsIGZpdCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gd2l0aCBgbGlucHNgIGFkZGVkIGFzIGEgY292YXJpYXRlDQoNCmBgYHtyfQ0KYWRqLnJlZy5vdXQyIDwtIGdsbShvdXQyIH4gdHJlYXRlZCArIGxpbnBzLCBkYXRhPXRveSwgZmFtaWx5PWJpbm9taWFsKCkpDQoNCmFkal9vdXQyIDwtIHRpZHkoYWRqLnJlZy5vdXQyLCBleHBvbmVudGlhdGUgPSBUUlVFLCBjb25mLmludCA9IFRSVUUpICU+JSANCiAgICBmaWx0ZXIodGVybSA9PSAidHJlYXRlZCIpDQoNCmFkal9vdXQyDQpgYGANCg0KIyMgLi4uIG9uIE91dGNvbWUgMyBbYSB0aW1lLXRvLWV2ZW50IG91dGNvbWVdDQoNCkFnYWluLCBzdWJqZWN0cyB3aXRoIGBvdXQyLmV2ZW50YCBObyBhcmUgcmlnaHQtY2Vuc29yZWQsIHRob3NlIHdpdGggWWVzIGZvciBgb3V0Mi5ldmVudGAgaGF2ZSB0aGVpciB0aW1lcyB0byBldmVudCBvYnNlcnZlZC4NCg0KV2UgZml0IGEgQ294IHByb3BvcnRpb25hbCBoYXphcmRzIG1vZGVsIHByZWRpY3RpbmcgdGltZSB0byBldmVudCAod2l0aCBldmVudD1ZZXMgaW5kaWNhdGluZyBub24tY2Vuc29yZWQgY2FzZXMpIGJhc2VkIG9uIHRyZWF0bWVudCBncm91cCAodHJlYXRlZCkgYW5kIG5vdyBhbHNvIHRoZSBsaW5lYXIgcHJvcGVuc2l0eSBzY29yZS4NCg0KYGBge3J9DQphZGoucmVnLm91dDMgPC0gY294cGgoU3VydihvdXQzLnRpbWUsIG91dDIpIH4gdHJlYXRlZCArIGxpbnBzLCBkYXRhPXRveSkNCg0KYWRqX291dDMgPC0gdGlkeShhZGoucmVnLm91dDIsIGV4cG9uZW50aWF0ZSA9IFRSVUUsIGNvbmYuaW50ID0gVFJVRSkgJT4lIA0KICAgIGZpbHRlcih0ZXJtID09ICJ0cmVhdGVkIikNCg0KYWRqX291dDMNCmBgYA0KDQpUaGUgYGV4cChjb2VmKWAgc2VjdGlvbiBvZiB0aGUgYHN1bW1hcnlgIGZvciB0aGlzIG1vZGVsIGluZGljYXRlcyB0aGUgcmVsYXRpdmUgaGF6YXJkIGVzdGltYXRlcyBhbmQgYXNzb2NpYXRlZCA5NVwlIENJLg0KDQojIyMgQ2hlY2sgcHJvcG9ydGlvbmFsIGhhemFyZHMgYXNzdW1wdGlvbg0KDQpIZXJlJ3MgdGhlIGNoZWNrIG9mIHRoZSBwcm9wb3J0aW9uYWwgaGF6YXJkcyBhc3N1bXB0aW9uLg0KDQpgYGB7cn0NCmNveC56cGgoYWRqLnJlZy5vdXQzKQ0KcGxvdChjb3guenBoKGFkai5yZWcub3V0MyksIHZhcj0idHJlYXRlZCIpDQpwbG90KGNveC56cGgoYWRqLnJlZy5vdXQzKSwgdmFyPSJsaW5wcyIpDQpgYGANCg0KIyMgUmVzdWx0cyBTbyBGYXIgKEFmdGVyIE1hdGNoaW5nLCBTdWJjbGFzc2lmaWNhdGlvbiwgV2VpZ2h0aW5nLCBBZGp1c3RtZW50KQ0KDQpFc3QuIFRyZWF0bWVudCBFZmZlY3QgKDk1JSBDSSkgfCBPdXRjb21lIDEgKENvc3QgZGlmZi4pIHwgT3V0Y29tZSAyIChSaXNrIGRpZmYuKSB8IE91dGNvbWUgMiAoT2RkcyBSYXRpbykgfCBPdXRjb21lIDMgKFJlbC4gSFIpDQotLS0tLS0tLS0tLS0tLS0tOiB8IC0tLS0tLS0tLS0tOiB8IC0tLS0tLS0tLS0tOiB8IC0tLS0tLS0tLS0tOiB8IC0tLS0tLS0tLS0tOiANCk5vIGNvdmFyaWF0ZSBhZGp1c3RtZW50IHwgKipgciBkZWNpbShyZXNfdW5hZGpfMSRlc3RpbWF0ZSwyKWAqKiB8ICoqYHIgZGVjaW0ocmVzX3VuYWRqXzJfcmlza2RpZmYkcmlzay5kaWZmLCAzKWAqKiB8ICoqYHIgZGVjaW0ocmVzX3VuYWRqXzJfb3IkZXN0aW1hdGUsIDIpYCoqIHwgKipgciBkZWNpbShyZXNfdW5hZGpfMyRlc3RpbWF0ZSwgMilgKiogDQoodW5hZGp1c3RlZCkgfCAoYHIgZGVjaW0ocmVzX3VuYWRqXzEkY29uZi5sb3csMilgLCBgciBkZWNpbShyZXNfdW5hZGpfMSRjb25mLmhpZ2gsMilgKSB8IChgciBkZWNpbShyZXNfdW5hZGpfMl9yaXNrZGlmZiRjb25mLmxvdywgMylgLCBgciBkZWNpbShyZXNfdW5hZGpfMl9yaXNrZGlmZiRjb25mLmhpZ2gsIDMpYCkgfCAoYHIgZGVjaW0ocmVzX3VuYWRqXzJfb3IkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0ocmVzX3VuYWRqXzJfb3IkY29uZi5oaWdoLCAyKWApIHwgKGByIGRlY2ltKHJlc191bmFkal8zJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHJlc191bmFkal8zJGNvbmYuaGlnaCwgMilgKQ0KQWZ0ZXIgMToxIFBTIE1hdGNoIHwgKipgciBkZWNpbShtYXRjaDEub3V0MSRlc3Qubm9hZGosIDIpYCoqIHwgKipgciBkZWNpbShtYXRjaDFfb3V0MiRlc3Qubm9hZGosIDMpYCoqIHwgTi9BIHwgTi9BIA0KKGBNYXRjaGA6IEF1dG9tYXRlZCkgfCAoYHIgZGVjaW0obWF0Y2gxLm91dDEkZXN0Lm5vYWRqIC0gMS45NiptYXRjaDEub3V0MSRzZS5zdGFuZGFyZCwgMilgLCBgciBkZWNpbShtYXRjaDEub3V0MSRlc3Qubm9hZGogKyAxLjk2Km1hdGNoMS5vdXQxJHNlLnN0YW5kYXJkLCAyKWApIHwgKGByIGRlY2ltKG1hdGNoMV9vdXQyJGVzdC5ub2FkaiAtIDEuOTYqbWF0Y2gxX291dDIkc2Uuc3RhbmRhcmQsIDMpYCwgYHIgZGVjaW0obWF0Y2gxX291dDIkZXN0Lm5vYWRqICsgMS45NiptYXRjaDFfb3V0MiRzZS5zdGFuZGFyZCwgMylgKSB8IE4vQSB8IE4vQQ0KQWZ0ZXIgMToxIFBTIE1hdGNoIHwgKipgciBkZWNpbShyZXNfbWF0Y2hlZF8xJGVzdGltYXRlLCAyKWAqKiB8IE4vQSB8ICoqYHIgZGVjaW0oYWRqLm0ub3V0Ml90aWR5JGVzdGltYXRlLCAyKWAqKiB8ICoqYHIgZGVjaW0oYWRqLm0ub3V0M190aWR5JGVzdGltYXRlLCAyKWAqKg0KKCJSZWdyZXNzaW9uIiBNb2RlbHMpIHwgKGByIGRlY2ltKHJlc19tYXRjaGVkXzEkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0ocmVzX21hdGNoZWRfMSRjb25mLmhpZ2gsIDIpYCkgfCBOL0EgfCAoYHIgZGVjaW0oYWRqLm0ub3V0Ml90aWR5JGNvbmYubG93LCAyKWAsIGByIGRlY2ltKGFkai5tLm91dDJfdGlkeSRjb25mLmhpZ2gsIDIpYCkgfCAoYHIgZGVjaW0oYWRqLm0ub3V0M190aWR5JGNvbmYubG93LCAyKWAsIGByIGRlY2ltKGFkai5tLm91dDNfdGlkeSRjb25mLmhpZ2gsIDIpYCkNCkFmdGVyIFBTIFN1YmNsYXNzaWZpY2F0aW9uIHwgKipgciBkZWNpbShzdHJhdC5yZXN1bHQxJGVzdGltYXRlLCAyKWAqKiB8IE4vQSB8ICoqYHIgZGVjaW0oc3RyYXQucmVzdWx0MiRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKHN0cmF0LnJlc3VsdDMkZXN0aW1hdGUsIDIpYCoqDQooIlJlZ3Jlc3Npb24iIG1vZGVscywgQVRFKSB8IChgciBkZWNpbShzdHJhdC5yZXN1bHQxJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHN0cmF0LnJlc3VsdDEkY29uZi5oaWdoLCAyKWApIHwgTi9BIHwgKGByIGRlY2ltKHN0cmF0LnJlc3VsdDIkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oc3RyYXQucmVzdWx0MSRjb25mLmhpZ2gsIDIpYCkgfCAoYHIgZGVjaW0oc3RyYXQucmVzdWx0MyRjb25mLmxvdywgMilgLCBgciBkZWNpbShzdHJhdC5yZXN1bHQzJGNvbmYuaGlnaCwgMilgKQ0KQVRUIFdlaWdodGluZyB8ICoqYHIgZGVjaW0od3RfYXR0X3Jlc3VsdHMxJGVzdGltYXRlLCAyKWAqKiB8IE4vQSB8ICoqYHIgZGVjaW0od3RfYXR0X3Jlc3VsdHMyJGVzdGltYXRlLCAyKWAqKiB8ICoqYHIgZGVjaW0od3RfYXR0X3Jlc3VsdHMzJGVzdGltYXRlLCAyKWAqKg0KKEFUVCkgfCAoYHIgZGVjaW0od3RfYXR0X3Jlc3VsdHMxJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMSRjb25mLmhpZ2gsIDIpYCkgfCBOL0EgfCAoYHIgZGVjaW0od3RfYXR0X3Jlc3VsdHMyJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMiRjb25mLmhpZ2gsIDIpYCkgfCAoYHIgZGVjaW0od3RfYXR0X3Jlc3VsdHMzJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMyRjb25mLmhpZ2gsIDIpYCkNCkFURSBXZWlnaHRpbmcgfCAqKmByIGRlY2ltKHd0X2F0ZV9yZXN1bHRzMSRlc3RpbWF0ZSwgMilgKiogfCBOL0EgfCAqKmByIGRlY2ltKHd0X2F0ZV9yZXN1bHRzMiRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKHd0X2F0ZV9yZXN1bHRzMyRlc3RpbWF0ZSwgMilgKioNCihBVEUpIHwgKGByIGRlY2ltKHd0X2F0ZV9yZXN1bHRzMSRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF9hdGVfcmVzdWx0czEkY29uZi5oaWdoLCAyKWApIHwgTi9BIHwgKGByIGRlY2ltKHd0X2F0ZV9yZXN1bHRzMiRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF9hdGVfcmVzdWx0czIkY29uZi5oaWdoLCAyKWApIHwgKGByIGRlY2ltKHd0X2F0ZV9yZXN1bHRzMyRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF9hdGVfcmVzdWx0czMkY29uZi5oaWdoLCAyKWApDQpgdHdhbmdgIEFUVCB3ZWlnaHRzIHwgKipgciBkZWNpbSh3dF90d2FuZ2F0dF9yZXN1bHRzMSRlc3RpbWF0ZSwgMilgKiogfCBOL0EgfCAqKmByIGRlY2ltKHd0X3R3YW5nYXR0X3Jlc3VsdHMyJGVzdGltYXRlLCAyKWAqKiB8ICoqYHIgZGVjaW0od3RfdHdhbmdhdHRfcmVzdWx0czMkZXN0aW1hdGUsIDIpYCoqDQooQVRUKSB8IChgciBkZWNpbSh3dF90d2FuZ2F0dF9yZXN1bHRzMSRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF90d2FuZ2F0dF9yZXN1bHRzMSRjb25mLmhpZ2gsIDIpYCkgfCBOL0EgfCAoYHIgZGVjaW0od3RfdHdhbmdhdHRfcmVzdWx0czIkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0od3RfdHdhbmdhdHRfcmVzdWx0czIkY29uZi5oaWdoLCAyKWApIHwgKGByIGRlY2ltKHd0X3R3YW5nYXR0X3Jlc3VsdHMzJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHd0X3R3YW5nYXR0X3Jlc3VsdHMzJGNvbmYuaGlnaCwgMilgKQ0KRGlyZWN0IEFkanVzdG1lbnQgfCAqKmByIGRlY2ltKGFkal9vdXQxJGVzdGltYXRlLCAyKWAqKiB8IE4vQSB8ICoqYHIgZGVjaW0oYWRqX291dDIkZXN0aW1hdGUsIDIpYCoqIHwgKipgciBkZWNpbShhZGpfb3V0MyRlc3RpbWF0ZSwgMilgKioNCih3aXRoIGBsaW5wc2AsIEFUVCkgfCAoYHIgZGVjaW0oYWRqX291dDEkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oYWRqX291dDEkY29uZi5oaWdoLCAyKWApIHwgTi9BIHwgKGByIGRlY2ltKGFkal9vdXQyJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKGFkal9vdXQyJGNvbmYuaGlnaCwgMilgKSB8IChgciBkZWNpbShhZGpfb3V0MyRjb25mLmxvdywgMilgLCBgciBkZWNpbShhZGpfb3V0MyRjb25mLmhpZ2gsIDIpYCkNCg0KIyBUYXNrIDExLiAiRG91YmxlIFJvYnVzdCIgQXBwcm9hY2ggLSBXZWlnaHRpbmcgKyBBZGp1c3RtZW50LCB3aGF0IGlzIHRoZSBlc3RpbWF0ZWQgYXZlcmFnZSBjYXVzYWwgZWZmZWN0IG9mIHRyZWF0bWVudD8NCg0KVGhpcyBhcHByb2FjaCBpcyBlc3NlbnRpYWxseSBpZGVudGljYWwgdG8gdGhlIHdlaWdodGluZyBhbmFseXNlcyBkb25lIGluIFRhc2sgOS4gVGhlIG9ubHkgY2hhbmdlIGlzIHRvIGFkZCBgbGlucHNgIHRvIGB0cmVhdGVkYCBpbiB0aGUgb3V0Y29tZSBtb2RlbHMuDQoNCiMjIC4uLiBvbiBPdXRjb21lIDEgW2EgY29udGludW91cyBvdXRjb21lXQ0KDQojIyMgd2l0aCBBVFQgd2VpZ2h0cw0KDQpUaGUgcmVsZXZhbnQgcmVncmVzc2lvbiBhcHByb2FjaCB1c2VzIHRoZSBgc3Z5ZGVzaWduYCBhbmQgYHN2eWdsbWAgZnVuY3Rpb25zIGZyb20gdGhlIGBzdXJ2ZXlgIHBhY2thZ2UuDQoNCmBgYHtyfQ0KdG95d3QxLmRlc2lnbiA8LSBzdnlkZXNpZ24oaWRzPX4xLCB3ZWlnaHRzPX53dHMxLCBkYXRhPXRveSkgIyB1c2luZyBBVFQgd2VpZ2h0cw0KDQpkci5vdXQxLnd0MSA8LSBzdnlnbG0ob3V0MS5jb3N0IH4gdHJlYXRlZCArIGxpbnBzLCBkZXNpZ249dG95d3QxLmRlc2lnbikNCg0KZHJfYXR0X291dDEgPC0gdGlkeShkci5vdXQxLnd0MSwgY29uZi5pbnQgPSBUUlVFKSAlPiUgZmlsdGVyKHRlcm0gPT0gInRyZWF0ZWQiKQ0KDQpkcl9hdHRfb3V0MQ0KYGBgDQoNCiMjIyB3aXRoIEFURSB3ZWlnaHRzDQoNCmBgYHtyfQ0KdG95d3QyLmRlc2lnbiA8LSBzdnlkZXNpZ24oaWRzPX4xLCB3ZWlnaHRzPX53dHMyLCBkYXRhPXRveSkgIyB1c2luZyBBVEUgd2VpZ2h0cw0KDQpkci5vdXQxLnd0MiA8LSBzdnlnbG0ob3V0MS5jb3N0IH4gdHJlYXRlZCArIGxpbnBzLCBkZXNpZ249dG95d3QyLmRlc2lnbikNCg0KZHJfYXRlX291dDEgPC0gdGlkeShkci5vdXQxLnd0MiwgY29uZi5pbnQgPSBUUlVFKSAlPiUgZmlsdGVyKHRlcm0gPT0gInRyZWF0ZWQiKQ0KDQpkcl9hdGVfb3V0MQ0KYGBgDQoNCiMjIyB3aXRoIGB0d2FuZ2AgYmFzZWQgQVRUIHdlaWdodHMNCg0KYGBge3J9DQp3dHMzIDwtIGdldC53ZWlnaHRzKHBzLnRveSwgc3RvcC5tZXRob2QgPSAiZXMubWVhbiIpDQoNCnRveXd0My5kZXNpZ24gPC0gc3Z5ZGVzaWduKGlkcz1+MSwgd2VpZ2h0cz1+d3RzMywgZGF0YT10b3kpICMgdHdhbmcgQVRUIHdlaWdodHMNCg0KZHIub3V0MS53dDMgPC0gc3Z5Z2xtKG91dDEuY29zdCB+IHRyZWF0ZWQgKyBsaW5wcywgZGVzaWduPXRveXd0My5kZXNpZ24pDQoNCmRyX3R3YW5nYXR0X291dDEgPC0gdGlkeShkci5vdXQxLnd0MywgY29uZi5pbnQgPSBUUlVFKSAlPiUgZmlsdGVyKHRlcm0gPT0gInRyZWF0ZWQiKQ0KDQpkcl90d2FuZ2F0dF9vdXQxDQpgYGANCg0KDQojIyAuLi4gb24gT3V0Y29tZSAyIFthIGJpbmFyeSBvdXRjb21lXQ0KDQpGb3IgYSBiaW5hcnkgb3V0Y29tZSwgd2UgYnVpbGQgdGhlIG91dGNvbWUgbW9kZWwgdXNpbmcgdGhlIHF1YXNpYmlub21pYWwsIHJhdGhlciB0aGFuIHRoZSB1c3VhbCBiaW5vbWlhbCBmYW1pbHkuIFdlIHVzZSB0aGUgc2FtZSBgc3Z5ZGVzaWduYCBpbmZvcm1hdGlvbiBhcyB3ZSBidWlsdCBmb3Igb3V0Y29tZSAxLg0KDQojIyMgVXNpbmcgQVRUIHdlaWdodHMNCg0KYGBge3J9DQpkci5vdXQyLnd0MSA8LSBzdnlnbG0ob3V0MiB+IHRyZWF0ZWQgKyBsaW5wcywgZGVzaWduPXRveXd0MS5kZXNpZ24sDQogICAgICAgICAgICAgICAgICAgICAgZmFtaWx5PXF1YXNpYmlub21pYWwoKSkNCmRyX2F0dF9vdXQyIDwtIHRpZHkoZHIub3V0Mi53dDEsIGV4cG9uZW50aWF0ZSA9IFRSVUUsIGNvbmYuaW50ID0gVFJVRSkgJT4lIA0KICAgIGZpbHRlcih0ZXJtID09ICJ0cmVhdGVkIikNCg0KZHJfYXR0X291dDINCmBgYA0KDQojIyMgVXNpbmcgQVRFIHdlaWdodHMNCg0KYGBge3J9DQpkci5vdXQyLnd0MiA8LSBzdnlnbG0ob3V0Mi5ldmVudCB+IHRyZWF0ZWQgKyBsaW5wcywgZGVzaWduPXRveXd0Mi5kZXNpZ24sDQogICAgICAgICAgICAgICAgICAgICAgZmFtaWx5PXF1YXNpYmlub21pYWwoKSkNCmRyX2F0ZV9vdXQyIDwtIHRpZHkoZHIub3V0Mi53dDIsIGV4cG9uZW50aWF0ZSA9IFRSVUUsIGNvbmYuaW50ID0gVFJVRSkgJT4lIA0KICAgIGZpbHRlcih0ZXJtID09ICJ0cmVhdGVkIikNCg0KZHJfYXRlX291dDINCmBgYA0KDQojIyMgVXNpbmcgYHR3YW5nYCBBVFQgd2VpZ2h0cw0KDQpgYGB7cn0NCmRyLm91dDIud3QzIDwtIHN2eWdsbShvdXQyIH4gdHJlYXRlZCArIGxpbnBzLCBkZXNpZ249dG95d3QzLmRlc2lnbiwNCiAgICAgICAgICAgICAgICAgICAgICBmYW1pbHk9cXVhc2liaW5vbWlhbCgpKQ0KZHJfdHdhbmdhdHRfb3V0MiA8LSB0aWR5KGRyLm91dDIud3QzLCBleHBvbmVudGlhdGUgPSBUUlVFLCBjb25mLmludCA9IFRSVUUpICU+JSANCiAgICBmaWx0ZXIodGVybSA9PSAidHJlYXRlZCIpDQoNCmRyX3R3YW5nYXR0X291dDINCmBgYA0KDQoNCiMjIC4uLiBvbiBPdXRjb21lIDMgW2EgdGltZSB0byBldmVudF0NCg0KQXMgYmVmb3JlLCBzdWJqZWN0cyB3aXRoIGBvdXQyLmV2ZW50YCA9ICJZZXMiIGFyZSB0cnVseSBvYnNlcnZlZCBldmVudHMsIHdoaWxlIHRob3NlIHdpdGggYG91dDIuZXZlbnRgID09ICJObyIgYXJlIGNlbnNvcmVkIGJlZm9yZSBhbiBldmVudCBjYW4gaGFwcGVuIHRvIHRoZW0uIA0KDQojIyMgVXNpbmcgQVRUIHdlaWdodHMNCg0KVGhlIENveCBtb2RlbCBjb21wYXJpbmcgdHJlYXRlZCB0byBjb250cm9sLCB3ZWlnaHRpbmcgYnkgQVRUIHdlaWdodHMgKGB3dHMxYCksIGlzLi4uDQoNCmBgYHtyfQ0KZHIub3V0My53dDEgPC0gY294cGgoU3VydihvdXQzLnRpbWUsIG91dDIpIH4gdHJlYXRlZCArIGxpbnBzLCBkYXRhPXRveSwgd2VpZ2h0cz13dHMxKQ0KZHJfYXR0X291dDMgPC0gdGlkeShkci5vdXQzLnd0MSwgZXhwb25lbnRpYXRlID0gVFJVRSwgY29uZi5pbnQgPSBUUlVFKSAlPiUgDQogICAgZmlsdGVyKHRlcm0gPT0gInRyZWF0ZWQiKQ0KDQpkcl9hdHRfb3V0Mw0KYGBgDQoNClRoZSBgZXhwKGNvZWYpYCBvdXRwdXQgZ2l2ZXMgdGhlIHJlbGF0aXZlIGhhemFyZCBvZiB0aGUgZXZlbnQgY29tcGFyaW5nIHRyZWF0ZWQgc3ViamVjdHMgdG8gY29udHJvbCBzdWJqZWN0cy4NCg0KQW5kIGhlcmUncyB0aGUgY2hlY2sgb2YgdGhlIHByb3BvcnRpb25hbCBoYXphcmRzIGFzc3VtcHRpb24uLi4NCg0KYGBge3J9DQpjb3guenBoKGRyLm91dDMud3QxKTsgcGxvdChjb3guenBoKGRyLm91dDMud3QxKSwgdmFyPSJ0cmVhdGVkIikNCmBgYA0KDQojIyMgVXNpbmcgQVRFIHdlaWdodHMNCg0KYGBge3J9DQpkci5vdXQzLnd0MiA8LSBjb3hwaChTdXJ2KG91dDMudGltZSwgb3V0MikgfiB0cmVhdGVkICsgbGlucHMsIGRhdGE9dG95LCB3ZWlnaHRzPXd0czIpDQoNCmRyX2F0ZV9vdXQzIDwtIHRpZHkoZHIub3V0My53dDIsIGV4cG9uZW50aWF0ZSA9IFRSVUUsIGNvbmYuaW50ID0gVFJVRSkgJT4lIA0KICAgIGZpbHRlcih0ZXJtID09ICJ0cmVhdGVkIikNCg0KZHJfYXRlX291dDMNCmBgYA0KDQpBbmQgaGVyZSdzIHRoZSBjaGVjayBvZiB0aGUgcHJvcG9ydGlvbmFsIGhhemFyZHMgYXNzdW1wdGlvbi4uLg0KDQpgYGB7cn0NCmNveC56cGgoZHIub3V0My53dDIpOyBwbG90KGNveC56cGgoZHIub3V0My53dDIpLCB2YXI9InRyZWF0ZWQiKQ0KYGBgDQoNCiMjIyBVc2luZyBgdHdhbmdgIEFUVCB3ZWlnaHRzDQoNCmBgYHtyfQ0KZHIub3V0My53dDMgPC0gY294cGgoU3VydihvdXQzLnRpbWUsIG91dDIpIH4gdHJlYXRlZCArIGxpbnBzLCANCiAgICAgICAgICAgICAgICAgICAgIGRhdGE9dG95LCB3ZWlnaHRzPXd0czMpDQpkcl90d2FuZ2F0dF9vdXQzIDwtIHRpZHkoZHIub3V0My53dDMsIGV4cG9uZW50aWF0ZSA9IFRSVUUsIGNvbmYuaW50ID0gVFJVRSkgJT4lIA0KICAgIGZpbHRlcih0ZXJtID09ICJ0cmVhdGVkIikNCg0KZHJfdHdhbmdhdHRfb3V0Mw0KYGBgDQoNClRoZSBgZXhwKGNvZWYpYCBvdXRwdXQgZ2l2ZXMgdGhlIHJlbGF0aXZlIGhhemFyZCBvZiB0aGUgZXZlbnQgY29tcGFyaW5nIHRyZWF0ZWQgc3ViamVjdHMgdG8gY29udHJvbCBzdWJqZWN0cy4NCg0KQW5kIGhlcmUncyB0aGUgY2hlY2sgb2YgdGhlIHByb3BvcnRpb25hbCBoYXphcmRzIGFzc3VtcHRpb24uLi4NCg0KYGBge3J9DQpjb3guenBoKGRyLm91dDMud3QzKTsgcGxvdChjb3guenBoKGRyLm91dDMud3QzKSwgdmFyPSJ0cmVhdGVkIikNCmBgYA0KDQojIFRhc2sgMTIuIFJlc3VsdHMNCg0KIyMgVHJlYXRtZW50IEVmZmVjdCBFc3RpbWF0ZXMNCg0KV2Ugbm93IGNhbiBidWlsZCB0aGUgdGFibGUgb2YgYWxsIG9mIHRoZSBvdXRjb21lIHJlc3VsdHMgd2UndmUgb2J0YWluZWQgaGVyZS4NCg0KRXN0LiBUcmVhdG1lbnQgRWZmZWN0ICg5NSUgQ0kpIHwgT3V0Y29tZSAxIChDb3N0IGRpZmYuKSB8IE91dGNvbWUgMiAoUmlzayBkaWZmLikgfCBPdXRjb21lIDIgKE9kZHMgUmF0aW8pIHwgT3V0Y29tZSAzIChSZWwuIEhSKQ0KLS0tLS0tLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogfCAtLS0tLS0tLS0tLTogDQpObyBjb3ZhcmlhdGUgYWRqdXN0bWVudCB8ICoqYHIgZGVjaW0ocmVzX3VuYWRqXzEkZXN0aW1hdGUsMilgKiogfCAqKmByIGRlY2ltKHJlc191bmFkal8yX3Jpc2tkaWZmJHJpc2suZGlmZiwgMylgKiogfCAqKmByIGRlY2ltKHJlc191bmFkal8yX29yJGVzdGltYXRlLCAyKWAqKiB8ICoqYHIgZGVjaW0ocmVzX3VuYWRqXzMkZXN0aW1hdGUsIDIpYCoqIA0KKHVuYWRqdXN0ZWQpIHwgKGByIGRlY2ltKHJlc191bmFkal8xJGNvbmYubG93LDIpYCwgYHIgZGVjaW0ocmVzX3VuYWRqXzEkY29uZi5oaWdoLDIpYCkgfCAoYHIgZGVjaW0ocmVzX3VuYWRqXzJfcmlza2RpZmYkY29uZi5sb3csIDMpYCwgYHIgZGVjaW0ocmVzX3VuYWRqXzJfcmlza2RpZmYkY29uZi5oaWdoLCAzKWApIHwgKGByIGRlY2ltKHJlc191bmFkal8yX29yJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHJlc191bmFkal8yX29yJGNvbmYuaGlnaCwgMilgKSB8IChgciBkZWNpbShyZXNfdW5hZGpfMyRjb25mLmxvdywgMilgLCBgciBkZWNpbShyZXNfdW5hZGpfMyRjb25mLmhpZ2gsIDIpYCkNCkFmdGVyIDE6MSBQUyBNYXRjaCB8ICoqYHIgZGVjaW0obWF0Y2gxLm91dDEkZXN0Lm5vYWRqLCAyKWAqKiB8ICoqYHIgZGVjaW0obWF0Y2gxX291dDIkZXN0Lm5vYWRqLCAzKWAqKiB8IE4vQSB8IE4vQSANCihgTWF0Y2hgOiBBdXRvbWF0ZWQpIHwgKGByIGRlY2ltKG1hdGNoMS5vdXQxJGVzdC5ub2FkaiAtIDEuOTYqbWF0Y2gxLm91dDEkc2Uuc3RhbmRhcmQsIDIpYCwgYHIgZGVjaW0obWF0Y2gxLm91dDEkZXN0Lm5vYWRqICsgMS45NiptYXRjaDEub3V0MSRzZS5zdGFuZGFyZCwgMilgKSB8IChgciBkZWNpbShtYXRjaDFfb3V0MiRlc3Qubm9hZGogLSAxLjk2Km1hdGNoMV9vdXQyJHNlLnN0YW5kYXJkLCAzKWAsIGByIGRlY2ltKG1hdGNoMV9vdXQyJGVzdC5ub2FkaiArIDEuOTYqbWF0Y2gxX291dDIkc2Uuc3RhbmRhcmQsIDMpYCkgfCBOL0EgfCBOL0ENCkFmdGVyIDE6MSBQUyBNYXRjaCB8ICoqYHIgZGVjaW0ocmVzX21hdGNoZWRfMSRlc3RpbWF0ZSwgMilgKiogfCBOL0EgfCAqKmByIGRlY2ltKGFkai5tLm91dDJfdGlkeSRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKGFkai5tLm91dDNfdGlkeSRlc3RpbWF0ZSwgMilgKioNCigiUmVncmVzc2lvbiIgTW9kZWxzKSB8IChgciBkZWNpbShyZXNfbWF0Y2hlZF8xJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHJlc19tYXRjaGVkXzEkY29uZi5oaWdoLCAyKWApIHwgTi9BIHwgKGByIGRlY2ltKGFkai5tLm91dDJfdGlkeSRjb25mLmxvdywgMilgLCBgciBkZWNpbShhZGoubS5vdXQyX3RpZHkkY29uZi5oaWdoLCAyKWApIHwgKGByIGRlY2ltKGFkai5tLm91dDNfdGlkeSRjb25mLmxvdywgMilgLCBgciBkZWNpbShhZGoubS5vdXQzX3RpZHkkY29uZi5oaWdoLCAyKWApDQpBZnRlciBQUyBTdWJjbGFzc2lmaWNhdGlvbiB8ICoqYHIgZGVjaW0oc3RyYXQucmVzdWx0MSRlc3RpbWF0ZSwgMilgKiogfCBOL0EgfCAqKmByIGRlY2ltKHN0cmF0LnJlc3VsdDIkZXN0aW1hdGUsIDIpYCoqIHwgKipgciBkZWNpbShzdHJhdC5yZXN1bHQzJGVzdGltYXRlLCAyKWAqKg0KKCJSZWdyZXNzaW9uIiBtb2RlbHMsIEFURSkgfCAoYHIgZGVjaW0oc3RyYXQucmVzdWx0MSRjb25mLmxvdywgMilgLCBgciBkZWNpbShzdHJhdC5yZXN1bHQxJGNvbmYuaGlnaCwgMilgKSB8IE4vQSB8IChgciBkZWNpbShzdHJhdC5yZXN1bHQyJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHN0cmF0LnJlc3VsdDEkY29uZi5oaWdoLCAyKWApIHwgKGByIGRlY2ltKHN0cmF0LnJlc3VsdDMkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oc3RyYXQucmVzdWx0MyRjb25mLmhpZ2gsIDIpYCkNCkFUVCBXZWlnaHRpbmcgfCAqKmByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMSRlc3RpbWF0ZSwgMilgKiogfCBOL0EgfCAqKmByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMiRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMyRlc3RpbWF0ZSwgMilgKioNCihBVFQpIHwgKGByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMSRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF9hdHRfcmVzdWx0czEkY29uZi5oaWdoLCAyKWApIHwgTi9BIHwgKGByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMiRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF9hdHRfcmVzdWx0czIkY29uZi5oaWdoLCAyKWApIHwgKGByIGRlY2ltKHd0X2F0dF9yZXN1bHRzMyRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF9hdHRfcmVzdWx0czMkY29uZi5oaWdoLCAyKWApDQpBVEUgV2VpZ2h0aW5nIHwgKipgciBkZWNpbSh3dF9hdGVfcmVzdWx0czEkZXN0aW1hdGUsIDIpYCoqIHwgTi9BIHwgKipgciBkZWNpbSh3dF9hdGVfcmVzdWx0czIkZXN0aW1hdGUsIDIpYCoqIHwgKipgciBkZWNpbSh3dF9hdGVfcmVzdWx0czMkZXN0aW1hdGUsIDIpYCoqDQooQVRFKSB8IChgciBkZWNpbSh3dF9hdGVfcmVzdWx0czEkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0od3RfYXRlX3Jlc3VsdHMxJGNvbmYuaGlnaCwgMilgKSB8IE4vQSB8IChgciBkZWNpbSh3dF9hdGVfcmVzdWx0czIkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0od3RfYXRlX3Jlc3VsdHMyJGNvbmYuaGlnaCwgMilgKSB8IChgciBkZWNpbSh3dF9hdGVfcmVzdWx0czMkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0od3RfYXRlX3Jlc3VsdHMzJGNvbmYuaGlnaCwgMilgKQ0KYHR3YW5nYCBBVFQgd2VpZ2h0cyB8ICoqYHIgZGVjaW0od3RfdHdhbmdhdHRfcmVzdWx0czEkZXN0aW1hdGUsIDIpYCoqIHwgTi9BIHwgKipgciBkZWNpbSh3dF90d2FuZ2F0dF9yZXN1bHRzMiRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKHd0X3R3YW5nYXR0X3Jlc3VsdHMzJGVzdGltYXRlLCAyKWAqKg0KKEFUVCkgfCAoYHIgZGVjaW0od3RfdHdhbmdhdHRfcmVzdWx0czEkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0od3RfdHdhbmdhdHRfcmVzdWx0czEkY29uZi5oaWdoLCAyKWApIHwgTi9BIHwgKGByIGRlY2ltKHd0X3R3YW5nYXR0X3Jlc3VsdHMyJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKHd0X3R3YW5nYXR0X3Jlc3VsdHMyJGNvbmYuaGlnaCwgMilgKSB8IChgciBkZWNpbSh3dF90d2FuZ2F0dF9yZXN1bHRzMyRjb25mLmxvdywgMilgLCBgciBkZWNpbSh3dF90d2FuZ2F0dF9yZXN1bHRzMyRjb25mLmhpZ2gsIDIpYCkNCkRpcmVjdCBBZGp1c3RtZW50IHwgKipgciBkZWNpbShhZGpfb3V0MSRlc3RpbWF0ZSwgMilgKiogfCBOL0EgfCAqKmByIGRlY2ltKGFkal9vdXQyJGVzdGltYXRlLCAyKWAqKiB8ICoqYHIgZGVjaW0oYWRqX291dDMkZXN0aW1hdGUsIDIpYCoqDQood2l0aCBgbGlucHNgLCBBVFQpIHwgKGByIGRlY2ltKGFkal9vdXQxJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKGFkal9vdXQxJGNvbmYuaGlnaCwgMilgKSB8IE4vQSB8IChgciBkZWNpbShhZGpfb3V0MiRjb25mLmxvdywgMilgLCBgciBkZWNpbShhZGpfb3V0MiRjb25mLmhpZ2gsIDIpYCkgfCAoYHIgZGVjaW0oYWRqX291dDMkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oYWRqX291dDMkY29uZi5oaWdoLCAyKWApDQpEb3VibGUgUm9idXN0ICAgICB8ICoqYHIgZGVjaW0oZHJfYXR0X291dDEkZXN0aW1hdGUsIDIpYCoqIHwgTi9BIHwgKipgciBkZWNpbShkcl9hdHRfb3V0MiRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKGRyX2F0dF9vdXQzJGVzdGltYXRlLCAyKWAqKiANCihBVFQgd3RzICsgYWRqLikgIHwgKGByIGRlY2ltKGRyX2F0dF9vdXQxJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKGRyX2F0dF9vdXQxJGNvbmYuaGlnaCwgMilgKSB8IE4vQSB8IChgciBkZWNpbShkcl9hdHRfb3V0MiRjb25mLmxvdywgMilgLCBgciBkZWNpbShkcl9hdHRfb3V0MiRjb25mLmhpZ2gsIDIpYCkgfCAoYHIgZGVjaW0oZHJfYXR0X291dDMkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oZHJfYXR0X291dDMkY29uZi5oaWdoLCAyKWApDQpEb3VibGUgUm9idXN0ICAgICB8ICoqYHIgZGVjaW0oZHJfYXRlX291dDEkZXN0aW1hdGUsIDIpYCoqIHwgTi9BIHwgKipgciBkZWNpbShkcl9hdGVfb3V0MiRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKGRyX2F0ZV9vdXQzJGVzdGltYXRlLCAyKWAqKg0KKEFURSB3dHMgKyBhZGouKSAgfCAoYHIgZGVjaW0oZHJfYXRlX291dDEkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oZHJfYXRlX291dDEkY29uZi5oaWdoLCAyKWApIHwgTi9BIHwgKGByIGRlY2ltKGRyX2F0ZV9vdXQyJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKGRyX2F0ZV9vdXQyJGNvbmYuaGlnaCwgMilgKSB8IChgciBkZWNpbShkcl9hdGVfb3V0MyRjb25mLmxvdywgMilgLCBgciBkZWNpbShkcl9hdGVfb3V0MyRjb25mLmhpZ2gsIDIpYCkNCkRvdWJsZSBSb2J1c3QgICAgIHwgKipgciBkZWNpbShkcl90d2FuZ2F0dF9vdXQxJGVzdGltYXRlLCAyKWAqKiB8IE4vQSB8ICoqYHIgZGVjaW0oZHJfdHdhbmdhdHRfb3V0MiRlc3RpbWF0ZSwgMilgKiogfCAqKmByIGRlY2ltKGRyX3R3YW5nYXR0X291dDMkZXN0aW1hdGUsIDIpYCoqDQooYHR3YW5nYCBBVFQgd3RzICsgYWRqLikgIHwgKGByIGRlY2ltKGRyX3R3YW5nYXR0X291dDEkY29uZi5sb3csIDIpYCwgYHIgZGVjaW0oZHJfdHdhbmdhdHRfb3V0MSRjb25mLmhpZ2gsIDIpYCkgfCBOL0EgfCAoYHIgZGVjaW0oZHJfdHdhbmdhdHRfb3V0MiRjb25mLmxvdywgMilgLCBgciBkZWNpbShkcl90d2FuZ2F0dF9vdXQyJGNvbmYuaGlnaCwgMilgKSB8IChgciBkZWNpbShkcl90d2FuZ2F0dF9vdXQzJGNvbmYubG93LCAyKWAsIGByIGRlY2ltKGRyX3R3YW5nYXR0X291dDMkY29uZi5oaWdoLCAyKWApDQoNClNvLCB3aXRoIHRoZSBleGNlcHRpb24gb2YgdGhlIHN1YmNsYXNzaWZpY2F0aW9uIGFwcHJvYWNoICh3aGljaCB3YXMgcHJvYmxlbWF0aWMgaW4gdGVybXMgb2Ygb2JzZXJ2ZWQgY292YXJpYXRlIGJhbGFuY2UpIHdlIG9ic2VydmUgc2lnbmlmaWNhbnQgcmVzdWx0cyAoaW5kaWNhdGluZyBoaWdoZXIgY29zdHMgd2l0aCB0aGUgdHJlYXRtZW50LCBhbmQgaGlnaGVyIGxpa2VsaWhvb2Qgb2YgZXhwZXJpZW5jaW5nIHRoZSBldmVudCwgYW5kIGluY3JlYXNlZCBoYXphcmQgb2YgZXZlbnQgb2NjdXJyZW5jZSkgZm9yIGV2ZXJ5IGFkanVzdG1lbnQgYXBwcm9hY2guDQoNCiMjIFF1YWxpdHkgb2YgQmFsYW5jZTogU3RhbmRhcmRpemVkIERpZmZlcmVuY2VzIGFuZCBWYXJpYW5jZSBSYXRpb3MNCg0KV2UncmUgbG9va2luZyBhdCB0aGUgYmFsYW5jZSBhY3Jvc3MgdGhlIGZvbGxvd2luZyAxMCBjb3ZhcmlhdGVzIGFuZCB0cmFuc2Zvcm1hdGlvbnMgaGVyZTogYGNvdkEsIGNvdkIsIGNvdkMsIGNvdkQsIGNvdkUsIGNvdkZbbWlkZGxlXSwgY292RltoaWdoXSwgQSBzcXVhcmVkLCBCeENgIGFuZCBgQnhEYCwgYXMgd2VsbCBhcyB0aGUgcmF3IGFuZCBsaW5lYXIgcHJvcGVuc2l0eSBzY29yZXMgLi4uDQoNCkFwcHJvYWNoIHwgU3RhbmRhcmRpemVkIERpZmZzIHwgVmFyaWFuY2UgUmF0aW9zDQotLS0tLS0tLTp8IC0tOiB8IC0tOg0KTW9zdCBEZXNpcmFibGUgVmFsdWVzIHwJQmV0d2VlbiAtMTAgYW5kICsxMCB8CUJldHdlZW4gMC44IGFuZCAxLjI1DQpObyBBZGp1c3RtZW50cyB8IGByIGRlY2ltKG1pbihtYXRjaF9zemQkcHJlLnN6ZCksMClgIHRvIGByIGRlY2ltKG1heChtYXRjaF9zemQkcHJlLnN6ZCksMClgIHwgYHIgZGVjaW0obWluKG1hdGNoX3ZyYXQkcHJlLnZyYXRpbyksMilgIHRvIGByIGRlY2ltKG1heChtYXRjaF92cmF0JHByZS52cmF0aW8pLDIpYA0KMToxIFByb3BlbnNpdHkgTWF0Y2hpbmcgfCBgciBkZWNpbShtaW4obWF0Y2hfc3pkJHBvc3Quc3pkKSwwKWAgdG8gYHIgZGVjaW0obWF4KG1hdGNoX3N6ZCRwb3N0LnN6ZCksMClgIHwgYHIgZGVjaW0obWluKG1hdGNoX3ZyYXQkcG9zdC52cmF0aW8pLDIpYCB0byBgciBkZWNpbShtYXgobWF0Y2hfdnJhdCRwb3N0LnZyYXRpbyksMilgDQpTdWJjbGFzc2lmaWNhdGlvbiBRdWludGlsZSAxIHwgYHIgZGVjaW0obWluKGQucTEpLDApYCB0byBgciBkZWNpbShtYXgoZC5xMSksMClgIHwgbm90IGNhbGN1bGF0ZWQgYWJvdmUNClF1aW50aWxlIDIgfCBgciBkZWNpbShtaW4oZC5xMiksMClgIHRvIGByIGRlY2ltKG1heChkLnEyKSwwKWAgfCBub3QgY2FsY3VsYXRlZCBhYm92ZQ0KUXVpbnRpbGUgMyB8IGByIGRlY2ltKG1pbihkLnEzKSwwKWAgdG8gYHIgZGVjaW0obWF4KGQucTMpLDApYCB8IG5vdCBjYWxjdWxhdGVkIGFib3ZlDQpRdWludGlsZSA0IHwgYHIgZGVjaW0obWluKGQucTQpLDApYCB0byBgciBkZWNpbShtYXgoZC5xNCksMClgIHwgbm90IGNhbGN1bGF0ZWQgYWJvdmUNClF1aW50aWxlIDUgfCBgciBkZWNpbShtaW4oZC5xNSksMClgIHRvIGByIGRlY2ltKG1heChkLnE1KSwwKWAgfCBub3QgY2FsY3VsYXRlZCBhYm92ZQ0KUHJvcGVuc2l0eSBXZWlnaHRpbmcsIEFUVCB8IGByIGJhbGFuY2UuYXR0LndlaWdodHMgJT4lIGZpbHRlcih0aW1pbmcgPT0gIkFUVC53ZWlnaHRlZCIpICU+JSBzdW1tYXJpemUobWluKHN6ZCkpICU+JSByb3VuZCguLCAwKWAgdG8gYHIgYmFsYW5jZS5hdHQud2VpZ2h0cyAlPiUgZmlsdGVyKHRpbWluZyA9PSAiQVRULndlaWdodGVkIikgJT4lIHN1bW1hcml6ZShtYXgoc3pkKSkgJT4lIHJvdW5kKC4sIDApYCB8IGByIGRlY2ltKG1pbihiYWwuYWZ0ZXIud3RzMVtbMV1dJHR4LnNkXjIvYmFsLmFmdGVyLnd0czFbWzFdXSRjdC5zZF4yKSwyKWAgdG8gYHIgZGVjaW0obWF4KGJhbC5hZnRlci53dHMxW1sxXV0kdHguc2ReMi9iYWwuYWZ0ZXIud3RzMVtbMV1dJGN0LnNkXjIpLDIpYA0KUHJvcGVuc2l0eSBXZWlnaHRpbmcsIEFURSB8IGByIGJhbGFuY2UuYXRlLndlaWdodHMgJT4lIGZpbHRlcih0aW1pbmcgPT0gIkFURS53ZWlnaHRlZCIpICU+JSBzdW1tYXJpemUobWluKHN6ZCkpICU+JSByb3VuZCguLDApYCB0byBgciBiYWxhbmNlLmF0ZS53ZWlnaHRzICU+JSBmaWx0ZXIodGltaW5nID09ICJBVEUud2VpZ2h0ZWQiKSAlPiUgc3VtbWFyaXplKG1heChzemQpKSAlPiUgcm91bmQoLiwwKWAgfCBgciBkZWNpbShtaW4oYmFsLmFmdGVyLnd0czJbWzFdXSR0eC5zZF4yL2JhbC5hZnRlci53dHMyW1sxXV0kY3Quc2ReMiksMilgIHRvIGByIGRlY2ltKG1heChiYWwuYWZ0ZXIud3RzMltbMV1dJHR4LnNkXjIvYmFsLmFmdGVyLnd0czJbWzFdXSRjdC5zZF4yKSwyKWANCg0KIyMgUXVhbGl0eSBvZiBCYWxhbmNlOiBSdWJpbidzIFJ1bGVzDQoNCkFwcHJvYWNoIHwgUnViaW4gMSB8IFJ1YmluIDIgfCBSdWJpbiAzDQotLTogfCAtLTogfCAtLTogfCAtLToNCiJQYXNzIiBSYW5nZSwgcGVyIFJ1YmluIHwJMCB0byA1MCB8CTAuNSB0byAyLjAgfCAwLjUgdG8gMi4wDQpObyBBZGp1c3RtZW50cwl8IGByIGRlY2ltKHJ1YmluMS51bmFkaiwgMSlgIHwgYHIgZGVjaW0ocnViaW4yLnVuYWRqLCAyKWAgfCBgciBkZWNpbShtaW4ocnViaW4zLnVuYWRqJHJlc2lkLnZhci5yYXRpbyksMilgIHRvIGByIGRlY2ltKG1heChydWJpbjMudW5hZGokcmVzaWQudmFyLnJhdGlvKSwyKWANCjE6MSBQcm9wZW5zaXR5IE1hdGNoaW5nIHwgYHIgZGVjaW0ocnViaW4xLm1hdGNoLCAxKWAgfAlgciBkZWNpbShydWJpbjIubWF0Y2gsIDIpYCB8IGByIGRlY2ltKG1pbihydWJpbjMubWF0Y2hlZCRyZXNpZC52YXIucmF0aW8pLDIpYCB0byBgciBkZWNpbShtYXgocnViaW4zLm1hdGNoZWQkcmVzaWQudmFyLnJhdGlvKSwyKWANClN1YmNsYXNzaWZpY2F0aW9uOiBRdWludGlsZSAxCXwgYHIgZGVjaW0ocnViaW4xLnExLCAxKWAgfAlgciBkZWNpbShydWJpbjIucTEsIDIpYHwgYHIgZGVjaW0obWluKHJ1YmluMy5xMSRyZXNpZC52YXIucmF0aW8pLDIpYCB0byBgciBkZWNpbShtYXgocnViaW4zLnExJHJlc2lkLnZhci5yYXRpbyksMilgDQpRdWludGlsZSAyIHwgYHIgZGVjaW0ocnViaW4xLnEyLCAxKWAgfAlgciBkZWNpbShydWJpbjIucTIsIDIpYCB8IGByIGRlY2ltKG1pbihydWJpbjMucTIkcmVzaWQudmFyLnJhdGlvKSwyKWAgdG8gYHIgZGVjaW0obWF4KHJ1YmluMy5xMiRyZXNpZC52YXIucmF0aW8pLDIpYA0KUXVpbnRpbGUgMyB8IGByIGRlY2ltKHJ1YmluMS5xMywgMSlgIHwgYHIgZGVjaW0ocnViaW4yLnEzLCAyKWAgfCBgciBkZWNpbShtaW4ocnViaW4zLnEzJHJlc2lkLnZhci5yYXRpbyksMilgIHRvIGByIGRlY2ltKG1heChydWJpbjMucTMkcmVzaWQudmFyLnJhdGlvKSwyKWANClF1aW50aWxlIDQgfCBgciBkZWNpbShydWJpbjEucTQsIDEpYCB8IGByIGRlY2ltKHJ1YmluMi5xNCwgMilgIHwgYHIgZGVjaW0obWluKHJ1YmluMy5xNCRyZXNpZC52YXIucmF0aW8pLDIpYCB0byBgciBkZWNpbShtYXgocnViaW4zLnE0JHJlc2lkLnZhci5yYXRpbyksMilgDQpRdWludGlsZSA1IHwgYHIgZGVjaW0ocnViaW4xLnE1LCAxKWAgfCBgciBkZWNpbShydWJpbjIucTUsIDIpYCB8IGByIGRlY2ltKG1pbihydWJpbjMucTUkcmVzaWQudmFyLnJhdGlvKSwyKWAgdG8gYHIgZGVjaW0obWF4KHJ1YmluMy5xNSRyZXNpZC52YXIucmF0aW8pLDIpYA0KUHJvcGVuc2l0eSBXZWlnaHRpbmcsIEFUVCB8CWByIGJhbGFuY2UuYXR0LndlaWdodHMgJT4lIGZpbHRlcihuYW1lcyA9PSAibGlucHMiLCB0aW1pbmcgPT0gIkFUVC53ZWlnaHRlZCIpICU+JSBzZWxlY3Qoc3pkKWAgfCBgciBkZWNpbSgoYmFsLmFmdGVyLnd0czFbWzFdXSR0eC5zZFsxM11eMikvKGJhbC5hZnRlci53dHMxW1sxXV0kY3Quc2RbMTNdXjIpLDIpYCB8IE5vdCBldmFsdWF0ZWQNClByb3BlbnNpdHkgV2VpZ2h0aW5nLCBBVEUgfCBgciBiYWxhbmNlLmF0ZS53ZWlnaHRzICU+JSBmaWx0ZXIobmFtZXMgPT0gImxpbnBzIiwgdGltaW5nID09ICJBVEUud2VpZ2h0ZWQiKSAlPiUgc2VsZWN0KHN6ZClgICB8IGByIGRlY2ltKChiYWwuYWZ0ZXIud3RzMltbMV1dJHR4LnNkWzEzXV4yKS8oYmFsLmFmdGVyLnd0czJbWzFdXSRjdC5zZFsxM11eMiksMilgIHwgTm90IGV2YWx1YXRlZA0KDQpDbGVhcmx5LCB0aGUgbWF0Y2hpbmcgYW5kIHByb3BlbnNpdHkgd2VpZ2h0aW5nIHNob3cgaW1wcm92ZW1lbnQgb3ZlciB0aGUgaW5pdGlhbCAobm8gYWRqdXN0bWVudHMpIHJlc3VsdHMsIGFsdGhvdWdoIG5laXRoZXIgaXMgY29tcGxldGVseSBzYXRpc2ZhY3RvcnkgaW4gdGVybXMgb2YgYWxsIGNvdmFyaWF0ZXMuIEluIHByYWN0aWNlLCBJIHdvdWxkIGJlIGNvbWZvcnRhYmxlIHdpdGggZWl0aGVyIGEgMToxIG1hdGNoIG9yIGEgd2VpZ2h0aW5nIGFwcHJvYWNoLCBJIHRoaW5rLiBJdCBpc24ndCBsaWtlbHkgdGhhdCB0aGUgc3ViY2xhc3NpZmljYXRpb24gd2lsbCBnZXQgdXMgYW55d2hlcmUgdXNlZnVsIGluIHRlcm1zIG9mIGJhbGFuY2UuIFJ1YmluJ3MgUnVsZSAzIGNvdWxkIGFsc28gYmUgYXBwbGllZCBhZnRlciB3ZWlnaHRpbmcgb24gdGhlIHByb3BlbnNpdHkgc2NvcmUuDQoNCiMgV2hhdCBpcyBhIFNlbnNpdGl2aXR5IEFuYWx5c2lzIGZvciBNYXRjaGVkIFNhbXBsZXM/DQoNCldlJ2xsIHN0dWR5IGEgZm9ybWFsIHNlbnNpdGl2aXR5IGFuYWx5c2lzIGFwcHJvYWNoIGZvciAqKm1hdGNoZWQqKiBzYW1wbGVzLiBOb3RlIHdlbGwgdGhhdCB0aGlzIHNwZWNpZmljIGFwcHJvYWNoIGlzIGFwcHJvcHJpYXRlIG9ubHkgd2hlbiB3ZSBoYXZlIA0KDQoxLiBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgY29uY2x1c2lvbiANCjIuIGZyb20gYSBtYXRjaGVkIHNhbXBsZXMgYW5hbHlzaXMgdXNpbmcgdGhlIHByb3BlbnNpdHkgc2NvcmUuDQoNCiMjIEdvYWwgb2YgYSBGb3JtYWwgU2Vuc2l0aXZpdHkgQW5hbHlzaXMgZm9yIE1hdGNoZWQgU2FtcGxlcw0KDQpUbyByZXBsYWNlIGEgZ2VuZXJhbCBxdWFsaXRhdGl2ZSBzdGF0ZW1lbnQgdGhhdCBhcHBsaWVzIGluIGFsbCBvYnNlcnZhdGlvbmFsIHN0dWRpZXMsIGxpa2UgLi4uDQoNCj4gdGhlIGFzc29jaWF0aW9uIHdlIG9ic2VydmUgYmV0d2VlbiB0cmVhdG1lbnQgYW5kIG91dGNvbWUgZG9lcyBub3QgaW1wbHkgY2F1c2F0aW9uDQoNCm9yIA0KDQo+IGhpZGRlbiBiaWFzZXMgY2FuIGV4cGxhaW4gb2JzZXJ2ZWQgYXNzb2NpYXRpb25zDQoNCi4uLiB3aXRoIGEgcXVhbnRpdGF0aXZlIHN0YXRlbWVudCB0aGF0IGlzIHNwZWNpZmljIHRvIHdoYXQgaXMgb2JzZXJ2ZWQgaW4gYSBwYXJ0aWN1bGFyIHN0dWR5LCBzdWNoIGFzIC4uLg0KDQo+IHRvIGV4cGxhaW4gdGhlIGFzc29jaWF0aW9uIHNlZW4gaW4gYSBwYXJ0aWN1bGFyIHN0dWR5LCBvbmUgd291bGQgbmVlZCBhIGhpZGRlbiBiaWFzDQpvZiBhIHBhcnRpY3VsYXIgbWFnbml0dWRlLg0KDQpJZiB0aGUgYXNzb2NpYXRpb24gaXMgc3Ryb25nLCB0aGUgaGlkZGVuIGJpYXMgbmVlZGVkIHRvIGV4cGxhaW4gaXQgd291bGQgYmUgbGFyZ2UuDQoNCi0gSWYgYSBzdHVkeSBpcyBmcmVlIG9mIGhpZGRlbiBiaWFzIChtYWluIGV4YW1wbGU6IGEgY2FyZWZ1bGx5IHJhbmRvbWl6ZWQgdHJpYWwpLCB0aGlzIG1lYW5zIHRoYXQgYW55IHR3byB1bml0cyAocGF0aWVudHMsIHN1YmplY3RzLCB3aGF0ZXZlcikgdGhhdCBhcHBlYXIgc2ltaWxhciBpbiB0ZXJtcyBvZiB0aGVpciBvYnNlcnZlZCBjb3ZhcmlhdGVzIGFjdHVhbGx5IGhhdmUgdGhlIHNhbWUgY2hhbmNlIG9mIGFzc2lnbm1lbnQgdG8gdHJlYXRtZW50Lg0KLSBUaGVyZSBpcyAqaGlkZGVuIGJpYXMqIGlmIHR3byB1bml0cyB3aXRoIHRoZSBzYW1lIG9ic2VydmVkIGNvdmFyaWF0ZXMgaGF2ZSBkaWZmZXJlbnQgY2hhbmNlcyBvZiByZWNlaXZpbmcgdGhlIHRyZWF0bWVudC4NCg0KQSAqKnNlbnNpdGl2aXR5IGFuYWx5c2lzKiogYXNrczogSG93IHdvdWxkIGluZmVyZW5jZXMgYWJvdXQgdHJlYXRtZW50IGVmZmVjdHMgYmUgYWx0ZXJlZCBieSBoaWRkZW4gYmlhc2VzIG9mIHZhcmlvdXMgbWFnbml0dWRlcz8gIEhvdyBsYXJnZSB3b3VsZCB0aGVzZSBkaWZmZXJlbmNlcyBoYXZlIHRvIGJlIHRvIGFsdGVyIHRoZSBxdWFsaXRhdGl2ZSBjb25jbHVzaW9ucyBvZiB0aGUgc3R1ZHk/DQoNClRoZSBtZXRob2RzIGZvciBidWlsZGluZyBzdWNoIHNlbnNpdGl2aXR5IGFuYWx5c2VzIGFyZSBsYXJnZWx5IGR1ZSB0byBQYXVsIFJvc2VuYmF1bSwgYW5kIGFzIGEgcmVzdWx0IHRoZSBtZXRob2RzIGFyZSBzb21ldGltZXMgcmVmZXJyZWQgdG8gYXMgKipSb3NlbmJhdW0gYm91bmRzKiouDQoNCiMjIFRoZSBTZW5zaXRpdml0eSBQYXJhbWV0ZXIsICRcR2FtbWEkDQoNClN1cHBvc2Ugd2UgaGF2ZSB0d28gdW5pdHMgKHN1YmplY3RzLCBwYXRpZW50cyksIHNheSwgJGokIGFuZCAkayQsIHdpdGggdGhlIHNhbWUgb2JzZXJ2ZWQgY292YXJpYXRlIHZhbHVlcyAqKngqKiBidXQgZGlmZmVyZW50IHByb2JhYmlsaXRpZXMgJHAkIG9mIHRyZWF0bWVudCBhc3NpZ25tZW50IChwb3NzaWJseSBkdWUgdG8gc29tZSB1bm9ic2VydmVkIGNvdmFyaWF0ZSksIHNvIHRoYXQgKip4KiokX2okID0gKip4KiokX2skIGJ1dCB0aGF0IHBvc3NpYmx5ICRwX2ogXG5lcSBwX2skLg0KDQpVbml0cyAkaiQgYW5kICRrJCBtaWdodCBiZSAqbWF0Y2hlZCogdG8gZm9ybSBhIG1hdGNoZWQgcGFpciBpbiBvdXIgYXR0ZW1wdCB0byBjb250cm9sIG92ZXJ0IGJpYXMgZHVlIHRvIHRoZSBjb3ZhcmlhdGVzICoqeCoqLg0KDQotIFRoZSBvZGRzIHRoYXQgdW5pdHMgJGokIGFuZCAkayQgcmVjZWl2ZSB0aGUgdHJlYXRtZW50IGFyZSwgcmVzcGVjdGl2ZWx5LCAkXGZyYWN7cF9qfXsxIC0gcF9qfSQgYW5kICRcZnJhY3twX2t9ezEgLSBwX2t9JCwgYW5kIHRoZSBvZGRzIHJhdGlvIGlzIHRodXMgdGhlIHJhdGlvIG9mIHRoZXNlIG9kZHMuDQoNCkltYWdpbmUgdGhhdCB3ZSBrbmV3IHRoYXQgdGhpcyBvZGRzIHJhdGlvIGZvciB1bml0cyB3aXRoIHRoZSBzYW1lICoqeCoqIHdhcyBhdCBtb3N0IHNvbWUgbnVtYmVyICRcR2FtbWEkLCBzbyB0aGF0ICRcR2FtbWEgXGdlcSAxJC4gVGhhdCBpcywNCiANCiQkDQpcZnJhY3sxfXtcR2FtbWF9IFxsZXEgXGZyYWN7cF9qKDEgLSBwX2opfXtwX2soMSAtIHBfayl9IFxsZXEgXEdhbW1hDQokJA0KDQpXZSBjYWxsICRcR2FtbWEkIHRoZSAqKnNlbnNpdGl2aXR5IHBhcmFtZXRlcioqLCBhbmQgaXQgaXMgdGhlIGJhc2lzIGZvciBvdXIgc2Vuc2l0aXZpdHkgYW5hbHlzZXMuICANCg0KLSBJZiAkXEdhbW1hID0gMSQsIHRoZW4gJHBfaiA9IHBfayQgd2hlbmV2ZXIgKip4KiokX2okID0gKip4KiokX2skLCBzbyB0aGUgc3R1ZHkgd291bGQgYmUgZnJlZSBvZiBoaWRkZW4gYmlhcywgYW5kIHN0YW5kYXJkIHN0YXRpc3RpY2FsIG1ldGhvZHMgZGVzaWduZWQgZm9yIHJhbmRvbWl6ZWQgdHJpYWxzIHdvdWxkIGFwcGx5Lg0KDQpJZiAkXEdhbW1hID0gMiQsIHRoZW4gdHdvIHVuaXRzIHdobyBhcHBlYXIgc2ltaWxhciBpbiB0aGF0IHRoZXkgaGF2ZSB0aGUgc2FtZSBzZXQgb2Ygb2JzZXJ2ZWQgY292YXJpYXRlcyAqKngqKiwgY291bGQgZGlmZmVyIGluIHRoZWlyIG9kZHMgb2YgcmVjZWl2aW5nIHRoZSB0cmVhdG1lbnQgYnkgYXMgbXVjaCBhcyBhIGZhY3RvciBvZiAyLCBzbyB0aGF0IG9uZSBjb3VsZCBiZSB0d2ljZSBhcyBsaWtlbHkgYXMgdGhlIG90aGVyIHRvIHJlY2VpdmUgdGhlIHRyZWF0bWVudC4NCg0KU28gJFxHYW1tYSQgaXMgYSB2YWx1ZSBiZXR3ZWVuIDEgYW5kICRcaW5mdHkkIHdoZXJlIHRoZSBzaXplIG9mICRcR2FtbWEkIGluZGljYXRlcyB0aGUgZGVncmVlIG9mIGEgZGVwYXJ0dXJlIGZyb20gYSBzdHVkeSBmcmVlIG9mIGhpZGRlbiBiaWFzLg0KDQojIyBJbnRlcnByZXRpbmcgdGhlIFNlbnNpdGl2aXR5IFBhcmFtZXRlciwgJFxHYW1tYSQNCg0KQWdhaW4sICRcR2FtbWEkIGlzIGEgbWVhc3VyZSBvZiB0aGUgZGVncmVlIG9mIGRlcGFydHVyZSBmcm9tIGEgc3R1ZHkgdGhhdCBpcyBmcmVlIG9mIGhpZGRlbiBiaWFzLiAgDQoNCkEgc2Vuc2l0aXZpdHkgYW5hbHlzaXMgd2lsbCBjb25zaWRlciBwb3NzaWJsZSB2YWx1ZXMgb2YgJFxHYW1tYSQgYW5kIHNob3cgaG93IHRoZSBpbmZlcmVuY2UgZm9yIG91ciBvdXRjb21lcyBtaWdodCBjaGFuZ2UgdW5kZXIgZGlmZmVyZW50IGxldmVscyBvZiBoaWRkZW4gYmlhcywgYXMgaW5kZXhlZCBieSAkXEdhbW1hJC4gIA0KDQotIEEgc3R1ZHkgaXMgKnNlbnNpdGl2ZSogaWYgdmFsdWVzIG9mICRcR2FtbWEkIGNsb3NlIHRvIDEgY291bGQgbGVhZCB0byBpbmZlcmVuY2VzIHRoYXQgYXJlIHZlcnkgZGlmZmVyZW50IGZyb20gdGhvc2Ugb2J0YWluZWQgYXNzdW1pbmcgdGhlIHN0dWR5IGlzIGZyZWUgb2YgaGlkZGVuIGJpYXMuDQotIEEgc3R1ZHkgaXMgKmluc2Vuc2l0aXZlKiAoYSBnb29kIHRoaW5nIGhlcmUpIGlmIGV4dHJlbWUgdmFsdWVzIG9mICRcR2FtbWEkIGFyZSByZXF1aXJlZCB0byBhbHRlciB0aGUgaW5mZXJlbmNlLg0KDQpXaGVuIHdlIHBlcmZvcm0gdGhpcyBzb3J0IG9mIHNlbnNpdGl2aXR5IGFuYWx5c2lzLCB3ZSB3aWxsIHNwZWNpZnkgZGlmZmVyZW50IGxldmVscyBvZiBoaWRkZW4gYmlhcyAoZGlmZmVyZW50ICRcR2FtbWEkIHZhbHVlcykgYW5kIHNlZSBob3cgbGFyZ2UgYSAkXEdhbW1hJCB3ZSBjYW4gaGF2ZSB3aGlsZSBzdGlsbCByZXRhaW5pbmcgdGhlIGZ1bmRhbWVudGFsIGNvbmNsdXNpb25zIG9mIHRoZSBtYXRjaGVkIG91dGNvbWVzIGFuYWx5c2lzLg0KDQojIFRhc2sgMTMuIFNlbnNpdGl2aXR5IEFuYWx5c2lzIGZvciBNYXRjaGVkIFNhbXBsZXMsIE91dGNvbWUgMSwgdXNpbmcgYHJib3VuZHNgDQoNCkluIG91ciBtYXRjaGVkIHNhbXBsZSBhbmFseXNpcywgZm9yIG91dGNvbWUgMSAoY29zdCkgaW4gdGhlIHRveSBleGFtcGxlLCB3ZSBzYXcgYSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IHJlc3VsdC4gQSBmb3JtYWwgKnNlbnNpdGl2aXR5IGFuYWx5c2lzKiBpcyBjYWxsZWQgZm9yLCBhcyBhIHJlc3VsdCwgYW5kIHdlIHdpbGwgYWNjb21wbGlzaCBvbmUgZm9yIHRoaXMgcXVhbnRpdGF0aXZlIG91dGNvbWUsIHVzaW5nIHRoZSBgcmJvdW5kc2AgcGFja2FnZS4NCg0KVGhlIGByYm91bmRzYCBwYWNrYWdlIGlzIGRlc2lnbmVkIHRvIHdvcmsgd2l0aCB0aGUgb3V0cHV0IGZyb20gYE1hdGNoaW5nYCwgYW5kIGNhbiBjYWxjdWxhdGUgUm9zZW5iYXVtIHNlbnNpdGl2aXR5IGJvdW5kcyBmb3IgdGhlIHRyZWF0bWVudCBlZmZlY3QsIHdoaWNoIGhlbHAgdXMgdW5kZXJzdGFuZCB0aGUgaW1wYWN0IG9mIGhpZGRlbiBiaWFzIG5lZWRlZCB0byBpbnZhbGlkYXRlIG91ciBzaWduaWZpY2FudCBjb25jbHVzaW9ucyBmcm9tIHRoZSBtYXRjaGVkIHNhbXBsZXMgYW5hbHlzaXMuDQoNCiMjIFJvc2VuYmF1bSBCb3VuZHMgZm9yIHRoZSBXaWxjb3hvbiBTaWduZWQgUmFuayB0ZXN0IChRdWFudGl0YXRpdmUgb3V0Y29tZSkNCg0KV2UgaGF2ZSBhbHJlYWR5IHVzZWQgdGhlIE1hdGNoIGZ1bmN0aW9uIGZyb20gdGhlIE1hdGNoaW5nIHBhY2thZ2UgdG8gZGV2ZWxvcCBhIG1hdGNoZWQgc2FtcGxlLiBHaXZlbiB0aGlzLCB3ZSBuZWVkIG9ubHkgcnVuIHRoZSBgcHNlbnNgIGZ1bmN0aW9uIGZyb20gdGhlIGByYm91bmRzYCBwYWNrYWdlIHRvIG9idGFpbiBzZW5zaXRpdml0eSByZXN1bHRzLg0KDQpgYGB7cn0NClggPC0gdG95JGxpbnBzICMjIG1hdGNoaW5nIG9uIHRoZSBsaW5lYXIgcHJvcGVuc2l0eSBzY29yZQ0KVHIgPC0gYXMubG9naWNhbCh0b3kkdHJlYXRlZCkNClkgPC0gdG95JG91dDEuY29zdA0KbWF0Y2gxIDwtIE1hdGNoKFRyPVRyLCBYPVgsIFkgPSBZLCBNID0gMSwgcmVwbGFjZT1GQUxTRSwgdGllcz1GQUxTRSkNCnN1bW1hcnkobWF0Y2gxKQ0KcHNlbnMobWF0Y2gxLCBHYW1tYSA9IDUsIEdhbW1hSW5jID0gMC4yNSkNCmBgYA0KDQpJZiB0aGUgc3R1ZHkgd2VyZSBmcmVlIG9mIGhpZGRlbiBiaWFzLCB0aGF0IGlzLCBpZiAkXEdhbW1hID0gMSQsIHRoZW4gdGhlcmUgd291bGQgYmUgKipzdHJvbmcqKiBldmlkZW5jZSB0aGF0IHRoZSB0cmVhdGVkIHBhdGllbnRzIGhhZCBoaWdoZXIgY29zdHMsIGFuZCB0aGUgc3BlY2lmaWMgV2lsY294b24gc2lnbmVkIHJhbmsgdGVzdCB3ZSdyZSBsb29raW5nIGF0IGhlcmUgc2hvd3MgYSAkcCQgdmFsdWUgPCAwLjAwMDEuIFRoZSBzZW5zaXRpdml0eSBhbmFseXNpcyB3ZSdsbCBjb25kdWN0IG5vdyBhc2tzIGhvdyB0aGlzIGNvbmNsdXNpb24gbWlnaHQgYmUgY2hhbmdlZCBieSBoaWRkZW4gYmlhc2VzIG9mIHZhcmlvdXMgbWFnbml0dWRlcywgZGVwZW5kaW5nIG9uIHRoZSBzaWduaWZpY2FuY2UgbGV2ZWwgd2UgcGxhbiB0byB1c2UgaW4gb3VyIHRlc3QuDQoNCiMjIFNwZWNpZnlpbmcgVGhlIFRocmVzaG9sZCAkXEdhbW1hJCB2YWx1ZQ0KDQpGcm9tIHRoZSBvdXRwdXQgYWJvdmUsIGZpbmQgdGhlICRcR2FtbWEkIHZhbHVlIHdoZXJlIHRoZSB1cHBlciBib3VuZCBmb3Igb3VyICRwJCB2YWx1ZSBzbGlwcyBmcm9tICJzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IiB0byAibm90IHNpZ25pZmljYW50IiB0ZXJyaXRvcnkuDQoNCi0gV2UncmUgZG9pbmcgYSB0d28tdGFpbGVkIHRlc3QsIHdpdGggYSA5NVwlIGNvbmZpZGVuY2UgbGV2ZWwsIHNvIHRoZSAkXEdhbW1hJCBzdGF0aXN0aWMgZm9yIHRoaXMgc2l0dWF0aW9uIGlzIGJldHdlZW4gMi4wIGFuZCAyLjI1LCBzaW5jZSB0aGF0IGlzIHRoZSBwb2ludCB3aGVyZSB0aGUgdXBwZXIgYm91bmQgZm9yIHRoZSAqcCogdmFsdWUgY3Jvc3NlcyB0aGUgdGhyZXNob2xkIG9mICRcYWxwaGEvMiA9IDAuMDI1JC4NCg0KU28gdGhpcyBzdHVkeSdzIGNvbmNsdXNpb24gKHRoYXQgdHJlYXRlZCBwYXRpZW50cyBoYWQgc2lnbmlmaWNhbnRseSBoaWdoZXIgY29zdHMpIHdvdWxkIHN0aWxsIGhvbGQgZXZlbiBpbiB0aGUgZmFjZSBvZiBhIGhpZGRlbiBiaWFzIHdpdGggJFxHYW1tYSA9IDIkLCBidXQgbm90IHdpdGggJFxHYW1tYSA9IDIuMjUkLg0KDQpUaGUgdGlwcGluZyBwb2ludCBmb3IgdGhlIHNlbnNpdGl2aXR5IHBhcmFtZXRlciBpcyBhIGxpdHRsZSBvdmVyIDIuMC4gVG8gZXhwbGFpbiBhd2F5IHRoZSBvYnNlcnZlZCBhc3NvY2lhdGlvbiBiZXR3ZWVuIHRyZWF0bWVudCBhbmQgdGhpcyBvdXRjb21lIChjb3N0KSwgYSBoaWRkZW4gYmlhcyBvciB1bm9ic2VydmVkIGNvdmFyaWF0ZSB3b3VsZCBuZWVkIHRvIGluY3JlYXNlIHRoZSBvZGRzIG9mIHRyZWF0bWVudCBieSBtb3JlIHRoYW4gYSBmYWN0b3Igb2YgJFxHYW1tYSA9IDIkLiANCg0KUmV0dXJuaW5nIHRvIHRoZSBvdXRwdXQ6DQoNCi0gSWYgaW5zdGVhZCB3ZSB3ZXJlIGRvaW5nIGEgb25lLXRhaWxlZCB0ZXN0IHdpdGggYSA5MFwlIGNvbmZpZGVuY2UgbGV2ZWwsIHRoZW4gdGhlICRcR2FtbWEkIHN0YXRpc3RpYyB3b3VsZCBiZSBiZXR3ZWVuIDIuMjUgYW5kIDIuNTAsIHNpbmNlIHRoYXQgaXMgd2hlcmUgdGhlIHVwcGVyIGJvdW5kIGZvciB0aGUgKnAqIHZhbHVlIGNyb3NzZXMgJFxhbHBoYSA9IDAuMTAkLg0KDQojIyBJbnRlcnByZXRpbmcgJFxHYW1tYSQgYXBwcm9wcmlhdGVseQ0KDQokXEdhbW1hJCB0ZWxscyB5b3Ugb25seSAqaG93IGJpZyBhIGJpYXMgaXMgbmVlZGVkIHRvIGNoYW5nZSB0aGUgYW5zd2VyKi4gQnkgaXRzZWxmLCBpdCBzYXlzIE5PVEhJTkcgYWJvdXQgdGhlIGxpa2VsaWhvb2QgdGhhdCBhIGJpYXMgb2YgdGhhdCBzaXplIGlzIHByZXNlbnQgaW4geW91ciBzdHVkeSwgZXhjZXB0IHRoYXQsIG9mIGNvdXJzZSwgc21hbGxlciBiaWFzZXMgaGlkZSBtb3JlIGVmZmVjdGl2ZWx5IHRoYW4gbGFyZ2Ugb25lcywgb24gYXZlcmFnZS4NCg0KLSBJbiBzb21lIHNldHRpbmdzLCB3ZSdsbCB0aGluayBvZiAkXEdhbW1hJCBpbiB0ZXJtcyBvZiBzbWFsbCAoPCAxLjUpLCBtb2Rlc3QgKDEuNSAtIDIuNSksIG1vZGVyYXRlICgyLjUgLSA0KSBhbmQgbGFyZ2UgKD4gNCkgaGlkZGVuIGJpYXMgcmVxdWlyZW1lbnRzLiBCdXQgdGhlc2UgYXJlIGNvbXBsZXRlbHkgYXJiaXRyYXJ5IGRpc3RpbmN0aW9ucywgYW5kIEkgY2FuIHByb3ZpZGUgbm8gZ29vZCBhcmd1bWVudCBmb3IgdGhlaXIgdXNlLg0KDQpUaGUgKipvbmx5KiogZGVmZW5zZSBhZ2FpbnN0IGhpZGRlbiBiaWFzIGFmZmVjdGluZyB5b3VyIGNvbmNsdXNpb25zIGlzIHRvIHRyeSB0byByZWR1Y2UgdGhlIHBvdGVudGlhbCBmb3IgaGlkZGVuIGJpYXMgaW4gdGhlIGZpcnN0IHBsYWNlLiBXZSB3b3JrIG9uIHRoaXMgdmlhIGNhcmVmdWwgZGVzaWduIG9mICBvYnNlcnZhdGlvbmFsIHN0dWRpZXMsIGVzcGVjaWFsbHkgYnkgaW5jbHVkaW5nIGFzIG1hbnkgZGlmZmVyZW50IGRpbWVuc2lvbnMgb2YgdGhlIHNlbGVjdGlvbiBwcm9ibGVtIGFzIHBvc3NpYmxlIGluIHlvdXIgcHJvcGVuc2l0eSBtb2RlbC4NCg0KIyMgQWx0ZXJuYXRpdmUgRGVzY3JpcHRpb25zIG9mICRcR2FtbWEkDQoNCkFzIHdlIHNlZSBpbiBDaGFwdGVyIDkgb2YgUm9zZW5iYXVtJ3MgKk9ic2VydmF0aW9uIGFuZCBFeHBlcmltZW50Kiwgd2UgY2FuIGRlc2NyaWJlIGEgJFxHYW1tYSQgPSAyIGFzIGJlaW5nIGVxdWl2YWxlbnQgdG8gYSByYW5nZSBvZiBwb3RlbnRpYWwgdmFsdWVzIG9mICRcVGhldGFfcCQgZnJvbSAwLjMzIHRvIDAuNjcsIGFuZCB2YWx1ZXMgb2YgJFxMYW1iZGEgPSAzJCBhbmQgJFxEZWx0YSA9IDUkLiAkXFRoZXRhX3AkIHByb3ZpZGVzIGFuIGVzdGltYXRlIG9mIHRoZSBjaGFuY2UgdGhhdCB0aGUgZmlyc3QgcGVyc29uIGluIGEgcGFpciBpcyB0aGUgdHJlYXRlZCBzdWJqZWN0LiAkXExhbWJkYSQgYW5kICRcRGVsdGEkIHJlZmVyIHRvIHRoZSBhbXBsaWZpY2F0aW9uIG9mIHNlbnNpdGl2aXR5IGFuYWx5c2lzLCB3aXRoIHJlZmVyZW5jZSB0byBhIHNwdXJpb3VzIGFzc29jaWF0ZWQgYmV0d2VlbiB0cmVhdG1lbnQgcmVjZWl2ZWQgYW5kIG91dGNvbWUgb2JzZXJ2ZWQgaW4gdGhlIGFic2VuY2Ugb2YgYSB0cmVhdG1lbnQgZWZmZWN0LiBUaGUgb2RkcyB0aGF0IHRoZSBmaXJzdCBwZXJzb24gaW4gYSBwYWlyIGlzIHRyZWF0ZWQgcmF0aGVyIHRoYW4gY29udHJvbCBpcyBib3VuZGVkIGJ5ICRcTGFtYmRhJCBhbmQgJDEvXExhbWJkYSQuIFRoZSBwYXJhbWV0ZXIgJFxEZWx0YSQgZGVmaW5lcyB0aGUgb2RkcyB0aGF0IHRoZSBwYWlyZWQgZGlmZmVyZW5jZSBpbiBvdXRjb21lcyBpcyBncmVhdGVyIHRoYW4gMCAoYXMgY29tcGFyZWQgdG8gbGVzcyB0aGFuIDApIGlmIHRoZXJlIGlzIGluIGZhY3Qgbm8gdHJlYXRtZW50IGVmZmVjdC4NCg0KIyMgQW4gQWx0ZXJuYXRlIEFwcHJvYWNoIC0gdGhlIEhvZGdlcy1MZWhtYW4gZXN0aW1hdGUNCg0KYGBge3J9DQpobHNlbnMobWF0Y2gxKQ0KYGBgDQoNCklmIHRoZSAkXEdhbW1hJCB2YWx1ZSBpcyAyLjAsIHRoZW4gdGhpcyBpbXBsaWVzIHRoYXQgdGhlIEhvZGdlcy1MZWhtYW5uIGVzdGltYXRlIG1pZ2h0IGJlIGFzIGxvdyBhcyA0IG9yIGFzIGhpZ2ggYXMgMTYuMSAoaXQgaXMgMTAuMCBpbiB0aGUgYWJzZW5jZSBvZiBoaWRkZW4gYmlhcyBpbiB0aGlzIGNhc2UgLSB3aGVuICRcR2FtbWEkID0gMC4pDQoNCiMjIFdoYXQgYWJvdXQgb3RoZXIgdHlwZXMgb2Ygb3V0Y29tZXM/DQoNClRoZSBgcmJvdW5kc2AgcGFja2FnZSBjYW4gZXZhbHVhdGUgYmluYXJ5IG91dGNvbWVzIHVzaW5nIHRoZSBgYmluYXJ5c2Vuc2AgYW5kIGBGaXNoZXJzZW5zYCBmdW5jdGlvbnMuDQoNClN1cnZpdmFsIG91dGNvbWVzIGNhbiBiZSBhc3Nlc3NlZCwgdG9vLCBidXQgbm90LCBJIGJlbGlldmUsIHVzaW5nIGByYm91bmRzYCB1bmxlc3MgdGhlcmUgaXMgbm8gY2Vuc29yaW5nLiBTb21lIHRpbWUgYmFjaywgSSBidWlsdCBhIHNwcmVhZHNoZWV0IGZvciB0aGlzIHRhc2ssIHdoaWNoIEknbGwgYmUgaGFwcHkgdG8gc2hhcmUuDQoNCiMjIFdoYXQgYWJvdXQgd2hlbiB3ZSBtYXRjaCAxOjIgb3IgMTozIGluc3RlYWQgb2YgMToxPw0KDQpUaGUgYG1jb250cm9sYCBmdW5jdGlvbiBpbiB0aGUgYHJib3VuZHNgIHBhY2thZ2UgY2FuIGJlIGhlbHBmdWwgaW4gc3VjaCBhIHNldHRpbmcuDQoNCiMgV3JhcHVwDQoNCklmIHlvdSBydW4gdGhpcyBzY3JpcHQsIHlvdSdsbCB3aW5kIHVwIHdpdGggYSB2ZXJzaW9uIG9mIHRoZSBgdG95YCB0aWJibGUgdGhhdCBjb250YWlucyA0MDAgb2JzZXJ2YXRpb25zIG9uIDI4IHZhcmlhYmxlcywgYWxvbmcgd2l0aCBhIGB0b3kuY29kZWJvb2tgIGxpc3QuDQoNCllvdSdsbCBhbHNvIGhhdmUgdHdvIG5ldyBmdW5jdGlvbnMsIGNhbGxlZCBgc3pkYCBhbmQgYHJ1YmluM2AsIHRoYXQsIHdpdGggc29tZSBtb2RpZmljYXRpb24sIG1heSBiZSB1c2VmdWwgZWxzZXdoZXJlLg0KDQpUbyBkcm9wIGV2ZXJ5dGhpbmcgZWxzZSBpbiB0aGUgZ2xvYmFsIGVudmlyb25tZW50IGNyZWF0ZWQgYnkgdGhpcyBNYXJrZG93biBmaWxlLCBydW4gdGhlIGNvZGUgdGhhdCBmb2xsb3dzLg0KDQpgYGB7ciBjbGVhbiB1cH0NCnJtKGxpc3QgPSBjKCJhZGoubS5vdXQxIiwgImFkai5tLm91dDEudGlkeSIsICJhZGoubS5vdXQyIiwgImFkai5tLm91dDJfdGlkeSIsIA0KImFkai5tLm91dDMiLCAiYWRqLm0ub3V0M190aWR5IiwgImFkai5yZWcub3V0MSIsICJhZGoucmVnLm91dDIiLCANCiJhZGoucmVnLm91dDMiLCAiYWRqLnMub3V0MyIsICJhZGpfb3V0MSIsICJhZGpfb3V0MiIsICJhZGpfb3V0MyIsIA0KImFkam91dDEud3QxIiwgImFkam91dDEud3QyIiwgImFkam91dDEud3QzIiwgImFkam91dDIud3QxIiwgImFkam91dDIud3QyIiwgDQoiYWRqb3V0Mi53dDMiLCAiYWRqb3V0My53dDEiLCAiYWRqb3V0My53dDIiLCAiYWRqb3V0My53dDMiLCAiYWxlcnQiLCANCiJiIiwgImJhbC5hZnRlci53dHMxIiwgImJhbC5hZnRlci53dHMyIiwgImJhbC5iZWZvcmUud3RzMSIsICJiYWwuYmVmb3JlLnd0czIiLCANCiJiYWwud3RzMSIsICJiYWwud3RzMiIsICJiYWxhbmNlLmF0ZS53ZWlnaHRzIiwgImJhbGFuY2UuYXR0LndlaWdodHMiLCANCiJjb3Yuc3ViIiwgImNvdmxpc3QiLCAiY292bmFtZXMiLCAiY292cyIsICJkLmFsbCIsICJkLnExIiwgImQucTIiLCANCiJkLnEzIiwgImQucTQiLCAiZC5xNSIsICJkZWNpbSIsICJkci5vdXQxLnd0MSIsICJkci5vdXQxLnd0MiIsIA0KImRyLm91dDEud3QzIiwgImRyLm91dDIud3QxIiwgImRyLm91dDIud3QyIiwgImRyLm91dDIud3QzIiwgImRyLm91dDMud3QxIiwgDQoiZHIub3V0My53dDIiLCAiZHIub3V0My53dDMiLCAiZHJfYXRlX291dDEiLCAiZHJfYXRlX291dDIiLCAiZHJfYXRlX291dDMiLCANCiJkcl9hdHRfb3V0MSIsICJkcl9hdHRfb3V0MiIsICJkcl9hdHRfb3V0MyIsICJkcl90d2FuZ2F0dF9vdXQxIiwgDQoiZHJfdHdhbmdhdHRfb3V0MiIsICJkcl90d2FuZ2F0dF9vdXQzIiwgImVzdC5zdCIsICJmYWN0b3JsaXN0IiwgDQoiaSIsICJtYXRjaF9zemQiLCAibWF0Y2hfdnJhdCIsICJtYXRjaDEiLCAibWF0Y2gxLm91dDEiLCAibWF0Y2gxLm91dDEuQVRFIiwgDQoibWF0Y2gxX291dDIiLCAibWF0Y2hlZF9taXhlZG1vZGVsLm91dDEiLCAibWF0Y2hlcyIsICJtYjEiLCAicCIsIA0KInBvc3Quc3pkIiwgInBvc3QudnJhdGlvIiwgInByZS5zemQiLCAicHJlLnZyYXRpbyIsICJwcy50b3kiLCANCiJwc21vZGVsIiwgInF1aW4xIiwgInF1aW4xLm91dDEiLCAicXVpbjEub3V0MiIsICJxdWluMiIsICJxdWluMi5vdXQxIiwgDQoicXVpbjIub3V0MiIsICJxdWluMyIsICJxdWluMy5vdXQxIiwgInF1aW4zLm91dDIiLCAicXVpbjQiLCAicXVpbjQub3V0MSIsIA0KInF1aW40Lm91dDIiLCAicXVpbjUiLCAicXVpbjUub3V0MSIsICJxdWluNS5vdXQyIiwgInJlc19tYXRjaGVkXzEiLCANCiJyZXNfdW5hZGpfMSIsICJyZXNfdW5hZGpfMl9vZGRzcmF0aW8iLCAicmVzX3VuYWRqXzJfb3IiLCAicmVzX3VuYWRqXzJfcmlza2RpZmYiLCANCiJyZXNfdW5hZGpfMyIsICJydWJpbjEubWF0Y2giLCAicnViaW4xLnExIiwgInJ1YmluMS5xMiIsICJydWJpbjEucTMiLCANCiJydWJpbjEucTQiLCAicnViaW4xLnE1IiwgInJ1YmluMS5zdWIiLCAicnViaW4xLnVuYWRqIiwgInJ1YmluMi5tYXRjaCIsIA0KInJ1YmluMi5xMSIsICJydWJpbjIucTIiLCAicnViaW4yLnEzIiwgInJ1YmluMi5xNCIsICJydWJpbjIucTUiLCANCiJydWJpbjIuc3ViIiwgInJ1YmluMi51bmFkaiIsICJydWJpbjMuYm90aCIsICJydWJpbjMubWF0Y2hlZCIsIA0KInJ1YmluMy5xMSIsICJydWJpbjMucTIiLCAicnViaW4zLnEzIiwgInJ1YmluMy5xNCIsICJydWJpbjMucTUiLCANCiJydWJpbjMudW5hZGoiLCAic2UucTEiLCAic2UucTIiLCAic2UucTMiLCAic2UucTQiLCAic2UucTUiLCANCiJzZS5zdCIsICJzdHJhdC5yZXN1bHQxIiwgInN0cmF0LnJlc3VsdDIiLCAic3RyYXQucmVzdWx0MyIsICANCiJ0ZW1wIiwgInRveS5tYXRjaGVkc2FtcGxlIiwgInRveS5ydWJpbjMiLCANCiJ0b3kuc3pkIiwgInRveV9kZiIsICJ0b3l3dDEuZGVzaWduIiwgInRveXd0Mi5kZXNpZ24iLCAidG95d3QzLmRlc2lnbiIsIA0KIlRyIiwgInVuYWRqLm91dDEiLCAidW5hZGoub3V0MiIsICJ1bmFkai5vdXQzIiwgInZhcmxpc3QiLCAid3RfYXRlX3Jlc3VsdHMxIiwgDQoid3RfYXRlX3Jlc3VsdHMyIiwgInd0X2F0ZV9yZXN1bHRzMyIsICJ3dF9hdHRfcmVzdWx0czEiLCAid3RfYXR0X3Jlc3VsdHMyIiwgDQoid3RfYXR0X3Jlc3VsdHMzIiwgInd0X3R3YW5nYXR0X3Jlc3VsdHMxIiwgInd0X3R3YW5nYXR0X3Jlc3VsdHMyIiwgDQoid3RfdHdhbmdhdHRfcmVzdWx0czMiLCAid3RzMyIsICJYIiwgIlkiKSkNCmBgYA0KDQojIyBTZXNzaW9uIEluZm9ybWF0aW9uDQoNCmBgYHtyfQ0KeGZ1bjo6c2Vzc2lvbl9pbmZvKCkNCmBgYA0KDQo=