1 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())

1.1 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>

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

1.3 “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

skim_variable treated n_missing complete_rate ordered n_unique top_counts
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

skim_variable treated n_missing complete_rate mean sd p0 p25 p50 p75 p100
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

1.4 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     

2 Data Management and Cleanup

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

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

2.2.1 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

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

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

2.3.1 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

2.4 Creating the Transformation and Product Terms

Remember that we have reason to believe that the square of covA as well as the interaction of covB with covC and also covB with covD will have an impact on treatment assignment. It will be useful to have these transformations in our data set for modeling and summarizing. I will use covB in its numeric (0,1) form (rather than as a factor - covB.f) when creating product terms, as shown below.

toy <- toy %>%
    mutate(Asqr = covA^2,
           BC = covB*covC,
           BD = covB*covD)

3 Data Set After Cleaning

3.1 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

skim_variable treated_f n_missing complete_rate ordered n_unique top_counts
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

skim_variable treated_f n_missing complete_rate mean sd p0 p25 p50 p75 p100
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

3.2 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     

4 The 13 Tasks We’ll Tackle in this Example

  1. 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)?
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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?
  7. 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.
  8. 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?
  9. 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.
  10. 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.
  11. Now, try a double robust approach. Weight, then adjust for linear propensity score.
  12. 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.
  13. 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.

5 Task 1. Ignoring covariates, estimate the effect of treatment vs. control on…

5.1 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

skim_variable treated_f n_missing complete_rate mean sd p0 p25 p50 p75 p100
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).

5.2 Outcome 2 (a binary outcome)

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

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

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

5.4 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:

Est. Treatment Effect (95% CI) Outcome 1 (Cost diff.) Outcome 2 (Risk diff.) Outcome 2 (Odds Ratio) Outcome 3 (Relative Hazard Rate)
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)

6 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

6.1 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

skim_variable treated_f n_missing complete_rate mean sd p0 p25 p50 p75 p100
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.

7 Task 3. Rubin’s Rules to Check Overlap Before Propensity Adjustment

In his 2001 article1 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.

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

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

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

7.3.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).

8 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 

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

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

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

8.1 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…

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

8.3 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 = "") 

8.4 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

8.4.1 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()

8.4.2 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()

8.5 Extracting, Tabulating Variance Ratios (without cobalt)

Next, we extract the variance ratios, and build a table.

pre.vratio <- NULL; post.vratio <- NULL
for(i in 1:length(covnames)) {
  pre.vratio[i] <- mb1$BeforeMatching[[i]]$var.ratio
  post.vratio[i] <- mb1$AfterMatching[[i]]$var.ratio
}

## Table of Variance Ratios
match_vrat <- tibble(names = covnames, pre.vratio, post.vratio, row.names=covnames)
print(match_vrat, digits=2)
# A tibble: 12 x 4
   names         pre.vratio post.vratio row.names    
   <chr>              <dbl>       <dbl> <chr>        
 1 covA               1.08        1.00  covA         
 2 covB               1.20        1.01  covB         
 3 covC               0.838       0.913 covC         
 4 covD               0.887       1.14  covD         
 5 covE               0.688       1.11  covE         
 6 covF - Middle      1.01        0.955 covF - Middle
 7 covF - High        1.61        1.23  covF - High  
 8 A^2                1.26        1.17  A^2          
 9 B*C                1.10        0.931 B*C          
10 B*D                1.41        1.10  B*D          
11 raw PS             0.944       1.22  raw PS       
12 linear PS          0.627       1.24  linear PS    

8.6 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

8.7 Rubin’s Rules to Check Balance After Matching

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

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

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

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

9 Task 5. After matching, estimate the causal effect of treatment on …

9.1 Outcome 1 (a continuous outcome)

9.1.1 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).

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

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

9.1.4 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 test2, but this isn’t something I’m completely comfortable with.

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

9.1.6 Practically, does any of this matter in this example?

Not much in this example, no, as long as you stick to ATT approaches.

Approach Effect Estimate Standard Error 95% CI
“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)

9.2 Outcome 2 (a binary outcome)

9.2.1 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).

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

9.3 Outcome 3 (a time-to-event outcome)

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

9.3.2 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")

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

Est. Treatment Effect (95% CI) Outcome 1 (Cost diff.) Outcome 2 (Risk diff.) Outcome 2 (Odds Ratio) Outcome 3 (Relative Hazard Rate)
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)

10 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

skim_variable stratum n_missing complete_rate mean sd p0 p25 p50 p75 p100
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

10.1 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")

10.2 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
}

10.3 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)

10.4 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

10.5 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")

10.6 Checking Rubin’s Rules Post-Subclassification

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

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

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

11 Task 7. After subclassifying, what is the estimated average treatment effect?

11.1 … 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.

11.2 … 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

11.3 … 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

11.3.1 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")

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

Est. Treatment Effect (95% CI) Outcome 1 (Cost diff.) Outcome 2 (Risk diff.) Outcome 2 (Odds Ratio) Outcome 3 (Relative Hazard Rate)
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)

12 Task 8. Execute weighting by the inverse PS, then assess covariate balance

12.1 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")

12.2 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")

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

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

12.3.2 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") 

12.3.3 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") 

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

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

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

12.4.3 Rubin’s Rule 3

Rubin’s Rule 3 requires some more substantial manipulation of the data. I’ll skip that here.

12.5 Rubin’s Rules after ATE weighting

Again, our summary statistic for Rules 1 and 2 may be found from the bal.table output.

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

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

12.5.3 Rubin’s Rule 3

Again, for now, I’m skipping Rubin’s Rule 3 after weighting.

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

13.1 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)

13.1.1 Did we let the simulations run long enough to stabilize estimates?

plot(ps.toy)

13.1.2 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

13.1.3 How is the balance?

plot(ps.toy, plots = 2)

plot(ps.toy, plots = 3)

13.1.4 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

13.2 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()

13.3 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()

14 Task 9. After weighting, what is the estimated average causal effect of treatment?

14.1 … on Outcome 1 [a continuous outcome]

14.1.1 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

14.1.2 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

14.1.3 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

14.2 … 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.

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

14.2.2 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

14.2.3 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

14.3 … 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.

14.3.1 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

14.3.2 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

14.3.3 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

14.4 Results So Far (After Matching, Subclassification and Weighting)

Est. Treatment Effect (95% CI) Outcome 1 (Cost diff.) Outcome 2 (Risk diff.) Outcome 2 (Odds Ratio) Outcome 3 (Rel. HR)
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)

15 Task 10. After direct adjustment for the linear PS, what is the estimated average causal treatment effect?

15.1 … 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

15.2 … 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

15.3 … 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.

15.3.1 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")

15.4 Results So Far (After Matching, Subclassification, Weighting, Adjustment)

Est. Treatment Effect (95% CI) Outcome 1 (Cost diff.) Outcome 2 (Risk diff.) Outcome 2 (Odds Ratio) Outcome 3 (Rel. HR)
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)

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

16.1 … on Outcome 1 [a continuous outcome]

16.1.1 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

16.1.2 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

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

16.2 … 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.

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

16.2.2 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

16.2.3 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

16.3 … 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.

16.3.1 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

16.3.2 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

16.3.3 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

17 Task 12. Results

17.1 Treatment Effect Estimates

We now can build the table of all of the outcome results we’ve obtained here.

Est. Treatment Effect (95% CI) Outcome 1 (Cost diff.) Outcome 2 (Risk diff.) Outcome 2 (Odds Ratio) Outcome 3 (Rel. HR)
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.

17.2 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 …

Approach Standardized Diffs Variance Ratios
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

17.3 Quality of Balance: Rubin’s Rules

Approach Rubin 1 Rubin 2 Rubin 3
“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.

18 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

  1. a statistically significant conclusion
  2. from a matched samples analysis using the propensity score.

18.1 Goal of a Formal Sensitivity Analysis for Matched Samples

To replace a general qualitative statement that applies in all observational studies, like …

the association we observe between treatment and outcome does not imply causation

or

hidden biases can explain observed associations

… with a quantitative statement that is specific to what is observed in a particular study, such as …

to explain the association seen in a particular study, one would need a hidden bias of a particular magnitude.

If the association is strong, the hidden bias needed to explain it would be large.

  • If a study is free of hidden bias (main example: a carefully randomized trial), this means that any two units (patients, subjects, whatever) that appear similar in terms of their observed covariates actually have the same chance of assignment to treatment.
  • There is hidden bias if two units with the same observed covariates have different chances of receiving the treatment.

A sensitivity analysis asks: How would inferences about treatment effects be altered by hidden biases of various magnitudes? How large would these differences have to be to alter the qualitative conclusions of the study?

The methods for building such sensitivity analyses are largely due to Paul Rosenbaum, and as a result the methods are sometimes referred to as Rosenbaum bounds.

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

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

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

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

19.2 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\).

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

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

19.5 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.)

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

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

20 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"))

20.1 Session Information

xfun::session_info()
R version 4.1.2 (2021-11-01)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19043)

Locale:
  LC_COLLATE=English_United States.1252 
  LC_CTYPE=English_United States.1252   
  LC_MONETARY=English_United States.1252
  LC_NUMERIC=C                          
  LC_TIME=English_United States.1252    

Package version:
  askpass_1.1              assertthat_0.2.1         backports_1.4.1         
  base64enc_0.1-3          bit_4.0.4                bit64_4.0.5             
  blob_1.2.2               boot_1.3-28              brio_1.1.3              
  broom_0.7.12             broom.mixed_0.2.7        bslib_0.3.1             
  callr_3.7.0              cellranger_1.1.0         checkmate_2.0.0         
  class_7.3-19             cli_3.1.1                clipr_0.7.1             
  cluster_2.1.2            cmprsk_2.2-11            cobalt_4.3.2            
  coda_0.19.4              colorspace_2.0-2         compiler_4.1.2          
  cpp11_0.4.2              crayon_1.4.2             curl_4.3.2              
  data.table_1.14.2        DBI_1.1.2                dbplyr_2.1.1            
  desc_1.4.0               diffobj_0.3.5            digest_0.6.29           
  dplyr_1.0.7              dtplyr_1.2.1             e1071_1.7-9             
  ellipsis_0.3.2           Epi_2.44                 etm_1.1.1               
  evaluate_0.14            fansi_1.0.2              farver_2.1.0            
  fastmap_1.1.0            forcats_0.5.1            foreign_0.8-82          
  Formula_1.2-4            fs_1.5.2                 gargle_1.2.0            
  gbm_2.1.8                gdata_2.18.0             generics_0.1.1          
  ggplot2_3.3.5            glue_1.6.1               gmodels_2.18.1          
  googledrive_2.0.0        googlesheets4_1.0.0      graphics_4.1.2          
  grDevices_4.1.2          grid_4.1.2               gridExtra_2.3           
  gtable_0.3.0             gtools_3.9.2             haven_2.4.3             
  highr_0.9                Hmisc_4.6-0              hms_1.1.1               
  htmlTable_2.4.0          htmltools_0.5.2          htmlwidgets_1.5.4       
  httr_1.4.2               ids_1.0.1                isoband_0.2.5           
  jpeg_0.1-9               jquerylib_0.1.4          jsonlite_1.7.3          
  knitr_1.37               labeling_0.4.2           labelled_2.9.0          
  lattice_0.20-45          latticeExtra_0.6-29      lifecycle_1.0.1         
  lme4_1.1-28              lubridate_1.8.0          magrittr_2.0.2          
  MASS_7.3-55              Matching_4.9-11          Matrix_1.3-4            
  MatrixModels_0.5-0       methods_4.1.2            mgcv_1.8-38             
  mime_0.12                minqa_1.2.4              mitools_2.4             
  modelr_0.1.8             munsell_0.5.0            nlme_3.1-153            
  nloptr_2.0.0             nnet_7.3-17              numDeriv_2016.8-1.1     
  openssl_1.4.6            parallel_4.1.2           pillar_1.6.5            
  pkgconfig_2.0.3          pkgload_1.2.4            plyr_1.8.6              
  png_0.1-7                praise_1.0.0             prettyunits_1.1.1       
  processx_3.5.2           progress_1.2.2           proxy_0.4-26            
  ps_1.6.0                 purrr_0.3.4              R6_2.5.1                
  rappdirs_0.3.3           rbounds_2.1              RColorBrewer_1.1-2      
  Rcpp_1.0.8               RcppArmadillo_0.10.8.1.0 RcppEigen_0.3.3.9.1     
  readr_2.1.1              readxl_1.3.1             rematch_1.0.1           
  rematch2_2.1.2           repr_1.1.4               reprex_2.0.1            
  rlang_1.0.0              rmarkdown_2.11           rpart_4.1-15            
  rprojroot_2.0.2          rstudioapi_0.13          rvest_1.0.2             
  sass_0.4.0               scales_1.1.1             selectr_0.4.2           
  skimr_2.1.3              splines_4.1.2            stats_4.1.2             
  stringi_1.7.6            stringr_1.4.0            survey_4.1-1            
  survival_3.2-13          sys_3.4                  tableone_0.13.0         
  testthat_3.1.2           tibble_3.1.6             tidyr_1.1.4             
  tidyselect_1.1.1         tidyverse_1.3.1          tinytex_0.36            
  tools_4.1.2              twang_2.5                tzdb_0.2.0              
  utf8_1.2.2               utils_4.1.2              uuid_1.0.3              
  vctrs_0.3.8              viridis_0.6.2            viridisLite_0.4.0       
  vroom_1.5.7              waldo_0.3.1              withr_2.4.3             
  xfun_0.29                xgboost_1.5.0.2          xml2_1.3.3              
  xtable_1.8-4             yaml_2.2.2               zoo_1.8-9               

  1. Rubin DB 2001 Using Propensity Scores to Help Design Observational Studies: Application to the Tobacco Litigation. Health Services & Outcomes Research Methodology 2: 169-188.↩︎

  2. I’ll leave checking that this is true as an exercise for the curious.↩︎

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=