Data Exploration and Decomposition

Time series components

A time series is a combination of systematic and random (noise) components. Only the systematic part can be forecasted.

random (noise) components

Noise is a series of independently distributed normal sequences: the sequence is non-correlationary, non-trend, and random. It obeys the normal distribution with mean of 0 and a variance of \(\sigma^2\).

Generating a Noise Series in R

  • set.seed(): Sets random number generator seed.

  • ts(rnorm(50))Creates a time series variable: 50 observations that are random and normally distributed Plots the time series.

set.seed(000929)
y <- ts(rnorm(50))
plot(y)

Systematic Time Series Components

  • Level: if a time series lacks trend/cycle then it fluctuates around a level. The level can be described as part of the trend component.

  • Trend: is the long term increase or decrease in the data.

  • Cycle: is a long term up and down movements - not of fixed period.

  • Season: is a repeating pattern of fixed period.

Irregular Components

  • Outliers: one-period shift from the expected value.

  • Level Shift: multi-period shifts from the expected value.

Different Decomposition Model Forms

Pure Additive Model:

\[Y_t=T_t+S_t+\epsilon_t\]

Pure Multiplicative Model:

\[Y_t=T_t*S_t*\epsilon_t\]

Mixed Multiplicative Model:

\[Y_t=T_t*S_t+\epsilon_t\]

Time Series Practice

For this exercise, you will have to load the packages “tsutils” and “forecast”. Go to the Packages tab on the right and tick on the checkbox next to “tsutils”, and also for the one next to “forecast”. Alternatively, you can also type the following statement:

library(tsutils)
library(forecast)

The package is now loaded, and it is time to load the data. Open the Notepad file called “Sales.txt”.

data <- read.table("./Sales.txt")

Given that the data is a time series, it would be useful to convert the data into a time series format. A time series format requires a seasonal frequency. In this case, since the data is monthly, the frequency would be 12. In order to do so, enter the statement:

data <- ts(data, frequency = 12, start = c(2016,1))

The above statement has converted the variable data into a time series. The frequency=12 specifies the seasonal frequency, while the start=c(2016,1) specifies that the first entry occurs on January 2016. The start is not a compulsory argument as it helps in organizing the data; however the frequency is!

If you wanted to inspect the seasonal frequency of your series, just type:

frequency(data)
[1] 12

The first task consisted of plotting the time series. This is done by using the plot() function as follows:

plot(data)

Plotting in R

For the ts format, when entering the plot() function, the output was a line graph. However for other formats the output is a scatterplot. In order to specify that you need a line graph, the following should be inserted:

# Create data
data1 <- 1:20
# Plot
plot(data1, type = "l")

  • By adding the type="l" argument, the plot will now be a line. Furthermore if you wanted to colour it, then another argument should be added as such:
plot(data1, type = "l", col = "blue")

Centred Moving Average

In order to create a CMA, the function cmav() from the “tsutils” package is used. To test this, create a variable that will have the CMA values stored in it as such:

data_cma <- cmav(data, ma = 12, fill = FALSE)
  • The first argument passed is which series is being used, which in your case is the variable called data.

  • The ma() argument specifies the length of the CMA. If this is omitted, then the default value is the seasonal frequency.

  • The fill=FALSE argument means that the first and last observations that were expected to be left blank will be left blank.You will see that the first values of the “data cma” variable are NA, since they don’t exist. If you wanted to impute values on these observations, then use the fill=TRUE argument.

If you wanted to overlay the graphs, this is how it is done:

#Plot the original time series in blue 
plot(data, col="blue")
#Plot the CMA(12) in red 
lines(data_cma, col = "red")

  • By adding the lines() statement, you can overlay a line graph on your original time series.

  • If you were to use the plot() statement, then a new graph will be plotted instead.

Seasonal Plots

In the “tsutils” package, a function has been created that will automatically calculate the seasonal indices and profile, and will display the seasonal plot. Just type:

seasplot(data)
Results of statistical testing
Evidence of trend: TRUE  (pval: 0)
Evidence of seasonality: TRUE  (pval: 0)

If you wished to stored the seasonal indices in a variable, say “data seas index”, then type:

data_seas_index <- seasplot(data)$season

data_seas_index
           1         2         3         4         5        6        7        8
s1 0.8993358 0.8708480 0.9894718 0.9700460 0.9785901 1.142859 1.253256 1.220492
s2 0.9045226 0.8526912 0.9954561 0.9629886 0.9739369 1.149342 1.258599 1.258054
s3 0.9060626 0.8414553 0.9435274 0.8961373 0.9330620 1.117414 1.258437 1.288265
s4 0.8761789 0.8231047 0.9758638 0.9511609 0.9988109 1.109283 1.272323 1.284688
s5 0.9138057 0.8474668 0.9006717 0.9822443 0.9984135 1.126217 1.303067 1.263477
          9        10        11        12
s1 1.061418 0.9065547 0.7957910 0.8893194
s2 1.085535 0.9317521 0.8182428 0.8992974
s3 1.276488 0.9005017 0.7691512 0.8280946
s4 1.057782 0.9229897 0.8119626 0.8987517
s5 1.054112 0.9520553 0.8016295 0.8837926

Decomposition

The decomp() function in the “tsutils” package can automatically decompose the trend, seasonal and irregular parts of a time series. Create a variable named “decomposition”:

decomposition <- decomp(data,decomposition = "multiplicative",outplot = TRUE)

  • The first argument is which series to decompose, which is the “data” series.

  • The decomposition argument specifies whether the decomposition should be additive or multiplicative. So if you wanted to try with an additive counterpart then alter the statement to: decomposition = "additive".

  • The outplot=TRUE specifies whether the plots should be displayed or not.

In order to see what it consists of, you need the str() function. This function will display the structure of the variable.

str(decomposition)
List of 5
 $ trend    : Time-Series [1:60, 1] from 2016 to 2021: 316 318 320 323 325 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : NULL
  .. ..$ : chr "V1"
 $ season   : Time-Series [1:60, 1] from 2016 to 2021: 0.9 0.847 0.961 0.953 0.977 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : NULL
  .. ..$ : chr "V1"
 $ irregular: Time-Series [1:60, 1] from 2016 to 2021: -0.204 7.55 9.122 5.656 0.659 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : NULL
  .. ..$ : chr "y"
 $ f.season : NULL
 $ g        : NULL

In order to extract for example the irregular (which is the noise component) component, then run the following command:

decomposition$irregular
              Jan          Feb          Mar          Apr          May          Jun
2016  -0.20378923   7.54956895   9.12218012   5.65649295   0.65882745   4.52782124
2017   1.58157428   1.96903835  12.32303062   3.78473752  -0.95710562   7.46037922
2018   2.28208399  -2.13819903  -6.70296331 -21.89349087 -16.92358199  -4.51925521
2019  -9.77974348  -9.97553701   6.18472104  -0.56392283   9.35538295  -8.39929725
2020   6.30861469   0.16314467 -28.06435549  13.95275954  10.32998166  -1.33297963
              Jul          Aug          Sep          Oct          Nov          Dec
2016  -5.23331338 -14.10396706 -15.26778851  -5.47355933  -1.21383462   3.25787958
2017  -3.89300666  -1.83441017  -8.01344051   3.34481258   7.04025884   7.26560985
2018  -4.17470558   9.90581088  66.89304049  -8.87791824 -12.17354977 -21.06276219
2019   1.37239090   9.43913510 -21.57245775   0.09657099   5.62069859   8.51707043
2020  16.19650458   0.23119971 -25.52008498  14.18006067   1.10636910   1.92658343

Check the Decomposition model

A question can arise when using the decomp() function:

  • Is the Multiplicative decomposition performed by this function a Pure Multiplicative Model or a Mixed Multiplicative Model?

In order to confirm this, isolate the Regular Components from the decomposition. So far, all the data is stored in the variable called “decomposition”. The first step would be to extract the regular components (Trend and Seasonality) and multiply them:

#Extract the Trend
decomposition$trend
          Jan      Feb      Mar      Apr      May      Jun      Jul      Aug      Sep
2016 315.7886 318.0808 320.3730 322.6651 324.9573 327.2495 329.5417 331.8333 334.4583
2017 348.2500 353.0000 357.6250 361.3750 364.5000 367.1667 369.4583 371.2083 372.1667
2018 375.2500 377.9167 383.6667 388.3333 389.0417 389.2917 390.1667 392.0000 394.8333
2019 410.8750 415.5000 416.0417 416.3333 420.5000 425.5000 430.7083 435.1250 437.7083
2020 456.3333 461.3750 465.2083 469.3333 472.7500 475.0417 477.3352 479.6287 481.9221
          Oct      Nov      Dec
2016 337.5417 340.5417 344.0833
2017 372.4167 372.7500 373.6250
2018 398.6667 403.0417 406.9583
2019 440.9583 445.8333 450.6250
2020 484.2156 486.5090 488.8025
#Extract the Seasonality
decomposition$season
           Jan       Feb       Mar       Apr       May       Jun       Jul       Aug
2016 0.8999811 0.8471132 0.9609982 0.9525154 0.9765627 1.1290230 1.2691364 1.2629954
2017 0.8999811 0.8471132 0.9609982 0.9525154 0.9765627 1.1290230 1.2691364 1.2629954
2018 0.8999811 0.8471132 0.9609982 0.9525154 0.9765627 1.1290230 1.2691364 1.2629954
2019 0.8999811 0.8471132 0.9609982 0.9525154 0.9765627 1.1290230 1.2691364 1.2629954
2020 0.8999811 0.8471132 0.9609982 0.9525154 0.9765627 1.1290230 1.2691364 1.2629954
           Sep       Oct       Nov       Dec
2016 1.1070670 0.9227707 0.7993554 0.8798512
2017 1.1070670 0.9227707 0.7993554 0.8798512
2018 1.1070670 0.9227707 0.7993554 0.8798512
2019 1.1070670 0.9227707 0.7993554 0.8798512
2020 1.1070670 0.9227707 0.7993554 0.8798512
#Multiply the Trend and Seasonality
regular_components <- decomposition$trend * decomposition$season

This has now created a new variable called “regular components”, which is equal to \(Trend × Seasonality\).

Now that the Regular Component has been created, you can calculate the errors for the Mixed Multiplicative Model as:

mmm_errors <- data - regular_components

The last step is to compare these errors with the errors from the decomposition. Go to the console and type:

decomposition$irregular - mmm_errors
     Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
2016   0   0   0   0   0   0   0   0   0   0   0   0
2017   0   0   0   0   0   0   0   0   0   0   0   0
2018   0   0   0   0   0   0   0   0   0   0   0   0
2019   0   0   0   0   0   0   0   0   0   0   0   0
2020   0   0   0   0   0   0   0   0   0   0   0   0

You will see that the result is a vector of 0. This means that the Multiplicative option in this function is actually a Mixed Multiplicative Model.

Exponential Smoothing Methods with Level and Trend

This part aims at introducing data partitioning for forecasting methods fitting and assessment(Train Set and Test Set), the implementation of simple forecasting methods, exponential smoothing methods, as well as error metrics in order to compare these methods with each other (model selection).

The forecasting methods that will be used are:

The error measures that will be encountered are:

Loading the Data

The first step is to load the necessary libraries. For this session, we will use three libraries: forecast, smooth and tsutils.

4 time series only with Level and no trend or season are stored in that file, which are: ”Medium Noise”, ”High Noise”, ”Medium Noise with Level Shift”, ”High Noise with Level Shift”.

Level_Data <- read.csv("./Level_Data.csv")

In order to look at its structure, type:

str(Level_Data)
'data.frame':   48 obs. of  5 variables:
 $ Date                         : chr  "Jan-12" "Feb-12" "Mar-12" "Apr-12" ...
 $ Medium.Noise                 : num  100.8 91.5 102 96.6 95.5 ...
 $ High.Noise                   : num  106 116 110 90 109 ...
 $ Medium.Noise.with.Level.Shift: num  100.8 91.5 102 96.6 95.5 ...
 $ High.Noise.with.Level.Shift  : num  106 116 110 90 109 ...

For the remainder of this tutorial, you will work with one series. Pick the first series called Medium Noise and store it in a variable of type ts:

medium_noise <- ts(Level_Data[,2],frequency=12)
  • [,1] means that you are using the first column of the matrix Workshop3, which refers to the Medium Noise series.

Analysing the series

As usual, the first step is to visualise the series. Plot it:

plot(medium_noise)

The next step is to decompose it in order to check which components it consists of. Use either function decomp() from tsutils package or decompose() from stats package:

# plot the Additive Decomposition
decomp(medium_noise, decomposition="additive", outplot=TRUE)
$trend
        Jan       Feb       Mar       Apr       May       Jun       Jul       Aug
1  97.26088  97.41820  97.61484  97.86064  98.16789  98.55196  99.03208  99.63208
2 102.51708 101.99958 101.70875 101.75167 101.99333 102.44667 102.87083 102.65250
3  99.69292 100.12750 100.79250 101.04167 101.06292 100.55000 100.10375 100.08333
4 100.73500 100.76667 101.06500 101.37542 100.69833 100.55667 100.44319 100.35244
        Sep       Oct       Nov       Dec
1 100.93375 101.83458 102.31917 102.75792
2 101.13958  99.75625  99.63500  99.72042
3 100.45208 101.09167 101.03000 100.68625
4 100.27983 100.22175 100.17529 100.13812

$season
         Jan        Feb        Mar        Apr        May        Jun        Jul
1  3.6135296 -3.4154863  4.0197283 -2.7198472  0.7993813  0.3336774  2.2550366
2  3.6135296 -3.4154863  4.0197283 -2.7198472  0.7993813  0.3336774  2.2550366
3  3.6135296 -3.4154863  4.0197283 -2.7198472  0.7993813  0.3336774  2.2550366
4  3.6135296 -3.4154863  4.0197283 -2.7198472  0.7993813  0.3336774  2.2550366
         Aug        Sep        Oct        Nov        Dec
1 -1.0475880  0.5311874 -4.3735632  1.6201364  0.6393252
2 -1.0475880  0.5311874 -4.3735632  1.6201364  0.6393252
3 -1.0475880  0.5311874 -4.3735632  1.6201364  0.6393252
4 -1.0475880  0.5311874 -4.3735632  1.6201364  0.6393252

$irregular
           Jan          Feb          Mar          Apr          May          Jun
1  -0.07441132  -2.53270881   0.38543496   1.48920842  -3.42727266   0.85436557
2  -3.14061289   5.09590294  15.32152168   0.18818053   1.78728533  -1.55034408
3   1.92355377  -0.51201373 -12.59222832  -3.47181947   4.17770200   0.93632259
4   1.29147044  -2.05118040  -3.11472832   1.79443053  -2.53771467  -0.24034408
           Jul          Aug          Sep          Oct          Nov          Dec
1   7.07288007  -0.53449537  -3.18493739  -3.78102017   0.60069693  -5.21724188
2  -4.03586993  -8.70491204  -5.22077073   1.15731316   6.22486360   5.76025812
3  -2.51878660   5.54425463  -0.25327073   1.52189649   3.63986360  -6.32557521
4  -0.51822353   3.69515279   8.65897884   1.10181052 -10.46542414   5.78255897

$f.season
NULL

$g
NULL

# plot the Multiplicative Decomposition
decomp(medium_noise,decomposition="multiplicative", outplot=TRUE)
$trend
        Jan       Feb       Mar       Apr       May       Jun       Jul       Aug
1  97.26088  97.41820  97.61484  97.86064  98.16789  98.55196  99.03208  99.63208
2 102.51708 101.99958 101.70875 101.75167 101.99333 102.44667 102.87083 102.65250
3  99.69292 100.12750 100.79250 101.04167 101.06292 100.55000 100.10375 100.08333
4 100.73500 100.76667 101.06500 101.37542 100.69833 100.55667 100.44319 100.35244
        Sep       Oct       Nov       Dec
1 100.93375 101.83458 102.31917 102.75792
2 101.13958  99.75625  99.63500  99.72042
3 100.45208 101.09167 101.03000 100.68625
4 100.27983 100.22175 100.17529 100.13812

$season
        Jan       Feb       Mar       Apr       May       Jun       Jul       Aug
1 1.0363086 0.9654852 1.0397987 0.9730342 1.0076441 1.0034344 1.0228841 0.9901069
2 1.0363086 0.9654852 1.0397987 0.9730342 1.0076441 1.0034344 1.0228841 0.9901069
3 1.0363086 0.9654852 1.0397987 0.9730342 1.0076441 1.0034344 1.0228841 0.9901069
4 1.0363086 0.9654852 1.0397987 0.9730342 1.0076441 1.0034344 1.0228841 0.9901069
        Sep       Oct       Nov       Dec
1 1.0054381 0.9567071 1.0160520 1.0068200
2 1.0054381 0.9567071 1.0160520 1.0068200
3 1.0054381 0.9567071 1.0160520 1.0068200
4 1.0054381 0.9567071 1.0160520 1.0068200

$irregular
            Jan           Feb           Mar           Apr           May           Jun
1   0.007710648  -2.585821896   0.520220658   1.408252121  -3.378295792   0.849576339
2  -3.249336300   5.200915721  15.293374992   0.212148921   1.807020168  -1.568509286
3   1.917371938  -0.471615597 -12.583909458  -3.466996800   4.204549026   0.924671289
4   1.247452005  -1.988721530  -3.117254601   1.808253037  -2.508080731  -0.252018274
            Jul           Aug           Sep           Oct           Nov           Dec
1   7.061660702  -0.596414261  -3.202638608  -3.745866291   0.578406263  -5.278727784
2  -4.134935477  -8.736949676  -5.239591285   1.102489912   6.245659168   5.719488029
3  -2.554530047   5.486799996  -0.268352586   1.524887340   3.638266631  -6.372932308
4  -0.561734671   3.640360406   8.644833695   1.067140245 -10.453301267   5.738940162

$f.season
NULL

$g
NULL

# or do that using decompose for multiplicative:
plot(decompose(medium_noise, type="m"))

# and additive seasonality:
plot(decompose(medium_noise, type="a"))

  • As you may see, no regular components emerge for this series.

Splitting the Series

The next step is to split the data into training (in-sample) and test (out-of-sample, holdout) sets. The purpose of the former is to fit various methods only on a part instead of the whole data. Since the test set is withheld from the method fitting procedure, it is thus unknown to our forecasting methods, and can be used in assessing the accuracy of the methods.

The series must be split into training and test sets. For the sake of this example, the training set size will be equal to 36.

# find the total number of observations
medium_noise_length <- length(medium_noise)
# write down size of training set
train_length <- 36
# and the forecasting horizon
h <- 12
# create the training set
train <- ts(medium_noise[1:train_length], frequency=12)
# create the test set
test <- ts(medium_noise[(train_length+1):medium_noise_length],frequency=12)

Fitting a Naive

In order to calculate the Naive method, the naive() function from the forecast package can be used. It accepts horizon parameter h and produces forecasts straight away:

naive_method <- naive(train, h=h)

The forecasts of Naive method for the holdout can be extracted using $mean variable:

naive_forecast <- naive_method$mean

Fitting a Simple Moving Average

Recall that the forecast of a SMA of order \(\ell\) is given as:

\[ \hat{y}_{t+1|t}=\frac{1}{\ell}\sum^{t}_{i=t-\ell+1}y_i\]

where \(y_t\) is actual value on observation \(t\) and \(\hat{y}_{t+1|t}\) is one-step ahead forecast for the observation \(t+1\). In our case \(\ell = 3\), and this reduces to:

\[ \hat{y}_{t+1|t}=\frac{1}{3}\sum^{t}_{i=t-3+1}y_i\]

We will use function ma() from forecast package:

SMA <- ma(train, order=3, centre=FALSE)

Once the statement is executed, you will notice in the Environment that a variable called SMA has been created. This SMA output is defined as “Time Series” in the Environment. It contains the fitted values of SMA(3). As we remember, the forecast of SMA is a flat line, repeating the last fitted value. Let’s store the forecast from SMA in a variable:

# Firstly we get rid of NA (‘‘Not Assigned’’) values:
SMA_no_NAs <- SMA[!is.na(SMA)]
# Then form a forecast:
SMA3_forecast <- ts(rep(SMA_no_NAs[length(SMA_no_NAs)],12), frequency=12)

Error Measures

This section will utilize the previously created forecasts for the SMA to demonstrate how error measures can be calculated in R. Specifically, these metrics will be computed solely on the test set.

The first step consists of having your errors created. Recall that the errors are just the difference between the actuals and the forecasts \(e_t=y_t-\hat{y}_{t|t-1}\):

SMA3_errors <- test - SMA3_forecast

ME

Mean Error (ME): it is the average of the errors defined as:

\[ME = \frac{1}{m}\sum^{m}_{i=1}e_t\] The ME can be calculated as:

SMA3_ME <- mean(SMA3_errors)

MSE

Mean Squared Error (MSE): This is the average of the squared errors defined as:

\[MSE = \frac{1}{m}\sum^{m}_{i=1}e_t^2\]

The MSE can be calculated as:

SMA3_MSE <- mean(SMA3_errors ^ 2)

MAE

Mean Absolute Error (MAE): This is equal to the average of the absolute value of errors defined as:

\[MAE = \frac{1}{m}\sum^{m}_{i=1}|e_t|\]

The MAE can be calculated as:

SMA3_MAE <- mean(abs(SMA3_errors))

MAPE

Mean Absolute Percentage Error (MAPE): The MAPE is expressed in percentages, thus the multiplication by 100. It is defined as:

\[MAPE = 100 * \frac{1}{m}\sum^{m}_{i=1}\frac{|e_t|}{|y_t|}\]

The MAPE can be calculated as:

SMA3_MAPE <- 100 * mean(abs(SMA3_errors)/test)

Fitting Simple Exponential Smoothing (Level)

The last method is Simple Exponential Smoothing (SES) method. The SES is given by:

\[\hat{y}_{t+1|t} = \alpha y_t+(1-\alpha)\hat{y}_{t|t-1} \]

where \(\alpha\) is the smoothing parameter, which is usually assumed to lie in [0, 1] region.

The final method, through which R possesses a considerable superiority in producing over Excel, is Exponential Smoothing. For this example, since there were no regular components in our time series, the Simple Exponential Smoothing will be used. There is ets() function in forecast package and es() function in smooth package. They both fit exponential smoothing in a so called “State-space form”, but have some differences in the behaviour and available features.

ets() function

As you recall, the SES is defined by the \(\alpha\) parameter. If you want to compute a SES with \(\alpha\), for example, predefined \(\alpha= 0.15\) , then you would type the following:

ETS_ANN_0.15 <- ets(train, model="ANN", alpha=0.15)
ETS_ANN_0.15
ETS(A,N,N) 

Call:
 ets(y = train, model = "ANN", alpha = 0.15) 

  Smoothing parameters:
    alpha = 0.15 

  Initial states:
    l = 99.2358 

  sigma:  6.1351

     AIC     AICc      BIC 
261.5586 261.9223 264.7257 
  • The first parameter is the data to which SES is applied.

  • The model refers to the Error Trend Seasonality taxonomy. The Error can be either Additive (A) or Multiplicative (M). Since there is no Trend or Seasonality, they are represented by the letter ‘N’, which stands for “None”.

  • So if you want to try to fit the same SES with Multiplicative error, then you would substitute the model = “ANN” part with model = “MNN”.

  • The last parameter involved is the α which is set to 0.15. If you wanted the \(\alpha\) parameter to be optimised, then this argument should be omitted: ETS_ANN_opt <- ets(train, "ANN")

ETS_MNN_0.15 <- ets(train, "MNN", alpha=0.15)
ETS_MNN_0.15
ETS(M,N,N) 

Call:
 ets(y = train, model = "MNN", alpha = 0.15) 

  Smoothing parameters:
    alpha = 0.15 

  Initial states:
    l = 99.1685 

  sigma:  0.0611

     AIC     AICc      BIC 
261.5753 261.9389 264.7423 

ETS_ANN_opt
ETS(A,N,N) 

Call:
 ets(y = train, model = "ANN") 

  Smoothing parameters:
    alpha = 1e-04 

  Initial states:
    l = 100.6021 

  sigma:  5.8221

     AIC     AICc      BIC 
259.7885 260.5385 264.5391 

In order to retrieve the \(\alpha\) parameter, the following statement could be used:

coef(ETS_ANN_opt)
       alpha            l 
1.000169e-04 1.006021e+02 
  • This will return a vector with \(\alpha\) parameter as a first element and the initial value of SES as a second.

Now we need to produce forecasts. In order to generate them, you will need to use forecast() function in similar fashion to the Naive method. The below example will be used for the ETS ANN 0.15 method created in the beginning, where the \(\alpha\) was preset at 0.15. Finally, we can plot the data, fitted values and the forecast using the following command:

ETS_ANN_0.15_forecast <- forecast(ETS_ANN_0.15, h=h)$mean
plot(forecast(ETS_ANN_0.15, h=h))

es() function

As we have pointed out before, there is another exponential smoothing function, called es(). It has a similar syntax, but produces more information. In order to obtain forecast similar to ETS ANN 0.15, we can type:

ES_ANN_0.15 <- es(train, "ANN", persistence=0.15, h=h)
ES_ANN_0.15
Time elapsed: 0.02 seconds
Model estimated: ETS(ANN)
Persistence vector g:
alpha 
 0.15 
Initial values were optimised.

Loss function type: likelihood; Loss function value: 115.3578
Error standard deviation: 6.1351
Sample size: 36
Number of estimated parameters: 2
Number of provided parameters: 1
Number of degrees of freedom: 34
Information criteria:
     AIC     AICc      BIC     BICc 
234.7155 235.0791 237.8825 238.5341 
  • Here persistence parameter includes the vector of smoothing parameters, which in our case consists of only one value \(\alpha\).

The optimised SES can be constructed in a similar to ets() way, and the parameters can also be retrieved via coef() function:

ES_ANN_opt <- es(train, "ANN", h=h, silent="a")
coef(ES_ANN_opt)
   alpha    level 
  0.0000 100.6017 

And in case you need a forecast of a different length (let’s say 18 observations ahead), you can use forecast() function:

ES_ANN_opt_forecast <- forecast(ES_ANN_opt, h=18)$mean

Initialising Seeds

The aim of this task is to analyse the impact of different initial values on final forecasts for the SES. However, when you used the ets() and es(), the initial value was no longer an issue. That is because the both these functions treat the seed as an unknown parameter, and thus optimise it along with the smoothing parameter.

The ets() function does not permit the user to input the initial value, and so for this exercise the es() function will be used instead:

# Fit SES with fixed initial seed
es_ANN_initial_1 <- es(medium_noise, model="ANN", initial=medium_noise[1],
                       h=h, holdout=TRUE)
  • In es() function if the initial is set to a value, then it will not be optimised, and instead the function will use the number provided by the user.

  • medium noise[1] is the first entry in the training set, and so the model is initialised with this fixed value.

  • The parameter holdout in es() function defines, if the function needs to split data in two parts and produce forecast on the second (test set). If this parameter is set to TRUE, then the function will return forecast errors on the test set. They are stored in $accuracy variable:

es_ANN_initial_1$accuracy
           ME           MAE           MSE           MPE          MAPE           sCE 
 2.383333e-01  3.765000e+00  2.318078e+01  6.058165e-05  3.750751e-02  2.842895e-02 
         sMAE          sMSE          MASE         RMSSE          rMAE         rRMSE 
 3.742483e-02  2.290434e-03  6.170397e-01  6.161206e-01  5.661654e-01  6.237261e-01 
         rAME     asymmetry          sPIS 
 3.947005e-02  9.463655e-02 -1.947284e-01 

So far, you have calculated a SES on the data with a fixed seed. This will be different than in a case of SES with the optimised initial value:

# Fit SES with optimised Seed
es_ANN_opt <- es(medium_noise, model="ANN", h=h, holdout=TRUE)
  • The statement passed is the same as the previous one, with the exception that the initial argument is by default is set to NULL. By default, the es() function optimises the seed as a parameter. Just as before, let us see how the model performed:
es_ANN_opt$accuracy
          ME          MAE          MSE          MPE         MAPE          sCE 
 0.436666667  3.773055556 23.314658334  0.002028055  0.037513751  0.052086612 
        sMAE         sMSE         MASE        RMSSE         rMAE        rRMSE 
 0.037504901  0.002303662  0.618359920  0.617897209  0.567376775  0.625524632 
        rAME    asymmetry         sPIS 
 0.072315761  0.178231642 -0.348503173 

Benchmarking

A crucial practice that is sometimes overlooked in forecasting is to benchmark your forecasts with a simple forecasting method. The easiest method to use as a benchmark is the Naive. Here we will use es() function for the same purposes, recalling features of SES:

# Fit SES with optimised Seed
medium_noise_naive <- es(medium_noise, model="ANN", persistence=1,
                         h=h, holdout=TRUE)
  • this way we set smoothing parameter equal to one, which makes ETS(A,N,N) model produce Naive forecasts.
medium_noise_naive$accuracy
          ME          MAE          MSE          MPE         MAPE          sCE 
 6.038333333  6.650000000 59.585450000  0.057596778  0.064294103  0.720266397 
        sMAE         sMSE         MASE        RMSSE         rMAE        rRMSE 
 0.066102285  0.005887486  1.089857651  0.987806359  1.000000000  1.000000000 
        rAME    asymmetry         sPIS 
 1.000000000  0.910667086 -4.691671775 

Fitting Exponential Smoothing Models (Trend)

In cases, when there is a trend in data, underlying the SES, cannot be used for forecasting. In this case we need a forecasting method which can produce trends. One of these methods is Holt’s method. It has underlying statistical model ETS(A,A,N) (Additive error, Additive trend and No seasonality).

In order to see how this method works, we will use a different time series. Copy the file called “trend data.csv” to your working directory and load it into R. Save the resulting time series into trend data variable.

trend_data <- read.csv("./trend_data.csv")
trend_data <- ts(trend_data,frequency = 12)
plot(trend_data)

Note that there is no obvious seasonality in the data, which implies that an ETS model with no seasonality can be used. The data consists of 48 observations. As usual, we will use 36 of them for in-sample and 12 for the holdout.

h=12
trend_data_length <- length(trend_data)
# Split into training and test
trend_data_train <- ts(trend_data[1:36], frequency = 12)
trend_data_test <- ts(trend_data[37:trend_data_length],frequency = 12)

Now in order to estimate the parameters and the initial states of this model, we will use the ets() function:

# calculate Holt Method
ets_AAN <- ets(trend_data_train, model="AAN")
ets_AAN
ETS(A,A,N) 

Call:
 ets(y = trend_data_train, model = "AAN") 

  Smoothing parameters:
    alpha = 0.1931 
    beta  = 0.1931 

  Initial states:
    l = 1022.1387 
    b = 22.784 

  sigma:  36.1903

     AIC     AICc      BIC 
393.1595 395.1595 401.0771 
  • Both smoothing parameters, \(\alpha\) and \(\beta\), are reported to be equal to 0.19.

In case you need these parameters, you can use coef() function:

coef(ets_AAN)
       alpha         beta            l            b 
   0.1930941    0.1930940 1022.1386524   22.7839859 

Just like any other forecasting model constructed using the ets() function in R, the forecasts are produced by using the forecast function:

forecast(ets_AAN, h=h)$mean
       Jan      Feb      Mar      Apr      May      Jun      Jul      Aug      Sep
4 2027.636 2055.188 2082.741 2110.294 2137.846 2165.399 2192.951 2220.504 2248.056
       Oct      Nov      Dec
4 2275.609 2303.161 2330.714
plot(forecast(ets_AAN, h=h))

Damped Trend

If you have plotted the data, you may have noticed that the forecast produced by ETS(A,A,N) is biased (it overshoots the data in the holdout). This is a common thing in forecasting, so in order to address this issue a damped trend method has been proposed. The underlying statistical model is ETS(A,Ad,N).

In order to fit the Damped trend method, the same syntax as before can be used, but with an additional parameter:

# Calculate a Damped Holt Method
ets_AAdN <- ets(trend_data_train, model="AAN", damped=TRUE)
ets_AAdN
ETS(A,Ad,N) 

Call:
 ets(y = trend_data_train, model = "AAN", damped = TRUE) 

  Smoothing parameters:
    alpha = 0.1927 
    beta  = 0.1927 
    phi   = 0.98 

  Initial states:
    l = 1015.4688 
    b = 26.5557 

  sigma:  36.7087

     AIC     AICc      BIC 
395.0406 397.9372 404.5417 
  • a new parameter \(\phi\), representing the damping parameter, which is equal to 0.98.

  • Please note that the default argument for the ets() function is damped=NULL, meaning that if not specified, the function will have to choose between damped = TRUE and damped = FALSE, based on an Information Criterion.

The syntax for es() function is different, as it accepts “d” right in the name of the model the following way:

es_AAdN <- es(trend_data, model="AAdN", h=h, holdout=TRUE)
forecast(es_AAdN, h=h)$mean
       Jan      Feb      Mar      Apr      May      Jun      Jul      Aug      Sep
4 2026.260 2053.489 2080.387 2106.959 2133.207 2159.137 2184.751 2210.055 2235.051
       Oct      Nov      Dec
4 2259.743 2284.135 2308.231
plot(forecast(es_AAdN, h=h))

Notation for Exponential Smoothing Models

ETS:Error, Trend, Seasonality;

  • for N: None,

  • for A Additive,

  • for Ad Additive damped,

  • for M Multiplicative,

  • for Md Multiplicative damped.

Examples:

  • ETS(A,N,N) Additive error, no trend, no seasonality

  • ETS(A,A,M) Additive error, additive trend, multiplicative seasonality

Fitting Exponential Smoothing Models (Trend and Season)

In this subsection, you will see how to calculate an Additive Holt-Winters method. Recall that this method involves the presence of both trend and seasonality in the series.

Copy the trend seasonal data.csv file into your Working directory, and plot the data in order to analyse it visually.


trend_seasonal_data <- ts(trend_seasonal_data, frequency = 12)
plot(trend_seasonal_data)

  • The plot indicates that there are both trend and seasonality in the data. This means that a Holt-Winters method can be used.

Split the data into two sets with 36 and 12 observations:

# find the total number of observations
trend_seasonal_data_length <- length(trend_seasonal_data)
# write down size of training set
train_length <- 36
# and the forecasting horizon
h <- 12
# create the training set
trend_seasonal_data_train <- ts(trend_seasonal_data[1:train_length], frequency=12)
# create the test set
trend_seasonal_data_test <- ts(trend_seasonal_data[
  (train_length+1):trend_seasonal_data_length],frequency=12)

The additive Holt-Winters method has an underlying ETS(A,A,A) model. There are other types of trend-seasonal models in ETS: ETS(A,Ad,A), ETS(A,A,M), ETS(M,A,M) and so on. Constructing a seasonal model is straightforward:

# Fit a model using ets():
ets_AAA <- ets(trend_seasonal_data_train, model="AAA", damped=FALSE)
ets_AAA
ETS(A,A,A) 

Call:
 ets(y = trend_seasonal_data_train, model = "AAA", damped = FALSE) 

  Smoothing parameters:
    alpha = 0.2039 
    beta  = 0.1948 
    gamma = 1e-04 

  Initial states:
    l = 611.8942 
    b = 47.8868 
    s = -347.5472 -255.5593 433.4323 -360.1945 188.8925 93.2961
           242.9259 201.1537 224.9884 108.9299 -409.7781 -120.5396

  sigma:  22.7599

     AIC     AICc      BIC 
366.8465 400.8465 393.7663 
# Do the same using es():
es_AAA <- es(trend_seasonal_data_train, model="AAA", h=h)
es_AAA
Time elapsed: 0.08 seconds
Model estimated: ETS(AAA)
Persistence vector g:
 alpha   beta  gamma 
0.2082 0.2082 0.0073 
Initial values were optimised.

Loss function type: likelihood; Loss function value: 147.7294
Error standard deviation: 20.7227
Sample size: 36
Number of estimated parameters: 18
Number of degrees of freedom: 18
Information criteria:
     AIC     AICc      BIC     BICc 
331.4588 371.6941 359.9621 432.0541 
  • If you look at the output of these function, you will notice that there is a third smoothing parameter now, called \(\gamma\) (used for seasonal part of the model) and that now there is a set of initial seasonal components.

  • Overall ETS(A,A,A) has 18 parameters (3 smoothing parameters, 12 initial seasonal indices, 2 initials for level and trend and an estimate of standard deviation of the error).

Selecting the best model based on optimisation

So far, you have seen how to fit specific models using ets() and es() functions in R. Both of them also allow selecting the most appropriate model for the data. This is done via fitting a set of them and selecting the one that has a minimal Information Criterion. This can either be done using AICc, AIC or BIC. By default both ets() and es() use AICc.

In order to use this feature, you need to set model argument equal to "ZZZ":

# Calculate an Optimized ETS Method using ets()
ets_ZZZ <- ets(trend_seasonal_data_train, model="ZZZ")
ets_ZZZ
ETS(A,A,A) 

Call:
 ets(y = trend_seasonal_data_train, model = "ZZZ") 

  Smoothing parameters:
    alpha = 0.2039 
    beta  = 0.1948 
    gamma = 1e-04 

  Initial states:
    l = 611.8942 
    b = 47.8868 
    s = -347.5472 -255.5593 433.4323 -360.1945 188.8925 93.2961
           242.9259 201.1537 224.9884 108.9299 -409.7781 -120.5396

  sigma:  22.7599

     AIC     AICc      BIC 
366.8465 400.8465 393.7663 
# Do the same using es()
es_ZZZ <- es(trend_seasonal_data_train, model="ZZZ")
es_ZZZ
Time elapsed: 0.73 seconds
Model estimated: ETS(AMdA)
Persistence vector g:
 alpha   beta  gamma 
0.2592 0.0000 0.0000 
Damping parameter: 0.9608
Initial values were optimised.

Loss function type: likelihood; Loss function value: 147.9314
Error standard deviation: 20.2802
Sample size: 36
Number of estimated parameters: 17
Number of provided parameters: 2
Number of degrees of freedom: 19
Information criteria:
     AIC     AICc      BIC     BICc 
329.8627 363.8627 356.7826 417.7024 
  • By setting the model argument equal to "ZZZ", you ask function to explore many models and return the optimal one. In our case, the optimal model for the data is ETS(A,Md,A).

If you are sure that some components have a specific type in your data, you can pass them in the function. For example, we can be certain that there is no seasonality in the trend data, that we used in previous sections. So we can ask to exclude different types of seasonal models from the set of models (this should speed up the calculations and will result in a non-seasonal ETS model.):

# Select the most appropriate non-seasonal model with ets()
ets_ZZN <- ets(trend_data_train, model="ZZN")
ets_ZZN
ETS(M,A,N) 

Call:
 ets(y = trend_data_train, model = "ZZN") 

  Smoothing parameters:
    alpha = 0.1804 
    beta  = 0.1804 

  Initial states:
    l = 1020.0086 
    b = 25.6762 

  sigma:  0.0249

     AIC     AICc      BIC 
392.2672 394.2672 400.1848 
# The same thing with es()
es_ZZN <- es(trend_data_train, model="ZZN", silent="a")
Warning: The parameter phi is equal to one, so we reverted to the non-damped version of the model
es_ZZN
Time elapsed: 0.34 seconds
Model estimated: ETS(MMN)
Persistence vector g:
 alpha   beta 
0.3199 0.0000 
Initial values were optimised.

Loss function type: likelihood; Loss function value: 177.4165
Error standard deviation: 0.0247
Sample size: 36
Number of estimated parameters: 4
Number of provided parameters: 1
Number of degrees of freedom: 32
Information criteria:
     AIC     AICc      BIC     BICc 
362.8330 364.1234 369.1671 371.4791 

Rolling origin

So far, we have produced forecasts from one and the same point in time, from the same origin. The technique that we used is called “static origin”. The idea behind “rolling origin” is to produce several forecasts by moving the origin of the forecast.

This is an iterative procedure and on each iteration the train set size is increased by one observation, while the test set size is decreased by one.

Let’s try this technique on the example of “medium_noise” data:

# Load the Data
medium_noise <- read.csv("./medium_noise.csv", header=FALSE)
# Convert to Time Series
medium_noise <- ts(medium_noise, frequency=12, start=c(2012,1))
plot(medium_noise)

We will produce ten forecasts using ETS(A,N,N) model with the horizon of 12. First, write down the length of the series (48). Now we need to figure out the lengths of train and test sets. In order to do that you can use the following formulae:

\[T_0=T-h-n+1\]

\[T_1=h+n-1\]

where \(T_0\) is the size of train set, \(T_1\) is the size of test set, \(h\) is forecasting horizon and \(n\) is number of origins.

Now let us set training and test sets, and we do not need to preserve the time series structure of the test set, so we can simply save it as a vector:

# Set horizon and number of rolling origins
h <- 12
origins <- 10
medium_noise_length <- length(medium_noise)
train_length <- medium_noise_length - h - origins + 1
test_length <- h + origins - 1
medium_noise_train <- ts(medium_noise[1:train_length],
                         frequency = frequency(medium_noise),
                         start = start(medium_noise))
medium_noise_test <- medium_noise[(train_length+1):medium_noise_length]

Now that we managed to split the data into two parts, we will prepare matrix for forecasts and for the holdout values, so that we can easily calculate any error we want. In order to remember, what is what, we will give names to the rows and columns:

medium_noise_forecasts <- matrix(NA, nrow=origins, ncol=h)
medium_noise_holdout <- matrix(NA, nrow=origins, ncol=h)
colnames(medium_noise_forecasts) <- paste0("horizon",c(1:h))
rownames(medium_noise_forecasts) <- paste0("origin",c(1:origins))
dimnames(medium_noise_holdout) <- dimnames(medium_noise_forecasts)

Finally we can start producing forecasts using rolling origin. We will need a loop for that:

for(i in 1:origins){
# Create a ts object out of the medium_noise data
    our_train_set <- ts(medium_noise[1:(train_length+i-1)],
                        frequency=frequency(medium_noise),
                        start=start(medium_noise))
# Write down the holdout values from the test set
    medium_noise_holdout[i,] <- medium_noise_test[i-1+(1:h)]
# Produce forecasts and write them down
    medium_noise_forecasts[i,] <- forecast(ets(our_train_set,"ANN"),h=h)$mean
}

After that we will have two matrices with forecasts and holdouts. You can then work with them, calculating different types of errors. For example, MAE for each horizon can be calculated following way:

colMeans(abs(medium_noise_holdout - medium_noise_forecasts))
 horizon1  horizon2  horizon3  horizon4  horizon5  horizon6  horizon7  horizon8 
 3.641481  3.570178  3.148363  3.049997  3.170479  2.735337  2.897582  2.865993 
 horizon9 horizon10 horizon11 horizon12 
 3.200920  2.958815  3.443546  3.500544 

Now we have these two matrices and can calculate different errors and analyse how our fore- casting methods perform on different horizons. We can also use other statistical methods you may know (calculate mean, median, quantiles, standard deviation etc) for that.

LS0tCnRpdGxlOiAiNTcwIEZvcmVjYXN0aW5nICYgUHJlZGljdGl2ZSBBbmFseXRpY3MiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KIyBEYXRhIEV4cGxvcmF0aW9uIGFuZCBEZWNvbXBvc2l0aW9uCgojIyBUaW1lIHNlcmllcyBjb21wb25lbnRzCkEgdGltZSBzZXJpZXMgaXMgYSBjb21iaW5hdGlvbiBvZiBzeXN0ZW1hdGljIGFuZCByYW5kb20gKG5vaXNlKSBjb21wb25lbnRzLiBPbmx5IHRoZSBzeXN0ZW1hdGljIHBhcnQgY2FuIGJlIGZvcmVjYXN0ZWQuCgojIyMgcmFuZG9tIChub2lzZSkgY29tcG9uZW50cwoKTm9pc2UgaXMgYSBzZXJpZXMgb2YgaW5kZXBlbmRlbnRseSBkaXN0cmlidXRlZCBub3JtYWwgc2VxdWVuY2VzOiB0aGUgc2VxdWVuY2UgaXMgbm9uLWNvcnJlbGF0aW9uYXJ5LCBub24tdHJlbmQsIGFuZCByYW5kb20uIEl0IG9iZXlzIHRoZSBub3JtYWwgZGlzdHJpYnV0aW9uIHdpdGggbWVhbiBvZiAwIGFuZCBhIHZhcmlhbmNlIG9mICRcc2lnbWFeMiQuCgpHZW5lcmF0aW5nIGEgTm9pc2UgU2VyaWVzIGluIFIKCi0gYHNldC5zZWVkKClgOiBTZXRzIHJhbmRvbSBudW1iZXIgZ2VuZXJhdG9yIHNlZWQuCgotIGB0cyhybm9ybSg1MCkpYENyZWF0ZXMgYSB0aW1lIHNlcmllcyB2YXJpYWJsZTogNTAgb2JzZXJ2YXRpb25zIHRoYXQgYXJlICoqcmFuZG9tKiogYW5kICoqbm9ybWFsbHkqKiBkaXN0cmlidXRlZCBQbG90cyB0aGUgdGltZSBzZXJpZXMuCgpgYGB7cn0Kc2V0LnNlZWQoMDAwOTI5KQp5IDwtIHRzKHJub3JtKDUwKSkKcGxvdCh5KQpgYGAKCiMjIyBTeXN0ZW1hdGljIFRpbWUgU2VyaWVzIENvbXBvbmVudHMKCi0gTGV2ZWw6IGlmIGEgdGltZSBzZXJpZXMgbGFja3MgdHJlbmQvY3ljbGUgdGhlbiBpdCBmbHVjdHVhdGVzIGFyb3VuZCBhIGxldmVsLiBUaGUgbGV2ZWwgY2FuIGJlIGRlc2NyaWJlZCBhcyBwYXJ0IG9mIHRoZSB0cmVuZCBjb21wb25lbnQuCgotIFRyZW5kOiBpcyB0aGUgbG9uZyB0ZXJtIGluY3JlYXNlIG9yIGRlY3JlYXNlIGluIHRoZSBkYXRhLgoKLSBDeWNsZTogaXMgYSBsb25nIHRlcm0gdXAgYW5kIGRvd24gbW92ZW1lbnRzIC0gbm90IG9mIGZpeGVkIHBlcmlvZC4KCi0gU2Vhc29uOiBpcyBhIHJlcGVhdGluZyBwYXR0ZXJuIG9mIGZpeGVkIHBlcmlvZC4KCiMjIyBJcnJlZ3VsYXIgQ29tcG9uZW50cwoKLSBPdXRsaWVyczogb25lLXBlcmlvZCBzaGlmdCBmcm9tIHRoZSBleHBlY3RlZCB2YWx1ZS4KCi0gTGV2ZWwgU2hpZnQ6IG11bHRpLXBlcmlvZCBzaGlmdHMgZnJvbSB0aGUgZXhwZWN0ZWQgdmFsdWUuCgojIyBEaWZmZXJlbnQgRGVjb21wb3NpdGlvbiBNb2RlbCBGb3JtcwoKUHVyZSBBZGRpdGl2ZSBNb2RlbDoKCiQkWV90PVRfdCtTX3QrXGVwc2lsb25fdCQkCgpQdXJlIE11bHRpcGxpY2F0aXZlIE1vZGVsOgoKJCRZX3Q9VF90KlNfdCpcZXBzaWxvbl90JCQKCk1peGVkIE11bHRpcGxpY2F0aXZlIE1vZGVsOgoKJCRZX3Q9VF90KlNfdCtcZXBzaWxvbl90JCQKCiMjIFRpbWUgU2VyaWVzIFByYWN0aWNlCgpGb3IgdGhpcyBleGVyY2lzZSwgeW91IHdpbGwgaGF2ZSB0byBsb2FkIHRoZSBwYWNrYWdlcyDigJwqdHN1dGlscyrigJ0gYW5kIOKAnCpmb3JlY2FzdCrigJ0uIEdvIHRvIHRoZSBQYWNrYWdlcyB0YWIgb24gdGhlIHJpZ2h0IGFuZCB0aWNrIG9uIHRoZSBjaGVja2JveCBuZXh0IHRvIOKAnCp0c3V0aWxzKuKAnSwgYW5kIGFsc28gZm9yIHRoZSBvbmUgbmV4dCB0byDigJwqZm9yZWNhc3Qq4oCdLiBBbHRlcm5hdGl2ZWx5LCB5b3UgY2FuIGFsc28gdHlwZSB0aGUgZm9sbG93aW5nIHN0YXRlbWVudDoKCmBgYHtyfQpsaWJyYXJ5KHRzdXRpbHMpCmxpYnJhcnkoZm9yZWNhc3QpCmBgYAoKVGhlIHBhY2thZ2UgaXMgbm93IGxvYWRlZCwgYW5kIGl0IGlzIHRpbWUgdG8gbG9hZCB0aGUgZGF0YS4gT3BlbiB0aGUgTm90ZXBhZCBmaWxlIGNhbGxlZCDigJxgU2FsZXMudHh0YOKAnS4KCmBgYHtyfQojIEltcG9ydGluZyB0aGUgZGF0YQpkYXRhIDwtIHJlYWQudGFibGUoIi4vU2FsZXMudHh0IikKYGBgCgpHaXZlbiB0aGF0IHRoZSBkYXRhIGlzIGEgKip0aW1lIHNlcmllcyoqLCBpdCB3b3VsZCBiZSB1c2VmdWwgdG8gKmNvbnZlcnQgdGhlIGRhdGEgaW50byBhIHRpbWUgc2VyaWVzIGZvcm1hdCouIEEgdGltZSBzZXJpZXMgZm9ybWF0IHJlcXVpcmVzIGEgc2Vhc29uYWwgZnJlcXVlbmN5LiBJbiB0aGlzIGNhc2UsIHNpbmNlIHRoZSBkYXRhIGlzICoqbW9udGhseSoqLCB0aGUgZnJlcXVlbmN5IHdvdWxkIGJlICoqMTIqKi4gSW4gb3JkZXIgdG8gZG8gc28sIGVudGVyIHRoZSBzdGF0ZW1lbnQ6CgpgYGB7cn0KZGF0YSA8LSB0cyhkYXRhLCBmcmVxdWVuY3kgPSAxMiwgc3RhcnQgPSBjKDIwMTYsMSkpCmBgYAoKVGhlIGFib3ZlIHN0YXRlbWVudCBoYXMgY29udmVydGVkIHRoZSB2YXJpYWJsZSBkYXRhIGludG8gYSB0aW1lIHNlcmllcy4gVGhlIGBmcmVxdWVuY3k9MTJgIHNwZWNpZmllcyB0aGUgc2Vhc29uYWwgZnJlcXVlbmN5LCB3aGlsZSB0aGUgYHN0YXJ0PWMoMjAxNiwxKWAgc3BlY2lmaWVzIHRoYXQgdGhlIGZpcnN0IGVudHJ5IG9jY3VycyBvbiBKYW51YXJ5IDIwMTYuIFRoZSBzdGFydCBpcyBub3QgYSBjb21wdWxzb3J5IGFyZ3VtZW50IGFzIGl0IGhlbHBzIGluIG9yZ2FuaXppbmcgdGhlIGRhdGE7IGhvd2V2ZXIgdGhlIGZyZXF1ZW5jeSBpcyEKCklmIHlvdSB3YW50ZWQgdG8gaW5zcGVjdCB0aGUgc2Vhc29uYWwgZnJlcXVlbmN5IG9mIHlvdXIgc2VyaWVzLCBqdXN0IHR5cGU6CgpgYGB7cn0KZnJlcXVlbmN5KGRhdGEpCmBgYAoKVGhlIGZpcnN0IHRhc2sgY29uc2lzdGVkIG9mIHBsb3R0aW5nIHRoZSB0aW1lIHNlcmllcy4gVGhpcyBpcyBkb25lIGJ5IHVzaW5nIHRoZSBgcGxvdCgpYCBmdW5jdGlvbiBhcyBmb2xsb3dzOgoKYGBge3J9CnBsb3QoZGF0YSkKYGBgCgojIyMgUGxvdHRpbmcgaW4gUgoKRm9yIHRoZSB0cyBmb3JtYXQsIHdoZW4gZW50ZXJpbmcgdGhlIGBwbG90KClgIGZ1bmN0aW9uLCB0aGUgb3V0cHV0IHdhcyBhIGxpbmUgZ3JhcGguIEhvd2V2ZXIgZm9yIG90aGVyIGZvcm1hdHMgdGhlIG91dHB1dCBpcyBhIHNjYXR0ZXJwbG90LiBJbiBvcmRlciB0byBzcGVjaWZ5IHRoYXQgeW91IG5lZWQgYSBsaW5lIGdyYXBoLCB0aGUgZm9sbG93aW5nIHNob3VsZCBiZSBpbnNlcnRlZDoKCmBgYHtyfQojIENyZWF0ZSBkYXRhCmRhdGExIDwtIDE6MjAKIyBQbG90CnBsb3QoZGF0YTEsIHR5cGUgPSAibCIpCmBgYAoKLSBCeSBhZGRpbmcgdGhlIGB0eXBlPSJsImAgYXJndW1lbnQsIHRoZSBwbG90IHdpbGwgbm93IGJlIGEgbGluZS4gRnVydGhlcm1vcmUgaWYgeW91IHdhbnRlZCB0byBjb2xvdXIgaXQsIHRoZW4gYW5vdGhlciBhcmd1bWVudCBzaG91bGQgYmUgYWRkZWQgYXMgc3VjaDoKCmBgYHtyfQpwbG90KGRhdGExLCB0eXBlID0gImwiLCBjb2wgPSAiYmx1ZSIpCmBgYAoKIyMjIENlbnRyZWQgTW92aW5nIEF2ZXJhZ2UKCkluIG9yZGVyIHRvIGNyZWF0ZSBhIENNQSwgdGhlIGZ1bmN0aW9uIGBjbWF2KClgIGZyb20gdGhlIOKAnCp0c3V0aWxzKuKAnSBwYWNrYWdlIGlzIHVzZWQuIFRvIHRlc3QgdGhpcywgY3JlYXRlIGEgdmFyaWFibGUgdGhhdCB3aWxsIGhhdmUgdGhlIENNQSB2YWx1ZXMgc3RvcmVkIGluIGl0IGFzIHN1Y2g6CgpgYGB7cn0KZGF0YV9jbWEgPC0gY21hdihkYXRhLCBtYSA9IDEyLCBmaWxsID0gRkFMU0UpCmBgYAoKLSBUaGUgZmlyc3QgYXJndW1lbnQgcGFzc2VkIGlzIHdoaWNoIHNlcmllcyBpcyBiZWluZyB1c2VkLCB3aGljaCBpbiB5b3VyIGNhc2UgaXMgdGhlIHZhcmlhYmxlIGNhbGxlZCBgZGF0YWAuCgotIFRoZSBgbWEoKWAgYXJndW1lbnQgc3BlY2lmaWVzIHRoZSBsZW5ndGggb2YgdGhlIENNQS4gSWYgdGhpcyBpcyBvbWl0dGVkLCB0aGVuIHRoZSBkZWZhdWx0IHZhbHVlIGlzIHRoZSBzZWFzb25hbCBmcmVxdWVuY3kuCgotIFRoZSBgZmlsbD1GQUxTRWAgYXJndW1lbnQgbWVhbnMgdGhhdCB0aGUgKipmaXJzdCBhbmQgbGFzdCBvYnNlcnZhdGlvbnMgdGhhdCB3ZXJlIGV4cGVjdGVkIHRvIGJlIGxlZnQgYmxhbmsqKiB3aWxsIGJlIGxlZnQgYmxhbmsuWW91IHdpbGwgc2VlIHRoYXQgdGhlIGZpcnN0IHZhbHVlcyBvZiB0aGUg4oCcZGF0YSBjbWHigJ0gdmFyaWFibGUgYXJlIE5BLCBzaW5jZSB0aGV5IGRvbuKAmXQgZXhpc3QuIElmIHlvdSB3YW50ZWQgdG8gaW1wdXRlIHZhbHVlcyBvbiB0aGVzZSBvYnNlcnZhdGlvbnMsIHRoZW4gdXNlIHRoZSBgZmlsbD1UUlVFYCBhcmd1bWVudC4KCklmIHlvdSB3YW50ZWQgdG8gb3ZlcmxheSB0aGUgZ3JhcGhzLCB0aGlzIGlzIGhvdyBpdCBpcyBkb25lOgoKYGBge3J9CiNQbG90IHRoZSBvcmlnaW5hbCB0aW1lIHNlcmllcyBpbiBibHVlIApwbG90KGRhdGEsIGNvbD0iYmx1ZSIpCiNQbG90IHRoZSBDTUEoMTIpIGluIHJlZCAKbGluZXMoZGF0YV9jbWEsIGNvbCA9ICJyZWQiKQpgYGAKCi0gQnkgYWRkaW5nIHRoZSBgbGluZXMoKWAgc3RhdGVtZW50LCB5b3UgY2FuIG92ZXJsYXkgYSBsaW5lIGdyYXBoIG9uIHlvdXIgb3JpZ2luYWwgdGltZSBzZXJpZXMuIAoKLSBJZiB5b3Ugd2VyZSB0byB1c2UgdGhlIGBwbG90KClgIHN0YXRlbWVudCwgdGhlbiBhIG5ldyBncmFwaCB3aWxsIGJlIHBsb3R0ZWQgaW5zdGVhZC4KCiMjIyBTZWFzb25hbCBQbG90cwoKSW4gdGhlIOKAnCp0c3V0aWxzKuKAnSBwYWNrYWdlLCBhIGZ1bmN0aW9uIGhhcyBiZWVuIGNyZWF0ZWQgdGhhdCB3aWxsIGF1dG9tYXRpY2FsbHkgY2FsY3VsYXRlIHRoZSBzZWFzb25hbCBpbmRpY2VzIGFuZCBwcm9maWxlLCBhbmQgd2lsbCBkaXNwbGF5IHRoZSBzZWFzb25hbCBwbG90LiBKdXN0IHR5cGU6CgpgYGB7cn0Kc2Vhc3Bsb3QoZGF0YSkKYGBgCgpJZiB5b3Ugd2lzaGVkIHRvIHN0b3JlZCB0aGUgc2Vhc29uYWwgaW5kaWNlcyBpbiBhIHZhcmlhYmxlLCBzYXkgKuKAnGRhdGEgc2VhcyBpbmRleOKAnSosIHRoZW4gdHlwZTogCgpgYGB7cn0KZGF0YV9zZWFzX2luZGV4IDwtIHNlYXNwbG90KGRhdGEpJHNlYXNvbgpkYXRhX3NlYXNfaW5kZXgKYGBgCgojIyMgRGVjb21wb3NpdGlvbgpUaGUgYGRlY29tcCgpYCBmdW5jdGlvbiBpbiB0aGUg4oCcdHN1dGlsc+KAnSBwYWNrYWdlIGNhbiBhdXRvbWF0aWNhbGx5IGRlY29tcG9zZSB0aGUgKip0cmVuZCoqLCAqKnNlYXNvbmFsKiogYW5kICoqaXJyZWd1bGFyKiogcGFydHMgb2YgYSB0aW1lIHNlcmllcy4gQ3JlYXRlIGEgdmFyaWFibGUgbmFtZWQg4oCcZGVjb21wb3NpdGlvbuKAnToKCmBgYHtyfQpkZWNvbXBvc2l0aW9uIDwtIGRlY29tcChkYXRhLGRlY29tcG9zaXRpb24gPSAibXVsdGlwbGljYXRpdmUiLG91dHBsb3QgPSBUUlVFKQpgYGAKCi0gVGhlIGZpcnN0IGFyZ3VtZW50IGlzIHdoaWNoIHNlcmllcyB0byBkZWNvbXBvc2UsIHdoaWNoIGlzIHRoZSDigJxkYXRh4oCdIHNlcmllcy4KCi0gVGhlIGBkZWNvbXBvc2l0aW9uYCBhcmd1bWVudCBzcGVjaWZpZXMgd2hldGhlciB0aGUgZGVjb21wb3NpdGlvbiBzaG91bGQgYmUgKiphZGRpdGl2ZSoqIG9yICoqbXVsdGlwbGljYXRpdmUqKi4gU28gaWYgeW91IHdhbnRlZCB0byB0cnkgd2l0aCBhbiBhZGRpdGl2ZSBjb3VudGVycGFydCB0aGVuIGFsdGVyIHRoZSBzdGF0ZW1lbnQgdG86IGBkZWNvbXBvc2l0aW9uID0gImFkZGl0aXZlImAuCgotIFRoZSBgb3V0cGxvdD1UUlVFYCBzcGVjaWZpZXMgd2hldGhlciB0aGUgcGxvdHMgc2hvdWxkIGJlIGRpc3BsYXllZCBvciBub3QuCgpJbiBvcmRlciB0byBzZWUgd2hhdCBpdCBjb25zaXN0cyBvZiwgeW91IG5lZWQgdGhlIGBzdHIoKWAgZnVuY3Rpb24uIFRoaXMgZnVuY3Rpb24gd2lsbCBkaXNwbGF5IHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIHZhcmlhYmxlLgoKYGBge3J9CnN0cihkZWNvbXBvc2l0aW9uKQpgYGAKCkluIG9yZGVyIHRvIGV4dHJhY3QgZm9yIGV4YW1wbGUgdGhlICoqaXJyZWd1bGFyKiogKHdoaWNoIGlzIHRoZSBub2lzZSBjb21wb25lbnQpIGNvbXBvbmVudCwgdGhlbiBydW4gdGhlIGZvbGxvd2luZyBjb21tYW5kOgoKYGBge3J9CmRlY29tcG9zaXRpb24kaXJyZWd1bGFyCmBgYAoKIyMjIENoZWNrIHRoZSBEZWNvbXBvc2l0aW9uIG1vZGVsCgpBIHF1ZXN0aW9uIGNhbiBhcmlzZSB3aGVuIHVzaW5nIHRoZSBgZGVjb21wKClgIGZ1bmN0aW9uOiAKCi0gSXMgdGhlIE11bHRpcGxpY2F0aXZlIGRlY29tcG9zaXRpb24gcGVyZm9ybWVkIGJ5IHRoaXMgZnVuY3Rpb24gYSBQdXJlIE11bHRpcGxpY2F0aXZlIE1vZGVsIG9yIGEgTWl4ZWQgTXVsdGlwbGljYXRpdmUgTW9kZWw/CgpJbiBvcmRlciB0byBjb25maXJtIHRoaXMsIGlzb2xhdGUgdGhlICoqUmVndWxhciBDb21wb25lbnRzKiogZnJvbSB0aGUgZGVjb21wb3NpdGlvbi4gU28gZmFyLCBhbGwgdGhlIGRhdGEgaXMgc3RvcmVkIGluIHRoZSB2YXJpYWJsZSBjYWxsZWQg4oCcZGVjb21wb3NpdGlvbuKAnS4gVGhlIGZpcnN0IHN0ZXAgd291bGQgYmUgdG8gZXh0cmFjdCB0aGUgKipyZWd1bGFyIGNvbXBvbmVudHMqKiAoKlRyZW5kIGFuZCBTZWFzb25hbGl0eSopIGFuZCBtdWx0aXBseSB0aGVtOgoKYGBge3J9CiNFeHRyYWN0IHRoZSBUcmVuZApkZWNvbXBvc2l0aW9uJHRyZW5kCiNFeHRyYWN0IHRoZSBTZWFzb25hbGl0eQpkZWNvbXBvc2l0aW9uJHNlYXNvbgojTXVsdGlwbHkgdGhlIFRyZW5kIGFuZCBTZWFzb25hbGl0eQpyZWd1bGFyX2NvbXBvbmVudHMgPC0gZGVjb21wb3NpdGlvbiR0cmVuZCAqIGRlY29tcG9zaXRpb24kc2Vhc29uCmBgYAoKVGhpcyBoYXMgbm93IGNyZWF0ZWQgYSBuZXcgdmFyaWFibGUgY2FsbGVkIOKAnHJlZ3VsYXIgY29tcG9uZW50c+KAnSwgd2hpY2ggaXMgZXF1YWwgdG8gJFRyZW5kIMOXIFNlYXNvbmFsaXR5JC4KCk5vdyB0aGF0IHRoZSBSZWd1bGFyIENvbXBvbmVudCBoYXMgYmVlbiBjcmVhdGVkLCB5b3UgY2FuIGNhbGN1bGF0ZSB0aGUgZXJyb3JzIGZvciB0aGUgKipNaXhlZCBNdWx0aXBsaWNhdGl2ZSBNb2RlbCoqIGFzOgoKYGBge3J9Cm1tbV9lcnJvcnMgPC0gZGF0YSAtIHJlZ3VsYXJfY29tcG9uZW50cwpgYGAKClRoZSBsYXN0IHN0ZXAgaXMgdG8gKipjb21wYXJlIHRoZXNlIGVycm9ycyB3aXRoIHRoZSBlcnJvcnMgZnJvbSB0aGUgZGVjb21wb3NpdGlvbioqLiBHbyB0byB0aGUgY29uc29sZSBhbmQgdHlwZToKCmBgYHtyfQpkZWNvbXBvc2l0aW9uJGlycmVndWxhciAtIG1tbV9lcnJvcnMKYGBgCgpZb3Ugd2lsbCBzZWUgdGhhdCB0aGUgcmVzdWx0IGlzIGEgdmVjdG9yIG9mIDAuIFRoaXMgbWVhbnMgdGhhdCB0aGUgTXVsdGlwbGljYXRpdmUgb3B0aW9uIGluIHRoaXMgZnVuY3Rpb24gaXMgYWN0dWFsbHkgYSAqKk1peGVkIE11bHRpcGxpY2F0aXZlIE1vZGVsKiouCgojIEV4cG9uZW50aWFsIFNtb290aGluZyBNZXRob2RzIHdpdGggTGV2ZWwgYW5kIFRyZW5kClRoaXMgcGFydCBhaW1zIGF0IGludHJvZHVjaW5nIGRhdGEgcGFydGl0aW9uaW5nIGZvciBmb3JlY2FzdGluZyBtZXRob2RzIGZpdHRpbmcgYW5kIGFzc2Vzc21lbnQoKipUcmFpbiBTZXQgYW5kIFRlc3QgU2V0KiopLCB0aGUgaW1wbGVtZW50YXRpb24gb2YgKnNpbXBsZSBmb3JlY2FzdGluZyBtZXRob2RzKiwgKmV4cG9uZW50aWFsIHNtb290aGluZyBtZXRob2RzKiwgYXMgd2VsbCBhcyAqZXJyb3IgbWV0cmljcyogaW4gb3JkZXIgdG8gY29tcGFyZSB0aGVzZSBtZXRob2RzIHdpdGggZWFjaCBvdGhlciAobW9kZWwgc2VsZWN0aW9uKS4KClRoZSBmb3JlY2FzdGluZyBtZXRob2RzIHRoYXQgd2lsbCBiZSB1c2VkIGFyZToKCi0gU2ltcGxlIE1vdmluZyBBdmVyYWdlIChTTUEpOwoKLSBOYWl2ZSBGb3JlY2FzdDsKCi0gU2luZ2xlIChvciBTaW1wbGUpIEV4cG9uZW50aWFsIFNtb290aGluZyAoU0VTKS4KClRoZSBlcnJvciBtZWFzdXJlcyB0aGF0IHdpbGwgYmUgZW5jb3VudGVyZWQgYXJlOgoKLSBNZWFuIEVycm9yIChNRSk7CgotIE1lYW4gU3F1YXJlZCBFcnJvciAoTVNFKTsKCi0gTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKTsKCi0gTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yIChNQVBFKS4KCiMjIExvYWRpbmcgdGhlIERhdGEKClRoZSBmaXJzdCBzdGVwIGlzIHRvIGxvYWQgdGhlIG5lY2Vzc2FyeSBsaWJyYXJpZXMuIEZvciB0aGlzIHNlc3Npb24sIHdlIHdpbGwgdXNlIHRocmVlIGxpYnJhcmllczogKmZvcmVjYXN0KiwgKnNtb290aCogYW5kICp0c3V0aWxzKi4KCmBgYHtyIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkoZm9yZWNhc3QpCmxpYnJhcnkoc21vb3RoKQpsaWJyYXJ5KHRzdXRpbHMpCmBgYAoKNCB0aW1lIHNlcmllcyBvbmx5IHdpdGggTGV2ZWwgYW5kIG5vIHRyZW5kIG9yIHNlYXNvbiBhcmUgc3RvcmVkIGluIHRoYXQgZmlsZSwgd2hpY2ggYXJlOiDigJ1NZWRpdW0gTm9pc2XigJ0sIOKAnUhpZ2ggTm9pc2XigJ0sIOKAnU1lZGl1bSBOb2lzZSB3aXRoIExldmVsIFNoaWZ04oCdLCDigJ1IaWdoIE5vaXNlIHdpdGggTGV2ZWwgU2hpZnTigJ0uCgpgYGB7cn0KTGV2ZWxfRGF0YSA8LSByZWFkLmNzdigiLi9MZXZlbF9EYXRhLmNzdiIpCmBgYAoKSW4gb3JkZXIgdG8gbG9vayBhdCBpdHMgc3RydWN0dXJlLCB0eXBlOgoKYGBge3J9CnN0cihMZXZlbF9EYXRhKQpgYGAKCkZvciB0aGUgcmVtYWluZGVyIG9mIHRoaXMgdHV0b3JpYWwsIHlvdSB3aWxsIHdvcmsgd2l0aCBvbmUgc2VyaWVzLiBQaWNrIHRoZSBmaXJzdCBzZXJpZXMgY2FsbGVkIE1lZGl1bSBOb2lzZSBhbmQgc3RvcmUgaXQgaW4gYSB2YXJpYWJsZSBvZiB0eXBlIGB0c2A6CgpgYGB7cn0KbWVkaXVtX25vaXNlIDwtIHRzKExldmVsX0RhdGFbLDJdLGZyZXF1ZW5jeT0xMikKYGBgCgotIGBbLDFdYCBtZWFucyB0aGF0IHlvdSBhcmUgdXNpbmcgdGhlIGZpcnN0IGNvbHVtbiBvZiB0aGUgbWF0cml4IFdvcmtzaG9wMywgd2hpY2ggcmVmZXJzIHRvIHRoZSBNZWRpdW0gTm9pc2Ugc2VyaWVzLgoKIyMgQW5hbHlzaW5nIHRoZSBzZXJpZXMKCkFzIHVzdWFsLCB0aGUgZmlyc3Qgc3RlcCBpcyB0byB2aXN1YWxpc2UgdGhlIHNlcmllcy4gUGxvdCBpdDoKCmBgYHtyfQpwbG90KG1lZGl1bV9ub2lzZSkKYGBgCgpUaGUgbmV4dCBzdGVwIGlzIHRvIGRlY29tcG9zZSBpdCBpbiBvcmRlciB0byBjaGVjayB3aGljaCBjb21wb25lbnRzIGl0IGNvbnNpc3RzIG9mLiBVc2UgZWl0aGVyIGZ1bmN0aW9uIGBkZWNvbXAoKWAgZnJvbSAqdHN1dGlscyogcGFja2FnZSBvciBgZGVjb21wb3NlKClgIGZyb20gKnN0YXRzKiBwYWNrYWdlOgoKYGBge3J9CiMgcGxvdCB0aGUgQWRkaXRpdmUgRGVjb21wb3NpdGlvbgpkZWNvbXAobWVkaXVtX25vaXNlLCBkZWNvbXBvc2l0aW9uPSJhZGRpdGl2ZSIsIG91dHBsb3Q9VFJVRSkKIyBwbG90IHRoZSBNdWx0aXBsaWNhdGl2ZSBEZWNvbXBvc2l0aW9uCmRlY29tcChtZWRpdW1fbm9pc2UsZGVjb21wb3NpdGlvbj0ibXVsdGlwbGljYXRpdmUiLCBvdXRwbG90PVRSVUUpCmBgYAoKYGBge3J9CiMgb3IgZG8gdGhhdCB1c2luZyBkZWNvbXBvc2UgZm9yIG11bHRpcGxpY2F0aXZlOgpwbG90KGRlY29tcG9zZShtZWRpdW1fbm9pc2UsIHR5cGU9Im0iKSkKIyBhbmQgYWRkaXRpdmUgc2Vhc29uYWxpdHk6CnBsb3QoZGVjb21wb3NlKG1lZGl1bV9ub2lzZSwgdHlwZT0iYSIpKQoKYGBgCgotIEFzIHlvdSBtYXkgc2VlLCBubyByZWd1bGFyIGNvbXBvbmVudHMgZW1lcmdlIGZvciB0aGlzIHNlcmllcy4KCiMjIFNwbGl0dGluZyB0aGUgU2VyaWVzCgpUaGUgbmV4dCBzdGVwIGlzIHRvIHNwbGl0IHRoZSBkYXRhIGludG8gKip0cmFpbmluZyoqIChpbi1zYW1wbGUpIGFuZCAqKnRlc3QqKiAob3V0LW9mLXNhbXBsZSwgaG9sZG91dCkgc2V0cy4gVGhlIHB1cnBvc2Ugb2YgdGhlIGZvcm1lciBpcyB0byBmaXQgdmFyaW91cyBtZXRob2RzIG9ubHkgb24gYSBwYXJ0IGluc3RlYWQgb2YgdGhlIHdob2xlIGRhdGEuIFNpbmNlIHRoZSB0ZXN0IHNldCBpcyB3aXRoaGVsZCBmcm9tIHRoZSBtZXRob2QgZml0dGluZyBwcm9jZWR1cmUsIGl0IGlzIHRodXMgdW5rbm93biB0byBvdXIgZm9yZWNhc3RpbmcgbWV0aG9kcywgYW5kIGNhbiBiZSB1c2VkIGluIGFzc2Vzc2luZyB0aGUgYWNjdXJhY3kgb2YgdGhlIG1ldGhvZHMuCgpUaGUgc2VyaWVzIG11c3QgYmUgc3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBzZXRzLiBGb3IgdGhlIHNha2Ugb2YgdGhpcyBleGFtcGxlLCB0aGUgdHJhaW5pbmcgc2V0IHNpemUgd2lsbCBiZSBlcXVhbCB0byAzNi4KCmBgYHtyfQojIGZpbmQgdGhlIHRvdGFsIG51bWJlciBvZiBvYnNlcnZhdGlvbnMKbWVkaXVtX25vaXNlX2xlbmd0aCA8LSBsZW5ndGgobWVkaXVtX25vaXNlKQojIHdyaXRlIGRvd24gc2l6ZSBvZiB0cmFpbmluZyBzZXQKdHJhaW5fbGVuZ3RoIDwtIDM2CiMgYW5kIHRoZSBmb3JlY2FzdGluZyBob3Jpem9uCmggPC0gMTIKIyBjcmVhdGUgdGhlIHRyYWluaW5nIHNldAp0cmFpbiA8LSB0cyhtZWRpdW1fbm9pc2VbMTp0cmFpbl9sZW5ndGhdLCBmcmVxdWVuY3k9MTIpCiMgY3JlYXRlIHRoZSB0ZXN0IHNldAp0ZXN0IDwtIG1lZGl1bV9ub2lzZVsodHJhaW5fbGVuZ3RoKzEpOm1lZGl1bV9ub2lzZV9sZW5ndGhdCmBgYAoKIyMgRml0dGluZyBhIE5haXZlCgpJbiBvcmRlciB0byBjYWxjdWxhdGUgdGhlIE5haXZlIG1ldGhvZCwgdGhlIGBuYWl2ZSgpYCBmdW5jdGlvbiBmcm9tIHRoZSBmb3JlY2FzdCBwYWNrYWdlIGNhbiBiZSB1c2VkLiBJdCBhY2NlcHRzIGhvcml6b24gcGFyYW1ldGVyIGggYW5kIHByb2R1Y2VzIGZvcmVjYXN0cyBzdHJhaWdodCBhd2F5OgoKYGBge3J9Cm5haXZlX21ldGhvZCA8LSBuYWl2ZSh0cmFpbiwgaD1oKQpgYGAKClRoZSBmb3JlY2FzdHMgb2YgTmFpdmUgbWV0aG9kIGZvciB0aGUgaG9sZG91dCBjYW4gYmUgZXh0cmFjdGVkIHVzaW5nIGAkbWVhbmAgdmFyaWFibGU6CgpgYGB7cn0KbmFpdmVfZm9yZWNhc3QgPC0gbmFpdmVfbWV0aG9kJG1lYW4KYGBgCgojIyBGaXR0aW5nIGEgU2ltcGxlIE1vdmluZyBBdmVyYWdlCgpSZWNhbGwgdGhhdCB0aGUgZm9yZWNhc3Qgb2YgYSBTTUEgb2Ygb3JkZXIgJFxlbGwkIGlzIGdpdmVuIGFzOgoKJCQgXGhhdHt5fV97dCsxfHR9PVxmcmFjezF9e1xlbGx9XHN1bV57dH1fe2k9dC1cZWxsKzF9eV9pJCQKCndoZXJlICR5X3QkIGlzIGFjdHVhbCB2YWx1ZSBvbiBvYnNlcnZhdGlvbiAkdCQgYW5kICRcaGF0e3l9X3t0KzF8dH0kIGlzIG9uZS1zdGVwIGFoZWFkIGZvcmVjYXN0IGZvciB0aGUgb2JzZXJ2YXRpb24gJHQrMSQuIEluIG91ciBjYXNlICRcZWxsID0gMyQsIGFuZCB0aGlzIHJlZHVjZXMgdG86CgokJCBcaGF0e3l9X3t0KzF8dH09XGZyYWN7MX17M31cc3VtXnt0fV97aT10LTMrMX15X2kkJAoKV2Ugd2lsbCB1c2UgZnVuY3Rpb24gYG1hKClgIGZyb20gZm9yZWNhc3QgcGFja2FnZToKCmBgYHtyfQpTTUEgPC0gbWEodHJhaW4sIG9yZGVyPTMsIGNlbnRyZT1GQUxTRSkKYGBgCgpPbmNlIHRoZSBzdGF0ZW1lbnQgaXMgZXhlY3V0ZWQsIHlvdSB3aWxsIG5vdGljZSBpbiB0aGUgKkVudmlyb25tZW50KiB0aGF0IGEgdmFyaWFibGUgY2FsbGVkIGBTTUFgIGhhcyBiZWVuIGNyZWF0ZWQuIFRoaXMgYFNNQWAgb3V0cHV0IGlzIGRlZmluZWQgYXMg4oCcVGltZSBTZXJpZXPigJ0gaW4gdGhlICpFbnZpcm9ubWVudCouIEl0IGNvbnRhaW5zIHRoZSBmaXR0ZWQgdmFsdWVzIG9mIFNNQSgzKS4gQXMgd2UgcmVtZW1iZXIsIHRoZSBmb3JlY2FzdCBvZiBgU01BYCBpcyBhIGZsYXQgbGluZSwgcmVwZWF0aW5nIHRoZSBsYXN0IGZpdHRlZCB2YWx1ZS4gTGV04oCZcyBzdG9yZSB0aGUgZm9yZWNhc3QgZnJvbSBgU01BYCBpbiBhIHZhcmlhYmxlOgoKYGBge3J9CiMgRmlyc3RseSB3ZSBnZXQgcmlkIG9mIE5BICjigJjigJhOb3QgQXNzaWduZWTigJnigJkpIHZhbHVlczoKU01BX25vX05BcyA8LSBTTUFbIWlzLm5hKFNNQSldCiMgVGhlbiBmb3JtIGEgZm9yZWNhc3Q6ClNNQTNfZm9yZWNhc3QgPC0gdHMocmVwKFNNQV9ub19OQXNbbGVuZ3RoKFNNQV9ub19OQXMpXSwxMiksIGZyZXF1ZW5jeT0xMikKYGBgCgojIyMgRXJyb3IgTWVhc3VyZXMKClRoaXMgc2VjdGlvbiB3aWxsIHV0aWxpemUgdGhlIHByZXZpb3VzbHkgY3JlYXRlZCBmb3JlY2FzdHMgZm9yIHRoZSBTTUEgdG8gZGVtb25zdHJhdGUgaG93IGVycm9yIG1lYXN1cmVzIGNhbiBiZSBjYWxjdWxhdGVkIGluIFIuIFNwZWNpZmljYWxseSwgdGhlc2UgbWV0cmljcyB3aWxsIGJlIGNvbXB1dGVkIHNvbGVseSBvbiB0aGUgdGVzdCBzZXQuCgpUaGUgZmlyc3Qgc3RlcCBjb25zaXN0cyBvZiBoYXZpbmcgeW91ciBlcnJvcnMgY3JlYXRlZC4gUmVjYWxsIHRoYXQgdGhlIGVycm9ycyBhcmUganVzdCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBhY3R1YWxzIGFuZCB0aGUgZm9yZWNhc3RzICRlX3Q9eV90LVxoYXR7eX1fe3R8dC0xfSQ6CgpgYGB7cn0KU01BM19lcnJvcnMgPC0gdGVzdCAtIFNNQTNfZm9yZWNhc3QKYGBgCgojIyMjIE1FCgoqKk1lYW4gRXJyb3IgKE1FKSoqOiBpdCBpcyB0aGUgYXZlcmFnZSBvZiB0aGUgZXJyb3JzIGRlZmluZWQgYXM6IAoKJCRNRSA9IFxmcmFjezF9e219XHN1bV57bX1fe2k9MX1lX3QkJApUaGUgTUUgY2FuIGJlIGNhbGN1bGF0ZWQgYXM6CgpgYGB7cn0KU01BM19NRSA8LSBtZWFuKFNNQTNfZXJyb3JzKQpgYGAKCiMjIyMgTVNFCgoqKk1lYW4gU3F1YXJlZCBFcnJvciAoTVNFKSoqOiBUaGlzIGlzIHRoZSBhdmVyYWdlIG9mIHRoZSBzcXVhcmVkIGVycm9ycyBkZWZpbmVkIGFzOgoKJCRNU0UgPSBcZnJhY3sxfXttfVxzdW1ee219X3tpPTF9ZV90XjIkJAoKVGhlIE1TRSBjYW4gYmUgY2FsY3VsYXRlZCBhczoKCmBgYHtyfQpTTUEzX01TRSA8LSBtZWFuKFNNQTNfZXJyb3JzIF4gMikKYGBgCgojIyMjIE1BRQoKKipNZWFuIEFic29sdXRlIEVycm9yIChNQUUpKio6IFRoaXMgaXMgZXF1YWwgdG8gdGhlIGF2ZXJhZ2Ugb2YgdGhlIGFic29sdXRlIHZhbHVlIG9mIGVycm9ycyBkZWZpbmVkIGFzOgoKJCRNQUUgPSBcZnJhY3sxfXttfVxzdW1ee219X3tpPTF9fGVfdHwkJAoKVGhlIE1BRSBjYW4gYmUgY2FsY3VsYXRlZCBhczoKCmBgYHtyfQpTTUEzX01BRSA8LSBtZWFuKGFicyhTTUEzX2Vycm9ycykpCmBgYAoKIyMjIyBNQVBFCgoqKk1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvciAoTUFQRSkqKjogVGhlIE1BUEUgaXMgZXhwcmVzc2VkIGluIHBlcmNlbnRhZ2VzLCB0aHVzIHRoZSBtdWx0aXBsaWNhdGlvbiBieSAxMDAuIEl0IGlzIGRlZmluZWQgYXM6CgokJE1BUEUgPSAxMDAgKiBcZnJhY3sxfXttfVxzdW1ee219X3tpPTF9XGZyYWN7fGVfdHx9e3x5X3R8fSQkCgpUaGUgTUFQRSBjYW4gYmUgY2FsY3VsYXRlZCBhczoKCmBgYHtyfQpTTUEzX01BUEUgPC0gMTAwICogbWVhbihhYnMoU01BM19lcnJvcnMpL3Rlc3QpCmBgYAoKIyMgRml0dGluZyBTaW1wbGUgRXhwb25lbnRpYWwgU21vb3RoaW5nIChMZXZlbCkKClRoZSBsYXN0IG1ldGhvZCBpcyBTaW1wbGUgRXhwb25lbnRpYWwgU21vb3RoaW5nIChTRVMpIG1ldGhvZC4gVGhlIFNFUyBpcyBnaXZlbiBieToKCiQkXGhhdHt5fV97dCsxfHR9ID0gXGFscGhhIHlfdCsoMS1cYWxwaGEpXGhhdHt5fV97dHx0LTF9ICQkCgp3aGVyZSAkXGFscGhhJCBpcyB0aGUgc21vb3RoaW5nIHBhcmFtZXRlciwgd2hpY2ggaXMgdXN1YWxseSBhc3N1bWVkIHRvIGxpZSBpbiBbMCwgMV0gcmVnaW9uLgoKVGhlIGZpbmFsIG1ldGhvZCwgdGhyb3VnaCB3aGljaCBSIHBvc3Nlc3NlcyBhIGNvbnNpZGVyYWJsZSBzdXBlcmlvcml0eSBpbiBwcm9kdWNpbmcgb3ZlciBFeGNlbCwgaXMgRXhwb25lbnRpYWwgU21vb3RoaW5nLiBGb3IgdGhpcyBleGFtcGxlLCBzaW5jZSB0aGVyZSB3ZXJlICoqbm8gcmVndWxhciBjb21wb25lbnRzKiogaW4gb3VyIHRpbWUgc2VyaWVzLCB0aGUgKipTaW1wbGUgRXhwb25lbnRpYWwgU21vb3RoaW5nKiogd2lsbCBiZSB1c2VkLiBUaGVyZSBpcyBgZXRzKClgIGZ1bmN0aW9uIGluICpmb3JlY2FzdCogcGFja2FnZSBhbmQgYGVzKClgIGZ1bmN0aW9uIGluICpzbW9vdGgqIHBhY2thZ2UuIFRoZXkgYm90aCBmaXQgZXhwb25lbnRpYWwgc21vb3RoaW5nIGluIGEgc28gY2FsbGVkIOKAnFN0YXRlLXNwYWNlIGZvcm3igJ0sIGJ1dCBoYXZlIHNvbWUgZGlmZmVyZW5jZXMgaW4gdGhlIGJlaGF2aW91ciBhbmQgYXZhaWxhYmxlIGZlYXR1cmVzLgoKIyMjIGV0cygpIGZ1bmN0aW9uCgpBcyB5b3UgcmVjYWxsLCB0aGUgU0VTIGlzIGRlZmluZWQgYnkgdGhlICRcYWxwaGEkIHBhcmFtZXRlci4gSWYgeW91IHdhbnQgdG8gY29tcHV0ZSBhIFNFUyB3aXRoICRcYWxwaGEkLCBmb3IgZXhhbXBsZSwgcHJlZGVmaW5lZCAkXGFscGhhPSAwLjE1JCAsIHRoZW4geW91IHdvdWxkIHR5cGUgdGhlIGZvbGxvd2luZzoKCmBgYHtyfQpFVFNfQU5OXzAuMTUgPC0gZXRzKHRyYWluLCBtb2RlbD0iQU5OIiwgYWxwaGE9MC4xNSkKRVRTX0FOTl8wLjE1CmBgYAoKLSBUaGUgZmlyc3QgcGFyYW1ldGVyIGlzIHRoZSBkYXRhIHRvIHdoaWNoIFNFUyBpcyBhcHBsaWVkLiAKCi0gVGhlIGBtb2RlbGAgcmVmZXJzIHRvIHRoZSBFcnJvciBUcmVuZCBTZWFzb25hbGl0eSB0YXhvbm9teS4gVGhlIEVycm9yIGNhbiBiZSBlaXRoZXIgQWRkaXRpdmUgKEEpIG9yIE11bHRpcGxpY2F0aXZlIChNKS4gU2luY2UgdGhlcmUgaXMgbm8gVHJlbmQgb3IgU2Vhc29uYWxpdHksIHRoZXkgYXJlIHJlcHJlc2VudGVkIGJ5IHRoZSBsZXR0ZXIg4oCYTuKAmSwgd2hpY2ggc3RhbmRzIGZvciDigJxOb25l4oCdLgoKLSBTbyBpZiB5b3Ugd2FudCB0byB0cnkgdG8gZml0IHRoZSBzYW1lIFNFUyB3aXRoIE11bHRpcGxpY2F0aXZlIGVycm9yLCB0aGVuIHlvdSB3b3VsZCBzdWJzdGl0dXRlIHRoZSBgbW9kZWwgPSDigJxBTk7igJ1gIHBhcnQgd2l0aCBgbW9kZWwgPSDigJxNTk7igJ1gLgoKLSBUaGUgbGFzdCBwYXJhbWV0ZXIgaW52b2x2ZWQgaXMgdGhlIM6xIHdoaWNoIGlzIHNldCB0byAwLjE1LiBJZiB5b3Ugd2FudGVkIHRoZSAkXGFscGhhJCBwYXJhbWV0ZXIgdG8gYmUgb3B0aW1pc2VkLCB0aGVuIHRoaXMgYXJndW1lbnQgc2hvdWxkIGJlIG9taXR0ZWQ6IGBFVFNfQU5OX29wdCA8LSBldHModHJhaW4sICJBTk4iKWAKCmBgYHtyfQpFVFNfTU5OXzAuMTUgPC0gZXRzKHRyYWluLCAiTU5OIiwgYWxwaGE9MC4xNSkKRVRTX01OTl8wLjE1CmBgYAoKYGBge3J9CkVUU19BTk5fb3B0IDwtIGV0cyh0cmFpbiwgIkFOTiIpCkVUU19BTk5fb3B0CmBgYAoKSW4gb3JkZXIgdG8gcmV0cmlldmUgdGhlICRcYWxwaGEkIHBhcmFtZXRlciwgdGhlIGZvbGxvd2luZyBzdGF0ZW1lbnQgY291bGQgYmUgdXNlZDoKCmBgYHtyfQpjb2VmKEVUU19BTk5fb3B0KQpgYGAKCi0gVGhpcyB3aWxsIHJldHVybiBhIHZlY3RvciB3aXRoICRcYWxwaGEkIHBhcmFtZXRlciBhcyBhIGZpcnN0IGVsZW1lbnQgYW5kIHRoZSBpbml0aWFsIHZhbHVlIG9mIFNFUyBhcyBhIHNlY29uZC4KCk5vdyB3ZSBuZWVkIHRvIHByb2R1Y2UgZm9yZWNhc3RzLiBJbiBvcmRlciB0byBnZW5lcmF0ZSB0aGVtLCB5b3Ugd2lsbCBuZWVkIHRvIHVzZSBgZm9yZWNhc3QoKWAgZnVuY3Rpb24gaW4gc2ltaWxhciBmYXNoaW9uIHRvIHRoZSBOYWl2ZSBtZXRob2QuIFRoZSBiZWxvdyBleGFtcGxlIHdpbGwgYmUgdXNlZCBmb3IgdGhlIGBFVFMgQU5OIDAuMTVgIG1ldGhvZCBjcmVhdGVkIGluIHRoZSBiZWdpbm5pbmcsIHdoZXJlIHRoZSAkXGFscGhhJCB3YXMgcHJlc2V0IGF0IDAuMTUuIEZpbmFsbHksIHdlIGNhbiBwbG90IHRoZSBkYXRhLCBmaXR0ZWQgdmFsdWVzIGFuZCB0aGUgZm9yZWNhc3QgdXNpbmcgdGhlIGZvbGxvd2luZyBjb21tYW5kOgoKYGBge3J9CkVUU19BTk5fMC4xNV9mb3JlY2FzdCA8LSBmb3JlY2FzdChFVFNfQU5OXzAuMTUsIGg9aCkkbWVhbgpwbG90KGZvcmVjYXN0KEVUU19BTk5fMC4xNSwgaD1oKSkKYGBgCgojIyMgZXMoKSBmdW5jdGlvbgoKQXMgd2UgaGF2ZSBwb2ludGVkIG91dCBiZWZvcmUsIHRoZXJlIGlzIGFub3RoZXIgZXhwb25lbnRpYWwgc21vb3RoaW5nIGZ1bmN0aW9uLCBjYWxsZWQgYGVzKClgLiBJdCBoYXMgYSBzaW1pbGFyIHN5bnRheCwgYnV0IHByb2R1Y2VzIG1vcmUgaW5mb3JtYXRpb24uIEluIG9yZGVyIHRvIG9idGFpbiBmb3JlY2FzdCBzaW1pbGFyIHRvIGBFVFMgQU5OIDAuMTVgLCB3ZSBjYW4gdHlwZToKCmBgYHtyfQpFU19BTk5fMC4xNSA8LSBlcyh0cmFpbiwgIkFOTiIsIHBlcnNpc3RlbmNlPTAuMTUsIGg9aCkKRVNfQU5OXzAuMTUKYGBgCgotIEhlcmUgYHBlcnNpc3RlbmNlYCBwYXJhbWV0ZXIgaW5jbHVkZXMgdGhlIHZlY3RvciBvZiBzbW9vdGhpbmcgcGFyYW1ldGVycywgd2hpY2ggaW4gb3VyIGNhc2UgY29uc2lzdHMgb2Ygb25seSBvbmUgdmFsdWUgJFxhbHBoYSQuCgpUaGUgb3B0aW1pc2VkIFNFUyBjYW4gYmUgY29uc3RydWN0ZWQgaW4gYSBzaW1pbGFyIHRvIGBldHMoKWAgd2F5LCBhbmQgdGhlIHBhcmFtZXRlcnMgY2FuIGFsc28gYmUgcmV0cmlldmVkIHZpYSBgY29lZigpYCBmdW5jdGlvbjoKCmBgYHtyfQpFU19BTk5fb3B0IDwtIGVzKHRyYWluLCAiQU5OIiwgaD1oLCBzaWxlbnQ9ImEiKQpjb2VmKEVTX0FOTl9vcHQpCmBgYAoKQW5kIGluIGNhc2UgeW91IG5lZWQgYSBmb3JlY2FzdCBvZiBhIGRpZmZlcmVudCBsZW5ndGggKGxldOKAmXMgc2F5IDE4IG9ic2VydmF0aW9ucyBhaGVhZCksIHlvdSBjYW4gdXNlIGBmb3JlY2FzdCgpYCBmdW5jdGlvbjoKCmBgYHtyfQpFU19BTk5fb3B0X2ZvcmVjYXN0IDwtIGZvcmVjYXN0KEVTX0FOTl9vcHQsIGg9MTgpJG1lYW4KYGBgCgojIyMgSW5pdGlhbGlzaW5nIFNlZWRzCgpUaGUgYWltIG9mIHRoaXMgdGFzayBpcyB0byBhbmFseXNlIHRoZSBpbXBhY3Qgb2YgZGlmZmVyZW50IGluaXRpYWwgdmFsdWVzIG9uIGZpbmFsIGZvcmVjYXN0cyBmb3IgdGhlIFNFUy4gSG93ZXZlciwgd2hlbiB5b3UgdXNlZCB0aGUgYGV0cygpYCBhbmQgYGVzKClgLCB0aGUgaW5pdGlhbCB2YWx1ZSB3YXMgbm8gbG9uZ2VyIGFuIGlzc3VlLiBUaGF0IGlzIGJlY2F1c2UgdGhlIGJvdGggdGhlc2UgZnVuY3Rpb25zIHRyZWF0IHRoZSBzZWVkIGFzIGFuIHVua25vd24gcGFyYW1ldGVyLCBhbmQgdGh1cyBvcHRpbWlzZSBpdCBhbG9uZyB3aXRoIHRoZSBzbW9vdGhpbmcgcGFyYW1ldGVyLgoKVGhlIGBldHMoKWAgZnVuY3Rpb24gZG9lcyBub3QgcGVybWl0IHRoZSB1c2VyIHRvIGlucHV0IHRoZSBpbml0aWFsIHZhbHVlLCBhbmQgc28gZm9yIHRoaXMgZXhlcmNpc2UgdGhlIGBlcygpYCBmdW5jdGlvbiB3aWxsIGJlIHVzZWQgaW5zdGVhZDoKCmBgYHtyfQojIEZpdCBTRVMgd2l0aCBmaXhlZCBpbml0aWFsIHNlZWQKZXNfQU5OX2luaXRpYWxfMSA8LSBlcyhtZWRpdW1fbm9pc2UsIG1vZGVsPSJBTk4iLCBpbml0aWFsPW1lZGl1bV9ub2lzZVsxXSwKICAgICAgICAgICAgICAgICAgICAgICBoPWgsIGhvbGRvdXQ9VFJVRSkKYGBgCgotIEluIGBlcygpYCBmdW5jdGlvbiBpZiB0aGUgYGluaXRpYWxgIGlzIHNldCB0byBhIHZhbHVlLCB0aGVuIGl0IHdpbGwgbm90IGJlIG9wdGltaXNlZCwgYW5kIGluc3RlYWQgdGhlIGZ1bmN0aW9uIHdpbGwgdXNlIHRoZSBudW1iZXIgcHJvdmlkZWQgYnkgdGhlIHVzZXIuIAoKLSBgbWVkaXVtIG5vaXNlWzFdYCBpcyB0aGUgZmlyc3QgZW50cnkgaW4gdGhlIHRyYWluaW5nIHNldCwgYW5kIHNvIHRoZSBtb2RlbCBpcyBpbml0aWFsaXNlZCB3aXRoIHRoaXMgZml4ZWQgdmFsdWUuCgotIFRoZSBwYXJhbWV0ZXIgYGhvbGRvdXRgIGluIGBlcygpYCBmdW5jdGlvbiBkZWZpbmVzLCBpZiB0aGUgZnVuY3Rpb24gbmVlZHMgdG8gc3BsaXQgZGF0YSBpbiB0d28gcGFydHMgYW5kIHByb2R1Y2UgZm9yZWNhc3Qgb24gdGhlIHNlY29uZCAodGVzdCBzZXQpLiBJZiB0aGlzIHBhcmFtZXRlciBpcyBzZXQgdG8gYFRSVUVgLCB0aGVuIHRoZSBmdW5jdGlvbiB3aWxsIHJldHVybiBmb3JlY2FzdCBlcnJvcnMgb24gdGhlIHRlc3Qgc2V0LiBUaGV5IGFyZSBzdG9yZWQgaW4gYCRhY2N1cmFjeWAgdmFyaWFibGU6CgpgYGB7cn0KZXNfQU5OX2luaXRpYWxfMSRhY2N1cmFjeQpgYGAKClNvIGZhciwgeW91IGhhdmUgY2FsY3VsYXRlZCBhIFNFUyBvbiB0aGUgZGF0YSB3aXRoIGEgZml4ZWQgc2VlZC4gVGhpcyB3aWxsIGJlIGRpZmZlcmVudCB0aGFuIGluIGEgY2FzZSBvZiBTRVMgd2l0aCB0aGUgb3B0aW1pc2VkIGluaXRpYWwgdmFsdWU6CgpgYGB7cn0KIyBGaXQgU0VTIHdpdGggb3B0aW1pc2VkIFNlZWQKZXNfQU5OX29wdCA8LSBlcyhtZWRpdW1fbm9pc2UsIG1vZGVsPSJBTk4iLCBoPWgsIGhvbGRvdXQ9VFJVRSkKYGBgCgotIFRoZSBzdGF0ZW1lbnQgcGFzc2VkIGlzIHRoZSBzYW1lIGFzIHRoZSBwcmV2aW91cyBvbmUsIHdpdGggdGhlIGV4Y2VwdGlvbiB0aGF0IHRoZSBgaW5pdGlhbGAgYXJndW1lbnQgaXMgYnkgZGVmYXVsdCBpcyBzZXQgdG8gYE5VTExgLiBCeSBkZWZhdWx0LCB0aGUgYGVzKClgIGZ1bmN0aW9uIG9wdGltaXNlcyB0aGUgc2VlZCBhcyBhIHBhcmFtZXRlci4gSnVzdCBhcyBiZWZvcmUsIGxldCB1cyBzZWUgaG93IHRoZSBtb2RlbCBwZXJmb3JtZWQ6CgpgYGB7cn0KZXNfQU5OX29wdCRhY2N1cmFjeQpgYGAKCiMjIyBCZW5jaG1hcmtpbmcKCkEgY3J1Y2lhbCBwcmFjdGljZSB0aGF0IGlzIHNvbWV0aW1lcyBvdmVybG9va2VkIGluIGZvcmVjYXN0aW5nIGlzIHRvIGJlbmNobWFyayB5b3VyIGZvcmVjYXN0cyB3aXRoIGEgc2ltcGxlIGZvcmVjYXN0aW5nIG1ldGhvZC4gVGhlIGVhc2llc3QgbWV0aG9kIHRvIHVzZSBhcyBhIGJlbmNobWFyayBpcyB0aGUgTmFpdmUuIEhlcmUgd2Ugd2lsbCB1c2UgYGVzKClgIGZ1bmN0aW9uIGZvciB0aGUgc2FtZSBwdXJwb3NlcywgcmVjYWxsaW5nIGZlYXR1cmVzIG9mIFNFUzoKCmBgYHtyfQojIEZpdCBTRVMgd2l0aCBvcHRpbWlzZWQgU2VlZAptZWRpdW1fbm9pc2VfbmFpdmUgPC0gZXMobWVkaXVtX25vaXNlLCBtb2RlbD0iQU5OIiwgcGVyc2lzdGVuY2U9MSwKICAgICAgICAgICAgICAgICAgICAgICAgIGg9aCwgaG9sZG91dD1UUlVFKQpgYGAKCi0gdGhpcyB3YXkgd2Ugc2V0IHNtb290aGluZyBwYXJhbWV0ZXIgZXF1YWwgdG8gb25lLCB3aGljaCBtYWtlcyBFVFMoQSxOLE4pIG1vZGVsIHByb2R1Y2UgTmFpdmUgZm9yZWNhc3RzLgoKYGBge3J9Cm1lZGl1bV9ub2lzZV9uYWl2ZSRhY2N1cmFjeQpgYGAKCiMjIEZpdHRpbmcgRXhwb25lbnRpYWwgU21vb3RoaW5nIE1vZGVscyAoVHJlbmQpCgpJbiBjYXNlcywgd2hlbiB0aGVyZSBpcyBhIHRyZW5kIGluIGRhdGEsIHVuZGVybHlpbmcgdGhlIFNFUywgY2Fubm90IGJlIHVzZWQgZm9yIGZvcmVjYXN0aW5nLiBJbiB0aGlzIGNhc2Ugd2UgbmVlZCBhIGZvcmVjYXN0aW5nIG1ldGhvZCB3aGljaCBjYW4gcHJvZHVjZSB0cmVuZHMuIE9uZSBvZiB0aGVzZSBtZXRob2RzIGlzIEhvbHTigJlzIG1ldGhvZC4gSXQgaGFzIHVuZGVybHlpbmcgc3RhdGlzdGljYWwgbW9kZWwgRVRTKEEsQSxOKSAoQWRkaXRpdmUgZXJyb3IsIEFkZGl0aXZlIHRyZW5kIGFuZCBObyBzZWFzb25hbGl0eSkuCgpJbiBvcmRlciB0byBzZWUgaG93IHRoaXMgbWV0aG9kIHdvcmtzLCB3ZSB3aWxsIHVzZSBhIGRpZmZlcmVudCB0aW1lIHNlcmllcy4gQ29weSB0aGUgZmlsZSBjYWxsZWQgIioqdHJlbmQgZGF0YS5jc3YqKiIgdG8geW91ciB3b3JraW5nIGRpcmVjdG9yeSBhbmQgbG9hZCBpdCBpbnRvIFIuIFNhdmUgdGhlIHJlc3VsdGluZyB0aW1lIHNlcmllcyBpbnRvIHRyZW5kIGRhdGEgdmFyaWFibGUuCgpgYGB7cn0KdHJlbmRfZGF0YSA8LSByZWFkLmNzdigiLi90cmVuZF9kYXRhLmNzdiIpCnRyZW5kX2RhdGEgPC0gdHModHJlbmRfZGF0YSxmcmVxdWVuY3kgPSAxMikKcGxvdCh0cmVuZF9kYXRhKQpgYGAKCk5vdGUgdGhhdCB0aGVyZSBpcyBubyBvYnZpb3VzIHNlYXNvbmFsaXR5IGluIHRoZSBkYXRhLCB3aGljaCBpbXBsaWVzIHRoYXQgYW4gRVRTIG1vZGVsIHdpdGggbm8gc2Vhc29uYWxpdHkgY2FuIGJlIHVzZWQuIFRoZSBkYXRhIGNvbnNpc3RzIG9mIDQ4IG9ic2VydmF0aW9ucy4gQXMgdXN1YWwsIHdlIHdpbGwgdXNlIDM2IG9mIHRoZW0gZm9yIGluLXNhbXBsZSBhbmQgMTIgZm9yIHRoZSBob2xkb3V0LgoKYGBge3J9Cmg9MTIKdHJlbmRfZGF0YV9sZW5ndGggPC0gbGVuZ3RoKHRyZW5kX2RhdGEpCiMgU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdAp0cmVuZF9kYXRhX3RyYWluIDwtIHRzKHRyZW5kX2RhdGFbMTozNl0sIGZyZXF1ZW5jeSA9IDEyKQp0cmVuZF9kYXRhX3Rlc3QgPC0gdHModHJlbmRfZGF0YVszNzp0cmVuZF9kYXRhX2xlbmd0aF0sZnJlcXVlbmN5ID0gMTIpCmBgYAoKTm93IGluIG9yZGVyIHRvIGVzdGltYXRlIHRoZSBwYXJhbWV0ZXJzIGFuZCB0aGUgaW5pdGlhbCBzdGF0ZXMgb2YgdGhpcyBtb2RlbCwgd2Ugd2lsbCB1c2UgdGhlIGBldHMoKWAgZnVuY3Rpb246CgpgYGB7cn0KIyBjYWxjdWxhdGUgSG9sdCBNZXRob2QKZXRzX0FBTiA8LSBldHModHJlbmRfZGF0YV90cmFpbiwgbW9kZWw9IkFBTiIpCmV0c19BQU4KYGBgCgotIEJvdGggc21vb3RoaW5nIHBhcmFtZXRlcnMsICRcYWxwaGEkIGFuZCAkXGJldGEkLCBhcmUgcmVwb3J0ZWQgdG8gYmUgZXF1YWwgdG8gMC4xOS4KCkluIGNhc2UgeW91IG5lZWQgdGhlc2UgcGFyYW1ldGVycywgeW91IGNhbiB1c2UgYGNvZWYoKWAgZnVuY3Rpb246CgpgYGB7cn0KY29lZihldHNfQUFOKQpgYGAKCkp1c3QgbGlrZSBhbnkgb3RoZXIgZm9yZWNhc3RpbmcgbW9kZWwgY29uc3RydWN0ZWQgdXNpbmcgdGhlIGBldHMoKWAgZnVuY3Rpb24gaW4gUiwgdGhlIGZvcmVjYXN0cyBhcmUgcHJvZHVjZWQgYnkgdXNpbmcgdGhlIGZvcmVjYXN0IGZ1bmN0aW9uOgoKYGBge3J9CmZvcmVjYXN0KGV0c19BQU4sIGg9aCkkbWVhbgpwbG90KGZvcmVjYXN0KGV0c19BQU4sIGg9aCkpCmBgYAoKIyMjIERhbXBlZCBUcmVuZApJZiB5b3UgaGF2ZSBwbG90dGVkIHRoZSBkYXRhLCB5b3UgbWF5IGhhdmUgbm90aWNlZCB0aGF0IHRoZSBmb3JlY2FzdCBwcm9kdWNlZCBieSBgRVRTKEEsQSxOKWAgaXMgYmlhc2VkIChpdCBvdmVyc2hvb3RzIHRoZSBkYXRhIGluIHRoZSBob2xkb3V0KS4gVGhpcyBpcyBhIGNvbW1vbiB0aGluZyBpbiBmb3JlY2FzdGluZywgc28gaW4gb3JkZXIgdG8gYWRkcmVzcyB0aGlzIGlzc3VlIGEgZGFtcGVkIHRyZW5kIG1ldGhvZCBoYXMgYmVlbiBwcm9wb3NlZC4gVGhlIHVuZGVybHlpbmcgc3RhdGlzdGljYWwgbW9kZWwgaXMgYEVUUyhBLEFkLE4pYC4KCkluIG9yZGVyIHRvIGZpdCB0aGUgKipEYW1wZWQgdHJlbmQgbWV0aG9kKiosIHRoZSBzYW1lIHN5bnRheCBhcyBiZWZvcmUgY2FuIGJlIHVzZWQsIGJ1dCB3aXRoIGFuIGFkZGl0aW9uYWwgcGFyYW1ldGVyOgoKYGBge3J9CiMgQ2FsY3VsYXRlIGEgRGFtcGVkIEhvbHQgTWV0aG9kCmV0c19BQWROIDwtIGV0cyh0cmVuZF9kYXRhX3RyYWluLCBtb2RlbD0iQUFOIiwgZGFtcGVkPVRSVUUpCmV0c19BQWROCmBgYAoKLSBhIG5ldyBwYXJhbWV0ZXIgJFxwaGkkLCByZXByZXNlbnRpbmcgdGhlIGRhbXBpbmcgcGFyYW1ldGVyLCB3aGljaCBpcyBlcXVhbCB0byAwLjk4LgoKLSBQbGVhc2Ugbm90ZSB0aGF0IHRoZSBkZWZhdWx0IGFyZ3VtZW50IGZvciB0aGUgYGV0cygpYCBmdW5jdGlvbiBpcyBgZGFtcGVkPU5VTExgLCBtZWFuaW5nIHRoYXQgaWYgbm90IHNwZWNpZmllZCwgdGhlIGZ1bmN0aW9uIHdpbGwgaGF2ZSB0byBjaG9vc2UgYmV0d2VlbiBgZGFtcGVkID0gVFJVRWAgYW5kIGBkYW1wZWQgPSBGQUxTRWAsIGJhc2VkIG9uIGFuIEluZm9ybWF0aW9uIENyaXRlcmlvbi4KClRoZSBzeW50YXggZm9yIGBlcygpYCBmdW5jdGlvbiBpcyBkaWZmZXJlbnQsIGFzIGl0IGFjY2VwdHMg4oCcZOKAnSByaWdodCBpbiB0aGUgbmFtZSBvZiB0aGUgbW9kZWwgdGhlIGZvbGxvd2luZyB3YXk6CmBgYHtyfQplc19BQWROIDwtIGVzKHRyZW5kX2RhdGEsIG1vZGVsPSJBQWROIiwgaD1oLCBob2xkb3V0PVRSVUUpCmZvcmVjYXN0KGVzX0FBZE4sIGg9aCkkbWVhbgpwbG90KGZvcmVjYXN0KGVzX0FBZE4sIGg9aCkpCmBgYAoKIyMgTm90YXRpb24gZm9yIEV4cG9uZW50aWFsIFNtb290aGluZyBNb2RlbHMKCkVUUzpFcnJvciwgVHJlbmQsIFNlYXNvbmFsaXR5OwoKLSBmb3IgYE5gOiBOb25lLAoKLSBmb3IgYEFgIEFkZGl0aXZlLAoKLSBmb3IgYEFkYCBBZGRpdGl2ZSBkYW1wZWQsCgotIGZvciBgTWAgTXVsdGlwbGljYXRpdmUsCgotIGZvciBgTWRgIE11bHRpcGxpY2F0aXZlIGRhbXBlZC4KCkV4YW1wbGVzOgoKLSBgRVRTKEEsTixOKWAgQWRkaXRpdmUgZXJyb3IsIG5vIHRyZW5kLCBubyBzZWFzb25hbGl0eSAKCi0gYEVUUyhBLEEsTSlgIEFkZGl0aXZlIGVycm9yLCBhZGRpdGl2ZSB0cmVuZCwgbXVsdGlwbGljYXRpdmUgc2Vhc29uYWxpdHkKCiMjIEZpdHRpbmcgRXhwb25lbnRpYWwgU21vb3RoaW5nIE1vZGVscyAoVHJlbmQgYW5kIFNlYXNvbikKCkluIHRoaXMgc3Vic2VjdGlvbiwgeW91IHdpbGwgc2VlIGhvdyB0byBjYWxjdWxhdGUgYW4gQWRkaXRpdmUgKipIb2x0LVdpbnRlcnMgbWV0aG9kKiouIFJlY2FsbCB0aGF0IHRoaXMgbWV0aG9kIGludm9sdmVzIHRoZSBwcmVzZW5jZSBvZiBib3RoICoqdHJlbmQqKiBhbmQgKipzZWFzb25hbGl0eSoqIGluIHRoZSBzZXJpZXMuCgpDb3B5IHRoZSBgdHJlbmQgc2Vhc29uYWwgZGF0YS5jc3ZgIGZpbGUgaW50byB5b3VyIFdvcmtpbmcgZGlyZWN0b3J5LCBhbmQgcGxvdCB0aGUgZGF0YSBpbiBvcmRlciB0byBhbmFseXNlIGl0IHZpc3VhbGx5LgoKYGBge3J9CnRyZW5kX3NlYXNvbmFsX2RhdGEgPC0gcmVhZC5jc3YoIi4vdHJlbmRfc2Vhc29uYWxfZGF0YS5jc3YiLCBjb2wubmFtZXMgPSBGQUxTRSkKdHJlbmRfc2Vhc29uYWxfZGF0YSA8LSB0cyh0cmVuZF9zZWFzb25hbF9kYXRhLCBmcmVxdWVuY3kgPSAxMikKcGxvdCh0cmVuZF9zZWFzb25hbF9kYXRhKQpgYGAKCi0gVGhlIHBsb3QgaW5kaWNhdGVzIHRoYXQgdGhlcmUgYXJlICoqYm90aCB0cmVuZCBhbmQgc2Vhc29uYWxpdHkqKiBpbiB0aGUgZGF0YS4gVGhpcyBtZWFucyB0aGF0IGEgSG9sdC1XaW50ZXJzIG1ldGhvZCBjYW4gYmUgdXNlZC4KClNwbGl0IHRoZSBkYXRhIGludG8gdHdvIHNldHMgd2l0aCAzNiBhbmQgMTIgb2JzZXJ2YXRpb25zOgoKYGBge3J9CiMgZmluZCB0aGUgdG90YWwgbnVtYmVyIG9mIG9ic2VydmF0aW9ucwp0cmVuZF9zZWFzb25hbF9kYXRhX2xlbmd0aCA8LSBsZW5ndGgodHJlbmRfc2Vhc29uYWxfZGF0YSkKIyB3cml0ZSBkb3duIHNpemUgb2YgdHJhaW5pbmcgc2V0CnRyYWluX2xlbmd0aCA8LSAzNgojIGFuZCB0aGUgZm9yZWNhc3RpbmcgaG9yaXpvbgpoIDwtIDEyCiMgY3JlYXRlIHRoZSB0cmFpbmluZyBzZXQKdHJlbmRfc2Vhc29uYWxfZGF0YV90cmFpbiA8LSB0cyh0cmVuZF9zZWFzb25hbF9kYXRhWzE6dHJhaW5fbGVuZ3RoXSwgZnJlcXVlbmN5PTEyKQojIGNyZWF0ZSB0aGUgdGVzdCBzZXQKdHJlbmRfc2Vhc29uYWxfZGF0YV90ZXN0IDwtIHRyZW5kX3NlYXNvbmFsX2RhdGFbKHRyYWluX2xlbmd0aCsxKTp0cmVuZF9zZWFzb25hbF9kYXRhX2xlbmd0aF0KYGBgCgpUaGUgYWRkaXRpdmUgSG9sdC1XaW50ZXJzIG1ldGhvZCBoYXMgYW4gdW5kZXJseWluZyBgRVRTKEEsQSxBKWAgbW9kZWwuIFRoZXJlIGFyZSBvdGhlciB0eXBlcyBvZiB0cmVuZC1zZWFzb25hbCBtb2RlbHMgaW4gRVRTOiBgRVRTKEEsQWQsQSlgLCBgRVRTKEEsQSxNKWAsIGBFVFMoTSxBLE0pYCBhbmQgc28gb24uIENvbnN0cnVjdGluZyBhIHNlYXNvbmFsIG1vZGVsIGlzIHN0cmFpZ2h0Zm9yd2FyZDoKCmBgYHtyfQojIEZpdCBhIG1vZGVsIHVzaW5nIGV0cygpOgpldHNfQUFBIDwtIGV0cyh0cmVuZF9zZWFzb25hbF9kYXRhX3RyYWluLCBtb2RlbD0iQUFBIiwgZGFtcGVkPUZBTFNFKQpldHNfQUFBCmBgYAoKYGBge3J9CiMgRG8gdGhlIHNhbWUgdXNpbmcgZXMoKToKZXNfQUFBIDwtIGVzKHRyZW5kX3NlYXNvbmFsX2RhdGFfdHJhaW4sIG1vZGVsPSJBQUEiLCBoPWgpCmVzX0FBQQpgYGAKCi0gSWYgeW91IGxvb2sgYXQgdGhlIG91dHB1dCBvZiB0aGVzZSBmdW5jdGlvbiwgeW91IHdpbGwgbm90aWNlIHRoYXQgdGhlcmUgaXMgYSB0aGlyZCBzbW9vdGhpbmcgcGFyYW1ldGVyIG5vdywgY2FsbGVkICRcZ2FtbWEkICh1c2VkIGZvciBzZWFzb25hbCBwYXJ0IG9mIHRoZSBtb2RlbCkgYW5kIHRoYXQgbm93IHRoZXJlIGlzIGEgc2V0IG9mIGluaXRpYWwgc2Vhc29uYWwgY29tcG9uZW50cy4gCgotIE92ZXJhbGwgYEVUUyhBLEEsQSlgIGhhcyAxOCBwYXJhbWV0ZXJzICgzIHNtb290aGluZyBwYXJhbWV0ZXJzLCAxMiBpbml0aWFsIHNlYXNvbmFsIGluZGljZXMsIDIgaW5pdGlhbHMgZm9yIGxldmVsIGFuZCB0cmVuZCBhbmQgYW4gZXN0aW1hdGUgb2Ygc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoZSBlcnJvcikuCgojIyMgU2VsZWN0aW5nIHRoZSBiZXN0IG1vZGVsIGJhc2VkIG9uIG9wdGltaXNhdGlvbgoKU28gZmFyLCB5b3UgaGF2ZSBzZWVuIGhvdyB0byBmaXQgc3BlY2lmaWMgbW9kZWxzIHVzaW5nIGBldHMoKWAgYW5kIGBlcygpYCBmdW5jdGlvbnMgaW4gUi4gQm90aCBvZiB0aGVtIGFsc28gYWxsb3cgc2VsZWN0aW5nIHRoZSBtb3N0IGFwcHJvcHJpYXRlIG1vZGVsIGZvciB0aGUgZGF0YS4gVGhpcyBpcyBkb25lIHZpYSBmaXR0aW5nIGEgc2V0IG9mIHRoZW0gYW5kIHNlbGVjdGluZyB0aGUgb25lIHRoYXQgaGFzIGEgbWluaW1hbCAqKkluZm9ybWF0aW9uIENyaXRlcmlvbioqLiBUaGlzIGNhbiBlaXRoZXIgYmUgZG9uZSB1c2luZyBgQUlDY2AsIGBBSUNgIG9yIGBCSUNgLiBCeSBkZWZhdWx0IGJvdGggYGV0cygpYCBhbmQgYGVzKClgIHVzZSBgQUlDY2AuCgpJbiBvcmRlciB0byB1c2UgdGhpcyBmZWF0dXJlLCB5b3UgbmVlZCB0byBzZXQgbW9kZWwgYXJndW1lbnQgZXF1YWwgdG8gYCJaWloiYDoKCmBgYHtyfQojIENhbGN1bGF0ZSBhbiBPcHRpbWl6ZWQgRVRTIE1ldGhvZCB1c2luZyBldHMoKQpldHNfWlpaIDwtIGV0cyh0cmVuZF9zZWFzb25hbF9kYXRhX3RyYWluLCBtb2RlbD0iWlpaIikKZXRzX1paWgpgYGAKCmBgYHtyfQojIERvIHRoZSBzYW1lIHVzaW5nIGVzKCkKZXNfWlpaIDwtIGVzKHRyZW5kX3NlYXNvbmFsX2RhdGFfdHJhaW4sIG1vZGVsPSJaWloiKQplc19aWloKYGBgCgotIEJ5IHNldHRpbmcgdGhlIG1vZGVsIGFyZ3VtZW50IGVxdWFsIHRvIGAiWlpaImAsIHlvdSBhc2sgZnVuY3Rpb24gdG8gZXhwbG9yZSBtYW55IG1vZGVscyBhbmQgcmV0dXJuIHRoZSBvcHRpbWFsIG9uZS4gSW4gb3VyIGNhc2UsIHRoZSBvcHRpbWFsIG1vZGVsIGZvciB0aGUgZGF0YSBpcyBFVFMoQSxNZCxBKS4KCklmIHlvdSBhcmUgc3VyZSB0aGF0IHNvbWUgY29tcG9uZW50cyBoYXZlIGEgc3BlY2lmaWMgdHlwZSBpbiB5b3VyIGRhdGEsIHlvdSBjYW4gcGFzcyB0aGVtIGluIHRoZSBmdW5jdGlvbi4gRm9yIGV4YW1wbGUsIHdlIGNhbiBiZSBjZXJ0YWluIHRoYXQgdGhlcmUgaXMgbm8gc2Vhc29uYWxpdHkgaW4gdGhlIHRyZW5kIGRhdGEsIHRoYXQgd2UgdXNlZCBpbiBwcmV2aW91cyBzZWN0aW9ucy4gU28gd2UgY2FuIGFzayB0byBleGNsdWRlIGRpZmZlcmVudCB0eXBlcyBvZiBzZWFzb25hbCBtb2RlbHMgZnJvbSB0aGUgc2V0IG9mIG1vZGVscyAodGhpcyBzaG91bGQgc3BlZWQgdXAgdGhlIGNhbGN1bGF0aW9ucyBhbmQgd2lsbCByZXN1bHQgaW4gYSBub24tc2Vhc29uYWwgRVRTIG1vZGVsLik6CgpgYGB7cn0KIyBTZWxlY3QgdGhlIG1vc3QgYXBwcm9wcmlhdGUgbm9uLXNlYXNvbmFsIG1vZGVsIHdpdGggZXRzKCkKZXRzX1paTiA8LSBldHModHJlbmRfZGF0YV90cmFpbiwgbW9kZWw9IlpaTiIpCmV0c19aWk4KYGBgCgpgYGB7cn0KIyBUaGUgc2FtZSB0aGluZyB3aXRoIGVzKCkKZXNfWlpOIDwtIGVzKHRyZW5kX2RhdGFfdHJhaW4sIG1vZGVsPSJaWk4iLCBzaWxlbnQ9ImEiKQplc19aWk4KYGBgCgojIyBSb2xsaW5nIG9yaWdpbgoKU28gZmFyLCB3ZSBoYXZlIHByb2R1Y2VkIGZvcmVjYXN0cyBmcm9tIG9uZSBhbmQgdGhlIHNhbWUgcG9pbnQgaW4gdGltZSwgZnJvbSB0aGUgc2FtZSBvcmlnaW4uIFRoZSB0ZWNobmlxdWUgdGhhdCB3ZSB1c2VkIGlzIGNhbGxlZCDigJxzdGF0aWMgb3JpZ2lu4oCdLiBUaGUgaWRlYSBiZWhpbmQg4oCccm9sbGluZyBvcmlnaW7igJ0gaXMgdG8gcHJvZHVjZSBzZXZlcmFsIGZvcmVjYXN0cyBieSBtb3ZpbmcgdGhlIG9yaWdpbiBvZiB0aGUgZm9yZWNhc3QuCgohW10oLi9Sb2xsaW5nIE9yaWdpbiBFdmFsdWF0aW9uLnBuZykKClRoaXMgaXMgYW4gaXRlcmF0aXZlIHByb2NlZHVyZSBhbmQgb24gZWFjaCBpdGVyYXRpb24gdGhlICoqdHJhaW4gc2V0IHNpemUgaXMgaW5jcmVhc2VkIGJ5IG9uZSoqIG9ic2VydmF0aW9uLCB3aGlsZSB0aGUgKip0ZXN0IHNldCBzaXplIGlzIGRlY3JlYXNlZCBieSBvbmUqKi4KCkxldOKAmXMgdHJ5IHRoaXMgdGVjaG5pcXVlIG9uIHRoZSBleGFtcGxlIG9mIOKAnG1lZGl1bV9ub2lzZeKAnSBkYXRhOgoKYGBge3J9CiMgTG9hZCB0aGUgRGF0YQptZWRpdW1fbm9pc2UgPC0gcmVhZC5jc3YoIi4vbWVkaXVtX25vaXNlLmNzdiIsIGhlYWRlcj1GQUxTRSkKIyBDb252ZXJ0IHRvIFRpbWUgU2VyaWVzCm1lZGl1bV9ub2lzZSA8LSB0cyhtZWRpdW1fbm9pc2UsIGZyZXF1ZW5jeT0xMiwgc3RhcnQ9YygyMDEyLDEpKQpwbG90KG1lZGl1bV9ub2lzZSkKYGBgCgpXZSB3aWxsIHByb2R1Y2UgdGVuIGZvcmVjYXN0cyB1c2luZyBFVFMoQSxOLE4pIG1vZGVsIHdpdGggdGhlICoqaG9yaXpvbiBvZiAxMioqLiBGaXJzdCwgd3JpdGUgZG93biB0aGUgbGVuZ3RoIG9mIHRoZSBzZXJpZXMgKDQ4KS4gTm93IHdlIG5lZWQgdG8gZmlndXJlIG91dCB0aGUgKipsZW5ndGhzIG9mIHRyYWluIGFuZCB0ZXN0IHNldHMqKi4gSW4gb3JkZXIgdG8gZG8gdGhhdCB5b3UgY2FuIHVzZSB0aGUgZm9sbG93aW5nIGZvcm11bGFlOgoKJCRUXzA9VC1oLW4rMSQkCgokJFRfMT1oK24tMSQkCgp3aGVyZSAkVF8wJCBpcyB0aGUgc2l6ZSBvZiB0cmFpbiBzZXQsICRUXzEkIGlzIHRoZSBzaXplIG9mIHRlc3Qgc2V0LCAkaCQgaXMgZm9yZWNhc3RpbmcgaG9yaXpvbiBhbmQgJG4kIGlzIG51bWJlciBvZiBvcmlnaW5zLgoKTm93IGxldCB1cyBzZXQgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cywgYW5kIHdlIGRvIG5vdCBuZWVkIHRvIHByZXNlcnZlIHRoZSB0aW1lIHNlcmllcyBzdHJ1Y3R1cmUgb2YgdGhlIHRlc3Qgc2V0LCBzbyB3ZSBjYW4gc2ltcGx5IHNhdmUgaXQgYXMgYSB2ZWN0b3I6CgoKYGBge3J9CiMgU2V0IGhvcml6b24gYW5kIG51bWJlciBvZiByb2xsaW5nIG9yaWdpbnMKaCA8LSAxMgpvcmlnaW5zIDwtIDEwCm1lZGl1bV9ub2lzZV9sZW5ndGggPC0gbGVuZ3RoKG1lZGl1bV9ub2lzZSkKdHJhaW5fbGVuZ3RoIDwtIG1lZGl1bV9ub2lzZV9sZW5ndGggLSBoIC0gb3JpZ2lucyArIDEKdGVzdF9sZW5ndGggPC0gaCArIG9yaWdpbnMgLSAxCm1lZGl1bV9ub2lzZV90cmFpbiA8LSB0cyhtZWRpdW1fbm9pc2VbMTp0cmFpbl9sZW5ndGhdLAogICAgICAgICAgICAgICAgICAgICAgICAgZnJlcXVlbmN5ID0gZnJlcXVlbmN5KG1lZGl1bV9ub2lzZSksCiAgICAgICAgICAgICAgICAgICAgICAgICBzdGFydCA9IHN0YXJ0KG1lZGl1bV9ub2lzZSkpCm1lZGl1bV9ub2lzZV90ZXN0IDwtIG1lZGl1bV9ub2lzZVsodHJhaW5fbGVuZ3RoKzEpOm1lZGl1bV9ub2lzZV9sZW5ndGhdCmBgYAoKTm93IHRoYXQgd2UgbWFuYWdlZCB0byBzcGxpdCB0aGUgZGF0YSBpbnRvIHR3byBwYXJ0cywgd2Ugd2lsbCBwcmVwYXJlICoqbWF0cml4KiogZm9yIGZvcmVjYXN0cyBhbmQgZm9yIHRoZSBob2xkb3V0IHZhbHVlcywgc28gdGhhdCB3ZSBjYW4gZWFzaWx5IGNhbGN1bGF0ZSBhbnkgZXJyb3Igd2Ugd2FudC4gSW4gb3JkZXIgdG8gcmVtZW1iZXIsIHdoYXQgaXMgd2hhdCwgd2Ugd2lsbCBnaXZlIG5hbWVzIHRvIHRoZSByb3dzIGFuZCBjb2x1bW5zOgoKYGBge3J9Cm1lZGl1bV9ub2lzZV9mb3JlY2FzdHMgPC0gbWF0cml4KE5BLCBucm93PW9yaWdpbnMsIG5jb2w9aCkKbWVkaXVtX25vaXNlX2hvbGRvdXQgPC0gbWF0cml4KE5BLCBucm93PW9yaWdpbnMsIG5jb2w9aCkKY29sbmFtZXMobWVkaXVtX25vaXNlX2ZvcmVjYXN0cykgPC0gcGFzdGUwKCJob3Jpem9uIixjKDE6aCkpCnJvd25hbWVzKG1lZGl1bV9ub2lzZV9mb3JlY2FzdHMpIDwtIHBhc3RlMCgib3JpZ2luIixjKDE6b3JpZ2lucykpCmRpbW5hbWVzKG1lZGl1bV9ub2lzZV9ob2xkb3V0KSA8LSBkaW1uYW1lcyhtZWRpdW1fbm9pc2VfZm9yZWNhc3RzKQpgYGAKCkZpbmFsbHkgd2UgY2FuIHN0YXJ0IHByb2R1Y2luZyBmb3JlY2FzdHMgdXNpbmcgcm9sbGluZyBvcmlnaW4uIFdlIHdpbGwgbmVlZCBhIGxvb3AgZm9yIHRoYXQ6CgpgYGB7cn0KZm9yKGkgaW4gMTpvcmlnaW5zKXsKIyBDcmVhdGUgYSB0cyBvYmplY3Qgb3V0IG9mIHRoZSBtZWRpdW1fbm9pc2UgZGF0YQogICAgb3VyX3RyYWluX3NldCA8LSB0cyhtZWRpdW1fbm9pc2VbMToodHJhaW5fbGVuZ3RoK2ktMSldLAogICAgICAgICAgICAgICAgICAgICAgICBmcmVxdWVuY3k9ZnJlcXVlbmN5KG1lZGl1bV9ub2lzZSksCiAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0PXN0YXJ0KG1lZGl1bV9ub2lzZSkpCiMgV3JpdGUgZG93biB0aGUgaG9sZG91dCB2YWx1ZXMgZnJvbSB0aGUgdGVzdCBzZXQKICAgIG1lZGl1bV9ub2lzZV9ob2xkb3V0W2ksXSA8LSBtZWRpdW1fbm9pc2VfdGVzdFtpLTErKDE6aCldCiMgUHJvZHVjZSBmb3JlY2FzdHMgYW5kIHdyaXRlIHRoZW0gZG93bgogICAgbWVkaXVtX25vaXNlX2ZvcmVjYXN0c1tpLF0gPC0gZm9yZWNhc3QoZXRzKG91cl90cmFpbl9zZXQsIkFOTiIpLGg9aCkkbWVhbgp9CmBgYAoKQWZ0ZXIgdGhhdCB3ZSB3aWxsIGhhdmUgdHdvIG1hdHJpY2VzIHdpdGggZm9yZWNhc3RzIGFuZCBob2xkb3V0cy4gWW91IGNhbiB0aGVuIHdvcmsgd2l0aCB0aGVtLCBjYWxjdWxhdGluZyBkaWZmZXJlbnQgdHlwZXMgb2YgZXJyb3JzLiBGb3IgZXhhbXBsZSwgKipNQUUqKiBmb3IgZWFjaCBob3Jpem9uIGNhbiBiZSBjYWxjdWxhdGVkIGZvbGxvd2luZyB3YXk6CgpgYGB7cn0KY29sTWVhbnMoYWJzKG1lZGl1bV9ub2lzZV9ob2xkb3V0IC0gbWVkaXVtX25vaXNlX2ZvcmVjYXN0cykpCmBgYAoKTm93IHdlIGhhdmUgdGhlc2UgdHdvIG1hdHJpY2VzIGFuZCBjYW4gY2FsY3VsYXRlIGRpZmZlcmVudCBlcnJvcnMgYW5kIGFuYWx5c2UgaG93IG91ciBmb3JlLSBjYXN0aW5nIG1ldGhvZHMgcGVyZm9ybSBvbiBkaWZmZXJlbnQgaG9yaXpvbnMuIFdlIGNhbiBhbHNvIHVzZSBvdGhlciBzdGF0aXN0aWNhbCBtZXRob2RzIHlvdSBtYXkga25vdyAoY2FsY3VsYXRlIG1lYW4sIG1lZGlhbiwgcXVhbnRpbGVzLCBzdGFuZGFyZCBkZXZpYXRpb24gZXRjKSBmb3IgdGhhdC4KCgo=