1 Introduction

There are a lot of debates on the difference between statistics and machine learning in statistics and machine learning communities. Certainly, statistics and machine learning are not the same although there is an overlap. A major difference between machine learning and statistics is indeed their purpose.

  • Statistics focuses on the inference and interpretability of the relationships between variables.

  • Machine learning focuses on the accuracy of the prediction of future values of (response) variables and detecting hidden patterns. Machine learning is traditionally considered to be a subfield of artificial intelligence, which is broadly defined as the capability of a machine to imitate intelligent human behavior.

A lot of statistical models can make predictions, but predictive accuracy is not their strength while machine learning models provide various degrees of interpretability sacrifice interpretability for predictive power. For example, regularized regressions as machine learning algorithms are interpretable but neural networks (particularly multi-layer networks ) are almost uninterpretable.

Statistics and machine learning are two of the key players in data science. As data science practitioners, our primary interest is to develop and select the right tools to build data solutions for real-world applications.


2 Some Machine Learning Jargon

Before demonstrating how some classical models are used as machine learning algorithms, we first introduce a partial list of machine learning jargon. This terms

Statistics Machine Learning Comments
data point, record, row of data example, instance Both domains also use “observation,” which can refer to a single measurement or an entire vector of attributes depending on context.
response variable, dependent variable label, output Both domains also use “target.” Since practically all variables depend on other variables, the term “dependent variable” is potentially misleading.
regressions supervised learners, machines Both estimate output(s) in terms of input(s).
regression intercept bias the default prediction of a linear model in the special case where all inputs are 0.
Maximize the likelihood of estimating model parameters Minimize the entropy to derive the best parameters in categorical regression or maximize the likelihood for continuous regression. For discrete distributions, maximizing the likelihood is equivalent to minimizing the entropy.
logistic/multinomial regression maximum entropy, MaxEnt They are equivalent except in special multinomial settings like ordinal logistic regression.


3 Logistic Regression Model Revisited

Recall that the binary logistic regression model with \(k\) feature variables \(x_1, x_2, \cdots, x_k\) is given by

\[ P[Y = 1 \big| (x_1, x_2, \cdots, x_k)] = \frac{\exp(w_0 + w_1x_1 + w_2x_2 + \cdots + w_kx_k)}{1+\exp(w_0 + w_1x_1 + w_2x_2 + \cdots + w_kx_k)} \]

\[ = \frac{\exp(w_0+\sum_{i=1}^k w_ix_i)}{1 + \exp(w_0+\sum_{i=1}^k w_ix_i)} = \frac{1}{1+ \exp[-(w_0+\sum_{i=1}^k w_ix_i)]} \]

where \(w_0, w_1, \cdots, w_n\) are regression coefficients. Let

\[ \pi(x_1,x_2, \cdots,x_k) = P[Y=1\big| (x_1,x_2,\cdots,x_k)]. \]

be the probability of success given the covariate pattern \((x_1, x_2, \cdots, x_k)\). We can re-express the logistic regression model in the following form

\[ \log \left( \frac{\pi(x_1,x_2, \cdots,x_k)}{1 + \pi(x_1,x_2, \cdots,x_k)}\right) = w_0+\sum_{i=1}^k w_ix_i \]

where

\[ \frac{\pi(x_1,x_2, \cdots,x_k)}{1 + \pi(x_1,x_2, \cdots,x_k)} = \text{odds of success for given } (x_1, x_2, \cdots, x_k). \]

Therefore, the general logistic regression model is also called log odds regression. This also makes logistic regression interpretable since the regression coefficient \(w_i\) is the change of log odds of success when the covariate \(x_i\) increases by a unit and all other covariates remain unchanged (for \(i = 1, 2, \cdots, k\)).

Let \(z = w_0+\sum_{i=1}^k w_ix_i\) be the linear combination of predictors, that impact the success probability, then

\[ \pi(z) = \frac{1}{1+ \exp(-z)} \ \ \text{ for } \ \ -\infty < z < \infty. \]

Note that \(\pi(z)\) is the well-known logistic function. The curve of the logistic function is given by

x = seq(-5, 5, length = 100)
y = 1/(1+exp(-x))
plot(x,y, type = "l", lwd = 2, xlab ="z ", ylab = expression(pi(z)), 
     main = "Logistic Curve", col = "blue")
text(-3, 0.8, expression(pi(z) ==frac(1,1+exp(-z))), col = "blue")
Figure 5.  The curve of the logistic function.

Figure 5. The curve of the logistic function.

The main feature of logistic function \(f(z)\) is its S-shape curve with a range \(f(z) \in [0,1]\), domain \(z \in (-\infty, \infty)\), and \(f(z) = 1/2\). Other functions have the same properties as logistic functions. These types of functions are called sigmoid functions.

Note that the inverse of the logistic function is called the logit function that is given by

\[ f^{-1}(x) = \log \left( \frac{x}{1-x}\right). \]

The logistic regression is also called logit regression. It is also called log odds regression because \(P(Y=\text{success})/[1-P(Y=\text{success})]\) is the odds of success.

4 Working Data Set and EDA

This section introduces the data set and prepares an analytic data set to perform predictive modeling using both classical statistical methods and machine learning algorithms.

4.1 Framingham Study Data

The Framingham Data is collected from an ongoing cardiovascular study on residents of the town of Framingham, Massachusetts. The goal of this application is to predict whether the patient has a 10-year risk of future coronary heart disease (CHD). The data set contains 4000 records and 15 variables that provide the patients’ demographic and medical information.

A brief description of the variables is given below.

male: male ( = 1) or female ( = 0)

age: Age of the patient;

education: A categorical variable of the participants’ education, with the levels: Some high school (1), high school/GED (2), some college/vocational school (3), college (4)

currentSmoker: Current cigarette smoking at the time of examinations

cigsPerDay: Number of cigarettes smoked each day

BPmeds: Use of Anti-hypertensive medication at exam

prevalentStroke: Prevalent Stroke (0 = free of disease)

prevalentHyp: Prevalent Hypertensive. The subject was defined as hypertensive if treated

diabetes: Diabetic according to criteria of first exam treated

totChol: Total cholesterol (mg/dL)

sysBP: Systolic Blood Pressure (mmHg)

diaBP: Diastolic blood pressure (mmHg)

BMI: Body Mass Index, weight (kg)/height (m)^2

heartRate: Heart rate (beats/minute)

glucose: Blood glucose level (mg/dL)

TenYearCHD: The 10 year risk of coronary heart disease(CHD)


4.2 Exploratory Data Analysis

We first inspect the data set by creating tables and making some plots to assess the distribution of each variable in the data set.

Framingham0 = read.csv("https://pengdsci.github.io/STA551/w08/framingham.csv")
Framingham = Framingham0
summary(Framingham0)
      male             age          education     currentSmoker   
 Min.   :0.0000   Min.   :32.00   Min.   :1.000   Min.   :0.0000  
 1st Qu.:0.0000   1st Qu.:42.00   1st Qu.:1.000   1st Qu.:0.0000  
 Median :0.0000   Median :49.00   Median :2.000   Median :0.0000  
 Mean   :0.4292   Mean   :49.58   Mean   :1.979   Mean   :0.4941  
 3rd Qu.:1.0000   3rd Qu.:56.00   3rd Qu.:3.000   3rd Qu.:1.0000  
 Max.   :1.0000   Max.   :70.00   Max.   :4.000   Max.   :1.0000  
                                  NA's   :105                     
   cigsPerDay         BPMeds        prevalentStroke     prevalentHyp   
 Min.   : 0.000   Min.   :0.00000   Min.   :0.000000   Min.   :0.0000  
 1st Qu.: 0.000   1st Qu.:0.00000   1st Qu.:0.000000   1st Qu.:0.0000  
 Median : 0.000   Median :0.00000   Median :0.000000   Median :0.0000  
 Mean   : 9.003   Mean   :0.02963   Mean   :0.005899   Mean   :0.3105  
 3rd Qu.:20.000   3rd Qu.:0.00000   3rd Qu.:0.000000   3rd Qu.:1.0000  
 Max.   :70.000   Max.   :1.00000   Max.   :1.000000   Max.   :1.0000  
 NA's   :29       NA's   :53                                           
    diabetes          totChol          sysBP           diaBP       
 Min.   :0.00000   Min.   :107.0   Min.   : 83.5   Min.   : 48.00  
 1st Qu.:0.00000   1st Qu.:206.0   1st Qu.:117.0   1st Qu.: 75.00  
 Median :0.00000   Median :234.0   Median :128.0   Median : 82.00  
 Mean   :0.02572   Mean   :236.7   Mean   :132.4   Mean   : 82.89  
 3rd Qu.:0.00000   3rd Qu.:263.0   3rd Qu.:144.0   3rd Qu.: 89.88  
 Max.   :1.00000   Max.   :696.0   Max.   :295.0   Max.   :142.50  
                   NA's   :50                                      
      BMI          heartRate         glucose         TenYearCHD   
 Min.   :15.54   Min.   : 44.00   Min.   : 40.00   Min.   :0.000  
 1st Qu.:23.07   1st Qu.: 68.00   1st Qu.: 71.00   1st Qu.:0.000  
 Median :25.40   Median : 75.00   Median : 78.00   Median :0.000  
 Mean   :25.80   Mean   : 75.88   Mean   : 81.97   Mean   :0.152  
 3rd Qu.:28.04   3rd Qu.: 83.00   3rd Qu.: 87.00   3rd Qu.:0.000  
 Max.   :56.80   Max.   :143.00   Max.   :394.00   Max.   :1.000  
 NA's   :19      NA's   :1        NA's   :388                     

The above descriptive tables indicate a few variables involved in missing values. Two variables that have a significant portion of missing values are levels of education and glucose.

The variable glucose is clinically associated with diabetes(which has only 5 missing values in it). We could use this relationship to impute the missing values in glucose. After looking at the proportion of missing values in the diabetes group and diabetes-free group, the missing percentage points are about 9% and 4%, respectively.

diabetes.id = which(Framingham$diabetes == 1)
diab.glucose = Framingham[diabetes.id, "glucose"]
no.diab.glucose = Framingham[-diabetes.id, "glucose"]
#table(Framingham$diabetes)
#summary(diab.glucose)
#summary(no.diab.glucose)
plot(density(na.omit(no.diab.glucose)), col = "darkred",
     main = "Distribution of Glucose Levels",
     xlab = "Glucose")
lines(density(na.omit(diab.glucose)), col = "blue")
legend("topright", c("Diabetes Group", "Diabetes Free"), 
       lty =rep(1,2),
       col=c("darkred", "blue"),
       bty = "n", cex = 0.8)

There are about 2.5% of participants in the study had diabetes. This may cause a potential imbalanced category issue. As anticipated, the distribution glucose levels of diabetes and diabetes-free groups are significantly different.

NumVar = Framingham[, c(2,10:15)] 
pairs(NumVar, cex = 0.3, col = "navy", main ="Pair-wise scatter plot of numerical variables")

We will discuss imputation methods to handle missing values in related variables for algorithm-based prediction.

sex = as.data.frame(table(Framingham$male))
colnames(sex) = c("sex", "counts")
sex$sex = ifelse(sex$sex == 1, "male", "female")
###
edu = as.data.frame(table(Framingham$education))
colnames(edu) = c("EduLevel", "counts")
edu$EduLevel = ifelse(edu$EduLevel == 1, "HS-", 
                 ifelse(edu$EduLevel == 2, "HS", 
                      ifelse(edu$EduLevel == 3, "Col-", "Col" )))
###
par(mfrow = c(2,2))
barplot(height=sex$counts, names = sex$sex, col = "steelblue",
        main = "Distribution of Sex")
hist(Framingham$age, xlab="age", ylab="counts", col="steelblue",
     main = "Age Distribution")
barplot(height=edu$counts, names = edu$EduLevel, col = "steelblue",
        main = "Distribution of Education")
hist(Framingham$cigsPerDay, xlab="cigs per day", ylab="counts",
     col="steelblue",
     main = "Number of Cigs Day")

currentSmoker = as.data.frame(table(Framingham$currentSmoker))
colnames(currentSmoker) = c("currentSmoker", "counts")
currentSmoker$currentSmoker = ifelse(currentSmoker$currentSmoker == 1, "Yes", "No ")
###
BPMeds = as.data.frame(table(Framingham$BPMeds))
colnames(BPMeds) = c("BPMeds", "counts")
BPMeds$BPMeds = ifelse(BPMeds$BPMeds == 1, "Yes", "No ")
###
prevalentStroke = as.data.frame(table(Framingham$prevalentStroke))
colnames(prevalentStroke) = c("prevalentStroke", "counts")
prevalentStroke$prevalentStroke = ifelse(prevalentStroke$prevalentStroke == 1, "Yes", "No ")
###   
prevalentHyp = as.data.frame(table(Framingham$prevalentHyp))
colnames(prevalentHyp) = c("prevalentHyp", "counts")
prevalentHyp$prevalentHyp = ifelse(prevalentHyp$prevalentHyp == 1, "Yes", "No ")
###
par(mfrow=c(2,2))
barplot(height=currentSmoker$counts, names = currentSmoker$currentSmoker, col = "steelblue",
        main = "Smoking Status")
barplot(height=BPMeds$counts, names = BPMeds$BPMeds, col = "steelblue",
        main = "Blood Pressure Treatment")
barplot(height=prevalentStroke$counts, names = prevalentStroke$prevalentStroke, col = "steelblue",
        main = "Stroke Status")
barplot(height=prevalentHyp$counts, names = prevalentHyp$prevalentHyp, col = "steelblue",
        main = "Hypertension Status")

diabetes = as.data.frame(table(Framingham$diabetes))
colnames(diabetes) = c("diabetes", "counts")
diabetes$diabetes = ifelse(diabetes$diabetes == 1, "Yes", "No ")
###
par(mfrow=c(2,2))
barplot(height=diabetes$counts, names = diabetes$diabetes, col = "steelblue",
        main = "Diabetes Status")
hist(Framingham$totChol, xlab="Total Cholestrol", ylab="counts",
     col="steelblue",
     main = "Total Cholestrol (mg/dL)")
hist(Framingham$sysBP, xlab="Systolic BP", ylab="counts",
     col="steelblue",
     main = "Systolic Blood Pressure (mmHg)")
hist(Framingham$diaBP, xlab="Systolic BP", ylab="counts",
     col="steelblue",
     main = "Diastolic Blood Pressure (mmHg)")

TenYearCHD = as.data.frame(table(Framingham$TenYearCHD))
colnames(TenYearCHD) = c("TenYearCHD", "counts")
TenYearCHD$TenYearCHD = ifelse(TenYearCHD$TenYearCHD == 1, "Yes", "No ")
###
par(mfrow=c(2,2))
hist(Framingham$BMI, xlab="BMI", ylab="counts",
     col="steelblue",
     main = "Body Mass Index")
hist(Framingham$heartRate, xlab="Heart Rate", ylab="counts",
     col="steelblue",
     main = "Heart Rate (beats/minute)")
hist(Framingham$glucose, xlab="Glucose", ylab="counts",
     col="steelblue",
     main = "Blood Glucose Level (mg/dL)")
barplot(height=TenYearCHD$counts, names = TenYearCHD$TenYearCHD, col = "steelblue",
        main = "Coronary Heart Disease Status")

The above distributional graphics indicate that BPMeds (receiving blood pressure medication), diabetes, and prevalentStroke have small categories (less than 1%). Some of these might cause potential issues in the final modeling. According to a recent study (https://www.bmj.com/content/BMJ/360/BMJ.j5855.Full.pdf), smoking is a significant contributor to CHD. We will include the variable cigsPerDay in the modeling. However, cigsPerDay needs to be regrouped to make a categorical variable with categories: 0, 1-10, 11-19, and 20+ representing current smoking status: nonsmoker, light smoker, moderate smoker, and heavy smoker.


4.3 Missing-data Imputation

Among variables that have missing values, education and glucose have a significant portion of missing values. Variables cigsPerDay, BPMeds, totChol, BMI, and heartRate have less than 1% missing values. For those with a small portion of missing values, we use mean/mode replacement method to impute the missing values.

Variable education has slightly less than 2.5% missing values. There are no auxiliary variables in the data that can be used to impute the missing values in education. The simple mode replacement will change the probability structure. Therefore, education will not used in the subsequent modeling. We will glucose is clinically associated with several variables such as diabetes, TenYearCHD, currentSmoker, etc. We will use these auxiliary variables using regression methods.

4.3.1 Mean/Mode/Random Replacement Methods

There is no base R function to find the mode of a data set. We first define an R function to find the mode of a given data set.

# Create the function.
getmode <- function(v) {
   uniqv <- unique(v)
   uniqv[which.max(tabulate(match(v, uniqv)))]
}

The method of random replacement uses the (empirical) distribution of the complete data values to simulate random values and replace the missing values with these generated random values. This type of imputation is recommended for no other auxiliary variables in the data set.

## mode replacement
Framingham$cigsPerDay[is.na(Framingham$cigsPerDay)] = getmode(Framingham$cigsPerDay)
## Random replacement for BPMeds using binomial distribution with p = 0.03
Framingham$BPMeds[is.na(Framingham$BPMeds)] =rbinom(53,1,0.03)  # Bernoulli trial
## Remove NA first then take a random sample to replace the missing values
Framingham$totChol[is.na(Framingham$totChol)] = sample(na.omit(Framingham$totChol), 
                                                       50, 
                                                       replace = FALSE)
Framingham$BMI[is.na(Framingham$BMI)] = sample(na.omit(Framingham$BMI), 
                                                       19, 
                                                       replace = FALSE)
Framingham$heartRate[is.na(Framingham$heartRate)] = sample(na.omit(Framingham$heartRate), 
                                                       1, 
                                                       replace = FALSE)
Framingham$SmokerClass = ifelse(Framingham$cigsPerDay==0, "0none", 
                          ifelse(Framingham$cigsPerDay<= 10, "light",
                            ifelse(Framingham$cigsPerDay< 20, "moderate", "heavy")))

4.3.2 Regression Imputation

We now impute glucose using linear regression model. After trying several models including sets of variables that all include the major clinical variables currentSmoker, TenYearCHD, and diabetes. The model with three clinical variables yielded the best \(R^2\). We decide to use the following model to predict the missing glucose.

\[ \text{glucose} = \alpha_0 + \alpha_1 \text{currentSmoker} + \alpha_2 \text{TenYearCHD} + \alpha_3\text{diabetes} \]

Regression imputation is essentially a predictive modeling approach. The performance of this imputation method is heavily dependent on the strength of association between the set of auxiliary variables in the predictive model.

## linear regression imputation - glucose
## Split the data into two sets: subset with complete records for fitting 
## regression model and a data frame to predict the missing values in glucose
na.ID = which(is.na(Framingham$glucose)==TRUE)
ImputRegDat = Framingham[-na.ID,]
predData = Framingham[na.ID, c("glucose", "currentSmoker", "TenYearCHD", "diabetes")]
imput.Model = lm(glucose ~ currentSmoker + TenYearCHD + diabetes, data = ImputRegDat)
pander(summary(imput.Model)$coef)
  Estimate Std. Error t value Pr(>|t|)
(Intercept) 79.49 0.4449 178.7 0
currentSmoker -1.335 0.6061 -2.203 0.02768
TenYearCHD 4.468 0.8417 5.308 1.171e-07
diabetes 89.69 1.869 48 0

The resulting prediction model is given by

\[ \text{glucose} = 79.49 -1.335\times \text{currentSmoker} + 4.468 \times\text{TenYearCHD} + 89.69 \times\text{diabetes} \]

We next impute glucose using the above model in the following code.

imputNA = predict(imput.Model, newdata = predData)   # predicted glucose
Framingham$glucose[is.na(Framingham$glucose)] = imputNA
## Recheck the imputed data set
ImputedFramingham = Framingham
# write to the file directory for subsequent analysis 
write.csv(ImputedFramingham, "ImputedFramingham.csv")
# upload the data to the GitHub repository
ImputedFramingham  = read.csv("https://pengdsci.github.io/STA551/w08/ImputedFramingham.csv")

We check the performance of the imputation by comparing the distributions of the variables before and after the imputation.

plot(density(na.omit(ImputedFramingham$glucose)), 
     xlab="glucose level",
     col="darkred",
     lwd=2,
     main="Glucose Distributions Before and After Imputation")
lines(density(na.omit(Framingham0$glucose)), 
      col = "steelblue",
      lwd =2)
legend("topright", c("Imputed Glucose", "Original Glucose"),
       col=c("darkred", "steelblue"),
       lwd=rep(2,2),
       bty="n")

The above density curves indicate that the distributions of glucose levels before and after imputation are close to each other. With the above EDA and imputation and discretization of the


5 Statistical Prediction

This section uses the classical approach to building logistic regression models and searching for the final predictive model. The candidate models will be built based on the analytic data set ImputedFramingham.

The general model-building process involves the following three steps.

Step 1: build a small model that contains practically important variables regardless of their statistical significance. This step requires inputs from domain experts to identify these variables. For convenience, we this initial small model reduced model. These variables will be kept in the final model.

Step 2: add all variables that have potential statistical significance to the reduced model. This expanded model is called full model. We would expect that the optimal model must be between the reduced model and the full model.

Step 3: use an appropriate model performance measure to search for the best model between the reduced model and the full model. In R MASS library,

# The following reduced model includes practically significant predictor variables
reducedModel = glm(TenYearCHD ~ prevalentStroke + BMI + BPMeds + totChol, 
                   family = binomial(link = logit),
                   data = ImputedFramingham)
# Adding some potential statistically significant variables
fullModel = glm(TenYearCHD ~ prevalentStroke + BMI + BPMeds + totChol + age + 
                  currentSmoker + SmokerClass + prevalentHyp + glucose + diaBP + 
                  diabetes + male + sysBP + diaBP + heartRate, 
                   family = binomial(link = logit),
                   data = ImputedFramingham)
## Automatics variable selection procedure for searching for the best model
## for association analysis
forwards = step(reducedModel,
                scope=list(lower=formula(reducedModel),upper=formula(fullModel)), 
                direction="forward",
                trace = FALSE)

We next use ROC curves to choose the best predictive model. The R library pROC to extract information about the ROC for each of the candidate models.

## predict the "success" probabilities of each model based on the entire data set
preReduced = predict(reducedModel, newdata = ImputedFramingham,type="response" )
predfullModel = predict(fullModel, newdata = ImputedFramingham,type="response" ) 
predforwards = predict(forwards, newdata = ImputedFramingham,type="response" ) 
##
prediction.reduced = preReduced
prediction.full = predfullModel
prediction.forwards = predforwards 
  category = ImputedFramingham$TenYearCHD == 1
  ROCobj.reduced <- roc(category, prediction.reduced)
  ROCobj.full <- roc(category, prediction.full)
  ROCobj.forwards <- roc(category, prediction.forwards)
## AUC
  reducedAUC = ROCobj.reduced$auc
  fullAUC = ROCobj.full$auc
  forwardsAUC = ROCobj.forwards$auc
## extract sensitivity and specificity from candidate models
  sen.reduced = ROCobj.reduced$sensitivities
  fnr.reduced = 1 - ROCobj.reduced$specificities
  #
  sen.full = ROCobj.full$sensitivities
  fnr.full = 1 - ROCobj.full$specificities
  #
  sen.forwards = ROCobj.forwards$sensitivities
  fnr.forwards = 1 - ROCobj.forwards$specificities
## Fond contrast color for ROC curves
  colors = c("#8B4500", "#00008B", "#8B008B")
## Plotting ROC curves
#par(type="s")
plot(fnr.reduced, sen.reduced, type = "l", lwd = 2, col = colors[1],
     xlim = c(0,1),
     ylim = c(0,1),
     xlab = "1 - specificity",
     ylab = "sensitivity",
     main = "ROC Curves of Candidate Models")
lines(fnr.full, sen.full, lwd = 2, lty = 2, col = colors[2])
lines(fnr.forwards, sen.forwards, lwd = 1, col = colors[3])
segments(0,0,1,1, lwd =1, col = "red", lty = 2)
legend("topleft", c("reduced", "full", "forwards", "random guess"), 
       col=c(colors, "red"), lwd=c(2,2,1,1),
       lty=c(1,2,1,2), bty = "n", cex = 0.7)
## annotating AUC
text(0.87, 0.25, paste("AUC.reduced = ", round(reducedAUC,4)), col=colors[1], cex = 0.7, adj = 1)
text(0.87, 0.20, paste("AUC.full = ", round(fullAUC,4)), col=colors[2], cex = 0.7, adj = 1)
text(0.87, 0.15, paste("AUC.forwards = ", round(forwardsAUC,4)), col=colors[3], cex = 0.7, adj = 1)
ROC curves comparing the model performance of the three candidate models.

ROC curves comparing the model performance of the three candidate models.

The above ROCs show that the full and forwards models have similar predictive performance. Since the forwards model has fewer variables, the forwards model should be selected as the final model for implementation.

Remarks: The performance metrics used in constructing the ROC curves are based on prediction errors of the model. ROC curves and AUC are performance metrics for predictive models. The model with the biggest AUC may not be the best model for association analysis.

6 Rebranding Logistic Regression

Recall that the analytic expression of the logistic regression model has the following explicit expression.

\[ Pr(Y = 1) = \frac{\exp(w_0 + w_1 x_1 + \cdots + w_k x_k)}{1 + \exp(w_0 + w_1 x_1 + \cdots + w_k x_k)}. \]

The diagrammatic representation of the above model is given by

Figure 6. Diagram Representation of logistic regression models.

Figure 6. Diagram Representation of logistic regression models.

The above diagram of the logistic regression model is the basic single layer sigmoid neural network model - perceptron.

6.1 Single Layer Neural Network - Perceptron

The perceptron is a supervised learning binary classification algorithm, originally developed by Frank Rosenblatt in 1957. It is a type of artificial neural network. Its architecture is the same as the diagram of the logistic regression model. The more general

Figure 7.  Architecture of Single layer neural network models (perceptron).

Figure 7. Architecture of Single layer neural network models (perceptron).

Each input \(x_i\) has an associated weight \(w_i\) (like regression coefficient). The sum of all weighted inputs, \(\sum_{i=1}^n w_ix_i\) , is then passed through a nonlinear activation function \(f()\), to transform the pre-activation level of the neuron to output \(y_j\). For simplicity, the bias term is set to \(w_0\) which is equivalent to the intercept of a regression model.

To summarize, we explicitly list the major components of perceptron in the following.

  • Input Layer: The input layer consists of one or more input neurons, which receive input signals from the external world or other layers of the neural network.

  • Weights: Each input neuron is associated with a weight, which represents the strength of the connection between the input neuron and the output neuron.

  • Bias: A bias term is added to the input layer to provide the perceptron with additional flexibility in modeling complex patterns in the input data.

  • Activation Function: The activation function determines the output of the perceptron based on the weighted sum of the inputs and the bias term. Common activation functions used in perceptrons include the step function, sigmoid function, and ReLU function, etc.

  • Output: The output of the perceptron is a single binary value, either 0 or 1, which indicates the class or category to which the input data belongs.

Note that when the sigmoid (i.e., logistic) function.

\[ f(x) = \frac{\exp(x)}{1 + \exp(x)} = \frac{1}{1+\exp(-x)}. \]

is used in the perceptron, the single-layer perception with logistic activation is equivalent to the binary logistic regression.


Remarks:

  1. The output of the above perceptron network is binary, i.e., \(\hat{Y} = 0\) or \(1\) since an implicit decision boundary based on the sign of the value of the transfer function \(\sum_{i=1}^m w_ix_i + w_0\). In the sigmoid perceptron network, this is equivalent to setting the threshold probability to 0.5. To see this, not that, if \(\sum_{i=1}^m w_ix_i + w_0 = 0\), then \[ P\left[Y=1 \Bigg| \sum_{i=1}^m w_ix_i + w_0\right]=\frac{1}{1+\exp\left[-(\sum_{i=1}^m w_ix_i + w_0) \right]} = \frac{1}{1+\exp(0)} = \frac{1}{2} \]

  2. If the cut-off probability \(0.5\) is used in the logistic predictive model, this logistic predictive model is equivalent to the perceptron with sigmoid being the activation function.

  3. There are several other commonly used activation functions in perceptron. The sigmoid activation function is only one of them. This implies that the binary logistic regression model is a special perceptron network model.

6.2 Commonly Used Activation Functions

The sigmoid function is only one of the activation functions used in neural networks. The table below lists several other commonly used activation functions in neural network modeling.

Figure 9.  Popular activation functions in neural networks.

Figure 9. Popular activation functions in neural networks.

6.3 Algorithms for Estimating Weights

We know that the estimation of the regression coefficient in logistic regression is to maximize the likelihood function defined based on the binomial distribution. Algorithms such as Newton and its variants, scoring methods, etc. are used to obtain the estimated regression coefficients.

In neural network models, the weights are estimated by minimizing the loss function (also called cost function) when training neural networks. The loss function could be defined as mean square error (MSE) for regression tasks.

\[ \text{Error}(w_0, w_1, \cdots, w_k) = \frac{1}{n}\sum_{i=1}^n [\hat{y}_i - (w_0 + w_1x_{1i} + \cdots + w_k x_{ki})]^2 \]

For the binary classification task, the loss function is defined to be cross-entropy (ce) with the following explicit expression

\[ \text{Error}(w_0, w_1, \cdots, w_k) = -\frac{\sum_{i=1}^N[y_i\log(p_i) + (1-y_i)\log(1-p_i)]}{N}. \]

where

\[ p_i = \frac{\exp(w_0 + w_1x_{1i} + \cdots + w_k x_{ki})}{1 + \exp(w_0 + w_1x_{1i} + \cdots + w_k x_{ki})}. \]

Learning algorithms forward and backward propagation that depend on each other are used in minimizing the underlying loss function.

  • Forward propagation is where input data is fed through a network, in a forward direction, to generate an output. The data is accepted by hidden layers and processed, as per the activation function, and moves to the successive layer. During forward propagation, the activation function is applied, based on the weighted sum, to make the neural network flow non-linearly using bias. Forward propagation is the way data moves from left (input layer) to right (output layer) in the neural network.

  • Backpropagation is used to improve the prediction accuracy of a node is expressed as a loss function or error rate. Backpropagation calculates the slope of (gradient) a loss function of other weights in the neural network and updates the weights using gradient descent through the learning rate.

Figure 10.  Updating weights with backpropagation algorithm.

Figure 10. Updating weights with backpropagation algorithm.

The general architecture of the backpropagation network model is depicted in the following diagram.

Figure 11.  The idea of backpropagation neural networks.

Figure 11. The idea of backpropagation neural networks.

The algorithm of backpropagation is not used in classical statistics. This is why the neural network model outperformed the classical logistic model in terms of predictive power.

The R library neuralnet has the following five algorithms:

backprop - traditional backpropagation.

rprop+ - resilient backpropagation with weight backtracking.

rprop- - resilient backpropagation without weight backtracking.

sag - modified globally convergent algorithm (gr-prop) with the smallest absolute gradient.

slr - modified globally convergent algorithm (gr-prop) with the smallest learning rate.

Although it is not required, scaling can improve the performance of neural network models. There are different types of scaling and standardization. The following scaling is commonly used in practice.

\[ \text{scaled.var} = \frac{\text{orig.var} - \min(\text{orig.var})}{\max(\text{orig.var})-\min(\text{orig.var})}. \]


6.4 Implementing NN with R

Several R libraries can run neural network models. nnet is the simplest one that only implements single-layer networks. neuralnet can run both single-layer and multiple-layer neural networks. RSNNS (R Stuttgart Neural Network Simulator) is a wrapper of multiple R libraries that implements different network models.

6.4.1 Syntax of neuralnet

We use neuralnet library to run the neural network model in the example (code for installing and loading this library is placed in the setup code chunk).

The syntax of neuralnet() is given below

 neuralnet(formula,       
           data, 
           hidden = 1,    
           threshold = 0.01, 
           stepmax = 1e+05, 
           rep = 1, 
           startweights = NULL,
           learningrate.limit = NULL,
           learningrate.factor =list(minus = 0.5, plus = 1.2),
           learningrate=NULL, 
           lifesign = "none",
           lifesign.step = 1000, 
           algorithm = "rprop+",
           err.fct = "sse", 
           act.fct = "logistic",
           linear.output = TRUE, 
           exclude = NULL,
           constant.weights = NULL, 
           likelihood = FALSE)

The detailed help document can be found at https://www.rdocumentation.org/packages/neuralnet/versions/1.44.2/topics/neuralnet.

The function is fairly flexible and allows different loss functions, methods of estimation, and different types of outputs. The authors also required scaled features and the explicit definition for dummy variables derived from underlying categorical feature variables.


6.4.2 Feature Conversion and Model Formula

When using neuralnet(), we should keep the following in mind when preparing data for the algorithm.

  • neuralnet() requires all features to be in the numeric form (dummy variable for categorical features, normalization of numerical features).

  • The model formula in neuralnet() requires dummy variables to be explicitly defined.

  • It is also highly recommended to scale all numerical features before being included in the network model.

  • Extract all feature names (numeric and all dummy variables) and write them in the model formula like the one in glm: response ~ var.1 + var.2 + ... +var.k,


7 A Case Study

For illustration, we will use the best model selected from the previous section to build a single-layer neural network model (perceptron) using the Framingham CHD data. Since the original data set has been feature engineered, we will illustrate the steps for preparing the data for the neuralnet() function based on the engineered data set.

7.1 Subsetting and Scaling Data

We first subset the data with only variables used in the logistic regression model (the full model) and then scale all numerical variables. The above suggested min-max scaling method will be used in this case study (which is reflected in the following code).

fullModelNames=c("prevalentStroke", "BMI","BPMeds","totChol","age", "currentSmoker","SmokerClass","prevalentHyp","glucose","diaBP","diabetes", "male","sysBP","heartRate", "TenYearCHD")
neuralData = ImputedFramingham[, fullModelNames]
## feature scaling
neuralData$BMIscale = (neuralData$BMI-min(neuralData$BMI))/(max(neuralData$BMI) - min(neuralData$BMI))
neuralData$totCholscale = (neuralData$totChol-min(neuralData$totChol))/(max(neuralData$totChol) - min(neuralData$totChol))
neuralData$agescale = (neuralData$age-min(neuralData$age))/(max(neuralData$age) - min(neuralData$age)) 
neuralData$glucosescale = (neuralData$glucose-min(neuralData$glucose))/(max(neuralData$glucose) - min(neuralData$glucose))                                                                        
neuralData$diaBPcale = (neuralData$diaBP-min(neuralData$diaBP))/(max(neuralData$diaBP) - min(neuralData$diaBP))  
neuralData$sysBPscale = (neuralData$sysBP-min(neuralData$sysBP))/(max(neuralData$sysBP) - min(neuralData$sysBP))  
neuralData$heartRatescale = (neuralData$heartRate-min(neuralData$heartRate))/(max(neuralData$heartRate) - min(neuralData$heartRate))  
## drop original feature - keeping only features to be used in the neural network
ANNModelNames=c("prevalentStroke", "BPMeds", "currentSmoker","SmokerClass", "prevalentHyp", "diabetes","male","BMIscale","totCholscale","agescale","glucosescale", "diaBPcale","sysBPscale", "heartRatescale","TenYearCHD")
## final data for the neuralnet() function
neuralDataFinal = neuralData[,ANNModelNames]

7.2 Creating Model Formula

Instead of writing the formula explicitly, we use the R function model.matrix to create the model formula for neuralnet(). This method allows us to generalize the cases with many variables in which the explicit expression is not practically feasible. For illustration, we will define the formula implicitly and explicitly.


7.2.1 Implicitly Definition

The following code explicitly defines the dummy variables to be used in the final model formula.

neuralModelFormula = model.matrix(~prevalentStroke+ BPMeds+ currentSmoker+SmokerClass+ prevalentHyp+ diabetes+male+ BMIscale+totCholscale+agescale+glucosescale+diaBPcale+sysBPscale + heartRatescale, data = neuralDataFinal)
# The following will list all numerical variables and automatically derived dummy variables
colnames(neuralModelFormula) 
neuralModelDesignMatrix = model.matrix(~prevalentStroke+ BPMeds+ currentSmoker+SmokerClass+ prevalentHyp+ diabetes+male+ BMIscale+totCholscale+agescale+glucosescale+diaBPcale+sysBPscale + heartRatescale + TenYearCHD, data = neuralDataFinal)
# The following will list all numerical variables and automatically derived dummy variables
colnames(neuralModelDesignMatrix) 
 [1] "(Intercept)"         "prevalentStroke"     "BPMeds"             
 [4] "currentSmoker"       "SmokerClassheavy"    "SmokerClasslight"   
 [7] "SmokerClassmoderate" "prevalentHyp"        "diabetes"           
[10] "male"                "BMIscale"            "totCholscale"       
[13] "agescale"            "glucosescale"        "diaBPcale"          
[16] "sysBPscale"          "heartRatescale"      "TenYearCHD"         

Dummy variables SmokerClasslight, SmokerClassmoderate, and SmokerClassnone dummy variables are defined based on the categorical variable "SmokerClass. The object in the above code defines the design matrix which will be used in the underlying model.

7.2.2 Implicit Definition

To use the implicit method, we need to create a data frame that contains the feature variables to be included in the neural network model. Since the neuralData data set contains all feature variables and the response variable, we need to drop the response variable and then use the short-cut implicit method.

implicitFormula = model.matrix( ~., data = neuralDataFinal)
colnames(implicitFormula) 
implicitFormulaDesignMatrix = model.matrix( ~., data = neuralDataFinal)
colnames(implicitFormulaDesignMatrix) 
 [1] "(Intercept)"         "prevalentStroke"     "BPMeds"             
 [4] "currentSmoker"       "SmokerClassheavy"    "SmokerClasslight"   
 [7] "SmokerClassmoderate" "prevalentHyp"        "diabetes"           
[10] "male"                "BMIscale"            "totCholscale"       
[13] "agescale"            "glucosescale"        "diaBPcale"          
[16] "sysBPscale"          "heartRatescale"      "TenYearCHD"         

The above code produces the same set of features including automatically defined dummy variables.

7.2.3 Ensemble Model Formula

After defining the design matrix with explicitly defined dummy variables based on the corresponding categorical variables, we use the string function to define the model formula. Since both implicit and explicit methods are equivalent, we use the list of variables to define the final model formula.

Note that the first column of the model matrix corresponds to the intercept (bias in neural network algorithm) and the last column is the response variable. We eventually want to create a model formula in the form similar to response ~ var.1 + var.2 + ... + var.m.

paste() is a powerful string function. We will use it to define the model formula using the following code.

columnNames = colnames(implicitFormulaDesignMatrix)
columnList = paste(columnNames[-c(1,length(columnNames))], collapse = "+")
columnList = paste(c(columnNames[length(columnNames)],"~",columnList), collapse="")
modelFormula = formula(columnList)
modelFormula
columnNames = colnames(implicitFormulaDesignMatrix)
columnList = paste(columnNames[-c(1,length(columnNames))], collapse = "+")
columnList = paste(c(columnNames[length(columnNames)],"~",columnList), collapse="")
modelFormula = formula(columnList)
modelFormula
TenYearCHD ~ prevalentStroke + BPMeds + currentSmoker + SmokerClassheavy + 
    SmokerClasslight + SmokerClassmoderate + prevalentHyp + diabetes + 
    male + BMIscale + totCholscale + agescale + glucosescale + 
    diaBPcale + sysBPscale + heartRatescale

The above formula will used in neuralnet().


7.2.4 Building Perceptron Model

The neuralnet() function provides many arguments (also called **hyperparameters). In this class, we will not focus on tuning these hyperparameters to find a model with an optimal performance. The objective is to gain a basic knowledge of the architecture of neural network models. We will use the default arguments provided in the function.

Recall that the perceptron model be used for both regression and classification. The argument linear.output needs to be specified correctly to perform the appropriate modeling.

  • When performing regression modeling with continuous response, the argument linear.output should be set to TRUE.

  • When performing classification modeling with a categorical response, the argument linear.output should be set to FALSE.

NetworkModel = neuralnet(modelFormula,
                         data = implicitFormulaDesignMatrix,  # must be the design matrix
                         hidden = 1,
                         act.fct = "logistic",     # sigmoid activation function
                         linear.output = FALSE 
                         )
kable(NetworkModel$result.matrix)
error 243.6372827
reached.threshold 0.0086654
steps 28378.0000000
Intercept.to.1layhid1 2.0397519
prevalentStroke.to.1layhid1 -0.3545044
BPMeds.to.1layhid1 -0.0342340
currentSmoker.to.1layhid1 0.5730107
SmokerClassheavy.to.1layhid1 -0.7840597
SmokerClasslight.to.1layhid1 -0.5032639
SmokerClassmoderate.to.1layhid1 -0.7777946
prevalentHyp.to.1layhid1 -0.1173958
diabetes.to.1layhid1 0.0494690
male.to.1layhid1 -0.2401516
BMIscale.to.1layhid1 0.0087685
totCholscale.to.1layhid1 -0.4392284
agescale.to.1layhid1 -0.8965684
glucosescale.to.1layhid1 -1.3780420
diaBPcale.to.1layhid1 0.2418179
sysBPscale.to.1layhid1 -1.3853478
heartRatescale.to.1layhid1 0.0915862
Intercept.to.TenYearCHD 5.9603163
1layhid1.to.TenYearCHD -10.9841215

The above table lists the estimated weights in the perceptron model. Next, we create a visual representation of the perceptron model. Instead of using the default plot.nn(), we use a wrapper of a plot function to create the following nice-looking figure (see the link to the source code).

plot(NetworkModel, rep="best")
Figure 12. Single-layer backpropagation Neural network model for Pima Indian diabetes

Figure 12. Single-layer backpropagation Neural network model for Pima Indian diabetes

7.3 Prediction and ROC Analysis

The neuralnet library has the generic R function predict() to make a prediction using the perceptron model object and a set of new data (in an R data frame). Depending on how ROC curves are used, they could be constructed on either training, testing, or the entire data. To keep consistency, we will construct the ROC of the perceptron model using the entire data set so we can fairly compare the ROC curves and the corresponding AUCs among the three logistic prediction models and the perceptron model.

predNN = predict(NetworkModel, newdata = implicitFormulaDesignMatrix, linear.output = FALSE)
preReduced = predict(reducedModel, newdata = ImputedFramingham,type="response" )
predfullModel = predict(fullModel, newdata = ImputedFramingham,type="response" ) 
predforwards = predict(forwards, newdata = ImputedFramingham,type="response" ) 
##
##
prediction.reduced = preReduced
prediction.full = predfullModel
prediction.forwards = predforwards 
  category = ImputedFramingham$TenYearCHD == 1
  ROCobj.reduced <- roc(category, prediction.reduced)
  ROCobj.full <- roc(category, prediction.full)
  ROCobj.forwards <- roc(category, prediction.forwards)
  ROCobj.NN <-roc(category, predNN)
## AUC
  reducedAUC = ROCobj.reduced$auc
  fullAUC = ROCobj.full$auc
  forwardsAUC = ROCobj.forwards$auc
  NNAUC = ROCobj.NN$auc
## extract sensitivity and specificity from candidate models
  sen.reduced = ROCobj.reduced$sensitivities
  fnr.reduced = 1 - ROCobj.reduced$specificities
  #
  sen.full = ROCobj.full$sensitivities
  fnr.full = 1 - ROCobj.full$specificities
  #
  sen.forwards = ROCobj.forwards$sensitivities
  fnr.forwards = 1 - ROCobj.forwards$specificities
  #
  sen.NN = ROCobj.NN$sensitivities
  fnr.NN = 1 - ROCobj.NN$specificities
  
## Fond contrast color for ROC curves
  colors = c("#8B4500", "#00008B", "#8B008B", "#055d03")
## Plotting ROC curves
#par(type="s")
plot(fnr.reduced, sen.reduced, type = "l", lwd = 2, col = colors[1],
     xlim = c(0,1),
     ylim = c(0,1),
     xlab = "1 - specificity",
     ylab = "sensitivity",
     main = "ROC Curves of Candidate Models")
lines(fnr.full, sen.full, lwd = 2, lty = 2, col = colors[2])
lines(fnr.forwards, sen.forwards, lwd = 1, col = colors[3])
lines(fnr.NN, sen.NN, lwd = 1, col = colors[4])

segments(0,0,1,1, lwd =1, col = "red", lty = 2)
legend("topleft", c("reduced", "full", "forwards", "NN", "random guess"), 
       col=c(colors, "red"), lwd=c(2,2,1,1,1),
       lty=c(1,2,1,1,2), bty = "n", cex = 0.7)
## annotating AUC
text(0.87, 0.25, paste("AUC.reduced = ", round(reducedAUC,4)), col=colors[1], cex = 0.7, adj = 1)
text(0.87, 0.20, paste("AUC.full = ", round(fullAUC,4)), col=colors[2], cex = 0.7, adj = 1)
text(0.87, 0.15, paste("AUC.forwards = ", round(forwardsAUC,4)), col=colors[3], cex = 0.7, adj = 1)
text(0.87, 0.10, paste("AUC.NN = ", round(NNAUC,4)), col=colors[4], cex = 0.7, adj = 1)
ROC curves comparing the model performance of the three logistic models and a single-layer neural network.

ROC curves comparing the model performance of the three logistic models and a single-layer neural network.

As anticipated, the overall performance of the perceptron model and the full and reduced models are similar to each other because we used the sigmoid activation function in the perceptron model.

7.4 Cross-validation in Neural Network

The algorithm of Cross-validation is primarily used for tuning hyper-parameters. For example, in the sigmoid perceptron, the optimal cut-off scores for the binary decision can be obtained through cross-validation. One of the important hyperparameters in the neural network model is the learning rate \(\alpha\) (in the backpropagation algorithm) that impacts the learning speed in training neural network models.

8 About Deep Learning

From Wikipedia, the free encyclopedia

Deep learning is part of a broader family of machine learning methods, which is based on artificial neural networks with representation learning. The adjective “deep” in deep learning refers to the use of multiple layers in the network. Methods used can be either supervised, semi-supervised, or unsupervised.

Deep-learning architectures such as deep neural networks, deep belief networks, deep reinforcement learning, recurrent neural networks, convolutional neural networks, and transformers have been applied to fields including computer vision, speech recognition, natural language processing, machine translation, bioinformatics, drug design, medical image analysis, climate science, material inspection and board game programs, where they have produced results comparable to and in some cases surpassing human expert performance.

8.1 Multi-layer Perceptron

A Multi-Layer Perceptron (MLP) contains one or more hidden layers (apart from one input and one output layer). While a single-layer perceptron can only learn linear functions, a multi-layer perceptron can also learn non-linear functions. The following is an illustrative MLP.

Figure 8.  Multi-layer perceptron.

Figure 8. Multi-layer perceptron.

The major components in the above MLP are described in the following.

Input Layer: The Input layer has three nodes. The Bias node has a value of 1. The other two nodes take \(X_1\) and \(X_2\) as external inputs (which are numerical values depending upon the input data set). No computation is performed in the Input layer, so the outputs from nodes in the Input layer are 1, \(X_1\), and \(X_2\) respectively, which are fed into the Hidden Layer.

Hidden Layer: The Hidden layer also has three nodes with the Bias node having an output of 1. The output of the other two nodes in the Hidden layer depends on the outputs from the Input layer (1, \(X_1\), \(X_2\)) as well as the weights associated with the connections (edges). Figure 16 shows the output calculations for the hidden nodes. Remember that \(f()\) refers to the activation function. These outputs are then fed to the nodes in the Output layer.

Output Layer: The Output layer has two nodes that take inputs from the Hidden layer and perform similar computations as shown in the above figure. The values calculated (\(Y_1\) and \(Y_2\)) as a result of these computations act as outputs of the Multi-Layer Perceptron.


LS0tDQp0aXRsZTogIkZyb20gU3RhdGlzdGljYWwgTW9kZWxzIHRvIE1hY2hpbmUgTGVhcm5pbmcgQWxnb3JpdGhtcyINCmF1dGhvcjogIkNoZW5nIFBlbmciDQpkYXRlOiAiIFNUQTUxMSBGb3VuZGF0aW9ucyBvZiBEYXRhIFNjaWVuY2UiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfd2lkdGg6IDYNCiAgICBmaWdfaGVpZ2h0OiA0DQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jX2NvbGxhcHNlZDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogeWVzDQogICAgdGhlbWU6IGx1bWVuDQogIHdvcmRfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIGtlZXBfbWQ6IHllcw0KICBwZGZfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgZmlnX3dpZHRoOiA1DQogICAgZmlnX2hlaWdodDogNA0KLS0tDQoNCmBgYHs9aHRtbH0NCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoNCmRpdiNUT0MgbGkgew0KICAgIGxpc3Qtc3R5bGU6bm9uZTsNCiAgICBiYWNrZ3JvdW5kLWltYWdlOm5vbmU7DQogICAgYmFja2dyb3VuZC1yZXBlYXQ6bm9uZTsNCiAgICBiYWNrZ3JvdW5kLXBvc2l0aW9uOjA7DQp9DQoNCmgxLnRpdGxlIHsNCiAgZm9udC1zaXplOiAyMHB4Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgY29sb3I6IERhcmtSZWQ7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCmg0LmF1dGhvciB7IC8qIEhlYWRlciA0IC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOw0KICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgY29sb3I6IERhcmtSZWQ7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCmg0LmRhdGUgeyAvKiBIZWFkZXIgNCAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgY29sb3I6IERhcmtCbHVlOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoMSB7IC8qIEhlYWRlciAzIC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogICAgZm9udC1zaXplOiAyMnB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOw0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KICAgIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCmgyIHsgLyogSGVhZGVyIDMgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDMgeyAvKiBIZWFkZXIgMyAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMTZweDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoNCB7IC8qIEhlYWRlciA0IC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogICAgZm9udC1zaXplOiAxNHB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOw0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQo8L3N0eWxlPg0KYGBgDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMgY29kZSBjaHVuayBzcGVjaWZpZXMgd2hldGhlciB0aGUgUiBjb2RlLCB3YXJuaW5ncywgYW5kIG91dHB1dCANCiMgd2lsbCBiZSBpbmNsdWRlZCBpbiB0aGUgb3V0cHV0IGZpbGVzLg0KaWYgKCFyZXF1aXJlKCJnZ3Bsb3QyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KICAgbGlicmFyeShnZ3Bsb3QyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJrbml0ciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJrbml0ciIpDQogICBsaWJyYXJ5KGtuaXRyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJJU0xSIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIklTTFIiKQ0KICAgbGlicmFyeShJU0xSKQ0KfQ0KaWYgKCFyZXF1aXJlKCJuZXVyYWxuZXQiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygibmV1cmFsbmV0IikNCiAgIGxpYnJhcnkobmV1cmFsbmV0KQ0KfQ0KaWYgKCFyZXF1aXJlKCJjYXJldCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJjYXJldCIpDQogICBsaWJyYXJ5KGNhcmV0KQ0KfQ0KaWYgKCFyZXF1aXJlKCJubmV0IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIm5uZXQiKQ0KICAgbGlicmFyeShubmV0KQ0KfQ0KaWYgKCFyZXF1aXJlKCJoYXZlbiIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJoYXZlbiIpDQogICBsaWJyYXJ5KGhhdmVuKQ0KfQ0KaWYgKCFyZXF1aXJlKCJwYW5kZXIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicGFuZGVyIikNCiAgIGxpYnJhcnkocGFuZGVyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJwUk9DIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInBST0MiKQ0KICAgbGlicmFyeShwUk9DKQ0KfQ0KaWYgKCFyZXF1aXJlKCJncmlkRXh0cmEiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZ3JpZEV4dHJhIikNCiAgIGxpYnJhcnkoZ3JpZEV4dHJhKQ0KfQ0KaWYgKCFyZXF1aXJlKCJnZ3BhcmFsbGVsIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImdncGFyYWxsZWwiKQ0KICAgbGlicmFyeShnZ3BhcmFsbGVsKQ0KfQ0KIyBUaGUgZm9sbG93aW5nIFIgc291cmNlIGNvZGUgd2lsbCBiZSB1c2UgdG8gcGxvdCB0aGUgcGF0aCBwbG90DQojIG9mIG5ldXJhbCBuZXR3b3JrIG1vZGVsIHdpdGggZXN0aW1hdGVkIHdlaWdodHMgZnJvbSB0aGUgZGF0YS4NCnNvdXJjZSgiaHR0cHM6Ly9wZW5nZHNjaS5naXRodWIuaW8vU1RBNTUxL3Bsb3ROZXVyYWxuZXRSZnVuLnR4dCIpDQojIGtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gIkM6XFxTVEE1NTFcXHcwOCIpDQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCAgIA0KICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdHMgPSBUUlVFLCAgIA0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBjb21tZW50PSBOQSkNCmBgYA0KDQoNClwNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KVGhlcmUgYXJlIGEgbG90IG9mIGRlYmF0ZXMgb24gdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBzdGF0aXN0aWNzIGFuZCBtYWNoaW5lIGxlYXJuaW5nIGluIHN0YXRpc3RpY3MgYW5kIG1hY2hpbmUgbGVhcm5pbmcgY29tbXVuaXRpZXMuIENlcnRhaW5seSwgc3RhdGlzdGljcyBhbmQgbWFjaGluZSBsZWFybmluZyBhcmUgbm90IHRoZSBzYW1lIGFsdGhvdWdoIHRoZXJlIGlzIGFuIG92ZXJsYXAuIEEgbWFqb3IgZGlmZmVyZW5jZSBiZXR3ZWVuIG1hY2hpbmUgbGVhcm5pbmcgYW5kIHN0YXRpc3RpY3MgaXMgaW5kZWVkIHRoZWlyIHB1cnBvc2UuDQoNCi0gICBTdGF0aXN0aWNzIGZvY3VzZXMgb24gdGhlIGluZmVyZW5jZSBhbmQgaW50ZXJwcmV0YWJpbGl0eSBvZiB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHZhcmlhYmxlcy4NCg0KLSAgIE1hY2hpbmUgbGVhcm5pbmcgZm9jdXNlcyBvbiB0aGUgYWNjdXJhY3kgb2YgdGhlIHByZWRpY3Rpb24gb2YgZnV0dXJlIHZhbHVlcyBvZiAocmVzcG9uc2UpIHZhcmlhYmxlcyBhbmQgZGV0ZWN0aW5nIGhpZGRlbiBwYXR0ZXJucy4gTWFjaGluZSBsZWFybmluZyBpcyB0cmFkaXRpb25hbGx5IGNvbnNpZGVyZWQgdG8gYmUgYSBzdWJmaWVsZCBvZiBhcnRpZmljaWFsIGludGVsbGlnZW5jZSwgd2hpY2ggaXMgYnJvYWRseSBkZWZpbmVkIGFzIHRoZSBjYXBhYmlsaXR5IG9mIGEgbWFjaGluZSB0byBpbWl0YXRlIGludGVsbGlnZW50IGh1bWFuIGJlaGF2aW9yLg0KDQpBIGxvdCBvZiBzdGF0aXN0aWNhbCBtb2RlbHMgY2FuIG1ha2UgcHJlZGljdGlvbnMsIGJ1dCBwcmVkaWN0aXZlIGFjY3VyYWN5IGlzIG5vdCB0aGVpciBzdHJlbmd0aCB3aGlsZSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyBwcm92aWRlIHZhcmlvdXMgZGVncmVlcyBvZiBpbnRlcnByZXRhYmlsaXR5IHNhY3JpZmljZSBpbnRlcnByZXRhYmlsaXR5IGZvciBwcmVkaWN0aXZlIHBvd2VyLiBGb3IgZXhhbXBsZSwgcmVndWxhcml6ZWQgcmVncmVzc2lvbnMgYXMgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIGFyZSBpbnRlcnByZXRhYmxlIGJ1dCBuZXVyYWwgbmV0d29ya3MgKHBhcnRpY3VsYXJseSBtdWx0aS1sYXllciBuZXR3b3JrcyApIGFyZSBhbG1vc3QgdW5pbnRlcnByZXRhYmxlLg0KDQpTdGF0aXN0aWNzIGFuZCBtYWNoaW5lIGxlYXJuaW5nIGFyZSB0d28gb2YgdGhlIGtleSBwbGF5ZXJzIGluIGRhdGEgc2NpZW5jZS4gQXMgZGF0YSBzY2llbmNlIHByYWN0aXRpb25lcnMsIG91ciBwcmltYXJ5IGludGVyZXN0IGlzIHRvIGRldmVsb3AgYW5kIHNlbGVjdCB0aGUgcmlnaHQgdG9vbHMgdG8gYnVpbGQgZGF0YSBzb2x1dGlvbnMgZm9yIHJlYWwtd29ybGQgYXBwbGljYXRpb25zLg0KDQpcDQoNCiMgU29tZSBNYWNoaW5lIExlYXJuaW5nIEphcmdvbg0KDQpCZWZvcmUgZGVtb25zdHJhdGluZyBob3cgc29tZSBjbGFzc2ljYWwgbW9kZWxzIGFyZSB1c2VkIGFzIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcywgd2UgZmlyc3QgaW50cm9kdWNlIGEgcGFydGlhbCBsaXN0IG9mIG1hY2hpbmUgbGVhcm5pbmcgamFyZ29uLiBUaGlzIHRlcm1zDQoNCnwgU3RhdGlzdGljcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IE1hY2hpbmUgTGVhcm5pbmcgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBDb21tZW50cyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfDotLS0tLS0tLS0tLS0tLS0tLXw6LS0tLS0tLS0tLS0tLS0tLS0tLS0tfDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCBkYXRhIHBvaW50LCByZWNvcmQsIHJvdyBvZiBkYXRhICAgICAgICAgICAgICAgICAgICAgIHwgZXhhbXBsZSwgaW5zdGFuY2UgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IEJvdGggZG9tYWlucyBhbHNvIHVzZSAib2JzZXJ2YXRpb24sIiB3aGljaCBjYW4gcmVmZXIgdG8gYSBzaW5nbGUgbWVhc3VyZW1lbnQgb3IgYW4gZW50aXJlIHZlY3RvciBvZiBhdHRyaWJ1dGVzIGRlcGVuZGluZyBvbiBjb250ZXh0LiAgICAgICAgICAgICAgICB8DQp8IHJlc3BvbnNlIHZhcmlhYmxlLCBkZXBlbmRlbnQgdmFyaWFibGUgICAgICAgICAgICAgICAgfCBsYWJlbCwgb3V0cHV0ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgQm90aCBkb21haW5zIGFsc28gdXNlICJ0YXJnZXQuIiBTaW5jZSBwcmFjdGljYWxseSBhbGwgdmFyaWFibGVzIGRlcGVuZCBvbiBvdGhlciB2YXJpYWJsZXMsIHRoZSB0ZXJtICJkZXBlbmRlbnQgdmFyaWFibGUiIGlzIHBvdGVudGlhbGx5IG1pc2xlYWRpbmcuIHwNCnwgcmVncmVzc2lvbnMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IHN1cGVydmlzZWQgbGVhcm5lcnMsIG1hY2hpbmVzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBCb3RoIGVzdGltYXRlIG91dHB1dChzKSBpbiB0ZXJtcyBvZiBpbnB1dChzKS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCByZWdyZXNzaW9uIGludGVyY2VwdCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgYmlhcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IHRoZSBkZWZhdWx0IHByZWRpY3Rpb24gb2YgYSBsaW5lYXIgbW9kZWwgaW4gdGhlIHNwZWNpYWwgY2FzZSB3aGVyZSBhbGwgaW5wdXRzIGFyZSAwLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IE1heGltaXplIHRoZSBsaWtlbGlob29kIG9mIGVzdGltYXRpbmcgbW9kZWwgcGFyYW1ldGVycyB8IE1pbmltaXplIHRoZSBlbnRyb3B5IHRvIGRlcml2ZSB0aGUgYmVzdCBwYXJhbWV0ZXJzIGluIGNhdGVnb3JpY2FsIHJlZ3Jlc3Npb24gb3IgbWF4aW1pemUgdGhlIGxpa2VsaWhvb2QgZm9yIGNvbnRpbnVvdXMgcmVncmVzc2lvbi4gfCBGb3IgZGlzY3JldGUgZGlzdHJpYnV0aW9ucywgbWF4aW1pemluZyB0aGUgbGlrZWxpaG9vZCBpcyBlcXVpdmFsZW50IHRvIG1pbmltaXppbmcgdGhlIGVudHJvcHkuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCBsb2dpc3RpYy9tdWx0aW5vbWlhbCByZWdyZXNzaW9uICAgICAgICAgICAgICAgICAgICAgIHwgbWF4aW11bSBlbnRyb3B5LCBNYXhFbnQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IFRoZXkgYXJlIGVxdWl2YWxlbnQgZXhjZXB0IGluIHNwZWNpYWwgbXVsdGlub21pYWwgc2V0dGluZ3MgbGlrZSBvcmRpbmFsIGxvZ2lzdGljIHJlZ3Jlc3Npb24uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQoNClwNCg0KIyBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIFJldmlzaXRlZA0KDQpSZWNhbGwgdGhhdCB0aGUgYmluYXJ5IGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCAkayQgZmVhdHVyZSB2YXJpYWJsZXMgJHhfMSwgeF8yLCBcY2RvdHMsIHhfayQgaXMgZ2l2ZW4gYnkNCg0KJCQNClBbWSA9IDEgXGJpZ3wgKHhfMSwgeF8yLCBcY2RvdHMsIHhfayldID0gXGZyYWN7XGV4cCh3XzAgKyB3XzF4XzEgKyB3XzJ4XzIgKyBcY2RvdHMgKyB3X2t4X2spfXsxK1xleHAod18wICsgd18xeF8xICsgd18yeF8yICsgXGNkb3RzICsgd19reF9rKX0gDQokJA0KDQokJA0KPSBcZnJhY3tcZXhwKHdfMCtcc3VtX3tpPTF9Xmsgd19peF9pKX17MSArIFxleHAod18wK1xzdW1fe2k9MX1eayB3X2l4X2kpfSA9IFxmcmFjezF9ezErIFxleHBbLSh3XzArXHN1bV97aT0xfV5rIHdfaXhfaSldfQ0KJCQNCg0Kd2hlcmUgJHdfMCwgd18xLCBcY2RvdHMsIHdfbiQgYXJlIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzLiBMZXQNCg0KJCQNClxwaSh4XzEseF8yLCBcY2RvdHMseF9rKSA9IFBbWT0xXGJpZ3wgKHhfMSx4XzIsXGNkb3RzLHhfayldLg0KJCQNCg0KYmUgdGhlIHByb2JhYmlsaXR5IG9mIGBzdWNjZXNzYCBnaXZlbiB0aGUgY292YXJpYXRlIHBhdHRlcm4gJCh4XzEsIHhfMiwgXGNkb3RzLCB4X2spJC4gV2UgY2FuIHJlLWV4cHJlc3MgdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgaW4gdGhlIGZvbGxvd2luZyBmb3JtDQoNCiQkDQpcbG9nIFxsZWZ0KCBcZnJhY3tccGkoeF8xLHhfMiwgXGNkb3RzLHhfayl9ezEgKyBccGkoeF8xLHhfMiwgXGNkb3RzLHhfayl9XHJpZ2h0KSA9IHdfMCtcc3VtX3tpPTF9Xmsgd19peF9pDQokJA0KDQp3aGVyZQ0KDQokJA0KXGZyYWN7XHBpKHhfMSx4XzIsIFxjZG90cyx4X2spfXsxICsgXHBpKHhfMSx4XzIsIFxjZG90cyx4X2spfSA9IFx0ZXh0e29kZHMgb2Ygc3VjY2VzcyBmb3IgZ2l2ZW4gfSAoeF8xLCB4XzIsIFxjZG90cywgeF9rKS4NCiQkDQoNClRoZXJlZm9yZSwgdGhlIGdlbmVyYWwgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBpcyBhbHNvIGNhbGxlZCAqKmxvZyBvZGRzIHJlZ3Jlc3Npb24qKi4gVGhpcyBhbHNvIG1ha2VzIGxvZ2lzdGljIHJlZ3Jlc3Npb24gaW50ZXJwcmV0YWJsZSBzaW5jZSB0aGUgcmVncmVzc2lvbiBjb2VmZmljaWVudCAkd19pJCBpcyB0aGUgY2hhbmdlIG9mIGxvZyBvZGRzIG9mIGBzdWNjZXNzYCB3aGVuIHRoZSBjb3ZhcmlhdGUgJHhfaSQgaW5jcmVhc2VzIGJ5IGEgdW5pdCBhbmQgYWxsIG90aGVyIGNvdmFyaWF0ZXMgcmVtYWluIHVuY2hhbmdlZCAoZm9yICRpID0gMSwgMiwgXGNkb3RzLCBrJCkuDQoNCkxldCAkeiA9IHdfMCtcc3VtX3tpPTF9Xmsgd19peF9pJCBiZSB0aGUgbGluZWFyIGNvbWJpbmF0aW9uIG9mIHByZWRpY3RvcnMsIHRoYXQgaW1wYWN0IHRoZSBgc3VjY2Vzc2AgcHJvYmFiaWxpdHksIHRoZW4NCg0KJCQNClxwaSh6KSA9IFxmcmFjezF9ezErIFxleHAoLXopfSBcIFwgXHRleHR7IGZvciB9IFwgXCAtXGluZnR5IDwgeiA8IFxpbmZ0eS4NCiQkDQoNCk5vdGUgdGhhdCAkXHBpKHopJCBpcyB0aGUgd2VsbC1rbm93biAqKmxvZ2lzdGljIGZ1bmN0aW9uKiouIFRoZSBjdXJ2ZSBvZiB0aGUgbG9naXN0aWMgZnVuY3Rpb24gaXMgZ2l2ZW4gYnkNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuY2FwPSJGaWd1cmUgNS4gIFRoZSBjdXJ2ZSBvZiB0aGUgbG9naXN0aWMgZnVuY3Rpb24uIn0NCnggPSBzZXEoLTUsIDUsIGxlbmd0aCA9IDEwMCkNCnkgPSAxLygxK2V4cCgteCkpDQpwbG90KHgseSwgdHlwZSA9ICJsIiwgbHdkID0gMiwgeGxhYiA9InogIiwgeWxhYiA9IGV4cHJlc3Npb24ocGkoeikpLCANCiAgICAgbWFpbiA9ICJMb2dpc3RpYyBDdXJ2ZSIsIGNvbCA9ICJibHVlIikNCnRleHQoLTMsIDAuOCwgZXhwcmVzc2lvbihwaSh6KSA9PWZyYWMoMSwxK2V4cCgteikpKSwgY29sID0gImJsdWUiKQ0KYGBgDQoNClRoZSBtYWluIGZlYXR1cmUgb2YgbG9naXN0aWMgZnVuY3Rpb24gJGYoeikkIGlzIGl0cyBTLXNoYXBlIGN1cnZlIHdpdGggYSByYW5nZSAkZih6KSBcaW4gWzAsMV0kLCBkb21haW4gJHogXGluICgtXGluZnR5LCBcaW5mdHkpJCwgYW5kICRmKHopID0gMS8yJC4gT3RoZXIgZnVuY3Rpb25zIGhhdmUgdGhlIHNhbWUgcHJvcGVydGllcyBhcyBsb2dpc3RpYyBmdW5jdGlvbnMuIFRoZXNlIHR5cGVzIG9mIGZ1bmN0aW9ucyBhcmUgY2FsbGVkICoqc2lnbW9pZCoqIGZ1bmN0aW9ucy4NCg0KTm90ZSB0aGF0IHRoZSBpbnZlcnNlIG9mIHRoZSBsb2dpc3RpYyBmdW5jdGlvbiBpcyBjYWxsZWQgdGhlICoqbG9naXQgZnVuY3Rpb24qKiB0aGF0IGlzIGdpdmVuIGJ5DQoNCiQkDQpmXnstMX0oeCkgPSBcbG9nIFxsZWZ0KCBcZnJhY3t4fXsxLXh9XHJpZ2h0KS4NCiQkDQoNClRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIGlzIGFsc28gY2FsbGVkICoqbG9naXQqKiByZWdyZXNzaW9uLiBJdCBpcyBhbHNvIGNhbGxlZCAqKmxvZyBvZGRzIHJlZ3Jlc3Npb24qKiBiZWNhdXNlICRQKFk9XHRleHR7c3VjY2Vzc30pL1sxLVAoWT1cdGV4dHtzdWNjZXNzfSldJCBpcyB0aGUgb2RkcyBvZiBzdWNjZXNzLg0KDQojIFdvcmtpbmcgRGF0YSBTZXQgYW5kIEVEQQ0KDQpUaGlzIHNlY3Rpb24gaW50cm9kdWNlcyB0aGUgZGF0YSBzZXQgYW5kIHByZXBhcmVzIGFuIGFuYWx5dGljIGRhdGEgc2V0IHRvIHBlcmZvcm0gcHJlZGljdGl2ZSBtb2RlbGluZyB1c2luZyBib3RoIGNsYXNzaWNhbCBzdGF0aXN0aWNhbCBtZXRob2RzIGFuZCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMuDQoNCiMjIEZyYW1pbmdoYW0gU3R1ZHkgRGF0YQ0KDQpUaGUgRnJhbWluZ2hhbSBEYXRhIGlzIGNvbGxlY3RlZCBmcm9tIGFuIG9uZ29pbmcgY2FyZGlvdmFzY3VsYXIgc3R1ZHkgb24gcmVzaWRlbnRzIG9mIHRoZSB0b3duIG9mIEZyYW1pbmdoYW0sIE1hc3NhY2h1c2V0dHMuIFRoZSBnb2FsIG9mIHRoaXMgYXBwbGljYXRpb24gaXMgdG8gcHJlZGljdCB3aGV0aGVyIHRoZSBwYXRpZW50IGhhcyBhIDEwLXllYXIgcmlzayBvZiBmdXR1cmUgY29yb25hcnkgaGVhcnQgZGlzZWFzZSAoQ0hEKS4gVGhlIGRhdGEgc2V0IGNvbnRhaW5zIDQwMDAgcmVjb3JkcyBhbmQgMTUgdmFyaWFibGVzIHRoYXQgcHJvdmlkZSB0aGUgcGF0aWVudHMnIGRlbW9ncmFwaGljIGFuZCBtZWRpY2FsIGluZm9ybWF0aW9uLg0KDQpBIGJyaWVmIGRlc2NyaXB0aW9uIG9mIHRoZSB2YXJpYWJsZXMgaXMgZ2l2ZW4gYmVsb3cuDQoNCioqbWFsZSoqOiBtYWxlICggPSAxKSBvciBmZW1hbGUgKCA9IDApDQoNCioqYWdlKio6IEFnZSBvZiB0aGUgcGF0aWVudDsNCg0KKiplZHVjYXRpb24qKjogQSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBvZiB0aGUgcGFydGljaXBhbnRzJyBlZHVjYXRpb24sIHdpdGggdGhlIGxldmVsczogU29tZSBoaWdoIHNjaG9vbCAoMSksIGhpZ2ggc2Nob29sL0dFRCAoMiksIHNvbWUgY29sbGVnZS92b2NhdGlvbmFsIHNjaG9vbCAoMyksIGNvbGxlZ2UgKDQpDQoNCioqY3VycmVudFNtb2tlcioqOiBDdXJyZW50IGNpZ2FyZXR0ZSBzbW9raW5nIGF0IHRoZSB0aW1lIG9mIGV4YW1pbmF0aW9ucw0KDQoqKmNpZ3NQZXJEYXkqKjogTnVtYmVyIG9mIGNpZ2FyZXR0ZXMgc21va2VkIGVhY2ggZGF5DQoNCioqQlBtZWRzKio6IFVzZSBvZiBBbnRpLWh5cGVydGVuc2l2ZSBtZWRpY2F0aW9uIGF0IGV4YW0NCg0KKipwcmV2YWxlbnRTdHJva2UqKjogUHJldmFsZW50IFN0cm9rZSAoMCA9IGZyZWUgb2YgZGlzZWFzZSkNCg0KKipwcmV2YWxlbnRIeXAqKjogUHJldmFsZW50IEh5cGVydGVuc2l2ZS4gVGhlIHN1YmplY3Qgd2FzIGRlZmluZWQgYXMgaHlwZXJ0ZW5zaXZlIGlmIHRyZWF0ZWQNCg0KKipkaWFiZXRlcyoqOiBEaWFiZXRpYyBhY2NvcmRpbmcgdG8gY3JpdGVyaWEgb2YgZmlyc3QgZXhhbSB0cmVhdGVkDQoNCioqdG90Q2hvbCoqOiBUb3RhbCBjaG9sZXN0ZXJvbCAobWcvZEwpDQoNCioqc3lzQlAqKjogU3lzdG9saWMgQmxvb2QgUHJlc3N1cmUgKG1tSGcpDQoNCioqZGlhQlAqKjogRGlhc3RvbGljIGJsb29kIHByZXNzdXJlIChtbUhnKQ0KDQoqKkJNSSoqOiBCb2R5IE1hc3MgSW5kZXgsIHdlaWdodCAoa2cpL2hlaWdodCAobSlcXjINCg0KKipoZWFydFJhdGUqKjogSGVhcnQgcmF0ZSAoYmVhdHMvbWludXRlKQ0KDQoqKmdsdWNvc2UqKjogQmxvb2QgZ2x1Y29zZSBsZXZlbCAobWcvZEwpDQoNCioqVGVuWWVhckNIRCoqOiBUaGUgMTAgeWVhciByaXNrIG9mIGNvcm9uYXJ5IGhlYXJ0IGRpc2Vhc2UoQ0hEKQ0KDQpcDQoNCiMjIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMNCg0KV2UgZmlyc3QgaW5zcGVjdCB0aGUgZGF0YSBzZXQgYnkgY3JlYXRpbmcgdGFibGVzIGFuZCBtYWtpbmcgc29tZSBwbG90cyB0byBhc3Nlc3MgdGhlIGRpc3RyaWJ1dGlvbiBvZiBlYWNoIHZhcmlhYmxlIGluIHRoZSBkYXRhIHNldC4NCg0KYGBge3J9DQpGcmFtaW5naGFtMCA9IHJlYWQuY3N2KCJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9TVEE1NTEvdzA4L2ZyYW1pbmdoYW0uY3N2IikNCkZyYW1pbmdoYW0gPSBGcmFtaW5naGFtMA0Kc3VtbWFyeShGcmFtaW5naGFtMCkNCmBgYA0KDQpUaGUgYWJvdmUgZGVzY3JpcHRpdmUgdGFibGVzIGluZGljYXRlIGEgZmV3IHZhcmlhYmxlcyBpbnZvbHZlZCBpbiBtaXNzaW5nIHZhbHVlcy4gVHdvIHZhcmlhYmxlcyB0aGF0IGhhdmUgYSBzaWduaWZpY2FudCBwb3J0aW9uIG9mIG1pc3NpbmcgdmFsdWVzIGFyZSBsZXZlbHMgb2YgYGVkdWNhdGlvbmAgYW5kIGBnbHVjb3NlYC4NCg0KVGhlIHZhcmlhYmxlIGBnbHVjb3NlYCBpcyBjbGluaWNhbGx5IGFzc29jaWF0ZWQgd2l0aCBgZGlhYmV0ZXNgKHdoaWNoIGhhcyBvbmx5IDUgbWlzc2luZyB2YWx1ZXMgaW4gaXQpLiBXZSBjb3VsZCB1c2UgdGhpcyByZWxhdGlvbnNoaXAgdG8gaW1wdXRlIHRoZSBtaXNzaW5nIHZhbHVlcyBpbiBgZ2x1Y29zZWAuIEFmdGVyIGxvb2tpbmcgYXQgdGhlIHByb3BvcnRpb24gb2YgbWlzc2luZyB2YWx1ZXMgaW4gdGhlIGRpYWJldGVzIGdyb3VwIGFuZCBkaWFiZXRlcy1mcmVlIGdyb3VwLCB0aGUgbWlzc2luZyBwZXJjZW50YWdlIHBvaW50cyBhcmUgYWJvdXQgOSUgYW5kIDQlLCByZXNwZWN0aXZlbHkuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NX0NCmRpYWJldGVzLmlkID0gd2hpY2goRnJhbWluZ2hhbSRkaWFiZXRlcyA9PSAxKQ0KZGlhYi5nbHVjb3NlID0gRnJhbWluZ2hhbVtkaWFiZXRlcy5pZCwgImdsdWNvc2UiXQ0Kbm8uZGlhYi5nbHVjb3NlID0gRnJhbWluZ2hhbVstZGlhYmV0ZXMuaWQsICJnbHVjb3NlIl0NCiN0YWJsZShGcmFtaW5naGFtJGRpYWJldGVzKQ0KI3N1bW1hcnkoZGlhYi5nbHVjb3NlKQ0KI3N1bW1hcnkobm8uZGlhYi5nbHVjb3NlKQ0KcGxvdChkZW5zaXR5KG5hLm9taXQobm8uZGlhYi5nbHVjb3NlKSksIGNvbCA9ICJkYXJrcmVkIiwNCiAgICAgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgR2x1Y29zZSBMZXZlbHMiLA0KICAgICB4bGFiID0gIkdsdWNvc2UiKQ0KbGluZXMoZGVuc2l0eShuYS5vbWl0KGRpYWIuZ2x1Y29zZSkpLCBjb2wgPSAiYmx1ZSIpDQpsZWdlbmQoInRvcHJpZ2h0IiwgYygiRGlhYmV0ZXMgR3JvdXAiLCAiRGlhYmV0ZXMgRnJlZSIpLCANCiAgICAgICBsdHkgPXJlcCgxLDIpLA0KICAgICAgIGNvbD1jKCJkYXJrcmVkIiwgImJsdWUiKSwNCiAgICAgICBidHkgPSAibiIsIGNleCA9IDAuOCkNCmBgYA0KDQpUaGVyZSBhcmUgYWJvdXQgMi41JSBvZiBwYXJ0aWNpcGFudHMgaW4gdGhlIHN0dWR5IGhhZCBkaWFiZXRlcy4gVGhpcyBtYXkgY2F1c2UgYSBwb3RlbnRpYWwgaW1iYWxhbmNlZCBjYXRlZ29yeSBpc3N1ZS4gQXMgYW50aWNpcGF0ZWQsIHRoZSBkaXN0cmlidXRpb24gYGdsdWNvc2VgIGxldmVscyBvZiBkaWFiZXRlcyBhbmQgZGlhYmV0ZXMtZnJlZSBncm91cHMgYXJlIHNpZ25pZmljYW50bHkgZGlmZmVyZW50Lg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTV9DQpOdW1WYXIgPSBGcmFtaW5naGFtWywgYygyLDEwOjE1KV0gDQpwYWlycyhOdW1WYXIsIGNleCA9IDAuMywgY29sID0gIm5hdnkiLCBtYWluID0iUGFpci13aXNlIHNjYXR0ZXIgcGxvdCBvZiBudW1lcmljYWwgdmFyaWFibGVzIikNCmBgYA0KDQpXZSB3aWxsIGRpc2N1c3MgaW1wdXRhdGlvbiBtZXRob2RzIHRvIGhhbmRsZSBtaXNzaW5nIHZhbHVlcyBpbiByZWxhdGVkIHZhcmlhYmxlcyBmb3IgYWxnb3JpdGhtLWJhc2VkIHByZWRpY3Rpb24uDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NX0NCnNleCA9IGFzLmRhdGEuZnJhbWUodGFibGUoRnJhbWluZ2hhbSRtYWxlKSkNCmNvbG5hbWVzKHNleCkgPSBjKCJzZXgiLCAiY291bnRzIikNCnNleCRzZXggPSBpZmVsc2Uoc2V4JHNleCA9PSAxLCAibWFsZSIsICJmZW1hbGUiKQ0KIyMjDQplZHUgPSBhcy5kYXRhLmZyYW1lKHRhYmxlKEZyYW1pbmdoYW0kZWR1Y2F0aW9uKSkNCmNvbG5hbWVzKGVkdSkgPSBjKCJFZHVMZXZlbCIsICJjb3VudHMiKQ0KZWR1JEVkdUxldmVsID0gaWZlbHNlKGVkdSRFZHVMZXZlbCA9PSAxLCAiSFMtIiwgDQogICAgICAgICAgICAgICAgIGlmZWxzZShlZHUkRWR1TGV2ZWwgPT0gMiwgIkhTIiwgDQogICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGVkdSRFZHVMZXZlbCA9PSAzLCAiQ29sLSIsICJDb2wiICkpKQ0KIyMjDQpwYXIobWZyb3cgPSBjKDIsMikpDQpiYXJwbG90KGhlaWdodD1zZXgkY291bnRzLCBuYW1lcyA9IHNleCRzZXgsIGNvbCA9ICJzdGVlbGJsdWUiLA0KICAgICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBTZXgiKQ0KaGlzdChGcmFtaW5naGFtJGFnZSwgeGxhYj0iYWdlIiwgeWxhYj0iY291bnRzIiwgY29sPSJzdGVlbGJsdWUiLA0KICAgICBtYWluID0gIkFnZSBEaXN0cmlidXRpb24iKQ0KYmFycGxvdChoZWlnaHQ9ZWR1JGNvdW50cywgbmFtZXMgPSBlZHUkRWR1TGV2ZWwsIGNvbCA9ICJzdGVlbGJsdWUiLA0KICAgICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBFZHVjYXRpb24iKQ0KaGlzdChGcmFtaW5naGFtJGNpZ3NQZXJEYXksIHhsYWI9ImNpZ3MgcGVyIGRheSIsIHlsYWI9ImNvdW50cyIsDQogICAgIGNvbD0ic3RlZWxibHVlIiwNCiAgICAgbWFpbiA9ICJOdW1iZXIgb2YgQ2lncyBEYXkiKQ0KYGBgDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NX0NCmN1cnJlbnRTbW9rZXIgPSBhcy5kYXRhLmZyYW1lKHRhYmxlKEZyYW1pbmdoYW0kY3VycmVudFNtb2tlcikpDQpjb2xuYW1lcyhjdXJyZW50U21va2VyKSA9IGMoImN1cnJlbnRTbW9rZXIiLCAiY291bnRzIikNCmN1cnJlbnRTbW9rZXIkY3VycmVudFNtb2tlciA9IGlmZWxzZShjdXJyZW50U21va2VyJGN1cnJlbnRTbW9rZXIgPT0gMSwgIlllcyIsICJObyAiKQ0KIyMjDQpCUE1lZHMgPSBhcy5kYXRhLmZyYW1lKHRhYmxlKEZyYW1pbmdoYW0kQlBNZWRzKSkNCmNvbG5hbWVzKEJQTWVkcykgPSBjKCJCUE1lZHMiLCAiY291bnRzIikNCkJQTWVkcyRCUE1lZHMgPSBpZmVsc2UoQlBNZWRzJEJQTWVkcyA9PSAxLCAiWWVzIiwgIk5vICIpDQojIyMNCnByZXZhbGVudFN0cm9rZSA9IGFzLmRhdGEuZnJhbWUodGFibGUoRnJhbWluZ2hhbSRwcmV2YWxlbnRTdHJva2UpKQ0KY29sbmFtZXMocHJldmFsZW50U3Ryb2tlKSA9IGMoInByZXZhbGVudFN0cm9rZSIsICJjb3VudHMiKQ0KcHJldmFsZW50U3Ryb2tlJHByZXZhbGVudFN0cm9rZSA9IGlmZWxzZShwcmV2YWxlbnRTdHJva2UkcHJldmFsZW50U3Ryb2tlID09IDEsICJZZXMiLCAiTm8gIikNCiMjIyAgIA0KcHJldmFsZW50SHlwID0gYXMuZGF0YS5mcmFtZSh0YWJsZShGcmFtaW5naGFtJHByZXZhbGVudEh5cCkpDQpjb2xuYW1lcyhwcmV2YWxlbnRIeXApID0gYygicHJldmFsZW50SHlwIiwgImNvdW50cyIpDQpwcmV2YWxlbnRIeXAkcHJldmFsZW50SHlwID0gaWZlbHNlKHByZXZhbGVudEh5cCRwcmV2YWxlbnRIeXAgPT0gMSwgIlllcyIsICJObyAiKQ0KIyMjDQpwYXIobWZyb3c9YygyLDIpKQ0KYmFycGxvdChoZWlnaHQ9Y3VycmVudFNtb2tlciRjb3VudHMsIG5hbWVzID0gY3VycmVudFNtb2tlciRjdXJyZW50U21va2VyLCBjb2wgPSAic3RlZWxibHVlIiwNCiAgICAgICAgbWFpbiA9ICJTbW9raW5nIFN0YXR1cyIpDQpiYXJwbG90KGhlaWdodD1CUE1lZHMkY291bnRzLCBuYW1lcyA9IEJQTWVkcyRCUE1lZHMsIGNvbCA9ICJzdGVlbGJsdWUiLA0KICAgICAgICBtYWluID0gIkJsb29kIFByZXNzdXJlIFRyZWF0bWVudCIpDQpiYXJwbG90KGhlaWdodD1wcmV2YWxlbnRTdHJva2UkY291bnRzLCBuYW1lcyA9IHByZXZhbGVudFN0cm9rZSRwcmV2YWxlbnRTdHJva2UsIGNvbCA9ICJzdGVlbGJsdWUiLA0KICAgICAgICBtYWluID0gIlN0cm9rZSBTdGF0dXMiKQ0KYmFycGxvdChoZWlnaHQ9cHJldmFsZW50SHlwJGNvdW50cywgbmFtZXMgPSBwcmV2YWxlbnRIeXAkcHJldmFsZW50SHlwLCBjb2wgPSAic3RlZWxibHVlIiwNCiAgICAgICAgbWFpbiA9ICJIeXBlcnRlbnNpb24gU3RhdHVzIikNCmBgYA0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTV9DQpkaWFiZXRlcyA9IGFzLmRhdGEuZnJhbWUodGFibGUoRnJhbWluZ2hhbSRkaWFiZXRlcykpDQpjb2xuYW1lcyhkaWFiZXRlcykgPSBjKCJkaWFiZXRlcyIsICJjb3VudHMiKQ0KZGlhYmV0ZXMkZGlhYmV0ZXMgPSBpZmVsc2UoZGlhYmV0ZXMkZGlhYmV0ZXMgPT0gMSwgIlllcyIsICJObyAiKQ0KIyMjDQpwYXIobWZyb3c9YygyLDIpKQ0KYmFycGxvdChoZWlnaHQ9ZGlhYmV0ZXMkY291bnRzLCBuYW1lcyA9IGRpYWJldGVzJGRpYWJldGVzLCBjb2wgPSAic3RlZWxibHVlIiwNCiAgICAgICAgbWFpbiA9ICJEaWFiZXRlcyBTdGF0dXMiKQ0KaGlzdChGcmFtaW5naGFtJHRvdENob2wsIHhsYWI9IlRvdGFsIENob2xlc3Ryb2wiLCB5bGFiPSJjb3VudHMiLA0KICAgICBjb2w9InN0ZWVsYmx1ZSIsDQogICAgIG1haW4gPSAiVG90YWwgQ2hvbGVzdHJvbCAobWcvZEwpIikNCmhpc3QoRnJhbWluZ2hhbSRzeXNCUCwgeGxhYj0iU3lzdG9saWMgQlAiLCB5bGFiPSJjb3VudHMiLA0KICAgICBjb2w9InN0ZWVsYmx1ZSIsDQogICAgIG1haW4gPSAiU3lzdG9saWMgQmxvb2QgUHJlc3N1cmUgKG1tSGcpIikNCmhpc3QoRnJhbWluZ2hhbSRkaWFCUCwgeGxhYj0iU3lzdG9saWMgQlAiLCB5bGFiPSJjb3VudHMiLA0KICAgICBjb2w9InN0ZWVsYmx1ZSIsDQogICAgIG1haW4gPSAiRGlhc3RvbGljIEJsb29kIFByZXNzdXJlIChtbUhnKSIpDQoNCmBgYA0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTV9DQpUZW5ZZWFyQ0hEID0gYXMuZGF0YS5mcmFtZSh0YWJsZShGcmFtaW5naGFtJFRlblllYXJDSEQpKQ0KY29sbmFtZXMoVGVuWWVhckNIRCkgPSBjKCJUZW5ZZWFyQ0hEIiwgImNvdW50cyIpDQpUZW5ZZWFyQ0hEJFRlblllYXJDSEQgPSBpZmVsc2UoVGVuWWVhckNIRCRUZW5ZZWFyQ0hEID09IDEsICJZZXMiLCAiTm8gIikNCiMjIw0KcGFyKG1mcm93PWMoMiwyKSkNCmhpc3QoRnJhbWluZ2hhbSRCTUksIHhsYWI9IkJNSSIsIHlsYWI9ImNvdW50cyIsDQogICAgIGNvbD0ic3RlZWxibHVlIiwNCiAgICAgbWFpbiA9ICJCb2R5IE1hc3MgSW5kZXgiKQ0KaGlzdChGcmFtaW5naGFtJGhlYXJ0UmF0ZSwgeGxhYj0iSGVhcnQgUmF0ZSIsIHlsYWI9ImNvdW50cyIsDQogICAgIGNvbD0ic3RlZWxibHVlIiwNCiAgICAgbWFpbiA9ICJIZWFydCBSYXRlIChiZWF0cy9taW51dGUpIikNCmhpc3QoRnJhbWluZ2hhbSRnbHVjb3NlLCB4bGFiPSJHbHVjb3NlIiwgeWxhYj0iY291bnRzIiwNCiAgICAgY29sPSJzdGVlbGJsdWUiLA0KICAgICBtYWluID0gIkJsb29kIEdsdWNvc2UgTGV2ZWwgKG1nL2RMKSIpDQpiYXJwbG90KGhlaWdodD1UZW5ZZWFyQ0hEJGNvdW50cywgbmFtZXMgPSBUZW5ZZWFyQ0hEJFRlblllYXJDSEQsIGNvbCA9ICJzdGVlbGJsdWUiLA0KICAgICAgICBtYWluID0gIkNvcm9uYXJ5IEhlYXJ0IERpc2Vhc2UgU3RhdHVzIikNCmBgYA0KDQpUaGUgYWJvdmUgZGlzdHJpYnV0aW9uYWwgZ3JhcGhpY3MgaW5kaWNhdGUgdGhhdCBgQlBNZWRzYCAocmVjZWl2aW5nIGJsb29kIHByZXNzdXJlIG1lZGljYXRpb24pLCBgZGlhYmV0ZXNgLCBhbmQgYHByZXZhbGVudFN0cm9rZWAgaGF2ZSBzbWFsbCBjYXRlZ29yaWVzIChsZXNzIHRoYW4gMSUpLiBTb21lIG9mIHRoZXNlIG1pZ2h0IGNhdXNlIHBvdGVudGlhbCBpc3N1ZXMgaW4gdGhlIGZpbmFsIG1vZGVsaW5nLiBBY2NvcmRpbmcgdG8gYSByZWNlbnQgc3R1ZHkgKDxodHRwczovL3d3dy5ibWouY29tL2NvbnRlbnQvQk1KLzM2MC9CTUouajU4NTUuRnVsbC5wZGY+KSwgc21va2luZyBpcyBhIHNpZ25pZmljYW50IGNvbnRyaWJ1dG9yIHRvIENIRC4gV2Ugd2lsbCBpbmNsdWRlIHRoZSB2YXJpYWJsZSBgY2lnc1BlckRheWAgaW4gdGhlIG1vZGVsaW5nLiBIb3dldmVyLCBgY2lnc1BlckRheWAgbmVlZHMgdG8gYmUgcmVncm91cGVkIHRvIG1ha2UgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSB3aXRoIGNhdGVnb3JpZXM6IGAwYCwgYDEtMTBgLCBgMTEtMTlgLCBhbmQgYDIwK2AgcmVwcmVzZW50aW5nICoqY3VycmVudCBzbW9raW5nIHN0YXR1cyoqOiBub25zbW9rZXIsIGxpZ2h0IHNtb2tlciwgbW9kZXJhdGUgc21va2VyLCBhbmQgaGVhdnkgc21va2VyLg0KDQpcDQoNCiMjIE1pc3NpbmctZGF0YSBJbXB1dGF0aW9uDQoNCkFtb25nIHZhcmlhYmxlcyB0aGF0IGhhdmUgbWlzc2luZyB2YWx1ZXMsIGBlZHVjYXRpb25gIGFuZCBgZ2x1Y29zZWAgaGF2ZSBhIHNpZ25pZmljYW50IHBvcnRpb24gb2YgbWlzc2luZyB2YWx1ZXMuIFZhcmlhYmxlcyBgY2lnc1BlckRheWAsIGBCUE1lZHNgLCBgdG90Q2hvbGAsIGBCTUlgLCBhbmQgYGhlYXJ0UmF0ZWAgaGF2ZSBsZXNzIHRoYW4gMSUgbWlzc2luZyB2YWx1ZXMuIEZvciB0aG9zZSB3aXRoIGEgc21hbGwgcG9ydGlvbiBvZiBtaXNzaW5nIHZhbHVlcywgd2UgdXNlICoqbWVhbi9tb2RlIHJlcGxhY2VtZW50IG1ldGhvZCoqIHRvIGltcHV0ZSB0aGUgbWlzc2luZyB2YWx1ZXMuDQoNClZhcmlhYmxlIGBlZHVjYXRpb25gIGhhcyBzbGlnaHRseSBsZXNzIHRoYW4gMi41JSBtaXNzaW5nIHZhbHVlcy4gVGhlcmUgYXJlIG5vICoqYXV4aWxpYXJ5KiogdmFyaWFibGVzIGluIHRoZSBkYXRhIHRoYXQgY2FuIGJlIHVzZWQgdG8gaW1wdXRlIHRoZSBtaXNzaW5nIHZhbHVlcyBpbiBgZWR1Y2F0aW9uYC4gVGhlIHNpbXBsZSAqKm1vZGUgcmVwbGFjZW1lbnQqKiB3aWxsIGNoYW5nZSB0aGUgcHJvYmFiaWxpdHkgc3RydWN0dXJlLiBUaGVyZWZvcmUsIGBlZHVjYXRpb25gIHdpbGwgbm90IHVzZWQgaW4gdGhlIHN1YnNlcXVlbnQgbW9kZWxpbmcuIFdlIHdpbGwgYGdsdWNvc2VgIGlzIGNsaW5pY2FsbHkgYXNzb2NpYXRlZCB3aXRoIHNldmVyYWwgdmFyaWFibGVzIHN1Y2ggYXMgYGRpYWJldGVzYCwgYFRlblllYXJDSERgLCBgY3VycmVudFNtb2tlcmAsIGV0Yy4gV2Ugd2lsbCB1c2UgdGhlc2UgKiphdXhpbGlhcnkqKiB2YXJpYWJsZXMgdXNpbmcgcmVncmVzc2lvbiBtZXRob2RzLg0KDQojIyMgTWVhbi9Nb2RlL1JhbmRvbSBSZXBsYWNlbWVudCBNZXRob2RzDQoNClRoZXJlIGlzIG5vIGJhc2UgUiBmdW5jdGlvbiB0byBmaW5kIHRoZSBtb2RlIG9mIGEgZGF0YSBzZXQuIFdlIGZpcnN0IGRlZmluZSBhbiBSIGZ1bmN0aW9uIHRvIGZpbmQgdGhlIG1vZGUgb2YgYSBnaXZlbiBkYXRhIHNldC4NCg0KYGBge3J9DQojIENyZWF0ZSB0aGUgZnVuY3Rpb24uDQpnZXRtb2RlIDwtIGZ1bmN0aW9uKHYpIHsNCiAgIHVuaXF2IDwtIHVuaXF1ZSh2KQ0KICAgdW5pcXZbd2hpY2gubWF4KHRhYnVsYXRlKG1hdGNoKHYsIHVuaXF2KSkpXQ0KfQ0KYGBgDQoNClRoZSBtZXRob2Qgb2YgKipyYW5kb20gcmVwbGFjZW1lbnQqKiB1c2VzIHRoZSA8Zm9udCBjb2xvciA9ICJyZWQiPipcY29sb3J7cmVkfShlbXBpcmljYWwpIGRpc3RyaWJ1dGlvbio8L2ZvbnQ+IG9mIHRoZSBjb21wbGV0ZSBkYXRhIHZhbHVlcyB0byBzaW11bGF0ZSByYW5kb20gdmFsdWVzIGFuZCByZXBsYWNlIHRoZSBtaXNzaW5nIHZhbHVlcyB3aXRoIHRoZXNlIGdlbmVyYXRlZCByYW5kb20gdmFsdWVzLiBUaGlzIHR5cGUgb2YgaW1wdXRhdGlvbiBpcyByZWNvbW1lbmRlZCBmb3Igbm8gb3RoZXIgYXV4aWxpYXJ5IHZhcmlhYmxlcyBpbiB0aGUgZGF0YSBzZXQuDQoNCmBgYHtyfQ0KIyMgbW9kZSByZXBsYWNlbWVudA0KRnJhbWluZ2hhbSRjaWdzUGVyRGF5W2lzLm5hKEZyYW1pbmdoYW0kY2lnc1BlckRheSldID0gZ2V0bW9kZShGcmFtaW5naGFtJGNpZ3NQZXJEYXkpDQojIyBSYW5kb20gcmVwbGFjZW1lbnQgZm9yIEJQTWVkcyB1c2luZyBiaW5vbWlhbCBkaXN0cmlidXRpb24gd2l0aCBwID0gMC4wMw0KRnJhbWluZ2hhbSRCUE1lZHNbaXMubmEoRnJhbWluZ2hhbSRCUE1lZHMpXSA9cmJpbm9tKDUzLDEsMC4wMykgICMgQmVybm91bGxpIHRyaWFsDQojIyBSZW1vdmUgTkEgZmlyc3QgdGhlbiB0YWtlIGEgcmFuZG9tIHNhbXBsZSB0byByZXBsYWNlIHRoZSBtaXNzaW5nIHZhbHVlcw0KRnJhbWluZ2hhbSR0b3RDaG9sW2lzLm5hKEZyYW1pbmdoYW0kdG90Q2hvbCldID0gc2FtcGxlKG5hLm9taXQoRnJhbWluZ2hhbSR0b3RDaG9sKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgNTAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2UgPSBGQUxTRSkNCkZyYW1pbmdoYW0kQk1JW2lzLm5hKEZyYW1pbmdoYW0kQk1JKV0gPSBzYW1wbGUobmEub21pdChGcmFtaW5naGFtJEJNSSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDE5LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBsYWNlID0gRkFMU0UpDQpGcmFtaW5naGFtJGhlYXJ0UmF0ZVtpcy5uYShGcmFtaW5naGFtJGhlYXJ0UmF0ZSldID0gc2FtcGxlKG5hLm9taXQoRnJhbWluZ2hhbSRoZWFydFJhdGUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBsYWNlID0gRkFMU0UpDQpGcmFtaW5naGFtJFNtb2tlckNsYXNzID0gaWZlbHNlKEZyYW1pbmdoYW0kY2lnc1BlckRheT09MCwgIjBub25lIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShGcmFtaW5naGFtJGNpZ3NQZXJEYXk8PSAxMCwgImxpZ2h0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoRnJhbWluZ2hhbSRjaWdzUGVyRGF5PCAyMCwgIm1vZGVyYXRlIiwgImhlYXZ5IikpKQ0KYGBgDQoNCiMjIyBSZWdyZXNzaW9uIEltcHV0YXRpb24NCg0KV2Ugbm93IGltcHV0ZSBgZ2x1Y29zZWAgdXNpbmcgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwuIEFmdGVyIHRyeWluZyBzZXZlcmFsIG1vZGVscyBpbmNsdWRpbmcgc2V0cyBvZiB2YXJpYWJsZXMgdGhhdCBhbGwgaW5jbHVkZSB0aGUgbWFqb3IgY2xpbmljYWwgdmFyaWFibGVzICoqY3VycmVudFNtb2tlcioqLCAqKlRlblllYXJDSEQqKiwgYW5kICoqZGlhYmV0ZXMqKi4gVGhlIG1vZGVsIHdpdGggdGhyZWUgY2xpbmljYWwgdmFyaWFibGVzIHlpZWxkZWQgdGhlIGJlc3QgJFJeMiQuIFdlIGRlY2lkZSB0byB1c2UgdGhlIGZvbGxvd2luZyBtb2RlbCB0byBwcmVkaWN0IHRoZSBtaXNzaW5nIGBnbHVjb3NlYC4NCg0KJCQNClx0ZXh0e2dsdWNvc2V9ID0gXGFscGhhXzAgKyBcYWxwaGFfMSBcdGV4dHtjdXJyZW50U21va2VyfSArIFxhbHBoYV8yIFx0ZXh0e1RlblllYXJDSER9ICsgXGFscGhhXzNcdGV4dHtkaWFiZXRlc30NCiQkDQoNClJlZ3Jlc3Npb24gaW1wdXRhdGlvbiBpcyBlc3NlbnRpYWxseSBhIHByZWRpY3RpdmUgbW9kZWxpbmcgYXBwcm9hY2guIFRoZSBwZXJmb3JtYW5jZSBvZiB0aGlzIGltcHV0YXRpb24gbWV0aG9kIGlzIGhlYXZpbHkgZGVwZW5kZW50IG9uIHRoZSBzdHJlbmd0aCBvZiBhc3NvY2lhdGlvbiBiZXR3ZWVuIHRoZSBzZXQgb2YgYXV4aWxpYXJ5IHZhcmlhYmxlcyBpbiB0aGUgcHJlZGljdGl2ZSBtb2RlbC4NCg0KYGBge3J9DQojIyBsaW5lYXIgcmVncmVzc2lvbiBpbXB1dGF0aW9uIC0gZ2x1Y29zZQ0KIyMgU3BsaXQgdGhlIGRhdGEgaW50byB0d28gc2V0czogc3Vic2V0IHdpdGggY29tcGxldGUgcmVjb3JkcyBmb3IgZml0dGluZyANCiMjIHJlZ3Jlc3Npb24gbW9kZWwgYW5kIGEgZGF0YSBmcmFtZSB0byBwcmVkaWN0IHRoZSBtaXNzaW5nIHZhbHVlcyBpbiBnbHVjb3NlDQpuYS5JRCA9IHdoaWNoKGlzLm5hKEZyYW1pbmdoYW0kZ2x1Y29zZSk9PVRSVUUpDQpJbXB1dFJlZ0RhdCA9IEZyYW1pbmdoYW1bLW5hLklELF0NCnByZWREYXRhID0gRnJhbWluZ2hhbVtuYS5JRCwgYygiZ2x1Y29zZSIsICJjdXJyZW50U21va2VyIiwgIlRlblllYXJDSEQiLCAiZGlhYmV0ZXMiKV0NCmltcHV0Lk1vZGVsID0gbG0oZ2x1Y29zZSB+IGN1cnJlbnRTbW9rZXIgKyBUZW5ZZWFyQ0hEICsgZGlhYmV0ZXMsIGRhdGEgPSBJbXB1dFJlZ0RhdCkNCnBhbmRlcihzdW1tYXJ5KGltcHV0Lk1vZGVsKSRjb2VmKQ0KYGBgDQoNClRoZSByZXN1bHRpbmcgcHJlZGljdGlvbiBtb2RlbCBpcyBnaXZlbiBieQ0KDQokJA0KXHRleHR7Z2x1Y29zZX0gPSA3OS40OSAtMS4zMzVcdGltZXMgXHRleHR7Y3VycmVudFNtb2tlcn0gKyA0LjQ2OCBcdGltZXNcdGV4dHtUZW5ZZWFyQ0hEfSArIDg5LjY5IFx0aW1lc1x0ZXh0e2RpYWJldGVzfQ0KJCQNCg0KV2UgbmV4dCBpbXB1dGUgYGdsdWNvc2VgIHVzaW5nIHRoZSBhYm92ZSBtb2RlbCBpbiB0aGUgZm9sbG93aW5nIGNvZGUuDQoNCmBgYHtyfQ0KaW1wdXROQSA9IHByZWRpY3QoaW1wdXQuTW9kZWwsIG5ld2RhdGEgPSBwcmVkRGF0YSkgICAjIHByZWRpY3RlZCBnbHVjb3NlDQpGcmFtaW5naGFtJGdsdWNvc2VbaXMubmEoRnJhbWluZ2hhbSRnbHVjb3NlKV0gPSBpbXB1dE5BDQojIyBSZWNoZWNrIHRoZSBpbXB1dGVkIGRhdGEgc2V0DQpJbXB1dGVkRnJhbWluZ2hhbSA9IEZyYW1pbmdoYW0NCmBgYA0KDQpgYGB7cn0NCiMgd3JpdGUgdG8gdGhlIGZpbGUgZGlyZWN0b3J5IGZvciBzdWJzZXF1ZW50IGFuYWx5c2lzIA0Kd3JpdGUuY3N2KEltcHV0ZWRGcmFtaW5naGFtLCAiSW1wdXRlZEZyYW1pbmdoYW0uY3N2IikNCiMgdXBsb2FkIHRoZSBkYXRhIHRvIHRoZSBHaXRIdWIgcmVwb3NpdG9yeQ0KSW1wdXRlZEZyYW1pbmdoYW0gID0gcmVhZC5jc3YoImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1MS93MDgvSW1wdXRlZEZyYW1pbmdoYW0uY3N2IikNCmBgYA0KDQpXZSBjaGVjayB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIGltcHV0YXRpb24gYnkgY29tcGFyaW5nIHRoZSBkaXN0cmlidXRpb25zIG9mIHRoZSB2YXJpYWJsZXMgYmVmb3JlIGFuZCBhZnRlciB0aGUgaW1wdXRhdGlvbi4NCg0KYGBge3J9DQpwbG90KGRlbnNpdHkobmEub21pdChJbXB1dGVkRnJhbWluZ2hhbSRnbHVjb3NlKSksIA0KICAgICB4bGFiPSJnbHVjb3NlIGxldmVsIiwNCiAgICAgY29sPSJkYXJrcmVkIiwNCiAgICAgbHdkPTIsDQogICAgIG1haW49IkdsdWNvc2UgRGlzdHJpYnV0aW9ucyBCZWZvcmUgYW5kIEFmdGVyIEltcHV0YXRpb24iKQ0KbGluZXMoZGVuc2l0eShuYS5vbWl0KEZyYW1pbmdoYW0wJGdsdWNvc2UpKSwgDQogICAgICBjb2wgPSAic3RlZWxibHVlIiwNCiAgICAgIGx3ZCA9MikNCmxlZ2VuZCgidG9wcmlnaHQiLCBjKCJJbXB1dGVkIEdsdWNvc2UiLCAiT3JpZ2luYWwgR2x1Y29zZSIpLA0KICAgICAgIGNvbD1jKCJkYXJrcmVkIiwgInN0ZWVsYmx1ZSIpLA0KICAgICAgIGx3ZD1yZXAoMiwyKSwNCiAgICAgICBidHk9Im4iKQ0KYGBgDQoNClRoZSBhYm92ZSBkZW5zaXR5IGN1cnZlcyBpbmRpY2F0ZSB0aGF0IHRoZSBkaXN0cmlidXRpb25zIG9mIGdsdWNvc2UgbGV2ZWxzIGJlZm9yZSBhbmQgYWZ0ZXIgaW1wdXRhdGlvbiBhcmUgY2xvc2UgdG8gZWFjaCBvdGhlci4gV2l0aCB0aGUgYWJvdmUgRURBIGFuZCBpbXB1dGF0aW9uIGFuZCBkaXNjcmV0aXphdGlvbiBvZiB0aGUNCg0KXA0KDQojIFN0YXRpc3RpY2FsIFByZWRpY3Rpb24NCg0KVGhpcyBzZWN0aW9uIHVzZXMgdGhlIGNsYXNzaWNhbCBhcHByb2FjaCB0byBidWlsZGluZyBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVscyBhbmQgc2VhcmNoaW5nIGZvciB0aGUgZmluYWwgcHJlZGljdGl2ZSBtb2RlbC4gVGhlIGNhbmRpZGF0ZSBtb2RlbHMgd2lsbCBiZSBidWlsdCBiYXNlZCBvbiB0aGUgYW5hbHl0aWMgZGF0YSBzZXQgYEltcHV0ZWRGcmFtaW5naGFtYC4NCg0KVGhlIGdlbmVyYWwgbW9kZWwtYnVpbGRpbmcgcHJvY2VzcyBpbnZvbHZlcyB0aGUgZm9sbG93aW5nIHRocmVlIHN0ZXBzLg0KDQoqKlN0ZXAgMSoqOiBidWlsZCBhIHNtYWxsIG1vZGVsIHRoYXQgY29udGFpbnMgcHJhY3RpY2FsbHkgaW1wb3J0YW50IHZhcmlhYmxlcyByZWdhcmRsZXNzIG9mIHRoZWlyIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZS4gVGhpcyBzdGVwIHJlcXVpcmVzIGlucHV0cyBmcm9tIGRvbWFpbiBleHBlcnRzIHRvIGlkZW50aWZ5IHRoZXNlIHZhcmlhYmxlcy4gRm9yIGNvbnZlbmllbmNlLCB3ZSB0aGlzIGluaXRpYWwgc21hbGwgbW9kZWwgKipyZWR1Y2VkIG1vZGVsKiouIFRoZXNlIHZhcmlhYmxlcyB3aWxsIGJlIGtlcHQgaW4gdGhlIGZpbmFsIG1vZGVsLg0KDQoqKlN0ZXAgMioqOiBhZGQgYWxsIHZhcmlhYmxlcyB0aGF0IGhhdmUgcG90ZW50aWFsIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSB0byB0aGUgKipyZWR1Y2VkIG1vZGVsKiouIFRoaXMgZXhwYW5kZWQgbW9kZWwgaXMgY2FsbGVkICoqZnVsbCBtb2RlbCoqLiBXZSB3b3VsZCBleHBlY3QgdGhhdCB0aGUgb3B0aW1hbCBtb2RlbCBtdXN0IGJlIGJldHdlZW4gdGhlICoqcmVkdWNlZCBtb2RlbCoqIGFuZCB0aGUgKipmdWxsIG1vZGVsKiouDQoNCioqU3RlcCAzKio6IHVzZSBhbiBhcHByb3ByaWF0ZSBtb2RlbCBwZXJmb3JtYW5jZSBtZWFzdXJlIHRvIHNlYXJjaCBmb3IgdGhlIGJlc3QgbW9kZWwgYmV0d2VlbiB0aGUgKipyZWR1Y2VkIG1vZGVsKiogYW5kIHRoZSAqKmZ1bGwgbW9kZWwqKi4gSW4gUiBNQVNTIGxpYnJhcnksDQoNCmBgYHtyfQ0KIyBUaGUgZm9sbG93aW5nIHJlZHVjZWQgbW9kZWwgaW5jbHVkZXMgcHJhY3RpY2FsbHkgc2lnbmlmaWNhbnQgcHJlZGljdG9yIHZhcmlhYmxlcw0KcmVkdWNlZE1vZGVsID0gZ2xtKFRlblllYXJDSEQgfiBwcmV2YWxlbnRTdHJva2UgKyBCTUkgKyBCUE1lZHMgKyB0b3RDaG9sLCANCiAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gbG9naXQpLA0KICAgICAgICAgICAgICAgICAgIGRhdGEgPSBJbXB1dGVkRnJhbWluZ2hhbSkNCiMgQWRkaW5nIHNvbWUgcG90ZW50aWFsIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgdmFyaWFibGVzDQpmdWxsTW9kZWwgPSBnbG0oVGVuWWVhckNIRCB+IHByZXZhbGVudFN0cm9rZSArIEJNSSArIEJQTWVkcyArIHRvdENob2wgKyBhZ2UgKyANCiAgICAgICAgICAgICAgICAgIGN1cnJlbnRTbW9rZXIgKyBTbW9rZXJDbGFzcyArIHByZXZhbGVudEh5cCArIGdsdWNvc2UgKyBkaWFCUCArIA0KICAgICAgICAgICAgICAgICAgZGlhYmV0ZXMgKyBtYWxlICsgc3lzQlAgKyBkaWFCUCArIGhlYXJ0UmF0ZSwgDQogICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmlub21pYWwobGluayA9IGxvZ2l0KSwNCiAgICAgICAgICAgICAgICAgICBkYXRhID0gSW1wdXRlZEZyYW1pbmdoYW0pDQojIyBBdXRvbWF0aWNzIHZhcmlhYmxlIHNlbGVjdGlvbiBwcm9jZWR1cmUgZm9yIHNlYXJjaGluZyBmb3IgdGhlIGJlc3QgbW9kZWwNCiMjIGZvciBhc3NvY2lhdGlvbiBhbmFseXNpcw0KZm9yd2FyZHMgPSBzdGVwKHJlZHVjZWRNb2RlbCwNCiAgICAgICAgICAgICAgICBzY29wZT1saXN0KGxvd2VyPWZvcm11bGEocmVkdWNlZE1vZGVsKSx1cHBlcj1mb3JtdWxhKGZ1bGxNb2RlbCkpLCANCiAgICAgICAgICAgICAgICBkaXJlY3Rpb249ImZvcndhcmQiLA0KICAgICAgICAgICAgICAgIHRyYWNlID0gRkFMU0UpDQpgYGANCg0KV2UgbmV4dCB1c2UgUk9DIGN1cnZlcyB0byBjaG9vc2UgdGhlIGJlc3QgcHJlZGljdGl2ZSBtb2RlbC4gVGhlIFIgbGlicmFyeSBgcFJPQ2AgdG8gZXh0cmFjdCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgUk9DIGZvciBlYWNoIG9mIHRoZSBjYW5kaWRhdGUgbW9kZWxzLg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTUsIGZpZy5jYXA9IlJPQyBjdXJ2ZXMgY29tcGFyaW5nIHRoZSBtb2RlbCBwZXJmb3JtYW5jZSBvZiB0aGUgdGhyZWUgY2FuZGlkYXRlIG1vZGVscy4ifQ0KIyMgcHJlZGljdCB0aGUgInN1Y2Nlc3MiIHByb2JhYmlsaXRpZXMgb2YgZWFjaCBtb2RlbCBiYXNlZCBvbiB0aGUgZW50aXJlIGRhdGEgc2V0DQpwcmVSZWR1Y2VkID0gcHJlZGljdChyZWR1Y2VkTW9kZWwsIG5ld2RhdGEgPSBJbXB1dGVkRnJhbWluZ2hhbSx0eXBlPSJyZXNwb25zZSIgKQ0KcHJlZGZ1bGxNb2RlbCA9IHByZWRpY3QoZnVsbE1vZGVsLCBuZXdkYXRhID0gSW1wdXRlZEZyYW1pbmdoYW0sdHlwZT0icmVzcG9uc2UiICkgDQpwcmVkZm9yd2FyZHMgPSBwcmVkaWN0KGZvcndhcmRzLCBuZXdkYXRhID0gSW1wdXRlZEZyYW1pbmdoYW0sdHlwZT0icmVzcG9uc2UiICkgDQojIw0KcHJlZGljdGlvbi5yZWR1Y2VkID0gcHJlUmVkdWNlZA0KcHJlZGljdGlvbi5mdWxsID0gcHJlZGZ1bGxNb2RlbA0KcHJlZGljdGlvbi5mb3J3YXJkcyA9IHByZWRmb3J3YXJkcyANCiAgY2F0ZWdvcnkgPSBJbXB1dGVkRnJhbWluZ2hhbSRUZW5ZZWFyQ0hEID09IDENCiAgUk9Db2JqLnJlZHVjZWQgPC0gcm9jKGNhdGVnb3J5LCBwcmVkaWN0aW9uLnJlZHVjZWQpDQogIFJPQ29iai5mdWxsIDwtIHJvYyhjYXRlZ29yeSwgcHJlZGljdGlvbi5mdWxsKQ0KICBST0NvYmouZm9yd2FyZHMgPC0gcm9jKGNhdGVnb3J5LCBwcmVkaWN0aW9uLmZvcndhcmRzKQ0KIyMgQVVDDQogIHJlZHVjZWRBVUMgPSBST0NvYmoucmVkdWNlZCRhdWMNCiAgZnVsbEFVQyA9IFJPQ29iai5mdWxsJGF1Yw0KICBmb3J3YXJkc0FVQyA9IFJPQ29iai5mb3J3YXJkcyRhdWMNCiMjIGV4dHJhY3Qgc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5IGZyb20gY2FuZGlkYXRlIG1vZGVscw0KICBzZW4ucmVkdWNlZCA9IFJPQ29iai5yZWR1Y2VkJHNlbnNpdGl2aXRpZXMNCiAgZm5yLnJlZHVjZWQgPSAxIC0gUk9Db2JqLnJlZHVjZWQkc3BlY2lmaWNpdGllcw0KICAjDQogIHNlbi5mdWxsID0gUk9Db2JqLmZ1bGwkc2Vuc2l0aXZpdGllcw0KICBmbnIuZnVsbCA9IDEgLSBST0NvYmouZnVsbCRzcGVjaWZpY2l0aWVzDQogICMNCiAgc2VuLmZvcndhcmRzID0gUk9Db2JqLmZvcndhcmRzJHNlbnNpdGl2aXRpZXMNCiAgZm5yLmZvcndhcmRzID0gMSAtIFJPQ29iai5mb3J3YXJkcyRzcGVjaWZpY2l0aWVzDQojIyBGb25kIGNvbnRyYXN0IGNvbG9yIGZvciBST0MgY3VydmVzDQogIGNvbG9ycyA9IGMoIiM4QjQ1MDAiLCAiIzAwMDA4QiIsICIjOEIwMDhCIikNCiMjIFBsb3R0aW5nIFJPQyBjdXJ2ZXMNCiNwYXIodHlwZT0icyIpDQpwbG90KGZuci5yZWR1Y2VkLCBzZW4ucmVkdWNlZCwgdHlwZSA9ICJsIiwgbHdkID0gMiwgY29sID0gY29sb3JzWzFdLA0KICAgICB4bGltID0gYygwLDEpLA0KICAgICB5bGltID0gYygwLDEpLA0KICAgICB4bGFiID0gIjEgLSBzcGVjaWZpY2l0eSIsDQogICAgIHlsYWIgPSAic2Vuc2l0aXZpdHkiLA0KICAgICBtYWluID0gIlJPQyBDdXJ2ZXMgb2YgQ2FuZGlkYXRlIE1vZGVscyIpDQpsaW5lcyhmbnIuZnVsbCwgc2VuLmZ1bGwsIGx3ZCA9IDIsIGx0eSA9IDIsIGNvbCA9IGNvbG9yc1syXSkNCmxpbmVzKGZuci5mb3J3YXJkcywgc2VuLmZvcndhcmRzLCBsd2QgPSAxLCBjb2wgPSBjb2xvcnNbM10pDQpzZWdtZW50cygwLDAsMSwxLCBsd2QgPTEsIGNvbCA9ICJyZWQiLCBsdHkgPSAyKQ0KbGVnZW5kKCJ0b3BsZWZ0IiwgYygicmVkdWNlZCIsICJmdWxsIiwgImZvcndhcmRzIiwgInJhbmRvbSBndWVzcyIpLCANCiAgICAgICBjb2w9Yyhjb2xvcnMsICJyZWQiKSwgbHdkPWMoMiwyLDEsMSksDQogICAgICAgbHR5PWMoMSwyLDEsMiksIGJ0eSA9ICJuIiwgY2V4ID0gMC43KQ0KIyMgYW5ub3RhdGluZyBBVUMNCnRleHQoMC44NywgMC4yNSwgcGFzdGUoIkFVQy5yZWR1Y2VkID0gIiwgcm91bmQocmVkdWNlZEFVQyw0KSksIGNvbD1jb2xvcnNbMV0sIGNleCA9IDAuNywgYWRqID0gMSkNCnRleHQoMC44NywgMC4yMCwgcGFzdGUoIkFVQy5mdWxsID0gIiwgcm91bmQoZnVsbEFVQyw0KSksIGNvbD1jb2xvcnNbMl0sIGNleCA9IDAuNywgYWRqID0gMSkNCnRleHQoMC44NywgMC4xNSwgcGFzdGUoIkFVQy5mb3J3YXJkcyA9ICIsIHJvdW5kKGZvcndhcmRzQVVDLDQpKSwgY29sPWNvbG9yc1szXSwgY2V4ID0gMC43LCBhZGogPSAxKQ0KYGBgDQoNClRoZSBhYm92ZSBST0NzIHNob3cgdGhhdCB0aGUgYGZ1bGxgIGFuZCBgZm9yd2FyZHNgIG1vZGVscyBoYXZlIHNpbWlsYXIgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZS4gU2luY2UgdGhlIGBmb3J3YXJkc2AgbW9kZWwgaGFzIGZld2VyIHZhcmlhYmxlcywgdGhlIGBmb3J3YXJkc2AgbW9kZWwgc2hvdWxkIGJlIHNlbGVjdGVkIGFzIHRoZSBmaW5hbCBtb2RlbCBmb3IgaW1wbGVtZW50YXRpb24uDQoNCioqUmVtYXJrcyoqOiBUaGUgcGVyZm9ybWFuY2UgbWV0cmljcyB1c2VkIGluIGNvbnN0cnVjdGluZyB0aGUgUk9DIGN1cnZlcyBhcmUgYmFzZWQgb24gcHJlZGljdGlvbiBlcnJvcnMgb2YgdGhlIG1vZGVsLiBST0MgY3VydmVzIGFuZCBBVUMgYXJlIHBlcmZvcm1hbmNlIG1ldHJpY3MgZm9yIHByZWRpY3RpdmUgbW9kZWxzLiBUaGUgbW9kZWwgd2l0aCB0aGUgYmlnZ2VzdCBBVUMgbWF5IG5vdCBiZSB0aGUgYmVzdCBtb2RlbCBmb3IgYXNzb2NpYXRpb24gYW5hbHlzaXMuDQoNCiMgUmVicmFuZGluZyBMb2dpc3RpYyBSZWdyZXNzaW9uDQoNClJlY2FsbCB0aGF0IHRoZSBhbmFseXRpYyBleHByZXNzaW9uIG9mIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGhhcyB0aGUgZm9sbG93aW5nIGV4cGxpY2l0IGV4cHJlc3Npb24uDQoNCiQkDQpQcihZID0gMSkgPSBcZnJhY3tcZXhwKHdfMCArIHdfMSB4XzEgKyBcY2RvdHMgKyB3X2sgeF9rKX17MSArIFxleHAod18wICsgd18xIHhfMSArIFxjZG90cyArIHdfayB4X2spfS4NCiQkDQoNClRoZSBkaWFncmFtbWF0aWMgcmVwcmVzZW50YXRpb24gb2YgdGhlIGFib3ZlIG1vZGVsIGlzIGdpdmVuIGJ5DQoNCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI5MCUiLCBmaWcuY2FwPSJGaWd1cmUgNi4gRGlhZ3JhbSBSZXByZXNlbnRhdGlvbiBvZiBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVscy4ifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL0xvZ2lzdGljTW9kZWxEaWFncmFtLmpwZyIpDQpgYGANCg0KVGhlIGFib3ZlIGRpYWdyYW0gb2YgdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgaXMgdGhlIGJhc2ljIHNpbmdsZSBsYXllciAqKnNpZ21vaWQqKiBuZXVyYWwgbmV0d29yayBtb2RlbCAtIHBlcmNlcHRyb24uDQoNCiMjIFNpbmdsZSBMYXllciBOZXVyYWwgTmV0d29yayAtIFBlcmNlcHRyb24NCg0KVGhlIHBlcmNlcHRyb24gaXMgYSBzdXBlcnZpc2VkIGxlYXJuaW5nIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG0sIG9yaWdpbmFsbHkgZGV2ZWxvcGVkIGJ5IEZyYW5rIFJvc2VuYmxhdHQgaW4gMTk1Ny4gSXQgaXMgYSB0eXBlIG9mIGFydGlmaWNpYWwgbmV1cmFsIG5ldHdvcmsuIEl0cyBhcmNoaXRlY3R1cmUgaXMgdGhlIHNhbWUgYXMgdGhlIGRpYWdyYW0gb2YgdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwuIFRoZSBtb3JlIGdlbmVyYWwNCg0KYGBge3IgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjcwJSIsIGZpZy5jYXA9IkZpZ3VyZSA3LiAgQXJjaGl0ZWN0dXJlIG9mIFNpbmdsZSBsYXllciBuZXVyYWwgbmV0d29yayBtb2RlbHMgKHBlcmNlcHRyb24pLiJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdzA3LVBlcmNlcHRyb25OZXR3b3JrLmpwZyIpDQpgYGANCg0KRWFjaCBpbnB1dCAkeF9pJCBoYXMgYW4gYXNzb2NpYXRlZCB3ZWlnaHQgJHdfaSQgKGxpa2UgcmVncmVzc2lvbiBjb2VmZmljaWVudCkuIFRoZSBzdW0gb2YgYWxsIHdlaWdodGVkIGlucHV0cywgJFxzdW1fe2k9MX1ebiB3X2l4X2kkICwgaXMgdGhlbiBwYXNzZWQgdGhyb3VnaCBhIG5vbmxpbmVhciBhY3RpdmF0aW9uIGZ1bmN0aW9uICRmKCkkLCB0byB0cmFuc2Zvcm0gdGhlIHByZS1hY3RpdmF0aW9uIGxldmVsIG9mIHRoZSBuZXVyb24gdG8gb3V0cHV0ICR5X2okLiBGb3Igc2ltcGxpY2l0eSwgdGhlIGJpYXMgdGVybSBpcyBzZXQgdG8gKiokd18wJCoqIHdoaWNoIGlzIGVxdWl2YWxlbnQgdG8gdGhlIGludGVyY2VwdCBvZiBhIHJlZ3Jlc3Npb24gbW9kZWwuDQoNClRvIHN1bW1hcml6ZSwgd2UgZXhwbGljaXRseSBsaXN0IHRoZSBtYWpvciBjb21wb25lbnRzIG9mIHBlcmNlcHRyb24gaW4gdGhlIGZvbGxvd2luZy4NCg0KLSAgICoqSW5wdXQgTGF5ZXIqKjogVGhlIGlucHV0IGxheWVyIGNvbnNpc3RzIG9mIG9uZSBvciBtb3JlIGlucHV0IG5ldXJvbnMsIHdoaWNoIHJlY2VpdmUgaW5wdXQgc2lnbmFscyBmcm9tIHRoZSBleHRlcm5hbCB3b3JsZCBvciBvdGhlciBsYXllcnMgb2YgdGhlIG5ldXJhbCBuZXR3b3JrLg0KDQotICAgKipXZWlnaHRzKio6IEVhY2ggaW5wdXQgbmV1cm9uIGlzIGFzc29jaWF0ZWQgd2l0aCBhIHdlaWdodCwgd2hpY2ggcmVwcmVzZW50cyB0aGUgc3RyZW5ndGggb2YgdGhlIGNvbm5lY3Rpb24gYmV0d2VlbiB0aGUgaW5wdXQgbmV1cm9uIGFuZCB0aGUgb3V0cHV0IG5ldXJvbi4NCg0KLSAgICoqQmlhcyoqOiBBIGJpYXMgdGVybSBpcyBhZGRlZCB0byB0aGUgaW5wdXQgbGF5ZXIgdG8gcHJvdmlkZSB0aGUgcGVyY2VwdHJvbiB3aXRoIGFkZGl0aW9uYWwgZmxleGliaWxpdHkgaW4gbW9kZWxpbmcgY29tcGxleCBwYXR0ZXJucyBpbiB0aGUgaW5wdXQgZGF0YS4NCg0KLSAgICoqQWN0aXZhdGlvbiBGdW5jdGlvbioqOiBUaGUgYWN0aXZhdGlvbiBmdW5jdGlvbiBkZXRlcm1pbmVzIHRoZSBvdXRwdXQgb2YgdGhlIHBlcmNlcHRyb24gYmFzZWQgb24gdGhlIHdlaWdodGVkIHN1bSBvZiB0aGUgaW5wdXRzIGFuZCB0aGUgYmlhcyB0ZXJtLiBDb21tb24gYWN0aXZhdGlvbiBmdW5jdGlvbnMgdXNlZCBpbiBwZXJjZXB0cm9ucyBpbmNsdWRlIHRoZSBgc3RlcCBmdW5jdGlvbmAsIGBzaWdtb2lkIGZ1bmN0aW9uYCwgYW5kIGBSZUxVIGZ1bmN0aW9uYCwgZXRjLg0KDQotICAgKipPdXRwdXQqKjogVGhlIG91dHB1dCBvZiB0aGUgcGVyY2VwdHJvbiBpcyBhIHNpbmdsZSBiaW5hcnkgdmFsdWUsIGVpdGhlciAwIG9yIDEsIHdoaWNoIGluZGljYXRlcyB0aGUgY2xhc3Mgb3IgY2F0ZWdvcnkgdG8gd2hpY2ggdGhlIGlucHV0IGRhdGEgYmVsb25ncy4NCg0KTm90ZSB0aGF0IHdoZW4gdGhlIHNpZ21vaWQgKGkuZS4sIGxvZ2lzdGljKSBmdW5jdGlvbi4NCg0KJCQNCmYoeCkgPSBcZnJhY3tcZXhwKHgpfXsxICsgXGV4cCh4KX0gPSBcZnJhY3sxfXsxK1xleHAoLXgpfS4NCiQkDQoNCmlzIHVzZWQgaW4gdGhlIHBlcmNlcHRyb24sIHRoZSBzaW5nbGUtbGF5ZXIgcGVyY2VwdGlvbiB3aXRoIGxvZ2lzdGljIGFjdGl2YXRpb24gaXMgZXF1aXZhbGVudCB0byB0aGUgYmluYXJ5IGxvZ2lzdGljIHJlZ3Jlc3Npb24uDQoNClwNCg0KKipSZW1hcmtzKio6DQoNCjEuICBUaGUgb3V0cHV0IG9mIHRoZSBhYm92ZSBwZXJjZXB0cm9uIG5ldHdvcmsgaXMgYmluYXJ5LCBpLmUuLCAkXGhhdHtZfSA9IDAkIG9yICQxJCBzaW5jZSBhbiBpbXBsaWNpdCBkZWNpc2lvbiBib3VuZGFyeSBiYXNlZCBvbiB0aGUgc2lnbiBvZiB0aGUgdmFsdWUgb2YgdGhlIHRyYW5zZmVyIGZ1bmN0aW9uICRcc3VtX3tpPTF9Xm0gd19peF9pICsgd18wJC4gSW4gdGhlIHNpZ21vaWQgcGVyY2VwdHJvbiBuZXR3b3JrLCB0aGlzIGlzIGVxdWl2YWxlbnQgdG8gc2V0dGluZyB0aGUgdGhyZXNob2xkIHByb2JhYmlsaXR5IHRvIDAuNS4gVG8gc2VlIHRoaXMsIG5vdCB0aGF0LCBpZiAkXHN1bV97aT0xfV5tIHdfaXhfaSArIHdfMCA9IDAkLCB0aGVuICQkDQogICAgUFxsZWZ0W1k9MSBcQmlnZ3wgXHN1bV97aT0xfV5tIHdfaXhfaSArIHdfMFxyaWdodF09XGZyYWN7MX17MStcZXhwXGxlZnRbLShcc3VtX3tpPTF9Xm0gd19peF9pICsgd18wKSBccmlnaHRdfSA9IFxmcmFjezF9ezErXGV4cCgwKX0gPSBcZnJhY3sxfXsyfQ0KICAgICQkDQoNCjIuICBJZiB0aGUgY3V0LW9mZiBwcm9iYWJpbGl0eSAkMC41JCBpcyB1c2VkIGluIHRoZSBsb2dpc3RpYyBwcmVkaWN0aXZlIG1vZGVsLCB0aGlzIGxvZ2lzdGljIHByZWRpY3RpdmUgbW9kZWwgaXMgZXF1aXZhbGVudCB0byB0aGUgcGVyY2VwdHJvbiB3aXRoIHNpZ21vaWQgYmVpbmcgdGhlIGFjdGl2YXRpb24gZnVuY3Rpb24uDQoNCjMuICBUaGVyZSBhcmUgc2V2ZXJhbCBvdGhlciBjb21tb25seSB1c2VkIGFjdGl2YXRpb24gZnVuY3Rpb25zIGluIHBlcmNlcHRyb24uIFRoZSBzaWdtb2lkIGFjdGl2YXRpb24gZnVuY3Rpb24gaXMgb25seSBvbmUgb2YgdGhlbS4gVGhpcyBpbXBsaWVzIHRoYXQgdGhlIGJpbmFyeSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGlzIGEgc3BlY2lhbCBwZXJjZXB0cm9uIG5ldHdvcmsgbW9kZWwuDQoNCiMjIENvbW1vbmx5IFVzZWQgQWN0aXZhdGlvbiBGdW5jdGlvbnMNCg0KVGhlIHNpZ21vaWQgZnVuY3Rpb24gaXMgb25seSBvbmUgb2YgdGhlIGFjdGl2YXRpb24gZnVuY3Rpb25zIHVzZWQgaW4gbmV1cmFsIG5ldHdvcmtzLiBUaGUgdGFibGUgYmVsb3cgbGlzdHMgc2V2ZXJhbCBvdGhlciBjb21tb25seSB1c2VkIGFjdGl2YXRpb24gZnVuY3Rpb25zIGluIG5ldXJhbCBuZXR3b3JrIG1vZGVsaW5nLg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iNjAlIiwgZmlnLmNhcD0iRmlndXJlIDkuICBQb3B1bGFyIGFjdGl2YXRpb24gZnVuY3Rpb25zIGluIG5ldXJhbCBuZXR3b3Jrcy4ifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL3cwNy1jb21tb25seVVzZWRBY3RpdmF0aW9uRnVucy5qcGciKQ0KYGBgDQoNCiMjIEFsZ29yaXRobXMgZm9yIEVzdGltYXRpbmcgV2VpZ2h0cw0KDQpXZSBrbm93IHRoYXQgdGhlIGVzdGltYXRpb24gb2YgdGhlIHJlZ3Jlc3Npb24gY29lZmZpY2llbnQgaW4gbG9naXN0aWMgcmVncmVzc2lvbiBpcyB0byBtYXhpbWl6ZSB0aGUgbGlrZWxpaG9vZCBmdW5jdGlvbiBkZWZpbmVkIGJhc2VkIG9uIHRoZSBiaW5vbWlhbCBkaXN0cmlidXRpb24uIEFsZ29yaXRobXMgc3VjaCBhcyBOZXd0b24gYW5kIGl0cyB2YXJpYW50cywgc2NvcmluZyBtZXRob2RzLCBldGMuIGFyZSB1c2VkIHRvIG9idGFpbiB0aGUgZXN0aW1hdGVkIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzLg0KDQpJbiBuZXVyYWwgbmV0d29yayBtb2RlbHMsIHRoZSB3ZWlnaHRzIGFyZSBlc3RpbWF0ZWQgYnkgbWluaW1pemluZyB0aGUgKipsb3NzIGZ1bmN0aW9uIChhbHNvIGNhbGxlZCBjb3N0IGZ1bmN0aW9uKSoqIHdoZW4gdHJhaW5pbmcgbmV1cmFsIG5ldHdvcmtzLiBUaGUgbG9zcyBmdW5jdGlvbiBjb3VsZCBiZSBkZWZpbmVkIGFzICoqbWVhbiBzcXVhcmUgZXJyb3IgKE1TRSkqKiBmb3IgcmVncmVzc2lvbiB0YXNrcy4NCg0KJCQNClx0ZXh0e0Vycm9yfSh3XzAsIHdfMSwgXGNkb3RzLCB3X2spID0gXGZyYWN7MX17bn1cc3VtX3tpPTF9Xm4gW1xoYXR7eX1faSAtICh3XzAgKyB3XzF4X3sxaX0gKyBcY2RvdHMgKyB3X2sgeF97a2l9KV1eMg0KJCQNCg0KRm9yIHRoZSBiaW5hcnkgY2xhc3NpZmljYXRpb24gdGFzaywgdGhlIGxvc3MgZnVuY3Rpb24gaXMgZGVmaW5lZCB0byBiZSAqKmNyb3NzLWVudHJvcHkgKGNlKSoqIHdpdGggdGhlIGZvbGxvd2luZyBleHBsaWNpdCBleHByZXNzaW9uDQoNCiQkDQpcdGV4dHtFcnJvcn0od18wLCB3XzEsIFxjZG90cywgd19rKSA9IC1cZnJhY3tcc3VtX3tpPTF9Xk5beV9pXGxvZyhwX2kpICsgKDEteV9pKVxsb2coMS1wX2kpXX17Tn0uDQokJA0KDQp3aGVyZQ0KDQokJA0KcF9pID0gXGZyYWN7XGV4cCh3XzAgKyB3XzF4X3sxaX0gKyBcY2RvdHMgKyB3X2sgeF97a2l9KX17MSArIFxleHAod18wICsgd18xeF97MWl9ICsgXGNkb3RzICsgd19rIHhfe2tpfSl9Lg0KJCQNCg0KTGVhcm5pbmcgYWxnb3JpdGhtcyAqKmZvcndhcmQgYW5kIGJhY2t3YXJkIHByb3BhZ2F0aW9uKiogdGhhdCBkZXBlbmQgb24gZWFjaCBvdGhlciBhcmUgdXNlZCBpbiBtaW5pbWl6aW5nIHRoZSB1bmRlcmx5aW5nICoqbG9zcyBmdW5jdGlvbioqLg0KDQotICAgKipGb3J3YXJkIHByb3BhZ2F0aW9uKiogaXMgd2hlcmUgaW5wdXQgZGF0YSBpcyBmZWQgdGhyb3VnaCBhIG5ldHdvcmssIGluIGEgZm9yd2FyZCBkaXJlY3Rpb24sIHRvIGdlbmVyYXRlIGFuIG91dHB1dC4gVGhlIGRhdGEgaXMgYWNjZXB0ZWQgYnkgaGlkZGVuIGxheWVycyBhbmQgcHJvY2Vzc2VkLCBhcyBwZXIgdGhlIGFjdGl2YXRpb24gZnVuY3Rpb24sIGFuZCBtb3ZlcyB0byB0aGUgc3VjY2Vzc2l2ZSBsYXllci4gRHVyaW5nIGZvcndhcmQgcHJvcGFnYXRpb24sIHRoZSBhY3RpdmF0aW9uIGZ1bmN0aW9uIGlzIGFwcGxpZWQsIGJhc2VkIG9uIHRoZSB3ZWlnaHRlZCBzdW0sIHRvIG1ha2UgdGhlIG5ldXJhbCBuZXR3b3JrIGZsb3cgbm9uLWxpbmVhcmx5IHVzaW5nIGJpYXMuIEZvcndhcmQgcHJvcGFnYXRpb24gaXMgdGhlIHdheSBkYXRhIG1vdmVzIGZyb20gbGVmdCAoaW5wdXQgbGF5ZXIpIHRvIHJpZ2h0IChvdXRwdXQgbGF5ZXIpIGluIHRoZSBuZXVyYWwgbmV0d29yay4NCg0KLSAgICoqQmFja3Byb3BhZ2F0aW9uKiogaXMgdXNlZCB0byBpbXByb3ZlIHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IG9mIGEgbm9kZSBpcyBleHByZXNzZWQgYXMgYSBsb3NzIGZ1bmN0aW9uIG9yIGVycm9yIHJhdGUuIEJhY2twcm9wYWdhdGlvbiBjYWxjdWxhdGVzIHRoZSBzbG9wZSBvZiAoZ3JhZGllbnQpIGEgKipsb3NzIGZ1bmN0aW9uKiogb2Ygb3RoZXIgd2VpZ2h0cyBpbiB0aGUgbmV1cmFsIG5ldHdvcmsgYW5kIHVwZGF0ZXMgdGhlIHdlaWdodHMgdXNpbmcgZ3JhZGllbnQgZGVzY2VudCB0aHJvdWdoIHRoZSBsZWFybmluZyByYXRlLg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iNjAlIiwgZmlnLmNhcD0iRmlndXJlIDEwLiAgVXBkYXRpbmcgd2VpZ2h0cyB3aXRoIGJhY2twcm9wYWdhdGlvbiBhbGdvcml0aG0uIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy93MDctYmFja3Byb3BhZ2F0aW9uR3JhZGllbnQuanBnIikNCmBgYA0KDQpUaGUgZ2VuZXJhbCBhcmNoaXRlY3R1cmUgb2YgdGhlIGJhY2twcm9wYWdhdGlvbiBuZXR3b3JrIG1vZGVsIGlzIGRlcGljdGVkIGluIHRoZSBmb2xsb3dpbmcgZGlhZ3JhbS4NCg0KYGBge3IgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjYwJSIsIGZpZy5jYXA9IkZpZ3VyZSAxMS4gIFRoZSBpZGVhIG9mIGJhY2twcm9wYWdhdGlvbiBuZXVyYWwgbmV0d29ya3MuIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy93MDctYmFja3Byb3BhZ2F0aW9uTk4uanBnIikNCmBgYA0KDQpUaGUgYWxnb3JpdGhtIG9mIGJhY2twcm9wYWdhdGlvbiBpcyBub3QgdXNlZCBpbiBjbGFzc2ljYWwgc3RhdGlzdGljcy4gVGhpcyBpcyB3aHkgdGhlIG5ldXJhbCBuZXR3b3JrIG1vZGVsIG91dHBlcmZvcm1lZCB0aGUgY2xhc3NpY2FsIGxvZ2lzdGljIG1vZGVsIGluIHRlcm1zIG9mIHByZWRpY3RpdmUgcG93ZXIuDQoNClRoZSBSIGxpYnJhcnkgYG5ldXJhbG5ldGAgaGFzIHRoZSBmb2xsb3dpbmcgZml2ZSBhbGdvcml0aG1zOg0KDQoqKmJhY2twcm9wKiogLSB0cmFkaXRpb25hbCBgYmFja3Byb3BhZ2F0aW9uYC4NCg0KKipycHJvcCsqKiAtIHJlc2lsaWVudCBiYWNrcHJvcGFnYXRpb24gd2l0aCB3ZWlnaHQgYmFja3RyYWNraW5nLg0KDQoqKnJwcm9wLSoqIC0gcmVzaWxpZW50IGJhY2twcm9wYWdhdGlvbiB3aXRob3V0IHdlaWdodCBiYWNrdHJhY2tpbmcuDQoNCioqc2FnKiogLSBtb2RpZmllZCBnbG9iYWxseSBjb252ZXJnZW50IGFsZ29yaXRobSAoZ3ItcHJvcCkgd2l0aCB0aGUgc21hbGxlc3QgYWJzb2x1dGUgZ3JhZGllbnQuDQoNCioqc2xyKiogLSBtb2RpZmllZCBnbG9iYWxseSBjb252ZXJnZW50IGFsZ29yaXRobSAoZ3ItcHJvcCkgd2l0aCB0aGUgc21hbGxlc3QgbGVhcm5pbmcgcmF0ZS4NCg0KQWx0aG91Z2ggaXQgaXMgbm90IHJlcXVpcmVkLCBzY2FsaW5nIGNhbiBpbXByb3ZlIHRoZSBwZXJmb3JtYW5jZSBvZiBuZXVyYWwgbmV0d29yayBtb2RlbHMuIFRoZXJlIGFyZSBkaWZmZXJlbnQgdHlwZXMgb2Ygc2NhbGluZyBhbmQgc3RhbmRhcmRpemF0aW9uLiBUaGUgZm9sbG93aW5nIHNjYWxpbmcgaXMgY29tbW9ubHkgdXNlZCBpbiBwcmFjdGljZS4NCg0KJCQNClx0ZXh0e3NjYWxlZC52YXJ9ID0gXGZyYWN7XHRleHR7b3JpZy52YXJ9IC0gXG1pbihcdGV4dHtvcmlnLnZhcn0pfXtcbWF4KFx0ZXh0e29yaWcudmFyfSktXG1pbihcdGV4dHtvcmlnLnZhcn0pfS4NCiQkDQoNClwNCg0KIyMgSW1wbGVtZW50aW5nIE5OIHdpdGggUg0KDQpTZXZlcmFsIFIgbGlicmFyaWVzIGNhbiBydW4gbmV1cmFsIG5ldHdvcmsgbW9kZWxzLiBgbm5ldGAgaXMgdGhlIHNpbXBsZXN0IG9uZSB0aGF0IG9ubHkgaW1wbGVtZW50cyBzaW5nbGUtbGF5ZXIgbmV0d29ya3MuIGBuZXVyYWxuZXRgIGNhbiBydW4gYm90aCBzaW5nbGUtbGF5ZXIgYW5kIG11bHRpcGxlLWxheWVyIG5ldXJhbCBuZXR3b3Jrcy4gYFJTTk5TYCAoUiBTdHV0dGdhcnQgTmV1cmFsIE5ldHdvcmsgU2ltdWxhdG9yKSBpcyBhIHdyYXBwZXIgb2YgbXVsdGlwbGUgUiBsaWJyYXJpZXMgdGhhdCBpbXBsZW1lbnRzIGRpZmZlcmVudCBuZXR3b3JrIG1vZGVscy4NCg0KIyMjICoqU3ludGF4IG9mIGBuZXVyYWxuZXRgKioNCg0KV2UgdXNlIGBuZXVyYWxuZXRgIGxpYnJhcnkgdG8gcnVuIHRoZSBuZXVyYWwgbmV0d29yayBtb2RlbCBpbiB0aGUgZXhhbXBsZSAoY29kZSBmb3IgaW5zdGFsbGluZyBhbmQgbG9hZGluZyB0aGlzIGxpYnJhcnkgaXMgcGxhY2VkIGluIHRoZSBzZXR1cCBjb2RlIGNodW5rKS4NCg0KVGhlIHN5bnRheCBvZiBgbmV1cmFsbmV0KClgIGlzIGdpdmVuIGJlbG93DQoNCmBgYCAgICAgICAgIA0KIG5ldXJhbG5ldChmb3JtdWxhLCAgICAgICANCiAgICAgICAgICAgZGF0YSwgDQogICAgICAgICAgIGhpZGRlbiA9IDEsICAgIA0KICAgICAgICAgICB0aHJlc2hvbGQgPSAwLjAxLCANCiAgICAgICAgICAgc3RlcG1heCA9IDFlKzA1LCANCiAgICAgICAgICAgcmVwID0gMSwgDQogICAgICAgICAgIHN0YXJ0d2VpZ2h0cyA9IE5VTEwsDQogICAgICAgICAgIGxlYXJuaW5ncmF0ZS5saW1pdCA9IE5VTEwsDQogICAgICAgICAgIGxlYXJuaW5ncmF0ZS5mYWN0b3IgPWxpc3QobWludXMgPSAwLjUsIHBsdXMgPSAxLjIpLA0KICAgICAgICAgICBsZWFybmluZ3JhdGU9TlVMTCwgDQogICAgICAgICAgIGxpZmVzaWduID0gIm5vbmUiLA0KICAgICAgICAgICBsaWZlc2lnbi5zdGVwID0gMTAwMCwgDQogICAgICAgICAgIGFsZ29yaXRobSA9ICJycHJvcCsiLA0KICAgICAgICAgICBlcnIuZmN0ID0gInNzZSIsIA0KICAgICAgICAgICBhY3QuZmN0ID0gImxvZ2lzdGljIiwNCiAgICAgICAgICAgbGluZWFyLm91dHB1dCA9IFRSVUUsIA0KICAgICAgICAgICBleGNsdWRlID0gTlVMTCwNCiAgICAgICAgICAgY29uc3RhbnQud2VpZ2h0cyA9IE5VTEwsIA0KICAgICAgICAgICBsaWtlbGlob29kID0gRkFMU0UpDQpgYGANCg0KVGhlIGRldGFpbGVkIGBoZWxwIGRvY3VtZW50YCBjYW4gYmUgZm91bmQgYXQgPGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9uZXVyYWxuZXQvdmVyc2lvbnMvMS40NC4yL3RvcGljcy9uZXVyYWxuZXQ+Lg0KDQpUaGUgZnVuY3Rpb24gaXMgZmFpcmx5IGZsZXhpYmxlIGFuZCBhbGxvd3MgZGlmZmVyZW50IGxvc3MgZnVuY3Rpb25zLCBtZXRob2RzIG9mIGVzdGltYXRpb24sIGFuZCBkaWZmZXJlbnQgdHlwZXMgb2Ygb3V0cHV0cy4gVGhlIGF1dGhvcnMgYWxzbyByZXF1aXJlZCBzY2FsZWQgZmVhdHVyZXMgYW5kIHRoZSBleHBsaWNpdCBkZWZpbml0aW9uIGZvciBkdW1teSB2YXJpYWJsZXMgZGVyaXZlZCBmcm9tIHVuZGVybHlpbmcgY2F0ZWdvcmljYWwgZmVhdHVyZSB2YXJpYWJsZXMuDQoNCg0KXA0KDQojIyMgRmVhdHVyZSBDb252ZXJzaW9uIGFuZCBNb2RlbCBGb3JtdWxhDQoNCldoZW4gdXNpbmcgYG5ldXJhbG5ldCgpYCwgd2Ugc2hvdWxkIGtlZXAgdGhlIGZvbGxvd2luZyBpbiBtaW5kIHdoZW4gcHJlcGFyaW5nIGRhdGEgZm9yIHRoZSBhbGdvcml0aG0uDQoNCi0gICBgbmV1cmFsbmV0KClgIHJlcXVpcmVzIGFsbCBmZWF0dXJlcyB0byBiZSBpbiB0aGUgKipudW1lcmljIGZvcm0gKGR1bW15IHZhcmlhYmxlIGZvciBjYXRlZ29yaWNhbCBmZWF0dXJlcywgbm9ybWFsaXphdGlvbiBvZiBudW1lcmljYWwgZmVhdHVyZXMpKiouDQoNCi0gICBUaGUgbW9kZWwgZm9ybXVsYSBpbiBgbmV1cmFsbmV0KClgIHJlcXVpcmVzIGR1bW15IHZhcmlhYmxlcyB0byBiZSAqKmV4cGxpY2l0bHkgZGVmaW5lZCoqLg0KDQotICAgSXQgaXMgYWxzbyBoaWdobHkgcmVjb21tZW5kZWQgdG8gc2NhbGUgYWxsIG51bWVyaWNhbCBmZWF0dXJlcyBiZWZvcmUgYmVpbmcgaW5jbHVkZWQgaW4gdGhlIG5ldHdvcmsgbW9kZWwuDQoNCi0gICBFeHRyYWN0IGFsbCBmZWF0dXJlIG5hbWVzIChudW1lcmljIGFuZCBhbGwgZHVtbXkgdmFyaWFibGVzKSBhbmQgd3JpdGUgdGhlbSBpbiB0aGUgbW9kZWwgZm9ybXVsYSBsaWtlIHRoZSBvbmUgaW4gYGdsbWA6IGByZXNwb25zZSB+IHZhci4xICsgdmFyLjIgKyAuLi4gK3Zhci5rYCwNCg0KXA0KDQojIEEgQ2FzZSBTdHVkeQ0KDQpGb3IgaWxsdXN0cmF0aW9uLCB3ZSB3aWxsIHVzZSB0aGUgYmVzdCBtb2RlbCBzZWxlY3RlZCBmcm9tIHRoZSBwcmV2aW91cyBzZWN0aW9uIHRvIGJ1aWxkIGEgc2luZ2xlLWxheWVyIG5ldXJhbCBuZXR3b3JrIG1vZGVsIChwZXJjZXB0cm9uKSB1c2luZyB0aGUgRnJhbWluZ2hhbSBDSEQgZGF0YS4gU2luY2UgdGhlIG9yaWdpbmFsIGRhdGEgc2V0IGhhcyBiZWVuIGZlYXR1cmUgZW5naW5lZXJlZCwgd2Ugd2lsbCBpbGx1c3RyYXRlIHRoZSBzdGVwcyBmb3IgcHJlcGFyaW5nIHRoZSBkYXRhIGZvciB0aGUgYG5ldXJhbG5ldCgpYCBmdW5jdGlvbiBiYXNlZCBvbiB0aGUgKiplbmdpbmVlcmVkIGRhdGEgc2V0KiouDQoNCiMjIFN1YnNldHRpbmcgYW5kIFNjYWxpbmcgRGF0YQ0KDQpXZSBmaXJzdCBzdWJzZXQgdGhlIGRhdGEgd2l0aCBvbmx5IHZhcmlhYmxlcyB1c2VkIGluIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsICh0aGUgZnVsbCBtb2RlbCkgYW5kIHRoZW4gc2NhbGUgYWxsIG51bWVyaWNhbCB2YXJpYWJsZXMuIFRoZSBhYm92ZSBzdWdnZXN0ZWQgKiptaW4tbWF4IHNjYWxpbmcqKiBtZXRob2Qgd2lsbCBiZSB1c2VkIGluIHRoaXMgY2FzZSBzdHVkeSAod2hpY2ggaXMgcmVmbGVjdGVkIGluIHRoZSBmb2xsb3dpbmcgY29kZSkuDQoNCmBgYHtyfQ0KZnVsbE1vZGVsTmFtZXM9YygicHJldmFsZW50U3Ryb2tlIiwgIkJNSSIsIkJQTWVkcyIsInRvdENob2wiLCJhZ2UiLCAiY3VycmVudFNtb2tlciIsIlNtb2tlckNsYXNzIiwicHJldmFsZW50SHlwIiwiZ2x1Y29zZSIsImRpYUJQIiwiZGlhYmV0ZXMiLCAibWFsZSIsInN5c0JQIiwiaGVhcnRSYXRlIiwgIlRlblllYXJDSEQiKQ0KbmV1cmFsRGF0YSA9IEltcHV0ZWRGcmFtaW5naGFtWywgZnVsbE1vZGVsTmFtZXNdDQojIyBmZWF0dXJlIHNjYWxpbmcNCm5ldXJhbERhdGEkQk1Jc2NhbGUgPSAobmV1cmFsRGF0YSRCTUktbWluKG5ldXJhbERhdGEkQk1JKSkvKG1heChuZXVyYWxEYXRhJEJNSSkgLSBtaW4obmV1cmFsRGF0YSRCTUkpKQ0KbmV1cmFsRGF0YSR0b3RDaG9sc2NhbGUgPSAobmV1cmFsRGF0YSR0b3RDaG9sLW1pbihuZXVyYWxEYXRhJHRvdENob2wpKS8obWF4KG5ldXJhbERhdGEkdG90Q2hvbCkgLSBtaW4obmV1cmFsRGF0YSR0b3RDaG9sKSkNCm5ldXJhbERhdGEkYWdlc2NhbGUgPSAobmV1cmFsRGF0YSRhZ2UtbWluKG5ldXJhbERhdGEkYWdlKSkvKG1heChuZXVyYWxEYXRhJGFnZSkgLSBtaW4obmV1cmFsRGF0YSRhZ2UpKSANCm5ldXJhbERhdGEkZ2x1Y29zZXNjYWxlID0gKG5ldXJhbERhdGEkZ2x1Y29zZS1taW4obmV1cmFsRGF0YSRnbHVjb3NlKSkvKG1heChuZXVyYWxEYXRhJGdsdWNvc2UpIC0gbWluKG5ldXJhbERhdGEkZ2x1Y29zZSkpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpuZXVyYWxEYXRhJGRpYUJQY2FsZSA9IChuZXVyYWxEYXRhJGRpYUJQLW1pbihuZXVyYWxEYXRhJGRpYUJQKSkvKG1heChuZXVyYWxEYXRhJGRpYUJQKSAtIG1pbihuZXVyYWxEYXRhJGRpYUJQKSkgIA0KbmV1cmFsRGF0YSRzeXNCUHNjYWxlID0gKG5ldXJhbERhdGEkc3lzQlAtbWluKG5ldXJhbERhdGEkc3lzQlApKS8obWF4KG5ldXJhbERhdGEkc3lzQlApIC0gbWluKG5ldXJhbERhdGEkc3lzQlApKSAgDQpuZXVyYWxEYXRhJGhlYXJ0UmF0ZXNjYWxlID0gKG5ldXJhbERhdGEkaGVhcnRSYXRlLW1pbihuZXVyYWxEYXRhJGhlYXJ0UmF0ZSkpLyhtYXgobmV1cmFsRGF0YSRoZWFydFJhdGUpIC0gbWluKG5ldXJhbERhdGEkaGVhcnRSYXRlKSkgIA0KIyMgZHJvcCBvcmlnaW5hbCBmZWF0dXJlIC0ga2VlcGluZyBvbmx5IGZlYXR1cmVzIHRvIGJlIHVzZWQgaW4gdGhlIG5ldXJhbCBuZXR3b3JrDQpBTk5Nb2RlbE5hbWVzPWMoInByZXZhbGVudFN0cm9rZSIsICJCUE1lZHMiLCAiY3VycmVudFNtb2tlciIsIlNtb2tlckNsYXNzIiwgInByZXZhbGVudEh5cCIsICJkaWFiZXRlcyIsIm1hbGUiLCJCTUlzY2FsZSIsInRvdENob2xzY2FsZSIsImFnZXNjYWxlIiwiZ2x1Y29zZXNjYWxlIiwgImRpYUJQY2FsZSIsInN5c0JQc2NhbGUiLCAiaGVhcnRSYXRlc2NhbGUiLCJUZW5ZZWFyQ0hEIikNCiMjIGZpbmFsIGRhdGEgZm9yIHRoZSBuZXVyYWxuZXQoKSBmdW5jdGlvbg0KbmV1cmFsRGF0YUZpbmFsID0gbmV1cmFsRGF0YVssQU5OTW9kZWxOYW1lc10NCmBgYA0KDQojIyBDcmVhdGluZyBNb2RlbCBGb3JtdWxhDQoNCkluc3RlYWQgb2Ygd3JpdGluZyB0aGUgZm9ybXVsYSBleHBsaWNpdGx5LCB3ZSB1c2UgdGhlIFIgZnVuY3Rpb24gYG1vZGVsLm1hdHJpeGAgdG8gY3JlYXRlIHRoZSBtb2RlbCBmb3JtdWxhIGZvciBgbmV1cmFsbmV0KClgLiBUaGlzIG1ldGhvZCBhbGxvd3MgdXMgdG8gZ2VuZXJhbGl6ZSB0aGUgY2FzZXMgd2l0aCBtYW55IHZhcmlhYmxlcyBpbiB3aGljaCB0aGUgZXhwbGljaXQgZXhwcmVzc2lvbiBpcyBub3QgcHJhY3RpY2FsbHkgZmVhc2libGUuIEZvciBpbGx1c3RyYXRpb24sIHdlIHdpbGwgZGVmaW5lIHRoZSBmb3JtdWxhIGltcGxpY2l0bHkgYW5kIGV4cGxpY2l0bHkuDQoNClwNCg0KIyMjIEltcGxpY2l0bHkgRGVmaW5pdGlvbg0KDQpUaGUgZm9sbG93aW5nIGNvZGUgZXhwbGljaXRseSBkZWZpbmVzIHRoZSBkdW1teSB2YXJpYWJsZXMgdG8gYmUgdXNlZCBpbiB0aGUgZmluYWwgbW9kZWwgZm9ybXVsYS4NCg0KYGBgICAgICAgICAgDQpuZXVyYWxNb2RlbEZvcm11bGEgPSBtb2RlbC5tYXRyaXgofnByZXZhbGVudFN0cm9rZSsgQlBNZWRzKyBjdXJyZW50U21va2VyK1Ntb2tlckNsYXNzKyBwcmV2YWxlbnRIeXArIGRpYWJldGVzK21hbGUrIEJNSXNjYWxlK3RvdENob2xzY2FsZSthZ2VzY2FsZStnbHVjb3Nlc2NhbGUrZGlhQlBjYWxlK3N5c0JQc2NhbGUgKyBoZWFydFJhdGVzY2FsZSwgZGF0YSA9IG5ldXJhbERhdGFGaW5hbCkNCiMgVGhlIGZvbGxvd2luZyB3aWxsIGxpc3QgYWxsIG51bWVyaWNhbCB2YXJpYWJsZXMgYW5kIGF1dG9tYXRpY2FsbHkgZGVyaXZlZCBkdW1teSB2YXJpYWJsZXMNCmNvbG5hbWVzKG5ldXJhbE1vZGVsRm9ybXVsYSkgDQpgYGANCg0KYGBge3J9DQpuZXVyYWxNb2RlbERlc2lnbk1hdHJpeCA9IG1vZGVsLm1hdHJpeCh+cHJldmFsZW50U3Ryb2tlKyBCUE1lZHMrIGN1cnJlbnRTbW9rZXIrU21va2VyQ2xhc3MrIHByZXZhbGVudEh5cCsgZGlhYmV0ZXMrbWFsZSsgQk1Jc2NhbGUrdG90Q2hvbHNjYWxlK2FnZXNjYWxlK2dsdWNvc2VzY2FsZStkaWFCUGNhbGUrc3lzQlBzY2FsZSArIGhlYXJ0UmF0ZXNjYWxlICsgVGVuWWVhckNIRCwgZGF0YSA9IG5ldXJhbERhdGFGaW5hbCkNCiMgVGhlIGZvbGxvd2luZyB3aWxsIGxpc3QgYWxsIG51bWVyaWNhbCB2YXJpYWJsZXMgYW5kIGF1dG9tYXRpY2FsbHkgZGVyaXZlZCBkdW1teSB2YXJpYWJsZXMNCmNvbG5hbWVzKG5ldXJhbE1vZGVsRGVzaWduTWF0cml4KSANCmBgYA0KDQpEdW1teSB2YXJpYWJsZXMgYFNtb2tlckNsYXNzbGlnaHRgLCBgU21va2VyQ2xhc3Ntb2RlcmF0ZWAsIGFuZCBgU21va2VyQ2xhc3Nub25lYCBkdW1teSB2YXJpYWJsZXMgYXJlIGRlZmluZWQgYmFzZWQgb24gdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlIGAiU21va2VyQ2xhc3NgLiBUaGUgb2JqZWN0IGluIHRoZSBhYm92ZSBjb2RlIGRlZmluZXMgdGhlIGRlc2lnbiBtYXRyaXggd2hpY2ggd2lsbCBiZSB1c2VkIGluIHRoZSB1bmRlcmx5aW5nIG1vZGVsLg0KDQojIyMgSW1wbGljaXQgRGVmaW5pdGlvbg0KDQpUbyB1c2UgdGhlIGltcGxpY2l0IG1ldGhvZCwgd2UgbmVlZCB0byBjcmVhdGUgYSBkYXRhIGZyYW1lIHRoYXQgY29udGFpbnMgdGhlIGZlYXR1cmUgdmFyaWFibGVzIHRvIGJlIGluY2x1ZGVkIGluIHRoZSBuZXVyYWwgbmV0d29yayBtb2RlbC4gU2luY2UgdGhlIGBuZXVyYWxEYXRhYCBkYXRhIHNldCBjb250YWlucyBhbGwgZmVhdHVyZSB2YXJpYWJsZXMgYW5kIHRoZSByZXNwb25zZSB2YXJpYWJsZSwgd2UgbmVlZCB0byBkcm9wIHRoZSByZXNwb25zZSB2YXJpYWJsZSBhbmQgdGhlbiB1c2UgdGhlIHNob3J0LWN1dCBpbXBsaWNpdCBtZXRob2QuDQoNCmBgYCAgICAgICAgIA0KaW1wbGljaXRGb3JtdWxhID0gbW9kZWwubWF0cml4KCB+LiwgZGF0YSA9IG5ldXJhbERhdGFGaW5hbCkNCmNvbG5hbWVzKGltcGxpY2l0Rm9ybXVsYSkgDQpgYGANCg0KYGBge3J9DQppbXBsaWNpdEZvcm11bGFEZXNpZ25NYXRyaXggPSBtb2RlbC5tYXRyaXgoIH4uLCBkYXRhID0gbmV1cmFsRGF0YUZpbmFsKQ0KY29sbmFtZXMoaW1wbGljaXRGb3JtdWxhRGVzaWduTWF0cml4KSANCmBgYA0KDQpUaGUgYWJvdmUgY29kZSBwcm9kdWNlcyB0aGUgc2FtZSBzZXQgb2YgZmVhdHVyZXMgaW5jbHVkaW5nIGF1dG9tYXRpY2FsbHkgZGVmaW5lZCBkdW1teSB2YXJpYWJsZXMuDQoNCiMjIyBFbnNlbWJsZSBNb2RlbCBGb3JtdWxhDQoNCkFmdGVyIGRlZmluaW5nIHRoZSBkZXNpZ24gbWF0cml4IHdpdGggZXhwbGljaXRseSBkZWZpbmVkIGR1bW15IHZhcmlhYmxlcyBiYXNlZCBvbiB0aGUgY29ycmVzcG9uZGluZyBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsIHdlIHVzZSB0aGUgc3RyaW5nIGZ1bmN0aW9uIHRvIGRlZmluZSB0aGUgbW9kZWwgZm9ybXVsYS4gU2luY2UgYm90aCBpbXBsaWNpdCBhbmQgZXhwbGljaXQgbWV0aG9kcyBhcmUgZXF1aXZhbGVudCwgd2UgdXNlIHRoZSBsaXN0IG9mIHZhcmlhYmxlcyB0byBkZWZpbmUgdGhlIGZpbmFsIG1vZGVsIGZvcm11bGEuDQoNCk5vdGUgdGhhdCB0aGUgZmlyc3QgY29sdW1uIG9mIHRoZSBtb2RlbCBtYXRyaXggY29ycmVzcG9uZHMgdG8gdGhlIGludGVyY2VwdCAoYmlhcyBpbiBuZXVyYWwgbmV0d29yayBhbGdvcml0aG0pIGFuZCB0aGUgbGFzdCBjb2x1bW4gaXMgdGhlIHJlc3BvbnNlIHZhcmlhYmxlLiBXZSBldmVudHVhbGx5IHdhbnQgdG8gY3JlYXRlIGEgbW9kZWwgZm9ybXVsYSBpbiB0aGUgZm9ybSBzaW1pbGFyIHRvIGByZXNwb25zZSB+IHZhci4xICsgdmFyLjIgKyAuLi4gKyB2YXIubWAuDQoNCmBwYXN0ZSgpYCBpcyBhIHBvd2VyZnVsIHN0cmluZyBmdW5jdGlvbi4gV2Ugd2lsbCB1c2UgaXQgdG8gZGVmaW5lIHRoZSBtb2RlbCBmb3JtdWxhIHVzaW5nIHRoZSBmb2xsb3dpbmcgY29kZS4NCg0KYGBgICAgICAgICAgDQpjb2x1bW5OYW1lcyA9IGNvbG5hbWVzKGltcGxpY2l0Rm9ybXVsYURlc2lnbk1hdHJpeCkNCmNvbHVtbkxpc3QgPSBwYXN0ZShjb2x1bW5OYW1lc1stYygxLGxlbmd0aChjb2x1bW5OYW1lcykpXSwgY29sbGFwc2UgPSAiKyIpDQpjb2x1bW5MaXN0ID0gcGFzdGUoYyhjb2x1bW5OYW1lc1tsZW5ndGgoY29sdW1uTmFtZXMpXSwifiIsY29sdW1uTGlzdCksIGNvbGxhcHNlPSIiKQ0KbW9kZWxGb3JtdWxhID0gZm9ybXVsYShjb2x1bW5MaXN0KQ0KbW9kZWxGb3JtdWxhDQpgYGANCg0KYGBge3J9DQpjb2x1bW5OYW1lcyA9IGNvbG5hbWVzKGltcGxpY2l0Rm9ybXVsYURlc2lnbk1hdHJpeCkNCmNvbHVtbkxpc3QgPSBwYXN0ZShjb2x1bW5OYW1lc1stYygxLGxlbmd0aChjb2x1bW5OYW1lcykpXSwgY29sbGFwc2UgPSAiKyIpDQpjb2x1bW5MaXN0ID0gcGFzdGUoYyhjb2x1bW5OYW1lc1tsZW5ndGgoY29sdW1uTmFtZXMpXSwifiIsY29sdW1uTGlzdCksIGNvbGxhcHNlPSIiKQ0KbW9kZWxGb3JtdWxhID0gZm9ybXVsYShjb2x1bW5MaXN0KQ0KbW9kZWxGb3JtdWxhDQpgYGANCg0KVGhlIGFib3ZlIGZvcm11bGEgd2lsbCB1c2VkIGluIGBuZXVyYWxuZXQoKWAuDQoNClwNCg0KIyMjIEJ1aWxkaW5nIFBlcmNlcHRyb24gTW9kZWwNCg0KVGhlIGBuZXVyYWxuZXQoKWAgZnVuY3Rpb24gcHJvdmlkZXMgbWFueSBhcmd1bWVudHMgKGFsc28gY2FsbGVkICoqKmh5cGVycGFyYW1ldGVycyopLiBJbiB0aGlzIGNsYXNzLCB3ZSB3aWxsIG5vdCBmb2N1cyBvbiB0dW5pbmcgdGhlc2UgaHlwZXJwYXJhbWV0ZXJzIHRvIGZpbmQgYSBtb2RlbCB3aXRoIGFuIG9wdGltYWwgcGVyZm9ybWFuY2UuIFRoZSBvYmplY3RpdmUgaXMgdG8gZ2FpbiBhIGJhc2ljIGtub3dsZWRnZSBvZiB0aGUgYXJjaGl0ZWN0dXJlIG9mIG5ldXJhbCBuZXR3b3JrIG1vZGVscy4gV2Ugd2lsbCB1c2UgdGhlIGRlZmF1bHQgYXJndW1lbnRzIHByb3ZpZGVkIGluIHRoZSBmdW5jdGlvbi4gDQoNClJlY2FsbCB0aGF0IHRoZSBwZXJjZXB0cm9uIG1vZGVsIGJlIHVzZWQgZm9yIGJvdGggcmVncmVzc2lvbiBhbmQgY2xhc3NpZmljYXRpb24uIFRoZSBhcmd1bWVudCBgbGluZWFyLm91dHB1dGAgbmVlZHMgdG8gYmUgc3BlY2lmaWVkIGNvcnJlY3RseSB0byBwZXJmb3JtIHRoZSBhcHByb3ByaWF0ZSBtb2RlbGluZy4NCg0KKiBXaGVuIHBlcmZvcm1pbmcgcmVncmVzc2lvbiBtb2RlbGluZyB3aXRoIGNvbnRpbnVvdXMgcmVzcG9uc2UsIHRoZSBhcmd1bWVudCBgbGluZWFyLm91dHB1dGAgc2hvdWxkIGJlIHNldCB0byBgVFJVRWAuDQoNCiogV2hlbiBwZXJmb3JtaW5nIGNsYXNzaWZpY2F0aW9uIG1vZGVsaW5nIHdpdGggYSBjYXRlZ29yaWNhbCByZXNwb25zZSwgdGhlIGFyZ3VtZW50IGBsaW5lYXIub3V0cHV0YCBzaG91bGQgYmUgc2V0IHRvIGBGQUxTRWAuDQoNCg0KDQoNCmBgYHtyfQ0KTmV0d29ya01vZGVsID0gbmV1cmFsbmV0KG1vZGVsRm9ybXVsYSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gaW1wbGljaXRGb3JtdWxhRGVzaWduTWF0cml4LCAgIyBtdXN0IGJlIHRoZSBkZXNpZ24gbWF0cml4DQogICAgICAgICAgICAgICAgICAgICAgICAgaGlkZGVuID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBhY3QuZmN0ID0gImxvZ2lzdGljIiwgICAgICMgc2lnbW9pZCBhY3RpdmF0aW9uIGZ1bmN0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgbGluZWFyLm91dHB1dCA9IEZBTFNFIA0KICAgICAgICAgICAgICAgICAgICAgICAgICkNCmthYmxlKE5ldHdvcmtNb2RlbCRyZXN1bHQubWF0cml4KQ0KYGBgDQoNClRoZSBhYm92ZSB0YWJsZSBsaXN0cyB0aGUgZXN0aW1hdGVkIHdlaWdodHMgaW4gdGhlIHBlcmNlcHRyb24gbW9kZWwuIE5leHQsIHdlIGNyZWF0ZSBhIHZpc3VhbCByZXByZXNlbnRhdGlvbiBvZiB0aGUgcGVyY2VwdHJvbiBtb2RlbC4gSW5zdGVhZCBvZiB1c2luZyB0aGUgZGVmYXVsdCBgcGxvdC5ubigpYCwgd2UgdXNlIGEgd3JhcHBlciBvZiBhIHBsb3QgZnVuY3Rpb24gdG8gY3JlYXRlIHRoZSBmb2xsb3dpbmcgbmljZS1sb29raW5nIGZpZ3VyZSAoc2VlIHRoZSBsaW5rIHRvIHRoZSBzb3VyY2UgY29kZSkuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NiwgIGZpZy5jYXA9IkZpZ3VyZSAxMi4gU2luZ2xlLWxheWVyIGJhY2twcm9wYWdhdGlvbiBOZXVyYWwgbmV0d29yayBtb2RlbCBmb3IgUGltYSBJbmRpYW4gZGlhYmV0ZXMifQ0KcGxvdChOZXR3b3JrTW9kZWwsIHJlcD0iYmVzdCIpDQpgYGANCg0KDQojIyBQcmVkaWN0aW9uIGFuZCBST0MgQW5hbHlzaXMNCg0KVGhlIGBuZXVyYWxuZXRgIGxpYnJhcnkgaGFzIHRoZSBnZW5lcmljIFIgZnVuY3Rpb24gYHByZWRpY3QoKWAgdG8gbWFrZSBhIHByZWRpY3Rpb24gdXNpbmcgdGhlIHBlcmNlcHRyb24gbW9kZWwgb2JqZWN0IGFuZCBhIHNldCBvZiBuZXcgZGF0YSAoaW4gYW4gUiBkYXRhIGZyYW1lKS4gRGVwZW5kaW5nIG9uIGhvdyBST0MgY3VydmVzIGFyZSB1c2VkLCB0aGV5IGNvdWxkIGJlIGNvbnN0cnVjdGVkIG9uIGVpdGhlciB0cmFpbmluZywgdGVzdGluZywgb3IgdGhlIGVudGlyZSBkYXRhLiBUbyBrZWVwIGNvbnNpc3RlbmN5LCB3ZSB3aWxsIGNvbnN0cnVjdCB0aGUgUk9DIG9mIHRoZSBwZXJjZXB0cm9uIG1vZGVsIHVzaW5nIHRoZSBlbnRpcmUgZGF0YSBzZXQgc28gd2UgY2FuIGZhaXJseSBjb21wYXJlIHRoZSBST0MgY3VydmVzIGFuZCB0aGUgY29ycmVzcG9uZGluZyBBVUNzIGFtb25nIHRoZSB0aHJlZSBsb2dpc3RpYyBwcmVkaWN0aW9uIG1vZGVscyBhbmQgdGhlIHBlcmNlcHRyb24gbW9kZWwuDQoNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01LCBmaWcuY2FwPSJST0MgY3VydmVzIGNvbXBhcmluZyB0aGUgbW9kZWwgcGVyZm9ybWFuY2Ugb2YgdGhlIHRocmVlIGxvZ2lzdGljIG1vZGVscyBhbmQgYSBzaW5nbGUtbGF5ZXIgbmV1cmFsIG5ldHdvcmsuIn0NCnByZWROTiA9IHByZWRpY3QoTmV0d29ya01vZGVsLCBuZXdkYXRhID0gaW1wbGljaXRGb3JtdWxhRGVzaWduTWF0cml4LCBsaW5lYXIub3V0cHV0ID0gRkFMU0UpDQpwcmVSZWR1Y2VkID0gcHJlZGljdChyZWR1Y2VkTW9kZWwsIG5ld2RhdGEgPSBJbXB1dGVkRnJhbWluZ2hhbSx0eXBlPSJyZXNwb25zZSIgKQ0KcHJlZGZ1bGxNb2RlbCA9IHByZWRpY3QoZnVsbE1vZGVsLCBuZXdkYXRhID0gSW1wdXRlZEZyYW1pbmdoYW0sdHlwZT0icmVzcG9uc2UiICkgDQpwcmVkZm9yd2FyZHMgPSBwcmVkaWN0KGZvcndhcmRzLCBuZXdkYXRhID0gSW1wdXRlZEZyYW1pbmdoYW0sdHlwZT0icmVzcG9uc2UiICkgDQojIw0KIyMNCnByZWRpY3Rpb24ucmVkdWNlZCA9IHByZVJlZHVjZWQNCnByZWRpY3Rpb24uZnVsbCA9IHByZWRmdWxsTW9kZWwNCnByZWRpY3Rpb24uZm9yd2FyZHMgPSBwcmVkZm9yd2FyZHMgDQogIGNhdGVnb3J5ID0gSW1wdXRlZEZyYW1pbmdoYW0kVGVuWWVhckNIRCA9PSAxDQogIFJPQ29iai5yZWR1Y2VkIDwtIHJvYyhjYXRlZ29yeSwgcHJlZGljdGlvbi5yZWR1Y2VkKQ0KICBST0NvYmouZnVsbCA8LSByb2MoY2F0ZWdvcnksIHByZWRpY3Rpb24uZnVsbCkNCiAgUk9Db2JqLmZvcndhcmRzIDwtIHJvYyhjYXRlZ29yeSwgcHJlZGljdGlvbi5mb3J3YXJkcykNCiAgUk9Db2JqLk5OIDwtcm9jKGNhdGVnb3J5LCBwcmVkTk4pDQojIyBBVUMNCiAgcmVkdWNlZEFVQyA9IFJPQ29iai5yZWR1Y2VkJGF1Yw0KICBmdWxsQVVDID0gUk9Db2JqLmZ1bGwkYXVjDQogIGZvcndhcmRzQVVDID0gUk9Db2JqLmZvcndhcmRzJGF1Yw0KICBOTkFVQyA9IFJPQ29iai5OTiRhdWMNCiMjIGV4dHJhY3Qgc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5IGZyb20gY2FuZGlkYXRlIG1vZGVscw0KICBzZW4ucmVkdWNlZCA9IFJPQ29iai5yZWR1Y2VkJHNlbnNpdGl2aXRpZXMNCiAgZm5yLnJlZHVjZWQgPSAxIC0gUk9Db2JqLnJlZHVjZWQkc3BlY2lmaWNpdGllcw0KICAjDQogIHNlbi5mdWxsID0gUk9Db2JqLmZ1bGwkc2Vuc2l0aXZpdGllcw0KICBmbnIuZnVsbCA9IDEgLSBST0NvYmouZnVsbCRzcGVjaWZpY2l0aWVzDQogICMNCiAgc2VuLmZvcndhcmRzID0gUk9Db2JqLmZvcndhcmRzJHNlbnNpdGl2aXRpZXMNCiAgZm5yLmZvcndhcmRzID0gMSAtIFJPQ29iai5mb3J3YXJkcyRzcGVjaWZpY2l0aWVzDQogICMNCiAgc2VuLk5OID0gUk9Db2JqLk5OJHNlbnNpdGl2aXRpZXMNCiAgZm5yLk5OID0gMSAtIFJPQ29iai5OTiRzcGVjaWZpY2l0aWVzDQogIA0KIyMgRm9uZCBjb250cmFzdCBjb2xvciBmb3IgUk9DIGN1cnZlcw0KICBjb2xvcnMgPSBjKCIjOEI0NTAwIiwgIiMwMDAwOEIiLCAiIzhCMDA4QiIsICIjMDU1ZDAzIikNCiMjIFBsb3R0aW5nIFJPQyBjdXJ2ZXMNCiNwYXIodHlwZT0icyIpDQpwbG90KGZuci5yZWR1Y2VkLCBzZW4ucmVkdWNlZCwgdHlwZSA9ICJsIiwgbHdkID0gMiwgY29sID0gY29sb3JzWzFdLA0KICAgICB4bGltID0gYygwLDEpLA0KICAgICB5bGltID0gYygwLDEpLA0KICAgICB4bGFiID0gIjEgLSBzcGVjaWZpY2l0eSIsDQogICAgIHlsYWIgPSAic2Vuc2l0aXZpdHkiLA0KICAgICBtYWluID0gIlJPQyBDdXJ2ZXMgb2YgQ2FuZGlkYXRlIE1vZGVscyIpDQpsaW5lcyhmbnIuZnVsbCwgc2VuLmZ1bGwsIGx3ZCA9IDIsIGx0eSA9IDIsIGNvbCA9IGNvbG9yc1syXSkNCmxpbmVzKGZuci5mb3J3YXJkcywgc2VuLmZvcndhcmRzLCBsd2QgPSAxLCBjb2wgPSBjb2xvcnNbM10pDQpsaW5lcyhmbnIuTk4sIHNlbi5OTiwgbHdkID0gMSwgY29sID0gY29sb3JzWzRdKQ0KDQpzZWdtZW50cygwLDAsMSwxLCBsd2QgPTEsIGNvbCA9ICJyZWQiLCBsdHkgPSAyKQ0KbGVnZW5kKCJ0b3BsZWZ0IiwgYygicmVkdWNlZCIsICJmdWxsIiwgImZvcndhcmRzIiwgIk5OIiwgInJhbmRvbSBndWVzcyIpLCANCiAgICAgICBjb2w9Yyhjb2xvcnMsICJyZWQiKSwgbHdkPWMoMiwyLDEsMSwxKSwNCiAgICAgICBsdHk9YygxLDIsMSwxLDIpLCBidHkgPSAibiIsIGNleCA9IDAuNykNCiMjIGFubm90YXRpbmcgQVVDDQp0ZXh0KDAuODcsIDAuMjUsIHBhc3RlKCJBVUMucmVkdWNlZCA9ICIsIHJvdW5kKHJlZHVjZWRBVUMsNCkpLCBjb2w9Y29sb3JzWzFdLCBjZXggPSAwLjcsIGFkaiA9IDEpDQp0ZXh0KDAuODcsIDAuMjAsIHBhc3RlKCJBVUMuZnVsbCA9ICIsIHJvdW5kKGZ1bGxBVUMsNCkpLCBjb2w9Y29sb3JzWzJdLCBjZXggPSAwLjcsIGFkaiA9IDEpDQp0ZXh0KDAuODcsIDAuMTUsIHBhc3RlKCJBVUMuZm9yd2FyZHMgPSAiLCByb3VuZChmb3J3YXJkc0FVQyw0KSksIGNvbD1jb2xvcnNbM10sIGNleCA9IDAuNywgYWRqID0gMSkNCnRleHQoMC44NywgMC4xMCwgcGFzdGUoIkFVQy5OTiA9ICIsIHJvdW5kKE5OQVVDLDQpKSwgY29sPWNvbG9yc1s0XSwgY2V4ID0gMC43LCBhZGogPSAxKQ0KYGBgDQoNCkFzIGFudGljaXBhdGVkLCB0aGUgb3ZlcmFsbCBwZXJmb3JtYW5jZSBvZiB0aGUgcGVyY2VwdHJvbiBtb2RlbCBhbmQgdGhlIGZ1bGwgYW5kIHJlZHVjZWQgbW9kZWxzIGFyZSBzaW1pbGFyIHRvIGVhY2ggb3RoZXIgYmVjYXVzZSB3ZSB1c2VkIHRoZSAqKnNpZ21vaWQqKiBhY3RpdmF0aW9uIGZ1bmN0aW9uIGluIHRoZSBwZXJjZXB0cm9uIG1vZGVsLg0KDQoNCg0KIyMgQ3Jvc3MtdmFsaWRhdGlvbiBpbiBOZXVyYWwgTmV0d29yaw0KDQpUaGUgYWxnb3JpdGhtIG9mIENyb3NzLXZhbGlkYXRpb24gaXMgcHJpbWFyaWx5IHVzZWQgZm9yIHR1bmluZyBoeXBlci1wYXJhbWV0ZXJzLiBGb3IgZXhhbXBsZSwgaW4gdGhlIHNpZ21vaWQgcGVyY2VwdHJvbiwgdGhlIG9wdGltYWwgY3V0LW9mZiBzY29yZXMgZm9yIHRoZSBiaW5hcnkgZGVjaXNpb24gY2FuIGJlIG9idGFpbmVkIHRocm91Z2ggY3Jvc3MtdmFsaWRhdGlvbi4gT25lIG9mIHRoZSBpbXBvcnRhbnQgaHlwZXJwYXJhbWV0ZXJzIGluIHRoZSBuZXVyYWwgbmV0d29yayBtb2RlbCBpcyB0aGUgbGVhcm5pbmcgcmF0ZSAkXGFscGhhJCAoaW4gdGhlIGJhY2twcm9wYWdhdGlvbiBhbGdvcml0aG0pIHRoYXQgaW1wYWN0cyB0aGUgbGVhcm5pbmcgc3BlZWQgaW4gdHJhaW5pbmcgbmV1cmFsIG5ldHdvcmsgbW9kZWxzLg0KXA0KDQojIEFib3V0IERlZXAgTGVhcm5pbmcNCg0KKkZyb20gV2lraXBlZGlhLCB0aGUgZnJlZSBlbmN5Y2xvcGVkaWEqDQoNCkRlZXAgbGVhcm5pbmcgaXMgcGFydCBvZiBhIGJyb2FkZXIgZmFtaWx5IG9mIG1hY2hpbmUgbGVhcm5pbmcgbWV0aG9kcywgd2hpY2ggaXMgYmFzZWQgb24gYXJ0aWZpY2lhbCBuZXVyYWwgbmV0d29ya3Mgd2l0aCByZXByZXNlbnRhdGlvbiBsZWFybmluZy4gVGhlIGFkamVjdGl2ZSAiZGVlcCIgaW4gZGVlcCBsZWFybmluZyByZWZlcnMgdG8gdGhlIHVzZSBvZiBtdWx0aXBsZSBsYXllcnMgaW4gdGhlIG5ldHdvcmsuIE1ldGhvZHMgdXNlZCBjYW4gYmUgZWl0aGVyIHN1cGVydmlzZWQsIHNlbWktc3VwZXJ2aXNlZCwgb3IgdW5zdXBlcnZpc2VkLg0KDQpEZWVwLWxlYXJuaW5nIGFyY2hpdGVjdHVyZXMgc3VjaCBhcyBkZWVwIG5ldXJhbCBuZXR3b3JrcywgZGVlcCBiZWxpZWYgbmV0d29ya3MsIGRlZXAgcmVpbmZvcmNlbWVudCBsZWFybmluZywgcmVjdXJyZW50IG5ldXJhbCBuZXR3b3JrcywgY29udm9sdXRpb25hbCBuZXVyYWwgbmV0d29ya3MsIGFuZCB0cmFuc2Zvcm1lcnMgaGF2ZSBiZWVuIGFwcGxpZWQgdG8gZmllbGRzIGluY2x1ZGluZyBjb21wdXRlciB2aXNpb24sIHNwZWVjaCByZWNvZ25pdGlvbiwgbmF0dXJhbCBsYW5ndWFnZSBwcm9jZXNzaW5nLCBtYWNoaW5lIHRyYW5zbGF0aW9uLCBiaW9pbmZvcm1hdGljcywgZHJ1ZyBkZXNpZ24sIG1lZGljYWwgaW1hZ2UgYW5hbHlzaXMsIGNsaW1hdGUgc2NpZW5jZSwgbWF0ZXJpYWwgaW5zcGVjdGlvbiBhbmQgYm9hcmQgZ2FtZSBwcm9ncmFtcywgd2hlcmUgdGhleSBoYXZlIHByb2R1Y2VkIHJlc3VsdHMgY29tcGFyYWJsZSB0byBhbmQgaW4gc29tZSBjYXNlcyBzdXJwYXNzaW5nIGh1bWFuIGV4cGVydCBwZXJmb3JtYW5jZS4NCg0KIyMgTXVsdGktbGF5ZXIgUGVyY2VwdHJvbg0KDQpBIE11bHRpLUxheWVyIFBlcmNlcHRyb24gKE1MUCkgY29udGFpbnMgb25lIG9yIG1vcmUgaGlkZGVuIGxheWVycyAoYXBhcnQgZnJvbSBvbmUgaW5wdXQgYW5kIG9uZSBvdXRwdXQgbGF5ZXIpLiBXaGlsZSBhIHNpbmdsZS1sYXllciBwZXJjZXB0cm9uIGNhbiBvbmx5IGxlYXJuIGxpbmVhciBmdW5jdGlvbnMsIGEgbXVsdGktbGF5ZXIgcGVyY2VwdHJvbiBjYW4gYWxzbyBsZWFybiBub24tbGluZWFyIGZ1bmN0aW9ucy4gVGhlIGZvbGxvd2luZyBpcyBhbiBpbGx1c3RyYXRpdmUgTUxQLg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTMsIGZpZy5jYXA9IkZpZ3VyZSA4LiAgTXVsdGktbGF5ZXIgcGVyY2VwdHJvbi4ifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL3cwNy1NdWx0aWxheWVyLVBlcmNlcHRyb24uanBnIikNCmBgYA0KDQpUaGUgYG1ham9yIGNvbXBvbmVudHNgIGluIHRoZSBhYm92ZSBNTFAgYXJlIGRlc2NyaWJlZCBpbiB0aGUgZm9sbG93aW5nLg0KDQoqKklucHV0IExheWVyKio6IFRoZSBJbnB1dCBsYXllciBoYXMgdGhyZWUgbm9kZXMuIFRoZSBCaWFzIG5vZGUgaGFzIGEgdmFsdWUgb2YgMS4gVGhlIG90aGVyIHR3byBub2RlcyB0YWtlICRYXzEkIGFuZCAkWF8yJCBhcyBleHRlcm5hbCBpbnB1dHMgKHdoaWNoIGFyZSBudW1lcmljYWwgdmFsdWVzIGRlcGVuZGluZyB1cG9uIHRoZSBpbnB1dCBkYXRhIHNldCkuIE5vIGNvbXB1dGF0aW9uIGlzIHBlcmZvcm1lZCBpbiB0aGUgSW5wdXQgbGF5ZXIsIHNvIHRoZSBvdXRwdXRzIGZyb20gbm9kZXMgaW4gdGhlIElucHV0IGxheWVyIGFyZSAxLCAkWF8xJCwgYW5kICRYXzIkIHJlc3BlY3RpdmVseSwgd2hpY2ggYXJlIGZlZCBpbnRvIHRoZSBIaWRkZW4gTGF5ZXIuDQoNCioqSGlkZGVuIExheWVyKio6IFRoZSBIaWRkZW4gbGF5ZXIgYWxzbyBoYXMgdGhyZWUgbm9kZXMgd2l0aCB0aGUgQmlhcyBub2RlIGhhdmluZyBhbiBvdXRwdXQgb2YgMS4gVGhlIG91dHB1dCBvZiB0aGUgb3RoZXIgdHdvIG5vZGVzIGluIHRoZSBIaWRkZW4gbGF5ZXIgZGVwZW5kcyBvbiB0aGUgb3V0cHV0cyBmcm9tIHRoZSBJbnB1dCBsYXllciAoMSwgJFhfMSQsICRYXzIkKSBhcyB3ZWxsIGFzIHRoZSB3ZWlnaHRzIGFzc29jaWF0ZWQgd2l0aCB0aGUgY29ubmVjdGlvbnMgKGVkZ2VzKS4gRmlndXJlIDE2IHNob3dzIHRoZSBvdXRwdXQgY2FsY3VsYXRpb25zIGZvciB0aGUgaGlkZGVuIG5vZGVzLiBSZW1lbWJlciB0aGF0ICRmKCkkIHJlZmVycyB0byB0aGUgYWN0aXZhdGlvbiBmdW5jdGlvbi4gVGhlc2Ugb3V0cHV0cyBhcmUgdGhlbiBmZWQgdG8gdGhlIG5vZGVzIGluIHRoZSBPdXRwdXQgbGF5ZXIuDQoNCioqT3V0cHV0IExheWVyKio6IFRoZSBPdXRwdXQgbGF5ZXIgaGFzIHR3byBub2RlcyB0aGF0IHRha2UgaW5wdXRzIGZyb20gdGhlIEhpZGRlbiBsYXllciBhbmQgcGVyZm9ybSBzaW1pbGFyIGNvbXB1dGF0aW9ucyBhcyBzaG93biBpbiB0aGUgYWJvdmUgZmlndXJlLiBUaGUgdmFsdWVzIGNhbGN1bGF0ZWQgKCRZXzEkIGFuZCAkWV8yJCkgYXMgYSByZXN1bHQgb2YgdGhlc2UgY29tcHV0YXRpb25zIGFjdCBhcyBvdXRwdXRzIG9mIHRoZSBNdWx0aS1MYXllciBQZXJjZXB0cm9uLg0KDQpcDQo=