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
- 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=