Dataset: Marketing Data

Task: Exploratory and Statistical Analysis Task.

Contents:

Load libraries

library(tidyverse)
library(skimr)
library(lubridate)
library(reshape)
library(Hmisc)
library(corrplot)
library(tibble)
library(viridis)
library(rstatix)
library(wesanderson)
library(ggsci)

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" ...

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
skim(data)
── Data Summary ────────────────────────
                           Values
Name                       data  
Number of rows             2240  
Number of columns          28    
_______________________          
Column type frequency:           
  character                3     
  Date                     1     
  numeric                  24    
________________________         
Group variables            None  

── Variable type: character ─────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable  n_missing complete_rate   min   max empty n_unique whitespace
1 Education              0             1     3    10     0        5          0
2 Marital_Status         0             1     4     8     0        8          0
3 Country                0             1     2     3     0        8          0

── Variable type: Date ──────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min        max        median     n_unique
1 Dt_Customer           0             1 2012-07-30 2014-06-29 2013-07-08      663

── Variable type: numeric ───────────────────────────────────────────────────────────────────────────────────────────────────
   skim_variable       n_missing complete_rate        mean         sd    p0     p25    p50    p75   p100 hist 
 1 ID                          0         1      5592.       3247.         0  2828.   5458.  8428.  11191 ▇▇▇▇▇
 2 Year_Birth                  0         1      1969.         12.0     1893  1959    1970   1977    1996 ▁▁▂▇▅
 3 Income                     24         0.989 52247.      25173.      1730 35303   51382. 68522  666666 ▇▁▁▁▁
 4 Kidhome                     0         1         0.444       0.538      0     0       0      1       2 ▇▁▆▁▁
 5 Teenhome                    0         1         0.506       0.545      0     0       0      1       2 ▇▁▇▁▁
 6 Recency                     0         1        49.1        29.0        0    24      49     74      99 ▇▇▇▇▇
 7 MntWines                    0         1       304.        337.         0    23.8   174.   504.   1493 ▇▂▂▁▁
 8 MntFruits                   0         1        26.3        39.8        0     1       8     33     199 ▇▁▁▁▁
 9 MntMeatProducts             0         1       167.        226.         0    16      67    232    1725 ▇▁▁▁▁
10 MntFishProducts             0         1        37.5        54.6        0     3      12     50     259 ▇▁▁▁▁
11 MntSweetProducts            0         1        27.1        41.3        0     1       8     33     263 ▇▁▁▁▁
12 MntGoldProds                0         1        44.0        52.2        0     9      24     56     362 ▇▁▁▁▁
13 NumDealsPurchases           0         1         2.33        1.93       0     1       2      3      15 ▇▂▁▁▁
14 NumWebPurchases             0         1         4.08        2.78       0     2       4      6      27 ▇▃▁▁▁
15 NumCatalogPurchases         0         1         2.66        2.92       0     0       2      4      28 ▇▂▁▁▁
16 NumStorePurchases           0         1         5.79        3.25       0     3       5      8      13 ▂▇▂▃▂
17 NumWebVisitsMonth           0         1         5.32        2.43       0     3       6      7      20 ▅▇▁▁▁
18 AcceptedCmp3                0         1         0.0728      0.260      0     0       0      0       1 ▇▁▁▁▁
19 AcceptedCmp4                0         1         0.0746      0.263      0     0       0      0       1 ▇▁▁▁▁
20 AcceptedCmp5                0         1         0.0728      0.260      0     0       0      0       1 ▇▁▁▁▁
21 AcceptedCmp1                0         1         0.0643      0.245      0     0       0      0       1 ▇▁▁▁▁
22 AcceptedCmp2                0         1         0.0134      0.115      0     0       0      0       1 ▇▁▁▁▁
23 Response                    0         1         0.149       0.356      0     0       0      0       1 ▇▁▁▁▂
24 Complain                    0         1         0.00938     0.0964     0     0       0      0       1 ▇▁▁▁▁
# Drop 24 obs without income 
data = data %>% filter(!is.na(Income))
dim(data)
[1] 2216   28
# Unique levels in categorical variables
summary(as.factor(data$Education))
  2n Cycle      Basic Graduation     Master        PhD 
       200         54       1116        365        481 
summary(as.factor(data$Marital_Status))
  Absurd    Alone Divorced  Married   Single Together    Widow     YOLO 
       2        3      232      857      471      573       76        2 
summary(as.factor(data$Country))
 AUS   CA  GER  IND   ME   SA   SP   US 
 147  266  116  147    3  337 1093  107 
# Find duplicates
length(unique(data$ID)) 
[1] 2216
datacopy = data %>% distinct(Income,Year_Birth, Kidhome, Teenhome, Recency, Dt_Customer, MntWines,MntFruits,MntMeatProducts,MntFishProducts,MntSweetProducts, MntGoldProds,NumDealsPurchases, NumWebPurchases, NumCatalogPurchases, NumStorePurchases, NumWebVisitsMonth, .keep_all=TRUE)
dim(datacopy)
[1] 2011   28
# Recode levels after filtering duplicates 
library(plyr)
revalue(datacopy$Education,c("2n Cycle" = "Master")) -> datacopy$Education
revalue(datacopy$Marital_Status,c("Alone" = "Single")) -> datacopy$Marital_Status
revalue(datacopy$Marital_Status,c("Absurd" = "Together")) -> datacopy$Marital_Status
revalue(datacopy$Country,c("ME" = "AUS")) -> datacopy$Country

# Summary of unique levels after cleaning
summary(as.factor(datacopy$Education))
     Basic Graduation     Master        PhD 
        49       1013        514        435 
summary(as.factor(datacopy$Marital_Status))
Divorced  Married   Single Together    Widow 
     212      781      438      511       69 
summary(as.factor(datacopy$Country))
AUS  CA GER IND  SA  SP  US 
130 244 103 132 305 994 103 
data = datacopy
# Change variables types
data = data %>% mutate_at(vars(AcceptedCmp3,AcceptedCmp4,AcceptedCmp5,AcceptedCmp1,AcceptedCmp2,Response,Complain),list(factor))
# Outliers
# Scale
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()

# Plot Income
summary(data$Income)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1730   35534   51563   52375   68656  666666 
data %>% ggplot(aes(x=Year_Birth)) + geom_boxplot()


# Drop obs with outliers in Income and Year_Birth 
data = data %>% filter(!Year_Birth<1930) %>% filter(!Income==666666)
dim(data)
[1] 2007   28
# Feature Construction
# Dt_Customer days since the epoch
data$Dt_Customer_num = as.numeric(data$Dt_Customer)
summary(data$Dt_Customer_num)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  15551   15722   15899   15898   16072   16250 
# TotalPurchases
data$TotalPurchases = data$NumWebPurchases + data$NumCatalogPurchases + data$NumStorePurchases + data$NumDealsPurchases
summary(data$TotalPurchases)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   0.00    8.00   15.00   14.89   21.00   44.00 
# Obs without purchases
NoPurchases = data %>% filter(TotalPurchases==0) 
head(NoPurchases)

# 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   397.0   607.8  1047.5  2525.0 
# Plot TotalPurchases and TotalProducts
data %>% ggplot(aes(x=TotalPurchases,y=TotalProducts)) + geom_point(alpha=0.5)


# Plot anomalies 
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)


# Drop anomalies 
data = data %>% filter(TotalPurchases!=0)
dim(data)
[1] 2003   31

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 
    2003        0        2 
                      
Value         US World
Frequency    103  1900
Proportion 0.051 0.949
# Independent t test
t.test(TotalPurchases ~ country2, data= data22, var.equal=TRUE,alternative="greater")

    Two Sample t-test

data:  TotalPurchases by country2
t = 1.8535, df = 2001, p-value = 0.03198
alternative hypothesis: true difference in means is greater than 0
95 percent confidence interval:
 0.1605314       Inf
sample estimates:
   mean in group US mean in group World 
           16.28155            14.85053 
  • The independent t-test results suggests that the total purchase of US is 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 
    2003        0        2 
                                      
Value          above_avg not_above_avg
Frequency            631          1372
Proportion         0.315         0.685
# Independent t test
t.test(NumStorePurchases ~ gold2, data= data23, var.equal=TRUE,alternative="greater")

    Two Sample t-test

data:  NumStorePurchases by gold2
t = 19.39, df = 2001, p-value < 2.2e-16
alternative hypothesis: true difference in means is greater than 0
95 percent confidence interval:
 2.531203      Inf
sample estimates:
    mean in group above_avg mean in group not_above_avg 
                   7.698891                    4.932945 
  • 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?

# 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)
res.aov
  • 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
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,180)) + 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())

3.2: What does the average customer look like for this company?

# Average Year_Birth and Income 
data %>% summarise(avg_Year_Birth = round(mean(Year_Birth)), avg_Income = round(mean(Income)), avg_Kidhome = round(mean(Kidhome)), avg_Teenhome= round(mean(Teenhome)))
# 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,180)) + annotate("text",label="Average = $52,002", x= 95000,y=170) + labs(x="", y="Customer Count", title="Yearly household income")
# Year_Birth
p2 = data %>% ggplot(aes(x=Year_Birth)) + geom_histogram(binwidth=5, color="#1d3557",fill="#457b9d") + geom_vline(xintercept= 1969, color="#e63946",linetype="dashed") + scale_y_continuous(limits=c(0,380)) + annotate("text",label="Average = 1969", x= 1985,y=350) + labs(title="Birth year", y="Customer Count",x="") 
# Plot 
ggarrange(p1,p2,ncol=2)

# 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,700000)) + 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.6) + geom_text(stat="identity",aes(label=n),hjust=-0.2,size=3.5) + scale_y_continuous(limits=c(0,13000)) + 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=11)) + labs(y="Number of purchases", x= "", title = "Which channels are underperforming?", subtitle = "Number of purchases made through store, web and catalog") + scale_x_discrete(labels=ChLabel)

LS0tCnRpdGxlOiAiTWFya2V0aW5nIERhdGEgQW5hbHlzaXMiCmRhdGU6ICJEZWMgMjAyMCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCkRhdGFzZXQ6IFtNYXJrZXRpbmcgRGF0YV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9qYWNrZGFvdWQvbWFya2V0aW5nLWRhdGEpCgpUYXNrOiBbRXhwbG9yYXRvcnkgYW5kIFN0YXRpc3RpY2FsIEFuYWx5c2lzIFRhc2tdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vamFja2Rhb3VkL21hcmtldGluZy1kYXRhL3Rhc2tzP3Rhc2tJZD0yOTg2KS4gCgpDb250ZW50czogCgoqIFNlY3Rpb24gMTogRGF0YSBjbGVhbmluZyBhbmQgYmFzaWMgZXhwbG9yYXRpb24KKiBTZWN0aW9uIDI6IFN0YXRpc3RpY2FsIGFuYWx5c2lzCiogU2VjdGlvbiAzOiBEYXRhIHZpc3VhbGl6YXRpb24KCgojIyMgTG9hZCBsaWJyYXJpZXMKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNraW1yKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShyZXNoYXBlKQpsaWJyYXJ5KEhtaXNjKQpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeSh2aXJpZGlzKQpsaWJyYXJ5KHJzdGF0aXgpCmxpYnJhcnkod2VzYW5kZXJzb24pCmxpYnJhcnkoZ2dzY2kpCmBgYAoKIyMjIEltcG9ydCBEYXRhCmBgYHtyfQpkYXRhID0gcmVhZC5jc3YoIm1hcmtldGluZ19kYXRhLmNzdiIsaGVhZGVyPVRSVUUpCmRpbShkYXRhKQpzdHIoZGF0YSkKYGBgCgojIyMgU2VjdGlvbiAxOiBEYXRhIGNsZWFuaW5nIGFuZCBiYXNpYyBleHBsb3JhdGlvbgpgYGB7cn0KIyBGb3JtYXQgZGF0ZXMgaW4gRHRfQ3VzdG9tZXIKZGF0YSREdF9DdXN0b21lciA9IG1keShkYXRhJER0X0N1c3RvbWVyKQpzdW1tYXJ5KGRhdGEkRHRfQ3VzdG9tZXIpCgojIERyb3Agc3BlY2lhbCBjaGFyYWN0ZXJzIGluIEluY29tZSAKZGF0YSRJbmNvbWUgPSBnc3ViKCdbJF0oWzAtOV0rKVssXShbMC05XSspJywnXFwxXFwyJyxkYXRhJEluY29tZSkKZGF0YSRJbmNvbWUgPSBhcy5udW1lcmljKGRhdGEkSW5jb21lKQpgYGAKCiogVGhlIGVhcmxpZXN0IGN1c3RvbWVyIGVucm9sbG1lbnQgZGF0ZSBpbiB0aGUgZGF0YXNldCBpcyAyMDEyLTA3LTMwIGFuZCBsYXRlc3QgMjAxNC0wNi0yOQoKYGBge3J9CiMgTWlzc2luZyBkYXRhIGFuYWx5c2lzCnNraW0oZGF0YSkKIyBEcm9wIDI0IG9icyB3aXRob3V0IGluY29tZSAKZGF0YSA9IGRhdGEgJT4lIGZpbHRlcighaXMubmEoSW5jb21lKSkKZGltKGRhdGEpCmBgYAoKKiBUaGVyZSBhcmUgMjQgb2JzZXJ2YXRpb25zIHdpdGhvdXQgaW5jb21lIHNwZWNpZmllZC4gCgpgYGB7cn0KIyBVbmlxdWUgbGV2ZWxzIGluIGNhdGVnb3JpY2FsIHZhcmlhYmxlcwpzdW1tYXJ5KGFzLmZhY3RvcihkYXRhJEVkdWNhdGlvbikpCnN1bW1hcnkoYXMuZmFjdG9yKGRhdGEkTWFyaXRhbF9TdGF0dXMpKQpzdW1tYXJ5KGFzLmZhY3RvcihkYXRhJENvdW50cnkpKQpgYGAKCiogVGhlcmUgYXJlIG9ubHkgMyBvYnNlcnZhdGlvbnMgd2l0aCBjb3VudHJ5IGxpc3RlZCBhcyBfTUVfCiogVGhlcmUgYXJlIHNvbWUgcXVlc3Rpb25hYmxlIGxldmVscyBpbiB0aGUgTWFyaXRhbCBTdGF0dXMgbGV2ZWwgd2hpY2ggd2lsbCBiZSBoYW5kbGVkIGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbiBhZnRlciBjaGVja2luZyBmb3IgZHVwbGljYXRlcy4gCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBGaW5kIGR1cGxpY2F0ZXMKbGVuZ3RoKHVuaXF1ZShkYXRhJElEKSkgCmRhdGFjb3B5ID0gZGF0YSAlPiUgZGlzdGluY3QoSW5jb21lLFllYXJfQmlydGgsIEtpZGhvbWUsIFRlZW5ob21lLCBSZWNlbmN5LCBEdF9DdXN0b21lciwgTW50V2luZXMsTW50RnJ1aXRzLE1udE1lYXRQcm9kdWN0cyxNbnRGaXNoUHJvZHVjdHMsTW50U3dlZXRQcm9kdWN0cywgTW50R29sZFByb2RzLE51bURlYWxzUHVyY2hhc2VzLCBOdW1XZWJQdXJjaGFzZXMsIE51bUNhdGFsb2dQdXJjaGFzZXMsIE51bVN0b3JlUHVyY2hhc2VzLCBOdW1XZWJWaXNpdHNNb250aCwgLmtlZXBfYWxsPVRSVUUpCmRpbShkYXRhY29weSkKCiMgUmVjb2RlIGxldmVscyBhZnRlciBmaWx0ZXJpbmcgZHVwbGljYXRlcyAKbGlicmFyeShwbHlyKQpyZXZhbHVlKGRhdGFjb3B5JEVkdWNhdGlvbixjKCIybiBDeWNsZSIgPSAiTWFzdGVyIikpIC0+IGRhdGFjb3B5JEVkdWNhdGlvbgpyZXZhbHVlKGRhdGFjb3B5JE1hcml0YWxfU3RhdHVzLGMoIkFsb25lIiA9ICJTaW5nbGUiKSkgLT4gZGF0YWNvcHkkTWFyaXRhbF9TdGF0dXMKcmV2YWx1ZShkYXRhY29weSRNYXJpdGFsX1N0YXR1cyxjKCJBYnN1cmQiID0gIlRvZ2V0aGVyIikpIC0+IGRhdGFjb3B5JE1hcml0YWxfU3RhdHVzCnJldmFsdWUoZGF0YWNvcHkkQ291bnRyeSxjKCJNRSIgPSAiQVVTIikpIC0+IGRhdGFjb3B5JENvdW50cnkKCiMgU3VtbWFyeSBvZiB1bmlxdWUgbGV2ZWxzIGFmdGVyIGNsZWFuaW5nCnN1bW1hcnkoYXMuZmFjdG9yKGRhdGFjb3B5JEVkdWNhdGlvbikpCnN1bW1hcnkoYXMuZmFjdG9yKGRhdGFjb3B5JE1hcml0YWxfU3RhdHVzKSkKc3VtbWFyeShhcy5mYWN0b3IoZGF0YWNvcHkkQ291bnRyeSkpCmRhdGEgPSBkYXRhY29weQpgYGAKCiogQWx0aG91Z2ggdGhlcmUgYXJlIGFyZSBubyBkdXBsaWNhdGVzIGJhc2VkIG9uIHRoZSBJRCB2YXJpYWJsZSwgdGhlcmUgYXJlIDIwNSBkdXBsaWNhdGVzIGZvdW5kIGJhc2VkIG9uIDE3IG51bWVyaWMgdmFyaWFibGVzIGluIHRoZSBkYXRhc2V0LgoqIEVkdWNhdGlvbiB2YXJpYWJsZTogcmVjb2RlZCBfMm4gQ3ljbGVfIHRvIF9NYXN0ZXJzXyBpbiBFZHVjYXRpb24gdmFyaWFibGUgYXMgdGhleSBhcmUgZXF1aXZhbGVudCBsZXZlbHMgYWNjb3JkaW5nIHRvIFtFdXJvcGVhbiBIaWdoZXIgRWR1Y2F0aW9uIEFyZWFdKGh0dHA6Ly93d3cuZWhlYS5pbmZvL3BhZ2UtdGhyZWUtY3ljbGUtc3lzdGVtKSAKKiBNYXJpdGFsX1N0YXR1cyB2YXJpYWJsZTogcmVjb2RlZCBfQWxvbmVfIGFuZCBfQWJzdXJkXyBiYXNlZCBvbiB0aGUgcmVzcGVjdGl2ZSBkdXBsaWNhdGVzIGZvdW5kLgoqIENvdW50cnk6IHJlY29kZWQgX01FXyBiYXNlZCBvbiB0aGUgcmVzcGVjdGl2ZSBkdXBsaWNhdGUgZm91bmQuCiogVGhlcmUgYXJlIDQgRWR1Y2F0aW9uIGxldmVscywgNSBNYXJpdGFsX1N0YXR1cyBsZXZlbHMgYW5kIDYgQ291bnRyeSBsZXZlbHMgYWZ0ZXIgZGF0YSBjbGVhbmluZy4gCgpgYGB7cn0KIyBDaGFuZ2UgdmFyaWFibGVzIHR5cGVzCmRhdGEgPSBkYXRhICU+JSBtdXRhdGVfYXQodmFycyhBY2NlcHRlZENtcDMsQWNjZXB0ZWRDbXA0LEFjY2VwdGVkQ21wNSxBY2NlcHRlZENtcDEsQWNjZXB0ZWRDbXAyLFJlc3BvbnNlLENvbXBsYWluKSxsaXN0KGZhY3RvcikpCmBgYAoKYGBge3J9CiMgT3V0bGllcnMKIyBTY2FsZQpudW1lcmljX2RhdGEgPSBkYXRhICU+JSBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYykpCnpfZGF0YSA9IGRhdGEuZnJhbWUoc2NhbGUobnVtZXJpY19kYXRhKSkKel9kYXRhJHR5cGUgPSByb3duYW1lcyhkYXRhKQoKIyBQbG90IGFsbCBudW1lcmljIHZhcmlhYmxlcwp6X2RhdGEgJT4lIAogIHBpdm90X2xvbmdlcihjb2xzPS10eXBlKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gbmFtZSwgeSA9IHZhbHVlKSkgKwogICAgZ2VvbV9ib3hwbG90KCkgKwogICAgdGhlbWVfY2xhc3NpYygpICsKICAgIGV4cGFuZF9saW1pdHMoeD0xMi42KSArIGNvb3JkX2ZsaXAoKQojIFBsb3QgSW5jb21lCnN1bW1hcnkoZGF0YSRJbmNvbWUpCmRhdGEgJT4lIGdncGxvdChhZXMoeD1ZZWFyX0JpcnRoKSkgKyBnZW9tX2JveHBsb3QoKQoKIyBEcm9wIG9icyB3aXRoIG91dGxpZXJzIGluIEluY29tZSBhbmQgWWVhcl9CaXJ0aCAKZGF0YSA9IGRhdGEgJT4lIGZpbHRlcighWWVhcl9CaXJ0aDwxOTMwKSAlPiUgZmlsdGVyKCFJbmNvbWU9PTY2NjY2NikKZGltKGRhdGEpCmBgYAoKKiBUaGUgYm94cGxvdCBvZiBhbGwgbnVtZXJpYyB2YXJpYWJsZXMgc2hvdyB0aGF0IHRoZXJlIGFyZSBvdXRsaWVycyBpbiBtdWx0aXBsZSBmZWF0dXJlcywgaW4gcGFydGljdWxhcjoKICAqIFllYXJfQmlydGggdmFyaWFibGUgY29udGFpbnMgMyB2YWx1ZXMgYmVmb3JlIDE5MDAgCiAgKiBJbmNvbWUgaGFzIGFuIGV4dHJlbWUgb3V0bGllciBpLmUuICQ2NjYsNjY2CiogT2JzZXJ2YXRpb25zIHdpdGggdGhlIGFib3ZlLW1lbnRpb25lZCBwb2ludHMgYXJlIGRyb3BwZWQsIHdoaWxlIHRoZSBvdGhlciBvdXRsaWVycyBpZGVudGlmaWVkIHJlbWFpbnMgaW4gdGhlIGRhdGFmcmFtZSB0byBhdm9pZCBsb3NpbmcgdG9vIG11Y2ggaW5mb3JtYXRpb24uIAoKCgpgYGB7cn0KIyBGZWF0dXJlIENvbnN0cnVjdGlvbgojIER0X0N1c3RvbWVyIGRheXMgc2luY2UgdGhlIGVwb2NoCmRhdGEkRHRfQ3VzdG9tZXJfbnVtID0gYXMubnVtZXJpYyhkYXRhJER0X0N1c3RvbWVyKQpzdW1tYXJ5KGRhdGEkRHRfQ3VzdG9tZXJfbnVtKQoKIyBUb3RhbFB1cmNoYXNlcwpkYXRhJFRvdGFsUHVyY2hhc2VzID0gZGF0YSROdW1XZWJQdXJjaGFzZXMgKyBkYXRhJE51bUNhdGFsb2dQdXJjaGFzZXMgKyBkYXRhJE51bVN0b3JlUHVyY2hhc2VzICsgZGF0YSROdW1EZWFsc1B1cmNoYXNlcwpzdW1tYXJ5KGRhdGEkVG90YWxQdXJjaGFzZXMpCgojIE9icyB3aXRob3V0IHB1cmNoYXNlcwpOb1B1cmNoYXNlcyA9IGRhdGEgJT4lIGZpbHRlcihUb3RhbFB1cmNoYXNlcz09MCkgCmhlYWQoTm9QdXJjaGFzZXMpCgojIFRvdGFsUHJvZHVjdHMKZGF0YSRUb3RhbFByb2R1Y3RzID0gZGF0YSRNbnRXaW5lcyArIGRhdGEkTW50RnJ1aXRzICsgZGF0YSRNbnRNZWF0UHJvZHVjdHMgKyBkYXRhJE1udEZpc2hQcm9kdWN0cyArIGRhdGEkTW50U3dlZXRQcm9kdWN0cyArIGRhdGEkTW50R29sZFByb2RzCnN1bW1hcnkoZGF0YSRUb3RhbFByb2R1Y3RzKQoKIyBQbG90IFRvdGFsUHVyY2hhc2VzIGFuZCBUb3RhbFByb2R1Y3RzCmRhdGEgJT4lIGdncGxvdChhZXMoeD1Ub3RhbFB1cmNoYXNlcyx5PVRvdGFsUHJvZHVjdHMpKSArIGdlb21fcG9pbnQoYWxwaGE9MC41KQoKIyBQbG90IGFub21hbGllcyAKZGF0YSAlPiUgZmlsdGVyKFRvdGFsUHVyY2hhc2VzPT0wKSAlPiUgZ2dwbG90KGFlcyh4PVRvdGFsUHVyY2hhc2VzLHk9VG90YWxQcm9kdWN0cykpICsgZ2VvbV9jb3VudCgpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAuMDAsMTAuMDApKSArIHNjYWxlX3NpemVfY29udGludW91cyhicmVha3M9cm91bmQpCgojIERyb3AgYW5vbWFsaWVzIApkYXRhID0gZGF0YSAlPiUgZmlsdGVyKFRvdGFsUHVyY2hhc2VzIT0wKQpkaW0oZGF0YSkKCmBgYAoqIFRoZXJlIGFyZSA0IG9ic2VydmF0aW9ucyB3aXRoIDAgVG90YWwgUHVyY2hhc2VzIGJ1dCBoYXZlIDUgb3IgbW9yZSBUb3RhbCBQcm9kdWN0cwoKIyMjIFNlY3Rpb24gMjogU3RhdGlzdGljYWwgYW5hbHlzaXMKIyMjIyAyLjE6IFdoYXQgZmFjdG9ycyBhcmUgc2lnbmlmaWNhbnRseSByZWxhdGVkIHRvIHRoZSBudW1iZXIgb2Ygc3RvcmUgcHVyY2hhc2VzCmBgYHtyfQojIFJlZ3Jlc3Npb24KZGF0YV8yMSA9IGRhdGFbLWMoMSw4LDMwLDMxKV0KbGluZWFyTW9kMSA9IGxtKE51bVN0b3JlUHVyY2hhc2VzfiAuLCBkYXRhID0gZGF0YV8yMSkKc3VtbWFyeShsaW5lYXJNb2QxKQpgYGAKCiogVGhlIHZhcmlhYmxlcyBpbiB0aGUgcmVncmVzc2lvbiBtb2RlbCBhcmUgam9pbnRseSBzaWduaWZpY2FudCBhcyBGLXN0YXRpc3RpYyBwLXZhbHVlIGlzIDwwLjA1LgoqIFRoZSBhYm92ZSByZWdyZXNzaW9uIG1vZGVsIHNob3cgdGhhdCBmYWN0b3JzIGJlbG93IGFyZSBzaWduaWZpY2FudGx5IHJlbGF0ZWQgdG8gdGhlIG51bWJlciBvZiBzdG9yZSBwdXJjaGFzZXMKICArIHllYXJseSBob3VzZWhvbGQgaW5jb21lCiAgKyBudW1iZXIgb2YgY2hpbGRyZW4gaW4gIGhvdXNlaG9sZAogICsgYW1vdW50IHNwZW50IG9uOiB3aW5lLCBmcnVpdHMsIG1lYXQgcHJvZHVjdHMsIHN3ZWV0IHByb2R1Y3RzIGluIHRoZSBsYXN0IDIgeWVhcnMKICArIG51bWJlciBvZiBwdXJjaGFzZXMgbWFkZTogd2l0aCBhIGRpc2NvdW50LCB0aHJvdWdoIHdlYnNpdGUsIHVzaW5nIGEgY2F0YWxvZwogICsgbnVtYmVyIG9mIHdlYnNpdGUgdmlzaXRzIGluIHRoZSBsYXN0IG1vbnRoIAogICsgYWNjZXB0YW5jZSBvZiBvZmZlciBpbjogMm5kLCAzcmQsIDV0aCBjYW1wYWlnbiAKICArIGRhdGUgb2YgY3VzdG9tZXIncyBlbnJvbGxtZW50IHdpdGggdGhlIGNvbXBhbnkKCiMjIyMgMi4yOiBEb2VzIFVTIGZhcmUgc2lnbmlmaWNhbnRseSBiZXR0ZXIgdGhhbiB0aGUgUmVzdCBvZiB0aGUgV29ybGQgaW4gdGVybXMgb2YgdG90YWwgcHVyY2hhc2VzPyAKYGBge3J9CiMgTGFiZWwKZGF0YTIyID0gZGF0YQpkYXRhMjIkY291bnRyeTIgPSBpZmVsc2UoZGF0YTIyJENvdW50cnk9PSJVUyIsIlVTIiwiV29ybGQiKQpIbWlzYzo6ZGVzY3JpYmUoZGF0YTIyJGNvdW50cnkyKSAKCiMgSW5kZXBlbmRlbnQgdCB0ZXN0CnQudGVzdChUb3RhbFB1cmNoYXNlcyB+IGNvdW50cnkyLCBkYXRhPSBkYXRhMjIsIHZhci5lcXVhbD1UUlVFLGFsdGVybmF0aXZlPSJncmVhdGVyIikKYGBgCgoqIFRoZSBpbmRlcGVuZGVudCB0LXRlc3QgcmVzdWx0cyBzdWdnZXN0cyB0aGF0IHRoZSB0b3RhbCBwdXJjaGFzZSBvZiBVUyBpcyBzdGF0aXN0aWNhbGx5IGdyZWF0ZXIgdGhhbiB0aGUgcmVzdCBvZiB0aGUgd29ybGQsIGFzIHRoZSBwLXZhbHVlIGlzIDwwLjA1LiAKCiMjIyMgMi4zOiBQZW9wbGUgd2hvIGJ1eSBhbiBhYm92ZSBhdmVyYWdlIGFtb3VudCBvbiBnb2xkIGluIHRoZSBsYXN0IDIgeWVhcnMgaGF2ZSBtb3JlIGluIHN0b3JlIHB1cmNoYXNlPwpgYGB7cn0KIyBMYWJlbApkYXRhMjMgPSBkYXRhCmRhdGEyMyRnb2xkMiA9IGlmZWxzZShkYXRhMjMkTW50R29sZFByb2RzPiBtZWFuKGRhdGEyMyRNbnRHb2xkUHJvZHMpLCJhYm92ZV9hdmciLCJub3RfYWJvdmVfYXZnIikKSG1pc2M6OmRlc2NyaWJlKGRhdGEyMyRnb2xkMikKCiMgSW5kZXBlbmRlbnQgdCB0ZXN0CnQudGVzdChOdW1TdG9yZVB1cmNoYXNlcyB+IGdvbGQyLCBkYXRhPSBkYXRhMjMsIHZhci5lcXVhbD1UUlVFLGFsdGVybmF0aXZlPSJncmVhdGVyIikKYGBgCgoqIFRoZSBpbmRlcGVuZGVudCB0LXRlc3QgcmVzdWx0cyBzaG93IHRoYXQgdGhlIG1lYW4gb2Ygc3RvcmUgcHVyY2hhc2VzIGZvciBwZW9wbGUgd2hvIGJ1eSBhbiBhYm92ZSBhdmVyYWdlIGFtb3VudCBvZiBnb2xkIGluIHRoZSBsYXN0IDIgeWVhcnMgc3RhdGlzdGljYWxseSBncmVhdGVyIHRoYW4gcGVvcGxlIHdobyBidXkgYSBiZWxvdyBhdmVyYWdlIGFtb3VudCBpbiB0aGUgcGFzdCAyIHllYXJzLCBhcyB0aGUgcC12YWx1ZXMgaXMgPDAuMDUuICAKCgojIyMjIDIuNDogRG8gbWFycmllZCBQaEQgY291cGxlcyBoYXZlIGEgc2lnbmlmaWNhbnQgcmVsYXRpb24gd2l0aCBhbW91bnQgc3BlbnQgb24gZmlzaD8gV2hhdCBvdGhlciBmYWN0b3JzIGFyZSBzaWduaWZpY2FudGx5IHJlbGF0ZWQgdG8gYW1vdW50IHNwZW50IG9uIGZpc2g/IAoKYGBge3J9CiMgTGFiZWw6IGNvdXBsZXMgKGVpdGhlciBtYXJyaWVkIG9yIHRvZ2V0aGVyKQpkYXRhMjQgPSBkYXRhICU+JSBtdXRhdGUoY291cGxlcyA9IGlmZWxzZShNYXJpdGFsX1N0YXR1cyA9PSJNYXJyaWVkIiB8IE1hcml0YWxfU3RhdHVzID09IlRvZ2V0aGVyIiwiMSIsIjAiKSkKSG1pc2M6OmRlc2NyaWJlKGRhdGEyNCRjb3VwbGVzKQojIExhYmVsOiBQaEQgY291cGxlcyAKZGF0YTI0JFBoRGNvdXBsZXMgPSBpZmVsc2UoZGF0YTI0JGNvdXBsZXMgPT0iMSIgJiBkYXRhMjQkRWR1Y2F0aW9uID09IlBoRCIsICIxIiwiMCIpCkhtaXNjOjpkZXNjcmliZShkYXRhMjQkUGhEY291cGxlcykKYGBgCgpgYGB7cn0KIyBSZWdyZXNzaW9uCmRhdGEyNF9tb2RlbCA9IGRhdGEyNFstYygxLDMsNCw4LDMwLDMxLDMyKV0KbGluZWFyTW9kMiA9IGxtKE1udEZpc2hQcm9kdWN0c34gLiwgZGF0YSA9IGRhdGEyNF9tb2RlbCkKc3VtbWFyeShsaW5lYXJNb2QyKQpgYGAKCiogVGhlIHZhcmlhYmxlcyBpbiB0aGUgcmVncmVzc2lvbiBtb2RlbCBhcmUgam9pbnRseSBzaWduaWZpY2FudCBhcyBGLXN0YXRpc3RpYyBwLXZhbHVlIGlzIDwwLjA1LgoqIFRoZSByZWdyZXNzaW9uIG1vZGVsIHN1Z2dlc3RzIHRoZSBmb2xsb3dpbmcgZmVhdHVyZXMgaGF2ZSBhIHNpZ25pZmljYW50IHJlbGF0aW9uIHRvIGFtb3VudCBzcGVudCBvbiBmaXNoIHByb2R1Y3RzIGluIHRoZSBwYXN0IDIgeWVhcnM6IAogICsgUGhEIGNvdXBsZXMKICArIG51bWJlciBvZiB0ZWVucyBpbiBjdXN0b21lcnMnIGhvdXNlaG9sZAogICsgYW1vdW50IHNwZW50IG9uOiBmcnVpdHMsIG1lYXQgcHJvZHVjdHMsIHN3ZWV0IHByb2R1Y3RzLCBnb2xkIHByb2R1Y3RzIGluIHRoZSBsYXN0IDIgeWVhcnMKICArIG51bWJlciBvZiBwdXJjaGFzZXMgbWFkZTogd2l0aCBhIGRpc2NvdW50LCB1c2luZyBhIGNhdGFsb2csIGRpcmVjdGx5IGluIHN0b3JlcwogICsgbnVtYmVyIG9mIHdlYnNpdGUgdmlzaXRzIGluIHRoZSBsYXN0IG1vbnRoCiAgKyBhY2NlcHRhbmNlIG9mIG9mZmVyIGluOiAxc3QsIDV0aCBjYW1wYWlnbgogICsgZGF0ZSBvZiBjdXN0b21lcidzIGVucm9sbG1lbnQgd2l0aCB0aGUgY29tcGFueQoKIyMjIyAyLjU6IElzIHRoZXJlIGEgc2lnbmlmaWNhbnQgcmVsYXRpb25zaGlwIGJldHdlZW4gZ2VvZ3JhcGhpY2FsIHJlZ2lvbnMgYW5kIHN1Y2Vzc3Mgb2YgYSBjb21wYWlnbj8KCmBgYHtyfQojIFN1bW1hcnkgb2YgZ3JvdXBzCmRhdGEyNSA9IGRhdGEKZGF0YTI1JFJlc3BvbnNlID0gYXMubnVtZXJpYyhkYXRhMjUkUmVzcG9uc2UpCmRhdGEyNSAlPiUgZ3JvdXBfYnkoQ291bnRyeSkgJT4lIGdldF9zdW1tYXJ5X3N0YXRzKFJlc3BvbnNlLCB0eXBlPSJtZWFuX3NkIikKIyBDb21wYXJlIG1lYW5zIG9mIGdyb3VwIHVzaW5nIEFOT1ZBIHRlc3QKcmVzLmFvdiA9IGRhdGEyNSAlPiUgYW5vdmFfdGVzdChSZXNwb25zZSB+IENvdW50cnkpCnJlcy5hb3YKYGBgCiogVGhlIEFOT1ZBIHRlc3Qgc2hvd3MgdGhhdCB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgbWVhbnMgb2Ygb2ZmZXIgYWNjZXB0YW5jZSBiZXR3ZWVuIHJlZ2lvbnMgYXJlIG5vdCBzdGF0aXN0aWNhbGx5IGRpZmZlcmVudCwgYW5kIHdlIGNhbiBjb25jbHVkZSB0aGF0IHRoZXJlIGlzIG5vIHNpZ25pZmljYW50IHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGdlb2dyYXBoaWNhbCByZWdpb25zIGFuZCBzdWNjZXNzIG9mIGNhbXBhaWducyBhcyB0aGUgcC12YWx1ZSBpcyA+MC4wNS4gIAoKCiMjIyBTZWN0aW9uIDM6IERhdGEgVmlzdWFsaXphdGlvbnMKIyMjIyAzLjE6IFdoaWNoIG1hcmtldGluZyBjYW1wYWlnbiBpcyBtb3N0IHN1Y2Nlc3NmdWw/IApgYGB7cn0KIyBQcmVwYXJlIGRhdGEKZGF0YTMgPSBkYXRhICU+JSBzZWxlY3QgKEFjY2VwdGVkQ21wMSxBY2NlcHRlZENtcDIsQWNjZXB0ZWRDbXAzLEFjY2VwdGVkQ21wNCwgQWNjZXB0ZWRDbXA1KQpkYXRhMyA9IG11dGF0ZV9pZihkYXRhMywgaXMuZmFjdG9yLCB+YXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoLngpKSkKIyBQbG90CmRhdGEzICU+JSBnYXRoZXIoQ21wLCBPdXRjb21lLCBBY2NlcHRlZENtcDE6QWNjZXB0ZWRDbXA1KSAlPiUgZmlsdGVyKE91dGNvbWU+MCkgJT4lIGdyb3VwX2J5KENtcCkgJT4lIHRhbGx5KCkgJT4lIGdncGxvdChhZXMoeD1yZW9yZGVyKENtcCxuKSwgeT1uLCBmaWxsPUNtcCkpICsgZ2VvbV9jb2wod2lkdGg9MC43KSAgKyBnZW9tX3RleHQoc3RhdD0iaWRlbnRpdHkiLGFlcyhsYWJlbD1uKSxoanVzdD0tMC4yLHNpemU9My41KSArIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDE4MCkpICsgY29vcmRfZmxpcCgpICsgbGFicyh5PSIiLCB4PSIiLHRpdGxlPSJXaGljaCBtYXJrZXRpbmcgY2FtcGFpZ24gaXMgdGhlIG1vc3Qgc3VjZXNzZnVsPyIsIHN1YnRpdGxlID0iTnVtYmVyIG9mIGN1c3RvbWVycyB3aG8gYWNjZXB0ZWQgdGhlIG9mZmVyIikgKyBzY2FsZV9maWxsX3VjaGljYWdvKCkgKyB0aGVtZV9jbGFzc2ljKCkgKyB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uPSAibm9uZSIsIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSkKYGBgCgoKIyMjIyAzLjI6IFdoYXQgZG9lcyB0aGUgYXZlcmFnZSBjdXN0b21lciBsb29rIGxpa2UgZm9yIHRoaXMgY29tcGFueT8KYGBge3J9CiMgQXZlcmFnZSBZZWFyX0JpcnRoIGFuZCBJbmNvbWUgCmRhdGEgJT4lIHN1bW1hcmlzZShhdmdfWWVhcl9CaXJ0aCA9IHJvdW5kKG1lYW4oWWVhcl9CaXJ0aCkpLCBhdmdfSW5jb21lID0gcm91bmQobWVhbihJbmNvbWUpKSwgYXZnX0tpZGhvbWUgPSByb3VuZChtZWFuKEtpZGhvbWUpKSwgYXZnX1RlZW5ob21lPSByb3VuZChtZWFuKFRlZW5ob21lKSkpCmBgYAoKYGBge3J9CiMgSW5jb21lCnAxID0gZGF0YSAlPiUgZ2dwbG90KGFlcyh4PUluY29tZSkpICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGg9NTAwMCwgY29sb3I9IiMxZDM1NTciLGZpbGw9IiM0NTdiOWQiKSArIGdlb21fdmxpbmUoeGludGVyY2VwdD0gNTIwMDIsIGNvbG9yPSIjZTYzOTQ2IixsaW5ldHlwZT0iZGFzaGVkIikgKyBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMCwxODApKSArIGFubm90YXRlKCJ0ZXh0IixsYWJlbD0iQXZlcmFnZSA9ICQ1MiwwMDIiLCB4PSA5NTAwMCx5PTE3MCkgKyBsYWJzKHg9IiIsIHk9IkN1c3RvbWVyIENvdW50IiwgdGl0bGU9IlllYXJseSBob3VzZWhvbGQgaW5jb21lIikKIyBZZWFyX0JpcnRoCnAyID0gZGF0YSAlPiUgZ2dwbG90KGFlcyh4PVllYXJfQmlydGgpKSArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoPTUsIGNvbG9yPSIjMWQzNTU3IixmaWxsPSIjNDU3YjlkIikgKyBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9IDE5NjksIGNvbG9yPSIjZTYzOTQ2IixsaW5ldHlwZT0iZGFzaGVkIikgKyBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMCwzODApKSArIGFubm90YXRlKCJ0ZXh0IixsYWJlbD0iQXZlcmFnZSA9IDE5NjkiLCB4PSAxOTg1LHk9MzUwKSArIGxhYnModGl0bGU9IkJpcnRoIHllYXIiLCB5PSJDdXN0b21lciBDb3VudCIseD0iIikgCiMgUGxvdCAKZ2dhcnJhbmdlKHAxLHAyLG5jb2w9MikKYGBgCmBgYHtyfQojIEtpZGhvbWUKcDMgPSBkYXRhICU+JSBncm91cF9ieShLaWRob21lKSAlPiUgdGFsbHkoKSAlPiUgZ2dwbG90KGFlcyh4PWZhY3RvcihLaWRob21lKSx5PW4sIGZpbGw9bikpICsgZ2VvbV9jb2wod2lkdGg9MC43KSArIHNjYWxlX2ZpbGxfdmlyaWRpcygpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKyBsYWJzKHk9IkN1c3RvbWVyIGNvdW50IiwgeD0iIiwgdGl0bGU9Ik51bWJlciBvZiBjaGlsZHJlbiBpbiBob3VzZWhvbGQiKSArIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEzMDApKQojIFRlZW5ob21lCnA0ID0gZGF0YSAlPiUgZ3JvdXBfYnkoVGVlbmhvbWUpICU+JSB0YWxseSgpICU+JSBnZ3Bsb3QoYWVzKHg9ZmFjdG9yKFRlZW5ob21lKSx5PW4sIGZpbGw9bikpICsgZ2VvbV9jb2wod2lkdGg9MC43KSArIHNjYWxlX2ZpbGxfdmlyaWRpcygpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKyBsYWJzKHk9IkN1c3RvbWVyIGNvdW50IiwgeD0iIiwgdGl0bGU9Ik51bWJlciBvZiB0ZWVucyBpbiBob3VzZWhvbGQiKSArIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEzMDApKQojIE1hcml0YWwgU3RhdHVzIApwNSA9IGRhdGEgJT4lIGdyb3VwX2J5KE1hcml0YWxfU3RhdHVzKSAlPiUgdGFsbHkoKSAlPiUgZ2dwbG90KGFlcyh4PXJlb3JkZXIoTWFyaXRhbF9TdGF0dXMsbikseT1uLCBmaWxsPW4pKSArIGdlb21fY29sKHdpZHRoPTAuNykgKyBzY2FsZV9maWxsX3ZpcmlkaXMoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsgbGFicyh5PSJDdXN0b21lciBjb3VudCIsIHg9IiIsIHRpdGxlPSJNYXJpdGFsIHN0YXR1cyIpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikKIyBFZHVjYXRpb24KcDYgPSBkYXRhICU+JSBncm91cF9ieShFZHVjYXRpb24pICU+JSB0YWxseSgpICU+JSBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihFZHVjYXRpb24sbikseT1uLCBmaWxsPW4pKSArIGdlb21fY29sKHdpZHRoPTAuNykgKyBzY2FsZV9maWxsX3ZpcmlkaXMoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsgbGFicyh5PSJDdXN0b21lciBjb3VudCIsIHg9IiIsIHRpdGxlPSJDdXN0b21lcidzIGVkdWNhdGlvbmFsIGxldmVsIikgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQoKIyBDb21iaW5lZCBwbG90CmdnYXJyYW5nZShwMyxwNCxwNSxwNiwgbmNvbD0yLCBucm93PTIpIApgYGAKCmBgYHtyfQojIE1hcml0YWwgc3RhdHVzIGFuZCBlZHVjYXRpb24gbGV2ZWwKZGF0YSAlPiUgZ3JvdXBfYnkoTWFyaXRhbF9TdGF0dXMsRWR1Y2F0aW9uKSAlPiUgdGFsbHkoc29ydD1UKSAlPiUgbXV0YXRlKE1hcml0YWxfRWR1ID0gcGFzdGUoTWFyaXRhbF9TdGF0dXMsIEVkdWNhdGlvbiwgc2VwPSItIikpICU+JSBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihNYXJpdGFsX0VkdSxuKSwgeT1uKSkgKyBnZW9tX3BvaW50KGNvbG9yPSIjZjc3ZjAwIikgKyBnZW9tX3NlZ21lbnQoYWVzKHg9TWFyaXRhbF9FZHUsIHhlbmQ9TWFyaXRhbF9FZHUsIHk9MCwgeWVuZD1uKSxjb2xvcj0iIzMzNWM2NyIpICsgY29vcmRfZmxpcCgpICsgdGhlbWVfbGlnaHQoKSArIHRoZW1lKHBhbmVsLmJvcmRlcj1lbGVtZW50X2JsYW5rKCksYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSxheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1ham9yLnk9ZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yLng9ZWxlbWVudF9ibGFuaygpKSArIGxhYnMoeT0iQ3VzdG9tZXIgY291bnQiLCB4PSIiLCB0aXRsZT0iQ3VzdG9tZXJzJyBtYXJpdGFsIHN0YXR1cyBhbmQgZWR1Y2F0aW9uIGxldmVsIikKYGBgCgoKIyMjIyAzLjM6IFdoaWNoIHByb2R1Y3RzIGFyZSBwZXJmb3JtaW5nIGJlc3Q/CmBgYHtyfQpQckxhYmVsID0gYygiRnJ1aXRzIiwiU3dlZXQiLCJGaXNoIiwiR29sZCIsIk1lYXQiLCJXaW5lcyIpCmRhdGEgJT4lIHNlbGVjdChNbnRXaW5lcywgTW50RnJ1aXRzLCBNbnRNZWF0UHJvZHVjdHMsIE1udEZpc2hQcm9kdWN0cywgTW50U3dlZXRQcm9kdWN0cywgTW50R29sZFByb2RzKSAlPiUgZ2F0aGVyKFByb2R1Y3QsIEFtdFNwZW50LCBNbnRXaW5lczpNbnRHb2xkUHJvZHMpICU+JSBncm91cF9ieShQcm9kdWN0KSAlPiUgdGFsbHkoQW10U3BlbnQpICU+JSBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihQcm9kdWN0LG4pLCB5PW4sIGZpbGw9UHJvZHVjdCkpICsgZ2VvbV9jb2woKSArIGdlb21fdGV4dChzdGF0PSJpZGVudGl0eSIsYWVzKGxhYmVsPW4pLGhqdXN0PS0wLjIsc2l6ZT0zKSArIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDcwMDAwMCkpICsgY29vcmRfZmxpcCgpICsgc2NhbGVfZmlsbF91Y2hpY2FnbygpICsgdGhlbWVfY2xhc3NpYygpICsgdGhlbWUoYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCksIGxlZ2VuZC5wb3NpdGlvbj0gIm5vbmUiLCBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTApKSArIGxhYnMoeT0iQW1vdW50IHNwZW50IiwgeD0gIiIsIHRpdGxlID0gIldoaWNoIHByb2R1Y3RzIGFyZSBwZXJmb3JtaW5nIGJlc3Q/Iiwgc3VidGl0bGUgPSAiQW1vdW50IHNwZW50IGJ5IGN1c3RvbWVycyBpbiB0aGUgbGFzdCB0d28geWVhcnMgYnkgcHJvZHVjdCB0eXBlIikgKyBzY2FsZV94X2Rpc2NyZXRlKGxhYmVsPSBQckxhYmVsKQpgYGAKCgojIyMjIDMuMzogV2hpY2ggcHJvZHVjdHMgYXJlIHBlcmZvcm1pbmcgYmVzdD8KYGBge3J9CiMgTGFiZWxzCkNoTGFiZWwgPSBjKCJDYXRhbG9nIiwiV2ViIiwiU3RvcmUiKQojIFBsb3QKZGF0YSAlPiUgc2VsZWN0KE51bVdlYlB1cmNoYXNlcywgTnVtQ2F0YWxvZ1B1cmNoYXNlcywgTnVtU3RvcmVQdXJjaGFzZXMpICU+JSBnYXRoZXIoQ2hhbm5lbCwgUHVyY2hhc2VzLCBOdW1XZWJQdXJjaGFzZXM6TnVtU3RvcmVQdXJjaGFzZXMpICU+JSBncm91cF9ieShDaGFubmVsKSAlPiUgdGFsbHkoUHVyY2hhc2VzKSAlPiUgZ2dwbG90KGFlcyh4PXJlb3JkZXIoQ2hhbm5lbCxuKSwgeT1uLCBmaWxsPUNoYW5uZWwpKSArIGdlb21fY29sKHdpZHRoPTAuNikgKyBnZW9tX3RleHQoc3RhdD0iaWRlbnRpdHkiLGFlcyhsYWJlbD1uKSxoanVzdD0tMC4yLHNpemU9My41KSArIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEzMDAwKSkgKyBjb29yZF9mbGlwKCkgKyBzY2FsZV9maWxsX3VjaGljYWdvKCkgKyB0aGVtZV9jbGFzc2ljKCkgKyB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uPSAibm9uZSIsIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTEpKSArIGxhYnMoeT0iTnVtYmVyIG9mIHB1cmNoYXNlcyIsIHg9ICIiLCB0aXRsZSA9ICJXaGljaCBjaGFubmVscyBhcmUgdW5kZXJwZXJmb3JtaW5nPyIsIHN1YnRpdGxlID0gIk51bWJlciBvZiBwdXJjaGFzZXMgbWFkZSB0aHJvdWdoIHN0b3JlLCB3ZWIgYW5kIGNhdGFsb2ciKSArIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPUNoTGFiZWwpCmBgYAoK