Section 1: Data cleaning and basic exploration
# Format dates in Dt_Customer
data$Dt_Customer = mdy(data$Dt_Customer)
summary(data$Dt_Customer)
Min. 1st Qu. Median Mean 3rd Qu. Max.
"2012-07-30" "2013-01-16" "2013-07-08" "2013-07-10" "2013-12-30" "2014-06-29"
# Drop special characters in Income
data$Income = gsub('[$]([0-9]+)[,]([0-9]+)','\\1\\2',data$Income)
data$Income = as.numeric(data$Income)
- The earliest customer enrollment date in the dataset is 2012-07-30 and latest 2014-06-29
# Missing data analysis
data.frame("NA_Count" = sapply(data, function(x) { length(which(is.na(x))) })) %>%
filter(NA_Count > 0) %>%
arrange(., desc(NA_Count))
- There are 24 observations without income specified.
# Unique levels in categorical variables
summary(as.factor(data$Education))
2n Cycle Basic Graduation Master PhD
203 54 1127 370 486
summary(as.factor(data$Marital_Status))
Absurd Alone Divorced Married Single Together Widow YOLO
2 3 232 864 480 580 77 2
summary(as.factor(data$Country))
AUS CA GER IND ME SA SP US
160 268 120 148 3 337 1095 109
- Master education level are also indicated as 2n cycles in the dataset
- There are some odd levels in the Marital Status level i.e., Absurd, Alone and YOLO.
- There are only 3 observations with country listed as ME
# Recode 2n cycle to master
revalue(data$Education,c("2n Cycle" = "Master")) -> data$Education
summary(as.factor(data$Education))
Basic Graduation Master PhD
54 1127 573 486
# Recode YOLO, Alone, Absurd to Single
data$Marital_Status[data$Marital_Status == "YOLO" |
data$Marital_Status == "Absurd" |
data$Marital_Status == "Alone"] <- "Single"
summary(as.factor(data$Marital_Status))
Divorced Married Single Together Widow
232 864 487 580 77
Missing data imputation
References: Jack Daoud’s Notebook and Saeed Jafari’s Notebook.
# Summary of Income for each Education level
library(psych)
describeBy(data$Income, data$Education, mat = TRUE)
# Table of mean values
#detach(package:plyr)
average_income = data %>% group_by(Education) %>% summarise(avg_Income = mean(Income,na.rm = TRUE)) %>% as.data.frame()
head(average_income)
# Join
imput_data = left_join(data[is.na(data$Income),],
average_income,
by = c("Education"))
# Replace missing values with Avg_Income
imput_data$Income = imput_data$avg_Income
# drop avg_Income col
imput_data <- imput_data %>% select(-"avg_Income")
imput_data
# Full input data with full dataset
marketing_data <- full_join(data,
imput_data,
by = c('ID',
'Year_Birth',
'Education',
'Marital_Status',
'Kidhome',
'Teenhome',
'Dt_Customer',
'Recency',
'MntWines',
'MntFruits',
'MntMeatProducts',
'MntFishProducts',
'MntSweetProducts',
'MntGoldProds',
'NumDealsPurchases',
'NumWebPurchases',
'NumCatalogPurchases',
'NumStorePurchases',
'NumWebVisitsMonth',
'AcceptedCmp3',
'AcceptedCmp4',
'AcceptedCmp5',
'AcceptedCmp1',
'AcceptedCmp2',
'Response',
'Complain',
'Country'))
# merge Income columns into a single column
marketing_data <- marketing_data %>%
mutate(Income = coalesce(Income.x, Income.y)) %>%
select(-c("Income.x", "Income.y"))
head(marketing_data)
NA
# Check for missing values after inputation
sum(is.na(marketing_data$Income))
[1] 0
sum(is.na(marketing_data))
[1] 0
Duplicates
# Check for duplicates in ID col
length(unique(data$ID))
[1] 2240
# Drop 3 obs with country listed as ME
marketing_data = marketing_data %>% filter(Country!="ME")
dim(marketing_data)
[1] 2237 28
summary(as.factor(marketing_data$Country))
AUS CA GER IND SA SP US
160 268 120 148 337 1095 109
#examine duplicated cases using all cols except for ID
dupes = marketing_data %>% get_dupes(c(-ID))
dim(dupes)
[1] 94 29
head(dupes)
- Although no duplicates were found based on ID column, there are 94 problematic cases (47 duplicates) detected based on the rest of the columns.
# dataframe without duplicates
length(unique(marketing_data$ID))
[1] 2237
data = marketing_data %>% distinct_at(vars(-ID))
dim(data)
[1] 2190 27
Outliers and Anomalies
# Change variables types
data = data %>% mutate_at(vars(AcceptedCmp3,AcceptedCmp4,AcceptedCmp5,AcceptedCmp1,AcceptedCmp2,Response,Complain),list(factor))
# Scale numeric data
numeric_data = data %>% select(where(is.numeric))
z_data = data.frame(scale(numeric_data))
z_data$type = rownames(data)
# Plot all numeric variables
z_data %>%
pivot_longer(cols=-type) %>%
ggplot(aes(x = name, y = value)) +
geom_boxplot() +
theme_classic() +
expand_limits(x=12.6) + coord_flip()

- The boxplot of all numeric variables show that there are outliers in multiple features, in particular:
- Year_Birth variable contains 3 values before 1900
- Income has an extreme outlier i.e. $666,666
- Observations with the above-mentioned points are dropped, while the other outliers identified remains in the dataframe to avoid losing too much information.
# Drop obs with outliers in Income and Year_Birth
data = data %>% filter(!Year_Birth<1930) %>% filter(!Income==666666)
dim(data)
[1] 2186 27
Feature Construction
# Feature construction
# Numeric variable from date (Dt_Customer)
data$Dt_Customer_num = as.numeric(data$Dt_Customer)
summary(data$Dt_Customer_num)
Min. 1st Qu. Median Mean 3rd Qu. Max.
15551 15721 15894 15897 16071 16250
# TotalPurchases
data$TotalPurchases = data$NumWebPurchases + data$NumCatalogPurchases + data$NumStorePurchases
summary(data$TotalPurchases)
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.00 6.00 12.00 12.54 18.00 32.00
# Obs without purchases
data %>% filter(TotalPurchases==0)
# TotalProducts
data$TotalProducts = data$MntWines + data$MntFruits + data$MntMeatProducts + data$MntFishProducts + data$MntSweetProducts + data$MntGoldProds
summary(data$TotalProducts)
Min. 1st Qu. Median Mean 3rd Qu. Max.
5.0 69.0 396.5 605.9 1044.8 2525.0
# Plot TotalPurchases and TotalProducts
data %>% ggplot(aes(x=TotalPurchases,y=TotalProducts)) + geom_point(alpha=0.5)

# Plot total products of TotalPurchases==0
data %>% filter(TotalPurchases==0) %>% ggplot(aes(x=TotalPurchases,y=TotalProducts)) + geom_count() + scale_y_continuous(limits=c(0.00,10.00)) + scale_size_continuous(breaks=round)

- There are 6 obs with no purchases but have amount spent on products.
# Get ratio of NumDealsPurchased to TotalPurchases
data = data %>% mutate(ratio_dt = round((NumDealsPurchases/TotalPurchases),3)) %>% mutate(ratio_dt = na_if(ratio_dt,Inf))
summary(data$ratio_dt)
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
0.0000 0.0830 0.2000 0.2481 0.3330 15.0000 6
- As there are 6 obs with no purchases, there are 6 NAs in the variable ratio_dt.
The variables Age, Age_group and Total Children are inspired by Jack Daoud’s Notebook
# Age
data$Age = 2014 - data$Year_Birth
# Age_group
data$Age_group <- ""
for (i in 1:nrow(data)) {
if (data[i, "Age"] > 54) {
data[i, "Age_group"] <- "Baby Boomer"
} else if (data[i, "Age"] > 38) {
data[i, "Age_group"] <- "Gen X"
} else if (data[i, "Age"] > 18) {
data[i, "Age_group"] <- "Gen Y"
} else {
data[i, "Age_group"] <- "Gen Z"
}
}
# TotalChildren
data$TotalChildren = data$Kidhome + data$Teenhome
# Drop anomalies>
#data = data %>% filter(TotalPurchases!=0)
#dim(data)
One hot encoding
References: Jack Daoud’s Notebook and Saeed Jafari’s Notebook.
# One hot encoding
dummy_obj = dummyVars(" ~.", data = data, fullRank = T) #full rank to avoid multi-collinearity
data_onehot = data.frame(predict(dummy_obj, newdata = data))
dim(data)
[1] 2186 34
dim(data_onehot)
[1] 2186 46
colnames(data_onehot)
[1] "Year_Birth" "EducationGraduation" "EducationMaster" "EducationPhD"
[5] "Marital_StatusMarried" "Marital_StatusSingle" "Marital_StatusTogether" "Marital_StatusWidow"
[9] "Kidhome" "Teenhome" "Dt_Customer" "Recency"
[13] "MntWines" "MntFruits" "MntMeatProducts" "MntFishProducts"
[17] "MntSweetProducts" "MntGoldProds" "NumDealsPurchases" "NumWebPurchases"
[21] "NumCatalogPurchases" "NumStorePurchases" "NumWebVisitsMonth" "AcceptedCmp3.1"
[25] "AcceptedCmp4.1" "AcceptedCmp5.1" "AcceptedCmp1.1" "AcceptedCmp2.1"
[29] "Response.1" "Complain.1" "CountryCA" "CountryGER"
[33] "CountryIND" "CountrySA" "CountrySP" "CountryUS"
[37] "Income" "Dt_Customer_num" "TotalPurchases" "TotalProducts"
[41] "ratio_dt" "Age" "Age_groupGen.X" "Age_groupGen.Y"
[45] "Age_groupGen.Z" "TotalChildren"
Section 2: Statistical analysis
2.2: Does US fare significantly better than the Rest of the World in terms of total purchases?
# Label
data22 = data
data22$country2 = ifelse(data22$Country=="US","US","World")
Hmisc::describe(data22$country2)
data22$country2
n missing distinct
2186 0 2
Value US World
Frequency 109 2077
Proportion 0.05 0.95
# Independent t test
t.test(TotalPurchases ~ country2, data= data22, var.equal=TRUE,alternative="greater")
Two Sample t-test
data: TotalPurchases by country2
t = 1.4424, df = 2184, p-value = 0.07467
alternative hypothesis: true difference in means is greater than 0
95 percent confidence interval:
-0.1437916 Inf
sample estimates:
mean in group US mean in group World
13.51376 12.49302
- The independent t-test results suggests that the total purchase of US is not statistically greater than the rest of the world, as the p-value is >0.05.
2.3: People who buy an above average amount on gold in the last 2 years have more in store purchase?
# Label
data23 = data
data23$gold2 = ifelse(data23$MntGoldProds> mean(data23$MntGoldProds),"above_avg","not_above_avg")
Hmisc::describe(data23$gold2)
data23$gold2
n missing distinct
2186 0 2
Value above_avg not_above_avg
Frequency 680 1506
Proportion 0.311 0.689
# Independent t test
t.test(NumStorePurchases ~ gold2, data= data23, var.equal=TRUE,alternative="greater")
Two Sample t-test
data: NumStorePurchases by gold2
t = 20.899, df = 2184, p-value < 2.2e-16
alternative hypothesis: true difference in means is greater than 0
95 percent confidence interval:
2.641222 Inf
sample estimates:
mean in group above_avg mean in group not_above_avg
7.764706 4.897742
- The independent t-test results show that the mean of store purchases for people who buy an above average amount of gold in the last 2 years statistically greater than people who buy a below average amount in the past 2 years, as the p-values is <0.05.
2.5: Is there a significant relationship between geographical regions and sucesss of a compaign?
- Using Response variable (if customer accepted the offer in the last campaign) as an indicator of success
# Summary of groups
data25 = data
data25$Response = as.numeric(data25$Response)
data25 %>% group_by(Country) %>% get_summary_stats(Response, type="mean_sd")
# Compare means of group using ANOVA test
res.aov = data25 %>% anova_test(Response ~ Country)
Coefficient covariances computed by hccm()
res.aov
ANOVA Table (type II tests)
Effect DFn DFd F p p<.05 ges
1 Country 6 2179 1.071 0.378 0.003
- The ANOVA test shows that the differences between the means of offer acceptance between regions are not statistically different, and we can conclude that there is no significant relationship between geographical regions and success of campaigns as the p-value is >0.05.
Section 3: Data Visualizations
3.1: Which marketing campaign is most successful?
# Prepare data
data3 = data %>% select (AcceptedCmp1,AcceptedCmp2,AcceptedCmp3,AcceptedCmp4, AcceptedCmp5)
data3 = mutate_if(data3, is.factor, ~as.numeric(as.character(.x)))
# Plot
CmpLabel = c("2nd campaign","1st campaign","3rd campaign","5th campaign","4th campaign")
data3 %>% gather(Cmp, Outcome, AcceptedCmp1:AcceptedCmp5) %>% filter(Outcome>0) %>% group_by(Cmp) %>% tally() %>% ggplot(aes(x=reorder(Cmp,n), y=n, fill=Cmp)) + geom_col(width=0.7) + geom_text(stat="identity",aes(label=n),hjust=-0.2,size=3.5) + scale_y_continuous(limits=c(0,185)) + coord_flip() + labs(y="", x="",title="Which marketing campaign is the most sucessful?", subtitle ="Number of customers who accepted the offer") + scale_fill_uchicago() + theme_classic() + theme(axis.ticks.x = element_blank(),axis.ticks.y = element_blank(), panel.grid.major.y = element_blank(), panel.grid.minor.x = element_blank(), legend.position= "none", axis.text.x=element_blank()) + scale_x_discrete(label= CmpLabel)

3.2: What does the average customer look like for this company?
# Average Year_Birth and Income
data %>% summarise(avg_Age = round(mean(Age)), avg_Income = round(mean(Income)), avg_Kidhome = round(mean(Kidhome)), avg_Teenhome= round(mean(Teenhome)))
sum(is.na(data$income))
[1] 0
# Income
p1 = data %>% ggplot(aes(x=Income)) + geom_histogram(binwidth=5000, color="#1d3557",fill="#457b9d") + geom_vline(xintercept= 52002, color="#e63946",linetype="dashed") + scale_y_continuous(limits=c(0,200)) + annotate("text",label="Average = $52,002", x= 95000,y=190) + labs(x="", y="Customer Count", title="Yearly household income")
# Age
p2 = data %>% ggplot(aes(x=Age)) + geom_histogram(binwidth=5, color="#1d3557",fill="#457b9d") + geom_vline(xintercept= 45, color="#e63946",linetype="dashed") + annotate("text",label="Average = 45", x= 56,y=360) + labs(title="Customer Age", y="Customer Count",x="")
ggarrange(p1,p2,ncol=2)

# Age group
p2_2 = data %>% group_by(Age_group) %>% tally() %>% ggplot(aes(x=Age_group,y=n, fill=Age_group)) + geom_col(width=0.5) + geom_text(stat="identity",aes(label=n),vjust=-0.3,size=3.5) + scale_fill_uchicago() + theme_classic() + theme(axis.ticks.x = element_blank(),axis.ticks.y = element_blank(), panel.grid.major.y = element_blank(), panel.grid.minor.x = element_blank(), legend.position= "none") + labs(x= "", y="Customer count", title="Age demographic")
# Kidhome
p3 = data %>% group_by(Kidhome) %>% tally() %>% ggplot(aes(x=factor(Kidhome),y=n, fill=n)) + geom_col(width=0.7) + scale_fill_viridis() + theme(legend.position="none") + labs(y="Customer count", x="", title="Number of children in household") + scale_y_continuous(limits=c(0,1300))
# Teenhome
p4 = data %>% group_by(Teenhome) %>% tally() %>% ggplot(aes(x=factor(Teenhome),y=n, fill=n)) + geom_col(width=0.7) + scale_fill_viridis() + theme(legend.position="none") + labs(y="Customer count", x="", title="Number of teens in household") + scale_y_continuous(limits=c(0,1300))
# Marital Status
p5 = data %>% group_by(Marital_Status) %>% tally() %>% ggplot(aes(x=reorder(Marital_Status,n),y=n, fill=n)) + geom_col(width=0.7) + scale_fill_viridis() + theme(legend.position="none") + labs(y="Customer count", x="", title="Marital status") + theme(legend.position="none")
# Education
p6 = data %>% group_by(Education) %>% tally() %>% ggplot(aes(x=reorder(Education,n),y=n, fill=n)) + geom_col(width=0.7) + scale_fill_viridis() + theme(legend.position="none") + labs(y="Customer count", x="", title="Customer's educational level") + theme(legend.position="none")
# Combined plot
ggarrange(p3,p4,p5,p6, ncol=2, nrow=2)

# Marital status and education level
data %>% group_by(Marital_Status,Education) %>% tally(sort=T) %>% mutate(Marital_Edu = paste(Marital_Status, Education, sep="-")) %>% ggplot(aes(x=reorder(Marital_Edu,n), y=n)) + geom_point(color="#f77f00") + geom_segment(aes(x=Marital_Edu, xend=Marital_Edu, y=0, yend=n),color="#335c67") + coord_flip() + theme_light() + theme(panel.border=element_blank(),axis.ticks.x=element_blank(),axis.ticks.y=element_blank(), panel.grid.major.y=element_blank(), panel.grid.minor.x=element_blank()) + labs(y="Customer count", x="", title="Customers' marital status and education level")

LS0tCnRpdGxlOiAiTWFya2V0aW5nIERhdGEgQW5hbHlzaXMgdjIiCmRhdGU6ICJEZWMgMjAyMCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKKipEYXRhc2V0Kio6IFtlQ29tbWVyY2UgTWFya2V0aW5nXShodHRwczovL3d3dy5rYWdnbGUuY29tL2phY2tkYW91ZC9tYXJrZXRpbmctZGF0YSkKCioqVGFzayoqOiBbRXhwbG9yYXRvcnkgYW5kIFN0YXRpc3RpY2FsIEFuYWx5c2lzXShodHRwczovL3d3dy5rYWdnbGUuY29tL2phY2tkYW91ZC9tYXJrZXRpbmctZGF0YS90YXNrcz90YXNrSWQ9Mjk4NikuICAKCioqQ29udGVudHMqKjogCgoqIFNlY3Rpb24gMTogRGF0YSBjbGVhbmluZyBhbmQgYmFzaWMgZXhwbG9yYXRpb24KKiBTZWN0aW9uIDI6IFN0YXRpc3RpY2FsIGFuYWx5c2lzCiogU2VjdGlvbiAzOiBEYXRhIHZpc3VhbGl6YXRpb24KCiMjIyBMb2FkIGxpYnJhcmllcwpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShwbHlyKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShza2ltcikKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkocmVzaGFwZSkKbGlicmFyeShIbWlzYykKbGlicmFyeShjb3JycGxvdCkKbGlicmFyeSh0aWJibGUpCmxpYnJhcnkodmlyaWRpcykKbGlicmFyeShyc3RhdGl4KQpsaWJyYXJ5KHdlc2FuZGVyc29uKQpsaWJyYXJ5KGdnc2NpKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KGdncHVicikKbGlicmFyeShqYW5pdG9yKQpgYGAKCiMjIyBJbXBvcnQgRGF0YQpgYGB7cn0KZGF0YSA9IHJlYWQuY3N2KCJtYXJrZXRpbmdfZGF0YS5jc3YiLGhlYWRlcj1UUlVFKQpkaW0oZGF0YSkKc3RyKGRhdGEpCmBgYAoKKipEYXRhc2V0IHZhcmlhYmxlcyoqIGZyb20gW0phY2sgRGFvdWQncyBOb3RlYm9va10oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9qYWNrZGFvdWQvZWNvbW1lcmNlLW1hcmtldGluZy1lZGEtc3RhdGlzdGljcy1hbmFseXNpcy9jb21tZW50cyk6IAoKX1Blb3BsZV8KCiogSUQ6IEN1c3RvbWVyJ3MgdW5pcXVlIGlkZW50aWZpZXIgICAKKiBZZWFyX0JpcnRoOiBDdXN0b21lcidzIGJpcnRoIHllYXIgICAKKiBFZHVjYXRpb246IEN1c3RvbWVyJ3MgZWR1Y2F0aW9uIGxldmVsCiogTWFyaXRhbF9TdGF0dXM6IEN1c3RvbWVyJ3MgbWFyaXRhbCBzdGF0dXMKKiBJbmNvbWU6IEN1c3RvbWVyJ3MgeWVhcmx5IGhvdXNlaG9sZCBpbmNvbWUKKiBLaWRob21lOiBOdW1iZXIgb2YgY2hpbGRyZW4gaW4gY3VzdG9tZXIncyBob3VzZWhvbGQKKiBUZWVuaG9tZTogTnVtYmVyIG9mIHRlZW5hZ2VycyBpbiBjdXN0b21lcidzIGhvdXNlaG9sZAoqIER0X0N1c3RvbWVyOiBEYXRlIG9mIGN1c3RvbWVyJ3MgZW5yb2xsbWVudCB3aXRoIHRoZSBjb21wYW55CiogUmVjZW5jeTogTnVtYmVyIG9mIGRheXMgc2luY2UgY3VzdG9tZXIncyBsYXN0IHB1cmNoYXNlCiogQ29tcGxhaW46IDEgaWYgY3VzdG9tZXIgY29tcGxhaW5lZCBpbiB0aGUgbGFzdCAyIHllYXJzLCAwIG90aGVyd2lzZQoqIENvdW50cnk6IEN1c3RvbWVyJ3MgbG9jYXRpb24KCl9Qcm9kdWN0c18KCiogTW50V2luZXM6IEFtb3VudCBzcGVudCBvbiB3aW5lIGluIHRoZSBsYXN0IDIgeWVhcnMKKiBNbnRGcnVpdHM6IEFtb3VudCBzcGVudCBvbiBmcnVpdHMgaW4gdGhlIGxhc3QgMiB5ZWFycwoqIE1udE1lYXRQcm9kdWN0czogQW1vdW50IHNwZW50IG9uIG1lYXQgaW4gdGhlIGxhc3QgMiB5ZWFycwoqIE1udEZpc2hQcm9kdWN0czogQW1vdW50IHNwZW50IG9uIGZpc2ggaW4gdGhlIGxhc3QgMiB5ZWFycwoqIE1udFN3ZWV0UHJvZHVjdHM6IEFtb3VudCBzcGVudCBvbiBzd2VldHMgaW4gdGhlIGxhc3QgMiB5ZWFycwoqIE1udEdvbGRQcm9kczogQW1vdW50IHNwZW50IG9uIGdvbGQgaW4gdGhlIGxhc3QgMiB5ZWFycwoKX1BsYWNlXwoKKiBOdW1XZWJQdXJjaGFzZXM6IE51bWJlciBvZiBwdXJjaGFzZXMgbWFkZSB0aHJvdWdoIHRoZSBjb21wYW55J3Mgd2ViIHNpdGUKKiBOdW1DYXRhbG9nUHVyY2hhc2VzOiBOdW1iZXIgb2YgcHVyY2hhc2VzIG1hZGUgdXNpbmcgYSBjYXRhbG9ndWUKKiBOdW1TdG9yZVB1cmNoYXNlczogTnVtYmVyIG9mIHB1cmNoYXNlcyBtYWRlIGRpcmVjdGx5IGluIHN0b3JlcwoqIE51bVdlYlZpc2l0c01vbnRoOiBOdW1iZXIgb2YgdmlzaXRzIHRvIGNvbXBhbnkncyB3ZWIgc2l0ZSBpbiB0aGUgbGFzdCBtb250aAoKX1Byb21vdGlvbl8KCiogTnVtRGVhbHNQdXJjaGFzZXM6IE51bWJlciBvZiBwdXJjaGFzZXMgbWFkZSB3aXRoIGEgZGlzY291bnQKKiBBY2NlcHRlZENtcDM6IDEgaWYgY3VzdG9tZXIgYWNjZXB0ZWQgdGhlIG9mZmVyIGluIHRoZSAzcmQgY2FtcGFpZ24sIDAgb3RoZXJ3aXNlCiogQWNjZXB0ZWRDbXA0OiAxIGlmIGN1c3RvbWVyIGFjY2VwdGVkIHRoZSBvZmZlciBpbiB0aGUgNHRoIGNhbXBhaWduLCAwIG90aGVyd2lzZQoqIEFjY2VwdGVkQ21wNTogMSBpZiBjdXN0b21lciBhY2NlcHRlZCB0aGUgb2ZmZXIgaW4gdGhlIDV0aCBjYW1wYWlnbiwgMCBvdGhlcndpc2UKKiBBY2NlcHRlZENtcDE6IDEgaWYgY3VzdG9tZXIgYWNjZXB0ZWQgdGhlIG9mZmVyIGluIHRoZSAxc3QgY2FtcGFpZ24sIDAgb3RoZXJ3aXNlCiogQWNjZXB0ZWRDbXAyOiAxIGlmIGN1c3RvbWVyIGFjY2VwdGVkIHRoZSBvZmZlciBpbiB0aGUgMm5kIGNhbXBhaWduLCAwIG90aGVyd2lzZQoqIFJlc3BvbnNlOiAxIGlmIGN1c3RvbWVyIGFjY2VwdGVkIHRoZSBvZmZlciBpbiB0aGUgbGFzdCBjYW1wYWlnbiwgMCBvdGhlcndpc2UKCgojIyMgU2VjdGlvbiAxOiBEYXRhIGNsZWFuaW5nIGFuZCBiYXNpYyBleHBsb3JhdGlvbgoKYGBge3J9CiMgRm9ybWF0IGRhdGVzIGluIER0X0N1c3RvbWVyCmRhdGEkRHRfQ3VzdG9tZXIgPSBtZHkoZGF0YSREdF9DdXN0b21lcikKc3VtbWFyeShkYXRhJER0X0N1c3RvbWVyKQoKIyBEcm9wIHNwZWNpYWwgY2hhcmFjdGVycyBpbiBJbmNvbWUgCmRhdGEkSW5jb21lID0gZ3N1YignWyRdKFswLTldKylbLF0oWzAtOV0rKScsJ1xcMVxcMicsZGF0YSRJbmNvbWUpCmRhdGEkSW5jb21lID0gYXMubnVtZXJpYyhkYXRhJEluY29tZSkKYGBgCgoqIFRoZSBlYXJsaWVzdCBjdXN0b21lciBlbnJvbGxtZW50IGRhdGUgaW4gdGhlIGRhdGFzZXQgaXMgMjAxMi0wNy0zMCBhbmQgbGF0ZXN0IDIwMTQtMDYtMjkKCgpgYGB7cn0KIyBNaXNzaW5nIGRhdGEgYW5hbHlzaXMKZGF0YS5mcmFtZSgiTkFfQ291bnQiID0gc2FwcGx5KGRhdGEsIGZ1bmN0aW9uKHgpIHsgbGVuZ3RoKHdoaWNoKGlzLm5hKHgpKSkgfSkpICU+JSAKICBmaWx0ZXIoTkFfQ291bnQgPiAwKSAlPiUgCiAgYXJyYW5nZSguLCBkZXNjKE5BX0NvdW50KSkKYGBgCgoqIFRoZXJlIGFyZSAyNCBvYnNlcnZhdGlvbnMgd2l0aG91dCBpbmNvbWUgc3BlY2lmaWVkLgoKYGBge3J9CiMgVW5pcXVlIGxldmVscyBpbiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMKc3VtbWFyeShhcy5mYWN0b3IoZGF0YSRFZHVjYXRpb24pKQpzdW1tYXJ5KGFzLmZhY3RvcihkYXRhJE1hcml0YWxfU3RhdHVzKSkKc3VtbWFyeShhcy5mYWN0b3IoZGF0YSRDb3VudHJ5KSkKYGBgCgoqIE1hc3RlciBlZHVjYXRpb24gbGV2ZWwgYXJlIGFsc28gaW5kaWNhdGVkIGFzIDJuIGN5Y2xlcyBpbiB0aGUgZGF0YXNldAoqIFRoZXJlIGFyZSBzb21lIG9kZCBsZXZlbHMgaW4gdGhlIE1hcml0YWwgU3RhdHVzIGxldmVsIGkuZS4sIEFic3VyZCwgQWxvbmUgYW5kIFlPTE8uIAoqIFRoZXJlIGFyZSBvbmx5IDMgb2JzZXJ2YXRpb25zIHdpdGggY291bnRyeSBsaXN0ZWQgYXMgX01FXwoKYGBge3J9CiMgUmVjb2RlIDJuIGN5Y2xlIHRvIG1hc3RlciAKcmV2YWx1ZShkYXRhJEVkdWNhdGlvbixjKCIybiBDeWNsZSIgPSAiTWFzdGVyIikpIC0+IGRhdGEkRWR1Y2F0aW9uCnN1bW1hcnkoYXMuZmFjdG9yKGRhdGEkRWR1Y2F0aW9uKSkKIyBSZWNvZGUgWU9MTywgQWxvbmUsIEFic3VyZCB0byBTaW5nbGUgCmRhdGEkTWFyaXRhbF9TdGF0dXNbZGF0YSRNYXJpdGFsX1N0YXR1cyA9PSAiWU9MTyIgfAogICAgICAgICAgICAgICAgICAgIGRhdGEkTWFyaXRhbF9TdGF0dXMgPT0gIkFic3VyZCIgfAogICAgICAgICAgICAgICAgICAgIGRhdGEkTWFyaXRhbF9TdGF0dXMgPT0gIkFsb25lIl0gPC0gIlNpbmdsZSIKc3VtbWFyeShhcy5mYWN0b3IoZGF0YSRNYXJpdGFsX1N0YXR1cykpCmBgYAoKIyMjIyBNaXNzaW5nIGRhdGEgaW1wdXRhdGlvbgpSZWZlcmVuY2VzOiBbSmFjayBEYW91ZCdzIE5vdGVib29rXShodHRwczovL3d3dy5rYWdnbGUuY29tL2phY2tkYW91ZC9lY29tbWVyY2UtbWFya2V0aW5nLWVkYS1zdGF0aXN0aWNzLWFuYWx5c2lzL2NvbW1lbnRzKSBhbmQgW1NhZWVkIEphZmFyaSdzIE5vdGVib29rXShodHRwczovL3d3dy5rYWdnbGUuY29tL3NhZWVkamFmYXJpL3NlY3Rpb24tMS9jb21tZW50cykuIAoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CiMgU3VtbWFyeSBvZiBJbmNvbWUgZm9yIGVhY2ggRWR1Y2F0aW9uIGxldmVsCmxpYnJhcnkocHN5Y2gpCmRlc2NyaWJlQnkoZGF0YSRJbmNvbWUsIGRhdGEkRWR1Y2F0aW9uLCBtYXQgPSBUUlVFKSAKYGBgCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KIyBUYWJsZSBvZiBtZWFuIHZhbHVlcwojZGV0YWNoKHBhY2thZ2U6cGx5cikKYXZlcmFnZV9pbmNvbWUgPSBkYXRhICU+JSBncm91cF9ieShFZHVjYXRpb24pICU+JSBzdW1tYXJpc2UoYXZnX0luY29tZSA9IG1lYW4oSW5jb21lLG5hLnJtID0gVFJVRSkpICU+JSBhcy5kYXRhLmZyYW1lKCkKaGVhZChhdmVyYWdlX2luY29tZSkKYGBgCgpgYGB7cn0KIyBKb2luCmltcHV0X2RhdGEgPSBsZWZ0X2pvaW4oZGF0YVtpcy5uYShkYXRhJEluY29tZSksXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXZlcmFnZV9pbmNvbWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygiRWR1Y2F0aW9uIikpIAoKIyBSZXBsYWNlIG1pc3NpbmcgdmFsdWVzIHdpdGggQXZnX0luY29tZQppbXB1dF9kYXRhJEluY29tZSA9IGltcHV0X2RhdGEkYXZnX0luY29tZSAKIyBkcm9wIGF2Z19JbmNvbWUgY29sCmltcHV0X2RhdGEgPC0gaW1wdXRfZGF0YSAlPiUgc2VsZWN0KC0iYXZnX0luY29tZSIpIAoKaW1wdXRfZGF0YQpgYGAKCmBgYHtyfQojIEZ1bGwgaW5wdXQgZGF0YSB3aXRoIGZ1bGwgZGF0YXNldAptYXJrZXRpbmdfZGF0YSA8LSBmdWxsX2pvaW4oZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGltcHV0X2RhdGEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCdJRCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1llYXJfQmlydGgnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdFZHVjYXRpb24nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdNYXJpdGFsX1N0YXR1cycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0tpZGhvbWUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdUZWVuaG9tZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0R0X0N1c3RvbWVyJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnUmVjZW5jeScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ01udFdpbmVzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTW50RnJ1aXRzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTW50TWVhdFByb2R1Y3RzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTW50RmlzaFByb2R1Y3RzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTW50U3dlZXRQcm9kdWN0cycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ01udEdvbGRQcm9kcycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ051bURlYWxzUHVyY2hhc2VzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTnVtV2ViUHVyY2hhc2VzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTnVtQ2F0YWxvZ1B1cmNoYXNlcycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ051bVN0b3JlUHVyY2hhc2VzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTnVtV2ViVmlzaXRzTW9udGgnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdBY2NlcHRlZENtcDMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdBY2NlcHRlZENtcDQnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdBY2NlcHRlZENtcDUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdBY2NlcHRlZENtcDEnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdBY2NlcHRlZENtcDInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdSZXNwb25zZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0NvbXBsYWluJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnQ291bnRyeScpKQoKIyBtZXJnZSBJbmNvbWUgY29sdW1ucyBpbnRvIGEgc2luZ2xlIGNvbHVtbgptYXJrZXRpbmdfZGF0YSA8LSBtYXJrZXRpbmdfZGF0YSAgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgIG11dGF0ZShJbmNvbWUgPSBjb2FsZXNjZShJbmNvbWUueCwgSW5jb21lLnkpKSAlPiUKICAgc2VsZWN0KC1jKCJJbmNvbWUueCIsICJJbmNvbWUueSIpKQoKaGVhZChtYXJrZXRpbmdfZGF0YSkKCmBgYApgYGB7cn0KIyBDaGVjayBmb3IgbWlzc2luZyB2YWx1ZXMgYWZ0ZXIgaW5wdXRhdGlvbgpzdW0oaXMubmEobWFya2V0aW5nX2RhdGEkSW5jb21lKSkKc3VtKGlzLm5hKG1hcmtldGluZ19kYXRhKSkKYGBgCgojIyMjIER1cGxpY2F0ZXMgCgpgYGB7cn0KIyBDaGVjayBmb3IgZHVwbGljYXRlcyBpbiBJRCBjb2wKbGVuZ3RoKHVuaXF1ZShkYXRhJElEKSkgCiMgRHJvcCAzIG9icyB3aXRoIGNvdW50cnkgbGlzdGVkIGFzIE1FCm1hcmtldGluZ19kYXRhID0gbWFya2V0aW5nX2RhdGEgJT4lIGZpbHRlcihDb3VudHJ5IT0iTUUiKQpkaW0obWFya2V0aW5nX2RhdGEpCnN1bW1hcnkoYXMuZmFjdG9yKG1hcmtldGluZ19kYXRhJENvdW50cnkpKQpgYGAKCgpgYGB7cn0KI2V4YW1pbmUgZHVwbGljYXRlZCBjYXNlcyB1c2luZyBhbGwgY29scyBleGNlcHQgZm9yIElECmR1cGVzID0gbWFya2V0aW5nX2RhdGEgJT4lIGdldF9kdXBlcyhjKC1JRCkpCmRpbShkdXBlcykKaGVhZChkdXBlcykKYGBgCgoqIEFsdGhvdWdoIG5vIGR1cGxpY2F0ZXMgd2VyZSBmb3VuZCBiYXNlZCBvbiBJRCBjb2x1bW4sIHRoZXJlIGFyZSA5NCBwcm9ibGVtYXRpYyBjYXNlcyAoNDcgZHVwbGljYXRlcykgZGV0ZWN0ZWQgYmFzZWQgb24gdGhlIHJlc3Qgb2YgdGhlIGNvbHVtbnMuIAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgZGF0YWZyYW1lIHdpdGhvdXQgZHVwbGljYXRlcwpsZW5ndGgodW5pcXVlKG1hcmtldGluZ19kYXRhJElEKSkgCmRhdGEgPSBtYXJrZXRpbmdfZGF0YSAlPiUgZGlzdGluY3RfYXQodmFycygtSUQpKQpkaW0oZGF0YSkKYGBgCgoKIyMjIyBPdXRsaWVycyBhbmQgQW5vbWFsaWVzIAoKYGBge3J9CiMgQ2hhbmdlIHZhcmlhYmxlcyB0eXBlcwpkYXRhID0gZGF0YSAlPiUgbXV0YXRlX2F0KHZhcnMoQWNjZXB0ZWRDbXAzLEFjY2VwdGVkQ21wNCxBY2NlcHRlZENtcDUsQWNjZXB0ZWRDbXAxLEFjY2VwdGVkQ21wMixSZXNwb25zZSxDb21wbGFpbiksbGlzdChmYWN0b3IpKQoKIyBTY2FsZSBudW1lcmljIGRhdGEKbnVtZXJpY19kYXRhID0gZGF0YSAlPiUgc2VsZWN0KHdoZXJlKGlzLm51bWVyaWMpKQp6X2RhdGEgPSBkYXRhLmZyYW1lKHNjYWxlKG51bWVyaWNfZGF0YSkpCnpfZGF0YSR0eXBlID0gcm93bmFtZXMoZGF0YSkKCiMgUGxvdCBhbGwgbnVtZXJpYyB2YXJpYWJsZXMKel9kYXRhICU+JSAKICBwaXZvdF9sb25nZXIoY29scz0tdHlwZSkgJT4lIAogIGdncGxvdChhZXMoeCA9IG5hbWUsIHkgPSB2YWx1ZSkpICsKICAgIGdlb21fYm94cGxvdCgpICsKICAgIHRoZW1lX2NsYXNzaWMoKSArCiAgICBleHBhbmRfbGltaXRzKHg9MTIuNikgKyBjb29yZF9mbGlwKCkKYGBgCiogVGhlIGJveHBsb3Qgb2YgYWxsIG51bWVyaWMgdmFyaWFibGVzIHNob3cgdGhhdCB0aGVyZSBhcmUgb3V0bGllcnMgaW4gbXVsdGlwbGUgZmVhdHVyZXMsIGluIHBhcnRpY3VsYXI6CiAgKiBZZWFyX0JpcnRoIHZhcmlhYmxlIGNvbnRhaW5zIDMgdmFsdWVzIGJlZm9yZSAxOTAwIAogICogSW5jb21lIGhhcyBhbiBleHRyZW1lIG91dGxpZXIgaS5lLiAkNjY2LDY2NgoqIE9ic2VydmF0aW9ucyB3aXRoIHRoZSBhYm92ZS1tZW50aW9uZWQgcG9pbnRzIGFyZSBkcm9wcGVkLCB3aGlsZSB0aGUgb3RoZXIgb3V0bGllcnMgaWRlbnRpZmllZCByZW1haW5zIGluIHRoZSBkYXRhZnJhbWUgdG8gYXZvaWQgbG9zaW5nIHRvbyBtdWNoIGluZm9ybWF0aW9uLiAKCmBgYHtyfQojIERyb3Agb2JzIHdpdGggb3V0bGllcnMgaW4gSW5jb21lIGFuZCBZZWFyX0JpcnRoIApkYXRhID0gZGF0YSAlPiUgZmlsdGVyKCFZZWFyX0JpcnRoPDE5MzApICU+JSBmaWx0ZXIoIUluY29tZT09NjY2NjY2KQpkaW0oZGF0YSkKYGBgCgoKIyMjIyBGZWF0dXJlIENvbnN0cnVjdGlvbgoKYGBge3J9CiMgRmVhdHVyZSBjb25zdHJ1Y3Rpb24KIyBOdW1lcmljIHZhcmlhYmxlIGZyb20gZGF0ZSAoRHRfQ3VzdG9tZXIpCmRhdGEkRHRfQ3VzdG9tZXJfbnVtID0gYXMubnVtZXJpYyhkYXRhJER0X0N1c3RvbWVyKQpzdW1tYXJ5KGRhdGEkRHRfQ3VzdG9tZXJfbnVtKQoKIyBUb3RhbFB1cmNoYXNlcwpkYXRhJFRvdGFsUHVyY2hhc2VzID0gZGF0YSROdW1XZWJQdXJjaGFzZXMgKyBkYXRhJE51bUNhdGFsb2dQdXJjaGFzZXMgKyBkYXRhJE51bVN0b3JlUHVyY2hhc2VzCnN1bW1hcnkoZGF0YSRUb3RhbFB1cmNoYXNlcykKIyBPYnMgd2l0aG91dCBwdXJjaGFzZXMKZGF0YSAlPiUgZmlsdGVyKFRvdGFsUHVyY2hhc2VzPT0wKQoKIyBUb3RhbFByb2R1Y3RzCmRhdGEkVG90YWxQcm9kdWN0cyA9IGRhdGEkTW50V2luZXMgKyBkYXRhJE1udEZydWl0cyArIGRhdGEkTW50TWVhdFByb2R1Y3RzICsgZGF0YSRNbnRGaXNoUHJvZHVjdHMgKyBkYXRhJE1udFN3ZWV0UHJvZHVjdHMgKyBkYXRhJE1udEdvbGRQcm9kcwpzdW1tYXJ5KGRhdGEkVG90YWxQcm9kdWN0cykKCiMgUGxvdCBUb3RhbFB1cmNoYXNlcyBhbmQgVG90YWxQcm9kdWN0cwpkYXRhICU+JSBnZ3Bsb3QoYWVzKHg9VG90YWxQdXJjaGFzZXMseT1Ub3RhbFByb2R1Y3RzKSkgKyBnZW9tX3BvaW50KGFscGhhPTAuNSkKCiMgUGxvdCB0b3RhbCBwcm9kdWN0cyBvZiBUb3RhbFB1cmNoYXNlcz09MApkYXRhICU+JSBmaWx0ZXIoVG90YWxQdXJjaGFzZXM9PTApICU+JSBnZ3Bsb3QoYWVzKHg9VG90YWxQdXJjaGFzZXMseT1Ub3RhbFByb2R1Y3RzKSkgKyBnZW9tX2NvdW50KCkgKyBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMC4wMCwxMC4wMCkpICsgc2NhbGVfc2l6ZV9jb250aW51b3VzKGJyZWFrcz1yb3VuZCkKYGBgCgoqIFRoZXJlIGFyZSA2IG9icyB3aXRoIG5vIHB1cmNoYXNlcyBidXQgaGF2ZSBhbW91bnQgc3BlbnQgb24gcHJvZHVjdHMuCgpgYGB7cn0KIyBHZXQgcmF0aW8gb2YgTnVtRGVhbHNQdXJjaGFzZWQgdG8gVG90YWxQdXJjaGFzZXMKZGF0YSA9IGRhdGEgJT4lIG11dGF0ZShyYXRpb19kdCA9IHJvdW5kKChOdW1EZWFsc1B1cmNoYXNlcy9Ub3RhbFB1cmNoYXNlcyksMykpICU+JSBtdXRhdGUocmF0aW9fZHQgPSBuYV9pZihyYXRpb19kdCxJbmYpKQpzdW1tYXJ5KGRhdGEkcmF0aW9fZHQpCmBgYAoKKiBBcyB0aGVyZSBhcmUgNiBvYnMgd2l0aCBubyBwdXJjaGFzZXMsIHRoZXJlIGFyZSA2IE5BcyBpbiB0aGUgdmFyaWFibGUgcmF0aW9fZHQuIAoKVGhlIHZhcmlhYmxlcyBBZ2UsIEFnZV9ncm91cCBhbmQgVG90YWwgQ2hpbGRyZW4gYXJlIGluc3BpcmVkIGJ5IFtKYWNrIERhb3VkJ3MgTm90ZWJvb2tdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vamFja2Rhb3VkL2Vjb21tZXJjZS1tYXJrZXRpbmctZWRhLXN0YXRpc3RpY3MtYW5hbHlzaXMvY29tbWVudHMpCmBgYHtyfQojIEFnZQpkYXRhJEFnZSA9IDIwMTQgLSBkYXRhJFllYXJfQmlydGgKCiMgQWdlX2dyb3VwCmRhdGEkQWdlX2dyb3VwIDwtICIiCmZvciAoaSBpbiAxOm5yb3coZGF0YSkpIHsKICAgIGlmIChkYXRhW2ksICJBZ2UiXSA+IDU0KSB7CiAgICAgICAgZGF0YVtpLCAiQWdlX2dyb3VwIl0gPC0gIkJhYnkgQm9vbWVyIgogICAgfSBlbHNlIGlmIChkYXRhW2ksICJBZ2UiXSA+IDM4KSB7CiAgICAgICAgZGF0YVtpLCAiQWdlX2dyb3VwIl0gPC0gIkdlbiBYIgogICAgfSBlbHNlIGlmIChkYXRhW2ksICJBZ2UiXSA+IDE4KSB7CiAgICAgICAgZGF0YVtpLCAiQWdlX2dyb3VwIl0gPC0gIkdlbiBZIgogICAgfSBlbHNlIHsKICAgICAgICBkYXRhW2ksICJBZ2VfZ3JvdXAiXSA8LSAiR2VuIFoiCiAgICB9Cn0KCiMgVG90YWxDaGlsZHJlbgpkYXRhJFRvdGFsQ2hpbGRyZW4gPSBkYXRhJEtpZGhvbWUgKyBkYXRhJFRlZW5ob21lCmBgYAoKCmBgYHtyfQojIERyb3AgYW5vbWFsaWVzPiAKI2RhdGEgPSBkYXRhICU+JSBmaWx0ZXIoVG90YWxQdXJjaGFzZXMhPTApCiNkaW0oZGF0YSkKYGBgCgojIyMjIE9uZSBob3QgZW5jb2RpbmcKUmVmZXJlbmNlczogW0phY2sgRGFvdWQncyBOb3RlYm9va10oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9qYWNrZGFvdWQvZWNvbW1lcmNlLW1hcmtldGluZy1lZGEtc3RhdGlzdGljcy1hbmFseXNpcy9jb21tZW50cykgYW5kIFtTYWVlZCBKYWZhcmkncyBOb3RlYm9va10oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9zYWVlZGphZmFyaS9zZWN0aW9uLTEvY29tbWVudHMpLgoKYGBge3J9CiMgT25lIGhvdCBlbmNvZGluZyAKZHVtbXlfb2JqID0gZHVtbXlWYXJzKCIgfi4iLCBkYXRhID0gZGF0YSwgZnVsbFJhbmsgPSBUKSAjZnVsbCByYW5rIHRvIGF2b2lkIG11bHRpLWNvbGxpbmVhcml0eSAKZGF0YV9vbmVob3QgPSBkYXRhLmZyYW1lKHByZWRpY3QoZHVtbXlfb2JqLCBuZXdkYXRhID0gZGF0YSkpCmRpbShkYXRhKQpkaW0oZGF0YV9vbmVob3QpCmNvbG5hbWVzKGRhdGFfb25laG90KQpgYGAKCgojIyMgU2VjdGlvbiAyOiBTdGF0aXN0aWNhbCBhbmFseXNpcwoKIyMjIyAyLjE6IFdoYXQgZmFjdG9ycyBhcmUgc2lnbmlmaWNhbnRseSByZWxhdGVkIHRvIHRoZSBudW1iZXIgb2Ygc3RvcmUgcHVyY2hhc2VzCmBgYHtyfQojIFJlZ3Jlc3Npb24KZGF0YV8yMSA9IGRhdGFbLWMoMSwyOCwyOSwzMCwzMSwzMywzNCldCmxpbmVhck1vZDEgPSBsbShOdW1TdG9yZVB1cmNoYXNlc34gLiwgZGF0YSA9IGRhdGFfMjEpCnN1bW1hcnkobGluZWFyTW9kMSkKYGBgCgoqIFRoZSB2YXJpYWJsZXMgaW4gdGhlIHJlZ3Jlc3Npb24gbW9kZWwgYXJlIGpvaW50bHkgc2lnbmlmaWNhbnQgYXMgRi1zdGF0aXN0aWMgcC12YWx1ZSBpcyA8MC4wNS4KKiBUaGUgYWJvdmUgcmVncmVzc2lvbiBtb2RlbCBzaG93IHRoYXQgZmFjdG9ycyBiZWxvdyBhcmUgc2lnbmlmaWNhbnRseSByZWxhdGVkIHRvIHRoZSBudW1iZXIgb2Ygc3RvcmUgcHVyY2hhc2VzCiAgKyB5ZWFybHkgaG91c2Vob2xkIGluY29tZQogICsgbnVtYmVyIG9mIGtpZHMgaW4gIGhvdXNlaG9sZAogICsgYW1vdW50IHNwZW50IG9uOiB3aW5lLCBmcnVpdHMsIG1lYXQgcHJvZHVjdHMsIHN3ZWV0IHByb2R1Y3RzIGluIHRoZSBsYXN0IDIgeWVhcnMKICArIG51bWJlciBvZiBwdXJjaGFzZXMgbWFkZTogd2l0aCBhIGRpc2NvdW50LCB0aHJvdWdoIHdlYnNpdGUsIHVzaW5nIGEgY2F0YWxvZwogICsgbnVtYmVyIG9mIHdlYnNpdGUgdmlzaXRzIGluIHRoZSBsYXN0IG1vbnRoIAogICsgYWNjZXB0YW5jZSBvZiBvZmZlciBpbjogM3JkLCA1dGggY2FtcGFpZ24gCiAgKyBkYXRlIG9mIGN1c3RvbWVyJ3MgZW5yb2xsbWVudCB3aXRoIHRoZSBjb21wYW55CgojIyMjIDIuMjogRG9lcyBVUyBmYXJlIHNpZ25pZmljYW50bHkgYmV0dGVyIHRoYW4gdGhlIFJlc3Qgb2YgdGhlIFdvcmxkIGluIHRlcm1zIG9mIHRvdGFsIHB1cmNoYXNlcz8gCmBgYHtyfQojIExhYmVsCmRhdGEyMiA9IGRhdGEKZGF0YTIyJGNvdW50cnkyID0gaWZlbHNlKGRhdGEyMiRDb3VudHJ5PT0iVVMiLCJVUyIsIldvcmxkIikKSG1pc2M6OmRlc2NyaWJlKGRhdGEyMiRjb3VudHJ5MikgCgojIEluZGVwZW5kZW50IHQgdGVzdAp0LnRlc3QoVG90YWxQdXJjaGFzZXMgfiBjb3VudHJ5MiwgZGF0YT0gZGF0YTIyLCB2YXIuZXF1YWw9VFJVRSxhbHRlcm5hdGl2ZT0iZ3JlYXRlciIpCmBgYAoKKiBUaGUgaW5kZXBlbmRlbnQgdC10ZXN0IHJlc3VsdHMgc3VnZ2VzdHMgdGhhdCB0aGUgdG90YWwgcHVyY2hhc2Ugb2YgVVMgaXMgbm90IHN0YXRpc3RpY2FsbHkgZ3JlYXRlciB0aGFuIHRoZSByZXN0IG9mIHRoZSB3b3JsZCwgYXMgdGhlIHAtdmFsdWUgaXMgPjAuMDUuIAoKIyMjIyAyLjM6IFBlb3BsZSB3aG8gYnV5IGFuIGFib3ZlIGF2ZXJhZ2UgYW1vdW50IG9uIGdvbGQgaW4gdGhlIGxhc3QgMiB5ZWFycyBoYXZlIG1vcmUgaW4gc3RvcmUgcHVyY2hhc2U/CmBgYHtyfQojIExhYmVsCmRhdGEyMyA9IGRhdGEKZGF0YTIzJGdvbGQyID0gaWZlbHNlKGRhdGEyMyRNbnRHb2xkUHJvZHM+IG1lYW4oZGF0YTIzJE1udEdvbGRQcm9kcyksImFib3ZlX2F2ZyIsIm5vdF9hYm92ZV9hdmciKQpIbWlzYzo6ZGVzY3JpYmUoZGF0YTIzJGdvbGQyKQoKIyBJbmRlcGVuZGVudCB0IHRlc3QKdC50ZXN0KE51bVN0b3JlUHVyY2hhc2VzIH4gZ29sZDIsIGRhdGE9IGRhdGEyMywgdmFyLmVxdWFsPVRSVUUsYWx0ZXJuYXRpdmU9ImdyZWF0ZXIiKQpgYGAKCiogVGhlIGluZGVwZW5kZW50IHQtdGVzdCByZXN1bHRzIHNob3cgdGhhdCB0aGUgbWVhbiBvZiBzdG9yZSBwdXJjaGFzZXMgZm9yIHBlb3BsZSB3aG8gYnV5IGFuIGFib3ZlIGF2ZXJhZ2UgYW1vdW50IG9mIGdvbGQgaW4gdGhlIGxhc3QgMiB5ZWFycyBzdGF0aXN0aWNhbGx5IGdyZWF0ZXIgdGhhbiBwZW9wbGUgd2hvIGJ1eSBhIGJlbG93IGF2ZXJhZ2UgYW1vdW50IGluIHRoZSBwYXN0IDIgeWVhcnMsIGFzIHRoZSBwLXZhbHVlcyBpcyA8MC4wNS4gIAoKCiMjIyMgMi40OiBEbyBtYXJyaWVkIFBoRCBjb3VwbGVzIGhhdmUgYSBzaWduaWZpY2FudCByZWxhdGlvbiB3aXRoIGFtb3VudCBzcGVudCBvbiBmaXNoPyBXaGF0IG90aGVyIGZhY3RvcnMgYXJlIHNpZ25pZmljYW50bHkgcmVsYXRlZCB0byBhbW91bnQgc3BlbnQgb24gZmlzaD8gCgpgYGB7cn0KIyBMYWJlbDogUGhEIGNvdXBsZXMgCmRhdGEyNCA9IGRhdGEKZGF0YTI0JFBoRGNvdXBsZXMgPSBpZmVsc2UoZGF0YTI0JE1hcml0YWxfU3RhdHVzID09Ik1hcnJpZWQiICYgZGF0YTI0JEVkdWNhdGlvbiA9PSJQaEQiLCAiMSIsIjAiKQpIbWlzYzo6ZGVzY3JpYmUoZGF0YTI0JFBoRGNvdXBsZXMpCmBgYAoKKiBUaGVyZSBhcmUgMTg4IG1hcnJpZWQgUGhEIGNvdXBsZXMgaW4gdGhlIGRhdGFzZXQuIAoKYGBge3J9CiMgUmVncmVzc2lvbgpkYXRhMjRfbW9kZWwgPSBkYXRhMjRbLWMoMSwyLDMsMjgsMjksMzAsMzEsMzMsMzQpXQpsaW5lYXJNb2QyID0gbG0oTW50RmlzaFByb2R1Y3RzfiAuLCBkYXRhID0gZGF0YTI0X21vZGVsKQpzdW1tYXJ5KGxpbmVhck1vZDIpCmBgYAoKKiBUaGUgdmFyaWFibGVzIGluIHRoZSByZWdyZXNzaW9uIG1vZGVsIGFyZSBqb2ludGx5IHNpZ25pZmljYW50IGFzIEYtc3RhdGlzdGljIHAtdmFsdWUgaXMgPDAuMDUuCiogVGhlIHJlZ3Jlc3Npb24gbW9kZWwgc3VnZ2VzdHMgdGhlIGZvbGxvd2luZyBmZWF0dXJlcyBoYXZlIGEgc2lnbmlmaWNhbnQgcmVsYXRpb24gdG8gYW1vdW50IHNwZW50IG9uIGZpc2ggcHJvZHVjdHMgaW4gdGhlIHBhc3QgMiB5ZWFyczogCiAgKyBQaEQgY291cGxlcwogICsgbnVtYmVyIG9mIHRlZW5zIGluIGN1c3RvbWVycycgaG91c2Vob2xkCiAgKyBhbW91bnQgc3BlbnQgb246IGZydWl0cywgbWVhdCBwcm9kdWN0cywgc3dlZXQgcHJvZHVjdHMsIGdvbGQgcHJvZHVjdHMgaW4gdGhlIGxhc3QgMiB5ZWFycwogICsgbnVtYmVyIG9mIHB1cmNoYXNlcyBtYWRlOiB1c2luZyBhIGNhdGFsb2csIGRpcmVjdGx5IGluIHN0b3JlcwogICsgbnVtYmVyIG9mIHdlYnNpdGUgdmlzaXRzIGluIHRoZSBsYXN0IG1vbnRoCiAgKyBhY2NlcHRhbmNlIG9mIG9mZmVyIGluOiAxc3QsIDV0aCBjYW1wYWlnbgogICsgZGF0ZSBvZiBjdXN0b21lcidzIGVucm9sbG1lbnQgd2l0aCB0aGUgY29tcGFueQoKCiMjIyMgMi41OiBJcyB0aGVyZSBhIHNpZ25pZmljYW50IHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGdlb2dyYXBoaWNhbCByZWdpb25zIGFuZCBzdWNlc3NzIG9mIGEgY29tcGFpZ24/CiogVXNpbmcgUmVzcG9uc2UgdmFyaWFibGUgKGlmIGN1c3RvbWVyIGFjY2VwdGVkIHRoZSBvZmZlciBpbiB0aGUgbGFzdCBjYW1wYWlnbikgYXMgYW4gaW5kaWNhdG9yIG9mIHN1Y2Nlc3MKCmBgYHtyfQojIFN1bW1hcnkgb2YgZ3JvdXBzCmRhdGEyNSA9IGRhdGEKZGF0YTI1JFJlc3BvbnNlID0gYXMubnVtZXJpYyhkYXRhMjUkUmVzcG9uc2UpCmRhdGEyNSAlPiUgZ3JvdXBfYnkoQ291bnRyeSkgJT4lIGdldF9zdW1tYXJ5X3N0YXRzKFJlc3BvbnNlLCB0eXBlPSJtZWFuX3NkIikKIyBDb21wYXJlIG1lYW5zIG9mIGdyb3VwIHVzaW5nIEFOT1ZBIHRlc3QKcmVzLmFvdiA9IGRhdGEyNSAlPiUgYW5vdmFfdGVzdChSZXNwb25zZSB+IENvdW50cnkpCnJlcy5hb3YKYGBgCiogVGhlIEFOT1ZBIHRlc3Qgc2hvd3MgdGhhdCB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgbWVhbnMgb2Ygb2ZmZXIgYWNjZXB0YW5jZSBiZXR3ZWVuIHJlZ2lvbnMgYXJlIG5vdCBzdGF0aXN0aWNhbGx5IGRpZmZlcmVudCwgYW5kIHdlIGNhbiBjb25jbHVkZSB0aGF0IHRoZXJlIGlzIG5vIHNpZ25pZmljYW50IHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGdlb2dyYXBoaWNhbCByZWdpb25zIGFuZCBzdWNjZXNzIG9mIGNhbXBhaWducyBhcyB0aGUgcC12YWx1ZSBpcyA+MC4wNS4gIAoKCiMjIyBTZWN0aW9uIDM6IERhdGEgVmlzdWFsaXphdGlvbnMKIyMjIyAzLjE6IFdoaWNoIG1hcmtldGluZyBjYW1wYWlnbiBpcyBtb3N0IHN1Y2Nlc3NmdWw/IApgYGB7cn0KIyBQcmVwYXJlIGRhdGEKZGF0YTMgPSBkYXRhICU+JSBzZWxlY3QgKEFjY2VwdGVkQ21wMSxBY2NlcHRlZENtcDIsQWNjZXB0ZWRDbXAzLEFjY2VwdGVkQ21wNCwgQWNjZXB0ZWRDbXA1KQpkYXRhMyA9IG11dGF0ZV9pZihkYXRhMywgaXMuZmFjdG9yLCB+YXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoLngpKSkKIyBQbG90CkNtcExhYmVsID0gYygiMm5kIGNhbXBhaWduIiwiMXN0IGNhbXBhaWduIiwiM3JkIGNhbXBhaWduIiwiNXRoIGNhbXBhaWduIiwiNHRoIGNhbXBhaWduIikKZGF0YTMgJT4lIGdhdGhlcihDbXAsIE91dGNvbWUsIEFjY2VwdGVkQ21wMTpBY2NlcHRlZENtcDUpICU+JSBmaWx0ZXIoT3V0Y29tZT4wKSAlPiUgZ3JvdXBfYnkoQ21wKSAlPiUgdGFsbHkoKSAlPiUgZ2dwbG90KGFlcyh4PXJlb3JkZXIoQ21wLG4pLCB5PW4sIGZpbGw9Q21wKSkgKyBnZW9tX2NvbCh3aWR0aD0wLjcpICArIGdlb21fdGV4dChzdGF0PSJpZGVudGl0eSIsYWVzKGxhYmVsPW4pLGhqdXN0PS0wLjIsc2l6ZT0zLjUpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsMTg1KSkgKyBjb29yZF9mbGlwKCkgKyBsYWJzKHk9IiIsIHg9IiIsdGl0bGU9IldoaWNoIG1hcmtldGluZyBjYW1wYWlnbiBpcyB0aGUgbW9zdCBzdWNlc3NmdWw/Iiwgc3VidGl0bGUgPSJOdW1iZXIgb2YgY3VzdG9tZXJzIHdobyBhY2NlcHRlZCB0aGUgb2ZmZXIiKSArIHNjYWxlX2ZpbGxfdWNoaWNhZ28oKSArIHRoZW1lX2NsYXNzaWMoKSArIHRoZW1lKGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb249ICJub25lIiwgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpKSArIHNjYWxlX3hfZGlzY3JldGUobGFiZWw9IENtcExhYmVsKQpgYGAKCgojIyMjIDMuMjogV2hhdCBkb2VzIHRoZSBhdmVyYWdlIGN1c3RvbWVyIGxvb2sgbGlrZSBmb3IgdGhpcyBjb21wYW55PwpgYGB7cn0KIyBBdmVyYWdlIFllYXJfQmlydGggYW5kIEluY29tZSAKZGF0YSAlPiUgc3VtbWFyaXNlKGF2Z19BZ2UgPSByb3VuZChtZWFuKEFnZSkpLCBhdmdfSW5jb21lID0gcm91bmQobWVhbihJbmNvbWUpKSwgYXZnX0tpZGhvbWUgPSByb3VuZChtZWFuKEtpZGhvbWUpKSwgYXZnX1RlZW5ob21lPSByb3VuZChtZWFuKFRlZW5ob21lKSkpCmBgYAoKYGBge3J9CnN1bShpcy5uYShkYXRhJGluY29tZSkpCmBgYAoKCmBgYHtyfQojIEluY29tZQpwMSA9IGRhdGEgJT4lIGdncGxvdChhZXMoeD1JbmNvbWUpKSArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoPTUwMDAsIGNvbG9yPSIjMWQzNTU3IixmaWxsPSIjNDU3YjlkIikgKyBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9IDUyMDAyLCBjb2xvcj0iI2U2Mzk0NiIsbGluZXR5cGU9ImRhc2hlZCIpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsMjAwKSkgKyBhbm5vdGF0ZSgidGV4dCIsbGFiZWw9IkF2ZXJhZ2UgPSAkNTIsMDAyIiwgeD0gOTUwMDAseT0xOTApICsgbGFicyh4PSIiLCB5PSJDdXN0b21lciBDb3VudCIsIHRpdGxlPSJZZWFybHkgaG91c2Vob2xkIGluY29tZSIpCgoKIyBBZ2UKcDIgPSBkYXRhICU+JSBnZ3Bsb3QoYWVzKHg9QWdlKSkgKyBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aD01LCBjb2xvcj0iIzFkMzU1NyIsZmlsbD0iIzQ1N2I5ZCIpICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0PSA0NSwgY29sb3I9IiNlNjM5NDYiLGxpbmV0eXBlPSJkYXNoZWQiKSArIGFubm90YXRlKCJ0ZXh0IixsYWJlbD0iQXZlcmFnZSA9IDQ1IiwgeD0gNTYseT0zNjApICsgbGFicyh0aXRsZT0iQ3VzdG9tZXIgQWdlIiwgeT0iQ3VzdG9tZXIgQ291bnQiLHg9IiIpCgpnZ2FycmFuZ2UocDEscDIsbmNvbD0yKQpgYGAKCmBgYHtyfQojIEFnZSBncm91cApwMl8yID0gZGF0YSAlPiUgZ3JvdXBfYnkoQWdlX2dyb3VwKSAlPiUgdGFsbHkoKSAlPiUgZ2dwbG90KGFlcyh4PUFnZV9ncm91cCx5PW4sIGZpbGw9QWdlX2dyb3VwKSkgKyBnZW9tX2NvbCh3aWR0aD0wLjUpICsgZ2VvbV90ZXh0KHN0YXQ9ImlkZW50aXR5IixhZXMobGFiZWw9biksdmp1c3Q9LTAuMyxzaXplPTMuNSkgKyBzY2FsZV9maWxsX3VjaGljYWdvKCkgKyB0aGVtZV9jbGFzc2ljKCkgKyB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uPSAibm9uZSIpICsgbGFicyh4PSAiIiwgeT0iQ3VzdG9tZXIgY291bnQiLCB0aXRsZT0iQWdlIGRlbW9ncmFwaGljIikKYGBgCgoKYGBge3J9CiMgS2lkaG9tZQpwMyA9IGRhdGEgJT4lIGdyb3VwX2J5KEtpZGhvbWUpICU+JSB0YWxseSgpICU+JSBnZ3Bsb3QoYWVzKHg9ZmFjdG9yKEtpZGhvbWUpLHk9biwgZmlsbD1uKSkgKyBnZW9tX2NvbCh3aWR0aD0wLjcpICsgc2NhbGVfZmlsbF92aXJpZGlzKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArIGxhYnMoeT0iQ3VzdG9tZXIgY291bnQiLCB4PSIiLCB0aXRsZT0iTnVtYmVyIG9mIGNoaWxkcmVuIGluIGhvdXNlaG9sZCIpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsMTMwMCkpCiMgVGVlbmhvbWUKcDQgPSBkYXRhICU+JSBncm91cF9ieShUZWVuaG9tZSkgJT4lIHRhbGx5KCkgJT4lIGdncGxvdChhZXMoeD1mYWN0b3IoVGVlbmhvbWUpLHk9biwgZmlsbD1uKSkgKyBnZW9tX2NvbCh3aWR0aD0wLjcpICsgc2NhbGVfZmlsbF92aXJpZGlzKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArIGxhYnMoeT0iQ3VzdG9tZXIgY291bnQiLCB4PSIiLCB0aXRsZT0iTnVtYmVyIG9mIHRlZW5zIGluIGhvdXNlaG9sZCIpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsMTMwMCkpCiMgTWFyaXRhbCBTdGF0dXMgCnA1ID0gZGF0YSAlPiUgZ3JvdXBfYnkoTWFyaXRhbF9TdGF0dXMpICU+JSB0YWxseSgpICU+JSBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihNYXJpdGFsX1N0YXR1cyxuKSx5PW4sIGZpbGw9bikpICsgZ2VvbV9jb2wod2lkdGg9MC43KSArIHNjYWxlX2ZpbGxfdmlyaWRpcygpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKyBsYWJzKHk9IkN1c3RvbWVyIGNvdW50IiwgeD0iIiwgdGl0bGU9Ik1hcml0YWwgc3RhdHVzIikgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQojIEVkdWNhdGlvbgpwNiA9IGRhdGEgJT4lIGdyb3VwX2J5KEVkdWNhdGlvbikgJT4lIHRhbGx5KCkgJT4lIGdncGxvdChhZXMoeD1yZW9yZGVyKEVkdWNhdGlvbixuKSx5PW4sIGZpbGw9bikpICsgZ2VvbV9jb2wod2lkdGg9MC43KSArIHNjYWxlX2ZpbGxfdmlyaWRpcygpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKyBsYWJzKHk9IkN1c3RvbWVyIGNvdW50IiwgeD0iIiwgdGl0bGU9IkN1c3RvbWVyJ3MgZWR1Y2F0aW9uYWwgbGV2ZWwiKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpCgojIENvbWJpbmVkIHBsb3QKZ2dhcnJhbmdlKHAzLHA0LHA1LHA2LCBuY29sPTIsIG5yb3c9MikgCmBgYAoKYGBge3J9CiMgTWFyaXRhbCBzdGF0dXMgYW5kIGVkdWNhdGlvbiBsZXZlbApkYXRhICU+JSBncm91cF9ieShNYXJpdGFsX1N0YXR1cyxFZHVjYXRpb24pICU+JSB0YWxseShzb3J0PVQpICU+JSBtdXRhdGUoTWFyaXRhbF9FZHUgPSBwYXN0ZShNYXJpdGFsX1N0YXR1cywgRWR1Y2F0aW9uLCBzZXA9Ii0iKSkgJT4lIGdncGxvdChhZXMoeD1yZW9yZGVyKE1hcml0YWxfRWR1LG4pLCB5PW4pKSArIGdlb21fcG9pbnQoY29sb3I9IiNmNzdmMDAiKSArIGdlb21fc2VnbWVudChhZXMoeD1NYXJpdGFsX0VkdSwgeGVuZD1NYXJpdGFsX0VkdSwgeT0wLCB5ZW5kPW4pLGNvbG9yPSIjMzM1YzY3IikgKyBjb29yZF9mbGlwKCkgKyB0aGVtZV9saWdodCgpICsgdGhlbWUocGFuZWwuYm9yZGVyPWVsZW1lbnRfYmxhbmsoKSxheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWFqb3IueT1lbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IueD1lbGVtZW50X2JsYW5rKCkpICsgbGFicyh5PSJDdXN0b21lciBjb3VudCIsIHg9IiIsIHRpdGxlPSJDdXN0b21lcnMnIG1hcml0YWwgc3RhdHVzIGFuZCBlZHVjYXRpb24gbGV2ZWwiKQpgYGAKCgojIyMjIDMuMzogV2hpY2ggcHJvZHVjdHMgYXJlIHBlcmZvcm1pbmcgYmVzdD8KYGBge3J9ClByTGFiZWwgPSBjKCJGcnVpdHMiLCJTd2VldCIsIkZpc2giLCJHb2xkIiwiTWVhdCIsIldpbmVzIikKZGF0YSAlPiUgc2VsZWN0KE1udFdpbmVzLCBNbnRGcnVpdHMsIE1udE1lYXRQcm9kdWN0cywgTW50RmlzaFByb2R1Y3RzLCBNbnRTd2VldFByb2R1Y3RzLCBNbnRHb2xkUHJvZHMpICU+JSBnYXRoZXIoUHJvZHVjdCwgQW10U3BlbnQsIE1udFdpbmVzOk1udEdvbGRQcm9kcykgJT4lIGdyb3VwX2J5KFByb2R1Y3QpICU+JSB0YWxseShBbXRTcGVudCkgJT4lIGdncGxvdChhZXMoeD1yZW9yZGVyKFByb2R1Y3QsbiksIHk9biwgZmlsbD1Qcm9kdWN0KSkgKyBnZW9tX2NvbCgpICsgZ2VvbV90ZXh0KHN0YXQ9ImlkZW50aXR5IixhZXMobGFiZWw9biksaGp1c3Q9LTAuMixzaXplPTMpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsNzUwMDAwKSkgKyBjb29yZF9mbGlwKCkgKyBzY2FsZV9maWxsX3VjaGljYWdvKCkgKyB0aGVtZV9jbGFzc2ljKCkgKyB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uPSAibm9uZSIsIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSxheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xMCkpICsgbGFicyh5PSJBbW91bnQgc3BlbnQiLCB4PSAiIiwgdGl0bGUgPSAiV2hpY2ggcHJvZHVjdHMgYXJlIHBlcmZvcm1pbmcgYmVzdD8iLCBzdWJ0aXRsZSA9ICJBbW91bnQgc3BlbnQgYnkgY3VzdG9tZXJzIGluIHRoZSBsYXN0IHR3byB5ZWFycyBieSBwcm9kdWN0IHR5cGUiKSArIHNjYWxlX3hfZGlzY3JldGUobGFiZWw9IFByTGFiZWwpCmBgYAoKCiMjIyMgMy4zOiBXaGljaCBwcm9kdWN0cyBhcmUgcGVyZm9ybWluZyBiZXN0PwpgYGB7cn0KIyBMYWJlbHMKQ2hMYWJlbCA9IGMoIkNhdGFsb2ciLCJXZWIiLCJTdG9yZSIpCiMgUGxvdApkYXRhICU+JSBzZWxlY3QoTnVtV2ViUHVyY2hhc2VzLCBOdW1DYXRhbG9nUHVyY2hhc2VzLCBOdW1TdG9yZVB1cmNoYXNlcykgJT4lIGdhdGhlcihDaGFubmVsLCBQdXJjaGFzZXMsIE51bVdlYlB1cmNoYXNlczpOdW1TdG9yZVB1cmNoYXNlcykgJT4lIGdyb3VwX2J5KENoYW5uZWwpICU+JSB0YWxseShQdXJjaGFzZXMpICU+JSBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihDaGFubmVsLG4pLCB5PW4sIGZpbGw9Q2hhbm5lbCkpICsgZ2VvbV9jb2wod2lkdGg9MC41KSArIGdlb21fdGV4dChzdGF0PSJpZGVudGl0eSIsYWVzKGxhYmVsPW4pLHZqdXN0PS0wLjUsc2l6ZT0zLjUpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsMTUwMDApKSArIHNjYWxlX2ZpbGxfdWNoaWNhZ28oKSArIHRoZW1lX2NsYXNzaWMoKSArIHRoZW1lKGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb249ICJub25lIiwgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTApLGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTExKSkgKyBsYWJzKHk9Ik51bWJlciBvZiBwdXJjaGFzZXMiLCB4PSAiQ2hhbm5lbCIsIHRpdGxlID0gIldoaWNoIGNoYW5uZWxzIGFyZSB1bmRlcnBlcmZvcm1pbmc/Iiwgc3VidGl0bGUgPSAiTnVtYmVyIG9mIHB1cmNoYXNlcyBtYWRlIHRocm91Z2ggY2hhbm5lbHMiKSArIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPUNoTGFiZWwpCmBgYAoK