1 Prerequisites

Before we even start, it is of crucial importance to first understand deeply linear and logistic regression. This is because (as we will see in later discussion) they are the very basic components in a general neural network model.

1.1 Linear Regression

A linear model can be written in a matrix form

\[ Y = X\beta + \epsilon, \]

where \(Y\) is the output or label vector of length \(N\) (number of observations), \(X\) is the input feature matrix (referred to as the design matrix in statistics) with dimension \(N\) by \(P\) (number of features), \(\beta\) is the model weights/coefficients (a column vector of length \(P\)) for which we’d like to solve, \(\epsilon\) is the model residual or error vector.

1.1.1 Ordinary Least Squares

The classical way to solve for \(\beta\) is ordinary least squares. The idea of OLS is find out the weights that minimize the mean of squared model errors:

\[ \begin{aligned} \mbox{mse (loss)} &= \frac{1}{N}\sum_i\epsilon^2_i \\ &= \frac{1}{N}\sum_i(y_i - \beta x_i)^2, \end{aligned} \]

where \(y_i\) and \(x_i\) is the \(i\)-th observation. The first-order condition (requiring that the first-order derivative w.r.t. weights are all zero) gives us the OLS solution for model weights \(\beta\) analytically (in matrix notation):

\[ \begin{equation} \label{eq:ols} \hat{\beta} = (X'X)^{-1}X'Y. \end{equation} \]

Let’s consider a toy model with only one non-constant feature:

\[ y_i = 6 + 4x_i + \epsilon_i. \]

In this model the outcome \(y\) is determined by a bias term plus a single variable \(x\), with an independently distributed noise term \(\epsilon \sim \mbox{Normal}(0, 1)\).

Create some random data generated from this model:

[[ 1.         -0.46820879]
 [ 1.         -0.82282485]
 [ 1.         -0.0653801 ]
 [ 1.         -0.71336192]
 [ 1.          0.90635089]
 [ 1.          0.76623673]
 [ 1.          0.82605407]
 [ 1.         -1.32368279]
 [ 1.         -1.75244452]
 [ 1.          1.00244907]]

Without consideration of the noise term, the OLS estimator will solve precisely for the true model weights:

[6. 4.]

Of course in the real world the noise term cannot be determined and usually the feature alone cannot explain entirely the variation in the outcome. This means that what we actually estimate will be the expected value of model weights:

[6.01132196 3.96247175]

We can check the result from scikit-learn (Pedregosa et al. (2011)):

[6.01132196 3.96247175]

By the Law of Large Number and Central Limit Theorem, under a relatively loose assumption of \(E(\epsilon | X) = 0\), the OLS estimator will converge in probability to the true model weights and distributed asymptotically Normal in large sample.1

The issue of the above approach is that equation \(\eqref{eq:ols}\) is not numerically stable when it comes to large-scale application where we may have lots of observations and lots of features. One very useful solution to solve the estimator numerically in large-scale application is the gradient descent approach.

1.1.2 Gradient Descent with Mean Squared Error

Instead of solving the first-order condition analytically, we can do it numerically. Gradient descent is a 1st-order optimization technique to find local optimum of a given function.

In the model training exercise our target function is the loss so the optimization problem is:

\[ \operatorname*{argmin}_\beta \mbox{Loss} = \frac{1}{N}\sum_i(y_i - \beta x_i)^2. \]

That is, we’d like to figure out model weights that minimize the loss which is defined by the mean squared errors when the model is a regression model.

The idea of gradient descent is to

  1. Derive the functional form of the gradient of loss w.r.t. to all weights
  2. Initialize all model weights randomly
  3. Calculate the gradient value using the actual data and the current value of weights
  4. Update the weights by (partially) the amount of gradient just calculated
  5. Repeat 3 and 4 until the resulting gradients become small enough

Let’s use the toy example to actually implement a gradent descent optimizer from scratch. First we re-write the loss function explicitly with our setup of one coefficient with a constant (\(\beta = [\beta_0, \beta_1]\)):

\[ \mbox{Loss} = \frac{1}{N}\sum_i\big[y_i-(\beta_0 + \beta_1x_i)\big]^2. \]

Now the gradient (or equivalently the 1st-order derivative) w.r.t. to weights will be:

\[ \begin{aligned} \frac{\partial\mbox{Loss}}{\partial\beta_0} &= - \frac{2}{N}\sum_i \big[ y_i - (\beta_0 + \beta_1x_i) \big], \\ \frac{\partial\mbox{Loss}}{\partial\beta_1} &= - \frac{2}{N}\sum_i \big[ y_i - (\beta_0 + \beta_1x_i) \big]x_i. \end{aligned} \]

The corresponding python function can be coded as:

If we set the above equations to zero we can solve for \(\beta_0\) and \(\beta_1\) analytically and the solution will be exactly just equation \(\eqref{eq:ols}\). But as we just pointed out it suffers from numerical stability issue.

The minimum implementation of our gradient descent optimizer is just a few lines:

Training Steps    10 | Estimate: [0.78324608 1.28348889]
Training Steps    50 | Estimate: [3.71164397 2.83692621]
Training Steps   100 | Estimate: [5.18681121 3.58312847]
Training Steps   500 | Estimate: [6.01108844 3.9624236 ]
Training Steps  1000 | Estimate: [6.01132195 3.96247175]
Training Steps  3000 | Estimate: [6.01132196 3.96247175]

As we can see the result is approaching to our analytical solution as the number of steps grows.

On Learning Rate

Learning rate is a hyper-parameter for gradient descent optimizer. The gradient update to our model weights is scaled down by the learning rate to make sure convergence. Too large the learning rate will explode the gradient. Too small the learning rate will slow down the convergence and sometimes result in the optimizer trapped at local sub-optimum.

Let’s re-write our gradient descent optimizer to also track the loss from each training step. And we use the same initialization for a fair comparison.

Now we run the optimization with a variety of different learning rates. For illustration purpose we will only run a few steps.

Learning Rate 0.001 | Estimate: [0.18155223 0.12379996]
Learning Rate  0.01 | Estimate: [1.60013258 1.08670659]
Learning Rate  0.05 | Estimate: [4.81547513 3.22246947]
Learning Rate   0.1 | Estimate: [5.81500752 3.85150789]
Learning Rate     1 | Estimate: [19.8158439  18.42703331]

The result suggests that a learning rate of 1 is too large for our problem. The gradient explodes which make our solution diverge. And a lower learning rate in general converges slower to the optimum. Number of examples used to calculate the gradient also will affect the convergence behavior. In general if the sample size is too small a smaller learning rate should be used to avoid gradient explosion.

This can be more clearly seen if we plot the trace of our training losses:

If the loss doesn’t decrease over training iteration, it is a signal that something is wrong with our model.

Unlike our toy implementation, in modern implementation of any numerical optimizer there will be a lot of techniques to do the best to avoid convergence failure. But it is the model developer’s responsibility to diagnose the training behavior before anything is delivered to the stakeholder. Checking the dynamics of loss is usually the first and quick step to examine whether the training task is functioning as expected.

Stocahstic Gradient Descent

The vanilla gradient descent optimizer we just implemented has one issue. Since for each update it needs to traverse over the entire dataset, it becomes too slow when it comes to large dataset.

Stochastic gradient descent is to overcome this issue. Instead of calculate the gradient using the entire dataset, we can use only one example per update. Each step will be less precise but statistically the final result should be consistent.

Here we introduce the term epoch: One epoch is for the optimizer to traverse the entire dataset once. Number of epochs can be considered as another hyper-parameter of a model.

Here comes the minimum SGD implementation:

Training Epochs    1 | Estimate: [5.94304058 3.9934523 ]
Training Epochs    5 | Estimate: [5.90854388 3.87784646]
Training Epochs   10 | Estimate: [6.16073223 4.04277355]
Training Epochs   50 | Estimate: [5.92701188 3.86582267]
Training Epochs  100 | Estimate: [6.10863654 3.89402869]

SGD won’t be as precise as vanilla GD but for large scale application it can reduce considerable amount of computing time (sometimes from infeasible to feasible). In our simple problem indeed just 1 epoch can have a good approximation already.

The trace of (per-instance) loss for the first 500 updates:

Since the loss is calculated on a per-instance basis, it will fluctuate but with a decreasing trend if nothing went wrong about the optimization process.

In sklearn there are dedicated SGD classes for a variety of learning algorithms. For a linear regression model with SGD solver:

[6.01665156 3.97557829]

Batch Gradient Descent

To reduce the noise in SGD we can modify it by replacing 1 training example with a batch of examples in a single gradient update. Here’s such implementation of a batch gradient descent optimizer2:

[6.01401595 3.96198209]

For this simple problem batch size doesn’t have any important impact given enough number of training epochs:

Batch Size:    8 | Estimate: [6.05434008 3.95548771]
Batch Size:   16 | Estimate: [6.01925548 3.97143916]
Batch Size:   32 | Estimate: [6.00071779 3.9636924 ]
Batch Size:   64 | Estimate: [6.01279841 3.96202727]
Batch Size:  128 | Estimate: [6.01202513 3.96292586]
Batch Size:  256 | Estimate: [6.00931051 3.9613001 ]

Batch optimizer is currently the best practice of training neural networks in large scale application. The batch size depends on the actual application but usually ranges from 8 (mini) to 1024 (large). The mini-batch stochastic gradient descent is so previal that in general when people talk about SGD for a neural network they are actually referring to the batch gradient descent.

1.2 Logistic Regression

A logistic regression models the outcome probablistically:

\[ \begin{equation} \label{eq:logit} P(Y = 1) = \frac{1}{1 + e^{-X\beta}}, \end{equation} \]

Here the sigmoid function \(s(t) = \frac{1}{1 + e^{-t}}\) is used to transform a real number into probability space \([0, 1]\).

We can interpret the model as a linear model in log-odds. Assuming Y is binary and take a value of 0 or 1, the odds of \(Y = 1\) is defined as \(\frac{P(Y = 1)}{P(Y = 0)} = \frac{P(Y = 1)}{1 - P(Y = 1)}\). We can re-arrange the logistic model equation:

\[ \begin{aligned} \ln \Bigg[ \frac{P(Y = 1)}{1 - P(Y = 1)} \Bigg] &= \ln \Bigg[ \frac{\frac{1}{1 + e^{-X\beta}}}{\frac{e^{-X\beta}}{1 + e^{-X\beta}}} \Bigg] \\ &= \ln(1 + e^{-X\beta}) - \ln e^{-X\beta}(1 + e^{-X\beta}) \\ &= \ln(1 + e^{-X\beta}) - \ln e^{-X\beta} - \ln(1 + e^{-X\beta}) \\ &= - \ln e^{-X\beta} \\ &= X\beta. \end{aligned} \]

That is, the model weights are linear in the log-odds of our target outcome.

When the probability is transformed into odds, the range is transformed from \([0,1]\) to \([0,\infty]\).

If we further transform odds to log-odds, the range is transformed from \([0,\infty]\) to \([-\infty,\infty]\).

Put it together is the effect of the sigmoid function:

1.2.1 Cross Entropy

How do we solve for the model weights \(\beta\) in equation \(\eqref{eq:logit}\)? In the linear regression model we solve for the weights by minimizing the mean squared error. In logistic regression model we need to define measurement for modeling error as well.

Put it differently, we’d like to calculate the distance between our predicted probability and the real event label distribution (called the empirical distribution). In information theory the cross entropy is used to measure the distance between two probability distribution.

Shannon’s Entropy as a Measure of Information

The entropy of a discrete probability distribution is defined as:

\[ \begin{equation} \label{eq:entropy} H(p) = - \sum_i p_i \log_2p_i, \end{equation} \]

where \(p_i\) is the probability for event \(i\). It measures the uncertainty of a stochastic event.

Take a coin flip event as example. We plot the entropy value at different value of coin bias (the probability of having a head instead of a tail.)

It is obvious that the entropy of this event is maximized when the probability of flipping a head is exactly 0.5. At this level the event has a highest level of uncertainty in a sense that it is the most difficult case to predict the outcome of a flip.

What is the intuition behind entropy?

Surprisal in Terms of Rarity

In equation \(\eqref{eq:entropy}\) we first need to interpret the seemingly mysterious term \(-\log_2p_i\). It is the log of the reciprocal of a probability. Generally speaking, the reciproal of a probability \(\frac{1}{p(x)}\) is a 1-in-n scale of probability statement saying that in order for the outcome \(x\) to occur at least once from a stochastic event, it is expected to require having repeated \(n = \frac{1}{p(x)}\) times the event. The rare the outcome, the higher the reciprocal of its probability. When a rare event occurs, it contains much more information than a frequent (or more probable) one. Information in this way can be considered as the level of surprisal.

Information Encoding

But why taking log? Especially why log of base two in Shannon’s entropy? This relates to the use of bits to encode messages/information/random states/facts. A bit (0 or 1) can be used to encode two facts. For example, 0 for a tail and 1 for a head in a coin flip exercise. Apparently if we have \(N\) bits we can encode up to \(2^N\) different outcomes in a stochastic event.

The discussion doesn’t limit to binary. If we have 100 possible outcomes from a random event, with 10 distinct symbols (0 to 9), we only need \(\log_{10}100 = 2\) digits to encode them (decimal 0 to 99).

Now put them together, the rare the outcome, the higher the reciprocal of its probability, and the more bits required to encode this information (so as to distinguish from other possible outcomes).

The formula \(\log_2\frac{1}{P(x)}\) calculates how many bits are necessary to encode a stochastic outcome \(x\) that may occur once out of \(\frac{1}{P(x)}\) times. Let’s call this amount as the information contained by outcome \(x\). Then the entropy in \(\eqref{eq:entropy}\) essentially is calculating the expected number of bits required to encode any outcome possible from the given stochastic event. Or it is the expected information contained by a given stochastic event.

One last thing to note is that the number of bits here is a mathematical artifect that could not always be represented by our discrete real world. Certainly we don’t have non-integer bit in real world computing but the value of entropy is a positive real number not limited to integer.

Distributional Difference

Now move on to cross entropy. Cross entropy between two discrete probability distribution \(p\) and \(q\) over the same support is defined as:

\[ \begin{equation} \label{eq:crossentropy} H(p, q) = - \sum_i p_i \log_2q_i. \end{equation} \]

Intuitively, it means that if we encode a stochastic event with a probability distribution NOT linked to the event, for example a predicted distribution \(q\) based on a model, while the true distribution is \(p\), what will be the expected number of bits required to encode all possibilities from that event.

To understand cross entropy one useful concept is the Kullback-Leibler divergence from \(q\) to \(p\):

\[ \begin{aligned} KL(p \vert\vert q) &= H(p, q) - H(p) \\ &= \sum_ip_i\log_2\frac{p_i}{q_i}. \end{aligned} \]

Mathematically it is just the sum of difference of log-probability from two distribution \(p\) and \(q\), weighted by \(p\). Distribution \(p\) is the reference distribution and \(q\) the approximation distribution. It is a measure of how closely the distribution \(q\) is approximating \(p\). If \(q\) is perfectly mimicking \(p\), the KL divergence is 0 and the cross entropy between \(p\) and \(q\) is the same as the entropy of \(p\): \(H(p, q) = H(p)\).

Based on the above formula we can also interpret KL divergence as the extra bits required due to encoding a stochastic event with a wrong probability distribution.

For our logistic regression model, distribution \(p\) is the empirical distribution (label distribution) and distribution \(q\) is our model predicted distribution. Denote \(q_i = P(y_i = 1)\) for the prediction of \(i\)-th example. The cross-entropy-loss of our model hence can be written as:

\[ \mbox{Cross-Entropy Loss} = - \frac{1}{N}\sum_i^N \bigg[ y_i\log_2q_i + (1 - y_i)\log_2(1 - q_i)\bigg], \]

where \(y_i \in \{0,1\}\) is the \(i\)-th binary training label and \(P(y_i = 1)\) the model prediction for the \(i\)-th example out of \(N\) total training examples. This is the mean value of cross entropy for each training example. It measures how well our model is predicting the label based on the distributional difference between the labels and the model outputs.

Since \(q_i = P(y_i = 1)\) is expressed by our model equation \(\eqref{eq:logit}\), now we can apply gradient descent to the loss function to find out the optimum model weights that minimize the cross-entropy loss.

1.2.2 Maximum Likelihood Estimator

Before we implement our optimizer for a logistic regression model, we demonstrate that minimizing cross entropy is indeed equivalent to maximizing data likelihood.

The data likelihood is just the product of all predicted probabilities for individual example, provided that they are independent.

\[ \mbox{likelihood} = \prod_i^N q_i^{y_i}(1 - q_i)^{1 - y_i}. \]

The maximum likelihood estimator will try to maximize the log of the likelihood, which is:

\[ \begin{aligned} \mbox{log-lik} &= \log_2 \prod_i^N q_i^{y_i}(1 - q_i)^{1 - y_i} \\ &= \sum_i^N \log_2 q_i^{y_i}(1 - q_i)^{1 - y_i} \\ &= \sum_i^N \bigg[ y_i\log_2 q_i + (1 - y_i)\log_2(1 - q_i) \bigg]. \end{aligned} \]

By taking the average as well, the negative log-likelihood is exactly the cross entropy.

Though we didn’t discuss this linkage in linear regression, if we assume the error term in the model is independently distributed Normally, the OLS solution to the model weights (equation \(\eqref{eq:ols}\)) is indeed also the MLE solution.

1.2.3 Gradient Descent with Log Loss

Now let’s also implement a batch gradient descent optimizer for a logistic regression model. We will use the same toy example and create additional random binary labels for this exercise.

The gradient function must be derived with respect to model weights. To simplify the math we replace log of base 2 with natural log (with no impact on the optimization), and we take advantage of the fact that the derivative of the sigmoid function is:3

\[ \begin{aligned} \frac{ds(t)}{dt} &= \frac{d\frac{1}{1 + e^{-t}}}{dt} \\ &= -(1 + e^{-t})^{-2} \cdot (- e^{-t}) \\ &= \frac{e^{-t}}{(1 + e^{-t})^2} \\ &= \frac{1}{1 + e^{-t}} \cdot \frac{e^{-t}}{1 + e^{-t}} \\ &= s(t) \cdot [1 - s(t)]. \end{aligned} \]

The gradient in our univariate model w.r.t. the bias term will be:4

\[ \begin{aligned} \frac{\partial\mbox{LogLoss}}{\partial\beta_0} &= - \frac{1}{N}\sum_i \bigg[ y_i\frac{\partial\ln q_i}{\partial\beta_0} + (1 - y_i)\frac{\partial\ln(1 - q_i)}{\partial\beta_0}\bigg] \\ &= - \frac{1}{N}\sum_i \bigg[ y_i\frac{1}{q_i}\frac{\partial q_i}{\partial\beta_0} + (1 - y_i)\frac{1}{1 - q_i}\frac{\partial(1 - q_i)}{\partial\beta_0}\bigg] \\ &= - \frac{1}{N}\sum_i \bigg[ y_i\frac{1}{q_i}\frac{\partial (\beta_0 + \beta_1x_i)}{\partial\beta_0}\frac{\partial q_i(t)}{\partial t} - (1 - y_i)\frac{1}{1 - q_i}\frac{\partial (\beta_0 + \beta_1x_i)}{\partial\beta_0}\frac{\partial q_i(t)}{\partial t}\bigg] \\ &= - \frac{1}{N}\sum_i \bigg[ y_i\frac{1}{q_i}q_i(1 - q_i) - (1 - y_i)\frac{1}{1 - q_i}q_i(1 - q_i)\bigg] \\ &= - \frac{1}{N}\sum_i \bigg[ y_i(1 - q_i) - (1 - y_i)q_i \bigg] \\ &= - \frac{1}{N}\sum_i (y_i - q_i). \end{aligned} \]

Similary for the weight:

\[ \frac{\partial\mbox{LogLoss}}{\partial\beta_1} = - \frac{1}{N}\sum_i (y_i - q_i)x_i. \]

Now the Python code for batch gradient descent with log-loss:

[2.50014806 1.28482629]

Comparing to linear regression, a logistic regression model is harder to converge. Since our naive implementation does not do convergence diagnostics, let’s use R’s built-in glm function which use Newton’s method (a 2nd-order optimizer utilizing not only 1st-order but also 2nd-order derivatives) to check the estimation result of our toy example:

(Intercept)           x 
    6.65212     4.50262 

Now increase both the learning rate and training epochs of our naive gradient descent optimizer:

[6.66970964 4.45789135]

Seems better.

Or we can use the Python package statsmodels (Seabold and Perktold (2010)), which also uses a 2nd-order optimizer by default, to check our result:

Optimization terminated successfully.
         Current function value: 0.107650
         Iterations 10
[6.65211962 4.50262017]

Or the sklearn result:

[[6.65211771 4.5026188 ]]

2 Automatic Differentiation

In the previous section we implement a simple gradient descent optimizer by manually derive the functional form of gradient on our own. This could be troublesome if our model becomes more and more complicated, as in the case of a deep neural net.

Automatci differentiation is a programming technique to calculate the gradient of any given function. One of the most popular library for this purpose is TensorFlow (Abadi et al. (2015)).

Let’s use tensorflow to implement our simple gradient descent optimizer again. But this time we will NOT explicitly derive the gradient function. Instead, we will only specify the target function which is just the loss function of our model.

1.13.1

WARNING: The TensorFlow contrib module will not be included in TensorFlow 2.0.
For more information, please see:
  * https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md
  * https://github.com/tensorflow/addons
If you depend on functionality not listed there, please file an issue.

[[6.0113106]
 [3.9624665]]
[[6.6248612]
 [4.482333 ]]

In the above coding example one should realize that we no longer need to hardcode the functional form of the gradients. Instead we just plug-in the loss function and let tensorflow to do the gradient calculation for us.

Of course in actual development we will use higher-level APIs to implement our model, where the entire optimization process is abstracted away from the application code.

3 Neural Networks

Both linear regression and logistic regression can be considered as simple additive model of the form:

\[ \hat{y} = \Phi\bigg(\sum_{i=1}^Pw_ix_i\bigg), \]

where \(P\) is the number of features used, \(x_i\) is the \(i\)-th feature, and \(\Phi(\cdot)\) is a function applied to the output. In linear regression \(\Phi(\cdot)\) is simply an identity function. In logistic regression \(\Phi(\cdot)\) is the standard sigmoid function.

Now consider there is a way to ensemble multiple such additive models together to generate a potentially better and more sophisticated model. We use the following diagram for illustration.

In the above diagram, \(Y_{11}\) is a single additive model

\[ Y_{11} = \Phi(W_{111}X_1 + W_{211}X_2 + W_{311}X_3). \]

Similarly \(Y_{12}\) is another such model (with the same input feature set but different model weights)

\[ Y_{12} = \Phi(W_{112}X_1 + W_{212}X_2 + W_{312}X_3). \]

Model \(Y_2\) is yet another additive model but takes the output of the above two models:

\[ Y_2 = \Phi(W_{1121}Y_{11} + W_{1221}Y_{12}). \]

The above setup is a simple architecture of a neural network model, with only one hidden layer of two neurons. (We also ignore the constant/bias term in each layer for simplicity.) A neuron is simply an additive model with a so-called activation function \(\Phi(\cdot)\) to transform the output from any real number into a scaled signal.

One now can easily realize that a logistic regression model could be viewed as a degenerated neural network model with single neuron and using sigmoid as the activation function. And a linear regression model is a degenerated neural network model with single neuron and without an activation function.

3.1 Shallow v.s. Deep

The above simple neural network is indeed a shallow one, with only one hidden layer. Conventionally people refer to a deep neural network as a network with at least 2 hidden layers. The buzz word deep learning is used to describe complicated neural network architecture that can solve problems that traditional machine learning algorithms have difficulty dealing with, especially in the field of image (and video) and natural language (and voice) processing and understanding.

Deep model is indeed much more than just adding more hidden layers. The way neuron is connected and how they are structured can be highly creative (for outstanding examples being convolutional and recurrent neural network) and way beyond the scope of this notebook. Though a deep model is definitely more complicated than a shallow one, the foundation is all the same.

3.2 Activation Function

Why do we need the activation function? In the above neural network model in the absence of activation function the final output model \(Y_2\) will degenerate into a simple linear model. We can use simpler notations to demonstrate this:

\[ \begin{aligned} y_1 &= ax + b, \\ y_2 &= cx + d, \\ y_3 &= e + fy_1 + gy_2 \\ &= e + f(ax + b) + g(cx + d) \\ &= \underbrace{(e + fb + gd)}_\text{Bias} + \underbrace{(fa + gc)}_\text{Weight}x. \end{aligned} \]

Without activation function, no matter how many neurons or layers we design for our model, it eventually reduces to a simple linear model. With the activation function applied to each neuron, the model becomes non-linear and hence can handle much more complicated patterns hidden behind the data.

Some popular activation functions:

3.3 Backpropagation

To solve for model weights in a neural network, we use a technique called backpropagation which is essentially an iterative process of gradient descent.

To simplify notation we assume each neuron is simply a univariate model. Consider the following minimum architecture:

Mathematically:

\[ \begin{aligned} \hat{y} &= \Phi(b_1 + w_1z) \\ &= \Phi(b_1 + w_1\Phi(b_0 + w_0x)), \end{aligned} \]

where

\[ z = \Phi(b_0 + w_0x), \]

and

\[ \Phi(t) = \frac{1}{1 + e^{-t}}. \]

3.3.1 MSE Loss

Though it may not be very meaningful to use MSE loss when the output layer is applied with an activation function, we can still do it for educational purpose.

\[ \mbox{MSE-Loss} = \frac{1}{N}\sum_i^N (y_i - \hat{y}_i)^2. \]

Firstly we take the derivative w.r.t. the weight in the last layer:

\[ \begin{aligned} \frac{\partial \mbox{MSE-Loss}}{\partial w_1} &= - \frac{1}{N}\sum_i (y_i - \hat{y}_i)\frac{\partial \hat{y}_i}{\partial w_1} \\ &= - \frac{1}{N}\sum_i (y_i - \hat{y}_i) \underbrace{ \frac{\partial t}{\partial w_1}\frac{\partial \Phi(t)}{\partial t} }_{t = b_1 + w_1\Phi(b_0 + w_0x)}\\ &= - \frac{1}{N}\sum_i (y_i - \hat{y}_i) \cdot \Phi(t)(1 - \Phi(t)) \cdot z_i. \end{aligned} \]

Similarly for the bias in the last layer:

\[ \frac{\partial \mbox{MSE-Loss}}{\partial b_1} = - \frac{1}{N}\sum_i (y_i - \hat{y}_i) \cdot \Phi(t)(1 - \Phi(t)). \]

Now move on to the bias and weight in the first layer:

\[ \begin{aligned} \frac{\partial \mbox{MSE-Loss}}{\partial w_0} &= - \frac{1}{N}\sum_i (y_i - \hat{y}_i)\frac{\partial \hat{y}_i}{\partial w_0} \\ &= - \frac{1}{N}\sum_i (y_i - \hat{y}_i) \underbrace{ \frac{\partial t}{\partial w_0}\frac{\partial \Phi(t)}{\partial t} }_{t = b_1 + w_1\Phi(b_0 + w_0x)}\\ &= - \frac{1}{N}\sum_i (y_i - \hat{y}_i) \cdot \Phi(t)(1 - \Phi(t)) \cdot \underbrace{ \Phi(k)(1 - \Phi(k)) }_{k = b_0 +w_0x} \cdot w_1 \cdot x_i, \\ \frac{\partial \mbox{MSE-Loss}}{\partial b_0} &= - \frac{1}{N}\sum_i (y_i - \hat{y}_i) \cdot \Phi(t)(1 - \Phi(t)) \cdot \Phi(k)(1 - \Phi(k)). \end{aligned} \]

One can clearly see there is a linkage between the derivative of the weights in consecutive layers:

\[ \begin{aligned} \frac{\partial \mbox{MSE-Loss}}{\partial w_1} &= - \frac{1}{N}\sum_i (y_i - \hat{y}_i) \cdot \Phi(t)(1 - \Phi(t)) \cdot z_i, \\ \frac{\partial \mbox{MSE-Loss}}{\partial w_0} &= - \frac{1}{N}\sum_i (y_i - \hat{y}_i) \cdot \underbrace{\Phi(t)(1 - \Phi(t))}_{\hat{y}_i(1 - \hat{y}_i)} \cdot \underbrace{\Phi(k)(1 - \Phi(k)) \cdot w_1 \cdot x_i}_{\frac{\partial w_1z}{\partial w_0} = \frac{\partial w_1\Phi(b0 + w_0x)}{\partial w_0}}. \end{aligned} \]

3.3.2 Cross-Entropy Loss

Similarly we can derive the gradients for cross-entropy loss.

\[ \begin{aligned} \mbox{LogLoss} &= - \frac{1}{N}\sum_i^N \bigg[ y_i\ln\hat{y}_i + (1 - y_i)\ln(1 - \hat{y}_i)\bigg], \\ \frac{\partial \mbox{LogLoss}}{\partial w_1} &= - \frac{1}{N} \sum_i (y_i - \hat{y}_i)z_i, \\ \frac{\partial \mbox{LogLoss}}{\partial w_0} &= - \frac{1}{N} \sum_i (y_i - \hat{y}_i)\Phi(k)(1 - \Phi(k))w_1x_i.\\ \end{aligned} \]

(Skip the bias terms to save space.)

Now let’s implement the simple neural network model in Python:

Here we will use the hello-world example of artificial neural network: The XOR problem. The XOR logical outcome, besides extremely simple, is not linearly separable. So it serves as a good example of showcasing neural networks’ non-linearity.

The input data is simply combination of two binary switches. (For completeness we also include a constant term which always evaluate to 1 as the first feature.) The output data is the XOR result.

[[1 0 0]
 [1 1 0]
 [1 0 1]
 [1 1 1]]
[[0]
 [1]
 [1]
 [0]]

Now let’s see if our simple neural network model can learn the XOR pattern.

0.013322228699994642,0.9755617091310563,0.9799803737183681,0.024680348085204088
[[-1.78102396 -4.57750429  1.42272976 -0.69372436]
 [ 6.06730656  2.57250239  5.87690076 -1.30777168]
 [ 5.6074491   3.44863401 -3.70793822  4.57256567]]
[[10.35252814]
 [-5.91518016]
 [-5.30338267]
 [-4.3944565 ]]
0.0007966687865677773,0.9998283998346575,0.9990768834133713,0.0004795536969026687

For this simple example, both MSE and cross-entropy loss can work fine to figure out the XOR pattern. (Strictly speaking cross-entropy loss performs better.)

4 Regularization

Regularization is a technique to mitigate overfitting. It is not particular to neural networks but general to all machine learning models.

A regularization is a constraint added onto the original optimization problem (loss minimization). Consider model weights as a real vector, we usually use the norm of the vector to constrain its size in the optimization. The norm is a measure of size of a vector. The general idea is to add a penalty to the target function (which is a loss function) such that it discourages using large weights (which increase the size of vector) to achieve the minimization goal.

In general, a MSE loss minimization with a p-norm regularization for a linear model can be written (in matrix form) as:

\[ \begin{aligned} \hat{y} &= X\mathrm{B}, \\ \operatorname*{argmin}_\beta \mbox{Loss} &= \frac{1}{N}\bigg[ \underbrace{(y - X\mathrm{B})^T(y - X\mathrm{B})}_\text{sum of squared errors} + \underbrace{\lambda\Vert\mathrm{B}\Vert_{p}}_\text{p-norm regularization} \bigg], \end{aligned} \]

with a model weight vector \(\mathrm{B} = [\beta_1, \beta2, ..., \beta_k]\). The parameter \(\lambda\) is a new hyper-parameter introduced by the regularization.

Or put it in a scalar form:

\[ \begin{equation} \label{eq:loss_min_reg} \operatorname*{argmin}_\beta \mbox{Loss} = \frac{1}{N}\sum_{i=1}^N\bigg( y_i - \mathrm{B}x_i\bigg)^2 + \frac{\lambda}{N}\bigg(\sum_{j=1}^k\vert\beta_j\vert^p\bigg)^{1/p}. \end{equation} \]

Popular choices of \(p\) is \(p = 1\) (L1-Norm) and \(p = 2\) (L2-Norm). Notably, \(p\) can be any non-integer real value and would have even better result than integer-norm. But their computational difficulty make them much less desirable in practice.

In practice, we also ignore the root in the norm for computational simplicity. So the problem indeed becomes

\[ \operatorname*{argmin}_\beta \mbox{Loss} = \frac{1}{N}\sum_{i=1}^N\bigg( y_i - \mathrm{B}x_i\bigg)^2 + \frac{\lambda}{N}\bigg(\sum_{j=1}^k\vert\beta_j\vert^p\bigg). \]

There is also a so-called elasticnet regularization which combines both L1 and L2 norms together to give potentially better result. However it requires a lot more computing resources and hence become much less feasible in a neural network model.

4.1 L2-Norm Regularization

The L2-Norm shrinks the size of model weights. Those who contribute less to loss minimization will shrink more.

Back to our univariate linear model \(\hat{y}_i = \beta_0 + \beta_1x_i\) for symolic simplicity. Our MSE loss is

\[ \mbox{MSE-Loss} = \frac{1}{N}\sum_i\big[y_i-(\beta_0 + \beta_1x_i)\big]^2 + \frac{\lambda}{N}(\beta_0^2 + \beta_1^2). \]

Since the penaty is additive, our gradient solution is extremely easy:

\[ \begin{aligned} \frac{\partial\mbox{MSE-Loss}}{\partial\beta_0} &= - \frac{2}{N}\sum_i \big[ y_i - (\beta_0 + \beta_1x_i) \big] + \frac{2}{N}\lambda\beta_0, \\ \frac{\partial\mbox{MSE-Loss}}{\partial\beta_1} &= - \frac{2}{N}\sum_i \big[ y_i - (\beta_0 + \beta_1x_i) \big]x_i + \frac{2}{N}\lambda\beta_1. \end{aligned} \]

A linear regression model with l2 regularization is referred to as the ridge regression. Be aware that in implementing the batch optimizer we need to scale down the update from l2 norm by the size of batch in order to balance the size of l2 norm and the original loss.

[5.917945   3.90542865]

The resulting estimates are smaller than the vanilla gradient descent.

One thing worth noting is that when batch gradient descent is used along with regularization, a side effect exists due to the scaling of the batch size on the regularization term.

Experiments with different batch sizes with l2 regularization:

Batch Size:    8 | Estimate: [5.40822766 3.58409232]
Batch Size:   16 | Estimate: [5.65860196 3.73012032]
Batch Size:   32 | Estimate: [5.82685968 3.81718301]
Batch Size:   64 | Estimate: [5.91404397 3.90443054]
Batch Size:  128 | Estimate: [5.9665459  3.93418727]
Batch Size:  256 | Estimate: [5.98786988 3.94779378]
Batch Size:  512 | Estimate: [5.99939005 3.95494569]
Batch Size: 1000 | Estimate: [6.00521432 3.95865926]

Let’s check the result using sklearn’s Ridge regressor (which doesn’t use gradient descent as its solver):

[5.99956319 3.95500059]

The result is closed to gradient descent with nearly a full-batch update (i.e., use the entire training data for one update).

Exactly the same logic can apply to logistic regression and is not discussed here to avoid redundancy.

4.2 L1-Norm Regularization

The L1-Norm prefers sparsity in the model weights. That is, it may zero out weights that are not contributing to the loss. While the L2-Norm only makes them arbitrarily small.

MSE loss with l1 norm:

\[ \mbox{MSE-Loss} = \frac{1}{N}\sum_i\big[y_i-(\beta_0 + \beta_1x_i)\big]^2 + \lambda(\vert\beta_0\vert + \vert\beta_1\vert), \]

with gradients:5

\[ \begin{aligned} \frac{\partial\mbox{MSE-Loss}}{\partial\beta_0} &= - \frac{2}{N}\sum_i \big[ y_i - (\beta_0 + \beta_1x_i) \big] + \lambda\frac{\beta_0}{\vert\beta_0\vert}, \\ \frac{\partial\mbox{MSE-Loss}}{\partial\beta_1} &= - \frac{2}{N}\sum_i \big[ y_i - (\beta_0 + \beta_1x_i) \big]x_i + \lambda\frac{\beta_1}{\vert\beta_1\vert}. \end{aligned} \]

A linear regression model with l1 regularization is referred to as the lasso regression. Let’s first check the result using sklearn’s Lasso regressor (which uses a coordinate descent solver):

[5.042239   3.00143701]

To derive roughly the same result, we need to calibrate the coefficient on regularization in our gradient descent solver:

[5.04223889 3.00143701]

We can also check the consistency by calling sklearn’s SGDRegressor:

[5.04755575 3.02641941]

Now let’s purposely add one random feature as a noise into the design matrix and see how the regularization helps to shrink the weight of the noise.

[5.98214338 3.95518688 0.03392461]
[5.042239   3.00143701 0.        ]

As one can see, Lasso regressor is able to completely wipe out the weight on the redundant noise, while Ridge can only shrink it toward zero.

To arrive roughly at the same result of Ridge using our simple gradient descent solver:

[5.97074062 3.95918406 0.0629631 ]

And for the Lasso case:

[ 5.02107606e+00  3.00162683e+00 -2.54383624e-03]

One thing to note: It is in general difficult to arrive at sparse solution (0 weight) for a naive gradient descent implementation such as ours. Some further mathematical tricks like the proximal gradient metohd must be applied in order to produce sparse result under finite iterations.

Finally, again, exactly the same logic can apply to logistic regression and is not discussed further to avoid redundancy.

4.3 Geometric Interpretation of L1/L2 Regularization

Consider a two-parameter modeling case, where we plot the two possible weights \(w_1\) and \(w_2\) as a Euclidean coordinate system. The MSE loss minimization problem stated in equation \(\eqref{eq:loss_min_reg}\) can be rewritten as an optimization with linear constraint:

\[ \begin{aligned} & \operatorname*{argmin}_\beta & & \frac{1}{N}\sum_{i=1}^N\bigg( y_i - \mathrm{B}x_i\bigg)^2 \\ & \text{subject to} & & \bigg(\sum_{j=1}^k\vert\beta_j\vert^p\bigg)^{1/p} \le t. \end{aligned} \]

Solving the above problem (using for example the method of Lagrange multipliers) will result in exactly the same target function as in equation \(\eqref{eq:loss_min_reg}\)

Now if we plot the linear constraint on the Euclidean space (for the case of \(k = 2\) and an arbitrary value of constant \(t\)), the possible parameter combination will fall into a cirle for \(p = 2\) (L2 regularization) or a squared diamond for \(p = 1\) (L1 regularization), both centered at the origin.

Simply plot the constraint area is not that informative. We need to also visualize the possible values of loss function.

Let’s use the toy model in our previous linear regression section to do the visualization. We will iterate over the coordinate space within \(-10 \le w_1 \le 10\) and \(-10 \le w_2 \le 10\) to collect all possible resulting loss values. The loss will be a 3rd axis added onto the existing plain.

Note that we also plot the contour on the original 2-d plain. The contour depicts all the weight combinations that lead to the same amount of loss. And we mark the minimum with a cross to clearly point out the optimum loss which can be achieved by the unregularized OLS estimator in equation \(\eqref{eq:ols}\).

Now if we combine the two plots together, especially the contour on the parameter plain, the tangent point between the regularization area and the contour is exactly our regularized solution for the weights.6

The constraint constant control the size of regularization, for a larger constant (looser constraint) our tagent point will move closer to the unregularized optimum. For this geometric point of view, one should also realize that since the L1 regularization area is a sharp diamond square, it is more likely to tangent the contour on the edge which exactly represent a sparse solution to the weight (on each edge point there is one weight zeroed out).

4.4 Probabilistic Interpretation of L1/L2 Regularization

If we took a Bayesian approach on the weight estimation, the task is to solve for the posterior distribution of weight:

\[ P(W|y) = \frac{P(y|W) \cdot P(W)}{P(y)} \propto P(y|W) \cdot P(W). \]

then a Normal prior on the weight \(W \sim \mbox{Normal}\) will result in a maximum a posteriori estimator to have a target function with the L2 regularization term, and a Laplacean prior \(W \sim \mbox{Laplace}\) will result in the L1 term.

This is quite straightforward if we take a look at the probability density function of these two distributions:

\[ \begin{aligned} \mbox{Normal}(\mu, \sigma)_\text{pdf} &= f(w) = \frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{(w - \mu)^2}{2\sigma^2}}, \\ \mbox{Laplace}(\mu, b)_\text{pdf} &= g(w) = \frac{1}{2b}e^{-\frac{\vert w - \mu\vert}{b}}. \end{aligned} \]

Now if we want to maximize the log of \(P(W|y)\) (maximum a posteriori), the gradient to the log of a Normal prior will give us:

\[ \begin{aligned} \frac{\partial \ln f(w)}{\partial w} &= \frac{\partial \bigg[-\frac{1}{2}\ln(2\pi\sigma^2) - \frac{(w - \mu)^2}{2\sigma^2}\bigg]}{\partial w} \\ &= - \frac{1}{\sigma^2}(w - \mu), \end{aligned} \]

and for a Laplacean prior:

\[ \begin{aligned} \frac{\partial \ln g(w)}{\partial w} &= \frac{\partial \bigg[-\frac{1}{2b} - \frac{\vert w - \mu \vert}{b}\bigg]}{\partial w} \\ &= - \frac{1}{b}\frac{w}{\vert w \vert}. \end{aligned} \]

The regularization coefficient we previously discussed (hyper-parameter \(\lambda\)) can be mapped to the constant on the (reciprocal of) scale parameter of the Bayesian prior. A larger \(\lambda\) corresponds to a smaller variance on the prior, hence the size of weight is being constrained more in the posterior estimation, resulting in smaller weight.

We can plot the standardized Normal and Laplace distribution to clearly see their difference:

Compared to a Normal prior, a Laplacean put much more density at the center. When both distribution is standardized (with location at 0 and scale at 1) as just plotted above, this can be interpreted as that a Laplacean prior has a stronger preference for a sparse solution (weight = 0).

4.5 Dropout

Another practical and maybe also easier way of regularization is the dropout mechanism (Srivastava et al. (2014)). It is first introduced in a neural network model but the concept has been successfully applied as well to some other machine learning algorithms (such as gradient boosting trees).

Dropout of a neuron means that it is ignored in the backprop training process. Take our previous simple neural network model as example, there are two neuron in the hidden layer, if we dropout the first unit then the model temporarily becomes:

When a neuron is dropped out, its subsequent connection to the network are all removed. This means that both feed-forward and backpropagation will ignore the discarded connection.

Usually dropout is applied to a model by setting up a dropout rate to one or more layers. For example we can set the dropout rate to 50% at the first hidden layer. This means that for each batch gradient update every neuron in the layer will have a 50% chance of being ignored before a training step.

Dropout doesn’t limit to hidden layer only. One can also apply dropout at the input layer, but usually at a relatively lower drop rate (commonly 20%) than hidden layer. Certainly dropout will never be applied at a output layer because output layer is the required component to well define a model.

After dropout is applied, a neural network model becomes thinner in a sense that it has fewer number of weights to response to both input feed forward and error backprop. The rationale is that we’d like to prevent all the neurons from co-adapting too much in the training process to avoid overfitting to the training data.

4.5.1 Sampling Ensemble with Shared Weights

Consider a neural network with \(n\) neurons. If each neuron can be either masked or not masked, there will be theoretically in total \(2^n\) subnets. In this view training a neural network with dropout can be thought of as ensembling different subnets by random sampling a subnet at each step. The difference between the usual bagging technique and dropout is that the latter has all the models share their weights.

Model ensemble in general outperforms any single model. Since dropout is a form of approximated ensemble from a huge amount (\(2^n\)) of subnet models, it may well outperforms the single neural network with \(n\) neurons.

4.5.2 Weight Scaling on Inference

There is one side-effect when using dropout to train a neural network. When a trained model is put to an inference task, output from the single neural network (with all neurons unmasked) no longer represents the distribution in the training unless we use exactly the same set of subnets to do the prediction and average them together. But this is not practical.

To solve this problem, we still use only the original neural networks (with all neurons unmasked) to feed forward for prediction, but every model weight is scaled down by the dropout rate of the corresponding neuron. It is proven by many empirical works that this weight scaling technique can give very good modeling result. Probabilistically, the model output here is the expected output from all the subnets given the dropout scheme.

4.6 On the Bias Term

In a general additive model a bias term is just a feature with a constant value. In a neural network model when a bias term is used in a hidden layer, it has no connection between the previous layer but simply act as one extra unit linked to the next layer. (See our built-from-scratch toy example in the previous section.)

It is easier to understand why we may want a bias term in a linear regression model. Since without a bias term we are essentially restricting the fitted model to pass through the origin, which can be unrealistic in most use cases. On the other hand, adding a bias term doesn’t prevent from that special scenario to occur. If the true model does pass through the origin, given enough data the OLS estimator should be able to estimate the weight on bias to be insignificantly different from zero.

Here is a simple example to showcase estimation on a true model without bias:

                 Results: Ordinary least squares
==================================================================
Model:              OLS              Adj. R-squared:     0.937    
Dependent Variable: y                AIC:                2901.1253
Date:               2019-11-21 00:19 BIC:                2910.9408
No. Observations:   1000             Log-Likelihood:     -1448.6  
Df Model:           1                F-statistic:        1.487e+04
Df Residuals:       998              Prob (F-statistic): 0.00     
R-squared:          0.937            Scale:              1.0632   
--------------------------------------------------------------------
            Coef.    Std.Err.      t       P>|t|     [0.025   0.975]
--------------------------------------------------------------------
const       0.0113     0.0326     0.3471   0.7286   -0.0527   0.0753
x1          3.9625     0.0325   121.9539   0.0000    3.8987   4.0262
------------------------------------------------------------------
Omnibus:              4.505         Durbin-Watson:           1.973
Prob(Omnibus):        0.105         Jarque-Bera (JB):        5.227
Skew:                 -0.050        Prob(JB):                0.073
Kurtosis:             3.340         Condition No.:           1    
==================================================================

As one can see the estimated weight on bias is statistically insignificant.

Should regularization applies to bias term? In our previous working example we apply norm regularization on both bias and weight. Practically speaking there may not be any benefit on regularizing the bias term. The rationale is that bias comes from a constant feature. Such feature is in theory not possible to overfit the data since it is not varying with the data at all.

Indeed, regularization on bias term may even have negative impact. This is because by regularizing a term that is not exposed to any risk of overfitting, the model is constrained without any benefit. We only end up with a less flexible parameter space which could be potentially suboptimal.

To sum up, the general strategy of regularization is NOT to regularize the bias term, if any.

5 Batch Normalization

5.1 Normalization on Linear Models

Before we talk about batch normalization, let’s examine the impact of feature normalization on a simple linear regression model.7

Given a simple univariate regression model:

\[ y = a + bx + \epsilon. \]

What is the impact if we standardize \(x\)? It means that we estimate the following model instead:

\[ y = c + d\bigg(\frac{x - \mu_x}{\sigma_x}\bigg) + \eta. \]

Now simply a re-arrangement of the model formula gives:

\[ \begin{aligned} y &= c + d\bigg(\frac{x - \mu_x}{\sigma_x}\bigg) + \eta \\ &= \underbrace{c - \frac{d\mu_x}{\sigma_x}}_{a} + \underbrace{\frac{d}{\sigma_x}}_{b}\cdot x + \underbrace{\eta}_{\epsilon}. \end{aligned} \]

Apparently by using standardized feature the weights are simply linearly scaled. It won’t have any analytical impact.

However for large scale application we are not solving the model analytically, but rather numerically. Here comes the impact of feature normalization, especially for first-order optimizer like gradient descent. If there is any feature with outlying scale compared to the others, the performance of gradient descent is hugely damaged.

Let’s examine this using a working example:

[[ 1.         -0.46820879 43.4755395 ]
 [ 1.         -0.82282485 43.18192098]
 [ 1.         -0.0653801  54.09287917]
 [ 1.         -0.71336192 58.53293621]
 [ 1.          0.90635089 54.1564682 ]
 [ 1.          0.76623673 42.42103854]
 [ 1.          0.82605407 54.26729587]
 [ 1.         -1.32368279 62.14688398]
 [ 1.         -1.75244452 34.17196666]
 [ 1.          1.00244907 44.17910818]]

Here we create a bivariate linear model where the 2nd feature has a much larger scale than the 1st one. The analytical OLS estimator has no problem solving this model:

[6.00952581 3.93270425 3.00017441]

Our batch gradient descent solver, however, suffer a lot:

[nan nan nan]

/home/kylechung/.pyenv/versions/k9-nn/bin/python:2: RuntimeWarning:

overflow encountered in square

/home/kylechung/.pyenv/versions/k9-nn/bin/python:14: RuntimeWarning:

invalid value encountered in subtract

Here due to the imbalanced scale of the features, our solver becomes numerically unstable given a moderate learning rate (resulting in the exploding gradients). This is because the size of gradient update is improperly dominated by high-scaled feature.

We can still manage to solve the model, but with a much lower learning rate to down-scale the high-scaled feature, accompanied with a larger number of epochs since other low-scaled features now are updated by too small a step:

Training Epochs   100 | Estimate: [0.45991644 2.27156008 3.10917524]
Training Epochs  1000 | Estimate: [3.28390533 3.95687798 3.05183397]
Training Epochs  5000 | Estimate: [5.09751447 3.94145204 3.01695605]
Training Epochs 10000 | Estimate: [5.88796916 3.93388052 3.00508586]

Even for such a simple linear model we now need much more training steps to ensure convergence, not to mention a more complicated neural network model.

A second-order optimizer can mitigate the problem. But for training large scale neural networks we must stick to first-order optimizer for the sack of performance. Feature normalization comes to the rescue.

Let’s experiment with a new design matrix where all the features (excluding the bias) are normalized to have zero meanand unit standard deviation.

[[ 1.         -0.49861967 -0.64279134]
 [ 1.         -0.85198872 -0.6712771 ]
 [ 1.         -0.09720753  0.38726279]
 [ 1.         -0.74291072  0.81802033]
 [ 1.          0.87110636  0.39343196]
 [ 1.          0.73148492 -0.74509504]
 [ 1.          0.79109191  0.40418404]
 [ 1.         -1.35108538  1.16863187]
 [ 1.         -1.77833937 -1.54538879]
 [ 1.          0.96686661 -0.57453377]]

Since our feature scale changes, the analytical solution for the weights changes as well. Let’s check the OLS estimation (the correct solution for our reference) after normalization:

[156.44822091   3.94658245  30.92446288]

Now examine our gradient descent solver:

Training Epochs     1 | Estimate: [152.08538432   3.69797309  30.14852295]
Training Epochs    10 | Estimate: [156.45440292   3.96343058  30.96520361]
Training Epochs    50 | Estimate: [156.44691019   3.98666368  30.88663084]
Training Epochs   100 | Estimate: [156.34646482   3.94522662  30.88778661]

Bingo!

Now it takes no extra effort (less than 10 epochs) for our numerical solver to arrive at a good approximation. The only additional step is to remember to transform all the features also before model prediction since the feature space is not in the original dimension anymore.

5.2 Normalization on Logistic Models

When it comes to a logistic regression, normalization can be even more critical since the sigmoid function can suffer from saturating when the input is either too small or too big.

Remember that a sigmoid function looks like this shape. The gradient becomes nearly zero at both extreme sides of the input space. Or mathematically:

\[ \begin{aligned} z &= \frac{1}{1 + e^{-t}}, \\ \frac{dz}{dt} &= z(1 - z), \\ t \to \infty & \Rightarrow e^{-t} \to 0 \mbox{ and } z \to 1 \Rightarrow z(1-z) \to 0, \\ t \to - \infty & \Rightarrow e^{-t} \to \infty \mbox{ and } z \to 0 \Rightarrow z(1-z) \to 0. \end{aligned} \]

Hence as the fitted value \(X\beta\) goes unbounded, the gradient becomes zero and the gradient descent optimization get stuck since all updates will be multiplied by zero. This is referred to as the problem of vanishing gradients.

Feature normalization in this case also help avoid vanishing gradients since the size of the fitted values are well constrained.

5.3 Normalization on Neural Networks

Exactly the same logic can apply to training a neural network, especially when sigmoid (or function with similar property such as the tanh) is used as the activation function in any layer of the network. The only problem is when the application is too huge to compute normalization. Since normalization requires the entire dataset to calculate the scale, it may be prohibitively costly to do so.8

To overcome the computing cost the batch normalization approach is proposed (BN hereafter). It simply normalizes the input at batch level when doing batch gradient descent. This is done on a per-layer basis when training a neural network.

5.3.1 Theoretical Ground on Batch Norm

Indeed BN exists for more than just to mitigate numerical issues. In the original work of Ioffe and Szegedy (2015) BN is proposed to mainly deal with a problem called internal covariate shift. Such a point of view, however, has been heavily challenged by subsequent research such as in Santurkar et al. (2018).

The success of BN is proven by its empirical results but not its theoretical stand, which is yet to be clear. The debate is still ongoing so as of now we should consider it as a good practice that always worths experimenting.

5.3.2 Batch Norm After Activation

Should we BN the input before or after applying the activator at each layer? Though the original paper applies BN before the activator, the common practice now seems to be applying it after activator since the empirical results are better.

BN after activator seems also more interpretable since it is the output but not the input of the activator becomes the input to the next layer. By applying BN after activator, we are controlling each input to be normalized just before the multiplication of the weight matrix in the next layer.

But if the sigmoid is used as activator, would BN after activation defeat another purpose which is to mitigate vanishing gradients? If the input layer is also normalized, this should be less of a problem. Of course if the chosen activator has no saturating point (such as ReLU) this is also not a problem.

5.3.3 Batch Norm at Inference Time

How do we run inference based on a model trained with BN? If the input data is also by batch we can directly apply the same network architecture. But more of the time the inference is done by a streaming manner (online predicting). In order to BN the data we need to keep track of the feature means and variances when we are doing model training on the fly. A common practice is to use a moving average of feature means and variances as additional parameters used in model inference mode.

6 Gradient Clipping

Gradient clipping is a technique to handle exploding gradients. Such problem can result from improper learning rates, imbalanced feature scales, or a mis-configured loss function. The general idea is to constrain the calculated gradients before applying backpropagation update.

The simplest way of doing this is to just set a lower and upper bound so that all gradients are bounded by the lower and capped at upper bound.

Another way of doing that is to constrain the L2 norm of gradient vector by a given value:

Let’s add the clipping to our batch gradient descent optimizer in our previous example in BN section where the gradient got exploded with imbalanced scaled features:

[[ 1.         -0.46820879 43.4755395 ]
 [ 1.         -0.82282485 43.18192098]
 [ 1.         -0.0653801  54.09287917]
 [ 1.         -0.71336192 58.53293621]
 [ 1.          0.90635089 54.1564682 ]]
Training Epochs  1000 | Estimate: [4.2681595  3.91673636 2.59477635]
Training Epochs  5000 | Estimate: [5.9558284  3.94616001 3.39861039]
Training Epochs 10000 | Estimate: [6.0188452  3.9357066  2.92287519]
Training Epochs 20000 | Estimate: [6.00534916 3.94543276 2.91034558]
Training Epochs 30000 | Estimate: [5.96425905 3.9384467  2.88635615]

We have successfully avoided exploding gradients this time with gradient clipping and without normalization of the input feature. But the noise in the estimation for high-scaled feature seems to be bigger as well. Also gradient clipping won’t help us improve the speed of convergence. It is merely a technique to tacke down numerical stability problem.9

7 References

Abadi, Martín, Ashish Agarwal, Paul Barham, Eugene Brevdo, Zhifeng Chen, Craig Citro, Greg S. Corrado, et al. 2015. “TensorFlow: Large-Scale Machine Learning on Heterogeneous Systems.” http://tensorflow.org/.

Ioffe, Sergey, and Christian Szegedy. 2015. “Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift.” arXiv Preprint arXiv:1502.03167.

Pedregosa, F., G. Varoquaux, A. Gramfort, V. Michel, B. Thirion, O. Grisel, M. Blondel, et al. 2011. “Scikit-Learn: Machine Learning in Python.” Journal of Machine Learning Research 12: 2825–30.

Santurkar, Shibani, Dimitris Tsipras, Andrew Ilyas, and Aleksander Madry. 2018. “How Does Batch Normalization Help Optimization?” In Advances in Neural Information Processing Systems, 2483–93.

Seabold, Skipper, and Josef Perktold. 2010. “Statsmodels: Econometric and Statistical Modeling with Python.” In 9th Python in Science Conference.

Srivastava, Nitish, Geoffrey Hinton, Alex Krizhevsky, Ilya Sutskever, and Ruslan Salakhutdinov. 2014. “Dropout: A Simple Way to Prevent Neural Networks from Overfitting.” The Journal of Machine Learning Research 15 (1). JMLR. org: 1929–58.


  1. One can try change the sample size arbitrarily to see the behavior of the estimator in our toy example.

  2. Sometimes “mini-batch gradient descent” is used to describe SGD with batch size > 1. Here we ignore the redundant wording and just call it batch gradient descent.

  3. Here we use the common derivatives: \(\frac{df(x)^n}{dx} = nf(x)^{n-1}f'(x)\) and \(\frac{de^{f(x)}}{dx} = f'(x)e^{f(x)}.\)

  4. Here we use the common derivative \(\frac{d\ln f(x)}{dx} = \frac{f'(x)}{f(x)}\) and the chain rule to handle the term \(\frac{\partial\ln q_i}{\partial\beta_0}\) where \(q_i\) is the sigmoid computed for the \(i\)-th example.

  5. Note that the derivative \(\frac{du}{dx} = \frac{u}{\vert u \vert}\frac{du}{dx}\) at \(u = 0\) is undefined.

  6. Since plotly.js currently doesn’t support uneven interval for contour plot, in the contour-regularization plot we take natrual log of MSE just to scale the value so that the contour line will have non-linear interval. This help us show more lines when loss becomes smaller.

  7. The term normalization is indeed not well defined. Here what we mean is actually standardization: by transforming a variable to have zero mean and unit standard deviation. Other common normalization include simple de-meaned (substraction from mean), compression to the range of \([0, 1]\) or \([-1, 1]\), …, etc.

  8. Notice that we need to normalize not just the input layer but every hidden layers as well.

  9. In tensorflow the function clip_by_value implements the bounded method and clip_by_norm implements exactly the L2 norm method we’ve implemented here.

LS0tCnRpdGxlOiAiTmV1cmFsIE5ldHdvcmtzIEZ1bmRhbWVudGFscyIKc3VidGl0bGU6ICJ3aXRoIEV4YW1wbGVzIHVzaW5nIFB5dGhvbiIKYXV0aG9yOgotIG5hbWU6IEt5bGUgQ2h1bmcKICBhZmZpbGlhdGlvbjoKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJWIgJVknKWAgTGFzdCBVcGRhdGVkICgyNSBKdW4gMjAxOSBGaXJzdCBVcGxvYWRlZCkiCm91dHB1dDoKICBodG1sX25vdGVib29rOiAKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0aGVtZTogcGFwZXIKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIGluY2x1ZGVzOgogICAgICBpbl9oZWFkZXI6IC90bXAvbWV0YV9oZWFkZXIuaHRtbAogIGNvZGVfZG93bmxvYWQ6IHRydWUKYmlibGlvZ3JhcGh5OiBuZXVyYWxfbmV0d29ya3NfZnVuZGFtZW50YWxzLmJpYgphYnN0cmFjdDogfAogIFRoZSBub3RlYm9vayB3YWxrcyB0aHJvdWdoIHRoZSBmdW5kYW1lbnRhbCBrbm93bGVkZ2UgYW5kIGludHVpdGlvbiByZXF1aXJlZCB0byB1bmRlcnN0YW5kIHRoZSBpbm5lciB3b3JraW5nIG9mIGEgbWluaW1hbGx5IGNvbmZpZ3VyZWQgbmV1cmFsIG5ldHdvcmsgbW9kZWwgdXNpbmcgY29kaW5nIGV4YW1wbGVzIGJ1aWx0IGZyb20gc2NyYXRjaC4gQnkgYXZvaWRpbmcgdGhlIHVzZSBvZiBhbnkgaGlnaC1sZXZlbCBtYWNoaW5lIGxlYXJuaW5nIEFQSSB3ZSBjYW4gZ2V0IHJpZCBvZiB0aGUgYWJzdHJhY3Rpb24gYW5kIGdhaW4gbW9yZSBpbi1kZXB0aCBoYW5kcy1vbiBleHBlcmllbmNlcyBkdXJpbmcgdGhlIGpvdXJuZXkuCgogIFRoZSB0b3BpY3MgaW5jbHVkZSAxLikgYW4gaW1wb3J0YW50IHNlY3Rpb24gcmV2aXNpdGluZyB3aGF0IGlzIGxpbmVhciBhbmQgbG9naXN0aWMgcmVncmVzc2lvbiBhbmQgaG93IHdlIGNhbiB0cmFpbiB0aGVzZSBtb2RlbHMgdXNpbmcgZmlyc3Qtb3JkZXIgbnVtZXJpY2FsIG9wdGltaXplci4gRm9sbG93ZWQgYnkgMi4pIGEgcXVpY2sgZGVtb25zdHJhdGlvbiBvZiBhdXRvbWF0aWMgZGlmZmVybmV0aWF0aW9uIHdoaWNoIGlzIGNvbnNpZGVyZWQgYXMgdGhlIGNvbXB1dGluZyB3b3JraG9yc2UgZm9yIHRyYWluaW5nIG1vZGVybiBuZXVyYWwgbmV0d29ya3MuIEFuZCAzLikgYSBzaW1wbGUgbmV1cmFsIG5ldHdvcmsgc29sdmVyIGlzIGJ1aWx0IGFuZCBleGFtaW5lZC4gRmluYWxseSA0LikgdGhlIHJlZ3VsYXJpemF0aW9uIHRlY2hpbnF1ZSB0byBvdmVyY29tZSBvdmVyZml0dGluZyBwcm9ibGVtIGZvbGxvd2VkIGJ5IGRpc2N1c3Npb24gb24gYmF0Y2ggbm9ybWFsaXphdGlvbiBhbmQgZ3JhZGllbnQgY2xpcHBpbmcuCi0tLQo8IS0tRm9yIGNvbnRyb2xpbmcgY29kZSBmb2xkaW5nIGJ5IGNodW5rLi0tPgo8c2NyaXB0IHNyYz0iLi4vc2l0ZV9saWJzL3V0aWxzL2hpZGVfb3V0cHV0LmpzIj48L3NjcmlwdD4KCjwhLS0gRW1iZWQgcGxvdGx5IGphdmFzY3JpcHQgbGlicmFyeS4KICBUaGUgcHJldmlldyBvZiBwbG90bHkgb3V0cHV0IGluIFJTdHVkaW8gd29uJ3Qgd29yawogIHNpbmNlIHdlIGRvbid0IGluY2x1ZGUgdGhlIHBsb3RseS5qcyBpbiBlYWNoIGluZGl2aWR1YWwgcGxvdCAodG8gcmVkdWNlIGZpbGUgc2l6ZSkuCi0tPgo8c2NyaXB0IHNyYz0iLi4vc2l0ZV9saWJzL3V0aWxzL3Bsb3RseS1sYXRlc3QubWluLmpzIj48L3NjcmlwdD4KCjwhLS0gTGluayB0byBtYXRoamF4IGphdmFzY3JpcHQgbGlicmFyeS4KICBUaGlzIGlzIGZvciBsYXRleCBub3RhdGlvbiBpbiBwbG90bHkgb3V0cHV0LgogIFRoZSBidWlsdC1pbiBvbmUgZnJvbSBSU3R1ZGlvIGNkbiBzb21laG93IGRpZG4ndCB3b3JrLgotLT4KPHNjcmlwdCB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiIGFzeW5jCiAgc3JjPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9tYXRoamF4LzIuNy41L01hdGhKYXguanM/Y29uZmlnPVRlWC1NTUwtQU1fQ0hUTUwiPgo8L3NjcmlwdD4KCjwhLS1Gb3IgZXF1YXRpb24gcmVmZXJlbmNlIGluIFJtZC4tLT4KPHNjcmlwdCB0eXBlPSJ0ZXh0L3gtbWF0aGpheC1jb25maWciPgpNYXRoSmF4Lkh1Yi5Db25maWcoewogIFRlWDogeyBlcXVhdGlvbk51bWJlcnM6IHsgYXV0b051bWJlcjogIkFNUyIgfSB9Cn0pOwo8L3NjcmlwdD4KCmBgYHtjc3MgZm9sZF9jb2RlX2J5X2NodW5rLCBlY2hvPUZBTFNFfQouc2hvd29wdCB7CiAgYmFja2dyb3VuZC1jb2xvcjogIzAwNGM5MzsKICBjb2xvcjogI0ZGRkZGRjsKICB3aWR0aDogMTAwcHg7CiAgaGVpZ2h0OiAyMHB4OwogIHRleHQtYWxpZ246IGNlbnRlcjsKICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlICFpbXBvcnRhbnQ7CiAgZmxvYXQ6IHJpZ2h0OwogIGZvbnQtZmFtaWx5OiBzYW5zLXNlcmlmOwogIGJvcmRlci1yYWRpdXM6IDhweDsKfQoKLnNob3dvcHQ6aG92ZXIgewogICAgYmFja2dyb3VuZC1jb2xvcjogI2RmZTRmMjsKICAgIGNvbG9yOiAjMDA0YzkzOwp9CgpwcmUucGxvdCB7CiAgYmFja2dyb3VuZC1jb2xvcjogd2hpdGUgIWltcG9ydGFudDsKfQpgYGAKCmBgYHtyIG1ldGEsIGluY2x1ZGU9RkFMU0V9Cm1ldGFfaGVhZGVyX2ZpbGUgPC0gZmlsZSgiL3RtcC9tZXRhX2hlYWRlci5odG1sIikKbWV0YSA8LSBjKAogICc8bWV0YSBuYW1lPSJhdXRob3IiIGNvbnRlbnQ9Ikt5bGUgQ2h1bmciPicsCiAgJzxtZXRhIHByb3BlcnR5PSJvZzp0aXRsZSIgY29udGVudD0iTmV1cmFsIE5ldHdvcmtzIEZ1bmRhbWVudGFscyI+JywKICAnPG1ldGEgcHJvcGVydHk9Im9nOnR5cGUiIGNvbnRlbnQ9ImFydGljbGUiPicsCiAgJzxtZXRhIHByb3BlcnR5PSJvZzp1cmwiIGNvbnRlbnQ9Imh0dHBzOi8vZXZlcmRhcmsuZ2l0aHViLmlvL2s5L25ldXJhbF9uZXRzL25ldXJhbF9uZXR3b3Jrc19mdW5kYW1lbnRhbHMubmIuaHRtbCI+JywKICAnPG1ldGEgcHJvcGVydHk9Im9nOmltYWdlIiBjb250ZW50PSJodHRwczovL2V2ZXJkYXJrLmdpdGh1Yi5pby9rOS9hc3NldHMvYXZhdGFyLmpwZyI+JywKICAnPG1ldGEgcHJvcGVydHk9Im9nOmRlc2NyaXB0aW9uIiBjb250ZW50PSJBIGRhdGEgc2NpZW5jZSBub3RlYm9vayBhYm91dCBmb3VuZGF0aW9ucyBvZiBuZXVyYWwgbmV0d29yayBtb2RlbGluZyB3aXRoIGJ1aWx0LWZyb20tc2NyYXRjaCBQeXRob24gZXhhbXBsZXMuIj4nCikKY29udGVudHMgPC0gbWV0YQoKIyBBZGQgR2l0aHViIGNvcm5lci4KZ2l0aHViX2Nvcm5lcl9zdmcgPC0gIi4uL2Fzc2V0cy9naXRodWJfY29ybmVyLmh0bWwiCmdpdGh1Yl9jb3JuZXJfY29uZiA8LSBsaXN0KGdpdGh1Yl9saW5rPSJodHRwczovL2dpdGh1Yi5jb20vZXZlcmRhcmsvazkvdHJlZS9tYXN0ZXIvbmV1cmFsX25ldHMiKQpjb250ZW50cyA8LSBjKGNvbnRlbnRzLCBzdHJpbmdyOjpzdHJfaW50ZXJwKHJlYWRMaW5lcyhnaXRodWJfY29ybmVyX3N2ZyksIGdpdGh1Yl9jb3JuZXJfY29uZikpCndyaXRlTGluZXMoY29udGVudHMsIG1ldGFfaGVhZGVyX2ZpbGUpCgpjbG9zZShtZXRhX2hlYWRlcl9maWxlKQpgYGAKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQpsaWJyYXJ5KHJldGljdWxhdGUpCnIgPC0gdHJ5KHVzZV9weXRob24oU3lzLmdldGVudigiUFlUSE9OX1BBVEgiKSwgcmVxdWlyZWQ9VFJVRSksIHNpbGVudD1UUlVFKQppZiAoIGlzKHIsICJ0cnktZXJyb3IiKSApIHsKICByIDwtIHRyeSh1c2VfdmlydHVhbGVudihTeXMuZ2V0ZW52KCJQWVRIT05fUEFUSCIpLCByZXF1aXJlZD1UUlVFKSwgc2lsZW50PVRSVUUpCiAgaWYgKCBpcyhyLCAidHJ5LWVycm9yIikgKSB1c2VfY29uZGFlbnYoU3lzLmdldGVudigiUFlUSE9OX1BBVEgiKSwgcmVxdWlyZWQ9VFJVRSkKfQpgYGAKCiMgUHJlcmVxdWlzaXRlcwoKQmVmb3JlIHdlIGV2ZW4gc3RhcnQsCml0IGlzIG9mIGNydWNpYWwgaW1wb3J0YW5jZSB0byBmaXJzdCB1bmRlcnN0YW5kIGRlZXBseSBsaW5lYXIgYW5kIGxvZ2lzdGljIHJlZ3Jlc3Npb24uClRoaXMgaXMgYmVjYXVzZSAoYXMgd2Ugd2lsbCBzZWUgaW4gbGF0ZXIgZGlzY3Vzc2lvbikgdGhleSBhcmUgdGhlIHZlcnkgYmFzaWMgY29tcG9uZW50cyBpbiBhIGdlbmVyYWwgbmV1cmFsIG5ldHdvcmsgbW9kZWwuCgojIyBMaW5lYXIgUmVncmVzc2lvbgoKQSBsaW5lYXIgbW9kZWwgY2FuIGJlIHdyaXR0ZW4gaW4gYSBtYXRyaXggZm9ybQoKJCQKWSA9IFhcYmV0YSArIFxlcHNpbG9uLAokJAoKd2hlcmUgJFkkIGlzIHRoZSBvdXRwdXQgb3IgbGFiZWwgdmVjdG9yIG9mIGxlbmd0aCAkTiQgKG51bWJlciBvZiBvYnNlcnZhdGlvbnMpLAokWCQgaXMgdGhlIGlucHV0IGZlYXR1cmUgbWF0cml4IChyZWZlcnJlZCB0byBhcyB0aGUgKmRlc2lnbiBtYXRyaXgqIGluIHN0YXRpc3RpY3MpIHdpdGggZGltZW5zaW9uICROJCBieSAkUCQgKG51bWJlciBvZiBmZWF0dXJlcyksCiRcYmV0YSQgaXMgdGhlIG1vZGVsIHdlaWdodHMvY29lZmZpY2llbnRzIChhIGNvbHVtbiB2ZWN0b3Igb2YgbGVuZ3RoICRQJCkgZm9yIHdoaWNoIHdlJ2QgbGlrZSB0byBzb2x2ZSwKJFxlcHNpbG9uJCBpcyB0aGUgbW9kZWwgcmVzaWR1YWwgb3IgZXJyb3IgdmVjdG9yLgoKIyMjIE9yZGluYXJ5IExlYXN0IFNxdWFyZXMKClRoZSBjbGFzc2ljYWwgd2F5IHRvIHNvbHZlIGZvciAkXGJldGEkIGlzIFtvcmRpbmFyeSBsZWFzdCBzcXVhcmVzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9PcmRpbmFyeV9sZWFzdF9zcXVhcmVzKS4KVGhlIGlkZWEgb2YgT0xTIGlzIGZpbmQgb3V0IHRoZSB3ZWlnaHRzIHRoYXQgbWluaW1pemUgdGhlIG1lYW4gb2Ygc3F1YXJlZCBtb2RlbCBlcnJvcnM6CgokJApcYmVnaW57YWxpZ25lZH0KXG1ib3h7bXNlIChsb3NzKX0KJj0gXGZyYWN7MX17Tn1cc3VtX2lcZXBzaWxvbl4yX2kgXFwKJj0gXGZyYWN7MX17Tn1cc3VtX2koeV9pIC0gXGJldGEgeF9pKV4yLApcZW5ke2FsaWduZWR9CiQkCgp3aGVyZSAkeV9pJCBhbmQgJHhfaSQgaXMgdGhlICRpJC10aCBvYnNlcnZhdGlvbi4KVGhlIGZpcnN0LW9yZGVyIGNvbmRpdGlvbiAocmVxdWlyaW5nIHRoYXQgdGhlIGZpcnN0LW9yZGVyIGRlcml2YXRpdmUgdy5yLnQuIHdlaWdodHMgYXJlIGFsbCB6ZXJvKSBnaXZlcyB1cyB0aGUgT0xTIHNvbHV0aW9uIGZvciBtb2RlbCB3ZWlnaHRzICRcYmV0YSQgYW5hbHl0aWNhbGx5IChpbiBtYXRyaXggbm90YXRpb24pOgoKJCQKXGJlZ2lue2VxdWF0aW9ufSBcbGFiZWx7ZXE6b2xzfQpcaGF0e1xiZXRhfSA9IChYJ1gpXnstMX1YJ1kuClxlbmR7ZXF1YXRpb259CiQkCgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfb2xzfQppbXBvcnQgbnVtcHkgYXMgbnAKZnJvbSBudW1weS5saW5hbGcgaW1wb3J0IGludgoKZGVmIG9scyhYLCB5KToKICAgIHJldHVybiBpbnYoWC5ULmRvdChYKSkuZG90KFguVCkuZG90KHkpCmBgYAoKTGV0J3MgY29uc2lkZXIgYSB0b3kgbW9kZWwgd2l0aCBvbmx5IG9uZSBub24tY29uc3RhbnQgZmVhdHVyZToKCiQkCnlfaSA9IDYgKyA0eF9pICsgXGVwc2lsb25faS4KJCQKCkluIHRoaXMgbW9kZWwgdGhlIG91dGNvbWUgJHkkIGlzIGRldGVybWluZWQgYnkgYSBiaWFzIHRlcm0gcGx1cyBhIHNpbmdsZSB2YXJpYWJsZSAkeCQsCndpdGggYW4gKmluZGVwZW5kZW50bHkgZGlzdHJpYnV0ZWQqIG5vaXNlIHRlcm0gJFxlcHNpbG9uIFxzaW0gXG1ib3h7Tm9ybWFsfSgwLCAxKSQuCgpDcmVhdGUgc29tZSByYW5kb20gZGF0YSBnZW5lcmF0ZWQgZnJvbSB0aGlzIG1vZGVsOgoKYGBge3B5dGhvbiBsaW5lYXJfcmVnX3RveV9leGFtcGxlfQojIENyZWF0ZSB0b3kgZXhhbXBsZS4KbnAucmFuZG9tLnNlZWQoNzc3KQoKTiA9IDEwMDAKWCA9IG5wLnN0YWNrKFtucC5vbmVzKE4pLCBucC5yYW5kb20ubm9ybWFsKHNpemU9TildLCBheGlzPTEpCmJldGEgPSBucC5hcnJheShbNiwgNF0sIGR0eXBlPW5wLmZsb2F0MzIpICAjIFRydWUgbW9kZWwgd2VpZ2h0cy4KZSA9IG5wLnJhbmRvbS5ub3JtYWwoc2l6ZT1OKQp5ID0gWC5kb3QoYmV0YSkgKyBlCgpwcmludChYWzoxMF0pCmBgYAoKYGBge3B5dGhvbiBsaW5lYXJfcmVnX3Bsb3R9CmZyb20gcGxvdGx5Lm9mZmxpbmUgaW1wb3J0IHBsb3QKaW1wb3J0IHBsb3RseS5ncmFwaF9vYmpzIGFzIGdvCgpkZWYgcGxvdF9vZmZsaW5lKGRhdGEsIGxheW91dCwgb2ZpbGUpOgogIHAgPSBwbG90KHsiZGF0YSI6IGRhdGEsICJsYXlvdXQiOiBsYXlvdXR9LAogICAgICAgICAgIGZpbGVuYW1lPW9maWxlLCBhdXRvX29wZW49RmFsc2UsCiAgICAgICAgICAgaW5jbHVkZV9wbG90bHlqcz1GYWxzZSkKICByZXR1cm4gcAoKb2ZpbGUgPSAicGxvdHMvdG95X3JlZy5odG1sIgpwID0gcGxvdF9vZmZsaW5lKAogIG9maWxlPW9maWxlLAogIGRhdGE9W2dvLlNjYXR0ZXIoeD1YWzosMV0sIHk9eSwgbW9kZT0ibWFya2VycyIpXSwKICBsYXlvdXQ9Z28uTGF5b3V0KHRpdGxlPSJEYXRhIEdlbmVyYXRlZCBmcm9tIFRveSBNb2RlbCIsCiAgICAgICAgICAgICAgICAgICB4YXhpcz1kaWN0KHRpdGxlPSJ4IiksCiAgICAgICAgICAgICAgICAgICB5YXhpcz1kaWN0KHRpdGxlPSJ5IikpKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpodG1sdG9vbHM6OmluY2x1ZGVIVE1MKHB5JG9maWxlKQpgYGAKCldpdGhvdXQgY29uc2lkZXJhdGlvbiBvZiB0aGUgbm9pc2UgdGVybSwKdGhlIE9MUyBlc3RpbWF0b3Igd2lsbCBzb2x2ZSBwcmVjaXNlbHkgZm9yIHRoZSB0cnVlIG1vZGVsIHdlaWdodHM6CgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfb2xzX2RldGVybWluaXN0aWN9CnByaW50KG9scyhYLCB5IC0gZSkpCmBgYAoKT2YgY291cnNlIGluIHRoZSByZWFsIHdvcmxkIHRoZSBub2lzZSB0ZXJtIGNhbm5vdCBiZSBkZXRlcm1pbmVkIGFuZCB1c3VhbGx5IHRoZSBmZWF0dXJlIGFsb25lIGNhbm5vdCBleHBsYWluIGVudGlyZWx5IHRoZSB2YXJpYXRpb24gaW4gdGhlIG91dGNvbWUuClRoaXMgbWVhbnMgdGhhdCB3aGF0IHdlIGFjdHVhbGx5IGVzdGltYXRlIHdpbGwgYmUgdGhlIGV4cGVjdGVkIHZhbHVlIG9mIG1vZGVsIHdlaWdodHM6CgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfb2xzX3dpdGhfZXBzaWxvbn0KcHJpbnQob2xzKFgsIHkpKQpgYGAKCldlIGNhbiBjaGVjayB0aGUgcmVzdWx0IGZyb20gW2BzY2lraXQtbGVhcm5gXShodHRwczovL3NjaWtpdC1sZWFybi5vcmcvKSAoQHNjaWtpdC1sZWFybik6CgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfc2tsZWFybn0KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgTGluZWFyUmVncmVzc2lvbgojIFdlIHNldCBmaXRfaW50ZXJjZXB0PUZhbHNlIHNpbmNlIG91ciBkZXNpZ24gbWF0cml4IGFscmVhZHkgY29udGFpbnMgaW50ZXJjZXB0LgpwcmludChMaW5lYXJSZWdyZXNzaW9uKGZpdF9pbnRlcmNlcHQ9RmFsc2UpLmZpdChYLCB5KS5jb2VmXykKYGBgCgpCeSB0aGUgW0xhdyBvZiBMYXJnZSBOdW1iZXJdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0xhd19vZl9sYXJnZV9udW1iZXJzKSBhbmQgW0NlbnRyYWwgTGltaXQgVGhlb3JlbV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQ2VudHJhbF9saW1pdF90aGVvcmVtKSwKdW5kZXIgYSByZWxhdGl2ZWx5IGxvb3NlIGFzc3VtcHRpb24gb2YgJEUoXGVwc2lsb24gfCBYKSA9IDAkLAp0aGUgT0xTIGVzdGltYXRvciB3aWxsIGNvbnZlcmdlIGluIHByb2JhYmlsaXR5IHRvIHRoZSB0cnVlIG1vZGVsIHdlaWdodHMgYW5kIGRpc3RyaWJ1dGVkIGFzeW1wdG90aWNhbGx5IE5vcm1hbCBpbiBsYXJnZSBzYW1wbGUuXltPbmUgY2FuIHRyeSBjaGFuZ2UgdGhlIHNhbXBsZSBzaXplIGFyYml0cmFyaWx5IHRvIHNlZSB0aGUgYmVoYXZpb3Igb2YgdGhlIGVzdGltYXRvciBpbiBvdXIgdG95IGV4YW1wbGUuXQoKVGhlIGlzc3VlIG9mIHRoZSBhYm92ZSBhcHByb2FjaCBpcyB0aGF0IGVxdWF0aW9uICRcZXFyZWZ7ZXE6b2xzfSQgaXMgbm90IG51bWVyaWNhbGx5IHN0YWJsZSB3aGVuIGl0IGNvbWVzIHRvIGxhcmdlLXNjYWxlIGFwcGxpY2F0aW9uIHdoZXJlIHdlIG1heSBoYXZlIGxvdHMgb2Ygb2JzZXJ2YXRpb25zIGFuZCBsb3RzIG9mIGZlYXR1cmVzLgpPbmUgdmVyeSB1c2VmdWwgc29sdXRpb24gdG8gc29sdmUgdGhlIGVzdGltYXRvciBudW1lcmljYWxseSBpbiBsYXJnZS1zY2FsZSBhcHBsaWNhdGlvbiBpcyB0aGUgKmdyYWRpZW50IGRlc2NlbnQqIGFwcHJvYWNoLgoKIyMjIEdyYWRpZW50IERlc2NlbnQgd2l0aCBNZWFuIFNxdWFyZWQgRXJyb3IKCkluc3RlYWQgb2Ygc29sdmluZyB0aGUgZmlyc3Qtb3JkZXIgY29uZGl0aW9uIGFuYWx5dGljYWxseSwKd2UgY2FuIGRvIGl0IG51bWVyaWNhbGx5LgpHcmFkaWVudCBkZXNjZW50IGlzIGEgMXN0LW9yZGVyIG9wdGltaXphdGlvbiB0ZWNobmlxdWUgdG8gZmluZCBsb2NhbCBvcHRpbXVtIG9mIGEgZ2l2ZW4gZnVuY3Rpb24uCgpJbiB0aGUgbW9kZWwgdHJhaW5pbmcgZXhlcmNpc2Ugb3VyIHRhcmdldCBmdW5jdGlvbiBpcyB0aGUgbG9zcyBzbyB0aGUgb3B0aW1pemF0aW9uIHByb2JsZW0gaXM6CgokJApcb3BlcmF0b3JuYW1lKnthcmdtaW59X1xiZXRhIFxtYm94e0xvc3N9ID0gXGZyYWN7MX17Tn1cc3VtX2koeV9pIC0gXGJldGEgeF9pKV4yLgokJAoKVGhhdCBpcywKd2UnZCBsaWtlIHRvIGZpZ3VyZSBvdXQgbW9kZWwgd2VpZ2h0cyB0aGF0IG1pbmltaXplIHRoZSBsb3NzIHdoaWNoIGlzIGRlZmluZWQgYnkgdGhlIG1lYW4gc3F1YXJlZCBlcnJvcnMgd2hlbiB0aGUgbW9kZWwgaXMgYSByZWdyZXNzaW9uIG1vZGVsLgoKVGhlIGlkZWEgb2YgZ3JhZGllbnQgZGVzY2VudCBpcyB0bwoKMS4gRGVyaXZlIHRoZSBmdW5jdGlvbmFsIGZvcm0gb2YgdGhlIGdyYWRpZW50IG9mIGxvc3Mgdy5yLnQuIHRvIGFsbCB3ZWlnaHRzCjIuIEluaXRpYWxpemUgYWxsIG1vZGVsIHdlaWdodHMgcmFuZG9tbHkKMy4gQ2FsY3VsYXRlIHRoZSBncmFkaWVudCB2YWx1ZSB1c2luZyB0aGUgYWN0dWFsIGRhdGEgYW5kIHRoZSBjdXJyZW50IHZhbHVlIG9mIHdlaWdodHMKNC4gVXBkYXRlIHRoZSB3ZWlnaHRzIGJ5IChwYXJ0aWFsbHkpIHRoZSBhbW91bnQgb2YgZ3JhZGllbnQganVzdCBjYWxjdWxhdGVkCjUuIFJlcGVhdCAzIGFuZCA0IHVudGlsIHRoZSByZXN1bHRpbmcgZ3JhZGllbnRzIGJlY29tZSBzbWFsbCBlbm91Z2gKCkxldCdzIHVzZSB0aGUgdG95IGV4YW1wbGUgdG8gYWN0dWFsbHkgaW1wbGVtZW50IGEgZ3JhZGVudCBkZXNjZW50IG9wdGltaXplciBmcm9tIHNjcmF0Y2guCkZpcnN0IHdlIHJlLXdyaXRlIHRoZSBsb3NzIGZ1bmN0aW9uIGV4cGxpY2l0bHkgd2l0aCBvdXIgc2V0dXAgb2Ygb25lIGNvZWZmaWNpZW50IHdpdGggYSBjb25zdGFudCAoJFxiZXRhID0gW1xiZXRhXzAsIFxiZXRhXzFdJCk6CgokJApcbWJveHtMb3NzfSA9IFxmcmFjezF9e059XHN1bV9pXGJpZ1t5X2ktKFxiZXRhXzAgKyBcYmV0YV8xeF9pKVxiaWddXjIuCiQkCgpOb3cgdGhlIGdyYWRpZW50IChvciBlcXVpdmFsZW50bHkgdGhlIDFzdC1vcmRlciBkZXJpdmF0aXZlKSB3LnIudC4gdG8gd2VpZ2h0cyB3aWxsIGJlOgoKJCQKXGJlZ2lue2FsaWduZWR9ClxmcmFje1xwYXJ0aWFsXG1ib3h7TG9zc319e1xwYXJ0aWFsXGJldGFfMH0gCiY9IC0gXGZyYWN7Mn17Tn1cc3VtX2kgXGJpZ1sgeV9pIC0gKFxiZXRhXzAgKyBcYmV0YV8xeF9pKSBcYmlnXSwgXFwKXGZyYWN7XHBhcnRpYWxcbWJveHtMb3NzfX17XHBhcnRpYWxcYmV0YV8xfSAKJj0gLSBcZnJhY3syfXtOfVxzdW1faSBcYmlnWyB5X2kgLSAoXGJldGFfMCArIFxiZXRhXzF4X2kpIFxiaWddeF9pLgpcZW5ke2FsaWduZWR9CiQkCgpUaGUgY29ycmVzcG9uZGluZyBweXRob24gZnVuY3Rpb24gY2FuIGJlIGNvZGVkIGFzOgoKYGBge3B5dGhvbiBsaW5lYXJfcmVnX2dyYWRfZnVuY30KZGVmIGdyYWRfZnVuYyhYLCB5LCBiZXRhKToKICAiIiJDYWxjdWxhdGUgdmVjdG9yaXplZCBncmFkaWVudHMuIiIiCiAgcmV0dXJuIC0yICogKCh5IC0gWC5kb3QoYmV0YSkpLmRvdChYKSkgLyBYLnNoYXBlWzBdCmBgYAoKSWYgd2Ugc2V0IHRoZSBhYm92ZSBlcXVhdGlvbnMgdG8gemVybyB3ZSBjYW4gc29sdmUgZm9yICRcYmV0YV8wJCBhbmQgJFxiZXRhXzEkIGFuYWx5dGljYWxseSBhbmQgdGhlIHNvbHV0aW9uIHdpbGwgYmUgZXhhY3RseSBqdXN0IGVxdWF0aW9uICRcZXFyZWZ7ZXE6b2xzfSQuCkJ1dCBhcyB3ZSBqdXN0IHBvaW50ZWQgb3V0IGl0IHN1ZmZlcnMgZnJvbSBudW1lcmljYWwgc3RhYmlsaXR5IGlzc3VlLgoKVGhlIG1pbmltdW0gaW1wbGVtZW50YXRpb24gb2Ygb3VyIGdyYWRpZW50IGRlc2NlbnQgb3B0aW1pemVyIGlzIGp1c3QgYSBmZXcgbGluZXM6CgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfZ2R9CmRlZiBnZF9vcHRpbWl6ZShYLCB5LCBncmFkX2Z1bmMsIGxyPS4wMSwgbl9zdGVwPTEwMCk6CiAgYiA9IG5wLnJhbmRvbS5ub3JtYWwoc2l6ZT1YLnNoYXBlWzFdKQogIG91dCA9IGIuY29weSgpCiAgZm9yIHN0ZXAgaW4gcmFuZ2Uobl9zdGVwKToKICAgIGIgLT0gbHIgKiBncmFkX2Z1bmMoWCwgeSwgYikKICAgIG91dCA9IG5wLnJvd19zdGFjayhbb3V0LCBiXSkgICMgVHJhY2UgcmVzdWx0IG9mIGVhY2ggc3RlcC4KICByZXR1cm4gb3V0CgpiID0gZ2Rfb3B0aW1pemUoWCwgeSwgZ3JhZF9mdW5jLCBuX3N0ZXA9MzAwMCkKZm9yIHMgaW4gWzEwLCA1MCwgMTAwLCA1MDAsIDEwMDAsIDMwMDBdOgogIHByaW50KCJUcmFpbmluZyBTdGVwcyB7OjV9IHwgRXN0aW1hdGU6IHt9Ii5mb3JtYXQocywgYltzXSkpCmBgYAoKQXMgd2UgY2FuIHNlZSB0aGUgcmVzdWx0IGlzIGFwcHJvYWNoaW5nIHRvIG91ciBhbmFseXRpY2FsIHNvbHV0aW9uIGFzIHRoZSBudW1iZXIgb2Ygc3RlcHMgZ3Jvd3MuCgojIyMjIE9uIExlYXJuaW5nIFJhdGUgey19CgpMZWFybmluZyByYXRlIGlzIGEgaHlwZXItcGFyYW1ldGVyIGZvciBncmFkaWVudCBkZXNjZW50IG9wdGltaXplci4KVGhlIGdyYWRpZW50IHVwZGF0ZSB0byBvdXIgbW9kZWwgd2VpZ2h0cyBpcyBzY2FsZWQgZG93biBieSB0aGUgbGVhcm5pbmcgcmF0ZSB0byBtYWtlIHN1cmUgY29udmVyZ2VuY2UuClRvbyBsYXJnZSB0aGUgbGVhcm5pbmcgcmF0ZSB3aWxsIGV4cGxvZGUgdGhlIGdyYWRpZW50LgpUb28gc21hbGwgdGhlIGxlYXJuaW5nIHJhdGUgd2lsbCBzbG93IGRvd24gdGhlIGNvbnZlcmdlbmNlIGFuZCBzb21ldGltZXMgcmVzdWx0IGluIHRoZSBvcHRpbWl6ZXIgdHJhcHBlZCBhdCBsb2NhbCBzdWItb3B0aW11bS4KCkxldCdzIHJlLXdyaXRlIG91ciBncmFkaWVudCBkZXNjZW50IG9wdGltaXplciB0byBhbHNvIHRyYWNrIHRoZSBsb3NzIGZyb20gZWFjaCB0cmFpbmluZyBzdGVwLgpBbmQgd2UgdXNlIHRoZSBzYW1lIGluaXRpYWxpemF0aW9uIGZvciBhIGZhaXIgY29tcGFyaXNvbi4KCmBgYHtweXRob24gbGluZWFyX3JlZ19nZF93aXRoX2xvc3N9CmRlZiBsb3NzKFgsIHksIGJldGEpOgogIHJldHVybiAoKFguZG90KGJldGEpIC0geSkqKjIpLm1lYW4oKQoKZGVmIGdkX29wdGltaXplKFgsIHksIGdyYWRfZnVuYywgbHI9LjAxLCBuX3N0ZXA9MTAwKToKICBiID0gbnAuYXJyYXkoWzAuMCwgMC4wXSkKICBsID0gW2xvc3MoWCwgeSwgYildCiAgZm9yIHN0ZXAgaW4gcmFuZ2Uobl9zdGVwKToKICAgIGIgLT0gbHIqZ3JhZF9mdW5jKFgsIHksIGIpCiAgICBsLmFwcGVuZChsb3NzKFgsIHksIGIpKQogIHJldHVybiBiLCBsCmBgYAoKTm93IHdlIHJ1biB0aGUgb3B0aW1pemF0aW9uIHdpdGggYSB2YXJpZXR5IG9mIGRpZmZlcmVudCBsZWFybmluZyByYXRlcy4KRm9yIGlsbHVzdHJhdGlvbiBwdXJwb3NlIHdlIHdpbGwgb25seSBydW4gYSBmZXcgc3RlcHMuCgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfZGlmZl9scn0KYmV0YXMgPSB7fQpsb3NzZXMgPSB7fQpmb3IgbHIgaW4gWy4wMDEsIC4wMSwgLjA1LCAuMSwgMV06CiAgYmV0YXNbbHJdLCBsb3NzZXNbbHJdID0gZ2Rfb3B0aW1pemUoWCwgeSwgZ3JhZF9mdW5jLCBscj1sciwgbl9zdGVwPTE1KQoKZm9yIHIsIGIgaW4gYmV0YXMuaXRlbXMoKToKICBwcmludCgiTGVhcm5pbmcgUmF0ZSB7OjV9IHwgRXN0aW1hdGU6IHt9Ii5mb3JtYXQociwgYikpCmBgYAoKVGhlIHJlc3VsdCBzdWdnZXN0cyB0aGF0IGEgbGVhcm5pbmcgcmF0ZSBvZiAxIGlzIHRvbyBsYXJnZSBmb3Igb3VyIHByb2JsZW0uClRoZSBncmFkaWVudCBleHBsb2RlcyB3aGljaCBtYWtlIG91ciBzb2x1dGlvbiBkaXZlcmdlLgpBbmQgYSBsb3dlciBsZWFybmluZyByYXRlIGluIGdlbmVyYWwgY29udmVyZ2VzIHNsb3dlciB0byB0aGUgb3B0aW11bS4KTnVtYmVyIG9mIGV4YW1wbGVzIHVzZWQgdG8gY2FsY3VsYXRlIHRoZSBncmFkaWVudCBhbHNvIHdpbGwgYWZmZWN0IHRoZSBjb252ZXJnZW5jZSBiZWhhdmlvci4KSW4gZ2VuZXJhbCBpZiB0aGUgc2FtcGxlIHNpemUgaXMgdG9vIHNtYWxsIGEgc21hbGxlciBsZWFybmluZyByYXRlIHNob3VsZCBiZSB1c2VkIHRvIGF2b2lkIGdyYWRpZW50IGV4cGxvc2lvbi4KClRoaXMgY2FuIGJlIG1vcmUgY2xlYXJseSBzZWVuIGlmIHdlIHBsb3QgdGhlIHRyYWNlIG9mIG91ciB0cmFpbmluZyBsb3NzZXM6CgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfZGlmZl9scl9wbG90fQpvZmlsZSA9ICJwbG90cy90b3lfcmVnX2xvc3NfY29tcGFyZS5odG1sIgpwZGF0YSA9IFtnby5TY2F0dGVyKHg9bnAuYXJhbmdlKGxlbihsb3NzZXNbbHJdKSksIHk9bG9zc2VzW2xyXSwgbmFtZT1scikgCiAgICAgICAgIGZvciBsciBpbiBsb3NzZXMua2V5cygpXQpwID0gcGxvdF9vZmZsaW5lKAogIG9maWxlPW9maWxlLAogIGRhdGE9cGRhdGEsCiAgbGF5b3V0PWdvLkxheW91dCgKICAgIHRpdGxlPSJUcmFjZSBvZiBUcmFpbmluZyBMb3NzIHdpdGggVmFyaW91cyBMZWFybmluZyBSYXRlcyIsCiAgICB4YXhpcz1kaWN0KHRpdGxlPSJTdGVwIiksCiAgICB5YXhpcz1kaWN0KHRpdGxlPSJMb3NzIikpKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpodG1sdG9vbHM6OmluY2x1ZGVIVE1MKHB5JG9maWxlKQpgYGAKCklmIHRoZSBsb3NzIGRvZXNuJ3QgZGVjcmVhc2Ugb3ZlciB0cmFpbmluZyBpdGVyYXRpb24sCml0IGlzIGEgc2lnbmFsIHRoYXQgc29tZXRoaW5nIGlzIHdyb25nIHdpdGggb3VyIG1vZGVsLgoKVW5saWtlIG91ciB0b3kgaW1wbGVtZW50YXRpb24sCmluIG1vZGVybiBpbXBsZW1lbnRhdGlvbiBvZiBhbnkgbnVtZXJpY2FsIG9wdGltaXplciB0aGVyZSB3aWxsIGJlIGEgbG90IG9mIHRlY2huaXF1ZXMgdG8gZG8gdGhlIGJlc3QgdG8gYXZvaWQgY29udmVyZ2VuY2UgZmFpbHVyZS4KQnV0IGl0IGlzIHRoZSBtb2RlbCBkZXZlbG9wZXIncyByZXNwb25zaWJpbGl0eSB0byBkaWFnbm9zZSB0aGUgdHJhaW5pbmcgYmVoYXZpb3IgYmVmb3JlIGFueXRoaW5nIGlzIGRlbGl2ZXJlZCB0byB0aGUgc3Rha2Vob2xkZXIuCkNoZWNraW5nIHRoZSBkeW5hbWljcyBvZiBsb3NzIGlzIHVzdWFsbHkgdGhlIGZpcnN0IGFuZCBxdWljayBzdGVwIHRvIGV4YW1pbmUgd2hldGhlciB0aGUgdHJhaW5pbmcgdGFzayBpcyBmdW5jdGlvbmluZyBhcyBleHBlY3RlZC4KCiMjIyMgU3RvY2Foc3RpYyBHcmFkaWVudCBEZXNjZW50IHstfQoKVGhlIHZhbmlsbGEgZ3JhZGllbnQgZGVzY2VudCBvcHRpbWl6ZXIgd2UganVzdCBpbXBsZW1lbnRlZCBoYXMgb25lIGlzc3VlLgpTaW5jZSBmb3IgZWFjaCB1cGRhdGUgaXQgbmVlZHMgdG8gdHJhdmVyc2Ugb3ZlciB0aGUgZW50aXJlIGRhdGFzZXQsCml0IGJlY29tZXMgdG9vIHNsb3cgd2hlbiBpdCBjb21lcyB0byBsYXJnZSBkYXRhc2V0LgoKU3RvY2hhc3RpYyBncmFkaWVudCBkZXNjZW50IGlzIHRvIG92ZXJjb21lIHRoaXMgaXNzdWUuCkluc3RlYWQgb2YgY2FsY3VsYXRlIHRoZSBncmFkaWVudCB1c2luZyB0aGUgZW50aXJlIGRhdGFzZXQsCndlIGNhbiB1c2Ugb25seSBvbmUgZXhhbXBsZSBwZXIgdXBkYXRlLgpFYWNoIHN0ZXAgd2lsbCBiZSBsZXNzIHByZWNpc2UgYnV0IHN0YXRpc3RpY2FsbHkgdGhlIGZpbmFsIHJlc3VsdCBzaG91bGQgYmUgY29uc2lzdGVudC4KCkhlcmUgd2UgaW50cm9kdWNlIHRoZSB0ZXJtICplcG9jaCo6Ck9uZSBlcG9jaCBpcyBmb3IgdGhlIG9wdGltaXplciB0byB0cmF2ZXJzZSB0aGUgZW50aXJlIGRhdGFzZXQgb25jZS4KTnVtYmVyIG9mIGVwb2NocyBjYW4gYmUgY29uc2lkZXJlZCBhcyBhbm90aGVyIGh5cGVyLXBhcmFtZXRlciBvZiBhIG1vZGVsLgoKSGVyZSBjb21lcyB0aGUgbWluaW11bSBTR0QgaW1wbGVtZW50YXRpb246CgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfc2dkfQpkZWYgc2dkX29wdGltaXplKFgsIHksIGdyYWRfZnVuYywgbHI9LjAxLCBuX2Vwb2NoPTEwKToKICBiID0gbnAucmFuZG9tLm5vcm1hbChzaXplPVguc2hhcGVbMV0pCiAgbCA9IFtsb3NzKFgsIHksIGIpXQogIG91dCA9IGIuY29weSgpCiAgZm9yIGVwb2NoIGluIHJhbmdlKG5fZXBvY2gpOgogICAgIyBTaHVmZmxlIHRoZSBkYXRhc2V0IGJlZm9yZSBlYWNoIGVwb2NoLgogICAgc2lkID0gbnAucmFuZG9tLnBlcm11dGF0aW9uKFguc2hhcGVbMF0pCiAgICBmb3IgaSBpbiBzaWQ6CiAgICAgIGIgLT0gbHIgKiBncmFkX2Z1bmMoWFtOb25lLGksOl0sIHlbaV0sIGIpCiAgICAgIGwuYXBwZW5kKGxvc3MoWFtOb25lLGksOl0sIHlbaV0sIGIpKQogICAgIyBUcmFjZSB0aGUgcmVzdWx0IHBlciBlcG9jaC4KICAgIG91dCA9IG5wLnJvd19zdGFjayhbb3V0LCBiXSkKICByZXR1cm4gb3V0LCBsCgpzZ2RfYmV0YSwgc2dkX2xvc3MgPSBzZ2Rfb3B0aW1pemUoWCwgeSwgZ3JhZF9mdW5jLCBscj0uMDEsIG5fZXBvY2g9MTAwKQpmb3IgZXBvY2ggaW4gWzEsIDUsIDEwLCA1MCwgMTAwXToKICBwcmludCgiVHJhaW5pbmcgRXBvY2hzIHs6NH0gfCBFc3RpbWF0ZToge30iLmZvcm1hdChlcG9jaCwgc2dkX2JldGFbZXBvY2hdKSkKYGBgCgpTR0Qgd29uJ3QgYmUgYXMgcHJlY2lzZSBhcyB2YW5pbGxhIEdEIGJ1dCBmb3IgbGFyZ2Ugc2NhbGUgYXBwbGljYXRpb24gaXQgY2FuIHJlZHVjZSBjb25zaWRlcmFibGUgYW1vdW50IG9mIGNvbXB1dGluZyB0aW1lIChzb21ldGltZXMgZnJvbSBpbmZlYXNpYmxlIHRvIGZlYXNpYmxlKS4KSW4gb3VyIHNpbXBsZSBwcm9ibGVtIGluZGVlZCBqdXN0IDEgZXBvY2ggY2FuIGhhdmUgYSBnb29kIGFwcHJveGltYXRpb24gYWxyZWFkeS4KClRoZSB0cmFjZSBvZiAocGVyLWluc3RhbmNlKSBsb3NzIGZvciB0aGUgZmlyc3QgNTAwIHVwZGF0ZXM6CgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfc2dkX3Bsb3R9Cm9maWxlID0gInBsb3RzL3RveV9yZWdfc2dkX2xvc3MuaHRtbCIKcCA9IHBsb3Rfb2ZmbGluZSgKICBvZmlsZT1vZmlsZSwKICBkYXRhPVtnby5TY2F0dGVyKHg9bnAuYXJhbmdlKGxlbihzZ2RfbG9zc1s6NTAwXSkpLCB5PXNnZF9sb3NzWzo1MDBdKV0sCiAgbGF5b3V0PWdvLkxheW91dCgKICAgIHRpdGxlPSJUcmFjZSBvZiBTR0QgVHJhaW5pbmcgTG9zcyIsCiAgICB4YXhpcz1kaWN0KHRpdGxlPSJTdGVwIiksCiAgICB5YXhpcz1kaWN0KHRpdGxlPSJMb3NzIikpKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpodG1sdG9vbHM6OmluY2x1ZGVIVE1MKHB5JG9maWxlKQpgYGAKClNpbmNlIHRoZSBsb3NzIGlzIGNhbGN1bGF0ZWQgb24gYSBwZXItaW5zdGFuY2UgYmFzaXMsCml0IHdpbGwgZmx1Y3R1YXRlIGJ1dCB3aXRoIGEgZGVjcmVhc2luZyB0cmVuZCBpZiBub3RoaW5nIHdlbnQgd3JvbmcgYWJvdXQgdGhlIG9wdGltaXphdGlvbiBwcm9jZXNzLgoKSW4gYHNrbGVhcm5gIHRoZXJlIGFyZSBkZWRpY2F0ZWQgU0dEIGNsYXNzZXMgZm9yIGEgdmFyaWV0eSBvZiBsZWFybmluZyBhbGdvcml0aG1zLgpGb3IgYSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCB3aXRoIFNHRCBzb2x2ZXI6CgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfc2dkX3NrbGVhcm59CmZyb20gc2tsZWFybi5saW5lYXJfbW9kZWwgaW1wb3J0IFNHRFJlZ3Jlc3NvcgojIFR1cm4gb2ZmIHRoZSBkZWZhdWx0IGVsYXN0aWNuZXQgcmVndWxhcml6YXRpb24gZm9yIGNvbXBhcmlzb24uCnByaW50KFNHRFJlZ3Jlc3NvcihhbHBoYT0wLCBsMV9yYXRpbz0wLCBmaXRfaW50ZXJjZXB0PUZhbHNlKS5maXQoWCwgeSkuY29lZl8pCmBgYAoKIyMjIyBCYXRjaCBHcmFkaWVudCBEZXNjZW50IHstfQoKVG8gcmVkdWNlIHRoZSBub2lzZSBpbiBTR0Qgd2UgY2FuIG1vZGlmeSBpdCBieSByZXBsYWNpbmcgMSB0cmFpbmluZyBleGFtcGxlIHdpdGggYSBiYXRjaCBvZiBleGFtcGxlcyBpbiBhIHNpbmdsZSBncmFkaWVudCB1cGRhdGUuCkhlcmUncyBzdWNoIGltcGxlbWVudGF0aW9uIG9mIGEgYmF0Y2ggZ3JhZGllbnQgZGVzY2VudCBvcHRpbWl6ZXJeW1NvbWV0aW1lcyAibWluaS1iYXRjaCBncmFkaWVudCBkZXNjZW50IiBpcyB1c2VkIHRvIGRlc2NyaWJlIFNHRCB3aXRoIGJhdGNoIHNpemUgPiAxLiBIZXJlIHdlIGlnbm9yZSB0aGUgcmVkdW5kYW50IHdvcmRpbmcgYW5kIGp1c3QgY2FsbCBpdCBiYXRjaCBncmFkaWVudCBkZXNjZW50Ll06CgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfZ2RfYmF0Y2h9CmRlZiBnZF9iYXRjaF9vcHRpbWl6ZShYLCB5LCBncmFkX2Z1bmMsIGxyPS4wMSwgbl9lcG9jaD0xMCwgYmF0Y2hfc2l6ZT02NCk6CiAgYiA9IG5wLnJhbmRvbS5ub3JtYWwoc2l6ZT1YLnNoYXBlWzFdKQogIGwgPSBbbG9zcyhYLCB5LCBiKV0KICBmb3IgZXBvY2ggaW4gcmFuZ2Uobl9lcG9jaCk6CiAgICAjIFNodWZmbGUgdGhlIGRhdGFzZXQgYmVmb3JlIGVhY2ggZXBvY2guCiAgICBzaWQgPSBucC5yYW5kb20ucGVybXV0YXRpb24oWC5zaGFwZVswXSkKICAgIFhzID0gWFtzaWQsOl0KICAgIHlzID0geVtzaWRdCiAgICBpID0gMAogICAgbl9zdGVwID0gaW50KG5wLmNlaWwoWC5zaGFwZVswXSAvIGJhdGNoX3NpemUpKQogICAgZm9yIHN0ZXAgaW4gcmFuZ2Uobl9zdGVwKToKICAgICAgWGIgPSBYc1tpOmkrYmF0Y2hfc2l6ZSw6XQogICAgICB5YiA9IHlzW2k6aStiYXRjaF9zaXplXQogICAgICBiIC09IGxyKmdyYWRfZnVuYyhYYiwgeWIsIGIpCiAgICAgIGwuYXBwZW5kKGxvc3MoWGIsIHliLCBiKSkKICAgICAgaSArPSBiYXRjaF9zaXplCiAgcmV0dXJuIGIsIGwKCmJhdGNoX2JldGEsIGJhdGNoX2xvc3MgPSBnZF9iYXRjaF9vcHRpbWl6ZShYLCB5LCBncmFkX2Z1bmMsIG5fZXBvY2g9MTAwLCBiYXRjaF9zaXplPTY0KQpwcmludChiYXRjaF9iZXRhKQpgYGAKCkZvciB0aGlzIHNpbXBsZSBwcm9ibGVtIGJhdGNoIHNpemUgZG9lc24ndCBoYXZlIGFueSBpbXBvcnRhbnQgaW1wYWN0IGdpdmVuIGVub3VnaCBudW1iZXIgb2YgdHJhaW5pbmcgZXBvY2hzOgoKYGBge3B5dGhvbiBsaW5lYXJfcmVnX2dkX2RpZmZfYmF0Y2hfc2l6ZX0KZm9yIGJzaXplIGluIFs4LCAxNiwgMzIsIDY0LCAxMjgsIDI1Nl06CiAgcHJpbnQoIkJhdGNoIFNpemU6IHs6NH0gfCBFc3RpbWF0ZToge30iLmZvcm1hdCgKICAgIGJzaXplLAogICAgZ2RfYmF0Y2hfb3B0aW1pemUoWCwgeSwgZ3JhZF9mdW5jLCBuX2Vwb2NoPTEwMCwgYmF0Y2hfc2l6ZT1ic2l6ZSlbMF0pKQpgYGAKCkJhdGNoIG9wdGltaXplciBpcyBjdXJyZW50bHkgdGhlIGJlc3QgcHJhY3RpY2Ugb2YgdHJhaW5pbmcgbmV1cmFsIG5ldHdvcmtzIGluIGxhcmdlIHNjYWxlIGFwcGxpY2F0aW9uLgpUaGUgYmF0Y2ggc2l6ZSBkZXBlbmRzIG9uIHRoZSBhY3R1YWwgYXBwbGljYXRpb24gYnV0IHVzdWFsbHkgcmFuZ2VzIGZyb20gOCAobWluaSkgdG8gMTAyNCAobGFyZ2UpLgpUaGUgbWluaS1iYXRjaCBzdG9jaGFzdGljIGdyYWRpZW50IGRlc2NlbnQgaXMgc28gcHJldmlhbCB0aGF0IGluIGdlbmVyYWwgd2hlbiBwZW9wbGUgdGFsayBhYm91dCBTR0QgZm9yIGEgbmV1cmFsIG5ldHdvcmsgdGhleSBhcmUgYWN0dWFsbHkgcmVmZXJyaW5nIHRvIHRoZSBiYXRjaCBncmFkaWVudCBkZXNjZW50LgoKIyMgTG9naXN0aWMgUmVncmVzc2lvbgoKQSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVscyB0aGUgb3V0Y29tZSBwcm9iYWJsaXN0aWNhbGx5OgoKJCQKXGJlZ2lue2VxdWF0aW9ufSBcbGFiZWx7ZXE6bG9naXR9ClAoWSA9IDEpID0gXGZyYWN7MX17MSArIGVeey1YXGJldGF9fSwKXGVuZHtlcXVhdGlvbn0KJCQKCkhlcmUgdGhlIHNpZ21vaWQgZnVuY3Rpb24gJHModCkgPSBcZnJhY3sxfXsxICsgZV57LXR9fSQgaXMgdXNlZCB0byB0cmFuc2Zvcm0gYSByZWFsIG51bWJlciBpbnRvIHByb2JhYmlsaXR5IHNwYWNlICRbMCwgMV0kLgoKV2UgY2FuIGludGVycHJldCB0aGUgbW9kZWwgYXMgYSBsaW5lYXIgbW9kZWwgaW4gbG9nLW9kZHMuCkFzc3VtaW5nIFkgaXMgYmluYXJ5IGFuZCB0YWtlIGEgdmFsdWUgb2YgMCBvciAxLAp0aGUgb2RkcyBvZiAkWSA9IDEkIGlzIGRlZmluZWQgYXMgJFxmcmFje1AoWSA9IDEpfXtQKFkgPSAwKX0gPSBcZnJhY3tQKFkgPSAxKX17MSAtIFAoWSA9IDEpfSQuCldlIGNhbiByZS1hcnJhbmdlIHRoZSBsb2dpc3RpYyBtb2RlbCBlcXVhdGlvbjoKCiQkClxiZWdpbnthbGlnbmVkfQpcbG4gXEJpZ2dbIFxmcmFje1AoWSA9IDEpfXsxIC0gUChZID0gMSl9IFxCaWdnXSAKJj0gXGxuIFxCaWdnWyBcZnJhY3tcZnJhY3sxfXsxICsgZV57LVhcYmV0YX19fXtcZnJhY3tlXnstWFxiZXRhfX17MSArIGVeey1YXGJldGF9fX0gXEJpZ2ddIFxcCiY9IFxsbigxICsgZV57LVhcYmV0YX0pIC0gXGxuIGVeey1YXGJldGF9KDEgKyBlXnstWFxiZXRhfSkgXFwKJj0gXGxuKDEgKyBlXnstWFxiZXRhfSkgLSBcbG4gZV57LVhcYmV0YX0gLSBcbG4oMSArIGVeey1YXGJldGF9KSBcXAomPSAtIFxsbiBlXnstWFxiZXRhfSBcXAomPSBYXGJldGEuClxlbmR7YWxpZ25lZH0KJCQKClRoYXQgaXMsCnRoZSBtb2RlbCB3ZWlnaHRzIGFyZSBsaW5lYXIgaW4gdGhlIGxvZy1vZGRzIG9mIG91ciB0YXJnZXQgb3V0Y29tZS4KCldoZW4gdGhlIHByb2JhYmlsaXR5IGlzIHRyYW5zZm9ybWVkIGludG8gb2RkcywKdGhlIHJhbmdlIGlzIHRyYW5zZm9ybWVkIGZyb20gJFswLDFdJCB0byAkWzAsXGluZnR5XSQuCgpgYGB7cHl0aG9uIGxvZ2l0X3Byb2Jfb2Rkc19wbG90fQojIFdlIHBsb3Qgb25seSB1cCB0byAuOTUgc2luY2UgdGhlIHZhbHVlIG9mIG9kZHMgd2lsbCBncm93IHVuYm91bmRlZGx5Lgpwcm9iID0gbnAubGluc3BhY2UoLjAwMSwgLjk1LCBudW09MTAwKQpvZGRzID0gcHJvYiAvICgxIC0gcHJvYikKbG9nX29kZHMgPSBucC5sb2cob2RkcykKCm9maWxlID0gInBsb3RzL3Byb2Jfb2Rkcy5odG1sIgpwID0gcGxvdF9vZmZsaW5lKAogIG9maWxlPW9maWxlLAogIGRhdGE9W2dvLlNjYXR0ZXIoeD1wcm9iLCB5PW9kZHMpXSwKICBsYXlvdXQ9Z28uTGF5b3V0KAogICAgdGl0bGU9IkV2ZW50IFByb2JhYmlsaXRpZXMgdG8gT2RkcyIsCiAgICB4YXhpcz1kaWN0KHRpdGxlPSJQcm9iYWJpbGl0eSIpLAogICAgeWF4aXM9ZGljdCh0aXRsZT0iT2RkcyIpKSkKYGBgCgpgYGB7ciwgZWNobz1GQUxTRX0KaHRtbHRvb2xzOjppbmNsdWRlSFRNTChweSRvZmlsZSkKYGBgCgpJZiB3ZSBmdXJ0aGVyIHRyYW5zZm9ybSBvZGRzIHRvIGxvZy1vZGRzLAp0aGUgcmFuZ2UgaXMgdHJhbnNmb3JtZWQgZnJvbSAkWzAsXGluZnR5XSQgdG8gJFstXGluZnR5LFxpbmZ0eV0kLgoKYGBge3B5dGhvbiBsb2dpdF9vZGRzX2xvZ29kZHNfcGxvdH0Kb2ZpbGUgPSAicGxvdHMvcHJvYl9vZGRzLmh0bWwiCnAgPSBwbG90X29mZmxpbmUoCiAgb2ZpbGU9b2ZpbGUsCiAgZGF0YT1bZ28uU2NhdHRlcih4PW9kZHMsIHk9bG9nX29kZHMpXSwKICBsYXlvdXQ9Z28uTGF5b3V0KAogICAgdGl0bGU9IkV2ZW50IE9kZHMgdG8gTG9nLU9kZHMiLAogICAgeGF4aXM9ZGljdCh0aXRsZT0iT2RkcyIpLAogICAgeWF4aXM9ZGljdCh0aXRsZT0iTG9nLU9kZHMiKSkpCmBgYAoKYGBge3IsIGVjaG89RkFMU0V9Cmh0bWx0b29sczo6aW5jbHVkZUhUTUwocHkkb2ZpbGUpCmBgYAoKUHV0IGl0IHRvZ2V0aGVyIGlzIHRoZSBlZmZlY3Qgb2YgdGhlIHNpZ21vaWQgZnVuY3Rpb246CgpgYGB7cHl0aG9uIGxvZ2l0X3NpZ21vaWRfcGxvdH0KIyBOb3RlIHRoYXQgaW5zdGVhZCBvZiBjb2RpbmcgcyA9IDEgLyAoMSArIG5wLmV4cCgtdCkpLAojIHdoaWNoIGlzIG51bWVyaWNhbGx5IHVuc3RhYmxlLAojIHdlIHNob3VsZCB1c2UgYSBtYXRoIHRyaWNrIHRvIG1ha2UgaXQgc3RhYmxlLgpkZWYgc2lnbW9pZCh0KToKICAiIiJOdW1lcmljYWxseSBzdGFibGUgc2lnbW9pZC4iIiIKICByZXR1cm4gbnAuZXhwKC1ucC5sb2dhZGRleHAoMCwgLXQpKQoKdCA9IG5wLmxpbnNwYWNlKC0xMCwgMTAsIG51bT0xMDApCm9maWxlID0gInBsb3RzL3NpZ21vaWQuaHRtbCIKcCA9IHBsb3Rfb2ZmbGluZSgKICBvZmlsZT1vZmlsZSwKICBkYXRhPVtnby5TY2F0dGVyKHg9dCwgeT1zaWdtb2lkKHQpKV0sCiAgbGF5b3V0PWdvLkxheW91dCgKICAgIHRpdGxlPSJBIFNpZ21vaWQgRnVuY3Rpb24iLAogICAgeGF4aXM9ZGljdCh0aXRsZT0iUmF3IFZhbHVlIiksCiAgICB5YXhpcz1kaWN0KHRpdGxlPSJQcm9iYWJpbGl0eSIpKSkKYGBgCgo8YSBuYW1lPSJzaWdtb2lkX3Bsb3QiPjwvYT4KCmBgYHtyLCBlY2hvPUZBTFNFfQpodG1sdG9vbHM6OmluY2x1ZGVIVE1MKHB5JG9maWxlKQpgYGAKCiMjIyBDcm9zcyBFbnRyb3B5CgpIb3cgZG8gd2Ugc29sdmUgZm9yIHRoZSBtb2RlbCB3ZWlnaHRzICRcYmV0YSQgaW4gZXF1YXRpb24gJFxlcXJlZntlcTpsb2dpdH0kPwpJbiB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgd2Ugc29sdmUgZm9yIHRoZSB3ZWlnaHRzIGJ5IG1pbmltaXppbmcgdGhlIG1lYW4gc3F1YXJlZCBlcnJvci4KSW4gbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB3ZSBuZWVkIHRvIGRlZmluZSBtZWFzdXJlbWVudCBmb3IgbW9kZWxpbmcgZXJyb3IgYXMgd2VsbC4KClB1dCBpdCBkaWZmZXJlbnRseSwKd2UnZCBsaWtlIHRvIGNhbGN1bGF0ZSB0aGUgZGlzdGFuY2UgYmV0d2VlbiBvdXIgcHJlZGljdGVkIHByb2JhYmlsaXR5IGFuZCB0aGUgcmVhbCBldmVudCBsYWJlbCBkaXN0cmlidXRpb24gKGNhbGxlZCB0aGUgKmVtcGlyaWNhbCBkaXN0cmlidXRpb24qKS4KSW4gaW5mb3JtYXRpb24gdGhlb3J5IHRoZSBjcm9zcyBlbnRyb3B5IGlzIHVzZWQgdG8gbWVhc3VyZSB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0d28gcHJvYmFiaWxpdHkgZGlzdHJpYnV0aW9uLgoKIyMjIyBTaGFubm9uJ3MgRW50cm9weSBhcyBhIE1lYXN1cmUgb2YgSW5mb3JtYXRpb24gey19CgpUaGUgW2VudHJvcHldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0VudHJvcHlfKGluZm9ybWF0aW9uX3RoZW9yeSkpIG9mIGEgZGlzY3JldGUgcHJvYmFiaWxpdHkgZGlzdHJpYnV0aW9uIGlzIGRlZmluZWQgYXM6CgokJApcYmVnaW57ZXF1YXRpb259IFxsYWJlbHtlcTplbnRyb3B5fQpIKHApID0gLSBcc3VtX2kgcF9pIFxsb2dfMnBfaSwKXGVuZHtlcXVhdGlvbn0KJCQKCndoZXJlICRwX2kkIGlzIHRoZSBwcm9iYWJpbGl0eSBmb3IgZXZlbnQgJGkkLgpJdCBtZWFzdXJlcyB0aGUgdW5jZXJ0YWludHkgb2YgYSBzdG9jaGFzdGljIGV2ZW50LgoKVGFrZSBhIGNvaW4gZmxpcCBldmVudCBhcyBleGFtcGxlLgpXZSBwbG90IHRoZSBlbnRyb3B5IHZhbHVlIGF0IGRpZmZlcmVudCB2YWx1ZSBvZiBjb2luIGJpYXMgKHRoZSBwcm9iYWJpbGl0eSBvZiBoYXZpbmcgYSBoZWFkIGluc3RlYWQgb2YgYSB0YWlsLikKCmBgYHtweXRob24gbG9naXRfY29pbl9mbGlwX2VudHJvcHlfcGxvdH0KZGVmIGVudHJvcHlfYmluYXJ5KHApOgogIHJldHVybiAtcCpucC5sb2cyKHApIC0gKDEgLSBwKSpucC5sb2cyKDEgLSBwKQogIApwcm9iID0gbnAubGluc3BhY2UoLjAxLCAuOTksIG51bT0xMDApCgpvZmlsZSA9ICJwbG90cy9lbnRyb3B5Lmh0bWwiCnAgPSBwbG90X29mZmxpbmUoCiAgb2ZpbGU9b2ZpbGUsCiAgZGF0YT1bZ28uU2NhdHRlcih4PXByb2IsIHk9ZW50cm9weV9iaW5hcnkocHJvYikpXSwKICBsYXlvdXQ9Z28uTGF5b3V0KAogICAgdGl0bGU9IkVudHJvcHkgb2YgYSBCaW5hcnkgRXZlbnQgKENvaW4gRmxpcCkiLAogICAgeGF4aXM9ZGljdCh0aXRsZT0iUHJvYmFiaWxpdHkgb2YgYSBIZWFkIiksCiAgICB5YXhpcz1kaWN0KHRpdGxlPSJFbnRyb3B5IikpKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpodG1sdG9vbHM6OmluY2x1ZGVIVE1MKHB5JG9maWxlKQpgYGAKCkl0IGlzIG9idmlvdXMgdGhhdCB0aGUgZW50cm9weSBvZiB0aGlzIGV2ZW50IGlzIG1heGltaXplZCB3aGVuIHRoZSBwcm9iYWJpbGl0eSBvZiBmbGlwcGluZyBhIGhlYWQgaXMgZXhhY3RseSAwLjUuCkF0IHRoaXMgbGV2ZWwgdGhlIGV2ZW50IGhhcyBhIGhpZ2hlc3QgbGV2ZWwgb2YgdW5jZXJ0YWludHkgaW4gYSBzZW5zZSB0aGF0IGl0IGlzIHRoZSBtb3N0IGRpZmZpY3VsdCBjYXNlIHRvIHByZWRpY3QgdGhlIG91dGNvbWUgb2YgYSBmbGlwLgoKV2hhdCBpcyB0aGUgaW50dWl0aW9uIGJlaGluZCBlbnRyb3B5PwoKIyMjIyMgKipTdXJwcmlzYWwgaW4gVGVybXMgb2YgUmFyaXR5Kiogey19CgpJbiBlcXVhdGlvbiAkXGVxcmVme2VxOmVudHJvcHl9JCB3ZSBmaXJzdCBuZWVkIHRvIGludGVycHJldCB0aGUgc2VlbWluZ2x5IG15c3RlcmlvdXMgdGVybSAkLVxsb2dfMnBfaSQuCkl0IGlzIHRoZSBsb2cgb2YgdGhlIHJlY2lwcm9jYWwgb2YgYSBwcm9iYWJpbGl0eS4KR2VuZXJhbGx5IHNwZWFraW5nLAp0aGUgcmVjaXByb2FsIG9mIGEgcHJvYmFiaWxpdHkgJFxmcmFjezF9e3AoeCl9JCBpcyBhICoxLWluLW4qIHNjYWxlIG9mIHByb2JhYmlsaXR5IHN0YXRlbWVudCBzYXlpbmcgdGhhdCBpbiBvcmRlciBmb3IgdGhlIG91dGNvbWUgJHgkIHRvIG9jY3VyIGF0IGxlYXN0IG9uY2UgZnJvbSBhIHN0b2NoYXN0aWMgZXZlbnQsCml0IGlzIGV4cGVjdGVkIHRvIHJlcXVpcmUgaGF2aW5nIHJlcGVhdGVkICRuID0gXGZyYWN7MX17cCh4KX0kIHRpbWVzIHRoZSBldmVudC4KVGhlIHJhcmUgdGhlIG91dGNvbWUsCnRoZSBoaWdoZXIgdGhlIHJlY2lwcm9jYWwgb2YgaXRzIHByb2JhYmlsaXR5LgpXaGVuIGEgcmFyZSBldmVudCBvY2N1cnMsCml0IGNvbnRhaW5zIG11Y2ggbW9yZSBpbmZvcm1hdGlvbiB0aGFuIGEgZnJlcXVlbnQgKG9yIG1vcmUgcHJvYmFibGUpIG9uZS4KSW5mb3JtYXRpb24gaW4gdGhpcyB3YXkgY2FuIGJlIGNvbnNpZGVyZWQgYXMgdGhlIGxldmVsIG9mICpzdXJwcmlzYWwqLgoKIyMjIyMgKipJbmZvcm1hdGlvbiBFbmNvZGluZyoqIHstfQoKQnV0IHdoeSB0YWtpbmcgbG9nPwpFc3BlY2lhbGx5IHdoeSBsb2cgb2YgYmFzZSAqdHdvKiBpbiBTaGFubm9uJ3MgZW50cm9weT8KVGhpcyByZWxhdGVzIHRvIHRoZSB1c2Ugb2YgKmJpdHMqIHRvIGVuY29kZSBtZXNzYWdlcy9pbmZvcm1hdGlvbi9yYW5kb20gc3RhdGVzL2ZhY3RzLgpBIGJpdCAoMCBvciAxKSBjYW4gYmUgdXNlZCB0byBlbmNvZGUgKnR3byogZmFjdHMuCkZvciBleGFtcGxlLAowIGZvciBhIHRhaWwgYW5kIDEgZm9yIGEgaGVhZCBpbiBhIGNvaW4gZmxpcCBleGVyY2lzZS4KQXBwYXJlbnRseSBpZiB3ZSBoYXZlICROJCBiaXRzIHdlIGNhbiBlbmNvZGUgdXAgdG8gJDJeTiQgZGlmZmVyZW50IG91dGNvbWVzIGluIGEgc3RvY2hhc3RpYyBldmVudC4KClRoZSBkaXNjdXNzaW9uIGRvZXNuJ3QgbGltaXQgdG8gYmluYXJ5LgpJZiB3ZSBoYXZlIDEwMCBwb3NzaWJsZSBvdXRjb21lcyBmcm9tIGEgcmFuZG9tIGV2ZW50LAp3aXRoIDEwIGRpc3RpbmN0IHN5bWJvbHMgKDAgdG8gOSksCndlIG9ubHkgbmVlZCAkXGxvZ197MTB9MTAwID0gMiQgZGlnaXRzIHRvIGVuY29kZSB0aGVtIChkZWNpbWFsIDAgdG8gOTkpLgoKTm93IHB1dCB0aGVtIHRvZ2V0aGVyLAp0aGUgcmFyZSB0aGUgb3V0Y29tZSwKdGhlIGhpZ2hlciB0aGUgcmVjaXByb2NhbCBvZiBpdHMgcHJvYmFiaWxpdHksCmFuZCB0aGUgbW9yZSBiaXRzIHJlcXVpcmVkIHRvIGVuY29kZSB0aGlzIGluZm9ybWF0aW9uIChzbyBhcyB0byBkaXN0aW5ndWlzaCBmcm9tIG90aGVyIHBvc3NpYmxlIG91dGNvbWVzKS4KClRoZSBmb3JtdWxhICRcbG9nXzJcZnJhY3sxfXtQKHgpfSQgY2FsY3VsYXRlcyBob3cgbWFueSBiaXRzIGFyZSBuZWNlc3NhcnkgdG8gZW5jb2RlIGEgc3RvY2hhc3RpYyBvdXRjb21lICR4JCB0aGF0IG1heSBvY2N1ciBvbmNlIG91dCBvZiAkXGZyYWN7MX17UCh4KX0kIHRpbWVzLgpMZXQncyBjYWxsIHRoaXMgYW1vdW50IGFzIHRoZSBpbmZvcm1hdGlvbiBjb250YWluZWQgYnkgb3V0Y29tZSAkeCQuClRoZW4gdGhlIGVudHJvcHkgaW4gJFxlcXJlZntlcTplbnRyb3B5fSQgZXNzZW50aWFsbHkgaXMgY2FsY3VsYXRpbmcgdGhlIGV4cGVjdGVkIG51bWJlciBvZiBiaXRzIHJlcXVpcmVkIHRvIGVuY29kZSBhbnkgb3V0Y29tZSBwb3NzaWJsZSBmcm9tIHRoZSBnaXZlbiBzdG9jaGFzdGljIGV2ZW50LgpPciBpdCBpcyB0aGUgZXhwZWN0ZWQgaW5mb3JtYXRpb24gY29udGFpbmVkIGJ5IGEgZ2l2ZW4gc3RvY2hhc3RpYyBldmVudC4KCk9uZSBsYXN0IHRoaW5nIHRvIG5vdGUgaXMgdGhhdCB0aGUgbnVtYmVyIG9mIGJpdHMgaGVyZSBpcyBhIG1hdGhlbWF0aWNhbCBhcnRpZmVjdCB0aGF0IGNvdWxkIG5vdCBhbHdheXMgYmUgcmVwcmVzZW50ZWQgYnkgb3VyIGRpc2NyZXRlIHJlYWwgd29ybGQuCkNlcnRhaW5seSB3ZSBkb24ndCBoYXZlIG5vbi1pbnRlZ2VyIGJpdCBpbiByZWFsIHdvcmxkIGNvbXB1dGluZyBidXQgdGhlIHZhbHVlIG9mIGVudHJvcHkgaXMgYSBwb3NpdGl2ZSByZWFsIG51bWJlciBub3QgbGltaXRlZCB0byBpbnRlZ2VyLgoKIyMjIyBEaXN0cmlidXRpb25hbCBEaWZmZXJlbmNlIHstfQoKTm93IG1vdmUgb24gdG8gY3Jvc3MgZW50cm9weS4KQ3Jvc3MgZW50cm9weSBiZXR3ZWVuIHR3byBkaXNjcmV0ZSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24gJHAkIGFuZCAkcSQgb3ZlciB0aGUgc2FtZSBzdXBwb3J0IGlzIGRlZmluZWQgYXM6CgokJApcYmVnaW57ZXF1YXRpb259IFxsYWJlbHtlcTpjcm9zc2VudHJvcHl9CkgocCwgcSkgPSAtIFxzdW1faSBwX2kgXGxvZ18ycV9pLgpcZW5ke2VxdWF0aW9ufQokJAoKSW50dWl0aXZlbHksCml0IG1lYW5zIHRoYXQgaWYgd2UgZW5jb2RlIGEgc3RvY2hhc3RpYyBldmVudCB3aXRoIGEgcHJvYmFiaWxpdHkgZGlzdHJpYnV0aW9uIE5PVCBsaW5rZWQgdG8gdGhlIGV2ZW50LApmb3IgZXhhbXBsZSBhIHByZWRpY3RlZCBkaXN0cmlidXRpb24gJHEkIGJhc2VkIG9uIGEgbW9kZWwsCndoaWxlIHRoZSB0cnVlIGRpc3RyaWJ1dGlvbiBpcyAkcCQsCndoYXQgd2lsbCBiZSB0aGUgZXhwZWN0ZWQgbnVtYmVyIG9mIGJpdHMgcmVxdWlyZWQgdG8gZW5jb2RlIGFsbCBwb3NzaWJpbGl0aWVzIGZyb20gdGhhdCBldmVudC4KClRvIHVuZGVyc3RhbmQgY3Jvc3MgZW50cm9weSBvbmUgdXNlZnVsIGNvbmNlcHQgaXMgdGhlIFtLdWxsYmFjay1MZWlibGVyIGRpdmVyZ2VuY2VdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0t1bGxiYWNrJUUyJTgwJTkzTGVpYmxlcl9kaXZlcmdlbmNlKSBmcm9tICRxJCB0byAkcCQ6CgokJApcYmVnaW57YWxpZ25lZH0KS0wocCBcdmVydFx2ZXJ0IHEpCiY9IEgocCwgcSkgLSBIKHApIFxcCiY9IFxzdW1faXBfaVxsb2dfMlxmcmFje3BfaX17cV9pfS4KXGVuZHthbGlnbmVkfQokJAoKTWF0aGVtYXRpY2FsbHkgaXQgaXMganVzdCB0aGUgc3VtIG9mIGRpZmZlcmVuY2Ugb2YgbG9nLXByb2JhYmlsaXR5IGZyb20gdHdvIGRpc3RyaWJ1dGlvbiAkcCQgYW5kICRxJCwKd2VpZ2h0ZWQgYnkgJHAkLgpEaXN0cmlidXRpb24gJHAkIGlzIHRoZSByZWZlcmVuY2UgZGlzdHJpYnV0aW9uIGFuZCAkcSQgdGhlIGFwcHJveGltYXRpb24gZGlzdHJpYnV0aW9uLgpJdCBpcyBhIG1lYXN1cmUgb2YgaG93IGNsb3NlbHkgdGhlIGRpc3RyaWJ1dGlvbiAkcSQgaXMgYXBwcm94aW1hdGluZyAkcCQuCklmICRxJCBpcyBwZXJmZWN0bHkgbWltaWNraW5nICRwJCwKdGhlIEtMIGRpdmVyZ2VuY2UgaXMgMCBhbmQgdGhlIGNyb3NzIGVudHJvcHkgYmV0d2VlbiAkcCQgYW5kICRxJCBpcyB0aGUgc2FtZSBhcyB0aGUgZW50cm9weSBvZiAkcCQ6CiRIKHAsIHEpID0gSChwKSQuCgpCYXNlZCBvbiB0aGUgYWJvdmUgZm9ybXVsYSB3ZSBjYW4gYWxzbyBpbnRlcnByZXQgS0wgZGl2ZXJnZW5jZSBhcyB0aGUgZXh0cmEgYml0cyByZXF1aXJlZCBkdWUgdG8gZW5jb2RpbmcgYSBzdG9jaGFzdGljIGV2ZW50IHdpdGggYSB3cm9uZyBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24uCgpGb3Igb3VyIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwsCmRpc3RyaWJ1dGlvbiAkcCQgaXMgdGhlIGVtcGlyaWNhbCBkaXN0cmlidXRpb24gKGxhYmVsIGRpc3RyaWJ1dGlvbikgYW5kIGRpc3RyaWJ1dGlvbiAkcSQgaXMgb3VyIG1vZGVsIHByZWRpY3RlZCBkaXN0cmlidXRpb24uCkRlbm90ZSAkcV9pID0gUCh5X2kgPSAxKSQgZm9yIHRoZSBwcmVkaWN0aW9uIG9mICRpJC10aCBleGFtcGxlLgpUaGUgY3Jvc3MtZW50cm9weS1sb3NzIG9mIG91ciBtb2RlbCBoZW5jZSBjYW4gYmUgd3JpdHRlbiBhczoKCiQkClxtYm94e0Nyb3NzLUVudHJvcHkgTG9zc30gPSAtIFxmcmFjezF9e059XHN1bV9pXk4gXGJpZ2dbIHlfaVxsb2dfMnFfaSArICgxIC0geV9pKVxsb2dfMigxIC0gcV9pKVxiaWdnXSwKJCQKCndoZXJlICR5X2kgXGluIFx7MCwxXH0kIGlzIHRoZSAkaSQtdGggYmluYXJ5IHRyYWluaW5nIGxhYmVsIGFuZCAkUCh5X2kgPSAxKSQgdGhlIG1vZGVsIHByZWRpY3Rpb24gZm9yIHRoZSAkaSQtdGggZXhhbXBsZSBvdXQgb2YgJE4kIHRvdGFsIHRyYWluaW5nIGV4YW1wbGVzLgpUaGlzIGlzIHRoZSBtZWFuIHZhbHVlIG9mIGNyb3NzIGVudHJvcHkgZm9yIGVhY2ggdHJhaW5pbmcgZXhhbXBsZS4KSXQgbWVhc3VyZXMgaG93IHdlbGwgb3VyIG1vZGVsIGlzIHByZWRpY3RpbmcgdGhlIGxhYmVsIGJhc2VkIG9uIHRoZSBkaXN0cmlidXRpb25hbCBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGxhYmVscyBhbmQgdGhlIG1vZGVsIG91dHB1dHMuCgpTaW5jZSAkcV9pID0gUCh5X2kgPSAxKSQgaXMgZXhwcmVzc2VkIGJ5IG91ciBtb2RlbCBlcXVhdGlvbiAkXGVxcmVme2VxOmxvZ2l0fSQsCm5vdyB3ZSBjYW4gYXBwbHkgZ3JhZGllbnQgZGVzY2VudCB0byB0aGUgbG9zcyBmdW5jdGlvbiB0byBmaW5kIG91dCB0aGUgb3B0aW11bSBtb2RlbCB3ZWlnaHRzIHRoYXQgbWluaW1pemUgdGhlIGNyb3NzLWVudHJvcHkgbG9zcy4KCiMjIyBNYXhpbXVtIExpa2VsaWhvb2QgRXN0aW1hdG9yCgpCZWZvcmUgd2UgaW1wbGVtZW50IG91ciBvcHRpbWl6ZXIgZm9yIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCwKd2UgZGVtb25zdHJhdGUgdGhhdCBtaW5pbWl6aW5nIGNyb3NzIGVudHJvcHkgaXMgaW5kZWVkIGVxdWl2YWxlbnQgdG8gbWF4aW1pemluZyBkYXRhIGxpa2VsaWhvb2QuCgpUaGUgZGF0YSBsaWtlbGlob29kIGlzIGp1c3QgdGhlIHByb2R1Y3Qgb2YgYWxsIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIGZvciBpbmRpdmlkdWFsIGV4YW1wbGUsCnByb3ZpZGVkIHRoYXQgdGhleSBhcmUgaW5kZXBlbmRlbnQuCgokJApcbWJveHtsaWtlbGlob29kfSA9IFxwcm9kX2leTiBxX2lee3lfaX0oMSAtIHFfaSleezEgLSB5X2l9LgokJAoKVGhlIG1heGltdW0gbGlrZWxpaG9vZCBlc3RpbWF0b3Igd2lsbCB0cnkgdG8gbWF4aW1pemUgdGhlIGxvZyBvZiB0aGUgbGlrZWxpaG9vZCwKd2hpY2ggaXM6CgokJApcYmVnaW57YWxpZ25lZH0KXG1ib3h7bG9nLWxpa30gCiY9IFxsb2dfMiBccHJvZF9pXk4gcV9pXnt5X2l9KDEgLSBxX2kpXnsxIC0geV9pfSBcXAomPSBcc3VtX2leTiBcbG9nXzIgcV9pXnt5X2l9KDEgLSBxX2kpXnsxIC0geV9pfSBcXAomPSBcc3VtX2leTiBcYmlnZ1sgeV9pXGxvZ18yIHFfaSArICgxIC0geV9pKVxsb2dfMigxIC0gcV9pKSBcYmlnZ10uClxlbmR7YWxpZ25lZH0KJCQKCkJ5IHRha2luZyB0aGUgYXZlcmFnZSBhcyB3ZWxsLAp0aGUgbmVnYXRpdmUgbG9nLWxpa2VsaWhvb2QgaXMgZXhhY3RseSB0aGUgY3Jvc3MgZW50cm9weS4KClRob3VnaCB3ZSBkaWRuJ3QgZGlzY3VzcyB0aGlzIGxpbmthZ2UgaW4gbGluZWFyIHJlZ3Jlc3Npb24sCmlmIHdlIGFzc3VtZSB0aGUgZXJyb3IgdGVybSBpbiB0aGUgbW9kZWwgaXMgaW5kZXBlbmRlbnRseSBkaXN0cmlidXRlZCBOb3JtYWxseSwKdGhlIE9MUyBzb2x1dGlvbiB0byB0aGUgbW9kZWwgd2VpZ2h0cyAoZXF1YXRpb24gJFxlcXJlZntlcTpvbHN9JCkgaXMgaW5kZWVkIGFsc28gdGhlIE1MRSBzb2x1dGlvbi4KCiMjIyBHcmFkaWVudCBEZXNjZW50IHdpdGggTG9nIExvc3MKCk5vdyBsZXQncyBhbHNvIGltcGxlbWVudCBhIGJhdGNoIGdyYWRpZW50IGRlc2NlbnQgb3B0aW1pemVyIGZvciBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwuCldlIHdpbGwgdXNlIHRoZSBzYW1lIHRveSBleGFtcGxlIGFuZCBjcmVhdGUgYWRkaXRpb25hbCByYW5kb20gYmluYXJ5IGxhYmVscyBmb3IgdGhpcyBleGVyY2lzZS4KCmBgYHtweXRob24gbG9naXRfZmFrZV9kYXRhfQp5MiA9IG5wLmFycmF5KFtucC5yYW5kb20uYmlub21pYWwoMSwgdikgZm9yIHYgaW4gc2lnbW9pZChYLmRvdChiZXRhKSldKQpgYGAKClRoZSBncmFkaWVudCBmdW5jdGlvbiBtdXN0IGJlIGRlcml2ZWQgd2l0aCByZXNwZWN0IHRvIG1vZGVsIHdlaWdodHMuClRvIHNpbXBsaWZ5IHRoZSBtYXRoIHdlIHJlcGxhY2UgbG9nIG9mIGJhc2UgMiB3aXRoIG5hdHVyYWwgbG9nICh3aXRoIG5vIGltcGFjdCBvbiB0aGUgb3B0aW1pemF0aW9uKSwKYW5kIHdlIHRha2UgYWR2YW50YWdlIG9mIHRoZSBmYWN0IHRoYXQgdGhlIGRlcml2YXRpdmUgb2YgdGhlIHNpZ21vaWQgZnVuY3Rpb24gaXM6XltIZXJlIHdlIHVzZSB0aGUgY29tbW9uIGRlcml2YXRpdmVzOiAkXGZyYWN7ZGYoeClebn17ZHh9ID0gbmYoeClee24tMX1mJyh4KSQgYW5kICRcZnJhY3tkZV57Zih4KX19e2R4fSA9IGYnKHgpZV57Zih4KX0uJF0KCiQkClxiZWdpbnthbGlnbmVkfQpcZnJhY3tkcyh0KX17ZHR9CiY9IFxmcmFje2RcZnJhY3sxfXsxICsgZV57LXR9fX17ZHR9IFxcCiY9IC0oMSArIGVeey10fSleey0yfSBcY2RvdCAoLSBlXnstdH0pIFxcCiY9IFxmcmFje2Veey10fX17KDEgKyBlXnstdH0pXjJ9IFxcCiY9IFxmcmFjezF9ezEgKyBlXnstdH19IFxjZG90IFxmcmFje2Veey10fX17MSArIGVeey10fX0gXFwKJj0gcyh0KSBcY2RvdCBbMSAtIHModCldLgpcZW5ke2FsaWduZWR9CiQkCgpUaGUgZ3JhZGllbnQgaW4gb3VyIHVuaXZhcmlhdGUgbW9kZWwgdy5yLnQuIHRoZSBiaWFzIHRlcm0gd2lsbCBiZTpeW0hlcmUgd2UgdXNlIHRoZSBjb21tb24gZGVyaXZhdGl2ZSAkXGZyYWN7ZFxsbiBmKHgpfXtkeH0gPSBcZnJhY3tmJyh4KX17Zih4KX0kIGFuZCB0aGUgW2NoYWluIHJ1bGVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0NoYWluX3J1bGUpIHRvIGhhbmRsZSB0aGUgdGVybSAkXGZyYWN7XHBhcnRpYWxcbG4gcV9pfXtccGFydGlhbFxiZXRhXzB9JCB3aGVyZSAkcV9pJCBpcyB0aGUgc2lnbW9pZCBjb21wdXRlZCBmb3IgdGhlICRpJC10aCBleGFtcGxlLl0KCiQkClxiZWdpbnthbGlnbmVkfQpcZnJhY3tccGFydGlhbFxtYm94e0xvZ0xvc3N9fXtccGFydGlhbFxiZXRhXzB9IAomPSAtIFxmcmFjezF9e059XHN1bV9pIFxiaWdnWyB5X2lcZnJhY3tccGFydGlhbFxsbiBxX2l9e1xwYXJ0aWFsXGJldGFfMH0gKyAKICAoMSAtIHlfaSlcZnJhY3tccGFydGlhbFxsbigxIC0gcV9pKX17XHBhcnRpYWxcYmV0YV8wfVxiaWdnXSBcXAomPSAtIFxmcmFjezF9e059XHN1bV9pIFxiaWdnWyB5X2lcZnJhY3sxfXtxX2l9XGZyYWN7XHBhcnRpYWwgcV9pfXtccGFydGlhbFxiZXRhXzB9ICsgCiAgKDEgLSB5X2kpXGZyYWN7MX17MSAtIHFfaX1cZnJhY3tccGFydGlhbCgxIC0gcV9pKX17XHBhcnRpYWxcYmV0YV8wfVxiaWdnXSBcXAomPSAtIFxmcmFjezF9e059XHN1bV9pIFxiaWdnWyB5X2lcZnJhY3sxfXtxX2l9XGZyYWN7XHBhcnRpYWwgKFxiZXRhXzAgKyBcYmV0YV8xeF9pKX17XHBhcnRpYWxcYmV0YV8wfVxmcmFje1xwYXJ0aWFsIHFfaSh0KX17XHBhcnRpYWwgdH0gLSAKICAoMSAtIHlfaSlcZnJhY3sxfXsxIC0gcV9pfVxmcmFje1xwYXJ0aWFsIChcYmV0YV8wICsgXGJldGFfMXhfaSl9e1xwYXJ0aWFsXGJldGFfMH1cZnJhY3tccGFydGlhbCBxX2kodCl9e1xwYXJ0aWFsIHR9XGJpZ2ddIFxcCiY9IC0gXGZyYWN7MX17Tn1cc3VtX2kgXGJpZ2dbIHlfaVxmcmFjezF9e3FfaX1xX2koMSAtIHFfaSkgLSAKICAoMSAtIHlfaSlcZnJhY3sxfXsxIC0gcV9pfXFfaSgxIC0gcV9pKVxiaWdnXSBcXAomPSAtIFxmcmFjezF9e059XHN1bV9pIFxiaWdnWyB5X2koMSAtIHFfaSkgLSAoMSAtIHlfaSlxX2kgXGJpZ2ddIFxcCiY9IC0gXGZyYWN7MX17Tn1cc3VtX2kgKHlfaSAtIHFfaSkuClxlbmR7YWxpZ25lZH0KJCQKClNpbWlsYXJ5IGZvciB0aGUgd2VpZ2h0OgoKJCQKXGZyYWN7XHBhcnRpYWxcbWJveHtMb2dMb3NzfX17XHBhcnRpYWxcYmV0YV8xfQo9IC0gXGZyYWN7MX17Tn1cc3VtX2kgKHlfaSAtIHFfaSl4X2kuCiQkCgpOb3cgdGhlIFB5dGhvbiBjb2RlIGZvciBiYXRjaCBncmFkaWVudCBkZXNjZW50IHdpdGggbG9nLWxvc3M6CgpgYGB7cHl0aG9uIGxvZ2l0X2dkfQpkZWYgbG9nbG9zcyhYLCB5LCBiKToKICBsb2dsb3NzX3BvcyA9IHkgKiBucC5sb2coc2lnbW9pZChYLmRvdChiKSkpCiAgbG9nbG9zc19uZWcgPSAoMSAtIHkpICogbnAubG9nKDEgLSBzaWdtb2lkKFguZG90KGIpKSkKICByZXR1cm4gLSAobG9nbG9zc19wb3MgKyBsb2dsb3NzX25lZykubWVhbigpCgpkZWYgbG9naXRfZ3JhZF9mdW5jKFgsIHksIGIpOgogIHJldHVybiAtICh5IC0gc2lnbW9pZChYLmRvdChiKSkpLmRvdChYKSAvIFguc2hhcGVbMF0KCmRlZiBjcm9zc19lbnRyb3B5X2dkX29wdGltaXplKFgsIHksIGxyPS4wMSwgbl9lcG9jaD0xMCwgYmF0Y2hfc2l6ZT02NCk6CiAgYiA9IG5wLnJhbmRvbS5ub3JtYWwoc2l6ZT1YLnNoYXBlWzFdKQogIGwgPSBbbG9nbG9zcyhYLCB5LCBiKV0KICBmb3IgZXBvY2ggaW4gcmFuZ2Uobl9lcG9jaCk6CiAgICAjIFNodWZmbGUgdGhlIGRhdGFzZXQgYmVmb3JlIGVhY2ggZXBvY2guCiAgICBzaWQgPSBucC5yYW5kb20ucGVybXV0YXRpb24oWC5zaGFwZVswXSkKICAgIFhzID0gWFtzaWQsOl0KICAgIHlzID0geVtzaWRdCiAgICBpID0gMAogICAgbl9zdGVwID0gaW50KG5wLmNlaWwoWC5zaGFwZVswXSAvIGJhdGNoX3NpemUpKQogICAgZm9yIHN0ZXAgaW4gcmFuZ2Uobl9zdGVwKToKICAgICAgWGIgPSBYc1tpOmkrYmF0Y2hfc2l6ZSw6XQogICAgICB5YiA9IHlzW2k6aStiYXRjaF9zaXplXQogICAgICBiIC09IGxyKmxvZ2l0X2dyYWRfZnVuYyhYYiwgeWIsIGIpCiAgICAgIGwuYXBwZW5kKGxvZ2xvc3MoWGIsIHliLCBiKSkKICAgICAgaSArPSBiYXRjaF9zaXplCiAgcmV0dXJuIGIsIGwKCmxvZ19iZXRhLCBsb2dfbG9zcyA9IGNyb3NzX2VudHJvcHlfZ2Rfb3B0aW1pemUoWCwgeTIsIGxyPS4wMSwgbl9lcG9jaD0xMDApCnByaW50KGxvZ19iZXRhKQpgYGAKCmBgYHtweXRob24gbG9naXRfbG9zc19wbG90fQpvZmlsZSA9ICJwbG90cy90b3lfbG9nX2xvc3MuaHRtbCIKcGRhdGEgPSBbZ28uU2NhdHRlcih4PW5wLmFyYW5nZShsZW4obG9nX2xvc3MpKSwgeT1sb2dfbG9zcyldCnAgPSBwbG90X29mZmxpbmUoCiAgb2ZpbGU9b2ZpbGUsCiAgZGF0YT1wZGF0YSwKICBsYXlvdXQ9Z28uTGF5b3V0KAogICAgdGl0bGU9IlRyYWNlIG9mIFRyYWluaW5nIExvZy1Mb3NzIiwKICAgIHhheGlzPWRpY3QodGl0bGU9IlN0ZXAiKSwKICAgIHlheGlzPWRpY3QodGl0bGU9Ikxvc3MiKSkpCmBgYAoKYGBge3IsIGVjaG89RkFMU0V9Cmh0bWx0b29sczo6aW5jbHVkZUhUTUwocHkkb2ZpbGUpCmBgYAoKQ29tcGFyaW5nIHRvIGxpbmVhciByZWdyZXNzaW9uLAphIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgaXMgaGFyZGVyIHRvIGNvbnZlcmdlLgpTaW5jZSBvdXIgbmFpdmUgaW1wbGVtZW50YXRpb24gZG9lcyBub3QgZG8gY29udmVyZ2VuY2UgZGlhZ25vc3RpY3MsCmxldCdzIHVzZSBSJ3MgYnVpbHQtaW4gYGdsbWAgZnVuY3Rpb24gd2hpY2ggdXNlIE5ld3RvbidzIG1ldGhvZCAoYSAybmQtb3JkZXIgb3B0aW1pemVyIHV0aWxpemluZyBub3Qgb25seSAxc3Qtb3JkZXIgYnV0IGFsc28gMm5kLW9yZGVyIGRlcml2YXRpdmVzKSB0byBjaGVjayB0aGUgZXN0aW1hdGlvbiByZXN1bHQgb2Ygb3VyIHRveSBleGFtcGxlOgoKYGBge3IgbG9naXRfY2hlY2tfc29sdXRpb259CiMgVGhpcyBpcyBSIGNvZGUuCmNvZWYoZ2xtKHkgfiB4LCBkYXRhPWRhdGEuZnJhbWUoeT1weSR5MiwgeD1weSRYWywyXSksIGZhbWlseT1iaW5vbWlhbCkpCmBgYAoKTm93IGluY3JlYXNlIGJvdGggdGhlIGxlYXJuaW5nIHJhdGUgYW5kIHRyYWluaW5nIGVwb2NocyBvZiBvdXIgbmFpdmUgZ3JhZGllbnQgZGVzY2VudCBvcHRpbWl6ZXI6CgpgYGB7cHl0aG9uIGxvZ2l0X2dkX21vcmV9CnByaW50KGNyb3NzX2VudHJvcHlfZ2Rfb3B0aW1pemUoWCwgeTIsIGxyPTEsIG5fZXBvY2g9MjAwKVswXSkKYGBgCgpTZWVtcyBiZXR0ZXIuCgpPciB3ZSBjYW4gdXNlIHRoZSBQeXRob24gcGFja2FnZSBgc3RhdHNtb2RlbHNgIChAc2VhYm9sZDIwMTBzdGF0c21vZGVscyksCndoaWNoIGFsc28gdXNlcyBhIDJuZC1vcmRlciBvcHRpbWl6ZXIgYnkgZGVmYXVsdCwKdG8gY2hlY2sgb3VyIHJlc3VsdDogCgpgYGB7cHl0aG9uIGxvZ2l0X3N0YXRzbW9kZWxzfQppbXBvcnQgc3RhdHNtb2RlbHMuYXBpIGFzIHNtCnByaW50KHNtLkxvZ2l0KHkyLCBYKS5maXQoKS5wYXJhbXMpCmBgYAoKT3IgdGhlIGBza2xlYXJuYCByZXN1bHQ6CgpgYGB7cHl0aG9uIGxvZ2l0X3NrbGVhcm59CmZyb20gc2tsZWFybi5saW5lYXJfbW9kZWwgaW1wb3J0IExvZ2lzdGljUmVncmVzc2lvbgojIFdlIG5lZWQgdG8gc2V0IHBhcmFtIEMgdG8gYW4gYXJiaXRyYXJpbHkgbGFyZ2UgbnVtYmVyIHRvIHN1cHByZXNzIHJlZ3VsYXJpemF0aW9uCiMgc2luY2Ugb3VyIG5haXZlIGFwcHJvYWNoIGRvZXNuJ3QgaW1wbGVtZW50IGFueSByZWd1bGFyaXphdGlvbi4KIyBXZSBzZXQgZml0X2ludGVyY2VwdD1GYWxzZSBzaW5jZSBvdXIgZGVzaWduIG1hdHJpeCBhbHJlYWR5IGNvbnRhaW5zIGludGVyY2VwdC4KcHJpbnQoTG9naXN0aWNSZWdyZXNzaW9uKEM9MWUxNiwgZml0X2ludGVyY2VwdD1GYWxzZSwgc29sdmVyPSJsaWJsaW5lYXIiKS5maXQoWCwgeTIpLmNvZWZfKQpgYGAKCiMgQXV0b21hdGljIERpZmZlcmVudGlhdGlvbgoKSW4gdGhlIHByZXZpb3VzIHNlY3Rpb24gd2UgaW1wbGVtZW50IGEgc2ltcGxlIGdyYWRpZW50IGRlc2NlbnQgb3B0aW1pemVyIGJ5IG1hbnVhbGx5IGRlcml2ZSB0aGUgZnVuY3Rpb25hbCBmb3JtIG9mIGdyYWRpZW50IG9uIG91ciBvd24uClRoaXMgY291bGQgYmUgdHJvdWJsZXNvbWUgaWYgb3VyIG1vZGVsIGJlY29tZXMgbW9yZSBhbmQgbW9yZSBjb21wbGljYXRlZCwKYXMgaW4gdGhlIGNhc2Ugb2YgYSBkZWVwIG5ldXJhbCBuZXQuCgpBdXRvbWF0Y2kgZGlmZmVyZW50aWF0aW9uIGlzIGEgcHJvZ3JhbW1pbmcgdGVjaG5pcXVlIHRvIGNhbGN1bGF0ZSB0aGUgZ3JhZGllbnQgb2YgYW55IGdpdmVuIGZ1bmN0aW9uLgpPbmUgb2YgdGhlIG1vc3QgcG9wdWxhciBsaWJyYXJ5IGZvciB0aGlzIHB1cnBvc2UgaXMgW1RlbnNvckZsb3ddKGh0dHBzOi8vZ2l0aHViLmNvbS90ZW5zb3JmbG93L3RlbnNvcmZsb3cpIChAdGVuc29yZmxvdzIwMTUtd2hpdGVwYXBlcikuCgpMZXQncyB1c2UgYHRlbnNvcmZsb3dgIHRvIGltcGxlbWVudCBvdXIgc2ltcGxlIGdyYWRpZW50IGRlc2NlbnQgb3B0aW1pemVyIGFnYWluLgpCdXQgdGhpcyB0aW1lIHdlIHdpbGwgTk9UIGV4cGxpY2l0bHkgZGVyaXZlIHRoZSBncmFkaWVudCBmdW5jdGlvbi4KSW5zdGVhZCwKd2Ugd2lsbCBvbmx5IHNwZWNpZnkgdGhlIHRhcmdldCBmdW5jdGlvbiB3aGljaCBpcyBqdXN0IHRoZSBsb3NzIGZ1bmN0aW9uIG9mIG91ciBtb2RlbC4KCmBgYHtweXRob24gaW1wb3J0X3RlbnNvcmZsb3d9CmltcG9ydCBsb2dnaW5nCmxvZ2dpbmcuZ2V0TG9nZ2VyKCJ0ZW5zb3JmbG93Iikuc2V0TGV2ZWwobG9nZ2luZy5FUlJPUikKCmltcG9ydCB0ZW5zb3JmbG93IGFzIHRmCnByaW50KHRmLl9fdmVyc2lvbl9fKQoKdGYuZW5hYmxlX2VhZ2VyX2V4ZWN1dGlvbigpCmBgYAoKYGBge3B5dGhvbiB0Zl9hZH0KIyBVc2UgdGVuc29yIHRvIHJlcHJlc2VudCBvdXIgZGF0YS4KIyBOb3RlIHRoYXQgd2UgbmVlZCB0byBiZSB2ZXJ5IHNwZWNpZmljIGFib3V0IGR0eXBlL3NoYXBlIG9mIG91ciB0ZW5zb3JzLgpYX3RmID0gdGYuY29udmVydF90b190ZW5zb3IoWCwgZHR5cGU9dGYuZmxvYXQzMikKYmV0YV90ZiA9IHRmLnJlc2hhcGUodGYuY29udmVydF90b190ZW5zb3IoYmV0YSwgZHR5cGU9dGYuZmxvYXQzMiksICgyLDEpKQplX3RmID0gdGYucmVzaGFwZSh0Zi5jb252ZXJ0X3RvX3RlbnNvcihlLCBkdHlwZT10Zi5mbG9hdDMyKSwgKE4sIDEpKQp5X3RmID0gdGYubWF0bXVsKFhfdGYsIGJldGFfdGYpICsgZV90Zgp5Ml90ZiA9IHRmLnJlc2hhcGUodGYuY29udmVydF90b190ZW5zb3IoeTIsIGR0eXBlPXRmLmZsb2F0MzIpLCAoTiwgMSkpCgpkZWYgbG9zc19tc2VfdGYoWCwgeSwgYmV0YSk6CiAgeV9oYXQgPSB0Zi5tYXRtdWwoWCwgYmV0YSkKICByZXR1cm4gdGYucmVkdWNlX21lYW4oaW5wdXRfdGVuc29yID0gKHlfaGF0IC0geSkqKjIpCgpkZWYgbG9nbG9zc190Zl9iYWQoWCwgeSwgYmV0YSk6CiAgIyBUaGlzIGNhbiBzdWZmZXIgZnJvbSBudW1lcmljYWwgc3RhYmlsaXR5IGlzc3VlLgogIGxvZ2xvc3NfcG9zID0geSAqIHRmLmxvZyh0Zi5zaWdtb2lkKHRmLm1hdG11bChYLCBiZXRhKSkpCiAgbG9nbG9zc19uZWcgPSAoMSAtIHkpICogdGYubG9nKDEgLSB0Zi5zaWdtb2lkKHRmLm1hdG11bChYLCBiZXRhKSkpCiAgcmV0dXJuIC0gdGYucmVkdWNlX21lYW4obG9nbG9zc19wb3MgKyBsb2dsb3NzX25lZykKCmRlZiBsb2dsb3NzX3RmKFgsIHksIGJldGEpOgogICMgdGYuc2lnbW9pZCBpcyBub3QgbnVtZXJpY2FsbHkgc3RhYmxlLgogICMgQnV0IHRoZXJlIGlzIGEgY29udmVuaWVudCBmdW5jdGlvbiB0byBkbyB0aGUgY3Jvc3MgZW50cm9weSBjYWxjdWxhdGlvbiBzdGFibHkuCiAgcyA9IHRmLm5uLnNpZ21vaWRfY3Jvc3NfZW50cm9weV93aXRoX2xvZ2l0cyhsYWJlbHM9eSwgbG9naXRzPXRmLm1hdG11bChYLCBiZXRhKSkKICByZXR1cm4gdGYucmVkdWNlX21lYW4ocykKCmRlZiBnZF9vcHRpbWl6ZV90ZihYLCB5LCBsb3NzX2Z1bmMsIGxyPS4wMSwgbl9zdGVwPTEwMCk6CiAgZ3JhZF9mdW5jX3RmID0gdGYuY29udHJpYi5lYWdlci5ncmFkaWVudHNfZnVuY3Rpb24obG9zc19mdW5jLCBwYXJhbXM9WzJdKQogIGJldGEgPSB0Zi5yYW5kb21fbm9ybWFsKCgyLCAxKSkKICBmb3Igc3RlcCBpbiByYW5nZShuX3N0ZXApOgogICAgZ3JhZCA9IGdyYWRfZnVuY190ZihYLCB5LCBiZXRhKVswXQogICAgYmV0YSAtPSBsciAqIGdyYWQKICByZXR1cm4gYmV0YS5udW1weSgpCgoKcHJpbnQoZ2Rfb3B0aW1pemVfdGYoWF90ZiwgeV90ZiwgbG9zc19mdW5jPWxvc3NfbXNlX3RmLCBuX3N0ZXA9MzAwMCkpICAjIE1TRSBsb3NzLgoKcHJpbnQoZ2Rfb3B0aW1pemVfdGYoWF90ZiwgeTJfdGYsIGxvc3NfZnVuYz1sb2dsb3NzX3RmLCBscj0uNSwgbl9zdGVwPTUwMDApKSAgIyBDcm9zcyBlbnRyb3B5IGxvc3MuCmBgYAoKSW4gdGhlIGFib3ZlIGNvZGluZyBleGFtcGxlIG9uZSBzaG91bGQgcmVhbGl6ZSB0aGF0IHdlIG5vIGxvbmdlciBuZWVkIHRvIGhhcmRjb2RlIHRoZSBmdW5jdGlvbmFsIGZvcm0gb2YgdGhlIGdyYWRpZW50cy4KSW5zdGVhZCB3ZSBqdXN0IHBsdWctaW4gdGhlIGxvc3MgZnVuY3Rpb24gYW5kIGxldCBgdGVuc29yZmxvd2AgdG8gZG8gdGhlIGdyYWRpZW50IGNhbGN1bGF0aW9uIGZvciB1cy4KCk9mIGNvdXJzZSBpbiBhY3R1YWwgZGV2ZWxvcG1lbnQgd2Ugd2lsbCB1c2UgaGlnaGVyLWxldmVsIEFQSXMgdG8gaW1wbGVtZW50IG91ciBtb2RlbCwKd2hlcmUgdGhlIGVudGlyZSBvcHRpbWl6YXRpb24gcHJvY2VzcyBpcyBhYnN0cmFjdGVkIGF3YXkgZnJvbSB0aGUgYXBwbGljYXRpb24gY29kZS4KCiMgTmV1cmFsIE5ldHdvcmtzCgpCb3RoIGxpbmVhciByZWdyZXNzaW9uIGFuZCBsb2dpc3RpYyByZWdyZXNzaW9uIGNhbiBiZSBjb25zaWRlcmVkIGFzIHNpbXBsZSBhZGRpdGl2ZSBtb2RlbCBvZiB0aGUgZm9ybToKCiQkClxoYXR7eX0gPSBcUGhpXGJpZ2coXHN1bV97aT0xfV5Qd19peF9pXGJpZ2cpLAokJAoKd2hlcmUgJFAkIGlzIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgdXNlZCwKJHhfaSQgaXMgdGhlICRpJC10aCBmZWF0dXJlLAphbmQgJFxQaGkoXGNkb3QpJCBpcyBhIGZ1bmN0aW9uIGFwcGxpZWQgdG8gdGhlIG91dHB1dC4KSW4gbGluZWFyIHJlZ3Jlc3Npb24gJFxQaGkoXGNkb3QpJCBpcyBzaW1wbHkgYW4gaWRlbnRpdHkgZnVuY3Rpb24uCkluIGxvZ2lzdGljIHJlZ3Jlc3Npb24gJFxQaGkoXGNkb3QpJCBpcyB0aGUgc3RhbmRhcmQgc2lnbW9pZCBmdW5jdGlvbi4KCk5vdyBjb25zaWRlciB0aGVyZSBpcyBhIHdheSB0byBlbnNlbWJsZSBtdWx0aXBsZSBzdWNoIGFkZGl0aXZlIG1vZGVscyB0b2dldGhlciB0byBnZW5lcmF0ZSBhIHBvdGVudGlhbGx5IGJldHRlciBhbmQgbW9yZSBzb3BoaXN0aWNhdGVkIG1vZGVsLgpXZSB1c2UgdGhlIGZvbGxvd2luZyBkaWFncmFtIGZvciBpbGx1c3RyYXRpb24uCgo8ZGl2IGNsYXNzPSJmb2xkIHMiPgpgYGB7ciBubl9kaWFncmFtfQojIERyYXcgYSBzaW1wbGUgbmV1cmFsIG5ldHMuCkRpYWdyYW1tZVI6OmdyVml6KCIKZGlncmFwaCBzdWJzY3JpcHQgewoKICBncmFwaCBbbGF5b3V0ID0gZG90IHJhbmtkaXIgPSBMUiBvcmRlcmluZyA9IGluIHJhbmtzZXA9MiBzcGxpbmVzPXRydWVdCgogIG5vZGUgW3NoYXBlID0gY2lyY2xlXQoKICBzdWJncmFwaCBjbHVzdGVyX2lucHV0X2xheWVyIHsKICAgIGxhYmVsID0gJ0lucHV0IExheWVyJwogICAgeDEgW2xhYmVsID0gJ3hAX3sxfSddCiAgICB4MiBbbGFiZWwgPSAneEBfezJ9J10KICAgIHgzIFtsYWJlbCA9ICd4QF97M30nXQogIH0KCiAgc3ViZ3JhcGggY2x1c3Rlcl9oaWRkZW5fbGF5ZXIgewogICAgbGFiZWwgPSAnSGlkZGVuIExheWVyJwogICAgbjExIFtsYWJlbCA9ICd5QF97MTF9JyBjb2xvciA9IGJsdWUgZm9udGNvbG9yID0gYmx1ZV0KICAgIG4xMiBbbGFiZWwgPSAneUBfezEyfScgY29sb3IgPSBkYXJrZ3JlZW4gZm9udGNvbG9yID0gZGFya2dyZWVuXQogIH0KCiAgc3ViZ3JhcGggY2x1c3Rlcl9vdXRwdXRfbGF5ZXIgewogICAgbGFiZWwgPSAnT3V0cHV0IExheWVyJwogICAgbjIxIFtsYWJlbCA9ICd5QF97Mn0nXQogIH0KCiAgZWRnZSBbYXJyb3dzaXplID0gLjI1XQoKICB4MSAtPiBuMTEgW2xhYmVsID0gJ3dAX3sxMTF9JyBjb2xvciA9IGJsdWUgZm9udGNvbG9yID0gYmx1ZV0KICB4MSAtPiBuMTIgW2xhYmVsID0gJ3dAX3sxMTJ9JyBjb2xvciA9IGRhcmtncmVlbiBmb250Y29sb3IgPSBkYXJrZ3JlZW5dCgogIHgyIC0+IG4xMSBbbGFiZWwgPSAnd0BfezIxMX0nIGNvbG9yID0gYmx1ZSBmb250Y29sb3IgPSBibHVlXQogIHgyIC0+IG4xMiBbbGFiZWwgPSAnd0BfezIxMn0nIGNvbG9yID0gZGFya2dyZWVuIGZvbnRjb2xvciA9IGRhcmtncmVlbl0KCiAgeDMgLT4gbjExIFtsYWJlbCA9ICd3QF97MzExfScgY29sb3IgPSBibHVlIGZvbnRjb2xvciA9IGJsdWVdCiAgeDMgLT4gbjEyIFtsYWJlbCA9ICd3QF97MzEyfScgY29sb3IgPSBkYXJrZ3JlZW4gZm9udGNvbG9yID0gZGFya2dyZWVuXQoKICBuMTEgLT4gbjIxIFtsYWJlbCA9ICd3QF97MTEyMX0nXQogIG4xMiAtPiBuMjEgW2xhYmVsID0gJ3dAX3sxMjIxfSddCgp9IikKYGBgCjwvZGl2PgoKSW4gdGhlIGFib3ZlIGRpYWdyYW0sCiRZX3sxMX0kIGlzIGEgc2luZ2xlIGFkZGl0aXZlIG1vZGVsCgokJApZX3sxMX0gPSBcUGhpKFdfezExMX1YXzEgKyBXX3syMTF9WF8yICsgV197MzExfVhfMykuCiQkCgpTaW1pbGFybHkgJFlfezEyfSQgaXMgYW5vdGhlciBzdWNoIG1vZGVsICh3aXRoIHRoZSBzYW1lIGlucHV0IGZlYXR1cmUgc2V0IGJ1dCBkaWZmZXJlbnQgbW9kZWwgd2VpZ2h0cykKCiQkCllfezEyfSA9IFxQaGkoV197MTEyfVhfMSArIFdfezIxMn1YXzIgKyBXX3szMTJ9WF8zKS4KJCQKCk1vZGVsICRZXzIkIGlzIHlldCBhbm90aGVyIGFkZGl0aXZlIG1vZGVsIGJ1dCB0YWtlcyB0aGUgb3V0cHV0IG9mIHRoZSBhYm92ZSB0d28gbW9kZWxzOgoKJCQKWV8yID0gXFBoaShXX3sxMTIxfVlfezExfSArIFdfezEyMjF9WV97MTJ9KS4KJCQKClRoZSBhYm92ZSBzZXR1cCBpcyBhIHNpbXBsZSBhcmNoaXRlY3R1cmUgb2YgYSBuZXVyYWwgbmV0d29yayBtb2RlbCwKd2l0aCBvbmx5IG9uZSBoaWRkZW4gbGF5ZXIgb2YgdHdvICpuZXVyb25zKi4KKFdlIGFsc28gaWdub3JlIHRoZSBjb25zdGFudC9iaWFzIHRlcm0gaW4gZWFjaCBsYXllciBmb3Igc2ltcGxpY2l0eS4pCkEgbmV1cm9uIGlzIHNpbXBseSBhbiBhZGRpdGl2ZSBtb2RlbCB3aXRoIGEgc28tY2FsbGVkIGFjdGl2YXRpb24gZnVuY3Rpb24gJFxQaGkoXGNkb3QpJCB0byB0cmFuc2Zvcm0gdGhlIG91dHB1dCBmcm9tIGFueSByZWFsIG51bWJlciBpbnRvIGEgc2NhbGVkIHNpZ25hbC4KCk9uZSBub3cgY2FuIGVhc2lseSByZWFsaXplIHRoYXQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGNvdWxkIGJlIHZpZXdlZCBhcyBhIGRlZ2VuZXJhdGVkIG5ldXJhbCBuZXR3b3JrIG1vZGVsIHdpdGggc2luZ2xlIG5ldXJvbiBhbmQgdXNpbmcgc2lnbW9pZCBhcyB0aGUgYWN0aXZhdGlvbiBmdW5jdGlvbi4KQW5kIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgaXMgYSBkZWdlbmVyYXRlZCBuZXVyYWwgbmV0d29yayBtb2RlbCB3aXRoIHNpbmdsZSBuZXVyb24gYW5kIHdpdGhvdXQgYW4gYWN0aXZhdGlvbiBmdW5jdGlvbi4KCiMjIFNoYWxsb3cgdi5zLiBEZWVwCgpUaGUgYWJvdmUgc2ltcGxlIG5ldXJhbCBuZXR3b3JrIGlzIGluZGVlZCBhIHNoYWxsb3cgb25lLAp3aXRoIG9ubHkgb25lIGhpZGRlbiBsYXllci4KQ29udmVudGlvbmFsbHkgcGVvcGxlIHJlZmVyIHRvIGEgZGVlcCBuZXVyYWwgbmV0d29yayBhcyBhIG5ldHdvcmsgd2l0aCBhdCBsZWFzdCAyIGhpZGRlbiBsYXllcnMuClRoZSBidXp6IHdvcmQgKmRlZXAgbGVhcm5pbmcqIGlzIHVzZWQgdG8gZGVzY3JpYmUgY29tcGxpY2F0ZWQgbmV1cmFsIG5ldHdvcmsgYXJjaGl0ZWN0dXJlIHRoYXQgY2FuIHNvbHZlIHByb2JsZW1zIHRoYXQgdHJhZGl0aW9uYWwgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIGhhdmUgZGlmZmljdWx0eSBkZWFsaW5nIHdpdGgsCmVzcGVjaWFsbHkgaW4gdGhlIGZpZWxkIG9mIGltYWdlIChhbmQgdmlkZW8pIGFuZCBuYXR1cmFsIGxhbmd1YWdlIChhbmQgdm9pY2UpIHByb2Nlc3NpbmcgYW5kIHVuZGVyc3RhbmRpbmcuCgpEZWVwIG1vZGVsIGlzIGluZGVlZCBtdWNoIG1vcmUgdGhhbiBqdXN0IGFkZGluZyBtb3JlIGhpZGRlbiBsYXllcnMuClRoZSB3YXkgbmV1cm9uIGlzIGNvbm5lY3RlZCBhbmQgaG93IHRoZXkgYXJlIHN0cnVjdHVyZWQgY2FuIGJlIGhpZ2hseSBjcmVhdGl2ZSAoZm9yIG91dHN0YW5kaW5nIGV4YW1wbGVzIGJlaW5nIFtjb252b2x1dGlvbmFsXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Db252b2x1dGlvbmFsX25ldXJhbF9uZXR3b3JrKSBhbmQgW3JlY3VycmVudF0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUmVjdXJyZW50X25ldXJhbF9uZXR3b3JrKSBuZXVyYWwgbmV0d29yaykgYW5kIHdheSBiZXlvbmQgdGhlIHNjb3BlIG9mIHRoaXMgbm90ZWJvb2suClRob3VnaCBhIGRlZXAgbW9kZWwgaXMgZGVmaW5pdGVseSBtb3JlIGNvbXBsaWNhdGVkIHRoYW4gYSBzaGFsbG93IG9uZSwKdGhlIGZvdW5kYXRpb24gaXMgYWxsIHRoZSBzYW1lLgoKIyMgQWN0aXZhdGlvbiBGdW5jdGlvbgoKV2h5IGRvIHdlIG5lZWQgdGhlIGFjdGl2YXRpb24gZnVuY3Rpb24/CkluIHRoZSBhYm92ZSBuZXVyYWwgbmV0d29yayBtb2RlbCBpbiB0aGUgYWJzZW5jZSBvZiBhY3RpdmF0aW9uIGZ1bmN0aW9uIHRoZSBmaW5hbCBvdXRwdXQgbW9kZWwgJFlfMiQgd2lsbCBkZWdlbmVyYXRlIGludG8gYSBzaW1wbGUgbGluZWFyIG1vZGVsLgpXZSBjYW4gdXNlIHNpbXBsZXIgbm90YXRpb25zIHRvIGRlbW9uc3RyYXRlIHRoaXM6CgokJApcYmVnaW57YWxpZ25lZH0KeV8xICY9IGF4ICsgYiwgXFwKeV8yICY9IGN4ICsgZCwgXFwKeV8zICY9IGUgKyBmeV8xICsgZ3lfMiBcXAomPSBlICsgZihheCArIGIpICsgZyhjeCArIGQpIFxcCiY9IFx1bmRlcmJyYWNleyhlICsgZmIgKyBnZCl9X1x0ZXh0e0JpYXN9ICsgXHVuZGVyYnJhY2V7KGZhICsgZ2MpfV9cdGV4dHtXZWlnaHR9eC4KXGVuZHthbGlnbmVkfQokJAoKV2l0aG91dCBhY3RpdmF0aW9uIGZ1bmN0aW9uLApubyBtYXR0ZXIgaG93IG1hbnkgbmV1cm9ucyBvciBsYXllcnMgd2UgZGVzaWduIGZvciBvdXIgbW9kZWwsCml0IGV2ZW50dWFsbHkgcmVkdWNlcyB0byBhIHNpbXBsZSBsaW5lYXIgbW9kZWwuCldpdGggdGhlIGFjdGl2YXRpb24gZnVuY3Rpb24gYXBwbGllZCB0byBlYWNoIG5ldXJvbiwKdGhlIG1vZGVsIGJlY29tZXMgKm5vbi1saW5lYXIqIGFuZCBoZW5jZSBjYW4gaGFuZGxlIG11Y2ggbW9yZSBjb21wbGljYXRlZCBwYXR0ZXJucyBoaWRkZW4gYmVoaW5kIHRoZSBkYXRhLgoKU29tZSBwb3B1bGFyIGFjdGl2YXRpb24gZnVuY3Rpb25zOgoKKyBbU2lnbW9pZCAoTG9naXN0aWMpXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Mb2dpc3RpY19mdW5jdGlvbikKKyBbVGFuaCAoSHlwZXJib2xpYyBUYW5nZW50KV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvSHlwZXJib2xpY19mdW5jdGlvbiNIeXBlcmJvbGljX3RhbmdlbnQpCisgW1JlTFUgKFJlY3RpZmllZCBMaW5lYXIgVW5pdCldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1JlY3RpZmllcl8obmV1cmFsX25ldHdvcmtzKSkKKyBTd2lzaDogJGYoeCkgPSB4IFxjZG90IFxtYm94e3NpZ21vaWR9KHgpJCwgcHJvcG9zZWQgYnkgW0dvb2dsZSBCcmFpbl0oaHR0cHM6Ly9haS5nb29nbGUvcmVzZWFyY2gvdGVhbXMvYnJhaW4vKQoKIyMgQmFja3Byb3BhZ2F0aW9uCgpUbyBzb2x2ZSBmb3IgbW9kZWwgd2VpZ2h0cyBpbiBhIG5ldXJhbCBuZXR3b3JrLAp3ZSB1c2UgYSB0ZWNobmlxdWUgY2FsbGVkIGJhY2twcm9wYWdhdGlvbiB3aGljaCBpcyBlc3NlbnRpYWxseSBhbiBpdGVyYXRpdmUgcHJvY2VzcyBvZiBncmFkaWVudCBkZXNjZW50LgoKVG8gc2ltcGxpZnkgbm90YXRpb24gd2UgYXNzdW1lIGVhY2ggbmV1cm9uIGlzIHNpbXBseSBhIHVuaXZhcmlhdGUgbW9kZWwuCkNvbnNpZGVyIHRoZSBmb2xsb3dpbmcgbWluaW11bSBhcmNoaXRlY3R1cmU6Cgo8ZGl2IGNsYXNzPSJmb2xkIHMiPgpgYGB7ciBzaW1wbGlmaWVkX25uX2RpYWdyYW19CkRpYWdyYW1tZVI6OmdyVml6KCIKZGlncmFwaCBzdWJzY3JpcHQgewoKICBncmFwaCBbbGF5b3V0ID0gZG90IHJhbmtkaXIgPSBMUiBvcmRlcmluZyA9IGluIHJhbmtzZXA9Ml0KCiAgbm9kZSBbc2hhcGUgPSBjaXJjbGVdCgogIHgwIFtsYWJlbCA9ICcxJ10KICB4MSBbbGFiZWwgPSAneCddCiAgejAgW2xhYmVsID0gJzEnXQogIHoxIFtsYWJlbCA9ICd6J10KICB5ICBbbGFiZWwgPSAneSddCgogIGVkZ2UgW2Fycm93c2l6ZSA9IC4yNV0KCiAgeDAgLT4gejEgW2xhYmVsID0gJ2JAX3swfSddCiAgeDEgLT4gejEgW2xhYmVsID0gJ3dAX3swfSddCiAgejAgLT4geSAgW2xhYmVsID0gJ2JAX3sxfSddCiAgejEgLT4geSAgW2xhYmVsID0gJ3dAX3sxfSddCgp9IikKYGBgCjwvZGl2PgoKTWF0aGVtYXRpY2FsbHk6CgokJApcYmVnaW57YWxpZ25lZH0KXGhhdHt5fSAKJj0gXFBoaShiXzEgKyB3XzF6KSBcXAomPSBcUGhpKGJfMSArIHdfMVxQaGkoYl8wICsgd18weCkpLApcZW5ke2FsaWduZWR9CiQkCgp3aGVyZQoKJCQKeiA9IFxQaGkoYl8wICsgd18weCksCiQkCgphbmQKCiQkClxQaGkodCkgPSBcZnJhY3sxfXsxICsgZV57LXR9fS4KJCQKCiMjIyBNU0UgTG9zcwoKVGhvdWdoIGl0IG1heSBub3QgYmUgdmVyeSBtZWFuaW5nZnVsIHRvIHVzZSBNU0UgbG9zcyB3aGVuIHRoZSBvdXRwdXQgbGF5ZXIgaXMgYXBwbGllZCB3aXRoIGFuIGFjdGl2YXRpb24gZnVuY3Rpb24sCndlIGNhbiBzdGlsbCBkbyBpdCBmb3IgZWR1Y2F0aW9uYWwgcHVycG9zZS4KCiQkClxtYm94e01TRS1Mb3NzfSA9IFxmcmFjezF9e059XHN1bV9pXk4gICh5X2kgLSBcaGF0e3l9X2kpXjIuCiQkCgpGaXJzdGx5IHdlIHRha2UgdGhlIGRlcml2YXRpdmUgdy5yLnQuIHRoZSB3ZWlnaHQgaW4gdGhlIGxhc3QgbGF5ZXI6CgokJApcYmVnaW57YWxpZ25lZH0KXGZyYWN7XHBhcnRpYWwgXG1ib3h7TVNFLUxvc3N9fXtccGFydGlhbCB3XzF9CiY9IC0gXGZyYWN7MX17Tn1cc3VtX2kgKHlfaSAtIFxoYXR7eX1faSlcZnJhY3tccGFydGlhbCBcaGF0e3l9X2l9e1xwYXJ0aWFsIHdfMX0gXFwKJj0gLSBcZnJhY3sxfXtOfVxzdW1faSAoeV9pIC0gXGhhdHt5fV9pKQpcdW5kZXJicmFjZXsgIFxmcmFje1xwYXJ0aWFsIHR9e1xwYXJ0aWFsIHdfMX1cZnJhY3tccGFydGlhbCBcUGhpKHQpfXtccGFydGlhbCB0fSB9X3t0ID0gYl8xICsgd18xXFBoaShiXzAgKyB3XzB4KX1cXAomPSAtIFxmcmFjezF9e059XHN1bV9pICh5X2kgLSBcaGF0e3l9X2kpIFxjZG90IFxQaGkodCkoMSAtIFxQaGkodCkpIFxjZG90IHpfaS4KXGVuZHthbGlnbmVkfQokJAoKU2ltaWxhcmx5IGZvciB0aGUgYmlhcyBpbiB0aGUgbGFzdCBsYXllcjoKCiQkClxmcmFje1xwYXJ0aWFsIFxtYm94e01TRS1Mb3NzfX17XHBhcnRpYWwgYl8xfSA9IC0gXGZyYWN7MX17Tn1cc3VtX2kgKHlfaSAtIFxoYXR7eX1faSkgXGNkb3QgXFBoaSh0KSgxIC0gXFBoaSh0KSkuCiQkCgpOb3cgbW92ZSBvbiB0byB0aGUgYmlhcyBhbmQgd2VpZ2h0IGluIHRoZSBmaXJzdCBsYXllcjoKCiQkClxiZWdpbnthbGlnbmVkfQpcZnJhY3tccGFydGlhbCBcbWJveHtNU0UtTG9zc319e1xwYXJ0aWFsIHdfMH0KJj0gLSBcZnJhY3sxfXtOfVxzdW1faSAoeV9pIC0gXGhhdHt5fV9pKVxmcmFje1xwYXJ0aWFsIFxoYXR7eX1faX17XHBhcnRpYWwgd18wfSBcXAomPSAtIFxmcmFjezF9e059XHN1bV9pICh5X2kgLSBcaGF0e3l9X2kpClx1bmRlcmJyYWNleyAgXGZyYWN7XHBhcnRpYWwgdH17XHBhcnRpYWwgd18wfVxmcmFje1xwYXJ0aWFsIFxQaGkodCl9e1xwYXJ0aWFsIHR9IH1fe3QgPSBiXzEgKyB3XzFcUGhpKGJfMCArIHdfMHgpfVxcCiY9IC0gXGZyYWN7MX17Tn1cc3VtX2kgKHlfaSAtIFxoYXR7eX1faSkgXGNkb3QgXFBoaSh0KSgxIC0gXFBoaSh0KSkgXGNkb3QgClx1bmRlcmJyYWNleyBcUGhpKGspKDEgLSBcUGhpKGspKSB9X3trID0gYl8wICt3XzB4fSBcY2RvdCB3XzEgXGNkb3QgeF9pLCBcXApcZnJhY3tccGFydGlhbCBcbWJveHtNU0UtTG9zc319e1xwYXJ0aWFsIGJfMH0gCiY9IC0gXGZyYWN7MX17Tn1cc3VtX2kgKHlfaSAtIFxoYXR7eX1faSkgXGNkb3QgXFBoaSh0KSgxIC0gXFBoaSh0KSkgXGNkb3QgXFBoaShrKSgxIC0gXFBoaShrKSkuClxlbmR7YWxpZ25lZH0KJCQKCk9uZSBjYW4gY2xlYXJseSBzZWUgdGhlcmUgaXMgYSBsaW5rYWdlIGJldHdlZW4gdGhlIGRlcml2YXRpdmUgb2YgdGhlIHdlaWdodHMgaW4gY29uc2VjdXRpdmUgbGF5ZXJzOgoKJCQKXGJlZ2lue2FsaWduZWR9ClxmcmFje1xwYXJ0aWFsIFxtYm94e01TRS1Mb3NzfX17XHBhcnRpYWwgd18xfQomPSAtIFxmcmFjezF9e059XHN1bV9pICh5X2kgLSBcaGF0e3l9X2kpIFxjZG90IFxQaGkodCkoMSAtIFxQaGkodCkpIFxjZG90IHpfaSwgXFwKXGZyYWN7XHBhcnRpYWwgXG1ib3h7TVNFLUxvc3N9fXtccGFydGlhbCB3XzB9CiY9IC0gXGZyYWN7MX17Tn1cc3VtX2kgKHlfaSAtIFxoYXR7eX1faSkgXGNkb3QgClx1bmRlcmJyYWNle1xQaGkodCkoMSAtIFxQaGkodCkpfV97XGhhdHt5fV9pKDEgLSBcaGF0e3l9X2kpfSBcY2RvdCAKXHVuZGVyYnJhY2V7XFBoaShrKSgxIC0gXFBoaShrKSkgXGNkb3Qgd18xIFxjZG90IHhfaX1fe1xmcmFje1xwYXJ0aWFsIHdfMXp9e1xwYXJ0aWFsIHdfMH0gPSBcZnJhY3tccGFydGlhbCB3XzFcUGhpKGIwICsgd18weCl9e1xwYXJ0aWFsIHdfMH19LgpcZW5ke2FsaWduZWR9CiQkCgojIyMgQ3Jvc3MtRW50cm9weSBMb3NzCgpTaW1pbGFybHkgd2UgY2FuIGRlcml2ZSB0aGUgZ3JhZGllbnRzIGZvciBjcm9zcy1lbnRyb3B5IGxvc3MuCgokJApcYmVnaW57YWxpZ25lZH0KXG1ib3h7TG9nTG9zc30gCiY9IC0gXGZyYWN7MX17Tn1cc3VtX2leTiBcYmlnZ1sgeV9pXGxuXGhhdHt5fV9pICsgKDEgLSB5X2kpXGxuKDEgLSBcaGF0e3l9X2kpXGJpZ2ddLCBcXApcZnJhY3tccGFydGlhbCBcbWJveHtMb2dMb3NzfX17XHBhcnRpYWwgd18xfQomPSAtIFxmcmFjezF9e059IFxzdW1faSAoeV9pIC0gXGhhdHt5fV9pKXpfaSwgXFwKXGZyYWN7XHBhcnRpYWwgXG1ib3h7TG9nTG9zc319e1xwYXJ0aWFsIHdfMH0KJj0gLSBcZnJhY3sxfXtOfSBcc3VtX2kgKHlfaSAtIFxoYXR7eX1faSlcUGhpKGspKDEgLSBcUGhpKGspKXdfMXhfaS5cXApcZW5ke2FsaWduZWR9CiQkCgooU2tpcCB0aGUgYmlhcyB0ZXJtcyB0byBzYXZlIHNwYWNlLikKCk5vdyBsZXQncyBpbXBsZW1lbnQgdGhlIHNpbXBsZSBuZXVyYWwgbmV0d29yayBtb2RlbCBpbiBQeXRob246CgpgYGB7cHl0aG9uIG5ufQpkZWYgc2lnbW9pZCh4KToKICByZXR1cm4gbnAuZXhwKC1ucC5sb2dhZGRleHAoMCwgLXgpKQoKCmRlZiBzaWdtb2lkX2dyYWQoeik6CiAgcmV0dXJuIHoqKDEgLSB6KQoKCmNsYXNzIE5ldXJhbE5ldHdvcms6CiAgIiIiU2ltcGxlIG5ldXJhbCBuZXR3b3JrIHdpdGggMSBoaWRkZW4gbGF5ZXIgb2YgNCBuZXVyb25zLiIiIgogIGRlZiBfX2luaXRfXyhzZWxmLCB4LCB5KToKICAgIHNlbGYuaW5wdXQgPSB4CiAgICBzZWxmLncxID0gbnAucmFuZG9tLnJhbmQoc2VsZi5pbnB1dC5zaGFwZVsxXSwgNCkKICAgIHNlbGYudzIgPSBucC5yYW5kb20ucmFuZCg0LCAxKQogICAgc2VsZi55ID0geQogICAgc2VsZi5vdXRwdXQgPSBucC56ZXJvcyh5LnNoYXBlKQoKICBkZWYgZmVlZGZvcndhcmQoc2VsZik6CiAgICBzZWxmLmxheWVyMSA9IHNpZ21vaWQobnAuZG90KHNlbGYuaW5wdXQsIHNlbGYudzEpKQogICAgc2VsZi5vdXRwdXQgPSBzaWdtb2lkKG5wLmRvdChzZWxmLmxheWVyMSwgc2VsZi53MikpCgogIGRlZiBiYWNrcHJvcChzZWxmLCBscik6CiAgICByYWlzZSBOb3RJbXBsZW1lbnRlZEVycm9yCiAgCiAgZGVmIHRyYWluKHNlbGYsIGxyLCBuX3N0ZXApOgogICAgZm9yIHN0ZXAgaW4gcmFuZ2Uobl9zdGVwKToKICAgICAgc2VsZi5mZWVkZm9yd2FyZCgpCiAgICAgIHNlbGYuYmFja3Byb3AobHI9bHIpCiAgICAgIHloYXQgPSAiLCIuam9pbihbc3RyKHkpIGZvciB5IGluIG5wLnNxdWVlemUoc2VsZi5vdXRwdXQpXSkKICAgIHJldHVybiB5aGF0CgoKY2xhc3MgTmVydWFsTmV0d29ya1dpdGhNU0VMb3NzKE5ldXJhbE5ldHdvcmspOgogIGRlZiBiYWNrcHJvcChzZWxmLCBscik6CiAgICAjIFdlIGlnbm9yZSBhbGwgY29uc3RhbnQgdGVybXMgaW4gdGhlIGdyYWRpZW50LgogICAgc2VsZi5keSA9IHNlbGYueSAtIHNlbGYub3V0cHV0CiAgICBkdzIgPSAtIG5wLmRvdChzZWxmLmxheWVyMS5ULCAoc2VsZi5keSpzaWdtb2lkX2dyYWQoc2VsZi5vdXRwdXQpKSkKICAgIGR3MSA9IC0gbnAuZG90KHNlbGYuaW5wdXQuVCwgCiAgICAgICAgICAgICAgICAgICAobnAuZG90KHNlbGYuZHkqc2lnbW9pZF9ncmFkKHNlbGYub3V0cHV0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZi53Mi5UKSpzaWdtb2lkX2dyYWQoc2VsZi5sYXllcjEpKSkKICAgIHNlbGYudzEgLT0gbHIgKiBkdzEKICAgIHNlbGYudzIgLT0gbHIgKiBkdzIKCgpjbGFzcyBOZXJ1YWxOZXR3b3JrV2l0aExvZ0xvc3MoTmV1cmFsTmV0d29yayk6CiAgZGVmIGJhY2twcm9wKHNlbGYsIGxyKToKICAgICMgV2UgaWdub3JlIGFsbCBjb25zdGFudCB0ZXJtcyBpbiB0aGUgZ3JhZGllbnQuCiAgICBzZWxmLmR5ID0gc2VsZi55IC0gc2VsZi5vdXRwdXQKICAgIGR3MiA9IC0gbnAuZG90KHNlbGYubGF5ZXIxLlQsIHNlbGYuZHkpCiAgICBkdzEgPSAtIG5wLmRvdChzZWxmLmlucHV0LlQsIAogICAgICAgICAgICAgICAgICAgbnAuZG90KHNlbGYuZHksIHNlbGYudzIuVCkqc2lnbW9pZF9ncmFkKHNlbGYubGF5ZXIxKSkKICAgIHNlbGYudzEgLT0gbHIgKiBkdzEKICAgIHNlbGYudzIgLT0gbHIgKiBkdzIKYGBgCgpIZXJlIHdlIHdpbGwgdXNlIHRoZSBoZWxsby13b3JsZCBleGFtcGxlIG9mIGFydGlmaWNpYWwgbmV1cmFsIG5ldHdvcms6ClRoZSBbWE9SXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9YT1JfZ2F0ZSkgcHJvYmxlbS4KVGhlIFhPUiBsb2dpY2FsIG91dGNvbWUsCmJlc2lkZXMgZXh0cmVtZWx5IHNpbXBsZSwKaXMgbm90IGxpbmVhcmx5IHNlcGFyYWJsZS4KU28gaXQgc2VydmVzIGFzIGEgZ29vZCBleGFtcGxlIG9mIHNob3djYXNpbmcgbmV1cmFsIG5ldHdvcmtzJyBub24tbGluZWFyaXR5LgoKVGhlIGlucHV0IGRhdGEgaXMgc2ltcGx5IGNvbWJpbmF0aW9uIG9mIHR3byBiaW5hcnkgc3dpdGNoZXMuCihGb3IgY29tcGxldGVuZXNzIHdlIGFsc28gaW5jbHVkZSBhIGNvbnN0YW50IHRlcm0gd2hpY2ggYWx3YXlzIGV2YWx1YXRlIHRvIDEgYXMgdGhlIGZpcnN0IGZlYXR1cmUuKQpUaGUgb3V0cHV0IGRhdGEgaXMgdGhlIFhPUiByZXN1bHQuCgpgYGB7cHl0aG9uIG5uX3hvcl9kYXRhfQpYbm4gPSBucC5hcnJheSgKICAgIFtbMSwgMCwgMF0sCiAgICAgWzEsIDEsIDBdLAogICAgIFsxLCAwLCAxXSwKICAgICBbMSwgMSwgMV1dKQp5bm4gPSBucC5hcnJheShbWzBdLCBbMV0sIFsxXSwgWzBdXSkKCnByaW50KFhubikgICMgSW5wdXQgZmVhdHVyZXM6IEJpYXMgKyB0d28gYmluYXJ5IHN3aXRjaGVzLgpwcmludCh5bm4pICAjIFhPUiBvdXRwdXQuCmBgYAoKTm93IGxldCdzIHNlZSBpZiBvdXIgc2ltcGxlIG5ldXJhbCBuZXR3b3JrIG1vZGVsIGNhbiBsZWFybiB0aGUgWE9SIHBhdHRlcm4uCgpgYGB7cHl0aG9uIG5uX2dkX21zZV9sb3NzfQpubl9tc2UgPSBOZXJ1YWxOZXR3b3JrV2l0aE1TRUxvc3MoWG5uLCB5bm4pCnByaW50KG5uX21zZS50cmFpbihscj0xLCBuX3N0ZXA9MzAwMCkpICAjIFRoZSBwcmVkaWN0ZWQgWE9SIG91dGNvbWUuCnByaW50KG5uX21zZS53MSkgICMgRXN0aW1hdGVkIDFzdC1sYXllciBtb2RlbCB3ZWlnaHRzCnByaW50KG5uX21zZS53MikgICMgRXN0aW1hdGVkIDJuZC1sYXllciBtb2RlbCB3ZWlnaHRzCmBgYAoKYGBge3B5dGhvbiBubl9nZF9sb2dfbG9zc30Kbm5fbG9nbG9zcyA9IE5lcnVhbE5ldHdvcmtXaXRoTG9nTG9zcyhYbm4sIHlubikKcHJpbnQobm5fbG9nbG9zcy50cmFpbihscj0xLCBuX3N0ZXA9MzAwMCkpCmBgYAoKRm9yIHRoaXMgc2ltcGxlIGV4YW1wbGUsCmJvdGggTVNFIGFuZCBjcm9zcy1lbnRyb3B5IGxvc3MgY2FuIHdvcmsgZmluZSB0byBmaWd1cmUgb3V0IHRoZSBYT1IgcGF0dGVybi4KKFN0cmljdGx5IHNwZWFraW5nIGNyb3NzLWVudHJvcHkgbG9zcyBwZXJmb3JtcyBiZXR0ZXIuKQoKIyBSZWd1bGFyaXphdGlvbgoKUmVndWxhcml6YXRpb24gaXMgYSB0ZWNobmlxdWUgdG8gbWl0aWdhdGUgW292ZXJmaXR0aW5nXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9PdmVyZml0dGluZykuCkl0IGlzIG5vdCBwYXJ0aWN1bGFyIHRvIG5ldXJhbCBuZXR3b3JrcyBidXQgZ2VuZXJhbCB0byBhbGwgbWFjaGluZSBsZWFybmluZyBtb2RlbHMuCgpBIHJlZ3VsYXJpemF0aW9uIGlzIGEgY29uc3RyYWludCBhZGRlZCBvbnRvIHRoZSBvcmlnaW5hbCBvcHRpbWl6YXRpb24gcHJvYmxlbSAobG9zcyBtaW5pbWl6YXRpb24pLgpDb25zaWRlciBtb2RlbCB3ZWlnaHRzIGFzIGEgcmVhbCB2ZWN0b3IsCndlIHVzdWFsbHkgdXNlIHRoZSBbbm9ybV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTm9ybV8obWF0aGVtYXRpY3MpKSBvZiB0aGUgdmVjdG9yIHRvIGNvbnN0cmFpbiBpdHMgc2l6ZSBpbiB0aGUgb3B0aW1pemF0aW9uLgpUaGUgbm9ybSBpcyBhIG1lYXN1cmUgb2Ygc2l6ZSBvZiBhIHZlY3Rvci4KVGhlIGdlbmVyYWwgaWRlYSBpcyB0byBhZGQgYSBwZW5hbHR5IHRvIHRoZSB0YXJnZXQgZnVuY3Rpb24gKHdoaWNoIGlzIGEgbG9zcyBmdW5jdGlvbikgc3VjaCB0aGF0IGl0IGRpc2NvdXJhZ2VzIHVzaW5nIGxhcmdlIHdlaWdodHMgKHdoaWNoIGluY3JlYXNlIHRoZSBzaXplIG9mIHZlY3RvcikgdG8gYWNoaWV2ZSB0aGUgbWluaW1pemF0aW9uIGdvYWwuCgpJbiBnZW5lcmFsLAphIE1TRSBsb3NzIG1pbmltaXphdGlvbiB3aXRoIGEgcC1ub3JtIHJlZ3VsYXJpemF0aW9uIGZvciBhIGxpbmVhciBtb2RlbCBjYW4gYmUgd3JpdHRlbiAoaW4gbWF0cml4IGZvcm0pIGFzOgoKJCQKXGJlZ2lue2FsaWduZWR9ClxoYXR7eX0gJj0gWFxtYXRocm17Qn0sIFxcClxvcGVyYXRvcm5hbWUqe2FyZ21pbn1fXGJldGEgXG1ib3h7TG9zc30gJj0KXGZyYWN7MX17Tn1cYmlnZ1sKXHVuZGVyYnJhY2V7KHkgLSBYXG1hdGhybXtCfSleVCh5IC0gWFxtYXRocm17Qn0pfV9cdGV4dHtzdW0gb2Ygc3F1YXJlZCBlcnJvcnN9ICsgClx1bmRlcmJyYWNle1xsYW1iZGFcVmVydFxtYXRocm17Qn1cVmVydF97cH19X1x0ZXh0e3Atbm9ybSByZWd1bGFyaXphdGlvbn0KXGJpZ2ddLApcZW5ke2FsaWduZWR9CiQkCgp3aXRoIGEgbW9kZWwgd2VpZ2h0IHZlY3RvciAkXG1hdGhybXtCfSA9IFtcYmV0YV8xLCBcYmV0YTIsIC4uLiwgXGJldGFfa10kLgpUaGUgcGFyYW1ldGVyICRcbGFtYmRhJCBpcyBhIG5ldyBoeXBlci1wYXJhbWV0ZXIgaW50cm9kdWNlZCBieSB0aGUgcmVndWxhcml6YXRpb24uCgpPciBwdXQgaXQgaW4gYSBzY2FsYXIgZm9ybToKCiQkClxiZWdpbntlcXVhdGlvbn0gXGxhYmVse2VxOmxvc3NfbWluX3JlZ30KXG9wZXJhdG9ybmFtZSp7YXJnbWlufV9cYmV0YSBcbWJveHtMb3NzfSA9ClxmcmFjezF9e059XHN1bV97aT0xfV5OXGJpZ2coIHlfaSAtIFxtYXRocm17Qn14X2lcYmlnZyleMiArIApcZnJhY3tcbGFtYmRhfXtOfVxiaWdnKFxzdW1fe2o9MX1ea1x2ZXJ0XGJldGFfalx2ZXJ0XnBcYmlnZyleezEvcH0uClxlbmR7ZXF1YXRpb259CiQkCgpQb3B1bGFyIGNob2ljZXMgb2YgJHAkIGlzICRwID0gMSQgKEwxLU5vcm0pIGFuZCAkcCA9IDIkIChMMi1Ob3JtKS4KTm90YWJseSwKJHAkIGNhbiBiZSBhbnkgbm9uLWludGVnZXIgcmVhbCB2YWx1ZSBhbmQgd291bGQgaGF2ZSBldmVuIGJldHRlciByZXN1bHQgdGhhbiBpbnRlZ2VyLW5vcm0uCkJ1dCB0aGVpciBjb21wdXRhdGlvbmFsIGRpZmZpY3VsdHkgbWFrZSB0aGVtIG11Y2ggbGVzcyBkZXNpcmFibGUgaW4gcHJhY3RpY2UuCgpJbiBwcmFjdGljZSwKd2UgYWxzbyBpZ25vcmUgdGhlIHJvb3QgaW4gdGhlIG5vcm0gZm9yIGNvbXB1dGF0aW9uYWwgc2ltcGxpY2l0eS4KU28gdGhlIHByb2JsZW0gaW5kZWVkIGJlY29tZXMKCiQkClxvcGVyYXRvcm5hbWUqe2FyZ21pbn1fXGJldGEgXG1ib3h7TG9zc30gPQpcZnJhY3sxfXtOfVxzdW1fe2k9MX1eTlxiaWdnKCB5X2kgLSBcbWF0aHJte0J9eF9pXGJpZ2cpXjIgKyAKXGZyYWN7XGxhbWJkYX17Tn1cYmlnZyhcc3VtX3tqPTF9XmtcdmVydFxiZXRhX2pcdmVydF5wXGJpZ2cpLgokJAoKVGhlcmUgaXMgYWxzbyBhIHNvLWNhbGxlZCBbZWxhc3RpY25ldCByZWd1bGFyaXphdGlvbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRWxhc3RpY19uZXRfcmVndWxhcml6YXRpb24pIHdoaWNoIGNvbWJpbmVzIGJvdGggTDEgYW5kIEwyIG5vcm1zIHRvZ2V0aGVyIHRvIGdpdmUgcG90ZW50aWFsbHkgYmV0dGVyIHJlc3VsdC4KSG93ZXZlciBpdCByZXF1aXJlcyBhIGxvdCBtb3JlIGNvbXB1dGluZyByZXNvdXJjZXMgYW5kIGhlbmNlIGJlY29tZSBtdWNoIGxlc3MgZmVhc2libGUgaW4gYSBuZXVyYWwgbmV0d29yayBtb2RlbC4KCiMjIEwyLU5vcm0gUmVndWxhcml6YXRpb24KClRoZSBMMi1Ob3JtIHNocmlua3MgdGhlIHNpemUgb2YgbW9kZWwgd2VpZ2h0cy4KVGhvc2Ugd2hvIGNvbnRyaWJ1dGUgbGVzcyB0byBsb3NzIG1pbmltaXphdGlvbiB3aWxsIHNocmluayBtb3JlLgoKQmFjayB0byBvdXIgdW5pdmFyaWF0ZSBsaW5lYXIgbW9kZWwgJFxoYXR7eX1faSA9IFxiZXRhXzAgKyBcYmV0YV8xeF9pJCBmb3Igc3ltb2xpYyBzaW1wbGljaXR5LgpPdXIgTVNFIGxvc3MgaXMgCgokJApcbWJveHtNU0UtTG9zc30gPSBcZnJhY3sxfXtOfVxzdW1faVxiaWdbeV9pLShcYmV0YV8wICsgXGJldGFfMXhfaSlcYmlnXV4yICsgXGZyYWN7XGxhbWJkYX17Tn0oXGJldGFfMF4yICsgXGJldGFfMV4yKS4KJCQKClNpbmNlIHRoZSBwZW5hdHkgaXMgYWRkaXRpdmUsCm91ciBncmFkaWVudCBzb2x1dGlvbiBpcyBleHRyZW1lbHkgZWFzeToKCiQkClxiZWdpbnthbGlnbmVkfQpcZnJhY3tccGFydGlhbFxtYm94e01TRS1Mb3NzfX17XHBhcnRpYWxcYmV0YV8wfSAKJj0gLSBcZnJhY3syfXtOfVxzdW1faSBcYmlnWyB5X2kgLSAoXGJldGFfMCArIFxiZXRhXzF4X2kpIFxiaWddICsgXGZyYWN7Mn17Tn1cbGFtYmRhXGJldGFfMCwgXFwKXGZyYWN7XHBhcnRpYWxcbWJveHtNU0UtTG9zc319e1xwYXJ0aWFsXGJldGFfMX0gCiY9IC0gXGZyYWN7Mn17Tn1cc3VtX2kgXGJpZ1sgeV9pIC0gKFxiZXRhXzAgKyBcYmV0YV8xeF9pKSBcYmlnXXhfaSArIFxmcmFjezJ9e059XGxhbWJkYVxiZXRhXzEuClxlbmR7YWxpZ25lZH0KJCQKCkEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCBsMiByZWd1bGFyaXphdGlvbiBpcyByZWZlcnJlZCB0byBhcyB0aGUgcmlkZ2UgcmVncmVzc2lvbi4KQmUgYXdhcmUgdGhhdCBpbiBpbXBsZW1lbnRpbmcgdGhlIGJhdGNoIG9wdGltaXplciB3ZSBuZWVkIHRvIHNjYWxlIGRvd24gdGhlIHVwZGF0ZSBmcm9tIGwyIG5vcm0gYnkgdGhlIHNpemUgb2YgYmF0Y2ggaW4gb3JkZXIgdG8gYmFsYW5jZSB0aGUgc2l6ZSBvZiBsMiBub3JtIGFuZCB0aGUgb3JpZ2luYWwgbG9zcy4KCmBgYHtweXRob24gcmlnZGVfcmVnfQpkZWYgbXNlX2dyYWRfbDIoWCwgeSwgYmV0YSk6CiAgYSA9IDEgICMgSGFyZGNvZGUgdGhlIHJlZ3VsYXJpemF0aW9uIGNvZWZmaWNpZW50IGZvciBzaW1wbGljaXR5LgogIHJldHVybiAtMiAqICgoeSAtIFguZG90KGJldGEpKS5kb3QoWCkpIC8gWC5zaGFwZVswXSArIDIgKiBhICogYmV0YSAvIFguc2hhcGVbMF0KCmJhdGNoX2JldGEsIGJhdGNoX2xvc3MgPSBnZF9iYXRjaF9vcHRpbWl6ZShYLCB5LCBtc2VfZ3JhZF9sMiwgbl9lcG9jaD0xMDAsIGJhdGNoX3NpemU9NjQpCnByaW50KGJhdGNoX2JldGEpCmBgYAoKVGhlIHJlc3VsdGluZyBlc3RpbWF0ZXMgYXJlIHNtYWxsZXIgdGhhbiB0aGUgdmFuaWxsYSBncmFkaWVudCBkZXNjZW50LgoKT25lIHRoaW5nIHdvcnRoIG5vdGluZyBpcyB0aGF0IHdoZW4gYmF0Y2ggZ3JhZGllbnQgZGVzY2VudCBpcyB1c2VkIGFsb25nIHdpdGggcmVndWxhcml6YXRpb24sCmEgc2lkZSBlZmZlY3QgZXhpc3RzIGR1ZSB0byB0aGUgc2NhbGluZyBvZiB0aGUgYmF0Y2ggc2l6ZSBvbiB0aGUgcmVndWxhcml6YXRpb24gdGVybS4KCkV4cGVyaW1lbnRzIHdpdGggZGlmZmVyZW50IGJhdGNoIHNpemVzIHdpdGggbDIgcmVndWxhcml6YXRpb246CgpgYGB7cHl0aG9uIHJpZGdlX3JlZ19kaWZmZXJfYmF0Y2hfc2l6ZX0KZm9yIGJzaXplIGluIFs4LCAxNiwgMzIsIDY0LCAxMjgsIDI1NiwgNTEyLCAxMDAwXToKICBwcmludCgiQmF0Y2ggU2l6ZTogezo0fSB8IEVzdGltYXRlOiB7fSIuZm9ybWF0KAogICAgYnNpemUsCiAgICBnZF9iYXRjaF9vcHRpbWl6ZShYLCB5LCBtc2VfZ3JhZF9sMiwgbl9lcG9jaD01MDAsIGJhdGNoX3NpemU9YnNpemUpWzBdKSkKYGBgCgpMZXQncyBjaGVjayB0aGUgcmVzdWx0IHVzaW5nIGBza2xlYXJuYCdzIGBSaWRnZWAgcmVncmVzc29yICh3aGljaCBkb2Vzbid0IHVzZSBncmFkaWVudCBkZXNjZW50IGFzIGl0cyBzb2x2ZXIpOgoKYGBge3B5dGhvbiByaWRnZV9yZWdfc2tsZWFybn0KIyBXZSBzZXQgZml0X2ludGVyY2VwdD1GYWxzZSBzaW5jZSBvdXIgZGVzaWduIG1hdHJpeCBhbHJlYWR5IGNvbnRhaW5zIGludGVyY2VwdC4KIyBBY2NvcmRpbmcgdG8gdGhlIGRvYyB0aGUgY29uc3RhbnQgMiBpbiB0aGUgZGVyaXZhdGl2ZSBpcyBub3QgaW5jbHVkZWQgc28gd2Ugc2V0IGFscGhhID0gMi4KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgUmlkZ2UKcHJpbnQoUmlkZ2UoYWxwaGE9MiwgZml0X2ludGVyY2VwdD1GYWxzZSkuZml0KFgsIHkpLmNvZWZfKQpgYGAKClRoZSByZXN1bHQgaXMgY2xvc2VkIHRvIGdyYWRpZW50IGRlc2NlbnQgd2l0aCBuZWFybHkgYSBmdWxsLWJhdGNoIHVwZGF0ZSAoaS5lLiwgdXNlIHRoZSBlbnRpcmUgdHJhaW5pbmcgZGF0YSBmb3Igb25lIHVwZGF0ZSkuCgpFeGFjdGx5IHRoZSBzYW1lIGxvZ2ljIGNhbiBhcHBseSB0byBsb2dpc3RpYyByZWdyZXNzaW9uIGFuZCBpcyBub3QgZGlzY3Vzc2VkIGhlcmUgdG8gYXZvaWQgcmVkdW5kYW5jeS4KCiMjIEwxLU5vcm0gUmVndWxhcml6YXRpb24KClRoZSBMMS1Ob3JtIHByZWZlcnMgc3BhcnNpdHkgaW4gdGhlIG1vZGVsIHdlaWdodHMuClRoYXQgaXMsCml0IG1heSB6ZXJvIG91dCB3ZWlnaHRzIHRoYXQgYXJlIG5vdCBjb250cmlidXRpbmcgdG8gdGhlIGxvc3MuCldoaWxlIHRoZSBMMi1Ob3JtIG9ubHkgbWFrZXMgdGhlbSBhcmJpdHJhcmlseSBzbWFsbC4KCk1TRSBsb3NzIHdpdGggbDEgbm9ybToKCiQkClxtYm94e01TRS1Mb3NzfSA9IFxmcmFjezF9e059XHN1bV9pXGJpZ1t5X2ktKFxiZXRhXzAgKyBcYmV0YV8xeF9pKVxiaWddXjIgKyBcbGFtYmRhKFx2ZXJ0XGJldGFfMFx2ZXJ0ICsgXHZlcnRcYmV0YV8xXHZlcnQpLAokJAoKd2l0aCBncmFkaWVudHM6XltOb3RlIHRoYXQgdGhlIGRlcml2YXRpdmUgJFxmcmFje2R1fXtkeH0gPSBcZnJhY3t1fXtcdmVydCB1IFx2ZXJ0fVxmcmFje2R1fXtkeH0kIGF0ICR1ID0gMCQgaXMgdW5kZWZpbmVkLl0KCiQkClxiZWdpbnthbGlnbmVkfQpcZnJhY3tccGFydGlhbFxtYm94e01TRS1Mb3NzfX17XHBhcnRpYWxcYmV0YV8wfSAKJj0gLSBcZnJhY3syfXtOfVxzdW1faSBcYmlnWyB5X2kgLSAoXGJldGFfMCArIFxiZXRhXzF4X2kpIFxiaWddICsgXGxhbWJkYVxmcmFje1xiZXRhXzB9e1x2ZXJ0XGJldGFfMFx2ZXJ0fSwgXFwKXGZyYWN7XHBhcnRpYWxcbWJveHtNU0UtTG9zc319e1xwYXJ0aWFsXGJldGFfMX0gCiY9IC0gXGZyYWN7Mn17Tn1cc3VtX2kgXGJpZ1sgeV9pIC0gKFxiZXRhXzAgKyBcYmV0YV8xeF9pKSBcYmlnXXhfaSArIFxsYW1iZGFcZnJhY3tcYmV0YV8xfXtcdmVydFxiZXRhXzFcdmVydH0uClxlbmR7YWxpZ25lZH0KJCQKCkEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCBsMSByZWd1bGFyaXphdGlvbiBpcyByZWZlcnJlZCB0byBhcyB0aGUgbGFzc28gcmVncmVzc2lvbi4KTGV0J3MgZmlyc3QgY2hlY2sgdGhlIHJlc3VsdCB1c2luZyBgc2tsZWFybmAncyBgTGFzc29gIHJlZ3Jlc3NvciAod2hpY2ggdXNlcyBhIGNvb3JkaW5hdGUgZGVzY2VudCBzb2x2ZXIpOgoKYGBge3B5dGhvbiBsYXNzb19yZWdfc2tsZWFybn0KIyBXZSBzZXQgZml0X2ludGVyY2VwdD1GYWxzZSBzaW5jZSBvdXIgZGVzaWduIG1hdHJpeCBhbHJlYWR5IGNvbnRhaW5zIGludGVyY2VwdC4KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgTGFzc28KcHJpbnQoTGFzc28oYWxwaGE9MSwgZml0X2ludGVyY2VwdD1GYWxzZSkuZml0KFgsIHkpLmNvZWZfKQpgYGAKClRvIGRlcml2ZSByb3VnaGx5IHRoZSBzYW1lIHJlc3VsdCwKd2UgbmVlZCB0byBjYWxpYnJhdGUgdGhlIGNvZWZmaWNpZW50IG9uIHJlZ3VsYXJpemF0aW9uIGluIG91ciBncmFkaWVudCBkZXNjZW50IHNvbHZlcjoKCmBgYHtweXRob24gbGFzc29fcmVnfQpkZWYgbXNlX2dyYWRfbDEoWCwgeSwgYmV0YSk6CiAgIyBhIGlzIHNldCB0byAyIHNpbmNlIGluIHNrbGVhcm4gY29udmVudGlvbiB0aGUgZW50aXJlIGxvc3MgaXMgc2NhbGVkIGJ5IGEgZmFjdG9yIG9mIDEvMgogICMgdG8gY2FuY2VsIG91dCB0aGUgZGVyaXZhdGl2ZSBjb25zdGFudCBvbiB0aGUgc3F1YXJlZCBlcnJvciB0ZXJtLgogIGEgPSAyCiAgcmV0dXJuIC0yICogKCh5IC0gWC5kb3QoYmV0YSkpLmRvdChYKSkgLyBYLnNoYXBlWzBdICsgYSAqIG5wLnNpZ24oYmV0YSkKCiMgV2UgdXNlIGEgZnVsbC1iYXRjaCBncmFkaWVudCBkZXNjZW50IGhlcmUuCmJhdGNoX2JldGEsIGJhdGNoX2xvc3MgPSBnZF9iYXRjaF9vcHRpbWl6ZShYLCB5LCBtc2VfZ3JhZF9sMSwgbl9lcG9jaD0xMDAwLCBiYXRjaF9zaXplPTEwMDApCnByaW50KGJhdGNoX2JldGEpCmBgYAoKV2UgY2FuIGFsc28gY2hlY2sgdGhlIGNvbnNpc3RlbmN5IGJ5IGNhbGxpbmcgYHNrbGVhcm5gJ3MgYFNHRFJlZ3Jlc3NvcmA6CgpgYGB7cHl0aG9uIGxhc3NvX3JlZ19za2xlYXJuX3NnZH0KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgU0dEUmVncmVzc29yCnByaW50KFNHRFJlZ3Jlc3NvcihwZW5hbHR5PSJsMSIsIGFscGhhPTEsIGZpdF9pbnRlcmNlcHQ9RmFsc2UpLmZpdChYLCB5KS5jb2VmXykKYGBgCgpOb3cgbGV0J3MgcHVycG9zZWx5IGFkZCBvbmUgcmFuZG9tIGZlYXR1cmUgYXMgYSBub2lzZSBpbnRvIHRoZSBkZXNpZ24gbWF0cml4IGFuZCBzZWUgaG93IHRoZSByZWd1bGFyaXphdGlvbiBoZWxwcyB0byBzaHJpbmsgdGhlIHdlaWdodCBvZiB0aGUgbm9pc2UuCgpgYGB7cHl0aG9uIHNocmlua2FnZV9leHB9CiMgSW50cm9kdWNlIGEgdG90YWxseSBpcnJlbGV2YW50IGZlYXR1cmUgaW50byBvdXIgbGluZWFyIG1vZGVsLgpub2lzZSA9IG5wLnJhbmRvbS51bmlmb3JtKHNpemU9TikucmVzaGFwZShOLCAxKQpYbm9pc2UgPSBucC5jb25jYXRlbmF0ZShbWCwgbm9pc2VdLCBheGlzPTEpCgpwcmludChSaWRnZShhbHBoYT0yLCBmaXRfaW50ZXJjZXB0PUZhbHNlKS5maXQoWG5vaXNlLCB5KS5jb2VmXykKCnByaW50KExhc3NvKGFscGhhPTEsIGZpdF9pbnRlcmNlcHQ9RmFsc2UpLmZpdChYbm9pc2UsIHkpLmNvZWZfKQpgYGAKCkFzIG9uZSBjYW4gc2VlLApgTGFzc29gIHJlZ3Jlc3NvciBpcyBhYmxlIHRvIGNvbXBsZXRlbHkgd2lwZSBvdXQgdGhlIHdlaWdodCBvbiB0aGUgcmVkdW5kYW50IG5vaXNlLAp3aGlsZSBgUmlkZ2VgIGNhbiBvbmx5IHNocmluayBpdCB0b3dhcmQgemVyby4KClRvIGFycml2ZSByb3VnaGx5IGF0IHRoZSBzYW1lIHJlc3VsdCBvZiBgUmlkZ2VgIHVzaW5nIG91ciBzaW1wbGUgZ3JhZGllbnQgZGVzY2VudCBzb2x2ZXI6CgpgYGB7cHl0aG9uIHNocmlua2FnZV9leHBfcmlnZGVfc2dkfQpwcmludChnZF9iYXRjaF9vcHRpbWl6ZShYbm9pc2UsIHksIG1zZV9ncmFkX2wyLCBscj0uMDMsIG5fZXBvY2g9MTAwMCwgYmF0Y2hfc2l6ZT0xMDAwKVswXSkKYGBgCgpBbmQgZm9yIHRoZSBgTGFzc29gIGNhc2U6CgpgYGB7cHl0aG9uIHNocmlua2FnZV9leHBfbGFzc29fc2dkfQpwcmludChnZF9iYXRjaF9vcHRpbWl6ZShYbm9pc2UsIHksIG1zZV9ncmFkX2wxLCBscj0uMDMsIG5fZXBvY2g9MTAwMCwgYmF0Y2hfc2l6ZT0xMDAwKVswXSkKYGBgCgpPbmUgdGhpbmcgdG8gbm90ZToKSXQgaXMgaW4gZ2VuZXJhbCBkaWZmaWN1bHQgdG8gYXJyaXZlIGF0IHNwYXJzZSBzb2x1dGlvbiAoMCB3ZWlnaHQpIGZvciBhIG5haXZlIGdyYWRpZW50IGRlc2NlbnQgaW1wbGVtZW50YXRpb24gc3VjaCBhcyBvdXJzLgpTb21lIGZ1cnRoZXIgbWF0aGVtYXRpY2FsIHRyaWNrcyBsaWtlIHRoZSBbcHJveGltYWwgZ3JhZGllbnQgbWV0b2hkXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Qcm94aW1hbF9ncmFkaWVudF9tZXRob2QpIG11c3QgYmUgYXBwbGllZCBpbiBvcmRlciB0byBwcm9kdWNlIHNwYXJzZSByZXN1bHQgdW5kZXIgZmluaXRlIGl0ZXJhdGlvbnMuCgpGaW5hbGx5LAphZ2FpbiwKZXhhY3RseSB0aGUgc2FtZSBsb2dpYyBjYW4gYXBwbHkgdG8gbG9naXN0aWMgcmVncmVzc2lvbiBhbmQgaXMgbm90IGRpc2N1c3NlZCBmdXJ0aGVyIHRvIGF2b2lkIHJlZHVuZGFuY3kuCgojIyBHZW9tZXRyaWMgSW50ZXJwcmV0YXRpb24gb2YgTDEvTDIgUmVndWxhcml6YXRpb24KCkNvbnNpZGVyIGEgdHdvLXBhcmFtZXRlciBtb2RlbGluZyBjYXNlLAp3aGVyZSB3ZSBwbG90IHRoZSB0d28gcG9zc2libGUgd2VpZ2h0cyAkd18xJCBhbmQgJHdfMiQgYXMgYSBFdWNsaWRlYW4gY29vcmRpbmF0ZSBzeXN0ZW0uClRoZSBNU0UgbG9zcyBtaW5pbWl6YXRpb24gcHJvYmxlbSBzdGF0ZWQgaW4gZXF1YXRpb24gJFxlcXJlZntlcTpsb3NzX21pbl9yZWd9JCBjYW4gYmUgcmV3cml0dGVuIGFzIGFuICpvcHRpbWl6YXRpb24gd2l0aCBsaW5lYXIgY29uc3RyYWludCo6CgokJApcYmVnaW57YWxpZ25lZH0KJiBcb3BlcmF0b3JuYW1lKnthcmdtaW59X1xiZXRhCiYgJiBcZnJhY3sxfXtOfVxzdW1fe2k9MX1eTlxiaWdnKCB5X2kgLSBcbWF0aHJte0J9eF9pXGJpZ2cpXjIgXFwKJiBcdGV4dHtzdWJqZWN0IHRvfQomICYgXGJpZ2coXHN1bV97aj0xfV5rXHZlcnRcYmV0YV9qXHZlcnRecFxiaWdnKV57MS9wfSBcbGUgdC4KXGVuZHthbGlnbmVkfQokJAoKU29sdmluZyB0aGUgYWJvdmUgcHJvYmxlbSAodXNpbmcgZm9yIGV4YW1wbGUgdGhlIG1ldGhvZCBvZiBMYWdyYW5nZSBtdWx0aXBsaWVycykgd2lsbCByZXN1bHQgaW4gZXhhY3RseSB0aGUgc2FtZSB0YXJnZXQgZnVuY3Rpb24gYXMgaW4gZXF1YXRpb24gJFxlcXJlZntlcTpsb3NzX21pbl9yZWd9JAoKTm93IGlmIHdlIHBsb3QgdGhlIGxpbmVhciBjb25zdHJhaW50IG9uIHRoZSBFdWNsaWRlYW4gc3BhY2UgKGZvciB0aGUgY2FzZSBvZiAkayA9IDIkIGFuZCBhbiBhcmJpdHJhcnkgdmFsdWUgb2YgY29uc3RhbnQgJHQkKSwKdGhlIHBvc3NpYmxlIHBhcmFtZXRlciBjb21iaW5hdGlvbiB3aWxsIGZhbGwgaW50byBhIGNpcmxlIGZvciAkcCA9IDIkIChMMiByZWd1bGFyaXphdGlvbikgb3IgYSBzcXVhcmVkIGRpYW1vbmQgZm9yICRwID0gMSQgKEwxIHJlZ3VsYXJpemF0aW9uKSwKYm90aCBjZW50ZXJlZCBhdCB0aGUgb3JpZ2luLgoKYGBge3B5dGhvbiBnZW9tX2wyfQp0ID0gMiAgIyBBZC1ob2MgY29uc3RhbnQgY29uc3RyYWludCBvbiB0aGUgbm9ybS4KCiMgQ29kZSBzb21lIHJldXNhYmxlIG9iamVjdHMuCnNoYXBlcyA9IFsKICBkaWN0KHR5cGU9ImNpcmNsZSIsIHgwPS10LCB5MD0tdCwgeDE9dCwgeTE9dCwKICAgICAgIGZpbGxjb2xvcj0iZGFya2dyZWVuIiwgb3BhY2l0eT0wLjI1LCBsaW5lPWRpY3QoY29sb3I9ImRhcmtncmVlbiIpKSwKICBkaWN0KHR5cGU9InBhdGgiLCBwYXRoPSJNIDAsLXt0fSBMLXt0fSwwIEwwLHt0fSBMe3R9LDAgWiIuZm9ybWF0KHQ9dCksCiAgICAgICBmaWxsY29sb3I9ImJsdWUiLCBvcGFjaXR5PTAuMjUsIGxpbmU9ZGljdChjb2xvcj0iYmx1ZSIpKV0KdzFfYXhpcyA9IGRpY3QodGl0bGU9ciIkd197MX0kIiwgcmFuZ2U9Wy10KjEuNSwgdCoxLjVdKQp3Ml9heGlzID0gZGljdCh0aXRsZT1yIiR3X3syfSQiLCByYW5nZT1bLXQqMS41LCB0KjEuNV0sIHNjYWxlYW5jaG9yPSJ4Iiwgc2NhbGVyYXRpbz0xKQoKb2ZpbGUgPSAicGxvdHMvcmVnX2dlb20uaHRtbCIKcCA9IHBsb3Rfb2ZmbGluZSgKICBvZmlsZT1vZmlsZSwKICBkYXRhPVsKICAgIGdvLlNjYXR0ZXIoCiAgICAgIHg9W3QrMV0sIHk9W3RdLCBtb2RlPSJ0ZXh0IiwgbmFtZT0iTDIgUmVndWxhcml6YXRpb24iLAogICAgICB0ZXh0PVtyIiRcc3FydHt3XzFeMSArIHdfMl4yfSA8PSAiICsgInt9JCIuZm9ybWF0KHQpXSwKICAgICAgdGV4dGZvbnQ9ZGljdChjb2xvcj0iZGFya2dyZWVuIikpLAogICAgZ28uU2NhdHRlcigKICAgICAgeD1bdCsxXSwgeT1bLXRdLCBtb2RlPSJ0ZXh0IiwgbmFtZT0iTDEgUmVndWxhcml6YXRpb24iLAogICAgICB0ZXh0PVtyIiRcdmVydCB3XzEgXHZlcnQgKyBcdmVydCB3XzIgXHZlcnQgPD0gIiArICJ7fSQiLmZvcm1hdCh0KV0sCiAgICAgIHRleHRmb250PWRpY3QoY29sb3I9ImJsdWUiKSkKICBdLAogIGxheW91dD1nby5MYXlvdXQodGl0bGU9IlBhcmFtZXRlciBTcGFjZSIsIHNob3dsZWdlbmQ9VHJ1ZSwKICAgICAgICAgICAgICAgICAgIHhheGlzPXcxX2F4aXMsIHlheGlzPXcyX2F4aXMsIHNoYXBlcz1zaGFwZXMpKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpodG1sdG9vbHM6OmluY2x1ZGVIVE1MKHB5JG9maWxlKQpgYGAKClNpbXBseSBwbG90IHRoZSBjb25zdHJhaW50IGFyZWEgaXMgbm90IHRoYXQgaW5mb3JtYXRpdmUuCldlIG5lZWQgdG8gYWxzbyB2aXN1YWxpemUgdGhlIHBvc3NpYmxlIHZhbHVlcyBvZiBsb3NzIGZ1bmN0aW9uLgoKTGV0J3MgdXNlIHRoZSB0b3kgbW9kZWwgaW4gb3VyIHByZXZpb3VzIGxpbmVhciByZWdyZXNzaW9uIHNlY3Rpb24gdG8gZG8gdGhlIHZpc3VhbGl6YXRpb24uCldlIHdpbGwgaXRlcmF0ZSBvdmVyIHRoZSBjb29yZGluYXRlIHNwYWNlIHdpdGhpbiAkLTEwIFxsZSB3XzEgXGxlIDEwJCBhbmQgJC0xMCBcbGUgd18yIFxsZSAxMCQgdG8gY29sbGVjdCBhbGwgcG9zc2libGUgcmVzdWx0aW5nIGxvc3MgdmFsdWVzLgpUaGUgbG9zcyB3aWxsIGJlIGEgM3JkIGF4aXMgYWRkZWQgb250byB0aGUgZXhpc3RpbmcgcGxhaW4uCgpgYGB7cHl0aG9uIDNkX2xvc3Nfd2VpZ2h0c30Kel9sb3NzID0gW10KX3ggPSBucC5hcmFuZ2UoLTEwLCAxMCkKX3kgPSBucC5hcmFuZ2UoLTEwLCAxMCkKZm9yIF93MSBpbiBfeDoKICBmb3IgX3cyIGluIF95OgogICAgel9sb3NzLmFwcGVuZChsb3NzKFgsIHksIFtfdzEsIF93Ml0pKQp6X2xvc3MgPSBucC5hc2FycmF5KHpfbG9zcykucmVzaGFwZSgyMCwgMjAsIG9yZGVyPSJGIikKYGBgCgpgYGB7cHl0aG9uIDNkX2xvc3NfcGxvdH0KdzEsIHcyID0gb2xzKFgsIHkpICAjIFVucmVndWxhcml6ZWQgT0xTIHNvbHV0aW9uLgoKb2ZpbGUgPSAicGxvdHMvM2RfbG9zcy5odG1sIgpwID0gcGxvdF9vZmZsaW5lKAogIG9maWxlPW9maWxlLAogIGRhdGE9WwogICAgZ28uU3VyZmFjZSgKICAgICAgeD1feCwgeT1feSwgej16X2xvc3MsCiAgICAgIGNvbnRvdXJzPWdvLnN1cmZhY2UuQ29udG91cnMoCiAgICAgICAgej1nby5zdXJmYWNlLmNvbnRvdXJzLlooCiAgICAgICAgICBzaG93PVRydWUsCiAgICAgICAgICB1c2Vjb2xvcm1hcD1UcnVlLAogICAgICAgICAgaGlnaGxpZ2h0Y29sb3I9IiM0MmY0NjIiLAogICAgICAgICAgcHJvamVjdD1kaWN0KHo9VHJ1ZSkpKSksCiAgICBnby5TY2F0dGVyM2QoCiAgICAgIHg9W3cxXSwgeT1bdzJdLCB6PVswXSwgbW9kZT0ibWFya2VycyIsCiAgICAgIG1hcmtlcj1kaWN0KHN5bWJvbD0iY3Jvc3MiLCBjb2xvcj0iYmx1ZSIpKQogIF0sCiAgbGF5b3V0PWdvLkxheW91dCgKICAgIHRpdGxlPSJNU0UgTG9zcyBvbiBQYXJhbWV0ZXIgU3BhY2UiLAogICAgc2NlbmU9ZGljdCgKICAgICAgIyBMYXRleCBub3RhdGlvbiBkb2Vzbid0IHdvcmsgaW4gM2QgcmVuZGVyLiBLbm93biBpc3N1ZSBvZiBwbG90bHkuanMuCiAgICAgIHhheGlzPWRpY3QodGl0bGU9IncxIiksCiAgICAgIHlheGlzPWRpY3QodGl0bGU9IncyIiksCiAgICAgIHpheGlzPWRpY3QodGl0bGU9Ikxvc3MiKSkpKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9Cmh0bWx0b29sczo6aW5jbHVkZUhUTUwocHkkb2ZpbGUpCmBgYAoKTm90ZSB0aGF0IHdlIGFsc28gcGxvdCB0aGUgY29udG91ciBvbiB0aGUgb3JpZ2luYWwgMi1kIHBsYWluLgpUaGUgY29udG91ciBkZXBpY3RzIGFsbCB0aGUgd2VpZ2h0IGNvbWJpbmF0aW9ucyB0aGF0IGxlYWQgdG8gdGhlIHNhbWUgYW1vdW50IG9mIGxvc3MuCkFuZCB3ZSBtYXJrIHRoZSBtaW5pbXVtIHdpdGggYSBjcm9zcyB0byBjbGVhcmx5IHBvaW50IG91dCB0aGUgb3B0aW11bSBsb3NzIHdoaWNoIGNhbiBiZSBhY2hpZXZlZCBieSB0aGUgdW5yZWd1bGFyaXplZCBPTFMgZXN0aW1hdG9yIGluIGVxdWF0aW9uICRcZXFyZWZ7ZXE6b2xzfSQuCgpOb3cgaWYgd2UgY29tYmluZSB0aGUgdHdvIHBsb3RzIHRvZ2V0aGVyLAplc3BlY2lhbGx5IHRoZSBjb250b3VyIG9uIHRoZSBwYXJhbWV0ZXIgcGxhaW4sCnRoZSB0YW5nZW50IHBvaW50IGJldHdlZW4gdGhlIHJlZ3VsYXJpemF0aW9uIGFyZWEgYW5kIHRoZSBjb250b3VyIGlzIGV4YWN0bHkgb3VyIHJlZ3VsYXJpemVkIHNvbHV0aW9uIGZvciB0aGUgd2VpZ2h0cy5eW1NpbmNlIHBsb3RseS5qcyBjdXJyZW50bHkgZG9lc24ndCBzdXBwb3J0IHVuZXZlbiBpbnRlcnZhbCBmb3IgY29udG91ciBwbG90LCBpbiB0aGUgY29udG91ci1yZWd1bGFyaXphdGlvbiBwbG90IHdlIHRha2UgbmF0cnVhbCBsb2cgb2YgTVNFIGp1c3QgdG8gc2NhbGUgdGhlIHZhbHVlIHNvIHRoYXQgdGhlIGNvbnRvdXIgbGluZSB3aWxsIGhhdmUgbm9uLWxpbmVhciBpbnRlcnZhbC4gVGhpcyBoZWxwIHVzIHNob3cgbW9yZSBsaW5lcyB3aGVuIGxvc3MgYmVjb21lcyBzbWFsbGVyLl0KCmBgYHtweXRob24gbG9zc19jb250b3VyX3JlZ19wbG90fQpvZmlsZSA9ICJwbG90cy9sb3NzX2NvbnRvdXJfcmVnLmh0bWwiCnAgPSBwbG90X29mZmxpbmUoCiAgb2ZpbGU9b2ZpbGUsCiAgZGF0YT1bCiAgICBnby5Db250b3VyKAogICAgICB4PV94LCB5PV95LCB6PW5wLmxvZyh6X2xvc3MpLAogICAgICBjb250b3Vycz1kaWN0KGNvbG9yaW5nPSJsaW5lcyIpLAogICAgICBjb2xvcmJhcj1kaWN0KHRpdGxlPSJMbihNU0UgTG9zcykiKSwKICAgICAgbGluZT1kaWN0KHdpZHRoPTIpKQogIF0sCiAgbGF5b3V0PWdvLkxheW91dCh0aXRsZT0iTVNFIExvc3Mgb24gUGFyYW1ldGVyIFNwYWNlIiwKICAgICAgICAgICAgICAgICAgIHhheGlzPXcxX2F4aXMsIHlheGlzPXcyX2F4aXMsIHNoYXBlcz1zaGFwZXMpKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9Cmh0bWx0b29sczo6aW5jbHVkZUhUTUwocHkkb2ZpbGUpCmBgYAoKVGhlIGNvbnN0cmFpbnQgY29uc3RhbnQgY29udHJvbCB0aGUgc2l6ZSBvZiByZWd1bGFyaXphdGlvbiwKZm9yIGEgbGFyZ2VyIGNvbnN0YW50IChsb29zZXIgY29uc3RyYWludCkgb3VyIHRhZ2VudCBwb2ludCB3aWxsIG1vdmUgY2xvc2VyIHRvIHRoZSB1bnJlZ3VsYXJpemVkIG9wdGltdW0uCkZvciB0aGlzIGdlb21ldHJpYyBwb2ludCBvZiB2aWV3LApvbmUgc2hvdWxkIGFsc28gcmVhbGl6ZSB0aGF0IHNpbmNlIHRoZSBMMSByZWd1bGFyaXphdGlvbiBhcmVhIGlzIGEgc2hhcnAgZGlhbW9uZCBzcXVhcmUsCml0IGlzIG1vcmUgbGlrZWx5IHRvIHRhbmdlbnQgdGhlIGNvbnRvdXIgb24gdGhlIGVkZ2Ugd2hpY2ggZXhhY3RseSByZXByZXNlbnQgYSBzcGFyc2Ugc29sdXRpb24gdG8gdGhlIHdlaWdodCAob24gZWFjaCBlZGdlIHBvaW50IHRoZXJlIGlzIG9uZSB3ZWlnaHQgemVyb2VkIG91dCkuCgojIyBQcm9iYWJpbGlzdGljIEludGVycHJldGF0aW9uIG9mIEwxL0wyIFJlZ3VsYXJpemF0aW9uCgpJZiB3ZSB0b29rIGEgQmF5ZXNpYW4gYXBwcm9hY2ggb24gdGhlIHdlaWdodCBlc3RpbWF0aW9uLAp0aGUgdGFzayBpcyB0byBzb2x2ZSBmb3IgdGhlIHBvc3RlcmlvciBkaXN0cmlidXRpb24gb2Ygd2VpZ2h0OgoKJCQKUChXfHkpID0gXGZyYWN7UCh5fFcpIFxjZG90IFAoVyl9e1AoeSl9Clxwcm9wdG8gClAoeXxXKSBcY2RvdCBQKFcpLgokJAoKdGhlbiBhIE5vcm1hbCBwcmlvciBvbiB0aGUgd2VpZ2h0ICRXIFxzaW0gXG1ib3h7Tm9ybWFsfSQgd2lsbCByZXN1bHQgaW4gYSBtYXhpbXVtIGEgcG9zdGVyaW9yaSBlc3RpbWF0b3IgdG8gaGF2ZSBhIHRhcmdldCBmdW5jdGlvbiB3aXRoIHRoZSBMMiByZWd1bGFyaXphdGlvbiB0ZXJtLAphbmQgYSBbTGFwbGFjZWFuXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MYXBsYWNlX2Rpc3RyaWJ1dGlvbikgcHJpb3IgJFcgXHNpbSBcbWJveHtMYXBsYWNlfSQgd2lsbCByZXN1bHQgaW4gdGhlIEwxIHRlcm0uCgpUaGlzIGlzIHF1aXRlIHN0cmFpZ2h0Zm9yd2FyZCBpZiB3ZSB0YWtlIGEgbG9vayBhdCB0aGUgcHJvYmFiaWxpdHkgZGVuc2l0eSBmdW5jdGlvbiBvZiB0aGVzZSB0d28gZGlzdHJpYnV0aW9uczoKCiQkClxiZWdpbnthbGlnbmVkfQpcbWJveHtOb3JtYWx9KFxtdSwgXHNpZ21hKV9cdGV4dHtwZGZ9ICY9IGYodykgPQpcZnJhY3sxfXtcc3FydHsyXHBpXHNpZ21hXjJ9fWVeey1cZnJhY3sodyAtIFxtdSleMn17MlxzaWdtYV4yfX0sIFxcClxtYm94e0xhcGxhY2V9KFxtdSwgYilfXHRleHR7cGRmfSAmPSBnKHcpID0KXGZyYWN7MX17MmJ9ZV57LVxmcmFje1x2ZXJ0IHcgLSBcbXVcdmVydH17Yn19LgpcZW5ke2FsaWduZWR9CiQkCgpOb3cgaWYgd2Ugd2FudCB0byBtYXhpbWl6ZSB0aGUgbG9nIG9mICRQKFd8eSkkIChtYXhpbXVtIGEgcG9zdGVyaW9yaSksCnRoZSBncmFkaWVudCB0byB0aGUgbG9nIG9mIGEgTm9ybWFsIHByaW9yIHdpbGwgZ2l2ZSB1czoKCiQkClxiZWdpbnthbGlnbmVkfQpcZnJhY3tccGFydGlhbCBcbG4gZih3KX17XHBhcnRpYWwgd30gCiY9IFxmcmFje1xwYXJ0aWFsIFxiaWdnWy1cZnJhY3sxfXsyfVxsbigyXHBpXHNpZ21hXjIpIC0gXGZyYWN7KHcgLSBcbXUpXjJ9ezJcc2lnbWFeMn1cYmlnZ119e1xwYXJ0aWFsIHd9IFxcCiY9IC0gXGZyYWN7MX17XHNpZ21hXjJ9KHcgLSBcbXUpLApcZW5ke2FsaWduZWR9CiQkCgphbmQgZm9yIGEgTGFwbGFjZWFuIHByaW9yOgoKJCQKXGJlZ2lue2FsaWduZWR9ClxmcmFje1xwYXJ0aWFsIFxsbiBnKHcpfXtccGFydGlhbCB3fSAKJj0gXGZyYWN7XHBhcnRpYWwgXGJpZ2dbLVxmcmFjezF9ezJifSAtIFxmcmFje1x2ZXJ0IHcgLSBcbXUgXHZlcnR9e2J9XGJpZ2ddfXtccGFydGlhbCB3fSBcXAomPSAtIFxmcmFjezF9e2J9XGZyYWN7d317XHZlcnQgdyBcdmVydH0uClxlbmR7YWxpZ25lZH0KJCQKClRoZSByZWd1bGFyaXphdGlvbiBjb2VmZmljaWVudCB3ZSBwcmV2aW91c2x5IGRpc2N1c3NlZCAoaHlwZXItcGFyYW1ldGVyICRcbGFtYmRhJCkgY2FuIGJlIG1hcHBlZCB0byB0aGUgY29uc3RhbnQgb24gdGhlIChyZWNpcHJvY2FsIG9mKSBzY2FsZSBwYXJhbWV0ZXIgb2YgdGhlIEJheWVzaWFuIHByaW9yLgpBIGxhcmdlciAkXGxhbWJkYSQgY29ycmVzcG9uZHMgdG8gYSBzbWFsbGVyIHZhcmlhbmNlIG9uIHRoZSBwcmlvciwKaGVuY2UgdGhlIHNpemUgb2Ygd2VpZ2h0IGlzIGJlaW5nIGNvbnN0cmFpbmVkIG1vcmUgaW4gdGhlIHBvc3RlcmlvciBlc3RpbWF0aW9uLApyZXN1bHRpbmcgaW4gc21hbGxlciB3ZWlnaHQuCgpXZSBjYW4gcGxvdCB0aGUgc3RhbmRhcmRpemVkIE5vcm1hbCBhbmQgTGFwbGFjZSBkaXN0cmlidXRpb24gdG8gY2xlYXJseSBzZWUgdGhlaXIgZGlmZmVyZW5jZToKCmBgYHtweXRob24gYmF5ZXNpYW5fcHJpb3JfcGxvdH0KZnJvbSBzY2lweS5zdGF0cyBpbXBvcnQgbGFwbGFjZSwgbm9ybQoKeHRpY2tzID0gbnAubGluc3BhY2UoLTUsIDUsIG51bT0xMDAwKQoKb2ZpbGUgPSAicGxvdHMvcmVnX3ByaW9yLmh0bWwiCnAgPSBwbG90X29mZmxpbmUoCiAgb2ZpbGU9b2ZpbGUsCiAgZGF0YT1bZ28uU2NhdHRlcih4PXh0aWNrcywgeT1sYXBsYWNlLnBkZih4dGlja3MpLCBuYW1lPSJMYXBsYWNlYW4gUHJpb3IgKEwxKSIpLAogICAgICAgIGdvLlNjYXR0ZXIoeD14dGlja3MsIHk9bm9ybS5wZGYoeHRpY2tzKSwgbmFtZT0iTm9ybWFsIFByaW9yIChMMikiKV0sCiAgbGF5b3V0PWdvLkxheW91dCgKICAgIHRpdGxlPSJCYXllc2lhbiBQcmlvciBvbiBNb2RlbCBXZWlnaHQiLAogICAgeGF4aXM9ZGljdCh0aXRsZT0iTW9kZWwgV2VpZ2h0IiksCiAgICB5YXhpcz1kaWN0KHRpdGxlPSJEZW5zaXR5IikpKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpodG1sdG9vbHM6OmluY2x1ZGVIVE1MKHB5JG9maWxlKQpgYGAKCkNvbXBhcmVkIHRvIGEgTm9ybWFsIHByaW9yLAphIExhcGxhY2VhbiBwdXQgbXVjaCBtb3JlIGRlbnNpdHkgYXQgdGhlIGNlbnRlci4KV2hlbiBib3RoIGRpc3RyaWJ1dGlvbiBpcyBzdGFuZGFyZGl6ZWQgKHdpdGggbG9jYXRpb24gYXQgMCBhbmQgc2NhbGUgYXQgMSkgYXMganVzdCBwbG90dGVkIGFib3ZlLAp0aGlzIGNhbiBiZSBpbnRlcnByZXRlZCBhcyB0aGF0IGEgTGFwbGFjZWFuIHByaW9yIGhhcyBhIHN0cm9uZ2VyIHByZWZlcmVuY2UgZm9yIGEgc3BhcnNlIHNvbHV0aW9uICh3ZWlnaHQgPSAwKS4KCiMjIERyb3BvdXQKCkFub3RoZXIgcHJhY3RpY2FsIGFuZCBtYXliZSBhbHNvIGVhc2llciB3YXkgb2YgcmVndWxhcml6YXRpb24gaXMgdGhlIGRyb3BvdXQgbWVjaGFuaXNtIChAc3JpdmFzdGF2YTIwMTRkcm9wb3V0KS4KSXQgaXMgZmlyc3QgaW50cm9kdWNlZCBpbiBhIG5ldXJhbCBuZXR3b3JrIG1vZGVsIGJ1dCB0aGUgY29uY2VwdCBoYXMgYmVlbiBzdWNjZXNzZnVsbHkgYXBwbGllZCBhcyB3ZWxsIHRvIHNvbWUgb3RoZXIgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIChzdWNoIGFzIGdyYWRpZW50IGJvb3N0aW5nIHRyZWVzKS4KCkRyb3BvdXQgb2YgYSBuZXVyb24gbWVhbnMgdGhhdCBpdCBpcyBpZ25vcmVkIGluIHRoZSBiYWNrcHJvcCB0cmFpbmluZyBwcm9jZXNzLgpUYWtlIG91ciBwcmV2aW91cyBzaW1wbGUgbmV1cmFsIG5ldHdvcmsgbW9kZWwgYXMgZXhhbXBsZSwKdGhlcmUgYXJlIHR3byBuZXVyb24gaW4gdGhlIGhpZGRlbiBsYXllciwKaWYgd2UgZHJvcG91dCB0aGUgZmlyc3QgdW5pdCB0aGVuIHRoZSBtb2RlbCB0ZW1wb3JhcmlseSBiZWNvbWVzOgoKPGRpdiBjbGFzcz0iZm9sZCBzIj4KYGBge3Igbm5fZHJvcG91dF9kaWFncmFtfQojIERyYXcgYSBzaW1wbGUgbmV1cmFsIG5ldHMuCkRpYWdyYW1tZVI6OmdyVml6KCIKZGlncmFwaCBzdWJzY3JpcHQgewoKICBncmFwaCBbbGF5b3V0ID0gZG90IHJhbmtkaXIgPSBMUiBvcmRlcmluZyA9IGluIHJhbmtzZXA9MiBzcGxpbmVzPXRydWVdCgogIG5vZGUgW3NoYXBlID0gY2lyY2xlXQoKICBzdWJncmFwaCBjbHVzdGVyX2lucHV0X2xheWVyIHsKICAgIGxhYmVsID0gJ0lucHV0IExheWVyJwogICAgeDEgW2xhYmVsID0gJ3hAX3sxfSddCiAgICB4MiBbbGFiZWwgPSAneEBfezJ9J10KICAgIHgzIFtsYWJlbCA9ICd4QF97M30nXQogIH0KCiAgc3ViZ3JhcGggY2x1c3Rlcl9oaWRkZW5fbGF5ZXIgewogICAgbGFiZWwgPSAnSGlkZGVuIExheWVyJwogICAgbjExIFtsYWJlbCA9ICd5QF97MTF9JyBjb2xvciA9IGdyZXkgZm9udGNvbG9yID0gZ3JleV0KICAgIG4xMiBbbGFiZWwgPSAneUBfezEyfScgY29sb3IgPSBkYXJrZ3JlZW4gZm9udGNvbG9yID0gZGFya2dyZWVuXQogIH0KCiAgc3ViZ3JhcGggY2x1c3Rlcl9vdXRwdXRfbGF5ZXIgewogICAgbGFiZWwgPSAnT3V0cHV0IExheWVyJwogICAgbjIxIFtsYWJlbCA9ICd5QF97Mn0nXQogIH0KCiAgZWRnZSBbYXJyb3dzaXplID0gLjI1XQoKICB4MSAtPiBuMTIgW2xhYmVsID0gJ3dAX3sxMTJ9JyBjb2xvciA9IGRhcmtncmVlbiBmb250Y29sb3IgPSBkYXJrZ3JlZW5dCiAgeDIgLT4gbjEyIFtsYWJlbCA9ICd3QF97MjEyfScgY29sb3IgPSBkYXJrZ3JlZW4gZm9udGNvbG9yID0gZGFya2dyZWVuXQogIHgzIC0+IG4xMiBbbGFiZWwgPSAnd0BfezMxMn0nIGNvbG9yID0gZGFya2dyZWVuIGZvbnRjb2xvciA9IGRhcmtncmVlbl0KCiAgbjEyIC0+IG4yMSBbbGFiZWwgPSAnd0BfezEyMjF9J10KCn0iKQpgYGAKPC9kaXY+CgpXaGVuIGEgbmV1cm9uIGlzIGRyb3BwZWQgb3V0LAppdHMgc3Vic2VxdWVudCBjb25uZWN0aW9uIHRvIHRoZSBuZXR3b3JrIGFyZSBhbGwgcmVtb3ZlZC4KVGhpcyBtZWFucyB0aGF0IGJvdGggZmVlZC1mb3J3YXJkIGFuZCBiYWNrcHJvcGFnYXRpb24gd2lsbCBpZ25vcmUgdGhlIGRpc2NhcmRlZCBjb25uZWN0aW9uLgoKVXN1YWxseSBkcm9wb3V0IGlzIGFwcGxpZWQgdG8gYSBtb2RlbCBieSBzZXR0aW5nIHVwIGEgKmRyb3BvdXQgcmF0ZSogdG8gb25lIG9yIG1vcmUgbGF5ZXJzLgpGb3IgZXhhbXBsZSB3ZSBjYW4gc2V0IHRoZSBkcm9wb3V0IHJhdGUgdG8gNTAlIGF0IHRoZSBmaXJzdCBoaWRkZW4gbGF5ZXIuClRoaXMgbWVhbnMgdGhhdCBmb3IgZWFjaCBiYXRjaCBncmFkaWVudCB1cGRhdGUgZXZlcnkgbmV1cm9uIGluIHRoZSBsYXllciB3aWxsIGhhdmUgYSA1MCUgY2hhbmNlIG9mIGJlaW5nIGlnbm9yZWQgYmVmb3JlIGEgdHJhaW5pbmcgc3RlcC4KCkRyb3BvdXQgZG9lc24ndCBsaW1pdCB0byBoaWRkZW4gbGF5ZXIgb25seS4KT25lIGNhbiBhbHNvIGFwcGx5IGRyb3BvdXQgYXQgdGhlIGlucHV0IGxheWVyLApidXQgdXN1YWxseSBhdCBhIHJlbGF0aXZlbHkgbG93ZXIgZHJvcCByYXRlIChjb21tb25seSAyMCUpIHRoYW4gaGlkZGVuIGxheWVyLgpDZXJ0YWlubHkgZHJvcG91dCB3aWxsIG5ldmVyIGJlIGFwcGxpZWQgYXQgYSBvdXRwdXQgbGF5ZXIgYmVjYXVzZSBvdXRwdXQgbGF5ZXIgaXMgdGhlIHJlcXVpcmVkIGNvbXBvbmVudCB0byB3ZWxsIGRlZmluZSBhIG1vZGVsLgoKQWZ0ZXIgZHJvcG91dCBpcyBhcHBsaWVkLAphIG5ldXJhbCBuZXR3b3JrIG1vZGVsIGJlY29tZXMgdGhpbm5lciBpbiBhIHNlbnNlIHRoYXQgaXQgaGFzIGZld2VyIG51bWJlciBvZiB3ZWlnaHRzIHRvIHJlc3BvbnNlIHRvIGJvdGggaW5wdXQgZmVlZCBmb3J3YXJkIGFuZCBlcnJvciBiYWNrcHJvcC4KVGhlIHJhdGlvbmFsZSBpcyB0aGF0IHdlJ2QgbGlrZSB0byBwcmV2ZW50IGFsbCB0aGUgbmV1cm9ucyBmcm9tIGNvLWFkYXB0aW5nIHRvbyBtdWNoIGluIHRoZSB0cmFpbmluZyBwcm9jZXNzIHRvIGF2b2lkIG92ZXJmaXR0aW5nIHRvIHRoZSB0cmFpbmluZyBkYXRhLgoKIyMjIFNhbXBsaW5nIEVuc2VtYmxlIHdpdGggU2hhcmVkIFdlaWdodHMKCkNvbnNpZGVyIGEgbmV1cmFsIG5ldHdvcmsgd2l0aCAkbiQgbmV1cm9ucy4KSWYgZWFjaCBuZXVyb24gY2FuIGJlIGVpdGhlciBtYXNrZWQgb3Igbm90IG1hc2tlZCwKdGhlcmUgd2lsbCBiZSB0aGVvcmV0aWNhbGx5IGluIHRvdGFsICQyXm4kIHN1Ym5ldHMuCkluIHRoaXMgdmlldyB0cmFpbmluZyBhIG5ldXJhbCBuZXR3b3JrIHdpdGggZHJvcG91dCBjYW4gYmUgdGhvdWdodCBvZiBhcyBlbnNlbWJsaW5nIGRpZmZlcmVudCBzdWJuZXRzIGJ5IHJhbmRvbSBzYW1wbGluZyBhIHN1Ym5ldCBhdCBlYWNoIHN0ZXAuClRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHVzdWFsIFtiYWdnaW5nXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Cb290c3RyYXBfYWdncmVnYXRpbmcpIHRlY2huaXF1ZSBhbmQgZHJvcG91dCBpcyB0aGF0IHRoZSBsYXR0ZXIgaGFzIGFsbCB0aGUgbW9kZWxzIHNoYXJlIHRoZWlyIHdlaWdodHMuCgpNb2RlbCBlbnNlbWJsZSBpbiBnZW5lcmFsIG91dHBlcmZvcm1zIGFueSBzaW5nbGUgbW9kZWwuClNpbmNlIGRyb3BvdXQgaXMgYSBmb3JtIG9mIGFwcHJveGltYXRlZCBlbnNlbWJsZSBmcm9tIGEgaHVnZSBhbW91bnQgKCQyXm4kKSBvZiBzdWJuZXQgbW9kZWxzLAppdCBtYXkgd2VsbCBvdXRwZXJmb3JtcyB0aGUgc2luZ2xlIG5ldXJhbCBuZXR3b3JrIHdpdGggJG4kIG5ldXJvbnMuCgojIyMgV2VpZ2h0IFNjYWxpbmcgb24gSW5mZXJlbmNlCgpUaGVyZSBpcyBvbmUgc2lkZS1lZmZlY3Qgd2hlbiB1c2luZyBkcm9wb3V0IHRvIHRyYWluIGEgbmV1cmFsIG5ldHdvcmsuCldoZW4gYSB0cmFpbmVkIG1vZGVsIGlzIHB1dCB0byBhbiBpbmZlcmVuY2UgdGFzaywKb3V0cHV0IGZyb20gdGhlIHNpbmdsZSBuZXVyYWwgbmV0d29yayAod2l0aCBhbGwgbmV1cm9ucyB1bm1hc2tlZCkgbm8gbG9uZ2VyIHJlcHJlc2VudHMgdGhlIGRpc3RyaWJ1dGlvbiBpbiB0aGUgdHJhaW5pbmcgdW5sZXNzIHdlIHVzZSBleGFjdGx5IHRoZSBzYW1lIHNldCBvZiBzdWJuZXRzIHRvIGRvIHRoZSBwcmVkaWN0aW9uIGFuZCBhdmVyYWdlIHRoZW0gdG9nZXRoZXIuCkJ1dCB0aGlzIGlzIG5vdCBwcmFjdGljYWwuCgpUbyBzb2x2ZSB0aGlzIHByb2JsZW0sCndlIHN0aWxsIHVzZSBvbmx5IHRoZSBvcmlnaW5hbCBuZXVyYWwgbmV0d29ya3MgKHdpdGggYWxsIG5ldXJvbnMgdW5tYXNrZWQpIHRvIGZlZWQgZm9yd2FyZCBmb3IgcHJlZGljdGlvbiwKYnV0IGV2ZXJ5IG1vZGVsIHdlaWdodCBpcyBzY2FsZWQgZG93biBieSB0aGUgZHJvcG91dCByYXRlIG9mIHRoZSBjb3JyZXNwb25kaW5nIG5ldXJvbi4KSXQgaXMgcHJvdmVuIGJ5IG1hbnkgZW1waXJpY2FsIHdvcmtzIHRoYXQgdGhpcyB3ZWlnaHQgc2NhbGluZyB0ZWNobmlxdWUgY2FuIGdpdmUgdmVyeSBnb29kIG1vZGVsaW5nIHJlc3VsdC4KUHJvYmFiaWxpc3RpY2FsbHksCnRoZSBtb2RlbCBvdXRwdXQgaGVyZSBpcyB0aGUgKmV4cGVjdGVkKiBvdXRwdXQgZnJvbSBhbGwgdGhlIHN1Ym5ldHMgZ2l2ZW4gdGhlIGRyb3BvdXQgc2NoZW1lLgoKIyMgT24gdGhlIEJpYXMgVGVybQoKSW4gYSBnZW5lcmFsIGFkZGl0aXZlIG1vZGVsIGEgYmlhcyB0ZXJtIGlzIGp1c3QgYSBmZWF0dXJlIHdpdGggYSBjb25zdGFudCB2YWx1ZS4KSW4gYSBuZXVyYWwgbmV0d29yayBtb2RlbCB3aGVuIGEgYmlhcyB0ZXJtIGlzIHVzZWQgaW4gYSBoaWRkZW4gbGF5ZXIsCml0IGhhcyBubyBjb25uZWN0aW9uIGJldHdlZW4gdGhlIHByZXZpb3VzIGxheWVyIGJ1dCBzaW1wbHkgYWN0IGFzIG9uZSBleHRyYSB1bml0IGxpbmtlZCB0byB0aGUgbmV4dCBsYXllci4KKFNlZSBvdXIgYnVpbHQtZnJvbS1zY3JhdGNoIHRveSBleGFtcGxlIGluIHRoZSBbcHJldmlvdXMgc2VjdGlvbl0oI2JhY2twcm9wYWdhdGlvbikuKQoKSXQgaXMgZWFzaWVyIHRvIHVuZGVyc3RhbmQgd2h5IHdlIG1heSB3YW50IGEgYmlhcyB0ZXJtIGluIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwuClNpbmNlIHdpdGhvdXQgYSBiaWFzIHRlcm0gd2UgYXJlIGVzc2VudGlhbGx5IHJlc3RyaWN0aW5nIHRoZSBmaXR0ZWQgbW9kZWwgdG8gcGFzcyB0aHJvdWdoIHRoZSBvcmlnaW4sCndoaWNoIGNhbiBiZSB1bnJlYWxpc3RpYyBpbiBtb3N0IHVzZSBjYXNlcy4KT24gdGhlIG90aGVyIGhhbmQsCmFkZGluZyBhIGJpYXMgdGVybSBkb2Vzbid0IHByZXZlbnQgZnJvbSB0aGF0IHNwZWNpYWwgc2NlbmFyaW8gdG8gb2NjdXIuCklmIHRoZSB0cnVlIG1vZGVsIGRvZXMgcGFzcyB0aHJvdWdoIHRoZSBvcmlnaW4sCmdpdmVuIGVub3VnaCBkYXRhIHRoZSBPTFMgZXN0aW1hdG9yIHNob3VsZCBiZSBhYmxlIHRvIGVzdGltYXRlIHRoZSB3ZWlnaHQgb24gYmlhcyB0byBiZSBpbnNpZ25pZmljYW50bHkgZGlmZmVyZW50IGZyb20gemVyby4KCkhlcmUgaXMgYSBzaW1wbGUgZXhhbXBsZSB0byBzaG93Y2FzZSBlc3RpbWF0aW9uIG9uIGEgdHJ1ZSBtb2RlbCB3aXRob3V0IGJpYXM6CgpgYGB7cHl0aG9uIGxpbmVhcl9yZWdfemVyb19iaWFzfQpucC5yYW5kb20uc2VlZCg3NzcpCk4gPSAxMDAwClggPSBucC5zdGFjayhbbnAub25lcyhOKSwgbnAucmFuZG9tLm5vcm1hbChzaXplPU4pXSwgYXhpcz0xKQpiZXRhID0gbnAuYXJyYXkoWzAsIDRdLCBkdHlwZT1ucC5mbG9hdDMyKSAgIyBUcnVlIG1vZGVsIHdlaWdodHMgd2hlcmUgYmlhcyBpcyB6ZXJvLgplID0gbnAucmFuZG9tLm5vcm1hbChzaXplPU4pCnkgPSBYLmRvdChiZXRhKSArIGUKCiMgV2UgdXNlIGBzdGF0c21vZGVsc2AgaGVyZSBiZWNhdXNlIGl0IGNvbWVzIHdpdGggYSBoeXBvdGhlc2lzIHRlc3Rpbmcgb24gdGhlIGNvZWZmaWNpZW50LgpwcmludChzbS5PTFMoeSwgWCkuZml0KCkuc3VtbWFyeTIoKSkKYGBgCgpBcyBvbmUgY2FuIHNlZSB0aGUgZXN0aW1hdGVkIHdlaWdodCBvbiBiaWFzIGlzIHN0YXRpc3RpY2FsbHkgaW5zaWduaWZpY2FudC4KClNob3VsZCByZWd1bGFyaXphdGlvbiBhcHBsaWVzIHRvIGJpYXMgdGVybT8KSW4gb3VyIHByZXZpb3VzIHdvcmtpbmcgZXhhbXBsZSB3ZSBhcHBseSBub3JtIHJlZ3VsYXJpemF0aW9uIG9uIGJvdGggYmlhcyBhbmQgd2VpZ2h0LgpQcmFjdGljYWxseSBzcGVha2luZyB0aGVyZSBtYXkgbm90IGJlIGFueSBiZW5lZml0IG9uIHJlZ3VsYXJpemluZyB0aGUgYmlhcyB0ZXJtLgpUaGUgcmF0aW9uYWxlIGlzIHRoYXQgYmlhcyBjb21lcyBmcm9tIGEgY29uc3RhbnQgZmVhdHVyZS4KU3VjaCBmZWF0dXJlIGlzIGluIHRoZW9yeSBub3QgcG9zc2libGUgdG8gb3ZlcmZpdCB0aGUgZGF0YSBzaW5jZSBpdCBpcyBub3QgdmFyeWluZyB3aXRoIHRoZSBkYXRhIGF0IGFsbC4KCkluZGVlZCwKcmVndWxhcml6YXRpb24gb24gYmlhcyB0ZXJtIG1heSBldmVuIGhhdmUgbmVnYXRpdmUgaW1wYWN0LgpUaGlzIGlzIGJlY2F1c2UgYnkgcmVndWxhcml6aW5nIGEgdGVybSB0aGF0IGlzIG5vdCBleHBvc2VkIHRvIGFueSByaXNrIG9mIG92ZXJmaXR0aW5nLAp0aGUgbW9kZWwgaXMgY29uc3RyYWluZWQgd2l0aG91dCBhbnkgYmVuZWZpdC4KV2Ugb25seSBlbmQgdXAgd2l0aCBhIGxlc3MgZmxleGlibGUgcGFyYW1ldGVyIHNwYWNlIHdoaWNoIGNvdWxkIGJlIHBvdGVudGlhbGx5IHN1Ym9wdGltYWwuCgpUbyBzdW0gdXAsCnRoZSBnZW5lcmFsIHN0cmF0ZWd5IG9mIHJlZ3VsYXJpemF0aW9uIGlzIE5PVCB0byByZWd1bGFyaXplIHRoZSBiaWFzIHRlcm0sCmlmIGFueS4KCiMgQmF0Y2ggTm9ybWFsaXphdGlvbgoKIyMgTm9ybWFsaXphdGlvbiBvbiBMaW5lYXIgTW9kZWxzCgpCZWZvcmUgd2UgdGFsayBhYm91dCBiYXRjaCBub3JtYWxpemF0aW9uLApsZXQncyBleGFtaW5lIHRoZSBpbXBhY3Qgb2YgZmVhdHVyZSBub3JtYWxpemF0aW9uIG9uIGEgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsLl5bVGhlIHRlcm0gbm9ybWFsaXphdGlvbiBpcyBpbmRlZWQgbm90IHdlbGwgZGVmaW5lZC4gSGVyZSB3aGF0IHdlIG1lYW4gaXMgYWN0dWFsbHkgc3RhbmRhcmRpemF0aW9uOiBieSB0cmFuc2Zvcm1pbmcgYSB2YXJpYWJsZSB0byBoYXZlIHplcm8gbWVhbiBhbmQgdW5pdCBzdGFuZGFyZCBkZXZpYXRpb24uIE90aGVyIGNvbW1vbiBub3JtYWxpemF0aW9uIGluY2x1ZGUgc2ltcGxlIGRlLW1lYW5lZCAoc3Vic3RyYWN0aW9uIGZyb20gbWVhbiksIGNvbXByZXNzaW9uIHRvIHRoZSByYW5nZSBvZiAkWzAsIDFdJCBvciAkWy0xLCAxXSQsIC4uLiwgZXRjLl0KCkdpdmVuIGEgc2ltcGxlIHVuaXZhcmlhdGUgcmVncmVzc2lvbiBtb2RlbDoKCiQkCnkgPSBhICsgYnggKyBcZXBzaWxvbi4KJCQKCldoYXQgaXMgdGhlIGltcGFjdCBpZiB3ZSBzdGFuZGFyZGl6ZSAkeCQ/Ckl0IG1lYW5zIHRoYXQgd2UgZXN0aW1hdGUgdGhlIGZvbGxvd2luZyBtb2RlbCBpbnN0ZWFkOgoKJCQKeSA9IGMgKyBkXGJpZ2coXGZyYWN7eCAtIFxtdV94fXtcc2lnbWFfeH1cYmlnZykgKyBcZXRhLgokJAoKTm93IHNpbXBseSBhIHJlLWFycmFuZ2VtZW50IG9mIHRoZSBtb2RlbCBmb3JtdWxhIGdpdmVzOgoKJCQKXGJlZ2lue2FsaWduZWR9CnkKJj0gYyArIGRcYmlnZyhcZnJhY3t4IC0gXG11X3h9e1xzaWdtYV94fVxiaWdnKSArIFxldGEgXFwKJj0gXHVuZGVyYnJhY2V7YyAtIFxmcmFje2RcbXVfeH17XHNpZ21hX3h9fV97YX0gKyBcdW5kZXJicmFjZXtcZnJhY3tkfXtcc2lnbWFfeH19X3tifVxjZG90IHggKyBcdW5kZXJicmFjZXtcZXRhfV97XGVwc2lsb259LgpcZW5ke2FsaWduZWR9CiQkCgpBcHBhcmVudGx5IGJ5IHVzaW5nIHN0YW5kYXJkaXplZCBmZWF0dXJlIHRoZSB3ZWlnaHRzIGFyZSBzaW1wbHkgbGluZWFybHkgc2NhbGVkLgpJdCB3b24ndCBoYXZlIGFueSBhbmFseXRpY2FsIGltcGFjdC4KCkhvd2V2ZXIgZm9yIGxhcmdlIHNjYWxlIGFwcGxpY2F0aW9uIHdlIGFyZSBub3Qgc29sdmluZyB0aGUgbW9kZWwgYW5hbHl0aWNhbGx5LApidXQgcmF0aGVyIG51bWVyaWNhbGx5LgpIZXJlIGNvbWVzIHRoZSBpbXBhY3Qgb2YgZmVhdHVyZSBub3JtYWxpemF0aW9uLAplc3BlY2lhbGx5IGZvciBmaXJzdC1vcmRlciBvcHRpbWl6ZXIgbGlrZSBncmFkaWVudCBkZXNjZW50LgpJZiB0aGVyZSBpcyBhbnkgZmVhdHVyZSB3aXRoIG91dGx5aW5nIHNjYWxlIGNvbXBhcmVkIHRvIHRoZSBvdGhlcnMsCnRoZSBwZXJmb3JtYW5jZSBvZiBncmFkaWVudCBkZXNjZW50IGlzIGh1Z2VseSBkYW1hZ2VkLgoKTGV0J3MgZXhhbWluZSB0aGlzIHVzaW5nIGEgd29ya2luZyBleGFtcGxlOgoKYGBge3B5dGhvbiBzY2FsZV9leGFtcGxlfQpucC5yYW5kb20uc2VlZCg3NzcpCk4gPSAxMDAwClggPSBucC5zdGFjayhbCiAgbnAub25lcyhOKSwKICBucC5yYW5kb20ubm9ybWFsKHNpemU9TiksCiAgbnAucmFuZG9tLm5vcm1hbChzaXplPU4sIGxvYz01MCwgc2NhbGU9MTApXSwgYXhpcz0xKQpiZXRhID0gbnAuYXJyYXkoWzYsIDQsIDNdLCBkdHlwZT1ucC5mbG9hdDMyKSAgIyBUcnVlIG1vZGVsIHdlaWdodHMuCmUgPSBucC5yYW5kb20ubm9ybWFsKHNpemU9TikKeSA9IFguZG90KGJldGEpICsgZQoKcHJpbnQoWFs6MTAsOl0pCmBgYAoKSGVyZSB3ZSBjcmVhdGUgYSBiaXZhcmlhdGUgbGluZWFyIG1vZGVsIHdoZXJlIHRoZSAybmQgZmVhdHVyZSBoYXMgYSBtdWNoIGxhcmdlciBzY2FsZSB0aGFuIHRoZSAxc3Qgb25lLgpUaGUgYW5hbHl0aWNhbCBPTFMgZXN0aW1hdG9yIGhhcyBubyBwcm9ibGVtIHNvbHZpbmcgdGhpcyBtb2RlbDoKCmBgYHtweXRob24gc2NhbGVfb2xzfQpwcmludChvbHMoWCwgeSkpCmBgYAoKT3VyIGJhdGNoIGdyYWRpZW50IGRlc2NlbnQgc29sdmVyLApob3dldmVyLApzdWZmZXIgYSBsb3Q6CgpgYGB7cHl0aG9uIHNjYWxlX2dkX2ZhaWxlZH0KcHJpbnQoZ2RfYmF0Y2hfb3B0aW1pemUoWCwgeSwgZ3JhZF9mdW5jLCBscj0wLjEsIG5fZXBvY2g9MTAsIGJhdGNoX3NpemU9NjQpWzBdKQpgYGAKCkhlcmUgZHVlIHRvIHRoZSBpbWJhbGFuY2VkIHNjYWxlIG9mIHRoZSBmZWF0dXJlcywKb3VyIHNvbHZlciBiZWNvbWVzIG51bWVyaWNhbGx5IHVuc3RhYmxlIGdpdmVuIGEgbW9kZXJhdGUgbGVhcm5pbmcgcmF0ZSAocmVzdWx0aW5nIGluIHRoZSAqZXhwbG9kaW5nIGdyYWRpZW50cyopLgpUaGlzIGlzIGJlY2F1c2UgdGhlIHNpemUgb2YgZ3JhZGllbnQgdXBkYXRlIGlzIGltcHJvcGVybHkgZG9taW5hdGVkIGJ5IGhpZ2gtc2NhbGVkIGZlYXR1cmUuCgpXZSBjYW4gc3RpbGwgbWFuYWdlIHRvIHNvbHZlIHRoZSBtb2RlbCwKYnV0IHdpdGggYSBtdWNoIGxvd2VyIGxlYXJuaW5nIHJhdGUgdG8gZG93bi1zY2FsZSB0aGUgaGlnaC1zY2FsZWQgZmVhdHVyZSwKYWNjb21wYW5pZWQgd2l0aCBhIGxhcmdlciBudW1iZXIgb2YgZXBvY2hzIHNpbmNlIG90aGVyIGxvdy1zY2FsZWQgZmVhdHVyZXMgbm93IGFyZSB1cGRhdGVkIGJ5IHRvbyBzbWFsbCBhIHN0ZXA6CgpgYGB7cHl0aG9uIHNjYWxlX2dkfQpmb3IgZXBvY2ggaW4gWzEwMCwgMTAwMCwgNTAwMCwgMTAwMDBdOgogIGIsIF8gPSBnZF9iYXRjaF9vcHRpbWl6ZShYLCB5LCBncmFkX2Z1bmMsIGxyPTAuMDAwMywgbl9lcG9jaD1lcG9jaCwgYmF0Y2hfc2l6ZT02NCkKICBwcmludCgiVHJhaW5pbmcgRXBvY2hzIHs6NX0gfCBFc3RpbWF0ZToge30iLmZvcm1hdChlcG9jaCwgYikpCmBgYAoKRXZlbiBmb3Igc3VjaCBhIHNpbXBsZSBsaW5lYXIgbW9kZWwgd2Ugbm93IG5lZWQgbXVjaCBtb3JlIHRyYWluaW5nIHN0ZXBzIHRvIGVuc3VyZSBjb252ZXJnZW5jZSwKbm90IHRvIG1lbnRpb24gYSBtb3JlIGNvbXBsaWNhdGVkIG5ldXJhbCBuZXR3b3JrIG1vZGVsLgoKQSBzZWNvbmQtb3JkZXIgb3B0aW1pemVyIGNhbiBtaXRpZ2F0ZSB0aGUgcHJvYmxlbS4KQnV0IGZvciB0cmFpbmluZyBsYXJnZSBzY2FsZSBuZXVyYWwgbmV0d29ya3Mgd2UgbXVzdCBzdGljayB0byBmaXJzdC1vcmRlciBvcHRpbWl6ZXIgZm9yIHRoZSBzYWNrIG9mIHBlcmZvcm1hbmNlLgpGZWF0dXJlIG5vcm1hbGl6YXRpb24gY29tZXMgdG8gdGhlIHJlc2N1ZS4KCkxldCdzIGV4cGVyaW1lbnQgd2l0aCBhIG5ldyBkZXNpZ24gbWF0cml4IHdoZXJlIGFsbCB0aGUgZmVhdHVyZXMgKGV4Y2x1ZGluZyB0aGUgYmlhcykgYXJlIG5vcm1hbGl6ZWQgdG8gaGF2ZSB6ZXJvIG1lYW5hbmQgdW5pdCBzdGFuZGFyZCBkZXZpYXRpb24uCgpgYGB7cHl0aG9uIHNjYWxlX3hfbm9ybWFsaXplZH0KWHMgPSBucC5jb2x1bW5fc3RhY2soW25wLm9uZXMoTiksIChYWzosMTpdIC0gWFs6LDE6XS5tZWFuKGF4aXM9MCkpIC8gWFs6LDE6XS5zdGQoYXhpcz0wKV0pCnByaW50KFhzWzoxMCw6XSkKYGBgCgpTaW5jZSBvdXIgZmVhdHVyZSBzY2FsZSBjaGFuZ2VzLAp0aGUgYW5hbHl0aWNhbCBzb2x1dGlvbiBmb3IgdGhlIHdlaWdodHMgY2hhbmdlcyBhcyB3ZWxsLgpMZXQncyBjaGVjayB0aGUgT0xTIGVzdGltYXRpb24gKHRoZSBjb3JyZWN0IHNvbHV0aW9uIGZvciBvdXIgcmVmZXJlbmNlKSBhZnRlciBub3JtYWxpemF0aW9uOgoKYGBge3B5dGhvbiBzY2FsZV94X25vcm1hbGl6ZWRfb2xzfQpwcmludChvbHMoWHMsIHkpKQpgYGAKCk5vdyBleGFtaW5lIG91ciBncmFkaWVudCBkZXNjZW50IHNvbHZlcjoKCmBgYHtweXRob24gc2NhbGVfeF9ub3JtYWxpemVkX2dkfQpmb3IgZXBvY2ggaW4gWzEsIDEwLCA1MCwgMTAwXToKICBiLCBfID0gZ2RfYmF0Y2hfb3B0aW1pemUoWHMsIHksIGdyYWRfZnVuYywgbHI9MC4xLCBuX2Vwb2NoPWVwb2NoLCBiYXRjaF9zaXplPTY0KQogIHByaW50KCJUcmFpbmluZyBFcG9jaHMgezo1fSB8IEVzdGltYXRlOiB7fSIuZm9ybWF0KGVwb2NoLCBiKSkKYGBgCgpCaW5nbyEKCk5vdyBpdCB0YWtlcyBubyBleHRyYSBlZmZvcnQgKGxlc3MgdGhhbiAxMCBlcG9jaHMpIGZvciBvdXIgbnVtZXJpY2FsIHNvbHZlciB0byBhcnJpdmUgYXQgYSBnb29kIGFwcHJveGltYXRpb24uClRoZSBvbmx5IGFkZGl0aW9uYWwgc3RlcCBpcyB0byByZW1lbWJlciB0byB0cmFuc2Zvcm0gYWxsIHRoZSBmZWF0dXJlcyBhbHNvIGJlZm9yZSBtb2RlbCBwcmVkaWN0aW9uIHNpbmNlIHRoZSBmZWF0dXJlIHNwYWNlIGlzIG5vdCBpbiB0aGUgb3JpZ2luYWwgZGltZW5zaW9uIGFueW1vcmUuCgojIyBOb3JtYWxpemF0aW9uIG9uIExvZ2lzdGljIE1vZGVscwoKV2hlbiBpdCBjb21lcyB0byBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24sCm5vcm1hbGl6YXRpb24gY2FuIGJlIGV2ZW4gbW9yZSBjcml0aWNhbCBzaW5jZSB0aGUgc2lnbW9pZCBmdW5jdGlvbiBjYW4gc3VmZmVyIGZyb20gc2F0dXJhdGluZyB3aGVuIHRoZSBpbnB1dCBpcyBlaXRoZXIgdG9vIHNtYWxsIG9yIHRvbyBiaWcuCgpSZW1lbWJlciB0aGF0IGEgc2lnbW9pZCBmdW5jdGlvbiBsb29rcyBsaWtlIFt0aGlzIHNoYXBlXSgjc2lnbW9pZF9wbG90KS4KVGhlIGdyYWRpZW50IGJlY29tZXMgbmVhcmx5IHplcm8gYXQgYm90aCBleHRyZW1lIHNpZGVzIG9mIHRoZSBpbnB1dCBzcGFjZS4KT3IgbWF0aGVtYXRpY2FsbHk6CgokJApcYmVnaW57YWxpZ25lZH0KeiAmPSBcZnJhY3sxfXsxICsgZV57LXR9fSwgXFwKXGZyYWN7ZHp9e2R0fSAmPSB6KDEgLSB6KSwgXFwKdCAgXHRvIFxpbmZ0eSAmIFxSaWdodGFycm93IGVeey10fSBcdG8gMCBcbWJveHsgYW5kIH0geiBcdG8gMSBcUmlnaHRhcnJvdyB6KDEteikgXHRvIDAsIFxcCnQgIFx0byAtIFxpbmZ0eSAmIFxSaWdodGFycm93IGVeey10fSBcdG8gXGluZnR5IFxtYm94eyBhbmQgfSB6IFx0byAwIFxSaWdodGFycm93IHooMS16KSBcdG8gMC4KXGVuZHthbGlnbmVkfQokJAoKSGVuY2UgYXMgdGhlIGZpdHRlZCB2YWx1ZSAkWFxiZXRhJCBnb2VzIHVuYm91bmRlZCwKdGhlIGdyYWRpZW50IGJlY29tZXMgemVybyBhbmQgdGhlIGdyYWRpZW50IGRlc2NlbnQgb3B0aW1pemF0aW9uIGdldCBzdHVjayBzaW5jZSBhbGwgdXBkYXRlcyB3aWxsIGJlIG11bHRpcGxpZWQgYnkgemVyby4KVGhpcyBpcyByZWZlcnJlZCB0byBhcyB0aGUgcHJvYmxlbSBvZiAqdmFuaXNoaW5nIGdyYWRpZW50cyouCgpGZWF0dXJlIG5vcm1hbGl6YXRpb24gaW4gdGhpcyBjYXNlIGFsc28gaGVscCBhdm9pZCB2YW5pc2hpbmcgZ3JhZGllbnRzIHNpbmNlIHRoZSBzaXplIG9mIHRoZSBmaXR0ZWQgdmFsdWVzIGFyZSB3ZWxsIGNvbnN0cmFpbmVkLgoKIyMgTm9ybWFsaXphdGlvbiBvbiBOZXVyYWwgTmV0d29ya3MKCkV4YWN0bHkgdGhlIHNhbWUgbG9naWMgY2FuIGFwcGx5IHRvIHRyYWluaW5nIGEgbmV1cmFsIG5ldHdvcmssCmVzcGVjaWFsbHkgd2hlbiBzaWdtb2lkIChvciBmdW5jdGlvbiB3aXRoIHNpbWlsYXIgcHJvcGVydHkgc3VjaCBhcyB0aGUgdGFuaCkgaXMgdXNlZCBhcyB0aGUgYWN0aXZhdGlvbiBmdW5jdGlvbiBpbiBhbnkgbGF5ZXIgb2YgdGhlIG5ldHdvcmsuClRoZSBvbmx5IHByb2JsZW0gaXMgd2hlbiB0aGUgYXBwbGljYXRpb24gaXMgdG9vIGh1Z2UgdG8gY29tcHV0ZSBub3JtYWxpemF0aW9uLgpTaW5jZSBub3JtYWxpemF0aW9uIHJlcXVpcmVzIHRoZSBlbnRpcmUgZGF0YXNldCB0byBjYWxjdWxhdGUgdGhlIHNjYWxlLAppdCBtYXkgYmUgcHJvaGliaXRpdmVseSBjb3N0bHkgdG8gZG8gc28uXltOb3RpY2UgdGhhdCB3ZSBuZWVkIHRvIG5vcm1hbGl6ZSBub3QganVzdCB0aGUgaW5wdXQgbGF5ZXIgYnV0ICpldmVyeSogaGlkZGVuIGxheWVycyBhcyB3ZWxsLl0KClRvIG92ZXJjb21lIHRoZSBjb21wdXRpbmcgY29zdCB0aGUgYmF0Y2ggbm9ybWFsaXphdGlvbiBhcHByb2FjaCBpcyBwcm9wb3NlZCAoQk4gaGVyZWFmdGVyKS4KSXQgc2ltcGx5IG5vcm1hbGl6ZXMgdGhlIGlucHV0IGF0IGJhdGNoIGxldmVsIHdoZW4gZG9pbmcgYmF0Y2ggZ3JhZGllbnQgZGVzY2VudC4KVGhpcyBpcyBkb25lIG9uIGEgcGVyLWxheWVyIGJhc2lzIHdoZW4gdHJhaW5pbmcgYSBuZXVyYWwgbmV0d29yay4KCiMjIyBUaGVvcmV0aWNhbCBHcm91bmQgb24gQmF0Y2ggTm9ybQoKSW5kZWVkIEJOIGV4aXN0cyBmb3IgbW9yZSB0aGFuIGp1c3QgdG8gbWl0aWdhdGUgbnVtZXJpY2FsIGlzc3Vlcy4KSW4gdGhlIG9yaWdpbmFsIHdvcmsgb2YgQGlvZmZlMjAxNWJhdGNoIEJOIGlzIHByb3Bvc2VkIHRvIG1haW5seSBkZWFsIHdpdGggYSBwcm9ibGVtIGNhbGxlZCAqaW50ZXJuYWwgY292YXJpYXRlIHNoaWZ0Ki4KU3VjaCBhIHBvaW50IG9mIHZpZXcsCmhvd2V2ZXIsCmhhcyBiZWVuIGhlYXZpbHkgY2hhbGxlbmdlZCBieSBzdWJzZXF1ZW50IHJlc2VhcmNoIHN1Y2ggYXMgaW4gQHNhbnR1cmthcjIwMThkb2VzLgoKVGhlIHN1Y2Nlc3Mgb2YgQk4gaXMgcHJvdmVuIGJ5IGl0cyBlbXBpcmljYWwgcmVzdWx0cyBidXQgbm90IGl0cyB0aGVvcmV0aWNhbCBzdGFuZCwKd2hpY2ggaXMgeWV0IHRvIGJlIGNsZWFyLgpUaGUgZGViYXRlIGlzIHN0aWxsIG9uZ29pbmcgc28gYXMgb2Ygbm93IHdlIHNob3VsZCBjb25zaWRlciBpdCBhcyBhIGdvb2QgcHJhY3RpY2UgdGhhdCBhbHdheXMgd29ydGhzIGV4cGVyaW1lbnRpbmcuCgojIyMgQmF0Y2ggTm9ybSBBZnRlciBBY3RpdmF0aW9uCgpTaG91bGQgd2UgQk4gdGhlIGlucHV0IGJlZm9yZSBvciBhZnRlciBhcHBseWluZyB0aGUgYWN0aXZhdG9yIGF0IGVhY2ggbGF5ZXI/ClRob3VnaCB0aGUgb3JpZ2luYWwgcGFwZXIgYXBwbGllcyBCTiBiZWZvcmUgdGhlIGFjdGl2YXRvciwKdGhlIGNvbW1vbiBwcmFjdGljZSBub3cgc2VlbXMgdG8gYmUgYXBwbHlpbmcgaXQgYWZ0ZXIgYWN0aXZhdG9yIHNpbmNlIHRoZSBlbXBpcmljYWwgcmVzdWx0cyBhcmUgYmV0dGVyLgoKQk4gYWZ0ZXIgYWN0aXZhdG9yIHNlZW1zIGFsc28gbW9yZSBpbnRlcnByZXRhYmxlIHNpbmNlIGl0IGlzIHRoZSBvdXRwdXQgYnV0IG5vdCB0aGUgaW5wdXQgb2YgdGhlIGFjdGl2YXRvciBiZWNvbWVzIHRoZSBpbnB1dCB0byB0aGUgbmV4dCBsYXllci4KQnkgYXBwbHlpbmcgQk4gYWZ0ZXIgYWN0aXZhdG9yLAp3ZSBhcmUgY29udHJvbGxpbmcgZWFjaCBpbnB1dCB0byBiZSBub3JtYWxpemVkIGp1c3QgYmVmb3JlIHRoZSBtdWx0aXBsaWNhdGlvbiBvZiB0aGUgd2VpZ2h0IG1hdHJpeCBpbiB0aGUgbmV4dCBsYXllci4KCkJ1dCBpZiB0aGUgc2lnbW9pZCBpcyB1c2VkIGFzIGFjdGl2YXRvciwKd291bGQgQk4gYWZ0ZXIgYWN0aXZhdGlvbiBkZWZlYXQgYW5vdGhlciBwdXJwb3NlIHdoaWNoIGlzIHRvIG1pdGlnYXRlIHZhbmlzaGluZyBncmFkaWVudHM/CklmIHRoZSBpbnB1dCBsYXllciBpcyBhbHNvIG5vcm1hbGl6ZWQsCnRoaXMgc2hvdWxkIGJlIGxlc3Mgb2YgYSBwcm9ibGVtLgpPZiBjb3Vyc2UgaWYgdGhlIGNob3NlbiBhY3RpdmF0b3IgaGFzIG5vIHNhdHVyYXRpbmcgcG9pbnQgKHN1Y2ggYXMgUmVMVSkgdGhpcyBpcyBhbHNvIG5vdCBhIHByb2JsZW0uCgojIyMgQmF0Y2ggTm9ybSBhdCBJbmZlcmVuY2UgVGltZQoKSG93IGRvIHdlIHJ1biBpbmZlcmVuY2UgYmFzZWQgb24gYSBtb2RlbCB0cmFpbmVkIHdpdGggQk4/CklmIHRoZSBpbnB1dCBkYXRhIGlzIGFsc28gYnkgYmF0Y2ggd2UgY2FuIGRpcmVjdGx5IGFwcGx5IHRoZSBzYW1lIG5ldHdvcmsgYXJjaGl0ZWN0dXJlLgpCdXQgbW9yZSBvZiB0aGUgdGltZSB0aGUgaW5mZXJlbmNlIGlzIGRvbmUgYnkgYSBzdHJlYW1pbmcgbWFubmVyIChvbmxpbmUgcHJlZGljdGluZykuCkluIG9yZGVyIHRvIEJOIHRoZSBkYXRhIHdlIG5lZWQgdG8ga2VlcCB0cmFjayBvZiB0aGUgZmVhdHVyZSBtZWFucyBhbmQgdmFyaWFuY2VzIHdoZW4gd2UgYXJlIGRvaW5nIG1vZGVsIHRyYWluaW5nIG9uIHRoZSBmbHkuCkEgY29tbW9uIHByYWN0aWNlIGlzIHRvIHVzZSBhIG1vdmluZyBhdmVyYWdlIG9mIGZlYXR1cmUgbWVhbnMgYW5kIHZhcmlhbmNlcyBhcyBhZGRpdGlvbmFsIHBhcmFtZXRlcnMgdXNlZCBpbiBtb2RlbCBpbmZlcmVuY2UgbW9kZS4KCiMgR3JhZGllbnQgQ2xpcHBpbmcKCkdyYWRpZW50IGNsaXBwaW5nIGlzIGEgdGVjaG5pcXVlIHRvIGhhbmRsZSBleHBsb2RpbmcgZ3JhZGllbnRzLgpTdWNoIHByb2JsZW0gY2FuIHJlc3VsdCBmcm9tIGltcHJvcGVyIGxlYXJuaW5nIHJhdGVzLAppbWJhbGFuY2VkIGZlYXR1cmUgc2NhbGVzLApvciBhIG1pcy1jb25maWd1cmVkIGxvc3MgZnVuY3Rpb24uClRoZSBnZW5lcmFsIGlkZWEgaXMgdG8gY29uc3RyYWluIHRoZSBjYWxjdWxhdGVkIGdyYWRpZW50cyBiZWZvcmUgYXBwbHlpbmcgYmFja3Byb3BhZ2F0aW9uIHVwZGF0ZS4KClRoZSBzaW1wbGVzdCB3YXkgb2YgZG9pbmcgdGhpcyBpcyB0byBqdXN0IHNldCBhIGxvd2VyIGFuZCB1cHBlciBib3VuZCBzbyB0aGF0IGFsbCBncmFkaWVudHMgYXJlIGJvdW5kZWQgYnkgdGhlIGxvd2VyIGFuZCBjYXBwZWQgYXQgdXBwZXIgYm91bmQuCgpBbm90aGVyIHdheSBvZiBkb2luZyB0aGF0IGlzIHRvIGNvbnN0cmFpbiB0aGUgTDIgbm9ybSBvZiBncmFkaWVudCB2ZWN0b3IgYnkgYSBnaXZlbiB2YWx1ZToKCmBgYHtweXRob24gY2xpcH0KZGVmIGNsaXAoZywgbm9ybSk6CiAgZ25vcm0gPSBucC5saW5hbGcubm9ybShnKQogIGlmIGdub3JtID4gbm9ybToKICAgIGdub3JtID0gZyAqIG5vcm0gLyBnbm9ybQogIHJldHVybiBnbm9ybQoKZGVmIGdkX2NsaXBfYmF0Y2hfb3B0aW1pemUoWCwgeSwgZ3JhZF9mdW5jLCBjbGlwX25vcm09NSwgbHI9LjAxLCBuX2Vwb2NoPTEwLCBiYXRjaF9zaXplPTY0KToKICBiID0gbnAucmFuZG9tLm5vcm1hbChzaXplPVguc2hhcGVbMV0pCiAgbCA9IFtsb3NzKFgsIHksIGIpXQogIGZvciBlcG9jaCBpbiByYW5nZShuX2Vwb2NoKToKICAgICMgU2h1ZmZsZSB0aGUgZGF0YXNldCBiZWZvcmUgZWFjaCBlcG9jaC4KICAgIHNpZCA9IG5wLnJhbmRvbS5wZXJtdXRhdGlvbihYLnNoYXBlWzBdKQogICAgWHMgPSBYW3NpZCw6XQogICAgeXMgPSB5W3NpZF0KICAgIGkgPSAwCiAgICBuX3N0ZXAgPSBpbnQobnAuY2VpbChYLnNoYXBlWzBdIC8gYmF0Y2hfc2l6ZSkpCiAgICBmb3Igc3RlcCBpbiByYW5nZShuX3N0ZXApOgogICAgICBYYiA9IFhzW2k6aStiYXRjaF9zaXplLDpdCiAgICAgIHliID0geXNbaTppK2JhdGNoX3NpemVdCiAgICAgIGIgLT0gbHIqY2xpcChncmFkX2Z1bmMoWGIsIHliLCBiKSwgY2xpcF9ub3JtKQogICAgICBsLmFwcGVuZChsb3NzKFhiLCB5YiwgYikpCiAgICAgIGkgKz0gYmF0Y2hfc2l6ZQogIHJldHVybiBiLCBsCmBgYAoKTGV0J3MgYWRkIHRoZSBjbGlwcGluZyB0byBvdXIgYmF0Y2ggZ3JhZGllbnQgZGVzY2VudCBvcHRpbWl6ZXIgaW4gb3VyIHByZXZpb3VzIGV4YW1wbGUgaW4gQk4gc2VjdGlvbiB3aGVyZSB0aGUgZ3JhZGllbnQgZ290IGV4cGxvZGVkIHdpdGggaW1iYWxhbmNlZCBzY2FsZWQgZmVhdHVyZXM6CgpgYGB7cHl0aG9uIGNsaXBfdW5zY2FsZWRfeH0KcHJpbnQoWFs6NSw6XSkKYGBgCgpgYGB7cHl0aG9uIGNsaXBfc2dkfQpmb3IgZXBvY2ggaW4gWzEwMDAsIDUwMDAsIDEwMDAwLCAyMDAwMCwgMzAwMDBdOgogIGIsIF8gPSBnZF9jbGlwX2JhdGNoX29wdGltaXplKFgsIHksIGdyYWRfZnVuYywgbHI9MC4xLCBuX2Vwb2NoPWVwb2NoLCBiYXRjaF9zaXplPTY0KQogIHByaW50KCJUcmFpbmluZyBFcG9jaHMgezo1fSB8IEVzdGltYXRlOiB7fSIuZm9ybWF0KGVwb2NoLCBiKSkKYGBgCgpXZSBoYXZlIHN1Y2Nlc3NmdWxseSBhdm9pZGVkIGV4cGxvZGluZyBncmFkaWVudHMgdGhpcyB0aW1lIHdpdGggZ3JhZGllbnQgY2xpcHBpbmcgYW5kIHdpdGhvdXQgbm9ybWFsaXphdGlvbiBvZiB0aGUgaW5wdXQgZmVhdHVyZS4KQnV0IHRoZSBub2lzZSBpbiB0aGUgZXN0aW1hdGlvbiBmb3IgaGlnaC1zY2FsZWQgZmVhdHVyZSBzZWVtcyB0byBiZSBiaWdnZXIgYXMgd2VsbC4KQWxzbyBncmFkaWVudCBjbGlwcGluZyB3b24ndCBoZWxwIHVzIGltcHJvdmUgdGhlIHNwZWVkIG9mIGNvbnZlcmdlbmNlLgpJdCBpcyBtZXJlbHkgYSB0ZWNobmlxdWUgdG8gdGFja2UgZG93biBudW1lcmljYWwgc3RhYmlsaXR5IHByb2JsZW0uXltJbiBgdGVuc29yZmxvd2AgdGhlIGZ1bmN0aW9uIGBjbGlwX2J5X3ZhbHVlYCBpbXBsZW1lbnRzIHRoZSBib3VuZGVkIG1ldGhvZCBhbmQgYGNsaXBfYnlfbm9ybWAgaW1wbGVtZW50cyBleGFjdGx5IHRoZSBMMiBub3JtIG1ldGhvZCB3ZSd2ZSBpbXBsZW1lbnRlZCBoZXJlLl0KCiMgUmVmZXJlbmNlcwo=