Step 1: Get data

df <- read.csv("~/Documents/GitHub/Machine-Learning-R/Machine-Learning-R/datasets/Telco-Customer-Churn.csv")

Step 2: Explore Data

Spot problems

str(df)
'data.frame':   7043 obs. of  21 variables:
 $ customerID      : chr  "7590-VHVEG" "5575-GNVDE" "3668-QPYBK" "7795-CFOCW" ...
 $ gender          : chr  "Female" "Male" "Male" "Male" ...
 $ SeniorCitizen   : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Partner         : chr  "Yes" "No" "No" "No" ...
 $ Dependents      : chr  "No" "No" "No" "No" ...
 $ tenure          : int  1 34 2 45 2 8 22 10 28 62 ...
 $ PhoneService    : chr  "No" "Yes" "Yes" "No" ...
 $ MultipleLines   : chr  "No phone service" "No" "No" "No phone service" ...
 $ InternetService : chr  "DSL" "DSL" "DSL" "DSL" ...
 $ OnlineSecurity  : chr  "No" "Yes" "Yes" "Yes" ...
 $ OnlineBackup    : chr  "Yes" "No" "Yes" "No" ...
 $ DeviceProtection: chr  "No" "Yes" "No" "Yes" ...
 $ TechSupport     : chr  "No" "No" "No" "Yes" ...
 $ StreamingTV     : chr  "No" "No" "No" "No" ...
 $ StreamingMovies : chr  "No" "No" "No" "No" ...
 $ Contract        : chr  "Month-to-month" "One year" "Month-to-month" "One year" ...
 $ PaperlessBilling: chr  "Yes" "No" "Yes" "No" ...
 $ PaymentMethod   : chr  "Electronic check" "Mailed check" "Mailed check" "Bank transfer (automatic)" ...
 $ MonthlyCharges  : num  29.9 57 53.9 42.3 70.7 ...
 $ TotalCharges    : num  29.9 1889.5 108.2 1840.8 151.7 ...
 $ Churn           : chr  "No" "No" "Yes" "No" ...
#7043 obs and 21 vars
#Explore data interactively with Explore package
#explore(df) #uncomment to run

#we have several different measures in the dataset


#churn rate baseline put into its own data
(churn.base <- df %>% 
  group_by(Churn) %>% 
  count(Churn) %>% 
  mutate(perc = n/nrow(df) * 100) %>% 
  rename(customers = n))



#ordered bar plots - need to arrange column in desc order b4 plotting
ggbarplot(churn.base, x = "customers", y = "perc",               
         fill = "Churn", 
         color = "white",
         palette = "jco",
          sort.val = "desc",          # Sort the value in dscending order
          sort.by.groups = FALSE,     # Don't sort inside each group
          x.text.angle = 0,          # Rotate vertically x axis texts
          legend = "right",
          xlab = " Churn",
          ylab = " Percentage",
          label = paste(round(churn.base$perc,0),"%", sep = ""),
          label.pos = "out",
          title = "Churn rate baseline",
          ggtheme = theme_minimal()
         )

table(df$Churn)
prop.table(table(df$Churn))

#Our churn rate is 27%


# Don't map a variable to y
  ggplot(df, aes(x=factor(gender)))+
  geom_bar(stat="count", width=0.7, fill="#a6cee3")+
  theme_minimal()

  ggplot(df, aes(x=factor(SeniorCitizen)))+
  geom_bar(stat="count", width=0.7, fill="#1f78b4")+
  theme_minimal()

  ggplot(df, aes(x=factor(Partner)))+
  geom_bar(stat="count", width=0.7, fill="#b2df8a")+
  theme_minimal()

  ggplot(df, aes(x=factor(Dependents)))+
  geom_bar(stat="count", width=0.7, fill="#33a02c")+
  theme_minimal()

  ggplot(df, aes(x=factor(PhoneService)))+
  geom_bar(stat="count", width=0.7, fill="#fb9a99")+
  theme_minimal()

  ggplot(df, aes(x=factor(MultipleLines)))+
  geom_bar(stat="count", width=0.7, fill="#e31a1c")+
  theme_minimal()

  ggplot(df, aes(x=factor(InternetService)))+
  geom_bar(stat="count", width=0.7, fill="#fdbf6f")+
  theme_minimal()

  ggplot(df, aes(x=factor(OnlineSecurity)))+
  geom_bar(stat="count", width=0.7, fill="#ff7f00")+
  theme_minimal()

  ggplot(df, aes(x=factor(OnlineBackup)))+
  geom_bar(stat="count", width=0.7, fill="#cab2d6")+
  theme_minimal()

  ggplot(df, aes(x=factor(DeviceProtection)))+
  geom_bar(stat="count", width=0.7, fill="#6a3d9a")+
  theme_minimal()

  ggplot(df, aes(x=factor(TechSupport)))+
  geom_bar(stat="count", width=0.7, fill="#ffff99")+
  theme_minimal()

  ggplot(df, aes(x=factor(StreamingTV)))+
  geom_bar(stat="count", width=0.7, fill="#b15928")+
  theme_minimal()



  ggplot(df, aes(x=factor(StreamingMovies)))+
  geom_bar(stat="count", width=0.7, fill="#8dd3c7")+
  theme_minimal()

  ggplot(df, aes(x=factor(Contract)))+
  geom_bar(stat="count", width=0.7, fill="#ffffb3")+
  theme_minimal()

  ggplot(df, aes(x=factor(PaperlessBilling)))+
  geom_bar(stat="count", width=0.7, fill="#bebada")+
  theme_minimal()
  
  ggplot(df, aes(x=factor(PaymentMethod)))+
  geom_bar(stat="count", width=0.7, fill="#fb8072")+
  theme_minimal() + coord_flip()

#gender on x axis
ggplot(data = df, aes(x = gender, y = MonthlyCharges, fill = gender))+geom_boxplot() + stat_summary(fun=mean, geom="point", shape=20, size=8, color="red", fill="red")

ggplot(data = df, aes(x = gender, y = TotalCharges, fill = gender))+geom_boxplot()

ggplot(data = df, aes(x = gender, y = tenure, fill = gender))+geom_boxplot()


#phone service on x axis
ggplot(data = df, aes(x = PhoneService, y = MonthlyCharges, fill = PhoneService))+geom_boxplot()

ggplot(data = df, aes(x = PhoneService, y = TotalCharges, fill = PhoneService))+geom_boxplot()

ggplot(data = df, aes(x = PhoneService, y = tenure, fill = PhoneService))+geom_boxplot()

#contract on x axis
ggplot(data = df, aes(x = Contract, y = MonthlyCharges, fill = Contract))+geom_boxplot() + stat_summary(fun=mean, geom="point", shape=20, size=8, color="red", fill="red") + coord_flip()
#Similar avg monthly charges across all the different term plans,
#with the largest variability amongst 1-year contract holders

ggplot(data = df, aes(x = Contract, y = TotalCharges, fill = Contract))+geom_boxplot() # some missing values
#higher than avg total charges for those on 2-year contracts

ggplot(data = df, aes(x = Contract, y = tenure, fill = Contract))+geom_boxplot()
#The longer the contract the longer the average customer tenure with -year contract giving the highest avg tenure. Makes sense!

ggplot(data = df, aes(x = factor(Churn), y = tenure, fill = Churn)) +geom_boxplot()
#High churn for those with lower tenures

ggplot(data = df, aes(x = factor(Churn), y = MonthlyCharges, fill = Churn))+geom_boxplot()
#Higher churn for those with higher than avg monthly charges

ggplot(data = df, aes(x = factor(Churn), y = TotalCharges, fill = Churn))+geom_boxplot()
#Lower churn for those with lower than avg total charges

ggplot(data = df, aes(x=gender, fill = factor(Churn))) + geom_bar(position = "fill") + scale_fill_manual(values = c("#1b9e77", "#d95f02")) + theme(
    plot.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank(),
    plot.title = element_text(hjust = 0.5),
    text=element_text(size=14,  family="Helvetica")) + labs(x = " ", title = "Churn by gender")
#no difference by gender


ggplot(data = df, aes(x=Contract, fill = factor(Churn)))+ geom_bar(position = "fill") + scale_fill_mconfuanual(values = c("#1b9e77", "#d95f02")) + theme(
    plot.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank(),
    plot.title = element_text(hjust = 0.5),
    text=element_text(size=14,  family="Helvetica")) + labs(x = " ", title = "Contract type") + coord_flip()
#We have significantly higher churn than for those on month-to-month contracts


ggplot(data = df, aes(x=factor(SeniorCitizen), fill = factor(Churn)))+ geom_bar(position = "fill") + scale_fill_manual(values = c("#1b9e77", "#d95f02")) + theme(
    plot.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank(),
    plot.title = element_text(hjust = 0.5),
    text=element_text(size=14,  family="Helvetica")) + labs(x = " ", title = "Is a senior citizen")
#Higher churn for Senior Citizens


ggplot(data = df, aes(x=PaperlessBilling, fill = factor(Churn))) + geom_bar(position = "fill") + scale_fill_manual(values = c("#1b9e77", "#d95f02")) + theme(
    plot.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank(),
    plot.title = element_text(hjust = 0.5),
    text=element_text(size=14,  family="Helvetica")) + labs(x = " ", title = "Has paperless billing")
#higher churn rate for those on paperless billing

ggplot(data = df, aes(x=PaymentMethod, fill = factor(Churn))) + geom_bar(position = "fill") + scale_fill_manual(values = c("#1b9e77", "#d95f02")) + theme(
    plot.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank(),
    plot.title = element_text(hjust = 0.5),
    text=element_text(size=12,  family="Helvetica")) + labs(x = " ", title = "Payment Method") + coord_flip() 
#higher churn rate for those who pay by electronic
#check - lowest churn rates among those with
#automatic billing.

ggplot(data = df, aes(x=PhoneService, fill = factor(Churn))) + geom_bar(position = "fill") + labs(title = "By phone service")
#no difference by phone service


ggplot(data = df, aes(x=Partner, fill = factor(Churn))) + geom_bar(position = "fill") + scale_fill_manual(values = c("#1b9e77", "#d95f02")) + theme(
    plot.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank(),
    plot.title = element_text(hjust = 0.5),
    text=element_text(size=12,  family="Helvetica")) + labs(x = " ", title = "Has a partner") 
#slightly higher churn rate for singles


ggplot(data = df, aes(x=Dependents, fill = factor(Churn))) + geom_bar(position = "fill") + scale_fill_manual(values = c("#1b9e77", "#d95f02")) + theme(
    plot.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank(),
    plot.title = element_text(hjust = 0.5),
    text=element_text(size=12,  family="Helvetica")) + labs(x = " ", title = "Has dependents") + coord_flip() 
#higher churn rate for those with no dependents

ggplot(data = df, aes(x=MultipleLines, fill = factor(Churn))) + geom_bar(position = "fill") + labs(title = "Has multiple phone lines")
##very little difference if have multiple lines


ggplot(data = df, aes(x=InternetService, fill = factor(Churn))) + geom_bar(position = "fill") + scale_fill_manual(values = c("#1b9e77", "#d95f02")) + theme(
    plot.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank(),
    plot.title = element_text(hjust = 0.5),
    text=element_text(size=12,  family="Helvetica")) + labs(x = " ", title = "Internet service") + coord_flip() 
#Much higher churn if have fiber optic internet service


ggplot(data = df, aes(x=OnlineSecurity, fill = factor(Churn))) + geom_bar(position = "fill") + coord_flip() + labs(title = "Online security")
#higher churn if have no online security

ggplot(data = df, aes(x=OnlineBackup, fill = factor(Churn))) + geom_bar(position = "fill") + coord_flip() + labs(title = "Online backup")
#higher churn for those who have no online backup

ggplot(data = df, aes(x=DeviceProtection, fill = factor(Churn))) + geom_bar(position = "fill") + coord_flip() + labs(title = "Device protection")
#higher churn for those who have no device protection

ggplot(data = df, aes(x=TechSupport, fill = factor(Churn))) + geom_bar(position = "fill") + coord_flip() + labs(title = "Tech support")
#higher churn for those with no tech support

ggplot(data = df, aes(x=StreamingTV, fill = factor(Churn))) + geom_bar(position = "fill") + labs(title = "Streaming TV")
#no difference among TV streamers - but very low churners if you don't have internet service

ggplot(data = df, aes(x=StreamingMovies, fill = factor(Churn))) + geom_bar(position = "fill") + labs(title = "Streaming movies")
#no difference among movie streamers - but very low churners if you don't have internet service

ggplot(data = df, aes(x=MonthlyCharges, y=tenure, color=factor(Churn))) + geom_point(alpha = 0.5) + geom_smooth(method=lm) + labs(title = " ")
#lots of vertical lines - no horizontal patterns
#suggests that monthly charges has some association with churn as the churned customers are heavily represented on the higher end of the monthly charges
#there is most likely a higher likelihood of churn once you reach a certain point on the monthly fee


#believe that total charges can also be a proxy for tenure-hence the triangle looking scatterplot
ggplot(data = df, aes(x=TotalCharges, y=tenure, color=factor(Churn))) + geom_point(alpha = 0.5) +geom_smooth(method=lm) + labs(title = " ")

#histogram of tenure
hist(df$tenure)

#log transformation
ggplot(data = df, aes(x=MonthlyCharges, y=tenure, color=factor(Churn))) + geom_point(alpha = 0.5) + geom_smooth(method=lm) + scale_x_log10() + scale_y_log10() + labs(title = "Log transformation")

ggplot(data = df, aes(x=TotalCharges, y=tenure, color=factor(Churn))) + geom_point(alpha = 0.5) +geom_smooth(method=lm) + scale_x_log10() + labs(title = "Log transformation")

#Tenure and total charges are possibly collinear or bordering on collinearity!

#Internet Service
CrossTable(df$InternetService, df$Churn, digits=2, prop.c = TRUE,
  prop.r = TRUE, prop.t = FALSE, chisq = FALSE, format = "SAS", expected = FALSE)
#

table(df$InternetService, df$Churn)

round(addmargins(prop.table(table(df$InternetService, df$Churn))),3)


#Statistical test
chisq.test(df$Churn, df$InternetService)
#Contract
CrossTable(df$Contract, df$Churn, digits=2, prop.c = TRUE,
  prop.r = TRUE, prop.t = FALSE, chisq = FALSE, format = "SAS", expected = FALSE)
#

table(df$Contract, df$Churn)

round(addmargins(prop.table(table(df$Contract, df$Churn))),3)

#statistical test
chisq.test(df$Churn, df$Contract)

#Let's take age and put it into buckets
#let's get min and max Monthly Charge 
min(df$MonthlyCharges)
max(df$MonthlyCharges)
mean(df$MonthlyCharges)
median(df$MonthlyCharges)

#quartiles
ggplot(df, aes(y=MonthlyCharges)) + geom_boxplot()

#quantiles - takes a vector of proportions
quantile(df$MonthlyCharges, probs = c(0, 0.2, 0.4, 0.6, 0.8,1))

#create monthly fee bands
df$monthly_fee_bin <- cut(df$MonthlyCharges, 
                   breaks= 5, 
                   labels=c("bin1", "bin2", "bin3", "bin4", "bin5"), 
                   right=FALSE)

table(df$monthly_fee_bin) #check results


#check averages across the different fee bands
(fees <- df %>%  
  group_by(monthly_fee_bin) %>% 
  summarize(avg_monthly_fee = format(round(mean(MonthlyCharges),2)),
            median_monthly_fee = format(round(median(MonthlyCharges),2))))


#visualize fee bands
ggplot(data = df, aes(x=monthly_fee_bin, fill = factor(Churn))) + geom_bar(position = "fill")

##Step 3: Manage data

#any missing data?

#Using VIM package
missing <- aggr(df, prop = TRUE, bars = TRUE) # - some missing values

summary(missing)

## Show cases with missing values
(missing.df <- df[!complete.cases(df),]) #11 rows
#Some total charges missing

#We can remove missing rows - removing them won't affect our results that much - this is the code to remove below
df <- df[complete.cases(df),] 


#check results
sum(is.na(df$TotalCharges)) #all gone

#visualize entire dataframe again
aggr(df, prop = TRUE, bars = TRUE)
#any duplicate rows -
anyDuplicated(df)

#any duplicate columns -
names(df)[duplicated(names(df))]

df[names(df)[!duplicated(names(df))]]

There were no duplicates to worry about

Step 4: Statistical analysis & understanding relationships

Now we want to dig deeper We have some ideas of what to look for thanks to our EDA

#pairs.panels
df %>% 
  select_if(is.numeric) %>% 
  scale() %>% 
    pairs.panels()

#Tenure and TotalCharges has a very high correlation coefficient!
#Also shows us that SeniorCitizen is coded as a numerical variable (0 or 1)
#Monthly charges and TotalCharges has a high correlation coefficient also.

#Correlation Matrix
df %>% 
  select_if(is.numeric) %>% 
  cor() %>% 
    corrplot(type = "upper", insig = "blank", addCoef.col = "black", diag=FALSE)
#monthly charges is highly-correlated with total charges which makes sense. at 0.65.
# I take these results with a grain of salt.

#total charges and tenure has a high correlation.83
#which is bordering on collinearity
str(df$Churn)
 chr [1:7043] "No" "No" "Yes" "No" "Yes" "Yes" "No" "No" ...
(churnRate <- round(prop.table(table(df$Churn)),5))

     No     Yes 
0.73463 0.26537 
(churn_rate_overall <- churnRate[[2]])
[1] 0.26537
# recode churn variable into a numeric variable
df <- df %>% 
      mutate(Churn = ifelse(Churn == "Yes", 1, 0)) 

#check results
head(df$Churn)
[1] 0 0 1 0 1 1
# Find total and fraction of a churn outcome variable
df %>%
summarize(total = n(),
percent_churn = mean(Churn == 1))

#data frame grouped by fee
monthly.fee.df <- df %>% 
  group_by(monthly_fee_bin) %>% 
  summarize(total_count = n(),
    total_churns = sum(Churn)) %>% 
  mutate(churn_rate = total_churns/total_count)

head(monthly.fee.df) #check results

#check churn columns
sum(monthly.fee.df$total_churns)
sum(monthly.fee.df$total_count)

#add highlight flag column
monthly.fee.df <- monthly.fee.df %>% 
mutate(highlight_flag =
    ifelse(churn_rate > churn_rate_overall, 1, 0))

#check results
head(monthly.fee.df$highlight_flag)

#plot response rate by fee difference
plot.fee.diff <- ggplot(data=monthly.fee.df, aes(x=reorder(monthly_fee_bin, churn_rate), y=churn_rate,
  fill = factor(highlight_flag))) +
  geom_bar(stat="identity") +
  geom_hline(yintercept=churn_rate_overall, linetype="dashed", color = "black") +
  theme(axis.text.x = element_text(angle = 90)) +
  coord_flip() +
    scale_fill_manual(values = c('#595959', '#e41a1c')) +
  labs(x = ' ', y = 'Churn Rate', title = str_c("Churn rate by monthly fee")) +
  theme(legend.position = "none")

plot.fee.diff


#data frame grouped by fee
internet.df <- df %>% 
  group_by(InternetService) %>% 
  summarize(total_count = n(),
    total_churns = sum(Churn)) %>% 
  mutate(churn_rate = total_churns/total_count)

head(internet.df) #check results

#check churn columns
sum(internet.df$total_churns)
sum(internet.df$total_count)

#add highlight flag column
internet.df <- internet.df %>% 
mutate(highlight_flag =
    ifelse(churn_rate > churn_rate_overall, 1, 0))

#check results
head(internet.df$highlight_flag)

#plot response rate by fee difference
ggplot(data=internet.df, aes(x=reorder(InternetService, churn_rate), y=churn_rate,
  fill = factor(highlight_flag))) +
  geom_bar(stat="identity") +
  geom_hline(yintercept=churn_rate_overall, linetype="dashed", color = "black") +
  theme(axis.text.x = element_text(angle = 90)) +
  coord_flip() +
    scale_fill_manual(values = c('#595959', '#e41a1c')) +
  labs(x = ' ', y = 'Churn Rate', title = str_c("Churn rate by internet service")) +
  theme(legend.position = "none")

#data frame grouped by payment method
pymt.method.df <- df %>% 
  group_by(PaymentMethod) %>% 
  summarize(total_count = n(),
    total_churns = sum(Churn)) %>% 
  mutate(churn_rate = total_churns/total_count)

head(pymt.method.df) #check results

#check churn columns
sum(pymt.method.df$total_churns)
sum(pymt.method.df$total_count)

#add highlight flag column
pymt.method.df <- pymt.method.df %>% 
mutate(highlight_flag =
    ifelse(churn_rate > churn_rate_overall, 1, 0))

#check results
head(pymt.method.df$highlight_flag)

#plot response rate by fee difference
ggplot(data=pymt.method.df, aes(x=reorder(PaymentMethod, churn_rate), y=churn_rate,
  fill = factor(highlight_flag))) +
  geom_bar(stat="identity") +
  geom_hline(yintercept=churn_rate_overall, linetype="dashed", color = "black") +
  theme(axis.text.x = element_text(angle = 90)) +
  coord_flip() +
    scale_fill_manual(values = c('#595959', '#e41a1c')) +
  labs(x = ' ', y = 'Churn Rate', title = str_c("Churn rate by payment method")) +
  theme(legend.position = "none")

We have uncovered some insights so far through data visualization and statistical tests:

Low churners characteristics: -Very low churn for those users who don’t have the internet service!!!!

High churners characteristics: -Fiber optic service -Senior Citizens -Slightly higher for singles & those with no dependents (but not statistically significant) -Paperless billing or electronic check -Month-to-month contracts

Monthly charges has some association with churn as the churned customers are heavily represented on the higher end of the monthly charges. There is most likely a higher likelihood of churn once you reach a certain point on the monthly fee.

Now we have to decide whether we should do some data modeling.

Step 5A: Data pre-proessing

#let's use a fresh dataset
df_raw <- read.csv("~/Documents/GitHub/Machine-Learning-R/Machine-Learning-R/datasets/Telco-Customer-Churn.csv")

#str(df_raw) #check results

#Remove rows with missing observations
df_raw <- df_raw[complete.cases(df_raw),] 

#get rid of customerID column - we don't need it for modeling
#let's just keep the variables we need
df_premodel <- df_raw %>% 
  select(gender, SeniorCitizen, Partner, Dependents, tenure, PhoneService, MultipleLines, InternetService, OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTV, StreamingMovies,Contract,PaperlessBilling, PaymentMethod, MonthlyCharges, TotalCharges, Churn)


#merge unnecessary levels (no internet) and recode variables to dummy variable
df_dummy <- df_premodel %>% 
    mutate(gender = ifelse(gender == "Female", 1, 0),
           Partner = ifelse(Partner == "Yes", 1, 0),
           Dependents = ifelse(Dependents == "Yes", 1, 0),
           PhoneService = ifelse(PhoneService == "Yes", 1, 0),
           MultipleLines = ifelse(MultipleLines == "Yes", 1, 0),
          OnlineSecurity = ifelse(OnlineSecurity  == "Yes", 1, 0),
          OnlineBackup = ifelse(OnlineBackup  == "Yes", 1, 0),
          DeviceProtection = ifelse(DeviceProtection  == "Yes", 1, 0),
          TechSupport = ifelse(TechSupport  == "Yes", 1, 0),
          StreamingTV = ifelse(StreamingTV  == "Yes", 1, 0),
          StreamingMovies = ifelse(StreamingMovies  == "Yes", 1, 0),
          PaperlessBilling = ifelse(PaperlessBilling  == "Yes", 1, 0),
          Churn = ifelse(Churn == "Yes", 1, 0))

#check results
str(df_dummy)

#recode numeric variables into factor variables
df_dummy <- df_dummy %>% 
  mutate_at(c("gender", "SeniorCitizen", "Partner", "Dependents", "PhoneService", "MultipleLines", "InternetService", "OnlineSecurity", "OnlineBackup", "DeviceProtection", "TechSupport", "StreamingTV", "StreamingMovies","Contract","PaperlessBilling", "PaymentMethod", "Churn"), .funs = factor)

#check results
str(df_dummy)
str(df_dummy$Churn) #check churn variable

table(df_dummy$Churn)

Part 5B: Modeling - Logistic Regression


# Check total number of rows in our dataset before splitting
nrow(df_dummy)

#Set seed for reproducibility
set.seed(123) 

#Split the dataset into training and test sets at 70:30 ratio using caTools package
split <- sample.split(df_dummy$Churn, SplitRatio = 0.7)
train_data <- subset(df_dummy, split == TRUE)
test_data <- subset(df_dummy, split == FALSE)

#Check if distribution of partition data is correct for train and test set
prop.table(table(train_data$Churn))
prop.table(table(test_data$Churn)) 

set.seed(1)  # set seed for reproducibility

#Use glm function from glmnet package to create model
model_01_glm <- glm(formula = Churn ~ ., 
                             data = train_data, family = "binomial")
 
#use to debug if glm function not working   
# train_data %>%
#   select_if(is.factor) %>%
#   lapply(table)
                         
#Print the model output
summary(model_01_glm)

The Odds Ratio and Probability of each x variable is calculated based on the formulae, Odds Ratio = exp(Co-efficient estimate) Probability = Odds Ratio / (1 + Odds Ratio)


##Individual coefficients significance and interpretation

#library(coef.lmList)
summary(model_01_glm)

#prints confidence intervals
exp(confint(model_01_glm))


#plot coefficients on odds ratio using sjPlot package
plot_model(model_01_glm, vline.color = "red",
  sort.est = TRUE, show.values = TRUE)
#Calculate Variance Inflation factor using car package
vif(model_01_glm)

# Feature (x) variables with a VIF value above 5 indicate high degree of
# multi-collinearity.

MonthlyCharges has a very high VIF at 600+. We have some other variables e.g. InternetServiceFiber.optic, InternetServiceNo, and TotalCharges, PhoneServiceYes, with high VIFs. Normally, we would want to remove variables with high VIF as it can really mess with our model.

pred <- predict(model_01_glm, test_data, type = "response") #predict using test data

#check results
head(pred)

predicted <- round(pred) #>0.5 will convert to 1

#contingency table
contingency_tab <- table(test_data$Churn, predicted)
contingency_tab

# Confusion Matrix using the caret package
caret::confusionMatrix(contingency_tab)

#Plot ROC Curve & Calculate AUC area
library(ROCR)

#ROC Curves are useful for comparing classifiers

#Check data structures first or else the ROC curve won't plot
typeof(predicted)
typeof(test_data$Churn)


pr <- prediction(pred, test_data$Churn)
prf <- performance(pr, measure = "tpr", x.measure = "fpr")

plot(prf)

#The ideal ROC curve hugs the top left corner, indicating a high
#true positive rate and a low false positive rate.
#True positive rate on y-axis
#False positive rate on the x-axis

#The larger the AUC, the better the classifier
#The AUC line is insufficient to identify a best model
#It's used in combination with qualitative examination
#of the ROC curve
auc <- performance(pr, measure = "auc")
auc

# AUC is 0.861989
as.numeric(performance(pr, measure = "auc")@y.values)

#Double density plot for explaining and picking thresholds of predicted churn probabilities. Perhaps better alternative than AUC or ROC curve
ggplot(data = test_data) + geom_density(aes(x=pred, color = Churn,linetype = Churn))

AUC Interpretation A: Outstanding = 0.9 to 1.0 B: Excellent/Good = 0.8 to 0.9 C: Acceptable/Fair = 0.7 to 0.8 D: Poor = 0.6 to 0.7 E: No Discrimination = 0.5 to 0.6

set.seed(1)  # for reproducibility
model_02_glm <- glm(formula = Churn ~ . -MonthlyCharges,
                             data = train_data, family = "binomial")

summary(model_02_glm)

vif(model_02_glm)

#plot coefficients on odds ratio
plot_model(model_02_glm, vline.color = "red",
  sort.est = TRUE, show.values = TRUE)

InternetService - Fiber Optic cable still increases likelihood of churn followed by paperless billing and being a senior citizen.

pred <- predict(model_02_glm, test_data, type = "response") #predict using test data

#check results
head(pred)

predicted <- round(pred) #>0.5 will convert predicted values to 1

#contingency table - manually done with table function
contingency_tab <- table(test_data$Churn, predicted)
contingency_tab

# Confusion Matrix using the caret package
caret::confusionMatrix(contingency_tab)

#Plot ROC Curve & Calculate AUC area
library(ROCR)


#check data structures first
typeof(predicted)
typeof(test_data$Churn)


pr <- prediction(pred, test_data$Churn)
prf <- performance(pr, measure = "tpr", x.measure = "fpr")

plot(prf)

#The ideal ROC curve hugs the top left corner, indicating a high
#true positive rate and a low false positive rate.
#True positive rate on y-axis
#False positive rate on the x-axis

#The larger the AUC, the better the classifier
#The AUC line is insufficient to identify a best model
#It's used in combination with qualitative examination
#of the ROC curve
auc <- performance(pr, measure = "auc")
auc

as.numeric(performance(pr, measure = "auc")@y.values)

Comparing logistic regression models 1 and 2 -Model 1 has better accuracy than Model 2 (just slightly better), but we do care about specificity and sensitivity.

Sensitivity is the proportion of our variable of interest(churn) correctly identified.

Specificity is the proportion of our variable of interest(churn)

Read: https://www.theanalysisfactor.com/sensitivity-and-specificity/

Part 5B: Modeling - Random Forest

# Train a Random Forest using randomForest package
set.seed(1)  # for reproducibility
model_01_rf <- randomForest(formula = Churn ~ ., data = train_data, importance = TRUE, na.action=na.exclude)
                             
# Print the model output                
print(model_01_rf)

Model 500 trees: -No. of variables tried at each split: 5 -OOB estimate of error rate: 20%

# prints variable importance
summary(model_01_rf)


varImpPlot(model_01_rf, main="Variable Importance")
# Train a Random Forest
set.seed(1)  # for reproducibility
model_02_rf <- randomForest(formula = Churn ~ . -tenure,
                             data = train_data, importance = TRUE)
                             
# Print the model output
print(model_02_rf)

varImpPlot(model_02_rf, main="Variable Importance without tenure")

Type of random forest: classification Number of trees: 500 No. of variables tried at each split: 4

OOB estimate of error rate: 21.51% Confusion matrix: 0 1 class.error 0 3621 499 0.1211165 1 829 677 0.5504648

our oob error rate increased to 21.51% from 21.3% - let’s tune the 1st model - model_01_rf

# Grab OOB error matrix & take a look
err <- model_01_rf$err.rate
head(err)

# Look at final OOB error rate (last row in err matrix)
oob_err <- err[nrow(err), "OOB"]
print(oob_err)

# Plot the model trained
plot(model_01_rf)

# Add a legend since it doesn't have one by default
legend(x = "right", 
       legend = colnames(err),
       fill = 1:ncol(err))
# Generate predicted classes using the model object
class_prediction <- predict(object = model_01_rf,   # model object 
                            newdata = test_data,  # test dataset
                            type = "class") # return classification labels
                            
# Calculate the confusion matrix for the test set
cm <- caret::confusionMatrix(data = class_prediction,#predicted classes
                      reference = test_data$Churn)  # actual classes
print(cm)

# Compare test set accuracy to OOB accuracy
paste0("Test Accuracy: ", cm$overall[1])
paste0("OOB Accuracy: ", 1 - oob_err)

Compare test set accuracy to OOB accuracy

[1] “Test Accuracy: 0.809388335704125”

[1] “OOB Accuracy: 0.796836118023462”


# Generate predictions on the test set
pred <-predict(object = model_01_rf,
            newdata = test_data,
            type = "prob") 

# Uncomment to take a look at the pred format - `pred` object is a matrix
#head(pred)
                
# Compute the AUC (`actual` must be a binary 1/0 numeric vector)
round(auc(actual = ifelse(test_data$Churn == "1", 1, 0),
    predicted = pred[,"1"]) ,2)

AUC is good to excellent

CAUTION: Random forest models are computationally heavy to run on your computer especially the parameter tuning as the model needs to iterate through all possible values in the hyper grid. This could take many hours to run and if your dataset is large, could crash your computer!

Optimal model

Mtry = 4 nodesize = 7 sampsize = 3445.4

# Train a Random Forest
set.seed(1)  # for reproducibility
model_03_rf_final <- randomForest(formula = Churn ~ ., 
                                  mtry = 4,
                                  nodesize = 7,
                                  sampsize = 3445.4,
                             data = train_data, importance = TRUE, keep.forest=TRUE)
                             
# Print the model output
print(model_03_rf_final)

#variable importance
importance(model_03_rf_final)

#variable importance plot
varImpPlot(model_03_rf_final, main="Variable importance on the tuned model")
# Generate predictions on the test set
pred <-predict(object = model_03_rf_final,
            newdata = test_data,
            type = "prob") 

round(auc(actual = ifelse(test_data$Churn == "1", 1, 0),
    predicted = pred[,"1"]) ,2)
require(pROC)
rf.roc<-roc(train_data$Churn,model_03_rf_final$votes[,2])
plot(rf.roc)
auc(rf.roc)

# Train the model (to predict 'Churn') using rpart package
tree_mod_01 <- rpart(formula = Churn ~., 
                      data = train_data,
                      method = "class")

# Look at the model output                  
print(tree_mod_01)


# Display the results using rpart.plot package
rpart.plot(x = tree_mod_01, yesno = 2, type = 0, extra = 0)

rpart.plot(tree_mod_01,
yesno = 2,
extra = 104, # show fitted class, probs, percentages
box.palette = "GnBu", # color scheme
branch.lty = 3, # 1= solid, 3 = dotted branch lines
shadow.col = "gray", # shadows under the node boxes
nn = TRUE, 
main = "Classification tree on our churn data")
# Train a gini-based model
tree_mod_02<- rpart(formula = Churn ~., 
                      data = train_data_tree,
                       method = "class",
                       parms = list(split = "gini"))

# Display the results
rpart.plot(x = tree_mod_02, yesno = 2, type = 0, extra = 0)

# Train an information-based model
tree_mod_03<- rpart(formula = Churn ~., 
                      data = train_data_tree, 
                       method = "class",
                       parms = list(split = "information"))

# Display the results
rpart.plot(x = tree_mod_03, yesno = 2, type = 5, extra = 6)

# Generate predictions on the test set using the gini model
pred1 <- predict(object = tree_mod_02, 
             newdata = test_data_tree,
             type = "class")    

# Generate predictions on the test set using the information model
pred2 <- predict(object = tree_mod_03, 
             newdata = test_data_tree,
             type = "class")

# Compare classification error - using ModelMetrics library
ce(actual = test_data$Churn, 
   predicted = pred1)
ce(actual = test_data$Churn, 
   predicted = pred2)  

Part 6: Summary of Insights and hypotheses generated and testing them

What insights have we discovered and can confidently report to our stakeholders?

1.Fiber optic service is a pain point for customers, but why? Difficult to set up. Quality concerns, Can’t stream Netflix, etc. 2. Contract type is a no-brainer. If you lock customers into a contract, they can’t churn.


df %>% 
  filter(Churn == "Yes" & InternetService == "Fiber optic") %>% 
  summarize(lost_revenue = sum(TotalCharges))

# If we plot lost revenue (churned customers) by internet service, this could help create a sense of urgency for your business stakeholders.

Now, you have a good starting point to present your findings & insights to stakeholders, but this is only the beginning. Currently, you only have a strong hypotheses so now you need to validate and test what you have uncovered. You may need to do more research or data to do this as you may need to uncover the root causes.

LS0tCnRpdGxlOiAiVGVsY28gQ3VzdG9tZXIgQ2h1cm4iCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0Ci0tLQojIyBTdGVwIDE6IEdldCBkYXRhCgpgYGB7ciBMb2FkIGxpYnJhcmllcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KI0NvcmUgJiBzdGF0cyBwYWNrYWdlcwpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwc3ljaCkKbGlicmFyeShnbW9kZWxzKQpsaWJyYXJ5KFZJTSkKbGlicmFyeShnZ2NvcnJwbG90KQpsaWJyYXJ5KGNvcnJwbG90KQoKI0RhdGEgdmlzdWFsaXphdGlvbiBwYWNrYWdlcwpsaWJyYXJ5KGV4cGxvcmUpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KGdncHVicikKbGlicmFyeShzalBsb3QpCgojTW9kZWxpbmcgcGFja2FnZXMKbGlicmFyeShjYVRvb2xzKQpsaWJyYXJ5KGNhcikKbGlicmFyeShnbG1uZXQpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoUk9DUikKbGlicmFyeShQUlJPQykKbGlicmFyeShyYW5kb21Gb3Jlc3QpCmxpYnJhcnkocFJPQykKbGlicmFyeShycGFydCkKbGlicmFyeShycGFydC5wbG90KQpsaWJyYXJ5KE1vZGVsTWV0cmljcykKYGBgCgoKCmBgYHtyIENoYW5nZSBhbmQgY2hlY2sgd29ya2luZyBkaXJlY3RvcnkgaWYgbmVlZGVkLCBlY2hvID0gRkFMU0V9Cgojc2V0d2QoIi9Vc2Vycy9hbml0YW93ZW5zL1JfUHJvZ3JhbW1pbmcvSEkgQm9vdGNhbXAiKQojZ2V0d2QoKSAjY2hlY2sgcmVzdWx0cwpgYGAKCmBgYHtyIExvYWQgZGF0YXNldH0KZGYgPC0gcmVhZC5jc3YoIn4vRG9jdW1lbnRzL0dpdEh1Yi9NYWNoaW5lLUxlYXJuaW5nLVIvTWFjaGluZS1MZWFybmluZy1SL2RhdGFzZXRzL1RlbGNvLUN1c3RvbWVyLUNodXJuLmNzdiIpCmBgYAoKCiMjIFN0ZXAgMjogRXhwbG9yZSBEYXRhClNwb3QgcHJvYmxlbXMKCmBgYHtyIENoZWNrIHN0cnVjdHVyZX0Kc3RyKGRmKQojNzA0MyBvYnMgYW5kIDIxIHZhcnMKYGBgCgoKCgpgYGB7ciBFeHBsb3JlIERhdGEgSW50ZXJhdGl2ZWx5fQojRXhwbG9yZSBkYXRhIGludGVyYWN0aXZlbHkgd2l0aCBFeHBsb3JlIHBhY2thZ2UKI2V4cGxvcmUoZGYpICN1bmNvbW1lbnQgdG8gcnVuCgojd2UgaGF2ZSBzZXZlcmFsIGRpZmZlcmVudCBtZWFzdXJlcyBpbiB0aGUgZGF0YXNldApgYGAKCgoKYGBge3IgRXN0YWJsaXNoIGNodXJuIHJhdGUgYmFzZWxpbmUgLSB0aGF0IGlzIHdoYXQgd2UgYXJlIGludGVyZXN0ZWQgaW59CgoKI2NodXJuIHJhdGUgYmFzZWxpbmUgcHV0IGludG8gaXRzIG93biBkYXRhCihjaHVybi5iYXNlIDwtIGRmICU+JSAKICBncm91cF9ieShDaHVybikgJT4lIAogIGNvdW50KENodXJuKSAlPiUgCiAgbXV0YXRlKHBlcmMgPSBuL25yb3coZGYpICogMTAwKSAlPiUgCiAgcmVuYW1lKGN1c3RvbWVycyA9IG4pKQoKCgojb3JkZXJlZCBiYXIgcGxvdHMgLSBuZWVkIHRvIGFycmFuZ2UgY29sdW1uIGluIGRlc2Mgb3JkZXIgYjQgcGxvdHRpbmcKZ2diYXJwbG90KGNodXJuLmJhc2UsIHggPSAiY3VzdG9tZXJzIiwgeSA9ICJwZXJjIiwgICAgICAgICAgICAgICAKICAgICAgICAgZmlsbCA9ICJDaHVybiIsIAogICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsCiAgICAgICAgIHBhbGV0dGUgPSAiamNvIiwKICAgICAgICAgIHNvcnQudmFsID0gImRlc2MiLCAgICAgICAgICAjIFNvcnQgdGhlIHZhbHVlIGluIGRzY2VuZGluZyBvcmRlcgogICAgICAgICAgc29ydC5ieS5ncm91cHMgPSBGQUxTRSwgICAgICMgRG9uJ3Qgc29ydCBpbnNpZGUgZWFjaCBncm91cAogICAgICAgICAgeC50ZXh0LmFuZ2xlID0gMCwgICAgICAgICAgIyBSb3RhdGUgdmVydGljYWxseSB4IGF4aXMgdGV4dHMKICAgICAgICAgIGxlZ2VuZCA9ICJyaWdodCIsCiAgICAgICAgICB4bGFiID0gIiBDaHVybiIsCiAgICAgICAgICB5bGFiID0gIiBQZXJjZW50YWdlIiwKICAgICAgICAgIGxhYmVsID0gcGFzdGUocm91bmQoY2h1cm4uYmFzZSRwZXJjLDApLCIlIiwgc2VwID0gIiIpLAogICAgICAgICAgbGFiZWwucG9zID0gIm91dCIsCiAgICAgICAgICB0aXRsZSA9ICJDaHVybiByYXRlIGJhc2VsaW5lIiwKICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkKICAgICAgICAgKQoKdGFibGUoZGYkQ2h1cm4pCnByb3AudGFibGUodGFibGUoZGYkQ2h1cm4pKQoKI091ciBjaHVybiByYXRlIGlzIDI3JQpgYGAKCgpgYGB7ciBVbml2YXJpYXRlIEFuYWx5c2lzOiBCYXJwbG90cyBmb3IgaW1wb3J0YW50IGNhdGVnb3JpY2FsIHZhcmlhYmxlcyAoeCl9CgoKIyBEb24ndCBtYXAgYSB2YXJpYWJsZSB0byB5CiAgZ2dwbG90KGRmLCBhZXMoeD1mYWN0b3IoZ2VuZGVyKSkpKwogIGdlb21fYmFyKHN0YXQ9ImNvdW50Iiwgd2lkdGg9MC43LCBmaWxsPSIjYTZjZWUzIikrCiAgdGhlbWVfbWluaW1hbCgpCgogIGdncGxvdChkZiwgYWVzKHg9ZmFjdG9yKFNlbmlvckNpdGl6ZW4pKSkrCiAgZ2VvbV9iYXIoc3RhdD0iY291bnQiLCB3aWR0aD0wLjcsIGZpbGw9IiMxZjc4YjQiKSsKICB0aGVtZV9taW5pbWFsKCkKCiAgZ2dwbG90KGRmLCBhZXMoeD1mYWN0b3IoUGFydG5lcikpKSsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHdpZHRoPTAuNywgZmlsbD0iI2IyZGY4YSIpKwogIHRoZW1lX21pbmltYWwoKQoKICBnZ3Bsb3QoZGYsIGFlcyh4PWZhY3RvcihEZXBlbmRlbnRzKSkpKwogIGdlb21fYmFyKHN0YXQ9ImNvdW50Iiwgd2lkdGg9MC43LCBmaWxsPSIjMzNhMDJjIikrCiAgdGhlbWVfbWluaW1hbCgpCgogIGdncGxvdChkZiwgYWVzKHg9ZmFjdG9yKFBob25lU2VydmljZSkpKSsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHdpZHRoPTAuNywgZmlsbD0iI2ZiOWE5OSIpKwogIHRoZW1lX21pbmltYWwoKQoKICBnZ3Bsb3QoZGYsIGFlcyh4PWZhY3RvcihNdWx0aXBsZUxpbmVzKSkpKwogIGdlb21fYmFyKHN0YXQ9ImNvdW50Iiwgd2lkdGg9MC43LCBmaWxsPSIjZTMxYTFjIikrCiAgdGhlbWVfbWluaW1hbCgpCgogIGdncGxvdChkZiwgYWVzKHg9ZmFjdG9yKEludGVybmV0U2VydmljZSkpKSsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHdpZHRoPTAuNywgZmlsbD0iI2ZkYmY2ZiIpKwogIHRoZW1lX21pbmltYWwoKQoKICBnZ3Bsb3QoZGYsIGFlcyh4PWZhY3RvcihPbmxpbmVTZWN1cml0eSkpKSsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHdpZHRoPTAuNywgZmlsbD0iI2ZmN2YwMCIpKwogIHRoZW1lX21pbmltYWwoKQoKICBnZ3Bsb3QoZGYsIGFlcyh4PWZhY3RvcihPbmxpbmVCYWNrdXApKSkrCiAgZ2VvbV9iYXIoc3RhdD0iY291bnQiLCB3aWR0aD0wLjcsIGZpbGw9IiNjYWIyZDYiKSsKICB0aGVtZV9taW5pbWFsKCkKCiAgZ2dwbG90KGRmLCBhZXMoeD1mYWN0b3IoRGV2aWNlUHJvdGVjdGlvbikpKSsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHdpZHRoPTAuNywgZmlsbD0iIzZhM2Q5YSIpKwogIHRoZW1lX21pbmltYWwoKQoKICBnZ3Bsb3QoZGYsIGFlcyh4PWZhY3RvcihUZWNoU3VwcG9ydCkpKSsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHdpZHRoPTAuNywgZmlsbD0iI2ZmZmY5OSIpKwogIHRoZW1lX21pbmltYWwoKQoKICBnZ3Bsb3QoZGYsIGFlcyh4PWZhY3RvcihTdHJlYW1pbmdUVikpKSsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHdpZHRoPTAuNywgZmlsbD0iI2IxNTkyOCIpKwogIHRoZW1lX21pbmltYWwoKQoKCgogIGdncGxvdChkZiwgYWVzKHg9ZmFjdG9yKFN0cmVhbWluZ01vdmllcykpKSsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHdpZHRoPTAuNywgZmlsbD0iIzhkZDNjNyIpKwogIHRoZW1lX21pbmltYWwoKQoKICBnZ3Bsb3QoZGYsIGFlcyh4PWZhY3RvcihDb250cmFjdCkpKSsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHdpZHRoPTAuNywgZmlsbD0iI2ZmZmZiMyIpKwogIHRoZW1lX21pbmltYWwoKQoKICBnZ3Bsb3QoZGYsIGFlcyh4PWZhY3RvcihQYXBlcmxlc3NCaWxsaW5nKSkpKwogIGdlb21fYmFyKHN0YXQ9ImNvdW50Iiwgd2lkdGg9MC43LCBmaWxsPSIjYmViYWRhIikrCiAgdGhlbWVfbWluaW1hbCgpCiAgCiAgZ2dwbG90KGRmLCBhZXMoeD1mYWN0b3IoUGF5bWVudE1ldGhvZCkpKSsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHdpZHRoPTAuNywgZmlsbD0iI2ZiODA3MiIpKwogIHRoZW1lX21pbmltYWwoKSArIGNvb3JkX2ZsaXAoKQoKYGBgCgoKCmBgYHtyIFVuaXZhcmlhdGUgQW5hbHlzaXM6ICBIaXN0b2dyYW1zIGZvciBudW1lcmljIHZhcmlhYmxlcywgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KCiAgZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHggPSB0ZW51cmUpKSsKICAgICAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICIjZTQxYTFjIiwgYmlud2lkdGggPSA1LCBjb2xvdXIgPSAiYmxhY2siKSArCiAgICAgIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtZWRpYW4odGVudXJlKSksIGxpbmV0eXBlID0gImRhc2hlZCIpICsgdGhlbWVfdHVmdGUoKSArIHlsYWIoIkNvdW50cyIpCiNiaS1tb2RhbCBkaXN0cmlidXRpb24KCiAgZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHggPSBNb250aGx5Q2hhcmdlcykpKwogICAgICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gIiMzNzdlYjgiLCBiaW53aWR0aCA9IDUsIGNvbG91ciA9ICJibGFjayIpICsKICAgICAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1lZGlhbih0ZW51cmUpKSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKyB0aGVtZV90dWZ0ZSgpICsgeWxhYigiQ291bnRzIikKI2JpLW1vZGFsCgogIGdncGxvdChkYXRhID0gZGYsIGFlcyh4ID0gVG90YWxDaGFyZ2VzKSkrCiAgICAgIGdlb21faGlzdG9ncmFtKGZpbGwgPSAiI2U0MWExYyIsIGJpbndpZHRoID0gNSwgY29sb3VyID0gImJsYWNrIikgKwogICAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVkaWFuKHRlbnVyZSkpLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICIjZTQxYTFjIikgKyBsYWJzKHRpdGxlID0gIlRvdGFsIGNoYXJnZXMiKSArIHRoZW1lX3R1ZnRlKCkgKyB5bGFiKCJDb3VudHMiKQojcmlnaHQtc2tldwoKICAjVG90YWwgQ2hhcmdlcyBvbiBsb2cKICBnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeCA9IFRvdGFsQ2hhcmdlcykpKwogICAgICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gIiMzNzdlYjgiLCBiaW53aWR0aCA9IDUsIGNvbG91ciA9ICJibGFjayIpICsKICAgICAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1lZGlhbih0ZW51cmUpKSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKyBzY2FsZV94X2xvZzEwKCkgKyBsYWJzKHRpdGxlID0gIlRvdGFsIGNoYXJnZXMgb24gbG9nIikgKyB0aGVtZV90dWZ0ZSgpICsgeWxhYigiQ291bnRzIikKI3N0aWxsIGEgYml0IG9mIHNrZXcKCgpgYGAKCgoKCmBgYHtyIEJpdmFyaWF0ZSBBbmFseXNpczogQm94cGxvdHMgZm9yIGltcG9ydGFudCAoeCkgY2F0ZWdvcmljYWwgdmFyaWFibGVzICYgKHkpIG51bWVyaWMgdmFyaWFibGVzfQoKI2dlbmRlciBvbiB4IGF4aXMKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHggPSBnZW5kZXIsIHkgPSBNb250aGx5Q2hhcmdlcywgZmlsbCA9IGdlbmRlcikpK2dlb21fYm94cGxvdCgpICsgc3RhdF9zdW1tYXJ5KGZ1bj1tZWFuLCBnZW9tPSJwb2ludCIsIHNoYXBlPTIwLCBzaXplPTgsIGNvbG9yPSJyZWQiLCBmaWxsPSJyZWQiKQoKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHggPSBnZW5kZXIsIHkgPSBUb3RhbENoYXJnZXMsIGZpbGwgPSBnZW5kZXIpKStnZW9tX2JveHBsb3QoKQoKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHggPSBnZW5kZXIsIHkgPSB0ZW51cmUsIGZpbGwgPSBnZW5kZXIpKStnZW9tX2JveHBsb3QoKQoKCiNwaG9uZSBzZXJ2aWNlIG9uIHggYXhpcwpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeCA9IFBob25lU2VydmljZSwgeSA9IE1vbnRobHlDaGFyZ2VzLCBmaWxsID0gUGhvbmVTZXJ2aWNlKSkrZ2VvbV9ib3hwbG90KCkKCmdncGxvdChkYXRhID0gZGYsIGFlcyh4ID0gUGhvbmVTZXJ2aWNlLCB5ID0gVG90YWxDaGFyZ2VzLCBmaWxsID0gUGhvbmVTZXJ2aWNlKSkrZ2VvbV9ib3hwbG90KCkKCmdncGxvdChkYXRhID0gZGYsIGFlcyh4ID0gUGhvbmVTZXJ2aWNlLCB5ID0gdGVudXJlLCBmaWxsID0gUGhvbmVTZXJ2aWNlKSkrZ2VvbV9ib3hwbG90KCkKCiNjb250cmFjdCBvbiB4IGF4aXMKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHggPSBDb250cmFjdCwgeSA9IE1vbnRobHlDaGFyZ2VzLCBmaWxsID0gQ29udHJhY3QpKStnZW9tX2JveHBsb3QoKSArIHN0YXRfc3VtbWFyeShmdW49bWVhbiwgZ2VvbT0icG9pbnQiLCBzaGFwZT0yMCwgc2l6ZT04LCBjb2xvcj0icmVkIiwgZmlsbD0icmVkIikgKyBjb29yZF9mbGlwKCkKI1NpbWlsYXIgYXZnIG1vbnRobHkgY2hhcmdlcyBhY3Jvc3MgYWxsIHRoZSBkaWZmZXJlbnQgdGVybSBwbGFucywKI3dpdGggdGhlIGxhcmdlc3QgdmFyaWFiaWxpdHkgYW1vbmdzdCAxLXllYXIgY29udHJhY3QgaG9sZGVycwoKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHggPSBDb250cmFjdCwgeSA9IFRvdGFsQ2hhcmdlcywgZmlsbCA9IENvbnRyYWN0KSkrZ2VvbV9ib3hwbG90KCkgIyBzb21lIG1pc3NpbmcgdmFsdWVzCiNoaWdoZXIgdGhhbiBhdmcgdG90YWwgY2hhcmdlcyBmb3IgdGhvc2Ugb24gMi15ZWFyIGNvbnRyYWN0cwoKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHggPSBDb250cmFjdCwgeSA9IHRlbnVyZSwgZmlsbCA9IENvbnRyYWN0KSkrZ2VvbV9ib3hwbG90KCkKI1RoZSBsb25nZXIgdGhlIGNvbnRyYWN0IHRoZSBsb25nZXIgdGhlIGF2ZXJhZ2UgY3VzdG9tZXIgdGVudXJlIHdpdGggLXllYXIgY29udHJhY3QgZ2l2aW5nIHRoZSBoaWdoZXN0IGF2ZyB0ZW51cmUuIE1ha2VzIHNlbnNlIQpgYGAKCgpgYGB7ciBCaXZhcmlhdGUgYW5hbHlzaXM6IEJveHBsb3RzIGZvciBDSFVSTiAoYXMgeCB2YXJpYWJsZSkgdnMgaW1wb3J0YW50IG51bWVyaWMgKHkgdmFyaWFibGVzKX0KCmdncGxvdChkYXRhID0gZGYsIGFlcyh4ID0gZmFjdG9yKENodXJuKSwgeSA9IHRlbnVyZSwgZmlsbCA9IENodXJuKSkgK2dlb21fYm94cGxvdCgpCiNIaWdoIGNodXJuIGZvciB0aG9zZSB3aXRoIGxvd2VyIHRlbnVyZXMKCmdncGxvdChkYXRhID0gZGYsIGFlcyh4ID0gZmFjdG9yKENodXJuKSwgeSA9IE1vbnRobHlDaGFyZ2VzLCBmaWxsID0gQ2h1cm4pKStnZW9tX2JveHBsb3QoKQojSGlnaGVyIGNodXJuIGZvciB0aG9zZSB3aXRoIGhpZ2hlciB0aGFuIGF2ZyBtb250aGx5IGNoYXJnZXMKCmdncGxvdChkYXRhID0gZGYsIGFlcyh4ID0gZmFjdG9yKENodXJuKSwgeSA9IFRvdGFsQ2hhcmdlcywgZmlsbCA9IENodXJuKSkrZ2VvbV9ib3hwbG90KCkKI0xvd2VyIGNodXJuIGZvciB0aG9zZSB3aXRoIGxvd2VyIHRoYW4gYXZnIHRvdGFsIGNoYXJnZXMKCmBgYAoKCgpgYGB7ciBCaXZhcmlhdGUgQW5hbHlzaXM6IFN0YWNrZWQgYmFyIGNoYXJ0cyBmb3IgaW1wb3J0YW50IGNhdGVnb3JpY2FsIHZhcmlhYmxlcyAoeCkgYnkgQ0hVUk4gKHkpIGZhY3Rvcn0KCmdncGxvdChkYXRhID0gZGYsIGFlcyh4PWdlbmRlciwgZmlsbCA9IGZhY3RvcihDaHVybikpKSArIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiMxYjllNzciLCAiI2Q5NWYwMiIpKSArIHRoZW1lKAogICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgIHRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTQsICBmYW1pbHk9IkhlbHZldGljYSIpKSArIGxhYnMoeCA9ICIgIiwgdGl0bGUgPSAiQ2h1cm4gYnkgZ2VuZGVyIikKI25vIGRpZmZlcmVuY2UgYnkgZ2VuZGVyCgoKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHg9Q29udHJhY3QsIGZpbGwgPSBmYWN0b3IoQ2h1cm4pKSkrIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArIHNjYWxlX2ZpbGxfbWNvbmZ1YW51YWwodmFsdWVzID0gYygiIzFiOWU3NyIsICIjZDk1ZjAyIikpICsgdGhlbWUoCiAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgdGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgIGZhbWlseT0iSGVsdmV0aWNhIikpICsgbGFicyh4ID0gIiAiLCB0aXRsZSA9ICJDb250cmFjdCB0eXBlIikgKyBjb29yZF9mbGlwKCkKI1dlIGhhdmUgc2lnbmlmaWNhbnRseSBoaWdoZXIgY2h1cm4gdGhhbiBmb3IgdGhvc2Ugb24gbW9udGgtdG8tbW9udGggY29udHJhY3RzCgoKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHg9ZmFjdG9yKFNlbmlvckNpdGl6ZW4pLCBmaWxsID0gZmFjdG9yKENodXJuKSkpKyBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjMWI5ZTc3IiwgIiNkOTVmMDIiKSkgKyB0aGVtZSgKICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICB0ZXh0PWVsZW1lbnRfdGV4dChzaXplPTE0LCAgZmFtaWx5PSJIZWx2ZXRpY2EiKSkgKyBsYWJzKHggPSAiICIsIHRpdGxlID0gIklzIGEgc2VuaW9yIGNpdGl6ZW4iKQojSGlnaGVyIGNodXJuIGZvciBTZW5pb3IgQ2l0aXplbnMKCgpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeD1QYXBlcmxlc3NCaWxsaW5nLCBmaWxsID0gZmFjdG9yKENodXJuKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiIzFiOWU3NyIsICIjZDk1ZjAyIikpICsgdGhlbWUoCiAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgdGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgIGZhbWlseT0iSGVsdmV0aWNhIikpICsgbGFicyh4ID0gIiAiLCB0aXRsZSA9ICJIYXMgcGFwZXJsZXNzIGJpbGxpbmciKQojaGlnaGVyIGNodXJuIHJhdGUgZm9yIHRob3NlIG9uIHBhcGVybGVzcyBiaWxsaW5nCgpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeD1QYXltZW50TWV0aG9kLCBmaWxsID0gZmFjdG9yKENodXJuKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiIzFiOWU3NyIsICIjZDk1ZjAyIikpICsgdGhlbWUoCiAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgdGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiwgIGZhbWlseT0iSGVsdmV0aWNhIikpICsgbGFicyh4ID0gIiAiLCB0aXRsZSA9ICJQYXltZW50IE1ldGhvZCIpICsgY29vcmRfZmxpcCgpIAojaGlnaGVyIGNodXJuIHJhdGUgZm9yIHRob3NlIHdobyBwYXkgYnkgZWxlY3Ryb25pYwojY2hlY2sgLSBsb3dlc3QgY2h1cm4gcmF0ZXMgYW1vbmcgdGhvc2Ugd2l0aAojYXV0b21hdGljIGJpbGxpbmcuCgpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeD1QaG9uZVNlcnZpY2UsIGZpbGwgPSBmYWN0b3IoQ2h1cm4pKSkgKyBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKyBsYWJzKHRpdGxlID0gIkJ5IHBob25lIHNlcnZpY2UiKQojbm8gZGlmZmVyZW5jZSBieSBwaG9uZSBzZXJ2aWNlCgoKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHg9UGFydG5lciwgZmlsbCA9IGZhY3RvcihDaHVybikpKSArIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiMxYjllNzciLCAiI2Q5NWYwMiIpKSArIHRoZW1lKAogICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgIHRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIsICBmYW1pbHk9IkhlbHZldGljYSIpKSArIGxhYnMoeCA9ICIgIiwgdGl0bGUgPSAiSGFzIGEgcGFydG5lciIpIAojc2xpZ2h0bHkgaGlnaGVyIGNodXJuIHJhdGUgZm9yIHNpbmdsZXMKCgpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeD1EZXBlbmRlbnRzLCBmaWxsID0gZmFjdG9yKENodXJuKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiIzFiOWU3NyIsICIjZDk1ZjAyIikpICsgdGhlbWUoCiAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgdGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiwgIGZhbWlseT0iSGVsdmV0aWNhIikpICsgbGFicyh4ID0gIiAiLCB0aXRsZSA9ICJIYXMgZGVwZW5kZW50cyIpICsgY29vcmRfZmxpcCgpIAojaGlnaGVyIGNodXJuIHJhdGUgZm9yIHRob3NlIHdpdGggbm8gZGVwZW5kZW50cwoKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHg9TXVsdGlwbGVMaW5lcywgZmlsbCA9IGZhY3RvcihDaHVybikpKSArIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArIGxhYnModGl0bGUgPSAiSGFzIG11bHRpcGxlIHBob25lIGxpbmVzIikKIyN2ZXJ5IGxpdHRsZSBkaWZmZXJlbmNlIGlmIGhhdmUgbXVsdGlwbGUgbGluZXMKCgpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeD1JbnRlcm5ldFNlcnZpY2UsIGZpbGwgPSBmYWN0b3IoQ2h1cm4pKSkgKyBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjMWI5ZTc3IiwgIiNkOTVmMDIiKSkgKyB0aGVtZSgKICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICB0ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyLCAgZmFtaWx5PSJIZWx2ZXRpY2EiKSkgKyBsYWJzKHggPSAiICIsIHRpdGxlID0gIkludGVybmV0IHNlcnZpY2UiKSArIGNvb3JkX2ZsaXAoKSAKI011Y2ggaGlnaGVyIGNodXJuIGlmIGhhdmUgZmliZXIgb3B0aWMgaW50ZXJuZXQgc2VydmljZQoKCmdncGxvdChkYXRhID0gZGYsIGFlcyh4PU9ubGluZVNlY3VyaXR5LCBmaWxsID0gZmFjdG9yKENodXJuKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsgY29vcmRfZmxpcCgpICsgbGFicyh0aXRsZSA9ICJPbmxpbmUgc2VjdXJpdHkiKQojaGlnaGVyIGNodXJuIGlmIGhhdmUgbm8gb25saW5lIHNlY3VyaXR5CgpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeD1PbmxpbmVCYWNrdXAsIGZpbGwgPSBmYWN0b3IoQ2h1cm4pKSkgKyBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKyBjb29yZF9mbGlwKCkgKyBsYWJzKHRpdGxlID0gIk9ubGluZSBiYWNrdXAiKQojaGlnaGVyIGNodXJuIGZvciB0aG9zZSB3aG8gaGF2ZSBubyBvbmxpbmUgYmFja3VwCgpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeD1EZXZpY2VQcm90ZWN0aW9uLCBmaWxsID0gZmFjdG9yKENodXJuKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsgY29vcmRfZmxpcCgpICsgbGFicyh0aXRsZSA9ICJEZXZpY2UgcHJvdGVjdGlvbiIpCiNoaWdoZXIgY2h1cm4gZm9yIHRob3NlIHdobyBoYXZlIG5vIGRldmljZSBwcm90ZWN0aW9uCgpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeD1UZWNoU3VwcG9ydCwgZmlsbCA9IGZhY3RvcihDaHVybikpKSArIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArIGNvb3JkX2ZsaXAoKSArIGxhYnModGl0bGUgPSAiVGVjaCBzdXBwb3J0IikKI2hpZ2hlciBjaHVybiBmb3IgdGhvc2Ugd2l0aCBubyB0ZWNoIHN1cHBvcnQKCmdncGxvdChkYXRhID0gZGYsIGFlcyh4PVN0cmVhbWluZ1RWLCBmaWxsID0gZmFjdG9yKENodXJuKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsgbGFicyh0aXRsZSA9ICJTdHJlYW1pbmcgVFYiKQojbm8gZGlmZmVyZW5jZSBhbW9uZyBUViBzdHJlYW1lcnMgLSBidXQgdmVyeSBsb3cgY2h1cm5lcnMgaWYgeW91IGRvbid0IGhhdmUgaW50ZXJuZXQgc2VydmljZQoKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHg9U3RyZWFtaW5nTW92aWVzLCBmaWxsID0gZmFjdG9yKENodXJuKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsgbGFicyh0aXRsZSA9ICJTdHJlYW1pbmcgbW92aWVzIikKI25vIGRpZmZlcmVuY2UgYW1vbmcgbW92aWUgc3RyZWFtZXJzIC0gYnV0IHZlcnkgbG93IGNodXJuZXJzIGlmIHlvdSBkb24ndCBoYXZlIGludGVybmV0IHNlcnZpY2UKYGBgCgoKYGBge3IgTXVsdGl2YXJpYXRlIEFuYWx5c2lzIC0gU2NhdHRlcnBsb3RzIGFuZCBsb2cgdHJhbnNmb3JtYXRpb259CgpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeD1Nb250aGx5Q2hhcmdlcywgeT10ZW51cmUsIGNvbG9yPWZhY3RvcihDaHVybikpKSArIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsgZ2VvbV9zbW9vdGgobWV0aG9kPWxtKSArIGxhYnModGl0bGUgPSAiICIpCiNsb3RzIG9mIHZlcnRpY2FsIGxpbmVzIC0gbm8gaG9yaXpvbnRhbCBwYXR0ZXJucwojc3VnZ2VzdHMgdGhhdCBtb250aGx5IGNoYXJnZXMgaGFzIHNvbWUgYXNzb2NpYXRpb24gd2l0aCBjaHVybiBhcyB0aGUgY2h1cm5lZCBjdXN0b21lcnMgYXJlIGhlYXZpbHkgcmVwcmVzZW50ZWQgb24gdGhlIGhpZ2hlciBlbmQgb2YgdGhlIG1vbnRobHkgY2hhcmdlcwojdGhlcmUgaXMgbW9zdCBsaWtlbHkgYSBoaWdoZXIgbGlrZWxpaG9vZCBvZiBjaHVybiBvbmNlIHlvdSByZWFjaCBhIGNlcnRhaW4gcG9pbnQgb24gdGhlIG1vbnRobHkgZmVlCgoKI2JlbGlldmUgdGhhdCB0b3RhbCBjaGFyZ2VzIGNhbiBhbHNvIGJlIGEgcHJveHkgZm9yIHRlbnVyZS1oZW5jZSB0aGUgdHJpYW5nbGUgbG9va2luZyBzY2F0dGVycGxvdApnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeD1Ub3RhbENoYXJnZXMsIHk9dGVudXJlLCBjb2xvcj1mYWN0b3IoQ2h1cm4pKSkgKyBnZW9tX3BvaW50KGFscGhhID0gMC41KSArZ2VvbV9zbW9vdGgobWV0aG9kPWxtKSArIGxhYnModGl0bGUgPSAiICIpCgojaGlzdG9ncmFtIG9mIHRlbnVyZQpoaXN0KGRmJHRlbnVyZSkKCiNsb2cgdHJhbnNmb3JtYXRpb24KZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHg9TW9udGhseUNoYXJnZXMsIHk9dGVudXJlLCBjb2xvcj1mYWN0b3IoQ2h1cm4pKSkgKyBnZW9tX3BvaW50KGFscGhhID0gMC41KSArIGdlb21fc21vb3RoKG1ldGhvZD1sbSkgKyBzY2FsZV94X2xvZzEwKCkgKyBzY2FsZV95X2xvZzEwKCkgKyBsYWJzKHRpdGxlID0gIkxvZyB0cmFuc2Zvcm1hdGlvbiIpCgpnZ3Bsb3QoZGF0YSA9IGRmLCBhZXMoeD1Ub3RhbENoYXJnZXMsIHk9dGVudXJlLCBjb2xvcj1mYWN0b3IoQ2h1cm4pKSkgKyBnZW9tX3BvaW50KGFscGhhID0gMC41KSArZ2VvbV9zbW9vdGgobWV0aG9kPWxtKSArIHNjYWxlX3hfbG9nMTAoKSArIGxhYnModGl0bGUgPSAiTG9nIHRyYW5zZm9ybWF0aW9uIikKCiNUZW51cmUgYW5kIHRvdGFsIGNoYXJnZXMgYXJlIHBvc3NpYmx5IGNvbGxpbmVhciBvciBib3JkZXJpbmcgb24gY29sbGluZWFyaXR5IQpgYGAKCgoKCgpgYGB7ciAyLVdheSBDcm9zcyBUYWJ1bGF0aW9uIFRhYmxlcyB0byBjb21wYXJlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyAtIEludGVybmV0IFNlcnZpY2V9CgojSW50ZXJuZXQgU2VydmljZQpDcm9zc1RhYmxlKGRmJEludGVybmV0U2VydmljZSwgZGYkQ2h1cm4sIGRpZ2l0cz0yLCBwcm9wLmMgPSBUUlVFLAogIHByb3AuciA9IFRSVUUsIHByb3AudCA9IEZBTFNFLCBjaGlzcSA9IEZBTFNFLCBmb3JtYXQgPSAiU0FTIiwgZXhwZWN0ZWQgPSBGQUxTRSkKIwoKdGFibGUoZGYkSW50ZXJuZXRTZXJ2aWNlLCBkZiRDaHVybikKCnJvdW5kKGFkZG1hcmdpbnMocHJvcC50YWJsZSh0YWJsZShkZiRJbnRlcm5ldFNlcnZpY2UsIGRmJENodXJuKSkpLDMpCgoKI1N0YXRpc3RpY2FsIHRlc3QKY2hpc3EudGVzdChkZiRDaHVybiwgZGYkSW50ZXJuZXRTZXJ2aWNlKQoKCmBgYAoKCmBgYHtyIDItV2F5IENyb3NzIFRhYnVsYXRpb24gVGFibGVzIHRvIGNvbXBhcmUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIC0gQ29udHJhY3R9CiNDb250cmFjdApDcm9zc1RhYmxlKGRmJENvbnRyYWN0LCBkZiRDaHVybiwgZGlnaXRzPTIsIHByb3AuYyA9IFRSVUUsCiAgcHJvcC5yID0gVFJVRSwgcHJvcC50ID0gRkFMU0UsIGNoaXNxID0gRkFMU0UsIGZvcm1hdCA9ICJTQVMiLCBleHBlY3RlZCA9IEZBTFNFKQojCgp0YWJsZShkZiRDb250cmFjdCwgZGYkQ2h1cm4pCgpyb3VuZChhZGRtYXJnaW5zKHByb3AudGFibGUodGFibGUoZGYkQ29udHJhY3QsIGRmJENodXJuKSkpLDMpCgojc3RhdGlzdGljYWwgdGVzdApjaGlzcS50ZXN0KGRmJENodXJuLCBkZiRDb250cmFjdCkKYGBgCgpgYGB7ciBDcmVhdGUgYmlucyBmb3IgbW9udGhseSBjaGFyZ2VzIHRvIGV4cGxvcmUgZGF0YSwgZWNobz1UUlVFfQoKI0xldCdzIHRha2UgYWdlIGFuZCBwdXQgaXQgaW50byBidWNrZXRzCiNsZXQncyBnZXQgbWluIGFuZCBtYXggTW9udGhseSBDaGFyZ2UgCm1pbihkZiRNb250aGx5Q2hhcmdlcykKbWF4KGRmJE1vbnRobHlDaGFyZ2VzKQptZWFuKGRmJE1vbnRobHlDaGFyZ2VzKQptZWRpYW4oZGYkTW9udGhseUNoYXJnZXMpCgojcXVhcnRpbGVzCmdncGxvdChkZiwgYWVzKHk9TW9udGhseUNoYXJnZXMpKSArIGdlb21fYm94cGxvdCgpCgojcXVhbnRpbGVzIC0gdGFrZXMgYSB2ZWN0b3Igb2YgcHJvcG9ydGlvbnMKcXVhbnRpbGUoZGYkTW9udGhseUNoYXJnZXMsIHByb2JzID0gYygwLCAwLjIsIDAuNCwgMC42LCAwLjgsMSkpCgojY3JlYXRlIG1vbnRobHkgZmVlIGJhbmRzCmRmJG1vbnRobHlfZmVlX2JpbiA8LSBjdXQoZGYkTW9udGhseUNoYXJnZXMsIAogICAgICAgICAgICAgICAgICAgYnJlYWtzPSA1LCAKICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJiaW4xIiwgImJpbjIiLCAiYmluMyIsICJiaW40IiwgImJpbjUiKSwgCiAgICAgICAgICAgICAgICAgICByaWdodD1GQUxTRSkKCnRhYmxlKGRmJG1vbnRobHlfZmVlX2JpbikgI2NoZWNrIHJlc3VsdHMKCgojY2hlY2sgYXZlcmFnZXMgYWNyb3NzIHRoZSBkaWZmZXJlbnQgZmVlIGJhbmRzCihmZWVzIDwtIGRmICU+JSAgCiAgZ3JvdXBfYnkobW9udGhseV9mZWVfYmluKSAlPiUgCiAgc3VtbWFyaXplKGF2Z19tb250aGx5X2ZlZSA9IGZvcm1hdChyb3VuZChtZWFuKE1vbnRobHlDaGFyZ2VzKSwyKSksCiAgICAgICAgICAgIG1lZGlhbl9tb250aGx5X2ZlZSA9IGZvcm1hdChyb3VuZChtZWRpYW4oTW9udGhseUNoYXJnZXMpLDIpKSkpCgoKI3Zpc3VhbGl6ZSBmZWUgYmFuZHMKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHg9bW9udGhseV9mZWVfYmluLCBmaWxsID0gZmFjdG9yKENodXJuKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpCmBgYAoKIyNTdGVwIDM6IE1hbmFnZSBkYXRhCgoKYGBge3IgQ2hlY2sgZm9yIE1pc3NpbmcgRGF0YSAtIFJlbW92ZSBNaXNzaW5nIERhdGF9CiNhbnkgbWlzc2luZyBkYXRhPwoKI1VzaW5nIFZJTSBwYWNrYWdlCm1pc3NpbmcgPC0gYWdncihkZiwgcHJvcCA9IFRSVUUsIGJhcnMgPSBUUlVFKSAjIC0gc29tZSBtaXNzaW5nIHZhbHVlcwoKc3VtbWFyeShtaXNzaW5nKQoKIyMgU2hvdyBjYXNlcyB3aXRoIG1pc3NpbmcgdmFsdWVzCihtaXNzaW5nLmRmIDwtIGRmWyFjb21wbGV0ZS5jYXNlcyhkZiksXSkgIzExIHJvd3MKI1NvbWUgdG90YWwgY2hhcmdlcyBtaXNzaW5nCgojV2UgY2FuIHJlbW92ZSBtaXNzaW5nIHJvd3MgLSByZW1vdmluZyB0aGVtIHdvbid0IGFmZmVjdCBvdXIgcmVzdWx0cyB0aGF0IG11Y2ggLSB0aGlzIGlzIHRoZSBjb2RlIHRvIHJlbW92ZSBiZWxvdwpkZiA8LSBkZltjb21wbGV0ZS5jYXNlcyhkZiksXSAKCgojY2hlY2sgcmVzdWx0cwpzdW0oaXMubmEoZGYkVG90YWxDaGFyZ2VzKSkgI2FsbCBnb25lCgojdmlzdWFsaXplIGVudGlyZSBkYXRhZnJhbWUgYWdhaW4KYWdncihkZiwgcHJvcCA9IFRSVUUsIGJhcnMgPSBUUlVFKQpgYGAKCgpgYGB7ciBDaGVjayBmb3IgZHVwbGljYXRlIGRhdGF9CiNhbnkgZHVwbGljYXRlIHJvd3MgLQphbnlEdXBsaWNhdGVkKGRmKQoKI2FueSBkdXBsaWNhdGUgY29sdW1ucyAtCm5hbWVzKGRmKVtkdXBsaWNhdGVkKG5hbWVzKGRmKSldCgpkZltuYW1lcyhkZilbIWR1cGxpY2F0ZWQobmFtZXMoZGYpKV1dCgpgYGAKClRoZXJlIHdlcmUgbm8gZHVwbGljYXRlcyB0byB3b3JyeSBhYm91dAoKCgojIyBTdGVwIDQ6IFN0YXRpc3RpY2FsIGFuYWx5c2lzICYgdW5kZXJzdGFuZGluZyByZWxhdGlvbnNoaXBzCgpOb3cgd2Ugd2FudCB0byBkaWcgZGVlcGVyCldlIGhhdmUgc29tZSBpZGVhcyBvZiB3aGF0IHRvIGxvb2sgZm9yIHRoYW5rcyB0byBvdXIgRURBCgoKYGBge3IgUGFpcnMgZnVuY3Rpb259CiNwYWlycy5wYW5lbHMKZGYgJT4lIAogIHNlbGVjdF9pZihpcy5udW1lcmljKSAlPiUgCiAgc2NhbGUoKSAlPiUgCiAgICBwYWlycy5wYW5lbHMoKQoKI1RlbnVyZSBhbmQgVG90YWxDaGFyZ2VzIGhhcyBhIHZlcnkgaGlnaCBjb3JyZWxhdGlvbiBjb2VmZmljaWVudCEKI0Fsc28gc2hvd3MgdXMgdGhhdCBTZW5pb3JDaXRpemVuIGlzIGNvZGVkIGFzIGEgbnVtZXJpY2FsIHZhcmlhYmxlICgwIG9yIDEpCiNNb250aGx5IGNoYXJnZXMgYW5kIFRvdGFsQ2hhcmdlcyBoYXMgYSBoaWdoIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IGFsc28uCmBgYAoKCgpgYGB7ciBDb3JyZWxhdGlvbiBtYXRyaXggZm9yIG51bWVyaWMgdmFyaWFibGVzfQoKI0NvcnJlbGF0aW9uIE1hdHJpeApkZiAlPiUgCiAgc2VsZWN0X2lmKGlzLm51bWVyaWMpICU+JSAKICBjb3IoKSAlPiUgCiAgICBjb3JycGxvdCh0eXBlID0gInVwcGVyIiwgaW5zaWcgPSAiYmxhbmsiLCBhZGRDb2VmLmNvbCA9ICJibGFjayIsIGRpYWc9RkFMU0UpCiNtb250aGx5IGNoYXJnZXMgaXMgaGlnaGx5LWNvcnJlbGF0ZWQgd2l0aCB0b3RhbCBjaGFyZ2VzIHdoaWNoIG1ha2VzIHNlbnNlLiBhdCAwLjY1LgojIEkgdGFrZSB0aGVzZSByZXN1bHRzIHdpdGggYSBncmFpbiBvZiBzYWx0LgoKI3RvdGFsIGNoYXJnZXMgYW5kIHRlbnVyZSBoYXMgYSBoaWdoIGNvcnJlbGF0aW9uLjgzCiN3aGljaCBpcyBib3JkZXJpbmcgb24gY29sbGluZWFyaXR5CmBgYAoKCmBgYHtyIFJlY29kZSBjaHVybiBjaGFyYWN0ZXIgdmFyaWFibGUgaW50byBhIG51bWVyaWMgdmFyaWFibGV9CiNjaGVjayBzdHJ1Y3R1cmUgb2YgQ2h1cm4gdmFyaWFibGUgd2hpY2ggaXMgYSBjaGFyYWN0ZXIgdmFyaWFibGUKc3RyKGRmJENodXJuKQoKI0dpdmVzIHRoZSBzcGxpdCBwcm9wb3J0aW9uIG9mIG5vdCBjaHVybmVkIHZzIGNodXJuCihjaHVyblJhdGUgPC0gcm91bmQocHJvcC50YWJsZSh0YWJsZShkZiRDaHVybikpLDUpKQooY2h1cm5fcmF0ZV9vdmVyYWxsIDwtIGNodXJuUmF0ZVtbMl1dKQoKIyByZWNvZGUgY2h1cm4gdmFyaWFibGUgaW50byBhIG51bWVyaWMgdmFyaWFibGUKZGYgPC0gZGYgJT4lIAogICAgICBtdXRhdGUoQ2h1cm4gPSBpZmVsc2UoQ2h1cm4gPT0gIlllcyIsIDEsIDApKSAKCiMgUmUtY2hlY2sgdG90YWwgYW5kIGZyYWN0aW9uIG9mIGEgY2h1cm4gb3V0Y29tZSB2YXJpYWJsZQpkZiAlPiUKc3VtbWFyaXplKHRvdGFsID0gbigpLApwZXJjZW50X2NodXJuID0gbWVhbihDaHVybiA9PSAxKSkKYGBgCgoKYGBge3IgQ2h1cm4gcmF0ZSBieSBjb250cmFjdCB0eXBlLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojZGF0YSBmcmFtZSBncm91cGVkIGJ5IGNvbnRyYWN0CmNvbnRyYWN0LmRmIDwtIGRmICU+JSAKICBncm91cF9ieShDb250cmFjdCkgJT4lIAogIHN1bW1hcml6ZSh0b3RhbF9jb3VudCA9IG4oKSwKICAgIHRvdGFsX2NodXJucyA9IHN1bShDaHVybikpICU+JSAKICBtdXRhdGUoY2h1cm5fcmF0ZSA9IHRvdGFsX2NodXJucy90b3RhbF9jb3VudCkKCmNvbnRyYWN0LmRmICNjaGVjayByZXN1bHRzCgojY2hlY2sgY2h1cm4gY29sdW1ucwpzdW0oY29udHJhY3QuZGYkdG90YWxfY2h1cm5zKQpzdW0oY29udHJhY3QuZGYkdG90YWxfY291bnQpCgojYWRkIGhpZ2hsaWdodCBmbGFnIGNvbHVtbgpjb250cmFjdC5kZiA8LSBjb250cmFjdC5kZiAlPiUgCm11dGF0ZShoaWdobGlnaHRfZmxhZyA9CiAgICBpZmVsc2UoY2h1cm5fcmF0ZSA+IGNodXJuX3JhdGVfb3ZlcmFsbCwgMSwgMCkpCgojY2hlY2sgcmVzdWx0cwpoZWFkKGNvbnRyYWN0LmRmJGhpZ2hsaWdodF9mbGFnKQoKI3Bsb3QgcmVzcG9uc2UgcmF0ZSBieSBjYXQgdmFyCmdncGxvdChkYXRhPWNvbnRyYWN0LmRmLCBhZXMoeD1yZW9yZGVyKENvbnRyYWN0LCBjaHVybl9yYXRlKSwgeT1jaHVybl9yYXRlLAogIGZpbGwgPSBmYWN0b3IoaGlnaGxpZ2h0X2ZsYWcpKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIGdlb21faGxpbmUoeWludGVyY2VwdD1jaHVybl9yYXRlX292ZXJhbGwsIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvciA9ICJibGFjayIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCcjNTk1OTU5JywgJyNlNDFhMWMnKSkgKwogIGxhYnMoeCA9ICcgJywgeSA9ICdDaHVybiBSYXRlJywgdGl0bGUgPSBzdHJfYygiQ2h1cm4gcmF0ZSBieSBjb250cmFjdCIpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgoKYGBge3IgQ2h1cm4gcmF0ZSBieSBtb250aGx5IGZlZSBsZXZlbH0KCiNkYXRhIGZyYW1lIGdyb3VwZWQgYnkgZmVlCm1vbnRobHkuZmVlLmRmIDwtIGRmICU+JSAKICBncm91cF9ieShtb250aGx5X2ZlZV9iaW4pICU+JSAKICBzdW1tYXJpemUodG90YWxfY291bnQgPSBuKCksCiAgICB0b3RhbF9jaHVybnMgPSBzdW0oQ2h1cm4pKSAlPiUgCiAgbXV0YXRlKGNodXJuX3JhdGUgPSB0b3RhbF9jaHVybnMvdG90YWxfY291bnQpCgpoZWFkKG1vbnRobHkuZmVlLmRmKSAjY2hlY2sgcmVzdWx0cwoKI2NoZWNrIGNodXJuIGNvbHVtbnMKc3VtKG1vbnRobHkuZmVlLmRmJHRvdGFsX2NodXJucykKc3VtKG1vbnRobHkuZmVlLmRmJHRvdGFsX2NvdW50KQoKI2FkZCBoaWdobGlnaHQgZmxhZyBjb2x1bW4KbW9udGhseS5mZWUuZGYgPC0gbW9udGhseS5mZWUuZGYgJT4lIAptdXRhdGUoaGlnaGxpZ2h0X2ZsYWcgPQogICAgaWZlbHNlKGNodXJuX3JhdGUgPiBjaHVybl9yYXRlX292ZXJhbGwsIDEsIDApKQoKI2NoZWNrIHJlc3VsdHMKaGVhZChtb250aGx5LmZlZS5kZiRoaWdobGlnaHRfZmxhZykKCiNwbG90IHJlc3BvbnNlIHJhdGUgYnkgZmVlIGRpZmZlcmVuY2UKcGxvdC5mZWUuZGlmZiA8LSBnZ3Bsb3QoZGF0YT1tb250aGx5LmZlZS5kZiwgYWVzKHg9cmVvcmRlcihtb250aGx5X2ZlZV9iaW4sIGNodXJuX3JhdGUpLCB5PWNodXJuX3JhdGUsCiAgZmlsbCA9IGZhY3RvcihoaWdobGlnaHRfZmxhZykpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PWNodXJuX3JhdGVfb3ZlcmFsbCwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yID0gImJsYWNrIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKSArCiAgY29vcmRfZmxpcCgpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoJyM1OTU5NTknLCAnI2U0MWExYycpKSArCiAgbGFicyh4ID0gJyAnLCB5ID0gJ0NodXJuIFJhdGUnLCB0aXRsZSA9IHN0cl9jKCJDaHVybiByYXRlIGJ5IG1vbnRobHkgZmVlIikpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpwbG90LmZlZS5kaWZmCgpgYGAKCgoKYGBge3IgQ2h1cm4gcmF0ZSBieSBJbnRlcm5ldFNlcnZpY2V9CgoKI2RhdGEgZnJhbWUgZ3JvdXBlZCBieSBmZWUKaW50ZXJuZXQuZGYgPC0gZGYgJT4lIAogIGdyb3VwX2J5KEludGVybmV0U2VydmljZSkgJT4lIAogIHN1bW1hcml6ZSh0b3RhbF9jb3VudCA9IG4oKSwKICAgIHRvdGFsX2NodXJucyA9IHN1bShDaHVybikpICU+JSAKICBtdXRhdGUoY2h1cm5fcmF0ZSA9IHRvdGFsX2NodXJucy90b3RhbF9jb3VudCkKCmhlYWQoaW50ZXJuZXQuZGYpICNjaGVjayByZXN1bHRzCgojY2hlY2sgY2h1cm4gY29sdW1ucwpzdW0oaW50ZXJuZXQuZGYkdG90YWxfY2h1cm5zKQpzdW0oaW50ZXJuZXQuZGYkdG90YWxfY291bnQpCgojYWRkIGhpZ2hsaWdodCBmbGFnIGNvbHVtbgppbnRlcm5ldC5kZiA8LSBpbnRlcm5ldC5kZiAlPiUgCm11dGF0ZShoaWdobGlnaHRfZmxhZyA9CiAgICBpZmVsc2UoY2h1cm5fcmF0ZSA+IGNodXJuX3JhdGVfb3ZlcmFsbCwgMSwgMCkpCgojY2hlY2sgcmVzdWx0cwpoZWFkKGludGVybmV0LmRmJGhpZ2hsaWdodF9mbGFnKQoKI3Bsb3QgcmVzcG9uc2UgcmF0ZSBieSBmZWUgZGlmZmVyZW5jZQpnZ3Bsb3QoZGF0YT1pbnRlcm5ldC5kZiwgYWVzKHg9cmVvcmRlcihJbnRlcm5ldFNlcnZpY2UsIGNodXJuX3JhdGUpLCB5PWNodXJuX3JhdGUsCiAgZmlsbCA9IGZhY3RvcihoaWdobGlnaHRfZmxhZykpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PWNodXJuX3JhdGVfb3ZlcmFsbCwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yID0gImJsYWNrIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKSArCiAgY29vcmRfZmxpcCgpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoJyM1OTU5NTknLCAnI2U0MWExYycpKSArCiAgbGFicyh4ID0gJyAnLCB5ID0gJ0NodXJuIFJhdGUnLCB0aXRsZSA9IHN0cl9jKCJDaHVybiByYXRlIGJ5IGludGVybmV0IHNlcnZpY2UiKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgoKCmBgYHtyIENodXJuIHJhdGUgYnkgUGF5bWVudCBNZXRob2R9CgojZGF0YSBmcmFtZSBncm91cGVkIGJ5IHBheW1lbnQgbWV0aG9kCnB5bXQubWV0aG9kLmRmIDwtIGRmICU+JSAKICBncm91cF9ieShQYXltZW50TWV0aG9kKSAlPiUgCiAgc3VtbWFyaXplKHRvdGFsX2NvdW50ID0gbigpLAogICAgdG90YWxfY2h1cm5zID0gc3VtKENodXJuKSkgJT4lIAogIG11dGF0ZShjaHVybl9yYXRlID0gdG90YWxfY2h1cm5zL3RvdGFsX2NvdW50KQoKaGVhZChweW10Lm1ldGhvZC5kZikgI2NoZWNrIHJlc3VsdHMKCiNjaGVjayBjaHVybiBjb2x1bW5zCnN1bShweW10Lm1ldGhvZC5kZiR0b3RhbF9jaHVybnMpCnN1bShweW10Lm1ldGhvZC5kZiR0b3RhbF9jb3VudCkKCiNhZGQgaGlnaGxpZ2h0IGZsYWcgY29sdW1uCnB5bXQubWV0aG9kLmRmIDwtIHB5bXQubWV0aG9kLmRmICU+JSAKbXV0YXRlKGhpZ2hsaWdodF9mbGFnID0KICAgIGlmZWxzZShjaHVybl9yYXRlID4gY2h1cm5fcmF0ZV9vdmVyYWxsLCAxLCAwKSkKCiNjaGVjayByZXN1bHRzCmhlYWQocHltdC5tZXRob2QuZGYkaGlnaGxpZ2h0X2ZsYWcpCgojcGxvdCByZXNwb25zZSByYXRlIGJ5IGZlZSBkaWZmZXJlbmNlCmdncGxvdChkYXRhPXB5bXQubWV0aG9kLmRmLCBhZXMoeD1yZW9yZGVyKFBheW1lbnRNZXRob2QsIGNodXJuX3JhdGUpLCB5PWNodXJuX3JhdGUsCiAgZmlsbCA9IGZhY3RvcihoaWdobGlnaHRfZmxhZykpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PWNodXJuX3JhdGVfb3ZlcmFsbCwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yID0gImJsYWNrIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKSArCiAgY29vcmRfZmxpcCgpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoJyM1OTU5NTknLCAnI2U0MWExYycpKSArCiAgbGFicyh4ID0gJyAnLCB5ID0gJ0NodXJuIFJhdGUnLCB0aXRsZSA9IHN0cl9jKCJDaHVybiByYXRlIGJ5IHBheW1lbnQgbWV0aG9kIikpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpgYGAKCldlIGhhdmUgdW5jb3ZlcmVkIHNvbWUgaW5zaWdodHMgc28gZmFyIHRocm91Z2ggZGF0YSB2aXN1YWxpemF0aW9uIGFuZCBzdGF0aXN0aWNhbCB0ZXN0czoKCkxvdyBjaHVybmVycyBjaGFyYWN0ZXJpc3RpY3M6Ci1WZXJ5IGxvdyBjaHVybiBmb3IgdGhvc2UgdXNlcnMgd2hvIGRvbuKAmXQgaGF2ZSB0aGUgaW50ZXJuZXQgc2VydmljZSEhISEKCkhpZ2ggY2h1cm5lcnMgY2hhcmFjdGVyaXN0aWNzOgotRmliZXIgb3B0aWMgc2VydmljZQotU2VuaW9yIENpdGl6ZW5zCi1TbGlnaHRseSBoaWdoZXIgZm9yIHNpbmdsZXMgJiB0aG9zZSB3aXRoIG5vIGRlcGVuZGVudHMgKGJ1dCBub3Qgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCkKLVBhcGVybGVzcyBiaWxsaW5nIG9yIGVsZWN0cm9uaWMgY2hlY2sKLU1vbnRoLXRvLW1vbnRoIGNvbnRyYWN0cwoKTW9udGhseSBjaGFyZ2VzIGhhcyBzb21lIGFzc29jaWF0aW9uIHdpdGggY2h1cm4gYXMgdGhlIGNodXJuZWQgY3VzdG9tZXJzIGFyZSBoZWF2aWx5IHJlcHJlc2VudGVkIG9uIHRoZSBoaWdoZXIgZW5kIG9mIHRoZSBtb250aGx5IGNoYXJnZXMuIFRoZXJlIGlzIG1vc3QgbGlrZWx5IGEgaGlnaGVyIGxpa2VsaWhvb2Qgb2YgY2h1cm4gb25jZSB5b3UgcmVhY2ggYSBjZXJ0YWluIHBvaW50IG9uIHRoZSBtb250aGx5IGZlZS4KCgpOb3cgd2UgaGF2ZSB0byBkZWNpZGUgd2hldGhlciB3ZSBzaG91bGQgZG8gc29tZSBkYXRhIG1vZGVsaW5nLgoKCgojIyBTdGVwIDVBOiBEYXRhIHByZS1wcm9lc3NpbmcKCgpgYGB7ciBHZXQgZGF0YSByZWFkeSBmb3IgbW9kZWxpbmd9CiNsZXQncyB1c2UgYSBmcmVzaCBkYXRhc2V0CmRmX3JhdyA8LSByZWFkLmNzdigifi9Eb2N1bWVudHMvR2l0SHViL01hY2hpbmUtTGVhcm5pbmctUi9NYWNoaW5lLUxlYXJuaW5nLVIvZGF0YXNldHMvVGVsY28tQ3VzdG9tZXItQ2h1cm4uY3N2IikKCiNzdHIoZGZfcmF3KSAjY2hlY2sgcmVzdWx0cwoKI1JlbW92ZSByb3dzIHdpdGggbWlzc2luZyBvYnNlcnZhdGlvbnMKZGZfcmF3IDwtIGRmX3Jhd1tjb21wbGV0ZS5jYXNlcyhkZl9yYXcpLF0gCgojZ2V0IHJpZCBvZiBjdXN0b21lcklEIGNvbHVtbiAtIHdlIGRvbid0IG5lZWQgaXQgZm9yIG1vZGVsaW5nCiNsZXQncyBqdXN0IGtlZXAgdGhlIHZhcmlhYmxlcyB3ZSBuZWVkCmRmX3ByZW1vZGVsIDwtIGRmX3JhdyAlPiUgCiAgc2VsZWN0KGdlbmRlciwgU2VuaW9yQ2l0aXplbiwgUGFydG5lciwgRGVwZW5kZW50cywgdGVudXJlLCBQaG9uZVNlcnZpY2UsIE11bHRpcGxlTGluZXMsIEludGVybmV0U2VydmljZSwgT25saW5lU2VjdXJpdHksIE9ubGluZUJhY2t1cCwgRGV2aWNlUHJvdGVjdGlvbiwgVGVjaFN1cHBvcnQsIFN0cmVhbWluZ1RWLCBTdHJlYW1pbmdNb3ZpZXMsQ29udHJhY3QsUGFwZXJsZXNzQmlsbGluZywgUGF5bWVudE1ldGhvZCwgTW9udGhseUNoYXJnZXMsIFRvdGFsQ2hhcmdlcywgQ2h1cm4pCgoKI21lcmdlIHVubmVjZXNzYXJ5IGxldmVscyAobm8gaW50ZXJuZXQpIGFuZCByZWNvZGUgdmFyaWFibGVzIHRvIGR1bW15IHZhcmlhYmxlCmRmX2R1bW15IDwtIGRmX3ByZW1vZGVsICU+JSAKICAgIG11dGF0ZShnZW5kZXIgPSBpZmVsc2UoZ2VuZGVyID09ICJGZW1hbGUiLCAxLCAwKSwKICAgICAgICAgICBQYXJ0bmVyID0gaWZlbHNlKFBhcnRuZXIgPT0gIlllcyIsIDEsIDApLAogICAgICAgICAgIERlcGVuZGVudHMgPSBpZmVsc2UoRGVwZW5kZW50cyA9PSAiWWVzIiwgMSwgMCksCiAgICAgICAgICAgUGhvbmVTZXJ2aWNlID0gaWZlbHNlKFBob25lU2VydmljZSA9PSAiWWVzIiwgMSwgMCksCiAgICAgICAgICAgTXVsdGlwbGVMaW5lcyA9IGlmZWxzZShNdWx0aXBsZUxpbmVzID09ICJZZXMiLCAxLCAwKSwKICAgICAgICAgIE9ubGluZVNlY3VyaXR5ID0gaWZlbHNlKE9ubGluZVNlY3VyaXR5ICA9PSAiWWVzIiwgMSwgMCksCiAgICAgICAgICBPbmxpbmVCYWNrdXAgPSBpZmVsc2UoT25saW5lQmFja3VwICA9PSAiWWVzIiwgMSwgMCksCiAgICAgICAgICBEZXZpY2VQcm90ZWN0aW9uID0gaWZlbHNlKERldmljZVByb3RlY3Rpb24gID09ICJZZXMiLCAxLCAwKSwKICAgICAgICAgIFRlY2hTdXBwb3J0ID0gaWZlbHNlKFRlY2hTdXBwb3J0ICA9PSAiWWVzIiwgMSwgMCksCiAgICAgICAgICBTdHJlYW1pbmdUViA9IGlmZWxzZShTdHJlYW1pbmdUViAgPT0gIlllcyIsIDEsIDApLAogICAgICAgICAgU3RyZWFtaW5nTW92aWVzID0gaWZlbHNlKFN0cmVhbWluZ01vdmllcyAgPT0gIlllcyIsIDEsIDApLAogICAgICAgICAgUGFwZXJsZXNzQmlsbGluZyA9IGlmZWxzZShQYXBlcmxlc3NCaWxsaW5nICA9PSAiWWVzIiwgMSwgMCksCiAgICAgICAgICBDaHVybiA9IGlmZWxzZShDaHVybiA9PSAiWWVzIiwgMSwgMCkpCgojY2hlY2sgcmVzdWx0cwpzdHIoZGZfZHVtbXkpCgojcmVjb2RlIG51bWVyaWMgdmFyaWFibGVzIGludG8gZmFjdG9yIHZhcmlhYmxlcwpkZl9kdW1teSA8LSBkZl9kdW1teSAlPiUgCiAgbXV0YXRlX2F0KGMoImdlbmRlciIsICJTZW5pb3JDaXRpemVuIiwgIlBhcnRuZXIiLCAiRGVwZW5kZW50cyIsICJQaG9uZVNlcnZpY2UiLCAiTXVsdGlwbGVMaW5lcyIsICJJbnRlcm5ldFNlcnZpY2UiLCAiT25saW5lU2VjdXJpdHkiLCAiT25saW5lQmFja3VwIiwgIkRldmljZVByb3RlY3Rpb24iLCAiVGVjaFN1cHBvcnQiLCAiU3RyZWFtaW5nVFYiLCAiU3RyZWFtaW5nTW92aWVzIiwiQ29udHJhY3QiLCJQYXBlcmxlc3NCaWxsaW5nIiwgIlBheW1lbnRNZXRob2QiLCAiQ2h1cm4iKSwgLmZ1bnMgPSBmYWN0b3IpCgojY2hlY2sgcmVzdWx0cwpzdHIoZGZfZHVtbXkpCgpgYGAKCgpgYGB7ciBDaGVjayBjaHVybiB2YXJpYWJsZSBpZiBuZWVkZWR9CnN0cihkZl9kdW1teSRDaHVybikgI2NoZWNrIGNodXJuIHZhcmlhYmxlCgp0YWJsZShkZl9kdW1teSRDaHVybikKYGBgCgoKCgoKIyMgUGFydCA1QjogTW9kZWxpbmcgLSBMb2dpc3RpYyBSZWdyZXNzaW9uCgoKCmBgYHtyIFRyYWluIGFuZCB0ZXN0IGRhdGEgc3BsaXR9CgojIENoZWNrIHRvdGFsIG51bWJlciBvZiByb3dzIGluIG91ciBkYXRhc2V0IGJlZm9yZSBzcGxpdHRpbmcKbnJvdyhkZl9kdW1teSkKCiNTZXQgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5CnNldC5zZWVkKDEyMykgCgojU3BsaXQgdGhlIGRhdGFzZXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBzZXRzIGF0IDcwOjMwIHJhdGlvIHVzaW5nIGNhVG9vbHMgcGFja2FnZQpzcGxpdCA8LSBzYW1wbGUuc3BsaXQoZGZfZHVtbXkkQ2h1cm4sIFNwbGl0UmF0aW8gPSAwLjcpCnRyYWluX2RhdGEgPC0gc3Vic2V0KGRmX2R1bW15LCBzcGxpdCA9PSBUUlVFKQp0ZXN0X2RhdGEgPC0gc3Vic2V0KGRmX2R1bW15LCBzcGxpdCA9PSBGQUxTRSkKCiNDaGVjayBpZiBkaXN0cmlidXRpb24gb2YgcGFydGl0aW9uIGRhdGEgaXMgY29ycmVjdCBmb3IgdHJhaW4gYW5kIHRlc3Qgc2V0CnByb3AudGFibGUodGFibGUodHJhaW5fZGF0YSRDaHVybikpCnByb3AudGFibGUodGFibGUodGVzdF9kYXRhJENodXJuKSkgCgpgYGAKCgpgYGB7ciBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIDEgLSBCYXNlbGluZSBNb2RlbH0KCnNldC5zZWVkKDEpICAjIHNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKCiNVc2UgZ2xtIGZ1bmN0aW9uIGZyb20gZ2xtbmV0IHBhY2thZ2UgdG8gY3JlYXRlIG1vZGVsCm1vZGVsXzAxX2dsbSA8LSBnbG0oZm9ybXVsYSA9IENodXJuIH4gLiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGEsIGZhbWlseSA9ICJiaW5vbWlhbCIpCiAKI3VzZSB0byBkZWJ1ZyBpZiBnbG0gZnVuY3Rpb24gbm90IHdvcmtpbmcgICAKIyB0cmFpbl9kYXRhICU+JQojICAgc2VsZWN0X2lmKGlzLmZhY3RvcikgJT4lCiMgICBsYXBwbHkodGFibGUpCiAgICAgICAgICAgICAgICAgICAgICAgICAKI1ByaW50IHRoZSBtb2RlbCBvdXRwdXQKc3VtbWFyeShtb2RlbF8wMV9nbG0pCmBgYAoKCgpUaGUgT2RkcyBSYXRpbyBhbmQgUHJvYmFiaWxpdHkgb2YgZWFjaCB4IHZhcmlhYmxlIGlzIGNhbGN1bGF0ZWQgYmFzZWQgb24gdGhlIGZvcm11bGFlLCBPZGRzIFJhdGlvID0gZXhwKENvLWVmZmljaWVudCBlc3RpbWF0ZSkgUHJvYmFiaWxpdHkgPSBPZGRzIFJhdGlvIC8gKDEgKyBPZGRzIFJhdGlvKQoKYGBge3IgQ29uZmlkZW5jZSBJbnRlcnZhbHMgYW5kIENvZWZmaWNpZW50cyBJbnRlcnByZXRhdGlvbn0KCiMjSW5kaXZpZHVhbCBjb2VmZmljaWVudHMgc2lnbmlmaWNhbmNlIGFuZCBpbnRlcnByZXRhdGlvbgoKI2xpYnJhcnkoY29lZi5sbUxpc3QpCnN1bW1hcnkobW9kZWxfMDFfZ2xtKQoKI3ByaW50cyBjb25maWRlbmNlIGludGVydmFscwpleHAoY29uZmludChtb2RlbF8wMV9nbG0pKQoKCiNwbG90IGNvZWZmaWNpZW50cyBvbiBvZGRzIHJhdGlvIHVzaW5nIHNqUGxvdCBwYWNrYWdlCnBsb3RfbW9kZWwobW9kZWxfMDFfZ2xtLCB2bGluZS5jb2xvciA9ICJyZWQiLAogIHNvcnQuZXN0ID0gVFJVRSwgc2hvdy52YWx1ZXMgPSBUUlVFKQoKYGBgCgoKYGBge3IgQ2hlY2sgZm9yIGNvbGxpbmVhcml0eSAtIFZhcmlhbmNlIEluZmxhdGlvbiBGYWN0b3J9CiNDYWxjdWxhdGUgVmFyaWFuY2UgSW5mbGF0aW9uIGZhY3RvciB1c2luZyBjYXIgcGFja2FnZQp2aWYobW9kZWxfMDFfZ2xtKQoKIyBGZWF0dXJlICh4KSB2YXJpYWJsZXMgd2l0aCBhIFZJRiB2YWx1ZSBhYm92ZSA1IGluZGljYXRlIGhpZ2ggZGVncmVlIG9mCiMgbXVsdGktY29sbGluZWFyaXR5LgpgYGAKCk1vbnRobHlDaGFyZ2VzICBoYXMgYSB2ZXJ5IGhpZ2ggVklGIGF0IDYwMCsuIFdlIGhhdmUgc29tZSBvdGhlciB2YXJpYWJsZXMgZS5nLiBJbnRlcm5ldFNlcnZpY2VGaWJlci5vcHRpYywgSW50ZXJuZXRTZXJ2aWNlTm8sIGFuZCBUb3RhbENoYXJnZXMsIFBob25lU2VydmljZVllcywgd2l0aCBoaWdoIFZJRnMuIE5vcm1hbGx5LCB3ZSB3b3VsZCB3YW50IHRvIHJlbW92ZSB2YXJpYWJsZXMgd2l0aCBoaWdoIFZJRiBhcyBpdCBjYW4gcmVhbGx5IG1lc3Mgd2l0aCBvdXIgbW9kZWwuCgoKYGBge3IgTG9naXN0aWMgUmVncmVzc2lvbiBBY2N1cmFjeSBhbmQgTW9kZWwgRXZhbHVhdGlvbiB1c2luZyBNb2RlbCAxfQpwcmVkIDwtIHByZWRpY3QobW9kZWxfMDFfZ2xtLCB0ZXN0X2RhdGEsIHR5cGUgPSAicmVzcG9uc2UiKSAjcHJlZGljdCB1c2luZyB0ZXN0IGRhdGEKCiNjaGVjayByZXN1bHRzCmhlYWQocHJlZCkKCnByZWRpY3RlZCA8LSByb3VuZChwcmVkKSAjPjAuNSB3aWxsIGNvbnZlcnQgdG8gMQoKI2NvbnRpbmdlbmN5IHRhYmxlCmNvbnRpbmdlbmN5X3RhYiA8LSB0YWJsZSh0ZXN0X2RhdGEkQ2h1cm4sIHByZWRpY3RlZCkKY29udGluZ2VuY3lfdGFiCgojIENvbmZ1c2lvbiBNYXRyaXggdXNpbmcgdGhlIGNhcmV0IHBhY2thZ2UKY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChjb250aW5nZW5jeV90YWIpCgojUGxvdCBST0MgQ3VydmUgJiBDYWxjdWxhdGUgQVVDIGFyZWEKbGlicmFyeShST0NSKQoKI1JPQyBDdXJ2ZXMgYXJlIHVzZWZ1bCBmb3IgY29tcGFyaW5nIGNsYXNzaWZpZXJzCgojQ2hlY2sgZGF0YSBzdHJ1Y3R1cmVzIGZpcnN0IG9yIGVsc2UgdGhlIFJPQyBjdXJ2ZSB3b24ndCBwbG90CnR5cGVvZihwcmVkaWN0ZWQpCnR5cGVvZih0ZXN0X2RhdGEkQ2h1cm4pCgoKcHIgPC0gcHJlZGljdGlvbihwcmVkLCB0ZXN0X2RhdGEkQ2h1cm4pCnByZiA8LSBwZXJmb3JtYW5jZShwciwgbWVhc3VyZSA9ICJ0cHIiLCB4Lm1lYXN1cmUgPSAiZnByIikKCnBsb3QocHJmKQoKI1RoZSBpZGVhbCBST0MgY3VydmUgaHVncyB0aGUgdG9wIGxlZnQgY29ybmVyLCBpbmRpY2F0aW5nIGEgaGlnaAojdHJ1ZSBwb3NpdGl2ZSByYXRlIGFuZCBhIGxvdyBmYWxzZSBwb3NpdGl2ZSByYXRlLgojVHJ1ZSBwb3NpdGl2ZSByYXRlIG9uIHktYXhpcwojRmFsc2UgcG9zaXRpdmUgcmF0ZSBvbiB0aGUgeC1heGlzCgojVGhlIGxhcmdlciB0aGUgQVVDLCB0aGUgYmV0dGVyIHRoZSBjbGFzc2lmaWVyCiNUaGUgQVVDIGxpbmUgaXMgaW5zdWZmaWNpZW50IHRvIGlkZW50aWZ5IGEgYmVzdCBtb2RlbAojSXQncyB1c2VkIGluIGNvbWJpbmF0aW9uIHdpdGggcXVhbGl0YXRpdmUgZXhhbWluYXRpb24KI29mIHRoZSBST0MgY3VydmUKYXVjIDwtIHBlcmZvcm1hbmNlKHByLCBtZWFzdXJlID0gImF1YyIpCmF1YwoKIyBBVUMgaXMgMC44NjE5ODkKYXMubnVtZXJpYyhwZXJmb3JtYW5jZShwciwgbWVhc3VyZSA9ICJhdWMiKUB5LnZhbHVlcykKCiNEb3VibGUgZGVuc2l0eSBwbG90IGZvciBleHBsYWluaW5nIGFuZCBwaWNraW5nIHRocmVzaG9sZHMgb2YgcHJlZGljdGVkIGNodXJuIHByb2JhYmlsaXRpZXMuIFBlcmhhcHMgYmV0dGVyIGFsdGVybmF0aXZlIHRoYW4gQVVDIG9yIFJPQyBjdXJ2ZQpnZ3Bsb3QoZGF0YSA9IHRlc3RfZGF0YSkgKyBnZW9tX2RlbnNpdHkoYWVzKHg9cHJlZCwgY29sb3IgPSBDaHVybixsaW5ldHlwZSA9IENodXJuKSkKYGBgCkFVQyBJbnRlcnByZXRhdGlvbgpBOiBPdXRzdGFuZGluZyA9IDAuOSB0byAxLjAKQjogRXhjZWxsZW50L0dvb2QgPSAwLjggdG8gMC45CkM6IEFjY2VwdGFibGUvRmFpciA9IDAuNyB0byAwLjgKRDogUG9vciA9IDAuNiB0byAwLjcKRTogTm8gRGlzY3JpbWluYXRpb24gPSAwLjUgdG8gMC42CgoKYGBge3IgTG9nIFJlZ3Jlc3Npb24gTW9kZWwgMiB3aXRob3V0IGhpZ2ggVklGIHZhcmlhYmxlfQpzZXQuc2VlZCgxKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5Cm1vZGVsXzAyX2dsbSA8LSBnbG0oZm9ybXVsYSA9IENodXJuIH4gLiAtTW9udGhseUNoYXJnZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGEsIGZhbWlseSA9ICJiaW5vbWlhbCIpCgpzdW1tYXJ5KG1vZGVsXzAyX2dsbSkKCnZpZihtb2RlbF8wMl9nbG0pCgojcGxvdCBjb2VmZmljaWVudHMgb24gb2RkcyByYXRpbwpwbG90X21vZGVsKG1vZGVsXzAyX2dsbSwgdmxpbmUuY29sb3IgPSAicmVkIiwKICBzb3J0LmVzdCA9IFRSVUUsIHNob3cudmFsdWVzID0gVFJVRSkKYGBgCkludGVybmV0U2VydmljZSAtIEZpYmVyIE9wdGljIGNhYmxlIHN0aWxsIGluY3JlYXNlcyBsaWtlbGlob29kIG9mIGNodXJuIGZvbGxvd2VkIGJ5IHBhcGVybGVzcyBiaWxsaW5nIGFuZCBiZWluZyBhIHNlbmlvciBjaXRpemVuLgoKCmBgYHtyIExvZ2lzdGljIFJlZ3Jlc3Npb24gQWNjdXJhY3kgYW5kIE1vZGVsIEV2YWx1YXRpb24gdXNpbmcgTW9kZWwgMn0KcHJlZCA8LSBwcmVkaWN0KG1vZGVsXzAyX2dsbSwgdGVzdF9kYXRhLCB0eXBlID0gInJlc3BvbnNlIikgI3ByZWRpY3QgdXNpbmcgdGVzdCBkYXRhCgojY2hlY2sgcmVzdWx0cwpoZWFkKHByZWQpCgpwcmVkaWN0ZWQgPC0gcm91bmQocHJlZCkgIz4wLjUgd2lsbCBjb252ZXJ0IHByZWRpY3RlZCB2YWx1ZXMgdG8gMQoKI2NvbnRpbmdlbmN5IHRhYmxlIC0gbWFudWFsbHkgZG9uZSB3aXRoIHRhYmxlIGZ1bmN0aW9uCmNvbnRpbmdlbmN5X3RhYiA8LSB0YWJsZSh0ZXN0X2RhdGEkQ2h1cm4sIHByZWRpY3RlZCkKY29udGluZ2VuY3lfdGFiCgojIENvbmZ1c2lvbiBNYXRyaXggdXNpbmcgdGhlIGNhcmV0IHBhY2thZ2UKY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChjb250aW5nZW5jeV90YWIpCgojUGxvdCBST0MgQ3VydmUgJiBDYWxjdWxhdGUgQVVDIGFyZWEKbGlicmFyeShST0NSKQoKCiNjaGVjayBkYXRhIHN0cnVjdHVyZXMgZmlyc3QKdHlwZW9mKHByZWRpY3RlZCkKdHlwZW9mKHRlc3RfZGF0YSRDaHVybikKCgpwciA8LSBwcmVkaWN0aW9uKHByZWQsIHRlc3RfZGF0YSRDaHVybikKcHJmIDwtIHBlcmZvcm1hbmNlKHByLCBtZWFzdXJlID0gInRwciIsIHgubWVhc3VyZSA9ICJmcHIiKQoKcGxvdChwcmYpCgojVGhlIGlkZWFsIFJPQyBjdXJ2ZSBodWdzIHRoZSB0b3AgbGVmdCBjb3JuZXIsIGluZGljYXRpbmcgYSBoaWdoCiN0cnVlIHBvc2l0aXZlIHJhdGUgYW5kIGEgbG93IGZhbHNlIHBvc2l0aXZlIHJhdGUuCiNUcnVlIHBvc2l0aXZlIHJhdGUgb24geS1heGlzCiNGYWxzZSBwb3NpdGl2ZSByYXRlIG9uIHRoZSB4LWF4aXMKCiNUaGUgbGFyZ2VyIHRoZSBBVUMsIHRoZSBiZXR0ZXIgdGhlIGNsYXNzaWZpZXIKI1RoZSBBVUMgbGluZSBpcyBpbnN1ZmZpY2llbnQgdG8gaWRlbnRpZnkgYSBiZXN0IG1vZGVsCiNJdCdzIHVzZWQgaW4gY29tYmluYXRpb24gd2l0aCBxdWFsaXRhdGl2ZSBleGFtaW5hdGlvbgojb2YgdGhlIFJPQyBjdXJ2ZQphdWMgPC0gcGVyZm9ybWFuY2UocHIsIG1lYXN1cmUgPSAiYXVjIikKYXVjCgphcy5udW1lcmljKHBlcmZvcm1hbmNlKHByLCBtZWFzdXJlID0gImF1YyIpQHkudmFsdWVzKQpgYGAKCgpDb21wYXJpbmcgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMgMSBhbmQgMgotTW9kZWwgMSBoYXMgYmV0dGVyIGFjY3VyYWN5IHRoYW4gTW9kZWwgMiAoanVzdCBzbGlnaHRseSBiZXR0ZXIpLCBidXQgd2UgZG8gY2FyZSBhYm91dCBzcGVjaWZpY2l0eSBhbmQgc2Vuc2l0aXZpdHkuCgpTZW5zaXRpdml0eSBpcyB0aGUgcHJvcG9ydGlvbiBvZiBvdXIgdmFyaWFibGUgb2YgaW50ZXJlc3QoY2h1cm4pIGNvcnJlY3RseSBpZGVudGlmaWVkLgoKU3BlY2lmaWNpdHkgaXMgdGhlIHByb3BvcnRpb24gb2Ygb3VyIHZhcmlhYmxlIG9mIGludGVyZXN0KGNodXJuKQoKUmVhZDogaHR0cHM6Ly93d3cudGhlYW5hbHlzaXNmYWN0b3IuY29tL3NlbnNpdGl2aXR5LWFuZC1zcGVjaWZpY2l0eS8KCgojIyBQYXJ0IDVCOiBNb2RlbGluZyAtIFJhbmRvbSBGb3Jlc3QKCgoKCmBgYHtyIFRyYWluIGEgUmFuZG9tIEZvcmVzdCBtb2RlbCBSRiBNb2RlbCAxfQojIFRyYWluIGEgUmFuZG9tIEZvcmVzdCB1c2luZyByYW5kb21Gb3Jlc3QgcGFja2FnZQpzZXQuc2VlZCgxKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5Cm1vZGVsXzAxX3JmIDwtIHJhbmRvbUZvcmVzdChmb3JtdWxhID0gQ2h1cm4gfiAuLCBkYXRhID0gdHJhaW5fZGF0YSwgaW1wb3J0YW5jZSA9IFRSVUUsIG5hLmFjdGlvbj1uYS5leGNsdWRlKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIAojIFByaW50IHRoZSBtb2RlbCBvdXRwdXQgICAgICAgICAgICAgICAgCnByaW50KG1vZGVsXzAxX3JmKQpgYGAKTW9kZWwgNTAwIHRyZWVzOgotTm8uIG9mIHZhcmlhYmxlcyB0cmllZCBhdCBlYWNoIHNwbGl0OiA1Ci1PT0IgZXN0aW1hdGUgb2YgIGVycm9yIHJhdGU6IDIwJQoKYGBge3IgVmFyaWFibGUgSW1wb3J0YW5jZX0KIyBwcmludHMgdmFyaWFibGUgaW1wb3J0YW5jZQpzdW1tYXJ5KG1vZGVsXzAxX3JmKQoKCnZhckltcFBsb3QobW9kZWxfMDFfcmYsIG1haW49IlZhcmlhYmxlIEltcG9ydGFuY2UiKQpgYGAKCmBgYHtyIFJGIE1vZGVsIDIgLSBXaXRob3V0IFRlbnVyZX0KIyBUcmFpbiBhIFJhbmRvbSBGb3Jlc3QKc2V0LnNlZWQoMSkgICMgZm9yIHJlcHJvZHVjaWJpbGl0eQptb2RlbF8wMl9yZiA8LSByYW5kb21Gb3Jlc3QoZm9ybXVsYSA9IENodXJuIH4gLiAtdGVudXJlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhLCBpbXBvcnRhbmNlID0gVFJVRSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKIyBQcmludCB0aGUgbW9kZWwgb3V0cHV0CnByaW50KG1vZGVsXzAyX3JmKQoKdmFySW1wUGxvdChtb2RlbF8wMl9yZiwgbWFpbj0iVmFyaWFibGUgSW1wb3J0YW5jZSB3aXRob3V0IHRlbnVyZSIpCmBgYAoKVHlwZSBvZiByYW5kb20gZm9yZXN0OiBjbGFzc2lmaWNhdGlvbgogICAgICAgICAgICAgICAgICAgICBOdW1iZXIgb2YgdHJlZXM6IDUwMApOby4gb2YgdmFyaWFibGVzIHRyaWVkIGF0IGVhY2ggc3BsaXQ6IDQKCk9PQiBlc3RpbWF0ZSBvZiAgZXJyb3IgcmF0ZTogMjEuNTElCkNvbmZ1c2lvbiBtYXRyaXg6CiAgICAgMCAgIDEgY2xhc3MuZXJyb3IKMCAzNjIxIDQ5OSAgIDAuMTIxMTE2NQoxICA4MjkgNjc3ICAgMC41NTA0NjQ4CgpvdXIgb29iIGVycm9yIHJhdGUgaW5jcmVhc2VkIHRvIDIxLjUxJSBmcm9tIDIxLjMlIC0KbGV0J3MgdHVuZSB0aGUgMXN0IG1vZGVsIC0gbW9kZWxfMDFfcmYKCmBgYHtyIEV2YWx1YXRlIG91dC1vZi1iYWcgZXJyb3J9CiMgR3JhYiBPT0IgZXJyb3IgbWF0cml4ICYgdGFrZSBhIGxvb2sKZXJyIDwtIG1vZGVsXzAxX3JmJGVyci5yYXRlCmhlYWQoZXJyKQoKIyBMb29rIGF0IGZpbmFsIE9PQiBlcnJvciByYXRlIChsYXN0IHJvdyBpbiBlcnIgbWF0cml4KQpvb2JfZXJyIDwtIGVycltucm93KGVyciksICJPT0IiXQpwcmludChvb2JfZXJyKQoKIyBQbG90IHRoZSBtb2RlbCB0cmFpbmVkCnBsb3QobW9kZWxfMDFfcmYpCgojIEFkZCBhIGxlZ2VuZCBzaW5jZSBpdCBkb2Vzbid0IGhhdmUgb25lIGJ5IGRlZmF1bHQKbGVnZW5kKHggPSAicmlnaHQiLCAKICAgICAgIGxlZ2VuZCA9IGNvbG5hbWVzKGVyciksCiAgICAgICBmaWxsID0gMTpuY29sKGVycikpCmBgYAoKCgoKYGBge3IgRXZhbHVhdGUgbW9kZWwgcGVyZm9ybWFuY2Ugb24gYSB0ZXN0IHNldCB9CiMgR2VuZXJhdGUgcHJlZGljdGVkIGNsYXNzZXMgdXNpbmcgdGhlIG1vZGVsIG9iamVjdApjbGFzc19wcmVkaWN0aW9uIDwtIHByZWRpY3Qob2JqZWN0ID0gbW9kZWxfMDFfcmYsICAgIyBtb2RlbCBvYmplY3QgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gdGVzdF9kYXRhLCAgIyB0ZXN0IGRhdGFzZXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiY2xhc3MiKSAjIHJldHVybiBjbGFzc2lmaWNhdGlvbiBsYWJlbHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgIAojIENhbGN1bGF0ZSB0aGUgY29uZnVzaW9uIG1hdHJpeCBmb3IgdGhlIHRlc3Qgc2V0CmNtIDwtIGNhcmV0Ojpjb25mdXNpb25NYXRyaXgoZGF0YSA9IGNsYXNzX3ByZWRpY3Rpb24sI3ByZWRpY3RlZCBjbGFzc2VzCiAgICAgICAgICAgICAgICAgICAgICByZWZlcmVuY2UgPSB0ZXN0X2RhdGEkQ2h1cm4pICAjIGFjdHVhbCBjbGFzc2VzCnByaW50KGNtKQoKIyBDb21wYXJlIHRlc3Qgc2V0IGFjY3VyYWN5IHRvIE9PQiBhY2N1cmFjeQpwYXN0ZTAoIlRlc3QgQWNjdXJhY3k6ICIsIGNtJG92ZXJhbGxbMV0pCnBhc3RlMCgiT09CIEFjY3VyYWN5OiAiLCAxIC0gb29iX2VycikKYGBgCgpDb21wYXJlIHRlc3Qgc2V0IGFjY3VyYWN5IHRvIE9PQiBhY2N1cmFjeQoKWzFdICJUZXN0IEFjY3VyYWN5OiAwLjgwOTM4ODMzNTcwNDEyNSIKClsxXSAiT09CIEFjY3VyYWN5OiAwLjc5NjgzNjExODAyMzQ2MiIKCgoKYGBge3IgRXZhbHVhdGUgdGVzdCBzZXQgQVVDIC0gQXJlYSB1bmRlciB0aGUgY3VydmUgZm9yIGZpcnN0IFJGIG1vZGVsfQoKIyBHZW5lcmF0ZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBzZXQKcHJlZCA8LXByZWRpY3Qob2JqZWN0ID0gbW9kZWxfMDFfcmYsCiAgICAgICAgICAgIG5ld2RhdGEgPSB0ZXN0X2RhdGEsCiAgICAgICAgICAgIHR5cGUgPSAicHJvYiIpIAoKIyBVbmNvbW1lbnQgdG8gdGFrZSBhIGxvb2sgYXQgdGhlIHByZWQgZm9ybWF0IC0gYHByZWRgIG9iamVjdCBpcyBhIG1hdHJpeAojaGVhZChwcmVkKQogICAgICAgICAgICAgICAgCiMgQ29tcHV0ZSB0aGUgQVVDIChgYWN0dWFsYCBtdXN0IGJlIGEgYmluYXJ5IDEvMCBudW1lcmljIHZlY3RvcikKcm91bmQoYXVjKGFjdHVhbCA9IGlmZWxzZSh0ZXN0X2RhdGEkQ2h1cm4gPT0gIjEiLCAxLCAwKSwKICAgIHByZWRpY3RlZCA9IHByZWRbLCIxIl0pICwyKQpgYGAKCkFVQyBpcyBnb29kIHRvIGV4Y2VsbGVudAoKCgoKQ0FVVElPTjogUmFuZG9tIGZvcmVzdCBtb2RlbHMgYXJlIGNvbXB1dGF0aW9uYWxseSBoZWF2eSB0byBydW4gb24geW91ciBjb21wdXRlciBlc3BlY2lhbGx5IHRoZSBwYXJhbWV0ZXIgdHVuaW5nIGFzIHRoZSBtb2RlbCBuZWVkcyB0byBpdGVyYXRlIHRocm91Z2ggYWxsIHBvc3NpYmxlIHZhbHVlcyBpbiB0aGUgaHlwZXIgZ3JpZC4gVGhpcyBjb3VsZCB0YWtlIG1hbnkgaG91cnMgdG8gcnVuIGFuZCBpZiB5b3VyIGRhdGFzZXQgaXMgbGFyZ2UsIGNvdWxkIGNyYXNoIHlvdXIgY29tcHV0ZXIhCgpgYGB7ciBUdW5pbmcgUmFuZG9tIEZvcmVzdCAtIGh5cGVyIGdyaWQgLSBtdHJ5IG5vZGVzaXplIHNhbXBzaXplLCBldmFsPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQoKIyBFc3RhYmxpc2ggYSBsaXN0IG9mIHBvc3NpYmxlIHZhbHVlcyBmb3IgbXRyeSwgbm9kZXNpemUgYW5kIHNhbXBzaXplCm10cnkgPC0gc2VxKDQsIG5jb2wodHJhaW5fZGF0YSkgKiAwLjgsIDIpCm5vZGVzaXplIDwtIHNlcSgzLCA4LCAyKQpzYW1wc2l6ZSA8LSBucm93KHRyYWluX2RhdGEpICogYygwLjcsIDAuOCkKCiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSBjb250YWluaW5nIGFsbCBjb21iaW5hdGlvbnMgCmh5cGVyX2dyaWQgPC0gZXhwYW5kLmdyaWQobXRyeSA9IG10cnksIG5vZGVzaXplID0gbm9kZXNpemUsIHNhbXBzaXplID0gc2FtcHNpemUpCgojIENyZWF0ZSBhbiBlbXB0eSB2ZWN0b3IgdG8gc3RvcmUgT09CIGVycm9yIHZhbHVlcwpvb2JfZXJyIDwtIGMoKQoKIyBXcml0ZSBhIGxvb3Agb3ZlciB0aGUgcm93cyBvZiBoeXBlcl9ncmlkIHRvIHRyYWluIHRoZSBncmlkIG9mIG1vZGVscwpmb3IgKGkgaW4gMTpucm93KGh5cGVyX2dyaWQpKSB7CgojIFRyYWluIGEgUmFuZG9tIEZvcmVzdCBtb2RlbAogICAgbW9kZWwgPC0gcmFuZG9tRm9yZXN0KGZvcm11bGEgPSBDaHVybiB+IC4sIAogICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSBoeXBlcl9ncmlkJG10cnlbaV0sIyBtb3N0IGltcG9ydGFudCB0dW5pbmcgcGFyYW0KICAgICAgICAgICAgICAgICAgICAgICAgICBub2Rlc2l6ZSA9IGh5cGVyX2dyaWQkbm9kZXNpemVbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcHNpemUgPSBoeXBlcl9ncmlkJHNhbXBzaXplW2ldKQogICAgICAgICAgICAgICAgICAgICAgICAgIAojIFN0b3JlIE9PQiBlcnJvciBmb3IgdGhlIG1vZGVsICAgICAgICAgICAgICAgICAgICAgIAogICAgb29iX2VycltpXSA8LSBtb2RlbCRlcnIucmF0ZVtucm93KG1vZGVsJGVyci5yYXRlKSwgIk9PQiJdCn0KCiMgSWRlbnRpZnkgb3B0aW1hbCBzZXQgb2YgaHlwZXJwYXJtZXRlcnMgYmFzZWQgb24gT09CIGVycm9yCm9wdF9pIDwtIHdoaWNoLm1pbihvb2JfZXJyKQpwcmludChoeXBlcl9ncmlkW29wdF9pLF0pCgpgYGAKT3B0aW1hbCBtb2RlbAoKTXRyeSA9IDQKbm9kZXNpemUgPSA3CnNhbXBzaXplID0gMzQ0NS40CQoKCgpgYGB7ciBSRiBNb2RlbCAzIC0gVGhlIFR1bmVkIE1vZGVsfQojIFRyYWluIGEgUmFuZG9tIEZvcmVzdApzZXQuc2VlZCgxKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5Cm1vZGVsXzAzX3JmX2ZpbmFsIDwtIHJhbmRvbUZvcmVzdChmb3JtdWxhID0gQ2h1cm4gfiAuLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSA0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm9kZXNpemUgPSA3LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcHNpemUgPSAzNDQ1LjQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGEsIGltcG9ydGFuY2UgPSBUUlVFLCBrZWVwLmZvcmVzdD1UUlVFKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIAojIFByaW50IHRoZSBtb2RlbCBvdXRwdXQKcHJpbnQobW9kZWxfMDNfcmZfZmluYWwpCgojdmFyaWFibGUgaW1wb3J0YW5jZQppbXBvcnRhbmNlKG1vZGVsXzAzX3JmX2ZpbmFsKQoKI3ZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdAp2YXJJbXBQbG90KG1vZGVsXzAzX3JmX2ZpbmFsLCBtYWluPSJWYXJpYWJsZSBpbXBvcnRhbmNlIG9uIHRoZSB0dW5lZCBtb2RlbCIpCmBgYAoKYGBge3IgUGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzIGZvciB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzLCBldmFsPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQojIExldOKAmXMgY2hlY2sgcGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzIGZvciBhIGZldyB2YXJpYWJsZXM6CgpvcCA8LSBwYXIobWZyb3c9YygyLCAyKSkKCnBhcnRpYWxQbG90KG1vZGVsXzAzX3JmX2ZpbmFsLCB0cmFpbl9kYXRhLCB0ZW51cmUsIDEpCgpwYXJ0aWFsUGxvdChtb2RlbF8wM19yZl9maW5hbCwgdHJhaW5fZGF0YSwgVG90YWxDaGFyZ2VzLCAxKQoKcGFydGlhbFBsb3QobW9kZWxfMDNfcmZfZmluYWwsIHRyYWluX2RhdGEsIENvbnRyYWN0LCAxKQoKcGFydGlhbFBsb3QobW9kZWxfMDNfcmZfZmluYWwsIHRyYWluX2RhdGEsIE1vbnRobHlDaGFyZ2VzLCAxKQoKcGFyKG9wKQpgYGAKCgoKYGBge3IgRXZhbHVhdGUgdGVzdCBzZXQgQVVDIC0gQXJlYSB1bmRlciB0aGUgY3VydmUgZm9yIGJlc3QgdHVuZWQgbW9kZWwgLSBSRjN9CiMgR2VuZXJhdGUgcHJlZGljdGlvbnMgb24gdGhlIHRlc3Qgc2V0CnByZWQgPC1wcmVkaWN0KG9iamVjdCA9IG1vZGVsXzAzX3JmX2ZpbmFsLAogICAgICAgICAgICBuZXdkYXRhID0gdGVzdF9kYXRhLAogICAgICAgICAgICB0eXBlID0gInByb2IiKSAKCnJvdW5kKGF1YyhhY3R1YWwgPSBpZmVsc2UodGVzdF9kYXRhJENodXJuID09ICIxIiwgMSwgMCksCiAgICBwcmVkaWN0ZWQgPSBwcmVkWywiMSJdKSAsMikKYGBgCgoKYGBge3IgUk9DIEN1cnZlIGZvciBSRiBNb2RlbCAtIEJlc3QgTW9kZWx9CnJlcXVpcmUocFJPQykKcmYucm9jPC1yb2ModHJhaW5fZGF0YSRDaHVybixtb2RlbF8wM19yZl9maW5hbCR2b3Rlc1ssMl0pCnBsb3QocmYucm9jKQphdWMocmYucm9jKQpgYGAKCgoKCmBgYHtyIERlY2lzaW9uIFRyZWUgTW9kZWx9CgojIFRyYWluIHRoZSBtb2RlbCAodG8gcHJlZGljdCAnQ2h1cm4nKSB1c2luZyBycGFydCBwYWNrYWdlCnRyZWVfbW9kXzAxIDwtIHJwYXJ0KGZvcm11bGEgPSBDaHVybiB+LiwgCiAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YSwKICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJjbGFzcyIpCgojIExvb2sgYXQgdGhlIG1vZGVsIG91dHB1dCAgICAgICAgICAgICAgICAgIApwcmludCh0cmVlX21vZF8wMSkKCgojIERpc3BsYXkgdGhlIHJlc3VsdHMgdXNpbmcgcnBhcnQucGxvdCBwYWNrYWdlCnJwYXJ0LnBsb3QoeCA9IHRyZWVfbW9kXzAxLCB5ZXNubyA9IDIsIHR5cGUgPSAwLCBleHRyYSA9IDApCgpycGFydC5wbG90KHRyZWVfbW9kXzAxLAp5ZXNubyA9IDIsCmV4dHJhID0gMTA0LCAjIHNob3cgZml0dGVkIGNsYXNzLCBwcm9icywgcGVyY2VudGFnZXMKYm94LnBhbGV0dGUgPSAiR25CdSIsICMgY29sb3Igc2NoZW1lCmJyYW5jaC5sdHkgPSAzLCAjIDE9IHNvbGlkLCAzID0gZG90dGVkIGJyYW5jaCBsaW5lcwpzaGFkb3cuY29sID0gImdyYXkiLCAjIHNoYWRvd3MgdW5kZXIgdGhlIG5vZGUgYm94ZXMKbm4gPSBUUlVFLCAKbWFpbiA9ICJDbGFzc2lmaWNhdGlvbiB0cmVlIG9uIG91ciBjaHVybiBkYXRhIikKYGBgCgoKCmBgYHtyIENvbXBhcmUgdHJlZSBtb2RlbHMgd2l0aCBhIGRpZmZlcmVudCBzcGxpdHRpbmcgY3JpdGVyaW9ufQojIFRyYWluIGEgZ2luaS1iYXNlZCBtb2RlbAp0cmVlX21vZF8wMjwtIHJwYXJ0KGZvcm11bGEgPSBDaHVybiB+LiwgCiAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YV90cmVlLAogICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJjbGFzcyIsCiAgICAgICAgICAgICAgICAgICAgICAgcGFybXMgPSBsaXN0KHNwbGl0ID0gImdpbmkiKSkKCiMgRGlzcGxheSB0aGUgcmVzdWx0cwpycGFydC5wbG90KHggPSB0cmVlX21vZF8wMiwgeWVzbm8gPSAyLCB0eXBlID0gMCwgZXh0cmEgPSAwKQoKIyBUcmFpbiBhbiBpbmZvcm1hdGlvbi1iYXNlZCBtb2RlbAp0cmVlX21vZF8wMzwtIHJwYXJ0KGZvcm11bGEgPSBDaHVybiB+LiwgCiAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YV90cmVlLCAKICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2xhc3MiLAogICAgICAgICAgICAgICAgICAgICAgIHBhcm1zID0gbGlzdChzcGxpdCA9ICJpbmZvcm1hdGlvbiIpKQoKIyBEaXNwbGF5IHRoZSByZXN1bHRzCnJwYXJ0LnBsb3QoeCA9IHRyZWVfbW9kXzAzLCB5ZXNubyA9IDIsIHR5cGUgPSA1LCBleHRyYSA9IDYpCgojIEdlbmVyYXRlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldCB1c2luZyB0aGUgZ2luaSBtb2RlbApwcmVkMSA8LSBwcmVkaWN0KG9iamVjdCA9IHRyZWVfbW9kXzAyLCAKICAgICAgICAgICAgIG5ld2RhdGEgPSB0ZXN0X2RhdGFfdHJlZSwKICAgICAgICAgICAgIHR5cGUgPSAiY2xhc3MiKSAgICAKCiMgR2VuZXJhdGUgcHJlZGljdGlvbnMgb24gdGhlIHRlc3Qgc2V0IHVzaW5nIHRoZSBpbmZvcm1hdGlvbiBtb2RlbApwcmVkMiA8LSBwcmVkaWN0KG9iamVjdCA9IHRyZWVfbW9kXzAzLCAKICAgICAgICAgICAgIG5ld2RhdGEgPSB0ZXN0X2RhdGFfdHJlZSwKICAgICAgICAgICAgIHR5cGUgPSAiY2xhc3MiKQoKIyBDb21wYXJlIGNsYXNzaWZpY2F0aW9uIGVycm9yIC0gdXNpbmcgTW9kZWxNZXRyaWNzIGxpYnJhcnkKY2UoYWN0dWFsID0gdGVzdF9kYXRhJENodXJuLCAKICAgcHJlZGljdGVkID0gcHJlZDEpCmNlKGFjdHVhbCA9IHRlc3RfZGF0YSRDaHVybiwgCiAgIHByZWRpY3RlZCA9IHByZWQyKSAgCgpgYGAKCiMjIFBhcnQgNjogU3VtbWFyeSBvZiBJbnNpZ2h0cyBhbmQgaHlwb3RoZXNlcyBnZW5lcmF0ZWQgYW5kIHRlc3RpbmcgdGhlbQoKCldoYXQgaW5zaWdodHMgaGF2ZSB3ZSBkaXNjb3ZlcmVkIGFuZCBjYW4gY29uZmlkZW50bHkgcmVwb3J0IHRvIG91ciBzdGFrZWhvbGRlcnM/CgoxLkZpYmVyIG9wdGljIHNlcnZpY2UgaXMgYSBwYWluIHBvaW50IGZvciBjdXN0b21lcnMsIGJ1dCB3aHk/IERpZmZpY3VsdCB0byBzZXQgdXAuIFF1YWxpdHkgY29uY2VybnMsIENhbuKAmXQgc3RyZWFtIE5ldGZsaXgsIGV0Yy4KMi4gQ29udHJhY3QgdHlwZSBpcyBhIG5vLWJyYWluZXIuIElmIHlvdSBsb2NrIGN1c3RvbWVycyBpbnRvIGEgY29udHJhY3QsIHRoZXkgY2Fu4oCZdCBjaHVybi4KCgoKYGBge3IgV2hhdCBpcyB0aGUgZWNvbm9taWMgaW1wYWN0IG9mIHN1Y2ggYSBoaWdoIGNodXJuIHJhdGUgZm9yIGZpYmVyIG9wdGljIHByb2R1Y3Q/fQoKZGYgJT4lIAogIGZpbHRlcihDaHVybiA9PSAiWWVzIiAmIEludGVybmV0U2VydmljZSA9PSAiRmliZXIgb3B0aWMiKSAlPiUgCiAgc3VtbWFyaXplKGxvc3RfcmV2ZW51ZSA9IHN1bShUb3RhbENoYXJnZXMpKQoKIyBJZiB3ZSBwbG90IGxvc3QgcmV2ZW51ZSAoY2h1cm5lZCBjdXN0b21lcnMpIGJ5IGludGVybmV0IHNlcnZpY2UsIHRoaXMgY291bGQgaGVscCBjcmVhdGUgYSBzZW5zZSBvZiB1cmdlbmN5IGZvciB5b3VyIGJ1c2luZXNzIHN0YWtlaG9sZGVycy4KCmBgYAoKCk5vdywgeW91IGhhdmUgYSBnb29kIHN0YXJ0aW5nIHBvaW50IHRvIHByZXNlbnQgeW91ciBmaW5kaW5ncyAmIGluc2lnaHRzIHRvIHN0YWtlaG9sZGVycywgYnV0IHRoaXMgaXMgb25seSB0aGUgYmVnaW5uaW5nLiBDdXJyZW50bHksIHlvdSBvbmx5IGhhdmUgYSBzdHJvbmcgaHlwb3RoZXNlcyBzbyBub3cgeW91IG5lZWQgdG8gdmFsaWRhdGUgYW5kIHRlc3Qgd2hhdCB5b3UgaGF2ZSB1bmNvdmVyZWQuIFlvdSBtYXkgbmVlZCB0byBkbyBtb3JlIHJlc2VhcmNoIG9yIGRhdGEgdG8gZG8gdGhpcyBhcyB5b3UgbWF5IG5lZWQgdG8gdW5jb3ZlciB0aGUgcm9vdCBjYXVzZXMu