1. Introduction

The Lending Club data contains complete loan data for all loans issued through the 2007-2015, including the current loan status (Current, Late, Fully Paid, etc.) and latest payment information. Financial decision making regarding to the credit risks is one of the crucial operations for the lending businesses. In this analysis, I present here exploratory data analysis, visualizations, word cloud, loan outcome prediction and lots of other interesting insights.

# read data file 
data <- read.csv("loan.csv")
# See dimension of dataset
dim(data)
[1] 887379     74

1.1 Data Overview

The data is sourced from the Kaggle website https://www.kaggle.com/wendykan/lending-club-loan-data

The LendingClub is a matrix of about 890 thousand observations and 74 variables. Some important features:

  • loan_amnt - Amount of money requested by the borrower.

  • int_rate - Interest rate of the loan.

  • grade - Loan grade with categories A, B, C, D, E, F, G.

  • loan_status - Current loan status, i.e. approved, default, late, etc.

  • annual_inc - Borrowers annual income.

  • purpose - The primary purpose of borrowing.

2. Risk Modelling

In this section, I will fit logistics regression model for loan outcome and predict future loan status. I have divided it into four subsections through different steps.

2.1 Select Variables

The original dataset contains 74 columns, which are not necessarily useful and relevant in regression model. In addition, there are some missing values. Thus I did feature selection and data cleaning first. I select the following features:

  • loan_status - Current loan status, binary variable.

  • loan_amnt - Amount of money requested by the borrower.

  • int_rate - Interest rate of the loan.

  • emp_length - Borrowers’ employment length.

  • grade - Loan grade with categories A, B, C, D, E, F, G.

  • annual_inc - Borrowers’ annual income.

  • home_ownership - A form of housing tenure: rent/own/mortgage/none, factors.

  • term - Regular payments over a set period of time, factors.

loan <- data %>%
        select(loan_status, loan_amnt, int_rate, grade, emp_length, home_ownership, annual_inc, term)
loan <- loan %>%
        filter(!is.na(annual_inc) , 
               !(home_ownership %in% c('NONE' , 'ANY')) , 
               emp_length != 'n/a')
loan$grade <- as.factor(loan$grade)
loan$home_ownership <- as.factor(loan$home_ownership)
loan$term <- as.factor(loan$term)

2.2 Binary Outcomes

I neet to establish a binary outcome, i.e. 0 as default, 1 as non-default. we have 10 outcomes at indecated above, I select “Fully paid” as 1; then “Charged off”,“Late(31-120 days)”,“Late(16-30days)”,“Default”, “In Grace Period” as 0. The next step is to select loan data only with 0 and 1.

loan <- loan %>%
        mutate(loan_outcome = ifelse(loan_status %in% c('Charged Off' , 'Default', 'Late (16-30 days)','Late (31-120 days)','In Grace Period') , 0, 
                                     ifelse(loan_status == 'Fully Paid' , 1 , 'No info')))
loan2 <- loan %>%select(-loan_status) %>%filter(loan_outcome %in% c(0 , 1))

2.3 Fit Model

Split the dataset by 6:4, as training set and test set. The new dataset has 263281 rows, thus training set has 151446 rows, and test set has 100962.

dim(loan2)
[1] 263281      8
loan2$loan_outcome = as.numeric(loan2$loan_outcome)
training <- loan2[1:151446, ]
test <- loan2[151447:263281, ]
# Fit logistic regression
model1 <- glm(loan_outcome ~ loan_amnt + int_rate + grade + emp_length + home_ownership + annual_inc + term, data = training, family = binomial(link = 'logit'))
summary(model1)

Call:
glm(formula = loan_outcome ~ loan_amnt + int_rate + grade + emp_length + 
    home_ownership + annual_inc + term, family = binomial(link = "logit"), 
    data = training)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-4.5671   0.3449   0.5380   0.6911   1.3784  

Coefficients:
                      Estimate Std. Error z value Pr(>|z|)    
(Intercept)          3.404e+00  5.179e-02  65.735  < 2e-16 ***
loan_amnt           -1.364e-05  1.045e-06 -13.050  < 2e-16 ***
int_rate            -1.361e-01  4.930e-03 -27.605  < 2e-16 ***
gradeB              -1.764e-01  3.436e-02  -5.132 2.87e-07 ***
gradeC              -1.769e-01  4.601e-02  -3.846  0.00012 ***
gradeD              -8.686e-02  5.863e-02  -1.482  0.13843    
gradeE               8.298e-02  7.214e-02   1.150  0.25005    
gradeF               2.365e-01  8.576e-02   2.757  0.00583 ** 
gradeG               3.116e-01  1.081e-01   2.881  0.00396 ** 
emp_length1 year     3.033e-02  3.463e-02   0.876  0.38113    
emp_length10+ years -4.355e-02  2.665e-02  -1.634  0.10222    
emp_length2 years    2.540e-02  3.179e-02   0.799  0.42424    
emp_length3 years   -2.321e-03  3.279e-02  -0.071  0.94357    
emp_length4 years    4.186e-02  3.490e-02   1.199  0.23043    
emp_length5 years    2.602e-02  3.320e-02   0.784  0.43319    
emp_length6 years   -4.306e-02  3.494e-02  -1.232  0.21779    
emp_length7 years   -3.291e-02  3.598e-02  -0.915  0.36042    
emp_length8 years   -6.148e-02  3.871e-02  -1.588  0.11226    
emp_length9 years   -6.663e-02  4.138e-02  -1.610  0.10733    
home_ownershipOTHER -2.798e-01  2.230e-01  -1.254  0.20973    
home_ownershipOWN   -7.105e-02  2.626e-02  -2.705  0.00682 ** 
home_ownershipRENT  -1.030e-01  1.507e-02  -6.835 8.20e-12 ***
annual_inc           6.633e-06  2.274e-07  29.172  < 2e-16 ***
term 60 months      -4.863e-01  1.835e-02 -26.497  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 150151  on 151445  degrees of freedom
Residual deviance: 138273  on 151422  degrees of freedom
AIC: 138321

Number of Fisher Scoring iterations: 5

loan_amnt, int_rate, grade B, grade C, grade F, grade G, home_ownership type own, home_ownership type rent, annual_inc and term 60 months are clearly significant (the significance p-value is indicated by *** in the summary of the fit).

  • The coefficients loan_amnt, int_rate, grade B, grade C, home_ownership type own, home_ownership type rent, and term 60 months are negative, these coefficients will negative effect the probability of non-default.

  • The coefficients annual_inc, grade F and grade G are positive, these coefficients will positive effect the probability of non-default.

    • An interesting observation is that the coefficients of grade F and grade G are positive, and I need more detailed analysis on these.

The interpretation can be provided in terms of log-odds. Applying model1 to our training dataset, each estimated coefficient is the expected change in the log odds of default for a unit increase in the corresponding predictor variable holding the other predictor variables constant at certain value. Each exponentiated coefficient is the ratio of two odds, or the change in odds in the multiplicative scale for a unit increase in the corresponding predictor variable holding other variables at certain value. Since I set 0 as default, 1 as non-default, the interpretation is a little bit counter-intuitive.

exp(coef(model1))
        (Intercept)           loan_amnt            int_rate              gradeB              gradeC 
         30.0934106           0.9999864           0.8727535           0.8383189           0.8378266 
             gradeD              gradeE              gradeF              gradeG    emp_length1 year 
          0.9168021           1.0865189           1.2667901           1.3655773           1.0307939 
emp_length10+ years   emp_length2 years   emp_length3 years   emp_length4 years   emp_length5 years 
          0.9573850           1.0257297           0.9976816           1.0427457           1.0263591 
  emp_length6 years   emp_length7 years   emp_length8 years   emp_length9 years home_ownershipOTHER 
          0.9578548           0.9676293           0.9403701           0.9355377           0.7559713 
  home_ownershipOWN  home_ownershipRENT          annual_inc      term 60 months 
          0.9314146           0.9021214           1.0000066           0.6149161 
  1. For example, the odds of a borrower will not default in the future with term 60 months over the odds of default with term 36 months is exp(-4.863e-01) = 0.6149161, holding other variables unchanged. In terms of percent change, we can say that the odds for non-default with long-term loan is 61.49% of short-term loan, i.e. long-term loan is more likely default in the future.

2.4 Predict Model On Test Set

# Prediction on test set
predict_model1 <- predict.glm(model1, newdata = test, type = "response")
summary(predict_model1)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.3048  0.7182  0.8126  0.7876  0.8771  1.0000 

Here I plot the ROC curve to see the sensitivity and specificity. As a result, it gives Area under the curve: 67.12%.

library(pROC)
# Area Under Curve
roc(test$loan_outcome, predict_model1, percent=T, boot.n=1000, ci.alpha=0.9, stratified=T, plot=TRUE, grid=TRUE, show.thres=TRUE, legacy.axes = TRUE, reuse.auc = TRUE, print.auc = TRUE, print.thres.col = "blue", ci=TRUE, ci.type="bars", print.thres.cex = 0.7, col = 'red', main = paste("ROC curve using","(N = ",nrow(test),")") )

Call:
roc.default(response = test$loan_outcome, predictor = predict_model1,     percent = T, ci = TRUE, plot = TRUE, boot.n = 1000, ci.alpha = 0.9,     stratified = T, grid = TRUE, show.thres = TRUE, legacy.axes = TRUE,     reuse.auc = TRUE, print.auc = TRUE, print.thres.col = "blue",     ci.type = "bars", print.thres.cex = 0.7, col = "red", main = paste("ROC curve using",         "(N = ", nrow(test), ")"))

Data: predict_model1 in 33185 controls (test$loan_outcome 0) < 78650 cases (test$loan_outcome 1).
Area under the curve: 67.13%
95% CI: 66.79%-67.47% (DeLong)

3. Exploratory Data Analysis for Borrowers

In this section, I will detail exploratory data analysis and visualization for borrowers. I have divided it into six subsections through a variety of different visualization.

3.1 Borrowers Home Ownership Type

In total, there are six type of home ownership types. Most borrowers have mortgages(443557) and 356117 borrowers rent house/apartment.

data %>%
  group_by(home_ownership) %>%
  summarize(freq = n()) %>%
  ggplot(aes(reorder(home_ownership, freq), y = freq, fill = freq)) +   
  geom_bar(stat = "identity", position = "dodge") +
  xlab("home_ownership") +
  ylab("Frequency") +
  theme_fivethirtyeight() + 
  theme(legend.position ='none', axis.text.x = element_text(size = 15)) + 
  geom_text(aes(label = freq), vjust = -0.1, size = 4.5) +
  scale_fill_gradientn(name = '',colours = rev(brewer.pal(10,'Spectral'))) +
  ggtitle("Home Ownership")

3.2 Borrowers Employment Length

I look at the borrower’s employment length. Most borrowers have 10-year+ employment length.

data %>%
  group_by(emp_length) %>%
  summarize(freq = n()) %>%
  top_n(50) %>%
  ggplot(aes(reorder(emp_length, freq), y = freq, fill = freq)) +   
  geom_bar(stat = "identity", position = "dodge") +
  xlab("Employment Length") +
  ylab("Frequency") +
  coord_flip() +
  theme_fivethirtyeight() + 
  theme(legend.position ='none', axis.text.y = element_text(size = 12)) + 
  scale_fill_gradientn(name = '',colours = rev(brewer.pal(10,'Spectral'))) +
  ggtitle("Employment Length")
Selecting by freq

3.3 Borrowers’ Grade

There are seven grades. Most borrowers have grade B.

data %>%
  group_by(grade) %>%
  summarize(freq = n()) %>%
  ggplot(aes(reorder(grade, freq), y = freq, fill = freq)) +   
  geom_bar(stat = "identity", position = "dodge") +
  xlab("grade") +
  ylab("Frequency") +
  theme_fivethirtyeight() + 
  theme(legend.position ='none', axis.text.x = element_text(size = 15)) + 
  geom_text(aes(label = freq), vjust = -0.1, size = 4.5) +
  scale_fill_gradientn(name = '',colours = rev(brewer.pal(10,'Spectral'))) +
  ggtitle("Grade")

3.4 Distribution of the loan amount by borrower’s grade

I seperate loan amount by different grades, we can see that the lower the borrower’s grade, the higher amount of borrowing.

ggplot(data, aes(x=loan_amnt,fill=grade))+
  geom_density(alpha = 0.3) + 
  theme(legend.position="none") +
  theme_fivethirtyeight() + 
  xlab("Loan Amount") + 
  ggtitle("Loan Amount by Borrower's Grade") + 
  facet_grid(grade ~ ., scales = "free")

3.5 Borrower’s Annual Income

Here I create bins to examine the annual income distribution.

summary(data$annual_inc)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
      0   45000   65000   75028   90000 9500000       4 
# set up boundaries for intervals/bins
breaks <- c(0,20000,40000,60000,80000,90000,100000,150000,300000, 9500000)
# specify interval/bin labels
labels <- c("<20000", "20000-40000)", "40000-60000)", "60000-80000)", "80000-90000)", "90000-100000)", "100000-150000)", "150000-300000)", "<=9500000")
# bucketing data points into bins
bins <- cut(data$annual_inc, breaks, include.lowest = T, right=FALSE, labels=labels)
# inspect bins
bin <- summary(bins)
freq_inc <- c(10204, 129409, 241593, 203729, 7258i, 54965, 123426, 45865, 5603)
annual_income <- data.frame(labels, freq_inc)
plot(bins, main="Annual Income", ylab="Annual Income")

3.6 Borrower’s Purpose

We observe that debt consolidation is the most frequently used reason to borrow.

data %>%
  group_by(purpose) %>%
  summarize(freq = n()) %>%
  top_n(50) %>%
  ggplot(aes(reorder(purpose, freq), y = freq, fill = freq)) +   
  geom_bar(stat = "identity", position = "dodge") +
  xlab("Purpose") +
  ylab("Frequency") +
  coord_flip() +
  theme_fivethirtyeight() + 
  theme(legend.position ='none', axis.text.y = element_text(size = 12)) + 
  scale_fill_gradientn(name = '',colours = rev(brewer.pal(10,'Spectral'))) +
  ggtitle("purpose")

4. Exploratory Data Analysis for Loans

In this section, I will detail exploratory data analysis and visualization for loans. I have divided it into five subsections through a variety of different visualization.

4.1 Loan Amount Summary

I create a line chart to view the loan amount. The median loan amount is 13000 and mean is 14755.

summary(data$loan_amnt)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    500    8000   13000   14755   20000   35000 
ggplot(data, aes(x=loan_amnt)) + 
  geom_density() + 
  theme(legend.position="none") +
  xlab("Loan Amount") + 
  ggtitle("Loan Amount") + 
  geom_vline(aes(xintercept=mean(loan_amnt)), color="black", linetype="dashed", size=1) +
  geom_vline(aes(xintercept=median(loan_amnt)), color="red", linetype="dashed", size=1) + 
  geom_vline(aes(xintercept=quantile(loan_amnt, 0.25)), color="blue", linetype="dashed", size=1) +
  geom_vline(aes(xintercept=quantile(loan_amnt, 0.75)), color="yellow", linetype="dashed", size=1)

4.2 Interest Rate Summary

Here I examine the distribution of interest rate. The median interest rate is 12.99 and mean is 13.25.

summary(data$int_rate)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   5.32    9.99   12.99   13.25   16.20   28.99 
ggplot(data, aes(x=int_rate)) + 
  geom_density() + 
  theme(legend.position="none") +
  xlab("Interest Rate") + 
  ggtitle("Interest Rate") + 
  geom_vline(aes(xintercept=mean(int_rate)), color="black", linetype="dashed", size=1) +
  geom_vline(aes(xintercept=median(int_rate)), color="red", linetype="dashed", size=1) +
  geom_vline(aes(xintercept=quantile(int_rate, 0.25)), color="blue", linetype="dashed", size=1) +
  geom_vline(aes(xintercept=quantile(int_rate, 0.75)), color="yellow", linetype="dashed", size=1)

4.3 Loan Term Length

There are two term length, 3-year and 5-year. Most borrowers have loan in 3 years.

data %>%
  group_by(term) %>%
  summarize(freq = n()) %>%
  ggplot(aes(reorder(term, freq), y = freq, fill = freq)) +   
  geom_bar(stat = "identity", position = "dodge", width = 0.4) +
  xlab("Term") +
  ylab("Frequency") +
  theme_fivethirtyeight() + 
  theme(legend.position ='none', axis.text.x = element_text(size = 15)) + 
  geom_text(aes(label = freq), vjust = -0.1, size = 4.5) +
  scale_fill_gradientn(name = '',colours = rev(brewer.pal(10,'Spectral'))) +
  ggtitle("Term Length")

4.4 Loan Distribution by Geographic Region

Here I plot the loan volume by different US states.

data$region <- data$addr_state
data$region <- as.factor(data$region)
levels(data$region)<- c("alaska", "alabama","arkansas", "arizona", "california","colorado","connecticut","district of columbia","delaware","florida","georgia","hawaii","iowa","idaho","illinois","indiana","kansas","kentucky","louisiana","massachusetts","maryland","maine","michigan","minnesota","missouri","mississippi","montana","north carolina","north dakota","nebraska","new hampshire","new jersey","new mexico","nevada","new york","ohio","oklahoma","oregon","pennsylvania","rhode island","south carolina","south dakota","tennessee","texas","utah","virginia","vermont","washington","wisconsin","west virginia","wyoming")
us_states <- map_data("state")
loanvolume <-data %>% group_by(region) %>%
                summarise(value = sum(loan_amnt, na.rm=TRUE))
loanvolume$region <- as.character(loanvolume$region)
loanvolume
loandata <- merge(us_states, loanvolume, by="region")
ggplot() + 
  geom_polygon(data=loandata, aes(x=long, y=lat, group = group, fill=loandata$value)) +
  theme_void() +
  labs(fill = "Gradient" ,title = "Loan Volume by States", x="", y="")

4.5 Loan Status

We observe that most loan status are current, which means this loan still has one or more scheduled future payments. It also reflect that some late and default loans.

data %>%
  group_by(loan_status) %>%
  summarize(freq = n()) %>%
  ggplot(aes(reorder(loan_status, freq), y = freq, fill = freq)) +   
  geom_bar(stat = "identity", position = "dodge", width=0.4) +
  xlab("Loan Status") +
  ylab("Frequency") +
  theme_fivethirtyeight() + 
  coord_flip() +
  theme(legend.position ='none', axis.text.x = element_text(size = 7)) + 
  scale_fill_gradientn(name = '',colours = rev(brewer.pal(11,'Spectral'))) +
  ggtitle("Loan Status")

summary(data$loan_status)
                                        Charged Off                                             Current 
                                              45248                                              601779 
                                            Default Does not meet the credit policy. Status:Charged Off 
                                               1219                                                 761 
 Does not meet the credit policy. Status:Fully Paid                                          Fully Paid 
                                               1988                                              207723 
                                    In Grace Period                                              Issued 
                                               6253                                                8460 
                                  Late (16-30 days)                                  Late (31-120 days) 
                                               2357                                               11591 

5. Word Cloud

The WordCloud generator presents interesting insights.

5.1 Word Cloud By Title

loanCorpus <- Corpus(VectorSource(data$title))
loanCorpus <- tm_map(loanCorpus, content_transformer(tolower))
loanCorpus <- tm_map(loanCorpus, removePunctuation)
wordcloud(loanCorpus, max.words = 100, random.order = FALSE,rot.per=0, 
          colors=brewer.pal(8, "Dark2"))

5.2 Word Cloud By Employment Title

loantitleCorpus <- Corpus(VectorSource(data$emp_title))
loantitleCorpus <- tm_map(loantitleCorpus, content_transformer(tolower))
loantitleCorpus <- tm_map(loantitleCorpus, removePunctuation)
wordcloud(loantitleCorpus, max.words = 100, random.order = FALSE,rot.per=0, 
          colors=brewer.pal(8, "Dark2"))

LS0tCnRpdGxlOiAiTGVuZGluZ0NsdWIgRGF0YSBBbmFseXNpcyIKYXV0aG9yOiAiTWVuZ3lhbyBYdWUiCmRhdGU6ICcyMDE5LTAzLTA4JwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIHdvcmRfZG9jdW1lbnQ6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojMS4gSW50cm9kdWN0aW9uCgpUaGUgTGVuZGluZyBDbHViIGRhdGEgY29udGFpbnMgY29tcGxldGUgbG9hbiBkYXRhIGZvciBhbGwgbG9hbnMgaXNzdWVkIHRocm91Z2ggdGhlIDIwMDctMjAxNSwgaW5jbHVkaW5nIHRoZSBjdXJyZW50IGxvYW4gc3RhdHVzIChDdXJyZW50LCBMYXRlLCBGdWxseSBQYWlkLCBldGMuKSBhbmQgbGF0ZXN0IHBheW1lbnQgaW5mb3JtYXRpb24uIEZpbmFuY2lhbCBkZWNpc2lvbiBtYWtpbmcgcmVnYXJkaW5nIHRvIHRoZSBjcmVkaXQgcmlza3MgaXMgb25lIG9mIHRoZSBjcnVjaWFsIG9wZXJhdGlvbnMgZm9yIHRoZSBsZW5kaW5nIGJ1c2luZXNzZXMuIEluIHRoaXMgYW5hbHlzaXMsIEkgcHJlc2VudCBoZXJlIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMsIHZpc3VhbGl6YXRpb25zLCB3b3JkIGNsb3VkLCBsb2FuIG91dGNvbWUgcHJlZGljdGlvbiBhbmQgbG90cyBvZiBvdGhlciBpbnRlcmVzdGluZyBpbnNpZ2h0cy4KCgpgYGB7ciBsb2FkLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KGNob3JvcGxldGhyKQpsaWJyYXJ5KGNob3JvcGxldGhyTWFwcykKbGlicmFyeShEZXNjVG9vbHMpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoImdncGxvdDIiKQpsaWJyYXJ5KCJSQ29sb3JCcmV3ZXIiKQpsaWJyYXJ5KCJnZ3RoZW1lcyIpCmxpYnJhcnkoImdncmVwZWwiKQpsaWJyYXJ5KCJncmlkRXh0cmEiKQpsaWJyYXJ5KCJsdWJyaWRhdGUiKQpsaWJyYXJ5KHVzbWFwKQpsaWJyYXJ5KCJ0bSIpCmxpYnJhcnkoIlNub3diYWxsQyIpCmxpYnJhcnkoIndvcmRjbG91ZCIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KEdHYWxseSkKbGlicmFyeShEVCkKYGBgCgoKYGBge3IgbG9hZCBkYXRhfQojIHJlYWQgZGF0YSBmaWxlIApkYXRhIDwtIHJlYWQuY3N2KCJsb2FuLmNzdiIpCgojIFNlZSBkaW1lbnNpb24gb2YgZGF0YXNldApkaW0oZGF0YSkKYGBgCgoKIyMxLjEgRGF0YSBPdmVydmlldwpUaGUgZGF0YSBpcyBzb3VyY2VkIGZyb20gdGhlIEthZ2dsZSB3ZWJzaXRlIGBodHRwczovL3d3dy5rYWdnbGUuY29tL3dlbmR5a2FuL2xlbmRpbmctY2x1Yi1sb2FuLWRhdGFgCgpUaGUgTGVuZGluZ0NsdWIgaXMgYSBtYXRyaXggb2YgYWJvdXQgODkwIHRob3VzYW5kIG9ic2VydmF0aW9ucyBhbmQgNzQgdmFyaWFibGVzLgpTb21lIGltcG9ydGFudCBmZWF0dXJlczoKCiogYGxvYW5fYW1udGAgLSBBbW91bnQgb2YgbW9uZXkgcmVxdWVzdGVkIGJ5IHRoZSBib3Jyb3dlci4KCiogYGludF9yYXRlYCAtIEludGVyZXN0IHJhdGUgb2YgdGhlIGxvYW4uCgoqIGBncmFkZWAgLSBMb2FuIGdyYWRlIHdpdGggY2F0ZWdvcmllcyBBLCBCLCBDLCBELCBFLCBGLCBHLgoKKiBgbG9hbl9zdGF0dXNgIC0gQ3VycmVudCBsb2FuIHN0YXR1cywgaS5lLiBhcHByb3ZlZCwgZGVmYXVsdCwgbGF0ZSwgZXRjLgoKKiBgYW5udWFsX2luY2AgLSBCb3Jyb3dlcnMgYW5udWFsIGluY29tZS4KCiogYHB1cnBvc2VgIC0gVGhlIHByaW1hcnkgcHVycG9zZSBvZiBib3Jyb3dpbmcuCgoKCiMyLiBSaXNrIE1vZGVsbGluZyAKSW4gdGhpcyBzZWN0aW9uLCBJIHdpbGwgZml0IGxvZ2lzdGljcyByZWdyZXNzaW9uIG1vZGVsIGZvciBsb2FuIG91dGNvbWUgYW5kIHByZWRpY3QgZnV0dXJlIGxvYW4gc3RhdHVzLiBJIGhhdmUgZGl2aWRlZCBpdCBpbnRvIGZvdXIgc3Vic2VjdGlvbnMgdGhyb3VnaCBkaWZmZXJlbnQgc3RlcHMuIAoKIyMjMi4xIFNlbGVjdCBWYXJpYWJsZXMKClRoZSBvcmlnaW5hbCBkYXRhc2V0IGNvbnRhaW5zIDc0IGNvbHVtbnMsIHdoaWNoIGFyZSBub3QgbmVjZXNzYXJpbHkgdXNlZnVsIGFuZCByZWxldmFudCBpbiByZWdyZXNzaW9uIG1vZGVsLiBJbiBhZGRpdGlvbiwgdGhlcmUgYXJlIHNvbWUgbWlzc2luZyB2YWx1ZXMuIFRodXMgSSBkaWQgZmVhdHVyZSBzZWxlY3Rpb24gYW5kIGRhdGEgY2xlYW5pbmcgZmlyc3QuIEkgc2VsZWN0IHRoZSBmb2xsb3dpbmcgZmVhdHVyZXM6CgoqIGBsb2FuX3N0YXR1c2AgLSBDdXJyZW50IGxvYW4gc3RhdHVzLCBiaW5hcnkgdmFyaWFibGUuCgoqIGBsb2FuX2FtbnRgIC0gQW1vdW50IG9mIG1vbmV5IHJlcXVlc3RlZCBieSB0aGUgYm9ycm93ZXIuCgoqIGBpbnRfcmF0ZWAgLSBJbnRlcmVzdCByYXRlIG9mIHRoZSBsb2FuLgoKKiBgZW1wX2xlbmd0aGAgLSBCb3Jyb3dlcnMnIGVtcGxveW1lbnQgbGVuZ3RoLgoKKiBgZ3JhZGVgIC0gTG9hbiBncmFkZSB3aXRoIGNhdGVnb3JpZXMgQSwgQiwgQywgRCwgRSwgRiwgRy4KCiogYGFubnVhbF9pbmNgIC0gQm9ycm93ZXJzJyBhbm51YWwgaW5jb21lLgoKKiBgaG9tZV9vd25lcnNoaXBgIC0gQSBmb3JtIG9mIGhvdXNpbmcgdGVudXJlOiByZW50L293bi9tb3J0Z2FnZS9ub25lLCBmYWN0b3JzLgoKKiBgdGVybWAgLSBSZWd1bGFyIHBheW1lbnRzIG92ZXIgYSBzZXQgcGVyaW9kIG9mIHRpbWUsIGZhY3RvcnMuCgpgYGB7ciBmZWF0dXJlIHNlbGVjdGlvbn0KbG9hbiA8LSBkYXRhICU+JQogICAgICAgIHNlbGVjdChsb2FuX3N0YXR1cywgbG9hbl9hbW50LCBpbnRfcmF0ZSwgZ3JhZGUsIGVtcF9sZW5ndGgsIGhvbWVfb3duZXJzaGlwLCBhbm51YWxfaW5jLCB0ZXJtKQoKbG9hbiA8LSBsb2FuICU+JQogICAgICAgIGZpbHRlcighaXMubmEoYW5udWFsX2luYykgLCAKICAgICAgICAgICAgICAgIShob21lX293bmVyc2hpcCAlaW4lIGMoJ05PTkUnICwgJ0FOWScpKSAsIAogICAgICAgICAgICAgICBlbXBfbGVuZ3RoICE9ICduL2EnKQoKbG9hbiRncmFkZSA8LSBhcy5mYWN0b3IobG9hbiRncmFkZSkKbG9hbiRob21lX293bmVyc2hpcCA8LSBhcy5mYWN0b3IobG9hbiRob21lX293bmVyc2hpcCkKbG9hbiR0ZXJtIDwtIGFzLmZhY3Rvcihsb2FuJHRlcm0pCmBgYAoKIyMjMi4yIEJpbmFyeSBPdXRjb21lcwoKSSBuZWV0IHRvIGVzdGFibGlzaCBhIGJpbmFyeSBvdXRjb21lLCBpLmUuIDAgYXMgZGVmYXVsdCwgMSBhcyBub24tZGVmYXVsdC4gd2UgaGF2ZSAxMCBvdXRjb21lcyBhdCBpbmRlY2F0ZWQgYWJvdmUsIEkgc2VsZWN0ICJGdWxseSBwYWlkIiBhcyAxOyB0aGVuICJDaGFyZ2VkIG9mZiIsIkxhdGUoMzEtMTIwIGRheXMpIiwiTGF0ZSgxNi0zMGRheXMpIiwiRGVmYXVsdCIsICJJbiBHcmFjZSBQZXJpb2QiIGFzIDAuIFRoZSBuZXh0IHN0ZXAgaXMgdG8gc2VsZWN0IGxvYW4gZGF0YSBvbmx5IHdpdGggMCBhbmQgMS4KCmBgYHtyIGJpbmFyeSB2YXJpYWJsZXN9CmxvYW4gPC0gbG9hbiAlPiUKICAgICAgICBtdXRhdGUobG9hbl9vdXRjb21lID0gaWZlbHNlKGxvYW5fc3RhdHVzICVpbiUgYygnQ2hhcmdlZCBPZmYnICwgJ0RlZmF1bHQnLCAnTGF0ZSAoMTYtMzAgZGF5cyknLCdMYXRlICgzMS0xMjAgZGF5cyknLCdJbiBHcmFjZSBQZXJpb2QnKSAsIDAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGxvYW5fc3RhdHVzID09ICdGdWxseSBQYWlkJyAsIDEgLCAnTm8gaW5mbycpKSkKCmxvYW4yIDwtIGxvYW4gJT4lc2VsZWN0KC1sb2FuX3N0YXR1cykgJT4lZmlsdGVyKGxvYW5fb3V0Y29tZSAlaW4lIGMoMCAsIDEpKQoKYGBgCgojIyMyLjMgRml0IE1vZGVsCgpTcGxpdCB0aGUgZGF0YXNldCBieSA2OjQsIGFzIHRyYWluaW5nIHNldCBhbmQgdGVzdCBzZXQuIFRoZSBuZXcgZGF0YXNldCBoYXMgMjYzMjgxIHJvd3MsIHRodXMgdHJhaW5pbmcgc2V0IGhhcyAxNTE0NDYgcm93cywgYW5kIHRlc3Qgc2V0IGhhcyAxMDA5NjIuCmBgYHtyIGZpdCBnbG0sIHdhcm5pbmc9RkFMU0V9CmRpbShsb2FuMikKbG9hbjIkbG9hbl9vdXRjb21lID0gYXMubnVtZXJpYyhsb2FuMiRsb2FuX291dGNvbWUpCnRyYWluaW5nIDwtIGxvYW4yWzE6MTUxNDQ2LCBdCnRlc3QgPC0gbG9hbjJbMTUxNDQ3OjI2MzI4MSwgXQoKIyBGaXQgbG9naXN0aWMgcmVncmVzc2lvbgptb2RlbDEgPC0gZ2xtKGxvYW5fb3V0Y29tZSB+IGxvYW5fYW1udCArIGludF9yYXRlICsgZ3JhZGUgKyBlbXBfbGVuZ3RoICsgaG9tZV9vd25lcnNoaXAgKyBhbm51YWxfaW5jICsgdGVybSwgZGF0YSA9IHRyYWluaW5nLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gJ2xvZ2l0JykpCnN1bW1hcnkobW9kZWwxKQpgYGAKYGxvYW5fYW1udGAsIGBpbnRfcmF0ZWAsIGBncmFkZSBCYCwgYGdyYWRlIENgLCBgZ3JhZGUgRmAsIGBncmFkZSBHYCwgYGhvbWVfb3duZXJzaGlwIHR5cGUgb3duYCwgYGhvbWVfb3duZXJzaGlwIHR5cGUgcmVudGAsIGBhbm51YWxfaW5jYCBhbmQgYHRlcm0gNjAgbW9udGhzYCBhcmUgY2xlYXJseSBzaWduaWZpY2FudCAodGhlIHNpZ25pZmljYW5jZSBwLXZhbHVlIGlzIGluZGljYXRlZCBieSAqKiogaW4gdGhlIHN1bW1hcnkgb2YgdGhlIGZpdCkuCgoqICBUaGUgY29lZmZpY2llbnRzIGBsb2FuX2FtbnRgLCBgaW50X3JhdGVgLCBgZ3JhZGUgQmAsIGBncmFkZSBDYCwgYGhvbWVfb3duZXJzaGlwIHR5cGUgb3duYCwgYGhvbWVfb3duZXJzaGlwIHR5cGUgcmVudGAsIGFuZCBgdGVybSA2MCBtb250aHNgIGFyZSBuZWdhdGl2ZSwgdGhlc2UgY29lZmZpY2llbnRzIHdpbGwgbmVnYXRpdmUgZWZmZWN0IHRoZSBwcm9iYWJpbGl0eSBvZiBub24tZGVmYXVsdC4KCiogIFRoZSBjb2VmZmljaWVudHMgYGFubnVhbF9pbmNgLCBgZ3JhZGUgRmAgYW5kIGBncmFkZSBHYCBhcmUgcG9zaXRpdmUsIHRoZXNlIGNvZWZmaWNpZW50cyB3aWxsIHBvc2l0aXZlIGVmZmVjdCB0aGUgcHJvYmFiaWxpdHkgb2Ygbm9uLWRlZmF1bHQuIAoKICAgICsgQW4gaW50ZXJlc3Rpbmcgb2JzZXJ2YXRpb24gaXMgdGhhdCB0aGUgY29lZmZpY2llbnRzIG9mIGBncmFkZSBGYCBhbmQgYGdyYWRlIEdgIGFyZSBwb3NpdGl2ZSwgYW5kIEkgbmVlZCBtb3JlIGRldGFpbGVkIGFuYWx5c2lzIG9uIHRoZXNlLgoKClRoZSBpbnRlcnByZXRhdGlvbiBjYW4gYmUgcHJvdmlkZWQgaW4gdGVybXMgb2YgbG9nLW9kZHMuIEFwcGx5aW5nIG1vZGVsMSB0byBvdXIgdHJhaW5pbmcgZGF0YXNldCwgZWFjaCBlc3RpbWF0ZWQgY29lZmZpY2llbnQgaXMgdGhlIGV4cGVjdGVkIGNoYW5nZSBpbiB0aGUgbG9nIG9kZHMgb2YgZGVmYXVsdCBmb3IgYSB1bml0IGluY3JlYXNlIGluIHRoZSBjb3JyZXNwb25kaW5nIHByZWRpY3RvciB2YXJpYWJsZSBob2xkaW5nIHRoZSBvdGhlciBwcmVkaWN0b3IgdmFyaWFibGVzIGNvbnN0YW50IGF0IGNlcnRhaW4gdmFsdWUuIEVhY2ggZXhwb25lbnRpYXRlZCBjb2VmZmljaWVudCBpcyB0aGUgcmF0aW8gb2YgdHdvIG9kZHMsIG9yIHRoZSBjaGFuZ2UgaW4gb2RkcyBpbiB0aGUgbXVsdGlwbGljYXRpdmUgc2NhbGUgZm9yIGEgdW5pdCBpbmNyZWFzZSBpbiB0aGUgY29ycmVzcG9uZGluZyBwcmVkaWN0b3IgdmFyaWFibGUgaG9sZGluZyBvdGhlciB2YXJpYWJsZXMgYXQgY2VydGFpbiB2YWx1ZS4gU2luY2UgSSBzZXQgMCBhcyBkZWZhdWx0LCAxIGFzIG5vbi1kZWZhdWx0LCB0aGUgaW50ZXJwcmV0YXRpb24gaXMgYSBsaXR0bGUgYml0IGNvdW50ZXItaW50dWl0aXZlLiAKCmBgYHtyIGludGVycHJldGF0aW9uIGFuZCBjb2VmfQpleHAoY29lZihtb2RlbDEpKQpgYGAKMS4gRm9yIGV4YW1wbGUsIHRoZSBvZGRzIG9mIGEgYm9ycm93ZXIgd2lsbCBub3QgZGVmYXVsdCBpbiB0aGUgZnV0dXJlIHdpdGggdGVybSA2MCBtb250aHMgb3ZlciB0aGUgb2RkcyBvZiBkZWZhdWx0IHdpdGggdGVybSAzNiBtb250aHMgaXMgZXhwKC00Ljg2M2UtMDEpID0gMC42MTQ5MTYxLCBob2xkaW5nIG90aGVyIHZhcmlhYmxlcyB1bmNoYW5nZWQuIEluIHRlcm1zIG9mIHBlcmNlbnQgY2hhbmdlLCB3ZSBjYW4gc2F5IHRoYXQgdGhlIG9kZHMgZm9yIG5vbi1kZWZhdWx0IHdpdGggbG9uZy10ZXJtIGxvYW4gaXMgNjEuNDklIG9mIHNob3J0LXRlcm0gbG9hbiwgaS5lLiBsb25nLXRlcm0gbG9hbiBpcyBtb3JlIGxpa2VseSBkZWZhdWx0IGluIHRoZSBmdXR1cmUuCgoKIyMjMi40IFByZWRpY3QgTW9kZWwgT24gVGVzdCBTZXQKYGBge3IgcHJlZGljdCBnbG19CiMgUHJlZGljdGlvbiBvbiB0ZXN0IHNldApwcmVkaWN0X21vZGVsMSA8LSBwcmVkaWN0LmdsbShtb2RlbDEsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gInJlc3BvbnNlIikKc3VtbWFyeShwcmVkaWN0X21vZGVsMSkKYGBgCgoKCgpIZXJlIEkgcGxvdCB0aGUgUk9DIGN1cnZlIHRvIHNlZSB0aGUgc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5LiBBcyBhIHJlc3VsdCwgaXQgZ2l2ZXMgQXJlYSB1bmRlciB0aGUgY3VydmU6IDY3LjEyJS4gCmBgYHtyIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkocFJPQykKCiMgQXJlYSBVbmRlciBDdXJ2ZQpyb2ModGVzdCRsb2FuX291dGNvbWUsIHByZWRpY3RfbW9kZWwxLCBwZXJjZW50PVQsIGJvb3Qubj0xMDAwLCBjaS5hbHBoYT0wLjksIHN0cmF0aWZpZWQ9VCwgcGxvdD1UUlVFLCBncmlkPVRSVUUsIHNob3cudGhyZXM9VFJVRSwgbGVnYWN5LmF4ZXMgPSBUUlVFLCByZXVzZS5hdWMgPSBUUlVFLCBwcmludC5hdWMgPSBUUlVFLCBwcmludC50aHJlcy5jb2wgPSAiYmx1ZSIsIGNpPVRSVUUsIGNpLnR5cGU9ImJhcnMiLCBwcmludC50aHJlcy5jZXggPSAwLjcsIGNvbCA9ICdyZWQnLCBtYWluID0gcGFzdGUoIlJPQyBjdXJ2ZSB1c2luZyIsIihOID0gIixucm93KHRlc3QpLCIpIikgKQpgYGAKCgoKIzMuIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMgZm9yIEJvcnJvd2VycwoKSW4gdGhpcyBzZWN0aW9uLCBJIHdpbGwgZGV0YWlsIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgYW5kIHZpc3VhbGl6YXRpb24gZm9yIGJvcnJvd2Vycy4gSSBoYXZlIGRpdmlkZWQgaXQgaW50byBzaXggc3Vic2VjdGlvbnMgdGhyb3VnaCBhIHZhcmlldHkgb2YgZGlmZmVyZW50IHZpc3VhbGl6YXRpb24uCgojIyMzLjEgQm9ycm93ZXJzIEhvbWUgT3duZXJzaGlwIFR5cGUgCgpJbiB0b3RhbCwgdGhlcmUgYXJlIHNpeCB0eXBlIG9mIGhvbWUgb3duZXJzaGlwIHR5cGVzLiBNb3N0IGJvcnJvd2VycyBoYXZlIG1vcnRnYWdlcyg0NDM1NTcpIGFuZCAzNTYxMTcgYm9ycm93ZXJzIHJlbnQgaG91c2UvYXBhcnRtZW50LiAgCmBgYHtyIGhvbWVfb3duZXJzaGlwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpkYXRhICU+JQogIGdyb3VwX2J5KGhvbWVfb3duZXJzaGlwKSAlPiUKICBzdW1tYXJpemUoZnJlcSA9IG4oKSkgJT4lCiAgZ2dwbG90KGFlcyhyZW9yZGVyKGhvbWVfb3duZXJzaGlwLCBmcmVxKSwgeSA9IGZyZXEsIGZpbGwgPSBmcmVxKSkgKyAgIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICB4bGFiKCJob21lX293bmVyc2hpcCIpICsKICB5bGFiKCJGcmVxdWVuY3kiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSdub25lJywgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KSkgKyAKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gZnJlcSksIHZqdXN0ID0gLTAuMSwgc2l6ZSA9IDQuNSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKG5hbWUgPSAnJyxjb2xvdXJzID0gcmV2KGJyZXdlci5wYWwoMTAsJ1NwZWN0cmFsJykpKSArCiAgZ2d0aXRsZSgiSG9tZSBPd25lcnNoaXAiKQpgYGAKCgojIyMzLjIgQm9ycm93ZXJzIEVtcGxveW1lbnQgTGVuZ3RoCgpJIGxvb2sgYXQgdGhlIGJvcnJvd2VyJ3MgZW1wbG95bWVudCBsZW5ndGguIE1vc3QgYm9ycm93ZXJzIGhhdmUgMTAteWVhcisgZW1wbG95bWVudCBsZW5ndGguCmBgYHtyIGVtcF9sZW5ndGgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmRhdGEgJT4lCiAgZ3JvdXBfYnkoZW1wX2xlbmd0aCkgJT4lCiAgc3VtbWFyaXplKGZyZXEgPSBuKCkpICU+JQogIHRvcF9uKDUwKSAlPiUKICBnZ3Bsb3QoYWVzKHJlb3JkZXIoZW1wX2xlbmd0aCwgZnJlcSksIHkgPSBmcmVxLCBmaWxsID0gZnJlcSkpICsgICAKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgeGxhYigiRW1wbG95bWVudCBMZW5ndGgiKSArCiAgeWxhYigiRnJlcXVlbmN5IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSdub25lJywgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkgKyAKICBzY2FsZV9maWxsX2dyYWRpZW50bihuYW1lID0gJycsY29sb3VycyA9IHJldihicmV3ZXIucGFsKDEwLCdTcGVjdHJhbCcpKSkgKwogIGdndGl0bGUoIkVtcGxveW1lbnQgTGVuZ3RoIikKYGBgCgojIyMzLjMgQm9ycm93ZXJzJyBHcmFkZQoKVGhlcmUgYXJlIHNldmVuIGdyYWRlcy4gTW9zdCBib3Jyb3dlcnMgaGF2ZSBncmFkZSBCLgpgYGB7ciBsb2FuIGdyYWRlfQpkYXRhICU+JQogIGdyb3VwX2J5KGdyYWRlKSAlPiUKICBzdW1tYXJpemUoZnJlcSA9IG4oKSkgJT4lCiAgZ2dwbG90KGFlcyhyZW9yZGVyKGdyYWRlLCBmcmVxKSwgeSA9IGZyZXEsIGZpbGwgPSBmcmVxKSkgKyAgIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICB4bGFiKCJncmFkZSIpICsKICB5bGFiKCJGcmVxdWVuY3kiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSdub25lJywgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KSkgKyAKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gZnJlcSksIHZqdXN0ID0gLTAuMSwgc2l6ZSA9IDQuNSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKG5hbWUgPSAnJyxjb2xvdXJzID0gcmV2KGJyZXdlci5wYWwoMTAsJ1NwZWN0cmFsJykpKSArCiAgZ2d0aXRsZSgiR3JhZGUiKQpgYGAKCgojIyMzLjQgRGlzdHJpYnV0aW9uIG9mIHRoZSBsb2FuIGFtb3VudCBieSBib3Jyb3dlcidzIGdyYWRlCgpJIHNlcGVyYXRlIGxvYW4gYW1vdW50IGJ5IGRpZmZlcmVudCBncmFkZXMsIHdlIGNhbiBzZWUgdGhhdCB0aGUgbG93ZXIgdGhlIGJvcnJvd2VyJ3MgZ3JhZGUsIHRoZSBoaWdoZXIgYW1vdW50IG9mIGJvcnJvd2luZy4gCmBgYHtyIGxvYW4gYW1vdW50IGRpc3RyaWJ1dGlvbiBieSBib3Jyb3dlcnMgZ3JhZGV9CmdncGxvdChkYXRhLCBhZXMoeD1sb2FuX2FtbnQsZmlsbD1ncmFkZSkpKwogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuMykgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKyAKICB4bGFiKCJMb2FuIEFtb3VudCIpICsgCiAgZ2d0aXRsZSgiTG9hbiBBbW91bnQgYnkgQm9ycm93ZXIncyBHcmFkZSIpICsgCiAgZmFjZXRfZ3JpZChncmFkZSB+IC4sIHNjYWxlcyA9ICJmcmVlIikKCmBgYAoKCiMjIzMuNSBCb3Jyb3dlcidzIEFubnVhbCBJbmNvbWUgCkhlcmUgSSBjcmVhdGUgYmlucyB0byBleGFtaW5lIHRoZSBhbm51YWwgaW5jb21lIGRpc3RyaWJ1dGlvbi4KYGBge3IgQW5udWFsIEluY29tZX0Kc3VtbWFyeShkYXRhJGFubnVhbF9pbmMpCgojIHNldCB1cCBib3VuZGFyaWVzIGZvciBpbnRlcnZhbHMvYmlucwpicmVha3MgPC0gYygwLDIwMDAwLDQwMDAwLDYwMDAwLDgwMDAwLDkwMDAwLDEwMDAwMCwxNTAwMDAsMzAwMDAwLCA5NTAwMDAwKQojIHNwZWNpZnkgaW50ZXJ2YWwvYmluIGxhYmVscwpsYWJlbHMgPC0gYygiPDIwMDAwIiwgIjIwMDAwLTQwMDAwKSIsICI0MDAwMC02MDAwMCkiLCAiNjAwMDAtODAwMDApIiwgIjgwMDAwLTkwMDAwKSIsICI5MDAwMC0xMDAwMDApIiwgIjEwMDAwMC0xNTAwMDApIiwgIjE1MDAwMC0zMDAwMDApIiwgIjw9OTUwMDAwMCIpCiMgYnVja2V0aW5nIGRhdGEgcG9pbnRzIGludG8gYmlucwpiaW5zIDwtIGN1dChkYXRhJGFubnVhbF9pbmMsIGJyZWFrcywgaW5jbHVkZS5sb3dlc3QgPSBULCByaWdodD1GQUxTRSwgbGFiZWxzPWxhYmVscykKIyBpbnNwZWN0IGJpbnMKYmluIDwtIHN1bW1hcnkoYmlucykKZnJlcV9pbmMgPC0gYygxMDIwNCwgMTI5NDA5LCAyNDE1OTMsIDIwMzcyOSwgNzI1OGksIDU0OTY1LCAxMjM0MjYsIDQ1ODY1LCA1NjAzKQphbm51YWxfaW5jb21lIDwtIGRhdGEuZnJhbWUobGFiZWxzLCBmcmVxX2luYykKcGxvdChiaW5zLCBtYWluPSJBbm51YWwgSW5jb21lIiwgeWxhYj0iQW5udWFsIEluY29tZSIpCgpgYGAKCiMjIzMuNiBCb3Jyb3dlcidzIFB1cnBvc2UKCldlIG9ic2VydmUgdGhhdCAqZGVidCBjb25zb2xpZGF0aW9uKiBpcyB0aGUgbW9zdCBmcmVxdWVudGx5IHVzZWQgcmVhc29uIHRvIGJvcnJvdy4gCmBgYHtyIHB1cnBvc2UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmRhdGEgJT4lCiAgZ3JvdXBfYnkocHVycG9zZSkgJT4lCiAgc3VtbWFyaXplKGZyZXEgPSBuKCkpICU+JQogIHRvcF9uKDUwKSAlPiUKICBnZ3Bsb3QoYWVzKHJlb3JkZXIocHVycG9zZSwgZnJlcSksIHkgPSBmcmVxLCBmaWxsID0gZnJlcSkpICsgICAKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgeGxhYigiUHVycG9zZSIpICsKICB5bGFiKCJGcmVxdWVuY3kiKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9J25vbmUnLCBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKSArIAogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKG5hbWUgPSAnJyxjb2xvdXJzID0gcmV2KGJyZXdlci5wYWwoMTAsJ1NwZWN0cmFsJykpKSArCiAgZ2d0aXRsZSgicHVycG9zZSIpCmBgYAoKCgojNC4gRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyBmb3IgTG9hbnMKCkluIHRoaXMgc2VjdGlvbiwgSSB3aWxsIGRldGFpbCBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9uIGZvciBsb2Fucy4gSSBoYXZlIGRpdmlkZWQgaXQgaW50byBmaXZlIHN1YnNlY3Rpb25zIHRocm91Z2ggYSB2YXJpZXR5IG9mIGRpZmZlcmVudCB2aXN1YWxpemF0aW9uLgoKIyMjNC4xIExvYW4gQW1vdW50IFN1bW1hcnkKCkkgY3JlYXRlIGEgbGluZSBjaGFydCB0byB2aWV3IHRoZSBsb2FuIGFtb3VudC4gVGhlIG1lZGlhbiBsb2FuIGFtb3VudCBpcyAxMzAwMCBhbmQgbWVhbiBpcyAxNDc1NS4KYGBge3IgTG9hbiBBbW91bnR9CnN1bW1hcnkoZGF0YSRsb2FuX2FtbnQpCgpnZ3Bsb3QoZGF0YSwgYWVzKHg9bG9hbl9hbW50KSkgKyAKICBnZW9tX2RlbnNpdHkoKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsKICB4bGFiKCJMb2FuIEFtb3VudCIpICsgCiAgZ2d0aXRsZSgiTG9hbiBBbW91bnQiKSArIAogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bWVhbihsb2FuX2FtbnQpKSwgY29sb3I9ImJsYWNrIiwgbGluZXR5cGU9ImRhc2hlZCIsIHNpemU9MSkgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bWVkaWFuKGxvYW5fYW1udCkpLCBjb2xvcj0icmVkIiwgbGluZXR5cGU9ImRhc2hlZCIsIHNpemU9MSkgKyAKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PXF1YW50aWxlKGxvYW5fYW1udCwgMC4yNSkpLCBjb2xvcj0iYmx1ZSIsIGxpbmV0eXBlPSJkYXNoZWQiLCBzaXplPTEpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PXF1YW50aWxlKGxvYW5fYW1udCwgMC43NSkpLCBjb2xvcj0ieWVsbG93IiwgbGluZXR5cGU9ImRhc2hlZCIsIHNpemU9MSkKCmBgYAojIyM0LjIgSW50ZXJlc3QgUmF0ZSBTdW1tYXJ5CgpIZXJlIEkgZXhhbWluZSB0aGUgZGlzdHJpYnV0aW9uIG9mIGludGVyZXN0IHJhdGUuIFRoZSBtZWRpYW4gaW50ZXJlc3QgcmF0ZSBpcyAxMi45OSBhbmQgbWVhbiBpcyAxMy4yNS4gCmBgYHtyIEludGVyZXN0IFJhdGUsIHdhcm5pbmc9RkFMU0V9CnN1bW1hcnkoZGF0YSRpbnRfcmF0ZSkKZ2dwbG90KGRhdGEsIGFlcyh4PWludF9yYXRlKSkgKyAKICBnZW9tX2RlbnNpdHkoKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsKICB4bGFiKCJJbnRlcmVzdCBSYXRlIikgKyAKICBnZ3RpdGxlKCJJbnRlcmVzdCBSYXRlIikgKyAKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PW1lYW4oaW50X3JhdGUpKSwgY29sb3I9ImJsYWNrIiwgbGluZXR5cGU9ImRhc2hlZCIsIHNpemU9MSkgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bWVkaWFuKGludF9yYXRlKSksIGNvbG9yPSJyZWQiLCBsaW5ldHlwZT0iZGFzaGVkIiwgc2l6ZT0xKSArCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1xdWFudGlsZShpbnRfcmF0ZSwgMC4yNSkpLCBjb2xvcj0iYmx1ZSIsIGxpbmV0eXBlPSJkYXNoZWQiLCBzaXplPTEpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PXF1YW50aWxlKGludF9yYXRlLCAwLjc1KSksIGNvbG9yPSJ5ZWxsb3ciLCBsaW5ldHlwZT0iZGFzaGVkIiwgc2l6ZT0xKQpgYGAKCiMjIzQuMyBMb2FuIFRlcm0gTGVuZ3RoCgpUaGVyZSBhcmUgdHdvIHRlcm0gbGVuZ3RoLCAzLXllYXIgYW5kIDUteWVhci4gTW9zdCBib3Jyb3dlcnMgaGF2ZSBsb2FuIGluIDMgeWVhcnMuCmBgYHtyIExvYW4gVGVybSBMZW5ndGh9CmRhdGEgJT4lCiAgZ3JvdXBfYnkodGVybSkgJT4lCiAgc3VtbWFyaXplKGZyZXEgPSBuKCkpICU+JQogIGdncGxvdChhZXMocmVvcmRlcih0ZXJtLCBmcmVxKSwgeSA9IGZyZXEsIGZpbGwgPSBmcmVxKSkgKyAgIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIsIHdpZHRoID0gMC40KSArCiAgeGxhYigiVGVybSIpICsKICB5bGFiKCJGcmVxdWVuY3kiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSdub25lJywgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KSkgKyAKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gZnJlcSksIHZqdXN0ID0gLTAuMSwgc2l6ZSA9IDQuNSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKG5hbWUgPSAnJyxjb2xvdXJzID0gcmV2KGJyZXdlci5wYWwoMTAsJ1NwZWN0cmFsJykpKSArCiAgZ2d0aXRsZSgiVGVybSBMZW5ndGgiKQpgYGAKCiMjIzQuNCBMb2FuIERpc3RyaWJ1dGlvbiBieSBHZW9ncmFwaGljIFJlZ2lvbgoKSGVyZSBJIHBsb3QgdGhlIGxvYW4gdm9sdW1lIGJ5IGRpZmZlcmVudCBVUyBzdGF0ZXMuIApgYGB7ciBHZW9ncmFwaGljIFJlZ2lvbn0KZGF0YSRyZWdpb24gPC0gZGF0YSRhZGRyX3N0YXRlCmRhdGEkcmVnaW9uIDwtIGFzLmZhY3RvcihkYXRhJHJlZ2lvbikKbGV2ZWxzKGRhdGEkcmVnaW9uKTwtIGMoImFsYXNrYSIsICJhbGFiYW1hIiwiYXJrYW5zYXMiLCAiYXJpem9uYSIsICJjYWxpZm9ybmlhIiwiY29sb3JhZG8iLCJjb25uZWN0aWN1dCIsImRpc3RyaWN0IG9mIGNvbHVtYmlhIiwiZGVsYXdhcmUiLCJmbG9yaWRhIiwiZ2VvcmdpYSIsImhhd2FpaSIsImlvd2EiLCJpZGFobyIsImlsbGlub2lzIiwiaW5kaWFuYSIsImthbnNhcyIsImtlbnR1Y2t5IiwibG91aXNpYW5hIiwibWFzc2FjaHVzZXR0cyIsIm1hcnlsYW5kIiwibWFpbmUiLCJtaWNoaWdhbiIsIm1pbm5lc290YSIsIm1pc3NvdXJpIiwibWlzc2lzc2lwcGkiLCJtb250YW5hIiwibm9ydGggY2Fyb2xpbmEiLCJub3J0aCBkYWtvdGEiLCJuZWJyYXNrYSIsIm5ldyBoYW1wc2hpcmUiLCJuZXcgamVyc2V5IiwibmV3IG1leGljbyIsIm5ldmFkYSIsIm5ldyB5b3JrIiwib2hpbyIsIm9rbGFob21hIiwib3JlZ29uIiwicGVubnN5bHZhbmlhIiwicmhvZGUgaXNsYW5kIiwic291dGggY2Fyb2xpbmEiLCJzb3V0aCBkYWtvdGEiLCJ0ZW5uZXNzZWUiLCJ0ZXhhcyIsInV0YWgiLCJ2aXJnaW5pYSIsInZlcm1vbnQiLCJ3YXNoaW5ndG9uIiwid2lzY29uc2luIiwid2VzdCB2aXJnaW5pYSIsInd5b21pbmciKQoKdXNfc3RhdGVzIDwtIG1hcF9kYXRhKCJzdGF0ZSIpCmxvYW52b2x1bWUgPC1kYXRhICU+JSBncm91cF9ieShyZWdpb24pICU+JQogICAgICAgICAgICAgICAgc3VtbWFyaXNlKHZhbHVlID0gc3VtKGxvYW5fYW1udCwgbmEucm09VFJVRSkpCmxvYW52b2x1bWUkcmVnaW9uIDwtIGFzLmNoYXJhY3Rlcihsb2Fudm9sdW1lJHJlZ2lvbikKbG9hbmRhdGEgPC0gbWVyZ2UodXNfc3RhdGVzLCBsb2Fudm9sdW1lLCBieT0icmVnaW9uIikKZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oZGF0YT1sb2FuZGF0YSwgYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwID0gZ3JvdXAsIGZpbGw9bG9hbmRhdGEkdmFsdWUpKSArCiAgdGhlbWVfdm9pZCgpICsKICBsYWJzKGZpbGwgPSAiR3JhZGllbnQiICx0aXRsZSA9ICJMb2FuIFZvbHVtZSBieSBTdGF0ZXMiLCB4PSIiLCB5PSIiKQpgYGAKCiMjIzQuNSBMb2FuIFN0YXR1cwoKV2Ugb2JzZXJ2ZSB0aGF0IG1vc3QgbG9hbiBzdGF0dXMgYXJlIGN1cnJlbnQsIHdoaWNoIG1lYW5zIHRoaXMgbG9hbiBzdGlsbCBoYXMgb25lIG9yIG1vcmUgc2NoZWR1bGVkIGZ1dHVyZSBwYXltZW50cy4gSXQgYWxzbyByZWZsZWN0IHRoYXQgc29tZSBsYXRlIGFuZCBkZWZhdWx0IGxvYW5zLgpgYGB7ciBMb2FuIFN0YXR1c30KZGF0YSAlPiUKICBncm91cF9ieShsb2FuX3N0YXR1cykgJT4lCiAgc3VtbWFyaXplKGZyZXEgPSBuKCkpICU+JQogIGdncGxvdChhZXMocmVvcmRlcihsb2FuX3N0YXR1cywgZnJlcSksIHkgPSBmcmVxLCBmaWxsID0gZnJlcSkpICsgICAKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiLCB3aWR0aD0wLjQpICsKICB4bGFiKCJMb2FuIFN0YXR1cyIpICsKICB5bGFiKCJGcmVxdWVuY3kiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKyAKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9J25vbmUnLCBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gNykpICsgCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4obmFtZSA9ICcnLGNvbG91cnMgPSByZXYoYnJld2VyLnBhbCgxMSwnU3BlY3RyYWwnKSkpICsKICBnZ3RpdGxlKCJMb2FuIFN0YXR1cyIpCgpzdW1tYXJ5KGRhdGEkbG9hbl9zdGF0dXMpCmBgYAoKCiM1LiBXb3JkIENsb3VkCgpUaGUgV29yZENsb3VkIGdlbmVyYXRvciBwcmVzZW50cyBpbnRlcmVzdGluZyBpbnNpZ2h0cy4KCiMjIzUuMSBXb3JkIENsb3VkIEJ5IFRpdGxlCmBgYHtyIHdvcmQgY2xvdWQgYnkgdGl0bGUsIHdhcm5pbmc9RkFMU0V9CmxvYW5Db3JwdXMgPC0gQ29ycHVzKFZlY3RvclNvdXJjZShkYXRhJHRpdGxlKSkKbG9hbkNvcnB1cyA8LSB0bV9tYXAobG9hbkNvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkKbG9hbkNvcnB1cyA8LSB0bV9tYXAobG9hbkNvcnB1cywgcmVtb3ZlUHVuY3R1YXRpb24pCndvcmRjbG91ZChsb2FuQ29ycHVzLCBtYXgud29yZHMgPSAxMDAsIHJhbmRvbS5vcmRlciA9IEZBTFNFLHJvdC5wZXI9MCwgCiAgICAgICAgICBjb2xvcnM9YnJld2VyLnBhbCg4LCAiRGFyazIiKSkKYGBgCgojIyM1LjIgV29yZCBDbG91ZCBCeSBFbXBsb3ltZW50IFRpdGxlCmBgYHtyIHdvcmQgY2xvdWQgYnkgZW1wIHRpdGxlLCB3YXJuaW5nPUZBTFNFfQpsb2FudGl0bGVDb3JwdXMgPC0gQ29ycHVzKFZlY3RvclNvdXJjZShkYXRhJGVtcF90aXRsZSkpCmxvYW50aXRsZUNvcnB1cyA8LSB0bV9tYXAobG9hbnRpdGxlQ29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKQpsb2FudGl0bGVDb3JwdXMgPC0gdG1fbWFwKGxvYW50aXRsZUNvcnB1cywgcmVtb3ZlUHVuY3R1YXRpb24pCndvcmRjbG91ZChsb2FudGl0bGVDb3JwdXMsIG1heC53b3JkcyA9IDEwMCwgcmFuZG9tLm9yZGVyID0gRkFMU0Uscm90LnBlcj0wLCAKICAgICAgICAgIGNvbG9ycz1icmV3ZXIucGFsKDgsICJEYXJrMiIpKQpgYGAKCgo=