Overview

In this notebook, we’ll leverage the data and best fit model that we learned from the walkthrough last week (Week 11). The aim is to extract insights from the machine learning model to better understand who is covered and who is not.

require(tidyverse)
require(caret) # for machine learning
require(recipes) # For preprocessing your data
require(vip) # For variable importance
require(pdp) # pdp and ice plots

# For parallelization (to run the models across many cores) -- speeds up computation!
# install.packages("doMC")
doMC::registerDoMC()

Data

The following data contains information regarding whether or not someone has health coverage. The outcome of interest is whether of not an individual has healthcare coverage or not. The available predictive features capture socio-economic and descriptive factors.

set.seed(1988)

# Again use a small random sample of the data so that it runs quicker
dat = suppressMessages(read_csv("health-coverage.csv")) %>%
  
  # Convert education into an ordered category.
  mutate(
    
    educ = case_when(
      educ == 'Less than HS' ~ 0,
      educ == 'HS Degree' ~ 1,
      educ == 'Undergraduate Degree' ~ 2,
      educ == 'Graduate Degree' ~ 3),
    
    race = case_when(
      race == 'White' ~ 'white',
      race == 'Black' ~ 'black',
      race == 'Asian' ~ 'asian',
      T ~ 'other'),
    
    race = factor(race,level=c("white",'black','asian','other')),
    
    mar = factor(mar,levels= c('Never Married',
                               'Divorced','Married',
                               'Separated','Widowed'))
    
    ) %>% 
  
  # Convert all remaining factor variables into character variables
  mutate_if(is.character,as.factor) %>% 
  
  # Make sure that the category you want to predict is the first category in
  # your factor variable
  mutate(coverage = factor(coverage,
                           levels = c("Coverage","No_Coverage"))) %>% 
  
  sample_n(5000)

head(dat) # Peek at the data just to make sure everything was read in correctly. 

Split the Sample: Training and test data

As before, let’s split the sample up into a training and test dataset. We’ll completely hold off on viewing the test data. We’ll leverage the test data to explore some of the insights we learn from the training data. Let’s use rsample to split the data.

set.seed(123)
# install.packages("rsample") # If you don't have the pack
split <- rsample::initial_split(dat,prop = .8,)
train_data <- rsample::training(split) 
test_data  <- rsample::testing(split) 


# Breakdown
nrow(train_data)/nrow(dat) 
[1] 0.8002
nrow(test_data)/nrow(dat)
[1] 0.1998

Data Pre-processing

In the Week 11 Walkthrough, we explore the data and outlined a preprocessing strategy. Let’s re-implement it here.

rcp <- 
  recipe(coverage ~  .,train_data) %>% 
  step_dummy(all_nominal(),-coverage) %>% #
  step_log(wage,offset = 1) %>% # Log the skewed wage variable
  step_range(all_numeric()) %>%  # Normalize scale
  prep()


# Apply the recipe to the training and test data
train_data2 <- bake(rcp,train_data)
test_data2 <- bake(rcp,test_data) 

Cross-validation

Let’s use the k-fold cross-validation method with 5 folds in the data to tune the model. We also need to specify that we’re dealing with a classification problem: recall we can do this by adding summaryFunction = twoClassSummary and classProbs = TRUE to the cross-validation function.

set.seed(1988) # set a seed for replication purposes 

# Partition the data into 5 equal folds
folds <- createFolds(train_data2$coverage, k = 5) 

# Cross validation object
control_conditions <- 
  trainControl(method='cv', # K-fold cross validation
               summaryFunction = twoClassSummary, # Two classes 
               classProbs = TRUE, # Need this b/c it's a classification problem
               index = folds # The indices for our folds
  )

Fit Model

In the Week 11 Walkthrough, we found that the random forest model had the best performance on predicting the outcome. Let’s re-run that model here.

mod_rf <-
  train(coverage ~ ., # Equation (outcome and everything else)
        data=train_data2, # Training data 
        method = "ranger", # random forest (ranger is much faster than rf)
        metric = "ROC", # area under the curve
        trControl = control_conditions
  )

Plot the model output. When we plot the performance of the above model, we see that we get a fairly high AUC (.83) on the held out data.

# AUC on the Training data 
prob_pred <- predict(mod_rf,type="prob")$Coverage
auc_rf <- Metrics::auc(train_data2$coverage=="Coverage",prob_pred)
auc_rf
[1] 0.8838971
# Accuracy on the Training data 
acc_rf <- Metrics::accuracy(train_data2$coverage,predict(mod_rf))
acc_rf
[1] 0.7853037

Model Interpretation

Variable Importance

Let’s now extract some substantive insights from the model using variable importance approach learned in class. Which variables does the model rely on most to make it’s predictions?

vi_plot<- 
  vip(mod_rf, # Machine learning model
      train = train_data2, # Training data 
      method="permute", # permuted importance
      nsim = 10, # number of times to impute
      geom = "boxplot", # Type of plot 
      target = "coverage", # outcome
      reference_class = "Coverage", # what class are you predicting
      metric = "accuracy",
      pred_wrapper = predict)

# Plot VIP
vi_plot

The variable importance plot shows:

  • age, wage, and educ, is highly important to the models predictive accuracy
  • Things that matter less, but still matter to whether you’re covered or not
    • If someone is married,
    • Citizenship status.

Looking at the variable importance alone, we could refine our original model to only include those features that matter. Let’s see if doing so boosts performance.

# Re-run the random forest, this time with most important variable only. 
mod_rf_selected <-
  train(coverage ~ age + wage + educ + mar_Married + cit_Non.citizen, 
        data=train_data2, # Training data 
        method = "ranger", # random forest (ranger is much faster than rf)
        metric = "ROC", # area under the curve
        trControl = control_conditions
  )

Does the model do better, worse, or about the same?

# AUC on the Training data 
prob_pred <- predict(mod_rf_selected,type="prob")$Coverage
auc_rf_sel <- Metrics::auc(train_data2$coverage=="Coverage",prob_pred)

cat("AUC of the selected model:",auc_rf_sel,"\n",
    "AUC of the full model:",auc_rf,"\n",
    "Difference:", auc_rf_sel-auc_rf,"\n") # Difference: a 4.2% increase in AUC
AUC of the selected model: 0.9201622 
 AUC of the full model: 0.8838971 
 Difference: 0.0362651 
# Accuracy on the Training data 
acc_rf_sel <- Metrics::accuracy(train_data2$coverage,predict(mod_rf_selected))

cat("Accuracy of the selected model:",acc_rf_sel,"\n",
    "Accuracy of the full model:",acc_rf,"\n",
    "Difference:", acc_rf_sel-acc_rf,"\n") # Difference: a 3.4% increase in accuracy
Accuracy of the selected model: 0.8322919 
 Accuracy of the full model: 0.7853037 
 Difference: 0.04698825 

Clearly the model performs better (in this case) when we only include the features that matter most.

Looking into which variables are important to the model helped us refine the model and the features worth using.

Marginal Effects

Partial Dependencies

It’s useful to know that age and wage are important to the model, but how do they relate to whether an individual is covered or not in the data? Let’s explore a partial dependency plot to unpack the average marginal effect for age and wage.

# PDP for age
age_pdp <- partial(mod_rf_selected, pred.var = "age", plot = TRUE,prob=T,
                   grid.resolution = 20, # choosing less points makes it run quicker
                   plot.engine = "ggplot2")

# PDP for wage
wage_pdp <- partial(mod_rf_selected, pred.var = "wage", plot = TRUE,prob=T,
                    grid.resolution = 20, # choosing less points makes it run quicker
                   plot.engine = "ggplot2")

# PDP for education level
educ_pdp <- partial(mod_rf_selected, pred.var = "educ", plot = TRUE,prob=T,
                    grid.resolution = 4,plot.engine = "ggplot2")

# PDP for Married (note that this is a dummy var, so only can take on one of two values)
mar_pdp <- partial(mod_rf_selected, pred.var = "mar_Married", plot = TRUE,prob=T,
                   plot.engine = "ggplot2")

# PDP for Not a citizen (note that this is a dummy var, so only can take on one of two values)
cit_pdp <- partial(mod_rf_selected, pred.var = "cit_Non.citizen", plot = TRUE,prob=T,
                   plot.engine = "ggplot2")

# GridExtra allows us to stack two ggplot objects into a single plot
gridExtra::grid.arrange(age_pdp,wage_pdp,educ_pdp,mar_pdp,cit_pdp)

Looking above at the partial dependencies for all the variables in the model, we can see some interesting things:

  • For age, there appears to exist a threshold. Once you get past a certain age (50-sh), you’re incredibly more likely to be covered.

  • As a side note, notice the bump at the lower end of the age distribution? Minimum age in the data is 16. This predicted increase in the probability of coverage likely reflects children who are covered by their parents.

  • For wage, it looks like as you get wealthier, you’re more likely to be covered.

  • The more educated one is, the higher the likelihood that they’ll be covered.

  • For the dummy variables: being married corresponds with an increase in the probability of being covered, whereas not being a citizen corresponds with a decrease in the likelihood of being covered.

Individual Conditional Expectations

One additional question we might want to ask is whether there is are important interactions. One way we can explore this is if we look at the individual expectations for each observation in the data as we manipulate the values of the target variable. If the ICE curves diverge (have different trajectories), that can be evidence that there is a potential interaction.

A few things to note here:

  • It looks like there is a potential interaction in wage. Notice how some of the curves have a very different trend toward the end of the distribution? The reason for this is likely due to some interaction.

  • The idea that only old people are covered (our conclusion after looking at the PDP plot) isn’t entirely accurate. In fact, there are a number of respondents in the data that are predicted to have a higher probability of coverage at a young age; however, these observations are canceled out by other young respondents who are less likely to be covered.

  • Likewise it looks like there is heterogeneity in the effect of education on the likelihood of being covered.

  • Finally, for the dummy variables, note how the impact of being married or not a citizen impacts different respondents differently in the model.

Let’s now explore the potential interaction between wage and age, wage and educ, and educ and wage. Is it the combination of being wealthy and old that corresponds with coverage, or does wealth make younger people more willing to purchase coverage?

From the above plot, we can indeed see that there is an interaction between age and wage. Specifically,

  • We still note the threshold effect past .65 on the age scale (that is, all older people are more likely to be covered).
  • However, what this plot demonstrates is that wage mediates age. Even if you’re young, if you’re making a high salary you’re more likely to be covered. Chances are that a high wage is correlated with employer-provided health coverage.
  • Finally, there appears to be a major region in which the model predicts a lower probability of coverage. Specifically for young people who don’t have a high paying wage.

W/r/t education and wage: the probability of coverage is highest for educated people who are on the high end of the wage distribution. However, the effect of education is much less (even for highly educated people) if they are lower on the wage distribution.

W/r/t education and age, it doesn’t look like there is much of an interaction.

Global Surrogate Model

Tree

The random forest model does a fantastic job at predicting the likelihood of coverage, but it’s difficult to interpret. In the above sections we explored variable importance and the marginal effects of dependencies of the variables on the predicted probabilities to gain some insight into how the model is using the data to make its predictions. Let’s now take this a step further by building a global surrogate model. The aim here is to build a more interpretable model on top of our black box model. That is, we can train an interpretable model to approximate the predictions of the random forest model. We can then interpret this more interpretable model.

train_data3 <- 
  train_data2 %>% 
  mutate(
    # Probability predictions from the random forest model
    coverage_probs = predict(mod_rf_selected,type = "prob")$Coverage
  )  


surrogate_tree <-
  
  # Decision tree model (directly us the model rather than use the implementation in caret)
  rpart::rpart(
    # Main selected model. The outcome is now the predicted probabilities from
    # the RF model
    coverage_probs ~ age + wage + educ + mar_Married + cit_Non.citizen,
    
    # Data is being passed by the pipe
    data = train_data3,
    
    # Note that we can control the depth of the tree
    # a deeper tree increase fit but reduces interpretability
    control = rpart::rpart.control(maxdepth = 4)
  )

Let’s now assess the model fit.

tibble(truth = train_data3$coverage_probs,
       estimate = predict(surrogate_tree)) %>% 
  rsq(truth,estimate)

An R-Squared of .78 isn’t bad. It indicates that the surrogate model does a good job approximating the black box model (i.e. the random forest model).

Let’s now look at the tree.

rattle::fancyRpartPlot(surrogate_tree,sub="",type=1)

The surrogate model provides a picture that chimes well with our other insights, but new ones clearly stand out.

  • As we previously noted, there is an interaction between age and wage. Though there are two notable interactions given ones wage. Assuming one is less than 65 years of age (i.e. .64 on our age scale):

    • If one doesn’t make the upper quantile of the wage distribution, then coverage depends on whether they’re a citizen or not (though note the probabilities are low for both splits).
    • If one does make a high wage and is married he/she is more likely to be covered.
  • If one is more than 65 years of age, coverage mainly depends on whether he/she is a citizen or not. If you’re above 65 and a citizen, you have a 95% probability of being covered.

NOTE: that if we built a deeper model, a richer story emerges. But that story doesn’t differ much from the one we’ve already been piecing together.

surrogate_tree2 <-
  rpart::rpart(coverage_probs ~ age + wage + educ + mar_Married + cit_Non.citizen,
    data = train_data3,
    control = rpart::rpart.control(maxdepth = 7)
  )
rattle::fancyRpartPlot(surrogate_tree2,sub="",type=1)

Linear Model

We can use the insights above to build out a linear model (actually, a logistic model b/c the outcome is binary) to help with interpretability.

linear_surrogate_model <- 
  train_data2 %>% 
  mutate(coverage_dummy = 1*(coverage == "Coverage")) %>% 
  glm(coverage_dummy ~ 
        age*wage + 
        wage*educ +
        mar_Married + 
        cit_Non.citizen,
      data=.,family = binomial("logit"))

# Print the regression results
stargazer::stargazer(linear_surrogate_model,type = "text")
length of NULL cannot be changedlength of NULL cannot be changedlength of NULL cannot be changedlength of NULL cannot be changedlength of NULL cannot be changed

=============================================
                      Dependent variable:    
                  ---------------------------
                        coverage_dummy       
---------------------------------------------
age                        3.505***          
                            (0.230)          
                                             
wage                        0.418*           
                            (0.243)          
                                             
educ                       0.821***          
                            (0.247)          
                                             
mar_Married                0.690***          
                            (0.077)          
                                             
cit_Non.citizen            -1.551***         
                            (0.148)          
                                             
age:wage                   -1.925***         
                            (0.475)          
                                             
wage:educ                  2.345***          
                            (0.416)          
                                             
Constant                   -1.988***         
                            (0.128)          
                                             
---------------------------------------------
Observations                 4,001           
Log Likelihood            -2,230.519         
Akaike Inf. Crit.          4,477.038         
=============================================
Note:             *p<0.1; **p<0.05; ***p<0.01

We could now test these theoretical insights on new, previously unseen data… i.e. the test data!

How well does the theoretical model predict future data? Pretty well, which indicates that our insights likely generalize to the test data.

Metrics::auc(actual = 1*(test_data2$coverage=="Coverage"),
             predicted = predict(linear_surrogate_model,
                                 newdata = test_data2))
[1] 0.7827528

Theory Development

We’ve learned a lot from the training data and model about the factors that increase the likelihood of coverage. We’ve generated a number of interesting hypotheses:

  • Retirement age US citizens are more likely to have healthcare coverage than non-retirement age citizens.
  • Young married individuals making a high salary are more likely to be covered than their non-married counterparts.
  • Coverage largely depends on an individuals income unless he/she is above 65 years of age.
LS0tCnRpdGxlOiAiUFBPTDY3MCB8IEludGVycHJldGFibGUgTWFjaGluZSBMZWFybmluZyB8IFdlZWsgMTIiCnN1YnRpdGxlOiAiV2Fsa3Rocm91Z2ggb24gc29tZSBhcHByb2FjaGVzIHRvIG1vZGVsIGludGVycHJldGF0aW9uIgpkYXRlOiBXZWVrIDEyCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdGhlbWU6IHVuaXRlZAogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIHRvY19kZXB0aDogNAotLS0KCmBgYHtyIFNldHVwLCBpbmNsdWRlPUZ9CmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRixlcnJvciA9IEYsbWVzc2FnZSA9IEYsY2FjaGU9VCkKYGBgCgojIE92ZXJ2aWV3IAoKSW4gdGhpcyBub3RlYm9vaywgd2UnbGwgbGV2ZXJhZ2UgdGhlIGRhdGEgYW5kIGJlc3QgZml0IG1vZGVsIHRoYXQgd2UgbGVhcm5lZCBmcm9tIHRoZSB3YWxrdGhyb3VnaCBsYXN0IHdlZWsgKCoqV2VlayAxMSoqKS4gVGhlIGFpbSBpcyB0byBleHRyYWN0IGluc2lnaHRzIGZyb20gdGhlIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwgdG8gYmV0dGVyIHVuZGVyc3RhbmQgd2hvIGlzIGNvdmVyZWQgYW5kIHdobyBpcyBub3QuIAoKYGBge3J9CnJlcXVpcmUodGlkeXZlcnNlKQpyZXF1aXJlKGNhcmV0KSAjIGZvciBtYWNoaW5lIGxlYXJuaW5nCnJlcXVpcmUocmVjaXBlcykgIyBGb3IgcHJlcHJvY2Vzc2luZyB5b3VyIGRhdGEKcmVxdWlyZSh2aXApICMgRm9yIHZhcmlhYmxlIGltcG9ydGFuY2UKcmVxdWlyZShwZHApICMgcGRwIGFuZCBpY2UgcGxvdHMKCiMgRm9yIHBhcmFsbGVsaXphdGlvbiAodG8gcnVuIHRoZSBtb2RlbHMgYWNyb3NzIG1hbnkgY29yZXMpIC0tIHNwZWVkcyB1cCBjb21wdXRhdGlvbiEKIyBpbnN0YWxsLnBhY2thZ2VzKCJkb01DIikKZG9NQzo6cmVnaXN0ZXJEb01DKCkKYGBgCgoKIyBEYXRhIAoKVGhlIGZvbGxvd2luZyBkYXRhIGNvbnRhaW5zIGluZm9ybWF0aW9uIHJlZ2FyZGluZyB3aGV0aGVyIG9yIG5vdCBzb21lb25lIGhhcyBoZWFsdGggY292ZXJhZ2UuIFRoZSBvdXRjb21lIG9mIGludGVyZXN0IGlzIHdoZXRoZXIgb2Ygbm90IGFuIGluZGl2aWR1YWwgaGFzIGhlYWx0aGNhcmUgY292ZXJhZ2Ugb3Igbm90LiBUaGUgYXZhaWxhYmxlIHByZWRpY3RpdmUgZmVhdHVyZXMgY2FwdHVyZSBzb2Npby1lY29ub21pYyBhbmQgZGVzY3JpcHRpdmUgZmFjdG9ycy4gCgoKYGBge3J9CnNldC5zZWVkKDE5ODgpCgojIEFnYWluIHVzZSBhIHNtYWxsIHJhbmRvbSBzYW1wbGUgb2YgdGhlIGRhdGEgc28gdGhhdCBpdCBydW5zIHF1aWNrZXIKZGF0ID0gc3VwcHJlc3NNZXNzYWdlcyhyZWFkX2NzdigiaGVhbHRoLWNvdmVyYWdlLmNzdiIpKSAlPiUKICAKICAjIENvbnZlcnQgZWR1Y2F0aW9uIGludG8gYW4gb3JkZXJlZCBjYXRlZ29yeS4KICBtdXRhdGUoCiAgICAKICAgIGVkdWMgPSBjYXNlX3doZW4oCiAgICAgIGVkdWMgPT0gJ0xlc3MgdGhhbiBIUycgfiAwLAogICAgICBlZHVjID09ICdIUyBEZWdyZWUnIH4gMSwKICAgICAgZWR1YyA9PSAnVW5kZXJncmFkdWF0ZSBEZWdyZWUnIH4gMiwKICAgICAgZWR1YyA9PSAnR3JhZHVhdGUgRGVncmVlJyB+IDMpLAogICAgCiAgICByYWNlID0gY2FzZV93aGVuKAogICAgICByYWNlID09ICdXaGl0ZScgfiAnd2hpdGUnLAogICAgICByYWNlID09ICdCbGFjaycgfiAnYmxhY2snLAogICAgICByYWNlID09ICdBc2lhbicgfiAnYXNpYW4nLAogICAgICBUIH4gJ290aGVyJyksCiAgICAKICAgIHJhY2UgPSBmYWN0b3IocmFjZSxsZXZlbD1jKCJ3aGl0ZSIsJ2JsYWNrJywnYXNpYW4nLCdvdGhlcicpKSwKICAgIAogICAgbWFyID0gZmFjdG9yKG1hcixsZXZlbHM9IGMoJ05ldmVyIE1hcnJpZWQnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0Rpdm9yY2VkJywnTWFycmllZCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnU2VwYXJhdGVkJywnV2lkb3dlZCcpKQogICAgCiAgICApICU+JSAKICAKICAjIENvbnZlcnQgYWxsIHJlbWFpbmluZyBmYWN0b3IgdmFyaWFibGVzIGludG8gY2hhcmFjdGVyIHZhcmlhYmxlcwogIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsYXMuZmFjdG9yKSAlPiUgCiAgCiAgIyBNYWtlIHN1cmUgdGhhdCB0aGUgY2F0ZWdvcnkgeW91IHdhbnQgdG8gcHJlZGljdCBpcyB0aGUgZmlyc3QgY2F0ZWdvcnkgaW4KICAjIHlvdXIgZmFjdG9yIHZhcmlhYmxlCiAgbXV0YXRlKGNvdmVyYWdlID0gZmFjdG9yKGNvdmVyYWdlLAogICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJDb3ZlcmFnZSIsIk5vX0NvdmVyYWdlIikpKSAlPiUgCiAgCiAgc2FtcGxlX24oNTAwMCkKCmhlYWQoZGF0KSAjIFBlZWsgYXQgdGhlIGRhdGEganVzdCB0byBtYWtlIHN1cmUgZXZlcnl0aGluZyB3YXMgcmVhZCBpbiBjb3JyZWN0bHkuIApgYGAKCgojIFNwbGl0IHRoZSBTYW1wbGU6IFRyYWluaW5nIGFuZCB0ZXN0IGRhdGEKCkFzIGJlZm9yZSwgbGV0J3Mgc3BsaXQgdGhlIHNhbXBsZSB1cCBpbnRvIGEgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YXNldC4gV2UnbGwgY29tcGxldGVseSBob2xkIG9mZiBvbiB2aWV3aW5nIHRoZSB0ZXN0IGRhdGEuIFdlJ2xsIGxldmVyYWdlIHRoZSB0ZXN0IGRhdGEgdG8gZXhwbG9yZSBzb21lIG9mIHRoZSBpbnNpZ2h0cyB3ZSBsZWFybiBmcm9tIHRoZSB0cmFpbmluZyBkYXRhLiBMZXQncyB1c2UgYHJzYW1wbGVgIHRvIHNwbGl0IHRoZSBkYXRhLiAKCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQojIGluc3RhbGwucGFja2FnZXMoInJzYW1wbGUiKSAjIElmIHlvdSBkb24ndCBoYXZlIHRoZSBwYWNrCnNwbGl0IDwtIHJzYW1wbGU6OmluaXRpYWxfc3BsaXQoZGF0LHByb3AgPSAuOCwpCnRyYWluX2RhdGEgPC0gcnNhbXBsZTo6dHJhaW5pbmcoc3BsaXQpIAp0ZXN0X2RhdGEgIDwtIHJzYW1wbGU6OnRlc3Rpbmcoc3BsaXQpIAoKCiMgQnJlYWtkb3duCm5yb3codHJhaW5fZGF0YSkvbnJvdyhkYXQpIApucm93KHRlc3RfZGF0YSkvbnJvdyhkYXQpCmBgYAoKIyBEYXRhIFByZS1wcm9jZXNzaW5nCgpJbiB0aGUgKipXZWVrIDExIFdhbGt0aHJvdWdoKiosIHdlIGV4cGxvcmUgdGhlIGRhdGEgYW5kIG91dGxpbmVkIGEgcHJlcHJvY2Vzc2luZyBzdHJhdGVneS4gTGV0J3MgcmUtaW1wbGVtZW50IGl0IGhlcmUuIAoKYGBge3J9CnJjcCA8LSAKICByZWNpcGUoY292ZXJhZ2UgfiAgLix0cmFpbl9kYXRhKSAlPiUgCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLC1jb3ZlcmFnZSkgJT4lICMKICBzdGVwX2xvZyh3YWdlLG9mZnNldCA9IDEpICU+JSAjIExvZyB0aGUgc2tld2VkIHdhZ2UgdmFyaWFibGUKICBzdGVwX3JhbmdlKGFsbF9udW1lcmljKCkpICU+JSAgIyBOb3JtYWxpemUgc2NhbGUKICBwcmVwKCkKCgojIEFwcGx5IHRoZSByZWNpcGUgdG8gdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEKdHJhaW5fZGF0YTIgPC0gYmFrZShyY3AsdHJhaW5fZGF0YSkKdGVzdF9kYXRhMiA8LSBiYWtlKHJjcCx0ZXN0X2RhdGEpIApgYGAKCgoKIyBDcm9zcy12YWxpZGF0aW9uCgpMZXQncyB1c2UgdGhlIGstZm9sZCBjcm9zcy12YWxpZGF0aW9uIG1ldGhvZCB3aXRoIDUgZm9sZHMgaW4gdGhlIGRhdGEgdG8gdHVuZSB0aGUgbW9kZWwuIFdlIGFsc28gbmVlZCB0byBzcGVjaWZ5IHRoYXQgd2UncmUgZGVhbGluZyB3aXRoIGEgY2xhc3NpZmljYXRpb24gcHJvYmxlbTogcmVjYWxsIHdlIGNhbiBkbyB0aGlzIGJ5IGFkZGluZyBgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5YCBhbmQgYGNsYXNzUHJvYnMgPSBUUlVFYCB0byB0aGUgY3Jvc3MtdmFsaWRhdGlvbiBmdW5jdGlvbi4gCgpgYGB7cn0Kc2V0LnNlZWQoMTk4OCkgIyBzZXQgYSBzZWVkIGZvciByZXBsaWNhdGlvbiBwdXJwb3NlcyAKCiMgUGFydGl0aW9uIHRoZSBkYXRhIGludG8gNSBlcXVhbCBmb2xkcwpmb2xkcyA8LSBjcmVhdGVGb2xkcyh0cmFpbl9kYXRhMiRjb3ZlcmFnZSwgayA9IDUpIAoKIyBDcm9zcyB2YWxpZGF0aW9uIG9iamVjdApjb250cm9sX2NvbmRpdGlvbnMgPC0gCiAgdHJhaW5Db250cm9sKG1ldGhvZD0nY3YnLCAjIEstZm9sZCBjcm9zcyB2YWxpZGF0aW9uCiAgICAgICAgICAgICAgIHN1bW1hcnlGdW5jdGlvbiA9IHR3b0NsYXNzU3VtbWFyeSwgIyBUd28gY2xhc3NlcyAKICAgICAgICAgICAgICAgY2xhc3NQcm9icyA9IFRSVUUsICMgTmVlZCB0aGlzIGIvYyBpdCdzIGEgY2xhc3NpZmljYXRpb24gcHJvYmxlbQogICAgICAgICAgICAgICBpbmRleCA9IGZvbGRzICMgVGhlIGluZGljZXMgZm9yIG91ciBmb2xkcwogICkKYGBgCgoKIyBGaXQgTW9kZWwKCkluIHRoZSAqKldlZWsgMTEgV2Fsa3Rocm91Z2gqKiwgd2UgZm91bmQgdGhhdCB0aGUgKipfcmFuZG9tIGZvcmVzdF8qKiBtb2RlbCBoYWQgdGhlIGJlc3QgcGVyZm9ybWFuY2Ugb24gcHJlZGljdGluZyB0aGUgb3V0Y29tZS4gTGV0J3MgcmUtcnVuIHRoYXQgbW9kZWwgaGVyZS4gCgoKYGBge3J9Cm1vZF9yZiA8LQogIHRyYWluKGNvdmVyYWdlIH4gLiwgIyBFcXVhdGlvbiAob3V0Y29tZSBhbmQgZXZlcnl0aGluZyBlbHNlKQogICAgICAgIGRhdGE9dHJhaW5fZGF0YTIsICMgVHJhaW5pbmcgZGF0YSAKICAgICAgICBtZXRob2QgPSAicmFuZ2VyIiwgIyByYW5kb20gZm9yZXN0IChyYW5nZXIgaXMgbXVjaCBmYXN0ZXIgdGhhbiByZikKICAgICAgICBtZXRyaWMgPSAiUk9DIiwgIyBhcmVhIHVuZGVyIHRoZSBjdXJ2ZQogICAgICAgIHRyQ29udHJvbCA9IGNvbnRyb2xfY29uZGl0aW9ucwogICkKYGBgCgpQbG90IHRoZSBtb2RlbCBvdXRwdXQuIFdoZW4gd2UgcGxvdCB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIGFib3ZlIG1vZGVsLCB3ZSBzZWUgdGhhdCB3ZSBnZXQgYSBmYWlybHkgaGlnaCBBVUMgKC44Mykgb24gdGhlIGhlbGQgb3V0IGRhdGEuCgpgYGB7cn0KIyBBVUMgb24gdGhlIFRyYWluaW5nIGRhdGEgCnByb2JfcHJlZCA8LSBwcmVkaWN0KG1vZF9yZix0eXBlPSJwcm9iIikkQ292ZXJhZ2UKYXVjX3JmIDwtIE1ldHJpY3M6OmF1Yyh0cmFpbl9kYXRhMiRjb3ZlcmFnZT09IkNvdmVyYWdlIixwcm9iX3ByZWQpCmF1Y19yZgpgYGAKCgpgYGB7cn0KIyBBY2N1cmFjeSBvbiB0aGUgVHJhaW5pbmcgZGF0YSAKYWNjX3JmIDwtIE1ldHJpY3M6OmFjY3VyYWN5KHRyYWluX2RhdGEyJGNvdmVyYWdlLHByZWRpY3QobW9kX3JmKSkKYWNjX3JmCmBgYAoKIyBNb2RlbCBJbnRlcnByZXRhdGlvbiAKCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UKCkxldCdzIG5vdyBleHRyYWN0IHNvbWUgc3Vic3RhbnRpdmUgaW5zaWdodHMgZnJvbSB0aGUgbW9kZWwgdXNpbmcgdmFyaWFibGUgaW1wb3J0YW5jZSBhcHByb2FjaCBsZWFybmVkIGluIGNsYXNzLiAqKl9XaGljaCB2YXJpYWJsZXMgZG9lcyB0aGUgbW9kZWwgcmVseSBvbiBtb3N0IHRvIG1ha2UgaXQncyBwcmVkaWN0aW9ucz9fKioKCmBgYHtyLGZpZy5hbGlnbj0iY2VudGVyIixmaWcuaGVpZ2h0PTUsZmlnLndpZHRoPTd9CnZpX3Bsb3Q8LSAKICB2aXAobW9kX3JmLCAjIE1hY2hpbmUgbGVhcm5pbmcgbW9kZWwKICAgICAgdHJhaW4gPSB0cmFpbl9kYXRhMiwgIyBUcmFpbmluZyBkYXRhIAogICAgICBtZXRob2Q9InBlcm11dGUiLCAjIHBlcm11dGVkIGltcG9ydGFuY2UKICAgICAgbnNpbSA9IDEwLCAjIG51bWJlciBvZiB0aW1lcyB0byBpbXB1dGUKICAgICAgZ2VvbSA9ICJib3hwbG90IiwgIyBUeXBlIG9mIHBsb3QgCiAgICAgIHRhcmdldCA9ICJjb3ZlcmFnZSIsICMgb3V0Y29tZQogICAgICByZWZlcmVuY2VfY2xhc3MgPSAiQ292ZXJhZ2UiLCAjIHdoYXQgY2xhc3MgYXJlIHlvdSBwcmVkaWN0aW5nCiAgICAgIG1ldHJpYyA9ICJhY2N1cmFjeSIsCiAgICAgIHByZWRfd3JhcHBlciA9IHByZWRpY3QpCgojIFBsb3QgVklQCnZpX3Bsb3QKYGBgCgoKVGhlIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdCBzaG93czoKCi0gYGFnZWAsIGB3YWdlYCwgYW5kIGBlZHVjYCwgaXMgaGlnaGx5IGltcG9ydGFudCB0byB0aGUgbW9kZWxzIHByZWRpY3RpdmUgYWNjdXJhY3kKLSBUaGluZ3MgdGhhdCBtYXR0ZXIgbGVzcywgYnV0IHN0aWxsIG1hdHRlciB0byB3aGV0aGVyIHlvdSdyZSBjb3ZlcmVkIG9yIG5vdAogICAgLSBJZiBzb21lb25lIGlzIG1hcnJpZWQsIAogICAgLSBDaXRpemVuc2hpcCBzdGF0dXMuCiAgICAKTG9va2luZyBhdCB0aGUgdmFyaWFibGUgaW1wb3J0YW5jZSBhbG9uZSwgd2UgY291bGQgKipfcmVmaW5lIG91ciBvcmlnaW5hbCBtb2RlbF8qKiB0byBvbmx5IGluY2x1ZGUgdGhvc2UgZmVhdHVyZXMgdGhhdCBtYXR0ZXIuIExldCdzIHNlZSBpZiBkb2luZyBzbyBib29zdHMgcGVyZm9ybWFuY2UuIAoKCmBgYHtyfQojIFJlLXJ1biB0aGUgcmFuZG9tIGZvcmVzdCwgdGhpcyB0aW1lIHdpdGggbW9zdCBpbXBvcnRhbnQgdmFyaWFibGUgb25seS4gCm1vZF9yZl9zZWxlY3RlZCA8LQogIHRyYWluKGNvdmVyYWdlIH4gYWdlICsgd2FnZSArIGVkdWMgKyBtYXJfTWFycmllZCArIGNpdF9Ob24uY2l0aXplbiwgCiAgICAgICAgZGF0YT10cmFpbl9kYXRhMiwgIyBUcmFpbmluZyBkYXRhIAogICAgICAgIG1ldGhvZCA9ICJyYW5nZXIiLCAjIHJhbmRvbSBmb3Jlc3QgKHJhbmdlciBpcyBtdWNoIGZhc3RlciB0aGFuIHJmKQogICAgICAgIG1ldHJpYyA9ICJST0MiLCAjIGFyZWEgdW5kZXIgdGhlIGN1cnZlCiAgICAgICAgdHJDb250cm9sID0gY29udHJvbF9jb25kaXRpb25zCiAgKQpgYGAKCkRvZXMgdGhlIG1vZGVsIGRvIGJldHRlciwgd29yc2UsIG9yIGFib3V0IHRoZSBzYW1lPwoKYGBge3J9CiMgQVVDIG9uIHRoZSBUcmFpbmluZyBkYXRhIApwcm9iX3ByZWQgPC0gcHJlZGljdChtb2RfcmZfc2VsZWN0ZWQsdHlwZT0icHJvYiIpJENvdmVyYWdlCmF1Y19yZl9zZWwgPC0gTWV0cmljczo6YXVjKHRyYWluX2RhdGEyJGNvdmVyYWdlPT0iQ292ZXJhZ2UiLHByb2JfcHJlZCkKCmNhdCgiQVVDIG9mIHRoZSBzZWxlY3RlZCBtb2RlbDoiLGF1Y19yZl9zZWwsIlxuIiwKICAgICJBVUMgb2YgdGhlIGZ1bGwgbW9kZWw6IixhdWNfcmYsIlxuIiwKICAgICJEaWZmZXJlbmNlOiIsIGF1Y19yZl9zZWwtYXVjX3JmLCJcbiIpICMgRGlmZmVyZW5jZTogYSA0LjIlIGluY3JlYXNlIGluIEFVQwpgYGAKCmBgYHtyfQojIEFjY3VyYWN5IG9uIHRoZSBUcmFpbmluZyBkYXRhIAphY2NfcmZfc2VsIDwtIE1ldHJpY3M6OmFjY3VyYWN5KHRyYWluX2RhdGEyJGNvdmVyYWdlLHByZWRpY3QobW9kX3JmX3NlbGVjdGVkKSkKCmNhdCgiQWNjdXJhY3kgb2YgdGhlIHNlbGVjdGVkIG1vZGVsOiIsYWNjX3JmX3NlbCwiXG4iLAogICAgIkFjY3VyYWN5IG9mIHRoZSBmdWxsIG1vZGVsOiIsYWNjX3JmLCJcbiIsCiAgICAiRGlmZmVyZW5jZToiLCBhY2NfcmZfc2VsLWFjY19yZiwiXG4iKSAjIERpZmZlcmVuY2U6IGEgMy40JSBpbmNyZWFzZSBpbiBhY2N1cmFjeQpgYGAKCkNsZWFybHkgdGhlIG1vZGVsIHBlcmZvcm1zIF9iZXR0ZXJfIChpbiB0aGlzIGNhc2UpIHdoZW4gd2Ugb25seSBpbmNsdWRlIHRoZSBmZWF0dXJlcyB0aGF0IG1hdHRlciBtb3N0LiAKCkxvb2tpbmcgaW50byB3aGljaCB2YXJpYWJsZXMgYXJlIGltcG9ydGFudCB0byB0aGUgbW9kZWwgaGVscGVkIHVzICoqX3JlZmluZV8qKiB0aGUgbW9kZWwgYW5kIHRoZSBmZWF0dXJlcyB3b3J0aCB1c2luZy4gCgoKIyMgTWFyZ2luYWwgRWZmZWN0cwoKIyMjIFBhcnRpYWwgRGVwZW5kZW5jaWVzCgpJdCdzIHVzZWZ1bCB0byBrbm93IHRoYXQgYGFnZWAgYW5kIGB3YWdlYCBhcmUgaW1wb3J0YW50IHRvIHRoZSBtb2RlbCwgYnV0IGhvdyBkbyB0aGV5IHJlbGF0ZSB0byB3aGV0aGVyIGFuIGluZGl2aWR1YWwgaXMgY292ZXJlZCBvciBub3QgaW4gdGhlIGRhdGE/IExldCdzIGV4cGxvcmUgYSBwYXJ0aWFsIGRlcGVuZGVuY3kgcGxvdCB0byB1bnBhY2sgdGhlIGF2ZXJhZ2UgbWFyZ2luYWwgZWZmZWN0IGZvciBgYWdlYCBhbmQgYHdhZ2VgLgoKYGBge3IsZmlnLmFsaWduPSJjZW50ZXIiLGZpZy5oZWlnaHQ9MTAsZmlnLndpZHRoPTEwfQojIFBEUCBmb3IgYWdlCmFnZV9wZHAgPC0gcGFydGlhbChtb2RfcmZfc2VsZWN0ZWQsIHByZWQudmFyID0gImFnZSIsIHBsb3QgPSBUUlVFLHByb2I9VCwKICAgICAgICAgICAgICAgICAgIGdyaWQucmVzb2x1dGlvbiA9IDIwLCAjIGNob29zaW5nIGxlc3MgcG9pbnRzIG1ha2VzIGl0IHJ1biBxdWlja2VyCiAgICAgICAgICAgICAgICAgICBwbG90LmVuZ2luZSA9ICJnZ3Bsb3QyIikKCiMgUERQIGZvciB3YWdlCndhZ2VfcGRwIDwtIHBhcnRpYWwobW9kX3JmX3NlbGVjdGVkLCBwcmVkLnZhciA9ICJ3YWdlIiwgcGxvdCA9IFRSVUUscHJvYj1ULAogICAgICAgICAgICAgICAgICAgIGdyaWQucmVzb2x1dGlvbiA9IDIwLCAjIGNob29zaW5nIGxlc3MgcG9pbnRzIG1ha2VzIGl0IHJ1biBxdWlja2VyCiAgICAgICAgICAgICAgICAgICBwbG90LmVuZ2luZSA9ICJnZ3Bsb3QyIikKCiMgUERQIGZvciBlZHVjYXRpb24gbGV2ZWwKZWR1Y19wZHAgPC0gcGFydGlhbChtb2RfcmZfc2VsZWN0ZWQsIHByZWQudmFyID0gImVkdWMiLCBwbG90ID0gVFJVRSxwcm9iPVQsCiAgICAgICAgICAgICAgICAgICAgZ3JpZC5yZXNvbHV0aW9uID0gNCxwbG90LmVuZ2luZSA9ICJnZ3Bsb3QyIikKCiMgUERQIGZvciBNYXJyaWVkIChub3RlIHRoYXQgdGhpcyBpcyBhIGR1bW15IHZhciwgc28gb25seSBjYW4gdGFrZSBvbiBvbmUgb2YgdHdvIHZhbHVlcykKbWFyX3BkcCA8LSBwYXJ0aWFsKG1vZF9yZl9zZWxlY3RlZCwgcHJlZC52YXIgPSAibWFyX01hcnJpZWQiLCBwbG90ID0gVFJVRSxwcm9iPVQsCiAgICAgICAgICAgICAgICAgICBwbG90LmVuZ2luZSA9ICJnZ3Bsb3QyIikKCiMgUERQIGZvciBOb3QgYSBjaXRpemVuIChub3RlIHRoYXQgdGhpcyBpcyBhIGR1bW15IHZhciwgc28gb25seSBjYW4gdGFrZSBvbiBvbmUgb2YgdHdvIHZhbHVlcykKY2l0X3BkcCA8LSBwYXJ0aWFsKG1vZF9yZl9zZWxlY3RlZCwgcHJlZC52YXIgPSAiY2l0X05vbi5jaXRpemVuIiwgcGxvdCA9IFRSVUUscHJvYj1ULAogICAgICAgICAgICAgICAgICAgcGxvdC5lbmdpbmUgPSAiZ2dwbG90MiIpCgojIEdyaWRFeHRyYSBhbGxvd3MgdXMgdG8gc3RhY2sgdHdvIGdncGxvdCBvYmplY3RzIGludG8gYSBzaW5nbGUgcGxvdApncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShhZ2VfcGRwLHdhZ2VfcGRwLGVkdWNfcGRwLG1hcl9wZHAsY2l0X3BkcCkKYGBgCgpMb29raW5nIGFib3ZlIGF0IHRoZSBwYXJ0aWFsIGRlcGVuZGVuY2llcyBmb3IgYWxsIHRoZSB2YXJpYWJsZXMgaW4gdGhlIG1vZGVsLCB3ZSBjYW4gc2VlIHNvbWUgaW50ZXJlc3RpbmcgdGhpbmdzOgoKLSBGb3IgYWdlLCB0aGVyZSBhcHBlYXJzIHRvIGV4aXN0IGEgX3RocmVzaG9sZF8uIE9uY2UgeW91IGdldCBwYXN0IGEgY2VydGFpbiBhZ2UgKDUwLXNoKSwgeW91J3JlIGluY3JlZGlibHkgbW9yZSBsaWtlbHkgdG8gYmUgY292ZXJlZC4gCgogKyBBcyBhIHNpZGUgbm90ZSwgbm90aWNlIHRoZSBidW1wIGF0IHRoZSBsb3dlciBlbmQgb2YgdGhlIGFnZSBkaXN0cmlidXRpb24/IE1pbmltdW0gYWdlIGluIHRoZSBkYXRhIGlzIDE2LiBUaGlzIHByZWRpY3RlZCBpbmNyZWFzZSBpbiB0aGUgcHJvYmFiaWxpdHkgb2YgY292ZXJhZ2UgbGlrZWx5IHJlZmxlY3RzIGNoaWxkcmVuIHdobyBhcmUgY292ZXJlZCBieSB0aGVpciBwYXJlbnRzLiAKCi0gRm9yIHdhZ2UsIGl0IGxvb2tzIGxpa2UgYXMgeW91IGdldCB3ZWFsdGhpZXIsIHlvdSdyZSBtb3JlIGxpa2VseSB0byBiZSBjb3ZlcmVkLgoKLSBUaGUgbW9yZSBlZHVjYXRlZCBvbmUgaXMsIHRoZSBoaWdoZXIgdGhlIGxpa2VsaWhvb2QgdGhhdCB0aGV5J2xsIGJlIGNvdmVyZWQuIAoKLSBGb3IgdGhlIGR1bW15IHZhcmlhYmxlczogYmVpbmcgbWFycmllZCBjb3JyZXNwb25kcyB3aXRoIGFuIGluY3JlYXNlIGluIHRoZSBwcm9iYWJpbGl0eSBvZiBiZWluZyBjb3ZlcmVkLCB3aGVyZWFzIG5vdCBiZWluZyBhIGNpdGl6ZW4gY29ycmVzcG9uZHMgd2l0aCBhIF9kZWNyZWFzZV8gaW4gdGhlIGxpa2VsaWhvb2Qgb2YgYmVpbmcgY292ZXJlZC4gCgojIyMgSW5kaXZpZHVhbCBDb25kaXRpb25hbCBFeHBlY3RhdGlvbnMKCk9uZSBhZGRpdGlvbmFsIHF1ZXN0aW9uIHdlIG1pZ2h0IHdhbnQgdG8gYXNrIGlzIHdoZXRoZXIgdGhlcmUgaXMgYXJlIGltcG9ydGFudCBfaW50ZXJhY3Rpb25zXy4gT25lIHdheSB3ZSBjYW4gZXhwbG9yZSB0aGlzIGlzIGlmIHdlIGxvb2sgYXQgdGhlIGluZGl2aWR1YWwgZXhwZWN0YXRpb25zIGZvciBlYWNoIG9ic2VydmF0aW9uIGluIHRoZSBkYXRhIGFzIHdlIG1hbmlwdWxhdGUgdGhlIHZhbHVlcyBvZiB0aGUgdGFyZ2V0IHZhcmlhYmxlLiBJZiB0aGUgSUNFIGN1cnZlcyBkaXZlcmdlIChoYXZlIGRpZmZlcmVudCB0cmFqZWN0b3JpZXMpLCB0aGF0IGNhbiBiZSBldmlkZW5jZSB0aGF0IHRoZXJlIGlzIGEgcG90ZW50aWFsIGludGVyYWN0aW9uLiAKCmBgYHtyLGZpZy5hbGlnbj0iY2VudGVyIixmaWcuaGVpZ2h0PTEwLGZpZy53aWR0aD0xMH0KIyBQRFAgZm9yIGFnZQphZ2VfaWNlIDwtIHBhcnRpYWwobW9kX3JmX3NlbGVjdGVkLCBwcmVkLnZhciA9ICJhZ2UiLCAKICAgICAgICAgICAgICAgICAgIHBsb3QgPSBUUlVFLHByb2I9VCwKICAgICAgICAgICAgICAgICAgIGljZT1ULGNlbnRlcj1ULGFscGhhPS4wNSwKICAgICAgICAgICAgICAgICAgIGdyaWQucmVzb2x1dGlvbiA9IDIwLCAjIGNob29zaW5nIGxlc3MgcG9pbnRzIG1ha2VzIGl0IHJ1biBxdWlja2VyCiAgICAgICAgICAgICAgICAgICBwbG90LmVuZ2luZSA9ICJnZ3Bsb3QyIikKCiMgUERQIGZvciB3YWdlCndhZ2VfaWNlIDwtIHBhcnRpYWwobW9kX3JmX3NlbGVjdGVkLCBwcmVkLnZhciA9ICJ3YWdlIiwgCiAgICAgICAgICAgICAgICAgICAgcGxvdCA9IFRSVUUscHJvYj1ULAogICAgICAgICAgICAgICAgICAgIGljZT1ULGNlbnRlcj1ULGFscGhhPS4wNSwKICAgICAgICAgICAgICAgICAgICBncmlkLnJlc29sdXRpb24gPSAyMCwgIyBjaG9vc2luZyBsZXNzIHBvaW50cyBtYWtlcyBpdCBydW4gcXVpY2tlcgogICAgICAgICAgICAgICAgICAgcGxvdC5lbmdpbmUgPSAiZ2dwbG90MiIpCgojIFBEUCBmb3IgZWR1Y2F0aW9uIGxldmVsCmVkdWNfaWNlIDwtIHBhcnRpYWwobW9kX3JmX3NlbGVjdGVkLCBwcmVkLnZhciA9ICJlZHVjIiwgCiAgICAgICAgICAgICAgICAgICAgcGxvdCA9IFRSVUUscHJvYj1ULAogICAgICAgICAgICAgICAgICAgIGljZT1ULGNlbnRlcj1ULGFscGhhPS4wNSwKICAgICAgICAgICAgICAgICAgICBncmlkLnJlc29sdXRpb24gPSA0LHBsb3QuZW5naW5lID0gImdncGxvdDIiKQoKIyBQRFAgZm9yIE1hcnJpZWQgKG5vdGUgdGhhdCB0aGlzIGlzIGEgZHVtbXkgdmFyLCBzbyBvbmx5IGNhbiB0YWtlIG9uIG9uZSBvZiB0d28gdmFsdWVzKQptYXJfaWNlIDwtIHBhcnRpYWwobW9kX3JmX3NlbGVjdGVkLCBwcmVkLnZhciA9ICJtYXJfTWFycmllZCIsIAogICAgICAgICAgICAgICAgICAgcGxvdCA9IFRSVUUscHJvYj1ULAogICAgICAgICAgICAgICAgICAgaWNlPVQsY2VudGVyPVQsYWxwaGE9LjA1LAogICAgICAgICAgICAgICAgICAgcGxvdC5lbmdpbmUgPSAiZ2dwbG90MiIpCgojIFBEUCBmb3IgTm90IGEgY2l0aXplbiAobm90ZSB0aGF0IHRoaXMgaXMgYSBkdW1teSB2YXIsIHNvIG9ubHkgY2FuIHRha2Ugb24gb25lIG9mIHR3byB2YWx1ZXMpCmNpdF9pY2UgPC0gcGFydGlhbChtb2RfcmZfc2VsZWN0ZWQsIHByZWQudmFyID0gImNpdF9Ob24uY2l0aXplbiIsIAogICAgICAgICAgICAgICAgICAgcGxvdCA9IFRSVUUscHJvYj1ULAogICAgICAgICAgICAgICAgICAgaWNlPVQsY2VudGVyPVQsYWxwaGE9LjA1LAogICAgICAgICAgICAgICAgICAgcGxvdC5lbmdpbmUgPSAiZ2dwbG90MiIpCgojIEdyaWRFeHRyYSBhbGxvd3MgdXMgdG8gc3RhY2sgdHdvIGdncGxvdCBvYmplY3RzIGludG8gYSBzaW5nbGUgcGxvdApncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShhZ2VfaWNlLHdhZ2VfaWNlLGVkdWNfaWNlLG1hcl9pY2UsY2l0X2ljZSkKYGBgCgpBIGZldyB0aGluZ3MgdG8gbm90ZSBoZXJlOgoKLSBJdCBsb29rcyBsaWtlIHRoZXJlIGlzIGEgcG90ZW50aWFsIGludGVyYWN0aW9uIGluIGB3YWdlYC4gTm90aWNlIGhvdyBzb21lIG9mIHRoZSBjdXJ2ZXMgaGF2ZSBhIHZlcnkgZGlmZmVyZW50IHRyZW5kIHRvd2FyZCB0aGUgZW5kIG9mIHRoZSBkaXN0cmlidXRpb24/IFRoZSByZWFzb24gZm9yIHRoaXMgaXMgbGlrZWx5IGR1ZSB0byBzb21lIGludGVyYWN0aW9uLiAKCi0gVGhlIGlkZWEgdGhhdCBvbmx5IG9sZCBwZW9wbGUgYXJlIGNvdmVyZWQgKG91ciBjb25jbHVzaW9uIGFmdGVyIGxvb2tpbmcgYXQgdGhlIFBEUCBwbG90KSBpc24ndCBlbnRpcmVseSBhY2N1cmF0ZS4gSW4gZmFjdCwgdGhlcmUgYXJlIGEgbnVtYmVyIG9mIHJlc3BvbmRlbnRzIGluIHRoZSBkYXRhIHRoYXQgYXJlIHByZWRpY3RlZCB0byBoYXZlIGEgaGlnaGVyIHByb2JhYmlsaXR5IG9mIGNvdmVyYWdlIGF0IGEgeW91bmcgYWdlOyBob3dldmVyLCB0aGVzZSBvYnNlcnZhdGlvbnMgYXJlIGNhbmNlbGVkIG91dCBieSBvdGhlciB5b3VuZyByZXNwb25kZW50cyB3aG8gYXJlIGxlc3MgbGlrZWx5IHRvIGJlIGNvdmVyZWQuIAoKLSBMaWtld2lzZSBpdCBsb29rcyBsaWtlIHRoZXJlIGlzIGhldGVyb2dlbmVpdHkgaW4gdGhlIGVmZmVjdCBvZiBlZHVjYXRpb24gb24gdGhlIGxpa2VsaWhvb2Qgb2YgYmVpbmcgY292ZXJlZC4gCgotIEZpbmFsbHksIGZvciB0aGUgZHVtbXkgdmFyaWFibGVzLCBub3RlIGhvdyB0aGUgaW1wYWN0IG9mIGJlaW5nIG1hcnJpZWQgb3Igbm90IGEgY2l0aXplbiBpbXBhY3RzIGRpZmZlcmVudCByZXNwb25kZW50cyBkaWZmZXJlbnRseSBpbiB0aGUgbW9kZWwuCgoKTGV0J3Mgbm93IGV4cGxvcmUgdGhlIHBvdGVudGlhbCBpbnRlcmFjdGlvbiBiZXR3ZWVuIGB3YWdlYCBhbmQgYGFnZWAsIGB3YWdlYCBhbmQgYGVkdWNgLCBhbmQgYGVkdWNgIGFuZCBgd2FnZWAuIElzIGl0IHRoZSBjb21iaW5hdGlvbiBvZiBiZWluZyB3ZWFsdGh5IGFuZCBvbGQgdGhhdCBjb3JyZXNwb25kcyB3aXRoIGNvdmVyYWdlLCBvciBkb2VzIHdlYWx0aCBtYWtlIHlvdW5nZXIgcGVvcGxlIG1vcmUgd2lsbGluZyB0byBwdXJjaGFzZSBjb3ZlcmFnZT8KCmBgYHtyLGZpZy5hbGlnbj0iY2VudGVyIixmaWcuaGVpZ2h0PTEwLGZpZy53aWR0aD03fQojIENhbGN1bGF0aW5nIHRoZSBwYXJ0aWFsIGRlcGVuZGVuY2llcyBmb3IgZXZlcnkgdmFsdWUgaXMgY29tcHV0YXRpb25hbGx5CiMgZXhwZW5zaXZlOyB3ZSdyZSBpbnN0ZWFkIG9ubHkgZ29pbmcgdG8gZG8gaXQgZm9yIGEgZmV3IHZhbHVlcyB1c2luZyBhIHByb3ZpZGVkIGdyaWQuCmFnZV93YWdlX3BkcCA8LSBwYXJ0aWFsKG1vZF9yZl9zZWxlY3RlZCwgcHJlZC52YXIgPSBjKCJhZ2UiLCJ3YWdlIiksIAogICAgICAgICAgICAgICAgICAgICAgICBwbG90ID0gVFJVRSxwcm9iPVQsCiAgICAgICAgICAgICAgICAgICAgICAgIHByZWQuZ3JpZCA9IGV4cGFuZC5ncmlkKGFnZSA9IHNlcSgwLDEsYnk9LjEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3YWdlID0gc2VxKDAsMSxieT0uMSkpLAogICAgICAgICAgICAgICAgICAgICAgICBwbG90LmVuZ2luZSA9ICJnZ3Bsb3QyIiwKICAgICAgICAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9ICJtYWdtYSIpCgoKd2FnZV9lZHVjX3BkcCA8LSBwYXJ0aWFsKG1vZF9yZl9zZWxlY3RlZCwgcHJlZC52YXIgPSBjKCJlZHVjIiwid2FnZSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgcGxvdCA9IFRSVUUscHJvYj1ULAogICAgICAgICAgICAgICAgICAgICAgICBwcmVkLmdyaWQgPSBleHBhbmQuZ3JpZCh3YWdlID0gc2VxKDAsMSxieT0uMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVkdWMgPSBzZXEoMCwxLGJ5PS4yNSkpLAogICAgICAgICAgICAgICAgICAgICAgICBwbG90LmVuZ2luZSA9ICJnZ3Bsb3QyIiwKICAgICAgICAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9ICJtYWdtYSIpCgoKYWdlX2VkdWNfcGRwIDwtIHBhcnRpYWwobW9kX3JmX3NlbGVjdGVkLCBwcmVkLnZhciA9IGMoImVkdWMiLCJhZ2UiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIHBsb3QgPSBUUlVFLHByb2I9VCwKICAgICAgICAgICAgICAgICAgICAgICAgcHJlZC5ncmlkID0gZXhwYW5kLmdyaWQoYWdlID0gc2VxKDAsMSxieT0uMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVkdWMgPSBzZXEoMCwxLGJ5PS4yNSkpLAogICAgICAgICAgICAgICAgICAgICAgICBwbG90LmVuZ2luZSA9ICJnZ3Bsb3QyIiwKICAgICAgICAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9ICJtYWdtYSIpCgoKZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UoYWdlX3dhZ2VfcGRwLHdhZ2VfZWR1Y19wZHAsYWdlX2VkdWNfcGRwLG5jb2w9MSkKYGBgCgpGcm9tIHRoZSBhYm92ZSBwbG90LCB3ZSBjYW4gaW5kZWVkIHNlZSB0aGF0IHRoZXJlIGlzIGFuIGludGVyYWN0aW9uIGJldHdlZW4gYGFnZWAgYW5kIGB3YWdlYC4gU3BlY2lmaWNhbGx5LCAKCi0gV2Ugc3RpbGwgbm90ZSB0aGUgdGhyZXNob2xkIGVmZmVjdCBwYXN0IC42NSBvbiB0aGUgYWdlIHNjYWxlICh0aGF0IGlzLCBhbGwgb2xkZXIgcGVvcGxlIGFyZSBtb3JlIGxpa2VseSB0byBiZSBjb3ZlcmVkKS4gCi0gSG93ZXZlciwgd2hhdCB0aGlzIHBsb3QgZGVtb25zdHJhdGVzIGlzIHRoYXQgd2FnZSBtZWRpYXRlcyBhZ2UuIEV2ZW4gaWYgeW91J3JlIHlvdW5nLCBpZiB5b3UncmUgbWFraW5nIGEgaGlnaCBzYWxhcnkgeW91J3JlIG1vcmUgbGlrZWx5IHRvIGJlIGNvdmVyZWQuIENoYW5jZXMgYXJlIHRoYXQgYSBoaWdoIHdhZ2UgaXMgY29ycmVsYXRlZCB3aXRoIGVtcGxveWVyLXByb3ZpZGVkIGhlYWx0aCBjb3ZlcmFnZS4gCi0gRmluYWxseSwgdGhlcmUgYXBwZWFycyB0byBiZSBhIG1ham9yIHJlZ2lvbiBpbiB3aGljaCB0aGUgbW9kZWwgcHJlZGljdHMgYSBsb3dlciBwcm9iYWJpbGl0eSBvZiBjb3ZlcmFnZS4gU3BlY2lmaWNhbGx5IGZvciB5b3VuZyBwZW9wbGUgd2hvIGRvbid0IGhhdmUgYSBoaWdoIHBheWluZyB3YWdlLiAKClcvci90IGVkdWNhdGlvbiBhbmQgd2FnZTogdGhlIHByb2JhYmlsaXR5IG9mIGNvdmVyYWdlIGlzIGhpZ2hlc3QgZm9yIGVkdWNhdGVkIHBlb3BsZSB3aG8gYXJlIG9uIHRoZSBoaWdoIGVuZCBvZiB0aGUgd2FnZSBkaXN0cmlidXRpb24uIEhvd2V2ZXIsIHRoZSBlZmZlY3Qgb2YgZWR1Y2F0aW9uIGlzIG11Y2ggbGVzcyAoZXZlbiBmb3IgaGlnaGx5IGVkdWNhdGVkIHBlb3BsZSkgaWYgdGhleSBhcmUgbG93ZXIgb24gdGhlIHdhZ2UgZGlzdHJpYnV0aW9uLiAKClcvci90IGVkdWNhdGlvbiBhbmQgYWdlLCBpdCBkb2Vzbid0IGxvb2sgbGlrZSB0aGVyZSBpcyBtdWNoIG9mIGFuIGludGVyYWN0aW9uLgoKCiMjIEdsb2JhbCBTdXJyb2dhdGUgTW9kZWwKCgojIyMgVHJlZQoKVGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwgZG9lcyBhIGZhbnRhc3RpYyBqb2IgYXQgcHJlZGljdGluZyB0aGUgbGlrZWxpaG9vZCBvZiBjb3ZlcmFnZSwgYnV0IGl0J3MgZGlmZmljdWx0IHRvIGludGVycHJldC4gSW4gdGhlIGFib3ZlIHNlY3Rpb25zIHdlIGV4cGxvcmVkIHZhcmlhYmxlIGltcG9ydGFuY2UgYW5kIHRoZSBtYXJnaW5hbCBlZmZlY3RzIG9mIGRlcGVuZGVuY2llcyBvZiB0aGUgdmFyaWFibGVzIG9uIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyB0byBnYWluIHNvbWUgaW5zaWdodCBpbnRvIGhvdyB0aGUgbW9kZWwgaXMgdXNpbmcgdGhlIGRhdGEgdG8gbWFrZSBpdHMgcHJlZGljdGlvbnMuIExldCdzIG5vdyB0YWtlIHRoaXMgYSBzdGVwIGZ1cnRoZXIgYnkgYnVpbGRpbmcgYSAqKmdsb2JhbCBzdXJyb2dhdGUgbW9kZWwqKi4gVGhlIGFpbSBoZXJlIGlzIHRvIGJ1aWxkIGEgbW9yZSBpbnRlcnByZXRhYmxlIG1vZGVsIF9vbiB0b3BfIG9mIG91ciBibGFjayBib3ggbW9kZWwuIFRoYXQgaXMsIHdlIGNhbiB0cmFpbiBhbiBpbnRlcnByZXRhYmxlIG1vZGVsIHRvIGFwcHJveGltYXRlIHRoZSBwcmVkaWN0aW9ucyBvZiB0aGUgcmFuZG9tIGZvcmVzdCBtb2RlbC4gV2UgY2FuIHRoZW4gaW50ZXJwcmV0IHRoaXMgbW9yZSBpbnRlcnByZXRhYmxlIG1vZGVsLiAKCmBgYHtyfQp0cmFpbl9kYXRhMyA8LSAKICB0cmFpbl9kYXRhMiAlPiUgCiAgbXV0YXRlKAogICAgIyBQcm9iYWJpbGl0eSBwcmVkaWN0aW9ucyBmcm9tIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsCiAgICBjb3ZlcmFnZV9wcm9icyA9IHByZWRpY3QobW9kX3JmX3NlbGVjdGVkLHR5cGUgPSAicHJvYiIpJENvdmVyYWdlCiAgKSAgCgoKc3Vycm9nYXRlX3RyZWUgPC0KICAKICAjIERlY2lzaW9uIHRyZWUgbW9kZWwgKGRpcmVjdGx5IHVzIHRoZSBtb2RlbCByYXRoZXIgdGhhbiB1c2UgdGhlIGltcGxlbWVudGF0aW9uIGluIGNhcmV0KQogIHJwYXJ0OjpycGFydCgKICAgICMgTWFpbiBzZWxlY3RlZCBtb2RlbC4gVGhlIG91dGNvbWUgaXMgbm93IHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBmcm9tCiAgICAjIHRoZSBSRiBtb2RlbAogICAgY292ZXJhZ2VfcHJvYnMgfiBhZ2UgKyB3YWdlICsgZWR1YyArIG1hcl9NYXJyaWVkICsgY2l0X05vbi5jaXRpemVuLAogICAgCiAgICAjIERhdGEgaXMgYmVpbmcgcGFzc2VkIGJ5IHRoZSBwaXBlCiAgICBkYXRhID0gdHJhaW5fZGF0YTMsCiAgICAKICAgICMgTm90ZSB0aGF0IHdlIGNhbiBjb250cm9sIHRoZSBkZXB0aCBvZiB0aGUgdHJlZQogICAgIyBhIGRlZXBlciB0cmVlIGluY3JlYXNlIGZpdCBidXQgcmVkdWNlcyBpbnRlcnByZXRhYmlsaXR5CiAgICBjb250cm9sID0gcnBhcnQ6OnJwYXJ0LmNvbnRyb2wobWF4ZGVwdGggPSA0KQogICkKYGBgCgoKTGV0J3Mgbm93IGFzc2VzcyB0aGUgbW9kZWwgZml0LgoKYGBge3J9CnRpYmJsZSh0cnV0aCA9IHRyYWluX2RhdGEzJGNvdmVyYWdlX3Byb2JzLAogICAgICAgZXN0aW1hdGUgPSBwcmVkaWN0KHN1cnJvZ2F0ZV90cmVlKSkgJT4lIAogIHJzcSh0cnV0aCxlc3RpbWF0ZSkKYGBgCgpBbiBSLVNxdWFyZWQgb2YgLjc4IGlzbid0IGJhZC4gSXQgaW5kaWNhdGVzIHRoYXQgdGhlIHN1cnJvZ2F0ZSBtb2RlbCBkb2VzIGEgZ29vZCBqb2IgYXBwcm94aW1hdGluZyB0aGUgYmxhY2sgYm94IG1vZGVsIChpLmUuIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsKS4KCkxldCdzIG5vdyBsb29rIGF0IHRoZSB0cmVlLiAKCmBgYHtyLGZpZy5hbGlnbj0iY2VudGVyIixmaWcuaGVpZ2h0PTUsZmlnLndpZHRoPTEwfQpyYXR0bGU6OmZhbmN5UnBhcnRQbG90KHN1cnJvZ2F0ZV90cmVlLHN1Yj0iIix0eXBlPTEpCmBgYAoKClRoZSBzdXJyb2dhdGUgbW9kZWwgcHJvdmlkZXMgYSBwaWN0dXJlIHRoYXQgY2hpbWVzIHdlbGwgd2l0aCBvdXIgb3RoZXIgaW5zaWdodHMsIGJ1dCBuZXcgb25lcyBjbGVhcmx5IHN0YW5kIG91dC4KCi0gQXMgd2UgcHJldmlvdXNseSBub3RlZCwgdGhlcmUgaXMgYW4gaW50ZXJhY3Rpb24gYmV0d2VlbiBgYWdlYCBhbmQgYHdhZ2VgLiBUaG91Z2ggdGhlcmUgYXJlIHR3byBub3RhYmxlIGludGVyYWN0aW9ucyBnaXZlbiBvbmVzIHdhZ2UuIEFzc3VtaW5nIG9uZSBpcyBsZXNzIHRoYW4gNjUgeWVhcnMgb2YgYWdlIChpLmUuIC42NCBvbiBvdXIgYWdlIHNjYWxlKToKCiAgICArIElmIG9uZSBkb2Vzbid0IG1ha2UgdGhlIHVwcGVyIHF1YW50aWxlIG9mIHRoZSBgd2FnZWAgZGlzdHJpYnV0aW9uLCB0aGVuIGNvdmVyYWdlIGRlcGVuZHMgb24gd2hldGhlciB0aGV5J3JlIGEgY2l0aXplbiBvciBub3QgKHRob3VnaCBub3RlIHRoZSBwcm9iYWJpbGl0aWVzIGFyZSBsb3cgZm9yIGJvdGggc3BsaXRzKS4KICAgICsgSWYgb25lIGRvZXMgbWFrZSBhIGhpZ2ggd2FnZSBhbmQgaXMgbWFycmllZCBoZS9zaGUgaXMgX21vcmVfIGxpa2VseSB0byBiZSBjb3ZlcmVkLgogICAgCi0gSWYgb25lIGlzIG1vcmUgdGhhbiA2NSB5ZWFycyBvZiBgYWdlYCwgY292ZXJhZ2UgbWFpbmx5IGRlcGVuZHMgb24gd2hldGhlciBoZS9zaGUgaXMgYSBjaXRpemVuIG9yIG5vdC4gSWYgeW91J3JlIGFib3ZlIDY1IGFuZCBhIGNpdGl6ZW4sIHlvdSBoYXZlIGEgOTUlIHByb2JhYmlsaXR5IG9mIGJlaW5nIGNvdmVyZWQuICAKICAKKipfTk9URV86IHRoYXQgaWYgd2UgYnVpbHQgYSBkZWVwZXIgbW9kZWwsIGEgcmljaGVyIHN0b3J5IGVtZXJnZXMuKiogQnV0IHRoYXQgc3RvcnkgZG9lc24ndCBkaWZmZXIgbXVjaCBmcm9tIHRoZSBvbmUgd2UndmUgYWxyZWFkeSBiZWVuIHBpZWNpbmcgdG9nZXRoZXIuCgpgYGB7cixmaWcuYWxpZ249ImNlbnRlciIsZmlnLmhlaWdodD01LGZpZy53aWR0aD0xMH0Kc3Vycm9nYXRlX3RyZWUyIDwtCiAgcnBhcnQ6OnJwYXJ0KGNvdmVyYWdlX3Byb2JzIH4gYWdlICsgd2FnZSArIGVkdWMgKyBtYXJfTWFycmllZCArIGNpdF9Ob24uY2l0aXplbiwKICAgIGRhdGEgPSB0cmFpbl9kYXRhMywKICAgIGNvbnRyb2wgPSBycGFydDo6cnBhcnQuY29udHJvbChtYXhkZXB0aCA9IDcpCiAgKQpyYXR0bGU6OmZhbmN5UnBhcnRQbG90KHN1cnJvZ2F0ZV90cmVlMixzdWI9IiIsdHlwZT0xKQpgYGAKCiAgCiMjIyBMaW5lYXIgTW9kZWwKCldlIGNhbiB1c2UgdGhlIGluc2lnaHRzIGFib3ZlIHRvIGJ1aWxkIG91dCBhIGxpbmVhciBtb2RlbCAoYWN0dWFsbHksIGEgbG9naXN0aWMgbW9kZWwgYi9jIHRoZSBvdXRjb21lIGlzIGJpbmFyeSkgdG8gaGVscCB3aXRoIGludGVycHJldGFiaWxpdHkuIApgYGB7cn0KbGluZWFyX3N1cnJvZ2F0ZV9tb2RlbCA8LSAKICB0cmFpbl9kYXRhMiAlPiUgCiAgbXV0YXRlKGNvdmVyYWdlX2R1bW15ID0gMSooY292ZXJhZ2UgPT0gIkNvdmVyYWdlIikpICU+JSAKICBnbG0oY292ZXJhZ2VfZHVtbXkgfiAKICAgICAgICBhZ2Uqd2FnZSArIAogICAgICAgIHdhZ2UqZWR1YyArCiAgICAgICAgbWFyX01hcnJpZWQgKyAKICAgICAgICBjaXRfTm9uLmNpdGl6ZW4sCiAgICAgIGRhdGE9LixmYW1pbHkgPSBiaW5vbWlhbCgibG9naXQiKSkKCiMgUHJpbnQgdGhlIHJlZ3Jlc3Npb24gcmVzdWx0cwpzdGFyZ2F6ZXI6OnN0YXJnYXplcihsaW5lYXJfc3Vycm9nYXRlX21vZGVsLHR5cGUgPSAidGV4dCIpCmBgYAoKV2UgY291bGQgbm93IHRlc3QgdGhlc2UgdGhlb3JldGljYWwgaW5zaWdodHMgb24gbmV3LCBwcmV2aW91c2x5IHVuc2VlbiBkYXRhLi4uIGkuZS4gdGhlIHRlc3QgZGF0YSEgCgpIb3cgd2VsbCBkb2VzIHRoZSB0aGVvcmV0aWNhbCBtb2RlbCBwcmVkaWN0IGZ1dHVyZSBkYXRhPyBQcmV0dHkgd2VsbCwgd2hpY2ggaW5kaWNhdGVzIHRoYXQgb3VyIGluc2lnaHRzIGxpa2VseSBfZ2VuZXJhbGl6ZV8gdG8gdGhlIHRlc3QgZGF0YS4gCgpgYGB7cn0KTWV0cmljczo6YXVjKGFjdHVhbCA9IDEqKHRlc3RfZGF0YTIkY292ZXJhZ2U9PSJDb3ZlcmFnZSIpLAogICAgICAgICAgICAgcHJlZGljdGVkID0gcHJlZGljdChsaW5lYXJfc3Vycm9nYXRlX21vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gdGVzdF9kYXRhMikpCmBgYAoKCgojIFRoZW9yeSBEZXZlbG9wbWVudAoKV2UndmUgbGVhcm5lZCBhIGxvdCBmcm9tIHRoZSB0cmFpbmluZyBkYXRhIGFuZCBtb2RlbCBhYm91dCB0aGUgZmFjdG9ycyB0aGF0IGluY3JlYXNlIHRoZSBsaWtlbGlob29kIG9mIGNvdmVyYWdlLiBXZSd2ZSBnZW5lcmF0ZWQgYSBudW1iZXIgb2YgaW50ZXJlc3RpbmcgaHlwb3RoZXNlczogCgotIFJldGlyZW1lbnQgYWdlIFVTIGNpdGl6ZW5zIGFyZSBtb3JlIGxpa2VseSB0byBoYXZlIGhlYWx0aGNhcmUgY292ZXJhZ2UgdGhhbiBub24tcmV0aXJlbWVudCBhZ2UgY2l0aXplbnMuIAotIFlvdW5nIG1hcnJpZWQgaW5kaXZpZHVhbHMgbWFraW5nIGEgaGlnaCBzYWxhcnkgYXJlIG1vcmUgbGlrZWx5IHRvIGJlIGNvdmVyZWQgdGhhbiB0aGVpciBub24tbWFycmllZCBjb3VudGVycGFydHMuCi0gQ292ZXJhZ2UgbGFyZ2VseSBkZXBlbmRzIG9uIGFuIGluZGl2aWR1YWxzIGluY29tZSB1bmxlc3MgaGUvc2hlIGlzIGFib3ZlIDY1IHllYXJzIG9mIGFnZS4gCg==