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