1 Introduction

As reported by the American Cancer Society, as of 2024 breast cancer accounts for approximately 1 in 3 of all new female cancer cases in the United States annually. Despite the steadily declining rates in breast cancer mortality, breast cancer is still the second leading cause of cancer death in women. As early detection and diagnosis is pivotal to improved treatment outcome, increasing need has been identified for improved predictive models to assist in rapid diagnosis.

Tissue biopsy is currently the only diagnostic tool to determine cancer content in identified breast tumors. Majority of breast tissue biopsy is completed utilizing a core needle biopsy; a procedure that collects 1 or multiple small cylinders of tumor tissue using a fine gauge hollow needle. As illustrated in the image below, there is a chance that an incomplete tumor punch can be collected, leading to increased difficulty in diagnosis. Core needle biopsies are preferred by physicians and patients due to the increased tissue volume obtained compared to a fine needle aspirate biopsy and the ability to biopsy in an outpatient setting without surgery.

Image 1: Core Needle Breast Tissue Biopsy

1.1 Purpose of Investigation

In order to improve breast cancer diagnostic capabilities through minimal tissue collection biopsies such as core needle punch biopsies, improved predictive models must be developed to assist in filling in the blanks in the event an incomplete punch biopsy. Increasing predictive ability with minimal tissue requirements will in effect reduce patient burden and increase the speed at which malignant tissue is diagnosed.

1.2 Data Utilized

raw.data <- read.csv("https://nlepera.github.io/sta551/HW03/data/syntheticBreastCancerData.csv") %>% 
  subset( , select = c(2:11))

colnames(raw.data)[colnames(raw.data) == "Single_Epithelial_Cell_Size"] <- "eCellSize"
colnames(raw.data)[colnames(raw.data) == "Thickness_of_Clump"] <- "clumpThickness"
colnames(raw.data)[colnames(raw.data) == "Cell_Size_Uniformity"] <- "sizeUniform"
colnames(raw.data)[colnames(raw.data) == "Cell_Shape_Uniformity"] <- "shapeUniform"
colnames(raw.data)[colnames(raw.data) == "Marginal_Adhesion"] <- "margAdh"
colnames(raw.data)[colnames(raw.data) == "Bare_Nuclei"] <- "bareNuclei"
colnames(raw.data)[colnames(raw.data) == "Bland_Chromatin"] <- "blChrom"
colnames(raw.data)[colnames(raw.data) == "Normal_Nucleoli"] <- "normalNucleoli"


raw.data$Outcome <- as.factor(as.character(raw.data$Outcome))

Synthetic Breast Cancer Data was obtained from Applied Analytics through Case Studies using SAS and R by Deepti Gupta [link]. The synthetic data set initially contained 600 observations and 11 variables. Initial variable names were converted for ease of utilization and the first variable Sample_No was dropped for analysis purposes, as Sample_No was an unrelated identifier key rather than a true variable.

Variables retained are as follows:

  • clumpThickness : Integer value measuring thickness of tumor clump obtained by biopsy. Mono-layers (clumpThickness = 1) indicate benign mass (no cancer) while higher values indicate malignancy (cancer).

  • sizeUniform : Integer value measuring variation in cell size. Greater values indicate greater variation in cell size. Increased cell size variation considered associated with malignancy.

  • shapeUniform: Integer value measuring variation in cell shape. Greater values indicate greater variation in cell shape. Increased cell shape variation considered associated with malignancy.

  • margAdh: Integer value measuring variation in adhesion among neighboring cells within a clump. Greater values indicate greater adhesion between neighboring cells. Decreased adhesion considered associated with malignancy.

  • eCellSize : Integer value measuring respective size of single epithelial cells obtained from biopsy. Greater values indicate greater epithelial cell size. Increased epithelial cell size considered associated with malignancy.

  • bareNuclei : Integer value measuring count of bare nuclei observed in sample cells. Greater values indicate greater number of cells with bare nuclei surrounded by cytoplasm. Increased bare nuclei count considered associated with malignancy.

  • blChrom : Integer value measuring coarseness of chromatin. Greater values indicate greater chromatin coarseness. Increased chromatin coarseness considered associated with malignancy.

  • normalNucleoli: Integer value measuring size normal (mean) nucleoli size as measured in biopsy sample. Greater values indicate mean nucleoli size. Increased mean nucleoli considered associated with malignancy.

  • Mitoses : Integer value measuring the number of cells undergoing mitosis observed in the biopsy sample. Greater values indicate greater rates of mitosis. Increased mitosis rates are considered associated with malignancy.

  • Outcome : Character value classifying final biopsy diagnosis. “No” indicates tumor diagnosed as benign (no cancer). “Yes” indicates tumor diagnosed as malignant (cancer).

Once the variable names were transformed it was confirmed the resultant data set contained 600 observations across 10 variables with 0 missing values. As no values are missing from the data set it was determined that no imputation was necessary.

kable(summary(raw.data), caption = "Structure of Synthetic Breast Cancer Data", align = "c", size = 6) %>% 
  kable_styling() %>% 
  row_spec(0, font_size = 12)
Structure of Synthetic Breast Cancer Data
clumpThickness sizeUniform shapeUniform margAdh eCellSize bareNuclei blChrom normalNucleoli Mitoses Outcome
Min. : 1.00 Min. : 1.000 Min. : 1.000 Min. : 1.000 Min. : 1.000 Min. : 1.0 Min. : 1.000 Min. : 1.000 Min. : 1.000 No :380
1st Qu.: 3.00 1st Qu.: 2.000 1st Qu.: 2.000 1st Qu.: 2.000 1st Qu.: 3.000 1st Qu.: 2.0 1st Qu.: 3.000 1st Qu.: 2.000 1st Qu.: 1.000 Yes:220
Median : 5.00 Median : 3.000 Median : 3.000 Median : 3.000 Median : 4.000 Median : 3.0 Median : 4.000 Median : 3.000 Median : 2.000 NA
Mean : 5.41 Mean : 4.122 Mean : 4.195 Mean : 3.763 Mean : 4.293 Mean : 4.5 Mean : 4.495 Mean : 3.812 Mean : 2.093 NA
3rd Qu.: 7.25 3rd Qu.: 6.000 3rd Qu.: 6.000 3rd Qu.: 5.000 3rd Qu.: 5.000 3rd Qu.: 9.0 3rd Qu.: 6.000 3rd Qu.: 5.000 3rd Qu.: 2.000 NA
Max. :10.00 Max. :10.000 Max. :10.000 Max. :10.000 Max. :10.000 Max. :10.0 Max. :10.000 Max. :10.000 Max. :10.000 NA
kable(sapply(raw.data, function (x) sum(is.na(x))), caption = "Count of Blank Values in for all Labels", col.names = c("Labels", "Count of Blank Values")) %>% 
  kable_styling()
Count of Blank Values in for all Labels
Labels Count of Blank Values
clumpThickness 0
sizeUniform 0
shapeUniform 0
margAdh 0
eCellSize 0
bareNuclei 0
blChrom 0
normalNucleoli 0
Mitoses 0
Outcome 0

One engineered variable was created to convert Outcome from a character variable to a binary integer response variable:

  • out : Engineered outcome variable converted to binary integer response variable. 1 = “No” (biopsy diagnosed as benign) and 2 = “Yes” (biopsy diagnosed as malignant).

A brief summary of the variables with original variable values and additional supplimental details can be found at: [link].

1.3 Distribution of data

Upon visualizing the pairwise distribution of all integer variables, controlled for Outcome, it becomes evident that there are multiple significant correlations across multiple variables. 16 combinations demonstrate a correlation greater than 0.600 indicating a high value of correlation between these variable pairs. The greatest correlation seen was between shapeUniform and sizeUniformindicating an increase in cell shape variation is accompanied by an increase in cell size variation.

ggpairs(raw.data, 
        columns = c(1:10), 
        aes(alpha = 0.05, color = Outcome), 
        lower = list(continuous = wrap("smooth", method = "lm", se = FALSE, alpha = 0.25)), 
        upper = list(continuous = wrap("cor", size = 3))) +
  theme (
    strip.text = element_text(size = 7),
    axis.text = element_text(size = 8),
    axis.title = element_text(size = 6),
    legend.text = element_text(size = 8),
    legend.title = element_text(size = 8)
  )

The high correlation values across nearly all variables in the data set underpin the need for a complex modeling procedure to create accurate predictions of the biopsy diagnosis.

2 Logistic Prediction

Two logistic prediction models were created to examine the potential predictive value of a multiple variable logistic regression model in determining breast cancer biopsy outcomes. An initial model including all variables was created, and then trimmed down to create a secondary reduced model for more precise regression analysis and potential future predictions.

2.1 First Model (full.model.logit)

To start, a logistic regression model was created to compare the logistic regression relationship between the Outcome variable and all integer variables in the data set. This initial model is referred to henceforth as full.model.logit. The full modelfull.model.legit` is represented as follows:

Outcome ~ Clump Thickness + Cell Size Uniformity + Cell Shape Uniformity + Marginal Adhesion + Single Epithelial Cell Size + Bare Nuclei + Bland Chromatin + Normal Nucleoli + Mitoses

full.model.logit <- glm(Outcome ~ ., family = binomial, data = raw.data)

A summary of regression coefficients, z values, and probability of associated z values (p vlaue associated with listed z value) for each variable in the logistic regression is included below. Multiple variables illustrate a strong correlation as indicated by estimate values closer to 1.0, with strong statistic significance as indicated by Pr(|z|) values less than 0.050000. The stronger a correlation indicated in the Estimate value, the stronger that variable will serve as a predictive value during predictive model creation.

A good fit model will rely on both correlation and statistical signficance to prodcue a model that is sensitive, specific, and accurate.

kable(summary(full.model.logit)$coef, caption = "Logistic Regression Coefficients for Full Model", col.names = c("" , "Estimate", "Std. Error", "z Value", 'Pr (|>z|)'), format = "html") %>% 
  kable_styling()
Logistic Regression Coefficients for Full Model
Estimate Std. Error z Value Pr (|>z|)
(Intercept) -11.3994839 1.2546649 -9.0856797 0.0000000
clumpThickness 0.4669781 0.1257561 3.7133626 0.0002045
sizeUniform 0.0313879 0.1567402 0.2002543 0.8412817
shapeUniform 0.3685873 0.1686559 2.1854394 0.0288566
margAdh 0.1968390 0.1111111 1.7715519 0.0764690
eCellSize 0.0760556 0.1391434 0.5465989 0.5846543
bareNuclei 0.4282675 0.0871709 4.9129632 0.0000009
blChrom 0.3640725 0.1365713 2.6658054 0.0076804
normalNucleoli 0.1406214 0.1015091 1.3853083 0.1659582
Mitoses 0.4012046 0.2317122 1.7314786 0.0833664

2.2 Second Model (reduced.model.logit)

To obtain improved predictive values from a model, reducing the number of variables for analysis and inclusion can improve model fitting without accidental over fitting. In order to best select the variables for retention in the reduced model, an automated step() function was utilized to identify best fit reduced model. Trace was included to illustrate the step-wise removal of variables based on calculated Akaike information criterion (AIC) values until the final best fit reduced model identified.

reduced.model.logit <- step(full.model.logit, directon = "both")
Start:  AIC=138.62
Outcome ~ clumpThickness + sizeUniform + shapeUniform + margAdh + 
    eCellSize + bareNuclei + blChrom + normalNucleoli + Mitoses

                 Df Deviance    AIC
- sizeUniform     1   118.66 136.66
- eCellSize       1   118.92 136.92
- normalNucleoli  1   120.53 138.53
<none>                118.62 138.62
- margAdh         1   121.87 139.87
- Mitoses         1   122.62 140.62
- shapeUniform    1   123.58 141.58
- blChrom         1   125.99 143.99
- clumpThickness  1   135.04 153.04
- bareNuclei      1   145.79 163.79

Step:  AIC=136.66
Outcome ~ clumpThickness + shapeUniform + margAdh + eCellSize + 
    bareNuclei + blChrom + normalNucleoli + Mitoses

                 Df Deviance    AIC
- eCellSize       1   119.02 135.02
<none>                118.66 136.66
- normalNucleoli  1   120.69 136.69
- margAdh         1   122.09 138.09
- Mitoses         1   122.93 138.93
- blChrom         1   126.26 142.26
- shapeUniform    1   128.26 144.26
- clumpThickness  1   135.58 151.58
- bareNuclei      1   146.31 162.31

Step:  AIC=135.02
Outcome ~ clumpThickness + shapeUniform + margAdh + bareNuclei + 
    blChrom + normalNucleoli + Mitoses

                 Df Deviance    AIC
<none>                119.02 135.02
- normalNucleoli  1   121.72 135.72
- margAdh         1   123.38 137.38
- Mitoses         1   123.81 137.81
- blChrom         1   127.13 141.13
- shapeUniform    1   129.50 143.50
- clumpThickness  1   136.26 150.26
- bareNuclei      1   146.97 160.97

The step function removed the variables sizeUniform (Cell Size Uniformity) & eCellSize (Single Epithelial Cell Size) based on AIC to measure the reduced model’s maximum likelihood estimation (log-likelihood) for model fit. The AIC equation is given by:

\[AIC = -2*ln(L) + 2k\]

A summary of the model characteristics starting with the initial full model as well as at each step leading to creation of the reduced model is included below. To note the negative signs in the Step column indicate the removal of the listed variable from the logistic regression model.

kable(reduced.model.logit$anova) %>% 
  kable_styling()
Step Df Deviance Resid. Df Resid. Dev AIC
NA NA 590 118.6189 138.6189
  • sizeUniform
1 0.0402921 591 118.6592 136.6592
  • eCellSize
1 0.3593399 592 119.0185 135.0185

reduced model - Outcome ~ clumpThickness + shapeUniform + margAdh + bareNuclei + blChrom + normalNucleoli + Mitoses

A summary of the reduced model’s regression coefficients is included below. Unlike with the full.model.logit (full model) all but two remaining variables are found to have a statistical significance of \(Pr(|z|) < 0.05\) along with high correlation values obtained as evident by the regression coefficients. The combination of all these findings indicate that the reduced logistic regression model will likely serve as a good fit as a predictor model for breast cancer biopsy diagnosis data.

kable(summary(reduced.model.logit)$coef, caption = "Logistic Regression Coefficients for Reduced.Model", col.names = c("" , "Estimate", "Std. Error", "z Value", 'Pr (|>z|)'), format = "html") %>% 
  kable_styling()
Logistic Regression Coefficients for Reduced.Model
Estimate Std. Error z Value Pr (|>z|)
(Intercept) -11.3645496 1.2250769 -9.276601 0.0000000
clumpThickness 0.4725699 0.1250565 3.778850 0.0001576
shapeUniform 0.4054938 0.1346086 3.012391 0.0025920
margAdh 0.2169802 0.1064015 2.039259 0.0414242
bareNuclei 0.4324737 0.0873739 4.949691 0.0000007
blChrom 0.3766827 0.1349834 2.790585 0.0052613
normalNucleoli 0.1596089 0.0974167 1.638413 0.1013355
Mitoses 0.4154991 0.2249179 1.847336 0.0646984

2.3 Model Comparrison & Selection of Best Fit Logistic Regression Model

In order to formally compare both the full.model.logit and reduced. model.logit models to determine which will serve as a better predictor for biopsy diagnosis, each model’s AIC and ROC curves were compared to determine the best fit model.

2.3.1 AIC Comparrison

When comparing AIC values, a lower value indicates a better model fit while balancing generalization. Meanwhile a greater AIC value indicates a poor fit and subsequently poor predictive values.

2.3.1.1 AIC of full.model.logit (full model)

kable(full.model.logit$aic, caption = "AIC value of Full Model", col.names = c("full.model.logit AIC ="), align = "c") %>% 
  kable_styling()
AIC value of Full Model
full.model.logit AIC =
138.6189

2.3.1.2 AIC of reduced.model.logit (reduced model)

kable(reduced.model.logit$aic, caption = "AIC value of Reduced Model", col.names = c("reduced.model.logit AIC ="), align = "c") %>% 
  kable_styling()
AIC value of Reduced Model
reduced.model.logit AIC =
135.0185

When comparing by AIC values, the reduced model returns a lower AIC value indicating a better fit to the initial data. Based on AIC values the reduced model will provide improved prediction values for patient’s outcome (accurate diagnosis of malignancies during biopsy).

2.3.2 ROC Comparrison

When comparing ROC values, the area under the curve (AUC) values and visible Receiver Operating Characteristics (ROC) curves can be used to compare the fit of multiple logistic regression models as predictive models for a binary output variable such as Outcome. The greater the AUC the better fit the model is to the original synthetic data & the more accurate the model will be in providing predictions on breast tissue biopsy diagnosis.

data.logit <- raw.data

no.outcome <- which(data.logit$Outcome == "No")
yes.outcome <-  which(data.logit$Outcome == "Yes")

data.logit$out = 0
data.logit$out[yes.outcome] = 1
cutoff <- seq(0, 1, length = 100)
sensitiv = NULL
specif = NULL
sensitiv2 = NULL
specif2 = NULL
accur = NULL
accur2 = NULL


predict.full.logit <- predict.glm(full.model.logit, data.logit, type = "response")
predict.reduced.logit <- predict.glm(reduced.model.logit, data.logit, type = "response")

for (i in 1:100){
  
data.logit$out.train = as.numeric(predict.full.logit > cutoff[i])

TN = sum(data.logit$out.train == 0 & data.logit$out == 0) # true negative
FN = sum(data.logit$out.train == 0 & data.logit$out == 1) # false negative
FP = sum(data.logit$out.train == 1 & data.logit$out == 0) # false positive
TP = sum(data.logit$out.train == 1 & data.logit$out == 1) # true positive

sensitiv[i] = TP / (TP + FN)
specif[i] = TN / (TN + FP)
accur[i] = (TP + TN) / (TP + TN + FN + FP) #accuracy
}

for (i in 1:100){
  
data.logit$out.train = as.numeric(predict.reduced.logit > cutoff[i])

TN2 = sum(data.logit$out.train == 0 & data.logit$out == 0) # true negative
FN2 = sum(data.logit$out.train == 0 & data.logit$out == 1) # false negative
FP2 = sum(data.logit$out.train == 1 & data.logit$out == 0) # false positive
TP2 = sum(data.logit$out.train == 1 & data.logit$out == 1) # true positive

sensitiv2[i] = TP2 / (TP2 + FN2)
specif2[i] = TN2 / (TN2 + FP2)
accur2[i] = (TP2 + TN2)/(TP2 + TN2 + FN2 + FP2) #accuracy

}

specificity <-  1 - specif
sensitivity <-  sensitiv

specificity2 <-  1 - specif2
sensitivity2 <-  sensitiv2

prediction <-  predict.full.logit
category <-  data.logit$out == 1
ROCobj <- roc(category, prediction)
AUC.full <- round(auc(ROCobj), 5)

prediction2 <-  predict.reduced.logit
category2 <-  data.logit$out == 1
ROCobj2 <- roc(category2, prediction2)
AUC.reduced <- round(auc(ROCobj2), 5)

specif.ori <- ROCobj$specificities
specif.ori2 <- ROCobj2$specificities
sens.ori <- ROCobj$sensitivities
sens.ori2 <- ROCobj2$sensitivities

diff <- abs(sens.ori - specif.ori)
diff2 <- abs(sens.ori2 - specif.ori2)

min.full <- which(diff == min(diff))
min.reduced <- which(diff2 == min(diff2))

ROC <- plot(specificity, sensitivity,
            type = "l",
            main = "Logistic Regression Model Comparrison Using ROC and AUC",
            xlab="1-specificity",
            ylab="sensitivity") +
  segments(0,0,1,1, lty=2, col = "red") + 
  lines(specificity, sensitivity, lwd = 1, col = "navy") +
  lines(specificity2, sensitivity2, lwd = 1, col = "darkgreen") +
  text(0.8, 0.2, paste("Full Model AUC = ", AUC.full), col = "navy", cex = 0.8) +
  text(0.8, 0.25, paste("Reduced Model AUC = ", AUC.reduced), col = "darkgreen", cex = 0.8) +
  points((1-specif.ori[min.full]), specif.ori[min.full], col = "navy", pch = 17) +
  points((1-specif.ori2[min.reduced]), specif.ori2[min.reduced], col = "darkgreen", pch = 17) 
  text(0.8, 0.18, paste("Full Model Calculated Cutoff = (", round((1-specif.ori[min.full]),4), ", " , round(sens.ori[min.full],4),")"), col = "navy", cex = 0.8) +
  text(0.8, 0.23, paste("Reduced Model Calculated Cutoff = (", round((1-specif.ori2[min.reduced]),4), ", " , round(sens.ori2[min.reduced],4),")"), col = "darkgreen", cex = 0.8) 

integer(0)

While the ROC curves are near identical and the AUC values differ by only 0.00004 both models are near identical in terms of predictive power. While the reduced model has a marginally greater AUC value (0.0040% greater than the full model AUC), the reduced model also contains fewer variables and therefore should be selected as the predictive model of choice.

3 Neural Network Prediction with Perceptron

A machine learning algorithm known as the Perceptron model can be utilized to create predictive models for binary classifiers, capable of undergoing consistent supervised learning.

data.neural <- raw.data

3.1 Preparing the data for NeuralNet use

In order to run a neural network as a predictive model, the following considerations must be met. The data was modified accordingly to meet these conditions:

  1. All features must be numeric
    1. Outcome converted from factor to numeric
  2. All dummy variables must be explicitly defined
    1. Outcome = 1 means No (benign tissue)
    2. Outcome = 2 means Yes (malignant tissue)
  3. Recommended to scale all numerical values
    1. Min-max scaling utilized (formula below)

\[ x_{scaled} = \frac{x - min(x)}{max(x) - min(x)}\]

data.neural$Outcome <- as.numeric(as.factor(data.neural$Outcome))
data.neural$clumpThickScale <- ((data.neural$clumpThickness - min(data.neural$clumpThickness))/(max(data.neural$clumpThickness)-min(data.neural$clumpThickness))) #scale clumpThickness

data.neural$sizeUniScale <- ((data.neural$sizeUniform - min(data.neural$sizeUniform))/(max(data.neural$sizeUniform)-min(data.neural$sizeUniform))) #scale sizeUniform

data.neural$shapeUniScale <- ((data.neural$shapeUniform - min(data.neural$shapeUniform))/(max(data.neural$shapeUniform)-min(data.neural$shapeUniform))) #scale shapeUniform

data.neural$margAdhScale <- ((data.neural$margAdh - min(data.neural$margAdh))/(max(data.neural$margAdh)-min(data.neural$margAdh))) #scale margAdh

data.neural$eCellSizeScale <- ((data.neural$eCellSize - min(data.neural$eCellSize))/(max(data.neural$eCellSize)-min(data.neural$eCellSize))) #scale eCellSize

data.neural$blChromScale <- ((data.neural$blChrom - min(data.neural$blChrom))/(max(data.neural$blChrom)-min(data.neural$blChrom))) #scale blCrom

data.neural$normalNuclScale <- ((data.neural$normalNucleoli - min(data.neural$normalNucleoli))/(max(data.neural$normalNucleoli)-min(data.neural$normalNucleoli))) #scale normalNucleoli

data.neural$MitosesScale <- ((data.neural$Mitoses - min(data.neural$Mitoses))/(max(data.neural$Mitoses)-min(data.neural$Mitoses))) #scale mitoses

data.neural$OutcomeScale <- ((data.neural$Outcome - min(data.neural$Outcome))/(max(data.neural$Outcome)-min(data.neural$Outcome))) #scale outcome

data.neural.scale <- subset(data.neural, select = c(clumpThickScale, sizeUniScale, shapeUniScale, margAdhScale, eCellSizeScale, blChromScale, normalNuclScale, MitosesScale, OutcomeScale))

A summary of the transformed (min-max scaled) synthetic breast cancer biopsy data is included below

kable(summary(data.neural.scale),  caption = "Summary of Min-Max transformed Synthetic Breast Cancer Data", align = "c", size = 6) %>% 
  kable_styling() %>% 
  row_spec(0, font_size = 12)
Summary of Min-Max transformed Synthetic Breast Cancer Data
clumpThickScale sizeUniScale shapeUniScale margAdhScale eCellSizeScale blChromScale normalNuclScale MitosesScale OutcomeScale
Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000
1st Qu.:0.2222 1st Qu.:0.1111 1st Qu.:0.1111 1st Qu.:0.1111 1st Qu.:0.2222 1st Qu.:0.2222 1st Qu.:0.1111 1st Qu.:0.0000 1st Qu.:0.0000
Median :0.4444 Median :0.2222 Median :0.2222 Median :0.2222 Median :0.3333 Median :0.3333 Median :0.2222 Median :0.1111 Median :0.0000
Mean :0.4900 Mean :0.3469 Mean :0.3550 Mean :0.3070 Mean :0.3659 Mean :0.3883 Mean :0.3124 Mean :0.1215 Mean :0.3667
3rd Qu.:0.6944 3rd Qu.:0.5556 3rd Qu.:0.5556 3rd Qu.:0.4444 3rd Qu.:0.4444 3rd Qu.:0.5556 3rd Qu.:0.4444 3rd Qu.:0.1111 3rd Qu.:1.0000
Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000

3.2 Create Model and Model Formula

The model was created implicitly utilizing the current variable names. Models do not have to be created implicitly but implicit creation ensures no data entry errors during model creation and prevents opportunity for human error.

3.2.1 Implicitly define model formula

All variable names were extracted in preparation of model and model formula creation.

neural.implic.design = model.matrix( ~., data = data.neural.scale)
colnames(neural.implic.design)
 [1] "(Intercept)"     "clumpThickScale" "sizeUniScale"    "shapeUniScale"  
 [5] "margAdhScale"    "eCellSizeScale"  "blChromScale"    "normalNuclScale"
 [9] "MitosesScale"    "OutcomeScale"   

OutcomeScale is response variable and requires proper attention in ensemble model formula creation.

3.2.2 Create Ensemble Model Formula

Once the variable names were extracted, a string function was utilized to design the model formula from the model.matrix. The model format must be in the following equation format:

response ~ var.1 + var.2 + ... + var.n

neural.formula.names <-  colnames(neural.implic.design)
neural.response.name <- paste(neural.formula.names[10])
neural.var.names <- paste(neural.formula.names[-c(1,10)], collapse = "+")
neural.formula <- formula(paste(c(neural.response.name, "~", neural.var.names), collapse = ""))
neural.formula
OutcomeScale ~ clumpThickScale + sizeUniScale + shapeUniScale + 
    margAdhScale + eCellSizeScale + blChromScale + normalNuclScale + 
    MitosesScale

The above ensemble model formula was then utilized in the neuralnet() function to create a single layer neural network predictive model.

neural.model <- neuralnet(neural.formula,
                          data = neural.implic.design, #design matrix
                          hidden = 1,
                          act.fct = "logistic", #sigmoid activation fxn
                          linear.output = FALSE #must be false for logistic
                          )

kable(neural.model$result.matrix)
error 7.5079378
reached.threshold 0.0098939
steps 5317.0000000
Intercept.to.1layhid1 -13.3255423
clumpThickScale.to.1layhid1 7.1910769
sizeUniScale.to.1layhid1 2.2113967
shapeUniScale.to.1layhid1 9.6031389
margAdhScale.to.1layhid1 3.9146535
eCellSizeScale.to.1layhid1 -5.5270566
blChromScale.to.1layhid1 7.0063959
normalNuclScale.to.1layhid1 7.1026281
MitosesScale.to.1layhid1 8.8580648
Intercept.to.OutcomeScale -27.5277181
1layhid1.to.OutcomeScale 64.8763323

3.3 Plotting Neural Net Model (neural.model) with Associated Weights

A visual representation of the neural network and the respective calculated weights for each variable (and hidden weights) is included below.

plot(neural.model, rep="best", title = "Single-layer back propogation neural network for breast tissue biopsy diagnosis outcome")

Values along black lines following variable names represent the calculated weight of each variable. All weight adjustment was completed through \(z = w_0 +w_1 x_1 + ... + w_n x_n\)

Blue lines, nodes, and associated weight values represent the hidden layers required for logistic regression (a.k.a. bias)

First node (left) represents the linear transfer function (z equation above) and bias

Second node (right) represents the sigmoid activation function. This function is calculated by:

\(g(\delta) = \frac{e^\delta}{1+e^\delta}\) where \(\delta = \sum_{i=1}^n z = \sum_{i=1}^{n} w_i x_i + w_0\)

Decision threshold (\(\delta_0\)) is not represented on the flowchart but is used to determine the final output value. If \(g(\delta) > \delta_0\) then \(\hat{y} = 1\) otherwise \(\hat{y} = 0\)

OutcomeScale represents the output prediction values \(\hat{y}\) (0 = no cancer / 1 = cancer)

predict.neural <- predict(neural.model, newdata = neural.implic.design, linear.output = FALSE)
cat.nn <- data.neural$OutcomeScale == 1
ROCobj.nn <- roc(cat.nn, predict.neural)


plot(1- ROCobj.nn$sensitivities, ROCobj.nn$specificities,
     type = "l",
     xlim = c(0,1),
     ylim = c(0,1),
     xlab = "1 - Specificity (False Positive)",
     ylab = "Sensitivity (True Negative)",
     col = "darkviolet",
     lwd = 2,
     lty = 1,
     main = "ROC Curve of Neural Network Perceptron Model") 
abline(0,1, lty = 2, col = "darkred", lwd = 2) 
  legend("bottomright", c(paste("Perceptron Model, AUC =", ROCobj.nn$auc)),
         col = c("darkviolet", "darkorange", "darkgreen", "navy"),
         lwd=2, lty=rep(1), cex = 0.8, bty = "n")

4 Decision Tree Prediction

data.tree <- raw.data

Another common approach to creating predictive models is through the creation of decision trees. Two main approaches to decision tree splitting were utilized: Gini Index & Entropy

tree.model <- function (input.data, fp, fn, purity){
  tree = rpart(Outcome ~ .,
               data = input.data,
               na.action = na.rpart,
               method = "class",
               model = FALSE,
               x = FALSE,
               y = TRUE,
               parms = list(loss = matrix(c(0, fp, fn, 0), ncol = 2, byrow = TRUE),
                            split = purity), #parms creates ability to weight false positives and false negatives
               control = rpart.control(
                 minsplit = 10, #min values before split
                 minbucket = 3, #min values for leaf nodes
                 cp = 0.01, #comlpexity parameter
                 xval = 5 #number of cross validation runs
               )
  )
}
gini.tree.unweight <- tree.model(input.data = data.tree, fp = 1, fn = 1, purity = "gini")
info.tree.unweight <- tree.model(input.data = data.tree, fp = 1, fn = 1, purity = "info")
gini.tree.neg <- tree.model(input.data = data.tree, fp = 10, fn = 10, purity = "gini")
info.tree.neg <- tree.model(input.data = data.tree, fp = 10, fn = 10, purity = "info")

A note on interpreting the decision trees below. Each node & leaf contains the following values in the following order: 1. Predicted class (Yes/No) 2. Probability of Predicted Class 3. Percentage of Observations in Node

4.1 Gini Index

The gini index utilized for calculating node split values is given by the following equation:

\[ Gini(D) = \sum_{i=1}^{n} p_i (1-p_i) = 1 - \sum_{i=1}^{n} p_i^2 \]

where \(p_i\) represents the probability of being classified to a particular class.

4.1.1 Decision Tree Models for Predicting Outcome Values Gini Index

par(mfrow=c(1,2))
rpart.plot(gini.tree.unweight, main = "No Additional Weight", type = 1)
rpart.plot(gini.tree.neg, main = "False Values x10 Weight True Values", type = 1)

4.2 Entropy

The Entropy utilized for calculating node split values is given by the following equation:

\[ E = \sum_{i=1}^{n} -p_i \log_2 (p_i) \] where \(p_i\) represents the probability of being classified to a particular class. Entropy can then be utilized to calculate the information gain from a model InfoGain = E(Parent Node) - E(Child Node)

Information gain is a measure of the information that would be gained by splitting a feature of interest. Higher info gain values indicate greater model benefit to splitting that feature.

4.2.1 Decision Tree Models for Predicting Outcome Values Entropy

par(mfrow=c(1,2))
rpart.plot(info.tree.unweight, main = "No Additional Weight", type = 1)
rpart.plot(info.tree.neg, main = "False Values x10 Weight True Values", type = 1)

4.3 Compare Decision Tree Models Using ROC & AUC

To compare the four decision trees and determine the best fit model and subsequent best model to use for predictions, the respective ROC curves and AUC values were compared.

Both the Gini and Entropy models were compared with no weighting as well as with 10x weighing for false negatives compared to false positives. False positives of a core needle punch biopsy can be identified with further blood biomarker screening, surgical biopsy, and additional core needle punch biopsies. Alternatively, a false negative result will lead a patient to delay or ultimately forgo potentially life saving treatment. In terms of cancer, false negatives are costly, and thus were weighted as such in the prediction models.

#Creating a function allows for streamlined "plug and play" processing of various decision tree models for ROC & AUC comparison.

dec.matrix.sesp <- function (input.data, fp, fn, purity){
  cutoff <- seq(0,1,length=100)
  model = tree.model(input.data, fp, fn, purity)
  pred.dec = predict(model, newdata = input.data, type = "prob") #creates 2 column matrix
  matrix.sesp <- matrix(0, ncol = length(cutoff), nrow =3, byrow = FALSE)
  
  for (i in 1:length(cutoff)){
    pred.output <- ifelse(pred.dec[,"Yes"] >= cutoff[i], "Yes", "No") #[,"Yes"] forces selection of only "success" (yes) probs
    TP1 = sum(pred.output == "Yes" & input.data$Outcome == "Yes")
    TN1 = sum(pred.output == "No" & input.data$Outcome == "No")
    FP1 = sum(pred.output == "Yes" & input.data$Outcome == "No")
    FN1 = sum(pred.output == "No" & input.data$Outcome == "Yes")
    
    matrix.sesp[1,i] = TP1/(TP1 + FN1)
    matrix.sesp[2,i] = TN1/(TN1 + FP1)
    matrix.sesp[3,i] = (TP1 + TN1)/(TP1 + TN1 + FN1 + FP1) #accuracy
  }
  
  pred.dec.final  <-  pred.dec[ , "Yes"]
  cat.dec <-  input.data$Outcome == "Yes"
  ROCObj.dec <- roc(cat.dec, pred.dec.final)
  AUC.dec <-  auc(ROCObj.dec)
  
  list(matrix.sesp = matrix.sesp, AUC = round(AUC.dec, 3))
  
}
gini.ROC.unweight <- dec.matrix.sesp(input.data = data.tree, fp = 1, fn = 1, purity = "gini")
info.ROC.unweight <- dec.matrix.sesp(input.data = data.tree, fp = 1, fn = 1, purity = "information")
gini.ROC.neg <- dec.matrix.sesp(input.data = data.tree, fp = 1, fn = 10, purity = "gini")
info.ROC.neg <- dec.matrix.sesp(input.data = data.tree, fp = 1, fn = 10, purity = "information")
plot(1 - gini.ROC.unweight$matrix.sesp[2,], gini.ROC.unweight$matrix.sesp[1,],
     type = "l",
     xlim = c(0,1),
     ylim = c(0,1),
     xlab = "1 - Specificity (False Positive)",
     ylab = "Sensitivity (True Negative)",
     col = "darkviolet",
     lwd = 2,
     lty = 3,
     main = "ROC Curve Comparrison of Decision Tree Models") 
abline(0,1, lty = 2, col = "darkred", lwd = 2) 
  lines(1 - info.ROC.unweight$matrix.sesp[2,], info.ROC.unweight$matrix.sesp[1,], col = "darkorange", lwd = 2, lty = 3) 
  lines(1 - gini.ROC.neg$matrix.sesp[2,], gini.ROC.neg$matrix.sesp[1,], col = "darkgreen", lwd = 2, lty = 3) 
  lines(1 - info.ROC.neg$matrix.sesp[2,], info.ROC.neg$matrix.sesp[1,], col = "navy", lwd = 2, lty = 3) 
  legend("bottomright", c(paste("Gini Unweighted, AUC =", gini.ROC.unweight$AUC),
                          paste("Info Unweighted, AUC =", info.ROC.unweight$AUC),
                          paste("Gini Neg. Weight, AUC =", gini.ROC.neg$AUC),
                          paste("Info Neg. Weight, AUC =", info.ROC.neg$AUC)),
         col = c("darkviolet", "darkorange", "darkgreen", "navy"),
         lwd=2, lty=rep(3), cex = 0.8, bty = "n")

Info.neg.weight (Entropy model with false negative weighted 10x) proves to be the best fit model with the AUC closest to 1.000 (\(AUC_{info.neg.weight} = 0.982\)). With the greatest AUC value, this model will provide the most accurate predictions of the four decision tree models compared.

5 Bootstrap Prediction

Finally, a bootstrap prediction model with bagging was implemented to provide an additional potential predictive model for breast cancer biopsy outcomes.

Assumptions of the bootstrap model:

  • Sample is a random sample of the population
  • Sample size is large enough so the sample distribution mirrors the population distribution

To note: the bootstrap model does not assume any specific distribution of the data prior to model fitting.

An initial bootstrap model was run to predict the biopsy outcome but all AUC values returned as 1.000 indicating model over-fitting of the model. In order to prevent over-fitting and thus inaccurate future predictions, a bagging ensemble was utilized.

5.1 Bagging

The data was subset into test and train data, in a 70:30 random split, with no replacement. An entropy was utilized and false negatives were again weighted 10x of a false positive. Bagging was employed to force the model to aggregate the parallel models and thus reduce variance across models. Additionally, bagging does not allow a single observation to be repeated in a sample, further reducing variance.

The bagged model was first trained on the training data.

data.boot <- raw.data
#converts from 1/2 to 0/1 variable
no.out.boot <- which(data.boot$Outcome == "No") 
yes.out.boot <-  which(data.boot$Outcome == "Yes")
data.boot$out = 0
data.boot$out[yes.out.boot] = 1
data.boot <- data.boot[,-10]  #remove original outcome variable to prevent overfitting
nbag = dim(data.boot)[1] #sample size
train.boot.id <- sample(1:nbag, round(0.7*nbag), replace = FALSE) #split with no replacement
train.boot <- data.boot[train.boot.id,] #test data
test.boot <- data.boot[-train.boot.id,] #train data
outcome.bag.train <- bagging(as.factor(out) ~ .,
                             data = train.boot,
                             nbagg = 150,
                             coob = TRUE,
                             parms = list(loss = matrix(c(0,1,10,0),
                                                        byrow = TRUE),
                                                        split = "info"),  
                              control = rpart.control(minsplit = 10, 
                                                      cp = 0.02))

bag.predict <- predict(outcome.bag.train, train.boot, type ="prob")

boot.cutoff <- seq(0,1,length = 20)

boot.snsp.matrix <- matrix(0, ncol = length(boot.cutoff), nrow = 3, byrow = FALSE)
  
  for(i in 1:length(boot.cutoff)){
    pred.boot.out <- ifelse(bag.predict[,2] >= boot.cutoff[i], 1, 0)
    
    TNb = sum(pred.boot.out == 0 & train.boot$out == 0) # true negative
    FNb = sum(pred.boot.out == 0 & train.boot$out == 1) # false negative
    FPb = sum(pred.boot.out == 1 & train.boot$out == 0) # false positive
    TPb = sum(pred.boot.out == 1 & train.boot$out == 1) # true positive
    
    boot.snsp.matrix[1,i] = TPb/(TPb + FNb) #sensitivity
    boot.snsp.matrix[2,i] = TNb/(TNb + FPb) #specificity
    boot.snsp.matrix[3,i] = (TPb + TNb)/(TPb + TNb + FNb + FPb) #accuracy
  }

  prediction.boot <-  bag.predict[,1]
  category.boot <- train.boot$out == 1
  ROCobj.boot <- roc(category.boot, prediction.boot)
  bootAUC <- auc(ROCobj.boot)

Once trained, the bagged model was then re-run on the test data to ensure an equal or better fit to the utilized training data. This ensures the model was not over-fitted to the training data and is able to be utilized for predictions.

outcome.bag.test <- bagging(as.factor(out) ~ .,
                             data = test.boot,
                             nbagg = 150,
                             coob = TRUE,
                             parms = list(loss = matrix(c(0,1,10,0),
                                                        byrow = TRUE),
                                                        split = "info"),  
                              control = rpart.control(minsplit = 10, 
                                                      cp = 0.02))

bag.predict.test <- predict(outcome.bag.test, test.boot, type ="prob")

boot.cutoff.test <- seq(0,1,length = 20)

boot.snsp.matrix.test <- matrix(0, ncol = length(boot.cutoff.test), nrow = 3, byrow = FALSE)
  
  for(i in 1:length(boot.cutoff.test)){
    pred.boot.out.test <- ifelse(bag.predict.test[,2] >= boot.cutoff.test[i], 1, 0)
    
    TNbt = sum(pred.boot.out.test == 0 & test.boot$out == 0) # true negative
    FNbt = sum(pred.boot.out.test == 0 & test.boot$out == 1) # false negative
    FPbt = sum(pred.boot.out.test == 1 & test.boot$out == 0) # false positive
    TPbt = sum(pred.boot.out.test == 1 & test.boot$out == 1) # true positive
    
    boot.snsp.matrix.test[1,i] = TPbt/(TPbt + FNbt) #sensitivity
    boot.snsp.matrix.test[2,i] = TNbt/(TNbt + FPbt) #specificity
    boot.snsp.matrix.test[3,i] = (TPbt + TNbt)/(TPbt + TNbt + FNbt + FPbt) #accuracy
  }

  prediction.boot.test <-  bag.predict.test[,1]
  category.boot.test <- test.boot$out == 1
  ROCobj.boot.test <- roc(category.boot.test, prediction.boot.test)
  bootAUC.test <- auc(ROCobj.boot.test)
  
nl.test <- length(boot.snsp.matrix.test[3,])
idx.test <- which(boot.snsp.matrix.test[3,] == max(boot.snsp.matrix[3,]))  #max accuracy
tick.test <- as.character(round(boot.cutoff.test, 2))

Finally the bagged model was run a third time on the entire dataset to allow for comparrison across all models utilized as potential predictive models. Checking against the full dataset again also ensures no over-fitting took place during model construction.

outcome.bag.full <- bagging(as.factor(out) ~ .,
                             data = data.boot,
                             nbagg = 150,
                             coob = TRUE,
                             parms = list(loss = matrix(c(0,1,10,0),
                                                        byrow = TRUE),
                                                        split = "info"),  
                              control = rpart.control(minsplit = 10, 
                                                      cp = 0.02))

bag.predict.full <- predict(outcome.bag.full, data.boot, type ="prob")

boot.cutoff.full <- seq(0,1,length = 20)

boot.snsp.matrix.full <- matrix(0, ncol = length(boot.cutoff.full), nrow = 3, byrow = FALSE)
  
  for(i in 1:length(boot.cutoff.full)){
    pred.boot.out.full <- ifelse(bag.predict.full[,2] >= boot.cutoff.full[i], 1, 0)
    
    TNbf = sum(pred.boot.out.full == 0 & data.boot$out == 0) # true negative
    FNbf = sum(pred.boot.out.full == 0 & data.boot$out == 1) # false negative
    FPbf = sum(pred.boot.out.full == 1 & data.boot$out == 0) # false positive
    TPbf = sum(pred.boot.out.full == 1 & data.boot$out == 1) # true positive
    
    boot.snsp.matrix.full[1,i] = TPbf/(TPbf + FNbf) #sensitivity
    boot.snsp.matrix.full[2,i] = TNbf/(TNbf + FPbf) #specificity
    boot.snsp.matrix.full[3,i] = (TPbf + TNbf)/(TPbf + TNbf + FNbf + FPbf) #accuracy
  }

  prediction.boot.full <-  bag.predict.full[,1]
  category.boot.full <- data.boot$out == 1
  ROCobj.boot.full <- roc(category.boot.full, prediction.boot.full)
  bootAUC.full <- auc(ROCobj.boot.full)
  
nl.full <- length(boot.snsp.matrix.full[3,])
idx.full <- which(boot.snsp.matrix.full[3,] == max(boot.snsp.matrix[3,]))  #max accuracy
tick.full <- as.character(round(boot.cutoff.full, 2))
label <- "BAGGING Bootstrap Model: 
Entropy Model Weighted 10x for False Negatives

(A missed diagnosis of malignancy 10x worse than
a false positive diagnosis of malignancy at biopsy)"

plot((1 - boot.snsp.matrix[2,]), boot.snsp.matrix[1,],
     type = "l",
     xlim = c(0,1),
     ylim = c(0,1),
     xlab = "1 - Specificity (False Positive)",
     ylab = "Sensitivity (True Negative)",
     col = "darkviolet",
     lwd = 2,
     lty = 1,
     main = "ROC Curve of BAGGING Bootstrap Training Data") 
abline(0,1, lty = 2, col = "darkred", lwd = 2) 
lines((1 - boot.snsp.matrix.test[2,]), boot.snsp.matrix.test[1,], col = "darkorange", lwd = 2,  lty = 1)
lines((1 - boot.snsp.matrix.full[2,]), boot.snsp.matrix.full[1,], col = "darkblue", lwd = 2,  lty = 1)
text(0.75, 0.4, label)
legend("bottomright", c(paste("Info Weighted Train, AUC =", bootAUC),
                        paste("Info Weighted Test, AUC =", bootAUC.test),
                        paste("Info Weighted Full, AUC =", bootAUC.full)),
         col = c("darkviolet", "darkorange", "darkblue"),
         lwd=2, lty=rep(1), cex = 0.8, bty = "n")

6 Final Model Comaprrison & Conclusions

Each model explored returned a high AUC value and overall demonstrated a good fit and high predictive possibility for the breast cancer biopsy data. In order to determine the true best fit model, all four ROC curves and AUC values were plotted for comparison.

Both the reduced logistic regression model and bagged model have near identical AUC values (0.994 and 0.993 respectively), and both can be utilized to accurately predict the outcome values for breast cancer biopsy data.

plot((1 - boot.snsp.matrix.full[2,]), boot.snsp.matrix.full[1,],
     type = "l",
     xlim = c(0,1),
     ylim = c(0,1),
     xlab = "1 - Specificity (False Positive)",
     ylab = "Sensitivity (True Negative)",
     col = "darkviolet",
     lwd = 2,
     lty = 1,
     main = "ROC Curve of BAGGING Bootstrap Training Data") 
abline(0,1, lty = 2, col = "darkred", lwd = 2) 
lines((1 - info.ROC.neg$matrix.sesp[2,]), info.ROC.neg$matrix.sesp[1,], col = "darkorange", lwd = 2,  lty = 1)
lines((1 - ROCobj.nn$sensitivities), ROCobj.nn$specificities, col = "darkblue", lwd = 2,  lty = 1)
lines((1 - sensitiv), specif2, col = "darkgreen", lwd = 2,  lty = 1)
legend("bottomright", c(paste("BAGGED Bootstrap Model, AUC =", round(bootAUC.full, 3)),
                        paste("Entropy Neg. Weighted Model, AUC =", info.ROC.neg$AUC),
                        paste("Neural Net Perceptron Model AUC =", round(ROCobj.nn$auc, 3)),
                        paste("Reduced Linear Regression Model AUC =", round(ROCobj2$auc, 3))),
         col = c("darkviolet", "darkorange", "darkblue", "darkgreen"),
         lwd=2, lty=rep(1), cex = 0.8, bty = "n")

plot((1 - boot.snsp.matrix.full[2,]), boot.snsp.matrix.full[1,],
     type = "l",
     xlim = c(0,1),
     ylim = c(0,1),
     xlab = "1 - Specificity (False Positive)",
     ylab = "Sensitivity (True Negative)",
     col = "darkviolet",
     lwd = 3,
     lty = 1,
     main = "ROC Curves of BAGGED Bootstrap &
Reduced Logistic Regression Models") 
abline(0,1, lty = 2, col = "darkred", lwd = 2) 
lines((1 - sensitiv), specif2, col = "darkgreen", lwd = 3,  lty = 1)
legend("bottomright", c(paste("BAGGED Bootstrap Model, AUC =", round(bootAUC.full, 3)),
                        paste("Reduced Linear Regression Model AUC =", round(ROCobj2$auc, 3))),
         col = c("darkviolet", "darkgreen"),
         lwd=2, lty=rep(1), cex = 0.8, bty = "n")

LS0tDQp0aXRsZTogIkJsaW5kIEJyZWFzdCBCaW9wc2llcyA6PGltZyBzcmM9XCJodHRwczovL25sZXBlcmEuZ2l0aHViLmlvL3N0YTU1MS9IVzAxL2ltZy9wZW5ndWluX2N1dGUucG5nXCIgc3R5bGU9XCJmbG9hdDogcmlnaHQ7IHdpZHRoOiAxMiVcIi8+Ig0Kc3VidGl0bGU6ICJQcmVkaWN0aW5nIEJyZWFzdCBUaXNzdWUgQmlvcHN5IERpYWdub3NpcyBJbiB0aGUgRXZlbnQgb2YgSW5jb21wbGV0ZSBCaW9wc3kiDQphdXRob3I6DQotIG5hbWU6IE5hdGFsaWUgTGVQZXJhDQogIGFmZmlsaWF0aW9uOiBXZXN0IENoZXN0ZXIgVW5pdmVyc2l0eSB8IFNUQTU1MSAtIEhXIDAzDQpkYXRlOiAiMTQgTm92IDIwMjQiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICB0b2NfY29sbGFwc2U6IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogdHJ1ZQ0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIGZpZ19hbGlnbjogY2VudGVyDQplZGl0b3Jfb3B0aW9uczogDQogIG1hcmtkb3duOiANCiAgICB3cmFwOiA3Mg0KLS0tDQoNCmBgYHtjc3MsIGVjaG8gPSBGQUxTRX0NCmgxLnRpdGxlIHsgIC8qIFRpdGxlIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiB0aGUgcmVwb3J0IHRpdGxlICovDQogIGZvbnQtd2VpZ2h0OmJvbGQ7DQogIGNvbG9yOiBkYXJrbWFnZW50YSA7DQp9DQpoMS5zdWJ0aXRsZSB7ICAvKiBUaXRsZSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgdGhlIHJlcG9ydCB0aXRsZSAqLw0KICBmb250LXdlaWdodDpib2xkOw0KICBjb2xvcjogZGFya21hZ2VudGEgOw0KfQ0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBhdXRob3JzICAqLw0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogbmF2eTsNCn0NCmg0LmRhdGUgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIHRoZSBkYXRlICAqLw0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogbmF2eTsNCn0NCmgxIHsgLyogSGVhZGVyIDEgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAxIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC13ZWlnaHQ6Ym9sZDsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KaDIgeyAvKiBIZWFkZXIgMiAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGxldmVsIDIgc2VjdGlvbiB0aXRsZSAqLw0KICAgIGZvbnQtd2VpZ2h0OmJvbGQ7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDMgeyAvKiBIZWFkZXIgMyAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgMyBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtd2VpZ2h0OmJvbGQ7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDQgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgNCBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmJvZHkgew0KICBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOw0KfQ0KDQouaGlnaGxpZ2h0bWUgeyANCiAgYmFja2dyb3VuZC1jb2xvcjp5ZWxsb3c7IA0KfQ0KDQpwIHsgDQogIGJhY2tncm91bmQtY29sb3I6d2hpdGU7IA0KfQ0KDQpoNSB7DQogIGNvbG9yOiBuYXZ5Ow0KfQ0KDQouaWZyYW1lIHsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KDQphOmxpbmsgew0KICBjb2xvcjogZGFya21hZ2VudGE7DQp9DQoNCi5maWdsYWJlbCB7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgY29sb3I6IGRhcmtzbGF0ZWdyYXk7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KICBmb250LXNpemU6IDE4Ow0KfQ0KDQpgYGANCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQppZiAoIXJlcXVpcmUoImRwbHlyIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJkcGx5ciIpDQp9DQoNCmlmICghcmVxdWlyZSgic3RyaW5nciIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygic3RyaW5nciIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJzdHJpbmdyIikNCn0NCg0KaWYgKCFyZXF1aXJlKCJwbG90bHkiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoInBsb3RseSIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJwbG90bHkiKQ0KfQ0KDQppZiAoIXJlcXVpcmUoInBhbmRvYyIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygicGFuZG9jIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInBhbmRvYyIpDQp9DQoNCmlmICghcmVxdWlyZSgiZ3JpZEV4dHJhIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJncmlkRXh0cmEiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiZ3JpZEV4dHJhIikNCn0NCg0KaWYgKCFyZXF1aXJlKCJncmlkIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJncmlkIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImdyaWQiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJyYXN0ZXIiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoInJhc3RlciIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJyYXN0ZXIiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJtaWNlIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJtaWNlIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoIm1pY2UiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJwUk9DIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJwUk9DIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInBST0MiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJnZ3JpZGdlcyIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiZ2dyaWRnZXMiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiZ2dyaWRnZXMiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJrbml0ciIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgia25pdHIiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJHR2FsbHkiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIkdHYWxseSIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJHR2FsbHkiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJnZ3Bsb3QyIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImdnbHBvdDIiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJNQVNTIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJNQVNTIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoIk1BU1MiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJrYWJsZUV4dHJhIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJrYWJsZUV4dHJhIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImthYmxlRXh0cmEiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJuZXVyYWxuZXQiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIm5ldXJhbG5ldCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJuZXVyYWxuZXQiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJycGFydCIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygicnBhcnQiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgicnBhcnQiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJycGFydC5wbG90IikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJycGFydC5wbG90IiwgZGVwZW5kZW5jaWVzID0gVFJVRSkgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInJwYXJ0LnBsb3QiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJpcHJlZCIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiaXByZWQiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiaXByZWQiKQ0KfQ0KDQojIFRoZSBmb2xsb3dpbmcgUiBzb3VyY2UgY29kZSB3aWxsIGJlIHVzZSB0byBwbG90IHRoZSBwYXRoIHBsb3QNCiMgb2YgbmV1cmFsIG5ldHdvcmsgbW9kZWwgd2l0aCBlc3RpbWF0ZWQgd2VpZ2h0cyBmcm9tIHRoZSBkYXRhLg0Kc291cmNlKCJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9TVEE1NTEvcGxvdE5ldXJhbG5ldFJmdW4udHh0IikNCg0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsICAgDQogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID0gVFJVRSwgICANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BLA0KICAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbiA9ICdjZW50ZXInKQ0KDQpvcHRpb25zKERULm9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSA1LCBzY3JvbGxYID0gVFJVRSkpDQpgYGANCg0KIyBJbnRyb2R1Y3Rpb24NCg0KQXMgcmVwb3J0ZWQgYnkgdGhlIEFtZXJpY2FuIENhbmNlciBTb2NpZXR5LCBhcyBvZiAyMDI0IGJyZWFzdCBjYW5jZXINCmFjY291bnRzIGZvciBhcHByb3hpbWF0ZWx5IDEgaW4gMyBvZiBhbGwgbmV3IGZlbWFsZSBjYW5jZXIgY2FzZXMgaW4gdGhlDQpVbml0ZWQgU3RhdGVzIGFubnVhbGx5LiBEZXNwaXRlIHRoZSBzdGVhZGlseSBkZWNsaW5pbmcgcmF0ZXMgaW4gYnJlYXN0DQpjYW5jZXIgbW9ydGFsaXR5LCBicmVhc3QgY2FuY2VyIGlzIHN0aWxsIHRoZSBzZWNvbmQgbGVhZGluZyBjYXVzZSBvZg0KY2FuY2VyIGRlYXRoIGluIHdvbWVuLiBBcyBlYXJseSBkZXRlY3Rpb24gYW5kIGRpYWdub3NpcyBpcyBwaXZvdGFsIHRvDQppbXByb3ZlZCB0cmVhdG1lbnQgb3V0Y29tZSwgaW5jcmVhc2luZyBuZWVkIGhhcyBiZWVuIGlkZW50aWZpZWQgZm9yDQppbXByb3ZlZCBwcmVkaWN0aXZlIG1vZGVscyB0byBhc3Npc3QgaW4gcmFwaWQgZGlhZ25vc2lzLg0KDQpUaXNzdWUgYmlvcHN5IGlzIGN1cnJlbnRseSB0aGUgb25seSBkaWFnbm9zdGljIHRvb2wgdG8gZGV0ZXJtaW5lIGNhbmNlcg0KY29udGVudCBpbiBpZGVudGlmaWVkIGJyZWFzdCB0dW1vcnMuIE1ham9yaXR5IG9mIGJyZWFzdCB0aXNzdWUgYmlvcHN5IGlzDQpjb21wbGV0ZWQgdXRpbGl6aW5nIGEgY29yZSBuZWVkbGUgYmlvcHN5OyBhIHByb2NlZHVyZSB0aGF0IGNvbGxlY3RzIDEgb3INCm11bHRpcGxlIHNtYWxsIGN5bGluZGVycyBvZiB0dW1vciB0aXNzdWUgdXNpbmcgYSBmaW5lIGdhdWdlIGhvbGxvdw0KbmVlZGxlLiBBcyBpbGx1c3RyYXRlZCBpbiB0aGUgaW1hZ2UgYmVsb3csIHRoZXJlIGlzIGEgY2hhbmNlIHRoYXQgYW4NCmluY29tcGxldGUgdHVtb3IgcHVuY2ggY2FuIGJlIGNvbGxlY3RlZCwgbGVhZGluZyB0byBpbmNyZWFzZWQgZGlmZmljdWx0eQ0KaW4gZGlhZ25vc2lzLiBDb3JlIG5lZWRsZSBiaW9wc2llcyBhcmUgcHJlZmVycmVkIGJ5IHBoeXNpY2lhbnMgYW5kDQpwYXRpZW50cyBkdWUgdG8gdGhlIGluY3JlYXNlZCB0aXNzdWUgdm9sdW1lIG9idGFpbmVkIGNvbXBhcmVkIHRvIGEgZmluZQ0KbmVlZGxlIGFzcGlyYXRlIGJpb3BzeSBhbmQgdGhlIGFiaWxpdHkgdG8gYmlvcHN5IGluIGFuIG91dHBhdGllbnQNCnNldHRpbmcgd2l0aG91dCBzdXJnZXJ5Lg0KDQpbIVtJbWFnZSAxOiBDb3JlIE5lZWRsZSBCcmVhc3QgVGlzc3VlDQpCaW9wc3ldKGltYWdlcy9jb3JlLW5lZWRsZS1iaW9wc3kuZ2lmKXt3aWR0aD0iNTE0In1dKGh0dHBzOi8vd3d3LmNhbmNlci5vcmcvY2FuY2VyL3R5cGVzL2JyZWFzdC1jYW5jZXIvc2NyZWVuaW5nLXRlc3RzLWFuZC1lYXJseS1kZXRlY3Rpb24vYnJlYXN0LWJpb3BzeS9jb3JlLW5lZWRsZS1iaW9wc3ktb2YtdGhlLWJyZWFzdC5odG1sKQ0KDQojIyBQdXJwb3NlIG9mIEludmVzdGlnYXRpb24NCg0KSW4gb3JkZXIgdG8gaW1wcm92ZSBicmVhc3QgY2FuY2VyIGRpYWdub3N0aWMgY2FwYWJpbGl0aWVzIHRocm91Z2gNCm1pbmltYWwgdGlzc3VlIGNvbGxlY3Rpb24gYmlvcHNpZXMgc3VjaCBhcyBjb3JlIG5lZWRsZSBwdW5jaCBiaW9wc2llcywNCmltcHJvdmVkIHByZWRpY3RpdmUgbW9kZWxzIG11c3QgYmUgZGV2ZWxvcGVkIHRvIGFzc2lzdCBpbiBmaWxsaW5nIGluIHRoZQ0KYmxhbmtzIGluIHRoZSBldmVudCBhbiBpbmNvbXBsZXRlIHB1bmNoIGJpb3BzeS4gSW5jcmVhc2luZyBwcmVkaWN0aXZlDQphYmlsaXR5IHdpdGggbWluaW1hbCB0aXNzdWUgcmVxdWlyZW1lbnRzIHdpbGwgaW4gZWZmZWN0IHJlZHVjZSBwYXRpZW50DQpidXJkZW4gYW5kIGluY3JlYXNlIHRoZSBzcGVlZCBhdCB3aGljaCBtYWxpZ25hbnQgdGlzc3VlIGlzIGRpYWdub3NlZC4NCg0KIyMgRGF0YSBVdGlsaXplZA0KDQpgYGB7cn0NCnJhdy5kYXRhIDwtIHJlYWQuY3N2KCJodHRwczovL25sZXBlcmEuZ2l0aHViLmlvL3N0YTU1MS9IVzAzL2RhdGEvc3ludGhldGljQnJlYXN0Q2FuY2VyRGF0YS5jc3YiKSAlPiUgDQogIHN1YnNldCggLCBzZWxlY3QgPSBjKDI6MTEpKQ0KDQpjb2xuYW1lcyhyYXcuZGF0YSlbY29sbmFtZXMocmF3LmRhdGEpID09ICJTaW5nbGVfRXBpdGhlbGlhbF9DZWxsX1NpemUiXSA8LSAiZUNlbGxTaXplIg0KY29sbmFtZXMocmF3LmRhdGEpW2NvbG5hbWVzKHJhdy5kYXRhKSA9PSAiVGhpY2tuZXNzX29mX0NsdW1wIl0gPC0gImNsdW1wVGhpY2tuZXNzIg0KY29sbmFtZXMocmF3LmRhdGEpW2NvbG5hbWVzKHJhdy5kYXRhKSA9PSAiQ2VsbF9TaXplX1VuaWZvcm1pdHkiXSA8LSAic2l6ZVVuaWZvcm0iDQpjb2xuYW1lcyhyYXcuZGF0YSlbY29sbmFtZXMocmF3LmRhdGEpID09ICJDZWxsX1NoYXBlX1VuaWZvcm1pdHkiXSA8LSAic2hhcGVVbmlmb3JtIg0KY29sbmFtZXMocmF3LmRhdGEpW2NvbG5hbWVzKHJhdy5kYXRhKSA9PSAiTWFyZ2luYWxfQWRoZXNpb24iXSA8LSAibWFyZ0FkaCINCmNvbG5hbWVzKHJhdy5kYXRhKVtjb2xuYW1lcyhyYXcuZGF0YSkgPT0gIkJhcmVfTnVjbGVpIl0gPC0gImJhcmVOdWNsZWkiDQpjb2xuYW1lcyhyYXcuZGF0YSlbY29sbmFtZXMocmF3LmRhdGEpID09ICJCbGFuZF9DaHJvbWF0aW4iXSA8LSAiYmxDaHJvbSINCmNvbG5hbWVzKHJhdy5kYXRhKVtjb2xuYW1lcyhyYXcuZGF0YSkgPT0gIk5vcm1hbF9OdWNsZW9saSJdIDwtICJub3JtYWxOdWNsZW9saSINCg0KDQpyYXcuZGF0YSRPdXRjb21lIDwtIGFzLmZhY3Rvcihhcy5jaGFyYWN0ZXIocmF3LmRhdGEkT3V0Y29tZSkpDQpgYGANCg0KU3ludGhldGljIEJyZWFzdCBDYW5jZXIgRGF0YSB3YXMgb2J0YWluZWQgZnJvbSBBcHBsaWVkIEFuYWx5dGljcyB0aHJvdWdoDQpDYXNlIFN0dWRpZXMgdXNpbmcgU0FTIGFuZCBSIGJ5IERlZXB0aSBHdXB0YQ0KW1tsaW5rXV0oaHR0cHM6Ly9saW5rLnNwcmluZ2VyLmNvbS9ib29rLzEwLjEwMDcvOTc4LTEtNDg0Mi0zNTI1LTYgIkxpbmsgdG8gQXBwbGllZCBBbmFseXRpY3MgdGhyb3VnaCBDYXNlIFN0dWRpZXMgdXNpbmcgU0FTIGFuZCBSIGJ5IERlZXB0aSBHdXB0YSIpLg0KVGhlIGBzeW50aGV0aWNgIGRhdGEgc2V0IGluaXRpYWxseSBjb250YWluZWQgNjAwIG9ic2VydmF0aW9ucyBhbmQgMTENCnZhcmlhYmxlcy4gSW5pdGlhbCB2YXJpYWJsZSBuYW1lcyB3ZXJlIGNvbnZlcnRlZCBmb3IgZWFzZSBvZiB1dGlsaXphdGlvbg0KYW5kIHRoZSBmaXJzdCB2YXJpYWJsZSBgU2FtcGxlX05vYCB3YXMgZHJvcHBlZCBmb3IgYW5hbHlzaXMgcHVycG9zZXMsIGFzDQpgU2FtcGxlX05vYCB3YXMgYW4gdW5yZWxhdGVkIGlkZW50aWZpZXIga2V5IHJhdGhlciB0aGFuIGEgdHJ1ZSB2YXJpYWJsZS4NCg0KVmFyaWFibGVzIHJldGFpbmVkIGFyZSBhcyBmb2xsb3dzOg0KDQotICAgYGNsdW1wVGhpY2tuZXNzYCA6IEludGVnZXIgdmFsdWUgbWVhc3VyaW5nIHRoaWNrbmVzcyBvZiB0dW1vciBjbHVtcA0KICAgIG9idGFpbmVkIGJ5IGJpb3BzeS4gTW9uby1sYXllcnMgKGNsdW1wVGhpY2tuZXNzID0gMSkgaW5kaWNhdGUgYmVuaWduDQogICAgbWFzcyAobm8gY2FuY2VyKSB3aGlsZSBoaWdoZXIgdmFsdWVzIGluZGljYXRlIG1hbGlnbmFuY3kgKGNhbmNlcikuDQoNCi0gICBgc2l6ZVVuaWZvcm1gIDogSW50ZWdlciB2YWx1ZSBtZWFzdXJpbmcgdmFyaWF0aW9uIGluIGNlbGwgc2l6ZS4NCiAgICBHcmVhdGVyIHZhbHVlcyBpbmRpY2F0ZSBncmVhdGVyIHZhcmlhdGlvbiBpbiBjZWxsIHNpemUuIEluY3JlYXNlZA0KICAgIGNlbGwgc2l6ZSB2YXJpYXRpb24gY29uc2lkZXJlZCBhc3NvY2lhdGVkIHdpdGggbWFsaWduYW5jeS4NCg0KLSAgIGBzaGFwZVVuaWZvcm1gOiBJbnRlZ2VyIHZhbHVlIG1lYXN1cmluZyB2YXJpYXRpb24gaW4gY2VsbCBzaGFwZS4NCiAgICBHcmVhdGVyIHZhbHVlcyBpbmRpY2F0ZSBncmVhdGVyIHZhcmlhdGlvbiBpbiBjZWxsIHNoYXBlLiBJbmNyZWFzZWQNCiAgICBjZWxsIHNoYXBlIHZhcmlhdGlvbiBjb25zaWRlcmVkIGFzc29jaWF0ZWQgd2l0aCBtYWxpZ25hbmN5Lg0KDQotICAgYG1hcmdBZGhgOiBJbnRlZ2VyIHZhbHVlIG1lYXN1cmluZyB2YXJpYXRpb24gaW4gYWRoZXNpb24gYW1vbmcNCiAgICBuZWlnaGJvcmluZyBjZWxscyB3aXRoaW4gYSBjbHVtcC4gR3JlYXRlciB2YWx1ZXMgaW5kaWNhdGUgZ3JlYXRlcg0KICAgIGFkaGVzaW9uIGJldHdlZW4gbmVpZ2hib3JpbmcgY2VsbHMuIERlY3JlYXNlZCBhZGhlc2lvbiBjb25zaWRlcmVkDQogICAgYXNzb2NpYXRlZCB3aXRoIG1hbGlnbmFuY3kuDQoNCi0gICBgZUNlbGxTaXplYCA6IEludGVnZXIgdmFsdWUgbWVhc3VyaW5nIHJlc3BlY3RpdmUgc2l6ZSBvZiBzaW5nbGUNCiAgICBlcGl0aGVsaWFsIGNlbGxzIG9idGFpbmVkIGZyb20gYmlvcHN5LiBHcmVhdGVyIHZhbHVlcyBpbmRpY2F0ZQ0KICAgIGdyZWF0ZXIgZXBpdGhlbGlhbCBjZWxsIHNpemUuIEluY3JlYXNlZCBlcGl0aGVsaWFsIGNlbGwgc2l6ZQ0KICAgIGNvbnNpZGVyZWQgYXNzb2NpYXRlZCB3aXRoIG1hbGlnbmFuY3kuDQoNCi0gICBgYmFyZU51Y2xlaWAgOiBJbnRlZ2VyIHZhbHVlIG1lYXN1cmluZyBjb3VudCBvZiBiYXJlIG51Y2xlaSBvYnNlcnZlZA0KICAgIGluIHNhbXBsZSBjZWxscy4gR3JlYXRlciB2YWx1ZXMgaW5kaWNhdGUgZ3JlYXRlciBudW1iZXIgb2YgY2VsbHMNCiAgICB3aXRoIGJhcmUgbnVjbGVpIHN1cnJvdW5kZWQgYnkgY3l0b3BsYXNtLiBJbmNyZWFzZWQgYmFyZSBudWNsZWkNCiAgICBjb3VudCBjb25zaWRlcmVkIGFzc29jaWF0ZWQgd2l0aCBtYWxpZ25hbmN5Lg0KDQotICAgYGJsQ2hyb21gIDogSW50ZWdlciB2YWx1ZSBtZWFzdXJpbmcgY29hcnNlbmVzcyBvZiBjaHJvbWF0aW4uIEdyZWF0ZXINCiAgICB2YWx1ZXMgaW5kaWNhdGUgZ3JlYXRlciBjaHJvbWF0aW4gY29hcnNlbmVzcy4gSW5jcmVhc2VkIGNocm9tYXRpbg0KICAgIGNvYXJzZW5lc3MgY29uc2lkZXJlZCBhc3NvY2lhdGVkIHdpdGggbWFsaWduYW5jeS4NCg0KLSAgIGBub3JtYWxOdWNsZW9saWA6IEludGVnZXIgdmFsdWUgbWVhc3VyaW5nIHNpemUgbm9ybWFsIChtZWFuKQ0KICAgIG51Y2xlb2xpIHNpemUgYXMgbWVhc3VyZWQgaW4gYmlvcHN5IHNhbXBsZS4gR3JlYXRlciB2YWx1ZXMgaW5kaWNhdGUNCiAgICBtZWFuIG51Y2xlb2xpIHNpemUuIEluY3JlYXNlZCBtZWFuIG51Y2xlb2xpIGNvbnNpZGVyZWQgYXNzb2NpYXRlZA0KICAgIHdpdGggbWFsaWduYW5jeS4NCg0KLSAgIGBNaXRvc2VzYCA6IEludGVnZXIgdmFsdWUgbWVhc3VyaW5nIHRoZSBudW1iZXIgb2YgY2VsbHMgdW5kZXJnb2luZw0KICAgIG1pdG9zaXMgb2JzZXJ2ZWQgaW4gdGhlIGJpb3BzeSBzYW1wbGUuIEdyZWF0ZXIgdmFsdWVzIGluZGljYXRlDQogICAgZ3JlYXRlciByYXRlcyBvZiBtaXRvc2lzLiBJbmNyZWFzZWQgbWl0b3NpcyByYXRlcyBhcmUgY29uc2lkZXJlZA0KICAgIGFzc29jaWF0ZWQgd2l0aCBtYWxpZ25hbmN5Lg0KDQotICAgYE91dGNvbWVgIDogQ2hhcmFjdGVyIHZhbHVlIGNsYXNzaWZ5aW5nIGZpbmFsIGJpb3BzeSBkaWFnbm9zaXMuICJObyINCiAgICBpbmRpY2F0ZXMgdHVtb3IgZGlhZ25vc2VkIGFzIGJlbmlnbiAobm8gY2FuY2VyKS4gIlllcyIgaW5kaWNhdGVzDQogICAgdHVtb3IgZGlhZ25vc2VkIGFzIG1hbGlnbmFudCAoY2FuY2VyKS4NCg0KT25jZSB0aGUgdmFyaWFibGUgbmFtZXMgd2VyZSB0cmFuc2Zvcm1lZCBpdCB3YXMgY29uZmlybWVkIHRoZSByZXN1bHRhbnQNCmRhdGEgc2V0IGNvbnRhaW5lZCA2MDAgb2JzZXJ2YXRpb25zIGFjcm9zcyAxMCB2YXJpYWJsZXMgd2l0aCAwIG1pc3NpbmcNCnZhbHVlcy4gQXMgbm8gdmFsdWVzIGFyZSBtaXNzaW5nIGZyb20gdGhlIGRhdGEgc2V0IGl0IHdhcyBkZXRlcm1pbmVkDQp0aGF0IG5vIGltcHV0YXRpb24gd2FzIG5lY2Vzc2FyeS4NCg0KYGBge3IsIGZpZy53aWR0aD05fQ0Ka2FibGUoc3VtbWFyeShyYXcuZGF0YSksIGNhcHRpb24gPSAiU3RydWN0dXJlIG9mIFN5bnRoZXRpYyBCcmVhc3QgQ2FuY2VyIERhdGEiLCBhbGlnbiA9ICJjIiwgc2l6ZSA9IDYpICU+JSANCiAga2FibGVfc3R5bGluZygpICU+JSANCiAgcm93X3NwZWMoMCwgZm9udF9zaXplID0gMTIpDQoNCmthYmxlKHNhcHBseShyYXcuZGF0YSwgZnVuY3Rpb24gKHgpIHN1bShpcy5uYSh4KSkpLCBjYXB0aW9uID0gIkNvdW50IG9mIEJsYW5rIFZhbHVlcyBpbiBmb3IgYWxsIExhYmVscyIsIGNvbC5uYW1lcyA9IGMoIkxhYmVscyIsICJDb3VudCBvZiBCbGFuayBWYWx1ZXMiKSkgJT4lIA0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpPbmUgZW5naW5lZXJlZCB2YXJpYWJsZSB3YXMgY3JlYXRlZCB0byBjb252ZXJ0IGBPdXRjb21lYCBmcm9tIGENCmNoYXJhY3RlciB2YXJpYWJsZSB0byBhIGJpbmFyeSBpbnRlZ2VyIHJlc3BvbnNlIHZhcmlhYmxlOg0KDQotICAgYG91dGAgOiBFbmdpbmVlcmVkIG91dGNvbWUgdmFyaWFibGUgY29udmVydGVkIHRvIGJpbmFyeSBpbnRlZ2VyDQogICAgcmVzcG9uc2UgdmFyaWFibGUuIDEgPSAiTm8iIChiaW9wc3kgZGlhZ25vc2VkIGFzIGJlbmlnbikgYW5kIDIgPQ0KICAgICJZZXMiIChiaW9wc3kgZGlhZ25vc2VkIGFzIG1hbGlnbmFudCkuDQoNCkEgYnJpZWYgc3VtbWFyeSBvZiB0aGUgdmFyaWFibGVzIHdpdGggb3JpZ2luYWwgdmFyaWFibGUgdmFsdWVzIGFuZA0KYWRkaXRpb25hbCBzdXBwbGltZW50YWwgZGV0YWlscyBjYW4gYmUgZm91bmQgYXQ6DQpbW2xpbmtdXShodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy9CcmVhc3RDYW5jZXIvc3ludGhldGljQnJlYXN0Q2FuY2VyRGF0YS1EZXNjcmlwdGlvbi5wZGYgIlN5bnRoZXRpYyBCcmVhc3QgQ2FuY2VyIERhdGEgVmFyaWFibGUgRGV0YWlscyBTdW1tYXJ5Iil7LnVyaX0uDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiBkYXRhDQoNClVwb24gdmlzdWFsaXppbmcgdGhlIHBhaXJ3aXNlIGRpc3RyaWJ1dGlvbiBvZiBhbGwgaW50ZWdlciB2YXJpYWJsZXMsIGNvbnRyb2xsZWQgZm9yIE91dGNvbWUsIGl0IGJlY29tZXMgZXZpZGVudCB0aGF0IHRoZXJlIGFyZSBtdWx0aXBsZSBzaWduaWZpY2FudCBjb3JyZWxhdGlvbnMgYWNyb3NzIG11bHRpcGxlIHZhcmlhYmxlcy4gMTYgY29tYmluYXRpb25zIGRlbW9uc3RyYXRlIGEgY29ycmVsYXRpb24gZ3JlYXRlciB0aGFuIDAuNjAwIGluZGljYXRpbmcgYSBoaWdoIHZhbHVlIG9mIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlc2UgdmFyaWFibGUgcGFpcnMuIFRoZSBncmVhdGVzdCBjb3JyZWxhdGlvbiBzZWVuIHdhcyBiZXR3ZWVuIGBzaGFwZVVuaWZvcm1gIGFuZCBgc2l6ZVVuaWZvcm1gaW5kaWNhdGluZyBhbiBpbmNyZWFzZSBpbiBjZWxsIHNoYXBlIHZhcmlhdGlvbiBpcyBhY2NvbXBhbmllZCBieSBhbiBpbmNyZWFzZSBpbiBjZWxsIHNpemUgdmFyaWF0aW9uLg0KDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PSAxMiwgZmlnLndpZHRoPTEyLCB3YXJuaW5nPUZBTFNFfQ0KDQoNCmdncGFpcnMocmF3LmRhdGEsIA0KICAgICAgICBjb2x1bW5zID0gYygxOjEwKSwgDQogICAgICAgIGFlcyhhbHBoYSA9IDAuMDUsIGNvbG9yID0gT3V0Y29tZSksIA0KICAgICAgICBsb3dlciA9IGxpc3QoY29udGludW91cyA9IHdyYXAoInNtb290aCIsIG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIGFscGhhID0gMC4yNSkpLCANCiAgICAgICAgdXBwZXIgPSBsaXN0KGNvbnRpbnVvdXMgPSB3cmFwKCJjb3IiLCBzaXplID0gMykpKSArDQogIHRoZW1lICgNCiAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA3KSwNCiAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpLA0KICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpLA0KICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSwNCiAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpDQogICkNCg0KYGBgDQoNCg0KVGhlIGhpZ2ggY29ycmVsYXRpb24gdmFsdWVzIGFjcm9zcyBuZWFybHkgYWxsIHZhcmlhYmxlcyBpbiB0aGUgZGF0YSBzZXQgdW5kZXJwaW4gdGhlIG5lZWQgZm9yIGEgY29tcGxleCBtb2RlbGluZyBwcm9jZWR1cmUgdG8gY3JlYXRlIGFjY3VyYXRlIHByZWRpY3Rpb25zIG9mIHRoZSBiaW9wc3kgZGlhZ25vc2lzLg0KDQoNCiMgTG9naXN0aWMgUHJlZGljdGlvbg0KDQpUd28gbG9naXN0aWMgcHJlZGljdGlvbiBtb2RlbHMgd2VyZSBjcmVhdGVkIHRvIGV4YW1pbmUgdGhlIHBvdGVudGlhbCBwcmVkaWN0aXZlIHZhbHVlIG9mIGEgbXVsdGlwbGUgdmFyaWFibGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBpbiBkZXRlcm1pbmluZyBicmVhc3QgY2FuY2VyIGJpb3BzeSBvdXRjb21lcy4gIEFuIGluaXRpYWwgbW9kZWwgaW5jbHVkaW5nIGFsbCB2YXJpYWJsZXMgd2FzIGNyZWF0ZWQsIGFuZCB0aGVuIHRyaW1tZWQgZG93biB0byBjcmVhdGUgYSBzZWNvbmRhcnkgcmVkdWNlZCBtb2RlbCBmb3IgbW9yZSBwcmVjaXNlIHJlZ3Jlc3Npb24gYW5hbHlzaXMgYW5kIHBvdGVudGlhbCBmdXR1cmUgcHJlZGljdGlvbnMuIA0KDQojIyBGaXJzdCBNb2RlbCAoZnVsbC5tb2RlbC5sb2dpdCkNCg0KVG8gc3RhcnQsIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB3YXMgY3JlYXRlZCB0byBjb21wYXJlIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBgT3V0Y29tZWAgdmFyaWFibGUgYW5kIGFsbCBpbnRlZ2VyIHZhcmlhYmxlcyBpbiB0aGUgZGF0YSBzZXQuICBUaGlzIGluaXRpYWwgbW9kZWwgaXMgcmVmZXJyZWQgdG8gaGVuY2Vmb3J0aCBhcyBgZnVsbC5tb2RlbC5sb2dpdGAuICBUaGUgYGZ1bGwgbW9kZWxgZnVsbC5tb2RlbC5sZWdpdGAgaXMgcmVwcmVzZW50ZWQgYXMgZm9sbG93czogIA0KDQpPdXRjb21lIFx+IENsdW1wIFRoaWNrbmVzcyArIENlbGwgU2l6ZSBVbmlmb3JtaXR5ICsgQ2VsbA0KU2hhcGUgVW5pZm9ybWl0eSArIE1hcmdpbmFsIEFkaGVzaW9uICsgU2luZ2xlIEVwaXRoZWxpYWwgQ2VsbCBTaXplICsNCkJhcmUgTnVjbGVpICsgQmxhbmQgQ2hyb21hdGluICsgTm9ybWFsIE51Y2xlb2xpICsgTWl0b3Nlcw0KDQpgYGB7cn0NCmZ1bGwubW9kZWwubG9naXQgPC0gZ2xtKE91dGNvbWUgfiAuLCBmYW1pbHkgPSBiaW5vbWlhbCwgZGF0YSA9IHJhdy5kYXRhKQ0KYGBgDQoNCkEgc3VtbWFyeSBvZiByZWdyZXNzaW9uIGNvZWZmaWNpZW50cywgeiB2YWx1ZXMsIGFuZCBwcm9iYWJpbGl0eSBvZiBhc3NvY2lhdGVkIHogdmFsdWVzIChwIHZsYXVlIGFzc29jaWF0ZWQgd2l0aCBsaXN0ZWQgeiB2YWx1ZSkgZm9yIGVhY2ggdmFyaWFibGUgaW4gdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgaW5jbHVkZWQgYmVsb3cuICBNdWx0aXBsZSB2YXJpYWJsZXMgaWxsdXN0cmF0ZSBhIHN0cm9uZyBjb3JyZWxhdGlvbiBhcyBpbmRpY2F0ZWQgYnkgZXN0aW1hdGUgdmFsdWVzIGNsb3NlciB0byAxLjAsIHdpdGggc3Ryb25nIHN0YXRpc3RpYyBzaWduaWZpY2FuY2UgYXMgaW5kaWNhdGVkIGJ5IFByKHx6fCkgdmFsdWVzIGxlc3MgdGhhbiAwLjA1MDAwMC4gIFRoZSBzdHJvbmdlciBhIGNvcnJlbGF0aW9uIGluZGljYXRlZCBpbiB0aGUgRXN0aW1hdGUgdmFsdWUsIHRoZSBzdHJvbmdlciB0aGF0IHZhcmlhYmxlIHdpbGwgc2VydmUgYXMgYSBwcmVkaWN0aXZlIHZhbHVlIGR1cmluZyBwcmVkaWN0aXZlIG1vZGVsIGNyZWF0aW9uLiAgDQoNCkEgZ29vZCBmaXQgbW9kZWwgd2lsbCByZWx5IG9uIGJvdGggY29ycmVsYXRpb24gYW5kIHN0YXRpc3RpY2FsIHNpZ25maWNhbmNlIHRvIHByb2RjdWUgYSBtb2RlbCB0aGF0IGlzIHNlbnNpdGl2ZSwgc3BlY2lmaWMsIGFuZCBhY2N1cmF0ZS4gDQoNCmBgYHtyfQ0Ka2FibGUoc3VtbWFyeShmdWxsLm1vZGVsLmxvZ2l0KSRjb2VmLCBjYXB0aW9uID0gIkxvZ2lzdGljIFJlZ3Jlc3Npb24gQ29lZmZpY2llbnRzIGZvciBGdWxsIE1vZGVsIiwgY29sLm5hbWVzID0gYygiIiAsICJFc3RpbWF0ZSIsICJTdGQuIEVycm9yIiwgInogVmFsdWUiLCAnUHIgKHw+enwpJyksIGZvcm1hdCA9ICJodG1sIikgJT4lIA0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQojIyBTZWNvbmQgTW9kZWwgKHJlZHVjZWQubW9kZWwubG9naXQpDQoNClRvIG9idGFpbiBpbXByb3ZlZCBwcmVkaWN0aXZlIHZhbHVlcyBmcm9tIGEgbW9kZWwsIHJlZHVjaW5nIHRoZSBudW1iZXIgb2YgdmFyaWFibGVzIGZvciBhbmFseXNpcyBhbmQgaW5jbHVzaW9uIGNhbiBpbXByb3ZlIG1vZGVsIGZpdHRpbmcgd2l0aG91dCBhY2NpZGVudGFsIG92ZXIgZml0dGluZy4gIEluIG9yZGVyIHRvIGJlc3Qgc2VsZWN0IHRoZSB2YXJpYWJsZXMgZm9yIHJldGVudGlvbiBpbiB0aGUgcmVkdWNlZCBtb2RlbCwgYW4gYXV0b21hdGVkIGBzdGVwKClgIGZ1bmN0aW9uIHdhcyB1dGlsaXplZCB0byBpZGVudGlmeSBiZXN0IGZpdCByZWR1Y2VkIG1vZGVsLiBUcmFjZSB3YXMgaW5jbHVkZWQgdG8gaWxsdXN0cmF0ZSB0aGUgc3RlcC13aXNlIHJlbW92YWwgb2YgdmFyaWFibGVzIGJhc2VkIG9uIGNhbGN1bGF0ZWQgQWthaWtlIGluZm9ybWF0aW9uIGNyaXRlcmlvbiAoQUlDKSB2YWx1ZXMgdW50aWwgdGhlIGZpbmFsIGJlc3QgZml0IHJlZHVjZWQgbW9kZWwgaWRlbnRpZmllZC4NCg0KYGBge3J9DQpyZWR1Y2VkLm1vZGVsLmxvZ2l0IDwtIHN0ZXAoZnVsbC5tb2RlbC5sb2dpdCwgZGlyZWN0b24gPSAiYm90aCIpDQpgYGANCg0KVGhlIHN0ZXAgZnVuY3Rpb24gcmVtb3ZlZCB0aGUgdmFyaWFibGVzIGBzaXplVW5pZm9ybWAgKENlbGwgU2l6ZSBVbmlmb3JtaXR5KSAmIGBlQ2VsbFNpemVgIChTaW5nbGUgRXBpdGhlbGlhbCBDZWxsIFNpemUpIGJhc2VkIG9uIEFJQyB0byBtZWFzdXJlIHRoZSByZWR1Y2VkIG1vZGVsJ3MgbWF4aW11bSBsaWtlbGlob29kIGVzdGltYXRpb24gKGxvZy1saWtlbGlob29kKSBmb3IgbW9kZWwgZml0LiBUaGUgQUlDIGVxdWF0aW9uIGlzIGdpdmVuIGJ5Og0KDQokJEFJQyA9IC0yKmxuKEwpICsgMmskJA0KDQpBIHN1bW1hcnkgb2YgdGhlIG1vZGVsIGNoYXJhY3RlcmlzdGljcyBzdGFydGluZyB3aXRoIHRoZSBpbml0aWFsIGZ1bGwgbW9kZWwgYXMgd2VsbCBhcyBhdCBlYWNoIHN0ZXAgbGVhZGluZyB0byBjcmVhdGlvbiBvZiB0aGUgcmVkdWNlZCBtb2RlbCBpcyBpbmNsdWRlZCBiZWxvdy4gIFRvIG5vdGUgdGhlIG5lZ2F0aXZlIHNpZ25zIGluIHRoZSBTdGVwIGNvbHVtbiBpbmRpY2F0ZSB0aGUgcmVtb3ZhbCBvZiB0aGUgbGlzdGVkIHZhcmlhYmxlIGZyb20gdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwuIA0KDQpgYGB7cn0NCmthYmxlKHJlZHVjZWQubW9kZWwubG9naXQkYW5vdmEpICU+JSANCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KcmVkdWNlZCBtb2RlbCAtIE91dGNvbWUgXH4gY2x1bXBUaGlja25lc3MgKyBzaGFwZVVuaWZvcm0gKyBtYXJnQWRoICsNCmJhcmVOdWNsZWkgKyBibENocm9tICsgbm9ybWFsTnVjbGVvbGkgKyBNaXRvc2VzDQoNCg0KQSBzdW1tYXJ5IG9mIHRoZSByZWR1Y2VkIG1vZGVsJ3MgcmVncmVzc2lvbiBjb2VmZmljaWVudHMgaXMgaW5jbHVkZWQgYmVsb3cuIFVubGlrZSB3aXRoIHRoZSBgZnVsbC5tb2RlbC5sb2dpdGAgKGZ1bGwgbW9kZWwpIGFsbCBidXQgdHdvIHJlbWFpbmluZyB2YXJpYWJsZXMgYXJlIGZvdW5kIHRvIGhhdmUgYSBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2Ugb2YgJFByKHx6fCkgPCAwLjA1JCBhbG9uZyB3aXRoIGhpZ2ggY29ycmVsYXRpb24gdmFsdWVzIG9idGFpbmVkIGFzIGV2aWRlbnQgYnkgdGhlIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzLiAgVGhlIGNvbWJpbmF0aW9uIG9mIGFsbCB0aGVzZSBmaW5kaW5ncyBpbmRpY2F0ZSB0aGF0IHRoZSByZWR1Y2VkIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgd2lsbCBsaWtlbHkgc2VydmUgYXMgYSBnb29kIGZpdCBhcyBhIHByZWRpY3RvciBtb2RlbCBmb3IgYnJlYXN0IGNhbmNlciBiaW9wc3kgZGlhZ25vc2lzIGRhdGEuIA0KDQpgYGB7cn0NCmthYmxlKHN1bW1hcnkocmVkdWNlZC5tb2RlbC5sb2dpdCkkY29lZiwgY2FwdGlvbiA9ICJMb2dpc3RpYyBSZWdyZXNzaW9uIENvZWZmaWNpZW50cyBmb3IgUmVkdWNlZC5Nb2RlbCIsIGNvbC5uYW1lcyA9IGMoIiIgLCAiRXN0aW1hdGUiLCAiU3RkLiBFcnJvciIsICJ6IFZhbHVlIiwgJ1ByICh8Pnp8KScpLCBmb3JtYXQgPSAiaHRtbCIpICU+JSANCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KIyMgTW9kZWwgQ29tcGFycmlzb24gJiBTZWxlY3Rpb24gb2YgQmVzdCBGaXQgTG9naXN0aWMgUmVncmVzc2lvbiBNb2RlbA0KDQpJbiBvcmRlciB0byBmb3JtYWxseSBjb21wYXJlIGJvdGggdGhlIGBmdWxsLm1vZGVsLmxvZ2l0YCBhbmQgYHJlZHVjZWQuIG1vZGVsLmxvZ2l0YCBtb2RlbHMgdG8gZGV0ZXJtaW5lIHdoaWNoIHdpbGwgc2VydmUgYXMgYSBiZXR0ZXIgcHJlZGljdG9yIGZvciBiaW9wc3kgZGlhZ25vc2lzLCBlYWNoIG1vZGVsJ3MgQUlDIGFuZCBST0MgY3VydmVzIHdlcmUgY29tcGFyZWQgdG8gZGV0ZXJtaW5lIHRoZSBiZXN0IGZpdCBtb2RlbC4NCg0KIyMjIEFJQyBDb21wYXJyaXNvbg0KDQpXaGVuIGNvbXBhcmluZyBBSUMgdmFsdWVzLCBhIGxvd2VyIHZhbHVlIGluZGljYXRlcyBhIGJldHRlciBtb2RlbCBmaXQgd2hpbGUgYmFsYW5jaW5nIGdlbmVyYWxpemF0aW9uLiAgTWVhbndoaWxlIGEgZ3JlYXRlciBBSUMgdmFsdWUgaW5kaWNhdGVzIGEgcG9vciBmaXQgYW5kIHN1YnNlcXVlbnRseSBwb29yIHByZWRpY3RpdmUgdmFsdWVzLiANCg0KDQojIyMjIEFJQyBvZiBgZnVsbC5tb2RlbC5sb2dpdGAgKGZ1bGwgbW9kZWwpDQpgYGB7cn0NCmthYmxlKGZ1bGwubW9kZWwubG9naXQkYWljLCBjYXB0aW9uID0gIkFJQyB2YWx1ZSBvZiBGdWxsIE1vZGVsIiwgY29sLm5hbWVzID0gYygiZnVsbC5tb2RlbC5sb2dpdCBBSUMgPSIpLCBhbGlnbiA9ICJjIikgJT4lIA0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQoNCiMjIyMgQUlDIG9mIGByZWR1Y2VkLm1vZGVsLmxvZ2l0YCAocmVkdWNlZCBtb2RlbCkNCg0KYGBge3J9DQprYWJsZShyZWR1Y2VkLm1vZGVsLmxvZ2l0JGFpYywgY2FwdGlvbiA9ICJBSUMgdmFsdWUgb2YgUmVkdWNlZCBNb2RlbCIsIGNvbC5uYW1lcyA9IGMoInJlZHVjZWQubW9kZWwubG9naXQgQUlDID0iKSwgYWxpZ24gPSAiYyIpICU+JSANCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KV2hlbiBjb21wYXJpbmcgYnkgQUlDIHZhbHVlcywgdGhlIHJlZHVjZWQgbW9kZWwgcmV0dXJucyBhIGxvd2VyIEFJQyB2YWx1ZSBpbmRpY2F0aW5nIGEgYmV0dGVyIGZpdCB0byB0aGUgaW5pdGlhbCBkYXRhLiBCYXNlZCBvbiBBSUMgdmFsdWVzIHRoZSByZWR1Y2VkIG1vZGVsIHdpbGwgcHJvdmlkZSBpbXByb3ZlZCBwcmVkaWN0aW9uIHZhbHVlcyBmb3IgcGF0aWVudCdzIG91dGNvbWUgKGFjY3VyYXRlIGRpYWdub3NpcyBvZiBtYWxpZ25hbmNpZXMgZHVyaW5nIGJpb3BzeSkuDQoNCiMjIyBST0MgQ29tcGFycmlzb24NCg0KV2hlbiBjb21wYXJpbmcgUk9DIHZhbHVlcywgdGhlIGFyZWEgdW5kZXIgdGhlIGN1cnZlIChBVUMpIHZhbHVlcyBhbmQgdmlzaWJsZSBSZWNlaXZlciBPcGVyYXRpbmcgQ2hhcmFjdGVyaXN0aWNzIChST0MpIGN1cnZlcyBjYW4gYmUgdXNlZCB0byBjb21wYXJlIHRoZSBmaXQgb2YgbXVsdGlwbGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMgYXMgcHJlZGljdGl2ZSBtb2RlbHMgZm9yIGEgYmluYXJ5IG91dHB1dCB2YXJpYWJsZSBzdWNoIGFzIGBPdXRjb21lYC4gIFRoZSBncmVhdGVyIHRoZSBBVUMgdGhlIGJldHRlciBmaXQgdGhlIG1vZGVsIGlzIHRvIHRoZSBvcmlnaW5hbCBzeW50aGV0aWMgZGF0YSAmIHRoZSBtb3JlIGFjY3VyYXRlIHRoZSBtb2RlbCB3aWxsIGJlIGluIHByb3ZpZGluZyBwcmVkaWN0aW9ucyBvbiBicmVhc3QgdGlzc3VlIGJpb3BzeSBkaWFnbm9zaXMuIA0KDQoNCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD05fQ0KDQpkYXRhLmxvZ2l0IDwtIHJhdy5kYXRhDQoNCm5vLm91dGNvbWUgPC0gd2hpY2goZGF0YS5sb2dpdCRPdXRjb21lID09ICJObyIpDQp5ZXMub3V0Y29tZSA8LSAgd2hpY2goZGF0YS5sb2dpdCRPdXRjb21lID09ICJZZXMiKQ0KDQpkYXRhLmxvZ2l0JG91dCA9IDANCmRhdGEubG9naXQkb3V0W3llcy5vdXRjb21lXSA9IDENCmN1dG9mZiA8LSBzZXEoMCwgMSwgbGVuZ3RoID0gMTAwKQ0Kc2Vuc2l0aXYgPSBOVUxMDQpzcGVjaWYgPSBOVUxMDQpzZW5zaXRpdjIgPSBOVUxMDQpzcGVjaWYyID0gTlVMTA0KYWNjdXIgPSBOVUxMDQphY2N1cjIgPSBOVUxMDQoNCg0KcHJlZGljdC5mdWxsLmxvZ2l0IDwtIHByZWRpY3QuZ2xtKGZ1bGwubW9kZWwubG9naXQsIGRhdGEubG9naXQsIHR5cGUgPSAicmVzcG9uc2UiKQ0KcHJlZGljdC5yZWR1Y2VkLmxvZ2l0IDwtIHByZWRpY3QuZ2xtKHJlZHVjZWQubW9kZWwubG9naXQsIGRhdGEubG9naXQsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQpmb3IgKGkgaW4gMToxMDApew0KICANCmRhdGEubG9naXQkb3V0LnRyYWluID0gYXMubnVtZXJpYyhwcmVkaWN0LmZ1bGwubG9naXQgPiBjdXRvZmZbaV0pDQoNClROID0gc3VtKGRhdGEubG9naXQkb3V0LnRyYWluID09IDAgJiBkYXRhLmxvZ2l0JG91dCA9PSAwKSAjIHRydWUgbmVnYXRpdmUNCkZOID0gc3VtKGRhdGEubG9naXQkb3V0LnRyYWluID09IDAgJiBkYXRhLmxvZ2l0JG91dCA9PSAxKSAjIGZhbHNlIG5lZ2F0aXZlDQpGUCA9IHN1bShkYXRhLmxvZ2l0JG91dC50cmFpbiA9PSAxICYgZGF0YS5sb2dpdCRvdXQgPT0gMCkgIyBmYWxzZSBwb3NpdGl2ZQ0KVFAgPSBzdW0oZGF0YS5sb2dpdCRvdXQudHJhaW4gPT0gMSAmIGRhdGEubG9naXQkb3V0ID09IDEpICMgdHJ1ZSBwb3NpdGl2ZQ0KDQpzZW5zaXRpdltpXSA9IFRQIC8gKFRQICsgRk4pDQpzcGVjaWZbaV0gPSBUTiAvIChUTiArIEZQKQ0KYWNjdXJbaV0gPSAoVFAgKyBUTikgLyAoVFAgKyBUTiArIEZOICsgRlApICNhY2N1cmFjeQ0KfQ0KDQpmb3IgKGkgaW4gMToxMDApew0KICANCmRhdGEubG9naXQkb3V0LnRyYWluID0gYXMubnVtZXJpYyhwcmVkaWN0LnJlZHVjZWQubG9naXQgPiBjdXRvZmZbaV0pDQoNClROMiA9IHN1bShkYXRhLmxvZ2l0JG91dC50cmFpbiA9PSAwICYgZGF0YS5sb2dpdCRvdXQgPT0gMCkgIyB0cnVlIG5lZ2F0aXZlDQpGTjIgPSBzdW0oZGF0YS5sb2dpdCRvdXQudHJhaW4gPT0gMCAmIGRhdGEubG9naXQkb3V0ID09IDEpICMgZmFsc2UgbmVnYXRpdmUNCkZQMiA9IHN1bShkYXRhLmxvZ2l0JG91dC50cmFpbiA9PSAxICYgZGF0YS5sb2dpdCRvdXQgPT0gMCkgIyBmYWxzZSBwb3NpdGl2ZQ0KVFAyID0gc3VtKGRhdGEubG9naXQkb3V0LnRyYWluID09IDEgJiBkYXRhLmxvZ2l0JG91dCA9PSAxKSAjIHRydWUgcG9zaXRpdmUNCg0Kc2Vuc2l0aXYyW2ldID0gVFAyIC8gKFRQMiArIEZOMikNCnNwZWNpZjJbaV0gPSBUTjIgLyAoVE4yICsgRlAyKQ0KYWNjdXIyW2ldID0gKFRQMiArIFROMikvKFRQMiArIFROMiArIEZOMiArIEZQMikgI2FjY3VyYWN5DQoNCn0NCg0Kc3BlY2lmaWNpdHkgPC0gIDEgLSBzcGVjaWYNCnNlbnNpdGl2aXR5IDwtICBzZW5zaXRpdg0KDQpzcGVjaWZpY2l0eTIgPC0gIDEgLSBzcGVjaWYyDQpzZW5zaXRpdml0eTIgPC0gIHNlbnNpdGl2Mg0KDQpwcmVkaWN0aW9uIDwtICBwcmVkaWN0LmZ1bGwubG9naXQNCmNhdGVnb3J5IDwtICBkYXRhLmxvZ2l0JG91dCA9PSAxDQpST0NvYmogPC0gcm9jKGNhdGVnb3J5LCBwcmVkaWN0aW9uKQ0KQVVDLmZ1bGwgPC0gcm91bmQoYXVjKFJPQ29iaiksIDUpDQoNCnByZWRpY3Rpb24yIDwtICBwcmVkaWN0LnJlZHVjZWQubG9naXQNCmNhdGVnb3J5MiA8LSAgZGF0YS5sb2dpdCRvdXQgPT0gMQ0KUk9Db2JqMiA8LSByb2MoY2F0ZWdvcnkyLCBwcmVkaWN0aW9uMikNCkFVQy5yZWR1Y2VkIDwtIHJvdW5kKGF1YyhST0NvYmoyKSwgNSkNCg0Kc3BlY2lmLm9yaSA8LSBST0NvYmokc3BlY2lmaWNpdGllcw0Kc3BlY2lmLm9yaTIgPC0gUk9Db2JqMiRzcGVjaWZpY2l0aWVzDQpzZW5zLm9yaSA8LSBST0NvYmokc2Vuc2l0aXZpdGllcw0Kc2Vucy5vcmkyIDwtIFJPQ29iajIkc2Vuc2l0aXZpdGllcw0KDQpkaWZmIDwtIGFicyhzZW5zLm9yaSAtIHNwZWNpZi5vcmkpDQpkaWZmMiA8LSBhYnMoc2Vucy5vcmkyIC0gc3BlY2lmLm9yaTIpDQoNCm1pbi5mdWxsIDwtIHdoaWNoKGRpZmYgPT0gbWluKGRpZmYpKQ0KbWluLnJlZHVjZWQgPC0gd2hpY2goZGlmZjIgPT0gbWluKGRpZmYyKSkNCg0KUk9DIDwtIHBsb3Qoc3BlY2lmaWNpdHksIHNlbnNpdGl2aXR5LA0KICAgICAgICAgICAgdHlwZSA9ICJsIiwNCiAgICAgICAgICAgIG1haW4gPSAiTG9naXN0aWMgUmVncmVzc2lvbiBNb2RlbCBDb21wYXJyaXNvbiBVc2luZyBST0MgYW5kIEFVQyIsDQogICAgICAgICAgICB4bGFiPSIxLXNwZWNpZmljaXR5IiwNCiAgICAgICAgICAgIHlsYWI9InNlbnNpdGl2aXR5IikgKw0KICBzZWdtZW50cygwLDAsMSwxLCBsdHk9MiwgY29sID0gInJlZCIpICsgDQogIGxpbmVzKHNwZWNpZmljaXR5LCBzZW5zaXRpdml0eSwgbHdkID0gMSwgY29sID0gIm5hdnkiKSArDQogIGxpbmVzKHNwZWNpZmljaXR5Miwgc2Vuc2l0aXZpdHkyLCBsd2QgPSAxLCBjb2wgPSAiZGFya2dyZWVuIikgKw0KICB0ZXh0KDAuOCwgMC4yLCBwYXN0ZSgiRnVsbCBNb2RlbCBBVUMgPSAiLCBBVUMuZnVsbCksIGNvbCA9ICJuYXZ5IiwgY2V4ID0gMC44KSArDQogIHRleHQoMC44LCAwLjI1LCBwYXN0ZSgiUmVkdWNlZCBNb2RlbCBBVUMgPSAiLCBBVUMucmVkdWNlZCksIGNvbCA9ICJkYXJrZ3JlZW4iLCBjZXggPSAwLjgpICsNCiAgcG9pbnRzKCgxLXNwZWNpZi5vcmlbbWluLmZ1bGxdKSwgc3BlY2lmLm9yaVttaW4uZnVsbF0sIGNvbCA9ICJuYXZ5IiwgcGNoID0gMTcpICsNCiAgcG9pbnRzKCgxLXNwZWNpZi5vcmkyW21pbi5yZWR1Y2VkXSksIHNwZWNpZi5vcmkyW21pbi5yZWR1Y2VkXSwgY29sID0gImRhcmtncmVlbiIsIHBjaCA9IDE3KSANCiAgdGV4dCgwLjgsIDAuMTgsIHBhc3RlKCJGdWxsIE1vZGVsIENhbGN1bGF0ZWQgQ3V0b2ZmID0gKCIsIHJvdW5kKCgxLXNwZWNpZi5vcmlbbWluLmZ1bGxdKSw0KSwgIiwgIiAsIHJvdW5kKHNlbnMub3JpW21pbi5mdWxsXSw0KSwiKSIpLCBjb2wgPSAibmF2eSIsIGNleCA9IDAuOCkgKw0KICB0ZXh0KDAuOCwgMC4yMywgcGFzdGUoIlJlZHVjZWQgTW9kZWwgQ2FsY3VsYXRlZCBDdXRvZmYgPSAoIiwgcm91bmQoKDEtc3BlY2lmLm9yaTJbbWluLnJlZHVjZWRdKSw0KSwgIiwgIiAsIHJvdW5kKHNlbnMub3JpMlttaW4ucmVkdWNlZF0sNCksIikiKSwgY29sID0gImRhcmtncmVlbiIsIGNleCA9IDAuOCkgDQoNCiAgDQpgYGANCg0KV2hpbGUgdGhlIFJPQyBjdXJ2ZXMgYXJlIG5lYXIgaWRlbnRpY2FsIGFuZCB0aGUgQVVDIHZhbHVlcyBkaWZmZXIgYnkgb25seSA8Yj4gMC4wMDAwNCA8L2I+IGJvdGggbW9kZWxzIGFyZSBuZWFyIGlkZW50aWNhbCBpbiB0ZXJtcyBvZiBwcmVkaWN0aXZlIHBvd2VyLiBXaGlsZSB0aGUgcmVkdWNlZCBtb2RlbCBoYXMgYSBtYXJnaW5hbGx5IGdyZWF0ZXIgQVVDIHZhbHVlICgwLjAwNDAlIGdyZWF0ZXIgdGhhbiB0aGUgZnVsbCBtb2RlbCBBVUMpLCB0aGUgcmVkdWNlZCBtb2RlbCBhbHNvIGNvbnRhaW5zIGZld2VyIHZhcmlhYmxlcyBhbmQgdGhlcmVmb3JlIHNob3VsZCBiZSBzZWxlY3RlZCBhcyB0aGUgcHJlZGljdGl2ZSBtb2RlbCBvZiBjaG9pY2UuDQoNCiMgTmV1cmFsIE5ldHdvcmsgUHJlZGljdGlvbiB3aXRoIFBlcmNlcHRyb24gDQoNCkEgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0ga25vd24gYXMgdGhlIFBlcmNlcHRyb24gbW9kZWwgY2FuIGJlIHV0aWxpemVkIHRvIGNyZWF0ZSBwcmVkaWN0aXZlIG1vZGVscyBmb3IgYmluYXJ5IGNsYXNzaWZpZXJzLCBjYXBhYmxlIG9mIHVuZGVyZ29pbmcgY29uc2lzdGVudCBzdXBlcnZpc2VkIGxlYXJuaW5nLiAgDQoNCmBgYHtyfQ0KZGF0YS5uZXVyYWwgPC0gcmF3LmRhdGENCmBgYA0KDQojIyBQcmVwYXJpbmcgdGhlIGRhdGEgZm9yIE5ldXJhbE5ldCB1c2UNCg0KSW4gb3JkZXIgdG8gcnVuIGEgbmV1cmFsIG5ldHdvcmsgYXMgYSBwcmVkaWN0aXZlIG1vZGVsLCB0aGUgZm9sbG93aW5nIGNvbnNpZGVyYXRpb25zIG11c3QgYmUgbWV0LiAgVGhlIGRhdGEgd2FzIG1vZGlmaWVkIGFjY29yZGluZ2x5IHRvIG1lZXQgdGhlc2UgY29uZGl0aW9uczogDQoNCjEuICBBbGwgZmVhdHVyZXMgbXVzdCBiZSBudW1lcmljDQogICAgYS4gT3V0Y29tZSBjb252ZXJ0ZWQgZnJvbSBmYWN0b3IgdG8gbnVtZXJpYw0KMi4gIEFsbCBkdW1teSB2YXJpYWJsZXMgbXVzdCBiZSBleHBsaWNpdGx5IGRlZmluZWQNCiAgICBhLiBPdXRjb21lID0gMSBtZWFucyBObyAoYmVuaWduIHRpc3N1ZSkNCiAgICBiLiBPdXRjb21lID0gMiBtZWFucyBZZXMgKG1hbGlnbmFudCB0aXNzdWUpDQozLiAgUmVjb21tZW5kZWQgdG8gc2NhbGUgYWxsIG51bWVyaWNhbCB2YWx1ZXMNCiAgICBhLiBNaW4tbWF4IHNjYWxpbmcgdXRpbGl6ZWQgKGZvcm11bGEgYmVsb3cpDQogICAgDQoNCiQkIHhfe3NjYWxlZH0gPSBcZnJhY3t4IC0gbWluKHgpfXttYXgoeCkgLSBtaW4oeCl9JCQNCg0KYGBge3J9DQoNCmRhdGEubmV1cmFsJE91dGNvbWUgPC0gYXMubnVtZXJpYyhhcy5mYWN0b3IoZGF0YS5uZXVyYWwkT3V0Y29tZSkpDQoNCmBgYA0KDQoNCmBgYHtyfQ0KZGF0YS5uZXVyYWwkY2x1bXBUaGlja1NjYWxlIDwtICgoZGF0YS5uZXVyYWwkY2x1bXBUaGlja25lc3MgLSBtaW4oZGF0YS5uZXVyYWwkY2x1bXBUaGlja25lc3MpKS8obWF4KGRhdGEubmV1cmFsJGNsdW1wVGhpY2tuZXNzKS1taW4oZGF0YS5uZXVyYWwkY2x1bXBUaGlja25lc3MpKSkgI3NjYWxlIGNsdW1wVGhpY2tuZXNzDQoNCmRhdGEubmV1cmFsJHNpemVVbmlTY2FsZSA8LSAoKGRhdGEubmV1cmFsJHNpemVVbmlmb3JtIC0gbWluKGRhdGEubmV1cmFsJHNpemVVbmlmb3JtKSkvKG1heChkYXRhLm5ldXJhbCRzaXplVW5pZm9ybSktbWluKGRhdGEubmV1cmFsJHNpemVVbmlmb3JtKSkpICNzY2FsZSBzaXplVW5pZm9ybQ0KDQpkYXRhLm5ldXJhbCRzaGFwZVVuaVNjYWxlIDwtICgoZGF0YS5uZXVyYWwkc2hhcGVVbmlmb3JtIC0gbWluKGRhdGEubmV1cmFsJHNoYXBlVW5pZm9ybSkpLyhtYXgoZGF0YS5uZXVyYWwkc2hhcGVVbmlmb3JtKS1taW4oZGF0YS5uZXVyYWwkc2hhcGVVbmlmb3JtKSkpICNzY2FsZSBzaGFwZVVuaWZvcm0NCg0KZGF0YS5uZXVyYWwkbWFyZ0FkaFNjYWxlIDwtICgoZGF0YS5uZXVyYWwkbWFyZ0FkaCAtIG1pbihkYXRhLm5ldXJhbCRtYXJnQWRoKSkvKG1heChkYXRhLm5ldXJhbCRtYXJnQWRoKS1taW4oZGF0YS5uZXVyYWwkbWFyZ0FkaCkpKSAjc2NhbGUgbWFyZ0FkaA0KDQpkYXRhLm5ldXJhbCRlQ2VsbFNpemVTY2FsZSA8LSAoKGRhdGEubmV1cmFsJGVDZWxsU2l6ZSAtIG1pbihkYXRhLm5ldXJhbCRlQ2VsbFNpemUpKS8obWF4KGRhdGEubmV1cmFsJGVDZWxsU2l6ZSktbWluKGRhdGEubmV1cmFsJGVDZWxsU2l6ZSkpKSAjc2NhbGUgZUNlbGxTaXplDQoNCmRhdGEubmV1cmFsJGJsQ2hyb21TY2FsZSA8LSAoKGRhdGEubmV1cmFsJGJsQ2hyb20gLSBtaW4oZGF0YS5uZXVyYWwkYmxDaHJvbSkpLyhtYXgoZGF0YS5uZXVyYWwkYmxDaHJvbSktbWluKGRhdGEubmV1cmFsJGJsQ2hyb20pKSkgI3NjYWxlIGJsQ3JvbQ0KDQpkYXRhLm5ldXJhbCRub3JtYWxOdWNsU2NhbGUgPC0gKChkYXRhLm5ldXJhbCRub3JtYWxOdWNsZW9saSAtIG1pbihkYXRhLm5ldXJhbCRub3JtYWxOdWNsZW9saSkpLyhtYXgoZGF0YS5uZXVyYWwkbm9ybWFsTnVjbGVvbGkpLW1pbihkYXRhLm5ldXJhbCRub3JtYWxOdWNsZW9saSkpKSAjc2NhbGUgbm9ybWFsTnVjbGVvbGkNCg0KZGF0YS5uZXVyYWwkTWl0b3Nlc1NjYWxlIDwtICgoZGF0YS5uZXVyYWwkTWl0b3NlcyAtIG1pbihkYXRhLm5ldXJhbCRNaXRvc2VzKSkvKG1heChkYXRhLm5ldXJhbCRNaXRvc2VzKS1taW4oZGF0YS5uZXVyYWwkTWl0b3NlcykpKSAjc2NhbGUgbWl0b3Nlcw0KDQpkYXRhLm5ldXJhbCRPdXRjb21lU2NhbGUgPC0gKChkYXRhLm5ldXJhbCRPdXRjb21lIC0gbWluKGRhdGEubmV1cmFsJE91dGNvbWUpKS8obWF4KGRhdGEubmV1cmFsJE91dGNvbWUpLW1pbihkYXRhLm5ldXJhbCRPdXRjb21lKSkpICNzY2FsZSBvdXRjb21lDQoNCmRhdGEubmV1cmFsLnNjYWxlIDwtIHN1YnNldChkYXRhLm5ldXJhbCwgc2VsZWN0ID0gYyhjbHVtcFRoaWNrU2NhbGUsIHNpemVVbmlTY2FsZSwgc2hhcGVVbmlTY2FsZSwgbWFyZ0FkaFNjYWxlLCBlQ2VsbFNpemVTY2FsZSwgYmxDaHJvbVNjYWxlLCBub3JtYWxOdWNsU2NhbGUsIE1pdG9zZXNTY2FsZSwgT3V0Y29tZVNjYWxlKSkNCmBgYA0KDQpBIHN1bW1hcnkgb2YgdGhlIHRyYW5zZm9ybWVkIChtaW4tbWF4IHNjYWxlZCkgc3ludGhldGljIGJyZWFzdCBjYW5jZXIgYmlvcHN5IGRhdGEgaXMgaW5jbHVkZWQgYmVsb3cgDQoNCmBgYHtyfQ0Ka2FibGUoc3VtbWFyeShkYXRhLm5ldXJhbC5zY2FsZSksICBjYXB0aW9uID0gIlN1bW1hcnkgb2YgTWluLU1heCB0cmFuc2Zvcm1lZCBTeW50aGV0aWMgQnJlYXN0IENhbmNlciBEYXRhIiwgYWxpZ24gPSAiYyIsIHNpemUgPSA2KSAlPiUgDQogIGthYmxlX3N0eWxpbmcoKSAlPiUgDQogIHJvd19zcGVjKDAsIGZvbnRfc2l6ZSA9IDEyKQ0KYGBgDQoNCiMjIENyZWF0ZSBNb2RlbCBhbmQgTW9kZWwgRm9ybXVsYQ0KDQpUaGUgbW9kZWwgd2FzIGNyZWF0ZWQgaW1wbGljaXRseSB1dGlsaXppbmcgdGhlIGN1cnJlbnQgdmFyaWFibGUgbmFtZXMuICBNb2RlbHMgZG8gbm90IGhhdmUgdG8gYmUgY3JlYXRlZCBpbXBsaWNpdGx5IGJ1dCBpbXBsaWNpdCBjcmVhdGlvbiBlbnN1cmVzIG5vIGRhdGEgZW50cnkgZXJyb3JzIGR1cmluZyBtb2RlbCBjcmVhdGlvbiBhbmQgcHJldmVudHMgb3Bwb3J0dW5pdHkgZm9yIGh1bWFuIGVycm9yLiANCg0KIyMjIEltcGxpY2l0bHkgZGVmaW5lIG1vZGVsIGZvcm11bGENCg0KQWxsIHZhcmlhYmxlIG5hbWVzIHdlcmUgZXh0cmFjdGVkIGluIHByZXBhcmF0aW9uIG9mIG1vZGVsIGFuZCBtb2RlbCBmb3JtdWxhIGNyZWF0aW9uLiANCg0KYGBge3J9DQpuZXVyYWwuaW1wbGljLmRlc2lnbiA9IG1vZGVsLm1hdHJpeCggfi4sIGRhdGEgPSBkYXRhLm5ldXJhbC5zY2FsZSkNCmNvbG5hbWVzKG5ldXJhbC5pbXBsaWMuZGVzaWduKQ0KYGBgDQoNCmBPdXRjb21lU2NhbGVgIGlzIHJlc3BvbnNlIHZhcmlhYmxlIGFuZCByZXF1aXJlcyBwcm9wZXIgYXR0ZW50aW9uIGluIGVuc2VtYmxlIG1vZGVsIGZvcm11bGEgY3JlYXRpb24uDQoNCiMjIyBDcmVhdGUgRW5zZW1ibGUgTW9kZWwgRm9ybXVsYQ0KDQpPbmNlIHRoZSB2YXJpYWJsZSBuYW1lcyB3ZXJlIGV4dHJhY3RlZCwgYSBzdHJpbmcgZnVuY3Rpb24gd2FzIHV0aWxpemVkIHRvIGRlc2lnbiB0aGUgbW9kZWwgZm9ybXVsYSBmcm9tIHRoZSBtb2RlbC5tYXRyaXguIFRoZSBtb2RlbCBmb3JtYXQgbXVzdCBiZSBpbiB0aGUgZm9sbG93aW5nIGVxdWF0aW9uIGZvcm1hdDoNCg0KYHJlc3BvbnNlIH4gdmFyLjEgKyB2YXIuMiArIC4uLiArIHZhci5uYA0KDQpgYGB7cn0NCm5ldXJhbC5mb3JtdWxhLm5hbWVzIDwtICBjb2xuYW1lcyhuZXVyYWwuaW1wbGljLmRlc2lnbikNCm5ldXJhbC5yZXNwb25zZS5uYW1lIDwtIHBhc3RlKG5ldXJhbC5mb3JtdWxhLm5hbWVzWzEwXSkNCm5ldXJhbC52YXIubmFtZXMgPC0gcGFzdGUobmV1cmFsLmZvcm11bGEubmFtZXNbLWMoMSwxMCldLCBjb2xsYXBzZSA9ICIrIikNCm5ldXJhbC5mb3JtdWxhIDwtIGZvcm11bGEocGFzdGUoYyhuZXVyYWwucmVzcG9uc2UubmFtZSwgIn4iLCBuZXVyYWwudmFyLm5hbWVzKSwgY29sbGFwc2UgPSAiIikpDQpuZXVyYWwuZm9ybXVsYQ0KYGBgDQoNClRoZSBhYm92ZSBlbnNlbWJsZSBtb2RlbCBmb3JtdWxhIHdhcyB0aGVuIHV0aWxpemVkIGluIHRoZSBgbmV1cmFsbmV0KClgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBhIHNpbmdsZSBsYXllciBuZXVyYWwgbmV0d29yayBwcmVkaWN0aXZlIG1vZGVsLiANCg0KYGBge3J9DQpuZXVyYWwubW9kZWwgPC0gbmV1cmFsbmV0KG5ldXJhbC5mb3JtdWxhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gbmV1cmFsLmltcGxpYy5kZXNpZ24sICNkZXNpZ24gbWF0cml4DQogICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGFjdC5mY3QgPSAibG9naXN0aWMiLCAjc2lnbW9pZCBhY3RpdmF0aW9uIGZ4bg0KICAgICAgICAgICAgICAgICAgICAgICAgICBsaW5lYXIub3V0cHV0ID0gRkFMU0UgI211c3QgYmUgZmFsc2UgZm9yIGxvZ2lzdGljDQogICAgICAgICAgICAgICAgICAgICAgICAgICkNCg0Ka2FibGUobmV1cmFsLm1vZGVsJHJlc3VsdC5tYXRyaXgpDQpgYGANCg0KIyMgUGxvdHRpbmcgTmV1cmFsIE5ldCBNb2RlbCAobmV1cmFsLm1vZGVsKSB3aXRoIEFzc29jaWF0ZWQgV2VpZ2h0cw0KDQpBIHZpc3VhbCByZXByZXNlbnRhdGlvbiBvZiB0aGUgbmV1cmFsIG5ldHdvcmsgYW5kIHRoZSByZXNwZWN0aXZlIGNhbGN1bGF0ZWQgd2VpZ2h0cyBmb3IgZWFjaCB2YXJpYWJsZSAoYW5kIGhpZGRlbiB3ZWlnaHRzKSBpcyBpbmNsdWRlZCBiZWxvdy4gDQoNCmBgYHtyfQ0KcGxvdChuZXVyYWwubW9kZWwsIHJlcD0iYmVzdCIsIHRpdGxlID0gIlNpbmdsZS1sYXllciBiYWNrIHByb3BvZ2F0aW9uIG5ldXJhbCBuZXR3b3JrIGZvciBicmVhc3QgdGlzc3VlIGJpb3BzeSBkaWFnbm9zaXMgb3V0Y29tZSIpDQpgYGANCg0KVmFsdWVzIGFsb25nIGJsYWNrIGxpbmVzIGZvbGxvd2luZyB2YXJpYWJsZSBuYW1lcyByZXByZXNlbnQgdGhlIGNhbGN1bGF0ZWQgd2VpZ2h0IG9mDQplYWNoIHZhcmlhYmxlLiAgQWxsIHdlaWdodCBhZGp1c3RtZW50IHdhcyBjb21wbGV0ZWQgdGhyb3VnaCAkeiA9IHdfMCArd18xIHhfMSArIC4uLiArIHdfbiB4X24kDQoNCkJsdWUgbGluZXMsIG5vZGVzLCBhbmQgYXNzb2NpYXRlZCB3ZWlnaHQgdmFsdWVzIHJlcHJlc2VudCB0aGUgaGlkZGVuIGxheWVycyByZXF1aXJlZCBmb3IgbG9naXN0aWMgcmVncmVzc2lvbiAoYS5rLmEuIGJpYXMpDQoNCkZpcnN0IG5vZGUgKGxlZnQpIHJlcHJlc2VudHMgdGhlIGxpbmVhciB0cmFuc2ZlciBmdW5jdGlvbiAoeiBlcXVhdGlvbiBhYm92ZSkgYW5kIGJpYXMgDQoNClNlY29uZCBub2RlIChyaWdodCkgcmVwcmVzZW50cyB0aGUgc2lnbW9pZCBhY3RpdmF0aW9uIGZ1bmN0aW9uLiBUaGlzIGZ1bmN0aW9uIGlzIGNhbGN1bGF0ZWQgYnk6IA0KDQokZyhcZGVsdGEpID0gXGZyYWN7ZV5cZGVsdGF9ezErZV5cZGVsdGF9JCB3aGVyZQ0KJFxkZWx0YSA9IFxzdW1fe2k9MX1ebiB6ID0gXHN1bV97aT0xfV57bn0gd19pIHhfaSArIHdfMCQNCg0KRGVjaXNpb24gdGhyZXNob2xkICgkXGRlbHRhXzAkKSBpcyBub3QgcmVwcmVzZW50ZWQgb24gdGhlIGZsb3djaGFydCBidXQgaXMgdXNlZCB0byBkZXRlcm1pbmUgdGhlIGZpbmFsIG91dHB1dCB2YWx1ZS4gSWYNCiRnKFxkZWx0YSkgPiBcZGVsdGFfMCQgdGhlbiAkXGhhdHt5fSA9IDEkIG90aGVyd2lzZSAkXGhhdHt5fSA9IDAkDQoNCk91dGNvbWVTY2FsZSByZXByZXNlbnRzIHRoZSBvdXRwdXQgcHJlZGljdGlvbiB2YWx1ZXMgJFxoYXR7eX0kICgwID0gbm8gY2FuY2VyIC8gMSA9IGNhbmNlcikNCg0KYGBge3J9DQpwcmVkaWN0Lm5ldXJhbCA8LSBwcmVkaWN0KG5ldXJhbC5tb2RlbCwgbmV3ZGF0YSA9IG5ldXJhbC5pbXBsaWMuZGVzaWduLCBsaW5lYXIub3V0cHV0ID0gRkFMU0UpDQpjYXQubm4gPC0gZGF0YS5uZXVyYWwkT3V0Y29tZVNjYWxlID09IDENClJPQ29iai5ubiA8LSByb2MoY2F0Lm5uLCBwcmVkaWN0Lm5ldXJhbCkNCg0KDQpwbG90KDEtIFJPQ29iai5ubiRzZW5zaXRpdml0aWVzLCBST0NvYmoubm4kc3BlY2lmaWNpdGllcywNCiAgICAgdHlwZSA9ICJsIiwNCiAgICAgeGxpbSA9IGMoMCwxKSwNCiAgICAgeWxpbSA9IGMoMCwxKSwNCiAgICAgeGxhYiA9ICIxIC0gU3BlY2lmaWNpdHkgKEZhbHNlIFBvc2l0aXZlKSIsDQogICAgIHlsYWIgPSAiU2Vuc2l0aXZpdHkgKFRydWUgTmVnYXRpdmUpIiwNCiAgICAgY29sID0gImRhcmt2aW9sZXQiLA0KICAgICBsd2QgPSAyLA0KICAgICBsdHkgPSAxLA0KICAgICBtYWluID0gIlJPQyBDdXJ2ZSBvZiBOZXVyYWwgTmV0d29yayBQZXJjZXB0cm9uIE1vZGVsIikgDQphYmxpbmUoMCwxLCBsdHkgPSAyLCBjb2wgPSAiZGFya3JlZCIsIGx3ZCA9IDIpIA0KICBsZWdlbmQoImJvdHRvbXJpZ2h0IiwgYyhwYXN0ZSgiUGVyY2VwdHJvbiBNb2RlbCwgQVVDID0iLCBST0NvYmoubm4kYXVjKSksDQogICAgICAgICBjb2wgPSBjKCJkYXJrdmlvbGV0IiwgImRhcmtvcmFuZ2UiLCAiZGFya2dyZWVuIiwgIm5hdnkiKSwNCiAgICAgICAgIGx3ZD0yLCBsdHk9cmVwKDEpLCBjZXggPSAwLjgsIGJ0eSA9ICJuIikNCg0KYGBgDQoNCg0KDQojIERlY2lzaW9uIFRyZWUgUHJlZGljdGlvbg0KDQpgYGB7cn0NCmRhdGEudHJlZSA8LSByYXcuZGF0YQ0KYGBgDQoNCkFub3RoZXIgY29tbW9uIGFwcHJvYWNoIHRvIGNyZWF0aW5nIHByZWRpY3RpdmUgbW9kZWxzIGlzIHRocm91Z2ggdGhlIGNyZWF0aW9uIG9mIGRlY2lzaW9uIHRyZWVzLiBUd28gbWFpbiBhcHByb2FjaGVzIHRvIGRlY2lzaW9uIHRyZWUgc3BsaXR0aW5nIHdlcmUgdXRpbGl6ZWQ6IEdpbmkgSW5kZXggJiBFbnRyb3B5DQoNCmBgYHtyfQ0KdHJlZS5tb2RlbCA8LSBmdW5jdGlvbiAoaW5wdXQuZGF0YSwgZnAsIGZuLCBwdXJpdHkpew0KICB0cmVlID0gcnBhcnQoT3V0Y29tZSB+IC4sDQogICAgICAgICAgICAgICBkYXRhID0gaW5wdXQuZGF0YSwNCiAgICAgICAgICAgICAgIG5hLmFjdGlvbiA9IG5hLnJwYXJ0LA0KICAgICAgICAgICAgICAgbWV0aG9kID0gImNsYXNzIiwNCiAgICAgICAgICAgICAgIG1vZGVsID0gRkFMU0UsDQogICAgICAgICAgICAgICB4ID0gRkFMU0UsDQogICAgICAgICAgICAgICB5ID0gVFJVRSwNCiAgICAgICAgICAgICAgIHBhcm1zID0gbGlzdChsb3NzID0gbWF0cml4KGMoMCwgZnAsIGZuLCAwKSwgbmNvbCA9IDIsIGJ5cm93ID0gVFJVRSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXQgPSBwdXJpdHkpLCAjcGFybXMgY3JlYXRlcyBhYmlsaXR5IHRvIHdlaWdodCBmYWxzZSBwb3NpdGl2ZXMgYW5kIGZhbHNlIG5lZ2F0aXZlcw0KICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woDQogICAgICAgICAgICAgICAgIG1pbnNwbGl0ID0gMTAsICNtaW4gdmFsdWVzIGJlZm9yZSBzcGxpdA0KICAgICAgICAgICAgICAgICBtaW5idWNrZXQgPSAzLCAjbWluIHZhbHVlcyBmb3IgbGVhZiBub2Rlcw0KICAgICAgICAgICAgICAgICBjcCA9IDAuMDEsICNjb21scGV4aXR5IHBhcmFtZXRlcg0KICAgICAgICAgICAgICAgICB4dmFsID0gNSAjbnVtYmVyIG9mIGNyb3NzIHZhbGlkYXRpb24gcnVucw0KICAgICAgICAgICAgICAgKQ0KICApDQp9DQpgYGANCg0KDQpgYGB7cn0NCmdpbmkudHJlZS51bndlaWdodCA8LSB0cmVlLm1vZGVsKGlucHV0LmRhdGEgPSBkYXRhLnRyZWUsIGZwID0gMSwgZm4gPSAxLCBwdXJpdHkgPSAiZ2luaSIpDQppbmZvLnRyZWUudW53ZWlnaHQgPC0gdHJlZS5tb2RlbChpbnB1dC5kYXRhID0gZGF0YS50cmVlLCBmcCA9IDEsIGZuID0gMSwgcHVyaXR5ID0gImluZm8iKQ0KZ2luaS50cmVlLm5lZyA8LSB0cmVlLm1vZGVsKGlucHV0LmRhdGEgPSBkYXRhLnRyZWUsIGZwID0gMTAsIGZuID0gMTAsIHB1cml0eSA9ICJnaW5pIikNCmluZm8udHJlZS5uZWcgPC0gdHJlZS5tb2RlbChpbnB1dC5kYXRhID0gZGF0YS50cmVlLCBmcCA9IDEwLCBmbiA9IDEwLCBwdXJpdHkgPSAiaW5mbyIpDQpgYGANCg0KDQpBIG5vdGUgb24gaW50ZXJwcmV0aW5nIHRoZSBkZWNpc2lvbiB0cmVlcyBiZWxvdy4gIEVhY2ggbm9kZSAmIGxlYWYgY29udGFpbnMgdGhlIGZvbGxvd2luZyB2YWx1ZXMgaW4gdGhlIGZvbGxvd2luZyBvcmRlcjoNCiAgMS4gUHJlZGljdGVkIGNsYXNzIChZZXMvTm8pIA0KICAyLiBQcm9iYWJpbGl0eSBvZiBQcmVkaWN0ZWQgQ2xhc3MNCiAgMy4gUGVyY2VudGFnZSBvZiBPYnNlcnZhdGlvbnMgaW4gTm9kZQ0KDQojIyBHaW5pIEluZGV4DQoNClRoZSBnaW5pIGluZGV4IHV0aWxpemVkIGZvciBjYWxjdWxhdGluZyBub2RlIHNwbGl0IHZhbHVlcyBpcyBnaXZlbiBieSB0aGUgZm9sbG93aW5nIGVxdWF0aW9uOg0KDQokJCBHaW5pKEQpID0gXHN1bV97aT0xfV57bn0gcF9pICgxLXBfaSkgPSAxIC0gXHN1bV97aT0xfV57bn0gcF9pXjIgJCQNCg0Kd2hlcmUgJHBfaSQgcmVwcmVzZW50cyB0aGUgcHJvYmFiaWxpdHkgb2YgYmVpbmcgY2xhc3NpZmllZCB0byBhIHBhcnRpY3VsYXIgY2xhc3MuDQoNCg0KIyMjIERlY2lzaW9uIFRyZWUgTW9kZWxzIGZvciBQcmVkaWN0aW5nIE91dGNvbWUgVmFsdWVzIFtHaW5pIEluZGV4XQ0KDQpgYGB7ciwgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9OX0NCnBhcihtZnJvdz1jKDEsMikpDQpycGFydC5wbG90KGdpbmkudHJlZS51bndlaWdodCwgbWFpbiA9ICJObyBBZGRpdGlvbmFsIFdlaWdodCIsIHR5cGUgPSAxKQ0KcnBhcnQucGxvdChnaW5pLnRyZWUubmVnLCBtYWluID0gIkZhbHNlIFZhbHVlcyB4MTAgV2VpZ2h0IFRydWUgVmFsdWVzIiwgdHlwZSA9IDEpDQoNCmBgYA0KDQojIyBFbnRyb3B5DQoNClRoZSBFbnRyb3B5ICB1dGlsaXplZCBmb3IgY2FsY3VsYXRpbmcgbm9kZSBzcGxpdCB2YWx1ZXMgaXMgZ2l2ZW4gYnkgdGhlIGZvbGxvd2luZyBlcXVhdGlvbjoNCg0KJCQgRSA9IFxzdW1fe2k9MX1ee259IC1wX2kgXGxvZ18yIChwX2kpICQkIA0Kd2hlcmUgJHBfaSQgcmVwcmVzZW50cyB0aGUgcHJvYmFiaWxpdHkgb2YgYmVpbmcgY2xhc3NpZmllZCB0byBhIHBhcnRpY3VsYXIgY2xhc3MuIEVudHJvcHkgY2FuIHRoZW4gYmUgdXRpbGl6ZWQgdG8gY2FsY3VsYXRlIHRoZSBpbmZvcm1hdGlvbiBnYWluIGZyb20gYSBtb2RlbCA8Yj4gSW5mb0dhaW4gPSBFKFBhcmVudCBOb2RlKSAtIEUoQ2hpbGQgTm9kZSkgPC9iPg0KDQpJbmZvcm1hdGlvbiBnYWluIGlzIGEgbWVhc3VyZSBvZiB0aGUgaW5mb3JtYXRpb24gdGhhdCB3b3VsZCBiZSBnYWluZWQgYnkgc3BsaXR0aW5nIGEgZmVhdHVyZSBvZiBpbnRlcmVzdC4gSGlnaGVyIGluZm8gZ2FpbiB2YWx1ZXMgaW5kaWNhdGUgZ3JlYXRlciBtb2RlbCBiZW5lZml0IHRvIHNwbGl0dGluZyB0aGF0IGZlYXR1cmUuDQoNCg0KIyMjIERlY2lzaW9uIFRyZWUgTW9kZWxzIGZvciBQcmVkaWN0aW5nIE91dGNvbWUgVmFsdWVzIFtFbnRyb3B5XQ0KDQpgYGB7ciwgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9OX0NCnBhcihtZnJvdz1jKDEsMikpDQpycGFydC5wbG90KGluZm8udHJlZS51bndlaWdodCwgbWFpbiA9ICJObyBBZGRpdGlvbmFsIFdlaWdodCIsIHR5cGUgPSAxKQ0KcnBhcnQucGxvdChpbmZvLnRyZWUubmVnLCBtYWluID0gIkZhbHNlIFZhbHVlcyB4MTAgV2VpZ2h0IFRydWUgVmFsdWVzIiwgdHlwZSA9IDEpDQoNCmBgYA0KDQojIyBDb21wYXJlIERlY2lzaW9uIFRyZWUgTW9kZWxzIFVzaW5nIFJPQyAmIEFVQw0KDQpUbyBjb21wYXJlIHRoZSBmb3VyIGRlY2lzaW9uIHRyZWVzIGFuZCBkZXRlcm1pbmUgdGhlIGJlc3QgZml0IG1vZGVsIGFuZCBzdWJzZXF1ZW50IGJlc3QgbW9kZWwgdG8gdXNlIGZvciBwcmVkaWN0aW9ucywgdGhlIHJlc3BlY3RpdmUgUk9DIGN1cnZlcyBhbmQgQVVDIHZhbHVlcyB3ZXJlIGNvbXBhcmVkLiAgDQoNCkJvdGggdGhlIEdpbmkgYW5kIEVudHJvcHkgbW9kZWxzIHdlcmUgY29tcGFyZWQgd2l0aCBubyB3ZWlnaHRpbmcgYXMgd2VsbCBhcyB3aXRoIDEweCB3ZWlnaGluZyBmb3IgZmFsc2UgbmVnYXRpdmVzIGNvbXBhcmVkIHRvIGZhbHNlIHBvc2l0aXZlcy4gIEZhbHNlIHBvc2l0aXZlcyBvZiBhIGNvcmUgbmVlZGxlIHB1bmNoIGJpb3BzeSBjYW4gYmUgaWRlbnRpZmllZCB3aXRoIGZ1cnRoZXIgYmxvb2QgYmlvbWFya2VyIHNjcmVlbmluZywgc3VyZ2ljYWwgYmlvcHN5LCBhbmQgYWRkaXRpb25hbCBjb3JlIG5lZWRsZSBwdW5jaCBiaW9wc2llcy4gIEFsdGVybmF0aXZlbHksIGEgZmFsc2UgbmVnYXRpdmUgcmVzdWx0IHdpbGwgbGVhZCBhIHBhdGllbnQgdG8gZGVsYXkgb3IgdWx0aW1hdGVseSBmb3JnbyBwb3RlbnRpYWxseSBsaWZlIHNhdmluZyB0cmVhdG1lbnQuICBJbiB0ZXJtcyBvZiBjYW5jZXIsIGZhbHNlIG5lZ2F0aXZlcyBhcmUgY29zdGx5LCBhbmQgdGh1cyB3ZXJlIHdlaWdodGVkIGFzIHN1Y2ggaW4gdGhlIHByZWRpY3Rpb24gbW9kZWxzLiANCg0KDQpgYGB7cn0NCiNDcmVhdGluZyBhIGZ1bmN0aW9uIGFsbG93cyBmb3Igc3RyZWFtbGluZWQgInBsdWcgYW5kIHBsYXkiIHByb2Nlc3Npbmcgb2YgdmFyaW91cyBkZWNpc2lvbiB0cmVlIG1vZGVscyBmb3IgUk9DICYgQVVDIGNvbXBhcmlzb24uDQoNCmRlYy5tYXRyaXguc2VzcCA8LSBmdW5jdGlvbiAoaW5wdXQuZGF0YSwgZnAsIGZuLCBwdXJpdHkpew0KICBjdXRvZmYgPC0gc2VxKDAsMSxsZW5ndGg9MTAwKQ0KICBtb2RlbCA9IHRyZWUubW9kZWwoaW5wdXQuZGF0YSwgZnAsIGZuLCBwdXJpdHkpDQogIHByZWQuZGVjID0gcHJlZGljdChtb2RlbCwgbmV3ZGF0YSA9IGlucHV0LmRhdGEsIHR5cGUgPSAicHJvYiIpICNjcmVhdGVzIDIgY29sdW1uIG1hdHJpeA0KICBtYXRyaXguc2VzcCA8LSBtYXRyaXgoMCwgbmNvbCA9IGxlbmd0aChjdXRvZmYpLCBucm93ID0zLCBieXJvdyA9IEZBTFNFKQ0KICANCiAgZm9yIChpIGluIDE6bGVuZ3RoKGN1dG9mZikpew0KICAgIHByZWQub3V0cHV0IDwtIGlmZWxzZShwcmVkLmRlY1ssIlllcyJdID49IGN1dG9mZltpXSwgIlllcyIsICJObyIpICNbLCJZZXMiXSBmb3JjZXMgc2VsZWN0aW9uIG9mIG9ubHkgInN1Y2Nlc3MiICh5ZXMpIHByb2JzDQogICAgVFAxID0gc3VtKHByZWQub3V0cHV0ID09ICJZZXMiICYgaW5wdXQuZGF0YSRPdXRjb21lID09ICJZZXMiKQ0KICAgIFROMSA9IHN1bShwcmVkLm91dHB1dCA9PSAiTm8iICYgaW5wdXQuZGF0YSRPdXRjb21lID09ICJObyIpDQogICAgRlAxID0gc3VtKHByZWQub3V0cHV0ID09ICJZZXMiICYgaW5wdXQuZGF0YSRPdXRjb21lID09ICJObyIpDQogICAgRk4xID0gc3VtKHByZWQub3V0cHV0ID09ICJObyIgJiBpbnB1dC5kYXRhJE91dGNvbWUgPT0gIlllcyIpDQogICAgDQogICAgbWF0cml4LnNlc3BbMSxpXSA9IFRQMS8oVFAxICsgRk4xKQ0KICAgIG1hdHJpeC5zZXNwWzIsaV0gPSBUTjEvKFROMSArIEZQMSkNCiAgICBtYXRyaXguc2VzcFszLGldID0gKFRQMSArIFROMSkvKFRQMSArIFROMSArIEZOMSArIEZQMSkgI2FjY3VyYWN5DQogIH0NCiAgDQogIHByZWQuZGVjLmZpbmFsICA8LSAgcHJlZC5kZWNbICwgIlllcyJdDQogIGNhdC5kZWMgPC0gIGlucHV0LmRhdGEkT3V0Y29tZSA9PSAiWWVzIg0KICBST0NPYmouZGVjIDwtIHJvYyhjYXQuZGVjLCBwcmVkLmRlYy5maW5hbCkNCiAgQVVDLmRlYyA8LSAgYXVjKFJPQ09iai5kZWMpDQogIA0KICBsaXN0KG1hdHJpeC5zZXNwID0gbWF0cml4LnNlc3AsIEFVQyA9IHJvdW5kKEFVQy5kZWMsIDMpKQ0KICANCn0NCmBgYA0KDQpgYGB7cn0NCg0KZ2luaS5ST0MudW53ZWlnaHQgPC0gZGVjLm1hdHJpeC5zZXNwKGlucHV0LmRhdGEgPSBkYXRhLnRyZWUsIGZwID0gMSwgZm4gPSAxLCBwdXJpdHkgPSAiZ2luaSIpDQppbmZvLlJPQy51bndlaWdodCA8LSBkZWMubWF0cml4LnNlc3AoaW5wdXQuZGF0YSA9IGRhdGEudHJlZSwgZnAgPSAxLCBmbiA9IDEsIHB1cml0eSA9ICJpbmZvcm1hdGlvbiIpDQpnaW5pLlJPQy5uZWcgPC0gZGVjLm1hdHJpeC5zZXNwKGlucHV0LmRhdGEgPSBkYXRhLnRyZWUsIGZwID0gMSwgZm4gPSAxMCwgcHVyaXR5ID0gImdpbmkiKQ0KaW5mby5ST0MubmVnIDwtIGRlYy5tYXRyaXguc2VzcChpbnB1dC5kYXRhID0gZGF0YS50cmVlLCBmcCA9IDEsIGZuID0gMTAsIHB1cml0eSA9ICJpbmZvcm1hdGlvbiIpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9OSwgZmlnLndpZHRoPTl9DQoNCnBsb3QoMSAtIGdpbmkuUk9DLnVud2VpZ2h0JG1hdHJpeC5zZXNwWzIsXSwgZ2luaS5ST0MudW53ZWlnaHQkbWF0cml4LnNlc3BbMSxdLA0KICAgICB0eXBlID0gImwiLA0KICAgICB4bGltID0gYygwLDEpLA0KICAgICB5bGltID0gYygwLDEpLA0KICAgICB4bGFiID0gIjEgLSBTcGVjaWZpY2l0eSAoRmFsc2UgUG9zaXRpdmUpIiwNCiAgICAgeWxhYiA9ICJTZW5zaXRpdml0eSAoVHJ1ZSBOZWdhdGl2ZSkiLA0KICAgICBjb2wgPSAiZGFya3Zpb2xldCIsDQogICAgIGx3ZCA9IDIsDQogICAgIGx0eSA9IDMsDQogICAgIG1haW4gPSAiUk9DIEN1cnZlIENvbXBhcnJpc29uIG9mIERlY2lzaW9uIFRyZWUgTW9kZWxzIikgDQphYmxpbmUoMCwxLCBsdHkgPSAyLCBjb2wgPSAiZGFya3JlZCIsIGx3ZCA9IDIpIA0KICBsaW5lcygxIC0gaW5mby5ST0MudW53ZWlnaHQkbWF0cml4LnNlc3BbMixdLCBpbmZvLlJPQy51bndlaWdodCRtYXRyaXguc2VzcFsxLF0sIGNvbCA9ICJkYXJrb3JhbmdlIiwgbHdkID0gMiwgbHR5ID0gMykgDQogIGxpbmVzKDEgLSBnaW5pLlJPQy5uZWckbWF0cml4LnNlc3BbMixdLCBnaW5pLlJPQy5uZWckbWF0cml4LnNlc3BbMSxdLCBjb2wgPSAiZGFya2dyZWVuIiwgbHdkID0gMiwgbHR5ID0gMykgDQogIGxpbmVzKDEgLSBpbmZvLlJPQy5uZWckbWF0cml4LnNlc3BbMixdLCBpbmZvLlJPQy5uZWckbWF0cml4LnNlc3BbMSxdLCBjb2wgPSAibmF2eSIsIGx3ZCA9IDIsIGx0eSA9IDMpIA0KICBsZWdlbmQoImJvdHRvbXJpZ2h0IiwgYyhwYXN0ZSgiR2luaSBVbndlaWdodGVkLCBBVUMgPSIsIGdpbmkuUk9DLnVud2VpZ2h0JEFVQyksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCJJbmZvIFVud2VpZ2h0ZWQsIEFVQyA9IiwgaW5mby5ST0MudW53ZWlnaHQkQVVDKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoIkdpbmkgTmVnLiBXZWlnaHQsIEFVQyA9IiwgZ2luaS5ST0MubmVnJEFVQyksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCJJbmZvIE5lZy4gV2VpZ2h0LCBBVUMgPSIsIGluZm8uUk9DLm5lZyRBVUMpKSwNCiAgICAgICAgIGNvbCA9IGMoImRhcmt2aW9sZXQiLCAiZGFya29yYW5nZSIsICJkYXJrZ3JlZW4iLCAibmF2eSIpLA0KICAgICAgICAgbHdkPTIsIGx0eT1yZXAoMyksIGNleCA9IDAuOCwgYnR5ID0gIm4iKQ0KYGBgDQoNCmBJbmZvLm5lZy53ZWlnaHRgIChFbnRyb3B5IG1vZGVsIHdpdGggZmFsc2UgbmVnYXRpdmUgd2VpZ2h0ZWQgMTB4KSBwcm92ZXMgdG8gYmUgdGhlIGJlc3QgZml0IG1vZGVsIHdpdGggdGhlIEFVQyBjbG9zZXN0IHRvIDEuMDAwICgkQVVDX3tpbmZvLm5lZy53ZWlnaHR9ID0gMC45ODIkKS4gIFdpdGggdGhlIGdyZWF0ZXN0IEFVQyB2YWx1ZSwgdGhpcyBtb2RlbCB3aWxsIHByb3ZpZGUgdGhlIG1vc3QgYWNjdXJhdGUgcHJlZGljdGlvbnMgb2YgdGhlIGZvdXIgZGVjaXNpb24gdHJlZSBtb2RlbHMgY29tcGFyZWQuIA0KDQoNCiMgQm9vdHN0cmFwIFByZWRpY3Rpb24NCg0KRmluYWxseSwgYSBib290c3RyYXAgcHJlZGljdGlvbiBtb2RlbCB3aXRoIGJhZ2dpbmcgd2FzIGltcGxlbWVudGVkIHRvIHByb3ZpZGUgYW4gYWRkaXRpb25hbCBwb3RlbnRpYWwgcHJlZGljdGl2ZSBtb2RlbCBmb3IgYnJlYXN0IGNhbmNlciBiaW9wc3kgb3V0Y29tZXMuIA0KDQpBc3N1bXB0aW9ucyBvZiB0aGUgYm9vdHN0cmFwIG1vZGVsOg0KDQotICAgU2FtcGxlIGlzIGEgcmFuZG9tIHNhbXBsZSBvZiB0aGUgcG9wdWxhdGlvbg0KLSAgIFNhbXBsZSBzaXplIGlzIGxhcmdlIGVub3VnaCBzbyB0aGUgc2FtcGxlIGRpc3RyaWJ1dGlvbiBtaXJyb3JzIHRoZQ0KICAgIHBvcHVsYXRpb24gZGlzdHJpYnV0aW9uDQoNClRvIG5vdGU6IHRoZSBib290c3RyYXAgbW9kZWwgZG9lcyBub3QgYXNzdW1lIGFueSBzcGVjaWZpYyBkaXN0cmlidXRpb24gb2YgdGhlIGRhdGEgcHJpb3IgdG8gbW9kZWwgZml0dGluZy4NCg0KQW4gaW5pdGlhbCBib290c3RyYXAgbW9kZWwgd2FzIHJ1biB0byBwcmVkaWN0IHRoZSBiaW9wc3kgb3V0Y29tZSBidXQgYWxsIEFVQyB2YWx1ZXMgcmV0dXJuZWQgYXMgMS4wMDAgaW5kaWNhdGluZyBtb2RlbCBvdmVyLWZpdHRpbmcgb2YgdGhlIG1vZGVsLiBJbiBvcmRlciB0byBwcmV2ZW50IG92ZXItZml0dGluZyBhbmQgdGh1cyBpbmFjY3VyYXRlIGZ1dHVyZSBwcmVkaWN0aW9ucywgYSBiYWdnaW5nIGVuc2VtYmxlIHdhcyB1dGlsaXplZC4NCg0KIyMgQmFnZ2luZw0KDQpUaGUgZGF0YSB3YXMgc3Vic2V0IGludG8gdGVzdCBhbmQgdHJhaW4gZGF0YSwgaW4gYSA3MDozMCByYW5kb20gc3BsaXQsIHdpdGggbm8gcmVwbGFjZW1lbnQuIEFuIGVudHJvcHkgd2FzIHV0aWxpemVkIGFuZCBmYWxzZSBuZWdhdGl2ZXMgd2VyZSBhZ2FpbiB3ZWlnaHRlZCAxMHggb2YgYSBmYWxzZSBwb3NpdGl2ZS4gIEJhZ2dpbmcgd2FzIGVtcGxveWVkIHRvIGZvcmNlIHRoZSBtb2RlbCB0byBhZ2dyZWdhdGUgdGhlIHBhcmFsbGVsIG1vZGVscyBhbmQgdGh1cyByZWR1Y2UgdmFyaWFuY2UgYWNyb3NzIG1vZGVscy4gIEFkZGl0aW9uYWxseSwgYmFnZ2luZyBkb2VzIG5vdCBhbGxvdyBhIHNpbmdsZSBvYnNlcnZhdGlvbiB0byBiZSByZXBlYXRlZCBpbiBhIHNhbXBsZSwgZnVydGhlciByZWR1Y2luZyB2YXJpYW5jZS4gDQoNClRoZSBiYWdnZWQgbW9kZWwgd2FzIGZpcnN0IHRyYWluZWQgb24gdGhlIHRyYWluaW5nIGRhdGEuIA0KDQpgYGB7cn0NCmRhdGEuYm9vdCA8LSByYXcuZGF0YQ0KI2NvbnZlcnRzIGZyb20gMS8yIHRvIDAvMSB2YXJpYWJsZQ0Kbm8ub3V0LmJvb3QgPC0gd2hpY2goZGF0YS5ib290JE91dGNvbWUgPT0gIk5vIikgDQp5ZXMub3V0LmJvb3QgPC0gIHdoaWNoKGRhdGEuYm9vdCRPdXRjb21lID09ICJZZXMiKQ0KZGF0YS5ib290JG91dCA9IDANCmRhdGEuYm9vdCRvdXRbeWVzLm91dC5ib290XSA9IDENCmRhdGEuYm9vdCA8LSBkYXRhLmJvb3RbLC0xMF0gICNyZW1vdmUgb3JpZ2luYWwgb3V0Y29tZSB2YXJpYWJsZSB0byBwcmV2ZW50IG92ZXJmaXR0aW5nDQpuYmFnID0gZGltKGRhdGEuYm9vdClbMV0gI3NhbXBsZSBzaXplDQp0cmFpbi5ib290LmlkIDwtIHNhbXBsZSgxOm5iYWcsIHJvdW5kKDAuNypuYmFnKSwgcmVwbGFjZSA9IEZBTFNFKSAjc3BsaXQgd2l0aCBubyByZXBsYWNlbWVudA0KdHJhaW4uYm9vdCA8LSBkYXRhLmJvb3RbdHJhaW4uYm9vdC5pZCxdICN0ZXN0IGRhdGENCnRlc3QuYm9vdCA8LSBkYXRhLmJvb3RbLXRyYWluLmJvb3QuaWQsXSAjdHJhaW4gZGF0YQ0KYGBgDQoNCmBgYHtyfQ0Kb3V0Y29tZS5iYWcudHJhaW4gPC0gYmFnZ2luZyhhcy5mYWN0b3Iob3V0KSB+IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbi5ib290LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYmFnZyA9IDE1MCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29vYiA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcm1zID0gbGlzdChsb3NzID0gbWF0cml4KGMoMCwxLDEwLDApLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieXJvdyA9IFRSVUUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdCA9ICJpbmZvIiksICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBycGFydC5jb250cm9sKG1pbnNwbGl0ID0gMTAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3AgPSAwLjAyKSkNCg0KYmFnLnByZWRpY3QgPC0gcHJlZGljdChvdXRjb21lLmJhZy50cmFpbiwgdHJhaW4uYm9vdCwgdHlwZSA9InByb2IiKQ0KDQpib290LmN1dG9mZiA8LSBzZXEoMCwxLGxlbmd0aCA9IDIwKQ0KDQpib290LnNuc3AubWF0cml4IDwtIG1hdHJpeCgwLCBuY29sID0gbGVuZ3RoKGJvb3QuY3V0b2ZmKSwgbnJvdyA9IDMsIGJ5cm93ID0gRkFMU0UpDQogIA0KICBmb3IoaSBpbiAxOmxlbmd0aChib290LmN1dG9mZikpew0KICAgIHByZWQuYm9vdC5vdXQgPC0gaWZlbHNlKGJhZy5wcmVkaWN0WywyXSA+PSBib290LmN1dG9mZltpXSwgMSwgMCkNCiAgICANCiAgICBUTmIgPSBzdW0ocHJlZC5ib290Lm91dCA9PSAwICYgdHJhaW4uYm9vdCRvdXQgPT0gMCkgIyB0cnVlIG5lZ2F0aXZlDQogICAgRk5iID0gc3VtKHByZWQuYm9vdC5vdXQgPT0gMCAmIHRyYWluLmJvb3Qkb3V0ID09IDEpICMgZmFsc2UgbmVnYXRpdmUNCiAgICBGUGIgPSBzdW0ocHJlZC5ib290Lm91dCA9PSAxICYgdHJhaW4uYm9vdCRvdXQgPT0gMCkgIyBmYWxzZSBwb3NpdGl2ZQ0KICAgIFRQYiA9IHN1bShwcmVkLmJvb3Qub3V0ID09IDEgJiB0cmFpbi5ib290JG91dCA9PSAxKSAjIHRydWUgcG9zaXRpdmUNCiAgICANCiAgICBib290LnNuc3AubWF0cml4WzEsaV0gPSBUUGIvKFRQYiArIEZOYikgI3NlbnNpdGl2aXR5DQogICAgYm9vdC5zbnNwLm1hdHJpeFsyLGldID0gVE5iLyhUTmIgKyBGUGIpICNzcGVjaWZpY2l0eQ0KICAgIGJvb3Quc25zcC5tYXRyaXhbMyxpXSA9IChUUGIgKyBUTmIpLyhUUGIgKyBUTmIgKyBGTmIgKyBGUGIpICNhY2N1cmFjeQ0KICB9DQoNCiAgcHJlZGljdGlvbi5ib290IDwtICBiYWcucHJlZGljdFssMV0NCiAgY2F0ZWdvcnkuYm9vdCA8LSB0cmFpbi5ib290JG91dCA9PSAxDQogIFJPQ29iai5ib290IDwtIHJvYyhjYXRlZ29yeS5ib290LCBwcmVkaWN0aW9uLmJvb3QpDQogIGJvb3RBVUMgPC0gYXVjKFJPQ29iai5ib290KQ0KDQogIA0KYGBgDQoNCg0KT25jZSB0cmFpbmVkLCB0aGUgYmFnZ2VkIG1vZGVsIHdhcyB0aGVuIHJlLXJ1biBvbiB0aGUgdGVzdCBkYXRhIHRvIGVuc3VyZSBhbiBlcXVhbCBvciBiZXR0ZXIgZml0IHRvIHRoZSB1dGlsaXplZCB0cmFpbmluZyBkYXRhLiBUaGlzIGVuc3VyZXMgdGhlIG1vZGVsIHdhcyBub3Qgb3Zlci1maXR0ZWQgdG8gdGhlIHRyYWluaW5nIGRhdGEgYW5kIGlzIGFibGUgdG8gYmUgdXRpbGl6ZWQgZm9yIHByZWRpY3Rpb25zLiANCg0KDQpgYGB7cn0NCm91dGNvbWUuYmFnLnRlc3QgPC0gYmFnZ2luZyhhcy5mYWN0b3Iob3V0KSB+IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0ZXN0LmJvb3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5iYWdnID0gMTUwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb29iID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFybXMgPSBsaXN0KGxvc3MgPSBtYXRyaXgoYygwLDEsMTAsMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gVFJVRSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gImluZm8iKSwgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAxMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjcCA9IDAuMDIpKQ0KDQpiYWcucHJlZGljdC50ZXN0IDwtIHByZWRpY3Qob3V0Y29tZS5iYWcudGVzdCwgdGVzdC5ib290LCB0eXBlID0icHJvYiIpDQoNCmJvb3QuY3V0b2ZmLnRlc3QgPC0gc2VxKDAsMSxsZW5ndGggPSAyMCkNCg0KYm9vdC5zbnNwLm1hdHJpeC50ZXN0IDwtIG1hdHJpeCgwLCBuY29sID0gbGVuZ3RoKGJvb3QuY3V0b2ZmLnRlc3QpLCBucm93ID0gMywgYnlyb3cgPSBGQUxTRSkNCiAgDQogIGZvcihpIGluIDE6bGVuZ3RoKGJvb3QuY3V0b2ZmLnRlc3QpKXsNCiAgICBwcmVkLmJvb3Qub3V0LnRlc3QgPC0gaWZlbHNlKGJhZy5wcmVkaWN0LnRlc3RbLDJdID49IGJvb3QuY3V0b2ZmLnRlc3RbaV0sIDEsIDApDQogICAgDQogICAgVE5idCA9IHN1bShwcmVkLmJvb3Qub3V0LnRlc3QgPT0gMCAmIHRlc3QuYm9vdCRvdXQgPT0gMCkgIyB0cnVlIG5lZ2F0aXZlDQogICAgRk5idCA9IHN1bShwcmVkLmJvb3Qub3V0LnRlc3QgPT0gMCAmIHRlc3QuYm9vdCRvdXQgPT0gMSkgIyBmYWxzZSBuZWdhdGl2ZQ0KICAgIEZQYnQgPSBzdW0ocHJlZC5ib290Lm91dC50ZXN0ID09IDEgJiB0ZXN0LmJvb3Qkb3V0ID09IDApICMgZmFsc2UgcG9zaXRpdmUNCiAgICBUUGJ0ID0gc3VtKHByZWQuYm9vdC5vdXQudGVzdCA9PSAxICYgdGVzdC5ib290JG91dCA9PSAxKSAjIHRydWUgcG9zaXRpdmUNCiAgICANCiAgICBib290LnNuc3AubWF0cml4LnRlc3RbMSxpXSA9IFRQYnQvKFRQYnQgKyBGTmJ0KSAjc2Vuc2l0aXZpdHkNCiAgICBib290LnNuc3AubWF0cml4LnRlc3RbMixpXSA9IFROYnQvKFROYnQgKyBGUGJ0KSAjc3BlY2lmaWNpdHkNCiAgICBib290LnNuc3AubWF0cml4LnRlc3RbMyxpXSA9IChUUGJ0ICsgVE5idCkvKFRQYnQgKyBUTmJ0ICsgRk5idCArIEZQYnQpICNhY2N1cmFjeQ0KICB9DQoNCiAgcHJlZGljdGlvbi5ib290LnRlc3QgPC0gIGJhZy5wcmVkaWN0LnRlc3RbLDFdDQogIGNhdGVnb3J5LmJvb3QudGVzdCA8LSB0ZXN0LmJvb3Qkb3V0ID09IDENCiAgUk9Db2JqLmJvb3QudGVzdCA8LSByb2MoY2F0ZWdvcnkuYm9vdC50ZXN0LCBwcmVkaWN0aW9uLmJvb3QudGVzdCkNCiAgYm9vdEFVQy50ZXN0IDwtIGF1YyhST0NvYmouYm9vdC50ZXN0KQ0KICANCm5sLnRlc3QgPC0gbGVuZ3RoKGJvb3Quc25zcC5tYXRyaXgudGVzdFszLF0pDQppZHgudGVzdCA8LSB3aGljaChib290LnNuc3AubWF0cml4LnRlc3RbMyxdID09IG1heChib290LnNuc3AubWF0cml4WzMsXSkpICAjbWF4IGFjY3VyYWN5DQp0aWNrLnRlc3QgPC0gYXMuY2hhcmFjdGVyKHJvdW5kKGJvb3QuY3V0b2ZmLnRlc3QsIDIpKQ0KICANCmBgYA0KDQpGaW5hbGx5IHRoZSBiYWdnZWQgbW9kZWwgd2FzIHJ1biBhIHRoaXJkIHRpbWUgb24gdGhlIGVudGlyZSBkYXRhc2V0IHRvIGFsbG93IGZvciBjb21wYXJyaXNvbiBhY3Jvc3MgYWxsIG1vZGVscyB1dGlsaXplZCBhcyBwb3RlbnRpYWwgcHJlZGljdGl2ZSBtb2RlbHMuICBDaGVja2luZyBhZ2FpbnN0IHRoZSBmdWxsIGRhdGFzZXQgYWdhaW4gYWxzbyBlbnN1cmVzIG5vIG92ZXItZml0dGluZyB0b29rIHBsYWNlIGR1cmluZyBtb2RlbCBjb25zdHJ1Y3Rpb24uIA0KDQpgYGB7cn0NCm91dGNvbWUuYmFnLmZ1bGwgPC0gYmFnZ2luZyhhcy5mYWN0b3Iob3V0KSB+IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRhLmJvb3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5iYWdnID0gMTUwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb29iID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFybXMgPSBsaXN0KGxvc3MgPSBtYXRyaXgoYygwLDEsMTAsMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gVFJVRSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gImluZm8iKSwgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAxMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjcCA9IDAuMDIpKQ0KDQpiYWcucHJlZGljdC5mdWxsIDwtIHByZWRpY3Qob3V0Y29tZS5iYWcuZnVsbCwgZGF0YS5ib290LCB0eXBlID0icHJvYiIpDQoNCmJvb3QuY3V0b2ZmLmZ1bGwgPC0gc2VxKDAsMSxsZW5ndGggPSAyMCkNCg0KYm9vdC5zbnNwLm1hdHJpeC5mdWxsIDwtIG1hdHJpeCgwLCBuY29sID0gbGVuZ3RoKGJvb3QuY3V0b2ZmLmZ1bGwpLCBucm93ID0gMywgYnlyb3cgPSBGQUxTRSkNCiAgDQogIGZvcihpIGluIDE6bGVuZ3RoKGJvb3QuY3V0b2ZmLmZ1bGwpKXsNCiAgICBwcmVkLmJvb3Qub3V0LmZ1bGwgPC0gaWZlbHNlKGJhZy5wcmVkaWN0LmZ1bGxbLDJdID49IGJvb3QuY3V0b2ZmLmZ1bGxbaV0sIDEsIDApDQogICAgDQogICAgVE5iZiA9IHN1bShwcmVkLmJvb3Qub3V0LmZ1bGwgPT0gMCAmIGRhdGEuYm9vdCRvdXQgPT0gMCkgIyB0cnVlIG5lZ2F0aXZlDQogICAgRk5iZiA9IHN1bShwcmVkLmJvb3Qub3V0LmZ1bGwgPT0gMCAmIGRhdGEuYm9vdCRvdXQgPT0gMSkgIyBmYWxzZSBuZWdhdGl2ZQ0KICAgIEZQYmYgPSBzdW0ocHJlZC5ib290Lm91dC5mdWxsID09IDEgJiBkYXRhLmJvb3Qkb3V0ID09IDApICMgZmFsc2UgcG9zaXRpdmUNCiAgICBUUGJmID0gc3VtKHByZWQuYm9vdC5vdXQuZnVsbCA9PSAxICYgZGF0YS5ib290JG91dCA9PSAxKSAjIHRydWUgcG9zaXRpdmUNCiAgICANCiAgICBib290LnNuc3AubWF0cml4LmZ1bGxbMSxpXSA9IFRQYmYvKFRQYmYgKyBGTmJmKSAjc2Vuc2l0aXZpdHkNCiAgICBib290LnNuc3AubWF0cml4LmZ1bGxbMixpXSA9IFROYmYvKFROYmYgKyBGUGJmKSAjc3BlY2lmaWNpdHkNCiAgICBib290LnNuc3AubWF0cml4LmZ1bGxbMyxpXSA9IChUUGJmICsgVE5iZikvKFRQYmYgKyBUTmJmICsgRk5iZiArIEZQYmYpICNhY2N1cmFjeQ0KICB9DQoNCiAgcHJlZGljdGlvbi5ib290LmZ1bGwgPC0gIGJhZy5wcmVkaWN0LmZ1bGxbLDFdDQogIGNhdGVnb3J5LmJvb3QuZnVsbCA8LSBkYXRhLmJvb3Qkb3V0ID09IDENCiAgUk9Db2JqLmJvb3QuZnVsbCA8LSByb2MoY2F0ZWdvcnkuYm9vdC5mdWxsLCBwcmVkaWN0aW9uLmJvb3QuZnVsbCkNCiAgYm9vdEFVQy5mdWxsIDwtIGF1YyhST0NvYmouYm9vdC5mdWxsKQ0KICANCm5sLmZ1bGwgPC0gbGVuZ3RoKGJvb3Quc25zcC5tYXRyaXguZnVsbFszLF0pDQppZHguZnVsbCA8LSB3aGljaChib290LnNuc3AubWF0cml4LmZ1bGxbMyxdID09IG1heChib290LnNuc3AubWF0cml4WzMsXSkpICAjbWF4IGFjY3VyYWN5DQp0aWNrLmZ1bGwgPC0gYXMuY2hhcmFjdGVyKHJvdW5kKGJvb3QuY3V0b2ZmLmZ1bGwsIDIpKQ0KDQoNCiAgDQpgYGANCg0KYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTl9DQpsYWJlbCA8LSAiQkFHR0lORyBCb290c3RyYXAgTW9kZWw6IA0KRW50cm9weSBNb2RlbCBXZWlnaHRlZCAxMHggZm9yIEZhbHNlIE5lZ2F0aXZlcw0KDQooQSBtaXNzZWQgZGlhZ25vc2lzIG9mIG1hbGlnbmFuY3kgMTB4IHdvcnNlIHRoYW4NCmEgZmFsc2UgcG9zaXRpdmUgZGlhZ25vc2lzIG9mIG1hbGlnbmFuY3kgYXQgYmlvcHN5KSINCg0KcGxvdCgoMSAtIGJvb3Quc25zcC5tYXRyaXhbMixdKSwgYm9vdC5zbnNwLm1hdHJpeFsxLF0sDQogICAgIHR5cGUgPSAibCIsDQogICAgIHhsaW0gPSBjKDAsMSksDQogICAgIHlsaW0gPSBjKDAsMSksDQogICAgIHhsYWIgPSAiMSAtIFNwZWNpZmljaXR5IChGYWxzZSBQb3NpdGl2ZSkiLA0KICAgICB5bGFiID0gIlNlbnNpdGl2aXR5IChUcnVlIE5lZ2F0aXZlKSIsDQogICAgIGNvbCA9ICJkYXJrdmlvbGV0IiwNCiAgICAgbHdkID0gMiwNCiAgICAgbHR5ID0gMSwNCiAgICAgbWFpbiA9ICJST0MgQ3VydmUgb2YgQkFHR0lORyBCb290c3RyYXAgVHJhaW5pbmcgRGF0YSIpIA0KYWJsaW5lKDAsMSwgbHR5ID0gMiwgY29sID0gImRhcmtyZWQiLCBsd2QgPSAyKSANCmxpbmVzKCgxIC0gYm9vdC5zbnNwLm1hdHJpeC50ZXN0WzIsXSksIGJvb3Quc25zcC5tYXRyaXgudGVzdFsxLF0sIGNvbCA9ICJkYXJrb3JhbmdlIiwgbHdkID0gMiwgIGx0eSA9IDEpDQpsaW5lcygoMSAtIGJvb3Quc25zcC5tYXRyaXguZnVsbFsyLF0pLCBib290LnNuc3AubWF0cml4LmZ1bGxbMSxdLCBjb2wgPSAiZGFya2JsdWUiLCBsd2QgPSAyLCAgbHR5ID0gMSkNCnRleHQoMC43NSwgMC40LCBsYWJlbCkNCmxlZ2VuZCgiYm90dG9tcmlnaHQiLCBjKHBhc3RlKCJJbmZvIFdlaWdodGVkIFRyYWluLCBBVUMgPSIsIGJvb3RBVUMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoIkluZm8gV2VpZ2h0ZWQgVGVzdCwgQVVDID0iLCBib290QVVDLnRlc3QpLA0KICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoIkluZm8gV2VpZ2h0ZWQgRnVsbCwgQVVDID0iLCBib290QVVDLmZ1bGwpKSwNCiAgICAgICAgIGNvbCA9IGMoImRhcmt2aW9sZXQiLCAiZGFya29yYW5nZSIsICJkYXJrYmx1ZSIpLA0KICAgICAgICAgbHdkPTIsIGx0eT1yZXAoMSksIGNleCA9IDAuOCwgYnR5ID0gIm4iKQ0KDQpgYGANCg0KIyBGaW5hbCBNb2RlbCBDb21hcHJyaXNvbiAmIENvbmNsdXNpb25zDQoNCkVhY2ggbW9kZWwgZXhwbG9yZWQgcmV0dXJuZWQgYSBoaWdoIEFVQyB2YWx1ZSBhbmQgb3ZlcmFsbCBkZW1vbnN0cmF0ZWQgYSBnb29kIGZpdCBhbmQgaGlnaCBwcmVkaWN0aXZlIHBvc3NpYmlsaXR5IGZvciB0aGUgYnJlYXN0IGNhbmNlciBiaW9wc3kgZGF0YS4gIEluIG9yZGVyIHRvIGRldGVybWluZSB0aGUgdHJ1ZSBiZXN0IGZpdCBtb2RlbCwgYWxsIGZvdXIgUk9DIGN1cnZlcyBhbmQgQVVDIHZhbHVlcyB3ZXJlIHBsb3R0ZWQgZm9yIGNvbXBhcmlzb24uICANCg0KQm90aCB0aGUgcmVkdWNlZCBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGFuZCBiYWdnZWQgbW9kZWwgaGF2ZSBuZWFyIGlkZW50aWNhbCBBVUMgdmFsdWVzICgwLjk5NCBhbmQgMC45OTMgcmVzcGVjdGl2ZWx5KSwgYW5kIGJvdGggY2FuIGJlIHV0aWxpemVkIHRvIGFjY3VyYXRlbHkgcHJlZGljdCB0aGUgb3V0Y29tZSB2YWx1ZXMgZm9yIGJyZWFzdCBjYW5jZXIgYmlvcHN5IGRhdGEuDQoNCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD05fQ0KDQpwbG90KCgxIC0gYm9vdC5zbnNwLm1hdHJpeC5mdWxsWzIsXSksIGJvb3Quc25zcC5tYXRyaXguZnVsbFsxLF0sDQogICAgIHR5cGUgPSAibCIsDQogICAgIHhsaW0gPSBjKDAsMSksDQogICAgIHlsaW0gPSBjKDAsMSksDQogICAgIHhsYWIgPSAiMSAtIFNwZWNpZmljaXR5IChGYWxzZSBQb3NpdGl2ZSkiLA0KICAgICB5bGFiID0gIlNlbnNpdGl2aXR5IChUcnVlIE5lZ2F0aXZlKSIsDQogICAgIGNvbCA9ICJkYXJrdmlvbGV0IiwNCiAgICAgbHdkID0gMiwNCiAgICAgbHR5ID0gMSwNCiAgICAgbWFpbiA9ICJST0MgQ3VydmUgb2YgQkFHR0lORyBCb290c3RyYXAgVHJhaW5pbmcgRGF0YSIpIA0KYWJsaW5lKDAsMSwgbHR5ID0gMiwgY29sID0gImRhcmtyZWQiLCBsd2QgPSAyKSANCmxpbmVzKCgxIC0gaW5mby5ST0MubmVnJG1hdHJpeC5zZXNwWzIsXSksIGluZm8uUk9DLm5lZyRtYXRyaXguc2VzcFsxLF0sIGNvbCA9ICJkYXJrb3JhbmdlIiwgbHdkID0gMiwgIGx0eSA9IDEpDQpsaW5lcygoMSAtIFJPQ29iai5ubiRzZW5zaXRpdml0aWVzKSwgUk9Db2JqLm5uJHNwZWNpZmljaXRpZXMsIGNvbCA9ICJkYXJrYmx1ZSIsIGx3ZCA9IDIsICBsdHkgPSAxKQ0KbGluZXMoKDEgLSBzZW5zaXRpdiksIHNwZWNpZjIsIGNvbCA9ICJkYXJrZ3JlZW4iLCBsd2QgPSAyLCAgbHR5ID0gMSkNCmxlZ2VuZCgiYm90dG9tcmlnaHQiLCBjKHBhc3RlKCJCQUdHRUQgQm9vdHN0cmFwIE1vZGVsLCBBVUMgPSIsIHJvdW5kKGJvb3RBVUMuZnVsbCwgMykpLA0KICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoIkVudHJvcHkgTmVnLiBXZWlnaHRlZCBNb2RlbCwgQVVDID0iLCBpbmZvLlJPQy5uZWckQVVDKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCJOZXVyYWwgTmV0IFBlcmNlcHRyb24gTW9kZWwgQVVDID0iLCByb3VuZChST0NvYmoubm4kYXVjLCAzKSksDQogICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiUmVkdWNlZCBMaW5lYXIgUmVncmVzc2lvbiBNb2RlbCBBVUMgPSIsIHJvdW5kKFJPQ29iajIkYXVjLCAzKSkpLA0KICAgICAgICAgY29sID0gYygiZGFya3Zpb2xldCIsICJkYXJrb3JhbmdlIiwgImRhcmtibHVlIiwgImRhcmtncmVlbiIpLA0KICAgICAgICAgbHdkPTIsIGx0eT1yZXAoMSksIGNleCA9IDAuOCwgYnR5ID0gIm4iKQ0KDQoNCg0KYGBgDQoNCg0KYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTl9DQoNCnBsb3QoKDEgLSBib290LnNuc3AubWF0cml4LmZ1bGxbMixdKSwgYm9vdC5zbnNwLm1hdHJpeC5mdWxsWzEsXSwNCiAgICAgdHlwZSA9ICJsIiwNCiAgICAgeGxpbSA9IGMoMCwxKSwNCiAgICAgeWxpbSA9IGMoMCwxKSwNCiAgICAgeGxhYiA9ICIxIC0gU3BlY2lmaWNpdHkgKEZhbHNlIFBvc2l0aXZlKSIsDQogICAgIHlsYWIgPSAiU2Vuc2l0aXZpdHkgKFRydWUgTmVnYXRpdmUpIiwNCiAgICAgY29sID0gImRhcmt2aW9sZXQiLA0KICAgICBsd2QgPSAzLA0KICAgICBsdHkgPSAxLA0KICAgICBtYWluID0gIlJPQyBDdXJ2ZXMgb2YgQkFHR0VEIEJvb3RzdHJhcCAmDQpSZWR1Y2VkIExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWxzIikgDQphYmxpbmUoMCwxLCBsdHkgPSAyLCBjb2wgPSAiZGFya3JlZCIsIGx3ZCA9IDIpIA0KbGluZXMoKDEgLSBzZW5zaXRpdiksIHNwZWNpZjIsIGNvbCA9ICJkYXJrZ3JlZW4iLCBsd2QgPSAzLCAgbHR5ID0gMSkNCmxlZ2VuZCgiYm90dG9tcmlnaHQiLCBjKHBhc3RlKCJCQUdHRUQgQm9vdHN0cmFwIE1vZGVsLCBBVUMgPSIsIHJvdW5kKGJvb3RBVUMuZnVsbCwgMykpLA0KICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoIlJlZHVjZWQgTGluZWFyIFJlZ3Jlc3Npb24gTW9kZWwgQVVDID0iLCByb3VuZChST0NvYmoyJGF1YywgMykpKSwNCiAgICAgICAgIGNvbCA9IGMoImRhcmt2aW9sZXQiLCAiZGFya2dyZWVuIiksDQogICAgICAgICBsd2Q9MiwgbHR5PXJlcCgxKSwgY2V4ID0gMC44LCBidHkgPSAibiIpDQoNCg0KICAgICAgICAgIA0KYGBgDQoNCiMgUmVmZXJlbmNlcw0KDQpEYXRhIFNvdXJjZTogQm9vayAtIEFwcGxpZWQgQW5hbHl0aWNzIHRocm91Z2ggQ2FzZSBTdHVkaWVzIFVzaW5nIFNBUyBhbmQNClIsIERlZXB0aSBHdXB0YSBieSBBUHJlc3MsIElTQk4gLSA5NzgtMS00ODQyLTM1MjUtNg0KDQpSZWZlcmVuY2UgTWF0ZXJpYWxzOg0KPGh0dHBzOi8vYm9va2Rvd24ub3JnL2dtY2lyY280Mi9kZWNpc2lvbl90cmVlcy9kZWNpc2lvbl90cmVlcy5odG1sPg0KDQo8aHR0cHM6Ly93d3cuY2FuY2VyLm9yZy9jYW5jZXIvdHlwZXMvYnJlYXN0LWNhbmNlci9zY3JlZW5pbmctdGVzdHMtYW5kLWVhcmx5LWRldGVjdGlvbi9icmVhc3QtYmlvcHN5L2NvcmUtbmVlZGxlLWJpb3BzeS1vZi10aGUtYnJlYXN0Lmh0bWw+DQoNCjxodHRwczovL3d3dy5jYW5jZXIub3JnL2NhbmNlci90eXBlcy9icmVhc3QtY2FuY2VyL2Fib3V0L2hvdy1jb21tb24taXMtYnJlYXN0LWNhbmNlci5odG1sPg0K