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.
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
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. |
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")
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.
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.
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)
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.
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.
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")))
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)
(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
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)
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.
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
The above diagram of the logistic regression model is the basic
single layer sigmoid neural network model -
perceptron.
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
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:
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}
\]
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.
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.
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.
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.
The general architecture of the backpropagation network model is
depicted in the following diagram.
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})}.
\]
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.
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.
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.
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]
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.
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.
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.
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")
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)
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.
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.
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.
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.
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=