1 Generalized Linear Model (GLM)

  • This is a whole area in regression and we could spend a full semester on this topic. Todays goal is crash course on the basics of the most common type of GLM used, the logistics regression
  • So far you have been using a special case of the GLM, where we assume the underlying assumption is Gaussian distribution
  • Now we will expand out the examine the whole family
  • When you run a GLM, you need to state “family” and “linking”" function you will use

2 Linear regression on binomial DV

  • Lets simulate what happens when we analyze binomial DV as it were a plane old regression
  • My grandmother had a very hard time predicting gender in the 90s because clothing became big and baggy, men wore earrings and also started sporting long hair
  • Let’s simulate her prediction process Gender (DV = 0 = male, 1 =female) based on the Hair length (IV = -3 to 3) and bagginess of clothing (IV = -3 to 3) of the person she was looking at
  • The goal is to predict gender (not hair length of each gender, so t-tests are out. Also we can later add other predictors of gender)
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 3.4.1
set.seed(42)
n=30
x = runif(n,-3,3) #  Hair length centered [-3 short, 3 = long]
j = runif(n,-3,3) #  Baggy clothing centered [-3 not baggy, 3 = really baggy]
z =  .8*x - .2*j         
pr = 1/(1+exp(-z))  # pass through an inv-logit function
y = rbinom(n,1,pr)  # response variable
 
 #now feed it to glm:
LogisticStudy1= data.frame(Gender=y,HairLength=x, baggy=j)

ggplot(LogisticStudy1, aes(x=HairLength, y=Gender)) + geom_point() + 
  stat_smooth(method="lm", formula=y~x, se=FALSE)+
  theme_classic()

ggplot(LogisticStudy1, aes(x=baggy, y=Gender)) + geom_point() + 
  stat_smooth(method="lm", formula=y~x, se=FALSE)+
  theme_classic()

library(stargazer)
LM.1<-lm(Gender~HairLength+baggy,data=LogisticStudy1)
stargazer(LM.1,type="html",
          column.labels = c("LM"),
          intercept.bottom = FALSE,
          single.row=FALSE, 
          notes.append = FALSE,
          header=FALSE)
Dependent variable:
Gender
LM
Constant 0.501***
(0.083)
HairLength 0.160***
(0.045)
baggy -0.064
(0.043)
Observations 30
R2 0.334
Adjusted R2 0.285
Residual Std. Error 0.421 (df = 27)
F Statistic 6.782*** (df = 2; 27)
Note: p<0.1; p<0.05; p<0.01
  • The intercept reflects the mean gender at the mean hair length
  • The slope on hair says, as hair gets longer its more likely female
  • The slope on baggy says, as clothes get baggier its more likely male (but its not significant)
  • How the residuals look?
plot(LM.1, which =1)

  • That looks really odd because the fitted (predict values) are continuous but the response is binomial
  • Lets see how well we predicted our result for each individual:
LogisticStudy1$Predicted.Value<-predict(LM.1)
summary(LogisticStudy1$Predicted.Value)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
## 0.08036 0.35206 0.59839 0.60000 0.81592 1.09583
  • Yikes the model predicted a value outside the range of possible values
  • Lets say any prediction > .5 = Female and below that is Male
  • Then we will examine a contingency table (predict results to true results)
LogisticStudy1$Predicted.Gender<-ifelse(LogisticStudy1$Predicted.Value > .5,1,0)
C.Table<-with(LogisticStudy1,
   table(Predicted.Gender, Gender))
C.Table
##                 Gender
## Predicted.Gender  0  1
##                0  9  3
##                1  3 15
  • Correct predict is 0-0 and 1-1, bad prediction are mismatches
PercentPredicted<-(C.Table[1,1]+C.Table[2,2])/sum(C.Table)*100
PercentPredicted
## [1] 80
  • So the model did good job making the prediction in this simple case, but the \(R^2\) has no meaning (what variability of gender is it explaining?)

2.0.1 Summary

  • Using linear regression on this data produced odd predictions outside of the bounded range, violation of homoscedasticity, and an \(R^2\) which makes no sense
  • Instead lets try a GLM, but first lets understand the binomial distribution and logit link function

3 Logistic Regression

  • Logistic regression is that we call the regression where we analyze binomial DV

3.1 Binomial Distribution

  • Binomial can be expressed as follows: \[p(n|N) = (\frac{N}{n})p^n(1-p^{N-n})\]
  • where \(n\) = successes in \(N\) trials, at a specific \(p\) probability
  • These change as function of the underlying probability of getting a 0 or 1
  • The plot below has N=10 people making 1 response each, with probability changing from 0 to 1 by .25
par(mfrow=c(1, 5))
for(p in seq(0, 1, len=5))
{
    x <- dbinom(0:10, size=10, p=p)
    barplot(x, names.arg=0:10, space=0)
}

  • As the number of people increases you will notice it looks normal, here is 100 responses
par(mfrow=c(1, 5))
for(p in seq(0, 1, len=5))
{
    x <- dbinom(0:100, size=100, p=p)
    barplot(x, names.arg=0:100, space=0)
}

3.2 Logit Linking Function

  • We can bound our results making our best fit line asymptotic to the boundary conditions
  • To make this work we need to switch from straight lines to sigmoid
  • Also regression want the DV to be between \(-\infty,\infty\), so have will have to transform
  • \[Logit = log\frac{p}{1-p}\]
  • Note: I will use Log for natural log to be consistant with R (but in most text you will see natural log as LN)
logit.Transform<-function(p) {log(p/(1-p)) }

plot(logit.Transform(seq(0,1,.0001)),seq(0,1,.0001),
     main="Logit Transform",ylim = c(0,1),
  xlab="Logit",ylab="Probability")

3.3 Fit the logistic regression

  • \(logit(Gender) = B_1(HairLength) +B_2(baggy) + B_0\)
  • This can be accomplished by changing the function in R to glm from lm and specifying the family as binomial(link = “logit”)
  • First lets plot and then make sense of the parameters afterwards
LR.1<-glm(Gender~HairLength+baggy,data=LogisticStudy1, family=binomial(link = "logit"))

stargazer(LR.1,type="html",
          column.labels = c("Logistic"),
          intercept.bottom = FALSE,
          single.row=FALSE, 
          notes.append = FALSE,
          header=FALSE)
Dependent variable:
Gender
Logistic
Constant 0.085
(0.477)
HairLength 0.870***
(0.321)
baggy -0.429
(0.307)
Observations 30
Log Likelihood -14.477
Akaike Inf. Crit. 34.955
Note: p<0.1; p<0.05; p<0.01
library(ggplot2)
ggplot(LogisticStudy1, aes(x=HairLength, y=Gender)) + geom_point() + 
  stat_smooth(method="glm", method.args=list(family="binomial"), se=FALSE)+
  theme_classic()

ggplot(LogisticStudy1, aes(x=baggy, y=Gender)) + geom_point() + 
  stat_smooth(method="glm", method.args=list(family="binomial"), se=FALSE)+
  theme_classic()

3.4 Plot in probabilities

  • Y-axis = Predicted probability of gender as a function of hair length
library(effects)
PredictedData<-allEffects(LR.1)
plot(PredictedData)

  • How did we get the probability from logit?

3.5 Interpret coefficients

  • Raw coefficients are transformed and this hard to makes sense of
  • So we can transform them back to something meaningful; odds or probabilities
  • Odds = success are defined as the ratio of the probability of success over the probability of failure
  • 50% chance = Odds is 1 to 1 \[Odds = e^{logit(x)}\]
L.to.O.Transform<-function(p) {exp(logit.Transform(p))}

plot((logit.Transform(seq(0,.99,.01))),L.to.O.Transform(seq(0,.99,.01)),
     main="Logit to Odds Transform",
  xlab="Logit",ylab="Odds")

- here is our regression estimates as odds

LR.1.Trans <- coef(summary(LR.1))
LR.1.Trans[, "Estimate"] <- exp(coef(LR.1))
LR.1.Trans
##             Estimate Std. Error    z value    Pr(>|z|)
## (Intercept) 1.088608  0.4766947  0.1781009 0.858643712
## HairLength  2.385786  0.3213051  2.7062390 0.006805006
## baggy       0.651300  0.3069054 -1.3971242 0.162376227
  • MDs talk in odds, but psychologists prefer probabilities \[P = \frac{Odds} {1 + Odds}\]
O.to.P.Transform<-function(p) {L.to.O.Transform(p)/(1+L.to.O.Transform(p))}

plot(L.to.O.Transform(seq(0,.94,.01)),O.to.P.Transform(seq(0,.94,.01)),
     main="Odds to Probablity Transform",
  xlab="Odds",ylab="Probablity")

  • So we can convert our regression equation to give our probabilities directly

\[p_{(Gender)} =\frac{e^{(B_1(HairLength)+B_2(baggy) + B_0})}{1+e^{(B_1(HairLength)+B_2(baggy) + B_0})}\]

  • or more simply!

\[p_{(Gender)} =\frac{1}{1+e^{-(B_1(HairLength)+B_2(baggy) + B_0})}\]

LR.1.TransP <- coef(summary(LR.1))
LR.1.TransP[, "Estimate"] <- exp(coef(LR.1))/(1+exp(coef(LR.1)))
LR.1.TransP
##              Estimate Std. Error    z value    Pr(>|z|)
## (Intercept) 0.5212122  0.4766947  0.1781009 0.858643712
## HairLength  0.7046476  0.3213051  2.7062390 0.006805006
## baggy       0.3944165  0.3069054 -1.3971242 0.162376227
  • However, you have to report the raw values that come from the logistic regression
  • This is because your units of hair length are in raw units and if you used this table you would need to figure out how to convert all your IV units
  • Thus, you would simply use the equations above when you add up your predictors
  • So just link in linear regression you would add your predictor estimates (lets say if you has nominal variables and interactions) and than convert them as above

3.6 Wait - What about my R-squared?

  • \(R^2\) has no meaning has no meaning in these models
  • They do not measure the amount of variance accounted for
  • Binominal data cannot be homoscedastic, in fact you could see there is no spread of data around the line and also the variance at each value may not be same
  • Over the years people have created different types of pseudo-\(R^2\) in which to try to make linear regression types of understanding
  • Common favorites are Cox & Snell (aka Maximum likelihood), Nagelkerke (aka Cragg and Uhler’s), McFadden, Efron’s, etc
  • You can see an easy description of each type here: http://stats.idre.ucla.edu/other/mult-pkg/faq/general/faq-what-are-pseudo-r-squareds/
  • Each of them try to capture something like our original \(R^2\), such as (A) explaining variability, (B) model improvement such as \(change in R^2\) between models, (C) and finally as measure of multiple-correlation
  • Many of these between testing between a restricted model (Null model) and your model, so first we need to examine model fitting for GLMs

3.6.1 Deviance Testing

  • In linear regression, OLS was the fitting system and it worked because OLS can find mean and variance of the normal distribution, but now we are not working with normal distributions, so we need a new fitting procedure
  • In our OLS regression we had this concept: \[SS_{Resid} = SS_Y - SS_{Regression}\]
  • In other words, residual = Actual - Fitted values
  • in OLS, we can get the best fit analytically (solving an equation), but with GLM you cannot do that
  • We can calculate deviance of scores which is built on the idea of maximum likelihood
  • So we iterate a solution (since we cannot solve it without trial and error)
  • We need to find the likelihood; which is “a hypothetical probability that an event that has already occurred would yield a specific outcome” (http://mathworld.wolfram.com/Likelihood.html)
  • We will iterate through parameters until we maximize our likelihood (called maximal likelihood estimation)
  • There are different ML (or just L) functions that can be used and they can apply to most distributions
  • When the fit is perfect, \[L_{perfect} = 1\]
  • The null case (starting model) that ONLY has an intercept, this will probably yield the lowest likelihood
  • Our test model will be intercept + predictors (parameters - k) \[Likelihood ratio =\frac{L_{Simple}}{L_{Complex}}\]
  • Deviance this is the -2 x natual logLikelihood ratio \[D = 2*log(Likelihood ratio)\] AKA \[D = -2LL\] First I will show you the pseudo-\(R^2\) and than we will examine how to test between model fits

  • Null Deviance \[D_{Null} = -2[log(L_{Null}) - log(L_{Pefect})]\]

  • Model Deviance \[D_{K} = -2[log(L_{K} - log(L_{Pefect})]\]
  • this is like our SS residual from OLS

3.6.2 Pseudo-R-sqaured

  • We need those crazy Devience scores for some of our pseudo-\(R^2\) measurements
  • for example, \[R_L^2 = \frac{D_{Null} -D_{k}}{D_{Null}}\]
  • Cox and Snell is what SPSS gives and people report it often
  • Nagelkerke is an improvement (C&S) as it corrects some problems
  • but people like McFadden’s cause its easy to understand \[R_{McFadden}^2 = 1 - \frac{LL_{k}}{LL_{Null}}\]

  • We can just get them from the pscl package
  • llh = log-likelihood from the fitted model (llk above)
  • llhNull = The log-likelihood from the intercept-only restricted model
  • \(G^2 = -2(LL_{K} - LL_{NULL})\) is a one of the proposed goodness of fit measures (we will come back to this later)
  • McFadden = McFadden pseudo-\(R^2\)
  • r2ML = Cox & Snell pseudo-\(R^2\)
  • r2CU = Nagelkerke pseudo-\(R^2\)

library(pscl)
pR2(LR.1)
##         llh     llhNull          G2    McFadden        r2ML        r2CU 
## -14.4774392 -20.1903500  11.4258216   0.2829525   0.3167270   0.4281675

3.6.3 Wait why I do see Z and not t-values?

  • When testing individual predictors, you are not seeing t-tests (one sample t-tests against 0) you are looking at Wald Z-scores
  • Some argue that individual predictors should tested against a model that does not have that term (like a stepwise regression), but our programs will calculate a test-statistics based on each predictor in the model (more people ignore these and just look at change in overall model fit)

\[ Wald = \frac{B_j}{SE_{B_j}^2}\]

  • Wald follows a chi-square distribution

  • Or you can run a stepwise and test each predictor again the null model like this (order of predictors matter)

anova(LR.1, test="Chisq")
## Analysis of Deviance Table
## 
## Model: binomial, link: logit
## 
## Response: Gender
## 
## Terms added sequentially (first to last)
## 
## 
##            Df Deviance Resid. Df Resid. Dev Pr(>Chi)   
## NULL                          29     40.381            
## HairLength  1   9.0638        28     31.317 0.002607 **
## baggy       1   2.3620        27     28.955 0.124322   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

3.7 Hierarchical testing

  • Going stepwise can be difficult if you have lots of predictors
  • Since we cannot test the change in \(R^2\) we will instead test whether the deviance is significantly greater than the model without the predictor (just like above)
  • So we run likelihood ratio test between the models which tests against the chi-square distribution
LR.Model.1<-glm(Gender~baggy,data=LogisticStudy1, family=binomial(link = "logit"))
LR.Model.2<-glm(Gender~baggy+HairLength,data=LogisticStudy1, family=binomial(link = "logit"))
anova(LR.Model.1,LR.Model.2,test = "Chisq")
## Analysis of Deviance Table
## 
## Model 1: Gender ~ baggy
## Model 2: Gender ~ baggy + HairLength
##   Resid. Df Resid. Dev Df Deviance Pr(>Chi)   
## 1        28     39.639                        
## 2        27     28.955  1   10.684 0.001081 **
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  • Here we see model 2 has improvement in deviance (it fits better) and thus it means hair length did help the prediction

3.8 How well am I predicting?

  • Model 1
fitted.results <- predict(LR.Model.1,newdata=LogisticStudy1,type='response')
fitted.results <- ifelse(fitted.results > 0.5,1,0)
misClasificError <- mean(fitted.results != LogisticStudy1$Gender)
print(paste('Accuracy = ',round(1-misClasificError,3)))
## [1] "Accuracy =  0.633"
  • Model 2
fitted.results <- predict(LR.Model.2,newdata=LogisticStudy1,type='response')
fitted.results <- ifelse(fitted.results > 0.5,1,0)
misClasificError <- mean(fitted.results != LogisticStudy1$Gender)
print(paste('Accuracy = ',round(1-misClasificError,3)))
## [1] "Accuracy =  0.767"
  • This is not the only way to examine accuracy
  • So we have get correct responses, misses and false alarms (remember our type I and II boxes)
  • To visual this we will examine receiver operator curves (ROC)
  • It is the relationship between correct responses and false alarms
  • We will calculate the area under the curve to get a good measure of accuracy (the closer to 1 the better)
  • Also the curve should follow the shape you see below (if it is the opposite shape you have a problem)
library(ROCR)
fitted.results <- predict(LR.Model.2,newdata=LogisticStudy1,type='response')
pr <- prediction(fitted.results, LogisticStudy1$Gender)

prf <- performance(pr, measure = "tpr", x.measure = "fpr")
plot(prf)

auc <- performance(pr, measure = "auc")
auc <- auc@y.values[[1]]
print(paste('Area under the Curve = ',round(auc,3)))
## [1] "Area under the Curve =  0.833"

3.9 Some new things to keep in mind/check in your model

  • Should I doing logistics model: Is a sigmoid the right shape?
  • Hosmer-Lemeshow Test: Are the observed proportions of events similar to the predicted probabilities of occurrence in subgroups of the data set using a chi square test.
  • Like HOV you dont want this to be significant
library(MKmisc)
HLgof.test(fit = fitted(LR.Model.2), obs = LogisticStudy1$Gender)
## $C
## 
##  Hosmer-Lemeshow C statistic
## 
## data:  fitted(LR.Model.2) and LogisticStudy1$Gender
## X-squared = 5.4838, df = 8, p-value = 0.7048
## 
## 
## $H
## 
##  Hosmer-Lemeshow H statistic
## 
## data:  fitted(LR.Model.2) and LogisticStudy1$Gender
## X-squared = 4.5686, df = 8, p-value = 0.8025
LS0tDQp0aXRsZTogJ0dlbmVyYWxpemVkIExpbmVhciBNb2RlbCcNCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBmb250c2l6ZTogOHB0DQogICAgaGlnaGxpZ2h0OiB0ZXh0bWF0ZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IGZsYXRseQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiBubw0KLS0tDQoNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZSA9IEZBTFNFKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy53aWR0aD01KQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy5oZWlnaHQ9My43NSkNCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcuYWxpZ249J2NlbnRlcicpIA0KYGBgDQoNCg0KIyBHZW5lcmFsaXplZCBMaW5lYXIgTW9kZWwgKEdMTSkNCi0gKipUaGlzIGlzIGEgd2hvbGUgYXJlYSBpbiByZWdyZXNzaW9uIGFuZCB3ZSBjb3VsZCBzcGVuZCBhIGZ1bGwgc2VtZXN0ZXIgb24gdGhpcyB0b3BpYy4gVG9kYXlzIGdvYWwgaXMgY3Jhc2ggY291cnNlIG9uIHRoZSBiYXNpY3Mgb2YgdGhlIG1vc3QgY29tbW9uIHR5cGUgb2YgR0xNIHVzZWQsIHRoZSBsb2dpc3RpY3MgcmVncmVzc2lvbioqDQotIFNvIGZhciB5b3UgaGF2ZSBiZWVuIHVzaW5nIGEgc3BlY2lhbCBjYXNlIG9mIHRoZSBHTE0sIHdoZXJlIHdlIGFzc3VtZSB0aGUgdW5kZXJseWluZyBhc3N1bXB0aW9uIGlzIEdhdXNzaWFuIGRpc3RyaWJ1dGlvbg0KLSBOb3cgd2Ugd2lsbCBleHBhbmQgb3V0IHRoZSBleGFtaW5lIHRoZSB3aG9sZSBmYW1pbHkNCi0gV2hlbiB5b3UgcnVuIGEgR0xNLCB5b3UgbmVlZCB0byBzdGF0ZSAiZmFtaWx5IiBhbmQgImxpbmtpbmciIiBmdW5jdGlvbiB5b3Ugd2lsbCB1c2UNCg0KIyMgRmFtaWx5IGFuZCBMaW5rDQotIExpbmVhciByZWdyZXNzaW9uIGFzc3VtZXMgdGhhdCBEViA9ICRcbXUkIGFuZCBTRCA9ICRcc2lnbWEkIGFuZCB0aGUgcG9zc2libGUgcmFuZ2Ugb2YgcmVzcG9uc2VzIGlzIGZyb20gJC1caW5mdHksXGluZnR5JA0KLSBCdXQgR0xNIHdpbGwgYWxsb3cgdXMgdG8gZXhhbWluZSBjYXRlZ29yaWNhbCByZXNwb25zZXMgKDIgb3IgbW9yZSByZXNwb25zZXMpIGFuZCB5b3UgY2Fubm90IHNhdGlzZnkgdGhlIHNhbWUgcmVxdWlyZW1lbnRzIGFzIGxpbmVhciByZWdyZXNzaW9uICh5b3UgY2Fubm90IGhhdmUgJC1caW5mdHksXGluZnR5JCByZXNwb25zZSByYW5nZSkgDQotIFNvbWUgb2YgdGhlIG90aGVyIGRpc3RyaWJ1dGlvbnMsIGxpa2UgYmlub21pYWwgKDIgcmVzcG9uc2VzKSBvciBQb2lzc29uIChtdWx0aXBsZSByZXNwb25zZXMpIGNhbiBhcHByb3hpbWF0ZSBub3JtYWwgd2hlbiB0cmFuc2Zvcm1lZA0KLSBTbyB3ZSBuZWVkIHRvICJsaW5rIiB0aGUgbWVhbiBvZiBEViB0byB0aGUgbGluZWFyIHRlcm0gb2YgdGhlIG1vZGVsICh0aGluayB0cmFuc2Zvcm0pIHRvIG1ha2UgaXQgbWVldCBvdXIgcmVncmVzc2lvbiByZXF1aXJlbWVudHMgIA0KLSBIZXJlIGFyZSB0aGUgbW9zdCBjb21tb24gZmFtaWxpZXMgdXNlZCBpbiBwc3ljaA0KDQpGYW1pbHkgICAgfCBWYXJpYW5jZSAgfCBMaW5rDQotLS0tLS0tLS0tfCAtLS0tLS0tLSB8IC0tLS0tLS0NCkdhdXNzaWFuICB8IEdhdXNzaWFuIHwgaWRlbnRpdHkNCmJpbm9taWFsICB8IGJpbm9taWFsIHwgbG9naXQvUHJvYml0DQpQb2lzc29uICAgfCBQb2lzc29uICB8IGxvZw0KDQoNCiMgTGluZWFyIHJlZ3Jlc3Npb24gb24gYmlub21pYWwgRFYNCi0gTGV0cyBzaW11bGF0ZSB3aGF0IGhhcHBlbnMgd2hlbiB3ZSBhbmFseXplIGJpbm9taWFsIERWIGFzIGl0IHdlcmUgYSBwbGFuZSBvbGQgcmVncmVzc2lvbg0KLSBNeSBncmFuZG1vdGhlciBoYWQgYSB2ZXJ5IGhhcmQgdGltZSBwcmVkaWN0aW5nIGdlbmRlciBpbiB0aGUgOTBzIGJlY2F1c2UgY2xvdGhpbmcgYmVjYW1lIGJpZyBhbmQgYmFnZ3ksIG1lbiB3b3JlIGVhcnJpbmdzIGFuZCBhbHNvIHN0YXJ0ZWQgc3BvcnRpbmcgbG9uZyBoYWlyDQotIExldCdzIHNpbXVsYXRlIGhlciBwcmVkaWN0aW9uIHByb2Nlc3MgR2VuZGVyIChEViA9IDAgPSBtYWxlLCAxID1mZW1hbGUpIGJhc2VkIG9uIHRoZSBIYWlyIGxlbmd0aCAoSVYgPSAtMyB0byAzKSBhbmQgYmFnZ2luZXNzIG9mIGNsb3RoaW5nIChJViA9IC0zIHRvIDMpICBvZiB0aGUgcGVyc29uIHNoZSB3YXMgbG9va2luZyBhdA0KLSBUaGUgZ29hbCBpcyB0byBwcmVkaWN0IGdlbmRlciAobm90IGhhaXIgbGVuZ3RoIG9mIGVhY2ggZ2VuZGVyLCBzbyB0LXRlc3RzIGFyZSBvdXQuIEFsc28gd2UgY2FuIGxhdGVyIGFkZCBvdGhlciBwcmVkaWN0b3JzIG9mIGdlbmRlcikNCg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCg0Kc2V0LnNlZWQoNDIpDQpuPTMwDQp4ID0gcnVuaWYobiwtMywzKSAjICBIYWlyIGxlbmd0aCBjZW50ZXJlZCBbLTMgc2hvcnQsIDMgPSBsb25nXQ0KaiA9IHJ1bmlmKG4sLTMsMykgIyAgQmFnZ3kgY2xvdGhpbmcgY2VudGVyZWQgWy0zIG5vdCBiYWdneSwgMyA9IHJlYWxseSBiYWdneV0NCnogPSAgLjgqeCAtIC4yKmogICAgICAgICANCnByID0gMS8oMStleHAoLXopKSAgIyBwYXNzIHRocm91Z2ggYW4gaW52LWxvZ2l0IGZ1bmN0aW9uDQp5ID0gcmJpbm9tKG4sMSxwcikgICMgcmVzcG9uc2UgdmFyaWFibGUNCiANCiAjbm93IGZlZWQgaXQgdG8gZ2xtOg0KTG9naXN0aWNTdHVkeTE9IGRhdGEuZnJhbWUoR2VuZGVyPXksSGFpckxlbmd0aD14LCBiYWdneT1qKQ0KDQpnZ3Bsb3QoTG9naXN0aWNTdHVkeTEsIGFlcyh4PUhhaXJMZW5ndGgsIHk9R2VuZGVyKSkgKyBnZW9tX3BvaW50KCkgKyANCiAgc3RhdF9zbW9vdGgobWV0aG9kPSJsbSIsIGZvcm11bGE9eX54LCBzZT1GQUxTRSkrDQogIHRoZW1lX2NsYXNzaWMoKQ0KZ2dwbG90KExvZ2lzdGljU3R1ZHkxLCBhZXMoeD1iYWdneSwgeT1HZW5kZXIpKSArIGdlb21fcG9pbnQoKSArIA0KICBzdGF0X3Ntb290aChtZXRob2Q9ImxtIiwgZm9ybXVsYT15fngsIHNlPUZBTFNFKSsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCmBgYA0KDQoNCmBgYHtyLCBlY2hvPVRSVUUsIHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRSxyZXN1bHRzPSdhc2lzJ30NCmxpYnJhcnkoc3RhcmdhemVyKQ0KTE0uMTwtbG0oR2VuZGVyfkhhaXJMZW5ndGgrYmFnZ3ksZGF0YT1Mb2dpc3RpY1N0dWR5MSkNCnN0YXJnYXplcihMTS4xLHR5cGU9Imh0bWwiLA0KICAgICAgICAgIGNvbHVtbi5sYWJlbHMgPSBjKCJMTSIpLA0KICAgICAgICAgIGludGVyY2VwdC5ib3R0b20gPSBGQUxTRSwNCiAgICAgICAgICBzaW5nbGUucm93PUZBTFNFLCANCiAgICAgICAgICBub3Rlcy5hcHBlbmQgPSBGQUxTRSwNCiAgICAgICAgICBoZWFkZXI9RkFMU0UpDQoNCmBgYA0KDQotIFRoZSBpbnRlcmNlcHQgcmVmbGVjdHMgdGhlIG1lYW4gZ2VuZGVyIGF0IHRoZSBtZWFuIGhhaXIgbGVuZ3RoDQotIFRoZSBzbG9wZSBvbiBoYWlyIHNheXMsIGFzIGhhaXIgZ2V0cyBsb25nZXIgaXRzIG1vcmUgbGlrZWx5IGZlbWFsZQ0KLSBUaGUgc2xvcGUgb24gYmFnZ3kgc2F5cywgYXMgY2xvdGhlcyBnZXQgYmFnZ2llciBpdHMgbW9yZSBsaWtlbHkgbWFsZSAoYnV0IGl0cyBub3Qgc2lnbmlmaWNhbnQpDQotIEhvdyB0aGUgcmVzaWR1YWxzIGxvb2s/DQoNCmBgYHtyfQ0KcGxvdChMTS4xLCB3aGljaCA9MSkNCmBgYA0KDQotIFRoYXQgbG9va3MgcmVhbGx5IG9kZCBiZWNhdXNlIHRoZSBmaXR0ZWQgKHByZWRpY3QgdmFsdWVzKSBhcmUgY29udGludW91cyBidXQgdGhlIHJlc3BvbnNlIGlzIGJpbm9taWFsDQotIExldHMgc2VlIGhvdyB3ZWxsIHdlIHByZWRpY3RlZCBvdXIgcmVzdWx0IGZvciBlYWNoIGluZGl2aWR1YWw6DQoNCg0KYGBge3J9DQpMb2dpc3RpY1N0dWR5MSRQcmVkaWN0ZWQuVmFsdWU8LXByZWRpY3QoTE0uMSkNCnN1bW1hcnkoTG9naXN0aWNTdHVkeTEkUHJlZGljdGVkLlZhbHVlKQ0KYGBgDQoNCi0gWWlrZXMgdGhlIG1vZGVsIHByZWRpY3RlZCBhIHZhbHVlIG91dHNpZGUgdGhlIHJhbmdlIG9mIHBvc3NpYmxlIHZhbHVlcw0KLSBMZXRzIHNheSBhbnkgcHJlZGljdGlvbiA+IC41ID0gRmVtYWxlIGFuZCBiZWxvdyB0aGF0IGlzIE1hbGUNCi0gVGhlbiB3ZSB3aWxsIGV4YW1pbmUgYSBjb250aW5nZW5jeSB0YWJsZSAocHJlZGljdCByZXN1bHRzIHRvIHRydWUgcmVzdWx0cykNCg0KDQpgYGB7cn0NCkxvZ2lzdGljU3R1ZHkxJFByZWRpY3RlZC5HZW5kZXI8LWlmZWxzZShMb2dpc3RpY1N0dWR5MSRQcmVkaWN0ZWQuVmFsdWUgPiAuNSwxLDApDQpDLlRhYmxlPC13aXRoKExvZ2lzdGljU3R1ZHkxLA0KICAgdGFibGUoUHJlZGljdGVkLkdlbmRlciwgR2VuZGVyKSkNCkMuVGFibGUNCmBgYA0KDQotIENvcnJlY3QgcHJlZGljdCBpcyAwLTAgYW5kIDEtMSwgYmFkIHByZWRpY3Rpb24gYXJlIG1pc21hdGNoZXMNCg0KYGBge3J9DQpQZXJjZW50UHJlZGljdGVkPC0oQy5UYWJsZVsxLDFdK0MuVGFibGVbMiwyXSkvc3VtKEMuVGFibGUpKjEwMA0KUGVyY2VudFByZWRpY3RlZA0KYGBgDQoNCi0gU28gdGhlIG1vZGVsIGRpZCBnb29kIGpvYiBtYWtpbmcgdGhlIHByZWRpY3Rpb24gaW4gdGhpcyBzaW1wbGUgY2FzZSwgYnV0IHRoZSAkUl4yJCBoYXMgbm8gbWVhbmluZyAod2hhdCB2YXJpYWJpbGl0eSBvZiBnZW5kZXIgaXMgaXQgZXhwbGFpbmluZz8pDQoNCiMjIyBTdW1tYXJ5DQotIFVzaW5nIGxpbmVhciByZWdyZXNzaW9uIG9uIHRoaXMgZGF0YSBwcm9kdWNlZCBvZGQgcHJlZGljdGlvbnMgb3V0c2lkZSBvZiB0aGUgYm91bmRlZCByYW5nZSwgdmlvbGF0aW9uIG9mIGhvbW9zY2VkYXN0aWNpdHksIGFuZCBhbiAkUl4yJCB3aGljaCBtYWtlcyBubyBzZW5zZSAgIA0KLSBJbnN0ZWFkIGxldHMgdHJ5IGEgR0xNLCBidXQgZmlyc3QgbGV0cyB1bmRlcnN0YW5kIHRoZSBiaW5vbWlhbCBkaXN0cmlidXRpb24gYW5kIGxvZ2l0IGxpbmsgZnVuY3Rpb24NCg0KIyBMb2dpc3RpYyBSZWdyZXNzaW9uDQotIExvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgdGhhdCB3ZSBjYWxsIHRoZSByZWdyZXNzaW9uIHdoZXJlIHdlIGFuYWx5emUgYmlub21pYWwgRFYNCg0KIyMgQmlub21pYWwgRGlzdHJpYnV0aW9uIA0KLSBCaW5vbWlhbCBjYW4gYmUgZXhwcmVzc2VkIGFzIGZvbGxvd3M6IA0KJCRwKG58TikgPSAoXGZyYWN7Tn17bn0pcF5uKDEtcF57Ti1ufSkkJA0KLSB3aGVyZSAkbiQgPSBzdWNjZXNzZXMgaW4gJE4kIHRyaWFscywgYXQgYSBzcGVjaWZpYyAkcCQgcHJvYmFiaWxpdHkNCi0gVGhlc2UgY2hhbmdlIGFzIGZ1bmN0aW9uIG9mIHRoZSB1bmRlcmx5aW5nIHByb2JhYmlsaXR5IG9mIGdldHRpbmcgYSAwIG9yIDENCi0gVGhlIHBsb3QgYmVsb3cgaGFzIE49MTAgcGVvcGxlIG1ha2luZyAxIHJlc3BvbnNlIGVhY2gsIHdpdGggcHJvYmFiaWxpdHkgY2hhbmdpbmcgZnJvbSAwIHRvIDEgYnkgLjI1IA0KDQpgYGB7ciwgZmlnLndpZHRoPTcuNSwgZmlnLmhlaWdodD0zLjB9DQoNCnBhcihtZnJvdz1jKDEsIDUpKQ0KZm9yKHAgaW4gc2VxKDAsIDEsIGxlbj01KSkNCnsNCiAgICB4IDwtIGRiaW5vbSgwOjEwLCBzaXplPTEwLCBwPXApDQogICAgYmFycGxvdCh4LCBuYW1lcy5hcmc9MDoxMCwgc3BhY2U9MCkNCn0NCg0KYGBgDQoNCi0gQXMgdGhlIG51bWJlciBvZiBwZW9wbGUgaW5jcmVhc2VzIHlvdSB3aWxsIG5vdGljZSBpdCBsb29rcyBub3JtYWwsIGhlcmUgaXMgMTAwIHJlc3BvbnNlcw0KDQpgYGB7ciwgZmlnLndpZHRoPTcuNSwgZmlnLmhlaWdodD0zLjB9DQpwYXIobWZyb3c9YygxLCA1KSkNCmZvcihwIGluIHNlcSgwLCAxLCBsZW49NSkpDQp7DQogICAgeCA8LSBkYmlub20oMDoxMDAsIHNpemU9MTAwLCBwPXApDQogICAgYmFycGxvdCh4LCBuYW1lcy5hcmc9MDoxMDAsIHNwYWNlPTApDQp9DQpgYGANCg0KIyMgTG9naXQgTGlua2luZyBGdW5jdGlvbg0KLSBXZSBjYW4gYm91bmQgb3VyIHJlc3VsdHMgbWFraW5nIG91ciBiZXN0IGZpdCBsaW5lIGFzeW1wdG90aWMgdG8gdGhlIGJvdW5kYXJ5IGNvbmRpdGlvbnMNCi0gVG8gbWFrZSB0aGlzIHdvcmsgd2UgbmVlZCB0byBzd2l0Y2ggZnJvbSBzdHJhaWdodCBsaW5lcyB0byBzaWdtb2lkIA0KLSBBbHNvIHJlZ3Jlc3Npb24gd2FudCB0aGUgRFYgdG8gYmUgYmV0d2VlbiAkLVxpbmZ0eSxcaW5mdHkkLCBzbyBoYXZlIHdpbGwgaGF2ZSB0byB0cmFuc2Zvcm0NCi0gJCRMb2dpdCA9IGxvZ1xmcmFje3B9ezEtcH0kJCANCi0gTm90ZTogSSB3aWxsIHVzZSBMb2cgZm9yIG5hdHVyYWwgbG9nIHRvIGJlIGNvbnNpc3RhbnQgd2l0aCBSIChidXQgaW4gbW9zdCB0ZXh0IHlvdSB3aWxsIHNlZSBuYXR1cmFsIGxvZyBhcyBMTikNCg0KYGBge3IsIGZpZy53aWR0aD0zLjUsIGZpZy5oZWlnaHQ9My4wfQ0KbG9naXQuVHJhbnNmb3JtPC1mdW5jdGlvbihwKSB7bG9nKHAvKDEtcCkpIH0NCg0KcGxvdChsb2dpdC5UcmFuc2Zvcm0oc2VxKDAsMSwuMDAwMSkpLHNlcSgwLDEsLjAwMDEpLA0KICAgICBtYWluPSJMb2dpdCBUcmFuc2Zvcm0iLHlsaW0gPSBjKDAsMSksDQogIHhsYWI9IkxvZ2l0Iix5bGFiPSJQcm9iYWJpbGl0eSIpDQoNCmBgYA0KDQoNCiMjIEZpdCB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiANCi0gJGxvZ2l0KEdlbmRlcikgPSBCXzEoSGFpckxlbmd0aCkgK0JfMihiYWdneSkgKyBCXzAkDQotIFRoaXMgY2FuIGJlIGFjY29tcGxpc2hlZCBieSBjaGFuZ2luZyB0aGUgKmZ1bmN0aW9uKiBpbiBSIHRvICoqZ2xtKiogZnJvbSAqKmxtKiogYW5kIHNwZWNpZnlpbmcgdGhlICpmYW1pbHkqIGFzICoqYmlub21pYWwobGluayA9ICJsb2dpdCIpKioNCi0gRmlyc3QgbGV0cyBwbG90IGFuZCB0aGVuIG1ha2Ugc2Vuc2Ugb2YgdGhlIHBhcmFtZXRlcnMgYWZ0ZXJ3YXJkcw0KDQpgYGB7ciwgZWNobz1UUlVFLCB3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0UscmVzdWx0cz0nYXNpcyd9DQpMUi4xPC1nbG0oR2VuZGVyfkhhaXJMZW5ndGgrYmFnZ3ksZGF0YT1Mb2dpc3RpY1N0dWR5MSwgZmFtaWx5PWJpbm9taWFsKGxpbmsgPSAibG9naXQiKSkNCg0Kc3RhcmdhemVyKExSLjEsdHlwZT0iaHRtbCIsDQogICAgICAgICAgY29sdW1uLmxhYmVscyA9IGMoIkxvZ2lzdGljIiksDQogICAgICAgICAgaW50ZXJjZXB0LmJvdHRvbSA9IEZBTFNFLA0KICAgICAgICAgIHNpbmdsZS5yb3c9RkFMU0UsIA0KICAgICAgICAgIG5vdGVzLmFwcGVuZCA9IEZBTFNFLA0KICAgICAgICAgIGhlYWRlcj1GQUxTRSkNCg0KYGBgDQoNCg0KYGBge3IsIGZpZy53aWR0aD0zLjUsIGZpZy5oZWlnaHQ9My4wfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KZ2dwbG90KExvZ2lzdGljU3R1ZHkxLCBhZXMoeD1IYWlyTGVuZ3RoLCB5PUdlbmRlcikpICsgZ2VvbV9wb2ludCgpICsgDQogIHN0YXRfc21vb3RoKG1ldGhvZD0iZ2xtIiwgbWV0aG9kLmFyZ3M9bGlzdChmYW1pbHk9ImJpbm9taWFsIiksIHNlPUZBTFNFKSsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCmdncGxvdChMb2dpc3RpY1N0dWR5MSwgYWVzKHg9YmFnZ3ksIHk9R2VuZGVyKSkgKyBnZW9tX3BvaW50KCkgKyANCiAgc3RhdF9zbW9vdGgobWV0aG9kPSJnbG0iLCBtZXRob2QuYXJncz1saXN0KGZhbWlseT0iYmlub21pYWwiKSwgc2U9RkFMU0UpKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KYGBgDQoNCiMjIFBsb3QgaW4gcHJvYmFiaWxpdGllcyANCi0gWS1heGlzID0gUHJlZGljdGVkIHByb2JhYmlsaXR5IG9mIGdlbmRlciBhcyBhIGZ1bmN0aW9uIG9mIGhhaXIgbGVuZ3RoDQoNCmBgYHtyLCBmaWcud2lkdGg9NS41LCBmaWcuaGVpZ2h0PTMuMH0NCmxpYnJhcnkoZWZmZWN0cykNClByZWRpY3RlZERhdGE8LWFsbEVmZmVjdHMoTFIuMSkNCnBsb3QoUHJlZGljdGVkRGF0YSkNCmBgYA0KDQotIEhvdyBkaWQgd2UgZ2V0IHRoZSBwcm9iYWJpbGl0eSBmcm9tIGxvZ2l0Pw0KDQojIyBJbnRlcnByZXQgY29lZmZpY2llbnRzDQotIFJhdyBjb2VmZmljaWVudHMgYXJlIHRyYW5zZm9ybWVkIGFuZCB0aGlzIGhhcmQgdG8gbWFrZXMgc2Vuc2Ugb2YNCi0gU28gd2UgY2FuIHRyYW5zZm9ybSB0aGVtIGJhY2sgdG8gc29tZXRoaW5nIG1lYW5pbmdmdWw7IG9kZHMgb3IgcHJvYmFiaWxpdGllcw0KLSBPZGRzID0gc3VjY2VzcyBhcmUgZGVmaW5lZCBhcyB0aGUgcmF0aW8gb2YgdGhlIHByb2JhYmlsaXR5IG9mIHN1Y2Nlc3Mgb3ZlciB0aGUgcHJvYmFiaWxpdHkgb2YgZmFpbHVyZQ0KLSA1MCUgY2hhbmNlID0gT2RkcyBpcyAxIHRvIDENCiAkJE9kZHMgPSBlXntsb2dpdCh4KX0kJCANCg0KYGBge3IsIGZpZy53aWR0aD0zLjUsIGZpZy5oZWlnaHQ9My4wfQ0KTC50by5PLlRyYW5zZm9ybTwtZnVuY3Rpb24ocCkge2V4cChsb2dpdC5UcmFuc2Zvcm0ocCkpfQ0KDQpwbG90KChsb2dpdC5UcmFuc2Zvcm0oc2VxKDAsLjk5LC4wMSkpKSxMLnRvLk8uVHJhbnNmb3JtKHNlcSgwLC45OSwuMDEpKSwNCiAgICAgbWFpbj0iTG9naXQgdG8gT2RkcyBUcmFuc2Zvcm0iLA0KICB4bGFiPSJMb2dpdCIseWxhYj0iT2RkcyIpDQoNCmBgYA0KLSBoZXJlIGlzIG91ciByZWdyZXNzaW9uIGVzdGltYXRlcyBhcyBvZGRzDQoNCmBgYHtyLCBmaWcud2lkdGg9My41LCBmaWcuaGVpZ2h0PTMuMH0NCkxSLjEuVHJhbnMgPC0gY29lZihzdW1tYXJ5KExSLjEpKQ0KTFIuMS5UcmFuc1ssICJFc3RpbWF0ZSJdIDwtIGV4cChjb2VmKExSLjEpKQ0KTFIuMS5UcmFucw0KYGBgDQoNCg0KLSBNRHMgdGFsayBpbiBvZGRzLCBidXQgcHN5Y2hvbG9naXN0cyBwcmVmZXIgcHJvYmFiaWxpdGllcw0KJCRQID0gXGZyYWN7T2Rkc30gezEgKyBPZGRzfSQkDQoNCmBgYHtyLCBmaWcud2lkdGg9My41LCBmaWcuaGVpZ2h0PTMuMH0NCk8udG8uUC5UcmFuc2Zvcm08LWZ1bmN0aW9uKHApIHtMLnRvLk8uVHJhbnNmb3JtKHApLygxK0wudG8uTy5UcmFuc2Zvcm0ocCkpfQ0KDQpwbG90KEwudG8uTy5UcmFuc2Zvcm0oc2VxKDAsLjk0LC4wMSkpLE8udG8uUC5UcmFuc2Zvcm0oc2VxKDAsLjk0LC4wMSkpLA0KICAgICBtYWluPSJPZGRzIHRvIFByb2JhYmxpdHkgVHJhbnNmb3JtIiwNCiAgeGxhYj0iT2RkcyIseWxhYj0iUHJvYmFibGl0eSIpDQoNCmBgYA0KDQotIFNvIHdlIGNhbiBjb252ZXJ0IG91ciByZWdyZXNzaW9uIGVxdWF0aW9uIHRvIGdpdmUgb3VyIHByb2JhYmlsaXRpZXMgZGlyZWN0bHkNCg0KJCRwX3soR2VuZGVyKX0gPVxmcmFje2VeeyhCXzEoSGFpckxlbmd0aCkrQl8yKGJhZ2d5KSArIEJfMH0pfXsxK2VeeyhCXzEoSGFpckxlbmd0aCkrQl8yKGJhZ2d5KSArIEJfMH0pfSQkDQoNCi0gb3IgbW9yZSBzaW1wbHkhDQoNCiQkcF97KEdlbmRlcil9ID1cZnJhY3sxfXsxK2Veey0oQl8xKEhhaXJMZW5ndGgpK0JfMihiYWdneSkgKyBCXzB9KX0kJA0KDQoNCg0KYGBge3IsIGZpZy53aWR0aD0zLjUsIGZpZy5oZWlnaHQ9My4wfQ0KTFIuMS5UcmFuc1AgPC0gY29lZihzdW1tYXJ5KExSLjEpKQ0KTFIuMS5UcmFuc1BbLCAiRXN0aW1hdGUiXSA8LSBleHAoY29lZihMUi4xKSkvKDErZXhwKGNvZWYoTFIuMSkpKQ0KTFIuMS5UcmFuc1ANCmBgYA0KDQotIEhvd2V2ZXIsIHlvdSBoYXZlIHRvIHJlcG9ydCB0aGUgcmF3IHZhbHVlcyB0aGF0IGNvbWUgZnJvbSB0aGUgbG9naXN0aWMgcmVncmVzc2lvbg0KLSBUaGlzIGlzIGJlY2F1c2UgeW91ciB1bml0cyBvZiBoYWlyIGxlbmd0aCBhcmUgaW4gcmF3IHVuaXRzIGFuZCBpZiB5b3UgdXNlZCB0aGlzIHRhYmxlIHlvdSB3b3VsZCBuZWVkIHRvIGZpZ3VyZSBvdXQgaG93IHRvIGNvbnZlcnQgYWxsIHlvdXIgSVYgdW5pdHMNCi0gVGh1cywgeW91IHdvdWxkIHNpbXBseSB1c2UgdGhlIGVxdWF0aW9ucyBhYm92ZSB3aGVuIHlvdSBhZGQgdXAgeW91ciBwcmVkaWN0b3JzDQotIFNvIGp1c3QgbGluayAgaW4gbGluZWFyIHJlZ3Jlc3Npb24geW91IHdvdWxkIGFkZCB5b3VyIHByZWRpY3RvciBlc3RpbWF0ZXMgKGxldHMgc2F5IGlmIHlvdSBoYXMgbm9taW5hbCB2YXJpYWJsZXMgYW5kIGludGVyYWN0aW9ucykgYW5kIHRoYW4gY29udmVydCB0aGVtIGFzIGFib3ZlDQoNCiMjIFdhaXQgLSBXaGF0IGFib3V0IG15IFItc3F1YXJlZD8NCi0gJFJeMiQgaGFzIG5vIG1lYW5pbmcgaGFzIG5vIG1lYW5pbmcgaW4gdGhlc2UgbW9kZWxzDQotIFRoZXkgZG8gbm90IG1lYXN1cmUgdGhlIGFtb3VudCBvZiAqKnZhcmlhbmNlIGFjY291bnRlZCBmb3IqKg0KLSBCaW5vbWluYWwgZGF0YSBjYW5ub3QgYmUgaG9tb3NjZWRhc3RpYywgaW4gZmFjdCB5b3UgY291bGQgc2VlIHRoZXJlIGlzIG5vIHNwcmVhZCBvZiBkYXRhIGFyb3VuZCB0aGUgbGluZSBhbmQgYWxzbyB0aGUgdmFyaWFuY2UgYXQgZWFjaCB2YWx1ZSBtYXkgbm90IGJlIHNhbWUgDQotIE92ZXIgdGhlIHllYXJzIHBlb3BsZSBoYXZlIGNyZWF0ZWQgZGlmZmVyZW50IHR5cGVzIG9mIHBzZXVkby0kUl4yJCBpbiB3aGljaCB0byB0cnkgdG8gbWFrZSBsaW5lYXIgcmVncmVzc2lvbiB0eXBlcyBvZiB1bmRlcnN0YW5kaW5nIA0KLSBDb21tb24gZmF2b3JpdGVzIGFyZSBDb3ggJiBTbmVsbCAoYWthIE1heGltdW0gbGlrZWxpaG9vZCksIE5hZ2Vsa2Vya2UgKGFrYSBDcmFnZyBhbmQgVWhsZXIncyksIE1jRmFkZGVuLCBFZnJvbidzLCBldGMNCi0gWW91IGNhbiBzZWUgYW4gZWFzeSBkZXNjcmlwdGlvbiBvZiBlYWNoIHR5cGUgaGVyZTogaHR0cDovL3N0YXRzLmlkcmUudWNsYS5lZHUvb3RoZXIvbXVsdC1wa2cvZmFxL2dlbmVyYWwvZmFxLXdoYXQtYXJlLXBzZXVkby1yLXNxdWFyZWRzLw0KLSBFYWNoIG9mIHRoZW0gdHJ5IHRvIGNhcHR1cmUgc29tZXRoaW5nIGxpa2Ugb3VyIG9yaWdpbmFsICRSXjIkLCBzdWNoIGFzIChBKSBleHBsYWluaW5nIHZhcmlhYmlsaXR5LCAoQikgbW9kZWwgaW1wcm92ZW1lbnQgc3VjaCBhcyAkY2hhbmdlIGluIFJeMiQgYmV0d2VlbiBtb2RlbHMsIChDKSBhbmQgZmluYWxseSBhcyBtZWFzdXJlIG9mIG11bHRpcGxlLWNvcnJlbGF0aW9uDQotIE1hbnkgb2YgdGhlc2UgYmV0d2VlbiB0ZXN0aW5nIGJldHdlZW4gYSByZXN0cmljdGVkIG1vZGVsIChOdWxsIG1vZGVsKSBhbmQgeW91ciBtb2RlbCwgc28gZmlyc3Qgd2UgbmVlZCB0byBleGFtaW5lIG1vZGVsIGZpdHRpbmcgZm9yIEdMTXMNCg0KIyMjIERldmlhbmNlIFRlc3RpbmcgDQotIEluICBsaW5lYXIgcmVncmVzc2lvbiwgT0xTIHdhcyB0aGUgZml0dGluZyBzeXN0ZW0gYW5kIGl0IHdvcmtlZCBiZWNhdXNlIE9MUyBjYW4gZmluZCBtZWFuIGFuZCB2YXJpYW5jZSBvZiB0aGUgbm9ybWFsIGRpc3RyaWJ1dGlvbiwgYnV0IG5vdyB3ZSBhcmUgbm90IHdvcmtpbmcgd2l0aCBub3JtYWwgZGlzdHJpYnV0aW9ucywgc28gd2UgbmVlZCBhIG5ldyBmaXR0aW5nIHByb2NlZHVyZQ0KLSBJbiBvdXIgT0xTIHJlZ3Jlc3Npb24gd2UgaGFkIHRoaXMgY29uY2VwdDogJCRTU197UmVzaWR9ID0gU1NfWSAtIFNTX3tSZWdyZXNzaW9ufSQkDQotIEluIG90aGVyIHdvcmRzLCByZXNpZHVhbCA9IEFjdHVhbCAtIEZpdHRlZCB2YWx1ZXMgDQotIGluIE9MUywgd2UgY2FuIGdldCB0aGUgYmVzdCBmaXQgKmFuYWx5dGljYWxseSogKHNvbHZpbmcgYW4gZXF1YXRpb24pLCBidXQgd2l0aCBHTE0geW91IGNhbm5vdCBkbyB0aGF0DQotIFdlIGNhbiBjYWxjdWxhdGUgKmRldmlhbmNlKiBvZiBzY29yZXMgd2hpY2ggaXMgYnVpbHQgb24gdGhlIGlkZWEgb2YgbWF4aW11bSBsaWtlbGlob29kDQotIFNvIHdlICppdGVyYXRlKiBhIHNvbHV0aW9uIChzaW5jZSB3ZSBjYW5ub3Qgc29sdmUgaXQgd2l0aG91dCB0cmlhbCBhbmQgZXJyb3IpDQotIFdlIG5lZWQgdG8gZmluZCB0aGUgbGlrZWxpaG9vZDsgd2hpY2ggaXMgImEgaHlwb3RoZXRpY2FsIHByb2JhYmlsaXR5IHRoYXQgYW4gZXZlbnQgdGhhdCBoYXMgKiphbHJlYWR5Kiogb2NjdXJyZWQgd291bGQgeWllbGQgYSBzcGVjaWZpYyBvdXRjb21lIiAoaHR0cDovL21hdGh3b3JsZC53b2xmcmFtLmNvbS9MaWtlbGlob29kLmh0bWwpDQotIFdlIHdpbGwgaXRlcmF0ZSB0aHJvdWdoIHBhcmFtZXRlcnMgdW50aWwgd2UgbWF4aW1pemUgb3VyIGxpa2VsaWhvb2QgKGNhbGxlZCBtYXhpbWFsIGxpa2VsaWhvb2QgZXN0aW1hdGlvbikgDQotIFRoZXJlIGFyZSBkaWZmZXJlbnQgTUwgKG9yIGp1c3QgTCkgZnVuY3Rpb25zIHRoYXQgY2FuIGJlIHVzZWQgYW5kIHRoZXkgY2FuIGFwcGx5IHRvIG1vc3QgZGlzdHJpYnV0aW9ucw0KLSBXaGVuIHRoZSBmaXQgaXMgcGVyZmVjdCwgJCRMX3twZXJmZWN0fSA9IDEkJA0KLSBUaGUgbnVsbCBjYXNlIChzdGFydGluZyBtb2RlbCkgdGhhdCBPTkxZIGhhcyBhbiBpbnRlcmNlcHQsIHRoaXMgd2lsbCBwcm9iYWJseSB5aWVsZCB0aGUgbG93ZXN0IGxpa2VsaWhvb2QNCi0gT3VyIHRlc3QgbW9kZWwgd2lsbCBiZSBpbnRlcmNlcHQgKyBwcmVkaWN0b3JzIChwYXJhbWV0ZXJzIC0gaykNCiQkTGlrZWxpaG9vZCByYXRpbyA9XGZyYWN7TF97U2ltcGxlfX17TF97Q29tcGxleH19JCQNCi0gRGV2aWFuY2UgdGhpcyBpcyB0aGUgLTIgeCBuYXR1YWwgbG9nTGlrZWxpaG9vZCByYXRpbyAgICQkRCA9IDIqbG9nKExpa2VsaWhvb2QgcmF0aW8pJCQgQUtBICQkRCA9IC0yTEwkJA0KRmlyc3QgSSB3aWxsIHNob3cgeW91IHRoZSBwc2V1ZG8tJFJeMiQgYW5kIHRoYW4gd2Ugd2lsbCBleGFtaW5lIGhvdyB0byB0ZXN0IGJldHdlZW4gbW9kZWwgZml0cw0KDQotIE51bGwgRGV2aWFuY2UgDQokJERfe051bGx9ID0gLTJbbG9nKExfe051bGx9KSAtIGxvZyhMX3tQZWZlY3R9KV0kJA0KDQotIE1vZGVsIERldmlhbmNlDQokJERfe0t9ID0gLTJbbG9nKExfe0t9IC0gbG9nKExfe1BlZmVjdH0pXSQkDQotIHRoaXMgaXMgbGlrZSBvdXIgU1MgcmVzaWR1YWwgZnJvbSBPTFMNCg0KIyMjIFBzZXVkby1SLXNxYXVyZWQNCi0gV2UgbmVlZCB0aG9zZSBjcmF6eSBEZXZpZW5jZSBzY29yZXMgZm9yIHNvbWUgb2Ygb3VyIHBzZXVkby0kUl4yJCBtZWFzdXJlbWVudHMNCi0gZm9yIGV4YW1wbGUsIA0KJCRSX0xeMiA9IFxmcmFje0Rfe051bGx9IC1EX3trfX17RF97TnVsbH19JCQNCi0gQ294IGFuZCBTbmVsbCBpcyB3aGF0IFNQU1MgZ2l2ZXMgYW5kIHBlb3BsZSByZXBvcnQgaXQgb2Z0ZW4NCi0gTmFnZWxrZXJrZSBpcyBhbiBpbXByb3ZlbWVudCAoQyZTKSBhcyBpdCBjb3JyZWN0cyBzb21lIHByb2JsZW1zDQotIGJ1dCBwZW9wbGUgbGlrZSBNY0ZhZGRlbidzIGNhdXNlIGl0cyBlYXN5IHRvIHVuZGVyc3RhbmQNCiQkUl97TWNGYWRkZW59XjIgPSAxIC0gXGZyYWN7TExfe2t9fXtMTF97TnVsbH19JCQNCiANCi0gV2UgY2FuIGp1c3QgZ2V0IHRoZW0gZnJvbSB0aGUgcHNjbCBwYWNrYWdlDQotIGxsaCA9ICBsb2ctbGlrZWxpaG9vZCBmcm9tIHRoZSBmaXR0ZWQgbW9kZWwgKGxsayBhYm92ZSkNCi0gbGxoTnVsbCA9IFRoZSBsb2ctbGlrZWxpaG9vZCBmcm9tIHRoZSBpbnRlcmNlcHQtb25seSByZXN0cmljdGVkIG1vZGVsDQotICRHXjIgPSAtMihMTF97S30gLSBMTF97TlVMTH0pJCBpcyBhIG9uZSBvZiB0aGUgcHJvcG9zZWQgZ29vZG5lc3Mgb2YgZml0IG1lYXN1cmVzICh3ZSB3aWxsIGNvbWUgYmFjayB0byB0aGlzIGxhdGVyKQ0KLSBNY0ZhZGRlbiA9IE1jRmFkZGVuIHBzZXVkby0kUl4yJA0KLSByMk1MID0gQ294ICYgU25lbGwgcHNldWRvLSRSXjIkDQotIHIyQ1UgPSBOYWdlbGtlcmtlIHBzZXVkby0kUl4yJA0KYGBge3IsIGVjaG89VFJVRSwgd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShwc2NsKQ0KcFIyKExSLjEpDQoNCmBgYA0KIA0KIyMjIFdhaXQgd2h5IEkgZG8gc2VlIFogYW5kIG5vdCB0LXZhbHVlcz8NCi0gV2hlbiB0ZXN0aW5nIGluZGl2aWR1YWwgcHJlZGljdG9ycywgeW91IGFyZSBub3Qgc2VlaW5nIHQtdGVzdHMgKG9uZSBzYW1wbGUgdC10ZXN0cyBhZ2FpbnN0IDApIHlvdSBhcmUgbG9va2luZyBhdCBXYWxkIFotc2NvcmVzDQotIFNvbWUgYXJndWUgdGhhdCBpbmRpdmlkdWFsIHByZWRpY3RvcnMgc2hvdWxkIHRlc3RlZCBhZ2FpbnN0IGEgbW9kZWwgdGhhdCBkb2VzIG5vdCBoYXZlIHRoYXQgdGVybSAobGlrZSBhIHN0ZXB3aXNlIHJlZ3Jlc3Npb24pLCBidXQgb3VyIHByb2dyYW1zIHdpbGwgY2FsY3VsYXRlIGEgdGVzdC1zdGF0aXN0aWNzIGJhc2VkIG9uIGVhY2ggcHJlZGljdG9yIGluIHRoZSBtb2RlbCAobW9yZSBwZW9wbGUgaWdub3JlIHRoZXNlIGFuZCBqdXN0IGxvb2sgYXQgY2hhbmdlIGluIG92ZXJhbGwgbW9kZWwgZml0KSANCiANCiAkJCBXYWxkID0gXGZyYWN7Ql9qfXtTRV97Ql9qfV4yfSQkDQogDQotIFdhbGQgZm9sbG93cyBhIGNoaS1zcXVhcmUgZGlzdHJpYnV0aW9uDQoNCi0gT3IgeW91IGNhbiBydW4gYSBzdGVwd2lzZSBhbmQgdGVzdCBlYWNoIHByZWRpY3RvciBhZ2FpbiB0aGUgbnVsbCBtb2RlbCBsaWtlIHRoaXMgKG9yZGVyIG9mIHByZWRpY3RvcnMgbWF0dGVyKQ0KDQpgYGB7ciwgZWNobz1UUlVFLCB3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQphbm92YShMUi4xLCB0ZXN0PSJDaGlzcSIpDQpgYGANCiANCiMjIEhpZXJhcmNoaWNhbCB0ZXN0aW5nIA0KLSBHb2luZyBzdGVwd2lzZSBjYW4gYmUgZGlmZmljdWx0IGlmIHlvdSBoYXZlIGxvdHMgb2YgcHJlZGljdG9ycw0KLSBTaW5jZSB3ZSBjYW5ub3QgdGVzdCB0aGUgY2hhbmdlIGluICRSXjIkIHdlIHdpbGwgaW5zdGVhZCB0ZXN0IHdoZXRoZXIgdGhlIGRldmlhbmNlIGlzIHNpZ25pZmljYW50bHkgZ3JlYXRlciB0aGFuIHRoZSBtb2RlbCB3aXRob3V0IHRoZSBwcmVkaWN0b3IgKGp1c3QgbGlrZSBhYm92ZSkNCi0gU28gd2UgcnVuIGxpa2VsaWhvb2QgcmF0aW8gdGVzdCBiZXR3ZWVuIHRoZSBtb2RlbHMgd2hpY2ggdGVzdHMgYWdhaW5zdCB0aGUgY2hpLXNxdWFyZSBkaXN0cmlidXRpb24NCg0KYGBge3IsIGVjaG89VFJVRSwgd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KTFIuTW9kZWwuMTwtZ2xtKEdlbmRlcn5iYWdneSxkYXRhPUxvZ2lzdGljU3R1ZHkxLCBmYW1pbHk9Ymlub21pYWwobGluayA9ICJsb2dpdCIpKQ0KTFIuTW9kZWwuMjwtZ2xtKEdlbmRlcn5iYWdneStIYWlyTGVuZ3RoLGRhdGE9TG9naXN0aWNTdHVkeTEsIGZhbWlseT1iaW5vbWlhbChsaW5rID0gImxvZ2l0IikpDQphbm92YShMUi5Nb2RlbC4xLExSLk1vZGVsLjIsdGVzdCA9ICJDaGlzcSIpDQpgYGANCg0KLSBIZXJlIHdlIHNlZSBtb2RlbCAyIGhhcyBpbXByb3ZlbWVudCBpbiBkZXZpYW5jZSAoaXQgZml0cyBiZXR0ZXIpIGFuZCB0aHVzIGl0IG1lYW5zIGhhaXIgbGVuZ3RoIGRpZCBoZWxwIHRoZSBwcmVkaWN0aW9uDQoNCiMjIEhvdyB3ZWxsIGFtIEkgcHJlZGljdGluZz8NCi0gTW9kZWwgMQ0KYGBge3IsIGVjaG89VFJVRSwgd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KZml0dGVkLnJlc3VsdHMgPC0gcHJlZGljdChMUi5Nb2RlbC4xLG5ld2RhdGE9TG9naXN0aWNTdHVkeTEsdHlwZT0ncmVzcG9uc2UnKQ0KZml0dGVkLnJlc3VsdHMgPC0gaWZlbHNlKGZpdHRlZC5yZXN1bHRzID4gMC41LDEsMCkNCm1pc0NsYXNpZmljRXJyb3IgPC0gbWVhbihmaXR0ZWQucmVzdWx0cyAhPSBMb2dpc3RpY1N0dWR5MSRHZW5kZXIpDQpwcmludChwYXN0ZSgnQWNjdXJhY3kgPSAnLHJvdW5kKDEtbWlzQ2xhc2lmaWNFcnJvciwzKSkpDQpgYGANCi0gTW9kZWwgMg0KYGBge3IsIGVjaG89VFJVRSwgd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KZml0dGVkLnJlc3VsdHMgPC0gcHJlZGljdChMUi5Nb2RlbC4yLG5ld2RhdGE9TG9naXN0aWNTdHVkeTEsdHlwZT0ncmVzcG9uc2UnKQ0KZml0dGVkLnJlc3VsdHMgPC0gaWZlbHNlKGZpdHRlZC5yZXN1bHRzID4gMC41LDEsMCkNCm1pc0NsYXNpZmljRXJyb3IgPC0gbWVhbihmaXR0ZWQucmVzdWx0cyAhPSBMb2dpc3RpY1N0dWR5MSRHZW5kZXIpDQpwcmludChwYXN0ZSgnQWNjdXJhY3kgPSAnLHJvdW5kKDEtbWlzQ2xhc2lmaWNFcnJvciwzKSkpDQpgYGANCg0KLSBUaGlzIGlzIG5vdCB0aGUgb25seSB3YXkgdG8gZXhhbWluZSBhY2N1cmFjeQ0KLSBTbyB3ZSBoYXZlIGdldCBjb3JyZWN0IHJlc3BvbnNlcywgbWlzc2VzIGFuZCBmYWxzZSBhbGFybXMgKHJlbWVtYmVyIG91ciB0eXBlIEkgYW5kIElJIGJveGVzKQ0KLSBUbyB2aXN1YWwgdGhpcyB3ZSB3aWxsIGV4YW1pbmUgcmVjZWl2ZXIgb3BlcmF0b3IgY3VydmVzIChST0MpDQotIEl0IGlzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBjb3JyZWN0IHJlc3BvbnNlcyBhbmQgZmFsc2UgYWxhcm1zDQotIFdlIHdpbGwgY2FsY3VsYXRlIHRoZSBhcmVhIHVuZGVyIHRoZSBjdXJ2ZSB0byBnZXQgYSBnb29kIG1lYXN1cmUgb2YgYWNjdXJhY3kgKHRoZSBjbG9zZXIgdG8gMSB0aGUgYmV0dGVyKQ0KLSBBbHNvIHRoZSBjdXJ2ZSBzaG91bGQgZm9sbG93IHRoZSBzaGFwZSB5b3Ugc2VlIGJlbG93IChpZiBpdCBpcyB0aGUgb3Bwb3NpdGUgc2hhcGUgeW91IGhhdmUgYSBwcm9ibGVtKQ0KDQpgYGB7ciwgZWNobz1UUlVFLCB3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KFJPQ1IpDQpmaXR0ZWQucmVzdWx0cyA8LSBwcmVkaWN0KExSLk1vZGVsLjIsbmV3ZGF0YT1Mb2dpc3RpY1N0dWR5MSx0eXBlPSdyZXNwb25zZScpDQpwciA8LSBwcmVkaWN0aW9uKGZpdHRlZC5yZXN1bHRzLCBMb2dpc3RpY1N0dWR5MSRHZW5kZXIpDQoNCnByZiA8LSBwZXJmb3JtYW5jZShwciwgbWVhc3VyZSA9ICJ0cHIiLCB4Lm1lYXN1cmUgPSAiZnByIikNCnBsb3QocHJmKQ0KDQphdWMgPC0gcGVyZm9ybWFuY2UocHIsIG1lYXN1cmUgPSAiYXVjIikNCmF1YyA8LSBhdWNAeS52YWx1ZXNbWzFdXQ0KcHJpbnQocGFzdGUoJ0FyZWEgdW5kZXIgdGhlIEN1cnZlID0gJyxyb3VuZChhdWMsMykpKQ0KDQpgYGANCg0KDQoNCiMjIFNvbWUgbmV3IHRoaW5ncyB0byBrZWVwIGluIG1pbmQvY2hlY2sgaW4geW91ciBtb2RlbA0KDQotIFNob3VsZCBJIGRvaW5nIGxvZ2lzdGljcyBtb2RlbDogSXMgYSBzaWdtb2lkIHRoZSByaWdodCBzaGFwZT8NCi0gSG9zbWVyLUxlbWVzaG93IFRlc3Q6IEFyZSB0aGUgb2JzZXJ2ZWQgcHJvcG9ydGlvbnMgb2YgZXZlbnRzICBzaW1pbGFyIHRvIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBvZiBvY2N1cnJlbmNlIGluIHN1Ymdyb3VwcyBvZiB0aGUgZGF0YSBzZXQgdXNpbmcgYSBjaGkgc3F1YXJlIHRlc3QuIA0KLSBMaWtlIEhPViB5b3UgZG9udCB3YW50IHRoaXMgdG8gYmUgc2lnbmlmaWNhbnQgDQpgYGB7ciwgZWNobz1UUlVFLCB3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KE1LbWlzYykNCkhMZ29mLnRlc3QoZml0ID0gZml0dGVkKExSLk1vZGVsLjIpLCBvYnMgPSBMb2dpc3RpY1N0dWR5MSRHZW5kZXIpDQpgYGANCg0KDQoNCjxzY3JpcHQ+DQogIChmdW5jdGlvbihpLHMsbyxnLHIsYSxtKXtpWydHb29nbGVBbmFseXRpY3NPYmplY3QnXT1yO2lbcl09aVtyXXx8ZnVuY3Rpb24oKXsNCiAgKGlbcl0ucT1pW3JdLnF8fFtdKS5wdXNoKGFyZ3VtZW50cyl9LGlbcl0ubD0xKm5ldyBEYXRlKCk7YT1zLmNyZWF0ZUVsZW1lbnQobyksDQogIG09cy5nZXRFbGVtZW50c0J5VGFnTmFtZShvKVswXTthLmFzeW5jPTE7YS5zcmM9ZzttLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKGEsbSkNCiAgfSkod2luZG93LGRvY3VtZW50LCdzY3JpcHQnLCdodHRwczovL3d3dy5nb29nbGUtYW5hbHl0aWNzLmNvbS9hbmFseXRpY3MuanMnLCdnYScpOw0KDQogIGdhKCdjcmVhdGUnLCAnVUEtOTA0MTUxNjAtMScsICdhdXRvJyk7DQogIGdhKCdzZW5kJywgJ3BhZ2V2aWV3Jyk7DQoNCjwvc2NyaXB0Pg==