Linear Regression for High Dimensional Data
Consider linear regression model (for double centered data)
\[
Y_i = \beta_1X_{i1} + \beta_2 X_{i2} + \cdots + \beta_pX_{ip} + \epsilon_i ,
\]
with \(\text{E}\left[\epsilon \mid \mathbf{X}\right]=0\) and \(\text{var}\left[\epsilon \mid \mathbf{X}\right]=\sigma^2\).
In matrix notation the model becomes
\[
\mathbf{Y} = \mathbf{X}\mathbf\beta + \mathbf\epsilon.
\]
The least squares estimator of \(\mathbf\beta\) is given by
\[
\hat{\mathbf\beta} = (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y} ,
\]
and the variance of \(\hat{\mathbf\beta}\) equals
\[
\text{var}\left[\hat{\mathbf\beta}\right] = (\mathbf{X}^T\mathbf{X})^{-1}\sigma^2.
\]
\(\longrightarrow\) the \(p \times p\) matrix \((\mathbf{X}^T\mathbf{X})^{-1}\) is crucial
Note, that
with double centered data it is meant that both the responses are centered (mean of \(\mathbf{Y}\) is zero) and that all predictors are centered (columns of \(\mathbf{X}\) have zero mean). With double centered data the intercept in a linear regression model is always exactly equal to zero and hence the intercept must not be included in the model.
we do not assume that the residuals are normally distributed. For prediction purposes this is often not required (normality is particularly important for statistical inference in small samples).
Linear Regression for multivariate data vs High Dimensional Data
\(\mathbf{X^TX}\) and \((\mathbf{X^TX})^{-1}\) are \(p \times p\) matrices
\(\mathbf{X^TX}\) can only be inverted if it has rank \(p\)
Rank of a matrix of form \(\mathbf{X^TX}\), with \(\mathbf{X}\) and \(n\times p\) matrix, can never be larger than \(\min(n,p)\).
in most regression problems \(n>p\) and rank of \((\mathbf{X^TX})\) equals \(p\)
in high dimensional regression problems \(p >>> n\) and rank of \((\mathbf{X^TX})\) equals \(n<p\)
in the toxicogenomics example \(n=30<p=4000\) and \(\text{rank}(\mathbf{X^TX})\leq n=30\).
\(\longrightarrow\) \((\mathbf{X^TX})^{-1}\) does not exist, and neither does \(\hat{\boldsymbol{\beta}}\).
Can SVD help?
Since the columns of \(\mathbf{X}\) are centered, \(\mathbf{X^TX} \propto \text{var}\left[\mathbf{X}\right]\).
if \(\text{rank}(\mathbf{X^TX})=n=30\), the PCA will give 30 components, each being a linear combination of \(p=4000\) variables. These 30 PCs contain all information present in the original \(\mathbf{X}\) data.
if \(\text{rank}(\mathbf{X})=n=30\), the SVD of \(\mathbf{X}\) is given by
\[
\mathbf{X} = \sum_{i=1}^n \delta_i \mathbf{u}_i \mathbf{v}_i^T = \mathbf{U} \boldsymbol{\Delta} \mathbf{V}^T = \mathbf{ZV}^T,
\]
with \(\mathbf{Z}\) the \(n\times n\) matrix with the scores on the \(n\) PCs.
Still problematic because if we use all PCs \(n=p\).
Principal Component Regression
A principal component regression (PCR) consists of
transforming \(p=4000\) dimensional \(X\)-variable to the \(n=30\) dimensional \(Z\)-variable (PC scores). The \(n\) PCs are mutually uncorrelated.
using the \(n\) PC-variables as regressors in a linear regression model
performing feature selection to select the most important regressors (PC).
Feature selection is key, because we don’t want to have as many regressors as there are observations in the data. This would result in zero residual degrees of freedom. (see later)
To keep the exposition general so that we allow for a feature selection to have taken place, I use the notation \(\mathbf{U}_S\) to denote a matrix with left-singular column vectors \(\mathbf{u}_i\), with \(i \in {\cal{S}}\) (\({\cal{S}}\) an index set referring to the PCs to be included in the regression model).
For example, suppose that a feature selection method has resulted in the selection of PCs 1, 3 and 12 for inclusion in the prediction model, then \({\cal{S}}=\{1,3,12\}\) and
\[
\mathbf{U}_S = \begin{pmatrix}
\mathbf{u}_1 & \mathbf{u}_3 & \mathbf{u}_{12}
\end{pmatrix}.
\]
Example model based on first 4 PCs
k <- 30
Uk <- svdX$u[,1:k]
Dk <- diag(svdX$d[1:k])
Zk <- Uk%*%Dk
Y <- toxData %>%
pull(BA)
m4 <- lm(Y~Zk[,1:4])
summary(m4)
#>
#> Call:
#> lm(formula = Y ~ Zk[, 1:4])
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -2.1438 -0.7033 -0.1222 0.7255 2.2997
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 7.961e-16 2.081e-01 0.000 1.0000
#> Zk[, 1:4]1 -5.275e-01 7.725e-02 -6.828 3.72e-07 ***
#> Zk[, 1:4]2 -1.231e-02 8.262e-02 -0.149 0.8828
#> Zk[, 1:4]3 -1.759e-01 8.384e-02 -2.098 0.0461 *
#> Zk[, 1:4]4 -3.491e-02 8.396e-02 -0.416 0.6811
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 1.14 on 25 degrees of freedom
#> Multiple R-squared: 0.672, Adjusted R-squared: 0.6195
#> F-statistic: 12.8 on 4 and 25 DF, p-value: 8.352e-06
Note:
- the intercept is estimated as zero. (Why?) The model could have been fitted as
m4 <- lm(Y~-1+Zk[,1:4])
the PC-predictors are uncorrelated (by construction)
first PC-predictors are not necessarily the most important predictors
\(p\)-values are not very meaningful when prediction is the objective
Methods for feature selection will be discussed later.
Ridge Regression
Penalty
The ridge parameter estimator is defined as the parameter \(\mathbf\beta\) that minimises the penalised least squares criterion
\[
\text{SSE}_\text{pen}=\Vert\mathbf{Y} - \mathbf{X\beta}\Vert_2^2 + \lambda \Vert \boldsymbol{\beta} \Vert_2^2
\]
Note, that that is equivalent to minimizing
\[
\Vert\mathbf{Y} - \mathbf{X\beta}\Vert_2^2 \text{ subject to } \Vert \boldsymbol{\beta}\Vert^2_2\leq s
\]
Note, that \(s\) has a one-to-one correspondence with \(\lambda\)
Graphical interpretation
Solution
The solution is given by
\[
\hat{\boldsymbol{\beta}} = (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \mathbf{X^T Y}.
\]
It can be shown that \((\mathbf{X^TX}+\lambda \mathbf{I})\) is always of rank \(p\) if \(\lambda>0\).
Hence, \((\mathbf{X^TX}+\lambda \mathbf{I})\) is invertible and \(\hat{\boldsymbol{\beta}}\) exists even if \(p>>>n\).
We also find
\[
\text{var}\left[\hat{\mathbf\beta}\right] = (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \mathbf{X}^T\mathbf{X} (\mathbf{X^TX}+\lambda \mathbf{I})^{-1}\sigma^2
\]
However, it can be shown that improved intervals that also account for the bias can be constructed by using:
\[
\text{var}\left[\hat{\mathbf\beta}\right] = (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \sigma^2.
\]
Proof
The criterion to be minimised is
\[
\text{SSE}_\text{pen}=\Vert\mathbf{Y} - \mathbf{X\beta}\Vert_2^2 + \lambda \Vert \boldsymbol{\beta} \Vert_2^2.
\]
First we re-express SSE in matrix notation:
\[
\text{SSE}_\text{pen} = (\mathbf{Y}-\mathbf{X\beta})^T(\mathbf{Y}-\mathbf{X\beta}) + \lambda \boldsymbol{\beta}^T\boldsymbol{\beta}.
\]
The partial derivative w.r.t. \(\boldsymbol{\beta}\) is
\[
\frac{\partial}{\partial \boldsymbol{\beta}}\text{SSE}_\text{pen} = -2\mathbf{X}^T(\mathbf{Y}-\mathbf{X\beta})+2\lambda\boldsymbol{\beta}.
\]
Solving \(\frac{\partial}{\partial \boldsymbol{\beta}}\text{SSE}_\text{pen}=0\) gives
\[
\hat{\boldsymbol{\beta}} = (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \mathbf{X^T Y}.
\]
(assumption: \((\mathbf{X^TX}+\lambda \mathbf{I})\) is of rank \(p\). This is always true if \(\lambda>0\))
Link with SVD
SVD and inverse
Write the SVD of \(\mathbf{X}\) (\(p>n\)) as
\[
\mathbf{X} = \sum_{i=1}^n \delta_i \mathbf{u}_i \mathbf{v}_i^T = \sum_{i=1}^p \delta_i \mathbf{u}_i \mathbf{v}_i^T = \mathbf{U}\boldsymbol{\Delta} \mathbf{V}^T ,
\]
with
\(\delta_{n+1}=\delta_{n+2}= \cdots = \delta_p=0\)
\(\boldsymbol{\Delta}\) a \(p\times p\) diagonal matrix of the \(\delta_1,\ldots, \delta_p\)
\(\mathbf{U}\) an \(n\times p\) matrix and \(\mathbf{V}\) a \(p \times p\) matrix. Note that only the first \(n\) columns of \(\mathbf{U}\) and \(\mathbf{V}\) are informative.
With the SVD of \(\mathbf{X}\) we write
\[
\mathbf{X}^T\mathbf{X} = \mathbf{V}\boldsymbol{\Delta
}^2\mathbf{V}^T.
\]
The inverse of \(\mathbf{X}^T\mathbf{X}\) is then given by
\[
(\mathbf{X}^T\mathbf{X})^{-1} = \mathbf{V}\boldsymbol{\Delta}^{-2}\mathbf{V}^T.
\]
Since \(\boldsymbol{\Delta}\) has \(\delta_{n+1}=\delta_{n+2}= \cdots = \delta_p=0\), it is not invertible.
SVD of penalised matrix \(\mathbf{X^TX}+\lambda \mathbf{I}\)
It can be shown that
\[
\mathbf{X^TX}+\lambda \mathbf{I} = \mathbf{V} (\boldsymbol{\Delta}^2+\lambda \mathbf{I}) \mathbf{V}^T ,
\]
i.e. adding a constant to the diagonal elements does not affect the eigenvectors, and all eigenvalues are increased by this constant.
\(\longrightarrow\) zero eigenvalues become \(\lambda\).
Hence,
\[
(\mathbf{X^TX}+\lambda \mathbf{I})^{-1} = \mathbf{V} (\boldsymbol{\Delta}^2+\lambda \mathbf{I})^{-1} \mathbf{V}^T ,
\]
which can be computed even when some eigenvalues in \(\boldsymbol{\Delta}^2\) are zero.
Note, that for high dimensional data (\(p>>>n\)) many eigenvalues are zero because \(\mathbf{X^TX}\) is a \(p \times p\) matrix and has rank \(n\).
The identity \(\mathbf{X^TX}+\lambda \mathbf{I} = \mathbf{V} (\boldsymbol{\Delta}^2+\lambda \mathbf{I}) \mathbf{V}^T\) is easily checked:
\[
\mathbf{V} (\boldsymbol{\Delta}^2+\lambda \mathbf{I}) \mathbf{V}^T = \mathbf{V}\boldsymbol{\Delta}^2\mathbf{V}^T + \lambda \mathbf{VV}^T = \mathbf{V}\boldsymbol{\Delta}^2\mathbf{V}^T + \lambda \mathbf{I} = \mathbf{X^TX}+\lambda \mathbf{I}.
\]
Properties
The Ridge estimator is biased! The \(\boldsymbol{\beta}\) are shrunken to zero!
\[\begin{eqnarray}
\text{E}[\hat{\boldsymbol{\beta}}] &=& (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \mathbf{X}^T \text{E}[\mathbf{Y}]\\
&=& (\mathbf{X}^T\mathbf{X}+\lambda \mathbf{I})^{-1} \mathbf{X}^T \mathbf{X}\boldsymbol{\beta}\\
\end{eqnarray}\]
Note, that the shrinkage is larger in the direction of the smaller eigenvalues.
\[\begin{eqnarray}
\text{E}[\hat{\boldsymbol{\beta}}]&=&\mathbf{V} (\boldsymbol{\Delta}^2+\lambda \mathbf{I})^{-1} \mathbf{V}^T \mathbf{V} \boldsymbol{\Delta}^2 \mathbf{V}^T\boldsymbol{\beta}\\
&=&\mathbf{V} (\boldsymbol{\Delta}^2+\lambda \mathbf{I})^{-1} \boldsymbol{\Delta}^2 \mathbf{V}^T\boldsymbol{\beta}\\
&=& \mathbf{V}
\left[\begin{array}{ccc}
\frac{\delta_1^2}{\delta_1^2+\lambda}&\ldots&0 \\
&\vdots&\\
0&\ldots&\frac{\delta_r^2}{\delta_r^2+\lambda}
\end{array}\right]
\mathbf{V}^T\boldsymbol{\beta}
\end{eqnarray}\]
the variance of the prediction \(\hat{{Y}}(\mathbf{x})=\mathbf{x}^T\hat\beta\),
\[
\text{var}\left[\hat{{Y}}(\mathbf{x})\mid \mathbf{x}\right] = \mathbf{x}^T(\mathbf{X^TX}+\lambda \mathbf{I})^{-1}\mathbf{x}
\]
is smaller than with the least-squares estimator.
through the bias-variance trade-off it is hoped that better predictions in terms of expected conditional test error can be obtained, for an appropriate choice of \(\lambda\).
Recall the expression of the expected conditional test error
\[\begin{eqnarray}
Err(\mathbf{x}) &=& \text{E}\left[(\hat{Y} - Y^*)^2\mid \mathbf{x}\right]\\
&=&
\text{var}\left[\hat{Y}\mid \mathbf{x}\right] + \text{bias}^2(\mathbf{x})+
\text{var}\left[Y^*\mid \mathbf{x}\right]
\end{eqnarray}\]
where
- \(\hat{Y}=\hat{Y}(\mathbf{x})=\mathbf{x}^T\hat{\boldsymbol{\beta}}\) is the prediction at \(\mathbf{x}\)
- \(Y^*\) is an outcome at predictor \(\mathbf{x}\)
- \(\mu(\mathbf{x}) = \text{E}\left[\hat{Y}\mid \mathbf{x}\right] \text{ and } \mu^*(x)=\text{E}\left[Y^*\mid \mathbf{x}\right]\)
- \(\text{bias}(\mathbf{x})=\mu(\mathbf{x})-\mu^*(\mathbf{x})\)
- \(\text{var}\left[Y^*\mid \mathbf{x}\right]\) the irreducible error that does not depend on the model. It simply originates from observations that randomly fluctuate around the true mean \(\mu^*(x)\).
Toxicogenomics example
library(glmnet)
mRidge <- glmnet(
x = toxData[,-1] %>%
as.matrix,
y = toxData %>%
pull(BA),
alpha = 0) # ridge: alpha = 0
plot(mRidge, xvar="lambda")
The R function uses to refer to the penalty parameter. In this course we use \(\lambda\), because \(\lambda\) is often used as eigenvalues.
The graph shows that with increasing penalty parameter, the parameter estimates are shrunken towards zero. The estimates will only reach zero for \(\lambda \rightarrow \infty\). The stronger the shrinkage, the larger the bias (towards zero) and the smaller the variance of the parameter estimators (and hence also smaller variance of the predictions).
Another (informal) viewpoint is the following. By shrinking the estimates towards zero, the estimates loose some of their ``degrees of freedom’’ so that the parameters become estimable with only \(n<p\) data points. Even with a very small \(\lambda>0\), the parameters regain their estimability. However, note that the variance of the estimator is given by
\[
\text{var}\left[\hat{\mathbf\beta}\right] = (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \sigma^2 = \mathbf{V}(\boldsymbol{\Delta}^2+\lambda\mathbf{I})^{-1}\mathbf{V}^T\sigma^2.
\]
Hence, a small \(\lambda\) will result in large variances of the parameter estimators. The larger \(\lambda\), the smaller the variances become. In the limit, as \(\lambda\rightarrow\infty\), the estimates are converged to zero and show no variability any longer.
Lasso Regression
The Lasso is another example of penalised regression.
The lasso estimator of \(\boldsymbol{\beta}\) is the solution to minimising the penalised SSE
\[
\text{SSE}_\text{pen} = \sum_{i=1}^n (Y_i - \mathbf{x}_i^T\boldsymbol{\beta})^2 + \lambda \sum_{j=1}^p \vert \beta_j\vert.
\]
or, equivalently, minimising
\[
\text{SSE} = \Vert \mathbf{Y} - \mathbf{X\beta}\Vert_2^2 \text{ subject to } \Vert \mathbf\beta\Vert_1 \leq c
\]
with
\(\Vert \mathbf\beta\Vert_1 = \sum\limits_{j=1}^p \vert \beta_j \vert\)
Despite strong similarity between ridge and lasso regression (\(L_2\) versus \(L_1\) norm in penalty term), there is no analytical solution of the lasso parameter estimate of \(\mathbf\beta\).
Fortunately, computational efficient algorithms have been implemented in statistical software
The Lasso estimator of \(\boldsymbol{\beta}\) is biased and generally has a smaller variance then the least-squares estimator.
Hence, the bias-variance trade-off may here also help in finding better predictions with biased estimators.
In contrast to ridge regression, however, the lasso estimator can give at most \(\min(p,n)\) non-zero \(\beta\)-estimates.
Hence, at first sight the lasso is not directly appropriate for high-dimensional settings.
An important advantage of the lasso is that choosing an appropriate value for \(\lambda\) is a kind a model building or feature selection procedure (see further).
Graphical interpretation of Lasso vs ridge
Note that the lasso is a constrained regression problem with
\[
\Vert \mathbf{Y} - \mathbf{X\beta}\Vert_2^2 \text{ subject to } \Vert \mathbf\beta\Vert_1 \leq c
\]
and ridge
\[
\Vert \mathbf{Y} - \mathbf{X\beta}\Vert_2^2 \text{ subject to } \Vert \mathbf\beta\Vert^2_2 \leq c
\]
Note, that
- parameters for the lasso can never switch sign, they are set at zero! Selection!
- ridge regression can lead to parameters that switch sign.
Toxicogenomics example
mLasso <- glmnet(
x = toxData[,-1] %>%
as.matrix,
y = toxData %>%
pull(BA),
alpha = 1)
plot(mLasso, xvar = "lambda")
The graph with the paths of the parameter estimates nicely illustrates the typical behaviour of the lasso estimates as a function of \(\lambda\): when \(\lambda\) increases the estimates are shrunken towards zero.
When an estimate hits zero, it remains exactly equal to zero when \(\gamma\) further increases. A parameter estimate equal to zero, say \(\hat\beta_j=0\), implies that the corresponding predictor \(x_j\) is no longer included in the model (i.e. \(\beta_jx_j=0\)).
The model fit is known as a sparse model fit (many zeroes). Hence, choosing a appropriate value for \(\gamma\) is like choosing the important predictors in the model (feature selection).
Evaluation of Prediction Models
Predictions are calculated with the fitted model
\[
\hat{Y}(\mathbf{x}) = \hat{m}(\mathbf{x})=\mathbf{x}^T\hat{\beta}
\]
when focussing on prediction, we want the prediction error to be as small as possible.
The prediction error for a prediction at covariate pattern \(\mathbf{x}\) is given by
\[
\hat{Y}(\mathbf{x}) - Y^*,
\]
where
Prediction is typically used to predict an outcome before it is observed.
- Hence, the outcome \(Y^*\) is not observed yet, and
- the prediction error cannot be computed.
Recall that the prediction model \(\hat{Y}(\mathbf{x})\) is estimated by using data in the training data set \((\mathbf{X},\mathbf{Y})\), and
that the outcome \(Y^*\) is an outcome at \(\mathbf{x}\) which is assumed to be independent of the training data.
Goal is to use prediction model for predicting a future observation (\(Y^*\)), i.e. an observation that still has to be realised/observed (otherwise prediction seems rather useless).
Hence, \(Y^*\) can never be part of the training data set.
Here we provide definitions and we show how the prediction performance of a prediction model can be evaluated from data.
Let \({\cal{T}}=(\mathbf{Y},\mathbf{X})\) denote the training data, from which the prediction model \(\hat{Y}(\cdot)\) is build. This building process typically involves feature selection and parameter estimation.
We will use a more general notation for the prediction model: \(\hat{m}(\mathbf{x})=\hat{Y}(\mathbf{x})\).
Test or Generalisation Error
The test or generalisation error for prediction model \(\hat{m}(\cdot)\) is given by
\[
\text{Err}_{\cal{T}} = \text{E}_{Y^*,X^*}\left[(\hat{m}(\mathbf{X}^*) - Y^*)^2\mid {\cal{T}}\right]
\]
where \((Y^*,X^*)\) is independent of the training data.
- Note that the test error is conditional on the training data \({\cal{T}}\).
- Hence, the test error evaluates the performance of the single model build from the observed training data.
- This is the ultimate target of the model assessment, because it is exactly this prediction model that will be used in practice and applied to future predictors \(\mathbf{X}^*\) to predict \(Y^*\).
- The test error is defined as an average over all such future observations \((Y^*,\mathbf{X}^*)\).
Conditional test error
Sometimes the conditional test error is used:
The conditional test error in \(\mathbf{x}\) for prediction model \(\hat{m}(\mathbf{x})\) is given by
\[
\text{Err}_{\cal{T}}(\mathbf{x}) = \text{E}_{Y^*}\left[(\hat{m}(\mathbf{x}) - Y^*)^2\mid {\cal{T}}, \mathbf{x}\right]
\]
where \(Y^*\) is an outcome at predictor \(\mathbf{x}\), independent of the training data.
Hence,
\[
\text{Err}_{\cal{T}} = \text{E}_{X^*}\left[\text{Err}_{\cal{T}}(\mathbf{X}^*)\right].
\]
A closely related error is the insample error.
Insample Error
The insample error for prediction model \(\hat{m}(\mathbf{x})\) is given by
\[
\text{Err}_{\text{in} \cal{T}} = \frac{1}{n}\sum_{i=1}^n \text{Err}_{\cal{T}}(\mathbf{x}_i),
\]
i.e. the insample error is the sample average of the conditional test errors evaluated in the \(n\) training dataset predictors \(\mathbf{x}_i\).
Since \(\text{Err}_{\cal{T}}\) is an average over all \(\mathbf{X}\), even over those predictors not observed in the training dataset, it is sometimes referred to as the outsample error.
Estimation of the insample error
We start with introducing the training error rate, which is closely related to the MSE in linear models.
Training error
The training error is given by
\[
\overline{\text{err}} = \frac{1}{n}\sum_{i=1}^n (Y_i - \hat{m}(\mathbf{x}_i))^2 ,
\]
where the \((Y_i,\mathbf{x}_i)\) from the training dataset which is also used for the calculation of \(\hat{m}\).
The training error is an overly optimistic estimate of the test error \(\text{Err}_{\cal{T}}\).
The training error will never increases when the model becomes more complex. \(\longrightarrow\) cannot be used directly as a model selection criterion.
Indeed, model parameters are often estimated by minimising the training error (cfr. SSE).
- Hence the fitted model adapts to the training data, and
- training error will be an overly optimistic estimate of the test error \(\text{Err}_{\cal{T}}\).
It can be shown that the training error is related to the insample test error via
\[
\text{E}_\mathbf{Y}
\left[\text{Err}_{\text{in}{\cal{T}}}\right] = \text{E}_\mathbf{Y}\left[\overline{\text{err}}\right] + \frac{2}{n}\sum_{i=1}^n \text{cov}_\mathbf{Y}\left[\hat{m}(\mathbf{x}_i),Y_i\right],
\]
Note, that for linear models
\[ \hat{m}(\mathbf{x}_i) = \mathbf{X}\hat{\boldsymbol{\beta}}= \mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y} = \mathbf{HY}
\]
with
- \(\mathbf{H}\) the hat matrix and
- all \(Y_i\) are assumed to be independently distributed \(N(\mathbf{X}\boldsymbol{\beta},\sigma^2)\)
Hence, for linear models with independent observations
\[\begin{eqnarray}
\text{cov}_\mathbf{Y}\left[\hat{m}(\mathbf{x}_i),Y_i)\right] &=&
\text{cov}_\mathbf{Y}\left[\mathbf{H}_{i}^T\mathbf{Y},Y_i)\right]\\
&=& \text{cov}_\mathbf{Y}\left[h_{ii} Y_i,Y_i\right]\\
&=& h_{ii} \text{cov}_\mathbf{Y}\left[Y_i,Y_i\right]\\
&=& h_{ii} \sigma^2\\
\end{eqnarray}\]
And we can thus estimate the insample error by Mallow’s \(C_p\)
\[\begin{eqnarray}
C_p &=& \overline{\text{err}} + \frac{2\sigma^2}{n}\text{tr}(\mathbf{H})\\
&=& \overline{\text{err}} + \frac{2\sigma^2p}{n}
\end{eqnarray}\]
with \(p\) the number of predictors.
- Mallow’s \(C_p\) is often used for model selection.
- Note, that we can also consider it as a kind of penalized least squares:
\[
n \times C_p = \Vert \mathbf{Y} - \mathbf{X}\boldsymbol{\beta}\Vert_2^2 + 2\sigma^2 \Vert \boldsymbol{\beta} \Vert_0
\]
with \(L_0\) norm \(\Vert \boldsymbol{\beta} \Vert_0 = \sum_{j=1}^p \beta_p^0 = p\).
Expected test error
The test or generalisation error was defined conditionally on the training data. By averaging over the distribution of training datasets, the expected test error arises.
\[\begin{eqnarray*}
\text{E}_{\cal{T}}\left[\text{Err}_{{\cal{T}}}\right]
&=& \text{E}_{\cal{T}}\left[\text{E}_{Y^*,X^*}\left[(\hat{m}(\mathbf{X}^*) - Y^*)^2\mid {\cal{T}}\right]\right] \\
&=& \text{E}_{Y^*,X^*,{\cal{T}}}\left[(\hat{m}(\mathbf{X}^*) - Y^*)^2\right].
\end{eqnarray*}\]
The expected test error may not be of direct interest when the goal is to assess the prediction performance of a single prediction model \(\hat{m}(\cdot)\).
The expected test error averages the test errors of all models that can be build from all training datasets, and hence this may be less relevant when the interest is in evaluating one particular model that resulted from a single observed training dataset.
Also note that building a prediction model involves both parameter estimation and feature selection.
Hence the expected test error also evaluates the feature selection procedure (on average).
If the expected test error is small, it is an indication that the model building process gives good predictions for future observations \((Y^*,\mathbf{X}^*)\) on average.
Estimating the Expected test error
The expected test error may be estimated by cross validation (CV).
Leave one out cross validation (LOOCV)}
The LOOCV estimator of the expected test error (or expected outsample error) is given by
\[
\text{CV} = \frac{1}{n} \sum_{i=1}^n \left(Y_i - \hat{m}^{-i}(\mathbf{x}_i)\right)^2 ,
\]
where
- the \((Y_i,\mathbf{x}_i)\) form the training dataset
- \(\hat{m}^{-i}\) is the fitted model based on all training data, except observation \(i\)
- \(\hat{m}^{-i}(\mathbf{x}_i)\) is the prediction at \(\mathbf{x}_i\), which is the observation left out the training data before building model \(m\).
Some rationale as to why LOOCV offers a good estimator of the outsample error:
the prediction error \(Y^*-\hat{m}(\mathbf{x})\) is mimicked by not using one of the training outcomes \(Y_i\) for the estimation of the model so that this \(Y_i\) plays the role of \(Y^*\), and, consequently, the fitted model \(\hat{m}^{-i}\) is independent of \(Y_i\)
the sum in \(CV\) is over all \(\mathbf{x}_i\) in the training dataset, but each term \(\mathbf{x}_i\) was left out once for the calculation of \(\hat{m}^{-i}\). Hence, \(\hat{m}^{-i}(\mathbf{x}_i)\) mimics an outsample prediction.
the sum in CV is over \(n\) different training datasets (each one with a different observation removed), and hence CV is an estimator of the expected test error.
For linear models the LOOCV can be readily obtained from the fitted model: i.e.
\[\text{CV} = \frac{1}{n}\sum\limits_{i=1}^n \frac{e_i^2}{(1-h_{ii})^2}\]
with \(e_i\) the residuals from the model that is fitted based on all training data.
An alternative to LOOCV is the \(k\)-fold cross validation procedure. It also gives an estimate of the expected outsample error.
\(k\)-fold cross validation
Randomly divide the training dataset into \(k\) approximately equal subsets . Let \(S_j\) denote the index set of the \(j\)th subset (referred to as a fold). Let \(n_j\) denote the number of observations in fold \(j\).
The \(k\)-fold cross validation estimator of the expected outsample error is given by
\[
\text{CV}_k = \frac{1}{k}\sum_{j=1}^k \frac{1}{n_j} \sum_{i\in S_j} \left(Y_i - \hat{m}^{-S_j}(\mathbf{x}_i)\right)^2
\]
where \(\hat{m}^{-S_j}\) is the model fitted using all training data, except observations in fold \(j\) (i.e. observations \(i \in S_j\)).
The cross validation estimators of the expected outsample error are nearly unbiased. One argument that helps to understand where the bias comes from is the fact that e.g. in de LOOCV estimator the model is fit on only \(n-1\) observations, whereas we are aiming at estimating the outsample error of a model fit on all \(n\) training observations. Fortunately, the bias is often small and is in practice hardly a concern.
\(k\)-fold CV is computationally more complex.
Since CV and CV\(_k\) are estimators, they also show sampling variability. Standard errors of the CV or CV\(_k\) can be computed. We don’t show the details, but in the example this is illustrated.
Bias Variance trade-off
For the expected conditional test error in \(\mathbf{x}\), it holds that
\[\begin{eqnarray*}
\text{E}_{\cal{T}}\left[\text{Err}_{\cal{T}}(\mathbf{x})\right]
&=& \text{E}_{Y^*,{\cal{T}}}\left[(\hat{m}(\mathbf{x})-Y^*)^2 \mid \mathbf{x}\right] \\
&=& \text{var}_{\mathbf{Y}}\left[\hat{Y}(\mathbf{x})\mid \mathbf{x}\right] +(\mu(\mathbf{x})-\mu^*(\mathbf{x}))^2+\text{var}_{Y^*}\left[Y^*\mid \mathbf{x}\right]
\end{eqnarray*}\]
where \(\mu(\mathbf{x}) = \text{E}_{\mathbf{Y}}\left[\hat{Y}(\mathbf{x})\mid \mathbf{x}\right] \text{ and } \mu^*(\mathbf{x})=\text{E}_{Y^*}\left[Y^*\mid \mathbf{x}\right]\).
bias: \(\text{bias}(\mathbf{x})=\mu(\mathbf{x})-\mu^*(\mathbf{x})\)
\(\text{var}_{Y^*}\left[Y^*\mid \mathbf{x}\right]\) does not depend on the model, and is referred to as the irreducible variance.
The importance of the bias-variance trade-off can be seen from a model selection perspective. When we agree that a good model is a model that has a small expected conditional test error at some point \(\mathbf{x}\), then the bias-variance trade-off shows us that a model may be biased as long as it has a small variance to compensate for the bias. It often happens that a biased model has a substantial smaller variance. When these two are combined, a small expected test error may occur.
Also note that the model \(m\) which forms the basis of the prediction model \(\hat{m}(\mathbf{x})\) does NOT need to satisfy \(m(\mathbf{x})=\mu(\mathbf{x})\) or \(m(\mathbf{x})=\mu^*(\mathbf{x})\). The model \(m\) is known by the data-analyst (its the basis of the prediction model), whereas \(\mu(\mathbf{x})\) and \(\mu^*(\mathbf{x})\) are generally unknown to the data-analyst. We only hope that \(m\) serves well as a prediction model.
In practice
We use cross validation to estimate the lambda penalty for penalised regression:
- Ridge Regression
- Lasso
- Build models, e.g. select the number of PCs for PCA regression
- Splines
Toxicogenomics example
Lasso
set.seed(15)
library(glmnet)
mCvLasso <- cv.glmnet(
x = toxData[,-1] %>%
as.matrix,
y = toxData %>%
pull(BA),
alpha = 1) # lasso alpha=1
plot(mCvLasso)
Default CV procedure in is \(k=10\)-fold CV.
The Graphs shows
- 10-fold CV estimates of the extra-sample error as a function of the lasso penalty parameter \(\lambda\).
- estimate plus and minus once the estimated standard error of the CV estimate (grey bars)
- On top the number of non-zero regression parameter estimates are shown.
Two vertical reference lines are added to the graph. They correspond to
- the \(\log(\lambda)\) that gives the smallest CV estimate of the extra-sample error, and
- the largest \(\log(\lambda)\) that gives a CV estimate of the extra-sample error that is within one standard error from the smallest error estimate.
- The latter choice of \(\lambda\) has no firm theoretical basis, except that it somehow accounts for the imprecision of the error estimate. One could loosely say that this \(\gamma\) corresponds to the smallest model (i.e. least number of predictors) that gives an error that is within margin of error of the error of the best model.
mLassoOpt <- glmnet(
x = toxData[,-1] %>%
as.matrix,
y = toxData %>%
pull(BA),
alpha = 1,
lambda = mCvLasso$lambda.min)
summary(coef(mLassoOpt))
With the optimal \(\lambda\) (smallest error estimate) the output shows the 9 non-zero estimated regression coefficients (sparse solution).
mLasso1se <- glmnet(
x = toxData[,-1] %>%
as.matrix,
y= toxData %>%
pull(BA),
alpha = 1,
lambda = mCvLasso$lambda.1se)
mLasso1se %>%
coef %>%
summary
This shows the solution for the largest \(\lambda\) within one standard error of the optimal model. Now only 3 non-zero estimates result.
Ridge
mCvRidge <- cv.glmnet(
x = toxData[,-1] %>%
as.matrix,
y = toxData %>%
pull(BA),
alpha = 0) # ridge alpha=0
plot(mCvRidge)
- Ridge does not seem to have optimal solution.
- 10-fold CV is also larger than for lasso.
PCA regression
set.seed(1264)
library(DAAG)
tox <- data.frame(
Y = toxData %>%
pull(BA),
PC = Zk)
PC.seq <- 1:25
Err <- numeric(25)
mCvPca <- cv.lm(
Y~PC.1,
data = tox,
m = 5,
printit = FALSE)
Err[1]<-attr(mCvPca,"ms")
for(i in 2:25) {
mCvPca <- cv.lm(
as.formula(
paste("Y ~ PC.1 + ",
paste("PC.", 2:i, collapse = "+", sep=""),
sep=""
)
),
data = tox,
m = 5,
printit = FALSE)
Err[i]<-attr(mCvPca,"ms")
}
Here we illustrate principal component regression.
The most important PCs are selected in a forward model selection procedure.
Within the model selection procedure the models are evaluated with 5-fold CV estimates of the outsample error.
It is important to realise that a forward model selection procedure will not necessarily result in the best prediction model, particularly because the order of the PCs is generally not related to the importance of the PCs for predicting the outcome.
A supervised PC would be better.
pPCreg <- data.frame(PC.seq, Err) %>%
ggplot(aes(x = PC.seq, y = Err)) +
geom_line() +
geom_point() +
geom_hline(
yintercept = c(
mCvLasso$cvm[mCvLasso$lambda==mCvLasso$lambda.min],
mCvLasso$cvm[mCvLasso$lambda==mCvLasso$lambda.1se]),
col = "red") +
xlim(1,26)
grid.arrange(
pPCreg,
pPCreg + ylim(0,5),
ncol=2)
The graph shows the CV estimate of the outsample error as a function of the number of sparse PCs included in the model.
A very small error is obtained with the model with only the first PC. The best model with 3 PCs.
The two vertical reference lines correspond to the error estimates obtained with lasso (optimal \(\lambda\) and largest \(\lambda\) within one standard error).
Thus although there was a priori no guarantee that the first PCs are the most predictive, it seems to be the case here (we were lucky!).
Moreover, the first PC resulted in a small outsample error.
Note that the graph does not indicate the variability of the error estimates (no error bars).
Also note that the graph clearly illustrates the effect of overfitting: including too many PCs causes a large outsample error.
Lidar Example: splines
- We use the mgcv package to fit the spline model to the lidar data.
- A better basis is used than the truncated spline basis
- Thin plate splines are also linear smoothers, i.e.
\(\hat{Y} = \hat{m}(\mathbf{X}) = \mathbf{SY}\)
- So their variance can be easily calculated.
- The ridge/smoothness penalty is chosen by generalized cross validation.
library(mgcv)
gamfit <- gam(logratio ~ s(range), data = lidar)
gamfit$sp
#> s(range)
#> 0.006114634
pLidar +
geom_line(aes(x = lidar$range, y = gamfit$fitted), lwd = 2)
More general error definitions
So far we only looked at continuous outcomes \(Y\) and errors defined in terms of the squared loss \((\hat{m}(\mathbf{x})-Y^*)^2\).
More generally, a loss function measures an discrepancy between the prediction \(\hat{m}(\mathbf{x})\) and an independent outcome \(Y^*\) that corresponds to \(\mathbf{x}\).
Some examples for continuous \(Y\):
\[\begin{eqnarray*}
L(Y^*,\hat{m}(\mathbf{x}))
&=& (\hat{m}(\mathbf{x})-Y^*)^2 \;\;\text{(squared error)} \\
L(Y^*,\hat{m}(\mathbf{x}))
&=& \vert\hat{m}(\mathbf{x})-Y^*\vert \;\;\text{(absolute error)} \\
L(Y^*,\hat{m}(\mathbf{x}))
&=& 2 \int_{\cal{Y}} f_y(y) \log\frac{f_y(y)}{f_{\hat{m}}(y)} dy \;\;\text{(deviance)}.
\end{eqnarray*}\]
In the expression of the deviance
- \(f_y\) denotes the density function of a distribution with mean set to \(y\) (cfr. perfect fit), and
- \(f_{\hat{m}}\) is the density function of the same distribution but with mean set to the predicted outcome \(\hat{m}(\mathbf{x})\).
With a given loss function, the errors are defined as follows:
- Test or generalisation or outsample error
\[
\text{Err}_{\cal{T}} = \text{E}_{Y^*,X^*}\left[L(Y^*,\hat{m}(\mathbf{X}^*))\right]
\]
When an exponential family distribution is assumed for the outcome distribution, and when the deviance loss is used, the insample error can be estimated by means of the AIC and BIC.
Training and test sets
Sometimes, when a large (training) dataset is available, one may decide the split the dataset randomly in a
training dataset:
data are used for model fitting and for model building or feature selection (this may require e.g. cross validation)
test dataset:
this data are used to evaluate the final model (result of model building). An unbiased estimate of the outsample error (i.e. test or generalisation error) based on this test data is
\[
\frac{1}{m} \sum_{i=1}^m \left(\hat{m}(\mathbf{x}_i)-Y_i\right)^2,
\]
where
\((Y_1,\mathbf{x}_1), \ldots, (Y_m,\mathbf{x}_m)\) denote the \(m\) observations in the test dataset
\(\hat{m}\) is estimated from using the training data (this may also be the result from model building, using only the training data).
Note that the training dataset is used for model building or feature selection. This also requires the evaluation of models. For these evaluations the methods from the previous slides can be used (e.g. cross validation, \(k\)-fold CV, Mallow’s \(C_p\)). The test dataset is only used for the evaluation of the final model (estimated and build from using only the training data). The estimate of the outsample error based on the test dataset is the best possible estimate in the sense that it is unbiased. The observations used for this estimation are independent of the observations in the training data.
However, if the number of data points in the test dataset (\(m\)) is small, the estimate of the outsample error may show large variance and hence is not reliable.
Logistic Regression Analysis for High Dimensional Data
Breast Cancer Example
Schmidt et al., 2008, Cancer Research, 68, 5405-5413
Gene expression patterns in \(n=200\) breast tumors were investigated (\(p=22283\) genes)
After surgery the tumors were graded by a pathologist (stage 1,2,3)
Here the objective is to predict stage 3 from the gene expression data (prediction of binary outcome)
If the prediction model works well, it can be used to predict the stage from a biopsy sample.
Data
#BiocManager::install("genefu")
#BiocManager::install("breastCancerMAINZ")
library(genefu)
library(breastCancerMAINZ)
data(mainz)
X <- t(exprs(mainz)) # gene expressions
n <- nrow(X)
H <- diag(n)-1/n*matrix(1,ncol=n,nrow=n)
X <- H%*%X
Y <- ifelse(pData(mainz)$grade==3,1,0)
table(Y)
#> Y
#> 0 1
#> 165 35
svdX <- svd(X)
k <- 2
Zk <- svdX$u[,1:k] %*% diag(svdX$d[1:k])
colnames(Zk) <- paste0("Z",1:k)
Zk %>%
as.data.frame %>%
mutate(grade = Y %>% as.factor) %>%
ggplot(aes(x= Z1, y = Z2, color = grade)) +
geom_point(size = 3)
Logistic regression models
Binary outcomes are often analysed with logistic regression models.
Let \(Y\) denote the binary (1/0, case/control, positive/negative) outcome, and \(\mathbf{x}\) the \(p\)-dimensional predictor.
Logistic regression assumes
\[
Y \mid \mathbf{x} \sim \text{Bernoulli}(\pi(\mathbf{x}))
\]
with \(\pi(\mathbf{x}) = \text{P}\left[Y=1\mid \mathbf{x}\right]\) and
\[
\ln \frac{\pi(\mathbf{x})}{1-\pi(\mathbf{x})}=\beta_0 + \boldsymbol{\beta}^T\mathbf{x}.
\]
The parameters are typically estimated by maximising the log-likelihood, which is denoted by \(l(\mathbf{ \beta})\), i.e.
\[
\hat{\boldsymbol{\beta}} = \text{ArgMax}_\beta l(\boldsymbol{\beta}).
\]
Penalized maximum likelihood
Penalised estimation methods (e.g. lasso and ridge) can als be applied to maximum likelihood, resulting in the penalised maximum likelihood estimate.
Lasso:
\[
\hat{\boldsymbol{\beta}} = \text{ArgMax}_\beta l(\boldsymbol{\beta}) -\lambda \Vert \boldsymbol{\beta}\Vert_1.
\]
Ridge:
\[
\hat{\boldsymbol{\beta}} = \text{ArgMax}_\beta l(\boldsymbol{\beta}) -\lambda \Vert \boldsymbol{\beta}\Vert_2^2.
\]
Once the parameters are estimated, the model may be used to compute
\[
\hat{\pi}(\mathbf{x}) = \hat{\text{P}}\left[Y=1\mid \mathbf{x}\right].
\]
With these estimated probabilities the prediction rule becomes
\[\begin{eqnarray*}
\hat{\pi}(\mathbf{x}) &\leq c& \text{predict } Y=0 \\
\hat{\pi}(\mathbf{x}) &>c & \text{predict } Y=1
\end{eqnarray*}\]
with \(0<c<1\) a threshold that either is fixed (e.g. \(c=1/2\)), depends on prior probabilities, or is empirically determined by optimising e.g. the Area Under the ROC Curve (AUC) or by finding a good compromise between sensitivity and specificity.
Note that logistic regression directly models the Posterior probability that an observation belongs to class \(Y=1\), given the predictor \(\mathbf{x}\).
Model evaluation
Common model evaluation criteria for binary prediction models are:
sensitivity = true positive rate (TPR)
specificity = true negative rate (TNR)
misclassification error
area under the ROC curve (AUC)
These criteria can again be estimated via cross validation or via splitting of the data into training and test/validation data.
Sensitivity of a model \(\pi\) with threshold \(c\)
Sensitivity is the probability to correctly predict a positive outcome:
\[
\text{sens}(\pi,c)=\text{P}_{X^*}\left[\hat\pi(\mathbf{X}^*)>c \mid Y^*=1 \mid {\cal{T}}\right].
\]
It is also known as the true positive rate (TPR).
Specificity of a model \(\pi\) with threshold \(c\)
Specificity is the probability to correctly predict a negative outcome:
\[
\text{spec}(\pi,c)=\text{P}_{X^*}\left[\hat\pi(\mathbf{X}^*)\leq c \mid Y^*=0 \mid {\cal{T}}\right].
\]
It is also known as the true negative rate (TNR).
Misclassification error of a model \(\pi\) with threshold \(c\)
The misclassification error is the probability to incorrectly predict an outcome:
\[\begin{eqnarray*}
\text{mce}(\pi,c) &=&\text{P}_{X^*,Y^*}\left[\hat\pi(\mathbf{X})\leq c \text{ and } Y^*=1 \mid {\cal{T}}\right] \\
& & + \text{P}_{X^*,Y^*}\left[\hat\pi(\mathbf{X})> c \text{ and } Y^*=0 \mid {\cal{T}}\right].
\end{eqnarray*}\]
Note that in the definitions of sensitivity, specificity and the misclassification error, the probabilities refer to the distribution of the \((\mathbf{X}^*,Y^*)\), which is independent of the training data, conditional on the training data. This is in line with the test or generalisation error. The misclassification error is actually the test error when a 0/1 loss function is used. Just as before, the sensitivity, specificity and the misclassification error can also be averaged over the distribution of the training data set, which is in line with the expected test error which has been discussed earlier.
ROC curve of a model \(\pi\)
The Receiver Operating Characteristic (ROC) curve for model \(\pi\) is given by the function
\[
\text{ROC}: [0,1] \rightarrow [0,1]\times [0,1]: c \mapsto (1-\text{spec}(\pi,c), \text{sens}(\pi,c)).
\]
For when \(c\) moves from 1 to 0, the ROC function defines a curve in the plane \([0,1]\times [0,1]\), moving from \((0,0)\) for \(c=1\) to \((1,1)\) for \(c=0\).
The horizontal axis of the ROC curve shows 1-specificity. This is also known as the False Positive Rate (FPR).
Area under the curve (AUC) of a model \(\pi\)
The area under the curve (AUC) for model \(\pi\) is area under the ROC curve and is given by
\[
\int_0^1 \text{ROC}(c) dc.
\]
Some notes about the AUC:
AUC=0.5 results when the ROC curve is the diagonal. This corresponds to flipping a coin, i.e. a complete random prediction.
AUC=1 results from the perfect ROC curve, which is the ROC curve through the points \((0,0)\), \((0,1)\) and \((1,1)\). This ROC curve includes a threshold \(c\) such that sensitivity and specificity are equal to one.
Breast cancer example
Data
library(glmnet)
#BiocManager::install("genefu")
#BiocManager::install("breastCancerMAINZ")
library(genefu)
library(breastCancerMAINZ)
data(mainz)
X <- t(exprs(mainz)) # gene expressions
n <- nrow(X)
H <- diag(n)-1/n*matrix(1,ncol=n,nrow=n)
X <- H%*%X
Y <- ifelse(pData(mainz)$grade==3,1,0)
table(Y)
#> Y
#> 0 1
#> 165 35
From the table of the outcomes in Y we read that
- 35 tumors were graded as stage 3 and
- 165 tumors were graded as stage 1 or 2.
In this the stage 3 tumors are referred to as cases or postives and the stage 1 and 2 tumors as controls or negatives.
Training and test dataset
The use of the lasso logistic regression for the prediction of stage 3 breast cancer is illustrated here by
randomly splitting the dataset into a training dataset (\(80\%\) of data = 160 tumors) and a test dataset (40 tumors)
using the training data to select a good \(\lambda\) value in the lasso logistic regression model (through 10-fold CV)
evaluating the final model by means of the test dataset (ROC Curve, AUC).
## Used to provide same results as in previous R version
RNGkind(sample.kind = "Rounding")
set.seed(6977326)
####
n <- nrow(X)
nTrain <- round(0.8*n)
nTrain
#> [1] 160
indTrain <- sample(1:n,nTrain)
XTrain <- X[indTrain,]
YTrain <- Y[indTrain]
XTest <- X[-indTrain,]
YTest <- Y[-indTrain]
table(YTest)
#> YTest
#> 0 1
#> 32 8
Note that the randomly selected test data has 20% cases of stage 3 tumors.
This is a bit higher than the 17.5% in the complete data.
One could also perform the random splitting among the positives and the negatives separately (stratified splitting).
Model fitting based on training data
mLasso <- glmnet(
x = XTrain,
y = YTrain,
alpha = 1,
family="binomial") # lasso: alpha = 1
plot(mLasso, xvar = "lambda", xlim = c(-6,-1.5))
mCvLasso <- cv.glmnet(
x = XTrain,
y = YTrain,
alpha = 1,
type.measure = "class",
family = "binomial") # lasso alpha = 1
plot(mCvLasso)
#>
#> Call: cv.glmnet(x = XTrain, y = YTrain, type.measure = "class", alpha = 1, family = "binomial")
#>
#> Measure: Misclassification Error
#>
#> Lambda Index Measure SE Nonzero
#> min 0.1044 14 0.1437 0.03366 18
#> 1se 0.1911 1 0.1688 0.03492 0
The total misclassification error is used here to select a good value for \(\lambda\).
# BiocManager::install("plotROC")
library(plotROC)
dfLassoOpt <- data.frame(
pi = predict(mCvLasso,
newx = XTest,
s = mCvLasso$lambda.min,
type = "response") %>% c(.),
known.truth = YTest)
roc <-
dfLassoOpt %>%
ggplot(aes(d = known.truth, m = pi)) +
geom_roc(n.cuts = 0) +
xlab("1-specificity (FPR)") +
ylab("sensitivity (TPR)")
roc
The ROC curve is shown for the model based on \(\lambda\) with the smallest misclassification error. The model has an AUC of 0.83.
Based on this ROC curve an appropriate threshold \(c\) can be chosen. For example, from the ROC curve we see that it is possible to attain a specificity and a sensitivity of 75%.
The sensitivities and specificities in the ROC curve are unbiased (independent test dataset) for the prediction model build from the training data. The estimates of sensitivity and specificity, however, are based on only 40 observations.
mLambdaOpt <- glmnet(x = XTrain,
y = YTrain,
alpha = 1,
lambda = mCvLasso$lambda.min,
family="binomial")
qplot(
summary(coef(mLambdaOpt))[-1,1],
summary(coef(mLambdaOpt))[-1,3]) +
xlab("gene ID") +
ylab("beta-hat") +
geom_hline(yintercept = 0, color = "red")
- The model with the optimal \(\lambda\) has only 19 non-zero parameter estimates.
- Thus only 19 genes are involved in the prediction model.
- These 19 parameter estimates are plotting in the graph.
A listing of the model output would show the names of the genes.
dfLasso1se <- data.frame(
pi = predict(mCvLasso,
newx = XTest,
s = mCvLasso$lambda.1se,
type = "response") %>% c(.),
known.truth = YTest)
roc <-
rbind(
dfLassoOpt %>%
mutate(method = "min"),
dfLasso1se %>%
mutate(method = "1se")
) %>%
ggplot(aes(d = known.truth, m = pi, color = method)) +
geom_roc(n.cuts = 0) +
xlab("1-specificity (FPR)") +
ylab("sensitivity (TPR)")
roc
When using the \(\lambda\) of the optimal model up to 1 standard deviation, a diagonal ROC curve is obtained and hence AUC is \(0.5\).
This prediction model is thus equivalent to flipping a coin for making the prediction.
The reason is that with this choice of \(\lambda\) (strong penalisation) almost all predictors are removed from the model.
Therefore, do never blindly choose for the ``optimal’’ \(\lambda\) as defined here, but assess the performance of the model first.
mLambda1se <- glmnet(x = XTrain,
y = YTrain,
alpha = 1,
lambda = mCvLasso$lambda.1se,
family="binomial")
mLambda1se %>%
coef %>%
summary
The Elastic Net
The lasso and ridge regression have positive and negative properties.
Lasso
positive: sparse solution
negative: at most \(\min(n,p)\) predictors can be selected
negative: tend to select one predictor among a group of highly correlated predictors
Ridge
- negative: no sparse solution
- positive: more than \(\min(n,p)\) predictors can be selected
A compromise between lasso and ridge: the elastic net:
\[
\hat{\boldsymbol{\beta}} = \text{ArgMax}_\beta l(\boldsymbol{\beta}) -\gamma_1 \Vert \boldsymbol\beta\Vert_1 -\gamma_2 \Vert \boldsymbol\beta\Vert_2^2.
\]
The elastic gives a sparse solution with potentially more than \(\min(n,p)\) predictors.
The glmnet
R function uses the following parameterisation,
\[
\hat{\boldsymbol{\beta}} = \text{ArgMax}_\beta l(\boldsymbol{\beta}) -\lambda\alpha \Vert \boldsymbol\beta\Vert_1 -\lambda(1-\alpha) \Vert \boldsymbol\beta\Vert_2^2.
\]
\(\alpha\) parameter gives weight to \(L_1\) penalty term (hence \(\alpha=1\) gives the lasso, and \(\alpha=0\) gives ridge).
a \(\lambda\) parameter to give weight to the penalisation
Note that the combination of \(\lambda\) and \(\alpha\) gives the same flexibility as the combination of the parameters \(\lambda_1\) and \(\lambda_2\).
Breast cancer example
mElastic <- glmnet(
x = XTrain,
y = YTrain,
alpha = 0.5,
family="binomial") # elastic net
plot(mElastic, xvar = "lambda",xlim=c(-5.5,-1))
mCvElastic <- cv.glmnet(x = XTrain,
y = YTrain,
alpha = 0.5,
family = "binomial",
type.measure = "class") # elastic net
plot(mCvElastic)
#>
#> Call: cv.glmnet(x = XTrain, y = YTrain, type.measure = "class", alpha = 0.5, family = "binomial")
#>
#> Measure: Misclassification Error
#>
#> Lambda Index Measure SE Nonzero
#> min 0.01859 66 0.1313 0.02708 148
#> 1se 0.21876 13 0.1562 0.03391 26
dfElast <- data.frame(
pi = predict(mElastic,
newx = XTest,
s = mCvElastic$lambda.min,
type = "response") %>% c(.),
known.truth = YTest)
roc <- rbind(
dfLassoOpt %>% mutate(method = "lasso"),
dfElast %>% mutate(method = "elast. net")) %>%
ggplot(aes(d = known.truth, m = pi, color = method)) +
geom_roc(n.cuts = 0) +
xlab("1-specificity (FPR)") +
ylab("sensitivity (TPR)")
roc
- More parameters are used than for the lasso, but the performance does not improve.
mElasticOpt <- glmnet(x = XTrain,
y = YTrain,
alpha = 0.5,
lambda = mCvElastic$lambda.min,
family="binomial")
qplot(
summary(coef(mElasticOpt))[-1,1],
summary(coef(mElasticOpt))[-1,3]) +
xlab("gene ID") +
ylab("beta-hat") +
geom_hline(yintercept = 0, color = "red")
LS0tCnRpdGxlOiAiMy4gUHJlZGljdGlvbiB3aXRoIEhpZ2ggRGltZW5zaW9uYWwgUHJlZGljdG9ycyIKYXV0aG9yOiAiTGlldmVuIENsZW1lbnQiCmRhdGU6ICJzdGF0T21pY3MsIEdoZW50IFVuaXZlcnNpdHkgKGh0dHBzOi8vc3RhdG9taWNzLmdpdGh1Yi5pbykiCm91dHB1dDoKICBib29rZG93bjo6cGRmX2RvY3VtZW50MjoKICAgIHRvYzogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBsYXRleF9lbmdpbmU6IHhlbGF0ZXgKYWx3YXlzX2FsbG93X2h0bWw6IHRydWUKLS0tCgpgYGB7ciwgY2hpbGQ9Il9zZXR1cC5SbWQifQpgYGAKCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShncmlkRXh0cmEpCmBgYAoKIyBJbnRyb2R1Y3Rpb24KCiMjIFByZWRpY3Rpb24gd2l0aCBIaWdoIERpbWVuc2lvbmFsIFByZWRpY3RvcnMKCkdlbmVyYWwgc2V0dGluZzoKCi0gICBBaW06IGJ1aWxkIGEgKipwcmVkaWN0aW9uIG1vZGVsKiogdGhhdCBnaXZlcyBhIHByZWRpY3Rpb24gb2YgYW4gb3V0Y29tZSBmb3IgYSBnaXZlbiBzZXQgb2YgcHJlZGljdG9ycy4KCi0gV2UgdXNlICRYJCB0byByZWZlciB0byB0aGUgcHJlZGljdG9ycyBhbmQgJFkkIHRvIHJlZmVyIHRvIHRoZSBvdXRjb21lLgoKCi0gQSAqKnRyYWluaW5nIGRhdGEgc2V0KiogaXMgYXZhaWxhYmxlLCBzYXkgJChcbWF0aGJme1h9LFxtYXRoYmZ7WX0pJC4gSXQgY29udGFpbnMgJG4kIG9ic2VydmF0aW9ucyBvbiBvdXRjb21lcyBhbmQgb24gJHAkIHByZWRpY3RvcnMuCgotIFVzaW5nIHRoZSB0cmFpbmluZyBkYXRhLCBhIHByZWRpY3Rpb24gbW9kZWwgaXMgYnVpbGQsIHNheSAkXGhhdHttfShcbWF0aGJme1h9KSQuIFRoaXMgdHlwaWNhbGx5IGludm9sdmVzICoqbW9kZWwgYnVpbGRpbmcgKGZlYXR1cmUgc2VsZWN0aW9uKSoqIGFuZCBwYXJhbWV0ZXIgZXN0aW1hdGlvbi4KCgotICAgRHVyaW5nIHRoZSBtb2RlbCBidWlsZGluZywgcG90ZW50aWFsICoqbW9kZWxzIG5lZWQgdG8gYmUgZXZhbHVhdGVkKiogaW4gdGVybXMgb2YgdGhlaXIgcHJlZGljdGlvbiBxdWFsaXR5LgoKIyMgRXhhbXBsZTogVG94aWNvZ2Vub21pY3MgaW4gZWFybHkgZHJ1ZyBkZXZlbG9wbWVudAoKIyMjIEJhY2tncm91bmQKCi0gRWZmZWN0IG9mIGNvbXBvdW5kIG9uIGdlbmUgZXhwcmVzc2lvbi4KCi0gSW5zaWdodCBpbiBhY3Rpb24gYW5kIHRveGljaXR5IG9mIGRydWcgaW4gZWFybHkgcGhhc2UKLSBEZXRlcm1pbmUgYWN0aXZpdHkgd2l0aCBiaW8tYXNzYXk6IGUuZy4gYmluZGluZyBhZmZpbml0eSBvZiBjb21wb3VuZCB0byBjZWxsIHdhbGwgcmVjZXB0b3IgKHRhcmdldCwgSUM1MCkuCi0gRWFybHkgcGhhc2U6ICAyMCB0byA1MCBjb21wb3VuZHMKLSBCYXNlZCBvbiBpbiB2aXRybyByZXN1bHRzIG9uZSBhaW1zIHRvIGdldCBpbnNpZ2h0IGluIGhvdyB0byBidWlsZCBiZXR0ZXIgY29tcG91bmQgKGhpZ2hlciBvbi10YXJnZXQgYWN0aXZpdHkgbGVzcyB0b3hpY2l0eS4KLSBTbWFsbCB2YXJpYXRpb25zIGluIG1vbGVjdWxhciBzdHJ1Y3R1cmUgbGVhZCB0byB2YXJpYXRpb25zIGluIEJBIGFuZCBnZW5lIGV4cHJlc3Npb24uCi0gQWltOiBCdWlsZCBtb2RlbCB0byBwcmVkaWN0IGJpby1hY3Rpdml0eSBiYXNlZCBvbiBnZW5lIGV4cHJlc3Npb24gaW4gbGl2ZXIgY2VsbCBsaW5lLgoKIyMjIERhdGEKCi0gMzAgY2hlbWljYWwgY29tcG91bmRzIGhhdmUgYmVlbiBzY3JlZW5lZCBmb3IgdG94aWNpdHkKCi0gQmlvYXNzYXkgZGF0YSBvbiB0b3hpY2l0eSBzY3JlZW5pbmcKCi0gR2VuZSBleHByZXNzaW9ucyBpbiBhIGxpdmVyIGNlbGwgbGluZSBhcmUgcHJvZmlsZWQgZm9yIGVhY2ggY29tcG91bmQgKDQwMDAgZ2VuZXMpCgoKYGBge3J9CnRveERhdGEgPC0gcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvSERBMjAyMC9kYXRhL3RveERhdGFDZW50ZXJlZC5jc3YiLAogIGNvbF90eXBlcyA9IGNvbHMoKQopCnN2ZFggPC0gc3ZkKHRveERhdGFbLC0xXSkKYGBgCgpEYXRhIGlzIGFscmVhZHkgY2VudGVyZWQ6CgpgYGB7cn0KdG94RGF0YSAlPiUKICBjb2xNZWFucyAlPiUKICByYW5nZQpgYGAKCmBgYHtyfQogdG94RGF0YSAlPiUKICBuYW1lcyAlPiUKICBoZWFkCmBgYAoKLSBGaXJzdCBjb2x1bW4gY29udGFpbnMgZGF0YSBvbiBCaW9hc3NheS4KLSBUaGUgaGlnaGVyIHRoZSBzY29yZSBvbiBCaW9hc3NheSB0aGUgbW9yZSB0b3hpYyB0aGUgY29tcG91bmQKLSBPdGhlciBjb2x1bW5zIGNvbnRhaW4gZGF0YSBvbiBnZW5lIGV4cHJlc3Npb24gWDEsIC4uLiAsIFg0MDAwCgojIyMgRGF0YSBleHBsb3JhdGlvbgoKYGBge3J9CnRveERhdGEgJT4lCiAgZ2dwbG90KGFlcyh4PSIiLHk9QkEpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGU9TkEpICsKICBnZW9tX3BvaW50KHBvc2l0aW9uPSJqaXR0ZXIiKQpgYGAKCmBgYHtyfQpzdmRYIDwtIHRveERhdGFbLC0xXSAlPiUKICBzdmQKCmsgPC0gMgpWayA8LSBzdmRYJHZbLDE6a10KVWsgPC0gc3ZkWCR1WywxOmtdCkRrIDwtIGRpYWcoc3ZkWCRkWzE6a10pClprIDwtIFVrJSolRGsKY29sbmFtZXMoWmspIDwtIHBhc3RlMCgiWiIsMTprKQpjb2xuYW1lcyhWaykgPC0gcGFzdGUwKCJWIiwxOmspCgpaayAlPiUKICBhcy5kYXRhLmZyYW1lICU+JQogIG11dGF0ZShCQSA9IHRveERhdGEgJT4lIHB1bGwoQkEpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9IFoxLCB5ID0gWjIsIGNvbG9yID0gQkEpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMykgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93ID0gImJsdWUiLG1pZD0id2hpdGUiLGhpZ2g9InJlZCIpICsKICBnZW9tX3BvaW50KHNpemUgPSAzLCBwY2ggPSAyMSwgY29sb3IgPSAiYmxhY2siKQpgYGAKCi0gU2NvcmVzIG9uIHRoZSBmaXJzdCB0d28gcHJpbmNpcGFsIGNvbXBvbmVudHMgKG9yIE1EUyBwbG90KS4KLSBFYWNoIHBvaW50IGNvcnJlc3BvbmRzIHRvIGEgY29tcG91bmQuCi0gQ29sb3IgY29kZSByZWZlcnMgdG8gdGhlIHRveGljaXR5IHNjb3JlIChoaWdoZXIgc2NvcmUgbW9yZSB0b3hpYykuCi0gQ2xlYXIgc2VwYXJhdGlvbiBiZXR3ZWVuIGNvbXBvdW5kcyBhY2NvcmRpbmcgdG8gdG94aWNpdHkuCgotLS0KCi0gTmV4dCBsb2dpYyBzdGVwIGluIGEgUENBIGlzIHRvIGludGVycHJldCB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMuCi0gV2UgdGh1cyBoYXZlIHRvIGFzc2VzcyB0aGUgbG9hZGluZ3MuCi0gV2UgY2FuIGFkZCBhIHZlY3RvciBmb3IgZWFjaCBnZW5lIHRvIGdldCBhIGJpcGxvdCwgYnV0IHRoaXMgd291bGQgcmVxdWlyZSBwbG90dGluZyA0MDAwIHZlY3RvcnMsIHdoaWNoIHdvdWxkIHJlbmRlciB0aGUgcGxvdCB1bnJlYWRhYmxlLgoKQWx0ZXJuYXRpdmUgZ3JhcGggdG8gbG9vayBhdCB0aGUgbWFueSBsb2FkaW5ncyBvZiB0aGUgZmlyc3QgdHdvIFBDcy4KCmBgYHtyfQpncmlkLmFycmFuZ2UoCiAgVmsgJT4lCiAgICBhcy5kYXRhLmZyYW1lICU+JQogICAgbXV0YXRlKGdlbmVJRCA9IDE6bnJvdyhWaykpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gZ2VuZUlELCB5ID0gVjEpKSArCiAgICBnZW9tX3BvaW50KHBjaD0yMSkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gYygtMiwwLDIpKnNkKFZrWywxXSksIGNvbCA9ICJyZWQiKSAsCiAgVmsgJT4lCiAgICBhcy5kYXRhLmZyYW1lICU+JQogICAgbXV0YXRlKGdlbmVJRCA9IDE6bnJvdyhWaykpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gZ2VuZUlELCB5ID0gVjIpKSArCiAgICBnZW9tX3BvaW50KHBjaD0yMSkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gYygtMiwwLDIpKnNkKFZrWywyXSksIGNvbCA9ICJyZWQiKSwKICBuY29sPTIpCmBgYAoKLSBJdCBpcyBhbG1vc3QgaW1wb3NzaWJsZSB0byBpbnRlcnByZXQgdGhlIFBDcyBiZWNhdXNlIHRoZXJlIGFyZSA0MDAwIGdlbmVzIGNvbnRyaWJ1dGluZyB0byBlYWNoIFBDLgoKLSBJbiBhbiBhdHRlbXB0IHRvIGZpbmQgdGhlIG1vc3QgaW1wb3J0YW50IGdlbmVzIChpbiB0aGUgc2Vuc2UgdGhhdCB0aGV5IGRyaXZlIHRoZSBpbnRlcnByZXRhdGlvbiBvZiB0aGUgUENzKSwgdGhlIHBsb3RzIHNob3cgaG9yaXpvbnRhbCByZWZlcmVuY2UgbGluZXM6IHRoZSBhdmVyYWdlIG9mIHRoZSBsb2FkaW5ncywgYW5kIHRoZSBhdmVyYWdlIMKxIHR3aWNlIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIGxvYWRpbmdzLiBJbiBiZXR3ZWVuIHRoZSBsaW5lcyB3ZSBleHBlY3RzIGFib3V0IDk1JSBvZiB0aGUgbG9hZGluZ3MgKGlmIHRoZXkgd2VyZSBub3JtYWxseSBkaXN0cmlidXRlZCkuCgotIFRoZSBwb2ludHMgb3V0c2lkZSB0aGUgYmFuZCBjb21lIGZyb20gdGhlIGdlbmVzIHRoYXQgaGF2ZSByYXRoZXIgbGFyZ2UgbG9hZGluZ3MgKGluIGFic29sdXRlIHZhbHVlKSBhbmQgaGVuY2UgYXJlIGltcG9ydGFudCBmb3IgdGhlIGludGVycHJldGF0aW9uIG9mIHRoZSBQQ3MuCgotIE5vdGUsIHRoYXQgcGFydGljdWxhcmx5IGZvciB0aGUgZmlyc3QgUEMsIG9ubHkgYSBmZXcgZ2VuZXMgc2hvdyBhIG1hcmtlZGx5IGxhcmdlIGxvYWRpbmdzIHRoYXQgYXJlIG5lZ2F0aXZlLiBUaGlzIG1lYW5zIHRoYXQgYW4gdXByZWd1bGF0aW9uIG9mIHRoZXNlIGdlbmVzIHdpbGwgbGVhZCB0byBsb3cgc2NvcmVzIG9uIFBDMS4KLSBUaGVzZSBnZW5lcyB3aWxsIHZlcnkgbGlrZWx5IHBsYXkgYW4gaW1wb3J0YW50IHJvbGUgaW4gdGhlIHRveGljaXR5IG1lY2hhbmlzbS4KLSBJbmRlZWQsIGxvdyBzY29yZXMgb24gUEMxIGFyZSBpbiB0aGUgZGlyZWN0aW9uIG9mIG1vcmUgdG94aWNpdHkuCi0gSW4gdGhlIG5leHQgY2hhcHRlciB3ZSB3aWxsIGludHJvZHVjZSBhIG1ldGhvZCB0byBvYnRhaW4gc3BhcnNlIFBDcy4KCiMjIyBQcmVkaWN0aW9uIG1vZGVsCgpgYGB7cn0KbTEgPC0gbG0oQkEgfiAtMSArIC4sIHRveERhdGEpCgptMSAlPiUKICBjb2VmICU+JQogIGhlYWQoNDApCgptMSAlPiUKICBjb2VmICU+JQogIGlzLm5hICU+JQogIHN1bQoKc3VtbWFyeShtMSkkci5zcXVhcmVkCmBgYAoKUHJvYmxlbT8/CgojIyBCcmFpbiBleGFtcGxlCgotIENvdXJ0ZXN5IHRvIFNvbG9tb24gS3Vyei4gU3RhdGlzdGljYWwgcmV0aGlua2luZyB3aXRoIGJybXMsIGdncGxvdDIsIGFuZCB0aGUgdGlkeXZlcnNlIHZlcnNpb24gMS4yLjAuCgpodHRwczovL2Jvb2tkb3duLm9yZy9jb250ZW50LzM4OTAvCmh0dHBzOi8vZ2l0aHViLmNvbS9BU0t1cnovU3RhdGlzdGljYWxfUmV0aGlua2luZ193aXRoX2JybXNfZ2dwbG90Ml9hbmRfdGhlX3RpZHl2ZXJzZQoKLSBEYXRhIHdpdGggYnJhaW4gc2l6ZSBhbmQgYm9keSBzaXplIGZvciBzZXZlbiBzcGVjaWVzCgpgYGB7cn0KYnJhaW4gPC0KdGliYmxlKHNwZWNpZXMgPSBjKCJhZmFyZW5zaXMiLCAiYWZyaWNhbnVzIiwgImhhYmlsaXMiLCAiYm9pc2VpIiwgInJ1ZG9sZmVuc2lzIiwgImVyZ2FzdGVyIiwgInNhcGllbnMiKSwKICAgICAgIGJyYWluICAgPSBjKDQzOCwgNDUyLCA2MTIsIDUyMSwgNzUyLCA4NzEsIDEzNTApLAogICAgICAgbWFzcyAgICA9IGMoMzcuMCwgMzUuNSwgMzQuNSwgNDEuNSwgNTUuNSwgNjEuMCwgNTMuNSkpCmBgYAoKIyMjIERhdGEgZXhwbG9yYXRpb24KCmBgYHtyfQpicmFpbgoKcCA8LSBicmFpbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSAgbWFzcywgeSA9IGJyYWluLCBsYWJlbCA9IHNwZWNpZXMpKSArCiAgZ2VvbV9wb2ludCgpCgpwICsgZ2VvbV90ZXh0KG51ZGdlX3kgPSA0MCkKYGBgCgojIyMgTW9kZWxzCgpTaXggbW9kZWxzIHJhbmdlIGluIGNvbXBsZXhpdHkgZnJvbSB0aGUgc2ltcGxlIHVuaXZhcmlhdGUgbW9kZWwKClxiZWdpbnthbGlnbip9Clx0ZXh0e2JyYWlufV9pICYgXHNpbSBcb3BlcmF0b3JuYW1le05vcm1hbH0gKFxtdV9pLCBcc2lnbWEpIFxcClxtdV9pICYgPSBcYmV0YV8wICsgXGJldGFfMSBcdGV4dHttYXNzfV9pLApcZW5ke2FsaWduKn0KCnRvIHRoZSBkaXp6eWluZyBzaXh0aC1kZWdyZWUgcG9seW5vbWlhbCBtb2RlbAoKXGJlZ2lue2FsaWduKn0KXHRleHR7YnJhaW59X2kgJiBcc2ltIFxvcGVyYXRvcm5hbWV7Tm9ybWFsfSAoXG11X2ksIFxzaWdtYSkgXFwKXG11X2kgJiA9IFxiZXRhXzAgKyBcYmV0YV8xIFx0ZXh0e21hc3N9X2kgKyBcYmV0YV8yIFx0ZXh0e21hc3N9X2leMiArIFxiZXRhXzMgXHRleHR7bWFzc31faV4zICsgXGJldGFfNCBcdGV4dHttYXNzfV9pXjQgKyBcYmV0YV81IFx0ZXh0e21hc3N9X2leNSArIFxiZXRhXzYgXHRleHR7bWFzc31faV42LgpcZW5ke2FsaWduKn0KCmBgYHtyLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CmZvcm11bGFzIDwtIHNhcHBseSgxOjYsIGZ1bmN0aW9uKGkpCiAgcmV0dXJuKAogICAgIHBhc3RlMCgiSShtYXNzXiIsMTppLCIpIikgJT4lIHBhc3RlKGNvbGxhcHNlPSIgKyAiKQogICAgKQopCgpmb3JtdWxhcyA8LSBzYXBwbHkoCiAgcGFzdGUwKCJicmFpbiB+ICIsIGZvcm11bGFzKSwKICBhcy5mb3JtdWxhKQoKbW9kZWxzIDwtIGxhcHBseShmb3JtdWxhcywgbG0gLCBkYXRhID0gYnJhaW4pCmBgYAoKYGBge3J9CmRhdGEuZnJhbWUoCiAgZm9ybXVsYT1mb3JtdWxhcyAlPiUKICAgIGFzLmNoYXJhY3RlciwKICByMiA9IHNhcHBseSgKICAgIG1vZGVscywKICAgIGZ1bmN0aW9uKG1vZCkgc3VtbWFyeShtb2QpJHIuc3F1YXJlZCkKICApICAlPiUKICBnZ3Bsb3QoCiAgICBhZXMoeCA9IHIyLAogICAgICB5ID0gZm9ybXVsYSwKICAgICAgbGFiZWwgPSByMiAlPiUKICAgICAgICByb3VuZCgyKSAlPiUKICAgICAgICBhcy5jaGFyYWN0ZXIpCiAgKSArCiAgZ2VvbV90ZXh0KCkKYGBgCgpXZSBwbG90IHRoZSBmaXQgZm9yIGVhY2ggbW9kZWwgaW5kaXZpZHVhbGx5IGFuZCB0aGVtIGFycmFuZ2UgdGhlbSB0b2dldGhlciBpbiBvbmUgcGxvdC4KCmBgYHtyfQpwbG90cyA8LSBsYXBwbHkoMTo2LCBmdW5jdGlvbihpKQp7CiAgcCArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgZm9ybXVsYSA9IHkgfiBwb2x5KHgsaSkpICsKICBnZ3RpdGxlKAogICAgcGFzdGUwKAogICAgICAicjIgPSAiLAogICAgICByb3VuZChzdW1tYXJ5KG1vZGVsc1tbaV1dKSRyLnNxdWFyZWQqMTAwLDEpLAogICAgICAiJSIpCiAgICApCn0pCgpkby5jYWxsKCJncmlkLmFycmFuZ2UiLGMocGxvdHMsIG5jb2wgPSAzKSkKYGBgCgotIFdlIGNsZWFybHkgc2VlIHRoYXQgaW5jcmVhc2luZyB0aGUgbW9kZWwgY29tcGxleGl0eSBhbHdheXMgcHJvZHVjZXMgYSBmaXQgd2l0aCBhIHNtYWxsZXIgU1NFLgotIFRoZSBwcm9ibGVtIG9mIG92ZXJmaXR0aW5nIGlzIHZlcnkgb2J2aW91cy4gVGhlIG1vcmUgY29tcGxleCBwb2x5bm9taWFsIG1vZGVscyB3aWxsIG5vdCBnZW5lcmFsaXNlIHdlbGwgZm9yIHByZWRpY3Rpb24hCi0gV2UgZXZlbiBoYXZlIGEgbW9kZWwgdGhhdCBmaXRzIHRoZSBkYXRhIHBlcmZlY3RseSwgYnV0IHRoYXQgd2lsbCBtYWtlIHZlcnkgYWJzdXJkIHByZWRpdGlvbnMhCgotIFRvbyBmZXcgcGFyYW1ldGVycyBodXJ0cywgdG9vLiBGaXQgdGhlIHVuZGVyZml0IGludGVyY2VwdC1vbmx5IG1vZGVsLgoKYGBge3J9Cm0wIDwtIGxtKGJyYWluIH4gMSwgYnJhaW4pCnN1bW1hcnkobTApCgpwICsKICBzdGF0X3Ntb290aChtZXRob2QgPSAibG0iLCBmb3JtdWxhID0geSB+IDEpICsKICBnZ3RpdGxlKAogICAgcGFzdGUwKAogICAgICAicjIgPSAiLAogICAgICByb3VuZChzdW1tYXJ5KG0wKSRyLnNxdWFyZWQqMTAwLDEpLAogICAgICAiJSIpCiAgICApCmBgYAoKVGhlIHVuZGVyZml0IG1vZGVsIGRpZCBub3QgbGVhcm4gYW55dGhpbmcgYWJvdXQgdGhlIHJlbGF0aW9uIGJldHdlZW4gbWFzcyBhbmQgYnJhaW4uIEl0IHdvdWxkIGFsc28gZG8gYSB2ZXJ5IHBvb3Igam9iIGZvciBwcmVkaWN0aW5nIG5ldyBkYXRhLgoKIyMgT3ZlcnZpZXcKCldlIHdpbGwgbWFrZSBhIGRpc3RpbmN0aW9uIGJldHdlZW4gY29udGludW91cyBhbmQgZGlzY3JldGUgb3V0Y29tZXMuIEluIHRoaXMgY291cnNlIHdlIGZvY3VzIG9uCgotIExpbmVhciByZWdyZXNzaW9uIG1vZGVscyBmb3IgY29udGlub3VzIG91dGNvbWVzCgogIC0gUGVuYWxpc2VkIHJlZ3Jlc3Npb246IExhc3NvIGFuZCByaWRnZQogIC0gUHJpbmNpcGFsIGNvbXBvbmVudCByZWdyZXNzaW9uIChQQ1IpCgotIExvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWxzIGZvciBiaW5hcnkgb3V0Y29tZXMKCiAgLSBQZW5hbGlzZWQgcmVncmVzc2lvbjogTGFzc28gYW5kIHJpZGdlCgpGb3IgYWxsIHR5cGVzIG9mIG1vZGVsLCB3ZSB3aWxsIGRpc2N1c3MgZmVhdHVyZSBzZWxlY3Rpb24gbWV0aG9kcy4KCiMgTGluZWFyIFJlZ3Jlc3Npb24gZm9yIEhpZ2ggRGltZW5zaW9uYWwgRGF0YQoKQ29uc2lkZXIgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgKGZvciBkb3VibGUgY2VudGVyZWQgZGF0YSkKXFsKICBZX2kgPSBcYmV0YV8xWF97aTF9ICsgXGJldGFfMiBYX3tpMn0gKyBcY2RvdHMgKyBcYmV0YV9wWF97aXB9ICsgXGVwc2lsb25faSAsClxdCndpdGggJFx0ZXh0e0V9XGxlZnRbXGVwc2lsb24gXG1pZCBcbWF0aGJme1h9XHJpZ2h0XT0wJCBhbmQgJFx0ZXh0e3Zhcn1cbGVmdFtcZXBzaWxvbiBcbWlkIFxtYXRoYmZ7WH1ccmlnaHRdPVxzaWdtYV4yJC4KCkluIG1hdHJpeCBub3RhdGlvbiB0aGUgbW9kZWwgYmVjb21lcwpcWwogIFxtYXRoYmZ7WX0gPSBcbWF0aGJme1h9XG1hdGhiZlxiZXRhICsgXG1hdGhiZlxlcHNpbG9uLgpcXQpUaGUgbGVhc3Qgc3F1YXJlcyBlc3RpbWF0b3Igb2YgJFxtYXRoYmZcYmV0YSQgaXMgZ2l2ZW4gYnkKXFsKICBcaGF0e1xtYXRoYmZcYmV0YX0gPSAoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1eVFxtYXRoYmZ7WX0gLApcXQphbmQgdGhlIHZhcmlhbmNlIG9mICRcaGF0e1xtYXRoYmZcYmV0YX0kIGVxdWFscwpcWwogIFx0ZXh0e3Zhcn1cbGVmdFtcaGF0e1xtYXRoYmZcYmV0YX1ccmlnaHRdID0gKFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMX1cc2lnbWFeMi4KXF0KJFxsb25ncmlnaHRhcnJvdyQgdGhlICRwIFx0aW1lcyBwJCBtYXRyaXggJChcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9JCBpcyBjcnVjaWFsCgpOb3RlLCB0aGF0CgotIHdpdGggZG91YmxlIGNlbnRlcmVkIGRhdGEgaXQgaXMgbWVhbnQgdGhhdCBib3RoIHRoZSByZXNwb25zZXMgYXJlIGNlbnRlcmVkIChtZWFuIG9mICRcbWF0aGJme1l9JCBpcyB6ZXJvKSBhbmQgdGhhdCBhbGwgcHJlZGljdG9ycyBhcmUgY2VudGVyZWQgKGNvbHVtbnMgb2YgJFxtYXRoYmZ7WH0kIGhhdmUgemVybyBtZWFuKS4gV2l0aCBkb3VibGUgY2VudGVyZWQgZGF0YSB0aGUgaW50ZXJjZXB0IGluIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgaXMgYWx3YXlzIGV4YWN0bHkgZXF1YWwgdG8gemVybyBhbmQgaGVuY2UgdGhlIGludGVyY2VwdCBtdXN0IG5vdCBiZSBpbmNsdWRlZCBpbiB0aGUgbW9kZWwuCgotIHdlIGRvIG5vdCBhc3N1bWUgdGhhdCB0aGUgcmVzaWR1YWxzIGFyZSBub3JtYWxseSBkaXN0cmlidXRlZC4gRm9yIHByZWRpY3Rpb24gcHVycG9zZXMgdGhpcyBpcyBvZnRlbiBub3QgcmVxdWlyZWQgKG5vcm1hbGl0eSBpcyBwYXJ0aWN1bGFybHkgaW1wb3J0YW50IGZvciBzdGF0aXN0aWNhbCBpbmZlcmVuY2UgaW4gc21hbGwgc2FtcGxlcykuCgojIyBMaW5lYXIgUmVncmVzc2lvbiBmb3IgbXVsdGl2YXJpYXRlIGRhdGEgdnMgSGlnaCBEaW1lbnNpb25hbCBEYXRhCgotICRcbWF0aGJme1heVFh9JCBhbmQgJChcbWF0aGJme1heVFh9KV57LTF9JCBhcmUgJHAgXHRpbWVzIHAkIG1hdHJpY2VzCgotICRcbWF0aGJme1heVFh9JCBjYW4gb25seSBiZSBpbnZlcnRlZCBpZiBpdCBoYXMgcmFuayAkcCQKCi0gUmFuayBvZiBhIG1hdHJpeCBvZiBmb3JtICRcbWF0aGJme1heVFh9JCwgd2l0aCAkXG1hdGhiZntYfSQgYW5kICRuXHRpbWVzIHAkIG1hdHJpeCwgY2FuIG5ldmVyIGJlIGxhcmdlciB0aGFuICRcbWluKG4scCkkLgoKLSBpbiBtb3N0IHJlZ3Jlc3Npb24gcHJvYmxlbXMgJG4+cCQgYW5kIHJhbmsgb2YgJChcbWF0aGJme1heVFh9KSQgZXF1YWxzICRwJAoKLSBpbiBoaWdoIGRpbWVuc2lvbmFsIHJlZ3Jlc3Npb24gcHJvYmxlbXMgJHAgPj4+IG4kIGFuZCByYW5rIG9mICQoXG1hdGhiZntYXlRYfSkkIGVxdWFscyAkbjxwJAoKLSBpbiB0aGUgdG94aWNvZ2Vub21pY3MgZXhhbXBsZSAkbj0zMDxwPTQwMDAkIGFuZCAkXHRleHR7cmFua30oXG1hdGhiZntYXlRYfSlcbGVxIG49MzAkLgogICRcbG9uZ3JpZ2h0YXJyb3ckICQoXG1hdGhiZntYXlRYfSleey0xfSQgZG9lcyBub3QgZXhpc3QsIGFuZCBuZWl0aGVyIGRvZXMgJFxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fSQuCgojIyBDYW4gU1ZEIGhlbHA/CiAgLSBTaW5jZSB0aGUgY29sdW1ucyBvZiAkXG1hdGhiZntYfSQgYXJlIGNlbnRlcmVkLCAkXG1hdGhiZntYXlRYfSBccHJvcHRvIFx0ZXh0e3Zhcn1cbGVmdFtcbWF0aGJme1h9XHJpZ2h0XSQuCgogIC0gaWYgJFx0ZXh0e3Jhbmt9KFxtYXRoYmZ7WF5UWH0pPW49MzAkLCB0aGUgUENBIHdpbGwgZ2l2ZSAzMCBjb21wb25lbnRzLCBlYWNoIGJlaW5nIGEgbGluZWFyIGNvbWJpbmF0aW9uIG9mICRwPTQwMDAkIHZhcmlhYmxlcy4gVGhlc2UgMzAgUENzIGNvbnRhaW4gYWxsIGluZm9ybWF0aW9uIHByZXNlbnQgaW4gdGhlIG9yaWdpbmFsICRcbWF0aGJme1h9JCBkYXRhLgoKICAtIGlmICRcdGV4dHtyYW5rfShcbWF0aGJme1h9KT1uPTMwJCwgdGhlIFNWRCBvZiAkXG1hdGhiZntYfSQgaXMgZ2l2ZW4gYnkKICBcWwogICBcbWF0aGJme1h9ID0gXHN1bV97aT0xfV5uIFxkZWx0YV9pIFxtYXRoYmZ7dX1faSBcbWF0aGJme3Z9X2leVCA9IFxtYXRoYmZ7VX0gXGJvbGRzeW1ib2x7XERlbHRhfSBcbWF0aGJme1Z9XlQgPSBcbWF0aGJme1pWfV5ULAogIFxdCiAgd2l0aCAkXG1hdGhiZntafSQgdGhlICRuXHRpbWVzIG4kIG1hdHJpeCB3aXRoIHRoZSBzY29yZXMgb24gdGhlICRuJCBQQ3MuCgogIC0gU3RpbGwgcHJvYmxlbWF0aWMgYmVjYXVzZSBpZiB3ZSB1c2UgYWxsIFBDcyAkbj1wJC4KCgojIFByaW5jaXBhbCBDb21wb25lbnQgUmVncmVzc2lvbgoKQSBwcmluY2lwYWwgY29tcG9uZW50IHJlZ3Jlc3Npb24gKFBDUikgY29uc2lzdHMgb2YKCjEuIHRyYW5zZm9ybWluZyAkcD00MDAwJCBkaW1lbnNpb25hbCAkWCQtdmFyaWFibGUgdG8gdGhlICRuPTMwJCBkaW1lbnNpb25hbCAkWiQtdmFyaWFibGUgKFBDIHNjb3JlcykuIFRoZSAkbiQgUENzIGFyZSBtdXR1YWxseSB1bmNvcnJlbGF0ZWQuCgoyLiB1c2luZyB0aGUgJG4kIFBDLXZhcmlhYmxlcyBhcyByZWdyZXNzb3JzIGluIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwKCjMuIHBlcmZvcm1pbmcgZmVhdHVyZSBzZWxlY3Rpb24gdG8gc2VsZWN0IHRoZSBtb3N0IGltcG9ydGFudCByZWdyZXNzb3JzIChQQykuCgpGZWF0dXJlIHNlbGVjdGlvbiBpcyBrZXksIGJlY2F1c2Ugd2UgZG9uJ3Qgd2FudCB0byBoYXZlIGFzIG1hbnkgcmVncmVzc29ycyBhcyB0aGVyZSBhcmUgb2JzZXJ2YXRpb25zIGluIHRoZSBkYXRhLiBUaGlzIHdvdWxkIHJlc3VsdCBpbiB6ZXJvIHJlc2lkdWFsIGRlZ3JlZXMgb2YgZnJlZWRvbS4gKHNlZSBsYXRlcikKCi0tLQoKVG8ga2VlcCB0aGUgZXhwb3NpdGlvbiBnZW5lcmFsIHNvIHRoYXQgd2UgYWxsb3cgZm9yIGEgZmVhdHVyZSBzZWxlY3Rpb24gdG8gaGF2ZSB0YWtlbiBwbGFjZSwgSSB1c2UgdGhlIG5vdGF0aW9uICRcbWF0aGJme1V9X1MkIHRvIGRlbm90ZSBhIG1hdHJpeCB3aXRoIGxlZnQtc2luZ3VsYXIgY29sdW1uIHZlY3RvcnMgJFxtYXRoYmZ7dX1faSQsIHdpdGggJGkgXGluIHtcY2Fse1N9fSQgKCR7XGNhbHtTfX0kIGFuIGluZGV4IHNldCByZWZlcnJpbmcgdG8gdGhlIFBDcyB0byBiZSBpbmNsdWRlZCBpbiB0aGUgcmVncmVzc2lvbiBtb2RlbCkuCgpGb3IgZXhhbXBsZSwgc3VwcG9zZSB0aGF0IGEgZmVhdHVyZSBzZWxlY3Rpb24gbWV0aG9kIGhhcyByZXN1bHRlZCBpbiB0aGUgc2VsZWN0aW9uIG9mIFBDcyAxLCAzIGFuZCAxMiBmb3IgaW5jbHVzaW9uIGluIHRoZSBwcmVkaWN0aW9uIG1vZGVsLCB0aGVuICR7XGNhbHtTfX09XHsxLDMsMTJcfSQgYW5kClxbCiBcbWF0aGJme1V9X1MgPSBcYmVnaW57cG1hdHJpeH0KICBcbWF0aGJme3V9XzEgJiBcbWF0aGJme3V9XzMgJiBcbWF0aGJme3V9X3sxMn0KIFxlbmR7cG1hdHJpeH0uClxdCgotLS0KCiMjIyBFeGFtcGxlIG1vZGVsIGJhc2VkIG9uIGZpcnN0IDQgUENzCgpgYGB7cn0KayA8LSAzMApVayA8LSBzdmRYJHVbLDE6a10KRGsgPC0gZGlhZyhzdmRYJGRbMTprXSkKWmsgPC0gVWslKiVEawpZIDwtIHRveERhdGEgJT4lCiAgcHVsbChCQSkKCm00IDwtIGxtKFl+WmtbLDE6NF0pCnN1bW1hcnkobTQpCmBgYAoKTm90ZToKCi0gdGhlIGludGVyY2VwdCBpcyBlc3RpbWF0ZWQgYXMgemVyby4gKFdoeT8pIFRoZSBtb2RlbCBjb3VsZCBoYXZlIGJlZW4gZml0dGVkIGFzCgpgYGAKbTQgPC0gbG0oWX4tMStaa1ssMTo0XSkKYGBgCgotIHRoZSBQQy1wcmVkaWN0b3JzIGFyZSB1bmNvcnJlbGF0ZWQgKGJ5IGNvbnN0cnVjdGlvbikKCi0gZmlyc3QgUEMtcHJlZGljdG9ycyBhcmUgbm90IG5lY2Vzc2FyaWx5IHRoZSBtb3N0IGltcG9ydGFudCBwcmVkaWN0b3JzCgotICRwJC12YWx1ZXMgYXJlIG5vdCB2ZXJ5IG1lYW5pbmdmdWwgd2hlbiBwcmVkaWN0aW9uIGlzIHRoZSBvYmplY3RpdmUKCk1ldGhvZHMgZm9yIGZlYXR1cmUgc2VsZWN0aW9uIHdpbGwgYmUgZGlzY3Vzc2VkIGxhdGVyLgoKIyBSaWRnZSBSZWdyZXNzaW9uCgojIyBQZW5hbHR5CgogVGhlIHJpZGdlIHBhcmFtZXRlciBlc3RpbWF0b3IgaXMgZGVmaW5lZCBhcyB0aGUgcGFyYW1ldGVyICRcbWF0aGJmXGJldGEkIHRoYXQgbWluaW1pc2VzIHRoZSAqKnBlbmFsaXNlZCBsZWFzdCBzcXVhcmVzIGNyaXRlcmlvbioqCgogXFsKIFx0ZXh0e1NTRX1fXHRleHR7cGVufT1cVmVydFxtYXRoYmZ7WX0gLSBcbWF0aGJme1hcYmV0YX1cVmVydF8yXjIgKyBcbGFtYmRhIFxWZXJ0IFxib2xkc3ltYm9se1xiZXRhfSBcVmVydF8yXjIKXF0KCi0gJFxWZXJ0IFxib2xkc3ltYm9se1xiZXRhfSBcVmVydF8yXjI9XHN1bV97aj0xfV5wIFxiZXRhX2peMiQgaXMgdGhlICoqJExfMiQgcGVuYWx0eSB0ZXJtKioKCi0gJFxsYW1iZGE+MCQgaXMgdGhlIHBlbmFsdHkgcGFyYW1ldGVyICh0byBiZSBjaG9zZW4gYnkgdGhlIHVzZXIpLgoKTm90ZSwgdGhhdCB0aGF0IGlzIGVxdWl2YWxlbnQgdG8gbWluaW1pemluZwpcWwpcVmVydFxtYXRoYmZ7WX0gLSBcbWF0aGJme1hcYmV0YX1cVmVydF8yXjIgXHRleHR7IHN1YmplY3QgdG8gfSBcVmVydCBcYm9sZHN5bWJvbHtcYmV0YX1cVmVydF4yXzJcbGVxIHMKXF0KCk5vdGUsIHRoYXQgJHMkIGhhcyBhIG9uZS10by1vbmUgY29ycmVzcG9uZGVuY2Ugd2l0aCAkXGxhbWJkYSQKCiMjIEdyYXBoaWNhbCBpbnRlcnByZXRhdGlvbgoKYGBge3IgZWNobyA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeShnZ2ZvcmNlKQpsaWJyYXJ5KGxhdGV4MmV4cCkKbGlicmFyeShncmlkRXh0cmEpCgpwMSA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9lbGxpcHNlKGFlcyh4MCA9IDQsIHkwID0gMTEsIGEgPSAxMCwgYiA9IDMsIGFuZ2xlID0gcGkgLyA0KSkgKwogIGdlb21fZWxsaXBzZShhZXMoeDAgPSA0LCB5MCA9IDExLCBhID0gNSwgYiA9IDEuNSwgYW5nbGUgPSBwaSAvIDQpKSArCiAgeGxpbSgtMTIuNSwgMTIuNSkgKwogIHlsaW0oLTUsIDIwKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IDQsIHkgPSAxMSkpICsKICBhbm5vdGF0ZSgidGV4dCIsIGxhYmVsID0gVGVYKCIkKFxcaGF0e1xcYmV0YX1fMV57b2xzfSwgXFxoYXR7XFxiZXRhfV8yXntvbHN9KSQiKSwgeCA9IC01LCB5ID0gMTUsIHNpemUgPSA2LCBwYXJzZSA9IFRSVUUpICsKICB4bGFiKFRlWCgiJFxcYmV0YV8xJCIpKSArCiAgeWxhYihUZVgoIiRcXGJldGFfMiQiKSkgKwogIGdlb21fc2VnbWVudCgKICAgIGFlcyh4ID0gLTUsIHkgPSAxMi41LCB4ZW5kID0gMy43LCB5ZW5kID0gMTEuMyksCiAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yNSwgImNtIikpCiAgICApICsKICBjb29yZF9maXhlZCgpCgpwUmlkZ2UgPC0gcDEgKwogIGdlb21fY2lyY2xlKGFlcyh4MCA9IDAsIHkwID0gMCwgciA9IDMuOSkgLCBjb2xvciA9ICJyZWQiKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IC0xLjEsIHkgPSAzLjc1KSwgY29sb3IgPSAicmVkIikgKwogIGFubm90YXRlKCJ0ZXh0IiwgbGFiZWwgPSBUZVgoIiQoXFxoYXR7XFxiZXRhfV8xXntyaWRnZX0sIFxcaGF0e1xcYmV0YX1fMl57cmlkZ2V9KSQiKSwgeCA9IC04LjEsIHkgPSA0LjQ1LCBzaXplID0gNiwgcGFyc2UgPSBUUlVFLCBjb2xvciA9ICJyZWQiKSArCiAgZ2d0aXRsZSgiUmlkZ2UiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAiZ3JleSIpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJncmV5IikgKwogIHRoZW1lX21pbmltYWwoKQoKcFJpZGdlCmBgYAoKIyMgU29sdXRpb24KClRoZSBzb2x1dGlvbiBpcyBnaXZlbiBieQpcWwogIFxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fSA9IChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSBcbWF0aGJme1heVCBZfS4KXF0KSXQgY2FuIGJlIHNob3duIHRoYXQgJChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSkkIGlzIGFsd2F5cyBvZiByYW5rICRwJCBpZiAkXGxhbWJkYT4wJC4KCkhlbmNlLCAkKFxtYXRoYmZ7WF5UWH0rXGxhbWJkYSBcbWF0aGJme0l9KSQgaXMgaW52ZXJ0aWJsZSBhbmQgJFxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fSQgZXhpc3RzIGV2ZW4gaWYgJHA+Pj5uJC4KCldlIGFsc28gZmluZApcWwogIFx0ZXh0e3Zhcn1cbGVmdFtcaGF0e1xtYXRoYmZcYmV0YX1ccmlnaHRdID0gKFxtYXRoYmZ7WF5UWH0rXGxhbWJkYSBcbWF0aGJme0l9KV57LTF9IFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0gKFxtYXRoYmZ7WF5UWH0rXGxhbWJkYSBcbWF0aGJme0l9KV57LTF9XHNpZ21hXjIKXF0KCkhvd2V2ZXIsIGl0IGNhbiBiZSBzaG93biB0aGF0IGltcHJvdmVkIGludGVydmFscyB0aGF0IGFsc28gYWNjb3VudCBmb3IgdGhlIGJpYXMgY2FuIGJlIGNvbnN0cnVjdGVkIGJ5IHVzaW5nOgoKXFsKICBcdGV4dHt2YXJ9XGxlZnRbXGhhdHtcbWF0aGJmXGJldGF9XHJpZ2h0XSA9IChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSAgXHNpZ21hXjIuClxdCgojIyMgUHJvb2YKClRoZSBjcml0ZXJpb24gdG8gYmUgbWluaW1pc2VkIGlzCiAgXFsKICAgXHRleHR7U1NFfV9cdGV4dHtwZW59PVxWZXJ0XG1hdGhiZntZfSAtIFxtYXRoYmZ7WFxiZXRhfVxWZXJ0XzJeMiArIFxsYW1iZGEgXFZlcnQgXGJvbGRzeW1ib2x7XGJldGF9IFxWZXJ0XzJeMi4KIFxdCiBGaXJzdCB3ZSByZS1leHByZXNzIFNTRSBpbiBtYXRyaXggbm90YXRpb246CiBcWwogICBcdGV4dHtTU0V9X1x0ZXh0e3Blbn0gPSAoXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX0pXlQoXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX0pICsgXGxhbWJkYSBcYm9sZHN5bWJvbHtcYmV0YX1eVFxib2xkc3ltYm9se1xiZXRhfS4KIFxdCiBUaGUgcGFydGlhbCBkZXJpdmF0aXZlIHcuci50LiAkXGJvbGRzeW1ib2x7XGJldGF9JCBpcwogXFsKICAgXGZyYWN7XHBhcnRpYWx9e1xwYXJ0aWFsIFxib2xkc3ltYm9se1xiZXRhfX1cdGV4dHtTU0V9X1x0ZXh0e3Blbn0gPSAtMlxtYXRoYmZ7WH1eVChcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfSkrMlxsYW1iZGFcYm9sZHN5bWJvbHtcYmV0YX0uCiBcXQogU29sdmluZyAkXGZyYWN7XHBhcnRpYWx9e1xwYXJ0aWFsIFxib2xkc3ltYm9se1xiZXRhfX1cdGV4dHtTU0V9X1x0ZXh0e3Blbn09MCQgZ2l2ZXMKIFxbCiAgIFxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fSA9IChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSBcbWF0aGJme1heVCBZfS4KIFxdCiAoYXNzdW1wdGlvbjogJChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSkkIGlzIG9mIHJhbmsgJHAkLiBUaGlzIGlzIGFsd2F5cyB0cnVlIGlmICRcbGFtYmRhPjAkKQoKIyMgTGluayB3aXRoIFNWRAoKIyMjIFNWRCBhbmQgaW52ZXJzZQpXcml0ZSB0aGUgU1ZEIG9mICRcbWF0aGJme1h9JCAoJHA+biQpIGFzClxbCiAgIFxtYXRoYmZ7WH0gPSBcc3VtX3tpPTF9Xm4gXGRlbHRhX2kgXG1hdGhiZnt1fV9pIFxtYXRoYmZ7dn1faV5UID0gXHN1bV97aT0xfV5wIFxkZWx0YV9pIFxtYXRoYmZ7dX1faSBcbWF0aGJme3Z9X2leVCAgPSBcbWF0aGJme1V9XGJvbGRzeW1ib2x7XERlbHRhfSBcbWF0aGJme1Z9XlQgLApcXQp3aXRoCgotICRcZGVsdGFfe24rMX09XGRlbHRhX3tuKzJ9PSBcY2RvdHMgPSBcZGVsdGFfcD0wJAoKLSAkXGJvbGRzeW1ib2x7XERlbHRhfSQgYSAkcFx0aW1lcyBwJCBkaWFnb25hbCBtYXRyaXggb2YgdGhlICRcZGVsdGFfMSxcbGRvdHMsIFxkZWx0YV9wJAoKLSAgJFxtYXRoYmZ7VX0kIGFuICRuXHRpbWVzIHAkIG1hdHJpeCBhbmQgJFxtYXRoYmZ7Vn0kIGEgJHAgXHRpbWVzIHAkIG1hdHJpeC4gTm90ZSB0aGF0IG9ubHkgdGhlIGZpcnN0ICRuJCBjb2x1bW5zIG9mICRcbWF0aGJme1V9JCBhbmQgJFxtYXRoYmZ7Vn0kIGFyZSBpbmZvcm1hdGl2ZS4KCldpdGggdGhlIFNWRCBvZiAkXG1hdGhiZntYfSQgd2Ugd3JpdGUKIFxbCiAgIFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0gPSBcbWF0aGJme1Z9XGJvbGRzeW1ib2x7XERlbHRhCiAgICAgfV4yXG1hdGhiZntWfV5ULgogXF0KIFRoZSBpbnZlcnNlIG9mICRcbWF0aGJme1h9XlRcbWF0aGJme1h9JCBpcyB0aGVuIGdpdmVuIGJ5CiBcWwogICAoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfSA9IFxtYXRoYmZ7Vn1cYm9sZHN5bWJvbHtcRGVsdGF9XnstMn1cbWF0aGJme1Z9XlQuCiBcXQogU2luY2UgJFxib2xkc3ltYm9se1xEZWx0YX0kIGhhcyAkXGRlbHRhX3tuKzF9PVxkZWx0YV97bisyfT0gXGNkb3RzID0gXGRlbHRhX3A9MCQsIGl0IGlzIG5vdCBpbnZlcnRpYmxlLgoKIyMjIFNWRCBvZiBwZW5hbGlzZWQgbWF0cml4ICRcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSQKCkl0IGNhbiBiZSBzaG93biB0aGF0ClxbCiAgXG1hdGhiZntYXlRYfStcbGFtYmRhIFxtYXRoYmZ7SX0gPSBcbWF0aGJme1Z9IChcYm9sZHN5bWJvbHtcRGVsdGF9XjIrXGxhbWJkYSBcbWF0aGJme0l9KSBcbWF0aGJme1Z9XlQgLApcXQppLmUuIGFkZGluZyBhIGNvbnN0YW50IHRvIHRoZSBkaWFnb25hbCBlbGVtZW50cyBkb2VzIG5vdCBhZmZlY3QgdGhlIGVpZ2VudmVjdG9ycywgYW5kIGFsbCBlaWdlbnZhbHVlcyBhcmUgaW5jcmVhc2VkIGJ5IHRoaXMgY29uc3RhbnQuCiRcbG9uZ3JpZ2h0YXJyb3ckIHplcm8gZWlnZW52YWx1ZXMgYmVjb21lICRcbGFtYmRhJC4KCkhlbmNlLApcWwogIChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSA9IFxtYXRoYmZ7Vn0gKFxib2xkc3ltYm9se1xEZWx0YX1eMitcbGFtYmRhIFxtYXRoYmZ7SX0pXnstMX0gXG1hdGhiZntWfV5UICwKXF0Kd2hpY2ggY2FuIGJlIGNvbXB1dGVkIGV2ZW4gd2hlbiBzb21lIGVpZ2VudmFsdWVzIGluICRcYm9sZHN5bWJvbHtcRGVsdGF9XjIkIGFyZSB6ZXJvLgoKTm90ZSwgdGhhdCBmb3IgaGlnaCBkaW1lbnNpb25hbCBkYXRhICgkcD4+Pm4kKSBtYW55IGVpZ2VudmFsdWVzIGFyZSB6ZXJvIGJlY2F1c2UgJFxtYXRoYmZ7WF5UWH0kIGlzIGEgJHAgXHRpbWVzIHAkIG1hdHJpeCBhbmQgaGFzIHJhbmsgJG4kLgoKVGhlIGlkZW50aXR5ICRcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSA9IFxtYXRoYmZ7Vn0gKFxib2xkc3ltYm9se1xEZWx0YX1eMitcbGFtYmRhIFxtYXRoYmZ7SX0pIFxtYXRoYmZ7Vn1eVCQgaXMgZWFzaWx5IGNoZWNrZWQ6ClxbCiAgXG1hdGhiZntWfSAoXGJvbGRzeW1ib2x7XERlbHRhfV4yK1xsYW1iZGEgXG1hdGhiZntJfSkgXG1hdGhiZntWfV5UID0gXG1hdGhiZntWfVxib2xkc3ltYm9se1xEZWx0YX1eMlxtYXRoYmZ7Vn1eVCArIFxsYW1iZGEgXG1hdGhiZntWVn1eVCAgPSBcbWF0aGJme1Z9XGJvbGRzeW1ib2x7XERlbHRhfV4yXG1hdGhiZntWfV5UICsgXGxhbWJkYSBcbWF0aGJme0l9ID0gXG1hdGhiZntYXlRYfStcbGFtYmRhIFxtYXRoYmZ7SX0uClxdCgoKIyMgUHJvcGVydGllcwoKLSBUaGUgUmlkZ2UgZXN0aW1hdG9yIGlzIGJpYXNlZCEgVGhlICRcYm9sZHN5bWJvbHtcYmV0YX0kIGFyZSBzaHJ1bmtlbiB0byB6ZXJvIQpcYmVnaW57ZXFuYXJyYXl9CiBcdGV4dHtFfVtcaGF0e1xib2xkc3ltYm9se1xiZXRhfX1dICY9JiAoXG1hdGhiZntYXlRYfStcbGFtYmRhIFxtYXRoYmZ7SX0pXnstMX0gXG1hdGhiZntYfV5UIFx0ZXh0e0V9W1xtYXRoYmZ7WX1dXFwKJj0mIChcbWF0aGJme1h9XlRcbWF0aGJme1h9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSBcbWF0aGJme1h9XlQgXG1hdGhiZntYfVxib2xkc3ltYm9se1xiZXRhfVxcClxlbmR7ZXFuYXJyYXl9CgotIE5vdGUsIHRoYXQgdGhlIHNocmlua2FnZSBpcyBsYXJnZXIgaW4gdGhlIGRpcmVjdGlvbiBvZiB0aGUgc21hbGxlciBlaWdlbnZhbHVlcy4KClxiZWdpbntlcW5hcnJheX0KXHRleHR7RX1bXGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19XSY9JlxtYXRoYmZ7Vn0gKFxib2xkc3ltYm9se1xEZWx0YX1eMitcbGFtYmRhIFxtYXRoYmZ7SX0pXnstMX0gXG1hdGhiZntWfV5UIFxtYXRoYmZ7Vn0gXGJvbGRzeW1ib2x7XERlbHRhfV4yIFxtYXRoYmZ7Vn1eVFxib2xkc3ltYm9se1xiZXRhfVxcCiY9JlxtYXRoYmZ7Vn0gKFxib2xkc3ltYm9se1xEZWx0YX1eMitcbGFtYmRhIFxtYXRoYmZ7SX0pXnstMX0gXGJvbGRzeW1ib2x7XERlbHRhfV4yIFxtYXRoYmZ7Vn1eVFxib2xkc3ltYm9se1xiZXRhfVxcCiY9JiBcbWF0aGJme1Z9ClxsZWZ0W1xiZWdpbnthcnJheX17Y2NjfQpcZnJhY3tcZGVsdGFfMV4yfXtcZGVsdGFfMV4yK1xsYW1iZGF9JlxsZG90cyYwIFxcCiZcdmRvdHMmXFwKMCZcbGRvdHMmXGZyYWN7XGRlbHRhX3JeMn17XGRlbHRhX3JeMitcbGFtYmRhfQpcZW5ke2FycmF5fVxyaWdodF0KXG1hdGhiZntWfV5UXGJvbGRzeW1ib2x7XGJldGF9ClxlbmR7ZXFuYXJyYXl9CgotICB0aGUgdmFyaWFuY2Ugb2YgdGhlIHByZWRpY3Rpb24gJFxoYXR7e1l9fShcbWF0aGJme3h9KT1cbWF0aGJme3h9XlRcaGF0XGJldGEkLAogIFxbCiAgICBcdGV4dHt2YXJ9XGxlZnRbXGhhdHt7WX19KFxtYXRoYmZ7eH0pXG1pZCBcbWF0aGJme3h9XHJpZ2h0XSA9IFxtYXRoYmZ7eH1eVChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfVxtYXRoYmZ7eH0KICBcXQogIGlzIHNtYWxsZXIgdGhhbiB3aXRoIHRoZSBsZWFzdC1zcXVhcmVzIGVzdGltYXRvci4KCi0gIHRocm91Z2ggdGhlIGJpYXMtdmFyaWFuY2UgdHJhZGUtb2ZmIGl0IGlzIGhvcGVkIHRoYXQgYmV0dGVyIHByZWRpY3Rpb25zIGluIHRlcm1zIG9mIGV4cGVjdGVkIGNvbmRpdGlvbmFsIHRlc3QgZXJyb3IgY2FuIGJlIG9idGFpbmVkLCBmb3IgYW4gYXBwcm9wcmlhdGUgY2hvaWNlIG9mICRcbGFtYmRhJC4KCgpSZWNhbGwgdGhlIGV4cHJlc3Npb24gb2YgdGhlIGV4cGVjdGVkIGNvbmRpdGlvbmFsIHRlc3QgZXJyb3IKXGJlZ2lue2VxbmFycmF5fQogIEVycihcbWF0aGJme3h9KSAmPSYgXHRleHR7RX1cbGVmdFsoXGhhdHtZfSAtIFleKileMlxtaWQgXG1hdGhiZnt4fVxyaWdodF1cXAogICY9JgogIFx0ZXh0e3Zhcn1cbGVmdFtcaGF0e1l9XG1pZCBcbWF0aGJme3h9XHJpZ2h0XSArIFx0ZXh0e2JpYXN9XjIoXG1hdGhiZnt4fSkrCiAgXHRleHR7dmFyfVxsZWZ0W1leKlxtaWQgXG1hdGhiZnt4fVxyaWdodF0KXGVuZHtlcW5hcnJheX0Kd2hlcmUKCi0gJFxoYXR7WX09XGhhdHtZfShcbWF0aGJme3h9KT1cbWF0aGJme3h9XlRcaGF0e1xib2xkc3ltYm9se1xiZXRhfX0kIGlzIHRoZSBwcmVkaWN0aW9uIGF0ICRcbWF0aGJme3h9JAotICRZXiokIGlzIGFuIG91dGNvbWUgYXQgcHJlZGljdG9yICRcbWF0aGJme3h9JAotICRcbXUoXG1hdGhiZnt4fSkgPSBcdGV4dHtFfVxsZWZ0W1xoYXR7WX1cbWlkIFxtYXRoYmZ7eH1ccmlnaHRdIFx0ZXh0eyBhbmQgfSBcbXVeKih4KT1cdGV4dHtFfVxsZWZ0W1leKlxtaWQgXG1hdGhiZnt4fVxyaWdodF0kCi0gJFx0ZXh0e2JpYXN9KFxtYXRoYmZ7eH0pPVxtdShcbWF0aGJme3h9KS1cbXVeKihcbWF0aGJme3h9KSQKLSAkXHRleHR7dmFyfVxsZWZ0W1leKlxtaWQgXG1hdGhiZnt4fVxyaWdodF0kIHRoZSBpcnJlZHVjaWJsZSBlcnJvciB0aGF0IGRvZXMgbm90IGRlcGVuZCBvbiB0aGUgbW9kZWwuIEl0IHNpbXBseSBvcmlnaW5hdGVzIGZyb20gb2JzZXJ2YXRpb25zIHRoYXQgcmFuZG9tbHkgZmx1Y3R1YXRlIGFyb3VuZCB0aGUgdHJ1ZSBtZWFuICRcbXVeKih4KSQuCgojIyBUb3hpY29nZW5vbWljcyBleGFtcGxlCgpgYGB7cn0KbGlicmFyeShnbG1uZXQpCm1SaWRnZSA8LSBnbG1uZXQoCiAgeCA9IHRveERhdGFbLC0xXSAlPiUKICAgIGFzLm1hdHJpeCwKICB5ID0gdG94RGF0YSAlPiUKICAgIHB1bGwoQkEpLAogIGFscGhhID0gMCkgIyByaWRnZTogYWxwaGEgPSAwCgpwbG90KG1SaWRnZSwgeHZhcj0ibGFtYmRhIikKYGBgCgoKVGhlIFIgZnVuY3Rpb24gXHRleHRzZntnbG1uZXR9IHVzZXMgXHRleHRzZntsYW1iZGF9IHRvIHJlZmVyIHRvIHRoZSBwZW5hbHR5IHBhcmFtZXRlci4gSW4gdGhpcyBjb3Vyc2Ugd2UgdXNlICRcbGFtYmRhJCwgYmVjYXVzZSAkXGxhbWJkYSQgaXMgb2Z0ZW4gdXNlZCBhcyBlaWdlbnZhbHVlcy4KClRoZSBncmFwaCBzaG93cyB0aGF0IHdpdGggaW5jcmVhc2luZyBwZW5hbHR5IHBhcmFtZXRlciwgdGhlIHBhcmFtZXRlciBlc3RpbWF0ZXMgYXJlIHNocnVua2VuIHRvd2FyZHMgemVyby4gVGhlIGVzdGltYXRlcyB3aWxsIG9ubHkgcmVhY2ggemVybyBmb3IgJFxsYW1iZGEgXHJpZ2h0YXJyb3cgXGluZnR5JC4gVGhlIHN0cm9uZ2VyIHRoZSBzaHJpbmthZ2UsIHRoZSBsYXJnZXIgdGhlIGJpYXMgKHRvd2FyZHMgemVybykgYW5kIHRoZSBzbWFsbGVyIHRoZSB2YXJpYW5jZSBvZiB0aGUgcGFyYW1ldGVyIGVzdGltYXRvcnMgKGFuZCBoZW5jZSBhbHNvIHNtYWxsZXIgdmFyaWFuY2Ugb2YgdGhlIHByZWRpY3Rpb25zKS4KCkFub3RoZXIgKGluZm9ybWFsKSB2aWV3cG9pbnQgaXMgdGhlIGZvbGxvd2luZy4gQnkgc2hyaW5raW5nIHRoZSBlc3RpbWF0ZXMgdG93YXJkcyB6ZXJvLCB0aGUgZXN0aW1hdGVzIGxvb3NlIHNvbWUgb2YgdGhlaXIgYGBkZWdyZWVzIG9mIGZyZWVkb20nJyBzbyB0aGF0IHRoZSBwYXJhbWV0ZXJzIGJlY29tZSBlc3RpbWFibGUgd2l0aCBvbmx5ICRuPHAkIGRhdGEgcG9pbnRzLiBFdmVuIHdpdGggYSB2ZXJ5IHNtYWxsICRcbGFtYmRhPjAkLCB0aGUgcGFyYW1ldGVycyByZWdhaW4gdGhlaXIgZXN0aW1hYmlsaXR5LiBIb3dldmVyLCBub3RlIHRoYXQgdGhlIHZhcmlhbmNlIG9mIHRoZSBlc3RpbWF0b3IgaXMgZ2l2ZW4gYnkKXFsKICBcdGV4dHt2YXJ9XGxlZnRbXGhhdHtcbWF0aGJmXGJldGF9XHJpZ2h0XSA9IChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSBcc2lnbWFeMiA9IFxtYXRoYmZ7Vn0oXGJvbGRzeW1ib2x7XERlbHRhfV4yK1xsYW1iZGFcbWF0aGJme0l9KV57LTF9XG1hdGhiZntWfV5UXHNpZ21hXjIuClxdCkhlbmNlLCBhIHNtYWxsICRcbGFtYmRhJCB3aWxsIHJlc3VsdCBpbiBsYXJnZSB2YXJpYW5jZXMgb2YgdGhlIHBhcmFtZXRlciBlc3RpbWF0b3JzLiBUaGUgbGFyZ2VyICRcbGFtYmRhJCwgdGhlIHNtYWxsZXIgdGhlIHZhcmlhbmNlcyBiZWNvbWUuIEluIHRoZSBsaW1pdCwgYXMgJFxsYW1iZGFccmlnaHRhcnJvd1xpbmZ0eSQsIHRoZSBlc3RpbWF0ZXMgYXJlIGNvbnZlcmdlZCB0byB6ZXJvIGFuZCBzaG93IG5vIHZhcmlhYmlsaXR5IGFueSBsb25nZXIuCgojIExhc3NvIFJlZ3Jlc3Npb24KCi0gVGhlIExhc3NvIGlzIGFub3RoZXIgZXhhbXBsZSBvZiBwZW5hbGlzZWQgcmVncmVzc2lvbi4KCi0gVGhlIGxhc3NvIGVzdGltYXRvciBvZiAkXGJvbGRzeW1ib2x7XGJldGF9JCBpcyB0aGUgc29sdXRpb24gdG8gbWluaW1pc2luZyB0aGUgcGVuYWxpc2VkIFNTRQpcWwogXHRleHR7U1NFfV9cdGV4dHtwZW59ID0gXHN1bV97aT0xfV5uIChZX2kgLSBcbWF0aGJme3h9X2leVFxib2xkc3ltYm9se1xiZXRhfSleMiArIFxsYW1iZGEgXHN1bV97aj0xfV5wIFx2ZXJ0IFxiZXRhX2pcdmVydC4KXF0KCgpvciwgZXF1aXZhbGVudGx5LCBtaW5pbWlzaW5nCgpcWwpcdGV4dHtTU0V9ICA9IFxWZXJ0IFxtYXRoYmZ7WX0gLSBcbWF0aGJme1hcYmV0YX1cVmVydF8yXjIgXHRleHR7IHN1YmplY3QgdG8gfSBcVmVydCBcbWF0aGJmXGJldGFcVmVydF8xIFxsZXEgYwpcXQp3aXRoCgotICRcVmVydCBcbWF0aGJmXGJldGFcVmVydF8xID0gXHN1bVxsaW1pdHNfe2o9MX1ecCBcdmVydCBcYmV0YV9qIFx2ZXJ0JAoKLSBEZXNwaXRlIHN0cm9uZyBzaW1pbGFyaXR5IGJldHdlZW4gcmlkZ2UgYW5kIGxhc3NvIHJlZ3Jlc3Npb24gKCRMXzIkIHZlcnN1cyAkTF8xJCBub3JtIGluIHBlbmFsdHkgdGVybSksIHRoZXJlIGlzIG5vIGFuYWx5dGljYWwgc29sdXRpb24gb2YgdGhlIGxhc3NvIHBhcmFtZXRlciBlc3RpbWF0ZSBvZiAkXG1hdGhiZlxiZXRhJC4KCi0gRm9ydHVuYXRlbHksIGNvbXB1dGF0aW9uYWwgZWZmaWNpZW50IGFsZ29yaXRobXMgaGF2ZSBiZWVuIGltcGxlbWVudGVkIGluIHN0YXRpc3RpY2FsIHNvZnR3YXJlCgotIFRoZSBMYXNzbyBlc3RpbWF0b3Igb2YgJFxib2xkc3ltYm9se1xiZXRhfSQgaXMgYmlhc2VkIGFuZCBnZW5lcmFsbHkgaGFzIGEgc21hbGxlciB2YXJpYW5jZSB0aGVuIHRoZSBsZWFzdC1zcXVhcmVzIGVzdGltYXRvci4KCi0gSGVuY2UsIHRoZSBiaWFzLXZhcmlhbmNlIHRyYWRlLW9mZiBtYXkgaGVyZSBhbHNvIGhlbHAgaW4gZmluZGluZyBiZXR0ZXIgcHJlZGljdGlvbnMgd2l0aCBiaWFzZWQgZXN0aW1hdG9ycy4KCi0gSW4gY29udHJhc3QgdG8gcmlkZ2UgcmVncmVzc2lvbiwgaG93ZXZlciwgdGhlIGxhc3NvIGVzdGltYXRvciBjYW4gZ2l2ZSBhdCBtb3N0ICRcbWluKHAsbikkIG5vbi16ZXJvICRcYmV0YSQtZXN0aW1hdGVzLgoKLSBIZW5jZSwgYXQgZmlyc3Qgc2lnaHQgdGhlIGxhc3NvIGlzIG5vdCBkaXJlY3RseSBhcHByb3ByaWF0ZSBmb3IgaGlnaC1kaW1lbnNpb25hbCBzZXR0aW5ncy4KCi0gQW4gaW1wb3J0YW50IGFkdmFudGFnZSBvZiB0aGUgbGFzc28gaXMgdGhhdCBjaG9vc2luZyBhbiBhcHByb3ByaWF0ZSB2YWx1ZSBmb3IgJFxsYW1iZGEkIGlzIGEga2luZCBhIG1vZGVsIGJ1aWxkaW5nIG9yIGZlYXR1cmUgc2VsZWN0aW9uIHByb2NlZHVyZSAoc2VlIGZ1cnRoZXIpLgoKIyMgR3JhcGhpY2FsIGludGVycHJldGF0aW9uIG9mIExhc3NvIHZzIHJpZGdlCgpOb3RlIHRoYXQgdGhlIGxhc3NvIGlzIGEgY29uc3RyYWluZWQgcmVncmVzc2lvbiBwcm9ibGVtIHdpdGgKClxbClxWZXJ0IFxtYXRoYmZ7WX0gLSBcbWF0aGJme1hcYmV0YX1cVmVydF8yXjIgXHRleHR7IHN1YmplY3QgdG8gfSBcVmVydCBcbWF0aGJmXGJldGFcVmVydF8xIFxsZXEgYwpcXQphbmQgcmlkZ2UKXFsKXFZlcnQgXG1hdGhiZntZfSAtIFxtYXRoYmZ7WFxiZXRhfVxWZXJ0XzJeMiBcdGV4dHsgc3ViamVjdCB0byB9IFxWZXJ0IFxtYXRoYmZcYmV0YVxWZXJ0XjJfMiBcbGVxIGMKXF0KCmBgYHtyIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnBMYXNzbyA8LSBwMSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMCwgeSA9IDQuMiAsIHhlbmQgPSA0LjIsIHllbmQgPSAwKSwgY29sb3IgPSAicmVkIikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDAsIHkgPSA0LjIgLCB4ZW5kID0gLSA0LjIsIHllbmQgPSAwKSwgY29sb3IgPSAicmVkIikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDQuMiwgeSA9IDAgLCB4ZW5kID0gMCwgeWVuZCA9IC00LjIpLCBjb2xvciA9ICJyZWQiKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMCwgeSA9IC0gNC4yICwgeGVuZCA9IC0gNC4yLCB5ZW5kID0gMCksIGNvbG9yID0gInJlZCIpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gMCwgeSA9IDQuMiksIGNvbG9yID0gInJlZCIpICsKICBhbm5vdGF0ZSgidGV4dCIsIGxhYmVsID0gVGVYKCIkKFxcaGF0e1xcYmV0YX1fMV57bGFzc299LCBcXGhhdHtcXGJldGF9XzJee2xhc3NvfSkkIiksIHggPSA3LCB5ID0gNC4yLCBzaXplID0gNiwgcGFyc2UgPSBUUlVFLCBjb2xvciA9ICJyZWQiKSArCiAgZ2d0aXRsZSgiTGFzc28iKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAiZ3JleSIpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJncmV5IikgKwogIHRoZW1lX21pbmltYWwoKQoKZ3JpZC5hcnJhbmdlKHBMYXNzbywgcFJpZGdlLCBuY29sID0gMikKYGBgCgpOb3RlLCB0aGF0CgotIHBhcmFtZXRlcnMgZm9yIHRoZSBsYXNzbyBjYW4gbmV2ZXIgc3dpdGNoIHNpZ24sIHRoZXkgYXJlIHNldCBhdCB6ZXJvISBTZWxlY3Rpb24hCi0gcmlkZ2UgcmVncmVzc2lvbiBjYW4gbGVhZCB0byBwYXJhbWV0ZXJzIHRoYXQgc3dpdGNoIHNpZ24uCgojIyBUb3hpY29nZW5vbWljcyBleGFtcGxlCgpgYGB7cn0KbUxhc3NvIDwtIGdsbW5ldCgKICB4ID0gdG94RGF0YVssLTFdICU+JQogICAgYXMubWF0cml4LAogIHkgPSB0b3hEYXRhICU+JQogICAgcHVsbChCQSksCmFscGhhID0gMSkKcGxvdChtTGFzc28sIHh2YXIgPSAibGFtYmRhIikKYGBgCgotIFRoZSBncmFwaCB3aXRoIHRoZSBwYXRocyBvZiB0aGUgcGFyYW1ldGVyIGVzdGltYXRlcyBuaWNlbHkgaWxsdXN0cmF0ZXMgdGhlIHR5cGljYWwgYmVoYXZpb3VyIG9mIHRoZSBsYXNzbyBlc3RpbWF0ZXMgYXMgYSBmdW5jdGlvbiBvZiAkXGxhbWJkYSQ6IHdoZW4gJFxsYW1iZGEkIGluY3JlYXNlcyB0aGUgZXN0aW1hdGVzIGFyZSBzaHJ1bmtlbiB0b3dhcmRzIHplcm8uCgotIFdoZW4gYW4gZXN0aW1hdGUgaGl0cyB6ZXJvLCBpdCByZW1haW5zIGV4YWN0bHkgZXF1YWwgdG8gemVybyB3aGVuICRcZ2FtbWEkIGZ1cnRoZXIgaW5jcmVhc2VzLiBBIHBhcmFtZXRlciBlc3RpbWF0ZSBlcXVhbCB0byB6ZXJvLCBzYXkgJFxoYXRcYmV0YV9qPTAkLCBpbXBsaWVzIHRoYXQgdGhlIGNvcnJlc3BvbmRpbmcgcHJlZGljdG9yICR4X2okIGlzIG5vIGxvbmdlciBpbmNsdWRlZCBpbiB0aGUgbW9kZWwgKGkuZS4gJFxiZXRhX2p4X2o9MCQpLgoKLSBUaGUgbW9kZWwgZml0IGlzIGtub3duIGFzIGEgc3BhcnNlIG1vZGVsIGZpdCAobWFueSB6ZXJvZXMpLiBIZW5jZSwgY2hvb3NpbmcgYSBhcHByb3ByaWF0ZSB2YWx1ZSBmb3IgJFxnYW1tYSQgaXMgbGlrZSBjaG9vc2luZyB0aGUgaW1wb3J0YW50IHByZWRpY3RvcnMgaW4gdGhlIG1vZGVsIChmZWF0dXJlIHNlbGVjdGlvbikuCgoKIyBTcGxpbmVzIGFuZCB0aGUgY29ubmVjdGlvbiB0byByaWRnZSByZWdyZXNzaW9uLgoKIyMgTGlkYXIgZGF0YXNldAoKLSBMSURBUiAobGlnaHQgZGV0ZWN0aW9uIGFuZCByYW5naW5nKSB1c2VzIHRoZSByZWZsZWN0aW9uIG9mIGxhc2VyLWVtaXR0ZWQgbGlnaHQgdG8gZGV0ZWN0IGNoZW1pY2FsIGNvbXBvdW5kcyBpbiB0aGUgYXRtb3NwaGVyZS4KLSBUaGUgTElEQVIgdGVjaG5pcXVlIGhhcyBwcm92ZW4gdG8gYmUgYW4gZWZmaWNpZW50IHRvb2wgZm9yIG1vbml0b3JpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiBzZXZlcmFsIGF0bW9zcGhlcmljIHBvbGx1dGFudHMgb2YgaW1wb3J0YW5jZTsgc2VlIFNpZ3Jpc3QgKDE5OTQpLgotIFRoZSByYW5nZSBpcyB0aGUgZGlzdGFuY2UgdHJhdmVsZWQgYmVmb3JlIHRoZSBsaWdodCBpcyByZWZsZWN0ZWQgYmFjayB0byBpdHMgc291cmNlLgotIFRoZSBsb2dyYXRpbyBpcyB0aGUgbG9nYXJpdGhtIG9mIHRoZSByYXRpbyBvZiByZWNlaXZlZCBsaWdodCBmcm9tIHR3byBsYXNlciBzb3VyY2VzLgoKICAtIE9uZSBzb3VyY2UgaGFkIGEgZnJlcXVlbmN5IGVxdWFsIHRvIHRoZSByZXNvbmFuY2UgZnJlcXVlbmN5IG9mIHRoZSBjb21wb3VuZCBvZiBpbnRlcmVzdCwgd2hpY2ggd2FzIG1lcmN1cnkgaW4gdGhpcyBzdHVkeS4KICAtIFRoZSBvdGhlciBzb3VyY2UgaGFkIGEgZnJlcXVlbmN5IG9mZiB0aGlzIHJlc29uYW5jZSBmcmVxdWVuY3kuCgogIC0gVGhlIGNvbmNlbnRyYXRpb24gb2YgbWVyY3VyeSBjYW4gYmUgZGVyaXZlZCBmcm9tIGEgcmVncmVzc2lvbiBtb2RlbCBvZiB0aGUgbG9ncmF0aW8gaW4gZnVuY3Rpb24gb2YgIHRoZSByYW5nZSBmb3IgZWFjaCByYW5nZSB4LgoKYGBge3J9CmxpYnJhcnkoIlNlbWlQYXIiKQpkYXRhKGxpZGFyKQpwTGlkYXIgPC0gbGlkYXIgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcmFuZ2UsIHkgPSBsb2dyYXRpbykpICsKICBnZW9tX3BvaW50KCkgKwogIHhsYWIoInJhbmdlIChtKSIpCgpwTGlkYXIgKwogIGdlb21fc21vb3RoKCkKYGBgCgotIFRoZSBkYXRhIGlzIG5vbi1saW5lYXIKLSBMaW5lYXIgcmVncmVzc2lvbiB3aWxsIG5vdCB3b3JrIQotIFRoZSBkYXRhIHNob3dzIGEgc21vb3RoIHJlbGF0aW9uIGJldHdlZW4gdGhlIGxvZ3JhdGlvIGFuZCB0aGUgcmFuZ2UKCiMjIEJhc2lzIGV4cGFuc2lvbgoKXFt5X2k9Zih4X2kpK1xlcHNpbG9uX2ksXF0Kd2l0aApcW2YoeCk9XHN1bVxsaW1pdHNfe2s9MX1eSyBcdGhldGFfayBiX2soeClcXQoKLSAgU2VsZWN0IHNldCBvZiBiYXNpcyBmdW5jdGlvbnMgJGJfayh4KSQKLSAgU2VsZWN0IG51bWJlciBvZiBiYXNpcyBmdW5jdGlvbnMgJEskCi0gIEV4YW1wbGVzCgogICAgLSAgUG9seW5vbWlhbCBtb2RlbDogJHheayQKICAgIC0gIE9ydGhvZ29uYWwgc2VyaWVzOiBGb3VyaWVyLCBMZWdlbmRyZSBwb2x5bm9taWFscywgV2F2ZWxldHMKICAgIC0gIFBvbHlub21pYWwgc3BsaW5lczogJDEsIHgsICh4LXRfbSlfKyQgd2l0aCAkbT0xLCBcbGRvdHMsIEstMiQga25vdHMgJHRfbSQKICAgIC0gIC4uLgoKIyMjIFRydW5jdGF0ZWQgbGluZSBiYXNpcwoKXFt5X2k9Zih4X2kpK1xlcHNpbG9uX2ksXF0KCi0gIE9uZSBvZiB0aGUgbW9zdCBzaW1wbGUgYmFzaXMgZXhwYW5zaW9ucwotICAkZih4X2kpPVxiZXRhXzArXGJldGFfMXhfaStcc3VtXGxpbWl0c197bT0xfV57Sy0yfVx0aGV0YV9tKHhfaS10X20pXyskIHdpdGggJCguKV8rJCB0aGUgb3BlcmF0b3IgdGhhdCB0YWtlcyB0aGUgcG9zaXRpdmUgcGFydC4KLSAgTm90ZSwgdGhhdCBiZXR0ZXIgYmFzaXMgZXhwYW5zaW9ucyBleGlzdCwgd2hpY2ggYXJlIG9ydGhvZ29uYWwsIGNvbXB1dGF0aW9uYWwgbW9yZSBzdGFibGUgYW5kL29yIGNvbnRpbnVvdXMgZGVyaXZhdGl2ZSBiZXlvbmQgZmlyc3Qgb3JkZXIKLSAgV2Ugd2lsbCB1c2UgdGhpcyBiYXNpcyBmb3IgZGlkYWN0aWNhbCBwdXJwb3NlcwotIFdlIGNhbiB1c2UgT0xTIHRvIGZpdCB5IHcuci50LiB0aGUgYmFzaXMuCgpgYGB7cn0Ka25vdHMgPC0gc2VxKDQwMCw3MDAsMTIuNSkKCmJhc2lzIDwtIHNhcHBseShrbm90cywKICBmdW5jdGlvbihrLHkpICh5LWspKih5PmspLAogIHk9IGxpZGFyICU+JSBwdWxsKHJhbmdlKQogICkKCmJhc2lzRXhwIDwtIGNiaW5kKDEsIHJhbmdlID0gbGlkYXIgJT4lIHB1bGwocmFuZ2UpLCBiYXNpcykKCnNwbGluZUZpdExzIDwtIGxtKGxvZ3JhdGlvIH4gLTEgKyBiYXNpc0V4cCwgbGlkYXIpCgpwQmFzaXMgPC0gYmFzaXNFeHBbLC0xXSAlPiUKICBkYXRhLmZyYW1lICU+JQogIGdhdGhlcigiYmFzaXMiLCJ2YWx1ZXMiLC0xKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSByYW5nZSwgeSA9IHZhbHVlcywgY29sb3IgPSBiYXNpcykpICsKICBnZW9tX2xpbmUoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKwogIHlsYWIoImJhc2lzIikKCmdyaWQuYXJyYW5nZSgKICBwTGlkYXIgKwogICAgZ2VvbV9saW5lKGFlcyh4ID0gbGlkYXIkcmFuZ2UsIHkgPSBzcGxpbmVGaXRMcyRmaXR0ZWQpLCBsd2QgPSAyKSwKICBwQmFzaXMsCiAgbmNvbD0xKQpgYGAKCi0gTm90ZSwgdGhhdCB0aGUgbW9kZWwgaXMgb3ZlcmZpdHRpbmchCi0gVGhlIGZpdCBpcyB2ZXJ5IHdpZ2dseSBhbmQgaXMgdHVuZWQgdG9vIG11Y2ggdG8gdGhlIGRhdGEuCi0gVGhlIGZpdCBoYXMgYSBsYXJnZSB2YXJpYW5jZSBhbmQgbG93IGJpYXMuCi0gSXQgd2lsbCB0aGVyZWZvcmUgbm90IGdlbmVyYWxpc2Ugd2VsbCB0byBwcmVkaWN0IHRoZSBsb2dyYXRpbyBvZiBmdXR1cmUgb2JzZXJ2YXRpb25zLgoKIyMjIyBTb2x1dGlvbiBmb3Igb3ZlcmZpdHRpbmc/CgotIFdlIGNvdWxkIHBlcmZvcm0gbW9kZWwgc2VsZWN0aW9uIG9uIHRoZSBiYXNpcyB0byBzZWxlY3QgdGhlIGltcG9ydGFudCBiYXNpcyBmdW5jdGlvbnMgdG8gbW9kZWwgdGhlIHNpZ25hbC4gQnV0LCB0aGlzIHdpbGwgaGF2ZSB0aGUgdW5kZXNpcmVkIHByb3BlcnR5IHRoYXQgdGhlIGZpdCB3aWxsIG5vIGxvbmdlciBiZSBzbW9vdGguCgotIFdlIGNhbiBhbHNvIGFkb3B0IGEgcmlkZ2UgcGVuYWx0eSEKLSBIb3dldmVyLCB3ZSBkbyBub3Qgd2FudCB0byBwZW5hbGlzZSB0aGUgaW50ZXJjZXB0IGFuZCB0aGUgbGluZWFyIHRlcm0uCi0gUmlkZ2UgY3JpdGVyaW9uCgpcW1xWZXJ0XG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX1cVmVydF4yK1xsYW1iZGFcYm9sZHN5bWJvbHtcYmV0YX1eVFxtYXRoYmZ7RH1cYm9sZHN5bWJvbHtcYmV0YX0KXF0KCldpdGggJFxtYXRoYmZ7RH0kIHdpdGggZGltZW5zaW9ucyAoSyxLKTogJFxtYXRoYmZ7RH09XGxlZnRbXGJlZ2lue2FycmF5fXtjY31cbWF0aGJmezB9X3syXHRpbWVzMn0mIFxtYXRoYmZ7MH1fezJcdGltZXMgSy0yfVxcClxtYXRoYmZ7MH1fe0stMlx0aW1lczJ9JlxtYXRoYmZ7SX1fe0stMlx0aW1lcyBLLTJ9XGVuZHthcnJheX1ccmlnaHRdJAoKLSBIZXJlIHdlIHdpbGwgc2V0IHRoZSBwZW5hbHR5IGF0IDkwMC4KCmBgYHtyfQpEIDwtIGRpYWcobmNvbChiYXNpc0V4cCkpCkRbMToyLDE6Ml0gPC0gMApsYW1iZGEgPC0gOTAwCmJldGFSaWRnZSA8LSBzb2x2ZSh0KGJhc2lzRXhwKSUqJWJhc2lzRXhwKyhsYW1iZGEqRCkpJSoldChiYXNpc0V4cCklKiVsaWRhciRsb2dyYXRpbwpncmlkLmFycmFuZ2UoCiAgcExpZGFyICsKICAgIGdlb21fbGluZShhZXMoeCA9IGxpZGFyJHJhbmdlLCB5ID0gYyhiYXNpc0V4cCAlKiUgYmV0YVJpZGdlKSksIGx3ZCA9IDIpLAogIHBCYXNpcywKICBuY29sPTEpCmBgYAoKSG93IGRvIHdlIGNob29zZSAkXGxhbWJkYSQ/CgotLS0KCiMgRXZhbHVhdGlvbiBvZiBQcmVkaWN0aW9uIE1vZGVscwoKClByZWRpY3Rpb25zIGFyZSBjYWxjdWxhdGVkIHdpdGggdGhlIGZpdHRlZCBtb2RlbAogXFsKICAgXGhhdHtZfShcbWF0aGJme3h9KSA9IFxoYXR7bX0oXG1hdGhiZnt4fSk9XG1hdGhiZnt4fV5UXGhhdHtcYmV0YX0KIFxdCiB3aGVuIGZvY3Vzc2luZyBvbiBwcmVkaWN0aW9uLCB3ZSB3YW50IHRoZSBwcmVkaWN0aW9uIGVycm9yIHRvIGJlIGFzIHNtYWxsIGFzIHBvc3NpYmxlLgoKVGhlICoqcHJlZGljdGlvbiBlcnJvcioqIGZvciBhIHByZWRpY3Rpb24gYXQgY292YXJpYXRlIHBhdHRlcm4gJFxtYXRoYmZ7eH0kIGlzIGdpdmVuIGJ5CiAgXFsKICAgICBcaGF0e1l9KFxtYXRoYmZ7eH0pIC0gWV4qLAogIFxdCndoZXJlCgotICRcaGF0e1l9KFxtYXRoYmZ7eH0pPVxtYXRoYmZ7eH1eVFxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fSQgaXMgdGhlIHByZWRpY3Rpb24gYXQgJFxtYXRoYmZ7eH0kCgotICAkWV4qJCBpcyBhbiBvdXRjb21lIGF0IGNvdmFyaWF0ZSBwYXR0ZXJuICRcbWF0aGJme3h9JAoKUHJlZGljdGlvbiBpcyB0eXBpY2FsbHkgdXNlZCB0byBwcmVkaWN0IGFuIG91dGNvbWUgYmVmb3JlIGl0IGlzIG9ic2VydmVkLgoKLSBIZW5jZSwgdGhlIG91dGNvbWUgJFleKiQgaXMgbm90IG9ic2VydmVkIHlldCwgYW5kCi0gdGhlIHByZWRpY3Rpb24gZXJyb3IgY2Fubm90IGJlIGNvbXB1dGVkLgoKLS0tCgotIFJlY2FsbCB0aGF0IHRoZSBwcmVkaWN0aW9uIG1vZGVsICRcaGF0e1l9KFxtYXRoYmZ7eH0pJCBpcyBlc3RpbWF0ZWQgYnkgdXNpbmcgZGF0YSBpbiB0aGUgdHJhaW5pbmcgZGF0YSBzZXQgJChcbWF0aGJme1h9LFxtYXRoYmZ7WX0pJCwgYW5kCi0gdGhhdCB0aGUgb3V0Y29tZSAkWV4qJCBpcyBhbiBvdXRjb21lIGF0ICRcbWF0aGJme3h9JCB3aGljaCBpcyBhc3N1bWVkIHRvIGJlIGluZGVwZW5kZW50IG9mIHRoZSB0cmFpbmluZyBkYXRhLgoKLSBHb2FsIGlzIHRvIHVzZSBwcmVkaWN0aW9uIG1vZGVsIGZvciBwcmVkaWN0aW5nIGEgZnV0dXJlIG9ic2VydmF0aW9uICgkWV4qJCksIGkuZS4gYW4gb2JzZXJ2YXRpb24gdGhhdCBzdGlsbCBoYXMgdG8gYmUgcmVhbGlzZWQvb2JzZXJ2ZWQgKG90aGVyd2lzZSBwcmVkaWN0aW9uIHNlZW1zIHJhdGhlciB1c2VsZXNzKS4KCi0gSGVuY2UsICRZXiokIGNhbiBuZXZlciBiZSBwYXJ0IG9mIHRoZSB0cmFpbmluZyBkYXRhIHNldC4KCi0tLQoKSGVyZSB3ZSBwcm92aWRlIGRlZmluaXRpb25zIGFuZCB3ZSBzaG93IGhvdyB0aGUgcHJlZGljdGlvbiBwZXJmb3JtYW5jZSBvZiBhIHByZWRpY3Rpb24gbW9kZWwgY2FuIGJlIGV2YWx1YXRlZCBmcm9tIGRhdGEuCgpMZXQgJHtcY2Fse1R9fT0oXG1hdGhiZntZfSxcbWF0aGJme1h9KSQgZGVub3RlIHRoZSB0cmFpbmluZyBkYXRhLCBmcm9tIHdoaWNoIHRoZSBwcmVkaWN0aW9uIG1vZGVsICRcaGF0e1l9KFxjZG90KSQgaXMgYnVpbGQuIFRoaXMgYnVpbGRpbmcgcHJvY2VzcyB0eXBpY2FsbHkgaW52b2x2ZXMgZmVhdHVyZSBzZWxlY3Rpb24gYW5kIHBhcmFtZXRlciBlc3RpbWF0aW9uLgoKIFdlIHdpbGwgdXNlIGEgbW9yZSBnZW5lcmFsIG5vdGF0aW9uIGZvciB0aGUgcHJlZGljdGlvbiBtb2RlbDogJFxoYXR7bX0oXG1hdGhiZnt4fSk9XGhhdHtZfShcbWF0aGJme3h9KSQuCgotLS0KCiMjIFRlc3Qgb3IgR2VuZXJhbGlzYXRpb24gRXJyb3IKCiBUaGUgdGVzdCBvciBnZW5lcmFsaXNhdGlvbiBlcnJvciBmb3IgcHJlZGljdGlvbiBtb2RlbCAkXGhhdHttfShcY2RvdCkkIGlzIGdpdmVuIGJ5CiAgXFsKICAgIFx0ZXh0e0Vycn1fe1xjYWx7VH19ID0gXHRleHR7RX1fe1leKixYXip9XGxlZnRbKFxoYXR7bX0oXG1hdGhiZntYfV4qKSAtIFleKileMlxtaWQge1xjYWx7VH19XHJpZ2h0XQogIFxdCiAgd2hlcmUgJChZXiosWF4qKSQgaXMgaW5kZXBlbmRlbnQgb2YgdGhlIHRyYWluaW5nIGRhdGEuCgotLS0KCi0gTm90ZSB0aGF0IHRoZSB0ZXN0IGVycm9yIGlzIGNvbmRpdGlvbmFsIG9uIHRoZSB0cmFpbmluZyBkYXRhICR7XGNhbHtUfX0kLgotIEhlbmNlLCB0aGUgdGVzdCBlcnJvciBldmFsdWF0ZXMgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBzaW5nbGUgbW9kZWwgYnVpbGQgZnJvbSB0aGUgb2JzZXJ2ZWQgdHJhaW5pbmcgZGF0YS4KLSBUaGlzIGlzIHRoZSB1bHRpbWF0ZSB0YXJnZXQgb2YgdGhlIG1vZGVsIGFzc2Vzc21lbnQsIGJlY2F1c2UgaXQgaXMgZXhhY3RseSB0aGlzIHByZWRpY3Rpb24gbW9kZWwgdGhhdCB3aWxsIGJlIHVzZWQgaW4gcHJhY3RpY2UgYW5kIGFwcGxpZWQgdG8gZnV0dXJlIHByZWRpY3RvcnMgJFxtYXRoYmZ7WH1eKiQgdG8gcHJlZGljdCAkWV4qJC4KLSBUaGUgdGVzdCBlcnJvciBpcyBkZWZpbmVkIGFzIGFuIGF2ZXJhZ2Ugb3ZlciBhbGwgc3VjaCBmdXR1cmUgb2JzZXJ2YXRpb25zICQoWV4qLFxtYXRoYmZ7WH1eKikkLgoKLS0tCgojIyBDb25kaXRpb25hbCB0ZXN0IGVycm9yCgpTb21ldGltZXMgdGhlIGNvbmRpdGlvbmFsIHRlc3QgZXJyb3IgaXMgdXNlZDoKClRoZSBjb25kaXRpb25hbCB0ZXN0IGVycm9yIGluICRcbWF0aGJme3h9JCBmb3IgcHJlZGljdGlvbiBtb2RlbCAkXGhhdHttfShcbWF0aGJme3h9KSQgaXMgZ2l2ZW4gYnkKIFxbCiAgIFx0ZXh0e0Vycn1fe1xjYWx7VH19KFxtYXRoYmZ7eH0pID0gXHRleHR7RX1fe1leKn1cbGVmdFsoXGhhdHttfShcbWF0aGJme3h9KSAtIFleKileMlxtaWQge1xjYWx7VH19LCBcbWF0aGJme3h9XHJpZ2h0XQogXF0KIHdoZXJlICRZXiokIGlzIGFuIG91dGNvbWUgYXQgcHJlZGljdG9yICRcbWF0aGJme3h9JCwgaW5kZXBlbmRlbnQgb2YgdGhlIHRyYWluaW5nIGRhdGEuCgogSGVuY2UsCiBcWwogICBcdGV4dHtFcnJ9X3tcY2Fse1R9fSA9IFx0ZXh0e0V9X3tYXip9XGxlZnRbXHRleHR7RXJyfV97XGNhbHtUfX0oXG1hdGhiZntYfV4qKVxyaWdodF0uCiBcXQoKQSBjbG9zZWx5IHJlbGF0ZWQgZXJyb3IgaXMgdGhlICoqaW5zYW1wbGUgZXJyb3IqKi4KCi0tLQoKIyMgSW5zYW1wbGUgRXJyb3IKClRoZSBpbnNhbXBsZSBlcnJvciBmb3IgcHJlZGljdGlvbiBtb2RlbCAkXGhhdHttfShcbWF0aGJme3h9KSQgaXMgZ2l2ZW4gYnkKIFxbCiAgIFx0ZXh0e0Vycn1fe1x0ZXh0e2lufSBcY2Fse1R9fSA9IFxmcmFjezF9e259XHN1bV97aT0xfV5uIFx0ZXh0e0Vycn1fe1xjYWx7VH19KFxtYXRoYmZ7eH1faSksCiBcXQoKaS5lLiB0aGUgaW5zYW1wbGUgZXJyb3IgaXMgdGhlIHNhbXBsZSBhdmVyYWdlIG9mIHRoZSBjb25kaXRpb25hbCB0ZXN0IGVycm9ycyBldmFsdWF0ZWQgaW4gdGhlICRuJCB0cmFpbmluZyBkYXRhc2V0IHByZWRpY3RvcnMgJFxtYXRoYmZ7eH1faSQuCgpTaW5jZSAkXHRleHR7RXJyfV97XGNhbHtUfX0kIGlzIGFuIGF2ZXJhZ2Ugb3ZlciBhbGwgJFxtYXRoYmZ7WH0kLCBldmVuIG92ZXIgdGhvc2UgcHJlZGljdG9ycyBub3Qgb2JzZXJ2ZWQgaW4gdGhlIHRyYWluaW5nIGRhdGFzZXQsIGl0IGlzIHNvbWV0aW1lcyByZWZlcnJlZCB0byBhcyB0aGUgKipvdXRzYW1wbGUgZXJyb3IqKi4KCi0tLQoKIyMgRXN0aW1hdGlvbiBvZiB0aGUgaW5zYW1wbGUgZXJyb3IKCldlIHN0YXJ0IHdpdGggaW50cm9kdWNpbmcgdGhlIHRyYWluaW5nIGVycm9yIHJhdGUsIHdoaWNoIGlzIGNsb3NlbHkgcmVsYXRlZCB0byB0aGUgTVNFIGluIGxpbmVhciBtb2RlbHMuCgojIyMgVHJhaW5pbmcgZXJyb3IKCiBUaGUgdHJhaW5pbmcgZXJyb3IgaXMgZ2l2ZW4gYnkKIFxbCiAgIFxvdmVybGluZXtcdGV4dHtlcnJ9fSA9IFxmcmFjezF9e259XHN1bV97aT0xfV5uIChZX2kgLSBcaGF0e219KFxtYXRoYmZ7eH1faSkpXjIgLAogXF0KIHdoZXJlIHRoZSAkKFlfaSxcbWF0aGJme3h9X2kpJCBmcm9tIHRoZSB0cmFpbmluZyBkYXRhc2V0IHdoaWNoIGlzIGFsc28gdXNlZCBmb3IgdGhlIGNhbGN1bGF0aW9uIG9mICRcaGF0e219JC4KCi0gVGhlIHRyYWluaW5nIGVycm9yIGlzIGFuIG92ZXJseSBvcHRpbWlzdGljIGVzdGltYXRlIG9mIHRoZSB0ZXN0IGVycm9yICRcdGV4dHtFcnJ9X3tcY2Fse1R9fSQuCgotIFRoZSB0cmFpbmluZyBlcnJvciB3aWxsIG5ldmVyIGluY3JlYXNlcyB3aGVuIHRoZSBtb2RlbCBiZWNvbWVzIG1vcmUgY29tcGxleC4gJFxsb25ncmlnaHRhcnJvdyQgY2Fubm90IGJlIHVzZWQgZGlyZWN0bHkgYXMgYSBtb2RlbCBzZWxlY3Rpb24gY3JpdGVyaW9uLgoKSW5kZWVkLCBtb2RlbCBwYXJhbWV0ZXJzIGFyZSBvZnRlbiBlc3RpbWF0ZWQgYnkgbWluaW1pc2luZyB0aGUgdHJhaW5pbmcgZXJyb3IgKGNmci4gU1NFKS4KCi0gSGVuY2UgdGhlIGZpdHRlZCBtb2RlbCBhZGFwdHMgdG8gdGhlIHRyYWluaW5nIGRhdGEsIGFuZAotIHRyYWluaW5nIGVycm9yIHdpbGwgYmUgYW4gb3Zlcmx5IG9wdGltaXN0aWMgZXN0aW1hdGUgb2YgdGhlIHRlc3QgZXJyb3IgJFx0ZXh0e0Vycn1fe1xjYWx7VH19JC4KCi0tLQoKSXQgY2FuIGJlIHNob3duIHRoYXQgdGhlIHRyYWluaW5nIGVycm9yIGlzIHJlbGF0ZWQgdG8gdGhlIGluc2FtcGxlIHRlc3QgZXJyb3IgdmlhCgpcWwpcdGV4dHtFfV9cbWF0aGJme1l9ClxsZWZ0W1x0ZXh0e0Vycn1fe1x0ZXh0e2lufXtcY2Fse1R9fX1ccmlnaHRdID0gXHRleHR7RX1fXG1hdGhiZntZfVxsZWZ0W1xvdmVybGluZXtcdGV4dHtlcnJ9fVxyaWdodF0gKyBcZnJhY3syfXtufVxzdW1fe2k9MX1ebiBcdGV4dHtjb3Z9X1xtYXRoYmZ7WX1cbGVmdFtcaGF0e219KFxtYXRoYmZ7eH1faSksWV9pXHJpZ2h0XSwKXF0KCk5vdGUsIHRoYXQgZm9yIGxpbmVhciBtb2RlbHMKXFsgXGhhdHttfShcbWF0aGJme3h9X2kpID0gXG1hdGhiZntYfVxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fT0gXG1hdGhiZntYfShcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XG1hdGhiZntYfV5UXG1hdGhiZntZfSA9IFxtYXRoYmZ7SFl9ClxdCndpdGgKCi0gJFxtYXRoYmZ7SH0kIHRoZSBoYXQgbWF0cml4IGFuZAotIGFsbCAkWV9pJCBhcmUgYXNzdW1lZCB0byBiZSBpbmRlcGVuZGVudGx5IGRpc3RyaWJ1dGVkICAkTihcbWF0aGJme1h9XGJvbGRzeW1ib2x7XGJldGF9LFxzaWdtYV4yKSQKCkhlbmNlLCBmb3IgbGluZWFyIG1vZGVscyB3aXRoIGluZGVwZW5kZW50IG9ic2VydmF0aW9ucwoKXGJlZ2lue2VxbmFycmF5fQpcdGV4dHtjb3Z9X1xtYXRoYmZ7WX1cbGVmdFtcaGF0e219KFxtYXRoYmZ7eH1faSksWV9pKVxyaWdodF0gJj0mClx0ZXh0e2Nvdn1fXG1hdGhiZntZfVxsZWZ0W1xtYXRoYmZ7SH1fe2l9XlRcbWF0aGJme1l9LFlfaSlccmlnaHRdXFwKJj0mIFx0ZXh0e2Nvdn1fXG1hdGhiZntZfVxsZWZ0W2hfe2lpfSBZX2ksWV9pXHJpZ2h0XVxcCiY9JiBoX3tpaX0gXHRleHR7Y292fV9cbWF0aGJme1l9XGxlZnRbWV9pLFlfaVxyaWdodF1cXAomPSYgaF97aWl9IFxzaWdtYV4yXFwKXGVuZHtlcW5hcnJheX0KCkFuZCB3ZSBjYW4gdGh1cyBlc3RpbWF0ZSB0aGUgaW5zYW1wbGUgZXJyb3IgYnkgTWFsbG93J3MgJENfcCQKClxiZWdpbntlcW5hcnJheX0KQ19wICY9JiBcb3ZlcmxpbmV7XHRleHR7ZXJyfX0gKyBcZnJhY3syXHNpZ21hXjJ9e259XHRleHR7dHJ9KFxtYXRoYmZ7SH0pXFwKJj0mIFxvdmVybGluZXtcdGV4dHtlcnJ9fSArIFxmcmFjezJcc2lnbWFeMnB9e259ClxlbmR7ZXFuYXJyYXl9Cgp3aXRoICRwJCB0aGUgbnVtYmVyIG9mIHByZWRpY3RvcnMuCgotIE1hbGxvdydzICRDX3AkIGlzIG9mdGVuIHVzZWQgZm9yIG1vZGVsIHNlbGVjdGlvbi4KLSBOb3RlLCB0aGF0IHdlIGNhbiBhbHNvIGNvbnNpZGVyIGl0IGFzIGEga2luZCBvZiBwZW5hbGl6ZWQgbGVhc3Qgc3F1YXJlczoKClxbCm4gXHRpbWVzIENfcCA9IFxWZXJ0IFxtYXRoYmZ7WX0gLSBcbWF0aGJme1h9XGJvbGRzeW1ib2x7XGJldGF9XFZlcnRfMl4yICsgMlxzaWdtYV4yIFxWZXJ0IFxib2xkc3ltYm9se1xiZXRhfSBcVmVydF8wClxdCndpdGggJExfMCQgbm9ybSAkXFZlcnQgXGJvbGRzeW1ib2x7XGJldGF9IFxWZXJ0XzAgPSBcc3VtX3tqPTF9XnAgXGJldGFfcF4wID0gcCQuCgotLS0KCiMjIEV4cGVjdGVkIHRlc3QgZXJyb3IKClRoZSB0ZXN0IG9yIGdlbmVyYWxpc2F0aW9uIGVycm9yIHdhcyBkZWZpbmVkIGNvbmRpdGlvbmFsbHkgb24gdGhlIHRyYWluaW5nIGRhdGEuIEJ5IGF2ZXJhZ2luZyBvdmVyIHRoZSBkaXN0cmlidXRpb24gb2YgdHJhaW5pbmcgZGF0YXNldHMsIHRoZSBleHBlY3RlZCB0ZXN0IGVycm9yIGFyaXNlcy4KClxiZWdpbntlcW5hcnJheSp9CiAgIFx0ZXh0e0V9X3tcY2Fse1R9fVxsZWZ0W1x0ZXh0e0Vycn1fe3tcY2Fse1R9fX1ccmlnaHRdCiAgICAgJj0mIFx0ZXh0e0V9X3tcY2Fse1R9fVxsZWZ0W1x0ZXh0e0V9X3tZXiosWF4qfVxsZWZ0WyhcaGF0e219KFxtYXRoYmZ7WH1eKikgLSBZXiopXjJcbWlkIHtcY2Fse1R9fVxyaWdodF1ccmlnaHRdIFxcCiAgICAgJj0mIFx0ZXh0e0V9X3tZXiosWF4qLHtcY2Fse1R9fX1cbGVmdFsoXGhhdHttfShcbWF0aGJme1h9XiopIC0gWV4qKV4yXHJpZ2h0XS4KIFxlbmR7ZXFuYXJyYXkqfQoKIC0gVGhlIGV4cGVjdGVkIHRlc3QgZXJyb3IgbWF5IG5vdCBiZSBvZiBkaXJlY3QgaW50ZXJlc3Qgd2hlbiB0aGUgZ29hbCBpcyB0byBhc3Nlc3MgdGhlIHByZWRpY3Rpb24gcGVyZm9ybWFuY2Ugb2YgYSBzaW5nbGUgcHJlZGljdGlvbiBtb2RlbCAkXGhhdHttfShcY2RvdCkkLgoKIC0gVGhlIGV4cGVjdGVkIHRlc3QgZXJyb3IgYXZlcmFnZXMgdGhlIHRlc3QgZXJyb3JzIG9mIGFsbCBtb2RlbHMgdGhhdCBjYW4gYmUgYnVpbGQgZnJvbSBhbGwgdHJhaW5pbmcgZGF0YXNldHMsIGFuZCBoZW5jZSB0aGlzIG1heSBiZSBsZXNzIHJlbGV2YW50IHdoZW4gdGhlIGludGVyZXN0IGlzIGluIGV2YWx1YXRpbmcgb25lIHBhcnRpY3VsYXIgbW9kZWwgdGhhdCByZXN1bHRlZCBmcm9tIGEgc2luZ2xlIG9ic2VydmVkIHRyYWluaW5nIGRhdGFzZXQuCgogLSBBbHNvIG5vdGUgdGhhdCBidWlsZGluZyBhIHByZWRpY3Rpb24gbW9kZWwgaW52b2x2ZXMgYm90aCBwYXJhbWV0ZXIgZXN0aW1hdGlvbiBhbmQgZmVhdHVyZSBzZWxlY3Rpb24uCgogLSBIZW5jZSB0aGUgZXhwZWN0ZWQgdGVzdCBlcnJvciBhbHNvIGV2YWx1YXRlcyB0aGUgZmVhdHVyZSBzZWxlY3Rpb24gcHJvY2VkdXJlIChvbiBhdmVyYWdlKS4KCiAtIElmIHRoZSBleHBlY3RlZCB0ZXN0IGVycm9yIGlzIHNtYWxsLCBpdCBpcyBhbiBpbmRpY2F0aW9uIHRoYXQgdGhlIG1vZGVsIGJ1aWxkaW5nIHByb2Nlc3MgZ2l2ZXMgZ29vZCBwcmVkaWN0aW9ucyBmb3IgZnV0dXJlIG9ic2VydmF0aW9ucyAkKFleKixcbWF0aGJme1h9XiopJCBvbiBhdmVyYWdlLgoKIyMjIEVzdGltYXRpbmcgdGhlIEV4cGVjdGVkIHRlc3QgZXJyb3IKClRoZSBleHBlY3RlZCB0ZXN0IGVycm9yIG1heSBiZSBlc3RpbWF0ZWQgYnkgY3Jvc3MgdmFsaWRhdGlvbiAoQ1YpLgoKIyMjIyBMZWF2ZSBvbmUgb3V0IGNyb3NzIHZhbGlkYXRpb24gKExPT0NWKX0KClRoZSBMT09DViBlc3RpbWF0b3Igb2YgdGhlIGV4cGVjdGVkIHRlc3QgZXJyb3IgKG9yIGV4cGVjdGVkIG91dHNhbXBsZSBlcnJvcikgaXMgZ2l2ZW4gYnkKICBcWwogICAgIFx0ZXh0e0NWfSA9IFxmcmFjezF9e259IFxzdW1fe2k9MX1ebiBcbGVmdChZX2kgLSBcaGF0e219XnstaX0oXG1hdGhiZnt4fV9pKVxyaWdodCleMiAsCiAgXF0Kd2hlcmUKCi0gdGhlICQoWV9pLFxtYXRoYmZ7eH1faSkkIGZvcm0gdGhlIHRyYWluaW5nIGRhdGFzZXQKLSAgICRcaGF0e219XnstaX0kIGlzIHRoZSBmaXR0ZWQgbW9kZWwgYmFzZWQgb24gYWxsIHRyYWluaW5nIGRhdGEsIGV4Y2VwdCBvYnNlcnZhdGlvbiAkaSQKLSAgICRcaGF0e219XnstaX0oXG1hdGhiZnt4fV9pKSQgaXMgdGhlIHByZWRpY3Rpb24gYXQgJFxtYXRoYmZ7eH1faSQsIHdoaWNoIGlzIHRoZSBvYnNlcnZhdGlvbiBsZWZ0IG91dCB0aGUgdHJhaW5pbmcgZGF0YSBiZWZvcmUgYnVpbGRpbmcgbW9kZWwgJG0kLgoKU29tZSByYXRpb25hbGUgYXMgdG8gd2h5IExPT0NWIG9mZmVycyBhIGdvb2QgZXN0aW1hdG9yIG9mIHRoZSBvdXRzYW1wbGUgZXJyb3I6CgotIHRoZSBwcmVkaWN0aW9uIGVycm9yICRZXiotXGhhdHttfShcbWF0aGJme3h9KSQgaXMgbWltaWNrZWQgYnkgbm90IHVzaW5nIG9uZSBvZiB0aGUgdHJhaW5pbmcgb3V0Y29tZXMgJFlfaSQgZm9yIHRoZSBlc3RpbWF0aW9uIG9mIHRoZSBtb2RlbCBzbyB0aGF0IHRoaXMgJFlfaSQgcGxheXMgdGhlIHJvbGUgb2YgJFleKiQsIGFuZCwgY29uc2VxdWVudGx5LCB0aGUgZml0dGVkIG1vZGVsICRcaGF0e219XnstaX0kIGlzIGluZGVwZW5kZW50IG9mICRZX2kkCgogLSB0aGUgc3VtIGluICRDViQgaXMgb3ZlciBhbGwgJFxtYXRoYmZ7eH1faSQgaW4gdGhlIHRyYWluaW5nIGRhdGFzZXQsIGJ1dCBlYWNoIHRlcm0gJFxtYXRoYmZ7eH1faSQgd2FzIGxlZnQgb3V0IG9uY2UgZm9yIHRoZSBjYWxjdWxhdGlvbiBvZiAkXGhhdHttfV57LWl9JC4gSGVuY2UsICRcaGF0e219XnstaX0oXG1hdGhiZnt4fV9pKSQgbWltaWNzIGFuIG91dHNhbXBsZSBwcmVkaWN0aW9uLgoKIC0gdGhlIHN1bSBpbiBDViBpcyBvdmVyICRuJCBkaWZmZXJlbnQgdHJhaW5pbmcgZGF0YXNldHMgKGVhY2ggb25lIHdpdGggYSBkaWZmZXJlbnQgb2JzZXJ2YXRpb24gcmVtb3ZlZCksIGFuZCBoZW5jZSBDViBpcyBhbiBlc3RpbWF0b3Igb2YgdGhlICpleHBlY3RlZCogdGVzdCBlcnJvci4KCiAtIEZvciBsaW5lYXIgbW9kZWxzIHRoZSBMT09DViBjYW4gYmUgcmVhZGlseSBvYnRhaW5lZCBmcm9tIHRoZSBmaXR0ZWQgbW9kZWw6IGkuZS4KCiBcW1x0ZXh0e0NWfSA9IFxmcmFjezF9e259XHN1bVxsaW1pdHNfe2k9MX1ebiBcZnJhY3tlX2leMn17KDEtaF97aWl9KV4yfVxdCgogd2l0aCAkZV9pJCB0aGUgcmVzaWR1YWxzIGZyb20gdGhlIG1vZGVsIHRoYXQgaXMgZml0dGVkIGJhc2VkIG9uIGFsbCB0cmFpbmluZyBkYXRhLgoKLS0tCgpBbiBhbHRlcm5hdGl2ZSB0byBMT09DViBpcyB0aGUgJGskLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBwcm9jZWR1cmUuIEl0IGFsc28gZ2l2ZXMgYW4gZXN0aW1hdGUgb2YgdGhlIGV4cGVjdGVkIG91dHNhbXBsZSBlcnJvci4KCiMjIyMgJGskLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbgoKLSAgUmFuZG9tbHkgZGl2aWRlIHRoZSB0cmFpbmluZyBkYXRhc2V0IGludG8gJGskIGFwcHJveGltYXRlbHkgZXF1YWwgc3Vic2V0cyAuIExldCAkU19qJCBkZW5vdGUgdGhlIGluZGV4IHNldCBvZiB0aGUgJGokdGggc3Vic2V0IChyZWZlcnJlZCB0byBhcyBhICoqZm9sZCoqKS4gTGV0ICRuX2okIGRlbm90ZSB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBmb2xkICRqJC4KCi0gVGhlICRrJC1mb2xkIGNyb3NzIHZhbGlkYXRpb24gZXN0aW1hdG9yIG9mIHRoZSBleHBlY3RlZCBvdXRzYW1wbGUgZXJyb3IgaXMgZ2l2ZW4gYnkKIFxbCiAgICAgXHRleHR7Q1Z9X2sgPSBcZnJhY3sxfXtrfVxzdW1fe2o9MX1eayBcZnJhY3sxfXtuX2p9IFxzdW1fe2lcaW4gU19qfSBcbGVmdChZX2kgLSBcaGF0e219XnstU19qfShcbWF0aGJme3h9X2kpXHJpZ2h0KV4yCiBcXQogd2hlcmUgJFxoYXR7bX1eey1TX2p9JCBpcyB0aGUgbW9kZWwgZml0dGVkIHVzaW5nIGFsbCB0cmFpbmluZyBkYXRhLCBleGNlcHQgb2JzZXJ2YXRpb25zIGluIGZvbGQgJGokIChpLmUuIG9ic2VydmF0aW9ucyAkaSBcaW4gU19qJCkuCgotLS0KClRoZSBjcm9zcyB2YWxpZGF0aW9uIGVzdGltYXRvcnMgb2YgdGhlIGV4cGVjdGVkIG91dHNhbXBsZSBlcnJvciBhcmUgbmVhcmx5IHVuYmlhc2VkLiBPbmUgYXJndW1lbnQgdGhhdCBoZWxwcyB0byB1bmRlcnN0YW5kIHdoZXJlIHRoZSBiaWFzIGNvbWVzIGZyb20gaXMgdGhlIGZhY3QgdGhhdCBlLmcuIGluIGRlIExPT0NWIGVzdGltYXRvciB0aGUgbW9kZWwgaXMgZml0IG9uIG9ubHkgJG4tMSQgb2JzZXJ2YXRpb25zLCB3aGVyZWFzIHdlIGFyZSBhaW1pbmcgYXQgZXN0aW1hdGluZyB0aGUgb3V0c2FtcGxlIGVycm9yIG9mIGEgbW9kZWwgZml0IG9uIGFsbCAkbiQgdHJhaW5pbmcgb2JzZXJ2YXRpb25zLiBGb3J0dW5hdGVseSwgdGhlIGJpYXMgaXMgb2Z0ZW4gc21hbGwgYW5kIGlzIGluIHByYWN0aWNlIGhhcmRseSBhIGNvbmNlcm4uCgokayQtZm9sZCBDViBpcyBjb21wdXRhdGlvbmFsbHkgbW9yZSBjb21wbGV4LgoKU2luY2UgQ1YgYW5kIENWJF9rJCBhcmUgZXN0aW1hdG9ycywgdGhleSBhbHNvIHNob3cgc2FtcGxpbmcgdmFyaWFiaWxpdHkuIFN0YW5kYXJkIGVycm9ycyBvZiB0aGUgQ1Ygb3IgQ1YkX2skIGNhbiBiZSBjb21wdXRlZC4gV2UgZG9uJ3Qgc2hvdyB0aGUgZGV0YWlscywgYnV0IGluIHRoZSBleGFtcGxlIHRoaXMgaXMgaWxsdXN0cmF0ZWQuCgojIyMgQmlhcyBWYXJpYW5jZSB0cmFkZS1vZmYKCkZvciB0aGUgZXhwZWN0ZWQgY29uZGl0aW9uYWwgdGVzdCBlcnJvciBpbiAkXG1hdGhiZnt4fSQsIGl0IGhvbGRzIHRoYXQKXGJlZ2lue2VxbmFycmF5Kn0KICBcdGV4dHtFfV97XGNhbHtUfX1cbGVmdFtcdGV4dHtFcnJ9X3tcY2Fse1R9fShcbWF0aGJme3h9KVxyaWdodF0KICAgICY9JiBcdGV4dHtFfV97WV4qLHtcY2Fse1R9fX1cbGVmdFsoXGhhdHttfShcbWF0aGJme3h9KS1ZXiopXjIgXG1pZCBcbWF0aGJme3h9XHJpZ2h0XSBcXAogICAgJj0mICBcdGV4dHt2YXJ9X3tcbWF0aGJme1l9fVxsZWZ0W1xoYXR7WX0oXG1hdGhiZnt4fSlcbWlkIFxtYXRoYmZ7eH1ccmlnaHRdICsoXG11KFxtYXRoYmZ7eH0pLVxtdV4qKFxtYXRoYmZ7eH0pKV4yK1x0ZXh0e3Zhcn1fe1leKn1cbGVmdFtZXipcbWlkIFxtYXRoYmZ7eH1ccmlnaHRdClxlbmR7ZXFuYXJyYXkqfQp3aGVyZSAkXG11KFxtYXRoYmZ7eH0pID0gXHRleHR7RX1fe1xtYXRoYmZ7WX19XGxlZnRbXGhhdHtZfShcbWF0aGJme3h9KVxtaWQgXG1hdGhiZnt4fVxyaWdodF0gXHRleHR7IGFuZCB9IFxtdV4qKFxtYXRoYmZ7eH0pPVx0ZXh0e0V9X3tZXip9XGxlZnRbWV4qXG1pZCBcbWF0aGJme3h9XHJpZ2h0XSQuCgotICoqYmlhcyoqOiAkXHRleHR7Ymlhc30oXG1hdGhiZnt4fSk9XG11KFxtYXRoYmZ7eH0pLVxtdV4qKFxtYXRoYmZ7eH0pJAoKLSAkXHRleHR7dmFyfV97WV4qfVxsZWZ0W1leKlxtaWQgXG1hdGhiZnt4fVxyaWdodF0kIGRvZXMgbm90IGRlcGVuZCBvbiB0aGUgbW9kZWwsIGFuZCBpcyByZWZlcnJlZCB0byBhcyB0aGUgKippcnJlZHVjaWJsZSB2YXJpYW5jZSoqLgoKLS0tCgpUaGUgaW1wb3J0YW5jZSBvZiB0aGUgYmlhcy12YXJpYW5jZSB0cmFkZS1vZmYgY2FuIGJlIHNlZW4gZnJvbSBhIG1vZGVsIHNlbGVjdGlvbiBwZXJzcGVjdGl2ZS4gV2hlbiB3ZSBhZ3JlZSB0aGF0IGEgZ29vZCBtb2RlbCBpcyBhIG1vZGVsIHRoYXQgaGFzIGEgc21hbGwgZXhwZWN0ZWQgY29uZGl0aW9uYWwgdGVzdCBlcnJvciBhdCBzb21lIHBvaW50ICRcbWF0aGJme3h9JCwgdGhlbiB0aGUgYmlhcy12YXJpYW5jZSB0cmFkZS1vZmYgc2hvd3MgdXMgdGhhdCBhIG1vZGVsIG1heSBiZSBiaWFzZWQgYXMgbG9uZyBhcyBpdCBoYXMgYSBzbWFsbCB2YXJpYW5jZSB0byBjb21wZW5zYXRlIGZvciB0aGUgYmlhcy4gIEl0IG9mdGVuIGhhcHBlbnMgdGhhdCBhIGJpYXNlZCBtb2RlbCBoYXMgYSBzdWJzdGFudGlhbCBzbWFsbGVyIHZhcmlhbmNlLiBXaGVuIHRoZXNlIHR3byBhcmUgY29tYmluZWQsIGEgc21hbGwgZXhwZWN0ZWQgdGVzdCBlcnJvciBtYXkgb2NjdXIuCgpBbHNvIG5vdGUgdGhhdCB0aGUgbW9kZWwgJG0kIHdoaWNoIGZvcm1zIHRoZSBiYXNpcyBvZiB0aGUgcHJlZGljdGlvbiBtb2RlbCAkXGhhdHttfShcbWF0aGJme3h9KSQgZG9lcyBOT1QgbmVlZCB0byBzYXRpc2Z5ICRtKFxtYXRoYmZ7eH0pPVxtdShcbWF0aGJme3h9KSQgb3IgJG0oXG1hdGhiZnt4fSk9XG11XiooXG1hdGhiZnt4fSkkLiBUaGUgbW9kZWwgJG0kIGlzIGtub3duIGJ5IHRoZSBkYXRhLWFuYWx5c3QgKGl0cyB0aGUgYmFzaXMgb2YgdGhlIHByZWRpY3Rpb24gbW9kZWwpLCB3aGVyZWFzICRcbXUoXG1hdGhiZnt4fSkkIGFuZCAkXG11XiooXG1hdGhiZnt4fSkkIGFyZSBnZW5lcmFsbHkgdW5rbm93biB0byB0aGUgZGF0YS1hbmFseXN0LiBXZSBvbmx5IGhvcGUgdGhhdCAkbSQgc2VydmVzIHdlbGwgYXMgYSBwcmVkaWN0aW9uIG1vZGVsLgoKLS0tCgojIyMgSW4gcHJhY3RpY2UKCldlIHVzZSBjcm9zcyB2YWxpZGF0aW9uIHRvIGVzdGltYXRlIHRoZSBsYW1iZGEgcGVuYWx0eSBmb3IgcGVuYWxpc2VkIHJlZ3Jlc3Npb246CgotIFJpZGdlIFJlZ3Jlc3Npb24KLSBMYXNzbwotIEJ1aWxkIG1vZGVscywgZS5nLiBzZWxlY3QgdGhlIG51bWJlciBvZiBQQ3MgZm9yIFBDQSByZWdyZXNzaW9uCi0gU3BsaW5lcwoKIyMjIFRveGljb2dlbm9taWNzIGV4YW1wbGUKCiMjIyMgTGFzc28KCmBgYHtyfQpzZXQuc2VlZCgxNSkKbGlicmFyeShnbG1uZXQpCm1Ddkxhc3NvIDwtIGN2LmdsbW5ldCgKICB4ID0gdG94RGF0YVssLTFdICU+JQogICAgYXMubWF0cml4LAogIHkgPSB0b3hEYXRhICU+JQogICAgcHVsbChCQSksCiAgYWxwaGEgPSAxKSAgIyBsYXNzbyBhbHBoYT0xCgpwbG90KG1Ddkxhc3NvKQpgYGAKCkRlZmF1bHQgQ1YgcHJvY2VkdXJlIGluIFx0ZXh0c2Z7Y3YuZ2xtbmV0fSBpcyAkaz0xMCQtZm9sZCBDVi4KClRoZSBHcmFwaHMgc2hvd3MKCi0gMTAtZm9sZCBDViBlc3RpbWF0ZXMgb2YgdGhlIGV4dHJhLXNhbXBsZSBlcnJvciBhcyBhIGZ1bmN0aW9uIG9mIHRoZSBsYXNzbyBwZW5hbHR5IHBhcmFtZXRlciAkXGxhbWJkYSQuCi0gZXN0aW1hdGUgcGx1cyBhbmQgbWludXMgb25jZSB0aGUgZXN0aW1hdGVkIHN0YW5kYXJkIGVycm9yIG9mIHRoZSBDViBlc3RpbWF0ZSAoZ3JleSBiYXJzKQotIE9uIHRvcCB0aGUgbnVtYmVyIG9mIG5vbi16ZXJvIHJlZ3Jlc3Npb24gcGFyYW1ldGVyIGVzdGltYXRlcyBhcmUgc2hvd24uCgpUd28gdmVydGljYWwgcmVmZXJlbmNlIGxpbmVzIGFyZSBhZGRlZCB0byB0aGUgZ3JhcGguIFRoZXkgY29ycmVzcG9uZCB0bwoKLSB0aGUgJFxsb2coXGxhbWJkYSkkIHRoYXQgZ2l2ZXMgdGhlIHNtYWxsZXN0IENWIGVzdGltYXRlIG9mIHRoZSBleHRyYS1zYW1wbGUgZXJyb3IsIGFuZAotIHRoZSBsYXJnZXN0ICRcbG9nKFxsYW1iZGEpJCB0aGF0IGdpdmVzIGEgQ1YgZXN0aW1hdGUgb2YgdGhlIGV4dHJhLXNhbXBsZSBlcnJvciB0aGF0IGlzIHdpdGhpbiBvbmUgc3RhbmRhcmQgZXJyb3IgZnJvbSB0aGUgc21hbGxlc3QgZXJyb3IgZXN0aW1hdGUuCi0gVGhlIGxhdHRlciBjaG9pY2Ugb2YgJFxsYW1iZGEkIGhhcyBubyBmaXJtIHRoZW9yZXRpY2FsIGJhc2lzLCBleGNlcHQgdGhhdCBpdCBzb21laG93IGFjY291bnRzIGZvciB0aGUgaW1wcmVjaXNpb24gb2YgdGhlIGVycm9yIGVzdGltYXRlLiBPbmUgY291bGQgbG9vc2VseSBzYXkgdGhhdCB0aGlzICRcZ2FtbWEkIGNvcnJlc3BvbmRzIHRvIHRoZSBzbWFsbGVzdCBtb2RlbCAoaS5lLiBsZWFzdCBudW1iZXIgb2YgcHJlZGljdG9ycykgdGhhdCBnaXZlcyBhbiBlcnJvciB0aGF0IGlzIHdpdGhpbiBtYXJnaW4gb2YgZXJyb3Igb2YgdGhlIGVycm9yIG9mIHRoZSBiZXN0IG1vZGVsLgoKLS0tCgpgYGB7cn0KbUxhc3NvT3B0IDwtIGdsbW5ldCgKICB4ID0gdG94RGF0YVssLTFdICU+JQogICAgYXMubWF0cml4LAogIHkgPSB0b3hEYXRhICU+JQogICAgcHVsbChCQSksCiAgICBhbHBoYSA9IDEsCiAgICBsYW1iZGEgPSBtQ3ZMYXNzbyRsYW1iZGEubWluKQoKc3VtbWFyeShjb2VmKG1MYXNzb09wdCkpCmBgYAoKCldpdGggdGhlIG9wdGltYWwgJFxsYW1iZGEkIChzbWFsbGVzdCBlcnJvciBlc3RpbWF0ZSkgdGhlIG91dHB1dCBzaG93cyB0aGUgYHIgIG5yb3coc3VtbWFyeShjb2VmKG1MYXNzb09wdCkpKWAgbm9uLXplcm8gZXN0aW1hdGVkIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIChzcGFyc2Ugc29sdXRpb24pLgoKLS0tCgpgYGB7cn0KbUxhc3NvMXNlIDwtIGdsbW5ldCgKICB4ID0gdG94RGF0YVssLTFdICU+JQogICAgYXMubWF0cml4LAogICAgeT0gdG94RGF0YSAlPiUKICAgICAgcHVsbChCQSksCiAgICBhbHBoYSA9IDEsCiAgICBsYW1iZGEgPSBtQ3ZMYXNzbyRsYW1iZGEuMXNlKQoKbUxhc3NvMXNlICU+JQogIGNvZWYgJT4lCiAgc3VtbWFyeQpgYGAKClRoaXMgc2hvd3MgdGhlIHNvbHV0aW9uIGZvciB0aGUgbGFyZ2VzdCAkXGxhbWJkYSQgd2l0aGluIG9uZSBzdGFuZGFyZCBlcnJvciBvZiB0aGUgb3B0aW1hbCBtb2RlbC4gTm93IG9ubHkgYHIgIG5yb3coc3VtbWFyeShjb2VmKG1MYXNzbzFzZSkpKWAgbm9uLXplcm8gZXN0aW1hdGVzIHJlc3VsdC4KCi0tLQoKIyMjIyBSaWRnZQoKYGBge3J9Cm1DdlJpZGdlIDwtIGN2LmdsbW5ldCgKICB4ID0gdG94RGF0YVssLTFdICU+JQogICAgYXMubWF0cml4LAogICAgeSA9IHRveERhdGEgJT4lCiAgICAgIHB1bGwoQkEpLAogICAgICBhbHBoYSA9IDApICAjIHJpZGdlIGFscGhhPTAKCnBsb3QobUN2UmlkZ2UpCmBgYAoKLSBSaWRnZSBkb2VzIG5vdCBzZWVtIHRvIGhhdmUgb3B0aW1hbCBzb2x1dGlvbi4KLSAxMC1mb2xkIENWIGlzIGFsc28gbGFyZ2VyIHRoYW4gZm9yIGxhc3NvLgoKLS0tCgojIyMjIFBDQSByZWdyZXNzaW9uCgpgYGB7ciBmaWcua2VlcCA9ICJub25lIiwgd2FybmluZyA9IEZBTFNFfQpzZXQuc2VlZCgxMjY0KQpsaWJyYXJ5KERBQUcpCgp0b3ggPC0gZGF0YS5mcmFtZSgKICBZID0gdG94RGF0YSAlPiUKICAgIHB1bGwoQkEpLAogIFBDID0gWmspCgpQQy5zZXEgPC0gMToyNQpFcnIgPC0gbnVtZXJpYygyNSkKCm1DdlBjYSA8LSBjdi5sbSgKICBZflBDLjEsCiAgZGF0YSA9IHRveCwKICBtID0gNSwKICBwcmludGl0ID0gRkFMU0UpCgpFcnJbMV08LWF0dHIobUN2UGNhLCJtcyIpCgpmb3IoaSBpbiAyOjI1KSB7CiAgbUN2UGNhIDwtIGN2LmxtKAogICAgYXMuZm9ybXVsYSgKICAgICAgcGFzdGUoIlkgfiBQQy4xICsgIiwKICAgICAgICBwYXN0ZSgiUEMuIiwgMjppLCBjb2xsYXBzZSA9ICIrIiwgc2VwPSIiKSwKICAgICAgICBzZXA9IiIKICAgICAgKQogICAgKSwKICAgIGRhdGEgPSB0b3gsCiAgICBtID0gNSwKICAgIHByaW50aXQgPSBGQUxTRSkKICBFcnJbaV08LWF0dHIobUN2UGNhLCJtcyIpCn0KYGBgCgotIEhlcmUgd2UgaWxsdXN0cmF0ZSBwcmluY2lwYWwgY29tcG9uZW50IHJlZ3Jlc3Npb24uCgotIFRoZSBtb3N0IGltcG9ydGFudCBQQ3MgYXJlIHNlbGVjdGVkIGluIGEgZm9yd2FyZCBtb2RlbCBzZWxlY3Rpb24gcHJvY2VkdXJlLgoKLSBXaXRoaW4gdGhlIG1vZGVsIHNlbGVjdGlvbiBwcm9jZWR1cmUgdGhlIG1vZGVscyBhcmUgZXZhbHVhdGVkIHdpdGggNS1mb2xkIENWIGVzdGltYXRlcyBvZiB0aGUgb3V0c2FtcGxlIGVycm9yLgoKLSBJdCBpcyBpbXBvcnRhbnQgdG8gcmVhbGlzZSB0aGF0IGEgZm9yd2FyZCBtb2RlbCBzZWxlY3Rpb24gcHJvY2VkdXJlIHdpbGwgbm90IG5lY2Vzc2FyaWx5IHJlc3VsdCBpbiB0aGUgYmVzdCBwcmVkaWN0aW9uIG1vZGVsLCBwYXJ0aWN1bGFybHkgYmVjYXVzZSB0aGUgb3JkZXIgb2YgdGhlIFBDcyBpcyBnZW5lcmFsbHkgbm90IHJlbGF0ZWQgdG8gdGhlIGltcG9ydGFuY2Ugb2YgdGhlIFBDcyBmb3IgcHJlZGljdGluZyB0aGUgb3V0Y29tZS4KCi0gQSBzdXBlcnZpc2VkIFBDIHdvdWxkIGJlIGJldHRlci4KCmBgYHtyfQpwUENyZWcgPC0gZGF0YS5mcmFtZShQQy5zZXEsIEVycikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gUEMuc2VxLCB5ID0gRXJyKSkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21faGxpbmUoCiAgICB5aW50ZXJjZXB0ID0gYygKICAgICAgbUN2TGFzc28kY3ZtW21Ddkxhc3NvJGxhbWJkYT09bUN2TGFzc28kbGFtYmRhLm1pbl0sCiAgICAgIG1Ddkxhc3NvJGN2bVttQ3ZMYXNzbyRsYW1iZGE9PW1Ddkxhc3NvJGxhbWJkYS4xc2VdKSwKICAgIGNvbCA9ICJyZWQiKSArCiAgeGxpbSgxLDI2KQoKZ3JpZC5hcnJhbmdlKAogIHBQQ3JlZywKICBwUENyZWcgKyB5bGltKDAsNSksCiAgbmNvbD0yKQpgYGAKCi0gVGhlIGdyYXBoIHNob3dzIHRoZSBDViBlc3RpbWF0ZSBvZiB0aGUgb3V0c2FtcGxlIGVycm9yIGFzIGEgZnVuY3Rpb24gb2YgdGhlIG51bWJlciBvZiBzcGFyc2UgUENzIGluY2x1ZGVkIGluIHRoZSBtb2RlbC4KCi0gQSB2ZXJ5IHNtYWxsIGVycm9yIGlzIG9idGFpbmVkIHdpdGggdGhlIG1vZGVsIHdpdGggb25seSB0aGUgZmlyc3QgUEMuIFRoZSBiZXN0IG1vZGVsIHdpdGggMyBQQ3MuCgotIFRoZSB0d28gdmVydGljYWwgcmVmZXJlbmNlIGxpbmVzIGNvcnJlc3BvbmQgdG8gdGhlIGVycm9yIGVzdGltYXRlcyBvYnRhaW5lZCB3aXRoIGxhc3NvIChvcHRpbWFsICRcbGFtYmRhJCBhbmQgbGFyZ2VzdCAkXGxhbWJkYSQgd2l0aGluIG9uZSBzdGFuZGFyZCBlcnJvcikuCgotIFRodXMgYWx0aG91Z2ggdGhlcmUgd2FzIGEgcHJpb3JpIG5vIGd1YXJhbnRlZSB0aGF0IHRoZSBmaXJzdCBQQ3MgYXJlIHRoZSBtb3N0IHByZWRpY3RpdmUsIGl0IHNlZW1zIHRvIGJlIHRoZSBjYXNlIGhlcmUgKHdlIHdlcmUgbHVja3khKS4KCi0gTW9yZW92ZXIsIHRoZSBmaXJzdCBQQyByZXN1bHRlZCBpbiBhIHNtYWxsIG91dHNhbXBsZSBlcnJvci4KCi0gTm90ZSB0aGF0IHRoZSBncmFwaCBkb2VzIG5vdCBpbmRpY2F0ZSB0aGUgdmFyaWFiaWxpdHkgb2YgdGhlIGVycm9yIGVzdGltYXRlcyAobm8gZXJyb3IgYmFycykuCgotIEFsc28gbm90ZSB0aGF0IHRoZSBncmFwaCBjbGVhcmx5IGlsbHVzdHJhdGVzIHRoZSBlZmZlY3Qgb2Ygb3ZlcmZpdHRpbmc6IGluY2x1ZGluZyB0b28gbWFueSBQQ3MgY2F1c2VzIGEgbGFyZ2Ugb3V0c2FtcGxlIGVycm9yLgoKIyMjIExpZGFyIEV4YW1wbGU6IHNwbGluZXMKCi0gV2UgdXNlIHRoZSBtZ2N2IHBhY2thZ2UgdG8gZml0IHRoZSBzcGxpbmUgbW9kZWwgdG8gdGhlIGxpZGFyIGRhdGEuCi0gQSBiZXR0ZXIgYmFzaXMgaXMgdXNlZCB0aGFuIHRoZSB0cnVuY2F0ZWQgc3BsaW5lIGJhc2lzCi0gVGhpbiBwbGF0ZSBzcGxpbmVzIGFyZSBhbHNvIGxpbmVhciBzbW9vdGhlcnMsIGkuZS4KJFxoYXR7WX0gPSBcaGF0e219KFxtYXRoYmZ7WH0pID0gXG1hdGhiZntTWX0kCi0gU28gdGhlaXIgdmFyaWFuY2UgY2FuIGJlIGVhc2lseSBjYWxjdWxhdGVkLgotIFRoZSByaWRnZS9zbW9vdGhuZXNzIHBlbmFsdHkgaXMgY2hvc2VuIGJ5IGdlbmVyYWxpemVkIGNyb3NzIHZhbGlkYXRpb24uCgpgYGB7cn0KbGlicmFyeShtZ2N2KQpnYW1maXQgPC0gZ2FtKGxvZ3JhdGlvIH4gcyhyYW5nZSksIGRhdGEgPSBsaWRhcikKZ2FtZml0JHNwCgpwTGlkYXIgKwogIGdlb21fbGluZShhZXMoeCA9IGxpZGFyJHJhbmdlLCB5ID0gZ2FtZml0JGZpdHRlZCksIGx3ZCA9IDIpCmBgYAoKIyMgTW9yZSBnZW5lcmFsIGVycm9yIGRlZmluaXRpb25zCgpTbyBmYXIgd2Ugb25seSBsb29rZWQgYXQgY29udGludW91cyBvdXRjb21lcyAkWSQgYW5kIGVycm9ycyBkZWZpbmVkIGluIHRlcm1zIG9mIHRoZSBzcXVhcmVkIGxvc3MgJChcaGF0e219KFxtYXRoYmZ7eH0pLVleKileMiQuCgpNb3JlIGdlbmVyYWxseSwgYSAqKmxvc3MgZnVuY3Rpb24qKiBtZWFzdXJlcyBhbiBkaXNjcmVwYW5jeSBiZXR3ZWVuIHRoZSBwcmVkaWN0aW9uICRcaGF0e219KFxtYXRoYmZ7eH0pJCBhbmQgYW4gaW5kZXBlbmRlbnQgb3V0Y29tZSAkWV4qJCB0aGF0IGNvcnJlc3BvbmRzIHRvICRcbWF0aGJme3h9JC4KCgpTb21lIGV4YW1wbGVzIGZvciBjb250aW51b3VzICRZJDoKXGJlZ2lue2VxbmFycmF5Kn0KICBMKFleKixcaGF0e219KFxtYXRoYmZ7eH0pKQogICAgJj0mIChcaGF0e219KFxtYXRoYmZ7eH0pLVleKileMiBcO1w7XHRleHR7KHNxdWFyZWQgZXJyb3IpfSBcXAogIEwoWV4qLFxoYXR7bX0oXG1hdGhiZnt4fSkpCiAgICAmPSYgXHZlcnRcaGF0e219KFxtYXRoYmZ7eH0pLVleKlx2ZXJ0IFw7XDtcdGV4dHsoYWJzb2x1dGUgZXJyb3IpfSBcXAogICBMKFleKixcaGF0e219KFxtYXRoYmZ7eH0pKQogICAgJj0mIDIgXGludF97XGNhbHtZfX0gZl95KHkpIFxsb2dcZnJhY3tmX3koeSl9e2Zfe1xoYXR7bX19KHkpfSBkeSBcO1w7XHRleHR7KGRldmlhbmNlKX0uClxlbmR7ZXFuYXJyYXkqfQoKCkluIHRoZSBleHByZXNzaW9uIG9mIHRoZSBkZXZpYW5jZQoKLSAkZl95JCBkZW5vdGVzIHRoZSBkZW5zaXR5IGZ1bmN0aW9uIG9mIGEgZGlzdHJpYnV0aW9uIHdpdGggbWVhbiBzZXQgdG8gJHkkIChjZnIuIHBlcmZlY3QgZml0KSwgYW5kCi0gJGZfe1xoYXR7bX19JCBpcyB0aGUgZGVuc2l0eSBmdW5jdGlvbiBvZiB0aGUgc2FtZSBkaXN0cmlidXRpb24gYnV0IHdpdGggbWVhbiBzZXQgdG8gdGhlIHByZWRpY3RlZCBvdXRjb21lICRcaGF0e219KFxtYXRoYmZ7eH0pJC4KCi0tLQoKV2l0aCBhIGdpdmVuIGxvc3MgZnVuY3Rpb24sIHRoZSBlcnJvcnMgYXJlIGRlZmluZWQgYXMgZm9sbG93czoKLSBUZXN0IG9yIGdlbmVyYWxpc2F0aW9uIG9yIG91dHNhbXBsZSBlcnJvcgogICAgXFsKICAgICAgXHRleHR7RXJyfV97XGNhbHtUfX0gPSBcdGV4dHtFfV97WV4qLFheKn1cbGVmdFtMKFleKixcaGF0e219KFxtYXRoYmZ7WH1eKikpXHJpZ2h0XQogICAgXF0KCi0gVHJhaW5pbmcgZXJyb3IKICBcWwogICAgXG92ZXJsaW5le1x0ZXh0e2Vycn19ID0gXGZyYWN7MX17bn1cc3VtX3tpPTF9Xm4gTChZX2ksXGhhdHttfShcbWF0aGJme3h9X2kpKQogIFxdCgotICRcbGRvdHMkCgotLS0KCldoZW4gYW4gZXhwb25lbnRpYWwgZmFtaWx5IGRpc3RyaWJ1dGlvbiBpcyBhc3N1bWVkIGZvciB0aGUgb3V0Y29tZSBkaXN0cmlidXRpb24sIGFuZCB3aGVuIHRoZSBkZXZpYW5jZSBsb3NzIGlzIHVzZWQsIHRoZSBpbnNhbXBsZSBlcnJvciBjYW4gYmUgZXN0aW1hdGVkIGJ5IG1lYW5zIG9mIHRoZSBBSUMgYW5kIEJJQy4KCiMjIyBBa2Fpa2UncyBJbmZvcm1hdGlvbiBDcml0ZXJpb24gKEFJQykKClRoZSBBSUMgZm9yIGEgbW9kZWwgJG0kIGlzIGdpdmVuIGJ5ClxbClx0ZXh0e0FJQ30gPSAtMiBcbG4gXGhhdHtMfShtKSArMnAKXF0Kd2hlcmUgJFxoYXR7TH0obSkkIGlzIHRoZSBtYXhpbWlzZWQgbGlrZWxpaG9vZCBmb3IgbW9kZWwgJG0kLgoKV2hlbiBhc3N1bWluZyBub3JtYWxseSBkaXN0cmlidXRlZCBlcnJvciB0ZXJtcyBhbmQgaG9tb3NjZWRhc3RpY2l0eSwgdGhlIEFJQyBiZWNvbWVzClxbClx0ZXh0e0FJQ30gPSBuXGxuIFx0ZXh0e1NTRX0obSkgKzJwID0gblxsbihuXG92ZXJsaW5le1x0ZXh0e2Vycn19KG0pKSArIDJwClxdCndpdGggJFx0ZXh0e1NTRX0obSkkIHRoZSByZXNpZHVhbCBzdW0gb2Ygc3F1YXJlcyBvZiBtb2RlbCAkbSQuCgpJbiBsaW5lYXIgbW9kZWxzIHdpdGggbm9ybWFsIGVycm9yIHRlcm1zLCBNYWxsb3cncyAkQ19wJCBjcml0ZXJpb24gKHN0YXRpc3RpYykgaXMgYSBsaW5lYXJpc2VkIHZlcnNpb24gb2YgQUlDIGFuZCBpdCBpcyBhbiB1bmJpYXNlZCBlc3RpbWF0b3Igb2YgdGhlIGluLXNhbXBsZSBlcnJvci4KCi0tLQoKIyMjIEJheWVzaWFuIEluZm9ybWF0aW9uIENyaXRlcmlvbiAoQklDKX0KClRoZSBCSUMgZm9yIGEgbW9kZWwgJG0kIGlzIGdpdmVuIGJ5ClxbClx0ZXh0e0JJQ30gPSAtMiBcbG4gXGhhdHtMfShtKSArcFxsbihuKQpcXQp3aGVyZSAkXGhhdHtMfShtKSQgaXMgdGhlIG1heGltaXNlZCBsaWtlbGlob29kIGZvciBtb2RlbCAkbSQuCgpXaGVuIGFzc3VtaW5nIG5vcm1hbGx5IGRpc3RyaWJ1dGVkIGVycm9yIHRlcm1zIGFuZCBob21vc2NlZGFzdGljaXR5LCB0aGUgQklDIGJlY29tZXMKXFsKXHRleHR7QklDfSA9IG5cbG4gXHRleHR7U1NFfShtKSArcFxsbihuKSA9IG5cbG4oblxvdmVybGluZXtcdGV4dHtlcnJ9fShtKSkgKyBwXGxuKG4pClxdCndpdGggJFx0ZXh0e1NTRX0obSkkIHRoZSByZXNpZHVhbCBzdW0gb2Ygc3F1YXJlcyBvZiBtb2RlbCAkbSQuCgpXaGVuIGxhcmdlIGRhdGFzZXRzIGFyZSB1c2VkLCB0aGUgQklDIHdpbGwgZmF2b3VyIHNtYWxsZXIgbW9kZWxzIHRoYW4gdGhlIEFJQy4KCi0tLQoKIyMgVHJhaW5pbmcgYW5kIHRlc3Qgc2V0cwoKU29tZXRpbWVzLCB3aGVuIGEgbGFyZ2UgKHRyYWluaW5nKSBkYXRhc2V0IGlzIGF2YWlsYWJsZSwgb25lIG1heSBkZWNpZGUgdGhlIHNwbGl0IHRoZSBkYXRhc2V0IHJhbmRvbWx5IGluIGEKCi0gKip0cmFpbmluZyBkYXRhc2V0Kio6CiAgIGRhdGEgYXJlIHVzZWQgZm9yIG1vZGVsIGZpdHRpbmcgYW5kIGZvciBtb2RlbCBidWlsZGluZyBvciBmZWF0dXJlIHNlbGVjdGlvbiAodGhpcyBtYXkgcmVxdWlyZSBlLmcuIGNyb3NzIHZhbGlkYXRpb24pCgotICoqdGVzdCBkYXRhc2V0Kio6CiAgIHRoaXMgZGF0YSBhcmUgdXNlZCB0byBldmFsdWF0ZSB0aGUgZmluYWwgbW9kZWwgKHJlc3VsdCBvZiBtb2RlbCBidWlsZGluZykuIEFuIHVuYmlhc2VkIGVzdGltYXRlIG9mIHRoZSBvdXRzYW1wbGUgZXJyb3IgKGkuZS4gdGVzdCBvciBnZW5lcmFsaXNhdGlvbiBlcnJvcikgYmFzZWQgb24gdGhpcyB0ZXN0IGRhdGEgaXMKICBcWwogICAgIFxmcmFjezF9e219IFxzdW1fe2k9MX1ebSBcbGVmdChcaGF0e219KFxtYXRoYmZ7eH1faSktWV9pXHJpZ2h0KV4yLAogIFxdCiAgd2hlcmUKICAgIC0gJChZXzEsXG1hdGhiZnt4fV8xKSwgXGxkb3RzLCAoWV9tLFxtYXRoYmZ7eH1fbSkkIGRlbm90ZSB0aGUgJG0kIG9ic2VydmF0aW9ucyBpbiB0aGUgdGVzdCBkYXRhc2V0CgogICAgLSAkXGhhdHttfSQgaXMgZXN0aW1hdGVkIGZyb20gdXNpbmcgdGhlIHRyYWluaW5nIGRhdGEgKHRoaXMgbWF5IGFsc28gYmUgdGhlIHJlc3VsdCBmcm9tIG1vZGVsIGJ1aWxkaW5nLCB1c2luZyBvbmx5IHRoZSB0cmFpbmluZyBkYXRhKS4KCi0tLQoKTm90ZSB0aGF0IHRoZSB0cmFpbmluZyBkYXRhc2V0IGlzIHVzZWQgZm9yIG1vZGVsIGJ1aWxkaW5nIG9yIGZlYXR1cmUgc2VsZWN0aW9uLiBUaGlzIGFsc28gcmVxdWlyZXMgdGhlIGV2YWx1YXRpb24gb2YgbW9kZWxzLiBGb3IgdGhlc2UgZXZhbHVhdGlvbnMgdGhlIG1ldGhvZHMgZnJvbSB0aGUgcHJldmlvdXMgc2xpZGVzIGNhbiBiZSB1c2VkIChlLmcuIGNyb3NzIHZhbGlkYXRpb24sICRrJC1mb2xkIENWLCBNYWxsb3cncyAkQ19wJCkuIFRoZSB0ZXN0IGRhdGFzZXQgaXMgb25seSB1c2VkIGZvciB0aGUgIGV2YWx1YXRpb24gb2YgdGhlIGZpbmFsIG1vZGVsIChlc3RpbWF0ZWQgYW5kIGJ1aWxkIGZyb20gdXNpbmcgb25seSB0aGUgdHJhaW5pbmcgZGF0YSkuIFRoZSBlc3RpbWF0ZSBvZiB0aGUgb3V0c2FtcGxlIGVycm9yIGJhc2VkIG9uIHRoZSB0ZXN0IGRhdGFzZXQgaXMgdGhlIGJlc3QgcG9zc2libGUgZXN0aW1hdGUgaW4gdGhlIHNlbnNlIHRoYXQgaXQgaXMgdW5iaWFzZWQuIFRoZSBvYnNlcnZhdGlvbnMgdXNlZCBmb3IgdGhpcyBlc3RpbWF0aW9uIGFyZSBpbmRlcGVuZGVudCBvZiB0aGUgb2JzZXJ2YXRpb25zIGluIHRoZSB0cmFpbmluZyBkYXRhLgpIb3dldmVyLCBpZiB0aGUgbnVtYmVyIG9mIGRhdGEgcG9pbnRzIGluIHRoZSB0ZXN0IGRhdGFzZXQgKCRtJCkgaXMgc21hbGwsIHRoZSBlc3RpbWF0ZSBvZiB0aGUgb3V0c2FtcGxlIGVycm9yIG1heSBzaG93IGxhcmdlIHZhcmlhbmNlIGFuZCBoZW5jZSBpcyBub3QgcmVsaWFibGUuCgojIExvZ2lzdGljIFJlZ3Jlc3Npb24gQW5hbHlzaXMgZm9yIEhpZ2ggRGltZW5zaW9uYWwgRGF0YQoKIyMgQnJlYXN0IENhbmNlciBFeGFtcGxlCgotIFNjaG1pZHQgKmV0IGFsLiosIDIwMDgsIENhbmNlciBSZXNlYXJjaCwgKio2OCoqLCA1NDA1LTU0MTMKCi0gR2VuZSBleHByZXNzaW9uIHBhdHRlcm5zIGluICRuPTIwMCQgYnJlYXN0IHR1bW9ycyB3ZXJlIGludmVzdGlnYXRlZCAoJHA9MjIyODMkIGdlbmVzKQoKLSBBZnRlciBzdXJnZXJ5IHRoZSB0dW1vcnMgd2VyZSBncmFkZWQgYnkgYSBwYXRob2xvZ2lzdCAoc3RhZ2UgMSwyLDMpCgotIEhlcmUgdGhlIG9iamVjdGl2ZSBpcyB0byBwcmVkaWN0IHN0YWdlIDMgZnJvbSB0aGUgZ2VuZSBleHByZXNzaW9uIGRhdGEgKHByZWRpY3Rpb24gb2YgYmluYXJ5IG91dGNvbWUpCgotIElmIHRoZSBwcmVkaWN0aW9uIG1vZGVsIHdvcmtzIHdlbGwsIGl0IGNhbiBiZSB1c2VkIHRvIHByZWRpY3QgdGhlIHN0YWdlIGZyb20gYSBiaW9wc3kgc2FtcGxlLgoKIyMgRGF0YQoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiNCaW9jTWFuYWdlcjo6aW5zdGFsbCgiZ2VuZWZ1IikKI0Jpb2NNYW5hZ2VyOjppbnN0YWxsKCJicmVhc3RDYW5jZXJNQUlOWiIpCgpsaWJyYXJ5KGdlbmVmdSkKbGlicmFyeShicmVhc3RDYW5jZXJNQUlOWikKZGF0YShtYWlueikKClggPC0gdChleHBycyhtYWlueikpICMgZ2VuZSBleHByZXNzaW9ucwpuIDwtIG5yb3coWCkKSCA8LSBkaWFnKG4pLTEvbiptYXRyaXgoMSxuY29sPW4sbnJvdz1uKQpYIDwtIEglKiVYClkgPC0gaWZlbHNlKHBEYXRhKG1haW56KSRncmFkZT09MywxLDApCnRhYmxlKFkpCgpzdmRYIDwtIHN2ZChYKQprIDwtIDIKWmsgPC0gc3ZkWCR1WywxOmtdICUqJSBkaWFnKHN2ZFgkZFsxOmtdKQpjb2xuYW1lcyhaaykgPC0gcGFzdGUwKCJaIiwxOmspCgpaayAlPiUKICBhcy5kYXRhLmZyYW1lICU+JQogIG11dGF0ZShncmFkZSA9IFkgJT4lIGFzLmZhY3RvcikgJT4lCiAgZ2dwbG90KGFlcyh4PSBaMSwgeSA9IFoyLCBjb2xvciA9IGdyYWRlKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDMpCmBgYAoKLS0tCgojIyBMb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVscwoKQmluYXJ5IG91dGNvbWVzIGFyZSBvZnRlbiBhbmFseXNlZCB3aXRoICoqbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMqKi4KCkxldCAkWSQgZGVub3RlIHRoZSBiaW5hcnkgKDEvMCwgY2FzZS9jb250cm9sLCBwb3NpdGl2ZS9uZWdhdGl2ZSkgb3V0Y29tZSwgYW5kICRcbWF0aGJme3h9JCB0aGUgJHAkLWRpbWVuc2lvbmFsIHByZWRpY3Rvci4KCkxvZ2lzdGljIHJlZ3Jlc3Npb24gIGFzc3VtZXMKXFsKICAgWSBcbWlkIFxtYXRoYmZ7eH0gXHNpbSBcdGV4dHtCZXJub3VsbGl9KFxwaShcbWF0aGJme3h9KSkKXF0Kd2l0aCAkXHBpKFxtYXRoYmZ7eH0pID0gXHRleHR7UH1cbGVmdFtZPTFcbWlkIFxtYXRoYmZ7eH1ccmlnaHRdJCBhbmQKXFsKICAgXGxuIFxmcmFje1xwaShcbWF0aGJme3h9KX17MS1ccGkoXG1hdGhiZnt4fSl9PVxiZXRhXzAgKyBcYm9sZHN5bWJvbHtcYmV0YX1eVFxtYXRoYmZ7eH0uClxdCgpUaGUgcGFyYW1ldGVycyBhcmUgdHlwaWNhbGx5IGVzdGltYXRlZCBieSBtYXhpbWlzaW5nIHRoZSBsb2ctbGlrZWxpaG9vZCwgd2hpY2ggaXMgZGVub3RlZCBieSAkbChcbWF0aGJmewpcYmV0YX0pJCwgaS5lLgpcWwogICBcaGF0e1xib2xkc3ltYm9se1xiZXRhfX0gPSBcdGV4dHtBcmdNYXh9X1xiZXRhIGwoXGJvbGRzeW1ib2x7XGJldGF9KS4KXF0KCi0gTWF4aW11bSBsaWtlbGlob29kIGlzIG9ubHkgYXBwbGljYWJsZSB3aGVuICRuPnAkLgoKLSBXaGVuICRwPm4kIHBlbmFsaXNlZCBtYXhpbXVtIGxpa2VsaWhvb2QgbWV0aG9kcyBhcmUgYXBwbGljYWJsZS4KCi0tLQoKIyMgUGVuYWxpemVkIG1heGltdW0gbGlrZWxpaG9vZAoKUGVuYWxpc2VkIGVzdGltYXRpb24gbWV0aG9kcyAoZS5nLiBsYXNzbyBhbmQgcmlkZ2UpIGNhbiBhbHMgYmUgYXBwbGllZCB0byBtYXhpbXVtIGxpa2VsaWhvb2QsIHJlc3VsdGluZyBpbiB0aGUgKipwZW5hbGlzZWQgbWF4aW11bSBsaWtlbGlob29kIGVzdGltYXRlKiouCgpMYXNzbzoKXFsKICBcaGF0e1xib2xkc3ltYm9se1xiZXRhfX0gPSBcdGV4dHtBcmdNYXh9X1xiZXRhIGwoXGJvbGRzeW1ib2x7XGJldGF9KSAtXGxhbWJkYSBcVmVydCBcYm9sZHN5bWJvbHtcYmV0YX1cVmVydF8xLgpcXQoKUmlkZ2U6ClxbCiAgXGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19ID0gXHRleHR7QXJnTWF4fV9cYmV0YSBsKFxib2xkc3ltYm9se1xiZXRhfSkgLVxsYW1iZGEgXFZlcnQgXGJvbGRzeW1ib2x7XGJldGF9XFZlcnRfMl4yLgpcXQoKT25jZSB0aGUgcGFyYW1ldGVycyBhcmUgZXN0aW1hdGVkLCB0aGUgbW9kZWwgbWF5IGJlIHVzZWQgdG8gY29tcHV0ZQpcWwogIFxoYXR7XHBpfShcbWF0aGJme3h9KSA9IFxoYXR7XHRleHR7UH19XGxlZnRbWT0xXG1pZCBcbWF0aGJme3h9XHJpZ2h0XS4KXF0KV2l0aCB0aGVzZSBlc3RpbWF0ZWQgcHJvYmFiaWxpdGllcyB0aGUgcHJlZGljdGlvbiBydWxlIGJlY29tZXMKXGJlZ2lue2VxbmFycmF5Kn0KICBcaGF0e1xwaX0oXG1hdGhiZnt4fSkgJlxsZXEgYyYgXHRleHR7cHJlZGljdCB9IFk9MCBcXAogIFxoYXR7XHBpfShcbWF0aGJme3h9KSAmPmMgJiBcdGV4dHtwcmVkaWN0IH0gWT0xClxlbmR7ZXFuYXJyYXkqfQp3aXRoICQwPGM8MSQgYSB0aHJlc2hvbGQgdGhhdCBlaXRoZXIgaXMgZml4ZWQgKGUuZy4gJGM9MS8yJCksIGRlcGVuZHMgb24gcHJpb3IgcHJvYmFiaWxpdGllcywgb3IgaXMgZW1waXJpY2FsbHkgZGV0ZXJtaW5lZCBieSBvcHRpbWlzaW5nIGUuZy4gdGhlIEFyZWEgVW5kZXIgdGhlIFJPQyBDdXJ2ZSAoQVVDKSBvciBieSBmaW5kaW5nIGEgZ29vZCBjb21wcm9taXNlIGJldHdlZW4gc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5LgoKTm90ZSB0aGF0IGxvZ2lzdGljIHJlZ3Jlc3Npb24gZGlyZWN0bHkgbW9kZWxzIHRoZSAqKlBvc3RlcmlvciBwcm9iYWJpbGl0eSoqIHRoYXQgYW4gb2JzZXJ2YXRpb24gYmVsb25ncyB0byBjbGFzcyAkWT0xJCwgZ2l2ZW4gdGhlIHByZWRpY3RvciAkXG1hdGhiZnt4fSQuCgojIyBNb2RlbCBldmFsdWF0aW9uCgpDb21tb24gbW9kZWwgZXZhbHVhdGlvbiBjcml0ZXJpYSBmb3IgYmluYXJ5IHByZWRpY3Rpb24gbW9kZWxzIGFyZToKCi0gc2Vuc2l0aXZpdHkgPSB0cnVlIHBvc2l0aXZlIHJhdGUgKFRQUikKCi0gc3BlY2lmaWNpdHkgPSB0cnVlIG5lZ2F0aXZlIHJhdGUgKFROUikKCi0gbWlzY2xhc3NpZmljYXRpb24gZXJyb3IKCi0gYXJlYSB1bmRlciB0aGUgUk9DIGN1cnZlIChBVUMpCgpUaGVzZSBjcml0ZXJpYSBjYW4gYWdhaW4gYmUgZXN0aW1hdGVkIHZpYSBjcm9zcyB2YWxpZGF0aW9uIG9yIHZpYSBzcGxpdHRpbmcgb2YgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdC92YWxpZGF0aW9uIGRhdGEuCgojIyMgU2Vuc2l0aXZpdHkgb2YgYSBtb2RlbCAkXHBpJCB3aXRoIHRocmVzaG9sZCAkYyQKClNlbnNpdGl2aXR5IGlzIHRoZSBwcm9iYWJpbGl0eSB0byBjb3JyZWN0bHkgcHJlZGljdCBhIHBvc2l0aXZlIG91dGNvbWU6ClxbClx0ZXh0e3NlbnN9KFxwaSxjKT1cdGV4dHtQfV97WF4qfVxsZWZ0W1xoYXRccGkoXG1hdGhiZntYfV4qKT5jIFxtaWQgWV4qPTEgXG1pZCB7XGNhbHtUfX1ccmlnaHRdLgpcXQoKSXQgaXMgYWxzbyBrbm93biBhcyB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlIChUUFIpLgoKIyMjIFNwZWNpZmljaXR5IG9mIGEgbW9kZWwgJFxwaSQgd2l0aCB0aHJlc2hvbGQgJGMkCgpTcGVjaWZpY2l0eSBpcyB0aGUgcHJvYmFiaWxpdHkgdG8gY29ycmVjdGx5IHByZWRpY3QgYSBuZWdhdGl2ZSBvdXRjb21lOgpcWwpcdGV4dHtzcGVjfShccGksYyk9XHRleHR7UH1fe1heKn1cbGVmdFtcaGF0XHBpKFxtYXRoYmZ7WH1eKilcbGVxIGMgXG1pZCBZXio9MCBcbWlkIHtcY2Fse1R9fVxyaWdodF0uClxdCgpJdCBpcyBhbHNvIGtub3duIGFzIHRoZSB0cnVlIG5lZ2F0aXZlIHJhdGUgKFROUikuCgotLS0KCiMjIyBNaXNjbGFzc2lmaWNhdGlvbiBlcnJvciBvZiBhIG1vZGVsICRccGkkIHdpdGggdGhyZXNob2xkICRjJAoKVGhlIG1pc2NsYXNzaWZpY2F0aW9uIGVycm9yIGlzIHRoZSBwcm9iYWJpbGl0eSB0byBpbmNvcnJlY3RseSBwcmVkaWN0IGFuIG91dGNvbWU6ClxiZWdpbntlcW5hcnJheSp9Clx0ZXh0e21jZX0oXHBpLGMpICY9Jlx0ZXh0e1B9X3tYXiosWV4qfVxsZWZ0W1xoYXRccGkoXG1hdGhiZntYfSlcbGVxIGMgXHRleHR7IGFuZCB9IFleKj0xIFxtaWQge1xjYWx7VH19XHJpZ2h0XSBcXAomICAmICsgXHRleHR7UH1fe1heKixZXip9XGxlZnRbXGhhdFxwaShcbWF0aGJme1h9KT4gYyBcdGV4dHsgYW5kIH0gWV4qPTAgXG1pZCB7XGNhbHtUfX1ccmlnaHRdLgpcZW5ke2VxbmFycmF5Kn0KCk5vdGUgdGhhdCBpbiB0aGUgZGVmaW5pdGlvbnMgb2Ygc2Vuc2l0aXZpdHksIHNwZWNpZmljaXR5IGFuZCB0aGUgbWlzY2xhc3NpZmljYXRpb24gZXJyb3IsIHRoZSBwcm9iYWJpbGl0aWVzIHJlZmVyIHRvIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlICAkKFxtYXRoYmZ7WH1eKixZXiopJCwgd2hpY2ggaXMgaW5kZXBlbmRlbnQgb2YgdGhlIHRyYWluaW5nIGRhdGEsIGNvbmRpdGlvbmFsIG9uIHRoZSB0cmFpbmluZyBkYXRhLiBUaGlzIGlzIGluIGxpbmUgd2l0aCB0aGUgdGVzdCBvciBnZW5lcmFsaXNhdGlvbiBlcnJvci4gVGhlIG1pc2NsYXNzaWZpY2F0aW9uIGVycm9yIGlzIGFjdHVhbGx5IHRoZSB0ZXN0IGVycm9yIHdoZW4gYSAwLzEgbG9zcyBmdW5jdGlvbiBpcyB1c2VkLiBKdXN0IGFzIGJlZm9yZSwgdGhlIHNlbnNpdGl2aXR5LCBzcGVjaWZpY2l0eSBhbmQgdGhlIG1pc2NsYXNzaWZpY2F0aW9uIGVycm9yIGNhbiBhbHNvIGJlIGF2ZXJhZ2VkIG92ZXIgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdHJhaW5pbmcgZGF0YSBzZXQsIHdoaWNoIGlzIGluIGxpbmUgd2l0aCB0aGUgZXhwZWN0ZWQgdGVzdCBlcnJvciB3aGljaCBoYXMgYmVlbiBkaXNjdXNzZWQgZWFybGllci4KCi0tLQoKIyMjIFJPQyBjdXJ2ZSBvZiBhIG1vZGVsICRccGkkCgpUaGUgUmVjZWl2ZXIgT3BlcmF0aW5nIENoYXJhY3RlcmlzdGljIChST0MpIGN1cnZlIGZvciBtb2RlbCAkXHBpJCBpcyBnaXZlbiBieSB0aGUgZnVuY3Rpb24KClxbClx0ZXh0e1JPQ306IFswLDFdIFxyaWdodGFycm93IFswLDFdXHRpbWVzIFswLDFdOiBjIFxtYXBzdG8gKDEtXHRleHR7c3BlY30oXHBpLGMpLCBcdGV4dHtzZW5zfShccGksYykpLgpcXQoKRm9yIHdoZW4gJGMkIG1vdmVzIGZyb20gMSB0byAwLCB0aGUgUk9DIGZ1bmN0aW9uIGRlZmluZXMgYSBjdXJ2ZSBpbiB0aGUgcGxhbmUgJFswLDFdXHRpbWVzIFswLDFdJCwgbW92aW5nIGZyb20gJCgwLDApJCBmb3IgJGM9MSQgdG8gJCgxLDEpJCBmb3IgJGM9MCQuCgpUaGUgaG9yaXpvbnRhbCBheGlzIG9mIHRoZSBST0MgY3VydmUgc2hvd3MgMS1zcGVjaWZpY2l0eS4gVGhpcyBpcyBhbHNvIGtub3duIGFzIHRoZSBGYWxzZSBQb3NpdGl2ZSBSYXRlIChGUFIpLgoKLS0tCgojIyMgQXJlYSB1bmRlciB0aGUgY3VydmUgKEFVQykgb2YgYSBtb2RlbCAkXHBpJAoKVGhlIGFyZWEgdW5kZXIgdGhlIGN1cnZlIChBVUMpIGZvciBtb2RlbCAkXHBpJCBpcyBhcmVhIHVuZGVyIHRoZSBST0MgY3VydmUgYW5kIGlzIGdpdmVuIGJ5ClxbClxpbnRfMF4xIFx0ZXh0e1JPQ30oYykgZGMuClxdCgpTb21lIG5vdGVzIGFib3V0IHRoZSBBVUM6CgotIEFVQz0wLjUgcmVzdWx0cyB3aGVuIHRoZSBST0MgY3VydmUgaXMgdGhlIGRpYWdvbmFsLiBUaGlzIGNvcnJlc3BvbmRzIHRvIGZsaXBwaW5nIGEgY29pbiwgaS5lLiBhIGNvbXBsZXRlIHJhbmRvbSBwcmVkaWN0aW9uLgoKLSBBVUM9MSByZXN1bHRzIGZyb20gdGhlIHBlcmZlY3QgUk9DIGN1cnZlLCB3aGljaCBpcyB0aGUgUk9DIGN1cnZlIHRocm91Z2ggdGhlIHBvaW50cyAkKDAsMCkkLCAkKDAsMSkkIGFuZCAkKDEsMSkkLiBUaGlzIFJPQyBjdXJ2ZSBpbmNsdWRlcyBhIHRocmVzaG9sZCAkYyQgc3VjaCB0aGF0IHNlbnNpdGl2aXR5IGFuZCBzcGVjaWZpY2l0eSBhcmUgZXF1YWwgdG8gb25lLgoKIyMgQnJlYXN0IGNhbmNlciBleGFtcGxlCgojIyMgRGF0YQoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoZ2xtbmV0KQoKI0Jpb2NNYW5hZ2VyOjppbnN0YWxsKCJnZW5lZnUiKQojQmlvY01hbmFnZXI6Omluc3RhbGwoImJyZWFzdENhbmNlck1BSU5aIikKCmxpYnJhcnkoZ2VuZWZ1KQpsaWJyYXJ5KGJyZWFzdENhbmNlck1BSU5aKQpkYXRhKG1haW56KQoKWCA8LSB0KGV4cHJzKG1haW56KSkgIyBnZW5lIGV4cHJlc3Npb25zCm4gPC0gbnJvdyhYKQpIIDwtIGRpYWcobiktMS9uKm1hdHJpeCgxLG5jb2w9bixucm93PW4pClggPC0gSCUqJVgKWSA8LSBpZmVsc2UocERhdGEobWFpbnopJGdyYWRlPT0zLDEsMCkKdGFibGUoWSkKYGBgCgotLS0KCkZyb20gdGhlIHRhYmxlIG9mIHRoZSBvdXRjb21lcyBpbiBZIHdlIHJlYWQgdGhhdAoKLSBgciBzdW0oWT09MSlgIHR1bW9ycyB3ZXJlIGdyYWRlZCBhcyBzdGFnZSAzIGFuZAotIGByIHN1bShZPT0wKWAgdHVtb3JzIHdlcmUgZ3JhZGVkIGFzIHN0YWdlIDEgb3IgMi4KCkluIHRoaXMgdGhlIHN0YWdlIDMgdHVtb3JzIGFyZSByZWZlcnJlZCB0byBhcyBjYXNlcyBvciBwb3N0aXZlcyBhbmQgdGhlIHN0YWdlIDEgYW5kIDIgdHVtb3JzIGFzIGNvbnRyb2xzIG9yIG5lZ2F0aXZlcy4KCi0tLQoKIyMjIFRyYWluaW5nIGFuZCB0ZXN0IGRhdGFzZXQKClRoZSB1c2Ugb2YgdGhlIGxhc3NvIGxvZ2lzdGljIHJlZ3Jlc3Npb24gZm9yIHRoZSBwcmVkaWN0aW9uIG9mIHN0YWdlIDMgYnJlYXN0IGNhbmNlciBpcyBpbGx1c3RyYXRlZCBoZXJlIGJ5CgotIHJhbmRvbWx5IHNwbGl0dGluZyB0aGUgZGF0YXNldCBpbnRvIGEgdHJhaW5pbmcgZGF0YXNldCAoJDgwXCUkIG9mIGRhdGEgPSAxNjAgdHVtb3JzKSBhbmQgYSB0ZXN0IGRhdGFzZXQgKDQwIHR1bW9ycykKCi0gdXNpbmcgdGhlIHRyYWluaW5nIGRhdGEgdG8gc2VsZWN0IGEgZ29vZCAkXGxhbWJkYSQgdmFsdWUgaW4gdGhlIGxhc3NvIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgKHRocm91Z2ggMTAtZm9sZCBDVikKCi0gZXZhbHVhdGluZyB0aGUgZmluYWwgbW9kZWwgYnkgbWVhbnMgb2YgdGhlIHRlc3QgZGF0YXNldCAoUk9DIEN1cnZlLCBBVUMpLgoKCmBgYHtyfQoKIyMgVXNlZCB0byBwcm92aWRlIHNhbWUgcmVzdWx0cyBhcyBpbiBwcmV2aW91cyBSIHZlcnNpb24KUk5Ha2luZChzYW1wbGUua2luZCA9ICJSb3VuZGluZyIpCnNldC5zZWVkKDY5NzczMjYpCiMjIyMKCm4gPC0gbnJvdyhYKQpuVHJhaW4gPC0gcm91bmQoMC44Km4pCm5UcmFpbgoKaW5kVHJhaW4gPC0gc2FtcGxlKDE6bixuVHJhaW4pClhUcmFpbiA8LSBYW2luZFRyYWluLF0KWVRyYWluIDwtIFlbaW5kVHJhaW5dClhUZXN0IDwtIFhbLWluZFRyYWluLF0KWVRlc3QgPC0gWVstaW5kVHJhaW5dCnRhYmxlKFlUZXN0KQpgYGAKCk5vdGUgdGhhdCB0aGUgcmFuZG9tbHkgc2VsZWN0ZWQgdGVzdCBkYXRhIGhhcyBgciBtZWFuKFlUZXN0PT0xKSoxMDBgJSBjYXNlcyBvZiBzdGFnZSAzIHR1bW9ycy4KVGhpcyBpcyBhIGJpdCBoaWdoZXIgdGhhbiB0aGUgYHIgbWVhbihZPT0xKSoxMDBgJSAgaW4gdGhlIGNvbXBsZXRlIGRhdGEuCgpPbmUgY291bGQgYWxzbyBwZXJmb3JtIHRoZSByYW5kb20gc3BsaXR0aW5nIGFtb25nIHRoZSBwb3NpdGl2ZXMgYW5kIHRoZSBuZWdhdGl2ZXMgc2VwYXJhdGVseSAoc3RyYXRpZmllZCBzcGxpdHRpbmcpLgoKIyMjIE1vZGVsIGZpdHRpbmcgYmFzZWQgb24gdHJhaW5pbmcgZGF0YQoKYGBge3J9Cm1MYXNzbyA8LSBnbG1uZXQoCiAgeCA9IFhUcmFpbiwKICB5ID0gWVRyYWluLAogIGFscGhhID0gMSwKICBmYW1pbHk9ImJpbm9taWFsIikgICMgbGFzc286IGFscGhhID0gMQoKcGxvdChtTGFzc28sIHh2YXIgPSAibGFtYmRhIiwgeGxpbSA9IGMoLTYsLTEuNSkpCmBgYAoKLS0tCgpgYGB7cn0KbUN2TGFzc28gPC0gY3YuZ2xtbmV0KAogIHggPSBYVHJhaW4sCiAgeSA9IFlUcmFpbiwKICBhbHBoYSA9IDEsCiAgdHlwZS5tZWFzdXJlID0gImNsYXNzIiwKCWZhbWlseSA9ICJiaW5vbWlhbCIpICAjIGxhc3NvIGFscGhhID0gMQoKcGxvdChtQ3ZMYXNzbykKbUN2TGFzc28KYGBgCgpUaGUgdG90YWwgbWlzY2xhc3NpZmljYXRpb24gZXJyb3IgaXMgdXNlZCBoZXJlIHRvIHNlbGVjdCBhIGdvb2QgdmFsdWUgZm9yICRcbGFtYmRhJC4KCmBgYHtyfQojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJwbG90Uk9DIikKbGlicmFyeShwbG90Uk9DKQoKZGZMYXNzb09wdCA8LSBkYXRhLmZyYW1lKAogIHBpID0gcHJlZGljdChtQ3ZMYXNzbywKICAgIG5ld3ggPSBYVGVzdCwKICAgIHMgPSBtQ3ZMYXNzbyRsYW1iZGEubWluLAogICAgdHlwZSA9ICJyZXNwb25zZSIpICU+JSBjKC4pLAogIGtub3duLnRydXRoID0gWVRlc3QpCgpyb2MgPC0KICBkZkxhc3NvT3B0ICAlPiUKICBnZ3Bsb3QoYWVzKGQgPSBrbm93bi50cnV0aCwgbSA9IHBpKSkgKwogIGdlb21fcm9jKG4uY3V0cyA9IDApICsKICB4bGFiKCIxLXNwZWNpZmljaXR5IChGUFIpIikgKwogIHlsYWIoInNlbnNpdGl2aXR5IChUUFIpIikKCnJvYwoKY2FsY19hdWMocm9jKQpgYGAKCi0gVGhlIFJPQyBjdXJ2ZSBpcyBzaG93biBmb3IgdGhlIG1vZGVsIGJhc2VkIG9uICRcbGFtYmRhJCB3aXRoIHRoZSBzbWFsbGVzdCBtaXNjbGFzc2lmaWNhdGlvbiBlcnJvci4gVGhlIG1vZGVsIGhhcyBhbiBBVUMgb2YgYHIgY2FsY19hdWMocm9jKSAlPiUgcHVsbChBVUMpICU+JSByb3VuZCgyKWAuCgotIEJhc2VkIG9uIHRoaXMgUk9DIGN1cnZlIGFuIGFwcHJvcHJpYXRlIHRocmVzaG9sZCAkYyQgY2FuIGJlIGNob3Nlbi4gRm9yIGV4YW1wbGUsIGZyb20gdGhlIFJPQyBjdXJ2ZSB3ZSBzZWUgdGhhdCBpdCBpcyBwb3NzaWJsZSB0byBhdHRhaW4gYSBzcGVjaWZpY2l0eSBhbmQgYSBzZW5zaXRpdml0eSBvZiA3NVwlLgoKLSBUaGUgc2Vuc2l0aXZpdGllcyBhbmQgc3BlY2lmaWNpdGllcyBpbiB0aGUgUk9DIGN1cnZlIGFyZSB1bmJpYXNlZCAoaW5kZXBlbmRlbnQgdGVzdCBkYXRhc2V0KSBmb3IgdGhlIHByZWRpY3Rpb24gbW9kZWwgYnVpbGQgZnJvbSB0aGUgdHJhaW5pbmcgZGF0YS4gVGhlIGVzdGltYXRlcyBvZiBzZW5zaXRpdml0eSBhbmQgc3BlY2lmaWNpdHksIGhvd2V2ZXIsIGFyZSBiYXNlZCBvbiBvbmx5IDQwIG9ic2VydmF0aW9ucy4KCi0tLQoKYGBge3J9Cm1MYW1iZGFPcHQgPC0gZ2xtbmV0KHggPSBYVHJhaW4sCiAgeSA9IFlUcmFpbiwKICBhbHBoYSA9IDEsCiAgbGFtYmRhID0gbUN2TGFzc28kbGFtYmRhLm1pbiwKICBmYW1pbHk9ImJpbm9taWFsIikKCnFwbG90KAogIHN1bW1hcnkoY29lZihtTGFtYmRhT3B0KSlbLTEsMV0sCiAgc3VtbWFyeShjb2VmKG1MYW1iZGFPcHQpKVstMSwzXSkgKwogIHhsYWIoImdlbmUgSUQiKSArCiAgeWxhYigiYmV0YS1oYXQiKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikKYGBgCgotIFRoZSBtb2RlbCB3aXRoIHRoZSBvcHRpbWFsICRcbGFtYmRhJCBoYXMgb25seSBgciBtTGFtYmRhT3B0ICU+JSBjb2VmICU+JSBzdW1tYXJ5ICU+JSBucm93YCBub24temVybyBwYXJhbWV0ZXIgZXN0aW1hdGVzLgotIFRodXMgb25seSBgciBtTGFtYmRhT3B0ICU+JSBjb2VmICU+JSBzdW1tYXJ5ICU+JSBucm93YCBnZW5lcyBhcmUgaW52b2x2ZWQgaW4gdGhlIHByZWRpY3Rpb24gbW9kZWwuCi0gVGhlc2UgYHIgbUxhbWJkYU9wdCAlPiUgY29lZiAlPiUgc3VtbWFyeSAlPiUgbnJvd2AgcGFyYW1ldGVyIGVzdGltYXRlcyBhcmUgcGxvdHRpbmcgaW4gdGhlIGdyYXBoLgpBIGxpc3Rpbmcgb2YgdGhlIG1vZGVsIG91dHB1dCB3b3VsZCBzaG93IHRoZSBuYW1lcyBvZiB0aGUgZ2VuZXMuCgotLS0KCmBgYHtyfQoKZGZMYXNzbzFzZSA8LSBkYXRhLmZyYW1lKAogIHBpID0gcHJlZGljdChtQ3ZMYXNzbywKICAgIG5ld3ggPSBYVGVzdCwKICAgIHMgPSBtQ3ZMYXNzbyRsYW1iZGEuMXNlLAogICAgdHlwZSA9ICJyZXNwb25zZSIpICU+JSBjKC4pLAogIGtub3duLnRydXRoID0gWVRlc3QpCgpyb2MgPC0KICByYmluZCgKICAgIGRmTGFzc29PcHQgJT4lCiAgICAgIG11dGF0ZShtZXRob2QgPSAibWluIiksCiAgICBkZkxhc3NvMXNlICU+JQogICAgICBtdXRhdGUobWV0aG9kID0gIjFzZSIpCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKGQgPSBrbm93bi50cnV0aCwgbSA9IHBpLCBjb2xvciA9IG1ldGhvZCkpICsKICBnZW9tX3JvYyhuLmN1dHMgPSAwKSArCiAgeGxhYigiMS1zcGVjaWZpY2l0eSAoRlBSKSIpICsKICB5bGFiKCJzZW5zaXRpdml0eSAoVFBSKSIpCgpyb2MKCmNhbGNfYXVjKHJvYykKYGBgCgotIFdoZW4gdXNpbmcgdGhlICRcbGFtYmRhJCBvZiB0aGUgb3B0aW1hbCBtb2RlbCB1cCB0byAxIHN0YW5kYXJkIGRldmlhdGlvbiwgYSBkaWFnb25hbCBST0MgY3VydmUgaXMgb2J0YWluZWQgYW5kIGhlbmNlIEFVQyBpcyAkMC41JC4KCi0gVGhpcyBwcmVkaWN0aW9uIG1vZGVsIGlzIHRodXMgZXF1aXZhbGVudCB0byBmbGlwcGluZyBhIGNvaW4gZm9yIG1ha2luZyB0aGUgcHJlZGljdGlvbi4KCi0gVGhlIHJlYXNvbiBpcyB0aGF0IHdpdGggdGhpcyBjaG9pY2Ugb2YgJFxsYW1iZGEkIChzdHJvbmcgcGVuYWxpc2F0aW9uKSBhbG1vc3QgYWxsIHByZWRpY3RvcnMgYXJlIHJlbW92ZWQgZnJvbSB0aGUgbW9kZWwuCgotIFRoZXJlZm9yZSwgZG8gbmV2ZXIgYmxpbmRseSBjaG9vc2UgZm9yIHRoZSBgYG9wdGltYWwnJyAkXGxhbWJkYSQgYXMgZGVmaW5lZCBoZXJlLCBidXQgYXNzZXNzIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgbW9kZWwgZmlyc3QuCgpgYGB7cn0KbUxhbWJkYTFzZSA8LSBnbG1uZXQoeCA9IFhUcmFpbiwKICB5ID0gWVRyYWluLAogIGFscGhhID0gMSwKICBsYW1iZGEgPSBtQ3ZMYXNzbyRsYW1iZGEuMXNlLAogIGZhbWlseT0iYmlub21pYWwiKQoKbUxhbWJkYTFzZSAlPiUKICBjb2VmICU+JQogIHN1bW1hcnkKYGBgCgotLS0KCiMjIFRoZSBFbGFzdGljIE5ldAoKVGhlIGxhc3NvIGFuZCByaWRnZSByZWdyZXNzaW9uIGhhdmUgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHByb3BlcnRpZXMuCgotIExhc3NvCgogICAtIHBvc2l0aXZlOiBzcGFyc2Ugc29sdXRpb24KCiAgIC0gbmVnYXRpdmU6IGF0IG1vc3QgJFxtaW4obixwKSQgcHJlZGljdG9ycyBjYW4gYmUgc2VsZWN0ZWQKCiAgIC0gbmVnYXRpdmU6IHRlbmQgdG8gc2VsZWN0IG9uZSBwcmVkaWN0b3IgYW1vbmcgYSBncm91cCBvZiBoaWdobHkgY29ycmVsYXRlZCBwcmVkaWN0b3JzCgoKLSBSaWRnZQoKICAgIC0gbmVnYXRpdmU6IG5vIHNwYXJzZSBzb2x1dGlvbgogICAgLSBwb3NpdGl2ZTogbW9yZSB0aGFuICRcbWluKG4scCkkIHByZWRpY3RvcnMgY2FuIGJlIHNlbGVjdGVkCgpBIGNvbXByb21pc2UgYmV0d2VlbiBsYXNzbyBhbmQgcmlkZ2U6IHRoZSAqKmVsYXN0aWMgbmV0Kio6ClxbCiAgXGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19ID0gXHRleHR7QXJnTWF4fV9cYmV0YSBsKFxib2xkc3ltYm9se1xiZXRhfSkgLVxnYW1tYV8xIFxWZXJ0IFxib2xkc3ltYm9sXGJldGFcVmVydF8xIC1cZ2FtbWFfMiBcVmVydCBcYm9sZHN5bWJvbFxiZXRhXFZlcnRfMl4yLgpcXQoKVGhlIGVsYXN0aWMgZ2l2ZXMgYSBzcGFyc2Ugc29sdXRpb24gd2l0aCBwb3RlbnRpYWxseSBtb3JlIHRoYW4gJFxtaW4obixwKSQgcHJlZGljdG9ycy4KCi0tLQoKVGhlIGBnbG1uZXRgIFIgZnVuY3Rpb24gdXNlcyB0aGUgZm9sbG93aW5nIHBhcmFtZXRlcmlzYXRpb24sClxbCiAgXGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19ID0gXHRleHR7QXJnTWF4fV9cYmV0YSBsKFxib2xkc3ltYm9se1xiZXRhfSkgLVxsYW1iZGFcYWxwaGEgXFZlcnQgXGJvbGRzeW1ib2xcYmV0YVxWZXJ0XzEgLVxsYW1iZGEoMS1cYWxwaGEpIFxWZXJ0IFxib2xkc3ltYm9sXGJldGFcVmVydF8yXjIuClxdCgotICRcYWxwaGEkIHBhcmFtZXRlciBnaXZlcyB3ZWlnaHQgdG8gJExfMSQgcGVuYWx0eSB0ZXJtIChoZW5jZSAkXGFscGhhPTEkIGdpdmVzIHRoZSBsYXNzbywgYW5kICRcYWxwaGE9MCQgZ2l2ZXMgcmlkZ2UpLgoKLSBhICRcbGFtYmRhJCBwYXJhbWV0ZXIgdG8gZ2l2ZSB3ZWlnaHQgdG8gdGhlIHBlbmFsaXNhdGlvbgoKLSBOb3RlIHRoYXQgdGhlIGNvbWJpbmF0aW9uIG9mICRcbGFtYmRhJCBhbmQgJFxhbHBoYSQgZ2l2ZXMgdGhlIHNhbWUgZmxleGliaWxpdHkgYXMgdGhlIGNvbWJpbmF0aW9uIG9mIHRoZSBwYXJhbWV0ZXJzICRcbGFtYmRhXzEkIGFuZCAkXGxhbWJkYV8yJC4KCi0tLQoKIyMjIEJyZWFzdCBjYW5jZXIgZXhhbXBsZQoKYGBge3J9Cm1FbGFzdGljIDwtIGdsbW5ldCgKICB4ID0gWFRyYWluLAogIHkgPSBZVHJhaW4sCiAgYWxwaGEgPSAwLjUsCiAgZmFtaWx5PSJiaW5vbWlhbCIpICAjIGVsYXN0aWMgbmV0CgpwbG90KG1FbGFzdGljLCB4dmFyID0gImxhbWJkYSIseGxpbT1jKC01LjUsLTEpKQpgYGAKCmBgYHtyfQptQ3ZFbGFzdGljIDwtIGN2LmdsbW5ldCh4ID0gWFRyYWluLAogIHkgPSBZVHJhaW4sCiAgYWxwaGEgPSAwLjUsCiAgZmFtaWx5ID0gImJpbm9taWFsIiwKCXR5cGUubWVhc3VyZSA9ICJjbGFzcyIpICAjIGVsYXN0aWMgbmV0CgpwbG90KG1DdkVsYXN0aWMpCm1DdkVsYXN0aWMKYGBgCgpgYGB7cn0KZGZFbGFzdCA8LSBkYXRhLmZyYW1lKAogIHBpID0gcHJlZGljdChtRWxhc3RpYywKICAgIG5ld3ggPSBYVGVzdCwKICAgIHMgPSBtQ3ZFbGFzdGljJGxhbWJkYS5taW4sCiAgICB0eXBlID0gInJlc3BvbnNlIikgJT4lIGMoLiksCiAga25vd24udHJ1dGggPSBZVGVzdCkKCnJvYyA8LSByYmluZCgKICBkZkxhc3NvT3B0ICU+JSBtdXRhdGUobWV0aG9kID0gImxhc3NvIiksCiAgZGZFbGFzdCAlPiUgbXV0YXRlKG1ldGhvZCA9ICJlbGFzdC4gbmV0IikpICU+JQogIGdncGxvdChhZXMoZCA9IGtub3duLnRydXRoLCBtID0gcGksIGNvbG9yID0gbWV0aG9kKSkgKwogIGdlb21fcm9jKG4uY3V0cyA9IDApICsKICB4bGFiKCIxLXNwZWNpZmljaXR5IChGUFIpIikgKwogIHlsYWIoInNlbnNpdGl2aXR5IChUUFIpIikKCnJvYwoKY2FsY19hdWMocm9jKQpgYGAKCi0gTW9yZSBwYXJhbWV0ZXJzIGFyZSB1c2VkIHRoYW4gZm9yIHRoZSBsYXNzbywgYnV0IHRoZSBwZXJmb3JtYW5jZSBkb2VzIG5vdCBpbXByb3ZlLgoKYGBge3J9Cm1FbGFzdGljT3B0IDwtIGdsbW5ldCh4ID0gWFRyYWluLAogIHkgPSBZVHJhaW4sCiAgYWxwaGEgPSAwLjUsCiAgbGFtYmRhID0gbUN2RWxhc3RpYyRsYW1iZGEubWluLAogIGZhbWlseT0iYmlub21pYWwiKQoKcXBsb3QoCiAgc3VtbWFyeShjb2VmKG1FbGFzdGljT3B0KSlbLTEsMV0sCiAgc3VtbWFyeShjb2VmKG1FbGFzdGljT3B0KSlbLTEsM10pICsKICB4bGFiKCJnZW5lIElEIikgKwogIHlsYWIoImJldGEtaGF0IikgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpCmBgYAoKIyBBY2tub3dsZWRnZW1lbnQgey19CgotIE9saXZpZXIgVGhhcyBmb3Igc2hhcmluZyBoaXMgbWF0ZXJpYWxzIG9mIEFuYWx5c2lzIG9mIEhpZ2ggRGltZW5zaW9uYWwgRGF0YSAyMDE5LTIwMjAsIHdoaWNoIEkgdXNlZCBhcyB0aGUgc3RhcnRpbmcgcG9pbnQgZm9yIHRoaXMgY2hhcHRlci4KCmBgYHtyLCBjaGlsZD0iX3Nlc3Npb24taW5mby5SbWQifQpgYGAK