A San Francisco-based ride sharing company is interested in predicting rider churn. To help explore this question, we have provided a sample dataset of a cohort of users who signed up for an account in January 2014. The data was pulled several months later; we consider a user retained if they were “active” (i.e. took a trip) in the preceding 30 days (from the day the data was pulled). Assume the latest day of last_trip_date to be when the data was pulled. * The data is churn.csv .
Here is a detailed description of the data:
city: city this user signed up in
phone: primary device for this user
signup_date: date of account registration; in the form YYYYMMDD
last_trip_date: the last time this user completed a trip; in the form YYYYMMDD
avg_dist: the average distance (in miles) per trip taken in the first 30 days after signup
avg_rating_by_driver: the rider’s average rating over all of their trips
avg_rating_of_driver: the rider’s average rating of their drivers over all of their trips
surge_pct: the percent of trips taken with surge multiplier > 1
avg_surge: The average surge multiplier over all of this user’s trips
trips_in_first_30_days: the number of trips this user took in the first 30 days after signing up
luxury_car_user: TRUE if the user took a luxury car in their first 30 days; FALSE otherwise
weekday_pct: the percent of the user’s trips occurring during a weekday
We would like you to use this data set to help understand what factors are the best predictors for churn, and offer suggestions to operationalize those insights to help this ride sharing company. Therefore, your task is not only to build a model that minimizes error, but also a model that allows you to interpret the factors that contributed to your predictions.
Work Flow
Step 1. Perform any cleaning, exploratory analysis, and/or visualizations to use the provided. data for this analysis.
Step 2. Build a predictive model to help determine whether or not a user will churn.
Step 3. Evaluate the model.
Step 4. Identify / interpret features that are the most influential in affecting your predictions.
Step 5. Discuss the validity of your model.
Deliverables
Code you used to clean data, explore data, build model, validate the model. Documentations, including the following points:
How did you computed the features and target?
I create several new categorical features, such as surge_or_not, used_or_not_30days, weekday_bucket (zero, soso, everyday). Meanwhile, I also transform the three city feature columns to city column where contains 3 classes. And did the same for phone. At last, I re-classcify the churn from 1/0 to churn/not_churn. Because if it is 1 or 0, it will cause error on the caret package train function when we assign a variable to 0 (e.g. 0 <- a)
What model did you use in the end? Why?
I used xgboost for the churn prediction. One of the reason is gradient boosting method are relative fast in runing time compared with random forest. And gradient boosting build tree one after another, so each new tree helps to correct errors made by previously trained tree. In contrast, random forest train each tree independently. However, gradient boosting method is more easier to over-fitting compared with random forest, so need to be careful when using it.
Alternative models you have considered. Why are they not good enough?
Random forest is another model I have considered. Due to the runing time is mcuh slow than xgboost and GBM, and some research on advantage/disadvantage of random forest vs. gradient boosting method, I eventually did not choose random forest. Although I used one random forest method for feature selection since I have use this method before and the selected features are good for my model.
What performance metric did you use? Why?
I used ROC curve and AUC. Because ROC is one of the most common binary classifiers. To identify if the model can predict churn or not, we can use ROC curve and AUC are evaluation metrics to quantify how predictive the model is. Meanwhile, use the Accuracy to access the model (accuracy = TP+TN/TP+FP+FN+TN) T means true, P means positive, F means false, N means Negative.
Based on insights from the model, actionable plans to reduce churn.
Based on the model prediction and feature selection, I found the top features for the model are city, phone, avg_rating_by_driver, weekday_bucket, luxury_car_user, surge_pct
. Address the weekday_prc user who is zero, target a particular time such as morning work hour and afternoon take off hours to run ads. Reallocation the rider resource to city Kinginland which is the city has the lowest churn rate. Re-desgin the rating system for encouraging a driver to rate user more reliable ways. Thinking the retention of a churned user who used the service quite often. How to keep them engaging them the service.
Discussion for the results The most important factors from this study showed may be city
(location), and customized user engagement service (weekday_prt
,luxury_car_user
,surge_pct
). The location matter which means for place like KinginLand will have more user keep the service than other two cities. The ride-share company should provice more luxury car and surge service on city like KinginLand rather than other two cities. Especially focusing on how to get a new user start to use the service. Once use the service, it’s possible to form a habit of use the service for a relatively long term. There is probabaly the churn group has more users not used the service at all in the first 30 days users. Because they registered the service for just be curious but not actually want to use it on a daily base. Last but not the lest, most of user are iPhone user, improve the app in iPhone is important to keep those user and reducing churn.
Thoughts about how this related to BitTiger Regarding to what this insight can help BitTiger, the rating system is important for end-user. We should interact with our customers more to get more reliable feedback data. The city (location) matters. Deploy the most of resource to the platform or geo-area to attract more customers is practical. We can fiture out the distribution of user’s phone type (iPhone vs. Android) and optimize the user experience on BitTiger on the phone plateform. Try to move a zero day user (a user who does not click any of ther links BitTiger post on Wetchat, YouTube, or other social media channels) to a frequent user (click the links a lot and watched a ton of video and webinar from BitTiger) by customized ads in WeChat. Forming a habit is important. It may be a major swifter between churn and not churn.
My conlusions: 1. The model using xbgboost can predict the churn by around 78% accuracy with 0.84 AUC. The selected features prediction results (6 selected features:) a similar result compared with all features included for prediction. This indicates if I fine tune the hyperparameters and increase the iterations with those selected features, still have a chance to increase the model accuracy. 2. This rider-share company (a.k.a Uber) not sale rides, it sales time. More specifically, it sales the perception of time. It would be intersting to see the timestamp data and how it play out with different features.
Below are two functions for some visulization.
##### function.1
multiplot <- function(..., plotlist=NULL, file, cols=1, layout=NULL) {
require(grid)
# Make a list from the ... arguments and plotlist
plots <- c(list(...), plotlist)
numPlots = length(plots)
# If layout is NULL, then use 'cols' to determine layout
if (is.null(layout)) {
# Make the panel
# ncol: Number of columns of plots
# nrow: Number of rows needed, calculated from # of cols
layout <- matrix(seq(1, cols * ceiling(numPlots/cols)),
ncol = cols, nrow = ceiling(numPlots/cols))
}
if (numPlots==1) {
print(plots[[1]])
} else {
# Set up the page
grid.newpage()
pushViewport(viewport(layout = grid.layout(nrow(layout), ncol(layout))))
# Make each plot, in the correct location
for (i in 1:numPlots) {
# Get the i,j matrix positions of the regions that contain this subplot
matchidx <- as.data.frame(which(layout == i, arr.ind = TRUE))
print(plots[[i]], vp = viewport(layout.pos.row = matchidx$row,
layout.pos.col = matchidx$col))
}
}
}
##### function 2.
see_distribution <- function(){
require(ggplot2)
p1 <- df %>%
ggplot(aes(churn, fill = churn)) +
geom_bar() +
theme(legend.position = "none")
p2 <- df %>%
ggplot(aes(avg_dist, fill = churn)) +
geom_density() +
theme(legend.position = "none")
p3 <- df %>%
ggplot(aes(avg_rating_by_driver, fill = churn)) +
geom_bar() +
theme(legend.position = "none") +
scale_y_continuous(limits = c(0,200000))
p4 <- df %>%
ggplot(aes(avg_rating_of_driver, fill = churn)) +
geom_bar() +
theme(legend.position = "none") +
scale_y_continuous(limits = c(0,200000))
p5 <- df %>%
ggplot(aes(avg_surge, fill = churn)) +
geom_density() +
theme(legend.position = "none")
df <- df %>% mutate(surge_or_not = ifelse(avg_surge==1,"not_surged","surged"))
df %>%
ggplot(aes(surge_or_not, fill = churn)) +
geom_bar() +
theme(legend.position = "none")
p6 <- df %>%
ggplot(aes(surge_pct, fill = churn)) +
geom_density() +
theme(legend.position = "none")
p7 <- df %>%
ggplot(aes(trips_in_first_30_days, fill = churn)) +
geom_density(aes(y = (..count..)/sum(..count..))) +
theme(legend.position = "none")
df <- df %>% mutate(used_or_not = ifelse(trips_in_first_30_days==0,"not_used","used"))
df %>% ggplot(aes(used_or_not, fill = churn)) +
geom_density(aes(y = (..count..)/sum(..count..))) +
theme(legend.position = "none")
p8 <- df %>%
ggplot(aes(luxury_car_user, fill = churn)) +
geom_bar() +
theme(legend.position = "none")
p9 <- df %>%
ggplot(aes(weekday_pct, fill = churn)) +
geom_density() +
theme(legend.position = "none")
df <- df %>% mutate(weekday_bucket = case_when(
weekday_pct == 0 ~ "zero",
(weekday_pct > 0) & (weekday_pct < 100) ~ "soso",
weekday_pct ==100 ~ "everyday"
))
ggplot(df, aes(x = factor(weekday_bucket),fill=churn)) +
geom_bar(aes(y = (..count..)/sum(..count..)))
p10 <- df %>%
ggplot(aes(city_value, fill = churn)) +
geom_bar(aes(y = (..count..)/sum(..count..))) +
theme(legend.position = "none") +
facet_wrap(~churn)
p11 <- df %>%
ggplot(aes(phone, fill = churn)) +
geom_bar(aes(y = (..count..)/sum(..count..))) +
theme(legend.position = "none") +
facet_wrap(~churn)
# One of the key points of machine learning for classification is to see the distribution of each feature.
# Based on experience, the more feature's distribution apart from each other, more easier to classify the label right.
# Let me remove categorical features and plot distribution only on one column and see (Because the code part is simply change geom_bar to # geom_density, so here will not include the code instead just results)
final <- multiplot(p2,p3,p4,p5,p6,p7,p9,cols=1)
return(final)
}
#####
Define the problems
- Classify if a user is going to churn or not
- Find out the factors are the best for predictors for churn and explian why?
- Offer the solution to reduce the churn rate for this company a.k.a. Uber
Step 1. Perform any cleaning, exploratory analysis, and/or visualizations to use the provided. data for this analysis.
Install packages
# Only run the first time
install.packages(c("ggplot","dplyr","tidyr","tidyverse","plotly","caret","corrplot","pROC","mlbench"))
Load packages
library(ggplot2)
library(dplyr)
library(tidyr)
library(tidyverse)
library(plotly)
library(caret)
library(corrplot)
library(pROC)
library(mlbench)
options(scipen = 999, stringsAsFactors=FALSE) # Avoid automatic scientific notation of numbers
df <- read.csv(file="churn.csv", header=TRUE, sep=",")
# gather city and phone type from wide format to long format
df <- df %>%
mutate(city = case_when(
(city_Astapor == 1) ~ "Astapor",
(city_King.s.Landing == 1) ~ "King",
(city_Winterfell == 1) ~ "Winterfell")) %>%
mutate(phone = case_when(
(phone_Android == 1) ~ "Android",
(phone_iPhone == 1) ~ "iPhone",
(phone_no_phone == 1) ~ "Other")) %>%
mutate(churn=ifelse(churn==1,"churned","not_churned"))
df <- subset(df, select = -c(city_Astapor:phone_no_phone))
head(df)
## summary datasets to get a sense of data distribution.
summary(df)
avg_dist avg_rating_by_driver avg_rating_of_driver avg_surge surge_pct trips_in_first_30_days luxury_car_user
Min. : 0.000 Min. :1.000 Min. :1.00 Min. :1.000 Min. : 0.00 Min. : 0.000 Min. :0.0000
1st Qu.: 2.420 1st Qu.:4.700 1st Qu.:4.50 1st Qu.:1.000 1st Qu.: 0.00 1st Qu.: 0.000 1st Qu.:0.0000
Median : 3.880 Median :5.000 Median :4.90 Median :1.000 Median : 0.00 Median : 1.000 Median :0.0000
Mean : 5.797 Mean :4.779 Mean :4.65 Mean :1.075 Mean : 8.85 Mean : 2.278 Mean :0.3771
3rd Qu.: 6.940 3rd Qu.:5.000 3rd Qu.:5.00 3rd Qu.:1.050 3rd Qu.: 8.60 3rd Qu.: 3.000 3rd Qu.:1.0000
Max. :160.960 Max. :5.000 Max. :5.00 Max. :8.000 Max. :100.00 Max. :125.000 Max. :1.0000
weekday_pct churn city phone
Min. : 0.00 Length:50000 Length:50000 Length:50000
1st Qu.: 33.30 Class :character Class :character Class :character
Median : 66.70 Mode :character Mode :character Mode :character
Mean : 60.93
3rd Qu.:100.00
Max. :100.00
## check data type
str(df)
'data.frame': 50000 obs. of 11 variables:
$ avg_dist : num 3.67 8.26 0.77 2.36 3.13 ...
$ avg_rating_by_driver : num 5 5 5 4.9 4.9 5 4 5 5 5 ...
$ avg_rating_of_driver : num 4.7 5 4.3 4.6 4.4 3.5 4.9 5 4.5 4.9 ...
$ avg_surge : num 1.1 1 1 1.14 1.19 1 1 1 1 1 ...
$ surge_pct : num 15.4 0 0 20 11.8 0 0 0 0 0 ...
$ trips_in_first_30_days: int 4 0 3 9 14 2 1 2 2 1 ...
$ luxury_car_user : int 1 0 0 1 0 1 0 0 0 0 ...
$ weekday_pct : num 46.2 50 100 80 82.4 100 100 100 100 0 ...
$ churn : chr "not_churned" "churned" "churned" "not_churned" ...
$ city : chr "King" "Astapor" "Astapor" "King" ...
$ phone : chr "iPhone" "Android" "iPhone" "iPhone" ...
## check missing value
sum(is.na(df))
[1] 0
I got some sense of what data type this dataset has and there is no missing value. You guys are so nice.
Next, check the distribution of churn and each feature to get a better sense. The code below is for the distribution by each feature against with churn or not churn. Some of the exploratory code for distribution within churn class I did not show.
p1 <- df %>%
ggplot(aes(churn, fill = churn)) +
geom_bar() +
theme(legend.position = "none")
p1

62% of user churned, oops…
p2 <- df %>%
ggplot(aes(avg_dist, fill = churn)) +
geom_bar() +
theme(legend.position = "none")
p2

It seems some outlier long distantce greater than 50 miles. And a lot of small distant, see if I can visual them The max count distance is 2.47 miles
p3 <- df %>%
ggplot(aes(avg_rating_by_driver, fill = churn)) +
geom_bar() +
theme(legend.position = "none") +
scale_y_continuous(limits = c(0,30000)) + facet_grid(~churn)
p3

Look like the driver rates customer above the 75% percentile at a 5 stars rating system. And the churned user has higher 5 start rating than non churned user? Interesting. But Churned user has less rating in general. Looks driver don’t rate that much.
p4 <- df %>%
ggplot(aes(avg_rating_of_driver, fill = churn)) +
geom_bar() +
theme(legend.position = "none") +
scale_y_continuous(limits = c(0,30000)) + facet_grid(~churn)
p4

Rider’s rating distribution also above 75% percentile. Both rider and driver rating are not normal distribute. Could it be less sample sizes or people just be nice and rating others very high, or lack or reference experience?
multiplot(p3,p4,cols=2)

Set rating by driver and rating of driver side by side, and it seems driver don’t like to rate rider compared with rider likes to rate driver. Churned has higer 5 stars rating.
p5 <- df %>%
ggplot(aes(avg_surge, fill = churn)) +
geom_bar() +
theme(legend.position = "none") + facet_grid(~churn)
p5

It looks like another outlier for surge rate. Wait a second! it’s not outlier, it’s zero, Let me classify it as surge and non surge.
df <- df %>% mutate(surge_or_not = ifelse(avg_surge==1,"not_surged","surged"))
df %>%
ggplot(aes(surge_or_not, fill = surge_or_not)) +
geom_bar() +
theme(legend.position = "none") + facet_grid(~churn)

More than 60% user not surged (churn + not churn). And it clearly shows churned user don’t do surge.
p6 <- df %>%
ggplot(aes(surge_pct, fill = churn)) +
geom_bar() +
theme(legend.position = "none") + facet_grid(~churn)
p6

Same as avg_surge. But the distribution is interesing which most of surge percent are in the low 25% percentile. Too expensive user still care even though time is important.
p7 <- df %>%
ggplot(aes(trips_in_first_30_days, fill = churn)) +
geom_bar(aes(y = (..count..)/sum(..count..))) +
theme(legend.position = "none") + facet_grid(~churn)
p7

20% user don’t use the server the first 30 days after signup churned, compared with 10% user who don’t churn. Maybe should bucket those by used and not used.
df <- df %>% mutate(used_or_not_30days = ifelse(trips_in_first_30_days==0,"not_used","used"))
df %>% ggplot(aes(used_or_not_30days, fill = used_or_not_30days)) +
geom_bar(aes(y = (..count..)/sum(..count..))) +
theme(legend.position = "none") + facet_grid(~churn)

35% user (churn + not churn) not used the service as the first 30 days. And interestingly churned user has higher used percent in in whole group.
p8 <- df %>%
ggplot(aes(luxury_car_user, fill = as.factor(luxury_car_user))) +
geom_bar(aes(y = (..count..)/sum(..count..))) +
theme(legend.position = "none") + facet_grid(~churn)
p8

42% user don’t get luxury car in churn group! And not churn group is hald and half. I suspect user who get luxury car don’t care much about the price rather than the time. But user who just register for fun don’t wanna waster money in luxury car.
p9 <- df %>%
ggplot(aes(weekday_pct, fill = churn)) +
geom_bar(aes(y = (..count..)/sum(..count..))) +
theme(legend.position = "none") + facet_grid(~churn)
p9

So intersting. In the chured group, largest percent is 100% , more than 25% every weekday use. Maybe churn user just wanna use the service for a short period of time due to some reason and drop it. Let’s dig more by bucket the weekday percent
df <- df %>% mutate(weekday_bucket = case_when(
weekday_pct == 0 ~ "zero",
(weekday_pct > 0) & (weekday_pct < 100) ~ "soso",
weekday_pct ==100 ~ "everyday"
))
ggplot(df, aes(x = factor(weekday_bucket),fill=churn)) +
geom_bar(aes(y = (..count..)/sum(..count..)))

Confirmed, 30% every day user and 20% no user user (CHURN POTENTIAL ALART!!)
p10 <- df %>%
ggplot(aes(city, fill = churn)) +
geom_bar(aes(y = (..count..)/sum(..count..))) +
theme(legend.position = "none") + facet_grid(~churn)
p10

King in churn has much less churn than other citeis. In total, Winterfell has 46% rider and King.slanding has 20% rider ( churn + nonchurn ). But since this is a corort of user, we see if location are important or not later. Seems are three location are psdo locations can’t find on google maps.
p11 <- df %>%
ggplot(aes(phone, fill = churn)) +
geom_bar(aes(y = (..count..)/sum(..count..))) +
theme(legend.position = "none") + facet_grid(~churn)
p11

In churn and not churn class, more iPhone user than Android. 69% of iPhone user and 30% Android user in totaoly.
One of the key point of machine learning for classification is to see the distribution of each feature. Based on experience, the more feature’s distribution apart from each other, more easier to classify the label right. Let me remove categorical features and plot distribution only on one column and see
see_distribution()
NULL

Seems most of the features distribution on the either low end or high end. But witin churn and not churn, distribution of one feature are together. I’m more looking for feature’s distribution can separate churn and not churn. May not be a good sign here.
Correlation analysis for find the high nad low correlated features
num <- c("avg_dist","avg_rating_by_driver","avg_rating_of_driver","avg_surge","surge_pct","trips_in_first_30_days","weekday_pct")
df_num <- select(df, one_of(num))
corrplot(cor(df_num, use="complete.obs"),type="lower")

Seems avg_dist has some small negetive corrleation with all other numeric features, and weekday has some positive correlation with avg_surge and surge_pct.
All the features are less correlated except avg_surge and surge_pct, good sign. Maybe later more easy to capture churn or not churn.
Step 2. Build a predictive model to help determine whether or not a user will churn.
In this step, I’m going to use two machine learning models to predict churn or not churn. Along with step 2, there are some step 3. Evaluate the model in the process, in order to explain the performance of models and models comparison.
require(caret)
## formating some features
names(df)
[1] "avg_dist" "avg_rating_by_driver" "avg_rating_of_driver" "avg_surge" "surge_pct"
[6] "trips_in_first_30_days" "luxury_car_user" "weekday_pct" "city" "phone"
[11] "surge_or_not" "used_or_not_30days" "weekday_bucket" "churn"
# [1] "avg_dist" "avg_rating_by_driver" "avg_rating_of_driver"
# [4] "avg_surge" "surge_pct" "trips_in_first_30_days"
# [7] "luxury_car_user" "weekday_pct" "churn"
# [10] "city" "phone" "surge_or_not"
# [13] "used_or_not_30days" "weekday_bucket"
cols <- c("luxury_car_user", "city", "phone", "surge_or_not","used_or_not_30days" , "weekday_bucket" ,"churn")
df[cols] <- lapply(df[cols], factor)
cols_num <- c("avg_dist", "avg_rating_by_driver", "avg_rating_of_driver", "avg_surge","surge_pct" , "trips_in_first_30_days" ,"weekday_pct")
df[cols_num] <- lapply(df[cols_num], as.numeric)
# Move churn to the last column, otherwise hard to see
col_idx <- grep("churn", names(df))
df <- df[, c( (1:ncol(df))[-col_idx], col_idx)]
Partition the dataset to training and testing sets Let’s do 80:20, 80% for trining, 20% for testing
trainIndex <- createDataPartition(df$churn, p = 0.8,
list = FALSE,
times = 1)
head(trainIndex)
Resample1
[1,] 2
[2,] 3
[3,] 4
[4,] 5
[5,] 9
[6,] 10
df_train <- df[ trainIndex,]
df_test <- df[-trainIndex,]
Start to build the model
After calculting ROC, let’s plot it and calculate AUC.
plot(gbm_default)

Due to run time constrain, here didn’t use k-fold cross-validation. So only choose simple cross validation with less iterations. Also I did not tune the parameter by grid searching due to time limit.
set.seed(1)
pt_gbm <- predict(gbm_default, newdata = df_test)
confusionMatrix(pt_gbm, df_test$churn)
Confusion Matrix and Statistics
Reference
Prediction churned not_churned
churned 5339 1288
not_churned 900 2472
Accuracy : 0.7812
95% CI : (0.7729, 0.7892)
No Information Rate : 0.624
P-Value [Acc > NIR] : < 0.00000000000000022
Kappa : 0.5239
Mcnemar's Test P-Value : < 0.00000000000000022
Sensitivity : 0.8557
Specificity : 0.6574
Pos Pred Value : 0.8056
Neg Pred Value : 0.7331
Prevalence : 0.6240
Detection Rate : 0.5340
Detection Prevalence : 0.6628
Balanced Accuracy : 0.7566
'Positive' Class : churned
We got 78% accuracy.
set.seed(1)
gbm_probs <- predict(gbm_default, newdata = df_test, type = "prob")
gbm_ROC <- roc(predictor=gbm_probs$churned,
response=df_test$churn,
levels=rev(levels(df_test$churn)))
gbm_ROC$auc
Area under the curve: 0.8499
The Area under the curve (AUC) is around 0.85, which is not bad.
plot(gbm_ROC,main="GBM ROC")

ROC calcualted by sensitivity (True positive)/specificity (False positive). We want ROC higher and AUC (under curve area) greater.
hist1 <- histogram(~gbm_probs$churned|df_test$churn,xlab="Probability of Churn, GBM",ylim = c(0,30))
hist1

The histogram of probability distribution of churn or not churn users. Seems the model predicts well (high probability) of churn user. Let’s try another model xgboost in caret package.
After iteraction of calculating ROC, let’s plot them and calculate AUC.
plot(xgb_default)


pt_xgb <- predict(xgb_default, newdata = df_test)
confusionMatrix(pt_xgb, df_test$churn)
Confusion Matrix and Statistics
Reference
Prediction churned not_churned
churned 5384 1242
not_churned 855 2518
Accuracy : 0.7903
95% CI : (0.7822, 0.7982)
No Information Rate : 0.624
P-Value [Acc > NIR] : < 0.00000000000000022
Kappa : 0.5438
Mcnemar's Test P-Value : < 0.00000000000000022
Sensitivity : 0.8630
Specificity : 0.6697
Pos Pred Value : 0.8126
Neg Pred Value : 0.7465
Prevalence : 0.6240
Detection Rate : 0.5385
Detection Prevalence : 0.6627
Balanced Accuracy : 0.7663
'Positive' Class : churned
set.seed(1)
xgb_probs <- predict(xgb_default, newdata = df_test, type = "prob")
xgb_ROC <- roc(predictor=xgb_probs$churned,
response=df_test$churn,
levels=rev(levels(df_test$churn)))
xgb_ROC$auc
Area under the curve: 0.8557
The area under the curve is around 0.85, a little higer than gbm
plot(xgb_ROC,main="XGBBOOST ROC")

ROC calcualted by sensitivity (True positive)/specificity (False positive). We want ROC higher and AUC (under curve area) greater.
hist2 <- histogram(~xgb_probs$churned|df_test$churn,xlab="Probability of Churn, XGBOOST",ylim = c(0,30))
hist2

Seems the model predicts was very similar compared with gbm for predicting churn. And more blue area in the high end of churn category.
Step 3. Evalution the model
Some parts of this step has been done the step2. We need do a side by side comparison. We need to figure out if the two method results are statistical different. So we first collect the resampling results using resamples.
set.seed(1)
resamps <- resamples(list(GBM = gbm_default,
XGBBOOST = xgb_default))
summary(resamps)
Call:
summary.resamples(object = resamps)
Models: GBM, XGBBOOST
Number of resamples: 5
ROC
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
GBM 0.8447 0.8459 0.8487 0.8509 0.8538 0.8614 0
XGBBOOST 0.8512 0.8539 0.8549 0.8572 0.8600 0.8663 0
Sens
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
GBM 0.8546 0.8607 0.8622 0.8625 0.8644 0.8708 0
XGBBOOST 0.8600 0.8630 0.8636 0.8639 0.8660 0.8670 0
Spec
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
GBM 0.6503 0.6550 0.6567 0.6596 0.665 0.671 0
XGBBOOST 0.6570 0.6599 0.6623 0.6635 0.663 0.675 0
trellis.par.set(caretTheme())
Note: The default device has been opened to honour attempt to modify trellis settings
dotplot(resamps, metric = "ROC")

We can see some overlapping sometime (if set seed) on the error bar which can indicate not significant different. In terms of magnitude of values, the ROC values are not different.
In conclusion of step 2 and step 3, the xgboost is slight better than gmb on AUC and ROC.
The histogram also shows a relative lift on the probability on churn prediction by XGBOOST.
Alternative models you have considered. Why are they not good enough?
I also tried random forest RF (results below), but the ROC value is lower those gbm abd xgboost.
# Random Forest
#
# 40001 samples
# 13 predictors
# 2 classes: 'churned', 'not_churned'
#
# No pre-processing
# Resampling: Cross-Validated (5 fold)
# Summary of sample sizes: 32001, 32000, 32001, 32001, 32001
# Resampling results across tuning parameters:
#
# mtry ROC Sens Spec
# 2 0.8324291 0.8775095 0.6093455
# 9 0.8360655 0.8461755 0.6583351
# 16 0.8246700 0.8219739 0.6617914
#
# ROC was used to select the optimal model using the largest value.
# The final value used for the model was mtry = 9.
I didn’t include random forest code here because it takes long hours to run. It appears in the default setting, gbm and xgboost are better than random forest in this case. The reason is more trees from random forest will cause better results by reduce variance, but also more computationally expensive. In general, I found random forest is slow than gbm. If I have more time to try out, random forest maybe good because it only has one parameter to tune, the number of features to randomly select at each node in the caret package. However, Boosted Trees is getting popular in Kaggle because it add new trees that compliment the already built ones and this can give you better accuracy with less trees.It fast generate some weak leaner (tree) which make it faster. xgboost used a more regularized model compared with gbm to control over-fitting, which gives it better performance. Although it’s over specialization, after test I found the speed and results are okay for xgboost. No wonder people used it frequently in Kaggle competition.
Step 4. Identify / interpret features that are the most influential in affecting your predictions.
UP to this point, I just throw each things on the wall and see if the mud will stick. It’s time to take a close look that which parameters are actually important. And I used xgbTree based on previous conclusion that xgb is better than random forest in this case.
Now I need to Feature Ranking
Suprisingly, the top five important feature selected are avg_rating_by_driver, surge_pct,cityKing, iPhone avg_rating_by_driver: some churn user had reviered more 5 star rating compared with not-churn user. So user churn because afraid of not meet driver’s high expection ? It may not be the case. Keep looking. surge_pct: Higer 0% surge_prc in churn class which indicates those people not use surge may more likely to churn. cityKing: In the churn class, city KinginLang has low churn rate compared with other two citys. weekday_pct: the distribution are different compared churn and not churn. Again churn has high 100% use percent which is intersting. iPhone: big percent in churn calss, could be the rider app is more easy for iPhone user to signup? More marketing on iPhone user?
plot(importances)

Let me try feature selection by using another method
This method run too long so I set eval=FALSE
The top 5 variables (out of 12 features): city, phone, luxury_car_user, avg_rating_by_driver, weekday_bucket
list the chosen features.
plot(results, type=c("g", "o"))

Based on the “eblow” rules, we choose the top 5 features. Compared both xbgboost and random forest selected feastures, city, phone, avg_rating_by_driver, weekday_bucket are common
. But I also want to add luxury_car_user
and surge_pct
since those two features’ distribution are more separate and may be able to help to classify churn and not churn well.
So use those six features to predit again.
control <- trainControl(method="cv", classProbs = TRUE, number=5, summaryFunction=twoClassSummary)
seed <- 1
metric <- "ROC"
set.seed(seed)
xgb_select <- train(churn~ city + phone + avg_rating_by_driver + weekday_bucket + luxury_car_user + surge_pct, data=df_train, method="xgbTree", metric=metric, trControl=control)
Then get confusion Matrix again to see the accuracy
pt_select <- predict(xgb_select, newdata = df_test)
confusionMatrix(pt_select, df_test$churn)
Confusion Matrix and Statistics
Reference
Prediction churned not_churned
churned 5228 1365
not_churned 1011 2395
Accuracy : 0.7624
95% CI : (0.7539, 0.7707)
No Information Rate : 0.624
P-Value [Acc > NIR] : < 0.00000000000000022
Kappa : 0.484
Mcnemar's Test P-Value : 0.0000000000004425
Sensitivity : 0.8380
Specificity : 0.6370
Pos Pred Value : 0.7930
Neg Pred Value : 0.7032
Prevalence : 0.6240
Detection Rate : 0.5229
Detection Prevalence : 0.6594
Balanced Accuracy : 0.7375
'Positive' Class : churned
The accuracy is round 0.76
xgb_probs <- predict(xgb_select, newdata = df_test, type = "prob")
xgb_ROC <- roc(predictor=xgb_probs$churned,
response=df_test$churn,
levels=rev(levels(df_test$churn)))
xgb_ROC$auc
Area under the curve: 0.8185
The AUC is around 0.82, and slight drop but still ok.
hist3 <- histogram(~xgb_probs$churned|df_test$churn,xlab="Probability of Churn by selected feature, XGBOOST",ylim = c(0,35))
hist3

When look at the new histogram distribution on churn, seems not as good as before. More features still be better than less features in XGBOOST in such case.
I get a sense of I can use other methods to conduct feature selection such as PCA. However, as this point, it seems the amount of the features is still helpful to improve the AUC value based on the dataset amount.
Step 5. Discuss the validity of the model
The machine learning model for classifying churn is XGBOOST. It reaches accucary 0.78 and AUC 0.86. The reduced features prediction by xgboost may not help improving the prediction accuracy at this point. In general, I believe this model can be use as a starting point for predicting churn or not churn for this ride-share company. However, if I have more time, I will take a close look on the ROC for individual user, which is the posterior distribution for each user. If we can success predict the churn in a particular corhort users, we may get more accurate prediction of churn.
With the important feature, city, phone, avg_rating_by_driver, weekday_prc, surg_prc, luxury_car_user
, the model can reach a reseasoble prediction posibility. We can use this model to predict the next 30 days if a user is going to churn or not by at least 76% accuracy! If we use all feature, we can improve this prediction accuracy to 79%.
LS0tCnRpdGxlOiAiQ2h1cm4gUHJlZGljdGlvbiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0Ci0tLQpBIFNhbiBGcmFuY2lzY28tYmFzZWQgcmlkZSBzaGFyaW5nIGNvbXBhbnkgaXMgaW50ZXJlc3RlZCBpbiBwcmVkaWN0aW5nIHJpZGVyIGNodXJuLiBUbyBoZWxwIGV4cGxvcmUgdGhpcyBxdWVzdGlvbiwgd2UgaGF2ZQpwcm92aWRlZCBhIHNhbXBsZSBkYXRhc2V0IG9mIGEgY29ob3J0IG9mIHVzZXJzIHdobyBzaWduZWQgdXAgZm9yIGFuIGFjY291bnQgaW4gSmFudWFyeSAyMDE0LiBUaGUgZGF0YSB3YXMgcHVsbGVkIHNldmVyYWwKbW9udGhzIGxhdGVyOyB3ZSBjb25zaWRlciBhIHVzZXIgcmV0YWluZWQgaWYgdGhleSB3ZXJlIOKAnGFjdGl2ZeKAnSAoaS5lLiB0b29rIGEgdHJpcCkgaW4gdGhlIHByZWNlZGluZyAzMCBkYXlzIChmcm9tIHRoZSBkYXkgdGhlCmRhdGEgd2FzIHB1bGxlZCkuIEFzc3VtZSB0aGUgbGF0ZXN0IGRheSBvZiBsYXN0X3RyaXBfZGF0ZSB0byBiZSB3aGVuIHRoZSBkYXRhIHdhcyBwdWxsZWQuICogVGhlIGRhdGEgaXMgY2h1cm4uY3N2IC4KCkhlcmUgaXMgYSBkZXRhaWxlZCBkZXNjcmlwdGlvbiBvZiB0aGUgZGF0YToKCmBjaXR5OiBjaXR5IHRoaXMgdXNlciBzaWduZWQgdXAgaW5gCmBwaG9uZTogcHJpbWFyeSBkZXZpY2UgZm9yIHRoaXMgdXNlcmAKYHNpZ251cF9kYXRlOiBkYXRlIG9mIGFjY291bnQgcmVnaXN0cmF0aW9uOyBpbiB0aGUgZm9ybSBZWVlZTU1ERGAKYGxhc3RfdHJpcF9kYXRlOiB0aGUgbGFzdCB0aW1lIHRoaXMgdXNlciBjb21wbGV0ZWQgYSB0cmlwOyBpbiB0aGUgZm9ybSBZWVlZTU1ERGAKYGF2Z19kaXN0OiB0aGUgYXZlcmFnZSBkaXN0YW5jZSAoaW4gbWlsZXMpIHBlciB0cmlwIHRha2VuIGluIHRoZSBmaXJzdCAzMCBkYXlzIGFmdGVyIHNpZ251cGAKYGF2Z19yYXRpbmdfYnlfZHJpdmVyOiB0aGUgcmlkZXLigJlzIGF2ZXJhZ2UgcmF0aW5nIG92ZXIgYWxsIG9mIHRoZWlyIHRyaXBzYApgYXZnX3JhdGluZ19vZl9kcml2ZXI6IHRoZSByaWRlcuKAmXMgYXZlcmFnZSByYXRpbmcgb2YgdGhlaXIgZHJpdmVycyBvdmVyIGFsbCBvZiB0aGVpciB0cmlwc2AKYHN1cmdlX3BjdDogdGhlIHBlcmNlbnQgb2YgdHJpcHMgdGFrZW4gd2l0aCBzdXJnZSBtdWx0aXBsaWVyID4gMWAKYGF2Z19zdXJnZTogVGhlIGF2ZXJhZ2Ugc3VyZ2UgbXVsdGlwbGllciBvdmVyIGFsbCBvZiB0aGlzIHVzZXLigJlzIHRyaXBzYApgdHJpcHNfaW5fZmlyc3RfMzBfZGF5czogdGhlIG51bWJlciBvZiB0cmlwcyB0aGlzIHVzZXIgdG9vayBpbiB0aGUgZmlyc3QgMzAgZGF5cyBhZnRlciBzaWduaW5nIHVwYApgbHV4dXJ5X2Nhcl91c2VyOiBUUlVFIGlmIHRoZSB1c2VyIHRvb2sgYSBsdXh1cnkgY2FyIGluIHRoZWlyIGZpcnN0IDMwIGRheXM7IEZBTFNFIG90aGVyd2lzZWAKYHdlZWtkYXlfcGN0OiB0aGUgcGVyY2VudCBvZiB0aGUgdXNlcuKAmXMgdHJpcHMgb2NjdXJyaW5nIGR1cmluZyBhIHdlZWtkYXlgCgpXZSB3b3VsZCBsaWtlIHlvdSB0byB1c2UgdGhpcyBkYXRhIHNldCB0byBoZWxwIHVuZGVyc3RhbmQgd2hhdCBmYWN0b3JzIGFyZSB0aGUgYmVzdCBwcmVkaWN0b3JzIGZvciBjaHVybiwgYW5kIG9mZmVyCnN1Z2dlc3Rpb25zIHRvIG9wZXJhdGlvbmFsaXplIHRob3NlIGluc2lnaHRzIHRvIGhlbHAgdGhpcyByaWRlIHNoYXJpbmcgY29tcGFueS4gVGhlcmVmb3JlLCB5b3VyIHRhc2sgaXMgbm90IG9ubHkgdG8gYnVpbGQgYQptb2RlbCB0aGF0IG1pbmltaXplcyBlcnJvciwgYnV0IGFsc28gYSBtb2RlbCB0aGF0IGFsbG93cyB5b3UgdG8gaW50ZXJwcmV0IHRoZSBmYWN0b3JzIHRoYXQgY29udHJpYnV0ZWQgdG8geW91ciBwcmVkaWN0aW9ucy4KCldvcmsgRmxvdwoKU3RlcCAxLiBQZXJmb3JtIGFueSBjbGVhbmluZywgZXhwbG9yYXRvcnkgYW5hbHlzaXMsIGFuZC9vciB2aXN1YWxpemF0aW9ucyB0byB1c2UgdGhlIHByb3ZpZGVkLiBkYXRhIGZvciB0aGlzIGFuYWx5c2lzLgoKU3RlcCAyLiBCdWlsZCBhIHByZWRpY3RpdmUgbW9kZWwgdG8gaGVscCBkZXRlcm1pbmUgd2hldGhlciBvciBub3QgYSB1c2VyIHdpbGwgY2h1cm4uCgpTdGVwIDMuIEV2YWx1YXRlIHRoZSBtb2RlbC4KClN0ZXAgNC4gSWRlbnRpZnkgLyBpbnRlcnByZXQgZmVhdHVyZXMgdGhhdCBhcmUgdGhlIG1vc3QgaW5mbHVlbnRpYWwgaW4gYWZmZWN0aW5nIHlvdXIgcHJlZGljdGlvbnMuCgpTdGVwIDUuIERpc2N1c3MgdGhlIHZhbGlkaXR5IG9mIHlvdXIgbW9kZWwuCgoKRGVsaXZlcmFibGVzCgpDb2RlIHlvdSB1c2VkIHRvIGNsZWFuIGRhdGEsIGV4cGxvcmUgZGF0YSwgYnVpbGQgbW9kZWwsIHZhbGlkYXRlIHRoZSBtb2RlbC4gRG9jdW1lbnRhdGlvbnMsIGluY2x1ZGluZyB0aGUgZm9sbG93aW5nIHBvaW50czoKCipIb3cgZGlkIHlvdSBjb21wdXRlZCB0aGUgZmVhdHVyZXMgYW5kIHRhcmdldD8qCgpJIGNyZWF0ZSBzZXZlcmFsIG5ldyBjYXRlZ29yaWNhbCBmZWF0dXJlcywgc3VjaCBhcyBzdXJnZV9vcl9ub3QsIHVzZWRfb3Jfbm90XzMwZGF5cywgd2Vla2RheV9idWNrZXQgKHplcm8sIHNvc28sIGV2ZXJ5ZGF5KS4gTWVhbndoaWxlLCBJIGFsc28gdHJhbnNmb3JtIHRoZSB0aHJlZSBjaXR5IGZlYXR1cmUgY29sdW1ucyB0byBjaXR5IGNvbHVtbiB3aGVyZSBjb250YWlucyAzIGNsYXNzZXMuIEFuZCBkaWQgdGhlIHNhbWUgZm9yIHBob25lLiBBdCBsYXN0LCBJIHJlLWNsYXNzY2lmeSB0aGUgY2h1cm4gZnJvbSAxLzAgdG8gY2h1cm4vbm90X2NodXJuLiBCZWNhdXNlIGlmIGl0IGlzIDEgb3IgMCwgaXQgd2lsbCBjYXVzZSBlcnJvciBvbiB0aGUgY2FyZXQgcGFja2FnZSB0cmFpbiBmdW5jdGlvbiB3aGVuIHdlIGFzc2lnbiBhIHZhcmlhYmxlIHRvIDAgKGUuZy4gMCA8LSBhKQoKKldoYXQgbW9kZWwgZGlkIHlvdSB1c2UgaW4gdGhlIGVuZD8gV2h5PyoKCkkgdXNlZCB4Z2Jvb3N0IGZvciB0aGUgY2h1cm4gcHJlZGljdGlvbi4gT25lIG9mIHRoZSByZWFzb24gaXMgZ3JhZGllbnQgIGJvb3N0aW5nIG1ldGhvZCBhcmUgcmVsYXRpdmUgZmFzdCBpbiBydW5pbmcgdGltZSBjb21wYXJlZCB3aXRoIHJhbmRvbSBmb3Jlc3QuCkFuZCBncmFkaWVudCAgYm9vc3RpbmcgYnVpbGQgdHJlZSBvbmUgYWZ0ZXIgYW5vdGhlciwgc28gZWFjaCBuZXcgdHJlZSBoZWxwcyB0byBjb3JyZWN0IGVycm9ycyBtYWRlIGJ5IHByZXZpb3VzbHkgdHJhaW5lZCB0cmVlLgpJbiBjb250cmFzdCwgcmFuZG9tIGZvcmVzdCB0cmFpbiBlYWNoIHRyZWUgaW5kZXBlbmRlbnRseS4gSG93ZXZlciwgZ3JhZGllbnQgYm9vc3RpbmcgbWV0aG9kIGlzIG1vcmUgZWFzaWVyIHRvIG92ZXItZml0dGluZyBjb21wYXJlZCB3aXRoIHJhbmRvbSBmb3Jlc3QsIHNvIG5lZWQgdG8gYmUgY2FyZWZ1bCB3aGVuIHVzaW5nIGl0LgoKKkFsdGVybmF0aXZlIG1vZGVscyB5b3UgaGF2ZSBjb25zaWRlcmVkLiBXaHkgYXJlIHRoZXkgbm90IGdvb2QgZW5vdWdoPyoKClJhbmRvbSBmb3Jlc3QgaXMgYW5vdGhlciBtb2RlbCBJIGhhdmUgY29uc2lkZXJlZC4gRHVlIHRvIHRoZSBydW5pbmcgdGltZSBpcyBtY3VoIHNsb3cgdGhhbiB4Z2Jvb3N0IGFuZCBHQk0sIGFuZCBzb21lIHJlc2VhcmNoIG9uIGFkdmFudGFnZS9kaXNhZHZhbnRhZ2Ugb2YgcmFuZG9tIGZvcmVzdCB2cy4gZ3JhZGllbnQgYm9vc3RpbmcgbWV0aG9kLCBJIGV2ZW50dWFsbHkgZGlkIG5vdCBjaG9vc2UgcmFuZG9tIGZvcmVzdC4gQWx0aG91Z2ggSSB1c2VkIG9uZSByYW5kb20gZm9yZXN0IG1ldGhvZCBmb3IgZmVhdHVyZSBzZWxlY3Rpb24gc2luY2UgSSBoYXZlIHVzZSB0aGlzIG1ldGhvZCBiZWZvcmUgYW5kIHRoZSBzZWxlY3RlZCBmZWF0dXJlcyBhcmUgZ29vZCBmb3IgbXkgbW9kZWwuCgoqV2hhdCBwZXJmb3JtYW5jZSBtZXRyaWMgZGlkIHlvdSB1c2U/IFdoeT8qCgpJIHVzZWQgUk9DIGN1cnZlIGFuZCBBVUMuIEJlY2F1c2UgUk9DIGlzIG9uZSBvZiB0aGUgbW9zdCBjb21tb24gYmluYXJ5IGNsYXNzaWZpZXJzLgpUbyBpZGVudGlmeSBpZiB0aGUgbW9kZWwgY2FuIHByZWRpY3QgY2h1cm4gb3Igbm90LCB3ZSBjYW4gdXNlIFJPQyBjdXJ2ZSBhbmQgQVVDIGFyZSBldmFsdWF0aW9uIG1ldHJpY3MgdG8gcXVhbnRpZnkgaG93IHByZWRpY3RpdmUgdGhlIG1vZGVsIGlzLiBNZWFud2hpbGUsIHVzZSB0aGUgQWNjdXJhY3kgdG8gYWNjZXNzIHRoZSBtb2RlbCAoYWNjdXJhY3kgPSBUUCtUTi9UUCtGUCtGTitUTikgVCBtZWFucyB0cnVlLCBQIG1lYW5zIHBvc2l0aXZlLCBGIG1lYW5zIGZhbHNlLCBOIG1lYW5zIE5lZ2F0aXZlLiAKCipCYXNlZCBvbiBpbnNpZ2h0cyBmcm9tIHRoZSBtb2RlbCwgYWN0aW9uYWJsZSBwbGFucyB0byByZWR1Y2UgY2h1cm4uKgoKQmFzZWQgb24gdGhlIG1vZGVsIHByZWRpY3Rpb24gYW5kIGZlYXR1cmUgc2VsZWN0aW9uLCBJIGZvdW5kIHRoZSB0b3AgZmVhdHVyZXMgZm9yIHRoZSBtb2RlbCBhcmUgYGNpdHksIHBob25lLCBhdmdfcmF0aW5nX2J5X2RyaXZlciwgd2Vla2RheV9idWNrZXQsIGx1eHVyeV9jYXJfdXNlciwgc3VyZ2VfcGN0YC4KQWRkcmVzcyB0aGUgd2Vla2RheV9wcmMgdXNlciB3aG8gaXMgemVybywgdGFyZ2V0IGEgcGFydGljdWxhciB0aW1lIHN1Y2ggYXMgbW9ybmluZyB3b3JrIGhvdXIgYW5kIGFmdGVybm9vbiB0YWtlIG9mZiBob3VycyB0byBydW4gYWRzLgpSZWFsbG9jYXRpb24gdGhlIHJpZGVyIHJlc291cmNlIHRvIGNpdHkgS2luZ2lubGFuZCB3aGljaCBpcyB0aGUgY2l0eSBoYXMgdGhlIGxvd2VzdCBjaHVybiByYXRlLgpSZS1kZXNnaW4gdGhlIHJhdGluZyBzeXN0ZW0gZm9yIGVuY291cmFnaW5nIGEgZHJpdmVyIHRvIHJhdGUgdXNlciBtb3JlIHJlbGlhYmxlIHdheXMuClRoaW5raW5nIHRoZSByZXRlbnRpb24gb2YgYSBjaHVybmVkIHVzZXIgd2hvIHVzZWQgdGhlIHNlcnZpY2UgcXVpdGUgb2Z0ZW4uIEhvdyB0byBrZWVwIHRoZW0gZW5nYWdpbmcgdGhlbSB0aGUgc2VydmljZS4KCipEaXNjdXNzaW9uIGZvciB0aGUgcmVzdWx0cyoKVGhlIG1vc3QgaW1wb3J0YW50IGZhY3RvcnMgZnJvbSB0aGlzIHN0dWR5IHNob3dlZCBtYXkgYmUgYGNpdHlgIChsb2NhdGlvbiksIGFuZCBjdXN0b21pemVkIHVzZXIgZW5nYWdlbWVudCBzZXJ2aWNlIChgd2Vla2RheV9wcnRgLGBsdXh1cnlfY2FyX3VzZXJgLGBzdXJnZV9wY3RgKS4gVGhlIGxvY2F0aW9uIG1hdHRlciB3aGljaCBtZWFucyBmb3IgcGxhY2UgbGlrZSBLaW5naW5MYW5kIHdpbGwgaGF2ZSBtb3JlIHVzZXIga2VlcCB0aGUgc2VydmljZSB0aGFuIG90aGVyIHR3byBjaXRpZXMuIFRoZSByaWRlLXNoYXJlIGNvbXBhbnkgc2hvdWxkIHByb3ZpY2UgbW9yZSBsdXh1cnkgY2FyIGFuZCBzdXJnZSBzZXJ2aWNlIG9uIGNpdHkgbGlrZSBLaW5naW5MYW5kIHJhdGhlciB0aGFuIG90aGVyIHR3byBjaXRpZXMuIEVzcGVjaWFsbHkgZm9jdXNpbmcgb24gaG93IHRvIGdldCBhIG5ldyB1c2VyIHN0YXJ0IHRvIHVzZSB0aGUgc2VydmljZS4gT25jZSB1c2UgdGhlIHNlcnZpY2UsIGl0J3MgcG9zc2libGUgdG8gZm9ybSBhIGhhYml0IG9mIHVzZSB0aGUgc2VydmljZSBmb3IgYSByZWxhdGl2ZWx5IGxvbmcgdGVybS4gVGhlcmUgaXMgcHJvYmFiYWx5IHRoZSBjaHVybiBncm91cCBoYXMgbW9yZSB1c2VycyBub3QgdXNlZCB0aGUgc2VydmljZSBhdCBhbGwgaW4gdGhlIGZpcnN0IDMwIGRheXMgdXNlcnMuIEJlY2F1c2UgdGhleSByZWdpc3RlcmVkIHRoZSBzZXJ2aWNlIGZvciBqdXN0IGJlIGN1cmlvdXMgYnV0IG5vdCBhY3R1YWxseSB3YW50IHRvIHVzZSBpdCBvbiBhIGRhaWx5IGJhc2UuIExhc3QgYnV0IG5vdCB0aGUgbGVzdCwgbW9zdCBvZiB1c2VyIGFyZSBpUGhvbmUgdXNlciwgaW1wcm92ZSB0aGUgYXBwIGluIGlQaG9uZSBpcyBpbXBvcnRhbnQgdG8ga2VlcCB0aG9zZSB1c2VyIGFuZCByZWR1Y2luZyBjaHVybi4KCipUaG91Z2h0cyBhYm91dCBob3cgdGhpcyByZWxhdGVkIHRvIEJpdFRpZ2VyKgpSZWdhcmRpbmcgdG8gd2hhdCB0aGlzIGluc2lnaHQgY2FuIGhlbHAgQml0VGlnZXIsIHRoZSByYXRpbmcgc3lzdGVtIGlzIGltcG9ydGFudCBmb3IgZW5kLXVzZXIuIFdlIHNob3VsZCBpbnRlcmFjdCB3aXRoIG91ciBjdXN0b21lcnMgbW9yZSB0byBnZXQgbW9yZSByZWxpYWJsZSBmZWVkYmFjayBkYXRhLgpUaGUgY2l0eSAobG9jYXRpb24pIG1hdHRlcnMuIERlcGxveSB0aGUgbW9zdCBvZiByZXNvdXJjZSB0byB0aGUgcGxhdGZvcm0gb3IgZ2VvLWFyZWEgdG8gYXR0cmFjdCBtb3JlIGN1c3RvbWVycyBpcyBwcmFjdGljYWwuCldlIGNhbiBmaXR1cmUgb3V0IHRoZSBkaXN0cmlidXRpb24gb2YgdXNlcidzIHBob25lIHR5cGUgKGlQaG9uZSB2cy4gQW5kcm9pZCkgYW5kIG9wdGltaXplIHRoZSB1c2VyIGV4cGVyaWVuY2Ugb24gQml0VGlnZXIgb24gdGhlIHBob25lIHBsYXRlZm9ybS4KVHJ5IHRvIG1vdmUgYSB6ZXJvIGRheSB1c2VyIChhIHVzZXIgd2hvIGRvZXMgbm90IGNsaWNrIGFueSBvZiB0aGVyIGxpbmtzIEJpdFRpZ2VyIHBvc3Qgb24gV2V0Y2hhdCwgWW91VHViZSwgb3Igb3RoZXIgc29jaWFsIG1lZGlhIGNoYW5uZWxzKSB0byBhIGZyZXF1ZW50IHVzZXIgKGNsaWNrIHRoZSBsaW5rcyBhIGxvdCBhbmQgd2F0Y2hlZCBhIHRvbiBvZiB2aWRlbyBhbmQgd2ViaW5hciBmcm9tIEJpdFRpZ2VyKSBieSBjdXN0b21pemVkIGFkcyBpbiBXZUNoYXQuIEZvcm1pbmcgYSBoYWJpdCBpcyBpbXBvcnRhbnQuIEl0IG1heSBiZSBhIG1ham9yIHN3aWZ0ZXIgYmV0d2VlbiBjaHVybiBhbmQgbm90IGNodXJuLgoKKk15IGNvbmx1c2lvbnM6KgoxLiBUaGUgbW9kZWwgdXNpbmcgeGJnYm9vc3QgY2FuIHByZWRpY3QgdGhlIGNodXJuIGJ5IGFyb3VuZCA3OCUgYWNjdXJhY3kgd2l0aCAwLjg0IEFVQy4gVGhlIHNlbGVjdGVkIGZlYXR1cmVzIHByZWRpY3Rpb24gcmVzdWx0cyAoNiBzZWxlY3RlZCBmZWF0dXJlczopIGEgc2ltaWxhciByZXN1bHQgY29tcGFyZWQgd2l0aCBhbGwgZmVhdHVyZXMgaW5jbHVkZWQgZm9yIHByZWRpY3Rpb24uIFRoaXMgaW5kaWNhdGVzIGlmIEkgZmluZSB0dW5lIHRoZSBoeXBlcnBhcmFtZXRlcnMgYW5kIGluY3JlYXNlIHRoZSBpdGVyYXRpb25zIHdpdGggdGhvc2Ugc2VsZWN0ZWQgZmVhdHVyZXMsIHN0aWxsIGhhdmUgYSBjaGFuY2UgdG8gaW5jcmVhc2UgdGhlIG1vZGVsIGFjY3VyYWN5LgoyLiBUaGlzIHJpZGVyLXNoYXJlIGNvbXBhbnkgKGEuay5hIFViZXIpIG5vdCBzYWxlIHJpZGVzLCBpdCBzYWxlcyB0aW1lLiBNb3JlIHNwZWNpZmljYWxseSwgaXQgc2FsZXMgdGhlIHBlcmNlcHRpb24gb2YgdGltZS4gSXQgd291bGQgYmUgaW50ZXJzdGluZyB0byBzZWUgdGhlIHRpbWVzdGFtcCBkYXRhIGFuZCBob3cgaXQgcGxheSBvdXQgd2l0aCBkaWZmZXJlbnQgZmVhdHVyZXMuCgoKQmVsb3cgYXJlIHR3byBmdW5jdGlvbnMgZm9yIHNvbWUgdmlzdWxpemF0aW9uLgpgYGB7cn0KIyMjIyMgZnVuY3Rpb24uMQptdWx0aXBsb3QgPC0gZnVuY3Rpb24oLi4uLCBwbG90bGlzdD1OVUxMLCBmaWxlLCBjb2xzPTEsIGxheW91dD1OVUxMKSB7CiAgcmVxdWlyZShncmlkKQogIAogICMgTWFrZSBhIGxpc3QgZnJvbSB0aGUgLi4uIGFyZ3VtZW50cyBhbmQgcGxvdGxpc3QKICBwbG90cyA8LSBjKGxpc3QoLi4uKSwgcGxvdGxpc3QpCiAgCiAgbnVtUGxvdHMgPSBsZW5ndGgocGxvdHMpCiAgCiAgIyBJZiBsYXlvdXQgaXMgTlVMTCwgdGhlbiB1c2UgJ2NvbHMnIHRvIGRldGVybWluZSBsYXlvdXQKICBpZiAoaXMubnVsbChsYXlvdXQpKSB7CiAgICAjIE1ha2UgdGhlIHBhbmVsCiAgICAjIG5jb2w6IE51bWJlciBvZiBjb2x1bW5zIG9mIHBsb3RzCiAgICAjIG5yb3c6IE51bWJlciBvZiByb3dzIG5lZWRlZCwgY2FsY3VsYXRlZCBmcm9tICMgb2YgY29scwogICAgbGF5b3V0IDwtIG1hdHJpeChzZXEoMSwgY29scyAqIGNlaWxpbmcobnVtUGxvdHMvY29scykpLAogICAgICAgICAgICAgICAgICAgICBuY29sID0gY29scywgbnJvdyA9IGNlaWxpbmcobnVtUGxvdHMvY29scykpCiAgfQogIAogIGlmIChudW1QbG90cz09MSkgewogICAgcHJpbnQocGxvdHNbWzFdXSkKICAgIAogIH0gZWxzZSB7CiAgICAjIFNldCB1cCB0aGUgcGFnZQogICAgZ3JpZC5uZXdwYWdlKCkKICAgIHB1c2hWaWV3cG9ydCh2aWV3cG9ydChsYXlvdXQgPSBncmlkLmxheW91dChucm93KGxheW91dCksIG5jb2wobGF5b3V0KSkpKQogICAgCiAgICAjIE1ha2UgZWFjaCBwbG90LCBpbiB0aGUgY29ycmVjdCBsb2NhdGlvbgogICAgZm9yIChpIGluIDE6bnVtUGxvdHMpIHsKICAgICAgIyBHZXQgdGhlIGksaiBtYXRyaXggcG9zaXRpb25zIG9mIHRoZSByZWdpb25zIHRoYXQgY29udGFpbiB0aGlzIHN1YnBsb3QKICAgICAgbWF0Y2hpZHggPC0gYXMuZGF0YS5mcmFtZSh3aGljaChsYXlvdXQgPT0gaSwgYXJyLmluZCA9IFRSVUUpKQogICAgICAKICAgICAgcHJpbnQocGxvdHNbW2ldXSwgdnAgPSB2aWV3cG9ydChsYXlvdXQucG9zLnJvdyA9IG1hdGNoaWR4JHJvdywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXlvdXQucG9zLmNvbCA9IG1hdGNoaWR4JGNvbCkpCiAgICB9CiAgfQp9CiMjIyMjIGZ1bmN0aW9uIDIuCnNlZV9kaXN0cmlidXRpb24gPC0gZnVuY3Rpb24oKXsKICByZXF1aXJlKGdncGxvdDIpCiAgcDEgPC0gIGRmICU+JQogICAgZ2dwbG90KGFlcyhjaHVybiwgZmlsbCA9IGNodXJuKSkgKwogICAgZ2VvbV9iYXIoKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCiAgCiAgcDIgPC0gZGYgJT4lCiAgICBnZ3Bsb3QoYWVzKGF2Z19kaXN0LCBmaWxsID0gY2h1cm4pKSArCiAgICBnZW9tX2RlbnNpdHkoKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpIAogIAogIHAzIDwtIGRmICU+JQogICAgZ2dwbG90KGFlcyhhdmdfcmF0aW5nX2J5X2RyaXZlciwgZmlsbCA9IGNodXJuKSkgKwogICAgZ2VvbV9iYXIoKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICAgIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsMjAwMDAwKSkKICAKICBwNCA8LSBkZiAlPiUKICAgIGdncGxvdChhZXMoYXZnX3JhdGluZ19vZl9kcml2ZXIsIGZpbGwgPSBjaHVybikpICsKICAgIGdlb21fYmFyKCkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLDIwMDAwMCkpCiAgCiAgcDUgPC0gZGYgJT4lCiAgICBnZ3Bsb3QoYWVzKGF2Z19zdXJnZSwgZmlsbCA9IGNodXJuKSkgKwogICAgZ2VvbV9kZW5zaXR5KCkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQogIAogIGRmIDwtIGRmICU+JSBtdXRhdGUoc3VyZ2Vfb3Jfbm90ID0gaWZlbHNlKGF2Z19zdXJnZT09MSwibm90X3N1cmdlZCIsInN1cmdlZCIpKQogIGRmICU+JQogICAgZ2dwbG90KGFlcyhzdXJnZV9vcl9ub3QsIGZpbGwgPSBjaHVybikpICsKICAgIGdlb21fYmFyKCkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQogIAogIHA2IDwtIGRmICU+JQogICAgZ2dwbG90KGFlcyhzdXJnZV9wY3QsIGZpbGwgPSBjaHVybikpICsKICAgIGdlb21fZGVuc2l0eSgpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKICAKICBwNyA8LSBkZiAlPiUKICAgIGdncGxvdChhZXModHJpcHNfaW5fZmlyc3RfMzBfZGF5cywgZmlsbCA9IGNodXJuKSkgKwogICAgZ2VvbV9kZW5zaXR5KGFlcyh5ID0gKC4uY291bnQuLikvc3VtKC4uY291bnQuLikpKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCiAgCiAgZGYgPC0gZGYgJT4lIG11dGF0ZSh1c2VkX29yX25vdCA9IGlmZWxzZSh0cmlwc19pbl9maXJzdF8zMF9kYXlzPT0wLCJub3RfdXNlZCIsInVzZWQiKSkKICBkZiAlPiUgZ2dwbG90KGFlcyh1c2VkX29yX25vdCwgZmlsbCA9IGNodXJuKSkgKwogICAgZ2VvbV9kZW5zaXR5KGFlcyh5ID0gKC4uY291bnQuLikvc3VtKC4uY291bnQuLikpKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCiAgCiAgcDggPC0gZGYgJT4lCiAgICBnZ3Bsb3QoYWVzKGx1eHVyeV9jYXJfdXNlciwgZmlsbCA9IGNodXJuKSkgKwogICAgZ2VvbV9iYXIoKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCiAgCiAgcDkgPC0gZGYgJT4lCiAgICBnZ3Bsb3QoYWVzKHdlZWtkYXlfcGN0LCBmaWxsID0gY2h1cm4pKSArCiAgICBnZW9tX2RlbnNpdHkoKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCiAgCiAgZGYgPC0gZGYgJT4lIG11dGF0ZSh3ZWVrZGF5X2J1Y2tldCA9IGNhc2Vfd2hlbigKICAgIHdlZWtkYXlfcGN0ID09IDAgfiAiemVybyIsCiAgICAod2Vla2RheV9wY3QgPiAwKSAmICh3ZWVrZGF5X3BjdCA8IDEwMCkgfiAic29zbyIsCiAgICB3ZWVrZGF5X3BjdCA9PTEwMCB+ICJldmVyeWRheSIKICApKSAKICBnZ3Bsb3QoZGYsIGFlcyh4ID0gZmFjdG9yKHdlZWtkYXlfYnVja2V0KSxmaWxsPWNodXJuKSkgKyAgCiAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSkgCiAgCiAgcDEwIDwtIGRmICU+JQogICAgZ2dwbG90KGFlcyhjaXR5X3ZhbHVlLCBmaWxsID0gY2h1cm4pKSArCiAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgICBmYWNldF93cmFwKH5jaHVybikKICAKICBwMTEgPC0gZGYgJT4lCiAgICBnZ3Bsb3QoYWVzKHBob25lLCBmaWxsID0gY2h1cm4pKSArCiAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgICBmYWNldF93cmFwKH5jaHVybikKICAKICAjIE9uZSBvZiB0aGUga2V5IHBvaW50cyBvZiBtYWNoaW5lIGxlYXJuaW5nIGZvciBjbGFzc2lmaWNhdGlvbiBpcyB0byBzZWUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBlYWNoIGZlYXR1cmUuIAogICMgQmFzZWQgb24gZXhwZXJpZW5jZSwgdGhlIG1vcmUgZmVhdHVyZSdzIGRpc3RyaWJ1dGlvbiBhcGFydCBmcm9tIGVhY2ggb3RoZXIsIG1vcmUgZWFzaWVyIHRvIGNsYXNzaWZ5IHRoZSBsYWJlbCByaWdodC4KICAjIExldCBtZSByZW1vdmUgY2F0ZWdvcmljYWwgZmVhdHVyZXMgYW5kIHBsb3QgZGlzdHJpYnV0aW9uIG9ubHkgb24gb25lIGNvbHVtbiBhbmQgc2VlIChCZWNhdXNlIHRoZSBjb2RlIHBhcnQgaXMgc2ltcGx5IGNoYW5nZSBnZW9tX2JhciB0byAgICAjIGdlb21fZGVuc2l0eSwgc28gaGVyZSB3aWxsIG5vdCBpbmNsdWRlIHRoZSBjb2RlIGluc3RlYWQganVzdCByZXN1bHRzKQogIGZpbmFsIDwtICBtdWx0aXBsb3QocDIscDMscDQscDUscDYscDcscDksY29scz0xKQogIHJldHVybihmaW5hbCkKfQojIyMjIwpgYGAKCiMjIyMgRGVmaW5lIHRoZSBwcm9ibGVtcwoqIENsYXNzaWZ5IGlmIGEgdXNlciBpcyBnb2luZyB0byBjaHVybiBvciBub3QKKiBGaW5kIG91dCB0aGUgZmFjdG9ycyBhcmUgdGhlIGJlc3QgZm9yIHByZWRpY3RvcnMgZm9yIGNodXJuIGFuZCBleHBsaWFuIHdoeT8KKiBPZmZlciB0aGUgc29sdXRpb24gdG8gcmVkdWNlIHRoZSBjaHVybiByYXRlIGZvciB0aGlzIGNvbXBhbnkgYS5rLmEuIFViZXIKCiMjIyMgU3RlcCAxLiAgUGVyZm9ybSBhbnkgY2xlYW5pbmcsIGV4cGxvcmF0b3J5IGFuYWx5c2lzLCBhbmQvb3IgdmlzdWFsaXphdGlvbnMgdG8gdXNlIHRoZSBwcm92aWRlZC4gZGF0YSBmb3IgdGhpcyBhbmFseXNpcy4KCkluc3RhbGwgcGFja2FnZXMKYGBge3J9CiMgT25seSBydW4gdGhlIGZpcnN0IHRpbWUKaW5zdGFsbC5wYWNrYWdlcyhjKCJnZ3Bsb3QiLCJkcGx5ciIsInRpZHlyIiwidGlkeXZlcnNlIiwicGxvdGx5IiwiY2FyZXQiLCJjb3JycGxvdCIsInBST0MiLCJtbGJlbmNoIikpCmBgYApMb2FkIHBhY2thZ2VzCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShjYXJldCkKbGlicmFyeShjb3JycGxvdCkKbGlicmFyeShwUk9DKQpsaWJyYXJ5KG1sYmVuY2gpCm9wdGlvbnMoc2NpcGVuID0gOTk5LCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKSAgICMgQXZvaWQgYXV0b21hdGljIHNjaWVudGlmaWMgbm90YXRpb24gb2YgbnVtYmVycwpgYGAKCgpgYGB7cn0KZGYgPC0gcmVhZC5jc3YoZmlsZT0iY2h1cm4uY3N2IiwgaGVhZGVyPVRSVUUsIHNlcD0iLCIpCiMgZ2F0aGVyIGNpdHkgYW5kIHBob25lIHR5cGUgZnJvbSB3aWRlIGZvcm1hdCB0byBsb25nIGZvcm1hdApkZiA8LSBkZiAlPiUgCiAgbXV0YXRlKGNpdHkgPSBjYXNlX3doZW4oCiAgICAoY2l0eV9Bc3RhcG9yID09IDEpIH4gIkFzdGFwb3IiLAogICAgKGNpdHlfS2luZy5zLkxhbmRpbmcgPT0gMSkgIH4gIktpbmciLAogICAgKGNpdHlfV2ludGVyZmVsbCA9PSAxKSB+ICJXaW50ZXJmZWxsIikpICU+JSAKICBtdXRhdGUocGhvbmUgPSBjYXNlX3doZW4oCiAgICAocGhvbmVfQW5kcm9pZCA9PSAxKSAgfiAiQW5kcm9pZCIsCiAgICAocGhvbmVfaVBob25lID09IDEpIH4gImlQaG9uZSIsCiAgICAocGhvbmVfbm9fcGhvbmUgPT0gMSkgfiAiT3RoZXIiKSkgJT4lIAogIG11dGF0ZShjaHVybj1pZmVsc2UoY2h1cm49PTEsImNodXJuZWQiLCJub3RfY2h1cm5lZCIpKQpkZiA8LSBzdWJzZXQoZGYsIHNlbGVjdCA9IC1jKGNpdHlfQXN0YXBvcjpwaG9uZV9ub19waG9uZSkpCmhlYWQoZGYpCmBgYApgYGB7cn0KIyMgc3VtbWFyeSBkYXRhc2V0cyB0byBnZXQgYSBzZW5zZSBvZiBkYXRhIGRpc3RyaWJ1dGlvbi4Kc3VtbWFyeShkZikKIyMgY2hlY2sgZGF0YSB0eXBlCnN0cihkZikKIyMgY2hlY2sgbWlzc2luZyB2YWx1ZQpzdW0oaXMubmEoZGYpKQpgYGAKSSBnb3Qgc29tZSBzZW5zZSBvZiB3aGF0IGRhdGEgdHlwZSB0aGlzIGRhdGFzZXQgaGFzIGFuZCB0aGVyZSBpcyBubyBtaXNzaW5nIHZhbHVlLiBZb3UgZ3V5cyBhcmUgc28gbmljZS4KCk5leHQsIGNoZWNrIHRoZSBkaXN0cmlidXRpb24gb2YgY2h1cm4gYW5kIGVhY2ggZmVhdHVyZSB0byBnZXQgYSBiZXR0ZXIgc2Vuc2UuIFRoZSBjb2RlIGJlbG93IGlzIGZvciB0aGUgZGlzdHJpYnV0aW9uIGJ5IGVhY2ggZmVhdHVyZSBhZ2FpbnN0IHdpdGggY2h1cm4gb3Igbm90IGNodXJuLgpTb21lIG9mIHRoZSBleHBsb3JhdG9yeSBjb2RlIGZvciBkaXN0cmlidXRpb24gd2l0aGluIGNodXJuIGNsYXNzIEkgZGlkIG5vdCBzaG93LgoKYGBge3J9CnAxIDwtICBkZiAlPiUKICBnZ3Bsb3QoYWVzKGNodXJuLCBmaWxsID0gY2h1cm4pKSArCiAgZ2VvbV9iYXIoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKcDEKYGBgCjYyJSBvZiB1c2VyIGNodXJuZWQsIG9vcHMuLi4KYGBge3J9CnAyIDwtIGRmICU+JQogIGdncGxvdChhZXMoYXZnX2Rpc3QsIGZpbGwgPSBjaHVybikpICsKICBnZW9tX2JhcigpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpIApwMgpgYGAKSXQgc2VlbXMgc29tZSBvdXRsaWVyIGxvbmcgZGlzdGFudGNlIGdyZWF0ZXIgdGhhbiA1MCBtaWxlcy4gQW5kIGEgbG90IG9mIHNtYWxsIGRpc3RhbnQsIHNlZSBpZiBJIGNhbiB2aXN1YWwgdGhlbQpUaGUgbWF4IGNvdW50IGRpc3RhbmNlIGlzIDIuNDcgbWlsZXMKYGBge3J9CnAzIDwtIGRmICU+JQogIGdncGxvdChhZXMoYXZnX3JhdGluZ19ieV9kcml2ZXIsIGZpbGwgPSBjaHVybikpICsKICBnZW9tX2JhcigpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLDMwMDAwKSkgKyBmYWNldF9ncmlkKH5jaHVybikKcDMKYGBgCkxvb2sgbGlrZSB0aGUgZHJpdmVyIHJhdGVzIGN1c3RvbWVyIGFib3ZlIHRoZSA3NSUgcGVyY2VudGlsZSBhdCBhIDUgc3RhcnMgcmF0aW5nIHN5c3RlbS4gQW5kIHRoZSBjaHVybmVkIHVzZXIgaGFzIGhpZ2hlciA1IHN0YXJ0IHJhdGluZyB0aGFuIG5vbiBjaHVybmVkIHVzZXI/IEludGVyZXN0aW5nLiBCdXQgQ2h1cm5lZCB1c2VyIGhhcyBsZXNzIHJhdGluZyBpbiBnZW5lcmFsLiBMb29rcyBkcml2ZXIgZG9uJ3QgcmF0ZSB0aGF0IG11Y2guCmBgYHtyfQpwNCA8LSBkZiAlPiUKICBnZ3Bsb3QoYWVzKGF2Z19yYXRpbmdfb2ZfZHJpdmVyLCBmaWxsID0gY2h1cm4pKSArCiAgZ2VvbV9iYXIoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwzMDAwMCkpICsgZmFjZXRfZ3JpZCh+Y2h1cm4pCnA0CmBgYApSaWRlcidzIHJhdGluZyBkaXN0cmlidXRpb24gYWxzbyBhYm92ZSA3NSUgcGVyY2VudGlsZS4gQm90aCByaWRlciBhbmQgZHJpdmVyIHJhdGluZyBhcmUgbm90IG5vcm1hbCBkaXN0cmlidXRlLiBDb3VsZCBpdCBiZSBsZXNzIHNhbXBsZSBzaXplcyBvciBwZW9wbGUganVzdCBiZSBuaWNlIGFuZCByYXRpbmcgb3RoZXJzIHZlcnkgaGlnaCwgb3IgbGFjayBvciByZWZlcmVuY2UgZXhwZXJpZW5jZT8KYGBge3J9Cm11bHRpcGxvdChwMyxwNCxjb2xzPTIpCmBgYApTZXQgcmF0aW5nIGJ5IGRyaXZlciBhbmQgcmF0aW5nIG9mIGRyaXZlciBzaWRlIGJ5IHNpZGUsIGFuZCBpdCBzZWVtcyBkcml2ZXIgZG9uJ3QgbGlrZSB0byByYXRlIHJpZGVyIGNvbXBhcmVkIHdpdGggcmlkZXIgbGlrZXMgdG8gcmF0ZSBkcml2ZXIuIENodXJuZWQgaGFzIGhpZ2VyIDUgc3RhcnMgcmF0aW5nLiAKYGBge3J9CnA1IDwtIGRmICU+JQogIGdncGxvdChhZXMoYXZnX3N1cmdlLCBmaWxsID0gY2h1cm4pKSArCiAgZ2VvbV9iYXIoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIGZhY2V0X2dyaWQofmNodXJuKQpwNQpgYGAKSXQgbG9va3MgbGlrZSBhbm90aGVyIG91dGxpZXIgZm9yIHN1cmdlIHJhdGUuIFdhaXQgYSBzZWNvbmQhIGl0J3Mgbm90IG91dGxpZXIsIGl0J3MgemVybywgTGV0IG1lIGNsYXNzaWZ5IGl0IGFzIHN1cmdlIGFuZCBub24gc3VyZ2UuCmBgYHtyfQpkZiA8LSBkZiAlPiUgbXV0YXRlKHN1cmdlX29yX25vdCA9IGlmZWxzZShhdmdfc3VyZ2U9PTEsIm5vdF9zdXJnZWQiLCJzdXJnZWQiKSkKZGYgJT4lCiAgZ2dwbG90KGFlcyhzdXJnZV9vcl9ub3QsIGZpbGwgPSBzdXJnZV9vcl9ub3QpKSArCiAgZ2VvbV9iYXIoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIGZhY2V0X2dyaWQofmNodXJuKQpgYGAKTW9yZSB0aGFuIDYwJSB1c2VyIG5vdCBzdXJnZWQgKGNodXJuICsgbm90IGNodXJuKS4gQW5kIGl0IGNsZWFybHkgc2hvd3MgY2h1cm5lZCB1c2VyIGRvbid0IGRvIHN1cmdlLgpgYGB7cn0KcDYgPC0gZGYgJT4lCiAgZ2dwbG90KGFlcyhzdXJnZV9wY3QsIGZpbGwgPSBjaHVybikpICsKICBnZW9tX2JhcigpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgZmFjZXRfZ3JpZCh+Y2h1cm4pCnA2CmBgYApTYW1lIGFzIGF2Z19zdXJnZS4gQnV0IHRoZSBkaXN0cmlidXRpb24gaXMgaW50ZXJlc2luZyB3aGljaCBtb3N0IG9mIHN1cmdlIHBlcmNlbnQgYXJlIGluIHRoZSBsb3cgMjUlIHBlcmNlbnRpbGUuIFRvbyBleHBlbnNpdmUgdXNlciBzdGlsbCBjYXJlIGV2ZW4gdGhvdWdoIHRpbWUgaXMgaW1wb3J0YW50LgpgYGB7cn0KcDcgPC0gZGYgJT4lCiAgZ2dwbG90KGFlcyh0cmlwc19pbl9maXJzdF8zMF9kYXlzLCBmaWxsID0gY2h1cm4pKSArCiAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKS9zdW0oLi5jb3VudC4uKSkpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgZmFjZXRfZ3JpZCh+Y2h1cm4pCnA3CmBgYAoyMCUgdXNlciBkb24ndCB1c2UgdGhlIHNlcnZlciB0aGUgZmlyc3QgMzAgZGF5cyBhZnRlciBzaWdudXAgY2h1cm5lZCwgY29tcGFyZWQgd2l0aCAxMCUgdXNlciB3aG8gZG9uJ3QgY2h1cm4uIE1heWJlIHNob3VsZCBidWNrZXQgdGhvc2UgYnkgdXNlZCBhbmQgbm90IHVzZWQuCmBgYHtyfQpkZiA8LSBkZiAlPiUgbXV0YXRlKHVzZWRfb3Jfbm90XzMwZGF5cyA9IGlmZWxzZSh0cmlwc19pbl9maXJzdF8zMF9kYXlzPT0wLCJub3RfdXNlZCIsInVzZWQiKSkKZGYgJT4lIGdncGxvdChhZXModXNlZF9vcl9ub3RfMzBkYXlzLCBmaWxsID0gdXNlZF9vcl9ub3RfMzBkYXlzKSkgKwogIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikvc3VtKC4uY291bnQuLikpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIGZhY2V0X2dyaWQofmNodXJuKQpgYGAKMzUlIHVzZXIgKGNodXJuICsgbm90IGNodXJuKSBub3QgdXNlZCB0aGUgc2VydmljZSBhcyB0aGUgZmlyc3QgMzAgZGF5cy4gQW5kIGludGVyZXN0aW5nbHkgY2h1cm5lZCB1c2VyIGhhcyBoaWdoZXIgdXNlZCBwZXJjZW50IGluIGluIHdob2xlIGdyb3VwLgpgYGB7cn0KcDggPC0gZGYgJT4lCiAgZ2dwbG90KGFlcyhsdXh1cnlfY2FyX3VzZXIsIGZpbGwgPSBhcy5mYWN0b3IobHV4dXJ5X2Nhcl91c2VyKSkpICsKICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyBmYWNldF9ncmlkKH5jaHVybikKcDgKYGBgCjQyJSB1c2VyIGRvbid0IGdldCBsdXh1cnkgY2FyIGluIGNodXJuIGdyb3VwISBBbmQgbm90IGNodXJuIGdyb3VwIGlzIGhhbGQgYW5kIGhhbGYuIEkgc3VzcGVjdCB1c2VyIHdobyBnZXQgbHV4dXJ5IGNhciBkb24ndCBjYXJlIG11Y2ggYWJvdXQgdGhlIHByaWNlIHJhdGhlciB0aGFuIHRoZSB0aW1lLiBCdXQgdXNlciB3aG8ganVzdCByZWdpc3RlciBmb3IgZnVuIGRvbid0IHdhbm5hIHdhc3RlciBtb25leSBpbiBsdXh1cnkgY2FyLgpgYGB7cn0KcDkgPC0gZGYgJT4lCiAgZ2dwbG90KGFlcyh3ZWVrZGF5X3BjdCwgZmlsbCA9IGNodXJuKSkgKwogIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikvc3VtKC4uY291bnQuLikpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIGZhY2V0X2dyaWQofmNodXJuKQpwOQpgYGAKU28gaW50ZXJzdGluZy4gSW4gdGhlIGNodXJlZCBncm91cCwgbGFyZ2VzdCBwZXJjZW50IGlzIDEwMCUgLCBtb3JlIHRoYW4gMjUlIGV2ZXJ5IHdlZWtkYXkgdXNlLiBNYXliZSBjaHVybiB1c2VyIGp1c3Qgd2FubmEgdXNlIHRoZSBzZXJ2aWNlIGZvciBhIHNob3J0IHBlcmlvZCBvZiB0aW1lIGR1ZSB0byBzb21lIHJlYXNvbiBhbmQgZHJvcCBpdC4KTGV0J3MgZGlnIG1vcmUgYnkgYnVja2V0IHRoZSB3ZWVrZGF5IHBlcmNlbnQKYGBge3J9CmRmIDwtIGRmICU+JSBtdXRhdGUod2Vla2RheV9idWNrZXQgPSBjYXNlX3doZW4oCiAgd2Vla2RheV9wY3QgPT0gMCB+ICJ6ZXJvIiwKICAod2Vla2RheV9wY3QgPiAwKSAmICh3ZWVrZGF5X3BjdCA8IDEwMCkgfiAic29zbyIsCiAgd2Vla2RheV9wY3QgPT0xMDAgfiAiZXZlcnlkYXkiCikpIApnZ3Bsb3QoZGYsIGFlcyh4ID0gZmFjdG9yKHdlZWtkYXlfYnVja2V0KSxmaWxsPWNodXJuKSkgKyAgCiAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKS9zdW0oLi5jb3VudC4uKSkpIApgYGAKQ29uZmlybWVkLCAzMCUgZXZlcnkgZGF5IHVzZXIgYW5kIDIwJSBubyB1c2VyIHVzZXIgKENIVVJOIFBPVEVOVElBTCBBTEFSVCEhKSAKYGBge3J9CnAxMCA8LSBkZiAlPiUKICBnZ3Bsb3QoYWVzKGNpdHksIGZpbGwgPSBjaHVybikpICsKICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgICsgZmFjZXRfZ3JpZCh+Y2h1cm4pCnAxMApgYGAKS2luZyBpbiBjaHVybiBoYXMgbXVjaCBsZXNzIGNodXJuIHRoYW4gb3RoZXIgY2l0ZWlzLiBJbiB0b3RhbCwgV2ludGVyZmVsbCBoYXMgNDYlIHJpZGVyIGFuZCBLaW5nLnNsYW5kaW5nIGhhcyAyMCUgcmlkZXIgKCBjaHVybiArIG5vbmNodXJuICkuIEJ1dCBzaW5jZSB0aGlzIGlzIGEgY29yb3J0IG9mIHVzZXIsIHdlIHNlZSBpZiBsb2NhdGlvbiBhcmUgaW1wb3J0YW50IG9yIG5vdCBsYXRlci4gU2VlbXMgYXJlIHRocmVlIGxvY2F0aW9uIGFyZSBwc2RvIGxvY2F0aW9ucyBjYW4ndCBmaW5kIG9uIGdvb2dsZSBtYXBzLgpgYGB7cn0KcDExIDwtIGRmICU+JQogIGdncGxvdChhZXMocGhvbmUsIGZpbGwgPSBjaHVybikpICsKICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyBmYWNldF9ncmlkKH5jaHVybikKcDExCmBgYApJbiBjaHVybiBhbmQgbm90IGNodXJuIGNsYXNzLCBtb3JlIGlQaG9uZSB1c2VyIHRoYW4gQW5kcm9pZC4gNjklIG9mIGlQaG9uZSB1c2VyIGFuZCAzMCUgQW5kcm9pZCB1c2VyIGluIHRvdGFvbHkuCgpPbmUgb2YgdGhlIGtleSBwb2ludCBvZiBtYWNoaW5lIGxlYXJuaW5nIGZvciBjbGFzc2lmaWNhdGlvbiBpcyB0byBzZWUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBlYWNoIGZlYXR1cmUuIApCYXNlZCBvbiBleHBlcmllbmNlLCB0aGUgbW9yZSBmZWF0dXJlJ3MgZGlzdHJpYnV0aW9uIGFwYXJ0IGZyb20gZWFjaCBvdGhlciwgbW9yZSBlYXNpZXIgdG8gY2xhc3NpZnkgdGhlIGxhYmVsIHJpZ2h0LgpMZXQgbWUgcmVtb3ZlIGNhdGVnb3JpY2FsIGZlYXR1cmVzIGFuZCBwbG90IGRpc3RyaWJ1dGlvbiBvbmx5IG9uIG9uZSBjb2x1bW4gYW5kIHNlZQpgYGB7cn0Kc2VlX2Rpc3RyaWJ1dGlvbigpIApgYGAKU2VlbXMgbW9zdCBvZiB0aGUgZmVhdHVyZXMgZGlzdHJpYnV0aW9uIG9uIHRoZSBlaXRoZXIgbG93IGVuZCBvciBoaWdoIGVuZC4gQnV0IHdpdGluIGNodXJuIGFuZCBub3QgY2h1cm4sIGRpc3RyaWJ1dGlvbiBvZiBvbmUgZmVhdHVyZSBhcmUgdG9nZXRoZXIuICBJJ20gbW9yZSBsb29raW5nIGZvciBmZWF0dXJlJ3MgZGlzdHJpYnV0aW9uIGNhbiBzZXBhcmF0ZSBjaHVybiBhbmQgbm90IGNodXJuLiBNYXkgbm90IGJlIGEgZ29vZCBzaWduIGhlcmUuCgoKQ29ycmVsYXRpb24gYW5hbHlzaXMgZm9yIGZpbmQgdGhlIGhpZ2ggbmFkIGxvdyBjb3JyZWxhdGVkIGZlYXR1cmVzCmBgYHtyfQpudW0gPC0gYygiYXZnX2Rpc3QiLCJhdmdfcmF0aW5nX2J5X2RyaXZlciIsImF2Z19yYXRpbmdfb2ZfZHJpdmVyIiwiYXZnX3N1cmdlIiwic3VyZ2VfcGN0IiwidHJpcHNfaW5fZmlyc3RfMzBfZGF5cyIsIndlZWtkYXlfcGN0IikKZGZfbnVtIDwtIHNlbGVjdChkZiwgb25lX29mKG51bSkpCmNvcnJwbG90KGNvcihkZl9udW0sIHVzZT0iY29tcGxldGUub2JzIiksdHlwZT0ibG93ZXIiKQpgYGAKU2VlbXMgYXZnX2Rpc3QgaGFzIHNvbWUgc21hbGwgbmVnZXRpdmUgY29ycmxlYXRpb24gd2l0aCBhbGwgb3RoZXIgbnVtZXJpYyBmZWF0dXJlcywgYW5kIHdlZWtkYXkgaGFzIHNvbWUgcG9zaXRpdmUgY29ycmVsYXRpb24gd2l0aCBhdmdfc3VyZ2UgYW5kIHN1cmdlX3BjdC4KCkFsbCB0aGUgZmVhdHVyZXMgYXJlIGxlc3MgY29ycmVsYXRlZCBleGNlcHQgYXZnX3N1cmdlIGFuZCBzdXJnZV9wY3QsIGdvb2Qgc2lnbi4gTWF5YmUgbGF0ZXIgbW9yZSBlYXN5IHRvIGNhcHR1cmUgY2h1cm4gb3Igbm90IGNodXJuLgoKCgojIyMjIFN0ZXAgMi4gQnVpbGQgYSBwcmVkaWN0aXZlIG1vZGVsIHRvIGhlbHAgZGV0ZXJtaW5lIHdoZXRoZXIgb3Igbm90IGEgdXNlciB3aWxsIGNodXJuLgpJbiB0aGlzIHN0ZXAsIEknbSBnb2luZyB0byB1c2UgdHdvIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHRvIHByZWRpY3QgY2h1cm4gb3Igbm90IGNodXJuLgpBbG9uZyB3aXRoIHN0ZXAgMiwgdGhlcmUgYXJlIHNvbWUgc3RlcCAzLiBFdmFsdWF0ZSB0aGUgbW9kZWwgaW4gdGhlIHByb2Nlc3MsIGluIG9yZGVyIHRvIGV4cGxhaW4gdGhlIHBlcmZvcm1hbmNlIG9mIG1vZGVscyBhbmQgbW9kZWxzIGNvbXBhcmlzb24uCmBgYHtyfQpyZXF1aXJlKGNhcmV0KQojIyBmb3JtYXRpbmcgc29tZSBmZWF0dXJlcwpuYW1lcyhkZikKIyBbMV0gImF2Z19kaXN0IiAgICAgICAgICAgICAgICJhdmdfcmF0aW5nX2J5X2RyaXZlciIgICAiYXZnX3JhdGluZ19vZl9kcml2ZXIiICAKIyBbNF0gImF2Z19zdXJnZSIgICAgICAgICAgICAgICJzdXJnZV9wY3QiICAgICAgICAgICAgICAidHJpcHNfaW5fZmlyc3RfMzBfZGF5cyIKIyBbN10gImx1eHVyeV9jYXJfdXNlciIgICAgICAgICJ3ZWVrZGF5X3BjdCIgICAgICAgICAgICAiY2h1cm4iICAgICAgICAgICAgICAgICAKIyBbMTBdICJjaXR5IiAgICAgICAgICAgICAgICAgICAicGhvbmUiICAgICAgICAgICAgICAgICAgInN1cmdlX29yX25vdCIgICAgICAgICAgCiMgWzEzXSAidXNlZF9vcl9ub3RfMzBkYXlzIiAgICAgICJ3ZWVrZGF5X2J1Y2tldCIgIApjb2xzIDwtIGMoImx1eHVyeV9jYXJfdXNlciIsICJjaXR5IiwgInBob25lIiwgInN1cmdlX29yX25vdCIsInVzZWRfb3Jfbm90XzMwZGF5cyIgLCAid2Vla2RheV9idWNrZXQiICwiY2h1cm4iKQpkZltjb2xzXSA8LSBsYXBwbHkoZGZbY29sc10sIGZhY3RvcikKY29sc19udW0gPC0gYygiYXZnX2Rpc3QiLCAiYXZnX3JhdGluZ19ieV9kcml2ZXIiLCAiYXZnX3JhdGluZ19vZl9kcml2ZXIiLCAiYXZnX3N1cmdlIiwic3VyZ2VfcGN0IiAsICJ0cmlwc19pbl9maXJzdF8zMF9kYXlzIiAsIndlZWtkYXlfcGN0IikKZGZbY29sc19udW1dIDwtIGxhcHBseShkZltjb2xzX251bV0sIGFzLm51bWVyaWMpCgojIE1vdmUgY2h1cm4gdG8gdGhlIGxhc3QgY29sdW1uLCBvdGhlcndpc2UgaGFyZCB0byBzZWUKY29sX2lkeCA8LSBncmVwKCJjaHVybiIsIG5hbWVzKGRmKSkKZGYgPC0gZGZbLCBjKCAoMTpuY29sKGRmKSlbLWNvbF9pZHhdLCBjb2xfaWR4KV0KYGBgClBhcnRpdGlvbiB0aGUgZGF0YXNldCB0byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzCkxldCdzIGRvIDgwOjIwLCA4MCUgZm9yIHRyaW5pbmcsIDIwJSBmb3IgdGVzdGluZwpgYGB7cn0KdHJhaW5JbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRmJGNodXJuLCBwID0gMC44LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lcyA9IDEpCmhlYWQodHJhaW5JbmRleCkKZGZfdHJhaW4gPC0gZGZbIHRyYWluSW5kZXgsXQpkZl90ZXN0ICA8LSBkZlstdHJhaW5JbmRleCxdCmBgYApTdGFydCB0byBidWlsZCB0aGUgbW9kZWwKYGBge3IsIGluY2x1ZGU9RkFMU0V9CmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCBjbGFzc1Byb2JzID0gVFJVRSwgbnVtYmVyPTUsIHN1bW1hcnlGdW5jdGlvbj10d29DbGFzc1N1bW1hcnkpCnNlZWQgPC0gMQptZXRyaWMgPC0gIlJPQyIKc2V0LnNlZWQoc2VlZCkKIyBtdHJ5IDwtIHNxcnQobmNvbChkZikpCiMgdHVuZWdyaWQgPC0gZXhwYW5kLmdyaWQoLm10cnk9bXRyeSkKIyBnYm1HcmlkIDwtICBleHBhbmQuZ3JpZChpbnRlcmFjdGlvbi5kZXB0aCA9IGMoMSwgNSwgOSksIAojICAgICAgICAgICAgICAgICAgICAgICAgIG4udHJlZXMgPSAoMTozMCkqNTAsIAojICAgICAgICAgICAgICAgICAgICAgICAgIHNocmlua2FnZSA9IDAuMSwKIyAgICAgICAgICAgICAgICAgICAgICAgICBuLm1pbm9ic2lubm9kZSA9IDIwKQpnYm1fZGVmYXVsdCA8LSB0cmFpbihjaHVybn4uLCBkYXRhPWRmX3RyYWluLCBtZXRob2Q9ImdibSIsIG1ldHJpYz1tZXRyaWMsIHRyQ29udHJvbD1jb250cm9sKQpwcmludChnYm1fZGVmYXVsdCkKIyBTdG9jaGFzdGljIEdyYWRpZW50IEJvb3N0aW5nIAojIAojIDQwMDAxIHNhbXBsZXMKIyAgICAxMyBwcmVkaWN0b3IKIyAgICAgMiBjbGFzc2VzOiAnY2h1cm5lZCcsICdub3RfY2h1cm5lZCcgCiMgCiMgTm8gcHJlLXByb2Nlc3NpbmcKIyBSZXNhbXBsaW5nOiBDcm9zcy1WYWxpZGF0ZWQgKDUgZm9sZCkgCiMgU3VtbWFyeSBvZiBzYW1wbGUgc2l6ZXM6IDMyMDAxLCAzMjAwMCwgMzIwMDEsIDMyMDAwLCAzMjAwMiAKIyBSZXNhbXBsaW5nIHJlc3VsdHMgYWNyb3NzIHR1bmluZyBwYXJhbWV0ZXJzOgojIAojICAgaW50ZXJhY3Rpb24uZGVwdGggIG4udHJlZXMgIFJPQyAgICAgICAgU2VucyAgICAgICBTcGVjICAgICAKIyAgIDEgICAgICAgICAgICAgICAgICAgNTAgICAgICAwLjgyMjY3NzIgIDAuODg3MjQ1OSAgMC41NjAyOTA0CiMgICAxICAgICAgICAgICAgICAgICAgMTAwICAgICAgMC44MzQyNzAzICAwLjg3NzAyODQgIDAuNTk5MzA4OQojICAgMSAgICAgICAgICAgICAgICAgIDE1MCAgICAgIDAuODM5MTkzNyAgMC44NzA0MTcwICAwLjYyMTM3NzcKIyAgIDIgICAgICAgICAgICAgICAgICAgNTAgICAgICAwLjgzODUyNjQgIDAuODcwMzc2OSAgMC42MTY5MjM2CiMgICAyICAgICAgICAgICAgICAgICAgMTAwICAgICAgMC44NDU5MzQwICAwLjg1ODM1NjIgIDAuNjUwNDI1NwojICAgMiAgICAgICAgICAgICAgICAgIDE1MCAgICAgIDAuODQ4NzkzMSAgMC44NTg5OTc0ICAwLjY1NzY3MTQKIyAgIDMgICAgICAgICAgICAgICAgICAgNTAgICAgICAwLjg0Mzg0NDIgIDAuODYzNjg1MiAgMC42MzYwNjgxCiMgICAzICAgICAgICAgICAgICAgICAgMTAwICAgICAgMC44NDg4NDM3ICAwLjg1OTc5ODcgIDAuNjU3MTM5MwojICAgMyAgICAgICAgICAgICAgICAgIDE1MCAgICAgIDAuODUxMDYzNyAgMC44NjE2MDE4ICAwLjY2MDEzMDUKIyAKIyBUdW5pbmcgcGFyYW1ldGVyICdzaHJpbmthZ2UnIHdhcyBoZWxkIGNvbnN0YW50IGF0IGEgdmFsdWUgb2YgMC4xCiMgVHVuaW5nIHBhcmFtZXRlcgojICduLm1pbm9ic2lubm9kZScgd2FzIGhlbGQgY29uc3RhbnQgYXQgYSB2YWx1ZSBvZiAxMAojIFJPQyB3YXMgdXNlZCB0byBzZWxlY3QgdGhlIG9wdGltYWwgbW9kZWwgdXNpbmcgIHRoZSBsYXJnZXN0IHZhbHVlLgojIFRoZSBmaW5hbCB2YWx1ZXMgdXNlZCBmb3IgdGhlIG1vZGVsIHdlcmUgbi50cmVlcyA9IDE1MCwgaW50ZXJhY3Rpb24uZGVwdGggPSAzLCBzaHJpbmthZ2UgPSAwLjEKIyBhbmQgbi5taW5vYnNpbm5vZGUgPSAxMC4KYGBgCkFmdGVyIGNhbGN1bHRpbmcgUk9DLCBsZXQncyBwbG90IGl0IGFuZCBjYWxjdWxhdGUgQVVDLgpgYGB7cn0KcGxvdChnYm1fZGVmYXVsdCkKYGBgCkR1ZSB0byBydW4gdGltZSBjb25zdHJhaW4sIGhlcmUgZGlkbid0IHVzZSBrLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbi4gU28gb25seSBjaG9vc2Ugc2ltcGxlIGNyb3NzIHZhbGlkYXRpb24gd2l0aCBsZXNzIGl0ZXJhdGlvbnMuCkFsc28gSSBkaWQgbm90IHR1bmUgdGhlIHBhcmFtZXRlciBieSBncmlkIHNlYXJjaGluZyBkdWUgdG8gdGltZSBsaW1pdC4KYGBge3J9CnNldC5zZWVkKDEpCnB0X2dibSA8LSBwcmVkaWN0KGdibV9kZWZhdWx0LCBuZXdkYXRhID0gZGZfdGVzdCkKY29uZnVzaW9uTWF0cml4KHB0X2dibSwgZGZfdGVzdCRjaHVybikKYGBgCldlIGdvdCA3OCUgYWNjdXJhY3kuCmBgYHtyfQpzZXQuc2VlZCgxKQpnYm1fcHJvYnMgPC0gcHJlZGljdChnYm1fZGVmYXVsdCwgbmV3ZGF0YSA9IGRmX3Rlc3QsIHR5cGUgPSAicHJvYiIpCmdibV9ST0MgPC0gcm9jKHByZWRpY3Rvcj1nYm1fcHJvYnMkY2h1cm5lZCwKICAgICAgICAgICAgICAgcmVzcG9uc2U9ZGZfdGVzdCRjaHVybiwKICAgICAgICAgICAgICAgbGV2ZWxzPXJldihsZXZlbHMoZGZfdGVzdCRjaHVybikpKQpnYm1fUk9DJGF1YwpgYGAKVGhlIEFyZWEgdW5kZXIgdGhlIGN1cnZlIChBVUMpIGlzIGFyb3VuZCAwLjg1LCB3aGljaCBpcyBub3QgYmFkLgpgYGB7cn0KcGxvdChnYm1fUk9DLG1haW49IkdCTSBST0MiKQpgYGAKUk9DIGNhbGN1YWx0ZWQgYnkgc2Vuc2l0aXZpdHkgKFRydWUgcG9zaXRpdmUpL3NwZWNpZmljaXR5IChGYWxzZSBwb3NpdGl2ZSkuIFdlIHdhbnQgUk9DIGhpZ2hlciBhbmQgQVVDICh1bmRlciBjdXJ2ZSBhcmVhKSBncmVhdGVyLgpgYGB7cn0KaGlzdDEgPC0gaGlzdG9ncmFtKH5nYm1fcHJvYnMkY2h1cm5lZHxkZl90ZXN0JGNodXJuLHhsYWI9IlByb2JhYmlsaXR5IG9mIENodXJuLCBHQk0iLHlsaW0gPSBjKDAsMzApKQpoaXN0MQpgYGAKVGhlIGhpc3RvZ3JhbSBvZiBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24gb2YgY2h1cm4gb3Igbm90IGNodXJuIHVzZXJzLiAKU2VlbXMgdGhlIG1vZGVsIHByZWRpY3RzIHdlbGwgKGhpZ2ggcHJvYmFiaWxpdHkpIG9mIGNodXJuIHVzZXIuCkxldCdzIHRyeSBhbm90aGVyIG1vZGVsIHhnYm9vc3QgaW4gY2FyZXQgcGFja2FnZS4KYGBge3IsIGluY2x1ZGU9RkFMU0V9CmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCBjbGFzc1Byb2JzID0gVFJVRSwgbnVtYmVyPTUsIHN1bW1hcnlGdW5jdGlvbj10d29DbGFzc1N1bW1hcnkpCnNlZWQgPC0gMQptZXRyaWMgPC0gIlJPQyIKc2V0LnNlZWQoc2VlZCkKIyBtdHJ5IDwtIHNxcnQobmNvbChkZikpCiMgdHVuZWdyaWQgPC0gZXhwYW5kLmdyaWQoLm10cnk9bXRyeSkKeGdiX2RlZmF1bHQgPC0gdHJhaW4oY2h1cm5+LiwgZGF0YT1kZl90cmFpbiwgbWV0aG9kPSJ4Z2JUcmVlIiwgbWV0cmljPW1ldHJpYywgdHJDb250cm9sPWNvbnRyb2wpCnByaW50KHhnYl9kZWZhdWx0KQpgYGAKQWZ0ZXIgaXRlcmFjdGlvbiBvZiBjYWxjdWxhdGluZyBST0MsIGxldCdzIHBsb3QgdGhlbSBhbmQgY2FsY3VsYXRlIEFVQy4KYGBge3J9CnBsb3QoeGdiX2RlZmF1bHQpCmBgYAoKYGBge3J9CnB0X3hnYiA8LSBwcmVkaWN0KHhnYl9kZWZhdWx0LCBuZXdkYXRhID0gZGZfdGVzdCkKY29uZnVzaW9uTWF0cml4KHB0X3hnYiwgZGZfdGVzdCRjaHVybikKYGBgCmBgYHtyfQpzZXQuc2VlZCgxKQp4Z2JfcHJvYnMgPC0gcHJlZGljdCh4Z2JfZGVmYXVsdCwgbmV3ZGF0YSA9IGRmX3Rlc3QsIHR5cGUgPSAicHJvYiIpCnhnYl9ST0MgPC0gcm9jKHByZWRpY3Rvcj14Z2JfcHJvYnMkY2h1cm5lZCwKICAgICAgICAgICAgICAgcmVzcG9uc2U9ZGZfdGVzdCRjaHVybiwKICAgICAgICAgICAgICAgbGV2ZWxzPXJldihsZXZlbHMoZGZfdGVzdCRjaHVybikpKQp4Z2JfUk9DJGF1YwpgYGAKVGhlIGFyZWEgdW5kZXIgdGhlIGN1cnZlIGlzIGFyb3VuZCAwLjg1LCBhIGxpdHRsZSBoaWdlciB0aGFuIGdibQpgYGB7cn0KcGxvdCh4Z2JfUk9DLG1haW49IlhHQkJPT1NUIFJPQyIpCmBgYApST0MgY2FsY3VhbHRlZCBieSBzZW5zaXRpdml0eSAoVHJ1ZSBwb3NpdGl2ZSkvc3BlY2lmaWNpdHkgKEZhbHNlIHBvc2l0aXZlKS4gV2Ugd2FudCBST0MgaGlnaGVyIGFuZCBBVUMgKHVuZGVyIGN1cnZlIGFyZWEpIGdyZWF0ZXIuCmBgYHtyfQpoaXN0MiA8LSBoaXN0b2dyYW0ofnhnYl9wcm9icyRjaHVybmVkfGRmX3Rlc3QkY2h1cm4seGxhYj0iUHJvYmFiaWxpdHkgb2YgQ2h1cm4sIFhHQk9PU1QiLHlsaW0gPSBjKDAsMzApKQpoaXN0MgpgYGAKU2VlbXMgdGhlIG1vZGVsIHByZWRpY3RzIHdhcyB2ZXJ5IHNpbWlsYXIgY29tcGFyZWQgd2l0aCBnYm0gZm9yIHByZWRpY3RpbmcgY2h1cm4uIEFuZCBtb3JlIGJsdWUgYXJlYSBpbiB0aGUgaGlnaCBlbmQgb2YgY2h1cm4gY2F0ZWdvcnkuCgoKIyMjIyBTdGVwIDMuIEV2YWx1dGlvbiB0aGUgbW9kZWwKU29tZSBwYXJ0cyBvZiB0aGlzIHN0ZXAgaGFzIGJlZW4gZG9uZSB0aGUgc3RlcDIuCldlIG5lZWQgZG8gYSBzaWRlIGJ5IHNpZGUgY29tcGFyaXNvbi4KV2UgbmVlZCB0byBmaWd1cmUgb3V0IGlmIHRoZSB0d28gbWV0aG9kIHJlc3VsdHMgYXJlIHN0YXRpc3RpY2FsIGRpZmZlcmVudC4gU28gd2UgZmlyc3QgY29sbGVjdCB0aGUgcmVzYW1wbGluZyByZXN1bHRzIHVzaW5nIHJlc2FtcGxlcy4KYGBge3J9CnNldC5zZWVkKDEpCnJlc2FtcHMgPC0gcmVzYW1wbGVzKGxpc3QoR0JNID0gZ2JtX2RlZmF1bHQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgWEdCQk9PU1QgPSB4Z2JfZGVmYXVsdCkpCnN1bW1hcnkocmVzYW1wcykKdHJlbGxpcy5wYXIuc2V0KGNhcmV0VGhlbWUoKSkKZG90cGxvdChyZXNhbXBzLCBtZXRyaWMgPSAiUk9DIikKYGBgCldlIGNhbiBzZWUgc29tZSBvdmVybGFwcGluZyBzb21ldGltZSAoaWYgc2V0IHNlZWQpIG9uIHRoZSBlcnJvciBiYXIgd2hpY2ggY2FuIGluZGljYXRlIG5vdCBzaWduaWZpY2FudCBkaWZmZXJlbnQuIEluIHRlcm1zIG9mIG1hZ25pdHVkZSBvZiB2YWx1ZXMsIHRoZSBST0MgdmFsdWVzIGFyZSBub3QgZGlmZmVyZW50LgoKSW4gY29uY2x1c2lvbiBvZiBzdGVwIDIgYW5kIHN0ZXAgMywgdGhlIHhnYm9vc3QgaXMgc2xpZ2h0IGJldHRlciB0aGFuIGdtYiBvbiBBVUMgYW5kIFJPQy4gCgpUaGUgaGlzdG9ncmFtIGFsc28gc2hvd3MgYSByZWxhdGl2ZSBsaWZ0IG9uIHRoZSBwcm9iYWJpbGl0eSBvbiBjaHVybiBwcmVkaWN0aW9uIGJ5IFhHQk9PU1QuCgoqKkFsdGVybmF0aXZlIG1vZGVscyB5b3UgaGF2ZSBjb25zaWRlcmVkLiBXaHkgYXJlIHRoZXkgbm90IGdvb2QgZW5vdWdoPyoqCgpJIGFsc28gdHJpZWQgcmFuZG9tIGZvcmVzdCBSRiAocmVzdWx0cyBiZWxvdyksIGJ1dCB0aGUgUk9DIHZhbHVlIGlzIGxvd2VyIHRob3NlIGdibSBhYmQgeGdib29zdC4KYGBge3J9CiMgUmFuZG9tIEZvcmVzdAojCiMgNDAwMDEgc2FtcGxlcwojIDEzIHByZWRpY3RvcnMKIyAyIGNsYXNzZXM6ICdjaHVybmVkJywgJ25vdF9jaHVybmVkJwojCiMgTm8gcHJlLXByb2Nlc3NpbmcKIyBSZXNhbXBsaW5nOiBDcm9zcy1WYWxpZGF0ZWQgKDUgZm9sZCkKIyBTdW1tYXJ5IG9mIHNhbXBsZSBzaXplczogMzIwMDEsIDMyMDAwLCAzMjAwMSwgMzIwMDEsIDMyMDAxCiMgUmVzYW1wbGluZyByZXN1bHRzIGFjcm9zcyB0dW5pbmcgcGFyYW1ldGVyczoKIwojIG10cnkgIFJPQyAgICAgICAgU2VucyAgICAgICBTcGVjCiMgMiAgICAwLjgzMjQyOTEgIDAuODc3NTA5NSAgMC42MDkzNDU1CiMgOSAgICAwLjgzNjA2NTUgIDAuODQ2MTc1NSAgMC42NTgzMzUxCiMgMTYgICAwLjgyNDY3MDAgIDAuODIxOTczOSAgMC42NjE3OTE0CiMKIyBST0Mgd2FzIHVzZWQgdG8gc2VsZWN0IHRoZSBvcHRpbWFsIG1vZGVsIHVzaW5nICB0aGUgbGFyZ2VzdCB2YWx1ZS4KIyBUaGUgZmluYWwgdmFsdWUgdXNlZCBmb3IgdGhlIG1vZGVsIHdhcyBtdHJ5ID0gOS4KYGBgCkkgZGlkbid0IGluY2x1ZGUgcmFuZG9tIGZvcmVzdCBjb2RlIGhlcmUgYmVjYXVzZSBpdCB0YWtlcyBsb25nIGhvdXJzIHRvIHJ1bi4KSXQgYXBwZWFycyBpbiB0aGUgZGVmYXVsdCBzZXR0aW5nLCBnYm0gYW5kIHhnYm9vc3QgYXJlIGJldHRlciB0aGFuIHJhbmRvbSBmb3Jlc3QgaW4gdGhpcyBjYXNlLgpUaGUgcmVhc29uIGlzIG1vcmUgdHJlZXMgZnJvbSByYW5kb20gZm9yZXN0IHdpbGwgY2F1c2UgYmV0dGVyIHJlc3VsdHMgYnkgcmVkdWNlIHZhcmlhbmNlLCBidXQgYWxzbyBtb3JlIGNvbXB1dGF0aW9uYWxseSBleHBlbnNpdmUuCkluIGdlbmVyYWwsIEkgZm91bmQgcmFuZG9tIGZvcmVzdCBpcyBzbG93IHRoYW4gZ2JtLgpJZiBJIGhhdmUgbW9yZSB0aW1lIHRvIHRyeSBvdXQsIHJhbmRvbSBmb3Jlc3QgbWF5YmUgZ29vZCBiZWNhdXNlIGl0IG9ubHkgaGFzIG9uZSBwYXJhbWV0ZXIgdG8gdHVuZSwgdGhlIG51bWJlciBvZiBmZWF0dXJlcyB0byByYW5kb21seSBzZWxlY3QgYXQgZWFjaCBub2RlIGluIHRoZSBjYXJldCBwYWNrYWdlLgpIb3dldmVyLCBCb29zdGVkIFRyZWVzIGlzIGdldHRpbmcgcG9wdWxhciBpbiBLYWdnbGUgYmVjYXVzZSBpdCBhZGQgbmV3IHRyZWVzIHRoYXQgY29tcGxpbWVudCB0aGUgYWxyZWFkeSBidWlsdCBvbmVzIGFuZCB0aGlzIGNhbiBnaXZlIHlvdSBiZXR0ZXIgYWNjdXJhY3kgd2l0aCBsZXNzIHRyZWVzLkl0IGZhc3QgZ2VuZXJhdGUgc29tZSB3ZWFrIGxlYW5lciAodHJlZSkgd2hpY2ggbWFrZSBpdCBmYXN0ZXIuCnhnYm9vc3QgdXNlZCBhIG1vcmUgcmVndWxhcml6ZWQgbW9kZWwgY29tcGFyZWQgd2l0aCBnYm0gdG8gY29udHJvbCBvdmVyLWZpdHRpbmcsIHdoaWNoIGdpdmVzIGl0IGJldHRlciBwZXJmb3JtYW5jZS4gQWx0aG91Z2ggaXQncyBvdmVyIHNwZWNpYWxpemF0aW9uLCBhZnRlciB0ZXN0IEkgZm91bmQgdGhlIHNwZWVkIGFuZCByZXN1bHRzIGFyZSBva2F5IGZvciB4Z2Jvb3N0LiBObyB3b25kZXIgcGVvcGxlIHVzZWQgaXQgZnJlcXVlbnRseSBpbiBLYWdnbGUgY29tcGV0aXRpb24uCgoKIyMjIyBTdGVwIDQuIElkZW50aWZ5IC8gaW50ZXJwcmV0IGZlYXR1cmVzIHRoYXQgYXJlIHRoZSBtb3N0IGluZmx1ZW50aWFsIGluIGFmZmVjdGluZyB5b3VyIHByZWRpY3Rpb25zLgoKVVAgdG8gdGhpcyBwb2ludCwgSSBqdXN0IHRocm93IGVhY2ggdGhpbmdzIG9uIHRoZSB3YWxsIGFuZCBzZWUgaWYgdGhlIG11ZCB3aWxsIHN0aWNrLgpJdCdzIHRpbWUgdG8gdGFrZSBhIGNsb3NlIGxvb2sgdGhhdCB3aGljaCBwYXJhbWV0ZXJzIGFyZSBhY3R1YWxseSBpbXBvcnRhbnQuCkFuZCBJIHVzZWQgeGdiVHJlZSBiYXNlZCBvbiBwcmV2aW91cyBjb25jbHVzaW9uIHRoYXQgeGdiIGlzIGJldHRlciB0aGFuIHJhbmRvbSBmb3Jlc3QgaW4gdGhpcyBjYXNlLiAKCk5vdyBJIG5lZWQgdG8gRmVhdHVyZSBSYW5raW5nCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQojIHByZXBhcmUgdHJhaW5pbmcgc2NoZW1lCmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCBudW1iZXI9NSkKIyB0cmFpbiB0aGUgbW9kZWwKc2V0LnNlZWQoMSkKbW9kZWwgPC0gdHJhaW4oY2h1cm5+LiwgZGF0YT1kZl90cmFpbiwgbWV0aG9kPSJ4Z2JUcmVlIiwgIHRyQ29udHJvbD1jb250cm9sLCAgaW1wb3J0YW5jZSA9IFRSVUUpCiMgSGVyZSBvbmx5IHVzZWQgdHJhaW5pbmcgZGF0YXNldCBpcyBiZWNhdXNlIHRvIGdldCBhbiB1bmJpYXNlZCBwZXJmb3JtYW5jZSBlc3RpbWF0ZSwKIyBJdCdzIGJldHRlciB0byBub3QgdXNlIHRoZSB3aG9sZSBkYXRhc2V0IHdoaWNoIGluY2x1ZGluZyB0ZXN0IGRhdGFzZXQgZm9yIGZlYXR1cmUgc2VsZWN0aW9uLgppbXBvcnRhbmNlcyA8LSB2YXJJbXAobW9kZWwsIHNjYWxlPUZBTFNFKQpwcmludChpbXBvcnRhbmNlcykKIyB4Z2JUcmVlIHZhcmlhYmxlIGltcG9ydGFuY2UKIyAKIyAgICAgICAgICAgICAgICAgICAgICAgICAgT3ZlcmFsbAojIGF2Z19yYXRpbmdfYnlfZHJpdmVyICAgMC4yMzkxMDMzCiMgc3VyZ2VfcGN0ICAgICAgICAgICAgICAwLjE3Mjk3NzQKIyBjaXR5S2luZyAgICAgICAgICAgICAgIDAuMTQ1MjM1OQojIHdlZWtkYXlfcGN0ICAgICAgICAgICAgMC4xNDI0MjY5CiMgcGhvbmVpUGhvbmUgICAgICAgICAgICAwLjA4NTQ2NTgKIyBsdXh1cnlfY2FyX3VzZXIxICAgICAgIDAuMDY5MjU2OQojIHRyaXBzX2luX2ZpcnN0XzMwX2RheXMgMC4wNjc1ODg1CiMgYXZnX2Rpc3QgICAgICAgICAgICAgICAwLjAzMzYzNTcKIyBjaXR5V2ludGVyZmVsbCAgICAgICAgIDAuMDE5Mzg5MQojIGF2Z19yYXRpbmdfb2ZfZHJpdmVyICAgMC4wMTU4NjUwCiMgYXZnX3N1cmdlICAgICAgICAgICAgICAwLjAwODI4NTYKIyBwaG9uZU90aGVyICAgICAgICAgICAgIDAuMDAwNzY5OQojIHN1cmdlX29yX25vdHN1cmdlZCAgICAgMC4wMDAwMDAwCiMgdXNlZF9vcl9ub3RfMzBkYXlzdXNlZCAwLjAwMDAwMDAKIyB3ZWVrZGF5X2J1Y2tldHplcm8gICAgIDAuMDAwMDAwMAojIHdlZWtkYXlfYnVja2V0c29zbyAgICAgMC4wMDAwMDAwCgpgYGAKU3VwcmlzaW5nbHksIHRoZSB0b3AgZml2ZSBpbXBvcnRhbnQgZmVhdHVyZSBzZWxlY3RlZCBhcmUgYXZnX3JhdGluZ19ieV9kcml2ZXIsIHN1cmdlX3BjdCxjaXR5S2luZywgaVBob25lCioqYXZnX3JhdGluZ19ieV9kcml2ZXIqKjogc29tZSBjaHVybiB1c2VyIGhhZCByZXZpZXJlZCBtb3JlIDUgc3RhciByYXRpbmcgY29tcGFyZWQgd2l0aCBub3QtY2h1cm4gdXNlci4gU28gdXNlciBjaHVybiBiZWNhdXNlIGFmcmFpZCBvZiBub3QgbWVldCBkcml2ZXIncyBoaWdoIGV4cGVjdGlvbiA/IEl0IG1heSBub3QgYmUgdGhlIGNhc2UuIEtlZXAgbG9va2luZy4KKipzdXJnZV9wY3QqKjogSGlnZXIgMCUgc3VyZ2VfcHJjIGluIGNodXJuIGNsYXNzIHdoaWNoIGluZGljYXRlcyB0aG9zZSBwZW9wbGUgbm90IHVzZSBzdXJnZSBtYXkgbW9yZSBsaWtlbHkgdG8gY2h1cm4uCioqY2l0eUtpbmcqKjogSW4gdGhlIGNodXJuIGNsYXNzLCBjaXR5IEtpbmdpbkxhbmcgaGFzIGxvdyBjaHVybiByYXRlIGNvbXBhcmVkIHdpdGggb3RoZXIgdHdvIGNpdHlzLgoqKndlZWtkYXlfcGN0Kio6IHRoZSBkaXN0cmlidXRpb24gYXJlIGRpZmZlcmVudCBjb21wYXJlZCBjaHVybiBhbmQgbm90IGNodXJuLiBBZ2FpbiBjaHVybiBoYXMgaGlnaCAxMDAlIHVzZSBwZXJjZW50IHdoaWNoIGlzIGludGVyc3RpbmcuCioqaVBob25lKio6IGJpZyBwZXJjZW50IGluIGNodXJuIGNhbHNzLCBjb3VsZCBiZSB0aGUgcmlkZXIgYXBwIGlzIG1vcmUgZWFzeSBmb3IgaVBob25lIHVzZXIgdG8gc2lnbnVwPyBNb3JlIG1hcmtldGluZyBvbiBpUGhvbmUgdXNlcj8KYGBge3J9CnBsb3QoaW1wb3J0YW5jZXMpCmBgYApMZXQgbWUgdHJ5IGZlYXR1cmUgc2VsZWN0aW9uIGJ5IHVzaW5nIGFub3RoZXIgbWV0aG9kCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpzZXQuc2VlZCgxKQpjb250cm9sIDwtIHJmZUNvbnRyb2woZnVuY3Rpb25zPXJmRnVuY3MsIG1ldGhvZD0iY3YiLCBudW1iZXI9NSkKcmVzdWx0cyA8LSByZmUoZGZfdHJhaW5bLDE6MTNdLCBkZl90cmFpblssMTRdLCBzaXplcz1jKDE6MTMpLCByZmVDb250cm9sPWNvbnRyb2wpCnByaW50KHJlc3VsdHMpCiMgUmVjdXJzaXZlIGZlYXR1cmUgc2VsZWN0aW9uCiMgCiMgT3V0ZXIgcmVzYW1wbGluZyBtZXRob2Q6IENyb3NzLVZhbGlkYXRlZCAoNSBmb2xkKSAKIyAKIyBSZXNhbXBsaW5nIHBlcmZvcm1hbmNlIG92ZXIgc3Vic2V0IHNpemU6CiMgICAKIyAgIFZhcmlhYmxlcyBBY2N1cmFjeSAgS2FwcGEgQWNjdXJhY3lTRCAgS2FwcGFTRCBTZWxlY3RlZAojIDEgICAwLjY3NzEgMC4yNDMzICAgMC4wMDI5ODUgMC4wMDY3MTIgICAgICAgICAKIyAyICAgMC42ODMwIDAuMjM0NCAgIDAuMDAyMjIxIDAuMDA1OTQ3ICAgICAgICAgCiMgMyAgIDAuNjk3MCAwLjMxMzIgICAwLjAwNjU0NyAwLjAzMzA2NiAgICAgICAgIAojIDQgICAwLjc1NDMgMC40NzA2ICAgMC4wMDM2MzEgMC4wMDc0NDkgICAgICAgICAKIyA1ICAgMC43NzE1IDAuNDk5NSAgIDAuMDA1MTQwIDAuMDExNjQxICAgICAgICAgCiMgNiAgIDAuNzc0NiAwLjUxMDcgICAwLjAwMzk3NyAwLjAwODg2NyAgICAgICAgIAojIDcgICAwLjc3ODYgMC41MTc0ICAgMC4wMDU3MzYgMC4wMTI3MDUgICAgICAgICAKIyA4ICAgMC43ODE1IDAuNTIyMyAgIDAuMDA0OTE1IDAuMDExMDYzICAgICAgICAgCiMgOSAgIDAuNzgyMiAwLjUyNTYgICAwLjAwNDM4NSAwLjAxMDIzNiAgICAgICAgIAojIDEwICAgMC43ODM1IDAuNTI3OSAgIDAuMDA0Mzk0IDAuMDEwNjkzICAgICAgICAgCiMgMTEgICAwLjc4NDAgMC41Mjg0ICAgMC4wMDM5MDQgMC4wMDkxMTggICAgICAgICAKIyAxMiAgIDAuNzg0MyAwLjUyODkgICAwLjAwMzc4NSAwLjAwODkyMyAgICAgICAgKgojIDEzICAgMC43ODM5IDAuNTI3NCAgIDAuMDAzNTQ5IDAuMDA4MTU0ICAgICAgIApgYGAKVGhpcyBtZXRob2QgcnVuIHRvbyBsb25nIHNvIEkgc2V0IGBldmFsPUZBTFNFYAoKVGhlIHRvcCA1IHZhcmlhYmxlcyAob3V0IG9mIDEyIGZlYXR1cmVzKToKYGNpdHksIHBob25lLCBsdXh1cnlfY2FyX3VzZXIsIGF2Z19yYXRpbmdfYnlfZHJpdmVyLCB3ZWVrZGF5X2J1Y2tldGAgbGlzdCB0aGUgY2hvc2VuIGZlYXR1cmVzLgoKYGBge3J9CnBsb3QocmVzdWx0cywgdHlwZT1jKCJnIiwgIm8iKSkKYGBgCkJhc2VkIG9uIHRoZSAiZWJsb3ciIHJ1bGVzLCB3ZSBjaG9vc2UgdGhlIHRvcCA1IGZlYXR1cmVzLgpDb21wYXJlZCBib3RoIHhiZ2Jvb3N0IGFuZCByYW5kb20gZm9yZXN0IHNlbGVjdGVkIGZlYXN0dXJlcywgYGNpdHksIHBob25lLCBhdmdfcmF0aW5nX2J5X2RyaXZlciwgd2Vla2RheV9idWNrZXQgYXJlIGNvbW1vbmAuCkJ1dCBJIGFsc28gd2FudCB0byBhZGQgYGx1eHVyeV9jYXJfdXNlcmAgYW5kIGBzdXJnZV9wY3RgIHNpbmNlIHRob3NlIHR3byBmZWF0dXJlcycgZGlzdHJpYnV0aW9uIGFyZSBtb3JlIHNlcGFyYXRlIGFuZCBtYXkgYmUgYWJsZSB0byBoZWxwIHRvIGNsYXNzaWZ5IGNodXJuIGFuZCBub3QgY2h1cm4gd2VsbC4KClNvIHVzZSB0aG9zZSBzaXggZmVhdHVyZXMgdG8gcHJlZGl0IGFnYWluLgpgYGB7cn0KY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIGNsYXNzUHJvYnMgPSBUUlVFLCBudW1iZXI9NSwgc3VtbWFyeUZ1bmN0aW9uPXR3b0NsYXNzU3VtbWFyeSkKc2VlZCA8LSAxCm1ldHJpYyA8LSAiUk9DIgpzZXQuc2VlZChzZWVkKQp4Z2Jfc2VsZWN0IDwtIHRyYWluKGNodXJufiBjaXR5ICsgcGhvbmUgKyBhdmdfcmF0aW5nX2J5X2RyaXZlciArIHdlZWtkYXlfYnVja2V0ICsgbHV4dXJ5X2Nhcl91c2VyICsgc3VyZ2VfcGN0LCBkYXRhPWRmX3RyYWluLCBtZXRob2Q9InhnYlRyZWUiLCBtZXRyaWM9bWV0cmljLCB0ckNvbnRyb2w9Y29udHJvbCkKcHJpbnQoeGdiX3NlbGVjdCkKIyBST0MgaXMgYWJvdXQgMC44MQpgYGAKVGhlbiBnZXQgY29uZnVzaW9uIE1hdHJpeCBhZ2FpbiB0byBzZWUgdGhlIGFjY3VyYWN5CmBgYHtyfQpwdF9zZWxlY3QgPC0gcHJlZGljdCh4Z2Jfc2VsZWN0LCBuZXdkYXRhID0gZGZfdGVzdCkKY29uZnVzaW9uTWF0cml4KHB0X3NlbGVjdCwgZGZfdGVzdCRjaHVybikKYGBgClRoZSBhY2N1cmFjeSBpcyByb3VuZCAwLjc2CmBgYHtyfQp4Z2JfcHJvYnMgPC0gcHJlZGljdCh4Z2Jfc2VsZWN0LCBuZXdkYXRhID0gZGZfdGVzdCwgdHlwZSA9ICJwcm9iIikKeGdiX1JPQyA8LSByb2MocHJlZGljdG9yPXhnYl9wcm9icyRjaHVybmVkLAogICAgICAgICAgICAgICByZXNwb25zZT1kZl90ZXN0JGNodXJuLAogICAgICAgICAgICAgICBsZXZlbHM9cmV2KGxldmVscyhkZl90ZXN0JGNodXJuKSkpCnhnYl9ST0MkYXVjCmBgYApUaGUgQVVDIGlzIGFyb3VuZCAwLjgyLCBhbmQgc2xpZ2h0IGRyb3AgYnV0IHN0aWxsIG9rLgpgYGB7cn0KaGlzdDMgPC0gaGlzdG9ncmFtKH54Z2JfcHJvYnMkY2h1cm5lZHxkZl90ZXN0JGNodXJuLHhsYWI9IlByb2JhYmlsaXR5IG9mIENodXJuIGJ5IHNlbGVjdGVkIGZlYXR1cmUsIFhHQk9PU1QiLHlsaW0gPSBjKDAsMzUpKQpoaXN0MwpgYGAKV2hlbiBsb29rIGF0IHRoZSBuZXcgaGlzdG9ncmFtIGRpc3RyaWJ1dGlvbiBvbiBjaHVybiwgc2VlbXMgbm90IGFzIGdvb2QgYXMgYmVmb3JlLiBNb3JlIGZlYXR1cmVzIHN0aWxsIGJlIGJldHRlciB0aGFuIGxlc3MgZmVhdHVyZXMgaW4gWEdCT09TVCBpbiBzdWNoIGNhc2UuCgpJIGdldCBhIHNlbnNlIG9mIEkgY2FuIHVzZSBvdGhlciBtZXRob2RzIHRvIGNvbmR1Y3QgZmVhdHVyZSBzZWxlY3Rpb24gc3VjaCBhcyBQQ0EuIEhvd2V2ZXIsIGFzIHRoaXMgcG9pbnQsIGl0IHNlZW1zIHRoZSBhbW91bnQgb2YgdGhlIGZlYXR1cmVzIGlzIHN0aWxsIGhlbHBmdWwgdG8gaW1wcm92ZSB0aGUgQVVDIHZhbHVlIGJhc2VkIG9uIHRoZSBkYXRhc2V0IGFtb3VudC4gCgoKIyMjIyBTdGVwIDUuIERpc2N1c3MgdGhlIHZhbGlkaXR5IG9mIHRoZSBtb2RlbAoKVGhlIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwgZm9yIGNsYXNzaWZ5aW5nIGNodXJuIGlzIFhHQk9PU1QuIEl0IHJlYWNoZXMgYWNjdWNhcnkgMC43OCBhbmQgQVVDIDAuODYuClRoZSByZWR1Y2VkIGZlYXR1cmVzIHByZWRpY3Rpb24gYnkgeGdib29zdCBtYXkgbm90IGhlbHAgaW1wcm92aW5nIHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IGF0IHRoaXMgcG9pbnQuIEluIGdlbmVyYWwsIEkgYmVsaWV2ZSB0aGlzIG1vZGVsIGNhbiBiZSB1c2UgYXMgYSBzdGFydGluZyBwb2ludCBmb3IgcHJlZGljdGluZyBjaHVybiBvciBub3QgY2h1cm4gZm9yIHRoaXMgcmlkZS1zaGFyZSBjb21wYW55LiBIb3dldmVyLCBpZiBJIGhhdmUgbW9yZSB0aW1lLCBJIHdpbGwgdGFrZSBhIGNsb3NlIGxvb2sgb24gdGhlIFJPQyBmb3IgaW5kaXZpZHVhbCB1c2VyLCB3aGljaCBpcyB0aGUgcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbiBmb3IgZWFjaCB1c2VyLiBJZiB3ZSBjYW4gc3VjY2VzcyBwcmVkaWN0IHRoZSBjaHVybiBpbiBhIHBhcnRpY3VsYXIgY29yaG9ydCB1c2Vycywgd2UgbWF5IGdldCBtb3JlIGFjY3VyYXRlIHByZWRpY3Rpb24gb2YgY2h1cm4uCgpXaXRoIHRoZSBpbXBvcnRhbnQgZmVhdHVyZSwgYGNpdHksIHBob25lLCBhdmdfcmF0aW5nX2J5X2RyaXZlciwgd2Vla2RheV9wcmMsIHN1cmdfcHJjLCBsdXh1cnlfY2FyX3VzZXJgLCB0aGUgbW9kZWwgY2FuIHJlYWNoIGEgcmVzZWFzb2JsZSBwcmVkaWN0aW9uIHBvc2liaWxpdHkuIFdlIGNhbiB1c2UgdGhpcyBtb2RlbCB0byBwcmVkaWN0IHRoZSBuZXh0IDMwIGRheXMgaWYgYSB1c2VyIGlzIGdvaW5nIHRvIGNodXJuIG9yIG5vdCBieSBhdCBsZWFzdCA3NiUgYWNjdXJhY3khCklmIHdlIHVzZSBhbGwgZmVhdHVyZSwgd2UgY2FuIGltcHJvdmUgdGhpcyBwcmVkaWN0aW9uIGFjY3VyYWN5IHRvIDc5JS4K