1 Introduction

This dataset is titled “Student Habits vs Academic Performance” and shows various explanatory variables and one target/response variable (exam_score). The purpose of collecting this dataset is to see what factors may affect exam scores. This dataset was collected from Kaggle. This dataset contains 1000 observations and 16 variables (15 feature variables and 1 target variable).

The student_id serves as the identifier. The age (numerical) shows each persons age. The gender (categorical) shows each persons gender. The study_hours_per_day shows hours studied per day. The social_media_hours shows hours used daily on social media. The netflix_hours shows hours used daily on netflix. The part_time_job (no/yes) shows if the person has a part time job. The attendance percentage shows the percentage of classes attended. The sleep_hours shows amount of sleep per night. The diet_quality (poor/fair/good) shows quality of ones diet. The exercise_frequency shows how many times one exercises per week. The parental_education_level (none/High School/bachelor/master) shows the highest level of education of ones parents. The internet_quality(poor/avg/good) shows how good ones wi-fi is. The mental_health_rating (1-10) shows how one rates their mental health. The extracurricular_participation (n/y) shows if one participates in extracurricular activities. The target/response variable is exam_score (continuous).

Based on this dataset, we can formulate a research question. The question we can form is what variables are the most important in predicting exam score. For regression, we can use exam_score (continuous) as our response variable. For classification, we can create a binary response variable with 1 (pass) and 0 (fail). We can use the condition if exam score is greater or less than 0.60.

We will use CART, bagging, and random forests to try to answer the research question.

2 Read in Dataset

Read in dataset.

habits <- read.csv("student_habits_performance.csv")

See dataset structure.

str(habits)
'data.frame':   1000 obs. of  16 variables:
 $ student_id                   : chr  "S1000" "S1001" "S1002" "S1003" ...
 $ age                          : int  23 20 21 23 19 24 21 21 23 18 ...
 $ gender                       : chr  "Female" "Female" "Male" "Female" ...
 $ study_hours_per_day          : num  0 6.9 1.4 1 5 7.2 5.6 4.3 4.4 4.8 ...
 $ social_media_hours           : num  1.2 2.8 3.1 3.9 4.4 1.3 1.5 1 2.2 3.1 ...
 $ netflix_hours                : num  1.1 2.3 1.3 1 0.5 0 1.4 2 1.7 1.3 ...
 $ part_time_job                : chr  "No" "No" "No" "No" ...
 $ attendance_percentage        : num  85 97.3 94.8 71 90.9 82.9 85.8 77.7 100 95.4 ...
 $ sleep_hours                  : num  8 4.6 8 9.2 4.9 7.4 6.5 4.6 7.1 7.5 ...
 $ diet_quality                 : chr  "Fair" "Good" "Poor" "Poor" ...
 $ exercise_frequency           : int  6 6 1 4 3 1 2 0 3 5 ...
 $ parental_education_level     : chr  "Master" "High School" "High School" "Master" ...
 $ internet_quality             : chr  "Average" "Average" "Poor" "Good" ...
 $ mental_health_rating         : int  8 8 1 1 1 4 4 8 1 10 ...
 $ extracurricular_participation: chr  "Yes" "No" "No" "Yes" ...
 $ exam_score                   : num  56.2 100 34.3 26.8 66.4 100 89.8 72.6 78.9 100 ...

Check for Missing Values.

colSums(is.na(habits))
                   student_id                           age 
                            0                             0 
                       gender           study_hours_per_day 
                            0                             0 
           social_media_hours                 netflix_hours 
                            0                             0 
                part_time_job         attendance_percentage 
                            0                             0 
                  sleep_hours                  diet_quality 
                            0                             0 
           exercise_frequency      parental_education_level 
                            0                             0 
             internet_quality          mental_health_rating 
                            0                             0 
extracurricular_participation                    exam_score 
                            0                             0 

3 EDA

This section will show the distribution of each individual feature.

The below figure shows the distribution of the gender variable.

ggplot(habits, aes(x = gender)) + 
  
  geom_bar() +
  labs(title = "Gender")

The below figure shows the distribution of the part_time_job variable.

ggplot(habits, aes(x = part_time_job)) + 
  
  geom_bar() +
  labs(title = "part_time_job")

The below figure shows the distribution of the diet_quality variable.

ggplot(habits, aes(x = diet_quality)) + 
  
  geom_bar() +
  labs(title = "diet_quality")

The below figure shows the distribution of the parental_education_level variable.

ggplot(habits, aes(x = parental_education_level)) + 
  
  geom_bar() +
  labs(title = "parental_education_level")

The below figure shows the distribution of the internet_quality variable.

ggplot(habits, aes(x = internet_quality)) + 
  
  geom_bar() +
  labs(title = "internet_quality")

The below figure shows the distribution of the extracurricular_participation variable.

ggplot(habits, aes(x = extracurricular_participation)) + 
  
  geom_bar() +
  labs(title = "extracurricular_participation")

The below figure shows the distribution of the age variable.

ggplot(data = habits, aes(x = age)) + 
  geom_boxplot() + 
  
  labs(title = "age")

The below figure shows the distribution of the study_hours_per_day variable.

ggplot(data = habits, aes(x = study_hours_per_day)) + 
  geom_boxplot() + 
  
  labs(title = "study_hours_per_day")

The below figure shows the distribution of the social_media_hours variable.

ggplot(data = habits, aes(x = social_media_hours)) + 
  geom_boxplot() + 
  
  labs(title = "social_media_hours")

The below figure shows the distribution of the netflix_hours variable.

ggplot(data = habits, aes(x = netflix_hours)) + 
  geom_boxplot() + 
  
  labs(title = "netflix_hours")

The below figure shows the distribution of the attendance_percentage variable.

ggplot(data = habits, aes(x = attendance_percentage)) + 
  geom_boxplot() + 
  
  labs(title = "attendance_percentage")

The below figure shows the distribution of the sleep_hours variable.

ggplot(data = habits, aes(x = sleep_hours)) + 
  geom_boxplot() + 
  
  labs(title = "sleep_hours")

The below figure shows the distribution of the exercise_frequency variable.

ggplot(data = habits, aes(x = exercise_frequency)) + 
  geom_boxplot() + 
  
  labs(title = "exercise_frequency")

The below figure shows the distribution of the mental_health_rating variable.

ggplot(data = habits, aes(x = mental_health_rating)) + 
  geom_boxplot() + 
  
  labs(title = "mental_health_rating")

4 Imputation/Feature Engineering

The variables part_time_job, diet_quality, internet_quality, and extracurricular_participation are converted to categorical/factor.

#habits$gender <- as.factor(habits$gender)
habits$part_time_job <- as.factor(habits$part_time_job)
habits$diet_quality <- factor(habits$diet_quality,levels = c("Poor", "Fair", "Good"))
#habits$parental_education_level <- as.factor(habits$parental_education_level)
habits$internet_quality <- factor(habits$internet_quality, levels = c("Poor", "Average", "Good"))
habits$extracurricular_participation <- as.factor(habits$extracurricular_participation)

The variable gender contains the value ‘Other’. Since we only want to work with Male/female, we set ‘Other’ to missing and use MICE imputation.

habits$gender[habits$gender== "Other"] = NA
habits$gender <- as.factor(habits$gender)

init2 <- mice(habits, maxit = 0)
init2$method
                   student_id                           age 
                           ""                            "" 
                       gender           study_hours_per_day 
                     "logreg"                            "" 
           social_media_hours                 netflix_hours 
                           ""                            "" 
                part_time_job         attendance_percentage 
                           ""                            "" 
                  sleep_hours                  diet_quality 
                           ""                            "" 
           exercise_frequency      parental_education_level 
                           ""                            "" 
             internet_quality          mental_health_rating 
                           ""                            "" 
extracurricular_participation                    exam_score 
                           ""                            "" 
imp2 <- mice(habits, method = c("","", "logreg", "",  "", "", "", "", "", "", "", "", "", "", "", ""), 
                 maxit = 10,  
                 m = 5, 
                 seed=123,
                 print=F)     



complete_habits_data <- complete(imp2)

For parental_education_level, we convert both Bachelor and Master to College to reduce categories.

complete_habits_data$parental_education_level[complete_habits_data$parental_education_level == 'Bachelor'] <- 'College'
complete_habits_data$parental_education_level[complete_habits_data$parental_education_level == 'Master'] <- 'College'

complete_habits_data$parental_education_level <- factor(complete_habits_data$parental_education_level,levels = c("None", "High School", "College"))

str(complete_habits_data)
'data.frame':   1000 obs. of  16 variables:
 $ student_id                   : chr  "S1000" "S1001" "S1002" "S1003" ...
 $ age                          : int  23 20 21 23 19 24 21 21 23 18 ...
 $ gender                       : Factor w/ 2 levels "Female","Male": 1 1 2 1 1 2 1 1 1 1 ...
 $ study_hours_per_day          : num  0 6.9 1.4 1 5 7.2 5.6 4.3 4.4 4.8 ...
 $ social_media_hours           : num  1.2 2.8 3.1 3.9 4.4 1.3 1.5 1 2.2 3.1 ...
 $ netflix_hours                : num  1.1 2.3 1.3 1 0.5 0 1.4 2 1.7 1.3 ...
 $ part_time_job                : Factor w/ 2 levels "No","Yes": 1 1 1 1 1 1 2 2 1 1 ...
 $ attendance_percentage        : num  85 97.3 94.8 71 90.9 82.9 85.8 77.7 100 95.4 ...
 $ sleep_hours                  : num  8 4.6 8 9.2 4.9 7.4 6.5 4.6 7.1 7.5 ...
 $ diet_quality                 : Factor w/ 3 levels "Poor","Fair",..: 2 3 1 1 2 2 3 2 3 3 ...
 $ exercise_frequency           : int  6 6 1 4 3 1 2 0 3 5 ...
 $ parental_education_level     : Factor w/ 3 levels "None","High School",..: 3 2 2 3 3 3 3 3 3 3 ...
 $ internet_quality             : Factor w/ 3 levels "Poor","Average",..: 2 2 1 3 3 2 1 2 3 3 ...
 $ mental_health_rating         : int  8 8 1 1 1 4 4 8 1 10 ...
 $ extracurricular_participation: Factor w/ 2 levels "No","Yes": 2 1 1 2 1 1 1 1 1 2 ...
 $ exam_score                   : num  56.2 100 34.3 26.8 66.4 100 89.8 72.6 78.9 100 ...

5 CART - Regression

Decision trees recursively splits the data into subsets based on the value of a feature, with the goal of creating subsets that are as homogeneous as possible with respect to the target variable.

In regression trees, we can predict continuous outcomes by choosing splits to minimize the MSE of the target variable.

As shown below, we first build the initial regression tree to get a general sense of all the predictors and their splits.

# Set seed for reproducibility
set.seed(123)

# Split data into training (70%) and test (30%) sets
train.index3 <- sample(1:nrow(complete_habits_data), size = 0.8 * nrow(complete_habits_data))
train.data3 <- complete_habits_data[train.index3, ]
test.data3 <- complete_habits_data[-train.index3, ]

# 1. Tree Induction & 2. Splitting Criteria
# Build the initial regression tree using rpart
tree.model <- rpart(exam_score ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, 
                    data = train.data3,
                    method = "anova",     # For regression
                    control = rpart.control(
                      minsplit = 20,    # 3. Stopping rule: min observations to split
                      minbucket = 7,    # Min observations in terminal node
                      cp = seq(0, 0.05, 20), # Complexity parameter
                      maxdepth = 5      # Maximum tree depth
                    ))

# Visualize the unpruned tree
rpart.plot(tree.model, main = "Initial Regression Tree")

Next, we would want to prune the initial tree. Below shows a table of all the CP, nsplit, rel error, xerror, and xstd values.

#summary(tree.model)
pander(tree.model$cptable)
CP nsplit rel error xerror xstd
0.4477 0 1 1.003 0.04526
0.09511 1 0.5523 0.5551 0.02378
0.07915 2 0.4572 0.4788 0.02219
0.03897 3 0.378 0.4081 0.01805
0.02907 4 0.339 0.3724 0.01721
0.01974 5 0.31 0.3448 0.01638
0.01766 6 0.2902 0.3279 0.01603
0.01426 7 0.2726 0.3168 0.01575
0.009995 8 0.2583 0.3053 0.01527
0.009898 9 0.2483 0.3007 0.01514
0.008774 10 0.2384 0.3008 0.01528
0.006461 11 0.2297 0.2942 0.01484
0.005783 12 0.2232 0.2837 0.01412
0.005088 13 0.2174 0.2837 0.0143
0.004425 14 0.2123 0.28 0.01407
0.004335 15 0.2079 0.2805 0.01415
0.004165 16 0.2036 0.2808 0.01422
0.004046 17 0.1994 0.281 0.01422
0.003626 18 0.1953 0.2794 0.01406
0.003483 19 0.1917 0.2774 0.01395
0.002993 20 0.1882 0.2764 0.01388
0.002847 21 0.1852 0.2762 0.01382
0.002724 22 0.1824 0.2756 0.01371
0.002333 23 0.1797 0.2716 0.01328
0.0009291 24 0.1773 0.2705 0.01297
0.0006958 25 0.1764 0.2723 0.01294
6.369e-05 27 0.175 0.2724 0.01295
0 28 0.175 0.2723 0.01294

Below shows a plot of how cp changes as tree size increases. This can help with selecting cp values for pruning.

plotcp(tree.model)

For pruning, we select both best CP and min CP. Best CP is the largest CP when the xerror is within 1 standard error of the minimum. The min CP is simply the smallest CP. Best CP is preferred because it can give us the simplest tree as well as retaining accuracy.

cp.table <- tree.model$cptable

## Identify the minimum `xerror` and its `cp`.
min.xerror <- min(cp.table[, "xerror"])
min.cp.row <- which.min(cp.table[, "xerror"])
min.cp <- cp.table[min.cp.row, "CP"]

## Get the standard error (`xstd`) of the minimum `xerror`
xerror.std <- cp.table[min.cp.row, "xstd"]
threshold <- min.xerror + xerror.std  # Upper bound (1 SE rule)

## Find the simplest tree (`cp`) Where `xerror less than or equal to Threshold`.
best.cp.row <- which(cp.table[, "xerror"] <= threshold)[1]  # First row meeting criteria
best.cp <- cp.table[best.cp.row, "CP"]

## Two different trees: best CP vs minimum CP
pruned.tree.best.cp <- prune(tree.model, cp = best.cp)
pruned.tree.min.cp <- prune(tree.model, cp = min.cp)

# Visualize the pruned tree: best CP
rpart.plot(pruned.tree.best.cp, main = paste("Pruned Tree (Best CP): cp = ", round(best.cp,4)))

The above shows the tree with the best CP of 0.0044.

The below shows the tree with the min CP of 0.0009.

rpart.plot(pruned.tree.min.cp, main = paste("Pruned Tree (Minimum CP): cp = ", round(min.cp,4)))

Below we make predictions on test data using the best CP tree, min CP tree, OLS regression using features from the best CP tree (study_hours_per_day, mental_health_rating, netflix_hours, exercise_frequency, sleep_hours), and OLS regression using stepwise selection.

As shown in the output below, it seems like the OLS stepwise did the best with MSE of 29.19.

# 5. Prediction
# Make predictions on test data
pred.best.cp <- predict(pruned.tree.best.cp, newdata = test.data3)
pred.min.cp <- predict(pruned.tree.min.cp, newdata = test.data3)


# Evaluate model performance: best.cp
mse.tree.best.cp <- mean((test.data3$exam_score - pred.best.cp)^2)
rmse.tree.best.cp <- sqrt(mse.tree.best.cp)
r.squared.tree.best.cp <- cor(test.data3$exam_score, pred.best.cp)^2
# min.cp
mse.tree.min.cp <- mean((test.data3$exam_score - pred.min.cp)^2)
rmse.tree.min.cp <- sqrt(mse.tree.min.cp)
r.squared.tree.min.cp <- cor(test.data3$exam_score, pred.min.cp)^2

##
# fit ordinary least square regression 
LSE01 <- lm(exam_score ~ study_hours_per_day + mental_health_rating + netflix_hours + exercise_frequency + sleep_hours, data = train.data3)
pred.lse01 <-  predict(LSE01, newdata = test.data3)
mse.lse01 <- mean((test.data3$exam_score - pred.lse01)^2)
rmse.lse01 <- sqrt(mse.lse01)
r.squared.lse01 <- cor(test.data3$exam_score, pred.lse01)^2

##
## ordinary LSE regression model with step-wise variable selection
lse02.fit <- lm(exam_score ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, data = train.data3)
AIC.fit <- stepAIC(lse02.fit, direction="both", trace = FALSE)
pred.lse02 <- predict(AIC.fit, test.data3)
mse.lse02 <- mean((test.data3$exam_score - pred.lse02)^2)    # mean square error
rmse.lse02 <- sqrt(mse.lse02)                       # root mean square error
r.squared.lse02 <- (cor(test.data3$exam_score, pred.lse02))^2 # r-squared

###
Errors <- cbind(MSE = c(mse.tree.best.cp, mse.tree.min.cp, mse.lse01, mse.lse02),
                RMSE = c(rmse.tree.best.cp, rmse.tree.min.cp, rmse.lse01, rmse.lse02),
                r.squared = c(r.squared.tree.best.cp, r.squared.tree.min.cp, r.squared.lse01, r.squared.lse02))
rownames(Errors) = c("tree.best.cp", "tree.min.cp", "lse01", "lse02")
pander(Errors)
  MSE RMSE r.squared
tree.best.cp 82.23 9.068 0.7488
tree.min.cp 80.59 8.977 0.7521
lse01 45.05 6.712 0.8654
lse02 29.19 5.402 0.9143

Below we show what variables are most important in predicting exam score based on the best CP tree. Based on the below chart, we see that study hours, mental health, netflix hours, exercise frequency, and sleep hours are the most important variables in predicting exam score.

# Variable importance
importance <- pruned.tree.best.cp$variable.importance
barplot(sort(importance, decreasing = TRUE), 
        main = "Variable Importance: Best CP",
        las = 2)

Below we show what variables are most important in predicting exam score based on the min CP tree. Based on the below chart, we see that study hours, mental health, netflix hours, social media hours, and exercise freq are the most important variables in predicting exam score.

# Variable importance
importance2 <- pruned.tree.min.cp$variable.importance
barplot(sort(importance2, decreasing = TRUE), 
        main = "Variable Importance: Minimum CP",
        las = 2)

6 CART - Classification

In this section, we do classification trees. We can use them to predict categorical outcomes by using Gini impurity and entropy.

First, we need to create a binary response variable since our original response is continuous. Since exam scores over 60 is generally considered passing, we create ‘pass’ where 1 is pass and 0 is fail.

Then we create the initial tree, as shown below.

complete_habits_data <- transform(complete_habits_data, pass=ifelse(exam_score >= 60, 1, 0))

set.seed(123)
train.index4 <- createDataPartition(complete_habits_data$pass, p = 0.8, list = FALSE)
train.data4 <- complete_habits_data[train.index4, ]
test.data4 <- complete_habits_data[-train.index4, ]

train.data4$pass <- factor(train.data4$pass)
test.data4$pass <- factor(test.data4$pass)

# Build the initial classification tree
tree.model2 <- rpart(pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, 
                    data = train.data4,
                    method = "class",   # classification tree
                    parms = list(split = "gini",  # Using Gini index
                                 # FN cost = 1, FP cost = 0.5
                                 loss = matrix(c(0, 0.5, 1, 0), nrow = 2)  
                                 ),
                    control = rpart.control(minsplit = 15,  # Min 15 obs to split
                                           minbucket = 5,   # Min 7 obs in leaf
                                           # Complexity parameter
                                           cp = 0.001, # complex parameter
                                           maxdepth = 5))   # Max tree depth


rpart.plot(tree.model2, 
           extra = 104, # check the help document for more information
           # color palette is a sequential color scheme that blends green (G) to blue (Bu)
           box.palette = "GnBu",  
           branch.lty = 1, 
           shadow.col = "gray", 
           nn = TRUE)

Below shows the cp table to help with the pruning process.

pander(tree.model2$cptable)
CP nsplit rel error xerror xstd
0.5982 0 1 0.5 0.02835
0.04353 1 0.4018 0.6049 0.04515
0.01786 3 0.3147 0.3103 0.03132
0.01562 7 0.2433 0.3839 0.03579
0.01004 8 0.2277 0.3683 0.03592
0.002976 10 0.2076 0.3728 0.03628
0.002232 13 0.1987 0.3772 0.03608
0.001 15 0.1942 0.3884 0.03655

Below shows the cp plot.

plotcp(tree.model2)

Below, we get the best CP and the min CP values.

cp.table2 <- tree.model2$cptable
#min.cp2 <- tree.model2$cptable[which.min(tree.model2$cptable[,"xerror"]),"CP"]
## Identify the minimum `xerror` and its `cp`.
min.xerror2 <- min(cp.table2[, "xerror"])
min.cp.row2 <- which.min(cp.table2[, "xerror"])
min.cp2 <- cp.table2[min.cp.row2, "CP"]

## Get the standard error (`xstd`) of the minimum `xerror`
xerror.std2 <- cp.table2[min.cp.row2, "xstd"]
threshold2 <- min.xerror2 + xerror.std2  # Upper bound (1 SE rule)

## Find the simplest tree (`cp`) Where `xerror less than or equal to Threshold`.
best.cp.row2 <- which(cp.table2[, "xerror"] <= threshold2)[1]  # First row meeting criteria
best.cp2 <- cp.table2[best.cp.row2, "CP"]

## Two different trees: best CP vs minimum CP
pruned.tree.1SE <- prune(tree.model2, cp = best.cp2,main = paste("Pruned Tree (Best CP): cp = ", round(best.cp2,4)))
pruned.tree.min.cp2 <- prune(tree.model2, cp = min.cp2,main = paste("Pruned Tree (Minimum CP): cp = ", round(min.cp2,4)))

Below shows the pruned tree for the best CP.

rpart.plot(pruned.tree.1SE, 
           extra = 104, # check the help document for more information
           # color palette is a sequential color scheme that blends green (G) to blue (Bu)
           box.palette = "GnBu",  
           branch.lty = 1, 
           shadow.col = "gray", 
           nn = TRUE)

Below shows the pruned tree for the min CP. The best and min CP values are the same (0.018).

# Visualize the pruned tree
rpart.plot(pruned.tree.min.cp2, 
           extra = 104, # check the help document for more information
           # color palette is a sequential color scheme that blends green (G) to blue (Bu)
           box.palette = "GnBu",  
           branch.lty = 1, 
           shadow.col = "gray", 
           nn = TRUE)

Next we make predictions on the test set and generate the ROC curves. The below plot shows that standard logistic regression did better than both the best and min CP trees.

# Make predictions on the test set
pred.label.1SE <- predict(pruned.tree.1SE, test.data4, type = "class") # default cutoff 0.5
pred.prob.1SE <- predict(pruned.tree.1SE, test.data4, type = "prob")[,2]
##
pred.label.min <- predict(pruned.tree.min.cp2, test.data4, type = "class") # default cutoff 0.5
pred.prob.min <- predict(pruned.tree.min.cp2, test.data4, type = "prob")[,2]

# Confusion matrix
#conf.matrix <- confusionMatrix(pred.label, test.data$diabetes, positive = "pos")
#print(conf.matrix)

########################
###  logistic regression
logit.fit <- glm(pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, data = train.data4, family = binomial)
AIC.logit <- step(logit.fit, direction = "both", trace = 0)
pred.logit <- predict(AIC.logit, test.data4, type = "response")

# ROC curve and AUC
roc.tree.1SE <- roc(test.data4$pass, pred.prob.1SE)
roc.tree.min <- roc(test.data4$pass, pred.prob.min)
roc.logit <- roc(test.data4$pass, pred.logit)

##
### Sen-Spe
tree.1SE.sen <- roc.tree.1SE$sensitivities
tree.1SE.spe <- roc.tree.1SE$specificities
#
tree.min.sen <- roc.tree.min$sensitivities
tree.min.spe <- roc.tree.min$specificities
#
logit.sen <- roc.logit$sensitivities
logit.spe <- roc.logit$specificities
## AUC
auc.tree.1SE <- roc.tree.1SE$auc
auc.tree.min <- roc.tree.min$auc
auc.logit <- roc.logit$auc
###
plot(1-logit.spe, logit.sen,  
     xlab = "1 - specificity",
     ylab = "sensitivity",
     col = "darkred",
     type = "l",
     lty = 1,
     lwd = 1,
     main = "ROC: CART and Logistic Regressopm")
lines(1-tree.1SE.spe, tree.1SE.sen, 
      col = "blue",
      lty = 1,
      lwd = 1)
lines(1-tree.min.spe, tree.min.sen,      
      col = "orange",
      lty = 1,
      lwd = 1)
abline(0,1, col = "skyblue3", lty = 2, lwd = 2)
legend("bottomright", c("Logistic", "Tree 1SE", "Tree Min"),
       lty = c(1,1,1), lwd = rep(1,3),
       col = c("red", "blue", "orange"),
       bty="n",cex = 0.8)
## annotation - AUC
text(0.8, 0.46, paste("Logistic AUC: ", round(auc.logit,4)), cex = 0.8)
text(0.8, 0.4, paste("Tree 1SE AUC: ", round(auc.tree.1SE,4)), cex = 0.8)
text(0.8, 0.34, paste("Tree Min AUC: ", round(auc.tree.min,4)), cex = 0.8)

Below shows that the optimal cut-off probability is 0.5 This value represents the value that minimizes the total cost of misclassification of our response variable (pass).

# preditive probabilities of tree.min model
#train.data4$pass <- as.factor(train.data4$pass)

pred.prob.min2 <- predict(pruned.tree.min.cp2, train.data4, type = "prob")[,2]
##
cost <- NULL
cutoff <-seq(0,1, length = 10)
##
for (i in 1:10){
  pred.label <- ifelse(pred.prob.min2 > cutoff[i], "1", "0")
  FN <- sum(pred.label == "0" & train.data4$pass == "1")
  FP <- sum(pred.label == "1" & train.data4$pass == "0")
  cost[i] = 5*FP + 20*FN
}
## identify optimal cut-off
min.ID <- which(cost == min(cost))   # could have multiple minimum
optim.prob <- mean(cutoff[min.ID])   # take the average of the cut-offs
##
plot(cutoff, cost, type = "b", col = "navy",
     main = "Cutoff vs Misclassification Cost")
##
text(0.2, 3500, paste("Optimal cutoff:", round(optim.prob,4)), cex = 0.8)

7 BAGGING Regression

CART produces a single tree. Bagging uses multiple trees. Bagging generates multiple bootstrap samples from the training data, trains individual regression trees on each subset, and aggregates their predictions. The goal is to produce a more robust model.

Below shows the nbagg, cp, maxdepth, oob.error table. These are the optimal hyperparameters used to train the final model.

# Split the data
set.seed(123)
train.index5 <- createDataPartition(complete_habits_data$exam_score, p = 0.8, list = FALSE)
train.data5 <- complete_habits_data[train.index5, ]
test.data5 <- complete_habits_data[-train.index5, ]

# Set up train control for cross-validation
ctrl <- trainControl(
  method = "cv",
  number = 5,
  verboseIter = TRUE
)

# Define parameter combinations to test
nbagg.values <- c(10, 25, 50)     # number bagged trees
cp.values <- c(0.01, 0.05, 0.1)   # candidate cp values
maxdepth.values <- c(5, 10, 20)   # maximum depth of the candidate tree

# Create an empty data frame to store results
results <- data.frame()
######## Model tuning
# Manual tuning loop
for (nbagg in nbagg.values) {
  for (cp in cp.values) {
    for (maxdepth in maxdepth.values) {
      set.seed(123)
      model <- bagging(
        exam_score ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation,
        data = train.data5,
        nbagg = nbagg,
        coob = TRUE,
        trControl = ctrl,
        control = rpart.control(cp = cp, 
                                maxdepth = maxdepth)
       )
      # Get OOB error from each iteration
      oob.error <- model$err
      # Store results
      results <- rbind(results, 
                       data.frame( nbagg = nbagg,
                                   cp = cp,
                                   maxdepth = maxdepth,
                                   oob.error = oob.error))
    }
  }
}
# Find the best combination that yields the minimum out-of-bag's error
best.params <- results[which.min(results$oob.error), ]
pander(best.params)
  nbagg cp maxdepth oob.error
10 25 0.01 5 8.252

Using the optimal hyperparameters from above, we train the final model and get a RMSE of 8.748 and Rsquared of 0.7364 as shown below.

# Train the final model with the best parameters
final.model <- bagging(
  exam_score ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation,
  data = train.data5,
  nbagg = best.params$nbagg,
  coob = TRUE,
  trControl = ctrl,
  control = rpart.control(cp = best.params$cp, 
                          maxdepth = best.params$maxdepth),
  
  importance = TRUE
)

# Evaluate on test set
predictions <- predict(final.model, newdata = test.data5)
## Using the caret function to calculate errors across re-samples
baggedError <- postResample(pred = predictions, obs = test.data5$exam_score)
pander(baggedError)
RMSE Rsquared MAE
8.748 0.7364 7.088

Based on the bagging model, the variable importance chart shown below shows that study hours, mental health, netflix hours, social media hours, and sleep hours are the most important variables in predicting exam score.

var.imp <- varImp(final.model)
# Extract variable importance (requires a custom function)
get.bagging.importance <- function(model) {
  # Get all the trees from the bagging model
  trees <- model$mtrees
  
  # Initialize importance vector
  imp <- numeric(length(trees[[1]]$btree$variable.importance))
  names(imp) <- names(trees[[1]]$btree$variable.importance)
  
  # Sum importance across all trees
  for(tree in trees) {
    imp[names(tree$btree$variable.importance)] <- 
      imp[names(tree$btree$variable.importance)] + 
      tree$btree$variable.importance
  }
  
  # Average importance
  imp <- imp/length(trees)
  return(imp)
}
# Get importance
importance.scores <- get.bagging.importance(final.model)

# Sort and plot
importance.scores <- sort(importance.scores, decreasing = TRUE)
barplot(importance.scores, horiz = TRUE, las = 1,
        main = "Variable Importance - Bagging (ipred)",
        xlab = "Importance Score")

Below we generate the MAE/RMSE for the bagged model, pruned tree using min CP, and standard regression. Based on the values shown below, the bagged model did not do as well as the standard regression.

## ordinary LSE regression model with step-wise variable selection
lse.fit <- lm(exam_score ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, data = train.data5)
AIC.fit2 <- stepAIC(lse.fit, direction="both", trace = FALSE)
##
pred.lse <- predict(AIC.fit2, test.data5)
mae.lse <- mean(abs(test.data5$exam_score - pred.lse)) # mean absolutesquare error
mse.lse <- mean((test.data5$exam_score - pred.lse)^2)  # mean square error
rmse.lse <- sqrt(mse.lse)                       # root mean square error
r.squared.lse <- (cor(test.data5$exam_score, pred.lse))^2 # r-squared
##
# Base regression tree
tree.model3 <- rpart(exam_score ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, 
                    data = train.data5,
                    method = "anova",     # For regression
                    control = rpart.control(
                      minsplit = 20,    # 3. Stopping rule: min observations to split
                      minbucket = 7,    # Min observations in terminal node
                      cp = seq(0, 0.05, 20), # Complexity parameter
                      maxdepth = 5      # Maximum tree depth
                    ))
# cp table
cp.table3 <- tree.model3$cptable
##
## Identify the minimum `xerror` and its `cp`.
min.xerror3 <- min(cp.table3[, "xerror"])
min.cp.row3 <- which.min(cp.table3[, "xerror"])
min.cp3 <- cp.table3[min.cp.row3, "CP"]
##
pruned.tree.min.cp3 <- prune(tree.model3, cp = min.cp3)
pred.min.cp3 <- predict(pruned.tree.min.cp3, newdata = test.data5)
##
# min.cp
mae.tree.min.cp3 <- mean(abs(test.data5$exam_score - pred.min.cp3))
mse.tree.min.cp3 <- mean((test.data5$exam_score - pred.min.cp3)^2)
rmse.tree.min.cp3 <- sqrt(mse.tree.min.cp3)
r.squared.tree.min.cp3 <- cor(test.data5$exam_score, pred.min.cp3)^2
##
###
Errors2 <- cbind(MAE = c(baggedError[1], mae.tree.min.cp3, mae.lse),
          RMSE = c(baggedError[3], rmse.tree.min.cp3, rmse.lse),
          r.squared = c(baggedError[2], r.squared.tree.min.cp3, r.squared.lse))
rownames(Errors2) = c("bagged Tree", "tree.min.cp", "lse")
pander(Errors2)
  MAE RMSE r.squared
bagged Tree 8.748 7.088 0.7364
tree.min.cp 7.052 9.024 0.7205
lse 4.533 5.708 0.8875

8 BAGGING Classification

In this section, we do the bagging classification model.

Below shows the optimal hyperparameters for nbagg, minsplit, maxdepth, and cp. We use these to train the final model.

set.seed(123)

# Split data into training and testing sets
trainIndex6 <- createDataPartition(complete_habits_data$pass, p = 0.8, list = FALSE)
trainData6 <- complete_habits_data[trainIndex6, ]
testData6 <- complete_habits_data[-trainIndex6, ]

# Create a grid of hyperparameter combinations
hyper.grid <- expand.grid(
  nbagg = c(25, 50, 100),
  minsplit = c(5, 10, 20),
  maxdepth = c(5, 10, 20),
  cp = c(0.01, 0.001)
)
# Initialize a results data frame
results2 <- data.frame() # store values of tuned hyperparameters
best.accuracy <- 0      # store accuracy
best.params2 <- list()   # store best values of hyperparameter

testData6$pass <- as.factor(testData6$pass)
trainData6$pass <- as.factor(trainData6$pass)

# Loop through each hyperparameter combination
for(i in 1:nrow(hyper.grid)) {
  # Get current hyperparameters
  params <- hyper.grid[i, ]
  
  # Set rpart control parameters
  rpart.control <- rpart.control(
    minsplit = params$minsplit,
    maxdepth = params$maxdepth,
    cp = params$cp
  )
  
  # Train bagging model
  bag.model <- bagging(
    pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation,
    data = trainData6,
    nbagg = params$nbagg,
    coob = TRUE,
    control = rpart.control
  )
  
  # Make predictions: default cut-off 0.5
  preds <- predict(bag.model, newdata = testData6)
  
  # Calculate accuracy
  cm <- confusionMatrix(preds, testData6$pass)
  accuracy <- cm$overall["Accuracy"]
  
  # Store results
  results2 <- rbind(results2, data.frame(
    nbagg = params$nbagg,
    minsplit = params$minsplit,
    maxdepth = params$maxdepth,
    cp = params$cp,
    Accuracy = accuracy
  ))
  
  # Update best parameters if current model is better
  if(accuracy > best.accuracy) {
    best.accuracy <- accuracy
    best.params <- params
  }
  
  # Print progress
  #cat("Completed", i, "of", nrow(hyper.grid), "combinations\n")
}
pander(best.params)
  nbagg minsplit maxdepth cp
53 50 20 20 0.001

Below we train the final model using the optimal parameters and generate the confusion matrix.

# Set rpart control with best parameters
best.control <- rpart.control(
  minsplit = best.params$minsplit,
  maxdepth = best.params$maxdepth,
  cp = best.params$cp
)

# Train final model
final.bag.model <- bagging(
  pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation,
  data = trainData6,
  nbagg = best.params$nbagg,
  coob = TRUE,
  control = best.control
)

# Evaluate on test set
final.preds <- predict(final.bag.model, newdata = testData6)
final.cm <- confusionMatrix(final.preds, testData6$pass)
final.cm$table
          Reference
Prediction   0   1
         0  42   5
         1  14 139

Below we generate the ROC curves for standard logistic, bagged model, and the pruned tree with min CP. Based on the below plot, we see that the standard logistic performs better than both the bagged model and the pruned tree with min CP.

########################
###  logistic regression
logit.fit2 <- glm(pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, data = trainData6, family = binomial)
AIC.logit2 <- step(logit.fit2, direction = "both", trace = 0)
pred.logit2 <- predict(AIC.logit2, testData6, type = "response")
##
# Build the initial classification tree
tree.model4 <- rpart(pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, 
                    data = trainData6,
                    method = "class",   # classification tree
                    parms = list(split = "gini",  # Using Gini index
                                 # FN cost = 1, FP cost = 0.5
                                 loss = matrix(c(0, 0.5, 1, 0), nrow = 2)  
                                 ),
                    control = rpart.control(minsplit = 15,  # Min 15 obs to split
                                           minbucket = 5,   # Min 7 obs in leaf
                                           # Complexity parameter
                                           cp = 0.001, # complex parameter
                                           maxdepth = 5))   # Max tree depth
###
min.cp4 <- tree.model4$cptable[which.min(tree.model4$cptable[,"xerror"]),"CP"]

# Prediction with three candidate models
pruned.tree.min <- prune(tree.model4, cp = min.cp)
pred.prob.min2 <- predict(pruned.tree.min, testData6, type = "prob")[,2]
pred.prob.bagg <- predict(final.bag.model, newdata = testData6, type = "prob")[,2]
##
# ROC object
roc.tree.min2 <- roc(testData6$pass, pred.prob.min2)
roc.logit2 <- roc(testData6$pass, pred.logit2)
roc.bagg <- roc(testData6$pass, pred.prob.bagg)
##
##
### Sen-Spe
bagg.sen <- roc.bagg$sensitivities
bagg.spe <- roc.bagg$specificities
#
tree.min.sen2 <- roc.tree.min2$sensitivities
tree.min.spe2 <- roc.tree.min2$specificities
#
logit.sen2 <- roc.logit2$sensitivities
logit.spe2 <- roc.logit2$specificities
## AUC
auc.bagg <- roc.bagg$auc
auc.tree.min2 <- roc.tree.min2$auc
auc.logit2 <- roc.logit2$auc
###
plot(1-logit.spe2, logit.sen2,  
     xlab = "1 - specificity",
     ylab = "sensitivity",
     col = "darkred",
     type = "l",
     lty = 1,
     lwd = 1,
     main = "ROC: Classification Models")
lines(1-bagg.spe, bagg.sen, 
      col = "blue",
      lty = 1,
      lwd = 1)
lines(1-tree.min.spe2, tree.min.sen2,      
      col = "orange",
      lty = 1,
      lwd = 1)
abline(0,1, col = "skyblue3", lty = 2, lwd = 2)
legend("bottomright", c("Logistic", "bagg", "Tree Min"),
       lty = c(1,1,1), lwd = rep(1,3),
       col = c("red", "blue", "orange"),
       bty="n",cex = 0.8)
## annotation - AUC
text(0.8, 0.46, paste("Logistic AUC: ", round(auc.logit2,4)), cex = 0.8)
text(0.8, 0.4, paste("Bagg AUC: ", round(auc.bagg,4)), cex = 0.8)
text(0.8, 0.34, paste("Tree Min AUC: ", round(auc.tree.min2,4)), cex = 0.8)

9 Random Forest Regression

In this section, we do random forest. Random forest is similar to bagging, except that at each split, only a random subset of features is used whereas all features are considered in bagging.

Below we split the data into training/testing and generate the optimal hyperparameters shown in the table (ntree, mtry, nodesize, maxnodes, and best.rmse).

set.seed(123)  # For reproducibility
train.index7 <- sample(1:nrow(complete_habits_data), 0.8 * nrow(complete_habits_data))
train.data7 <- complete_habits_data[train.index7, ]
test.data7<- complete_habits_data[-train.index7, ]
# Create a grid (data frame) of hyperparameter combinations to test
hyper.grid2 <- expand.grid(
  ntree = c(100, 300, 500),    
  mtry = c(3, 5, 7), # Dependent on the total number features available in the data
  nodesize = c(1, 3, 5), 
  maxnodes = c(5, 10, 20, NULL)
)
##
# Initialize results storage
results3 <- data.frame()  # combination of hyperparameters and corresponding RMSE
best.rmse <- Inf         # place-holder of RMSE with initial value inf
best.params3 <- list()    # update the hyperparameter list according to the best rmse

# Set up k-fold cross-validation
k <- 5                   # 5-fold cross-validation
n0 <- dim(train.data7)[1] # size of the training data 
fold.size <- floor(n0/k) # fold size. Caution: floor() should be used. 
                         # round( ,0) should be used. why?

# Loop through each hyperparameter combination
for(i in 1:nrow(hyper.grid2)) {           
  current.params <- hyper.grid2[i, ]   # vector of hyperparameters for cross validation
  cv.errors <- numeric(k)             # store RMSE from cross-validation
  
  # Perform k-fold cross-validation
  for(j in 1:k) {
    # Split into training and validation folds
    valid.indices <- (1 + fold.size*(j-1)):(fold.size*j)  # CV observation ID vector
    cv.train <- train.data7[-valid.indices, ]   # training data in cross-validation
    cv.valid <- train.data7[valid.indices, ]    # testing data in cross-validation
    
    # Train model with current parameters
    rf.model <- randomForest(
      exam_score ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation,
      data = cv.train,
      ntree = current.params$ntree,
      mtry = current.params$mtry,
      nodesize = current.params$nodesize,
      maxnodes = current.params$maxnodes,
      importance = TRUE
    )
    
    # Make predictions on validation set
    preds2 <- predict(rf.model, newdata = cv.valid)    
    
    # Calculate RMSE
    cv.errors[j] <- sqrt(mean((preds2 - cv.valid$exam_score)^2))
  }
  
  # Average RMSE across folds
  avg.rmse <- mean(cv.errors)    
  
  # Store results: the data frame defined to store combinations of hyperparameters
  # and the resulting mean RMSEs from cross-validation
  results3 <- rbind(results3, data.frame(
    ntree = current.params$ntree,
    mtry = current.params$mtry,
    nodesize = current.params$nodesize,
    maxnodes = ifelse(is.null(current.params$maxnodes), "NULL", current.params$maxnodes),
    rmse = avg.rmse
  ))
  
  # Update best parameters if current model is better
  if(avg.rmse < best.rmse) {
    best.rmse <- avg.rmse
    best.params <- current.params
  }
  # Print progress: It is always a good idea to print something out in loops
  # cat(paste0("Completed ", i, "/", nrow(hyper.grid), " - RMSE: ", round(avg.rmse, 4), "\n"))
}  
pander(data.frame(cbind(current.params,best.rmse)))    # resulting tuned hyperparameters
  ntree mtry nodesize maxnodes best.rmse
81 500 7 5 20 7.655

Below we train the final model using the optimal hyperparameters generated in the above section and make predictions on the test set. The scatterplot below shows the actual vs predicted Values of the exam scores of the test set.

# Train final model with best parameters on full training set
final.rf <- randomForest(
  exam_score ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation,
  data = train.data7,
  ntree = best.params$ntree,
  mtry = best.params$mtry,
  nodesize = best.params$nodesize,
  maxnodes = best.params$maxnodes,
  importance = TRUE
)

# View model summary
# print(final.rf)
# Make predictions on test set
test.preds <- predict(final.rf, newdata = test.data7)

# Calculate test RMAE
test.mae <- mean(abs(test.preds - test.data7$exam_score))
# Calculate test RMSE
test.rmse <- sqrt(mean((test.preds - test.data7$exam_score)^2))
# Calculate R-squared
test.r2 <- 1 - sum((test.data7$exam_score - test.preds)^2) / sum((test.data7$exam_score - mean(test.data7$exam_score))^2)
# cat("Test R-squared:", test.r2, "\n")

### Performance vector
RF.performance = c(test.mae, test.rmse, test.r2)

# Plot actual vs predicted values
plot.data <- data.frame(Actual = test.data7$exam_score, Predicted = test.preds)
ggplot(plot.data, aes(x = Actual, y = Predicted)) +
  geom_point() +
  geom_abline(intercept = 0, slope = 1, color = "red") +
  labs(title = "Actual vs Predicted Values",
       x = "Actual Median Value ($1000s)",
       y = "Predicted Median Value ($1000s)") +
  theme_minimal()

Below we compare the performances of standard OLS regression, base regression tree, bagged, and the random forest models. Based on the RMSE values below, we see that base tree, bagged, and the random forest perform similarly.

## ordinary LSE regression model with step-wise variable selection
lse.fit2 <- lm(exam_score ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, data = train.data7)
AIC.fit3 <- stepAIC(lse.fit2, direction="both", trace = FALSE)
pred.lse3 <- predict(AIC.fit3, test.data7)
mae.lse3 <- mean(abs(test.data7$exam_score - pred.lse3))      # mean absolute error
mse.lse3 <- mean((test.data7$exam_score - pred.lse3)^2)       # mean square error
rmse.lse3 <- sqrt(mse.lse3)                            # root mean square error
r.squared.lse3 <- (cor(test.data7$exam_score, pred.lse3))^2 # r-squared

### base regression tree
tree.model5 <- rpart(exam_score ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, 
                    data = train.data7,
                    method = "anova",     # For regression
                    control = rpart.control(
                      minsplit = 20,    # 3. Stopping rule: min observations to split
                      minbucket = 7,    # Min observations in terminal node
                      cp = seq(0, 0.05, 20), # Complexity parameter
                      maxdepth = 5      # Maximum tree depth
                    ))
cp.table4 <- tree.model5$cptable
min.xerror4 <- min(cp.table4[, "xerror"])
min.cp.row4 <- which.min(cp.table4[, "xerror"])
min.cp5 <- cp.table[min.cp.row4, "CP"]
## 
pruned.tree.min.cp4 <- prune(tree.model5, cp = min.cp5)
pred.min.cp4 <- predict(pruned.tree.min.cp4, newdata = test.data7)
# performance measures
mae.tree.min.cp4 <- mean(abs(test.data7$exam_score - pred.min.cp4))   # MAD
mse.tree.min.cp4 <- mean((test.data7$exam_score - pred.min.cp4)^2)
rmse.tree.min.cp4 <- sqrt(mse.tree.min.cp4)                    # MSE
r.squared.tree.min.cp4 <- cor(test.data7$exam_score, pred.min.cp4)^2  # R.sq

### bagging regression: from the previous section
BaggPerf <- baggedError 

### Performance Comparison Table
Errors4 <- cbind(MAE = c(mae.lse3, mae.tree.min.cp4, BaggPerf[3], RF.performance[1]),
                RMSE = c(rmse.lse3, rmse.tree.min.cp4, BaggPerf[1], RF.performance[2]),
                r.squared = c(r.squared.lse3, r.squared.tree.min.cp4, BaggPerf[2],RF.performance[3]))
rownames(Errors4) = c("LS Regression", "Regression Tree", "BAGGING", "Random Forest")
pander(Errors4)
  MAE RMSE r.squared
LS Regression 4.325 5.402 0.9143
Regression Tree 7.522 9.202 0.7411
BAGGING 7.088 8.748 0.7364
Random Forest 6.952 8.374 0.7838

THe variable importance plots shown below are based on the MSE and node impurity. According to the %IncMSE plot, study hours, mental health, exercise, social media, and netflix are the most important features. The IncNodePurity plot shows the same variables.

varImpPlot(final.rf, pch = 19, main = "Variable Importance")

10 Random Forest Classification

In this section, we do random forest for our binary response variable ‘pass’.

The table below shows the optimal hyperparameters.

set.seed(123)
train.idx <- createDataPartition(complete_habits_data$pass, p = 0.8, list = FALSE)
train.data8 <- complete_habits_data[train.idx, ]
test.data8 <- complete_habits_data[-train.idx, ]

train.data8$pass <- factor(train.data8$pass)
test.data8$pass <- factor(test.data8$pass)

# cross-validation setting
k = 5
train.size <- dim(train.data8)[1]  # training data size
fold.size2 <- floor(train.size/k)  # fold size

##
tune.grid <- expand.grid(
  mtry = c(2, 3, 4, 5),
  ntree = c(100, 300, 500),
  nodesize = c(1, 3, 5, 10),
  maxnodes = c(5, 10, 20, NULL)
)
### store hyperparameters and avg of cv AUC
results4 <- data.frame()
best.auc <- 0.5         # place-holder of AUC, 0.5 = random guess
best.hyp.params <- list()   # update the hyperparameter list according to the best auc

##
for (i in 1:nrow(tune.grid)){
  current.tune.params <- tune.grid[i, ]  # subset of DATA FRAME!! 
  cv.auc <- rep(0,k)
  ##
  for (j in 1:k){
      cv.id <- (1 + (j-1)*fold.size2):(j*fold.size2)
      cv.train2 <- train.data8[-cv.id, ]
      cv.valid2 <- train.data8[cv.id, ]
      ##
       rf.cv <- randomForest(
                   pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation,
                   data = cv.train2,
                   mtry = current.tune.params$mtry,
                   ntree = current.tune.params$ntree,
                   nodesize = current.tune.params$nodesize,
                   maxnodes = current.tune.params$maxnodes)
       ##
       prob.cv <- predict(rf.cv, cv.valid2, type = "prob")[, "1"]
       cv.auc[j] <- auc(roc(cv.valid2$pass, prob.cv))
      }
      ##
      # Average RMSE across folds
      avg.auc <- mean(cv.auc)  
      ##
      # Store results: the data frame defined to store combinations of hyperparameters
      # and the resulting mean RMSEs from cross-validation
      results4 <- rbind(results4, data.frame(
                   mtry = current.tune.params$mtry,
                   ntree = current.tune.params$ntree,
                   nodesize = current.tune.params$nodesize,
                   maxnodes = current.tune.params$maxnodes,
                   auc = avg.auc))
  
     # Update best parameters if current model is better
     if(avg.auc > best.auc) {
           best.auc <- avg.auc
           best.hyp.params <- current.tune.params }
    
}
pander(data.frame(cbind(best.hyp.params,best.auc)))
  mtry ntree nodesize maxnodes best.auc
108 5 500 1 20 0.9539

Below we train the final random forest model using the optimal hyperparameters from above and generate the Area under the curve below (0.9707).

final.rf.cls <- randomForest(
      pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation,
      data = train.data8,
      ntree = best.hyp.params$ntree,
      mtry = best.hyp.params$mtry,
      nodesize = best.hyp.params$nodesize,
      maxnodes = current.tune.params$maxnodes,
      importance = TRUE
     )

test.pred <- predict(final.rf.cls, test.data8)
test.prob <- predict(final.rf.cls, test.data8, type = "prob")
rf.roc <- roc(test.data8$pass, test.prob[, "1"])
test.auc <- auc(rf.roc)
#confusionMatrix(test.pred, test.data$diabetes)
test.auc
Area under the curve: 0.9707

The variable importance plots below show that study hours, mental health, social media, sleep, and Netflix are the most important features in predicting ‘pass’.

varImpPlot(final.rf.cls, pch = 19, main = "Variable Importance of RF Classification" )

Below we compare the performances of standard logistic, base tree, bagged, and random forest models. Based on the areas below, bagged and random forest does better than the base decision tree.

########################
###  logistic regression
logit.fit3 <- glm(pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, data = train.data8, family = binomial)
AIC.logit3 <- step(logit.fit3, direction = "both", trace = 0)
pred.logit3 <- predict(AIC.logit3, test.data8, type = "response")

# Build the initial classification tree
tree.model6 <- rpart(pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation, 
                    data = train.data8,
                    method = "class",   # classification tree
                    parms = list(split = "gini",  # Using Gini index
                                 # FN cost = 1, FP cost = 0.5
                                 loss = matrix(c(0, 0.5, 1, 0), nrow = 2)  
                                 ),
                    control = rpart.control(minsplit = 15,  # Min 15 obs to split
                                           minbucket = 5,   # Min 7 obs in leaf
                                           # Complexity parameter
                                           cp = 0.001, # complex parameter
                                           maxdepth = 5))   # Max tree depth
# Find the optimal cp value that minimizes cross-validated error
min.cp6 <- tree.model6$cptable[which.min(tree.model6$cptable[,"xerror"]),"CP"]
pruned.tree.min5 <- prune(tree.model6, cp = min.cp6)


# BAGGING
# Create a grid of hyperparameter combinations
hyper.grid3 <- expand.grid(
  nbagg = c(25, 50, 100),
  minsplit = c(5, 10, 20),
  maxdepth = c(5, 10, 20),
  cp = c(0.01, 0.001)
)
# Initialize a results dataframe
results5 <- data.frame() # store values of tuned hyperparameters
best.accuracy2 <- 0      # store accuracy
best.params4 <- list()   # store best values of hyperparameter

# Loop through each hyperparameter combination
for(i in 1:nrow(hyper.grid3)) {
  # Get current hyperparameters
  params2 <- hyper.grid3[i, ]
  
  # Set rpart control parameters
  rpart.control2 <- rpart.control(
    minsplit = params2$minsplit,
    maxdepth = params2$maxdepth,
    cp = params2$cp
  )
  
  # Train bagging model
  bag.model2 <- bagging(
    pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation,
    data = train.data8,
    nbagg = params2$nbagg,
    coob = TRUE,
    control = rpart.control2
  )
  
  # Make predictions: default cut-off 0.5
  preds3 <- predict(bag.model2, newdata = test.data8)
  
  # Calculate accuracy
  cm2 <- confusionMatrix(preds3, test.data8$pass)
  accuracy2 <- cm2$overall["Accuracy"]
  
  # Store results
  results5 <- rbind(results5, data.frame(
    nbagg = params2$nbagg,
    minsplit = params2$minsplit,
    maxdepth = params2$maxdepth,
    cp = params2$cp,
    Accuracy = accuracy2
  ))
  
  # Update best parameters if current model is better
  if(accuracy2 > best.accuracy2) {
    best.accuracy2 <- accuracy2
    best.params4 <- params2
  }
}
# Set rpart control with best parameters
best.control2 <- rpart.control(
  minsplit = best.params4$minsplit,
  maxdepth = best.params4$maxdepth,
  cp = best.params4$cp
)

# Train final model
final.bag.model2 <- bagging(
  pass ~ age + gender + study_hours_per_day + social_media_hours + netflix_hours + part_time_job + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participation,
  data = train.data8,
  nbagg = best.params4$nbagg,
  coob = TRUE,
  control = best.control2
)
#pred.prob.bagg <- predict(final.bag.model, newdata = test.data, type = "prob")[,2]
###
# Prediction with three candidate models
pruned.tree.min6 <- prune(tree.model6, cp = min.cp6)
pred.prob.min3 <- predict(pruned.tree.min6, test.data8, type = "prob")[,2]
pred.prob.bagg2 <- predict(final.bag.model2, newdata = test.data8, type = "prob")[,2]
##
# ROC object
roc.tree.min3 <- roc(test.data8$pass, pred.prob.min3)
roc.logit3 <- roc(test.data8$pass, pred.logit3)
roc.bagg2 <- roc(test.data8$pass, pred.prob.bagg2)
roc.rf <- roc(test.data8$pass, test.prob[, "1"])

##
##
### Sen-Spe
bagg.sen2 <- roc.bagg2$sensitivities
bagg.spe2 <- roc.bagg2$specificities
#
tree.min.sen3 <- roc.tree.min3$sensitivities
tree.min.spe3 <- roc.tree.min3$specificities
#
logit.sen3 <- roc.logit3$sensitivities
logit.spe3 <- roc.logit3$specificities
#
rf.sen <- roc.rf$sensitivities
rf.spe <- roc.rf$specificities

## AUC
auc.bagg2 <- roc.bagg2$auc
auc.tree.min3 <- roc.tree.min3$auc
auc.logit3 <- roc.logit3$auc
auc.rf <- roc.rf$auc
###
###
plot(1-logit.spe3, logit.sen3,  
     xlab = "1 - specificity",
     ylab = "sensitivity",
     col = "darkred",
     type = "l",
     lty = 1,
     lwd = 1,
     main = "ROC: Classification Models")
lines(1-bagg.spe2, bagg.sen2, 
      col = "blue",
      lty = 1,
      lwd = 1)
lines(1-tree.min.spe3, tree.min.sen3,      
      col = "orange",
      lty = 1,
      lwd = 1)
lines(1-rf.spe, rf.sen,      
      col = "purple4",
      lty = 1,
      lwd = 1)
abline(0,1, col = "skyblue3", lty = 2, lwd = 2)
legend("bottomright", c("Logistic", "bagg", "Tree Min", "Random Forest"),
       lty = c(1,1,1), lwd = rep(1,3),
       col = c("red", "blue", "orange", "purple4"),
       bty="n",cex = 0.8)
## annotation - AUC
text(0.8, 0.46, paste("Logistic AUC: ", round(auc.logit3,4)), cex = 0.8)
text(0.8, 0.4, paste("Bagg AUC: ", round(auc.bagg2,4)), cex = 0.8)
text(0.8, 0.34, paste("Tree Min AUC: ", round(auc.tree.min3,4)), cex = 0.8)
text(0.8, 0.28, paste("Forest AUC: ", round(auc.rf,4)), cex = 0.8)

11 Results/Conclusion

Study hours, mental health, social media, sleep, Netflix, and exercise were consistently shown to be the most important features in predicting exam score/pass, out of all the models performed.

Performing CART alone may not be enough for prediction since they only generate a single decision tree. Bagging can produce a more robust model by generating/aggregating multiple trees. Random Forest may be even better than bagging because they use a random subset of features instead of all features.

LS0tDQp0aXRsZTogIlN0dWRlbnQgSGFiaXRzIHZzIEFjYWRlbWljIFBlcmZvcm1hbmNlIg0KYXV0aG9yOiAiRXJpYyBaaHUiDQpkYXRlOiAiMjAyNS0wNC0xNiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jX2NvbGxhcHNlZDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogeWVzDQogICAgdGhlbWU6IGx1bWVuDQogIHBkZl9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIGZpZ193aWR0aDogMw0KICAgIGZpZ19oZWlnaHQ6IDMNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQoNCmBgYHs9aHRtbH0NCg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KLyogQ2FzY2FkaW5nIFN0eWxlIFNoZWV0cyAoQ1NTKSBpcyBhIHN0eWxlc2hlZXQgbGFuZ3VhZ2UgdXNlZCB0byBkZXNjcmliZSB0aGUgcHJlc2VudGF0aW9uIG9mIGEgZG9jdW1lbnQgd3JpdHRlbiBpbiBIVE1MIG9yIFhNTC4gaXQgaXMgYSBzaW1wbGUgbWVjaGFuaXNtIGZvciBhZGRpbmcgc3R5bGUgKGUuZy4sIGZvbnRzLCBjb2xvcnMsIHNwYWNpbmcpIHRvIFdlYiBkb2N1bWVudHMuICovDQoNCmgxLnRpdGxlIHsgIC8qIFRpdGxlIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiB0aGUgcmVwb3J0IHRpdGxlICovDQogIGZvbnQtc2l6ZTogMjRweDsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGNvbG9yOiBuYXZ5Ow0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCn0NCmg0LmF1dGhvciB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgYXV0aG9ycyAgKi8NCiAgZm9udC1zaXplOiAxOHB4Ow0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBmb250LXdlaWdodDogYm9sZDsNCiAgY29sb3I6IG5hdnk7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCmg0LmRhdGUgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIHRoZSBkYXRlICAqLw0KICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgxIHsgLyogSGVhZGVyIDEgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAxIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAyMHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgyIHsgLyogSGVhZGVyIDIgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAyIHNlY3Rpb24gdGl0bGUgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCn0NCg0KaDMgeyAvKiBIZWFkZXIgMyAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgMyBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMTZweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoNCB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCA0IHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxNHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmJvZHkgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCi5oaWdobGlnaHRtZSB7IGJhY2tncm91bmQtY29sb3I6eWVsbG93OyB9DQoNCnAgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCjwvc3R5bGU+DQoNCmBgYA0KDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBjb2RlIGNodW5rIHNwZWNpZmllcyB3aGV0aGVyIHRoZSBSIGNvZGUsIHdhcm5pbmdzLCBhbmQgb3V0cHV0IA0KIyB3aWxsIGJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQgZmlsZXMuDQppZiAoIXJlcXVpcmUoImtuaXRyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikNCiAgIGxpYnJhcnkoa25pdHIpDQp9DQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQp9DQppZiAoIXJlcXVpcmUoInJlYWR4bCIpKSB7ICMgU1ZNIG1ldGhvZG9sb2d5DQogICBpbnN0YWxsLnBhY2thZ2VzKCJyZWFkeGwiKQ0KbGlicmFyeShyZWFkeGwpDQp9DQppZiAoIXJlcXVpcmUoImdncGxvdDIiKSkgeyAjIFNWTSBtZXRob2RvbG9neQ0KICAgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpDQpsaWJyYXJ5KGdncGxvdDIpDQp9DQppZiAoIXJlcXVpcmUoIklTTFIiKSkgeyAjIGNvbnRhaW5zIGV4YW1wbGUgZGF0YSBzZXQgIktoYW4iDQogICBpbnN0YWxsLnBhY2thZ2VzKCJJU0xSIikNCmxpYnJhcnkoSVNMUikNCn0NCmlmICghcmVxdWlyZSgiTUFTU0V4dHJhIikpIHsgIyBjb250YWlucyBleGFtcGxlIGRhdGEgc2V0ICJLaGFuIg0KICAgaW5zdGFsbC5wYWNrYWdlcygiTUFTU0V4dHJhIikNCmxpYnJhcnkoTUFTU0V4dHJhKQ0KfQ0KaWYgKCFyZXF1aXJlKCJtaXNzTWV0aG9kcyIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygibWlzc01ldGhvZHMiKQ0KbGlicmFyeShtaXNzTWV0aG9kcykNCn0NCmlmICghcmVxdWlyZSgiSG1pc2MiKSkgeyAjIGN1c3RvbWl6ZWQgY29sb3Jpbmcgb2YgcGxvdHMNCiAgIGluc3RhbGwucGFja2FnZXMoIkhtaXNjIikNCmxpYnJhcnkoSG1pc2MpDQp9DQppZiAoIXJlcXVpcmUoImdsbW5ldCIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygiZ2xtbmV0IikNCmxpYnJhcnkoZ2xtbmV0KQ0KfQ0KaWYgKCFyZXF1aXJlKCJHR2FsbHkiKSkgeyAjIGN1c3RvbWl6ZWQgY29sb3Jpbmcgb2YgcGxvdHMNCiAgIGluc3RhbGwucGFja2FnZXMoIkdHYWxseSIpDQpsaWJyYXJ5KEdHYWxseSkNCn0NCmlmICghcmVxdWlyZSgibWljZSIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygibWljZSIpDQpsaWJyYXJ5KG1pY2UpDQp9DQppZiAoIXJlcXVpcmUoInBsb3RseSIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygicGxvdGx5IikNCmxpYnJhcnkocGxvdGx5KQ0KfQ0KaWYgKCFyZXF1aXJlKCJjYXJldCIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQ0KbGlicmFyeShjYXJldCkNCn0NCmlmICghcmVxdWlyZSgicFJPQyIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygicFJPQyIpDQpsaWJyYXJ5KHBST0MpDQp9DQppZiAoIXJlcXVpcmUoInBhbmRlciIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygicGFuZGVyIikNCmxpYnJhcnkocGFuZGVyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJlMTA3MSIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygiZTEwNzEiKQ0KbGlicmFyeShlMTA3MSkNCn0NCmlmICghcmVxdWlyZSgicnBhcnQiKSkgeyAjIGN1c3RvbWl6ZWQgY29sb3Jpbmcgb2YgcGxvdHMNCiAgIGluc3RhbGwucGFja2FnZXMoInJwYXJ0IikNCmxpYnJhcnkocnBhcnQpDQp9DQppZiAoIXJlcXVpcmUoInJwYXJ0LnBsb3QiKSkgeyAjIGN1c3RvbWl6ZWQgY29sb3Jpbmcgb2YgcGxvdHMNCiAgIGluc3RhbGwucGFja2FnZXMoInJwYXJ0LnBsb3QiKQ0KbGlicmFyeShycGFydC5wbG90KQ0KfQ0KaWYgKCFyZXF1aXJlKCJyYW5kb21Gb3Jlc3QiKSkgeyAjIGN1c3RvbWl6ZWQgY29sb3Jpbmcgb2YgcGxvdHMNCiAgIGluc3RhbGwucGFja2FnZXMoInJhbmRvbUZvcmVzdCIpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCn0NCmlmICghcmVxdWlyZSgiZm9yY2F0cyIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygiZm9yY2F0cyIpDQpsaWJyYXJ5KGZvcmNhdHMpDQp9DQppZiAoIXJlcXVpcmUoImlwcmVkIikpIHsgIyBjdXN0b21pemVkIGNvbG9yaW5nIG9mIHBsb3RzDQogICBpbnN0YWxsLnBhY2thZ2VzKCJpcHJlZCIpDQpsaWJyYXJ5KGlwcmVkKQ0KfQ0KaWYgKCFyZXF1aXJlKCJuZXVyYWxuZXQiKSkgeyAjIGN1c3RvbWl6ZWQgY29sb3Jpbmcgb2YgcGxvdHMNCiAgIGluc3RhbGwucGFja2FnZXMoIm5ldXJhbG5ldCIpDQpsaWJyYXJ5KG5ldXJhbG5ldCkNCn0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgICAgICAgIyBpbmNsdWRlIGNvZGUgY2h1bmsgaW4gdGhlIG91dHB1dCBmaWxlDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCAgICMgc29tZXRpbWVzLCB5b3UgY29kZSBtYXkgcHJvZHVjZSB3YXJuaW5nIG1lc3NhZ2VzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHlvdSBjYW4gY2hvb3NlIHRvIGluY2x1ZGUgdGhlIHdhcm5pbmcgbWVzc2FnZXMgaW4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB0aGUgb3V0cHV0IGZpbGUuIA0KICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdHMgPSBUUlVFLCAgICAjIHlvdSBjYW4gYWxzbyBkZWNpZGUgd2hldGhlciB0byBpbmNsdWRlIHRoZSBvdXRwdXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBpbiB0aGUgb3V0cHV0IGZpbGUuDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSBOQQ0KICAgICAgICAgICAgICAgICAgICAgICkNCg0KYGBgDQoNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KVGhpcyBkYXRhc2V0IGlzIHRpdGxlZCDigJxTdHVkZW50IEhhYml0cyB2cyBBY2FkZW1pYyBQZXJmb3JtYW5jZeKAnSBhbmQgc2hvd3MgdmFyaW91cyBleHBsYW5hdG9yeSB2YXJpYWJsZXMgYW5kIG9uZSB0YXJnZXQvcmVzcG9uc2UgdmFyaWFibGUgKGV4YW1fc2NvcmUpLiBUaGUgcHVycG9zZSBvZiBjb2xsZWN0aW5nIHRoaXMgZGF0YXNldCBpcyB0byBzZWUgd2hhdCBmYWN0b3JzIG1heSBhZmZlY3QgZXhhbSBzY29yZXMuIFRoaXMgZGF0YXNldCB3YXMgY29sbGVjdGVkIGZyb20gS2FnZ2xlLiBUaGlzIGRhdGFzZXQgY29udGFpbnMgMTAwMCBvYnNlcnZhdGlvbnMgYW5kIDE2IHZhcmlhYmxlcyAoMTUgZmVhdHVyZSB2YXJpYWJsZXMgYW5kIDEgdGFyZ2V0IHZhcmlhYmxlKS4NCg0KVGhlIHN0dWRlbnRfaWQgc2VydmVzIGFzIHRoZSBpZGVudGlmaWVyLiBUaGUgYWdlIChudW1lcmljYWwpIHNob3dzIGVhY2ggcGVyc29ucyBhZ2UuIFRoZSBnZW5kZXIgKGNhdGVnb3JpY2FsKSBzaG93cyBlYWNoIHBlcnNvbnMgZ2VuZGVyLiBUaGUgc3R1ZHlfaG91cnNfcGVyX2RheSBzaG93cyBob3VycyBzdHVkaWVkIHBlciBkYXkuIFRoZSBzb2NpYWxfbWVkaWFfaG91cnMgc2hvd3MgaG91cnMgdXNlZCBkYWlseSBvbiBzb2NpYWwgbWVkaWEuIFRoZSBuZXRmbGl4X2hvdXJzIHNob3dzIGhvdXJzIHVzZWQgZGFpbHkgb24gbmV0ZmxpeC4gVGhlIHBhcnRfdGltZV9qb2IgKG5vL3llcykgc2hvd3MgaWYgdGhlIHBlcnNvbiBoYXMgYSBwYXJ0IHRpbWUgam9iLiBUaGUgYXR0ZW5kYW5jZSBwZXJjZW50YWdlIHNob3dzIHRoZSBwZXJjZW50YWdlIG9mIGNsYXNzZXMgYXR0ZW5kZWQuIFRoZSBzbGVlcF9ob3VycyBzaG93cyBhbW91bnQgb2Ygc2xlZXAgcGVyIG5pZ2h0LiBUaGUgZGlldF9xdWFsaXR5IChwb29yL2ZhaXIvZ29vZCkgc2hvd3MgcXVhbGl0eSBvZiBvbmVzIGRpZXQuIFRoZSBleGVyY2lzZV9mcmVxdWVuY3kgc2hvd3MgaG93IG1hbnkgdGltZXMgb25lIGV4ZXJjaXNlcyBwZXIgd2Vlay4gVGhlIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCAobm9uZS9IaWdoIFNjaG9vbC9iYWNoZWxvci9tYXN0ZXIpIHNob3dzIHRoZSBoaWdoZXN0IGxldmVsIG9mIGVkdWNhdGlvbiBvZiBvbmVzIHBhcmVudHMuIFRoZSBpbnRlcm5ldF9xdWFsaXR5KHBvb3IvYXZnL2dvb2QpIHNob3dzIGhvdyBnb29kIG9uZXMgd2ktZmkgaXMuIFRoZSBtZW50YWxfaGVhbHRoX3JhdGluZyAoMS0xMCkgc2hvd3MgaG93IG9uZSByYXRlcyB0aGVpciBtZW50YWwgaGVhbHRoLiBUaGUgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb24gKG4veSkgc2hvd3MgaWYgb25lIHBhcnRpY2lwYXRlcyBpbiBleHRyYWN1cnJpY3VsYXIgYWN0aXZpdGllcy4gVGhlIHRhcmdldC9yZXNwb25zZSB2YXJpYWJsZSBpcyBleGFtX3Njb3JlIChjb250aW51b3VzKS4NCg0KQmFzZWQgb24gdGhpcyBkYXRhc2V0LCB3ZSBjYW4gZm9ybXVsYXRlIGEgcmVzZWFyY2ggcXVlc3Rpb24uIFRoZSBxdWVzdGlvbiB3ZSBjYW4gZm9ybSBpcyB3aGF0IHZhcmlhYmxlcyBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IGluIHByZWRpY3RpbmcgZXhhbSBzY29yZS4gRm9yIHJlZ3Jlc3Npb24sIHdlIGNhbiB1c2UgZXhhbV9zY29yZSAoY29udGludW91cykgYXMgb3VyIHJlc3BvbnNlIHZhcmlhYmxlLiBGb3IgY2xhc3NpZmljYXRpb24sIHdlIGNhbiBjcmVhdGUgYSBiaW5hcnkgcmVzcG9uc2UgdmFyaWFibGUgd2l0aCAxIChwYXNzKSBhbmQgMCAoZmFpbCkuIFdlIGNhbiB1c2UgdGhlIGNvbmRpdGlvbiBpZiBleGFtIHNjb3JlIGlzIGdyZWF0ZXIgb3IgbGVzcyB0aGFuIDAuNjAuDQoNCldlIHdpbGwgdXNlIENBUlQsIGJhZ2dpbmcsIGFuZCByYW5kb20gZm9yZXN0cyB0byB0cnkgdG8gYW5zd2VyIHRoZSByZXNlYXJjaCBxdWVzdGlvbi4NCg0KIyBSZWFkIGluIERhdGFzZXQNCg0KUmVhZCBpbiBkYXRhc2V0Lg0KDQpgYGB7cn0NCmhhYml0cyA8LSByZWFkLmNzdigic3R1ZGVudF9oYWJpdHNfcGVyZm9ybWFuY2UuY3N2IikNCmBgYA0KDQpTZWUgZGF0YXNldCBzdHJ1Y3R1cmUuDQoNCmBgYHtyfQ0Kc3RyKGhhYml0cykNCmBgYA0KDQpDaGVjayBmb3IgTWlzc2luZyBWYWx1ZXMuDQoNCmBgYHtyfQ0KY29sU3Vtcyhpcy5uYShoYWJpdHMpKQ0KYGBgDQoNCg0KIyBFREENCg0KVGhpcyBzZWN0aW9uIHdpbGwgc2hvdyB0aGUgZGlzdHJpYnV0aW9uIG9mIGVhY2ggaW5kaXZpZHVhbCBmZWF0dXJlLg0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGdlbmRlciB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoaGFiaXRzLCBhZXMoeCA9IGdlbmRlcikpICsgDQogIA0KICBnZW9tX2JhcigpICsNCiAgbGFicyh0aXRsZSA9ICJHZW5kZXIiKQ0KYGBgDQoNClRoZSBiZWxvdyBmaWd1cmUgc2hvd3MgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgcGFydF90aW1lX2pvYiB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoaGFiaXRzLCBhZXMoeCA9IHBhcnRfdGltZV9qb2IpKSArIA0KICANCiAgZ2VvbV9iYXIoKSArDQogIGxhYnModGl0bGUgPSAicGFydF90aW1lX2pvYiIpDQpgYGANCg0KVGhlIGJlbG93IGZpZ3VyZSBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBkaWV0X3F1YWxpdHkgdmFyaWFibGUuDQoNCmBgYHtyfQ0KZ2dwbG90KGhhYml0cywgYWVzKHggPSBkaWV0X3F1YWxpdHkpKSArIA0KICANCiAgZ2VvbV9iYXIoKSArDQogIGxhYnModGl0bGUgPSAiZGlldF9xdWFsaXR5IikNCmBgYA0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoaGFiaXRzLCBhZXMoeCA9IHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCkpICsgDQogIA0KICBnZW9tX2JhcigpICsNCiAgbGFicyh0aXRsZSA9ICJwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwiKQ0KYGBgDQoNClRoZSBiZWxvdyBmaWd1cmUgc2hvd3MgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgaW50ZXJuZXRfcXVhbGl0eSB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoaGFiaXRzLCBhZXMoeCA9IGludGVybmV0X3F1YWxpdHkpKSArIA0KICANCiAgZ2VvbV9iYXIoKSArDQogIGxhYnModGl0bGUgPSAiaW50ZXJuZXRfcXVhbGl0eSIpDQpgYGANCg0KVGhlIGJlbG93IGZpZ3VyZSBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoaGFiaXRzLCBhZXMoeCA9IGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uKSkgKyANCiAgDQogIGdlb21fYmFyKCkgKw0KICBsYWJzKHRpdGxlID0gImV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uIikNCmBgYA0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGFnZSB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGhhYml0cywgYWVzKHggPSBhZ2UpKSArIA0KICBnZW9tX2JveHBsb3QoKSArIA0KICANCiAgbGFicyh0aXRsZSA9ICJhZ2UiKQ0KYGBgDQoNClRoZSBiZWxvdyBmaWd1cmUgc2hvd3MgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgc3R1ZHlfaG91cnNfcGVyX2RheSB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGhhYml0cywgYWVzKHggPSBzdHVkeV9ob3Vyc19wZXJfZGF5KSkgKyANCiAgZ2VvbV9ib3hwbG90KCkgKyANCiAgDQogIGxhYnModGl0bGUgPSAic3R1ZHlfaG91cnNfcGVyX2RheSIpDQpgYGANCg0KVGhlIGJlbG93IGZpZ3VyZSBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBzb2NpYWxfbWVkaWFfaG91cnMgdmFyaWFibGUuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBoYWJpdHMsIGFlcyh4ID0gc29jaWFsX21lZGlhX2hvdXJzKSkgKyANCiAgZ2VvbV9ib3hwbG90KCkgKyANCiAgDQogIGxhYnModGl0bGUgPSAic29jaWFsX21lZGlhX2hvdXJzIikNCmBgYA0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIG5ldGZsaXhfaG91cnMgdmFyaWFibGUuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBoYWJpdHMsIGFlcyh4ID0gbmV0ZmxpeF9ob3VycykpICsgDQogIGdlb21fYm94cGxvdCgpICsgDQogIA0KICBsYWJzKHRpdGxlID0gIm5ldGZsaXhfaG91cnMiKQ0KYGBgDQoNClRoZSBiZWxvdyBmaWd1cmUgc2hvd3MgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgYXR0ZW5kYW5jZV9wZXJjZW50YWdlIHZhcmlhYmxlLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gaGFiaXRzLCBhZXMoeCA9IGF0dGVuZGFuY2VfcGVyY2VudGFnZSkpICsgDQogIGdlb21fYm94cGxvdCgpICsgDQogIA0KICBsYWJzKHRpdGxlID0gImF0dGVuZGFuY2VfcGVyY2VudGFnZSIpDQpgYGANCg0KVGhlIGJlbG93IGZpZ3VyZSBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBzbGVlcF9ob3VycyB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGhhYml0cywgYWVzKHggPSBzbGVlcF9ob3VycykpICsgDQogIGdlb21fYm94cGxvdCgpICsgDQogIA0KICBsYWJzKHRpdGxlID0gInNsZWVwX2hvdXJzIikNCmBgYA0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGV4ZXJjaXNlX2ZyZXF1ZW5jeSB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGhhYml0cywgYWVzKHggPSBleGVyY2lzZV9mcmVxdWVuY3kpKSArIA0KICBnZW9tX2JveHBsb3QoKSArIA0KICANCiAgbGFicyh0aXRsZSA9ICJleGVyY2lzZV9mcmVxdWVuY3kiKQ0KYGBgDQoNClRoZSBiZWxvdyBmaWd1cmUgc2hvd3MgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgbWVudGFsX2hlYWx0aF9yYXRpbmcgdmFyaWFibGUuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBoYWJpdHMsIGFlcyh4ID0gbWVudGFsX2hlYWx0aF9yYXRpbmcpKSArIA0KICBnZW9tX2JveHBsb3QoKSArIA0KICANCiAgbGFicyh0aXRsZSA9ICJtZW50YWxfaGVhbHRoX3JhdGluZyIpDQpgYGANCg0KIyBJbXB1dGF0aW9uL0ZlYXR1cmUgRW5naW5lZXJpbmcNCg0KVGhlIHZhcmlhYmxlcyBwYXJ0X3RpbWVfam9iLCBkaWV0X3F1YWxpdHksIGludGVybmV0X3F1YWxpdHksIGFuZCBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiBhcmUgY29udmVydGVkIHRvIGNhdGVnb3JpY2FsL2ZhY3Rvci4NCg0KYGBge3J9DQojaGFiaXRzJGdlbmRlciA8LSBhcy5mYWN0b3IoaGFiaXRzJGdlbmRlcikNCmhhYml0cyRwYXJ0X3RpbWVfam9iIDwtIGFzLmZhY3RvcihoYWJpdHMkcGFydF90aW1lX2pvYikNCmhhYml0cyRkaWV0X3F1YWxpdHkgPC0gZmFjdG9yKGhhYml0cyRkaWV0X3F1YWxpdHksbGV2ZWxzID0gYygiUG9vciIsICJGYWlyIiwgIkdvb2QiKSkNCiNoYWJpdHMkcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsIDwtIGFzLmZhY3RvcihoYWJpdHMkcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsKQ0KaGFiaXRzJGludGVybmV0X3F1YWxpdHkgPC0gZmFjdG9yKGhhYml0cyRpbnRlcm5ldF9xdWFsaXR5LCBsZXZlbHMgPSBjKCJQb29yIiwgIkF2ZXJhZ2UiLCAiR29vZCIpKQ0KaGFiaXRzJGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uIDwtIGFzLmZhY3RvcihoYWJpdHMkZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb24pDQpgYGANCg0KVGhlIHZhcmlhYmxlIGdlbmRlciBjb250YWlucyB0aGUgdmFsdWUgJ090aGVyJy4gU2luY2Ugd2Ugb25seSB3YW50IHRvIHdvcmsgd2l0aCBNYWxlL2ZlbWFsZSwgd2Ugc2V0ICdPdGhlcicgdG8gbWlzc2luZyBhbmQgdXNlIE1JQ0UgaW1wdXRhdGlvbi4NCg0KYGBge3J9DQpoYWJpdHMkZ2VuZGVyW2hhYml0cyRnZW5kZXI9PSAiT3RoZXIiXSA9IE5BDQpoYWJpdHMkZ2VuZGVyIDwtIGFzLmZhY3RvcihoYWJpdHMkZ2VuZGVyKQ0KDQppbml0MiA8LSBtaWNlKGhhYml0cywgbWF4aXQgPSAwKQ0KaW5pdDIkbWV0aG9kDQoNCmltcDIgPC0gbWljZShoYWJpdHMsIG1ldGhvZCA9IGMoIiIsIiIsICJsb2dyZWciLCAiIiwgICIiLCAiIiwgIiIsICIiLCAiIiwgIiIsICIiLCAiIiwgIiIsICIiLCAiIiwgIiIpLCANCiAgICAgICAgICAgICAgICAgbWF4aXQgPSAxMCwgIA0KICAgICAgICAgICAgICAgICBtID0gNSwgDQogICAgICAgICAgICAgICAgIHNlZWQ9MTIzLA0KICAgICAgICAgICAgICAgICBwcmludD1GKSAgICAgDQoNCg0KDQpjb21wbGV0ZV9oYWJpdHNfZGF0YSA8LSBjb21wbGV0ZShpbXAyKQ0KYGBgDQoNCkZvciBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwsIHdlIGNvbnZlcnQgYm90aCBCYWNoZWxvciBhbmQgTWFzdGVyIHRvIENvbGxlZ2UgdG8gcmVkdWNlIGNhdGVnb3JpZXMuDQoNCmBgYHtyfQ0KY29tcGxldGVfaGFiaXRzX2RhdGEkcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsW2NvbXBsZXRlX2hhYml0c19kYXRhJHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCA9PSAnQmFjaGVsb3InXSA8LSAnQ29sbGVnZScNCmNvbXBsZXRlX2hhYml0c19kYXRhJHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbFtjb21wbGV0ZV9oYWJpdHNfZGF0YSRwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgPT0gJ01hc3RlciddIDwtICdDb2xsZWdlJw0KDQpjb21wbGV0ZV9oYWJpdHNfZGF0YSRwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgPC0gZmFjdG9yKGNvbXBsZXRlX2hhYml0c19kYXRhJHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCxsZXZlbHMgPSBjKCJOb25lIiwgIkhpZ2ggU2Nob29sIiwgIkNvbGxlZ2UiKSkNCg0Kc3RyKGNvbXBsZXRlX2hhYml0c19kYXRhKQ0KYGBgDQoNCiMgQ0FSVCAtIFJlZ3Jlc3Npb24NCg0KRGVjaXNpb24gdHJlZXMgcmVjdXJzaXZlbHkgc3BsaXRzIHRoZSBkYXRhIGludG8gc3Vic2V0cyBiYXNlZCBvbiB0aGUgdmFsdWUgb2YgYSBmZWF0dXJlLCB3aXRoIHRoZSBnb2FsIG9mIGNyZWF0aW5nIHN1YnNldHMgdGhhdCBhcmUgYXMgaG9tb2dlbmVvdXMgYXMgcG9zc2libGUgd2l0aCByZXNwZWN0IHRvIHRoZSB0YXJnZXQgdmFyaWFibGUuDQoNCkluIHJlZ3Jlc3Npb24gdHJlZXMsIHdlIGNhbiBwcmVkaWN0IGNvbnRpbnVvdXMgb3V0Y29tZXMgYnkgY2hvb3Npbmcgc3BsaXRzIHRvIG1pbmltaXplIHRoZSBNU0Ugb2YgdGhlIHRhcmdldCB2YXJpYWJsZS4NCg0KQXMgc2hvd24gYmVsb3csIHdlIGZpcnN0IGJ1aWxkIHRoZSBpbml0aWFsIHJlZ3Jlc3Npb24gdHJlZSB0byBnZXQgYSBnZW5lcmFsIHNlbnNlIG9mIGFsbCB0aGUgcHJlZGljdG9ycyBhbmQgdGhlaXIgc3BsaXRzLg0KDQpgYGB7cn0NCiMgU2V0IHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQ0Kc2V0LnNlZWQoMTIzKQ0KDQojIFNwbGl0IGRhdGEgaW50byB0cmFpbmluZyAoNzAlKSBhbmQgdGVzdCAoMzAlKSBzZXRzDQp0cmFpbi5pbmRleDMgPC0gc2FtcGxlKDE6bnJvdyhjb21wbGV0ZV9oYWJpdHNfZGF0YSksIHNpemUgPSAwLjggKiBucm93KGNvbXBsZXRlX2hhYml0c19kYXRhKSkNCnRyYWluLmRhdGEzIDwtIGNvbXBsZXRlX2hhYml0c19kYXRhW3RyYWluLmluZGV4MywgXQ0KdGVzdC5kYXRhMyA8LSBjb21wbGV0ZV9oYWJpdHNfZGF0YVstdHJhaW4uaW5kZXgzLCBdDQoNCiMgMS4gVHJlZSBJbmR1Y3Rpb24gJiAyLiBTcGxpdHRpbmcgQ3JpdGVyaWENCiMgQnVpbGQgdGhlIGluaXRpYWwgcmVncmVzc2lvbiB0cmVlIHVzaW5nIHJwYXJ0DQp0cmVlLm1vZGVsIDwtIHJwYXJ0KGV4YW1fc2NvcmUgfiBhZ2UgKyBnZW5kZXIgKyBzdHVkeV9ob3Vyc19wZXJfZGF5ICsgc29jaWFsX21lZGlhX2hvdXJzICsgbmV0ZmxpeF9ob3VycyArIHBhcnRfdGltZV9qb2IgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uLCANCiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLmRhdGEzLA0KICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiYW5vdmEiLCAgICAgIyBGb3IgcmVncmVzc2lvbg0KICAgICAgICAgICAgICAgICAgICBjb250cm9sID0gcnBhcnQuY29udHJvbCgNCiAgICAgICAgICAgICAgICAgICAgICBtaW5zcGxpdCA9IDIwLCAgICAjIDMuIFN0b3BwaW5nIHJ1bGU6IG1pbiBvYnNlcnZhdGlvbnMgdG8gc3BsaXQNCiAgICAgICAgICAgICAgICAgICAgICBtaW5idWNrZXQgPSA3LCAgICAjIE1pbiBvYnNlcnZhdGlvbnMgaW4gdGVybWluYWwgbm9kZQ0KICAgICAgICAgICAgICAgICAgICAgIGNwID0gc2VxKDAsIDAuMDUsIDIwKSwgIyBDb21wbGV4aXR5IHBhcmFtZXRlcg0KICAgICAgICAgICAgICAgICAgICAgIG1heGRlcHRoID0gNSAgICAgICMgTWF4aW11bSB0cmVlIGRlcHRoDQogICAgICAgICAgICAgICAgICAgICkpDQoNCiMgVmlzdWFsaXplIHRoZSB1bnBydW5lZCB0cmVlDQpycGFydC5wbG90KHRyZWUubW9kZWwsIG1haW4gPSAiSW5pdGlhbCBSZWdyZXNzaW9uIFRyZWUiKQ0KYGBgDQoNCk5leHQsIHdlIHdvdWxkIHdhbnQgdG8gcHJ1bmUgdGhlIGluaXRpYWwgdHJlZS4gQmVsb3cgc2hvd3MgYSB0YWJsZSBvZiBhbGwgdGhlIENQLCBuc3BsaXQsIHJlbCBlcnJvciwgeGVycm9yLCBhbmQgeHN0ZCB2YWx1ZXMuIA0KDQpgYGB7cn0NCiNzdW1tYXJ5KHRyZWUubW9kZWwpDQpwYW5kZXIodHJlZS5tb2RlbCRjcHRhYmxlKQ0KYGBgDQoNCkJlbG93IHNob3dzIGEgcGxvdCBvZiBob3cgY3AgY2hhbmdlcyBhcyB0cmVlIHNpemUgaW5jcmVhc2VzLiBUaGlzIGNhbiBoZWxwIHdpdGggc2VsZWN0aW5nIGNwIHZhbHVlcyBmb3IgcHJ1bmluZy4NCg0KYGBge3J9DQpwbG90Y3AodHJlZS5tb2RlbCkNCmBgYA0KDQpGb3IgcHJ1bmluZywgd2Ugc2VsZWN0IGJvdGggYmVzdCBDUCBhbmQgbWluIENQLiBCZXN0IENQIGlzIHRoZSBsYXJnZXN0IENQIHdoZW4gdGhlIHhlcnJvciBpcyB3aXRoaW4gMSBzdGFuZGFyZCBlcnJvciBvZiB0aGUgbWluaW11bS4gVGhlIG1pbiBDUCBpcyBzaW1wbHkgdGhlIHNtYWxsZXN0IENQLiBCZXN0IENQIGlzIHByZWZlcnJlZCBiZWNhdXNlIGl0IGNhbiBnaXZlIHVzIHRoZSBzaW1wbGVzdCB0cmVlIGFzIHdlbGwgYXMgcmV0YWluaW5nIGFjY3VyYWN5Lg0KDQpgYGB7cn0NCmNwLnRhYmxlIDwtIHRyZWUubW9kZWwkY3B0YWJsZQ0KDQojIyBJZGVudGlmeSB0aGUgbWluaW11bSBgeGVycm9yYCBhbmQgaXRzIGBjcGAuDQptaW4ueGVycm9yIDwtIG1pbihjcC50YWJsZVssICJ4ZXJyb3IiXSkNCm1pbi5jcC5yb3cgPC0gd2hpY2gubWluKGNwLnRhYmxlWywgInhlcnJvciJdKQ0KbWluLmNwIDwtIGNwLnRhYmxlW21pbi5jcC5yb3csICJDUCJdDQoNCiMjIEdldCB0aGUgc3RhbmRhcmQgZXJyb3IgKGB4c3RkYCkgb2YgdGhlIG1pbmltdW0gYHhlcnJvcmANCnhlcnJvci5zdGQgPC0gY3AudGFibGVbbWluLmNwLnJvdywgInhzdGQiXQ0KdGhyZXNob2xkIDwtIG1pbi54ZXJyb3IgKyB4ZXJyb3Iuc3RkICAjIFVwcGVyIGJvdW5kICgxIFNFIHJ1bGUpDQoNCiMjIEZpbmQgdGhlIHNpbXBsZXN0IHRyZWUgKGBjcGApIFdoZXJlIGB4ZXJyb3IgbGVzcyB0aGFuIG9yIGVxdWFsIHRvIFRocmVzaG9sZGAuDQpiZXN0LmNwLnJvdyA8LSB3aGljaChjcC50YWJsZVssICJ4ZXJyb3IiXSA8PSB0aHJlc2hvbGQpWzFdICAjIEZpcnN0IHJvdyBtZWV0aW5nIGNyaXRlcmlhDQpiZXN0LmNwIDwtIGNwLnRhYmxlW2Jlc3QuY3Aucm93LCAiQ1AiXQ0KDQojIyBUd28gZGlmZmVyZW50IHRyZWVzOiBiZXN0IENQIHZzIG1pbmltdW0gQ1ANCnBydW5lZC50cmVlLmJlc3QuY3AgPC0gcHJ1bmUodHJlZS5tb2RlbCwgY3AgPSBiZXN0LmNwKQ0KcHJ1bmVkLnRyZWUubWluLmNwIDwtIHBydW5lKHRyZWUubW9kZWwsIGNwID0gbWluLmNwKQ0KDQojIFZpc3VhbGl6ZSB0aGUgcHJ1bmVkIHRyZWU6IGJlc3QgQ1ANCnJwYXJ0LnBsb3QocHJ1bmVkLnRyZWUuYmVzdC5jcCwgbWFpbiA9IHBhc3RlKCJQcnVuZWQgVHJlZSAoQmVzdCBDUCk6IGNwID0gIiwgcm91bmQoYmVzdC5jcCw0KSkpDQpgYGANCg0KVGhlIGFib3ZlIHNob3dzIHRoZSB0cmVlIHdpdGggdGhlIGJlc3QgQ1Agb2YgMC4wMDQ0Lg0KDQpUaGUgYmVsb3cgc2hvd3MgdGhlIHRyZWUgd2l0aCB0aGUgbWluIENQIG9mIDAuMDAwOS4NCg0KYGBge3J9DQpycGFydC5wbG90KHBydW5lZC50cmVlLm1pbi5jcCwgbWFpbiA9IHBhc3RlKCJQcnVuZWQgVHJlZSAoTWluaW11bSBDUCk6IGNwID0gIiwgcm91bmQobWluLmNwLDQpKSkNCmBgYA0KDQpCZWxvdyB3ZSBtYWtlIHByZWRpY3Rpb25zIG9uIHRlc3QgZGF0YSB1c2luZyB0aGUgYmVzdCBDUCB0cmVlLCBtaW4gQ1AgdHJlZSwgT0xTIHJlZ3Jlc3Npb24gdXNpbmcgZmVhdHVyZXMgZnJvbSB0aGUgYmVzdCBDUCB0cmVlIChzdHVkeV9ob3Vyc19wZXJfZGF5LCBtZW50YWxfaGVhbHRoX3JhdGluZywgbmV0ZmxpeF9ob3VycywgZXhlcmNpc2VfZnJlcXVlbmN5LCBzbGVlcF9ob3VycyksIGFuZCBPTFMgcmVncmVzc2lvbiB1c2luZyBzdGVwd2lzZSBzZWxlY3Rpb24uDQoNCkFzIHNob3duIGluIHRoZSBvdXRwdXQgYmVsb3csIGl0IHNlZW1zIGxpa2UgdGhlIE9MUyBzdGVwd2lzZSBkaWQgdGhlIGJlc3Qgd2l0aCBNU0Ugb2YgMjkuMTkuIA0KDQpgYGB7cn0NCiMgNS4gUHJlZGljdGlvbg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRlc3QgZGF0YQ0KcHJlZC5iZXN0LmNwIDwtIHByZWRpY3QocHJ1bmVkLnRyZWUuYmVzdC5jcCwgbmV3ZGF0YSA9IHRlc3QuZGF0YTMpDQpwcmVkLm1pbi5jcCA8LSBwcmVkaWN0KHBydW5lZC50cmVlLm1pbi5jcCwgbmV3ZGF0YSA9IHRlc3QuZGF0YTMpDQoNCg0KIyBFdmFsdWF0ZSBtb2RlbCBwZXJmb3JtYW5jZTogYmVzdC5jcA0KbXNlLnRyZWUuYmVzdC5jcCA8LSBtZWFuKCh0ZXN0LmRhdGEzJGV4YW1fc2NvcmUgLSBwcmVkLmJlc3QuY3ApXjIpDQpybXNlLnRyZWUuYmVzdC5jcCA8LSBzcXJ0KG1zZS50cmVlLmJlc3QuY3ApDQpyLnNxdWFyZWQudHJlZS5iZXN0LmNwIDwtIGNvcih0ZXN0LmRhdGEzJGV4YW1fc2NvcmUsIHByZWQuYmVzdC5jcCleMg0KIyBtaW4uY3ANCm1zZS50cmVlLm1pbi5jcCA8LSBtZWFuKCh0ZXN0LmRhdGEzJGV4YW1fc2NvcmUgLSBwcmVkLm1pbi5jcCleMikNCnJtc2UudHJlZS5taW4uY3AgPC0gc3FydChtc2UudHJlZS5taW4uY3ApDQpyLnNxdWFyZWQudHJlZS5taW4uY3AgPC0gY29yKHRlc3QuZGF0YTMkZXhhbV9zY29yZSwgcHJlZC5taW4uY3ApXjINCg0KIyMNCiMgZml0IG9yZGluYXJ5IGxlYXN0IHNxdWFyZSByZWdyZXNzaW9uIA0KTFNFMDEgPC0gbG0oZXhhbV9zY29yZSB+IHN0dWR5X2hvdXJzX3Blcl9kYXkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIG5ldGZsaXhfaG91cnMgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBzbGVlcF9ob3VycywgZGF0YSA9IHRyYWluLmRhdGEzKQ0KcHJlZC5sc2UwMSA8LSAgcHJlZGljdChMU0UwMSwgbmV3ZGF0YSA9IHRlc3QuZGF0YTMpDQptc2UubHNlMDEgPC0gbWVhbigodGVzdC5kYXRhMyRleGFtX3Njb3JlIC0gcHJlZC5sc2UwMSleMikNCnJtc2UubHNlMDEgPC0gc3FydChtc2UubHNlMDEpDQpyLnNxdWFyZWQubHNlMDEgPC0gY29yKHRlc3QuZGF0YTMkZXhhbV9zY29yZSwgcHJlZC5sc2UwMSleMg0KDQojIw0KIyMgb3JkaW5hcnkgTFNFIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCBzdGVwLXdpc2UgdmFyaWFibGUgc2VsZWN0aW9uDQpsc2UwMi5maXQgPC0gbG0oZXhhbV9zY29yZSB+IGFnZSArIGdlbmRlciArIHN0dWR5X2hvdXJzX3Blcl9kYXkgKyBzb2NpYWxfbWVkaWFfaG91cnMgKyBuZXRmbGl4X2hvdXJzICsgcGFydF90aW1lX2pvYiArIGF0dGVuZGFuY2VfcGVyY2VudGFnZSArIHNsZWVwX2hvdXJzICsgZGlldF9xdWFsaXR5ICsgZXhlcmNpc2VfZnJlcXVlbmN5ICsgcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsICsgaW50ZXJuZXRfcXVhbGl0eSArIG1lbnRhbF9oZWFsdGhfcmF0aW5nICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb24sIGRhdGEgPSB0cmFpbi5kYXRhMykNCkFJQy5maXQgPC0gc3RlcEFJQyhsc2UwMi5maXQsIGRpcmVjdGlvbj0iYm90aCIsIHRyYWNlID0gRkFMU0UpDQpwcmVkLmxzZTAyIDwtIHByZWRpY3QoQUlDLmZpdCwgdGVzdC5kYXRhMykNCm1zZS5sc2UwMiA8LSBtZWFuKCh0ZXN0LmRhdGEzJGV4YW1fc2NvcmUgLSBwcmVkLmxzZTAyKV4yKSAgICAjIG1lYW4gc3F1YXJlIGVycm9yDQpybXNlLmxzZTAyIDwtIHNxcnQobXNlLmxzZTAyKSAgICAgICAgICAgICAgICAgICAgICAgIyByb290IG1lYW4gc3F1YXJlIGVycm9yDQpyLnNxdWFyZWQubHNlMDIgPC0gKGNvcih0ZXN0LmRhdGEzJGV4YW1fc2NvcmUsIHByZWQubHNlMDIpKV4yICMgci1zcXVhcmVkDQoNCiMjIw0KRXJyb3JzIDwtIGNiaW5kKE1TRSA9IGMobXNlLnRyZWUuYmVzdC5jcCwgbXNlLnRyZWUubWluLmNwLCBtc2UubHNlMDEsIG1zZS5sc2UwMiksDQogICAgICAgICAgICAgICAgUk1TRSA9IGMocm1zZS50cmVlLmJlc3QuY3AsIHJtc2UudHJlZS5taW4uY3AsIHJtc2UubHNlMDEsIHJtc2UubHNlMDIpLA0KICAgICAgICAgICAgICAgIHIuc3F1YXJlZCA9IGMoci5zcXVhcmVkLnRyZWUuYmVzdC5jcCwgci5zcXVhcmVkLnRyZWUubWluLmNwLCByLnNxdWFyZWQubHNlMDEsIHIuc3F1YXJlZC5sc2UwMikpDQpyb3duYW1lcyhFcnJvcnMpID0gYygidHJlZS5iZXN0LmNwIiwgInRyZWUubWluLmNwIiwgImxzZTAxIiwgImxzZTAyIikNCnBhbmRlcihFcnJvcnMpDQpgYGANCg0KQmVsb3cgd2Ugc2hvdyB3aGF0IHZhcmlhYmxlcyBhcmUgbW9zdCBpbXBvcnRhbnQgaW4gcHJlZGljdGluZyBleGFtIHNjb3JlIGJhc2VkIG9uIHRoZSBiZXN0IENQIHRyZWUuIEJhc2VkIG9uIHRoZSBiZWxvdyBjaGFydCwgd2Ugc2VlIHRoYXQgc3R1ZHkgaG91cnMsIG1lbnRhbCBoZWFsdGgsIG5ldGZsaXggaG91cnMsIGV4ZXJjaXNlIGZyZXF1ZW5jeSwgYW5kIHNsZWVwIGhvdXJzIGFyZSB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzIGluIHByZWRpY3RpbmcgZXhhbSBzY29yZS4NCg0KYGBge3J9DQojIFZhcmlhYmxlIGltcG9ydGFuY2UNCmltcG9ydGFuY2UgPC0gcHJ1bmVkLnRyZWUuYmVzdC5jcCR2YXJpYWJsZS5pbXBvcnRhbmNlDQpiYXJwbG90KHNvcnQoaW1wb3J0YW5jZSwgZGVjcmVhc2luZyA9IFRSVUUpLCANCiAgICAgICAgbWFpbiA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlOiBCZXN0IENQIiwNCiAgICAgICAgbGFzID0gMikNCmBgYA0KDQpCZWxvdyB3ZSBzaG93IHdoYXQgdmFyaWFibGVzIGFyZSBtb3N0IGltcG9ydGFudCBpbiBwcmVkaWN0aW5nIGV4YW0gc2NvcmUgYmFzZWQgb24gdGhlIG1pbiBDUCB0cmVlLiBCYXNlZCBvbiB0aGUgYmVsb3cgY2hhcnQsIHdlIHNlZSB0aGF0IHN0dWR5IGhvdXJzLCBtZW50YWwgaGVhbHRoLCBuZXRmbGl4IGhvdXJzLCBzb2NpYWwgbWVkaWEgaG91cnMsIGFuZCBleGVyY2lzZSBmcmVxIGFyZSB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzIGluIHByZWRpY3RpbmcgZXhhbSBzY29yZS4NCg0KYGBge3J9DQojIFZhcmlhYmxlIGltcG9ydGFuY2UNCmltcG9ydGFuY2UyIDwtIHBydW5lZC50cmVlLm1pbi5jcCR2YXJpYWJsZS5pbXBvcnRhbmNlDQpiYXJwbG90KHNvcnQoaW1wb3J0YW5jZTIsIGRlY3JlYXNpbmcgPSBUUlVFKSwgDQogICAgICAgIG1haW4gPSAiVmFyaWFibGUgSW1wb3J0YW5jZTogTWluaW11bSBDUCIsDQogICAgICAgIGxhcyA9IDIpDQpgYGANCg0KIyBDQVJUIC0gQ2xhc3NpZmljYXRpb24NCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBkbyBjbGFzc2lmaWNhdGlvbiB0cmVlcy4gV2UgY2FuIHVzZSB0aGVtIHRvIHByZWRpY3QgY2F0ZWdvcmljYWwgb3V0Y29tZXMgYnkgdXNpbmcgR2luaSBpbXB1cml0eSBhbmQgZW50cm9weS4NCg0KRmlyc3QsIHdlIG5lZWQgdG8gY3JlYXRlIGEgYmluYXJ5IHJlc3BvbnNlIHZhcmlhYmxlIHNpbmNlIG91ciBvcmlnaW5hbCByZXNwb25zZSBpcyBjb250aW51b3VzLiBTaW5jZSBleGFtIHNjb3JlcyBvdmVyIDYwIGlzIGdlbmVyYWxseSBjb25zaWRlcmVkIHBhc3NpbmcsIHdlIGNyZWF0ZSAncGFzcycgd2hlcmUgMSBpcyBwYXNzIGFuZCAwIGlzIGZhaWwuDQoNClRoZW4gd2UgY3JlYXRlIHRoZSBpbml0aWFsIHRyZWUsIGFzIHNob3duIGJlbG93Lg0KDQpgYGB7cn0NCmNvbXBsZXRlX2hhYml0c19kYXRhIDwtIHRyYW5zZm9ybShjb21wbGV0ZV9oYWJpdHNfZGF0YSwgcGFzcz1pZmVsc2UoZXhhbV9zY29yZSA+PSA2MCwgMSwgMCkpDQoNCnNldC5zZWVkKDEyMykNCnRyYWluLmluZGV4NCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGNvbXBsZXRlX2hhYml0c19kYXRhJHBhc3MsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkNCnRyYWluLmRhdGE0IDwtIGNvbXBsZXRlX2hhYml0c19kYXRhW3RyYWluLmluZGV4NCwgXQ0KdGVzdC5kYXRhNCA8LSBjb21wbGV0ZV9oYWJpdHNfZGF0YVstdHJhaW4uaW5kZXg0LCBdDQoNCnRyYWluLmRhdGE0JHBhc3MgPC0gZmFjdG9yKHRyYWluLmRhdGE0JHBhc3MpDQp0ZXN0LmRhdGE0JHBhc3MgPC0gZmFjdG9yKHRlc3QuZGF0YTQkcGFzcykNCg0KIyBCdWlsZCB0aGUgaW5pdGlhbCBjbGFzc2lmaWNhdGlvbiB0cmVlDQp0cmVlLm1vZGVsMiA8LSBycGFydChwYXNzIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwgDQogICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbi5kYXRhNCwNCiAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImNsYXNzIiwgICAjIGNsYXNzaWZpY2F0aW9uIHRyZWUNCiAgICAgICAgICAgICAgICAgICAgcGFybXMgPSBsaXN0KHNwbGl0ID0gImdpbmkiLCAgIyBVc2luZyBHaW5pIGluZGV4DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEZOIGNvc3QgPSAxLCBGUCBjb3N0ID0gMC41DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb3NzID0gbWF0cml4KGMoMCwgMC41LCAxLCAwKSwgbnJvdyA9IDIpICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksDQogICAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBycGFydC5jb250cm9sKG1pbnNwbGl0ID0gMTUsICAjIE1pbiAxNSBvYnMgdG8gc3BsaXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5idWNrZXQgPSA1LCAgICMgTWluIDcgb2JzIGluIGxlYWYNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIENvbXBsZXhpdHkgcGFyYW1ldGVyDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3AgPSAwLjAwMSwgIyBjb21wbGV4IHBhcmFtZXRlcg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heGRlcHRoID0gNSkpICAgIyBNYXggdHJlZSBkZXB0aA0KDQoNCnJwYXJ0LnBsb3QodHJlZS5tb2RlbDIsIA0KICAgICAgICAgICBleHRyYSA9IDEwNCwgIyBjaGVjayB0aGUgaGVscCBkb2N1bWVudCBmb3IgbW9yZSBpbmZvcm1hdGlvbg0KICAgICAgICAgICAjIGNvbG9yIHBhbGV0dGUgaXMgYSBzZXF1ZW50aWFsIGNvbG9yIHNjaGVtZSB0aGF0IGJsZW5kcyBncmVlbiAoRykgdG8gYmx1ZSAoQnUpDQogICAgICAgICAgIGJveC5wYWxldHRlID0gIkduQnUiLCAgDQogICAgICAgICAgIGJyYW5jaC5sdHkgPSAxLCANCiAgICAgICAgICAgc2hhZG93LmNvbCA9ICJncmF5IiwgDQogICAgICAgICAgIG5uID0gVFJVRSkNCmBgYA0KDQpCZWxvdyBzaG93cyB0aGUgY3AgdGFibGUgdG8gaGVscCB3aXRoIHRoZSBwcnVuaW5nIHByb2Nlc3MuDQoNCmBgYHtyfQ0KcGFuZGVyKHRyZWUubW9kZWwyJGNwdGFibGUpDQpgYGANCg0KQmVsb3cgc2hvd3MgdGhlIGNwIHBsb3QuDQoNCmBgYHtyfQ0KcGxvdGNwKHRyZWUubW9kZWwyKQ0KYGBgDQoNCkJlbG93LCB3ZSBnZXQgdGhlIGJlc3QgQ1AgYW5kIHRoZSBtaW4gQ1AgdmFsdWVzLg0KDQpgYGB7cn0NCmNwLnRhYmxlMiA8LSB0cmVlLm1vZGVsMiRjcHRhYmxlDQojbWluLmNwMiA8LSB0cmVlLm1vZGVsMiRjcHRhYmxlW3doaWNoLm1pbih0cmVlLm1vZGVsMiRjcHRhYmxlWywieGVycm9yIl0pLCJDUCJdDQojIyBJZGVudGlmeSB0aGUgbWluaW11bSBgeGVycm9yYCBhbmQgaXRzIGBjcGAuDQptaW4ueGVycm9yMiA8LSBtaW4oY3AudGFibGUyWywgInhlcnJvciJdKQ0KbWluLmNwLnJvdzIgPC0gd2hpY2gubWluKGNwLnRhYmxlMlssICJ4ZXJyb3IiXSkNCm1pbi5jcDIgPC0gY3AudGFibGUyW21pbi5jcC5yb3cyLCAiQ1AiXQ0KDQojIyBHZXQgdGhlIHN0YW5kYXJkIGVycm9yIChgeHN0ZGApIG9mIHRoZSBtaW5pbXVtIGB4ZXJyb3JgDQp4ZXJyb3Iuc3RkMiA8LSBjcC50YWJsZTJbbWluLmNwLnJvdzIsICJ4c3RkIl0NCnRocmVzaG9sZDIgPC0gbWluLnhlcnJvcjIgKyB4ZXJyb3Iuc3RkMiAgIyBVcHBlciBib3VuZCAoMSBTRSBydWxlKQ0KDQojIyBGaW5kIHRoZSBzaW1wbGVzdCB0cmVlIChgY3BgKSBXaGVyZSBgeGVycm9yIGxlc3MgdGhhbiBvciBlcXVhbCB0byBUaHJlc2hvbGRgLg0KYmVzdC5jcC5yb3cyIDwtIHdoaWNoKGNwLnRhYmxlMlssICJ4ZXJyb3IiXSA8PSB0aHJlc2hvbGQyKVsxXSAgIyBGaXJzdCByb3cgbWVldGluZyBjcml0ZXJpYQ0KYmVzdC5jcDIgPC0gY3AudGFibGUyW2Jlc3QuY3Aucm93MiwgIkNQIl0NCg0KIyMgVHdvIGRpZmZlcmVudCB0cmVlczogYmVzdCBDUCB2cyBtaW5pbXVtIENQDQpwcnVuZWQudHJlZS4xU0UgPC0gcHJ1bmUodHJlZS5tb2RlbDIsIGNwID0gYmVzdC5jcDIsbWFpbiA9IHBhc3RlKCJQcnVuZWQgVHJlZSAoQmVzdCBDUCk6IGNwID0gIiwgcm91bmQoYmVzdC5jcDIsNCkpKQ0KcHJ1bmVkLnRyZWUubWluLmNwMiA8LSBwcnVuZSh0cmVlLm1vZGVsMiwgY3AgPSBtaW4uY3AyLG1haW4gPSBwYXN0ZSgiUHJ1bmVkIFRyZWUgKE1pbmltdW0gQ1ApOiBjcCA9ICIsIHJvdW5kKG1pbi5jcDIsNCkpKQ0KYGBgDQoNCkJlbG93IHNob3dzIHRoZSBwcnVuZWQgdHJlZSBmb3IgdGhlIGJlc3QgQ1AuDQoNCmBgYHtyfQ0KcnBhcnQucGxvdChwcnVuZWQudHJlZS4xU0UsIA0KICAgICAgICAgICBleHRyYSA9IDEwNCwgIyBjaGVjayB0aGUgaGVscCBkb2N1bWVudCBmb3IgbW9yZSBpbmZvcm1hdGlvbg0KICAgICAgICAgICAjIGNvbG9yIHBhbGV0dGUgaXMgYSBzZXF1ZW50aWFsIGNvbG9yIHNjaGVtZSB0aGF0IGJsZW5kcyBncmVlbiAoRykgdG8gYmx1ZSAoQnUpDQogICAgICAgICAgIGJveC5wYWxldHRlID0gIkduQnUiLCAgDQogICAgICAgICAgIGJyYW5jaC5sdHkgPSAxLCANCiAgICAgICAgICAgc2hhZG93LmNvbCA9ICJncmF5IiwgDQogICAgICAgICAgIG5uID0gVFJVRSkNCmBgYA0KDQpCZWxvdyBzaG93cyB0aGUgcHJ1bmVkIHRyZWUgZm9yIHRoZSBtaW4gQ1AuIFRoZSBiZXN0IGFuZCBtaW4gQ1AgdmFsdWVzIGFyZSB0aGUgc2FtZSAoMC4wMTgpLg0KDQpgYGB7cn0NCiMgVmlzdWFsaXplIHRoZSBwcnVuZWQgdHJlZQ0KcnBhcnQucGxvdChwcnVuZWQudHJlZS5taW4uY3AyLCANCiAgICAgICAgICAgZXh0cmEgPSAxMDQsICMgY2hlY2sgdGhlIGhlbHAgZG9jdW1lbnQgZm9yIG1vcmUgaW5mb3JtYXRpb24NCiAgICAgICAgICAgIyBjb2xvciBwYWxldHRlIGlzIGEgc2VxdWVudGlhbCBjb2xvciBzY2hlbWUgdGhhdCBibGVuZHMgZ3JlZW4gKEcpIHRvIGJsdWUgKEJ1KQ0KICAgICAgICAgICBib3gucGFsZXR0ZSA9ICJHbkJ1IiwgIA0KICAgICAgICAgICBicmFuY2gubHR5ID0gMSwgDQogICAgICAgICAgIHNoYWRvdy5jb2wgPSAiZ3JheSIsIA0KICAgICAgICAgICBubiA9IFRSVUUpDQpgYGANCg0KTmV4dCB3ZSBtYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldCBhbmQgZ2VuZXJhdGUgdGhlIFJPQyBjdXJ2ZXMuIFRoZSBiZWxvdyBwbG90IHNob3dzIHRoYXQgc3RhbmRhcmQgbG9naXN0aWMgcmVncmVzc2lvbiBkaWQgYmV0dGVyIHRoYW4gYm90aCB0aGUgYmVzdCBhbmQgbWluIENQIHRyZWVzLg0KDQpgYGB7cn0NCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBzZXQNCnByZWQubGFiZWwuMVNFIDwtIHByZWRpY3QocHJ1bmVkLnRyZWUuMVNFLCB0ZXN0LmRhdGE0LCB0eXBlID0gImNsYXNzIikgIyBkZWZhdWx0IGN1dG9mZiAwLjUNCnByZWQucHJvYi4xU0UgPC0gcHJlZGljdChwcnVuZWQudHJlZS4xU0UsIHRlc3QuZGF0YTQsIHR5cGUgPSAicHJvYiIpWywyXQ0KIyMNCnByZWQubGFiZWwubWluIDwtIHByZWRpY3QocHJ1bmVkLnRyZWUubWluLmNwMiwgdGVzdC5kYXRhNCwgdHlwZSA9ICJjbGFzcyIpICMgZGVmYXVsdCBjdXRvZmYgMC41DQpwcmVkLnByb2IubWluIDwtIHByZWRpY3QocHJ1bmVkLnRyZWUubWluLmNwMiwgdGVzdC5kYXRhNCwgdHlwZSA9ICJwcm9iIilbLDJdDQoNCiMgQ29uZnVzaW9uIG1hdHJpeA0KI2NvbmYubWF0cml4IDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkLmxhYmVsLCB0ZXN0LmRhdGEkZGlhYmV0ZXMsIHBvc2l0aXZlID0gInBvcyIpDQojcHJpbnQoY29uZi5tYXRyaXgpDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyMjICBsb2dpc3RpYyByZWdyZXNzaW9uDQpsb2dpdC5maXQgPC0gZ2xtKHBhc3MgfiBhZ2UgKyBnZW5kZXIgKyBzdHVkeV9ob3Vyc19wZXJfZGF5ICsgc29jaWFsX21lZGlhX2hvdXJzICsgbmV0ZmxpeF9ob3VycyArIHBhcnRfdGltZV9qb2IgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uLCBkYXRhID0gdHJhaW4uZGF0YTQsIGZhbWlseSA9IGJpbm9taWFsKQ0KQUlDLmxvZ2l0IDwtIHN0ZXAobG9naXQuZml0LCBkaXJlY3Rpb24gPSAiYm90aCIsIHRyYWNlID0gMCkNCnByZWQubG9naXQgPC0gcHJlZGljdChBSUMubG9naXQsIHRlc3QuZGF0YTQsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQojIFJPQyBjdXJ2ZSBhbmQgQVVDDQpyb2MudHJlZS4xU0UgPC0gcm9jKHRlc3QuZGF0YTQkcGFzcywgcHJlZC5wcm9iLjFTRSkNCnJvYy50cmVlLm1pbiA8LSByb2ModGVzdC5kYXRhNCRwYXNzLCBwcmVkLnByb2IubWluKQ0Kcm9jLmxvZ2l0IDwtIHJvYyh0ZXN0LmRhdGE0JHBhc3MsIHByZWQubG9naXQpDQoNCiMjDQojIyMgU2VuLVNwZQ0KdHJlZS4xU0Uuc2VuIDwtIHJvYy50cmVlLjFTRSRzZW5zaXRpdml0aWVzDQp0cmVlLjFTRS5zcGUgPC0gcm9jLnRyZWUuMVNFJHNwZWNpZmljaXRpZXMNCiMNCnRyZWUubWluLnNlbiA8LSByb2MudHJlZS5taW4kc2Vuc2l0aXZpdGllcw0KdHJlZS5taW4uc3BlIDwtIHJvYy50cmVlLm1pbiRzcGVjaWZpY2l0aWVzDQojDQpsb2dpdC5zZW4gPC0gcm9jLmxvZ2l0JHNlbnNpdGl2aXRpZXMNCmxvZ2l0LnNwZSA8LSByb2MubG9naXQkc3BlY2lmaWNpdGllcw0KIyMgQVVDDQphdWMudHJlZS4xU0UgPC0gcm9jLnRyZWUuMVNFJGF1Yw0KYXVjLnRyZWUubWluIDwtIHJvYy50cmVlLm1pbiRhdWMNCmF1Yy5sb2dpdCA8LSByb2MubG9naXQkYXVjDQojIyMNCnBsb3QoMS1sb2dpdC5zcGUsIGxvZ2l0LnNlbiwgIA0KICAgICB4bGFiID0gIjEgLSBzcGVjaWZpY2l0eSIsDQogICAgIHlsYWIgPSAic2Vuc2l0aXZpdHkiLA0KICAgICBjb2wgPSAiZGFya3JlZCIsDQogICAgIHR5cGUgPSAibCIsDQogICAgIGx0eSA9IDEsDQogICAgIGx3ZCA9IDEsDQogICAgIG1haW4gPSAiUk9DOiBDQVJUIGFuZCBMb2dpc3RpYyBSZWdyZXNzb3BtIikNCmxpbmVzKDEtdHJlZS4xU0Uuc3BlLCB0cmVlLjFTRS5zZW4sIA0KICAgICAgY29sID0gImJsdWUiLA0KICAgICAgbHR5ID0gMSwNCiAgICAgIGx3ZCA9IDEpDQpsaW5lcygxLXRyZWUubWluLnNwZSwgdHJlZS5taW4uc2VuLCAgICAgIA0KICAgICAgY29sID0gIm9yYW5nZSIsDQogICAgICBsdHkgPSAxLA0KICAgICAgbHdkID0gMSkNCmFibGluZSgwLDEsIGNvbCA9ICJza3libHVlMyIsIGx0eSA9IDIsIGx3ZCA9IDIpDQpsZWdlbmQoImJvdHRvbXJpZ2h0IiwgYygiTG9naXN0aWMiLCAiVHJlZSAxU0UiLCAiVHJlZSBNaW4iKSwNCiAgICAgICBsdHkgPSBjKDEsMSwxKSwgbHdkID0gcmVwKDEsMyksDQogICAgICAgY29sID0gYygicmVkIiwgImJsdWUiLCAib3JhbmdlIiksDQogICAgICAgYnR5PSJuIixjZXggPSAwLjgpDQojIyBhbm5vdGF0aW9uIC0gQVVDDQp0ZXh0KDAuOCwgMC40NiwgcGFzdGUoIkxvZ2lzdGljIEFVQzogIiwgcm91bmQoYXVjLmxvZ2l0LDQpKSwgY2V4ID0gMC44KQ0KdGV4dCgwLjgsIDAuNCwgcGFzdGUoIlRyZWUgMVNFIEFVQzogIiwgcm91bmQoYXVjLnRyZWUuMVNFLDQpKSwgY2V4ID0gMC44KQ0KdGV4dCgwLjgsIDAuMzQsIHBhc3RlKCJUcmVlIE1pbiBBVUM6ICIsIHJvdW5kKGF1Yy50cmVlLm1pbiw0KSksIGNleCA9IDAuOCkNCmBgYA0KDQpCZWxvdyBzaG93cyB0aGF0IHRoZSBvcHRpbWFsIGN1dC1vZmYgcHJvYmFiaWxpdHkgaXMgMC41DQpUaGlzIHZhbHVlIHJlcHJlc2VudHMgdGhlIHZhbHVlIHRoYXQgbWluaW1pemVzIHRoZSB0b3RhbCBjb3N0IG9mIG1pc2NsYXNzaWZpY2F0aW9uIG9mIG91ciByZXNwb25zZSB2YXJpYWJsZSAocGFzcykuDQoNCmBgYHtyfQ0KIyBwcmVkaXRpdmUgcHJvYmFiaWxpdGllcyBvZiB0cmVlLm1pbiBtb2RlbA0KI3RyYWluLmRhdGE0JHBhc3MgPC0gYXMuZmFjdG9yKHRyYWluLmRhdGE0JHBhc3MpDQoNCnByZWQucHJvYi5taW4yIDwtIHByZWRpY3QocHJ1bmVkLnRyZWUubWluLmNwMiwgdHJhaW4uZGF0YTQsIHR5cGUgPSAicHJvYiIpWywyXQ0KIyMNCmNvc3QgPC0gTlVMTA0KY3V0b2ZmIDwtc2VxKDAsMSwgbGVuZ3RoID0gMTApDQojIw0KZm9yIChpIGluIDE6MTApew0KICBwcmVkLmxhYmVsIDwtIGlmZWxzZShwcmVkLnByb2IubWluMiA+IGN1dG9mZltpXSwgIjEiLCAiMCIpDQogIEZOIDwtIHN1bShwcmVkLmxhYmVsID09ICIwIiAmIHRyYWluLmRhdGE0JHBhc3MgPT0gIjEiKQ0KICBGUCA8LSBzdW0ocHJlZC5sYWJlbCA9PSAiMSIgJiB0cmFpbi5kYXRhNCRwYXNzID09ICIwIikNCiAgY29zdFtpXSA9IDUqRlAgKyAyMCpGTg0KfQ0KIyMgaWRlbnRpZnkgb3B0aW1hbCBjdXQtb2ZmDQptaW4uSUQgPC0gd2hpY2goY29zdCA9PSBtaW4oY29zdCkpICAgIyBjb3VsZCBoYXZlIG11bHRpcGxlIG1pbmltdW0NCm9wdGltLnByb2IgPC0gbWVhbihjdXRvZmZbbWluLklEXSkgICAjIHRha2UgdGhlIGF2ZXJhZ2Ugb2YgdGhlIGN1dC1vZmZzDQojIw0KcGxvdChjdXRvZmYsIGNvc3QsIHR5cGUgPSAiYiIsIGNvbCA9ICJuYXZ5IiwNCiAgICAgbWFpbiA9ICJDdXRvZmYgdnMgTWlzY2xhc3NpZmljYXRpb24gQ29zdCIpDQojIw0KdGV4dCgwLjIsIDM1MDAsIHBhc3RlKCJPcHRpbWFsIGN1dG9mZjoiLCByb3VuZChvcHRpbS5wcm9iLDQpKSwgY2V4ID0gMC44KQ0KYGBgDQoNCiMgQkFHR0lORyBSZWdyZXNzaW9uDQoNCkNBUlQgcHJvZHVjZXMgYSBzaW5nbGUgdHJlZS4gQmFnZ2luZyB1c2VzIG11bHRpcGxlIHRyZWVzLiBCYWdnaW5nIGdlbmVyYXRlcyBtdWx0aXBsZSBib290c3RyYXAgc2FtcGxlcyBmcm9tIHRoZSB0cmFpbmluZyBkYXRhLCB0cmFpbnMgaW5kaXZpZHVhbCByZWdyZXNzaW9uIHRyZWVzIG9uIGVhY2ggc3Vic2V0LCBhbmQgYWdncmVnYXRlcyB0aGVpciBwcmVkaWN0aW9ucy4gVGhlIGdvYWwgaXMgdG8gcHJvZHVjZSBhIG1vcmUgcm9idXN0IG1vZGVsLg0KDQpCZWxvdyBzaG93cyB0aGUgbmJhZ2csIGNwLCBtYXhkZXB0aCwgb29iLmVycm9yIHRhYmxlLiBUaGVzZSBhcmUgdGhlIG9wdGltYWwgaHlwZXJwYXJhbWV0ZXJzIHVzZWQgdG8gdHJhaW4gdGhlIGZpbmFsIG1vZGVsLg0KDQpgYGB7cn0NCiMgU3BsaXQgdGhlIGRhdGENCnNldC5zZWVkKDEyMykNCnRyYWluLmluZGV4NSA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGNvbXBsZXRlX2hhYml0c19kYXRhJGV4YW1fc2NvcmUsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkNCnRyYWluLmRhdGE1IDwtIGNvbXBsZXRlX2hhYml0c19kYXRhW3RyYWluLmluZGV4NSwgXQ0KdGVzdC5kYXRhNSA8LSBjb21wbGV0ZV9oYWJpdHNfZGF0YVstdHJhaW4uaW5kZXg1LCBdDQoNCiMgU2V0IHVwIHRyYWluIGNvbnRyb2wgZm9yIGNyb3NzLXZhbGlkYXRpb24NCmN0cmwgPC0gdHJhaW5Db250cm9sKA0KICBtZXRob2QgPSAiY3YiLA0KICBudW1iZXIgPSA1LA0KICB2ZXJib3NlSXRlciA9IFRSVUUNCikNCg0KIyBEZWZpbmUgcGFyYW1ldGVyIGNvbWJpbmF0aW9ucyB0byB0ZXN0DQpuYmFnZy52YWx1ZXMgPC0gYygxMCwgMjUsIDUwKSAgICAgIyBudW1iZXIgYmFnZ2VkIHRyZWVzDQpjcC52YWx1ZXMgPC0gYygwLjAxLCAwLjA1LCAwLjEpICAgIyBjYW5kaWRhdGUgY3AgdmFsdWVzDQptYXhkZXB0aC52YWx1ZXMgPC0gYyg1LCAxMCwgMjApICAgIyBtYXhpbXVtIGRlcHRoIG9mIHRoZSBjYW5kaWRhdGUgdHJlZQ0KDQojIENyZWF0ZSBhbiBlbXB0eSBkYXRhIGZyYW1lIHRvIHN0b3JlIHJlc3VsdHMNCnJlc3VsdHMgPC0gZGF0YS5mcmFtZSgpDQojIyMjIyMjIyBNb2RlbCB0dW5pbmcNCiMgTWFudWFsIHR1bmluZyBsb29wDQpmb3IgKG5iYWdnIGluIG5iYWdnLnZhbHVlcykgew0KICBmb3IgKGNwIGluIGNwLnZhbHVlcykgew0KICAgIGZvciAobWF4ZGVwdGggaW4gbWF4ZGVwdGgudmFsdWVzKSB7DQogICAgICBzZXQuc2VlZCgxMjMpDQogICAgICBtb2RlbCA8LSBiYWdnaW5nKA0KICAgICAgICBleGFtX3Njb3JlIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwNCiAgICAgICAgZGF0YSA9IHRyYWluLmRhdGE1LA0KICAgICAgICBuYmFnZyA9IG5iYWdnLA0KICAgICAgICBjb29iID0gVFJVRSwNCiAgICAgICAgdHJDb250cm9sID0gY3RybCwNCiAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woY3AgPSBjcCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heGRlcHRoID0gbWF4ZGVwdGgpDQogICAgICAgKQ0KICAgICAgIyBHZXQgT09CIGVycm9yIGZyb20gZWFjaCBpdGVyYXRpb24NCiAgICAgIG9vYi5lcnJvciA8LSBtb2RlbCRlcnINCiAgICAgICMgU3RvcmUgcmVzdWx0cw0KICAgICAgcmVzdWx0cyA8LSByYmluZChyZXN1bHRzLCANCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YS5mcmFtZSggbmJhZ2cgPSBuYmFnZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3AgPSBjcCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4ZGVwdGggPSBtYXhkZXB0aCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb29iLmVycm9yID0gb29iLmVycm9yKSkNCiAgICB9DQogIH0NCn0NCiMgRmluZCB0aGUgYmVzdCBjb21iaW5hdGlvbiB0aGF0IHlpZWxkcyB0aGUgbWluaW11bSBvdXQtb2YtYmFnJ3MgZXJyb3INCmJlc3QucGFyYW1zIDwtIHJlc3VsdHNbd2hpY2gubWluKHJlc3VsdHMkb29iLmVycm9yKSwgXQ0KcGFuZGVyKGJlc3QucGFyYW1zKQ0KYGBgDQoNClVzaW5nIHRoZSBvcHRpbWFsIGh5cGVycGFyYW1ldGVycyBmcm9tIGFib3ZlLCB3ZSB0cmFpbiB0aGUgZmluYWwgbW9kZWwgYW5kIGdldCBhIFJNU0Ugb2YgOC43NDggYW5kIFJzcXVhcmVkIG9mIDAuNzM2NCBhcyBzaG93biBiZWxvdy4NCg0KYGBge3J9DQojIFRyYWluIHRoZSBmaW5hbCBtb2RlbCB3aXRoIHRoZSBiZXN0IHBhcmFtZXRlcnMNCmZpbmFsLm1vZGVsIDwtIGJhZ2dpbmcoDQogIGV4YW1fc2NvcmUgfiBhZ2UgKyBnZW5kZXIgKyBzdHVkeV9ob3Vyc19wZXJfZGF5ICsgc29jaWFsX21lZGlhX2hvdXJzICsgbmV0ZmxpeF9ob3VycyArIHBhcnRfdGltZV9qb2IgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uLA0KICBkYXRhID0gdHJhaW4uZGF0YTUsDQogIG5iYWdnID0gYmVzdC5wYXJhbXMkbmJhZ2csDQogIGNvb2IgPSBUUlVFLA0KICB0ckNvbnRyb2wgPSBjdHJsLA0KICBjb250cm9sID0gcnBhcnQuY29udHJvbChjcCA9IGJlc3QucGFyYW1zJGNwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4ZGVwdGggPSBiZXN0LnBhcmFtcyRtYXhkZXB0aCksDQogIA0KICBpbXBvcnRhbmNlID0gVFJVRQ0KKQ0KDQojIEV2YWx1YXRlIG9uIHRlc3Qgc2V0DQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGZpbmFsLm1vZGVsLCBuZXdkYXRhID0gdGVzdC5kYXRhNSkNCiMjIFVzaW5nIHRoZSBjYXJldCBmdW5jdGlvbiB0byBjYWxjdWxhdGUgZXJyb3JzIGFjcm9zcyByZS1zYW1wbGVzDQpiYWdnZWRFcnJvciA8LSBwb3N0UmVzYW1wbGUocHJlZCA9IHByZWRpY3Rpb25zLCBvYnMgPSB0ZXN0LmRhdGE1JGV4YW1fc2NvcmUpDQpwYW5kZXIoYmFnZ2VkRXJyb3IpDQpgYGANCg0KQmFzZWQgb24gdGhlIGJhZ2dpbmcgbW9kZWwsIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlIGNoYXJ0IHNob3duIGJlbG93IHNob3dzIHRoYXQgc3R1ZHkgaG91cnMsIG1lbnRhbCBoZWFsdGgsIG5ldGZsaXggaG91cnMsIHNvY2lhbCBtZWRpYSBob3VycywgYW5kIHNsZWVwIGhvdXJzIGFyZSB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzIGluIHByZWRpY3RpbmcgZXhhbSBzY29yZS4NCg0KYGBge3J9DQp2YXIuaW1wIDwtIHZhckltcChmaW5hbC5tb2RlbCkNCiMgRXh0cmFjdCB2YXJpYWJsZSBpbXBvcnRhbmNlIChyZXF1aXJlcyBhIGN1c3RvbSBmdW5jdGlvbikNCmdldC5iYWdnaW5nLmltcG9ydGFuY2UgPC0gZnVuY3Rpb24obW9kZWwpIHsNCiAgIyBHZXQgYWxsIHRoZSB0cmVlcyBmcm9tIHRoZSBiYWdnaW5nIG1vZGVsDQogIHRyZWVzIDwtIG1vZGVsJG10cmVlcw0KICANCiAgIyBJbml0aWFsaXplIGltcG9ydGFuY2UgdmVjdG9yDQogIGltcCA8LSBudW1lcmljKGxlbmd0aCh0cmVlc1tbMV1dJGJ0cmVlJHZhcmlhYmxlLmltcG9ydGFuY2UpKQ0KICBuYW1lcyhpbXApIDwtIG5hbWVzKHRyZWVzW1sxXV0kYnRyZWUkdmFyaWFibGUuaW1wb3J0YW5jZSkNCiAgDQogICMgU3VtIGltcG9ydGFuY2UgYWNyb3NzIGFsbCB0cmVlcw0KICBmb3IodHJlZSBpbiB0cmVlcykgew0KICAgIGltcFtuYW1lcyh0cmVlJGJ0cmVlJHZhcmlhYmxlLmltcG9ydGFuY2UpXSA8LSANCiAgICAgIGltcFtuYW1lcyh0cmVlJGJ0cmVlJHZhcmlhYmxlLmltcG9ydGFuY2UpXSArIA0KICAgICAgdHJlZSRidHJlZSR2YXJpYWJsZS5pbXBvcnRhbmNlDQogIH0NCiAgDQogICMgQXZlcmFnZSBpbXBvcnRhbmNlDQogIGltcCA8LSBpbXAvbGVuZ3RoKHRyZWVzKQ0KICByZXR1cm4oaW1wKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KIyBHZXQgaW1wb3J0YW5jZQ0KaW1wb3J0YW5jZS5zY29yZXMgPC0gZ2V0LmJhZ2dpbmcuaW1wb3J0YW5jZShmaW5hbC5tb2RlbCkNCg0KIyBTb3J0IGFuZCBwbG90DQppbXBvcnRhbmNlLnNjb3JlcyA8LSBzb3J0KGltcG9ydGFuY2Uuc2NvcmVzLCBkZWNyZWFzaW5nID0gVFJVRSkNCmJhcnBsb3QoaW1wb3J0YW5jZS5zY29yZXMsIGhvcml6ID0gVFJVRSwgbGFzID0gMSwNCiAgICAgICAgbWFpbiA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIC0gQmFnZ2luZyAoaXByZWQpIiwNCiAgICAgICAgeGxhYiA9ICJJbXBvcnRhbmNlIFNjb3JlIikNCmBgYA0KDQpCZWxvdyB3ZSBnZW5lcmF0ZSB0aGUgTUFFL1JNU0UgZm9yIHRoZSBiYWdnZWQgbW9kZWwsIHBydW5lZCB0cmVlIHVzaW5nIG1pbiBDUCwgYW5kIHN0YW5kYXJkIHJlZ3Jlc3Npb24uIEJhc2VkIG9uIHRoZSB2YWx1ZXMgc2hvd24gYmVsb3csIHRoZSBiYWdnZWQgbW9kZWwgZGlkIG5vdCBkbyBhcyB3ZWxsIGFzIHRoZSBzdGFuZGFyZCByZWdyZXNzaW9uLg0KDQpgYGB7cn0NCiMjIG9yZGluYXJ5IExTRSByZWdyZXNzaW9uIG1vZGVsIHdpdGggc3RlcC13aXNlIHZhcmlhYmxlIHNlbGVjdGlvbg0KbHNlLmZpdCA8LSBsbShleGFtX3Njb3JlIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwgZGF0YSA9IHRyYWluLmRhdGE1KQ0KQUlDLmZpdDIgPC0gc3RlcEFJQyhsc2UuZml0LCBkaXJlY3Rpb249ImJvdGgiLCB0cmFjZSA9IEZBTFNFKQ0KIyMNCnByZWQubHNlIDwtIHByZWRpY3QoQUlDLmZpdDIsIHRlc3QuZGF0YTUpDQptYWUubHNlIDwtIG1lYW4oYWJzKHRlc3QuZGF0YTUkZXhhbV9zY29yZSAtIHByZWQubHNlKSkgIyBtZWFuIGFic29sdXRlc3F1YXJlIGVycm9yDQptc2UubHNlIDwtIG1lYW4oKHRlc3QuZGF0YTUkZXhhbV9zY29yZSAtIHByZWQubHNlKV4yKSAgIyBtZWFuIHNxdWFyZSBlcnJvcg0Kcm1zZS5sc2UgPC0gc3FydChtc2UubHNlKSAgICAgICAgICAgICAgICAgICAgICAgIyByb290IG1lYW4gc3F1YXJlIGVycm9yDQpyLnNxdWFyZWQubHNlIDwtIChjb3IodGVzdC5kYXRhNSRleGFtX3Njb3JlLCBwcmVkLmxzZSkpXjIgIyByLXNxdWFyZWQNCiMjDQojIEJhc2UgcmVncmVzc2lvbiB0cmVlDQp0cmVlLm1vZGVsMyA8LSBycGFydChleGFtX3Njb3JlIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwgDQogICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbi5kYXRhNSwNCiAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImFub3ZhIiwgICAgICMgRm9yIHJlZ3Jlc3Npb24NCiAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgbWluc3BsaXQgPSAyMCwgICAgIyAzLiBTdG9wcGluZyBydWxlOiBtaW4gb2JzZXJ2YXRpb25zIHRvIHNwbGl0DQogICAgICAgICAgICAgICAgICAgICAgbWluYnVja2V0ID0gNywgICAgIyBNaW4gb2JzZXJ2YXRpb25zIGluIHRlcm1pbmFsIG5vZGUNCiAgICAgICAgICAgICAgICAgICAgICBjcCA9IHNlcSgwLCAwLjA1LCAyMCksICMgQ29tcGxleGl0eSBwYXJhbWV0ZXINCiAgICAgICAgICAgICAgICAgICAgICBtYXhkZXB0aCA9IDUgICAgICAjIE1heGltdW0gdHJlZSBkZXB0aA0KICAgICAgICAgICAgICAgICAgICApKQ0KIyBjcCB0YWJsZQ0KY3AudGFibGUzIDwtIHRyZWUubW9kZWwzJGNwdGFibGUNCiMjDQojIyBJZGVudGlmeSB0aGUgbWluaW11bSBgeGVycm9yYCBhbmQgaXRzIGBjcGAuDQptaW4ueGVycm9yMyA8LSBtaW4oY3AudGFibGUzWywgInhlcnJvciJdKQ0KbWluLmNwLnJvdzMgPC0gd2hpY2gubWluKGNwLnRhYmxlM1ssICJ4ZXJyb3IiXSkNCm1pbi5jcDMgPC0gY3AudGFibGUzW21pbi5jcC5yb3czLCAiQ1AiXQ0KIyMNCnBydW5lZC50cmVlLm1pbi5jcDMgPC0gcHJ1bmUodHJlZS5tb2RlbDMsIGNwID0gbWluLmNwMykNCnByZWQubWluLmNwMyA8LSBwcmVkaWN0KHBydW5lZC50cmVlLm1pbi5jcDMsIG5ld2RhdGEgPSB0ZXN0LmRhdGE1KQ0KIyMNCiMgbWluLmNwDQptYWUudHJlZS5taW4uY3AzIDwtIG1lYW4oYWJzKHRlc3QuZGF0YTUkZXhhbV9zY29yZSAtIHByZWQubWluLmNwMykpDQptc2UudHJlZS5taW4uY3AzIDwtIG1lYW4oKHRlc3QuZGF0YTUkZXhhbV9zY29yZSAtIHByZWQubWluLmNwMyleMikNCnJtc2UudHJlZS5taW4uY3AzIDwtIHNxcnQobXNlLnRyZWUubWluLmNwMykNCnIuc3F1YXJlZC50cmVlLm1pbi5jcDMgPC0gY29yKHRlc3QuZGF0YTUkZXhhbV9zY29yZSwgcHJlZC5taW4uY3AzKV4yDQojIw0KIyMjDQpFcnJvcnMyIDwtIGNiaW5kKE1BRSA9IGMoYmFnZ2VkRXJyb3JbMV0sIG1hZS50cmVlLm1pbi5jcDMsIG1hZS5sc2UpLA0KICAgICAgICAgIFJNU0UgPSBjKGJhZ2dlZEVycm9yWzNdLCBybXNlLnRyZWUubWluLmNwMywgcm1zZS5sc2UpLA0KICAgICAgICAgIHIuc3F1YXJlZCA9IGMoYmFnZ2VkRXJyb3JbMl0sIHIuc3F1YXJlZC50cmVlLm1pbi5jcDMsIHIuc3F1YXJlZC5sc2UpKQ0Kcm93bmFtZXMoRXJyb3JzMikgPSBjKCJiYWdnZWQgVHJlZSIsICJ0cmVlLm1pbi5jcCIsICJsc2UiKQ0KcGFuZGVyKEVycm9yczIpDQpgYGANCg0KIyBCQUdHSU5HIENsYXNzaWZpY2F0aW9uDQoNCkluIHRoaXMgc2VjdGlvbiwgd2UgZG8gdGhlIGJhZ2dpbmcgY2xhc3NpZmljYXRpb24gbW9kZWwuDQoNCkJlbG93IHNob3dzIHRoZSBvcHRpbWFsIGh5cGVycGFyYW1ldGVycyBmb3IgbmJhZ2csIG1pbnNwbGl0LCBtYXhkZXB0aCwgYW5kIGNwLiBXZSB1c2UgdGhlc2UgdG8gdHJhaW4gdGhlIGZpbmFsIG1vZGVsLg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCg0KIyBTcGxpdCBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cw0KdHJhaW5JbmRleDYgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihjb21wbGV0ZV9oYWJpdHNfZGF0YSRwYXNzLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQp0cmFpbkRhdGE2IDwtIGNvbXBsZXRlX2hhYml0c19kYXRhW3RyYWluSW5kZXg2LCBdDQp0ZXN0RGF0YTYgPC0gY29tcGxldGVfaGFiaXRzX2RhdGFbLXRyYWluSW5kZXg2LCBdDQoNCiMgQ3JlYXRlIGEgZ3JpZCBvZiBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbnMNCmh5cGVyLmdyaWQgPC0gZXhwYW5kLmdyaWQoDQogIG5iYWdnID0gYygyNSwgNTAsIDEwMCksDQogIG1pbnNwbGl0ID0gYyg1LCAxMCwgMjApLA0KICBtYXhkZXB0aCA9IGMoNSwgMTAsIDIwKSwNCiAgY3AgPSBjKDAuMDEsIDAuMDAxKQ0KKQ0KIyBJbml0aWFsaXplIGEgcmVzdWx0cyBkYXRhIGZyYW1lDQpyZXN1bHRzMiA8LSBkYXRhLmZyYW1lKCkgIyBzdG9yZSB2YWx1ZXMgb2YgdHVuZWQgaHlwZXJwYXJhbWV0ZXJzDQpiZXN0LmFjY3VyYWN5IDwtIDAgICAgICAjIHN0b3JlIGFjY3VyYWN5DQpiZXN0LnBhcmFtczIgPC0gbGlzdCgpICAgIyBzdG9yZSBiZXN0IHZhbHVlcyBvZiBoeXBlcnBhcmFtZXRlcg0KDQp0ZXN0RGF0YTYkcGFzcyA8LSBhcy5mYWN0b3IodGVzdERhdGE2JHBhc3MpDQp0cmFpbkRhdGE2JHBhc3MgPC0gYXMuZmFjdG9yKHRyYWluRGF0YTYkcGFzcykNCg0KIyBMb29wIHRocm91Z2ggZWFjaCBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbg0KZm9yKGkgaW4gMTpucm93KGh5cGVyLmdyaWQpKSB7DQogICMgR2V0IGN1cnJlbnQgaHlwZXJwYXJhbWV0ZXJzDQogIHBhcmFtcyA8LSBoeXBlci5ncmlkW2ksIF0NCiAgDQogICMgU2V0IHJwYXJ0IGNvbnRyb2wgcGFyYW1ldGVycw0KICBycGFydC5jb250cm9sIDwtIHJwYXJ0LmNvbnRyb2woDQogICAgbWluc3BsaXQgPSBwYXJhbXMkbWluc3BsaXQsDQogICAgbWF4ZGVwdGggPSBwYXJhbXMkbWF4ZGVwdGgsDQogICAgY3AgPSBwYXJhbXMkY3ANCiAgKQ0KICANCiAgIyBUcmFpbiBiYWdnaW5nIG1vZGVsDQogIGJhZy5tb2RlbCA8LSBiYWdnaW5nKA0KICAgIHBhc3MgfiBhZ2UgKyBnZW5kZXIgKyBzdHVkeV9ob3Vyc19wZXJfZGF5ICsgc29jaWFsX21lZGlhX2hvdXJzICsgbmV0ZmxpeF9ob3VycyArIHBhcnRfdGltZV9qb2IgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uLA0KICAgIGRhdGEgPSB0cmFpbkRhdGE2LA0KICAgIG5iYWdnID0gcGFyYW1zJG5iYWdnLA0KICAgIGNvb2IgPSBUUlVFLA0KICAgIGNvbnRyb2wgPSBycGFydC5jb250cm9sDQogICkNCiAgDQogICMgTWFrZSBwcmVkaWN0aW9uczogZGVmYXVsdCBjdXQtb2ZmIDAuNQ0KICBwcmVkcyA8LSBwcmVkaWN0KGJhZy5tb2RlbCwgbmV3ZGF0YSA9IHRlc3REYXRhNikNCiAgDQogICMgQ2FsY3VsYXRlIGFjY3VyYWN5DQogIGNtIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkcywgdGVzdERhdGE2JHBhc3MpDQogIGFjY3VyYWN5IDwtIGNtJG92ZXJhbGxbIkFjY3VyYWN5Il0NCiAgDQogICMgU3RvcmUgcmVzdWx0cw0KICByZXN1bHRzMiA8LSByYmluZChyZXN1bHRzMiwgZGF0YS5mcmFtZSgNCiAgICBuYmFnZyA9IHBhcmFtcyRuYmFnZywNCiAgICBtaW5zcGxpdCA9IHBhcmFtcyRtaW5zcGxpdCwNCiAgICBtYXhkZXB0aCA9IHBhcmFtcyRtYXhkZXB0aCwNCiAgICBjcCA9IHBhcmFtcyRjcCwNCiAgICBBY2N1cmFjeSA9IGFjY3VyYWN5DQogICkpDQogIA0KICAjIFVwZGF0ZSBiZXN0IHBhcmFtZXRlcnMgaWYgY3VycmVudCBtb2RlbCBpcyBiZXR0ZXINCiAgaWYoYWNjdXJhY3kgPiBiZXN0LmFjY3VyYWN5KSB7DQogICAgYmVzdC5hY2N1cmFjeSA8LSBhY2N1cmFjeQ0KICAgIGJlc3QucGFyYW1zIDwtIHBhcmFtcw0KICB9DQogIA0KICAjIFByaW50IHByb2dyZXNzDQogICNjYXQoIkNvbXBsZXRlZCIsIGksICJvZiIsIG5yb3coaHlwZXIuZ3JpZCksICJjb21iaW5hdGlvbnNcbiIpDQp9DQpwYW5kZXIoYmVzdC5wYXJhbXMpDQpgYGANCg0KQmVsb3cgd2UgdHJhaW4gdGhlIGZpbmFsIG1vZGVsIHVzaW5nIHRoZSBvcHRpbWFsIHBhcmFtZXRlcnMgYW5kIGdlbmVyYXRlIHRoZSBjb25mdXNpb24gbWF0cml4Lg0KDQpgYGB7cn0NCiMgU2V0IHJwYXJ0IGNvbnRyb2wgd2l0aCBiZXN0IHBhcmFtZXRlcnMNCmJlc3QuY29udHJvbCA8LSBycGFydC5jb250cm9sKA0KICBtaW5zcGxpdCA9IGJlc3QucGFyYW1zJG1pbnNwbGl0LA0KICBtYXhkZXB0aCA9IGJlc3QucGFyYW1zJG1heGRlcHRoLA0KICBjcCA9IGJlc3QucGFyYW1zJGNwDQopDQoNCiMgVHJhaW4gZmluYWwgbW9kZWwNCmZpbmFsLmJhZy5tb2RlbCA8LSBiYWdnaW5nKA0KICBwYXNzIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwNCiAgZGF0YSA9IHRyYWluRGF0YTYsDQogIG5iYWdnID0gYmVzdC5wYXJhbXMkbmJhZ2csDQogIGNvb2IgPSBUUlVFLA0KICBjb250cm9sID0gYmVzdC5jb250cm9sDQopDQoNCiMgRXZhbHVhdGUgb24gdGVzdCBzZXQNCmZpbmFsLnByZWRzIDwtIHByZWRpY3QoZmluYWwuYmFnLm1vZGVsLCBuZXdkYXRhID0gdGVzdERhdGE2KQ0KZmluYWwuY20gPC0gY29uZnVzaW9uTWF0cml4KGZpbmFsLnByZWRzLCB0ZXN0RGF0YTYkcGFzcykNCmZpbmFsLmNtJHRhYmxlDQpgYGANCg0KQmVsb3cgd2UgZ2VuZXJhdGUgdGhlIFJPQyBjdXJ2ZXMgZm9yIHN0YW5kYXJkIGxvZ2lzdGljLCBiYWdnZWQgbW9kZWwsIGFuZCB0aGUgcHJ1bmVkIHRyZWUgd2l0aCBtaW4gQ1AuIEJhc2VkIG9uIHRoZSBiZWxvdyBwbG90LCB3ZSBzZWUgdGhhdCB0aGUgc3RhbmRhcmQgbG9naXN0aWMgcGVyZm9ybXMgYmV0dGVyIHRoYW4gYm90aCB0aGUgYmFnZ2VkIG1vZGVsIGFuZCB0aGUgcHJ1bmVkIHRyZWUgd2l0aCBtaW4gQ1AuDQoNCmBgYHtyfQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIyMgIGxvZ2lzdGljIHJlZ3Jlc3Npb24NCmxvZ2l0LmZpdDIgPC0gZ2xtKHBhc3MgfiBhZ2UgKyBnZW5kZXIgKyBzdHVkeV9ob3Vyc19wZXJfZGF5ICsgc29jaWFsX21lZGlhX2hvdXJzICsgbmV0ZmxpeF9ob3VycyArIHBhcnRfdGltZV9qb2IgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uLCBkYXRhID0gdHJhaW5EYXRhNiwgZmFtaWx5ID0gYmlub21pYWwpDQpBSUMubG9naXQyIDwtIHN0ZXAobG9naXQuZml0MiwgZGlyZWN0aW9uID0gImJvdGgiLCB0cmFjZSA9IDApDQpwcmVkLmxvZ2l0MiA8LSBwcmVkaWN0KEFJQy5sb2dpdDIsIHRlc3REYXRhNiwgdHlwZSA9ICJyZXNwb25zZSIpDQojIw0KIyBCdWlsZCB0aGUgaW5pdGlhbCBjbGFzc2lmaWNhdGlvbiB0cmVlDQp0cmVlLm1vZGVsNCA8LSBycGFydChwYXNzIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwgDQogICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbkRhdGE2LA0KICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2xhc3MiLCAgICMgY2xhc3NpZmljYXRpb24gdHJlZQ0KICAgICAgICAgICAgICAgICAgICBwYXJtcyA9IGxpc3Qoc3BsaXQgPSAiZ2luaSIsICAjIFVzaW5nIEdpbmkgaW5kZXgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRk4gY29zdCA9IDEsIEZQIGNvc3QgPSAwLjUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvc3MgPSBtYXRyaXgoYygwLCAwLjUsIDEsIDApLCBucm93ID0gMikgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwNCiAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAxNSwgICMgTWluIDE1IG9icyB0byBzcGxpdA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbmJ1Y2tldCA9IDUsICAgIyBNaW4gNyBvYnMgaW4gbGVhZg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ29tcGxleGl0eSBwYXJhbWV0ZXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjcCA9IDAuMDAxLCAjIGNvbXBsZXggcGFyYW1ldGVyDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4ZGVwdGggPSA1KSkgICAjIE1heCB0cmVlIGRlcHRoDQojIyMNCm1pbi5jcDQgPC0gdHJlZS5tb2RlbDQkY3B0YWJsZVt3aGljaC5taW4odHJlZS5tb2RlbDQkY3B0YWJsZVssInhlcnJvciJdKSwiQ1AiXQ0KDQojIFByZWRpY3Rpb24gd2l0aCB0aHJlZSBjYW5kaWRhdGUgbW9kZWxzDQpwcnVuZWQudHJlZS5taW4gPC0gcHJ1bmUodHJlZS5tb2RlbDQsIGNwID0gbWluLmNwKQ0KcHJlZC5wcm9iLm1pbjIgPC0gcHJlZGljdChwcnVuZWQudHJlZS5taW4sIHRlc3REYXRhNiwgdHlwZSA9ICJwcm9iIilbLDJdDQpwcmVkLnByb2IuYmFnZyA8LSBwcmVkaWN0KGZpbmFsLmJhZy5tb2RlbCwgbmV3ZGF0YSA9IHRlc3REYXRhNiwgdHlwZSA9ICJwcm9iIilbLDJdDQojIw0KIyBST0Mgb2JqZWN0DQpyb2MudHJlZS5taW4yIDwtIHJvYyh0ZXN0RGF0YTYkcGFzcywgcHJlZC5wcm9iLm1pbjIpDQpyb2MubG9naXQyIDwtIHJvYyh0ZXN0RGF0YTYkcGFzcywgcHJlZC5sb2dpdDIpDQpyb2MuYmFnZyA8LSByb2ModGVzdERhdGE2JHBhc3MsIHByZWQucHJvYi5iYWdnKQ0KIyMNCiMjDQojIyMgU2VuLVNwZQ0KYmFnZy5zZW4gPC0gcm9jLmJhZ2ckc2Vuc2l0aXZpdGllcw0KYmFnZy5zcGUgPC0gcm9jLmJhZ2ckc3BlY2lmaWNpdGllcw0KIw0KdHJlZS5taW4uc2VuMiA8LSByb2MudHJlZS5taW4yJHNlbnNpdGl2aXRpZXMNCnRyZWUubWluLnNwZTIgPC0gcm9jLnRyZWUubWluMiRzcGVjaWZpY2l0aWVzDQojDQpsb2dpdC5zZW4yIDwtIHJvYy5sb2dpdDIkc2Vuc2l0aXZpdGllcw0KbG9naXQuc3BlMiA8LSByb2MubG9naXQyJHNwZWNpZmljaXRpZXMNCiMjIEFVQw0KYXVjLmJhZ2cgPC0gcm9jLmJhZ2ckYXVjDQphdWMudHJlZS5taW4yIDwtIHJvYy50cmVlLm1pbjIkYXVjDQphdWMubG9naXQyIDwtIHJvYy5sb2dpdDIkYXVjDQojIyMNCnBsb3QoMS1sb2dpdC5zcGUyLCBsb2dpdC5zZW4yLCAgDQogICAgIHhsYWIgPSAiMSAtIHNwZWNpZmljaXR5IiwNCiAgICAgeWxhYiA9ICJzZW5zaXRpdml0eSIsDQogICAgIGNvbCA9ICJkYXJrcmVkIiwNCiAgICAgdHlwZSA9ICJsIiwNCiAgICAgbHR5ID0gMSwNCiAgICAgbHdkID0gMSwNCiAgICAgbWFpbiA9ICJST0M6IENsYXNzaWZpY2F0aW9uIE1vZGVscyIpDQpsaW5lcygxLWJhZ2cuc3BlLCBiYWdnLnNlbiwgDQogICAgICBjb2wgPSAiYmx1ZSIsDQogICAgICBsdHkgPSAxLA0KICAgICAgbHdkID0gMSkNCmxpbmVzKDEtdHJlZS5taW4uc3BlMiwgdHJlZS5taW4uc2VuMiwgICAgICANCiAgICAgIGNvbCA9ICJvcmFuZ2UiLA0KICAgICAgbHR5ID0gMSwNCiAgICAgIGx3ZCA9IDEpDQphYmxpbmUoMCwxLCBjb2wgPSAic2t5Ymx1ZTMiLCBsdHkgPSAyLCBsd2QgPSAyKQ0KbGVnZW5kKCJib3R0b21yaWdodCIsIGMoIkxvZ2lzdGljIiwgImJhZ2ciLCAiVHJlZSBNaW4iKSwNCiAgICAgICBsdHkgPSBjKDEsMSwxKSwgbHdkID0gcmVwKDEsMyksDQogICAgICAgY29sID0gYygicmVkIiwgImJsdWUiLCAib3JhbmdlIiksDQogICAgICAgYnR5PSJuIixjZXggPSAwLjgpDQojIyBhbm5vdGF0aW9uIC0gQVVDDQp0ZXh0KDAuOCwgMC40NiwgcGFzdGUoIkxvZ2lzdGljIEFVQzogIiwgcm91bmQoYXVjLmxvZ2l0Miw0KSksIGNleCA9IDAuOCkNCnRleHQoMC44LCAwLjQsIHBhc3RlKCJCYWdnIEFVQzogIiwgcm91bmQoYXVjLmJhZ2csNCkpLCBjZXggPSAwLjgpDQp0ZXh0KDAuOCwgMC4zNCwgcGFzdGUoIlRyZWUgTWluIEFVQzogIiwgcm91bmQoYXVjLnRyZWUubWluMiw0KSksIGNleCA9IDAuOCkNCmBgYA0KDQojIFJhbmRvbSBGb3Jlc3QgUmVncmVzc2lvbg0KDQpJbiB0aGlzIHNlY3Rpb24sIHdlIGRvIHJhbmRvbSBmb3Jlc3QuIFJhbmRvbSBmb3Jlc3QgaXMgc2ltaWxhciB0byBiYWdnaW5nLCBleGNlcHQgdGhhdCBhdCBlYWNoIHNwbGl0LCBvbmx5IGEgcmFuZG9tIHN1YnNldCBvZiBmZWF0dXJlcyBpcyB1c2VkIHdoZXJlYXMgYWxsIGZlYXR1cmVzIGFyZSBjb25zaWRlcmVkIGluIGJhZ2dpbmcuDQoNCkJlbG93IHdlIHNwbGl0IHRoZSBkYXRhIGludG8gdHJhaW5pbmcvdGVzdGluZyBhbmQgZ2VuZXJhdGUgdGhlIG9wdGltYWwgaHlwZXJwYXJhbWV0ZXJzIHNob3duIGluIHRoZSB0YWJsZSAobnRyZWUsIG10cnksIG5vZGVzaXplLCBtYXhub2RlcywgYW5kIGJlc3Qucm1zZSkuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKSAgIyBGb3IgcmVwcm9kdWNpYmlsaXR5DQp0cmFpbi5pbmRleDcgPC0gc2FtcGxlKDE6bnJvdyhjb21wbGV0ZV9oYWJpdHNfZGF0YSksIDAuOCAqIG5yb3coY29tcGxldGVfaGFiaXRzX2RhdGEpKQ0KdHJhaW4uZGF0YTcgPC0gY29tcGxldGVfaGFiaXRzX2RhdGFbdHJhaW4uaW5kZXg3LCBdDQp0ZXN0LmRhdGE3PC0gY29tcGxldGVfaGFiaXRzX2RhdGFbLXRyYWluLmluZGV4NywgXQ0KYGBgDQoNCmBgYHtyfQ0KIyBDcmVhdGUgYSBncmlkIChkYXRhIGZyYW1lKSBvZiBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbnMgdG8gdGVzdA0KaHlwZXIuZ3JpZDIgPC0gZXhwYW5kLmdyaWQoDQogIG50cmVlID0gYygxMDAsIDMwMCwgNTAwKSwgICAgDQogIG10cnkgPSBjKDMsIDUsIDcpLCAjIERlcGVuZGVudCBvbiB0aGUgdG90YWwgbnVtYmVyIGZlYXR1cmVzIGF2YWlsYWJsZSBpbiB0aGUgZGF0YQ0KICBub2Rlc2l6ZSA9IGMoMSwgMywgNSksIA0KICBtYXhub2RlcyA9IGMoNSwgMTAsIDIwLCBOVUxMKQ0KKQ0KIyMNCiMgSW5pdGlhbGl6ZSByZXN1bHRzIHN0b3JhZ2UNCnJlc3VsdHMzIDwtIGRhdGEuZnJhbWUoKSAgIyBjb21iaW5hdGlvbiBvZiBoeXBlcnBhcmFtZXRlcnMgYW5kIGNvcnJlc3BvbmRpbmcgUk1TRQ0KYmVzdC5ybXNlIDwtIEluZiAgICAgICAgICMgcGxhY2UtaG9sZGVyIG9mIFJNU0Ugd2l0aCBpbml0aWFsIHZhbHVlIGluZg0KYmVzdC5wYXJhbXMzIDwtIGxpc3QoKSAgICAjIHVwZGF0ZSB0aGUgaHlwZXJwYXJhbWV0ZXIgbGlzdCBhY2NvcmRpbmcgdG8gdGhlIGJlc3Qgcm1zZQ0KDQojIFNldCB1cCBrLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbg0KayA8LSA1ICAgICAgICAgICAgICAgICAgICMgNS1mb2xkIGNyb3NzLXZhbGlkYXRpb24NCm4wIDwtIGRpbSh0cmFpbi5kYXRhNylbMV0gIyBzaXplIG9mIHRoZSB0cmFpbmluZyBkYXRhIA0KZm9sZC5zaXplIDwtIGZsb29yKG4wL2spICMgZm9sZCBzaXplLiBDYXV0aW9uOiBmbG9vcigpIHNob3VsZCBiZSB1c2VkLiANCiAgICAgICAgICAgICAgICAgICAgICAgICAjIHJvdW5kKCAsMCkgc2hvdWxkIGJlIHVzZWQuIHdoeT8NCg0KIyBMb29wIHRocm91Z2ggZWFjaCBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbg0KZm9yKGkgaW4gMTpucm93KGh5cGVyLmdyaWQyKSkgeyAgICAgICAgICAgDQogIGN1cnJlbnQucGFyYW1zIDwtIGh5cGVyLmdyaWQyW2ksIF0gICAjIHZlY3RvciBvZiBoeXBlcnBhcmFtZXRlcnMgZm9yIGNyb3NzIHZhbGlkYXRpb24NCiAgY3YuZXJyb3JzIDwtIG51bWVyaWMoaykgICAgICAgICAgICAgIyBzdG9yZSBSTVNFIGZyb20gY3Jvc3MtdmFsaWRhdGlvbg0KICANCiAgIyBQZXJmb3JtIGstZm9sZCBjcm9zcy12YWxpZGF0aW9uDQogIGZvcihqIGluIDE6aykgew0KICAgICMgU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBmb2xkcw0KICAgIHZhbGlkLmluZGljZXMgPC0gKDEgKyBmb2xkLnNpemUqKGotMSkpOihmb2xkLnNpemUqaikgICMgQ1Ygb2JzZXJ2YXRpb24gSUQgdmVjdG9yDQogICAgY3YudHJhaW4gPC0gdHJhaW4uZGF0YTdbLXZhbGlkLmluZGljZXMsIF0gICAjIHRyYWluaW5nIGRhdGEgaW4gY3Jvc3MtdmFsaWRhdGlvbg0KICAgIGN2LnZhbGlkIDwtIHRyYWluLmRhdGE3W3ZhbGlkLmluZGljZXMsIF0gICAgIyB0ZXN0aW5nIGRhdGEgaW4gY3Jvc3MtdmFsaWRhdGlvbg0KICAgIA0KICAgICMgVHJhaW4gbW9kZWwgd2l0aCBjdXJyZW50IHBhcmFtZXRlcnMNCiAgICByZi5tb2RlbCA8LSByYW5kb21Gb3Jlc3QoDQogICAgICBleGFtX3Njb3JlIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwNCiAgICAgIGRhdGEgPSBjdi50cmFpbiwNCiAgICAgIG50cmVlID0gY3VycmVudC5wYXJhbXMkbnRyZWUsDQogICAgICBtdHJ5ID0gY3VycmVudC5wYXJhbXMkbXRyeSwNCiAgICAgIG5vZGVzaXplID0gY3VycmVudC5wYXJhbXMkbm9kZXNpemUsDQogICAgICBtYXhub2RlcyA9IGN1cnJlbnQucGFyYW1zJG1heG5vZGVzLA0KICAgICAgaW1wb3J0YW5jZSA9IFRSVUUNCiAgICApDQogICAgDQogICAgIyBNYWtlIHByZWRpY3Rpb25zIG9uIHZhbGlkYXRpb24gc2V0DQogICAgcHJlZHMyIDwtIHByZWRpY3QocmYubW9kZWwsIG5ld2RhdGEgPSBjdi52YWxpZCkgICAgDQogICAgDQogICAgIyBDYWxjdWxhdGUgUk1TRQ0KICAgIGN2LmVycm9yc1tqXSA8LSBzcXJ0KG1lYW4oKHByZWRzMiAtIGN2LnZhbGlkJGV4YW1fc2NvcmUpXjIpKQ0KICB9DQogIA0KICAjIEF2ZXJhZ2UgUk1TRSBhY3Jvc3MgZm9sZHMNCiAgYXZnLnJtc2UgPC0gbWVhbihjdi5lcnJvcnMpICAgIA0KICANCiAgIyBTdG9yZSByZXN1bHRzOiB0aGUgZGF0YSBmcmFtZSBkZWZpbmVkIHRvIHN0b3JlIGNvbWJpbmF0aW9ucyBvZiBoeXBlcnBhcmFtZXRlcnMNCiAgIyBhbmQgdGhlIHJlc3VsdGluZyBtZWFuIFJNU0VzIGZyb20gY3Jvc3MtdmFsaWRhdGlvbg0KICByZXN1bHRzMyA8LSByYmluZChyZXN1bHRzMywgZGF0YS5mcmFtZSgNCiAgICBudHJlZSA9IGN1cnJlbnQucGFyYW1zJG50cmVlLA0KICAgIG10cnkgPSBjdXJyZW50LnBhcmFtcyRtdHJ5LA0KICAgIG5vZGVzaXplID0gY3VycmVudC5wYXJhbXMkbm9kZXNpemUsDQogICAgbWF4bm9kZXMgPSBpZmVsc2UoaXMubnVsbChjdXJyZW50LnBhcmFtcyRtYXhub2RlcyksICJOVUxMIiwgY3VycmVudC5wYXJhbXMkbWF4bm9kZXMpLA0KICAgIHJtc2UgPSBhdmcucm1zZQ0KICApKQ0KICANCiAgIyBVcGRhdGUgYmVzdCBwYXJhbWV0ZXJzIGlmIGN1cnJlbnQgbW9kZWwgaXMgYmV0dGVyDQogIGlmKGF2Zy5ybXNlIDwgYmVzdC5ybXNlKSB7DQogICAgYmVzdC5ybXNlIDwtIGF2Zy5ybXNlDQogICAgYmVzdC5wYXJhbXMgPC0gY3VycmVudC5wYXJhbXMNCiAgfQ0KICAjIFByaW50IHByb2dyZXNzOiBJdCBpcyBhbHdheXMgYSBnb29kIGlkZWEgdG8gcHJpbnQgc29tZXRoaW5nIG91dCBpbiBsb29wcw0KICAjIGNhdChwYXN0ZTAoIkNvbXBsZXRlZCAiLCBpLCAiLyIsIG5yb3coaHlwZXIuZ3JpZCksICIgLSBSTVNFOiAiLCByb3VuZChhdmcucm1zZSwgNCksICJcbiIpKQ0KfSAgDQpwYW5kZXIoZGF0YS5mcmFtZShjYmluZChjdXJyZW50LnBhcmFtcyxiZXN0LnJtc2UpKSkgICAgIyByZXN1bHRpbmcgdHVuZWQgaHlwZXJwYXJhbWV0ZXJzDQpgYGANCg0KQmVsb3cgd2UgdHJhaW4gdGhlIGZpbmFsIG1vZGVsIHVzaW5nIHRoZSBvcHRpbWFsIGh5cGVycGFyYW1ldGVycyBnZW5lcmF0ZWQgaW4gdGhlIGFib3ZlIHNlY3Rpb24gYW5kIG1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRlc3Qgc2V0LiBUaGUgc2NhdHRlcnBsb3QgYmVsb3cgc2hvd3MgdGhlIGFjdHVhbCB2cyBwcmVkaWN0ZWQgVmFsdWVzIG9mIHRoZSBleGFtIHNjb3JlcyBvZiB0aGUgdGVzdCBzZXQuDQoNCmBgYHtyfQ0KIyBUcmFpbiBmaW5hbCBtb2RlbCB3aXRoIGJlc3QgcGFyYW1ldGVycyBvbiBmdWxsIHRyYWluaW5nIHNldA0KZmluYWwucmYgPC0gcmFuZG9tRm9yZXN0KA0KICBleGFtX3Njb3JlIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwNCiAgZGF0YSA9IHRyYWluLmRhdGE3LA0KICBudHJlZSA9IGJlc3QucGFyYW1zJG50cmVlLA0KICBtdHJ5ID0gYmVzdC5wYXJhbXMkbXRyeSwNCiAgbm9kZXNpemUgPSBiZXN0LnBhcmFtcyRub2Rlc2l6ZSwNCiAgbWF4bm9kZXMgPSBiZXN0LnBhcmFtcyRtYXhub2RlcywNCiAgaW1wb3J0YW5jZSA9IFRSVUUNCikNCg0KIyBWaWV3IG1vZGVsIHN1bW1hcnkNCiMgcHJpbnQoZmluYWwucmYpDQojIE1ha2UgcHJlZGljdGlvbnMgb24gdGVzdCBzZXQNCnRlc3QucHJlZHMgPC0gcHJlZGljdChmaW5hbC5yZiwgbmV3ZGF0YSA9IHRlc3QuZGF0YTcpDQoNCiMgQ2FsY3VsYXRlIHRlc3QgUk1BRQ0KdGVzdC5tYWUgPC0gbWVhbihhYnModGVzdC5wcmVkcyAtIHRlc3QuZGF0YTckZXhhbV9zY29yZSkpDQojIENhbGN1bGF0ZSB0ZXN0IFJNU0UNCnRlc3Qucm1zZSA8LSBzcXJ0KG1lYW4oKHRlc3QucHJlZHMgLSB0ZXN0LmRhdGE3JGV4YW1fc2NvcmUpXjIpKQ0KIyBDYWxjdWxhdGUgUi1zcXVhcmVkDQp0ZXN0LnIyIDwtIDEgLSBzdW0oKHRlc3QuZGF0YTckZXhhbV9zY29yZSAtIHRlc3QucHJlZHMpXjIpIC8gc3VtKCh0ZXN0LmRhdGE3JGV4YW1fc2NvcmUgLSBtZWFuKHRlc3QuZGF0YTckZXhhbV9zY29yZSkpXjIpDQojIGNhdCgiVGVzdCBSLXNxdWFyZWQ6IiwgdGVzdC5yMiwgIlxuIikNCg0KIyMjIFBlcmZvcm1hbmNlIHZlY3Rvcg0KUkYucGVyZm9ybWFuY2UgPSBjKHRlc3QubWFlLCB0ZXN0LnJtc2UsIHRlc3QucjIpDQoNCiMgUGxvdCBhY3R1YWwgdnMgcHJlZGljdGVkIHZhbHVlcw0KcGxvdC5kYXRhIDwtIGRhdGEuZnJhbWUoQWN0dWFsID0gdGVzdC5kYXRhNyRleGFtX3Njb3JlLCBQcmVkaWN0ZWQgPSB0ZXN0LnByZWRzKQ0KZ2dwbG90KHBsb3QuZGF0YSwgYWVzKHggPSBBY3R1YWwsIHkgPSBQcmVkaWN0ZWQpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIikgKw0KICBsYWJzKHRpdGxlID0gIkFjdHVhbCB2cyBQcmVkaWN0ZWQgVmFsdWVzIiwNCiAgICAgICB4ID0gIkFjdHVhbCBNZWRpYW4gVmFsdWUgKCQxMDAwcykiLA0KICAgICAgIHkgPSAiUHJlZGljdGVkIE1lZGlhbiBWYWx1ZSAoJDEwMDBzKSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KQmVsb3cgd2UgY29tcGFyZSB0aGUgcGVyZm9ybWFuY2VzIG9mIHN0YW5kYXJkIE9MUyByZWdyZXNzaW9uLCBiYXNlIHJlZ3Jlc3Npb24gdHJlZSwgYmFnZ2VkLCBhbmQgdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWxzLiBCYXNlZCBvbiB0aGUgUk1TRSB2YWx1ZXMgYmVsb3csIHdlIHNlZSB0aGF0IGJhc2UgdHJlZSwgYmFnZ2VkLCBhbmQgdGhlIHJhbmRvbSBmb3Jlc3QgcGVyZm9ybSBzaW1pbGFybHkuDQoNCmBgYHtyfQ0KIyMgb3JkaW5hcnkgTFNFIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCBzdGVwLXdpc2UgdmFyaWFibGUgc2VsZWN0aW9uDQpsc2UuZml0MiA8LSBsbShleGFtX3Njb3JlIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwgZGF0YSA9IHRyYWluLmRhdGE3KQ0KQUlDLmZpdDMgPC0gc3RlcEFJQyhsc2UuZml0MiwgZGlyZWN0aW9uPSJib3RoIiwgdHJhY2UgPSBGQUxTRSkNCnByZWQubHNlMyA8LSBwcmVkaWN0KEFJQy5maXQzLCB0ZXN0LmRhdGE3KQ0KbWFlLmxzZTMgPC0gbWVhbihhYnModGVzdC5kYXRhNyRleGFtX3Njb3JlIC0gcHJlZC5sc2UzKSkgICAgICAjIG1lYW4gYWJzb2x1dGUgZXJyb3INCm1zZS5sc2UzIDwtIG1lYW4oKHRlc3QuZGF0YTckZXhhbV9zY29yZSAtIHByZWQubHNlMyleMikgICAgICAgIyBtZWFuIHNxdWFyZSBlcnJvcg0Kcm1zZS5sc2UzIDwtIHNxcnQobXNlLmxzZTMpICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgcm9vdCBtZWFuIHNxdWFyZSBlcnJvcg0Kci5zcXVhcmVkLmxzZTMgPC0gKGNvcih0ZXN0LmRhdGE3JGV4YW1fc2NvcmUsIHByZWQubHNlMykpXjIgIyByLXNxdWFyZWQNCg0KIyMjIGJhc2UgcmVncmVzc2lvbiB0cmVlDQp0cmVlLm1vZGVsNSA8LSBycGFydChleGFtX3Njb3JlIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwgDQogICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbi5kYXRhNywNCiAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImFub3ZhIiwgICAgICMgRm9yIHJlZ3Jlc3Npb24NCiAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgbWluc3BsaXQgPSAyMCwgICAgIyAzLiBTdG9wcGluZyBydWxlOiBtaW4gb2JzZXJ2YXRpb25zIHRvIHNwbGl0DQogICAgICAgICAgICAgICAgICAgICAgbWluYnVja2V0ID0gNywgICAgIyBNaW4gb2JzZXJ2YXRpb25zIGluIHRlcm1pbmFsIG5vZGUNCiAgICAgICAgICAgICAgICAgICAgICBjcCA9IHNlcSgwLCAwLjA1LCAyMCksICMgQ29tcGxleGl0eSBwYXJhbWV0ZXINCiAgICAgICAgICAgICAgICAgICAgICBtYXhkZXB0aCA9IDUgICAgICAjIE1heGltdW0gdHJlZSBkZXB0aA0KICAgICAgICAgICAgICAgICAgICApKQ0KY3AudGFibGU0IDwtIHRyZWUubW9kZWw1JGNwdGFibGUNCm1pbi54ZXJyb3I0IDwtIG1pbihjcC50YWJsZTRbLCAieGVycm9yIl0pDQptaW4uY3Aucm93NCA8LSB3aGljaC5taW4oY3AudGFibGU0WywgInhlcnJvciJdKQ0KbWluLmNwNSA8LSBjcC50YWJsZVttaW4uY3Aucm93NCwgIkNQIl0NCiMjIA0KcHJ1bmVkLnRyZWUubWluLmNwNCA8LSBwcnVuZSh0cmVlLm1vZGVsNSwgY3AgPSBtaW4uY3A1KQ0KcHJlZC5taW4uY3A0IDwtIHByZWRpY3QocHJ1bmVkLnRyZWUubWluLmNwNCwgbmV3ZGF0YSA9IHRlc3QuZGF0YTcpDQojIHBlcmZvcm1hbmNlIG1lYXN1cmVzDQptYWUudHJlZS5taW4uY3A0IDwtIG1lYW4oYWJzKHRlc3QuZGF0YTckZXhhbV9zY29yZSAtIHByZWQubWluLmNwNCkpICAgIyBNQUQNCm1zZS50cmVlLm1pbi5jcDQgPC0gbWVhbigodGVzdC5kYXRhNyRleGFtX3Njb3JlIC0gcHJlZC5taW4uY3A0KV4yKQ0Kcm1zZS50cmVlLm1pbi5jcDQgPC0gc3FydChtc2UudHJlZS5taW4uY3A0KSAgICAgICAgICAgICAgICAgICAgIyBNU0UNCnIuc3F1YXJlZC50cmVlLm1pbi5jcDQgPC0gY29yKHRlc3QuZGF0YTckZXhhbV9zY29yZSwgcHJlZC5taW4uY3A0KV4yICAjIFIuc3ENCg0KIyMjIGJhZ2dpbmcgcmVncmVzc2lvbjogZnJvbSB0aGUgcHJldmlvdXMgc2VjdGlvbg0KQmFnZ1BlcmYgPC0gYmFnZ2VkRXJyb3IgDQoNCiMjIyBQZXJmb3JtYW5jZSBDb21wYXJpc29uIFRhYmxlDQpFcnJvcnM0IDwtIGNiaW5kKE1BRSA9IGMobWFlLmxzZTMsIG1hZS50cmVlLm1pbi5jcDQsIEJhZ2dQZXJmWzNdLCBSRi5wZXJmb3JtYW5jZVsxXSksDQogICAgICAgICAgICAgICAgUk1TRSA9IGMocm1zZS5sc2UzLCBybXNlLnRyZWUubWluLmNwNCwgQmFnZ1BlcmZbMV0sIFJGLnBlcmZvcm1hbmNlWzJdKSwNCiAgICAgICAgICAgICAgICByLnNxdWFyZWQgPSBjKHIuc3F1YXJlZC5sc2UzLCByLnNxdWFyZWQudHJlZS5taW4uY3A0LCBCYWdnUGVyZlsyXSxSRi5wZXJmb3JtYW5jZVszXSkpDQpyb3duYW1lcyhFcnJvcnM0KSA9IGMoIkxTIFJlZ3Jlc3Npb24iLCAiUmVncmVzc2lvbiBUcmVlIiwgIkJBR0dJTkciLCAiUmFuZG9tIEZvcmVzdCIpDQpwYW5kZXIoRXJyb3JzNCkNCmBgYA0KDQpUSGUgdmFyaWFibGUgaW1wb3J0YW5jZSBwbG90cyBzaG93biBiZWxvdyBhcmUgYmFzZWQgb24gdGhlIE1TRSBhbmQgbm9kZSBpbXB1cml0eS4gQWNjb3JkaW5nIHRvIHRoZSAlSW5jTVNFIHBsb3QsIHN0dWR5IGhvdXJzLCBtZW50YWwgaGVhbHRoLCBleGVyY2lzZSwgc29jaWFsIG1lZGlhLCBhbmQgbmV0ZmxpeCBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IGZlYXR1cmVzLiBUaGUgSW5jTm9kZVB1cml0eSBwbG90IHNob3dzIHRoZSBzYW1lIHZhcmlhYmxlcy4gDQoNCmBgYHtyfQ0KdmFySW1wUGxvdChmaW5hbC5yZiwgcGNoID0gMTksIG1haW4gPSAiVmFyaWFibGUgSW1wb3J0YW5jZSIpDQpgYGANCg0KDQojIFJhbmRvbSBGb3Jlc3QgQ2xhc3NpZmljYXRpb24NCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBkbyByYW5kb20gZm9yZXN0IGZvciBvdXIgYmluYXJ5IHJlc3BvbnNlIHZhcmlhYmxlICdwYXNzJy4NCg0KVGhlIHRhYmxlIGJlbG93IHNob3dzIHRoZSBvcHRpbWFsIGh5cGVycGFyYW1ldGVycy4NCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQp0cmFpbi5pZHggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihjb21wbGV0ZV9oYWJpdHNfZGF0YSRwYXNzLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQp0cmFpbi5kYXRhOCA8LSBjb21wbGV0ZV9oYWJpdHNfZGF0YVt0cmFpbi5pZHgsIF0NCnRlc3QuZGF0YTggPC0gY29tcGxldGVfaGFiaXRzX2RhdGFbLXRyYWluLmlkeCwgXQ0KDQp0cmFpbi5kYXRhOCRwYXNzIDwtIGZhY3Rvcih0cmFpbi5kYXRhOCRwYXNzKQ0KdGVzdC5kYXRhOCRwYXNzIDwtIGZhY3Rvcih0ZXN0LmRhdGE4JHBhc3MpDQoNCiMgY3Jvc3MtdmFsaWRhdGlvbiBzZXR0aW5nDQprID0gNQ0KdHJhaW4uc2l6ZSA8LSBkaW0odHJhaW4uZGF0YTgpWzFdICAjIHRyYWluaW5nIGRhdGEgc2l6ZQ0KZm9sZC5zaXplMiA8LSBmbG9vcih0cmFpbi5zaXplL2spICAjIGZvbGQgc2l6ZQ0KDQojIw0KdHVuZS5ncmlkIDwtIGV4cGFuZC5ncmlkKA0KICBtdHJ5ID0gYygyLCAzLCA0LCA1KSwNCiAgbnRyZWUgPSBjKDEwMCwgMzAwLCA1MDApLA0KICBub2Rlc2l6ZSA9IGMoMSwgMywgNSwgMTApLA0KICBtYXhub2RlcyA9IGMoNSwgMTAsIDIwLCBOVUxMKQ0KKQ0KIyMjIHN0b3JlIGh5cGVycGFyYW1ldGVycyBhbmQgYXZnIG9mIGN2IEFVQw0KcmVzdWx0czQgPC0gZGF0YS5mcmFtZSgpDQpiZXN0LmF1YyA8LSAwLjUgICAgICAgICAjIHBsYWNlLWhvbGRlciBvZiBBVUMsIDAuNSA9IHJhbmRvbSBndWVzcw0KYmVzdC5oeXAucGFyYW1zIDwtIGxpc3QoKSAgICMgdXBkYXRlIHRoZSBoeXBlcnBhcmFtZXRlciBsaXN0IGFjY29yZGluZyB0byB0aGUgYmVzdCBhdWMNCg0KIyMNCmZvciAoaSBpbiAxOm5yb3codHVuZS5ncmlkKSl7DQogIGN1cnJlbnQudHVuZS5wYXJhbXMgPC0gdHVuZS5ncmlkW2ksIF0gICMgc3Vic2V0IG9mIERBVEEgRlJBTUUhISANCiAgY3YuYXVjIDwtIHJlcCgwLGspDQogICMjDQogIGZvciAoaiBpbiAxOmspew0KICAgICAgY3YuaWQgPC0gKDEgKyAoai0xKSpmb2xkLnNpemUyKTooaipmb2xkLnNpemUyKQ0KICAgICAgY3YudHJhaW4yIDwtIHRyYWluLmRhdGE4Wy1jdi5pZCwgXQ0KICAgICAgY3YudmFsaWQyIDwtIHRyYWluLmRhdGE4W2N2LmlkLCBdDQogICAgICAjIw0KICAgICAgIHJmLmN2IDwtIHJhbmRvbUZvcmVzdCgNCiAgICAgICAgICAgICAgICAgICBwYXNzIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwNCiAgICAgICAgICAgICAgICAgICBkYXRhID0gY3YudHJhaW4yLA0KICAgICAgICAgICAgICAgICAgIG10cnkgPSBjdXJyZW50LnR1bmUucGFyYW1zJG10cnksDQogICAgICAgICAgICAgICAgICAgbnRyZWUgPSBjdXJyZW50LnR1bmUucGFyYW1zJG50cmVlLA0KICAgICAgICAgICAgICAgICAgIG5vZGVzaXplID0gY3VycmVudC50dW5lLnBhcmFtcyRub2Rlc2l6ZSwNCiAgICAgICAgICAgICAgICAgICBtYXhub2RlcyA9IGN1cnJlbnQudHVuZS5wYXJhbXMkbWF4bm9kZXMpDQogICAgICAgIyMNCiAgICAgICBwcm9iLmN2IDwtIHByZWRpY3QocmYuY3YsIGN2LnZhbGlkMiwgdHlwZSA9ICJwcm9iIilbLCAiMSJdDQogICAgICAgY3YuYXVjW2pdIDwtIGF1Yyhyb2MoY3YudmFsaWQyJHBhc3MsIHByb2IuY3YpKQ0KICAgICAgfQ0KICAgICAgIyMNCiAgICAgICMgQXZlcmFnZSBSTVNFIGFjcm9zcyBmb2xkcw0KICAgICAgYXZnLmF1YyA8LSBtZWFuKGN2LmF1YykgIA0KICAgICAgIyMNCiAgICAgICMgU3RvcmUgcmVzdWx0czogdGhlIGRhdGEgZnJhbWUgZGVmaW5lZCB0byBzdG9yZSBjb21iaW5hdGlvbnMgb2YgaHlwZXJwYXJhbWV0ZXJzDQogICAgICAjIGFuZCB0aGUgcmVzdWx0aW5nIG1lYW4gUk1TRXMgZnJvbSBjcm9zcy12YWxpZGF0aW9uDQogICAgICByZXN1bHRzNCA8LSByYmluZChyZXN1bHRzNCwgZGF0YS5mcmFtZSgNCiAgICAgICAgICAgICAgICAgICBtdHJ5ID0gY3VycmVudC50dW5lLnBhcmFtcyRtdHJ5LA0KICAgICAgICAgICAgICAgICAgIG50cmVlID0gY3VycmVudC50dW5lLnBhcmFtcyRudHJlZSwNCiAgICAgICAgICAgICAgICAgICBub2Rlc2l6ZSA9IGN1cnJlbnQudHVuZS5wYXJhbXMkbm9kZXNpemUsDQogICAgICAgICAgICAgICAgICAgbWF4bm9kZXMgPSBjdXJyZW50LnR1bmUucGFyYW1zJG1heG5vZGVzLA0KICAgICAgICAgICAgICAgICAgIGF1YyA9IGF2Zy5hdWMpKQ0KICANCiAgICAgIyBVcGRhdGUgYmVzdCBwYXJhbWV0ZXJzIGlmIGN1cnJlbnQgbW9kZWwgaXMgYmV0dGVyDQogICAgIGlmKGF2Zy5hdWMgPiBiZXN0LmF1Yykgew0KICAgICAgICAgICBiZXN0LmF1YyA8LSBhdmcuYXVjDQogICAgICAgICAgIGJlc3QuaHlwLnBhcmFtcyA8LSBjdXJyZW50LnR1bmUucGFyYW1zIH0NCiAgICANCn0NCnBhbmRlcihkYXRhLmZyYW1lKGNiaW5kKGJlc3QuaHlwLnBhcmFtcyxiZXN0LmF1YykpKQ0KYGBgDQoNCkJlbG93IHdlIHRyYWluIHRoZSBmaW5hbCByYW5kb20gZm9yZXN0IG1vZGVsIHVzaW5nIHRoZSBvcHRpbWFsIGh5cGVycGFyYW1ldGVycyBmcm9tIGFib3ZlIGFuZCBnZW5lcmF0ZSB0aGUgQXJlYSB1bmRlciB0aGUgY3VydmUgYmVsb3cgKDAuOTcwNykuDQoNCmBgYHtyfQ0KZmluYWwucmYuY2xzIDwtIHJhbmRvbUZvcmVzdCgNCiAgICAgIHBhc3MgfiBhZ2UgKyBnZW5kZXIgKyBzdHVkeV9ob3Vyc19wZXJfZGF5ICsgc29jaWFsX21lZGlhX2hvdXJzICsgbmV0ZmxpeF9ob3VycyArIHBhcnRfdGltZV9qb2IgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uLA0KICAgICAgZGF0YSA9IHRyYWluLmRhdGE4LA0KICAgICAgbnRyZWUgPSBiZXN0Lmh5cC5wYXJhbXMkbnRyZWUsDQogICAgICBtdHJ5ID0gYmVzdC5oeXAucGFyYW1zJG10cnksDQogICAgICBub2Rlc2l6ZSA9IGJlc3QuaHlwLnBhcmFtcyRub2Rlc2l6ZSwNCiAgICAgIG1heG5vZGVzID0gY3VycmVudC50dW5lLnBhcmFtcyRtYXhub2RlcywNCiAgICAgIGltcG9ydGFuY2UgPSBUUlVFDQogICAgICkNCg0KdGVzdC5wcmVkIDwtIHByZWRpY3QoZmluYWwucmYuY2xzLCB0ZXN0LmRhdGE4KQ0KdGVzdC5wcm9iIDwtIHByZWRpY3QoZmluYWwucmYuY2xzLCB0ZXN0LmRhdGE4LCB0eXBlID0gInByb2IiKQ0KcmYucm9jIDwtIHJvYyh0ZXN0LmRhdGE4JHBhc3MsIHRlc3QucHJvYlssICIxIl0pDQp0ZXN0LmF1YyA8LSBhdWMocmYucm9jKQ0KI2NvbmZ1c2lvbk1hdHJpeCh0ZXN0LnByZWQsIHRlc3QuZGF0YSRkaWFiZXRlcykNCnRlc3QuYXVjDQpgYGANCg0KVGhlIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdHMgYmVsb3cgc2hvdyB0aGF0IHN0dWR5IGhvdXJzLCBtZW50YWwgaGVhbHRoLCBzb2NpYWwgbWVkaWEsIHNsZWVwLCBhbmQgTmV0ZmxpeCBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IGZlYXR1cmVzIGluIHByZWRpY3RpbmcgJ3Bhc3MnLg0KDQpgYGB7cn0NCnZhckltcFBsb3QoZmluYWwucmYuY2xzLCBwY2ggPSAxOSwgbWFpbiA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIG9mIFJGIENsYXNzaWZpY2F0aW9uIiApDQoNCmBgYA0KDQpCZWxvdyB3ZSBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZXMgb2Ygc3RhbmRhcmQgbG9naXN0aWMsIGJhc2UgdHJlZSwgYmFnZ2VkLCBhbmQgcmFuZG9tIGZvcmVzdCBtb2RlbHMuIEJhc2VkIG9uIHRoZSBhcmVhcyBiZWxvdywgYmFnZ2VkIGFuZCByYW5kb20gZm9yZXN0IGRvZXMgYmV0dGVyIHRoYW4gdGhlIGJhc2UgZGVjaXNpb24gdHJlZS4NCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMjIyAgbG9naXN0aWMgcmVncmVzc2lvbg0KbG9naXQuZml0MyA8LSBnbG0ocGFzcyB+IGFnZSArIGdlbmRlciArIHN0dWR5X2hvdXJzX3Blcl9kYXkgKyBzb2NpYWxfbWVkaWFfaG91cnMgKyBuZXRmbGl4X2hvdXJzICsgcGFydF90aW1lX2pvYiArIGF0dGVuZGFuY2VfcGVyY2VudGFnZSArIHNsZWVwX2hvdXJzICsgZGlldF9xdWFsaXR5ICsgZXhlcmNpc2VfZnJlcXVlbmN5ICsgcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsICsgaW50ZXJuZXRfcXVhbGl0eSArIG1lbnRhbF9oZWFsdGhfcmF0aW5nICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb24sIGRhdGEgPSB0cmFpbi5kYXRhOCwgZmFtaWx5ID0gYmlub21pYWwpDQpBSUMubG9naXQzIDwtIHN0ZXAobG9naXQuZml0MywgZGlyZWN0aW9uID0gImJvdGgiLCB0cmFjZSA9IDApDQpwcmVkLmxvZ2l0MyA8LSBwcmVkaWN0KEFJQy5sb2dpdDMsIHRlc3QuZGF0YTgsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQojIEJ1aWxkIHRoZSBpbml0aWFsIGNsYXNzaWZpY2F0aW9uIHRyZWUNCnRyZWUubW9kZWw2IDwtIHJwYXJ0KHBhc3MgfiBhZ2UgKyBnZW5kZXIgKyBzdHVkeV9ob3Vyc19wZXJfZGF5ICsgc29jaWFsX21lZGlhX2hvdXJzICsgbmV0ZmxpeF9ob3VycyArIHBhcnRfdGltZV9qb2IgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uLCANCiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLmRhdGE4LA0KICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2xhc3MiLCAgICMgY2xhc3NpZmljYXRpb24gdHJlZQ0KICAgICAgICAgICAgICAgICAgICBwYXJtcyA9IGxpc3Qoc3BsaXQgPSAiZ2luaSIsICAjIFVzaW5nIEdpbmkgaW5kZXgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRk4gY29zdCA9IDEsIEZQIGNvc3QgPSAwLjUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvc3MgPSBtYXRyaXgoYygwLCAwLjUsIDEsIDApLCBucm93ID0gMikgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwNCiAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAxNSwgICMgTWluIDE1IG9icyB0byBzcGxpdA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbmJ1Y2tldCA9IDUsICAgIyBNaW4gNyBvYnMgaW4gbGVhZg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ29tcGxleGl0eSBwYXJhbWV0ZXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjcCA9IDAuMDAxLCAjIGNvbXBsZXggcGFyYW1ldGVyDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4ZGVwdGggPSA1KSkgICAjIE1heCB0cmVlIGRlcHRoDQojIEZpbmQgdGhlIG9wdGltYWwgY3AgdmFsdWUgdGhhdCBtaW5pbWl6ZXMgY3Jvc3MtdmFsaWRhdGVkIGVycm9yDQptaW4uY3A2IDwtIHRyZWUubW9kZWw2JGNwdGFibGVbd2hpY2gubWluKHRyZWUubW9kZWw2JGNwdGFibGVbLCJ4ZXJyb3IiXSksIkNQIl0NCnBydW5lZC50cmVlLm1pbjUgPC0gcHJ1bmUodHJlZS5tb2RlbDYsIGNwID0gbWluLmNwNikNCg0KDQojIEJBR0dJTkcNCiMgQ3JlYXRlIGEgZ3JpZCBvZiBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbnMNCmh5cGVyLmdyaWQzIDwtIGV4cGFuZC5ncmlkKA0KICBuYmFnZyA9IGMoMjUsIDUwLCAxMDApLA0KICBtaW5zcGxpdCA9IGMoNSwgMTAsIDIwKSwNCiAgbWF4ZGVwdGggPSBjKDUsIDEwLCAyMCksDQogIGNwID0gYygwLjAxLCAwLjAwMSkNCikNCiMgSW5pdGlhbGl6ZSBhIHJlc3VsdHMgZGF0YWZyYW1lDQpyZXN1bHRzNSA8LSBkYXRhLmZyYW1lKCkgIyBzdG9yZSB2YWx1ZXMgb2YgdHVuZWQgaHlwZXJwYXJhbWV0ZXJzDQpiZXN0LmFjY3VyYWN5MiA8LSAwICAgICAgIyBzdG9yZSBhY2N1cmFjeQ0KYmVzdC5wYXJhbXM0IDwtIGxpc3QoKSAgICMgc3RvcmUgYmVzdCB2YWx1ZXMgb2YgaHlwZXJwYXJhbWV0ZXINCg0KIyBMb29wIHRocm91Z2ggZWFjaCBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbg0KZm9yKGkgaW4gMTpucm93KGh5cGVyLmdyaWQzKSkgew0KICAjIEdldCBjdXJyZW50IGh5cGVycGFyYW1ldGVycw0KICBwYXJhbXMyIDwtIGh5cGVyLmdyaWQzW2ksIF0NCiAgDQogICMgU2V0IHJwYXJ0IGNvbnRyb2wgcGFyYW1ldGVycw0KICBycGFydC5jb250cm9sMiA8LSBycGFydC5jb250cm9sKA0KICAgIG1pbnNwbGl0ID0gcGFyYW1zMiRtaW5zcGxpdCwNCiAgICBtYXhkZXB0aCA9IHBhcmFtczIkbWF4ZGVwdGgsDQogICAgY3AgPSBwYXJhbXMyJGNwDQogICkNCiAgDQogICMgVHJhaW4gYmFnZ2luZyBtb2RlbA0KICBiYWcubW9kZWwyIDwtIGJhZ2dpbmcoDQogICAgcGFzcyB+IGFnZSArIGdlbmRlciArIHN0dWR5X2hvdXJzX3Blcl9kYXkgKyBzb2NpYWxfbWVkaWFfaG91cnMgKyBuZXRmbGl4X2hvdXJzICsgcGFydF90aW1lX2pvYiArIGF0dGVuZGFuY2VfcGVyY2VudGFnZSArIHNsZWVwX2hvdXJzICsgZGlldF9xdWFsaXR5ICsgZXhlcmNpc2VfZnJlcXVlbmN5ICsgcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsICsgaW50ZXJuZXRfcXVhbGl0eSArIG1lbnRhbF9oZWFsdGhfcmF0aW5nICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb24sDQogICAgZGF0YSA9IHRyYWluLmRhdGE4LA0KICAgIG5iYWdnID0gcGFyYW1zMiRuYmFnZywNCiAgICBjb29iID0gVFJVRSwNCiAgICBjb250cm9sID0gcnBhcnQuY29udHJvbDINCiAgKQ0KICANCiAgIyBNYWtlIHByZWRpY3Rpb25zOiBkZWZhdWx0IGN1dC1vZmYgMC41DQogIHByZWRzMyA8LSBwcmVkaWN0KGJhZy5tb2RlbDIsIG5ld2RhdGEgPSB0ZXN0LmRhdGE4KQ0KICANCiAgIyBDYWxjdWxhdGUgYWNjdXJhY3kNCiAgY20yIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkczMsIHRlc3QuZGF0YTgkcGFzcykNCiAgYWNjdXJhY3kyIDwtIGNtMiRvdmVyYWxsWyJBY2N1cmFjeSJdDQogIA0KICAjIFN0b3JlIHJlc3VsdHMNCiAgcmVzdWx0czUgPC0gcmJpbmQocmVzdWx0czUsIGRhdGEuZnJhbWUoDQogICAgbmJhZ2cgPSBwYXJhbXMyJG5iYWdnLA0KICAgIG1pbnNwbGl0ID0gcGFyYW1zMiRtaW5zcGxpdCwNCiAgICBtYXhkZXB0aCA9IHBhcmFtczIkbWF4ZGVwdGgsDQogICAgY3AgPSBwYXJhbXMyJGNwLA0KICAgIEFjY3VyYWN5ID0gYWNjdXJhY3kyDQogICkpDQogIA0KICAjIFVwZGF0ZSBiZXN0IHBhcmFtZXRlcnMgaWYgY3VycmVudCBtb2RlbCBpcyBiZXR0ZXINCiAgaWYoYWNjdXJhY3kyID4gYmVzdC5hY2N1cmFjeTIpIHsNCiAgICBiZXN0LmFjY3VyYWN5MiA8LSBhY2N1cmFjeTINCiAgICBiZXN0LnBhcmFtczQgPC0gcGFyYW1zMg0KICB9DQp9DQojIFNldCBycGFydCBjb250cm9sIHdpdGggYmVzdCBwYXJhbWV0ZXJzDQpiZXN0LmNvbnRyb2wyIDwtIHJwYXJ0LmNvbnRyb2woDQogIG1pbnNwbGl0ID0gYmVzdC5wYXJhbXM0JG1pbnNwbGl0LA0KICBtYXhkZXB0aCA9IGJlc3QucGFyYW1zNCRtYXhkZXB0aCwNCiAgY3AgPSBiZXN0LnBhcmFtczQkY3ANCikNCg0KIyBUcmFpbiBmaW5hbCBtb2RlbA0KZmluYWwuYmFnLm1vZGVsMiA8LSBiYWdnaW5nKA0KICBwYXNzIH4gYWdlICsgZ2VuZGVyICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBwYXJ0X3RpbWVfam9iICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiwNCiAgZGF0YSA9IHRyYWluLmRhdGE4LA0KICBuYmFnZyA9IGJlc3QucGFyYW1zNCRuYmFnZywNCiAgY29vYiA9IFRSVUUsDQogIGNvbnRyb2wgPSBiZXN0LmNvbnRyb2wyDQopDQojcHJlZC5wcm9iLmJhZ2cgPC0gcHJlZGljdChmaW5hbC5iYWcubW9kZWwsIG5ld2RhdGEgPSB0ZXN0LmRhdGEsIHR5cGUgPSAicHJvYiIpWywyXQ0KIyMjDQojIFByZWRpY3Rpb24gd2l0aCB0aHJlZSBjYW5kaWRhdGUgbW9kZWxzDQpwcnVuZWQudHJlZS5taW42IDwtIHBydW5lKHRyZWUubW9kZWw2LCBjcCA9IG1pbi5jcDYpDQpwcmVkLnByb2IubWluMyA8LSBwcmVkaWN0KHBydW5lZC50cmVlLm1pbjYsIHRlc3QuZGF0YTgsIHR5cGUgPSAicHJvYiIpWywyXQ0KcHJlZC5wcm9iLmJhZ2cyIDwtIHByZWRpY3QoZmluYWwuYmFnLm1vZGVsMiwgbmV3ZGF0YSA9IHRlc3QuZGF0YTgsIHR5cGUgPSAicHJvYiIpWywyXQ0KIyMNCiMgUk9DIG9iamVjdA0Kcm9jLnRyZWUubWluMyA8LSByb2ModGVzdC5kYXRhOCRwYXNzLCBwcmVkLnByb2IubWluMykNCnJvYy5sb2dpdDMgPC0gcm9jKHRlc3QuZGF0YTgkcGFzcywgcHJlZC5sb2dpdDMpDQpyb2MuYmFnZzIgPC0gcm9jKHRlc3QuZGF0YTgkcGFzcywgcHJlZC5wcm9iLmJhZ2cyKQ0Kcm9jLnJmIDwtIHJvYyh0ZXN0LmRhdGE4JHBhc3MsIHRlc3QucHJvYlssICIxIl0pDQoNCiMjDQojIw0KIyMjIFNlbi1TcGUNCmJhZ2cuc2VuMiA8LSByb2MuYmFnZzIkc2Vuc2l0aXZpdGllcw0KYmFnZy5zcGUyIDwtIHJvYy5iYWdnMiRzcGVjaWZpY2l0aWVzDQojDQp0cmVlLm1pbi5zZW4zIDwtIHJvYy50cmVlLm1pbjMkc2Vuc2l0aXZpdGllcw0KdHJlZS5taW4uc3BlMyA8LSByb2MudHJlZS5taW4zJHNwZWNpZmljaXRpZXMNCiMNCmxvZ2l0LnNlbjMgPC0gcm9jLmxvZ2l0MyRzZW5zaXRpdml0aWVzDQpsb2dpdC5zcGUzIDwtIHJvYy5sb2dpdDMkc3BlY2lmaWNpdGllcw0KIw0KcmYuc2VuIDwtIHJvYy5yZiRzZW5zaXRpdml0aWVzDQpyZi5zcGUgPC0gcm9jLnJmJHNwZWNpZmljaXRpZXMNCg0KIyMgQVVDDQphdWMuYmFnZzIgPC0gcm9jLmJhZ2cyJGF1Yw0KYXVjLnRyZWUubWluMyA8LSByb2MudHJlZS5taW4zJGF1Yw0KYXVjLmxvZ2l0MyA8LSByb2MubG9naXQzJGF1Yw0KYXVjLnJmIDwtIHJvYy5yZiRhdWMNCiMjIw0KIyMjDQpwbG90KDEtbG9naXQuc3BlMywgbG9naXQuc2VuMywgIA0KICAgICB4bGFiID0gIjEgLSBzcGVjaWZpY2l0eSIsDQogICAgIHlsYWIgPSAic2Vuc2l0aXZpdHkiLA0KICAgICBjb2wgPSAiZGFya3JlZCIsDQogICAgIHR5cGUgPSAibCIsDQogICAgIGx0eSA9IDEsDQogICAgIGx3ZCA9IDEsDQogICAgIG1haW4gPSAiUk9DOiBDbGFzc2lmaWNhdGlvbiBNb2RlbHMiKQ0KbGluZXMoMS1iYWdnLnNwZTIsIGJhZ2cuc2VuMiwgDQogICAgICBjb2wgPSAiYmx1ZSIsDQogICAgICBsdHkgPSAxLA0KICAgICAgbHdkID0gMSkNCmxpbmVzKDEtdHJlZS5taW4uc3BlMywgdHJlZS5taW4uc2VuMywgICAgICANCiAgICAgIGNvbCA9ICJvcmFuZ2UiLA0KICAgICAgbHR5ID0gMSwNCiAgICAgIGx3ZCA9IDEpDQpsaW5lcygxLXJmLnNwZSwgcmYuc2VuLCAgICAgIA0KICAgICAgY29sID0gInB1cnBsZTQiLA0KICAgICAgbHR5ID0gMSwNCiAgICAgIGx3ZCA9IDEpDQphYmxpbmUoMCwxLCBjb2wgPSAic2t5Ymx1ZTMiLCBsdHkgPSAyLCBsd2QgPSAyKQ0KbGVnZW5kKCJib3R0b21yaWdodCIsIGMoIkxvZ2lzdGljIiwgImJhZ2ciLCAiVHJlZSBNaW4iLCAiUmFuZG9tIEZvcmVzdCIpLA0KICAgICAgIGx0eSA9IGMoMSwxLDEpLCBsd2QgPSByZXAoMSwzKSwNCiAgICAgICBjb2wgPSBjKCJyZWQiLCAiYmx1ZSIsICJvcmFuZ2UiLCAicHVycGxlNCIpLA0KICAgICAgIGJ0eT0ibiIsY2V4ID0gMC44KQ0KIyMgYW5ub3RhdGlvbiAtIEFVQw0KdGV4dCgwLjgsIDAuNDYsIHBhc3RlKCJMb2dpc3RpYyBBVUM6ICIsIHJvdW5kKGF1Yy5sb2dpdDMsNCkpLCBjZXggPSAwLjgpDQp0ZXh0KDAuOCwgMC40LCBwYXN0ZSgiQmFnZyBBVUM6ICIsIHJvdW5kKGF1Yy5iYWdnMiw0KSksIGNleCA9IDAuOCkNCnRleHQoMC44LCAwLjM0LCBwYXN0ZSgiVHJlZSBNaW4gQVVDOiAiLCByb3VuZChhdWMudHJlZS5taW4zLDQpKSwgY2V4ID0gMC44KQ0KdGV4dCgwLjgsIDAuMjgsIHBhc3RlKCJGb3Jlc3QgQVVDOiAiLCByb3VuZChhdWMucmYsNCkpLCBjZXggPSAwLjgpDQpgYGANCg0KIyBSZXN1bHRzL0NvbmNsdXNpb24NCg0KU3R1ZHkgaG91cnMsIG1lbnRhbCBoZWFsdGgsIHNvY2lhbCBtZWRpYSwgc2xlZXAsIE5ldGZsaXgsIGFuZCBleGVyY2lzZSB3ZXJlIGNvbnNpc3RlbnRseSBzaG93biB0byBiZSB0aGUgbW9zdCBpbXBvcnRhbnQgZmVhdHVyZXMgaW4gcHJlZGljdGluZyBleGFtIHNjb3JlL3Bhc3MsIG91dCBvZiBhbGwgdGhlIG1vZGVscyBwZXJmb3JtZWQuDQoNClBlcmZvcm1pbmcgQ0FSVCBhbG9uZSBtYXkgbm90IGJlIGVub3VnaCBmb3IgcHJlZGljdGlvbiBzaW5jZSB0aGV5IG9ubHkgZ2VuZXJhdGUgYSBzaW5nbGUgZGVjaXNpb24gdHJlZS4gQmFnZ2luZyBjYW4gcHJvZHVjZSBhIG1vcmUgcm9idXN0IG1vZGVsIGJ5IGdlbmVyYXRpbmcvYWdncmVnYXRpbmcgbXVsdGlwbGUgdHJlZXMuIFJhbmRvbSBGb3Jlc3QgbWF5IGJlIGV2ZW4gYmV0dGVyIHRoYW4gYmFnZ2luZyBiZWNhdXNlIHRoZXkgdXNlIGEgcmFuZG9tIHN1YnNldCBvZiBmZWF0dXJlcyBpbnN0ZWFkIG9mIGFsbCBmZWF0dXJlcy4NCg0KDQoNCg0KDQoNCg0KDQoNCg0K