1 Problem Statement and Background

The aim of the following analysis is to examine the relationships between an individual requesting a loan, information about the loan request and if the loan was approved. This analysis hopes to identify key factors to predict the probability of a loan request being accepted Diving deep into this topic will hopefully provide useful insights for individuals who are applying for a loan and wish to calculate their chance of the loan being approved.

This dataset comes from Kaggle, a free web-based platform that data scientists and statisticians use to share both ideas and datasets. The link to the Loan Approval Classification Dataset dataset is below:

Loan Approval Classification Dataset

This dataset may be updated over time. I downloaded this dataset on Monday, November 4th, 2024. Link to this version of the dataset is below:

11/4/24 Version

R/R Markdown were used in this project as it is free and open-source, allowing users to customize their experience with various libraries and features that other coding software such as SAS do not have. R provides an easy-to-use and comprehensive toolset of statistical analyses and tests. While these advanced analyses will not be used in this project, further work can be done to provide additional insights to the major factors in laptops’ price.

This analysis seeks to answer the questions:

  • What aspects of a loan request determines the approval status of the request?
  • Does demographic information of the loan requester influence the chances of a request being approved?
loan.data <- read.csv("https://raw.githubusercontent.com/EPKeep32/STA551/refs/heads/main/loan_data.csv")

2 Descrption of the Data

Here is what the author on Kaggle had to say about the Loan Approval Classification Dataset: “This dataset is a synthetic version inspired by the original Credit Risk dataset on Kaggle and enriched with additional variables based on Financial Risk for Loan Approval data. SMOTENC was used to simulate new data points to enlarge the instances. The dataset is structured for both categorical and continuous features.”

A detailed description of the variables within the dataset is given below:

person_age: Age of the person [numerical]

person_gender: Gender of the person [categorical]

person_education: Highest education level [categorical]

person_income: Annual income [numerical]

person_emp_exp: Years of employment experience [numerical]

person_home_ownership: Home ownership status (e.g., rent, own, mortgage) [categorical]

loan_amnt: Loan amount requested [numerical]

loan_intent: Purpose of the loan [categorical]

loan_int_rate: Loan interest rate [numerical]

loan_percent_income: Loan amount as a percentage of annual income [numerical]

cb_person_cred_hist_length: Length of credit history in years [numerical]

credit_score: Credit score of the person [numerical]

previous_loan_defaults_on_file: Indicator of previous loan defaults [categorical]

loan_status: Loan approval status: 1 = approved; 0 = rejected [binary]

2.1 Additional Variables

Below is a list of any additional variables that were created during the model building process and their descriptions.

2.1.1 Dummy Variable Creation

For regression techniques, I created dummy variables for all categorical variables. The list of them can be found below:

Gender Male: if person_gender = “male”, 1. Otherwise, 0

Education HS: if person_education = “High School”, 1. Otherwise, 0 Associate: if person_education = “Associate”, 1. Otherwise, 0 Bachelor: if person_education = “Bachelor”, 1. Otherwise, 0 Master: if person_education = “Master”, 1. Otherwise, 0

Home Ownership Own_Other: if person_home_ownership = “OTHER”, 1. Otherwise, 0 Own_Own: if person_home_ownership = “OWN”, 1. Otherwise, 0 Own_Mortgage: if person_home_ownership = “MORTGAGE”, 1. Otherwise, 0

Loan Intent HomeImp: if loan_intent = “HOMEIMPROVEMENT”, 1. Otherwise, 0 Debt: if loan_intent = “DEBTCONSOLIDATION”, 1. Otherwise, 0 Personal: if loan_intent = “PERSONAL”, 1. Otherwise, 0 Venture: if loan_intent = “VENTURE”, 1. Otherwise, 0 Medical: if loan_intent = “MEDICAL”, 1. Otherwise, 0

Previous Loan Previous: if previous_loan_defaults_on_file = “Yes”, 1. Otherwise, 0

It is common practice for the amount of dummy variables per variable to be 1 less than the amount of groups. For example, there is only one dummy variable created for person_gender. If the applicant had a gender of “female”, their male value would be 0. This means that “female” is the reference category for male.

loan.data$Male <- ifelse(loan.data$person_gender == "male", 1, 0)

loan.data$HS <- ifelse(loan.data$person_education == "High School", 1, 0)
loan.data$Associate <- ifelse(loan.data$person_education == "Associate", 1, 0)
loan.data$Bachelor <- ifelse(loan.data$person_education == "Bachelor", 1, 0)
loan.data$Master <- ifelse(loan.data$person_education == "Master", 1, 0)

loan.data$Own_Other <- ifelse(loan.data$person_home_ownership == "OTHER", 1, 0)
loan.data$Own_Own <- ifelse(loan.data$person_home_ownership == "OWN", 1, 0)
loan.data$Own_Mortgage <- ifelse(loan.data$person_home_ownership == "MORTGAGE", 1, 0)

loan.data$HomeImp <- ifelse(loan.data$loan_intent == "HOMEIMPROVEMENT", 1, 0)
loan.data$Debt <- ifelse(loan.data$loan_intent == "DEBTCONSOLIDATION", 1, 0)
loan.data$Personal <- ifelse(loan.data$loan_intent == "PERSONAL", 1, 0)
loan.data$Venture <- ifelse(loan.data$loan_intent == "VENTURE", 1, 0)
loan.data$Medical <- ifelse(loan.data$loan_intent == "MEDICAL", 1, 0)

loan.data$Previous <- ifelse(loan.data$previous_loan_defaults_on_file == "Yes", 1, 0)

2.2 Handling Missing Values

The next step is to determine if there are any missing variables. Missing variables can cause biased models to be generated, ultimately causing decisions that may be incorrect.

Another issue with missing values is how to replace them. There are many ways to replace missing values, including mean and median imputation, and model-based imputation.

However, I will first determine if there are any missing values within the dataset before eplxoring replacement techniques.

missing_values <- is.na(loan.data)
summary(missing_values)
 person_age      person_gender   person_education person_income  
 Mode :logical   Mode :logical   Mode :logical    Mode :logical  
 FALSE:45000     FALSE:45000     FALSE:45000      FALSE:45000    
 person_emp_exp  person_home_ownership loan_amnt       loan_intent    
 Mode :logical   Mode :logical         Mode :logical   Mode :logical  
 FALSE:45000     FALSE:45000           FALSE:45000     FALSE:45000    
 loan_int_rate   loan_percent_income cb_person_cred_hist_length credit_score   
 Mode :logical   Mode :logical       Mode :logical              Mode :logical  
 FALSE:45000     FALSE:45000         FALSE:45000                FALSE:45000    
 previous_loan_defaults_on_file loan_status        Male             HS         
 Mode :logical                  Mode :logical   Mode :logical   Mode :logical  
 FALSE:45000                    FALSE:45000     FALSE:45000     FALSE:45000    
 Associate        Bachelor         Master        Own_Other      
 Mode :logical   Mode :logical   Mode :logical   Mode :logical  
 FALSE:45000     FALSE:45000     FALSE:45000     FALSE:45000    
  Own_Own        Own_Mortgage     HomeImp           Debt        
 Mode :logical   Mode :logical   Mode :logical   Mode :logical  
 FALSE:45000     FALSE:45000     FALSE:45000     FALSE:45000    
  Personal        Venture         Medical         Previous      
 Mode :logical   Mode :logical   Mode :logical   Mode :logical  
 FALSE:45000     FALSE:45000     FALSE:45000     FALSE:45000    
missing_rows <- loan.data[!complete.cases(loan.data), ]
print(missing_rows)
 [1] person_age                     person_gender                 
 [3] person_education               person_income                 
 [5] person_emp_exp                 person_home_ownership         
 [7] loan_amnt                      loan_intent                   
 [9] loan_int_rate                  loan_percent_income           
[11] cb_person_cred_hist_length     credit_score                  
[13] previous_loan_defaults_on_file loan_status                   
[15] Male                           HS                            
[17] Associate                      Bachelor                      
[19] Master                         Own_Other                     
[21] Own_Own                        Own_Mortgage                  
[23] HomeImp                        Debt                          
[25] Personal                       Venture                       
[27] Medical                        Previous                      
<0 rows> (or 0-length row.names)

The above summary table shows that 0 of the variables have missing values. If any of the variables had missing values, there would be an NA: n row beneath it, with n representing the number of missing values. Therefore, no imputations are necessary for the dataset.

2.3 Final Dataset

The final dataset has 45000 observations each with 23 variables. All 28 variables will be held within the dataset for any further research, regardless of their use in this research.

Below is a quick summary of all variables in the final dataset:

summary(loan.data)
   person_age     person_gender      person_education   person_income    
 Min.   : 20.00   Length:45000       Length:45000       Min.   :   8000  
 1st Qu.: 24.00   Class :character   Class :character   1st Qu.:  47204  
 Median : 26.00   Mode  :character   Mode  :character   Median :  67048  
 Mean   : 27.76                                         Mean   :  80319  
 3rd Qu.: 30.00                                         3rd Qu.:  95789  
 Max.   :144.00                                         Max.   :7200766  
 person_emp_exp   person_home_ownership   loan_amnt     loan_intent       
 Min.   :  0.00   Length:45000          Min.   :  500   Length:45000      
 1st Qu.:  1.00   Class :character      1st Qu.: 5000   Class :character  
 Median :  4.00   Mode  :character      Median : 8000   Mode  :character  
 Mean   :  5.41                         Mean   : 9583                     
 3rd Qu.:  8.00                         3rd Qu.:12237                     
 Max.   :125.00                         Max.   :35000                     
 loan_int_rate   loan_percent_income cb_person_cred_hist_length  credit_score  
 Min.   : 5.42   Min.   :0.0000      Min.   : 2.000             Min.   :390.0  
 1st Qu.: 8.59   1st Qu.:0.0700      1st Qu.: 3.000             1st Qu.:601.0  
 Median :11.01   Median :0.1200      Median : 4.000             Median :640.0  
 Mean   :11.01   Mean   :0.1397      Mean   : 5.867             Mean   :632.6  
 3rd Qu.:12.99   3rd Qu.:0.1900      3rd Qu.: 8.000             3rd Qu.:670.0  
 Max.   :20.00   Max.   :0.6600      Max.   :30.000             Max.   :850.0  
 previous_loan_defaults_on_file  loan_status          Male      
 Length:45000                   Min.   :0.0000   Min.   :0.000  
 Class :character               1st Qu.:0.0000   1st Qu.:0.000  
 Mode  :character               Median :0.0000   Median :1.000  
                                Mean   :0.2222   Mean   :0.552  
                                3rd Qu.:0.0000   3rd Qu.:1.000  
                                Max.   :1.0000   Max.   :1.000  
       HS          Associate         Bachelor          Master      
 Min.   :0.000   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000  
 1st Qu.:0.000   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.0000  
 Median :0.000   Median :0.0000   Median :0.0000   Median :0.0000  
 Mean   :0.266   Mean   :0.2673   Mean   :0.2978   Mean   :0.1551  
 3rd Qu.:1.000   3rd Qu.:1.0000   3rd Qu.:1.0000   3rd Qu.:0.0000  
 Max.   :1.000   Max.   :1.0000   Max.   :1.0000   Max.   :1.0000  
   Own_Other         Own_Own         Own_Mortgage       HomeImp      
 Min.   :0.0000   Min.   :0.00000   Min.   :0.0000   Min.   :0.0000  
 1st Qu.:0.0000   1st Qu.:0.00000   1st Qu.:0.0000   1st Qu.:0.0000  
 Median :0.0000   Median :0.00000   Median :0.0000   Median :0.0000  
 Mean   :0.0026   Mean   :0.06558   Mean   :0.4109   Mean   :0.1063  
 3rd Qu.:0.0000   3rd Qu.:0.00000   3rd Qu.:1.0000   3rd Qu.:0.0000  
 Max.   :1.0000   Max.   :1.00000   Max.   :1.0000   Max.   :1.0000  
      Debt           Personal         Venture          Medical    
 Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Min.   :0.00  
 1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.00  
 Median :0.0000   Median :0.0000   Median :0.0000   Median :0.00  
 Mean   :0.1588   Mean   :0.1678   Mean   :0.1738   Mean   :0.19  
 3rd Qu.:0.0000   3rd Qu.:0.0000   3rd Qu.:0.0000   3rd Qu.:0.00  
 Max.   :1.0000   Max.   :1.0000   Max.   :1.0000   Max.   :1.00  
    Previous    
 Min.   :0.000  
 1st Qu.:0.000  
 Median :1.000  
 Mean   :0.508  
 3rd Qu.:1.000  
 Max.   :1.000  

3 Exploratory Data Analysis

Next I will perform some Exploratpory Data Analysis, or EDA. EDA is an important step in the overarching data analysis process, as it is important to familiarize yourself with the dataset.

Note I will not be performing any EDA on the manually created dummy variables, as their distributions are covered in their corresponding original variable.

3.1 Distribution of “person_age”

Below is a histogram of person_age:

hist(loan.data$person_age,
     breaks = seq(0, 150, 10),
     main = "Distribution of Applicant's Age",
     xlim = c(0, 150),
     xlab = "Applicant's Age (years)",
     ylim = c(0, 40000),
     ylab = "Number of Applicants",
     col = "lightblue",
     labels = TRUE)
abline(h = seq(5000, 40000, 5000), col = "gray", lty = "dotted")

The histogram above shows that person_age has a very right-skewed distribution. Over 75% of applicants in the dataset are in their 30’s. A few outliers may be present, with some applicants over the age of 100 years old.

3.2 Distribution of “person_gender”

Below is a pie chart of person_gender:

pst <- loan.data %>%
  group_by(person_gender) %>%
  summarise(count = n()) %>%
  ungroup() %>%
  mutate(perc = count / sum(count)) %>%
  arrange(perc) %>%
  mutate(labels = scales::percent(perc))

ggplot(pst, aes(x = "", y = perc, fill = person_gender)) + 
  geom_col() + 
  geom_text(aes(label = paste(count, "\n(", labels, ")", sep = "")), 
            position = position_stack(vjust = (0.5))) + 
  coord_polar(theta = "y") +
  scale_fill_manual(values = c("female" = "#FF9999", "male" = "#66B3FF"), 
                    labels = c("Female", "Male")) +
  labs(title = "Pie Chart of Applicant's Gender", y = "", x = "") +
  guides(fill = guide_legend(title = "Gender"))

As shown in the pie chart above, 55% of our loan applicants are male, and 45% are female.

3.3 Distribution of “person_education”

Below is a pie chart of person_education:

pst <- loan.data %>%
  group_by(person_education) %>%
  summarise(count = n()) %>%
  ungroup() %>%
  mutate(perc = count / sum(count)) %>%
  arrange(perc) %>%
  mutate(labels = scales::percent(perc),
         person_education = factor(person_education, levels = c("High School",  "Associate", "Bachelor", "Master", "Doctorate")))

ggplot(pst, aes(x = "", y = perc, fill = person_education)) + 
  geom_col() + 
  geom_text(aes(label = paste(count, "\n(", labels, ")", sep = "")), 
            position = position_stack(vjust = (0.5))) + 
  coord_polar(theta = "y") +
  scale_fill_manual(values = c("High School" = "#BBDEFB", "Associate" = "#90CAF9", "Bachelor" = "#42A5F5", "Master" = "#1E88E5", "Doctorate" = "#1565C0"), 
                    labels = c("High School Diploma", "Associate's Degree", "Bachelor's Degree", "Master's Degree", "Doctorate's Degree")) +
  labs(title = "Pie Chart of Applicant's Highest Completed Education Level", y = "", x = "") +
  guides(fill = guide_legend(title = "Education Level"))

As shown in the pie chart above, the most common education level among applicants is an associate’s degree (29.78%).

3.4 Distribution of “person_income”

Below is a histogram of person_income:

hist(loan.data$person_income,
     breaks = seq(0, 7300000, 100000),
     main = "Distribution of Applicant's Income",
     xlim = c(0, 2000000),
     xlab = "Applicant's Yearly Income (USD)",
     ylim = c(0, 40000),
     ylab = "Number of Applicants",
     col = "lightgreen",
     labels = TRUE)
abline(h = seq(5000, 40000, 5000), col = "gray", lty = "dotted")

The histogram above shows that person_income has a very right-skewed distribution. Over 75% of applicants in the dataset have an annual income of less than $100,000. A few outliers may be present, with some applicants over $1.5 million.

NOTE: For visualization purposes, 3 applicants are not shown in the chart above, who have income values of $5,545,545.00, $5,556,399.00, and $7,200,766.00.

3.5 Distribution of “person_emp_exp”

Below is a histogram of person_emp_exp:

hist(loan.data$person_emp_exp,
     breaks = seq(0, 125, 5),
     main = "Distribution of Applicant's Employment Experience",
     xlim = c(0, 125),
     xaxt = "n",
     xlab = "Applicant's Employment Experience (Years)",
     ylim = c(0, 40000),
     ylab = "Number of Applicants",
     col = "yellow",
     labels = TRUE)
abline(h = seq(5000, 40000, 5000), col = "gray", lty = "dotted")
axis(1, at = seq(0, 125, 25))

The histogram above shows that person_emp_exp has a very right-skewed distribution. Over 60% of applicants in the dataset have less than 5 years of employment experience. A few outliers may be present, with some applicants over 75 years of employment experience.

3.6 Distribution of “person_home_ownership”

Below is a bar graph chart of person_home_ownership

lds <- loan.data %>%
  count(person_home_ownership) %>%
  mutate(perc = n / sum(n)*100, 
         label = paste(n, " (", round(perc, 1), "%)", sep = ""))

ggplot(lds, aes(x = reorder(person_home_ownership, n), y = n)) +
  geom_bar(stat = "identity", fill = "lightpink") +
  geom_text(aes(label = label), vjust = -0.5) +
  labs(title = "Applicant's Home Ownership", x = "Ownership Status", y = "Count")

As shown above, over half of the applicants currently rent their home.

3.7 Distribution of “loan_amnt”

Below is a histogram of loan_amnt:

hist(loan.data$loan_amnt,
     breaks = seq(0, 35000, 2500),
     main = "Distribution of Requested Loan Amount",
     xlim = c(0, 35000),
     xlab = "Requested Loan Amount (USD)",
     ylim = c(0, 10000),
     ylab = "Number of Applicants",
     col = "violet",
     labels = TRUE)
abline(h = seq(1000, 10000, 1000), col = "gray", lty = "dotted")

The histogram above shows that loan_amnt has a right-skewed distribution. A majority of applicants in the dataset requested between $2,500 and $10,000. A few outliers may be present, with some applicants requesting over $30,000.

3.8 Distribution of “loan_intent”

Below is a bar graph chart of loan_intent

lds <- loan.data %>%
  count(loan_intent) %>%
  mutate(perc = n / sum(n)*100, 
         label = paste(n, "\n(", round(perc, 1), "%)", sep = ""))

ggplot(lds, aes(x = reorder(loan_intent, n), y = n)) +
  geom_bar(stat = "identity", fill = "darkred") +
  geom_text(aes(label = label, y = n / 2), hjust = 0.5, color = "white") +
  labs(title = "Applicant's Loan Intent", x = "Loan Intent", y = "Count") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

As shown above, applicants are applying for a loan for many different reasons, with the most popular being an education loan.

3.9 Distribution of “loan_int_rate”

Below is a histogram of loan_int_rate:

hist(loan.data$loan_int_rate,
     breaks = seq(0, 20, 2),
     main = "Distribution of Loan Interest Rate",
     xlim = c(0, 20),
     xaxt = "n",
     xlab = "Interest rate (%)",
     ylim = c(0, 15000),
     ylab = "Number of Applicants",
     col = "gray50",
     labels = TRUE)
abline(h = seq(2500, 15000, 2500), col = "gray", lty = "dotted")
axis(1, at = seq(0, 20, 2))

The histogram above shows that loan_int_rate has a fairly Normal. Most applicants are applying for a loan with an interest rate between 10-12%.

3.10 Distribution of “loan_percent_income”

Below is a histogram of loan_percent_income:

hist(loan.data$loan_percent_income,
     breaks = seq(0, 1, .1),
     main = "Distribution of Loan Percentage of Annual Income",
     xlim = c(0, 1),
     xlab = "Loan Percentage of Annual Income (%)",
     ylim = c(0, 20000),
     ylab = "Number of Applicants",
     col = "lawngreen",
     labels = TRUE)
abline(h = seq(2500, 20000, 2500), col = "gray", lty = "dotted")

The histogram above shows that loan_percent_income has a very right-skewed distribution.

3.11 Distribution of “cb_person_cred_hist_length”

Below is a histogram of cb_person_cred_hist_length:

hist(loan.data$cb_person_cred_hist_length,
     breaks = seq(0, 30, 3),
     main = "Distribution of Applicant's Length of Credit History",
     xlim = c(0, 30),
     xlab = "Applicant's Length of Credit History (Years)",
     ylim = c(0, 20000),
     ylab = "Number of Applicants",
     col = "gold",
     labels = TRUE)
abline(h = seq(2500, 20000, 2500), col = "gray", lty = "dotted")

The histogram above shows that cb_person_cred_hist_length has a very right-skewed distribution.

3.12 Distribution of “credit_score”

Below is a histogram of credit_score:

hist(loan.data$credit_score,
     breaks = seq(350, 900, 50),
     main = "Distribution of Applicant's Credit Score",
     xlim = c(350, 900),
     xaxt = "n",
     xlab = "Applicant's Credit Score",
     ylim = c(0, 20000),
     ylab = "Number of Applicants",
     col = "plum",
     labels = TRUE)
abline(h = seq(2500, 20000, 2500), col = "gray", lty = "dotted")
axis(1, at = seq(350, 900, 50))

The histogram above shows that credit_score has a slightly left-skewed distribution. A majority of applicants in the dataset have a credit score between 600 and 700.

3.13 Distribution of “previous_loan_defaults_on_file”

Below is a pie chart of previous_loan_defaults_on_file:

pst <- loan.data %>%
  group_by(previous_loan_defaults_on_file) %>%
  summarise(count = n()) %>%
  ungroup() %>%
  mutate(perc = count / sum(count)) %>%
  arrange(perc) %>%
  mutate(labels = scales::percent(perc))

ggplot(pst, aes(x = "", y = perc, fill = previous_loan_defaults_on_file)) + 
  geom_col() + 
  geom_text(aes(label = paste(count, "\n(", labels, ")", sep = "")), 
            position = position_stack(vjust = (0.5))) + 
  coord_polar(theta = "y") +
  scale_fill_manual(values = c("No" = "palevioletred", "Yes" = "lightgreen"), 
                    labels = c("No", "Yes")) +
  labs(title = "Pie Chart of Previous Loan Status", y = "", x = "") +
  guides(fill = guide_legend(title = "Did the applicant\nhave a previous loan?"))

As shown in the pie chart above, 50.8% of our loan applicants had previously took out a loan.

3.14 Distribution of “loan_status”

Below is a pie chart of loan_status:

pst <- loan.data %>%
  group_by(loan_status) %>%
  summarise(count = n()) %>%
  ungroup() %>%
  mutate(perc = count / sum(count)) %>%
  arrange(perc) %>%
  mutate(labels = scales::percent(perc),
         loan_status = factor(loan_status, levels = c(0, 1), labels = c("No", "Yes")))

ggplot(pst, aes(x = "", y = perc, fill = loan_status)) + 
  geom_col() + 
  geom_text(aes(label = paste(count, "\n(", labels, ")", sep = "")), 
            position = position_stack(vjust = (0.5))) + 
  coord_polar(theta = "y") +
  scale_fill_manual(values = c("No" = "palevioletred", "Yes" = "lightgreen"), 
                    labels = c("No", "Yes")) +
  labs(title = "Pie Chart of Loan Status", y = "", x = "") +
  guides(fill = guide_legend(title = "Did the loan get approved?"))

As shown in the pie chart above, only 22% of the loans got approved.

4 Modeling Techniques

Next, I will go through 4 different modeling techniques in an attempt to find the best possible model. The 4 techniques that I will use are:

  1. Logistic Regression

  2. Perceptron

  3. Decision Tree

  4. Bagging

Each of the 4 listed-above algorithms are powerful in their own way. I will find the best model with each technique, and then compare each model to each other to determine no only the best model but best modeling technique.

In order to later test the models, I split the dataset into two separate datasets: Training Data and Testing Data. Doing so will allow for the models to be tested on real data without the need for additional research or sampling. Roughly 75% of the overall dataset will be in the training dataset, and the remaining ~25% will be in the testing dataset.

set.seed(323)
index <- sample.split(Y = loan.data$loan_status, SplitRatio = 0.75)
train.data <- loan.data[index, ]
test.data <- loan.data[!index, ]

4.1 Logistic Regression

Logistic Regression builds a linear model for binary classification that estimates the probability of any specified outcome. It estimates the probability of a binary outcome based on one or more predictor variables. It then uses the logistic function to map predictions to a probability between 0 and 1.

4.1.1 Technique

I will begin by running the full model with all predictor variables. Once ran, I will see if there are any highly correlated variables within the model. If so, the assumption of multicollinearity will be broken and the model will be biased.

If there are any highly correlated variables, I will remove them one-by-one and re-run the model, now with one less variable. Once all correlated predictors are removed, I will remove any insignificant variables from the model until there are only significant variables left. The remaining variables will make up the Final Model.

After running various models, I am left with highly significant and uncorrelated variables in the model. Below is a summary of the model:


Call:
glm(formula = loan_status ~ person_income + Own_Own + Own_Mortgage + 
    loan_amnt + HomeImp + Debt + Personal + Venture + Medical + 
    loan_int_rate + loan_percent_income, family = "binomial", 
    data = train.data)

Coefficients:
                         Estimate    Std. Error z value             Pr(>|z|)
(Intercept)         -6.6888123917  0.0965070171 -69.309 < 0.0000000000000002
person_income        0.0000006538  0.0000001973   3.313             0.000922
Own_Own             -2.3850021635  0.0991391337 -24.057 < 0.0000000000000002
Own_Mortgage        -0.8444037988  0.0377395933 -22.374 < 0.0000000000000002
loan_amnt           -0.0001001720  0.0000037972 -26.381 < 0.0000000000000002
HomeImp              0.8623407431  0.0613081280  14.066 < 0.0000000000000002
Debt                 0.9331440727  0.0533499202  17.491 < 0.0000000000000002
Personal             0.2227267684  0.0558353390   3.989           0.00006635
Venture             -0.2651102364  0.0588289465  -4.506           0.00000659
Medical              0.6224907458  0.0515580668  12.074 < 0.0000000000000002
loan_int_rate        0.3326549007  0.0062012245  53.643 < 0.0000000000000002
loan_percent_income 15.7528184713  0.2740399808  57.484 < 0.0000000000000002
                       
(Intercept)         ***
person_income       ***
Own_Own             ***
Own_Mortgage        ***
loan_amnt           ***
HomeImp             ***
Debt                ***
Personal            ***
Venture             ***
Medical             ***
loan_int_rate       ***
loan_percent_income ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 35755  on 33749  degrees of freedom
Residual deviance: 24449  on 33738  degrees of freedom
AIC: 24473

Number of Fisher Scoring iterations: 6

4.1.2 Best Model

Therefore, the best possible model is listed below:

\(loan\_status = -6.6888123917 + 0.0000006538*(person\_income) - 2.3850021635*(Own\_Own) - 0.8444037988*(Own\_Morgage) - 0.0001001720*(loan_amnt) \\ + 0.8623407431*(HomeImp) + 0.9331440727*(Debt) + 0.2227267684*(Personal) - 0.2651102364*(Venture) + 0.6224907458*(Medical) \\ + 0.3326549007*(loan\_int\_rate) + 15.7528184713*(loan\_percent\_income)\)

Below is additional information about the effectiveness of this model on correctly predicting if an applicant’s loan will be approved:

prob.pred <- predict(log.reg.12, newdata = test.data, type = "response")
pred.class <- ifelse(prob.pred > 0.5, 1, 0)

accuracy <- mean(pred.class == test.data$loan_status)
print(paste("Accuracy: ", accuracy))
[1] "Accuracy:  0.849955555555556"
confusionMatrix(factor(pred.class), factor(test.data$loan_status))
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 8306 1244
         1  444 1256
                                               
               Accuracy : 0.85                 
                 95% CI : (0.8432, 0.8565)     
    No Information Rate : 0.7778               
    P-Value [Acc > NIR] : < 0.00000000000000022
                                               
                  Kappa : 0.5099               
                                               
 Mcnemar's Test P-Value : < 0.00000000000000022
                                               
            Sensitivity : 0.9493               
            Specificity : 0.5024               
         Pos Pred Value : 0.8697               
         Neg Pred Value : 0.7388               
             Prevalence : 0.7778               
         Detection Rate : 0.7383               
   Detection Prevalence : 0.8489               
      Balanced Accuracy : 0.7258               
                                               
       'Positive' Class : 0                    
                                               

The model had an 85% accuracy rating.

An additional measure in comparing various models is an ROC Curve. Attached with an ROC Curve is an AUC value, or “Area Under Curve”. The closer to 1 this value is, the better the model is at prediction. Below is the ROC Curve for our model:

roc_curve <- roc(test.data$loan_status, prob.pred)
plot(roc_curve)

auc(roc_curve)
Area under the curve: 0.8637

As shown in the graph above, this model’s AUC is 0.8637. This value will be used to compare other models.

4.2 Perceptron

A perceptron is a simple type of artificial neural network that makes predictions based on a linear combination of input features. I utilizes an activation function (commonly referred to as a step function) to make predictions.

4.2.1 Technique

I will go through the process of making a simple percepton and testing its accuracy utilizing an ROC Curve.

neuralData <- train.data[, c("person_age", "person_income", "person_emp_exp", "loan_amnt", "loan_int_rate", "loan_percent_income", "cb_person_cred_hist_length", "credit_score", "Male", "HS", "Associate", "Bachelor", "Master", "Own_Other", "Own_Own", "Own_Mortgage", "HomeImp", "Debt", "Personal", "Venture", "Medical", "Previous", "loan_status")]

## feature scaling
neuralData$person_age <- (neuralData$person_age - min(neuralData$person_age)) / (max(neuralData$person_age) - min(neuralData$person_age))

neuralData$person_income <- (neuralData$person_income - min(neuralData$person_income)) / (max(neuralData$person_income) - min(neuralData$person_income))

neuralData$person_emp_exp <- (neuralData$person_emp_exp - min(neuralData$person_emp_exp)) / (max(neuralData$person_emp_exp) - min(neuralData$person_emp_exp))

neuralData$loan_amnt <- (neuralData$loan_amnt - min(neuralData$loan_amnt)) / (max(neuralData$loan_amnt) - min(neuralData$loan_amnt))

neuralData$loan_int_rate <- (neuralData$loan_int_rate - min(neuralData$loan_int_rate)) / (max(neuralData$loan_int_rate) - min(neuralData$loan_int_rate))

neuralData$loan_percent_income <- (neuralData$loan_percent_income - min(neuralData$loan_percent_income)) / (max(neuralData$loan_percent_income) - min(neuralData$loan_percent_income))

neuralData$cb_person_cred_hist_length <- (neuralData$cb_person_cred_hist_length - min(neuralData$cb_person_cred_hist_length)) / (max(neuralData$cb_person_cred_hist_length) - min(neuralData$cb_person_cred_hist_length))

neuralData$credit_score <- (neuralData$credit_score - min(neuralData$credit_score)) / (max(neuralData$credit_score) - min(neuralData$credit_score))
neuralModelFormula <- model.matrix(loan_status ~ person_age + person_income + person_emp_exp +
                                   loan_amnt + loan_int_rate + loan_percent_income
                                   + cb_person_cred_hist_length + credit_score + 
                                   Male + HS + Associate + Bachelor + Master + 
                                   Own_Other + Own_Own + Own_Mortgage + HomeImp +
                                   Debt + Personal + Venture + Medical + Previous,
                                   
                                   data = neuralData)
implicitFormula <- model.matrix(~., data = neuralData[, -ncol(neuralData)])
columnNames = colnames(implicitFormula)
columnList = paste(columnNames[-c(1,length(columnNames))], collapse = "+")
columnList = paste(c("loan_status", "~", columnList), collapse="")

modelFormula = formula(columnList)

Below shows the complicated neural network for the best model, accompanied by the weights for each variable.

set.seed(323)
neuralData_small <- neuralData[sample(1:nrow(neuralData), 1000), ]

nn_model <- neuralnet(modelFormula, data = neuralData_small, linear.output = FALSE, hidden = c(5))

plot(nn_model, rep = "best")

nn_model$result.matrix
                                                  [,1]
error                                     22.029343917
reached.threshold                          0.009677202
steps                                  12123.000000000
Intercept.to.1layhid1                      2.343522359
person_age.to.1layhid1                    -2.220192772
person_income.to.1layhid1                122.856875245
person_emp_exp.to.1layhid1                10.100241390
loan_amnt.to.1layhid1                      0.887542235
loan_int_rate.to.1layhid1                 -2.821403823
loan_percent_income.to.1layhid1          -10.820928021
cb_person_cred_hist_length.to.1layhid1    -0.325423014
credit_score.to.1layhid1                   0.211061858
Male.to.1layhid1                          -1.314749738
HS.to.1layhid1                             0.909318135
Associate.to.1layhid1                      0.533676683
Bachelor.to.1layhid1                       0.791546840
Master.to.1layhid1                         0.954568655
Own_Other.to.1layhid1                     36.656582695
Own_Own.to.1layhid1                        2.508295720
Own_Mortgage.to.1layhid1                  -3.053846439
HomeImp.to.1layhid1                        0.835072250
Debt.to.1layhid1                           1.868844391
Personal.to.1layhid1                       0.667489933
Venture.to.1layhid1                       -3.679322721
Medical.to.1layhid1                        1.955099128
Intercept.to.1layhid2                      4.009026174
person_age.to.1layhid2                   -63.583467746
person_income.to.1layhid2                354.463740574
person_emp_exp.to.1layhid2                65.070732368
loan_amnt.to.1layhid2                    -19.847012500
loan_int_rate.to.1layhid2                -13.004886138
loan_percent_income.to.1layhid2          -10.803943667
cb_person_cred_hist_length.to.1layhid2    57.881480216
credit_score.to.1layhid2                   9.583634227
Male.to.1layhid2                           8.123490457
HS.to.1layhid2                            -3.613416452
Associate.to.1layhid2                      1.800272281
Bachelor.to.1layhid2                       6.462884552
Master.to.1layhid2                         6.228900184
Own_Other.to.1layhid2                     30.176585490
Own_Own.to.1layhid2                       50.422175134
Own_Mortgage.to.1layhid2                  10.432800083
HomeImp.to.1layhid2                      -17.737359476
Debt.to.1layhid2                           3.433791142
Personal.to.1layhid2                      45.302012108
Venture.to.1layhid2                       -5.620044973
Medical.to.1layhid2                      -14.217293003
Intercept.to.1layhid3                     -2.034336185
person_age.to.1layhid3                    -7.155403901
person_income.to.1layhid3                -61.314970365
person_emp_exp.to.1layhid3                13.302612821
loan_amnt.to.1layhid3                     -1.565132721
loan_int_rate.to.1layhid3                  2.329824951
loan_percent_income.to.1layhid3            7.884036983
cb_person_cred_hist_length.to.1layhid3     1.794780294
credit_score.to.1layhid3                   0.863938824
Male.to.1layhid3                           0.316483883
HS.to.1layhid3                            -1.069344274
Associate.to.1layhid3                     -0.308853290
Bachelor.to.1layhid3                      -0.760741593
Master.to.1layhid3                         2.279709201
Own_Other.to.1layhid3                   -533.386560624
Own_Own.to.1layhid3                       -4.549112998
Own_Mortgage.to.1layhid3                  -4.776238141
HomeImp.to.1layhid3                        0.025697369
Debt.to.1layhid3                          -0.786603015
Personal.to.1layhid3                       0.426890577
Venture.to.1layhid3                       -1.195592676
Medical.to.1layhid3                        0.563119039
Intercept.to.1layhid4                      0.582345490
person_age.to.1layhid4                     4.845605284
person_income.to.1layhid4                -86.769179056
person_emp_exp.to.1layhid4                27.642077475
loan_amnt.to.1layhid4                     -0.009990142
loan_int_rate.to.1layhid4                  0.929297967
loan_percent_income.to.1layhid4           -1.068298954
cb_person_cred_hist_length.to.1layhid4   -12.387329033
credit_score.to.1layhid4                  -2.633312237
Male.to.1layhid4                           6.578211269
HS.to.1layhid4                            -3.471764012
Associate.to.1layhid4                      2.807139912
Bachelor.to.1layhid4                      -4.102273279
Master.to.1layhid4                         2.164172177
Own_Other.to.1layhid4                     39.461744394
Own_Own.to.1layhid4                       -3.634502257
Own_Mortgage.to.1layhid4                   2.926108097
HomeImp.to.1layhid4                       -1.466014010
Debt.to.1layhid4                         -12.061799004
Personal.to.1layhid4                      -2.954685917
Venture.to.1layhid4                        4.207105140
Medical.to.1layhid4                       -1.233650834
Intercept.to.1layhid5                     -1.444722328
person_age.to.1layhid5                    18.003004345
person_income.to.1layhid5                 82.664696946
person_emp_exp.to.1layhid5               -35.137088857
loan_amnt.to.1layhid5                     -2.297558086
loan_int_rate.to.1layhid5                 12.600861025
loan_percent_income.to.1layhid5          -19.928828490
cb_person_cred_hist_length.to.1layhid5    -4.624702274
credit_score.to.1layhid5                  -4.575712719
Male.to.1layhid5                           1.398126046
HS.to.1layhid5                            -0.376239601
Associate.to.1layhid5                     -1.633584357
Bachelor.to.1layhid5                      -0.052538748
Master.to.1layhid5                        -4.044571737
Own_Other.to.1layhid5                   -531.847963859
Own_Own.to.1layhid5                        2.803626744
Own_Mortgage.to.1layhid5                   6.058164217
HomeImp.to.1layhid5                        2.188492187
Debt.to.1layhid5                           1.471973705
Personal.to.1layhid5                      -1.625761996
Venture.to.1layhid5                       -1.811582060
Medical.to.1layhid5                       -1.972843936
Intercept.to.loan_status                  -0.029427523
1layhid1.to.loan_status                  -95.295865591
1layhid2.to.loan_status                  -48.306557045
1layhid3.to.loan_status                  136.430114803
1layhid4.to.loan_status                  -50.033306216
1layhid5.to.loan_status                   83.774193492

Since the perceptron does not result in a linear formula for prediction, it can be difficult to compare the model to others. This is why an ROC Curve is necessary to calculate an AUC value, whioch can be compared across model types.

Below is the ROC Curve with the model’s AUC value:

predNN = predict(nn_model, newdata = test.data, lineat.output = FALSE)

category = test.data$loan_status == 1
ROCobj.NN <- roc(category, predNN)

NNAUC = ROCobj.NN$auc

sen.NN = ROCobj.NN$sensitivities
fnr.NN = 1 - ROCobj.NN$specificities

plot(fnr.NN, sen.NN, type = "l", lwd = 2, col = "red",
     xlim = c(0, 1),
     ylim = c(0, 1),
     xlab = "1 - specificity",
     ylab = "sensitivity",
     main = "ROC Curve of Model")

text(0.87, 0.10, paste("AUC = ", round(NNAUC,4)), cex = 0.7, adj = 1)

As shown in the graph above, this model’s AUC is 0.5. This value will be used to compare other models.

4.3 Decision Tree

A decision tree is a tree-like structure that splits the data recursively based on feature values to create a set of decision rules. Each internal node represents a decision on a feature, each branch represents the outcome of that decision, and each leaf node represents a class label (for classification) or a numeric value (for regression).

4.3.1 Technique

I will create 6 different decision trees and all compare them against one another utilizing the AUC values. The 6 decision trees will all have varying node purity types, false positive weihts, and false negative weights.

tree.builder = function(in.data, fp, fn, purity){
  tree = rpart(loan_status ~ .,
               data = in.data,
               na.action = na.rpart,
               
               method = "class",
               model = FALSE,
               x = FALSE,
               y = TRUE,
               
               parms = list(loss = matrix(c(0, fp, fn, 0), ncol = 2, byrow = TRUE),
                            split = purity),
               control = rpart.control(
                 minsplit = 10,
                 minbucket = 10,
                 cp = 0.01,
                 xval = 10
               ))
}
gini.tree.1.1 = tree.builder(in.data = train.data, fp = 1, fn = 1, purity = "gini")
info.tree.1.1 = tree.builder(in.data = train.data, fp = 1, fn = 1, purity = "information")

gini.tree.1.10 = tree.builder(in.data = train.data, fp = 1, fn = 10, purity = "gini")
info.tree.1.10 = tree.builder(in.data = train.data, fp = 1, fn = 10, purity = "information")

gini.tree.10.1 = tree.builder(in.data = train.data, fp = 10, fn = 1, purity = "gini")
info.tree.10.1 = tree.builder(in.data = train.data, fp = 10, fn = 1, purity = "information")

Below are the 6 different decision trees:

rpart.plot(gini.tree.1.1, main = "Tree with Gini index: non-penalization")

rpart.plot(info.tree.1.1, main = "Tree with entropy: non-penalization")

rpart.plot(gini.tree.1.10, main = "Tree with Gini index: penalization")

rpart.plot(info.tree.1.10, main = "Tree with entropy: penalization")

rpart.plot(gini.tree.10.1, main = "Tree with Gini index: penalization")

rpart.plot(info.tree.10.1, main = "Tree with entropy: penalization")

4.3.2 Best Model

To determine which of the 6 decision trees are the best, I compare their AUC values. Below is an overlapping graph of all 6 ROC curves and AUC values:

SenSpe = function(in.data, fp, fn, purity){
  cutoff = seq(0,1, length = 20)   
  model = tree.builder(in.data, fp, fn, purity) 
 
  pred = predict(model, newdata = in.data, type = "prob") 
  senspe.mtx = matrix(0, ncol = length(cutoff), nrow= 2, byrow = FALSE)
  for (i in 1:length(cutoff)){

  pred.out =  ifelse(pred[,2] >= cutoff[i], 1, 0)  
  TP = sum(pred.out == 1 & in.data$loan_status == 1)
  TN = sum(pred.out == 0 & in.data$loan_status == 0)
  FP = sum(pred.out == 1 & in.data$loan_status == 0)
  FN = sum(pred.out == 0 & in.data$loan_status == 1)
  senspe.mtx[1,i] = TP/(TP + FN)
  senspe.mtx[2,i] = TN/(TN + FP)
  accuracy[i] = (TP + TN)/(TP + TN + FP + FN)
  }
  

  prediction = pred[, 2]
  category = in.data$loan_status == 1
  ROCobj <- roc(category, prediction)
  AUC = auc(ROCobj)

  list(senspe.mtx= senspe.mtx, AUC = round(AUC,5))
 }
giniROC11 = SenSpe(in.data = train.data, fp=1, fn=1, purity="gini")
infoROC11 = SenSpe(in.data = train.data, fp=1, fn=1, purity="information")
giniROC110 = SenSpe(in.data = train.data, fp=1, fn=10, purity="gini")
infoROC110 = SenSpe(in.data = train.data, fp=1, fn=10, purity="information")
giniROC101 = SenSpe(in.data = train.data, fp=10, fn=1, purity="gini")
infoROC101 = SenSpe(in.data = train.data, fp=10, fn=1, purity="information")
par(pty = "s")
colors = c("#008B8B", "#00008B",  "#8B008B",  "#8B0000",  "#8B8B00", "#8B4500")
plot(1 - giniROC11$senspe.mtx[2,], giniROC11$senspe.mtx[1,], 
     type = "l", 
     xlim = c(0,1), 
     ylim = c(0,1), 
     xlab = "1 - specificity: FPR", ylab = "Sensitivity: TPR", 
     col = colors[1], 
     lwd = 2,
     main = "ROC Curves of Decision Trees", 
     cex.main = 0.9, 
     col.main = "navy")

abline(0,1, lty = 2, col = "orchid4", lwd = 2)

lines(1 - infoROC11$senspe.mtx[2, ], infoROC11$senspe.mtx[1, ], 
      col = colors[2], lwd = 2, lty=2)
lines(1 - giniROC110$senspe.mtx[2, ], giniROC110$senspe.mtx[1, ],
      col = colors[3], lwd = 2)
lines(1 - infoROC110$senspe.mtx[2, ], infoROC110$senspe.mtx[1, ], 
      col = colors[4], lwd = 2, lty=2)
lines(1 - giniROC101$senspe.mtx[2, ], giniROC101$senspe.mtx[1, ], 
      col = colors[5], lwd = 2, lty = 4)
lines(1 - infoROC101$senspe.mtx[2, ], infoROC101$senspe.mtx[1, ], 
      col = colors[6], lwd = 2, lty=2)
legend("bottomright", c(paste("gini.1.1,  AUC =", giniROC11$AUC), 
                        paste("info.1.1,  AUC =",infoROC11$AUC), 
                        paste("gini.1.10, AUC =",giniROC110$AUC), 
                        paste("info.1.10, AUC =",infoROC110$AUC),
                        paste("gini.10.1, AUC =",giniROC101$AUC), 
                        paste("info.10.1, AUC =",infoROC101$AUC)),
                        col=colors, 
                        lty=rep(1:2,3), lwd=rep(2,6), cex = 0.8, bty = "n")

As shown by the ROC plot above, the gini.1.1 decision tree performed the best with an AUC of 0.94138. Therefore, this decision tree is the best performing tree of the available trees. A copy of the decision tree is below:

rpart.plot(gini.tree.1.1, main = "Tree with Gini index: non-penalization")

4.4 Bagging

Bagging is an ensemble method that aims to reduce variance by training multiple models on different subsets of the data and then aggregating their predictions. It works by bootstrapping the dataset and fitting a model on each bootstrap sample.

4.4.1 Technique

I will train the bagging model to create an assemble of decision trees with specific aspects that may or may not differ from the above decision tree. With the given parameters, I will evaluate the performance of the chosen decision tree and ultimate provide an ROC curve with an AUC value.

4.4.2 Best Model

train = train.data
test = test.data

Loan.bag.train <- bagging(as.factor(loan_status) ~ .,
                          data = train,
                          nbagg = 150,
                          coob = TRUE,
                          parms = list(loss = matrix(c(0, 10, 1, 0),
                                                                ncol = 20,
                                                                byrow = TRUE),
                                                  split = "gini"),
                          control = rpart.control(minsplit = 10,
                                                  cp = 0.02))

pred = predict(Loan.bag.train, test, type = "prob")

cut.prob = seq(0, 1, length = 20)
senspe.mtx = matrix(0, ncol = length(cut.prob), nrow = 3, byrow = FALSE)

for (i in 1:length(cut.prob)){
  pred.out = ifelse(pred[, "1"] >= cut.prob[i], "1", "0")
  
  TP = sum(pred.out == "1" & test$loan_status == 1)
  TN = sum(pred.out == "0" & test$loan_status == 0)
  FP = sum(pred.out == "1" & test$loan_status == 0)
  FN = sum(pred.out == "0" & test$loan_status == 1)
  
  senspe.mtx[1, i] = TP / (TP + FN)
  senspe.mtx[2, i] = TN / (TN + FP)
  senspe.mtx[3, i] = (TP + TN) / (TP + FN + TN + FP)
}

prediction = pred[, "1"]
category = test$loan_status == 1

ROCobj <- roc(category, prediction)
AUC = auc(ROCobj)
AUC = round(as.vector(AUC[1]), 3)

n = length(senspe.mtx[3,])
idx = which(senspe.mtx[3,] == max(senspe.mtx[3,]))
tick.label = as.character(round(cut.prob,2))

par(mfrow = c(1, 2))
plot(1 - senspe.mtx[2, ], senspe.mtx[1, ], 
     type = "l", lwd = 2, col = "navy", xlim = c(0, 1), 
     ylim = c(0, 1), xlab = "1 - Specificity", 
     ylab = "Sensitivity", main = "ROC (Testing Data)", 
     cex.main = 0.8)
segments(0, 0, 1, 1, lty = 2, col = "red")
legend("bottomright", c(paste("AUC =", AUC)), bty = "n", cex = 0.8)

plot(1:length(cut.prob), senspe.mtx[3,], 
     xlab = "Cut-off Probability", ylab = "Accuracy", 
     ylim = c(min(senspe.mtx[3,]), 1), axes = FALSE, 
     main = "Cut-off vs Accuracy", cex.main = 0.9, col.main = "navy")
axis(1, at = 1:20, label = round(cut.prob, 2), las = 2)
axis(2)
points(idx, senspe.mtx[3,][idx], pch = 19, col = "red")
segments(idx, min(senspe.mtx[3,]), idx, senspe.mtx[3,][idx], col = "red")
legend("topright", c(paste("Optimal cut-off prob = ", round(median(cut.prob[idx]), 3), sep = ""), 
                     paste("Accuracy = ", round(mean(senspe.mtx[3,][idx]), 3), sep = "")),
       cex = 0.8, bty = "n", adj = -0)

As shown in the graph above, this model’s AUC is 0.866. This value will be used to compare other models.

5 Model Comparison

Below is a list of the different modeling techniques and their corresponding AUC values in order from greatest to least

  1. Decision Tree: 0.94138
  2. Bagging: 0.866
  3. Log Reg AUC: 0.8637
  4. Perceptron AUC: 0.5

6 Conclusion

Therefore, the most effective model for our given dataset is the decision tree model, with an AUC of 0.94138. A copy of the decision tree is below:

rpart.plot(gini.tree.1.1, main = "Tree with Gini index: non-penalization")

I will now address the questions asked in the beginning of the report now that the best model has been selected

6.1 What aspects of a loan request determines the approval status of the request?

As shown in the decision tree above, the following variables are significant in predicting an applicant’s chances of getting their loan approved:

  • previous_loan_defaults_on_file
  • loan_percent_income
  • loan_int_rate
  • person_income
  • person_home_ownership

6.2 Does demographic information of the loan requester influence the chances of a request being approved?

The only demographic variables that influence the chances of a loan request being approved are previous_loan_defaults_on_file, person_income, and person_home_ownership. Other demographic variables such as person_age and person_gender did not have significant influence.

7 Recommendation

While the decision tree was best for the provided dataset, instances may occur where different modeling techniques are found to be more effective. I encourage everyone to do their own research when determining the best modeling technique.

LS0tDQp0aXRsZTogIlByZWRpY3RpdmUgTW9kZWxzIGZvciBMb2FuIEFjY2VwdGFuY2UiDQphdXRob3I6ICJFdmFuIFBhcmtlciINCmRhdGU6ICIyMDI0LTExLTE0Ig0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgZmlnX3dpZHRoOiA2DQogICAgZmlnX2hlaWdodDogNA0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRvY19jb2xsYXBzZWQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHNtb290aF9zY3JvbGw6IHllcw0KICAgIHRoZW1lOiBsdW1lbg0KLS0tDQpgYGB7PWh0bWx9DQoNCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoNCi8qIENhc2NhZGluZyBTdHlsZSBTaGVldHMgKENTUykgaXMgYSBzdHlsZXNoZWV0IGxhbmd1YWdlIHVzZWQgdG8gZGVzY3JpYmUgdGhlIHByZXNlbnRhdGlvbiBvZiBhIGRvY3VtZW50IHdyaXR0ZW4gaW4gSFRNTCBvciBYTUwuIGl0IGlzIGEgc2ltcGxlIG1lY2hhbmlzbSBmb3IgYWRkaW5nIHN0eWxlIChlLmcuLCBmb250cywgY29sb3JzLCBzcGFjaW5nKSB0byBXZWIgZG9jdW1lbnRzLiAqLw0KDQpoMS50aXRsZSB7ICAvKiBUaXRsZSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgdGhlIHJlcG9ydCB0aXRsZSAqLw0KICBmb250LXNpemU6IDI0cHg7DQogIGZvbnQtd2VpZ2h0OmJvbGQ7DQogIGNvbG9yOiBEYXJrUmVkOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCn0NCmg0LmF1dGhvciB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgYXV0aG9ycyAgKi8NCiAgZm9udC1zaXplOiAyMHB4Ow0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuZGF0ZSB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgdGhlIGRhdGUgICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgY29sb3I6IERhcmtCbHVlOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoMSB7IC8qIEhlYWRlciAxIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMSBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMjJweDsNCiAgICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICAgIGZvbnQtd2VpZ2h0OmJvbGQ7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCmgyIHsgLyogSGVhZGVyIDIgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAyIHNlY3Rpb24gdGl0bGUgKi8NCiAgICBmb250LXNpemU6IDIwcHg7DQogICAgZm9udC13ZWlnaHQ6Ym9sZDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoMyB7IC8qIEhlYWRlciAzIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCAzIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtd2VpZ2h0OmJvbGQ7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KYm9keSB7IGJhY2tncm91bmQtY29sb3I6d2hpdGU7IH0NCg0KLmhpZ2hsaWdodG1lIHsgYmFja2dyb3VuZC1jb2xvcjp5ZWxsb3c7IH0NCg0KcCB7IGJhY2tncm91bmQtY29sb3I6d2hpdGU7IH0NCg0KPC9zdHlsZT4NCmBgYA0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCm9wdGlvbnMocmVwb3MgPSBsaXN0KENSQU49Imh0dHA6Ly9jcmFuLnJzdHVkaW8uY29tLyIpKQ0KIyBjb2RlIGNodW5rIHNwZWNpZmllcyB3aGV0aGVyIHRoZSBSIGNvZGUsIHdhcm5pbmdzLCBhbmQgb3V0cHV0IA0KIyB3aWxsIGJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQgZmlsZXMuDQppZiAoIXJlcXVpcmUoImtuaXRyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikNCiAgIGxpYnJhcnkoa25pdHIpDQp9DQoNCmlmICghcmVxdWlyZSgiZHBseXIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZHBseXIiKQ0KICAgbGlicmFyeShkcGx5cikNCn0NCg0KaWYgKCFyZXF1aXJlKCJnZ3Bsb3QyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KICAgbGlicmFyeShnZ3Bsb3QyKQ0KfQ0KDQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KICAgbGlicmFyeSh0aWR5dmVyc2UpDQp9DQoNCmlmICghcmVxdWlyZSgiZ2dwdWJyIikpIHsNCiAgaW5zdGFsbC5wYWNrYWdlcygiZ2dwdWJyIikNCiAgbGlicmFyeShnZ3B1YnIpDQp9DQoNCmlmICghcmVxdWlyZSgicHN5Y2giKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJwc3ljaCIpDQogIGxpYnJhcnkocHN5Y2gpDQp9DQoNCmlmICghcmVxdWlyZSgiZ2dmb3J0aWZ5IikpIHsNCiAgaW5zdGFsbC5wYWNrYWdlcygiZ2dmb3J0aWZ5IikNCiAgbGlicmFyeShnZ2ZvcnRpZnkpDQp9DQoNCmlmICghcmVxdWlyZSgiTUFTUyIpKSB7DQogIGluc3RhbGwucGFja2FnZXMoIk1BU1MiKQ0KICBsaWJyYXJ5KE1BU1MpDQp9DQoNCmlmICghcmVxdWlyZSgicFJPQyIpKSB7DQogIGluc3RhbGwucGFja2FnZXMoInBST0MiKQ0KICBsaWJyYXJ5KHBST0MpDQp9DQoNCmlmICghcmVxdWlyZSgiY2FyZXQiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJjYXJldCIpDQogIGxpYnJhcnkoY2FyZXQpDQp9DQoNCmlmICghcmVxdWlyZSgiY2FUb29scyIpKSB7DQogIGluc3RhbGwucGFja2FnZXMoImNhVG9vbHMiKQ0KICBsaWJyYXJ5KENhVG9vbHMpDQp9DQoNCmlmICghcmVxdWlyZSgiY2FyIikpIHsNCiAgaW5zdGFsbC5wYWNrYWdlcygiY2FyIikNCiAgbGlicmFyeShjYXIpDQp9DQoNCmlmICghcmVxdWlyZSgia2VyYXMiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJrZXJhcyIpDQogIGxpYnJhcnkoa2VyYXMpDQp9DQoNCmlmICghcmVxdWlyZSgicnBhcnQiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJycGFydCIpDQogIGxpYnJhcnkocnBhcnQpDQp9DQoNCmlmICghcmVxdWlyZSgicnBhcnQucGxvdCIpKSB7DQogIGluc3RhbGwucGFja2FnZXMoInJwYXJ0LnBsb3QiKQ0KICBsaWJyYXJ5KHJwYXJ0LnBsb3QpDQp9DQoNCmlmICghcmVxdWlyZSgiZTEwNzEiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJlMTA3MSIpDQogIGxpYnJhcnkoZTEwNzEpDQp9DQoNCmlmICghcmVxdWlyZSgibm5ldCIpKSB7DQogIGluc3RhbGwucGFja2FnZXMoIm5uZXQiKQ0KICBsaWJyYXJ5KG5uZXQpDQp9DQoNCmlmICghcmVxdWlyZSgibmV1cmFsbmV0IikpIHsNCiAgaW5zdGFsbC5wYWNrYWdlcygibmV1cmFsbmV0IikNCiAgbGlicmFyeShuZXVyYWxuZXQpDQp9DQoNCmlmICghcmVxdWlyZSgiaXByZWQiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJpcHJlZCIpDQogIGxpYnJhcnkoaXByZWQpDQp9DQoNCg0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsICAgDQogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID0gVFJVRSwgICANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BKQ0KDQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCmBgYA0KDQojIFByb2JsZW0gU3RhdGVtZW50IGFuZCBCYWNrZ3JvdW5kDQoNClRoZSBhaW0gb2YgdGhlIGZvbGxvd2luZyBhbmFseXNpcyBpcyB0byBleGFtaW5lIHRoZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gYW4gaW5kaXZpZHVhbCByZXF1ZXN0aW5nIGEgbG9hbiwgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGxvYW4gcmVxdWVzdCBhbmQgaWYgdGhlIGxvYW4gd2FzIGFwcHJvdmVkLiBUaGlzIGFuYWx5c2lzIGhvcGVzIHRvIGlkZW50aWZ5IGtleSBmYWN0b3JzIHRvIHByZWRpY3QgdGhlIHByb2JhYmlsaXR5IG9mIGEgbG9hbiByZXF1ZXN0IGJlaW5nIGFjY2VwdGVkIERpdmluZyBkZWVwIGludG8gdGhpcyB0b3BpYyB3aWxsIGhvcGVmdWxseSBwcm92aWRlIHVzZWZ1bCBpbnNpZ2h0cyBmb3IgaW5kaXZpZHVhbHMgd2hvIGFyZSBhcHBseWluZyBmb3IgYSBsb2FuIGFuZCB3aXNoIHRvIGNhbGN1bGF0ZSB0aGVpciBjaGFuY2Ugb2YgdGhlIGxvYW4gYmVpbmcgYXBwcm92ZWQuDQoNClRoaXMgZGF0YXNldCBjb21lcyBmcm9tIEthZ2dsZSwgYSBmcmVlIHdlYi1iYXNlZCBwbGF0Zm9ybSB0aGF0IGRhdGEgc2NpZW50aXN0cyBhbmQgc3RhdGlzdGljaWFucyB1c2UgdG8gc2hhcmUgYm90aCBpZGVhcyBhbmQgZGF0YXNldHMuIFRoZSBsaW5rIHRvIHRoZSAqKkxvYW4gQXBwcm92YWwgQ2xhc3NpZmljYXRpb24gRGF0YXNldCoqIGRhdGFzZXQgaXMgYmVsb3c6DQoNCltMb2FuIEFwcHJvdmFsIENsYXNzaWZpY2F0aW9uIERhdGFzZXRdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMvdGF3ZWlsby9sb2FuLWFwcHJvdmFsLWNsYXNzaWZpY2F0aW9uLWRhdGE/cmVzb3VyY2U9ZG93bmxvYWQpDQoNClRoaXMgZGF0YXNldCBtYXkgYmUgdXBkYXRlZCBvdmVyIHRpbWUuIEkgZG93bmxvYWRlZCB0aGlzIGRhdGFzZXQgb24gTW9uZGF5LCBOb3ZlbWJlciA0dGgsIDIwMjQuIExpbmsgdG8gdGhpcyB2ZXJzaW9uIG9mIHRoZSBkYXRhc2V0IGlzIGJlbG93Og0KDQpbMTEvNC8yNCBWZXJzaW9uXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vRVBLZWVwMzIvU1RBNTUxL3JlZnMvaGVhZHMvbWFpbi9sb2FuX2RhdGEuY3N2KQ0KDQpSL1IgTWFya2Rvd24gd2VyZSB1c2VkIGluIHRoaXMgcHJvamVjdCBhcyBpdCBpcyBmcmVlIGFuZCBvcGVuLXNvdXJjZSwgYWxsb3dpbmcgdXNlcnMgdG8gY3VzdG9taXplIHRoZWlyIGV4cGVyaWVuY2Ugd2l0aCB2YXJpb3VzIGxpYnJhcmllcyBhbmQgZmVhdHVyZXMgdGhhdCBvdGhlciBjb2Rpbmcgc29mdHdhcmUgc3VjaCBhcyBTQVMgZG8gbm90IGhhdmUuIFIgcHJvdmlkZXMgYW4gZWFzeS10by11c2UgYW5kIGNvbXByZWhlbnNpdmUgdG9vbHNldCBvZiBzdGF0aXN0aWNhbCBhbmFseXNlcyBhbmQgdGVzdHMuIFdoaWxlIHRoZXNlIGFkdmFuY2VkIGFuYWx5c2VzIHdpbGwgbm90IGJlIHVzZWQgaW4gdGhpcyBwcm9qZWN0LCBmdXJ0aGVyIHdvcmsgY2FuIGJlIGRvbmUgdG8gcHJvdmlkZSBhZGRpdGlvbmFsIGluc2lnaHRzIHRvIHRoZSBtYWpvciBmYWN0b3JzIGluIGxhcHRvcHMnIHByaWNlLg0KDQpUaGlzIGFuYWx5c2lzIHNlZWtzIHRvIGFuc3dlciB0aGUgcXVlc3Rpb25zOiANCg0KLSAqV2hhdCBhc3BlY3RzIG9mIGEgbG9hbiByZXF1ZXN0IGRldGVybWluZXMgdGhlIGFwcHJvdmFsIHN0YXR1cyBvZiB0aGUgcmVxdWVzdD8qDQotICpEb2VzIGRlbW9ncmFwaGljIGluZm9ybWF0aW9uIG9mIHRoZSBsb2FuIHJlcXVlc3RlciBpbmZsdWVuY2UgdGhlIGNoYW5jZXMgb2YgYSByZXF1ZXN0IGJlaW5nIGFwcHJvdmVkPyoNCg0KYGBge3IgZGF0YSBpbnRlZ3JhdGlvbiwgaW5jbHVkZSA9IFRSVUV9DQpsb2FuLmRhdGEgPC0gcmVhZC5jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9FUEtlZXAzMi9TVEE1NTEvcmVmcy9oZWFkcy9tYWluL2xvYW5fZGF0YS5jc3YiKQ0KYGBgDQoNCiMgRGVzY3JwdGlvbiBvZiB0aGUgRGF0YQ0KDQpIZXJlIGlzIHdoYXQgdGhlIGF1dGhvciBvbiBLYWdnbGUgaGFkIHRvIHNheSBhYm91dCB0aGUgKipMb2FuIEFwcHJvdmFsIENsYXNzaWZpY2F0aW9uIERhdGFzZXQqKjogIlRoaXMgZGF0YXNldCBpcyBhIHN5bnRoZXRpYyB2ZXJzaW9uIGluc3BpcmVkIGJ5IHRoZSBvcmlnaW5hbCBDcmVkaXQgUmlzayBkYXRhc2V0IG9uIEthZ2dsZSBhbmQgZW5yaWNoZWQgd2l0aCBhZGRpdGlvbmFsIHZhcmlhYmxlcyBiYXNlZCBvbiBGaW5hbmNpYWwgUmlzayBmb3IgTG9hbiBBcHByb3ZhbCBkYXRhLiBTTU9URU5DIHdhcyB1c2VkIHRvIHNpbXVsYXRlIG5ldyBkYXRhIHBvaW50cyB0byBlbmxhcmdlIHRoZSBpbnN0YW5jZXMuIFRoZSBkYXRhc2V0IGlzIHN0cnVjdHVyZWQgZm9yIGJvdGggY2F0ZWdvcmljYWwgYW5kIGNvbnRpbnVvdXMgZmVhdHVyZXMuIg0KDQpBIGRldGFpbGVkIGRlc2NyaXB0aW9uIG9mIHRoZSB2YXJpYWJsZXMgd2l0aGluIHRoZSBkYXRhc2V0IGlzIGdpdmVuIGJlbG93Og0KDQpgcGVyc29uX2FnZWA6IEFnZSBvZiB0aGUgcGVyc29uIFsqbnVtZXJpY2FsKl0NCg0KYHBlcnNvbl9nZW5kZXJgOiBHZW5kZXIgb2YgdGhlIHBlcnNvbiBbKmNhdGVnb3JpY2FsKl0NCg0KYHBlcnNvbl9lZHVjYXRpb25gOiBIaWdoZXN0IGVkdWNhdGlvbiBsZXZlbCBbKmNhdGVnb3JpY2FsKl0NCg0KYHBlcnNvbl9pbmNvbWVgOiBBbm51YWwgaW5jb21lIFsqbnVtZXJpY2FsKl0NCg0KYHBlcnNvbl9lbXBfZXhwYDogWWVhcnMgb2YgZW1wbG95bWVudCBleHBlcmllbmNlIFsqbnVtZXJpY2FsKl0NCg0KYHBlcnNvbl9ob21lX293bmVyc2hpcGA6IEhvbWUgb3duZXJzaGlwIHN0YXR1cyAoZS5nLiwgcmVudCwgb3duLCBtb3J0Z2FnZSkgWypjYXRlZ29yaWNhbCpdDQoNCmBsb2FuX2FtbnRgOiBMb2FuIGFtb3VudCByZXF1ZXN0ZWQgWypudW1lcmljYWwqXQ0KDQpgbG9hbl9pbnRlbnRgOiBQdXJwb3NlIG9mIHRoZSBsb2FuIFsqY2F0ZWdvcmljYWwqXQ0KDQpgbG9hbl9pbnRfcmF0ZWA6IExvYW4gaW50ZXJlc3QgcmF0ZSBbKm51bWVyaWNhbCpdDQoNCmBsb2FuX3BlcmNlbnRfaW5jb21lYDogTG9hbiBhbW91bnQgYXMgYSBwZXJjZW50YWdlIG9mIGFubnVhbCBpbmNvbWUgWypudW1lcmljYWwqXQ0KDQpgY2JfcGVyc29uX2NyZWRfaGlzdF9sZW5ndGhgOiBMZW5ndGggb2YgY3JlZGl0IGhpc3RvcnkgaW4geWVhcnMgWypudW1lcmljYWwqXQ0KDQpgY3JlZGl0X3Njb3JlYDogQ3JlZGl0IHNjb3JlIG9mIHRoZSBwZXJzb24gWypudW1lcmljYWwqXQ0KDQpgcHJldmlvdXNfbG9hbl9kZWZhdWx0c19vbl9maWxlYDogSW5kaWNhdG9yIG9mIHByZXZpb3VzIGxvYW4gZGVmYXVsdHMgWypjYXRlZ29yaWNhbCpdDQoNCmBsb2FuX3N0YXR1c2A6IExvYW4gYXBwcm92YWwgc3RhdHVzOiAxID0gYXBwcm92ZWQ7IDAgPSByZWplY3RlZCBbKmJpbmFyeSpdDQoNCiMjIEFkZGl0aW9uYWwgVmFyaWFibGVzDQoNCkJlbG93IGlzIGEgbGlzdCBvZiBhbnkgYWRkaXRpb25hbCB2YXJpYWJsZXMgdGhhdCB3ZXJlIGNyZWF0ZWQgZHVyaW5nIHRoZSBtb2RlbCBidWlsZGluZyBwcm9jZXNzIGFuZCB0aGVpciBkZXNjcmlwdGlvbnMuDQoNCiMjIyBEdW1teSBWYXJpYWJsZSBDcmVhdGlvbg0KDQpGb3IgcmVncmVzc2lvbiB0ZWNobmlxdWVzLCBJIGNyZWF0ZWQgZHVtbXkgdmFyaWFibGVzIGZvciBhbGwgY2F0ZWdvcmljYWwgdmFyaWFibGVzLiBUaGUgbGlzdCBvZiB0aGVtIGNhbiBiZSBmb3VuZCBiZWxvdzoNCg0KKipHZW5kZXIqKg0KYE1hbGVgOiBpZiBgcGVyc29uX2dlbmRlcmAgPSAibWFsZSIsIDEuIE90aGVyd2lzZSwgMA0KDQoqKkVkdWNhdGlvbioqDQpgSFNgOiBpZiBgcGVyc29uX2VkdWNhdGlvbmAgPSAiSGlnaCBTY2hvb2wiLCAxLiBPdGhlcndpc2UsIDANCmBBc3NvY2lhdGVgOiBpZiBgcGVyc29uX2VkdWNhdGlvbmAgPSAiQXNzb2NpYXRlIiwgMS4gT3RoZXJ3aXNlLCAwDQpgQmFjaGVsb3JgOiBpZiBgcGVyc29uX2VkdWNhdGlvbmAgPSAiQmFjaGVsb3IiLCAxLiBPdGhlcndpc2UsIDANCmBNYXN0ZXJgOiBpZiBgcGVyc29uX2VkdWNhdGlvbmAgPSAiTWFzdGVyIiwgMS4gT3RoZXJ3aXNlLCAwDQoNCioqSG9tZSBPd25lcnNoaXAqKg0KYE93bl9PdGhlcmA6IGlmIGBwZXJzb25faG9tZV9vd25lcnNoaXBgID0gIk9USEVSIiwgMS4gT3RoZXJ3aXNlLCAwDQpgT3duX093bmA6IGlmIGBwZXJzb25faG9tZV9vd25lcnNoaXBgID0gIk9XTiIsIDEuIE90aGVyd2lzZSwgMA0KYE93bl9Nb3J0Z2FnZWA6IGlmIGBwZXJzb25faG9tZV9vd25lcnNoaXBgID0gIk1PUlRHQUdFIiwgMS4gT3RoZXJ3aXNlLCAwDQoNCioqTG9hbiBJbnRlbnQqKg0KYEhvbWVJbXBgOiBpZiBgbG9hbl9pbnRlbnRgID0gIkhPTUVJTVBST1ZFTUVOVCIsIDEuIE90aGVyd2lzZSwgMA0KYERlYnRgOiBpZiBgbG9hbl9pbnRlbnRgID0gIkRFQlRDT05TT0xJREFUSU9OIiwgMS4gT3RoZXJ3aXNlLCAwDQpgUGVyc29uYWxgOiBpZiBgbG9hbl9pbnRlbnRgID0gIlBFUlNPTkFMIiwgMS4gT3RoZXJ3aXNlLCAwDQpgVmVudHVyZWA6IGlmIGBsb2FuX2ludGVudGAgPSAiVkVOVFVSRSIsIDEuIE90aGVyd2lzZSwgMA0KYE1lZGljYWxgOiBpZiBgbG9hbl9pbnRlbnRgID0gIk1FRElDQUwiLCAxLiBPdGhlcndpc2UsIDANCg0KKipQcmV2aW91cyBMb2FuKioNCmBQcmV2aW91c2A6IGlmIGBwcmV2aW91c19sb2FuX2RlZmF1bHRzX29uX2ZpbGVgID0gIlllcyIsIDEuIE90aGVyd2lzZSwgMA0KDQpJdCBpcyBjb21tb24gcHJhY3RpY2UgZm9yIHRoZSBhbW91bnQgb2YgZHVtbXkgdmFyaWFibGVzIHBlciB2YXJpYWJsZSB0byBiZSAxIGxlc3MgdGhhbiB0aGUgYW1vdW50IG9mIGdyb3Vwcy4gRm9yIGV4YW1wbGUsIHRoZXJlIGlzIG9ubHkgb25lIGR1bW15IHZhcmlhYmxlIGNyZWF0ZWQgZm9yIGBwZXJzb25fZ2VuZGVyYC4gSWYgdGhlIGFwcGxpY2FudCBoYWQgYSBnZW5kZXIgb2YgImZlbWFsZSIsIHRoZWlyIGBtYWxlYCB2YWx1ZSB3b3VsZCBiZSAwLiBUaGlzIG1lYW5zIHRoYXQgImZlbWFsZSIgaXMgdGhlIHJlZmVyZW5jZSBjYXRlZ29yeSBmb3IgYG1hbGVgLg0KDQpgYGB7ciBsb2cgcmVnIGR1bW15IGNyZWF0aW9uLCBpbmNsdWRlID0gVFJVRX0NCmxvYW4uZGF0YSRNYWxlIDwtIGlmZWxzZShsb2FuLmRhdGEkcGVyc29uX2dlbmRlciA9PSAibWFsZSIsIDEsIDApDQoNCmxvYW4uZGF0YSRIUyA8LSBpZmVsc2UobG9hbi5kYXRhJHBlcnNvbl9lZHVjYXRpb24gPT0gIkhpZ2ggU2Nob29sIiwgMSwgMCkNCmxvYW4uZGF0YSRBc3NvY2lhdGUgPC0gaWZlbHNlKGxvYW4uZGF0YSRwZXJzb25fZWR1Y2F0aW9uID09ICJBc3NvY2lhdGUiLCAxLCAwKQ0KbG9hbi5kYXRhJEJhY2hlbG9yIDwtIGlmZWxzZShsb2FuLmRhdGEkcGVyc29uX2VkdWNhdGlvbiA9PSAiQmFjaGVsb3IiLCAxLCAwKQ0KbG9hbi5kYXRhJE1hc3RlciA8LSBpZmVsc2UobG9hbi5kYXRhJHBlcnNvbl9lZHVjYXRpb24gPT0gIk1hc3RlciIsIDEsIDApDQoNCmxvYW4uZGF0YSRPd25fT3RoZXIgPC0gaWZlbHNlKGxvYW4uZGF0YSRwZXJzb25faG9tZV9vd25lcnNoaXAgPT0gIk9USEVSIiwgMSwgMCkNCmxvYW4uZGF0YSRPd25fT3duIDwtIGlmZWxzZShsb2FuLmRhdGEkcGVyc29uX2hvbWVfb3duZXJzaGlwID09ICJPV04iLCAxLCAwKQ0KbG9hbi5kYXRhJE93bl9Nb3J0Z2FnZSA8LSBpZmVsc2UobG9hbi5kYXRhJHBlcnNvbl9ob21lX293bmVyc2hpcCA9PSAiTU9SVEdBR0UiLCAxLCAwKQ0KDQpsb2FuLmRhdGEkSG9tZUltcCA8LSBpZmVsc2UobG9hbi5kYXRhJGxvYW5faW50ZW50ID09ICJIT01FSU1QUk9WRU1FTlQiLCAxLCAwKQ0KbG9hbi5kYXRhJERlYnQgPC0gaWZlbHNlKGxvYW4uZGF0YSRsb2FuX2ludGVudCA9PSAiREVCVENPTlNPTElEQVRJT04iLCAxLCAwKQ0KbG9hbi5kYXRhJFBlcnNvbmFsIDwtIGlmZWxzZShsb2FuLmRhdGEkbG9hbl9pbnRlbnQgPT0gIlBFUlNPTkFMIiwgMSwgMCkNCmxvYW4uZGF0YSRWZW50dXJlIDwtIGlmZWxzZShsb2FuLmRhdGEkbG9hbl9pbnRlbnQgPT0gIlZFTlRVUkUiLCAxLCAwKQ0KbG9hbi5kYXRhJE1lZGljYWwgPC0gaWZlbHNlKGxvYW4uZGF0YSRsb2FuX2ludGVudCA9PSAiTUVESUNBTCIsIDEsIDApDQoNCmxvYW4uZGF0YSRQcmV2aW91cyA8LSBpZmVsc2UobG9hbi5kYXRhJHByZXZpb3VzX2xvYW5fZGVmYXVsdHNfb25fZmlsZSA9PSAiWWVzIiwgMSwgMCkNCmBgYA0KDQojIyBIYW5kbGluZyBNaXNzaW5nIFZhbHVlcw0KDQpUaGUgbmV4dCBzdGVwIGlzIHRvIGRldGVybWluZSBpZiB0aGVyZSBhcmUgYW55IG1pc3NpbmcgdmFyaWFibGVzLiBNaXNzaW5nIHZhcmlhYmxlcyBjYW4gY2F1c2UgYmlhc2VkIG1vZGVscyB0byBiZSBnZW5lcmF0ZWQsIHVsdGltYXRlbHkgY2F1c2luZyBkZWNpc2lvbnMgdGhhdCBtYXkgYmUgaW5jb3JyZWN0LiANCg0KQW5vdGhlciBpc3N1ZSB3aXRoIG1pc3NpbmcgdmFsdWVzIGlzIGhvdyB0byByZXBsYWNlIHRoZW0uIFRoZXJlIGFyZSBtYW55IHdheXMgdG8gcmVwbGFjZSBtaXNzaW5nIHZhbHVlcywgaW5jbHVkaW5nIG1lYW4gYW5kIG1lZGlhbiBpbXB1dGF0aW9uLCBhbmQgbW9kZWwtYmFzZWQgaW1wdXRhdGlvbi4NCg0KSG93ZXZlciwgSSB3aWxsIGZpcnN0IGRldGVybWluZSBpZiB0aGVyZSBhcmUgYW55IG1pc3NpbmcgdmFsdWVzIHdpdGhpbiB0aGUgZGF0YXNldCBiZWZvcmUgZXBseG9yaW5nIHJlcGxhY2VtZW50IHRlY2huaXF1ZXMuDQoNCmBgYHtyIG1pc3NpbmcgdmFsdWVzLCBpbmNsdWRlID0gVFJVRX0NCm1pc3NpbmdfdmFsdWVzIDwtIGlzLm5hKGxvYW4uZGF0YSkNCnN1bW1hcnkobWlzc2luZ192YWx1ZXMpDQoNCm1pc3Npbmdfcm93cyA8LSBsb2FuLmRhdGFbIWNvbXBsZXRlLmNhc2VzKGxvYW4uZGF0YSksIF0NCnByaW50KG1pc3Npbmdfcm93cykNCmBgYA0KDQpUaGUgYWJvdmUgc3VtbWFyeSB0YWJsZSBzaG93cyB0aGF0ICoqMCoqIG9mIHRoZSB2YXJpYWJsZXMgaGF2ZSBtaXNzaW5nIHZhbHVlcy4gSWYgYW55IG9mIHRoZSB2YXJpYWJsZXMgaGFkIG1pc3NpbmcgdmFsdWVzLCB0aGVyZSB3b3VsZCBiZSBhbiBgTkE6IG5gIHJvdyBiZW5lYXRoIGl0LCB3aXRoICpuKiByZXByZXNlbnRpbmcgdGhlIG51bWJlciBvZiBtaXNzaW5nIHZhbHVlcy4gVGhlcmVmb3JlLCBubyBpbXB1dGF0aW9ucyBhcmUgbmVjZXNzYXJ5IGZvciB0aGUgZGF0YXNldC4NCg0KIyMgRmluYWwgRGF0YXNldA0KDQpUaGUgZmluYWwgZGF0YXNldCBoYXMgKio0NTAwMCoqIG9ic2VydmF0aW9ucyBlYWNoIHdpdGggKioyMyoqIHZhcmlhYmxlcy4gQWxsIDI4IHZhcmlhYmxlcyB3aWxsIGJlIGhlbGQgd2l0aGluIHRoZSBkYXRhc2V0IGZvciBhbnkgZnVydGhlciByZXNlYXJjaCwgcmVnYXJkbGVzcyBvZiB0aGVpciB1c2UgaW4gdGhpcyByZXNlYXJjaC4NCg0KQmVsb3cgaXMgYSBxdWljayBzdW1tYXJ5IG9mIGFsbCB2YXJpYWJsZXMgaW4gdGhlIGZpbmFsIGRhdGFzZXQ6DQoNCmBgYHtyIGRhdGEgc3VtbWFyeSwgaW5jbHVkZSA9IFRSVUV9DQpzdW1tYXJ5KGxvYW4uZGF0YSkNCmBgYA0KDQojIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMNCg0KTmV4dCBJIHdpbGwgcGVyZm9ybSBzb21lICoqRSoqeHBsb3JhdHBvcnkgKipEKiphdGEgKipBKipuYWx5c2lzLCBvciAqKkVEQSoqLiBFREEgaXMgYW4gaW1wb3J0YW50IHN0ZXAgaW4gdGhlIG92ZXJhcmNoaW5nIGRhdGEgYW5hbHlzaXMgcHJvY2VzcywgYXMgaXQgaXMgaW1wb3J0YW50IHRvIGZhbWlsaWFyaXplIHlvdXJzZWxmIHdpdGggdGhlIGRhdGFzZXQuIA0KDQoqKk5vdGUqKg0KSSB3aWxsIG5vdCBiZSBwZXJmb3JtaW5nIGFueSBFREEgb24gdGhlIG1hbnVhbGx5IGNyZWF0ZWQgZHVtbXkgdmFyaWFibGVzLCBhcyB0aGVpciBkaXN0cmlidXRpb25zIGFyZSBjb3ZlcmVkIGluIHRoZWlyIGNvcnJlc3BvbmRpbmcgb3JpZ2luYWwgdmFyaWFibGUuDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiAicGVyc29uX2FnZSINCg0KQmVsb3cgaXMgYSBoaXN0b2dyYW0gb2YgYHBlcnNvbl9hZ2VgOg0KDQpgYGB7ciBhZ2UgaGlzdG9ncmFtLCBpbmNsdWRlID0gVFJVRX0NCmhpc3QobG9hbi5kYXRhJHBlcnNvbl9hZ2UsDQogICAgIGJyZWFrcyA9IHNlcSgwLCAxNTAsIDEwKSwNCiAgICAgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgQXBwbGljYW50J3MgQWdlIiwNCiAgICAgeGxpbSA9IGMoMCwgMTUwKSwNCiAgICAgeGxhYiA9ICJBcHBsaWNhbnQncyBBZ2UgKHllYXJzKSIsDQogICAgIHlsaW0gPSBjKDAsIDQwMDAwKSwNCiAgICAgeWxhYiA9ICJOdW1iZXIgb2YgQXBwbGljYW50cyIsDQogICAgIGNvbCA9ICJsaWdodGJsdWUiLA0KICAgICBsYWJlbHMgPSBUUlVFKQ0KYWJsaW5lKGggPSBzZXEoNTAwMCwgNDAwMDAsIDUwMDApLCBjb2wgPSAiZ3JheSIsIGx0eSA9ICJkb3R0ZWQiKQ0KYGBgDQoNClRoZSBoaXN0b2dyYW0gYWJvdmUgc2hvd3MgdGhhdCBgcGVyc29uX2FnZWAgaGFzIGEgdmVyeSByaWdodC1za2V3ZWQgZGlzdHJpYnV0aW9uLiBPdmVyIDc1JSBvZiBhcHBsaWNhbnRzIGluIHRoZSBkYXRhc2V0IGFyZSBpbiB0aGVpciAzMCdzLiBBIGZldyBvdXRsaWVycyBtYXkgYmUgcHJlc2VudCwgd2l0aCBzb21lIGFwcGxpY2FudHMgb3ZlciB0aGUgYWdlIG9mIDEwMCB5ZWFycyBvbGQuDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiAicGVyc29uX2dlbmRlciINCg0KQmVsb3cgaXMgYSBwaWUgY2hhcnQgb2YgYHBlcnNvbl9nZW5kZXJgOg0KDQpgYGB7ciBnZW5kZXIgcGllIGNoYXJ0LCBpbmNsdWRlID0gVFJVRX0NCnBzdCA8LSBsb2FuLmRhdGEgJT4lDQogIGdyb3VwX2J5KHBlcnNvbl9nZW5kZXIpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwZXJjID0gY291bnQgLyBzdW0oY291bnQpKSAlPiUNCiAgYXJyYW5nZShwZXJjKSAlPiUNCiAgbXV0YXRlKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudChwZXJjKSkNCg0KZ2dwbG90KHBzdCwgYWVzKHggPSAiIiwgeSA9IHBlcmMsIGZpbGwgPSBwZXJzb25fZ2VuZGVyKSkgKyANCiAgZ2VvbV9jb2woKSArIA0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcGFzdGUoY291bnQsICJcbigiLCBsYWJlbHMsICIpIiwgc2VwID0gIiIpKSwgDQogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gKDAuNSkpKSArIA0KICBjb29yZF9wb2xhcih0aGV0YSA9ICJ5IikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJmZW1hbGUiID0gIiNGRjk5OTkiLCAibWFsZSIgPSAiIzY2QjNGRiIpLCANCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiRmVtYWxlIiwgIk1hbGUiKSkgKw0KICBsYWJzKHRpdGxlID0gIlBpZSBDaGFydCBvZiBBcHBsaWNhbnQncyBHZW5kZXIiLCB5ID0gIiIsIHggPSAiIikgKw0KICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICJHZW5kZXIiKSkNCmBgYA0KDQpBcyBzaG93biBpbiB0aGUgcGllIGNoYXJ0IGFib3ZlLCA1NSUgb2Ygb3VyIGxvYW4gYXBwbGljYW50cyBhcmUgbWFsZSwgYW5kIDQ1JSBhcmUgZmVtYWxlLg0KDQojIyBEaXN0cmlidXRpb24gb2YgInBlcnNvbl9lZHVjYXRpb24iDQoNCkJlbG93IGlzIGEgcGllIGNoYXJ0IG9mIGBwZXJzb25fZWR1Y2F0aW9uYDoNCg0KYGBge3IgZWR1Y2F0aW9uIHBpZSBjaGFydCwgaW5jbHVkZSA9IFRSVUV9DQpwc3QgPC0gbG9hbi5kYXRhICU+JQ0KICBncm91cF9ieShwZXJzb25fZWR1Y2F0aW9uKSAlPiUNCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUocGVyYyA9IGNvdW50IC8gc3VtKGNvdW50KSkgJT4lDQogIGFycmFuZ2UocGVyYykgJT4lDQogIG11dGF0ZShsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQocGVyYyksDQogICAgICAgICBwZXJzb25fZWR1Y2F0aW9uID0gZmFjdG9yKHBlcnNvbl9lZHVjYXRpb24sIGxldmVscyA9IGMoIkhpZ2ggU2Nob29sIiwgICJBc3NvY2lhdGUiLCAiQmFjaGVsb3IiLCAiTWFzdGVyIiwgIkRvY3RvcmF0ZSIpKSkNCg0KZ2dwbG90KHBzdCwgYWVzKHggPSAiIiwgeSA9IHBlcmMsIGZpbGwgPSBwZXJzb25fZWR1Y2F0aW9uKSkgKyANCiAgZ2VvbV9jb2woKSArIA0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcGFzdGUoY291bnQsICJcbigiLCBsYWJlbHMsICIpIiwgc2VwID0gIiIpKSwgDQogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gKDAuNSkpKSArIA0KICBjb29yZF9wb2xhcih0aGV0YSA9ICJ5IikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJIaWdoIFNjaG9vbCIgPSAiI0JCREVGQiIsICJBc3NvY2lhdGUiID0gIiM5MENBRjkiLCAiQmFjaGVsb3IiID0gIiM0MkE1RjUiLCAiTWFzdGVyIiA9ICIjMUU4OEU1IiwgIkRvY3RvcmF0ZSIgPSAiIzE1NjVDMCIpLCANCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiSGlnaCBTY2hvb2wgRGlwbG9tYSIsICJBc3NvY2lhdGUncyBEZWdyZWUiLCAiQmFjaGVsb3IncyBEZWdyZWUiLCAiTWFzdGVyJ3MgRGVncmVlIiwgIkRvY3RvcmF0ZSdzIERlZ3JlZSIpKSArDQogIGxhYnModGl0bGUgPSAiUGllIENoYXJ0IG9mIEFwcGxpY2FudCdzIEhpZ2hlc3QgQ29tcGxldGVkIEVkdWNhdGlvbiBMZXZlbCIsIHkgPSAiIiwgeCA9ICIiKSArDQogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIkVkdWNhdGlvbiBMZXZlbCIpKQ0KYGBgDQoNCkFzIHNob3duIGluIHRoZSBwaWUgY2hhcnQgYWJvdmUsIHRoZSBtb3N0IGNvbW1vbiBlZHVjYXRpb24gbGV2ZWwgYW1vbmcgYXBwbGljYW50cyBpcyBhbiBhc3NvY2lhdGUncyBkZWdyZWUgKDI5Ljc4JSkuDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiAicGVyc29uX2luY29tZSINCg0KQmVsb3cgaXMgYSBoaXN0b2dyYW0gb2YgYHBlcnNvbl9pbmNvbWVgOg0KDQpgYGB7ciBpbmNvbWUgaGlzdG9ncmFtLCBpbmNsdWRlID0gVFJVRX0NCmhpc3QobG9hbi5kYXRhJHBlcnNvbl9pbmNvbWUsDQogICAgIGJyZWFrcyA9IHNlcSgwLCA3MzAwMDAwLCAxMDAwMDApLA0KICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBBcHBsaWNhbnQncyBJbmNvbWUiLA0KICAgICB4bGltID0gYygwLCAyMDAwMDAwKSwNCiAgICAgeGxhYiA9ICJBcHBsaWNhbnQncyBZZWFybHkgSW5jb21lIChVU0QpIiwNCiAgICAgeWxpbSA9IGMoMCwgNDAwMDApLA0KICAgICB5bGFiID0gIk51bWJlciBvZiBBcHBsaWNhbnRzIiwNCiAgICAgY29sID0gImxpZ2h0Z3JlZW4iLA0KICAgICBsYWJlbHMgPSBUUlVFKQ0KYWJsaW5lKGggPSBzZXEoNTAwMCwgNDAwMDAsIDUwMDApLCBjb2wgPSAiZ3JheSIsIGx0eSA9ICJkb3R0ZWQiKQ0KYGBgDQoNClRoZSBoaXN0b2dyYW0gYWJvdmUgc2hvd3MgdGhhdCBgcGVyc29uX2luY29tZWAgaGFzIGEgdmVyeSByaWdodC1za2V3ZWQgZGlzdHJpYnV0aW9uLiBPdmVyIDc1JSBvZiBhcHBsaWNhbnRzIGluIHRoZSBkYXRhc2V0IGhhdmUgYW4gYW5udWFsIGluY29tZSBvZiBsZXNzIHRoYW4gXCQxMDAsMDAwLiBBIGZldyBvdXRsaWVycyBtYXkgYmUgcHJlc2VudCwgd2l0aCBzb21lIGFwcGxpY2FudHMgb3ZlciAkMS41IG1pbGxpb24uDQoNCioqTk9URSoqOg0KRm9yIHZpc3VhbGl6YXRpb24gcHVycG9zZXMsIDMgYXBwbGljYW50cyBhcmUgbm90IHNob3duIGluIHRoZSBjaGFydCBhYm92ZSwgd2hvIGhhdmUgaW5jb21lIHZhbHVlcyBvZiBcJDUsNTQ1LDU0NS4wMCwgXCQ1LDU1NiwzOTkuMDAsIGFuZCBcJDcsMjAwLDc2Ni4wMC4NCg0KIyMgRGlzdHJpYnV0aW9uIG9mICJwZXJzb25fZW1wX2V4cCINCg0KQmVsb3cgaXMgYSBoaXN0b2dyYW0gb2YgYHBlcnNvbl9lbXBfZXhwYDoNCg0KYGBge3IgZW1wbG95bWVudCBleHBlcmllbmNlIGhpc3RvZ3JhbSwgaW5jbHVkZSA9IFRSVUV9DQpoaXN0KGxvYW4uZGF0YSRwZXJzb25fZW1wX2V4cCwNCiAgICAgYnJlYWtzID0gc2VxKDAsIDEyNSwgNSksDQogICAgIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIEFwcGxpY2FudCdzIEVtcGxveW1lbnQgRXhwZXJpZW5jZSIsDQogICAgIHhsaW0gPSBjKDAsIDEyNSksDQogICAgIHhheHQgPSAibiIsDQogICAgIHhsYWIgPSAiQXBwbGljYW50J3MgRW1wbG95bWVudCBFeHBlcmllbmNlIChZZWFycykiLA0KICAgICB5bGltID0gYygwLCA0MDAwMCksDQogICAgIHlsYWIgPSAiTnVtYmVyIG9mIEFwcGxpY2FudHMiLA0KICAgICBjb2wgPSAieWVsbG93IiwNCiAgICAgbGFiZWxzID0gVFJVRSkNCmFibGluZShoID0gc2VxKDUwMDAsIDQwMDAwLCA1MDAwKSwgY29sID0gImdyYXkiLCBsdHkgPSAiZG90dGVkIikNCmF4aXMoMSwgYXQgPSBzZXEoMCwgMTI1LCAyNSkpDQpgYGANCg0KVGhlIGhpc3RvZ3JhbSBhYm92ZSBzaG93cyB0aGF0IGBwZXJzb25fZW1wX2V4cGAgaGFzIGEgdmVyeSByaWdodC1za2V3ZWQgZGlzdHJpYnV0aW9uLiBPdmVyIDYwJSBvZiBhcHBsaWNhbnRzIGluIHRoZSBkYXRhc2V0IGhhdmUgbGVzcyB0aGFuIDUgeWVhcnMgb2YgZW1wbG95bWVudCBleHBlcmllbmNlLiBBIGZldyBvdXRsaWVycyBtYXkgYmUgcHJlc2VudCwgd2l0aCBzb21lIGFwcGxpY2FudHMgb3ZlciA3NSB5ZWFycyBvZiBlbXBsb3ltZW50IGV4cGVyaWVuY2UuDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiAicGVyc29uX2hvbWVfb3duZXJzaGlwIg0KDQpCZWxvdyBpcyBhIGJhciBncmFwaCBjaGFydCBvZiBgcGVyc29uX2hvbWVfb3duZXJzaGlwYA0KDQpgYGB7ciBob21lIG93bmVyc2hpcCBiYXIgZ3JhcGgsIGluY2x1ZGUgPSBUUlVFfQ0KbGRzIDwtIGxvYW4uZGF0YSAlPiUNCiAgY291bnQocGVyc29uX2hvbWVfb3duZXJzaGlwKSAlPiUNCiAgbXV0YXRlKHBlcmMgPSBuIC8gc3VtKG4pKjEwMCwgDQogICAgICAgICBsYWJlbCA9IHBhc3RlKG4sICIgKCIsIHJvdW5kKHBlcmMsIDEpLCAiJSkiLCBzZXAgPSAiIikpDQoNCmdncGxvdChsZHMsIGFlcyh4ID0gcmVvcmRlcihwZXJzb25faG9tZV9vd25lcnNoaXAsIG4pLCB5ID0gbikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRwaW5rIikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbGFiZWwpLCB2anVzdCA9IC0wLjUpICsNCiAgbGFicyh0aXRsZSA9ICJBcHBsaWNhbnQncyBIb21lIE93bmVyc2hpcCIsIHggPSAiT3duZXJzaGlwIFN0YXR1cyIsIHkgPSAiQ291bnQiKQ0KYGBgDQoNCkFzIHNob3duIGFib3ZlLCBvdmVyIGhhbGYgb2YgdGhlIGFwcGxpY2FudHMgY3VycmVudGx5IHJlbnQgdGhlaXIgaG9tZS4NCg0KIyMgRGlzdHJpYnV0aW9uIG9mICJsb2FuX2FtbnQiDQoNCkJlbG93IGlzIGEgaGlzdG9ncmFtIG9mIGBsb2FuX2FtbnRgOg0KDQpgYGB7ciBsb2FuIGFtb3VudCBoaXN0b2dyYW0sIGluY2x1ZGUgPSBUUlVFfQ0KaGlzdChsb2FuLmRhdGEkbG9hbl9hbW50LA0KICAgICBicmVha3MgPSBzZXEoMCwgMzUwMDAsIDI1MDApLA0KICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBSZXF1ZXN0ZWQgTG9hbiBBbW91bnQiLA0KICAgICB4bGltID0gYygwLCAzNTAwMCksDQogICAgIHhsYWIgPSAiUmVxdWVzdGVkIExvYW4gQW1vdW50IChVU0QpIiwNCiAgICAgeWxpbSA9IGMoMCwgMTAwMDApLA0KICAgICB5bGFiID0gIk51bWJlciBvZiBBcHBsaWNhbnRzIiwNCiAgICAgY29sID0gInZpb2xldCIsDQogICAgIGxhYmVscyA9IFRSVUUpDQphYmxpbmUoaCA9IHNlcSgxMDAwLCAxMDAwMCwgMTAwMCksIGNvbCA9ICJncmF5IiwgbHR5ID0gImRvdHRlZCIpDQpgYGANCg0KVGhlIGhpc3RvZ3JhbSBhYm92ZSBzaG93cyB0aGF0IGBsb2FuX2FtbnRgIGhhcyBhIHJpZ2h0LXNrZXdlZCBkaXN0cmlidXRpb24uIEEgbWFqb3JpdHkgb2YgYXBwbGljYW50cyBpbiB0aGUgZGF0YXNldCByZXF1ZXN0ZWQgYmV0d2VlbiBcJDIsNTAwIGFuZCBcJDEwLDAwMC4gQSBmZXcgb3V0bGllcnMgbWF5IGJlIHByZXNlbnQsIHdpdGggc29tZSBhcHBsaWNhbnRzIHJlcXVlc3Rpbmcgb3ZlciBcJDMwLDAwMC4NCg0KIyMgRGlzdHJpYnV0aW9uIG9mICJsb2FuX2ludGVudCINCg0KQmVsb3cgaXMgYSBiYXIgZ3JhcGggY2hhcnQgb2YgYGxvYW5faW50ZW50YA0KDQpgYGB7ciBsb2FuIGludGVudCBiYXIgZ3JhcGgsIGluY2x1ZGUgPSBUUlVFfQ0KbGRzIDwtIGxvYW4uZGF0YSAlPiUNCiAgY291bnQobG9hbl9pbnRlbnQpICU+JQ0KICBtdXRhdGUocGVyYyA9IG4gLyBzdW0obikqMTAwLCANCiAgICAgICAgIGxhYmVsID0gcGFzdGUobiwgIlxuKCIsIHJvdW5kKHBlcmMsIDEpLCAiJSkiLCBzZXAgPSAiIikpDQoNCmdncGxvdChsZHMsIGFlcyh4ID0gcmVvcmRlcihsb2FuX2ludGVudCwgbiksIHkgPSBuKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJkYXJrcmVkIikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbGFiZWwsIHkgPSBuIC8gMiksIGhqdXN0ID0gMC41LCBjb2xvciA9ICJ3aGl0ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJBcHBsaWNhbnQncyBMb2FuIEludGVudCIsIHggPSAiTG9hbiBJbnRlbnQiLCB5ID0gIkNvdW50IikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KYGBgDQoNCkFzIHNob3duIGFib3ZlLCBhcHBsaWNhbnRzIGFyZSBhcHBseWluZyBmb3IgYSBsb2FuIGZvciBtYW55IGRpZmZlcmVudCByZWFzb25zLCB3aXRoIHRoZSBtb3N0IHBvcHVsYXIgYmVpbmcgYW4gZWR1Y2F0aW9uIGxvYW4uDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiAibG9hbl9pbnRfcmF0ZSINCg0KQmVsb3cgaXMgYSBoaXN0b2dyYW0gb2YgYGxvYW5faW50X3JhdGVgOg0KDQpgYGB7ciBpbnRlcmVzdCByYXRlIGhpc3RvZ3JhbSwgaW5jbHVkZSA9IFRSVUV9DQpoaXN0KGxvYW4uZGF0YSRsb2FuX2ludF9yYXRlLA0KICAgICBicmVha3MgPSBzZXEoMCwgMjAsIDIpLA0KICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBMb2FuIEludGVyZXN0IFJhdGUiLA0KICAgICB4bGltID0gYygwLCAyMCksDQogICAgIHhheHQgPSAibiIsDQogICAgIHhsYWIgPSAiSW50ZXJlc3QgcmF0ZSAoJSkiLA0KICAgICB5bGltID0gYygwLCAxNTAwMCksDQogICAgIHlsYWIgPSAiTnVtYmVyIG9mIEFwcGxpY2FudHMiLA0KICAgICBjb2wgPSAiZ3JheTUwIiwNCiAgICAgbGFiZWxzID0gVFJVRSkNCmFibGluZShoID0gc2VxKDI1MDAsIDE1MDAwLCAyNTAwKSwgY29sID0gImdyYXkiLCBsdHkgPSAiZG90dGVkIikNCmF4aXMoMSwgYXQgPSBzZXEoMCwgMjAsIDIpKQ0KYGBgDQoNClRoZSBoaXN0b2dyYW0gYWJvdmUgc2hvd3MgdGhhdCBgbG9hbl9pbnRfcmF0ZWAgaGFzIGEgZmFpcmx5IE5vcm1hbC4gTW9zdCBhcHBsaWNhbnRzIGFyZSBhcHBseWluZyBmb3IgYSBsb2FuIHdpdGggYW4gaW50ZXJlc3QgcmF0ZSBiZXR3ZWVuIDEwLTEyJS4NCg0KIyMgRGlzdHJpYnV0aW9uIG9mICJsb2FuX3BlcmNlbnRfaW5jb21lIg0KDQpCZWxvdyBpcyBhIGhpc3RvZ3JhbSBvZiBgbG9hbl9wZXJjZW50X2luY29tZWA6DQoNCmBgYHtyIGluY29tZSByYXRlIGhpc3RvZ3JhbSwgaW5jbHVkZSA9IFRSVUV9DQpoaXN0KGxvYW4uZGF0YSRsb2FuX3BlcmNlbnRfaW5jb21lLA0KICAgICBicmVha3MgPSBzZXEoMCwgMSwgLjEpLA0KICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBMb2FuIFBlcmNlbnRhZ2Ugb2YgQW5udWFsIEluY29tZSIsDQogICAgIHhsaW0gPSBjKDAsIDEpLA0KICAgICB4bGFiID0gIkxvYW4gUGVyY2VudGFnZSBvZiBBbm51YWwgSW5jb21lICglKSIsDQogICAgIHlsaW0gPSBjKDAsIDIwMDAwKSwNCiAgICAgeWxhYiA9ICJOdW1iZXIgb2YgQXBwbGljYW50cyIsDQogICAgIGNvbCA9ICJsYXduZ3JlZW4iLA0KICAgICBsYWJlbHMgPSBUUlVFKQ0KYWJsaW5lKGggPSBzZXEoMjUwMCwgMjAwMDAsIDI1MDApLCBjb2wgPSAiZ3JheSIsIGx0eSA9ICJkb3R0ZWQiKQ0KYGBgDQoNClRoZSBoaXN0b2dyYW0gYWJvdmUgc2hvd3MgdGhhdCBgbG9hbl9wZXJjZW50X2luY29tZWAgaGFzIGEgdmVyeSByaWdodC1za2V3ZWQgZGlzdHJpYnV0aW9uLg0KDQojIyBEaXN0cmlidXRpb24gb2YgImNiX3BlcnNvbl9jcmVkX2hpc3RfbGVuZ3RoIg0KDQpCZWxvdyBpcyBhIGhpc3RvZ3JhbSBvZiBgY2JfcGVyc29uX2NyZWRfaGlzdF9sZW5ndGhgOg0KDQpgYGB7ciBjcmVkaXQgaGlzdG9yeSBoaXN0b2dyYW0sIGluY2x1ZGUgPSBUUlVFfQ0KaGlzdChsb2FuLmRhdGEkY2JfcGVyc29uX2NyZWRfaGlzdF9sZW5ndGgsDQogICAgIGJyZWFrcyA9IHNlcSgwLCAzMCwgMyksDQogICAgIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIEFwcGxpY2FudCdzIExlbmd0aCBvZiBDcmVkaXQgSGlzdG9yeSIsDQogICAgIHhsaW0gPSBjKDAsIDMwKSwNCiAgICAgeGxhYiA9ICJBcHBsaWNhbnQncyBMZW5ndGggb2YgQ3JlZGl0IEhpc3RvcnkgKFllYXJzKSIsDQogICAgIHlsaW0gPSBjKDAsIDIwMDAwKSwNCiAgICAgeWxhYiA9ICJOdW1iZXIgb2YgQXBwbGljYW50cyIsDQogICAgIGNvbCA9ICJnb2xkIiwNCiAgICAgbGFiZWxzID0gVFJVRSkNCmFibGluZShoID0gc2VxKDI1MDAsIDIwMDAwLCAyNTAwKSwgY29sID0gImdyYXkiLCBsdHkgPSAiZG90dGVkIikNCmBgYA0KDQpUaGUgaGlzdG9ncmFtIGFib3ZlIHNob3dzIHRoYXQgYGNiX3BlcnNvbl9jcmVkX2hpc3RfbGVuZ3RoYCBoYXMgYSB2ZXJ5IHJpZ2h0LXNrZXdlZCBkaXN0cmlidXRpb24uDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiAiY3JlZGl0X3Njb3JlIg0KDQpCZWxvdyBpcyBhIGhpc3RvZ3JhbSBvZiBgY3JlZGl0X3Njb3JlYDoNCg0KYGBge3IgY3JlZGl0IHNjb3JlIGhpc3RvZ3JhbSwgaW5jbHVkZSA9IFRSVUV9DQpoaXN0KGxvYW4uZGF0YSRjcmVkaXRfc2NvcmUsDQogICAgIGJyZWFrcyA9IHNlcSgzNTAsIDkwMCwgNTApLA0KICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBBcHBsaWNhbnQncyBDcmVkaXQgU2NvcmUiLA0KICAgICB4bGltID0gYygzNTAsIDkwMCksDQogICAgIHhheHQgPSAibiIsDQogICAgIHhsYWIgPSAiQXBwbGljYW50J3MgQ3JlZGl0IFNjb3JlIiwNCiAgICAgeWxpbSA9IGMoMCwgMjAwMDApLA0KICAgICB5bGFiID0gIk51bWJlciBvZiBBcHBsaWNhbnRzIiwNCiAgICAgY29sID0gInBsdW0iLA0KICAgICBsYWJlbHMgPSBUUlVFKQ0KYWJsaW5lKGggPSBzZXEoMjUwMCwgMjAwMDAsIDI1MDApLCBjb2wgPSAiZ3JheSIsIGx0eSA9ICJkb3R0ZWQiKQ0KYXhpcygxLCBhdCA9IHNlcSgzNTAsIDkwMCwgNTApKQ0KYGBgDQoNClRoZSBoaXN0b2dyYW0gYWJvdmUgc2hvd3MgdGhhdCBgY3JlZGl0X3Njb3JlYCBoYXMgYSBzbGlnaHRseSBsZWZ0LXNrZXdlZCBkaXN0cmlidXRpb24uIEEgbWFqb3JpdHkgb2YgYXBwbGljYW50cyBpbiB0aGUgZGF0YXNldCBoYXZlIGEgY3JlZGl0IHNjb3JlIGJldHdlZW4gNjAwIGFuZCA3MDAuDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiAicHJldmlvdXNfbG9hbl9kZWZhdWx0c19vbl9maWxlIg0KDQpCZWxvdyBpcyBhIHBpZSBjaGFydCBvZiBgcHJldmlvdXNfbG9hbl9kZWZhdWx0c19vbl9maWxlYDoNCg0KYGBge3IgcHJldmlvdXMgbG9hbiBwaWUgY2hhcnQsIGluY2x1ZGUgPSBUUlVFfQ0KcHN0IDwtIGxvYW4uZGF0YSAlPiUNCiAgZ3JvdXBfYnkocHJldmlvdXNfbG9hbl9kZWZhdWx0c19vbl9maWxlKSAlPiUNCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUocGVyYyA9IGNvdW50IC8gc3VtKGNvdW50KSkgJT4lDQogIGFycmFuZ2UocGVyYykgJT4lDQogIG11dGF0ZShsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQocGVyYykpDQoNCmdncGxvdChwc3QsIGFlcyh4ID0gIiIsIHkgPSBwZXJjLCBmaWxsID0gcHJldmlvdXNfbG9hbl9kZWZhdWx0c19vbl9maWxlKSkgKyANCiAgZ2VvbV9jb2woKSArIA0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcGFzdGUoY291bnQsICJcbigiLCBsYWJlbHMsICIpIiwgc2VwID0gIiIpKSwgDQogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gKDAuNSkpKSArIA0KICBjb29yZF9wb2xhcih0aGV0YSA9ICJ5IikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJObyIgPSAicGFsZXZpb2xldHJlZCIsICJZZXMiID0gImxpZ2h0Z3JlZW4iKSwgDQogICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIk5vIiwgIlllcyIpKSArDQogIGxhYnModGl0bGUgPSAiUGllIENoYXJ0IG9mIFByZXZpb3VzIExvYW4gU3RhdHVzIiwgeSA9ICIiLCB4ID0gIiIpICsNCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQodGl0bGUgPSAiRGlkIHRoZSBhcHBsaWNhbnRcbmhhdmUgYSBwcmV2aW91cyBsb2FuPyIpKQ0KYGBgDQoNCkFzIHNob3duIGluIHRoZSBwaWUgY2hhcnQgYWJvdmUsIDUwLjglIG9mIG91ciBsb2FuIGFwcGxpY2FudHMgaGFkIHByZXZpb3VzbHkgdG9vayBvdXQgYSBsb2FuLg0KDQojIyBEaXN0cmlidXRpb24gb2YgImxvYW5fc3RhdHVzIg0KDQpCZWxvdyBpcyBhIHBpZSBjaGFydCBvZiBgbG9hbl9zdGF0dXNgOg0KDQpgYGB7ciBsb2FuIHN0YXR1cyBwaWUgY2hhcnQsIGluY2x1ZGUgPSBUUlVFfQ0KcHN0IDwtIGxvYW4uZGF0YSAlPiUNCiAgZ3JvdXBfYnkobG9hbl9zdGF0dXMpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwZXJjID0gY291bnQgLyBzdW0oY291bnQpKSAlPiUNCiAgYXJyYW5nZShwZXJjKSAlPiUNCiAgbXV0YXRlKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudChwZXJjKSwNCiAgICAgICAgIGxvYW5fc3RhdHVzID0gZmFjdG9yKGxvYW5fc3RhdHVzLCBsZXZlbHMgPSBjKDAsIDEpLCBsYWJlbHMgPSBjKCJObyIsICJZZXMiKSkpDQoNCmdncGxvdChwc3QsIGFlcyh4ID0gIiIsIHkgPSBwZXJjLCBmaWxsID0gbG9hbl9zdGF0dXMpKSArIA0KICBnZW9tX2NvbCgpICsgDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZShjb3VudCwgIlxuKCIsIGxhYmVscywgIikiLCBzZXAgPSAiIikpLCANCiAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAoMC41KSkpICsgDQogIGNvb3JkX3BvbGFyKHRoZXRhID0gInkiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIk5vIiA9ICJwYWxldmlvbGV0cmVkIiwgIlllcyIgPSAibGlnaHRncmVlbiIpLCANCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiTm8iLCAiWWVzIikpICsNCiAgbGFicyh0aXRsZSA9ICJQaWUgQ2hhcnQgb2YgTG9hbiBTdGF0dXMiLCB5ID0gIiIsIHggPSAiIikgKw0KICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICJEaWQgdGhlIGxvYW4gZ2V0IGFwcHJvdmVkPyIpKQ0KYGBgDQoNCkFzIHNob3duIGluIHRoZSBwaWUgY2hhcnQgYWJvdmUsIG9ubHkgMjIlIG9mIHRoZSBsb2FucyBnb3QgYXBwcm92ZWQuDQoNCiMgTW9kZWxpbmcgVGVjaG5pcXVlcw0KDQpOZXh0LCBJIHdpbGwgZ28gdGhyb3VnaCA0IGRpZmZlcmVudCBtb2RlbGluZyB0ZWNobmlxdWVzIGluIGFuIGF0dGVtcHQgdG8gZmluZCB0aGUgYmVzdCBwb3NzaWJsZSBtb2RlbC4gVGhlIDQgdGVjaG5pcXVlcyB0aGF0IEkgd2lsbCB1c2UgYXJlOg0KDQoxKSBMb2dpc3RpYyBSZWdyZXNzaW9uDQoNCjIpIFBlcmNlcHRyb24NCg0KMykgRGVjaXNpb24gVHJlZQ0KDQo0KSBCYWdnaW5nDQoNCkVhY2ggb2YgdGhlIDQgbGlzdGVkLWFib3ZlIGFsZ29yaXRobXMgYXJlIHBvd2VyZnVsIGluIHRoZWlyIG93biB3YXkuIEkgd2lsbCBmaW5kIHRoZSBiZXN0IG1vZGVsIHdpdGggZWFjaCB0ZWNobmlxdWUsIGFuZCB0aGVuIGNvbXBhcmUgZWFjaCBtb2RlbCB0byBlYWNoIG90aGVyIHRvIGRldGVybWluZSBubyBvbmx5IHRoZSBiZXN0IG1vZGVsIGJ1dCBiZXN0IG1vZGVsaW5nIHRlY2huaXF1ZS4NCg0KSW4gb3JkZXIgdG8gbGF0ZXIgdGVzdCB0aGUgbW9kZWxzLCBJIHNwbGl0IHRoZSBkYXRhc2V0IGludG8gdHdvIHNlcGFyYXRlIGRhdGFzZXRzOiAqKlRyYWluaW5nIERhdGEqKiBhbmQgKipUZXN0aW5nIERhdGEqKi4gRG9pbmcgc28gd2lsbCBhbGxvdyBmb3IgdGhlIG1vZGVscyB0byBiZSB0ZXN0ZWQgb24gcmVhbCBkYXRhIHdpdGhvdXQgdGhlIG5lZWQgZm9yIGFkZGl0aW9uYWwgcmVzZWFyY2ggb3Igc2FtcGxpbmcuIFJvdWdobHkgNzUlIG9mIHRoZSBvdmVyYWxsIGRhdGFzZXQgd2lsbCBiZSBpbiB0aGUgdHJhaW5pbmcgZGF0YXNldCwgYW5kIHRoZSByZW1haW5pbmcgfjI1JSB3aWxsIGJlIGluIHRoZSB0ZXN0aW5nIGRhdGFzZXQuIA0KDQpgYGB7ciBkYXRhIHNwbGl0LCBpbmNsdWRlID0gVFJVRX0NCnNldC5zZWVkKDMyMykNCmluZGV4IDwtIHNhbXBsZS5zcGxpdChZID0gbG9hbi5kYXRhJGxvYW5fc3RhdHVzLCBTcGxpdFJhdGlvID0gMC43NSkNCnRyYWluLmRhdGEgPC0gbG9hbi5kYXRhW2luZGV4LCBdDQp0ZXN0LmRhdGEgPC0gbG9hbi5kYXRhWyFpbmRleCwgXQ0KYGBgDQoNCiMjIExvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KTG9naXN0aWMgUmVncmVzc2lvbiBidWlsZHMgYSBsaW5lYXIgbW9kZWwgZm9yIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiB0aGF0IGVzdGltYXRlcyB0aGUgcHJvYmFiaWxpdHkgb2YgYW55IHNwZWNpZmllZCBvdXRjb21lLiBJdCBlc3RpbWF0ZXMgdGhlIHByb2JhYmlsaXR5IG9mIGEgYmluYXJ5IG91dGNvbWUgYmFzZWQgb24gb25lIG9yIG1vcmUgcHJlZGljdG9yIHZhcmlhYmxlcy4gSXQgdGhlbiB1c2VzIHRoZSBsb2dpc3RpYyBmdW5jdGlvbiB0byBtYXAgcHJlZGljdGlvbnMgdG8gYSBwcm9iYWJpbGl0eSBiZXR3ZWVuIDAgYW5kIDEuDQoNCiMjIyBUZWNobmlxdWUNCg0KSSB3aWxsIGJlZ2luIGJ5IHJ1bm5pbmcgdGhlIGZ1bGwgbW9kZWwgd2l0aCBhbGwgcHJlZGljdG9yIHZhcmlhYmxlcy4gT25jZSByYW4sIEkgd2lsbCBzZWUgaWYgdGhlcmUgYXJlIGFueSBoaWdobHkgY29ycmVsYXRlZCB2YXJpYWJsZXMgd2l0aGluIHRoZSBtb2RlbC4gSWYgc28sIHRoZSBhc3N1bXB0aW9uIG9mIG11bHRpY29sbGluZWFyaXR5IHdpbGwgYmUgYnJva2VuIGFuZCB0aGUgbW9kZWwgd2lsbCBiZSBiaWFzZWQuDQoNCklmIHRoZXJlIGFyZSBhbnkgaGlnaGx5IGNvcnJlbGF0ZWQgdmFyaWFibGVzLCBJIHdpbGwgcmVtb3ZlIHRoZW0gb25lLWJ5LW9uZSBhbmQgcmUtcnVuIHRoZSBtb2RlbCwgbm93IHdpdGggb25lIGxlc3MgdmFyaWFibGUuIE9uY2UgYWxsIGNvcnJlbGF0ZWQgcHJlZGljdG9ycyBhcmUgcmVtb3ZlZCwgSSB3aWxsIHJlbW92ZSBhbnkgaW5zaWduaWZpY2FudCB2YXJpYWJsZXMgZnJvbSB0aGUgbW9kZWwgdW50aWwgdGhlcmUgYXJlIG9ubHkgc2lnbmlmaWNhbnQgdmFyaWFibGVzIGxlZnQuIFRoZSByZW1haW5pbmcgdmFyaWFibGVzIHdpbGwgbWFrZSB1cCB0aGUgKipGaW5hbCBNb2RlbCoqLg0KDQpgYGB7ciBsb2cgcmVnIG1vZGVsIDEsIGluY2x1ZGUgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQ0KbG9nLnJlZy4xIDwtIGdsbShmb3JtdWxhID0gbG9hbl9zdGF0dXMgfiBwZXJzb25fYWdlICsgTWFsZSArIEhTICsgQXNzb2NpYXRlICsgQmFjaGVsb3IgKyBNYXN0ZXIgKyBwZXJzb25faW5jb21lICsgcGVyc29uX2VtcF9leHAgKyBPd25fT3RoZXIgKyBPd25fT3duICsgT3duX01vcnRnYWdlICsgbG9hbl9hbW50ICsgSG9tZUltcCArIERlYnQgKyBQZXJzb25hbCArIFZlbnR1cmUgKyBNZWRpY2FsICsgbG9hbl9pbnRfcmF0ZSArIGxvYW5fcGVyY2VudF9pbmNvbWUgKyBjYl9wZXJzb25fY3JlZF9oaXN0X2xlbmd0aCArIGNyZWRpdF9zY29yZSArIFByZXZpb3VzLA0KICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLA0KICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW4uZGF0YSkNCg0Kc3VtbWFyeShsb2cucmVnLjEpDQpgYGANCg0KYGBge3IgbG9nIHJlZyBtb2RlbCAxIHZpZiwgaW5jbHVkZSA9IEZBTFNFLCBlY2hvID0gRkFMU0V9DQp2aWYobG9nLnJlZy4xKQ0KYGBgDQoNCmBgYHtyIGxvZyByZWcgbW9kZWwgMiwgaW5jbHVkZSA9IEZBTFNFLCBlY2hvID0gRkFMU0V9DQpsb2cucmVnLjIgPC0gZ2xtKGZvcm11bGEgPSBsb2FuX3N0YXR1cyB+IE1hbGUgKyBIUyArIEFzc29jaWF0ZSArIEJhY2hlbG9yICsgTWFzdGVyICsgcGVyc29uX2luY29tZSArIHBlcnNvbl9lbXBfZXhwICsgT3duX090aGVyICsgT3duX093biArIE93bl9Nb3J0Z2FnZSArIGxvYW5fYW1udCArIEhvbWVJbXAgKyBEZWJ0ICsgUGVyc29uYWwgKyBWZW50dXJlICsgTWVkaWNhbCArIGxvYW5faW50X3JhdGUgKyBsb2FuX3BlcmNlbnRfaW5jb21lICsgY2JfcGVyc29uX2NyZWRfaGlzdF9sZW5ndGggKyBjcmVkaXRfc2NvcmUgKyBQcmV2aW91cywNCiAgICAgICAgICAgICAgICAgZmFtaWx5ID0gImJpbm9taWFsIiwNCiAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLmRhdGEpDQoNCnN1bW1hcnkobG9nLnJlZy4yKQ0KYGBgDQoNCmBgYHtyIGxvZyByZWcgbW9kZWwgMiB2aWYsIGluY2x1ZGUgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQ0KdmlmKGxvZy5yZWcuMikNCmBgYA0KDQpgYGB7ciBsb2cgcmVnIG1vZGVsIDMsIGluY2x1ZGUgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQ0KbG9nLnJlZy4zIDwtIGdsbShmb3JtdWxhID0gbG9hbl9zdGF0dXMgfiBNYWxlICsgSFMgKyBBc3NvY2lhdGUgKyBNYXN0ZXIgKyBwZXJzb25faW5jb21lICsgcGVyc29uX2VtcF9leHAgKyBPd25fT3RoZXIgKyBPd25fT3duICsgT3duX01vcnRnYWdlICsgbG9hbl9hbW50ICsgSG9tZUltcCArIERlYnQgKyBQZXJzb25hbCArIFZlbnR1cmUgKyBNZWRpY2FsICsgbG9hbl9pbnRfcmF0ZSArIGxvYW5fcGVyY2VudF9pbmNvbWUgKyBjYl9wZXJzb25fY3JlZF9oaXN0X2xlbmd0aCArIGNyZWRpdF9zY29yZSArIFByZXZpb3VzLA0KICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLA0KICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW4uZGF0YSkNCg0Kc3VtbWFyeShsb2cucmVnLjMpDQpgYGANCg0KYGBge3IgbG9nIHJlZyBtb2RlbCAzIHZpZiwgaW5jbHVkZSA9IEZBTFNFLCBlY2hvID0gRkFMU0V9DQp2aWYobG9nLnJlZy4zKQ0KYGBgDQoNCmBgYHtyIGxvZyByZWcgbW9kZWwgNCwgaW5jbHVkZSA9IEZBTFNFLCBlY2hvID0gRkFMU0V9DQpsb2cucmVnLjQgPC0gZ2xtKGZvcm11bGEgPSBsb2FuX3N0YXR1cyB+IE1hbGUgKyBIUyArIE1hc3RlciArIHBlcnNvbl9pbmNvbWUgKyBwZXJzb25fZW1wX2V4cCArIE93bl9PdGhlciArIE93bl9Pd24gKyBPd25fTW9ydGdhZ2UgKyBsb2FuX2FtbnQgKyBIb21lSW1wICsgRGVidCArIFBlcnNvbmFsICsgVmVudHVyZSArIE1lZGljYWwgKyBsb2FuX2ludF9yYXRlICsgbG9hbl9wZXJjZW50X2luY29tZSArIGNiX3BlcnNvbl9jcmVkX2hpc3RfbGVuZ3RoICsgY3JlZGl0X3Njb3JlICsgUHJldmlvdXMsDQogICAgICAgICAgICAgICAgIGZhbWlseSA9ICJiaW5vbWlhbCIsDQogICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbi5kYXRhKQ0KDQpzdW1tYXJ5KGxvZy5yZWcuNCkNCmBgYA0KDQpgYGB7ciBsb2cgcmVnIG1vZGVsIDUsIGluY2x1ZGUgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQ0KbG9nLnJlZy41IDwtIGdsbShmb3JtdWxhID0gbG9hbl9zdGF0dXMgfiBNYWxlICsgSFMgKyBNYXN0ZXIgKyBwZXJzb25faW5jb21lICsgcGVyc29uX2VtcF9leHAgKyBPd25fT3RoZXIgKyBPd25fT3duICsgT3duX01vcnRnYWdlICsgbG9hbl9hbW50ICsgSG9tZUltcCArIERlYnQgKyBQZXJzb25hbCArIFZlbnR1cmUgKyBNZWRpY2FsICsgbG9hbl9pbnRfcmF0ZSArIGxvYW5fcGVyY2VudF9pbmNvbWUgKyBjYl9wZXJzb25fY3JlZF9oaXN0X2xlbmd0aCArIGNyZWRpdF9zY29yZSwNCiAgICAgICAgICAgICAgICAgZmFtaWx5ID0gImJpbm9taWFsIiwNCiAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLmRhdGEpDQoNCnN1bW1hcnkobG9nLnJlZy41KQ0KYGBgDQoNCmBgYHtyIGxvZyByZWcgbW9kZWwgNiwgaW5jbHVkZSA9IEZBTFNFLCBlY2hvID0gRkFMU0V9DQpsb2cucmVnLjYgPC0gZ2xtKGZvcm11bGEgPSBsb2FuX3N0YXR1cyB+IE1hbGUgKyBIUyArIE1hc3RlciArIHBlcnNvbl9pbmNvbWUgKyBwZXJzb25fZW1wX2V4cCArIE93bl9PdGhlciArIE93bl9Pd24gKyBPd25fTW9ydGdhZ2UgKyBsb2FuX2FtbnQgKyBIb21lSW1wICsgRGVidCArIFBlcnNvbmFsICsgVmVudHVyZSArIE1lZGljYWwgKyBsb2FuX2ludF9yYXRlICsgbG9hbl9wZXJjZW50X2luY29tZSArIGNyZWRpdF9zY29yZSwNCiAgICAgICAgICAgICAgICAgZmFtaWx5ID0gImJpbm9taWFsIiwNCiAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLmRhdGEpDQoNCnN1bW1hcnkobG9nLnJlZy42KQ0KYGBgDQoNCmBgYHtyIGxvZyByZWcgbW9kZWwgNywgaW5jbHVkZSA9IEZBTFNFLCBlY2hvID0gRkFMU0V9DQpsb2cucmVnLjcgPC0gZ2xtKGZvcm11bGEgPSBsb2FuX3N0YXR1cyB+IE1hbGUgKyBNYXN0ZXIgKyBwZXJzb25faW5jb21lICsgcGVyc29uX2VtcF9leHAgKyBPd25fT3RoZXIgKyBPd25fT3duICsgT3duX01vcnRnYWdlICsgbG9hbl9hbW50ICsgSG9tZUltcCArIERlYnQgKyBQZXJzb25hbCArIFZlbnR1cmUgKyBNZWRpY2FsICsgbG9hbl9pbnRfcmF0ZSArIGxvYW5fcGVyY2VudF9pbmNvbWUgKyBjcmVkaXRfc2NvcmUsDQogICAgICAgICAgICAgICAgIGZhbWlseSA9ICJiaW5vbWlhbCIsDQogICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbi5kYXRhKQ0KDQpzdW1tYXJ5KGxvZy5yZWcuNykNCmBgYA0KDQpgYGB7ciBsb2cgcmVnIG1vZGVsIDgsIGluY2x1ZGUgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQ0KbG9nLnJlZy44IDwtIGdsbShmb3JtdWxhID0gbG9hbl9zdGF0dXMgfiBNYWxlICsgcGVyc29uX2luY29tZSArIHBlcnNvbl9lbXBfZXhwICsgT3duX090aGVyICsgT3duX093biArIE93bl9Nb3J0Z2FnZSArIGxvYW5fYW1udCArIEhvbWVJbXAgKyBEZWJ0ICsgUGVyc29uYWwgKyBWZW50dXJlICsgTWVkaWNhbCArIGxvYW5faW50X3JhdGUgKyBsb2FuX3BlcmNlbnRfaW5jb21lICsgY3JlZGl0X3Njb3JlLA0KICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLA0KICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW4uZGF0YSkNCg0Kc3VtbWFyeShsb2cucmVnLjgpDQpgYGANCg0KYGBge3IgbG9nIHJlZyBtb2RlbCA5LCBpbmNsdWRlID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0NCmxvZy5yZWcuOSA8LSBnbG0oZm9ybXVsYSA9IGxvYW5fc3RhdHVzIH4gTWFsZSArIHBlcnNvbl9pbmNvbWUgKyBwZXJzb25fZW1wX2V4cCArIE93bl9PdGhlciArIE93bl9Pd24gKyBPd25fTW9ydGdhZ2UgKyBsb2FuX2FtbnQgKyBIb21lSW1wICsgRGVidCArIFBlcnNvbmFsICsgVmVudHVyZSArIE1lZGljYWwgKyBsb2FuX2ludF9yYXRlICsgbG9hbl9wZXJjZW50X2luY29tZSwNCiAgICAgICAgICAgICAgICAgZmFtaWx5ID0gImJpbm9taWFsIiwNCiAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLmRhdGEpDQoNCnN1bW1hcnkobG9nLnJlZy45KQ0KYGBgDQoNCmBgYHtyIGxvZyByZWcgbW9kZWwgMTAsIGluY2x1ZGUgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQ0KbG9nLnJlZy4xMCA8LSBnbG0oZm9ybXVsYSA9IGxvYW5fc3RhdHVzIH4gcGVyc29uX2luY29tZSArIHBlcnNvbl9lbXBfZXhwICsgT3duX090aGVyICsgT3duX093biArIE93bl9Nb3J0Z2FnZSArIGxvYW5fYW1udCArIEhvbWVJbXAgKyBEZWJ0ICsgUGVyc29uYWwgKyBWZW50dXJlICsgTWVkaWNhbCArIGxvYW5faW50X3JhdGUgKyBsb2FuX3BlcmNlbnRfaW5jb21lLA0KICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLA0KICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW4uZGF0YSkNCg0Kc3VtbWFyeShsb2cucmVnLjEwKQ0KYGBgDQoNCmBgYHtyIGxvZyByZWcgbW9kZWwgMTEsIGluY2x1ZGUgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQ0KbG9nLnJlZy4xMSA8LSBnbG0oZm9ybXVsYSA9IGxvYW5fc3RhdHVzIH4gcGVyc29uX2luY29tZSArIHBlcnNvbl9lbXBfZXhwICsgT3duX093biArIE93bl9Nb3J0Z2FnZSArIGxvYW5fYW1udCArIEhvbWVJbXAgKyBEZWJ0ICsgUGVyc29uYWwgKyBWZW50dXJlICsgTWVkaWNhbCArIGxvYW5faW50X3JhdGUgKyBsb2FuX3BlcmNlbnRfaW5jb21lLA0KICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLA0KICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW4uZGF0YSkNCg0Kc3VtbWFyeShsb2cucmVnLjExKQ0KYGBgDQoNCkFmdGVyIHJ1bm5pbmcgdmFyaW91cyBtb2RlbHMsIEkgYW0gbGVmdCB3aXRoIGhpZ2hseSBzaWduaWZpY2FudCBhbmQgdW5jb3JyZWxhdGVkIHZhcmlhYmxlcyBpbiB0aGUgbW9kZWwuIEJlbG93IGlzIGEgc3VtbWFyeSBvZiB0aGUgbW9kZWw6DQoNCmBgYHtyIGxvZyByZWcgbW9kZWwgMTIsIGluY2x1ZGUgPSBUUlVFLCBlY2hvID0gRkFMU0V9DQpsb2cucmVnLjEyIDwtIGdsbShmb3JtdWxhID0gbG9hbl9zdGF0dXMgfiBwZXJzb25faW5jb21lICsgT3duX093biArIE93bl9Nb3J0Z2FnZSArIGxvYW5fYW1udCArIEhvbWVJbXAgKyBEZWJ0ICsgUGVyc29uYWwgKyBWZW50dXJlICsgTWVkaWNhbCArIGxvYW5faW50X3JhdGUgKyBsb2FuX3BlcmNlbnRfaW5jb21lLA0KICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLA0KICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW4uZGF0YSkNCg0Kc3VtbWFyeShsb2cucmVnLjEyKQ0KYGBgDQoNCiMjIyBCZXN0IE1vZGVsDQoNClRoZXJlZm9yZSwgdGhlIGJlc3QgcG9zc2libGUgbW9kZWwgaXMgbGlzdGVkIGJlbG93Og0KDQokbG9hblxfc3RhdHVzID0gLTYuNjg4ODEyMzkxNyArIDAuMDAwMDAwNjUzOCoocGVyc29uXF9pbmNvbWUpIC0gMi4zODUwMDIxNjM1KihPd25cX093bikgLSAwLjg0NDQwMzc5ODgqKE93blxfTW9yZ2FnZSkgLSAwLjAwMDEwMDE3MjAqKGxvYW5fYW1udCkgXFwgKyAwLjg2MjM0MDc0MzEqKEhvbWVJbXApICsgIDAuOTMzMTQ0MDcyNyooRGVidCkgKyAwLjIyMjcyNjc2ODQqKFBlcnNvbmFsKSAtIDAuMjY1MTEwMjM2NCooVmVudHVyZSkgKyAwLjYyMjQ5MDc0NTgqKE1lZGljYWwpIFxcICsgMC4zMzI2NTQ5MDA3Kihsb2FuXF9pbnRcX3JhdGUpICsgMTUuNzUyODE4NDcxMyoobG9hblxfcGVyY2VudFxfaW5jb21lKSQNCg0KQmVsb3cgaXMgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgZWZmZWN0aXZlbmVzcyBvZiB0aGlzIG1vZGVsIG9uIGNvcnJlY3RseSBwcmVkaWN0aW5nIGlmIGFuIGFwcGxpY2FudCdzIGxvYW4gd2lsbCBiZSBhcHByb3ZlZDoNCg0KYGBge3IgbG9nIHJlZyBjb25mdXNpb24gbWF0cml4LCBpbmNsdWRlID0gVFJVRX0NCnByb2IucHJlZCA8LSBwcmVkaWN0KGxvZy5yZWcuMTIsIG5ld2RhdGEgPSB0ZXN0LmRhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQ0KcHJlZC5jbGFzcyA8LSBpZmVsc2UocHJvYi5wcmVkID4gMC41LCAxLCAwKQ0KDQphY2N1cmFjeSA8LSBtZWFuKHByZWQuY2xhc3MgPT0gdGVzdC5kYXRhJGxvYW5fc3RhdHVzKQ0KcHJpbnQocGFzdGUoIkFjY3VyYWN5OiAiLCBhY2N1cmFjeSkpDQoNCmNvbmZ1c2lvbk1hdHJpeChmYWN0b3IocHJlZC5jbGFzcyksIGZhY3Rvcih0ZXN0LmRhdGEkbG9hbl9zdGF0dXMpKQ0KYGBgDQoNClRoZSBtb2RlbCBoYWQgYW4gKio4NSUqKiBhY2N1cmFjeSByYXRpbmcuDQoNCkFuIGFkZGl0aW9uYWwgbWVhc3VyZSBpbiBjb21wYXJpbmcgdmFyaW91cyBtb2RlbHMgaXMgYW4gUk9DIEN1cnZlLiBBdHRhY2hlZCB3aXRoIGFuIFJPQyBDdXJ2ZSBpcyBhbiBBVUMgdmFsdWUsIG9yICJBcmVhIFVuZGVyIEN1cnZlIi4gVGhlIGNsb3NlciB0byAxIHRoaXMgdmFsdWUgaXMsIHRoZSBiZXR0ZXIgdGhlIG1vZGVsIGlzIGF0IHByZWRpY3Rpb24uIEJlbG93IGlzIHRoZSBST0MgQ3VydmUgZm9yIG91ciBtb2RlbDoNCg0KYGBge3IgbG9nIHJlZyByb2MsIGluY2x1ZGUgPSBUUlVFfQ0Kcm9jX2N1cnZlIDwtIHJvYyh0ZXN0LmRhdGEkbG9hbl9zdGF0dXMsIHByb2IucHJlZCkNCnBsb3Qocm9jX2N1cnZlKQ0KYXVjKHJvY19jdXJ2ZSkNCmBgYA0KDQpBcyBzaG93biBpbiB0aGUgZ3JhcGggYWJvdmUsIHRoaXMgbW9kZWwncyAqKkFVQyoqIGlzIDAuODYzNy4gVGhpcyB2YWx1ZSB3aWxsIGJlIHVzZWQgdG8gY29tcGFyZSBvdGhlciBtb2RlbHMuDQoNCiMjIFBlcmNlcHRyb24NCg0KQSBwZXJjZXB0cm9uIGlzIGEgc2ltcGxlIHR5cGUgb2YgYXJ0aWZpY2lhbCBuZXVyYWwgbmV0d29yayB0aGF0IG1ha2VzIHByZWRpY3Rpb25zIGJhc2VkIG9uIGEgbGluZWFyIGNvbWJpbmF0aW9uIG9mIGlucHV0IGZlYXR1cmVzLiBJIHV0aWxpemVzIGFuIGFjdGl2YXRpb24gZnVuY3Rpb24gKGNvbW1vbmx5IHJlZmVycmVkIHRvIGFzIGEgc3RlcCBmdW5jdGlvbikgdG8gbWFrZSBwcmVkaWN0aW9ucy4NCg0KIyMjIFRlY2huaXF1ZQ0KDQpJIHdpbGwgZ28gdGhyb3VnaCB0aGUgcHJvY2VzcyBvZiBtYWtpbmcgYSBzaW1wbGUgcGVyY2VwdG9uIGFuZCB0ZXN0aW5nIGl0cyBhY2N1cmFjeSB1dGlsaXppbmcgYW4gUk9DIEN1cnZlLg0KDQpgYGB7ciBwZXJjZXB0cm9uIHNjYWxpbmcsIGluY2x1ZGUgPSBUUlVFfQ0KbmV1cmFsRGF0YSA8LSB0cmFpbi5kYXRhWywgYygicGVyc29uX2FnZSIsICJwZXJzb25faW5jb21lIiwgInBlcnNvbl9lbXBfZXhwIiwgImxvYW5fYW1udCIsICJsb2FuX2ludF9yYXRlIiwgImxvYW5fcGVyY2VudF9pbmNvbWUiLCAiY2JfcGVyc29uX2NyZWRfaGlzdF9sZW5ndGgiLCAiY3JlZGl0X3Njb3JlIiwgIk1hbGUiLCAiSFMiLCAiQXNzb2NpYXRlIiwgIkJhY2hlbG9yIiwgIk1hc3RlciIsICJPd25fT3RoZXIiLCAiT3duX093biIsICJPd25fTW9ydGdhZ2UiLCAiSG9tZUltcCIsICJEZWJ0IiwgIlBlcnNvbmFsIiwgIlZlbnR1cmUiLCAiTWVkaWNhbCIsICJQcmV2aW91cyIsICJsb2FuX3N0YXR1cyIpXQ0KDQojIyBmZWF0dXJlIHNjYWxpbmcNCm5ldXJhbERhdGEkcGVyc29uX2FnZSA8LSAobmV1cmFsRGF0YSRwZXJzb25fYWdlIC0gbWluKG5ldXJhbERhdGEkcGVyc29uX2FnZSkpIC8gKG1heChuZXVyYWxEYXRhJHBlcnNvbl9hZ2UpIC0gbWluKG5ldXJhbERhdGEkcGVyc29uX2FnZSkpDQoNCm5ldXJhbERhdGEkcGVyc29uX2luY29tZSA8LSAobmV1cmFsRGF0YSRwZXJzb25faW5jb21lIC0gbWluKG5ldXJhbERhdGEkcGVyc29uX2luY29tZSkpIC8gKG1heChuZXVyYWxEYXRhJHBlcnNvbl9pbmNvbWUpIC0gbWluKG5ldXJhbERhdGEkcGVyc29uX2luY29tZSkpDQoNCm5ldXJhbERhdGEkcGVyc29uX2VtcF9leHAgPC0gKG5ldXJhbERhdGEkcGVyc29uX2VtcF9leHAgLSBtaW4obmV1cmFsRGF0YSRwZXJzb25fZW1wX2V4cCkpIC8gKG1heChuZXVyYWxEYXRhJHBlcnNvbl9lbXBfZXhwKSAtIG1pbihuZXVyYWxEYXRhJHBlcnNvbl9lbXBfZXhwKSkNCg0KbmV1cmFsRGF0YSRsb2FuX2FtbnQgPC0gKG5ldXJhbERhdGEkbG9hbl9hbW50IC0gbWluKG5ldXJhbERhdGEkbG9hbl9hbW50KSkgLyAobWF4KG5ldXJhbERhdGEkbG9hbl9hbW50KSAtIG1pbihuZXVyYWxEYXRhJGxvYW5fYW1udCkpDQoNCm5ldXJhbERhdGEkbG9hbl9pbnRfcmF0ZSA8LSAobmV1cmFsRGF0YSRsb2FuX2ludF9yYXRlIC0gbWluKG5ldXJhbERhdGEkbG9hbl9pbnRfcmF0ZSkpIC8gKG1heChuZXVyYWxEYXRhJGxvYW5faW50X3JhdGUpIC0gbWluKG5ldXJhbERhdGEkbG9hbl9pbnRfcmF0ZSkpDQoNCm5ldXJhbERhdGEkbG9hbl9wZXJjZW50X2luY29tZSA8LSAobmV1cmFsRGF0YSRsb2FuX3BlcmNlbnRfaW5jb21lIC0gbWluKG5ldXJhbERhdGEkbG9hbl9wZXJjZW50X2luY29tZSkpIC8gKG1heChuZXVyYWxEYXRhJGxvYW5fcGVyY2VudF9pbmNvbWUpIC0gbWluKG5ldXJhbERhdGEkbG9hbl9wZXJjZW50X2luY29tZSkpDQoNCm5ldXJhbERhdGEkY2JfcGVyc29uX2NyZWRfaGlzdF9sZW5ndGggPC0gKG5ldXJhbERhdGEkY2JfcGVyc29uX2NyZWRfaGlzdF9sZW5ndGggLSBtaW4obmV1cmFsRGF0YSRjYl9wZXJzb25fY3JlZF9oaXN0X2xlbmd0aCkpIC8gKG1heChuZXVyYWxEYXRhJGNiX3BlcnNvbl9jcmVkX2hpc3RfbGVuZ3RoKSAtIG1pbihuZXVyYWxEYXRhJGNiX3BlcnNvbl9jcmVkX2hpc3RfbGVuZ3RoKSkNCg0KbmV1cmFsRGF0YSRjcmVkaXRfc2NvcmUgPC0gKG5ldXJhbERhdGEkY3JlZGl0X3Njb3JlIC0gbWluKG5ldXJhbERhdGEkY3JlZGl0X3Njb3JlKSkgLyAobWF4KG5ldXJhbERhdGEkY3JlZGl0X3Njb3JlKSAtIG1pbihuZXVyYWxEYXRhJGNyZWRpdF9zY29yZSkpDQpgYGANCg0KYGBge3IgcGVyY2VwdHJvbiBtb2RlbCBmb3JtdWxhLCBpbmNsdWRlID0gVFJVRX0NCm5ldXJhbE1vZGVsRm9ybXVsYSA8LSBtb2RlbC5tYXRyaXgobG9hbl9zdGF0dXMgfiBwZXJzb25fYWdlICsgcGVyc29uX2luY29tZSArIHBlcnNvbl9lbXBfZXhwICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9hbl9hbW50ICsgbG9hbl9pbnRfcmF0ZSArIGxvYW5fcGVyY2VudF9pbmNvbWUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKyBjYl9wZXJzb25fY3JlZF9oaXN0X2xlbmd0aCArIGNyZWRpdF9zY29yZSArIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNYWxlICsgSFMgKyBBc3NvY2lhdGUgKyBCYWNoZWxvciArIE1hc3RlciArIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPd25fT3RoZXIgKyBPd25fT3duICsgT3duX01vcnRnYWdlICsgSG9tZUltcCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlYnQgKyBQZXJzb25hbCArIFZlbnR1cmUgKyBNZWRpY2FsICsgUHJldmlvdXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gbmV1cmFsRGF0YSkNCmBgYA0KDQpgYGB7ciBwZXJjZXB0b24gaW1wbGljaXQgZm9ybXVsYSwgaW5jbHVkZSA9IFRSVUV9DQppbXBsaWNpdEZvcm11bGEgPC0gbW9kZWwubWF0cml4KH4uLCBkYXRhID0gbmV1cmFsRGF0YVssIC1uY29sKG5ldXJhbERhdGEpXSkNCmBgYA0KDQpgYGB7ciBwZXJjZXB0b24gbW9kZWwgZm9ybXVsYSBjb21maXJtYXRpb24sIGluY2x1ZGUgPSBUUlVFfQ0KY29sdW1uTmFtZXMgPSBjb2xuYW1lcyhpbXBsaWNpdEZvcm11bGEpDQpjb2x1bW5MaXN0ID0gcGFzdGUoY29sdW1uTmFtZXNbLWMoMSxsZW5ndGgoY29sdW1uTmFtZXMpKV0sIGNvbGxhcHNlID0gIisiKQ0KY29sdW1uTGlzdCA9IHBhc3RlKGMoImxvYW5fc3RhdHVzIiwgIn4iLCBjb2x1bW5MaXN0KSwgY29sbGFwc2U9IiIpDQoNCm1vZGVsRm9ybXVsYSA9IGZvcm11bGEoY29sdW1uTGlzdCkNCmBgYA0KDQpCZWxvdyBzaG93cyB0aGUgY29tcGxpY2F0ZWQgbmV1cmFsIG5ldHdvcmsgZm9yIHRoZSBiZXN0IG1vZGVsLCBhY2NvbXBhbmllZCBieSB0aGUgd2VpZ2h0cyBmb3IgZWFjaCB2YXJpYWJsZS4NCg0KYGBge3Igbm4gbW9kZWwgZ3JhcGgsIGluY2x1ZGUgPSBUUlVFfQ0Kc2V0LnNlZWQoMzIzKQ0KbmV1cmFsRGF0YV9zbWFsbCA8LSBuZXVyYWxEYXRhW3NhbXBsZSgxOm5yb3cobmV1cmFsRGF0YSksIDEwMDApLCBdDQoNCm5uX21vZGVsIDwtIG5ldXJhbG5ldChtb2RlbEZvcm11bGEsIGRhdGEgPSBuZXVyYWxEYXRhX3NtYWxsLCBsaW5lYXIub3V0cHV0ID0gRkFMU0UsIGhpZGRlbiA9IGMoNSkpDQoNCnBsb3Qobm5fbW9kZWwsIHJlcCA9ICJiZXN0IikNCg0Kbm5fbW9kZWwkcmVzdWx0Lm1hdHJpeA0KYGBgDQoNClNpbmNlIHRoZSBwZXJjZXB0cm9uIGRvZXMgbm90IHJlc3VsdCBpbiBhIGxpbmVhciBmb3JtdWxhIGZvciBwcmVkaWN0aW9uLCBpdCBjYW4gYmUgZGlmZmljdWx0IHRvIGNvbXBhcmUgdGhlIG1vZGVsIHRvIG90aGVycy4gVGhpcyBpcyB3aHkgYW4gUk9DIEN1cnZlIGlzIG5lY2Vzc2FyeSB0byBjYWxjdWxhdGUgYW4gQVVDIHZhbHVlLCB3aGlvY2ggKipjYW4qKiBiZSBjb21wYXJlZCBhY3Jvc3MgbW9kZWwgdHlwZXMuDQoNCkJlbG93IGlzIHRoZSBST0MgQ3VydmUgd2l0aCB0aGUgbW9kZWwncyBBVUMgdmFsdWU6DQoNCmBgYHtyIHBlcmNlcHRyb24gcm9jL2F1YywgaW5jbHVkZSA9IFRSVUV9DQpwcmVkTk4gPSBwcmVkaWN0KG5uX21vZGVsLCBuZXdkYXRhID0gdGVzdC5kYXRhLCBsaW5lYXQub3V0cHV0ID0gRkFMU0UpDQoNCmNhdGVnb3J5ID0gdGVzdC5kYXRhJGxvYW5fc3RhdHVzID09IDENClJPQ29iai5OTiA8LSByb2MoY2F0ZWdvcnksIHByZWROTikNCg0KTk5BVUMgPSBST0NvYmouTk4kYXVjDQoNCnNlbi5OTiA9IFJPQ29iai5OTiRzZW5zaXRpdml0aWVzDQpmbnIuTk4gPSAxIC0gUk9Db2JqLk5OJHNwZWNpZmljaXRpZXMNCg0KcGxvdChmbnIuTk4sIHNlbi5OTiwgdHlwZSA9ICJsIiwgbHdkID0gMiwgY29sID0gInJlZCIsDQogICAgIHhsaW0gPSBjKDAsIDEpLA0KICAgICB5bGltID0gYygwLCAxKSwNCiAgICAgeGxhYiA9ICIxIC0gc3BlY2lmaWNpdHkiLA0KICAgICB5bGFiID0gInNlbnNpdGl2aXR5IiwNCiAgICAgbWFpbiA9ICJST0MgQ3VydmUgb2YgTW9kZWwiKQ0KDQp0ZXh0KDAuODcsIDAuMTAsIHBhc3RlKCJBVUMgPSAiLCByb3VuZChOTkFVQyw0KSksIGNleCA9IDAuNywgYWRqID0gMSkNCmBgYA0KDQpBcyBzaG93biBpbiB0aGUgZ3JhcGggYWJvdmUsIHRoaXMgbW9kZWzigJlzIEFVQyBpcyAqKjAuNSoqLiBUaGlzIHZhbHVlIHdpbGwgYmUgdXNlZCB0byBjb21wYXJlIG90aGVyIG1vZGVscy4NCg0KIyMgRGVjaXNpb24gVHJlZQ0KDQpBIGRlY2lzaW9uIHRyZWUgaXMgYSB0cmVlLWxpa2Ugc3RydWN0dXJlIHRoYXQgc3BsaXRzIHRoZSBkYXRhIHJlY3Vyc2l2ZWx5IGJhc2VkIG9uIGZlYXR1cmUgdmFsdWVzIHRvIGNyZWF0ZSBhIHNldCBvZiBkZWNpc2lvbiBydWxlcy4gRWFjaCBpbnRlcm5hbCBub2RlIHJlcHJlc2VudHMgYSBkZWNpc2lvbiBvbiBhIGZlYXR1cmUsIGVhY2ggYnJhbmNoIHJlcHJlc2VudHMgdGhlIG91dGNvbWUgb2YgdGhhdCBkZWNpc2lvbiwgYW5kIGVhY2ggbGVhZiBub2RlIHJlcHJlc2VudHMgYSBjbGFzcyBsYWJlbCAoZm9yIGNsYXNzaWZpY2F0aW9uKSBvciBhIG51bWVyaWMgdmFsdWUgKGZvciByZWdyZXNzaW9uKS4NCg0KIyMjIFRlY2huaXF1ZQ0KDQpJIHdpbGwgY3JlYXRlIDYgZGlmZmVyZW50IGRlY2lzaW9uIHRyZWVzIGFuZCBhbGwgY29tcGFyZSB0aGVtIGFnYWluc3Qgb25lIGFub3RoZXIgdXRpbGl6aW5nIHRoZSBBVUMgdmFsdWVzLiBUaGUgNiBkZWNpc2lvbiB0cmVlcyB3aWxsIGFsbCBoYXZlIHZhcnlpbmcgbm9kZSBwdXJpdHkgdHlwZXMsIGZhbHNlIHBvc2l0aXZlIHdlaWh0cywgYW5kIGZhbHNlIG5lZ2F0aXZlIHdlaWdodHMuDQoNCmBgYHtyIHRyZWUgYnVpbGRlciBmdW5jdGlvbiwgaW5jbHVkZSA9IFRSVUV9DQp0cmVlLmJ1aWxkZXIgPSBmdW5jdGlvbihpbi5kYXRhLCBmcCwgZm4sIHB1cml0eSl7DQogIHRyZWUgPSBycGFydChsb2FuX3N0YXR1cyB+IC4sDQogICAgICAgICAgICAgICBkYXRhID0gaW4uZGF0YSwNCiAgICAgICAgICAgICAgIG5hLmFjdGlvbiA9IG5hLnJwYXJ0LA0KICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICBtZXRob2QgPSAiY2xhc3MiLA0KICAgICAgICAgICAgICAgbW9kZWwgPSBGQUxTRSwNCiAgICAgICAgICAgICAgIHggPSBGQUxTRSwNCiAgICAgICAgICAgICAgIHkgPSBUUlVFLA0KICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICBwYXJtcyA9IGxpc3QobG9zcyA9IG1hdHJpeChjKDAsIGZwLCBmbiwgMCksIG5jb2wgPSAyLCBieXJvdyA9IFRSVUUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gcHVyaXR5KSwNCiAgICAgICAgICAgICAgIGNvbnRyb2wgPSBycGFydC5jb250cm9sKA0KICAgICAgICAgICAgICAgICBtaW5zcGxpdCA9IDEwLA0KICAgICAgICAgICAgICAgICBtaW5idWNrZXQgPSAxMCwNCiAgICAgICAgICAgICAgICAgY3AgPSAwLjAxLA0KICAgICAgICAgICAgICAgICB4dmFsID0gMTANCiAgICAgICAgICAgICAgICkpDQp9DQpgYGANCg0KYGBge3IgdHJlZSBkZWZpbmluZywgaW5jbHVkZSA9IFRSVUV9DQpnaW5pLnRyZWUuMS4xID0gdHJlZS5idWlsZGVyKGluLmRhdGEgPSB0cmFpbi5kYXRhLCBmcCA9IDEsIGZuID0gMSwgcHVyaXR5ID0gImdpbmkiKQ0KaW5mby50cmVlLjEuMSA9IHRyZWUuYnVpbGRlcihpbi5kYXRhID0gdHJhaW4uZGF0YSwgZnAgPSAxLCBmbiA9IDEsIHB1cml0eSA9ICJpbmZvcm1hdGlvbiIpDQoNCmdpbmkudHJlZS4xLjEwID0gdHJlZS5idWlsZGVyKGluLmRhdGEgPSB0cmFpbi5kYXRhLCBmcCA9IDEsIGZuID0gMTAsIHB1cml0eSA9ICJnaW5pIikNCmluZm8udHJlZS4xLjEwID0gdHJlZS5idWlsZGVyKGluLmRhdGEgPSB0cmFpbi5kYXRhLCBmcCA9IDEsIGZuID0gMTAsIHB1cml0eSA9ICJpbmZvcm1hdGlvbiIpDQoNCmdpbmkudHJlZS4xMC4xID0gdHJlZS5idWlsZGVyKGluLmRhdGEgPSB0cmFpbi5kYXRhLCBmcCA9IDEwLCBmbiA9IDEsIHB1cml0eSA9ICJnaW5pIikNCmluZm8udHJlZS4xMC4xID0gdHJlZS5idWlsZGVyKGluLmRhdGEgPSB0cmFpbi5kYXRhLCBmcCA9IDEwLCBmbiA9IDEsIHB1cml0eSA9ICJpbmZvcm1hdGlvbiIpDQpgYGANCg0KQmVsb3cgYXJlIHRoZSA2IGRpZmZlcmVudCBkZWNpc2lvbiB0cmVlczoNCg0KYGBge3IgdHJlZSBtYWtpbmcsIGluY2x1ZGUgPSBUUlVFfQ0KcnBhcnQucGxvdChnaW5pLnRyZWUuMS4xLCBtYWluID0gIlRyZWUgd2l0aCBHaW5pIGluZGV4OiBub24tcGVuYWxpemF0aW9uIikNCnJwYXJ0LnBsb3QoaW5mby50cmVlLjEuMSwgbWFpbiA9ICJUcmVlIHdpdGggZW50cm9weTogbm9uLXBlbmFsaXphdGlvbiIpDQpycGFydC5wbG90KGdpbmkudHJlZS4xLjEwLCBtYWluID0gIlRyZWUgd2l0aCBHaW5pIGluZGV4OiBwZW5hbGl6YXRpb24iKQ0KcnBhcnQucGxvdChpbmZvLnRyZWUuMS4xMCwgbWFpbiA9ICJUcmVlIHdpdGggZW50cm9weTogcGVuYWxpemF0aW9uIikNCnJwYXJ0LnBsb3QoZ2luaS50cmVlLjEwLjEsIG1haW4gPSAiVHJlZSB3aXRoIEdpbmkgaW5kZXg6IHBlbmFsaXphdGlvbiIpDQpycGFydC5wbG90KGluZm8udHJlZS4xMC4xLCBtYWluID0gIlRyZWUgd2l0aCBlbnRyb3B5OiBwZW5hbGl6YXRpb24iKQ0KYGBgDQoNCiMjIyBCZXN0IE1vZGVsDQoNClRvIGRldGVybWluZSB3aGljaCBvZiB0aGUgNiBkZWNpc2lvbiB0cmVlcyBhcmUgdGhlIGJlc3QsIEkgY29tcGFyZSB0aGVpciBBVUMgdmFsdWVzLiBCZWxvdyBpcyBhbiBvdmVybGFwcGluZyBncmFwaCBvZiBhbGwgNiBST0MgY3VydmVzIGFuZCBBVUMgdmFsdWVzOg0KDQpgYGB7ciByb2MgZnVuY3Rpb24sIGluY2x1ZGUgPSBUUlVFfQ0KU2VuU3BlID0gZnVuY3Rpb24oaW4uZGF0YSwgZnAsIGZuLCBwdXJpdHkpew0KICBjdXRvZmYgPSBzZXEoMCwxLCBsZW5ndGggPSAyMCkgICANCiAgbW9kZWwgPSB0cmVlLmJ1aWxkZXIoaW4uZGF0YSwgZnAsIGZuLCBwdXJpdHkpIA0KIA0KICBwcmVkID0gcHJlZGljdChtb2RlbCwgbmV3ZGF0YSA9IGluLmRhdGEsIHR5cGUgPSAicHJvYiIpIA0KICBzZW5zcGUubXR4ID0gbWF0cml4KDAsIG5jb2wgPSBsZW5ndGgoY3V0b2ZmKSwgbnJvdz0gMiwgYnlyb3cgPSBGQUxTRSkNCiAgZm9yIChpIGluIDE6bGVuZ3RoKGN1dG9mZikpew0KDQogIHByZWQub3V0ID0gIGlmZWxzZShwcmVkWywyXSA+PSBjdXRvZmZbaV0sIDEsIDApICANCiAgVFAgPSBzdW0ocHJlZC5vdXQgPT0gMSAmIGluLmRhdGEkbG9hbl9zdGF0dXMgPT0gMSkNCiAgVE4gPSBzdW0ocHJlZC5vdXQgPT0gMCAmIGluLmRhdGEkbG9hbl9zdGF0dXMgPT0gMCkNCiAgRlAgPSBzdW0ocHJlZC5vdXQgPT0gMSAmIGluLmRhdGEkbG9hbl9zdGF0dXMgPT0gMCkNCiAgRk4gPSBzdW0ocHJlZC5vdXQgPT0gMCAmIGluLmRhdGEkbG9hbl9zdGF0dXMgPT0gMSkNCiAgc2Vuc3BlLm10eFsxLGldID0gVFAvKFRQICsgRk4pDQogIHNlbnNwZS5tdHhbMixpXSA9IFROLyhUTiArIEZQKQ0KICBhY2N1cmFjeVtpXSA9IChUUCArIFROKS8oVFAgKyBUTiArIEZQICsgRk4pDQogIH0NCiAgDQoNCiAgcHJlZGljdGlvbiA9IHByZWRbLCAyXQ0KICBjYXRlZ29yeSA9IGluLmRhdGEkbG9hbl9zdGF0dXMgPT0gMQ0KICBST0NvYmogPC0gcm9jKGNhdGVnb3J5LCBwcmVkaWN0aW9uKQ0KICBBVUMgPSBhdWMoUk9Db2JqKQ0KDQogIGxpc3Qoc2Vuc3BlLm10eD0gc2Vuc3BlLm10eCwgQVVDID0gcm91bmQoQVVDLDUpKQ0KIH0NCmBgYA0KDQpgYGB7ciwgaW5jbHVkZSA9IFRSVUUsIGVycm9yID0gRkFMU0V9DQpnaW5pUk9DMTEgPSBTZW5TcGUoaW4uZGF0YSA9IHRyYWluLmRhdGEsIGZwPTEsIGZuPTEsIHB1cml0eT0iZ2luaSIpDQppbmZvUk9DMTEgPSBTZW5TcGUoaW4uZGF0YSA9IHRyYWluLmRhdGEsIGZwPTEsIGZuPTEsIHB1cml0eT0iaW5mb3JtYXRpb24iKQ0KZ2luaVJPQzExMCA9IFNlblNwZShpbi5kYXRhID0gdHJhaW4uZGF0YSwgZnA9MSwgZm49MTAsIHB1cml0eT0iZ2luaSIpDQppbmZvUk9DMTEwID0gU2VuU3BlKGluLmRhdGEgPSB0cmFpbi5kYXRhLCBmcD0xLCBmbj0xMCwgcHVyaXR5PSJpbmZvcm1hdGlvbiIpDQpnaW5pUk9DMTAxID0gU2VuU3BlKGluLmRhdGEgPSB0cmFpbi5kYXRhLCBmcD0xMCwgZm49MSwgcHVyaXR5PSJnaW5pIikNCmluZm9ST0MxMDEgPSBTZW5TcGUoaW4uZGF0YSA9IHRyYWluLmRhdGEsIGZwPTEwLCBmbj0xLCBwdXJpdHk9ImluZm9ybWF0aW9uIikNCmBgYA0KDQpgYGB7ciByb2MgY3VydmUsIGluY2x1ZGUgPSBUUlVFfQ0KcGFyKHB0eSA9ICJzIikNCmNvbG9ycyA9IGMoIiMwMDhCOEIiLCAiIzAwMDA4QiIsICAiIzhCMDA4QiIsICAiIzhCMDAwMCIsICAiIzhCOEIwMCIsICIjOEI0NTAwIikNCnBsb3QoMSAtIGdpbmlST0MxMSRzZW5zcGUubXR4WzIsXSwgZ2luaVJPQzExJHNlbnNwZS5tdHhbMSxdLCANCiAgICAgdHlwZSA9ICJsIiwgDQogICAgIHhsaW0gPSBjKDAsMSksIA0KICAgICB5bGltID0gYygwLDEpLCANCiAgICAgeGxhYiA9ICIxIC0gc3BlY2lmaWNpdHk6IEZQUiIsIHlsYWIgPSAiU2Vuc2l0aXZpdHk6IFRQUiIsIA0KICAgICBjb2wgPSBjb2xvcnNbMV0sIA0KICAgICBsd2QgPSAyLA0KICAgICBtYWluID0gIlJPQyBDdXJ2ZXMgb2YgRGVjaXNpb24gVHJlZXMiLCANCiAgICAgY2V4Lm1haW4gPSAwLjksIA0KICAgICBjb2wubWFpbiA9ICJuYXZ5IikNCg0KYWJsaW5lKDAsMSwgbHR5ID0gMiwgY29sID0gIm9yY2hpZDQiLCBsd2QgPSAyKQ0KDQpsaW5lcygxIC0gaW5mb1JPQzExJHNlbnNwZS5tdHhbMiwgXSwgaW5mb1JPQzExJHNlbnNwZS5tdHhbMSwgXSwgDQogICAgICBjb2wgPSBjb2xvcnNbMl0sIGx3ZCA9IDIsIGx0eT0yKQ0KbGluZXMoMSAtIGdpbmlST0MxMTAkc2Vuc3BlLm10eFsyLCBdLCBnaW5pUk9DMTEwJHNlbnNwZS5tdHhbMSwgXSwNCiAgICAgIGNvbCA9IGNvbG9yc1szXSwgbHdkID0gMikNCmxpbmVzKDEgLSBpbmZvUk9DMTEwJHNlbnNwZS5tdHhbMiwgXSwgaW5mb1JPQzExMCRzZW5zcGUubXR4WzEsIF0sIA0KICAgICAgY29sID0gY29sb3JzWzRdLCBsd2QgPSAyLCBsdHk9MikNCmxpbmVzKDEgLSBnaW5pUk9DMTAxJHNlbnNwZS5tdHhbMiwgXSwgZ2luaVJPQzEwMSRzZW5zcGUubXR4WzEsIF0sIA0KICAgICAgY29sID0gY29sb3JzWzVdLCBsd2QgPSAyLCBsdHkgPSA0KQ0KbGluZXMoMSAtIGluZm9ST0MxMDEkc2Vuc3BlLm10eFsyLCBdLCBpbmZvUk9DMTAxJHNlbnNwZS5tdHhbMSwgXSwgDQogICAgICBjb2wgPSBjb2xvcnNbNl0sIGx3ZCA9IDIsIGx0eT0yKQ0KbGVnZW5kKCJib3R0b21yaWdodCIsIGMocGFzdGUoImdpbmkuMS4xLCAgQVVDID0iLCBnaW5pUk9DMTEkQVVDKSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiaW5mby4xLjEsICBBVUMgPSIsaW5mb1JPQzExJEFVQyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoImdpbmkuMS4xMCwgQVVDID0iLGdpbmlST0MxMTAkQVVDKSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiaW5mby4xLjEwLCBBVUMgPSIsaW5mb1JPQzExMCRBVUMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoImdpbmkuMTAuMSwgQVVDID0iLGdpbmlST0MxMDEkQVVDKSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiaW5mby4xMC4xLCBBVUMgPSIsaW5mb1JPQzEwMSRBVUMpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbD1jb2xvcnMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgbHR5PXJlcCgxOjIsMyksIGx3ZD1yZXAoMiw2KSwgY2V4ID0gMC44LCBidHkgPSAibiIpDQpgYGANCg0KQXMgc2hvd24gYnkgdGhlIFJPQyBwbG90IGFib3ZlLCB0aGUgZ2luaS4xLjEgZGVjaXNpb24gdHJlZSBwZXJmb3JtZWQgdGhlIGJlc3Qgd2l0aCBhbiBBVUMgb2YgKiowLjk0MTM4KiouIFRoZXJlZm9yZSwgdGhpcyBkZWNpc2lvbiB0cmVlIGlzIHRoZSBiZXN0IHBlcmZvcm1pbmcgdHJlZSBvZiB0aGUgYXZhaWxhYmxlIHRyZWVzLiBBIGNvcHkgb2YgdGhlIGRlY2lzaW9uIHRyZWUgaXMgYmVsb3c6DQoNCmBgYHtyIGJlc3QgdHJlZSwgaW5jbHVkZSA9IFRSVUV9DQpycGFydC5wbG90KGdpbmkudHJlZS4xLjEsIG1haW4gPSAiVHJlZSB3aXRoIEdpbmkgaW5kZXg6IG5vbi1wZW5hbGl6YXRpb24iKQ0KYGBgDQoNCiMjIEJhZ2dpbmcNCg0KQmFnZ2luZyBpcyBhbiBlbnNlbWJsZSBtZXRob2QgdGhhdCBhaW1zIHRvIHJlZHVjZSB2YXJpYW5jZSBieSB0cmFpbmluZyBtdWx0aXBsZSBtb2RlbHMgb24gZGlmZmVyZW50IHN1YnNldHMgb2YgdGhlIGRhdGEgYW5kIHRoZW4gYWdncmVnYXRpbmcgdGhlaXIgcHJlZGljdGlvbnMuIEl0IHdvcmtzIGJ5IGJvb3RzdHJhcHBpbmcgdGhlIGRhdGFzZXQgYW5kIGZpdHRpbmcgYSBtb2RlbCBvbiBlYWNoIGJvb3RzdHJhcCBzYW1wbGUuDQoNCiMjIyBUZWNobmlxdWUNCg0KSSB3aWxsIHRyYWluIHRoZSBiYWdnaW5nIG1vZGVsIHRvIGNyZWF0ZSBhbiBhc3NlbWJsZSBvZiBkZWNpc2lvbiB0cmVlcyB3aXRoIHNwZWNpZmljIGFzcGVjdHMgdGhhdCBtYXkgb3IgbWF5IG5vdCBkaWZmZXIgZnJvbSB0aGUgYWJvdmUgZGVjaXNpb24gdHJlZS4gV2l0aCB0aGUgZ2l2ZW4gcGFyYW1ldGVycywgSSB3aWxsIGV2YWx1YXRlIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgY2hvc2VuIGRlY2lzaW9uIHRyZWUgYW5kIHVsdGltYXRlIHByb3ZpZGUgYW4gUk9DIGN1cnZlIHdpdGggYW4gQVVDIHZhbHVlLg0KDQojIyMgQmVzdCBNb2RlbA0KDQpgYGB7ciBiYWdnaW5nLCBpbmNsdWRlID0gVFJVRX0NCnRyYWluID0gdHJhaW4uZGF0YQ0KdGVzdCA9IHRlc3QuZGF0YQ0KDQpMb2FuLmJhZy50cmFpbiA8LSBiYWdnaW5nKGFzLmZhY3Rvcihsb2FuX3N0YXR1cykgfiAuLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgIG5iYWdnID0gMTUwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjb29iID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGFybXMgPSBsaXN0KGxvc3MgPSBtYXRyaXgoYygwLCAxMCwgMSwgMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IDIwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gVFJVRSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gImdpbmkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAxMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3AgPSAwLjAyKSkNCg0KcHJlZCA9IHByZWRpY3QoTG9hbi5iYWcudHJhaW4sIHRlc3QsIHR5cGUgPSAicHJvYiIpDQoNCmN1dC5wcm9iID0gc2VxKDAsIDEsIGxlbmd0aCA9IDIwKQ0Kc2Vuc3BlLm10eCA9IG1hdHJpeCgwLCBuY29sID0gbGVuZ3RoKGN1dC5wcm9iKSwgbnJvdyA9IDMsIGJ5cm93ID0gRkFMU0UpDQoNCmZvciAoaSBpbiAxOmxlbmd0aChjdXQucHJvYikpew0KICBwcmVkLm91dCA9IGlmZWxzZShwcmVkWywgIjEiXSA+PSBjdXQucHJvYltpXSwgIjEiLCAiMCIpDQogIA0KICBUUCA9IHN1bShwcmVkLm91dCA9PSAiMSIgJiB0ZXN0JGxvYW5fc3RhdHVzID09IDEpDQogIFROID0gc3VtKHByZWQub3V0ID09ICIwIiAmIHRlc3QkbG9hbl9zdGF0dXMgPT0gMCkNCiAgRlAgPSBzdW0ocHJlZC5vdXQgPT0gIjEiICYgdGVzdCRsb2FuX3N0YXR1cyA9PSAwKQ0KICBGTiA9IHN1bShwcmVkLm91dCA9PSAiMCIgJiB0ZXN0JGxvYW5fc3RhdHVzID09IDEpDQogIA0KICBzZW5zcGUubXR4WzEsIGldID0gVFAgLyAoVFAgKyBGTikNCiAgc2Vuc3BlLm10eFsyLCBpXSA9IFROIC8gKFROICsgRlApDQogIHNlbnNwZS5tdHhbMywgaV0gPSAoVFAgKyBUTikgLyAoVFAgKyBGTiArIFROICsgRlApDQp9DQoNCnByZWRpY3Rpb24gPSBwcmVkWywgIjEiXQ0KY2F0ZWdvcnkgPSB0ZXN0JGxvYW5fc3RhdHVzID09IDENCg0KUk9Db2JqIDwtIHJvYyhjYXRlZ29yeSwgcHJlZGljdGlvbikNCkFVQyA9IGF1YyhST0NvYmopDQpBVUMgPSByb3VuZChhcy52ZWN0b3IoQVVDWzFdKSwgMykNCg0KbiA9IGxlbmd0aChzZW5zcGUubXR4WzMsXSkNCmlkeCA9IHdoaWNoKHNlbnNwZS5tdHhbMyxdID09IG1heChzZW5zcGUubXR4WzMsXSkpDQp0aWNrLmxhYmVsID0gYXMuY2hhcmFjdGVyKHJvdW5kKGN1dC5wcm9iLDIpKQ0KDQpwYXIobWZyb3cgPSBjKDEsIDIpKQ0KcGxvdCgxIC0gc2Vuc3BlLm10eFsyLCBdLCBzZW5zcGUubXR4WzEsIF0sIA0KICAgICB0eXBlID0gImwiLCBsd2QgPSAyLCBjb2wgPSAibmF2eSIsIHhsaW0gPSBjKDAsIDEpLCANCiAgICAgeWxpbSA9IGMoMCwgMSksIHhsYWIgPSAiMSAtIFNwZWNpZmljaXR5IiwgDQogICAgIHlsYWIgPSAiU2Vuc2l0aXZpdHkiLCBtYWluID0gIlJPQyAoVGVzdGluZyBEYXRhKSIsIA0KICAgICBjZXgubWFpbiA9IDAuOCkNCnNlZ21lbnRzKDAsIDAsIDEsIDEsIGx0eSA9IDIsIGNvbCA9ICJyZWQiKQ0KbGVnZW5kKCJib3R0b21yaWdodCIsIGMocGFzdGUoIkFVQyA9IiwgQVVDKSksIGJ0eSA9ICJuIiwgY2V4ID0gMC44KQ0KDQpwbG90KDE6bGVuZ3RoKGN1dC5wcm9iKSwgc2Vuc3BlLm10eFszLF0sIA0KICAgICB4bGFiID0gIkN1dC1vZmYgUHJvYmFiaWxpdHkiLCB5bGFiID0gIkFjY3VyYWN5IiwgDQogICAgIHlsaW0gPSBjKG1pbihzZW5zcGUubXR4WzMsXSksIDEpLCBheGVzID0gRkFMU0UsIA0KICAgICBtYWluID0gIkN1dC1vZmYgdnMgQWNjdXJhY3kiLCBjZXgubWFpbiA9IDAuOSwgY29sLm1haW4gPSAibmF2eSIpDQpheGlzKDEsIGF0ID0gMToyMCwgbGFiZWwgPSByb3VuZChjdXQucHJvYiwgMiksIGxhcyA9IDIpDQpheGlzKDIpDQpwb2ludHMoaWR4LCBzZW5zcGUubXR4WzMsXVtpZHhdLCBwY2ggPSAxOSwgY29sID0gInJlZCIpDQpzZWdtZW50cyhpZHgsIG1pbihzZW5zcGUubXR4WzMsXSksIGlkeCwgc2Vuc3BlLm10eFszLF1baWR4XSwgY29sID0gInJlZCIpDQpsZWdlbmQoInRvcHJpZ2h0IiwgYyhwYXN0ZSgiT3B0aW1hbCBjdXQtb2ZmIHByb2IgPSAiLCByb3VuZChtZWRpYW4oY3V0LnByb2JbaWR4XSksIDMpLCBzZXAgPSAiIiksIA0KICAgICAgICAgICAgICAgICAgICAgcGFzdGUoIkFjY3VyYWN5ID0gIiwgcm91bmQobWVhbihzZW5zcGUubXR4WzMsXVtpZHhdKSwgMyksIHNlcCA9ICIiKSksDQogICAgICAgY2V4ID0gMC44LCBidHkgPSAibiIsIGFkaiA9IC0wKQ0KYGBgDQoNCkFzIHNob3duIGluIHRoZSBncmFwaCBhYm92ZSwgdGhpcyBtb2RlbOKAmXMgKipBVUMqKiBpcyAwLjg2Ni4gVGhpcyB2YWx1ZSB3aWxsIGJlIHVzZWQgdG8gY29tcGFyZSBvdGhlciBtb2RlbHMuDQoNCiMgTW9kZWwgQ29tcGFyaXNvbg0KDQpCZWxvdyBpcyBhIGxpc3Qgb2YgdGhlIGRpZmZlcmVudCBtb2RlbGluZyB0ZWNobmlxdWVzIGFuZCB0aGVpciBjb3JyZXNwb25kaW5nIEFVQyB2YWx1ZXMgaW4gb3JkZXIgZnJvbSBncmVhdGVzdCB0byBsZWFzdA0KDQoxKSBEZWNpc2lvbiBUcmVlOiAwLjk0MTM4DQoyKSBCYWdnaW5nOiAwLjg2Ng0KMykgTG9nIFJlZyBBVUM6IDAuODYzNw0KNCkgUGVyY2VwdHJvbiBBVUM6IDAuNQ0KDQojIENvbmNsdXNpb24NCg0KVGhlcmVmb3JlLCB0aGUgbW9zdCBlZmZlY3RpdmUgbW9kZWwgZm9yIG91ciBnaXZlbiBkYXRhc2V0IGlzIHRoZSBkZWNpc2lvbiB0cmVlIG1vZGVsLCB3aXRoIGFuICoqQVVDKiogb2YgMC45NDEzOC4gQSBjb3B5IG9mIHRoZSBkZWNpc2lvbiB0cmVlIGlzIGJlbG93Og0KDQpgYGB7ciBiZXN0IG1vZGVsLCBpbmNsdWRlID0gVFJVRX0NCnJwYXJ0LnBsb3QoZ2luaS50cmVlLjEuMSwgbWFpbiA9ICJUcmVlIHdpdGggR2luaSBpbmRleDogbm9uLXBlbmFsaXphdGlvbiIpDQpgYGANCg0KSSB3aWxsIG5vdyBhZGRyZXNzIHRoZSBxdWVzdGlvbnMgYXNrZWQgaW4gdGhlIGJlZ2lubmluZyBvZiB0aGUgcmVwb3J0IG5vdyB0aGF0IHRoZSBiZXN0IG1vZGVsIGhhcyBiZWVuIHNlbGVjdGVkDQoNCiMjIFdoYXQgYXNwZWN0cyBvZiBhIGxvYW4gcmVxdWVzdCBkZXRlcm1pbmVzIHRoZSBhcHByb3ZhbCBzdGF0dXMgb2YgdGhlIHJlcXVlc3Q/DQoNCkFzIHNob3duIGluIHRoZSBkZWNpc2lvbiB0cmVlIGFib3ZlLCB0aGUgZm9sbG93aW5nIHZhcmlhYmxlcyBhcmUgc2lnbmlmaWNhbnQgaW4gcHJlZGljdGluZyBhbiBhcHBsaWNhbnQncyBjaGFuY2VzIG9mIGdldHRpbmcgdGhlaXIgbG9hbiBhcHByb3ZlZDoNCg0KLSBgcHJldmlvdXNfbG9hbl9kZWZhdWx0c19vbl9maWxlYA0KLSBgbG9hbl9wZXJjZW50X2luY29tZWANCi0gYGxvYW5faW50X3JhdGVgDQotIGBwZXJzb25faW5jb21lYA0KLSBgcGVyc29uX2hvbWVfb3duZXJzaGlwYA0KDQojIyBEb2VzIGRlbW9ncmFwaGljIGluZm9ybWF0aW9uIG9mIHRoZSBsb2FuIHJlcXVlc3RlciBpbmZsdWVuY2UgdGhlIGNoYW5jZXMgb2YgYSByZXF1ZXN0IGJlaW5nIGFwcHJvdmVkPw0KDQpUaGUgb25seSBkZW1vZ3JhcGhpYyB2YXJpYWJsZXMgdGhhdCBpbmZsdWVuY2UgdGhlIGNoYW5jZXMgb2YgYSBsb2FuIHJlcXVlc3QgYmVpbmcgYXBwcm92ZWQgYXJlIGBwcmV2aW91c19sb2FuX2RlZmF1bHRzX29uX2ZpbGVgLCBgcGVyc29uX2luY29tZWAsIGFuZCBgcGVyc29uX2hvbWVfb3duZXJzaGlwYC4gT3RoZXIgZGVtb2dyYXBoaWMgdmFyaWFibGVzIHN1Y2ggYXMgYHBlcnNvbl9hZ2VgIGFuZCBgcGVyc29uX2dlbmRlcmAgZGlkIG5vdCBoYXZlIHNpZ25pZmljYW50IGluZmx1ZW5jZS4NCg0KIyBSZWNvbW1lbmRhdGlvbg0KDQpXaGlsZSB0aGUgZGVjaXNpb24gdHJlZSB3YXMgYmVzdCBmb3IgdGhlIHByb3ZpZGVkIGRhdGFzZXQsIGluc3RhbmNlcyBtYXkgb2NjdXIgd2hlcmUgZGlmZmVyZW50IG1vZGVsaW5nIHRlY2huaXF1ZXMgYXJlIGZvdW5kIHRvIGJlIG1vcmUgZWZmZWN0aXZlLiBJIGVuY291cmFnZSBldmVyeW9uZSB0byBkbyB0aGVpciBvd24gcmVzZWFyY2ggd2hlbiBkZXRlcm1pbmluZyB0aGUgYmVzdCBtb2RlbGluZyB0ZWNobmlxdWUu