library(fpp3)
-- Attaching packages ---------------------------------- fpp3 0.3 --
v tibble      3.0.6     v feasts      0.1.6
v tsibbledata 0.2.0     v fable       0.2.1
-- Conflicts ------------------------------------- fpp3_conflicts --
x lubridate::date()     masks base::date()
x dplyr::filter()       masks stats::filter()
x zoo::index()          masks tsibble::index()
x lubridate::interval() masks tsibble::interval()
x dplyr::lag()          masks stats::lag()

https://robjhyndman.com/seminars/uwa2017/

https://robjhyndman.com/seminars/isi2019workshop/

https://github.com/robjhyndman/ETC3550Slides/tree/fable

https://github.com/rstudio-conf-2020/time-series-forecasting/blob/master/materials/labs.R

http://www.gradaanwr.net/

1 ARIMA models

While exponential smoothing models are based on a description of the trend and seasonality in the data, ARIMA models aim to describe the autocorrelations in the data.

1.1 Stationarity and differencing

A stationary time series is one whose statistical properties do not depend on the time at which the series is observed. Thus, time series with trends, or with seasonality, are not stationary — the trend and seasonality will affect the value of the time series at different times. On the other hand, a white noise series is stationary — it does not matter when you observe it, it should look much the same at any point in time.

A time series with cyclic behaviour (but with no trend or seasonality) is stationary. This is because the cycles are not of a fixed length, so before we observe the series we cannot be sure where the peaks and troughs of the cycles will be.

In general, a stationary time series will have no predictable patterns in the long-term.

1.1.1 Differencing

Transformations such as logarithms can help to stabilise the variance of a time series. Differencing can help stabilise the mean of a time series by removing changes in the level of a time series, and therefore eliminating (or reducing) trend and seasonality.

For a stationary time series, the ACF will drop to zero relatively quickly, while the ACF of non-stationary data decreases slowly. Also, for non-stationary data, the value of \(r_1\) is often large and positive.

# Re-index based on trading days
google_stock <- gafa_stock %>%
  filter(Symbol == "GOOG", year(Date) >= 2015) %>%
  mutate(day = row_number()) %>%
  update_tsibble(index = day, regular = TRUE)
# Filter the year of interest
google_2015 <- google_stock %>% filter(year(Date) == 2015)

google_2015 %>%
  mutate(diff_close = difference(Close)) %>%
  features(diff_close, ljung_box, lag = 10)
p1 <- google_2015 %>% 
  ACF(Close) %>% 
  autoplot()
p2 <- google_2015 %>% 
  ACF(difference(Close)) %>% 
  autoplot()
library(cowplot)

Attaching package: 㤼㸱cowplot㤼㸲

The following object is masked from 㤼㸱package:lubridate㤼㸲:

    stamp
plot_grid(p1, p2, cols=2)
Argument 'cols' is deprecated. Use 'ncol' instead.

The ACF of the differenced Google stock price looks just like that of a white noise series. Only one autocorrelation is outside of the 95% limits, and the Ljung-Box Q^∗ statistic has a p-value of 0.637 (for \(h=10\)). This suggests that the daily change in the Google stock price is essentially a random amount which is uncorrelated with that of previous days.

1.1.2 Random walk model

Random walk models are widely used for non-stationary data, particularly financial and economic data. Random walks typically have:

  • long periods of apparent trends up or down
  • sudden and unpredictable changes in direction.

The forecasts from a random walk model are equal to the last observation, as future movements are unpredictable, and are equally likely to be up or down. Thus, the random walk model underpins naïve forecasts.

1.1.3 Second-order differencing

Occasionally the differenced data will not appear to be stationary and it may be necessary to difference the data a second time to obtain a stationary series.

1.1.4 Seasonal differencing

A seasonal difference is the difference between an observation and the previous observation from the same season.

Forecasts from this model are equal to the last observation from the relevant season. That is, this model gives seasonal naïve forecasts.

Sometimes it is necessary to take both a seasonal difference and a first difference to obtain stationary data.

PBS %>%
  filter(ATC2 == "H02") %>%
  summarise(Cost = sum(Cost)/1e6) %>%
  transmute(
    `Sales ($million)` = Cost,
    `Log sales` = log(Cost),
    `Annual change in log sales` = difference(log(Cost), 12),
    `Doubly differenced log sales` = difference(difference(log(Cost), 12), 1)
  ) %>%
  pivot_longer(-Month, names_to="Type", values_to="Sales") %>%
  mutate(
    Type = factor(Type, levels = c(
      "Sales ($million)",
      "Log sales",
      "Annual change in log sales",
      "Doubly differenced log sales"))
  ) %>%
  ggplot(aes(x = Month, y = Sales)) +
  geom_line() +
  facet_grid(vars(Type), scales = "free_y") +
  labs(title = "Corticosteroid drug sales", y = NULL)

If the data have a strong seasonal pattern, we recommend that seasonal differencing be done first, because the resulting series will sometimes be stationary and there will be no need for a further first difference.

First differences are the change between one observation and the next. Seasonal differences are the change between one year to the next. Other lags are unlikely to make much interpretable sense and should be avoided.

1.1.5 Unit root tests

One way to determine more objectively whether differencing is required is to use a unit root test. These are statistical hypothesis tests of stationarity that are designed for determining whether differencing is required.

we use the Kwiatkowski-Phillips-Schmidt-Shin (KPSS) test. In this test, the null hypothesis is that the data are stationary, and we look for evidence that the null hypothesis is false. Consequently, small p-values (e.g., less than 0.05) suggest that differencing is required. The test can be computed using the unitroot_kpss() function.

google_2015 %>%
  features(Close, unitroot_kpss)

The reported p-value is set to 0.01 if it is less than that, or to 0.1 if it is greater than that. In this case, the test statistic (3.56) is much bigger than the 1% critical value, so the p-value is less than 0.01, indicating that the null hypothesis is rejected. That is, the data are not stationary. We can difference the data, and apply the test again.

google_2015 %>%
  mutate(diff_close = difference(Close)) %>%
  features(diff_close, unitroot_kpss)

This time, the test statistic is tiny, and well within the range we would expect for stationary data, so the p-value is greater than 0.1. We can conclude that the differenced data appear stationary.

This process of using a sequence of KPSS tests to determine the appropriate number of first differences is carried out using the unitroot_ndiffs() feature.

google_2015 %>%
  features(Close, unitroot_ndiffs)

As we saw from the KPSS tests above, one difference is required to make the google_2015 data stationary.

A similar feature for determining whether seasonal differencing is required is unitroot_nsdiffs(), which uses the measure of seasonal strength introduced in Section 4.3 to determine the appropriate number of seasonal differences required. No seasonal differences are suggested if \(F_S<0.64\), otherwise one seasonal difference is suggested.

aus_total_retail <- aus_retail %>%
  summarise(Turnover = sum(Turnover))
aus_total_retail %>%
  mutate(log_turnover = log(Turnover)) %>%
  features(log_turnover, unitroot_nsdiffs)
aus_total_retail %>%
  mutate(log_turnover = difference(log(Turnover), 12)) %>%
  features(log_turnover, unitroot_ndiffs)

Because unitroot_nsdiffs() returns 1 (indicating one seasonal difference is required), we apply the unitroot_ndiffs() function to the seasonally differenced data. These functions suggest we should do both a seasonal difference and a first difference.

1.3 Autoregressive models

In an autoregression model, we forecast the variable of interest using a linear combination of past values of the variable. The term autoregression indicates that it is a regression of the variable against itself.

\(y_{t} = c + \phi_{1}y_{t-1} + \phi_{2}y_{t-2} + \dots + \phi_{p}y_{t-p} + \varepsilon_{t}\)

We refer to this as an AR(p) model, an autoregressive model of order p.

1.4 Moving average models

Rather than using past values of the forecast variable in a regression, a moving average model uses past forecast errors in a regression-like model.

\(y_{t} = c + \varepsilon_t + \theta_{1}\varepsilon_{t-1} + \theta_{2}\varepsilon_{t-2} + \dots + \theta_{q}\varepsilon_{t-q}\)

We refer to this as an MA(q) model, a moving average model of order
q. Of course, we do not observe the values of \(ε_t\), so it is not really a regression in the usual sense.

Notice that each value of \(y_t\) can be thought of as a weighted moving average of the past few forecast errors. However, moving average models should not be confused with the moving average smoothing. A moving average model is used for forecasting future values, while moving average smoothing is used for estimating the trend-cycle of past values.

1.5 Non-seasonal ARIMA models

If we combine differencing with autoregression and a moving average model, we obtain a non-seasonal ARIMA model. ARIMA is an acronym for AutoRegressive Integrated Moving Average (in this context, “integration” is the reverse of differencing). The full model can be written as

\[\begin{equation} y'_{t} = c + \phi_{1}y'_{t-1} + \cdots + \phi_{p}y'_{t-p} + \theta_{1}\varepsilon_{t-1} + \cdots + \theta_{q}\varepsilon_{t-q} + \varepsilon_{t}, \end{equation}\]

We call this an ARIMA(p,d,q) model, where:

p=order of the autoregressive part; d=degree of first differencing involved; q=order of the moving average part.

Special cases of ARIMA models:

White noise ARIMA(0,0,0)
Random walk ARIMA(0,1,0) with no constant
Random walk with drift ARIMA(0,1,0) with a constant
Autoregression ARIMA(p,0,0)
Moving average ARIMA(0,0,q)

Backshift notation:

\[\begin{equation} \begin{array}{c c c c} (1-\phi_1B - \cdots - \phi_p B^p) & (1-B)^d y_{t} &= &c + (1 + \theta_1 B + \cdots + \theta_q B^q)\varepsilon_t\\ {\uparrow} & {\uparrow} & &{\uparrow}\\ \text{AR($p$)} & \text{$d$ differences} & & \text{MA($q$)}\\ \end{array} \end{equation}\]

1.5.1 Egyptian exports

Egyptian exports as a percentage of GDP from 1960 to 2017.

global_economy %>%
  filter(Code == "EGY") %>%
  autoplot(Exports) +
  labs(y = "Percentage of GDP", title = "Egyptian Exports")

The following R code selects a non-seasonal ARIMA model automatically.

fit <- global_economy %>%
  filter(Code == "EGY") %>%
  model(ARIMA(Exports))
report(fit)
Series: Exports 
Model: ARIMA(2,0,1) w/ mean 

Coefficients:
         ar1      ar2      ma1  constant
      1.6764  -0.8034  -0.6896    2.5623
s.e.  0.1111   0.0928   0.1492    0.1161

sigma^2 estimated as 8.046:  log likelihood=-141.57
AIC=293.13   AICc=294.29   BIC=303.43

This is an ARIMA(2,0,1) model:

\(y_t = 2.56 + 1.68 y_{t-1} -0.80 y_{t-2} -0.69 \varepsilon_{t-1} + \varepsilon_{t}\)

Forecasts from the model are shown in Figure 9.8. Notice how they have picked up the cycles evident in the Egyptian economy over the last few decades.

fit %>% forecast(h=10) %>% autoplot(global_economy)

1.5.2 ACF and PACF plots

It is usually not possible to tell, simply from a time plot, what values of p and q are appropriate for the data. However, it is sometimes possible to use the ACF plot, and the closely related PACF plot, to determine appropriate values for p and q.

global_economy %>% filter(Code == "EGY") %>% ACF(Exports) %>% autoplot()

global_economy %>% filter(Code == "EGY") %>% PACF(Exports) %>% autoplot()

If the data are from an ARIMA(p,d,0) or ARIMA(0,d,q) model, then the ACF and PACF plots can be helpful in determining the value of p or q. If p and q are both positive, then the plots do not help in finding suitable values of p and q.

The data may follow an ARIMA(p,d,0) model if the ACF and PACF plots of the differenced data show the following patterns:

  • the ACF is exponentially decaying or sinusoidal;
  • there is a significant spike at lag p in the PACF, but none beyond lag p.

The data may follow an ARIMA(0,d,q) model if the ACF and PACF plots of the differenced data show the following patterns:

  • the PACF is exponentially decaying or sinusoidal;
  • there is a significant spike at lag q in the ACF, but none beyond lag q.

we see that there is a decaying sinusoidal pattern in the ACF, and the PACF shows the last significant spike at lag 4. This is what you would expect from an ARIMA(4,0,0) model.

fit2 <- global_economy %>%
  filter(Code == "EGY") %>%
  model(ARIMA(Exports ~ pdq(4,0,0)))
report(fit2)
Series: Exports 
Model: ARIMA(4,0,0) w/ mean 

Coefficients:
         ar1      ar2     ar3      ar4  constant
      0.9861  -0.1715  0.1807  -0.3283    6.6922
s.e.  0.1247   0.1865  0.1865   0.1273    0.3562

sigma^2 estimated as 7.885:  log likelihood=-140.53
AIC=293.05   AICc=294.7   BIC=305.41

This model is only slightly worse than the ARIMA(2,0,1) model identified by ARIMA() (with an AICc value of 294.70 compared to 294.29).

We can also specify particular values of pdq() that ARIMA can search for. For example, to find the best ARIMA model with \(p\in\{1,2,3\}\), \(q\in\{0,1,2\}\) and d=1, you could use ARIMA(y ~ pdq(p=1:3, d=1, q=0:2)).

1.6 Estimation and order selection

1.6.1 Maximum likelihood estimation

When fable estimates the ARIMA model, it uses maximum likelihood estimation (MLE). This technique finds the values of the parameters which maximise the probability of obtaining the data that we have observed. For ARIMA models, MLE is similar to the least squares estimates that would be obtained by minimising

\(\sum_{t=1}^T\varepsilon_t^2.\)

1.6.2 Information Criteria

Akaike’s Information Criterion (AIC), which was useful in selecting predictors for regression, is also useful for determining the order of an ARIMA model.

Good models are obtained by minimising the AIC, AICc or BIC. Our preference is to use the AICc.

t is important to note that these information criteria tend not to be good guides to selecting the appropriate order of differencing (d) of a model.

1.7 ARIMA modelling in fable

https://otexts.com/fpp3/arima-r.html

1.7.1 Modelling procedure

When fitting an ARIMA model to a set of (non-seasonal) time series data, the following procedure provides a useful general approach.

  1. Plot the data and identify any unusual observations.
  2. If necessary, transform the data (using a Box-Cox transformation) to stabilise the variance.
  3. If the data are non-stationary, take first differences of the data until the data are stationary.
  4. Examine the ACF/PACF: Is an ARIMA(p,d,0) or ARIMA(0,d,q) model appropriate?
  5. Try your chosen model(s), and use the AICc to search for a better model.
  6. Check the residuals from your chosen model by plotting the ACF of the residuals, and doing a portmanteau test of the residuals. If they do not look like white noise, try a modified model.
  7. Once the residuals look like white noise, calculate forecasts.

###Example: Central African Republic exports

global_economy %>%
  filter(Code == "CAF") %>%
  autoplot(Exports)

  1. The time plot shows some non-stationarity, with an overall decline. The improvement in 1994 was due to a new government which overthrew the military junta and had some initial success, before unrest caused further economic decline.

  2. There is no evidence of changing variance, so we will not do a Box-Cox transformation.

  3. To address the non-stationarity, we will take a first difference of the data

global_economy %>%
  filter(Code == "CAF") %>%
  gg_tsdisplay(difference(Exports), plot_type='partial')

  1. The PACF shown in Figure 9.14 is suggestive of an AR(2) model; so an initial candidate model is an ARIMA(2,1,0). The ACF suggests an MA(3) model; so an alternative candidate is an ARIMA(0,1,3).

  2. We fit both an ARIMA(2,1,0) and an ARIMA(0,1,3) model along with two automated model selections, one using the default stepwise procedure, and one working harder to search a larger model space.

caf_fit <- global_economy %>%
  filter(Code == "CAF") %>%
  model(
    arima210 = ARIMA(Exports ~ pdq(2,1,0)),
    arima013 = ARIMA(Exports ~ pdq(0,1,3)),
    stepwise = ARIMA(Exports),
    search = ARIMA(Exports, stepwise=FALSE)
  )
caf_fit
glance(caf_fit) %>% arrange(AICc)

The four models have almost the same AICc values. Of the models fitted, the full search has found that an ARIMA(3,1,0) gives the lowest AICc value, closely followed by the ARIMA(2,1,0) and ARIMA(0,1,3) — the latter two being the models that we guessed from the ACF and PACF plots. The automated stepwise selection has identified an ARIMA(2,1,2) model, which has the highest AICc value of the four models.

  1. The ACF plot of the residuals from the ARIMA(3,1,0) model shows that all autocorrelations are within the threshold limits, indicating that the residuals are behaving like white noise.
caf_fit %>%
  select(search) %>%
  gg_tsresiduals()

A portmanteau test returns a large p-value, also suggesting that the residuals are white noise.

augment(caf_fit) %>%
  filter(.model=='search') %>%
  features(.innov, ljung_box, lag = 10, dof = 3)
  1. Forecasts
caf_fit %>%
  forecast(h=5) %>%
  filter(.model=='search') %>%
  autoplot(global_economy)

Note that the mean forecasts look very similar to what we would get with a random walk (equivalent to an ARIMA(0,1,0)). The extra work to include AR and MA terms has made little difference to the point forecasts in this example, although the prediction intervals are much narrower than for a random walk model.

1.9 Seasonal ARIMA models

A seasonal ARIMA model is formed by including additional seasonal terms in the ARIMA models we have seen so far. It is written as follows:

\(\underbrace{(p, d, q)}\) \(\underbrace{(P, D, Q)_{m}}\)

1.9.1 ACF/PACF

The seasonal part of an AR or MA model will be seen in the seasonal lags of the PACF and ACF. For example, an \(ARIMA(0,0,0)(0,0,1)_12\) model will show:

  • a spike at lag 12 in the ACF but no other significant spikes;
  • exponential decay in the seasonal lags of the PACF (i.e., at lags 12, 24, 36, …).

Similarly, an \(ARIMA(0,0,0)(1,0,0)_12\) model will show:

  • exponential decay in the seasonal lags of the ACF;
  • a single significant spike at lag 12 in the PACF.

1.9.2 Example: Monthly US leisure and hospitality employment

monthly US employment data for leisure and hospitality jobs from January 2000 to September 2019:

leisure <- us_employment %>%
  filter(Title == "Leisure and Hospitality", year(Month) > 2000) %>%
  mutate(Employed = Employed/1000) %>%
  select(Month, Employed)
autoplot(leisure, Employed) +
  labs(y="Millions of people")

We will first take a seasonal difference

leisure %>%
  gg_tsdisplay(difference(Employed, 12), plot_type='partial', lag=36) +
  labs(y="Seasonally differenced")

leisure %>%
  gg_tsdisplay(difference(Employed, 12) %>% difference(), plot_type='partial', lag=36) +
  labs(y="Double differenced")

The significant spike at lag 2 in the ACF suggests a non-seasonal MA(2) component. The significant spike at lag 12 in the ACF suggests a seasonal MA(1) component. Consequently, we begin with an \(ARIMA(0,1,2)(0,1,1)_12\) model, indicating a first difference, a seasonal difference, and non-seasonal MA(2) and seasonal MA(1) component. If we had started with the PACF, we may have selected an \(ARIMA(2,1,0)(0,1,1)_12\) model.

fit <- leisure %>%
  model(
    arima012011 = ARIMA(Employed ~ pdq(0,1,2) + PDQ(0,1,1)),
    arima210011 = ARIMA(Employed ~ pdq(2,1,0) + PDQ(0,1,1)),
    auto = ARIMA(Employed, stepwise=FALSE, approximation=FALSE)
  )
fit
glance(fit) %>% arrange(AICc)

The three fitted models have similar AICc values, with the automatically selected model being a little better. Our second “guess” of \(ARIMA(2,1,0)(0,1,1)_12\) turned out to be very close to the automatically selected model of \(ARIMA(2,1,0)(1,1,1)_12\).

fit %>% select(auto) %>% gg_tsresiduals(lag=36)

One small but significant spike (at lag 11) out of 36 is still consistent with white noise. To be sure, we use a Ljung-Box test, which has a large p-value, confirming that the residuals are similar to white noise. Note that the alternative models also pass this test.

augment(fit) %>% features(.innov, ljung_box, lag=24, dof=4)

Thus, we now have a seasonal ARIMA model that passes the required checks and is ready for forecasting. Forecasts from the model for the next three years

fit %>% forecast(h=36) %>% filter(.model=='auto') %>% autoplot(leisure)

1.9.3 Example: Corticosteroid drug sales in Australia

For our second example, we will try to forecast monthly corticosteroid drug sales in Australia. These are known as H02 drugs under the Anatomical Therapeutic Chemical classification scheme.

h02 <- PBS %>%
  filter(ATC2 == "H02") %>%
  summarise(Cost = sum(Cost)/1e6)
h02 %>%
  mutate(log(Cost)) %>%
  pivot_longer(-Month) %>%
  ggplot(aes(x = Month, y = value)) +
  geom_line() +
  facet_grid(name ~ ., scales = "free_y") +
  labs(y="", title="Cortecosteroid drug scripts (H02)")

There is a small increase in the variance with the level, so we take logarithms to stabilise the variance.

The data are strongly seasonal and obviously non-stationary, so seasonal differencing will be used.

It is not clear at this point whether we should do another difference or not. We decide not to, but the choice is not obvious.

The last few observations appear to be different (more variable) from the earlier data. This may be due to the fact that data are sometimes revised when earlier sales are reported late.

h02 %>% gg_tsdisplay(difference(log(Cost), 12), plot_type='partial', lag_max = 24)

In the plots of the seasonally differenced data, there are spikes in the PACF at lags 12 and 24, but nothing at seasonal lags in the ACF. This may be suggestive of a seasonal AR(2) term. In the non-seasonal lags, there are three significant spikes in the PACF, suggesting a possible AR(3) term. The pattern in the ACF is not indicative of any simple model.

Consequently, this initial analysis suggests that a possible model for these data is an \(ARIMA(3,0,0)(2,1,0)_12\).

fit <- h02 %>%
  model(ARIMA(log(Cost) ~ 0 + pdq(3,0,1) + PDQ(0,1,2)))
report(fit)
Series: Cost 
Model: ARIMA(3,0,1)(0,1,2)[12] 
Transformation: log(Cost) 

Coefficients:
          ar1     ar2     ar3     ma1     sma1     sma2
      -0.1603  0.5481  0.5678  0.3827  -0.5222  -0.1768
s.e.   0.1636  0.0878  0.0942  0.1895   0.0861   0.0872

sigma^2 estimated as 0.004278:  log likelihood=250.04
AIC=-486.08   AICc=-485.48   BIC=-463.28
fit %>% gg_tsresiduals(lag_max=36)

augment(fit) %>%
  features(.innov, ljung_box, lag = 36, dof = 6)

The innovation residuals from this model are shown. There are a few significant spikes in the ACF, and the model fails the Ljung-Box test. The model can still be used for forecasting, but the prediction intervals may not be accurate due to the correlated residuals.

Sometimes it is just not possible to find a model that passes all of the tests.

None of the models considered here pass all of the residual tests. In practice, we would normally use the best model we could find, even if it did not pass all of the tests.

Forecasts from the \(ARIMA(3,0,1)(0,1,2)_12\) model (which has the second lowest RMSE value on the test set, and the best AICc value amongst models with only seasonal differencing)

h02 %>%
  model(ARIMA(log(Cost) ~ 0 + pdq(3,0,1) + PDQ(0,1,2))) %>%
  forecast() %>%
  autoplot(h02) +
    labs(y="H02 sales (million scripts)")

1.10 ARIMA vs ETS

https://otexts.com/fpp3/arima-ets.html

1.10.1 Comparing ARIMA() and ETS() on non-seasonal data

We can use time series cross-validation to compare an ARIMA model and an ETS model.

aus_economy <- global_economy %>% filter(Code == "AUS") %>%
  mutate(Population = Population/1e6)

aus_economy %>%
  slice(-n()) %>%
  stretch_tsibble(.init = 10) %>%
  model(
    ETS(Population),
    ARIMA(Population)
  ) %>%
  forecast(h = 1) %>%
  accuracy(aus_economy)

In this case the ETS model has higher accuracy on the cross-validated performance measures. Below we generate and plot forecasts for the next 5 years generated from an ETS model.

aus_economy %>%
  model(ETS(Population)) %>%
  forecast(h = "5 years") %>%
  autoplot(aus_economy)

1.10.2 Comparing ARIMA() and ETS() on seasonal data

we want to compare seasonal ARIMA and ETS models applied to the quarterly cement production data (from aus_production). Because the series is relatively long, we can afford to use a training and a test set rather than time series cross-validation. The advantage is that this is much faster. We create a training set from the beginning of 1988 to the end of 2007 and select an ARIMA and an ETS model using the ARIMA() and ETS() functions.

cement <- aus_production %>% filter_index("1988 Q1" ~ .)
train <- cement %>% filter_index(. ~ "2007 Q4")
fit_arima <- train %>% model(ARIMA(Cement))
report(fit_arima)
Series: Cement 
Model: ARIMA(1,0,1)(2,1,1)[4] w/ drift 

Coefficients:
         ar1      ma1   sar1     sar2     sma1  constant
      0.8886  -0.2366  0.081  -0.2345  -0.8979    5.3884
s.e.  0.0842   0.1334  0.157   0.1392   0.1780    1.4844

sigma^2 estimated as 11456:  log likelihood=-463.52
AIC=941.03   AICc=942.68   BIC=957.35

The ARIMA model does well in capturing all the dynamics in the data as the residuals seem to be white noise.

gg_tsresiduals(fit_arima, lag_max = 16)

augment(fit_arima) %>%
  features(.innov, ljung_box, lag = 16, dof = 6)

The output below also shows the ETS model selected and estimated by ETS(). This model also does well in capturing all the dynamics in the data, as the residuals similarly appear to be white noise.

fit_ets <- train %>% model(ETS(Cement))
report(fit_ets)
Series: Cement 
Model: ETS(M,N,M) 
  Smoothing parameters:
    alpha = 0.7533714 
    gamma = 0.0001000093 

  Initial states:
        l       s1       s2       s3        s4
 1694.712 1.031179 1.045209 1.011424 0.9121874

  sigma^2:  0.0034

     AIC     AICc      BIC 
1104.095 1105.650 1120.769 
fit_ets %>% gg_tsresiduals(lag_max = 16)

augment(fit_ets) %>%
  features(.innov, ljung_box, lag = 16, dof = 6)

The output below evaluates the forecasting performance of the two competing models over the test set. In this case the ARIMA model seems to be the slightly more accurate model based on the test set RMSE, MAPE and MASE.

# Generate forecasts and compare accuracy over the test set
bind_rows(
  fit_arima %>% accuracy(),
  fit_ets %>% accuracy(),
  fit_arima %>% forecast(h = "2 years 6 months") %>%
    accuracy(cement),
  fit_ets %>% forecast(h = "2 years 6 months") %>%
    accuracy(cement)
)

Below we generate and plot forecasts from the ARIMA model for the next 3 years.

cement %>%
  model(ARIMA(Cement)) %>%
  forecast(h="3 years") %>%
  autoplot(cement)

2 Dynamic regression models

In this chapter, we consider how to extend ARIMA models in order to allow other information to be included in the models.

In this chapter, we will allow the errors from a regression to contain autocorrelation. To emphasise this change in perspective, we will replace \(ε_t\) with \(η_t\) in the equation. The error series \(η_t\) is assumed to follow an ARIMA model. For example, if \(η_t\) follows an ARIMA(1,1,1) model, we can write

\[\begin{align*} y_t &= \beta_0 + \beta_1 x_{1,t} + \dots + \beta_k x_{k,t} + \eta_t,\\ & (1-\phi_1B)(1-B)\eta_t = (1+\theta_1B)\varepsilon_t, \end{align*}\]

where \(ε_t\) is a white noise series.

Notice that the model has two error terms here — the error from the regression model, which we denote by \(η_t\), and the error from the ARIMA model, which we denote by \(ε_t\). Only the ARIMA model errors are assumed to be white noise.

2.1 Estimation

When we estimate the parameters from the model, we need to minimise the sum of squared \(ε_t\) values.

An important consideration when estimating a regression with ARMA errors is that all of the variables in the model must first be stationary.

We therefore first difference the non-stationary variables in the model. It is often desirable to maintain the form of the relationship between \(y_t\) and the predictors, and consequently it is common to difference all of the variables if any of them need differencing. The resulting model is then called a “model in differences,” as distinct from a “model in levels,” which is what is obtained when the original data are used without differencing.

2.2 Regression with ARIMA errors using fable

The fable function ARIMA() will fit a regression model with ARIMA errors if exogenous regressors are included in the formula.

If differencing is specified, then the differencing is applied to all variables in the regression model before the model is estimated.

ARIMA(y ~ x + pdq(1,1,0))

The ARIMA() function can also be used to select the best ARIMA model for the errors. This is done by not specifying the pdq() special. If differencing is required, then all variables are differenced during the estimation process, although the final model will be expressed in terms of the original variables.

The AICc is calculated for the final model, and this value can be used to determine the best predictors. That is, the procedure should be repeated for all subsets of predictors to be considered, and the model with the lowest AICc value selected.

2.2.1 Example: US Personal Consumption and Income

Quarterly changes in personal consumption expenditure and personal disposable income from 1970 to 2016 Q3. We would like to forecast changes in expenditure based on changes in income.

us_change %>%
  gather("var", "value", Consumption, Income) %>%
  ggplot(aes(x = Quarter, y = value)) +
  geom_line() +
  facet_grid(vars(var), scales = "free_y") +
  labs(y = "",
       title = "Quarterly changes in US consumption and personal income")

fit <- us_change %>%
  model(ARIMA(Consumption ~ Income))
report(fit)
Series: Consumption 
Model: LM w/ ARIMA(1,0,2) errors 

Coefficients:
         ar1      ma1     ma2  Income  intercept
      0.7070  -0.6172  0.2066  0.1976     0.5949
s.e.  0.1068   0.1218  0.0741  0.0462     0.0850

sigma^2 estimated as 0.3113:  log likelihood=-163.04
AIC=338.07   AICc=338.51   BIC=357.8

The data are clearly already stationary (as we are considering percentage changes rather than raw expenditure and income), so there is no need for any differencing. The fitted model is

\[\begin{align*} y_t &= 0.595 + 0.198 x_t + \eta_t, \\ \eta_t &= 0.707 \eta_{t-1} + \varepsilon_t -0.617 \varepsilon_{t-1} + 0.207 \varepsilon_{t-2},\\ \varepsilon_t &\sim \text{NID}(0,0.311). \end{align*}\]

We can recover estimates of both the \(η_t\) and \(ε_t\) series using the residuals() function.

bind_rows(
    `Regression Errors` = as_tibble(residuals(fit, type = "regression")),
    `ARIMA Errors` = as_tibble(residuals(fit, type = "innovation")),
    .id = "type"
  ) %>%
  ggplot(aes(x = Quarter, y = .resid)) +
  geom_line() +
  facet_grid(vars(type), scales = "free_y") +
  labs(y = "")

It is the ARIMA errors (the innovations) that should resemble a white noise series.

fit %>% gg_tsresiduals()

augment(fit) %>%
  features(.innov, ljung_box, dof = 5, lag = 8)

The residuals (i.e., the ARIMA errors) are not significantly different from white noise.

2.3 Forecasting

To forecast using a regression model with ARIMA errors, we need to forecast the regression part of the model and the ARIMA part of the model, and combine the results.

As with ordinary regression models, in order to obtain forecasts we first need to forecast the predictors. When the predictors are known into the future (e.g., calendar-related variables such as time, day-of-week, etc.), this is straightforward. But when the predictors are themselves unknown, we must either model them separately, or use assumed future values for each predictor.

2.3.1 US Personal Consumption and Income

We will calculate forecasts for the next eight quarters assuming that the future percentage changes in personal disposable income will be equal to the mean percentage change from the last forty years.

us_change_future <- new_data(us_change, 8) %>%
  mutate(Income = mean(us_change$Income))
forecast(fit, new_data = us_change_future) %>%
  autoplot(us_change) +
  labs(y = "Percentage change")

The prediction intervals for this model are narrower than if we had fitted an ARIMA model without covariates, because we are now able to explain some of the variation in the data using the income predictor.

It is important to realise that the prediction intervals from regression models (with or without ARIMA errors) do not take into account the uncertainty in the forecasts of the predictors. So they should be interpreted as being conditional on the assumed (or estimated) future values of the predictor variables.

2.3.2 Forecasting electricity demand

Daily electricity demand can be modelled as a function of temperature. The higher demand on cold and hot days is reflected in the U-shape

vic_elec_daily <- vic_elec %>%
  filter(year(Time) == 2014) %>%
  index_by(Date = date(Time)) %>%
  summarise(
    Demand = sum(Demand) / 1e3,
    Temperature = max(Temperature),
    Holiday = any(Holiday)
  ) %>%
  mutate(Day_Type = case_when(
    Holiday ~ "Holiday",
    wday(Date) %in% 2:6 ~ "Weekday",
    TRUE ~ "Weekend"
  ))

vic_elec_daily %>%
  ggplot(aes(x = Temperature, y = Demand, colour = Day_Type)) +
  geom_point() +
  labs(y = "Electricity demand (GW)", x = "Maximum daily temperature")

The data stored as vic_elec_daily includes total daily demand, daily maximum temperatures, and an indicator variable for if that day is a public holiday.

vic_elec_daily %>%
  select(Date:Temperature) %>% 
  pivot_longer(-Date, names_to = "type", values_to = "data") %>% 
  ggplot(aes(x = Date, y = data)) +
  geom_line() +
  facet_grid(vars(type), scales = "free_y") +
  labs(y = "Value")

we fit a quadratic regression model with ARMA errors using the ARIMA() function. The model also includes an indicator variable for if the day was a working day or not.

fit <- vic_elec_daily %>%
  model(ARIMA(Demand ~ Temperature + I(Temperature^2) + (Day_Type == "Weekday")))
fit %>% gg_tsresiduals()

augment(fit) %>%
  features(.innov, ljung_box, dof = 8, lag = 14)

There is clear heteroskedasticity in the residuals, with higher variance in January and February, and lower variance in May. The model also has some significant autocorrelation in the residuals, and the histogram of the residuals shows long tails. All of these issues with the residuals may affect the coverage of the prediction intervals, but the point forecasts should still be ok.

Using the estimated model we forecast 14 days ahead starting from Thursday 1 January 2015 (a non-work-day being a public holiday for New Years Day). In this case, we could obtain weather forecasts from the weather bureau for the next 14 days. But for the sake of illustration, we will use scenario based forecasting where we set the temperature for the next 14 days to a constant 26 degrees.

vic_elec_future <- new_data(vic_elec_daily, 14) %>%
  mutate(
    Temperature = 26,
    Holiday = c(TRUE, rep(FALSE, 13)),
    Day_Type = case_when(
      Holiday ~ "Holiday",
      wday(Date) %in% 2:6 ~ "Weekday",
      TRUE ~ "Weekend"
    )
  )
forecast(fit, vic_elec_future) %>%
  autoplot(vic_elec_daily) + labs(y = "Electricity demand (GW)")

The point forecasts look reasonable for the first two weeks of 2015. The slow down in electricity demand at the end of 2014 (due to many people taking summer vacations) has caused the forecasts for the next two weeks to show similarly low demand values.

2.5 Lagged predictors

Sometimes, the impact of a predictor that is included in a regression model will not be simple and immediate.

In these situations, we need to allow for lagged effects of the predictor. Suppose that we have only one predictor in our model. Then a model which allows for lagged effects can be written as

\(y_t = \beta_0 + \gamma_0x_t + \gamma_1 x_{t-1} + \dots + \gamma_k x_{t-k} + \eta_t,\)

where \(η_t\) is an ARIMA process. The value of k can be selected using the AICc, along with thevalues of p and q for the ARIMA error.

2.5.1 Example: TV advertising and insurance quotations

A US insurance company advertises on national television in an attempt to increase the number of insurance quotations provided (and consequently the number of new policies).

Number of quotations and the expenditure on television advertising for the company each month from January 2002 to April 2005:

load(file = "datasets/insurance.rda")

insurance %>%
  pivot_longer(Quotes:TVadverts) %>%
  ggplot(aes(x = Month, y = value)) +
  geom_line() +
  facet_grid(vars(name), scales = "free_y") +
  labs(y = "", title = "Insurance advertising and quotations")

We will consider including advertising expenditure for up to four months; that is, the model may include advertising expenditure in the current month, and the three months before that. When comparing models, it is important that they all use the same training set. In the following code, we exclude the first three months in order to make fair comparisons.

fit <- insurance %>%
  # Restrict data so models use same fitting period
  mutate(Quotes = c(NA, NA, NA, Quotes[4:40])) %>%
  # Estimate models
  model(
    lag0 = ARIMA(Quotes ~ pdq(d = 0) + TVadverts),
    lag1 = ARIMA(Quotes ~ pdq(d = 0) + TVadverts + lag(TVadverts)),
    lag2 = ARIMA(Quotes ~ pdq(d = 0) + TVadverts + lag(TVadverts) + lag(TVadverts, 2)),
    lag3 = ARIMA(Quotes ~ pdq(d = 0) + TVadverts + lag(TVadverts) + lag(TVadverts, 2) + lag(TVadverts, 3))
  )

Next we choose the optimal lag length for advertising based on the AICc.

glance(fit)

The best model (with the smallest AICc value) is lag1 with two predictors; that is, it includes advertising only in the current month and the previous month. So we now re-estimate that model, but using all the available data.

fit_best <- insurance %>%
  model(ARIMA(Quotes ~ pdq(d = 0) + TVadverts + lag(TVadverts)))
report(fit_best)
Series: Quotes 
Model: LM w/ ARIMA(1,0,2) errors 

Coefficients:
         ar1     ma1     ma2  TVadverts  lag(TVadverts)  intercept
      0.5123  0.9169  0.4591     1.2527          0.1464     2.1554
s.e.  0.1849  0.2051  0.1895     0.0588          0.0531     0.8595

sigma^2 estimated as 0.2166:  log likelihood=-23.94
AIC=61.88   AICc=65.38   BIC=73.7

The chosen model has ARIMA(1,0,2) errors. The model can be written as

\(y_t = 2.155 + 1.253 x_t + 0.146 x_{t-1} + \eta_t\)

where \(y_t\) is the number of quotations provided in month t, \(x_t\) is the advertising expenditure in month t,

\(\eta_t = 0.512 \eta_{t-1} + \varepsilon_t + 0.917 \varepsilon_{t-1} + 0.459 \varepsilon_{t-2}\)

We can calculate forecasts using this model if we assume future values for the advertising variable. If we set the future monthly advertising to 8 units, we get the forecasts:

insurance_future <- new_data(insurance, 20) %>%
  mutate(TVadverts = 8)
fit_best %>%
  forecast(insurance_future) %>%
  autoplot(insurance) +
  labs(y = "Quotes",
       title = "Forecast quotes with future advertising set to 8")

3 Forecasting hierarchical and grouped time series

Time series can often be naturally disaggregated by various attributes of interest.

Hierarchical time series often arise due to geographic divisions.

Alternative aggregation structures arise when attributes of interest are crossed rather than nested. We refer to the resulting time series of crossed attributes as “grouped time series.”

More complex structures arise when attributes of interest are both nested and crossed.

Forecasts are often required for all disaggregate and aggregate series, and it is natural to want the forecasts to add up in the same way as the data.

3.1 Hierarchical and grouped time series

3.1.1 Hierarchical time series

https://otexts.com/fpp3/hts.html

For any time t, the observations at the bottom level of the hierarchy will sum to the observations of the series above. For example,

\[\begin{equation} y_{t}=\y{AA}{t}+\y{AB}{t}+\y{AC}{t}+\y{BA}{t}+\y{BB}{t}, \tag{11.1} \end{equation}\]

\[\begin{equation} \y{A}{t}=\y{AA}{t}+\y{AB}{t}+\y{AC}{t}\qquad \text{and} \qquad \y{B}{t}=\y{BA}{t}+\y{BB}{t}. \tag{11.2} \end{equation}\]

3.1.2 Example: Australian tourism hierarchy

Australia is divided into six states and two territories, with each one having its own government and some economic and administrative autonomy. Each of these states can be further subdivided into regions.

The tourism tsibble contains data on quarterly domestic tourism demand, measured as the number of overnight trips Australians spend away from home. The key variables State and Region denote the geographical areas, while a further key Purpose describes the purpose of travel.

#recode State to use abbreviations
tourism <- tsibble::tourism %>%
  mutate(State = recode(State,
    `New South Wales` = "NSW",
    `Northern Territory` = "NT",
    `Queensland` = "QLD",
    `South Australia` = "SA",
    `Tasmania` = "TAS",
    `Victoria` = "VIC",
    `Western Australia` = "WA"
  ))

Using the aggregate_key() function, we can create the hierarchical time series with overnight trips in regions at the bottom level of the hierarchy, aggregated to states, which are aggregated to the national total. A hierarchical time series corresponding to the nested structure is created using a parent/child specification.

tourism_hts <- tourism %>%
  aggregate_key(State / Region, Trips = sum(Trips))
tourism_hts

The new tsibble now has some additional rows corresponding to state and national aggregations for each quarter. Figure 11.3 shows the aggregate total overnight trips for the whole of Australia as well as the states, revealing diverse and rich dynamics. For example, there is noticeable national growth since 2010 and for some states such as the ACT, New South Wales, Queensland, South Australia, and Victoria. There seems to be a significant jump for Western Australia in 2014.

tourism_hts %>%
  filter(is_aggregated(Region)) %>%
  autoplot(Trips) +
  labs(y = "Trips ('000)",
       title = "Australian tourism: national total and states") +
  facet_wrap(vars(State), scales = "free_y", ncol = 3) +
  theme(legend.position = "none")

The seasonal pattern of the northern states, such as Queensland and the Northern Territory, leads to peak visits in winter (corresponding to Q3) due to the tropical climate and rainy summer months. In contrast, the southern states tend to peak in summer (corresponding to Q1).

3.1.3 Grouped time series

With grouped time series, the data structure does not naturally disaggregate in a unique hierarchical manner.

This example shows that there are alternative aggregation paths for grouped structures. For any time t, as with the hierarchical structure,

\[\begin{equation*} y_{t}=y_{AX}{t}+y_{AY,t}+y_{BX,t}+y_{BY,t}. \end{equation*}\]

However, for the first level of the grouped structure,

\[\begin{equation} \y{A}{t}=\y{AX}{t}+\y{AY}{t}\quad \quad \y{B}{t}=\y{BX}{t}+\y{BY}{t} \tag{11.3} \end{equation}\]

but also

\[\begin{equation} \y{X}{t}=\y{AX}{t}+\y{BX}{t}\quad \quad \y{Y}{t}=\y{AY}{t}+\y{BY}{t} \tag{11.4}. \end{equation}\]

Grouped time series can sometimes be thought of as hierarchical time series that do not impose a unique hierarchical structure, in the sense that the order by which the series can be grouped is not unique.

3.1.4 Example: Australian prison population

In this example we consider the Australia prison population.

prison <- readr::read_csv("https://OTexts.com/fpp3/extrafiles/prison_population.csv") %>%
  mutate(Quarter = yearquarter(Date)) %>%
  select(-Date)  %>%
  as_tsibble(key = c(Gender, Legal, State, Indigenous), index = Quarter) %>%
  relocate(Quarter)

-- Column specification ------------------------------------------------------------------
cols(
  Date = col_date(format = ""),
  State = col_character(),
  Gender = col_character(),
  Legal = col_character(),
  Indigenous = col_character(),
  Count = col_double()
)

We create a grouped time series using aggregate_key() with attributes or groupings of interest now being crossed using the syntax attribute1*attribute2 (in contrast to the parent/child syntax used for hierarchical time series). The following code builds a grouped tsibble for the prison data with crossed attributes: gender, legal status and state.

prison_gts <- prison %>%
  aggregate_key(Gender * Legal * State, Count = sum(Count)/1e3)

Using is_aggregated() within filter() is helpful for exploring or plotting the main groups shown in the bottom panels. For example, the following code plots the total numbers of female and male prisoners across Australia.

prison_gts %>%
  filter(!is_aggregated(Gender), is_aggregated(Legal), is_aggregated(State)) %>%
  autoplot(Count) +
  labs(y = "Number of prisoners ('000)")

Plots of other group combinations can be obtained:

prison_gts %>%
  filter(!is_aggregated(Gender), !is_aggregated(Legal), !is_aggregated(State)) %>%
  mutate(Gender = as.character(Gender)) %>%
  ggplot(aes(x = Quarter, y = Count, group = Gender, colour=Gender)) +
  stat_summary(fun = sum, geom = "line") +
  labs(title = "Prison population by state and gender",
       y = "Number of prisoners ('000)") +
  facet_wrap(~ as.character(State), nrow = 1, scales = "free_y") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

3.1.5 Mixed hierarchical and grouped structure

Often disaggregating factors are both nested and crossed. For example, the Australian tourism data can also be disaggregated by the four purposes of travel: holiday, business, visiting friends and relatives, and other. This grouping variable does not nest within any of the geographical variables. In fact, we could consider overnight trips split by purpose of travel for the whole of Australia, and for each state, and for each region. We describe such a structure as a “nested” geographic hierarchy “crossed” with the purpose of travel. Using aggregate_key this can be specified by simply combining the factors as follows.

tourism_full <- tourism %>%
  aggregate_key((State / Region) * Purpose, Trips = sum(Trips))

The tourism_full tsibble contains 425 series, including the 85 series from the hierarchical structure, as well as another 340 series obtained when each series of the hierarchical structure is crossed with the purpose of travel.

3.2 Single level approaches

Traditionally, forecasts of hierarchical or grouped time series involved selecting one level of aggregation and generating forecasts for that level. These are then either aggregated for higher levels, or disaggregated for lower levels, to obtain a set of coherent forecasts for the rest of the structure.

3.2.1 The bottom-up approach

A simple method for generating coherent forecasts is the “bottom-up” approach. This approach involves first generating forecasts for each series at the bottom level, and then summing these to produce forecasts for all the series in the structure.

An advantage of this approach is that we are forecasting at the bottom level of a structure, and therefore no information is lost due to aggregation. On the other hand, bottom-level data can be quite noisy and more challenging to model and forecast.

3.2.1.1 Example: generating bottom-up forecasts

Suppose we want national and state forecasts for the Australian tourism data, but we aren’t interested in disaggregations using regions or the purpose of travel. So we first create a simple tsibble object containing only state and national trip totals for each quarter.

tourism_states <- tourism %>%
  aggregate_key(State, Trips = sum(Trips))

We could generate the bottom-level state forecasts first, and then sum them to obtain the national forecasts as follows.

fcasts_state <- tourism_states %>%
  filter(!is_aggregated(State)) %>%
  model(ets = ETS(Trips)) %>%
  forecast()
# Sum bottom-level forecasts to get top-level forecasts
fcasts_national <- fcasts_state %>%
  summarise(value = sum(Trips), .mean = mean(value))

However, we want a more general approach that will work with all the forecasting methods discussed in this chapter. So we will use the reconcile() function to specify how we want to compute coherent forecasts.

tourism_states %>%
  model(ets = ETS(Trips)) %>%
  reconcile(bu = bottom_up(ets)) %>%
  forecast()

The reconcile() step has created a new “model” to produce bottom-up forecasts. The fable object contains the ets forecasts as well as the coherent bu forecasts, for the 8 states and the national aggregate. At the state level, these forecasts are identical, but the national ets forecasts will be different from the national bu forecasts.

For bottom-up forecasting, this is rather inefficient as we are not interested in the ETS model for the national total, and the resulting fable contains a lot of duplicates.

The above code illustrates the general workflow for hierarchical and grouped forecasts. We use the following pipeline of functions.

data %>% aggregate_key() %>% model() %>% reconcile() %>% forecast()
  1. Begin with a tsibble object (here labelled data) containing the individual bottom-level series.
  2. Define in aggregate_key() the aggregation structure and build a tsibble object that also contains the aggregate series.
  3. Identify a model() for each series, at all levels of aggregation.
  4. Specify in reconcile() how the coherent forecasts are to be generated from the selected models.
  5. Use the forecast() function to generate forecasts for the whole aggregation structure.

3.2.2 Top-down approaches

Top-down approaches involve first generating forecasts for the Total series \(y_t\), and then disaggregating these down the hierarchy. This only works with strictly hierarchical data, not with grouped or mixed aggregation structures.

Top-down forecasts can be generated using top_down() within the reconcile() function.

3.2.3 Average historical proportions

\(p_j=\frac{1}{T}\sum_{t=1}^{T}\frac{y_{j,t}}{{y_t}}\)

This approach is implemented in the top_down() function by setting method = “average_proportions”.

3.2.4 Proportions of the historical averages

\(p_j={\sum_{t=1}^{T}\frac{y_{j,t}}{T}}\Big/{\sum_{t=1}^{T}\frac{y_t}{T}}\)

This approach is implemented in the top_down() function by setting method = “proportion_averages”.

In general, these approaches seem to produce quite reliable forecasts for the aggregate levels and they are useful with low count data. On the other hand, one disadvantage is the loss of information due to aggregation. Using such top-down approaches, we are unable to capture and take advantage of individual series characteristics such as time dynamics, special events, different seasonal patterns, etc.

3.2.5 Forecast proportions

Because historical proportions used for disaggregation do not take account of how those proportions may change over time, top-down approaches based on historical proportions tend to produce less accurate forecasts at lower levels of the hierarchy than bottom-up approaches. To address this issue, proportions based on forecasts rather than historical data can be used.

\(p_j=\prod^{K-1}_{\ell=0}\frac{\hat{y}_{j,h}^{(\ell)}}{\hat{S}_{j,h}^{(\ell+1)}}\)

This approach is implemented in the top_down() function by setting method = “forecast_proportions”. Because this approach tends to work better than other top-down methods, it is the default choice in the top_down() function when no method argument is specified.

One disadvantage of all top-down approaches, is that they do not produce unbiased coherent forecasts even if the base forecasts are unbiased.

3.2.6 Middle-out approach

The middle-out approach combines bottom-up and top-down approaches. Again, it can only be used for strictly hierarchical aggregation structures.

This approach is implemented in the middle_out() function by specifying the appropriate middle level via the level argument and selecting the top-down approach with the method argument.

3.3 Forecast reconciliation

https://otexts.com/fpp3/reconciliation.html

3.3.1 The MinT optimal reconciliation approach

We refer to this as the MinT (or Minimum Trace) optimal reconciliation approach . MinT is implemented by min_trace() within the reconcile() function.

To use this in practice, we need to estimate \(W_h\). Four simplifying approximations:

  1. min_trace() by setting method = "ols"
  2. min_trace() by setting method = "wls_var"
  3. min_trace() by setting method = "wls_struct"
  4. min_trace() by setting method = "mint_cov"; for cases where the number of bottom-level series \(m\) is large compared to the length of the series \(T\), this is not a good estimator. Instead we use a shrinkage estimator which shrinks the sample covariance to a diagonal matrix. method = "mint_shrink".

the optimal reconciliation forecasts are generated using all the information available within a hierarchical or a grouped structure. This is important, as particular aggregation levels or groupings may reveal features of the data that are of interest to the user and are important to be modelled. These features may be completely hidden or not easily identifiable at other levels.

3.4 Forecasting Australian domestic tourism

We use the data up to the end of 2015 as a training set, withholding the final two years (eight quarters, 2016Q1–2017Q4) as a test set for evaluation. The code below demonstrates the full workflow for generating coherent forecasts using the bottom-up, OLS and MinT methods.

tourism_full <- tourism %>%
  aggregate_key((State / Region) * Purpose, Trips = sum(Trips))
fit <- tourism_full %>%
  filter(year(Quarter) <= 2015) %>%
  model(base = ETS(Trips)) %>%
  reconcile(
    bu = bottom_up(base),
    ols = min_trace(base, method = "ols"),
    mint = min_trace(base, method = "mint_shrink"),
  )

Here, fit contains the base ETS model for each series in tourism_full, along with the three methods for producing coherent forecasts as specified in the reconcile() function.

fc <- fit %>% forecast(h = "2 years")

Passing fit into forecast() generates base and coherent forecasts across all the series in the aggregation structure.

We plot the four point forecasts for the overnight trips for the Australian total, the states, and the purposes of travel, along with the actual observations of the test set. To make it easier to see the differences, we have included only the last five years of the training data, and have omitted the prediction intervals. In most panels, the increase in overnight trips, especially in the second half of the test set, is higher than what is predicted by the point forecasts. This is particularly noticeable for the mainland eastern states of ACT, New South Wales, Queensland and Victoria, and across all purposes of travel.

fc %>%
  filter(is_aggregated(Region), is_aggregated(Purpose)) %>%
  autoplot(
    tourism_full %>% filter(year(Quarter) >= 2011),
    level = NULL
  ) +
  labs(y = "Trips ('000)") +
  facet_wrap(vars(State), scales = "free_y")

fc %>%
  filter(is_aggregated(State), !is_aggregated(Purpose)) %>%
  autoplot(
    tourism_full %>% filter(year(Quarter) >= 2011),
    level = NULL
  ) +
  labs(y = "Trips ('000)") +
  facet_wrap(vars(Purpose), scales = "free_y")

The accuracy of the forecasts over the test set can be evaluated using the accuracy() function.

fc %>%
  filter(is_aggregated(State), is_aggregated(Purpose)) %>%
  accuracy(
    data = tourism_full,
    measures = list(rmse = RMSE, mase = MASE)
  ) %>%
  group_by(.model) %>%
  summarise(rmse = mean(rmse), mase = mean(mase))
fc %>%
  filter(is_aggregated(State), !is_aggregated(Purpose)) %>%
  accuracy(
    data = tourism_full,
    measures = list(rmse = RMSE, mase = MASE)
  ) %>%
  group_by(.model) %>%
  summarise(rmse = mean(rmse), mase = mean(mase))

https://otexts.com/fpp3/tourism.html

Reconciling the base forecasts using OLS and MinT results in more accurate forecasts compared to the bottom-up approach. This result is commonly observed in applications as reconciliation approaches use information from all levels of the structure, resulting in more accurate coherent forecasts compared to the older methods which use limited information. Furthermore, reconciliation usually improves the incoherent base forecasts for almost all levels.

3.5 Reconciled distributional forecasts

https://otexts.com/fpp3/rec-prob.html

3.5.1 Forecasting Australian prison population

Returning to the Australian prison population data, we will compare the forecasts from bottom-up and MinT methods applied to base ETS models, using a test set comprising the final two years or eight quarters 2015Q1–2016Q4 of the available data.

prison <- readr::read_csv("https://OTexts.com/fpp3/extrafiles/prison_population.csv") %>%
  mutate(Quarter = yearquarter(Date)) %>%
  select(-Date)  %>%
  as_tsibble(key = c(Gender, Legal, State, Indigenous), index = Quarter) %>%
  relocate(Quarter)

-- Column specification --------------------------------------------
cols(
  Date = col_date(format = ""),
  State = col_character(),
  Gender = col_character(),
  Legal = col_character(),
  Indigenous = col_character(),
  Count = col_double()
)
prison_gts <- prison %>%
  aggregate_key(Gender * Legal * State, Count = sum(Count)/1e3)

fit <- prison_gts %>%
  filter(year(Quarter) <= 2014) %>%
  model(base = ETS(Count)) %>%
  reconcile(
    bottom_up = bottom_up(base),
    MinT = min_trace(base, method = "mint_shrink")
  )
fc <- fit %>% forecast(h = 8)
fc %>%
  filter(is_aggregated(State), is_aggregated(Gender), is_aggregated(Legal)) %>%
  autoplot(prison_gts, alpha = 0.7, level = 90) +
  labs(y = "Number of prisoners ('000)",
       title = "Australian prison population (total)")

The three sets of forecasts for the aggregate Australian prison population. The base and bottom-up forecasts from the ETS models seem to underestimate the trend over the test period. The MinT approach combines information from all the base forecasts in the aggregation structure; in this case, the base forecasts at the top level are adjusted upwards.

The MinT reconciled prediction intervals are much tighter than the base forecasts, due to MinT being based on an estimator that minimizes variances. The base forecast distributions are also incoherent, and therefore carry with them the extra uncertainty of the incoherency error.

MinT and base forecasts at various levels of aggregation. To make it easier to see the effect, we only show the last five years of training data. In general, MinT adjusts the base forecasts in the direction of the test set, hence improving the forecast accuracy. There is no guarantee that MinT reconciled forecasts will be more accurate than the base forecasts for every series, but they will be more accurate on average

fc %>%
  filter(
    .model %in% c("base", "MinT"),
    !is_aggregated(State), is_aggregated(Legal), is_aggregated(Gender)
  ) %>%
  autoplot(
    prison_gts %>% filter(year(Quarter) >= 2010),
    alpha = 0.7, level = 90
  ) +
  labs(title = "Prison population (by state)",
       y = "Number of prisoners ('000)") +
  facet_wrap(vars(State), scales = "free_y", ncol = 4) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

Using the accuracy() function, we evaluate the forecast accuracy across the grouped structure. The code below evaluates the forecast accuracy for only the top-level national aggregate of the Australian prison population time series.

fc %>%
  filter(is_aggregated(State), is_aggregated(Gender), is_aggregated(Legal)) %>%
  accuracy(data = prison_gts, measures = list(
    mase = MASE,
    ss = skill_score(CRPS)
  )) %>%
  group_by(.model) %>%
  summarise(mase = mean(mase), sspc = mean(ss) * 100)

A low value of MASE indicates a good forecast, while a high value of the skill score indicates a good forecast.

The results show that the MinT reconciled forecasts improve on the accuracy of the base forecasts and are also more accurate than the bottom-up forecasts. As the MinT optimal reconciliation approach uses information from all levels in the structure, it generates more accurate forecasts than the traditional approaches (such as bottom-up) which use limited information.

LS0tDQp0aXRsZTogIkZvcmVjYXN0aW5nOiBQcmluY2lwbGVzIGFuZCBQcmFjdGljZSAzIg0KYXV0aG9yOiAiQVJJTUEsIER5bmFtaWMgcmVncmVzc2lvbiBtb2RlbHMgYW5kIEZvcmVjYXN0aW5nIGhpZXJhcmNoaWNhbCBhbmQgZ3JvdXBlZCB0aW1lIHNlcmllcyINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICB0b2NfY29sbGFwc2VkOiBmYWxzZQ0KICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgIA0KdG9jX2RlcHRoOiAzDQotLS0NCg0KYGBge3J9DQpsaWJyYXJ5KGZwcDMpDQpgYGANCg0KPGh0dHBzOi8vcm9iamh5bmRtYW4uY29tL3NlbWluYXJzL3V3YTIwMTcvPg0KDQo8aHR0cHM6Ly9yb2JqaHluZG1hbi5jb20vc2VtaW5hcnMvaXNpMjAxOXdvcmtzaG9wLz4NCg0KPGh0dHBzOi8vZ2l0aHViLmNvbS9yb2JqaHluZG1hbi9FVEMzNTUwU2xpZGVzL3RyZWUvZmFibGU+DQoNCjxodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby1jb25mLTIwMjAvdGltZS1zZXJpZXMtZm9yZWNhc3RpbmcvYmxvYi9tYXN0ZXIvbWF0ZXJpYWxzL2xhYnMuUj4NCg0KPGh0dHA6Ly93d3cuZ3JhZGFhbndyLm5ldC8+DQoNCiMgQVJJTUEgbW9kZWxzDQoNCldoaWxlIGV4cG9uZW50aWFsIHNtb290aGluZyBtb2RlbHMgYXJlIGJhc2VkIG9uIGEgZGVzY3JpcHRpb24gb2YgdGhlIHRyZW5kIGFuZCBzZWFzb25hbGl0eSBpbiB0aGUgZGF0YSwgQVJJTUEgbW9kZWxzIGFpbSB0byBkZXNjcmliZSB0aGUgYXV0b2NvcnJlbGF0aW9ucyBpbiB0aGUgZGF0YS4NCg0KIyMgU3RhdGlvbmFyaXR5IGFuZCBkaWZmZXJlbmNpbmcNCg0KQSBzdGF0aW9uYXJ5IHRpbWUgc2VyaWVzIGlzIG9uZSB3aG9zZSBzdGF0aXN0aWNhbCBwcm9wZXJ0aWVzIGRvIG5vdCBkZXBlbmQgb24gdGhlIHRpbWUgYXQgd2hpY2ggdGhlIHNlcmllcyBpcyBvYnNlcnZlZC4gVGh1cywgdGltZSBzZXJpZXMgd2l0aCB0cmVuZHMsIG9yIHdpdGggc2Vhc29uYWxpdHksIGFyZSBub3Qgc3RhdGlvbmFyeSAtLS0gdGhlIHRyZW5kIGFuZCBzZWFzb25hbGl0eSB3aWxsIGFmZmVjdCB0aGUgdmFsdWUgb2YgdGhlIHRpbWUgc2VyaWVzIGF0IGRpZmZlcmVudCB0aW1lcy4gT24gdGhlIG90aGVyIGhhbmQsIGEgd2hpdGUgbm9pc2Ugc2VyaWVzIGlzIHN0YXRpb25hcnkgLS0tIGl0IGRvZXMgbm90IG1hdHRlciB3aGVuIHlvdSBvYnNlcnZlIGl0LCBpdCBzaG91bGQgbG9vayBtdWNoIHRoZSBzYW1lIGF0IGFueSBwb2ludCBpbiB0aW1lLg0KDQpBIHRpbWUgc2VyaWVzIHdpdGggY3ljbGljIGJlaGF2aW91ciAoYnV0IHdpdGggbm8gdHJlbmQgb3Igc2Vhc29uYWxpdHkpIGlzIHN0YXRpb25hcnkuIFRoaXMgaXMgYmVjYXVzZSB0aGUgY3ljbGVzIGFyZSBub3Qgb2YgYSBmaXhlZCBsZW5ndGgsIHNvIGJlZm9yZSB3ZSBvYnNlcnZlIHRoZSBzZXJpZXMgd2UgY2Fubm90IGJlIHN1cmUgd2hlcmUgdGhlIHBlYWtzIGFuZCB0cm91Z2hzIG9mIHRoZSBjeWNsZXMgd2lsbCBiZS4NCg0KSW4gZ2VuZXJhbCwgYSBzdGF0aW9uYXJ5IHRpbWUgc2VyaWVzIHdpbGwgaGF2ZSBubyBwcmVkaWN0YWJsZSBwYXR0ZXJucyBpbiB0aGUgbG9uZy10ZXJtLg0KDQojIyMgRGlmZmVyZW5jaW5nDQoNClRyYW5zZm9ybWF0aW9ucyBzdWNoIGFzIGxvZ2FyaXRobXMgY2FuIGhlbHAgdG8gc3RhYmlsaXNlIHRoZSB2YXJpYW5jZSBvZiBhIHRpbWUgc2VyaWVzLiBEaWZmZXJlbmNpbmcgY2FuIGhlbHAgc3RhYmlsaXNlIHRoZSBtZWFuIG9mIGEgdGltZSBzZXJpZXMgYnkgcmVtb3ZpbmcgY2hhbmdlcyBpbiB0aGUgbGV2ZWwgb2YgYSB0aW1lIHNlcmllcywgYW5kIHRoZXJlZm9yZSBlbGltaW5hdGluZyAob3IgcmVkdWNpbmcpIHRyZW5kIGFuZCBzZWFzb25hbGl0eS4NCg0KRm9yIGEgc3RhdGlvbmFyeSB0aW1lIHNlcmllcywgdGhlIEFDRiB3aWxsIGRyb3AgdG8gemVybyByZWxhdGl2ZWx5IHF1aWNrbHksIHdoaWxlIHRoZSBBQ0Ygb2Ygbm9uLXN0YXRpb25hcnkgZGF0YSBkZWNyZWFzZXMgc2xvd2x5LiBBbHNvLCBmb3Igbm9uLXN0YXRpb25hcnkgZGF0YSwgdGhlIHZhbHVlIG9mICRyXzEkIGlzIG9mdGVuIGxhcmdlIGFuZCBwb3NpdGl2ZS4NCg0KYGBge3J9DQojIFJlLWluZGV4IGJhc2VkIG9uIHRyYWRpbmcgZGF5cw0KZ29vZ2xlX3N0b2NrIDwtIGdhZmFfc3RvY2sgJT4lDQogIGZpbHRlcihTeW1ib2wgPT0gIkdPT0ciLCB5ZWFyKERhdGUpID49IDIwMTUpICU+JQ0KICBtdXRhdGUoZGF5ID0gcm93X251bWJlcigpKSAlPiUNCiAgdXBkYXRlX3RzaWJibGUoaW5kZXggPSBkYXksIHJlZ3VsYXIgPSBUUlVFKQ0KIyBGaWx0ZXIgdGhlIHllYXIgb2YgaW50ZXJlc3QNCmdvb2dsZV8yMDE1IDwtIGdvb2dsZV9zdG9jayAlPiUgZmlsdGVyKHllYXIoRGF0ZSkgPT0gMjAxNSkNCg0KZ29vZ2xlXzIwMTUgJT4lDQogIG11dGF0ZShkaWZmX2Nsb3NlID0gZGlmZmVyZW5jZShDbG9zZSkpICU+JQ0KICBmZWF0dXJlcyhkaWZmX2Nsb3NlLCBsanVuZ19ib3gsIGxhZyA9IDEwKQ0KYGBgDQoNCmBgYHtyfQ0KcDEgPC0gZ29vZ2xlXzIwMTUgJT4lIA0KICBBQ0YoQ2xvc2UpICU+JSANCiAgYXV0b3Bsb3QoKQ0KcDIgPC0gZ29vZ2xlXzIwMTUgJT4lIA0KICBBQ0YoZGlmZmVyZW5jZShDbG9zZSkpICU+JSANCiAgYXV0b3Bsb3QoKQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShjb3dwbG90KQ0KcGxvdF9ncmlkKHAxLCBwMiwgY29scz0yKQ0KYGBgDQoNClRoZSBBQ0Ygb2YgdGhlIGRpZmZlcmVuY2VkIEdvb2dsZSBzdG9jayBwcmljZSBsb29rcyBqdXN0IGxpa2UgdGhhdCBvZiBhIHdoaXRlIG5vaXNlIHNlcmllcy4gT25seSBvbmUgYXV0b2NvcnJlbGF0aW9uIGlzIG91dHNpZGUgb2YgdGhlIDk1JSBsaW1pdHMsIGFuZCB0aGUgTGp1bmctQm94IFFcXuKIlyBzdGF0aXN0aWMgaGFzIGEgcC12YWx1ZSBvZiAwLjYzNyAoZm9yICRoPTEwJCkuIFRoaXMgc3VnZ2VzdHMgdGhhdCB0aGUgZGFpbHkgY2hhbmdlIGluIHRoZSBHb29nbGUgc3RvY2sgcHJpY2UgaXMgZXNzZW50aWFsbHkgYSByYW5kb20gYW1vdW50IHdoaWNoIGlzIHVuY29ycmVsYXRlZCB3aXRoIHRoYXQgb2YgcHJldmlvdXMgZGF5cy4NCg0KIyMjIFJhbmRvbSB3YWxrIG1vZGVsDQoNClJhbmRvbSB3YWxrIG1vZGVscyBhcmUgd2lkZWx5IHVzZWQgZm9yIG5vbi1zdGF0aW9uYXJ5IGRhdGEsIHBhcnRpY3VsYXJseSBmaW5hbmNpYWwgYW5kIGVjb25vbWljIGRhdGEuIFJhbmRvbSB3YWxrcyB0eXBpY2FsbHkgaGF2ZToNCg0KLSAgIGxvbmcgcGVyaW9kcyBvZiBhcHBhcmVudCB0cmVuZHMgdXAgb3IgZG93bg0KLSAgIHN1ZGRlbiBhbmQgdW5wcmVkaWN0YWJsZSBjaGFuZ2VzIGluIGRpcmVjdGlvbi4NCg0KVGhlIGZvcmVjYXN0cyBmcm9tIGEgcmFuZG9tIHdhbGsgbW9kZWwgYXJlIGVxdWFsIHRvIHRoZSBsYXN0IG9ic2VydmF0aW9uLCBhcyBmdXR1cmUgbW92ZW1lbnRzIGFyZSB1bnByZWRpY3RhYmxlLCBhbmQgYXJlIGVxdWFsbHkgbGlrZWx5IHRvIGJlIHVwIG9yIGRvd24uIFRodXMsIHRoZSByYW5kb20gd2FsayBtb2RlbCB1bmRlcnBpbnMgbmHDr3ZlIGZvcmVjYXN0cy4NCg0KIyMjIFNlY29uZC1vcmRlciBkaWZmZXJlbmNpbmcNCg0KT2NjYXNpb25hbGx5IHRoZSBkaWZmZXJlbmNlZCBkYXRhIHdpbGwgbm90IGFwcGVhciB0byBiZSBzdGF0aW9uYXJ5IGFuZCBpdCBtYXkgYmUgbmVjZXNzYXJ5IHRvIGRpZmZlcmVuY2UgdGhlIGRhdGEgYSBzZWNvbmQgdGltZSB0byBvYnRhaW4gYSBzdGF0aW9uYXJ5IHNlcmllcy4NCg0KIyMjIFNlYXNvbmFsIGRpZmZlcmVuY2luZw0KDQpBIHNlYXNvbmFsIGRpZmZlcmVuY2UgaXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBhbiBvYnNlcnZhdGlvbiBhbmQgdGhlIHByZXZpb3VzIG9ic2VydmF0aW9uIGZyb20gdGhlIHNhbWUgc2Vhc29uLg0KDQpGb3JlY2FzdHMgZnJvbSB0aGlzIG1vZGVsIGFyZSBlcXVhbCB0byB0aGUgbGFzdCBvYnNlcnZhdGlvbiBmcm9tIHRoZSByZWxldmFudCBzZWFzb24uIFRoYXQgaXMsIHRoaXMgbW9kZWwgZ2l2ZXMgc2Vhc29uYWwgbmHDr3ZlIGZvcmVjYXN0cy4NCg0KU29tZXRpbWVzIGl0IGlzIG5lY2Vzc2FyeSB0byB0YWtlIGJvdGggYSBzZWFzb25hbCBkaWZmZXJlbmNlIGFuZCBhIGZpcnN0IGRpZmZlcmVuY2UgdG8gb2J0YWluIHN0YXRpb25hcnkgZGF0YS4NCg0KYGBge3J9DQpQQlMgJT4lDQogIGZpbHRlcihBVEMyID09ICJIMDIiKSAlPiUNCiAgc3VtbWFyaXNlKENvc3QgPSBzdW0oQ29zdCkvMWU2KSAlPiUNCiAgdHJhbnNtdXRlKA0KICAgIGBTYWxlcyAoJG1pbGxpb24pYCA9IENvc3QsDQogICAgYExvZyBzYWxlc2AgPSBsb2coQ29zdCksDQogICAgYEFubnVhbCBjaGFuZ2UgaW4gbG9nIHNhbGVzYCA9IGRpZmZlcmVuY2UobG9nKENvc3QpLCAxMiksDQogICAgYERvdWJseSBkaWZmZXJlbmNlZCBsb2cgc2FsZXNgID0gZGlmZmVyZW5jZShkaWZmZXJlbmNlKGxvZyhDb3N0KSwgMTIpLCAxKQ0KICApICU+JQ0KICBwaXZvdF9sb25nZXIoLU1vbnRoLCBuYW1lc190bz0iVHlwZSIsIHZhbHVlc190bz0iU2FsZXMiKSAlPiUNCiAgbXV0YXRlKA0KICAgIFR5cGUgPSBmYWN0b3IoVHlwZSwgbGV2ZWxzID0gYygNCiAgICAgICJTYWxlcyAoJG1pbGxpb24pIiwNCiAgICAgICJMb2cgc2FsZXMiLA0KICAgICAgIkFubnVhbCBjaGFuZ2UgaW4gbG9nIHNhbGVzIiwNCiAgICAgICJEb3VibHkgZGlmZmVyZW5jZWQgbG9nIHNhbGVzIikpDQogICkgJT4lDQogIGdncGxvdChhZXMoeCA9IE1vbnRoLCB5ID0gU2FsZXMpKSArDQogIGdlb21fbGluZSgpICsNCiAgZmFjZXRfZ3JpZCh2YXJzKFR5cGUpLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICBsYWJzKHRpdGxlID0gIkNvcnRpY29zdGVyb2lkIGRydWcgc2FsZXMiLCB5ID0gTlVMTCkNCmBgYA0KDQpJZiB0aGUgZGF0YSBoYXZlIGEgc3Ryb25nIHNlYXNvbmFsIHBhdHRlcm4sIHdlIHJlY29tbWVuZCB0aGF0IHNlYXNvbmFsIGRpZmZlcmVuY2luZyBiZSBkb25lIGZpcnN0LCBiZWNhdXNlIHRoZSByZXN1bHRpbmcgc2VyaWVzIHdpbGwgc29tZXRpbWVzIGJlIHN0YXRpb25hcnkgYW5kIHRoZXJlIHdpbGwgYmUgbm8gbmVlZCBmb3IgYSBmdXJ0aGVyIGZpcnN0IGRpZmZlcmVuY2UuDQoNCkZpcnN0IGRpZmZlcmVuY2VzIGFyZSB0aGUgY2hhbmdlIGJldHdlZW4gb25lIG9ic2VydmF0aW9uIGFuZCB0aGUgbmV4dC4gU2Vhc29uYWwgZGlmZmVyZW5jZXMgYXJlIHRoZSBjaGFuZ2UgYmV0d2VlbiBvbmUgeWVhciB0byB0aGUgbmV4dC4gT3RoZXIgbGFncyBhcmUgdW5saWtlbHkgdG8gbWFrZSBtdWNoIGludGVycHJldGFibGUgc2Vuc2UgYW5kIHNob3VsZCBiZSBhdm9pZGVkLg0KDQojIyMgVW5pdCByb290IHRlc3RzDQoNCk9uZSB3YXkgdG8gZGV0ZXJtaW5lIG1vcmUgb2JqZWN0aXZlbHkgd2hldGhlciBkaWZmZXJlbmNpbmcgaXMgcmVxdWlyZWQgaXMgdG8gdXNlIGEgdW5pdCByb290IHRlc3QuIFRoZXNlIGFyZSBzdGF0aXN0aWNhbCBoeXBvdGhlc2lzIHRlc3RzIG9mIHN0YXRpb25hcml0eSB0aGF0IGFyZSBkZXNpZ25lZCBmb3IgZGV0ZXJtaW5pbmcgd2hldGhlciBkaWZmZXJlbmNpbmcgaXMgcmVxdWlyZWQuDQoNCndlIHVzZSB0aGUgS3dpYXRrb3dza2ktUGhpbGxpcHMtU2NobWlkdC1TaGluIChLUFNTKSB0ZXN0LiBJbiB0aGlzIHRlc3QsIHRoZSBudWxsIGh5cG90aGVzaXMgaXMgdGhhdCB0aGUgZGF0YSBhcmUgc3RhdGlvbmFyeSwgYW5kIHdlIGxvb2sgZm9yIGV2aWRlbmNlIHRoYXQgdGhlIG51bGwgaHlwb3RoZXNpcyBpcyBmYWxzZS4gQ29uc2VxdWVudGx5LCBzbWFsbCBwLXZhbHVlcyAoZS5nLiwgbGVzcyB0aGFuIDAuMDUpIHN1Z2dlc3QgdGhhdCBkaWZmZXJlbmNpbmcgaXMgcmVxdWlyZWQuIFRoZSB0ZXN0IGNhbiBiZSBjb21wdXRlZCB1c2luZyB0aGUgdW5pdHJvb3Rfa3BzcygpIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCmdvb2dsZV8yMDE1ICU+JQ0KICBmZWF0dXJlcyhDbG9zZSwgdW5pdHJvb3Rfa3BzcykNCmBgYA0KDQpUaGUgcmVwb3J0ZWQgcC12YWx1ZSBpcyBzZXQgdG8gMC4wMSBpZiBpdCBpcyBsZXNzIHRoYW4gdGhhdCwgb3IgdG8gMC4xIGlmIGl0IGlzIGdyZWF0ZXIgdGhhbiB0aGF0LiBJbiB0aGlzIGNhc2UsIHRoZSB0ZXN0IHN0YXRpc3RpYyAoMy41NikgaXMgbXVjaCBiaWdnZXIgdGhhbiB0aGUgMSUgY3JpdGljYWwgdmFsdWUsIHNvIHRoZSBwLXZhbHVlIGlzIGxlc3MgdGhhbiAwLjAxLCBpbmRpY2F0aW5nIHRoYXQgdGhlIG51bGwgaHlwb3RoZXNpcyBpcyByZWplY3RlZC4gVGhhdCBpcywgdGhlIGRhdGEgYXJlIG5vdCBzdGF0aW9uYXJ5LiBXZSBjYW4gZGlmZmVyZW5jZSB0aGUgZGF0YSwgYW5kIGFwcGx5IHRoZSB0ZXN0IGFnYWluLg0KDQpgYGB7cn0NCmdvb2dsZV8yMDE1ICU+JQ0KICBtdXRhdGUoZGlmZl9jbG9zZSA9IGRpZmZlcmVuY2UoQ2xvc2UpKSAlPiUNCiAgZmVhdHVyZXMoZGlmZl9jbG9zZSwgdW5pdHJvb3Rfa3BzcykNCmBgYA0KDQpUaGlzIHRpbWUsIHRoZSB0ZXN0IHN0YXRpc3RpYyBpcyB0aW55LCBhbmQgd2VsbCB3aXRoaW4gdGhlIHJhbmdlIHdlIHdvdWxkIGV4cGVjdCBmb3Igc3RhdGlvbmFyeSBkYXRhLCBzbyB0aGUgcC12YWx1ZSBpcyBncmVhdGVyIHRoYW4gMC4xLiBXZSBjYW4gY29uY2x1ZGUgdGhhdCB0aGUgZGlmZmVyZW5jZWQgZGF0YSBhcHBlYXIgc3RhdGlvbmFyeS4NCg0KVGhpcyBwcm9jZXNzIG9mIHVzaW5nIGEgc2VxdWVuY2Ugb2YgS1BTUyB0ZXN0cyB0byBkZXRlcm1pbmUgdGhlIGFwcHJvcHJpYXRlIG51bWJlciBvZiBmaXJzdCBkaWZmZXJlbmNlcyBpcyBjYXJyaWVkIG91dCB1c2luZyB0aGUgdW5pdHJvb3RfbmRpZmZzKCkgZmVhdHVyZS4NCg0KYGBge3J9DQpnb29nbGVfMjAxNSAlPiUNCiAgZmVhdHVyZXMoQ2xvc2UsIHVuaXRyb290X25kaWZmcykNCmBgYA0KDQpBcyB3ZSBzYXcgZnJvbSB0aGUgS1BTUyB0ZXN0cyBhYm92ZSwgb25lIGRpZmZlcmVuY2UgaXMgcmVxdWlyZWQgdG8gbWFrZSB0aGUgZ29vZ2xlXzIwMTUgZGF0YSBzdGF0aW9uYXJ5Lg0KDQpBIHNpbWlsYXIgZmVhdHVyZSBmb3IgZGV0ZXJtaW5pbmcgd2hldGhlciBzZWFzb25hbCBkaWZmZXJlbmNpbmcgaXMgcmVxdWlyZWQgaXMgdW5pdHJvb3RfbnNkaWZmcygpLCB3aGljaCB1c2VzIHRoZSBtZWFzdXJlIG9mIHNlYXNvbmFsIHN0cmVuZ3RoIGludHJvZHVjZWQgaW4gU2VjdGlvbiA0LjMgdG8gZGV0ZXJtaW5lIHRoZSBhcHByb3ByaWF0ZSBudW1iZXIgb2Ygc2Vhc29uYWwgZGlmZmVyZW5jZXMgcmVxdWlyZWQuIE5vIHNlYXNvbmFsIGRpZmZlcmVuY2VzIGFyZSBzdWdnZXN0ZWQgaWYgJEZfUzwwLjY0JCwgb3RoZXJ3aXNlIG9uZSBzZWFzb25hbCBkaWZmZXJlbmNlIGlzIHN1Z2dlc3RlZC4NCg0KYGBge3J9DQphdXNfdG90YWxfcmV0YWlsIDwtIGF1c19yZXRhaWwgJT4lDQogIHN1bW1hcmlzZShUdXJub3ZlciA9IHN1bShUdXJub3ZlcikpDQphdXNfdG90YWxfcmV0YWlsICU+JQ0KICBtdXRhdGUobG9nX3R1cm5vdmVyID0gbG9nKFR1cm5vdmVyKSkgJT4lDQogIGZlYXR1cmVzKGxvZ190dXJub3ZlciwgdW5pdHJvb3RfbnNkaWZmcykNCmBgYA0KDQpgYGB7cn0NCmF1c190b3RhbF9yZXRhaWwgJT4lDQogIG11dGF0ZShsb2dfdHVybm92ZXIgPSBkaWZmZXJlbmNlKGxvZyhUdXJub3ZlciksIDEyKSkgJT4lDQogIGZlYXR1cmVzKGxvZ190dXJub3ZlciwgdW5pdHJvb3RfbmRpZmZzKQ0KYGBgDQoNCkJlY2F1c2UgdW5pdHJvb3RfbnNkaWZmcygpIHJldHVybnMgMSAoaW5kaWNhdGluZyBvbmUgc2Vhc29uYWwgZGlmZmVyZW5jZSBpcyByZXF1aXJlZCksIHdlIGFwcGx5IHRoZSB1bml0cm9vdF9uZGlmZnMoKSBmdW5jdGlvbiB0byB0aGUgc2Vhc29uYWxseSBkaWZmZXJlbmNlZCBkYXRhLiBUaGVzZSBmdW5jdGlvbnMgc3VnZ2VzdCB3ZSBzaG91bGQgZG8gYm90aCBhIHNlYXNvbmFsIGRpZmZlcmVuY2UgYW5kIGEgZmlyc3QgZGlmZmVyZW5jZS4NCg0KIyMgQmFja3NoaWZ0IG5vdGF0aW9uDQoNCjxodHRwczovL290ZXh0cy5jb20vZnBwMy9iYWNrc2hpZnQuaHRtbD4NCg0KIyMgQXV0b3JlZ3Jlc3NpdmUgbW9kZWxzDQoNCkluIGFuIGF1dG9yZWdyZXNzaW9uIG1vZGVsLCB3ZSBmb3JlY2FzdCB0aGUgdmFyaWFibGUgb2YgaW50ZXJlc3QgdXNpbmcgYSBsaW5lYXIgY29tYmluYXRpb24gb2YgcGFzdCB2YWx1ZXMgb2YgdGhlIHZhcmlhYmxlLiBUaGUgdGVybSBhdXRvcmVncmVzc2lvbiBpbmRpY2F0ZXMgdGhhdCBpdCBpcyBhIHJlZ3Jlc3Npb24gb2YgdGhlIHZhcmlhYmxlIGFnYWluc3QgaXRzZWxmLg0KDQokeV97dH0gPSBjICsgXHBoaV97MX15X3t0LTF9ICsgXHBoaV97Mn15X3t0LTJ9ICsgXGRvdHMgKyBccGhpX3twfXlfe3QtcH0gKyBcdmFyZXBzaWxvbl97dH0kDQoNCldlIHJlZmVyIHRvIHRoaXMgYXMgYW4gQVIocCkgbW9kZWwsIGFuIGF1dG9yZWdyZXNzaXZlIG1vZGVsIG9mIG9yZGVyIHAuDQoNCiMjIE1vdmluZyBhdmVyYWdlIG1vZGVscw0KDQpSYXRoZXIgdGhhbiB1c2luZyBwYXN0IHZhbHVlcyBvZiB0aGUgZm9yZWNhc3QgdmFyaWFibGUgaW4gYSByZWdyZXNzaW9uLCBhIG1vdmluZyBhdmVyYWdlIG1vZGVsIHVzZXMgcGFzdCBmb3JlY2FzdCBlcnJvcnMgaW4gYSByZWdyZXNzaW9uLWxpa2UgbW9kZWwuDQoNCiR5X3t0fSA9IGMgKyBcdmFyZXBzaWxvbl90ICsgXHRoZXRhX3sxfVx2YXJlcHNpbG9uX3t0LTF9ICsgXHRoZXRhX3syfVx2YXJlcHNpbG9uX3t0LTJ9ICsgXGRvdHMgKyBcdGhldGFfe3F9XHZhcmVwc2lsb25fe3QtcX0kDQoNCldlIHJlZmVyIHRvIHRoaXMgYXMgYW4gTUEocSkgbW9kZWwsIGEgbW92aW5nIGF2ZXJhZ2UgbW9kZWwgb2Ygb3JkZXJcDQpxLiBPZiBjb3Vyc2UsIHdlIGRvIG5vdCBvYnNlcnZlIHRoZSB2YWx1ZXMgb2YgJM61X3QkLCBzbyBpdCBpcyBub3QgcmVhbGx5IGEgcmVncmVzc2lvbiBpbiB0aGUgdXN1YWwgc2Vuc2UuDQoNCk5vdGljZSB0aGF0IGVhY2ggdmFsdWUgb2YgJHlfdCQgY2FuIGJlIHRob3VnaHQgb2YgYXMgYSB3ZWlnaHRlZCBtb3ZpbmcgYXZlcmFnZSBvZiB0aGUgcGFzdCBmZXcgZm9yZWNhc3QgZXJyb3JzLiBIb3dldmVyLCBtb3ZpbmcgYXZlcmFnZSBtb2RlbHMgc2hvdWxkIG5vdCBiZSBjb25mdXNlZCB3aXRoIHRoZSBtb3ZpbmcgYXZlcmFnZSBzbW9vdGhpbmcuIEEgbW92aW5nIGF2ZXJhZ2UgbW9kZWwgaXMgdXNlZCBmb3IgZm9yZWNhc3RpbmcgZnV0dXJlIHZhbHVlcywgd2hpbGUgbW92aW5nIGF2ZXJhZ2Ugc21vb3RoaW5nIGlzIHVzZWQgZm9yIGVzdGltYXRpbmcgdGhlIHRyZW5kLWN5Y2xlIG9mIHBhc3QgdmFsdWVzLg0KDQojIyBOb24tc2Vhc29uYWwgQVJJTUEgbW9kZWxzDQoNCklmIHdlIGNvbWJpbmUgZGlmZmVyZW5jaW5nIHdpdGggYXV0b3JlZ3Jlc3Npb24gYW5kIGEgbW92aW5nIGF2ZXJhZ2UgbW9kZWwsIHdlIG9idGFpbiBhIG5vbi1zZWFzb25hbCBBUklNQSBtb2RlbC4gQVJJTUEgaXMgYW4gYWNyb255bSBmb3IgQXV0b1JlZ3Jlc3NpdmUgSW50ZWdyYXRlZCBNb3ZpbmcgQXZlcmFnZSAoaW4gdGhpcyBjb250ZXh0LCAiaW50ZWdyYXRpb24iIGlzIHRoZSByZXZlcnNlIG9mIGRpZmZlcmVuY2luZykuIFRoZSBmdWxsIG1vZGVsIGNhbiBiZSB3cml0dGVuIGFzDQoNCmBgYHs9dGV4fQ0KXGJlZ2lue2VxdWF0aW9ufQ0KICB5J197dH0gPSBjICsgXHBoaV97MX15J197dC0xfSArIFxjZG90cyArIFxwaGlfe3B9eSdfe3QtcH0NCiAgICAgKyBcdGhldGFfezF9XHZhcmVwc2lsb25fe3QtMX0gKyBcY2RvdHMgKyBcdGhldGFfe3F9XHZhcmVwc2lsb25fe3QtcX0gKyBcdmFyZXBzaWxvbl97dH0sDQpcZW5ke2VxdWF0aW9ufQ0KYGBgDQpXZSBjYWxsIHRoaXMgYW4gQVJJTUEocCxkLHEpIG1vZGVsLCB3aGVyZToNCg0KcD1vcmRlciBvZiB0aGUgYXV0b3JlZ3Jlc3NpdmUgcGFydDsgZD1kZWdyZWUgb2YgZmlyc3QgZGlmZmVyZW5jaW5nIGludm9sdmVkOyBxPW9yZGVyIG9mIHRoZSBtb3ZpbmcgYXZlcmFnZSBwYXJ0Lg0KDQpTcGVjaWFsIGNhc2VzIG9mIEFSSU1BIG1vZGVsczoNCg0KfCAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXw6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCBXaGl0ZSBub2lzZSAgICAgICAgICAgIHwgQVJJTUEoMCwwLDApICAgICAgICAgICAgICAgICAgfA0KfCBSYW5kb20gd2FsayAgICAgICAgICAgIHwgQVJJTUEoMCwxLDApIHdpdGggbm8gY29uc3RhbnQgfA0KfCBSYW5kb20gd2FsayB3aXRoIGRyaWZ0IHwgQVJJTUEoMCwxLDApIHdpdGggYSBjb25zdGFudCAgfA0KfCBBdXRvcmVncmVzc2lvbiAgICAgICAgIHwgQVJJTUEocCwwLDApICAgICAgICAgICAgICAgICAgfA0KfCBNb3ZpbmcgYXZlcmFnZSAgICAgICAgIHwgQVJJTUEoMCwwLHEpICAgICAgICAgICAgICAgICAgfA0KDQpCYWNrc2hpZnQgbm90YXRpb246DQoNClxiZWdpbntlcXVhdGlvbn0NCiAgXGJlZ2lue2FycmF5fXtjIGMgYyBjfQ0KICAgICgxLVxwaGlfMUIgLSBcY2RvdHMgLSBccGhpX3AgQl5wKSAmICgxLUIpXmQgeV97dH0gJj0gJmMgKyAoMSArIFx0aGV0YV8xIEIgKyBcY2RvdHMgKyBcdGhldGFfcSBCXnEpXHZhcmVwc2lsb25fdFxcDQogICAge1x1cGFycm93fSAmIHtcdXBhcnJvd30gJiAme1x1cGFycm93fVxcDQogICAgXHRleHR7QVIoJHAkKX0gJiBcdGV4dHskZCQgZGlmZmVyZW5jZXN9ICYgJiBcdGV4dHtNQSgkcSQpfVxcDQogIFxlbmR7YXJyYXl9DQpcZW5ke2VxdWF0aW9ufQ0KDQojIyMgRWd5cHRpYW4gZXhwb3J0cw0KDQpFZ3lwdGlhbiBleHBvcnRzIGFzIGEgcGVyY2VudGFnZSBvZiBHRFAgZnJvbSAxOTYwIHRvIDIwMTcuDQpgYGB7cn0NCmdsb2JhbF9lY29ub215ICU+JQ0KICBmaWx0ZXIoQ29kZSA9PSAiRUdZIikgJT4lDQogIGF1dG9wbG90KEV4cG9ydHMpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgR0RQIiwgdGl0bGUgPSAiRWd5cHRpYW4gRXhwb3J0cyIpDQpgYGANClRoZSBmb2xsb3dpbmcgUiBjb2RlIHNlbGVjdHMgYSBub24tc2Vhc29uYWwgQVJJTUEgbW9kZWwgYXV0b21hdGljYWxseS4NCmBgYHtyfQ0KZml0IDwtIGdsb2JhbF9lY29ub215ICU+JQ0KICBmaWx0ZXIoQ29kZSA9PSAiRUdZIikgJT4lDQogIG1vZGVsKEFSSU1BKEV4cG9ydHMpKQ0KcmVwb3J0KGZpdCkNCmBgYA0KVGhpcyBpcyBhbiBBUklNQSgyLDAsMSkgbW9kZWw6DQoNCiR5X3QgPSAyLjU2DQogICAgICAgICArIDEuNjggeV97dC0xfQ0KICAgICAgICAgIC0wLjgwIHlfe3QtMn0NCiAgICAgICAgICAtMC42OSBcdmFyZXBzaWxvbl97dC0xfQ0KICAgICAgICAgICsgXHZhcmVwc2lsb25fe3R9JA0KDQpGb3JlY2FzdHMgZnJvbSB0aGUgbW9kZWwgYXJlIHNob3duIGluIEZpZ3VyZSA5LjguIE5vdGljZSBob3cgdGhleSBoYXZlIHBpY2tlZCB1cCB0aGUgY3ljbGVzIGV2aWRlbnQgaW4gdGhlIEVneXB0aWFuIGVjb25vbXkgb3ZlciB0aGUgbGFzdCBmZXcgZGVjYWRlcy4NCmBgYHtyfQ0KZml0ICU+JSBmb3JlY2FzdChoPTEwKSAlPiUgYXV0b3Bsb3QoZ2xvYmFsX2Vjb25vbXkpDQpgYGANCiMjIyBBQ0YgYW5kIFBBQ0YgcGxvdHMNCg0KSXQgaXMgdXN1YWxseSBub3QgcG9zc2libGUgdG8gdGVsbCwgc2ltcGx5IGZyb20gYSB0aW1lIHBsb3QsIHdoYXQgdmFsdWVzIG9mIHAgYW5kIHEgYXJlIGFwcHJvcHJpYXRlIGZvciB0aGUgZGF0YS4gSG93ZXZlciwgaXQgaXMgc29tZXRpbWVzIHBvc3NpYmxlIHRvIHVzZSB0aGUgQUNGIHBsb3QsIGFuZCB0aGUgY2xvc2VseSByZWxhdGVkIFBBQ0YgcGxvdCwgdG8gZGV0ZXJtaW5lIGFwcHJvcHJpYXRlIHZhbHVlcyBmb3IgcCBhbmQgcS4NCg0KYGBge3J9DQpnbG9iYWxfZWNvbm9teSAlPiUgZmlsdGVyKENvZGUgPT0gIkVHWSIpICU+JSBBQ0YoRXhwb3J0cykgJT4lIGF1dG9wbG90KCkNCmBgYA0KYGBge3J9DQpnbG9iYWxfZWNvbm9teSAlPiUgZmlsdGVyKENvZGUgPT0gIkVHWSIpICU+JSBQQUNGKEV4cG9ydHMpICU+JSBhdXRvcGxvdCgpDQpgYGANCklmIHRoZSBkYXRhIGFyZSBmcm9tIGFuIEFSSU1BKHAsZCwwKSBvciBBUklNQSgwLGQscSkgbW9kZWwsIHRoZW4gdGhlIEFDRiBhbmQgUEFDRiBwbG90cyBjYW4gYmUgaGVscGZ1bCBpbiBkZXRlcm1pbmluZyB0aGUgdmFsdWUgb2YgcCBvciBxLiBJZiBwIGFuZCBxIGFyZSBib3RoIHBvc2l0aXZlLCB0aGVuIHRoZSBwbG90cyBkbyBub3QgaGVscCBpbiBmaW5kaW5nIHN1aXRhYmxlIHZhbHVlcyBvZiBwIGFuZCBxLg0KDQpUaGUgZGF0YSBtYXkgZm9sbG93IGFuIEFSSU1BKHAsZCwwKSBtb2RlbCBpZiB0aGUgQUNGIGFuZCBQQUNGIHBsb3RzIG9mIHRoZSBkaWZmZXJlbmNlZCBkYXRhIHNob3cgdGhlIGZvbGxvd2luZyBwYXR0ZXJuczoNCg0KLSB0aGUgQUNGIGlzIGV4cG9uZW50aWFsbHkgZGVjYXlpbmcgb3Igc2ludXNvaWRhbDsNCi0gdGhlcmUgaXMgYSBzaWduaWZpY2FudCBzcGlrZSBhdCBsYWcgcCBpbiB0aGUgUEFDRiwgYnV0IG5vbmUgYmV5b25kIGxhZyBwLg0KDQpUaGUgZGF0YSBtYXkgZm9sbG93IGFuIEFSSU1BKDAsZCxxKSBtb2RlbCBpZiB0aGUgQUNGIGFuZCBQQUNGIHBsb3RzIG9mIHRoZSBkaWZmZXJlbmNlZCBkYXRhIHNob3cgdGhlIGZvbGxvd2luZyBwYXR0ZXJuczoNCg0KLSB0aGUgUEFDRiBpcyBleHBvbmVudGlhbGx5IGRlY2F5aW5nIG9yIHNpbnVzb2lkYWw7DQotIHRoZXJlIGlzIGEgc2lnbmlmaWNhbnQgc3Bpa2UgYXQgbGFnIHEgaW4gdGhlIEFDRiwgYnV0IG5vbmUgYmV5b25kIGxhZyBxLg0KDQp3ZSBzZWUgdGhhdCB0aGVyZSBpcyBhIGRlY2F5aW5nIHNpbnVzb2lkYWwgcGF0dGVybiBpbiB0aGUgQUNGLCBhbmQgdGhlIFBBQ0Ygc2hvd3MgdGhlIGxhc3Qgc2lnbmlmaWNhbnQgc3Bpa2UgYXQgbGFnIDQuIFRoaXMgaXMgd2hhdCB5b3Ugd291bGQgZXhwZWN0IGZyb20gYW4gQVJJTUEoNCwwLDApIG1vZGVsLg0KYGBge3J9DQpmaXQyIDwtIGdsb2JhbF9lY29ub215ICU+JQ0KICBmaWx0ZXIoQ29kZSA9PSAiRUdZIikgJT4lDQogIG1vZGVsKEFSSU1BKEV4cG9ydHMgfiBwZHEoNCwwLDApKSkNCnJlcG9ydChmaXQyKQ0KYGBgDQpUaGlzIG1vZGVsIGlzIG9ubHkgc2xpZ2h0bHkgd29yc2UgdGhhbiB0aGUgQVJJTUEoMiwwLDEpIG1vZGVsIGlkZW50aWZpZWQgYnkgQVJJTUEoKSAod2l0aCBhbiBBSUNjIHZhbHVlIG9mIDI5NC43MCBjb21wYXJlZCB0byAyOTQuMjkpLg0KDQpXZSBjYW4gYWxzbyBzcGVjaWZ5IHBhcnRpY3VsYXIgdmFsdWVzIG9mIHBkcSgpIHRoYXQgQVJJTUEgY2FuIHNlYXJjaCBmb3IuIEZvciBleGFtcGxlLCB0byBmaW5kIHRoZSBiZXN0IEFSSU1BIG1vZGVsIHdpdGggJHBcaW5cezEsMiwzXH0kLCAkcVxpblx7MCwxLDJcfSQgYW5kIGQ9MSwgeW91IGNvdWxkIHVzZSBBUklNQSh5IH4gcGRxKHA9MTozLCBkPTEsIHE9MDoyKSkuDQoNCiMjIEVzdGltYXRpb24gYW5kIG9yZGVyIHNlbGVjdGlvbg0KDQoNCiMjIyBNYXhpbXVtIGxpa2VsaWhvb2QgZXN0aW1hdGlvbg0KDQpXaGVuIGZhYmxlIGVzdGltYXRlcyB0aGUgQVJJTUEgbW9kZWwsIGl0IHVzZXMgbWF4aW11bSBsaWtlbGlob29kIGVzdGltYXRpb24gKE1MRSkuIFRoaXMgdGVjaG5pcXVlIGZpbmRzIHRoZSB2YWx1ZXMgb2YgdGhlIHBhcmFtZXRlcnMgd2hpY2ggbWF4aW1pc2UgdGhlIHByb2JhYmlsaXR5IG9mIG9idGFpbmluZyB0aGUgZGF0YSB0aGF0IHdlIGhhdmUgb2JzZXJ2ZWQuIEZvciBBUklNQSBtb2RlbHMsIE1MRSBpcyBzaW1pbGFyIHRvIHRoZSBsZWFzdCBzcXVhcmVzIGVzdGltYXRlcyB0aGF0IHdvdWxkIGJlIG9idGFpbmVkIGJ5IG1pbmltaXNpbmcNCg0KJFxzdW1fe3Q9MX1eVFx2YXJlcHNpbG9uX3ReMi4kDQoNCiMjIyBJbmZvcm1hdGlvbiBDcml0ZXJpYQ0KDQpBa2Fpa2XigJlzIEluZm9ybWF0aW9uIENyaXRlcmlvbiAoQUlDKSwgd2hpY2ggd2FzIHVzZWZ1bCBpbiBzZWxlY3RpbmcgcHJlZGljdG9ycyBmb3IgcmVncmVzc2lvbiwgaXMgYWxzbyB1c2VmdWwgZm9yIGRldGVybWluaW5nIHRoZSBvcmRlciBvZiBhbiBBUklNQSBtb2RlbC4NCg0KR29vZCBtb2RlbHMgYXJlIG9idGFpbmVkIGJ5IG1pbmltaXNpbmcgdGhlIEFJQywgQUlDYyBvciBCSUMuIE91ciBwcmVmZXJlbmNlIGlzIHRvIHVzZSB0aGUgQUlDYy4NCg0KdCBpcyBpbXBvcnRhbnQgdG8gbm90ZSB0aGF0IHRoZXNlIGluZm9ybWF0aW9uIGNyaXRlcmlhIHRlbmQgbm90IHRvIGJlIGdvb2QgZ3VpZGVzIHRvIHNlbGVjdGluZyB0aGUgYXBwcm9wcmlhdGUgb3JkZXIgb2YgZGlmZmVyZW5jaW5nIChkKSBvZiBhIG1vZGVsLg0KDQojIyBBUklNQSBtb2RlbGxpbmcgaW4gZmFibGUNCg0KaHR0cHM6Ly9vdGV4dHMuY29tL2ZwcDMvYXJpbWEtci5odG1sDQoNCiMjIyBNb2RlbGxpbmcgcHJvY2VkdXJlDQoNCldoZW4gZml0dGluZyBhbiBBUklNQSBtb2RlbCB0byBhIHNldCBvZiAobm9uLXNlYXNvbmFsKSB0aW1lIHNlcmllcyBkYXRhLCB0aGUgZm9sbG93aW5nIHByb2NlZHVyZSBwcm92aWRlcyBhIHVzZWZ1bCBnZW5lcmFsIGFwcHJvYWNoLg0KDQoxLiBQbG90IHRoZSBkYXRhIGFuZCBpZGVudGlmeSBhbnkgdW51c3VhbCBvYnNlcnZhdGlvbnMuDQoyLiBJZiBuZWNlc3NhcnksIHRyYW5zZm9ybSB0aGUgZGF0YSAodXNpbmcgYSBCb3gtQ294IHRyYW5zZm9ybWF0aW9uKSB0byBzdGFiaWxpc2UgdGhlIHZhcmlhbmNlLg0KMy4gSWYgdGhlIGRhdGEgYXJlIG5vbi1zdGF0aW9uYXJ5LCB0YWtlIGZpcnN0IGRpZmZlcmVuY2VzIG9mIHRoZSBkYXRhIHVudGlsIHRoZSBkYXRhIGFyZSBzdGF0aW9uYXJ5Lg0KNC4gRXhhbWluZSB0aGUgQUNGL1BBQ0Y6IElzIGFuIEFSSU1BKHAsZCwwKSBvciBBUklNQSgwLGQscSkgbW9kZWwgYXBwcm9wcmlhdGU/DQo1LiBUcnkgeW91ciBjaG9zZW4gbW9kZWwocyksIGFuZCB1c2UgdGhlIEFJQ2MgdG8gc2VhcmNoIGZvciBhIGJldHRlciBtb2RlbC4NCjYuIENoZWNrIHRoZSByZXNpZHVhbHMgZnJvbSB5b3VyIGNob3NlbiBtb2RlbCBieSBwbG90dGluZyB0aGUgQUNGIG9mIHRoZSByZXNpZHVhbHMsIGFuZCBkb2luZyBhIHBvcnRtYW50ZWF1IHRlc3Qgb2YgdGhlIHJlc2lkdWFscy4gSWYgdGhleSBkbyBub3QgbG9vayBsaWtlIHdoaXRlIG5vaXNlLCB0cnkgYSBtb2RpZmllZCBtb2RlbC4NCjcuIE9uY2UgdGhlIHJlc2lkdWFscyBsb29rIGxpa2Ugd2hpdGUgbm9pc2UsIGNhbGN1bGF0ZSBmb3JlY2FzdHMuDQoNCiMjI0V4YW1wbGU6IENlbnRyYWwgQWZyaWNhbiBSZXB1YmxpYyBleHBvcnRzDQoNCmBgYHtyfQ0KZ2xvYmFsX2Vjb25vbXkgJT4lDQogIGZpbHRlcihDb2RlID09ICJDQUYiKSAlPiUNCiAgYXV0b3Bsb3QoRXhwb3J0cykNCmBgYA0KMS4gVGhlIHRpbWUgcGxvdCBzaG93cyBzb21lIG5vbi1zdGF0aW9uYXJpdHksIHdpdGggYW4gb3ZlcmFsbCBkZWNsaW5lLiBUaGUgaW1wcm92ZW1lbnQgaW4gMTk5NCB3YXMgZHVlIHRvIGEgbmV3IGdvdmVybm1lbnQgd2hpY2ggb3ZlcnRocmV3IHRoZSBtaWxpdGFyeSBqdW50YSBhbmQgaGFkIHNvbWUgaW5pdGlhbCBzdWNjZXNzLCBiZWZvcmUgdW5yZXN0IGNhdXNlZCBmdXJ0aGVyIGVjb25vbWljIGRlY2xpbmUuDQoNCjIuIFRoZXJlIGlzIG5vIGV2aWRlbmNlIG9mIGNoYW5naW5nIHZhcmlhbmNlLCBzbyB3ZSB3aWxsIG5vdCBkbyBhIEJveC1Db3ggdHJhbnNmb3JtYXRpb24uDQoNCjMuIFRvIGFkZHJlc3MgdGhlIG5vbi1zdGF0aW9uYXJpdHksIHdlIHdpbGwgdGFrZSBhIGZpcnN0IGRpZmZlcmVuY2Ugb2YgdGhlIGRhdGENCg0KYGBge3J9DQpnbG9iYWxfZWNvbm9teSAlPiUNCiAgZmlsdGVyKENvZGUgPT0gIkNBRiIpICU+JQ0KICBnZ190c2Rpc3BsYXkoZGlmZmVyZW5jZShFeHBvcnRzKSwgcGxvdF90eXBlPSdwYXJ0aWFsJykNCmBgYA0KNC4gVGhlIFBBQ0Ygc2hvd24gaW4gRmlndXJlIDkuMTQgaXMgc3VnZ2VzdGl2ZSBvZiBhbiBBUigyKSBtb2RlbDsgc28gYW4gaW5pdGlhbCBjYW5kaWRhdGUgbW9kZWwgaXMgYW4gQVJJTUEoMiwxLDApLiBUaGUgQUNGIHN1Z2dlc3RzIGFuIE1BKDMpIG1vZGVsOyBzbyBhbiBhbHRlcm5hdGl2ZSBjYW5kaWRhdGUgaXMgYW4gQVJJTUEoMCwxLDMpLg0KDQo1LiBXZSBmaXQgYm90aCBhbiBBUklNQSgyLDEsMCkgYW5kIGFuIEFSSU1BKDAsMSwzKSBtb2RlbCBhbG9uZyB3aXRoIHR3byBhdXRvbWF0ZWQgbW9kZWwgc2VsZWN0aW9ucywgb25lIHVzaW5nIHRoZSBkZWZhdWx0IHN0ZXB3aXNlIHByb2NlZHVyZSwgYW5kIG9uZSB3b3JraW5nIGhhcmRlciB0byBzZWFyY2ggYSBsYXJnZXIgbW9kZWwgc3BhY2UuDQpgYGB7cn0NCmNhZl9maXQgPC0gZ2xvYmFsX2Vjb25vbXkgJT4lDQogIGZpbHRlcihDb2RlID09ICJDQUYiKSAlPiUNCiAgbW9kZWwoDQogICAgYXJpbWEyMTAgPSBBUklNQShFeHBvcnRzIH4gcGRxKDIsMSwwKSksDQogICAgYXJpbWEwMTMgPSBBUklNQShFeHBvcnRzIH4gcGRxKDAsMSwzKSksDQogICAgc3RlcHdpc2UgPSBBUklNQShFeHBvcnRzKSwNCiAgICBzZWFyY2ggPSBBUklNQShFeHBvcnRzLCBzdGVwd2lzZT1GQUxTRSkNCiAgKQ0KY2FmX2ZpdA0KYGBgDQpgYGB7cn0NCmdsYW5jZShjYWZfZml0KSAlPiUgYXJyYW5nZShBSUNjKQ0KYGBgDQpUaGUgZm91ciBtb2RlbHMgaGF2ZSBhbG1vc3QgdGhlIHNhbWUgQUlDYyB2YWx1ZXMuIE9mIHRoZSBtb2RlbHMgZml0dGVkLCB0aGUgZnVsbCBzZWFyY2ggaGFzIGZvdW5kIHRoYXQgYW4gQVJJTUEoMywxLDApIGdpdmVzIHRoZSBsb3dlc3QgQUlDYyB2YWx1ZSwgY2xvc2VseSBmb2xsb3dlZCBieSB0aGUgQVJJTUEoMiwxLDApIGFuZCBBUklNQSgwLDEsMykg4oCUIHRoZSBsYXR0ZXIgdHdvIGJlaW5nIHRoZSBtb2RlbHMgdGhhdCB3ZSBndWVzc2VkIGZyb20gdGhlIEFDRiBhbmQgUEFDRiBwbG90cy4gVGhlIGF1dG9tYXRlZCBzdGVwd2lzZSBzZWxlY3Rpb24gaGFzIGlkZW50aWZpZWQgYW4gQVJJTUEoMiwxLDIpIG1vZGVsLCB3aGljaCBoYXMgdGhlIGhpZ2hlc3QgQUlDYyB2YWx1ZSBvZiB0aGUgZm91ciBtb2RlbHMuDQoNCjYuIFRoZSBBQ0YgcGxvdCBvZiB0aGUgcmVzaWR1YWxzIGZyb20gdGhlIEFSSU1BKDMsMSwwKSBtb2RlbCBzaG93cyB0aGF0IGFsbCBhdXRvY29ycmVsYXRpb25zIGFyZSB3aXRoaW4gdGhlIHRocmVzaG9sZCBsaW1pdHMsIGluZGljYXRpbmcgdGhhdCB0aGUgcmVzaWR1YWxzIGFyZSBiZWhhdmluZyBsaWtlIHdoaXRlIG5vaXNlLg0KYGBge3J9DQpjYWZfZml0ICU+JQ0KICBzZWxlY3Qoc2VhcmNoKSAlPiUNCiAgZ2dfdHNyZXNpZHVhbHMoKQ0KYGBgDQpBIHBvcnRtYW50ZWF1IHRlc3QgcmV0dXJucyBhIGxhcmdlIHAtdmFsdWUsIGFsc28gc3VnZ2VzdGluZyB0aGF0IHRoZSByZXNpZHVhbHMgYXJlIHdoaXRlIG5vaXNlLg0KYGBge3J9DQphdWdtZW50KGNhZl9maXQpICU+JQ0KICBmaWx0ZXIoLm1vZGVsPT0nc2VhcmNoJykgJT4lDQogIGZlYXR1cmVzKC5pbm5vdiwgbGp1bmdfYm94LCBsYWcgPSAxMCwgZG9mID0gMykNCmBgYA0KNy4gRm9yZWNhc3RzDQpgYGB7cn0NCmNhZl9maXQgJT4lDQogIGZvcmVjYXN0KGg9NSkgJT4lDQogIGZpbHRlcigubW9kZWw9PSdzZWFyY2gnKSAlPiUNCiAgYXV0b3Bsb3QoZ2xvYmFsX2Vjb25vbXkpDQpgYGANCk5vdGUgdGhhdCB0aGUgbWVhbiBmb3JlY2FzdHMgbG9vayB2ZXJ5IHNpbWlsYXIgdG8gd2hhdCB3ZSB3b3VsZCBnZXQgd2l0aCBhIHJhbmRvbSB3YWxrIChlcXVpdmFsZW50IHRvIGFuIEFSSU1BKDAsMSwwKSkuIFRoZSBleHRyYSB3b3JrIHRvIGluY2x1ZGUgQVIgYW5kIE1BIHRlcm1zIGhhcyBtYWRlIGxpdHRsZSBkaWZmZXJlbmNlIHRvIHRoZSBwb2ludCBmb3JlY2FzdHMgaW4gdGhpcyBleGFtcGxlLCBhbHRob3VnaCB0aGUgcHJlZGljdGlvbiBpbnRlcnZhbHMgYXJlIG11Y2ggbmFycm93ZXIgdGhhbiBmb3IgYSByYW5kb20gd2FsayBtb2RlbC4NCg0KIyMgRm9yZWNhc3RpbmcNCg0KaHR0cHM6Ly9vdGV4dHMuY29tL2ZwcDMvYXJpbWEtZm9yZWNhc3RpbmcuaHRtbA0KDQojIyBTZWFzb25hbCBBUklNQSBtb2RlbHMNCg0KQSBzZWFzb25hbCBBUklNQSBtb2RlbCBpcyBmb3JtZWQgYnkgaW5jbHVkaW5nIGFkZGl0aW9uYWwgc2Vhc29uYWwgdGVybXMgaW4gdGhlIEFSSU1BIG1vZGVscyB3ZSBoYXZlIHNlZW4gc28gZmFyLiBJdCBpcyB3cml0dGVuIGFzIGZvbGxvd3M6DQoNCiRcdW5kZXJicmFjZXsocCwgZCwgcSl9JCAkXHVuZGVyYnJhY2V7KFAsIEQsIFEpX3ttfX0kDQoNCiMjIyBBQ0YvUEFDRg0KDQpUaGUgc2Vhc29uYWwgcGFydCBvZiBhbiBBUiBvciBNQSBtb2RlbCB3aWxsIGJlIHNlZW4gaW4gdGhlIHNlYXNvbmFsIGxhZ3Mgb2YgdGhlIFBBQ0YgYW5kIEFDRi4gRm9yIGV4YW1wbGUsIGFuICRBUklNQSgwLDAsMCkoMCwwLDEpXzEyJCAgbW9kZWwgd2lsbCBzaG93Og0KDQotIGEgc3Bpa2UgYXQgbGFnIDEyIGluIHRoZSBBQ0YgYnV0IG5vIG90aGVyIHNpZ25pZmljYW50IHNwaWtlczsNCi0gZXhwb25lbnRpYWwgZGVjYXkgaW4gdGhlIHNlYXNvbmFsIGxhZ3Mgb2YgdGhlIFBBQ0YgKGkuZS4sIGF0IGxhZ3MgMTIsIDI0LCAzNiwg4oCmKS4NCg0KU2ltaWxhcmx5LCBhbiAkQVJJTUEoMCwwLDApKDEsMCwwKV8xMiQgbW9kZWwgd2lsbCBzaG93Og0KDQotIGV4cG9uZW50aWFsIGRlY2F5IGluIHRoZSBzZWFzb25hbCBsYWdzIG9mIHRoZSBBQ0Y7DQotIGEgc2luZ2xlIHNpZ25pZmljYW50IHNwaWtlIGF0IGxhZyAxMiBpbiB0aGUgUEFDRi4NCg0KIyMjIEV4YW1wbGU6IE1vbnRobHkgVVMgbGVpc3VyZSBhbmQgaG9zcGl0YWxpdHkgZW1wbG95bWVudA0KDQptb250aGx5IFVTIGVtcGxveW1lbnQgZGF0YSBmb3IgbGVpc3VyZSBhbmQgaG9zcGl0YWxpdHkgam9icyBmcm9tIEphbnVhcnkgMjAwMCB0byBTZXB0ZW1iZXIgMjAxOToNCmBgYHtyfQ0KbGVpc3VyZSA8LSB1c19lbXBsb3ltZW50ICU+JQ0KICBmaWx0ZXIoVGl0bGUgPT0gIkxlaXN1cmUgYW5kIEhvc3BpdGFsaXR5IiwgeWVhcihNb250aCkgPiAyMDAwKSAlPiUNCiAgbXV0YXRlKEVtcGxveWVkID0gRW1wbG95ZWQvMTAwMCkgJT4lDQogIHNlbGVjdChNb250aCwgRW1wbG95ZWQpDQphdXRvcGxvdChsZWlzdXJlLCBFbXBsb3llZCkgKw0KICBsYWJzKHk9Ik1pbGxpb25zIG9mIHBlb3BsZSIpDQpgYGANCiBXZSB3aWxsIGZpcnN0IHRha2UgYSBzZWFzb25hbCBkaWZmZXJlbmNlDQpgYGB7cn0NCmxlaXN1cmUgJT4lDQogIGdnX3RzZGlzcGxheShkaWZmZXJlbmNlKEVtcGxveWVkLCAxMiksIHBsb3RfdHlwZT0ncGFydGlhbCcsIGxhZz0zNikgKw0KICBsYWJzKHk9IlNlYXNvbmFsbHkgZGlmZmVyZW5jZWQiKQ0KYGBgDQpgYGB7cn0NCmxlaXN1cmUgJT4lDQogIGdnX3RzZGlzcGxheShkaWZmZXJlbmNlKEVtcGxveWVkLCAxMikgJT4lIGRpZmZlcmVuY2UoKSwgcGxvdF90eXBlPSdwYXJ0aWFsJywgbGFnPTM2KSArDQogIGxhYnMoeT0iRG91YmxlIGRpZmZlcmVuY2VkIikNCmBgYA0KVGhlIHNpZ25pZmljYW50IHNwaWtlIGF0IGxhZyAyIGluIHRoZSBBQ0Ygc3VnZ2VzdHMgYSBub24tc2Vhc29uYWwgTUEoMikgY29tcG9uZW50LiBUaGUgc2lnbmlmaWNhbnQgc3Bpa2UgYXQgbGFnIDEyIGluIHRoZSBBQ0Ygc3VnZ2VzdHMgYSBzZWFzb25hbCBNQSgxKSBjb21wb25lbnQuIENvbnNlcXVlbnRseSwgd2UgYmVnaW4gd2l0aCBhbiAkQVJJTUEoMCwxLDIpKDAsMSwxKV8xMiQgbW9kZWwsIGluZGljYXRpbmcgYSBmaXJzdCBkaWZmZXJlbmNlLCBhIHNlYXNvbmFsIGRpZmZlcmVuY2UsIGFuZCBub24tc2Vhc29uYWwgTUEoMikgYW5kIHNlYXNvbmFsIE1BKDEpIGNvbXBvbmVudC4gIElmIHdlIGhhZCBzdGFydGVkIHdpdGggdGhlIFBBQ0YsIHdlIG1heSBoYXZlIHNlbGVjdGVkIGFuICRBUklNQSgyLDEsMCkoMCwxLDEpXzEyJCBtb2RlbC4NCmBgYHtyfQ0KZml0IDwtIGxlaXN1cmUgJT4lDQogIG1vZGVsKA0KICAgIGFyaW1hMDEyMDExID0gQVJJTUEoRW1wbG95ZWQgfiBwZHEoMCwxLDIpICsgUERRKDAsMSwxKSksDQogICAgYXJpbWEyMTAwMTEgPSBBUklNQShFbXBsb3llZCB+IHBkcSgyLDEsMCkgKyBQRFEoMCwxLDEpKSwNCiAgICBhdXRvID0gQVJJTUEoRW1wbG95ZWQsIHN0ZXB3aXNlPUZBTFNFLCBhcHByb3hpbWF0aW9uPUZBTFNFKQ0KICApDQpmaXQNCmBgYA0KYGBge3J9DQpnbGFuY2UoZml0KSAlPiUgYXJyYW5nZShBSUNjKQ0KYGBgDQpUaGUgdGhyZWUgZml0dGVkIG1vZGVscyBoYXZlIHNpbWlsYXIgQUlDYyB2YWx1ZXMsIHdpdGggdGhlIGF1dG9tYXRpY2FsbHkgc2VsZWN0ZWQgbW9kZWwgYmVpbmcgYSBsaXR0bGUgYmV0dGVyLiBPdXIgc2Vjb25kIOKAnGd1ZXNz4oCdIG9mICRBUklNQSgyLDEsMCkoMCwxLDEpXzEyJCAgIHR1cm5lZCBvdXQgdG8gYmUgdmVyeSBjbG9zZSB0byB0aGUgYXV0b21hdGljYWxseSBzZWxlY3RlZCBtb2RlbCBvZiAkQVJJTUEoMiwxLDApKDEsMSwxKV8xMiQuDQoNCmBgYHtyfQ0KZml0ICU+JSBzZWxlY3QoYXV0bykgJT4lIGdnX3RzcmVzaWR1YWxzKGxhZz0zNikNCmBgYA0KT25lIHNtYWxsIGJ1dCBzaWduaWZpY2FudCBzcGlrZSAoYXQgbGFnIDExKSBvdXQgb2YgMzYgaXMgc3RpbGwgY29uc2lzdGVudCB3aXRoIHdoaXRlIG5vaXNlLiBUbyBiZSBzdXJlLCB3ZSB1c2UgYSBManVuZy1Cb3ggdGVzdCwgd2hpY2ggaGFzIGEgbGFyZ2UgcC12YWx1ZSwgY29uZmlybWluZyB0aGF0IHRoZSByZXNpZHVhbHMgYXJlIHNpbWlsYXIgdG8gd2hpdGUgbm9pc2UuIE5vdGUgdGhhdCB0aGUgYWx0ZXJuYXRpdmUgbW9kZWxzIGFsc28gcGFzcyB0aGlzIHRlc3QuDQpgYGB7cn0NCmF1Z21lbnQoZml0KSAlPiUgZmVhdHVyZXMoLmlubm92LCBsanVuZ19ib3gsIGxhZz0yNCwgZG9mPTQpDQpgYGANClRodXMsIHdlIG5vdyBoYXZlIGEgc2Vhc29uYWwgQVJJTUEgbW9kZWwgdGhhdCBwYXNzZXMgdGhlIHJlcXVpcmVkIGNoZWNrcyBhbmQgaXMgcmVhZHkgZm9yIGZvcmVjYXN0aW5nLiBGb3JlY2FzdHMgZnJvbSB0aGUgbW9kZWwgZm9yIHRoZSBuZXh0IHRocmVlIHllYXJzIA0KYGBge3J9DQpmaXQgJT4lIGZvcmVjYXN0KGg9MzYpICU+JSBmaWx0ZXIoLm1vZGVsPT0nYXV0bycpICU+JSBhdXRvcGxvdChsZWlzdXJlKQ0KYGBgDQojIyMgRXhhbXBsZTogQ29ydGljb3N0ZXJvaWQgZHJ1ZyBzYWxlcyBpbiBBdXN0cmFsaWENCg0KRm9yIG91ciBzZWNvbmQgZXhhbXBsZSwgd2Ugd2lsbCB0cnkgdG8gZm9yZWNhc3QgbW9udGhseSBjb3J0aWNvc3Rlcm9pZCBkcnVnIHNhbGVzIGluIEF1c3RyYWxpYS4gVGhlc2UgYXJlIGtub3duIGFzIEgwMiBkcnVncyB1bmRlciB0aGUgQW5hdG9taWNhbCBUaGVyYXBldXRpYyBDaGVtaWNhbCBjbGFzc2lmaWNhdGlvbiBzY2hlbWUuDQpgYGB7cn0NCmgwMiA8LSBQQlMgJT4lDQogIGZpbHRlcihBVEMyID09ICJIMDIiKSAlPiUNCiAgc3VtbWFyaXNlKENvc3QgPSBzdW0oQ29zdCkvMWU2KQ0KaDAyICU+JQ0KICBtdXRhdGUobG9nKENvc3QpKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKC1Nb250aCkgJT4lDQogIGdncGxvdChhZXMoeCA9IE1vbnRoLCB5ID0gdmFsdWUpKSArDQogIGdlb21fbGluZSgpICsNCiAgZmFjZXRfZ3JpZChuYW1lIH4gLiwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgbGFicyh5PSIiLCB0aXRsZT0iQ29ydGVjb3N0ZXJvaWQgZHJ1ZyBzY3JpcHRzIChIMDIpIikNCmBgYA0KVGhlcmUgaXMgYSBzbWFsbCBpbmNyZWFzZSBpbiB0aGUgdmFyaWFuY2Ugd2l0aCB0aGUgbGV2ZWwsIHNvIHdlIHRha2UgbG9nYXJpdGhtcyB0byBzdGFiaWxpc2UgdGhlIHZhcmlhbmNlLg0KDQpUaGUgZGF0YSBhcmUgc3Ryb25nbHkgc2Vhc29uYWwgYW5kIG9idmlvdXNseSBub24tc3RhdGlvbmFyeSwgc28gc2Vhc29uYWwgZGlmZmVyZW5jaW5nIHdpbGwgYmUgdXNlZC4gDQoNCiBJdCBpcyBub3QgY2xlYXIgYXQgdGhpcyBwb2ludCB3aGV0aGVyIHdlIHNob3VsZCBkbyBhbm90aGVyIGRpZmZlcmVuY2Ugb3Igbm90LiBXZSBkZWNpZGUgbm90IHRvLCBidXQgdGhlIGNob2ljZSBpcyBub3Qgb2J2aW91cy4NCg0KVGhlIGxhc3QgZmV3IG9ic2VydmF0aW9ucyBhcHBlYXIgdG8gYmUgZGlmZmVyZW50IChtb3JlIHZhcmlhYmxlKSBmcm9tIHRoZSBlYXJsaWVyIGRhdGEuIFRoaXMgbWF5IGJlIGR1ZSB0byB0aGUgZmFjdCB0aGF0IGRhdGEgYXJlIHNvbWV0aW1lcyByZXZpc2VkIHdoZW4gZWFybGllciBzYWxlcyBhcmUgcmVwb3J0ZWQgbGF0ZS4NCmBgYHtyfQ0KaDAyICU+JSBnZ190c2Rpc3BsYXkoZGlmZmVyZW5jZShsb2coQ29zdCksIDEyKSwgcGxvdF90eXBlPSdwYXJ0aWFsJywgbGFnX21heCA9IDI0KQ0KYGBgDQpJbiB0aGUgcGxvdHMgb2YgdGhlIHNlYXNvbmFsbHkgZGlmZmVyZW5jZWQgZGF0YSwgdGhlcmUgYXJlIHNwaWtlcyBpbiB0aGUgUEFDRiBhdCBsYWdzIDEyIGFuZCAyNCwgYnV0IG5vdGhpbmcgYXQgc2Vhc29uYWwgbGFncyBpbiB0aGUgQUNGLiBUaGlzIG1heSBiZSBzdWdnZXN0aXZlIG9mIGEgc2Vhc29uYWwgQVIoMikgdGVybS4gSW4gdGhlIG5vbi1zZWFzb25hbCBsYWdzLCB0aGVyZSBhcmUgdGhyZWUgc2lnbmlmaWNhbnQgc3Bpa2VzIGluIHRoZSBQQUNGLCBzdWdnZXN0aW5nIGEgcG9zc2libGUgQVIoMykgdGVybS4gVGhlIHBhdHRlcm4gaW4gdGhlIEFDRiBpcyBub3QgaW5kaWNhdGl2ZSBvZiBhbnkgc2ltcGxlIG1vZGVsLg0KDQpDb25zZXF1ZW50bHksIHRoaXMgaW5pdGlhbCBhbmFseXNpcyBzdWdnZXN0cyB0aGF0IGEgcG9zc2libGUgbW9kZWwgZm9yIHRoZXNlIGRhdGEgaXMgYW4gJEFSSU1BKDMsMCwwKSgyLDEsMClfMTIkLiANCmBgYHtyfQ0KZml0IDwtIGgwMiAlPiUNCiAgbW9kZWwoQVJJTUEobG9nKENvc3QpIH4gMCArIHBkcSgzLDAsMSkgKyBQRFEoMCwxLDIpKSkNCnJlcG9ydChmaXQpDQpgYGANCmBgYHtyfQ0KZml0ICU+JSBnZ190c3Jlc2lkdWFscyhsYWdfbWF4PTM2KQ0KYGBgDQpgYGB7cn0NCmF1Z21lbnQoZml0KSAlPiUNCiAgZmVhdHVyZXMoLmlubm92LCBsanVuZ19ib3gsIGxhZyA9IDM2LCBkb2YgPSA2KQ0KYGBgDQpUaGUgaW5ub3ZhdGlvbiByZXNpZHVhbHMgZnJvbSB0aGlzIG1vZGVsIGFyZSBzaG93bi4gVGhlcmUgYXJlIGEgZmV3IHNpZ25pZmljYW50IHNwaWtlcyBpbiB0aGUgQUNGLCBhbmQgdGhlIG1vZGVsIGZhaWxzIHRoZSBManVuZy1Cb3ggdGVzdC4gVGhlIG1vZGVsIGNhbiBzdGlsbCBiZSB1c2VkIGZvciBmb3JlY2FzdGluZywgYnV0IHRoZSBwcmVkaWN0aW9uIGludGVydmFscyBtYXkgbm90IGJlIGFjY3VyYXRlIGR1ZSB0byB0aGUgY29ycmVsYXRlZCByZXNpZHVhbHMuDQoNClNvbWV0aW1lcyBpdCBpcyBqdXN0IG5vdCBwb3NzaWJsZSB0byBmaW5kIGEgbW9kZWwgdGhhdCBwYXNzZXMgYWxsIG9mIHRoZSB0ZXN0cy4NCg0KTm9uZSBvZiB0aGUgbW9kZWxzIGNvbnNpZGVyZWQgaGVyZSBwYXNzIGFsbCBvZiB0aGUgcmVzaWR1YWwgdGVzdHMuIEluIHByYWN0aWNlLCB3ZSB3b3VsZCBub3JtYWxseSB1c2UgdGhlIGJlc3QgbW9kZWwgd2UgY291bGQgZmluZCwgZXZlbiBpZiBpdCBkaWQgbm90IHBhc3MgYWxsIG9mIHRoZSB0ZXN0cy4NCg0KRm9yZWNhc3RzIGZyb20gdGhlICRBUklNQSgzLDAsMSkoMCwxLDIpXzEyJCBtb2RlbCAod2hpY2ggaGFzIHRoZSBzZWNvbmQgbG93ZXN0IFJNU0UgdmFsdWUgb24gdGhlIHRlc3Qgc2V0LCBhbmQgdGhlIGJlc3QgQUlDYyB2YWx1ZSBhbW9uZ3N0IG1vZGVscyB3aXRoIG9ubHkgc2Vhc29uYWwgZGlmZmVyZW5jaW5nKSANCg0KYGBge3J9DQpoMDIgJT4lDQogIG1vZGVsKEFSSU1BKGxvZyhDb3N0KSB+IDAgKyBwZHEoMywwLDEpICsgUERRKDAsMSwyKSkpICU+JQ0KICBmb3JlY2FzdCgpICU+JQ0KICBhdXRvcGxvdChoMDIpICsNCiAgICBsYWJzKHk9IkgwMiBzYWxlcyAobWlsbGlvbiBzY3JpcHRzKSIpDQpgYGANCiMjIEFSSU1BIHZzIEVUUw0KDQpodHRwczovL290ZXh0cy5jb20vZnBwMy9hcmltYS1ldHMuaHRtbA0KDQoNCiMjIyBDb21wYXJpbmcgQVJJTUEoKSBhbmQgRVRTKCkgb24gbm9uLXNlYXNvbmFsIGRhdGENCg0KV2UgY2FuIHVzZSB0aW1lIHNlcmllcyBjcm9zcy12YWxpZGF0aW9uIHRvIGNvbXBhcmUgYW4gQVJJTUEgbW9kZWwgYW5kIGFuIEVUUyBtb2RlbC4NCmBgYHtyfQ0KYXVzX2Vjb25vbXkgPC0gZ2xvYmFsX2Vjb25vbXkgJT4lIGZpbHRlcihDb2RlID09ICJBVVMiKSAlPiUNCiAgbXV0YXRlKFBvcHVsYXRpb24gPSBQb3B1bGF0aW9uLzFlNikNCg0KYXVzX2Vjb25vbXkgJT4lDQogIHNsaWNlKC1uKCkpICU+JQ0KICBzdHJldGNoX3RzaWJibGUoLmluaXQgPSAxMCkgJT4lDQogIG1vZGVsKA0KICAgIEVUUyhQb3B1bGF0aW9uKSwNCiAgICBBUklNQShQb3B1bGF0aW9uKQ0KICApICU+JQ0KICBmb3JlY2FzdChoID0gMSkgJT4lDQogIGFjY3VyYWN5KGF1c19lY29ub215KQ0KYGBgDQpJbiB0aGlzIGNhc2UgdGhlIEVUUyBtb2RlbCBoYXMgaGlnaGVyIGFjY3VyYWN5IG9uIHRoZSBjcm9zcy12YWxpZGF0ZWQgcGVyZm9ybWFuY2UgbWVhc3VyZXMuIEJlbG93IHdlIGdlbmVyYXRlIGFuZCBwbG90IGZvcmVjYXN0cyBmb3IgdGhlIG5leHQgNSB5ZWFycyBnZW5lcmF0ZWQgZnJvbSBhbiBFVFMgbW9kZWwuDQpgYGB7cn0NCmF1c19lY29ub215ICU+JQ0KICBtb2RlbChFVFMoUG9wdWxhdGlvbikpICU+JQ0KICBmb3JlY2FzdChoID0gIjUgeWVhcnMiKSAlPiUNCiAgYXV0b3Bsb3QoYXVzX2Vjb25vbXkpDQpgYGANCiMjIyBDb21wYXJpbmcgQVJJTUEoKSBhbmQgRVRTKCkgb24gc2Vhc29uYWwgZGF0YQ0KDQp3ZSB3YW50IHRvIGNvbXBhcmUgc2Vhc29uYWwgQVJJTUEgYW5kIEVUUyBtb2RlbHMgYXBwbGllZCB0byB0aGUgcXVhcnRlcmx5IGNlbWVudCBwcm9kdWN0aW9uIGRhdGEgKGZyb20gYXVzX3Byb2R1Y3Rpb24pLiBCZWNhdXNlIHRoZSBzZXJpZXMgaXMgcmVsYXRpdmVseSBsb25nLCB3ZSBjYW4gYWZmb3JkIHRvIHVzZSBhIHRyYWluaW5nIGFuZCBhIHRlc3Qgc2V0IHJhdGhlciB0aGFuIHRpbWUgc2VyaWVzIGNyb3NzLXZhbGlkYXRpb24uIFRoZSBhZHZhbnRhZ2UgaXMgdGhhdCB0aGlzIGlzIG11Y2ggZmFzdGVyLiBXZSBjcmVhdGUgYSB0cmFpbmluZyBzZXQgZnJvbSB0aGUgYmVnaW5uaW5nIG9mIDE5ODggdG8gdGhlIGVuZCBvZiAyMDA3IGFuZCBzZWxlY3QgYW4gQVJJTUEgYW5kIGFuIEVUUyBtb2RlbCB1c2luZyB0aGUgQVJJTUEoKSBhbmQgRVRTKCkgZnVuY3Rpb25zLg0KYGBge3J9DQpjZW1lbnQgPC0gYXVzX3Byb2R1Y3Rpb24gJT4lIGZpbHRlcl9pbmRleCgiMTk4OCBRMSIgfiAuKQ0KdHJhaW4gPC0gY2VtZW50ICU+JSBmaWx0ZXJfaW5kZXgoLiB+ICIyMDA3IFE0IikNCmBgYA0KYGBge3J9DQpmaXRfYXJpbWEgPC0gdHJhaW4gJT4lIG1vZGVsKEFSSU1BKENlbWVudCkpDQpyZXBvcnQoZml0X2FyaW1hKQ0KYGBgDQpUaGUgQVJJTUEgbW9kZWwgZG9lcyB3ZWxsIGluIGNhcHR1cmluZyBhbGwgdGhlIGR5bmFtaWNzIGluIHRoZSBkYXRhIGFzIHRoZSByZXNpZHVhbHMgc2VlbSB0byBiZSB3aGl0ZSBub2lzZS4NCmBgYHtyfQ0KZ2dfdHNyZXNpZHVhbHMoZml0X2FyaW1hLCBsYWdfbWF4ID0gMTYpDQpgYGANCmBgYHtyfQ0KYXVnbWVudChmaXRfYXJpbWEpICU+JQ0KICBmZWF0dXJlcyguaW5ub3YsIGxqdW5nX2JveCwgbGFnID0gMTYsIGRvZiA9IDYpDQpgYGANClRoZSBvdXRwdXQgYmVsb3cgYWxzbyBzaG93cyB0aGUgRVRTIG1vZGVsIHNlbGVjdGVkIGFuZCBlc3RpbWF0ZWQgYnkgRVRTKCkuIFRoaXMgbW9kZWwgYWxzbyBkb2VzIHdlbGwgaW4gY2FwdHVyaW5nIGFsbCB0aGUgZHluYW1pY3MgaW4gdGhlIGRhdGEsIGFzIHRoZSByZXNpZHVhbHMgc2ltaWxhcmx5IGFwcGVhciB0byBiZSB3aGl0ZSBub2lzZS4NCg0KYGBge3J9DQpmaXRfZXRzIDwtIHRyYWluICU+JSBtb2RlbChFVFMoQ2VtZW50KSkNCnJlcG9ydChmaXRfZXRzKQ0KZml0X2V0cyAlPiUgZ2dfdHNyZXNpZHVhbHMobGFnX21heCA9IDE2KQ0KYGBgDQpgYGB7cn0NCmF1Z21lbnQoZml0X2V0cykgJT4lDQogIGZlYXR1cmVzKC5pbm5vdiwgbGp1bmdfYm94LCBsYWcgPSAxNiwgZG9mID0gNikNCmBgYA0KVGhlIG91dHB1dCBiZWxvdyBldmFsdWF0ZXMgdGhlIGZvcmVjYXN0aW5nIHBlcmZvcm1hbmNlIG9mIHRoZSB0d28gY29tcGV0aW5nIG1vZGVscyBvdmVyIHRoZSB0ZXN0IHNldC4gSW4gdGhpcyBjYXNlIHRoZSBBUklNQSBtb2RlbCBzZWVtcyB0byBiZSB0aGUgc2xpZ2h0bHkgbW9yZSBhY2N1cmF0ZSBtb2RlbCBiYXNlZCBvbiB0aGUgdGVzdCBzZXQgUk1TRSwgTUFQRSBhbmQgTUFTRS4NCmBgYHtyfQ0KIyBHZW5lcmF0ZSBmb3JlY2FzdHMgYW5kIGNvbXBhcmUgYWNjdXJhY3kgb3ZlciB0aGUgdGVzdCBzZXQNCmJpbmRfcm93cygNCiAgZml0X2FyaW1hICU+JSBhY2N1cmFjeSgpLA0KICBmaXRfZXRzICU+JSBhY2N1cmFjeSgpLA0KICBmaXRfYXJpbWEgJT4lIGZvcmVjYXN0KGggPSAiMiB5ZWFycyA2IG1vbnRocyIpICU+JQ0KICAgIGFjY3VyYWN5KGNlbWVudCksDQogIGZpdF9ldHMgJT4lIGZvcmVjYXN0KGggPSAiMiB5ZWFycyA2IG1vbnRocyIpICU+JQ0KICAgIGFjY3VyYWN5KGNlbWVudCkNCikNCmBgYA0KQmVsb3cgd2UgZ2VuZXJhdGUgYW5kIHBsb3QgZm9yZWNhc3RzIGZyb20gdGhlIEFSSU1BIG1vZGVsIGZvciB0aGUgbmV4dCAzIHllYXJzLg0KYGBge3J9DQpjZW1lbnQgJT4lDQogIG1vZGVsKEFSSU1BKENlbWVudCkpICU+JQ0KICBmb3JlY2FzdChoPSIzIHllYXJzIikgJT4lDQogIGF1dG9wbG90KGNlbWVudCkNCmBgYA0KIyBEeW5hbWljIHJlZ3Jlc3Npb24gbW9kZWxzDQoNCkluIHRoaXMgY2hhcHRlciwgd2UgY29uc2lkZXIgaG93IHRvIGV4dGVuZCBBUklNQSBtb2RlbHMgaW4gb3JkZXIgdG8gYWxsb3cgb3RoZXIgaW5mb3JtYXRpb24gdG8gYmUgaW5jbHVkZWQgaW4gdGhlIG1vZGVscy4NCiANCkluIHRoaXMgY2hhcHRlciwgd2Ugd2lsbCBhbGxvdyB0aGUgZXJyb3JzIGZyb20gYSByZWdyZXNzaW9uIHRvIGNvbnRhaW4gYXV0b2NvcnJlbGF0aW9uLiBUbyBlbXBoYXNpc2UgdGhpcyBjaGFuZ2UgaW4gcGVyc3BlY3RpdmUsIHdlIHdpbGwgcmVwbGFjZSAkzrVfdCQgd2l0aCAkzrdfdCQgaW4gdGhlIGVxdWF0aW9uLiBUaGUgZXJyb3Igc2VyaWVzICTOt190JCBpcyBhc3N1bWVkIHRvIGZvbGxvdyBhbiBBUklNQSBtb2RlbC4gRm9yIGV4YW1wbGUsIGlmICTOt190JCBmb2xsb3dzIGFuIEFSSU1BKDEsMSwxKSBtb2RlbCwgd2UgY2FuIHdyaXRlDQoNClxiZWdpbnthbGlnbip9DQogIHlfdCAmPSBcYmV0YV8wICsgXGJldGFfMSB4X3sxLHR9ICsgXGRvdHMgKyBcYmV0YV9rIHhfe2ssdH0gKyBcZXRhX3QsXFwNCiAgICAgICYgKDEtXHBoaV8xQikoMS1CKVxldGFfdCA9ICgxK1x0aGV0YV8xQilcdmFyZXBzaWxvbl90LA0KXGVuZHthbGlnbip9DQoNCndoZXJlICTOtV90JCBpcyBhIHdoaXRlIG5vaXNlIHNlcmllcy4NCg0KTm90aWNlIHRoYXQgdGhlIG1vZGVsIGhhcyB0d28gZXJyb3IgdGVybXMgaGVyZSDigJQgdGhlIGVycm9yIGZyb20gdGhlIHJlZ3Jlc3Npb24gbW9kZWwsIHdoaWNoIHdlIGRlbm90ZSBieSAkzrdfdCQsIGFuZCB0aGUgZXJyb3IgZnJvbSB0aGUgQVJJTUEgbW9kZWwsIHdoaWNoIHdlIGRlbm90ZSBieSAkzrVfdCQuIE9ubHkgdGhlIEFSSU1BIG1vZGVsIGVycm9ycyBhcmUgYXNzdW1lZCB0byBiZSB3aGl0ZSBub2lzZS4NCg0KIyMgRXN0aW1hdGlvbg0KDQpXaGVuIHdlIGVzdGltYXRlIHRoZSBwYXJhbWV0ZXJzIGZyb20gdGhlIG1vZGVsLCB3ZSBuZWVkIHRvIG1pbmltaXNlIHRoZSBzdW0gb2Ygc3F1YXJlZCAkzrVfdCQgdmFsdWVzLiANCg0KQW4gaW1wb3J0YW50IGNvbnNpZGVyYXRpb24gd2hlbiBlc3RpbWF0aW5nIGEgcmVncmVzc2lvbiB3aXRoIEFSTUEgZXJyb3JzIGlzIHRoYXQgYWxsIG9mIHRoZSB2YXJpYWJsZXMgaW4gdGhlIG1vZGVsIG11c3QgZmlyc3QgYmUgc3RhdGlvbmFyeS4gDQoNCldlIHRoZXJlZm9yZSBmaXJzdCBkaWZmZXJlbmNlIHRoZSBub24tc3RhdGlvbmFyeSB2YXJpYWJsZXMgaW4gdGhlIG1vZGVsLiBJdCBpcyBvZnRlbiBkZXNpcmFibGUgdG8gbWFpbnRhaW4gdGhlIGZvcm0gb2YgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuICR5X3QkIGFuZCB0aGUgcHJlZGljdG9ycywgYW5kIGNvbnNlcXVlbnRseSBpdCBpcyBjb21tb24gdG8gZGlmZmVyZW5jZSBhbGwgb2YgdGhlIHZhcmlhYmxlcyBpZiBhbnkgb2YgdGhlbSBuZWVkIGRpZmZlcmVuY2luZy4gVGhlIHJlc3VsdGluZyBtb2RlbCBpcyB0aGVuIGNhbGxlZCBhIOKAnG1vZGVsIGluIGRpZmZlcmVuY2VzLOKAnSBhcyBkaXN0aW5jdCBmcm9tIGEg4oCcbW9kZWwgaW4gbGV2ZWxzLOKAnSB3aGljaCBpcyB3aGF0IGlzIG9idGFpbmVkIHdoZW4gdGhlIG9yaWdpbmFsIGRhdGEgYXJlIHVzZWQgd2l0aG91dCBkaWZmZXJlbmNpbmcuDQoNCiMjIFJlZ3Jlc3Npb24gd2l0aCBBUklNQSBlcnJvcnMgdXNpbmcgZmFibGUNCg0KVGhlIGZhYmxlIGZ1bmN0aW9uIEFSSU1BKCkgd2lsbCBmaXQgYSByZWdyZXNzaW9uIG1vZGVsIHdpdGggQVJJTUEgZXJyb3JzIGlmIGV4b2dlbm91cyByZWdyZXNzb3JzIGFyZSBpbmNsdWRlZCBpbiB0aGUgZm9ybXVsYS4NCg0KSWYgZGlmZmVyZW5jaW5nIGlzIHNwZWNpZmllZCwgdGhlbiB0aGUgZGlmZmVyZW5jaW5nIGlzIGFwcGxpZWQgdG8gYWxsIHZhcmlhYmxlcyBpbiB0aGUgcmVncmVzc2lvbiBtb2RlbCBiZWZvcmUgdGhlIG1vZGVsIGlzIGVzdGltYXRlZC4gDQoNCiAgICBBUklNQSh5IH4geCArIHBkcSgxLDEsMCkpDQogICAgDQpUaGUgQVJJTUEoKSBmdW5jdGlvbiBjYW4gYWxzbyBiZSB1c2VkIHRvIHNlbGVjdCB0aGUgYmVzdCBBUklNQSBtb2RlbCBmb3IgdGhlIGVycm9ycy4gVGhpcyBpcyBkb25lIGJ5IG5vdCBzcGVjaWZ5aW5nIHRoZSBwZHEoKSBzcGVjaWFsLiBJZiBkaWZmZXJlbmNpbmcgaXMgcmVxdWlyZWQsIHRoZW4gYWxsIHZhcmlhYmxlcyBhcmUgZGlmZmVyZW5jZWQgZHVyaW5nIHRoZSBlc3RpbWF0aW9uIHByb2Nlc3MsIGFsdGhvdWdoIHRoZSBmaW5hbCBtb2RlbCB3aWxsIGJlIGV4cHJlc3NlZCBpbiB0ZXJtcyBvZiB0aGUgb3JpZ2luYWwgdmFyaWFibGVzLg0KDQpUaGUgQUlDYyBpcyBjYWxjdWxhdGVkIGZvciB0aGUgZmluYWwgbW9kZWwsIGFuZCB0aGlzIHZhbHVlIGNhbiBiZSB1c2VkIHRvIGRldGVybWluZSB0aGUgYmVzdCBwcmVkaWN0b3JzLiBUaGF0IGlzLCB0aGUgcHJvY2VkdXJlIHNob3VsZCBiZSByZXBlYXRlZCBmb3IgYWxsIHN1YnNldHMgb2YgcHJlZGljdG9ycyB0byBiZSBjb25zaWRlcmVkLCBhbmQgdGhlIG1vZGVsIHdpdGggdGhlIGxvd2VzdCBBSUNjIHZhbHVlIHNlbGVjdGVkLg0KDQojIyMgRXhhbXBsZTogVVMgUGVyc29uYWwgQ29uc3VtcHRpb24gYW5kIEluY29tZQ0KDQpRdWFydGVybHkgY2hhbmdlcyBpbiBwZXJzb25hbCBjb25zdW1wdGlvbiBleHBlbmRpdHVyZSBhbmQgcGVyc29uYWwgZGlzcG9zYWJsZSBpbmNvbWUgZnJvbSAxOTcwIHRvIDIwMTYgUTMuIFdlIHdvdWxkIGxpa2UgdG8gZm9yZWNhc3QgY2hhbmdlcyBpbiBleHBlbmRpdHVyZSBiYXNlZCBvbiBjaGFuZ2VzIGluIGluY29tZS4NCg0KYGBge3J9DQp1c19jaGFuZ2UgJT4lDQogIGdhdGhlcigidmFyIiwgInZhbHVlIiwgQ29uc3VtcHRpb24sIEluY29tZSkgJT4lDQogIGdncGxvdChhZXMoeCA9IFF1YXJ0ZXIsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBmYWNldF9ncmlkKHZhcnModmFyKSwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgbGFicyh5ID0gIiIsDQogICAgICAgdGl0bGUgPSAiUXVhcnRlcmx5IGNoYW5nZXMgaW4gVVMgY29uc3VtcHRpb24gYW5kIHBlcnNvbmFsIGluY29tZSIpDQpgYGANCmBgYHtyfQ0KZml0IDwtIHVzX2NoYW5nZSAlPiUNCiAgbW9kZWwoQVJJTUEoQ29uc3VtcHRpb24gfiBJbmNvbWUpKQ0KcmVwb3J0KGZpdCkNCmBgYA0KVGhlIGRhdGEgYXJlIGNsZWFybHkgYWxyZWFkeSBzdGF0aW9uYXJ5IChhcyB3ZSBhcmUgY29uc2lkZXJpbmcgcGVyY2VudGFnZSBjaGFuZ2VzIHJhdGhlciB0aGFuIHJhdyBleHBlbmRpdHVyZSBhbmQgaW5jb21lKSwgc28gdGhlcmUgaXMgbm8gbmVlZCBmb3IgYW55IGRpZmZlcmVuY2luZy4gVGhlIGZpdHRlZCBtb2RlbCBpcw0KDQpcYmVnaW57YWxpZ24qfQ0KICB5X3QgJj0gMC41OTUgKw0KICAgICAgICAgMC4xOTggeF90ICsgXGV0YV90LCBcXA0KICBcZXRhX3QgJj0gMC43MDcgXGV0YV97dC0xfSArIFx2YXJlcHNpbG9uX3QNCiAgICAgICAgLTAuNjE3IFx2YXJlcHNpbG9uX3t0LTF9ICsNCiAgICAgICAgMC4yMDcgXHZhcmVwc2lsb25fe3QtMn0sXFwNCiAgXHZhcmVwc2lsb25fdCAmXHNpbSBcdGV4dHtOSUR9KDAsMC4zMTEpLg0KXGVuZHthbGlnbip9DQoNCldlIGNhbiByZWNvdmVyIGVzdGltYXRlcyBvZiBib3RoIHRoZSAkzrdfdCQgYW5kICTOtV90JCBzZXJpZXMgdXNpbmcgdGhlIHJlc2lkdWFscygpIGZ1bmN0aW9uLg0KYGBge3J9DQpiaW5kX3Jvd3MoDQogICAgYFJlZ3Jlc3Npb24gRXJyb3JzYCA9IGFzX3RpYmJsZShyZXNpZHVhbHMoZml0LCB0eXBlID0gInJlZ3Jlc3Npb24iKSksDQogICAgYEFSSU1BIEVycm9yc2AgPSBhc190aWJibGUocmVzaWR1YWxzKGZpdCwgdHlwZSA9ICJpbm5vdmF0aW9uIikpLA0KICAgIC5pZCA9ICJ0eXBlIg0KICApICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBRdWFydGVyLCB5ID0gLnJlc2lkKSkgKw0KICBnZW9tX2xpbmUoKSArDQogIGZhY2V0X2dyaWQodmFycyh0eXBlKSwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgbGFicyh5ID0gIiIpDQpgYGANCkl0IGlzIHRoZSBBUklNQSBlcnJvcnMgKHRoZSBpbm5vdmF0aW9ucykgdGhhdCBzaG91bGQgcmVzZW1ibGUgYSB3aGl0ZSBub2lzZSBzZXJpZXMuDQpgYGB7cn0NCmZpdCAlPiUgZ2dfdHNyZXNpZHVhbHMoKQ0KYGBgDQpgYGB7cn0NCmF1Z21lbnQoZml0KSAlPiUNCiAgZmVhdHVyZXMoLmlubm92LCBsanVuZ19ib3gsIGRvZiA9IDUsIGxhZyA9IDgpDQpgYGANClRoZSByZXNpZHVhbHMgKGkuZS4sIHRoZSBBUklNQSBlcnJvcnMpIGFyZSBub3Qgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgZnJvbSB3aGl0ZSBub2lzZS4NCg0KIyMgRm9yZWNhc3RpbmcNCg0KVG8gZm9yZWNhc3QgdXNpbmcgYSByZWdyZXNzaW9uIG1vZGVsIHdpdGggQVJJTUEgZXJyb3JzLCB3ZSBuZWVkIHRvIGZvcmVjYXN0IHRoZSByZWdyZXNzaW9uIHBhcnQgb2YgdGhlIG1vZGVsIGFuZCB0aGUgQVJJTUEgcGFydCBvZiB0aGUgbW9kZWwsIGFuZCBjb21iaW5lIHRoZSByZXN1bHRzLg0KDQpBcyB3aXRoIG9yZGluYXJ5IHJlZ3Jlc3Npb24gbW9kZWxzLCBpbiBvcmRlciB0byBvYnRhaW4gZm9yZWNhc3RzIHdlIGZpcnN0IG5lZWQgdG8gZm9yZWNhc3QgdGhlIHByZWRpY3RvcnMuIFdoZW4gdGhlIHByZWRpY3RvcnMgYXJlIGtub3duIGludG8gdGhlIGZ1dHVyZSAoZS5nLiwgY2FsZW5kYXItcmVsYXRlZCB2YXJpYWJsZXMgc3VjaCBhcyB0aW1lLCBkYXktb2Ytd2VlaywgZXRjLiksIHRoaXMgaXMgc3RyYWlnaHRmb3J3YXJkLiBCdXQgd2hlbiB0aGUgcHJlZGljdG9ycyBhcmUgdGhlbXNlbHZlcyB1bmtub3duLCB3ZSBtdXN0IGVpdGhlciBtb2RlbCB0aGVtIHNlcGFyYXRlbHksIG9yIHVzZSBhc3N1bWVkIGZ1dHVyZSB2YWx1ZXMgZm9yIGVhY2ggcHJlZGljdG9yLg0KDQojIyMgVVMgUGVyc29uYWwgQ29uc3VtcHRpb24gYW5kIEluY29tZQ0KDQpXZSB3aWxsIGNhbGN1bGF0ZSBmb3JlY2FzdHMgZm9yIHRoZSBuZXh0IGVpZ2h0IHF1YXJ0ZXJzIGFzc3VtaW5nIHRoYXQgdGhlIGZ1dHVyZSBwZXJjZW50YWdlIGNoYW5nZXMgaW4gcGVyc29uYWwgZGlzcG9zYWJsZSBpbmNvbWUgd2lsbCBiZSBlcXVhbCB0byB0aGUgbWVhbiBwZXJjZW50YWdlIGNoYW5nZSBmcm9tIHRoZSBsYXN0IGZvcnR5IHllYXJzLg0KYGBge3J9DQp1c19jaGFuZ2VfZnV0dXJlIDwtIG5ld19kYXRhKHVzX2NoYW5nZSwgOCkgJT4lDQogIG11dGF0ZShJbmNvbWUgPSBtZWFuKHVzX2NoYW5nZSRJbmNvbWUpKQ0KZm9yZWNhc3QoZml0LCBuZXdfZGF0YSA9IHVzX2NoYW5nZV9mdXR1cmUpICU+JQ0KICBhdXRvcGxvdCh1c19jaGFuZ2UpICsNCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2UgY2hhbmdlIikNCmBgYA0KVGhlIHByZWRpY3Rpb24gaW50ZXJ2YWxzIGZvciB0aGlzIG1vZGVsIGFyZSBuYXJyb3dlciB0aGFuIGlmIHdlIGhhZCBmaXR0ZWQgYW4gQVJJTUEgbW9kZWwgd2l0aG91dCBjb3ZhcmlhdGVzLCBiZWNhdXNlIHdlIGFyZSBub3cgYWJsZSB0byBleHBsYWluIHNvbWUgb2YgdGhlIHZhcmlhdGlvbiBpbiB0aGUgZGF0YSB1c2luZyB0aGUgaW5jb21lIHByZWRpY3Rvci4NCg0KSXQgaXMgaW1wb3J0YW50IHRvIHJlYWxpc2UgdGhhdCB0aGUgcHJlZGljdGlvbiBpbnRlcnZhbHMgZnJvbSByZWdyZXNzaW9uIG1vZGVscyAod2l0aCBvciB3aXRob3V0IEFSSU1BIGVycm9ycykgZG8gbm90IHRha2UgaW50byBhY2NvdW50IHRoZSB1bmNlcnRhaW50eSBpbiB0aGUgZm9yZWNhc3RzIG9mIHRoZSBwcmVkaWN0b3JzLiBTbyB0aGV5IHNob3VsZCBiZSBpbnRlcnByZXRlZCBhcyBiZWluZyBjb25kaXRpb25hbCBvbiB0aGUgYXNzdW1lZCAob3IgZXN0aW1hdGVkKSBmdXR1cmUgdmFsdWVzIG9mIHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzLg0KDQojIyMgRm9yZWNhc3RpbmcgZWxlY3RyaWNpdHkgZGVtYW5kDQoNCkRhaWx5IGVsZWN0cmljaXR5IGRlbWFuZCBjYW4gYmUgbW9kZWxsZWQgYXMgYSBmdW5jdGlvbiBvZiB0ZW1wZXJhdHVyZS4gVGhlIGhpZ2hlciBkZW1hbmQgb24gY29sZCBhbmQgaG90IGRheXMgaXMgcmVmbGVjdGVkIGluIHRoZSBVLXNoYXBlDQpgYGB7cn0NCnZpY19lbGVjX2RhaWx5IDwtIHZpY19lbGVjICU+JQ0KICBmaWx0ZXIoeWVhcihUaW1lKSA9PSAyMDE0KSAlPiUNCiAgaW5kZXhfYnkoRGF0ZSA9IGRhdGUoVGltZSkpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgRGVtYW5kID0gc3VtKERlbWFuZCkgLyAxZTMsDQogICAgVGVtcGVyYXR1cmUgPSBtYXgoVGVtcGVyYXR1cmUpLA0KICAgIEhvbGlkYXkgPSBhbnkoSG9saWRheSkNCiAgKSAlPiUNCiAgbXV0YXRlKERheV9UeXBlID0gY2FzZV93aGVuKA0KICAgIEhvbGlkYXkgfiAiSG9saWRheSIsDQogICAgd2RheShEYXRlKSAlaW4lIDI6NiB+ICJXZWVrZGF5IiwNCiAgICBUUlVFIH4gIldlZWtlbmQiDQogICkpDQoNCnZpY19lbGVjX2RhaWx5ICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBUZW1wZXJhdHVyZSwgeSA9IERlbWFuZCwgY29sb3VyID0gRGF5X1R5cGUpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGxhYnMoeSA9ICJFbGVjdHJpY2l0eSBkZW1hbmQgKEdXKSIsIHggPSAiTWF4aW11bSBkYWlseSB0ZW1wZXJhdHVyZSIpDQpgYGANClRoZSBkYXRhIHN0b3JlZCBhcyB2aWNfZWxlY19kYWlseSBpbmNsdWRlcyB0b3RhbCBkYWlseSBkZW1hbmQsIGRhaWx5IG1heGltdW0gdGVtcGVyYXR1cmVzLCBhbmQgYW4gaW5kaWNhdG9yIHZhcmlhYmxlIGZvciBpZiB0aGF0IGRheSBpcyBhIHB1YmxpYyBob2xpZGF5LiANCmBgYHtyfQ0KdmljX2VsZWNfZGFpbHkgJT4lDQogIHNlbGVjdChEYXRlOlRlbXBlcmF0dXJlKSAlPiUgDQogIHBpdm90X2xvbmdlcigtRGF0ZSwgbmFtZXNfdG8gPSAidHlwZSIsIHZhbHVlc190byA9ICJkYXRhIikgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBEYXRlLCB5ID0gZGF0YSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBmYWNldF9ncmlkKHZhcnModHlwZSksIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIGxhYnMoeSA9ICJWYWx1ZSIpDQpgYGANCndlIGZpdCBhIHF1YWRyYXRpYyByZWdyZXNzaW9uIG1vZGVsIHdpdGggQVJNQSBlcnJvcnMgdXNpbmcgdGhlIEFSSU1BKCkgZnVuY3Rpb24uIFRoZSBtb2RlbCBhbHNvIGluY2x1ZGVzIGFuIGluZGljYXRvciB2YXJpYWJsZSBmb3IgaWYgdGhlIGRheSB3YXMgYSB3b3JraW5nIGRheSBvciBub3QuDQpgYGB7cn0NCmZpdCA8LSB2aWNfZWxlY19kYWlseSAlPiUNCiAgbW9kZWwoQVJJTUEoRGVtYW5kIH4gVGVtcGVyYXR1cmUgKyBJKFRlbXBlcmF0dXJlXjIpICsgKERheV9UeXBlID09ICJXZWVrZGF5IikpKQ0KZml0ICU+JSBnZ190c3Jlc2lkdWFscygpDQpgYGANCmBgYHtyfQ0KYXVnbWVudChmaXQpICU+JQ0KICBmZWF0dXJlcyguaW5ub3YsIGxqdW5nX2JveCwgZG9mID0gOCwgbGFnID0gMTQpDQpgYGANClRoZXJlIGlzIGNsZWFyIGhldGVyb3NrZWRhc3RpY2l0eSBpbiB0aGUgcmVzaWR1YWxzLCB3aXRoIGhpZ2hlciB2YXJpYW5jZSBpbiBKYW51YXJ5IGFuZCBGZWJydWFyeSwgYW5kIGxvd2VyIHZhcmlhbmNlIGluIE1heS4gVGhlIG1vZGVsIGFsc28gaGFzIHNvbWUgc2lnbmlmaWNhbnQgYXV0b2NvcnJlbGF0aW9uIGluIHRoZSByZXNpZHVhbHMsIGFuZCB0aGUgaGlzdG9ncmFtIG9mIHRoZSByZXNpZHVhbHMgc2hvd3MgbG9uZyB0YWlscy4gQWxsIG9mIHRoZXNlIGlzc3VlcyB3aXRoIHRoZSByZXNpZHVhbHMgbWF5IGFmZmVjdCB0aGUgY292ZXJhZ2Ugb2YgdGhlIHByZWRpY3Rpb24gaW50ZXJ2YWxzLCBidXQgdGhlIHBvaW50IGZvcmVjYXN0cyBzaG91bGQgc3RpbGwgYmUgb2suDQoNClVzaW5nIHRoZSBlc3RpbWF0ZWQgbW9kZWwgd2UgZm9yZWNhc3QgMTQgZGF5cyBhaGVhZCBzdGFydGluZyBmcm9tIFRodXJzZGF5IDEgSmFudWFyeSAyMDE1IChhIG5vbi13b3JrLWRheSBiZWluZyBhIHB1YmxpYyBob2xpZGF5IGZvciBOZXcgWWVhcnMgRGF5KS4gSW4gdGhpcyBjYXNlLCB3ZSBjb3VsZCBvYnRhaW4gd2VhdGhlciBmb3JlY2FzdHMgZnJvbSB0aGUgd2VhdGhlciBidXJlYXUgZm9yIHRoZSBuZXh0IDE0IGRheXMuIEJ1dCBmb3IgdGhlIHNha2Ugb2YgaWxsdXN0cmF0aW9uLCB3ZSB3aWxsIHVzZSBzY2VuYXJpbyBiYXNlZCBmb3JlY2FzdGluZyB3aGVyZSB3ZSBzZXQgdGhlIHRlbXBlcmF0dXJlIGZvciB0aGUgbmV4dCAxNCBkYXlzIHRvIGEgY29uc3RhbnQgMjYgZGVncmVlcy4NCmBgYHtyfQ0KdmljX2VsZWNfZnV0dXJlIDwtIG5ld19kYXRhKHZpY19lbGVjX2RhaWx5LCAxNCkgJT4lDQogIG11dGF0ZSgNCiAgICBUZW1wZXJhdHVyZSA9IDI2LA0KICAgIEhvbGlkYXkgPSBjKFRSVUUsIHJlcChGQUxTRSwgMTMpKSwNCiAgICBEYXlfVHlwZSA9IGNhc2Vfd2hlbigNCiAgICAgIEhvbGlkYXkgfiAiSG9saWRheSIsDQogICAgICB3ZGF5KERhdGUpICVpbiUgMjo2IH4gIldlZWtkYXkiLA0KICAgICAgVFJVRSB+ICJXZWVrZW5kIg0KICAgICkNCiAgKQ0KZm9yZWNhc3QoZml0LCB2aWNfZWxlY19mdXR1cmUpICU+JQ0KICBhdXRvcGxvdCh2aWNfZWxlY19kYWlseSkgKyBsYWJzKHkgPSAiRWxlY3RyaWNpdHkgZGVtYW5kIChHVykiKQ0KYGBgDQpUaGUgcG9pbnQgZm9yZWNhc3RzIGxvb2sgcmVhc29uYWJsZSBmb3IgdGhlIGZpcnN0IHR3byB3ZWVrcyBvZiAyMDE1LiBUaGUgc2xvdyBkb3duIGluIGVsZWN0cmljaXR5IGRlbWFuZCBhdCB0aGUgZW5kIG9mIDIwMTQgKGR1ZSB0byBtYW55IHBlb3BsZSB0YWtpbmcgc3VtbWVyIHZhY2F0aW9ucykgaGFzIGNhdXNlZCB0aGUgZm9yZWNhc3RzIGZvciB0aGUgbmV4dCB0d28gd2Vla3MgdG8gc2hvdyBzaW1pbGFybHkgbG93IGRlbWFuZCB2YWx1ZXMuDQoNCiMjIFN0b2NoYXN0aWMgYW5kIGRldGVybWluaXN0aWMgdHJlbmRzDQoNClRoZXJlIGFyZSB0d28gZGlmZmVyZW50IHdheXMgb2YgbW9kZWxsaW5nIGEgbGluZWFyIHRyZW5kLiBBIGRldGVybWluaXN0aWMgdHJlbmQgaXMgb2J0YWluZWQgdXNpbmcgdGhlIHJlZ3Jlc3Npb24gbW9kZWwNCg0KJHlfdCA9IFxiZXRhXzAgKyBcYmV0YV8xIHQgKyBcZXRhX3QkDQoNCndoZXJlICTOt190JCBpcyBhbiBBUk1BIHByb2Nlc3MuIEEgc3RvY2hhc3RpYyB0cmVuZCBpcyBvYnRhaW5lZCB1c2luZyB0aGUgbW9kZWwNCg0KJHlfdCA9IFxiZXRhXzAgKyBcYmV0YV8xIHQgKyBcZXRhX3QkDQoNCndoZXJlICTOt190JCBpcyBhbiBBUklNQSBwcm9jZXNzIHdpdGggJGQ9MSQuIEluIHRoZSBsYXR0ZXIgY2FzZSwgd2UgY2FuIGRpZmZlcmVuY2UgYm90aCBzaWRlcyBzbyB0aGF0ICR5X3QnID0gXGJldGFfMSArIFxldGFfdCckLCB3aGVyZSAkXGV0YV904oCyJCBpcyBhbiBBUk1BIHByb2Nlc3MNCg0KJHlfdCA9IHlfe3QtMX0gKyBcYmV0YV8xICsgXGV0YV90Jy4kDQoNCiMjIyBFeGFtcGxlOiBBaXIgdHJhbnNwb3J0IHBhc3NlbmdlcnMgQXVzdHJhbGlhDQoNCmBgYHtyfQ0KYXVzX2FpcnBhc3NlbmdlcnMgJT4lDQogIGF1dG9wbG90KFBhc3NlbmdlcnMpICsNCiAgbGFicyh4ID0gIlllYXIiLCB5ID0gIm1pbGxpb25zIG9mIHBlb3BsZSIsDQogICAgICAgdGl0bGUgPSAiVG90YWwgYW5udWFsIGFpciBwYXNzZW5nZXJzIikNCmBgYA0KSXQgc2hvd3MgdGhlIHRvdGFsIG51bWJlciBvZiBwYXNzZW5nZXJzIGZvciBBdXN0cmFsaWFuIGFpciBjYXJyaWVycyBlYWNoIHllYXIgZnJvbSAxOTcwIHRvIDIwMTYuIFdlIHdpbGwgZml0IGJvdGggYSBkZXRlcm1pbmlzdGljIGFuZCBhIHN0b2NoYXN0aWMgdHJlbmQgbW9kZWwgdG8gdGhlc2UgZGF0YS4NCg0KVGhlIGRldGVybWluaXN0aWMgdHJlbmQgbW9kZWwgaXMgb2J0YWluZWQgYXMgZm9sbG93czoNCmBgYHtyfQ0KZml0X2RldGVybWluaXN0aWMgPC0gYXVzX2FpcnBhc3NlbmdlcnMgJT4lDQogIG1vZGVsKGRldGVybWluaXN0aWMgPSBBUklNQShQYXNzZW5nZXJzIH4gMSArIHRyZW5kKCkgKyBwZHEoZCA9IDApKSkNCnJlcG9ydChmaXRfZGV0ZXJtaW5pc3RpYykNCmBgYA0KVGhpcyBtb2RlbCBjYW4gYmUgd3JpdHRlbiBhcw0KDQpcYmVnaW57YWxpZ24qfQ0KICB5X3QgJj0gMS4wOSArIDEuMzM3IHQgKyBcZXRhX3QgXFwNCiAgXGV0YV90ICY9IDAuOTQzIFxldGFfe3QtMX0gICsgXHZhcmVwc2lsb25fdFxcDQogIFx2YXJlcHNpbG9uX3QgJlxzaW0gXHRleHR7TklEfSgwLDQuNzEzKS4NClxlbmR7YWxpZ24qfQ0KDQpUaGUgZXN0aW1hdGVkIGdyb3d0aCBpbiB2aXNpdG9yIG51bWJlcnMgaXMgMS4zNCBtaWxsaW9uIHBlb3BsZSBwZXIgeWVhci4NCg0KQWx0ZXJuYXRpdmVseSwgdGhlIHN0b2NoYXN0aWMgdHJlbmQgbW9kZWwgY2FuIGJlIGVzdGltYXRlZC4NCmBgYHtyfQ0KZml0X3N0b2NoYXN0aWMgPC0gYXVzX2FpcnBhc3NlbmdlcnMgJT4lDQogIG1vZGVsKHN0b2NoYXN0aWMgPSBBUklNQShQYXNzZW5nZXJzIH4gcGRxKGQgPSAxKSkpDQpyZXBvcnQoZml0X3N0b2NoYXN0aWMpDQpgYGANClRoaXMgbW9kZWwgY2FuIGJlIHdyaXR0ZW4gYXMgJHlfdC15X3t0LTF9ID0gMS4zNjcgKyBcdmFyZXBzaWxvbl90JCwgb3IgZXF1aXZhbGVudGx5DQoNClxiZWdpbnthbGlnbip9DQogIHlfdCAmPSB5XzAgKyAxLjM2NyB0ICsgXGV0YV90IFxcDQogIFxldGFfdCAmPSBcZXRhX3t0LTF9ICsgXHZhcmVwc2lsb25fe3R9XFwNCiAgXHZhcmVwc2lsb25fdCAmXHNpbSBcdGV4dHtOSUR9KDAsNC42MjkpLg0KXGVuZHthbGlnbip9DQoNCkluIHRoaXMgY2FzZSwgdGhlIGVzdGltYXRlZCBncm93dGggaW4gdmlzaXRvciBudW1iZXJzIGlzIGFsc28gMS4zNyBtaWxsaW9uIHBlb3BsZSBwZXIgeWVhci4NCg0KQWx0aG91Z2ggdGhlIGdyb3d0aCBlc3RpbWF0ZXMgYXJlIHNpbWlsYXIsIHRoZSBwcmVkaWN0aW9uIGludGVydmFscyBhcmUgbm90LiBJbiBwYXJ0aWN1bGFyLCBzdG9jaGFzdGljIHRyZW5kcyBoYXZlIG11Y2ggd2lkZXIgcHJlZGljdGlvbiBpbnRlcnZhbHMgYmVjYXVzZSB0aGUgZXJyb3JzIGFyZSBub24tc3RhdGlvbmFyeS4NCmBgYHtyfQ0KYXVzX2FpcnBhc3NlbmdlcnMgJT4lDQogIGF1dG9wbG90KFBhc3NlbmdlcnMpICsNCiAgYXV0b2xheWVyKGZpdF9zdG9jaGFzdGljICU+JSBmb3JlY2FzdChoID0gMjApLA0KICAgIGNvbG91ciA9ICJibHVlIiwgYWxwaGEgPSAwLjUsIGxldmVsID0gOTUpICsNCiAgYXV0b2xheWVyKGZpdF9kZXRlcm1pbmlzdGljICU+JSBmb3JlY2FzdChoID0gMjApLA0KICAgIGNvbG91ciA9ICJyZWQiLCBhbHBoYSA9IDAuNSwgbGV2ZWwgPSA5NSkgKw0KICBsYWJzKHggPSAiWWVhciIsIHkgPSAiQWlyIHBhc3NlbmdlcnMgKG1pbGxpb25zKSIsDQogICAgICAgdGl0bGUgPSAiRm9yZWNhc3RzIGZyb20gdHJlbmQgbW9kZWxzIikNCmBgYA0KVGhlcmUgaXMgYW4gaW1wbGljaXQgYXNzdW1wdGlvbiB3aXRoIGRldGVybWluaXN0aWMgdHJlbmRzIHRoYXQgdGhlIHNsb3BlIG9mIHRoZSB0cmVuZCBpcyBub3QgZ29pbmcgdG8gY2hhbmdlIG92ZXIgdGltZS4gT24gdGhlIG90aGVyIGhhbmQsIHN0b2NoYXN0aWMgdHJlbmRzIGNhbiBjaGFuZ2UsIGFuZCB0aGUgZXN0aW1hdGVkIGdyb3d0aCBpcyBvbmx5IGFzc3VtZWQgdG8gYmUgdGhlIGF2ZXJhZ2UgZ3Jvd3RoIG92ZXIgdGhlIGhpc3RvcmljYWwgcGVyaW9kLCBub3QgbmVjZXNzYXJpbHkgdGhlIHJhdGUgb2YgZ3Jvd3RoIHRoYXQgd2lsbCBiZSBvYnNlcnZlZCBpbnRvIHRoZSBmdXR1cmUuIENvbnNlcXVlbnRseSwgaXQgaXMgc2FmZXIgdG8gZm9yZWNhc3Qgd2l0aCBzdG9jaGFzdGljIHRyZW5kcywgZXNwZWNpYWxseSBmb3IgbG9uZ2VyIGZvcmVjYXN0IGhvcml6b25zLCBhcyB0aGUgcHJlZGljdGlvbiBpbnRlcnZhbHMgYWxsb3cgZm9yIGdyZWF0ZXIgdW5jZXJ0YWludHkgaW4gZnV0dXJlIGdyb3d0aC4NCg0KIyMjIER5bmFtaWMgaGFybW9uaWMgcmVncmVzc2lvbg0KDQpXaGVuIHRoZXJlIGFyZSBsb25nIHNlYXNvbmFsIHBlcmlvZHMsIGEgZHluYW1pYyByZWdyZXNzaW9uIHdpdGggRm91cmllciB0ZXJtcyBpcyBvZnRlbiBiZXR0ZXIuDQoNClNvIGZvciBzdWNoIHRpbWUgc2VyaWVzLCB3ZSBwcmVmZXIgYSBoYXJtb25pYyByZWdyZXNzaW9uIGFwcHJvYWNoIHdoZXJlIHRoZSBzZWFzb25hbCBwYXR0ZXJuIGlzIG1vZGVsbGVkIHVzaW5nIEZvdXJpZXIgdGVybXMgd2l0aCBzaG9ydC10ZXJtIHRpbWUgc2VyaWVzIGR5bmFtaWNzIGhhbmRsZWQgYnkgYW4gQVJNQSBlcnJvci4NCg0KVGhlIGFkdmFudGFnZXMgb2YgdGhpcyBhcHByb2FjaCBhcmU6DQoNCi0gaXQgYWxsb3dzIGFueSBsZW5ndGggc2Vhc29uYWxpdHk7DQotIGZvciBkYXRhIHdpdGggbW9yZSB0aGFuIG9uZSBzZWFzb25hbCBwZXJpb2QsIEZvdXJpZXIgdGVybXMgb2YgZGlmZmVyZW50IGZyZXF1ZW5jaWVzIGNhbiBiZSBpbmNsdWRlZDsNCi0gdGhlIHNtb290aG5lc3Mgb2YgdGhlIHNlYXNvbmFsIHBhdHRlcm4gY2FuIGJlIGNvbnRyb2xsZWQgYnkgSywgdGhlIG51bWJlciBvZiBGb3VyaWVyIHNpbiBhbmQgY29zIHBhaXJzIOKAkyB0aGUgc2Vhc29uYWwgcGF0dGVybiBpcyBzbW9vdGhlciBmb3Igc21hbGxlciB2YWx1ZXMgb2YgSzsNCi0gdGhlIHNob3J0LXRlcm0gZHluYW1pY3MgYXJlIGVhc2lseSBoYW5kbGVkIHdpdGggYSBzaW1wbGUgQVJNQSBlcnJvci4NCg0KVGhlIG9ubHkgcmVhbCBkaXNhZHZhbnRhZ2UgKGNvbXBhcmVkIHRvIGEgc2Vhc29uYWwgQVJJTUEgbW9kZWwpIGlzIHRoYXQgdGhlIHNlYXNvbmFsaXR5IGlzIGFzc3VtZWQgdG8gYmUgZml4ZWQg4oCUIHRoZSBzZWFzb25hbCBwYXR0ZXJuIGlzIG5vdCBhbGxvd2VkIHRvIGNoYW5nZSBvdmVyIHRpbWUuIA0KDQojIyMgQXVzdHJhbGlhbiBlYXRpbmcgb3V0IGV4cGVuZGl0dXJlDQoNCndlIGRlbW9uc3RyYXRlIGNvbWJpbmluZyBGb3VyaWVyIHRlcm1zIGZvciBjYXB0dXJpbmcgc2Vhc29uYWxpdHkgd2l0aCBBUklNQSBlcnJvcnMgY2FwdHVyaW5nIG90aGVyIGR5bmFtaWNzIGluIHRoZSBkYXRhLiANCg0KV2UgdXNlIHRoZSB0b3RhbCBtb250aGx5IGV4cGVuZGl0dXJlIG9uIGNhZmVzLCByZXN0YXVyYW50cyBhbmQgdGFrZWF3YXkgZm9vZCBzZXJ2aWNlcyBpbiBBdXN0cmFsaWEgKCRiaWxsaW9uKSBmcm9tIDIwMDQgdXAgdG8gMjAxOCBhbmQgZm9yZWNhc3QgMjQgbW9udGhzIGFoZWFkLiBXZSB2YXJ5IEssIHRoZSBudW1iZXIgb2YgRm91cmllciBzaW4gYW5kIGNvcyBwYWlycywgZnJvbSBLPTEgdG8gSz02ICh3aGljaCBpcyBlcXVpdmFsZW50IHRvIGluY2x1ZGluZyBzZWFzb25hbCBkdW1taWVzKS4NCg0KTm90aWNlIHRoYXQgYXMgIA0KSw0KICBpbmNyZWFzZXMgdGhlIEZvdXJpZXIgdGVybXMgY2FwdHVyZSBhbmQgcHJvamVjdCBhIG1vcmUg4oCcd2lnZ2x54oCdIHNlYXNvbmFsIHBhdHRlcm4gYW5kIHNpbXBsZXIgQVJJTUEgbW9kZWxzIGFyZSByZXF1aXJlZCB0byBjYXB0dXJlIG90aGVyIGR5bmFtaWNzLiBUaGUgQUlDYyB2YWx1ZSBpcyBtaW5pbWlzZWQgZm9yIEs9Niwgd2l0aCBhIHNpZ25pZmljYW50IGp1bXAgZ29pbmcgZnJvbSAgSz00IHRvIEs9NSwgaGVuY2UgdGhlIGZvcmVjYXN0cyBnZW5lcmF0ZWQgZnJvbSB0aGlzIG1vZGVsIHdvdWxkIGJlIHRoZSBvbmVzIHVzZWQuDQoNCmBgYHtyfQ0KYXVzX2NhZmUgPC0gYXVzX3JldGFpbCAlPiUNCiAgZmlsdGVyKA0KICAgIEluZHVzdHJ5ID09ICJDYWZlcywgcmVzdGF1cmFudHMgYW5kIHRha2Vhd2F5IGZvb2Qgc2VydmljZXMiLA0KICAgIHllYXIoTW9udGgpICVpbiUgMjAwNDoyMDE4DQogICkgJT4lDQogIHN1bW1hcmlzZShUdXJub3ZlciA9IHN1bShUdXJub3ZlcikpDQoNCmZpdCA8LSBhdXNfY2FmZSAlPiUNCiAgbW9kZWwoDQogICAgYEsgPSAxYCA9IEFSSU1BKGxvZyhUdXJub3ZlcikgfiBmb3VyaWVyKEsgPSAxKSArIFBEUSgwLCAwLCAwKSksDQogICAgYEsgPSAyYCA9IEFSSU1BKGxvZyhUdXJub3ZlcikgfiBmb3VyaWVyKEsgPSAyKSArIFBEUSgwLCAwLCAwKSksDQogICAgYEsgPSAzYCA9IEFSSU1BKGxvZyhUdXJub3ZlcikgfiBmb3VyaWVyKEsgPSAzKSArIFBEUSgwLCAwLCAwKSksDQogICAgYEsgPSA0YCA9IEFSSU1BKGxvZyhUdXJub3ZlcikgfiBmb3VyaWVyKEsgPSA0KSArIFBEUSgwLCAwLCAwKSksDQogICAgYEsgPSA1YCA9IEFSSU1BKGxvZyhUdXJub3ZlcikgfiBmb3VyaWVyKEsgPSA1KSArIFBEUSgwLCAwLCAwKSksDQogICAgYEsgPSA2YCA9IEFSSU1BKGxvZyhUdXJub3ZlcikgfiBmb3VyaWVyKEsgPSA2KSArIFBEUSgwLCAwLCAwKSkNCiAgKQ0KDQpmaXQgJT4lDQogIGZvcmVjYXN0KGggPSAiMiB5ZWFycyIpICU+JQ0KICBhdXRvcGxvdChhdXNfY2FmZSwgbGV2ZWwgPSA5NSkgKw0KICBmYWNldF93cmFwKHZhcnMoLm1vZGVsKSwgbmNvbCA9IDIpICsNCiAgZ3VpZGVzKGNvbG91ciA9IEZBTFNFLCBmaWxsID0gRkFMU0UsIGxldmVsID0gRkFMU0UpICsNCiAgZ2VvbV9sYWJlbCgNCiAgICBhZXMoeCA9IHllYXJtb250aCgiMjAwNyBKYW4iKSwgeSA9IDQyNTAsIGxhYmVsID0gcGFzdGUwKCJBSUNjID0gIiwgZm9ybWF0KEFJQ2MpKSksDQogICAgZGF0YSA9IGdsYW5jZShmaXQpDQogICkNCmBgYA0KIyMgTGFnZ2VkIHByZWRpY3RvcnMNCg0KU29tZXRpbWVzLCB0aGUgaW1wYWN0IG9mIGEgcHJlZGljdG9yIHRoYXQgaXMgaW5jbHVkZWQgaW4gYSByZWdyZXNzaW9uIG1vZGVsIHdpbGwgbm90IGJlIHNpbXBsZSBhbmQgaW1tZWRpYXRlLiANCg0KSW4gdGhlc2Ugc2l0dWF0aW9ucywgd2UgbmVlZCB0byBhbGxvdyBmb3IgbGFnZ2VkIGVmZmVjdHMgb2YgdGhlIHByZWRpY3Rvci4gU3VwcG9zZSB0aGF0IHdlIGhhdmUgb25seSBvbmUgcHJlZGljdG9yIGluIG91ciBtb2RlbC4gVGhlbiBhIG1vZGVsIHdoaWNoIGFsbG93cyBmb3IgbGFnZ2VkIGVmZmVjdHMgY2FuIGJlIHdyaXR0ZW4gYXMNCg0KJHlfdCA9IFxiZXRhXzAgKyBcZ2FtbWFfMHhfdCArIFxnYW1tYV8xIHhfe3QtMX0gKyBcZG90cyArIFxnYW1tYV9rIHhfe3Qta30gKyBcZXRhX3QsJA0KDQp3aGVyZSAkzrdfdCQgaXMgYW4gQVJJTUEgcHJvY2Vzcy4gVGhlIHZhbHVlIG9mIGsgY2FuIGJlIHNlbGVjdGVkIHVzaW5nIHRoZSBBSUNjLCBhbG9uZyB3aXRoIHRoZXZhbHVlcyBvZiBwIGFuZCBxIGZvciB0aGUgQVJJTUEgZXJyb3IuDQoNCiMjIyBFeGFtcGxlOiBUViBhZHZlcnRpc2luZyBhbmQgaW5zdXJhbmNlIHF1b3RhdGlvbnMNCg0KQSBVUyBpbnN1cmFuY2UgY29tcGFueSBhZHZlcnRpc2VzIG9uIG5hdGlvbmFsIHRlbGV2aXNpb24gaW4gYW4gYXR0ZW1wdCB0byBpbmNyZWFzZSB0aGUgbnVtYmVyIG9mIGluc3VyYW5jZSBxdW90YXRpb25zIHByb3ZpZGVkIChhbmQgY29uc2VxdWVudGx5IHRoZSBudW1iZXIgb2YgbmV3IHBvbGljaWVzKS4NCg0KTnVtYmVyIG9mIHF1b3RhdGlvbnMgYW5kIHRoZSBleHBlbmRpdHVyZSBvbiB0ZWxldmlzaW9uIGFkdmVydGlzaW5nIGZvciB0aGUgY29tcGFueSBlYWNoIG1vbnRoIGZyb20gSmFudWFyeSAyMDAyIHRvIEFwcmlsIDIwMDU6DQpgYGB7cn0NCmxvYWQoZmlsZSA9ICJkYXRhc2V0cy9pbnN1cmFuY2UucmRhIikNCg0KaW5zdXJhbmNlICU+JQ0KICBwaXZvdF9sb25nZXIoUXVvdGVzOlRWYWR2ZXJ0cykgJT4lDQogIGdncGxvdChhZXMoeCA9IE1vbnRoLCB5ID0gdmFsdWUpKSArDQogIGdlb21fbGluZSgpICsNCiAgZmFjZXRfZ3JpZCh2YXJzKG5hbWUpLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICBsYWJzKHkgPSAiIiwgdGl0bGUgPSAiSW5zdXJhbmNlIGFkdmVydGlzaW5nIGFuZCBxdW90YXRpb25zIikNCmBgYA0KV2Ugd2lsbCBjb25zaWRlciBpbmNsdWRpbmcgYWR2ZXJ0aXNpbmcgZXhwZW5kaXR1cmUgZm9yIHVwIHRvIGZvdXIgbW9udGhzOyB0aGF0IGlzLCB0aGUgbW9kZWwgbWF5IGluY2x1ZGUgYWR2ZXJ0aXNpbmcgZXhwZW5kaXR1cmUgaW4gdGhlIGN1cnJlbnQgbW9udGgsIGFuZCB0aGUgdGhyZWUgbW9udGhzIGJlZm9yZSB0aGF0LiBXaGVuIGNvbXBhcmluZyBtb2RlbHMsIGl0IGlzIGltcG9ydGFudCB0aGF0IHRoZXkgYWxsIHVzZSB0aGUgc2FtZSB0cmFpbmluZyBzZXQuIEluIHRoZSBmb2xsb3dpbmcgY29kZSwgd2UgZXhjbHVkZSB0aGUgZmlyc3QgdGhyZWUgbW9udGhzIGluIG9yZGVyIHRvIG1ha2UgZmFpciBjb21wYXJpc29ucy4NCmBgYHtyfQ0KZml0IDwtIGluc3VyYW5jZSAlPiUNCiAgIyBSZXN0cmljdCBkYXRhIHNvIG1vZGVscyB1c2Ugc2FtZSBmaXR0aW5nIHBlcmlvZA0KICBtdXRhdGUoUXVvdGVzID0gYyhOQSwgTkEsIE5BLCBRdW90ZXNbNDo0MF0pKSAlPiUNCiAgIyBFc3RpbWF0ZSBtb2RlbHMNCiAgbW9kZWwoDQogICAgbGFnMCA9IEFSSU1BKFF1b3RlcyB+IHBkcShkID0gMCkgKyBUVmFkdmVydHMpLA0KICAgIGxhZzEgPSBBUklNQShRdW90ZXMgfiBwZHEoZCA9IDApICsgVFZhZHZlcnRzICsgbGFnKFRWYWR2ZXJ0cykpLA0KICAgIGxhZzIgPSBBUklNQShRdW90ZXMgfiBwZHEoZCA9IDApICsgVFZhZHZlcnRzICsgbGFnKFRWYWR2ZXJ0cykgKyBsYWcoVFZhZHZlcnRzLCAyKSksDQogICAgbGFnMyA9IEFSSU1BKFF1b3RlcyB+IHBkcShkID0gMCkgKyBUVmFkdmVydHMgKyBsYWcoVFZhZHZlcnRzKSArIGxhZyhUVmFkdmVydHMsIDIpICsgbGFnKFRWYWR2ZXJ0cywgMykpDQogICkNCmBgYA0KTmV4dCB3ZSBjaG9vc2UgdGhlIG9wdGltYWwgbGFnIGxlbmd0aCBmb3IgYWR2ZXJ0aXNpbmcgYmFzZWQgb24gdGhlIEFJQ2MuDQpgYGB7cn0NCmdsYW5jZShmaXQpDQpgYGANClRoZSBiZXN0IG1vZGVsICh3aXRoIHRoZSBzbWFsbGVzdCBBSUNjIHZhbHVlKSBpcyBsYWcxIHdpdGggdHdvIHByZWRpY3RvcnM7IHRoYXQgaXMsIGl0IGluY2x1ZGVzIGFkdmVydGlzaW5nIG9ubHkgaW4gdGhlIGN1cnJlbnQgbW9udGggYW5kIHRoZSBwcmV2aW91cyBtb250aC4gU28gd2Ugbm93IHJlLWVzdGltYXRlIHRoYXQgbW9kZWwsIGJ1dCB1c2luZyBhbGwgdGhlIGF2YWlsYWJsZSBkYXRhLg0KYGBge3J9DQpmaXRfYmVzdCA8LSBpbnN1cmFuY2UgJT4lDQogIG1vZGVsKEFSSU1BKFF1b3RlcyB+IHBkcShkID0gMCkgKyBUVmFkdmVydHMgKyBsYWcoVFZhZHZlcnRzKSkpDQpyZXBvcnQoZml0X2Jlc3QpDQpgYGANClRoZSBjaG9zZW4gbW9kZWwgaGFzIEFSSU1BKDEsMCwyKSBlcnJvcnMuIFRoZSBtb2RlbCBjYW4gYmUgd3JpdHRlbiBhcw0KDQokeV90ID0gMi4xNTUgKyAxLjI1MyB4X3QgKyAwLjE0NiB4X3t0LTF9ICsgXGV0YV90JA0KDQp3aGVyZSAkeV90JCBpcyB0aGUgbnVtYmVyIG9mIHF1b3RhdGlvbnMgcHJvdmlkZWQgaW4gbW9udGggdCwgJHhfdCQgaXMgdGhlIGFkdmVydGlzaW5nIGV4cGVuZGl0dXJlIGluIG1vbnRoIHQsDQoNCiRcZXRhX3QgPSAwLjUxMiBcZXRhX3t0LTF9ICsgXHZhcmVwc2lsb25fdCArIDAuOTE3IFx2YXJlcHNpbG9uX3t0LTF9ICsgMC40NTkgXHZhcmVwc2lsb25fe3QtMn0kDQoNCldlIGNhbiBjYWxjdWxhdGUgZm9yZWNhc3RzIHVzaW5nIHRoaXMgbW9kZWwgaWYgd2UgYXNzdW1lIGZ1dHVyZSB2YWx1ZXMgZm9yIHRoZSBhZHZlcnRpc2luZyB2YXJpYWJsZS4gSWYgd2Ugc2V0IHRoZSBmdXR1cmUgbW9udGhseSBhZHZlcnRpc2luZyB0byA4IHVuaXRzLCB3ZSBnZXQgdGhlIGZvcmVjYXN0czoNCmBgYHtyfQ0KaW5zdXJhbmNlX2Z1dHVyZSA8LSBuZXdfZGF0YShpbnN1cmFuY2UsIDIwKSAlPiUNCiAgbXV0YXRlKFRWYWR2ZXJ0cyA9IDgpDQpmaXRfYmVzdCAlPiUNCiAgZm9yZWNhc3QoaW5zdXJhbmNlX2Z1dHVyZSkgJT4lDQogIGF1dG9wbG90KGluc3VyYW5jZSkgKw0KICBsYWJzKHkgPSAiUXVvdGVzIiwNCiAgICAgICB0aXRsZSA9ICJGb3JlY2FzdCBxdW90ZXMgd2l0aCBmdXR1cmUgYWR2ZXJ0aXNpbmcgc2V0IHRvIDgiKQ0KYGBgDQojIEZvcmVjYXN0aW5nIGhpZXJhcmNoaWNhbCBhbmQgZ3JvdXBlZCB0aW1lIHNlcmllcw0KDQpUaW1lIHNlcmllcyBjYW4gb2Z0ZW4gYmUgbmF0dXJhbGx5IGRpc2FnZ3JlZ2F0ZWQgYnkgdmFyaW91cyBhdHRyaWJ1dGVzIG9mIGludGVyZXN0LiANCg0KSGllcmFyY2hpY2FsIHRpbWUgc2VyaWVzIG9mdGVuIGFyaXNlIGR1ZSB0byBnZW9ncmFwaGljIGRpdmlzaW9ucy4NCg0KQWx0ZXJuYXRpdmUgYWdncmVnYXRpb24gc3RydWN0dXJlcyBhcmlzZSB3aGVuIGF0dHJpYnV0ZXMgb2YgaW50ZXJlc3QgYXJlIGNyb3NzZWQgcmF0aGVyIHRoYW4gbmVzdGVkLiBXZSByZWZlciB0byB0aGUgcmVzdWx0aW5nIHRpbWUgc2VyaWVzIG9mIGNyb3NzZWQgYXR0cmlidXRlcyBhcyDigJxncm91cGVkIHRpbWUgc2VyaWVzLuKAnQ0KDQpNb3JlIGNvbXBsZXggc3RydWN0dXJlcyBhcmlzZSB3aGVuIGF0dHJpYnV0ZXMgb2YgaW50ZXJlc3QgYXJlIGJvdGggbmVzdGVkIGFuZCBjcm9zc2VkLg0KDQpGb3JlY2FzdHMgYXJlIG9mdGVuIHJlcXVpcmVkIGZvciBhbGwgZGlzYWdncmVnYXRlIGFuZCBhZ2dyZWdhdGUgc2VyaWVzLCBhbmQgaXQgaXMgbmF0dXJhbCB0byB3YW50IHRoZSBmb3JlY2FzdHMgdG8gYWRkIHVwIGluIHRoZSBzYW1lIHdheSBhcyB0aGUgZGF0YS4NCg0KIyMgSGllcmFyY2hpY2FsIGFuZCBncm91cGVkIHRpbWUgc2VyaWVzDQoNCiMjIyBIaWVyYXJjaGljYWwgdGltZSBzZXJpZXMNCg0KaHR0cHM6Ly9vdGV4dHMuY29tL2ZwcDMvaHRzLmh0bWwNCg0KRm9yIGFueSB0aW1lIHQsIHRoZSBvYnNlcnZhdGlvbnMgYXQgdGhlIGJvdHRvbSBsZXZlbCBvZiB0aGUgaGllcmFyY2h5IHdpbGwgc3VtIHRvIHRoZSBvYnNlcnZhdGlvbnMgb2YgdGhlIHNlcmllcyBhYm92ZS4gRm9yIGV4YW1wbGUsDQogDQpcYmVnaW57ZXF1YXRpb259DQogIHlfe3R9PVx5e0FBfXt0fStceXtBQn17dH0rXHl7QUN9e3R9K1x5e0JBfXt0fStceXtCQn17dH0sDQogIFx0YWd7MTEuMX0NClxlbmR7ZXF1YXRpb259DQoNClxiZWdpbntlcXVhdGlvbn0NCiAgXHl7QX17dH09XHl7QUF9e3R9K1x5e0FCfXt0fStceXtBQ317dH1ccXF1YWQgXHRleHR7YW5kfSBccXF1YWQgIFx5e0J9e3R9PVx5e0JBfXt0fStceXtCQn17dH0uDQogIFx0YWd7MTEuMn0NClxlbmR7ZXF1YXRpb259DQoNCiMjIyBFeGFtcGxlOiBBdXN0cmFsaWFuIHRvdXJpc20gaGllcmFyY2h5DQoNCkF1c3RyYWxpYSBpcyBkaXZpZGVkIGludG8gc2l4IHN0YXRlcyBhbmQgdHdvIHRlcnJpdG9yaWVzLCB3aXRoIGVhY2ggb25lIGhhdmluZyBpdHMgb3duIGdvdmVybm1lbnQgYW5kIHNvbWUgZWNvbm9taWMgYW5kIGFkbWluaXN0cmF0aXZlIGF1dG9ub215LiBFYWNoIG9mIHRoZXNlIHN0YXRlcyBjYW4gYmUgZnVydGhlciBzdWJkaXZpZGVkIGludG8gcmVnaW9ucy4NCg0KVGhlIHRvdXJpc20gdHNpYmJsZSBjb250YWlucyBkYXRhIG9uIHF1YXJ0ZXJseSBkb21lc3RpYyB0b3VyaXNtIGRlbWFuZCwgbWVhc3VyZWQgYXMgdGhlIG51bWJlciBvZiBvdmVybmlnaHQgdHJpcHMgQXVzdHJhbGlhbnMgc3BlbmQgYXdheSBmcm9tIGhvbWUuIFRoZSBrZXkgdmFyaWFibGVzIFN0YXRlIGFuZCBSZWdpb24gZGVub3RlIHRoZSBnZW9ncmFwaGljYWwgYXJlYXMsIHdoaWxlIGEgZnVydGhlciBrZXkgUHVycG9zZSBkZXNjcmliZXMgdGhlIHB1cnBvc2Ugb2YgdHJhdmVsLg0KYGBge3J9DQojcmVjb2RlIFN0YXRlIHRvIHVzZSBhYmJyZXZpYXRpb25zDQp0b3VyaXNtIDwtIHRzaWJibGU6OnRvdXJpc20gJT4lDQogIG11dGF0ZShTdGF0ZSA9IHJlY29kZShTdGF0ZSwNCiAgICBgTmV3IFNvdXRoIFdhbGVzYCA9ICJOU1ciLA0KICAgIGBOb3J0aGVybiBUZXJyaXRvcnlgID0gIk5UIiwNCiAgICBgUXVlZW5zbGFuZGAgPSAiUUxEIiwNCiAgICBgU291dGggQXVzdHJhbGlhYCA9ICJTQSIsDQogICAgYFRhc21hbmlhYCA9ICJUQVMiLA0KICAgIGBWaWN0b3JpYWAgPSAiVklDIiwNCiAgICBgV2VzdGVybiBBdXN0cmFsaWFgID0gIldBIg0KICApKQ0KYGBgDQoNClVzaW5nIHRoZSBhZ2dyZWdhdGVfa2V5KCkgZnVuY3Rpb24sIHdlIGNhbiBjcmVhdGUgdGhlIGhpZXJhcmNoaWNhbCB0aW1lIHNlcmllcyB3aXRoIG92ZXJuaWdodCB0cmlwcyBpbiByZWdpb25zIGF0IHRoZSBib3R0b20gbGV2ZWwgb2YgdGhlIGhpZXJhcmNoeSwgYWdncmVnYXRlZCB0byBzdGF0ZXMsIHdoaWNoIGFyZSBhZ2dyZWdhdGVkIHRvIHRoZSBuYXRpb25hbCB0b3RhbC4gQSBoaWVyYXJjaGljYWwgdGltZSBzZXJpZXMgY29ycmVzcG9uZGluZyB0byB0aGUgbmVzdGVkIHN0cnVjdHVyZSBpcyBjcmVhdGVkIHVzaW5nIGEgcGFyZW50L2NoaWxkIHNwZWNpZmljYXRpb24uDQpgYGB7cn0NCnRvdXJpc21faHRzIDwtIHRvdXJpc20gJT4lDQogIGFnZ3JlZ2F0ZV9rZXkoU3RhdGUgLyBSZWdpb24sIFRyaXBzID0gc3VtKFRyaXBzKSkNCnRvdXJpc21faHRzDQpgYGANClRoZSBuZXcgdHNpYmJsZSBub3cgaGFzIHNvbWUgYWRkaXRpb25hbCByb3dzIGNvcnJlc3BvbmRpbmcgdG8gc3RhdGUgYW5kIG5hdGlvbmFsIGFnZ3JlZ2F0aW9ucyBmb3IgZWFjaCBxdWFydGVyLiBGaWd1cmUgMTEuMyBzaG93cyB0aGUgYWdncmVnYXRlIHRvdGFsIG92ZXJuaWdodCB0cmlwcyBmb3IgdGhlIHdob2xlIG9mIEF1c3RyYWxpYSBhcyB3ZWxsIGFzIHRoZSBzdGF0ZXMsIHJldmVhbGluZyBkaXZlcnNlIGFuZCByaWNoIGR5bmFtaWNzLiBGb3IgZXhhbXBsZSwgdGhlcmUgaXMgbm90aWNlYWJsZSBuYXRpb25hbCBncm93dGggc2luY2UgMjAxMCBhbmQgZm9yIHNvbWUgc3RhdGVzIHN1Y2ggYXMgdGhlIEFDVCwgTmV3IFNvdXRoIFdhbGVzLCBRdWVlbnNsYW5kLCBTb3V0aCBBdXN0cmFsaWEsIGFuZCBWaWN0b3JpYS4gVGhlcmUgc2VlbXMgdG8gYmUgYSBzaWduaWZpY2FudCBqdW1wIGZvciBXZXN0ZXJuIEF1c3RyYWxpYSBpbiAyMDE0Lg0KYGBge3J9DQp0b3VyaXNtX2h0cyAlPiUNCiAgZmlsdGVyKGlzX2FnZ3JlZ2F0ZWQoUmVnaW9uKSkgJT4lDQogIGF1dG9wbG90KFRyaXBzKSArDQogIGxhYnMoeSA9ICJUcmlwcyAoJzAwMCkiLA0KICAgICAgIHRpdGxlID0gIkF1c3RyYWxpYW4gdG91cmlzbTogbmF0aW9uYWwgdG90YWwgYW5kIHN0YXRlcyIpICsNCiAgZmFjZXRfd3JhcCh2YXJzKFN0YXRlKSwgc2NhbGVzID0gImZyZWVfeSIsIG5jb2wgPSAzKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KVGhlIHNlYXNvbmFsIHBhdHRlcm4gb2YgdGhlIG5vcnRoZXJuIHN0YXRlcywgc3VjaCBhcyBRdWVlbnNsYW5kIGFuZCB0aGUgTm9ydGhlcm4gVGVycml0b3J5LCBsZWFkcyB0byBwZWFrIHZpc2l0cyBpbiB3aW50ZXIgKGNvcnJlc3BvbmRpbmcgdG8gUTMpIGR1ZSB0byB0aGUgdHJvcGljYWwgY2xpbWF0ZSBhbmQgcmFpbnkgc3VtbWVyIG1vbnRocy4gSW4gY29udHJhc3QsIHRoZSBzb3V0aGVybiBzdGF0ZXMgdGVuZCB0byBwZWFrIGluIHN1bW1lciAoY29ycmVzcG9uZGluZyB0byBRMSkuDQoNCiMjIyBHcm91cGVkIHRpbWUgc2VyaWVzDQoNCldpdGggZ3JvdXBlZCB0aW1lIHNlcmllcywgdGhlIGRhdGEgc3RydWN0dXJlIGRvZXMgbm90IG5hdHVyYWxseSBkaXNhZ2dyZWdhdGUgaW4gYSB1bmlxdWUgaGllcmFyY2hpY2FsIG1hbm5lci4NCg0KVGhpcyBleGFtcGxlIHNob3dzIHRoYXQgdGhlcmUgYXJlIGFsdGVybmF0aXZlIGFnZ3JlZ2F0aW9uIHBhdGhzIGZvciBncm91cGVkIHN0cnVjdHVyZXMuIEZvciBhbnkgdGltZSAgdCwgYXMgd2l0aCB0aGUgaGllcmFyY2hpY2FsIHN0cnVjdHVyZSwNCiANClxiZWdpbntlcXVhdGlvbip9DQp5X3t0fT15X3tBWH17dH0reV97QVksdH0reV97QlgsdH0reV97QlksdH0uDQpcZW5ke2VxdWF0aW9uKn0NCg0KSG93ZXZlciwgZm9yIHRoZSBmaXJzdCBsZXZlbCBvZiB0aGUgZ3JvdXBlZCBzdHJ1Y3R1cmUsDQoNClxiZWdpbntlcXVhdGlvbn0NClx5e0F9e3R9PVx5e0FYfXt0fStceXtBWX17dH1ccXVhZCBccXVhZCBceXtCfXt0fT1ceXtCWH17dH0rXHl7Qll9e3R9DQpcdGFnezExLjN9DQpcZW5ke2VxdWF0aW9ufQ0KDQpidXQgYWxzbw0KDQpcYmVnaW57ZXF1YXRpb259DQpceXtYfXt0fT1ceXtBWH17dH0rXHl7Qlh9e3R9XHF1YWQgXHF1YWQgXHl7WX17dH09XHl7QVl9e3R9K1x5e0JZfXt0fQ0KXHRhZ3sxMS40fS4NClxlbmR7ZXF1YXRpb259DQoNCkdyb3VwZWQgdGltZSBzZXJpZXMgY2FuIHNvbWV0aW1lcyBiZSB0aG91Z2h0IG9mIGFzIGhpZXJhcmNoaWNhbCB0aW1lIHNlcmllcyB0aGF0IGRvIG5vdCBpbXBvc2UgYSB1bmlxdWUgaGllcmFyY2hpY2FsIHN0cnVjdHVyZSwgaW4gdGhlIHNlbnNlIHRoYXQgdGhlIG9yZGVyIGJ5IHdoaWNoIHRoZSBzZXJpZXMgY2FuIGJlIGdyb3VwZWQgaXMgbm90IHVuaXF1ZS4NCg0KIyMjIEV4YW1wbGU6IEF1c3RyYWxpYW4gcHJpc29uIHBvcHVsYXRpb24NCg0KSW4gdGhpcyBleGFtcGxlIHdlIGNvbnNpZGVyIHRoZSBBdXN0cmFsaWEgcHJpc29uIHBvcHVsYXRpb24uIA0KYGBge3J9DQpwcmlzb24gPC0gcmVhZHI6OnJlYWRfY3N2KCJodHRwczovL09UZXh0cy5jb20vZnBwMy9leHRyYWZpbGVzL3ByaXNvbl9wb3B1bGF0aW9uLmNzdiIpICU+JQ0KICBtdXRhdGUoUXVhcnRlciA9IHllYXJxdWFydGVyKERhdGUpKSAlPiUNCiAgc2VsZWN0KC1EYXRlKSAgJT4lDQogIGFzX3RzaWJibGUoa2V5ID0gYyhHZW5kZXIsIExlZ2FsLCBTdGF0ZSwgSW5kaWdlbm91cyksIGluZGV4ID0gUXVhcnRlcikgJT4lDQogIHJlbG9jYXRlKFF1YXJ0ZXIpDQpgYGANCg0KV2UgY3JlYXRlIGEgZ3JvdXBlZCB0aW1lIHNlcmllcyB1c2luZyBhZ2dyZWdhdGVfa2V5KCkgd2l0aCBhdHRyaWJ1dGVzIG9yIGdyb3VwaW5ncyBvZiBpbnRlcmVzdCBub3cgYmVpbmcgY3Jvc3NlZCB1c2luZyB0aGUgc3ludGF4IGF0dHJpYnV0ZTEqYXR0cmlidXRlMiAoaW4gY29udHJhc3QgdG8gdGhlIHBhcmVudC9jaGlsZCBzeW50YXggdXNlZCBmb3IgaGllcmFyY2hpY2FsIHRpbWUgc2VyaWVzKS4gVGhlIGZvbGxvd2luZyBjb2RlIGJ1aWxkcyBhIGdyb3VwZWQgdHNpYmJsZSBmb3IgdGhlIHByaXNvbiBkYXRhIHdpdGggY3Jvc3NlZCBhdHRyaWJ1dGVzOiBnZW5kZXIsIGxlZ2FsIHN0YXR1cyBhbmQgc3RhdGUuDQpgYGB7cn0NCnByaXNvbl9ndHMgPC0gcHJpc29uICU+JQ0KICBhZ2dyZWdhdGVfa2V5KEdlbmRlciAqIExlZ2FsICogU3RhdGUsIENvdW50ID0gc3VtKENvdW50KS8xZTMpDQpgYGANClVzaW5nIGlzX2FnZ3JlZ2F0ZWQoKSB3aXRoaW4gZmlsdGVyKCkgaXMgaGVscGZ1bCBmb3IgZXhwbG9yaW5nIG9yIHBsb3R0aW5nIHRoZSBtYWluIGdyb3VwcyBzaG93biBpbiB0aGUgYm90dG9tIHBhbmVscy4gRm9yIGV4YW1wbGUsIHRoZSBmb2xsb3dpbmcgY29kZSBwbG90cyB0aGUgdG90YWwgbnVtYmVycyBvZiBmZW1hbGUgYW5kIG1hbGUgcHJpc29uZXJzIGFjcm9zcyBBdXN0cmFsaWEuDQpgYGB7cn0NCnByaXNvbl9ndHMgJT4lDQogIGZpbHRlcighaXNfYWdncmVnYXRlZChHZW5kZXIpLCBpc19hZ2dyZWdhdGVkKExlZ2FsKSwgaXNfYWdncmVnYXRlZChTdGF0ZSkpICU+JQ0KICBhdXRvcGxvdChDb3VudCkgKw0KICBsYWJzKHkgPSAiTnVtYmVyIG9mIHByaXNvbmVycyAoJzAwMCkiKQ0KYGBgDQpQbG90cyBvZiBvdGhlciBncm91cCBjb21iaW5hdGlvbnMgY2FuIGJlIG9idGFpbmVkOg0KYGBge3J9DQpwcmlzb25fZ3RzICU+JQ0KICBmaWx0ZXIoIWlzX2FnZ3JlZ2F0ZWQoR2VuZGVyKSwgIWlzX2FnZ3JlZ2F0ZWQoTGVnYWwpLCAhaXNfYWdncmVnYXRlZChTdGF0ZSkpICU+JQ0KICBtdXRhdGUoR2VuZGVyID0gYXMuY2hhcmFjdGVyKEdlbmRlcikpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBRdWFydGVyLCB5ID0gQ291bnQsIGdyb3VwID0gR2VuZGVyLCBjb2xvdXI9R2VuZGVyKSkgKw0KICBzdGF0X3N1bW1hcnkoZnVuID0gc3VtLCBnZW9tID0gImxpbmUiKSArDQogIGxhYnModGl0bGUgPSAiUHJpc29uIHBvcHVsYXRpb24gYnkgc3RhdGUgYW5kIGdlbmRlciIsDQogICAgICAgeSA9ICJOdW1iZXIgb2YgcHJpc29uZXJzICgnMDAwKSIpICsNCiAgZmFjZXRfd3JhcCh+IGFzLmNoYXJhY3RlcihTdGF0ZSksIG5yb3cgPSAxLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQ0KYGBgDQojIyMgTWl4ZWQgaGllcmFyY2hpY2FsIGFuZCBncm91cGVkIHN0cnVjdHVyZQ0KDQpPZnRlbiBkaXNhZ2dyZWdhdGluZyBmYWN0b3JzIGFyZSBib3RoIG5lc3RlZCBhbmQgY3Jvc3NlZC4gRm9yIGV4YW1wbGUsIHRoZSBBdXN0cmFsaWFuIHRvdXJpc20gZGF0YSBjYW4gYWxzbyBiZSBkaXNhZ2dyZWdhdGVkIGJ5IHRoZSBmb3VyIHB1cnBvc2VzIG9mIHRyYXZlbDogaG9saWRheSwgYnVzaW5lc3MsIHZpc2l0aW5nIGZyaWVuZHMgYW5kIHJlbGF0aXZlcywgYW5kIG90aGVyLiBUaGlzIGdyb3VwaW5nIHZhcmlhYmxlIGRvZXMgbm90IG5lc3Qgd2l0aGluIGFueSBvZiB0aGUgZ2VvZ3JhcGhpY2FsIHZhcmlhYmxlcy4gSW4gZmFjdCwgd2UgY291bGQgY29uc2lkZXIgb3Zlcm5pZ2h0IHRyaXBzIHNwbGl0IGJ5IHB1cnBvc2Ugb2YgdHJhdmVsIGZvciB0aGUgd2hvbGUgb2YgQXVzdHJhbGlhLCBhbmQgZm9yIGVhY2ggc3RhdGUsIGFuZCBmb3IgZWFjaCByZWdpb24uIFdlIGRlc2NyaWJlIHN1Y2ggYSBzdHJ1Y3R1cmUgYXMgYSDigJxuZXN0ZWTigJ0gZ2VvZ3JhcGhpYyBoaWVyYXJjaHkg4oCcY3Jvc3NlZOKAnSB3aXRoIHRoZSBwdXJwb3NlIG9mIHRyYXZlbC4gVXNpbmcgYWdncmVnYXRlX2tleSB0aGlzIGNhbiBiZSBzcGVjaWZpZWQgYnkgc2ltcGx5IGNvbWJpbmluZyB0aGUgZmFjdG9ycyBhcyBmb2xsb3dzLg0KYGBge3J9DQp0b3VyaXNtX2Z1bGwgPC0gdG91cmlzbSAlPiUNCiAgYWdncmVnYXRlX2tleSgoU3RhdGUgLyBSZWdpb24pICogUHVycG9zZSwgVHJpcHMgPSBzdW0oVHJpcHMpKQ0KYGBgDQpUaGUgdG91cmlzbV9mdWxsIHRzaWJibGUgY29udGFpbnMgNDI1IHNlcmllcywgaW5jbHVkaW5nIHRoZSA4NSBzZXJpZXMgZnJvbSB0aGUgaGllcmFyY2hpY2FsIHN0cnVjdHVyZSwgYXMgd2VsbCBhcyBhbm90aGVyIDM0MCBzZXJpZXMgb2J0YWluZWQgd2hlbiBlYWNoIHNlcmllcyBvZiB0aGUgaGllcmFyY2hpY2FsIHN0cnVjdHVyZSBpcyBjcm9zc2VkIHdpdGggdGhlIHB1cnBvc2Ugb2YgdHJhdmVsLg0KDQojIyAgU2luZ2xlIGxldmVsIGFwcHJvYWNoZXMNCg0KVHJhZGl0aW9uYWxseSwgZm9yZWNhc3RzIG9mIGhpZXJhcmNoaWNhbCBvciBncm91cGVkIHRpbWUgc2VyaWVzIGludm9sdmVkIHNlbGVjdGluZyBvbmUgbGV2ZWwgb2YgYWdncmVnYXRpb24gYW5kIGdlbmVyYXRpbmcgZm9yZWNhc3RzIGZvciB0aGF0IGxldmVsLiBUaGVzZSBhcmUgdGhlbiBlaXRoZXIgYWdncmVnYXRlZCBmb3IgaGlnaGVyIGxldmVscywgb3IgZGlzYWdncmVnYXRlZCBmb3IgbG93ZXIgbGV2ZWxzLCB0byBvYnRhaW4gYSBzZXQgb2YgY29oZXJlbnQgZm9yZWNhc3RzIGZvciB0aGUgcmVzdCBvZiB0aGUgc3RydWN0dXJlLg0KDQojIyMgVGhlIGJvdHRvbS11cCBhcHByb2FjaA0KDQpBIHNpbXBsZSBtZXRob2QgZm9yIGdlbmVyYXRpbmcgY29oZXJlbnQgZm9yZWNhc3RzIGlzIHRoZSDigJxib3R0b20tdXDigJ0gYXBwcm9hY2guIFRoaXMgYXBwcm9hY2ggaW52b2x2ZXMgZmlyc3QgZ2VuZXJhdGluZyBmb3JlY2FzdHMgZm9yIGVhY2ggc2VyaWVzIGF0IHRoZSBib3R0b20gbGV2ZWwsIGFuZCB0aGVuIHN1bW1pbmcgdGhlc2UgdG8gcHJvZHVjZSBmb3JlY2FzdHMgZm9yIGFsbCB0aGUgc2VyaWVzIGluIHRoZSBzdHJ1Y3R1cmUuDQoNCkFuIGFkdmFudGFnZSBvZiB0aGlzIGFwcHJvYWNoIGlzIHRoYXQgd2UgYXJlIGZvcmVjYXN0aW5nIGF0IHRoZSBib3R0b20gbGV2ZWwgb2YgYSBzdHJ1Y3R1cmUsIGFuZCB0aGVyZWZvcmUgbm8gaW5mb3JtYXRpb24gaXMgbG9zdCBkdWUgdG8gYWdncmVnYXRpb24uIE9uIHRoZSBvdGhlciBoYW5kLCBib3R0b20tbGV2ZWwgZGF0YSBjYW4gYmUgcXVpdGUgbm9pc3kgYW5kIG1vcmUgY2hhbGxlbmdpbmcgdG8gbW9kZWwgYW5kIGZvcmVjYXN0Lg0KDQojIyMjIEV4YW1wbGU6IGdlbmVyYXRpbmcgYm90dG9tLXVwIGZvcmVjYXN0cw0KDQpTdXBwb3NlIHdlIHdhbnQgbmF0aW9uYWwgYW5kIHN0YXRlIGZvcmVjYXN0cyBmb3IgdGhlIEF1c3RyYWxpYW4gdG91cmlzbSBkYXRhLCBidXQgd2UgYXJlbuKAmXQgaW50ZXJlc3RlZCBpbiBkaXNhZ2dyZWdhdGlvbnMgdXNpbmcgcmVnaW9ucyBvciB0aGUgcHVycG9zZSBvZiB0cmF2ZWwuIFNvIHdlIGZpcnN0IGNyZWF0ZSBhIHNpbXBsZSB0c2liYmxlIG9iamVjdCBjb250YWluaW5nIG9ubHkgc3RhdGUgYW5kIG5hdGlvbmFsIHRyaXAgdG90YWxzIGZvciBlYWNoIHF1YXJ0ZXIuDQpgYGB7cn0NCnRvdXJpc21fc3RhdGVzIDwtIHRvdXJpc20gJT4lDQogIGFnZ3JlZ2F0ZV9rZXkoU3RhdGUsIFRyaXBzID0gc3VtKFRyaXBzKSkNCmBgYA0KV2UgY291bGQgZ2VuZXJhdGUgdGhlIGJvdHRvbS1sZXZlbCBzdGF0ZSBmb3JlY2FzdHMgZmlyc3QsIGFuZCB0aGVuIHN1bSB0aGVtIHRvIG9idGFpbiB0aGUgbmF0aW9uYWwgZm9yZWNhc3RzIGFzIGZvbGxvd3MuDQpgYGB7cn0NCmZjYXN0c19zdGF0ZSA8LSB0b3VyaXNtX3N0YXRlcyAlPiUNCiAgZmlsdGVyKCFpc19hZ2dyZWdhdGVkKFN0YXRlKSkgJT4lDQogIG1vZGVsKGV0cyA9IEVUUyhUcmlwcykpICU+JQ0KICBmb3JlY2FzdCgpDQojIFN1bSBib3R0b20tbGV2ZWwgZm9yZWNhc3RzIHRvIGdldCB0b3AtbGV2ZWwgZm9yZWNhc3RzDQpmY2FzdHNfbmF0aW9uYWwgPC0gZmNhc3RzX3N0YXRlICU+JQ0KICBzdW1tYXJpc2UodmFsdWUgPSBzdW0oVHJpcHMpLCAubWVhbiA9IG1lYW4odmFsdWUpKQ0KYGBgDQpIb3dldmVyLCB3ZSB3YW50IGEgbW9yZSBnZW5lcmFsIGFwcHJvYWNoIHRoYXQgd2lsbCB3b3JrIHdpdGggYWxsIHRoZSBmb3JlY2FzdGluZyBtZXRob2RzIGRpc2N1c3NlZCBpbiB0aGlzIGNoYXB0ZXIuIFNvIHdlIHdpbGwgdXNlIHRoZSByZWNvbmNpbGUoKSBmdW5jdGlvbiB0byBzcGVjaWZ5IGhvdyB3ZSB3YW50IHRvIGNvbXB1dGUgY29oZXJlbnQgZm9yZWNhc3RzLg0KYGBge3J9DQp0b3VyaXNtX3N0YXRlcyAlPiUNCiAgbW9kZWwoZXRzID0gRVRTKFRyaXBzKSkgJT4lDQogIHJlY29uY2lsZShidSA9IGJvdHRvbV91cChldHMpKSAlPiUNCiAgZm9yZWNhc3QoKQ0KYGBgDQpUaGUgcmVjb25jaWxlKCkgc3RlcCBoYXMgY3JlYXRlZCBhIG5ldyDigJxtb2RlbOKAnSB0byBwcm9kdWNlIGJvdHRvbS11cCBmb3JlY2FzdHMuIFRoZSBmYWJsZSBvYmplY3QgY29udGFpbnMgdGhlIGV0cyBmb3JlY2FzdHMgYXMgd2VsbCBhcyB0aGUgY29oZXJlbnQgYnUgZm9yZWNhc3RzLCBmb3IgdGhlIDggc3RhdGVzIGFuZCB0aGUgbmF0aW9uYWwgYWdncmVnYXRlLiBBdCB0aGUgc3RhdGUgbGV2ZWwsIHRoZXNlIGZvcmVjYXN0cyBhcmUgaWRlbnRpY2FsLCBidXQgdGhlIG5hdGlvbmFsIGV0cyBmb3JlY2FzdHMgd2lsbCBiZSBkaWZmZXJlbnQgZnJvbSB0aGUgbmF0aW9uYWwgYnUgZm9yZWNhc3RzLg0KDQpGb3IgYm90dG9tLXVwIGZvcmVjYXN0aW5nLCB0aGlzIGlzIHJhdGhlciBpbmVmZmljaWVudCBhcyB3ZSBhcmUgbm90IGludGVyZXN0ZWQgaW4gdGhlIEVUUyBtb2RlbCBmb3IgdGhlIG5hdGlvbmFsIHRvdGFsLCBhbmQgdGhlIHJlc3VsdGluZyBmYWJsZSBjb250YWlucyBhIGxvdCBvZiBkdXBsaWNhdGVzLg0KDQpUaGUgYWJvdmUgY29kZSBpbGx1c3RyYXRlcyB0aGUgZ2VuZXJhbCB3b3JrZmxvdyBmb3IgaGllcmFyY2hpY2FsIGFuZCBncm91cGVkIGZvcmVjYXN0cy4gV2UgdXNlIHRoZSBmb2xsb3dpbmcgcGlwZWxpbmUgb2YgZnVuY3Rpb25zLg0KDQogICAgZGF0YSAlPiUgYWdncmVnYXRlX2tleSgpICU+JSBtb2RlbCgpICU+JSByZWNvbmNpbGUoKSAlPiUgZm9yZWNhc3QoKQ0KDQoxLiBCZWdpbiB3aXRoIGEgdHNpYmJsZSBvYmplY3QgKGhlcmUgbGFiZWxsZWQgZGF0YSkgY29udGFpbmluZyB0aGUgaW5kaXZpZHVhbCBib3R0b20tbGV2ZWwgc2VyaWVzLg0KMi4gRGVmaW5lIGluIGFnZ3JlZ2F0ZV9rZXkoKSB0aGUgYWdncmVnYXRpb24gc3RydWN0dXJlIGFuZCBidWlsZCBhIHRzaWJibGUgb2JqZWN0IHRoYXQgYWxzbyBjb250YWlucyB0aGUgYWdncmVnYXRlIHNlcmllcy4NCjMuIElkZW50aWZ5IGEgbW9kZWwoKSBmb3IgZWFjaCBzZXJpZXMsIGF0IGFsbCBsZXZlbHMgb2YgYWdncmVnYXRpb24uDQo0LiBTcGVjaWZ5IGluIHJlY29uY2lsZSgpIGhvdyB0aGUgY29oZXJlbnQgZm9yZWNhc3RzIGFyZSB0byBiZSBnZW5lcmF0ZWQgZnJvbSB0aGUgc2VsZWN0ZWQgbW9kZWxzLg0KNS4gVXNlIHRoZSBmb3JlY2FzdCgpIGZ1bmN0aW9uIHRvIGdlbmVyYXRlIGZvcmVjYXN0cyBmb3IgdGhlIHdob2xlIGFnZ3JlZ2F0aW9uIHN0cnVjdHVyZS4NCg0KIyMjIFRvcC1kb3duIGFwcHJvYWNoZXMNCg0KVG9wLWRvd24gYXBwcm9hY2hlcyBpbnZvbHZlIGZpcnN0IGdlbmVyYXRpbmcgZm9yZWNhc3RzIGZvciB0aGUgVG90YWwgc2VyaWVzICAkeV90JCwgYW5kIHRoZW4gZGlzYWdncmVnYXRpbmcgdGhlc2UgZG93biB0aGUgaGllcmFyY2h5LiBUaGlzIG9ubHkgd29ya3Mgd2l0aCBzdHJpY3RseSBoaWVyYXJjaGljYWwgZGF0YSwgbm90IHdpdGggZ3JvdXBlZCBvciBtaXhlZCBhZ2dyZWdhdGlvbiBzdHJ1Y3R1cmVzLg0KDQpUb3AtZG93biBmb3JlY2FzdHMgY2FuIGJlIGdlbmVyYXRlZCB1c2luZyB0b3BfZG93bigpIHdpdGhpbiB0aGUgcmVjb25jaWxlKCkgZnVuY3Rpb24uDQoNCiMjIyBBdmVyYWdlIGhpc3RvcmljYWwgcHJvcG9ydGlvbnMNCg0KJHBfaj1cZnJhY3sxfXtUfVxzdW1fe3Q9MX1ee1R9XGZyYWN7eV97aix0fX17e3lfdH19JA0KDQpUaGlzIGFwcHJvYWNoIGlzIGltcGxlbWVudGVkIGluIHRoZSB0b3BfZG93bigpIGZ1bmN0aW9uIGJ5IHNldHRpbmcgbWV0aG9kID0gImF2ZXJhZ2VfcHJvcG9ydGlvbnMiLg0KDQojIyMgUHJvcG9ydGlvbnMgb2YgdGhlIGhpc3RvcmljYWwgYXZlcmFnZXMNCg0KJHBfaj17XHN1bV97dD0xfV57VH1cZnJhY3t5X3tqLHR9fXtUfX1cQmlnL3tcc3VtX3t0PTF9XntUfVxmcmFje3lfdH17VH19JA0KDQpUaGlzIGFwcHJvYWNoIGlzIGltcGxlbWVudGVkIGluIHRoZSB0b3BfZG93bigpIGZ1bmN0aW9uIGJ5IHNldHRpbmcgbWV0aG9kID0gInByb3BvcnRpb25fYXZlcmFnZXMiLg0KDQpJbiBnZW5lcmFsLCB0aGVzZSBhcHByb2FjaGVzIHNlZW0gdG8gcHJvZHVjZSBxdWl0ZSByZWxpYWJsZSBmb3JlY2FzdHMgZm9yIHRoZSBhZ2dyZWdhdGUgbGV2ZWxzIGFuZCB0aGV5IGFyZSB1c2VmdWwgd2l0aCBsb3cgY291bnQgZGF0YS4gT24gdGhlIG90aGVyIGhhbmQsIG9uZSBkaXNhZHZhbnRhZ2UgaXMgdGhlIGxvc3Mgb2YgaW5mb3JtYXRpb24gZHVlIHRvIGFnZ3JlZ2F0aW9uLiBVc2luZyBzdWNoIHRvcC1kb3duIGFwcHJvYWNoZXMsIHdlIGFyZSB1bmFibGUgdG8gY2FwdHVyZSBhbmQgdGFrZSBhZHZhbnRhZ2Ugb2YgaW5kaXZpZHVhbCBzZXJpZXMgY2hhcmFjdGVyaXN0aWNzIHN1Y2ggYXMgdGltZSBkeW5hbWljcywgc3BlY2lhbCBldmVudHMsIGRpZmZlcmVudCBzZWFzb25hbCBwYXR0ZXJucywgZXRjLg0KDQojIyMgRm9yZWNhc3QgcHJvcG9ydGlvbnMNCg0KQmVjYXVzZSBoaXN0b3JpY2FsIHByb3BvcnRpb25zIHVzZWQgZm9yIGRpc2FnZ3JlZ2F0aW9uIGRvIG5vdCB0YWtlIGFjY291bnQgb2YgaG93IHRob3NlIHByb3BvcnRpb25zIG1heSBjaGFuZ2Ugb3ZlciB0aW1lLCB0b3AtZG93biBhcHByb2FjaGVzIGJhc2VkIG9uIGhpc3RvcmljYWwgcHJvcG9ydGlvbnMgdGVuZCB0byBwcm9kdWNlIGxlc3MgYWNjdXJhdGUgZm9yZWNhc3RzIGF0IGxvd2VyIGxldmVscyBvZiB0aGUgaGllcmFyY2h5IHRoYW4gYm90dG9tLXVwIGFwcHJvYWNoZXMuIFRvIGFkZHJlc3MgdGhpcyBpc3N1ZSwgcHJvcG9ydGlvbnMgYmFzZWQgb24gZm9yZWNhc3RzIHJhdGhlciB0aGFuIGhpc3RvcmljYWwgZGF0YSBjYW4gYmUgdXNlZC4NCg0KJHBfaj1ccHJvZF57Sy0xfV97XGVsbD0wfVxmcmFje1xoYXR7eX1fe2osaH1eeyhcZWxsKX19e1xoYXR7U31fe2osaH1eeyhcZWxsKzEpfX0kDQoNClRoaXMgYXBwcm9hY2ggaXMgaW1wbGVtZW50ZWQgaW4gdGhlIHRvcF9kb3duKCkgZnVuY3Rpb24gYnkgc2V0dGluZyBtZXRob2QgPSAiZm9yZWNhc3RfcHJvcG9ydGlvbnMiLiBCZWNhdXNlIHRoaXMgYXBwcm9hY2ggdGVuZHMgdG8gd29yayBiZXR0ZXIgdGhhbiBvdGhlciB0b3AtZG93biBtZXRob2RzLCBpdCBpcyB0aGUgZGVmYXVsdCBjaG9pY2UgaW4gdGhlIHRvcF9kb3duKCkgZnVuY3Rpb24gd2hlbiBubyBtZXRob2QgYXJndW1lbnQgaXMgc3BlY2lmaWVkLg0KDQpPbmUgZGlzYWR2YW50YWdlIG9mIGFsbCB0b3AtZG93biBhcHByb2FjaGVzLCBpcyB0aGF0IHRoZXkgZG8gbm90IHByb2R1Y2UgdW5iaWFzZWQgY29oZXJlbnQgZm9yZWNhc3RzIGV2ZW4gaWYgdGhlIGJhc2UgZm9yZWNhc3RzIGFyZSB1bmJpYXNlZC4NCg0KIyMjIE1pZGRsZS1vdXQgYXBwcm9hY2gNCg0KVGhlIG1pZGRsZS1vdXQgYXBwcm9hY2ggY29tYmluZXMgYm90dG9tLXVwIGFuZCB0b3AtZG93biBhcHByb2FjaGVzLiBBZ2FpbiwgaXQgY2FuIG9ubHkgYmUgdXNlZCBmb3Igc3RyaWN0bHkgaGllcmFyY2hpY2FsIGFnZ3JlZ2F0aW9uIHN0cnVjdHVyZXMuDQoNClRoaXMgYXBwcm9hY2ggaXMgaW1wbGVtZW50ZWQgaW4gdGhlIG1pZGRsZV9vdXQoKSBmdW5jdGlvbiBieSBzcGVjaWZ5aW5nIHRoZSBhcHByb3ByaWF0ZSBtaWRkbGUgbGV2ZWwgdmlhIHRoZSBsZXZlbCBhcmd1bWVudCBhbmQgc2VsZWN0aW5nIHRoZSB0b3AtZG93biBhcHByb2FjaCB3aXRoIHRoZSBtZXRob2QgYXJndW1lbnQuDQoNCiMjIEZvcmVjYXN0IHJlY29uY2lsaWF0aW9uDQoNCmh0dHBzOi8vb3RleHRzLmNvbS9mcHAzL3JlY29uY2lsaWF0aW9uLmh0bWwNCg0KIyMjIFRoZSBNaW5UIG9wdGltYWwgcmVjb25jaWxpYXRpb24gYXBwcm9hY2gNCg0KV2UgcmVmZXIgdG8gdGhpcyBhcyB0aGUgTWluVCAob3IgTWluaW11bSBUcmFjZSkgb3B0aW1hbCByZWNvbmNpbGlhdGlvbiBhcHByb2FjaCAuIE1pblQgaXMgaW1wbGVtZW50ZWQgYnkgbWluX3RyYWNlKCkgd2l0aGluIHRoZSByZWNvbmNpbGUoKSBmdW5jdGlvbi4NCg0KVG8gdXNlIHRoaXMgaW4gcHJhY3RpY2UsIHdlIG5lZWQgdG8gZXN0aW1hdGUgJFdfaCQuIEZvdXIgc2ltcGxpZnlpbmcgYXBwcm94aW1hdGlvbnM6DQoNCjEuIGBtaW5fdHJhY2UoKWAgYnkgc2V0dGluZyBgbWV0aG9kID0gIm9scyJgDQoyLiBgbWluX3RyYWNlKClgIGJ5IHNldHRpbmcgYG1ldGhvZCA9ICJ3bHNfdmFyImANCjMuIGBtaW5fdHJhY2UoKWAgYnkgc2V0dGluZyBgbWV0aG9kID0gIndsc19zdHJ1Y3QiYA0KNC4gYG1pbl90cmFjZSgpYCBieSBzZXR0aW5nIGBtZXRob2QgPSAibWludF9jb3YiYDsgZm9yIGNhc2VzIHdoZXJlIHRoZSBudW1iZXIgb2YgYm90dG9tLWxldmVsIHNlcmllcyAkbSQgaXMgbGFyZ2UgY29tcGFyZWQgdG8gdGhlIGxlbmd0aCBvZiB0aGUgc2VyaWVzICRUJCwgdGhpcyBpcyBub3QgYSBnb29kIGVzdGltYXRvci4gSW5zdGVhZCB3ZSB1c2UgYSBzaHJpbmthZ2UgZXN0aW1hdG9yIHdoaWNoIHNocmlua3MgdGhlIHNhbXBsZSBjb3ZhcmlhbmNlIHRvIGEgZGlhZ29uYWwgbWF0cml4LiBgbWV0aG9kID0gIm1pbnRfc2hyaW5rImAuDQoNCnRoZSBvcHRpbWFsIHJlY29uY2lsaWF0aW9uIGZvcmVjYXN0cyBhcmUgZ2VuZXJhdGVkIHVzaW5nIGFsbCB0aGUgaW5mb3JtYXRpb24gYXZhaWxhYmxlIHdpdGhpbiBhIGhpZXJhcmNoaWNhbCBvciBhIGdyb3VwZWQgc3RydWN0dXJlLiBUaGlzIGlzIGltcG9ydGFudCwgYXMgcGFydGljdWxhciBhZ2dyZWdhdGlvbiBsZXZlbHMgb3IgZ3JvdXBpbmdzIG1heSByZXZlYWwgZmVhdHVyZXMgb2YgdGhlIGRhdGEgdGhhdCBhcmUgb2YgaW50ZXJlc3QgdG8gdGhlIHVzZXIgYW5kIGFyZSBpbXBvcnRhbnQgdG8gYmUgbW9kZWxsZWQuIFRoZXNlIGZlYXR1cmVzIG1heSBiZSBjb21wbGV0ZWx5IGhpZGRlbiBvciBub3QgZWFzaWx5IGlkZW50aWZpYWJsZSBhdCBvdGhlciBsZXZlbHMuDQoNCiMjIEZvcmVjYXN0aW5nIEF1c3RyYWxpYW4gZG9tZXN0aWMgdG91cmlzbQ0KDQpXZSB1c2UgdGhlIGRhdGEgdXAgdG8gdGhlIGVuZCBvZiAyMDE1IGFzIGEgdHJhaW5pbmcgc2V0LCB3aXRoaG9sZGluZyB0aGUgZmluYWwgdHdvIHllYXJzIChlaWdodCBxdWFydGVycywgMjAxNlEx4oCTMjAxN1E0KSBhcyBhIHRlc3Qgc2V0IGZvciBldmFsdWF0aW9uLiBUaGUgY29kZSBiZWxvdyBkZW1vbnN0cmF0ZXMgdGhlIGZ1bGwgd29ya2Zsb3cgZm9yIGdlbmVyYXRpbmcgY29oZXJlbnQgZm9yZWNhc3RzIHVzaW5nIHRoZSBib3R0b20tdXAsIE9MUyBhbmQgTWluVCBtZXRob2RzLg0KYGBge3J9DQp0b3VyaXNtX2Z1bGwgPC0gdG91cmlzbSAlPiUNCiAgYWdncmVnYXRlX2tleSgoU3RhdGUgLyBSZWdpb24pICogUHVycG9zZSwgVHJpcHMgPSBzdW0oVHJpcHMpKQ0KZml0IDwtIHRvdXJpc21fZnVsbCAlPiUNCiAgZmlsdGVyKHllYXIoUXVhcnRlcikgPD0gMjAxNSkgJT4lDQogIG1vZGVsKGJhc2UgPSBFVFMoVHJpcHMpKSAlPiUNCiAgcmVjb25jaWxlKA0KICAgIGJ1ID0gYm90dG9tX3VwKGJhc2UpLA0KICAgIG9scyA9IG1pbl90cmFjZShiYXNlLCBtZXRob2QgPSAib2xzIiksDQogICAgbWludCA9IG1pbl90cmFjZShiYXNlLCBtZXRob2QgPSAibWludF9zaHJpbmsiKSwNCiAgKQ0KYGBgDQpIZXJlLCBmaXQgY29udGFpbnMgdGhlIGJhc2UgRVRTIG1vZGVsIGZvciBlYWNoIHNlcmllcyBpbiB0b3VyaXNtX2Z1bGwsIGFsb25nIHdpdGggdGhlIHRocmVlIG1ldGhvZHMgZm9yIHByb2R1Y2luZyBjb2hlcmVudCBmb3JlY2FzdHMgYXMgc3BlY2lmaWVkIGluIHRoZSByZWNvbmNpbGUoKSBmdW5jdGlvbi4NCmBgYHtyfQ0KZmMgPC0gZml0ICU+JSBmb3JlY2FzdChoID0gIjIgeWVhcnMiKQ0KYGBgDQpQYXNzaW5nIGZpdCBpbnRvIGZvcmVjYXN0KCkgZ2VuZXJhdGVzIGJhc2UgYW5kIGNvaGVyZW50IGZvcmVjYXN0cyBhY3Jvc3MgYWxsIHRoZSBzZXJpZXMgaW4gdGhlIGFnZ3JlZ2F0aW9uIHN0cnVjdHVyZS4NCg0KV2UgcGxvdCB0aGUgZm91ciBwb2ludCBmb3JlY2FzdHMgZm9yIHRoZSBvdmVybmlnaHQgdHJpcHMgZm9yIHRoZSBBdXN0cmFsaWFuIHRvdGFsLCB0aGUgc3RhdGVzLCBhbmQgdGhlIHB1cnBvc2VzIG9mIHRyYXZlbCwgYWxvbmcgd2l0aCB0aGUgYWN0dWFsIG9ic2VydmF0aW9ucyBvZiB0aGUgdGVzdCBzZXQuIFRvIG1ha2UgaXQgZWFzaWVyIHRvIHNlZSB0aGUgZGlmZmVyZW5jZXMsIHdlIGhhdmUgaW5jbHVkZWQgb25seSB0aGUgbGFzdCBmaXZlIHllYXJzIG9mIHRoZSB0cmFpbmluZyBkYXRhLCBhbmQgaGF2ZSBvbWl0dGVkIHRoZSBwcmVkaWN0aW9uIGludGVydmFscy4gSW4gbW9zdCBwYW5lbHMsIHRoZSBpbmNyZWFzZSBpbiBvdmVybmlnaHQgdHJpcHMsIGVzcGVjaWFsbHkgaW4gdGhlIHNlY29uZCBoYWxmIG9mIHRoZSB0ZXN0IHNldCwgaXMgaGlnaGVyIHRoYW4gd2hhdCBpcyBwcmVkaWN0ZWQgYnkgdGhlIHBvaW50IGZvcmVjYXN0cy4gVGhpcyBpcyBwYXJ0aWN1bGFybHkgbm90aWNlYWJsZSBmb3IgdGhlIG1haW5sYW5kIGVhc3Rlcm4gc3RhdGVzIG9mIEFDVCwgTmV3IFNvdXRoIFdhbGVzLCBRdWVlbnNsYW5kIGFuZCBWaWN0b3JpYSwgYW5kIGFjcm9zcyBhbGwgcHVycG9zZXMgb2YgdHJhdmVsLg0KYGBge3J9DQpmYyAlPiUNCiAgZmlsdGVyKGlzX2FnZ3JlZ2F0ZWQoUmVnaW9uKSwgaXNfYWdncmVnYXRlZChQdXJwb3NlKSkgJT4lDQogIGF1dG9wbG90KA0KICAgIHRvdXJpc21fZnVsbCAlPiUgZmlsdGVyKHllYXIoUXVhcnRlcikgPj0gMjAxMSksDQogICAgbGV2ZWwgPSBOVUxMDQogICkgKw0KICBsYWJzKHkgPSAiVHJpcHMgKCcwMDApIikgKw0KICBmYWNldF93cmFwKHZhcnMoU3RhdGUpLCBzY2FsZXMgPSAiZnJlZV95IikNCmBgYA0KYGBge3J9DQpmYyAlPiUNCiAgZmlsdGVyKGlzX2FnZ3JlZ2F0ZWQoU3RhdGUpLCAhaXNfYWdncmVnYXRlZChQdXJwb3NlKSkgJT4lDQogIGF1dG9wbG90KA0KICAgIHRvdXJpc21fZnVsbCAlPiUgZmlsdGVyKHllYXIoUXVhcnRlcikgPj0gMjAxMSksDQogICAgbGV2ZWwgPSBOVUxMDQogICkgKw0KICBsYWJzKHkgPSAiVHJpcHMgKCcwMDApIikgKw0KICBmYWNldF93cmFwKHZhcnMoUHVycG9zZSksIHNjYWxlcyA9ICJmcmVlX3kiKQ0KYGBgDQpUaGUgYWNjdXJhY3kgb2YgdGhlIGZvcmVjYXN0cyBvdmVyIHRoZSB0ZXN0IHNldCBjYW4gYmUgZXZhbHVhdGVkIHVzaW5nIHRoZSBhY2N1cmFjeSgpIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCmZjICU+JQ0KICBmaWx0ZXIoaXNfYWdncmVnYXRlZChTdGF0ZSksIGlzX2FnZ3JlZ2F0ZWQoUHVycG9zZSkpICU+JQ0KICBhY2N1cmFjeSgNCiAgICBkYXRhID0gdG91cmlzbV9mdWxsLA0KICAgIG1lYXN1cmVzID0gbGlzdChybXNlID0gUk1TRSwgbWFzZSA9IE1BU0UpDQogICkgJT4lDQogIGdyb3VwX2J5KC5tb2RlbCkgJT4lDQogIHN1bW1hcmlzZShybXNlID0gbWVhbihybXNlKSwgbWFzZSA9IG1lYW4obWFzZSkpDQpgYGANCmBgYHtyfQ0KZmMgJT4lDQogIGZpbHRlcihpc19hZ2dyZWdhdGVkKFN0YXRlKSwgIWlzX2FnZ3JlZ2F0ZWQoUHVycG9zZSkpICU+JQ0KICBhY2N1cmFjeSgNCiAgICBkYXRhID0gdG91cmlzbV9mdWxsLA0KICAgIG1lYXN1cmVzID0gbGlzdChybXNlID0gUk1TRSwgbWFzZSA9IE1BU0UpDQogICkgJT4lDQogIGdyb3VwX2J5KC5tb2RlbCkgJT4lDQogIHN1bW1hcmlzZShybXNlID0gbWVhbihybXNlKSwgbWFzZSA9IG1lYW4obWFzZSkpDQpgYGANCmh0dHBzOi8vb3RleHRzLmNvbS9mcHAzL3RvdXJpc20uaHRtbA0KDQpSZWNvbmNpbGluZyB0aGUgYmFzZSBmb3JlY2FzdHMgdXNpbmcgT0xTIGFuZCBNaW5UIHJlc3VsdHMgaW4gbW9yZSBhY2N1cmF0ZSBmb3JlY2FzdHMgY29tcGFyZWQgdG8gdGhlIGJvdHRvbS11cCBhcHByb2FjaC4gVGhpcyByZXN1bHQgaXMgY29tbW9ubHkgb2JzZXJ2ZWQgaW4gYXBwbGljYXRpb25zIGFzIHJlY29uY2lsaWF0aW9uIGFwcHJvYWNoZXMgdXNlIGluZm9ybWF0aW9uIGZyb20gYWxsIGxldmVscyBvZiB0aGUgc3RydWN0dXJlLCByZXN1bHRpbmcgaW4gbW9yZSBhY2N1cmF0ZSBjb2hlcmVudCBmb3JlY2FzdHMgY29tcGFyZWQgdG8gdGhlIG9sZGVyIG1ldGhvZHMgd2hpY2ggdXNlIGxpbWl0ZWQgaW5mb3JtYXRpb24uIEZ1cnRoZXJtb3JlLCByZWNvbmNpbGlhdGlvbiB1c3VhbGx5IGltcHJvdmVzIHRoZSBpbmNvaGVyZW50IGJhc2UgZm9yZWNhc3RzIGZvciBhbG1vc3QgYWxsIGxldmVscy4NCg0KIyMgUmVjb25jaWxlZCBkaXN0cmlidXRpb25hbCBmb3JlY2FzdHMNCg0KaHR0cHM6Ly9vdGV4dHMuY29tL2ZwcDMvcmVjLXByb2IuaHRtbA0KDQojIyMgRm9yZWNhc3RpbmcgQXVzdHJhbGlhbiBwcmlzb24gcG9wdWxhdGlvbg0KDQpSZXR1cm5pbmcgdG8gdGhlIEF1c3RyYWxpYW4gcHJpc29uIHBvcHVsYXRpb24gZGF0YSwgd2Ugd2lsbCBjb21wYXJlIHRoZSBmb3JlY2FzdHMgZnJvbSBib3R0b20tdXAgYW5kIE1pblQgbWV0aG9kcyBhcHBsaWVkIHRvIGJhc2UgRVRTIG1vZGVscywgdXNpbmcgYSB0ZXN0IHNldCBjb21wcmlzaW5nIHRoZSBmaW5hbCB0d28geWVhcnMgb3IgZWlnaHQgcXVhcnRlcnMgMjAxNVEx4oCTMjAxNlE0IG9mIHRoZSBhdmFpbGFibGUgZGF0YS4NCmBgYHtyfQ0KcHJpc29uIDwtIHJlYWRyOjpyZWFkX2NzdigiaHR0cHM6Ly9PVGV4dHMuY29tL2ZwcDMvZXh0cmFmaWxlcy9wcmlzb25fcG9wdWxhdGlvbi5jc3YiKSAlPiUNCiAgbXV0YXRlKFF1YXJ0ZXIgPSB5ZWFycXVhcnRlcihEYXRlKSkgJT4lDQogIHNlbGVjdCgtRGF0ZSkgICU+JQ0KICBhc190c2liYmxlKGtleSA9IGMoR2VuZGVyLCBMZWdhbCwgU3RhdGUsIEluZGlnZW5vdXMpLCBpbmRleCA9IFF1YXJ0ZXIpICU+JQ0KICByZWxvY2F0ZShRdWFydGVyKQ0KDQpwcmlzb25fZ3RzIDwtIHByaXNvbiAlPiUNCiAgYWdncmVnYXRlX2tleShHZW5kZXIgKiBMZWdhbCAqIFN0YXRlLCBDb3VudCA9IHN1bShDb3VudCkvMWUzKQ0KDQpmaXQgPC0gcHJpc29uX2d0cyAlPiUNCiAgZmlsdGVyKHllYXIoUXVhcnRlcikgPD0gMjAxNCkgJT4lDQogIG1vZGVsKGJhc2UgPSBFVFMoQ291bnQpKSAlPiUNCiAgcmVjb25jaWxlKA0KICAgIGJvdHRvbV91cCA9IGJvdHRvbV91cChiYXNlKSwNCiAgICBNaW5UID0gbWluX3RyYWNlKGJhc2UsIG1ldGhvZCA9ICJtaW50X3NocmluayIpDQogICkNCmZjIDwtIGZpdCAlPiUgZm9yZWNhc3QoaCA9IDgpDQpmYyAlPiUNCiAgZmlsdGVyKGlzX2FnZ3JlZ2F0ZWQoU3RhdGUpLCBpc19hZ2dyZWdhdGVkKEdlbmRlciksIGlzX2FnZ3JlZ2F0ZWQoTGVnYWwpKSAlPiUNCiAgYXV0b3Bsb3QocHJpc29uX2d0cywgYWxwaGEgPSAwLjcsIGxldmVsID0gOTApICsNCiAgbGFicyh5ID0gIk51bWJlciBvZiBwcmlzb25lcnMgKCcwMDApIiwNCiAgICAgICB0aXRsZSA9ICJBdXN0cmFsaWFuIHByaXNvbiBwb3B1bGF0aW9uICh0b3RhbCkiKQ0KYGBgDQpUaGUgdGhyZWUgc2V0cyBvZiBmb3JlY2FzdHMgZm9yIHRoZSBhZ2dyZWdhdGUgQXVzdHJhbGlhbiBwcmlzb24gcG9wdWxhdGlvbi4gVGhlIGJhc2UgYW5kIGJvdHRvbS11cCBmb3JlY2FzdHMgZnJvbSB0aGUgRVRTIG1vZGVscyBzZWVtIHRvIHVuZGVyZXN0aW1hdGUgdGhlIHRyZW5kIG92ZXIgdGhlIHRlc3QgcGVyaW9kLiBUaGUgTWluVCBhcHByb2FjaCBjb21iaW5lcyBpbmZvcm1hdGlvbiBmcm9tIGFsbCB0aGUgYmFzZSBmb3JlY2FzdHMgaW4gdGhlIGFnZ3JlZ2F0aW9uIHN0cnVjdHVyZTsgaW4gdGhpcyBjYXNlLCB0aGUgYmFzZSBmb3JlY2FzdHMgYXQgdGhlIHRvcCBsZXZlbCBhcmUgYWRqdXN0ZWQgdXB3YXJkcy4NCg0KVGhlIE1pblQgcmVjb25jaWxlZCBwcmVkaWN0aW9uIGludGVydmFscyBhcmUgbXVjaCB0aWdodGVyIHRoYW4gdGhlIGJhc2UgZm9yZWNhc3RzLCBkdWUgdG8gTWluVCBiZWluZyBiYXNlZCBvbiBhbiBlc3RpbWF0b3IgdGhhdCBtaW5pbWl6ZXMgdmFyaWFuY2VzLiBUaGUgYmFzZSBmb3JlY2FzdCBkaXN0cmlidXRpb25zIGFyZSBhbHNvIGluY29oZXJlbnQsIGFuZCB0aGVyZWZvcmUgY2Fycnkgd2l0aCB0aGVtIHRoZSBleHRyYSB1bmNlcnRhaW50eSBvZiB0aGUgaW5jb2hlcmVuY3kgZXJyb3IuDQoNCiBNaW5UIGFuZCBiYXNlIGZvcmVjYXN0cyBhdCB2YXJpb3VzIGxldmVscyBvZiBhZ2dyZWdhdGlvbi4gVG8gbWFrZSBpdCBlYXNpZXIgdG8gc2VlIHRoZSBlZmZlY3QsIHdlIG9ubHkgc2hvdyB0aGUgbGFzdCBmaXZlIHllYXJzIG9mIHRyYWluaW5nIGRhdGEuIEluIGdlbmVyYWwsIE1pblQgYWRqdXN0cyB0aGUgYmFzZSBmb3JlY2FzdHMgaW4gdGhlIGRpcmVjdGlvbiBvZiB0aGUgdGVzdCBzZXQsIGhlbmNlIGltcHJvdmluZyB0aGUgZm9yZWNhc3QgYWNjdXJhY3kuIFRoZXJlIGlzIG5vIGd1YXJhbnRlZSB0aGF0IE1pblQgcmVjb25jaWxlZCBmb3JlY2FzdHMgd2lsbCBiZSBtb3JlIGFjY3VyYXRlIHRoYW4gdGhlIGJhc2UgZm9yZWNhc3RzIGZvciBldmVyeSBzZXJpZXMsIGJ1dCB0aGV5IHdpbGwgYmUgbW9yZSBhY2N1cmF0ZSBvbiBhdmVyYWdlDQogDQpgYGB7cn0NCmZjICU+JQ0KICBmaWx0ZXIoDQogICAgLm1vZGVsICVpbiUgYygiYmFzZSIsICJNaW5UIiksDQogICAgIWlzX2FnZ3JlZ2F0ZWQoU3RhdGUpLCBpc19hZ2dyZWdhdGVkKExlZ2FsKSwgaXNfYWdncmVnYXRlZChHZW5kZXIpDQogICkgJT4lDQogIGF1dG9wbG90KA0KICAgIHByaXNvbl9ndHMgJT4lIGZpbHRlcih5ZWFyKFF1YXJ0ZXIpID49IDIwMTApLA0KICAgIGFscGhhID0gMC43LCBsZXZlbCA9IDkwDQogICkgKw0KICBsYWJzKHRpdGxlID0gIlByaXNvbiBwb3B1bGF0aW9uIChieSBzdGF0ZSkiLA0KICAgICAgIHkgPSAiTnVtYmVyIG9mIHByaXNvbmVycyAoJzAwMCkiKSArDQogIGZhY2V0X3dyYXAodmFycyhTdGF0ZSksIHNjYWxlcyA9ICJmcmVlX3kiLCBuY29sID0gNCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQ0KYGBgDQpVc2luZyB0aGUgYWNjdXJhY3koKSBmdW5jdGlvbiwgd2UgZXZhbHVhdGUgdGhlIGZvcmVjYXN0IGFjY3VyYWN5IGFjcm9zcyB0aGUgZ3JvdXBlZCBzdHJ1Y3R1cmUuIFRoZSBjb2RlIGJlbG93IGV2YWx1YXRlcyB0aGUgZm9yZWNhc3QgYWNjdXJhY3kgZm9yIG9ubHkgdGhlIHRvcC1sZXZlbCBuYXRpb25hbCBhZ2dyZWdhdGUgb2YgdGhlIEF1c3RyYWxpYW4gcHJpc29uIHBvcHVsYXRpb24gdGltZSBzZXJpZXMuIA0KYGBge3J9DQpmYyAlPiUNCiAgZmlsdGVyKGlzX2FnZ3JlZ2F0ZWQoU3RhdGUpLCBpc19hZ2dyZWdhdGVkKEdlbmRlciksIGlzX2FnZ3JlZ2F0ZWQoTGVnYWwpKSAlPiUNCiAgYWNjdXJhY3koZGF0YSA9IHByaXNvbl9ndHMsIG1lYXN1cmVzID0gbGlzdCgNCiAgICBtYXNlID0gTUFTRSwNCiAgICBzcyA9IHNraWxsX3Njb3JlKENSUFMpDQogICkpICU+JQ0KICBncm91cF9ieSgubW9kZWwpICU+JQ0KICBzdW1tYXJpc2UobWFzZSA9IG1lYW4obWFzZSksIHNzcGMgPSBtZWFuKHNzKSAqIDEwMCkNCmBgYA0KQSBsb3cgdmFsdWUgb2YgTUFTRSBpbmRpY2F0ZXMgYSBnb29kIGZvcmVjYXN0LCB3aGlsZSBhIGhpZ2ggdmFsdWUgb2YgdGhlIHNraWxsIHNjb3JlIGluZGljYXRlcyBhIGdvb2QgZm9yZWNhc3QuDQoNClRoZSByZXN1bHRzIHNob3cgdGhhdCB0aGUgTWluVCByZWNvbmNpbGVkIGZvcmVjYXN0cyBpbXByb3ZlIG9uIHRoZSBhY2N1cmFjeSBvZiB0aGUgYmFzZSBmb3JlY2FzdHMgYW5kIGFyZSBhbHNvIG1vcmUgYWNjdXJhdGUgdGhhbiB0aGUgYm90dG9tLXVwIGZvcmVjYXN0cy4gQXMgdGhlIE1pblQgb3B0aW1hbCByZWNvbmNpbGlhdGlvbiBhcHByb2FjaCB1c2VzIGluZm9ybWF0aW9uIGZyb20gYWxsIGxldmVscyBpbiB0aGUgc3RydWN0dXJlLCBpdCBnZW5lcmF0ZXMgbW9yZSBhY2N1cmF0ZSBmb3JlY2FzdHMgdGhhbiB0aGUgdHJhZGl0aW9uYWwgYXBwcm9hY2hlcyAoc3VjaCBhcyBib3R0b20tdXApIHdoaWNoIHVzZSBsaW1pdGVkIGluZm9ybWF0aW9uLg0KDQo=