Background

In this Kaggle competition, Social Impact for Women in Impoverished Countries WIDS_DataThon_2018 competition, the aim is to predict the gender of each survey respondent based on demographic and behavioural information from a representative sample of survey respondents from India and their usage of traditional and mobile financial services.

This notebook includes model training to create the predictions file for submission to Kaggle.

Submissions are evaluated on area under the ROC curve between the predicted probability and the observed target variable is_female.


Setup

Load packages

library(data.table)
library(knitr)
library(tidyverse)
library(stringr)
library(readxl)
library(caret)
library(xgboost)
library(pROC)
sessionInfo()
R version 3.4.2 (2017-09-28)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows >= 8 x64 (build 9200)

Matrix products: default

locale:
[1] LC_COLLATE=English_New Zealand.1252  LC_CTYPE=English_New Zealand.1252   
[3] LC_MONETARY=English_New Zealand.1252 LC_NUMERIC=C                        
[5] LC_TIME=English_New Zealand.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] pROC_1.10.0         xgboost_0.6.4.1     caret_6.0-78        lattice_0.20-35    
 [5] readxl_1.0.0        forcats_0.3.0       stringr_1.2.0       dplyr_0.7.4        
 [9] purrr_0.2.4         readr_1.1.1         tidyr_0.8.0         tibble_1.4.2       
[13] ggplot2_2.2.1.9000  tidyverse_1.2.1     knitr_1.19          data.table_1.10.4-3

loaded via a namespace (and not attached):
 [1] httr_1.3.1         ddalpha_1.3.1.1    sfsmisc_1.1-1      jsonlite_1.5      
 [5] splines_3.4.2      foreach_1.4.4      prodlim_1.6.1      modelr_0.1.1      
 [9] assertthat_0.2.0   stats4_3.4.2       DRR_0.0.3          cellranger_1.1.0  
[13] robustbase_0.92-8  ipred_0.9-6        pillar_1.1.0       glue_1.2.0        
[17] rvest_0.3.2        colorspace_1.3-2   recipes_0.1.2      Matrix_1.2-11     
[21] plyr_1.8.4         psych_1.7.8        timeDate_3042.101  pkgconfig_2.0.1   
[25] CVST_0.2-1         broom_0.4.3        haven_1.1.1        scales_0.5.0.9000 
[29] gower_0.1.2        lava_1.6           withr_2.1.1        nnet_7.3-12       
[33] lazyeval_0.2.1     cli_1.0.0          mnormt_1.5-5       survival_2.41-3   
[37] magrittr_1.5       crayon_1.3.4       nlme_3.1-131       MASS_7.3-47       
[41] xml2_1.2.0         dimRed_0.1.0       foreign_0.8-69     class_7.3-14      
[45] tools_3.4.2        hms_0.4.1          kernlab_0.9-25     munsell_0.4.3     
[49] bindrcpp_0.2       compiler_3.4.2     RcppRoll_0.2.2     rlang_0.1.6       
[53] grid_3.4.2         iterators_1.0.9    rstudioapi_0.7     gtable_0.2.0      
[57] ModelMetrics_1.1.0 codetools_0.2-15   reshape2_1.4.3     R6_2.2.2          
[61] lubridate_1.7.2    bindr_0.1          stringi_1.1.6      parallel_3.4.2    
[65] Rcpp_0.12.15       rpart_4.1-11       DEoptimR_1.0-8     tidyselect_0.2.3  

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()
summary(train$AA14)
   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:

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.

range(train$AA14)
[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 parameters

For each of these boosters, there are booster parameters, these are common between the two:

Parameters for Tree Booster also include:

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:

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

Part 4: Predictions

Make a prediction on the test set and create submission file to be loaded to Kaggle. Once loaded Kaggle will provide a score as AUC on the is_female predictions.

# Create a prediction file 
predicttest <- ifelse(predict(bst,test, type = "raw")== "two", 1, 0)
# Create Kaggle Submission File Female is 2, male is 1, while in our transformed data, is_female=1 for female and is_female=0 for male.
my_solution <- data.frame(id,predicttest)
names(my_solution) <- c("test_id","is_female")
# Check the number of rows in the solution file is 27285
nrow(my_solution)
[1] 27285
# Write solution to file submissionFile1.csv
write.csv(my_solution, file = "submissionFileC.csv", quote=F, row.names=F)
LS0tDQp0aXRsZTogIldJRFMgRGF0YVRob24gMjAxOCINCmF1dGhvcjogImtpbW5ld3plYWxhbmQiDQpkYXRlOiAiMjcgRmVicnVhcnkgMjAxOCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBmaWdfaGVpZ2h0OiA0DQogICAgaGlnaGxpZ2h0OiBweWdtZW50cw0KICAgIHRoZW1lOiBzcGFjZWxhYg0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQojIyBCYWNrZ3JvdW5kDQoNCkluIHRoaXMgS2FnZ2xlIGNvbXBldGl0aW9uLCBbIFNvY2lhbCBJbXBhY3QgZm9yIFdvbWVuIGluIEltcG92ZXJpc2hlZCBDb3VudHJpZXMgV0lEU19EYXRhVGhvbl8yMDE4IGNvbXBldGl0aW9uXShodHRwOi8vd3d3LndpZHNjb25mZXJlbmNlLm9yZy9kYXRhdGhvbi1kZXRhaWxzLmh0bWwpLCB0aGUgYWltIGlzIHRvIHByZWRpY3QgdGhlIGdlbmRlciBvZiBlYWNoIHN1cnZleSByZXNwb25kZW50IGJhc2VkIG9uIGRlbW9ncmFwaGljIGFuZCBiZWhhdmlvdXJhbCBpbmZvcm1hdGlvbiBmcm9tIGEgcmVwcmVzZW50YXRpdmUgc2FtcGxlIG9mIHN1cnZleSByZXNwb25kZW50cyBmcm9tIEluZGlhIGFuZCB0aGVpciB1c2FnZSBvZiB0cmFkaXRpb25hbCBhbmQgbW9iaWxlIGZpbmFuY2lhbCBzZXJ2aWNlcy4NCg0KVGhpcyBub3RlYm9vayBpbmNsdWRlcyBtb2RlbCB0cmFpbmluZyB0byBjcmVhdGUgdGhlIHByZWRpY3Rpb25zIGZpbGUgZm9yIHN1Ym1pc3Npb24gdG8gS2FnZ2xlLg0KDQpTdWJtaXNzaW9ucyBhcmUgZXZhbHVhdGVkIG9uIGFyZWEgdW5kZXIgdGhlIFJPQyBjdXJ2ZSBiZXR3ZWVuIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgYW5kIHRoZSBvYnNlcnZlZCB0YXJnZXQgdmFyaWFibGUgaXNfZmVtYWxlLg0KDQoqICogKg0KDQojIyMgU2V0dXANCg0KIyMgTG9hZCBwYWNrYWdlcw0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQpgYGB7ciBsb2FkIHBhY2thZ2VzLCBlcnJvcj1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoeGdib29zdCkNCmxpYnJhcnkocFJPQykNCmBgYA0KDQpgYGB7ciBzZXNzaW9uaW5mb30NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQojIyBMb2FkIERhdGENCg0KKipMb2FkIGRhdGFzZXRzKioNCg0KVGhlIGZpcnN0IHN0ZXAgd2lsbCBiZSB0byBsb2FkIHRoZSB0ZXN0IGRhdGEgZmlsZSBhbmQgdHJhaW4gZGF0YSBmaWxlLiBUaGVzZSBoYXZlIGJlZW4gZG93bmxvYWRlZCB0byBhIGxvY2FsIEthZ2dsZSBmb2xkZXIgb2ZmbGluZSBhcyB0aGVyZSBpcyBhbiBhZ3JlZW1lbnQgc3RlcCB0byB0aGUgS2FnZ2xlIGRhdGEgdGVybXMgYW5kIGNvbmRpdGlvbnMgYW5kIHVuemlwcGVkIHVzaW5nIDctemlwLg0KDQpXZSB3aWxsIHVzZSB0aGUgZGF0YS50YWJsZSBSIHBhY2thZ2UgZGVzaWduZWQgZm9yIGxhcmdlIGRhdGFzZXRzLg0KDQpgYGB7ciBsb2FkZGF0YSwgZXJyb3I9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNvbW1lbnQ9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp1cmwgPC0gImh0dHBzOi8vd3d3LmthZ2dsZS5jb20vYy93aWRzMjAxOGRhdGF0aG9uL2RhdGEiDQpzZXR3ZCgifi9LYWdnbGUvV2lEUyIpDQp0cmFpbiA8LSBmcmVhZCgiLi90cmFpbi5jc3YiKQ0KdGVzdCA8LSBmcmVhZCgiLi90ZXN0LmNzdiIpDQpkZDIgPC0gZGF0YS5mcmFtZShyZWFkX2V4Y2VsKCIuL1dpRFNERDIueGxzeCIpKQ0KYGBgDQoNCioqT3ZlcnZpZXcqKg0KDQpUaGUgdHJhaW4gZGF0YXNldCBoYXMgYHIgZGltKHRyYWluKVsxXWAgcm93cyBhbmQgYHIgZGltKHRyYWluKVsyXWAgY29sdW1ucy4NCg0KX1RoZSBmb2xsb3dpbmcgaW5mb3JtYXRpb24gaXMgYXZhaWxhYmxlIGZyb20gdGhlIEthZ2dsZSBkaXNjdXNzaW9uIGZvcnVtOiBfIA0KDQo+IGlzX2ZlbWFsZSAtIHRoZSBpc19mZW1hbGUgdmFyaWFibGUgeW91IGFyZSBnb2luZyB0byBwcmVkaWN0LiBOb3RlOiBpbiB0aGUgZGF0YSBkaWN0aW9uYXJ5LCBGZW1hbGUgaXMgMiwgbWFsZSBpcyAxLCB3aGlsZSBpbiBvdXIgdHJhbnNmb3JtZWQgZGF0YSwgaXNfZmVtYWxlPTEgZm9yIGZlbWFsZSBhbmQgaXNfZmVtYWxlPTAgZm9yIG1hbGUuDQpGb3IgdGhlIHJlc3Qgb2YgdGhlIDEwMDArIGNvbHVtbiBkZXNjcmlwdGlvbnMsIHBsZWFzZSByZWZlciB0byBXaURTRGF0YURpY3Rpb25hcnkgZm9yIGRldGFpbHMuIFBsZWFzZSBub3RlIHRoYXQgZGF0YSBoYXMgYmVlbiBwcm9jZXNzZWQgYW5kIHNvbWUgY29sdW1ucyB3ZXJlIHJlbW92ZWQgdG8gcHJldmVudCBkYXRhIGxlYWthZ2Ugb3IgdG8gcHJvdGVjdCBwcml2YWN5Lg0KDQpGcm9tIHJldmlld2luZyB0aGUgZGF0YSBkaWN0aW9uYXJ5LCB0aGUgdmFyaWFibGVzIGFyZSBhbGwgbnVtZXJpYyBvciBpbnRlZ2Vycy4gVGhlcmUgYXJlIGFsc28gYSBsYXJnZSBudW1iZXIgb2YgdmFyaWFibGVzLCB0aGVyZWZvcmUgd2Ugd2lsbCBpbml0aWFsbHkgbG9vayBmb3Igd2F5cyB0byBwZXJmb3JtIGRpbWVuc2lvbiByZWR1Y3Rpb24uIA0KDQpGcm9tIHJldmlld2luZyB0aGUgZGF0YSBkaWN0aW9uYXJ5LCB0aGUgdmFyaWFibGVzIGFyZSBhbGwgbnVtZXJpYyBvciBpbnRlZ2Vycy4gVGhlcmUgYXJlIGFsc28gYSBsYXJnZSBgciBkaW0odHJhaW4pWzJdYCB2YXJpYWJsZXMsIHRoZXJlZm9yZSB3ZSB3aWxsIGluaXRpYWxseSBsb29rIGZvciB3YXlzIHRvIHBlcmZvcm0gZGltZW5zaW9uIHJlZHVjdGlvbi4gTm90ZSB0aGVyZSBhcmUgbGVzcyB2YXJpYWJsZXMgaW4gdGhlIGRhdGEgZGljdGlvbmFyeSBgciBkaW0oZGQyKVsyXWAgc28gaXQgYXBwZWFycyB3ZSBhcmUgbWlzc2luZyBzb21lIHZhcmlhYmxlIGRlc2NyaXB0aW9ucy4gIEl0IGFwcGVhcnMgdGhhdCB0aGUgQUIgcXVlc3Rpb25zIGFyZSBhbHNvIG1pc3NpbmcgZnJvbSBvdXIgZGF0YXNldHMuDQoNCkNoZWNrIGlmIGFueSB2YXJpYWJsZXMgYXJlIGNoYXJhY3RlcnMsIGFuZCBoYXZlIGxhcmdlIGNvdW50LCB1c2luZyBhIHRocmVzaG9sZCBvZiAxNCwwMDAuDQoNCmBgYHtyIGNoZWNrY2hhcnN9DQojIHNlbGVjdCBjaGFyYWN0ZXIgdmFyaWFibGVzIHdpdGggbm9uIHdoaXRlIHNwYWNlDQppc2NoYXJzIDwtIHRyYWluICU+JSBzZWxlY3RfaWYoaXMuY2hhcmFjdGVyKQ0KaXNjaGFyc3RydWUgPC0gc2FwcGx5KGlzY2hhcnMsZnVuY3Rpb24oeCkgdGFibGUoeCA9PSIiKVsiVFJVRSJdKQ0KaXNjaGFyc3RydWVbaXNjaGFyc3RydWUgPCAxNDAwMF0NCiMgQ29udmVydCB0aGVzZSBjaGFyYWN0ZXIgdmFyaWFibGVzIHRvIGZhY3RvcnMgc28gdGhhdCB0aGV5IHJldGFpbiBhIHZhbHVlIHdoZW4gdGhlIHRyYWluIGFuZCB0ZXN0IHNldHMgYXJlIGNvbnZlcnRlZCB0byBudW1lcmljDQp0cmFpbiRMTjJfUkluZExuZ0JFT3RoIDwtIGFzLmZhY3Rvcih0cmFpbiRMTjJfUkluZExuZ0JFT3RoKQ0KdHJhaW4kTE4yX1dJbmRMbmdCRU90aCA8LSBhcy5mYWN0b3IodHJhaW4kTE4yX1dJbmRMbmdCRU90aCkNCnRlc3QkTE4yX1JJbmRMbmdCRU90aCA8LSBhcy5mYWN0b3IodGVzdCRMTjJfUkluZExuZ0JFT3RoKQ0KdGVzdCRMTjJfV0luZExuZ0JFT3RoIDwtIGFzLmZhY3Rvcih0ZXN0JExOMl9XSW5kTG5nQkVPdGgpDQpgYGANCg0KQ29udmVydCB0aGUgdHJhaW4gYW5kIHRlc3QgZGF0YXNldHMgdG8gbnVtZXJpYyBjbGFzc2VzIHVzaW5nIHRoZSBtYXAgZnVuY3Rpb24gZnJvbSB0aGUgW3B1cnJdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9wdXJyci92ZXJzaW9ucy8wLjIuMi4yKSBSIHBhY2thZ2UsIGZvciBYR0Jvb3N0IG1vZGVsbGluZy4NCg0KYGBge3IgbnVtZXJpY2RhdGEsIHdhcm5pbmc9RkFMU0V9DQp0cmFpbiA8LSBkYXRhLmZyYW1lKG1hcCh0cmFpbiwgfmFzLm51bWVyaWMoLikpICkNCnRlc3QgPC0gZGF0YS5mcmFtZShtYXAodGVzdCwgfmFzLm51bWVyaWMoLikpICkNCmBgYA0KDQoNCiogKiAqDQoNCiMjIyBQYXJ0IDE6IEVEQQ0KDQpTZWUgc2VwYXJhdGUgc2NyaXB0LCBXaURTRURBLlJtZA0KDQojIyMgUGFydCAyOiBEYXRhIENsZWFuaW5nDQoNCkxldCdzIGNsZWFuIHRoZSB0cmFpbiBhbmQgdGVzdCBkYXRhc2V0cyB2YXJpYWJsZSBieSB2YXJpYWJsZSBiYXNlZCBvbiB0aGUgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcy4NCg0KMi4xIFJlbW92ZSB0cmFpbiBJRHMNCg0KYGBge3IgcmVtb3ZlaWR9DQojIFJlbW92ZSBJRCBmZWF0dXJlIGZyb20gdHJhaW4gYXMgdGhpcyBpZGVudGlmaWVyIGlzIG5vdCBuZWVkZWQgaW4gdGhlIHRyYWluaW5nLiBUaGUgaWQgd2lsbCBiZSBsZWZ0IGluIHRoZSB0ZXN0IHNldCBmb3IgdGhlIHByZWRpY3Rpb25zLg0KdHJhaW4gPC0gdHJhaW4gJT4lIGRwbHlyOjpzZWxlY3QoLXRyYWluX2lkKQ0KaWQgPC0gdGVzdCR0ZXN0X2lkDQp0ZXN0IDwtIHRlc3QgJT4lIGRwbHlyOjpzZWxlY3QoLXRlc3RfaWQpDQpgYGANCg0KMi4yIFJlbW92ZSBuZWFyIHplcm8gdmFyaWFuY2UgdmFyaWFibGVzDQoNClJlbW92aW5nIHZhcmlhYmxlcyB3aXRoIG5lYXIgemVybyB2YXJpYW5jZSB1c2luZyB0aGUgW2NhcmV0XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvY2FyZXQvaW5kZXguaHRtbCkgUiBwYWNrYWdlLg0KDQpgYGB7ciBuZWFyemVyb3Zhcn0NCm5lYXIuemVybyA8LSBuZWFyWmVyb1Zhcih0cmFpbixzYXZlTWV0cmljcz1UUlVFKQ0KdmFyaWFibGVzIDwtIHJvdy5uYW1lcyhzdWJzZXQobmVhci56ZXJvLG5lYXIuemVybyRuenY9PUZBTFNFKSkNCnRyYWluIDwtIGRhdGEuZnJhbWUodHJhaW4pWyx2YXJpYWJsZXNdDQojIFBlcmZvcm0gdGhlIHNhbWUgYWN0aW9uIG9uIHRoZSB0ZXN0IHNldCwgYWZ0ZXIgcmVtb3ZpbmcgdGhlICJpc19mZW1hbGUiIGluIHBvc2l0aW9uIDkgZnJvbSB2YXJpYWJsZXMgYXMgdGhpcyBpcyB0byBiZSBwcmVkaWN0ZWQgaW4gdGhlIHRlc3Qgc2V0DQp0ZXN0IDwtIGRhdGEuZnJhbWUodGVzdClbLHZhcmlhYmxlc1stOV1dDQpgYGANCg0KMi4zIE1pc3NpbmcgVmFsdWVzDQoNCk5leHQgd2Ugd2lsbCBleHRyYWN0IGFuZCB2aWV3IHRoZSB2YXJpYWJsZXMgaW4gbW9yZSBkZXRhaWwgdGhhdCBoYXZlIHZhbHVlIE5BLCB3aGljaCByZXByZXNlbnQgdGhlIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBkYXRhc2V0LiBXaXRoIGRwbHlyIHdlIGNhbiBjYWxsIGZ1bmN0aW9ucyBmcm9tIGRpZmZlcmVudCBSIHBhY2thZ2VzIGRpcmVjdGx5IGluc2lkZSB0aGUgZHBseXIgZnVuY3Rpb25zLiBXZSB3aWxsIHVzZSB0aGUgW3N0cmluZ3JdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9zdHJpbmdyL3ZpZ25ldHRlcy9zdHJpbmdyLmh0bWwpIFIgcGFja2FnZSB3aXRoIGRwbHlyIHRvIHZpZXcgYSBzdW1tYXJ5IG9mIHRoZSBOQXMuIFdlIHdpbGwgYWxzbyB1c2UgdGhlIFtwdXJycl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3B1cnJyL3ZlcnNpb25zLzAuMi4yLjIpIHRvIGFwcGx5IHRoZSBzdW0gZnVuY3Rpb24gYWNyb3NzIG11bHRpcGxlIHZhcmlhYmxlcy4NCg0KYGBge3IgbWlzc2luZ30NCiMgVXNlIHRoZSBiYXNlIHN1bW1hcnkgZnVuY3Rpb24gZm9yIHJlc3VsdCBzdW1tYXJpZXMgbm90IGRwbHlyLiBUaGlzIHdpbGwgcHJvdmlkZSB1cyB3aXRoIHRoZSByYW5nZXMgb2YgdGhlIHZhcmlhYmxlcyBpbmNsdWRpbmcgdGhlIG1pbmltdW1zLg0KcyA8LSBzdW1tYXJ5KHRyYWluKSAgDQojICBFeHRyYWN0IGFuZCB2aWV3IHRoZSBmcmVxdWVuY3kgb2YgTkEncyBmcm9tIHRoZSBzdW1tYXJ5IHMgd2UganVzdCBjcmVhdGVkLiBVc2Ugc3RyX2RldGVjdCBmcm9tIHN0cmluZ3IgcGFja2FnZS4gDQpzICU+JSANCiAgICAgIGRhdGEuZnJhbWUoKSAlPiUgDQogICAgICBmaWx0ZXIoc3RyX2RldGVjdChGcmVxLCJOQSIpKSANCiMgU2luY2UgdGhlcmUgYXJlIHZhcmlhYmxlcyB3aXRoIHZlcnkgaGlnaCBwcm9wb3J0aW9uIG9mIE5BcywgbGV0J3MgcmVtb3ZlIHRoZXNlIHZhcmlhYmxlcyBhc3N1bWluZyB0aGV5IHdpbGwgbm90IGFkZCBwcmVkaWN0aXZlIHZhbHVlDQojIFVzaW5nIG1hcCBmdW5jdGlvbiBmcm9tIHRoZSBwdXJyIHBhY2thZ2Ugc3VtIHRoZSBOQXMgZm9yIGVhY2ggY29sdW1uDQpjb3VudF9uYSA8LSBtYXAodHJhaW4sIH5zdW0oaXMubmEoLikpKSANCiMgUmVtb3ZlIGNvbHVtbnMgd2l0aCBtb3JlIHRoYW4gJSBOQSB2YWx1ZXMgdXNpbmcgYW4gaW5kZXgNCmluZGV4MSA8LSB3aGljaChjb3VudF9uYSA8IDAuOSpkaW0odHJhaW4pWzFdKSAgDQp0cmFpbiA8LSB0cmFpblssIGluZGV4MV0NCiMgUGVyZm9ybSB0aGUgc2FtZSBhY3Rpb24gb24gdGhlIHRlc3Qgc2V0LCBhZnRlciByZW1vdmluZyB0aGUgImlzX2ZlbWFsZSIgaW4gcG9zaXRpb24gOCBmcm9tIHZhcmlhYmxlcyBhcyB0aGlzIGlzIHRvIGJlIHByZWRpY3RlZCBpbiB0aGUgdGVzdCBzZXQNCmluZGV4MiA8LWluZGV4MVstOF0NCmluZGV4MltpbmRleDI+OF08LWFzLmludGVnZXIoaW5kZXgyW2luZGV4Mj44XS0xKQ0KdGVzdCA8LSB0ZXN0WyxpbmRleDJdDQpgYGANCg0KMi40IEltcHV0YXRpb24gb2YgbWlzc2luZyB2YWx1ZXMNCg0KV2UgYXJlIGdvaW5nIHRvIHJldmlldyB0aGUgY29sdW1ucyB3aXRoIG1pc3NpbmcgdmFsdWVzIHRvIGltcHV0ZSB0aGUgdmFsdWVzLiBXZSB3aWxsIGNyZWF0ZSBhIG1lcmdlZCBkYXRhc2V0IGZyb20gdGhlIGRhdGEgZGljdGlvbmFyeS4gVGhlbiB1c2luZyB0aGUgcmVwbGFjZV9uYSBmdW5jdGlvbiBmcm9tIHRoZSBbdGlkeXJdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy90aWR5ci9pbmRleC5odG1sKSBSIHBhY2thZ2UuDQoNCmBgYHtyIGltcHV0ZX0NCiMgIEV4dHJhY3QgYW5kIHZpZXcgdGhlIGZyZXF1ZW5jeSBvZiBOQSdzIGFnYWluDQpzIDwtIHN1bW1hcnkodHJhaW4pIA0KbWlzc2luZyA8LSBzICU+JSANCiAgICAgIGRhdGEuZnJhbWUoKSAlPiUgDQogICAgICBmaWx0ZXIoc3RyX2RldGVjdChGcmVxLCJOQSIpKSANCiMgUmVtb3ZlIHRoZSB3aGl0ZSBzcGFjZSBhbmQgcmVuYW1lIGNvbHVtbnMgdG8gbWFrZSBpdCBlYXNpZXIgdG8gbWVyZ2UgaW50byBhIGRkbWlzc2luZyBkYXRhZnJhbWUNCm1pc3NpbmckVmFyMiA8LSBtaXNzaW5nJFZhcjIgJT4lIA0KICAgICAgYXMuY2hhcmFjdGVyKCkgJT4lDQogICAgICB0cmltd3MoKQ0KbWlzc2luZyRGcmVxIDwtIGFzLmNoYXJhY3RlcihtaXNzaW5nJEZyZXEpDQpuYW1lcyhtaXNzaW5nKSA8LSBjKCJDb2x1bW4uTmFtZSIsIkZyZXEiKQ0KZGRtaXNzaW5nIDwtIG1lcmdlKGRkMixtaXNzaW5nLGJ5ID0gIkNvbHVtbi5OYW1lIikNCmRkbWlzc2luZw0KIyBXZSBzZWUgdGhhdCBNVDFBIGhhcyBoaWdoIHZhcmlhYmxlIGltcG9ydGFuY2UgZnJvbSB0aGUgRURBIHNvIHdlIHdpbGwgcmVwbGFjZSB0aGUgTkEgdmFsdWVzIHdpdGggOTksIHdoaWNoIGlzIHRoZSBleGlzdGluZyBOQSBjYXRlZ29yeSBpbiB0aGlzIHF1ZXN0aW9ucy4gTm90ZSB0aGUgc3RhcnRlciBtb2JpbGUgcXVlc3Rpb24gTVQxIGFuZCBNVDIgaGF2ZSBubyBOQXMuIA0KdHJhaW4gPC0gdHJhaW4gJT4lIA0KICAgcmVwbGFjZV9uYShsaXN0KE1UMUE9OTkpKQ0KIyBXZSBzZWUgdGhhdCB0aGlzICBoYXMgaGlnaCB2YXJpYWJsZSBpbXBvcnRhbmNlIE1UNi5Ib3cgZGlkIHlvdSBvYnRhaW4geW91ciBwaG9uZT8gKFRvcCAxMCBWSSkgR09UIE5Bcy4gUmVwbGFjZSB3aXRoIGV4aXN0aW5nIGNhdGVnb3J5IDk5DQp0cmFpbiA8LSB0cmFpbiAlPiUgDQogICByZXBsYWNlX25hKE1UNj05OSkNCiMgV2Ugc2VlIHRoYXQgdGhpcyAgaGFzIGhpZ2ggdmFyaWFibGUgaW1wb3J0YW5jZSBETDIuIFdoYXQgaXMgeW91ciBwcmltYXJ5IGpvYiAoaS5lLiwgdGhlIGpvYiB3aGVyZSB5b3Ugc3AgUmVwbGFjZSB3aXRoIGV4aXN0aW5nIGNhdGVnb3J5IDk2DQp0cmFpbiA8LSB0cmFpbiAlPiUgDQogICByZXBsYWNlX25hKERMMj05NikNCiMgV2Ugc2VlIHRoYXQgdGhpcyAgaGFzIGhpZ2ggdmFyaWFibGUgaW1wb3J0YW5jZSBETDEuIEluIHRoZSBwYXN0IDEyIG1vbnRocywgd2VyZSB5b3UgbWFpbmx5Li4uPyBSZXBsYWNlIHdpdGggZXhpc3RpbmcgY2F0ZWdvcnkgOTYNCnRyYWluIDwtIHRyYWluICU+JSANCiAgIHJlcGxhY2VfbmEoREwxPTk2KQ0KIyBOQSBmaXggcmVwbGFjZSBhbGwgcmVtYWluaW5nIE5BIHdpdGggOTkuIFdlIHdpbGwgbm90IHVzZSB0aGlzIHJlcGxhY2VtZW50IGFzIFhHYm9vc3QgY2FuIGhhbmRsZSBtaXNzaW5nIHZhbHVlcw0KIyB0cmFpbiA8LSB0cmFpbiAlPiUgDQojICByZXBsYWNlKC4saXMubmEoLiksIDk5KQ0KIyB0ZXN0IDwtIHRlc3QgJT4lIA0KIyAgcmVwbGFjZSguLGlzLm5hKC4pLCA5OSkNCmBgYA0KDQoyLjUgSW1wdXRhdGlvbiBvZiB0aGUgbWVkaWFuDQoNClRoZSB2YXJpYWJsZSB0cmFpbiRBQTE0IGhhcyBzb21lIG91dGxpZXJzLCBhbmQgaXQgaXMgaW4gdGhlIHRvcCAxMCBpbiB0aGUgdmFyaWFibGUgaW1wb3J0YW5jZSBidXQgaXQgaXMgbm90IGluIHRoZSBkYXRhIGRpY3Rpb25hcnkuIFdlIHdpbGwgaW1wdXRlIHRoZSBtZWRpYW4gb24gdGhpcyB2YXJpYWJsZS4gDQoNCmBgYHtyIEFBMTRtZWRpYW59DQojIEltcHV0ZSB0aGUgb3V0bGllciB0byB0aGUgbWVkaWFuIG9mIHRoaXMgdmFyaWFibGUNCnRyYWluJEFBMTRbdHJhaW4kQUExND09OTk5OTldIDwtIG1lZGlhbih0cmFpbiRBQTE0KQ0KdGVzdCRBQTE0W3Rlc3QkQUExND09OTk5OTldIDwtIG1lZGlhbih0ZXN0JEFBMTQpDQojIENoZWNrIHRoZSBpbXB1dGF0aW9uDQp0cmFpbiAlPiUgc2VsZWN0KEFBMTQpICU+JSBmaWx0ZXIoQUExND09OTk5OTkpICU+JSBoZWFkKCkNCnN1bW1hcnkodHJhaW4kQUExNCkNCmBgYA0KDQoNCg0KKiAqICoNCg0KIyMgUGFydCAzOiBNb2RlbGluZw0KDQozLjEuICoqTU9ERUwgU0VMRUNUSU9OKioNCg0KU2luY2Ugd2Uga25vdyB0aGUgb3V0Y29tZSBkaXNjcmV0ZSB2YXJpYWJsZSwgd2Ugd2lsbCB1c2UgYSBzdXBlcnZpc2VkIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtLiBJdCBhbHNvIGFwcGVhcnMgZnJvbSBvdXIgRURBLCB0aGUgaXNfZmVtYWxlIHZhcmlhYmxlIGlzIDAgb3IgMSwgd2Ugd2lsbCBwb3RlbnRpYWxseSBuZWVkIGEgY2xhc3NpZmljYXRpb24gbW9kZWwuIA0KVGhlIGFsZ29yaXRobSB0aGF0IHdlIHdpbGwgdXNlIGlzIFhHQm9vc3Qgd2hlcmUgd2UgY2FuIGNob29zZSBiZXR3ZWVuIHRoZSB0d28gYm9vc3RlciBtZXRob2RzLiBJbml0aWFsbHkgd2Ugd2lsbCB1c2UgZGVmYXVsdCBYR0Jvb3N0IHBhcmFtZXRlcnMgYW5kIHBlcmZvcm0gc29tZSBpbml0aWFsIGZlYXR1cmUgZW5naW5lZXJpbmcuDQoNCldlIHdpbGwgdXNlIEFVQywgYXMgb3VyIG1ldHJpYyBhbmQgdWx0aW1hdGVseSBzZWxlY3QgdGhlIGZpbmFsIG1vZGVsIGJhc2VkIG9uIHRoZSBoaWdoZXN0IHZhbHVlLiBTZWUgdGhpcyBwb3N0IGZvciBtb3JlIGluZm9ybWF0aW9uIG9uIHRoZXNlIFtjdXJ2ZXNdKGh0dHA6Ly9ibG9nLnloYXQuY29tL3Bvc3RzL3JvYy1jdXJ2ZXMuaHRtbCkNCg0KSW4gUiwgdGhlIFhHQm9vc3QgcGFja2FnZSB1c2VzIHRoZSBmb2xsb3dpbmc6DQoNCi0gQSBtYXRyaXggb2YgaW5wdXQgZGF0YSBpbnN0ZWFkIG9mIGEgZGF0YSBmcmFtZS4gDQoNCi0gT25seSBudW1lcmljIHZhcmlhYmxlcy4NCg0KLSBUaGUgaXNfZmVtYWxlIHZhcmlhYmxlIHNlcGFyYXRlbHksIHdoaWNoIHdlIHdpbGwgY29kZSB0byB5Lg0KDQoNCldlIHdpbGwgY29udmVydCB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGludG8gZHVtbXkgdmFyaWFibGVzIHVzaW5nIG9uZSBob3QgZW5jb2RpbmcuICBJdCBpcyByZWNvbW1lbmRlZCBieSB0aGUgUiBwYWNrYWdlIFhHQm9vc3QncyB2aWduZXR0ZSB0byB1c2UgIHhnYi5ETWF0cml4Lg0KDQpYR0Jvb3N0IHdpbGwgYWxzbyBoYW5kbGUgTkEgbWlzc2luZyB2YWx1ZXMgc28gdGhlc2Ugd2lsbCBiZSBsZWZ0IGluIHRoZSB0cmFpbiBhbmQgdGVzdCBkYXRhc2V0cy4NCg0KMy4yLiAqKkZFQVRVUkUgRU5HSU5FRVJJTkcqKg0KDQozLjIuMSBBZGQgbmV3IGZlYXR1cmVzDQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3IgYm90aCB0aGUgdHJhaW4gYW5kIHRlc3Qgc2V0cywgYmFzZWQgb24gb3VyIEVEQSBhbmQgcHJldmlvdXMgcnVucyBvZiB0aGUgbW9kZWxsaW5nLg0KDQpBLiBBZ2UgYnJhY2tldHMNCg0KREcxIHJlbGF0ZXMgdG8gd2hhdCB5ZWFyIHRoZSByZXNwb25kZW50IHdhcyBib3JuLiBXZSBjYW4gYmluIGludG8gYWdlIGJyYWNrZXRzIGJhc2VkIG9uIHRoZSBzdXJ2ZXkgdGhhdCBpcyBkYXRlZCBbMjAxNl0oaHR0cDovL2ZpbmNsdXNpb24ub3JnL2Jsb2cvZmlpLXVwZGF0ZXMvZmluYW5jaWFsLWluY2x1c2lvbi1pbi1pbmRpYS1sZXNzb25zLWZyb20tdGhlLTIwMTYtZmlpLWRhdGEuaHRtbCkNCg0KYGBge3IgYWdlZ3JvdXBzfQ0KIyBDaGVjayB0aGUgcmFuZ2Ugb2YgdGhlIHllYXJzIGJvcm4gaW4gREcxDQpyYW5nZSh0cmFpbiRERzEpDQojIFNldCB0aGUgYnJlYWtzDQphZ2VicmVha3MgPC0gYygxOTE2LDE5NTYsMTk5MSwxOTk3LDIwMDEpDQojIEN1dCB0aGUgdHJhaW4gc2V0IGludG8gYSBuZXcgdmFyaWFibGUgY2FsbGVkIERHMUENCnRyYWluJERHMUEgPC0gY3V0KHRyYWluJERHMSwgYWdlYnJlYWtzLCBpbmNsdWRlLmxvd2VzdD1UKQ0KIyBSZW5hbWUgdGhlIGxldmVscw0KbGV2ZWxzKHRyYWluJERHMUEpIDwtIHJldihjKCIxNSB0byAxOCB5ZWFycyIsIjE5IHRvIDI0IHllYXJzIiwiMjUgdG8gNTkgeWVhcnMiLCI2MCB5ZWFycyBhbmQgb3ZlciIpKQ0KIyBDb252ZXJ0IHRvIG51bWVyaWMNCnRyYWluJERHMUE8LXVuY2xhc3ModHJhaW4kREcxQSkgJT4lIGFzLm51bWVyaWMNCiMgRG8gdGhlIHNhbWUgZm9yIHRoZSB0ZXN0IHNldC4gQ3V0IHRoZSB0ZXN0IHNldA0KdGVzdCRERzFBIDwtY3V0KHRlc3QkREcxLCBhZ2VicmVha3MsIGluY2x1ZGUubG93ZXN0PVQpDQojIFJlbmFtZSB0aGUgbGV2ZWxzDQpsZXZlbHModGVzdCRERzFBKSA8LSByZXYoYygiMTUgdG8gMTggeWVhcnMiLCIxOSB0byAyNCB5ZWFycyIsIjI1IHRvIDU5IHllYXJzIiwiNjAgeWVhcnMgYW5kIG92ZXIiKSkNCiMgQ29udmVydCB0byBudW1lcmljDQp0ZXN0JERHMUE8LXVuY2xhc3ModGVzdCRERzFBKSAlPiUgYXMubnVtZXJpYw0KIyBDaGVjayB0aGUgcmFuZ2Ugb2YgdGhlIG5ldyB2YXJpYWJsZSBERzFBICBoYXMgYmVlbiBjb252ZXJ0ZWQgZnJvbSBiaW5zIHRvIG51bWVyaWMgdmFsdWVzDQpyYW5nZSh0cmFpbiRERzFBKQ0KYGBgDQoNCkIuIExpdmUgaW4gdG93biBvciB2aWxsYWdlDQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3Igd2hldGhlciBhIHJlc3BvbmRlbnQgbGl2ZXMgaW4gdG93biBvciBub3QuDQoNCmBgYHtyIHRvd252aWxsYWdlfQ0KdHJhaW4kQUE1b3Jub3QgPC0gaWZlbHNlKGlzLm5hKHRyYWluJEFBNSksMCwxKQ0KdGVzdCRBQTVvcm5vdCA8LSBpZmVsc2UoaXMubmEodGVzdCRBQTUpLDAsMSkNCmBgYA0KDQpDLiBQYXJlbnQgb3Igbm90DQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3Igd2hldGhlciBhIHJlc3BvbmRlbnQgaXMgYSBwYXJlbnQgb3Igbm90LCBhbmQgYSBncmFuZHBhcmVudCBvciBub3QgYmFzZWQgb24gdGhlIHF1ZXN0aW9uOiBERzYuSG93IGFyZSB5b3UgcmVsYXRlZCB0byB0aGUgaG91c2Vob2xkIGhlYWQ/DQoiMT1NeXNlbGZcbjI9U3BvdXNlXG4zPVNvbi9EYXVnaHRlclxuND1GYXRoZXIvTW90aGVyXG41PVNpc3Rlci9Ccm90aGVyXG42PUdyYW5kY2hpbGRcbjc9T3RoZXIgcmVsYXRpdmVcbjk9T3RoZXIgbm9uLXJlbGF0aXZlXG45OT1ESyINCg0KYGBge3IgcGFyZW50fQ0KdHJhaW4kREc2cGFyZW50IDwtIGlmZWxzZSh0cmFpbiRERzY9PTEgfHRyYWluJERHNj09MiwxLDApDQp0cmFpbiRERzZncGFyZW50IDwtIGlmZWxzZSh0cmFpbiRERzY9PTQgLDEsMCkNCnRlc3QkREc2cGFyZW50IDwtIGlmZWxzZSh0ZXN0JERHNj09MSB8dGVzdCRERzY9PTIsMSwwKQ0KdGVzdCRERzZncGFyZW50IDwtIGlmZWxzZSh0ZXN0JERHNj09NCAsMSwwKQ0KYGBgDQoNCkQuIFdvcmtpbmcgb3Igbm90DQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3Igd2hldGhlciBhIHJlc3BvbmRlbnQgaXMgc3RheSBhdCBob21lIG9yIG5vdCBvciBhIHN0dWRlbnQgb3Igbm90Lg0KDQpgYGB7ciB3b3JraW5nfQ0KdHJhaW4kREwxaG91c2Vob2xkIDwtIGlmZWxzZSh0cmFpbiRETDE9PTcgLDEsMCkNCnRyYWluJERMMXN0dWRlbnQgPC0gaWZlbHNlKHRyYWluJERMMT09OCAsMSwwKQ0KdGVzdCRETDFob3VzZWhvbGQgPC0gaWZlbHNlKHRlc3QkREwxPT03ICwxLDApDQp0ZXN0JERMMXN0dWRlbnQgPC0gaWZlbHNlKHRlc3QkREwxPT04ICwxLDApDQpgYGANCg0KRS4gQm91Z2h0IHBob25lIG9yIG5vdA0KDQpXZSB3aWxsIGNyZWF0ZSBuZXcgZmVhdHVyZXMgZm9yIHdoZXRoZXIgYSByZXNwb25kZW50IGJvdWdodCB0aGVpciBwaG9uZSBmb3IgdGhlbXNlbGYgb3Igbm90Lg0KDQpgYGB7ciBwaG9uZX0NCnRyYWluJE1UNnBob25lIDwtIGlmZWxzZSh0cmFpbiRNVDY9PTEsMSwwKQ0KdGVzdCRNVDZwaG9uZSA8LSBpZmVsc2UodGVzdCRNVDY9PTEsMSwwKQ0KYGBgDQogICAgICAgICAgICAgICAgICAgICAgICAgICANCkYuQUExNCBncm91cGluZw0KDQpXZSB3aWxsIGFsc28gZ3JvdXAgdGhlIHVua25vd24gY29udGludW91cyB2YXJpYWJsZSB2YXJpYWJsZSBBQTE0IHdpdGggYSBsYXJnZSBudW1iZXIgb2YgZnJlcXVlbmN5IHZhbHVlcy4NCg0KYGBge3IgQUExNH0NCnJhbmdlKHRyYWluJEFBMTQpDQp0cmFpbiRBQTE0XzE8LSBpZmVsc2UodHJhaW4kQUExNDwyMDAwLDEsDQogICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHRyYWluJEFBMTQ8NDAwMCwyLDMpDQogICAgICAgICAgICAgICAgICAgICAgKQ0KdGVzdCRBQTE0XzE8LSBpZmVsc2UodGVzdCRBQTE0PDIwMDAsMSwNCiAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UodHJhaW4kQUExNDw0MDAwLDIsMykNCiAgICAgICAgICAgICAgICAgICAgICApDQpgYGANCkcuIENvaGFiaXRpbmcgb3Igbm90DQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3Igd2hldGhlciBhIHJlc3BvbmRlbnQgaXMgY29oYWJpdGluZyBvciBub3QuDQoNCmBgYHtyIGNvaGFifQ0KIyBERzMuIFdoYXQgaXMgeW91ciBtYXJpdGFsIHN0YXR1cz8NCnRyYWluJERHM18xPC0gaWZlbHNlKHRyYWluJERHMz09N3x0cmFpbiRERzM9PTgsMSwwKQ0KdGVzdCRERzNfMTwtIGlmZWxzZSh0ZXN0JERHMz09N3x0cmFpbiRERzM9PTgsMSwwKQ0KYGBgDQoNCkguIEZpbmFuY2lhbGx5IGluZGVwZW5kZW50DQoNCldlIHdpbGwgY3JlYXRlIG5ldyBmZWF0dXJlcyBmb3Igd2hldGhlciBhIHJlc3BvbmRlbnQgaXMgZmluYW5jaWFsbHkgaW5kZXBlbmRlbnQgb3Igbm90Lg0KDQpgYGB7ciBpbmRlcH0NCnRyYWluJEZMNF8xPC0gaWZlbHNlKHRyYWluJEZMND09MSwxLDApDQp0ZXN0JEZMNF8xPC0gaWZlbHNlKHRlc3QkRkw0PT0xLDEsMCkNCmBgYA0KDQozLjIgKipEVU1NWSBWQVJJQUJMRVMqKg0KDQpgYGB7cn0NCiNDb252ZXJ0aW5nIGV2ZXJ5IGNhdGVnb3JpY2FsIHZhcmlhYmxlIHRvIG51bWVyaWNhbCB1c2luZyBkdW1teSB2YXJpYWJsZXMNCiNkbXkgPC0gZHVtbXlWYXJzKCIgfiAuIiwgZGF0YSA9IHRyYWluLGZ1bGxSYW5rID0gVCkNCiN0cmFpbl90cmFuc2Zvcm1lZCA8LSBkYXRhLmZyYW1lKHByZWRpY3QoZG15LCBuZXdkYXRhID0gdHJhaW4pKQ0KDQojQ2hlY2tpbmcgdGhlIHN0cnVjdHVyZSBvZiB0cmFuc2Zvcm1lZCB0cmFpbiBmaWxlDQojc3RyKHRyYWluX3RyYW5zZm9ybWVkKQ0KYGBgDQoNCg0KMy4zLiAqKkRBVEEgU1BMSVRUSU5HKioNCg0KVGhlIHRyYWluIGRhdGEgc2V0IGlzIGZ1cnRoZXIgc3BsaXQgaW50byB0cmFpbmluZyAoNzAlKSBhbmQgdmFsaWRhdGlvbiAoMzAlKSBzZXRzIGZvciBjcm9zcyB2YWxpZGF0aW9uIHVzaW5nIGNhcmV0LCBhZnRlciBiZWluZyByYW5kb21pc2VkLiBUaGUgZnVuY3Rpb24gY3JlYXRlRGF0YVBhcnRpdGlvbiBjYW4gYmUgdXNlZCB0byBjcmVhdGUgc3RyYXRpZmllZCByYW5kb20gc3BsaXRzIG9mIGEgZGF0YSBzZXQuDQoNCmBgYHtyIHNwbGl0fQ0KIyBDcmVhdGUgYSBzZXQgb2YgdHJhaW5pbmcgYW5kIHZhbGlkIHNldHMgdGhhdCB3aWxsIGJlIHVzZWQgaW4gdGhlIFhHQmJvb3N0IHBhY2thZ2UsIGtlZXBpbmcgdGhlIHRhcmdldCB2YWlhYmxlIGFzIG51bWVyaWMNCmluVHJhaW4gPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5ID0gdHJhaW4kaXNfZmVtYWxlLCBwID0gMC43LCBsaXN0ID0gRikNCnRyYWluaW5nIDwtIHRyYWluW2luVHJhaW4sXQ0KdmFsaWQgPC0gdHJhaW5bLWluVHJhaW4sXQ0KDQojIFNldCB0aGUgdGFyZ2V0IHZhcmlhYmxlIHRvIGJlIGEgY2hhcmFjdGVyIGZhY3RvciB3aXRoIGxldmVscyBvbmUgYW5kIHR3byBmb3IgdXNlIGluIHRoZSB0cmFpbmluZyBpbiB0aGUgY2FyZXQgWEdCb29zdA0KdHJhaW4kaXNfZmVtYWxlIDwtIGFzLmZhY3Rvcih0cmFpbiRpc19mZW1hbGUpDQpsZXZlbHModHJhaW4kaXNfZmVtYWxlKVtsZXZlbHModHJhaW4kaXNfZmVtYWxlKT09IjAiXSA8LSAib25lIg0KbGV2ZWxzKHRyYWluJGlzX2ZlbWFsZSlbbGV2ZWxzKHRyYWluJGlzX2ZlbWFsZSk9PSIxIl0gPC0gInR3byINCg0KIyBDcmVhdGUgYSBzZXQgb2YgdHJhaW5pbmcgYW5kIHZhbGlkIHNldHMgdGhhdCB3aWxsIGJlIHVzZWQgaW4gdGhlIGNhcmV0IHBhY2thZ2UsIHdpdGggdGhlIHRhcmdldCB2YWlhYmxlIGFzIGZhY3Rvcg0KaW5UcmFpbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSB0cmFpbiRpc19mZW1hbGUsIHAgPSAwLjcsIGxpc3QgPSBGKQ0KdHJhaW5pbmcyIDwtIHRyYWluW2luVHJhaW4sXQ0KdmFsaWQyIDwtIHRyYWluWy1pblRyYWluLF0NCmBgYA0KDQozLjQuICoqREFUQSBNQVRSSVgqKg0KDQpgYGB7ciBtYXRyaXh9DQojIGlzX2ZlbWFsZSBudW1lcmljIG91dGNvbWUgeSAobGFiZWwpIG9uIHRyYWluaW5nIHNldA0KeSA9IHRyYWluaW5nJGlzX2ZlbWFsZQ0KDQojIFRvIHVzZSBhZHZhbmNlZCBmZWF0dXJlcyB4Z2Jvb3N0LCBhcyByZWNvbW1lbmRlZCwgd2UnbGwgdXNlIHhnYi5ETWF0cml4IGZ1bmN0aW9uIHRvIGNvbnZlcnQgYSBtYXRyaXggb3IgYSBkZ0NNYXRyaXggaW50byBhIHhnYi5ETWF0cml4IG9iamVjdCwgd2hpY2ggY29udGFpbnMgYSBsaXN0IHdpdGggZGdDTWF0cml4IGRhdGEgIGFuZCBudW1lcmljIGxhYmVsOiANCg0KZHRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkYXRhLm1hdHJpeCh0cmFpbmluZ1sgLC10cmFpbmluZyRpc19mZW1hbGVdKSwNCiAgICAgICAgICAgICAgICAgbGFiZWwgPSB5KQ0KZHZhbGlkIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkYXRhLm1hdHJpeCh2YWxpZFsgLC12YWxpZCRpc19mZW1hbGVdKSwgDQogICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSB2YWxpZCRpc19mZW1hbGUpDQoNCg0KZHRlc3QgPC0geGdiLkRNYXRyaXgoZGF0YS5tYXRyaXgodGVzdCkpDQojIFdlIHVzZSB3YXRjaGxpc3QgcGFyYW1ldGVyIHRvIG1lYXN1cmUgdGhlIHByb2dyZXNzIHdpdGggYSBzZWNvbmQgZGF0YXNldCB3aGljaCBpcyBhbHJlYWR5IGNsYXNzaWZpZWQuIA0Kd2F0Y2hsaXN0IDwtIGxpc3QodHJhaW49ZHRyYWluLCB0ZXN0PWR2YWxpZCkNCiMgQ2hlY2sgdGhhdCBkdGVzdCBoYXMgdGhlIHNhbWUgbnVtYmVyIG9mIHJvd3MgYXMgdGhlIG9yaWdpbmFsIHRlc3QgZmlsZSwgMjcyODUgcm93cw0KbnJvdyhkdGVzdCkNCmBgYA0KDQozLjUuICoqTU9ERUwgUEFSQU1FVEVSUyoqDQoNClRoZSBmb2xsb3dpbmcgYXJlIHBhcmFtZXRlcnMgYXZhaWxhYmxlIGZvciBYR0Jvb3N0IFIgcGFja2FnZSBiYXNlIGRvbiB0aGUgW2RvY3VtZW50YXRpb25dKGh0dHBzOi8veGdib29zdC5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvKToNCg0KKipHZW5lcmFsIHBhcmFtZXRlcnMqKiAgDQoNCi0gYm9vc3RlciAgDQpXZSB3aWxsIHJ1biBtb2RlbHMgZm9yIGJvb3N0ZXJzIGdibGluZWFyIGFuZCBnYnRyZWUuIA0KbnRocmVhZCBbZGVmYXVsdD1tYXhpbXVtIGNvcmVzIGF2YWlsYWJsZV0gc2lsZW50W2RlZmF1bHQ9MF0gdG8gbm90IHNlZSB0aGUgcnVubmluZyBtZXNzYWdlcw0KDQoqKkJvb3N0ZXIgcGFyYW1ldGVycyoqDQoNCkZvciBlYWNoIG9mIHRoZXNlIGJvb3N0ZXJzLCB0aGVyZSBhcmUgYm9vc3RlciBwYXJhbWV0ZXJzLCB0aGVzZSBhcmUgY29tbW9uIGJldHdlZW4gdGhlIHR3bzoNCg0KLSBucm91bmRzIC0gT2JzZXJ2ZSB0aGUgbnVtYmVyIGNob3NlbiBmb3IgbnJvdW5kcyBmb3IgYW55IG92ZXJmaXR0aW5nIHVzaW5nIENWLiB0aGUgbWF4IG51bWJlciBvZiBpdGVyYXRpb25zLg0KLSBhbHBoYVtkZWZhdWx0PTFdIGFuZCBsYW1iZGEgW2RlZmF1bHQ9MF0gdG8gY29udHJvbCByZWd1bGFyaXNhdGlvbg0KDQpQYXJhbWV0ZXJzIGZvciBUcmVlIEJvb3N0ZXIgYWxzbyBpbmNsdWRlOiAgDQoNCi0gZXRhW2RlZmF1bHQ9MC4zXVtyYW5nZTogKDAsMSldIGNvbnRyb2xzIHRoZSBsZWFybmluZyByYXRlLiAgDQotIG1heF9kZXB0aFtkZWZhdWx0PTZdW3JhbmdlOiAoMCxJbmYpXSBjb250cm9scyB0aGUgZGVwdGggb2YgdGhlIHRyZWUtIHR1bmVkIHVzaW5nIENWLiBIaWdoZXIgdmFsdWUgb2YgbWF4X2RlcHRoIHdpbGwgY3JlYXRlIG1vcmUgZGVlcGVyIHRyZWVzIG9yIHdlIGNhbiBzYXkgaXQgd2lsbCBjcmVhdGUgbW9yZSBjb21wbGV4IG1vZGVsLkhpZ2hlciB2YWx1ZSBvZiBtYXhfZGVwdGggbWF5IGNyZWF0ZSBvdmVyZml0dGluZyBhbmQgbG93ZXIgdmFsdWUgb2YgbWF4X2RlcHRoIG1heSBjcmVhdGUgdW5kZXJmaXR0aW5nLkFsbCBkZXBlbmRzIG9uIGRhdGEgaW4gaGFuZC4gIA0KLSBtaW5fY2hpbGRfd2VpZ2h0W2RlZmF1bHQ9MV1bcmFuZ2U6KDAsSW5mKV0gSW4gc2ltcGxlIHdvcmRzLCBpdCBibG9ja3MgdGhlIHBvdGVudGlhbCBmZWF0dXJlIGludGVyYWN0aW9ucyB0byBwcmV2ZW50IG92ZXJmaXR0aW5nLiBTaG91bGQgYmUgdHVuZWQgdXNpbmcgQ1YuIEl0IGlzIGxpa2UgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBhIHRlcm1pbmFsIG5vZGUuSWYgdGhlIHRyZWUgcGFydGl0aW9uIHN0ZXAgcmVzdWx0cyBpbiBhIGxlYWYgbm9kZSB3aXRoIHRoZSBzdW0gb2YgaW5zdGFuY2Ugd2VpZ2h0IGxlc3MgdGhhbiBtaW5fY2hpbGRfd2VpZ2h0LCB0aGVuIHRoZSBidWlsZGluZyBwcm9jZXNzIHdpbGwgZ2l2ZSB1cCBmdXJ0aGVyIHBhcnRpdGlvbmluZy4gSW4gbGluZWFyIHJlZ3Jlc3Npb24gbW9kZSwgdGhpcyBzaW1wbHkgY29ycmVzcG9uZHMgdG8gbWluaW11bSBudW1iZXIgb2YgaW5zdGFuY2VzIG5lZWRlZCB0byBiZSBpbiBlYWNoIG5vZGUuICANCi0gc3Vic2FtcGxlW2RlZmF1bHQ9MV1bcmFuZ2U6ICgwLDEpXSBjb250cm9scyB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgKG9ic2VydmF0aW9ucykgc3VwcGxpZWQgdG8gYSB0cmVlLiAgDQotIGNvbHNhbXBsZV9ieXRyZWVbZGVmYXVsdD0xXVtyYW5nZTogKDAsMSldY29udHJvbCB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzICh2YXJpYWJsZXMpIHN1cHBsaWVkIHRvIGEgdHJlZS4gUmFuZG9tbHkgY2hvb3NpbmcgdGhlIG51bWJlciBvZiBjb2x1bW5zIG91dCBvZiBhbGwgY29sdW1ucyBvciB2YXJpYWJsZXMgYXQgYSB0aW1lIHdoaWxlIHRyZWUgYnVpbGRpbmcgcHJvY2Vzcy5Zb3UgY2FuIHRoaW5rIG9mIG10cnkgcGFyYW1ldGVyIGluIHJhbmRvbSBmb3Jlc3QgdG8gYmVnaW4gdW5kZXJzdGFuZGluZyBtb3JlIGFib3V0IHRoaXMuSGlnaGVyIHZhbHVlIG1heSBjcmVhdGUgb3ZlcmZpdHRpbmcgYW5kIGxvd2VyIHZhbHVlIG1heSBjcmVhdGUgdW5kZXJmaXR0aW5nLk9uZSBuZWVkcyB0byBwbGF5IHdpdGggdGhpcyB2YWx1ZS4gIA0KLSBnYW1tYSAoTWluaW11bSBMb3NzIFJlZHVjdGlvbikgT25lIGNhbiBwbGF5IHdpdGggdGhpcyBwYXJhbWV0ZXIgYWxzbyBidXQgbW9zdGx5IG90aGVyIHBhcmFtZXRlcnMgYXJlIHVzZWQgZm9yIG1vZGVsIHR1bmluZy4gIA0KDQoqKkxlYXJuaW5nIFRhc2sgUGFyYW1ldGVycyoqDQoNClRoZXNlIHBhcmFtZXRlcnMgc3BlY2lmeSBtZXRob2RzIGZvciB0aGUgbG9zcyBmdW5jdGlvbiBhbmQgbW9kZWwgZXZhbHVhdGlvbi4gSW4gYWRkaXRpb24gdG8gdGhlIHBhcmFtZXRlcnMgbGlzdGVkIGJlbG93LCB5b3UgYXJlIGZyZWUgdG8gdXNlIGEgY3VzdG9taXplZCBvYmplY3RpdmUgLyBldmFsdWF0aW9uIGZ1bmN0aW9uLg0KDQpPYmplY3RpdmVbZGVmYXVsdD0iYmluYXJ5OmxvZ2lzdGljIl0NCg0KZXZhbF9tZXRyaWMgW25vIGRlZmF1bHQsIGRlcGVuZHMgb24gb2JqZWN0aXZlIHNlbGVjdGVkXQ0KVGhlc2UgbWV0cmljcyBhcmUgdXNlZCB0byBldmFsdWF0ZSBhIG1vZGVsJ3MgYWNjdXJhY3kgb24gdmFsaWRhdGlvbiBkYXRhLiBGb3IgcmVncmVzc2lvbiwgZGVmYXVsdCBtZXRyaWMgaXMgUk1TRS4NCg0KT25lIG9mIHRoZSBzaW1wbGVzdCB3YXkgdG8gc2VlIHRoZSB0cmFpbmluZyBwcm9ncmVzcyBpbiBYR0Jvb3N0IGlzIHRvIHNldCB0aGUgdmVyYm9zZSBvcHRpb24gdG8gIyB2ZXJib3NlID0gMCwgbm8gbWVzc2FnZSBidXQgdXNlIHByaW50LmV2ZXJ5Lm4sdmVyYm9zZSA9IDEsIHByaW50IGV2YWx1YXRpb24gbWV0cmljLCB2ZXJib3NlID0gMiwgYWxzbyBwcmludCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgdHJlZS4NCg0KMy42LiAqKkNST1NTIFZBTElEQVRJT04qKg0KDQpVc2luZyB0aGUgeGdiLmN2IGZ1bmN0aW9uIGZvciA1LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiwgdGhpcyBmdW5jdGlvbiByZXR1cm5zIENWIGVycm9yLCB3aGljaCBpcyBhbiBlc3RpbWF0ZSBvZiB0ZXN0IG9yIG91dCBvZiBzYW1wbGUgIGVycm9yLiANCg0KYGBge3IgeGdiY3Z9DQojIHNldCByYW5kb20gc2VlZCwgZm9yIHJlcHJvZHVjaWJpbGl0eSANCnNldC5zZWVkKDEyMzQpDQojIFVzaW5nIGJvb3N0ZXIgZ2J0cmVlLCB3aXRoIGEgbGFyZ2UgbnJvdW5kPTUwIA0KeGdiY3YgPC0geGdiLmN2KHBhcmFtcyA9IGxpc3QoDQogICAgICAjIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgDQogICAgICBvYmplY3RpdmUgPSAnYmluYXJ5OmxvZ2lzdGljJyksDQogICAgICBtZXRyaWNzID0gbGlzdCgicm1zZSIsImF1YyIpLA0KICAgICAgbGFiZWw9eSwNCiAgICAgIGRhdGEgPSBkdHJhaW4sDQogICAgICBucm91bmRzID0gNTAsDQogICAgICBuZm9sZCA9IDUsICMgNSBmb2xkIENWDQogICAgICBzaG93c2QgPSBULCANCiAgICAgIHByaW50X2V2ZXJ5X24gPSAxMCwgIyB3aGVuIHZlcmJvc2UgPTANCiAgICAgICMgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gNSwgDQogICAgICB2ZXJib3NlPTEsDQogICAgICBwcmVkaWN0aW9uID0gVCkNCiANCiMgIG5yb3VuZCBiZXN0IGl0ZXJhdGlvbiBpcywgYmFzZWQgb24gdGhlIG1pbiB0ZXN0IHJtc2U6DQppdCA8LSAgd2hpY2gubWluKHhnYmN2JGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQ0KYmVzdGl0ZXJhdGlvbiA8LSAgeGdiY3YkZXZhbHVhdGlvbl9sb2ckaXRlcltpdF0NCmJlc3RpdGVyYXRpb24NCmBgYA0KDQoNClBsb3QgUk1TRSBmb3IgdGhlIGR0cmFpbiBjcm9zcyB2YWxpZGF0aW9uLg0KDQpgYGB7ciBybXNlfQ0KIyBQbG90IHRoZSBSTVNFIGZyb20gdGhlIENWDQp4Z2JjdiRldmFsdWF0aW9uX2xvZyAlPiUNCiAgIGRwbHlyOjpzZWxlY3QoaXRlcix0cmFpbl9ybXNlX21lYW4sdGVzdF9ybXNlX21lYW4pICU+JQ0KICBnYXRoZXIoVGVzdE9yVHJhaW4sIFJNU0UsIC1pdGVyKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gaXRlciwgeSA9IFJNU0UsIGdyb3VwID0gVGVzdE9yVHJhaW4sIGNvbG9yID0gVGVzdE9yVHJhaW4pKSArIA0KICBnZW9tX2xpbmUoKSArIA0KICB0aGVtZV9idygpDQpgYGANCg0KDQpgYGB7ciBjdmF1Y30NCiMgQ2FsY3VsYXRlIEFVQyBmb3IgdGhlIHhnYmN2DQp4Z2Jjdi5ST0MgPC0gcm9jKHJlc3BvbnNlID0geSwNCiAgICAgICAgICAgICAgIHByZWRpY3RvciA9IHhnYmN2JHByZWQpDQojIEFyZWEgdW5kZXIgdGhlIGN1cnZlOiAxPw0KeGdiY3YuUk9DJGF1Yw0KYGBgDQoNClRoaXMgbW9kZWwgYXBwZWFycyB0byBzZXQgdGhlIEFVQyB0byAxLCB3ZSB3aWxsIGxvb2sgdG8gdXNlIGNhcmV0IGZvciB0aGUgbW9kZWwgdHJhaW5pbmcuDQoNCjMuNy4gKipNT0RFTCBUUkFJTklORyoqDQoNCk9uZSB3YXkgdG8gbWVhc3VyZSBwcm9ncmVzcyBpbiBsZWFybmluZyBvZiBhIG1vZGVsIGlzIHVzaW5nIHhnYi50cmFpbiwgcHJvdmlkaW5nIGEgc2Vjb25kIGRhdGFzZXQgYWxyZWFkeSBjbGFzc2lmaWVkLiBUaGVyZWZvcmUgaXQgY2FuIGxlYXJuIG9uIHRoZSBmaXJzdCBkYXRhc2V0IGFuZCB0ZXN0IGl0cyBtb2RlbCBvbiB0aGUgc2Vjb25kIG9uZS4gTWV0cmljcyBhcmUgbWVhc3VyZWQgYWZ0ZXIgZWFjaCByb3VuZCBkdXJpbmcgdGhlIGxlYXJuaW5nLiBIb3dldmVyIHdlIGNhbiBhbHNvIFt1c2UgY2FyZXQgdG8gdHJhaW4gdXNpbmcgeGdiVHJlZSB0byBjb21wYXJlIG1vZGVscy5dKGJsb2cucmV2b2x1dGlvbmFuYWx5dGljcy5jb20vMjAxNi8wNS91c2luZy1jYXJldC10by1jb21wYXJlLW1vZGVscy5odG1sKQ0KDQpJbiB0aGlzIG1vZGVsIHRyYWluaW5nIHdlIHdpbGwgdHVuZSB0aGUgaHlwZXIgcGFyYW1ldGVycywgYWx0aG91Z2ggdGhpcyBpcyBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlIHdheSB0byB0cmFpbiBhIG1vZGVsLiANCg0KV2Ugd2lsbCB1c2UgdGhlIGZvbGxvd2luZyBmdW5jdGlvbnM6DQoNCi0gZXhwYW5kLmdyaWQgZnVuY3Rpb24gdG8gbWFrZSBhIGRhdGEuZnJhbWUgd2l0aCBldmVyeSBjb21iaW5hdGlvbiBvZiBoeXBlcnBhcmFtZXRlcnMgDQotIGNhcmV0Ojp0cmFpbkNvbnRyb2wgdG8gc3BlY2lmeSB0aGUgdHlwZSBvZiBjcm9zcyB2YWxpZGF0aW9uIChoZXJlIHVzZSA1LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbikNCi0gY2FyZXQ6OnRyYWluIHRvIHNlYXJjaCBvdmVyIHRoZSBncmlkIG9mIGh5cGVycGFyYW1ldGVyIGNvbWJpbmF0aW9ucyB0byBmaW5kIHRoZSBtb2RlbCB0aGF0IG1heGltaXNlcyBST0MNCg0KYGBge3IgdHVuaW5nLCBtZXNzYWdlPUZBTFNFfQ0KIyBTZXQgcmFuZG9tIHNlZWQsIGZvciByZXByb2R1Y2liaWxpdHkgDQpzZXQuc2VlZCgxMjM0KQ0KIyBTZXQgZXhwYW5kLmdyaWQNCnhnYi5ncmlkIDwtIGV4cGFuZC5ncmlkKG5yb3VuZHMgPSBjKGJlc3RpdGVyYXRpb24sIDEwMCksICMgU2V0IHRoZSBudW1iZXIgb2YgaXRlcmF0aW9ucyBmcm9tIHRoZSB4Z2IuY3YgYW5kIHByb3ZpZGluZyBhbm90aGVyIGhpZ2hlciB2YWx1ZSBmb3IgY29tcGFyaXNvbg0KICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gc2VxKGZyb209MC4yLCB0bz0xLCBieT0wLjIpLCAjIHNocmlua2FnZQ0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoID0gYyg2LDgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC4yLCAwLjgpLCAjIHZhcmlhYmxlcyBwZXIgdHJlZS4gZGVmYXVsdCAxDQogICAgICAgICAgICAgICAgICAgICAgICBnYW1tYSA9IGMoMCwxKSwgIyBkZWZhdWx0IDANCiAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsMTApLA0KICAgICAgICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlPTEpICMgZGVmYXVsdCAxDQoNCg0KIyBTZXQgdHJhaW5pbmcgY29udHJvbA0KY250cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgICAjIDUgZm9sZCBjcm9zcyB2YWxpZGF0aW9uDQogICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSAyLAkJIyBkbyAyIHJlcGV0aXRpb25zIG9mIGN2DQogICAgICAgICAgICAgICAgICAgICByZXBlYXRzID0gMiwgIyByZXBlYXRlZCA1IHRpbWVzDQogICAgICAgICAgICAgICAgICAgICBzdW1tYXJ5RnVuY3Rpb249dHdvQ2xhc3NTdW1tYXJ5LAkjIGJ1aWx0LWluIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgYXJlYSB1bmRlciB0aGUgUk9DIGN1cnZlLCB0byBjb21wYXJlIG1vZGVscw0KICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICBhbGxvd1BhcmFsbGVsID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2VJdGVyID0gRkFMU0UpDQoNCiMgVHJhaW4gbW9kZWwgd2l0aCBnYnRyZWUgYW5kIHBhcmFtcyBhYm92ZSB1c2luZyBjYXJldCANCnR1bmluZ21vZGVsIDwtIHRyYWluKHg9dHJhaW5pbmcyWywtOV0sDQogICAgICAgICAgICAgeT10cmFpbmluZzIkaXNfZmVtYWxlLCAjIHRhcmdldCB2ZWN0b3Igc2hvdWxkIGJlIG5vbi1udW1lcmljIGZhY3RvcnMgdG8gaWRlbnRpZnkgb3VyIHRhc2sgYXMgY2xhc3NpZmljYXRpb24sIG5vdCByZWdyZXNzaW9uLg0KICAgICAgICAgICAgIG1ldGhvZD0ieGdiVHJlZSIsDQogICAgICAgICAgICAgbWV0cmljPSJST0MiLA0KICAgICAgICAgICAgIHRyQ29udHJvbD1jbnRybCwjIHNwZWNpZnkgY3Jvc3MgdmFsaWRhdGlvbiANCiAgICAgICAgICAgICB0dW5lR3JpZD14Z2IuZ3JpZCwgIyBXaGljaCBoeXBlcnBhcmFtZXRlcnMgd2UnbGwgdGVzdA0KICAgICAgICAgICAgIG1heGltaXplID0gVFJVRSkgDQoNCiMgVmlldyB0aGUgbW9kZWwgcmVzdWx0cw0KdHVuaW5nbW9kZWwkYmVzdFR1bmUNCg0KIyBQbG90IHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgdHJhaW5pbmcgbW9kZWxzDQojIHNjYXR0ZXIgcGxvdCBvZiB0aGUgQVVDIGFnYWluc3QgbWF4X2RlcHRoIGFuZCBldGENCmdncGxvdCh0dW5pbmdtb2RlbCRyZXN1bHRzLCBhZXMoeCA9IGFzLmZhY3RvcihldGEpLCB5ID0gbWF4X2RlcHRoLCBzaXplID0gUk9DLCBjb2xvciA9IFJPQykpICsgDQogICAgICBnZW9tX3BvaW50KCkgKyANCiAgICAgIGdndGl0bGUoIlNjYXR0ZXIgcGxvdCBvZiB0aGUgQVVDIGFnYWluc3QgbWF4X2RlcHRoIGFuZCBldGEiKSArDQogICAgICB0aGVtZV9idygpICsgDQogICAgICBzY2FsZV9zaXplX2NvbnRpbnVvdXMoZ3VpZGUgPSAibm9uZSIpICsNCiAgICAgIHNjYWxlX2NvbG91cl9ncmFkaWVudChsb3cgPSAiYmxhY2siLCBoaWdoPSJ5ZWxsb3ciKQ0KIyBQbG90IHRoZSByZXN1bHRzIG9mIHRoZSBncmlkIGNvbWJpbmF0aW9ucyAgDQpwbG90KHR1bmluZ21vZGVsKQ0KYGBgDQpCYXNlZCBvbiB0aGUgdHVuaW5nIHdlIHdpbGwgbm93IHRyYWluIG91ciAiYmVzdCBtb2RlbCIsIGJzdCB3aXRoIHNlbGVjdGVkIHBhcmFtZXRlcnMuDQogIA0KYGBge3IgYmVzdG1vZGVsfSANCiMgU2V0IGV4cGFuZC5ncmlkDQp4Z2IuZ3JpZCA8LSBleHBhbmQuZ3JpZChucm91bmRzID0gMTAwLCAjIFNldCB0aGUgbWF4aW11bSBudW1iZXIgb2YgaXRlcmF0aW9ucyBmcm9tIHRoZSBncmlkIHNlYXJjaA0KICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4yLCAjIHNocmlua2FnZQ0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoID0gOCwNCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbHNhbXBsZV9ieXRyZWUgPSAwLjgsICMgdmFyaWFibGVzIHBlciB0cmVlLiBkZWZhdWx0IDENCiAgICAgICAgICAgICAgICAgICAgICAgIGdhbW1hID0gMCwgIyBkZWZhdWx0IDANCiAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9jaGlsZF93ZWlnaHQgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlPTEpICMgZGVmYXVsdCAxDQoNCmJzdCA8LSB0cmFpbih4PXRyYWluaW5nMlssLTldLA0KICAgICAgICAgICAgIHk9dHJhaW5pbmcyJGlzX2ZlbWFsZSwgIyBUYXJnZXQgdmVjdG9yIHNob3VsZCBiZSBub24tbnVtZXJpYyBmYWN0b3JzIHRvIGlkZW50aWZ5IG91ciB0YXNrIGFzIGNsYXNzaWZpY2F0aW9uLCBub3QgcmVncmVzc2lvbi4NCiAgICAgICAgICAgICBtZXRob2Q9InhnYlRyZWUiLA0KICAgICAgICAgICAgIG1ldHJpYz0iUk9DIiwNCiAgICAgICAgICAgICB0ckNvbnRyb2w9Y250cmwsIyBTcGVjaWZ5IGNyb3NzIHZhbGlkYXRpb24gDQogICAgICAgICAgICAgdHVuZUdyaWQ9eGdiLmdyaWQsDQogICAgICAgICAgICAgbWF4aW1pemUgPSBUUlVFKSAjIFdoaWNoIGh5cGVycGFyYW1ldGVycyB3ZSdsbCB0ZXN0DQoNCiMgVmlldyB0aGUgbW9kZWwgcmVzdWx0cw0KYnN0JGJlc3RUdW5lDQoNCiMjIyB4Z2Jvb3N0TW9kZWwgUHJlZGljdGlvbnMgYW5kIFBlcmZvcm1hbmNlDQojIE1ha2UgcHJlZGljdGlvbnMgdXNpbmcgdGhlIHRlc3QgZGF0YSBzZXQNCnhnYi5wcmVkIDwtIGlmZWxzZShwcmVkaWN0KGJzdCx2YWxpZCwgdHlwZSA9ICJyYXciKT09ICJ0d28iLCAxLCAwKQ0KIA0KI0xvb2sgYXQgdGhlIGNvbmZ1c2lvbiBtYXRyaXggIA0KY29uZnVzaW9uTWF0cml4KHhnYi5wcmVkLHZhbGlkJGlzX2ZlbWFsZSkgICANCiANCiNEcmF3IHRoZSBST0MgY3VydmUgdXNpbmcgdGhlIHBSb2MgcGFja2FnZQ0KeGdiLnByb2JzIDwtIHByZWRpY3QoYnN0LHZhbGlkLHR5cGU9InByb2IiKQ0KeGdiLlJPQyA8LSByb2MocHJlZGljdG9yPXhnYi5wcm9icyRvbmUsDQogICAgICAgICAgICAgICByZXNwb25zZT12YWxpZCRpc19mZW1hbGUpDQp4Z2IuUk9DJGF1Yw0KIyBBcmVhIHVuZGVyIHRoZSBjdXJ2ZQ0KcGxvdCh4Z2IuUk9DLG1haW49InhnYm9vc3QgUk9DIikNCmBgYA0KDQpQbG90IHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlIG9mIHRoaXMgYmVzdCBtb2RlbCwgYnN0Lg0KDQpgYGB7ciB2YXJpbXB9DQojIFZhcmlhYmxlIEltcG9ydGFuY2UNCnZhcmltcHhnYiA8LSB2YXJJbXAoYnN0KSRpbXBvcnRhbmNlICU+JSANCiAgbXV0YXRlKENvbHVtbi5OYW1lPXJvdy5uYW1lcyguKSkgJT4lDQogIGFycmFuZ2UoLU92ZXJhbGwpDQpkZHZhcmltcCA8LSBtZXJnZShkZDIsdmFyaW1weGdiLGJ5ID0gIkNvbHVtbi5OYW1lIikgIA0KZGR2YXJpbXBbMToxMCxdDQpwb3NpdGlvbnMgPC0gdmFyaW1weGdiWzE6MTAsXSRDb2x1bW4uTmFtZQ0KZ2dwbG90KHZhcmltcHhnYlsxOjEwLF0pICsgZ2VvbV9iYXIoYWVzKENvbHVtbi5OYW1lLE92ZXJhbGwpLHN0YXQ9ImlkZW50aXR5IikgKyBjb29yZF9mbGlwKCkgKyBzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cyA9IHBvc2l0aW9ucykNCmBgYA0KDQpPbmUgb2Ygb3VyIGVuZ2luZWVyZWQgZmVhdHVyZXMsIERMMWhvdXNlaG9sZCwgY29udHJpYnV0ZXMgc2Vjb25kIGluIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlLg0KDQojIyBQYXJ0IDQ6IFByZWRpY3Rpb25zDQoNCk1ha2UgYSBwcmVkaWN0aW9uIG9uIHRoZSB0ZXN0IHNldCBhbmQgY3JlYXRlIHN1Ym1pc3Npb24gZmlsZSB0byBiZSBsb2FkZWQgdG8gS2FnZ2xlLiBPbmNlIGxvYWRlZCBLYWdnbGUgd2lsbCBwcm92aWRlIGEgc2NvcmUgYXMgQVVDIG9uIHRoZSBfaXNfZmVtYWxlXyBwcmVkaWN0aW9ucy4NCg0KYGBge3IgcHJlZGljdGlvbnMgdGVzdH0NCiMgQ3JlYXRlIGEgcHJlZGljdGlvbiBmaWxlIA0KcHJlZGljdHRlc3QgPC0gaWZlbHNlKHByZWRpY3QoYnN0LHRlc3QsIHR5cGUgPSAicmF3Iik9PSAidHdvIiwgMSwgMCkNCmBgYA0KICANCg0KYGBge3Igc3ViZmlsZX0NCiMgQ3JlYXRlIEthZ2dsZSBTdWJtaXNzaW9uIEZpbGUgRmVtYWxlIGlzIDIsIG1hbGUgaXMgMSwgd2hpbGUgaW4gb3VyIHRyYW5zZm9ybWVkIGRhdGEsIGlzX2ZlbWFsZT0xIGZvciBmZW1hbGUgYW5kIGlzX2ZlbWFsZT0wIGZvciBtYWxlLg0KbXlfc29sdXRpb24gPC0gZGF0YS5mcmFtZShpZCxwcmVkaWN0dGVzdCkNCm5hbWVzKG15X3NvbHV0aW9uKSA8LSBjKCJ0ZXN0X2lkIiwiaXNfZmVtYWxlIikNCiMgQ2hlY2sgdGhlIG51bWJlciBvZiByb3dzIGluIHRoZSBzb2x1dGlvbiBmaWxlIGlzIDI3Mjg1DQpucm93KG15X3NvbHV0aW9uKQ0KIyBXcml0ZSBzb2x1dGlvbiB0byBmaWxlIHN1Ym1pc3Npb25GaWxlMS5jc3YNCndyaXRlLmNzdihteV9zb2x1dGlvbiwgZmlsZSA9ICJzdWJtaXNzaW9uRmlsZUMuY3N2IiwgcXVvdGU9Riwgcm93Lm5hbWVzPUYpDQpgYGANCg0KDQo=