Overview

This vignette demonstrates how to construct a machine learning tactical asset allocation strategy using public data and open source. The proposed strategy rotates between two ETFs: high risk such as the SPY ETF and low risk such as 7-10 years IEF Treasury ETF. The strategy returns an improved performance over a naive strategy that allocates 60-40 between the SPY and IEF over time. We provide a detailed description of the implementation, data collection, feature space selection, training, and back-testing. While the literature on return predictability is extensive, it is still important to understand such predictability from the investor’s perspective rather than the econometrician’s alone. We hope that this vignette would encourage further research and reproducibility.

Getting the Data

We refer to the quantmod package to download data from Yahoo Finance. In particular, we focus on a number of market indicators: the SPY ETFs which tracks the S&P 500 index, the VIX index known as the fear gauge which indicates the investors view about the market volatility, the GLD gold ETF, the 7-10 years treasury bond ETF, and XLF the financial sector ETF. Note that all of which are tradable except for the VIX. While VIX can be traded using an exchange traded note, our analysis here does not include its volume. Additionally, the VXX became available in early 2009, the inclusion of which would limit the sample period.

The implementation is conducted mainly using R. In particular, we refer to a number of R packages. We rely on the quantmod package to download market data from Yahoo Finance, the lubridate library to manipulate date formats, plyr for general data manipulation, the glmnet for training and implementation of the machine learning algorithm, the PerformanceAnalytics for financial analytics/summary, and both ggplot2 and plotly for visualizations.

library(quantmod)
library(lubridate)
library(plyr)
library(glmnet)
library(PerformanceAnalytics)
library(ggplot2)
library(plotly)
library(parallel)
rm(list = ls())
t1 <- "1990-01-01"
v <- c("SPY","GLD","IEF","XLF")
P.list <- lapply(v, function(sym) get(getSymbols(sym,from = t1)) )
getSymbols("^VIX",from = t1)
[1] "^VIX"
P.list <- c(P.list,list(VIX))

The P.list returns a list of data for each symbol containing 6 columns. For each item, there are different number of observations. The reason of which is that each ETF/index dates to a different time period.

sapply(P.list,dim)
     [,1] [,2] [,3] [,4] [,5]
[1,] 6836 3861 4443 5346 7615
[2,]    6    6    6    6    6

To see the starting date of each, we can run the following command:

lapply(P.list, function(x)  first(date(x)) )
[[1]]
[1] "1993-01-29"

[[2]]
[1] "2004-11-18"

[[3]]
[1] "2002-07-30"

[[4]]
[1] "1998-12-22"

[[5]]
[1] "1990-01-02"

We note that the SPY ETF has the earliest inception date, whereas the GLD has the latest one.

Feature Space

To construct the main feature space, we focus on the adjusted prices (sixth column) along with volume (fifth column). As mentioned above, note that volume is available for each symbol except the VIX since it is not tradable.

P.list5 <- lapply(P.list, function(x) x[,5])
P.list6 <- lapply(P.list, function(x) x[,6])

Since we are dealing with xts and zoo objects, it is straightforward to merge the time series along:

P5 <- na.omit(Reduce(function(...) merge(...),P.list5 ))
P6 <- na.omit(Reduce(function(...) merge(...),P.list6 ))
# adjust names
names(P5) <- names(P6) <- c("SPY","GLD","IEF","XLF","VIX")
names(P5) <- paste(names(P5),"vol",sep = "_")
summary(P5$VIX_vol)
     Index               VIX_vol 
 Min.   :2004-11-18   Min.   :0  
 1st Qu.:2008-09-19   1st Qu.:0  
 Median :2012-07-19   Median :0  
 Mean   :2012-07-20   Mean   :0  
 3rd Qu.:2016-05-20   3rd Qu.:0  
 Max.   :2020-03-23   Max.   :0  
P5$VIX_vol <- NULL

For each adjusted price, we compute the returns. In addition, we compute the moving average of each return/change over the last 25 days. Our feature space mainly constitutes of the daily return of each symbol, the deviation of each from its MA, and the daily volume of each ETF. Note that R6_roll is a technical analysis tool that demonstrates whether the returns today are too high/low with respect to the corresponding MA. We stack the feature space in the R object. The variable of interest we are trying to model is the next day change in the SPY price

R6 <- Return.calculate(P6)
# add rolling difference 
R6_roll <- R6 - rollapply(R6,25,mean)
names(R6_roll) <- paste(names(R6_roll),"_roll",sep="")
R <- na.omit(merge(R6,R6_roll,P5))
SPY_next <- stats::lag(R$SPY,-1)
names(SPY_next) <- "SPY_next"
R <- na.omit(merge(SPY_next,R))

Finally, the data ranges between

range(date(R))
[1] "2004-12-27" "2020-03-20"

Let’s take a look at the contemporaneous correlation of each feature with the SPY return

cor(R)[,"SPY"]
   SPY_next         SPY         GLD         IEF         XLF         VIX    SPY_roll    GLD_roll    IEF_roll 
-0.11907331  1.00000000  0.02411089 -0.42064985  0.83082759 -0.73426497  0.98736461  0.02890102 -0.41768268 
   XLF_roll    VIX_roll     SPY_vol     GLD_vol     IEF_vol     XLF_vol 
 0.81905191 -0.71892022 -0.13650049 -0.08940766 -0.05839356 -0.05589110 

We observe that the VIX is highly negatively correlated with the same day SPY return. The same holds true for IEF, with -41% correlation. We also note that there is a high positive correlation between the SPY and XLF. Since, the SPY_roll is a function of the same day return, it exhibits high correlation. For the volume, we observe a weak negative correlation. Nonetheless, what matters the more is how the feature space correlates with the next day SPY return than the same return. The following command provides us a perspective on such:

cor(R)[,"SPY_next"]
    SPY_next          SPY          GLD          IEF          XLF          VIX     SPY_roll     GLD_roll 
 1.000000000 -0.119073310 -0.045545477  0.053076235 -0.090127108  0.070958696 -0.117080655 -0.041258439 
    IEF_roll     XLF_roll     VIX_roll      SPY_vol      GLD_vol      IEF_vol      XLF_vol 
 0.048699628 -0.089478329  0.073908555  0.001087906  0.008234174 -0.017657529  0.008429678 

Mainly, the market shows a reversal behavior, where today’s return has a negative 8% correlation with next day. This is also evident for the other indicators. Overall, we witness a weaker correlation between the next day return and the feature space. Obviously, predicting the next day return as a much more challenging task.

Response Variable

Rather than focusing on the next day return, we relate to the change in the SPY price as the response variable with two levels: down (-1) and up (+1). Specifically, we define

R$CHANGE_next <- 1
R$CHANGE_next[R$SPY_next < -0.01] <- -1
table(R$CHANGE)

  -1    1 
 455 3380 
# stack into a dataset rather than an xts object
ds <- data.frame(date = date(R),R)
rownames(ds) <- NULL
ds$SPY_next <- NULL # drop the next day return
# define features
features <- names(ds)[!names(ds) %in% c("date","CHANGE_next")]

Mainly the SPY exhibits 12% of the time a daily drop that is less than -1%. To see whether there is any heterogeneity across the feature space for each level, let’s take a look at the average of the feature space with respect to each level:

sum_change <- dlply(ds,"CHANGE_next",function(x)  apply(x[,features],2,mean) )
(sum_change[[2]] - sum_change[[1]])/abs(sum_change[[1]])
        SPY         GLD         IEF         XLF         VIX    SPY_roll    GLD_roll    IEF_roll    XLF_roll 
 2.11145747 -0.86021267 -0.45423073  0.02854905 -0.18182749 -1.22943200 -1.15739654  1.23701150 -1.19965445 
   VIX_roll     SPY_vol     GLD_vol     IEF_vol     XLF_vol 
 1.19411229 -0.37144701 -0.24487036 -0.09114107 -0.41105495 

Relatively, we observe that the above feature space provides some discrepancy between the two levels. For instance, the previous day return tends to be larger when the market goes up. The opposite is true for gold and bonds.

Machine Learning Application

After defining the feature space and the response variable, we need to predict the probability of the market going up or down on a daily basis. Given this probability we, eventually, will construct our tactical asset allocation strategy.

To get started, we fit a binomial model with an elastic penalty on weekly basis to find the optimal weights to map the feature space into the next day change. Since we use an elastic net, it combines between two penalties. The first is the LASSO which acts as a constraint on the first norm of the weights and serves as an elimination process. The second is the ridge regression which is a constraint on the second norm and serves as a shrinkage approach toward zero. By design, \(\alpha=0.5\) according to the glmnet package acts as in-between penalty of the two approaches. The Lagrangian or the magnitude attributed to the penalty of each constraint, denoted by \(\lambda\), is determined using 10 folds cross validation (henceforth CV).

In the code below, we run a loop in which we train the model using 50 weeks of history and predict the next day change using the following week. Given the 50 weeks of training set, we determine the optimal model using 10 folds CV. Given the test set, we predict the probability of the SPY going either up or down. To avoid data leakage into the test set, we drop the last observation in the training set, as it may contain knowledge about the following week price change.

weeks <- date(unique(floor_date(ds$date,"week")))
weeks <- c(weeks, last(weeks) + weeks(1))
W <- 50
al <- 0.5 # net elastic
ds_predict <- data.frame()
ds_beta <- list()
w_seq <- W:(length(weeks)-2)
ds_predict_f <- function(w) {
  #cat("This is week ",w, " out of ",length(weeks),"\n")
  # training set consists of relatively 250 daily observations
  train.weeks <- weeks[(w-W+1):(w+1)]
  train.index <- which((ds$date > train.weeks[1]) & (ds$date <= train.weeks[W+1]))
  
  # the weekly is around 5 days
  test.weeks <-  weeks[w+1:2]
  test.index <- which((ds$date > test.weeks[1]) & (ds$date < test.weeks[2]))
  
  # drop the last obs from the train set to avoid leakage
  DS <- ds[train.index[-length(train.index)],]
  x_train <- model.matrix( ~ .-1, DS[,features])
  
  # use CV
  set.seed(17)
  try_error <- try(lm <- cv.glmnet(x=x_train,y = as.factor(DS$CHANGE_next), intercept=FALSE,
                 family =   "multinomial", alpha=al, nfolds=10,parallel = T),silent = TRUE)
  
  i <- 1
  while(inherits(try_error,"try-error")) {
    #cat("Error in CV","\n")
    try_error <- try(lm <- cv.glmnet(x=x_train,y = as.factor(DS$CHANGE_next),
                  intercept=FALSE, family =   "multinomial", alpha=al, nfolds=10,
                  parallel = T),silent = TRUE)
    i <- i + 1
    if (i == 10)
        lm <- lm
    }
  
  # assign the lambda
  best_lambda <- lm$lambda.min
  
  # find the optimal model
  lm.star = glmnet(x=x_train,y = as.factor(DS$CHANGE_next), intercept=FALSE ,
                   family =   "multinomial", alpha=al, lambda = best_lambda)
  
  # fit the test sample
  DS_test <- ds[test.index,]
  x_test <- model.matrix( ~ .-1, DS_test[,features])
  DS_predict <- predict(lm.star,x_test,type = "response")
  # stack in data
  DS_predict <- data.frame(DS_predict)
  names(DS_predict) <- c("dn","up")
  DS_predict$date <- ds[test.index,"date"]
  #ds_predict <- rbind(ds_predict,DS_predict)
  
  # finally keep track of the glmnet results in a list
  ds_beta <- c(ds_beta,list(lm.star))
  
  list(DS_predict = DS_predict,lm_list = list(lm.star))
  }
mclapply_list <- mclapply(w_seq,ds_predict_f,mc.cores = detectCores())
ds_predict_l <- lapply(mclapply_list, function(x) x$DS_predict )
ds_predict <- ldply(ds_predict_l,data.frame)
head(ds_predict)

The above code stacks the predicted probabilities in a data frame object named ds_predict. From each iteration, we keep track of the optimal model weights, which allows us to investigate the important features over time. Due to the rolling window nature, which is not recursive in this case, we can refer to the mclapply function for parallelization

summary(ds_predict)
       dn                  up                 date           
 Min.   :0.0000006   Min.   :0.0000156   Min.   :2005-12-12  
 1st Qu.:0.0506136   1st Qu.:0.7717289   1st Qu.:2009-07-08  
 Median :0.1213678   Median :0.8786322   Median :2013-01-31  
 Mean   :0.1558049   Mean   :0.8441951   Mean   :2013-01-30  
 3rd Qu.:0.2282711   3rd Qu.:0.9493864   3rd Qu.:2016-08-24  
 Max.   :0.9999844   Max.   :0.9999994   Max.   :2020-03-20  

Signal Extraction

We merge the predicted probabilities with the original data to construct the main datasheet, which we will refer to for the trading strategy and, hence, the back-testing.

ds2 <- merge(ds,ds_predict,by = "date")
updn <- data.frame(dn = ds2$dn,up =ds2$up)
rownames(updn) <- ds2$date
updn <- as.xts(updn)
updn_roll <- na.omit(rollapply(updn,25,mean))
names(updn_roll) <- c("dn_roll","up_roll")
ds.updn <- data.frame(date = date(updn_roll),updn_roll)
rownames(ds.updn) <- NULL
ds3 <- merge(ds2,ds.updn, by = "date")

To create a smooth signal, we consider the 25 days moving average of the predicted probability. We merge the smoothed probabilities along with the ETF returns in a new data frame named ds3.

To again an initial perspective on the extracted signal, we plot the cumulative return on the SPY along with the probability going down over time. Interestingly, we observe that the signal (red line), which is data driven, reaches the highest level during the 2007-09 financial crisis, during which the SPY (black line) exhibits the largest over the whole sample. Additionally, while the probability seems declining the last couple of year, we observe that it picks up during the recent market turmoils in late 2018. Overall, we observe a negative relationship between the two.

plot(cumsum(SPY) ~ date, data = ds3, type = "l",ylab = "", main  = "cumulative return of SPY versus probability down")
lines(dn_roll ~ date, data = ds3,col = 2)
abline(h = 0, lty=2)
legend("topleft",c("SPY","Probability Down"),col = 1:2, lty = 1)
grid(10)

The above suggests that the probability of down/up can be utilize to avoid significant market drops. The question is how can we utilize this? In the following section, we propose an automated trading strategy that tries to materialize the above insights.

Tactical Asset Allocation Strategy

In this section, we discuss how we implement the tactical allocation strategy using the above extracted signal. In order to demonstrate this, we refer to some mathematical notation. Let \(\hat{\pi}_{t+1}\) denote the forecasted probability that the SPY at \(t+1\) will go up. The hat (\(\hat{\cdot}\)) implies that the probability is a forecast established at time \(t\) rather than \(t+1\). Depending on the intensity of signal, we create a portfolio that either longs the SPY or the IEF. To put formally, the return of the strategy at time \(t+1\) is given by \[\begin{equation} r_{s,t+1} = I_{\left[ \hat{\pi}_{t+1} > a \right]} r^{e}_{t+1} + I_{\left[ \hat{\pi}_{t+1} \leq a \right]} r^{b}_{t+1} \end{equation}\]

where \(r^{e}_{t+1}\) and \(r^{b}_{t+1}\) denote the return on SPY and IEF at \(t+1\), respectively. The parameter \(a\) is a predetermined minimum level of confidence for the investor to participate in the equity market. The strategy implies that the investor goes long in the equity alone, if and only if the confidence level that the market will go up the next day is high enough. If not, the strategy goes $1 in the IEF ETF alone.

Backtesting

As a benchmark, we compare our results with respect to a 60-40 strategy that invests 60% in SPY and 40% in IEF. While the benchmark is re-balanced on a daily basis, it does not incorporate any market information. In other words, regardless of what happens in the market, the benchmark will always maintain a 60-40 allocation. Additionally, we compare the strategy with each ETF alone. As a performance summary, we create a summary function that takes \(a\) as the main input and reports the performance of the strategy with respect to each.

next_f <- function(x) c(x[-1],NA)
ds3$IEF_next <- next_f(ds3$IEF)
ds3$SPY_next <- next_f(ds3$SPY)
ds3$BENCHMARK <- with(ds3,0.6*SPY_next + 0.4*IEF_next)
plot_performance <- function(a) {
  
  # round the probabilities to nearest 0.05
  ds3$up_roll <- round(ds3$up_roll/0.05)*0.05
  ds3$dn_roll <- round(ds3$dn_roll/0.05)*0.05
  ds3$PORT <- with(ds3, (up_roll >= a)*(SPY_next)  +  (up_roll < a)*IEF_next )
  
  
  # load ds3 into a ggplot friendly data
  ds_plot <- data.frame(Date = ds3$date, return = cumsum(ds3$PORT), Type = "Strategy")
  ds_plot <- rbind(ds_plot, 
                   data.frame(Date = ds3$date, return = cumsum(ds3$BENCHMARK), Type = "Benchmark"))
  ds_plot <- rbind(ds_plot, 
                   data.frame(Date = ds3$date, return = cumsum(ds3$SPY_next), Type = "SPY"))
  ds_plot <- rbind(ds_plot, 
                   data.frame(Date = ds3$date, return = cumsum(ds3$IEF_next), Type = "IEF"))
  ds_plot <- rbind(ds_plot, 
                   data.frame(Date = ds3$date, return = ds3$dn_roll, Type = "Probability Down"))
  
  ds_plot <- na.omit(ds_plot)
  
  p <- ggplot(ds_plot) + geom_line(aes(x = Date,y = return,colour = Type)) 
  p <- p + geom_abline(intercept =0,linetype = "dashed")
  p <- ggplotly(p,height =  500, width = 900)
  
  # also return the data for performance comparison
  ds_perf <- ds3[,c("IEF_next","SPY_next","BENCHMARK","PORT","SPY_next")]
  rownames(ds_perf) <- ds3$date
  ds_perf <- as.xts(ds_perf)
  list(plot_perf = p,data_perf = ds_perf)
}

Illustration

Let’s take a look the performance over time when \(a=90\%\). This represents a situation in which the investor allocates his wealth mainly to equity if the confidence level that the SPY is going up the following day is at least 90%. Otherwise, the investor is conservative and allocates his wealth to the Treasury ETF mainly.

perf1 <- plot_performance(0.9)
perf1$plot_perf

In the above plot, we observe that the proposed strategy dominates the other candidates in terms of cumulative return over the period, while at the same time trailing the SPY. Moreover, we observe that the strategy avoids the market crash during the 2007-09 financial crisis, indicating that it has a lower downside risk than the benchmark as well as the SPY. Additionally, given the market turmoil in late 2018, we observe that the the proposed strategy is successful in executing a flight to quality during times of uncertainty.

Suppose that the investor is more conservative, such he only invests in the SPY if the probability is at least 95%. In this case, we have

perf2 <- plot_performance(0.95)
perf2$plot_perf

We note that the proposed strategy avoids both the 2007-09 market crash and the recent market drop in late 2018. However, we observe that the strategy yields almost the same cumulative return as the benchmark.

Suppose that the investor is more risk tolerant, such he is willing to invest in the SPY with a level of confidence of 85%. In this case, we observe that the strategy does well in terms of cumulative return.

perf3 <- plot_performance(0.85)
perf3$plot_perf

However, at the same time, we observe that the strategy fails to shift to Treasury bonds during the recent market turmoils, draining the performance of the strategy over time.

Risk-Adjusted Returns

In terms of risk adjusted returns, we consider a number of statistics. The first is the Jensen’s alpha that captures the abnormal return above the benchmark. The second is Sortino ratio, which adjusts the mean return of the strategy with respect to a downside risk (standard deviation). Additionally, we report the beta of each strategy.

P1 <- perf1$data_perf$PORT
P2 <- perf2$data_perf$PORT
P3 <- perf3$data_perf$PORT
B <- perf1$data_perf$BENCHMARK
B2 <- perf1$data_perf$SPY_next
port_all <- list(P2,P1,P3,B,B2)
port_all <- na.omit(Reduce(merge,port_all))
names(port_all)[1:3] <- c("Portfolio_95","Portfolio_90","Portfolio_85")
table.CAPM(port_all[,1:3],port_all[,4])[c(6,2),]

In the above table, we observe that the more conservative strategies yield an annual alpha of 8%, at the same time, while being beta-neutral On the other hand, we note that when \(a\) decreases, the alpha decreases, and the strategy becomes less market beta-neutral.

In terms of downside risk, we compute the Sortino’s ratio for each strategy. Similar to the Sharpe ratio, we estimate the Sortino’s ratio as the ratio between the mean return and the downside risk. By downside risk, we refer to the semi-standard deviation in which we eliminate the positive returns in calculating the volatility. Finally, we scale the ratio into an annual basis using a scale of \(\sqrt{252}\). As an additional perspective, we report the Sharpe-ratio for comparison:

Sort_ratio <- function(x) sqrt(252)*mean(x)/sd(x[x < 0])
SR <-  function(x) sqrt(252)*mean(x)/sd(x)
Sortino <- apply(port_all,2,Sort_ratio)
Sharpe <- apply(port_all,2,SR)
data.frame(rbind(Sortino,Sharpe))

In all cases, except for the 85% level confidence, we observe that the proposed strategy yields a higher risk-adjusted return than the benchmark. This is more evident for the Sortino than the Sharpe. Hence, this indicates that the strategy outperforms the benchmark by bearing less downside risk, while, at the same time, achieving a better or comparable return.

Summary

This vignette provides a simple cost-efficient trading strategy that deploys machine learning using public data and open source software. While the performance of the strategy depends on a couple of specifications, such as the level of confidence \(a\) or what determines a price change in the market, the strategy is mainly data-driven. One may consider other approaches using cross-validation to determine these inputs automatically. Nonetheless, a similar strategy can be also deployed to screen different stocks or ETFs. For instance, one may consider a similar approach to perform as tactical asset allocation across sector ETFs. i.e. sector rotation strategy. We leave this for future investigation.

Disclaimer

The above vigentte is a short illustration of an ongoing research co-authored with Kris Boudt (Vrije Universiteit Brussel, University of Amsterdam and Finvex), Muzafer Cela (Vrije Universiteit Brussel), and Majeed Simaan (Stevens Institute of Technology). The research is titled “In Search of Return Predictability: Evidence from Machine Learning and Tactical Allocation”. For further application, see the implication of the this analysis into constructing machine learning frontiers via this paper.

LS0tCnRpdGxlOiAiVGFjdGljYWwgQXNzZXQgQWxsb2NhdGlvbiB1c2luZyBNYWNoaW5lIExlYXJuaW5nIgphdXRob3I6ICJLcmlzIEJvdWR0LCAgTXV6YWZlciBDZWxhLCBhbmQgTWFqZWVkIFNpbWFhbiIKZGF0ZTogTWFyY2ggMjR0aCwgMjAyMApvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIHBkZl9kb2N1bWVudDogZGVmYXVsdApmaWdfd2lkdGg6IDIwMAotLS0KCiMjIE92ZXJ2aWV3ClRoaXMgdmlnbmV0dGUgZGVtb25zdHJhdGVzIGhvdyB0byBjb25zdHJ1Y3QgYSBtYWNoaW5lIGxlYXJuaW5nIHRhY3RpY2FsIGFzc2V0IGFsbG9jYXRpb24gc3RyYXRlZ3kgdXNpbmcgcHVibGljIGRhdGEgYW5kIG9wZW4gc291cmNlLiBUaGUgcHJvcG9zZWQgc3RyYXRlZ3kgcm90YXRlcyBiZXR3ZWVuIHR3byBFVEZzOiBoaWdoIHJpc2sgc3VjaCBhcyB0aGUgU1BZIEVURiBhbmQgbG93IHJpc2sgc3VjaCBhcyA3LTEwIHllYXJzIElFRiBUcmVhc3VyeSBFVEYuIFRoZSBzdHJhdGVneSByZXR1cm5zIGFuIGltcHJvdmVkIHBlcmZvcm1hbmNlIG92ZXIgYSBuYWl2ZSBzdHJhdGVneSB0aGF0IGFsbG9jYXRlcyA2MC00MCBiZXR3ZWVuIHRoZSBTUFkgYW5kIElFRiBvdmVyIHRpbWUuIFdlIHByb3ZpZGUgYSBkZXRhaWxlZCBkZXNjcmlwdGlvbiBvZiB0aGUgaW1wbGVtZW50YXRpb24sIGRhdGEgY29sbGVjdGlvbiwgZmVhdHVyZSBzcGFjZSBzZWxlY3Rpb24sIHRyYWluaW5nLCBhbmQgYmFjay10ZXN0aW5nLiBXaGlsZSB0aGUgbGl0ZXJhdHVyZSBvbiByZXR1cm4gcHJlZGljdGFiaWxpdHkgaXMgZXh0ZW5zaXZlLCBpdCBpcyBzdGlsbCBpbXBvcnRhbnQgdG8gdW5kZXJzdGFuZCBzdWNoIHByZWRpY3RhYmlsaXR5IGZyb20gdGhlIGludmVzdG9yJ3MgcGVyc3BlY3RpdmUgcmF0aGVyIHRoYW4gdGhlIGVjb25vbWV0cmljaWFuJ3MgYWxvbmUuIFdlIGhvcGUgdGhhdCB0aGlzIHZpZ25ldHRlIHdvdWxkIGVuY291cmFnZSBmdXJ0aGVyIHJlc2VhcmNoIGFuZCByZXByb2R1Y2liaWxpdHkuCgoKIyMgR2V0dGluZyB0aGUgRGF0YQpXZSByZWZlciB0byB0aGUgYHF1YW50bW9kYCBwYWNrYWdlIHRvIGRvd25sb2FkIGRhdGEgZnJvbSBZYWhvbyBGaW5hbmNlLiBJbiBwYXJ0aWN1bGFyLCB3ZSBmb2N1cyBvbiBhIG51bWJlciBvZiBtYXJrZXQgaW5kaWNhdG9yczogdGhlIFNQWSBFVEZzIHdoaWNoIHRyYWNrcyB0aGUgU1wmUCA1MDAgaW5kZXgsIHRoZSBWSVggaW5kZXgga25vd24gYXMgdGhlIGZlYXIgZ2F1Z2Ugd2hpY2ggaW5kaWNhdGVzIHRoZSBpbnZlc3RvcnMgdmlldyBhYm91dCB0aGUgbWFya2V0IHZvbGF0aWxpdHksIHRoZSBHTEQgZ29sZCBFVEYsIHRoZSA3LTEwIHllYXJzIHRyZWFzdXJ5IGJvbmQgRVRGLCBhbmQgWExGIHRoZSBmaW5hbmNpYWwgc2VjdG9yIEVURi4gTm90ZSB0aGF0IGFsbCBvZiB3aGljaCBhcmUgdHJhZGFibGUgZXhjZXB0IGZvciB0aGUgVklYLiBXaGlsZSBWSVggY2FuIGJlIHRyYWRlZCB1c2luZyBhbiBleGNoYW5nZSB0cmFkZWQgbm90ZSwgb3VyIGFuYWx5c2lzIGhlcmUgZG9lcyBub3QgaW5jbHVkZSBpdHMgdm9sdW1lLiBBZGRpdGlvbmFsbHksIHRoZSBWWFggYmVjYW1lIGF2YWlsYWJsZSBpbiBlYXJseSAyMDA5LCB0aGUgaW5jbHVzaW9uIG9mIHdoaWNoIHdvdWxkIGxpbWl0IHRoZSBzYW1wbGUgcGVyaW9kLgoKVGhlIGltcGxlbWVudGF0aW9uIGlzIGNvbmR1Y3RlZCBtYWlubHkgdXNpbmcgUi4gSW4gcGFydGljdWxhciwgd2UgcmVmZXIgdG8gYSBudW1iZXIgb2YgUiBwYWNrYWdlcy4gV2UgcmVseSBvbiB0aGUgYHF1YW50bW9kYCBwYWNrYWdlIHRvIGRvd25sb2FkIG1hcmtldCBkYXRhIGZyb20gWWFob28gRmluYW5jZSwgdGhlIGBsdWJyaWRhdGVgIGxpYnJhcnkgdG8gbWFuaXB1bGF0ZSBkYXRlIGZvcm1hdHMsIGBwbHlyYCBmb3IgZ2VuZXJhbCBkYXRhIG1hbmlwdWxhdGlvbiwgdGhlIGBnbG1uZXRgIGZvciB0cmFpbmluZyBhbmQgaW1wbGVtZW50YXRpb24gb2YgdGhlIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtLCB0aGUgYFBlcmZvcm1hbmNlQW5hbHl0aWNzYCBmb3IgZmluYW5jaWFsIGFuYWx5dGljcy9zdW1tYXJ5LCBhbmQgYm90aCBgZ2dwbG90MmAgYW5kIGBwbG90bHlgIGZvciAgdmlzdWFsaXphdGlvbnMuIApgYGB7cix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkocXVhbnRtb2QpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KHBseXIpCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KFBlcmZvcm1hbmNlQW5hbHl0aWNzKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHBhcmFsbGVsKQoKcm0obGlzdCA9IGxzKCkpCgp0MSA8LSAiMTk5MC0wMS0wMSIKdiA8LSBjKCJTUFkiLCJHTEQiLCJJRUYiLCJYTEYiKQpQLmxpc3QgPC0gbGFwcGx5KHYsIGZ1bmN0aW9uKHN5bSkgZ2V0KGdldFN5bWJvbHMoc3ltLGZyb20gPSB0MSkpICkKZ2V0U3ltYm9scygiXlZJWCIsZnJvbSA9IHQxKQpQLmxpc3QgPC0gYyhQLmxpc3QsbGlzdChWSVgpKQpgYGAKVGhlIGBQLmxpc3RgIHJldHVybnMgYSBsaXN0IG9mIGRhdGEgZm9yIGVhY2ggc3ltYm9sIGNvbnRhaW5pbmcgNiBjb2x1bW5zLiBGb3IgZWFjaCBpdGVtLCB0aGVyZSBhcmUgZGlmZmVyZW50IG51bWJlciBvZiBvYnNlcnZhdGlvbnMuIFRoZSByZWFzb24gb2Ygd2hpY2ggaXMgdGhhdCBlYWNoIEVURi9pbmRleCBkYXRlcyB0byBhIGRpZmZlcmVudCB0aW1lIHBlcmlvZC4KCmBgYHtyfQpzYXBwbHkoUC5saXN0LGRpbSkKYGBgClRvIHNlZSB0aGUgc3RhcnRpbmcgZGF0ZSBvZiBlYWNoLCB3ZSBjYW4gcnVuIHRoZSBmb2xsb3dpbmcgY29tbWFuZDoKYGBge3J9CmxhcHBseShQLmxpc3QsIGZ1bmN0aW9uKHgpICBmaXJzdChkYXRlKHgpKSApCmBgYApXZSBub3RlIHRoYXQgdGhlIFNQWSBFVEYgaGFzIHRoZSBlYXJsaWVzdCBpbmNlcHRpb24gZGF0ZSwgd2hlcmVhcyB0aGUgR0xEIGhhcyB0aGUgbGF0ZXN0IG9uZS4gCgojIyMgRmVhdHVyZSBTcGFjZQpUbyBjb25zdHJ1Y3QgdGhlIG1haW4gZmVhdHVyZSBzcGFjZSwgd2UgZm9jdXMgb24gdGhlIGFkanVzdGVkIHByaWNlcyAoc2l4dGggY29sdW1uKSBhbG9uZyB3aXRoIHZvbHVtZSAoZmlmdGggY29sdW1uKS4gQXMgbWVudGlvbmVkIGFib3ZlLCBub3RlIHRoYXQgdm9sdW1lIGlzIGF2YWlsYWJsZSBmb3IgZWFjaCBzeW1ib2wgZXhjZXB0IHRoZSBWSVggc2luY2UgaXQgaXMgbm90IHRyYWRhYmxlLiAKYGBge3J9ClAubGlzdDUgPC0gbGFwcGx5KFAubGlzdCwgZnVuY3Rpb24oeCkgeFssNV0pClAubGlzdDYgPC0gbGFwcGx5KFAubGlzdCwgZnVuY3Rpb24oeCkgeFssNl0pCmBgYApTaW5jZSB3ZSBhcmUgZGVhbGluZyB3aXRoIGB4dHNgIGFuZCBgem9vYCBvYmplY3RzLCBpdCBpcyBzdHJhaWdodGZvcndhcmQgdG8gbWVyZ2UgdGhlIHRpbWUgc2VyaWVzIGFsb25nOgpgYGB7cn0KUDUgPC0gbmEub21pdChSZWR1Y2UoZnVuY3Rpb24oLi4uKSBtZXJnZSguLi4pLFAubGlzdDUgKSkKUDYgPC0gbmEub21pdChSZWR1Y2UoZnVuY3Rpb24oLi4uKSBtZXJnZSguLi4pLFAubGlzdDYgKSkKCiMgYWRqdXN0IG5hbWVzCm5hbWVzKFA1KSA8LSBuYW1lcyhQNikgPC0gYygiU1BZIiwiR0xEIiwiSUVGIiwiWExGIiwiVklYIikKbmFtZXMoUDUpIDwtIHBhc3RlKG5hbWVzKFA1KSwidm9sIixzZXAgPSAiXyIpCnN1bW1hcnkoUDUkVklYX3ZvbCkKUDUkVklYX3ZvbCA8LSBOVUxMCmBgYAoKRm9yIGVhY2ggYWRqdXN0ZWQgcHJpY2UsIHdlIGNvbXB1dGUgdGhlIHJldHVybnMuIEluIGFkZGl0aW9uLCB3ZSBjb21wdXRlIHRoZSBtb3ZpbmcgYXZlcmFnZSBvZiBlYWNoIHJldHVybi9jaGFuZ2Ugb3ZlciB0aGUgbGFzdCAyNSBkYXlzLiBPdXIgZmVhdHVyZSBzcGFjZSBtYWlubHkgY29uc3RpdHV0ZXMgb2YgdGhlIGRhaWx5IHJldHVybiBvZiBlYWNoIHN5bWJvbCwgdGhlIGRldmlhdGlvbiBvZiBlYWNoIGZyb20gaXRzIE1BLCBhbmQgdGhlIGRhaWx5IHZvbHVtZSBvZiBlYWNoIEVURi4gTm90ZSB0aGF0IGBSNl9yb2xsYCBpcyBhIHRlY2huaWNhbCBhbmFseXNpcyB0b29sIHRoYXQgZGVtb25zdHJhdGVzIHdoZXRoZXIgdGhlIHJldHVybnMgdG9kYXkgYXJlIHRvbyBoaWdoL2xvdyB3aXRoIHJlc3BlY3QgdG8gdGhlIGNvcnJlc3BvbmRpbmcgTUEuIFdlIHN0YWNrIHRoZSBmZWF0dXJlIHNwYWNlIGluIHRoZSBgUmAgb2JqZWN0LiBUaGUgdmFyaWFibGUgb2YgaW50ZXJlc3Qgd2UgYXJlIHRyeWluZyB0byBtb2RlbCBpcyB0aGUgbmV4dCBkYXkgY2hhbmdlIGluIHRoZSBTUFkgcHJpY2UKYGBge3J9ClI2IDwtIFJldHVybi5jYWxjdWxhdGUoUDYpCiMgYWRkIHJvbGxpbmcgZGlmZmVyZW5jZSAKUjZfcm9sbCA8LSBSNiAtIHJvbGxhcHBseShSNiwyNSxtZWFuKQpuYW1lcyhSNl9yb2xsKSA8LSBwYXN0ZShuYW1lcyhSNl9yb2xsKSwiX3JvbGwiLHNlcD0iIikKClIgPC0gbmEub21pdChtZXJnZShSNixSNl9yb2xsLFA1KSkKClNQWV9uZXh0IDwtIHN0YXRzOjpsYWcoUiRTUFksLTEpCm5hbWVzKFNQWV9uZXh0KSA8LSAiU1BZX25leHQiClIgPC0gbmEub21pdChtZXJnZShTUFlfbmV4dCxSKSkKYGBgCkZpbmFsbHksIHRoZSBkYXRhIHJhbmdlcyBiZXR3ZWVuCmBgYHtyfQpyYW5nZShkYXRlKFIpKQpgYGAKCgoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGNvbnRlbXBvcmFuZW91cyBjb3JyZWxhdGlvbiBvZiBlYWNoIGZlYXR1cmUgd2l0aCB0aGUgU1BZIHJldHVybgpgYGB7cn0KY29yKFIpWywiU1BZIl0KYGBgCiBXZSBvYnNlcnZlIHRoYXQgdGhlIFZJWCBpcyBoaWdobHkgbmVnYXRpdmVseSBjb3JyZWxhdGVkIHdpdGggdGhlIHNhbWUgZGF5IFNQWSByZXR1cm4uIFRoZSBzYW1lIGhvbGRzIHRydWUgZm9yIElFRiwgd2l0aCAtNDFcJSBjb3JyZWxhdGlvbi4gV2UgYWxzbyBub3RlIHRoYXQgdGhlcmUgaXMgYSBoaWdoIHBvc2l0aXZlIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIFNQWSBhbmQgWExGLiBTaW5jZSwgdGhlIGBTUFlfcm9sbGAgaXMgYSBmdW5jdGlvbiBvZiB0aGUgc2FtZSBkYXkgcmV0dXJuLCBpdCBleGhpYml0cyBoaWdoIGNvcnJlbGF0aW9uLiBGb3IgdGhlIHZvbHVtZSwgd2Ugb2JzZXJ2ZSBhIHdlYWsgbmVnYXRpdmUgY29ycmVsYXRpb24uICBOb25ldGhlbGVzcywgd2hhdCBtYXR0ZXJzIHRoZSBtb3JlIGlzIGhvdyB0aGUgZmVhdHVyZSBzcGFjZSBjb3JyZWxhdGVzIHdpdGggdGhlIG5leHQgZGF5IFNQWSByZXR1cm4gdGhhbiB0aGUgc2FtZSByZXR1cm4uIFRoZSBmb2xsb3dpbmcgY29tbWFuZCBwcm92aWRlcyB1cyBhIHBlcnNwZWN0aXZlIG9uIHN1Y2g6CmBgYHtyfQpjb3IoUilbLCJTUFlfbmV4dCJdCmBgYAoKTWFpbmx5LCB0aGUgbWFya2V0IHNob3dzIGEgcmV2ZXJzYWwgYmVoYXZpb3IsIHdoZXJlIHRvZGF5J3MgcmV0dXJuIGhhcyBhIG5lZ2F0aXZlIDhcJSBjb3JyZWxhdGlvbiB3aXRoIG5leHQgZGF5LiBUaGlzIGlzIGFsc28gZXZpZGVudCBmb3IgdGhlIG90aGVyIGluZGljYXRvcnMuIE92ZXJhbGwsIHdlIHdpdG5lc3MgYSB3ZWFrZXIgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgbmV4dCBkYXkgcmV0dXJuIGFuZCB0aGUgZmVhdHVyZSBzcGFjZS4gT2J2aW91c2x5LCBwcmVkaWN0aW5nIHRoZSBuZXh0IGRheSByZXR1cm4gYXMgYSBtdWNoIG1vcmUgY2hhbGxlbmdpbmcgdGFzay4gCgojIyMgUmVzcG9uc2UgVmFyaWFibGUKUmF0aGVyIHRoYW4gZm9jdXNpbmcgb24gdGhlIG5leHQgZGF5IHJldHVybiwgd2UgcmVsYXRlIHRvIHRoZSBjaGFuZ2UgaW4gdGhlIFNQWSBwcmljZSBhcyB0aGUgcmVzcG9uc2UgdmFyaWFibGUgd2l0aCB0d28gbGV2ZWxzOiBkb3duICgtMSkgYW5kIHVwICgrMSkuIFNwZWNpZmljYWxseSwgd2UgZGVmaW5lCmBgYHtyfQpSJENIQU5HRV9uZXh0IDwtIDEKUiRDSEFOR0VfbmV4dFtSJFNQWV9uZXh0IDwgLTAuMDFdIDwtIC0xCnRhYmxlKFIkQ0hBTkdFKQoKIyBzdGFjayBpbnRvIGEgZGF0YXNldCByYXRoZXIgdGhhbiBhbiB4dHMgb2JqZWN0CmRzIDwtIGRhdGEuZnJhbWUoZGF0ZSA9IGRhdGUoUiksUikKcm93bmFtZXMoZHMpIDwtIE5VTEwKZHMkU1BZX25leHQgPC0gTlVMTCAjIGRyb3AgdGhlIG5leHQgZGF5IHJldHVybgoKIyBkZWZpbmUgZmVhdHVyZXMKZmVhdHVyZXMgPC0gbmFtZXMoZHMpWyFuYW1lcyhkcykgJWluJSBjKCJkYXRlIiwiQ0hBTkdFX25leHQiKV0KYGBgCk1haW5seSB0aGUgU1BZIGV4aGliaXRzIDEyXCUgb2YgdGhlIHRpbWUgYSBkYWlseSBkcm9wIHRoYXQgaXMgbGVzcyB0aGFuIC0xXCUuICBUbyBzZWUgd2hldGhlciB0aGVyZSBpcyBhbnkgaGV0ZXJvZ2VuZWl0eSBhY3Jvc3MgdGhlIGZlYXR1cmUgc3BhY2UgZm9yIGVhY2ggbGV2ZWwsIGxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBhdmVyYWdlIG9mIHRoZSBmZWF0dXJlIHNwYWNlIHdpdGggcmVzcGVjdCB0byBlYWNoIGxldmVsOiAKYGBge3J9CnN1bV9jaGFuZ2UgPC0gZGxwbHkoZHMsIkNIQU5HRV9uZXh0IixmdW5jdGlvbih4KSAgYXBwbHkoeFssZmVhdHVyZXNdLDIsbWVhbikgKQooc3VtX2NoYW5nZVtbMl1dIC0gc3VtX2NoYW5nZVtbMV1dKS9hYnMoc3VtX2NoYW5nZVtbMV1dKQpgYGAKCgpSZWxhdGl2ZWx5LCB3ZSBvYnNlcnZlIHRoYXQgdGhlIGFib3ZlIGZlYXR1cmUgc3BhY2UgcHJvdmlkZXMgc29tZSBkaXNjcmVwYW5jeSBiZXR3ZWVuIHRoZSB0d28gbGV2ZWxzLiBGb3IgaW5zdGFuY2UsIHRoZSBwcmV2aW91cyBkYXkgcmV0dXJuIHRlbmRzIHRvIGJlIGxhcmdlciB3aGVuIHRoZSBtYXJrZXQgZ29lcyB1cC4gVGhlIG9wcG9zaXRlIGlzIHRydWUgZm9yIGdvbGQgYW5kIGJvbmRzLiAKCiMjIE1hY2hpbmUgTGVhcm5pbmcgQXBwbGljYXRpb24KQWZ0ZXIgZGVmaW5pbmcgdGhlIGZlYXR1cmUgc3BhY2UgYW5kIHRoZSByZXNwb25zZSB2YXJpYWJsZSwgd2UgbmVlZCB0byBwcmVkaWN0IHRoZSBwcm9iYWJpbGl0eSBvZiB0aGUgbWFya2V0IGdvaW5nIHVwIG9yIGRvd24gb24gYSBkYWlseSBiYXNpcy4gR2l2ZW4gdGhpcyBwcm9iYWJpbGl0eSB3ZSwgZXZlbnR1YWxseSwgd2lsbCBjb25zdHJ1Y3Qgb3VyIHRhY3RpY2FsIGFzc2V0IGFsbG9jYXRpb24gc3RyYXRlZ3kuIAoKVG8gZ2V0IHN0YXJ0ZWQsIHdlIGZpdCBhIGJpbm9taWFsIG1vZGVsIHdpdGggYW4gZWxhc3RpYyBwZW5hbHR5IG9uIHdlZWtseSBiYXNpcyB0byBmaW5kIHRoZSBvcHRpbWFsIHdlaWdodHMgdG8gbWFwIHRoZSBmZWF0dXJlIHNwYWNlIGludG8gdGhlIG5leHQgZGF5IGNoYW5nZS4gU2luY2Ugd2UgdXNlIGFuIGVsYXN0aWMgbmV0LCBpdCBjb21iaW5lcyBiZXR3ZWVuIHR3byBwZW5hbHRpZXMuIFRoZSBmaXJzdCBpcyB0aGUgTEFTU08gd2hpY2ggYWN0cyBhcyBhIGNvbnN0cmFpbnQgb24gdGhlIGZpcnN0IG5vcm0gb2YgdGhlIHdlaWdodHMgYW5kIHNlcnZlcyBhcyBhbiBlbGltaW5hdGlvbiBwcm9jZXNzLiBUaGUgIHNlY29uZCBpcyB0aGUgcmlkZ2UgcmVncmVzc2lvbiB3aGljaCBpcyBhIGNvbnN0cmFpbnQgb24gdGhlIHNlY29uZCBub3JtIGFuZCBzZXJ2ZXMgYXMgYSBzaHJpbmthZ2UgYXBwcm9hY2ggdG93YXJkIHplcm8uIEJ5IGRlc2lnbiwgICRcYWxwaGE9MC41JCBhY2NvcmRpbmcgdG8gdGhlIGBnbG1uZXRgIHBhY2thZ2UgYWN0cyBhcyBpbi1iZXR3ZWVuIHBlbmFsdHkgb2YgdGhlIHR3byBhcHByb2FjaGVzLiBUaGUgTGFncmFuZ2lhbiBvciB0aGUgbWFnbml0dWRlIGF0dHJpYnV0ZWQgdG8gdGhlIHBlbmFsdHkgb2YgZWFjaCBjb25zdHJhaW50LCBkZW5vdGVkIGJ5ICRcbGFtYmRhJCwgaXMgZGV0ZXJtaW5lZCB1c2luZyAxMCBmb2xkcyBjcm9zcyB2YWxpZGF0aW9uIChoZW5jZWZvcnRoIENWKS4gCgpJbiB0aGUgY29kZSBiZWxvdywgd2UgcnVuIGEgbG9vcCBpbiB3aGljaCB3ZSB0cmFpbiB0aGUgbW9kZWwgdXNpbmcgNTAgd2Vla3Mgb2YgaGlzdG9yeSBhbmQgcHJlZGljdCB0aGUgbmV4dCBkYXkgY2hhbmdlIHVzaW5nIHRoZSBmb2xsb3dpbmcgd2Vlay4gR2l2ZW4gdGhlIDUwIHdlZWtzIG9mIHRyYWluaW5nIHNldCwgd2UgZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIG1vZGVsIHVzaW5nIDEwIGZvbGRzIENWLiBHaXZlbiB0aGUgdGVzdCBzZXQsIHdlIHByZWRpY3QgdGhlIHByb2JhYmlsaXR5IG9mIHRoZSBTUFkgZ29pbmcgZWl0aGVyIHVwIG9yIGRvd24uIFRvIGF2b2lkIGRhdGEgbGVha2FnZSBpbnRvIHRoZSB0ZXN0IHNldCwgd2UgZHJvcCB0aGUgbGFzdCBvYnNlcnZhdGlvbiBpbiB0aGUgdHJhaW5pbmcgc2V0LCBhcyBpdCBtYXkgY29udGFpbiBrbm93bGVkZ2UgYWJvdXQgdGhlIGZvbGxvd2luZyB3ZWVrIHByaWNlIGNoYW5nZS4KCmBgYHtyLHdhcm5pbmc9RkFMU0V9CndlZWtzIDwtIGRhdGUodW5pcXVlKGZsb29yX2RhdGUoZHMkZGF0ZSwid2VlayIpKSkKd2Vla3MgPC0gYyh3ZWVrcywgbGFzdCh3ZWVrcykgKyB3ZWVrcygxKSkKClcgPC0gNTAKYWwgPC0gMC41ICMgbmV0IGVsYXN0aWMKZHNfcHJlZGljdCA8LSBkYXRhLmZyYW1lKCkKZHNfYmV0YSA8LSBsaXN0KCkKCndfc2VxIDwtIFc6KGxlbmd0aCh3ZWVrcyktMikKCmRzX3ByZWRpY3RfZiA8LSBmdW5jdGlvbih3KSB7CiAgI2NhdCgiVGhpcyBpcyB3ZWVrICIsdywgIiBvdXQgb2YgIixsZW5ndGgod2Vla3MpLCJcbiIpCiAgIyB0cmFpbmluZyBzZXQgY29uc2lzdHMgb2YgcmVsYXRpdmVseSAyNTAgZGFpbHkgb2JzZXJ2YXRpb25zCiAgdHJhaW4ud2Vla3MgPC0gd2Vla3NbKHctVysxKToodysxKV0KICB0cmFpbi5pbmRleCA8LSB3aGljaCgoZHMkZGF0ZSA+IHRyYWluLndlZWtzWzFdKSAmIChkcyRkYXRlIDw9IHRyYWluLndlZWtzW1crMV0pKQogIAogICMgdGhlIHdlZWtseSBpcyBhcm91bmQgNSBkYXlzCiAgdGVzdC53ZWVrcyA8LSAgd2Vla3NbdysxOjJdCiAgdGVzdC5pbmRleCA8LSB3aGljaCgoZHMkZGF0ZSA+IHRlc3Qud2Vla3NbMV0pICYgKGRzJGRhdGUgPCB0ZXN0LndlZWtzWzJdKSkKICAKICAjIGRyb3AgdGhlIGxhc3Qgb2JzIGZyb20gdGhlIHRyYWluIHNldCB0byBhdm9pZCBsZWFrYWdlCiAgRFMgPC0gZHNbdHJhaW4uaW5kZXhbLWxlbmd0aCh0cmFpbi5pbmRleCldLF0KICB4X3RyYWluIDwtIG1vZGVsLm1hdHJpeCggfiAuLTEsIERTWyxmZWF0dXJlc10pCiAgCiAgIyB1c2UgQ1YKICBzZXQuc2VlZCgxNykKICB0cnlfZXJyb3IgPC0gdHJ5KGxtIDwtIGN2LmdsbW5ldCh4PXhfdHJhaW4seSA9IGFzLmZhY3RvcihEUyRDSEFOR0VfbmV4dCksIGludGVyY2VwdD1GQUxTRSwKICAgICAgICAgICAgICAgICBmYW1pbHkgPSAgICJtdWx0aW5vbWlhbCIsIGFscGhhPWFsLCBuZm9sZHM9MTAscGFyYWxsZWwgPSBUKSxzaWxlbnQgPSBUUlVFKQogIAogIGkgPC0gMQogIHdoaWxlKGluaGVyaXRzKHRyeV9lcnJvciwidHJ5LWVycm9yIikpIHsKICAgICNjYXQoIkVycm9yIGluIENWIiwiXG4iKQogICAgdHJ5X2Vycm9yIDwtIHRyeShsbSA8LSBjdi5nbG1uZXQoeD14X3RyYWluLHkgPSBhcy5mYWN0b3IoRFMkQ0hBTkdFX25leHQpLAogICAgICAgICAgICAgICAgICBpbnRlcmNlcHQ9RkFMU0UsIGZhbWlseSA9ICAgIm11bHRpbm9taWFsIiwgYWxwaGE9YWwsIG5mb2xkcz0xMCwKICAgICAgICAgICAgICAgICAgcGFyYWxsZWwgPSBUKSxzaWxlbnQgPSBUUlVFKQogICAgaSA8LSBpICsgMQogICAgaWYgKGkgPT0gMTApCiAgICAgICAgbG0gPC0gbG0KICAgIH0KICAKICAjIGFzc2lnbiB0aGUgbGFtYmRhCiAgYmVzdF9sYW1iZGEgPC0gbG0kbGFtYmRhLm1pbgogIAogICMgZmluZCB0aGUgb3B0aW1hbCBtb2RlbAogIGxtLnN0YXIgPSBnbG1uZXQoeD14X3RyYWluLHkgPSBhcy5mYWN0b3IoRFMkQ0hBTkdFX25leHQpLCBpbnRlcmNlcHQ9RkFMU0UgLAogICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gICAibXVsdGlub21pYWwiLCBhbHBoYT1hbCwgbGFtYmRhID0gYmVzdF9sYW1iZGEpCiAgCiAgIyBmaXQgdGhlIHRlc3Qgc2FtcGxlCiAgRFNfdGVzdCA8LSBkc1t0ZXN0LmluZGV4LF0KICB4X3Rlc3QgPC0gbW9kZWwubWF0cml4KCB+IC4tMSwgRFNfdGVzdFssZmVhdHVyZXNdKQogIERTX3ByZWRpY3QgPC0gcHJlZGljdChsbS5zdGFyLHhfdGVzdCx0eXBlID0gInJlc3BvbnNlIikKCiAgIyBzdGFjayBpbiBkYXRhCiAgRFNfcHJlZGljdCA8LSBkYXRhLmZyYW1lKERTX3ByZWRpY3QpCiAgbmFtZXMoRFNfcHJlZGljdCkgPC0gYygiZG4iLCJ1cCIpCiAgRFNfcHJlZGljdCRkYXRlIDwtIGRzW3Rlc3QuaW5kZXgsImRhdGUiXQogICNkc19wcmVkaWN0IDwtIHJiaW5kKGRzX3ByZWRpY3QsRFNfcHJlZGljdCkKICAKICAjIGZpbmFsbHkga2VlcCB0cmFjayBvZiB0aGUgZ2xtbmV0IHJlc3VsdHMgaW4gYSBsaXN0CiAgZHNfYmV0YSA8LSBjKGRzX2JldGEsbGlzdChsbS5zdGFyKSkKICAKICBsaXN0KERTX3ByZWRpY3QgPSBEU19wcmVkaWN0LGxtX2xpc3QgPSBsaXN0KGxtLnN0YXIpKQogIH0KCm1jbGFwcGx5X2xpc3QgPC0gbWNsYXBwbHkod19zZXEsZHNfcHJlZGljdF9mLG1jLmNvcmVzID0gZGV0ZWN0Q29yZXMoKSkKCmRzX3ByZWRpY3RfbCA8LSBsYXBwbHkobWNsYXBwbHlfbGlzdCwgZnVuY3Rpb24oeCkgeCREU19wcmVkaWN0ICkKZHNfcHJlZGljdCA8LSBsZHBseShkc19wcmVkaWN0X2wsZGF0YS5mcmFtZSkKaGVhZChkc19wcmVkaWN0KQpgYGAKClRoZSBhYm92ZSBjb2RlIHN0YWNrcyB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgaW4gYSBkYXRhIGZyYW1lIG9iamVjdCBuYW1lZCBgZHNfcHJlZGljdGAuIEZyb20gZWFjaCBpdGVyYXRpb24sIHdlIGtlZXAgdHJhY2sgb2YgdGhlIG9wdGltYWwgbW9kZWwgd2VpZ2h0cywgd2hpY2ggYWxsb3dzIHVzIHRvIGludmVzdGlnYXRlIHRoZSBpbXBvcnRhbnQgZmVhdHVyZXMgb3ZlciB0aW1lLiBEdWUgdG8gdGhlIHJvbGxpbmcgd2luZG93IG5hdHVyZSwgd2hpY2ggaXMgbm90IHJlY3Vyc2l2ZSBpbiB0aGlzIGNhc2UsIHdlIGNhbiByZWZlciB0byB0aGUgYG1jbGFwcGx5YCBmdW5jdGlvbiBmb3IgcGFyYWxsZWxpemF0aW9uIAoKCmBgYHtyfQpzdW1tYXJ5KGRzX3ByZWRpY3QpCmBgYAoKIyMjIFNpZ25hbCBFeHRyYWN0aW9uCldlIG1lcmdlIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyB3aXRoIHRoZSBvcmlnaW5hbCBkYXRhIHRvIGNvbnN0cnVjdCB0aGUgbWFpbiBkYXRhc2hlZXQsIHdoaWNoIHdlIHdpbGwgcmVmZXIgdG8gZm9yIHRoZSB0cmFkaW5nIHN0cmF0ZWd5IGFuZCwgaGVuY2UsIHRoZSBiYWNrLXRlc3RpbmcuIApgYGB7cn0KZHMyIDwtIG1lcmdlKGRzLGRzX3ByZWRpY3QsYnkgPSAiZGF0ZSIpCnVwZG4gPC0gZGF0YS5mcmFtZShkbiA9IGRzMiRkbix1cCA9ZHMyJHVwKQpyb3duYW1lcyh1cGRuKSA8LSBkczIkZGF0ZQp1cGRuIDwtIGFzLnh0cyh1cGRuKQp1cGRuX3JvbGwgPC0gbmEub21pdChyb2xsYXBwbHkodXBkbiwyNSxtZWFuKSkKbmFtZXModXBkbl9yb2xsKSA8LSBjKCJkbl9yb2xsIiwidXBfcm9sbCIpCmRzLnVwZG4gPC0gZGF0YS5mcmFtZShkYXRlID0gZGF0ZSh1cGRuX3JvbGwpLHVwZG5fcm9sbCkKcm93bmFtZXMoZHMudXBkbikgPC0gTlVMTApkczMgPC0gbWVyZ2UoZHMyLGRzLnVwZG4sIGJ5ID0gImRhdGUiKQpgYGAKClRvIGNyZWF0ZSBhIHNtb290aCBzaWduYWwsIHdlIGNvbnNpZGVyIHRoZSAyNSBkYXlzIG1vdmluZyBhdmVyYWdlIG9mIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkuIFdlIG1lcmdlIHRoZSBzbW9vdGhlZCBwcm9iYWJpbGl0aWVzIGFsb25nIHdpdGggdGhlIEVURiByZXR1cm5zIGluIGEgIG5ldyBkYXRhIGZyYW1lIG5hbWVkIGBkczNgLgoKVG8gYWdhaW4gYW4gaW5pdGlhbCBwZXJzcGVjdGl2ZSBvbiB0aGUgZXh0cmFjdGVkIHNpZ25hbCwgd2UgcGxvdCB0aGUgY3VtdWxhdGl2ZSByZXR1cm4gb24gdGhlIFNQWSBhbG9uZyB3aXRoIHRoZSBwcm9iYWJpbGl0eSBnb2luZyBkb3duIG92ZXIgdGltZS4gSW50ZXJlc3RpbmdseSwgd2Ugb2JzZXJ2ZSB0aGF0IHRoZSBzaWduYWwgKHJlZCBsaW5lKSwgd2hpY2ggaXMgZGF0YSBkcml2ZW4sIHJlYWNoZXMgdGhlIGhpZ2hlc3QgbGV2ZWwgZHVyaW5nIHRoZSAyMDA3LTA5IGZpbmFuY2lhbCBjcmlzaXMsIGR1cmluZyB3aGljaCB0aGUgU1BZIChibGFjayBsaW5lKSBleGhpYml0cyB0aGUgbGFyZ2VzdCBvdmVyIHRoZSB3aG9sZSBzYW1wbGUuIEFkZGl0aW9uYWxseSwgd2hpbGUgdGhlIHByb2JhYmlsaXR5IHNlZW1zIGRlY2xpbmluZyB0aGUgbGFzdCBjb3VwbGUgb2YgeWVhciwgd2Ugb2JzZXJ2ZSB0aGF0IGl0IHBpY2tzIHVwIGR1cmluZyB0aGUgcmVjZW50IG1hcmtldCB0dXJtb2lscyBpbiBsYXRlIDIwMTguIE92ZXJhbGwsIHdlIG9ic2VydmUgYSBuZWdhdGl2ZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgdHdvLgoKYGBge3IsIGZpZy5hbGlnbj0iY2VudGVyIn0KcGxvdChjdW1zdW0oU1BZKSB+IGRhdGUsIGRhdGEgPSBkczMsIHR5cGUgPSAibCIseWxhYiA9ICIiLCBtYWluICA9ICJjdW11bGF0aXZlIHJldHVybiBvZiBTUFkgdmVyc3VzIHByb2JhYmlsaXR5IGRvd24iKQpsaW5lcyhkbl9yb2xsIH4gZGF0ZSwgZGF0YSA9IGRzMyxjb2wgPSAyKQphYmxpbmUoaCA9IDAsIGx0eT0yKQpsZWdlbmQoInRvcGxlZnQiLGMoIlNQWSIsIlByb2JhYmlsaXR5IERvd24iKSxjb2wgPSAxOjIsIGx0eSA9IDEpCmdyaWQoMTApCmBgYApUaGUgYWJvdmUgc3VnZ2VzdHMgdGhhdCB0aGUgcHJvYmFiaWxpdHkgb2YgZG93bi91cCBjYW4gYmUgdXRpbGl6ZSB0byBhdm9pZCBzaWduaWZpY2FudCBtYXJrZXQgZHJvcHMuIFRoZSBxdWVzdGlvbiBpcyBob3cgY2FuIHdlIHV0aWxpemUgdGhpcz8gSW4gdGhlIGZvbGxvd2luZyBzZWN0aW9uLCB3ZSBwcm9wb3NlIGFuIGF1dG9tYXRlZCB0cmFkaW5nIHN0cmF0ZWd5IHRoYXQgdHJpZXMgdG8gbWF0ZXJpYWxpemUgdGhlIGFib3ZlIGluc2lnaHRzLgoKIyMgVGFjdGljYWwgQXNzZXQgQWxsb2NhdGlvbiBTdHJhdGVneSAKSW4gdGhpcyBzZWN0aW9uLCB3ZSBkaXNjdXNzIGhvdyB3ZSBpbXBsZW1lbnQgdGhlIHRhY3RpY2FsIGFsbG9jYXRpb24gc3RyYXRlZ3kgdXNpbmcgdGhlIGFib3ZlIGV4dHJhY3RlZCBzaWduYWwuIEluIG9yZGVyIHRvIGRlbW9uc3RyYXRlIHRoaXMsIHdlIHJlZmVyIHRvIHNvbWUgbWF0aGVtYXRpY2FsIG5vdGF0aW9uLiBMZXQgJFxoYXR7XHBpfV97dCsxfSQgZGVub3RlIHRoZSBmb3JlY2FzdGVkIHByb2JhYmlsaXR5IHRoYXQgdGhlIFNQWSBhdCAkdCsxJCB3aWxsIGdvIHVwLiBUaGUgaGF0ICgkXGhhdHtcY2RvdH0kKSBpbXBsaWVzIHRoYXQgdGhlIHByb2JhYmlsaXR5IGlzIGEgZm9yZWNhc3QgZXN0YWJsaXNoZWQgYXQgdGltZSAkdCQgcmF0aGVyIHRoYW4gJHQrMSQuIERlcGVuZGluZyBvbiB0aGUgaW50ZW5zaXR5IG9mIHNpZ25hbCwgd2UgY3JlYXRlIGEgcG9ydGZvbGlvICB0aGF0IGVpdGhlciBsb25ncyB0aGUgU1BZIG9yIHRoZSBJRUYuIFRvIHB1dCBmb3JtYWxseSwgdGhlIHJldHVybiBvZiB0aGUgc3RyYXRlZ3kgYXQgdGltZSAkdCsxJCBpcyBnaXZlbiBieQogXGJlZ2lue2VxdWF0aW9ufQpyX3tzLHQrMX0gPSAgSV97XGxlZnRbIFxoYXR7XHBpfV97dCsxfSA+IGEgXHJpZ2h0XX0gcl57ZX1fe3QrMX0gKyAgSV97XGxlZnRbIFxoYXR7XHBpfV97dCsxfSBcbGVxIGEgXHJpZ2h0XX0gcl57Yn1fe3QrMX0KXGVuZHtlcXVhdGlvbn0Kd2hlcmUgJHJee2V9X3t0KzF9JCBhbmQgICRyXntifV97dCsxfSQgZGVub3RlIHRoZSByZXR1cm4gb24gU1BZIGFuZCBJRUYgYXQgJHQrMSQsIHJlc3BlY3RpdmVseS4gVGhlIHBhcmFtZXRlciAkYSQgaXMgYSBwcmVkZXRlcm1pbmVkIG1pbmltdW0gbGV2ZWwgb2YgY29uZmlkZW5jZSBmb3IgdGhlICBpbnZlc3RvciB0byBwYXJ0aWNpcGF0ZSBpbiB0aGUgZXF1aXR5IG1hcmtldC4gVGhlIHN0cmF0ZWd5IGltcGxpZXMgdGhhdCB0aGUgaW52ZXN0b3IgZ29lcyBsb25nIGluIHRoZSBlcXVpdHkgYWxvbmUsIGlmIGFuZCBvbmx5IGlmIHRoZSAgY29uZmlkZW5jZSBsZXZlbCB0aGF0IHRoZSBtYXJrZXQgd2lsbCBnbyB1cCB0aGUgbmV4dCBkYXkgaXMgaGlnaCBlbm91Z2guIElmIG5vdCwgdGhlIHN0cmF0ZWd5IGdvZXMgXCQxIGluIHRoZSBJRUYgRVRGIGFsb25lLiAKCiMjIyBCYWNrdGVzdGluZwpBcyBhIGJlbmNobWFyaywgd2UgY29tcGFyZSBvdXIgcmVzdWx0cyB3aXRoIHJlc3BlY3QgdG8gYSA2MC00MCBzdHJhdGVneSB0aGF0IGludmVzdHMgNjBcJSBpbiBTUFkgYW5kIDQwXCUgaW4gSUVGLiBXaGlsZSB0aGUgYmVuY2htYXJrIGlzIHJlLWJhbGFuY2VkIG9uIGEgZGFpbHkgYmFzaXMsIGl0IGRvZXMgbm90IGluY29ycG9yYXRlIGFueSBtYXJrZXQgaW5mb3JtYXRpb24uIEluIG90aGVyIHdvcmRzLCByZWdhcmRsZXNzIG9mIHdoYXQgaGFwcGVucyBpbiB0aGUgbWFya2V0LCB0aGUgYmVuY2htYXJrIHdpbGwgYWx3YXlzIG1haW50YWluIGEgNjAtNDAgYWxsb2NhdGlvbi4gQWRkaXRpb25hbGx5LCB3ZSBjb21wYXJlIHRoZSBzdHJhdGVneSB3aXRoIGVhY2ggRVRGIGFsb25lLiBBcyBhIHBlcmZvcm1hbmNlIHN1bW1hcnksIHdlIGNyZWF0ZSBhIHN1bW1hcnkgZnVuY3Rpb24gdGhhdCB0YWtlcyAkYSQgYXMgdGhlIG1haW4gaW5wdXQgYW5kIHJlcG9ydHMgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBzdHJhdGVneSB3aXRoIHJlc3BlY3QgdG8gZWFjaC4gCmBgYHtyLCB3YXJuaW5nPUZ9Cm5leHRfZiA8LSBmdW5jdGlvbih4KSBjKHhbLTFdLE5BKQpkczMkSUVGX25leHQgPC0gbmV4dF9mKGRzMyRJRUYpCmRzMyRTUFlfbmV4dCA8LSBuZXh0X2YoZHMzJFNQWSkKCmRzMyRCRU5DSE1BUksgPC0gd2l0aChkczMsMC42KlNQWV9uZXh0ICsgMC40KklFRl9uZXh0KQoKcGxvdF9wZXJmb3JtYW5jZSA8LSBmdW5jdGlvbihhKSB7CiAgCiAgIyByb3VuZCB0aGUgcHJvYmFiaWxpdGllcyB0byBuZWFyZXN0IDAuMDUKICBkczMkdXBfcm9sbCA8LSByb3VuZChkczMkdXBfcm9sbC8wLjA1KSowLjA1CiAgZHMzJGRuX3JvbGwgPC0gcm91bmQoZHMzJGRuX3JvbGwvMC4wNSkqMC4wNQoKCiAgZHMzJFBPUlQgPC0gd2l0aChkczMsICh1cF9yb2xsID49IGEpKihTUFlfbmV4dCkgICsgICh1cF9yb2xsIDwgYSkqSUVGX25leHQgKQogIAogIAogICMgbG9hZCBkczMgaW50byBhIGdncGxvdCBmcmllbmRseSBkYXRhCiAgZHNfcGxvdCA8LSBkYXRhLmZyYW1lKERhdGUgPSBkczMkZGF0ZSwgcmV0dXJuID0gY3Vtc3VtKGRzMyRQT1JUKSwgVHlwZSA9ICJTdHJhdGVneSIpCiAgZHNfcGxvdCA8LSByYmluZChkc19wbG90LCAKICAgICAgICAgICAgICAgICAgIGRhdGEuZnJhbWUoRGF0ZSA9IGRzMyRkYXRlLCByZXR1cm4gPSBjdW1zdW0oZHMzJEJFTkNITUFSSyksIFR5cGUgPSAiQmVuY2htYXJrIikpCiAgZHNfcGxvdCA8LSByYmluZChkc19wbG90LCAKICAgICAgICAgICAgICAgICAgIGRhdGEuZnJhbWUoRGF0ZSA9IGRzMyRkYXRlLCByZXR1cm4gPSBjdW1zdW0oZHMzJFNQWV9uZXh0KSwgVHlwZSA9ICJTUFkiKSkKICBkc19wbG90IDwtIHJiaW5kKGRzX3Bsb3QsIAogICAgICAgICAgICAgICAgICAgZGF0YS5mcmFtZShEYXRlID0gZHMzJGRhdGUsIHJldHVybiA9IGN1bXN1bShkczMkSUVGX25leHQpLCBUeXBlID0gIklFRiIpKQogIGRzX3Bsb3QgPC0gcmJpbmQoZHNfcGxvdCwgCiAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKERhdGUgPSBkczMkZGF0ZSwgcmV0dXJuID0gZHMzJGRuX3JvbGwsIFR5cGUgPSAiUHJvYmFiaWxpdHkgRG93biIpKQogIAogIGRzX3Bsb3QgPC0gbmEub21pdChkc19wbG90KQogIAogIHAgPC0gZ2dwbG90KGRzX3Bsb3QpICsgZ2VvbV9saW5lKGFlcyh4ID0gRGF0ZSx5ID0gcmV0dXJuLGNvbG91ciA9IFR5cGUpKSAKICBwIDwtIHAgKyBnZW9tX2FibGluZShpbnRlcmNlcHQgPTAsbGluZXR5cGUgPSAiZGFzaGVkIikKICBwIDwtIGdncGxvdGx5KHAsaGVpZ2h0ID0gIDUwMCwgd2lkdGggPSA5MDApCiAgCiAgIyBhbHNvIHJldHVybiB0aGUgZGF0YSBmb3IgcGVyZm9ybWFuY2UgY29tcGFyaXNvbgogIGRzX3BlcmYgPC0gZHMzWyxjKCJJRUZfbmV4dCIsIlNQWV9uZXh0IiwiQkVOQ0hNQVJLIiwiUE9SVCIsIlNQWV9uZXh0IildCiAgcm93bmFtZXMoZHNfcGVyZikgPC0gZHMzJGRhdGUKICBkc19wZXJmIDwtIGFzLnh0cyhkc19wZXJmKQoKICBsaXN0KHBsb3RfcGVyZiA9IHAsZGF0YV9wZXJmID0gZHNfcGVyZikKfQpgYGAKCgojIyMjIElsbHVzdHJhdGlvbgpMZXQncyB0YWtlIGEgbG9vayB0aGUgcGVyZm9ybWFuY2Ugb3ZlciB0aW1lIHdoZW4gJGE9OTBcJSQuIFRoaXMgcmVwcmVzZW50cyBhIHNpdHVhdGlvbiBpbiB3aGljaCB0aGUgaW52ZXN0b3IgYWxsb2NhdGVzIGhpcyB3ZWFsdGggbWFpbmx5IHRvIGVxdWl0eSBpZiB0aGUgY29uZmlkZW5jZSBsZXZlbCB0aGF0IHRoZSBTUFkgaXMgZ29pbmcgdXAgdGhlIGZvbGxvd2luZyBkYXkgaXMgYXQgbGVhc3QgOTBcJS4gT3RoZXJ3aXNlLCB0aGUgaW52ZXN0b3IgaXMgY29uc2VydmF0aXZlIGFuZCBhbGxvY2F0ZXMgaGlzIHdlYWx0aCB0byB0aGUgVHJlYXN1cnkgRVRGIG1haW5seS4gCmBgYHtyLCBmaWcuYWxpZ249ImNlbnRlciJ9CnBlcmYxIDwtIHBsb3RfcGVyZm9ybWFuY2UoMC45KQpwZXJmMSRwbG90X3BlcmYKYGBgCkluIHRoZSBhYm92ZSBwbG90LCB3ZSBvYnNlcnZlIHRoYXQgdGhlIHByb3Bvc2VkIHN0cmF0ZWd5IGRvbWluYXRlcyB0aGUgb3RoZXIgY2FuZGlkYXRlcyBpbiB0ZXJtcyBvZiBjdW11bGF0aXZlIHJldHVybiBvdmVyIHRoZSBwZXJpb2QsIHdoaWxlIGF0IHRoZSBzYW1lIHRpbWUgdHJhaWxpbmcgdGhlIFNQWS4gTW9yZW92ZXIsIHdlIG9ic2VydmUgdGhhdCB0aGUgc3RyYXRlZ3kgYXZvaWRzIHRoZSBtYXJrZXQgY3Jhc2ggZHVyaW5nIHRoZSAyMDA3LTA5IGZpbmFuY2lhbCBjcmlzaXMsIGluZGljYXRpbmcgdGhhdCBpdCBoYXMgYSBsb3dlciBkb3duc2lkZSByaXNrIHRoYW4gdGhlIGJlbmNobWFyayBhcyB3ZWxsIGFzIHRoZSBTUFkuIEFkZGl0aW9uYWxseSwgZ2l2ZW4gdGhlIG1hcmtldCB0dXJtb2lsIGluIGxhdGUgMjAxOCwgd2Ugb2JzZXJ2ZSB0aGF0IHRoZSB0aGUgcHJvcG9zZWQgc3RyYXRlZ3kgaXMgc3VjY2Vzc2Z1bCBpbiBleGVjdXRpbmcgYSBmbGlnaHQgdG8gcXVhbGl0eSBkdXJpbmcgdGltZXMgb2YgdW5jZXJ0YWludHkuCgoKU3VwcG9zZSB0aGF0IHRoZSBpbnZlc3RvciBpcyBtb3JlIGNvbnNlcnZhdGl2ZSwgc3VjaCBoZSBvbmx5IGludmVzdHMgaW4gdGhlIFNQWSBpZiB0aGUgcHJvYmFiaWxpdHkgaXMgYXQgbGVhc3QgOTVcJS4gSW4gdGhpcyBjYXNlLCB3ZSBoYXZlCmBgYHtyLCBmaWcuYWxpZ249ImNlbnRlciJ9CnBlcmYyIDwtIHBsb3RfcGVyZm9ybWFuY2UoMC45NSkKcGVyZjIkcGxvdF9wZXJmCmBgYApXZSBub3RlIHRoYXQgdGhlIHByb3Bvc2VkIHN0cmF0ZWd5IGF2b2lkcyBib3RoIHRoZSAyMDA3LTA5IG1hcmtldCBjcmFzaCBhbmQgdGhlIHJlY2VudCBtYXJrZXQgZHJvcCBpbiBsYXRlIDIwMTguIEhvd2V2ZXIsIHdlIG9ic2VydmUgdGhhdCB0aGUgc3RyYXRlZ3kgeWllbGRzIGFsbW9zdCB0aGUgc2FtZSBjdW11bGF0aXZlIHJldHVybiBhcyB0aGUgYmVuY2htYXJrLiAKClN1cHBvc2UgdGhhdCB0aGUgaW52ZXN0b3IgaXMgbW9yZSByaXNrIHRvbGVyYW50LCBzdWNoIGhlIGlzIHdpbGxpbmcgdG8gaW52ZXN0IGluIHRoZSBTUFkgd2l0aCBhIGxldmVsIG9mIGNvbmZpZGVuY2Ugb2YgODVcJS4gSW4gdGhpcyBjYXNlLCB3ZSBvYnNlcnZlIHRoYXQgdGhlIHN0cmF0ZWd5IGRvZXMgd2VsbCBpbiB0ZXJtcyBvZiBjdW11bGF0aXZlIHJldHVybi4KYGBge3IsIGZpZy5hbGlnbj0iY2VudGVyIn0KcGVyZjMgPC0gcGxvdF9wZXJmb3JtYW5jZSgwLjg1KQpwZXJmMyRwbG90X3BlcmYKYGBgCkhvd2V2ZXIsIGF0IHRoZSBzYW1lIHRpbWUsIHdlIG9ic2VydmUgdGhhdCB0aGUgc3RyYXRlZ3kgZmFpbHMgdG8gc2hpZnQgdG8gVHJlYXN1cnkgYm9uZHMgZHVyaW5nIHRoZSByZWNlbnQgbWFya2V0IHR1cm1vaWxzLCBkcmFpbmluZyB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIHN0cmF0ZWd5IG92ZXIgdGltZS4gCgoKIyMjIFJpc2stQWRqdXN0ZWQgUmV0dXJucwpJbiB0ZXJtcyBvZiByaXNrIGFkanVzdGVkIHJldHVybnMsIHdlIGNvbnNpZGVyIGEgbnVtYmVyIG9mIHN0YXRpc3RpY3MuIFRoZSBmaXJzdCBpcyB0aGUgSmVuc2VuJ3MgYWxwaGEgdGhhdCBjYXB0dXJlcyB0aGUgYWJub3JtYWwgcmV0dXJuIGFib3ZlIHRoZSBiZW5jaG1hcmsuIFRoZSBzZWNvbmQgaXMgU29ydGlubyByYXRpbywgd2hpY2ggYWRqdXN0cyB0aGUgbWVhbiByZXR1cm4gb2YgdGhlIHN0cmF0ZWd5IHdpdGggcmVzcGVjdCB0byBhIGRvd25zaWRlIHJpc2sgKHN0YW5kYXJkIGRldmlhdGlvbikuIEFkZGl0aW9uYWxseSwgd2UgcmVwb3J0IHRoZSBiZXRhIG9mIGVhY2ggc3RyYXRlZ3kuIAoKYGBge3J9ClAxIDwtIHBlcmYxJGRhdGFfcGVyZiRQT1JUClAyIDwtIHBlcmYyJGRhdGFfcGVyZiRQT1JUClAzIDwtIHBlcmYzJGRhdGFfcGVyZiRQT1JUCkIgPC0gcGVyZjEkZGF0YV9wZXJmJEJFTkNITUFSSwpCMiA8LSBwZXJmMSRkYXRhX3BlcmYkU1BZX25leHQKCnBvcnRfYWxsIDwtIGxpc3QoUDIsUDEsUDMsQixCMikKcG9ydF9hbGwgPC0gbmEub21pdChSZWR1Y2UobWVyZ2UscG9ydF9hbGwpKQpuYW1lcyhwb3J0X2FsbClbMTozXSA8LSBjKCJQb3J0Zm9saW9fOTUiLCJQb3J0Zm9saW9fOTAiLCJQb3J0Zm9saW9fODUiKQoKCnRhYmxlLkNBUE0ocG9ydF9hbGxbLDE6M10scG9ydF9hbGxbLDRdKVtjKDYsMiksXQoKYGBgCgpJbiB0aGUgYWJvdmUgdGFibGUsIHdlIG9ic2VydmUgdGhhdCB0aGUgbW9yZSBjb25zZXJ2YXRpdmUgc3RyYXRlZ2llcyB5aWVsZCBhbiBhbm51YWwgYWxwaGEgb2YgOFwlLCBhdCB0aGUgc2FtZSB0aW1lLCB3aGlsZSBiZWluZyBiZXRhLW5ldXRyYWwgT24gdGhlIG90aGVyIGhhbmQsIHdlIG5vdGUgdGhhdCB3aGVuICRhJCBkZWNyZWFzZXMsIHRoZSBhbHBoYSBkZWNyZWFzZXMsIGFuZCB0aGUgc3RyYXRlZ3kgYmVjb21lcyBsZXNzIG1hcmtldCBiZXRhLW5ldXRyYWwuIAoKSW4gdGVybXMgb2YgZG93bnNpZGUgcmlzaywgd2UgY29tcHV0ZSB0aGUgU29ydGlubydzIHJhdGlvICBmb3IgZWFjaCBzdHJhdGVneS4gU2ltaWxhciB0byB0aGUgU2hhcnBlIHJhdGlvLCB3ZSBlc3RpbWF0ZSB0aGUgU29ydGlubydzIHJhdGlvIGFzIHRoZSByYXRpbyBiZXR3ZWVuIHRoZSBtZWFuIHJldHVybiBhbmQgdGhlIGRvd25zaWRlIHJpc2suIEJ5IGRvd25zaWRlIHJpc2ssIHdlIHJlZmVyIHRvIHRoZSBzZW1pLXN0YW5kYXJkIGRldmlhdGlvbiBpbiB3aGljaCB3ZSBlbGltaW5hdGUgdGhlIHBvc2l0aXZlIHJldHVybnMgaW4gY2FsY3VsYXRpbmcgdGhlIHZvbGF0aWxpdHkuIEZpbmFsbHksIHdlIHNjYWxlIHRoZSByYXRpbyBpbnRvIGFuIGFubnVhbCBiYXNpcyB1c2luZyBhIHNjYWxlIG9mICRcc3FydHsyNTJ9JC4gQXMgYW4gYWRkaXRpb25hbCBwZXJzcGVjdGl2ZSwgd2UgcmVwb3J0IHRoZSBTaGFycGUtcmF0aW8gZm9yIGNvbXBhcmlzb246CmBgYHtyfQpTb3J0X3JhdGlvIDwtIGZ1bmN0aW9uKHgpIHNxcnQoMjUyKSptZWFuKHgpL3NkKHhbeCA8IDBdKQpTUiA8LSAgZnVuY3Rpb24oeCkgc3FydCgyNTIpKm1lYW4oeCkvc2QoeCkKU29ydGlubyA8LSBhcHBseShwb3J0X2FsbCwyLFNvcnRfcmF0aW8pClNoYXJwZSA8LSBhcHBseShwb3J0X2FsbCwyLFNSKQpkYXRhLmZyYW1lKHJiaW5kKFNvcnRpbm8sU2hhcnBlKSkKYGBgCkluIGFsbCBjYXNlcywgZXhjZXB0IGZvciB0aGUgODVcJSBsZXZlbCBjb25maWRlbmNlLCB3ZSBvYnNlcnZlIHRoYXQgdGhlIHByb3Bvc2VkIHN0cmF0ZWd5IHlpZWxkcyBhIGhpZ2hlciByaXNrLWFkanVzdGVkIHJldHVybiB0aGFuIHRoZSBiZW5jaG1hcmsuIFRoaXMgaXMgbW9yZSBldmlkZW50IGZvciB0aGUgU29ydGlubyB0aGFuIHRoZSBTaGFycGUuIEhlbmNlLCB0aGlzIGluZGljYXRlcyB0aGF0IHRoZSBzdHJhdGVneSBvdXRwZXJmb3JtcyB0aGUgYmVuY2htYXJrIGJ5IGJlYXJpbmcgbGVzcyBkb3duc2lkZSByaXNrLCB3aGlsZSwgYXQgdGhlIHNhbWUgdGltZSwgYWNoaWV2aW5nIGEgYmV0dGVyIG9yIGNvbXBhcmFibGUgcmV0dXJuLiAgCgoKIyMgU3VtbWFyeQpUaGlzIHZpZ25ldHRlIHByb3ZpZGVzIGEgc2ltcGxlIGNvc3QtZWZmaWNpZW50IHRyYWRpbmcgc3RyYXRlZ3kgdGhhdCBkZXBsb3lzIG1hY2hpbmUgbGVhcm5pbmcgdXNpbmcgcHVibGljIGRhdGEgYW5kIG9wZW4gc291cmNlIHNvZnR3YXJlLiBXaGlsZSB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIHN0cmF0ZWd5IGRlcGVuZHMgb24gYSBjb3VwbGUgb2Ygc3BlY2lmaWNhdGlvbnMsIHN1Y2ggYXMgdGhlIGxldmVsIG9mIGNvbmZpZGVuY2UgJGEkIG9yIHdoYXQgZGV0ZXJtaW5lcyBhIHByaWNlIGNoYW5nZSBpbiB0aGUgbWFya2V0LCB0aGUgc3RyYXRlZ3kgaXMgbWFpbmx5IGRhdGEtZHJpdmVuLiBPbmUgbWF5IGNvbnNpZGVyIG90aGVyIGFwcHJvYWNoZXMgdXNpbmcgY3Jvc3MtdmFsaWRhdGlvbiB0byBkZXRlcm1pbmUgdGhlc2UgaW5wdXRzIGF1dG9tYXRpY2FsbHkuIE5vbmV0aGVsZXNzLCBhIHNpbWlsYXIgc3RyYXRlZ3kgY2FuIGJlIGFsc28gZGVwbG95ZWQgdG8gc2NyZWVuIGRpZmZlcmVudCBzdG9ja3Mgb3IgRVRGcy4gRm9yIGluc3RhbmNlLCBvbmUgbWF5IGNvbnNpZGVyIGEgc2ltaWxhciBhcHByb2FjaCB0byBwZXJmb3JtIGFzIHRhY3RpY2FsIGFzc2V0IGFsbG9jYXRpb24gYWNyb3NzIHNlY3RvciBFVEZzLiBpLmUuIHNlY3RvciByb3RhdGlvbiBzdHJhdGVneS4gV2UgbGVhdmUgdGhpcyBmb3IgZnV0dXJlIGludmVzdGlnYXRpb24uIAoKIyMgRGlzY2xhaW1lciAKKlRoZSBhYm92ZSB2aWdlbnR0ZSBpcyBhIHNob3J0IGlsbHVzdHJhdGlvbiBvZiBhbiBvbmdvaW5nIHJlc2VhcmNoIGNvLWF1dGhvcmVkIHdpdGggS3JpcyBCb3VkdCAoVnJpamUgVW5pdmVyc2l0ZWl0IEJydXNzZWwsIFVuaXZlcnNpdHkgb2YgQW1zdGVyZGFtIGFuZCBGaW52ZXgpLCBNdXphZmVyIENlbGEgKFZyaWplIFVuaXZlcnNpdGVpdCBCcnVzc2VsKSwgYW5kIE1hamVlZCBTaW1hYW4gKFN0ZXZlbnMgSW5zdGl0dXRlIG9mIFRlY2hub2xvZ3kpLiBUaGUgcmVzZWFyY2ggaXMgdGl0bGVkICJJbiBTZWFyY2ggb2YgUmV0dXJuIFByZWRpY3RhYmlsaXR5OiBFdmlkZW5jZSBmcm9tIE1hY2hpbmUgTGVhcm5pbmcgYW5kIFRhY3RpY2FsIEFsbG9jYXRpb24iLiBGb3IgZnVydGhlciBhcHBsaWNhdGlvbiwgc2VlIHRoZSBpbXBsaWNhdGlvbiBvZiB0aGUgdGhpcyBhbmFseXNpcyBpbnRvIGNvbnN0cnVjdGluZyBtYWNoaW5lIGxlYXJuaW5nIGZyb250aWVycyB2aWEgdGhpcyBbcGFwZXJdKGh0dHBzOi8vcGFwZXJzLnNzcm4uY29tL3NvbDMvcGFwZXJzLmNmbT9hYnN0cmFjdF9pZD0zNTQxMzg3KSouCgoK