Note that the directories used to store data are likely different on your computer, and such references will need to be changed before using any such code.
library(knitr)
library(kableExtra)
html_df <- function(text, cols=NULL, col1=FALSE, full=F) {
if(!length(cols)) {
cols=colnames(text)
}
if(!col1) {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full)
} else {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full) %>%
column_spec(1,bold=T)
}
}
library(tidyverse)
library(plotly)
library(lubridate)
df <- read.csv("../../Data/Session_5-1.csv", stringsAsFactors=FALSE)
df_ratings <- read.csv("../../Data/Session_5-2.csv", stringsAsFactors=FALSE)
df_mve <- read.csv("../../Data/Session_5-3.csv", stringsAsFactors=FALSE)
df_rf <- read.csv("../../Data/Session_5-4.csv", stringsAsFactors=FALSE)
df_stock <- read.csv("../../Data/Session_5-5.csv", stringsAsFactors=FALSE)
# initial cleaning
# 100338 is an outlier in the bonds distribution
df <- df %>% filter(at >= 1, revt >= 1, gvkey != 100338)
## Merge in stock value
df$date <- as.Date(df$datadate)
df_mve <- df_mve %>%
mutate(date = as.Date(datadate),
mve = csho * prcc_f) %>%
rename(gvkey=GVKEY)
df <- left_join(df, df_mve[,c("gvkey","date","mve")])
Joining, by = c("gvkey", "date")
df <- df %>%
group_by(gvkey) %>%
mutate(bankrupt = ifelse(row_number() == n() & dlrsn == 2 &
!is.na(dlrsn), 1, 0)) %>%
ungroup()
# Calculate the measures needed
df <- df %>%
mutate(wcap_at = wcap / at, # x1
re_at = re / at, # x2
ebit_at = ebit / at, # x3
mve_lt = mve / lt, # x4
revt_at = revt / at) # x5
# cleanup
df <- df %>%
mutate_if(is.numeric, list(~replace(., !is.finite(.), NA)))
# Calculate the score
df <- df %>%
mutate(Z = 1.2 * wcap_at + 1.4 * re_at + 3.3 * ebit_at + 0.6 * mve_lt +
0.999 * revt_at)
# Calculate date info for merging
df$date <- as.Date(df$datadate)
df$year <- year(df$date)
df$month <- month(df$date)
# df_ratings has ratings data in it
# Ratings, in order from worst to best
ratings <- c("D", "C", "CC", "CCC-", "CCC","CCC+", "B-", "B", "B+", "BB-",
"BB", "BB+", "BBB-", "BBB", "BBB+", "A-", "A", "A+", "AA-", "AA",
"AA+", "AAA-", "AAA", "AAA+")
# Convert string ratings (splticrm) to numeric ratings
df_ratings$rating <- factor(df_ratings$splticrm, levels=ratings, ordered=T)
df_ratings$date <- as.Date(df_ratings$datadate)
df_ratings$year <- year(df_ratings$date)
df_ratings$month <- month(df_ratings$date)
# Merge together data
df <- left_join(df, df_ratings[,c("gvkey", "year", "month", "rating")])
Joining, by = c("gvkey", "year", "month")
plot <- df %>%
filter(!is.na(Z), !is.na(rating)) %>%
group_by(rating) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(rating, mean_Z) %>%
ggplot(aes(y=mean_Z, x=rating)) +
geom_col() +
ylab('Mean Altman Z') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(Z),
!is.na(bankrupt)) %>%
group_by(bankrupt) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt, mean_Z) %>%
html_df()
bankrupt |
mean_Z |
0 |
3.939223 |
1 |
0.927843 |
plot <- df %>%
filter(!is.na(Z), !is.na(rating), year >= 2000) %>%
group_by(rating) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(rating, mean_Z) %>%
ggplot(aes(y=mean_Z, x=rating)) +
geom_col() +
ylab('Mean Altman Z') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(Z),
!is.na(bankrupt),
year >= 2000) %>%
group_by(bankrupt) %>%
mutate(mean_Z=mean(Z,na.rm=T)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt, mean_Z) %>%
html_df()
bankrupt |
mean_Z |
0 |
3.822281 |
1 |
1.417683 |
fit_Z <- glm(bankrupt ~ Z, data=df, family=binomial)
summary(fit_Z)
Call:
glm(formula = bankrupt ~ Z, family = binomial, data = df)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.8297 -0.0676 -0.0654 -0.0624 3.7794
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -5.94354 0.11829 -50.245 < 2e-16 ***
Z -0.06383 0.01239 -5.151 2.59e-07 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 1085.2 on 35296 degrees of freedom
Residual deviance: 1066.5 on 35295 degrees of freedom
(15577 observations deleted due to missingness)
AIC: 1070.5
Number of Fisher Scoring iterations: 9
library(ROCR)
dfZ <- df %>% filter(!is.na(Z), !is.na(bankrupt))
pred_Z <- predict(fit_Z, dfZ, type="response")
ROCpred_Z <- prediction(as.numeric(pred_Z), as.numeric(dfZ$bankrupt))
ROCperf_Z <- performance(ROCpred_Z, 'tpr','fpr')
df_ROC_Z <- data.frame(
FP=c(ROCperf_Z@x.values[[1]]),
TP=c(ROCperf_Z@y.values[[1]]))
ggplot(data=df_ROC_Z,
aes(x=FP, y=TP)) + geom_line() +
geom_abline(slope=1)

plot(ROCperf_Z)

ggplot(data=df_ROC_Z, aes(x=FP, y=TP)) +
geom_line() +
geom_abline(slope=1) +
ylab("True positive rate (Sensitivity)") +
xlab("False positive rate (1 - Specificity)") +
ggtitle("ROC Curve")

auc_Z <- performance(ROCpred_Z, measure = "auc")
auc_Z@y.values[[1]]
[1] 0.8280943
score = 1
m = 0
std = 1
funcShaded <- function(x, lower_bound) {
y = dnorm(x, mean = m, sd = std)
y[x < lower_bound] <- NA
return(y)
}
ggplot(data.frame(x = c(-3, 3)), aes(x = x)) +
stat_function(fun = dnorm, args = list(mean = m, sd = std)) +
stat_function(fun = funcShaded, args = list(lower_bound = score),
geom = "area", fill = 'black', alpha = .2) +
scale_x_continuous(name = "Score", breaks = seq(-3, 3, std)) +
geom_text(data = data.frame(x=c(1.5), y=c(0.05)), aes(x=x, y=y, label="Prob(default)", size=30)) +
geom_line(data = data.frame(x=c(1,1), y=c(0,0.4)), aes(x=x,y=y)) +
geom_text(data = data.frame(x=c(1.3), y=c(0.4)), aes(x=x, y=y, label="DD", size=30)) +
theme(legend.position="none")

# df_stock is an already prepped csv from CRSP data
df_stock$date <- as.Date(df_stock$date)
df <- left_join(df, df_stock[,c("gvkey", "date", "ret", "ret.sd")])
Joining, by = c("gvkey", "date")
df_rf$date <- as.Date(df_rf$dateff)
df_rf$year <- year(df_rf$date)
df_rf$month <- month(df_rf$date)
df <- left_join(df, df_rf[,c("year", "month", "rf")])
Joining, by = c("year", "month")
df <- df %>%
mutate(DD = (log(mve / lt) + (rf - (ret.sd*sqrt(253))^2 / 2)) /
(ret.sd*sqrt(253)))
# Clean the measure
df <- df %>%
mutate_if(is.numeric, list(~replace(., !is.finite(.), NA)))
plot <- df %>%
filter(!is.na(DD),
!is.na(rating)) %>%
group_by(rating) %>%
mutate(mean_DD=mean(DD,na.rm=T),
prob_default = pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(rating, prob_default) %>%
ggplot(aes(y=prob_default, x=rating)) +
geom_col() +
ylab('Probability of default') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(DD),
!is.na(bankrupt)) %>%
group_by(bankrupt) %>%
mutate(mean_DD=mean(DD, na.rm=T),
prob_default =
pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt, mean_DD,
prob_default) %>%
html_df()
bankrupt |
mean_DD |
prob_default |
0 |
0.6096854 |
0.2710351 |
1 |
-2.4445081 |
0.9927475 |
plot <- df %>%
filter(!is.na(DD),
!is.na(rating),
year >= 2000) %>%
group_by(rating) %>%
mutate(mean_DD=mean(DD,na.rm=T),
prob_default = pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(rating, prob_default) %>%
ggplot(aes(y=prob_default, x=rating)) +
geom_col() +
ylab('Probability of default') + xlab('Credit rating') +
theme(axis.text.x = element_text(angle = 90))
ggplotly(plot)
df %>%
filter(!is.na(DD),
!is.na(bankrupt),
year >= 2000) %>%
group_by(bankrupt) %>%
mutate(mean_DD=mean(DD, na.rm=T),
prob_default =
pnorm(-1 * mean_DD)) %>%
slice(1) %>%
ungroup() %>%
select(bankrupt, mean_DD,
prob_default) %>%
html_df()
bankrupt |
mean_DD |
prob_default |
0 |
0.8379932 |
0.2010172 |
1 |
-4.3001844 |
0.9999915 |
fit_DD <- glm(bankrupt ~ DD, data=df, family=binomial)
summary(fit_DD)
Call:
glm(formula = bankrupt ~ DD, family = binomial, data = df)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.9929 -0.0750 -0.0634 -0.0506 3.6503
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -6.16394 0.15322 -40.230 < 2e-16 ***
DD -0.24459 0.03781 -6.469 9.89e-11 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 718.67 on 21563 degrees of freedom
Residual deviance: 677.27 on 21562 degrees of freedom
(33618 observations deleted due to missingness)
AIC: 681.27
Number of Fisher Scoring iterations: 9
dfDD <- df %>% filter(!is.na(DD), !is.na(bankrupt))
pred_DD <- predict(fit_DD, dfDD, type="response")
ROCpred_DD <- prediction(as.numeric(pred_DD), as.numeric(dfDD$bankrupt))
ROCperf_DD <- performance(ROCpred_DD, 'tpr','fpr')
df_ROC_DD <- data.frame(FalsePositive=c(ROCperf_DD@x.values[[1]]),
TruePositive=c(ROCperf_DD@y.values[[1]]))
ggplot() +
geom_line(data=df_ROC_DD, aes(x=FalsePositive, y=TruePositive, color="DD")) +
geom_line(data=df_ROC_Z, aes(x=FP, y=TP, color="Z")) +
geom_abline(slope=1)

#AUC
auc_DD <- performance(ROCpred_DD, measure = "auc")
AUCs <- c(auc_Z@y.values[[1]], auc_DD@y.values[[1]])
names(AUCs) <- c("Z", "DD")
AUCs
Z DD
0.8280943 0.8098304
# calculate downgrade
df <- df %>%
group_by(gvkey) %>%
arrange(date) %>%
mutate(downgrade = ifelse(rating < lag(rating),1,0),
diff_Z = Z - lag(Z),
diff_DD = DD - lag(DD)) %>%
ungroup()
# training sample
train <- df %>% filter(year < 2014, !is.na(diff_Z), !is.na(diff_DD), !is.na(downgrade),
year > 1985)
test <- df %>% filter(year >= 2014, !is.na(diff_Z), !is.na(diff_DD), !is.na(downgrade))
# glms
fit_Z2 <- glm(downgrade ~ diff_Z, data=train, family=binomial)
fit_DD2 <- glm(downgrade ~ diff_DD, data=train, family=binomial)
summary(fit_Z2)
Call:
glm(formula = downgrade ~ diff_Z, family = binomial, data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-3.4115 -0.4428 -0.4428 -0.3928 2.7437
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.27310 0.06139 -37.029 <2e-16 ***
diff_Z -0.77150 0.09245 -8.345 <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: 2145.3 on 3277 degrees of freedom
Residual deviance: 2065.8 on 3276 degrees of freedom
AIC: 2069.8
Number of Fisher Scoring iterations: 5
summary(fit_DD2)
Call:
glm(formula = downgrade ~ diff_DD, family = binomial, data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.5726 -0.4565 -0.4558 -0.4095 2.6804
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.21199 0.05926 -37.325 < 2e-16 ***
diff_DD -0.21378 0.03723 -5.742 9.37e-09 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2145.3 on 3277 degrees of freedom
Residual deviance: 2113.2 on 3276 degrees of freedom
AIC: 2117.2
Number of Fisher Scoring iterations: 5
pred_Z2 <- predict(fit_Z2, train, type="response")
pred_Z2 <- ifelse(!is.finite(pred_Z2),NA,pred_Z2)
ROCpred_Z2 <- prediction(as.numeric(pred_Z2[!is.na(train$downgrade) & !is.na(pred_Z2)]), as.numeric(train[!is.na(train$downgrade) & !is.na(pred_Z2),]$downgrade))
ROCperf_Z2 <- performance(ROCpred_Z2, 'tpr','fpr')
df_ROC_Z2 <- data.frame(FalsePositive=c(ROCperf_Z2@x.values[[1]]),
TruePositive=c(ROCperf_Z2@y.values[[1]]))
auc_Z2 <- performance(ROCpred_Z2, measure = "auc")
pred_DD2 <- predict(fit_DD2, train, type="response")
#[!is.na(pred)]
ROCpred_DD2 <- prediction(as.numeric(pred_DD2[!is.na(train$downgrade) & !is.na(pred_DD2)]), as.numeric(train[!is.na(train$downgrade) & !is.na(pred_DD2),]$downgrade))
ROCperf_DD2 <- performance(ROCpred_DD2, 'tpr','fpr')
df_ROC_DD2 <- data.frame(FalsePositive=c(ROCperf_DD2@x.values[[1]]),
TruePositive=c(ROCperf_DD2@y.values[[1]]))
ggplot() + geom_line(data=df_ROC_DD2, aes(x=FalsePositive, y=TruePositive, color='DD')) + geom_line(data=df_ROC_Z2, aes(x=FalsePositive, y=TruePositive, color='Z')) + geom_abline(slope=1)

auc_DD2 <- performance(ROCpred_DD2, measure = "auc")
AUCs <- c(auc_Z2@y.values[[1]], auc_DD2@y.values[[1]])
names(AUCs) <- c("Z", "DD")
AUCs
Z DD
0.6465042 0.5847885
pred_Z2 <- predict(fit_Z2, test, type="response")
ROCpred_Z2 <- prediction(as.numeric(pred_Z2[!is.na(test$downgrade) & !is.na(pred_Z2)]), as.numeric(test[!is.na(test$downgrade) & !is.na(pred_Z2),]$downgrade))
ROCperf_Z2 <- performance(ROCpred_Z2, 'tpr','fpr')
df_ROC_Z2 <- data.frame(FalsePositive=c(ROCperf_Z2@x.values[[1]]),
TruePositive=c(ROCperf_Z2@y.values[[1]]))
auc_Z2 <- performance(ROCpred_Z2, measure = "auc")
pred_DD2 <- predict(fit_DD2, test, type="response")
ROCpred_DD2 <- prediction(as.numeric(pred_DD2[!is.na(test$downgrade) & !is.na(pred_DD2)]), as.numeric(test[!is.na(test$downgrade) & !is.na(pred_DD2),]$downgrade))
ROCperf_DD2 <- performance(ROCpred_DD2, 'tpr','fpr')
df_ROC_DD2 <- data.frame(FalsePositive=c(ROCperf_DD2@x.values[[1]]),
TruePositive=c(ROCperf_DD2@y.values[[1]]))
ggplot() + geom_line(data=df_ROC_DD2, aes(x=FalsePositive, y=TruePositive, color='DD')) + geom_line(data=df_ROC_Z2, aes(x=FalsePositive, y=TruePositive, color='Z')) + geom_abline(slope=1)

auc_DD2 <- performance(ROCpred_DD2, measure = "auc")
AUCs <- c(auc_Z2@y.values[[1]], auc_DD2@y.values[[1]])
names(AUCs) <- c("Z", "DD")
AUCs
Z DD
0.8134671 0.7420213
fit_comb <- glm(downgrade ~ diff_Z + diff_DD, data=train, family=binomial)
summary(fit_comb)
Call:
glm(formula = downgrade ~ diff_Z + diff_DD, family = binomial,
data = train)
Deviance Residuals:
Min 1Q Median 3Q Max
-3.3263 -0.4431 -0.4430 -0.3892 2.7504
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.27217 0.06144 -36.980 < 2e-16 ***
diff_Z -0.71374 0.10709 -6.665 2.65e-11 ***
diff_DD -0.04884 0.04638 -1.053 0.292
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2145.3 on 3277 degrees of freedom
Residual deviance: 2064.7 on 3275 degrees of freedom
AIC: 2070.7
Number of Fisher Scoring iterations: 5
fit_comb %>%
margins::margins() %>%
summary()
pred_comb <- predict(fit_comb, test, type="response")
pred_comb <- ifelse(!is.finite(pred_comb),NA,pred_comb)
ROCpred_comb <- prediction(as.numeric(pred_comb[!is.na(test$downgrade) & !is.na(pred_comb)]), as.numeric(test[!is.na(test$downgrade) & !is.na(pred_comb),]$downgrade))
ROCperf_comb <- performance(ROCpred_comb, 'tpr','fpr')
df_ROC_comb <- data.frame(FalsePositive=c(ROCperf_comb@x.values[[1]]),
TruePositive=c(ROCperf_comb@y.values[[1]]))
auc_comb <- performance(ROCpred_comb, measure = "auc")
ggplot() +
geom_line(data=df_ROC_comb, aes(x=FalsePositive, y=TruePositive, color='Combined')) +
geom_line(data=df_ROC_Z2, aes(x=FalsePositive, y=TruePositive, color='Z')) +
geom_abline(slope=1) +
geom_line(data=df_ROC_DD2, aes(x=FalsePositive, y=TruePositive, color='DD'))

AUCs <- c(auc_comb@y.values[[1]], auc_Z2@y.values[[1]], auc_DD2@y.values[[1]])
names(AUCs) <- c("Combined", "Z", "DD")
AUCs
Combined Z DD
0.8151596 0.8134671 0.7420213
LS0tDQp0aXRsZTogIkNvZGUgZm9yIFNlc3Npb24gNSINCmF1dGhvcjogIkRyLiBSaWNoYXJkIE0uIENyb3dsZXkiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpOb3RlIHRoYXQgdGhlIGRpcmVjdG9yaWVzIHVzZWQgdG8gc3RvcmUgZGF0YSBhcmUgbGlrZWx5IGRpZmZlcmVudCBvbiB5b3VyIGNvbXB1dGVyLCBhbmQgc3VjaCByZWZlcmVuY2VzIHdpbGwgbmVlZCB0byBiZSBjaGFuZ2VkIGJlZm9yZSB1c2luZyBhbnkgc3VjaCBjb2RlLg0KDQpgYGB7ciBoZWxwZXJzLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmh0bWxfZGYgPC0gZnVuY3Rpb24odGV4dCwgY29scz1OVUxMLCBjb2wxPUZBTFNFLCBmdWxsPUYpIHsNCiAgaWYoIWxlbmd0aChjb2xzKSkgew0KICAgIGNvbHM9Y29sbmFtZXModGV4dCkNCiAgfQ0KICBpZighY29sMSkgew0KICAgIGthYmxlKHRleHQsImh0bWwiLCBjb2wubmFtZXMgPSBjb2xzLCBhbGlnbiA9IGMoImwiLHJlcCgnYycsbGVuZ3RoKGNvbHMpLTEpKSkgJT4lDQogICAgICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsImhvdmVyIiksIGZ1bGxfd2lkdGg9ZnVsbCkNCiAgfSBlbHNlIHsNCiAgICBrYWJsZSh0ZXh0LCJodG1sIiwgY29sLm5hbWVzID0gY29scywgYWxpZ24gPSBjKCJsIixyZXAoJ2MnLGxlbmd0aChjb2xzKS0xKSkpICU+JQ0KICAgICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCJob3ZlciIpLCBmdWxsX3dpZHRoPWZ1bGwpICU+JQ0KICAgICAgY29sdW1uX3NwZWMoMSxib2xkPVQpDQogIH0NCn0NCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmRmIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1Nlc3Npb25fNS0xLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQpkZl9yYXRpbmdzIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1Nlc3Npb25fNS0yLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQpkZl9tdmUgPC0gcmVhZC5jc3YoIi4uLy4uL0RhdGEvU2Vzc2lvbl81LTMuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkNCmRmX3JmIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1Nlc3Npb25fNS00LmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQpkZl9zdG9jayA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9TZXNzaW9uXzUtNS5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KYGBgDQoNCmBgYHtyfQ0KIyBpbml0aWFsIGNsZWFuaW5nDQojIDEwMDMzOCBpcyBhbiBvdXRsaWVyIGluIHRoZSBib25kcyBkaXN0cmlidXRpb24NCmRmIDwtIGRmICU+JSBmaWx0ZXIoYXQgPj0gMSwgcmV2dCA+PSAxLCBndmtleSAhPSAxMDAzMzgpDQoNCiMjIE1lcmdlIGluIHN0b2NrIHZhbHVlDQpkZiRkYXRlIDwtIGFzLkRhdGUoZGYkZGF0YWRhdGUpDQpkZl9tdmUgPC0gZGZfbXZlICU+JQ0KICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoZGF0YWRhdGUpLA0KICAgICAgICAgbXZlID0gY3NobyAqIHByY2NfZikgJT4lDQogIHJlbmFtZShndmtleT1HVktFWSkNCg0KZGYgPC0gbGVmdF9qb2luKGRmLCBkZl9tdmVbLGMoImd2a2V5IiwiZGF0ZSIsIm12ZSIpXSkNCg0KZGYgPC0gZGYgJT4lDQogIGdyb3VwX2J5KGd2a2V5KSAlPiUNCiAgbXV0YXRlKGJhbmtydXB0ID0gaWZlbHNlKHJvd19udW1iZXIoKSA9PSBuKCkgJiBkbHJzbiA9PSAyICYNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYShkbHJzbiksIDEsIDApKSAlPiUNCiAgdW5ncm91cCgpDQpgYGANCg0KYGBge3J9DQoNCiMgQ2FsY3VsYXRlIHRoZSBtZWFzdXJlcyBuZWVkZWQNCmRmIDwtIGRmICU+JQ0KICBtdXRhdGUod2NhcF9hdCA9IHdjYXAgLyBhdCwgICMgeDENCiAgICAgICAgIHJlX2F0ID0gcmUgLyBhdCwgICMgeDINCiAgICAgICAgIGViaXRfYXQgPSBlYml0IC8gYXQsICAjIHgzDQogICAgICAgICBtdmVfbHQgPSBtdmUgLyBsdCwgICMgeDQNCiAgICAgICAgIHJldnRfYXQgPSByZXZ0IC8gYXQpICAjIHg1DQojIGNsZWFudXANCmRmIDwtIGRmICU+JQ0KICBtdXRhdGVfaWYoaXMubnVtZXJpYywgbGlzdCh+cmVwbGFjZSguLCAhaXMuZmluaXRlKC4pLCBOQSkpKQ0KDQojIENhbGN1bGF0ZSB0aGUgc2NvcmUNCmRmIDwtIGRmICU+JQ0KICBtdXRhdGUoWiA9IDEuMiAqIHdjYXBfYXQgKyAxLjQgKiByZV9hdCArIDMuMyAqIGViaXRfYXQgKyAwLjYgKiBtdmVfbHQgKyANCiAgICAgICAgICAgMC45OTkgKiByZXZ0X2F0KQ0KDQojIENhbGN1bGF0ZSBkYXRlIGluZm8gZm9yIG1lcmdpbmcNCmRmJGRhdGUgPC0gYXMuRGF0ZShkZiRkYXRhZGF0ZSkNCmRmJHllYXIgPC0geWVhcihkZiRkYXRlKQ0KZGYkbW9udGggPC0gbW9udGgoZGYkZGF0ZSkNCmBgYA0KDQpgYGB7cn0NCiMgZGZfcmF0aW5ncyBoYXMgcmF0aW5ncyBkYXRhIGluIGl0DQoNCiMgUmF0aW5ncywgaW4gb3JkZXIgZnJvbSB3b3JzdCB0byBiZXN0DQpyYXRpbmdzIDwtIGMoIkQiLCAiQyIsICJDQyIsICJDQ0MtIiwgIkNDQyIsIkNDQysiLCAiQi0iLCAiQiIsICJCKyIsICJCQi0iLA0KICAgICAgICAgICAgICJCQiIsICJCQisiLCAiQkJCLSIsICJCQkIiLCAiQkJCKyIsICJBLSIsICJBIiwgIkErIiwgIkFBLSIsICJBQSIsDQogICAgICAgICAgICAgIkFBKyIsICJBQUEtIiwgIkFBQSIsICJBQUErIikNCiMgQ29udmVydCBzdHJpbmcgcmF0aW5ncyAoc3BsdGljcm0pIHRvIG51bWVyaWMgcmF0aW5ncw0KZGZfcmF0aW5ncyRyYXRpbmcgPC0gZmFjdG9yKGRmX3JhdGluZ3Mkc3BsdGljcm0sIGxldmVscz1yYXRpbmdzLCBvcmRlcmVkPVQpDQoNCmRmX3JhdGluZ3MkZGF0ZSA8LSBhcy5EYXRlKGRmX3JhdGluZ3MkZGF0YWRhdGUpDQpkZl9yYXRpbmdzJHllYXIgPC0geWVhcihkZl9yYXRpbmdzJGRhdGUpDQpkZl9yYXRpbmdzJG1vbnRoIDwtIG1vbnRoKGRmX3JhdGluZ3MkZGF0ZSkNCg0KIyBNZXJnZSB0b2dldGhlciBkYXRhDQpkZiA8LSBsZWZ0X2pvaW4oZGYsIGRmX3JhdGluZ3NbLGMoImd2a2V5IiwgInllYXIiLCAibW9udGgiLCAicmF0aW5nIildKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD00fQ0KcGxvdCA8LSBkZiAlPiUNCiAgZmlsdGVyKCFpcy5uYShaKSwgIWlzLm5hKHJhdGluZykpICU+JQ0KICBncm91cF9ieShyYXRpbmcpICU+JQ0KICBtdXRhdGUobWVhbl9aPW1lYW4oWixuYS5ybT1UKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChyYXRpbmcsIG1lYW5fWikgJT4lDQogIGdncGxvdChhZXMoeT1tZWFuX1osIHg9cmF0aW5nKSkgKyANCiAgZ2VvbV9jb2woKSArIA0KICB5bGFiKCdNZWFuIEFsdG1hbiBaJykgKyB4bGFiKCdDcmVkaXQgcmF0aW5nJykgKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQpnZ3Bsb3RseShwbG90KQ0KYGBgDQoNCmBgYHtyfQ0KZGYgJT4lDQogIGZpbHRlcighaXMubmEoWiksDQogICAgICAgICAhaXMubmEoYmFua3J1cHQpKSAlPiUNCiAgZ3JvdXBfYnkoYmFua3J1cHQpICU+JQ0KICBtdXRhdGUobWVhbl9aPW1lYW4oWixuYS5ybT1UKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChiYW5rcnVwdCwgbWVhbl9aKSAlPiUNCiAgaHRtbF9kZigpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTR9DQpwbG90IDwtIGRmICU+JQ0KICBmaWx0ZXIoIWlzLm5hKFopLCAhaXMubmEocmF0aW5nKSwgeWVhciA+PSAyMDAwKSAlPiUNCiAgZ3JvdXBfYnkocmF0aW5nKSAlPiUNCiAgbXV0YXRlKG1lYW5fWj1tZWFuKFosbmEucm09VCkpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QocmF0aW5nLCBtZWFuX1opICU+JQ0KICBnZ3Bsb3QoYWVzKHk9bWVhbl9aLCB4PXJhdGluZykpICsgDQogIGdlb21fY29sKCkgKyANCiAgeWxhYignTWVhbiBBbHRtYW4gWicpICsgeGxhYignQ3JlZGl0IHJhdGluZycpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KZ2dwbG90bHkocGxvdCkNCmBgYA0KDQpgYGB7cn0NCmRmICU+JQ0KICBmaWx0ZXIoIWlzLm5hKFopLA0KICAgICAgICAgIWlzLm5hKGJhbmtydXB0KSwNCiAgICAgICAgIHllYXIgPj0gMjAwMCkgJT4lDQogIGdyb3VwX2J5KGJhbmtydXB0KSAlPiUNCiAgbXV0YXRlKG1lYW5fWj1tZWFuKFosbmEucm09VCkpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QoYmFua3J1cHQsIG1lYW5fWikgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUZ9DQpmaXRfWiA8LSBnbG0oYmFua3J1cHQgfiBaLCBkYXRhPWRmLCBmYW1pbHk9Ymlub21pYWwpDQpzdW1tYXJ5KGZpdF9aKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZ9DQpsaWJyYXJ5KFJPQ1IpDQpkZlogPC0gZGYgJT4lIGZpbHRlcighaXMubmEoWiksICFpcy5uYShiYW5rcnVwdCkpDQpwcmVkX1ogPC0gcHJlZGljdChmaXRfWiwgZGZaLCB0eXBlPSJyZXNwb25zZSIpDQpST0NwcmVkX1ogPC0gcHJlZGljdGlvbihhcy5udW1lcmljKHByZWRfWiksIGFzLm51bWVyaWMoZGZaJGJhbmtydXB0KSkNClJPQ3BlcmZfWiA8LSBwZXJmb3JtYW5jZShST0NwcmVkX1osICd0cHInLCdmcHInKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTV9DQpkZl9ST0NfWiA8LSBkYXRhLmZyYW1lKA0KICBGUD1jKFJPQ3BlcmZfWkB4LnZhbHVlc1tbMV1dKSwNCiAgVFA9YyhST0NwZXJmX1pAeS52YWx1ZXNbWzFdXSkpDQpnZ3Bsb3QoZGF0YT1kZl9ST0NfWiwNCiAgYWVzKHg9RlAsIHk9VFApKSArIGdlb21fbGluZSgpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01fQ0KcGxvdChST0NwZXJmX1opDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YT1kZl9ST0NfWiwgYWVzKHg9RlAsIHk9VFApKSArDQogIGdlb21fbGluZSgpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkgKw0KICB5bGFiKCJUcnVlIHBvc2l0aXZlIHJhdGUgKFNlbnNpdGl2aXR5KSIpICsgDQogIHhsYWIoIkZhbHNlIHBvc2l0aXZlIHJhdGUgKDEgLSBTcGVjaWZpY2l0eSkiKSArDQogIGdndGl0bGUoIlJPQyBDdXJ2ZSIpDQpgYGANCg0KYGBge3J9DQphdWNfWiA8LSBwZXJmb3JtYW5jZShST0NwcmVkX1osIG1lYXN1cmUgPSAiYXVjIikNCmF1Y19aQHkudmFsdWVzW1sxXV0NCmBgYA0KDQpgYGB7cn0NCnNjb3JlID0gMQ0KbSA9IDANCnN0ZCA9IDENCg0KZnVuY1NoYWRlZCA8LSBmdW5jdGlvbih4LCBsb3dlcl9ib3VuZCkgew0KICAgIHkgPSBkbm9ybSh4LCBtZWFuID0gbSwgc2QgPSBzdGQpDQogICAgeVt4IDwgbG93ZXJfYm91bmRdIDwtIE5BDQogICAgcmV0dXJuKHkpDQp9DQoNCmdncGxvdChkYXRhLmZyYW1lKHggPSBjKC0zLCAzKSksIGFlcyh4ID0geCkpICsgDQogIHN0YXRfZnVuY3Rpb24oZnVuID0gZG5vcm0sIGFyZ3MgPSBsaXN0KG1lYW4gPSBtLCBzZCA9IHN0ZCkpICsgDQogIHN0YXRfZnVuY3Rpb24oZnVuID0gZnVuY1NoYWRlZCwgYXJncyA9IGxpc3QobG93ZXJfYm91bmQgPSBzY29yZSksIA0KICAgICAgICAgICAgICAgIGdlb20gPSAiYXJlYSIsIGZpbGwgPSAnYmxhY2snLCBhbHBoYSA9IC4yKSArDQogIHNjYWxlX3hfY29udGludW91cyhuYW1lID0gIlNjb3JlIiwgYnJlYWtzID0gc2VxKC0zLCAzLCBzdGQpKSArIA0KICBnZW9tX3RleHQoZGF0YSA9IGRhdGEuZnJhbWUoeD1jKDEuNSksIHk9YygwLjA1KSksIGFlcyh4PXgsIHk9eSwgbGFiZWw9IlByb2IoZGVmYXVsdCkiLCBzaXplPTMwKSkgKyANCiAgZ2VvbV9saW5lKGRhdGEgPSBkYXRhLmZyYW1lKHg9YygxLDEpLCB5PWMoMCwwLjQpKSwgYWVzKHg9eCx5PXkpKSArIA0KICBnZW9tX3RleHQoZGF0YSA9IGRhdGEuZnJhbWUoeD1jKDEuMyksIHk9YygwLjQpKSwgYWVzKHg9eCwgeT15LCBsYWJlbD0iREQiLCBzaXplPTMwKSkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQoNCmBgYHtyfQ0KIyBkZl9zdG9jayBpcyBhbiBhbHJlYWR5IHByZXBwZWQgY3N2IGZyb20gQ1JTUCBkYXRhDQpkZl9zdG9jayRkYXRlIDwtIGFzLkRhdGUoZGZfc3RvY2skZGF0ZSkNCmRmIDwtIGxlZnRfam9pbihkZiwgZGZfc3RvY2tbLGMoImd2a2V5IiwgImRhdGUiLCAicmV0IiwgInJldC5zZCIpXSkNCmBgYA0KDQpgYGB7cn0NCg0KZGZfcmYkZGF0ZSA8LSBhcy5EYXRlKGRmX3JmJGRhdGVmZikNCmRmX3JmJHllYXIgPC0geWVhcihkZl9yZiRkYXRlKQ0KZGZfcmYkbW9udGggPC0gbW9udGgoZGZfcmYkZGF0ZSkNCg0KZGYgPC0gbGVmdF9qb2luKGRmLCBkZl9yZlssYygieWVhciIsICJtb250aCIsICJyZiIpXSkNCg0KZGYgPC0gZGYgJT4lDQogIG11dGF0ZShERCA9IChsb2cobXZlIC8gbHQpICsgKHJmIC0gKHJldC5zZCpzcXJ0KDI1MykpXjIgLyAyKSkgLw0KICAgICAgICAgICAgICAocmV0LnNkKnNxcnQoMjUzKSkpDQojIENsZWFuIHRoZSBtZWFzdXJlDQpkZiA8LSBkZiAlPiUNCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGxpc3QofnJlcGxhY2UoLiwgIWlzLmZpbml0ZSguKSwgTkEpKSkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9NH0NCnBsb3QgPC0gZGYgJT4lDQogIGZpbHRlcighaXMubmEoREQpLA0KICAgICAgICAgIWlzLm5hKHJhdGluZykpICU+JQ0KICBncm91cF9ieShyYXRpbmcpICU+JQ0KICBtdXRhdGUobWVhbl9ERD1tZWFuKERELG5hLnJtPVQpLA0KICAgICAgICAgcHJvYl9kZWZhdWx0ID0gcG5vcm0oLTEgKiBtZWFuX0REKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChyYXRpbmcsIHByb2JfZGVmYXVsdCkgJT4lDQogIGdncGxvdChhZXMoeT1wcm9iX2RlZmF1bHQsIHg9cmF0aW5nKSkgKyANCiAgZ2VvbV9jb2woKSArIA0KICB5bGFiKCdQcm9iYWJpbGl0eSBvZiBkZWZhdWx0JykgKyB4bGFiKCdDcmVkaXQgcmF0aW5nJykgKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQpnZ3Bsb3RseShwbG90KQ0KYGBgDQoNCmBgYHtyfQ0KZGYgJT4lDQogIGZpbHRlcighaXMubmEoREQpLA0KICAgICAgICAgIWlzLm5hKGJhbmtydXB0KSkgJT4lDQogIGdyb3VwX2J5KGJhbmtydXB0KSAlPiUNCiAgbXV0YXRlKG1lYW5fREQ9bWVhbihERCwgbmEucm09VCksDQogICAgICAgICBwcm9iX2RlZmF1bHQgPQ0KICAgICAgICAgICBwbm9ybSgtMSAqIG1lYW5fREQpKSAlPiUNCiAgc2xpY2UoMSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc2VsZWN0KGJhbmtydXB0LCBtZWFuX0RELA0KICAgICAgICAgcHJvYl9kZWZhdWx0KSAlPiUNCiAgaHRtbF9kZigpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTR9DQpwbG90IDwtIGRmICU+JQ0KICBmaWx0ZXIoIWlzLm5hKEREKSwNCiAgICAgICAgICFpcy5uYShyYXRpbmcpLA0KICAgICAgICAgeWVhciA+PSAyMDAwKSAlPiUNCiAgZ3JvdXBfYnkocmF0aW5nKSAlPiUNCiAgbXV0YXRlKG1lYW5fREQ9bWVhbihERCxuYS5ybT1UKSwNCiAgICAgICAgIHByb2JfZGVmYXVsdCA9IHBub3JtKC0xICogbWVhbl9ERCkpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzZWxlY3QocmF0aW5nLCBwcm9iX2RlZmF1bHQpICU+JQ0KICBnZ3Bsb3QoYWVzKHk9cHJvYl9kZWZhdWx0LCB4PXJhdGluZykpICsgDQogIGdlb21fY29sKCkgKyANCiAgeWxhYignUHJvYmFiaWxpdHkgb2YgZGVmYXVsdCcpICsgeGxhYignQ3JlZGl0IHJhdGluZycpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KZ2dwbG90bHkocGxvdCkNCmBgYA0KDQpgYGB7cn0NCmRmICU+JQ0KICBmaWx0ZXIoIWlzLm5hKEREKSwNCiAgICAgICAgICFpcy5uYShiYW5rcnVwdCksDQogICAgICAgICB5ZWFyID49IDIwMDApICU+JQ0KICBncm91cF9ieShiYW5rcnVwdCkgJT4lDQogIG11dGF0ZShtZWFuX0REPW1lYW4oREQsIG5hLnJtPVQpLA0KICAgICAgICAgcHJvYl9kZWZhdWx0ID0NCiAgICAgICAgICAgcG5vcm0oLTEgKiBtZWFuX0REKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChiYW5rcnVwdCwgbWVhbl9ERCwNCiAgICAgICAgIHByb2JfZGVmYXVsdCkgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KZml0X0REIDwtIGdsbShiYW5rcnVwdCB+IERELCBkYXRhPWRmLCBmYW1pbHk9Ymlub21pYWwpDQpzdW1tYXJ5KGZpdF9ERCkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD00fQ0KZGZERCA8LSBkZiAlPiUgZmlsdGVyKCFpcy5uYShERCksICFpcy5uYShiYW5rcnVwdCkpDQpwcmVkX0REIDwtIHByZWRpY3QoZml0X0RELCBkZkRELCB0eXBlPSJyZXNwb25zZSIpDQpST0NwcmVkX0REIDwtIHByZWRpY3Rpb24oYXMubnVtZXJpYyhwcmVkX0REKSwgYXMubnVtZXJpYyhkZkREJGJhbmtydXB0KSkNClJPQ3BlcmZfREQgPC0gcGVyZm9ybWFuY2UoUk9DcHJlZF9ERCwgJ3RwcicsJ2ZwcicpDQpkZl9ST0NfREQgPC0gZGF0YS5mcmFtZShGYWxzZVBvc2l0aXZlPWMoUk9DcGVyZl9EREB4LnZhbHVlc1tbMV1dKSwNCiAgICAgICAgICAgICAgICAgVHJ1ZVBvc2l0aXZlPWMoUk9DcGVyZl9EREB5LnZhbHVlc1tbMV1dKSkNCmdncGxvdCgpICsNCiAgZ2VvbV9saW5lKGRhdGE9ZGZfUk9DX0RELCBhZXMoeD1GYWxzZVBvc2l0aXZlLCB5PVRydWVQb3NpdGl2ZSwgY29sb3I9IkREIikpICsgDQogIGdlb21fbGluZShkYXRhPWRmX1JPQ19aLCBhZXMoeD1GUCwgeT1UUCwgY29sb3I9IloiKSkgKyANCiAgZ2VvbV9hYmxpbmUoc2xvcGU9MSkNCmBgYA0KDQpgYGB7cn0NCiNBVUMNCmF1Y19ERCA8LSBwZXJmb3JtYW5jZShST0NwcmVkX0RELCBtZWFzdXJlID0gImF1YyIpDQpBVUNzIDwtIGMoYXVjX1pAeS52YWx1ZXNbWzFdXSwgYXVjX0REQHkudmFsdWVzW1sxXV0pDQpuYW1lcyhBVUNzKSA8LSBjKCJaIiwgIkREIikNCkFVQ3MNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMgY2FsY3VsYXRlIGRvd25ncmFkZQ0KZGYgPC0gZGYgJT4lDQogIGdyb3VwX2J5KGd2a2V5KSAlPiUNCiAgYXJyYW5nZShkYXRlKSAlPiUNCiAgbXV0YXRlKGRvd25ncmFkZSA9IGlmZWxzZShyYXRpbmcgPCBsYWcocmF0aW5nKSwxLDApLA0KICAgICAgICAgZGlmZl9aID0gWiAtIGxhZyhaKSwNCiAgICAgICAgIGRpZmZfREQgPSBERCAtIGxhZyhERCkpICU+JQ0KICB1bmdyb3VwKCkNCg0KDQojIHRyYWluaW5nIHNhbXBsZQ0KdHJhaW4gPC0gZGYgJT4lIGZpbHRlcih5ZWFyIDwgMjAxNCwgIWlzLm5hKGRpZmZfWiksICFpcy5uYShkaWZmX0REKSwgIWlzLm5hKGRvd25ncmFkZSksDQogICAgICAgICAgICAgICAgICAgICAgIHllYXIgPiAxOTg1KQ0KdGVzdCA8LSBkZiAlPiUgZmlsdGVyKHllYXIgPj0gMjAxNCwgIWlzLm5hKGRpZmZfWiksICFpcy5uYShkaWZmX0REKSwgIWlzLm5hKGRvd25ncmFkZSkpDQoNCiMgZ2xtcw0KZml0X1oyIDwtIGdsbShkb3duZ3JhZGUgfiBkaWZmX1osIGRhdGE9dHJhaW4sIGZhbWlseT1iaW5vbWlhbCkNCmZpdF9ERDIgPC0gZ2xtKGRvd25ncmFkZSB+IGRpZmZfREQsIGRhdGE9dHJhaW4sIGZhbWlseT1iaW5vbWlhbCkNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkoZml0X1oyKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShmaXRfREQyKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTV9DQpwcmVkX1oyIDwtIHByZWRpY3QoZml0X1oyLCB0cmFpbiwgdHlwZT0icmVzcG9uc2UiKQ0KcHJlZF9aMiA8LSBpZmVsc2UoIWlzLmZpbml0ZShwcmVkX1oyKSxOQSxwcmVkX1oyKQ0KDQpST0NwcmVkX1oyIDwtIHByZWRpY3Rpb24oYXMubnVtZXJpYyhwcmVkX1oyWyFpcy5uYSh0cmFpbiRkb3duZ3JhZGUpICYgIWlzLm5hKHByZWRfWjIpXSksIGFzLm51bWVyaWModHJhaW5bIWlzLm5hKHRyYWluJGRvd25ncmFkZSkgJiAhaXMubmEocHJlZF9aMiksXSRkb3duZ3JhZGUpKQ0KUk9DcGVyZl9aMiA8LSBwZXJmb3JtYW5jZShST0NwcmVkX1oyLCAndHByJywnZnByJykNCmRmX1JPQ19aMiA8LSBkYXRhLmZyYW1lKEZhbHNlUG9zaXRpdmU9YyhST0NwZXJmX1oyQHgudmFsdWVzW1sxXV0pLA0KICAgICAgICAgICAgICAgICBUcnVlUG9zaXRpdmU9YyhST0NwZXJmX1oyQHkudmFsdWVzW1sxXV0pKQ0KYXVjX1oyIDwtIHBlcmZvcm1hbmNlKFJPQ3ByZWRfWjIsIG1lYXN1cmUgPSAiYXVjIikNCg0KDQpwcmVkX0REMiA8LSBwcmVkaWN0KGZpdF9ERDIsIHRyYWluLCB0eXBlPSJyZXNwb25zZSIpDQojWyFpcy5uYShwcmVkKV0NClJPQ3ByZWRfREQyIDwtIHByZWRpY3Rpb24oYXMubnVtZXJpYyhwcmVkX0REMlshaXMubmEodHJhaW4kZG93bmdyYWRlKSAmICFpcy5uYShwcmVkX0REMildKSwgYXMubnVtZXJpYyh0cmFpblshaXMubmEodHJhaW4kZG93bmdyYWRlKSAmICFpcy5uYShwcmVkX0REMiksXSRkb3duZ3JhZGUpKQ0KUk9DcGVyZl9ERDIgPC0gcGVyZm9ybWFuY2UoUk9DcHJlZF9ERDIsICd0cHInLCdmcHInKQ0KZGZfUk9DX0REMiA8LSBkYXRhLmZyYW1lKEZhbHNlUG9zaXRpdmU9YyhST0NwZXJmX0REMkB4LnZhbHVlc1tbMV1dKSwNCiAgICAgICAgICAgICAgICAgVHJ1ZVBvc2l0aXZlPWMoUk9DcGVyZl9ERDJAeS52YWx1ZXNbWzFdXSkpDQpnZ3Bsb3QoKSArIGdlb21fbGluZShkYXRhPWRmX1JPQ19ERDIsIGFlcyh4PUZhbHNlUG9zaXRpdmUsIHk9VHJ1ZVBvc2l0aXZlLCBjb2xvcj0nREQnKSkgKyBnZW9tX2xpbmUoZGF0YT1kZl9ST0NfWjIsIGFlcyh4PUZhbHNlUG9zaXRpdmUsIHk9VHJ1ZVBvc2l0aXZlLCBjb2xvcj0nWicpKSArIGdlb21fYWJsaW5lKHNsb3BlPTEpDQphdWNfREQyIDwtIHBlcmZvcm1hbmNlKFJPQ3ByZWRfREQyLCBtZWFzdXJlID0gImF1YyIpDQpBVUNzIDwtIGMoYXVjX1oyQHkudmFsdWVzW1sxXV0sIGF1Y19ERDJAeS52YWx1ZXNbWzFdXSkNCm5hbWVzKEFVQ3MpIDwtIGMoIloiLCAiREQiKQ0KQVVDcw0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTV9DQpwcmVkX1oyIDwtIHByZWRpY3QoZml0X1oyLCB0ZXN0LCB0eXBlPSJyZXNwb25zZSIpDQpST0NwcmVkX1oyIDwtIHByZWRpY3Rpb24oYXMubnVtZXJpYyhwcmVkX1oyWyFpcy5uYSh0ZXN0JGRvd25ncmFkZSkgJiAhaXMubmEocHJlZF9aMildKSwgYXMubnVtZXJpYyh0ZXN0WyFpcy5uYSh0ZXN0JGRvd25ncmFkZSkgJiAhaXMubmEocHJlZF9aMiksXSRkb3duZ3JhZGUpKQ0KUk9DcGVyZl9aMiA8LSBwZXJmb3JtYW5jZShST0NwcmVkX1oyLCAndHByJywnZnByJykNCmRmX1JPQ19aMiA8LSBkYXRhLmZyYW1lKEZhbHNlUG9zaXRpdmU9YyhST0NwZXJmX1oyQHgudmFsdWVzW1sxXV0pLA0KICAgICAgICAgICAgICAgICBUcnVlUG9zaXRpdmU9YyhST0NwZXJmX1oyQHkudmFsdWVzW1sxXV0pKQ0KYXVjX1oyIDwtIHBlcmZvcm1hbmNlKFJPQ3ByZWRfWjIsIG1lYXN1cmUgPSAiYXVjIikNCg0KcHJlZF9ERDIgPC0gcHJlZGljdChmaXRfREQyLCB0ZXN0LCB0eXBlPSJyZXNwb25zZSIpDQpST0NwcmVkX0REMiA8LSBwcmVkaWN0aW9uKGFzLm51bWVyaWMocHJlZF9ERDJbIWlzLm5hKHRlc3QkZG93bmdyYWRlKSAmICFpcy5uYShwcmVkX0REMildKSwgYXMubnVtZXJpYyh0ZXN0WyFpcy5uYSh0ZXN0JGRvd25ncmFkZSkgJiAhaXMubmEocHJlZF9ERDIpLF0kZG93bmdyYWRlKSkNClJPQ3BlcmZfREQyIDwtIHBlcmZvcm1hbmNlKFJPQ3ByZWRfREQyLCAndHByJywnZnByJykNCmRmX1JPQ19ERDIgPC0gZGF0YS5mcmFtZShGYWxzZVBvc2l0aXZlPWMoUk9DcGVyZl9ERDJAeC52YWx1ZXNbWzFdXSksDQogICAgICAgICAgICAgICAgIFRydWVQb3NpdGl2ZT1jKFJPQ3BlcmZfREQyQHkudmFsdWVzW1sxXV0pKQ0KZ2dwbG90KCkgKyBnZW9tX2xpbmUoZGF0YT1kZl9ST0NfREQyLCBhZXMoeD1GYWxzZVBvc2l0aXZlLCB5PVRydWVQb3NpdGl2ZSwgY29sb3I9J0REJykpICsgZ2VvbV9saW5lKGRhdGE9ZGZfUk9DX1oyLCBhZXMoeD1GYWxzZVBvc2l0aXZlLCB5PVRydWVQb3NpdGl2ZSwgY29sb3I9J1onKSkgKyBnZW9tX2FibGluZShzbG9wZT0xKQ0KYXVjX0REMiA8LSBwZXJmb3JtYW5jZShST0NwcmVkX0REMiwgbWVhc3VyZSA9ICJhdWMiKQ0KQVVDcyA8LSBjKGF1Y19aMkB5LnZhbHVlc1tbMV1dLCBhdWNfREQyQHkudmFsdWVzW1sxXV0pDQpuYW1lcyhBVUNzKSA8LSBjKCJaIiwgIkREIikNCkFVQ3MNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GfQ0KZml0X2NvbWIgPC0gZ2xtKGRvd25ncmFkZSB+IGRpZmZfWiArIGRpZmZfREQsIGRhdGE9dHJhaW4sIGZhbWlseT1iaW5vbWlhbCkNCnN1bW1hcnkoZml0X2NvbWIpDQpgYGANCg0KYGBge3J9DQpmaXRfY29tYiAlPiUNCiAgbWFyZ2luczo6bWFyZ2lucygpICU+JQ0KICBzdW1tYXJ5KCkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01fQ0KcHJlZF9jb21iIDwtIHByZWRpY3QoZml0X2NvbWIsIHRlc3QsIHR5cGU9InJlc3BvbnNlIikNCnByZWRfY29tYiA8LSBpZmVsc2UoIWlzLmZpbml0ZShwcmVkX2NvbWIpLE5BLHByZWRfY29tYikNCg0KUk9DcHJlZF9jb21iIDwtIHByZWRpY3Rpb24oYXMubnVtZXJpYyhwcmVkX2NvbWJbIWlzLm5hKHRlc3QkZG93bmdyYWRlKSAmICFpcy5uYShwcmVkX2NvbWIpXSksIGFzLm51bWVyaWModGVzdFshaXMubmEodGVzdCRkb3duZ3JhZGUpICYgIWlzLm5hKHByZWRfY29tYiksXSRkb3duZ3JhZGUpKQ0KUk9DcGVyZl9jb21iIDwtIHBlcmZvcm1hbmNlKFJPQ3ByZWRfY29tYiwgJ3RwcicsJ2ZwcicpDQpkZl9ST0NfY29tYiA8LSBkYXRhLmZyYW1lKEZhbHNlUG9zaXRpdmU9YyhST0NwZXJmX2NvbWJAeC52YWx1ZXNbWzFdXSksDQogICAgICAgICAgICAgICAgICAgVHJ1ZVBvc2l0aXZlPWMoUk9DcGVyZl9jb21iQHkudmFsdWVzW1sxXV0pKQ0KYXVjX2NvbWIgPC0gcGVyZm9ybWFuY2UoUk9DcHJlZF9jb21iLCBtZWFzdXJlID0gImF1YyIpDQoNCmdncGxvdCgpICsgDQogIGdlb21fbGluZShkYXRhPWRmX1JPQ19jb21iLCBhZXMoeD1GYWxzZVBvc2l0aXZlLCB5PVRydWVQb3NpdGl2ZSwgY29sb3I9J0NvbWJpbmVkJykpICsNCiAgZ2VvbV9saW5lKGRhdGE9ZGZfUk9DX1oyLCBhZXMoeD1GYWxzZVBvc2l0aXZlLCB5PVRydWVQb3NpdGl2ZSwgY29sb3I9J1onKSkgKw0KICBnZW9tX2FibGluZShzbG9wZT0xKSArIA0KICBnZW9tX2xpbmUoZGF0YT1kZl9ST0NfREQyLCBhZXMoeD1GYWxzZVBvc2l0aXZlLCB5PVRydWVQb3NpdGl2ZSwgY29sb3I9J0REJykpDQoNCkFVQ3MgPC0gYyhhdWNfY29tYkB5LnZhbHVlc1tbMV1dLCBhdWNfWjJAeS52YWx1ZXNbWzFdXSwgYXVjX0REMkB5LnZhbHVlc1tbMV1dKQ0KbmFtZXMoQVVDcykgPC0gYygiQ29tYmluZWQiLCAiWiIsICJERCIpDQpBVUNzDQpgYGANCg0K