Download the .Rmd directly at the top-right.

Full repo: https://github.com/jrfarrer/stats701_hw2/

Published file: https://jrfarrer.github.io/stats701_hw2/

# Set the seed for reproducibility
set.seed(44)
# Set the locale of the session so languages other than English can be used
invisible(Sys.setlocale("LC_ALL", "en_US.UTF-8"))
# Prevent printing in scientific notation
options(digits = 4, width = 220)
# Create a logger function
logger <- function(msg, level = "info", file = log_file) {
    cat(paste0("[", format(Sys.time(), "%Y-%m-%d %H:%M:%S.%OS"), "][", level, "] ", msg, "\n"), file = stdout())
}
# Set the project directory
base_dir <- ''
data_dir <- paste0(base_dir, "data/")
code_dir <- paste0(base_dir, "code/")
viz_dir <- paste0(base_dir, "viz/")
dir.create(data_dir, showWarnings = FALSE)
dir.create(code_dir, showWarnings = FALSE)
dir.create(viz_dir, showWarnings = FALSE)
# To the font second font, run the following two lines of code and add name of user to vector
# system(paste0("cp -r ",viz_dir,"fonts/. ~/Library/Fonts/")) # instantaneous
# font_import() # takes approximately 5-10 min
users_v <- c("Jordan")
# Create a color palette
pal538 <- ggthemes_data$fivethirtyeight
# Create a theme to use throughout the analysis
theme_jrf <- function(base_size = 8, base_family = ifelse(Sys.info()[['user']] %in% users_v, "DecimaMonoPro", "Helvetica")) {
    theme(
        plot.background = element_rect(fill = "#F0F0F0", colour = "#606063"), 
        panel.background = element_rect(fill = "#F0F0F0", colour = NA), 
        panel.border = element_blank(),
        panel.grid.major =   element_line(colour = "#D7D7D8"),
        panel.grid.minor =   element_line(colour = "#D7D7D8", size = 0.25),
        panel.margin =       unit(0.25, "lines"),
        panel.margin.x =     NULL,
        panel.margin.y =     NULL,
        axis.ticks.x = element_blank(), 
        axis.ticks.y = element_blank(),
        axis.title = element_text(colour = "#A0A0A3"),
        axis.text.x = element_text(vjust = 1, family = 'Helvetica', colour = '#3C3C3C'),
        axis.text.y = element_text(hjust = 1, family = 'Helvetica', colour = '#3C3C3C'),
        legend.background = element_blank(),
        legend.key = element_blank(), 
        plot.title = element_text(face = 'bold', colour = '#3C3C3C', hjust = 0),
        text = element_text(size = 9, family = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica")),
        title = element_text(family = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica"))
        
    )
}

1 Question 1

1.1 Data Loading & Cleaning

# Load the csv with meaningful column names
framingham_raw <- read.table(paste0(data_dir, "Framingham.dat"), sep = ",", header = TRUE, as.is = TRUE) %>% as_tibble
    
framingham_cleaning <- 
    framingham_raw %>%
    rename(hd = `Heart.Disease.`)
framingham_cleaning <- 
    framingham_cleaning %>% 
    setNames(tolower(names(.))) %>%
    mutate(
        sex = factor(tolower(sex))
        , hd = factor(hd)
    )

We load the Framingham.dat file and summarize the 8 variables and 1407 records.

head(framingham_cleaning)

We then remove 14 observations that have missing values.

framingham_cleaning2 <- 
    framingham_cleaning %>% 
    filter(complete.cases(.))
pander(summary(framingham_cleaning2), missing = "", split.table = Inf)
hd age sex sbp dbp chol frw cig
0:1086 Min. :45.0 female:730 Min. : 90 Min. : 50.0 Min. : 96 Min. : 52 Min. : 0.00
1: 307 1st Qu.:48.0 male :663 1st Qu.:130 1st Qu.: 80.0 1st Qu.:200 1st Qu.: 94 1st Qu.: 0.00
Median :52.0 Median :142 Median : 90.0 Median :230 Median :103 Median : 0.00
Mean :52.4 Mean :148 Mean : 90.2 Mean :235 Mean :105 Mean : 8.04
3rd Qu.:56.0 3rd Qu.:160 3rd Qu.: 98.0 3rd Qu.:264 3rd Qu.:114 3rd Qu.:20.00
Max. :62.0 Max. :300 Max. :160.0 Max. :430 Max. :222 Max. :60.00
framingham_final <- framingham_cleaning2

To get a better idea of this dataset, we create a pairs plot colored by observations without heart disease (hd = 0) and with heart disease (hd = 1).

fpairs <-ggpairs(framingham_final, mapping = aes(colour = hd))
for (row in seq_len(fpairs$nrow))
    for (col in seq_len(fpairs$ncol))
        fpairs[row, col] <- fpairs[row, col] + scale_colour_manual(values = c(pal538['blue'][[1]], pal538['red'][[1]])) + scale_fill_manual(values = c(pal538['blue'][[1]], pal538['red'][[1]]))    
fpairs

For the variable sex, we create a frequency table.

pander(descr::CrossTable(framingham_final$hd, framingham_final$sex, prop.chisq=FALSE, prop.t = FALSE, chisq = TRUE, dnn = c("HD","Sex")), split.table = Inf, big.mark = ',')
 
HD
Sex
female
 
male
 
Total
0
N
Row(%)
Column(%)
 
610
56.17%
83.56%
 
476
43.83%
71.79%
 
1086
77.96%
1
N
Row(%)
Column(%)
 
120
39.09%
16.44%
 
187
60.91%
28.21%
 
307
22.04%
Total
730
52.4049%
663
47.5951%
1393

1.2 First Model

1.2.1 Model

We fit a logistic regression model to predict heart disease with only the explanatory variable systolic blood pressure (SBP).

fit1 <- glm(hd ~ sbp, data = framingham_final, family = binomial('logit'))
tidy(fit1) %>% pander(caption = paste0(as.character(summary(fit1)$call)[3],': ', as.character(summary(fit1)$call)[2]))
binomial(“logit”): hd ~ sbp
term estimate std.error statistic p.value
(Intercept) -3.655 0.3479 -10.51 8.075e-26
sbp 0.01581 0.002222 7.118 1.097e-12
glance(fit1) %>% pander()
null.deviance df.null logLik AIC BIC deviance df.residual
1469 1392 -708.7 1421 1432 1417 1391

The next variable we choose will have the largest \(|z|\) statistic or the smallest \(p\) value. We create 6 models that are hd ~ sbp + var and extract the summary of each variable var.

vars_rotating <- names(framingham_final)[!(names(framingham_final) %in% c("hd","sbp"))]
best_second_var <- data_frame()
for(var in vars_rotating) {
    best_second_var <- bind_rows(best_second_var,
    tidy(glm(paste0('hd ~ sbp + ', var), data = framingham_final, family = binomial('logit'))) %>% 
        filter(row_number() == 3) %>%
        rename(`z.statistic` = statistic)
    )
}
best_second_var %>%
    arrange(p.value)

We see that sex will be the most important addition on top of sbp.

fit2 <- glm(hd ~ sbp + sex, data = framingham_final, family = binomial('logit'))
fit2_summary <- summary(fit2)
tidy(fit2) %>% pander(caption = paste0(as.character(summary(fit2)$call)[3],': ', as.character(summary(fit2)$call)[2]))
binomial(“logit”): hd ~ sbp + sex
term estimate std.error statistic p.value
(Intercept) -4.57 0.3897 -11.73 9.289e-32
sbp 0.01872 0.002324 8.053 8.071e-16
sexmale 0.9034 0.1398 6.464 1.02e-10
glance(fit2) %>% pander()
null.deviance df.null logLik AIC BIC deviance df.residual
1469 1392 -686.9 1380 1395 1374 1390

1.2.2 Residual Deviance

The residual deviance of fit2 is always smaller than fit1 because fit1 is a nested model of fit2 and thus has more degress of freedom.

1.2.3 Wald and Likelihood Tests

We perform the Wald Test which tests whether the set of terms is significant.

confint.default(fit2) %>% pander()
  2.5 % 97.5 %
(Intercept) -5.334 -3.806
sbp 0.01416 0.02327
sexmale 0.6295 1.177
wald_test <- wald.test(b = coef(fit2), Sigma = vcov(fit2), Terms = 1:3)
wald_test$result$chi2 %>% as_tibble() %>% pander(caption = "Wald Test")
Wald Test
  value
chi2 393.9
df 3.0
P 0.0

The Wald Test yields a \(p\)-value of 1.019810^{-10}. The Wald Test for just one variable is the same test performed by the function summary. Below are the results for the Wald Test for just the term sex. Note that the statistic is z-value squared (\(6.464^2 = 41.7831\)) and the \(p\)-values are the same.

wald_test <- wald.test(b = coef(fit2), Sigma = vcov(fit2), Terms = 3)

Next we perform the Likelihood Test (Chi-squared).

chi_sq_fit2 <- anova(fit2, test = "Chisq")
tidy(chi_sq_fit2) %>% pander(missing = '', caption = 'Analysis of Deviance Table')
Analysis of Deviance Table
term df Deviance Resid..Df Resid..Dev Pr..Chi.
NULL 1392 1469
sbp 1 51.86 1391 1417 5.949e-13
sex 1 43.7 1390 1374 3.828e-11

The \(p\)-value for adding the variable sex is 3.827610^{-11}. The \(p\)-value is not the same as the Wald Test. The Wald Test and the Likelihood Test both hold for this new model.

1.3 Modeling Building

1.3.1 Backward Selection

Using backward selection, we are left with a model with 4 predictors: sbp, sex, age, and chol.

fit7 <- glm(hd ~ sbp+sex+age+cig+chol+frw+dbp, data = framingham_final, family = binomial('logit'))
fit6 <- glm(hd ~ sbp+sex+age+cig+chol+frw, data = framingham_final, family = binomial('logit'))
fit5 <- glm(hd ~ sbp+sex+age+cig+chol, data = framingham_final, family = binomial('logit'))
fit4 <- glm(hd ~ sbp+sex+age+chol, data = framingham_final, family = binomial('logit'))
tidy(fit4) %>% pander(caption = paste0(as.character(summary(fit4)$call)[3],': ', as.character(summary(fit4)$call)[2]))
binomial(“logit”): hd ~ sbp + sex + age + chol
term estimate std.error statistic p.value
(Intercept) -8.409 0.9086 -9.255 2.151e-20
sbp 0.01696 0.002362 7.179 7.021e-13
sexmale 0.9899 0.1451 6.824 8.842e-12
age 0.05664 0.0145 3.906 9.377e-05
chol 0.00448 0.001495 2.996 0.002737
glance(fit4) %>% pander()
null.deviance df.null logLik AIC BIC deviance df.residual
1469 1392 -674.5 1359 1385 1349 1388

1.3.2 AIC Criterion

Next we use exahustive search to find the best model using the AIC criterion.

Xy <- bind_cols(model.matrix(hd ~.+0, framingham_final) %>% as_tibble(), 
                framingham_final %>% select(hd)) 
exhaustive_model <- bestglm(as.data.frame(Xy), family = binomial('logit'),
                            method = "exhaustive", IC = "AIC", nvmax = 10)
Morgan-Tatar search since family is non-gaussian.
exhaustive_model$BestModels

This method is not guaranteed to include only variables with \(p\)-values less than 0.05. In this example, the best model includes frw which the glm summary shows is not significant at the 0.05 level. Thus, the best model returned by exhaustive search is not the same as the model returned by backwards elimination.

final_model <- exhaustive_model$BestModel
tidy(final_model) %>% pander(caption = "Best model returned by exhaustive search")
Best model returned by exhaustive search
term estimate std.error statistic p.value
(Intercept) -9.228 0.9962 -9.263 1.979e-20
age 0.06153 0.01478 4.164 3.122e-05
sexmale 0.9113 0.1571 5.8 6.633e-09
sbp 0.01597 0.002487 6.42 1.366e-10
chol 0.004493 0.001503 2.99 0.002794
frw 0.006039 0.004004 1.508 0.1315
cig 0.01228 0.006088 2.017 0.04369
glance(final_model) %>% pander()
null.deviance df.null logLik AIC BIC deviance df.residual
1469 1392 -671.6 1357 1394 1343 1386

1.3.3 Final Model

The final logistic regression model predicts whether or not an individual will have heart disease (hd) with 6 variables. The six variables are listed in the order of importance to the model below:

summary_vars <- 
    data_frame(
        variable = rownames(coef(summary(final_model))[-1, ])
        , p_value = coef(summary(final_model))[-1, 4]
        ) %>% 
        mutate(
            estimate = coef(summary(final_model))[-1, 1]
            , odds = exp(estimate)
            , prob = odds / (1 + odds)
            , percentage = paste0(round(prob * 100, 2), "%")
        ) %>%
        arrange(p_value)
summary_vars %>% select(-percentage)

Holding all other variables constant,

  • A one-unit increase in sbp, we expect to see about 50.4% increase in the probability of having heart disease.
  • The difference of being a male rather an a female, we expect to see about 71.33%% increase in the probability of having heart disease.
  • A one-unit increase in age, we expect to see about 51.54% increase in the probability of having heart disease.
  • A one-unit increase in chol, we expect to see about 50.11% increase in the probability of having heart disease.
  • A one-unit increase in cig, we expect to see about 50.31% increase in the probability of having heart disease.
  • A one-unit increase in frw, we expect to see about 50.15% increase in the probability of having heart disease.

1.3.4 Liz

liz <- data_frame(age = 50, sexmale = 0, sbp = 110, dbp = 80, chol = 180, frw = 105, cig = 0)
liz_predict <- exp(predict(final_model, liz, type = 'response')) / (1 + exp(predict(final_model, liz, type = 'response')))

The probability that Liz will have heart disease with our final model is 0.5124.

1.4 Classification

1.4.1

fit1_classificer <-
    data_frame(
        pred = framingham_final %>% mutate(pred = ifelse(sbp > 176, 1, 0)) %>% select(pred) %>% unlist(), 
        true = framingham_final %>% select(hd) %>% unlist()
        ) %>%
        mutate(type = ifelse(pred == true, ifelse(pred == 0, "tn", "tp"), ifelse(pred == 0, "fn", "fp"))) %>%
    group_by(type) %>%
    count() %>%
    spread(type, n) %>%
    summarise(fpr = fp / (fp + tn), tpr = tp / (tp + fn))

The ROC curve is used to assess the accuracy of a continuous variable for predicting a binary outcome. In the ROC above the continuous variable is systolic blood pressure (sbp) and the binary outcome is whether the individual will have heart disease (hd).

plot_df <- 
    data_frame(
        hd = framingham_final %>% mutate(hd = as.integer(hd) - 1) %>% select(hd) %>% unlist()
        , fitted_fit1 = fit1$fitted
        , fitted_fit2 = fit2$fitted
    )
#p1 <- 
ggplot(plot_df, aes(d = hd, m = fitted_fit1)) + 
    geom_roc() + style_roc() + 
    theme_jrf() +
    labs(title = "ROC for fit1") +
    geom_vline(xintercept = 0.1) + 
    geom_label(data = data_frame(hd = 0.11, fitted_fit1 = 0.8, label = "False Positive\nRate = 1"), 
               aes(x = hd, y = fitted_fit1, label = "False Positive Rate = 1"), hjust = 'left') + 
    geom_point(data = data_frame(hd = fit1_classificer$fpr, fitted_fit1 = fit1_classificer$tpr), 
               aes(x = hd, y = fitted_fit1), colour = pal538['red'][[1]]) +
    geom_text(data = data_frame(hd = fit1_classificer$fpr + 0.02, fitted_fit1 = fit1_classificer$tpr, text = "SBP > 176"), 
               aes(x = hd, y = fitted_fit1, label = text), colour = pal538['red'][[1]], hjust = 0.1)

The classifier such that the False Positive Rate is less than 0.1 and the true positive rate is as high as possible is SBP > 176, then HD = 1, else HD = 0.

1.4.2 Two ROC Curves

plot_df2 <- 
    plot_df %>% 
    gather(model, fitted, -hd) %>%
    mutate(model = gsub("fitted_","", model))
ggplot(plot_df2, aes(d = hd, m = fitted, colour = model)) + 
    geom_roc() + style_roc() + 
    theme_jrf() +
    labs(title = "ROC for fit1 and fit2") +
    scale_colour_manual("Model", values = c('fit1' = pal538['blue'][[1]], 'fit2' = pal538['red'][[1]]))

The ROC curve for fit2 always contains the ROC curve for fit1. The AUC of the ROC for fit2 is 0.6801 and AUC of the ROC for fit1 is 0.6357. We expected the AUC for fit2 to be larger than the AUC for fit1 because fit1 is a nested model.

1.4.3 Prediction Values

values_df <- 
    plot_df2 %>%
        mutate(pred = as.integer(fitted > .5)) %>%
        mutate(type = ifelse(pred == hd, ifelse(pred == 0, "tn", "tp"), ifelse(pred == 0, "fn", "fp"))) %>%
        group_by(model, type) %>%
        count() %>%
        ungroup() %>%
        spread(type, n) %>%
        mutate(
            positive_prediction_value = tp / (tp + fp)
            , negative_prediction_value = tn / (tn + fn)
        )
values_df %>%
    select(model, positive_prediction_value, negative_prediction_value) %>%
    pander()
model positive_prediction_value negative_prediction_value
fit1 0.4500 0.7830
fit2 0.4722 0.7863

fit2 is more desireable if we care about Positive Prediction Value because the PPV is 0.4722 for fit2 vs 0.45 for fit1.

1.4.4 PP/NP vs Thresholds

ppnp_vs_threshold_df <- data_frame()
prob_thresholds <- seq(from = 0, to = 1, by = 0.01)
for (threshold in prob_thresholds) {
    ppnp_vs_threshold_df <-
        bind_rows(
            ppnp_vs_threshold_df,  
            plot_df2 %>%
                mutate(pred = as.integer(fitted > threshold)) %>%
                mutate(
                    tn = as.integer(pred == hd & pred == 0)
                    , tp = as.integer(pred == hd & pred != 0)
                    , fn = as.integer(pred != hd & pred == 0)
                    , fp = as.integer(pred != hd & pred != 0)
                ) %>%
                group_by(model) %>%
                summarise(
                    tn = sum(tn)
                    , tp = sum(tp)
                    , fn = sum(fn)
                    , fp = sum(fp)
                ) %>%
                mutate(
                    positive_prediction_value = tp / (tp + fp)
                    , negative_prediction_value = tn / (tn + fn)
                ) %>%
                mutate(
                    threshold = threshold
                    , ppnp = positive_prediction_value / negative_prediction_value
                ) %>%
                select(threshold, model, positive_prediction_value, negative_prediction_value, ppnp)
        )
}
ggplot(ppnp_vs_threshold_df, aes(x = threshold, y = ppnp, colour = as.factor(model))) +
    geom_line() + geom_point() +
    theme_jrf() +
    scale_colour_manual("Model", values = c('fit1' = pal538['blue'][[1]], 'fit2' = pal538['red'][[1]])) +
    labs(title = "PP/NP vs Probability Threshold",
         x = "Probability Threshold", y = "Positive Prediction Value / Negative Prediction Value")

 ppnp_vs_threshold_df %>%
     select(-ppnp) %>%
     gather(metric, value, -threshold, -model) %>%
     ggplot(aes(x = threshold, y = value, colour = model)) + facet_grid(. ~ metric) + 
     geom_line() + geom_point() +
     theme_jrf() +
     scale_colour_manual("Model", values = c('fit1' = pal538['blue'][[1]], 'fit2' = pal538['red'][[1]])) +
     labs(title = "Positive Prediction Value & Negative Prediction Value",
         x = "Probability Threshold", y = "Prediction Value") +
    theme(legend.position = 'bottom')

I would choose fit2, but fit1 and fit2 are nearly the same. I would want to ise fit2 with a probability threshold slightly above 0.5.

1.5 Bayes Rule

1.5.1 a

If the risk ratio is \(\frac{a_{1,0}}{a_{0,1}} = 10\) then \(\frac{a_{0,1}}{a_{1,0}} = 0.1\). The threshold over the \(P(Y=1|x) > \frac{0.1}{1 + 0.1} = 0.0909\) or \(logit > log(\frac{0.0909}{1 - 0.0909}) = -2.3026 = log(0.1)\) gives us the Bayes rule.

We have the logit from 1 b), thus the linear boundary for the Bayes classifier is

\[-9.2279 + 0.0615age + 0.9113sexmale + 0.016sbp + 0.0045chol + 0.006frw + 0.0123cig \\ = log(0.1) = -2.3026\]

1.5.2 b

mce_df <- 
    data_frame(
        hd = framingham_final$hd
        , fitted = final_model$fitted.values
        , pred = as.integer(fitted > 0.0909)
        , a10 = 10 * ifelse(hd == 1 & pred != 1, 1, 0)
        , a01 = 1  * ifelse(hd == 0 & pred != 0, 1, 0)
        , a10_a01 = a10 + a01
    )
mce <- sum(mce_df$a10_a01) / nrow(mce_df)

We use the formula for weighted misclassification error

\[MCE=\frac{a_{10} \sum_{\hat{y}_i=1} I\{y_i \neq \hat{y}_i\} + a_{01} \sum_{\hat{y}_i=0} I\{y_i \neq \hat{y}_i\}}{n}\]

For the risk ratio of \(\frac{a_{1,0}}{a_{0,1}} = 10\), the MCE is 0.7143.

1.5.3 c

liz_pred_logit <- predict(final_model, liz, type = 'link')

For Liz, the logit is -2.9523 and thus we classify her as 0 or will not have heart disease.

1.5.4 d & e

Below is a plot of the posterior thresholds by the associated weighted misclassification rates using the two risk ratios \(\frac{a_{1,0}}{a_{0,1}} = 10\) and \(\frac{a_{1,0}}{a_{0,1}} = 1\).

threshold_mce_df <- data_frame()
posterior_thresholds <- seq(
 from = round(exp(min(final_model$fitted)) / (1 + exp(min(final_model$fitted))), 2) - 0.01, 
 to = round(exp(max(final_model$fitted)) / (1 + exp(max(final_model$fitted))), 2), 
 length.out = 40)
for (threshold in posterior_thresholds) {
    threshold_mce_df <-
        bind_rows(
        threshold_mce_df, 
            data_frame(
                hd = framingham_final$hd
                , fitted = final_model$fitted.values
                , pred = as.integer(fitted > log(threshold / (1 - threshold)))
                , a10_10 = 10 * ifelse(hd == 1 & pred != 1, 1, 0)
                , a10_1  = 1  * ifelse(hd == 1 & pred != 1, 1, 0)
                , a01    = 1  * ifelse(hd == 0 & pred != 0, 1, 0)
                , a10_a01_10 = a10_10 + a01
                , a10_a01_1  = a10_1  + a01
            ) %>% 
            summarise(
                mce_10 = sum(a10_a01_10) / n()
                , mce_1 = sum(a10_a01_1) / n()
            ) %>%
            mutate(threshold = threshold) %>%
            select(threshold, mce_10, mce_1)
        )
}
threshold_mce_df2 <-
    threshold_mce_df %>%
    select(threshold, mce_10, mce_1) %>%
    gather(risk_ratio, mce, -threshold) %>%
    mutate(risk_ratio = gsub("mce_","",risk_ratio))
ggplot(threshold_mce_df2, aes(x = threshold, y = mce, colour = as.factor(risk_ratio))) +
    geom_line() + geom_point() +
    theme_jrf() +
    scale_colour_manual("Risk Ratio", values = c('10' = pal538['blue'][[1]], '1' = pal538['red'][[1]])) +
    labs(title = "Misclassification Error (MCE) vs Posterior Threshold",
         x = "Posterior Threshold", y = "Weighted Misclassification Error (MCE)")

The range of the x-axis is from 0.5 to 0.7 which reflects the range of the fitted values from the final model for 1 b). We know from model the fitted values have a range from 0.0272 to 0.8301 which when exponentiated and converted to a probability is 0.5 to 0.7.

When \(\frac{a_{1,0}}{a_{0,1}} = 10\) the Bayes rule classifier does not perform well for large thresholds. When the threshold is 0.5, the rule is all observations are hd = 1 which means that \(a_{10} \sum_{\hat{y}_i=1} I\{y_i \neq \hat{y}_i\}\) goes to 0. The misclassification initially goes down, but then there is a huge penality for false negatives.

The Bayes rule classifier with \(\frac{a_{1,0}}{a_{0,1}} = 1\) performs much better and indeed the misclassification rate decreases as the threshold increases.

2 Question 2

bills_train <- read.csv(paste0(data_dir, 'Bills.subset.csv'), row.names = 1, na.strings=c("","NA")) %>% as_tibble()
bills_test <- read.csv(paste0(data_dir, 'Bills.subset.test.csv'), row.names = 1) %>% as_tibble()

2.1 Executive Summary

Our objective is to predict whether or not a bill will pass the the floor of the Pennsylvania State House based on characteristics of the bill. Some of the characteristics of bills that we use in this analysis is the number amendments the bill had, the session the bill was introduced, and the number of cosponsors. Using the process of training/test datasets and cross-validation, we built a logistic regression model for this prediction exercise. However, a bill passing the State House is a rare event. Less than 7% of introduced bills have passed the House since the 2009-2010 session. As a result, our final model is effective at identifying bills that will not pass, but is less effective at identifying bills that will pass.

2.2 Summary of Data

Collectively, the bills dataset contains 8011 observations split into training (7011) and test (1000) sets.

pass_vals <- c("bill:passed", "governor:signed", "governor:received")
bills_train2 <-
    bills_train %>%
    mutate(
        status2 = as.factor(as.numeric(status %in% pass_vals))
        , dow = factor(day.of.week.introduced, labels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat"))
        , is_sponsor_in_leadership2 = as.factor(is_sponsor_in_leadership)
    )
bills_test2 <-
    bills_test %>%
    mutate(
        status2 = as.factor(as.numeric(status %in% pass_vals))
        , dow = factor(day.of.week.introduced, labels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat"))
        , is_sponsor_in_leadership2 = as.factor(is_sponsor_in_leadership)
    )

We find that 364 observations in the training set are missing at least one value (1 in the test set). Let’s look by feature.

bills_train2 %>%
    summarise_all(funs(sum(is.na(.)))) %>%
    gather(feature, NAs) %>%
    arrange(desc(NAs))

There are 5 values of status that are NA. Based on the assignment’s comment that “All other outcomes are failures” we will assume these are failures to pass rather than missing values. We will remove the observations without day of week (from training and test datasets) so that different models are comparable.

bills_train3 <- 
    bills_train2 %>%
    select(-originating_committee) %>%
    filter(complete.cases(.))
bills_test3 <- 
    bills_test2 %>%
    select(-originating_committee) %>%
    filter(complete.cases(.))
pander(summary(bills_train3), missing = "", split.table = 150)
Table continues below
bill_id sponsor_party session num_cosponsors num_d_cosponsors num_r_cosponsors title_word_count
HB-1-2009-2010 : 1 Democratic:3212 2009-2010 :2738 Min. : 0.0 Min. : 0.0 Min. : 0.0 Min. : 6
HB-1-2011-2012 : 1 Republican:3692 2009-2010 Special Session #1 (Transportation): 0 1st Qu.: 11.0 1st Qu.: 3.0 1st Qu.: 3.0 1st Qu.: 22
HB-10-2009-2010 : 1 2011-2012 :2678 Median : 20.0 Median : 8.0 Median : 8.0 Median : 27
HB-10-2011-2012 : 1 2013-2014 :1488 Mean : 23.5 Mean :10.5 Mean :13.1 Mean : 34
HB-100-2009-2010: 1 3rd Qu.: 32.0 3rd Qu.:15.0 3rd Qu.:19.0 3rd Qu.: 34
HB-100-2011-2012: 1 Max. :165.0 Max. :90.0 Max. :99.0 Max. :751
(Other) :6898
Table continues below
day.of.week.introduced num_amendments status is_sponsor_in_leadership num_originating_committee_cosponsors
Min. :1.00 Min. :0.000 committee:referred:6015 Min. :0.000 Min. : 0.00
1st Qu.:2.00 1st Qu.:0.000 governor:signed : 427 1st Qu.:0.000 1st Qu.: 0.00
Median :3.00 Median :0.000 bill:reading:1 : 232 Median :1.000 Median : 1.00
Mean :2.72 Mean :0.179 committee:passed : 106 Mean :0.599 Mean : 2.06
3rd Qu.:4.00 3rd Qu.:0.000 bill:reading:2 : 72 3rd Qu.:1.000 3rd Qu.: 3.00
Max. :6.00 Max. :8.000 bill:passed : 31 Max. :1.000 Max. :19.00
(Other) : 21
num_originating_committee_cosponsors_r num_originating_committee_cosponsors_d status2 dow is_sponsor_in_leadership2
Min. : 0.00 Min. :0.000 0:6442 Mon:1628 0:2770
1st Qu.: 0.00 1st Qu.:0.000 1: 462 Tue:1630 1:4134
Median : 1.00 Median :0.000 Wed:1634
Mean : 1.34 Mean :0.712 Thu:1104
3rd Qu.: 2.00 3rd Qu.:1.000 Fri: 892
Max. :13.00 Max. :8.000 Sat: 16

To explore the data further we create a pairs plot.

bill_pairs <-ggpairs(bills_train3 %>% select(status2, sponsor_party, session, is_sponsor_in_leadership2, dow), 
                     mapping = aes(colour = status2))
for (row in seq_len(bill_pairs$nrow))
    for (col in seq_len(bill_pairs$ncol))
        bill_pairs[row, col] <- bill_pairs[row, col] + scale_colour_manual(values = c(pal538['blue'][[1]], pal538['red'][[1]])) + scale_fill_manual(values = c(pal538['blue'][[1]], pal538['red'][[1]]))    
bill_pairs

bill_pairs2 <-ggpairs(bills_train3 %>% select(status2, num_cosponsors, num_d_cosponsors, num_r_cosponsors, title_word_count, num_amendments, num_originating_committee_cosponsors, num_originating_committee_cosponsors_r, num_originating_committee_cosponsors_d), mapping = aes(colour = status2))
for (row in seq_len(bill_pairs2$nrow))
    for (col in seq_len(bill_pairs2$ncol))
        bill_pairs2[row, col] <- bill_pairs2[row, col] + scale_colour_manual(values = c(pal538['blue'][[1]], pal538['red'][[1]])) + scale_fill_manual(values = c(pal538['blue'][[1]], pal538['red'][[1]]))    
bill_pairs2

Based on the pairs plots we make the following observations

  1. A bill passing is a relatively rare event (462 in 6904 or 6.69%)
  2. Whether the bill’s sponser is in leadership does not have as a large of effect as expected
  3. The number of amendments features looks to be relatively powerful (fourth column from the right above)

Given the partisanship of the PA, we would imagine that a bill’s successful is likely dependent on the party sponsor. We create a frequency table and Pearson’s Chi-squared test.

pander(descr::CrossTable(bills_train3$status2, bills_train3$sponsor_party, prop.chisq=FALSE, prop.t = FALSE, chisq = TRUE, dnn = c("Status","Sponsor Party")), split.table = Inf, big.mark = ',')
 
Status
Sponsor Party
Democratic
 
Republican
 
Total
0
N
Row(%)
Column(%)
 
3082
47.842%
95.953%
 
3360
52.158%
91.008%
 
6442
93.308%
1
N
Row(%)
Column(%)
 
130
28.139%
4.047%
 
332
71.862%
8.992%
 
462
6.692%
Total
3212
46.5238%
3692
53.4762%
6904
pander(chisq.test(bills_train3$status2, bills_train3$sponsor_party), caption = "Pearson's Chi-squared test with Yates' continuity correction")
Pearson’s Chi-squared test with Yates’ continuity correction
Test statistic df P value
66.48 1 3.533e-16 * * *

We also look at the relationship between number of amendments and whether the bills passed. We note that bills with at least one amendment had 31.15% pass rate vs 2.94% for bills without an amendment.

bills_train3 %>%
    group_by(num_amendments, status2) %>%
    count() %>%
    spread(status2, n) %>%
    mutate(`Total` = as.integer(ifelse(is.na(`0`), 0 ,`0`)) + `1`) %>%
    rename(`Amendments | Status` = num_amendments) %>%
    pander(missing = '')
Amendments | Status 0 1 Total
0 5810 176 5986
1 554 146 700
2 65 84 149
3 11 37 48
4 2 11 13
5 5 5
6 2 2
8 1 1
bills_train3 %>%
    mutate(amendments = ifelse(num_amendments == 0, "0 Amendments", "1+ Amendments")) %>%
    group_by(amendments) %>%
    summarise(pass_rate = sum(status2 == 1) / n()) %>%
    ungroup() %>%
    spread(amendments, pass_rate) %>%
    pander()
0 Amendments 1+ Amendments
0.0294 0.3115

The many continous explanatory variables are derviatives of another and we would expect to so see some colinearity among these variables. The correlation matrix belows shows that many variables are very closely positively correlated to each other.

bills_train3 %>% 
    select(num_cosponsors, num_d_cosponsors, num_r_cosponsors, title_word_count, num_amendments,
           num_originating_committee_cosponsors, num_originating_committee_cosponsors_r, 
           num_originating_committee_cosponsors_d) %>%
    cor() %>%
    #corrplot(method="square", tl.cex = 1/par("cex"), tl.col = 'black', tl.pos = 'l')
    corrplot(type = "upper", tl.pos = "td", method = "square", tl.cex = 0.5, tl.col = 'black', diag = FALSE)

2.3 Classifier

2.3.1 Feature Engineering

We quickly notice that what is missing is a feature for the bipartisan support of a bill. In a state like Pennsylvania, whether or not a bill passes is likely a function of the bipartisanship of the bill. We can create an indicator feature as to whether or not a bill has both Democrat and Republican sponsors.

bills_train4 <- 
    bills_train3 %>%
    mutate(is_bipartisan = factor(ifelse(num_d_cosponsors > 0 & num_r_cosponsors > 0, 1, 0)))
bills_test4 <- 
    bills_test3 %>%
    mutate(is_bipartisan = factor(ifelse(num_d_cosponsors > 0 & num_r_cosponsors > 0, 1, 0)))
bipartisan_tbl <- table(bills_train4$status2, bills_train4$is_bipartisan)
pander(descr::CrossTable(bills_train4$status2, bills_train4$is_bipartisan, prop.chisq=FALSE, prop.t = FALSE, chisq = TRUE, dnn = c("Status","Bipartisan")), split.table = Inf, big.mark = ',')
 
Status
Bipartisan
0
 
1
 
Total
0
N
Row(%)
Column(%)
 
1043
16.191%
91.813%
 
5399
83.809%
93.603%
 
6442
93.308%
1
N
Row(%)
Column(%)
 
93
20.130%
8.187%
 
369
79.870%
6.397%
 
462
6.692%
Total
1136
16.4542%
5768
83.5458%
6904

We see that of the bills that do pass, nearly 79.87% of them have bipartisan support.

2.3.2 Backwards Selection

We begin by building a model with all the predictors we think could affect the status of a bill. As expected from our exploratory analysis, we find the number of cosponsors from each party and the number of party members on each originating committee is correlated to the number of cosponsors and number of members on each originating committee.

We remove these and continue with backward selection until all the variables in the logistic regression model are significant at the 0.01 confidence level. Our initial model is shown below.

bills_fit1 <- glm(status2 ~ session + dow + is_sponsor_in_leadership2 + sponsor_party + is_bipartisan +
                      num_cosponsors + num_d_cosponsors + num_r_cosponsors + title_word_count + num_amendments +
                      num_originating_committee_cosponsors + num_originating_committee_cosponsors_r +
                      num_originating_committee_cosponsors_d, data = bills_train4, family = binomial)
bills_fit2 <- glm(status2 ~ session + dow + is_sponsor_in_leadership2 + sponsor_party + is_bipartisan +
                      num_cosponsors + num_d_cosponsors + title_word_count + num_amendments +
                      num_originating_committee_cosponsors + num_originating_committee_cosponsors_r, 
                      data = bills_train4, family = binomial)
bills_fit3 <- glm(status2 ~ session + dow + is_sponsor_in_leadership2 + sponsor_party + is_bipartisan +
                      num_cosponsors + title_word_count + num_amendments +
                      num_originating_committee_cosponsors + num_originating_committee_cosponsors_r, 
                      data = bills_train4, family = binomial)
bills_fit4 <- glm(status2 ~ session + dow + is_sponsor_in_leadership2 + sponsor_party + is_bipartisan +
                      num_cosponsors + title_word_count + num_amendments +
                      num_originating_committee_cosponsors, data = bills_train4, family = binomial)
bills_fit5 <- glm(status2 ~ session + is_sponsor_in_leadership2 + sponsor_party +
                      num_cosponsors + title_word_count + num_amendments + is_bipartisan +
                      num_originating_committee_cosponsors, data = bills_train4, family = binomial)
bills_fit6 <- glm(status2 ~ session + is_sponsor_in_leadership2 + sponsor_party +
                      title_word_count + num_amendments + is_bipartisan +
                      num_originating_committee_cosponsors, data = bills_train4, family = binomial)
bills_fit7 <- glm(status2 ~ session + sponsor_party + title_word_count + num_amendments + is_bipartisan +
                      num_originating_committee_cosponsors, data = bills_train4, family = binomial)
bills_fit8 <- glm(status2 ~ session + sponsor_party + title_word_count + num_amendments + is_bipartisan, 
                  data = bills_train4, family = binomial)
tidy(bills_fit8) %>% pander(caption = paste0(as.character(summary(bills_fit8)$call)[3],': ', as.character(summary(bills_fit8)$call)[2]))
binomial: status2 ~ session + sponsor_party + title_word_count + num_amendments + is_bipartisan
term estimate std.error statistic p.value
(Intercept) -3.931 0.1742 -22.56 9.9e-113
session2011-2012 0.4799 0.1391 3.45 0.0005601
session2013-2014 0.4677 0.1571 2.977 0.002911
sponsor_partyRepublican 0.7379 0.1287 5.734 9.825e-09
title_word_count 0.004378 0.001094 4.001 6.312e-05
num_amendments 1.82 0.07698 23.64 1.516e-123
is_bipartisan1 -0.5002 0.143 -3.498 0.000468
glance(bills_fit8) %>% pander()
null.deviance df.null logLik AIC BIC deviance df.residual
3391 6903 -1243 2500 2548 2486 6897

2.3.3 Exhaustive Method

We use the package glmulti to select the best model based on AIC. We tried bestglm but it would not process locally even with only a few explantory variables. We start with a more limited set of variables, excluding the breakdowns by Republican and Democrat because they are linear combinations of num_cosponsors and num_originating_committee_cosponsors.

best_glm_bills <- glmulti::glmulti(status2 ~ session + dow + is_sponsor_in_leadership2 + sponsor_party +
                      num_cosponsors + title_word_count + num_amendments + is_bipartisan +
                      num_originating_committee_cosponsors, 
                      data = bills_train4, family = binomial, crit = aic, level = 1, plotty = FALSE, report = FALSE)

glmulit returns a best model with the equations status2 ~ 1 + session + sponsor_party + is_bipartisan + title_word_count + num_amendments + num_originating_committee_cosponsors.

We find that the best model has an AIC of 2496.2076 which is only slightly smaller than the AIC of the final model we found from backwards selection 2500.442.

plot(best_glm_bills, type = 's')

2.3.4 ROC Curve

We plot the ROC curves for the 8 models we have created. The first 6 ROC curves in gray are from the backwards selection process. The ROC curve in with heart disease (hd = 1) is the final model from backwards selection and the with heart disease (hd = 1) ROC curve is the best glm from the glmulti package. Noteablly the curves are relatively similar. This is expected because the same model type (logistic regression) is being used as opposed to another classification model (CART, SVM, etc.)

bills_plot_roc_df <- 
    bills_train4 %>%
    bind_cols(
        data_frame(
            backwards = bills_fit8$fitted
            , glmulti = bills_fit7$fitted
            , other1 = bills_fit6$fitted
            , other2 = bills_fit5$fitted
            , other3 = bills_fit4$fitted
            , other4 = bills_fit3$fitted
            , other5 = bills_fit2$fitted
            , other6 = bills_fit1$fitted
            )
    ) %>%
    select(status = status2, backwards, glmulti, starts_with("other")) %>%
    gather(model, fitted, -status) %>%
    mutate(model = factor(model, 
                          levels = c("other1","other2","other3","other4","other5", "other6","backwards","glmulti"))) %>%
    mutate(status = as.integer(status) - 1)
ggplot(bills_plot_roc_df, aes(d = status, m = fitted, colour = model)) + 
    geom_roc() + style_roc() + 
    theme_jrf() +
    labs(title = "ROC for Backwards Selection and glmulti Models") +
    scale_colour_manual("Model", values = c('backwards' = pal538['blue'][[1]], 'glmulti' = pal538['red'][[1]],
                                            'other1' = pal538['medgray'][[1]], 'other2' = pal538['medgray'][[1]],
                                            'other3' = pal538['medgray'][[1]], 'other4' = pal538['medgray'][[1]],
                                            'other5' = pal538['medgray'][[1]], 'other6' = pal538['medgray'][[1]]
                                            ))

Let’s review the AUC for the 8 curves.

data_frame(
    model = c("other1","other2","other3","other4","other5", "other6","backwards","glmulti")
    , AUC = c(auc(roc(bills_train4$status2, bills_fit1$fitted))[1]
            , auc(roc(bills_train4$status2, bills_fit2$fitted))[1]
            , auc(roc(bills_train4$status2, bills_fit3$fitted))[1]
            , auc(roc(bills_train4$status2, bills_fit4$fitted))[1]
            , auc(roc(bills_train4$status2, bills_fit5$fitted))[1]
            , auc(roc(bills_train4$status2, bills_fit6$fitted))[1]
            , auc(roc(bills_train4$status2, bills_fit8$fitted))[1]
            , auc(roc(bills_train4$status2, bills_fit7$fitted))[1])
    
) %>% 
pander()
model AUC
other1 0.8450
other2 0.8450
other3 0.8457
other4 0.8465
other5 0.8479
other6 0.8480
backwards 0.8451
glmulti 0.8482

2.3.5 \(K\)-fold Cross Validation

We perform \(K\)-fold cross validation on the training dataset with \(K = 10\) for two models we have focused in on: backwards and glmulti.

data_frame(
    model = c('backwards','glmulti')
    , `CV estimate of Prediction Rrror` = c(boot::cv.glm(bills_train4, bills_fit8, K = 10)$delta[[1]],
                                            boot::cv.glm(bills_train4, bills_fit7, K = 10)$delta[[1]])
) %>% 
pander()
model CV estimate of Prediction Rrror
backwards 0.04737
glmulti 0.04766

We see these are incredibly similar models and would be satisified with either model. The \(K\)-folds cross validation was a means to check that we were not overfitting.

2.3.6 Bayes Rule

We propose a loss function such that \(\frac{a_{1,0}}{a_{0,1}} = 5\) in order to minimize false negatives. A bill passing is a very rare event (unfortunately) so we want to maximize the negative predictive value (incidence of false negatives). We see no difference in weighted MCE.

data_frame(
    status = bills_train4$status2
    , fitted_back = bills_fit8$fitted.values
    , fitted_glmulit = bills_fit8$fitted.values
    , pred_back = as.integer(fitted_back > 0.2 / (1 + 0.2))
    , pred_glmulit = as.integer(fitted_glmulit > 0.2 / (1 + 0.2))
    , a10_back = 5 * ifelse(status == 1 & pred_back != 1, 1, 0)
    , a10_glmulti = 5 * ifelse(status == 1 & pred_glmulit != 1, 1, 0)
    , a01_back = 1  * ifelse(status == 0 & pred_back != 0, 1, 0)
    , a01_glmulti = 1  * ifelse(status == 0 & pred_glmulit != 0, 1, 0)
    , a10_a01_back = a10_back + a01_back
    , a10_a01_glmulti = a10_glmulti + a01_glmulti
) %>%
summarise(
    `MCE - Backwards` = sum(a10_a01_back) / n()
    , `MCE - glmulti` = sum(a10_a01_glmulti) / n()
) %>% 
pander()
MCE - Backwards MCE - glmulti
0.2152 0.2152

The confusion table for the backwards selection model is below.

confusion_bills <- table(bills_train4$status2, ifelse(predict(bills_fit8, type = 'response') > .5, 1, 0))
pander(descr::CrossTable(bills_train4$status2, ifelse(predict(bills_fit8, type = 'response') > .5, 1, 0), prop.chisq=FALSE, prop.r = F, prop.c = F, prop.t = TRUE, chisq = TRUE, dnn = c("Status","Prediction")), split.table = Inf, big.mark = ',')
 
Status
Prediction
0
 
1
 
Total
0
N
Total(%)
 
6392
92.5840%
 
50
0.7242%
 
6442
1
N
Total(%)
 
339
4.9102%
 
123
1.7816%
 
462
Total 6731 173 6904

We see that while this model goes a good job at predicting when a bill will not pass (specificity \(= \frac{TN}{TN + FP} = 0.9922\)), it doesn’t have the positive predictive value we’d hope (\(\frac{TP}{TP + FP} = 0.2662\)).

2.3.7 Final Model

As our final model we select the model identified by the backwards selection procedure. We chose this model over the one identified by the glmulti package because it contains fewer explanatory variables but has nearly the same AIC value (2500.442 vs 2496.2076).

We now use the test dataset to find the MCE.

tidy_bills_fit8 <- 
    tidy(bills_fit8) %>%
    mutate(term = gsub("_","\\\\,", term))
bills_test_pred <- ifelse(predict(bills_fit8, bills_test4, type = "response") > 0.5, 1, 0)
confusion_test_bills <- table(bills_test4$status2, bills_test_pred)
pander(descr::CrossTable(bills_test4$status2, bills_test_pred, prop.chisq=FALSE, prop.r = F, prop.c = F, prop.t = TRUE, chisq = TRUE, dnn = c("Status","Prediction")), split.table = Inf, big.mark = ',')
 
Status
Prediction
0
 
1
 
Total
0
N
Total(%)
 
918
91.892%
 
13
1.301%
 
931
1
N
Total(%)
 
50
5.005%
 
18
1.802%
 
68
Total 968 31 999

We see that the unweighted MCE is 0.0631. However, what we see is that again the model is good at predicting when a bill will not pass but has difficulty properly predicting a true positive (positive prediction value \(= \frac{TP}{TP + FP} = 0.2647\))

The final model is

\[\hat{y}_i = -3.9313 + 0.4799session2011-2012 + 0.4677session2013-2014 + 0.7379sponsor\,partyRepublican + \\ 0.0044title\,word\,count + 1.8198num\,amendments + -0.5002is\,bipartisan1\]

summary_vars_bills <- 
    data_frame(
        variable = rownames(coef(summary(bills_fit8))[-1, ])
        , p_value = coef(summary(bills_fit8))[-1, 4]
        ) %>% 
        mutate(
            estimate = coef(summary(bills_fit8))[-1, 1]
            , odds = exp(estimate)
            , prob = odds / (1 + odds)
            , percentage = paste0(round(prob * 100, 2), "%")
        ) %>%
        arrange(p_value)
summary_vars_bills %>% select(-percentage)

Holding all other variables constant,

  • An increase by one amendment to the bill, we expect to see about 86.05% increase in the probability of a bill passing.
  • The difference of a bill being sponsored by a Republican vs a Democrat, we expect to see about 67.65% increase in the probability of a bill passing.
  • The difference of a bill being bipartisan vs non-bipartisan, we expect to see about 37.75% increase in the probability of a bill passing.
  • A one word increase in the number of words in the bill’s title, we expect to see about 50.11% increase in the probability of a bill passing.
  • The difference of being in session 2011-2012 vs 2009-2010, we expect to see about 61.77% increase in the probability of a bill passing.
  • The difference of being in session 2013-2014 vs 2009-2010, we expect to see about 61.48% increase in the probability of a bill passing.

2.4 Suggestions

In considering how to better improve this model, we would want to consider gathering

  • How long was/has the bill been debated on the floor of the house (in days)
  • The number of outside lobbying groups involved in the authorship of the bill
  • The number of words in the bill
  • Some type of classification of the bill (i.e. tax reform, education spending, healthcare, public works, etc)

We think that these features could potentially help improve the classifier. However, we also recognize that a different family of classifier might be helpful for this type of problem, particularly CART.

LS0tCnRpdGxlOiAiU1RBVFM3MDEgSG9tZXdvcmsgMiIKYXV0aG9yOiAiSm9yZGFuIEZhcnJlciIKZGF0ZTogJzIwMTYtMTAtMTYnCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBjc3M6IHN0eWxlLmNzcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KCkRvd25sb2FkIHRoZSAuUm1kIGRpcmVjdGx5IGF0IHRoZSB0b3AtcmlnaHQuCgohW10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2pyZmFycmVyL3N0YXRzNzAxX2h3Mi9tYXN0ZXIvdml6L3N0YXQ3MDFfaHcyLmdpZikKCkZ1bGwgcmVwbzogW2h0dHBzOi8vZ2l0aHViLmNvbS9qcmZhcnJlci9zdGF0czcwMV9odzIvXShodHRwczovL2dpdGh1Yi5jb20vanJmYXJyZXIvc3RhdHM3MDFfaHcyLykKClB1Ymxpc2hlZCBmaWxlOiBbaHR0cHM6Ly9qcmZhcnJlci5naXRodWIuaW8vc3RhdHM3MDFfaHcyL10oaHR0cHM6Ly9qcmZhcnJlci5naXRodWIuaW8vc3RhdHM3MDFfaHcyLykKCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyBTZXQgb3B0aW9ucyBmb3IgdGhlIHJtYXJrZG93biBmaWxlCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGZpZy5hbGlnbiA9ICdjZW50ZXInLCB3aWR0aCA9IDEwMCkKYGBgCgpgYGB7ciBzZXR1cDJ9CiMgU2V0IHRoZSBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoNDQpCiMgU2V0IHRoZSBsb2NhbGUgb2YgdGhlIHNlc3Npb24gc28gbGFuZ3VhZ2VzIG90aGVyIHRoYW4gRW5nbGlzaCBjYW4gYmUgdXNlZAppbnZpc2libGUoU3lzLnNldGxvY2FsZSgiTENfQUxMIiwgImVuX1VTLlVURi04IikpCiMgUHJldmVudCBwcmludGluZyBpbiBzY2llbnRpZmljIG5vdGF0aW9uCm9wdGlvbnMoZGlnaXRzID0gNCwgd2lkdGggPSAyMjApCgojIENyZWF0ZSBhIGxvZ2dlciBmdW5jdGlvbgpsb2dnZXIgPC0gZnVuY3Rpb24obXNnLCBsZXZlbCA9ICJpbmZvIiwgZmlsZSA9IGxvZ19maWxlKSB7CiAgICBjYXQocGFzdGUwKCJbIiwgZm9ybWF0KFN5cy50aW1lKCksICIlWS0lbS0lZCAlSDolTTolUy4lT1MiKSwgIl1bIiwgbGV2ZWwsICJdICIsIG1zZywgIlxuIiksIGZpbGUgPSBzdGRvdXQoKSkKfQoKIyBTZXQgdGhlIHByb2plY3QgZGlyZWN0b3J5CmJhc2VfZGlyIDwtICcnCmRhdGFfZGlyIDwtIHBhc3RlMChiYXNlX2RpciwgImRhdGEvIikKY29kZV9kaXIgPC0gcGFzdGUwKGJhc2VfZGlyLCAiY29kZS8iKQp2aXpfZGlyIDwtIHBhc3RlMChiYXNlX2RpciwgInZpei8iKQoKZGlyLmNyZWF0ZShkYXRhX2Rpciwgc2hvd1dhcm5pbmdzID0gRkFMU0UpCmRpci5jcmVhdGUoY29kZV9kaXIsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQpkaXIuY3JlYXRlKHZpel9kaXIsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQpgYGAKCmBgYHtyIExvYWQgUGFja2FnZXMsIGluY2x1ZGUgPSBGQUxTRX0KIyBDcmVhdGUgYSBmdW5jdGlvbiB0aGF0IHdpbGwgYmUgdXNlZCB0byBsb2FkL2luc3RhbGwgcGFja2FnZXMKZm5fbG9hZF9wYWNrYWdlcyA8LSBmdW5jdGlvbihwKSB7CiAgaWYgKCFpcy5lbGVtZW50KHAsIGluc3RhbGxlZC5wYWNrYWdlcygpWywxXSkgfHwgKHAgPT0iRFQiICYmICEocGFja2FnZVZlcnNpb24ocCkgPiAiMC4xIikpKSB7CiAgICBpZiAocCA9PSAiRFQiKSB7CiAgICAgIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigncnN0dWRpby9EVCcpCiAgICB9IGVsc2UgewogICAgICBpbnN0YWxsLnBhY2thZ2VzKHAsIGRlcCA9IFRSVUUsIHJlcG9zID0gJ2h0dHA6Ly9jcmFuLnVzLnItcHJvamVjdC5vcmcnKQogICAgfQogIH0KICBhIDwtIHN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhyZXF1aXJlKHAsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkpCiAgaWYgKGEpIHsKICAgIGxvZ2dlcihwYXN0ZTAoIkxvYWRlZCBwYWNrYWdlICIsIHAsICIgdmVyc2lvbiAiLCBwYWNrYWdlVmVyc2lvbihwKSkpCiAgfSBlbHNlIHsKICAgIGxvZ2dlcihwYXN0ZTAoIlVuYWJsZSB0byBsb2FkIHBhY2thZ2VzICIsIHApKQogIH0KfQojIENyZWF0ZSBhIHZlY3RvciBvZiBwYWNrYWdlcwpwYWNrYWdlcyA8LSBjKCd0aWR5dmVyc2UnLCdnZ3RoZW1lcycsJ2tuaXRyJywnZXh0cmFmb250JywnYnJvb20nLCdhb2QnLCdsZWFwcycsJ2Jlc3RnbG0nLAogICAgICAgICAgICAgICdHR2FsbHknLCdwYW5kZXInLCdkZXNjcicsJ3Bsb3RST0MnLCAnUk9DUicsJ3BST0MnLCAnY29ycnBsb3QnKQojIFVzZSBmdW5jdGlvbiB0byBsb2FkIHRoZSByZXF1aXJlZCBwYWNrYWdlcwppbnZpc2libGUobGFwcGx5KHBhY2thZ2VzLCBmbl9sb2FkX3BhY2thZ2VzKSkKYGBgCgpgYGB7ciBJbXBvcnQgRm9udHN9CiMgVG8gdGhlIGZvbnQgc2Vjb25kIGZvbnQsIHJ1biB0aGUgZm9sbG93aW5nIHR3byBsaW5lcyBvZiBjb2RlIGFuZCBhZGQgbmFtZSBvZiB1c2VyIHRvIHZlY3RvcgojIHN5c3RlbShwYXN0ZTAoImNwIC1yICIsdml6X2RpciwiZm9udHMvLiB+L0xpYnJhcnkvRm9udHMvIikpICMgaW5zdGFudGFuZW91cwojIGZvbnRfaW1wb3J0KCkgIyB0YWtlcyBhcHByb3hpbWF0ZWx5IDUtMTAgbWluCnVzZXJzX3YgPC0gYygiSm9yZGFuIikKYGBgCgpgYGB7ciBDcmVhdGUgcGFsZXR0ZSBhbmQgdGhlbWV9CiMgQ3JlYXRlIGEgY29sb3IgcGFsZXR0ZQpwYWw1MzggPC0gZ2d0aGVtZXNfZGF0YSRmaXZldGhpcnR5ZWlnaHQKCiMgQ3JlYXRlIGEgdGhlbWUgdG8gdXNlIHRocm91Z2hvdXQgdGhlIGFuYWx5c2lzCnRoZW1lX2pyZiA8LSBmdW5jdGlvbihiYXNlX3NpemUgPSA4LCBiYXNlX2ZhbWlseSA9IGlmZWxzZShTeXMuaW5mbygpW1sndXNlciddXSAlaW4lIHVzZXJzX3YsICJEZWNpbWFNb25vUHJvIiwgIkhlbHZldGljYSIpKSB7CiAgICB0aGVtZSgKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICIjRjBGMEYwIiwgY29sb3VyID0gIiM2MDYwNjMiKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIiNGMEYwRjAiLCBjb2xvdXIgPSBOQSksIAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gICBlbGVtZW50X2xpbmUoY29sb3VyID0gIiNEN0Q3RDgiKSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gICBlbGVtZW50X2xpbmUoY29sb3VyID0gIiNEN0Q3RDgiLCBzaXplID0gMC4yNSksCiAgICAgICAgcGFuZWwubWFyZ2luID0gICAgICAgdW5pdCgwLjI1LCAibGluZXMiKSwKICAgICAgICBwYW5lbC5tYXJnaW4ueCA9ICAgICBOVUxMLAogICAgICAgIHBhbmVsLm1hcmdpbi55ID0gICAgIE5VTEwsCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSAiI0EwQTBBMyIpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHZqdXN0ID0gMSwgZmFtaWx5ID0gJ0hlbHZldGljYScsIGNvbG91ciA9ICcjM0MzQzNDJyksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAxLCBmYW1pbHkgPSAnSGVsdmV0aWNhJywgY29sb3VyID0gJyMzQzNDM0MnKSwKICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBsZWdlbmQua2V5ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAnYm9sZCcsIGNvbG91ciA9ICcjM0MzQzNDJywgaGp1c3QgPSAwKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LCBmYW1pbHkgPSBpZmVsc2UoU3lzLmluZm8oKVtbJ3VzZXInXV0gJWluJSB1c2Vyc192LCJEZWNpbWFNb25vUHJvIiwgIkhlbHZldGljYSIpKSwKICAgICAgICB0aXRsZSA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSBpZmVsc2UoU3lzLmluZm8oKVtbJ3VzZXInXV0gJWluJSB1c2Vyc192LCJEZWNpbWFNb25vUHJvIiwgIkhlbHZldGljYSIpKQogICAgICAgIAogICAgKQp9CmBgYAoKIyBRdWVzdGlvbiAxCgojIyBEYXRhIExvYWRpbmcgJiBDbGVhbmluZwoKYGBge3IgTG9hZCBhbmQgQ2xlYW4gZGF0YX0KIyBMb2FkIHRoZSBjc3Ygd2l0aCBtZWFuaW5nZnVsIGNvbHVtbiBuYW1lcwpmcmFtaW5naGFtX3JhdyA8LSByZWFkLnRhYmxlKHBhc3RlMChkYXRhX2RpciwgIkZyYW1pbmdoYW0uZGF0IiksIHNlcCA9ICIsIiwgaGVhZGVyID0gVFJVRSwgYXMuaXMgPSBUUlVFKSAlPiUgYXNfdGliYmxlCiAgICAKZnJhbWluZ2hhbV9jbGVhbmluZyA8LSAKICAgIGZyYW1pbmdoYW1fcmF3ICU+JQogICAgcmVuYW1lKGhkID0gYEhlYXJ0LkRpc2Vhc2UuYCkKCmZyYW1pbmdoYW1fY2xlYW5pbmcgPC0gCiAgICBmcmFtaW5naGFtX2NsZWFuaW5nICU+JSAKICAgIHNldE5hbWVzKHRvbG93ZXIobmFtZXMoLikpKSAlPiUKICAgIG11dGF0ZSgKICAgICAgICBzZXggPSBmYWN0b3IodG9sb3dlcihzZXgpKQogICAgICAgICwgaGQgPSBmYWN0b3IoaGQpCiAgICApCmBgYAoKV2UgbG9hZCB0aGUgYEZyYW1pbmdoYW0uZGF0YCBmaWxlIGFuZCBzdW1tYXJpemUgdGhlIGByIG5jb2woZnJhbWluZ2hhbV9yYXcpYCB2YXJpYWJsZXMgYW5kIGByIG5yb3coZnJhbWluZ2hhbV9yYXcpYCByZWNvcmRzLgoKYGBge3J9CmhlYWQoZnJhbWluZ2hhbV9jbGVhbmluZykKYGBgCgpXZSB0aGVuIHJlbW92ZSBgciBmcmFtaW5naGFtX2NsZWFuaW5nICU+JSBmaWx0ZXIoIWNvbXBsZXRlLmNhc2VzKC4pKSAlPiUgY291bnQoKSAlPiUgdW5saXN0KClgIG9ic2VydmF0aW9ucyB0aGF0IGhhdmUgbWlzc2luZyB2YWx1ZXMuCgpgYGB7ciBDaGVjayBtaXNzaW5nIGRhdGEgaW4gRnJhbX0KZnJhbWluZ2hhbV9jbGVhbmluZzIgPC0gCiAgICBmcmFtaW5naGFtX2NsZWFuaW5nICU+JSAKICAgIGZpbHRlcihjb21wbGV0ZS5jYXNlcyguKSkKYGBgCgpgYGB7ciBTdW1tYXJpc2UgZGF0YSwgcmVzdWx0cyA9ICdhc2lzJ30KcGFuZGVyKHN1bW1hcnkoZnJhbWluZ2hhbV9jbGVhbmluZzIpLCBtaXNzaW5nID0gIiIsIHNwbGl0LnRhYmxlID0gSW5mKQpgYGAKCmBgYHtyIH0KZnJhbWluZ2hhbV9maW5hbCA8LSBmcmFtaW5naGFtX2NsZWFuaW5nMgpgYGAKClRvIGdldCBhIGJldHRlciBpZGVhIG9mIHRoaXMgZGF0YXNldCwgd2UgY3JlYXRlIGEgcGFpcnMgcGxvdCBjb2xvcmVkIGJ5IG9ic2VydmF0aW9ucyA8c3BhbiBzdHlsZT0iY29sb3I6YHIgcGFsNTM4WydibHVlJ11bWzFdXWAiPndpdGhvdXQgaGVhcnQgZGlzZWFzZSAoaGQgPSAwKTwvc3Bhbj4gYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjpgciBwYWw1MzhbJ3JlZCddW1sxXV1gIj53aXRoIGhlYXJ0IGRpc2Vhc2UgKGhkID0gMSk8L3NwYW4+LgoKYGBge3IgUGFpcnMgUGxvdCBmb3IgRXhwbG9yYXRpb24sIGZpZy53aWR0aCA9IDcuNSwgZmlnLmhlaWdodCA9IDd9CmZwYWlycyA8LWdncGFpcnMoZnJhbWluZ2hhbV9maW5hbCwgbWFwcGluZyA9IGFlcyhjb2xvdXIgPSBoZCkpCgpmb3IgKHJvdyBpbiBzZXFfbGVuKGZwYWlycyRucm93KSkKICAgIGZvciAoY29sIGluIHNlcV9sZW4oZnBhaXJzJG5jb2wpKQogICAgICAgIGZwYWlyc1tyb3csIGNvbF0gPC0gZnBhaXJzW3JvdywgY29sXSArIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYyhwYWw1MzhbJ2JsdWUnXVtbMV1dLCBwYWw1MzhbJ3JlZCddW1sxXV0pKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMocGFsNTM4WydibHVlJ11bWzFdXSwgcGFsNTM4WydyZWQnXVtbMV1dKSkgICAgCgpmcGFpcnMKYGBgCgpGb3IgdGhlIHZhcmlhYmxlIHNleCwgd2UgY3JlYXRlIGEgZnJlcXVlbmN5IHRhYmxlLgoKYGBge3IsIHJlc3VsdHM9J2FzaXMnfQpwYW5kZXIoZGVzY3I6OkNyb3NzVGFibGUoZnJhbWluZ2hhbV9maW5hbCRoZCwgZnJhbWluZ2hhbV9maW5hbCRzZXgsIHByb3AuY2hpc3E9RkFMU0UsIHByb3AudCA9IEZBTFNFLCBjaGlzcSA9IFRSVUUsIGRubiA9IGMoIkhEIiwiU2V4IikpLCBzcGxpdC50YWJsZSA9IEluZiwgYmlnLm1hcmsgPSAnLCcpCmBgYAoKIyMgRmlyc3QgTW9kZWwKCiMjIyBNb2RlbAoKV2UgZml0IGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB0byBwcmVkaWN0IGhlYXJ0IGRpc2Vhc2Ugd2l0aCBvbmx5IHRoZSBleHBsYW5hdG9yeSB2YXJpYWJsZSBzeXN0b2xpYyBibG9vZCBwcmVzc3VyZSAoU0JQKS4KCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CmZpdDEgPC0gZ2xtKGhkIH4gc2JwLCBkYXRhID0gZnJhbWluZ2hhbV9maW5hbCwgZmFtaWx5ID0gYmlub21pYWwoJ2xvZ2l0JykpCnRpZHkoZml0MSkgJT4lIHBhbmRlcihjYXB0aW9uID0gcGFzdGUwKGFzLmNoYXJhY3RlcihzdW1tYXJ5KGZpdDEpJGNhbGwpWzNdLCc6ICcsIGFzLmNoYXJhY3RlcihzdW1tYXJ5KGZpdDEpJGNhbGwpWzJdKSkKZ2xhbmNlKGZpdDEpICU+JSBwYW5kZXIoKQpgYGAKClRoZSBuZXh0IHZhcmlhYmxlIHdlIGNob29zZSB3aWxsIGhhdmUgdGhlIGxhcmdlc3QgJHx6fCQgc3RhdGlzdGljIG9yIHRoZSBzbWFsbGVzdCAkcCQgdmFsdWUuIFdlIGNyZWF0ZSBgciBzdW0oIShuYW1lcyhmcmFtaW5naGFtX2ZpbmFsKSAlaW4lIGMoImhkIiwic2JwIikpKWAgbW9kZWxzIHRoYXQgYXJlIGBoZCB+IHNicCArIHZhcmAgYW5kIGV4dHJhY3QgdGhlIHN1bW1hcnkgb2YgZWFjaCB2YXJpYWJsZSBgdmFyYC4gCgpgYGB7cn0KdmFyc19yb3RhdGluZyA8LSBuYW1lcyhmcmFtaW5naGFtX2ZpbmFsKVshKG5hbWVzKGZyYW1pbmdoYW1fZmluYWwpICVpbiUgYygiaGQiLCJzYnAiKSldCmJlc3Rfc2Vjb25kX3ZhciA8LSBkYXRhX2ZyYW1lKCkKZm9yKHZhciBpbiB2YXJzX3JvdGF0aW5nKSB7CiAgICBiZXN0X3NlY29uZF92YXIgPC0gYmluZF9yb3dzKGJlc3Rfc2Vjb25kX3ZhciwKICAgIHRpZHkoZ2xtKHBhc3RlMCgnaGQgfiBzYnAgKyAnLCB2YXIpLCBkYXRhID0gZnJhbWluZ2hhbV9maW5hbCwgZmFtaWx5ID0gYmlub21pYWwoJ2xvZ2l0JykpKSAlPiUgCiAgICAgICAgZmlsdGVyKHJvd19udW1iZXIoKSA9PSAzKSAlPiUKICAgICAgICByZW5hbWUoYHouc3RhdGlzdGljYCA9IHN0YXRpc3RpYykKICAgICkKfQpiZXN0X3NlY29uZF92YXIgJT4lCiAgICBhcnJhbmdlKHAudmFsdWUpCmBgYAoKV2Ugc2VlIHRoYXQgKipzZXgqKiB3aWxsIGJlIHRoZSBtb3N0IGltcG9ydGFudCBhZGRpdGlvbiBvbiB0b3Agb2Ygc2JwLiAKCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CmZpdDIgPC0gZ2xtKGhkIH4gc2JwICsgc2V4LCBkYXRhID0gZnJhbWluZ2hhbV9maW5hbCwgZmFtaWx5ID0gYmlub21pYWwoJ2xvZ2l0JykpCmZpdDJfc3VtbWFyeSA8LSBzdW1tYXJ5KGZpdDIpCnRpZHkoZml0MikgJT4lIHBhbmRlcihjYXB0aW9uID0gcGFzdGUwKGFzLmNoYXJhY3RlcihzdW1tYXJ5KGZpdDIpJGNhbGwpWzNdLCc6ICcsIGFzLmNoYXJhY3RlcihzdW1tYXJ5KGZpdDIpJGNhbGwpWzJdKSkKZ2xhbmNlKGZpdDIpICU+JSBwYW5kZXIoKQpgYGAKCiMjIyBSZXNpZHVhbCBEZXZpYW5jZQoKVGhlIHJlc2lkdWFsIGRldmlhbmNlIG9mIGZpdDIgaXMgYWx3YXlzIHNtYWxsZXIgdGhhbiBmaXQxIGJlY2F1c2UgZml0MSBpcyBhIG5lc3RlZCBtb2RlbCBvZiBmaXQyIGFuZCB0aHVzIGhhcyBtb3JlIGRlZ3Jlc3Mgb2YgZnJlZWRvbS4KCiMjIyBXYWxkIGFuZCBMaWtlbGlob29kIFRlc3RzCgpXZSBwZXJmb3JtIHRoZSAqKldhbGQgVGVzdCoqIHdoaWNoIHRlc3RzIHdoZXRoZXIgdGhlIHNldCBvZiB0ZXJtcyBpcyBzaWduaWZpY2FudC4KCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CmNvbmZpbnQuZGVmYXVsdChmaXQyKSAlPiUgcGFuZGVyKCkKYGBgCgpgYGB7ciByZXN1bHRzID0gJ2FzaXMnfQp3YWxkX3Rlc3QgPC0gd2FsZC50ZXN0KGIgPSBjb2VmKGZpdDIpLCBTaWdtYSA9IHZjb3YoZml0MiksIFRlcm1zID0gMTozKQp3YWxkX3Rlc3QkcmVzdWx0JGNoaTIgJT4lIGFzX3RpYmJsZSgpICU+JSBwYW5kZXIoY2FwdGlvbiA9ICJXYWxkIFRlc3QiKQpgYGAKClRoZSBXYWxkIFRlc3QgeWllbGRzIGEgJHAkLXZhbHVlIG9mIGByIHdhbGRfdGVzdCRyZXN1bHQkY2hpMlszXWAuIFRoZSBXYWxkIFRlc3QgZm9yIGp1c3Qgb25lIHZhcmlhYmxlIGlzIHRoZSBzYW1lIHRlc3QgcGVyZm9ybWVkIGJ5IHRoZSBmdW5jdGlvbiBgc3VtbWFyeWAuIEJlbG93IGFyZSB0aGUgcmVzdWx0cyBmb3IgdGhlIFdhbGQgVGVzdCBmb3IganVzdCB0aGUgdGVybSBgc2V4YC4gTm90ZSB0aGF0IHRoZSBzdGF0aXN0aWMgaXMgei12YWx1ZSBzcXVhcmVkICgkYHIgY29lZihmaXQyX3N1bW1hcnkpWzMsIDNdYF4yID0gYHIgY29lZihmaXQyX3N1bW1hcnkpWzMsIDNdXjJgJCkgYW5kIHRoZSAkcCQtdmFsdWVzIGFyZSB0aGUgc2FtZS4KCmBgYHtyfQp3YWxkX3Rlc3QgPC0gd2FsZC50ZXN0KGIgPSBjb2VmKGZpdDIpLCBTaWdtYSA9IHZjb3YoZml0MiksIFRlcm1zID0gMykKYGBgCgpOZXh0IHdlIHBlcmZvcm0gdGhlIExpa2VsaWhvb2QgVGVzdCAoQ2hpLXNxdWFyZWQpLgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KY2hpX3NxX2ZpdDIgPC0gYW5vdmEoZml0MiwgdGVzdCA9ICJDaGlzcSIpCnRpZHkoY2hpX3NxX2ZpdDIpICU+JSBwYW5kZXIobWlzc2luZyA9ICcnLCBjYXB0aW9uID0gJ0FuYWx5c2lzIG9mIERldmlhbmNlIFRhYmxlJykKYGBgCgpUaGUgJHAkLXZhbHVlIGZvciBhZGRpbmcgdGhlIHZhcmlhYmxlIHNleCBpcyBgciBjaGlfc3FfZml0MltbNV1dWzNdYC4gVGhlICRwJC12YWx1ZSBpcyBub3QgdGhlIHNhbWUgYXMgdGhlIFdhbGQgVGVzdC4gVGhlIFdhbGQgVGVzdCBhbmQgdGhlIExpa2VsaWhvb2QgVGVzdCBib3RoIGhvbGQgZm9yIHRoaXMgbmV3IG1vZGVsLgoKIyMgTW9kZWxpbmcgQnVpbGRpbmcKCiMjIyBCYWNrd2FyZCBTZWxlY3Rpb24KClVzaW5nIGJhY2t3YXJkIHNlbGVjdGlvbiwgd2UgYXJlIGxlZnQgd2l0aCBhIG1vZGVsIHdpdGggNCBwcmVkaWN0b3JzOiBzYnAsIHNleCwgYWdlLCBhbmQgY2hvbC4KCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CmZpdDcgPC0gZ2xtKGhkIH4gc2JwK3NleCthZ2UrY2lnK2Nob2wrZnJ3K2RicCwgZGF0YSA9IGZyYW1pbmdoYW1fZmluYWwsIGZhbWlseSA9IGJpbm9taWFsKCdsb2dpdCcpKQpmaXQ2IDwtIGdsbShoZCB+IHNicCtzZXgrYWdlK2NpZytjaG9sK2ZydywgZGF0YSA9IGZyYW1pbmdoYW1fZmluYWwsIGZhbWlseSA9IGJpbm9taWFsKCdsb2dpdCcpKQpmaXQ1IDwtIGdsbShoZCB+IHNicCtzZXgrYWdlK2NpZytjaG9sLCBkYXRhID0gZnJhbWluZ2hhbV9maW5hbCwgZmFtaWx5ID0gYmlub21pYWwoJ2xvZ2l0JykpCmZpdDQgPC0gZ2xtKGhkIH4gc2JwK3NleCthZ2UrY2hvbCwgZGF0YSA9IGZyYW1pbmdoYW1fZmluYWwsIGZhbWlseSA9IGJpbm9taWFsKCdsb2dpdCcpKQp0aWR5KGZpdDQpICU+JSBwYW5kZXIoY2FwdGlvbiA9IHBhc3RlMChhcy5jaGFyYWN0ZXIoc3VtbWFyeShmaXQ0KSRjYWxsKVszXSwnOiAnLCBhcy5jaGFyYWN0ZXIoc3VtbWFyeShmaXQ0KSRjYWxsKVsyXSkpCmdsYW5jZShmaXQ0KSAlPiUgcGFuZGVyKCkKYGBgCgojIyMgQUlDIENyaXRlcmlvbgoKTmV4dCB3ZSB1c2UgZXhhaHVzdGl2ZSBzZWFyY2ggdG8gZmluZCB0aGUgYmVzdCBtb2RlbCB1c2luZyB0aGUgQUlDIGNyaXRlcmlvbi4KCmBgYHtyfQpYeSA8LSBiaW5kX2NvbHMobW9kZWwubWF0cml4KGhkIH4uKzAsIGZyYW1pbmdoYW1fZmluYWwpICU+JSBhc190aWJibGUoKSwgCiAgICAgICAgICAgICAgICBmcmFtaW5naGFtX2ZpbmFsICU+JSBzZWxlY3QoaGQpKSAKZXhoYXVzdGl2ZV9tb2RlbCA8LSBiZXN0Z2xtKGFzLmRhdGEuZnJhbWUoWHkpLCBmYW1pbHkgPSBiaW5vbWlhbCgnbG9naXQnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJleGhhdXN0aXZlIiwgSUMgPSAiQUlDIiwgbnZtYXggPSAxMCkKZXhoYXVzdGl2ZV9tb2RlbCRCZXN0TW9kZWxzCmBgYAoKVGhpcyBtZXRob2QgaXMgbm90IGd1YXJhbnRlZWQgdG8gaW5jbHVkZSBvbmx5IHZhcmlhYmxlcyB3aXRoICRwJC12YWx1ZXMgbGVzcyB0aGFuIDAuMDUuIEluIHRoaXMgZXhhbXBsZSwgdGhlIGJlc3QgbW9kZWwgaW5jbHVkZXMgZnJ3IHdoaWNoIHRoZSBnbG0gc3VtbWFyeSBzaG93cyBpcyBub3Qgc2lnbmlmaWNhbnQgYXQgdGhlIDAuMDUgbGV2ZWwuIFRodXMsIHRoZSBiZXN0IG1vZGVsIHJldHVybmVkIGJ5IGV4aGF1c3RpdmUgc2VhcmNoIGlzIG5vdCB0aGUgc2FtZSBhcyB0aGUgbW9kZWwgcmV0dXJuZWQgYnkgYmFja3dhcmRzIGVsaW1pbmF0aW9uLgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KZmluYWxfbW9kZWwgPC0gZXhoYXVzdGl2ZV9tb2RlbCRCZXN0TW9kZWwKdGlkeShmaW5hbF9tb2RlbCkgJT4lIHBhbmRlcihjYXB0aW9uID0gIkJlc3QgbW9kZWwgcmV0dXJuZWQgYnkgZXhoYXVzdGl2ZSBzZWFyY2giKQpnbGFuY2UoZmluYWxfbW9kZWwpICU+JSBwYW5kZXIoKQpgYGAKCiMjIyBGaW5hbCBNb2RlbAoKVGhlIGZpbmFsIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgcHJlZGljdHMgd2hldGhlciBvciBub3QgYW4gaW5kaXZpZHVhbCB3aWxsIGhhdmUgaGVhcnQgZGlzZWFzZSAoaGQpIHdpdGggNiB2YXJpYWJsZXMuIFRoZSBzaXggdmFyaWFibGVzIGFyZSBsaXN0ZWQgaW4gdGhlIG9yZGVyIG9mIGltcG9ydGFuY2UgdG8gdGhlIG1vZGVsIGJlbG93OgoKYGBge3J9CnN1bW1hcnlfdmFycyA8LSAKICAgIGRhdGFfZnJhbWUoCiAgICAgICAgdmFyaWFibGUgPSByb3duYW1lcyhjb2VmKHN1bW1hcnkoZmluYWxfbW9kZWwpKVstMSwgXSkKICAgICAgICAsIHBfdmFsdWUgPSBjb2VmKHN1bW1hcnkoZmluYWxfbW9kZWwpKVstMSwgNF0KICAgICAgICApICU+JSAKICAgICAgICBtdXRhdGUoCiAgICAgICAgICAgIGVzdGltYXRlID0gY29lZihzdW1tYXJ5KGZpbmFsX21vZGVsKSlbLTEsIDFdCiAgICAgICAgICAgICwgb2RkcyA9IGV4cChlc3RpbWF0ZSkKICAgICAgICAgICAgLCBwcm9iID0gb2RkcyAvICgxICsgb2RkcykKICAgICAgICAgICAgLCBwZXJjZW50YWdlID0gcGFzdGUwKHJvdW5kKHByb2IgKiAxMDAsIDIpLCAiJSIpCiAgICAgICAgKSAlPiUKICAgICAgICBhcnJhbmdlKHBfdmFsdWUpCgpzdW1tYXJ5X3ZhcnMgJT4lIHNlbGVjdCgtcGVyY2VudGFnZSkKYGBgCgpIb2xkaW5nIGFsbCBvdGhlciB2YXJpYWJsZXMgY29uc3RhbnQsCgorIEEgb25lLXVuaXQgaW5jcmVhc2UgaW4gKipzYnAqKiwgd2UgZXhwZWN0IHRvIHNlZSBhYm91dCBgciBzdW1tYXJ5X3ZhcnMgJT4lIGZpbHRlcih2YXJpYWJsZSA9PSAic2JwIikgJT4lIHNlbGVjdChwZXJjZW50YWdlKSAlPiUgdW5saXN0KClgIGluY3JlYXNlIGluIHRoZSBwcm9iYWJpbGl0eSBvZiBoYXZpbmcgaGVhcnQgZGlzZWFzZS4KKyBUaGUgZGlmZmVyZW5jZSBvZiBiZWluZyBhICoqbWFsZSByYXRoZXIgYW4gYSBmZW1hbGUqKiwgd2UgZXhwZWN0IHRvIHNlZSBhYm91dCBgciBzdW1tYXJ5X3ZhcnMgJT4lIGZpbHRlcih2YXJpYWJsZSA9PSAic2V4bWFsZSIpICU+JSBzZWxlY3QocGVyY2VudGFnZSkgJT4lIHVubGlzdCgpYCUgaW5jcmVhc2UgaW4gdGhlIHByb2JhYmlsaXR5IG9mIGhhdmluZyBoZWFydCBkaXNlYXNlLgorIEEgb25lLXVuaXQgaW5jcmVhc2UgaW4gKiphZ2UqKiwgd2UgZXhwZWN0IHRvIHNlZSBhYm91dCBgciBzdW1tYXJ5X3ZhcnMgJT4lIGZpbHRlcih2YXJpYWJsZSA9PSAiYWdlIikgJT4lIHNlbGVjdChwZXJjZW50YWdlKSAlPiUgdW5saXN0KClgIGluY3JlYXNlIGluIHRoZSBwcm9iYWJpbGl0eSBvZiBoYXZpbmcgaGVhcnQgZGlzZWFzZS4KKyBBIG9uZS11bml0IGluY3JlYXNlIGluICoqY2hvbCoqLCB3ZSBleHBlY3QgdG8gc2VlIGFib3V0IGByIHN1bW1hcnlfdmFycyAlPiUgZmlsdGVyKHZhcmlhYmxlID09ICJjaG9sIikgJT4lIHNlbGVjdChwZXJjZW50YWdlKSAlPiUgdW5saXN0KClgIGluY3JlYXNlIGluIHRoZSBwcm9iYWJpbGl0eSBvZiBoYXZpbmcgaGVhcnQgZGlzZWFzZS4KKyBBIG9uZS11bml0IGluY3JlYXNlIGluICoqY2lnKiosIHdlIGV4cGVjdCB0byBzZWUgYWJvdXQgYHIgc3VtbWFyeV92YXJzICU+JSBmaWx0ZXIodmFyaWFibGUgPT0gImNpZyIpICU+JSBzZWxlY3QocGVyY2VudGFnZSkgJT4lIHVubGlzdCgpYCBpbmNyZWFzZSBpbiB0aGUgcHJvYmFiaWxpdHkgb2YgaGF2aW5nIGhlYXJ0IGRpc2Vhc2UuCisgQSBvbmUtdW5pdCBpbmNyZWFzZSBpbiAqKmZydyoqLCB3ZSBleHBlY3QgdG8gc2VlIGFib3V0IGByIHN1bW1hcnlfdmFycyAlPiUgZmlsdGVyKHZhcmlhYmxlID09ICJmcnciKSAlPiUgc2VsZWN0KHBlcmNlbnRhZ2UpICU+JSB1bmxpc3QoKWAgaW5jcmVhc2UgaW4gdGhlIHByb2JhYmlsaXR5IG9mIGhhdmluZyBoZWFydCBkaXNlYXNlLgoKCgojIyMgTGl6CgpgYGB7cn0KbGl6IDwtIGRhdGFfZnJhbWUoYWdlID0gNTAsIHNleG1hbGUgPSAwLCBzYnAgPSAxMTAsIGRicCA9IDgwLCBjaG9sID0gMTgwLCBmcncgPSAxMDUsIGNpZyA9IDApCmxpel9wcmVkaWN0IDwtIGV4cChwcmVkaWN0KGZpbmFsX21vZGVsLCBsaXosIHR5cGUgPSAncmVzcG9uc2UnKSkgLyAoMSArIGV4cChwcmVkaWN0KGZpbmFsX21vZGVsLCBsaXosIHR5cGUgPSAncmVzcG9uc2UnKSkpCmBgYCAgICAKClRoZSBwcm9iYWJpbGl0eSB0aGF0IExpeiB3aWxsIGhhdmUgaGVhcnQgZGlzZWFzZSB3aXRoIG91ciBmaW5hbCBtb2RlbCBpcyAqKmByIGxpel9wcmVkaWN0YCoqLgoKIyMgQ2xhc3NpZmljYXRpb24KCiMjIyAgCgpgYGB7cn0KZml0MV9jbGFzc2lmaWNlciA8LQogICAgZGF0YV9mcmFtZSgKICAgICAgICBwcmVkID0gZnJhbWluZ2hhbV9maW5hbCAlPiUgbXV0YXRlKHByZWQgPSBpZmVsc2Uoc2JwID4gMTc2LCAxLCAwKSkgJT4lIHNlbGVjdChwcmVkKSAlPiUgdW5saXN0KCksIAogICAgICAgIHRydWUgPSBmcmFtaW5naGFtX2ZpbmFsICU+JSBzZWxlY3QoaGQpICU+JSB1bmxpc3QoKQogICAgICAgICkgJT4lCiAgICAgICAgbXV0YXRlKHR5cGUgPSBpZmVsc2UocHJlZCA9PSB0cnVlLCBpZmVsc2UocHJlZCA9PSAwLCAidG4iLCAidHAiKSwgaWZlbHNlKHByZWQgPT0gMCwgImZuIiwgImZwIikpKSAlPiUKICAgIGdyb3VwX2J5KHR5cGUpICU+JQogICAgY291bnQoKSAlPiUKICAgIHNwcmVhZCh0eXBlLCBuKSAlPiUKICAgIHN1bW1hcmlzZShmcHIgPSBmcCAvIChmcCArIHRuKSwgdHByID0gdHAgLyAodHAgKyBmbikpCmBgYAoKClRoZSBST0MgY3VydmUgaXMgdXNlZCB0byBhc3Nlc3MgdGhlIGFjY3VyYWN5IG9mIGEgY29udGludW91cyB2YXJpYWJsZSBmb3IgcHJlZGljdGluZyBhIGJpbmFyeSBvdXRjb21lLiBJbiB0aGUgUk9DIGFib3ZlIHRoZSBjb250aW51b3VzIHZhcmlhYmxlIGlzIHN5c3RvbGljIGJsb29kIHByZXNzdXJlIChzYnApIGFuZCB0aGUgYmluYXJ5IG91dGNvbWUgaXMgd2hldGhlciB0aGUgaW5kaXZpZHVhbCB3aWxsIGhhdmUgaGVhcnQgZGlzZWFzZSAoaGQpLgoKYGBge3IgZmlnLmFsaWduID0gVFJVRX0KcGxvdF9kZiA8LSAKICAgIGRhdGFfZnJhbWUoCiAgICAgICAgaGQgPSBmcmFtaW5naGFtX2ZpbmFsICU+JSBtdXRhdGUoaGQgPSBhcy5pbnRlZ2VyKGhkKSAtIDEpICU+JSBzZWxlY3QoaGQpICU+JSB1bmxpc3QoKQogICAgICAgICwgZml0dGVkX2ZpdDEgPSBmaXQxJGZpdHRlZAogICAgICAgICwgZml0dGVkX2ZpdDIgPSBmaXQyJGZpdHRlZAogICAgKQojcDEgPC0gCmdncGxvdChwbG90X2RmLCBhZXMoZCA9IGhkLCBtID0gZml0dGVkX2ZpdDEpKSArIAogICAgZ2VvbV9yb2MoKSArIHN0eWxlX3JvYygpICsgCiAgICB0aGVtZV9qcmYoKSArCiAgICBsYWJzKHRpdGxlID0gIlJPQyBmb3IgZml0MSIpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAuMSkgKyAKICAgIGdlb21fbGFiZWwoZGF0YSA9IGRhdGFfZnJhbWUoaGQgPSAwLjExLCBmaXR0ZWRfZml0MSA9IDAuOCwgbGFiZWwgPSAiRmFsc2UgUG9zaXRpdmVcblJhdGUgPSAxIiksIAogICAgICAgICAgICAgICBhZXMoeCA9IGhkLCB5ID0gZml0dGVkX2ZpdDEsIGxhYmVsID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUgPSAxIiksIGhqdXN0ID0gJ2xlZnQnKSArIAogICAgZ2VvbV9wb2ludChkYXRhID0gZGF0YV9mcmFtZShoZCA9IGZpdDFfY2xhc3NpZmljZXIkZnByLCBmaXR0ZWRfZml0MSA9IGZpdDFfY2xhc3NpZmljZXIkdHByKSwgCiAgICAgICAgICAgICAgIGFlcyh4ID0gaGQsIHkgPSBmaXR0ZWRfZml0MSksIGNvbG91ciA9IHBhbDUzOFsncmVkJ11bWzFdXSkgKwogICAgZ2VvbV90ZXh0KGRhdGEgPSBkYXRhX2ZyYW1lKGhkID0gZml0MV9jbGFzc2lmaWNlciRmcHIgKyAwLjAyLCBmaXR0ZWRfZml0MSA9IGZpdDFfY2xhc3NpZmljZXIkdHByLCB0ZXh0ID0gIlNCUCA+IDE3NiIpLCAKICAgICAgICAgICAgICAgYWVzKHggPSBoZCwgeSA9IGZpdHRlZF9maXQxLCBsYWJlbCA9IHRleHQpLCBjb2xvdXIgPSBwYWw1MzhbJ3JlZCddW1sxXV0sIGhqdXN0ID0gMC4xKQpgYGAKCgpUaGUgY2xhc3NpZmllciBzdWNoIHRoYXQgdGhlIEZhbHNlIFBvc2l0aXZlIFJhdGUgaXMgbGVzcyB0aGFuIDAuMSBhbmQgdGhlIHRydWUgcG9zaXRpdmUgcmF0ZSBpcyBhcyBoaWdoIGFzIHBvc3NpYmxlIGlzICoqU0JQID4gMTc2LCB0aGVuIEhEID0gMSwgZWxzZSBIRCA9IDAqKi4KCiMjIyBUd28gUk9DIEN1cnZlcwoKYGBge3J9CnBsb3RfZGYyIDwtIAogICAgcGxvdF9kZiAlPiUgCiAgICBnYXRoZXIobW9kZWwsIGZpdHRlZCwgLWhkKSAlPiUKICAgIG11dGF0ZShtb2RlbCA9IGdzdWIoImZpdHRlZF8iLCIiLCBtb2RlbCkpCgpnZ3Bsb3QocGxvdF9kZjIsIGFlcyhkID0gaGQsIG0gPSBmaXR0ZWQsIGNvbG91ciA9IG1vZGVsKSkgKyAKICAgIGdlb21fcm9jKCkgKyBzdHlsZV9yb2MoKSArIAogICAgdGhlbWVfanJmKCkgKwogICAgbGFicyh0aXRsZSA9ICJST0MgZm9yIGZpdDEgYW5kIGZpdDIiKSArCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKCJNb2RlbCIsIHZhbHVlcyA9IGMoJ2ZpdDEnID0gcGFsNTM4WydibHVlJ11bWzFdXSwgJ2ZpdDInID0gcGFsNTM4WydyZWQnXVtbMV1dKSkKYGBgCgpUaGUgUk9DIGN1cnZlIGZvciBmaXQyIGFsd2F5cyBjb250YWlucyB0aGUgUk9DIGN1cnZlIGZvciBmaXQxLiBUaGUgQVVDIG9mIHRoZSBST0MgZm9yIGZpdDIgaXMgYHIgYXVjKHJvYyhmcmFtaW5naGFtX2ZpbmFsJGhkLCBmaXQyJGZpdHRlZCkpWzFdYCBhbmQgQVVDIG9mIHRoZSBST0MgZm9yIGZpdDEgaXMgYHIgYXVjKHJvYyhmcmFtaW5naGFtX2ZpbmFsJGhkLCBmaXQxJGZpdHRlZCkpWzFdYC4gV2UgZXhwZWN0ZWQgdGhlIEFVQyBmb3IgZml0MiB0byBiZSBsYXJnZXIgdGhhbiB0aGUgQVVDIGZvciBmaXQxIGJlY2F1c2UgZml0MSBpcyBhIG5lc3RlZCBtb2RlbC4KCiMjIyBQcmVkaWN0aW9uIFZhbHVlcwoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KdmFsdWVzX2RmIDwtIAogICAgcGxvdF9kZjIgJT4lCiAgICAgICAgbXV0YXRlKHByZWQgPSBhcy5pbnRlZ2VyKGZpdHRlZCA+IC41KSkgJT4lCiAgICAgICAgbXV0YXRlKHR5cGUgPSBpZmVsc2UocHJlZCA9PSBoZCwgaWZlbHNlKHByZWQgPT0gMCwgInRuIiwgInRwIiksIGlmZWxzZShwcmVkID09IDAsICJmbiIsICJmcCIpKSkgJT4lCiAgICAgICAgZ3JvdXBfYnkobW9kZWwsIHR5cGUpICU+JQogICAgICAgIGNvdW50KCkgJT4lCiAgICAgICAgdW5ncm91cCgpICU+JQogICAgICAgIHNwcmVhZCh0eXBlLCBuKSAlPiUKICAgICAgICBtdXRhdGUoCiAgICAgICAgICAgIHBvc2l0aXZlX3ByZWRpY3Rpb25fdmFsdWUgPSB0cCAvICh0cCArIGZwKQogICAgICAgICAgICAsIG5lZ2F0aXZlX3ByZWRpY3Rpb25fdmFsdWUgPSB0biAvICh0biArIGZuKQogICAgICAgICkKCnZhbHVlc19kZiAlPiUKICAgIHNlbGVjdChtb2RlbCwgcG9zaXRpdmVfcHJlZGljdGlvbl92YWx1ZSwgbmVnYXRpdmVfcHJlZGljdGlvbl92YWx1ZSkgJT4lCiAgICBwYW5kZXIoKQpgYGAKCmZpdDIgaXMgbW9yZSBkZXNpcmVhYmxlIGlmIHdlIGNhcmUgYWJvdXQgUG9zaXRpdmUgUHJlZGljdGlvbiBWYWx1ZSBiZWNhdXNlIHRoZSBQUFYgaXMgYHIgdmFsdWVzX2RmJHBvc2l0aXZlX3ByZWRpY3Rpb25fdmFsdWVbMl1gIGZvciBmaXQyIHZzIGByIHZhbHVlc19kZiRwb3NpdGl2ZV9wcmVkaWN0aW9uX3ZhbHVlWzFdYCBmb3IgZml0MS4KCiMjIyBQUC9OUCB2cyBUaHJlc2hvbGRzCgpgYGB7cn0KcHBucF92c190aHJlc2hvbGRfZGYgPC0gZGF0YV9mcmFtZSgpCnByb2JfdGhyZXNob2xkcyA8LSBzZXEoZnJvbSA9IDAsIHRvID0gMSwgYnkgPSAwLjAxKQpmb3IgKHRocmVzaG9sZCBpbiBwcm9iX3RocmVzaG9sZHMpIHsKICAgIHBwbnBfdnNfdGhyZXNob2xkX2RmIDwtCiAgICAgICAgYmluZF9yb3dzKAogICAgICAgICAgICBwcG5wX3ZzX3RocmVzaG9sZF9kZiwgIAogICAgICAgICAgICBwbG90X2RmMiAlPiUKICAgICAgICAgICAgICAgIG11dGF0ZShwcmVkID0gYXMuaW50ZWdlcihmaXR0ZWQgPiB0aHJlc2hvbGQpKSAlPiUKICAgICAgICAgICAgICAgIG11dGF0ZSgKICAgICAgICAgICAgICAgICAgICB0biA9IGFzLmludGVnZXIocHJlZCA9PSBoZCAmIHByZWQgPT0gMCkKICAgICAgICAgICAgICAgICAgICAsIHRwID0gYXMuaW50ZWdlcihwcmVkID09IGhkICYgcHJlZCAhPSAwKQogICAgICAgICAgICAgICAgICAgICwgZm4gPSBhcy5pbnRlZ2VyKHByZWQgIT0gaGQgJiBwcmVkID09IDApCiAgICAgICAgICAgICAgICAgICAgLCBmcCA9IGFzLmludGVnZXIocHJlZCAhPSBoZCAmIHByZWQgIT0gMCkKICAgICAgICAgICAgICAgICkgJT4lCiAgICAgICAgICAgICAgICBncm91cF9ieShtb2RlbCkgJT4lCiAgICAgICAgICAgICAgICBzdW1tYXJpc2UoCiAgICAgICAgICAgICAgICAgICAgdG4gPSBzdW0odG4pCiAgICAgICAgICAgICAgICAgICAgLCB0cCA9IHN1bSh0cCkKICAgICAgICAgICAgICAgICAgICAsIGZuID0gc3VtKGZuKQogICAgICAgICAgICAgICAgICAgICwgZnAgPSBzdW0oZnApCiAgICAgICAgICAgICAgICApICU+JQogICAgICAgICAgICAgICAgbXV0YXRlKAogICAgICAgICAgICAgICAgICAgIHBvc2l0aXZlX3ByZWRpY3Rpb25fdmFsdWUgPSB0cCAvICh0cCArIGZwKQogICAgICAgICAgICAgICAgICAgICwgbmVnYXRpdmVfcHJlZGljdGlvbl92YWx1ZSA9IHRuIC8gKHRuICsgZm4pCiAgICAgICAgICAgICAgICApICU+JQogICAgICAgICAgICAgICAgbXV0YXRlKAogICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZCA9IHRocmVzaG9sZAogICAgICAgICAgICAgICAgICAgICwgcHBucCA9IHBvc2l0aXZlX3ByZWRpY3Rpb25fdmFsdWUgLyBuZWdhdGl2ZV9wcmVkaWN0aW9uX3ZhbHVlCiAgICAgICAgICAgICAgICApICU+JQogICAgICAgICAgICAgICAgc2VsZWN0KHRocmVzaG9sZCwgbW9kZWwsIHBvc2l0aXZlX3ByZWRpY3Rpb25fdmFsdWUsIG5lZ2F0aXZlX3ByZWRpY3Rpb25fdmFsdWUsIHBwbnApCiAgICAgICAgKQp9CgpnZ3Bsb3QocHBucF92c190aHJlc2hvbGRfZGYsIGFlcyh4ID0gdGhyZXNob2xkLCB5ID0gcHBucCwgY29sb3VyID0gYXMuZmFjdG9yKG1vZGVsKSkpICsKICAgIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludCgpICsKICAgIHRoZW1lX2pyZigpICsKICAgIHNjYWxlX2NvbG91cl9tYW51YWwoIk1vZGVsIiwgdmFsdWVzID0gYygnZml0MScgPSBwYWw1MzhbJ2JsdWUnXVtbMV1dLCAnZml0MicgPSBwYWw1MzhbJ3JlZCddW1sxXV0pKSArCiAgICBsYWJzKHRpdGxlID0gIlBQL05QIHZzIFByb2JhYmlsaXR5IFRocmVzaG9sZCIsCiAgICAgICAgIHggPSAiUHJvYmFiaWxpdHkgVGhyZXNob2xkIiwgeSA9ICJQb3NpdGl2ZSBQcmVkaWN0aW9uIFZhbHVlIC8gTmVnYXRpdmUgUHJlZGljdGlvbiBWYWx1ZSIpCgpgYGAKCgpgYGB7cn0KIHBwbnBfdnNfdGhyZXNob2xkX2RmICU+JQogICAgIHNlbGVjdCgtcHBucCkgJT4lCiAgICAgZ2F0aGVyKG1ldHJpYywgdmFsdWUsIC10aHJlc2hvbGQsIC1tb2RlbCkgJT4lCiAgICAgZ2dwbG90KGFlcyh4ID0gdGhyZXNob2xkLCB5ID0gdmFsdWUsIGNvbG91ciA9IG1vZGVsKSkgKyBmYWNldF9ncmlkKC4gfiBtZXRyaWMpICsgCiAgICAgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KCkgKwogICAgIHRoZW1lX2pyZigpICsKICAgICBzY2FsZV9jb2xvdXJfbWFudWFsKCJNb2RlbCIsIHZhbHVlcyA9IGMoJ2ZpdDEnID0gcGFsNTM4WydibHVlJ11bWzFdXSwgJ2ZpdDInID0gcGFsNTM4WydyZWQnXVtbMV1dKSkgKwogICAgIGxhYnModGl0bGUgPSAiUG9zaXRpdmUgUHJlZGljdGlvbiBWYWx1ZSAmIE5lZ2F0aXZlIFByZWRpY3Rpb24gVmFsdWUiLAogICAgICAgICB4ID0gIlByb2JhYmlsaXR5IFRocmVzaG9sZCIsIHkgPSAiUHJlZGljdGlvbiBWYWx1ZSIpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nKQoKYGBgCgpJIHdvdWxkIGNob29zZSBmaXQyLCBidXQgZml0MSBhbmQgZml0MiBhcmUgbmVhcmx5IHRoZSBzYW1lLiBJIHdvdWxkIHdhbnQgdG8gaXNlIGZpdDIgd2l0aCBhIHByb2JhYmlsaXR5IHRocmVzaG9sZCBzbGlnaHRseSBhYm92ZSAwLjUuCgojIyBCYXllcyBSdWxlCgojIyMgYQoKSWYgdGhlIHJpc2sgcmF0aW8gaXMgJFxmcmFje2FfezEsMH19e2FfezAsMX19ID0gMTAkIHRoZW4gJFxmcmFje2FfezAsMX19e2FfezEsMH19ID0gMC4xJC4gVGhlIHRocmVzaG9sZCBvdmVyIHRoZSAkUChZPTF8eCkgPiBcZnJhY3swLjF9ezEgKyAwLjF9ID0gYHIgMC4xIC8gKDEgKyAwLjEpYCQgb3IgJGxvZ2l0ID4gbG9nKFxmcmFje2ByIDAuMS8oMSswLjEpYH17MSAtIGByIDAuMS8oMSswLjEpYH0pID0gYHIgbG9nKCgwLjEvKDErMC4xKSkvKDEtKDAuMS8oMSswLjEpKSkpYCA9IGxvZygwLjEpJCBnaXZlcyB1cyB0aGUgQmF5ZXMgcnVsZS4KCldlIGhhdmUgdGhlIGxvZ2l0IGZyb20gMSBiKSwgdGh1cyB0aGUgbGluZWFyIGJvdW5kYXJ5IGZvciB0aGUgQmF5ZXMgY2xhc3NpZmllciBpcwoKJCRgciB0aWR5KGZpbmFsX21vZGVsKVsxLCAyXWAgKyAKYHIgdGlkeShmaW5hbF9tb2RlbClbMiwgMl1gYHIgdGlkeShmaW5hbF9tb2RlbClbMiwgMV1gICsKYHIgdGlkeShmaW5hbF9tb2RlbClbMywgMl1gYHIgdGlkeShmaW5hbF9tb2RlbClbMywgMV1gICsKYHIgdGlkeShmaW5hbF9tb2RlbClbNCwgMl1gYHIgdGlkeShmaW5hbF9tb2RlbClbNCwgMV1gICsKYHIgdGlkeShmaW5hbF9tb2RlbClbNSwgMl1gYHIgdGlkeShmaW5hbF9tb2RlbClbNSwgMV1gICsKYHIgdGlkeShmaW5hbF9tb2RlbClbNiwgMl1gYHIgdGlkeShmaW5hbF9tb2RlbClbNiwgMV1gICsKYHIgdGlkeShmaW5hbF9tb2RlbClbNywgMl1gYHIgdGlkeShmaW5hbF9tb2RlbClbNywgMV1gIFxcID0gbG9nKDAuMSkgPSBgciBsb2coMC4xKWAkJAoKIyMjIGIKCmBgYHtyfQptY2VfZGYgPC0gCiAgICBkYXRhX2ZyYW1lKAogICAgICAgIGhkID0gZnJhbWluZ2hhbV9maW5hbCRoZAogICAgICAgICwgZml0dGVkID0gZmluYWxfbW9kZWwkZml0dGVkLnZhbHVlcwogICAgICAgICwgcHJlZCA9IGFzLmludGVnZXIoZml0dGVkID4gMC4wOTA5KQogICAgICAgICwgYTEwID0gMTAgKiBpZmVsc2UoaGQgPT0gMSAmIHByZWQgIT0gMSwgMSwgMCkKICAgICAgICAsIGEwMSA9IDEgICogaWZlbHNlKGhkID09IDAgJiBwcmVkICE9IDAsIDEsIDApCiAgICAgICAgLCBhMTBfYTAxID0gYTEwICsgYTAxCiAgICApCm1jZSA8LSBzdW0obWNlX2RmJGExMF9hMDEpIC8gbnJvdyhtY2VfZGYpCmBgYAoKV2UgdXNlIHRoZSBmb3JtdWxhIGZvciB3ZWlnaHRlZCBtaXNjbGFzc2lmaWNhdGlvbiBlcnJvcgoKJCRNQ0U9XGZyYWN7YV97MTB9IFxzdW1fe1xoYXR7eX1faT0xfSBJXHt5X2kgXG5lcSBcaGF0e3l9X2lcfSArIGFfezAxfSBcc3VtX3tcaGF0e3l9X2k9MH0gSVx7eV9pIFxuZXEgXGhhdHt5fV9pXH19e259JCQKCgpGb3IgdGhlIHJpc2sgcmF0aW8gb2YgJFxmcmFje2FfezEsMH19e2FfezAsMX19ID0gMTAkLCB0aGUgTUNFIGlzICoqYHIgbWNlYCoqLgoKIyMjIGMKCmBgYHtyfQpsaXpfcHJlZF9sb2dpdCA8LSBwcmVkaWN0KGZpbmFsX21vZGVsLCBsaXosIHR5cGUgPSAnbGluaycpCmBgYAoKRm9yIExpeiwgdGhlIGxvZ2l0IGlzICoqYHIgbGl6X3ByZWRfbG9naXRgKiogYW5kIHRodXMgd2UgY2xhc3NpZnkgaGVyIGFzICoqMCBvciB3aWxsIG5vdCBoYXZlIGhlYXJ0IGRpc2Vhc2UqKi4KCiMjIyBkICYgZQoKQmVsb3cgaXMgYSBwbG90IG9mIHRoZSBwb3N0ZXJpb3IgdGhyZXNob2xkcyBieSB0aGUgYXNzb2NpYXRlZCB3ZWlnaHRlZCBtaXNjbGFzc2lmaWNhdGlvbiByYXRlcyB1c2luZyB0aGUgdHdvIHJpc2sgcmF0aW9zICRcZnJhY3thX3sxLDB9fXthX3swLDF9fSA9IDEwJCBhbmQgJFxmcmFje2FfezEsMH19e2FfezAsMX19ID0gMSQuIAoKYGBge3J9CnRocmVzaG9sZF9tY2VfZGYgPC0gZGF0YV9mcmFtZSgpCnBvc3Rlcmlvcl90aHJlc2hvbGRzIDwtIHNlcSgKIGZyb20gPSByb3VuZChleHAobWluKGZpbmFsX21vZGVsJGZpdHRlZCkpIC8gKDEgKyBleHAobWluKGZpbmFsX21vZGVsJGZpdHRlZCkpKSwgMikgLSAwLjAxLCAKIHRvID0gcm91bmQoZXhwKG1heChmaW5hbF9tb2RlbCRmaXR0ZWQpKSAvICgxICsgZXhwKG1heChmaW5hbF9tb2RlbCRmaXR0ZWQpKSksIDIpLCAKIGxlbmd0aC5vdXQgPSA0MCkKZm9yICh0aHJlc2hvbGQgaW4gcG9zdGVyaW9yX3RocmVzaG9sZHMpIHsKICAgIHRocmVzaG9sZF9tY2VfZGYgPC0KICAgICAgICBiaW5kX3Jvd3MoCiAgICAgICAgdGhyZXNob2xkX21jZV9kZiwgCiAgICAgICAgICAgIGRhdGFfZnJhbWUoCiAgICAgICAgICAgICAgICBoZCA9IGZyYW1pbmdoYW1fZmluYWwkaGQKICAgICAgICAgICAgICAgICwgZml0dGVkID0gZmluYWxfbW9kZWwkZml0dGVkLnZhbHVlcwogICAgICAgICAgICAgICAgLCBwcmVkID0gYXMuaW50ZWdlcihmaXR0ZWQgPiBsb2codGhyZXNob2xkIC8gKDEgLSB0aHJlc2hvbGQpKSkKICAgICAgICAgICAgICAgICwgYTEwXzEwID0gMTAgKiBpZmVsc2UoaGQgPT0gMSAmIHByZWQgIT0gMSwgMSwgMCkKICAgICAgICAgICAgICAgICwgYTEwXzEgID0gMSAgKiBpZmVsc2UoaGQgPT0gMSAmIHByZWQgIT0gMSwgMSwgMCkKICAgICAgICAgICAgICAgICwgYTAxICAgID0gMSAgKiBpZmVsc2UoaGQgPT0gMCAmIHByZWQgIT0gMCwgMSwgMCkKICAgICAgICAgICAgICAgICwgYTEwX2EwMV8xMCA9IGExMF8xMCArIGEwMQogICAgICAgICAgICAgICAgLCBhMTBfYTAxXzEgID0gYTEwXzEgICsgYTAxCiAgICAgICAgICAgICkgJT4lIAogICAgICAgICAgICBzdW1tYXJpc2UoCiAgICAgICAgICAgICAgICBtY2VfMTAgPSBzdW0oYTEwX2EwMV8xMCkgLyBuKCkKICAgICAgICAgICAgICAgICwgbWNlXzEgPSBzdW0oYTEwX2EwMV8xKSAvIG4oKQogICAgICAgICAgICApICU+JQogICAgICAgICAgICBtdXRhdGUodGhyZXNob2xkID0gdGhyZXNob2xkKSAlPiUKICAgICAgICAgICAgc2VsZWN0KHRocmVzaG9sZCwgbWNlXzEwLCBtY2VfMSkKICAgICAgICApCn0KCnRocmVzaG9sZF9tY2VfZGYyIDwtCiAgICB0aHJlc2hvbGRfbWNlX2RmICU+JQogICAgc2VsZWN0KHRocmVzaG9sZCwgbWNlXzEwLCBtY2VfMSkgJT4lCiAgICBnYXRoZXIocmlza19yYXRpbywgbWNlLCAtdGhyZXNob2xkKSAlPiUKICAgIG11dGF0ZShyaXNrX3JhdGlvID0gZ3N1YigibWNlXyIsIiIscmlza19yYXRpbykpCgpnZ3Bsb3QodGhyZXNob2xkX21jZV9kZjIsIGFlcyh4ID0gdGhyZXNob2xkLCB5ID0gbWNlLCBjb2xvdXIgPSBhcy5mYWN0b3Iocmlza19yYXRpbykpKSArCiAgICBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKSArCiAgICB0aGVtZV9qcmYoKSArCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKCJSaXNrIFJhdGlvIiwgdmFsdWVzID0gYygnMTAnID0gcGFsNTM4WydibHVlJ11bWzFdXSwgJzEnID0gcGFsNTM4WydyZWQnXVtbMV1dKSkgKwogICAgbGFicyh0aXRsZSA9ICJNaXNjbGFzc2lmaWNhdGlvbiBFcnJvciAoTUNFKSB2cyBQb3N0ZXJpb3IgVGhyZXNob2xkIiwKICAgICAgICAgeCA9ICJQb3N0ZXJpb3IgVGhyZXNob2xkIiwgeSA9ICJXZWlnaHRlZCBNaXNjbGFzc2lmaWNhdGlvbiBFcnJvciAoTUNFKSIpCmBgYAoKVGhlIHJhbmdlIG9mIHRoZSB4LWF4aXMgaXMgZnJvbSAwLjUgdG8gMC43IHdoaWNoIHJlZmxlY3RzIHRoZSByYW5nZSBvZiB0aGUgZml0dGVkIHZhbHVlcyBmcm9tIHRoZSBmaW5hbCBtb2RlbCBmb3IgMSBiKS4gV2Uga25vdyBmcm9tIG1vZGVsIHRoZSBmaXR0ZWQgdmFsdWVzIGhhdmUgYSByYW5nZSBmcm9tIGByIG1pbihmaW5hbF9tb2RlbCRmaXR0ZWQpYCB0byBgciBtYXgoZmluYWxfbW9kZWwkZml0dGVkKWAgd2hpY2ggd2hlbiBleHBvbmVudGlhdGVkIGFuZCBjb252ZXJ0ZWQgdG8gYSBwcm9iYWJpbGl0eSBpcyBgciByb3VuZChleHAobWluKGZpbmFsX21vZGVsJGZpdHRlZCkpIC8gKDEgKyBleHAobWluKGZpbmFsX21vZGVsJGZpdHRlZCkpKSwgMikgLSAwLjAxYCB0byBgciByb3VuZChleHAobWF4KGZpbmFsX21vZGVsJGZpdHRlZCkpIC8gKDEgKyBleHAobWF4KGZpbmFsX21vZGVsJGZpdHRlZCkpKSwgMilgLgoKV2hlbiAkXGZyYWN7YV97MSwwfX17YV97MCwxfX0gPSAxMCQgdGhlIEJheWVzIHJ1bGUgY2xhc3NpZmllciBkb2VzIG5vdCBwZXJmb3JtIHdlbGwgZm9yIGxhcmdlIHRocmVzaG9sZHMuIFdoZW4gdGhlIHRocmVzaG9sZCBpcyAwLjUsIHRoZSBydWxlIGlzIGFsbCBvYnNlcnZhdGlvbnMgYXJlIGBoZCA9IDFgIHdoaWNoIG1lYW5zIHRoYXQgJGFfezEwfSBcc3VtX3tcaGF0e3l9X2k9MX0gSVx7eV9pIFxuZXEgXGhhdHt5fV9pXH0kIGdvZXMgdG8gMC4gVGhlIG1pc2NsYXNzaWZpY2F0aW9uIGluaXRpYWxseSBnb2VzIGRvd24sIGJ1dCB0aGVuIHRoZXJlIGlzIGEgaHVnZSBwZW5hbGl0eSBmb3IgZmFsc2UgbmVnYXRpdmVzLgoKVGhlIEJheWVzIHJ1bGUgY2xhc3NpZmllciB3aXRoICRcZnJhY3thX3sxLDB9fXthX3swLDF9fSA9IDEkIHBlcmZvcm1zIG11Y2ggYmV0dGVyIGFuZCBpbmRlZWQgdGhlIG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUgZGVjcmVhc2VzIGFzIHRoZSB0aHJlc2hvbGQgaW5jcmVhc2VzLgoKIyBRdWVzdGlvbiAyCgpgYGB7cn0KYmlsbHNfdHJhaW4gPC0gcmVhZC5jc3YocGFzdGUwKGRhdGFfZGlyLCAnQmlsbHMuc3Vic2V0LmNzdicpLCByb3cubmFtZXMgPSAxLCBuYS5zdHJpbmdzPWMoIiIsIk5BIikpICU+JSBhc190aWJibGUoKQpiaWxsc190ZXN0IDwtIHJlYWQuY3N2KHBhc3RlMChkYXRhX2RpciwgJ0JpbGxzLnN1YnNldC50ZXN0LmNzdicpLCByb3cubmFtZXMgPSAxKSAlPiUgYXNfdGliYmxlKCkKYGBgCiMjIEV4ZWN1dGl2ZSBTdW1tYXJ5CgpPdXIgb2JqZWN0aXZlIGlzIHRvIHByZWRpY3Qgd2hldGhlciBvciBub3QgYSBiaWxsIHdpbGwgcGFzcyB0aGUgdGhlIGZsb29yIG9mIHRoZSBQZW5uc3lsdmFuaWEgU3RhdGUgSG91c2UgYmFzZWQgb24gY2hhcmFjdGVyaXN0aWNzIG9mIHRoZSBiaWxsLiBTb21lIG9mIHRoZSBjaGFyYWN0ZXJpc3RpY3Mgb2YgYmlsbHMgdGhhdCB3ZSB1c2UgaW4gdGhpcyBhbmFseXNpcyBpcyB0aGUgbnVtYmVyIGFtZW5kbWVudHMgdGhlIGJpbGwgaGFkLCB0aGUgc2Vzc2lvbiB0aGUgYmlsbCB3YXMgaW50cm9kdWNlZCwgYW5kIHRoZSBudW1iZXIgb2YgY29zcG9uc29ycy4gVXNpbmcgdGhlIHByb2Nlc3Mgb2YgdHJhaW5pbmcvdGVzdCBkYXRhc2V0cyBhbmQgY3Jvc3MtdmFsaWRhdGlvbiwgd2UgYnVpbHQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGZvciB0aGlzIHByZWRpY3Rpb24gZXhlcmNpc2UuIEhvd2V2ZXIsIGEgYmlsbCBwYXNzaW5nIHRoZSBTdGF0ZSBIb3VzZSBpcyBhIHJhcmUgZXZlbnQuIExlc3MgdGhhbiA3JSBvZiBpbnRyb2R1Y2VkIGJpbGxzIGhhdmUgcGFzc2VkIHRoZSBIb3VzZSBzaW5jZSB0aGUgMjAwOS0yMDEwIHNlc3Npb24uIEFzIGEgcmVzdWx0LCBvdXIgZmluYWwgbW9kZWwgaXMgZWZmZWN0aXZlIGF0IGlkZW50aWZ5aW5nIGJpbGxzIHRoYXQgd2lsbCBub3QgcGFzcywgYnV0IGlzIGxlc3MgZWZmZWN0aXZlIGF0IGlkZW50aWZ5aW5nIGJpbGxzIHRoYXQgd2lsbCBwYXNzLgoKIyMgU3VtbWFyeSBvZiBEYXRhCgpDb2xsZWN0aXZlbHksIHRoZSBiaWxscyBkYXRhc2V0IGNvbnRhaW5zICoqYHIgbnJvdyhiaWxsc190cmFpbikgKyBucm93KGJpbGxzX3Rlc3QpYCoqIG9ic2VydmF0aW9ucyBzcGxpdCBpbnRvIHRyYWluaW5nIChgciAgbnJvdyhiaWxsc190cmFpbilgKSBhbmQgdGVzdCAoYHIgIG5yb3coYmlsbHNfdGVzdClgKSBzZXRzLgoKYGBge3IgUmVjb2RlIHN0YXR1c30KcGFzc192YWxzIDwtIGMoImJpbGw6cGFzc2VkIiwgImdvdmVybm9yOnNpZ25lZCIsICJnb3Zlcm5vcjpyZWNlaXZlZCIpCmJpbGxzX3RyYWluMiA8LQogICAgYmlsbHNfdHJhaW4gJT4lCiAgICBtdXRhdGUoCiAgICAgICAgc3RhdHVzMiA9IGFzLmZhY3Rvcihhcy5udW1lcmljKHN0YXR1cyAlaW4lIHBhc3NfdmFscykpCiAgICAgICAgLCBkb3cgPSBmYWN0b3IoZGF5Lm9mLndlZWsuaW50cm9kdWNlZCwgbGFiZWxzID0gYygiTW9uIiwgIlR1ZSIsICJXZWQiLCAiVGh1IiwgIkZyaSIsICJTYXQiKSkKICAgICAgICAsIGlzX3Nwb25zb3JfaW5fbGVhZGVyc2hpcDIgPSBhcy5mYWN0b3IoaXNfc3BvbnNvcl9pbl9sZWFkZXJzaGlwKQogICAgKQoKYmlsbHNfdGVzdDIgPC0KICAgIGJpbGxzX3Rlc3QgJT4lCiAgICBtdXRhdGUoCiAgICAgICAgc3RhdHVzMiA9IGFzLmZhY3Rvcihhcy5udW1lcmljKHN0YXR1cyAlaW4lIHBhc3NfdmFscykpCiAgICAgICAgLCBkb3cgPSBmYWN0b3IoZGF5Lm9mLndlZWsuaW50cm9kdWNlZCwgbGFiZWxzID0gYygiTW9uIiwgIlR1ZSIsICJXZWQiLCAiVGh1IiwgIkZyaSIsICJTYXQiKSkKICAgICAgICAsIGlzX3Nwb25zb3JfaW5fbGVhZGVyc2hpcDIgPSBhcy5mYWN0b3IoaXNfc3BvbnNvcl9pbl9sZWFkZXJzaGlwKQogICAgKQpgYGAKCldlIGZpbmQgdGhhdCBgciBiaWxsc190cmFpbjIgJT4lIGZpbHRlcighY29tcGxldGUuY2FzZXMoLikpICU+JSBucm93KCkgJT4lIHVubGlzdCgpYCBvYnNlcnZhdGlvbnMgaW4gdGhlIHRyYWluaW5nIHNldCBhcmUgbWlzc2luZyBhdCBsZWFzdCBvbmUgdmFsdWUgKGByIGJpbGxzX3Rlc3QyICU+JSBmaWx0ZXIoIWNvbXBsZXRlLmNhc2VzKC4pKSAlPiUgbnJvdygpICU+JSB1bmxpc3QoKWAgaW4gdGhlIHRlc3Qgc2V0KS4gTGV0J3MgbG9vayBieSBmZWF0dXJlLgoKYGBge3IgTWlzc2luZyBEYXRhfQpiaWxsc190cmFpbjIgJT4lCiAgICBzdW1tYXJpc2VfYWxsKGZ1bnMoc3VtKGlzLm5hKC4pKSkpICU+JQogICAgZ2F0aGVyKGZlYXR1cmUsIE5BcykgJT4lCiAgICBhcnJhbmdlKGRlc2MoTkFzKSkKYGBgCgpUaGVyZSBhcmUgYHIgc3VtKGlzLm5hKGJpbGxzX3RyYWluMiRzdGF0dXMpKWAgdmFsdWVzIG9mIHN0YXR1cyB0aGF0IGFyZSBgTkFgLiBCYXNlZCBvbiB0aGUgYXNzaWdubWVudCdzIGNvbW1lbnQgdGhhdCAiQWxsIG90aGVyIG91dGNvbWVzIGFyZSBmYWlsdXJlcyIgd2Ugd2lsbCBhc3N1bWUgdGhlc2UgYXJlIGZhaWx1cmVzIHRvIHBhc3MgcmF0aGVyIHRoYW4gbWlzc2luZyB2YWx1ZXMuIFdlIHdpbGwgcmVtb3ZlIHRoZSBvYnNlcnZhdGlvbnMgd2l0aG91dCBkYXkgb2Ygd2VlayAoZnJvbSB0cmFpbmluZyBhbmQgdGVzdCBkYXRhc2V0cykgc28gdGhhdCBkaWZmZXJlbnQgbW9kZWxzIGFyZSBjb21wYXJhYmxlLgoKCmBgYHtyfQpiaWxsc190cmFpbjMgPC0gCiAgICBiaWxsc190cmFpbjIgJT4lCiAgICBzZWxlY3QoLW9yaWdpbmF0aW5nX2NvbW1pdHRlZSkgJT4lCiAgICBmaWx0ZXIoY29tcGxldGUuY2FzZXMoLikpCgpiaWxsc190ZXN0MyA8LSAKICAgIGJpbGxzX3Rlc3QyICU+JQogICAgc2VsZWN0KC1vcmlnaW5hdGluZ19jb21taXR0ZWUpICU+JQogICAgZmlsdGVyKGNvbXBsZXRlLmNhc2VzKC4pKQpgYGAKCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CnBhbmRlcihzdW1tYXJ5KGJpbGxzX3RyYWluMyksIG1pc3NpbmcgPSAiIiwgc3BsaXQudGFibGUgPSAxNTApCmBgYCAKClRvIGV4cGxvcmUgdGhlIGRhdGEgZnVydGhlciB3ZSBjcmVhdGUgYSBwYWlycyBwbG90LgoKYGBge3IgQmlsbHMgUGFpcnMsIGZpZy53aWR0aCA9IDcuNSwgZmlnLmhlaWdodCA9IDd9CmJpbGxfcGFpcnMgPC1nZ3BhaXJzKGJpbGxzX3RyYWluMyAlPiUgc2VsZWN0KHN0YXR1czIsIHNwb25zb3JfcGFydHksIHNlc3Npb24sIGlzX3Nwb25zb3JfaW5fbGVhZGVyc2hpcDIsIGRvdyksIAogICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKGNvbG91ciA9IHN0YXR1czIpKQoKZm9yIChyb3cgaW4gc2VxX2xlbihiaWxsX3BhaXJzJG5yb3cpKQogICAgZm9yIChjb2wgaW4gc2VxX2xlbihiaWxsX3BhaXJzJG5jb2wpKQogICAgICAgIGJpbGxfcGFpcnNbcm93LCBjb2xdIDwtIGJpbGxfcGFpcnNbcm93LCBjb2xdICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKHBhbDUzOFsnYmx1ZSddW1sxXV0sIHBhbDUzOFsncmVkJ11bWzFdXSkpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYyhwYWw1MzhbJ2JsdWUnXVtbMV1dLCBwYWw1MzhbJ3JlZCddW1sxXV0pKSAgICAKCmJpbGxfcGFpcnMKYGBgCgpgYGB7ciBCaWxscyBQYWlycyAyLCBmaWcud2lkdGggPSA3LjUsIGZpZy5oZWlnaHQgPSA3fQpiaWxsX3BhaXJzMiA8LWdncGFpcnMoYmlsbHNfdHJhaW4zICU+JSBzZWxlY3Qoc3RhdHVzMiwgbnVtX2Nvc3BvbnNvcnMsIG51bV9kX2Nvc3BvbnNvcnMsIG51bV9yX2Nvc3BvbnNvcnMsIHRpdGxlX3dvcmRfY291bnQsIG51bV9hbWVuZG1lbnRzLCBudW1fb3JpZ2luYXRpbmdfY29tbWl0dGVlX2Nvc3BvbnNvcnMsIG51bV9vcmlnaW5hdGluZ19jb21taXR0ZWVfY29zcG9uc29yc19yLCBudW1fb3JpZ2luYXRpbmdfY29tbWl0dGVlX2Nvc3BvbnNvcnNfZCksIG1hcHBpbmcgPSBhZXMoY29sb3VyID0gc3RhdHVzMikpCgpmb3IgKHJvdyBpbiBzZXFfbGVuKGJpbGxfcGFpcnMyJG5yb3cpKQogICAgZm9yIChjb2wgaW4gc2VxX2xlbihiaWxsX3BhaXJzMiRuY29sKSkKICAgICAgICBiaWxsX3BhaXJzMltyb3csIGNvbF0gPC0gYmlsbF9wYWlyczJbcm93LCBjb2xdICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKHBhbDUzOFsnYmx1ZSddW1sxXV0sIHBhbDUzOFsncmVkJ11bWzFdXSkpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYyhwYWw1MzhbJ2JsdWUnXVtbMV1dLCBwYWw1MzhbJ3JlZCddW1sxXV0pKSAgICAKCmJpbGxfcGFpcnMyCmBgYAoKQmFzZWQgb24gdGhlIHBhaXJzIHBsb3RzIHdlIG1ha2UgdGhlIGZvbGxvd2luZyBvYnNlcnZhdGlvbnMKCjEuIEEgYmlsbCBwYXNzaW5nIGlzIGEgcmVsYXRpdmVseSByYXJlIGV2ZW50IChgciBzdW0oYmlsbHNfdHJhaW4zJHN0YXR1czIgPT0gMSlgIGluIGByIG5yb3coYmlsbHNfdHJhaW4zKWAgb3IgYHIgcm91bmQoMTAwICogc3VtKGJpbGxzX3RyYWluMyRzdGF0dXMyID09IDEpIC8gbnJvdyhiaWxsc190cmFpbjMpLCAyKWAlKQoyLiBXaGV0aGVyIHRoZSBiaWxsJ3Mgc3BvbnNlciBpcyBpbiBsZWFkZXJzaGlwIGRvZXMgbm90IGhhdmUgYXMgYSBsYXJnZSBvZiBlZmZlY3QgYXMgZXhwZWN0ZWQKMy4gVGhlIG51bWJlciBvZiBhbWVuZG1lbnRzIGZlYXR1cmVzIGxvb2tzIHRvIGJlIHJlbGF0aXZlbHkgcG93ZXJmdWwgKGZvdXJ0aCBjb2x1bW4gZnJvbSB0aGUgcmlnaHQgYWJvdmUpCgpHaXZlbiB0aGUgcGFydGlzYW5zaGlwIG9mIHRoZSBQQSwgd2Ugd291bGQgaW1hZ2luZSB0aGF0IGEgYmlsbCdzIHN1Y2Nlc3NmdWwgaXMgbGlrZWx5IGRlcGVuZGVudCBvbiB0aGUgcGFydHkgc3BvbnNvci4gV2UgY3JlYXRlIGEgZnJlcXVlbmN5IHRhYmxlIGFuZCBQZWFyc29uJ3MgQ2hpLXNxdWFyZWQgdGVzdC4KCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CnBhbmRlcihkZXNjcjo6Q3Jvc3NUYWJsZShiaWxsc190cmFpbjMkc3RhdHVzMiwgYmlsbHNfdHJhaW4zJHNwb25zb3JfcGFydHksIHByb3AuY2hpc3E9RkFMU0UsIHByb3AudCA9IEZBTFNFLCBjaGlzcSA9IFRSVUUsIGRubiA9IGMoIlN0YXR1cyIsIlNwb25zb3IgUGFydHkiKSksIHNwbGl0LnRhYmxlID0gSW5mLCBiaWcubWFyayA9ICcsJykKCnBhbmRlcihjaGlzcS50ZXN0KGJpbGxzX3RyYWluMyRzdGF0dXMyLCBiaWxsc190cmFpbjMkc3BvbnNvcl9wYXJ0eSksIGNhcHRpb24gPSAiUGVhcnNvbidzIENoaS1zcXVhcmVkIHRlc3Qgd2l0aCBZYXRlcycgY29udGludWl0eSBjb3JyZWN0aW9uIikKYGBgCgpXZSBhbHNvIGxvb2sgYXQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG51bWJlciBvZiBhbWVuZG1lbnRzIGFuZCB3aGV0aGVyIHRoZSBiaWxscyBwYXNzZWQuIFdlIG5vdGUgdGhhdCBiaWxscyB3aXRoIGF0IGxlYXN0IG9uZSBhbWVuZG1lbnQgaGFkIGByIHJvdW5kKDEwMCAqIGJpbGxzX3RyYWluMyAlPiUgZmlsdGVyKG51bV9hbWVuZG1lbnRzID4gMCkgJT4lIHN1bW1hcmlzZShwYXNzX3JhdGUgPSBzdW0oc3RhdHVzMiA9PSAxKSAvIG4oKSksMikgJT4lIHVubGlzdCgpYCUgcGFzcyByYXRlIHZzIGByIHJvdW5kKDEwMCAqIGJpbGxzX3RyYWluMyAlPiUgZmlsdGVyKG51bV9hbWVuZG1lbnRzID09IDApICU+JSBzdW1tYXJpc2UocGFzc19yYXRlID0gc3VtKHN0YXR1czIgPT0gMSkgLyBuKCkpLDIpICU+JSB1bmxpc3QoKWAlIGZvciBiaWxscyB3aXRob3V0IGFuIGFtZW5kbWVudC4KCmBgYHtyLCByZXN1bHRzPSdhc2lzJ30KYmlsbHNfdHJhaW4zICU+JQogICAgZ3JvdXBfYnkobnVtX2FtZW5kbWVudHMsIHN0YXR1czIpICU+JQogICAgY291bnQoKSAlPiUKICAgIHNwcmVhZChzdGF0dXMyLCBuKSAlPiUKICAgIG11dGF0ZShgVG90YWxgID0gYXMuaW50ZWdlcihpZmVsc2UoaXMubmEoYDBgKSwgMCAsYDBgKSkgKyBgMWApICU+JQogICAgcmVuYW1lKGBBbWVuZG1lbnRzIHwgU3RhdHVzYCA9IG51bV9hbWVuZG1lbnRzKSAlPiUKICAgIHBhbmRlcihtaXNzaW5nID0gJycpCmBgYAoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KYmlsbHNfdHJhaW4zICU+JQogICAgbXV0YXRlKGFtZW5kbWVudHMgPSBpZmVsc2UobnVtX2FtZW5kbWVudHMgPT0gMCwgIjAgQW1lbmRtZW50cyIsICIxKyBBbWVuZG1lbnRzIikpICU+JQogICAgZ3JvdXBfYnkoYW1lbmRtZW50cykgJT4lCiAgICBzdW1tYXJpc2UocGFzc19yYXRlID0gc3VtKHN0YXR1czIgPT0gMSkgLyBuKCkpICU+JQogICAgdW5ncm91cCgpICU+JQogICAgc3ByZWFkKGFtZW5kbWVudHMsIHBhc3NfcmF0ZSkgJT4lCiAgICBwYW5kZXIoKQpgYGAKClRoZSBtYW55IGNvbnRpbm91cyBleHBsYW5hdG9yeSB2YXJpYWJsZXMgYXJlIGRlcnZpYXRpdmVzIG9mIGFub3RoZXIgYW5kIHdlIHdvdWxkIGV4cGVjdCB0byBzbyBzZWUgc29tZSBjb2xpbmVhcml0eSBhbW9uZyB0aGVzZSB2YXJpYWJsZXMuIFRoZSBjb3JyZWxhdGlvbiBtYXRyaXggYmVsb3dzIHNob3dzIHRoYXQgbWFueSB2YXJpYWJsZXMgYXJlIHZlcnkgY2xvc2VseSBwb3NpdGl2ZWx5IGNvcnJlbGF0ZWQgdG8gZWFjaCBvdGhlci4KCmBgYHtyIGZpZy53aWR0aCA9IDcuNX0KYmlsbHNfdHJhaW4zICU+JSAKICAgIHNlbGVjdChudW1fY29zcG9uc29ycywgbnVtX2RfY29zcG9uc29ycywgbnVtX3JfY29zcG9uc29ycywgdGl0bGVfd29yZF9jb3VudCwgbnVtX2FtZW5kbWVudHMsCiAgICAgICAgICAgbnVtX29yaWdpbmF0aW5nX2NvbW1pdHRlZV9jb3Nwb25zb3JzLCBudW1fb3JpZ2luYXRpbmdfY29tbWl0dGVlX2Nvc3BvbnNvcnNfciwgCiAgICAgICAgICAgbnVtX29yaWdpbmF0aW5nX2NvbW1pdHRlZV9jb3Nwb25zb3JzX2QpICU+JQogICAgY29yKCkgJT4lCiAgICAjY29ycnBsb3QobWV0aG9kPSJzcXVhcmUiLCB0bC5jZXggPSAxL3BhcigiY2V4IiksIHRsLmNvbCA9ICdibGFjaycsIHRsLnBvcyA9ICdsJykKICAgIGNvcnJwbG90KHR5cGUgPSAidXBwZXIiLCB0bC5wb3MgPSAidGQiLCBtZXRob2QgPSAic3F1YXJlIiwgdGwuY2V4ID0gMC41LCB0bC5jb2wgPSAnYmxhY2snLCBkaWFnID0gRkFMU0UpCmBgYAoKIyMgQ2xhc3NpZmllcgoKIyMjIEZlYXR1cmUgRW5naW5lZXJpbmcKCldlIHF1aWNrbHkgbm90aWNlIHRoYXQgd2hhdCBpcyBtaXNzaW5nIGlzIGEgZmVhdHVyZSBmb3IgdGhlIGJpcGFydGlzYW4gc3VwcG9ydCBvZiBhIGJpbGwuIEluIGEgc3RhdGUgbGlrZSBQZW5uc3lsdmFuaWEsIHdoZXRoZXIgb3Igbm90IGEgYmlsbCBwYXNzZXMgaXMgbGlrZWx5IGEgZnVuY3Rpb24gb2YgdGhlIGJpcGFydGlzYW5zaGlwIG9mIHRoZSBiaWxsLiBXZSBjYW4gY3JlYXRlIGFuIGluZGljYXRvciBmZWF0dXJlIGFzIHRvIHdoZXRoZXIgb3Igbm90IGEgYmlsbCBoYXMgYm90aCBEZW1vY3JhdCBhbmQgUmVwdWJsaWNhbiBzcG9uc29ycy4KCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CmJpbGxzX3RyYWluNCA8LSAKICAgIGJpbGxzX3RyYWluMyAlPiUKICAgIG11dGF0ZShpc19iaXBhcnRpc2FuID0gZmFjdG9yKGlmZWxzZShudW1fZF9jb3Nwb25zb3JzID4gMCAmIG51bV9yX2Nvc3BvbnNvcnMgPiAwLCAxLCAwKSkpCgpiaWxsc190ZXN0NCA8LSAKICAgIGJpbGxzX3Rlc3QzICU+JQogICAgbXV0YXRlKGlzX2JpcGFydGlzYW4gPSBmYWN0b3IoaWZlbHNlKG51bV9kX2Nvc3BvbnNvcnMgPiAwICYgbnVtX3JfY29zcG9uc29ycyA+IDAsIDEsIDApKSkKCmJpcGFydGlzYW5fdGJsIDwtIHRhYmxlKGJpbGxzX3RyYWluNCRzdGF0dXMyLCBiaWxsc190cmFpbjQkaXNfYmlwYXJ0aXNhbikKCnBhbmRlcihkZXNjcjo6Q3Jvc3NUYWJsZShiaWxsc190cmFpbjQkc3RhdHVzMiwgYmlsbHNfdHJhaW40JGlzX2JpcGFydGlzYW4sIHByb3AuY2hpc3E9RkFMU0UsIHByb3AudCA9IEZBTFNFLCBjaGlzcSA9IFRSVUUsIGRubiA9IGMoIlN0YXR1cyIsIkJpcGFydGlzYW4iKSksIHNwbGl0LnRhYmxlID0gSW5mLCBiaWcubWFyayA9ICcsJykKYGBgCgpXZSBzZWUgdGhhdCBvZiB0aGUgYmlsbHMgdGhhdCBkbyBwYXNzLCBuZWFybHkgYHIgcm91bmQoMTAwKiBiaXBhcnRpc2FuX3RibFsyLDJdIC8gKGJpcGFydGlzYW5fdGJsWzIsMl0gKyBiaXBhcnRpc2FuX3RibFsyLDFdKSwyKWAlIG9mIHRoZW0gaGF2ZSBiaXBhcnRpc2FuIHN1cHBvcnQuCgojIyMgQmFja3dhcmRzIFNlbGVjdGlvbgoKV2UgYmVnaW4gYnkgYnVpbGRpbmcgYSBtb2RlbCB3aXRoIGFsbCB0aGUgcHJlZGljdG9ycyB3ZSB0aGluayBjb3VsZCBhZmZlY3QgdGhlIHN0YXR1cyBvZiBhIGJpbGwuIEFzIGV4cGVjdGVkIGZyb20gb3VyIGV4cGxvcmF0b3J5IGFuYWx5c2lzLCB3ZSBmaW5kIHRoZSBudW1iZXIgb2YgY29zcG9uc29ycyBmcm9tIGVhY2ggcGFydHkgYW5kIHRoZSBudW1iZXIgb2YgcGFydHkgbWVtYmVycyBvbiBlYWNoIG9yaWdpbmF0aW5nIGNvbW1pdHRlZSBpcyBjb3JyZWxhdGVkIHRvIHRoZSBudW1iZXIgb2YgY29zcG9uc29ycyBhbmQgbnVtYmVyIG9mIG1lbWJlcnMgb24gZWFjaCBvcmlnaW5hdGluZyBjb21taXR0ZWUuCgpXZSByZW1vdmUgdGhlc2UgYW5kIGNvbnRpbnVlIHdpdGggYmFja3dhcmQgc2VsZWN0aW9uIHVudGlsIGFsbCB0aGUgdmFyaWFibGVzIGluIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGFyZSBzaWduaWZpY2FudCBhdCB0aGUgMC4wMSBjb25maWRlbmNlIGxldmVsLiBPdXIgaW5pdGlhbCBtb2RlbCBpcyBzaG93biBiZWxvdy4KCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CmJpbGxzX2ZpdDEgPC0gZ2xtKHN0YXR1czIgfiBzZXNzaW9uICsgZG93ICsgaXNfc3BvbnNvcl9pbl9sZWFkZXJzaGlwMiArIHNwb25zb3JfcGFydHkgKyBpc19iaXBhcnRpc2FuICsKICAgICAgICAgICAgICAgICAgICAgIG51bV9jb3Nwb25zb3JzICsgbnVtX2RfY29zcG9uc29ycyArIG51bV9yX2Nvc3BvbnNvcnMgKyB0aXRsZV93b3JkX2NvdW50ICsgbnVtX2FtZW5kbWVudHMgKwogICAgICAgICAgICAgICAgICAgICAgbnVtX29yaWdpbmF0aW5nX2NvbW1pdHRlZV9jb3Nwb25zb3JzICsgbnVtX29yaWdpbmF0aW5nX2NvbW1pdHRlZV9jb3Nwb25zb3JzX3IgKwogICAgICAgICAgICAgICAgICAgICAgbnVtX29yaWdpbmF0aW5nX2NvbW1pdHRlZV9jb3Nwb25zb3JzX2QsIGRhdGEgPSBiaWxsc190cmFpbjQsIGZhbWlseSA9IGJpbm9taWFsKQpiaWxsc19maXQyIDwtIGdsbShzdGF0dXMyIH4gc2Vzc2lvbiArIGRvdyArIGlzX3Nwb25zb3JfaW5fbGVhZGVyc2hpcDIgKyBzcG9uc29yX3BhcnR5ICsgaXNfYmlwYXJ0aXNhbiArCiAgICAgICAgICAgICAgICAgICAgICBudW1fY29zcG9uc29ycyArIG51bV9kX2Nvc3BvbnNvcnMgKyB0aXRsZV93b3JkX2NvdW50ICsgbnVtX2FtZW5kbWVudHMgKwogICAgICAgICAgICAgICAgICAgICAgbnVtX29yaWdpbmF0aW5nX2NvbW1pdHRlZV9jb3Nwb25zb3JzICsgbnVtX29yaWdpbmF0aW5nX2NvbW1pdHRlZV9jb3Nwb25zb3JzX3IsIAogICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGJpbGxzX3RyYWluNCwgZmFtaWx5ID0gYmlub21pYWwpCmJpbGxzX2ZpdDMgPC0gZ2xtKHN0YXR1czIgfiBzZXNzaW9uICsgZG93ICsgaXNfc3BvbnNvcl9pbl9sZWFkZXJzaGlwMiArIHNwb25zb3JfcGFydHkgKyBpc19iaXBhcnRpc2FuICsKICAgICAgICAgICAgICAgICAgICAgIG51bV9jb3Nwb25zb3JzICsgdGl0bGVfd29yZF9jb3VudCArIG51bV9hbWVuZG1lbnRzICsKICAgICAgICAgICAgICAgICAgICAgIG51bV9vcmlnaW5hdGluZ19jb21taXR0ZWVfY29zcG9uc29ycyArIG51bV9vcmlnaW5hdGluZ19jb21taXR0ZWVfY29zcG9uc29yc19yLCAKICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBiaWxsc190cmFpbjQsIGZhbWlseSA9IGJpbm9taWFsKQpiaWxsc19maXQ0IDwtIGdsbShzdGF0dXMyIH4gc2Vzc2lvbiArIGRvdyArIGlzX3Nwb25zb3JfaW5fbGVhZGVyc2hpcDIgKyBzcG9uc29yX3BhcnR5ICsgaXNfYmlwYXJ0aXNhbiArCiAgICAgICAgICAgICAgICAgICAgICBudW1fY29zcG9uc29ycyArIHRpdGxlX3dvcmRfY291bnQgKyBudW1fYW1lbmRtZW50cyArCiAgICAgICAgICAgICAgICAgICAgICBudW1fb3JpZ2luYXRpbmdfY29tbWl0dGVlX2Nvc3BvbnNvcnMsIGRhdGEgPSBiaWxsc190cmFpbjQsIGZhbWlseSA9IGJpbm9taWFsKQpiaWxsc19maXQ1IDwtIGdsbShzdGF0dXMyIH4gc2Vzc2lvbiArIGlzX3Nwb25zb3JfaW5fbGVhZGVyc2hpcDIgKyBzcG9uc29yX3BhcnR5ICsKICAgICAgICAgICAgICAgICAgICAgIG51bV9jb3Nwb25zb3JzICsgdGl0bGVfd29yZF9jb3VudCArIG51bV9hbWVuZG1lbnRzICsgaXNfYmlwYXJ0aXNhbiArCiAgICAgICAgICAgICAgICAgICAgICBudW1fb3JpZ2luYXRpbmdfY29tbWl0dGVlX2Nvc3BvbnNvcnMsIGRhdGEgPSBiaWxsc190cmFpbjQsIGZhbWlseSA9IGJpbm9taWFsKQpiaWxsc19maXQ2IDwtIGdsbShzdGF0dXMyIH4gc2Vzc2lvbiArIGlzX3Nwb25zb3JfaW5fbGVhZGVyc2hpcDIgKyBzcG9uc29yX3BhcnR5ICsKICAgICAgICAgICAgICAgICAgICAgIHRpdGxlX3dvcmRfY291bnQgKyBudW1fYW1lbmRtZW50cyArIGlzX2JpcGFydGlzYW4gKwogICAgICAgICAgICAgICAgICAgICAgbnVtX29yaWdpbmF0aW5nX2NvbW1pdHRlZV9jb3Nwb25zb3JzLCBkYXRhID0gYmlsbHNfdHJhaW40LCBmYW1pbHkgPSBiaW5vbWlhbCkKYmlsbHNfZml0NyA8LSBnbG0oc3RhdHVzMiB+IHNlc3Npb24gKyBzcG9uc29yX3BhcnR5ICsgdGl0bGVfd29yZF9jb3VudCArIG51bV9hbWVuZG1lbnRzICsgaXNfYmlwYXJ0aXNhbiArCiAgICAgICAgICAgICAgICAgICAgICBudW1fb3JpZ2luYXRpbmdfY29tbWl0dGVlX2Nvc3BvbnNvcnMsIGRhdGEgPSBiaWxsc190cmFpbjQsIGZhbWlseSA9IGJpbm9taWFsKQpiaWxsc19maXQ4IDwtIGdsbShzdGF0dXMyIH4gc2Vzc2lvbiArIHNwb25zb3JfcGFydHkgKyB0aXRsZV93b3JkX2NvdW50ICsgbnVtX2FtZW5kbWVudHMgKyBpc19iaXBhcnRpc2FuLCAKICAgICAgICAgICAgICAgICAgZGF0YSA9IGJpbGxzX3RyYWluNCwgZmFtaWx5ID0gYmlub21pYWwpCnRpZHkoYmlsbHNfZml0OCkgJT4lIHBhbmRlcihjYXB0aW9uID0gcGFzdGUwKGFzLmNoYXJhY3RlcihzdW1tYXJ5KGJpbGxzX2ZpdDgpJGNhbGwpWzNdLCc6ICcsIGFzLmNoYXJhY3RlcihzdW1tYXJ5KGJpbGxzX2ZpdDgpJGNhbGwpWzJdKSkKZ2xhbmNlKGJpbGxzX2ZpdDgpICU+JSBwYW5kZXIoKQpgYGAKCiMjIyBFeGhhdXN0aXZlIE1ldGhvZAoKV2UgdXNlIHRoZSBwYWNrYWdlIGBnbG11bHRpYCB0byBzZWxlY3QgdGhlIGJlc3QgbW9kZWwgYmFzZWQgb24gQUlDLiBXZSB0cmllZCBgYmVzdGdsbWAgYnV0IGl0IHdvdWxkIG5vdCBwcm9jZXNzIGxvY2FsbHkgZXZlbiB3aXRoIG9ubHkgYSBmZXcgZXhwbGFudG9yeSB2YXJpYWJsZXMuIFdlIHN0YXJ0IHdpdGggYSBtb3JlIGxpbWl0ZWQgc2V0IG9mIHZhcmlhYmxlcywgZXhjbHVkaW5nIHRoZSBicmVha2Rvd25zIGJ5IFJlcHVibGljYW4gYW5kIERlbW9jcmF0IGJlY2F1c2UgdGhleSBhcmUgbGluZWFyIGNvbWJpbmF0aW9ucyBvZiBgbnVtX2Nvc3BvbnNvcnNgIGFuZCBgbnVtX29yaWdpbmF0aW5nX2NvbW1pdHRlZV9jb3Nwb25zb3JzYC4KCmBgYHtyfQpiZXN0X2dsbV9iaWxscyA8LSBnbG11bHRpOjpnbG11bHRpKHN0YXR1czIgfiBzZXNzaW9uICsgZG93ICsgaXNfc3BvbnNvcl9pbl9sZWFkZXJzaGlwMiArIHNwb25zb3JfcGFydHkgKwogICAgICAgICAgICAgICAgICAgICAgbnVtX2Nvc3BvbnNvcnMgKyB0aXRsZV93b3JkX2NvdW50ICsgbnVtX2FtZW5kbWVudHMgKyBpc19iaXBhcnRpc2FuICsKICAgICAgICAgICAgICAgICAgICAgIG51bV9vcmlnaW5hdGluZ19jb21taXR0ZWVfY29zcG9uc29ycywgCiAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gYmlsbHNfdHJhaW40LCBmYW1pbHkgPSBiaW5vbWlhbCwgY3JpdCA9IGFpYywgbGV2ZWwgPSAxLCBwbG90dHkgPSBGQUxTRSwgcmVwb3J0ID0gRkFMU0UpCmBgYAoKYGdsbXVsaXRgIHJldHVybnMgYSBiZXN0IG1vZGVsIHdpdGggdGhlIGVxdWF0aW9ucyBgYHIgZ3N1YignICAnLCAnJywgcGFzdGUwKHN1bW1hcnkoYmVzdF9nbG1fYmlsbHMpJGJlc3Rtb2RlbCwgY29sbGFwc2UgPSAiIikpYGAuCgoKV2UgZmluZCB0aGF0IHRoZSBiZXN0IG1vZGVsIGhhcyBhbiBBSUMgb2YgYHIgc3VtbWFyeShiZXN0X2dsbV9iaWxscykkYmVzdGljYCB3aGljaCBpcyBvbmx5IHNsaWdodGx5IHNtYWxsZXIgdGhhbiB0aGUgQUlDIG9mIHRoZSBmaW5hbCBtb2RlbCB3ZSBmb3VuZCBmcm9tIGJhY2t3YXJkcyBzZWxlY3Rpb24gYHIgc3VtbWFyeShiaWxsc19maXQ4KSRhaWNgLgoKYGBge3J9CnBsb3QoYmVzdF9nbG1fYmlsbHMsIHR5cGUgPSAncycpCmBgYAoKIyMjIFJPQyBDdXJ2ZQoKV2UgcGxvdCB0aGUgUk9DIGN1cnZlcyBmb3IgdGhlIDggbW9kZWxzIHdlIGhhdmUgY3JlYXRlZC4gVGhlIGZpcnN0IDYgUk9DIGN1cnZlcyBpbiA8c3BhbiBzdHlsZT0iY29sb3I6YHIgcGFsNTM4WydtZWRncmF5J11bWzFdXWAiPmdyYXk8L3NwYW4+IGFyZSBmcm9tIHRoZSBiYWNrd2FyZHMgc2VsZWN0aW9uIHByb2Nlc3MuIFRoZSBST0MgY3VydmUgaW4gPHNwYW4gc3R5bGU9ImNvbG9yOmByIHBhbDUzOFsnYmx1ZSddW1sxXV1gIj53aXRoIGhlYXJ0IGRpc2Vhc2UgKGhkID0gMSk8L3NwYW4+IGlzIHRoZSBmaW5hbCBtb2RlbCBmcm9tIGJhY2t3YXJkcyBzZWxlY3Rpb24gYW5kIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6YHIgcGFsNTM4WydyZWQnXVtbMV1dYCI+d2l0aCBoZWFydCBkaXNlYXNlIChoZCA9IDEpPC9zcGFuPiBST0MgY3VydmUgaXMgdGhlIGJlc3QgZ2xtIGZyb20gdGhlIGBnbG11bHRpYCBwYWNrYWdlLiBOb3RlYWJsbHkgdGhlIGN1cnZlcyBhcmUgcmVsYXRpdmVseSBzaW1pbGFyLiBUaGlzIGlzIGV4cGVjdGVkIGJlY2F1c2UgdGhlIHNhbWUgbW9kZWwgdHlwZSAobG9naXN0aWMgcmVncmVzc2lvbikgaXMgYmVpbmcgdXNlZCBhcyBvcHBvc2VkIHRvIGFub3RoZXIgY2xhc3NpZmljYXRpb24gbW9kZWwgKENBUlQsIFNWTSwgZXRjLikKCmBgYHtyfQpiaWxsc19wbG90X3JvY19kZiA8LSAKICAgIGJpbGxzX3RyYWluNCAlPiUKICAgIGJpbmRfY29scygKICAgICAgICBkYXRhX2ZyYW1lKAogICAgICAgICAgICBiYWNrd2FyZHMgPSBiaWxsc19maXQ4JGZpdHRlZAogICAgICAgICAgICAsIGdsbXVsdGkgPSBiaWxsc19maXQ3JGZpdHRlZAogICAgICAgICAgICAsIG90aGVyMSA9IGJpbGxzX2ZpdDYkZml0dGVkCiAgICAgICAgICAgICwgb3RoZXIyID0gYmlsbHNfZml0NSRmaXR0ZWQKICAgICAgICAgICAgLCBvdGhlcjMgPSBiaWxsc19maXQ0JGZpdHRlZAogICAgICAgICAgICAsIG90aGVyNCA9IGJpbGxzX2ZpdDMkZml0dGVkCiAgICAgICAgICAgICwgb3RoZXI1ID0gYmlsbHNfZml0MiRmaXR0ZWQKICAgICAgICAgICAgLCBvdGhlcjYgPSBiaWxsc19maXQxJGZpdHRlZAogICAgICAgICAgICApCiAgICApICU+JQogICAgc2VsZWN0KHN0YXR1cyA9IHN0YXR1czIsIGJhY2t3YXJkcywgZ2xtdWx0aSwgc3RhcnRzX3dpdGgoIm90aGVyIikpICU+JQogICAgZ2F0aGVyKG1vZGVsLCBmaXR0ZWQsIC1zdGF0dXMpICU+JQogICAgbXV0YXRlKG1vZGVsID0gZmFjdG9yKG1vZGVsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJvdGhlcjEiLCJvdGhlcjIiLCJvdGhlcjMiLCJvdGhlcjQiLCJvdGhlcjUiLCAib3RoZXI2IiwiYmFja3dhcmRzIiwiZ2xtdWx0aSIpKSkgJT4lCiAgICBtdXRhdGUoc3RhdHVzID0gYXMuaW50ZWdlcihzdGF0dXMpIC0gMSkKCmdncGxvdChiaWxsc19wbG90X3JvY19kZiwgYWVzKGQgPSBzdGF0dXMsIG0gPSBmaXR0ZWQsIGNvbG91ciA9IG1vZGVsKSkgKyAKICAgIGdlb21fcm9jKCkgKyBzdHlsZV9yb2MoKSArIAogICAgdGhlbWVfanJmKCkgKwogICAgbGFicyh0aXRsZSA9ICJST0MgZm9yIEJhY2t3YXJkcyBTZWxlY3Rpb24gYW5kIGdsbXVsdGkgTW9kZWxzIikgKwogICAgc2NhbGVfY29sb3VyX21hbnVhbCgiTW9kZWwiLCB2YWx1ZXMgPSBjKCdiYWNrd2FyZHMnID0gcGFsNTM4WydibHVlJ11bWzFdXSwgJ2dsbXVsdGknID0gcGFsNTM4WydyZWQnXVtbMV1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdvdGhlcjEnID0gcGFsNTM4WydtZWRncmF5J11bWzFdXSwgJ290aGVyMicgPSBwYWw1MzhbJ21lZGdyYXknXVtbMV1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdvdGhlcjMnID0gcGFsNTM4WydtZWRncmF5J11bWzFdXSwgJ290aGVyNCcgPSBwYWw1MzhbJ21lZGdyYXknXVtbMV1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdvdGhlcjUnID0gcGFsNTM4WydtZWRncmF5J11bWzFdXSwgJ290aGVyNicgPSBwYWw1MzhbJ21lZGdyYXknXVtbMV1dCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkKYGBgCgpMZXQncyByZXZpZXcgdGhlIEFVQyBmb3IgdGhlIDggY3VydmVzLgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KZGF0YV9mcmFtZSgKICAgIG1vZGVsID0gYygib3RoZXIxIiwib3RoZXIyIiwib3RoZXIzIiwib3RoZXI0Iiwib3RoZXI1IiwgIm90aGVyNiIsImJhY2t3YXJkcyIsImdsbXVsdGkiKQogICAgLCBBVUMgPSBjKGF1Yyhyb2MoYmlsbHNfdHJhaW40JHN0YXR1czIsIGJpbGxzX2ZpdDEkZml0dGVkKSlbMV0KICAgICAgICAgICAgLCBhdWMocm9jKGJpbGxzX3RyYWluNCRzdGF0dXMyLCBiaWxsc19maXQyJGZpdHRlZCkpWzFdCiAgICAgICAgICAgICwgYXVjKHJvYyhiaWxsc190cmFpbjQkc3RhdHVzMiwgYmlsbHNfZml0MyRmaXR0ZWQpKVsxXQogICAgICAgICAgICAsIGF1Yyhyb2MoYmlsbHNfdHJhaW40JHN0YXR1czIsIGJpbGxzX2ZpdDQkZml0dGVkKSlbMV0KICAgICAgICAgICAgLCBhdWMocm9jKGJpbGxzX3RyYWluNCRzdGF0dXMyLCBiaWxsc19maXQ1JGZpdHRlZCkpWzFdCiAgICAgICAgICAgICwgYXVjKHJvYyhiaWxsc190cmFpbjQkc3RhdHVzMiwgYmlsbHNfZml0NiRmaXR0ZWQpKVsxXQogICAgICAgICAgICAsIGF1Yyhyb2MoYmlsbHNfdHJhaW40JHN0YXR1czIsIGJpbGxzX2ZpdDgkZml0dGVkKSlbMV0KICAgICAgICAgICAgLCBhdWMocm9jKGJpbGxzX3RyYWluNCRzdGF0dXMyLCBiaWxsc19maXQ3JGZpdHRlZCkpWzFdKQogICAgCikgJT4lIApwYW5kZXIoKQpgYGAKCiMjIyAkSyQtZm9sZCBDcm9zcyBWYWxpZGF0aW9uCgpXZSBwZXJmb3JtICRLJC1mb2xkIGNyb3NzIHZhbGlkYXRpb24gb24gdGhlIHRyYWluaW5nIGRhdGFzZXQgd2l0aCAkSyA9IDEwJCBmb3IgdHdvIG1vZGVscyB3ZSBoYXZlIGZvY3VzZWQgaW4gb246IGJhY2t3YXJkcyBhbmQgZ2xtdWx0aS4KCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CmRhdGFfZnJhbWUoCiAgICBtb2RlbCA9IGMoJ2JhY2t3YXJkcycsJ2dsbXVsdGknKQogICAgLCBgQ1YgZXN0aW1hdGUgb2YgUHJlZGljdGlvbiBScnJvcmAgPSBjKGJvb3Q6OmN2LmdsbShiaWxsc190cmFpbjQsIGJpbGxzX2ZpdDgsIEsgPSAxMCkkZGVsdGFbWzFdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib290Ojpjdi5nbG0oYmlsbHNfdHJhaW40LCBiaWxsc19maXQ3LCBLID0gMTApJGRlbHRhW1sxXV0pCikgJT4lIApwYW5kZXIoKQpgYGAKCldlIHNlZSB0aGVzZSBhcmUgaW5jcmVkaWJseSBzaW1pbGFyIG1vZGVscyBhbmQgd291bGQgYmUgc2F0aXNpZmllZCB3aXRoIGVpdGhlciBtb2RlbC4gVGhlICRLJC1mb2xkcyBjcm9zcyB2YWxpZGF0aW9uIHdhcyBhIG1lYW5zIHRvIGNoZWNrIHRoYXQgd2Ugd2VyZSBub3Qgb3ZlcmZpdHRpbmcuCgojIyMgQmF5ZXMgUnVsZQoKV2UgcHJvcG9zZSBhIGxvc3MgZnVuY3Rpb24gc3VjaCB0aGF0ICAkXGZyYWN7YV97MSwwfX17YV97MCwxfX0gPSA1JCBpbiBvcmRlciB0byBtaW5pbWl6ZSBmYWxzZSBuZWdhdGl2ZXMuIEEgYmlsbCBwYXNzaW5nIGlzIGEgdmVyeSByYXJlIGV2ZW50ICh1bmZvcnR1bmF0ZWx5KSBzbyB3ZSB3YW50IHRvIG1heGltaXplIHRoZSBuZWdhdGl2ZSBwcmVkaWN0aXZlIHZhbHVlIChpbmNpZGVuY2Ugb2YgZmFsc2UgbmVnYXRpdmVzKS4gV2Ugc2VlIG5vIGRpZmZlcmVuY2UgaW4gd2VpZ2h0ZWQgTUNFLgogICAgCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CmRhdGFfZnJhbWUoCiAgICBzdGF0dXMgPSBiaWxsc190cmFpbjQkc3RhdHVzMgogICAgLCBmaXR0ZWRfYmFjayA9IGJpbGxzX2ZpdDgkZml0dGVkLnZhbHVlcwogICAgLCBmaXR0ZWRfZ2xtdWxpdCA9IGJpbGxzX2ZpdDgkZml0dGVkLnZhbHVlcwogICAgLCBwcmVkX2JhY2sgPSBhcy5pbnRlZ2VyKGZpdHRlZF9iYWNrID4gMC4yIC8gKDEgKyAwLjIpKQogICAgLCBwcmVkX2dsbXVsaXQgPSBhcy5pbnRlZ2VyKGZpdHRlZF9nbG11bGl0ID4gMC4yIC8gKDEgKyAwLjIpKQogICAgLCBhMTBfYmFjayA9IDUgKiBpZmVsc2Uoc3RhdHVzID09IDEgJiBwcmVkX2JhY2sgIT0gMSwgMSwgMCkKICAgICwgYTEwX2dsbXVsdGkgPSA1ICogaWZlbHNlKHN0YXR1cyA9PSAxICYgcHJlZF9nbG11bGl0ICE9IDEsIDEsIDApCiAgICAsIGEwMV9iYWNrID0gMSAgKiBpZmVsc2Uoc3RhdHVzID09IDAgJiBwcmVkX2JhY2sgIT0gMCwgMSwgMCkKICAgICwgYTAxX2dsbXVsdGkgPSAxICAqIGlmZWxzZShzdGF0dXMgPT0gMCAmIHByZWRfZ2xtdWxpdCAhPSAwLCAxLCAwKQogICAgLCBhMTBfYTAxX2JhY2sgPSBhMTBfYmFjayArIGEwMV9iYWNrCiAgICAsIGExMF9hMDFfZ2xtdWx0aSA9IGExMF9nbG11bHRpICsgYTAxX2dsbXVsdGkKKSAlPiUKc3VtbWFyaXNlKAogICAgYE1DRSAtIEJhY2t3YXJkc2AgPSBzdW0oYTEwX2EwMV9iYWNrKSAvIG4oKQogICAgLCBgTUNFIC0gZ2xtdWx0aWAgPSBzdW0oYTEwX2EwMV9nbG11bHRpKSAvIG4oKQopICU+JSAKcGFuZGVyKCkKYGBgCgpUaGUgY29uZnVzaW9uIHRhYmxlIGZvciB0aGUgYmFja3dhcmRzIHNlbGVjdGlvbiBtb2RlbCBpcyBiZWxvdy4KCmBgYHtyLCByZXN1bHRzPSdhc2lzJ30KY29uZnVzaW9uX2JpbGxzIDwtIHRhYmxlKGJpbGxzX3RyYWluNCRzdGF0dXMyLCBpZmVsc2UocHJlZGljdChiaWxsc19maXQ4LCB0eXBlID0gJ3Jlc3BvbnNlJykgPiAuNSwgMSwgMCkpCgpwYW5kZXIoZGVzY3I6OkNyb3NzVGFibGUoYmlsbHNfdHJhaW40JHN0YXR1czIsIGlmZWxzZShwcmVkaWN0KGJpbGxzX2ZpdDgsIHR5cGUgPSAncmVzcG9uc2UnKSA+IC41LCAxLCAwKSwgcHJvcC5jaGlzcT1GQUxTRSwgcHJvcC5yID0gRiwgcHJvcC5jID0gRiwgcHJvcC50ID0gVFJVRSwgY2hpc3EgPSBUUlVFLCBkbm4gPSBjKCJTdGF0dXMiLCJQcmVkaWN0aW9uIikpLCBzcGxpdC50YWJsZSA9IEluZiwgYmlnLm1hcmsgPSAnLCcpCmBgYAoKV2Ugc2VlIHRoYXQgd2hpbGUgdGhpcyBtb2RlbCBnb2VzIGEgZ29vZCBqb2IgYXQgcHJlZGljdGluZyB3aGVuIGEgYmlsbCB3aWxsIG5vdCBwYXNzIChzcGVjaWZpY2l0eSAkPSBcZnJhY3tUTn17VE4gKyBGUH0gPSBgciBjb25mdXNpb25fYmlsbHNbMSwxXSAvIChjb25mdXNpb25fYmlsbHNbMSwxXSArIGNvbmZ1c2lvbl9iaWxsc1sxLDJdKWAkKSwgaXQgZG9lc24ndCBoYXZlIHRoZSBwb3NpdGl2ZSBwcmVkaWN0aXZlIHZhbHVlIHdlJ2QgaG9wZSAoJFxmcmFje1RQfXtUUCArIEZQfSA9IGByIGNvbmZ1c2lvbl9iaWxsc1syLDJdIC8gKGNvbmZ1c2lvbl9iaWxsc1syLDJdICsgY29uZnVzaW9uX2JpbGxzWzIsMV0pYCQpLgogICAgCiMjIyBGaW5hbCBNb2RlbAoKQXMgb3VyIGZpbmFsIG1vZGVsIHdlIHNlbGVjdCB0aGUgbW9kZWwgaWRlbnRpZmllZCBieSB0aGUgYmFja3dhcmRzIHNlbGVjdGlvbiBwcm9jZWR1cmUuIFdlIGNob3NlIHRoaXMgbW9kZWwgb3ZlciB0aGUgb25lIGlkZW50aWZpZWQgYnkgdGhlIGBnbG11bHRpYCBwYWNrYWdlIGJlY2F1c2UgaXQgY29udGFpbnMgZmV3ZXIgZXhwbGFuYXRvcnkgdmFyaWFibGVzIGJ1dCBoYXMgbmVhcmx5IHRoZSBzYW1lIEFJQyB2YWx1ZSAoYHIgc3VtbWFyeShiaWxsc19maXQ4KSRhaWNgIHZzIGByIHN1bW1hcnkoYmlsbHNfZml0NykkYWljYCkuCgpXZSBub3cgdXNlIHRoZSB0ZXN0IGRhdGFzZXQgdG8gZmluZCB0aGUgTUNFLgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KdGlkeV9iaWxsc19maXQ4IDwtIAogICAgdGlkeShiaWxsc19maXQ4KSAlPiUKICAgIG11dGF0ZSh0ZXJtID0gZ3N1YigiXyIsIlxcXFwsIiwgdGVybSkpCgpiaWxsc190ZXN0X3ByZWQgPC0gaWZlbHNlKHByZWRpY3QoYmlsbHNfZml0OCwgYmlsbHNfdGVzdDQsIHR5cGUgPSAicmVzcG9uc2UiKSA+IDAuNSwgMSwgMCkKCmNvbmZ1c2lvbl90ZXN0X2JpbGxzIDwtIHRhYmxlKGJpbGxzX3Rlc3Q0JHN0YXR1czIsIGJpbGxzX3Rlc3RfcHJlZCkKCnBhbmRlcihkZXNjcjo6Q3Jvc3NUYWJsZShiaWxsc190ZXN0NCRzdGF0dXMyLCBiaWxsc190ZXN0X3ByZWQsIHByb3AuY2hpc3E9RkFMU0UsIHByb3AuciA9IEYsIHByb3AuYyA9IEYsIHByb3AudCA9IFRSVUUsIGNoaXNxID0gVFJVRSwgZG5uID0gYygiU3RhdHVzIiwiUHJlZGljdGlvbiIpKSwgc3BsaXQudGFibGUgPSBJbmYsIGJpZy5tYXJrID0gJywnKQpgYGAKCldlIHNlZSB0aGF0IHRoZSB1bndlaWdodGVkIE1DRSBpcyBgciAoY29uZnVzaW9uX3Rlc3RfYmlsbHNbMSwyXSArIGNvbmZ1c2lvbl90ZXN0X2JpbGxzWzIsMV0pIC8gc3VtKGNvbmZ1c2lvbl90ZXN0X2JpbGxzKWAuIEhvd2V2ZXIsIHdoYXQgd2Ugc2VlIGlzIHRoYXQgYWdhaW4gdGhlIG1vZGVsIGlzIGdvb2QgYXQgcHJlZGljdGluZyB3aGVuIGEgYmlsbCB3aWxsIG5vdCBwYXNzIGJ1dCBoYXMgZGlmZmljdWx0eSBwcm9wZXJseSBwcmVkaWN0aW5nIGEgdHJ1ZSBwb3NpdGl2ZSAocG9zaXRpdmUgcHJlZGljdGlvbiB2YWx1ZSAkPSBcZnJhY3tUUH17VFAgKyBGUH0gPSBgciBjb25mdXNpb25fdGVzdF9iaWxsc1syLDJdIC8gKGNvbmZ1c2lvbl90ZXN0X2JpbGxzWzIsMl0gKyBjb25mdXNpb25fdGVzdF9iaWxsc1syLDFdKWAkKQoKVGhlIGZpbmFsIG1vZGVsIGlzIAoKJCRcaGF0e3l9X2kgPSBgciB0aWR5X2JpbGxzX2ZpdDhbMSwgMl1gICsgCmByIHRpZHlfYmlsbHNfZml0OFsyLCAyXWBgciB0aWR5X2JpbGxzX2ZpdDhbMiwgMV1gICsKYHIgdGlkeV9iaWxsc19maXQ4WzMsIDJdYGByIHRpZHlfYmlsbHNfZml0OFszLCAxXWAgKwpgciB0aWR5X2JpbGxzX2ZpdDhbNCwgMl1gYHIgdGlkeV9iaWxsc19maXQ4WzQsIDFdYCArIFxcCmByIHRpZHlfYmlsbHNfZml0OFs1LCAyXWBgciB0aWR5X2JpbGxzX2ZpdDhbNSwgMV1gICsKYHIgdGlkeV9iaWxsc19maXQ4WzYsIDJdYGByIHRpZHlfYmlsbHNfZml0OFs2LCAxXWAgKwpgciB0aWR5X2JpbGxzX2ZpdDhbNywgMl1gYHIgdGlkeV9iaWxsc19maXQ4WzcsIDFdYCQkCgpgYGB7cn0Kc3VtbWFyeV92YXJzX2JpbGxzIDwtIAogICAgZGF0YV9mcmFtZSgKICAgICAgICB2YXJpYWJsZSA9IHJvd25hbWVzKGNvZWYoc3VtbWFyeShiaWxsc19maXQ4KSlbLTEsIF0pCiAgICAgICAgLCBwX3ZhbHVlID0gY29lZihzdW1tYXJ5KGJpbGxzX2ZpdDgpKVstMSwgNF0KICAgICAgICApICU+JSAKICAgICAgICBtdXRhdGUoCiAgICAgICAgICAgIGVzdGltYXRlID0gY29lZihzdW1tYXJ5KGJpbGxzX2ZpdDgpKVstMSwgMV0KICAgICAgICAgICAgLCBvZGRzID0gZXhwKGVzdGltYXRlKQogICAgICAgICAgICAsIHByb2IgPSBvZGRzIC8gKDEgKyBvZGRzKQogICAgICAgICAgICAsIHBlcmNlbnRhZ2UgPSBwYXN0ZTAocm91bmQocHJvYiAqIDEwMCwgMiksICIlIikKICAgICAgICApICU+JQogICAgICAgIGFycmFuZ2UocF92YWx1ZSkKCnN1bW1hcnlfdmFyc19iaWxscyAlPiUgc2VsZWN0KC1wZXJjZW50YWdlKQpgYGAKCkhvbGRpbmcgYWxsIG90aGVyIHZhcmlhYmxlcyBjb25zdGFudCwKCisgQW4gaW5jcmVhc2UgYnkgKipvbmUgYW1lbmRtZW50IHRvIHRoZSBiaWxsKiosIHdlIGV4cGVjdCB0byBzZWUgYWJvdXQgYHIgc3VtbWFyeV92YXJzX2JpbGxzICU+JSBmaWx0ZXIodmFyaWFibGUgPT0gIm51bV9hbWVuZG1lbnRzIikgJT4lIHNlbGVjdChwZXJjZW50YWdlKSAlPiUgdW5saXN0KClgIGluY3JlYXNlIGluIHRoZSBwcm9iYWJpbGl0eSBvZiBhIGJpbGwgcGFzc2luZy4KKyBUaGUgZGlmZmVyZW5jZSBvZiBhIGJpbGwgYmVpbmcgKipzcG9uc29yZWQgYnkgYSBSZXB1YmxpY2FuIHZzIGEgRGVtb2NyYXQqKiwgd2UgZXhwZWN0IHRvIHNlZSBhYm91dCBgciBzdW1tYXJ5X3ZhcnNfYmlsbHMgJT4lIGZpbHRlcih2YXJpYWJsZSA9PSAic3BvbnNvcl9wYXJ0eVJlcHVibGljYW4iKSAlPiUgc2VsZWN0KHBlcmNlbnRhZ2UpICU+JSB1bmxpc3QoKWAgaW5jcmVhc2UgaW4gdGhlIHByb2JhYmlsaXR5IG9mIGEgYmlsbCBwYXNzaW5nLgorIFRoZSBkaWZmZXJlbmNlIG9mIGEgYmlsbCBiZWluZyAqKmJpcGFydGlzYW4gdnMgbm9uLWJpcGFydGlzYW4qKiwgd2UgZXhwZWN0IHRvIHNlZSBhYm91dCBgciBzdW1tYXJ5X3ZhcnNfYmlsbHMgJT4lIGZpbHRlcih2YXJpYWJsZSA9PSAiaXNfYmlwYXJ0aXNhbjEiKSAlPiUgc2VsZWN0KHBlcmNlbnRhZ2UpICU+JSB1bmxpc3QoKWAgaW5jcmVhc2UgaW4gdGhlIHByb2JhYmlsaXR5IG9mIGEgYmlsbCBwYXNzaW5nLgorIEEgb25lIHdvcmQgaW5jcmVhc2UgaW4gKip0aGUgbnVtYmVyIG9mIHdvcmRzIGluIHRoZSBiaWxsJ3MgdGl0bGUqKiwgd2UgZXhwZWN0IHRvIHNlZSBhYm91dCBgciBzdW1tYXJ5X3ZhcnNfYmlsbHMgJT4lIGZpbHRlcih2YXJpYWJsZSA9PSAidGl0bGVfd29yZF9jb3VudCIpICU+JSBzZWxlY3QocGVyY2VudGFnZSkgJT4lIHVubGlzdCgpYCBpbmNyZWFzZSBpbiB0aGUgcHJvYmFiaWxpdHkgb2YgYSBiaWxsIHBhc3NpbmcuCisgVGhlIGRpZmZlcmVuY2Ugb2YgYmVpbmcgaW4gKipzZXNzaW9uIDIwMTEtMjAxMiB2cyAyMDA5LTIwMTAqKiwgd2UgZXhwZWN0IHRvIHNlZSBhYm91dCBgciBzdW1tYXJ5X3ZhcnNfYmlsbHMgJT4lIGZpbHRlcih2YXJpYWJsZSA9PSAic2Vzc2lvbjIwMTEtMjAxMiIpICU+JSBzZWxlY3QocGVyY2VudGFnZSkgJT4lIHVubGlzdCgpYCBpbmNyZWFzZSBpbiB0aGUgcHJvYmFiaWxpdHkgb2YgYSBiaWxsIHBhc3NpbmcuCisgVGhlIGRpZmZlcmVuY2Ugb2YgYmVpbmcgaW4gKipzZXNzaW9uIDIwMTMtMjAxNCB2cyAyMDA5LTIwMTAqKiwgd2UgZXhwZWN0IHRvIHNlZSBhYm91dCBgciBzdW1tYXJ5X3ZhcnNfYmlsbHMgJT4lIGZpbHRlcih2YXJpYWJsZSA9PSAic2Vzc2lvbjIwMTMtMjAxNCIpICU+JSBzZWxlY3QocGVyY2VudGFnZSkgJT4lIHVubGlzdCgpYCBpbmNyZWFzZSBpbiB0aGUgcHJvYmFiaWxpdHkgb2YgYSBiaWxsIHBhc3NpbmcuCgojIyBTdWdnZXN0aW9ucwoKSW4gY29uc2lkZXJpbmcgaG93IHRvIGJldHRlciBpbXByb3ZlIHRoaXMgbW9kZWwsIHdlIHdvdWxkIHdhbnQgdG8gY29uc2lkZXIgZ2F0aGVyaW5nCgorIEhvdyBsb25nIHdhcy9oYXMgdGhlIGJpbGwgYmVlbiBkZWJhdGVkIG9uIHRoZSBmbG9vciBvZiB0aGUgaG91c2UgKGluIGRheXMpCisgVGhlIG51bWJlciBvZiBvdXRzaWRlIGxvYmJ5aW5nIGdyb3VwcyBpbnZvbHZlZCBpbiB0aGUgYXV0aG9yc2hpcCBvZiB0aGUgYmlsbAorIFRoZSBudW1iZXIgb2Ygd29yZHMgaW4gdGhlIGJpbGwKKyBTb21lIHR5cGUgb2YgY2xhc3NpZmljYXRpb24gb2YgdGhlIGJpbGwgKGkuZS4gdGF4IHJlZm9ybSwgZWR1Y2F0aW9uIHNwZW5kaW5nLCBoZWFsdGhjYXJlLCBwdWJsaWMgd29ya3MsIGV0YykKCldlIHRoaW5rIHRoYXQgdGhlc2UgZmVhdHVyZXMgY291bGQgcG90ZW50aWFsbHkgaGVscCBpbXByb3ZlIHRoZSBjbGFzc2lmaWVyLiBIb3dldmVyLCB3ZSBhbHNvIHJlY29nbml6ZSB0aGF0IGEgZGlmZmVyZW50IGZhbWlseSBvZiBjbGFzc2lmaWVyIG1pZ2h0IGJlIGhlbHBmdWwgZm9yIHRoaXMgdHlwZSBvZiBwcm9ibGVtLCBwYXJ0aWN1bGFybHkgQ0FSVC4=