E-commerce Project One

By Vincent Mwenda

Setting a working directory

 setwd("~/R training")

Importing library

library(readxl)
library(tidyverse)
library(ggplot2)
library(psych)
library(MASS)
library(dplyr)
library(graphics)
library(sjmisc) 
library(summarytools)
library(ggthemes)
library(car)
library(rstatix)
library(stargazer)
library(corrplot)
library(forecast)
library(lmtest)
library(scales)
library(stats)
library(mice)

importing data

data<-read.csv("E-commerce_data.csv")
head(data)
NA

using na.strings

data <- read.csv("E-commerce_data.csv", na.strings = c("", "NA", "null"))
head(data)

Data analysis

1. Handling missing values

identifying missing values

data<-read.csv("E-commerce_data.csv")
missing_value <- colSums(is.na(data))
data.frame(missing_value)

missing values in column

colSums(is.na(data))
   TransactionID       CustomerID        ProductID         Quantity    PaymentMethod 
               0                0                0                0                0 
 TransactionDate  ProductCategory            Price           Rating      TotalAmount 
               0                0                0                0                0 
             Age           Gender         Location MembershipStatus 
               8                0                0                0 

handling missing values

## handling missing value for Age
#data$Age[is.na(data$Age)] <- median(data$Age, na.rm = TRUE)
head(data)

checking if missing values have been cleaned

colSums(is.na(data))
   TransactionID       CustomerID        ProductID         Quantity    PaymentMethod 
               0                0                0                0                0 
 TransactionDate  ProductCategory            Price           Rating      TotalAmount 
               0                0                0                0                0 
             Age           Gender         Location MembershipStatus 
               8                0                0                0 

handling missing value for gender

## handling missing value for gender
get_mode <- function(x, na.rm = FALSE) {
  if (na.rm) x <- na.omit(x)
  ux <- unique(x)
  ux[which.max(tabulate(match(x, ux)))]
}

# Use it
get_mode(data$Gender, na.rm = TRUE)
[1] "Male"
data$Gender[is.na(data$Gender)] <- get_mode(data$Gender, na.rm = TRUE)

3.2 Outlier Detection

• Detect and handle outliers in numerical variables (e.g., Age, Price, TotalAmount) using methods like the IQR rule or Z-score.

check for outliers using IQR

find_outliers_iqr <- function(x) {
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  IQR_value <- Q3 - Q1
  lower_bound <- Q1 - 1.5 * IQR_value
  upper_bound <- Q3 + 1.5 * IQR_value
  return(which(x < lower_bound | x > upper_bound))
}
numeric_columns <- sapply(data, is.numeric)
outlier_indices_list <- lapply(data[, numeric_columns], find_outliers_iqr)

# Print summary of outliers
for (col in names(outlier_indices_list)) {
  cat("Variable:", col, " - Outliers found:", length(outlier_indices_list[[col]]), "\n")
}
Variable: TransactionID  - Outliers found: 0 
Variable: CustomerID  - Outliers found: 0 
Variable: ProductID  - Outliers found: 0 
Variable: Quantity  - Outliers found: 0 
Variable: Price  - Outliers found: 0 
Variable: Rating  - Outliers found: 0 
Variable: TotalAmount  - Outliers found: 4 
Variable: Age  - Outliers found: 0 

clean outliers using IQR(removed rows with outliers)

Q1 <- quantile(data$TotalAmount, 0.25, na.rm = TRUE)
Q3 <- quantile(data$TotalAmount, 0.75, na.rm = TRUE)
IQR <- Q3 - Q1

lower_bound <- Q1 - 1.5 * IQR
upper_bound <- Q3 + 1.5 * IQR

data_clean <- data[data$TotalAmount >= lower_bound & data$TotalAmount <= upper_bound, ]

checking if data has been cleaned using IQR

Q1 <- quantile(data_clean$TotalAmount, 0.25, na.rm = TRUE)
Q3 <- quantile(data_clean$TotalAmount, 0.75, na.rm = TRUE)
IQR <- Q3 - Q1
lower <- Q1 - 1.5 * IQR
upper <- Q3 + 1.5 * IQR

sum(data_clean$TotalAmount < lower | data_clean$TotalAmount > upper, na.rm = TRUE)
[1] 3

##cleaning the remaining outliers

Q1 <- quantile(data_clean$TotalAmount, 0.25, na.rm = TRUE)
Q3 <- quantile(data_clean$TotalAmount, 0.75, na.rm = TRUE)
IQR <- Q3 - Q1

lower_bound <- Q1 - 1.5 * IQR
upper_bound <- Q3 + 1.5 * IQR

#data_clean <- data[data_clean$TotalAmount >= lower_bound & data$TotalAmount <= upper_bound, ]
#data_clean

##double checking if the outliers have been cleaned

Q1 <- quantile(data_clean$TotalAmount, 0.25, na.rm = TRUE)
Q3 <- quantile(data_clean$TotalAmount, 0.75, na.rm = TRUE)
IQR <- Q3 - Q1
lower <- Q1 - 1.5 * IQR
upper <- Q3 + 1.5 * IQR

sum(data_clean$TotalAmount < lower | data_clean$TotalAmount > upper, na.rm = TRUE)
[1] 3

3.3 Exploratory Data Analysis (EDA)

• Perform univariate and bivariate analysis to understand the distribution of variables and relationships between them

## Summary Statistics 
summary(data_clean)
 TransactionID      CustomerID       ProductID        Quantity     PaymentMethod     
 Min.   :  1.00   Min.   :  4.00   Min.   : 1.00   Min.   :1.000   Length:296        
 1st Qu.: 74.75   1st Qu.: 76.75   1st Qu.: 6.00   1st Qu.:2.000   Class :character  
 Median :149.50   Median :156.50   Median :11.00   Median :3.000   Mode  :character  
 Mean   :149.80   Mean   :154.93   Mean   :10.64   Mean   :3.101                     
 3rd Qu.:224.25   3rd Qu.:230.25   3rd Qu.:15.25   3rd Qu.:4.000                     
 Max.   :300.00   Max.   :299.00   Max.   :20.00   Max.   :5.000                     
                                                                                     
 TransactionDate    ProductCategory        Price            Rating     
 Length:296         Length:296         Min.   : 33.36   Min.   :1.400  
 Class :character   Class :character   1st Qu.:145.89   1st Qu.:2.100  
 Mode  :character   Mode  :character   Median :215.02   Median :2.600  
                                       Mean   :250.65   Mean   :2.932  
                                       3rd Qu.:341.40   3rd Qu.:3.700  
                                       Max.   :466.49   Max.   :5.000  
                                                                       
  TotalAmount           Age           Gender            Location        
 Min.   :  33.36   Min.   :18.00   Length:296         Length:296        
 1st Qu.: 348.95   1st Qu.:30.00   Class :character   Class :character  
 Median : 650.96   Median :42.00   Mode  :character   Mode  :character  
 Mean   : 782.45   Mean   :41.80                                        
 3rd Qu.:1087.23   3rd Qu.:52.25                                        
 Max.   :2252.85   Max.   :65.00                                        
                   NA's   :8                                            
 MembershipStatus  
 Length:296        
 Class :character  
 Mode  :character  
                   
                   
                   
                   

frequency of categorical variables

## frequency of categorical variables
#freq(data_clean) 

boxplot of Age

boxplot(data_clean$Age, main='Age')

Histogram of price

hist(data_clean$Price,main="Histogram of Price",xlab="Price",col='blue')

Frequency Table for Gender

table(data_clean$Gender)

       Female   Male 
    13    125    158 
table(data_clean$PaymentMethod)

Cash on Delivery      Credit Card       Debit Card              UPI 
              79               63               65               89 
table(data_clean$MembershipStatus)

  Basic    Gold Premium 
    103      93     100 

Bivariate(price vs total amount,priduct category vs price,payment method vs memembership status)

##scatterplot

ggplot(data_clean,aes(x=Price,y=TotalAmount))+
  geom_point(alpha=0.6,color="blue")+
  labs(title="Scatterplot of Price vs TotalAmount")

boxplot

library(ggplot2)
ggplot(data_clean,aes(x=ProductCategory, y=Price,fill=ProductCategory))+
  geom_boxplot()+
  labs(title="ProductCategory vs Price")+
  geom_boxplot()

linegraph of product vs price category

ggplot(data=data_clean, aes(x=ProductCategory, y=Price))+
  geom_line()

contigency table of payment method vs membership status

contingency_table<-table(data_clean$PaymentMethod,data_clean$MembershipStatus)
print(contingency_table)
                  
                   Basic Gold Premium
  Cash on Delivery    24   34      21
  Credit Card         22   16      25
  Debit Card          22   20      23
  UPI                 35   23      31

3.4 REGRESSION MODEL

• Build a regression model to predict TotalAmount based on variables like Age, Price, Quantity, and Rating.

model<-lm(TotalAmount~Age+Price+Rating+Quantity,data=data_clean)
summary(model)

Call:
lm(formula = TotalAmount ~ Age + Price + Rating + Quantity, data = data_clean)

Residuals:
    Min      1Q  Median      3Q     Max 
-493.48  -97.40   12.02  104.31  455.06 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept) -727.22946   53.14078 -13.685   <2e-16 ***
Age            0.09187    0.74622   0.123    0.902    
Price          3.15513    0.08333  37.862   <2e-16 ***
Rating       -10.00590    9.44359  -1.060    0.290    
Quantity     239.44714    6.83102  35.053   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 167.6 on 283 degrees of freedom
  (8 observations deleted due to missingness)
Multiple R-squared:  0.9065,    Adjusted R-squared:  0.9052 
F-statistic: 685.8 on 4 and 283 DF,  p-value: < 2.2e-16

model diagnostics

library(car)
vif_values=vif(model)
print(vif_values)
     Age    Price   Rating Quantity 
1.004085 1.004369 1.012183 1.009450 

corelation

library(tidyverse)
library(corrplot)

cor_matrix<-cor(select(data_clean, where(is.numeric)),use="complete.obs")
corrplot(cor_matrix,
         method="color",
         type="full",
         tl.col="black",
         tl.srt=45,
         addCoef.col="white",
         number.cex=0.7,
         diag=TRUE)

checking residuals

library(forecast)
checkresiduals(model)

    Breusch-Godfrey test for serial correlation of order up to 10

data:  Residuals
LM test = 3.9246, df = 10, p-value = 0.9507

##3.5 Correlation Analysis • Analyze the correlation between numerical variables (e.g., Price vs. TotalAmount, Age vs. TotalAmount).

Hypothesis:H0:There is significant relationship between the variables H1:There is no significant Relationship in atleast one of the variables

##  correlation matrix
library(ggplot2)
library(corrplot)
num_data_clean<-data_clean[,c("Age","Price","Quantity","Rating","TotalAmount")]
cor_matrix<-cor(num_data_clean,use="complete.obs")
cor_matrix
                    Age       Price    Quantity      Rating TotalAmount
Age          1.00000000 -0.04463762  0.02247050  0.03892636 -0.01491569
Price       -0.04463762  1.00000000  0.01927763 -0.04750919  0.70292125
Quantity     0.02247050  0.01927763  1.00000000 -0.09178436  0.65531595
Rating       0.03892636 -0.04750919 -0.09178436  1.00000000 -0.11081925
TotalAmount -0.01491569  0.70292125  0.65531595 -0.11081925  1.00000000

##pearson Rank

##pearson Rank
library(corrplot)
num_data_clean<-data_clean[,c("Age","Price","Quantity","Rating","TotalAmount")]
pearson_matrix<-cor(num_data_clean,method="pearson")
pearson_matrix
            Age       Price    Quantity      Rating TotalAmount
Age           1          NA          NA          NA          NA
Price        NA  1.00000000  0.02973474 -0.06313723   0.7066571
Quantity     NA  0.02973474  1.00000000 -0.09877651   0.6594306
Rating       NA -0.06313723 -0.09877651  1.00000000  -0.1227862
TotalAmount  NA  0.70665706  0.65943058 -0.12278622   1.0000000

spearman

## spearman
library(corrplot)
num_data_clean<-data_clean[,c("Age","Price","Quantity","Rating","TotalAmount")]
spearman_matrix<-cor(num_data_clean,method="spearman")
spearman_matrix
            Age        Price    Quantity       Rating TotalAmount
Age           1           NA          NA           NA          NA
Price        NA  1.000000000  0.01763169 -0.001029652  0.68529096
Quantity     NA  0.017631694  1.00000000 -0.066746308  0.68980207
Rating       NA -0.001029652 -0.06674631  1.000000000 -0.05791432
TotalAmount  NA  0.685290962  0.68980207 -0.057914324  1.00000000

3.6 Statistical Tests

• Perform a t-test to compare the average TotalAmount spent by male and female customers. • Perform a chi-square test to check the association between ProductCategory and PaymentMethod. • Perform ANOVA to compare the average TotalAmount across different MembershipStatus levels. ________________________________________

• Perform a t-test to compare the average TotalAmount spent by male and female customers.

T-Test

## Hypothesis: H_0:There is a statistically significant difference between male and female spending.")
##             H_1:There is no statistically significant difference between male and female spending.") 
library(dplyr)

#Checking for missing values in Gender and TotalAmount
sum(is.na(data_clean$Gender))
[1] 0
sum(is.na(data_clean$TotalAmount))
[1] 0
# 5. Separate TotalAmount by Gender
male_spending <- data_clean %>% filter(Gender == "Male") %>% pull(TotalAmount)
female_spending <- data_clean %>% filter(Gender == "Female") %>% pull(TotalAmount)

# 6. Perform Independent T-Test (Welch's T-Test - assumes unequal variances)
t_test_result <- t.test(male_spending, female_spending, var.equal = FALSE)

# 7. Print the result
print(t_test_result)

    Welch Two Sample t-test

data:  male_spending and female_spending
t = 2.4726, df = 274.83, p-value = 0.01402
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
  32.3603 285.1822
sample estimates:
mean of x mean of y 
 854.8084  696.0371 

Output interpretation for t_test:p_value<0.05;Reject H0 conclusion: there is no significant difference between Female and male spending

##• Perform a chi-square test to check the association between ProductCategory and PaymentMethod. ## chi square Test

## Hypothesis:Hâ‚€: There is an association between Product Category and Payment Method
##            H1:There is no association between Product Category and Payment Method
contingency_table <- table(data_clean$ProductCategory, data_clean$PaymentMethod)
chi_square_result <- chisq.test(contingency_table)
print(chi_square_result)

    Pearson's Chi-squared test

data:  contingency_table
X-squared = 2.2394, df = 6, p-value = 0.8964

conclusion: P-value >0.05: Do not Reject the nullhypothesis; There is an association between payment method and product category.

##• Perform ANOVA to compare the average TotalAmount across different MembershipStatus levels.

##Hypothesis;H0:there is significant difference between average Total amount accross different membership levels
##          H1:there is no significant difference between average Total amount accross different membership levels
# Perform one-way ANOVA
anova_result <- aov(TotalAmount ~ MembershipStatus, data = data_clean)
summary(anova_result)
                  Df   Sum Sq Mean Sq F value Pr(>F)
MembershipStatus   2   451973  225986   0.762  0.467
Residuals        293 86840140  296383               

Output interpretation:P_value<0.05,Do not reject null hypothesis Conclusion:there is significant difference between average Total Amount across different membership levels ## 4. Data Visualization 4.1 Histogram • Visualize the distribution of numerical variables like Age, Price, and TotalAmount.

4.4 Donut Chart • Display the distribution of PaymentMethod used by customers. 4.5 Scatter Plot • Visualize the relationship between Price and TotalAmount. 4.6 Box Plot • Identify outliers in TotalAmount or Price. ________________________________________

par(mfrow = c(1, 3))

# Histograms
hist(data_clean$Age, col = "lightblue", main = "Distribution of Age", xlab = "Age")
hist(data_clean$Price, col = "lightgreen", main = "Distribution of Price", xlab = "Price")
hist(data_clean$TotalAmount, col = "salmon", main = "Distribution of TotalAmount", xlab = "Total Amount")

hist(data_clean$Rating, col = "red", main = "Distribution of Rating", xlab = "Rating")

##4.2 Pie Chart • Show the proportion of customers by Gender or MembershipStatus.

# Pie chart for Membership Status
membership_counts <- table(data_clean$MembershipStatus)
pie(membership_counts, main = "Membership Status Distribution", col = rainbow(length(membership_counts)))

Pie chart for Gender

# Pie chart for Gender
gender_counts <- table(data_clean$Gender)
pie(gender_counts, main = "Customer Gender Distribution", col = c("lightblue", "pink"))

4.3 Bar Chart

• Compare the average TotalAmount spent by customers in different Location or ProductCategory.

## TotalAmount by ProductCategory
library(ggplot2)
library(dplyr)
# Calculate average total amount by product category
avg_by_category <- data %>%
  group_by(ProductCategory) %>%
  summarise(AvgTotalAmount = mean(TotalAmount, na.rm = TRUE))
ggplot(avg_by_category, aes(x = reorder(ProductCategory, AvgTotalAmount), 
                            y = AvgTotalAmount, 
                            fill = ProductCategory)) +
  geom_bar(stat = "identity") +
  labs(title = "Average Total Amount Spent by Product Category", 
       x = "Product Category", 
       y = "Average Total Amount") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  guides(fill = FALSE)

TotalAmount by Location bar chart

## TotalAmount by Location bar chart
library(ggplot2)
library(dplyr)
# Calculate average total amount by Location
avg_by_Location<- data %>%
  group_by(Location) %>%
  summarise(AvgTotalAmount = mean(TotalAmount, na.rm = TRUE))
ggplot(avg_by_Location, aes(x = reorder(Location, AvgTotalAmount), 
                            y = AvgTotalAmount, 
                            fill = Location)) +
  geom_bar(stat = "identity") +
  labs(title = "Average Total Amount Spent by Location", 
       x = "Location", 
       y = "Average Total Amount") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  guides(fill = FALSE)

Donut PLOT

# Load libraries
library(ggplot2)
library(ggforce)
library(dplyr)


# Count payment method frequencies
payment_counts <- data %>%
  count(PaymentMethod, name = "Count") %>%
  mutate(
    Percent = round(Count / sum(Count) * 100, 1),
    Cumulative = cumsum(Count),
    start = lag(Cumulative, default = 0),
    end = Cumulative,
    label_position = (start + end) / 2
  )

# Convert to radians
payment_counts$radians_start <- payment_counts$start * 2 * pi / sum(payment_counts$Count)
payment_counts$radians_end <- payment_counts$end * 2 * pi / sum(payment_counts$Count)# Plot


ggplot(payment_counts) +
  geom_arc_bar(
    aes(
      x0 = 0, y0 = 0, r0 = 0.5, 
      r = 1,
      start = radians_start,
      end = radians_end,
      fill = PaymentMethod
    )
  ) +
  coord_polar(theta = "y") +
  xlim(c(-1.1, 1.1)) +
  ylim(c(0, 1.1)) +
  scale_fill_brewer(palette = "Set3") +
  theme_void() +
  labs(title = "Distribution of Payment Methods Used by Customers", fill = "Payment Method") +
  annotate("text",
           x = 0,
           y = seq(0.6, 1.05, length.out = nrow(payment_counts)),
           label = paste0(payment_counts$Percent, "%"),
           size = 4,
           color = "black")

scatterplot

ggplot(data = data, aes(x = Price, y = TotalAmount, color = as.factor(Quantity))) +
  geom_point(alpha = 0.7) +
  labs(
    title = "Price vs TotalAmount (Colored by Quantity)",
    x = "Price per Unit",
    y = "Total Amount Spent",
    color = "Quantity"
  ) +
  theme_minimal()

Boxplot

## TotalAmount
ggplot(data, aes(y = TotalAmount)) +
  geom_boxplot(fill = "lightblue", color = "black") +
  labs(title = "Box Plot of TotalAmount", y = "Total Amount Spent") +
  theme_minimal()

Price boxplot

##Price
ggplot(data, aes(y = Price)) +
  geom_boxplot(fill = "lightblue", color = "black") +
  labs(title = "Box Plot of Price", y = "Price") +
  theme_minimal()

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIEUtY29tbWVyY2UgUHJvamVjdCBPbmUNCg0KIyBCeSBWaW5jZW50IE13ZW5kYQ0KDQojIyBTZXR0aW5nIGEgd29ya2luZyBkaXJlY3RvcnkNCmBgYHtyfQ0KIHNldHdkKCJ+L1IgdHJhaW5pbmciKQ0KYGBgDQoNCiMjIEltcG9ydGluZyBsaWJyYXJ5DQpgYGB7cn0NCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHBzeWNoKQ0KbGlicmFyeShNQVNTKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ3JhcGhpY3MpDQpsaWJyYXJ5KHNqbWlzYykgDQpsaWJyYXJ5KHN1bW1hcnl0b29scykNCmxpYnJhcnkoZ2d0aGVtZXMpDQpsaWJyYXJ5KGNhcikNCmxpYnJhcnkocnN0YXRpeCkNCmxpYnJhcnkoc3RhcmdhemVyKQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoZm9yZWNhc3QpDQpsaWJyYXJ5KGxtdGVzdCkNCmxpYnJhcnkoc2NhbGVzKQ0KbGlicmFyeShzdGF0cykNCmxpYnJhcnkobWljZSkNCmBgYA0KDQoNCiMjIGltcG9ydGluZyBkYXRhDQpgYGB7cn0NCmRhdGE8LXJlYWQuY3N2KCJFLWNvbW1lcmNlX2RhdGEuY3N2IikNCmhlYWQoZGF0YSkNCg0KYGBgDQoNCiMjIHVzaW5nIG5hLnN0cmluZ3MNCmBgYHtyfQ0KZGF0YSA8LSByZWFkLmNzdigiRS1jb21tZXJjZV9kYXRhLmNzdiIsIG5hLnN0cmluZ3MgPSBjKCIiLCAiTkEiLCAibnVsbCIpKQ0KaGVhZChkYXRhKQ0KYGBgDQoNCiMjIERhdGEgYW5hbHlzaXMNCiMjIDEuIEhhbmRsaW5nIG1pc3NpbmcgdmFsdWVzDQojIyAqaWRlbnRpZnlpbmcgbWlzc2luZyB2YWx1ZXMqDQpgYGB7cn0NCmRhdGE8LXJlYWQuY3N2KCJFLWNvbW1lcmNlX2RhdGEuY3N2IikNCm1pc3NpbmdfdmFsdWUgPC0gY29sU3Vtcyhpcy5uYShkYXRhKSkNCmRhdGEuZnJhbWUobWlzc2luZ192YWx1ZSkNCmBgYA0KDQojIyBtaXNzaW5nIHZhbHVlcyBpbiBjb2x1bW4NCmBgYHtyfQ0KY29sU3Vtcyhpcy5uYShkYXRhKSkNCg0KYGBgDQoNCiMjIGhhbmRsaW5nIG1pc3NpbmcgdmFsdWVzDQpgYGB7cn0NCiMjIGhhbmRsaW5nIG1pc3NpbmcgdmFsdWUgZm9yIEFnZQ0KI2RhdGEkQWdlW2lzLm5hKGRhdGEkQWdlKV0gPC0gbWVkaWFuKGRhdGEkQWdlLCBuYS5ybSA9IFRSVUUpDQpoZWFkKGRhdGEpDQpgYGANCg0KIyMgY2hlY2tpbmcgaWYgbWlzc2luZyB2YWx1ZXMgaGF2ZSBiZWVuIGNsZWFuZWQNCmBgYHtyfQ0KY29sU3Vtcyhpcy5uYShkYXRhKSkNCmBgYA0KDQojIyBoYW5kbGluZyBtaXNzaW5nIHZhbHVlIGZvciBnZW5kZXINCmBgYHtyfQ0KIyMgaGFuZGxpbmcgbWlzc2luZyB2YWx1ZSBmb3IgZ2VuZGVyDQpnZXRfbW9kZSA8LSBmdW5jdGlvbih4LCBuYS5ybSA9IEZBTFNFKSB7DQogIGlmIChuYS5ybSkgeCA8LSBuYS5vbWl0KHgpDQogIHV4IDwtIHVuaXF1ZSh4KQ0KICB1eFt3aGljaC5tYXgodGFidWxhdGUobWF0Y2goeCwgdXgpKSldDQp9DQoNCiMgVXNlIGl0DQpnZXRfbW9kZShkYXRhJEdlbmRlciwgbmEucm0gPSBUUlVFKQ0KZGF0YSRHZW5kZXJbaXMubmEoZGF0YSRHZW5kZXIpXSA8LSBnZXRfbW9kZShkYXRhJEdlbmRlciwgbmEucm0gPSBUUlVFKQ0KDQpgYGANCg0KDQojIyAzLjIgT3V0bGllciBEZXRlY3Rpb24NCuKAoglEZXRlY3QgYW5kIGhhbmRsZSBvdXRsaWVycyBpbiBudW1lcmljYWwgdmFyaWFibGVzIChlLmcuLCBBZ2UsIFByaWNlLCBUb3RhbEFtb3VudCkgdXNpbmcgbWV0aG9kcyBsaWtlIHRoZSBJUVIgcnVsZSBvciBaLXNjb3JlLg0KDQojIyBjaGVjayBmb3Igb3V0bGllcnMgdXNpbmcgSVFSDQpgYGB7cn0NCmZpbmRfb3V0bGllcnNfaXFyIDwtIGZ1bmN0aW9uKHgpIHsNCiAgUTEgPC0gcXVhbnRpbGUoeCwgMC4yNSwgbmEucm0gPSBUUlVFKQ0KICBRMyA8LSBxdWFudGlsZSh4LCAwLjc1LCBuYS5ybSA9IFRSVUUpDQogIElRUl92YWx1ZSA8LSBRMyAtIFExDQogIGxvd2VyX2JvdW5kIDwtIFExIC0gMS41ICogSVFSX3ZhbHVlDQogIHVwcGVyX2JvdW5kIDwtIFEzICsgMS41ICogSVFSX3ZhbHVlDQogIHJldHVybih3aGljaCh4IDwgbG93ZXJfYm91bmQgfCB4ID4gdXBwZXJfYm91bmQpKQ0KfQ0KbnVtZXJpY19jb2x1bW5zIDwtIHNhcHBseShkYXRhLCBpcy5udW1lcmljKQ0Kb3V0bGllcl9pbmRpY2VzX2xpc3QgPC0gbGFwcGx5KGRhdGFbLCBudW1lcmljX2NvbHVtbnNdLCBmaW5kX291dGxpZXJzX2lxcikNCg0KIyBQcmludCBzdW1tYXJ5IG9mIG91dGxpZXJzDQpmb3IgKGNvbCBpbiBuYW1lcyhvdXRsaWVyX2luZGljZXNfbGlzdCkpIHsNCiAgY2F0KCJWYXJpYWJsZToiLCBjb2wsICIgLSBPdXRsaWVycyBmb3VuZDoiLCBsZW5ndGgob3V0bGllcl9pbmRpY2VzX2xpc3RbW2NvbF1dKSwgIlxuIikNCn0NCmBgYA0KDQojIyBjbGVhbiBvdXRsaWVycyB1c2luZyBJUVIocmVtb3ZlZCByb3dzIHdpdGggb3V0bGllcnMpDQpgYGB7cn0NClExIDwtIHF1YW50aWxlKGRhdGEkVG90YWxBbW91bnQsIDAuMjUsIG5hLnJtID0gVFJVRSkNClEzIDwtIHF1YW50aWxlKGRhdGEkVG90YWxBbW91bnQsIDAuNzUsIG5hLnJtID0gVFJVRSkNCklRUiA8LSBRMyAtIFExDQoNCmxvd2VyX2JvdW5kIDwtIFExIC0gMS41ICogSVFSDQp1cHBlcl9ib3VuZCA8LSBRMyArIDEuNSAqIElRUg0KDQpkYXRhX2NsZWFuIDwtIGRhdGFbZGF0YSRUb3RhbEFtb3VudCA+PSBsb3dlcl9ib3VuZCAmIGRhdGEkVG90YWxBbW91bnQgPD0gdXBwZXJfYm91bmQsIF0NCg0KYGBgDQoNCiMjIGNoZWNraW5nIGlmIGRhdGEgaGFzIGJlZW4gY2xlYW5lZCB1c2luZyBJUVINCmBgYHtyfQ0KUTEgPC0gcXVhbnRpbGUoZGF0YV9jbGVhbiRUb3RhbEFtb3VudCwgMC4yNSwgbmEucm0gPSBUUlVFKQ0KUTMgPC0gcXVhbnRpbGUoZGF0YV9jbGVhbiRUb3RhbEFtb3VudCwgMC43NSwgbmEucm0gPSBUUlVFKQ0KSVFSIDwtIFEzIC0gUTENCmxvd2VyIDwtIFExIC0gMS41ICogSVFSDQp1cHBlciA8LSBRMyArIDEuNSAqIElRUg0KDQpzdW0oZGF0YV9jbGVhbiRUb3RhbEFtb3VudCA8IGxvd2VyIHwgZGF0YV9jbGVhbiRUb3RhbEFtb3VudCA+IHVwcGVyLCBuYS5ybSA9IFRSVUUpDQpgYGANCg0KIyNjbGVhbmluZyB0aGUgcmVtYWluaW5nIG91dGxpZXJzDQpgYGB7cn0NClExIDwtIHF1YW50aWxlKGRhdGFfY2xlYW4kVG90YWxBbW91bnQsIDAuMjUsIG5hLnJtID0gVFJVRSkNClEzIDwtIHF1YW50aWxlKGRhdGFfY2xlYW4kVG90YWxBbW91bnQsIDAuNzUsIG5hLnJtID0gVFJVRSkNCklRUiA8LSBRMyAtIFExDQoNCmxvd2VyX2JvdW5kIDwtIFExIC0gMS41ICogSVFSDQp1cHBlcl9ib3VuZCA8LSBRMyArIDEuNSAqIElRUg0KDQojZGF0YV9jbGVhbiA8LSBkYXRhW2RhdGFfY2xlYW4kVG90YWxBbW91bnQgPj0gbG93ZXJfYm91bmQgJiBkYXRhJFRvdGFsQW1vdW50IDw9IHVwcGVyX2JvdW5kLCBdDQojZGF0YV9jbGVhbg0KYGBgDQoNCiMjZG91YmxlIGNoZWNraW5nIGlmIHRoZSBvdXRsaWVycyBoYXZlIGJlZW4gY2xlYW5lZA0KYGBge3J9DQpRMSA8LSBxdWFudGlsZShkYXRhX2NsZWFuJFRvdGFsQW1vdW50LCAwLjI1LCBuYS5ybSA9IFRSVUUpDQpRMyA8LSBxdWFudGlsZShkYXRhX2NsZWFuJFRvdGFsQW1vdW50LCAwLjc1LCBuYS5ybSA9IFRSVUUpDQpJUVIgPC0gUTMgLSBRMQ0KbG93ZXIgPC0gUTEgLSAxLjUgKiBJUVINCnVwcGVyIDwtIFEzICsgMS41ICogSVFSDQoNCnN1bShkYXRhX2NsZWFuJFRvdGFsQW1vdW50IDwgbG93ZXIgfCBkYXRhX2NsZWFuJFRvdGFsQW1vdW50ID4gdXBwZXIsIG5hLnJtID0gVFJVRSkNCmBgYA0KDQoNCg0KIyMjIDMuMyBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIChFREEpDQrigKIJUGVyZm9ybSB1bml2YXJpYXRlIGFuZCBiaXZhcmlhdGUgYW5hbHlzaXMgdG8gdW5kZXJzdGFuZCB0aGUgZGlzdHJpYnV0aW9uIG9mIHZhcmlhYmxlcyBhbmQgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHRoZW0NCg0KYGBge3J9DQojIyBTdW1tYXJ5IFN0YXRpc3RpY3MgDQpzdW1tYXJ5KGRhdGFfY2xlYW4pDQoNCmBgYA0KDQoNCiMjIGZyZXF1ZW5jeSBvZiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMNCmBgYHtyfQ0KIyMgZnJlcXVlbmN5IG9mIGNhdGVnb3JpY2FsIHZhcmlhYmxlcw0KI2ZyZXEoZGF0YV9jbGVhbikgDQpgYGANCg0KDQojIyBib3hwbG90IG9mIEFnZQ0KYGBge3J9DQpib3hwbG90KGRhdGFfY2xlYW4kQWdlLCBtYWluPSdBZ2UnKQ0KYGBgDQojIyBIaXN0b2dyYW0gb2YgcHJpY2UNCmBgYHtyfQ0KaGlzdChkYXRhX2NsZWFuJFByaWNlLG1haW49Ikhpc3RvZ3JhbSBvZiBQcmljZSIseGxhYj0iUHJpY2UiLGNvbD0nYmx1ZScpDQpgYGANCg0KIyMgRnJlcXVlbmN5IFRhYmxlIGZvciBHZW5kZXINCg0KYGBge3J9DQp0YWJsZShkYXRhX2NsZWFuJEdlbmRlcikNCnRhYmxlKGRhdGFfY2xlYW4kUGF5bWVudE1ldGhvZCkNCnRhYmxlKGRhdGFfY2xlYW4kTWVtYmVyc2hpcFN0YXR1cykNCmBgYA0KDQojIyBCaXZhcmlhdGUocHJpY2UgdnMgdG90YWwgYW1vdW50LHByaWR1Y3QgY2F0ZWdvcnkgdnMgcHJpY2UscGF5bWVudCBtZXRob2QgdnMgbWVtZW1iZXJzaGlwIHN0YXR1cykNCg0KIyNzY2F0dGVycGxvdA0KYGBge3J9DQpnZ3Bsb3QoZGF0YV9jbGVhbixhZXMoeD1QcmljZSx5PVRvdGFsQW1vdW50KSkrDQogIGdlb21fcG9pbnQoYWxwaGE9MC42LGNvbG9yPSJibHVlIikrDQogIGxhYnModGl0bGU9IlNjYXR0ZXJwbG90IG9mIFByaWNlIHZzIFRvdGFsQW1vdW50IikNCmBgYA0KDQojIyBib3hwbG90DQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmdncGxvdChkYXRhX2NsZWFuLGFlcyh4PVByb2R1Y3RDYXRlZ29yeSwgeT1QcmljZSxmaWxsPVByb2R1Y3RDYXRlZ29yeSkpKw0KICBnZW9tX2JveHBsb3QoKSsNCiAgbGFicyh0aXRsZT0iUHJvZHVjdENhdGVnb3J5IHZzIFByaWNlIikrDQogIGdlb21fYm94cGxvdCgpDQpgYGANCg0KIyMgbGluZWdyYXBoIG9mIHByb2R1Y3QgdnMgcHJpY2UgY2F0ZWdvcnkNCmBgYHtyfQ0KZ2dwbG90KGRhdGE9ZGF0YV9jbGVhbiwgYWVzKHg9UHJvZHVjdENhdGVnb3J5LCB5PVByaWNlKSkrDQogIGdlb21fbGluZSgpDQpgYGANCg0KIyMgY29udGlnZW5jeSB0YWJsZSBvZiBwYXltZW50IG1ldGhvZCB2cyBtZW1iZXJzaGlwIHN0YXR1cw0KYGBge3J9DQpjb250aW5nZW5jeV90YWJsZTwtdGFibGUoZGF0YV9jbGVhbiRQYXltZW50TWV0aG9kLGRhdGFfY2xlYW4kTWVtYmVyc2hpcFN0YXR1cykNCnByaW50KGNvbnRpbmdlbmN5X3RhYmxlKQ0KYGBgDQoNCiMjIDMuNCBSRUdSRVNTSU9OIE1PREVMDQrigKIJQnVpbGQgYSByZWdyZXNzaW9uIG1vZGVsIHRvIHByZWRpY3QgVG90YWxBbW91bnQgYmFzZWQgb24gdmFyaWFibGVzIGxpa2UgQWdlLCBQcmljZSwgUXVhbnRpdHksIGFuZCBSYXRpbmcuDQoNCmBgYHtyfQ0KbW9kZWw8LWxtKFRvdGFsQW1vdW50fkFnZStQcmljZStSYXRpbmcrUXVhbnRpdHksZGF0YT1kYXRhX2NsZWFuKQ0Kc3VtbWFyeShtb2RlbCkNCmBgYA0KDQojIyBtb2RlbCBkaWFnbm9zdGljcw0KYGBge3J9DQpsaWJyYXJ5KGNhcikNCnZpZl92YWx1ZXM9dmlmKG1vZGVsKQ0KcHJpbnQodmlmX3ZhbHVlcykNCmBgYA0KDQojIyBjb3JlbGF0aW9uDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShjb3JycGxvdCkNCg0KY29yX21hdHJpeDwtY29yKHNlbGVjdChkYXRhX2NsZWFuLCB3aGVyZShpcy5udW1lcmljKSksdXNlPSJjb21wbGV0ZS5vYnMiKQ0KY29ycnBsb3QoY29yX21hdHJpeCwNCiAgICAgICAgIG1ldGhvZD0iY29sb3IiLA0KICAgICAgICAgdHlwZT0iZnVsbCIsDQogICAgICAgICB0bC5jb2w9ImJsYWNrIiwNCiAgICAgICAgIHRsLnNydD00NSwNCiAgICAgICAgIGFkZENvZWYuY29sPSJ3aGl0ZSIsDQogICAgICAgICBudW1iZXIuY2V4PTAuNywNCiAgICAgICAgIGRpYWc9VFJVRSkNCmBgYA0KDQojIyBjaGVja2luZyByZXNpZHVhbHMNCmBgYHtyfQ0KbGlicmFyeShmb3JlY2FzdCkNCmNoZWNrcmVzaWR1YWxzKG1vZGVsKQ0KYGBgDQoNCiMjMy41IENvcnJlbGF0aW9uIEFuYWx5c2lzDQrigKIJQW5hbHl6ZSB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBudW1lcmljYWwgdmFyaWFibGVzIChlLmcuLCBQcmljZSB2cy4gVG90YWxBbW91bnQsIEFnZSB2cy4gVG90YWxBbW91bnQpLg0KDQpIeXBvdGhlc2lzOkgwOlRoZXJlIGlzIHNpZ25pZmljYW50IHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMNCiAgICAgICAgICAgSDE6VGhlcmUgaXMgbm8gc2lnbmlmaWNhbnQgUmVsYXRpb25zaGlwIGluIGF0bGVhc3Qgb25lIG9mIHRoZSB2YXJpYWJsZXMNCmBgYHtyfQ0KIyMgIGNvcnJlbGF0aW9uIG1hdHJpeA0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShjb3JycGxvdCkNCm51bV9kYXRhX2NsZWFuPC1kYXRhX2NsZWFuWyxjKCJBZ2UiLCJQcmljZSIsIlF1YW50aXR5IiwiUmF0aW5nIiwiVG90YWxBbW91bnQiKV0NCmNvcl9tYXRyaXg8LWNvcihudW1fZGF0YV9jbGVhbix1c2U9ImNvbXBsZXRlLm9icyIpDQpjb3JfbWF0cml4DQpgYGANCg0KIyNwZWFyc29uIFJhbmsNCmBgYHtyfQ0KIyNwZWFyc29uIFJhbmsNCmxpYnJhcnkoY29ycnBsb3QpDQpudW1fZGF0YV9jbGVhbjwtZGF0YV9jbGVhblssYygiQWdlIiwiUHJpY2UiLCJRdWFudGl0eSIsIlJhdGluZyIsIlRvdGFsQW1vdW50IildDQpwZWFyc29uX21hdHJpeDwtY29yKG51bV9kYXRhX2NsZWFuLG1ldGhvZD0icGVhcnNvbiIpDQpwZWFyc29uX21hdHJpeA0KYGBgDQoNCg0KIyMgc3BlYXJtYW4NCmBgYHtyfQ0KIyMgc3BlYXJtYW4NCmxpYnJhcnkoY29ycnBsb3QpDQpudW1fZGF0YV9jbGVhbjwtZGF0YV9jbGVhblssYygiQWdlIiwiUHJpY2UiLCJRdWFudGl0eSIsIlJhdGluZyIsIlRvdGFsQW1vdW50IildDQpzcGVhcm1hbl9tYXRyaXg8LWNvcihudW1fZGF0YV9jbGVhbixtZXRob2Q9InNwZWFybWFuIikNCnNwZWFybWFuX21hdHJpeA0KYGBgDQoNCiMjIDMuNiBTdGF0aXN0aWNhbCBUZXN0cw0K4oCiCVBlcmZvcm0gYSB0LXRlc3QgdG8gY29tcGFyZSB0aGUgYXZlcmFnZSBUb3RhbEFtb3VudCBzcGVudCBieSBtYWxlIGFuZCBmZW1hbGUgY3VzdG9tZXJzLg0K4oCiCVBlcmZvcm0gYSBjaGktc3F1YXJlIHRlc3QgdG8gY2hlY2sgdGhlIGFzc29jaWF0aW9uIGJldHdlZW4gUHJvZHVjdENhdGVnb3J5IGFuZCBQYXltZW50TWV0aG9kLg0K4oCiCVBlcmZvcm0gQU5PVkEgdG8gY29tcGFyZSB0aGUgYXZlcmFnZSBUb3RhbEFtb3VudCBhY3Jvc3MgZGlmZmVyZW50IE1lbWJlcnNoaXBTdGF0dXMgbGV2ZWxzLg0KX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXw0KDQojIyDigKIJUGVyZm9ybSBhIHQtdGVzdCB0byBjb21wYXJlIHRoZSBhdmVyYWdlIFRvdGFsQW1vdW50IHNwZW50IGJ5IG1hbGUgYW5kIGZlbWFsZSBjdXN0b21lcnMuDQojIyBULVRlc3QNCmBgYHtyfQ0KIyMgSHlwb3RoZXNpczogSF8wOlRoZXJlIGlzIGEgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGJldHdlZW4gbWFsZSBhbmQgZmVtYWxlIHNwZW5kaW5nLiIpDQojIyAgICAgICAgICAgICBIXzE6VGhlcmUgaXMgbm8gc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGJldHdlZW4gbWFsZSBhbmQgZmVtYWxlIHNwZW5kaW5nLiIpIA0KbGlicmFyeShkcGx5cikNCg0KI0NoZWNraW5nIGZvciBtaXNzaW5nIHZhbHVlcyBpbiBHZW5kZXIgYW5kIFRvdGFsQW1vdW50DQpzdW0oaXMubmEoZGF0YV9jbGVhbiRHZW5kZXIpKQ0Kc3VtKGlzLm5hKGRhdGFfY2xlYW4kVG90YWxBbW91bnQpKQ0KDQojIDUuIFNlcGFyYXRlIFRvdGFsQW1vdW50IGJ5IEdlbmRlcg0KbWFsZV9zcGVuZGluZyA8LSBkYXRhX2NsZWFuICU+JSBmaWx0ZXIoR2VuZGVyID09ICJNYWxlIikgJT4lIHB1bGwoVG90YWxBbW91bnQpDQpmZW1hbGVfc3BlbmRpbmcgPC0gZGF0YV9jbGVhbiAlPiUgZmlsdGVyKEdlbmRlciA9PSAiRmVtYWxlIikgJT4lIHB1bGwoVG90YWxBbW91bnQpDQoNCiMgNi4gUGVyZm9ybSBJbmRlcGVuZGVudCBULVRlc3QgKFdlbGNoJ3MgVC1UZXN0IC0gYXNzdW1lcyB1bmVxdWFsIHZhcmlhbmNlcykNCnRfdGVzdF9yZXN1bHQgPC0gdC50ZXN0KG1hbGVfc3BlbmRpbmcsIGZlbWFsZV9zcGVuZGluZywgdmFyLmVxdWFsID0gRkFMU0UpDQoNCiMgNy4gUHJpbnQgdGhlIHJlc3VsdA0KcHJpbnQodF90ZXN0X3Jlc3VsdCkNCg0KYGBgDQpPdXRwdXQgaW50ZXJwcmV0YXRpb24gZm9yIHRfdGVzdDpwX3ZhbHVlPDAuMDU7UmVqZWN0IEgwIA0KY29uY2x1c2lvbjogdGhlcmUgaXMgbm8gc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBiZXR3ZWVuIEZlbWFsZSBhbmQgbWFsZSBzcGVuZGluZw0KDQoNCiMj4oCiCVBlcmZvcm0gYSBjaGktc3F1YXJlIHRlc3QgdG8gY2hlY2sgdGhlIGFzc29jaWF0aW9uIGJldHdlZW4gUHJvZHVjdENhdGVnb3J5IGFuZCBQYXltZW50TWV0aG9kLg0KIyMgY2hpIHNxdWFyZSBUZXN0DQpgYGB7cn0NCiMjIEh5cG90aGVzaXM6SOKCgDogVGhlcmUgaXMgYW4gYXNzb2NpYXRpb24gYmV0d2VlbiBQcm9kdWN0IENhdGVnb3J5IGFuZCBQYXltZW50IE1ldGhvZA0KIyMgICAgICAgICAgICBIMTpUaGVyZSBpcyBubyBhc3NvY2lhdGlvbiBiZXR3ZWVuIFByb2R1Y3QgQ2F0ZWdvcnkgYW5kIFBheW1lbnQgTWV0aG9kDQpjb250aW5nZW5jeV90YWJsZSA8LSB0YWJsZShkYXRhX2NsZWFuJFByb2R1Y3RDYXRlZ29yeSwgZGF0YV9jbGVhbiRQYXltZW50TWV0aG9kKQ0KY2hpX3NxdWFyZV9yZXN1bHQgPC0gY2hpc3EudGVzdChjb250aW5nZW5jeV90YWJsZSkNCnByaW50KGNoaV9zcXVhcmVfcmVzdWx0KQ0KYGBgDQpjb25jbHVzaW9uOiBQLXZhbHVlID4wLjA1OiBEbyBub3QgUmVqZWN0IHRoZSBudWxsaHlwb3RoZXNpczsgVGhlcmUgaXMgYW4gYXNzb2NpYXRpb24gYmV0d2VlbiBwYXltZW50IG1ldGhvZCBhbmQgcHJvZHVjdCBjYXRlZ29yeS4NCg0KDQojI+KAoglQZXJmb3JtIEFOT1ZBIHRvIGNvbXBhcmUgdGhlIGF2ZXJhZ2UgVG90YWxBbW91bnQgYWNyb3NzIGRpZmZlcmVudCBNZW1iZXJzaGlwU3RhdHVzIGxldmVscy4NCg0KYGBge3J9DQojI0h5cG90aGVzaXM7SDA6dGhlcmUgaXMgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBiZXR3ZWVuIGF2ZXJhZ2UgVG90YWwgYW1vdW50IGFjY3Jvc3MgZGlmZmVyZW50IG1lbWJlcnNoaXAgbGV2ZWxzDQojIyAgICAgICAgICBIMTp0aGVyZSBpcyBubyBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGJldHdlZW4gYXZlcmFnZSBUb3RhbCBhbW91bnQgYWNjcm9zcyBkaWZmZXJlbnQgbWVtYmVyc2hpcCBsZXZlbHMNCiMgUGVyZm9ybSBvbmUtd2F5IEFOT1ZBDQphbm92YV9yZXN1bHQgPC0gYW92KFRvdGFsQW1vdW50IH4gTWVtYmVyc2hpcFN0YXR1cywgZGF0YSA9IGRhdGFfY2xlYW4pDQpzdW1tYXJ5KGFub3ZhX3Jlc3VsdCkNCmBgYA0KT3V0cHV0IGludGVycHJldGF0aW9uOlBfdmFsdWU8MC4wNSxEbyBub3QgcmVqZWN0IG51bGwgaHlwb3RoZXNpcw0KQ29uY2x1c2lvbjp0aGVyZSBpcyBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGJldHdlZW4gYXZlcmFnZSBUb3RhbCBBbW91bnQgYWNyb3NzIGRpZmZlcmVudCBtZW1iZXJzaGlwIGxldmVscw0KIyMgNC4gRGF0YSBWaXN1YWxpemF0aW9uDQo0LjEgSGlzdG9ncmFtDQrigKIJVmlzdWFsaXplIHRoZSBkaXN0cmlidXRpb24gb2YgbnVtZXJpY2FsIHZhcmlhYmxlcyBsaWtlIEFnZSwgUHJpY2UsIGFuZCBUb3RhbEFtb3VudC4NCg0KDQo0LjQgRG9udXQgQ2hhcnQNCuKAoglEaXNwbGF5IHRoZSBkaXN0cmlidXRpb24gb2YgUGF5bWVudE1ldGhvZCB1c2VkIGJ5IGN1c3RvbWVycy4NCjQuNSBTY2F0dGVyIFBsb3QNCuKAoglWaXN1YWxpemUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIFByaWNlIGFuZCBUb3RhbEFtb3VudC4NCjQuNiBCb3ggUGxvdA0K4oCiCUlkZW50aWZ5IG91dGxpZXJzIGluIFRvdGFsQW1vdW50IG9yIFByaWNlLg0KX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXw0KDQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygxLCAzKSkNCg0KIyBIaXN0b2dyYW1zDQpoaXN0KGRhdGFfY2xlYW4kQWdlLCBjb2wgPSAibGlnaHRibHVlIiwgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgQWdlIiwgeGxhYiA9ICJBZ2UiKQ0KaGlzdChkYXRhX2NsZWFuJFByaWNlLCBjb2wgPSAibGlnaHRncmVlbiIsIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIFByaWNlIiwgeGxhYiA9ICJQcmljZSIpDQpoaXN0KGRhdGFfY2xlYW4kVG90YWxBbW91bnQsIGNvbCA9ICJzYWxtb24iLCBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBUb3RhbEFtb3VudCIsIHhsYWIgPSAiVG90YWwgQW1vdW50IikNCmhpc3QoZGF0YV9jbGVhbiRSYXRpbmcsIGNvbCA9ICJyZWQiLCBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBSYXRpbmciLCB4bGFiID0gIlJhdGluZyIpDQpgYGANCg0KIyM0LjIgUGllIENoYXJ0DQrigKIJU2hvdyB0aGUgcHJvcG9ydGlvbiBvZiBjdXN0b21lcnMgYnkgR2VuZGVyIG9yIE1lbWJlcnNoaXBTdGF0dXMuDQpgYGB7cn0NCiMgUGllIGNoYXJ0IGZvciBNZW1iZXJzaGlwIFN0YXR1cw0KbWVtYmVyc2hpcF9jb3VudHMgPC0gdGFibGUoZGF0YV9jbGVhbiRNZW1iZXJzaGlwU3RhdHVzKQ0KcGllKG1lbWJlcnNoaXBfY291bnRzLCBtYWluID0gIk1lbWJlcnNoaXAgU3RhdHVzIERpc3RyaWJ1dGlvbiIsIGNvbCA9IHJhaW5ib3cobGVuZ3RoKG1lbWJlcnNoaXBfY291bnRzKSkpDQpgYGANCg0KIyBQaWUgY2hhcnQgZm9yIEdlbmRlcg0KYGBge3J9DQojIFBpZSBjaGFydCBmb3IgR2VuZGVyDQpnZW5kZXJfY291bnRzIDwtIHRhYmxlKGRhdGFfY2xlYW4kR2VuZGVyKQ0KcGllKGdlbmRlcl9jb3VudHMsIG1haW4gPSAiQ3VzdG9tZXIgR2VuZGVyIERpc3RyaWJ1dGlvbiIsIGNvbCA9IGMoImxpZ2h0Ymx1ZSIsICJwaW5rIikpDQpgYGANCg0KDQojIyA0LjMgQmFyIENoYXJ0DQrigKIJQ29tcGFyZSB0aGUgYXZlcmFnZSBUb3RhbEFtb3VudCBzcGVudCBieSBjdXN0b21lcnMgaW4gZGlmZmVyZW50IExvY2F0aW9uIG9yIFByb2R1Y3RDYXRlZ29yeS4NCmBgYHtyfQ0KIyMgVG90YWxBbW91bnQgYnkgUHJvZHVjdENhdGVnb3J5DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KIyBDYWxjdWxhdGUgYXZlcmFnZSB0b3RhbCBhbW91bnQgYnkgcHJvZHVjdCBjYXRlZ29yeQ0KYXZnX2J5X2NhdGVnb3J5IDwtIGRhdGEgJT4lDQogIGdyb3VwX2J5KFByb2R1Y3RDYXRlZ29yeSkgJT4lDQogIHN1bW1hcmlzZShBdmdUb3RhbEFtb3VudCA9IG1lYW4oVG90YWxBbW91bnQsIG5hLnJtID0gVFJVRSkpDQpnZ3Bsb3QoYXZnX2J5X2NhdGVnb3J5LCBhZXMoeCA9IHJlb3JkZXIoUHJvZHVjdENhdGVnb3J5LCBBdmdUb3RhbEFtb3VudCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBBdmdUb3RhbEFtb3VudCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFByb2R1Y3RDYXRlZ29yeSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgbGFicyh0aXRsZSA9ICJBdmVyYWdlIFRvdGFsIEFtb3VudCBTcGVudCBieSBQcm9kdWN0IENhdGVnb3J5IiwgDQogICAgICAgeCA9ICJQcm9kdWN0IENhdGVnb3J5IiwgDQogICAgICAgeSA9ICJBdmVyYWdlIFRvdGFsIEFtb3VudCIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKw0KICBndWlkZXMoZmlsbCA9IEZBTFNFKQ0KYGBgDQoNCiMjIFRvdGFsQW1vdW50IGJ5IExvY2F0aW9uIGJhciBjaGFydA0KYGBge3J9DQojIyBUb3RhbEFtb3VudCBieSBMb2NhdGlvbiBiYXIgY2hhcnQNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZHBseXIpDQojIENhbGN1bGF0ZSBhdmVyYWdlIHRvdGFsIGFtb3VudCBieSBMb2NhdGlvbg0KYXZnX2J5X0xvY2F0aW9uPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnkoTG9jYXRpb24pICU+JQ0KICBzdW1tYXJpc2UoQXZnVG90YWxBbW91bnQgPSBtZWFuKFRvdGFsQW1vdW50LCBuYS5ybSA9IFRSVUUpKQ0KZ2dwbG90KGF2Z19ieV9Mb2NhdGlvbiwgYWVzKHggPSByZW9yZGVyKExvY2F0aW9uLCBBdmdUb3RhbEFtb3VudCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBBdmdUb3RhbEFtb3VudCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IExvY2F0aW9uKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICBsYWJzKHRpdGxlID0gIkF2ZXJhZ2UgVG90YWwgQW1vdW50IFNwZW50IGJ5IExvY2F0aW9uIiwgDQogICAgICAgeCA9ICJMb2NhdGlvbiIsIA0KICAgICAgIHkgPSAiQXZlcmFnZSBUb3RhbCBBbW91bnQiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsNCiAgZ3VpZGVzKGZpbGwgPSBGQUxTRSkNCmBgYA0KDQojIyBEb251dCBQTE9UDQpgYGB7cn0NCiMgTG9hZCBsaWJyYXJpZXMNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZ2dmb3JjZSkNCmxpYnJhcnkoZHBseXIpDQoNCg0KIyBDb3VudCBwYXltZW50IG1ldGhvZCBmcmVxdWVuY2llcw0KcGF5bWVudF9jb3VudHMgPC0gZGF0YSAlPiUNCiAgY291bnQoUGF5bWVudE1ldGhvZCwgbmFtZSA9ICJDb3VudCIpICU+JQ0KICBtdXRhdGUoDQogICAgUGVyY2VudCA9IHJvdW5kKENvdW50IC8gc3VtKENvdW50KSAqIDEwMCwgMSksDQogICAgQ3VtdWxhdGl2ZSA9IGN1bXN1bShDb3VudCksDQogICAgc3RhcnQgPSBsYWcoQ3VtdWxhdGl2ZSwgZGVmYXVsdCA9IDApLA0KICAgIGVuZCA9IEN1bXVsYXRpdmUsDQogICAgbGFiZWxfcG9zaXRpb24gPSAoc3RhcnQgKyBlbmQpIC8gMg0KICApDQoNCiMgQ29udmVydCB0byByYWRpYW5zDQpwYXltZW50X2NvdW50cyRyYWRpYW5zX3N0YXJ0IDwtIHBheW1lbnRfY291bnRzJHN0YXJ0ICogMiAqIHBpIC8gc3VtKHBheW1lbnRfY291bnRzJENvdW50KQ0KcGF5bWVudF9jb3VudHMkcmFkaWFuc19lbmQgPC0gcGF5bWVudF9jb3VudHMkZW5kICogMiAqIHBpIC8gc3VtKHBheW1lbnRfY291bnRzJENvdW50KSMgUGxvdA0KDQoNCmdncGxvdChwYXltZW50X2NvdW50cykgKw0KICBnZW9tX2FyY19iYXIoDQogICAgYWVzKA0KICAgICAgeDAgPSAwLCB5MCA9IDAsIHIwID0gMC41LCANCiAgICAgIHIgPSAxLA0KICAgICAgc3RhcnQgPSByYWRpYW5zX3N0YXJ0LA0KICAgICAgZW5kID0gcmFkaWFuc19lbmQsDQogICAgICBmaWxsID0gUGF5bWVudE1ldGhvZA0KICAgICkNCiAgKSArDQogIGNvb3JkX3BvbGFyKHRoZXRhID0gInkiKSArDQogIHhsaW0oYygtMS4xLCAxLjEpKSArDQogIHlsaW0oYygwLCAxLjEpKSArDQogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiU2V0MyIpICsNCiAgdGhlbWVfdm9pZCgpICsNCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgUGF5bWVudCBNZXRob2RzIFVzZWQgYnkgQ3VzdG9tZXJzIiwgZmlsbCA9ICJQYXltZW50IE1ldGhvZCIpICsNCiAgYW5ub3RhdGUoInRleHQiLA0KICAgICAgICAgICB4ID0gMCwNCiAgICAgICAgICAgeSA9IHNlcSgwLjYsIDEuMDUsIGxlbmd0aC5vdXQgPSBucm93KHBheW1lbnRfY291bnRzKSksDQogICAgICAgICAgIGxhYmVsID0gcGFzdGUwKHBheW1lbnRfY291bnRzJFBlcmNlbnQsICIlIiksDQogICAgICAgICAgIHNpemUgPSA0LA0KICAgICAgICAgICBjb2xvciA9ICJibGFjayIpDQpgYGANCg0KIyMgc2NhdHRlcnBsb3QNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBkYXRhLCBhZXMoeCA9IFByaWNlLCB5ID0gVG90YWxBbW91bnQsIGNvbG9yID0gYXMuZmFjdG9yKFF1YW50aXR5KSkpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlByaWNlIHZzIFRvdGFsQW1vdW50IChDb2xvcmVkIGJ5IFF1YW50aXR5KSIsDQogICAgeCA9ICJQcmljZSBwZXIgVW5pdCIsDQogICAgeSA9ICJUb3RhbCBBbW91bnQgU3BlbnQiLA0KICAgIGNvbG9yID0gIlF1YW50aXR5Ig0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KIyMgQm94cGxvdA0KYGBge3J9DQojIyBUb3RhbEFtb3VudA0KZ2dwbG90KGRhdGEsIGFlcyh5ID0gVG90YWxBbW91bnQpKSArDQogIGdlb21fYm94cGxvdChmaWxsID0gImxpZ2h0Ymx1ZSIsIGNvbG9yID0gImJsYWNrIikgKw0KICBsYWJzKHRpdGxlID0gIkJveCBQbG90IG9mIFRvdGFsQW1vdW50IiwgeSA9ICJUb3RhbCBBbW91bnQgU3BlbnQiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCiMjIFByaWNlIGJveHBsb3QNCmBgYHtyfQ0KIyNQcmljZQ0KZ2dwbG90KGRhdGEsIGFlcyh5ID0gUHJpY2UpKSArDQogIGdlb21fYm94cGxvdChmaWxsID0gImxpZ2h0Ymx1ZSIsIGNvbG9yID0gImJsYWNrIikgKw0KICBsYWJzKHRpdGxlID0gIkJveCBQbG90IG9mIFByaWNlIiwgeSA9ICJQcmljZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3J9DQoNCmBgYA0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=