Set-up

library(tidyverse)
library(broom)
library(patchwork)

Caveats

This is very shallow discussion of non-linear models and logistic regression. We leave many details to the reader.

1 Predicting mortgage applications

Here are some mortgage applications from Boston in 1990:

data(HMDA, package = "AER")
slice_head(HMDA, n = 5)

The outcome of interest is the variable deny, indicating whether a mortgage application was denied (“no”). We might believe that the probability of denial is a function of pirat – the payment to income (PI). Applications are more likely to be denied when payments are a larger share of an applicant’s income (PI \(\rightarrow\) 1).

We can model this relationship with a linear probability model:

\[ P(\text{deny} = \text{yes} | \text{PI}) = P(\text{deny} = 1| \text{PI}) = \beta_0 + \beta_1(\text{PI}) + \epsilon \] and can estimate the parameters of our model with lm() – or we can use glm (for “generalized linear model”) and specify the outcome as gaussian (i.e. normally distributed, the standard OLS assumption):

# first mutate a numerical response variable
## in R the **response** variable always has to be numeric
## but the **features** or right-hand side variables can be characters or factors
HMDA = HMDA %>% 
  mutate(deny_numeric = ifelse(deny == "yes",1, 0))

# estimate the linear probability model (lpm)
lpm = HMDA %>% 
  glm(formula = deny_numeric ~ pirat, data = ., family = "gaussian")  
  
# summary  of the linear probability model
lpm %>% 
  summary()

Call:
glm(formula = deny_numeric ~ pirat, family = "gaussian", data = .)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-0.73070  -0.13736  -0.11322  -0.07097   1.05577  

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -0.07991    0.02116  -3.777 0.000163 ***
pirat        0.60353    0.06084   9.920  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for gaussian family taken to be 0.1013048)

    Null deviance: 250.87  on 2379  degrees of freedom
Residual deviance: 240.90  on 2378  degrees of freedom
AIC: 1308.8

Number of Fisher Scoring iterations: 2

As we suspected, higher PI ratios are positively correlated with application denials.

1.1 Issues with the linear probability model

Our model is easy to interpret because the marginal effect of PI on the probability of denial is constant. You can see this by taking the partial derivative with respect to GPA:

\[ \frac{\partial P(\text{deny} = 1| \text{PI}) }{\partial \text{PI}} = \beta_1 \]

But there are issues with this model.

The biggest issue is that it violates the OLS assumption of homoskedasticity or constant variance in the errors.

We can see this by plotting the residuals of our model: the difference between observed admissions and predicted admissions.

Let’s use broom::augment() to generate residuals from our model:

lpm %>% 
  augment() %>% 
  slice_head(n=5)

the column .resid shows us the residuals.

In linear models we assume the residuals are normally distributed with a mean of zero and standard deviation of one. And they should be independent of the “features” or right-hand side variables.

So if we plot the residuals against, say, GPA, we should no patterns whatsoever:

lpm %>% 
  augment() %>% 
  ggplot(data = ., aes(x = pirat, y = .resid)) + 
  geom_point()

but we clearly see a pattern! The assumption of homoskedasticity is very much violated.

Why is this a big deal?

Because it affects inference. The standard errors of the coefficients are wrong, which means the p-values are wrong, and our conclusions from our hypothesis tests may be wrong.

2 From linear to linear

Today we’ll explore a logistic regression – a model that improves inference in the linear probability model. It is also a commonly used technique for prediction of binary outcomes.

The logistic model is a special case of a general theory of models in linear models become non-linear models by way of a link function.

2.2 Logit

The link function in the logit model is the logistic function:

\[ P(\text{admit} = 1 | \mathbf{X}\beta) = \frac{\text{exp}( \mathbf{X}\beta)}{1 + \text{exp}( \mathbf{X}\beta)} = \frac{1}{1 + \text{exp}(-\mathbf{X}\beta)} \]

where \(\text{exp} = e\) is the natural exponent. Let’s break this down.

Take some outcome \(a\). The probability of \(a\) is \(P(a)=p_a\). The odds of \(a\) are then the ratio of the probability and its compliment:

\[ \text{odds}_a = \frac{p_a}{1 - p_a}. \]

If we take logarithms we end up with the logit or log-odds:

\[ \text{logit}(p_a) = \text{log} \frac{p_a}{1 - p_a}. \]

The goal is use some data \(\mathbf{X}\) to estimate \(p_a\). The output – the probability – has to be between zero and one. But the input – the data – may come in many flavors (e.g. discrete or continuous) up and down the real number line.

The logit gives us the transformation we want: the probability is constrained between zero and one (\(p_a \in [0,1]\)), but the log-odds or logit can be any real number (\(\text{logit}(p_a) \in (-\infty, \infty)\)).

So if we want to model \(p_a\) as function of our data, we can instead model \(\text{logit}(p_a)\):

\[ \text{logit}(p_a) = \text{log} \frac{p_a}{1 - p_a} = \mathbf{X}\beta \]

and then calculate \(p_a\) by taking the inverse logit:

\[ P (a | \mathbf{X}\beta) = \text{logit}^{-1}(\mathbf{X}\beta) = \frac{1}{1 + \text{exp}(-\mathbf{X}\beta)} = \text{logistic}(\mathbf{X}\beta) \]

and hence “logistic regression”.

To see this in action we can code up a logistic function:

link_logistic = function(x){
  probability = 1/(1 + exp(-x))
  return(probability)
}

and if we recall our linear probability results:

# summary of linear probability model
lpm %>% 
  summary()

Call:
glm(formula = deny_numeric ~ pirat, family = "gaussian", data = .)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-0.73070  -0.13736  -0.11322  -0.07097   1.05577  

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -0.07991    0.02116  -3.777 0.000163 ***
pirat        0.60353    0.06084   9.920  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for gaussian family taken to be 0.1013048)

    Null deviance: 250.87  on 2379  degrees of freedom
Residual deviance: 240.90  on 2378  degrees of freedom
AIC: 1308.8

Number of Fisher Scoring iterations: 2

The predicted probability an application will be denied for PI = 1.75 is

-0.07991 + 0.60353 * 1.81
[1] 1.012479

which is impossible! But if we feed it through our link function we get a more useable prediction:

link_logistic(-0.07991 + 0.60353 * 1.81)
[1] 0.7335051

In general the linear probability can produce predicted probabilities above 1 and below 0:

HMDA %>% 
  ggplot(data = ., aes(x = pirat, y = deny_numeric)) +
  geom_point() + 
  geom_smooth(method = "glm", method.args = list(family = "gaussian"))

But if we re-estimate the model by characterizing the response as “binomial” (as in binomial random variable), it passes the linear model through the logistic link function:

HMDA %>% 
  ggplot(data = ., aes(x = pirat, y = deny_numeric)) +
  geom_point() + 
  geom_smooth(method = "glm", method.args = list(family = binomial(link = "logit")))

and we get predictions exclusively between zero and one.

But better predictions come at the cost of harder interpretations. In the binomial model the marginal effect of PI is non-constant. You can see this in the graph: the derivative of the blue line (the marginal effect of pirat) depends on the value of pirat. That is not the case of for the Gaussian model, where the derivative of the blue line (the slope) is constant along the x-axis. In general, it makes more sense to visualize non-linear models rather than focus on coefficient estimates.

3 Logistic regression

Let’s re-estimate our model as a logistic regression. All we have to do is switch the “gaussian” flag to “binomial”. This model cannot be estimated with OLS. Instead it uses maximum likelihood, which sets up a “likelihood function” and maximizes it by finding the values of \(\beta_0\) and \(\beta_1\) that solve the first-order condition (first-derivative equal to zero).

# estimate binomial probability model (bpm)
HMDA %>% 
  glm(formula = deny_numeric ~ pirat, data = ., family = binomial(link = "logit"))  %>% 
  summary()

Call:
glm(formula = deny_numeric ~ pirat, family = binomial(link = "logit"), 
    data = .)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.6583  -0.5255  -0.4705  -0.3864   2.7624  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept)  -4.0284     0.2686 -14.999  < 2e-16 ***
pirat         5.8845     0.7336   8.021 1.05e-15 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1744.2  on 2379  degrees of freedom
Residual deviance: 1660.2  on 2378  degrees of freedom
AIC: 1664.2

Number of Fisher Scoring iterations: 5

3.1 Interpretation

The big picture is the same: pirat is significantly and positively correlated with application denials.

But the exact interpretation is trickier. The number 5.8845 is not the average marginal effect like it is in the linear probability model. Instead it is the “log-odds” of denial. There is nothing intuitive about this. And more importantly we saw that the marginal effect is non-constant across PI.

It’s easier to understand non-linear models by plotting them. We’ll use broom::augment(type.predict = "response") to get the fitted values of our model and then plot them:

HMDA %>% 
  # estimate binomial probability model (bpm)
  glm(formula = deny_numeric ~ pirat, data = ., family = binomial(link = "logit"))  %>% 
  # tidy and augment the model object with fitted values (and other stuff)
  augment(type.predict = "response") %>% 
  # plot the predicted probabilities of denial against PI
  ggplot(., aes(x = pirat, y = .fitted)) + 
  geom_line()

We can interpret the results by looking at how the curve changes across values of PI. Most of the action happens between PI between 0 and 1. The probability of denial appears to ramp up at around 0.10 and then flattens out above 1. Very small changes in PI can lead to big changes in the probability an application is denied!

3.1.1 Odds ratios

You will often see coefficient estimates reported as odds ratios, or \(e^{\hat{\beta}}\) (the natural exponent of an estimated coefficient). Odds ratios make sense for discrete predictors since their derivatives don’t exist (derivatives only exist for continuous variables).

For example, what if other factors besides PI affect loan denial? If we fail to control for them our model will suffer from omitted variable bias. The HMDA data is famous for showing systematic loan denial for African-Americans, controlling for PI:

HMDA %>% 
  glm(formula = deny_numeric ~ pirat + afam, data = .,  family = binomial(link = "logit"))  %>% 
  summary()

Call:
glm(formula = deny_numeric ~ pirat + afam, family = binomial(link = "logit"), 
    data = .)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.3709  -0.4732  -0.4219  -0.3556   2.8038  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept)  -4.1256     0.2684 -15.370  < 2e-16 ***
pirat         5.3704     0.7283   7.374 1.66e-13 ***
afamyes       1.2728     0.1462   8.706  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1744.2  on 2379  degrees of freedom
Residual deviance: 1591.4  on 2377  degrees of freedom
AIC: 1597.4

Number of Fisher Scoring iterations: 5

In the data race is not continuous (there is no race continuum: an applicant either is or is not African-American). To more interpret the coefficient on afamyes (an African-American applicant) we can calculate the odds-ratio:

exp(1.2728)
[1] 3.570837

which says that on average and controlling for PI, an African-American applicant compared to a white applicant is (or rather, was – the data are from 1990) almost four times more likely to see their mortgage application rejected.

Still, it’s easier to understand this model if we plot it:

HMDA %>% 
  glm(formula = deny_numeric ~ pirat + afam, data = .,  family = binomial(link = "logit"))  %>% 
  # tidy and augment the model object with fitted values (and other stuff)
  augment(type.predict = "response") %>% 
  # plot the predicted probabilities of denial against PI
  ggplot(., aes(x = pirat, y = .fitted, color = afam)) + 
  geom_line() 

That separation between the curves is the “race effect”.

3.2 Putting it all together

Let’s estimate a model including pirat, afam, lvrat (loan-to-value ratio), mhist (credit score) and hirat (inhouse expense-to-total-income ratio)

bpm = HMDA %>% 
  glm(formula = deny_numeric ~ pirat , data = .,  family = binomial(link = "logit")) 

Now let’s get the predicted probabilities with augment(type.predict = "response")

# get a tidy data frame with predicted probabilities
bpm_predictions = bpm %>% 
  augment(type.predict = "response") 

# first five rows
bpm_predictions %>%  slice_head(n=5)

3.2.1 Predictions and error

OK, now we can make some cold predictions. Let’s use a rule-of-thumb: if a predicated probability is greater than 0.5, the application is denied. We can then calculate the error: the absolute difference between observed and predicted denials.

So we need to mutate two variables:

bpm = bpm_predictions %>% 
  # predicted denial
  mutate(predicted_deny= ifelse(.fitted > 0.5, 1, 0)) %>% 
  # squared error
  mutate(error = (deny_numeric - predicted_deny)^2)

What was our average error rate?

bpm %>% 
  summarise(mean(error))

About 12%.

4 Checkpoint

Using the nhanes data:

nhanes = read_csv("https://query.data.world/s/k5y6uqf7xhsjqwcpldplc6kvytjtoy")

build a logistic regression that estimates the probability of a heart attack (heartatk) as a function of age and sex:

# estimate the model
heartattack_model = nhanes %>% 
  glm(formula = heartatk ~ age + sex, data = ., family = binomial(link = "logit")) 

# view the results
heartattack_model %>% 
  summary()

Call:
glm(formula = heartatk ~ age + sex, family = binomial(link = "logit"), 
    data = .)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-0.7363  -0.3594  -0.1834  -0.0903   3.4689  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)    
(Intercept) -8.410505   0.321683 -26.145   <2e-16 ***
age          0.085582   0.004876  17.552   <2e-16 ***
sexMale      0.910681   0.101941   8.933   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 3861.2  on 10348  degrees of freedom
Residual deviance: 3253.6  on 10346  degrees of freedom
  (2 observations deleted due to missingness)
AIC: 3259.6

Number of Fisher Scoring iterations: 7

What are the odds that an average male (controling for age) will suffer a heart attack compared to a female?

exp(0.910681)
[1] 2.486015

Plot the predicted probability of a heart attack over time and by sex:

heartattack_model %>% 
  # tidy and augment the model object with fitted values (and other stuff)
  augment(type.predict = "response") %>% 
  # plot the predicted probabilities 
  ggplot(., aes(x = age, y = .fitted, color = sex)) + 
  geom_line() 

5 Appendix

5.1 Probit

Probit regression is the bedfellow of logistic regression and produces very similar results. Choosing between one or the other is often just a matter of taste or convention.

The probit (short for “probability unit”) model uses the Standard Normal CDF (cumulative distribution function) as the link function. Consider some outcome \(y\) and data \(\mathbf{X}\). The probit model is

\[ P(y=1 | \mathbf{X}\beta) = \Phi(\mathbf{X}\beta + \epsilon) \]

where \(\Phi \sim N(\mu=0,\sigma^2 = 1)\) is the Standard Normal CDF (e.g. \(\Phi(0) = 0.5\); half the standard normal distribution lies below \(\mu = 0\)).

If we assume \(\varepsilon \sim N(0,1)\) so that \(\mathbb{E}[\varepsilon] = 0\), then we simply have

\[ P(y=1 | \mathbf{X}\beta) = \Phi(\mathbf{X}\beta) \]

or

\[ P(y=1 | \mathbf{X}\beta) = \Phi(z \leq \mathbf{X}\beta). \]

In other words, we can think of some predicted value as a \(\mathbf{z}\)-score which gets turned into a probability by \(\Phi(\cdot)\). The coefficients therefore describe the change in the \(z\)-score (e.g., “a one-unit change in \(x\) is associated with a \(\beta_1\) change in the z-score.”)

To estimate the model just change the flag for the link function from “logit” to “probit”:

HMDA %>% 
  glm(formula = deny_numeric ~ pirat + afam + lvrat + mhist + hirat, data = .,  family = binomial(link = "probit")) %>% 
  summary()

Call:
glm(formula = deny_numeric ~ pirat + afam + lvrat + mhist + hirat, 
    family = binomial(link = "probit"), data = .)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.8770  -0.5189  -0.4110  -0.2714   2.9776  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept) -3.28622    0.22832 -14.393  < 2e-16 ***
pirat        3.13327    0.51608   6.071 1.27e-09 ***
afamyes      0.60462    0.08576   7.050 1.79e-12 ***
lvrat        1.24333    0.23344   5.326 1.00e-07 ***
mhist2       0.24238    0.08745   2.772  0.00558 ** 
mhist3       0.49048    0.24884   1.971  0.04872 *  
mhist4       0.72501    0.32045   2.262  0.02367 *  
hirat       -0.89982    0.60795  -1.480  0.13885    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1744.2  on 2379  degrees of freedom
Residual deviance: 1542.7  on 2372  degrees of freedom
AIC: 1558.7

Number of Fisher Scoring iterations: 5

The coefficients are different numbers because they represent z-scores. But the basic relationships are the same. And the estimated probability curves are similar:

# base scatter plot
base_plot = HMDA %>% 
  ggplot(data = ., aes(x = pirat, y = deny_numeric)) +
  geom_point() 

# logit
plot_logit = base_plot + 
  geom_smooth(method = "glm", method.args = list(family = binomial(link = "logit"))) + 
  labs(title = "Logistic regression")

# probit
plot_probit = base_plot + 
  geom_smooth(method = "glm", method.args = list(family = binomial(link = "probit"))) + 
  labs(title = "Probit regression")

# combine with library(patchwork)
plot_logit + plot_probit

LS0tCnRpdGxlOiAiTm9ubGluZWFyIE1vZGVscyAoQ29tcGxldGVkIE5vdGVib29rKSIKc3VidGl0bGU6ICJSIGZvciBEYXRhIFNjaWVuY2UiCmF1dGhvcjogIkxERyIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICB0aGVtZTogcmVhZGFibGUKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiAKICAgICAgY29sbGFwc2VkOiB5ZXMgICAgICAKLS0tCgojIFNldC11cCB7LnVubnVtYmVyZWR9CgpgYGB7ciBsb2FkIHBhY2thZ2VzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShicm9vbSkKbGlicmFyeShwYXRjaHdvcmspCmBgYAoKIyMgQ2F2ZWF0cyB7LX0KClRoaXMgaXMgKip2ZXJ5Kiogc2hhbGxvdyBkaXNjdXNzaW9uIG9mIG5vbi1saW5lYXIgbW9kZWxzIGFuZCBsb2dpc3RpYyByZWdyZXNzaW9uLiBXZSBsZWF2ZSBtYW55IGRldGFpbHMgdG8gdGhlIHJlYWRlci4KCiMgUHJlZGljdGluZyBtb3J0Z2FnZSBhcHBsaWNhdGlvbnMKCkhlcmUgYXJlIHNvbWUgbW9ydGdhZ2UgYXBwbGljYXRpb25zIGZyb20gQm9zdG9uIGluIDE5OTA6IAoKYGBge3IgbG9hZCBhZG1pc3Npb25zIGRhdGEsIG1lc3NhZ2U9RkFMU0V9CmRhdGEoSE1EQSwgcGFja2FnZSA9ICJBRVIiKQpzbGljZV9oZWFkKEhNREEsIG4gPSA1KQpgYGAKClRoZSBvdXRjb21lIG9mIGludGVyZXN0IGlzIHRoZSB2YXJpYWJsZSBgZGVueWAsIGluZGljYXRpbmcgd2hldGhlciBhIG1vcnRnYWdlIGFwcGxpY2F0aW9uIHdhcyBkZW5pZWQgKCJubyIpLiBXZSBtaWdodCBiZWxpZXZlIHRoYXQgdGhlIHByb2JhYmlsaXR5IG9mIGRlbmlhbCBpcyBhIGZ1bmN0aW9uIG9mIGBwaXJhdGAgLS0gdGhlIHBheW1lbnQgdG8gaW5jb21lIChQSSkuIEFwcGxpY2F0aW9ucyBhcmUgbW9yZSBsaWtlbHkgdG8gYmUgZGVuaWVkIHdoZW4gcGF5bWVudHMgYXJlIGEgbGFyZ2VyIHNoYXJlIG9mIGFuIGFwcGxpY2FudCdzIGluY29tZSAoUEkgJFxyaWdodGFycm93JCAxKS4gCgpXZSBjYW4gbW9kZWwgdGhpcyByZWxhdGlvbnNoaXAgd2l0aCBhICoqbGluZWFyIHByb2JhYmlsaXR5IG1vZGVsKio6CgokJApQKFx0ZXh0e2Rlbnl9ID0gXHRleHR7eWVzfSB8IFx0ZXh0e1BJfSkgPSBQKFx0ZXh0e2Rlbnl9ID0gMXwgXHRleHR7UEl9KSA9IFxiZXRhXzAgKyBcYmV0YV8xKFx0ZXh0e1BJfSkgKyBcZXBzaWxvbiAKJCQKYW5kIGNhbiBlc3RpbWF0ZSB0aGUgcGFyYW1ldGVycyBvZiBvdXIgbW9kZWwgd2l0aCBgbG0oKWAgLS0gb3Igd2UgY2FuIHVzZSBgZ2xtYCAoZm9yICJnZW5lcmFsaXplZCBsaW5lYXIgbW9kZWwiKSBhbmQgc3BlY2lmeSB0aGUgb3V0Y29tZSBhcyBgZ2F1c3NpYW5gIChpLmUuIG5vcm1hbGx5IGRpc3RyaWJ1dGVkLCB0aGUgc3RhbmRhcmQgT0xTIGFzc3VtcHRpb24pOgoKCmBgYHtyIGVzdGltYXRlIGxpbmVhciBtb2RlbCBhZG1pc3Npb25zfQojIGZpcnN0IG11dGF0ZSBhIG51bWVyaWNhbCByZXNwb25zZSB2YXJpYWJsZQojIyBpbiBSIHRoZSAqKnJlc3BvbnNlKiogdmFyaWFibGUgYWx3YXlzIGhhcyB0byBiZSBudW1lcmljCiMjIGJ1dCB0aGUgKipmZWF0dXJlcyoqIG9yIHJpZ2h0LWhhbmQgc2lkZSB2YXJpYWJsZXMgY2FuIGJlIGNoYXJhY3RlcnMgb3IgZmFjdG9ycwpITURBID0gSE1EQSAlPiUgCiAgbXV0YXRlKGRlbnlfbnVtZXJpYyA9IGlmZWxzZShkZW55ID09ICJ5ZXMiLDEsIDApKQoKIyBlc3RpbWF0ZSB0aGUgbGluZWFyIHByb2JhYmlsaXR5IG1vZGVsIChscG0pCmxwbSA9IEhNREEgJT4lIAogIGdsbShmb3JtdWxhID0gZGVueV9udW1lcmljIH4gcGlyYXQsIGRhdGEgPSAuLCBmYW1pbHkgPSAiZ2F1c3NpYW4iKSAgCiAgCiMgc3VtbWFyeSAgb2YgdGhlIGxpbmVhciBwcm9iYWJpbGl0eSBtb2RlbApscG0gJT4lIAogIHN1bW1hcnkoKQpgYGAKCkFzIHdlIHN1c3BlY3RlZCwgaGlnaGVyIFBJIHJhdGlvcyBhcmUgcG9zaXRpdmVseSBjb3JyZWxhdGVkIHdpdGggYXBwbGljYXRpb24gZGVuaWFscy4gCgojIyBJc3N1ZXMgd2l0aCB0aGUgbGluZWFyIHByb2JhYmlsaXR5IG1vZGVsCgpPdXIgbW9kZWwgaXMgZWFzeSB0byBpbnRlcnByZXQgYmVjYXVzZSB0aGUgbWFyZ2luYWwgZWZmZWN0IG9mIFBJIG9uIHRoZSBwcm9iYWJpbGl0eSBvZiBkZW5pYWwgaXMgY29uc3RhbnQuIFlvdSBjYW4gc2VlIHRoaXMgYnkgdGFraW5nIHRoZSBwYXJ0aWFsIGRlcml2YXRpdmUgd2l0aCByZXNwZWN0IHRvIEdQQToKCiQkClxmcmFje1xwYXJ0aWFsIFAoXHRleHR7ZGVueX0gPSAxfCBcdGV4dHtQSX0pIH17XHBhcnRpYWwgXHRleHR7UEl9fSA9IFxiZXRhXzEKJCQKCkJ1dCB0aGVyZSBhcmUgaXNzdWVzIHdpdGggdGhpcyBtb2RlbC4KClRoZSBiaWdnZXN0IGlzc3VlIGlzIHRoYXQgaXQgdmlvbGF0ZXMgdGhlIE9MUyBhc3N1bXB0aW9uIG9mICoqaG9tb3NrZWRhc3RpY2l0eSoqIG9yIGNvbnN0YW50IHZhcmlhbmNlIGluIHRoZSBlcnJvcnMuCgpXZSBjYW4gc2VlIHRoaXMgYnkgcGxvdHRpbmcgdGhlICoqcmVzaWR1YWxzKiogb2Ygb3VyIG1vZGVsOiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIG9ic2VydmVkIGFkbWlzc2lvbnMgYW5kIHByZWRpY3RlZCBhZG1pc3Npb25zLgoKTGV0J3MgdXNlIGBicm9vbTo6YXVnbWVudCgpYCB0byBnZW5lcmF0ZSByZXNpZHVhbHMgZnJvbSBvdXIgbW9kZWw6CgpgYGB7ciByZXNpZHVhbHMgbGluZWFyIHByb2JhYmlsaXR5IG1vZGVsfQpscG0gJT4lIAogIGF1Z21lbnQoKSAlPiUgCiAgc2xpY2VfaGVhZChuPTUpCmBgYAoKdGhlIGNvbHVtbiBgLnJlc2lkYCBzaG93cyB1cyB0aGUgcmVzaWR1YWxzLgoKSW4gbGluZWFyIG1vZGVscyB3ZSBhc3N1bWUgdGhlIHJlc2lkdWFscyBhcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgd2l0aCBhIG1lYW4gb2YgemVybyBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIG9uZS4gQW5kIHRoZXkgc2hvdWxkIGJlIGluZGVwZW5kZW50IG9mIHRoZSAiZmVhdHVyZXMiIG9yIHJpZ2h0LWhhbmQgc2lkZSB2YXJpYWJsZXMuCgpTbyBpZiB3ZSBwbG90IHRoZSByZXNpZHVhbHMgYWdhaW5zdCwgc2F5LCBHUEEsIHdlIHNob3VsZCBubyBwYXR0ZXJucyB3aGF0c29ldmVyOgoKYGBge3IgcGxvdCByZXNpZHVhbHMgbGluZWFyIHByb2JhYmlsaXR5IG1vZGVsfQpscG0gJT4lIAogIGF1Z21lbnQoKSAlPiUgCiAgZ2dwbG90KGRhdGEgPSAuLCBhZXMoeCA9IHBpcmF0LCB5ID0gLnJlc2lkKSkgKyAKICBnZW9tX3BvaW50KCkKYGBgCgpidXQgd2UgY2xlYXJseSBzZWUgYSBwYXR0ZXJuISBUaGUgYXNzdW1wdGlvbiBvZiBob21vc2tlZGFzdGljaXR5IGlzIHZlcnkgbXVjaCB2aW9sYXRlZC4KCldoeSBpcyB0aGlzIGEgYmlnIGRlYWw/CgpCZWNhdXNlIGl0IGFmZmVjdHMgKippbmZlcmVuY2UqKi4gVGhlIHN0YW5kYXJkIGVycm9ycyBvZiB0aGUgY29lZmZpY2llbnRzIGFyZSB3cm9uZywgd2hpY2ggbWVhbnMgdGhlIHAtdmFsdWVzIGFyZSB3cm9uZywgYW5kIG91ciBjb25jbHVzaW9ucyBmcm9tIG91ciBoeXBvdGhlc2lzIHRlc3RzICoqbWF5KiogYmUgd3JvbmcuCgojIEZyb20gbGluZWFyIHRvIGxpbmVhcgoKVG9kYXkgd2UnbGwgZXhwbG9yZSBhICoqbG9naXN0aWMgcmVncmVzc2lvbioqIC0tIGEgbW9kZWwgdGhhdCBpbXByb3ZlcyAqKmluZmVyZW5jZSoqIGluIHRoZSBsaW5lYXIgcHJvYmFiaWxpdHkgbW9kZWwuIEl0IGlzIGFsc28gYSBjb21tb25seSB1c2VkIHRlY2huaXF1ZSBmb3IgKipwcmVkaWN0aW9uKiogb2YgYmluYXJ5IG91dGNvbWVzLgoKVGhlIGxvZ2lzdGljIG1vZGVsIGlzIGEgc3BlY2lhbCBjYXNlIG9mIGEgZ2VuZXJhbCB0aGVvcnkgb2YgbW9kZWxzIGluIGxpbmVhciBtb2RlbHMgYmVjb21lIG5vbi1saW5lYXIgbW9kZWxzIGJ5IHdheSBvZiBhICoqbGluayBmdW5jdGlvbioqLgoKIyMgTGluayBmdW5jdGlvbnMKClRoZSAqbGluZWFyIHByb2JhYmlsaXR5IG1vZGVsKiB2aW9sYXRlcyB0aGUgYXNzdW1wdGlvbiBvZiBub24tY29uc3RhbnQgdmFyaWFuY2UuIEl0IGlzIGFsc28gZmxhd2VkIHdoZW4gaXQgY29tZXMgdG8gcHJlZGljdGlvbnMuIFNpbmNlIE9MUyBhc3N1bWVzIGEgY29udGludW91cyBvdXRjb21lIGJvdW5kZWQgYmVsb3cgYnkgbmVnYXRpdmUgaW5maW5pdHkgYW5kIGFib3ZlIGJ5IGluZmluaXR5LCBpdCBjYW4gZ2VuZXJhdGUgYW55IHJlYWwgdmFsdWVzLiBUaGlzIGlzIGEgcHJvYmxlbSBpZiB5b3Ugd2FudCB0byBwcmVkaWN0IHByb2JhYmlsaXRpZXMuIFRoZXkgaGF2ZSB0byBiZSBiZXR3ZWVuIDAgYW5kIDEhCgpTbyB3ZSBuZWVkIHNvbWUgbWFwcGluZyAtLSBzb21lIGZ1bmN0aW9uIC0tIHRoYXQgZW5zdXJlcyBvdXIgbW9kZWwgc3BpdHMgb3V0IHRydWUgcHJvYmFiaWxpdGllcyAodmFsdWVzIGJldHdlZW4gemVybyBhbmQgb25lKSBmb3IgYW55IGlucHV0cyB0byB0aGUgbW9kZWwgKGluIHRoaXMgY2FzZTogR1BBLCBHUkUsIGFuZCBzY2hvb2wgcmFuaykuCgpUaGlzIG1hcHBpbmcgaXMga25vd24gYXMgYSAqKmxpbmsgZnVuY3Rpb24qKiAkRihcY2RvdCkkOgoKJCQKUChcdGV4dHtkZW55fSA9IDF8IFx0ZXh0e1BJfSkgPSBGKFxiZXRhXzAgKyBcYmV0YV8xXHRleHR7UEl9KyBcZXBzaWxvbikgCiQkCgpBbGwgd2UndmUgZG9uZSBpcyBwYXNzIHRoZSBsaW5lYXIgbW9kZWwgaW50byBhICRGKFxjZG90KSQuIEJ1dCB0aGlzIGNoYW5nZXMgdGhlIG1vZGVsIGRyYW1hdGljYWxseS4gTm93IGl0J3Mgbm9uLWxpbmVhciEKClRvIHNlZSB0aGlzLCB0YWtlIHRoZSBkZXJpdmF0aXZlIG9mIEdQQSB1c2luZyB0aGUgQ2hhaW4gUnVsZToKCiQkClxmcmFje1xwYXJ0aWFsIFAoXHRleHR7ZGVueX0gPSAxfCBcdGV4dHtQSX0pfXtccGFydGlhbCBcdGV4dHtQSX19ID0gXGJldGFfMUYnKFxiZXRhXzAgKyBcYmV0YV8xXHRleHR7UEl9KyBcZXBzaWxvbikKJCQKClRoZSBtYXJnaW5hbCBlZmZlY3QgaXMgbm8gbG9uZ2VyIGNvbnN0YW50LiBJbiB0aGUgbGluZWFyIG1vZGVsIGl0IHdhcyBzaW1wbHkgJFxiZXRhXzEkIChhIGNvbnN0YW50KS4gTm93IGl0IGRlcGVuZHMgbm90IG9ubHkgb24gJFxiZXRhXzEkIGJ1dCBhbHNvIHRoZSB2YWx1ZSBvZiB0aGUgcmVzdCBvZiB0aGUgcGFyYW1ldGVycyBpbiAkRihcY2RvdCkkLiBUaGlzIHdpbGwgYmUgY2xlYXJlciB3aGVuIHdlIHZpc3VhbGl6ZSBpdC4KCllvdSBvZnRlbiBoZWFyIGFib3V0ICJwcm9iaXQiIGFuZCAibG9naXQiIG1vZGVscy4gVGhlIGNob2ljZSBiZXR3ZWVuIGEgKipwcm9iaXQqKiBvciBhICoqbG9naXQqKiBib2lscyBkb3duIHRvIG91ciBjaG9pY2Ugb2YgbGluayBmdW5jdGlvbi4gQm90aCBtb2RlbHMgd2lsbCBwcm9kdWNlIHNpbWlsYXIgcmVzdWx0cywgdGhvdWdoIHRoZSBsb2dpdCBtb2RlbCBoYXMgc29tZSBhZHZhbnRhZ2VzIHdoZW4gaXQgY29tZXMgdG8gaW50ZXJwcmV0YXRpb24uCgojIyBMb2dpdAoKVGhlIGxpbmsgZnVuY3Rpb24gaW4gdGhlIGxvZ2l0IG1vZGVsIGlzIHRoZSBbbG9naXN0aWMgZnVuY3Rpb25dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0xvZ2lzdGljX2Z1bmN0aW9uKToKCiQkClAoXHRleHR7YWRtaXR9ID0gMSB8IFxtYXRoYmZ7WH1cYmV0YSkgID0gXGZyYWN7XHRleHR7ZXhwfSggXG1hdGhiZntYfVxiZXRhKX17MSArIFx0ZXh0e2V4cH0oIFxtYXRoYmZ7WH1cYmV0YSl9ID0gXGZyYWN7MX17MSArIFx0ZXh0e2V4cH0oLVxtYXRoYmZ7WH1cYmV0YSl9CiQkCgp3aGVyZSAkXHRleHR7ZXhwfSA9IGUkIGlzIHRoZSBuYXR1cmFsIGV4cG9uZW50LiBMZXQncyBicmVhayB0aGlzIGRvd24uCgpUYWtlIHNvbWUgb3V0Y29tZSAkYSQuIFRoZSBwcm9iYWJpbGl0eSBvZiAkYSQgaXMgJFAoYSk9cF9hJC4gVGhlICoqb2RkcyoqIG9mICRhJCBhcmUgdGhlbiB0aGUgcmF0aW8gb2YgdGhlIHByb2JhYmlsaXR5IGFuZCBpdHMgY29tcGxpbWVudDoKCiQkClx0ZXh0e29kZHN9X2EgPSBcZnJhY3twX2F9ezEgLSBwX2F9LgokJAoKSWYgd2UgdGFrZSBsb2dhcml0aG1zIHdlIGVuZCB1cCB3aXRoIHRoZSAqKmxvZ2l0Kiogb3IgbG9nLW9kZHM6CgokJApcdGV4dHtsb2dpdH0ocF9hKSA9IFx0ZXh0e2xvZ30gXGZyYWN7cF9hfXsxIC0gcF9hfS4KJCQKClRoZSBnb2FsIGlzIHVzZSBzb21lIGRhdGEgJFxtYXRoYmZ7WH0kIHRvIGVzdGltYXRlICRwX2EkLiBUaGUgb3V0cHV0IC0tIHRoZSBwcm9iYWJpbGl0eSAtLSBoYXMgdG8gYmUgYmV0d2VlbiB6ZXJvIGFuZCBvbmUuIEJ1dCB0aGUgaW5wdXQgLS0gdGhlIGRhdGEgLS0gbWF5IGNvbWUgaW4gbWFueSBmbGF2b3JzIChlLmcuIGRpc2NyZXRlIG9yIGNvbnRpbnVvdXMpIHVwIGFuZCBkb3duIHRoZSByZWFsIG51bWJlciBsaW5lLgoKVGhlIGxvZ2l0IGdpdmVzIHVzIHRoZSB0cmFuc2Zvcm1hdGlvbiB3ZSB3YW50OiB0aGUgcHJvYmFiaWxpdHkgaXMgY29uc3RyYWluZWQgYmV0d2VlbiB6ZXJvIGFuZCBvbmUgKCRwX2EgXGluIFswLDFdJCksIGJ1dCB0aGUgbG9nLW9kZHMgb3IgbG9naXQgY2FuIGJlIGFueSByZWFsIG51bWJlciAoJFx0ZXh0e2xvZ2l0fShwX2EpIFxpbiAoLVxpbmZ0eSwgXGluZnR5KSQpLgoKU28gaWYgd2Ugd2FudCB0byBtb2RlbCAkcF9hJCBhcyBmdW5jdGlvbiBvZiBvdXIgZGF0YSwgd2UgY2FuIGluc3RlYWQgbW9kZWwgJFx0ZXh0e2xvZ2l0fShwX2EpJDoKCiQkClx0ZXh0e2xvZ2l0fShwX2EpID0gXHRleHR7bG9nfSBcZnJhY3twX2F9ezEgLSBwX2F9ID0gXG1hdGhiZntYfVxiZXRhCiQkCgphbmQgdGhlbiBjYWxjdWxhdGUgJHBfYSQgYnkgdGFraW5nIHRoZSBpbnZlcnNlIGxvZ2l0OgoKJCQKUCAoYSB8IFxtYXRoYmZ7WH1cYmV0YSkgPSBcdGV4dHtsb2dpdH1eey0xfShcbWF0aGJme1h9XGJldGEpID0gXGZyYWN7MX17MSArIFx0ZXh0e2V4cH0oLVxtYXRoYmZ7WH1cYmV0YSl9ID0gIFx0ZXh0e2xvZ2lzdGljfShcbWF0aGJme1h9XGJldGEpIAokJAoKYW5kIGhlbmNlICJsb2dpc3RpYyByZWdyZXNzaW9uIi4KClRvIHNlZSB0aGlzIGluIGFjdGlvbiB3ZSBjYW4gY29kZSB1cCBhIGxvZ2lzdGljIGZ1bmN0aW9uOgoKYGBge3IgbG9naXN0aWMgZnVuY3Rpb259CmxpbmtfbG9naXN0aWMgPSBmdW5jdGlvbih4KXsKICBwcm9iYWJpbGl0eSA9IDEvKDEgKyBleHAoLXgpKQogIHJldHVybihwcm9iYWJpbGl0eSkKfQpgYGAKCmFuZCBpZiB3ZSByZWNhbGwgb3VyIGxpbmVhciBwcm9iYWJpbGl0eSByZXN1bHRzOgoKYGBge3IgbHBtIHN1bW1hcnkgMn0KIyBzdW1tYXJ5IG9mIGxpbmVhciBwcm9iYWJpbGl0eSBtb2RlbApscG0gJT4lIAogIHN1bW1hcnkoKQpgYGAKClRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgYW4gYXBwbGljYXRpb24gd2lsbCBiZSBkZW5pZWQgZm9yIFBJID0gMS43NSBpcyAKCmBgYHtyIGxwbSBwcmVkaWN0aW9ufQotMC4wNzk5MSArIDAuNjAzNTMgKiAxLjgxCmBgYAp3aGljaCBpcyBpbXBvc3NpYmxlISBCdXQgaWYgd2UgZmVlZCBpdCB0aHJvdWdoIG91ciBsaW5rIGZ1bmN0aW9uIHdlIGdldCBhIG1vcmUgdXNlYWJsZSBwcmVkaWN0aW9uOgoKYGBge3IgbHBtIGxpbmsgcHJlZGljdGlvbn0KbGlua19sb2dpc3RpYygtMC4wNzk5MSArIDAuNjAzNTMgKiAxLjgxKQpgYGAKCkluIGdlbmVyYWwgdGhlIGxpbmVhciBwcm9iYWJpbGl0eSBjYW4gcHJvZHVjZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBhYm92ZSAxIGFuZCBiZWxvdyAwOgoKYGBge3IgdmlzdWFsaXplIGxwbX0KSE1EQSAlPiUgCiAgZ2dwbG90KGRhdGEgPSAuLCBhZXMoeCA9IHBpcmF0LCB5ID0gZGVueV9udW1lcmljKSkgKwogIGdlb21fcG9pbnQoKSArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJnbG0iLCBtZXRob2QuYXJncyA9IGxpc3QoZmFtaWx5ID0gImdhdXNzaWFuIikpCmBgYApCdXQgaWYgd2UgcmUtZXN0aW1hdGUgdGhlIG1vZGVsIGJ5IGNoYXJhY3Rlcml6aW5nIHRoZSByZXNwb25zZSBhcyAiYmlub21pYWwiIChhcyBpbiBiaW5vbWlhbCByYW5kb20gdmFyaWFibGUpLCBpdCBwYXNzZXMgdGhlIGxpbmVhciBtb2RlbCB0aHJvdWdoIHRoZSBsb2dpc3RpYyBsaW5rIGZ1bmN0aW9uOgoKYGBge3IgdmlzdWFsaXplIGJpbm9taWFsIG1vZGVsfQpITURBICU+JSAKICBnZ3Bsb3QoZGF0YSA9IC4sIGFlcyh4ID0gcGlyYXQsIHkgPSBkZW55X251bWVyaWMpKSArCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImdsbSIsIG1ldGhvZC5hcmdzID0gbGlzdChmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IikpKQpgYGAKCmFuZCB3ZSBnZXQgcHJlZGljdGlvbnMgZXhjbHVzaXZlbHkgYmV0d2VlbiB6ZXJvIGFuZCBvbmUuIAoKQnV0IGJldHRlciBwcmVkaWN0aW9ucyBjb21lIGF0IHRoZSBjb3N0IG9mIGhhcmRlciBpbnRlcnByZXRhdGlvbnMuIEluIHRoZSBiaW5vbWlhbCBtb2RlbCB0aGUgbWFyZ2luYWwgZWZmZWN0IG9mIFBJIGlzIG5vbi1jb25zdGFudC4gWW91IGNhbiBzZWUgdGhpcyBpbiB0aGUgZ3JhcGg6IHRoZSBkZXJpdmF0aXZlIG9mIHRoZSBibHVlIGxpbmUgKHRoZSBtYXJnaW5hbCBlZmZlY3Qgb2YgYHBpcmF0YCkgZGVwZW5kcyBvbiB0aGUgdmFsdWUgb2YgYHBpcmF0YC4gVGhhdCBpcyBub3QgdGhlIGNhc2Ugb2YgZm9yIHRoZSBHYXVzc2lhbiBtb2RlbCwgd2hlcmUgdGhlIGRlcml2YXRpdmUgb2YgdGhlIGJsdWUgbGluZSAodGhlIHNsb3BlKSBpcyBjb25zdGFudCBhbG9uZyB0aGUgeC1heGlzLiBJbiBnZW5lcmFsLCBpdCBtYWtlcyBtb3JlIHNlbnNlIHRvICoqdmlzdWFsaXplKiogbm9uLWxpbmVhciBtb2RlbHMgcmF0aGVyIHRoYW4gZm9jdXMgb24gY29lZmZpY2llbnQgZXN0aW1hdGVzLgoKIyBMb2dpc3RpYyByZWdyZXNzaW9uCgpMZXQncyByZS1lc3RpbWF0ZSBvdXIgbW9kZWwgYXMgYSBsb2dpc3RpYyByZWdyZXNzaW9uLiBBbGwgd2UgaGF2ZSB0byBkbyBpcyBzd2l0Y2ggdGhlICJnYXVzc2lhbiIgZmxhZyB0byAiYmlub21pYWwiLiBUaGlzIG1vZGVsIGNhbm5vdCBiZSBlc3RpbWF0ZWQgd2l0aCBPTFMuIEluc3RlYWQgaXQgdXNlcyAqKm1heGltdW0gbGlrZWxpaG9vZCoqLCB3aGljaCBzZXRzIHVwIGEgImxpa2VsaWhvb2QgZnVuY3Rpb24iIGFuZCBtYXhpbWl6ZXMgaXQgYnkgZmluZGluZyB0aGUgdmFsdWVzIG9mICRcYmV0YV8wJCBhbmQgJFxiZXRhXzEkIHRoYXQgc29sdmUgdGhlIGZpcnN0LW9yZGVyIGNvbmRpdGlvbiAoZmlyc3QtZGVyaXZhdGl2ZSBlcXVhbCB0byB6ZXJvKS4gCgpgYGB7ciBlc3RpbWF0ZSBicG19CiMgZXN0aW1hdGUgYmlub21pYWwgcHJvYmFiaWxpdHkgbW9kZWwgKGJwbSkKSE1EQSAlPiUgCiAgZ2xtKGZvcm11bGEgPSBkZW55X251bWVyaWMgfiBwaXJhdCwgZGF0YSA9IC4sIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSAibG9naXQiKSkgICU+JSAKICBzdW1tYXJ5KCkKYGBgCgojIyBJbnRlcnByZXRhdGlvbgoKVGhlIGJpZyBwaWN0dXJlIGlzIHRoZSBzYW1lOiBgcGlyYXRgIGlzIHNpZ25pZmljYW50bHkgYW5kIHBvc2l0aXZlbHkgY29ycmVsYXRlZCB3aXRoIGFwcGxpY2F0aW9uIGRlbmlhbHMuIAoKQnV0IHRoZSBleGFjdCBpbnRlcnByZXRhdGlvbiBpcyB0cmlja2llci4gVGhlIG51bWJlciA1Ljg4NDUgaXMgKipub3QqKiB0aGUgYXZlcmFnZSBtYXJnaW5hbCBlZmZlY3QgbGlrZSBpdCBpcyBpbiB0aGUgbGluZWFyIHByb2JhYmlsaXR5IG1vZGVsLiBJbnN0ZWFkIGl0IGlzIHRoZSAibG9nLW9kZHMiIG9mIGRlbmlhbC4gVGhlcmUgaXMgbm90aGluZyBpbnR1aXRpdmUgYWJvdXQgdGhpcy4gQW5kIG1vcmUgaW1wb3J0YW50bHkgd2Ugc2F3IHRoYXQgdGhlIG1hcmdpbmFsIGVmZmVjdCBpcyBub24tY29uc3RhbnQgYWNyb3NzIFBJLgoKSXQncyBlYXNpZXIgdG8gdW5kZXJzdGFuZCBub24tbGluZWFyIG1vZGVscyBieSBwbG90dGluZyB0aGVtLiBXZSdsbCB1c2UgYGJyb29tOjphdWdtZW50KHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpYCB0byBnZXQgdGhlIGZpdHRlZCB2YWx1ZXMgb2Ygb3VyIG1vZGVsIGFuZCB0aGVuIHBsb3QgdGhlbTogCgpgYGB7ciBlc3RpbWF0ZSBicG0gYW5kIHBsb3R9CkhNREEgJT4lIAogICMgZXN0aW1hdGUgYmlub21pYWwgcHJvYmFiaWxpdHkgbW9kZWwgKGJwbSkKICBnbG0oZm9ybXVsYSA9IGRlbnlfbnVtZXJpYyB+IHBpcmF0LCBkYXRhID0gLiwgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpKSAgJT4lIAogICMgdGlkeSBhbmQgYXVnbWVudCB0aGUgbW9kZWwgb2JqZWN0IHdpdGggZml0dGVkIHZhbHVlcyAoYW5kIG90aGVyIHN0dWZmKQogIGF1Z21lbnQodHlwZS5wcmVkaWN0ID0gInJlc3BvbnNlIikgJT4lIAogICMgcGxvdCB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgb2YgZGVuaWFsIGFnYWluc3QgUEkKICBnZ3Bsb3QoLiwgYWVzKHggPSBwaXJhdCwgeSA9IC5maXR0ZWQpKSArIAogIGdlb21fbGluZSgpCmBgYAoKV2UgY2FuIGludGVycHJldCB0aGUgcmVzdWx0cyBieSBsb29raW5nIGF0IGhvdyB0aGUgY3VydmUgY2hhbmdlcyBhY3Jvc3MgdmFsdWVzIG9mIFBJLiBNb3N0IG9mIHRoZSBhY3Rpb24gaGFwcGVucyBiZXR3ZWVuIFBJIGJldHdlZW4gMCBhbmQgMS4gVGhlIHByb2JhYmlsaXR5IG9mIGRlbmlhbCBhcHBlYXJzIHRvIHJhbXAgdXAgYXQgYXJvdW5kIDAuMTAgYW5kIHRoZW4gZmxhdHRlbnMgb3V0IGFib3ZlIDEuIFZlcnkgc21hbGwgY2hhbmdlcyBpbiBQSSBjYW4gbGVhZCB0byBiaWcgY2hhbmdlcyBpbiB0aGUgcHJvYmFiaWxpdHkgYW4gYXBwbGljYXRpb24gaXMgZGVuaWVkIQoKIyMjIE9kZHMgcmF0aW9zCgpZb3Ugd2lsbCBvZnRlbiBzZWUgY29lZmZpY2llbnQgZXN0aW1hdGVzIHJlcG9ydGVkIGFzICoqb2RkcyByYXRpb3MqKiwgb3IgJGVee1xoYXR7XGJldGF9fSQgKHRoZSBuYXR1cmFsIGV4cG9uZW50IG9mIGFuIGVzdGltYXRlZCBjb2VmZmljaWVudCkuIE9kZHMgcmF0aW9zIG1ha2Ugc2Vuc2UgZm9yICoqZGlzY3JldGUqKiBwcmVkaWN0b3JzIHNpbmNlIHRoZWlyIGRlcml2YXRpdmVzIGRvbid0IGV4aXN0IChkZXJpdmF0aXZlcyBvbmx5IGV4aXN0IGZvciBjb250aW51b3VzIHZhcmlhYmxlcykuCgpGb3IgZXhhbXBsZSwgd2hhdCBpZiBvdGhlciBmYWN0b3JzIGJlc2lkZXMgUEkgYWZmZWN0IGxvYW4gZGVuaWFsPyBJZiB3ZSBmYWlsIHRvIGNvbnRyb2wgZm9yIHRoZW0gb3VyIG1vZGVsIHdpbGwgc3VmZmVyIGZyb20gKipvbWl0dGVkIHZhcmlhYmxlIGJpYXMqKi4gVGhlIGBITURBYCBkYXRhIGlzIGZhbW91cyBmb3Igc2hvd2luZyBzeXN0ZW1hdGljIGxvYW4gZGVuaWFsIGZvciBBZnJpY2FuLUFtZXJpY2FucywgY29udHJvbGxpbmcgZm9yIFBJOgoKYGBge3IgZXN0aW1hdGUgYnBtIHdpdGggYWZhbX0KSE1EQSAlPiUgCiAgZ2xtKGZvcm11bGEgPSBkZW55X251bWVyaWMgfiBwaXJhdCArIGFmYW0sIGRhdGEgPSAuLCAgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpKSAgJT4lIAogIHN1bW1hcnkoKQpgYGAKSW4gdGhlIGRhdGEgcmFjZSBpcyAqKm5vdCoqIGNvbnRpbnVvdXMgKHRoZXJlIGlzIG5vIHJhY2UgY29udGludXVtOiBhbiBhcHBsaWNhbnQgZWl0aGVyIGlzIG9yIGlzIG5vdCBBZnJpY2FuLUFtZXJpY2FuKS4gVG8gbW9yZSBpbnRlcnByZXQgdGhlIGNvZWZmaWNpZW50IG9uIGBhZmFteWVzYCAoYW4gQWZyaWNhbi1BbWVyaWNhbiBhcHBsaWNhbnQpIHdlIGNhbiBjYWxjdWxhdGUgdGhlIG9kZHMtcmF0aW86CgpgYGB7ciBvZGRzIGFmYW15ZXN9CmV4cCgxLjI3MjgpCmBgYAoKd2hpY2ggc2F5cyB0aGF0IG9uIGF2ZXJhZ2UgYW5kIGNvbnRyb2xsaW5nIGZvciBQSSwgYW4gQWZyaWNhbi1BbWVyaWNhbiBhcHBsaWNhbnQgY29tcGFyZWQgdG8gYSB3aGl0ZSBhcHBsaWNhbnQgaXMgKG9yIHJhdGhlciwgd2FzIC0tIHRoZSBkYXRhIGFyZSBmcm9tIDE5OTApIGFsbW9zdCBmb3VyIHRpbWVzIG1vcmUgbGlrZWx5IHRvIHNlZSB0aGVpciBtb3J0Z2FnZSBhcHBsaWNhdGlvbiByZWplY3RlZC4gCgpTdGlsbCwgaXQncyBlYXNpZXIgdG8gdW5kZXJzdGFuZCB0aGlzIG1vZGVsIGlmIHdlIHBsb3QgaXQ6CgpgYGB7ciBlc3RpbWF0ZSBicG0gd2l0aCBhZmFtIGFuZCBwbG90fQpITURBICU+JSAKICBnbG0oZm9ybXVsYSA9IGRlbnlfbnVtZXJpYyB+IHBpcmF0ICsgYWZhbSwgZGF0YSA9IC4sICBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IikpICAlPiUgCiAgIyB0aWR5IGFuZCBhdWdtZW50IHRoZSBtb2RlbCBvYmplY3Qgd2l0aCBmaXR0ZWQgdmFsdWVzIChhbmQgb3RoZXIgc3R1ZmYpCiAgYXVnbWVudCh0eXBlLnByZWRpY3QgPSAicmVzcG9uc2UiKSAlPiUgCiAgIyBwbG90IHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBvZiBkZW5pYWwgYWdhaW5zdCBQSQogIGdncGxvdCguLCBhZXMoeCA9IHBpcmF0LCB5ID0gLmZpdHRlZCwgY29sb3IgPSBhZmFtKSkgKyAKICBnZW9tX2xpbmUoKSAKYGBgClRoYXQgc2VwYXJhdGlvbiBiZXR3ZWVuIHRoZSBjdXJ2ZXMgaXMgdGhlICJyYWNlIGVmZmVjdCIuIAoKIyMgUHV0dGluZyBpdCBhbGwgdG9nZXRoZXIKCkxldCdzIGVzdGltYXRlIGEgbW9kZWwgaW5jbHVkaW5nIGBwaXJhdGAsIGBhZmFtYCwgYGx2cmF0YCAobG9hbi10by12YWx1ZSByYXRpbyksIGBtaGlzdGAgKGNyZWRpdCBzY29yZSkgYW5kIGBoaXJhdGAgKGluaG91c2UgZXhwZW5zZS10by10b3RhbC1pbmNvbWUgcmF0aW8pCgoKYGBge3IgYnBtIGZ1bGwgbW9kZWx9CmJwbSA9IEhNREEgJT4lIAogIGdsbShmb3JtdWxhID0gZGVueV9udW1lcmljIH4gcGlyYXQgLCBkYXRhID0gLiwgIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSAibG9naXQiKSkgCmBgYAoKTm93IGxldCdzIGdldCB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgd2l0aCBgYXVnbWVudCh0eXBlLnByZWRpY3QgPSAicmVzcG9uc2UiKWAKCgpgYGB7ciBicG19CiMgZ2V0IGEgdGlkeSBkYXRhIGZyYW1lIHdpdGggcHJlZGljdGVkIHByb2JhYmlsaXRpZXMKYnBtX3ByZWRpY3Rpb25zID0gYnBtICU+JSAKICBhdWdtZW50KHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpIAoKIyBmaXJzdCBmaXZlIHJvd3MKYnBtX3ByZWRpY3Rpb25zICU+JSAgc2xpY2VfaGVhZChuPTUpCmBgYAoKIyMjIFByZWRpY3Rpb25zIGFuZCBlcnJvcgoKT0ssIG5vdyB3ZSBjYW4gbWFrZSBzb21lIGNvbGQgcHJlZGljdGlvbnMuIExldCdzIHVzZSBhIHJ1bGUtb2YtdGh1bWI6IGlmIGEgcHJlZGljYXRlZCBwcm9iYWJpbGl0eSBpcyBncmVhdGVyIHRoYW4gMC41LCB0aGUgYXBwbGljYXRpb24gaXMgZGVuaWVkLiBXZSBjYW4gdGhlbiBjYWxjdWxhdGUgdGhlIGVycm9yOiB0aGUgKiphYnNvbHV0ZSoqIGRpZmZlcmVuY2UgYmV0d2VlbiBvYnNlcnZlZCBhbmQgcHJlZGljdGVkIGRlbmlhbHMuIAoKU28gd2UgbmVlZCB0byBgbXV0YXRlYCB0d28gdmFyaWFibGVzOgoKYGBge3IgcHJlZGljdCBkZW5pYWx9CmJwbSA9IGJwbV9wcmVkaWN0aW9ucyAlPiUgCiAgIyBwcmVkaWN0ZWQgZGVuaWFsCiAgbXV0YXRlKHByZWRpY3RlZF9kZW55PSBpZmVsc2UoLmZpdHRlZCA+IDAuNSwgMSwgMCkpICU+JSAKICAjIHNxdWFyZWQgZXJyb3IKICBtdXRhdGUoZXJyb3IgPSAoZGVueV9udW1lcmljIC0gcHJlZGljdGVkX2RlbnkpXjIpCmBgYAoKV2hhdCB3YXMgb3VyICoqYXZlcmFnZSoqIGVycm9yIHJhdGU/CgpgYGB7ciBhdmVyYWdlIGVycm9yfQpicG0gJT4lIAogIHN1bW1hcmlzZShtZWFuKGVycm9yKSkKYGBgCgpBYm91dCAxMiUuIAoKCiMgQ2hlY2twb2ludAoKVXNpbmcgdGhlIGBuaGFuZXNgIGRhdGE6CgpgYGB7ciBsb2FkIG5oYW5lcywgbWVzc2FnZT1GQUxTRX0KbmhhbmVzID0gcmVhZF9jc3YoImh0dHBzOi8vcXVlcnkuZGF0YS53b3JsZC9zL2s1eTZ1cWY3eGhzanF3Y3BsZHBsYzZrdnl0anRveSIpCmBgYAoKYnVpbGQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIHRoYXQgZXN0aW1hdGVzIHRoZSBwcm9iYWJpbGl0eSBvZiBhIGhlYXJ0IGF0dGFjayAoYGhlYXJ0YXRrYCkgYXMgYSBmdW5jdGlvbiBvZiBgYWdlYCBhbmQgYHNleGA6CgpgYGB7ciBoZWFydCBhdHRhY2sgbW9kZWx9CiMgZXN0aW1hdGUgdGhlIG1vZGVsCmhlYXJ0YXR0YWNrX21vZGVsID0gbmhhbmVzICU+JSAKICBnbG0oZm9ybXVsYSA9IGhlYXJ0YXRrIH4gYWdlICsgc2V4LCBkYXRhID0gLiwgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpKSAKCiMgdmlldyB0aGUgcmVzdWx0cwpoZWFydGF0dGFja19tb2RlbCAlPiUgCiAgc3VtbWFyeSgpCmBgYAoKV2hhdCBhcmUgdGhlIG9kZHMgdGhhdCBhbiBhdmVyYWdlIG1hbGUgKGNvbnRyb2xpbmcgZm9yIGFnZSkgd2lsbCBzdWZmZXIgYSBoZWFydCBhdHRhY2sgY29tcGFyZWQgdG8gYSBmZW1hbGU/CgpgYGB7ciBvZGRzIG1hbGUgaGVhcnQgYXR0YWNrfQpleHAoMC45MTA2ODEpCmBgYAoKUGxvdCB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXR5IG9mIGEgaGVhcnQgYXR0YWNrIG92ZXIgdGltZSBhbmQgYnkgc2V4OgoKYGBge3IgcGxvdCBwcm9iYWJpbGl0eSBoZWFydCBhdHRhY2t9CmhlYXJ0YXR0YWNrX21vZGVsICU+JSAKICAjIHRpZHkgYW5kIGF1Z21lbnQgdGhlIG1vZGVsIG9iamVjdCB3aXRoIGZpdHRlZCB2YWx1ZXMgKGFuZCBvdGhlciBzdHVmZikKICBhdWdtZW50KHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpICU+JSAKICAjIHBsb3QgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIAogIGdncGxvdCguLCBhZXMoeCA9IGFnZSwgeSA9IC5maXR0ZWQsIGNvbG9yID0gc2V4KSkgKyAKICBnZW9tX2xpbmUoKSAKYGBgCgoKCiMgQXBwZW5kaXgKCiMjIFByb2JpdAoKUHJvYml0IHJlZ3Jlc3Npb24gaXMgdGhlIGJlZGZlbGxvdyBvZiBsb2dpc3RpYyByZWdyZXNzaW9uIGFuZCBwcm9kdWNlcyB2ZXJ5IHNpbWlsYXIgcmVzdWx0cy4gQ2hvb3NpbmcgYmV0d2VlbiBvbmUgb3IgdGhlIG90aGVyIGlzIG9mdGVuIGp1c3QgYSBtYXR0ZXIgb2YgdGFzdGUgb3IgY29udmVudGlvbi4KClRoZSBwcm9iaXQgKHNob3J0IGZvciAicHJvYmFiaWxpdHkgdW5pdCIpIG1vZGVsIHVzZXMgdGhlIFN0YW5kYXJkIE5vcm1hbCBDREYgKGN1bXVsYXRpdmUgZGlzdHJpYnV0aW9uIGZ1bmN0aW9uKSBhcyB0aGUgbGluayBmdW5jdGlvbi4gCkNvbnNpZGVyIHNvbWUgb3V0Y29tZSAkeSQgYW5kIGRhdGEgJFxtYXRoYmZ7WH0kLiBUaGUgcHJvYml0IG1vZGVsIGlzCgokJApQKHk9MSB8IFxtYXRoYmZ7WH1cYmV0YSkgPSBcUGhpKFxtYXRoYmZ7WH1cYmV0YSArIFxlcHNpbG9uKQokJAoKd2hlcmUgJFxQaGkgXHNpbSBOKFxtdT0wLFxzaWdtYV4yID0gMSkkIGlzIHRoZSBTdGFuZGFyZCBOb3JtYWwgQ0RGIChlLmcuICRcUGhpKDApID0gMC41JDsgaGFsZiB0aGUgc3RhbmRhcmQgbm9ybWFsIGRpc3RyaWJ1dGlvbiBsaWVzIGJlbG93ICRcbXUgPSAwJCkuCgpJZiB3ZSBhc3N1bWUgJFx2YXJlcHNpbG9uIFxzaW0gTigwLDEpJCBzbyB0aGF0ICRcbWF0aGJie0V9W1x2YXJlcHNpbG9uXSA9IDAkLCB0aGVuIHdlIHNpbXBseSBoYXZlCgokJApQKHk9MSB8IFxtYXRoYmZ7WH1cYmV0YSkgPSBcUGhpKFxtYXRoYmZ7WH1cYmV0YSkKJCQKCm9yCgokJApQKHk9MSB8IFxtYXRoYmZ7WH1cYmV0YSkgPSBcUGhpKHogXGxlcSBcbWF0aGJme1h9XGJldGEpLgokJAoKSW4gb3RoZXIgd29yZHMsIHdlIGNhbiB0aGluayBvZiBzb21lIHByZWRpY3RlZCB2YWx1ZSBhcyBhICRcbWF0aGJme3p9JC1zY29yZSB3aGljaCBnZXRzIHR1cm5lZCBpbnRvIGEgcHJvYmFiaWxpdHkgYnkgJFxQaGkoXGNkb3QpJC4gVGhlIGNvZWZmaWNpZW50cyB0aGVyZWZvcmUgZGVzY3JpYmUgdGhlICpjaGFuZ2UqIGluIHRoZSAkeiQtc2NvcmUgKGUuZy4sICJhIG9uZS11bml0IGNoYW5nZSBpbiAkeCQgaXMgYXNzb2NpYXRlZCB3aXRoIGEgJFxiZXRhXzEkIGNoYW5nZSBpbiB0aGUgei1zY29yZS4iKQoKVG8gZXN0aW1hdGUgdGhlIG1vZGVsIGp1c3QgY2hhbmdlIHRoZSBmbGFnIGZvciB0aGUgbGluayBmdW5jdGlvbiBmcm9tICJsb2dpdCIgdG8gInByb2JpdCI6CgpgYGB7ciBicG0gZnVsbCBtb2RlbCBwcm9iaXR9CkhNREEgJT4lIAogIGdsbShmb3JtdWxhID0gZGVueV9udW1lcmljIH4gcGlyYXQgKyBhZmFtICsgbHZyYXQgKyBtaGlzdCArIGhpcmF0LCBkYXRhID0gLiwgIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSAicHJvYml0IikpICU+JSAKICBzdW1tYXJ5KCkKYGBgCgpUaGUgY29lZmZpY2llbnRzIGFyZSBkaWZmZXJlbnQgbnVtYmVycyBiZWNhdXNlIHRoZXkgcmVwcmVzZW50IHotc2NvcmVzLiBCdXQgdGhlIGJhc2ljIHJlbGF0aW9uc2hpcHMgYXJlIHRoZSBzYW1lLiBBbmQgdGhlIGVzdGltYXRlZCBwcm9iYWJpbGl0eSBjdXJ2ZXMgYXJlIHNpbWlsYXI6CgpgYGB7ciBsb2dpdCB2cyBwcm9iaXQsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRX0KIyBiYXNlIHNjYXR0ZXIgcGxvdApiYXNlX3Bsb3QgPSBITURBICU+JSAKICBnZ3Bsb3QoZGF0YSA9IC4sIGFlcyh4ID0gcGlyYXQsIHkgPSBkZW55X251bWVyaWMpKSArCiAgZ2VvbV9wb2ludCgpIAoKIyBsb2dpdApwbG90X2xvZ2l0ID0gYmFzZV9wbG90ICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImdsbSIsIG1ldGhvZC5hcmdzID0gbGlzdChmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IikpKSArIAogIGxhYnModGl0bGUgPSAiTG9naXN0aWMgcmVncmVzc2lvbiIpCgojIHByb2JpdApwbG90X3Byb2JpdCA9IGJhc2VfcGxvdCArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJnbG0iLCBtZXRob2QuYXJncyA9IGxpc3QoZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJwcm9iaXQiKSkpICsgCiAgbGFicyh0aXRsZSA9ICJQcm9iaXQgcmVncmVzc2lvbiIpCgojIGNvbWJpbmUgd2l0aCBsaWJyYXJ5KHBhdGNod29yaykKcGxvdF9sb2dpdCArIHBsb3RfcHJvYml0CmBgYA==