Dataset: eCommerce Marketing

Task: Exploratory and Statistical Analysis.

Contents:

Load libraries

library(plyr)
library(tidyverse)
library(skimr)
library(lubridate)
library(reshape)
library(Hmisc)
library(corrplot)
library(tibble)
library(viridis)
library(rstatix)
library(wesanderson)
library(ggsci)
library(caret)
library(ggpubr)
library(janitor)

Import Data

data = read.csv("marketing_data.csv",header=TRUE)
dim(data)
[1] 2240   28
str(data)
'data.frame':   2240 obs. of  28 variables:
 $ ID                 : int  1826 1 10476 1386 5371 7348 4073 1991 4047 9477 ...
 $ Year_Birth         : int  1970 1961 1958 1967 1989 1958 1954 1967 1954 1954 ...
 $ Education          : chr  "Graduation" "Graduation" "Graduation" "Graduation" ...
 $ Marital_Status     : chr  "Divorced" "Single" "Married" "Together" ...
 $ Income             : chr  "$84,835.00 " "$57,091.00 " "$67,267.00 " "$32,474.00 " ...
 $ Kidhome            : int  0 0 0 1 1 0 0 0 0 0 ...
 $ Teenhome           : int  0 0 1 1 0 0 0 1 1 1 ...
 $ Dt_Customer        : chr  "6/16/14" "6/15/14" "5/13/14" "5/11/14" ...
 $ Recency            : int  0 0 0 0 0 0 0 0 0 0 ...
 $ MntWines           : int  189 464 134 10 6 336 769 78 384 384 ...
 $ MntFruits          : int  104 5 11 0 16 130 80 0 0 0 ...
 $ MntMeatProducts    : int  379 64 59 1 24 411 252 11 102 102 ...
 $ MntFishProducts    : int  111 7 15 0 11 240 15 0 21 21 ...
 $ MntSweetProducts   : int  189 0 2 0 0 32 34 0 32 32 ...
 $ MntGoldProds       : int  218 37 30 0 34 43 65 7 5 5 ...
 $ NumDealsPurchases  : int  1 1 1 1 2 1 1 1 3 3 ...
 $ NumWebPurchases    : int  4 7 3 1 3 4 10 2 6 6 ...
 $ NumCatalogPurchases: int  4 3 2 0 1 7 10 1 2 2 ...
 $ NumStorePurchases  : int  6 7 5 2 2 5 7 3 9 9 ...
 $ NumWebVisitsMonth  : int  1 5 2 7 7 2 6 5 4 4 ...
 $ AcceptedCmp3       : int  0 0 0 0 1 0 1 0 0 0 ...
 $ AcceptedCmp4       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ AcceptedCmp5       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ AcceptedCmp1       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ AcceptedCmp2       : int  0 1 0 0 0 0 0 0 0 0 ...
 $ Response           : int  1 1 0 0 1 1 1 0 0 0 ...
 $ Complain           : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Country            : chr  "SP" "CA" "US" "AUS" ...

Dataset variables from Jack Daoud’s Notebook:

People

Products

Place

Promotion

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)
# Missing data analysis
data.frame("NA_Count" = sapply(data, function(x) { length(which(is.na(x))) })) %>% 
  filter(NA_Count > 0) %>% 
  arrange(., desc(NA_Count))
# 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 
# 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")

3.3: Which products are performing best?

PrLabel = c("Fruits","Sweet","Fish","Gold","Meat","Wines")
data %>% select(MntWines, MntFruits, MntMeatProducts, MntFishProducts, MntSweetProducts, MntGoldProds) %>% gather(Product, AmtSpent, MntWines:MntGoldProds) %>% group_by(Product) %>% tally(AmtSpent) %>% ggplot(aes(x=reorder(Product,n), y=n, fill=Product)) + geom_col() + geom_text(stat="identity",aes(label=n),hjust=-0.2,size=3) + scale_y_continuous(limits=c(0,750000)) + coord_flip() + 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(),axis.text.y=element_text(size=10)) + labs(y="Amount spent", x= "", title = "Which products are performing best?", subtitle = "Amount spent by customers in the last two years by product type") + scale_x_discrete(label= PrLabel)

3.3: Which products are performing best?

# Labels
ChLabel = c("Catalog","Web","Store")
# Plot
data %>% select(NumWebPurchases, NumCatalogPurchases, NumStorePurchases) %>% gather(Channel, Purchases, NumWebPurchases:NumStorePurchases) %>% group_by(Channel) %>% tally(Purchases) %>% ggplot(aes(x=reorder(Channel,n), y=n, fill=Channel)) + geom_col(width=0.5) + geom_text(stat="identity",aes(label=n),vjust=-0.5,size=3.5) + scale_y_continuous(limits=c(0,15000)) + 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.y=element_text(size=10),axis.text.x=element_text(size=11)) + labs(y="Number of purchases", x= "Channel", title = "Which channels are underperforming?", subtitle = "Number of purchases made through channels") + scale_x_discrete(labels=ChLabel)

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