Load Data
Load datasets
The first step will be to load the test data file and train data file. These have been downloaded to a local Kaggle folder offline as there is an agreement step to the Kaggle data terms and conditions and unzipped using 7-zip.
We will use the data.table R package designed for large datasets.
Overview
The train dataset has 18255 rows and 290 columns.
The following information is available from the Kaggle discussion forum:
is_female - the is_female variable you are going to predict. Note: in the data dictionary, Female is 2, male is 1, while in our transformed data, is_female=1 for female and is_female=0 for male. For the rest of the 1000+ column descriptions, please refer to WiDSDataDictionary for details. Please note that data has been processed and some columns were removed to prevent data leakage or to protect privacy.
From reviewing the data dictionary, the variables are all numeric or integers. There are also a large number of variables, therefore we will initially look for ways to perform dimension reduction.
From reviewing the data dictionary, the variables are all numeric or integers. There are also a large 290 variables, therefore we will initially look for ways to perform dimension reduction. Note there are less variables in the data dictionary 3 so it appears we are missing some variable descriptions. It appears that the AB questions are also missing from our datasets.
Check if any variables are characters, and have large count, using a threshold of 14,000.
# select character variables with non white space
ischars <- train %>% select_if(is.character)
ischarstrue <- sapply(ischars,function(x) table(x =="")["TRUE"])
ischarstrue[ischarstrue < 14000]
LN2_RIndLngBEOth.TRUE LN2_WIndLngBEOth.TRUE
6914 6911
# Convert these character variables to factors so that they retain a value when the train and test sets are converted to numeric
train$LN2_RIndLngBEOth <- as.factor(train$LN2_RIndLngBEOth)
train$LN2_WIndLngBEOth <- as.factor(train$LN2_WIndLngBEOth)
test$LN2_RIndLngBEOth <- as.factor(test$LN2_RIndLngBEOth)
test$LN2_WIndLngBEOth <- as.factor(test$LN2_WIndLngBEOth)
Convert the train and test datasets to numeric classes using the map function from the purr R package, for XGBoost modelling.
train <- data.frame(map(train, ~as.numeric(.)) )
test <- data.frame(map(test, ~as.numeric(.)) )
Part 1: EDA
See separate script, WiDSEDA.Rmd
Part 2: Data Cleaning
Let’s clean the train and test datasets variable by variable based on the exploratory data analysis.
2.1 Remove train IDs
# Remove ID feature from train as this identifier is not needed in the training. The id will be left in the test set for the predictions.
train <- train %>% dplyr::select(-train_id)
id <- test$test_id
test <- test %>% dplyr::select(-test_id)
2.2 Remove near zero variance variables
Removing variables with near zero variance using the caret R package.
near.zero <- nearZeroVar(train,saveMetrics=TRUE)
variables <- row.names(subset(near.zero,near.zero$nzv==FALSE))
train <- data.frame(train)[,variables]
# Perform the same action on the test set, after removing the "is_female" in position 9 from variables as this is to be predicted in the test set
test <- data.frame(test)[,variables[-9]]
2.3 Missing Values
Next we will extract and view the variables in more detail that have value NA, which represent the missing values in the dataset. With dplyr we can call functions from different R packages directly inside the dplyr functions. We will use the stringr R package with dplyr to view a summary of the NAs. We will also use the purrr to apply the sum function across multiple variables.
# Use the base summary function for result summaries not dplyr. This will provide us with the ranges of the variables including the minimums.
s <- summary(train)
# Extract and view the frequency of NA's from the summary s we just created. Use str_detect from stringr package.
s %>%
data.frame() %>%
filter(str_detect(Freq,"NA"))
# Since there are variables with very high proportion of NAs, let's remove these variables assuming they will not add predictive value
# Using map function from the purr package sum the NAs for each column
count_na <- map(train, ~sum(is.na(.)))
# Remove columns with more than % NA values using an index
index1 <- which(count_na < 0.9*dim(train)[1])
train <- train[, index1]
# Perform the same action on the test set, after removing the "is_female" in position 8 from variables as this is to be predicted in the test set
index2 <-index1[-8]
index2[index2>8]<-as.integer(index2[index2>8]-1)
test <- test[,index2]
2.4 Imputation of missing values
We are going to review the columns with missing values to impute the values. We will create a merged dataset from the data dictionary. Then using the replace_na function from the tidyr R package.
# Extract and view the frequency of NA's again
s <- summary(train)
missing <- s %>%
data.frame() %>%
filter(str_detect(Freq,"NA"))
# Remove the white space and rename columns to make it easier to merge into a ddmissing dataframe
missing$Var2 <- missing$Var2 %>%
as.character() %>%
trimws()
missing$Freq <- as.character(missing$Freq)
names(missing) <- c("Column.Name","Freq")
ddmissing <- merge(dd2,missing,by = "Column.Name")
ddmissing
# We see that MT1A has high variable importance from the EDA so we will replace the NA values with 99, which is the existing NA category in this questions. Note the starter mobile question MT1 and MT2 have no NAs.
train <- train %>%
replace_na(list(MT1A=99))
# We see that this has high variable importance MT6.How did you obtain your phone? (Top 10 VI) GOT NAs. Replace with existing category 99
train <- train %>%
replace_na(MT6=99)
# We see that this has high variable importance DL2. What is your primary job (i.e., the job where you sp Replace with existing category 96
train <- train %>%
replace_na(DL2=96)
# We see that this has high variable importance DL1. In the past 12 months, were you mainly...? Replace with existing category 96
train <- train %>%
replace_na(DL1=96)
# NA fix replace all remaining NA with 99. We will not use this replacement as XGboost can handle missing values
# train <- train %>%
# replace(.,is.na(.), 99)
# test <- test %>%
# replace(.,is.na(.), 99)
2.5 Imputation of the median
The variable train$AA14 has some outliers, and it is in the top 10 in the variable importance but it is not in the data dictionary. We will impute the median on this variable.
# Impute the outlier to the median of this variable
train$AA14[train$AA14==99999] <- median(train$AA14)
test$AA14[test$AA14==99999] <- median(test$AA14)
# Check the imputation
train %>% select(AA14) %>% filter(AA14==99999) %>% head()
Min. 1st Qu. Median Mean 3rd Qu. Max.
96 949 2902 2781 4165 5906
Part 3: Modeling
3.1. MODEL SELECTION
Since we know the outcome discrete variable, we will use a supervised machine learning algorithm. It also appears from our EDA, the is_female variable is 0 or 1, we will potentially need a classification model. The algorithm that we will use is XGBoost where we can choose between the two booster methods. Initially we will use default XGBoost parameters and perform some initial feature engineering.
We will use AUC, as our metric and ultimately select the final model based on the highest value. See this post for more information on these curves
In R, the XGBoost package uses the following:
A matrix of input data instead of a data frame.
Only numeric variables.
The is_female variable separately, which we will code to y.
We will convert the categorical variables into dummy variables using one hot encoding. It is recommended by the R package XGBoost’s vignette to use xgb.DMatrix.
XGBoost will also handle NA missing values so these will be left in the train and test datasets.
3.2. FEATURE ENGINEERING
3.2.1 Add new features
We will create new features for both the train and test sets, based on our EDA and previous runs of the modelling.
A. Age brackets
DG1 relates to what year the respondent was born. We can bin into age brackets based on the survey that is dated 2016
# Check the range of the years born in DG1
range(train$DG1)
[1] 1917 2001
# Set the breaks
agebreaks <- c(1916,1956,1991,1997,2001)
# Cut the train set into a new variable called DG1A
train$DG1A <- cut(train$DG1, agebreaks, include.lowest=T)
# Rename the levels
levels(train$DG1A) <- rev(c("15 to 18 years","19 to 24 years","25 to 59 years","60 years and over"))
# Convert to numeric
train$DG1A<-unclass(train$DG1A) %>% as.numeric
# Do the same for the test set. Cut the test set
test$DG1A <-cut(test$DG1, agebreaks, include.lowest=T)
# Rename the levels
levels(test$DG1A) <- rev(c("15 to 18 years","19 to 24 years","25 to 59 years","60 years and over"))
# Convert to numeric
test$DG1A<-unclass(test$DG1A) %>% as.numeric
# Check the range of the new variable DG1A has been converted from bins to numeric values
range(train$DG1A)
[1] 1 4
B. Live in town or village
We will create new features for whether a respondent lives in town or not.
train$AA5ornot <- ifelse(is.na(train$AA5),0,1)
test$AA5ornot <- ifelse(is.na(test$AA5),0,1)
C. Parent or not
We will create new features for whether a respondent is a parent or not, and a grandparent or not based on the question: DG6.How are you related to the household head? “1=Myself2=Spouse3=Son/Daughter4=Father/Mother5=Sister/Brother6=Grandchild7=Other relative9=Other non-relative99=DK”
train$DG6parent <- ifelse(train$DG6==1 |train$DG6==2,1,0)
train$DG6gparent <- ifelse(train$DG6==4 ,1,0)
test$DG6parent <- ifelse(test$DG6==1 |test$DG6==2,1,0)
test$DG6gparent <- ifelse(test$DG6==4 ,1,0)
D. Working or not
We will create new features for whether a respondent is stay at home or not or a student or not.
train$DL1household <- ifelse(train$DL1==7 ,1,0)
train$DL1student <- ifelse(train$DL1==8 ,1,0)
test$DL1household <- ifelse(test$DL1==7 ,1,0)
test$DL1student <- ifelse(test$DL1==8 ,1,0)
E. Bought phone or not
We will create new features for whether a respondent bought their phone for themself or not.
train$MT6phone <- ifelse(train$MT6==1,1,0)
test$MT6phone <- ifelse(test$MT6==1,1,0)
F.AA14 grouping
We will also group the unknown continuous variable variable AA14 with a large number of frequency values.
[1] 96 5906
train$AA14_1<- ifelse(train$AA14<2000,1,
ifelse(train$AA14<4000,2,3)
)
test$AA14_1<- ifelse(test$AA14<2000,1,
ifelse(train$AA14<4000,2,3)
)
G. Cohabiting or not
We will create new features for whether a respondent is cohabiting or not.
# DG3. What is your marital status?
train$DG3_1<- ifelse(train$DG3==7|train$DG3==8,1,0)
test$DG3_1<- ifelse(test$DG3==7|train$DG3==8,1,0)
longer object length is not a multiple of shorter object length
H. Financially independent
We will create new features for whether a respondent is financially independent or not.
train$FL4_1<- ifelse(train$FL4==1,1,0)
test$FL4_1<- ifelse(test$FL4==1,1,0)
3.2 DUMMY VARIABLES
#Converting every categorical variable to numerical using dummy variables
#dmy <- dummyVars(" ~ .", data = train,fullRank = T)
#train_transformed <- data.frame(predict(dmy, newdata = train))
#Checking the structure of transformed train file
#str(train_transformed)
3.3. DATA SPLITTING
The train data set is further split into training (70%) and validation (30%) sets for cross validation using caret, after being randomised. The function createDataPartition can be used to create stratified random splits of a data set.
# Create a set of training and valid sets that will be used in the XGBboost package, keeping the target vaiable as numeric
inTrain <- createDataPartition(y = train$is_female, p = 0.8, list = F)
training <- train[inTrain,]
valid <- train[-inTrain,]
# Set the target variable to be a character factor with levels one and two for use in the training in the caret XGBoost
train$is_female <- as.factor(train$is_female)
levels(train$is_female)[levels(train$is_female)=="0"] <- "one"
levels(train$is_female)[levels(train$is_female)=="1"] <- "two"
# Create a set of training and valid sets that will be used in the caret package, with the target vaiable as factor
inTrain <- createDataPartition(y = train$is_female, p = 0.7, list = F)
training2 <- train[inTrain,]
valid2 <- train[-inTrain,]
3.4. DATA MATRIX
# is_female numeric outcome y (label) on training set
y = training$is_female
# To use advanced features xgboost, as recommended, we'll use xgb.DMatrix function to convert a matrix or a dgCMatrix into a xgb.DMatrix object, which contains a list with dgCMatrix data and numeric label:
dtrain <- xgb.DMatrix(data = data.matrix(training[ ,-training$is_female]),
label = y)
dvalid <- xgb.DMatrix(data = data.matrix(valid[ ,-valid$is_female]),
label = valid$is_female)
dtest <- xgb.DMatrix(data.matrix(test))
# We use watchlist parameter to measure the progress with a second dataset which is already classified.
watchlist <- list(train=dtrain, test=dvalid)
# Check that dtest has the same number of rows as the original test file, 27285 rows
nrow(dtest)
[1] 27285
3.5. MODEL PARAMETERS
The following are parameters available for XGBoost R package base don the documentation:
General parameters
- booster
We will run models for boosters gblinear and gbtree. nthread [default=maximum cores available] silent[default=0] to not see the running messages
Booster parameters
For each of these boosters, there are booster parameters, these are common between the two:
- nrounds - Observe the number chosen for nrounds for any overfitting using CV. the max number of iterations.
- alpha[default=1] and lambda [default=0] to control regularisation
Parameters for Tree Booster also include:
- eta[default=0.3][range: (0,1)] controls the learning rate.
- max_depth[default=6][range: (0,Inf)] controls the depth of the tree- tuned using CV. Higher value of max_depth will create more deeper trees or we can say it will create more complex model.Higher value of max_depth may create overfitting and lower value of max_depth may create underfitting.All depends on data in hand.
- min_child_weight[default=1][range:(0,Inf)] In simple words, it blocks the potential feature interactions to prevent overfitting. Should be tuned using CV. It is like number of observations a terminal node.If the tree partition step results in a leaf node with the sum of instance weight less than min_child_weight, then the building process will give up further partitioning. In linear regression mode, this simply corresponds to minimum number of instances needed to be in each node.
- subsample[default=1][range: (0,1)] controls the number of samples (observations) supplied to a tree.
- colsample_bytree[default=1][range: (0,1)]control the number of features (variables) supplied to a tree. Randomly choosing the number of columns out of all columns or variables at a time while tree building process.You can think of mtry parameter in random forest to begin understanding more about this.Higher value may create overfitting and lower value may create underfitting.One needs to play with this value.
- gamma (Minimum Loss Reduction) One can play with this parameter also but mostly other parameters are used for model tuning.
Learning Task Parameters
These parameters specify methods for the loss function and model evaluation. In addition to the parameters listed below, you are free to use a customized objective / evaluation function.
Objective[default=“binary:logistic”]
eval_metric [no default, depends on objective selected] These metrics are used to evaluate a model’s accuracy on validation data. For regression, default metric is RMSE.
One of the simplest way to see the training progress in XGBoost is to set the verbose option to # verbose = 0, no message but use print.every.n,verbose = 1, print evaluation metric, verbose = 2, also print information about the tree.
3.6. CROSS VALIDATION
Using the xgb.cv function for 5-fold cross validation, this function returns CV error, which is an estimate of test or out of sample error.
# set random seed, for reproducibility
set.seed(1234)
# Using booster gbtree, with a large nround=50
xgbcv <- xgb.cv(params = list(
# booster = "gbtree",
objective = 'binary:logistic'),
metrics = list("rmse","auc"),
label=y,
data = dtrain,
nrounds = 50,
nfold = 5, # 5 fold CV
showsd = T,
print_every_n = 10, # when verbose =0
# early_stopping_rounds = 5,
verbose=1,
prediction = T)
xgboost: label will be ignored.
[1] train-rmse:0.354438+0.000000 train-auc:1.000000+0.000000 test-rmse:0.354438+0.000000 test-auc:1.000000+0.000000
[11] train-rmse:0.016630+0.000000 train-auc:1.000000+0.000000 test-rmse:0.016630+0.000000 test-auc:1.000000+0.000000
[21] train-rmse:0.000957+0.000000 train-auc:1.000000+0.000000 test-rmse:0.000956+0.000000 test-auc:1.000000+0.000000
[31] train-rmse:0.000172+0.000000 train-auc:1.000000+0.000000 test-rmse:0.000172+0.000000 test-auc:1.000000+0.000000
[41] train-rmse:0.000172+0.000000 train-auc:1.000000+0.000000 test-rmse:0.000172+0.000000 test-auc:1.000000+0.000000
[50] train-rmse:0.000172+0.000000 train-auc:1.000000+0.000000 test-rmse:0.000172+0.000001 test-auc:1.000000+0.000000
# nround best iteration is, based on the min test rmse:
it <- which.min(xgbcv$evaluation_log$test_rmse_mean)
bestiteration <- xgbcv$evaluation_log$iter[it]
bestiteration
[1] 30
Plot RMSE for the dtrain cross validation.
# Plot the RMSE from the CV
xgbcv$evaluation_log %>%
dplyr::select(iter,train_rmse_mean,test_rmse_mean) %>%
gather(TestOrTrain, RMSE, -iter) %>%
ggplot(aes(x = iter, y = RMSE, group = TestOrTrain, color = TestOrTrain)) +
geom_line() +
theme_bw()

# Calculate AUC for the xgbcv
xgbcv.ROC <- roc(response = y,
predictor = xgbcv$pred)
# Area under the curve: 1?
xgbcv.ROC$auc
Area under the curve: 1
This model appears to set the AUC to 1, we will look to use caret for the model training.
3.7. MODEL TRAINING
One way to measure progress in learning of a model is using xgb.train, providing a second dataset already classified. Therefore it can learn on the first dataset and test its model on the second one. Metrics are measured after each round during the learning. However we can also use caret to train using xgbTree to compare models.
In this model training we will tune the hyper parameters, although this is computationally expensive way to train a model.
We will use the following functions:
- expand.grid function to make a data.frame with every combination of hyperparameters
- caret::trainControl to specify the type of cross validation (here use 5-fold cross validation)
- caret::train to search over the grid of hyperparameter combinations to find the model that maximises ROC
# Set random seed, for reproducibility
set.seed(1234)
# Set expand.grid
xgb.grid <- expand.grid(nrounds = c(bestiteration, 100), # Set the number of iterations from the xgb.cv and providing another higher value for comparison
eta = seq(from=0.2, to=1, by=0.2), # shrinkage
max_depth = c(6,8),
colsample_bytree = c(0.2, 0.8), # variables per tree. default 1
gamma = c(0,1), # default 0
min_child_weight = c(1,10),
subsample=1) # default 1
# Set training control
cntrl <- trainControl(method = "repeatedcv", # 5 fold cross validation
number = 2, # do 2 repetitions of cv
repeats = 2, # repeated 5 times
summaryFunction=twoClassSummary, # built-in function to calculate the area under the ROC curve, to compare models
classProbs = TRUE,
allowParallel = TRUE,
verboseIter = FALSE)
# Train model with gbtree and params above using caret
tuningmodel <- train(x=training2[,-9],
y=training2$is_female, # target vector should be non-numeric factors to identify our task as classification, not regression.
method="xgbTree",
metric="ROC",
trControl=cntrl,# specify cross validation
tuneGrid=xgb.grid, # Which hyperparameters we'll test
maximize = TRUE)
# View the model results
tuningmodel$bestTune
# Plot the performance of the training models
# scatter plot of the AUC against max_depth and eta
ggplot(tuningmodel$results, aes(x = as.factor(eta), y = max_depth, size = ROC, color = ROC)) +
geom_point() +
ggtitle("Scatter plot of the AUC against max_depth and eta") +
theme_bw() +
scale_size_continuous(guide = "none") +
scale_colour_gradient(low = "black", high="yellow")

# Plot the results of the grid combinations
plot(tuningmodel)




Based on the tuning we will now train our “best model”, bst with selected parameters.
# Set expand.grid
xgb.grid <- expand.grid(nrounds = 100, # Set the maximum number of iterations from the xgb.cv
eta = 0.2, # shrinkage
max_depth = 8,
colsample_bytree = 0.8, # variables per tree. default 1
gamma = 0, # default 0
min_child_weight = 1,
subsample=1) # default 1
bst <- train(x=training2[,-9],
y=training2$is_female, # Target vector should be non-numeric factors to identify our task as classification, not regression.
method="xgbTree",
metric="ROC",
trControl=cntrl,# Specify cross validation
tuneGrid=xgb.grid,
maximize = TRUE) # Which hyperparameters we'll test
# View the model results
bst$bestTune
### xgboostModel Predictions and Performance
# Make predictions using the test data set
xgb.pred <- ifelse(predict(bst,valid, type = "raw")== "two", 1, 0)
#Look at the confusion matrix
confusionMatrix(xgb.pred,valid$is_female)
Confusion Matrix and Statistics
Reference
Prediction 0 1
0 1639 46
1 38 1928
Accuracy : 0.977
95% CI : (0.9716, 0.9816)
No Information Rate : 0.5407
P-Value [Acc > NIR] : <2e-16
Kappa : 0.9537
Mcnemar's Test P-Value : 0.445
Sensitivity : 0.9773
Specificity : 0.9767
Pos Pred Value : 0.9727
Neg Pred Value : 0.9807
Prevalence : 0.4593
Detection Rate : 0.4489
Detection Prevalence : 0.4615
Balanced Accuracy : 0.9770
'Positive' Class : 0
#Draw the ROC curve using the pRoc package
xgb.probs <- predict(bst,valid,type="prob")
xgb.ROC <- roc(predictor=xgb.probs$one,
response=valid$is_female)
xgb.ROC$auc
Area under the curve: 0.9941
# Area under the curve
plot(xgb.ROC,main="xgboost ROC")

Plot the variable importance of this best model, bst.
# Variable Importance
varimpxgb <- varImp(bst)$importance %>%
mutate(Column.Name=row.names(.)) %>%
arrange(-Overall)
ddvarimp <- merge(dd2,varimpxgb,by = "Column.Name")
ddvarimp[1:10,]
positions <- varimpxgb[1:10,]$Column.Name
ggplot(varimpxgb[1:10,]) + geom_bar(aes(Column.Name,Overall),stat="identity") + coord_flip() + scale_x_discrete(limits = positions)

One of our engineered features, DL1household, contributes second in the variable importance.
LS0tDQp0aXRsZTogIldJRFMgRGF0YVRob24gMjAxOCINCmF1dGhvcjogImtpbW5ld3plYWxhbmQiDQpkYXRlOiAiMjcgRmVicnVhcnkgMjAxOCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBmaWdfaGVpZ2h0OiA0DQogICAgaGlnaGxpZ2h0OiBweWdtZW50cw0KICAgIHRoZW1lOiBzcGFjZWxhYg0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQojIyBCYWNrZ3JvdW5kDQoNCkluIHRoaXMgS2FnZ2xlIGNvbXBldGl0aW9uLCBbIFNvY2lhbCBJbXBhY3QgZm9yIFdvbWVuIGluIEltcG92ZXJpc2hlZCBDb3VudHJpZXMgV0lEU19EYXRhVGhvbl8yMDE4IGNvbXBldGl0aW9uXShodHRwOi8vd3d3LndpZHNjb25mZXJlbmNlLm9yZy9kYXRhdGhvbi1kZXRhaWxzLmh0bWwpLCB0aGUgYWltIGlzIHRvIHByZWRpY3QgdGhlIGdlbmRlciBvZiBlYWNoIHN1cnZleSByZXNwb25kZW50IGJhc2VkIG9uIGRlbW9ncmFwaGljIGFuZCBiZWhhdmlvdXJhbCBpbmZvcm1hdGlvbiBmcm9tIGEgcmVwcmVzZW50YXRpdmUgc2FtcGxlIG9mIHN1cnZleSByZXNwb25kZW50cyBmcm9tIEluZGlhIGFuZCB0aGVpciB1c2FnZSBvZiB0cmFkaXRpb25hbCBhbmQgbW9iaWxlIGZpbmFuY2lhbCBzZXJ2aWNlcy4NCg0KVGhpcyBub3RlYm9vayBpbmNsdWRlcyBtb2RlbCB0cmFpbmluZyB0byBjcmVhdGUgdGhlIHByZWRpY3Rpb25zIGZpbGUgZm9yIHN1Ym1pc3Npb24gdG8gS2FnZ2xlLg0KDQpTdWJtaXNzaW9ucyBhcmUgZXZhbHVhdGVkIG9uIGFyZWEgdW5kZXIgdGhlIFJPQyBjdXJ2ZSBiZXR3ZWVuIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgYW5kIHRoZSBvYnNlcnZlZCB0YXJnZXQgdmFyaWFibGUgaXNfZmVtYWxlLg0KDQoqICogKg0KDQojIyMgU2V0dXANCg0KIyMgTG9hZCBwYWNrYWdlcw0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQpgYGB7ciBsb2FkIHBhY2thZ2VzLCBlcnJvcj1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoeGdib29zdCkNCmxpYnJhcnkocFJPQykNCmBgYA0KDQpgYGB7ciBzZXNzaW9uaW5mb30NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQojIyBMb2FkIERhdGENCg0KKipMb2FkIGRhdGFzZXRzKioNCg0KVGhlIGZpcnN0IHN0ZXAgd2lsbCBiZSB0byBsb2FkIHRoZSB0ZXN0IGRhdGEgZmlsZSBhbmQgdHJhaW4gZGF0YSBmaWxlLiBUaGVzZSBoYXZlIGJlZW4gZG93bmxvYWRlZCB0byBhIGxvY2FsIEthZ2dsZSBmb2xkZXIgb2ZmbGluZSBhcyB0aGVyZSBpcyBhbiBhZ3JlZW1lbnQgc3RlcCB0byB0aGUgS2FnZ2xlIGRhdGEgdGVybXMgYW5kIGNvbmRpdGlvbnMgYW5kIHVuemlwcGVkIHVzaW5nIDctemlwLg0KDQpXZSB3aWxsIHVzZSB0aGUgZGF0YS50YWJsZSBSIHBhY2thZ2UgZGVzaWduZWQgZm9yIGxhcmdlIGRhdGFzZXRzLg0KDQpgYGB7ciBsb2FkZGF0YSwgZXJyb3I9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNvbW1lbnQ9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp1cmwgPC0gImh0dHBzOi8vd3d3LmthZ2dsZS5jb20vYy93aWRzMjAxOGRhdGF0aG9uL2RhdGEiDQpzZXR3ZCgifi9LYWdnbGUvV2lEUyIpDQp0cmFpbiA8LSBmcmVhZCgiLi90cmFpbi5jc3YiKQ0KdGVzdCA8LSBmcmVhZCgiLi90ZXN0LmNzdiIpDQpkZDIgPC0gZGF0YS5mcmFtZShyZWFkX2V4Y2VsKCIuL1dpRFNERDIueGxzeCIpKQ0KYGBgDQoNCioqT3ZlcnZpZXcqKg0KDQpUaGUgdHJhaW4gZGF0YXNldCBoYXMgYHIgZGltKHRyYWluKVsxXWAgcm93cyBhbmQgYHIgZGltKHRyYWluKVsyXWAgY29sdW1ucy4NCg0KX1RoZSBmb2xsb3dpbmcgaW5mb3JtYXRpb24gaXMgYXZhaWxhYmxlIGZyb20gdGhlIEthZ2dsZSBkaXNjdXNzaW9uIGZvcnVtOiBfIA0KDQo+IGlzX2ZlbWFsZSAtIHRoZSBpc19mZW1hbGUgdmFyaWFibGUgeW91IGFyZSBnb2luZyB0byBwcmVkaWN0LiBOb3RlOiBpbiB0aGUgZGF0YSBkaWN0aW9uYXJ5LCBGZW1hbGUgaXMgMiwgbWFsZSBpcyAxLCB3aGlsZSBpbiBvdXIgdHJhbnNmb3JtZWQgZGF0YSwgaXNfZmVtYWxlPTEgZm9yIGZlbWFsZSBhbmQgaXNfZmVtYWxlPTAgZm9yIG1hbGUuDQpGb3IgdGhlIHJlc3Qgb2YgdGhlIDEwMDArIGNvbHVtbiBkZXNjcmlwdGlvbnMsIHBsZWFzZSByZWZlciB0byBXaURTRGF0YURpY3Rpb25hcnkgZm9yIGRldGFpbHMuIFBsZWFzZSBub3RlIHRoYXQgZGF0YSBoYXMgYmVlbiBwcm9jZXNzZWQgYW5kIHNvbWUgY29sdW1ucyB3ZXJlIHJlbW92ZWQgdG8gcHJldmVudCBkYXRhIGxlYWthZ2Ugb3IgdG8gcHJvdGVjdCBwcml2YWN5Lg0KDQpGcm9tIHJldmlld2luZyB0aGUgZGF0YSBkaWN0aW9uYXJ5LCB0aGUgdmFyaWFibGVzIGFyZSBhbGwgbnVtZXJpYyBvciBpbnRlZ2Vycy4gVGhlcmUgYXJlIGFsc28gYSBsYXJnZSBudW1iZXIgb2YgdmFyaWFibGVzLCB0aGVyZWZvcmUgd2Ugd2lsbCBpbml0aWFsbHkgbG9vayBmb3Igd2F5cyB0byBwZXJmb3JtIGRpbWVuc2lvbiByZWR1Y3Rpb24uIA0KDQpGcm9tIHJldmlld2luZyB0aGUgZGF0YSBkaWN0aW9uYXJ5LCB0aGUgdmFyaWFibGVzIGFyZSBhbGwgbnVtZXJpYyBvciBpbnRlZ2Vycy4gVGhlcmUgYXJlIGFsc28gYSBsYXJnZSBgciBkaW0odHJhaW4pWzJdYCB2YXJpYWJsZXMsIHRoZXJlZm9yZSB3ZSB3aWxsIGluaXRpYWxseSBsb29rIGZvciB3YXlzIHRvIHBlcmZvcm0gZGltZW5zaW9uIHJlZHVjdGlvbi4gTm90ZSB0aGVyZSBhcmUgbGVzcyB2YXJpYWJsZXMgaW4gdGhlIGRhdGEgZGljdGlvbmFyeSBgciBkaW0oZGQyKVsyXWAgc28gaXQgYXBwZWFycyB3ZSBhcmUgbWlzc2luZyBzb21lIHZhcmlhYmxlIGRlc2NyaXB0aW9ucy4gIEl0IGFwcGVhcnMgdGhhdCB0aGUgQUIgcXVlc3Rpb25zIGFyZSBhbHNvIG1pc3NpbmcgZnJvbSBvdXIgZGF0YXNldHMuDQoNCkNoZWNrIGlmIGFueSB2YXJpYWJsZXMgYXJlIGNoYXJhY3RlcnMsIGFuZCBoYXZlIGxhcmdlIGNvdW50LCB1c2luZyBhIHRocmVzaG9sZCBvZiAxNCwwMDAuDQoNCmBgYHtyIGNoZWNrY2hhcnN9DQojIHNlbGVjdCBjaGFyYWN0ZXIgdmFyaWFibGVzIHdpdGggbm9uIHdoaXRlIHNwYWNlDQppc2NoYXJzIDwtIHRyYWluICU+JSBzZWxlY3RfaWYoaXMuY2hhcmFjdGVyKQ0KaXNjaGFyc3RydWUgPC0gc2FwcGx5KGlzY2hhcnMsZnVuY3Rpb24oeCkgdGFibGUoeCA9PSIiKVsiVFJVRSJdKQ0KaXNjaGFyc3RydWVbaXNjaGFyc3RydWUgPCAxNDAwMF0NCiMgQ29udmVydCB0aGVzZSBjaGFyYWN0ZXIgdmFyaWFibGVzIHRvIGZhY3RvcnMgc28gdGhhdCB0aGV5IHJldGFpbiBhIHZhbHVlIHdoZW4gdGhlIHRyYWluIGFuZCB0ZXN0IHNldHMgYXJlIGNvbnZlcnRlZCB0byBudW1lcmljDQp0cmFpbiRMTjJfUkluZExuZ0JFT3RoIDwtIGFzLmZhY3Rvcih0cmFpbiRMTjJfUkluZExuZ0JFT3RoKQ0KdHJhaW4kTE4yX1dJbmRMbmdCRU90aCA8LSBhcy5mYWN0b3IodHJhaW4kTE4yX1dJbmRMbmdCRU90aCkNCnRlc3QkTE4yX1JJbmRMbmdCRU90aCA8LSBhcy5mYWN0b3IodGVzdCRMTjJfUkluZExuZ0JFT3RoKQ0KdGVzdCRMTjJfV0luZExuZ0JFT3RoIDwtIGFzLmZhY3Rvcih0ZXN0JExOMl9XSW5kTG5nQkVPdGgpDQpgYGANCg0KQ29udmVydCB0aGUgdHJhaW4gYW5kIHRlc3QgZGF0YXNldHMgdG8gbnVtZXJpYyBjbGFzc2VzIHVzaW5nIHRoZSBtYXAgZnVuY3Rpb24gZnJvbSB0aGUgW3B1cnJdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9wdXJyci92ZXJzaW9ucy8wLjIuMi4yKSBSIHBhY2thZ2UsIGZvciBYR0Jvb3N0IG1vZGVsbGluZy4NCg0KYGBge3IgbnVtZXJpY2RhdGEsIHdhcm5pbmc9RkFMU0V9DQp0cmFpbiA8LSBkYXRhLmZyYW1lKG1hcCh0cmFpbiwgfmFzLm51bWVyaWMoLikpICkNCnRlc3QgPC0gZGF0YS5mcmFtZShtYXAodGVzdCwgfmFzLm51bWVyaWMoLikpICkNCmBgYA0KDQoNCiogKiAqDQoNCiMjIyBQYXJ0IDE6IEVEQQ0KDQpTZWUgc2VwYXJhdGUgc2NyaXB0LCBXaURTRURBLlJtZA0KDQojIyMgUGFydCAyOiBEYXRhIENsZWFuaW5nDQoNCkxldCdzIGNsZWFuIHRoZSB0cmFpbiBhbmQgdGVzdCBkYXRhc2V0cyB2YXJpYWJsZSBieSB2YXJpYWJsZSBiYXNlZCBvbiB0aGUgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcy4NCg0KMi4xIFJlbW92ZSB0cmFpbiBJRHMNCg0KYGBge3IgcmVtb3ZlaWR9DQojIFJlbW92ZSBJRCBmZWF0dXJlIGZyb20gdHJhaW4gYXMgdGhpcyBpZGVudGlmaWVyIGlzIG5vdCBuZWVkZWQgaW4gdGhlIHRyYWluaW5nLiBUaGUgaWQgd2lsbCBiZSBsZWZ0IGluIHRoZSB0ZXN0IHNldCBmb3IgdGhlIHByZWRpY3Rpb25zLg0KdHJhaW4gPC0gdHJhaW4gJT4lIGRwbHlyOjpzZWxlY3QoLXRyYWluX2lkKQ0KaWQgPC0gdGVzdCR0ZXN0X2lkDQp0ZXN0IDwtIHRlc3QgJT4lIGRwbHlyOjpzZWxlY3QoLXRlc3RfaWQpDQpgYGANCg0KMi4yIFJlbW92ZSBuZWFyIHplcm8gdmFyaWFuY2UgdmFyaWFibGVzDQoNClJlbW92aW5nIHZhcmlhYmxlcyB3aXRoIG5lYXIgemVybyB2YXJpYW5jZSB1c2luZyB0aGUgW2NhcmV0XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvY2FyZXQvaW5kZXguaHRtbCkgUiBwYWNrYWdlLg0KDQpgYGB7ciBuZWFyemVyb3Zhcn0NCm5lYXIuemVybyA8LSBuZWFyWmVyb1Zhcih0cmFpbixzYXZlTWV0cmljcz1UUlVFKQ0KdmFyaWFibGVzIDwtIHJvdy5uYW1lcyhzdWJzZXQobmVhci56ZXJvLG5lYXIuemVybyRuenY9PUZBTFNFKSkNCnRyYWluIDwtIGRhdGEuZnJhbWUodHJhaW4pWyx2YXJpYWJsZXNdDQojIFBlcmZvcm0gdGhlIHNhbWUgYWN0aW9uIG9uIHRoZSB0ZXN0IHNldCwgYWZ0ZXIgcmVtb3ZpbmcgdGhlICJpc19mZW1hbGUiIGluIHBvc2l0aW9uIDkgZnJvbSB2YXJpYWJsZXMgYXMgdGhpcyBpcyB0byBiZSBwcmVkaWN0ZWQgaW4gdGhlIHRlc3Qgc2V0DQp0ZXN0IDwtIGRhdGEuZnJhbWUodGVzdClbLHZhcmlhYmxlc1stOV1dDQpgYGANCg0KMi4zIE1pc3NpbmcgVmFsdWVzDQoNCk5leHQgd2Ugd2lsbCBleHRyYWN0IGFuZCB2aWV3IHRoZSB2YXJpYWJsZXMgaW4gbW9yZSBkZXRhaWwgdGhhdCBoYXZlIHZhbHVlIE5BLCB3aGljaCByZXByZXNlbnQgdGhlIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBkYXRhc2V0LiBXaXRoIGRwbHlyIHdlIGNhbiBjYWxsIGZ1bmN0aW9ucyBmcm9tIGRpZmZlcmVudCBSIHBhY2thZ2VzIGRpcmVjdGx5IGluc2lkZSB0aGUgZHBseXIgZnVuY3Rpb25zLiBXZSB3aWxsIHVzZSB0aGUgW3N0cmluZ3JdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9zdHJpbmdyL3ZpZ25ldHRlcy9zdHJpbmdyLmh0bWwpIFIgcGFja2FnZSB3aXRoIGRwbHlyIHRvIHZpZXcgYSBzdW1tYXJ5IG9mIHRoZSBOQXMuIFdlIHdpbGwgYWxzbyB1c2UgdGhlIFtwdXJycl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3B1cnJyL3ZlcnNpb25zLzAuMi4yLjIpIHRvIGFwcGx5IHRoZSBzdW0gZnVuY3Rpb24gYWNyb3NzIG11bHRpcGxlIHZhcmlhYmxlcy4NCg0KYGBge3IgbWlzc2luZ30NCiMgVXNlIHRoZSBiYXNlIHN1bW1hcnkgZnVuY3Rpb24gZm9yIHJlc3VsdCBzdW1tYXJpZXMgbm90IGRwbHlyLiBUaGlzIHdpbGwgcHJvdmlkZSB1cyB3aXRoIHRoZSByYW5nZXMgb2YgdGhlIHZhcmlhYmxlcyBpbmNsdWRpbmcgdGhlIG1pbmltdW1zLg0KcyA8LSBzdW1tYXJ5KHRyYWluKSAgDQojICBFeHRyYWN0IGFuZCB2aWV3IHRoZSBmcmVxdWVuY3kgb2YgTkEncyBmcm9tIHRoZSBzdW1tYXJ5IHMgd2UganVzdCBjcmVhdGVkLiBVc2Ugc3RyX2RldGVjdCBmcm9tIHN0cmluZ3IgcGFja2FnZS4gDQpzICU+JSANCiAgICAgIGRhdGEuZnJhbWUoKSAlPiUgDQogICAgICBmaWx0ZXIoc3RyX2RldGVjdChGcmVxLCJOQSIpKSANCiMgU2luY2UgdGhlcmUgYXJlIHZhcmlhYmxlcyB3aXRoIHZlcnkgaGlnaCBwcm9wb3J0aW9uIG9mIE5BcywgbGV0J3MgcmVtb3ZlIHRoZXNlIHZhcmlhYmxlcyBhc3N1bWluZyB0aGV5IHdpbGwgbm90IGFkZCBwcmVkaWN0aXZlIHZhbHVlDQojIFVzaW5nIG1hcCBmdW5jdGlvbiBmcm9tIHRoZSBwdXJyIHBhY2thZ2Ugc3VtIHRoZSBOQXMgZm9yIGVhY2ggY29sdW1uDQpjb3VudF9uYSA8LSBtYXAodHJhaW4sIH5zdW0oaXMubmEoLikpKSANCiMgUmVtb3ZlIGNvbHVtbnMgd2l0aCBtb3JlIHRoYW4gJSBOQSB2YWx1ZXMgdXNpbmcgYW4gaW5kZXgNCmluZGV4MSA8LSB3aGljaChjb3VudF9uYSA8IDAuOSpkaW0odHJhaW4pWzFdKSAgDQp0cmFpbiA8LSB0cmFpblssIGluZGV4MV0NCiMgUGVyZm9ybSB0aGUgc2FtZSBhY3Rpb24gb24gdGhlIHRlc3Qgc2V0LCBhZnRlciByZW1vdmluZyB0aGUgImlzX2ZlbWFsZSIgaW4gcG9zaXRpb24gOCBmcm9tIHZhcmlhYmxlcyBhcyB0aGlzIGlzIHRvIGJlIHByZWRpY3RlZCBpbiB0aGUgdGVzdCBzZXQNCmluZGV4MiA8LWluZGV4MVstOF0NCmluZGV4MltpbmRleDI+OF08LWFzLmludGVnZXIoaW5kZXgyW2luZGV4Mj44XS0xKQ0KdGVzdCA8LSB0ZXN0WyxpbmRleDJdDQpgYGANCg0KMi40IEltcHV0YXRpb24gb2YgbWlzc2luZyB2YWx1ZXMNCg0KV2UgYXJlIGdvaW5nIHRvIHJldmlldyB0aGUgY29sdW1ucyB3aXRoIG1pc3NpbmcgdmFsdWVzIHRvIGltcHV0ZSB0aGUgdmFsdWVzLiBXZSB3aWxsIGNyZWF0ZSBhIG1lcmdlZCBkYXRhc2V0IGZyb20gdGhlIGRhdGEgZGljdGlvbmFyeS4gVGhlbiB1c2luZyB0aGUgcmVwbGFjZV9uYSBmdW5jdGlvbiBmcm9tIHRoZSBbdGlkeXJdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy90aWR5ci9pbmRleC5odG1sKSBSIHBhY2thZ2UuDQoNCmBgYHtyIGltcHV0ZX0NCiMgIEV4dHJhY3QgYW5kIHZpZXcgdGhlIGZyZXF1ZW5jeSBvZiBOQSdzIGFnYWluDQpzIDwtIHN1bW1hcnkodHJhaW4pIA0KbWlzc2luZyA8LSBzICU+JSANCiAgICAgIGRhdGEuZnJhbWUoKSAlPiUgDQogICAgICBmaWx0ZXIoc3RyX2RldGVjdChGcmVxLCJOQSIpKSANCiMgUmVtb3ZlIHRoZSB3aGl0ZSBzcGFjZSBhbmQgcmVuYW1lIGNvbHVtbnMgdG8gbWFrZSBpdCBlYXNpZXIgdG8gbWVyZ2UgaW50byBhIGRkbWlzc2luZyBkYXRhZnJhbWUNCm1pc3NpbmckVmFyMiA8LSBtaXNzaW5nJFZhcjIgJT4lIA0KICAgICAgYXMuY2hhcmFjdGVyKCkgJT4lDQogICAgICB0cmltd3MoKQ0KbWlzc2luZyRGcmVxIDwtIGFzLmNoYXJhY3RlcihtaXNzaW5nJEZyZXEpDQpuYW1lcyhtaXNzaW5nKSA8LSBjKCJDb2x1bW4uTmFtZSIsIkZyZXEiKQ0KZGRtaXNzaW5nIDwtIG1lcmdlKGRkMixtaXNzaW5nLGJ5ID0gIkNvbHVtbi5OYW1lIikNCmRkbWlzc2luZw0KIyBXZSBzZWUgdGhhdCBNVDFBIGhhcyBoaWdoIHZhcmlhYmxlIGltcG9ydGFuY2UgZnJvbSB0aGUgRURBIHNvIHdlIHdpbGwgcmVwbGFjZSB0aGUgTkEgdmFsdWVzIHdpdGggOTksIHdoaWNoIGlzIHRoZSBleGlzdGluZyBOQSBjYXRlZ29yeSBpbiB0aGlzIHF1ZXN0aW9ucy4gTm90ZSB0aGUgc3RhcnRlciBtb2JpbGUgcXVlc3Rpb24gTVQxIGFuZCBNVDIgaGF2ZSBubyBOQXMuIA0KdHJhaW4gPC0gdHJhaW4gJT4lIA0KICAgcmVwbGFjZV9uYShsaXN0KE1UMUE9OTkpKQ0KIyBXZSBzZWUgdGhhdCB0aGlzICBoYXMgaGlnaCB2YXJpYWJsZSBpbXBvcnRhbmNlIE1UNi5Ib3cgZGlkIHlvdSBvYnRhaW4geW91ciBwaG9uZT8gKFRvcCAxMCBWSSkgR09UIE5Bcy4gUmVwbGFjZSB3aXRoIGV4aXN0aW5nIGNhdGVnb3J5IDk5DQp0cmFpbiA8LSB0cmFpbiAlPiUgDQogICByZXBsYWNlX25hKE1UNj05OSkNCiMgV2Ugc2VlIHRoYXQgdGhpcyAgaGFzIGhpZ2ggdmFyaWFibGUgaW1wb3J0YW5jZSBETDIuIFdoYXQgaXMgeW91ciBwcmltYXJ5IGpvYiAoaS5lLiwgdGhlIGpvYiB3aGVyZSB5b3Ugc3AgUmVwbGFjZSB3aXRoIGV4aXN0aW5nIGNhdGVnb3J5IDk2DQp0cmFpbiA8LSB0cmFpbiAlPiUgDQogICByZXBsYWNlX25hKERMMj05NikNCiMgV2Ugc2VlIHRoYXQgdGhpcyAgaGFzIGhpZ2ggdmFyaWFibGUgaW1wb3J0YW5jZSBETDEuIEluIHRoZSBwYXN0IDEyIG1vbnRocywgd2VyZSB5b3UgbWFpbmx5Li4uPyBSZXBsYWNlIHdpdGggZXhpc3RpbmcgY2F0ZWdvcnkgOTYNCnRyYWluIDwtIHRyYWluICU+JSANCiAgIHJlcGxhY2VfbmEoREwxPTk2KQ0KIyBOQSBmaXggcmVwbGFjZSBhbGwgcmVtYWluaW5nIE5BIHdpdGggOTkuIFdlIHdpbGwgbm90IHVzZSB0aGlzIHJlcGxhY2VtZW50IGFzIFhHYm9vc3QgY2FuIGhhbmRsZSBtaXNzaW5nIHZhbHVlcw0KIyB0cmFpbiA8LSB0cmFpbiAlPiUgDQojICByZXBsYWNlKC4saXMubmEoLiksIDk5KQ0KIyB0ZXN0IDwtIHRlc3QgJT4lIA0KIyAgcmVwbGFjZSguLGlzLm5hKC4pLCA5OSkNCmBgYA0KDQoyLjUgSW1wdXRhdGlvbiBvZiB0aGUgbWVkaWFuDQoNClRoZSB2YXJpYWJsZSB0cmFpbiRBQTE0IGhhcyBzb21lIG91dGxpZXJzLCBhbmQgaXQgaXMgaW4gdGhlIHRvcCAxMCBpbiB0aGUgdmFyaWFibGUgaW1wb3J0YW5jZSBidXQgaXQgaXMgbm90IGluIHRoZSBkYXRhIGRpY3Rpb25hcnkuIFdlIHdpbGwgaW1wdXRlIHRoZSBtZWRpYW4gb24gdGhpcyB2YXJpYWJsZS4gDQoNCmBgYHtyIEFBMTRtZWRpYW59DQojIEltcHV0ZSB0aGUgb3V0bGllciB0byB0aGUgbWVkaWFuIG9mIHRoaXMgdmFyaWFibGUNCnRyYWluJEFBMTRbdHJhaW4kQUExND09OTk5OTldIDwtIG1lZGlhbih0cmFpbiRBQTE0KQ0KdGVzdCRBQTE0W3Rlc3QkQUExND09OTk5OTldIDwtIG1lZGlhbih0ZXN0JEFBMTQpDQojIENoZWNrIHRoZSBpbXB1dGF0aW9uDQp0cmFpbiAlPiUgc2VsZWN0KEFBMTQpICU+JSBmaWx0ZXIoQUExND09OTk5OTkpICU+JSBoZWFkKCkNCnN1bW1hcnkodHJhaW4kQUExNCkNCmBgYA0KDQoNCg0KKiAqICoNCg0KIyMgUGFydCAzOiBNb2RlbGluZw0KDQozLjEuICoqTU9ERUwgU0VMRUNUSU9OKioNCg0KU2luY2Ugd2Uga25vdyB0aGUgb3V0Y29tZSBkaXNjcmV0ZSB2YXJpYWJsZSwgd2Ugd2lsbCB1c2UgYSBzdXBlcnZpc2VkIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtLiBJdCBhbHNvIGFwcGVhcnMgZnJvbSBvdXIgRURBLCB0aGUgaXNfZmVtYWxlIHZhcmlhYmxlIGlzIDAgb3IgMSwgd2Ugd2lsbCBwb3RlbnRpYWxseSBuZWVkIGEgY2xhc3NpZmljYXRpb24gbW9kZWwuIA0KVGhlIGFsZ29yaXRobSB0aGF0IHdlIHdpbGwgdXNlIGlzIFhHQm9vc3Qgd2hlcmUgd2UgY2FuIGNob29zZSBiZXR3ZWVuIHRoZSB0d28gYm9vc3RlciBtZXRob2RzLiBJbml0aWFsbHkgd2Ugd2lsbCB1c2UgZGVmYXVsdCBYR0Jvb3N0IHBhcmFtZXRlcnMgYW5kIHBlcmZvcm0gc29tZSBpbml0aWFsIGZlYXR1cmUgZW5naW5lZXJpbmcuDQoNCldlIHdpbGwgdXNlIEFVQywgYXMgb3VyIG1ldHJpYyBhbmQgdWx0aW1hdGVseSBzZWxlY3QgdGhlIGZpbmFsIG1vZGVsIGJhc2VkIG9uIHRoZSBoaWdoZXN0IHZhbHVlLiBTZWUgdGhpcyBwb3N0IGZvciBtb3JlIGluZm9ybWF0aW9uIG9uIHRoZXNlIFtjdXJ2ZXNdKGh0dHA6Ly9ibG9nLnloYXQuY29tL3Bvc3RzL3JvYy1jdXJ2ZXMuaHRtbCkNCg0KSW4gUiwgdGhlIFhHQm9vc3QgcGFja2FnZSB1c2VzIHRoZSBmb2xsb3dpbmc6DQoNCi0gQSBtYXRyaXggb2YgaW5wdXQgZGF0YSBpbnN0ZWFkIG9mIGEgZGF0YSBmcmFtZS4gDQoNCi0gT25seSBudW1lcmljIHZhcmlhYmxlcy4NCg0KLSBUaGUgaXNfZmVtYWxlIHZhcmlhYmxlIHNlcGFyYXRlbHksIHdoaWNoIHdlIHdpbGwgY29kZSB0byB5Lg0KDQoNCldlIHdpbGwgY29udmVydCB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGludG8gZHVtbXkgdmFyaWFibGVzIHVzaW5nIG9uZSBob3QgZW5jb2RpbmcuICBJdCBpcyByZWNvbW1lbmRlZCBieSB0aGUgUiBwYWNrYWdlIFhHQm9vc3QncyB2aWduZXR0ZSB0byB1c2UgIHhnYi5ETWF0cml4Lg0KDQpYR0Jvb3N0IHdpbGwgYWxzbyBoYW5kbGUgTkEgbWlzc2luZyB2YWx1ZXMgc28gdGhlc2Ugd2lsbCBiZSBsZWZ0IGluIHRoZSB0cmFpbiBhbmQgdGVzdCBkYXRhc2V0cy4NCg0KMy4yLiAqKkZFQVRVUkUgRU5HSU5FRVJJTkcqKg0KDQozLjIuMSBBZGQgbmV3IGZlYXR1cmVzDQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3IgYm90aCB0aGUgdHJhaW4gYW5kIHRlc3Qgc2V0cywgYmFzZWQgb24gb3VyIEVEQSBhbmQgcHJldmlvdXMgcnVucyBvZiB0aGUgbW9kZWxsaW5nLg0KDQpBLiBBZ2UgYnJhY2tldHMNCg0KREcxIHJlbGF0ZXMgdG8gd2hhdCB5ZWFyIHRoZSByZXNwb25kZW50IHdhcyBib3JuLiBXZSBjYW4gYmluIGludG8gYWdlIGJyYWNrZXRzIGJhc2VkIG9uIHRoZSBzdXJ2ZXkgdGhhdCBpcyBkYXRlZCBbMjAxNl0oaHR0cDovL2ZpbmNsdXNpb24ub3JnL2Jsb2cvZmlpLXVwZGF0ZXMvZmluYW5jaWFsLWluY2x1c2lvbi1pbi1pbmRpYS1sZXNzb25zLWZyb20tdGhlLTIwMTYtZmlpLWRhdGEuaHRtbCkNCg0KYGBge3IgYWdlZ3JvdXBzfQ0KIyBDaGVjayB0aGUgcmFuZ2Ugb2YgdGhlIHllYXJzIGJvcm4gaW4gREcxDQpyYW5nZSh0cmFpbiRERzEpDQojIFNldCB0aGUgYnJlYWtzDQphZ2VicmVha3MgPC0gYygxOTE2LDE5NTYsMTk5MSwxOTk3LDIwMDEpDQojIEN1dCB0aGUgdHJhaW4gc2V0IGludG8gYSBuZXcgdmFyaWFibGUgY2FsbGVkIERHMUENCnRyYWluJERHMUEgPC0gY3V0KHRyYWluJERHMSwgYWdlYnJlYWtzLCBpbmNsdWRlLmxvd2VzdD1UKQ0KIyBSZW5hbWUgdGhlIGxldmVscw0KbGV2ZWxzKHRyYWluJERHMUEpIDwtIHJldihjKCIxNSB0byAxOCB5ZWFycyIsIjE5IHRvIDI0IHllYXJzIiwiMjUgdG8gNTkgeWVhcnMiLCI2MCB5ZWFycyBhbmQgb3ZlciIpKQ0KIyBDb252ZXJ0IHRvIG51bWVyaWMNCnRyYWluJERHMUE8LXVuY2xhc3ModHJhaW4kREcxQSkgJT4lIGFzLm51bWVyaWMNCiMgRG8gdGhlIHNhbWUgZm9yIHRoZSB0ZXN0IHNldC4gQ3V0IHRoZSB0ZXN0IHNldA0KdGVzdCRERzFBIDwtY3V0KHRlc3QkREcxLCBhZ2VicmVha3MsIGluY2x1ZGUubG93ZXN0PVQpDQojIFJlbmFtZSB0aGUgbGV2ZWxzDQpsZXZlbHModGVzdCRERzFBKSA8LSByZXYoYygiMTUgdG8gMTggeWVhcnMiLCIxOSB0byAyNCB5ZWFycyIsIjI1IHRvIDU5IHllYXJzIiwiNjAgeWVhcnMgYW5kIG92ZXIiKSkNCiMgQ29udmVydCB0byBudW1lcmljDQp0ZXN0JERHMUE8LXVuY2xhc3ModGVzdCRERzFBKSAlPiUgYXMubnVtZXJpYw0KIyBDaGVjayB0aGUgcmFuZ2Ugb2YgdGhlIG5ldyB2YXJpYWJsZSBERzFBICBoYXMgYmVlbiBjb252ZXJ0ZWQgZnJvbSBiaW5zIHRvIG51bWVyaWMgdmFsdWVzDQpyYW5nZSh0cmFpbiRERzFBKQ0KYGBgDQoNCkIuIExpdmUgaW4gdG93biBvciB2aWxsYWdlDQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3Igd2hldGhlciBhIHJlc3BvbmRlbnQgbGl2ZXMgaW4gdG93biBvciBub3QuDQoNCmBgYHtyIHRvd252aWxsYWdlfQ0KdHJhaW4kQUE1b3Jub3QgPC0gaWZlbHNlKGlzLm5hKHRyYWluJEFBNSksMCwxKQ0KdGVzdCRBQTVvcm5vdCA8LSBpZmVsc2UoaXMubmEodGVzdCRBQTUpLDAsMSkNCmBgYA0KDQpDLiBQYXJlbnQgb3Igbm90DQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3Igd2hldGhlciBhIHJlc3BvbmRlbnQgaXMgYSBwYXJlbnQgb3Igbm90LCBhbmQgYSBncmFuZHBhcmVudCBvciBub3QgYmFzZWQgb24gdGhlIHF1ZXN0aW9uOiBERzYuSG93IGFyZSB5b3UgcmVsYXRlZCB0byB0aGUgaG91c2Vob2xkIGhlYWQ/DQoiMT1NeXNlbGZcbjI9U3BvdXNlXG4zPVNvbi9EYXVnaHRlclxuND1GYXRoZXIvTW90aGVyXG41PVNpc3Rlci9Ccm90aGVyXG42PUdyYW5kY2hpbGRcbjc9T3RoZXIgcmVsYXRpdmVcbjk9T3RoZXIgbm9uLXJlbGF0aXZlXG45OT1ESyINCg0KYGBge3IgcGFyZW50fQ0KdHJhaW4kREc2cGFyZW50IDwtIGlmZWxzZSh0cmFpbiRERzY9PTEgfHRyYWluJERHNj09MiwxLDApDQp0cmFpbiRERzZncGFyZW50IDwtIGlmZWxzZSh0cmFpbiRERzY9PTQgLDEsMCkNCnRlc3QkREc2cGFyZW50IDwtIGlmZWxzZSh0ZXN0JERHNj09MSB8dGVzdCRERzY9PTIsMSwwKQ0KdGVzdCRERzZncGFyZW50IDwtIGlmZWxzZSh0ZXN0JERHNj09NCAsMSwwKQ0KYGBgDQoNCkQuIFdvcmtpbmcgb3Igbm90DQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3Igd2hldGhlciBhIHJlc3BvbmRlbnQgaXMgc3RheSBhdCBob21lIG9yIG5vdCBvciBhIHN0dWRlbnQgb3Igbm90Lg0KDQpgYGB7ciB3b3JraW5nfQ0KdHJhaW4kREwxaG91c2Vob2xkIDwtIGlmZWxzZSh0cmFpbiRETDE9PTcgLDEsMCkNCnRyYWluJERMMXN0dWRlbnQgPC0gaWZlbHNlKHRyYWluJERMMT09OCAsMSwwKQ0KdGVzdCRETDFob3VzZWhvbGQgPC0gaWZlbHNlKHRlc3QkREwxPT03ICwxLDApDQp0ZXN0JERMMXN0dWRlbnQgPC0gaWZlbHNlKHRlc3QkREwxPT04ICwxLDApDQpgYGANCg0KRS4gQm91Z2h0IHBob25lIG9yIG5vdA0KDQpXZSB3aWxsIGNyZWF0ZSBuZXcgZmVhdHVyZXMgZm9yIHdoZXRoZXIgYSByZXNwb25kZW50IGJvdWdodCB0aGVpciBwaG9uZSBmb3IgdGhlbXNlbGYgb3Igbm90Lg0KDQpgYGB7ciBwaG9uZX0NCnRyYWluJE1UNnBob25lIDwtIGlmZWxzZSh0cmFpbiRNVDY9PTEsMSwwKQ0KdGVzdCRNVDZwaG9uZSA8LSBpZmVsc2UodGVzdCRNVDY9PTEsMSwwKQ0KYGBgDQogICAgICAgICAgICAgICAgICAgICAgICAgICANCkYuQUExNCBncm91cGluZw0KDQpXZSB3aWxsIGFsc28gZ3JvdXAgdGhlIHVua25vd24gY29udGludW91cyB2YXJpYWJsZSB2YXJpYWJsZSBBQTE0IHdpdGggYSBsYXJnZSBudW1iZXIgb2YgZnJlcXVlbmN5IHZhbHVlcy4NCg0KYGBge3IgQUExNH0NCnJhbmdlKHRyYWluJEFBMTQpDQp0cmFpbiRBQTE0XzE8LSBpZmVsc2UodHJhaW4kQUExNDwyMDAwLDEsDQogICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHRyYWluJEFBMTQ8NDAwMCwyLDMpDQogICAgICAgICAgICAgICAgICAgICAgKQ0KdGVzdCRBQTE0XzE8LSBpZmVsc2UodGVzdCRBQTE0PDIwMDAsMSwNCiAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UodHJhaW4kQUExNDw0MDAwLDIsMykNCiAgICAgICAgICAgICAgICAgICAgICApDQpgYGANCkcuIENvaGFiaXRpbmcgb3Igbm90DQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3Igd2hldGhlciBhIHJlc3BvbmRlbnQgaXMgY29oYWJpdGluZyBvciBub3QuDQoNCmBgYHtyIGNvaGFifQ0KIyBERzMuIFdoYXQgaXMgeW91ciBtYXJpdGFsIHN0YXR1cz8NCnRyYWluJERHM18xPC0gaWZlbHNlKHRyYWluJERHMz09N3x0cmFpbiRERzM9PTgsMSwwKQ0KdGVzdCRERzNfMTwtIGlmZWxzZSh0ZXN0JERHMz09N3x0cmFpbiRERzM9PTgsMSwwKQ0KYGBgDQoNCkguIEZpbmFuY2lhbGx5IGluZGVwZW5kZW50DQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3Igd2hldGhlciBhIHJlc3BvbmRlbnQgaXMgZmluYW5jaWFsbHkgaW5kZXBlbmRlbnQgb3Igbm90Lg0KDQpgYGB7ciBpbmRlcH0NCnRyYWluJEZMNF8xPC0gaWZlbHNlKHRyYWluJEZMND09MSwxLDApDQp0ZXN0JEZMNF8xPC0gaWZlbHNlKHRlc3QkRkw0PT0xLDEsMCkNCmBgYA0KDQozLjIgKipEVU1NWSBWQVJJQUJMRVMqKg0KDQpgYGB7cn0NCiNDb252ZXJ0aW5nIGV2ZXJ5IGNhdGVnb3JpY2FsIHZhcmlhYmxlIHRvIG51bWVyaWNhbCB1c2luZyBkdW1teSB2YXJpYWJsZXMNCiNkbXkgPC0gZHVtbXlWYXJzKCIgfiAuIiwgZGF0YSA9IHRyYWluLGZ1bGxSYW5rID0gVCkNCiN0cmFpbl90cmFuc2Zvcm1lZCA8LSBkYXRhLmZyYW1lKHByZWRpY3QoZG15LCBuZXdkYXRhID0gdHJhaW4pKQ0KDQojQ2hlY2tpbmcgdGhlIHN0cnVjdHVyZSBvZiB0cmFuc2Zvcm1lZCB0cmFpbiBmaWxlDQojc3RyKHRyYWluX3RyYW5zZm9ybWVkKQ0KYGBgDQoNCg0KMy4zLiAqKkRBVEEgU1BMSVRUSU5HKioNCg0KVGhlIHRyYWluIGRhdGEgc2V0IGlzIGZ1cnRoZXIgc3BsaXQgaW50byB0cmFpbmluZyAoNzAlKSBhbmQgdmFsaWRhdGlvbiAoMzAlKSBzZXRzIGZvciBjcm9zcyB2YWxpZGF0aW9uIHVzaW5nIGNhcmV0LCBhZnRlciBiZWluZyByYW5kb21pc2VkLiBUaGUgZnVuY3Rpb24gY3JlYXRlRGF0YVBhcnRpdGlvbiBjYW4gYmUgdXNlZCB0byBjcmVhdGUgc3RyYXRpZmllZCByYW5kb20gc3BsaXRzIG9mIGEgZGF0YSBzZXQuDQoNCmBgYHtyIHNwbGl0fQ0KIyBDcmVhdGUgYSBzZXQgb2YgdHJhaW5pbmcgYW5kIHZhbGlkIHNldHMgdGhhdCB3aWxsIGJlIHVzZWQgaW4gdGhlIFhHQmJvb3N0IHBhY2thZ2UsIGtlZXBpbmcgdGhlIHRhcmdldCB2YWlhYmxlIGFzIG51bWVyaWMNCmluVHJhaW4gPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5ID0gdHJhaW4kaXNfZmVtYWxlLCBwID0gMC43LCBsaXN0ID0gRikNCnRyYWluaW5nIDwtIHRyYWluW2luVHJhaW4sXQ0KdmFsaWQgPC0gdHJhaW5bLWluVHJhaW4sXQ0KDQojIFNldCB0aGUgdGFyZ2V0IHZhcmlhYmxlIHRvIGJlIGEgY2hhcmFjdGVyIGZhY3RvciB3aXRoIGxldmVscyBvbmUgYW5kIHR3byBmb3IgdXNlIGluIHRoZSB0cmFpbmluZyBpbiB0aGUgY2FyZXQgWEdCb29zdA0KdHJhaW4kaXNfZmVtYWxlIDwtIGFzLmZhY3Rvcih0cmFpbiRpc19mZW1hbGUpDQpsZXZlbHModHJhaW4kaXNfZmVtYWxlKVtsZXZlbHModHJhaW4kaXNfZmVtYWxlKT09IjAiXSA8LSAib25lIg0KbGV2ZWxzKHRyYWluJGlzX2ZlbWFsZSlbbGV2ZWxzKHRyYWluJGlzX2ZlbWFsZSk9PSIxIl0gPC0gInR3byINCg0KIyBDcmVhdGUgYSBzZXQgb2YgdHJhaW5pbmcgYW5kIHZhbGlkIHNldHMgdGhhdCB3aWxsIGJlIHVzZWQgaW4gdGhlIGNhcmV0IHBhY2thZ2UsIHdpdGggdGhlIHRhcmdldCB2YWlhYmxlIGFzIGZhY3Rvcg0KaW5UcmFpbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSB0cmFpbiRpc19mZW1hbGUsIHAgPSAwLjcsIGxpc3QgPSBGKQ0KdHJhaW5pbmcyIDwtIHRyYWluW2luVHJhaW4sXQ0KdmFsaWQyIDwtIHRyYWluWy1pblRyYWluLF0NCmBgYA0KDQozLjQuICoqREFUQSBNQVRSSVgqKg0KDQpgYGB7ciBtYXRyaXh9DQojIGlzX2ZlbWFsZSBudW1lcmljIG91dGNvbWUgeSAobGFiZWwpIG9uIHRyYWluaW5nIHNldA0KeSA9IHRyYWluaW5nJGlzX2ZlbWFsZQ0KDQojIFRvIHVzZSBhZHZhbmNlZCBmZWF0dXJlcyB4Z2Jvb3N0LCBhcyByZWNvbW1lbmRlZCwgd2UnbGwgdXNlIHhnYi5ETWF0cml4IGZ1bmN0aW9uIHRvIGNvbnZlcnQgYSBtYXRyaXggb3IgYSBkZ0NNYXRyaXggaW50byBhIHhnYi5ETWF0cml4IG9iamVjdCwgd2hpY2ggY29udGFpbnMgYSBsaXN0IHdpdGggZGdDTWF0cml4IGRhdGEgIGFuZCBudW1lcmljIGxhYmVsOiANCg0KZHRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkYXRhLm1hdHJpeCh0cmFpbmluZ1sgLC10cmFpbmluZyRpc19mZW1hbGVdKSwNCiAgICAgICAgICAgICAgICAgbGFiZWwgPSB5KQ0KZHZhbGlkIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkYXRhLm1hdHJpeCh2YWxpZFsgLC12YWxpZCRpc19mZW1hbGVdKSwgDQogICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSB2YWxpZCRpc19mZW1hbGUpDQoNCg0KZHRlc3QgPC0geGdiLkRNYXRyaXgoZGF0YS5tYXRyaXgodGVzdCkpDQojIFdlIHVzZSB3YXRjaGxpc3QgcGFyYW1ldGVyIHRvIG1lYXN1cmUgdGhlIHByb2dyZXNzIHdpdGggYSBzZWNvbmQgZGF0YXNldCB3aGljaCBpcyBhbHJlYWR5IGNsYXNzaWZpZWQuIA0Kd2F0Y2hsaXN0IDwtIGxpc3QodHJhaW49ZHRyYWluLCB0ZXN0PWR2YWxpZCkNCiMgQ2hlY2sgdGhhdCBkdGVzdCBoYXMgdGhlIHNhbWUgbnVtYmVyIG9mIHJvd3MgYXMgdGhlIG9yaWdpbmFsIHRlc3QgZmlsZSwgMjcyODUgcm93cw0KbnJvdyhkdGVzdCkNCmBgYA0KDQozLjUuICoqTU9ERUwgUEFSQU1FVEVSUyoqDQoNClRoZSBmb2xsb3dpbmcgYXJlIHBhcmFtZXRlcnMgYXZhaWxhYmxlIGZvciBYR0Jvb3N0IFIgcGFja2FnZSBiYXNlIGRvbiB0aGUgW2RvY3VtZW50YXRpb25dKGh0dHBzOi8veGdib29zdC5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvKToNCg0KKipHZW5lcmFsIHBhcmFtZXRlcnMqKiAgDQoNCi0gYm9vc3RlciAgDQpXZSB3aWxsIHJ1biBtb2RlbHMgZm9yIGJvb3N0ZXJzIGdibGluZWFyIGFuZCBnYnRyZWUuIA0KbnRocmVhZCBbZGVmYXVsdD1tYXhpbXVtIGNvcmVzIGF2YWlsYWJsZV0gc2lsZW50W2RlZmF1bHQ9MF0gdG8gbm90IHNlZSB0aGUgcnVubmluZyBtZXNzYWdlcw0KDQoqKkJvb3N0ZXIgcGFyYW1ldGVycyoqDQoNCkZvciBlYWNoIG9mIHRoZXNlIGJvb3N0ZXJzLCB0aGVyZSBhcmUgYm9vc3RlciBwYXJhbWV0ZXJzLCB0aGVzZSBhcmUgY29tbW9uIGJldHdlZW4gdGhlIHR3bzoNCg0KLSBucm91bmRzIC0gT2JzZXJ2ZSB0aGUgbnVtYmVyIGNob3NlbiBmb3IgbnJvdW5kcyBmb3IgYW55IG92ZXJmaXR0aW5nIHVzaW5nIENWLiB0aGUgbWF4IG51bWJlciBvZiBpdGVyYXRpb25zLg0KLSBhbHBoYVtkZWZhdWx0PTFdIGFuZCBsYW1iZGEgW2RlZmF1bHQ9MF0gdG8gY29udHJvbCByZWd1bGFyaXNhdGlvbg0KDQpQYXJhbWV0ZXJzIGZvciBUcmVlIEJvb3N0ZXIgYWxzbyBpbmNsdWRlOiAgDQoNCi0gZXRhW2RlZmF1bHQ9MC4zXVtyYW5nZTogKDAsMSldIGNvbnRyb2xzIHRoZSBsZWFybmluZyByYXRlLiAgDQotIG1heF9kZXB0aFtkZWZhdWx0PTZdW3JhbmdlOiAoMCxJbmYpXSBjb250cm9scyB0aGUgZGVwdGggb2YgdGhlIHRyZWUtIHR1bmVkIHVzaW5nIENWLiBIaWdoZXIgdmFsdWUgb2YgbWF4X2RlcHRoIHdpbGwgY3JlYXRlIG1vcmUgZGVlcGVyIHRyZWVzIG9yIHdlIGNhbiBzYXkgaXQgd2lsbCBjcmVhdGUgbW9yZSBjb21wbGV4IG1vZGVsLkhpZ2hlciB2YWx1ZSBvZiBtYXhfZGVwdGggbWF5IGNyZWF0ZSBvdmVyZml0dGluZyBhbmQgbG93ZXIgdmFsdWUgb2YgbWF4X2RlcHRoIG1heSBjcmVhdGUgdW5kZXJmaXR0aW5nLkFsbCBkZXBlbmRzIG9uIGRhdGEgaW4gaGFuZC4gIA0KLSBtaW5fY2hpbGRfd2VpZ2h0W2RlZmF1bHQ9MV1bcmFuZ2U6KDAsSW5mKV0gSW4gc2ltcGxlIHdvcmRzLCBpdCBibG9ja3MgdGhlIHBvdGVudGlhbCBmZWF0dXJlIGludGVyYWN0aW9ucyB0byBwcmV2ZW50IG92ZXJmaXR0aW5nLiBTaG91bGQgYmUgdHVuZWQgdXNpbmcgQ1YuIEl0IGlzIGxpa2UgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBhIHRlcm1pbmFsIG5vZGUuSWYgdGhlIHRyZWUgcGFydGl0aW9uIHN0ZXAgcmVzdWx0cyBpbiBhIGxlYWYgbm9kZSB3aXRoIHRoZSBzdW0gb2YgaW5zdGFuY2Ugd2VpZ2h0IGxlc3MgdGhhbiBtaW5fY2hpbGRfd2VpZ2h0LCB0aGVuIHRoZSBidWlsZGluZyBwcm9jZXNzIHdpbGwgZ2l2ZSB1cCBmdXJ0aGVyIHBhcnRpdGlvbmluZy4gSW4gbGluZWFyIHJlZ3Jlc3Npb24gbW9kZSwgdGhpcyBzaW1wbHkgY29ycmVzcG9uZHMgdG8gbWluaW11bSBudW1iZXIgb2YgaW5zdGFuY2VzIG5lZWRlZCB0byBiZSBpbiBlYWNoIG5vZGUuICANCi0gc3Vic2FtcGxlW2RlZmF1bHQ9MV1bcmFuZ2U6ICgwLDEpXSBjb250cm9scyB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgKG9ic2VydmF0aW9ucykgc3VwcGxpZWQgdG8gYSB0cmVlLiAgDQotIGNvbHNhbXBsZV9ieXRyZWVbZGVmYXVsdD0xXVtyYW5nZTogKDAsMSldY29udHJvbCB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzICh2YXJpYWJsZXMpIHN1cHBsaWVkIHRvIGEgdHJlZS4gUmFuZG9tbHkgY2hvb3NpbmcgdGhlIG51bWJlciBvZiBjb2x1bW5zIG91dCBvZiBhbGwgY29sdW1ucyBvciB2YXJpYWJsZXMgYXQgYSB0aW1lIHdoaWxlIHRyZWUgYnVpbGRpbmcgcHJvY2Vzcy5Zb3UgY2FuIHRoaW5rIG9mIG10cnkgcGFyYW1ldGVyIGluIHJhbmRvbSBmb3Jlc3QgdG8gYmVnaW4gdW5kZXJzdGFuZGluZyBtb3JlIGFib3V0IHRoaXMuSGlnaGVyIHZhbHVlIG1heSBjcmVhdGUgb3ZlcmZpdHRpbmcgYW5kIGxvd2VyIHZhbHVlIG1heSBjcmVhdGUgdW5kZXJmaXR0aW5nLk9uZSBuZWVkcyB0byBwbGF5IHdpdGggdGhpcyB2YWx1ZS4gIA0KLSBnYW1tYSAoTWluaW11bSBMb3NzIFJlZHVjdGlvbikgT25lIGNhbiBwbGF5IHdpdGggdGhpcyBwYXJhbWV0ZXIgYWxzbyBidXQgbW9zdGx5IG90aGVyIHBhcmFtZXRlcnMgYXJlIHVzZWQgZm9yIG1vZGVsIHR1bmluZy4gIA0KDQoqKkxlYXJuaW5nIFRhc2sgUGFyYW1ldGVycyoqDQoNClRoZXNlIHBhcmFtZXRlcnMgc3BlY2lmeSBtZXRob2RzIGZvciB0aGUgbG9zcyBmdW5jdGlvbiBhbmQgbW9kZWwgZXZhbHVhdGlvbi4gSW4gYWRkaXRpb24gdG8gdGhlIHBhcmFtZXRlcnMgbGlzdGVkIGJlbG93LCB5b3UgYXJlIGZyZWUgdG8gdXNlIGEgY3VzdG9taXplZCBvYmplY3RpdmUgLyBldmFsdWF0aW9uIGZ1bmN0aW9uLg0KDQpPYmplY3RpdmVbZGVmYXVsdD0iYmluYXJ5OmxvZ2lzdGljIl0NCg0KZXZhbF9tZXRyaWMgW25vIGRlZmF1bHQsIGRlcGVuZHMgb24gb2JqZWN0aXZlIHNlbGVjdGVkXQ0KVGhlc2UgbWV0cmljcyBhcmUgdXNlZCB0byBldmFsdWF0ZSBhIG1vZGVsJ3MgYWNjdXJhY3kgb24gdmFsaWRhdGlvbiBkYXRhLiBGb3IgcmVncmVzc2lvbiwgZGVmYXVsdCBtZXRyaWMgaXMgUk1TRS4NCg0KT25lIG9mIHRoZSBzaW1wbGVzdCB3YXkgdG8gc2VlIHRoZSB0cmFpbmluZyBwcm9ncmVzcyBpbiBYR0Jvb3N0IGlzIHRvIHNldCB0aGUgdmVyYm9zZSBvcHRpb24gdG8gIyB2ZXJib3NlID0gMCwgbm8gbWVzc2FnZSBidXQgdXNlIHByaW50LmV2ZXJ5Lm4sdmVyYm9zZSA9IDEsIHByaW50IGV2YWx1YXRpb24gbWV0cmljLCB2ZXJib3NlID0gMiwgYWxzbyBwcmludCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgdHJlZS4NCg0KMy42LiAqKkNST1NTIFZBTElEQVRJT04qKg0KDQpVc2luZyB0aGUgeGdiLmN2IGZ1bmN0aW9uIGZvciA1LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiwgdGhpcyBmdW5jdGlvbiByZXR1cm5zIENWIGVycm9yLCB3aGljaCBpcyBhbiBlc3RpbWF0ZSBvZiB0ZXN0IG9yIG91dCBvZiBzYW1wbGUgIGVycm9yLiANCg0KYGBge3IgeGdiY3Z9DQojIHNldCByYW5kb20gc2VlZCwgZm9yIHJlcHJvZHVjaWJpbGl0eSANCnNldC5zZWVkKDEyMzQpDQojIFVzaW5nIGJvb3N0ZXIgZ2J0cmVlLCB3aXRoIGEgbGFyZ2UgbnJvdW5kPTUwIA0KeGdiY3YgPC0geGdiLmN2KHBhcmFtcyA9IGxpc3QoDQogICAgICAjIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgDQogICAgICBvYmplY3RpdmUgPSAnYmluYXJ5OmxvZ2lzdGljJyksDQogICAgICBtZXRyaWNzID0gbGlzdCgicm1zZSIsImF1YyIpLA0KICAgICAgbGFiZWw9eSwNCiAgICAgIGRhdGEgPSBkdHJhaW4sDQogICAgICBucm91bmRzID0gNTAsDQogICAgICBuZm9sZCA9IDUsICMgNSBmb2xkIENWDQogICAgICBzaG93c2QgPSBULCANCiAgICAgIHByaW50X2V2ZXJ5X24gPSAxMCwgIyB3aGVuIHZlcmJvc2UgPTANCiAgICAgICMgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gNSwgDQogICAgICB2ZXJib3NlPTEsDQogICAgICBwcmVkaWN0aW9uID0gVCkNCiANCiMgIG5yb3VuZCBiZXN0IGl0ZXJhdGlvbiBpcywgYmFzZWQgb24gdGhlIG1pbiB0ZXN0IHJtc2U6DQppdCA8LSAgd2hpY2gubWluKHhnYmN2JGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQ0KYmVzdGl0ZXJhdGlvbiA8LSAgeGdiY3YkZXZhbHVhdGlvbl9sb2ckaXRlcltpdF0NCmJlc3RpdGVyYXRpb24NCmBgYA0KDQoNClBsb3QgUk1TRSBmb3IgdGhlIGR0cmFpbiBjcm9zcyB2YWxpZGF0aW9uLg0KDQpgYGB7ciBybXNlfQ0KIyBQbG90IHRoZSBSTVNFIGZyb20gdGhlIENWDQp4Z2JjdiRldmFsdWF0aW9uX2xvZyAlPiUNCiAgIGRwbHlyOjpzZWxlY3QoaXRlcix0cmFpbl9ybXNlX21lYW4sdGVzdF9ybXNlX21lYW4pICU+JQ0KICBnYXRoZXIoVGVzdE9yVHJhaW4sIFJNU0UsIC1pdGVyKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gaXRlciwgeSA9IFJNU0UsIGdyb3VwID0gVGVzdE9yVHJhaW4sIGNvbG9yID0gVGVzdE9yVHJhaW4pKSArIA0KICBnZW9tX2xpbmUoKSArIA0KICB0aGVtZV9idygpDQpgYGANCg0KDQpgYGB7ciBjdmF1Y30NCiMgQ2FsY3VsYXRlIEFVQyBmb3IgdGhlIHhnYmN2DQp4Z2Jjdi5ST0MgPC0gcm9jKHJlc3BvbnNlID0geSwNCiAgICAgICAgICAgICAgIHByZWRpY3RvciA9IHhnYmN2JHByZWQpDQojIEFyZWEgdW5kZXIgdGhlIGN1cnZlOiAxPw0KeGdiY3YuUk9DJGF1Yw0KYGBgDQoNClRoaXMgbW9kZWwgYXBwZWFycyB0byBzZXQgdGhlIEFVQyB0byAxLCB3ZSB3aWxsIGxvb2sgdG8gdXNlIGNhcmV0IGZvciB0aGUgbW9kZWwgdHJhaW5pbmcuDQoNCjMuNy4gKipNT0RFTCBUUkFJTklORyoqDQoNCk9uZSB3YXkgdG8gbWVhc3VyZSBwcm9ncmVzcyBpbiBsZWFybmluZyBvZiBhIG1vZGVsIGlzIHVzaW5nIHhnYi50cmFpbiwgcHJvdmlkaW5nIGEgc2Vjb25kIGRhdGFzZXQgYWxyZWFkeSBjbGFzc2lmaWVkLiBUaGVyZWZvcmUgaXQgY2FuIGxlYXJuIG9uIHRoZSBmaXJzdCBkYXRhc2V0IGFuZCB0ZXN0IGl0cyBtb2RlbCBvbiB0aGUgc2Vjb25kIG9uZS4gTWV0cmljcyBhcmUgbWVhc3VyZWQgYWZ0ZXIgZWFjaCByb3VuZCBkdXJpbmcgdGhlIGxlYXJuaW5nLiBIb3dldmVyIHdlIGNhbiBhbHNvIFt1c2UgY2FyZXQgdG8gdHJhaW4gdXNpbmcgeGdiVHJlZSB0byBjb21wYXJlIG1vZGVscy5dKGJsb2cucmV2b2x1dGlvbmFuYWx5dGljcy5jb20vMjAxNi8wNS91c2luZy1jYXJldC10by1jb21wYXJlLW1vZGVscy5odG1sKQ0KDQpJbiB0aGlzIG1vZGVsIHRyYWluaW5nIHdlIHdpbGwgdHVuZSB0aGUgaHlwZXIgcGFyYW1ldGVycywgYWx0aG91Z2ggdGhpcyBpcyBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlIHdheSB0byB0cmFpbiBhIG1vZGVsLiANCg0KV2Ugd2lsbCB1c2UgdGhlIGZvbGxvd2luZyBmdW5jdGlvbnM6DQoNCi0gZXhwYW5kLmdyaWQgZnVuY3Rpb24gdG8gbWFrZSBhIGRhdGEuZnJhbWUgd2l0aCBldmVyeSBjb21iaW5hdGlvbiBvZiBoeXBlcnBhcmFtZXRlcnMgDQotIGNhcmV0Ojp0cmFpbkNvbnRyb2wgdG8gc3BlY2lmeSB0aGUgdHlwZSBvZiBjcm9zcyB2YWxpZGF0aW9uIChoZXJlIHVzZSA1LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbikNCi0gY2FyZXQ6OnRyYWluIHRvIHNlYXJjaCBvdmVyIHRoZSBncmlkIG9mIGh5cGVycGFyYW1ldGVyIGNvbWJpbmF0aW9ucyB0byBmaW5kIHRoZSBtb2RlbCB0aGF0IG1heGltaXNlcyBST0MNCg0KYGBge3IgdHVuaW5nLCBtZXNzYWdlPUZBTFNFfQ0KIyBTZXQgcmFuZG9tIHNlZWQsIGZvciByZXByb2R1Y2liaWxpdHkgDQpzZXQuc2VlZCgxMjM0KQ0KIyBTZXQgZXhwYW5kLmdyaWQNCnhnYi5ncmlkIDwtIGV4cGFuZC5ncmlkKG5yb3VuZHMgPSBjKGJlc3RpdGVyYXRpb24sIDEwMCksICMgU2V0IHRoZSBudW1iZXIgb2YgaXRlcmF0aW9ucyBmcm9tIHRoZSB4Z2IuY3YgYW5kIHByb3ZpZGluZyBhbm90aGVyIGhpZ2hlciB2YWx1ZSBmb3IgY29tcGFyaXNvbg0KICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gc2VxKGZyb209MC4yLCB0bz0xLCBieT0wLjIpLCAjIHNocmlua2FnZQ0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoID0gYyg2LDgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC4yLCAwLjgpLCAjIHZhcmlhYmxlcyBwZXIgdHJlZS4gZGVmYXVsdCAxDQogICAgICAgICAgICAgICAgICAgICAgICBnYW1tYSA9IGMoMCwxKSwgIyBkZWZhdWx0IDANCiAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsMTApLA0KICAgICAgICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlPTEpICMgZGVmYXVsdCAxDQoNCg0KIyBTZXQgdHJhaW5pbmcgY29udHJvbA0KY250cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgICAjIDUgZm9sZCBjcm9zcyB2YWxpZGF0aW9uDQogICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSAyLAkJIyBkbyAyIHJlcGV0aXRpb25zIG9mIGN2DQogICAgICAgICAgICAgICAgICAgICByZXBlYXRzID0gMiwgIyByZXBlYXRlZCA1IHRpbWVzDQogICAgICAgICAgICAgICAgICAgICBzdW1tYXJ5RnVuY3Rpb249dHdvQ2xhc3NTdW1tYXJ5LAkjIGJ1aWx0LWluIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgYXJlYSB1bmRlciB0aGUgUk9DIGN1cnZlLCB0byBjb21wYXJlIG1vZGVscw0KICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICBhbGxvd1BhcmFsbGVsID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2VJdGVyID0gRkFMU0UpDQoNCiMgVHJhaW4gbW9kZWwgd2l0aCBnYnRyZWUgYW5kIHBhcmFtcyBhYm92ZSB1c2luZyBjYXJldCANCnR1bmluZ21vZGVsIDwtIHRyYWluKHg9dHJhaW5pbmcyWywtOV0sDQogICAgICAgICAgICAgeT10cmFpbmluZzIkaXNfZmVtYWxlLCAjIHRhcmdldCB2ZWN0b3Igc2hvdWxkIGJlIG5vbi1udW1lcmljIGZhY3RvcnMgdG8gaWRlbnRpZnkgb3VyIHRhc2sgYXMgY2xhc3NpZmljYXRpb24sIG5vdCByZWdyZXNzaW9uLg0KICAgICAgICAgICAgIG1ldGhvZD0ieGdiVHJlZSIsDQogICAgICAgICAgICAgbWV0cmljPSJST0MiLA0KICAgICAgICAgICAgIHRyQ29udHJvbD1jbnRybCwjIHNwZWNpZnkgY3Jvc3MgdmFsaWRhdGlvbiANCiAgICAgICAgICAgICB0dW5lR3JpZD14Z2IuZ3JpZCwgIyBXaGljaCBoeXBlcnBhcmFtZXRlcnMgd2UnbGwgdGVzdA0KICAgICAgICAgICAgIG1heGltaXplID0gVFJVRSkgDQoNCiMgVmlldyB0aGUgbW9kZWwgcmVzdWx0cw0KdHVuaW5nbW9kZWwkYmVzdFR1bmUNCg0KIyBQbG90IHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgdHJhaW5pbmcgbW9kZWxzDQojIHNjYXR0ZXIgcGxvdCBvZiB0aGUgQVVDIGFnYWluc3QgbWF4X2RlcHRoIGFuZCBldGENCmdncGxvdCh0dW5pbmdtb2RlbCRyZXN1bHRzLCBhZXMoeCA9IGFzLmZhY3RvcihldGEpLCB5ID0gbWF4X2RlcHRoLCBzaXplID0gUk9DLCBjb2xvciA9IFJPQykpICsgDQogICAgICBnZW9tX3BvaW50KCkgKyANCiAgICAgIGdndGl0bGUoIlNjYXR0ZXIgcGxvdCBvZiB0aGUgQVVDIGFnYWluc3QgbWF4X2RlcHRoIGFuZCBldGEiKSArDQogICAgICB0aGVtZV9idygpICsgDQogICAgICBzY2FsZV9zaXplX2NvbnRpbnVvdXMoZ3VpZGUgPSAibm9uZSIpICsNCiAgICAgIHNjYWxlX2NvbG91cl9ncmFkaWVudChsb3cgPSAiYmxhY2siLCBoaWdoPSJ5ZWxsb3ciKQ0KIyBQbG90IHRoZSByZXN1bHRzIG9mIHRoZSBncmlkIGNvbWJpbmF0aW9ucyAgDQpwbG90KHR1bmluZ21vZGVsKQ0KYGBgDQpCYXNlZCBvbiB0aGUgdHVuaW5nIHdlIHdpbGwgbm93IHRyYWluIG91ciAiYmVzdCBtb2RlbCIsIGJzdCB3aXRoIHNlbGVjdGVkIHBhcmFtZXRlcnMuDQogIA0KYGBge3IgYmVzdG1vZGVsfSANCiMgU2V0IGV4cGFuZC5ncmlkDQp4Z2IuZ3JpZCA8LSBleHBhbmQuZ3JpZChucm91bmRzID0gMTAwLCAjIFNldCB0aGUgbWF4aW11bSBudW1iZXIgb2YgaXRlcmF0aW9ucyBmcm9tIHRoZSBncmlkIHNlYXJjaA0KICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4yLCAjIHNocmlua2FnZQ0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoID0gOCwNCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbHNhbXBsZV9ieXRyZWUgPSAwLjgsICMgdmFyaWFibGVzIHBlciB0cmVlLiBkZWZhdWx0IDENCiAgICAgICAgICAgICAgICAgICAgICAgIGdhbW1hID0gMCwgIyBkZWZhdWx0IDANCiAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9jaGlsZF93ZWlnaHQgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlPTEpICMgZGVmYXVsdCAxDQoNCmJzdCA8LSB0cmFpbih4PXRyYWluaW5nMlssLTldLA0KICAgICAgICAgICAgIHk9dHJhaW5pbmcyJGlzX2ZlbWFsZSwgIyBUYXJnZXQgdmVjdG9yIHNob3VsZCBiZSBub24tbnVtZXJpYyBmYWN0b3JzIHRvIGlkZW50aWZ5IG91ciB0YXNrIGFzIGNsYXNzaWZpY2F0aW9uLCBub3QgcmVncmVzc2lvbi4NCiAgICAgICAgICAgICBtZXRob2Q9InhnYlRyZWUiLA0KICAgICAgICAgICAgIG1ldHJpYz0iUk9DIiwNCiAgICAgICAgICAgICB0ckNvbnRyb2w9Y250cmwsIyBTcGVjaWZ5IGNyb3NzIHZhbGlkYXRpb24gDQogICAgICAgICAgICAgdHVuZUdyaWQ9eGdiLmdyaWQsDQogICAgICAgICAgICAgbWF4aW1pemUgPSBUUlVFKSAjIFdoaWNoIGh5cGVycGFyYW1ldGVycyB3ZSdsbCB0ZXN0DQoNCiMgVmlldyB0aGUgbW9kZWwgcmVzdWx0cw0KYnN0JGJlc3RUdW5lDQoNCiMjIyB4Z2Jvb3N0TW9kZWwgUHJlZGljdGlvbnMgYW5kIFBlcmZvcm1hbmNlDQojIE1ha2UgcHJlZGljdGlvbnMgdXNpbmcgdGhlIHRlc3QgZGF0YSBzZXQNCnhnYi5wcmVkIDwtIGlmZWxzZShwcmVkaWN0KGJzdCx2YWxpZCwgdHlwZSA9ICJyYXciKT09ICJ0d28iLCAxLCAwKQ0KIA0KI0xvb2sgYXQgdGhlIGNvbmZ1c2lvbiBtYXRyaXggIA0KY29uZnVzaW9uTWF0cml4KHhnYi5wcmVkLHZhbGlkJGlzX2ZlbWFsZSkgICANCiANCiNEcmF3IHRoZSBST0MgY3VydmUgdXNpbmcgdGhlIHBSb2MgcGFja2FnZQ0KeGdiLnByb2JzIDwtIHByZWRpY3QoYnN0LHZhbGlkLHR5cGU9InByb2IiKQ0KeGdiLlJPQyA8LSByb2MocHJlZGljdG9yPXhnYi5wcm9icyRvbmUsDQogICAgICAgICAgICAgICByZXNwb25zZT12YWxpZCRpc19mZW1hbGUpDQp4Z2IuUk9DJGF1Yw0KIyBBcmVhIHVuZGVyIHRoZSBjdXJ2ZQ0KcGxvdCh4Z2IuUk9DLG1haW49InhnYm9vc3QgUk9DIikNCmBgYA0KDQpQbG90IHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlIG9mIHRoaXMgYmVzdCBtb2RlbCwgYnN0Lg0KDQpgYGB7ciB2YXJpbXB9DQojIFZhcmlhYmxlIEltcG9ydGFuY2UNCnZhcmltcHhnYiA8LSB2YXJJbXAoYnN0KSRpbXBvcnRhbmNlICU+JSANCiAgbXV0YXRlKENvbHVtbi5OYW1lPXJvdy5uYW1lcyguKSkgJT4lDQogIGFycmFuZ2UoLU92ZXJhbGwpDQpkZHZhcmltcCA8LSBtZXJnZShkZDIsdmFyaW1weGdiLGJ5ID0gIkNvbHVtbi5OYW1lIikgIA0KZGR2YXJpbXBbMToxMCxdDQpwb3NpdGlvbnMgPC0gdmFyaW1weGdiWzE6MTAsXSRDb2x1bW4uTmFtZQ0KZ2dwbG90KHZhcmltcHhnYlsxOjEwLF0pICsgZ2VvbV9iYXIoYWVzKENvbHVtbi5OYW1lLE92ZXJhbGwpLHN0YXQ9ImlkZW50aXR5IikgKyBjb29yZF9mbGlwKCkgKyBzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cyA9IHBvc2l0aW9ucykNCmBgYA0KDQpPbmUgb2Ygb3VyIGVuZ2luZWVyZWQgZmVhdHVyZXMsIERMMWhvdXNlaG9sZCwgY29udHJpYnV0ZXMgc2Vjb25kIGluIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlLg0KDQojIyBQYXJ0IDQ6IFByZWRpY3Rpb25zDQoNCk1ha2UgYSBwcmVkaWN0aW9uIG9uIHRoZSB0ZXN0IHNldCBhbmQgY3JlYXRlIHN1Ym1pc3Npb24gZmlsZSB0byBiZSBsb2FkZWQgdG8gS2FnZ2xlLiBPbmNlIGxvYWRlZCBLYWdnbGUgd2lsbCBwcm92aWRlIGEgc2NvcmUgYXMgQVVDIG9uIHRoZSBfaXNfZmVtYWxlXyBwcmVkaWN0aW9ucy4NCg0KYGBge3IgcHJlZGljdGlvbnMgdGVzdH0NCiMgQ3JlYXRlIGEgcHJlZGljdGlvbiBmaWxlIA0KcHJlZGljdHRlc3QgPC0gaWZlbHNlKHByZWRpY3QoYnN0LHRlc3QsIHR5cGUgPSAicmF3Iik9PSAidHdvIiwgMSwgMCkNCmBgYA0KICANCg0KYGBge3Igc3ViZmlsZX0NCiMgQ3JlYXRlIEthZ2dsZSBTdWJtaXNzaW9uIEZpbGUgRmVtYWxlIGlzIDIsIG1hbGUgaXMgMSwgd2hpbGUgaW4gb3VyIHRyYW5zZm9ybWVkIGRhdGEsIGlzX2ZlbWFsZT0xIGZvciBmZW1hbGUgYW5kIGlzX2ZlbWFsZT0wIGZvciBtYWxlLg0KbXlfc29sdXRpb24gPC0gZGF0YS5mcmFtZShpZCxwcmVkaWN0dGVzdCkNCm5hbWVzKG15X3NvbHV0aW9uKSA8LSBjKCJ0ZXN0X2lkIiwiaXNfZmVtYWxlIikNCiMgQ2hlY2sgdGhlIG51bWJlciBvZiByb3dzIGluIHRoZSBzb2x1dGlvbiBmaWxlIGlzIDI3Mjg1DQpucm93KG15X3NvbHV0aW9uKQ0KIyBXcml0ZSBzb2x1dGlvbiB0byBmaWxlIHN1Ym1pc3Npb25GaWxlMS5jc3YNCndyaXRlLmNzdihteV9zb2x1dGlvbiwgZmlsZSA9ICJzdWJtaXNzaW9uRmlsZUMuY3N2IiwgcXVvdGU9Riwgcm93Lm5hbWVzPUYpDQpgYGANCg0KDQo=