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

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