Introduction

So far we have changed the mean model: which predictors enter and how they enter. Weighted least squares changes the error model. Some responses are measured more precisely than others, so their deviations from the fitted mean need not contribute equally.

If observation \(i\) has error variance \(\sigma_i^2\), the usual precision weight is proportional to \(1/\sigma_i^2\). A more precise observation receives a larger weight. We will start with means based on different numbers of people, then use the same rule when individual error variances change across the predictor range.

Weighting by Sample Size

The CPS5 data contain individual wages and characteristics for 528 employees. Here we focus on education (ED) and hourly wage (WG). Goldberger first averages wages within each education level, from 6 to 18 years, then regresses those group means on education. Most employees had 12, 14 or 16 years of education.

Each row here is a mean hourly wage for one education level, not one person. Suppose individual wages within the education groups have a common variance \(\sigma^2\) and are independent. A mean based on \(n_j\) people then has variance \(\sigma^2/n_j\), so inverse-variance weighting gives \(w_j \propto n_j\). This explains why the group containing three people should have less influence than a group containing many more.

CPS5 grouped data

Download CPS5grouped.csv

## CPS5grouped = read.csv("CPS5grouped.csv", header = TRUE)
CPS5grouped
   EDgroup  AvWage  SEMean   StDev   N
1        6  4.4567 0.79805 1.38226   3
2        7  5.7700 0.83690 1.87136   5
3        8  5.9787 0.62779 2.43142  15
4        9  7.3317 1.29904 4.50000  12
5       10  7.3182 0.64444 2.65711  17
6       11  6.5844 0.64084 3.32992  27
7       12  7.8182 0.25206 3.72159 218
8       13  7.8351 0.66765 4.06114  37
9       14 11.0223 0.92157 6.89643  56
10      15 10.6738 1.39739 5.03837  13
11      16 10.8361 0.63981 5.35304  70
12      17 13.6150 1.42417 6.97699  24
13      18 13.5310 1.09328 6.08715  31

Grouped data scatterplot

ggplot(data = CPS5grouped, mapping = aes(x = EDgroup, y = AvWage)) + geom_point(mapping = aes(size = N)) +
    geom_smooth(method = "lm") + labs(x = "Education (years)", y = "Average wage ($)")

unlabelled

Grouped regression summary

The group means lie close to a line, and their unweighted regression has an \(R^2\) above 0.9. This describes variation among 13 group means. The individual-level \(R^2\) also includes variation among people within each education group, so the two values are not directly comparable.

grouped.lm <- lm(AvWage ~ EDgroup, data = CPS5grouped)
summary(grouped.lm)

Call:
lm(formula = AvWage ~ EDgroup, data = CPS5grouped)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.5637 -0.7350  0.1266  0.7158  1.3198 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -0.01445    0.87462  -0.017    0.987    
EDgroup      0.72410    0.06958  10.406 4.96e-07 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.9387 on 11 degrees of freedom
Multiple R-squared:  0.9078,    Adjusted R-squared:  0.8994 
F-statistic: 108.3 on 1 and 11 DF,  p-value: 4.958e-07

The problem with equal weighting

The first point represents only three people, yet the unweighted fit gives it the same influence as the much larger 12-, 14- and 16-year education groups. If we want each employee to contribute equally, we need to weight those group means by their sizes.

The individual data show more variation because they retain the differences between people within each education group. The grouped and ungrouped fits therefore describe different response variation; we should not read their \(R^2\) values as a direct comparison of predictive quality.

Ungrouped CPS5 data

What does the relationship look like when we use individual values in place of group means?

Download CPS5.csv

## CPS5 = read.csv("CPS5.csv", header = TRUE)
head(CPS5)
  ED SO BL HP FE MS EX UN   WG
1 10  0  0  0  0  1 27  0  9.0
2 12  0  0  0  0  1 20  0  5.5
3 12  0  0  0  1  0  4  0  3.8
4 12  0  0  0  1  1 29  0 10.5
5 12  0  0  0  0  1 40  1 15.0
6 16  0  0  0  1  1 27  0  9.0

Ungrouped scatterplot

ggplot(data = CPS5, mapping = aes(x = ED, y = WG)) + geom_point() + geom_smooth(method = "lm") +
    labs(x = "Education (years)", y = "Wage ($)")
`geom_smooth()` using formula = 'y ~ x'

unlabelled

Ungrouped regression summary

cps5.lm = lm(WG ~ ED, data = CPS5)
summary(cps5.lm)

Call:
lm(formula = WG ~ ED, data = CPS5)

Residuals:
   Min     1Q Median     3Q    Max 
-8.068 -3.163 -0.700  2.289 34.709 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.60468    1.10318  -1.455    0.146    
ED           0.81395    0.08281   9.829   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 4.733 on 526 degrees of freedom
Multiple R-squared:  0.1552,    Adjusted R-squared:  0.1536 
F-statistic:  96.6 on 1 and 526 DF,  p-value: < 2.2e-16

We see there is an outlier, which could be a typo (decimal point in the wrong place?) However the main focus here is that there is a lot more variation among the individual data than there is among the averaged data, so the \(R^2\) is much lower. Also the regression coefficients have different values.

Why Grouped Regression Results Differ

Formulas not examinable but conclusion is important.

To explain the difference between these two regressions, consider the regression of \(Y\) on \(X\) for all individuals, and the regression of group means \(\bar Y\) on \(\bar X\). In the CPS5 example, everyone within an education group has the same ED value, so \(x_{ij}=\bar x_j\). The equivalence below depends on that condition. In Least Squares we choose \(\beta_0\) and \(\beta_1\) to minimise
\[SS_{error} = \sum^n_{i=1}(y_i - \beta_0- \beta_1 x_i)^2 ~~~~ (1) \] However the regression for the averages treats all \(m\) groups as being of equal worth: \[ \sum^m_{j=1}(\bar y_j - \beta_0- \beta_1 \bar x_j)^2 ~~~~~~ (2) \]
That is, the computations will try just as hard to fit a value that arises from a small group of data as a value from a large group.

Error SS decomposition

But in fact we don’t have all the \(y_{ij}\) equal. Instead what we do is to add and subtract \(\bar y_j\) inside the parentheses in equation (3), and then expand the square: \[\begin{aligned}SS_{error} &= \sum^m_{j=1}\sum^{n_j}_{i=1}(y_{ij} -\bar y_j + \bar y_j - \beta_0- \beta_1 x_{ij})^2\\&= \sum^m_{j=1}\sum^{n_j}_{i=1}(y_{ij} -\bar y_j)^2 + \sum^m_{j=1}\sum^{n_j}_{i=1}(\bar y_j - \beta_0- \beta_1 \bar x_j)^2 ~~~~~(*)\\&= \sum^m_{j=1}\sum^{n_j}_{i=1}(y_{ij} -\bar y_j)^2 + \sum^m_{j=1} n_j~(\bar y_j - \beta_0- \beta_1 \bar x_j)^2\end{aligned}\]

(*) Equality happens because the cross-product term \(\sum^m_{j=1} \sum^{n_j}_{i=1} ~2~(y_{ij} -\bar y_j) (\bar y_j - \beta_0- \beta_1 \bar x_j) =0\).

Key insight: SS decomposition

The key fact is that the error SS splits into two parts:

\[SS_{error} = \sum^m_{j=1}\sum^{n_j}_{i=1}(y_{ij} -\bar y_j)^2 + \sum^m_{j=1} n_j~(\bar y_j - \beta_0- \beta_1 \bar x_j)^2\] - The first term is the “pure error” sum of squares. It records how much individual wages vary around their education-group means \(\bar y_j\). - The second term is the weighted sum of squares for fitting the group means, with each squared residual multiplied by the group size \(n_j\).

Because everyone in an education group has the same ED value, the within-group sum of squares does not depend on the regression coefficients. Minimising the individual-level sum of squares therefore gives the same intercept and slope as a regression of the group means weighted by group size. This equality of coefficient estimates depends on the predictor being constant within each group. It does not say that the two analyses have the same residual degrees of freedom, standard errors or R-squared: the grouped analysis has lost the within-group variation.

Weighted regression in R

To do a weighted regression we have to specify weights in the lm() command. Below we compare the weighted least squares (WLS) estimates from the averaged data to the ungrouped ordinary least squares (OLS) estimates from the raw data.

cps.wls = lm(AvWage ~ EDgroup, weights = N, data = CPS5grouped)
summary(cps.wls)

Call:
lm(formula = AvWage ~ EDgroup, data = CPS5grouped, weights = N)

Weighted Residuals:
   Min     1Q Median     3Q    Max 
-6.944 -3.971  2.699  4.151  9.217 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.60468    1.27442  -1.259    0.234    
EDgroup      0.81395    0.09567   8.508 3.62e-06 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 5.467 on 11 degrees of freedom
Multiple R-squared:  0.8681,    Adjusted R-squared:  0.8561 
F-statistic: 72.39 on 1 and 11 DF,  p-value: 3.621e-06

OLS comparison

summary(cps5.lm)

Call:
lm(formula = WG ~ ED, data = CPS5)

Residuals:
   Min     1Q Median     3Q    Max 
-8.068 -3.163 -0.700  2.289 34.709 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.60468    1.10318  -1.455    0.146    
ED           0.81395    0.08281   9.829   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 4.733 on 526 degrees of freedom
Multiple R-squared:  0.1552,    Adjusted R-squared:  0.1536 
F-statistic:  96.6 on 1 and 526 DF,  p-value: < 2.2e-16

The weighted group-mean fit gives the same intercept and slope as the individual-level fit, apart from rounding in the supplied means. Education is constant within each group. The standard errors and \(R^2\) need not match: the grouped fit has only 13 rows and does not include the within-group variation in wages.

Comparing WLS vs OLS plots

The following plot shows the averaged data with the unweighted (dashed) and weighted (solid red) lines. The weighted line is closer to where more data are.

ggplot(data = CPS5grouped, mapping = aes(x = EDgroup, y = AvWage)) + geom_point(mapping = aes(size = N)) +
    geom_smooth(method = "lm", mapping = aes(weight = N), col = "red", se = FALSE) +
    geom_smooth(method = "lm", linetype = "dashed", se = FALSE) + labs(x = "Education (years)",
    y = "Average wage ($)")

unlabelled

Residuals for Weighted Regression

Clearly a plot of ordinary residuals could be misleading in the context of weighted least squares. The low weight points on the left are badly fitted and this could make them look like influential outliers.

augment(cps.wls, CPS5grouped) |>
    ggplot(mapping = aes(x = EDgroup, y = .resid)) + geom_point() + geom_hline(yintercept = 0,
    linetype = "dotted") + labs(y = "Simple residuals", x = "Education group (years)")

unlabelled

Pearson residuals

Instead we use weighted, or Pearson, residuals \(r_j = \sqrt{w_j}(y_j - \hat y_j)\).

augment(cps.wls, CPS5grouped) |>
    mutate(pearson = .resid * sqrt(N)) |>
    ggplot(mapping = aes(x = EDgroup, y = pearson)) + geom_point() + geom_hline(yintercept = 0,
    linetype = "dotted") + labs(y = "Pearson (weighted) residuals", x = "Education group (years)")

unlabelled

The weighted residuals are measured on the scale used by the weighted fit. A small weighted residual means the observation is close to its fitted value relative to the assumed variance; it is not a complete influence analysis and does not make a point scientifically unimportant. The ordinary and weighted residual panels have different scales, so compare their patterns rather than their vertical magnitudes.

Application: Weighting For Non-Constant Variance

Suppose we have data for which the variance is non-constant. So we want to give more weight to those points which have small variance (or are known more precisely) and give less weight to those points that have bigger variance (or are known less precisely).

This still fits with the idea that Weight represents the amount of information carried by each datapoint.

Supervisors and Workers

The following data is on the number of supervisors \(Y\) (e.g. team leaders) and the number of workers \(X\) at a number of industrial establishments.
The data are from the book by Chatterjee and Price “Regression Analysis by Example”

Download supervisors.csv

ggplot(supervisors, mapping = aes(x = X, y = Y)) + geom_point() + geom_smooth(method = "lm") +
    labs(x = "Number of workers (X)", y = "Number of supervisors (Y)")

unlabelled

OLS residual diagnostics

Note that the line is overshooting the cluster of points on the left.
The residual plots show curvature and heteroscedasticity.
The fourth graph shows a simple linear model is highly influenced by the points at the right, which have very large residuals.

sup.lm <- lm(Y ~ X, data = supervisors)
par(mfrow = c(1, 2))
plot(sup.lm, which = c(1, 5))

unlabelled

Heteroscedasticity pattern

In the residuals-versus-fitted plot, the horizontal axis shows fitted numbers of supervisors. Compare the vertical spread around fitted \(Y\) values of 50, 100 and 150. The spread increases as the fitted value rises. Since fitted \(Y\) also rises with \(X\), a reasonable working model is \(\mbox{sd}(\varepsilon_i) \propto x_i\), or \(\sigma_i=kx_i\) for some constant \(k\).

Now let us suppose we want to fit a straight-line model \[y_i = \beta_0 + \beta_1 x_i + \varepsilon_i ~ \] We would ordinarily solve this by minimising the \[SS_{error} = \sum( y_i - \beta_0 - \beta_1 x_i)^2\] where the components of the sum (the residuals \(y_i - \beta_0 - \beta_1 x_i\)) have constant standard deviation. Except now the standard deviation is not constant.

But what we could do is divide through by \(x_i\). The model becomes \[\begin{aligned}\frac{y_i}{x_i} &= \frac{\beta_0}{x_i} + \beta_1\frac{x_i}{x_i} + \frac{\varepsilon_i}{x_i}\\Y_i &=\beta_0 X_i + \beta_1 + \eta_i\end{aligned}\] where \(Y_i = \frac{y_i}{x_i}\), \(X_i = \frac{1}{x_i}\) and \(\eta_i = \frac{\varepsilon_i}{x_i}\).

Constant variance after transformation

The error term \(\eta_i\) now has constant standard deviation as

\[\mbox{sd}\left( \frac{\varepsilon_i}{x_i}\right) = \frac{ kx_i}{x_i} = k.\]

We now have a linear model with constant variance so can solve by least squares in the usual way.

We could do the transformation ourselves and then just use lm() normally, but another way to look at it is we’re minimising

\[\sum \left(\frac{y_i}{x_i} - \frac{\beta_0}{x_i} - \beta_1 \right)^2 ~~= ~~ \sum \frac{1}{x_i^2}\left(y_i - \beta_0 -\beta_1 x_i \right)^2\]

which is a weighted regression with \(w_i = 1/ x_i^2\). So the task with this transformation is the same as weighted least squares with these weights.

WLS for supervisors

sup.wls = lm(Y ~ X, data = supervisors, weights = 1/X^2)
summary(sup.wls)

Call:
lm(formula = Y ~ X, data = supervisors, weights = 1/X^2)

Weighted Residuals:
      Min        1Q    Median        3Q       Max 
-0.041477 -0.013852 -0.004998  0.024671  0.035427 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 3.803296   4.569745   0.832    0.413    
X           0.120990   0.008999  13.445 6.04e-13 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.02266 on 25 degrees of freedom
Multiple R-squared:  0.8785,    Adjusted R-squared:  0.8737 
F-statistic: 180.8 on 1 and 25 DF,  p-value: 6.044e-13

WLS vs OLS supervisors plot

ggplot(data = supervisors, mapping = aes(x = X, y = Y)) + geom_point() + geom_smooth(method = "lm",
    mapping = aes(weight = 1/X^2), col = "red", se = FALSE) + geom_smooth(method = "lm",
    linetype = "dotted", se = FALSE) + labs(x = "Number of workers (X)", y = "Number of supervisors (Y)")

unlabelled

Weighted residuals plot

The weighted line sits closer to the low-\(X\) observations, where the assumed error variance is smaller and the weights are larger. The OLS line gives these points the same per-observation weight as the higher-\(X\) observations, despite their greater residual spread.

Let’s check the weighted residuals below. Their spread is more similar across the range of \(X\), although a little curvature may remain. This is more consistent with the proposed variance model; the plot cannot establish that the variance is exactly constant.

augment(sup.wls) |>
    mutate(pearson = .resid/X) |>
    ggplot(mapping = aes(x = X, y = pearson)) + geom_point() + geom_hline(yintercept = 0,
    linetype = "dotted") + labs(y = "Pearson (weighted) residuals", x = "Number of workers (X)")

unlabelled

Basic rule for Weighting

The common rule is inverse variance: \(w_i \propto 1/\operatorname{Var}(Y_i \mid X_i)\). In the CPS5 example each row was a group mean; for supervisors each row was one establishment. Those different rows led to different expressions for the weight.

Application: Meta Analysis CPS5grouped data

Meta-analysis combines estimates from several studies. In a simple inverse-variance meta-analysis, each study contributes an estimate with its own standard error. The estimate is what we weight, so its weight is proportional to \(1/\mathrm{SE}^2\). An estimate with a smaller standard error contributes more.

In the CPS5 grouped data, each row is a mean wage for N employees. The StDev column estimates the standard deviation of individual wages within that group. The variance of the group mean is therefore approximately \(\mathrm{StDev}_j^2/n_j\), giving the weight \(w_j=n_j/\mathrm{StDev}_j^2\). This uses both group size and the variation among employees in each group.

CPS5weighted <- CPS5grouped |> mutate(Wt = N/ StDev^2)
cps.wls2 <- lm(AvWage ~ EDgroup, weights=Wt, data=CPS5weighted)
summary(cps.wls2)

Call:
lm(formula = AvWage ~ EDgroup, data = CPS5weighted, weights = Wt)

Weighted Residuals:
    Min      1Q  Median      3Q     Max 
-1.4367  0.1153  0.7399  1.0895  1.7058 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  0.26605    0.93427   0.285    0.781    
EDgroup      0.65602    0.07828   8.381 4.19e-06 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 1.21 on 11 degrees of freedom
Multiple R-squared:  0.8646,    Adjusted R-squared:  0.8523 
F-statistic: 70.24 on 1 and 11 DF,  p-value: 4.186e-06

CPS5 re-weighted plot

ggplot(data = CPS5weighted, mapping = aes(x = EDgroup, y = AvWage)) + geom_point(mapping = aes(size = N)) +
    geom_smooth(method = "lm", mapping = aes(weight = Wt), col = "red", se = FALSE) +
    geom_smooth(method = "lm", mapping = aes(weight = N), linetype = "dotted", se = FALSE) +
    labs(x = "Education group (years)", y = "Average Wage ($)")

unlabelled

The standard deviations are much higher for the highly-educated groups, so that counteracts some of the effect of higher sample sizes.

Running Example: Weighted Summaries for Seoul Bike Data

The Seoul bike data also show why grouped summaries need care. If we average demand within temperature bands, some groups represent many more hourly observations than others.

seoul_hourly = read_csv("../data/seoul_bike_hourly.csv")
seoul_grouped = seoul_hourly |>
    mutate(temp_band = cut(temperature, breaks = c(-20, -10, 0, 10, 20, 30, 40),
        include.lowest = TRUE)) |>
    group_by(temp_band) |>
    summarise(avg_bike_count = mean(rented_bike_count), avg_temperature = mean(temperature),
        N = n(), .groups = "drop")

seoul_grouped
# A tibble: 6 × 4
  temp_band avg_bike_count avg_temperature     N
  <fct>              <dbl>           <dbl> <int>
1 [-20,-10]           141.          -12.7    228
2 (-10,0]             210.           -4.19  1226
3 (0,10]              425.            5.04  2143
4 (10,20]             765.           15.3   2264
5 (20,30]            1120.           24.5   2387
6 (30,40]            1106.           33.0    512
ggplot(seoul_grouped, aes(x = avg_temperature, y = avg_bike_count)) + geom_point(aes(size = N)) +
    geom_smooth(method = "lm", se = FALSE, linetype = "dashed") + geom_smooth(method = "lm",
    aes(weight = N), se = FALSE, colour = "red") + labs(x = "Average temperature within band",
    y = "Average bike demand", title = "Aggregated groups with different support should not be treated equally")

unlabelled

These temperature bands summarise unequal numbers of hours. Weighting a band’s mean by its count changes how much that summary influences the line. Here temperature also varies within each band, so the line through weighted band means need not be identical to a line fitted to all the hourly observations. The exact equality in the education example relied on everybody in a group having the same predictor value.

Putting the weights together

We have now used four forms of weight. In every case, start with the variance of the response represented by one row, then take its reciprocal.

What one row represents Variance of that row Relative weight Example in this lecture
A mean of \(n_j\) independent people, with a common individual variance \(\sigma^2\) \(\sigma^2/n_j\) \(n_j\) First CPS5 grouped fit, weights=N
An observation or estimate with variance \(v_i\) \(v_i\) \(1/v_i\) General rule; study estimates in meta-analysis
A mean of \(n_j\) independent people, with individual variance \(\sigma_j^2\) in group \(j\) \(\sigma_j^2/n_j\) \(n_j/\sigma_j^2\) CPS5 re-weighted fit, weights=N/StDev^2
An observation whose error standard deviation is proportional to \(x_i\) \(k^2x_i^2\) \(1/x_i^2\) Supervisors and workers, weights=1/X^2

The extra \(n_j\) in \(n_j/\sigma_j^2\) comes from averaging: the variance of a mean of \(n_j\) independent observations is the individual variance divided by \(n_j\). Likewise, \(1/x_i^2\) follows from inverse variance when the error standard deviation is \(kx_i\). A constant shared by every row, such as \(\sigma^2\) or \(k^2\), can be left out of the relative weights.

LS0tDQp0aXRsZTogIkxlY3R1cmUgMzA6IFdlaWdodGVkIHJlZ3Jlc3Npb24iDQpzdWJ0aXRsZTogMTYxLjI1MSBSZWdyZXNzaW9uIE1vZGVsbGluZw0KYXV0aG9yOiAiUHJlc2VudGVkIGJ5IE5pY2sgS25vd2x0b24gPE4uS25vd2x0b25AbWFzc2V5LmFjLm56PiIgIA0KZGF0ZTogIldlZWsgMTAgb2YgU2VtZXN0ZXIgMiwgYHIgbHVicmlkYXRlOjp5ZWFyKGx1YnJpZGF0ZTo6bm93KCkpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IHlldGkNCiAgICBoaWdobGlnaHRfc3R5bGU6IHB5Z21lbnRzDQogIGh0bWxfbm90ZWJvb2s6DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiB5ZXRpDQogICAgaGlnaGxpZ2h0X3N0eWxlOiBweWdtZW50cw0KICBpb3NsaWRlc19wcmVzZW50YXRpb246DQogICAgd2lkZXNjcmVlbjogdHJ1ZQ0KICAgIHNtYWxsZXI6IHRydWUNCiAgd29yZF9kb2N1bWVudDogZGVmYXVsdA0KICBzbGlkeV9wcmVzZW50YXRpb246IA0KICAgIHRoZW1lOiB5ZXRpDQogICAgaGlnaGxpZ2h0X3N0eWxlOiBweWdtZW50cw0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQoNCg0KDQo8IS0tLSBEYXRhIGlzIG9uDQpodHRwczovL3ItcmVzb3VyY2VzLm1hc3NleS5hYy5uei9kYXRhLzE2MTI1MS8NCi0tLT4NCg0KYGBge3Igc2V0dXAsIHB1cmw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KGtuaXRyKQ0Kb3B0c19jaHVuayRzZXQoZGV2PWMoInBuZyIsICJwZGYiKSkNCm9wdHNfY2h1bmskc2V0KGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTcsIGZpZy5wYXRoPSJGaWd1cmVzLyIsIGZpZy5hbHQ9InVubGFiZWxsZWQiKQ0Kb3B0c19jaHVuayRzZXQoY29tbWVudD0iIiwgZmlnLmFsaWduPSJjZW50ZXIiLCB0aWR5PVRSVUUpDQpvcHRpb25zKGtuaXRyLmthYmxlLk5BID0gJycpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoYnJvb20pDQpgYGANCg0KDQo8IS0tLSBEbyBub3QgZWRpdCBhbnl0aGluZyBhYm92ZSB0aGlzIGxpbmUuIC0tLT4NCg0KIyMgSW50cm9kdWN0aW9uDQoNClNvIGZhciB3ZSBoYXZlIGNoYW5nZWQgdGhlIG1lYW4gbW9kZWw6IHdoaWNoIHByZWRpY3RvcnMgZW50ZXIgYW5kIGhvdyB0aGV5IGVudGVyLiBXZWlnaHRlZCBsZWFzdCBzcXVhcmVzIGNoYW5nZXMgdGhlIGVycm9yIG1vZGVsLiBTb21lIHJlc3BvbnNlcyBhcmUgbWVhc3VyZWQgbW9yZSBwcmVjaXNlbHkgdGhhbiBvdGhlcnMsIHNvIHRoZWlyIGRldmlhdGlvbnMgZnJvbSB0aGUgZml0dGVkIG1lYW4gbmVlZCBub3QgY29udHJpYnV0ZSBlcXVhbGx5Lg0KDQpJZiBvYnNlcnZhdGlvbiBcKGlcKSBoYXMgZXJyb3IgdmFyaWFuY2UgXChcc2lnbWFfaV4yXCksIHRoZSB1c3VhbCBwcmVjaXNpb24gd2VpZ2h0IGlzIHByb3BvcnRpb25hbCB0byBcKDEvXHNpZ21hX2leMlwpLiBBIG1vcmUgcHJlY2lzZSBvYnNlcnZhdGlvbiByZWNlaXZlcyBhIGxhcmdlciB3ZWlnaHQuIFdlIHdpbGwgc3RhcnQgd2l0aCBtZWFucyBiYXNlZCBvbiBkaWZmZXJlbnQgbnVtYmVycyBvZiBwZW9wbGUsIHRoZW4gdXNlIHRoZSBzYW1lIHJ1bGUgd2hlbiBpbmRpdmlkdWFsIGVycm9yIHZhcmlhbmNlcyBjaGFuZ2UgYWNyb3NzIHRoZSBwcmVkaWN0b3IgcmFuZ2UuDQoNCg0KIyMgV2VpZ2h0aW5nIGJ5IFNhbXBsZSBTaXplIA0KDQpUaGUgQ1BTNSBkYXRhIGNvbnRhaW4gaW5kaXZpZHVhbCB3YWdlcyBhbmQgY2hhcmFjdGVyaXN0aWNzIGZvciA1MjggZW1wbG95ZWVzLiBIZXJlIHdlIGZvY3VzIG9uIGVkdWNhdGlvbiAoYEVEYCkgYW5kIGhvdXJseSB3YWdlIChgV0dgKS4gR29sZGJlcmdlciBmaXJzdCBhdmVyYWdlcyB3YWdlcyB3aXRoaW4gZWFjaCBlZHVjYXRpb24gbGV2ZWwsIGZyb20gNiB0byAxOCB5ZWFycywgdGhlbiByZWdyZXNzZXMgdGhvc2UgZ3JvdXAgbWVhbnMgb24gZWR1Y2F0aW9uLiBNb3N0IGVtcGxveWVlcyBoYWQgMTIsIDE0IG9yIDE2IHllYXJzIG9mIGVkdWNhdGlvbi4NCg0KRWFjaCByb3cgaGVyZSBpcyBhIG1lYW4gaG91cmx5IHdhZ2UgZm9yIG9uZSBlZHVjYXRpb24gbGV2ZWwsIG5vdCBvbmUgcGVyc29uLiBTdXBwb3NlIGluZGl2aWR1YWwgd2FnZXMgd2l0aGluIHRoZSBlZHVjYXRpb24gZ3JvdXBzIGhhdmUgYSBjb21tb24gdmFyaWFuY2UgXChcc2lnbWFeMlwpIGFuZCBhcmUgaW5kZXBlbmRlbnQuIEEgbWVhbiBiYXNlZCBvbiBcKG5falwpIHBlb3BsZSB0aGVuIGhhcyB2YXJpYW5jZSBcKFxzaWdtYV4yL25falwpLCBzbyBpbnZlcnNlLXZhcmlhbmNlIHdlaWdodGluZyBnaXZlcyBcKHdfaiBccHJvcHRvIG5falwpLiBUaGlzIGV4cGxhaW5zIHdoeSB0aGUgZ3JvdXAgY29udGFpbmluZyB0aHJlZSBwZW9wbGUgc2hvdWxkIGhhdmUgbGVzcyBpbmZsdWVuY2UgdGhhbiBhIGdyb3VwIGNvbnRhaW5pbmcgbWFueSBtb3JlLg0KDQojIyMgQ1BTNSBncm91cGVkIGRhdGENCg0KYHIgeGZ1bjo6ZW1iZWRfZmlsZSgiLi4vZGF0YS9DUFM1Z3JvdXBlZC5jc3YiKWANCg0KYGBge3IgcmVhZC1DUFM1Z3JvdXBlZCwgZXZhbD0tMSwgZWNobz0tMn0NCkNQUzVncm91cGVkID1yZWFkLmNzdigiQ1BTNWdyb3VwZWQuY3N2IixoZWFkZXI9VFJVRSkNCkNQUzVncm91cGVkID1yZWFkLmNzdigiLi4vZGF0YS9DUFM1Z3JvdXBlZC5jc3YiKQ0KQ1BTNWdyb3VwZWQNCmBgYA0KDQojIyMgR3JvdXBlZCBkYXRhIHNjYXR0ZXJwbG90DQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KZ2dwbG90KGRhdGE9Q1BTNWdyb3VwZWQsDQogICAgICAgbWFwcGluZz1hZXMoeD1FRGdyb3VwLCB5PUF2V2FnZSkpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHNpemUgPSBOKSkgKw0KICBnZW9tX3Ntb290aChtZXRob2Q9J2xtJykgKw0KICBsYWJzKHg9IkVkdWNhdGlvbiAoeWVhcnMpIiwgeSA9ICJBdmVyYWdlIHdhZ2UgKCQpIikNCmBgYA0KDQojIyMgR3JvdXBlZCByZWdyZXNzaW9uIHN1bW1hcnkNCg0KVGhlIGdyb3VwIG1lYW5zIGxpZSBjbG9zZSB0byBhIGxpbmUsIGFuZCB0aGVpciB1bndlaWdodGVkIHJlZ3Jlc3Npb24gaGFzIGFuIFwoUl4yXCkgYWJvdmUgMC45LiBUaGlzIGRlc2NyaWJlcyB2YXJpYXRpb24gYW1vbmcgMTMgZ3JvdXAgbWVhbnMuIFRoZSBpbmRpdmlkdWFsLWxldmVsIFwoUl4yXCkgYWxzbyBpbmNsdWRlcyB2YXJpYXRpb24gYW1vbmcgcGVvcGxlIHdpdGhpbiBlYWNoIGVkdWNhdGlvbiBncm91cCwgc28gdGhlIHR3byB2YWx1ZXMgYXJlIG5vdCBkaXJlY3RseSBjb21wYXJhYmxlLg0KDQpgYGB7ciBncm91cGVkLXN1bW1hcnl9DQpncm91cGVkLmxtIDwtIGxtKEF2V2FnZSB+IEVEZ3JvdXAsIGRhdGE9Q1BTNWdyb3VwZWQpDQpzdW1tYXJ5KGdyb3VwZWQubG0pDQpgYGANCg0KIyMjIFRoZSBwcm9ibGVtIHdpdGggZXF1YWwgd2VpZ2h0aW5nDQoNClRoZSBmaXJzdCBwb2ludCByZXByZXNlbnRzIG9ubHkgdGhyZWUgcGVvcGxlLCB5ZXQgdGhlIHVud2VpZ2h0ZWQgZml0IGdpdmVzIGl0IHRoZSBzYW1lIGluZmx1ZW5jZSBhcyB0aGUgbXVjaCBsYXJnZXIgMTItLCAxNC0gYW5kIDE2LXllYXIgZWR1Y2F0aW9uIGdyb3Vwcy4gSWYgd2Ugd2FudCBlYWNoIGVtcGxveWVlIHRvIGNvbnRyaWJ1dGUgZXF1YWxseSwgd2UgbmVlZCB0byB3ZWlnaHQgdGhvc2UgZ3JvdXAgbWVhbnMgYnkgdGhlaXIgc2l6ZXMuDQoNClRoZSBpbmRpdmlkdWFsIGRhdGEgc2hvdyBtb3JlIHZhcmlhdGlvbiBiZWNhdXNlIHRoZXkgcmV0YWluIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIHBlb3BsZSB3aXRoaW4gZWFjaCBlZHVjYXRpb24gZ3JvdXAuIFRoZSBncm91cGVkIGFuZCB1bmdyb3VwZWQgZml0cyB0aGVyZWZvcmUgZGVzY3JpYmUgZGlmZmVyZW50IHJlc3BvbnNlIHZhcmlhdGlvbjsgd2Ugc2hvdWxkIG5vdCByZWFkIHRoZWlyIFwoUl4yXCkgdmFsdWVzIGFzIGEgZGlyZWN0IGNvbXBhcmlzb24gb2YgcHJlZGljdGl2ZSBxdWFsaXR5Lg0KDQojIyBVbmdyb3VwZWQgQ1BTNSBkYXRhDQoNCldoYXQgZG9lcyB0aGUgcmVsYXRpb25zaGlwIGxvb2sgbGlrZSB3aGVuIHdlIHVzZSBpbmRpdmlkdWFsIHZhbHVlcyBpbiBwbGFjZSBvZiBncm91cCBtZWFucz8NCg0KYHIgeGZ1bjo6ZW1iZWRfZmlsZSgiLi4vZGF0YS9DUFM1LmNzdiIpYA0KDQpgYGB7ciByZWFkLUNQUzUsIGV2YWw9LTEsIGVjaG89LTJ9DQpDUFM1ID0gcmVhZC5jc3YoIkNQUzUuY3N2IiwgaGVhZGVyPVRSVUUpDQpDUFM1ID0gcmVhZC5jc3YoIi4uL2RhdGEvQ1BTNS5jc3YiLCBoZWFkZXI9VFJVRSkNCmhlYWQoQ1BTNSk7DQpgYGANCg0KIyMjIFVuZ3JvdXBlZCBzY2F0dGVycGxvdA0KDQpgYGB7cn0NCmdncGxvdChkYXRhPUNQUzUsDQogICAgICAgbWFwcGluZz1hZXMoeD1FRCwgeT1XRykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScpICsNCiAgbGFicyh4PSJFZHVjYXRpb24gKHllYXJzKSIsIHk9IldhZ2UgKCQpIikNCmBgYA0KDQojIyMgVW5ncm91cGVkIHJlZ3Jlc3Npb24gc3VtbWFyeQ0KDQpgYGB7cn0NCmNwczUubG0gID0gbG0oV0cgfiBFRCwgZGF0YT1DUFM1KQ0Kc3VtbWFyeShjcHM1LmxtKQ0KYGBgDQoNCldlIHNlZSB0aGVyZSBpcyBhbiBvdXRsaWVyLCB3aGljaCBjb3VsZCBiZSBhIHR5cG8gKGRlY2ltYWwgcG9pbnQgaW4gdGhlIHdyb25nIHBsYWNlPykgSG93ZXZlciB0aGUgbWFpbiBmb2N1cyBoZXJlIGlzIHRoYXQgdGhlcmUgaXMgYSBsb3QgbW9yZSB2YXJpYXRpb24gYW1vbmcgdGhlIGluZGl2aWR1YWwgZGF0YSB0aGFuIHRoZXJlIGlzIGFtb25nIHRoZSBhdmVyYWdlZCBkYXRhLCBzbyB0aGUgJFJeMiQgaXMgbXVjaCBsb3dlci4gQWxzbyB0aGUgcmVncmVzc2lvbiBjb2VmZmljaWVudHMgaGF2ZSBkaWZmZXJlbnQgdmFsdWVzLiANCg0KIyMgV2h5IEdyb3VwZWQgUmVncmVzc2lvbiBSZXN1bHRzIERpZmZlcg0KDQoqRm9ybXVsYXMgbm90IGV4YW1pbmFibGUgYnV0IGNvbmNsdXNpb24gaXMgaW1wb3J0YW50LioNCg0KVG8gZXhwbGFpbiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZXNlIHR3byByZWdyZXNzaW9ucywgY29uc2lkZXIgdGhlIHJlZ3Jlc3Npb24gb2YgJFkkIG9uICRYJCBmb3IgYWxsIGluZGl2aWR1YWxzLCBhbmQgdGhlIHJlZ3Jlc3Npb24gb2YgZ3JvdXAgbWVhbnMgJFxiYXIgWSQgb24gJFxiYXIgWCQuIEluIHRoZSBDUFM1IGV4YW1wbGUsIGV2ZXJ5b25lIHdpdGhpbiBhbiBlZHVjYXRpb24gZ3JvdXAgaGFzIHRoZSBzYW1lIGBFRGAgdmFsdWUsIHNvICR4X3tpan09XGJhciB4X2okLiBUaGUgZXF1aXZhbGVuY2UgYmVsb3cgZGVwZW5kcyBvbiB0aGF0IGNvbmRpdGlvbi4NCkluIExlYXN0IFNxdWFyZXMgd2UgY2hvb3NlICRcYmV0YV8wJCBhbmQgJFxiZXRhXzEkICB0byBtaW5pbWlzZSAgICAgICAgICANCiQkU1Nfe2Vycm9yfSA9ICBcc3VtXm5fe2k9MX0oeV9pIC0gXGJldGFfMC0gXGJldGFfMSB4X2kpXjIgfn5+fiAgICgxKSAgJCQNCkhvd2V2ZXIgdGhlIHJlZ3Jlc3Npb24gZm9yIHRoZSBhdmVyYWdlcyB0cmVhdHMgYWxsICRtJCBncm91cHMgYXMgYmVpbmcgb2YgZXF1YWwgd29ydGg6DQogJCQgXHN1bV5tX3tqPTF9KFxiYXIgeV9qIC0gXGJldGFfMC0gXGJldGFfMSBcYmFyIHhfaileMiB+fn5+fn4gICAoMikgICQkICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpUaGF0IGlzLCAgdGhlIGNvbXB1dGF0aW9ucyB3aWxsIHRyeSBqdXN0IGFzIGhhcmQgdG8gZml0IGEgdmFsdWUgdGhhdCBhcmlzZXMgZnJvbSBhIHNtYWxsIGdyb3VwIG9mIGRhdGEgYXMgYSAgdmFsdWUgZnJvbSBhIGxhcmdlIGdyb3VwLiANCg0KIyMjIEhvdyBhcmUgdGhlc2UgdHdvIHN1bXMgb2Ygc3F1YXJlcyByZWxhdGVkPw0KDQpJZiB3ZSB0YWtlIHRoZSBzdW0gaW4gKDEpIGFuZCByZS1vcmRlciBpdCBpbnRvIEVkdWNhdGlvbiBsZXZlbCBncm91cHMgd2UgZ2V0DQokJFNTX3tlcnJvcn0gPSAgXHN1bV5tX3tqPTF9XHN1bV57bl9qfV97aT0xfSh5X3tpan0gLSBcYmV0YV8wLSBcYmV0YV8xIHhfe2lqfSleMiB+fn5+ICAgKDMpICAkJA0KIA0KTm93IHN1cHBvc2UgZm9yIGEgbW9tZW50IHdlIHByZXRlbmQgdGhhdCBhbGwgdGhlICR5X3tpan0kIHZhbHVlcyBhcmUgdGhlIHNhbWUgYXMgdGhlIGdyb3VwIG1lYW4gJFxiYXIgeV9qJC4gVGhlIHN1bSBvbiB0aGUgcmlnaHQtaGFuZCBzaWRlIHJlZHVjZXMgdG8NCiQkU1Nfe2Vycm9yfSA9ICBcc3VtXm1fe2o9MX0gIG5faihcYmFyIHlfeyBqfSAtIFxiZXRhXzAgLSBcYmV0YV8xIFxiYXIgeF9qKV4yIH5+fn4gICAoNCkgICQkDQoNCg0KU28gdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGlzIHN1bSBhbmQgdGhlIHN1bSAoMikgaXMgdGhhdCB0aGUgc3F1YXJlZCByZXNpZHVhbCAgaXMgbXVsdGlwbGllZCBieSAgJG5faiQsICB0aGUgbnVtYmVyIG9mIHBlb3BsZSBpbiB0aGF0IGdyb3VwLiAgV2UgY2FsbCB0aGlzIG11bHRpcGxpZXIgdGhlICoqd2VpZ2h0KiogICR3X2okLg0KDQpFc3NlbnRpYWxseSB3ZSBpbnRlcnByZXQgdGhlIHdlaWdodCBhcyB0aGUgKiphbW91bnQgb2YgaW5mb3JtYXRpb24gcmVwcmVzZW50ZWQgYnkgdGhlIGRhdGFwb2ludCoqLiAgU28gaGVyZSBpbiB0aGlzIHNpbXBsZSBleGFtcGxlICJhbW91bnQgb2YgaW5mb3JtYXRpb24iIGlzIGVhc2lseSBtZWFzdXJlZCBhcyB0aGUgc2FtcGxlIHNpemUgJHdfaiA9IG5faiQgYXQgZWFjaCBlZHVjYXRpb24gbGV2ZWwuICAgICAgIA0KDQojIyBFcnJvciBTUyBkZWNvbXBvc2l0aW9uDQoNCkJ1dCBpbiBmYWN0IHdlIGRvbid0IGhhdmUgYWxsIHRoZSAkeV97aWp9JCBlcXVhbC4gSW5zdGVhZCB3aGF0IHdlIGRvIGlzIHRvIGFkZCBhbmQgc3VidHJhY3QgJFxiYXIgeV9qJCBpbnNpZGUgdGhlIHBhcmVudGhlc2VzIGluIGVxdWF0aW9uICgzKSwgYW5kIHRoZW4gZXhwYW5kIHRoZSBzcXVhcmU6DQokJFxiZWdpbnthbGlnbmVkfVNTX3tlcnJvcn0gJj0gIFxzdW1ebV97aj0xfVxzdW1ee25fan1fe2k9MX0oeV97aWp9IC1cYmFyIHlfaiArIFxiYXIgeV9qIC0gXGJldGFfMC0gXGJldGFfMSB4X3tpan0pXjJcXCY9ICBcc3VtXm1fe2o9MX1cc3VtXntuX2p9X3tpPTF9KHlfe2lqfSAtXGJhciB5X2opXjIgICsgXHN1bV5tX3tqPTF9XHN1bV57bl9qfV97aT0xfShcYmFyIHlfaiAtIFxiZXRhXzAtIFxiZXRhXzEgXGJhciB4X2opXjIgfn5+fn4oKilcXCY9ICBcc3VtXm1fe2o9MX1cc3VtXntuX2p9X3tpPTF9KHlfe2lqfSAtXGJhciB5X2opXjIgICsgXHN1bV5tX3tqPTF9IG5fan4oXGJhciB5X2ogLSBcYmV0YV8wLSBcYmV0YV8xIFxiYXIgeF9qKV4yXGVuZHthbGlnbmVkfSQkDQoNCigqKSBFcXVhbGl0eSBoYXBwZW5zIGJlY2F1c2UgdGhlIGNyb3NzLXByb2R1Y3QgdGVybSAkXHN1bV5tX3tqPTF9IFxzdW1ee25fan1fe2k9MX0gfjJ+KHlfe2lqfSAtXGJhciB5X2opIChcYmFyIHlfaiAtIFxiZXRhXzAtIFxiZXRhXzEgXGJhciB4X2opID0wJC4gDQoNCiMjIyMgS2V5IGluc2lnaHQ6IFNTIGRlY29tcG9zaXRpb24NCg0KVGhlIGtleSBmYWN0IGlzIHRoYXQgdGhlIGVycm9yIFNTIHNwbGl0cyBpbnRvIHR3byBwYXJ0czogDQoNCiQkU1Nfe2Vycm9yfSA9ICBcc3VtXm1fe2o9MX1cc3VtXntuX2p9X3tpPTF9KHlfe2lqfSAtXGJhciB5X2opXjIgICsgXHN1bV5tX3tqPTF9IG5fan4oXGJhciB5X2ogLSBcYmV0YV8wLSBcYmV0YV8xIFxiYXIgeF9qKV4yJCQNCi0gVGhlIGZpcnN0IHRlcm0gaXMgdGhlICJwdXJlIGVycm9yIiBzdW0gb2Ygc3F1YXJlcy4gSXQgcmVjb3JkcyBob3cgbXVjaCBpbmRpdmlkdWFsIHdhZ2VzIHZhcnkgYXJvdW5kIHRoZWlyIGVkdWNhdGlvbi1ncm91cCBtZWFucyAkXGJhciB5X2okLg0KLSBUaGUgc2Vjb25kIHRlcm0gaXMgdGhlIHdlaWdodGVkIHN1bSBvZiBzcXVhcmVzIGZvciBmaXR0aW5nIHRoZSBncm91cCBtZWFucywgd2l0aCBlYWNoIHNxdWFyZWQgcmVzaWR1YWwgbXVsdGlwbGllZCBieSB0aGUgZ3JvdXAgc2l6ZSAkbl9qJC4NCg0KQmVjYXVzZSBldmVyeW9uZSBpbiBhbiBlZHVjYXRpb24gZ3JvdXAgaGFzIHRoZSBzYW1lIGBFRGAgdmFsdWUsIHRoZSB3aXRoaW4tZ3JvdXAgc3VtIG9mIHNxdWFyZXMgZG9lcyBub3QgZGVwZW5kIG9uIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cy4gTWluaW1pc2luZyB0aGUgaW5kaXZpZHVhbC1sZXZlbCBzdW0gb2Ygc3F1YXJlcyB0aGVyZWZvcmUgZ2l2ZXMgdGhlIHNhbWUgaW50ZXJjZXB0IGFuZCBzbG9wZSBhcyBhIHJlZ3Jlc3Npb24gb2YgdGhlIGdyb3VwIG1lYW5zIHdlaWdodGVkIGJ5IGdyb3VwIHNpemUuIFRoaXMgZXF1YWxpdHkgb2YgY29lZmZpY2llbnQgZXN0aW1hdGVzIGRlcGVuZHMgb24gdGhlIHByZWRpY3RvciBiZWluZyBjb25zdGFudCB3aXRoaW4gZWFjaCBncm91cC4gSXQgZG9lcyBub3Qgc2F5IHRoYXQgdGhlIHR3byBhbmFseXNlcyBoYXZlIHRoZSBzYW1lIHJlc2lkdWFsIGRlZ3JlZXMgb2YgZnJlZWRvbSwgc3RhbmRhcmQgZXJyb3JzIG9yIFItc3F1YXJlZDogdGhlIGdyb3VwZWQgYW5hbHlzaXMgaGFzIGxvc3QgdGhlIHdpdGhpbi1ncm91cCB2YXJpYXRpb24uDQoNCiMjIFdlaWdodGVkIHJlZ3Jlc3Npb24gaW4gUg0KDQpUbyBkbyBhIHdlaWdodGVkIHJlZ3Jlc3Npb24gd2UgaGF2ZSB0byBzcGVjaWZ5IGB3ZWlnaHRzYCBpbiB0aGUgYGxtKClgIGNvbW1hbmQuICAgQmVsb3cgd2UgY29tcGFyZSB0aGUgd2VpZ2h0ZWQgbGVhc3Qgc3F1YXJlcyAoKipXTFMqKikgZXN0aW1hdGVzIGZyb20gdGhlIGF2ZXJhZ2VkIGRhdGEgdG8gdGhlIHVuZ3JvdXBlZCBvcmRpbmFyeSBsZWFzdCBzcXVhcmVzICgqKk9MUyoqKSBlc3RpbWF0ZXMgZnJvbSB0aGUgcmF3IGRhdGEuDQoNCmBgYHtyIHdlaWdodGVkfQ0KY3BzLndscyA9IGxtKEF2V2FnZSB+IEVEZ3JvdXAsIHdlaWdodHM9TiwgZGF0YT1DUFM1Z3JvdXBlZCkNCnN1bW1hcnkoY3BzLndscykNCmBgYA0KDQojIyMjIE9MUyBjb21wYXJpc29uDQoNCmBgYHtyfQ0Kc3VtbWFyeShjcHM1LmxtKQ0KYGBgDQoNClRoZSB3ZWlnaHRlZCBncm91cC1tZWFuIGZpdCBnaXZlcyB0aGUgc2FtZSBpbnRlcmNlcHQgYW5kIHNsb3BlIGFzIHRoZSBpbmRpdmlkdWFsLWxldmVsIGZpdCwgYXBhcnQgZnJvbSByb3VuZGluZyBpbiB0aGUgc3VwcGxpZWQgbWVhbnMuIEVkdWNhdGlvbiBpcyBjb25zdGFudCB3aXRoaW4gZWFjaCBncm91cC4gVGhlIHN0YW5kYXJkIGVycm9ycyBhbmQgJFJeMiQgbmVlZCBub3QgbWF0Y2g6IHRoZSBncm91cGVkIGZpdCBoYXMgb25seSAxMyByb3dzIGFuZCBkb2VzIG5vdCBpbmNsdWRlIHRoZSB3aXRoaW4tZ3JvdXAgdmFyaWF0aW9uIGluIHdhZ2VzLg0KDQojIyBDb21wYXJpbmcgV0xTIHZzIE9MUyBwbG90cw0KDQpUaGUgZm9sbG93aW5nIHBsb3Qgc2hvd3MgdGhlIGF2ZXJhZ2VkIGRhdGEgd2l0aCB0aGUgdW53ZWlnaHRlZCAoZGFzaGVkKSBhbmQgd2VpZ2h0ZWQgKHNvbGlkIHJlZCkgbGluZXMuIFRoZSB3ZWlnaHRlZCBsaW5lIGlzIGNsb3NlciB0byB3aGVyZSBtb3JlIGRhdGEgYXJlLg0KDQpgYGB7ciBXTFMtdnMtT0xTLCBtZXNzYWdlPUZBTFNFfQ0KZ2dwbG90KGRhdGE9Q1BTNWdyb3VwZWQsDQogICAgICAgbWFwcGluZz1hZXMoeD1FRGdyb3VwLCB5PUF2V2FnZSkpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nPWFlcyhzaXplPU4pKSArDQogIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBtYXBwaW5nPWFlcyh3ZWlnaHQ9TiksIGNvbD0ncmVkJywgc2U9RkFMU0UpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIGxpbmV0eXBlID0gJ2Rhc2hlZCcgLCBzZT1GQUxTRSkgKw0KICBsYWJzKHg9IkVkdWNhdGlvbiAoeWVhcnMpIiwgeT0iQXZlcmFnZSB3YWdlICgkKSIpDQpgYGANCg0KIyMgUmVzaWR1YWxzIGZvciBXZWlnaHRlZCBSZWdyZXNzaW9uDQoNCkNsZWFybHkgYSBwbG90IG9mIG9yZGluYXJ5IHJlc2lkdWFscyBjb3VsZCBiZSBtaXNsZWFkaW5nIGluIHRoZSBjb250ZXh0IG9mIHdlaWdodGVkIGxlYXN0IHNxdWFyZXMuICBUaGUgbG93IHdlaWdodCBwb2ludHMgb24gdGhlIGxlZnQgYXJlIGJhZGx5IGZpdHRlZCBhbmQgdGhpcyBjb3VsZCBtYWtlIHRoZW0gbG9vayBsaWtlIGluZmx1ZW50aWFsIG91dGxpZXJzLiANCiANCmBgYHtyIHJlc2lkdWFscy1mcm9tLVdMUywgZmlnLmhlaWdodD00fQ0KYXVnbWVudChjcHMud2xzLCBDUFM1Z3JvdXBlZCkgfD4NCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeD1FRGdyb3VwLCB5PS5yZXNpZCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTAsIGxpbmV0eXBlPSJkb3R0ZWQiKSArDQogIGxhYnMoeT0iU2ltcGxlIHJlc2lkdWFscyIsIHg9IkVkdWNhdGlvbiBncm91cCAoeWVhcnMpIikNCmBgYA0KDQojIyMgUGVhcnNvbiByZXNpZHVhbHMNCg0KSW5zdGVhZCB3ZSB1c2Ugd2VpZ2h0ZWQsIG9yIFBlYXJzb24sIHJlc2lkdWFscyAkcl9qID0gXHNxcnR7d19qfSh5X2ogLSBcaGF0IHlfaikkLg0KIA0KYGBge3Igd2VpZ2h0ZWQtcmVzaWR1YWxzLCBmaWcuaGVpZ2h0PTQuNX0NCmF1Z21lbnQoY3BzLndscywgQ1BTNWdyb3VwZWQpIHw+DQogIG11dGF0ZShwZWFyc29uID0gLnJlc2lkKnNxcnQoTikpIHw+DQogIGdncGxvdChtYXBwaW5nID0gYWVzKHg9RURncm91cCwgeT1wZWFyc29uKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MCwgbGluZXR5cGU9ImRvdHRlZCIpICsNCiAgbGFicyh5PSJQZWFyc29uICh3ZWlnaHRlZCkgcmVzaWR1YWxzIiwgeD0iRWR1Y2F0aW9uIGdyb3VwICh5ZWFycykiKQ0KYGBgDQoNCg0KVGhlIHdlaWdodGVkIHJlc2lkdWFscyBhcmUgbWVhc3VyZWQgb24gdGhlIHNjYWxlIHVzZWQgYnkgdGhlIHdlaWdodGVkIGZpdC4gQSBzbWFsbCB3ZWlnaHRlZCByZXNpZHVhbCBtZWFucyB0aGUgb2JzZXJ2YXRpb24gaXMgY2xvc2UgdG8gaXRzIGZpdHRlZCB2YWx1ZSByZWxhdGl2ZSB0byB0aGUgYXNzdW1lZCB2YXJpYW5jZTsgaXQgaXMgbm90IGEgY29tcGxldGUgaW5mbHVlbmNlIGFuYWx5c2lzIGFuZCBkb2VzIG5vdCBtYWtlIGEgcG9pbnQgc2NpZW50aWZpY2FsbHkgdW5pbXBvcnRhbnQuIFRoZSBvcmRpbmFyeSBhbmQgd2VpZ2h0ZWQgcmVzaWR1YWwgcGFuZWxzIGhhdmUgZGlmZmVyZW50IHNjYWxlcywgc28gY29tcGFyZSB0aGVpciBwYXR0ZXJucyByYXRoZXIgdGhhbiB0aGVpciB2ZXJ0aWNhbCBtYWduaXR1ZGVzLg0KDQojIyBBcHBsaWNhdGlvbjogV2VpZ2h0aW5nIEZvciBOb24tQ29uc3RhbnQgVmFyaWFuY2UNCg0KU3VwcG9zZSB3ZSBoYXZlIGRhdGEgZm9yIHdoaWNoIHRoZSB2YXJpYW5jZSBpcyBub24tY29uc3RhbnQuIA0KU28gd2Ugd2FudCB0byBnaXZlIG1vcmUgd2VpZ2h0IHRvIHRob3NlIHBvaW50cyB3aGljaCBoYXZlIHNtYWxsIHZhcmlhbmNlIChvciBhcmUga25vd24gbW9yZSBwcmVjaXNlbHkpICBhbmQgZ2l2ZSBsZXNzIHdlaWdodCB0byB0aG9zZSBwb2ludHMgdGhhdCBoYXZlIGJpZ2dlciB2YXJpYW5jZSAgKG9yIGFyZSBrbm93biBsZXNzIHByZWNpc2VseSkuDQoNClRoaXMgc3RpbGwgZml0cyB3aXRoIHRoZSBpZGVhIHRoYXQgV2VpZ2h0IHJlcHJlc2VudHMgdGhlICBhbW91bnQgb2YgaW5mb3JtYXRpb24gY2FycmllZCBieSBlYWNoIGRhdGFwb2ludC4gDQoNCiMjIyBTdXBlcnZpc29ycyBhbmQgV29ya2Vycw0KDQpUaGUgZm9sbG93aW5nIGRhdGEgaXMgb24gdGhlIG51bWJlciBvZiBzdXBlcnZpc29ycyAkWSQgKGUuZy4gdGVhbSBsZWFkZXJzKSBhbmQgdGhlIG51bWJlciBvZiB3b3JrZXJzICRYJCBhdCBhIG51bWJlciBvZiBpbmR1c3RyaWFsIGVzdGFibGlzaG1lbnRzLiAgIA0KVGhlIGRhdGEgYXJlIGZyb20gdGhlIGJvb2sgYnkgQ2hhdHRlcmplZSBhbmQgUHJpY2UgIlJlZ3Jlc3Npb24gQW5hbHlzaXMgYnkgRXhhbXBsZSINCg0KYHIgeGZ1bjo6ZW1iZWRfZmlsZSgiLi4vZGF0YS9zdXBlcnZpc29ycy5jc3YiKWANCg0KYGBge3IgcmVhZC1TdXBlcnZpc29ycywgaW5jbHVkZT1GQUxTRX0NCnN1cGVydmlzb3JzID0gcmVhZC5jc3YoIi4uL2RhdGEvc3VwZXJ2aXNvcnMuY3N2IikNCnN1cGVydmlzb3JzDQpgYGANCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCmdncGxvdChzdXBlcnZpc29ycywNCiAgICAgICBtYXBwaW5nPWFlcyh4PVgsIHk9WSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScpICsNCiAgbGFicyh4ID0gIk51bWJlciBvZiB3b3JrZXJzIChYKSIsIHkgPSAiTnVtYmVyIG9mIHN1cGVydmlzb3JzIChZKSIpDQpgYGANCg0KIyMjIyBPTFMgcmVzaWR1YWwgZGlhZ25vc3RpY3MNCg0KTm90ZSB0aGF0IHRoZSBsaW5lIGlzIG92ZXJzaG9vdGluZyB0aGUgY2x1c3RlciBvZiBwb2ludHMgb24gdGhlIGxlZnQuICANClRoZSByZXNpZHVhbCBwbG90cyBzaG93IGN1cnZhdHVyZSBhbmQgaGV0ZXJvc2NlZGFzdGljaXR5LiAgDQpUaGUgZm91cnRoIGdyYXBoIHNob3dzIGEgc2ltcGxlIGxpbmVhciBtb2RlbCBpcyBoaWdobHkgaW5mbHVlbmNlZCBieSB0aGUgcG9pbnRzIGF0IHRoZSByaWdodCwgd2hpY2ggaGF2ZSB2ZXJ5IGxhcmdlIHJlc2lkdWFscy4NCg0KYGBge3IgU3VwZXJ2aXNvci1yZXNpZHVhbHMsIGZpZy5oZWlnaHQ9NH0NCnN1cC5sbSA8LSBsbShZIH4gWCwgZGF0YT1zdXBlcnZpc29ycykNCnBhcihtZnJvdz1jKDEsMikpDQpwbG90KHN1cC5sbSwgd2hpY2g9YygxLDUpKQ0KYGBgDQoNCiMjIEhldGVyb3NjZWRhc3RpY2l0eSBwYXR0ZXJuDQoNCkluIHRoZSByZXNpZHVhbHMtdmVyc3VzLWZpdHRlZCBwbG90LCB0aGUgaG9yaXpvbnRhbCBheGlzIHNob3dzIGZpdHRlZCBudW1iZXJzIG9mIHN1cGVydmlzb3JzLiBDb21wYXJlIHRoZSB2ZXJ0aWNhbCBzcHJlYWQgYXJvdW5kIGZpdHRlZCAkWSQgdmFsdWVzIG9mIDUwLCAxMDAgYW5kIDE1MC4gVGhlIHNwcmVhZCBpbmNyZWFzZXMgYXMgdGhlIGZpdHRlZCB2YWx1ZSByaXNlcy4gU2luY2UgZml0dGVkICRZJCBhbHNvIHJpc2VzIHdpdGggJFgkLCBhIHJlYXNvbmFibGUgd29ya2luZyBtb2RlbCBpcyAkXG1ib3h7c2R9KFx2YXJlcHNpbG9uX2kpIFxwcm9wdG8geF9pJCwgb3IgJFxzaWdtYV9pPWt4X2kkIGZvciBzb21lIGNvbnN0YW50ICRrJC4NCg0KTm93IGxldCB1cyBzdXBwb3NlIHdlIHdhbnQgdG8gZml0IGEgc3RyYWlnaHQtbGluZSBtb2RlbCAkJHlfaSA9IFxiZXRhXzAgKyBcYmV0YV8xIHhfaSAgKyBcdmFyZXBzaWxvbl9pIH4gICAkJA0KV2Ugd291bGQgb3JkaW5hcmlseSBzb2x2ZSB0aGlzIGJ5IG1pbmltaXNpbmcgdGhlIA0KJCRTU197ZXJyb3J9ID0gXHN1bSggeV9pIC0gXGJldGFfMCAtIFxiZXRhXzEgeF9pKV4yJCQgd2hlcmUgdGhlIGNvbXBvbmVudHMgb2YgdGhlIHN1bSAodGhlIHJlc2lkdWFscyAkeV9pIC0gXGJldGFfMCAtIFxiZXRhXzEgeF9pJCkgaGF2ZSBjb25zdGFudCBzdGFuZGFyZCBkZXZpYXRpb24uICBFeGNlcHQgbm93IHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gaXMgbm90IGNvbnN0YW50LiANCg0KQnV0IHdoYXQgd2UgY291bGQgZG8gaXMgZGl2aWRlIHRocm91Z2ggYnkgJHhfaSQuICBUaGUgbW9kZWwgYmVjb21lcw0KJCRcYmVnaW57YWxpZ25lZH1cZnJhY3t5X2l9e3hfaX0gJj0gXGZyYWN7XGJldGFfMH17eF9pfSArIFxiZXRhXzFcZnJhY3t4X2l9e3hfaX0gICsgXGZyYWN7XHZhcmVwc2lsb25faX17eF9pfVxcWV9pICY9XGJldGFfMCBYX2kgKyBcYmV0YV8xICsgXGV0YV9pXGVuZHthbGlnbmVkfSQkDQp3aGVyZSAkWV9pID0gXGZyYWN7eV9pfXt4X2l9JCwgJFhfaSA9IFxmcmFjezF9e3hfaX0kIGFuZCAkXGV0YV9pID0gXGZyYWN7XHZhcmVwc2lsb25faX17eF9pfSQuDQoNCiMjIyBDb25zdGFudCB2YXJpYW5jZSBhZnRlciB0cmFuc2Zvcm1hdGlvbg0KDQpUaGUgZXJyb3IgdGVybSAkXGV0YV9pJCBub3cgaGFzIGNvbnN0YW50IHN0YW5kYXJkIGRldmlhdGlvbiBhcw0KDQokJFxtYm94e3NkfVxsZWZ0KCBcZnJhY3tcdmFyZXBzaWxvbl9pfXt4X2l9XHJpZ2h0KSA9IFxmcmFjeyBreF9pfXt4X2l9ID0gay4kJA0KDQpXZSBub3cgaGF2ZSBhIGxpbmVhciBtb2RlbCB3aXRoIGNvbnN0YW50IHZhcmlhbmNlIHNvIGNhbiBzb2x2ZSBieSBsZWFzdCBzcXVhcmVzIGluIHRoZSB1c3VhbCB3YXkuDQoNCldlIGNvdWxkIGRvIHRoZSB0cmFuc2Zvcm1hdGlvbiBvdXJzZWx2ZXMgYW5kIHRoZW4ganVzdCB1c2UgYGxtKClgIG5vcm1hbGx5LCBidXQgYW5vdGhlciB3YXkgdG8gbG9vayBhdCBpdCBpcyB3ZSdyZSBtaW5pbWlzaW5nDQoNCiQkXHN1bSBcbGVmdChcZnJhY3t5X2l9e3hfaX0gLSBcZnJhY3tcYmV0YV8wfXt4X2l9IC0gXGJldGFfMSBccmlnaHQpXjIgICB+fj0gfn4gXHN1bSBcZnJhY3sxfXt4X2leMn1cbGVmdCh5X2kgLSBcYmV0YV8wIC1cYmV0YV8xIHhfaSBccmlnaHQpXjIkJA0KDQp3aGljaCBpcyBhIHdlaWdodGVkIHJlZ3Jlc3Npb24gd2l0aCAkd19pID0gMS8geF9pXjIkLiAgU28gdGhlIHRhc2sgd2l0aCB0aGlzIHRyYW5zZm9ybWF0aW9uIGlzIHRoZSBzYW1lIGFzIHdlaWdodGVkIGxlYXN0IHNxdWFyZXMgd2l0aCB0aGVzZSB3ZWlnaHRzLg0KDQojIyBXTFMgZm9yIHN1cGVydmlzb3JzDQoNCmBgYHtyIHdscy1mb3Itc3VwZXJ2aXNvcnN9DQpzdXAud2xzID0gbG0oWSB+IFggICwgZGF0YT1zdXBlcnZpc29ycywgd2VpZ2h0cyA9IDEvWF4yICkNCnN1bW1hcnkoc3VwLndscykNCmBgYA0KDQojIyMgV0xTIHZzIE9MUyBzdXBlcnZpc29ycyBwbG90DQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KZ2dwbG90KGRhdGE9c3VwZXJ2aXNvcnMsDQogICAgICAgbWFwcGluZz1hZXMoeD1YLCB5PVkpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBtYXBwaW5nPWFlcyh3ZWlnaHQ9MS9YXjIpLCBjb2w9J3JlZCcsIHNlPUZBTFNFKSArDQogIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBsaW5ldHlwZT0nZG90dGVkJywgc2U9RkFMU0UpICsNCiAgbGFicyh4PSJOdW1iZXIgb2Ygd29ya2VycyAoWCkiLCB5ID0gIk51bWJlciBvZiBzdXBlcnZpc29ycyAoWSkiKQ0KYGBgDQoNCiMjIyBXZWlnaHRlZCByZXNpZHVhbHMgcGxvdA0KDQpUaGUgd2VpZ2h0ZWQgbGluZSBzaXRzIGNsb3NlciB0byB0aGUgbG93LSRYJCBvYnNlcnZhdGlvbnMsIHdoZXJlIHRoZSBhc3N1bWVkIGVycm9yIHZhcmlhbmNlIGlzIHNtYWxsZXIgYW5kIHRoZSB3ZWlnaHRzIGFyZSBsYXJnZXIuIFRoZSBPTFMgbGluZSBnaXZlcyB0aGVzZSBwb2ludHMgdGhlIHNhbWUgcGVyLW9ic2VydmF0aW9uIHdlaWdodCBhcyB0aGUgaGlnaGVyLSRYJCBvYnNlcnZhdGlvbnMsIGRlc3BpdGUgdGhlaXIgZ3JlYXRlciByZXNpZHVhbCBzcHJlYWQuDQoNCkxldCdzIGNoZWNrIHRoZSB3ZWlnaHRlZCByZXNpZHVhbHMgYmVsb3cuIFRoZWlyIHNwcmVhZCBpcyBtb3JlIHNpbWlsYXIgYWNyb3NzIHRoZSByYW5nZSBvZiAkWCQsIGFsdGhvdWdoIGEgbGl0dGxlIGN1cnZhdHVyZSBtYXkgcmVtYWluLiBUaGlzIGlzIG1vcmUgY29uc2lzdGVudCB3aXRoIHRoZSBwcm9wb3NlZCB2YXJpYW5jZSBtb2RlbDsgdGhlIHBsb3QgY2Fubm90IGVzdGFibGlzaCB0aGF0IHRoZSB2YXJpYW5jZSBpcyBleGFjdGx5IGNvbnN0YW50Lg0KDQpgYGB7ciBXdC1SZXMtZm9yLVN1cC53bHMsIGZpZy5oZWlnaHQ9NH0NCmF1Z21lbnQoc3VwLndscykgfD4NCiAgbXV0YXRlKHBlYXJzb24gPSAucmVzaWQvWCkgfD4NCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeD1YLCB5PXBlYXJzb24pKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdD0wLCBsaW5ldHlwZT0iZG90dGVkIikgKw0KICBsYWJzKHk9IlBlYXJzb24gKHdlaWdodGVkKSByZXNpZHVhbHMiLCB4PSJOdW1iZXIgb2Ygd29ya2VycyAoWCkiKQ0KYGBgDQoNCiMjIyBCYXNpYyBydWxlIGZvciBXZWlnaHRpbmcNCg0KVGhlIGNvbW1vbiBydWxlIGlzIGludmVyc2UgdmFyaWFuY2U6IFwod19pIFxwcm9wdG8gMS9cb3BlcmF0b3JuYW1le1Zhcn0oWV9pIFxtaWQgWF9pKVwpLiBJbiB0aGUgQ1BTNSBleGFtcGxlIGVhY2ggcm93IHdhcyBhIGdyb3VwIG1lYW47IGZvciBzdXBlcnZpc29ycyBlYWNoIHJvdyB3YXMgb25lIGVzdGFibGlzaG1lbnQuIFRob3NlIGRpZmZlcmVudCByb3dzIGxlZCB0byBkaWZmZXJlbnQgZXhwcmVzc2lvbnMgZm9yIHRoZSB3ZWlnaHQuDQoNCiMjIEFwcGxpY2F0aW9uOiBNZXRhIEFuYWx5c2lzIENQUzVncm91cGVkIGRhdGENCg0KKipNZXRhLWFuYWx5c2lzKiogY29tYmluZXMgZXN0aW1hdGVzIGZyb20gc2V2ZXJhbCBzdHVkaWVzLiBJbiBhIHNpbXBsZSBpbnZlcnNlLXZhcmlhbmNlIG1ldGEtYW5hbHlzaXMsIGVhY2ggc3R1ZHkgY29udHJpYnV0ZXMgYW4gZXN0aW1hdGUgd2l0aCBpdHMgb3duIHN0YW5kYXJkIGVycm9yLiBUaGUgZXN0aW1hdGUgaXMgd2hhdCB3ZSB3ZWlnaHQsIHNvIGl0cyB3ZWlnaHQgaXMgcHJvcG9ydGlvbmFsIHRvIFwoMS9cbWF0aHJte1NFfV4yXCkuIEFuIGVzdGltYXRlIHdpdGggYSBzbWFsbGVyIHN0YW5kYXJkIGVycm9yIGNvbnRyaWJ1dGVzIG1vcmUuDQoNCkluIHRoZSBDUFM1IGdyb3VwZWQgZGF0YSwgZWFjaCByb3cgaXMgYSBtZWFuIHdhZ2UgZm9yIGBOYCBlbXBsb3llZXMuIFRoZSBgU3REZXZgIGNvbHVtbiBlc3RpbWF0ZXMgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBpbmRpdmlkdWFsIHdhZ2VzIHdpdGhpbiB0aGF0IGdyb3VwLiBUaGUgdmFyaWFuY2Ugb2YgdGhlIGdyb3VwIG1lYW4gaXMgdGhlcmVmb3JlIGFwcHJveGltYXRlbHkgXChcbWF0aHJte1N0RGV2fV9qXjIvbl9qXCksIGdpdmluZyB0aGUgd2VpZ2h0IFwod19qPW5fai9cbWF0aHJte1N0RGV2fV9qXjJcKS4gVGhpcyB1c2VzIGJvdGggZ3JvdXAgc2l6ZSBhbmQgdGhlIHZhcmlhdGlvbiBhbW9uZyBlbXBsb3llZXMgaW4gZWFjaCBncm91cC4NCg0KYGBge3IgQ1BTNS1yZS13ZWlnaHRlZCwgdGlkeT1GQUxTRX0NCkNQUzV3ZWlnaHRlZCA8LSBDUFM1Z3JvdXBlZCB8PiBtdXRhdGUoV3QgPSBOLyBTdERldl4yKQ0KY3BzLndsczIgPC0gbG0oQXZXYWdlIH4gRURncm91cCwgd2VpZ2h0cz1XdCwgZGF0YT1DUFM1d2VpZ2h0ZWQpDQpzdW1tYXJ5KGNwcy53bHMyKQ0KYGBgDQoNCiMjIyBDUFM1IHJlLXdlaWdodGVkIHBsb3QNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIGZpZy5oZWlnaHQ9NH0NCmdncGxvdChkYXRhPUNQUzV3ZWlnaHRlZCwNCiAgICAgICBtYXBwaW5nPWFlcyh4PUVEZ3JvdXAsIHk9QXZXYWdlKSkgKw0KICBnZW9tX3BvaW50KG1hcHBpbmc9YWVzKHNpemU9TikpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIG1hcHBpbmc9YWVzKHdlaWdodD1XdCksIGNvbD0ncmVkJywgc2U9RkFMU0UpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIG1hcHBpbmc9YWVzKHdlaWdodD1OKSwgbGluZXR5cGU9J2RvdHRlZCcsIHNlPUZBTFNFKSArDQogIGxhYnMoeD0iRWR1Y2F0aW9uIGdyb3VwICh5ZWFycykiLCB5PSJBdmVyYWdlIFdhZ2UgKCQpIikNCmBgYA0KDQpUaGUgc3RhbmRhcmQgZGV2aWF0aW9ucyBhcmUgbXVjaCBoaWdoZXIgZm9yIHRoZSBoaWdobHktZWR1Y2F0ZWQgZ3JvdXBzLCBzbyB0aGF0IGNvdW50ZXJhY3RzIHNvbWUgb2YgdGhlIGVmZmVjdCBvZiBoaWdoZXIgc2FtcGxlIHNpemVzLg0KDQojIyBSdW5uaW5nIEV4YW1wbGU6IFdlaWdodGVkIFN1bW1hcmllcyBmb3IgU2VvdWwgQmlrZSBEYXRhDQoNClRoZSBTZW91bCBiaWtlIGRhdGEgYWxzbyBzaG93IHdoeSBncm91cGVkIHN1bW1hcmllcyBuZWVkIGNhcmUuIElmIHdlIGF2ZXJhZ2UgZGVtYW5kIHdpdGhpbiB0ZW1wZXJhdHVyZSBiYW5kcywgc29tZSBncm91cHMgcmVwcmVzZW50IG1hbnkgbW9yZSBob3VybHkgb2JzZXJ2YXRpb25zIHRoYW4gb3RoZXJzLg0KDQpgYGB7ciBzZW91bC13ZWlnaHRlZC1yZWFkLCBtZXNzYWdlPUZBTFNFfQ0Kc2VvdWxfaG91cmx5ID0gcmVhZF9jc3YoIi4uL2RhdGEvc2VvdWxfYmlrZV9ob3VybHkuY3N2IikNCmBgYA0KDQpgYGB7ciBzZW91bC1ncm91cGVkfQ0Kc2VvdWxfZ3JvdXBlZCA9IHNlb3VsX2hvdXJseSB8Pg0KICBtdXRhdGUoDQogICAgdGVtcF9iYW5kID0gY3V0KA0KICAgICAgdGVtcGVyYXR1cmUsDQogICAgICBicmVha3MgPSBjKC0yMCwgLTEwLCAwLCAxMCwgMjAsIDMwLCA0MCksDQogICAgICBpbmNsdWRlLmxvd2VzdCA9IFRSVUUNCiAgICApDQogICkgfD4NCiAgZ3JvdXBfYnkodGVtcF9iYW5kKSB8Pg0KICBzdW1tYXJpc2UoDQogICAgYXZnX2Jpa2VfY291bnQgPSBtZWFuKHJlbnRlZF9iaWtlX2NvdW50KSwNCiAgICBhdmdfdGVtcGVyYXR1cmUgPSBtZWFuKHRlbXBlcmF0dXJlKSwNCiAgICBOID0gbigpLA0KICAgIC5ncm91cHMgPSAiZHJvcCINCiAgKQ0KDQpzZW91bF9ncm91cGVkDQpgYGANCg0KYGBge3Igc2VvdWwtZ3JvdXBlZC1wbG90LCBtZXNzYWdlPUZBTFNFfQ0KZ2dwbG90KHNlb3VsX2dyb3VwZWQsIGFlcyh4ID0gYXZnX3RlbXBlcmF0dXJlLCB5ID0gYXZnX2Jpa2VfY291bnQpKSArDQogIGdlb21fcG9pbnQoYWVzKHNpemUgPSBOKSkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGFlcyh3ZWlnaHQgPSBOKSwgc2UgPSBGQUxTRSwgY29sb3VyID0gInJlZCIpICsNCiAgbGFicygNCiAgICB4ID0gIkF2ZXJhZ2UgdGVtcGVyYXR1cmUgd2l0aGluIGJhbmQiLA0KICAgIHkgPSAiQXZlcmFnZSBiaWtlIGRlbWFuZCIsDQogICAgdGl0bGUgPSAiQWdncmVnYXRlZCBncm91cHMgd2l0aCBkaWZmZXJlbnQgc3VwcG9ydCBzaG91bGQgbm90IGJlIHRyZWF0ZWQgZXF1YWxseSINCiAgKQ0KYGBgDQoNClRoZXNlIHRlbXBlcmF0dXJlIGJhbmRzIHN1bW1hcmlzZSB1bmVxdWFsIG51bWJlcnMgb2YgaG91cnMuIFdlaWdodGluZyBhIGJhbmQncyBtZWFuIGJ5IGl0cyBjb3VudCBjaGFuZ2VzIGhvdyBtdWNoIHRoYXQgc3VtbWFyeSBpbmZsdWVuY2VzIHRoZSBsaW5lLiBIZXJlIHRlbXBlcmF0dXJlIGFsc28gdmFyaWVzICp3aXRoaW4qIGVhY2ggYmFuZCwgc28gdGhlIGxpbmUgdGhyb3VnaCB3ZWlnaHRlZCBiYW5kIG1lYW5zIG5lZWQgbm90IGJlIGlkZW50aWNhbCB0byBhIGxpbmUgZml0dGVkIHRvIGFsbCB0aGUgaG91cmx5IG9ic2VydmF0aW9ucy4gVGhlIGV4YWN0IGVxdWFsaXR5IGluIHRoZSBlZHVjYXRpb24gZXhhbXBsZSByZWxpZWQgb24gZXZlcnlib2R5IGluIGEgZ3JvdXAgaGF2aW5nIHRoZSBzYW1lIHByZWRpY3RvciB2YWx1ZS4NCg0KIyMgUHV0dGluZyB0aGUgd2VpZ2h0cyB0b2dldGhlcg0KDQpXZSBoYXZlIG5vdyB1c2VkIGZvdXIgZm9ybXMgb2Ygd2VpZ2h0LiBJbiBldmVyeSBjYXNlLCBzdGFydCB3aXRoIHRoZSB2YXJpYW5jZSBvZiB0aGUgcmVzcG9uc2UgcmVwcmVzZW50ZWQgYnkgb25lIHJvdywgdGhlbiB0YWtlIGl0cyByZWNpcHJvY2FsLg0KDQp8IFdoYXQgb25lIHJvdyByZXByZXNlbnRzIHwgVmFyaWFuY2Ugb2YgdGhhdCByb3cgfCBSZWxhdGl2ZSB3ZWlnaHQgfCBFeGFtcGxlIGluIHRoaXMgbGVjdHVyZSB8DQp8Oi0tfDotLXw6LS18Oi0tfA0KfCBBIG1lYW4gb2YgXChuX2pcKSBpbmRlcGVuZGVudCBwZW9wbGUsIHdpdGggYSBjb21tb24gaW5kaXZpZHVhbCB2YXJpYW5jZSBcKFxzaWdtYV4yXCkgfCBcKFxzaWdtYV4yL25falwpIHwgXChuX2pcKSB8IEZpcnN0IENQUzUgZ3JvdXBlZCBmaXQsIGB3ZWlnaHRzPU5gIHwNCnwgQW4gb2JzZXJ2YXRpb24gb3IgZXN0aW1hdGUgd2l0aCB2YXJpYW5jZSBcKHZfaVwpIHwgXCh2X2lcKSB8IFwoMS92X2lcKSB8IEdlbmVyYWwgcnVsZTsgc3R1ZHkgZXN0aW1hdGVzIGluIG1ldGEtYW5hbHlzaXMgfA0KfCBBIG1lYW4gb2YgXChuX2pcKSBpbmRlcGVuZGVudCBwZW9wbGUsIHdpdGggaW5kaXZpZHVhbCB2YXJpYW5jZSBcKFxzaWdtYV9qXjJcKSBpbiBncm91cCBcKGpcKSB8IFwoXHNpZ21hX2peMi9uX2pcKSB8IFwobl9qL1xzaWdtYV9qXjJcKSB8IENQUzUgcmUtd2VpZ2h0ZWQgZml0LCBgd2VpZ2h0cz1OL1N0RGV2XjJgIHwNCnwgQW4gb2JzZXJ2YXRpb24gd2hvc2UgZXJyb3Igc3RhbmRhcmQgZGV2aWF0aW9uIGlzIHByb3BvcnRpb25hbCB0byBcKHhfaVwpIHwgXChrXjJ4X2leMlwpIHwgXCgxL3hfaV4yXCkgfCBTdXBlcnZpc29ycyBhbmQgd29ya2VycywgYHdlaWdodHM9MS9YXjJgIHwNCg0KVGhlIGV4dHJhIFwobl9qXCkgaW4gXChuX2ovXHNpZ21hX2peMlwpIGNvbWVzIGZyb20gYXZlcmFnaW5nOiB0aGUgdmFyaWFuY2Ugb2YgYSBtZWFuIG9mIFwobl9qXCkgaW5kZXBlbmRlbnQgb2JzZXJ2YXRpb25zIGlzIHRoZSBpbmRpdmlkdWFsIHZhcmlhbmNlIGRpdmlkZWQgYnkgXChuX2pcKS4gTGlrZXdpc2UsIFwoMS94X2leMlwpIGZvbGxvd3MgZnJvbSBpbnZlcnNlIHZhcmlhbmNlIHdoZW4gdGhlIGVycm9yIHN0YW5kYXJkIGRldmlhdGlvbiBpcyBcKGt4X2lcKS4gQSBjb25zdGFudCBzaGFyZWQgYnkgZXZlcnkgcm93LCBzdWNoIGFzIFwoXHNpZ21hXjJcKSBvciBcKGteMlwpLCBjYW4gYmUgbGVmdCBvdXQgb2YgdGhlIHJlbGF0aXZlIHdlaWdodHMuDQo=