Version 1.0 | First Created Oct 22, 2023 | Updated Nov 1, 2023

Keywords: Logistic Regression, Confusion Matrix, ROC Curve, Sensitivity, Specificity, Cost-Benefit Analysis, McFadden R-Squared

Introduction

People-based machine learning, a field harnessing the power of data-driven insights into human behaviors and preferences, has become pivotal for businesses and organizations. With algorithms trained on human-centric data, businesses get to personalize products, services, and experiences based on individual customer preferences. At any point, the bottom line of any businesses and organizations may be affected, if a client no longer wish to participate, donate, or purchase a good, which makes it extra important to have a predictive model that captures these potential changes. Like any machine learning applications, however, it is difficult to capture the nuances in human behavior with a single model, nor is it possible to have a unbiased sample dataset that truly reflect human behavior. This means that continuous model adaptation and recognition of these complexities are essential for effective decision-making based on these models.

The goal of this report is to fit a logistic regression model to predict whether homeowners in Emil City will enter and take a home repair tax credit. To contextualize, the city is considering a more proactive approach for targeting homeowners who would quality for this tax credit program and the Department of Housing and Community Development (HCD) have been trying to proactively reach out to eligible homeowners ever year. It is predicted that houses that transacted after taking the credit, will sell with a $10,000 premium, on average. Homes surrounding the repaired home see an aggregate premium of $56,000, on average. The thorny task for the HCD is to target homeowners with the highest likelihood of accepting the credit. In this case, they could maximize the benefits of the aggregate premium of houses while reduce the marketing resources cost. As of now, typically not all eligible homeowners they reach out and enter the program ultimately takes the credit.

That said, we will need to train a better model to ensure that the HCD will not be reaching out to eligible homeowners at random. Logistic regression is used here due to its interpretability and suitability for binary classification problems (in this case, our dependent variable indicate whether the a homeowner will take the credit or not). We will first start with data exploration to determine variables most explains homeowner’s behavior. Then, we split the data into training and testing set, fit a regression model, and constructed confusion matrix at 50% threshold to see the extent to which our model can correctly predict homeowner entering the credit program (true positive), or correctly predict homeowner not taking the credit (true negative). This is followed by a cross validation test to compute ROC, sensitivity, and specificity. We conclude our analysis with a cost/benefit analysis and determine the optimal threshold of delineating positive and negative prediction that would maximize the revenue.

The GitHub repository for this report is here.

Variables Examination

The first part of this report focuses on data exploration to see how each factor in our dataset relates to homeowner’s decision to accept home repair tax credit. Variables are split into categorical variables and numeric variables respectively, for which we conducted chi-square and anova tests to examine if there’s a significant association between dependent variable and independent variables.

Categorical Variables

The figure below shows the difference in the number of homeowners who accepted and did not accept tax credit based on a different categorical features. We observed the following. There seems to be a significant difference in the number of people accepting credit who were contacted via cellular, who had a university degree, work as admin or technician, were married, were previously contacted in May, were contacted recently, were Philadelphia residents, and had previously accepted the credit. On the other hand, there doesn’t seem to be a significant difference in the number of people accepting credit depending on the day of the week they were contacted, their mortgage status, and presence of tax lien.

cat_var <- housing %>% 
  select(-c(X, age, y_numeric, previous, unemploy_rate, cons.price.idx, cons.conf.idx, inflation_rate, spent_on_repairs, campaign))
cat_var %>%
    gather(Variable, value, -y) %>%
    count(Variable, value, y) %>%
      ggplot(., aes(value, n, fill = y)) +   
        geom_bar(position = "dodge", stat="identity") +
        facet_wrap(~Variable, scales="free", ncol = 4, 
                   labeller= labeller(Variable = c(
                     `contact` = "Ways of Contact",
                     `day_of_week` = "Day of the Week",
                     `education` = "Educational Attainment",
                     `pdays` = "Days After Contact",
                     `job` = "Occupation Indicator",
                     `marital` = "Marital Status",
                     `month` = "Month of Last Contact",
                     `mortgage` = "Mortgage",
                     `poutcome` = "Previous Campaign Outcome",
                     `taxbill_in_phl` = "Philadelphia Residents",
                     `taxLien` = "Lien"))) +
        scale_fill_manual(values = palette2, name = "Credit") +
        labs(x="Credit", y="Count",
             title = "Feature Associations with the Likelihood of Credit Acceptance",
             subtitle = "Categorical features") +
        theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  theme(plot.subtitle = element_text(size = 12,face = "italic"),
        plot.title = element_text(size = 18, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=9), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))

Using pure visual evidence is not enough to explain the significance of our categorical variables in predicting whether homeowners will take the credit. To support the above visualization, we conducted chi-square test iteratively for all categorical variables. The chi-square test determines if there is a significant association between categorical variables. It compares the observed frequencies of different categories in a contingency table with the frequencies that would be expected if the variables were independent. The table below shows that Ways of Contact, Edcucation, Marital, Occupation, Month of Last Contact, Previoust Campaign Outcome, Lien Presence and Days After Last Contact are significant predictors in determining homeowner’s likelihood of accepting the credit.

chi_var <- c("contact", "day_of_week", "education", "job", "marital", "month", "mortgage", "poutcome", "taxbill_in_phl", "taxLien", "pdays")

Label = c("contact" = "Ways of Contact",
          "day_of_week" = "Day of the Week",
          "education" = "Educational Attainment",
          "job" = "Occupation Indicator",
          "marital" = "Marital Status",
          "month" = "Month of Last Contact",
          "mortgage" = "Mortgage",
          "poutcome" = "Previous Campaign Outcome",
          "taxbill_in_phl" = "Philadelphia Residents",
          "taxLien" = "Lien", 
          "pdays" = "Days After Contact")

chi_square_results <- data.frame(
  Df = integer(),
  X_Squared = numeric(),
  P_Value = numeric(),
  stringsAsFactors = FALSE
)

for (var in chi_var) {
  contingency_table <- table(cat_var$y, cat_var[[var]])
  chi_square <- chisq.test(contingency_table)
  chi_square_results <- rbind(chi_square_results, data.frame(
    Df = chi_square$parameter,
    X_Squared = chi_square$statistic,
    P_Value = chi_square$p.value
  ))
}

rownames(chi_square_results) <- Label


chi_square_results %>% 
 kable() %>% 
 kable_styling( bootstrap_options = c("striped", "hover", "condensed")) %>% 
    footnote(general_title = "\n", general = "Table 1")
Df X_Squared P_Value
Ways of Contact 1 76.8462374 0.0000000
Day of the Week 4 0.5121761 0.9723049
Educational Attainment 7 22.2918199 0.0022621
Occupation Indicator 11 69.9786737 0.0000000
Marital Status 3 10.2859188 0.0162857
Month of Last Contact 9 299.7881167 0.0000000
Mortgage 2 0.6273842 0.7307440
Previous Campaign Outcome 2 454.4865243 0.0000000
Philadelphia Residents 1 0.7595710 0.3834628
Lien 2 24.1856637 0.0000056
Days After Contact 1 448.2212094 0.0000000

Table 1

Numeric Variables

The figure below shows the difference in the number of homeowners who accepted and did not accept tax credit based on a different numeric features. There seems to be a significant difference in the number of people accepting credit based on their age, number of contacts for this campaign and before this campaign, as well as inflation and unemployment rate of that time.

numeric_var <- housing %>% 
  select(age, y, previous, unemploy_rate, cons.price.idx, cons.conf.idx, inflation_rate, spent_on_repairs, campaign)


numeric_var %>%
  gather(Variable, value, -y) %>%
    ggplot(aes(y, value, fill=y)) + 
      geom_bar(position = "dodge", stat = "summary", fun = "mean") + 
      facet_wrap(~Variable, scales = "free", ncol = 4, labeller= labeller(Variable = c(
                     `age` = "Age",
                     `previous` = "Contact before Campaign",
                     `unemploy_rate` = "Unemployment Rate",
                     `cons.price.idx` = "Consumer Price Index",
                     `cons.conf.idx` = "Consumer Confidence Index",
                     `campaign` = "Contacts for Campaign",
                     `inflation_rate` = "Inflation Rate",
                     `spent_on_repairs` = "Amount Spent on Repairs"))) +
      scale_fill_manual(values = palette2) +
      labs(x="Credit", y="Value", 
           title = "Feature Associations with the Likelihood of Credit Acceptance",
           subtitle = "Continous features") +
      theme(legend.position = "none") +
    theme(plot.subtitle = element_text(size = 12,face = "italic"),
        plot.title = element_text(size = 18, face = "bold"), 
        axis.text.x=element_text(size=10),
        axis.text.y=element_text(size=10), 
        axis.title=element_text(size=9), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))

Because these numeric features all have different scales and are difficult to compare across, we made another density plot, which conveys the same information, but shows distribution of a continuous variable. Surprisingly, we see that the distribution of Consumer Confidence Index, Consumer Price Index, and Amount Spent on Repairs all significantly differ between homeowners who accepted and did not accept the tax credit.

numeric_var %>%
    gather(Variable, value, -y) %>%
    ggplot() + 
    geom_density(aes(value, color=y), fill = "transparent") + 
   facet_wrap(~Variable, scales = "free", ncol = 4, labeller= labeller(Variable = c(
                     `age` = "Age",
                     `previous` = "Contact before Campaign",
                     `unemploy_rate` = "Unemployment Rate",
                     `cons.price.idx` = "Consumer Price Index",
                     `cons.conf.idx` = "Consumer Confidence Index",
                     `inflation_rate` = "Inflation Rate",
                     `campaign` = "Contacts for Campaign",
                     `spent_on_repairs` = "Amount Spent on Repairs")))+
  scale_color_manual(values = palette1) +
    labs(title = "Feature Distributions Based on Credit Acceptance",
         subtitle = "Continous features") +
  theme(plot.subtitle = element_text(size = 12,face = "italic"),
        plot.title = element_text(size = 18, face = "bold"), 
        axis.text.x=element_text(size=9),
        axis.text.y=element_text(size=9), 
        axis.title=element_text(size=9), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))

We further conducted ANOVA test for all numeric features to see if there’s a significant difference in mean age, for example, across people who take and did not take the credit. The result is in concur with our expectations: all numeric variables are significant predictors.

anova_var <- c("age", "previous", "unemploy_rate", "cons.price.idx", "cons.conf.idx", "inflation_rate", "spent_on_repairs", "campaign")

new_names <- c("age" = "Age",
                "previous" = "Contact before Campaign",
                "unemploy_rate" = "Unemployment Rate",
                "cons.price.idx" = "Consumer Price Index",
                "cons.conf.idx" = "Consumer Confidence Index",
                "inflation_rate" = "Inflation Rate",
                "spent_on_repairs" = "Amount Spent on Repairs",
               "campaign" = "Contacts for Campaign")

anova_results <- data.frame(
                            Df = integer(), 
                            Sum_Sq = numeric(), 
                            Mean_Sq = numeric(), 
                            F_value = numeric(), 
                            P_Value = numeric(), stringsAsFactors = FALSE)

for (var in anova_var) {
  anova_result <- aov(numeric_var[[var]] ~ numeric_var$y)
  summary_data <- summary(anova_result)[[1]][, c("Df", "Sum Sq", "Mean Sq", "F value", "Pr(>F)")][1:1, ]
  anova_results <- rbind(anova_results, summary_data)
}


rownames(anova_results) <- new_names


anova_results %>% 
 kable() %>% 
 kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>% 
    footnote(general_title = "\n", general = "Table 2")
Df Sum Sq Mean Sq F value Pr(>F)
Age 1 1596.56982 1596.56982 15.06149 0.0001057
Contact before Campaign 1 79.03066 79.03066 288.00244 0.0000000
Unemployment Rate 1 807.05379 807.05379 359.02715 0.0000000
Consumer Price Index 1 13.36299 13.36299 40.19177 0.0000000
Consumer Confidence Index 1 257.19577 257.19577 12.21670 0.0004786
Inflation Rate 1 1103.20812 1103.20812 402.90944 0.0000000
Amount Spent on Repairs 1 2725800.03197 2725800.03197 571.90285 0.0000000
Contacts for Campaign 1 157.25175 157.25175 23.97551 0.0000010

Table 2

Logistic Regression

We then fit a logistic regression containing all variables in our dataset. To do so, we split our data randomly into 65% of training dataset and 35% of testing dataset. Some of our variables, such as Lien and Education, have single occurrence and therefore needs to be put into the training set to avoid new levels appearing in the testing set. This model shows that some indicators are significantly correlated with credit acceptance with p < 0.05. This include age, some categories of job, ways of contact, month last contacted, previous campaign outcome, unemployment rate, consumer price and confidence indices. However, the majority of predictors are not significant.

set.seed(479)
trainIndex <- createDataPartition(y = paste(housing$taxLien, housing$education) , p = .65,
                                  list = FALSE,
                                  times = 1)
housingTrain <- housing[ trainIndex,]
housingTest  <- housing[-trainIndex,]

housingModel <- glm(y_numeric ~ .,
                  data=housingTrain %>% 
                    dplyr::select(age, job, marital, education, taxLien, mortgage, taxbill_in_phl, contact, month, day_of_week, campaign, pdays, poutcome, unemploy_rate, cons.conf.idx, cons.price.idx, inflation_rate, spent_on_repairs, y_numeric), family="binomial" (link="logit"))

housing_sum <- summary(housingModel)
coefficients_table <- as.data.frame(housing_sum$coefficients)

coefficients_table$significance <- ifelse(coefficients_table$`Pr(>|z|)` < 0.001, '***',
                                         ifelse(coefficients_table$`Pr(>|z|)` < 0.01, '**',
                                                ifelse(coefficients_table$`Pr(>|z|)` < 0.05, '*',
                                                       ifelse(coefficients_table$`Pr(>|z|)` < 0.1, '.', ''))))

coefficients_table$p_value <- paste0(round(coefficients_table$`Pr(>|z|)`, digits = 3), coefficients_table$significance)

coefficients_table %>%
  select(-significance, -`Pr(>|z|)`) %>% 
  kable(align = "r") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))  %>%
  footnote(general_title = "\n", general = "Table 3")
Estimate Std. Error z value p_value
(Intercept) -335.1496539 132.8764110 -2.5222660 0.012*
age 0.0204678 0.0086973 2.3533692 0.019*
jobblue-collar -0.5325684 0.2907044 -1.8319927 0.067.
jobentrepreneur -0.7143944 0.5111034 -1.3977491 0.162
jobhousemaid -0.3226287 0.5351631 -0.6028605 0.547
jobmanagement -0.6072725 0.3018896 -2.0115715 0.044*
jobretired -0.5592430 0.3903821 -1.4325527 0.152
jobself-employed -0.8229489 0.4298447 -1.9145261 0.056.
jobservices -0.3355901 0.3051141 -1.0998838 0.271
jobstudent 0.1281199 0.4256531 0.3009960 0.763
jobtechnician 0.0225732 0.2395056 0.0942491 0.925
jobunemployed 0.0109616 0.4385126 0.0249973 0.98
jobunknown -0.3124032 0.7707176 -0.4053407 0.685
maritalmarried 0.1371049 0.2520088 0.5440481 0.586
maritalsingle 0.2632753 0.2887379 0.9118142 0.362
maritalunknown -13.8144973 556.2455938 -0.0248352 0.98
educationbasic.6y 0.1448773 0.4419572 0.3278083 0.743
educationbasic.9y 0.1232291 0.3452445 0.3569327 0.721
educationhigh.school -0.0210035 0.3362686 -0.0624604 0.95
educationilliterate -14.0457400 1455.3976440 -0.0096508 0.992
educationprofessional.course -0.0864436 0.3719707 -0.2323935 0.816
educationuniversity.degree 0.2385109 0.3323036 0.7177499 0.473
educationunknown 0.2113254 0.4195492 0.5036964 0.614
taxLienunknown -0.0509971 0.2272948 -0.2243655 0.822
taxLienyes -12.2808134 1455.3976455 -0.0084381 0.993
mortgageunknown 0.0597981 0.5034481 0.1187771 0.905
mortgageyes -0.2120102 0.1481208 -1.4313331 0.152
taxbill_in_phlyes 0.1796557 0.2094041 0.8579381 0.391
contacttelephone -0.9822124 0.3039599 -3.2313878 0.001**
monthaug 0.4298805 0.4366130 0.9845802 0.325
monthdec 1.8238219 0.7846121 2.3244886 0.02*
monthjul 0.1025432 0.3828448 0.2678453 0.789
monthjun -0.1581236 0.4690165 -0.3371387 0.736
monthmar 1.5457954 0.5865365 2.6354634 0.008**
monthmay -0.2394845 0.3070199 -0.7800292 0.435
monthnov -0.0057614 0.4441257 -0.0129726 0.99
monthoct 0.4016013 0.5628718 0.7134863 0.476
monthsep 0.6429439 0.6474800 0.9929943 0.321
day_of_weekmon -0.1183577 0.2296430 -0.5153985 0.606
day_of_weekthu 0.0174090 0.2259178 0.0770591 0.939
day_of_weektue 0.0213120 0.2298068 0.0927387 0.926
day_of_weekwed -0.0203213 0.2397434 -0.0847626 0.932
campaign -0.0598325 0.0404233 -1.4801468 0.139
pdaysYes 0.6436547 0.6282873 1.0244592 0.306
poutcomenonexistent 0.5081719 0.2280928 2.2279169 0.026*
poutcomesuccess 1.1689464 0.6464320 1.8083054 0.071.
unemploy_rate -1.2113721 0.4927379 -2.4584514 0.014*
cons.conf.idx 0.0633293 0.0296774 2.1339226 0.033*
cons.price.idx 2.5636069 0.8719343 2.9401378 0.003**
inflation_rate -0.6576623 0.4702556 -1.3985209 0.162
spent_on_repairs 0.0187400 0.0108730 1.7235321 0.085.

Table 3

The model is being tested on the test set and a mosaic plot is created to visualize the confusion matrix. Unfortunately, the model has low sensitivity rate of only 0.23. Sensitivity is the true-positive rate, which is the proportion of actual positive cases that were correctly identified by the model. Since our model’s accuracy rate is high (0.89), this means that our model missing a significant number of positive cases. These are cases where we correctly predict that homeowners will accept the credit and they actually did.

testProbs <- data.frame(Outcome = as.factor(housingTest$y_numeric),
                        Probs = predict(housingModel, housingTest, type= "response"))

testProbs <- 
  testProbs %>%
  mutate(predOutcome  = as.factor(ifelse(testProbs$Probs > 0.5 , 1, 0)))  

 
mosaicplot(confusionMatrix(testProbs$predOutcome, testProbs$Outcome, 
                       positive = "1")$table, color=c("#DBC2CF","#9FA2B2"), main = "Mosaic Plot for Original Confusion Matrix",
           xlab = "Prediction", ylab = "Reference")

housingmatrix <- as.data.frame(as.table(confusionMatrix(testProbs$predOutcome, testProbs$Outcome, 
                       positive = "1")$table))
housingmatrix %>% 
  kable() %>% 
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))  %>%
  footnote(general_title = "\n", general = "Table 4")
Prediction Reference Freq
0 0 1251
1 0 22
0 1 122
1 1 37

Table 4

Improving Model

However, just because a variable is significant at predicting credit acceptance does not mean that it is a relevant or meaningful predictor. To improve this model, not only do we need to perform feature engineering to increase sensitivity while maintain overall accuracy, but also do we need to exclude irrelevant features. To fit a new model:

  • Firstly, we collapsed the categories in education and employment. For education, considering that having a university degree serves a benchmark in determining credit acceptance, we recode it into two categories: highschool.below and highschool.above. For employment, we recode it into four major categories: unemployed, white-collar, blue-collar, and self-employed, considering that employment status and employment sector will both affect credit acceptance.

  • Secondly, we create interaction features between education and employment considering that different job may require different levels or type of education.

  • Thirdly, we computed a new variable called campaign efficiency, which is the ratio of previous campaign successes to the number of contacts made, This will indicate the efficiency of previous campaigns in terms of successful credit acceptance. We also computed a composite feature representing economic conditions based on unemploy_rate, cons.price.idx, cons.conf.idx, and inflation_rate.

  • Fourthly, carefully reviewing the dataset leads us to realize that it is imbalance. Usually, imbalanced datasets can lead the model to be biased towards the majority class, causing it to perform poorly on the minority class. In our case, a significant number of attributes we have are cases when homeowner declined tax credit. This makes feature weighting extra important. By assigning appropriate weights to features, we can ensure that the model gives sufficient importance to the minority class, helping it learn the underlying patterns in both classes more effectively.

  • Finally, we excluded variable Month we last contacted individual, Day of the week we last contacted individual, Lien and marital, not merely because these variables are not significant predictors, but because the former two features won’t be relevant to credit acceptance, variable lien contains too many unknown values, and there might be colinearity between martial, age, job, and education.

housing <- housing %>% 
  mutate(edu = case_when(
    education == "high.school" | education == "professional.course" | education == "university.degree" ~ "highschool.above", 
    TRUE ~ "highschool.below"),
    employment = case_when(
      job == "self-employed" ~ "self-employed", 
      job == "retired" | job == "unemployed" | job == "student" | job == "unknown" | job == "housemaid" ~ "not-employed",
      job == "blue-collar" ~ "blue-collar", 
      TRUE ~ "white-collar"))
  
housing$job_education_interaction <- paste(housing$edu, housing$employment, sep="_")
housing$campaign_efficiency <- housing$previous / housing$campaign
housing$economic_conditions <- (housing$unemploy_rate + housing$cons.price.idx + housing$cons.conf.idx - housing$inflation_rate) / 4
imbalance_weight <- sum(housing$y_numeric == 0) / sum(housing$y_numeric == 1)

Our improved model shows that most indicators are significantly correlated with credit acceptance with p < 0.05, except for some categories of job-education interaction as well as pdays, which is whether an individual has been contacted before. This is probably because pday is correlated with poutcome, another variable indicating success of previous contact.

set.seed(479)
trainIndex <- createDataPartition(housing$y , p = .65,
                                  list = FALSE,
                                  times = 1)
housingTrain <- housing[ trainIndex,]
housingTest  <- housing[-trainIndex,]

improvedModel <- glm(y_numeric ~ .,
                  data=housingTrain %>% 
                    dplyr::select(y_numeric, age, job_education_interaction, mortgage, pdays, contact, poutcome, economic_conditions, previous, campaign_efficiency),  weights = ifelse(y_numeric == 1, imbalance_weight, 1), 
                  family="binomial" (link="logit"))

improved_sum <- summary(improvedModel)
coefficients_table <- as.data.frame(improved_sum$coefficients)

coefficients_table$significance <- ifelse(coefficients_table$`Pr(>|z|)` < 0.001, '***',
                                         ifelse(coefficients_table$`Pr(>|z|)` < 0.01, '**',
                                                ifelse(coefficients_table$`Pr(>|z|)` < 0.05, '*',
                                                       ifelse(coefficients_table$`Pr(>|z|)` < 0.1, '.', ''))))

coefficients_table$p_value <- paste0(round(coefficients_table$`Pr(>|z|)`, digits = 3), coefficients_table$significance)

coefficients_table %>%
  select(-significance, -`Pr(>|z|)`) %>% 
  kable(align = "r") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))  %>%
  footnote(general_title = "\n", general = "Table 5")
Estimate Std. Error z value p_value
(Intercept) -2.4483602 0.4237519 -5.7778149 0***
age 0.0067819 0.0029976 2.2624372 0.024*
job_education_interactionhighschool.above_not-employed 0.5082476 0.2026669 2.5077973 0.012*
job_education_interactionhighschool.above_self-employed -0.0568971 0.2457110 -0.2315608 0.817
job_education_interactionhighschool.above_white-collar 0.0943712 0.1708588 0.5523346 0.581
job_education_interactionhighschool.below_blue-collar -0.1894807 0.1833597 -1.0333825 0.301
job_education_interactionhighschool.below_not-employed 0.6054083 0.2088516 2.8987492 0.004**
job_education_interactionhighschool.below_self-employed -0.8584100 0.4391580 -1.9546725 0.051.
job_education_interactionhighschool.below_white-collar -0.1024480 0.2011602 -0.5092856 0.611
mortgageunknown -0.4819300 0.2161021 -2.2301038 0.026*
mortgageyes -0.1803795 0.0638614 -2.8245484 0.005**
pdaysYes 0.7306544 0.4883692 1.4961108 0.135
contacttelephone -0.8533536 0.0753342 -11.3275664 0***
poutcomenonexistent 0.8229103 0.2266384 3.6309397 0***
poutcomesuccess 1.3750342 0.5028598 2.7344284 0.006**
economic_conditions 0.1140043 0.0269205 4.2348516 0***
previous 1.1581184 0.2597144 4.4591994 0***
campaign_efficiency -0.4983248 0.2125183 -2.3448563 0.019*

Table 5

We tested our model and a mosaic plot is created to visualize the confusion matrix. Now, the model has much higher sensitivity rate of 0.61. The accuracy rate dropped to 0.70, but is still acceptable. This means that now, our model is able to capture a significantly more number of positive cases.

testProbs <- data.frame(Outcome = as.factor(housingTest$y_numeric),
                        Probs = predict(improvedModel, housingTest, type= "response"))

testProbs <- 
  testProbs %>%
  mutate(predOutcome  = as.factor(ifelse(testProbs$Probs > 0.5 , 1, 0)))  


mosaicplot(confusionMatrix(testProbs$predOutcome, testProbs$Outcome, 
                       positive = "1")$table, color=c("#DBC2CF","#9FA2B2"), main = "Mosaic Plot for Improved Confusion Matrix",
           xlab = "Prediction", ylab = "Reference")

improvedmatrix <- as.data.frame(as.table(confusionMatrix(testProbs$predOutcome, testProbs$Outcome, 
                       positive = "1")$table))

improvedmatrix %>% 
  kable() %>% 
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))  %>%
  footnote(general_title = "\n", general = "Table 6")
Prediction Reference Freq
0 0 915
1 0 368
0 1 60
1 1 97

Table 6

We computed the Area Under the Receiver Operating Characteristic Curve (AUC-ROC) metric to further evaluate the performance of our improved model. The AUC value ranges from 0 to 1, where 0.5 means a model with no discriminatory power (it performs as well as random chance), and 1 means a perfect model (it perfectly distinguishes between positive and negative cases).Generally, an AUC between 0.7 and 0.8 is considered acceptable and indicates a fair to good performance of the model. While there is room for improvement, a score of 0.74 suggests that the model is capturing important patterns in the data and making predictions better than random guessing.

ggplot(testProbs, aes(d = as.numeric(Outcome), m = Probs)) +
  geom_roc(n.cuts = 50, labels = FALSE, colour = "#2E4756") +
    labs(title = "ROC Curve for Improved Model") +
  style_roc(theme = theme_grey) +
  geom_abline(slope = 1, intercept = 0, size = 1.5, color = "#DBC2CF") +
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=9), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))

Cross Validation

Next, we performed cross validation test for both of original model and improved model. The trainControl parameter is set to run 100 k-folds and to output predicted probabilities, classProbs, for two classes, those who accepted the credit and those who did not. Additional parameters output AUC (the train function refers to this as ‘ROC’) and confusion metrics for each fold. The three metrics in the cvFit output are for mean AUC, Sensitivity, and Specificity across all 100 folds. Here, the ROC for our original model and improved model are 0.76 and 0.70, the Sensitivity are 0.97 and 0.98, while the Specificity are 0.21 and 0.17 respectively.

ctrl <- trainControl(method = "cv", number = 100, classProbs=TRUE, summaryFunction=twoClassSummary)

cvFit_original <- train(y ~ .,
                  data=housing %>% 
                    dplyr::select(age, job, marital, education, taxLien, mortgage, taxbill_in_phl, contact, month, day_of_week, campaign, pdays, poutcome, unemploy_rate, cons.conf.idx, cons.price.idx, inflation_rate, spent_on_repairs, y), 
                method="glm", family="binomial",
   
                             metric="ROC", trControl = ctrl)
cvFit_improved <- train(y ~ .,
                  data=housing %>% 
                    dplyr::select(y, age, job_education_interaction, mortgage, pdays, contact, poutcome, economic_conditions, previous, campaign_efficiency),   weights = ifelse(y == "Yes", imbalance_weight, 1),
                method="glm", family="binomial",
                metric="ROC", trControl = ctrl)

The figure below plots the distribution of AUC, Sensitivity, and Specificity across the 100 folds. The tighter each distribution is to its mean, the more generalizable the model. Both models generalizes well to sensitivity – the rate it correctly predicts credit acceptance, instead of specificity. It seems our would-be decision-making tool is inconsistent in how it predicts the credit acceptance. This inconsistency will have a direct effect on the marketing and resource allocation process should this algorithm be put into production.

dplyr::select(cvFit_original$resample, -Resample) %>%
  gather(metric, value) %>%
  left_join(gather(cvFit_original$results[2:4], metric, mean)) %>%
  ggplot(aes(value)) + 
    geom_histogram(bins=20, fill = "#DBC2CF") +
    facet_wrap(~metric) +
    geom_vline(aes(xintercept = mean), colour = "#2E4756", linetype = 2, size = 0.6) +
    scale_x_continuous(limits = c(0, 1)) +
    labs(x="Goodness of Fit", y="Count", title="CV Goodness of Fit Metrics Original Model",
         subtitle = "Across-fold mean reprented as dotted lines") + 
   theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=9), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))

dplyr::select(cvFit_improved$resample, -Resample) %>%
  gather(metric, value) %>%
  left_join(gather(cvFit_improved$results[2:4], metric, mean)) %>%
  ggplot(aes(value)) + 
    geom_histogram(bins=20, fill = "#DBC2CF") +
    facet_wrap(~metric) +
    geom_vline(aes(xintercept = mean), colour = "#2E4756", linetype = 2, size = 0.8) +
    scale_x_continuous(limits = c(0, 1)) +
    labs(x="Goodness of Fit", y="Count", title="CV Goodness of Fit Metrics for Improved Model",
         subtitle = "Across-fold mean reprented as dotted lines") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=9), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))

Cost Benefit Analysis

In this final section, we conducted a cost benefit analysis to help HCD maximize their revenue. If we predict that a household will take the credit, then HCD allocates (on average) $2,850 per homeowner which includes staff and resources to facilitate mailers, phone calls, and information/counseling sessions at the HCD offices. And among those eligible homeowners, 25% of them will take the $5000 credit. This wil lead to a $10,000 premium, on average, for each houses that transacted after taking the credit and an aggregate premium of $56,000, on average, for homes surrounding the repaired home. Based on this, we outlined the cost/benefit for each scenario in the confusion matrix, where Count refers to the number of occurrences of each scenario:

  • True Positive: We predicted correctly homeowner would enter credit program; allocated the marketing resources, and 25% ultimately achieved the credit: \(-2850 * Count - 5000 * Count * 0.25 + (10000 + 56000) * Count\)

  • True Negative: We predicted correctly homeowner would not take the credit, no marketing resources were allocated, and no credit was allocated: \(0*Count\)

  • False Positive: We predicted incorrectly homeowner would take the credit; allocated marketing resources; no credit allocated: \(-2850 * Count\)

  • False Negative: We predicted that a homeowner would not take the credit but they did. These are likely homeowners who signed up for reasons unrelated to the marketing campaign. Thus, we ‘0 out’ this category.

We may see that

cost_benefit_table <-
   testProbs %>%
      count(predOutcome, Outcome) %>%
      summarize(True_Negative = sum(n[predOutcome==0 & Outcome==0]),
                True_Positive = sum(n[predOutcome==1 & Outcome==1]),
                False_Negative = sum(n[predOutcome==0 & Outcome==1]),
                False_Positive = sum(n[predOutcome==1 & Outcome==0])) %>%
       gather(Variable, Count) %>%
       mutate(Revenue =
               ifelse(Variable == "True_Negative", Count * 0,
               ifelse(Variable == "True_Positive", 61900 * Count,
               ifelse(Variable == "False_Negative", 0 * Count,
               ifelse(Variable == "False_Positive", (-2850) * Count, 0)))))
   
kable(cost_benefit_table) %>% 
kable_styling(bootstrap_options = c("striped", "hover", "condensed"))  %>%
  footnote(general_title = "\n", general = "Table 7")
Variable Count Revenue
True_Negative 915 0
True_Positive 97 6004300
False_Negative 60 0
False_Positive 368 -1048800

Table 7

Because a different confusion matrix, set of errors and cost/benefit calculation exists for each threshold, we need to compute an ideal threshold that returns the greatest cost/benefit. The figure below visualizes how each of the four scenarios in our confusion matrix change along with the threshold. As we increase the threshold, our true positive and false positive rate both increases. This means that our likelihood of getting benefits or seeing costs both increases. The goal is therefore to find an optimal threshold that maximizes benefits and minimizes costs along this line.

all_threshold %>% 
  ggplot() +
  geom_line(aes(x = threshold, y=Count, color = Variable), size = 1.5, linetype = 1) + 
  scale_color_manual(values = palette4, guide=FALSE) + 
  facet_wrap(~Variable) + 
  labs(title = "Confusion Metric Outcomes for Each Threshold") +
  xlab("Threshold") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=9), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))

The iterateThresholds function is based on a while loop. The threshold x, starts at 0.01 and while it is less than 1, a predicted classification, predOutcome, is mutated given x; confusion metrics are calculated; the cost benefit is performed and is appended to a data frame called all_prediction. Finally x is iterated by adding 0.01 and the process continues until x = 1.

iterateThresholds <- function(data) {
  x = .01
  all_prediction <- data.frame()
  while (x <= 1) {
  
  this_prediction <-
      testProbs %>%
      mutate(predOutcome = ifelse(Probs > x, 1, 0)) %>%
      count(predOutcome, Outcome) %>%
      summarize(True_Negative = sum(n[predOutcome==0 & Outcome==0]),
                True_Positive = sum(n[predOutcome==1 & Outcome==1]),
                False_Negative = sum(n[predOutcome==0 & Outcome==1]),
                False_Positive = sum(n[predOutcome==1 & Outcome==0])) %>%
     gather(Variable, Count) %>%
     mutate(Revenue =
               ifelse(Variable == "True_Negative", Count * 0,
               ifelse(Variable == "True_Positive", 61900 * Count,
               ifelse(Variable == "False_Negative", 0 * Count,
               ifelse(Variable == "False_Positive", (-2850) * Count, 0)))),
            Threshold = x)
  
  all_prediction <- rbind(all_prediction, this_prediction)
  x <- x + .01
  }
return(all_prediction)
}

Using this function, we computed our optimal threshold, which is 0.4 in this case.

whichThreshold <- iterateThresholds(testProbs)

whichThreshold_revenue <- 
whichThreshold %>% 
    group_by(Threshold) %>% 
    summarize(Revenue = sum(Revenue))

plot1 <- ggplot(whichThreshold_revenue)+
  geom_line(aes(x = Threshold, y = Revenue), linewidth = 2, color = "#DBC2CF")+
  geom_vline(xintercept =  pull(arrange(whichThreshold_revenue, -Revenue)[1,1]), color = "#9FA2B2", linewidth = 2.5)+
    labs(title = "Model Revenues By Threshold and Count",
         subtitle = "Vertical Line Denotes Optimal Threshold") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=9), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))
  
credict_count <-whichThreshold %>% 
  filter(Variable == "True_Positive") %>% 
  mutate(Credit_Count = Count * 0.25)

plot2 <- credict_count %>% 
  ggplot() +
  geom_line(aes(x = Threshold, y = Credit_Count), linewidth = 2, color = "#DBC2CF") + 
  geom_vline(xintercept =  pull(arrange(whichThreshold_revenue, -Revenue)[1,1]), color = "#9FA2B2", linewidth = 2.5) +
   theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=9), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))

plot1 / plot2 

Finally, we compare the revenue produced with optimal threshold and a 50% threshold. It shows that a threshold of 40% is much more optimal in that it yields the greatest revenue at $6,403,3900 while a 50% threshold will only yield $4,817,450. In addition, the total count of credits earned is much higher when using the optimal threshold.

final_table<- data.frame(
  Threshold = c("Optimal_Threshold", "50%_Threshold"),
  Credit = c("34.75", "23.75"),
  Revenue = c("6403900", "4817450")
)

final_table %>% 
  kbl() %>% 
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))  %>%
  footnote(general_title = "\n", general = "Table 8")
Threshold Credit Revenue
Optimal_Threshold 34.75 6403900
50%_Threshold 23.75 4817450

Table 8

Conclusions

In conclusion, we have designed a model to predict the likelihood of Emil City homeowners accepting home repair tax credit with acceptable accuracy and sensitivity, but this does not mean that this model can be put into production at this point. Our primary concerns with the model include the following. Firstly, there’s significant discrepancies in terms of the number of outcomes of our predictor variables. In logistic regression, unbalanced sample sizes can lead the model to be biased towards the majority class. If the minority class is of particular interest (in our case, people who accepted the credit is the minority class), an unbalanced data set can result in poor predictive performance for the minority class, leading to a model with high accuracy because it predicts the majority class well, but performs poorly on the minority class. Despite that we have applied weight on our minority class to alleviate the effect of imbalanced dataset, it will be good to either oversample the minority class, undersample the majority class, or use alternative algorithm approaches to assign different classifications costs to different classes.

In addition, the current data set contains a lot of field categorized as unknown, and sometimes we see a statistically significant differences in mean between our unknown group and other group in a paired wise comparison. This is leading to misinterpretation in our results. In most cases, models are the most accurate when built using previous data on credit acceptance, and having unknown fields in these variables is likely lead to poor performance of our model.

Moreover, human behavior is influenced by a myriad of factors, including emotions, culture, social norms, personal experiences, and cognitive processes, and those dimensions are not fully included in our model. While capturing this complexity of human behavior in a mathematical model is inherently challenging, it is good practice to include variables such as lifestyle choices, emotional state, device usage, and most importantly, results from any feedback surveys into this model.

And finally, we need to think about what is the most “optimal” model for HCD. For organizations whose goal is solely to achieve greater economic benefits, than we have provided an optimized model for them. But for government agencies like HCD who has many unquantifiable bottom lines like equity and social cohesion, this might not be the case. Whether or not to allocated credit and to whom they allcoate credit should not be based entirely on revenues made, but on people who need those resources.

LS0tCnRpdGxlOiAiVGFyZ2V0aW5nIEhvdXNpbmcgU3Vic2lkeSB3aXRoIFBlb3BsZS1CYXNlZCBNYWNoaW5lIExlYXJuaW5nIgphdXRob3I6ICJFbWlseSBaaG91IgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6IHNpbXBsZXgKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMKZWRpdG9yX29wdGlvbnM6CiAgbWFya2Rvd246CiAgICB3cmFwOiBzZW50ZW5jZQotLS0KClZlcnNpb24gMS4wIFx8IEZpcnN0IENyZWF0ZWQgT2N0IDIyLCAyMDIzIFx8IFVwZGF0ZWQgTm92IDEsIDIwMjMKCktleXdvcmRzOiBMb2dpc3RpYyBSZWdyZXNzaW9uLCBDb25mdXNpb24gTWF0cml4LCBST0MgQ3VydmUsIFNlbnNpdGl2aXR5LCBTcGVjaWZpY2l0eSwgQ29zdC1CZW5lZml0IEFuYWx5c2lzLCBNY0ZhZGRlbiBSLVNxdWFyZWQKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKCiMgSW50cm9kdWN0aW9uCgpQZW9wbGUtYmFzZWQgbWFjaGluZSBsZWFybmluZywgYSBmaWVsZCBoYXJuZXNzaW5nIHRoZSBwb3dlciBvZiBkYXRhLWRyaXZlbiBpbnNpZ2h0cyBpbnRvIGh1bWFuIGJlaGF2aW9ycyBhbmQgcHJlZmVyZW5jZXMsIGhhcyBiZWNvbWUgcGl2b3RhbCBmb3IgYnVzaW5lc3NlcyBhbmQgb3JnYW5pemF0aW9ucy4gV2l0aCBhbGdvcml0aG1zIHRyYWluZWQgb24gaHVtYW4tY2VudHJpYyBkYXRhLCBidXNpbmVzc2VzIGdldCB0byBwZXJzb25hbGl6ZSBwcm9kdWN0cywgc2VydmljZXMsIGFuZCBleHBlcmllbmNlcyBiYXNlZCBvbiBpbmRpdmlkdWFsIGN1c3RvbWVyIHByZWZlcmVuY2VzLiBBdCBhbnkgcG9pbnQsIHRoZSBib3R0b20gbGluZSBvZiBhbnkgYnVzaW5lc3NlcyBhbmQgb3JnYW5pemF0aW9ucyBtYXkgYmUgYWZmZWN0ZWQsIGlmIGEgY2xpZW50IG5vIGxvbmdlciB3aXNoIHRvIHBhcnRpY2lwYXRlLCBkb25hdGUsIG9yIHB1cmNoYXNlIGEgZ29vZCwgd2hpY2ggbWFrZXMgaXQgZXh0cmEgaW1wb3J0YW50IHRvIGhhdmUgYSBwcmVkaWN0aXZlIG1vZGVsIHRoYXQgY2FwdHVyZXMgdGhlc2UgcG90ZW50aWFsIGNoYW5nZXMuIExpa2UgYW55IG1hY2hpbmUgbGVhcm5pbmcgYXBwbGljYXRpb25zLCBob3dldmVyLCBpdCBpcyBkaWZmaWN1bHQgdG8gY2FwdHVyZSB0aGUgbnVhbmNlcyBpbiBodW1hbiBiZWhhdmlvciB3aXRoIGEgc2luZ2xlIG1vZGVsLCBub3IgaXMgaXQgcG9zc2libGUgdG8gaGF2ZSBhIHVuYmlhc2VkIHNhbXBsZSBkYXRhc2V0IHRoYXQgdHJ1bHkgcmVmbGVjdCBodW1hbiBiZWhhdmlvci4gVGhpcyBtZWFucyB0aGF0IGNvbnRpbnVvdXMgbW9kZWwgYWRhcHRhdGlvbiBhbmQgcmVjb2duaXRpb24gb2YgdGhlc2UgY29tcGxleGl0aWVzIGFyZSBlc3NlbnRpYWwgZm9yIGVmZmVjdGl2ZSBkZWNpc2lvbi1tYWtpbmcgYmFzZWQgb24gdGhlc2UgbW9kZWxzLiAKClRoZSBnb2FsIG9mIHRoaXMgcmVwb3J0IGlzIHRvIGZpdCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgdG8gcHJlZGljdCB3aGV0aGVyIGhvbWVvd25lcnMgaW4gRW1pbCBDaXR5IHdpbGwgZW50ZXIgYW5kIHRha2UgYSBob21lIHJlcGFpciB0YXggY3JlZGl0LiBUbyBjb250ZXh0dWFsaXplLCB0aGUgY2l0eSBpcyBjb25zaWRlcmluZyBhIG1vcmUgcHJvYWN0aXZlIGFwcHJvYWNoIGZvciB0YXJnZXRpbmcgaG9tZW93bmVycyB3aG8gd291bGQgcXVhbGl0eSBmb3IgdGhpcyB0YXggY3JlZGl0IHByb2dyYW0gYW5kIHRoZSBEZXBhcnRtZW50IG9mIEhvdXNpbmcgYW5kIENvbW11bml0eSBEZXZlbG9wbWVudCAoSENEKSBoYXZlIGJlZW4gdHJ5aW5nIHRvIHByb2FjdGl2ZWx5IHJlYWNoIG91dCB0byBlbGlnaWJsZSBob21lb3duZXJzIGV2ZXIgeWVhci4gSXQgaXMgcHJlZGljdGVkIHRoYXQgaG91c2VzIHRoYXQgdHJhbnNhY3RlZCBhZnRlciB0YWtpbmcgdGhlIGNyZWRpdCwgd2lsbCBzZWxsIHdpdGggYSBcJDEwLDAwMCBwcmVtaXVtLCBvbiBhdmVyYWdlLiBIb21lcyBzdXJyb3VuZGluZyB0aGUgcmVwYWlyZWQgaG9tZSBzZWUgYW4gYWdncmVnYXRlIHByZW1pdW0gb2YgXCQ1NiwwMDAsIG9uIGF2ZXJhZ2UuIFRoZSB0aG9ybnkgdGFzayBmb3IgdGhlIEhDRCBpcyB0byB0YXJnZXQgaG9tZW93bmVycyB3aXRoIHRoZSBoaWdoZXN0IGxpa2VsaWhvb2Qgb2YgYWNjZXB0aW5nIHRoZSBjcmVkaXQuIEluIHRoaXMgY2FzZSwgdGhleSBjb3VsZCBtYXhpbWl6ZSB0aGUgYmVuZWZpdHMgb2YgdGhlIGFnZ3JlZ2F0ZSBwcmVtaXVtIG9mIGhvdXNlcyB3aGlsZSByZWR1Y2UgdGhlIG1hcmtldGluZyByZXNvdXJjZXMgY29zdC4gQXMgb2Ygbm93LCB0eXBpY2FsbHkgbm90IGFsbCBlbGlnaWJsZSBob21lb3duZXJzIHRoZXkgcmVhY2ggb3V0IGFuZCBlbnRlciB0aGUgcHJvZ3JhbSB1bHRpbWF0ZWx5IHRha2VzIHRoZSBjcmVkaXQuCgpUaGF0IHNhaWQsIHdlIHdpbGwgbmVlZCB0byB0cmFpbiBhIGJldHRlciBtb2RlbCB0byBlbnN1cmUgdGhhdCB0aGUgSENEIHdpbGwgbm90IGJlIHJlYWNoaW5nIG91dCB0byBlbGlnaWJsZSBob21lb3duZXJzIGF0IHJhbmRvbS4gTG9naXN0aWMgcmVncmVzc2lvbiBpcyB1c2VkIGhlcmUgZHVlIHRvIGl0cyBpbnRlcnByZXRhYmlsaXR5IGFuZCBzdWl0YWJpbGl0eSBmb3IgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHByb2JsZW1zIChpbiB0aGlzIGNhc2UsIG91ciBkZXBlbmRlbnQgdmFyaWFibGUgaW5kaWNhdGUgd2hldGhlciB0aGUgYSBob21lb3duZXIgd2lsbCB0YWtlIHRoZSBjcmVkaXQgb3Igbm90KS4gV2Ugd2lsbCAqKmZpcnN0Kiogc3RhcnQgd2l0aCBkYXRhIGV4cGxvcmF0aW9uIHRvIGRldGVybWluZSB2YXJpYWJsZXMgbW9zdCBleHBsYWlucyBob21lb3duZXIncyBiZWhhdmlvci4gKipUaGVuKiosIHdlIHNwbGl0IHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0LCBmaXQgYSByZWdyZXNzaW9uIG1vZGVsLCBhbmQgY29uc3RydWN0ZWQgY29uZnVzaW9uIG1hdHJpeCBhdCA1MCUgdGhyZXNob2xkIHRvIHNlZSB0aGUgZXh0ZW50IHRvIHdoaWNoIG91ciBtb2RlbCBjYW4gY29ycmVjdGx5IHByZWRpY3QgaG9tZW93bmVyIGVudGVyaW5nIHRoZSBjcmVkaXQgcHJvZ3JhbSAodHJ1ZSBwb3NpdGl2ZSksIG9yIGNvcnJlY3RseSBwcmVkaWN0IGhvbWVvd25lciBub3QgdGFraW5nIHRoZSBjcmVkaXQgKHRydWUgbmVnYXRpdmUpLiBUaGlzIGlzICoqZm9sbG93ZWQqKiBieSBhIGNyb3NzIHZhbGlkYXRpb24gdGVzdCB0byBjb21wdXRlIFJPQywgc2Vuc2l0aXZpdHksIGFuZCBzcGVjaWZpY2l0eS4gV2UgKipjb25jbHVkZSoqIG91ciBhbmFseXNpcyB3aXRoIGEgY29zdC9iZW5lZml0IGFuYWx5c2lzIGFuZCBkZXRlcm1pbmUgdGhlIG9wdGltYWwgdGhyZXNob2xkIG9mIGRlbGluZWF0aW5nIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBwcmVkaWN0aW9uIHRoYXQgd291bGQgbWF4aW1pemUgdGhlIHJldmVudWUuIAoKVGhlIEdpdEh1YiByZXBvc2l0b3J5IGZvciB0aGlzIHJlcG9ydCBpcyBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2VtaWx5emhvdTExMi9NVVNBNTA4MC1Ib3VzaW5nLVN1YnNpZHkpLgoKCmBgYHtyIGxpYnJhcnkgYW5kIGRhdGFzZXQsIGluY2x1ZGU9RkFMU0V9Cm9wdGlvbnMoc2NpcGVuPTEwMDAwMDAwKQoKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeShjYXJldCkKbGlicmFyeShrbml0cikgCmxpYnJhcnkocHNjbCkKbGlicmFyeShwbG90Uk9DKQpsaWJyYXJ5KHBST0MpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KHBhdGNod29yaykKCnBhbGV0dGU1IDwtIGMoIiNEQkMyQ0YiLCIjOUZBMkIyIiwiIzNDN0E4OSIsIiMyRTQ3NTYiLCIjMTYyNjJFIikKcGFsZXR0ZTQgPC0gYygiI0RCQzJDRiIsIiM5RkEyQjIiLCIjM0M3QTg5IiwiIzJFNDc1NiIpCnBhbGV0dGUyIDwtIGMoIiNEQkMyQ0YiLCIjOUZBMkIyIikKcGFsZXR0ZTEgPC0gYygiI0RCQzJDRiIsIiMxNjI2MkUiKQoKCmhvdXNpbmcgPC0gcmVhZC5jc3YoImhvdXNpbmdTdWJzaWR5LmNzdiIpCmhvdXNpbmcgPC0gaG91c2luZyAlPiUgCiAgbXV0YXRlKHBkYXlzID0gYXMuZmFjdG9yKHBkYXlzKSkgJT4lIAogIG11dGF0ZShwZGF5cyA9IGlmZWxzZShwZGF5cyA9PSI5OTkiLCAiTm8iLCAiWWVzIikpCmBgYAoKCiMgVmFyaWFibGVzIEV4YW1pbmF0aW9uCgpUaGUgZmlyc3QgcGFydCBvZiB0aGlzIHJlcG9ydCBmb2N1c2VzIG9uIGRhdGEgZXhwbG9yYXRpb24gdG8gc2VlIGhvdyBlYWNoIGZhY3RvciBpbiBvdXIgZGF0YXNldCByZWxhdGVzIHRvIGhvbWVvd25lcidzIGRlY2lzaW9uIHRvIGFjY2VwdCBob21lIHJlcGFpciB0YXggY3JlZGl0LiBWYXJpYWJsZXMgYXJlIHNwbGl0IGludG8gY2F0ZWdvcmljYWwgdmFyaWFibGVzIGFuZCBudW1lcmljIHZhcmlhYmxlcyByZXNwZWN0aXZlbHksIGZvciB3aGljaCB3ZSBjb25kdWN0ZWQgY2hpLXNxdWFyZSBhbmQgYW5vdmEgdGVzdHMgdG8gZXhhbWluZSBpZiB0aGVyZSdzIGEgc2lnbmlmaWNhbnQgYXNzb2NpYXRpb24gYmV0d2VlbiBkZXBlbmRlbnQgdmFyaWFibGUgYW5kIGluZGVwZW5kZW50IHZhcmlhYmxlcy4gCgojIyBDYXRlZ29yaWNhbCBWYXJpYWJsZXMKClRoZSBmaWd1cmUgYmVsb3cgc2hvd3MgdGhlIGRpZmZlcmVuY2UgaW4gdGhlIG51bWJlciBvZiBob21lb3duZXJzIHdobyBhY2NlcHRlZCBhbmQgZGlkIG5vdCBhY2NlcHQgdGF4IGNyZWRpdCBiYXNlZCBvbiBhIGRpZmZlcmVudCBjYXRlZ29yaWNhbCBmZWF0dXJlcy4gV2Ugb2JzZXJ2ZWQgdGhlIGZvbGxvd2luZy4gVGhlcmUgc2VlbXMgdG8gYmUgYSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGluIHRoZSBudW1iZXIgb2YgcGVvcGxlIGFjY2VwdGluZyBjcmVkaXQgd2hvIHdlcmUgY29udGFjdGVkIHZpYSBjZWxsdWxhciwgd2hvIGhhZCBhIHVuaXZlcnNpdHkgZGVncmVlLCB3b3JrIGFzIGFkbWluIG9yIHRlY2huaWNpYW4sIHdlcmUgbWFycmllZCwgd2VyZSBwcmV2aW91c2x5IGNvbnRhY3RlZCBpbiBNYXksIHdlcmUgY29udGFjdGVkIHJlY2VudGx5LCB3ZXJlIFBoaWxhZGVscGhpYSByZXNpZGVudHMsIGFuZCBoYWQgcHJldmlvdXNseSBhY2NlcHRlZCB0aGUgY3JlZGl0LiBPbiB0aGUgb3RoZXIgaGFuZCwgdGhlcmUgZG9lc24ndCBzZWVtIHRvIGJlIGEgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBpbiB0aGUgbnVtYmVyIG9mIHBlb3BsZSBhY2NlcHRpbmcgY3JlZGl0IGRlcGVuZGluZyBvbiB0aGUgZGF5IG9mIHRoZSB3ZWVrIHRoZXkgd2VyZSBjb250YWN0ZWQsIHRoZWlyIG1vcnRnYWdlIHN0YXR1cywgYW5kIHByZXNlbmNlIG9mIHRheCBsaWVuLiAKCgpgYGB7ciBjYXRlZ29yaWNhbCB2YXIsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xM30KCmNhdF92YXIgPC0gaG91c2luZyAlPiUgCiAgc2VsZWN0KC1jKFgsIGFnZSwgeV9udW1lcmljLCBwcmV2aW91cywgdW5lbXBsb3lfcmF0ZSwgY29ucy5wcmljZS5pZHgsIGNvbnMuY29uZi5pZHgsIGluZmxhdGlvbl9yYXRlLCBzcGVudF9vbl9yZXBhaXJzLCBjYW1wYWlnbikpCmNhdF92YXIgJT4lCiAgICBnYXRoZXIoVmFyaWFibGUsIHZhbHVlLCAteSkgJT4lCiAgICBjb3VudChWYXJpYWJsZSwgdmFsdWUsIHkpICU+JQogICAgICBnZ3Bsb3QoLiwgYWVzKHZhbHVlLCBuLCBmaWxsID0geSkpICsgICAKICAgICAgICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIsIHN0YXQ9ImlkZW50aXR5IikgKwogICAgICAgIGZhY2V0X3dyYXAoflZhcmlhYmxlLCBzY2FsZXM9ImZyZWUiLCBuY29sID0gNCwgCiAgICAgICAgICAgICAgICAgICBsYWJlbGxlcj0gbGFiZWxsZXIoVmFyaWFibGUgPSBjKAogICAgICAgICAgICAgICAgICAgICBgY29udGFjdGAgPSAiV2F5cyBvZiBDb250YWN0IiwKICAgICAgICAgICAgICAgICAgICAgYGRheV9vZl93ZWVrYCA9ICJEYXkgb2YgdGhlIFdlZWsiLAogICAgICAgICAgICAgICAgICAgICBgZWR1Y2F0aW9uYCA9ICJFZHVjYXRpb25hbCBBdHRhaW5tZW50IiwKICAgICAgICAgICAgICAgICAgICAgYHBkYXlzYCA9ICJEYXlzIEFmdGVyIENvbnRhY3QiLAogICAgICAgICAgICAgICAgICAgICBgam9iYCA9ICJPY2N1cGF0aW9uIEluZGljYXRvciIsCiAgICAgICAgICAgICAgICAgICAgIGBtYXJpdGFsYCA9ICJNYXJpdGFsIFN0YXR1cyIsCiAgICAgICAgICAgICAgICAgICAgIGBtb250aGAgPSAiTW9udGggb2YgTGFzdCBDb250YWN0IiwKICAgICAgICAgICAgICAgICAgICAgYG1vcnRnYWdlYCA9ICJNb3J0Z2FnZSIsCiAgICAgICAgICAgICAgICAgICAgIGBwb3V0Y29tZWAgPSAiUHJldmlvdXMgQ2FtcGFpZ24gT3V0Y29tZSIsCiAgICAgICAgICAgICAgICAgICAgIGB0YXhiaWxsX2luX3BobGAgPSAiUGhpbGFkZWxwaGlhIFJlc2lkZW50cyIsCiAgICAgICAgICAgICAgICAgICAgIGB0YXhMaWVuYCA9ICJMaWVuIikpKSArCiAgICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTIsIG5hbWUgPSAiQ3JlZGl0IikgKwogICAgICAgIGxhYnMoeD0iQ3JlZGl0IiwgeT0iQ291bnQiLAogICAgICAgICAgICAgdGl0bGUgPSAiRmVhdHVyZSBBc3NvY2lhdGlvbnMgd2l0aCB0aGUgTGlrZWxpaG9vZCBvZiBDcmVkaXQgQWNjZXB0YW5jZSIsCiAgICAgICAgICAgICBzdWJ0aXRsZSA9ICJDYXRlZ29yaWNhbCBmZWF0dXJlcyIpICsKICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTgsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCgkJCQkJCmBgYAoKVXNpbmcgcHVyZSB2aXN1YWwgZXZpZGVuY2UgaXMgbm90IGVub3VnaCB0byBleHBsYWluIHRoZSBzaWduaWZpY2FuY2Ugb2Ygb3VyIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBpbiBwcmVkaWN0aW5nIHdoZXRoZXIgaG9tZW93bmVycyB3aWxsIHRha2UgdGhlIGNyZWRpdC4gVG8gc3VwcG9ydCB0aGUgYWJvdmUgdmlzdWFsaXphdGlvbiwgd2UgY29uZHVjdGVkIGNoaS1zcXVhcmUgdGVzdCBpdGVyYXRpdmVseSBmb3IgYWxsIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gVGhlIGNoaS1zcXVhcmUgdGVzdCBkZXRlcm1pbmVzIGlmIHRoZXJlIGlzIGEgc2lnbmlmaWNhbnQgYXNzb2NpYXRpb24gYmV0d2VlbiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuIEl0IGNvbXBhcmVzIHRoZSBvYnNlcnZlZCBmcmVxdWVuY2llcyBvZiBkaWZmZXJlbnQgY2F0ZWdvcmllcyBpbiBhIGNvbnRpbmdlbmN5IHRhYmxlIHdpdGggdGhlIGZyZXF1ZW5jaWVzIHRoYXQgd291bGQgYmUgZXhwZWN0ZWQgaWYgdGhlIHZhcmlhYmxlcyB3ZXJlIGluZGVwZW5kZW50LiBUaGUgdGFibGUgYmVsb3cgc2hvd3MgdGhhdCBgV2F5cyBvZiBDb250YWN0YCwgYEVkY3VjYXRpb25gLCBgTWFyaXRhbGAsIGBPY2N1cGF0aW9uYCwgYE1vbnRoIG9mIExhc3QgQ29udGFjdGAsIGBQcmV2aW91c3QgQ2FtcGFpZ24gT3V0Y29tZWAsIGBMaWVuIFByZXNlbmNlYCBhbmQgYERheXMgQWZ0ZXIgTGFzdCBDb250YWN0YCBhcmUgc2lnbmlmaWNhbnQgcHJlZGljdG9ycyBpbiBkZXRlcm1pbmluZyBob21lb3duZXIncyBsaWtlbGlob29kIG9mIGFjY2VwdGluZyB0aGUgY3JlZGl0LiAKCmBgYHtyIGNoaXNxIHRlc3QsIHdhcm5pbmc9RkFMU0V9CgpjaGlfdmFyIDwtIGMoImNvbnRhY3QiLCAiZGF5X29mX3dlZWsiLCAiZWR1Y2F0aW9uIiwgImpvYiIsICJtYXJpdGFsIiwgIm1vbnRoIiwgIm1vcnRnYWdlIiwgInBvdXRjb21lIiwgInRheGJpbGxfaW5fcGhsIiwgInRheExpZW4iLCAicGRheXMiKQoKTGFiZWwgPSBjKCJjb250YWN0IiA9ICJXYXlzIG9mIENvbnRhY3QiLAogICAgICAgICAgImRheV9vZl93ZWVrIiA9ICJEYXkgb2YgdGhlIFdlZWsiLAogICAgICAgICAgImVkdWNhdGlvbiIgPSAiRWR1Y2F0aW9uYWwgQXR0YWlubWVudCIsCiAgICAgICAgICAiam9iIiA9ICJPY2N1cGF0aW9uIEluZGljYXRvciIsCiAgICAgICAgICAibWFyaXRhbCIgPSAiTWFyaXRhbCBTdGF0dXMiLAogICAgICAgICAgIm1vbnRoIiA9ICJNb250aCBvZiBMYXN0IENvbnRhY3QiLAogICAgICAgICAgIm1vcnRnYWdlIiA9ICJNb3J0Z2FnZSIsCiAgICAgICAgICAicG91dGNvbWUiID0gIlByZXZpb3VzIENhbXBhaWduIE91dGNvbWUiLAogICAgICAgICAgInRheGJpbGxfaW5fcGhsIiA9ICJQaGlsYWRlbHBoaWEgUmVzaWRlbnRzIiwKICAgICAgICAgICJ0YXhMaWVuIiA9ICJMaWVuIiwgCiAgICAgICAgICAicGRheXMiID0gIkRheXMgQWZ0ZXIgQ29udGFjdCIpCgpjaGlfc3F1YXJlX3Jlc3VsdHMgPC0gZGF0YS5mcmFtZSgKICBEZiA9IGludGVnZXIoKSwKICBYX1NxdWFyZWQgPSBudW1lcmljKCksCiAgUF9WYWx1ZSA9IG51bWVyaWMoKSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKQoKZm9yICh2YXIgaW4gY2hpX3ZhcikgewogIGNvbnRpbmdlbmN5X3RhYmxlIDwtIHRhYmxlKGNhdF92YXIkeSwgY2F0X3ZhcltbdmFyXV0pCiAgY2hpX3NxdWFyZSA8LSBjaGlzcS50ZXN0KGNvbnRpbmdlbmN5X3RhYmxlKQogIGNoaV9zcXVhcmVfcmVzdWx0cyA8LSByYmluZChjaGlfc3F1YXJlX3Jlc3VsdHMsIGRhdGEuZnJhbWUoCiAgICBEZiA9IGNoaV9zcXVhcmUkcGFyYW1ldGVyLAogICAgWF9TcXVhcmVkID0gY2hpX3NxdWFyZSRzdGF0aXN0aWMsCiAgICBQX1ZhbHVlID0gY2hpX3NxdWFyZSRwLnZhbHVlCiAgKSkKfQoKcm93bmFtZXMoY2hpX3NxdWFyZV9yZXN1bHRzKSA8LSBMYWJlbAoKCmNoaV9zcXVhcmVfcmVzdWx0cyAlPiUgCiBrYWJsZSgpICU+JSAKIGthYmxlX3N0eWxpbmcoIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lIAogICAgZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgMSIpCgoKYGBgCgoKIyMgTnVtZXJpYyBWYXJpYWJsZXMKClRoZSBmaWd1cmUgYmVsb3cgc2hvd3MgdGhlIGRpZmZlcmVuY2UgaW4gdGhlIG51bWJlciBvZiBob21lb3duZXJzIHdobyBhY2NlcHRlZCBhbmQgZGlkIG5vdCBhY2NlcHQgdGF4IGNyZWRpdCBiYXNlZCBvbiBhIGRpZmZlcmVudCBudW1lcmljIGZlYXR1cmVzLiBUaGVyZSBzZWVtcyB0byBiZSBhIHNpZ25pZmljYW50IGRpZmZlcmVuY2UgaW4gdGhlIG51bWJlciBvZiBwZW9wbGUgYWNjZXB0aW5nIGNyZWRpdCBiYXNlZCBvbiB0aGVpciBhZ2UsIG51bWJlciBvZiBjb250YWN0cyBmb3IgdGhpcyBjYW1wYWlnbiBhbmQgYmVmb3JlIHRoaXMgY2FtcGFpZ24sIGFzIHdlbGwgYXMgaW5mbGF0aW9uIGFuZCB1bmVtcGxveW1lbnQgcmF0ZSBvZiB0aGF0IHRpbWUuIAoKYGBge3IgbnVtZXJpYyB2YXIsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTExfQoKbnVtZXJpY192YXIgPC0gaG91c2luZyAlPiUgCiAgc2VsZWN0KGFnZSwgeSwgcHJldmlvdXMsIHVuZW1wbG95X3JhdGUsIGNvbnMucHJpY2UuaWR4LCBjb25zLmNvbmYuaWR4LCBpbmZsYXRpb25fcmF0ZSwgc3BlbnRfb25fcmVwYWlycywgY2FtcGFpZ24pCgoKbnVtZXJpY192YXIgJT4lCiAgZ2F0aGVyKFZhcmlhYmxlLCB2YWx1ZSwgLXkpICU+JQogICAgZ2dwbG90KGFlcyh5LCB2YWx1ZSwgZmlsbD15KSkgKyAKICAgICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiLCBzdGF0ID0gInN1bW1hcnkiLCBmdW4gPSAibWVhbiIpICsgCiAgICAgIGZhY2V0X3dyYXAoflZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSA0LCBsYWJlbGxlcj0gbGFiZWxsZXIoVmFyaWFibGUgPSBjKAogICAgICAgICAgICAgICAgICAgICBgYWdlYCA9ICJBZ2UiLAogICAgICAgICAgICAgICAgICAgICBgcHJldmlvdXNgID0gIkNvbnRhY3QgYmVmb3JlIENhbXBhaWduIiwKICAgICAgICAgICAgICAgICAgICAgYHVuZW1wbG95X3JhdGVgID0gIlVuZW1wbG95bWVudCBSYXRlIiwKICAgICAgICAgICAgICAgICAgICAgYGNvbnMucHJpY2UuaWR4YCA9ICJDb25zdW1lciBQcmljZSBJbmRleCIsCiAgICAgICAgICAgICAgICAgICAgIGBjb25zLmNvbmYuaWR4YCA9ICJDb25zdW1lciBDb25maWRlbmNlIEluZGV4IiwKICAgICAgICAgICAgICAgICAgICAgYGNhbXBhaWduYCA9ICJDb250YWN0cyBmb3IgQ2FtcGFpZ24iLAogICAgICAgICAgICAgICAgICAgICBgaW5mbGF0aW9uX3JhdGVgID0gIkluZmxhdGlvbiBSYXRlIiwKICAgICAgICAgICAgICAgICAgICAgYHNwZW50X29uX3JlcGFpcnNgID0gIkFtb3VudCBTcGVudCBvbiBSZXBhaXJzIikpKSArCiAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGUyKSArCiAgICAgIGxhYnMoeD0iQ3JlZGl0IiwgeT0iVmFsdWUiLCAKICAgICAgICAgICB0aXRsZSA9ICJGZWF0dXJlIEFzc29jaWF0aW9ucyB3aXRoIHRoZSBMaWtlbGlob29kIG9mIENyZWRpdCBBY2NlcHRhbmNlIiwKICAgICAgICAgICBzdWJ0aXRsZSA9ICJDb250aW5vdXMgZmVhdHVyZXMiKSArCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogICAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxOCwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OSksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoID0wLjgpKQpgYGAKCkJlY2F1c2UgdGhlc2UgbnVtZXJpYyBmZWF0dXJlcyBhbGwgaGF2ZSBkaWZmZXJlbnQgc2NhbGVzIGFuZCBhcmUgZGlmZmljdWx0IHRvIGNvbXBhcmUgYWNyb3NzLCB3ZSBtYWRlIGFub3RoZXIgZGVuc2l0eSBwbG90LCB3aGljaCBjb252ZXlzIHRoZSBzYW1lIGluZm9ybWF0aW9uLCBidXQgc2hvd3MgZGlzdHJpYnV0aW9uIG9mIGEgY29udGludW91cyB2YXJpYWJsZS4gU3VycHJpc2luZ2x5LCB3ZSBzZWUgdGhhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGBDb25zdW1lciBDb25maWRlbmNlIEluZGV4YCwgYENvbnN1bWVyIFByaWNlIEluZGV4YCwgYW5kIGBBbW91bnQgU3BlbnQgb24gUmVwYWlyc2AgYWxsIHNpZ25pZmljYW50bHkgZGlmZmVyIGJldHdlZW4gaG9tZW93bmVycyB3aG8gYWNjZXB0ZWQgYW5kIGRpZCBub3QgYWNjZXB0IHRoZSB0YXggY3JlZGl0LiAKCmBgYHtyIG51bWVyaWMgdmFyIGRlbnNpdHksIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEzfQoKbnVtZXJpY192YXIgJT4lCiAgICBnYXRoZXIoVmFyaWFibGUsIHZhbHVlLCAteSkgJT4lCiAgICBnZ3Bsb3QoKSArIAogICAgZ2VvbV9kZW5zaXR5KGFlcyh2YWx1ZSwgY29sb3I9eSksIGZpbGwgPSAidHJhbnNwYXJlbnQiKSArIAogICBmYWNldF93cmFwKH5WYXJpYWJsZSwgc2NhbGVzID0gImZyZWUiLCBuY29sID0gNCwgbGFiZWxsZXI9IGxhYmVsbGVyKFZhcmlhYmxlID0gYygKICAgICAgICAgICAgICAgICAgICAgYGFnZWAgPSAiQWdlIiwKICAgICAgICAgICAgICAgICAgICAgYHByZXZpb3VzYCA9ICJDb250YWN0IGJlZm9yZSBDYW1wYWlnbiIsCiAgICAgICAgICAgICAgICAgICAgIGB1bmVtcGxveV9yYXRlYCA9ICJVbmVtcGxveW1lbnQgUmF0ZSIsCiAgICAgICAgICAgICAgICAgICAgIGBjb25zLnByaWNlLmlkeGAgPSAiQ29uc3VtZXIgUHJpY2UgSW5kZXgiLAogICAgICAgICAgICAgICAgICAgICBgY29ucy5jb25mLmlkeGAgPSAiQ29uc3VtZXIgQ29uZmlkZW5jZSBJbmRleCIsCiAgICAgICAgICAgICAgICAgICAgIGBpbmZsYXRpb25fcmF0ZWAgPSAiSW5mbGF0aW9uIFJhdGUiLAogICAgICAgICAgICAgICAgICAgICBgY2FtcGFpZ25gID0gIkNvbnRhY3RzIGZvciBDYW1wYWlnbiIsCiAgICAgICAgICAgICAgICAgICAgIGBzcGVudF9vbl9yZXBhaXJzYCA9ICJBbW91bnQgU3BlbnQgb24gUmVwYWlycyIpKSkrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGUxKSArCiAgICBsYWJzKHRpdGxlID0gIkZlYXR1cmUgRGlzdHJpYnV0aW9ucyBCYXNlZCBvbiBDcmVkaXQgQWNjZXB0YW5jZSIsCiAgICAgICAgIHN1YnRpdGxlID0gIkNvbnRpbm91cyBmZWF0dXJlcyIpICsKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMixmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE4LCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OSksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OSksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OSksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoID0wLjgpKQpgYGAKCldlIGZ1cnRoZXIgY29uZHVjdGVkIEFOT1ZBIHRlc3QgZm9yIGFsbCBudW1lcmljIGZlYXR1cmVzIHRvIHNlZSBpZiB0aGVyZSdzIGEgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBpbiBtZWFuIGFnZSwgZm9yIGV4YW1wbGUsIGFjcm9zcyBwZW9wbGUgd2hvIHRha2UgYW5kIGRpZCBub3QgdGFrZSB0aGUgY3JlZGl0LiBUaGUgcmVzdWx0IGlzIGluIGNvbmN1ciB3aXRoIG91ciBleHBlY3RhdGlvbnM6IGFsbCBudW1lcmljIHZhcmlhYmxlcyBhcmUgc2lnbmlmaWNhbnQgcHJlZGljdG9ycy4gCgpgYGB7ciBhbm92YSB0ZXN0fQoKYW5vdmFfdmFyIDwtIGMoImFnZSIsICJwcmV2aW91cyIsICJ1bmVtcGxveV9yYXRlIiwgImNvbnMucHJpY2UuaWR4IiwgImNvbnMuY29uZi5pZHgiLCAiaW5mbGF0aW9uX3JhdGUiLCAic3BlbnRfb25fcmVwYWlycyIsICJjYW1wYWlnbiIpCgpuZXdfbmFtZXMgPC0gYygiYWdlIiA9ICJBZ2UiLAogICAgICAgICAgICAgICAgInByZXZpb3VzIiA9ICJDb250YWN0IGJlZm9yZSBDYW1wYWlnbiIsCiAgICAgICAgICAgICAgICAidW5lbXBsb3lfcmF0ZSIgPSAiVW5lbXBsb3ltZW50IFJhdGUiLAogICAgICAgICAgICAgICAgImNvbnMucHJpY2UuaWR4IiA9ICJDb25zdW1lciBQcmljZSBJbmRleCIsCiAgICAgICAgICAgICAgICAiY29ucy5jb25mLmlkeCIgPSAiQ29uc3VtZXIgQ29uZmlkZW5jZSBJbmRleCIsCiAgICAgICAgICAgICAgICAiaW5mbGF0aW9uX3JhdGUiID0gIkluZmxhdGlvbiBSYXRlIiwKICAgICAgICAgICAgICAgICJzcGVudF9vbl9yZXBhaXJzIiA9ICJBbW91bnQgU3BlbnQgb24gUmVwYWlycyIsCiAgICAgICAgICAgICAgICJjYW1wYWlnbiIgPSAiQ29udGFjdHMgZm9yIENhbXBhaWduIikKCmFub3ZhX3Jlc3VsdHMgPC0gZGF0YS5mcmFtZSgKICAgICAgICAgICAgICAgICAgICAgICAgICAgIERmID0gaW50ZWdlcigpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIFN1bV9TcSA9IG51bWVyaWMoKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBNZWFuX1NxID0gbnVtZXJpYygpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZfdmFsdWUgPSBudW1lcmljKCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgUF9WYWx1ZSA9IG51bWVyaWMoKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQoKZm9yICh2YXIgaW4gYW5vdmFfdmFyKSB7CiAgYW5vdmFfcmVzdWx0IDwtIGFvdihudW1lcmljX3ZhcltbdmFyXV0gfiBudW1lcmljX3ZhciR5KQogIHN1bW1hcnlfZGF0YSA8LSBzdW1tYXJ5KGFub3ZhX3Jlc3VsdClbWzFdXVssIGMoIkRmIiwgIlN1bSBTcSIsICJNZWFuIFNxIiwgIkYgdmFsdWUiLCAiUHIoPkYpIildWzE6MSwgXQogIGFub3ZhX3Jlc3VsdHMgPC0gcmJpbmQoYW5vdmFfcmVzdWx0cywgc3VtbWFyeV9kYXRhKQp9CgoKcm93bmFtZXMoYW5vdmFfcmVzdWx0cykgPC0gbmV3X25hbWVzCgoKYW5vdmFfcmVzdWx0cyAlPiUgCiBrYWJsZSgpICU+JSAKIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUgCiAgICBmb290bm90ZShnZW5lcmFsX3RpdGxlID0gIlxuIiwgZ2VuZXJhbCA9ICJUYWJsZSAyIikKCgpgYGAKCiMgTG9naXN0aWMgUmVncmVzc2lvbgoKV2UgdGhlbiBmaXQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIGNvbnRhaW5pbmcgYWxsIHZhcmlhYmxlcyBpbiBvdXIgZGF0YXNldC4gVG8gZG8gc28sIHdlIHNwbGl0IG91ciBkYXRhIHJhbmRvbWx5IGludG8gNjUlIG9mIHRyYWluaW5nIGRhdGFzZXQgYW5kIDM1JSBvZiB0ZXN0aW5nIGRhdGFzZXQuIFNvbWUgb2Ygb3VyIHZhcmlhYmxlcywgc3VjaCBhcyBMaWVuIGFuZCBFZHVjYXRpb24sIGhhdmUgc2luZ2xlIG9jY3VycmVuY2UgYW5kIHRoZXJlZm9yZSBuZWVkcyB0byBiZSBwdXQgaW50byB0aGUgdHJhaW5pbmcgc2V0IHRvIGF2b2lkIG5ldyBsZXZlbHMgYXBwZWFyaW5nIGluIHRoZSB0ZXN0aW5nIHNldC4gVGhpcyBtb2RlbCBzaG93cyB0aGF0IHNvbWUgaW5kaWNhdG9ycyBhcmUgc2lnbmlmaWNhbnRseSBjb3JyZWxhdGVkIHdpdGggY3JlZGl0IGFjY2VwdGFuY2Ugd2l0aCBwIDwgMC4wNS4gVGhpcyBpbmNsdWRlIGFnZSwgc29tZSBjYXRlZ29yaWVzIG9mIGpvYiwgd2F5cyBvZiBjb250YWN0LCBtb250aCBsYXN0IGNvbnRhY3RlZCwgcHJldmlvdXMgY2FtcGFpZ24gb3V0Y29tZSwgdW5lbXBsb3ltZW50IHJhdGUsIGNvbnN1bWVyIHByaWNlIGFuZCBjb25maWRlbmNlIGluZGljZXMuIEhvd2V2ZXIsIHRoZSBtYWpvcml0eSBvZiBwcmVkaWN0b3JzIGFyZSBub3Qgc2lnbmlmaWNhbnQuIAoKYGBge3IgaG91c2luZyBtb2RlbCwgbWF4LmhlaWdodD0nMjAwcHgnLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKc2V0LnNlZWQoNDc5KQp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IHBhc3RlKGhvdXNpbmckdGF4TGllbiwgaG91c2luZyRlZHVjYXRpb24pICwgcCA9IC42NSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVzID0gMSkKaG91c2luZ1RyYWluIDwtIGhvdXNpbmdbIHRyYWluSW5kZXgsXQpob3VzaW5nVGVzdCAgPC0gaG91c2luZ1stdHJhaW5JbmRleCxdCgpob3VzaW5nTW9kZWwgPC0gZ2xtKHlfbnVtZXJpYyB+IC4sCiAgICAgICAgICAgICAgICAgIGRhdGE9aG91c2luZ1RyYWluICU+JSAKICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGFnZSwgam9iLCBtYXJpdGFsLCBlZHVjYXRpb24sIHRheExpZW4sIG1vcnRnYWdlLCB0YXhiaWxsX2luX3BobCwgY29udGFjdCwgbW9udGgsIGRheV9vZl93ZWVrLCBjYW1wYWlnbiwgcGRheXMsIHBvdXRjb21lLCB1bmVtcGxveV9yYXRlLCBjb25zLmNvbmYuaWR4LCBjb25zLnByaWNlLmlkeCwgaW5mbGF0aW9uX3JhdGUsIHNwZW50X29uX3JlcGFpcnMsIHlfbnVtZXJpYyksIGZhbWlseT0iYmlub21pYWwiIChsaW5rPSJsb2dpdCIpKQoKaG91c2luZ19zdW0gPC0gc3VtbWFyeShob3VzaW5nTW9kZWwpCmNvZWZmaWNpZW50c190YWJsZSA8LSBhcy5kYXRhLmZyYW1lKGhvdXNpbmdfc3VtJGNvZWZmaWNpZW50cykKCmNvZWZmaWNpZW50c190YWJsZSRzaWduaWZpY2FuY2UgPC0gaWZlbHNlKGNvZWZmaWNpZW50c190YWJsZSRgUHIoPnx6fClgIDwgMC4wMDEsICcqKionLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShjb2VmZmljaWVudHNfdGFibGUkYFByKD58enwpYCA8IDAuMDEsICcqKicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShjb2VmZmljaWVudHNfdGFibGUkYFByKD58enwpYCA8IDAuMDUsICcqJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShjb2VmZmljaWVudHNfdGFibGUkYFByKD58enwpYCA8IDAuMSwgJy4nLCAnJykpKSkKCmNvZWZmaWNpZW50c190YWJsZSRwX3ZhbHVlIDwtIHBhc3RlMChyb3VuZChjb2VmZmljaWVudHNfdGFibGUkYFByKD58enwpYCwgZGlnaXRzID0gMyksIGNvZWZmaWNpZW50c190YWJsZSRzaWduaWZpY2FuY2UpCgpjb2VmZmljaWVudHNfdGFibGUgJT4lCiAgc2VsZWN0KC1zaWduaWZpY2FuY2UsIC1gUHIoPnx6fClgKSAlPiUgCiAga2FibGUoYWxpZ24gPSAiciIpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAgJT4lCiAgZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgMyIpCmBgYAoKVGhlIG1vZGVsIGlzIGJlaW5nIHRlc3RlZCBvbiB0aGUgdGVzdCBzZXQgYW5kIGEgbW9zYWljIHBsb3QgaXMgY3JlYXRlZCB0byB2aXN1YWxpemUgdGhlIGNvbmZ1c2lvbiBtYXRyaXguIFVuZm9ydHVuYXRlbHksIHRoZSBtb2RlbCBoYXMgbG93IHNlbnNpdGl2aXR5IHJhdGUgb2Ygb25seSAqKjAuMjMqKi4gU2Vuc2l0aXZpdHkgaXMgdGhlIHRydWUtcG9zaXRpdmUgcmF0ZSwgd2hpY2ggaXMgdGhlIHByb3BvcnRpb24gb2YgYWN0dWFsIHBvc2l0aXZlIGNhc2VzIHRoYXQgd2VyZSBjb3JyZWN0bHkgaWRlbnRpZmllZCBieSB0aGUgbW9kZWwuIFNpbmNlIG91ciBtb2RlbCdzIGFjY3VyYWN5IHJhdGUgaXMgaGlnaCAqKigwLjg5KSoqLCB0aGlzIG1lYW5zIHRoYXQgb3VyIG1vZGVsIG1pc3NpbmcgYSBzaWduaWZpY2FudCBudW1iZXIgb2YgcG9zaXRpdmUgY2FzZXMuIFRoZXNlIGFyZSBjYXNlcyB3aGVyZSB3ZSBjb3JyZWN0bHkgcHJlZGljdCB0aGF0IGhvbWVvd25lcnMgd2lsbCBhY2NlcHQgdGhlIGNyZWRpdCBhbmQgdGhleSBhY3R1YWxseSBkaWQuIAoKYGBge3IgZml0dGluZyBob3VzaW5nIG1vZGVsLCB3YXJuaW5nPUZBTFNFfQoKdGVzdFByb2JzIDwtIGRhdGEuZnJhbWUoT3V0Y29tZSA9IGFzLmZhY3Rvcihob3VzaW5nVGVzdCR5X251bWVyaWMpLAogICAgICAgICAgICAgICAgICAgICAgICBQcm9icyA9IHByZWRpY3QoaG91c2luZ01vZGVsLCBob3VzaW5nVGVzdCwgdHlwZT0gInJlc3BvbnNlIikpCgp0ZXN0UHJvYnMgPC0gCiAgdGVzdFByb2JzICU+JQogIG11dGF0ZShwcmVkT3V0Y29tZSAgPSBhcy5mYWN0b3IoaWZlbHNlKHRlc3RQcm9icyRQcm9icyA+IDAuNSAsIDEsIDApKSkgIAoKIAptb3NhaWNwbG90KGNvbmZ1c2lvbk1hdHJpeCh0ZXN0UHJvYnMkcHJlZE91dGNvbWUsIHRlc3RQcm9icyRPdXRjb21lLCAKICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGl2ZSA9ICIxIikkdGFibGUsIGNvbG9yPWMoIiNEQkMyQ0YiLCIjOUZBMkIyIiksIG1haW4gPSAiTW9zYWljIFBsb3QgZm9yIE9yaWdpbmFsIENvbmZ1c2lvbiBNYXRyaXgiLAogICAgICAgICAgIHhsYWIgPSAiUHJlZGljdGlvbiIsIHlsYWIgPSAiUmVmZXJlbmNlIikKCgpob3VzaW5nbWF0cml4IDwtIGFzLmRhdGEuZnJhbWUoYXMudGFibGUoY29uZnVzaW9uTWF0cml4KHRlc3RQcm9icyRwcmVkT3V0Y29tZSwgdGVzdFByb2JzJE91dGNvbWUsIAogICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gIjEiKSR0YWJsZSkpCmhvdXNpbmdtYXRyaXggJT4lIAogIGthYmxlKCkgJT4lIAogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAgJT4lCiAgZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgNCIpCmBgYAoKIyBJbXByb3ZpbmcgTW9kZWwKCkhvd2V2ZXIsIGp1c3QgYmVjYXVzZSBhIHZhcmlhYmxlIGlzIHNpZ25pZmljYW50IGF0IHByZWRpY3RpbmcgY3JlZGl0IGFjY2VwdGFuY2UgZG9lcyBub3QgbWVhbiB0aGF0IGl0IGlzIGEgcmVsZXZhbnQgb3IgbWVhbmluZ2Z1bCBwcmVkaWN0b3IuIFRvIGltcHJvdmUgdGhpcyBtb2RlbCwgbm90IG9ubHkgZG8gd2UgbmVlZCB0byBwZXJmb3JtIGZlYXR1cmUgZW5naW5lZXJpbmcgdG8gaW5jcmVhc2Ugc2Vuc2l0aXZpdHkgd2hpbGUgbWFpbnRhaW4gb3ZlcmFsbCBhY2N1cmFjeSwgYnV0IGFsc28gZG8gd2UgbmVlZCB0byBleGNsdWRlIGlycmVsZXZhbnQgZmVhdHVyZXMuIFRvIGZpdCBhIG5ldyBtb2RlbDogIAoKLSAqKkZpcnN0bHkqKiwgd2UgY29sbGFwc2VkIHRoZSBjYXRlZ29yaWVzIGluIGVkdWNhdGlvbiBhbmQgZW1wbG95bWVudC4gRm9yIGVkdWNhdGlvbiwgY29uc2lkZXJpbmcgdGhhdCBoYXZpbmcgYSB1bml2ZXJzaXR5IGRlZ3JlZSBzZXJ2ZXMgYSBiZW5jaG1hcmsgaW4gZGV0ZXJtaW5pbmcgY3JlZGl0IGFjY2VwdGFuY2UsIHdlIHJlY29kZSBpdCBpbnRvIHR3byBjYXRlZ29yaWVzOiBgaGlnaHNjaG9vbC5iZWxvd2AgYW5kIGBoaWdoc2Nob29sLmFib3ZlYC4gRm9yIGVtcGxveW1lbnQsIHdlIHJlY29kZSBpdCBpbnRvIGZvdXIgbWFqb3IgY2F0ZWdvcmllczogYHVuZW1wbG95ZWRgLCBgd2hpdGUtY29sbGFyYCwgYGJsdWUtY29sbGFyYCwgYW5kIGBzZWxmLWVtcGxveWVkYCwgY29uc2lkZXJpbmcgdGhhdCBlbXBsb3ltZW50IHN0YXR1cyBhbmQgZW1wbG95bWVudCBzZWN0b3Igd2lsbCBib3RoIGFmZmVjdCBjcmVkaXQgYWNjZXB0YW5jZS4gCgotICoqU2Vjb25kbHkqKiwgd2UgY3JlYXRlIGludGVyYWN0aW9uIGZlYXR1cmVzIGJldHdlZW4gZWR1Y2F0aW9uIGFuZCBlbXBsb3ltZW50IGNvbnNpZGVyaW5nIHRoYXQgZGlmZmVyZW50IGpvYiBtYXkgcmVxdWlyZSBkaWZmZXJlbnQgbGV2ZWxzIG9yIHR5cGUgb2YgZWR1Y2F0aW9uLiAKCi0gKipUaGlyZGx5KiosIHdlIGNvbXB1dGVkIGEgbmV3IHZhcmlhYmxlIGNhbGxlZCBjYW1wYWlnbiBlZmZpY2llbmN5LCB3aGljaCBpcyB0aGUgcmF0aW8gb2YgcHJldmlvdXMgY2FtcGFpZ24gc3VjY2Vzc2VzIHRvIHRoZSBudW1iZXIgb2YgY29udGFjdHMgbWFkZSwgVGhpcyB3aWxsIGluZGljYXRlIHRoZSBlZmZpY2llbmN5IG9mIHByZXZpb3VzIGNhbXBhaWducyBpbiB0ZXJtcyBvZiBzdWNjZXNzZnVsIGNyZWRpdCBhY2NlcHRhbmNlLiBXZSBhbHNvIGNvbXB1dGVkIGEgY29tcG9zaXRlIGZlYXR1cmUgcmVwcmVzZW50aW5nIGVjb25vbWljIGNvbmRpdGlvbnMgYmFzZWQgb24gYHVuZW1wbG95X3JhdGVgLCBgY29ucy5wcmljZS5pZHhgLCBgY29ucy5jb25mLmlkeGAsIGFuZCBgaW5mbGF0aW9uX3JhdGVgLgoKLSAqKkZvdXJ0aGx5KiosIGNhcmVmdWxseSByZXZpZXdpbmcgdGhlIGRhdGFzZXQgbGVhZHMgdXMgdG8gcmVhbGl6ZSB0aGF0IGl0IGlzIGltYmFsYW5jZS4gVXN1YWxseSwgaW1iYWxhbmNlZCBkYXRhc2V0cyBjYW4gbGVhZCB0aGUgbW9kZWwgdG8gYmUgYmlhc2VkIHRvd2FyZHMgdGhlIG1ham9yaXR5IGNsYXNzLCBjYXVzaW5nIGl0IHRvIHBlcmZvcm0gcG9vcmx5IG9uIHRoZSBtaW5vcml0eSBjbGFzcy4gSW4gb3VyIGNhc2UsIGEgc2lnbmlmaWNhbnQgbnVtYmVyIG9mIGF0dHJpYnV0ZXMgd2UgaGF2ZSBhcmUgY2FzZXMgd2hlbiBob21lb3duZXIgZGVjbGluZWQgdGF4IGNyZWRpdC4gVGhpcyBtYWtlcyBmZWF0dXJlIHdlaWdodGluZyBleHRyYSBpbXBvcnRhbnQuIEJ5IGFzc2lnbmluZyBhcHByb3ByaWF0ZSB3ZWlnaHRzIHRvIGZlYXR1cmVzLCB3ZSBjYW4gZW5zdXJlIHRoYXQgdGhlIG1vZGVsIGdpdmVzIHN1ZmZpY2llbnQgaW1wb3J0YW5jZSB0byB0aGUgbWlub3JpdHkgY2xhc3MsIGhlbHBpbmcgaXQgbGVhcm4gdGhlIHVuZGVybHlpbmcgcGF0dGVybnMgaW4gYm90aCBjbGFzc2VzIG1vcmUgZWZmZWN0aXZlbHkuCgotICoqRmluYWxseSoqLCB3ZSBleGNsdWRlZCB2YXJpYWJsZSBgTW9udGggd2UgbGFzdCBjb250YWN0ZWQgaW5kaXZpZHVhbGAsIGBEYXkgb2YgdGhlIHdlZWsgd2UgbGFzdCBjb250YWN0ZWQgaW5kaXZpZHVhbGAsIGBMaWVuYCBhbmQgYG1hcml0YWxgLCBub3QgbWVyZWx5IGJlY2F1c2UgdGhlc2UgdmFyaWFibGVzIGFyZSBub3Qgc2lnbmlmaWNhbnQgcHJlZGljdG9ycywgYnV0IGJlY2F1c2UgdGhlIGZvcm1lciB0d28gZmVhdHVyZXMgd29uJ3QgYmUgcmVsZXZhbnQgdG8gY3JlZGl0IGFjY2VwdGFuY2UsIHZhcmlhYmxlIGxpZW4gY29udGFpbnMgdG9vIG1hbnkgdW5rbm93biB2YWx1ZXMsIGFuZCB0aGVyZSBtaWdodCBiZSBjb2xpbmVhcml0eSBiZXR3ZWVuIG1hcnRpYWwsIGFnZSwgam9iLCBhbmQgZWR1Y2F0aW9uLiAKCgpgYGB7ciBmZWF0dXJlIGVuZ2luZWVyaW5nfQoKaG91c2luZyA8LSBob3VzaW5nICU+JSAKICBtdXRhdGUoZWR1ID0gY2FzZV93aGVuKAogICAgZWR1Y2F0aW9uID09ICJoaWdoLnNjaG9vbCIgfCBlZHVjYXRpb24gPT0gInByb2Zlc3Npb25hbC5jb3Vyc2UiIHwgZWR1Y2F0aW9uID09ICJ1bml2ZXJzaXR5LmRlZ3JlZSIgfiAiaGlnaHNjaG9vbC5hYm92ZSIsIAogICAgVFJVRSB+ICJoaWdoc2Nob29sLmJlbG93IiksCiAgICBlbXBsb3ltZW50ID0gY2FzZV93aGVuKAogICAgICBqb2IgPT0gInNlbGYtZW1wbG95ZWQiIH4gInNlbGYtZW1wbG95ZWQiLCAKICAgICAgam9iID09ICJyZXRpcmVkIiB8IGpvYiA9PSAidW5lbXBsb3llZCIgfCBqb2IgPT0gInN0dWRlbnQiIHwgam9iID09ICJ1bmtub3duIiB8IGpvYiA9PSAiaG91c2VtYWlkIiB+ICJub3QtZW1wbG95ZWQiLAogICAgICBqb2IgPT0gImJsdWUtY29sbGFyIiB+ICJibHVlLWNvbGxhciIsIAogICAgICBUUlVFIH4gIndoaXRlLWNvbGxhciIpKQogIApob3VzaW5nJGpvYl9lZHVjYXRpb25faW50ZXJhY3Rpb24gPC0gcGFzdGUoaG91c2luZyRlZHUsIGhvdXNpbmckZW1wbG95bWVudCwgc2VwPSJfIikKaG91c2luZyRjYW1wYWlnbl9lZmZpY2llbmN5IDwtIGhvdXNpbmckcHJldmlvdXMgLyBob3VzaW5nJGNhbXBhaWduCmhvdXNpbmckZWNvbm9taWNfY29uZGl0aW9ucyA8LSAoaG91c2luZyR1bmVtcGxveV9yYXRlICsgaG91c2luZyRjb25zLnByaWNlLmlkeCArIGhvdXNpbmckY29ucy5jb25mLmlkeCAtIGhvdXNpbmckaW5mbGF0aW9uX3JhdGUpIC8gNAppbWJhbGFuY2Vfd2VpZ2h0IDwtIHN1bShob3VzaW5nJHlfbnVtZXJpYyA9PSAwKSAvIHN1bShob3VzaW5nJHlfbnVtZXJpYyA9PSAxKQoKYGBgCgpPdXIgaW1wcm92ZWQgbW9kZWwgc2hvd3MgdGhhdCBtb3N0IGluZGljYXRvcnMgYXJlIHNpZ25pZmljYW50bHkgY29ycmVsYXRlZCB3aXRoIGNyZWRpdCBhY2NlcHRhbmNlIHdpdGggcCA8IDAuMDUsIGV4Y2VwdCBmb3Igc29tZSBjYXRlZ29yaWVzIG9mIGpvYi1lZHVjYXRpb24gaW50ZXJhY3Rpb24gYXMgd2VsbCBhcyBgcGRheXNgLCB3aGljaCBpcyB3aGV0aGVyIGFuIGluZGl2aWR1YWwgaGFzIGJlZW4gY29udGFjdGVkIGJlZm9yZS4gVGhpcyBpcyBwcm9iYWJseSBiZWNhdXNlIGBwZGF5YCBpcyBjb3JyZWxhdGVkIHdpdGggYHBvdXRjb21lYCwgYW5vdGhlciB2YXJpYWJsZSBpbmRpY2F0aW5nIHN1Y2Nlc3Mgb2YgcHJldmlvdXMgY29udGFjdC4gCgpgYGB7ciBpbXByb3ZlZCBtb2RlbCwgd2FybmluZz1GQUxTRX0KCnNldC5zZWVkKDQ3OSkKdHJhaW5JbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGhvdXNpbmckeSAsIHAgPSAuNjUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lcyA9IDEpCmhvdXNpbmdUcmFpbiA8LSBob3VzaW5nWyB0cmFpbkluZGV4LF0KaG91c2luZ1Rlc3QgIDwtIGhvdXNpbmdbLXRyYWluSW5kZXgsXQoKaW1wcm92ZWRNb2RlbCA8LSBnbG0oeV9udW1lcmljIH4gLiwKICAgICAgICAgICAgICAgICAgZGF0YT1ob3VzaW5nVHJhaW4gJT4lIAogICAgICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoeV9udW1lcmljLCBhZ2UsIGpvYl9lZHVjYXRpb25faW50ZXJhY3Rpb24sIG1vcnRnYWdlLCBwZGF5cywgY29udGFjdCwgcG91dGNvbWUsIGVjb25vbWljX2NvbmRpdGlvbnMsIHByZXZpb3VzLCBjYW1wYWlnbl9lZmZpY2llbmN5KSwgIHdlaWdodHMgPSBpZmVsc2UoeV9udW1lcmljID09IDEsIGltYmFsYW5jZV93ZWlnaHQsIDEpLCAKICAgICAgICAgICAgICAgICAgZmFtaWx5PSJiaW5vbWlhbCIgKGxpbms9ImxvZ2l0IikpCgppbXByb3ZlZF9zdW0gPC0gc3VtbWFyeShpbXByb3ZlZE1vZGVsKQpjb2VmZmljaWVudHNfdGFibGUgPC0gYXMuZGF0YS5mcmFtZShpbXByb3ZlZF9zdW0kY29lZmZpY2llbnRzKQoKY29lZmZpY2llbnRzX3RhYmxlJHNpZ25pZmljYW5jZSA8LSBpZmVsc2UoY29lZmZpY2llbnRzX3RhYmxlJGBQcig+fHp8KWAgPCAwLjAwMSwgJyoqKicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGNvZWZmaWNpZW50c190YWJsZSRgUHIoPnx6fClgIDwgMC4wMSwgJyoqJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGNvZWZmaWNpZW50c190YWJsZSRgUHIoPnx6fClgIDwgMC4wNSwgJyonLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGNvZWZmaWNpZW50c190YWJsZSRgUHIoPnx6fClgIDwgMC4xLCAnLicsICcnKSkpKQoKY29lZmZpY2llbnRzX3RhYmxlJHBfdmFsdWUgPC0gcGFzdGUwKHJvdW5kKGNvZWZmaWNpZW50c190YWJsZSRgUHIoPnx6fClgLCBkaWdpdHMgPSAzKSwgY29lZmZpY2llbnRzX3RhYmxlJHNpZ25pZmljYW5jZSkKCmNvZWZmaWNpZW50c190YWJsZSAlPiUKICBzZWxlY3QoLXNpZ25pZmljYW5jZSwgLWBQcig+fHp8KWApICU+JSAKICBrYWJsZShhbGlnbiA9ICJyIikgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIikpICAlPiUKICBmb290bm90ZShnZW5lcmFsX3RpdGxlID0gIlxuIiwgZ2VuZXJhbCA9ICJUYWJsZSA1IikKYGBgCgoKV2UgdGVzdGVkIG91ciBtb2RlbCBhbmQgYSBtb3NhaWMgcGxvdCBpcyBjcmVhdGVkIHRvIHZpc3VhbGl6ZSB0aGUgY29uZnVzaW9uIG1hdHJpeC4gTm93LCB0aGUgbW9kZWwgaGFzIG11Y2ggaGlnaGVyIHNlbnNpdGl2aXR5IHJhdGUgb2YgKiowLjYxKiouIFRoZSBhY2N1cmFjeSByYXRlIGRyb3BwZWQgdG8gKiowLjcwKiosIGJ1dCBpcyBzdGlsbCBhY2NlcHRhYmxlLiBUaGlzIG1lYW5zIHRoYXQgbm93LCBvdXIgbW9kZWwgaXMgYWJsZSB0byBjYXB0dXJlIGEgc2lnbmlmaWNhbnRseSBtb3JlIG51bWJlciBvZiBwb3NpdGl2ZSBjYXNlcy4gCgpgYGB7ciBmaXQgaW1wcm92ZWQgbW9kZWwsIHdhcm5pbmc9RkFMU0V9CgoKdGVzdFByb2JzIDwtIGRhdGEuZnJhbWUoT3V0Y29tZSA9IGFzLmZhY3Rvcihob3VzaW5nVGVzdCR5X251bWVyaWMpLAogICAgICAgICAgICAgICAgICAgICAgICBQcm9icyA9IHByZWRpY3QoaW1wcm92ZWRNb2RlbCwgaG91c2luZ1Rlc3QsIHR5cGU9ICJyZXNwb25zZSIpKQoKdGVzdFByb2JzIDwtIAogIHRlc3RQcm9icyAlPiUKICBtdXRhdGUocHJlZE91dGNvbWUgID0gYXMuZmFjdG9yKGlmZWxzZSh0ZXN0UHJvYnMkUHJvYnMgPiAwLjUgLCAxLCAwKSkpICAKCgptb3NhaWNwbG90KGNvbmZ1c2lvbk1hdHJpeCh0ZXN0UHJvYnMkcHJlZE91dGNvbWUsIHRlc3RQcm9icyRPdXRjb21lLCAKICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGl2ZSA9ICIxIikkdGFibGUsIGNvbG9yPWMoIiNEQkMyQ0YiLCIjOUZBMkIyIiksIG1haW4gPSAiTW9zYWljIFBsb3QgZm9yIEltcHJvdmVkIENvbmZ1c2lvbiBNYXRyaXgiLAogICAgICAgICAgIHhsYWIgPSAiUHJlZGljdGlvbiIsIHlsYWIgPSAiUmVmZXJlbmNlIikKCgppbXByb3ZlZG1hdHJpeCA8LSBhcy5kYXRhLmZyYW1lKGFzLnRhYmxlKGNvbmZ1c2lvbk1hdHJpeCh0ZXN0UHJvYnMkcHJlZE91dGNvbWUsIHRlc3RQcm9icyRPdXRjb21lLCAKICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGl2ZSA9ICIxIikkdGFibGUpKQoKaW1wcm92ZWRtYXRyaXggJT4lIAogIGthYmxlKCkgJT4lIAogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAgJT4lCiAgZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgNiIpCgpgYGAKV2UgY29tcHV0ZWQgdGhlIEFyZWEgVW5kZXIgdGhlIFJlY2VpdmVyIE9wZXJhdGluZyBDaGFyYWN0ZXJpc3RpYyBDdXJ2ZSAoQVVDLVJPQykgbWV0cmljIHRvIGZ1cnRoZXIgZXZhbHVhdGUgdGhlIHBlcmZvcm1hbmNlIG9mIG91ciBpbXByb3ZlZCBtb2RlbC4gVGhlIEFVQyB2YWx1ZSByYW5nZXMgZnJvbSAwIHRvIDEsIHdoZXJlIDAuNSBtZWFucyBhIG1vZGVsIHdpdGggbm8gZGlzY3JpbWluYXRvcnkgcG93ZXIgKGl0IHBlcmZvcm1zIGFzIHdlbGwgYXMgcmFuZG9tIGNoYW5jZSksIGFuZCAxIG1lYW5zIGEgcGVyZmVjdCBtb2RlbCAoaXQgcGVyZmVjdGx5IGRpc3Rpbmd1aXNoZXMgYmV0d2VlbiBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgY2FzZXMpLkdlbmVyYWxseSwgYW4gQVVDIGJldHdlZW4gMC43IGFuZCAwLjggaXMgY29uc2lkZXJlZCBhY2NlcHRhYmxlIGFuZCBpbmRpY2F0ZXMgYSBmYWlyIHRvIGdvb2QgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsLiBXaGlsZSB0aGVyZSBpcyByb29tIGZvciBpbXByb3ZlbWVudCwgYSBzY29yZSBvZiAwLjc0IHN1Z2dlc3RzIHRoYXQgdGhlIG1vZGVsIGlzIGNhcHR1cmluZyBpbXBvcnRhbnQgcGF0dGVybnMgaW4gdGhlIGRhdGEgYW5kIG1ha2luZyBwcmVkaWN0aW9ucyBiZXR0ZXIgdGhhbiByYW5kb20gZ3Vlc3NpbmcuCgpgYGB7ciBhdWMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CgphdWModGVzdFByb2JzJE91dGNvbWUsIHRlc3RQcm9icyRQcm9icykKCmBgYAoKYGBge3IgYXVjIHBsb3QsIHdhcm5pbmc9RkFMU0V9CmdncGxvdCh0ZXN0UHJvYnMsIGFlcyhkID0gYXMubnVtZXJpYyhPdXRjb21lKSwgbSA9IFByb2JzKSkgKwogIGdlb21fcm9jKG4uY3V0cyA9IDUwLCBsYWJlbHMgPSBGQUxTRSwgY29sb3VyID0gIiMyRTQ3NTYiKSArCiAgICBsYWJzKHRpdGxlID0gIlJPQyBDdXJ2ZSBmb3IgSW1wcm92ZWQgTW9kZWwiKSArCiAgc3R5bGVfcm9jKHRoZW1lID0gdGhlbWVfZ3JleSkgKwogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgc2l6ZSA9IDEuNSwgY29sb3IgPSAiI0RCQzJDRiIpICsKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCiAgCmBgYAoKCiMgQ3Jvc3MgVmFsaWRhdGlvbgoKTmV4dCwgd2UgcGVyZm9ybWVkIGNyb3NzIHZhbGlkYXRpb24gdGVzdCBmb3IgYm90aCBvZiBvcmlnaW5hbCBtb2RlbCBhbmQgaW1wcm92ZWQgbW9kZWwuIFRoZSB0cmFpbkNvbnRyb2wgcGFyYW1ldGVyIGlzIHNldCB0byBydW4gMTAwIGstZm9sZHMgYW5kIHRvIG91dHB1dCBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcywgY2xhc3NQcm9icywgZm9yIHR3byBjbGFzc2VzLCB0aG9zZSB3aG8gYWNjZXB0ZWQgdGhlIGNyZWRpdCBhbmQgdGhvc2Ugd2hvIGRpZCBub3QuIEFkZGl0aW9uYWwgcGFyYW1ldGVycyBvdXRwdXQgQVVDICh0aGUgdHJhaW4gZnVuY3Rpb24gcmVmZXJzIHRvIHRoaXMgYXMg4oCYUk9D4oCZKSBhbmQgY29uZnVzaW9uIG1ldHJpY3MgZm9yIGVhY2ggZm9sZC4gVGhlIHRocmVlIG1ldHJpY3MgaW4gdGhlIGN2Rml0IG91dHB1dCBhcmUgZm9yIG1lYW4gQVVDLCBTZW5zaXRpdml0eSwgYW5kIFNwZWNpZmljaXR5IGFjcm9zcyBhbGwgMTAwIGZvbGRzLiBIZXJlLCB0aGUgUk9DIGZvciBvdXIgb3JpZ2luYWwgbW9kZWwgYW5kIGltcHJvdmVkIG1vZGVsIGFyZSAwLjc2IGFuZCAwLjcwLCB0aGUgU2Vuc2l0aXZpdHkgYXJlIDAuOTcgYW5kIDAuOTgsIHdoaWxlIHRoZSBTcGVjaWZpY2l0eSBhcmUgMC4yMSBhbmQgMC4xNyByZXNwZWN0aXZlbHkuIAoKYGBge3IgY3Jvc3MgdmFsaWRhdGlvbiwgd2FybmluZz1GQUxTRX0KY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTAwLCBjbGFzc1Byb2JzPVRSVUUsIHN1bW1hcnlGdW5jdGlvbj10d29DbGFzc1N1bW1hcnkpCgpjdkZpdF9vcmlnaW5hbCA8LSB0cmFpbih5IH4gLiwKICAgICAgICAgICAgICAgICAgZGF0YT1ob3VzaW5nICU+JSAKICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGFnZSwgam9iLCBtYXJpdGFsLCBlZHVjYXRpb24sIHRheExpZW4sIG1vcnRnYWdlLCB0YXhiaWxsX2luX3BobCwgY29udGFjdCwgbW9udGgsIGRheV9vZl93ZWVrLCBjYW1wYWlnbiwgcGRheXMsIHBvdXRjb21lLCB1bmVtcGxveV9yYXRlLCBjb25zLmNvbmYuaWR4LCBjb25zLnByaWNlLmlkeCwgaW5mbGF0aW9uX3JhdGUsIHNwZW50X29uX3JlcGFpcnMsIHkpLCAKICAgICAgICAgICAgICAgIG1ldGhvZD0iZ2xtIiwgZmFtaWx5PSJiaW5vbWlhbCIsCiAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldHJpYz0iUk9DIiwgdHJDb250cm9sID0gY3RybCkKY3ZGaXRfaW1wcm92ZWQgPC0gdHJhaW4oeSB+IC4sCiAgICAgICAgICAgICAgICAgIGRhdGE9aG91c2luZyAlPiUgCiAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdCh5LCBhZ2UsIGpvYl9lZHVjYXRpb25faW50ZXJhY3Rpb24sIG1vcnRnYWdlLCBwZGF5cywgY29udGFjdCwgcG91dGNvbWUsIGVjb25vbWljX2NvbmRpdGlvbnMsIHByZXZpb3VzLCBjYW1wYWlnbl9lZmZpY2llbmN5KSwgICB3ZWlnaHRzID0gaWZlbHNlKHkgPT0gIlllcyIsIGltYmFsYW5jZV93ZWlnaHQsIDEpLAogICAgICAgICAgICAgICAgbWV0aG9kPSJnbG0iLCBmYW1pbHk9ImJpbm9taWFsIiwKICAgICAgICAgICAgICAgIG1ldHJpYz0iUk9DIiwgdHJDb250cm9sID0gY3RybCkKYGBgCgpUaGUgZmlndXJlIGJlbG93IHBsb3RzIHRoZSBkaXN0cmlidXRpb24gb2YgQVVDLCBTZW5zaXRpdml0eSwgYW5kIFNwZWNpZmljaXR5IGFjcm9zcyB0aGUgMTAwIGZvbGRzLiBUaGUgdGlnaHRlciBlYWNoIGRpc3RyaWJ1dGlvbiBpcyB0byBpdHMgbWVhbiwgdGhlIG1vcmUgZ2VuZXJhbGl6YWJsZSB0aGUgbW9kZWwuIEJvdGggbW9kZWxzIGdlbmVyYWxpemVzIHdlbGwgdG8gc2Vuc2l0aXZpdHkgLS0gdGhlIHJhdGUgaXQgY29ycmVjdGx5IHByZWRpY3RzIGNyZWRpdCBhY2NlcHRhbmNlLCBpbnN0ZWFkIG9mIHNwZWNpZmljaXR5LiBJdCBzZWVtcyBvdXIgd291bGQtYmUgZGVjaXNpb24tbWFraW5nIHRvb2wgaXMgaW5jb25zaXN0ZW50IGluIGhvdyBpdCBwcmVkaWN0cyB0aGUgY3JlZGl0IGFjY2VwdGFuY2UuIFRoaXMgaW5jb25zaXN0ZW5jeSAgd2lsbCBoYXZlIGEgZGlyZWN0IGVmZmVjdCBvbiB0aGUgbWFya2V0aW5nIGFuZCByZXNvdXJjZSBhbGxvY2F0aW9uIHByb2Nlc3Mgc2hvdWxkIHRoaXMgYWxnb3JpdGhtIGJlIHB1dCBpbnRvIHByb2R1Y3Rpb24uCgpgYGB7ciBDViBwbG90IG9yaWdpbmFsLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKZHBseXI6OnNlbGVjdChjdkZpdF9vcmlnaW5hbCRyZXNhbXBsZSwgLVJlc2FtcGxlKSAlPiUKICBnYXRoZXIobWV0cmljLCB2YWx1ZSkgJT4lCiAgbGVmdF9qb2luKGdhdGhlcihjdkZpdF9vcmlnaW5hbCRyZXN1bHRzWzI6NF0sIG1ldHJpYywgbWVhbikpICU+JQogIGdncGxvdChhZXModmFsdWUpKSArIAogICAgZ2VvbV9oaXN0b2dyYW0oYmlucz0yMCwgZmlsbCA9ICIjREJDMkNGIikgKwogICAgZmFjZXRfd3JhcCh+bWV0cmljKSArCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVhbiksIGNvbG91ciA9ICIjMkU0NzU2IiwgbGluZXR5cGUgPSAyLCBzaXplID0gMC42KSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxKSkgKwogICAgbGFicyh4PSJHb29kbmVzcyBvZiBGaXQiLCB5PSJDb3VudCIsIHRpdGxlPSJDViBHb29kbmVzcyBvZiBGaXQgTWV0cmljcyBPcmlnaW5hbCBNb2RlbCIsCiAgICAgICAgIHN1YnRpdGxlID0gIkFjcm9zcy1mb2xkIG1lYW4gcmVwcmVudGVkIGFzIGRvdHRlZCBsaW5lcyIpICsgCiAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTkpLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aCA9MC44KSkKICAKYGBgCgoKYGBge3IgQ1YgcGxvdCBpbXByb3ZlZCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KCmRwbHlyOjpzZWxlY3QoY3ZGaXRfaW1wcm92ZWQkcmVzYW1wbGUsIC1SZXNhbXBsZSkgJT4lCiAgZ2F0aGVyKG1ldHJpYywgdmFsdWUpICU+JQogIGxlZnRfam9pbihnYXRoZXIoY3ZGaXRfaW1wcm92ZWQkcmVzdWx0c1syOjRdLCBtZXRyaWMsIG1lYW4pKSAlPiUKICBnZ3Bsb3QoYWVzKHZhbHVlKSkgKyAKICAgIGdlb21faGlzdG9ncmFtKGJpbnM9MjAsIGZpbGwgPSAiI0RCQzJDRiIpICsKICAgIGZhY2V0X3dyYXAofm1ldHJpYykgKwogICAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1lYW4pLCBjb2xvdXIgPSAiIzJFNDc1NiIsIGxpbmV0eXBlID0gMiwgc2l6ZSA9IDAuOCkgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMSkpICsKICAgIGxhYnMoeD0iR29vZG5lc3Mgb2YgRml0IiwgeT0iQ291bnQiLCB0aXRsZT0iQ1YgR29vZG5lc3Mgb2YgRml0IE1ldHJpY3MgZm9yIEltcHJvdmVkIE1vZGVsIiwKICAgICAgICAgc3VidGl0bGUgPSAiQWNyb3NzLWZvbGQgbWVhbiByZXByZW50ZWQgYXMgZG90dGVkIGxpbmVzIikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCgpgYGAKCgojIENvc3QgQmVuZWZpdCBBbmFseXNpcwoKSW4gdGhpcyBmaW5hbCBzZWN0aW9uLCB3ZSBjb25kdWN0ZWQgYSBjb3N0IGJlbmVmaXQgYW5hbHlzaXMgdG8gaGVscCBIQ0QgbWF4aW1pemUgdGhlaXIgcmV2ZW51ZS4gSWYgd2UgcHJlZGljdCB0aGF0IGEgaG91c2Vob2xkIHdpbGwgdGFrZSB0aGUgY3JlZGl0LCB0aGVuIEhDRCBhbGxvY2F0ZXMgKG9uIGF2ZXJhZ2UpIFwkMiw4NTAgcGVyIGhvbWVvd25lciB3aGljaCBpbmNsdWRlcyBzdGFmZiBhbmQgcmVzb3VyY2VzIHRvIGZhY2lsaXRhdGUgbWFpbGVycywgcGhvbmUgY2FsbHMsIGFuZCBpbmZvcm1hdGlvbi9jb3Vuc2VsaW5nIHNlc3Npb25zIGF0IHRoZSBIQ0Qgb2ZmaWNlcy4gQW5kIGFtb25nIHRob3NlIGVsaWdpYmxlIGhvbWVvd25lcnMsIDI1JSBvZiB0aGVtIHdpbGwgdGFrZSB0aGUgXCQ1MDAwIGNyZWRpdC4gVGhpcyB3aWwgbGVhZCB0byBhICQxMCwwMDAgcHJlbWl1bSwgb24gYXZlcmFnZSwgZm9yIGVhY2ggaG91c2VzIHRoYXQgdHJhbnNhY3RlZCBhZnRlciB0YWtpbmcgdGhlIGNyZWRpdCBhbmQgYW4gYWdncmVnYXRlIHByZW1pdW0gb2YgXCQ1NiwwMDAsIG9uIGF2ZXJhZ2UsIGZvciBob21lcyBzdXJyb3VuZGluZyB0aGUgcmVwYWlyZWQgaG9tZS4gQmFzZWQgb24gdGhpcywgd2Ugb3V0bGluZWQgdGhlIGNvc3QvYmVuZWZpdCBmb3IgZWFjaCBzY2VuYXJpbyBpbiB0aGUgY29uZnVzaW9uIG1hdHJpeCwgd2hlcmUgYENvdW50YCByZWZlcnMgdG8gdGhlIG51bWJlciBvZiBvY2N1cnJlbmNlcyBvZiBlYWNoIHNjZW5hcmlvOiAKCi0gKipUcnVlIFBvc2l0aXZlKio6ICBXZSBwcmVkaWN0ZWQgY29ycmVjdGx5IGhvbWVvd25lciB3b3VsZCBlbnRlciBjcmVkaXQgcHJvZ3JhbTsgYWxsb2NhdGVkIHRoZSBtYXJrZXRpbmcgcmVzb3VyY2VzLCBhbmQgMjUlIHVsdGltYXRlbHkgYWNoaWV2ZWQgdGhlIGNyZWRpdDogJC0yODUwICogQ291bnQgLSA1MDAwICogQ291bnQgKiAwLjI1ICsgKDEwMDAwICsgNTYwMDApICogQ291bnQkCgotICoqVHJ1ZSBOZWdhdGl2ZSoqOiBXZSBwcmVkaWN0ZWQgY29ycmVjdGx5IGhvbWVvd25lciB3b3VsZCBub3QgdGFrZSB0aGUgY3JlZGl0LCBubyBtYXJrZXRpbmcgcmVzb3VyY2VzIHdlcmUgYWxsb2NhdGVkLCBhbmQgbm8gY3JlZGl0IHdhcyBhbGxvY2F0ZWQ6ICQwKkNvdW50JAoKLSAqKkZhbHNlIFBvc2l0aXZlKio6IFdlIHByZWRpY3RlZCBpbmNvcnJlY3RseSBob21lb3duZXIgd291bGQgdGFrZSB0aGUgY3JlZGl0OyBhbGxvY2F0ZWQgbWFya2V0aW5nIHJlc291cmNlczsgbm8gY3JlZGl0IGFsbG9jYXRlZDogJC0yODUwICogQ291bnQkIAoKLSAqKkZhbHNlIE5lZ2F0aXZlKio6IFdlIHByZWRpY3RlZCB0aGF0IGEgaG9tZW93bmVyIHdvdWxkIG5vdCB0YWtlIHRoZSBjcmVkaXQgYnV0IHRoZXkgZGlkLiBUaGVzZSBhcmUgbGlrZWx5IGhvbWVvd25lcnMgd2hvIHNpZ25lZCB1cCBmb3IgcmVhc29ucyB1bnJlbGF0ZWQgdG8gdGhlIG1hcmtldGluZyBjYW1wYWlnbi4gVGh1cywgd2Ug4oCYMCBvdXTigJkgdGhpcyBjYXRlZ29yeS4gCgpXZSBtYXkgc2VlIHRoYXQKCmBgYHtyIGNvc3QgYmVuZWZpdCBhbmFseXNpc30KCmNvc3RfYmVuZWZpdF90YWJsZSA8LQogICB0ZXN0UHJvYnMgJT4lCiAgICAgIGNvdW50KHByZWRPdXRjb21lLCBPdXRjb21lKSAlPiUKICAgICAgc3VtbWFyaXplKFRydWVfTmVnYXRpdmUgPSBzdW0obltwcmVkT3V0Y29tZT09MCAmIE91dGNvbWU9PTBdKSwKICAgICAgICAgICAgICAgIFRydWVfUG9zaXRpdmUgPSBzdW0obltwcmVkT3V0Y29tZT09MSAmIE91dGNvbWU9PTFdKSwKICAgICAgICAgICAgICAgIEZhbHNlX05lZ2F0aXZlID0gc3VtKG5bcHJlZE91dGNvbWU9PTAgJiBPdXRjb21lPT0xXSksCiAgICAgICAgICAgICAgICBGYWxzZV9Qb3NpdGl2ZSA9IHN1bShuW3ByZWRPdXRjb21lPT0xICYgT3V0Y29tZT09MF0pKSAlPiUKICAgICAgIGdhdGhlcihWYXJpYWJsZSwgQ291bnQpICU+JQogICAgICAgbXV0YXRlKFJldmVudWUgPQogICAgICAgICAgICAgICBpZmVsc2UoVmFyaWFibGUgPT0gIlRydWVfTmVnYXRpdmUiLCBDb3VudCAqIDAsCiAgICAgICAgICAgICAgIGlmZWxzZShWYXJpYWJsZSA9PSAiVHJ1ZV9Qb3NpdGl2ZSIsIDYxOTAwICogQ291bnQsCiAgICAgICAgICAgICAgIGlmZWxzZShWYXJpYWJsZSA9PSAiRmFsc2VfTmVnYXRpdmUiLCAwICogQ291bnQsCiAgICAgICAgICAgICAgIGlmZWxzZShWYXJpYWJsZSA9PSAiRmFsc2VfUG9zaXRpdmUiLCAoLTI4NTApICogQ291bnQsIDApKSkpKQogICAKa2FibGUoY29zdF9iZW5lZml0X3RhYmxlKSAlPiUgCmthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAgJT4lCiAgZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgNyIpCgpgYGAKCkJlY2F1c2UgYSBkaWZmZXJlbnQgY29uZnVzaW9uIG1hdHJpeCwgc2V0IG9mIGVycm9ycyBhbmQgY29zdC9iZW5lZml0IGNhbGN1bGF0aW9uIGV4aXN0cyBmb3IgZWFjaCB0aHJlc2hvbGQsIHdlIG5lZWQgdG8gY29tcHV0ZSBhbiBpZGVhbCB0aHJlc2hvbGQgdGhhdCByZXR1cm5zIHRoZSBncmVhdGVzdCBjb3N0L2JlbmVmaXQuIFRoZSBmaWd1cmUgYmVsb3cgdmlzdWFsaXplcyBob3cgZWFjaCBvZiB0aGUgZm91ciBzY2VuYXJpb3MgaW4gb3VyIGNvbmZ1c2lvbiBtYXRyaXggY2hhbmdlIGFsb25nIHdpdGggdGhlIHRocmVzaG9sZC4gQXMgd2UgaW5jcmVhc2UgdGhlIHRocmVzaG9sZCwgb3VyIHRydWUgcG9zaXRpdmUgYW5kIGZhbHNlIHBvc2l0aXZlIHJhdGUgYm90aCBpbmNyZWFzZXMuIFRoaXMgbWVhbnMgdGhhdCBvdXIgbGlrZWxpaG9vZCBvZiBnZXR0aW5nIGJlbmVmaXRzIG9yIHNlZWluZyBjb3N0cyBib3RoIGluY3JlYXNlcy4gVGhlIGdvYWwgaXMgdGhlcmVmb3JlIHRvIGZpbmQgYW4gb3B0aW1hbCB0aHJlc2hvbGQgdGhhdCBtYXhpbWl6ZXMgYmVuZWZpdHMgYW5kIG1pbmltaXplcyBjb3N0cyBhbG9uZyB0aGlzIGxpbmUuIAoKCmBgYHtyIHRocmVzaG9sZCB0YWJsZSwgaW5jbHVkZT1GQUxTRX0KCnggPSAuMDEKYWxsX3RocmVzaG9sZCA8LSBkYXRhLmZyYW1lKCkKCndoaWxlICh4IDw9IDEpIHsKdGhyZXNob2xkPC0gCiAgdGVzdFByb2JzICU+JQogIG11dGF0ZShwcmVkT3V0Y29tZSAgPSBhcy5mYWN0b3IoaWZlbHNlKHRlc3RQcm9icyRQcm9icyA+IHggLCAxLCAwKSkpICU+JSAKICBjb3VudChwcmVkT3V0Y29tZSwgT3V0Y29tZSkgJT4lIAogIHN1bW1hcml6ZShUcnVlX05lZ2F0aXZlID0gc3VtKG5bcHJlZE91dGNvbWU9PTAgJiBPdXRjb21lPT0wXSksCiAgICAgICAgICAgICAgICBUcnVlX1Bvc2l0aXZlID0gc3VtKG5bcHJlZE91dGNvbWU9PTEgJiBPdXRjb21lPT0xXSksCiAgICAgICAgICAgICAgICBGYWxzZV9OZWdhdGl2ZSA9IHN1bShuW3ByZWRPdXRjb21lPT0wICYgT3V0Y29tZT09MV0pLAogICAgICAgICAgICAgICAgRmFsc2VfUG9zaXRpdmUgPSBzdW0obltwcmVkT3V0Y29tZT09MSAmIE91dGNvbWU9PTBdKSkgJT4lIAogIGdhdGhlcihWYXJpYWJsZSwgQ291bnQpICU+JSAKICBtdXRhdGUodGhyZXNob2xkID0geCApCgphbGxfdGhyZXNob2xkIDwtIHJiaW5kKGFsbF90aHJlc2hvbGQsIHRocmVzaG9sZCkKIHggPC0geCArIC4wMQp9CgoKYGBgCgoKCmBgYHtyIHRocmVzaG9sZCBwbG90LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKYWxsX3RocmVzaG9sZCAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fbGluZShhZXMoeCA9IHRocmVzaG9sZCwgeT1Db3VudCwgY29sb3IgPSBWYXJpYWJsZSksIHNpemUgPSAxLjUsIGxpbmV0eXBlID0gMSkgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTQsIGd1aWRlPUZBTFNFKSArIAogIGZhY2V0X3dyYXAoflZhcmlhYmxlKSArIAogIGxhYnModGl0bGUgPSAiQ29uZnVzaW9uIE1ldHJpYyBPdXRjb21lcyBmb3IgRWFjaCBUaHJlc2hvbGQiKSArCiAgeGxhYigiVGhyZXNob2xkIikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCiAgCgpgYGAKClRoZSBgaXRlcmF0ZVRocmVzaG9sZHNgIGZ1bmN0aW9uIGlzIGJhc2VkIG9uIGEgd2hpbGUgbG9vcC4gVGhlIHRocmVzaG9sZCB4LCBzdGFydHMgYXQgMC4wMSBhbmQgd2hpbGUgaXQgaXMgbGVzcyB0aGFuIDEsIGEgcHJlZGljdGVkIGNsYXNzaWZpY2F0aW9uLCBwcmVkT3V0Y29tZSwgaXMgbXV0YXRlZCBnaXZlbiB4OyBjb25mdXNpb24gbWV0cmljcyBhcmUgY2FsY3VsYXRlZDsgdGhlIGNvc3QgYmVuZWZpdCBpcyBwZXJmb3JtZWQgYW5kIGlzIGFwcGVuZGVkIHRvIGEgZGF0YSBmcmFtZSBjYWxsZWQgYWxsX3ByZWRpY3Rpb24uIEZpbmFsbHkgeCBpcyBpdGVyYXRlZCBieSBhZGRpbmcgMC4wMSBhbmQgdGhlIHByb2Nlc3MgY29udGludWVzIHVudGlsIHggPSAxLgoKYGBge3IgaXRlcmF0aXZlIGZ1bmN0aW9ufQoKaXRlcmF0ZVRocmVzaG9sZHMgPC0gZnVuY3Rpb24oZGF0YSkgewogIHggPSAuMDEKICBhbGxfcHJlZGljdGlvbiA8LSBkYXRhLmZyYW1lKCkKICB3aGlsZSAoeCA8PSAxKSB7CiAgCiAgdGhpc19wcmVkaWN0aW9uIDwtCiAgICAgIHRlc3RQcm9icyAlPiUKICAgICAgbXV0YXRlKHByZWRPdXRjb21lID0gaWZlbHNlKFByb2JzID4geCwgMSwgMCkpICU+JQogICAgICBjb3VudChwcmVkT3V0Y29tZSwgT3V0Y29tZSkgJT4lCiAgICAgIHN1bW1hcml6ZShUcnVlX05lZ2F0aXZlID0gc3VtKG5bcHJlZE91dGNvbWU9PTAgJiBPdXRjb21lPT0wXSksCiAgICAgICAgICAgICAgICBUcnVlX1Bvc2l0aXZlID0gc3VtKG5bcHJlZE91dGNvbWU9PTEgJiBPdXRjb21lPT0xXSksCiAgICAgICAgICAgICAgICBGYWxzZV9OZWdhdGl2ZSA9IHN1bShuW3ByZWRPdXRjb21lPT0wICYgT3V0Y29tZT09MV0pLAogICAgICAgICAgICAgICAgRmFsc2VfUG9zaXRpdmUgPSBzdW0obltwcmVkT3V0Y29tZT09MSAmIE91dGNvbWU9PTBdKSkgJT4lCiAgICAgZ2F0aGVyKFZhcmlhYmxlLCBDb3VudCkgJT4lCiAgICAgbXV0YXRlKFJldmVudWUgPQogICAgICAgICAgICAgICBpZmVsc2UoVmFyaWFibGUgPT0gIlRydWVfTmVnYXRpdmUiLCBDb3VudCAqIDAsCiAgICAgICAgICAgICAgIGlmZWxzZShWYXJpYWJsZSA9PSAiVHJ1ZV9Qb3NpdGl2ZSIsIDYxOTAwICogQ291bnQsCiAgICAgICAgICAgICAgIGlmZWxzZShWYXJpYWJsZSA9PSAiRmFsc2VfTmVnYXRpdmUiLCAwICogQ291bnQsCiAgICAgICAgICAgICAgIGlmZWxzZShWYXJpYWJsZSA9PSAiRmFsc2VfUG9zaXRpdmUiLCAoLTI4NTApICogQ291bnQsIDApKSkpLAogICAgICAgICAgICBUaHJlc2hvbGQgPSB4KQogIAogIGFsbF9wcmVkaWN0aW9uIDwtIHJiaW5kKGFsbF9wcmVkaWN0aW9uLCB0aGlzX3ByZWRpY3Rpb24pCiAgeCA8LSB4ICsgLjAxCiAgfQpyZXR1cm4oYWxsX3ByZWRpY3Rpb24pCn0KYGBgCgpVc2luZyB0aGlzIGZ1bmN0aW9uLCB3ZSBjb21wdXRlZCBvdXIgb3B0aW1hbCB0aHJlc2hvbGQsIHdoaWNoIGlzIDAuNCBpbiB0aGlzIGNhc2UuIAoKYGBge3IgdGhyZXNob2xkIG92ZXIgcmV2ZW51ZSBhbmQsIGZpZy5oZWlnaHQ9Nn0KCndoaWNoVGhyZXNob2xkIDwtIGl0ZXJhdGVUaHJlc2hvbGRzKHRlc3RQcm9icykKCndoaWNoVGhyZXNob2xkX3JldmVudWUgPC0gCndoaWNoVGhyZXNob2xkICU+JSAKICAgIGdyb3VwX2J5KFRocmVzaG9sZCkgJT4lIAogICAgc3VtbWFyaXplKFJldmVudWUgPSBzdW0oUmV2ZW51ZSkpCgpwbG90MSA8LSBnZ3Bsb3Qod2hpY2hUaHJlc2hvbGRfcmV2ZW51ZSkrCiAgZ2VvbV9saW5lKGFlcyh4ID0gVGhyZXNob2xkLCB5ID0gUmV2ZW51ZSksIGxpbmV3aWR0aCA9IDIsIGNvbG9yID0gIiNEQkMyQ0YiKSsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAgcHVsbChhcnJhbmdlKHdoaWNoVGhyZXNob2xkX3JldmVudWUsIC1SZXZlbnVlKVsxLDFdKSwgY29sb3IgPSAiIzlGQTJCMiIsIGxpbmV3aWR0aCA9IDIuNSkrCiAgICBsYWJzKHRpdGxlID0gIk1vZGVsIFJldmVudWVzIEJ5IFRocmVzaG9sZCBhbmQgQ291bnQiLAogICAgICAgICBzdWJ0aXRsZSA9ICJWZXJ0aWNhbCBMaW5lIERlbm90ZXMgT3B0aW1hbCBUaHJlc2hvbGQiKSArIAogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTkpLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aCA9MC44KSkKICAKY3JlZGljdF9jb3VudCA8LXdoaWNoVGhyZXNob2xkICU+JSAKICBmaWx0ZXIoVmFyaWFibGUgPT0gIlRydWVfUG9zaXRpdmUiKSAlPiUgCiAgbXV0YXRlKENyZWRpdF9Db3VudCA9IENvdW50ICogMC4yNSkKCnBsb3QyIDwtIGNyZWRpY3RfY291bnQgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2xpbmUoYWVzKHggPSBUaHJlc2hvbGQsIHkgPSBDcmVkaXRfQ291bnQpLCBsaW5ld2lkdGggPSAyLCBjb2xvciA9ICIjREJDMkNGIikgKyAKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAgcHVsbChhcnJhbmdlKHdoaWNoVGhyZXNob2xkX3JldmVudWUsIC1SZXZlbnVlKVsxLDFdKSwgY29sb3IgPSAiIzlGQTJCMiIsIGxpbmV3aWR0aCA9IDIuNSkgKwogICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCgpwbG90MSAvIHBsb3QyIApgYGAKCkZpbmFsbHksIHdlIGNvbXBhcmUgdGhlIHJldmVudWUgcHJvZHVjZWQgd2l0aCBvcHRpbWFsIHRocmVzaG9sZCBhbmQgYSA1MCUgdGhyZXNob2xkLiBJdCBzaG93cyB0aGF0IGEgdGhyZXNob2xkIG9mIDQwJSBpcyBtdWNoIG1vcmUgb3B0aW1hbCBpbiB0aGF0IGl0IHlpZWxkcyB0aGUgZ3JlYXRlc3QgcmV2ZW51ZSBhdCBcJDYsNDAzLDM5MDAgd2hpbGUgYSA1MCUgdGhyZXNob2xkIHdpbGwgb25seSB5aWVsZCBcJDQsODE3LDQ1MC4gSW4gYWRkaXRpb24sIHRoZSB0b3RhbCBjb3VudCBvZiBjcmVkaXRzIGVhcm5lZCBpcyBtdWNoIGhpZ2hlciB3aGVuIHVzaW5nIHRoZSBvcHRpbWFsIHRocmVzaG9sZC4gCgpgYGB7ciByZXZlbnVlIHRhYmxlfQoKZmluYWxfdGFibGU8LSBkYXRhLmZyYW1lKAogIFRocmVzaG9sZCA9IGMoIk9wdGltYWxfVGhyZXNob2xkIiwgIjUwJV9UaHJlc2hvbGQiKSwKICBDcmVkaXQgPSBjKCIzNC43NSIsICIyMy43NSIpLAogIFJldmVudWUgPSBjKCI2NDAzOTAwIiwgIjQ4MTc0NTAiKQopCgpmaW5hbF90YWJsZSAlPiUgCiAga2JsKCkgJT4lIAogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAgJT4lCiAgZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgOCIpCmBgYAojIENvbmNsdXNpb25zCgpJbiBjb25jbHVzaW9uLCB3ZSBoYXZlIGRlc2lnbmVkIGEgbW9kZWwgdG8gcHJlZGljdCB0aGUgbGlrZWxpaG9vZCBvZiBFbWlsIENpdHkgaG9tZW93bmVycyBhY2NlcHRpbmcgaG9tZSByZXBhaXIgdGF4IGNyZWRpdCB3aXRoIGFjY2VwdGFibGUgYWNjdXJhY3kgYW5kIHNlbnNpdGl2aXR5LCBidXQgdGhpcyBkb2VzIG5vdCBtZWFuIHRoYXQgdGhpcyBtb2RlbCBjYW4gYmUgcHV0IGludG8gcHJvZHVjdGlvbiBhdCB0aGlzIHBvaW50LiBPdXIgcHJpbWFyeSBjb25jZXJucyB3aXRoIHRoZSBtb2RlbCBpbmNsdWRlIHRoZSBmb2xsb3dpbmcuIEZpcnN0bHksIHRoZXJlJ3Mgc2lnbmlmaWNhbnQgZGlzY3JlcGFuY2llcyBpbiB0ZXJtcyBvZiB0aGUgbnVtYmVyIG9mIG91dGNvbWVzIG9mIG91ciBwcmVkaWN0b3IgdmFyaWFibGVzLiBJbiBsb2dpc3RpYyByZWdyZXNzaW9uLCB1bmJhbGFuY2VkIHNhbXBsZSBzaXplcyBjYW4gbGVhZCB0aGUgbW9kZWwgdG8gYmUgYmlhc2VkIHRvd2FyZHMgdGhlIG1ham9yaXR5IGNsYXNzLiBJZiB0aGUgbWlub3JpdHkgY2xhc3MgaXMgb2YgcGFydGljdWxhciBpbnRlcmVzdCAoaW4gb3VyIGNhc2UsIHBlb3BsZSB3aG8gYWNjZXB0ZWQgdGhlIGNyZWRpdCBpcyB0aGUgbWlub3JpdHkgY2xhc3MpLCBhbiB1bmJhbGFuY2VkIGRhdGEgc2V0IGNhbiByZXN1bHQgaW4gcG9vciBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlIGZvciB0aGUgbWlub3JpdHkgY2xhc3MsIGxlYWRpbmcgdG8gYSBtb2RlbCB3aXRoIGhpZ2ggYWNjdXJhY3kgYmVjYXVzZSBpdCBwcmVkaWN0cyB0aGUgbWFqb3JpdHkgY2xhc3Mgd2VsbCwgYnV0IHBlcmZvcm1zIHBvb3JseSBvbiB0aGUgbWlub3JpdHkgY2xhc3MuIERlc3BpdGUgdGhhdCB3ZSBoYXZlIGFwcGxpZWQgd2VpZ2h0IG9uIG91ciBtaW5vcml0eSBjbGFzcyB0byBhbGxldmlhdGUgdGhlIGVmZmVjdCBvZiBpbWJhbGFuY2VkIGRhdGFzZXQsIGl0IHdpbGwgYmUgZ29vZCB0byBlaXRoZXIgb3ZlcnNhbXBsZSB0aGUgbWlub3JpdHkgY2xhc3MsIHVuZGVyc2FtcGxlIHRoZSBtYWpvcml0eSBjbGFzcywgb3IgdXNlIGFsdGVybmF0aXZlIGFsZ29yaXRobSBhcHByb2FjaGVzIHRvIGFzc2lnbiBkaWZmZXJlbnQgY2xhc3NpZmljYXRpb25zIGNvc3RzIHRvIGRpZmZlcmVudCBjbGFzc2VzLiAKCkluIGFkZGl0aW9uLCB0aGUgY3VycmVudCBkYXRhIHNldCBjb250YWlucyBhIGxvdCBvZiBmaWVsZCBjYXRlZ29yaXplZCBhcyB1bmtub3duLCBhbmQgc29tZXRpbWVzIHdlIHNlZSBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZXMgaW4gbWVhbiBiZXR3ZWVuIG91ciB1bmtub3duIGdyb3VwIGFuZCBvdGhlciBncm91cCBpbiBhIHBhaXJlZCB3aXNlIGNvbXBhcmlzb24uIFRoaXMgaXMgbGVhZGluZyB0byBtaXNpbnRlcnByZXRhdGlvbiBpbiBvdXIgcmVzdWx0cy4gSW4gbW9zdCBjYXNlcywgbW9kZWxzIGFyZSB0aGUgbW9zdCBhY2N1cmF0ZSB3aGVuIGJ1aWx0IHVzaW5nIHByZXZpb3VzIGRhdGEgb24gY3JlZGl0IGFjY2VwdGFuY2UsIGFuZCBoYXZpbmcgdW5rbm93biBmaWVsZHMgaW4gdGhlc2UgdmFyaWFibGVzIGlzIGxpa2VseSBsZWFkIHRvIHBvb3IgcGVyZm9ybWFuY2Ugb2Ygb3VyIG1vZGVsLiAKCk1vcmVvdmVyLCBodW1hbiBiZWhhdmlvciBpcyBpbmZsdWVuY2VkIGJ5IGEgbXlyaWFkIG9mIGZhY3RvcnMsIGluY2x1ZGluZyBlbW90aW9ucywgY3VsdHVyZSwgc29jaWFsIG5vcm1zLCBwZXJzb25hbCBleHBlcmllbmNlcywgYW5kIGNvZ25pdGl2ZSBwcm9jZXNzZXMsIGFuZCB0aG9zZSBkaW1lbnNpb25zIGFyZSBub3QgZnVsbHkgaW5jbHVkZWQgaW4gb3VyIG1vZGVsLiBXaGlsZSBjYXB0dXJpbmcgdGhpcyBjb21wbGV4aXR5IG9mIGh1bWFuIGJlaGF2aW9yIGluIGEgbWF0aGVtYXRpY2FsIG1vZGVsIGlzIGluaGVyZW50bHkgY2hhbGxlbmdpbmcsIGl0IGlzIGdvb2QgcHJhY3RpY2UgdG8gaW5jbHVkZSB2YXJpYWJsZXMgc3VjaCBhcyBsaWZlc3R5bGUgY2hvaWNlcywgZW1vdGlvbmFsIHN0YXRlLCBkZXZpY2UgdXNhZ2UsIGFuZCBtb3N0IGltcG9ydGFudGx5LCByZXN1bHRzIGZyb20gYW55IGZlZWRiYWNrIHN1cnZleXMgaW50byB0aGlzIG1vZGVsLiAKCkFuZCBmaW5hbGx5LCB3ZSBuZWVkIHRvIHRoaW5rIGFib3V0IHdoYXQgaXMgdGhlIG1vc3QgIm9wdGltYWwiIG1vZGVsIGZvciBIQ0QuIEZvciBvcmdhbml6YXRpb25zIHdob3NlIGdvYWwgaXMgc29sZWx5IHRvIGFjaGlldmUgZ3JlYXRlciBlY29ub21pYyBiZW5lZml0cywgdGhhbiB3ZSBoYXZlIHByb3ZpZGVkIGFuIG9wdGltaXplZCBtb2RlbCBmb3IgdGhlbS4gQnV0IGZvciBnb3Zlcm5tZW50IGFnZW5jaWVzIGxpa2UgSENEIHdobyBoYXMgbWFueSB1bnF1YW50aWZpYWJsZSBib3R0b20gbGluZXMgbGlrZSBlcXVpdHkgYW5kIHNvY2lhbCBjb2hlc2lvbiwgdGhpcyBtaWdodCBub3QgYmUgdGhlIGNhc2UuIFdoZXRoZXIgb3Igbm90IHRvIGFsbG9jYXRlZCBjcmVkaXQgYW5kIHRvIHdob20gdGhleSBhbGxjb2F0ZSBjcmVkaXQgc2hvdWxkIG5vdCBiZSBiYXNlZCBlbnRpcmVseSBvbiByZXZlbnVlcyBtYWRlLCBidXQgb24gcGVvcGxlIHdobyBuZWVkIHRob3NlIHJlc291cmNlcy4gCg==