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

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

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)
| 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)

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)
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)
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)
| bagged Tree |
8.748 |
7.088 |
0.7364 |
| tree.min.cp |
7.052 |
9.024 |
0.7205 |
| lse |
4.533 |
5.708 |
0.8875 |
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)
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)

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

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)))
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)

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