Table of Contents

Introduction
Step 0: Load Packages & Specify Directories
Step 1: Load and Process Data
Step 2: Feature Selection
Step 3: Implement Algorithm
Step 4: Evaluation

Introduction

This notebook will implement and examine different classification methods on the Breast Cancer Wisconsin (Diagnostic) Data Set to classify whether the breast cancer is malignant or benign based on the features computed from a digitized image of a fine needle aspirate (FNA) of a breast mass.

Step 0: Load Packages & Specify Directories

# Packages that will be used
packages.used <- c("corrplot", "caret", "randomForest", "e1071", "dplyr", "gbm")
# Check packages that need to be installed
packages.needed <- setdiff(packages.used, 
                           intersect(installed.packages()[,1], 
                                     packages.used))
# Install additional packages
if(length(packages.needed)>0){
  install.packages(packages.needed, dependencies = TRUE,
                   repos='http://cran.us.r-project.org')
}
# Load libraries  
library("corrplot")
library("caret")
library("randomForest")
library("e1071")
library("dplyr")
library("gbm")
# Set working directory to the doc folder 
setwd("~/GitHub/Spring2018-Project5-grp_2/doc")

Step 1: Load and Process Data

Load Data

# Load data
df <- read.csv("../data/data.csv", header = TRUE, stringsAsFactors = FALSE)

Explore Data

Structure

The dataset includes 569 observations of 33 variables described as below.
(1) ID number
(2) Diagnosis (M = malignant, B = benign)
(3)-(32) Ten real-valued features described as follows are computed for each cell nucleus:
a) radius (mean of distances from center to points on the perimeter)
b) texture (standard deviation of gray-scale values)
c) perimeter
d) area
e) smoothness (local variation in radius lengths)
f) compactness (perimeter^2 / area - 1.0)
g) concavity (severity of concave portions of the contour)
h) concave points (number of concave portions of the contour)
i) symmetry
j) fractal dimension (“coastline approximation” - 1)

The mean, standard error and “worst” or largest (mean of the three largest values) of these features were computed for each image, resulting in 30 features. For instance, field 3 is Mean Radius, field 13 is Radius SE, field 23 is Worst Radius.
(33) All entries are NA’s

# Strucutre of the dataset
str(df)
'data.frame':   569 obs. of  33 variables:
 $ id                     : int  842302 842517 84300903 84348301 84358402 843786 844359 84458202 844981 84501001 ...
 $ diagnosis              : chr  "M" "M" "M" "M" ...
 $ radius_mean            : num  18 20.6 19.7 11.4 20.3 ...
 $ texture_mean           : num  10.4 17.8 21.2 20.4 14.3 ...
 $ perimeter_mean         : num  122.8 132.9 130 77.6 135.1 ...
 $ area_mean              : num  1001 1326 1203 386 1297 ...
 $ smoothness_mean        : num  0.1184 0.0847 0.1096 0.1425 0.1003 ...
 $ compactness_mean       : num  0.2776 0.0786 0.1599 0.2839 0.1328 ...
 $ concavity_mean         : num  0.3001 0.0869 0.1974 0.2414 0.198 ...
 $ concave.points_mean    : num  0.1471 0.0702 0.1279 0.1052 0.1043 ...
 $ symmetry_mean          : num  0.242 0.181 0.207 0.26 0.181 ...
 $ fractal_dimension_mean : num  0.0787 0.0567 0.06 0.0974 0.0588 ...
 $ radius_se              : num  1.095 0.543 0.746 0.496 0.757 ...
 $ texture_se             : num  0.905 0.734 0.787 1.156 0.781 ...
 $ perimeter_se           : num  8.59 3.4 4.58 3.44 5.44 ...
 $ area_se                : num  153.4 74.1 94 27.2 94.4 ...
 $ smoothness_se          : num  0.0064 0.00522 0.00615 0.00911 0.01149 ...
 $ compactness_se         : num  0.049 0.0131 0.0401 0.0746 0.0246 ...
 $ concavity_se           : num  0.0537 0.0186 0.0383 0.0566 0.0569 ...
 $ concave.points_se      : num  0.0159 0.0134 0.0206 0.0187 0.0188 ...
 $ symmetry_se            : num  0.03 0.0139 0.0225 0.0596 0.0176 ...
 $ fractal_dimension_se   : num  0.00619 0.00353 0.00457 0.00921 0.00511 ...
 $ radius_worst           : num  25.4 25 23.6 14.9 22.5 ...
 $ texture_worst          : num  17.3 23.4 25.5 26.5 16.7 ...
 $ perimeter_worst        : num  184.6 158.8 152.5 98.9 152.2 ...
 $ area_worst             : num  2019 1956 1709 568 1575 ...
 $ smoothness_worst       : num  0.162 0.124 0.144 0.21 0.137 ...
 $ compactness_worst      : num  0.666 0.187 0.424 0.866 0.205 ...
 $ concavity_worst        : num  0.712 0.242 0.45 0.687 0.4 ...
 $ concave.points_worst   : num  0.265 0.186 0.243 0.258 0.163 ...
 $ symmetry_worst         : num  0.46 0.275 0.361 0.664 0.236 ...
 $ fractal_dimension_worst: num  0.1189 0.089 0.0876 0.173 0.0768 ...
 $ X                      : logi  NA NA NA NA NA NA ...
Summary
# Summary of the dataset
summary(df)
       id             diagnosis          radius_mean      texture_mean   perimeter_mean  
 Min.   :     8670   Length:569         Min.   : 6.981   Min.   : 9.71   Min.   : 43.79  
 1st Qu.:   869218   Class :character   1st Qu.:11.700   1st Qu.:16.17   1st Qu.: 75.17  
 Median :   906024   Mode  :character   Median :13.370   Median :18.84   Median : 86.24  
 Mean   : 30371831                      Mean   :14.127   Mean   :19.29   Mean   : 91.97  
 3rd Qu.:  8813129                      3rd Qu.:15.780   3rd Qu.:21.80   3rd Qu.:104.10  
 Max.   :911320502                      Max.   :28.110   Max.   :39.28   Max.   :188.50  
   area_mean      smoothness_mean   compactness_mean  concavity_mean    concave.points_mean
 Min.   : 143.5   Min.   :0.05263   Min.   :0.01938   Min.   :0.00000   Min.   :0.00000    
 1st Qu.: 420.3   1st Qu.:0.08637   1st Qu.:0.06492   1st Qu.:0.02956   1st Qu.:0.02031    
 Median : 551.1   Median :0.09587   Median :0.09263   Median :0.06154   Median :0.03350    
 Mean   : 654.9   Mean   :0.09636   Mean   :0.10434   Mean   :0.08880   Mean   :0.04892    
 3rd Qu.: 782.7   3rd Qu.:0.10530   3rd Qu.:0.13040   3rd Qu.:0.13070   3rd Qu.:0.07400    
 Max.   :2501.0   Max.   :0.16340   Max.   :0.34540   Max.   :0.42680   Max.   :0.20120    
 symmetry_mean    fractal_dimension_mean   radius_se        texture_se      perimeter_se   
 Min.   :0.1060   Min.   :0.04996        Min.   :0.1115   Min.   :0.3602   Min.   : 0.757  
 1st Qu.:0.1619   1st Qu.:0.05770        1st Qu.:0.2324   1st Qu.:0.8339   1st Qu.: 1.606  
 Median :0.1792   Median :0.06154        Median :0.3242   Median :1.1080   Median : 2.287  
 Mean   :0.1812   Mean   :0.06280        Mean   :0.4052   Mean   :1.2169   Mean   : 2.866  
 3rd Qu.:0.1957   3rd Qu.:0.06612        3rd Qu.:0.4789   3rd Qu.:1.4740   3rd Qu.: 3.357  
 Max.   :0.3040   Max.   :0.09744        Max.   :2.8730   Max.   :4.8850   Max.   :21.980  
    area_se        smoothness_se      compactness_se      concavity_se     concave.points_se 
 Min.   :  6.802   Min.   :0.001713   Min.   :0.002252   Min.   :0.00000   Min.   :0.000000  
 1st Qu.: 17.850   1st Qu.:0.005169   1st Qu.:0.013080   1st Qu.:0.01509   1st Qu.:0.007638  
 Median : 24.530   Median :0.006380   Median :0.020450   Median :0.02589   Median :0.010930  
 Mean   : 40.337   Mean   :0.007041   Mean   :0.025478   Mean   :0.03189   Mean   :0.011796  
 3rd Qu.: 45.190   3rd Qu.:0.008146   3rd Qu.:0.032450   3rd Qu.:0.04205   3rd Qu.:0.014710  
 Max.   :542.200   Max.   :0.031130   Max.   :0.135400   Max.   :0.39600   Max.   :0.052790  
  symmetry_se       fractal_dimension_se  radius_worst   texture_worst   perimeter_worst 
 Min.   :0.007882   Min.   :0.0008948    Min.   : 7.93   Min.   :12.02   Min.   : 50.41  
 1st Qu.:0.015160   1st Qu.:0.0022480    1st Qu.:13.01   1st Qu.:21.08   1st Qu.: 84.11  
 Median :0.018730   Median :0.0031870    Median :14.97   Median :25.41   Median : 97.66  
 Mean   :0.020542   Mean   :0.0037949    Mean   :16.27   Mean   :25.68   Mean   :107.26  
 3rd Qu.:0.023480   3rd Qu.:0.0045580    3rd Qu.:18.79   3rd Qu.:29.72   3rd Qu.:125.40  
 Max.   :0.078950   Max.   :0.0298400    Max.   :36.04   Max.   :49.54   Max.   :251.20  
   area_worst     smoothness_worst  compactness_worst concavity_worst  concave.points_worst
 Min.   : 185.2   Min.   :0.07117   Min.   :0.02729   Min.   :0.0000   Min.   :0.00000     
 1st Qu.: 515.3   1st Qu.:0.11660   1st Qu.:0.14720   1st Qu.:0.1145   1st Qu.:0.06493     
 Median : 686.5   Median :0.13130   Median :0.21190   Median :0.2267   Median :0.09993     
 Mean   : 880.6   Mean   :0.13237   Mean   :0.25427   Mean   :0.2722   Mean   :0.11461     
 3rd Qu.:1084.0   3rd Qu.:0.14600   3rd Qu.:0.33910   3rd Qu.:0.3829   3rd Qu.:0.16140     
 Max.   :4254.0   Max.   :0.22260   Max.   :1.05800   Max.   :1.2520   Max.   :0.29100     
 symmetry_worst   fractal_dimension_worst    X          
 Min.   :0.1565   Min.   :0.05504         Mode:logical  
 1st Qu.:0.2504   1st Qu.:0.07146         NA's:569      
 Median :0.2822   Median :0.08004                       
 Mean   :0.2901   Mean   :0.08395                       
 3rd Qu.:0.3179   3rd Qu.:0.09208                       
 Max.   :0.6638   Max.   :0.20750                       

Process Data

We will clean up the dataset and then split the dataset into 80% train set and 20% test set.

# Delete the first column from dataset as id won't be used, and delete last column from dataset as its entries are all NA's
df <- df[,-c(1,33)]
# Convert the diagnosis attribute
df$diagnosis <- factor(df$diagnosis)
df$diagnosis <- as.integer(df$diagnosis)-1   # M=1 B=0
# Split entire data into 80% train set and 20% test set
set.seed(123)
index <- sample(1:nrow(df),0.8*nrow(df))
df.train <- df[index,]
df.test <- df[-index,]
# Check proportion of diagnosis (Benign/Malignant) in train/test sets
#prop.table(table(df.train$diagnosis))
#prop.table(table(df.test$diagnosis))

Step 2: Feature Selection

30 features are grouped into 14 groups based on their correlation, and one feature (the italicized feature) from each group is selected based on their importance. Then using recursive feature elimination with 5-fold cross validation, we have found that a subset of 11 features (the bolded) for best accuracy.
a) texture_mean, texture_worst
b) area_se, radius_se, perimeter_se
c) area_mean, radius_mean, perimeter_mean, area_worst, radius_worst, perimeter_worst
d) concave.points_worst, concavity_mean, concave.points_mean e) compactness_mean, compactness_worst, concavity_worst
f) compactness_se, fractal_dimension_se
g) concavity_se, concave.points_se
h) texture_se
i) smoothness_se
j) smoothness_mean, smoothness_worst
k) fractal_dimension_mean, fractal_dimension_worst
l) symmetry_se
m) symmetry_mean
n) symmetry_worst

#library(corrplot)
# Compute and plot corrlation matrix
corr_mat <- cor(df.train[,2:ncol(df)])
corrplot(corr_mat, method = "square", order = "hclust",
         # adjust the color, size and rotation degree of the text label
         tl.col = "black", tl.cex = 0.6, tl.srt = 45, 
         # adjust the color, format, size of the corrlation display
         addCoef.col = "black", addCoefasPercent = TRUE, number.cex=0.45,
         addrect = 14)

# Compute importance of each feature
control <- trainControl(method = "repeatedcv", number = 5, repeats = 3)
model <- train(factor(diagnosis)~., data=df.train, method="rf", preProcess="scale", trControl=control)
importance <- varImp(model, scale=FALSE)
plot(importance)

# define selected features
feature_selected <- c("texture_worst", "area_se", "perimeter_worst", 
                      "concave.points_worst", "concavity_worst", "fractal_dimension_se",
                      "concavity_se", "texture_se", "smoothness_se", 
                      "smoothness_worst", "fractal_dimension_worst", "symmetry_se", 
                      "symmetry_mean", "symmetry_worst")
df.train2 <- df.train[,c("diagnosis",feature_selected)]
#library(caret)
#library(randomForest)
control <- rfeControl(functions=rfFuncs, method="cv", number=5)
results <- rfe(df.train2[,-1],factor(df.train2[,1]),size=c(1:14),rfeControl=control)
predictors(results)
[1] "perimeter_worst"      "concave.points_worst" "area_se"              "concavity_worst"     
[5] "texture_worst"        "smoothness_worst"     "symmetry_worst"       "concavity_se"        
#plot(results, type=c("g", "o"))
# redefine selected features 
feature_selected <- c("perimeter_worst", "concave.points_worst", "area_se",
                      "concavity_worst", "texture_worst", "smoothness_worst",
                      "symmetry_worst", "concavity_se", "fractal_dimension_worst", 
                      "symmetry_mean", "fractal_dimension_se")
df.train2 <- df.train2[,c("diagnosis",feature_selected)]
df.test2 <- df.test[,c("diagnosis",feature_selected)]

Step 3: Implement Algorithm

algorithms

We have impletemented six different classification methods on both the full set of features and reduced set of features.

Random Forest
run.rf <- FALSE
source("../lib/rf.R")
if(run.rf){
  output_rf <- RF(df.train,df.test)
  output2_rf <- RF(df.train2,df.test2)
  
  save(output_rf,file = "../output/output_rf.RData")
  save(output2_rf,file = "../output/output2_rf.RData")
}else{
  load("../output/output_rf.RData")
  load("../output/output2_rf.RData")
}
Logistic Regression
run.logi <- FALSE
source("../lib/logi.R")
if(run.logi){
  output_logi <- logi(df.train, df.test)
  output2_logi <- logi(df.train2, df.test2)
  
  save(output_logi, file = "../output/output_logi.RData")
  save(output2_logi, file = "../output/output2_logi.RData")
}else{
  load("../output/output_logi.RData")
  load("../output/output2_logi.RData")
}
GBM
run.gbm <- FALSE
source("../lib/gbmp.r")
if(run.gbm){
  output_gbm <- gbmp(df.train, df.test)
  output2_gbm <- gbmp(df.train2, df.test2)
  
  save(output_gbm, file = "../output/output_gbm.RData")
  save(output2_gbm, file = "../output/output2_gbm.RData")
}else{
  load("../output/output_gbm.RData")
  load("../output/output2_gbm.RData")
}
XGBoost
run.xg <- FALSE
source("../lib/xgboost.r")
if(run.xg){
  output_xg <- xgb(df.train,df.test)
  output2_xg <- xgb(df.train2,df.test2)
  
  save(output_xg,file = "../output/output_xg.RData")
  save(output2_xg,file = "../output/output2_xg.RData")
}else{
  load("../output/output_xg.RData")
  load("../output/output2_xg.RData")
}
AdaBoost
run.ada <- FALSE
source("../lib/adaboost.r")
if(run.ada){
  output_ada <- adaboost(df.train,df.test)
  output2_ada <- adaboost(df.train2,df.test2)
  
  save(output_ada,file = "../output/output_ada.RData")
  save(output2_ada,file = "../output/output2_ada.RData")
}else{
  load("../output/output_ada.RData")
  load("../output/output2_ada.RData")
}
SVM
run.svm <- FALSE
source("../lib/svm.R")
if(run.svm){
  output_svm <- SVM(df.train,df.test)
  output2_svm <- SVM(df.train2,df.test2)
  
  save(output_svm,file = "../output/output_svm.RData")
  save(output2_svm,file = "../output/output2_svm.RData")
}else{
  load("../output/output_svm.RData")
  load("../output/output2_svm.RData")
}

Step 4: Evaluation

We have compared the prediction accuracy and running time among the six classification methods with the two different sets of features, and have found that two best models are SVM with all features (100% prediction accuracy and 0.07s training time) and Logistic Regression with reduced features (98% prediction accuracy and 0.03s training time).

Accuracy

# compute confusion matrix
cm_rf <- confusionMatrix(output_rf$prediction,df.test$diagnosis)
cm_rf2 <- confusionMatrix(output2_rf$prediction,df.test$diagnosis)
cm_logi <- confusionMatrix(output_logi$prediction,df.test$diagnosis)
cm_logi2 <- confusionMatrix(output2_logi$prediction,df.test$diagnosis)
cm_gbm <- confusionMatrix(output_gbm$prediction,df.test$diagnosis)
cm_gbm2 <- confusionMatrix(output2_gbm$prediction,df.test$diagnosis)
cm_xg <- confusionMatrix(output_xg$prediction,df.test$diagnosis)
cm_xg2 <- confusionMatrix(output2_xg$prediction,df.test$diagnosis)
cm_ada <- confusionMatrix(output_ada$prediction,df.test$diagnosis)
cm_ada2 <- confusionMatrix(output2_ada$prediction,df.test$diagnosis)
cm_svm <- confusionMatrix(output_svm$prediction,df.test$diagnosis)
cm_svm2 <- confusionMatrix(output2_svm$prediction,df.test$diagnosis)
# compare prediction accuracy 
accuracy <- c(cm_rf$overall[1],cm_logi$overall[1],cm_gbm$overall[1],
              cm_xg$overall[1],cm_ada$overall[1],cm_svm$overall[1],
              cm_rf2$overall[1],cm_logi2$overall[1],cm_gbm2$overall[1],
              cm_xg2$overall[1],cm_ada2$overall[1],cm_svm2$overall[1])
accuracy_comparison <- matrix(accuracy, nrow = 2, byrow = TRUE,
                              dimnames = list(c("all_features","reduced_features"),
                                              c("RandomForest","Logistic Regression","GBM",
                                                 "XGBoost","AdaBoost","SVM")))
round(accuracy_comparison,4)
                 RandomForest Logistic Regression    GBM XGBoost AdaBoost    SVM
all_features           0.9737              0.9825 0.9825  0.9737   0.9825 1.0000
reduced_features       0.9561              0.9825 0.9561  0.9649   0.9737 0.9561

Running Time

# compare time
time <- c(output_rf$time,output_logi$time,output_gbm$time,output_xg$time,output_ada$time,output_svm$time,
          output2_rf$time,output2_logi$time,output2_gbm$time,output2_xg$time,output2_ada$time,output2_svm$time)
time_comparison <- matrix(time, nrow=2, byrow = TRUE,
                          dimnames = list(c("all_features","reduced_features"),
                                          c("RandomForest","Logistic Regression","GBM",
                                            "XGBoost","AdaBoost","SVM")))
round(time_comparison,2)
                 RandomForest Logistic Regression  GBM XGBoost AdaBoost  SVM
all_features             0.71                0.08 3.62   11.72     3.36 0.07
reduced_features         0.38                0.03 2.21    6.17     1.46 0.03

Confustion Matrix of Best Models

par(mfrow=c(1,2))
fourfoldplot(cm_logi2$table, conf.level = 0, margin = 1,
             main = paste0("Logistic Regression (", round(cm_logi2$overall[1]*100), "%)"))
fourfoldplot(cm_svm$table, conf.level = 0, margin = 1,
             main = paste0("SVM (", round(cm_svm$overall[1]*100), "%)"))

LS0tDQp0aXRsZTogIkFEQSBQcm9qZWN0IDUgLSBCcmVhc3QgQ2FuY2VyIENsYXNzaWZpY2F0aW9uIg0KYXV0aG9yOiAiR3JvdXAgMiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMjIFRhYmxlIG9mIENvbnRlbnRzICANCltJbnRyb2R1Y3Rpb25dKCNJbnRybykgIA0KW1N0ZXAgMDogTG9hZCBQYWNrYWdlcyAmIFNwZWNpZnkgRGlyZWN0b3JpZXNdKCNTdGVwMCkgIA0KW1N0ZXAgMTogTG9hZCBhbmQgUHJvY2VzcyBEYXRhXSgjU3RlcDEpICANCltTdGVwIDI6IEZlYXR1cmUgU2VsZWN0aW9uXSgjU3RlcDIpICANCltTdGVwIDM6IEltcGxlbWVudCBBbGdvcml0aG1dKCNTdGVwMykgIA0KW1N0ZXAgNDogRXZhbHVhdGlvbl0oI1N0ZXA0KSAgDQoNCiMjIyBJbnRyb2R1Y3Rpb24geyNJbnRyb30gICANClRoaXMgbm90ZWJvb2sgd2lsbCBpbXBsZW1lbnQgYW5kIGV4YW1pbmUgZGlmZmVyZW50IGNsYXNzaWZpY2F0aW9uIG1ldGhvZHMgb24gdGhlIFtCcmVhc3QgQ2FuY2VyIFdpc2NvbnNpbiAoRGlhZ25vc3RpYykgRGF0YSBTZXRdWzFdIHRvIGNsYXNzaWZ5IHdoZXRoZXIgdGhlIGJyZWFzdCBjYW5jZXIgaXMgbWFsaWduYW50IG9yIGJlbmlnbiBiYXNlZCBvbiB0aGUgZmVhdHVyZXMgY29tcHV0ZWQgZnJvbSBhIGRpZ2l0aXplZCBpbWFnZSBvZiBhIGZpbmUgbmVlZGxlIGFzcGlyYXRlIChGTkEpIG9mIGEgYnJlYXN0IG1hc3MuIA0KDQpbMV06IGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9kYXRhc2V0cy9CcmVhc3QrQ2FuY2VyK1dpc2NvbnNpbislMjhEaWFnbm9zdGljJTI5DQoNCiMjIyBTdGVwIDA6IExvYWQgUGFja2FnZXMgJiBTcGVjaWZ5IERpcmVjdG9yaWVzIHsjU3RlcDB9ICANCmBgYHtyfQ0KIyBQYWNrYWdlcyB0aGF0IHdpbGwgYmUgdXNlZA0KcGFja2FnZXMudXNlZCA8LSBjKCJjb3JycGxvdCIsICJjYXJldCIsICJyYW5kb21Gb3Jlc3QiLCAiZTEwNzEiLCAiZHBseXIiLCAiZ2JtIikNCiMgQ2hlY2sgcGFja2FnZXMgdGhhdCBuZWVkIHRvIGJlIGluc3RhbGxlZA0KcGFja2FnZXMubmVlZGVkIDwtIHNldGRpZmYocGFja2FnZXMudXNlZCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBpbnRlcnNlY3QoaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYWNrYWdlcy51c2VkKSkNCiMgSW5zdGFsbCBhZGRpdGlvbmFsIHBhY2thZ2VzDQppZihsZW5ndGgocGFja2FnZXMubmVlZGVkKT4wKXsNCiAgaW5zdGFsbC5wYWNrYWdlcyhwYWNrYWdlcy5uZWVkZWQsIGRlcGVuZGVuY2llcyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgcmVwb3M9J2h0dHA6Ly9jcmFuLnVzLnItcHJvamVjdC5vcmcnKQ0KfQ0KIyBMb2FkIGxpYnJhcmllcyAgDQpsaWJyYXJ5KCJjb3JycGxvdCIpDQpsaWJyYXJ5KCJjYXJldCIpDQpsaWJyYXJ5KCJyYW5kb21Gb3Jlc3QiKQ0KbGlicmFyeSgiZTEwNzEiKQ0KbGlicmFyeSgiZHBseXIiKQ0KbGlicmFyeSgiZ2JtIikNCiMgU2V0IHdvcmtpbmcgZGlyZWN0b3J5IHRvIHRoZSBkb2MgZm9sZGVyIA0Kc2V0d2QoIn4vR2l0SHViL1NwcmluZzIwMTgtUHJvamVjdDUtZ3JwXzIvZG9jIikNCmBgYA0KDQojIyMgU3RlcCAxOiBMb2FkIGFuZCBQcm9jZXNzIERhdGEgeyNTdGVwMX0gIA0KIyMjIyBMb2FkIERhdGEgIA0KYGBge3J9DQojIExvYWQgZGF0YQ0KZGYgPC0gcmVhZC5jc3YoIi4uL2RhdGEvZGF0YS5jc3YiLCBoZWFkZXIgPSBUUlVFLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQpgYGANCg0KIyMjIyBFeHBsb3JlIERhdGEgey50YWJzZXR9DQojIyMjIyBIZWFkICANCmBgYHtyfQ0KIyBQcmludCB0aGUgaGVhZCBvZiBkYXRhDQpoZWFkKGRmKQ0KYGBgDQojIyMjIyBTdHJ1Y3R1cmUgIA0KVGhlIGRhdGFzZXQgaW5jbHVkZXMgNTY5IG9ic2VydmF0aW9ucyBvZiAzMyB2YXJpYWJsZXMgZGVzY3JpYmVkIGFzIGJlbG93LiAgDQooMSkgSUQgbnVtYmVyICANCigyKSBEaWFnbm9zaXMgKE0gPSBtYWxpZ25hbnQsIEIgPSBiZW5pZ24pICAgDQooMyktKDMyKSBUZW4gcmVhbC12YWx1ZWQgZmVhdHVyZXMgZGVzY3JpYmVkIGFzIGZvbGxvd3MgYXJlIGNvbXB1dGVkIGZvciBlYWNoIGNlbGwgbnVjbGV1czogIA0KICAgIGEpIHJhZGl1cyAobWVhbiBvZiBkaXN0YW5jZXMgZnJvbSBjZW50ZXIgdG8gcG9pbnRzIG9uIHRoZSBwZXJpbWV0ZXIpICANCiAgICBiKSB0ZXh0dXJlIChzdGFuZGFyZCBkZXZpYXRpb24gb2YgZ3JheS1zY2FsZSB2YWx1ZXMpICANCiAgICBjKSBwZXJpbWV0ZXIgIA0KICAgIGQpIGFyZWEgIA0KICAgIGUpIHNtb290aG5lc3MgKGxvY2FsIHZhcmlhdGlvbiBpbiByYWRpdXMgbGVuZ3RocykgIA0KICAgIGYpIGNvbXBhY3RuZXNzIChwZXJpbWV0ZXJeMiAvIGFyZWEgLSAxLjApICANCiAgICBnKSBjb25jYXZpdHkgKHNldmVyaXR5IG9mIGNvbmNhdmUgcG9ydGlvbnMgb2YgdGhlIGNvbnRvdXIpICANCiAgICBoKSBjb25jYXZlIHBvaW50cyAobnVtYmVyIG9mIGNvbmNhdmUgcG9ydGlvbnMgb2YgdGhlIGNvbnRvdXIpICAgDQogICAgaSkgc3ltbWV0cnkgICANCiAgICBqKSBmcmFjdGFsIGRpbWVuc2lvbiAoImNvYXN0bGluZSBhcHByb3hpbWF0aW9uIiAtIDEpICAgDQoNClRoZSBtZWFuLCBzdGFuZGFyZCBlcnJvciBhbmQgIndvcnN0IiBvciBsYXJnZXN0IChtZWFuIG9mIHRoZSB0aHJlZSBsYXJnZXN0IHZhbHVlcykgb2YgdGhlc2UgZmVhdHVyZXMgICB3ZXJlIGNvbXB1dGVkIGZvciBlYWNoIGltYWdlLCByZXN1bHRpbmcgaW4gMzAgZmVhdHVyZXMuIEZvciBpbnN0YW5jZSwgZmllbGQgMyBpcyBNZWFuIFJhZGl1cywgZmllbGQgMTMgaXMgUmFkaXVzIFNFLCBmaWVsZCAyMyBpcyBXb3JzdCBSYWRpdXMuICAgDQooMzMpIEFsbCBlbnRyaWVzIGFyZSBOQSdzICAgDQoNCmBgYHtyfQ0KIyBTdHJ1Y3V0cmUgb2YgdGhlIGRhdGFzZXQNCnN0cihkZikNCmBgYA0KIyMjIyMgU3VtbWFyeQ0KYGBge3J9DQojIFN1bW1hcnkgb2YgdGhlIGRhdGFzZXQNCnN1bW1hcnkoZGYpDQpgYGANCg0KIyMjIyBQcm9jZXNzIERhdGEgIA0KV2Ugd2lsbCBjbGVhbiB1cCB0aGUgZGF0YXNldCBhbmQgdGhlbiBzcGxpdCB0aGUgZGF0YXNldCBpbnRvIDgwJSB0cmFpbiBzZXQgYW5kIDIwJSB0ZXN0IHNldC4gDQpgYGB7cn0NCiMgRGVsZXRlIHRoZSBmaXJzdCBjb2x1bW4gZnJvbSBkYXRhc2V0IGFzIGlkIHdvbid0IGJlIHVzZWQsIGFuZCBkZWxldGUgbGFzdCBjb2x1bW4gZnJvbSBkYXRhc2V0IGFzIGl0cyBlbnRyaWVzIGFyZSBhbGwgTkEncw0KZGYgPC0gZGZbLC1jKDEsMzMpXQ0KDQojIENvbnZlcnQgdGhlIGRpYWdub3NpcyBhdHRyaWJ1dGUNCmRmJGRpYWdub3NpcyA8LSBmYWN0b3IoZGYkZGlhZ25vc2lzKQ0KZGYkZGlhZ25vc2lzIDwtIGFzLmludGVnZXIoZGYkZGlhZ25vc2lzKS0xICAgIyBNPTEgQj0wDQoNCiMgU3BsaXQgZW50aXJlIGRhdGEgaW50byA4MCUgdHJhaW4gc2V0IGFuZCAyMCUgdGVzdCBzZXQNCnNldC5zZWVkKDEyMykNCmluZGV4IDwtIHNhbXBsZSgxOm5yb3coZGYpLDAuOCpucm93KGRmKSkNCmRmLnRyYWluIDwtIGRmW2luZGV4LF0NCmRmLnRlc3QgPC0gZGZbLWluZGV4LF0NCg0KIyBDaGVjayBwcm9wb3J0aW9uIG9mIGRpYWdub3NpcyAoQmVuaWduL01hbGlnbmFudCkgaW4gdHJhaW4vdGVzdCBzZXRzDQojcHJvcC50YWJsZSh0YWJsZShkZi50cmFpbiRkaWFnbm9zaXMpKQ0KI3Byb3AudGFibGUodGFibGUoZGYudGVzdCRkaWFnbm9zaXMpKQ0KYGBgDQojIyMgU3RlcCAyOiBGZWF0dXJlIFNlbGVjdGlvbiB7I1N0ZXAyfSAgDQozMCBmZWF0dXJlcyBhcmUgZ3JvdXBlZCBpbnRvIDE0IGdyb3VwcyBiYXNlZCBvbiB0aGVpciBjb3JyZWxhdGlvbiwgYW5kIG9uZSBmZWF0dXJlICh0aGUgaXRhbGljaXplZCBmZWF0dXJlKSBmcm9tIGVhY2ggZ3JvdXAgaXMgc2VsZWN0ZWQgYmFzZWQgb24gdGhlaXIgaW1wb3J0YW5jZS4gVGhlbiB1c2luZyByZWN1cnNpdmUgZmVhdHVyZSBlbGltaW5hdGlvbiB3aXRoIDUtZm9sZCBjcm9zcyB2YWxpZGF0aW9uLCB3ZSBoYXZlIGZvdW5kIHRoYXQgYSBzdWJzZXQgb2YgMTEgZmVhdHVyZXMgKHRoZSBib2xkZWQpIGZvciBiZXN0IGFjY3VyYWN5LiAgDQphKSB0ZXh0dXJlX21lYW4sICoqKnRleHR1cmVfd29yc3QqKiogIA0KYikgKioqYXJlYV9zZSoqKiwgcmFkaXVzX3NlLCBwZXJpbWV0ZXJfc2UgIA0KYykgYXJlYV9tZWFuLCByYWRpdXNfbWVhbiwgcGVyaW1ldGVyX21lYW4sIGFyZWFfd29yc3QsIHJhZGl1c193b3JzdCwgKioqcGVyaW1ldGVyX3dvcnN0KioqICANCmQpICoqKmNvbmNhdmUucG9pbnRzX3dvcnN0KioqLCBjb25jYXZpdHlfbWVhbiwgY29uY2F2ZS5wb2ludHNfbWVhbg0KZSkgY29tcGFjdG5lc3NfbWVhbiwgY29tcGFjdG5lc3Nfd29yc3QsICoqKmNvbmNhdml0eV93b3JzdCoqKiAgIA0KZikgY29tcGFjdG5lc3Nfc2UsICoqKmZyYWN0YWxfZGltZW5zaW9uX3NlKioqICANCmcpICoqKmNvbmNhdml0eV9zZSoqKiwgY29uY2F2ZS5wb2ludHNfc2UgIA0KaCkgKnRleHR1cmVfc2UqICANCmkpICpzbW9vdGhuZXNzX3NlKiAgDQpqKSBzbW9vdGhuZXNzX21lYW4sICoqKnNtb290aG5lc3Nfd29yc3QqKiogIA0KaykgZnJhY3RhbF9kaW1lbnNpb25fbWVhbiwgKioqZnJhY3RhbF9kaW1lbnNpb25fd29yc3QqKiogIA0KbCkgKnN5bW1ldHJ5X3NlKiAgDQptKSAqKipzeW1tZXRyeV9tZWFuKioqICAgDQpuKSAqKipzeW1tZXRyeV93b3JzdCoqKiANCg0KYGBge3J9DQojbGlicmFyeShjb3JycGxvdCkNCiMgQ29tcHV0ZSBhbmQgcGxvdCBjb3JybGF0aW9uIG1hdHJpeA0KY29ycl9tYXQgPC0gY29yKGRmLnRyYWluWywyOm5jb2woZGYpXSkNCmNvcnJwbG90KGNvcnJfbWF0LCBtZXRob2QgPSAic3F1YXJlIiwgb3JkZXIgPSAiaGNsdXN0IiwNCiAgICAgICAgICMgYWRqdXN0IHRoZSBjb2xvciwgc2l6ZSBhbmQgcm90YXRpb24gZGVncmVlIG9mIHRoZSB0ZXh0IGxhYmVsDQogICAgICAgICB0bC5jb2wgPSAiYmxhY2siLCB0bC5jZXggPSAwLjYsIHRsLnNydCA9IDQ1LCANCiAgICAgICAgICMgYWRqdXN0IHRoZSBjb2xvciwgZm9ybWF0LCBzaXplIG9mIHRoZSBjb3JybGF0aW9uIGRpc3BsYXkNCiAgICAgICAgIGFkZENvZWYuY29sID0gImJsYWNrIiwgYWRkQ29lZmFzUGVyY2VudCA9IFRSVUUsIG51bWJlci5jZXg9MC40NSwNCiAgICAgICAgIGFkZHJlY3QgPSAxNCkNCg0KYGBgDQpgYGB7cn0NCiMgQ29tcHV0ZSBpbXBvcnRhbmNlIG9mIGVhY2ggZmVhdHVyZQ0KY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCBudW1iZXIgPSA1LCByZXBlYXRzID0gMykNCm1vZGVsIDwtIHRyYWluKGZhY3RvcihkaWFnbm9zaXMpfi4sIGRhdGE9ZGYudHJhaW4sIG1ldGhvZD0icmYiLCBwcmVQcm9jZXNzPSJzY2FsZSIsIHRyQ29udHJvbD1jb250cm9sKQ0KaW1wb3J0YW5jZSA8LSB2YXJJbXAobW9kZWwsIHNjYWxlPUZBTFNFKQ0KcGxvdChpbXBvcnRhbmNlKQ0KYGBgDQoNCmBgYHtyfQ0KIyBkZWZpbmUgc2VsZWN0ZWQgZmVhdHVyZXMNCmZlYXR1cmVfc2VsZWN0ZWQgPC0gYygidGV4dHVyZV93b3JzdCIsICJhcmVhX3NlIiwgInBlcmltZXRlcl93b3JzdCIsIA0KICAgICAgICAgICAgICAgICAgICAgICJjb25jYXZlLnBvaW50c193b3JzdCIsICJjb25jYXZpdHlfd29yc3QiLCAiZnJhY3RhbF9kaW1lbnNpb25fc2UiLA0KICAgICAgICAgICAgICAgICAgICAgICJjb25jYXZpdHlfc2UiLCAidGV4dHVyZV9zZSIsICJzbW9vdGhuZXNzX3NlIiwgDQogICAgICAgICAgICAgICAgICAgICAgInNtb290aG5lc3Nfd29yc3QiLCAiZnJhY3RhbF9kaW1lbnNpb25fd29yc3QiLCAic3ltbWV0cnlfc2UiLCANCiAgICAgICAgICAgICAgICAgICAgICAic3ltbWV0cnlfbWVhbiIsICJzeW1tZXRyeV93b3JzdCIpDQpkZi50cmFpbjIgPC0gZGYudHJhaW5bLGMoImRpYWdub3NpcyIsZmVhdHVyZV9zZWxlY3RlZCldDQpgYGANCiANCmBgYHtyfQ0KIyByZWN1cnNpdmUgZmVhdHVyZSBlbGltaW5hdGlvbiB0byBmaW5kIGEgc3Vic2V0IG9mIGZlYXR1cmVzIGZvciBiZXN0IGFjY3VyYWN5DQojbGlicmFyeShjYXJldCkNCiNsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmNvbnRyb2wgPC0gcmZlQ29udHJvbChmdW5jdGlvbnM9cmZGdW5jcywgbWV0aG9kPSJjdiIsIG51bWJlcj01KQ0KcmVzdWx0cyA8LSByZmUoZGYudHJhaW4yWywtMV0sZmFjdG9yKGRmLnRyYWluMlssMV0pLHNpemU9YygxOjE0KSxyZmVDb250cm9sPWNvbnRyb2wpDQpwcmVkaWN0b3JzKHJlc3VsdHMpDQojcGxvdChyZXN1bHRzLCB0eXBlPWMoImciLCAibyIpKQ0KYGBgDQogICAgICAgICAgICAgIA0KYGBge3J9DQojIHJlZGVmaW5lIHNlbGVjdGVkIGZlYXR1cmVzIA0KZmVhdHVyZV9zZWxlY3RlZCA8LSBjKCJwZXJpbWV0ZXJfd29yc3QiLCAiY29uY2F2ZS5wb2ludHNfd29yc3QiLCAiYXJlYV9zZSIsDQogICAgICAgICAgICAgICAgICAgICAgImNvbmNhdml0eV93b3JzdCIsICJ0ZXh0dXJlX3dvcnN0IiwgInNtb290aG5lc3Nfd29yc3QiLA0KICAgICAgICAgICAgICAgICAgICAgICJzeW1tZXRyeV93b3JzdCIsICJjb25jYXZpdHlfc2UiLCAiZnJhY3RhbF9kaW1lbnNpb25fd29yc3QiLCANCiAgICAgICAgICAgICAgICAgICAgICAic3ltbWV0cnlfbWVhbiIsICJmcmFjdGFsX2RpbWVuc2lvbl9zZSIpDQpkZi50cmFpbjIgPC0gZGYudHJhaW4yWyxjKCJkaWFnbm9zaXMiLGZlYXR1cmVfc2VsZWN0ZWQpXQ0KZGYudGVzdDIgPC0gZGYudGVzdFssYygiZGlhZ25vc2lzIixmZWF0dXJlX3NlbGVjdGVkKV0NCmBgYA0KDQojIyMgU3RlcCAzOiBJbXBsZW1lbnQgQWxnb3JpdGhtICB7I1N0ZXAzfSAgDQojIyMjIGFsZ29yaXRobXMgey50YWJzZXR9ICANCldlIGhhdmUgaW1wbGV0ZW1lbnRlZCBzaXggZGlmZmVyZW50IGNsYXNzaWZpY2F0aW9uIG1ldGhvZHMgb24gYm90aCB0aGUgZnVsbCBzZXQgb2YgZmVhdHVyZXMgYW5kIHJlZHVjZWQgc2V0IG9mIGZlYXR1cmVzLiAgIA0KDQojIyMjIyBSYW5kb20gRm9yZXN0ICANCmBgYHtyfQ0KcnVuLnJmIDwtIEZBTFNFDQpzb3VyY2UoIi4uL2xpYi9yZi5SIikNCg0KaWYocnVuLnJmKXsNCiAgb3V0cHV0X3JmIDwtIFJGKGRmLnRyYWluLGRmLnRlc3QpDQogIG91dHB1dDJfcmYgPC0gUkYoZGYudHJhaW4yLGRmLnRlc3QyKQ0KICANCiAgc2F2ZShvdXRwdXRfcmYsZmlsZSA9ICIuLi9vdXRwdXQvb3V0cHV0X3JmLlJEYXRhIikNCiAgc2F2ZShvdXRwdXQyX3JmLGZpbGUgPSAiLi4vb3V0cHV0L291dHB1dDJfcmYuUkRhdGEiKQ0KfWVsc2V7DQogIGxvYWQoIi4uL291dHB1dC9vdXRwdXRfcmYuUkRhdGEiKQ0KICBsb2FkKCIuLi9vdXRwdXQvb3V0cHV0Ml9yZi5SRGF0YSIpDQp9DQpgYGANCg0KIyMjIyMgTG9naXN0aWMgUmVncmVzc2lvbg0KYGBge3J9DQpydW4ubG9naSA8LSBGQUxTRQ0Kc291cmNlKCIuLi9saWIvbG9naS5SIikNCg0KaWYocnVuLmxvZ2kpew0KICBvdXRwdXRfbG9naSA8LSBsb2dpKGRmLnRyYWluLCBkZi50ZXN0KQ0KICBvdXRwdXQyX2xvZ2kgPC0gbG9naShkZi50cmFpbjIsIGRmLnRlc3QyKQ0KICANCiAgc2F2ZShvdXRwdXRfbG9naSwgZmlsZSA9ICIuLi9vdXRwdXQvb3V0cHV0X2xvZ2kuUkRhdGEiKQ0KICBzYXZlKG91dHB1dDJfbG9naSwgZmlsZSA9ICIuLi9vdXRwdXQvb3V0cHV0Ml9sb2dpLlJEYXRhIikNCn1lbHNlew0KICBsb2FkKCIuLi9vdXRwdXQvb3V0cHV0X2xvZ2kuUkRhdGEiKQ0KICBsb2FkKCIuLi9vdXRwdXQvb3V0cHV0Ml9sb2dpLlJEYXRhIikNCn0NCmBgYA0KDQojIyMjIyBHQk0NCmBgYHtyfQ0KcnVuLmdibSA8LSBGQUxTRQ0Kc291cmNlKCIuLi9saWIvZ2JtcC5yIikNCg0KaWYocnVuLmdibSl7DQogIG91dHB1dF9nYm0gPC0gZ2JtcChkZi50cmFpbiwgZGYudGVzdCkNCiAgb3V0cHV0Ml9nYm0gPC0gZ2JtcChkZi50cmFpbjIsIGRmLnRlc3QyKQ0KICANCiAgc2F2ZShvdXRwdXRfZ2JtLCBmaWxlID0gIi4uL291dHB1dC9vdXRwdXRfZ2JtLlJEYXRhIikNCiAgc2F2ZShvdXRwdXQyX2dibSwgZmlsZSA9ICIuLi9vdXRwdXQvb3V0cHV0Ml9nYm0uUkRhdGEiKQ0KfWVsc2V7DQogIGxvYWQoIi4uL291dHB1dC9vdXRwdXRfZ2JtLlJEYXRhIikNCiAgbG9hZCgiLi4vb3V0cHV0L291dHB1dDJfZ2JtLlJEYXRhIikNCn0NCmBgYA0KIyMjIyMgWEdCb29zdCAgDQpgYGB7cn0NCnJ1bi54ZyA8LSBGQUxTRQ0Kc291cmNlKCIuLi9saWIveGdib29zdC5yIikNCg0KaWYocnVuLnhnKXsNCiAgb3V0cHV0X3hnIDwtIHhnYihkZi50cmFpbixkZi50ZXN0KQ0KICBvdXRwdXQyX3hnIDwtIHhnYihkZi50cmFpbjIsZGYudGVzdDIpDQogIA0KICBzYXZlKG91dHB1dF94ZyxmaWxlID0gIi4uL291dHB1dC9vdXRwdXRfeGcuUkRhdGEiKQ0KICBzYXZlKG91dHB1dDJfeGcsZmlsZSA9ICIuLi9vdXRwdXQvb3V0cHV0Ml94Zy5SRGF0YSIpDQp9ZWxzZXsNCiAgbG9hZCgiLi4vb3V0cHV0L291dHB1dF94Zy5SRGF0YSIpDQogIGxvYWQoIi4uL291dHB1dC9vdXRwdXQyX3hnLlJEYXRhIikNCn0NCmBgYA0KIyMjIyMgQWRhQm9vc3QgIA0KYGBge3J9DQpydW4uYWRhIDwtIEZBTFNFDQpzb3VyY2UoIi4uL2xpYi9hZGFib29zdC5yIikNCg0KaWYocnVuLmFkYSl7DQogIG91dHB1dF9hZGEgPC0gYWRhYm9vc3QoZGYudHJhaW4sZGYudGVzdCkNCiAgb3V0cHV0Ml9hZGEgPC0gYWRhYm9vc3QoZGYudHJhaW4yLGRmLnRlc3QyKQ0KICANCiAgc2F2ZShvdXRwdXRfYWRhLGZpbGUgPSAiLi4vb3V0cHV0L291dHB1dF9hZGEuUkRhdGEiKQ0KICBzYXZlKG91dHB1dDJfYWRhLGZpbGUgPSAiLi4vb3V0cHV0L291dHB1dDJfYWRhLlJEYXRhIikNCn1lbHNlew0KICBsb2FkKCIuLi9vdXRwdXQvb3V0cHV0X2FkYS5SRGF0YSIpDQogIGxvYWQoIi4uL291dHB1dC9vdXRwdXQyX2FkYS5SRGF0YSIpDQp9DQpgYGANCiMjIyMjIFNWTSAgICANCmBgYHtyfQ0KcnVuLnN2bSA8LSBGQUxTRQ0Kc291cmNlKCIuLi9saWIvc3ZtLlIiKQ0KDQppZihydW4uc3ZtKXsNCiAgb3V0cHV0X3N2bSA8LSBTVk0oZGYudHJhaW4sZGYudGVzdCkNCiAgb3V0cHV0Ml9zdm0gPC0gU1ZNKGRmLnRyYWluMixkZi50ZXN0MikNCiAgDQogIHNhdmUob3V0cHV0X3N2bSxmaWxlID0gIi4uL291dHB1dC9vdXRwdXRfc3ZtLlJEYXRhIikNCiAgc2F2ZShvdXRwdXQyX3N2bSxmaWxlID0gIi4uL291dHB1dC9vdXRwdXQyX3N2bS5SRGF0YSIpDQp9ZWxzZXsNCiAgbG9hZCgiLi4vb3V0cHV0L291dHB1dF9zdm0uUkRhdGEiKQ0KICBsb2FkKCIuLi9vdXRwdXQvb3V0cHV0Ml9zdm0uUkRhdGEiKQ0KfQ0KYGBgDQoNCiMjIyBTdGVwIDQ6IEV2YWx1YXRpb24gIHsjU3RlcDR9ICAgDQpXZSBoYXZlIGNvbXBhcmVkIHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IGFuZCBydW5uaW5nIHRpbWUgYW1vbmcgdGhlIHNpeCBjbGFzc2lmaWNhdGlvbiBtZXRob2RzIHdpdGggdGhlIHR3byBkaWZmZXJlbnQgc2V0cyBvZiBmZWF0dXJlcywgYW5kIGhhdmUgZm91bmQgdGhhdCB0d28gYmVzdCBtb2RlbHMgYXJlIFNWTSB3aXRoIGFsbCBmZWF0dXJlcyAoMTAwJSBwcmVkaWN0aW9uIGFjY3VyYWN5IGFuZCAwLjA3cyB0cmFpbmluZyB0aW1lKSBhbmQgTG9naXN0aWMgUmVncmVzc2lvbiB3aXRoIHJlZHVjZWQgZmVhdHVyZXMgKDk4JSBwcmVkaWN0aW9uIGFjY3VyYWN5IGFuZCAwLjAzcyB0cmFpbmluZyB0aW1lKS4gICANCg0KIyMjIyBBY2N1cmFjeSAgDQpgYGB7cn0NCiMgY29tcHV0ZSBjb25mdXNpb24gbWF0cml4DQpjbV9yZiA8LSBjb25mdXNpb25NYXRyaXgob3V0cHV0X3JmJHByZWRpY3Rpb24sZGYudGVzdCRkaWFnbm9zaXMpDQpjbV9yZjIgPC0gY29uZnVzaW9uTWF0cml4KG91dHB1dDJfcmYkcHJlZGljdGlvbixkZi50ZXN0JGRpYWdub3NpcykNCmNtX2xvZ2kgPC0gY29uZnVzaW9uTWF0cml4KG91dHB1dF9sb2dpJHByZWRpY3Rpb24sZGYudGVzdCRkaWFnbm9zaXMpDQpjbV9sb2dpMiA8LSBjb25mdXNpb25NYXRyaXgob3V0cHV0Ml9sb2dpJHByZWRpY3Rpb24sZGYudGVzdCRkaWFnbm9zaXMpDQpjbV9nYm0gPC0gY29uZnVzaW9uTWF0cml4KG91dHB1dF9nYm0kcHJlZGljdGlvbixkZi50ZXN0JGRpYWdub3NpcykNCmNtX2dibTIgPC0gY29uZnVzaW9uTWF0cml4KG91dHB1dDJfZ2JtJHByZWRpY3Rpb24sZGYudGVzdCRkaWFnbm9zaXMpDQpjbV94ZyA8LSBjb25mdXNpb25NYXRyaXgob3V0cHV0X3hnJHByZWRpY3Rpb24sZGYudGVzdCRkaWFnbm9zaXMpDQpjbV94ZzIgPC0gY29uZnVzaW9uTWF0cml4KG91dHB1dDJfeGckcHJlZGljdGlvbixkZi50ZXN0JGRpYWdub3NpcykNCmNtX2FkYSA8LSBjb25mdXNpb25NYXRyaXgob3V0cHV0X2FkYSRwcmVkaWN0aW9uLGRmLnRlc3QkZGlhZ25vc2lzKQ0KY21fYWRhMiA8LSBjb25mdXNpb25NYXRyaXgob3V0cHV0Ml9hZGEkcHJlZGljdGlvbixkZi50ZXN0JGRpYWdub3NpcykNCmNtX3N2bSA8LSBjb25mdXNpb25NYXRyaXgob3V0cHV0X3N2bSRwcmVkaWN0aW9uLGRmLnRlc3QkZGlhZ25vc2lzKQ0KY21fc3ZtMiA8LSBjb25mdXNpb25NYXRyaXgob3V0cHV0Ml9zdm0kcHJlZGljdGlvbixkZi50ZXN0JGRpYWdub3NpcykNCg0KIyBjb21wYXJlIHByZWRpY3Rpb24gYWNjdXJhY3kgDQphY2N1cmFjeSA8LSBjKGNtX3JmJG92ZXJhbGxbMV0sY21fbG9naSRvdmVyYWxsWzFdLGNtX2dibSRvdmVyYWxsWzFdLA0KICAgICAgICAgICAgICBjbV94ZyRvdmVyYWxsWzFdLGNtX2FkYSRvdmVyYWxsWzFdLGNtX3N2bSRvdmVyYWxsWzFdLA0KICAgICAgICAgICAgICBjbV9yZjIkb3ZlcmFsbFsxXSxjbV9sb2dpMiRvdmVyYWxsWzFdLGNtX2dibTIkb3ZlcmFsbFsxXSwNCiAgICAgICAgICAgICAgY21feGcyJG92ZXJhbGxbMV0sY21fYWRhMiRvdmVyYWxsWzFdLGNtX3N2bTIkb3ZlcmFsbFsxXSkNCmFjY3VyYWN5X2NvbXBhcmlzb24gPC0gbWF0cml4KGFjY3VyYWN5LCBucm93ID0gMiwgYnlyb3cgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBsaXN0KGMoImFsbF9mZWF0dXJlcyIsInJlZHVjZWRfZmVhdHVyZXMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKCJSYW5kb21Gb3Jlc3QiLCJMb2dpc3RpYyBSZWdyZXNzaW9uIiwiR0JNIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiWEdCb29zdCIsIkFkYUJvb3N0IiwiU1ZNIikpKQ0Kcm91bmQoYWNjdXJhY3lfY29tcGFyaXNvbiw0KQ0KDQoNCmBgYA0KDQojIyMjIFJ1bm5pbmcgVGltZSANCmBgYHtyfQ0KIyBjb21wYXJlIHRpbWUNCnRpbWUgPC0gYyhvdXRwdXRfcmYkdGltZSxvdXRwdXRfbG9naSR0aW1lLG91dHB1dF9nYm0kdGltZSxvdXRwdXRfeGckdGltZSxvdXRwdXRfYWRhJHRpbWUsb3V0cHV0X3N2bSR0aW1lLA0KICAgICAgICAgIG91dHB1dDJfcmYkdGltZSxvdXRwdXQyX2xvZ2kkdGltZSxvdXRwdXQyX2dibSR0aW1lLG91dHB1dDJfeGckdGltZSxvdXRwdXQyX2FkYSR0aW1lLG91dHB1dDJfc3ZtJHRpbWUpDQp0aW1lX2NvbXBhcmlzb24gPC0gbWF0cml4KHRpbWUsIG5yb3c9MiwgYnlyb3cgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBkaW1uYW1lcyA9IGxpc3QoYygiYWxsX2ZlYXR1cmVzIiwicmVkdWNlZF9mZWF0dXJlcyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygiUmFuZG9tRm9yZXN0IiwiTG9naXN0aWMgUmVncmVzc2lvbiIsIkdCTSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJYR0Jvb3N0IiwiQWRhQm9vc3QiLCJTVk0iKSkpDQpyb3VuZCh0aW1lX2NvbXBhcmlzb24sMikNCmBgYA0KIyMjIyBDb25mdXN0aW9uIE1hdHJpeCBvZiBCZXN0IE1vZGVscw0KYGBge3J9DQojIHZpc3VhbGl6ZSBjb25mdXNpb24gbWF0cml4IG9mIHR3byBiZXN0IG1vZGVscyANCnBhcihtZnJvdz1jKDEsMikpDQpmb3VyZm9sZHBsb3QoY21fbG9naTIkdGFibGUsIGNvbmYubGV2ZWwgPSAwLCBtYXJnaW4gPSAxLA0KICAgICAgICAgICAgIG1haW4gPSBwYXN0ZTAoIkxvZ2lzdGljIFJlZ3Jlc3Npb24gKCIsIHJvdW5kKGNtX2xvZ2kyJG92ZXJhbGxbMV0qMTAwKSwgIiUpIikpDQpmb3VyZm9sZHBsb3QoY21fc3ZtJHRhYmxlLCBjb25mLmxldmVsID0gMCwgbWFyZ2luID0gMSwNCiAgICAgICAgICAgICBtYWluID0gcGFzdGUwKCJTVk0gKCIsIHJvdW5kKGNtX3N2bSRvdmVyYWxsWzFdKjEwMCksICIlKSIpKQ0KYGBgDQoNCg==