1 Introduction

In this report, we will evaluate machine learning application on investing strategies. There are several ways to incorporate machine learning techniques to construct a portfolios. However, we will focus on 2 main approaches; sparse portfolio and signals.

1.1 Models

** Benchmark portfolio ** We need benchmark portfolio to evaluate our strategies. For simplicity, we use equally-weighted portfolio as our benchmark. Where it is needed, the same weighting scheme will be applied to predicted results from machine learning strategies to clearly contrast the impact of having included machine learning techniques in the strategies.

** Sparse portfolio Sparse portfolio applies machine learning algorithm to predict stock future return by using other stocks’ current returns as features. Although it is not conventional, this approach gave insightful results as discussed in the class through Lasso method that it can be used to assign weights to each stock directly. Here, we will investigate the methodology by applying elastic network**. Elastic network is a combination of Lasso and Ridge regressions. Both are regularization methods which help the model avoid overfitting problem caused by inclusion of too many features. To do this, they penalise insignificant features, meaning that they reduces beta of each feature leaving large beta’s for significant features. Nonetheless, the difference is that Ridge reduces all beta’s without kicking any of features out of the model (l2 norm), while Lasso does both (l1 norm). We decided to apply elastic network because it allows us to utilise both of their advantages; feature selection from Lasso while not kicking the feature out too fast from Ridge. Therefore, hyperparameter for this method is the degree to use l1 and l2 norms, which are alpha and alpha-1.

** Signals ** This approach is rather more conventional application of machine learning to predict future returns. We will use the features provided for each stock to predict their future returns in each period. However, to consider the distribution of the data, we also add mean, variance, and skewness of each stock.

Signal approach can be splitted into 2 sub-approaches; numerical and categorical dependent variables. As future returns are numerical variable, we can apply only some machine learning techniques. Hence, we will transform future returns into categorical variable to allow us to construct a portfolio by utilising both types of algorithm.

While still considering the sign (positive and negative returns), we will work with 3 signals; percentile, 1-period momentum, and 3-period momentum. For numerical dependent variable, we transform predicted future returns into signals before apply equally-weighted scheme. For categorical approach, we transform actual future returns into signals, then use the signal as dependent variables. Lastly, we use the predicted signals to apply equally-weighted scheme.

2 Data Preparation

2.1 Import Data

Import and preview data

load('data_full.RData')
head(data)

2.2 Data Pre-processing

In addition to the given data, we need to perform some important data-preprocessing in order to generate new variables that are essential to our analysis. Those variables include the following:

  1. P_Return (Past return): The original data only contains monthly closing price information from which we calculate past monthly return. In general, return is commonly used rather than closing price in portfolio backtesting.

  2. F_Return (Future return): Our y variable is future monthly return which can be obtained by leading past return by one period.

  3. Moments of Return (Mean, Variance, and Skewness): We decided to generate additional information including first three moments of past monthly return to maximize the benefit of the data we have.

if(!require(tidyverse)){install.packages('tidyverse')}
if(!require(PerformanceAnalytics)){install.packages('PerformanceAnalytics')}
if(!require(dplyr)){install.packages("dplyr")}
library(dplyr)
library(tidyverse)
library(PerformanceAnalytics)
load('data_full.RData')                             # Reload data
data <- data %>% arrange(Date,Tick)
tick <- levels(data$Tick) 
data <- data  %>% 
    group_by(Tick) %>%                              # Group asset by asset
    mutate(P_Return = Close / lag(Close) - 1) %>%   # Adding past returns
    mutate(F_Return = lead(P_Return)) %>%           # Adding forward returns
    na.omit() %>%                                   # Drop all NA row before calculate moments
    mutate(mean = apply.fromstart(P_Return, FUN = 'mean' , gap = 1)) %>%              # Adding past return mean
    mutate(variance = apply.fromstart(P_Return, FUN = 'var' , gap = 1)) %>%           # Adding past return variance
    mutate(skewness = apply.fromstart(P_Return, FUN = 'skewness' , gap = 1)) %>%      # Adding past return skewness
    na.omit() %>%                                                                        # Drop all NA row
    ungroup() #addadd
head(data)

Before starting the analysis, we would like to investigate correlation and autocorrelation between the features in order to be aware of any hidden relationship among the explanatory variables. First, we find correlation between explanatory variables.

corr <- data %>%
  dplyr::select(-one_of('Date', 'Tick', 'F_Return'))
corr <- data.frame(cor(corr))
corr

There are 3 pairs of interesting correlation between explanatory variables.

  1. RSI postively correlated with Past Return (0.92)
  2. D2E positively correlated with P2B (0.64)
  3. Vol1M negatively correlated with Close (-0.66)

It is important to be aware that overfitting might occur if the model uses the training data which includes 2 or more positively correlated variables due to their information redundancy.

Apart from correlation between explanatory variables, we should also be considering the autocorrelation for each feature. Below is the analysis of autocorrelation for our x variables. It is obvious than there is not any significant autocorrelation for any feature.

for (i in seq(1,length(colnames(acf)),1)){
  temp_c <- cbind(temp_c, cor(autocorr[1:nrow(autocorr)-1,i], autocorr[2:nrow(autocorr),i]))
}
Error in seq.default(1, length(colnames(acf)), 1) : 
  wrong sign in 'by' argument

2.3 Split data

Within our monthly data over 25 years, there are 229 dates from April 1994 to April 2018. We decided to split the data into training set and testing set using 70/30 ratio. Since we are dealing with timeseries data, we simply split the dataset using the separation date which will give such desirable ratio, January 2013.

dates_all <- unique(data$Date)              # Select all unique dates
sep_date <- as.Date("2013-01-02")           # Set the seperation date for training set and          out-of-sample
dates_oos <- dates_all[dates_all >= sep_date]        # Out of sample set

3 Pre-defined functions and variables

Below includes the global functions and variables, which are utilised throughout the file.

3.1 Returns

returns <- data %>%                         
    dplyr::select(Date, Tick, P_Return) %>%        
    spread(key = Tick, value = P_Return)    
head(returns)
asset_return <- filter(returns, Date>sep_date)   
returns_oos <- data %>%                         
    dplyr::select(Date, Tick, P_Return) %>%       
    spread(key = Tick, value = P_Return) %>%
    filter(Date > sep_date) %>%
    dplyr::select(-Date)

3.2 Equally-weighted calculation

getEW <- function(weight){ # Define a function to calculate equally-weighted portfolio
  ew <- (t(scale(t(weight), center=FALSE, scale=colSums(t(weight)))))  # Normalise weight (Equally weighted)
  ew[is.na(ew)] <- 0
  return (ew)
}

3.3 Feature Selection 1

Define a function for feature selection for each stock using lasso. The function receives stock ticker as a parameter and returns a dataframe with only selected explanatory variables. The function uses glmnet package to build a lasso model using lambda.min which minimize the mean cross-validate error. In general, LASSO is a common tool for feature selection due to its penalised terms in objective function. Any variables with insigficant meaning to our response variable will be eliminated by lasso. There is a special case in which lasso eliminates all features. As a result, past return (P_Return) is used for the only feature.

if(!require(glmnet)){install.packages('glmnet')}
library(glmnet)
selectFeature <- function(ticker){
  set.seed(9) # Set seed for consistency
  temp <- data %>% filter(Tick == ticker) %>% # Filter ticker
      dplyr::select(-one_of('Tick', 'Date','Close')) # Drop tick, date, and close columns
  lasso <- cv.glmnet(x = as.matrix(temp[,-which(names(temp) == "F_Return")]), y = as.matrix(temp[,which(names(temp) == "F_Return")])) # Train lasso model
  subset = coef(lasso, s = "lambda.min")@Dimnames[[1]][coef(lasso, s = "lambda.min")@i + 1] # Get list of remaining variables
  subset =  subset[!subset == "(Intercept)"] # Get rid of intercept value
  if(length(subset) == 0){ # If there is no remaining variable
    subset = c('P_Return') # Use P_Return as only explanatory variable
  }
  return (data %>% filter(Tick == ticker) %>% # Return dataframe with Date, F_Return, and the selected explanatory variables
    dplyr::select(cbind('Date','F_Return',subset)))
}

Test the function by selecting feature for Apple stock

selectFeature('AAPL')

For Apple, lasso chooses 6 meaningful features including Mkt_Cap, Vol_1M, Prof_growth, mean, variance, and skewness.

3.4 Feature Selection 2

Use signal of future returns as dependent variable and perform feature selection using lasso for each stock.

if(!require(glmnet)){install.packages('glmnet')}
library(glmnet)
selectFeature2 <- function(ticker,sig_dat){
    #sig_dat: matrix with signal outputs for each stock
  set.seed(9)
  temp <- data %>% filter(Tick == ticker) %>%
      select(-one_of('Date','Close'))
  temp <- temp[-c(1)]
  temp <- temp[37:226,] #we trim the dataset for categorical, the reason is explained in the categorial section below.
  y = as.tibble(sig_dat) %>%
    select(ticker) 
  y = as.matrix(y)
  colnames(y) <- "y"
  lasso <- cv.glmnet(x = as.matrix(temp[,-which(names(temp) == "F_Return")]), 
                     y = y)
  subset = coef(lasso, s = "lambda.min")@Dimnames[[1]][coef(lasso, s = "lambda.min")@i + 1]
  subset =  subset[!subset == "(Intercept)"]
  if(length(subset) == 0){
    subset = c('P_Return')
  }
  result <- (data %>% filter(Tick == ticker) %>%
    select(cbind('Date',subset))) %>%
    slice(37:226) %>%
    mutate(output = y)
}

3.5 Performance Metrics

perf_met <- function(portf_returns, weights, asset_returns){
    avg_ret <- mean(portf_returns, na.rm = T)                     # Arithmetic mean 
    vol <- sd(portf_returns, na.rm = T)                           # Volatility
    Sharpe_ratio <- avg_ret / vol                                 # Sharpe ratio
    VaR_5 <- quantile(portf_returns, 0.05, na.rm = TRUE)          # Value-at-risk
    turn <- 0    
    for(t in 2:dim(weights)[1]){
        realised_returns <- asset_returns %>% filter(Date == dates_oos[t]) %>%  dplyr::select(-Date)
        prior_weights <- weights[t-1,] * (1 + realised_returns)
        temp_turn <- apply(abs(weights[t,] - prior_weights/sum(prior_weights)),1,sum)
        if(!any(is.na(temp_turn))){
          turn <- turn + apply(abs(weights[t,] - prior_weights/sum(prior_weights)),1,sum)
        }
    }
    turn <- turn/(length(dates_oos)-1)                                # Average over time
    met <- data.frame(avg_ret, vol, Sharpe_ratio, VaR_5, turn)    # Aggregation of all of this
    rownames(met) <- "metrics"
    return(met)
}
perf_met2 <- function(ret,weight,dates_oos,port_name,actual_ret){
    avg_ret <- mean(ret, na.rm = T)                     # Arithmetic mean 
    vol <- sd(ret, na.rm = T)                           # Volatility
    Sharpe_ratio <- avg_ret / vol                           # Sharpe ratio
    VaR_5 <- quantile(ret, 0.05)                        # Value-at-risk
    turn <- 0
    for(t in 2:length(ret)) {
        realised_ret <- actual_ret %>%
          filter(Date == dates_oos[t]) %>%
          select(-Date)
        prior_weight <- weight[t-1,] * (1 + realised_ret) # Before rebalancing
        turn <- turn + apply(abs(weight[t,] - prior_weight/sum(prior_weight)),1,sum)
    }
    turn <- turn/(length(ret)-1)
    met <- data.frame(avg_ret, vol, Sharpe_ratio, VaR_5, turn)    # Aggregation of all of this
    rownames(met) <- port_name
    return(met)
}

4 Investing Strategies

We create various portfolios using same set of 209 stocks based on 3 different schemes.

  • Sparse portfolio Weight to each stock is assigned based on the influence of other stocks’ returns on that stock. Whereas, the degree of such influence (weight) is determined by the following algorithms.
    • Elastic Network
  • Signals
    • Numercial: use future returns as dependent variables Steps
      • Use Lasso to determine features for each stocks
      • Predict the future returns for each stock using different ML algorithms, including
        • Lasso
        • KNN
        • Random Forest
      • Translate the numerical results into signals using 3 criteria:
        • Percentile - use expanding historical data to split percentile decision regions P0-P33: ‘sell’ P33-P66: ‘hold’, includes in portfolio only if the stock is held/bought from the previous period P66-P100: ‘buy’

        • Monthly Momentum - ‘buy’ a stock if predicted returns increase from previous day, and sell otherwise
        • 3-period Momentum - ‘buy’ a stock if predicted returns have been increasing for all 3 previous periods and the predicted return for the 4th period is in the same direction (rising), and sell otherwise
      • Consider the numerical results, and override signals: ‘buy’ only when signal is ‘buy’ and numerical prediction is positive, ‘sell’ otherwise
      • Apply equally weighted scheme for stocks with ‘buy’ signals
    • Categorical: use signals of future returns as dependent variables Steps
      • Translate the numerical actual future returns into signals using the same 3 criteria:
        • Percentile
        • Monthly Momentum
        • 3-period Momentum
      • Use Lasso to determine features for each stocks
      • Predict the signals of future returns for each stock using different ML algorithms, including
        • QDA
        • KNN
        • Logistic Model
        • Probit Model
      • Apply equally weighted scheme for stocks with ‘buy’ signals
  • Benchmark portfolio To interpret and gain understanding of the use of machine learning, we set equally-weighted portfolio as a benchmark.
    • EW

4.1 Benchmark portfolio

We use equally weighted portfolio as a benchmark for other strategies. The portfolio is simply distributed the risk of each asset equally across the portfolio. The portfolio weights can be computed as it holds all selected assets equally as \(w=\frac{1}{N}\).

4.1.1 Setup functions to calculate equal weigths

weights_ew <- function(returns){
    N = length(returns[1,])
    return(rep(1/N,N))
}

4.1.2 Calculate portfolio return

# Initiate storage for portfolio weights
port_w_ew = matrix(0, nrow = length(dates_oos), ncol = length(tick))
colnames(port_w_ew) = tick


# Initiate storage for portfolio returns
port_return_ew = matrix(0, nrow = length(dates_oos))

for(i in 1:length(dates_oos)){
    
    # Get the past return data
    past_return <- returns %>% 
      filter(Date < dates_oos[i]) %>%
      dplyr::select(-Date)
    
    # Calculate portfolio weight
    port_w_ew[i,] <- weights_ew(past_return)
    
    # Get the realised data
    realised_returns <- returns %>% 
        filter(Date ==  dates_oos[i]) %>%
        dplyr::select(-Date)
    
    # Calculating returns for each portfolio
    port_return_ew[i] <- sum(port_w_ew[i,] * realised_returns)
}

4.1.3 Result: Performance matrix for Benchmark portfolio

asset_returns <- data %>% 
    dplyr::select(Tick, Date, F_Return) %>% 
    spread(key = Tick, value = F_Return)
met_ew = perf_met(port_return_ew,port_w_ew,asset_returns)
row.names(met_ew) = c("Equally weighted")
met_ew

4.2 Sparse Portfolio

4.2.1 Elastic Network

Elastic net is a form of penalized regression which used to create sparse portfolio based on returns of assets. The condition of the regression is as follows \[ \hat{\beta}^{elastic}=\underset{\beta}{\text{argmin}}\, \left\{\frac{1}{N}\sum_{n=1}^N\left( y_{i}-a_i+\sum_{k= 1}^K\beta_{k}x_{k,i}\right)^2+\lambda \alpha \sum_{k= 1}^K| \beta_k|+\lambda (1-\alpha)\sum_{k= 1}^K| \beta_{k}^2|\right\},\]

4.2.1.1 Setup for elastic network weight and return calculations

The model use two parameters \(\lambda\) and \(\alpha\). \(\alpha\) is the parameter that changes the weight between Ridge regression and LASSO regression (Ridge: \(\alpha = 0\), LASSO: \(\alpha = 1\)). \(\lambda\) is the parameter that adjust the weight of the penalised part.

To find portfolio weights using Elastic net, we regress the returns of the selected asset on the returns of other assets as. \(r_{i,t}=a_i+\sum_{n\neq i}^N\beta_{n}r_{n,t}+\epsilon_{t}\)

Using penalised regression to get. \[ \underset{\beta_{i|}}{\text{argmin}}\, \left\{\sum_{t=1}^T\left( r_{i,t}-a_i+\sum_{n\neq i}^N\beta_{i|n}r_{n,t}\right)^2+\lambda \alpha || \beta||_1+\lambda (1-\alpha)||\beta||_2^2\right\},\]

weights_lasso <- function(returns,alpha){  # The parameters are defined here
    w <- 0                                          # Initiate weights
    for(i in 1:ncol(returns)){                      # Loop on the assets
        y <- returns[,i]                            # Dependent variable
        x <- returns[,-i]                           # Independent variable
        fit <- glmnet(x,y, alpha = alpha,lambda = 0.5)
        err <- y-predict(fit, x)                    # Prediction errors
        w[i] <- (1-sum(fit$beta))/var(err)          # Output: weight of asset i
    }
    return(w / sum(w))                              # Normalisation of weights
}

4.2.1.2 Alpha selection and calculation weights & returns

Elastic net portfolio weights can be calculated using the following steps 1. Use the value of alphas ranging from 0, 0.1, 0.2,…,1 and using \(\lambda = 0.5\). 2. Selecting past return data and use the function weights_lasso to calculate protfolio weights 3. Multiply the weights with realised returns to get the portfolio returns 4. Select the highest return portfolio

# Finding alpha that generate largest average return
colMeans(port_return_elas)
         X0        X0.1        X0.2        X0.3        X0.4        X0.5        X0.6        X0.7        X0.8        X0.9          X1 
0.007399314 0.008577279 0.008623625 0.008620449 0.008619760 0.008619747 0.008619747 0.008619747 0.008619747 0.008619747 0.008619747 

4.2.1.3 Result: Performance matrix for Sparse portfolio

4.3 Signals

signals We separate the predicted returns into 3 categories based on the asset past returns. 1. BUY signal (+1): If the predicted return of asset i is higher than P66 of its entire past returns 2. SELL signal (0): If the predicted return of asset i is lower than P33 of its entire past returns 3. HOLD signal (-1):If the predicted return of asset i falls in between P33 and P66 of its entire past returns

4.3.0.1 LASSO

For LASSO method, glmnet is to use penalised regression to predict future returns using the past feature data including past returns. The procedure are as the following steps: 1. Get the past data as independent variables (x) 2. Get the past returns as a dependent variable (y) 3. Use LASSO as a model to predict the future returns 4. Perform cross validation from the function cv.glmnet. the objective is to use the lambda that minimise the variance of the error 5. Use the current feature data to predict the next period returns

4.3.0.1.1 Train LASSO model
# temp_return = matrix(NA, ncol = length(tick), nrow=length(dates_oos))
# colnames(temp_return) = tick
# for (i in 1:length(tick)){
#   print(i)
#   for(t in 1:length(dates_oos)){
#     past_data = data %>%
#       filter(Tick == tick[i]) %>%
#       filter(Date < dates_oos[t]) %>%
#       dplyr::select(-Date,-Tick,-Close) %>%
#       tail(.,48) %>%
#       as.matrix()
#     # Get the training data (3months)
#     train_data = head(past_data,36) %>%
#       as.data.frame()
#     x_train = train_data %>%
#       dplyr::select(-F_Return) %>%
#       as.matrix()
#     y_train = train_data %>%
#       dplyr::select(F_Return) %>%
#       as.matrix
# 
# 
#     MSE = matrix(NA, ncol=10,nrow=11)
# 
#     for(a in 1:11){
#       # Alpha = 0, 0.1, 0.2,...,1
#       alpha = (a-1)/10
#     for(l in 1:10){
#       # Lambda = 0, 0.1, 0.2,...,1
#       lambda = (l)/10
#     
#       # Run penalised regression
#       fit = glmnet(x = x_train, y = y_train, alpha = alpha, lambda = lambda)
# 
#       # Get the validation data (3months)
#       validate_data = tail(past_data,12) %>%
#         as.data.frame() 
#       x_validate = validate_data %>%
#         dplyr::select(-F_Return) %>%
#         as.matrix()
#       y_validate= validate_data %>%
#         dplyr::select(F_Return) %>%
#         as.matrix()
# 
#       # Calculate MSE
#       MSE[a,l] = sum((y_validate - predict(fit,x_validate))^2)
# 
#     }
#   }
# 
# # Optimal parameters
# min_alpha = (which(MSE == min(MSE), arr.ind = TRUE)[1] - 1)/10
# min_lambda = (which(MSE == min(MSE), arr.ind = TRUE)[2])/10
# 
# 
# 
# last_period_x = data %>%
#       filter(Tick == tick[i]) %>%
#       filter(Date == dates_oos[t]) %>%
#       dplyr::select(-Date,-Tick,-Close, -F_Return) %>%
#       as.matrix()
#     
#     fit2 = glmnet(x = x_train, y = y_train, alpha = min_alpha, lambda = min_lambda)
#     temp_return[t,i] <- predict(fit2, last_period_x)
#   }
# }
# 
# write.csv(temp_return, file = "return_lasso.csv")

# For further execution
temp_return = read.csv(file = "return_lasso.csv")
temp_return = temp_return[-1]

** with LASSO with percentile **

# Translate Hold signal
for (i in 1:length(tick)){
  print(i)
  for (t in 2:length(dates_oos)){
    if(q_signal[t,i] == 0){
      q_signal[t,i] = q_signal[t-1,i]
    }
  }
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10
[1] 11
[1] 12
[1] 13
[1] 14
[1] 15
[1] 16
[1] 17
[1] 18
[1] 19
[1] 20
[1] 21
[1] 22
[1] 23
[1] 24
[1] 25
[1] 26
[1] 27
[1] 28
[1] 29
[1] 30
[1] 31
[1] 32
[1] 33
[1] 34
[1] 35
[1] 36
[1] 37
[1] 38
[1] 39
[1] 40
[1] 41
[1] 42
[1] 43
[1] 44
[1] 45
[1] 46
[1] 47
[1] 48
[1] 49
[1] 50
[1] 51
[1] 52
[1] 53
[1] 54
[1] 55
[1] 56
[1] 57
[1] 58
[1] 59
[1] 60
[1] 61
[1] 62
[1] 63
[1] 64
[1] 65
[1] 66
[1] 67
[1] 68
[1] 69
[1] 70
[1] 71
[1] 72
[1] 73
[1] 74
[1] 75
[1] 76
[1] 77
[1] 78
[1] 79
[1] 80
[1] 81
[1] 82
[1] 83
[1] 84
[1] 85
[1] 86
[1] 87
[1] 88
[1] 89
[1] 90
[1] 91
[1] 92
[1] 93
[1] 94
[1] 95
[1] 96
[1] 97
[1] 98
[1] 99
[1] 100
[1] 101
[1] 102
[1] 103
[1] 104
[1] 105
[1] 106
[1] 107
[1] 108
[1] 109
[1] 110
[1] 111
[1] 112
[1] 113
[1] 114
[1] 115
[1] 116
[1] 117
[1] 118
[1] 119
[1] 120
[1] 121
[1] 122
[1] 123
[1] 124
[1] 125
[1] 126
[1] 127
[1] 128
[1] 129
[1] 130
[1] 131
[1] 132
[1] 133
[1] 134
[1] 135
[1] 136
[1] 137
[1] 138
[1] 139
[1] 140
[1] 141
[1] 142
[1] 143
[1] 144
[1] 145
[1] 146
[1] 147
[1] 148
[1] 149
[1] 150
[1] 151
[1] 152
[1] 153
[1] 154
[1] 155
[1] 156
[1] 157
[1] 158
[1] 159
[1] 160
[1] 161
[1] 162
[1] 163
[1] 164
[1] 165
[1] 166
[1] 167
[1] 168
[1] 169
[1] 170
[1] 171
[1] 172
[1] 173
[1] 174
[1] 175
[1] 176
[1] 177
[1] 178
[1] 179
[1] 180
[1] 181
[1] 182
[1] 183
[1] 184
[1] 185
[1] 186
[1] 187
[1] 188
[1] 189
[1] 190
[1] 191
[1] 192
[1] 193
[1] 194
[1] 195
[1] 196
[1] 197
[1] 198
[1] 199
[1] 200
[1] 201
[1] 202
[1] 203
[1] 204
[1] 205
[1] 206
[1] 207
[1] 208
[1] 209

** with LASSO with three-momentum **

for (i in 1:length(tick)){
  print(i)
  for (t in 1:length(dates_oos)){
    three_M_past_return = data %>%
      filter(Tick == tick[i]) %>%
      filter(Date <= dates_oos[t]) %>%
      dplyr::select(P_Return) %>%
      as.matrix() %>%
      tail(3)
    
    p_1 = three_M_past_return[3]
    p_2 = three_M_past_return[2]
    p_3 = three_M_past_return[1]
    
    if(temp_return[t,i]>0 & p_1>0 & p_2>0 & p_3>0){
      m_signal[t,i] = 1
    }
      else{
      m_signal[t,i] = 0
    }
  }
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10
[1] 11
[1] 12
[1] 13
[1] 14
[1] 15
[1] 16
[1] 17
[1] 18
[1] 19
[1] 20
[1] 21
[1] 22
[1] 23
[1] 24
[1] 25
[1] 26
[1] 27
[1] 28
[1] 29
[1] 30
[1] 31
[1] 32
[1] 33
[1] 34
[1] 35
[1] 36
[1] 37
[1] 38
[1] 39
[1] 40
[1] 41
[1] 42
[1] 43
[1] 44
[1] 45
[1] 46
[1] 47
[1] 48
[1] 49
[1] 50
[1] 51
[1] 52
[1] 53
[1] 54
[1] 55
[1] 56
[1] 57
[1] 58
[1] 59
[1] 60
[1] 61
[1] 62
[1] 63
[1] 64
[1] 65
[1] 66
[1] 67
[1] 68
[1] 69
[1] 70
[1] 71
[1] 72
[1] 73
[1] 74
[1] 75
[1] 76
[1] 77
[1] 78
[1] 79
[1] 80
[1] 81
[1] 82
[1] 83
[1] 84
[1] 85
[1] 86
[1] 87
[1] 88
[1] 89
[1] 90
[1] 91
[1] 92
[1] 93
[1] 94
[1] 95
[1] 96
[1] 97
[1] 98
[1] 99
[1] 100
[1] 101
[1] 102
[1] 103
[1] 104
[1] 105
[1] 106
[1] 107
[1] 108
[1] 109
[1] 110
[1] 111
[1] 112
[1] 113
[1] 114
[1] 115
[1] 116
[1] 117
[1] 118
[1] 119
[1] 120
[1] 121
[1] 122
[1] 123
[1] 124
[1] 125
[1] 126
[1] 127
[1] 128
[1] 129
[1] 130
[1] 131
[1] 132
[1] 133
[1] 134
[1] 135
[1] 136
[1] 137
[1] 138
[1] 139
[1] 140
[1] 141
[1] 142
[1] 143
[1] 144
[1] 145
[1] 146
[1] 147
[1] 148
[1] 149
[1] 150
[1] 151
[1] 152
[1] 153
[1] 154
[1] 155
[1] 156
[1] 157
[1] 158
[1] 159
[1] 160
[1] 161
[1] 162
[1] 163
[1] 164
[1] 165
[1] 166
[1] 167
[1] 168
[1] 169
[1] 170
[1] 171
[1] 172
[1] 173
[1] 174
[1] 175
[1] 176
[1] 177
[1] 178
[1] 179
[1] 180
[1] 181
[1] 182
[1] 183
[1] 184
[1] 185
[1] 186
[1] 187
[1] 188
[1] 189
[1] 190
[1] 191
[1] 192
[1] 193
[1] 194
[1] 195
[1] 196
[1] 197
[1] 198
[1] 199
[1] 200
[1] 201
[1] 202
[1] 203
[1] 204
[1] 205
[1] 206
[1] 207
[1] 208
[1] 209

** with LASSO with one-momentum **

for (i in 1:length(tick)){
  print(i)
  for (t in 1:length(dates_oos)){
    one_M_past_return = data %>%
      filter(Tick == tick[i]) %>%
      filter(Date <= dates_oos[t]) %>%
      dplyr::select(P_Return) %>%
      as.matrix() %>%
      tail(1)
    if(temp_return[t,i]>0 & one_M_past_return>0){
      m1_signal[t,i] = 1
    }
      else{
      m1_signal[t,i] = 0
    }
  }
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10
[1] 11
[1] 12
[1] 13
[1] 14
[1] 15
[1] 16
[1] 17
[1] 18
[1] 19
[1] 20
[1] 21
[1] 22
[1] 23
[1] 24
[1] 25
[1] 26
[1] 27
[1] 28
[1] 29
[1] 30
[1] 31
[1] 32
[1] 33
[1] 34
[1] 35
[1] 36
[1] 37
[1] 38
[1] 39
[1] 40
[1] 41
[1] 42
[1] 43
[1] 44
[1] 45
[1] 46
[1] 47
[1] 48
[1] 49
[1] 50
[1] 51
[1] 52
[1] 53
[1] 54
[1] 55
[1] 56
[1] 57
[1] 58
[1] 59
[1] 60
[1] 61
[1] 62
[1] 63
[1] 64
[1] 65
[1] 66
[1] 67
[1] 68
[1] 69
[1] 70
[1] 71
[1] 72
[1] 73
[1] 74
[1] 75
[1] 76
[1] 77
[1] 78
[1] 79
[1] 80
[1] 81
[1] 82
[1] 83
[1] 84
[1] 85
[1] 86
[1] 87
[1] 88
[1] 89
[1] 90
[1] 91
[1] 92
[1] 93
[1] 94
[1] 95
[1] 96
[1] 97
[1] 98
[1] 99
[1] 100
[1] 101
[1] 102
[1] 103
[1] 104
[1] 105
[1] 106
[1] 107
[1] 108
[1] 109
[1] 110
[1] 111
[1] 112
[1] 113
[1] 114
[1] 115
[1] 116
[1] 117
[1] 118
[1] 119
[1] 120
[1] 121
[1] 122
[1] 123
[1] 124
[1] 125
[1] 126
[1] 127
[1] 128
[1] 129
[1] 130
[1] 131
[1] 132
[1] 133
[1] 134
[1] 135
[1] 136
[1] 137
[1] 138
[1] 139
[1] 140
[1] 141
[1] 142
[1] 143
[1] 144
[1] 145
[1] 146
[1] 147
[1] 148
[1] 149
[1] 150
[1] 151
[1] 152
[1] 153
[1] 154
[1] 155
[1] 156
[1] 157
[1] 158
[1] 159
[1] 160
[1] 161
[1] 162
[1] 163
[1] 164
[1] 165
[1] 166
[1] 167
[1] 168
[1] 169
[1] 170
[1] 171
[1] 172
[1] 173
[1] 174
[1] 175
[1] 176
[1] 177
[1] 178
[1] 179
[1] 180
[1] 181
[1] 182
[1] 183
[1] 184
[1] 185
[1] 186
[1] 187
[1] 188
[1] 189
[1] 190
[1] 191
[1] 192
[1] 193
[1] 194
[1] 195
[1] 196
[1] 197
[1] 198
[1] 199
[1] 200
[1] 201
[1] 202
[1] 203
[1] 204
[1] 205
[1] 206
[1] 207
[1] 208
[1] 209
4.3.0.1.2 Result: Performance matrix for LASSO

4.3.0.2 K-Nearest Neighbour

This algorithm consider values of K-nearest data points to a particular observation in order to predict that observations. Therefore, the hyperparameter for this model is K. Note that, since we train the model for each stock, the model automatically tunes the best K-value for each stock’s model resulting in different K’s.

4.3.0.2.1 Setup functions for KNN

Signals

#percentile
P_sig <- function(dat,hist_dat,sig_date) { 
      #dat: one column of returns (predicted for numerical, actual for categorical)
    #hist_dat: historical data to create percentile
    #sig_date: date at which the signal is assigned
  sig <- matrix (0,nrow = length(dat),ncol = 1)
  for (i in 1:length(dat)) {
    P_dat <- hist_dat[1:(sig_date-2+i)]    #expanding data to create percentile
    if (dat[i] >= quantile(P_dat, .66)) {
        sig[i] = 1
    } else if (dat[i] >= quantile(P_dat, .33)) {
        if (i == 1) {
          sig[i] = 0
        } else if (sig[i-1] == 1) {
          sig[i] = 1
        } 
    } else {
        sig[i] = 0
    }
  }
  return(sig)
}

#For Momentum functions, the input is data frame with future returns as first column and current returns as second columns

#Monthly MoM
MoM.1_sig <- function(dat){
  sig <- matrix(0,nrow = nrow(dat),ncol = 1)
  for (i in 1:nrow(dat)) {
    if (dat[i,1] > dat[i,2]) {      #buy if predicted future return > current return
      sig[i] = 1
    } else {
      sig[i] = 0
    }
  }
  return(cbind(dat,sig))
}

# Quarterly MoM
MoM.3_sig <- function(dat){
  sig <- matrix(0,nrow = nrow(dat),ncol = 1)
  for (i in 4:nrow(dat)) {
    if ((dat[i-2,2] > dat[i-3,2]) & (dat[i-1,2] > dat[i-2,2]) & (dat[i,2] > dat[i-1,2]) &
    (dat[i,1] > dat[i,2])){ 
                #buy if predicted future return > current return
                #given that the past return had been on rising momentum for last 3 periods
      sig[i] = 1
    } else {
      sig[i] = 0
    }
  }
  result <- cbind(dat,sig)
  result <- result[4:(nrow(dat)),] #somehow this does not delete first 3 rows
  #result <- result[4:67,]
  return(result)
}

Cross-validation Unlike sparse portfolio method, we deal with time-seies data to predict future returns in this section. Hence, we need to set time-series cross-validation as time slice to maintain the structure in the data (oldest to newest). To be consistent with our out-of-sample date which separates training and test sets with approximately 70/30 ratio, we will set 36 and 12 observations for each training and validation examples when we fit the model.

#carat library for training
if(!require(caret)){install.packages('caret')}
library(caret)

#Training set starts from first date until date before separation date
timeSlices_train <- createTimeSlices(1:(length(dates_all)-length(dates_oos)), 
                   initialWindow = 36, horizon = 12, fixedWindow = TRUE)

Date variables

oos_start <- ((length(dates_all)-length(dates_oos))+1)
oos_length <- length(dates_all) - oos_start + 1
oos_ret <- as_tibble(returns) %>% 
  filter(Date >= sep_date) %>%    
  dplyr::select(-Date) %>%                                   
  as.matrix()    

Equally-weighted calculation

cal_port_weight <- function(sig_dat) {
  weight <- matrix(0, nrow= nrow(sig_dat),ncol = ncol(sig_dat))     # create weight matrix
  for (i in 1:nrow(sig_dat)) {                                      # assign weights
    buy_thisDay <- which(sig_dat[i,] == 1)
    for (j in 1:length(buy_thisDay)) {
      weight[i,buy_thisDay[j]] <- 1/length(buy_thisDay)
    }
  }
 return (weight)
}
4.3.0.2.2 Train KNN with percentile
weight_knn_P = read.csv(file = "weight_knn_P")
cannot open file 'weight_knn_P': No such file or directoryError in file(file, "rt") : cannot open the connection
4.3.0.2.3 Train KNN with percentile & sign
# signals_knn_P.s <- matrix(0, nrow = oos_length, ncol = length(tick)) #create table to store signals
# pred_knn_P.s <- matrix(0, nrow = oos_length, ncol = length(tick))
# colnames(signals_knn_P.s) <- tick
# colnames(pred_knn_P.s) <- tick
# for (i in 1:length(tick)){
#   a <- selectFeature(tick[i])
#   a_pred <- train(F_Return ~ ., data = a[unlist(timeSlices_train),], 
#                   method = "knn", 
#                   preProc = c("center", "scale"))
#   a_actual.future_test = a[oos_start:length(dates_all),]
#   a_pred.future_test <- predict(a_pred, a_actual.future_test)     # get prediction
#   a_signal <- P_sig(a_pred.future_test,as.matrix(returns[tick[i]]),oos_start)# transfrom into signals
#   pred_knn_P.s[, i] <- a_pred.future_test
#   for (j in 1: length(a_signal)){
#     if ((a_signal[j] == 1) & (a_pred.future_test[j] > 0)) {
#       signals_knn_P.s[j, i] = 1
#     } else {
#       signals_knn_P.s[j, i] = 0
#     }
#   }
# }
# weight_knn_P.s <- cal_port_weight(signals_knn_P.s)
# ret_knn_P.s <- rowSums(weight_knn_P.s*oos_ret)
# write.csv(weight_knn_P.s, file = "weight_knn_P_s.csv")
# write.csv(ret_knn_P.s, file = "ret_knn_P_s.csv")

weight_knn_P.s = read.csv(file = "weight_knn_P_s.csv")
weight_knn_P.s = weight_knn_P.s[-1]

ret_knn_P.s = read.csv(file = "ret_knn_P_s.csv")
ret_knn_P.s = ret_knn_P.s[-1]
4.3.0.2.4 Train KNN with 1-period momentum
weight_knn_MoM.1 <- cal_port_weight(signals_MoM.1) #addadd. change name of sig
Error in nrow(sig_dat) : object 'signals_MoM.1' not found
4.3.0.2.5 Train KNN with 1-period momentum & sign
# signals_knn_MoM.1.s <- matrix(0, nrow = oos_length, ncol = length(tick)) #create table to store signals
# pred_knn_MoM.1.s <- matrix(0, nrow = oos_length, ncol = length(tick))
# colnames(signals_knn_MoM.1.s) <- tick
# colnames(pred_knn_MoM.1.s) <- tick
# 
# for (i in 1:length(tick)){
#   a <- selectFeature(tick[i])
#   a_pred <- train(F_Return ~ ., data = a[unlist(timeSlices_train),], 
#                   method = "knn", 
#                   preProc = c("center", "scale"))
#   a_actual.future_test = a[oos_start:length(dates_all),]
#   a_pred.future_test <- predict(a_pred, a_actual.future_test)     # get prediction
#   a_actual.current_test <- a$F_Return[(oos_start-1):(length(dates_all)-1)]
#   a_table <- cbind(a_pred.future_test,a_actual.current_test)
#   a_signal <- MoM.1_sig(a_table)                # transfrom into signals
#   pred_knn_MoM.1.s[, i] <- a_pred.future_test
#   for (j in 1: nrow(a_signal)){
#     if ((a_signal[j,3] == 1) & (a_pred.future_test[j] > 0)) {
#       signals_knn_MoM.1.s[j, i] = 1
#     } else {
#       signals_knn_MoM.1.s[j, i] = 0
#     }
#   }
# }
# weight_knn_MoM.1.s <- cal_port_weight(signals_knn_MoM.1.s) 
# ret_knn_MoM.1.s <- rowSums(weight_knn_MoM.1.s*oos_ret)
# 
# write.csv(weight_knn_MoM.1.s, file = "weight_knn_MoM1s.csv")
# write.csv(ret_knn_MoM.1.s, file = "ret_knn_MoM1s.csv")

weight_knn_MoM.1.s = read.csv(file = "weight_knn_MoM1s.csv")
weight_knn_MoM.1.s = weight_knn_MoM.1.s[-1]

ret_knn_MoM.1.s = read.csv(file = "ret_knn_MoM1s.csv")
ret_knn_MoM.1.s = ret_knn_MoM.1.s[-1]
4.3.0.2.6 Train KNN with 3-period momentum
# signals_knn_MoM.3 <- matrix(0, nrow = oos_length, ncol = length(tick)) #create table to store signals
# colnames(signals_knn_MoM.3) <- tick
# for (i in 1:length(tick)){
#   a <- selectFeature(tick[i])
#   a_pred <- train(F_Return ~ ., data = a[unlist(timeSlices_train),], 
#                   method = "knn", 
#                   preProc = c("center", "scale"))
#   a_actual.future_test = a[oos_start:length(dates_all),]
#   pred <- predict(a_pred, a_actual.future_test)     # get prediction
#   a_pred.future_test <- c(0,0,0,pred)           # make lenght longer for previous actual returns
#   a_actual.current_test <- a$F_Return[(oos_start-3):(length(dates_all))]
#   a_table <- cbind(a_pred.future_test,a_actual.current_test)
#   a_signal <- MoM.3_sig(a_table)                # transfrom into signals
#   
#   signals_knn_MoM.3[,i]  <- a_signal[,3]          # combine signals for all stocks
# }
# weight_knn_MoM.3 <- cal_port_weight(signals_knn_MoM.3)
# ret_knn_MoM.3 <-rowSums(weight_knn_MoM.3*oos_ret)
# 
# write.csv(weight_knn_MoM.3, file = "weight_knn_MoM3.csv")
# write.csv(ret_knn_MoM.3, file = "ret_knn_MoM3.csv")

weight_knn_MoM.3 = read.csv(file = "weight_knn_MoM3.csv")
weight_knn_MoM.3 = weight_knn_MoM.3[-1]

ret_knn_MoM.3 = read.csv(file = "ret_knn_MoM3.csv")
ret_knn_MoM.3 = ret_knn_MoM.3[-1]
4.3.0.2.7 Train KNN with 3-period momentum & sign
# signals_knn_MoM.3.s <- matrix(0, nrow = oos_length, ncol = length(tick)) #create table to store signals
# colnames(signals_knn_MoM.3.s) <- tick
# pred_knn_MoM.3.s <- matrix(0, nrow = oos_length, ncol = length(tick)) 
# colnames(pred_knn_MoM.3.s) <- tick
# 
# for (i in 1:length(tick)){
#   a <- selectFeature(tick[i])
#   a_pred <- train(F_Return ~ ., data = a[unlist(timeSlices_train),], 
#                   method = "knn", 
#                   preProc = c("center", "scale"))
#   a_actual.future_test = a[oos_start:length(dates_all),]
#   pred <- predict(a_pred, a_actual.future_test)     # get prediction
#   a_pred.future_test <- c(0,0,0,pred)           # make lenght longer for previous actual returns
#   a_actual.current_test <- a$F_Return[(oos_start-3):(length(dates_all))]
#   a_table <- cbind(a_pred.future_test,a_actual.current_test)
#   a_signal <- MoM.3_sig(a_table)                # transfrom into signals
#   
#   pred_knn_MoM.3.s[, i] <- a_pred.future_test[4:length(a_pred.future_test)]
#   for (j in 1: nrow(a_signal)){
#     if ((a_signal[j, 3] == 1) & (a_pred.future_test[j] > 0)) {
#       signals_knn_MoM.3.s[j, i] = 1
#     } else {
#       signals_knn_MoM.3.s[j, i] = 0
#     }
#   }
# }
# weight_knn_MoM.3.s <- cal_port_weight(signals_knn_MoM.3.s)
# ret_knn_MoM.3.s <-rowSums(weight_knn_MoM.3.s*oos_ret)
# 
# write.csv(weight_knn_MoM.3.s, file = "weight_knn_MoM3s.csv")
# write.csv(ret_knn_MoM.3.s, file = "ret_knn_MoM3s.csv")

weight_knn_MoM.3.s = read.csv(file = "weight_knn_MoM3s.csv")
weight_knn_MoM.3.s = weight_knn_MoM.3.s[-1]

ret_knn_MoM.3.s = read.csv(file = "ret_knn_MoM3s.csv")
ret_knn_MoM.3.s = ret_knn_MoM.3.s[-1]
4.3.0.2.8 Result: Peformance Metrics for KNN (Numerical)

4.3.0.3 Random Forest

4.3.0.3.1 Growing a Portfolio

Random Forest is another common machine learning techniques which developed from a decision tree. It works well with both classification and regression problems. It is a powerful technique due to its highly non-linear model. However there is a trade-off by having many hyperparameters namely mtry (number of predictors sampled for spliting at each node.) and ntree (number of tree in the forest). In this case, signal criteria (percentile of momentum) is also treat as another hyperparameter since different algorithms yield different portfolio weight.

Regression random forest is used in this setting, where the user define a value for hyperparameter. (Impact of hyperparameter is discussed in the next section). For the Random Forest portfolio, the weight for each asset at each time period will be determined by the following procedures:

  1. At each date, train Random Forest model by using past data. (Note that different assets have different model due to feature selection process for each asset)
  2. Use the model to get a prediction for future return (F_Return)
  3. Derive investment decision for each asset by applying signal criteria (either percentile or momentum)to the prediction
  4. Normalise portfolio weight using equally weighted method

Then the strategy will be evaluated on its performance metrics based on the portfolio weight and return.

First, weight_RF function is defined to calculated portfolio weight using Random Forest technique. The function receives past and current data, mtry, ntree, and s_criteria (s_criteria) as parameters and return the buying decision as output.

if(!require(randomForest)){install.packages("randomForest")}
library(randomForest)
weight_RF <- function(past_data, current_data, mtry, ntree, s_criteria, prev_w){ # Function for weight calculation using random forest technique
      train <- past_data[-c(1)] #Training data, drop date columns
      test <- current_data[-c(1,2)] #Testing data, drop date and F_Return columns
      rf.fit <- randomForest(F_Return ~ ., # Grow random forest
                         data = train, # Training data
                         mtry = min(mtry,length(test)), # Number of predictive variables used is 4 (or less than if there is less variable)
                         ntree = ntree) # Number of random trees in the forest
      pred <- predict(rf.fit, test) # Predict F_Return using the fitted model
      if (s_criteria == 1){ # percentile signal criteria
        return (ifelse(pred < quantile(past_data$F_Return, c(.33)), 0, ifelse(pred < quantile(past_data$F_Return, c(.66)), prev_w, 1)))
      } else if (s_criteria == 2){ # momentum signal criteria
        return (ifelse(pred > 0, (ifelse(past_data[nrow(past_data)-1,]$F_Return > 0 & 
                                           past_data[nrow(past_data)-2,]$F_Return > 0 & 
                                           past_data[nrow(past_data)-3,]$F_Return > 0, 1, 0)), 0))
      } else { # just invest if return is positive
        return (ifelse(pred > 0, 1, 0)) # Invest if return > 0                                                                                  
      }
}

Function doRF performs backtesting evaluation by creating a portfolio by using Random Forest technique, given a specific set of hyperparameter values. The funcion returns a performance metric as a result.

doRF <- function(mtry = 4, ntree = 10, s_criteria = 0){
  portf_weight_RF <- matrix(0, nrow = length(dates_oos)-1, ncol = length(tick)) # Initiate weight matrix for RF portfolio
  portf_return_RF <- c() # # Initiate return list for RF portfolio
  prev_w <- 0
  for (i in 1:length(tick)){ # Repeat for all ticker
    temp <- selectFeature(tick[i]) # Feature selection for ticker i 
    w <- c() # Initiate an empty list of weight for ticker i
    for (t in 2:(length(dates_oos))){ # Expandind window for out-of-sample dates
      past_data <- temp %>% filter(Date < dates_oos[t-1]) # Past data is all data from the date before date t
      current_data <- temp %>% filter(Date == dates_oos[t-1]) # Current data is data at date t
      res <- weight_RF(past_data = past_data, current_data = current_data, mtry = mtry, ntree = ntree, s_criteria = s_criteria, prev_w = prev_w) # Get weight using random forest method with specific parameter
      w = rbind(w, res) # Append date for stock i at period t
      prev_w <- w
    }
    portf_weight_RF[,i] <- w # Append weight for stock i for all period t
  }
  portf_weight_RF <- getEW(portf_weight_RF) # Normalise weight (Equally weighted)
  for(j in 1:(length(dates_oos)-1)){ 
    portf_return_RF[j] <- sum(portf_weight_RF[j,] * returns_oos[j,]) # Calculate portfolio return
  }
  return (perf_met(portf_return_RF, portf_weight_RF, asset_return)) # Get performance metrics
}

Next, create a portfolio based on Random Forest strategy and evaluate the performance

doRF()
4.3.0.3.2 Sensitivity Analysis for Hyperparameters - Using the GridSearch

Performance could be improved by using the right values of hyperparameter. During the trading period, cross-validation technique could be used to tune hyperparameter. Analysis below demonstrate how much does hypermeters impact the performance of the portfolio by applying GridSearch technique. GridSearch can be performed with the following step:

  1. Set up range of values to be tested for each hyperparameter
  2. Set up list of all possible combinations for all hyperparameters
  3. Evaluate the model on all settings from step 2

This operation could significantly consume a huge runtime. Therefore RandomSearch can also be used to reduce runtime. The concept of RandomSearch is to initially randomly set a list of hyperparameters to be tested. Then the set that gives the best performance will be used to tuned the hyperparameters once again by doing GridSearch around those points.

Below, GridSearch is performed on selected combinations of hyperparameters and the resulting average return for each setting is visualized.

mtry <- c(6, 7, 8, 9, 10) # List of mtry to be tested
ntree <- c(10, 20, 30, 40, 50) # List of ntree to be tested
s_criteria <- c(0, 1, 2) # List of signal criteria to be tested
pars_RF <- expand.grid(mtry, ntree, s_criteria)  # Exploring all combinations
mtry <- pars_RF[,1]
ntree <- pars_RF[,2]
s_criteria <- pars_RF[,3]
grid_pars_RF <- function(mtry, ntree, s_criteria){  # Parameters for the grid search
  print(paste('mtry: ', toString(mtry), ' ntree: ', toString(ntree), ' s_criteria: ', toString(s_criteria)))
  temp <- doRF(mtry = mtry, ntree = ntree, s_criteria = s_criteria)
  return (temp$avg_ret) # Return average return
}
# Load result which has already been computed to reduce runtime 
grd <- read.csv('grd_RF.csv', header=TRUE, sep=",")
# If haven't computed before, uncomment the following block
#------------------------------------------------------------------------------#
# grd <- pmap(list(mtry, ntree, s_criteria), # Parameters for the grid search
#             grid_pars_RF) # Input for function: test labels 
# grd <- data.frame(mtry, ntree, s_criteria, avg_ret = unlist(grd))
# write.csv(grd, file = "grd_RF.csv", row.names=FALSE) # Keep as csv file on local
#------------------------------------------------------------------------------#
grd$s_criteria <- ifelse(grd$s_criteria == 0, "none", ifelse(grd$s_criteria == 1, "percentile", "momentum"))
grd %>% ggplot(aes(x = s_criteria, y = avg_ret, fill = s_criteria)) + # Plot!
    geom_bar(stat = "identity") +
    facet_grid(rows = vars(mtry), cols = vars(ntree))
Error in facet_grid(rows = vars(mtry), cols = vars(ntree)) : 
  unused arguments (rows = vars(mtry), cols = vars(ntree))
4.3.0.3.3 Result: Peformance Metrics for Random Forest

Obviously, various combinations of hyparameters impact average return of the portfolio signigicantly. The best set of hyperparameter are: mtry = 9, ntree = 20, and percentile signal criteria. Full performance metrics for this setting is shown below.

doRF(mtry = 9, ntree = 20, s_criteria = 1)

4.3.1 Categorical

Classification models are used when dependent variables are categorical, for example; when they are binary. Hence, we need to transform actual outputs into categories, then use them to fit the models. We will follow the same categories in the previous section, namely percentile and momemtum.

4.3.1.1 QDA (Quadratic Discriminant Analysis)

4.3.1.1.1 QDA-Driven Portolio

QDA or quadratic discriminate analysis is one of the famous machine learning technique for classification prediction which requires no hyperparameter. Applying QDA to this scenario, the response variable or future return (F_Return) must be binarised into 1 for positive and 0 for negative return. Therefore the prediction from QDA model is a probability of having a positive return in the next period. For QDA portfolio, the weight for each asset at each time period will be determined by the following procedures:

  1. At each date, train QDA model by using past data. (Note that different assets have different model due to feature selection process for each asset)
  2. Use the model to get a prediction for future return (F_Return)
  3. Derive investment decision for each asset by applying momentum signal criteria to the prediction
  4. Normalise portfolio weight using equally weighted method

Then the strategy will be evaluated on its performance metrics based on the portfolio weight and return.

First, weight_QDA function is defined to calculated portfolio weight using QDA technique. The function receives past and current data and decision threshold as parameters. Decision threshold is a choosen value between 0 and 1 which is used to change probability of having a positive future return into a buying decision. (If the probability is higher than the threshold, then buy an asset) Lastly, function returns either 0 not to invest or 1 to invest.

if(!require(MASS)){install.packages('MASS')}
library(MASS)
weight_QDA <- function(past_data, current_data, threshold = 0.5){ # If not passing threshould then set to 0.5
  y <- past_data$F_Return # Set past data F_Return as a response variable
  y <- ifelse(y > 0 , 1, 0) # Binarise response variable (set to 1 if the return is positive and 0 o.w.)
  x <- as.matrix(past_data[-c(1,2)]) #Remove Date and F_Return from explanatory variables (training data)
  new_x <- current_data[-c(1,2)] #Remove Date and F_Return from explanatory variables (testing data)
  qda.fit <- qda(x = x, grouping = y) # Train QDA model
  pred <- predict(qda.fit, new_x) # Apply model on testing data
  if (pred$posterior[2] > threshold){ # If probability of getting a positive return is greater than threshold
    return (ifelse(y[length(y)-1] > 0 & y[length(y)-2] > 0 & y[length(y)-3] > 0, 1, 0)) # Invest if in a good momentum 
  } else { # Else
    return (0) # Do not invest...
  }
}

Function doQDA performs backtesting evaluation by creating a portfolio by using QDA technique, given a specific decision threshold. The funcion return a performance metric as a result.

doQDA <- function(threshold = 0.5){
  portf_weight_QDA <- matrix(0, nrow = length(dates_oos)-1, ncol = length(tick)) # Initiate weight matrix for QDA portfolio
  portf_return_QDA <- c() # # Initiate return list for QDA portfolio
  for (i in 1:length(tick)){ # Repeat for all ticker
    temp <- selectFeature(ticker = tick[i]) # Feature selection for ticker i 
    w = c() # Initiate an empty list of weight for ticker i
    for (t in 2:(length(dates_oos))){ # Expandind window for out-of-sample dates
      past_data <- temp %>% filter(Date < dates_oos[t-1]) # Past data is all data from the date before date t
      current_data <- temp %>% filter(Date == dates_oos[t-1]) # Current data is data at date t
      res <- weight_QDA(past_data = past_data, current_data = current_data, threshold = threshold) # Get weight using QDA method with specific                                                                                                       threshold
      w = rbind(w, res) # Append date for stock i at period t
    }
    portf_weight_QDA[,i] <- w # Append weight for stock i for all period t
  }
  portf_weight_QDA <- getEW(portf_weight_QDA) # Normalise weight (Equally weighted)
  for(j in 1:(length(dates_oos)-1)){ 
    portf_return_QDA[j] <- sum(portf_weight_QDA[j,] * returns_oos[j,]) # Calculate portfolio return
  }
  return (perf_met(portf_return_QDA, portf_weight_QDA, asset_return)) # Get performance metrics
}

Next, create a portfolio based on QDA strategy and evaluate the performance

doQDA()
4.3.1.1.2 Impact of Decision Threshold

In practice, decision threshold need to be set along trading days. For those investors who strongly believe the QDA model, they can have a low threshold which interpret a low probability of having a positive return as a buy signal. Conversely, investors that do not fully trust the model can set a high threshold to only buy an asset when the probability is very high. If the decision threshold is not set, QDA default value is 0.5 or known as predicting a class with higher probability

However, decision threshold can be set to maximize the portfolio performance. Below, decision threshold is varied from 0.1 to 0.9 and the performance metrics are plotted to illustrate the impact of decision threshold. However, in practice, threshold below 0.5 should not be choosen since there is higher probabilty of having a negative return.

if(!require(ggplot2)){install.packages('ggplot2')}
if(!require(reshape2)){install.packages('reshape2')}
library(ggplot2)
library(reshape2)
perf_met_QDA <- data.frame(matrix(ncol = 6, nrow = 0)) # Initiate dataframe for performance metric of QDA with different parameter values
colnames(perf_met_QDA) <- c('threshold', 'avg_ret', 'vol', 'Sharpe_ratio', 'VaR_5', 'turn') # Set column names
# Load result which has already been computed to reduce runtime
perf_met_QDA <- read.csv('QDA_01.csv', header=TRUE, sep=",")
# If haven't computed before, uncomment the following block
#------------------------------------------------------------------------------#
# for (threshold in seq(0.1, 0.9, 0.1)){ # Loop through set of threshold value (Hyperparameter tuning)
#   perf_met_QDA <- rbind(perf_met_QDA,  cbind(threshold, t(unlist(doQDA(threshold = threshold))))) # Train/test model and store perf. met.
# }
# write.csv(perf_met_QDA, file = "QDA_01.csv", row.names=FALSE) # Keep as csv file on local
#------------------------------------------------------------------------------#
plot_data <- melt(perf_met_QDA  ,  id.vars = 'threshold', variable.name = 'series') # Plot performance evaluation for different value of hy.par.
ggplot(plot_data, aes(threshold, value)) + 
  geom_line(colours = "blue") + 
  facet_grid(series ~ ., scale = "free_y") +
  scale_x_continuous(breaks = seq(0.1, 0.9, 0.1))

Obviously, the optimal decision threshold which provide the best performance for most metrics is about 0.6. It yields the higer average return, sharpe ratio, and value-at-risk with lower volatility. Optimal decision threshold could be search again at values around 0.6.

4.3.1.1.3 Result: Peformance Metrics for QDA
perf_met_QDA <- data.frame(matrix(ncol = 6, nrow = 0)) # Initiate dataframe for performance metric of QDA with different parameter values
colnames(perf_met_QDA) <- c('threshold', 'avg_ret', 'vol', 'Sharpe_ratio', 'VaR_5', 'turn') # Set column names
# Load result which has already been computed to reduce runtime 
perf_met_QDA <- read.csv('QDA_02.csv', header=TRUE, sep=",")
# If haven't computed before, uncomment the following block
#------------------------------------------------------------------------------#
# for (threshold in seq(0.6, 0.7, 0.01)){ # Loop through set of threshold value (Hyperparameter tuning)
#   perf_met_QDA <- rbind(perf_met_QDA,  cbind(threshold, t(unlist(doQDA(threshold = threshold))))) # Train/test model and store perf. met.
#}
# write.csv(perf_met_QDA, file = "QDA_02.csv", row.names=FALSE) # Keep as csv file on local
#------------------------------------------------------------------------------#
plot_data <- melt(perf_met_QDA  ,  id.vars = 'threshold', variable.name = 'series') # Plot performance evaluation for different value of hy.par.
ggplot(plot_data, aes(threshold, value)) + 
  geom_line() + 
  facet_grid(series ~., scale = "free_y") +
  scale_x_continuous(breaks = seq(0.6, 0.7, 0.01))

In conclusion, optimal decision threshold is choosen at 0.65 which yields the best of most performance metrics. Below is the full performance metrics at optimal decision threshold.

perf_met_QDA_opt <- perf_met_QDA %>% # Get performance metric for threshold = 0.6
  filter(threshold == 0.65) %>%
  dplyr::select(-(threshold))
perf_met_QDA_opt <- cbind("method" = "QDA", perf_met_QDA_opt) # Add method name
perf_met_QDA_opt

Important: Once again, decision threshold need to be set along trading days. A good rule of thumb is to set it at 0.5 or adjust it to suit the investor’s confident on the model.

4.3.1.2 K-Nearest Neighbour

With KNN method, we factorised the future returns using 3 criteria: percentile and momentum with 1 and 3 periods, then the model is trained with the features selected by LASSO algorithm. Lastly, equal weight is applied to the stocks with ‘buy’ signals.

Nonetheless, there is a lost of information when strategies portfolio this way.

  1. For percentile categories, we need to sacrifice some first chunk of past data to create the percentile. Here, we start computing the signals after 36 first periods. (dataset = day 37-229)
outputs_P <- matrix(0, nrow = length(dates_all)-36, ncol = length(tick))
colnames(outputs_P) <- tick
for (i in 1:length(tick)) {
   class_P <- P_sig(as.matrix(returns[tick[i]][37:length(dates_all),]),
                    as.matrix(returns[tick[i]]), 37)
   outputs_P[,i] = class_P
}
  1. We will lose the last observation for 1 momentum critaria as there is no future returns for the last period. (dataset = day 2-229)
outputs_MoM.1 <- matrix(0, nrow = length(dates_all)-1, ncol = length(tick))
colnames(outputs_MoM.1) <- tick
for (i in 1:length(tick)) {
  a_table <- cbind(as.matrix(returns[tick[i]][2:(length(dates_all)),]),
                  as.matrix(returns[tick[i]][1:(length(dates_all)-1),]))
  class_MoM.1 <- MoM.1_sig(a_table)
  outputs_MoM.1[,i] = class_MoM.1[,3]
}
  1. For 3 momentum periods, it costs 7 observations. We lose the first 4 to consider previous momemtum for the first case and the last 3 for not having future momemtum. (dataset = day 5-226)
outputs_MoM.3 <- matrix(0, nrow = (length(dates_all)-7), ncol = length(tick) )
colnames(outputs_MoM.3) <- tick
for (i in 1:length(tick)) {
  zeros <- matrix(c(0,0,0), nrow = 3)
  F_vector <- rbind(zeros,as.matrix(returns[tick[i]][5:(length(dates_all)-3),]))
  P_vector <- as.matrix(returns[tick[i]][1:(length(dates_all)-4),])
  #P_vector <- P_vector[1:(nrow(P_vector)-1),]
  a_table <- cbind(F_vector, P_vector)
  class_MoM.3 <- MoM.3_sig(a_table)
  outputs_MoM.3[,i] = class_MoM.3[,3]
}

Therefore, for consistency, we reduce all dataset for categorical outputs to all start on day 37 and end on day 226.

outputs_P <- outputs_P[1:190,]
outputs_MoM.1 <- outputs_MoM.1[36:225,]
outputs_MoM.3 <- outputs_MoM.3[33:nrow(outputs_MoM.3),]
oos_start_cat <- ((length(dates_all)-length(dates_oos))+1) - 37 
oos_length_cat <- 190 - oos_start_cat + 1
oos_ret_cat <- as_tibble(returns) %>% 
  slice(oos_start_cat:190) %>%
  dplyr::select(-Date) %>%                                   
  as.matrix()    

#due to trimming of dataframe for categorical outputs, the end date 
timeSlices_cat_train <- createTimeSlices(1:oos_start_cat-1, 
                   initialWindow = 36, horizon = 12, fixedWindow = TRUE)
4.3.1.2.1 Train KNN with percentile
for (i in 1:length(tick)){
  a <- selectFeature2(tick[i],outputs_P) %>%
    mutate(output = as.factor(output))          
  a_pred <- train(output ~ ., data = a[unlist(timeSlices_train),], 
                  method = "knn", 
                  preProc = c("center", "scale"))
  a_actual.future_test = a[oos_start_cat:nrow(a),] %>%
    mutate(output = as.factor(output))          
  a_pred.future_test <- predict(a_pred, a_actual.future_test)       # get prediction
  signals_knn_P_cat[,i]  <- as.numeric(as.character(a_pred.future_test))   
  # combine signals for all stocks
  print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10
[1] 11
[1] 12
[1] 13
[1] 14
[1] 15
[1] 16
[1] 17
[1] 18
[1] 19
[1] 20
[1] 21
[1] 22
[1] 23
[1] 24
[1] 25
[1] 26
[1] 27
[1] 28
[1] 29
[1] 30
[1] 31
[1] 32
[1] 33
[1] 34
[1] 35
[1] 36
[1] 37
[1] 38
[1] 39
[1] 40
[1] 41
[1] 42
[1] 43
[1] 44
[1] 45
[1] 46
[1] 47
[1] 48
[1] 49
[1] 50
[1] 51
[1] 52
[1] 53
[1] 54
[1] 55
[1] 56
[1] 57
[1] 58
[1] 59
[1] 60
[1] 61
[1] 62
[1] 63
[1] 64
[1] 65
[1] 66
[1] 67
[1] 68
[1] 69
[1] 70
[1] 71
[1] 72
[1] 73
[1] 74
[1] 75
[1] 76
[1] 77
[1] 78
[1] 79
[1] 80
[1] 81
[1] 82
[1] 83
[1] 84
[1] 85
[1] 86
[1] 87
[1] 88
[1] 89
[1] 90
[1] 91
[1] 92
[1] 93
[1] 94
[1] 95
[1] 96
[1] 97
[1] 98
[1] 99
[1] 100
[1] 101
[1] 102
[1] 103
[1] 104
[1] 105
[1] 106
[1] 107
[1] 108
[1] 109
[1] 110
[1] 111
[1] 112
[1] 113
[1] 114
[1] 115
[1] 116
[1] 117
[1] 118
[1] 119
[1] 120
[1] 121
[1] 122
[1] 123
[1] 124
[1] 125
[1] 126
[1] 127
[1] 128
[1] 129
[1] 130
[1] 131
[1] 132
[1] 133
[1] 134
[1] 135
[1] 136
[1] 137
[1] 138
[1] 139
[1] 140
[1] 141
[1] 142
[1] 143
[1] 144
[1] 145
[1] 146
[1] 147
[1] 148
[1] 149
[1] 150
[1] 151
[1] 152
[1] 153
[1] 154
[1] 155
[1] 156
[1] 157
[1] 158
[1] 159
[1] 160
[1] 161
[1] 162
[1] 163
[1] 164
[1] 165
[1] 166
[1] 167
[1] 168
[1] 169
These variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_Cap
[1] 170
[1] 171
[1] 172
[1] 173
[1] 174
[1] 175
[1] 176
[1] 177
[1] 178
[1] 179
[1] 180
[1] 181
[1] 182
[1] 183
[1] 184
These variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yieldThese variables have zero variances: Div_yield
[1] 185
[1] 186
[1] 187
[1] 188
[1] 189
[1] 190
[1] 191
[1] 192
[1] 193
[1] 194
[1] 195
[1] 196
[1] 197
[1] 198
[1] 199
[1] 200
[1] 201
[1] 202
[1] 203
[1] 204
[1] 205
[1] 206
[1] 207
[1] 208
[1] 209
4.3.1.2.2 Train KNN with 1-period momentum
for (i in 1:length(tick)){
  a <- selectFeature2(tick[i], outputs_MoM.1) %>%
    mutate(output = as.factor(output))          
  a_pred <- train(output ~ ., data = a[unlist(timeSlices_train),], 
                  method = "knn", 
                  preProc = c("center", "scale"))
  a_actual.future_test = a[oos_start_cat:nrow(a),] %>%
    mutate(output = as.factor(output))          
  a_pred.future_test <- predict(a_pred, a_actual.future_test)       # get prediction
  
  signals_knn_MoM.1_cat[,i]  <- as.numeric(as.character(a_pred.future_test))   
    # combine signals for all stocks
  print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10
[1] 11
[1] 12
[1] 13
[1] 14
[1] 15
[1] 16
[1] 17
[1] 18
[1] 19
[1] 20
[1] 21
[1] 22
[1] 23
[1] 24
[1] 25
[1] 26
[1] 27
[1] 28
[1] 29
[1] 30
[1] 31
[1] 32
[1] 33
[1] 34
[1] 35
[1] 36
[1] 37
[1] 38
[1] 39
[1] 40
[1] 41
[1] 42
[1] 43
[1] 44
[1] 45
[1] 46
[1] 47
[1] 48
[1] 49
[1] 50
[1] 51
[1] 52
[1] 53
[1] 54
[1] 55
[1] 56
[1] 57
[1] 58
[1] 59
[1] 60
[1] 61
[1] 62
[1] 63
[1] 64
[1] 65
[1] 66
[1] 67
[1] 68
[1] 69
[1] 70
[1] 71
[1] 72
[1] 73
[1] 74
[1] 75
[1] 76
[1] 77
[1] 78
[1] 79
[1] 80
[1] 81
[1] 82
[1] 83
[1] 84
[1] 85
[1] 86
[1] 87
[1] 88
[1] 89
[1] 90
[1] 91
[1] 92
[1] 93
[1] 94
[1] 95
[1] 96
[1] 97
[1] 98
[1] 99
[1] 100
[1] 101
[1] 102
[1] 103
[1] 104
[1] 105
[1] 106
[1] 107
[1] 108
[1] 109
[1] 110
[1] 111
[1] 112
[1] 113
[1] 114
[1] 115
[1] 116
[1] 117
[1] 118
[1] 119
[1] 120
[1] 121
[1] 122
[1] 123
[1] 124
[1] 125
[1] 126
[1] 127
[1] 128
[1] 129
[1] 130
[1] 131
[1] 132
[1] 133
[1] 134
[1] 135
[1] 136
[1] 137
[1] 138
[1] 139
[1] 140
[1] 141
[1] 142
[1] 143
[1] 144
[1] 145
[1] 146
[1] 147
[1] 148
[1] 149
[1] 150
[1] 151
[1] 152
[1] 153
[1] 154
[1] 155
[1] 156
[1] 157
[1] 158
[1] 159
[1] 160
[1] 161
[1] 162
[1] 163
[1] 164
[1] 165
[1] 166
[1] 167
[1] 168
[1] 169
These variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_CapThese variables have zero variances: Ret_Cap
[1] 170
[1] 171
[1] 172
[1] 173
[1] 174
[1] 175
[1] 176
[1] 177
[1] 178
[1] 179
[1] 180
[1] 181
[1] 182
[1] 183
[1] 184
[1] 185
[1] 186
[1] 187
[1] 188
[1] 189
[1] 190
[1] 191
[1] 192
[1] 193
[1] 194
[1] 195
[1] 196
[1] 197
[1] 198
[1] 199
[1] 200
[1] 201
[1] 202
[1] 203
[1] 204
[1] 205
[1] 206
[1] 207
[1] 208
[1] 209
4.3.1.2.3 Result: Peformance Metrics for KNN (Categorical)

4.3.1.3 Logit Model

This model transform the outputs into probability. Therefore, the outputs are capped between 0 and 1. However, it uses non-normal distribution to transform the output. It consumes less power to perform without the normal distribution constraint, but it costs fatter tails. There is no hyperparameter in this model.

4.3.1.3.1 Train logit model with percentile
4.3.1.3.2 Train logit model with 1-period momentum

Since the condition is rarely meet, some test folds contain information missing in training folds. For this reason, we will skip this method with the assumption that the signals represent a lack in evidence of consistent increasing momentum over 3 periods. In other word, it can be interpreted that 3-period past information is irrelevant in determining significant signals.

For example, considering apple stock autocorrelation below. The figure shows that past information is barely indicative, except only for lag 15-period observation which might be too further away from the current time to have an economic meaning.

4.3.1.3.3 Result: Performance Metrics for Logit Model

4.3.1.4 Probit Model

Similar to logit model, this model transform also the outputs into probability, which cap outputs between 0 and 1. On a contrary to logit model, it uses normal distribution to transform the output.

4.3.1.4.1 Train probit model with percentile
4.3.1.4.2 Train probit model with 1-period momentum

addadd –> for the same reason, no mom3 for probit

4.3.1.4.3 Result: Performance Metrics for Probit Model

5 Analysis

To assess the performance of each machine learning technique, we focus on the aspect of portfolio. Accordingly, we score each aspect of performance metrics from 1 to 18 for 18 methods, where 1 is the best in that category and 18 is the worst. Thus, we call this score ‘rank’. Namely, rank 1 for average return, sharpe ratio and VaR are the highest, while rank 1 for volatility and turnover are the lowest.

Table 1: Summary table for performance metrics & rank

Text
Text

From the table, our benchmark portfolio is quite high in the rank at #5. The all ML techniques which outperforms benchmark are categorical dependent variable with signals, except sparse portfolio which is #1. Thus, clearly, sparse portfolio outperforms signal models.

Within the signal models, categorical method outshines the numerical. Consider KNN, which is used for both categorical and numerical outputs. It performs poorly in the numerical outputs in signal models, as seen in the bottom ranks. In contrast, it gives top-ten ranks for categorical one. This might be because KNN algorithm tries to separate outputs into regions based on features, thus; it does a better job for categorical outputs as there are less regions to work with. This is because numerical outputs have more various values which the model needs to learn and fit, hence these models might face overfitting problems.

The rank shows that the simpler models, including probit and logit models, have low-end ranks. Whereas, the complex models, such as random forest and KNN, have high-end ranks. Nonetheless, the intermediate model in terms of complexity, QDA, performs the best among categorical model. This reflects that although the model tends to improve with higher complexity (lower variance), however it will face with high variance in the out-of-sample set. Therefore, only some level of complexity is appropriate, where it minimise both bias and variance.

According to performances of our portfolios, it can be assigned to each investors who prefer different levels of risk. For instance, risk-aversive investors will be willing to allocate their capital on less volatile portfolios such as Sparse portfolio. On the other hand, investors who can bear more risks can choose to invest in more risky portfolios to gain higher returns such as Categorical KNN portfolios. Sparse portfolio and Equally weighted portfolio are recommended for passive portfolio managing investors which will cost significantly less transanction cost than other portfolio strategies.

LS0tDQp0aXRsZTogIk1hY2hpbmUgTGVhcm5pbmcgaW4gRmFjdG9yIEludmVzdGluZyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0KICBodG1sX25vdGVib29rOg0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IGRlZmF1bHQNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogbm8NCiAgd29yZF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KLS0tDQojIEludHJvZHVjdGlvbg0KDQpJbiB0aGlzIHJlcG9ydCwgd2Ugd2lsbCBldmFsdWF0ZSBtYWNoaW5lIGxlYXJuaW5nIGFwcGxpY2F0aW9uIG9uIGludmVzdGluZyBzdHJhdGVnaWVzLiBUaGVyZSBhcmUgc2V2ZXJhbCB3YXlzIHRvIGluY29ycG9yYXRlIG1hY2hpbmUgbGVhcm5pbmcgdGVjaG5pcXVlcyB0byBjb25zdHJ1Y3QgYSBwb3J0Zm9saW9zLiBIb3dldmVyLCB3ZSB3aWxsIGZvY3VzIG9uIDIgbWFpbiBhcHByb2FjaGVzOyBzcGFyc2UgcG9ydGZvbGlvIGFuZCBzaWduYWxzLiANCg0KIyMgTW9kZWxzDQoNCioqIEJlbmNobWFyayBwb3J0Zm9saW8gKioNCldlIG5lZWQgYmVuY2htYXJrIHBvcnRmb2xpbyB0byBldmFsdWF0ZSBvdXIgc3RyYXRlZ2llcy4gRm9yIHNpbXBsaWNpdHksIHdlIHVzZSBlcXVhbGx5LXdlaWdodGVkIHBvcnRmb2xpbyBhcyBvdXIgYmVuY2htYXJrLiBXaGVyZSBpdCBpcyBuZWVkZWQsIHRoZSBzYW1lIHdlaWdodGluZyBzY2hlbWUgd2lsbCBiZSBhcHBsaWVkIHRvIHByZWRpY3RlZCByZXN1bHRzIGZyb20gbWFjaGluZSBsZWFybmluZyBzdHJhdGVnaWVzIHRvIGNsZWFybHkgY29udHJhc3QgdGhlIGltcGFjdCBvZiBoYXZpbmcgaW5jbHVkZWQgbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWVzIGluIHRoZSBzdHJhdGVnaWVzLg0KDQoqKiBTcGFyc2UgcG9ydGZvbGlvICoqDQpTcGFyc2UgcG9ydGZvbGlvIGFwcGxpZXMgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0gdG8gcHJlZGljdCBzdG9jayBmdXR1cmUgcmV0dXJuIGJ5IHVzaW5nIG90aGVyIHN0b2Nrc+KAmSBjdXJyZW50IHJldHVybnMgYXMgZmVhdHVyZXMuICBBbHRob3VnaCBpdCBpcyBub3QgY29udmVudGlvbmFsLCB0aGlzIGFwcHJvYWNoIGdhdmUgaW5zaWdodGZ1bCByZXN1bHRzIGFzIGRpc2N1c3NlZCBpbiB0aGUgY2xhc3MgdGhyb3VnaCBMYXNzbyBtZXRob2QgdGhhdCBpdCBjYW4gYmUgdXNlZCB0byBhc3NpZ24gd2VpZ2h0cyB0byBlYWNoIHN0b2NrIGRpcmVjdGx5LiBIZXJlLCB3ZSB3aWxsIGludmVzdGlnYXRlIHRoZSBtZXRob2RvbG9neSBieSBhcHBseWluZyAqKmVsYXN0aWMgbmV0d29yayoqLiBFbGFzdGljIG5ldHdvcmsgaXMgYSBjb21iaW5hdGlvbiBvZiBMYXNzbyBhbmQgUmlkZ2UgcmVncmVzc2lvbnMuIEJvdGggYXJlIHJlZ3VsYXJpemF0aW9uIG1ldGhvZHMgd2hpY2ggaGVscCB0aGUgbW9kZWwgYXZvaWQgb3ZlcmZpdHRpbmcgcHJvYmxlbSBjYXVzZWQgYnkgaW5jbHVzaW9uIG9mIHRvbyBtYW55IGZlYXR1cmVzLiBUbyBkbyB0aGlzLCB0aGV5IHBlbmFsaXNlIGluc2lnbmlmaWNhbnQgZmVhdHVyZXMsIG1lYW5pbmcgdGhhdCB0aGV5IHJlZHVjZXMgYmV0YSBvZiBlYWNoIGZlYXR1cmUgbGVhdmluZyBsYXJnZSBiZXRh4oCZcyBmb3Igc2lnbmlmaWNhbnQgZmVhdHVyZXMuIE5vbmV0aGVsZXNzLCB0aGUgZGlmZmVyZW5jZSBpcyB0aGF0IFJpZGdlIHJlZHVjZXMgYWxsIGJldGHigJlzIHdpdGhvdXQga2lja2luZyBhbnkgb2YgZmVhdHVyZXMgb3V0IG9mIHRoZSBtb2RlbCAobDIgbm9ybSksIHdoaWxlIExhc3NvIGRvZXMgYm90aCAobDEgbm9ybSkuIFdlIGRlY2lkZWQgdG8gYXBwbHkgZWxhc3RpYyBuZXR3b3JrIGJlY2F1c2UgaXQgYWxsb3dzIHVzIHRvIHV0aWxpc2UgYm90aCBvZiB0aGVpciBhZHZhbnRhZ2VzOyBmZWF0dXJlIHNlbGVjdGlvbiBmcm9tIExhc3NvIHdoaWxlIG5vdCBraWNraW5nIHRoZSBmZWF0dXJlIG91dCB0b28gZmFzdCBmcm9tIFJpZGdlLiBUaGVyZWZvcmUsIGh5cGVycGFyYW1ldGVyIGZvciB0aGlzIG1ldGhvZCBpcyB0aGUgZGVncmVlIHRvIHVzZSBsMSBhbmQgbDIgbm9ybXMsIHdoaWNoIGFyZSBhbHBoYSBhbmQgYWxwaGEtMS4gDQoNCioqIFNpZ25hbHMgKioNClRoaXMgYXBwcm9hY2ggaXMgcmF0aGVyIG1vcmUgY29udmVudGlvbmFsIGFwcGxpY2F0aW9uIG9mIG1hY2hpbmUgbGVhcm5pbmcgdG8gcHJlZGljdCBmdXR1cmUgcmV0dXJucy4gV2Ugd2lsbCB1c2UgdGhlIGZlYXR1cmVzIHByb3ZpZGVkIGZvciBlYWNoIHN0b2NrIHRvIHByZWRpY3QgdGhlaXIgZnV0dXJlIHJldHVybnMgaW4gZWFjaCBwZXJpb2QuICBIb3dldmVyLCB0byBjb25zaWRlciB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBkYXRhLCB3ZSBhbHNvIGFkZCBtZWFuLCB2YXJpYW5jZSwgYW5kIHNrZXduZXNzIG9mIGVhY2ggc3RvY2suIA0KDQpTaWduYWwgYXBwcm9hY2ggY2FuIGJlIHNwbGl0dGVkIGludG8gMiBzdWItYXBwcm9hY2hlczsgbnVtZXJpY2FsIGFuZCBjYXRlZ29yaWNhbCBkZXBlbmRlbnQgdmFyaWFibGVzLiBBcyBmdXR1cmUgcmV0dXJucyBhcmUgbnVtZXJpY2FsIHZhcmlhYmxlLCB3ZSBjYW4gYXBwbHkgb25seSBzb21lIG1hY2hpbmUgbGVhcm5pbmcgdGVjaG5pcXVlcy4gSGVuY2UsIHdlIHdpbGwgdHJhbnNmb3JtIGZ1dHVyZSByZXR1cm5zIGludG8gY2F0ZWdvcmljYWwgdmFyaWFibGUgdG8gYWxsb3cgdXMgdG8gY29uc3RydWN0IGEgcG9ydGZvbGlvIGJ5IHV0aWxpc2luZyBib3RoIHR5cGVzIG9mIGFsZ29yaXRobS4gDQoNCldoaWxlIHN0aWxsIGNvbnNpZGVyaW5nIHRoZSBzaWduIChwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgcmV0dXJucyksIHdlIHdpbGwgd29yayB3aXRoIDMgc2lnbmFsczsgcGVyY2VudGlsZSwgMS1wZXJpb2QgbW9tZW50dW0sIGFuZCAzLXBlcmlvZCBtb21lbnR1bS4gRm9yIG51bWVyaWNhbCBkZXBlbmRlbnQgdmFyaWFibGUsIHdlIHRyYW5zZm9ybSBwcmVkaWN0ZWQgZnV0dXJlIHJldHVybnMgaW50byBzaWduYWxzIGJlZm9yZSBhcHBseSBlcXVhbGx5LXdlaWdodGVkIHNjaGVtZS4gRm9yIGNhdGVnb3JpY2FsIGFwcHJvYWNoLCB3ZSB0cmFuc2Zvcm0gYWN0dWFsIGZ1dHVyZSByZXR1cm5zIGludG8gc2lnbmFscywgdGhlbiB1c2UgdGhlIHNpZ25hbCBhcyBkZXBlbmRlbnQgdmFyaWFibGVzLiBMYXN0bHksIHdlIHVzZSB0aGUgcHJlZGljdGVkIHNpZ25hbHMgdG8gYXBwbHkgZXF1YWxseS13ZWlnaHRlZCBzY2hlbWUuDQoNCiMgRGF0YSBQcmVwYXJhdGlvbg0KIyMgSW1wb3J0IERhdGENCkltcG9ydCBhbmQgcHJldmlldyBkYXRhDQoNCmBgYHtyIHBrZ19pbXBvcnRfZGF0YSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpsb2FkKCdkYXRhX2Z1bGwuUkRhdGEnKQ0KaGVhZChkYXRhKQ0KYGBgDQojIyBEYXRhIFByZS1wcm9jZXNzaW5nDQpJbiBhZGRpdGlvbiB0byB0aGUgZ2l2ZW4gZGF0YSwgd2UgbmVlZCB0byBwZXJmb3JtIHNvbWUgaW1wb3J0YW50IGRhdGEtcHJlcHJvY2Vzc2luZyBpbiBvcmRlciB0byBnZW5lcmF0ZSBuZXcgdmFyaWFibGVzIHRoYXQgYXJlIGVzc2VudGlhbCB0byBvdXIgYW5hbHlzaXMuIFRob3NlIHZhcmlhYmxlcyBpbmNsdWRlIHRoZSBmb2xsb3dpbmc6DQoNCjEuICoqUF9SZXR1cm4qKiAoUGFzdCByZXR1cm4pOiBUaGUgb3JpZ2luYWwgZGF0YSBvbmx5IGNvbnRhaW5zIG1vbnRobHkgY2xvc2luZyBwcmljZSBpbmZvcm1hdGlvbiBmcm9tIHdoaWNoIHdlIGNhbGN1bGF0ZSBwYXN0IG1vbnRobHkgcmV0dXJuLiBJbiBnZW5lcmFsLCByZXR1cm4gaXMgY29tbW9ubHkgdXNlZCByYXRoZXIgdGhhbiBjbG9zaW5nIHByaWNlIGluIHBvcnRmb2xpbyBiYWNrdGVzdGluZy4NCg0KMi4gKipGX1JldHVybioqIChGdXR1cmUgcmV0dXJuKTogT3VyIHkgdmFyaWFibGUgaXMgZnV0dXJlIG1vbnRobHkgcmV0dXJuIHdoaWNoIGNhbiBiZSBvYnRhaW5lZCBieSBsZWFkaW5nIHBhc3QgcmV0dXJuIGJ5IG9uZSBwZXJpb2QuDQoNCjMuICoqTW9tZW50cyBvZiBSZXR1cm4qKiAoKipNZWFuKiosICoqVmFyaWFuY2UqKiwgYW5kICoqU2tld25lc3MqKik6IFdlIGRlY2lkZWQgdG8gZ2VuZXJhdGUgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBpbmNsdWRpbmcgZmlyc3QgdGhyZWUgbW9tZW50cyBvZiBwYXN0IG1vbnRobHkgcmV0dXJuIHRvIG1heGltaXplIHRoZSBiZW5lZml0IG9mIHRoZSBkYXRhIHdlIGhhdmUuDQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0NCmlmKCFyZXF1aXJlKHRpZHl2ZXJzZSkpe2luc3RhbGwucGFja2FnZXMoJ3RpZHl2ZXJzZScpfQ0KaWYoIXJlcXVpcmUoUGVyZm9ybWFuY2VBbmFseXRpY3MpKXtpbnN0YWxsLnBhY2thZ2VzKCdQZXJmb3JtYW5jZUFuYWx5dGljcycpfQ0KaWYoIXJlcXVpcmUoZHBseXIpKXtpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShQZXJmb3JtYW5jZUFuYWx5dGljcykNCmxvYWQoJ2RhdGFfZnVsbC5SRGF0YScpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbG9hZCBkYXRhDQpkYXRhIDwtIGRhdGEgJT4lIGFycmFuZ2UoRGF0ZSxUaWNrKQ0KdGljayA8LSBsZXZlbHMoZGF0YSRUaWNrKSANCmRhdGEgPC0gZGF0YSAgJT4lIA0KICAgIGdyb3VwX2J5KFRpY2spICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgR3JvdXAgYXNzZXQgYnkgYXNzZXQNCiAgICBtdXRhdGUoUF9SZXR1cm4gPSBDbG9zZSAvIGxhZyhDbG9zZSkgLSAxKSAlPiUgICAjIEFkZGluZyBwYXN0IHJldHVybnMNCiAgICBtdXRhdGUoRl9SZXR1cm4gPSBsZWFkKFBfUmV0dXJuKSkgJT4lICAgICAgICAgICAjIEFkZGluZyBmb3J3YXJkIHJldHVybnMNCiAgICBuYS5vbWl0KCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERyb3AgYWxsIE5BIHJvdyBiZWZvcmUgY2FsY3VsYXRlIG1vbWVudHMNCiAgICBtdXRhdGUobWVhbiA9IGFwcGx5LmZyb21zdGFydChQX1JldHVybiwgRlVOID0gJ21lYW4nICwgZ2FwID0gMSkpICU+JSAgICAgICAgICAgICAgIyBBZGRpbmcgcGFzdCByZXR1cm4gbWVhbg0KICAgIG11dGF0ZSh2YXJpYW5jZSA9IGFwcGx5LmZyb21zdGFydChQX1JldHVybiwgRlVOID0gJ3ZhcicgLCBnYXAgPSAxKSkgJT4lICAgICAgICAgICAjIEFkZGluZyBwYXN0IHJldHVybiB2YXJpYW5jZQ0KICAgIG11dGF0ZShza2V3bmVzcyA9IGFwcGx5LmZyb21zdGFydChQX1JldHVybiwgRlVOID0gJ3NrZXduZXNzJyAsIGdhcCA9IDEpKSAlPiUgICAgICAjIEFkZGluZyBwYXN0IHJldHVybiBza2V3bmVzcw0KICAgIG5hLm9taXQoKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERyb3AgYWxsIE5BIHJvdw0KICAgIHVuZ3JvdXAoKSAgDQpoZWFkKGRhdGEpDQpgYGANCg0KQmVmb3JlIHN0YXJ0aW5nIHRoZSBhbmFseXNpcywgd2Ugd291bGQgbGlrZSB0byBpbnZlc3RpZ2F0ZSBjb3JyZWxhdGlvbiBhbmQgYXV0b2NvcnJlbGF0aW9uIGJldHdlZW4gdGhlIGZlYXR1cmVzIGluIG9yZGVyIHRvIGJlIGF3YXJlIG9mIGFueSBoaWRkZW4gcmVsYXRpb25zaGlwIGFtb25nIHRoZSBleHBsYW5hdG9yeSB2YXJpYWJsZXMuIEZpcnN0LCB3ZSBmaW5kIGNvcnJlbGF0aW9uIGJldHdlZW4gZXhwbGFuYXRvcnkgdmFyaWFibGVzLg0KDQpgYGBgYGB7cn0NCmNvcnIgPC0gZGF0YSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtb25lX29mKCdEYXRlJywgJ1RpY2snLCAnRl9SZXR1cm4nKSkNCmNvcnIgPC0gZGF0YS5mcmFtZShjb3IoY29ycikpDQpjb3JyDQpgYGANCg0KVGhlcmUgYXJlIDMgcGFpcnMgb2YgaW50ZXJlc3RpbmcgY29ycmVsYXRpb24gYmV0d2VlbiBleHBsYW5hdG9yeSB2YXJpYWJsZXMuDQoNCjEuIFJTSSBwb3N0aXZlbHkgY29ycmVsYXRlZCB3aXRoIFBhc3QgUmV0dXJuICgwLjkyKQ0KMi4gRDJFIHBvc2l0aXZlbHkgY29ycmVsYXRlZCB3aXRoIFAyQiAoMC42NCkNCjMuIFZvbDFNIG5lZ2F0aXZlbHkgY29ycmVsYXRlZCB3aXRoIENsb3NlICgtMC42NikNCg0KSXQgaXMgaW1wb3J0YW50IHRvIGJlIGF3YXJlIHRoYXQgb3ZlcmZpdHRpbmcgbWlnaHQgb2NjdXIgaWYgdGhlIG1vZGVsIHVzZXMgdGhlIHRyYWluaW5nIGRhdGEgd2hpY2ggaW5jbHVkZXMgMiBvciBtb3JlIHBvc2l0aXZlbHkgY29ycmVsYXRlZCB2YXJpYWJsZXMgZHVlIHRvIHRoZWlyIGluZm9ybWF0aW9uIHJlZHVuZGFuY3kuIA0KDQpBcGFydCBmcm9tIGNvcnJlbGF0aW9uIGJldHdlZW4gZXhwbGFuYXRvcnkgdmFyaWFibGVzLCB3ZSBzaG91bGQgYWxzbyBiZSBjb25zaWRlcmluZyB0aGUgYXV0b2NvcnJlbGF0aW9uIGZvciBlYWNoIGZlYXR1cmUuIEJlbG93IGlzIHRoZSBhbmFseXNpcyBvZiBhdXRvY29ycmVsYXRpb24gZm9yIG91ciB4IHZhcmlhYmxlcy4gSXQgaXMgb2J2aW91cyB0aGFuIHRoZXJlIGlzIG5vdCBhbnkgc2lnbmlmaWNhbnQgYXV0b2NvcnJlbGF0aW9uIGZvciBhbnkgZmVhdHVyZS4NCg0KYGBgYGBge3J9DQojIGF1dG9jb3JyIDwtIGRhdGEgJT4lDQojICAgZHBseXI6OnNlbGVjdCgtb25lX29mKCdEYXRlJywgJ1RpY2snLCAnRl9SZXR1cm4nKSkNCiMgdGVtcF9jID0gYygpDQojIGZvciAoaSBpbiBzZXEoMSxsZW5ndGgoY29sbmFtZXMoYWNmKSksMSkpew0KIyAgIHRlbXBfYyA8LSBjYmluZCh0ZW1wX2MsIGNvcihhdXRvY29yclsxOm5yb3coYXV0b2NvcnIpLTEsaV0sIGF1dG9jb3JyWzI6bnJvdyhhdXRvY29yciksaV0pKQ0KIyB9DQojIHJvd25hbWVzKHRlbXBfYykgPC0gYygpDQojIGFjZiA8LSBkYXRhLmZyYW1lKHRlbXBfYykNCiMgYWNmDQpgYGANCg0KIyMgU3BsaXQgZGF0YQ0KV2l0aGluIG91ciBtb250aGx5IGRhdGEgb3ZlciAyNSB5ZWFycywgdGhlcmUgYXJlIDIyOSBkYXRlcyBmcm9tIEFwcmlsIDE5OTQgdG8gQXByaWwgMjAxOC4gV2UgZGVjaWRlZCB0byBzcGxpdCB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIHNldCBhbmQgdGVzdGluZyBzZXQgdXNpbmcgNzAvMzAgcmF0aW8uIFNpbmNlIHdlIGFyZSBkZWFsaW5nIHdpdGggdGltZXNlcmllcyBkYXRhLCB3ZSBzaW1wbHkgc3BsaXQgdGhlIGRhdGFzZXQgdXNpbmcgdGhlIHNlcGFyYXRpb24gZGF0ZSB3aGljaCB3aWxsIGdpdmUgc3VjaCBkZXNpcmFibGUgcmF0aW8sIEphbnVhcnkgMjAxMy4NCg0KDQpgYGBgYGB7cn0NCmRhdGVzX2FsbCA8LSB1bmlxdWUoZGF0YSREYXRlKSAgICAgICAgICAgICAgIyBTZWxlY3QgYWxsIHVuaXF1ZSBkYXRlcw0Kc2VwX2RhdGUgPC0gYXMuRGF0ZSgiMjAxMy0wMS0wMiIpICAgICAgICAgICAjIFNldCB0aGUgc2VwZXJhdGlvbiBkYXRlIGZvciB0cmFpbmluZyBzZXQgYW5kICAgICAgICAgIG91dC1vZi1zYW1wbGUNCmRhdGVzX29vcyA8LSBkYXRlc19hbGxbZGF0ZXNfYWxsID49IHNlcF9kYXRlXSAgICAgICAgIyBPdXQgb2Ygc2FtcGxlIHNldA0KYGBgDQoNCiMgUHJlLWRlZmluZWQgZnVuY3Rpb25zIGFuZCB2YXJpYWJsZXMNCkJlbG93IGluY2x1ZGVzIHRoZSBnbG9iYWwgZnVuY3Rpb25zIGFuZCB2YXJpYWJsZXMsIHdoaWNoIGFyZSB1dGlsaXNlZCB0aHJvdWdob3V0IHRoZSBmaWxlLiANCg0KIyMgUmV0dXJucw0KDQpgYGB7cn0NCnJldHVybnMgPC0gZGF0YSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgZHBseXI6OnNlbGVjdChEYXRlLCBUaWNrLCBQX1JldHVybikgJT4lICAgICAgICANCiAgICBzcHJlYWQoa2V5ID0gVGljaywgdmFsdWUgPSBQX1JldHVybikgICAgDQpoZWFkKHJldHVybnMpDQoNCmFzc2V0X3JldHVybiA8LSBmaWx0ZXIocmV0dXJucywgRGF0ZT5zZXBfZGF0ZSkgICANCg0KcmV0dXJuc19vb3MgPC0gZGF0YSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgZHBseXI6OnNlbGVjdChEYXRlLCBUaWNrLCBQX1JldHVybikgJT4lICAgICAgIA0KICAgIHNwcmVhZChrZXkgPSBUaWNrLCB2YWx1ZSA9IFBfUmV0dXJuKSAlPiUNCiAgICBmaWx0ZXIoRGF0ZSA+IHNlcF9kYXRlKSAlPiUNCiAgICBkcGx5cjo6c2VsZWN0KC1EYXRlKQ0KDQpgYGANCg0KIyMgRXF1YWxseS13ZWlnaHRlZCBjYWxjdWxhdGlvbg0KYGBge3J9DQpnZXRFVyA8LSBmdW5jdGlvbih3ZWlnaHQpeyAjIERlZmluZSBhIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSBlcXVhbGx5LXdlaWdodGVkIHBvcnRmb2xpbw0KICBldyA8LSAodChzY2FsZSh0KHdlaWdodCksIGNlbnRlcj1GQUxTRSwgc2NhbGU9Y29sU3Vtcyh0KHdlaWdodCkpKSkpICAjIE5vcm1hbGlzZSB3ZWlnaHQgKEVxdWFsbHkgd2VpZ2h0ZWQpDQogIGV3W2lzLm5hKGV3KV0gPC0gMA0KICByZXR1cm4gKGV3KQ0KfQ0KYGBgDQoNCiMjIEZlYXR1cmUgU2VsZWN0aW9uIDEgDQpEZWZpbmUgYSBmdW5jdGlvbiBmb3IgZmVhdHVyZSBzZWxlY3Rpb24gZm9yIGVhY2ggc3RvY2sgdXNpbmcgbGFzc28uIFRoZSBmdW5jdGlvbiByZWNlaXZlcyBzdG9jayB0aWNrZXIgYXMgYSBwYXJhbWV0ZXIgYW5kIHJldHVybnMgYSBkYXRhZnJhbWUgd2l0aCBvbmx5IHNlbGVjdGVkIGV4cGxhbmF0b3J5IHZhcmlhYmxlcy4gVGhlIGZ1bmN0aW9uIHVzZXMgKipnbG1uZXQqKiBwYWNrYWdlIHRvIGJ1aWxkIGEgbGFzc28gbW9kZWwgdXNpbmcgKmxhbWJkYS5taW4qIHdoaWNoIG1pbmltaXplIHRoZSBtZWFuIGNyb3NzLXZhbGlkYXRlIGVycm9yLiBJbiBnZW5lcmFsLCBMQVNTTyBpcyBhIGNvbW1vbiB0b29sIGZvciBmZWF0dXJlIHNlbGVjdGlvbiBkdWUgdG8gaXRzIHBlbmFsaXNlZCB0ZXJtcyBpbiBvYmplY3RpdmUgZnVuY3Rpb24uIEFueSB2YXJpYWJsZXMgd2l0aCBpbnNpZ2ZpY2FudCBtZWFuaW5nIHRvIG91ciByZXNwb25zZSB2YXJpYWJsZSB3aWxsIGJlIGVsaW1pbmF0ZWQgYnkgbGFzc28uIFRoZXJlIGlzIGEgc3BlY2lhbCBjYXNlIGluIHdoaWNoIGxhc3NvIGVsaW1pbmF0ZXMgYWxsIGZlYXR1cmVzLiBBcyBhIHJlc3VsdCwgcGFzdCByZXR1cm4gKFBfUmV0dXJuKSBpcyB1c2VkIGZvciB0aGUgb25seSBmZWF0dXJlLg0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQppZighcmVxdWlyZShnbG1uZXQpKXtpbnN0YWxsLnBhY2thZ2VzKCdnbG1uZXQnKX0NCmxpYnJhcnkoZ2xtbmV0KQ0Kc2VsZWN0RmVhdHVyZSA8LSBmdW5jdGlvbih0aWNrZXIpew0KICBzZXQuc2VlZCg5KSAjIFNldCBzZWVkIGZvciBjb25zaXN0ZW5jeQ0KICB0ZW1wIDwtIGRhdGEgJT4lIGZpbHRlcihUaWNrID09IHRpY2tlcikgJT4lICMgRmlsdGVyIHRpY2tlcg0KICAgICAgZHBseXI6OnNlbGVjdCgtb25lX29mKCdUaWNrJywgJ0RhdGUnLCdDbG9zZScpKSAjIERyb3AgdGljaywgZGF0ZSwgYW5kIGNsb3NlIGNvbHVtbnMNCiAgbGFzc28gPC0gY3YuZ2xtbmV0KHggPSBhcy5tYXRyaXgodGVtcFssLXdoaWNoKG5hbWVzKHRlbXApID09ICJGX1JldHVybiIpXSksIHkgPSBhcy5tYXRyaXgodGVtcFssd2hpY2gobmFtZXModGVtcCkgPT0gIkZfUmV0dXJuIildKSkgIyBUcmFpbiBsYXNzbyBtb2RlbA0KICBzdWJzZXQgPSBjb2VmKGxhc3NvLCBzID0gImxhbWJkYS5taW4iKUBEaW1uYW1lc1tbMV1dW2NvZWYobGFzc28sIHMgPSAibGFtYmRhLm1pbiIpQGkgKyAxXSAjIEdldCBsaXN0IG9mIHJlbWFpbmluZyB2YXJpYWJsZXMNCiAgc3Vic2V0ID0gIHN1YnNldFshc3Vic2V0ID09ICIoSW50ZXJjZXB0KSJdICMgR2V0IHJpZCBvZiBpbnRlcmNlcHQgdmFsdWUNCiAgaWYobGVuZ3RoKHN1YnNldCkgPT0gMCl7ICMgSWYgdGhlcmUgaXMgbm8gcmVtYWluaW5nIHZhcmlhYmxlDQogICAgc3Vic2V0ID0gYygnUF9SZXR1cm4nKSAjIFVzZSBQX1JldHVybiBhcyBvbmx5IGV4cGxhbmF0b3J5IHZhcmlhYmxlDQogIH0NCiAgcmV0dXJuIChkYXRhICU+JSBmaWx0ZXIoVGljayA9PSB0aWNrZXIpICU+JSAjIFJldHVybiBkYXRhZnJhbWUgd2l0aCBEYXRlLCBGX1JldHVybiwgYW5kIHRoZSBzZWxlY3RlZCBleHBsYW5hdG9yeSB2YXJpYWJsZXMNCiAgICBkcGx5cjo6c2VsZWN0KGNiaW5kKCdEYXRlJywnRl9SZXR1cm4nLHN1YnNldCkpKQ0KfQ0KYGBgDQoNClRlc3QgdGhlIGZ1bmN0aW9uIGJ5IHNlbGVjdGluZyBmZWF0dXJlIGZvciBBcHBsZSBzdG9jaw0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpzZWxlY3RGZWF0dXJlKCdBQVBMJykNCmBgYA0KDQpGb3IgQXBwbGUsIGxhc3NvIGNob29zZXMgNiBtZWFuaW5nZnVsIGZlYXR1cmVzIGluY2x1ZGluZyAqTWt0X0NhcCosICpWb2xfMU0qLCAqUHJvZl9ncm93dGgqLCAqbWVhbiosICp2YXJpYW5jZSosIGFuZCAqc2tld25lc3MqLg0KDQojIyBGZWF0dXJlIFNlbGVjdGlvbiAyDQpVc2UgKnNpZ25hbCogb2YgZnV0dXJlIHJldHVybnMgYXMgZGVwZW5kZW50IHZhcmlhYmxlIGFuZCBwZXJmb3JtIGZlYXR1cmUgc2VsZWN0aW9uIHVzaW5nIGxhc3NvIGZvciBlYWNoIHN0b2NrLg0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQppZighcmVxdWlyZShnbG1uZXQpKXtpbnN0YWxsLnBhY2thZ2VzKCdnbG1uZXQnKX0NCmxpYnJhcnkoZ2xtbmV0KQ0Kc2VsZWN0RmVhdHVyZTIgPC0gZnVuY3Rpb24odGlja2VyLHNpZ19kYXQpew0KICAgICNzaWdfZGF0OiBtYXRyaXggd2l0aCBzaWduYWwgb3V0cHV0cyBmb3IgZWFjaCBzdG9jaw0KICBzZXQuc2VlZCg5KQ0KICB0ZW1wIDwtIGRhdGEgJT4lIGZpbHRlcihUaWNrID09IHRpY2tlcikgJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KC1vbmVfb2YoJ0RhdGUnLCdDbG9zZScpKQ0KICB0ZW1wIDwtIHRlbXBbLWMoMSldDQogIHRlbXAgPC0gdGVtcFszNzoyMjYsXSAjd2UgdHJpbSB0aGUgZGF0YXNldCBmb3IgY2F0ZWdvcmljYWwsIHRoZSByZWFzb24gaXMgZXhwbGFpbmVkIGluIHRoZSBjYXRlZ29yaWFsIHNlY3Rpb24gYmVsb3cuDQogIHkgPSBhcy50aWJibGUoc2lnX2RhdCkgJT4lDQogICAgZHBseXI6OnNlbGVjdCh0aWNrZXIpIA0KICB5ID0gYXMubWF0cml4KHkpDQogIGNvbG5hbWVzKHkpIDwtICJ5Ig0KICBsYXNzbyA8LSBjdi5nbG1uZXQoeCA9IGFzLm1hdHJpeCh0ZW1wWywtd2hpY2gobmFtZXModGVtcCkgPT0gIkZfUmV0dXJuIildKSwgDQogICAgICAgICAgICAgICAgICAgICB5ID0geSkNCiAgc3Vic2V0ID0gY29lZihsYXNzbywgcyA9ICJsYW1iZGEubWluIilARGltbmFtZXNbWzFdXVtjb2VmKGxhc3NvLCBzID0gImxhbWJkYS5taW4iKUBpICsgMV0NCiAgc3Vic2V0ID0gIHN1YnNldFshc3Vic2V0ID09ICIoSW50ZXJjZXB0KSJdDQogIGlmKGxlbmd0aChzdWJzZXQpID09IDApew0KICAgIHN1YnNldCA9IGMoJ1BfUmV0dXJuJykNCiAgfQ0KICByZXN1bHQgPC0gKGRhdGEgJT4lIGZpbHRlcihUaWNrID09IHRpY2tlcikgJT4lDQogICAgZHBseXI6OnNlbGVjdChjYmluZCgnRGF0ZScsc3Vic2V0KSkpICU+JQ0KICAgIHNsaWNlKDM3OjIyNikgJT4lDQogICAgbXV0YXRlKG91dHB1dCA9IHkpDQp9DQpgYGANCg0KIyMgUGVyZm9ybWFuY2UgTWV0cmljcyANCmBgYHtyIHNldCB1cCBwZXJmb3JtYW5jZSBNZXRyaWNzfQ0KcGVyZl9tZXQgPC0gZnVuY3Rpb24ocG9ydGZfcmV0dXJucywgd2VpZ2h0cywgYXNzZXRfcmV0dXJucyl7DQogICAgYXZnX3JldCA8LSBtZWFuKHBvcnRmX3JldHVybnMsIG5hLnJtID0gVCkgICAgICAgICAgICAgICAgICAgICAjIEFyaXRobWV0aWMgbWVhbiANCiAgICB2b2wgPC0gc2QocG9ydGZfcmV0dXJucywgbmEucm0gPSBUKSAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVm9sYXRpbGl0eQ0KICAgIFNoYXJwZV9yYXRpbyA8LSBhdmdfcmV0IC8gdm9sICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTaGFycGUgcmF0aW8NCiAgICBWYVJfNSA8LSBxdWFudGlsZShwb3J0Zl9yZXR1cm5zLCAwLjA1LCBuYS5ybSA9IFRSVUUpICAgICAgICAgICMgVmFsdWUtYXQtcmlzaw0KICAgIHR1cm4gPC0gMCAgICANCiAgICBmb3IodCBpbiAyOmRpbSh3ZWlnaHRzKVsxXSl7DQogICAgICAgIHJlYWxpc2VkX3JldHVybnMgPC0gYXNzZXRfcmV0dXJucyAlPiUgZmlsdGVyKERhdGUgPT0gZGF0ZXNfb29zW3RdKSAlPiUgIGRwbHlyOjpzZWxlY3QoLURhdGUpDQogICAgICAgIHByaW9yX3dlaWdodHMgPC0gd2VpZ2h0c1t0LTEsXSAqICgxICsgcmVhbGlzZWRfcmV0dXJucykNCiAgICAgICAgdGVtcF90dXJuIDwtIGFwcGx5KGFicyh3ZWlnaHRzW3QsXSAtIHByaW9yX3dlaWdodHMvc3VtKHByaW9yX3dlaWdodHMpKSwxLHN1bSkNCiAgICAgICAgaWYoIWFueShpcy5uYSh0ZW1wX3R1cm4pKSl7DQogICAgICAgICAgdHVybiA8LSB0dXJuICsgYXBwbHkoYWJzKHdlaWdodHNbdCxdIC0gcHJpb3Jfd2VpZ2h0cy9zdW0ocHJpb3Jfd2VpZ2h0cykpLDEsc3VtKQ0KICAgICAgICB9DQogICAgfQ0KICAgIHR1cm4gPC0gdHVybi8obGVuZ3RoKGRhdGVzX29vcyktMSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQXZlcmFnZSBvdmVyIHRpbWUNCiAgICBtZXQgPC0gZGF0YS5mcmFtZShhdmdfcmV0LCB2b2wsIFNoYXJwZV9yYXRpbywgVmFSXzUsIHR1cm4pICAgICMgQWdncmVnYXRpb24gb2YgYWxsIG9mIHRoaXMNCiAgICByb3duYW1lcyhtZXQpIDwtICJtZXRyaWNzIg0KICAgIHJldHVybihtZXQpDQp9DQpgYGANCg0KYGBge3Igc2V0IHVwIHBlcmZvcm1hbmNlIE1ldHJpY3MyfQ0KcGVyZl9tZXQyIDwtIGZ1bmN0aW9uKHJldCx3ZWlnaHQsZGF0ZXNfb29zLHBvcnRfbmFtZSxhY3R1YWxfcmV0KXsNCiAgICBhdmdfcmV0IDwtIG1lYW4ocmV0LCBuYS5ybSA9IFQpICAgICAgICAgICAgICAgICAgICAgIyBBcml0aG1ldGljIG1lYW4gDQogICAgdm9sIDwtIHNkKHJldCwgbmEucm0gPSBUKSAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVm9sYXRpbGl0eQ0KICAgIFNoYXJwZV9yYXRpbyA8LSBhdmdfcmV0IC8gdm9sICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTaGFycGUgcmF0aW8NCiAgICBWYVJfNSA8LSBxdWFudGlsZShyZXQsIDAuMDUpICAgICAgICAgICAgICAgICAgICAgICAgIyBWYWx1ZS1hdC1yaXNrDQogICAgdHVybiA8LSAwDQogICAgZm9yKHQgaW4gMjpsZW5ndGgocmV0KSkgew0KICAgICAgICByZWFsaXNlZF9yZXQgPC0gYWN0dWFsX3JldCAlPiUNCiAgICAgICAgICBmaWx0ZXIoRGF0ZSA9PSBkYXRlc19vb3NbdF0pICU+JQ0KICAgICAgICAgIGRwbHlyOjpzZWxlY3QoLURhdGUpDQogICAgICAgIHByaW9yX3dlaWdodCA8LSB3ZWlnaHRbdC0xLF0gKiAoMSArIHJlYWxpc2VkX3JldCkgIyBCZWZvcmUgcmViYWxhbmNpbmcNCiAgICAgICAgdHVybiA8LSB0dXJuICsgYXBwbHkoYWJzKHdlaWdodFt0LF0gLSBwcmlvcl93ZWlnaHQvc3VtKHByaW9yX3dlaWdodCkpLDEsc3VtKQ0KICAgIH0NCiAgICB0dXJuIDwtIHR1cm4vKGxlbmd0aChyZXQpLTEpDQogICAgbWV0IDwtIGRhdGEuZnJhbWUoYXZnX3JldCwgdm9sLCBTaGFycGVfcmF0aW8sIFZhUl81LCB0dXJuKSAgICAjIEFnZ3JlZ2F0aW9uIG9mIGFsbCBvZiB0aGlzDQogICAgcm93bmFtZXMobWV0KSA8LSBwb3J0X25hbWUNCiAgICByZXR1cm4obWV0KQ0KfQ0KYGBgDQoNCiMgSW52ZXN0aW5nIFN0cmF0ZWdpZXMgDQpXZSBjcmVhdGUgdmFyaW91cyBwb3J0Zm9saW9zIHVzaW5nIHNhbWUgc2V0IG9mIDIwOSBzdG9ja3MgYmFzZWQgb24gMyBkaWZmZXJlbnQgc2NoZW1lcy4gDQoNCiogU3BhcnNlIHBvcnRmb2xpbw0KV2VpZ2h0IHRvIGVhY2ggc3RvY2sgaXMgYXNzaWduZWQgYmFzZWQgb24gdGhlIGluZmx1ZW5jZSBvZiBvdGhlciBzdG9ja3MnIHJldHVybnMgb24gdGhhdCBzdG9jay4gV2hlcmVhcywgdGhlIGRlZ3JlZSBvZiBzdWNoIGluZmx1ZW5jZSAod2VpZ2h0KSBpcyBkZXRlcm1pbmVkIGJ5IHRoZSBmb2xsb3dpbmcgYWxnb3JpdGhtcy4gDQogICAgKyBFbGFzdGljIE5ldHdvcmsNCiAgDQoqIFNpZ25hbHMNCiAgICArIE51bWVyY2lhbDogdXNlIGZ1dHVyZSByZXR1cm5zIGFzIGRlcGVuZGVudCB2YXJpYWJsZXMNCiAgICAqKlN0ZXBzKioNCiAgICAgICAgKyBVc2UgTGFzc28gdG8gZGV0ZXJtaW5lIGZlYXR1cmVzIGZvciBlYWNoIHN0b2Nrcw0KICAgICAgICArIFByZWRpY3QgdGhlIGZ1dHVyZSByZXR1cm5zIGZvciBlYWNoIHN0b2NrIHVzaW5nIGRpZmZlcmVudCBNTCBhbGdvcml0aG1zLCBpbmNsdWRpbmcNCiAgICAgICAgICAgICsgTGFzc28NCiAgICAgICAgICAgICsgS05ODQogICAgICAgICAgICArIFJhbmRvbSBGb3Jlc3QNCiAgICAgICAgKyBUcmFuc2xhdGUgdGhlIG51bWVyaWNhbCByZXN1bHRzIGludG8gc2lnbmFscyB1c2luZyAzIGNyaXRlcmlhOg0KICAgICAgICAgICAgKyBQZXJjZW50aWxlIC0gdXNlIGV4cGFuZGluZyBoaXN0b3JpY2FsIGRhdGEgdG8gc3BsaXQgcGVyY2VudGlsZSBkZWNpc2lvbiByZWdpb25zIA0KICAgICAgICAgICAgUDAtUDMzOiAnc2VsbCcNCiAgICAgICAgICAgIFAzMy1QNjY6ICdob2xkJywgaW5jbHVkZXMgaW4gcG9ydGZvbGlvIG9ubHkgaWYgdGhlIHN0b2NrIGlzIGhlbGQvYm91Z2h0IGZyb20gdGhlIHByZXZpb3VzIHBlcmlvZA0KICAgICAgICAgICAgUDY2LVAxMDA6ICdidXknDQogICAgICAgICAgDQogICAgICAgICAgKyBNb250aGx5IE1vbWVudHVtIC0gJ2J1eScgYSBzdG9jayBpZiBwcmVkaWN0ZWQgcmV0dXJucyBpbmNyZWFzZSBmcm9tIHByZXZpb3VzIGRheSwgYW5kIHNlbGwgb3RoZXJ3aXNlDQogICAgICAgICAgKyAzLXBlcmlvZCBNb21lbnR1bSAtICdidXknIGEgc3RvY2sgaWYgcHJlZGljdGVkIHJldHVybnMgaGF2ZSBiZWVuIGluY3JlYXNpbmcgZm9yIGFsbCAzIHByZXZpb3VzIHBlcmlvZHMgYW5kIHRoZSBwcmVkaWN0ZWQgcmV0dXJuIGZvciB0aGUgNHRoIHBlcmlvZCBpcyBpbiB0aGUgc2FtZSBkaXJlY3Rpb24gKHJpc2luZyksIGFuZCBzZWxsIG90aGVyd2lzZQ0KICAgICAgICArIENvbnNpZGVyIHRoZSBudW1lcmljYWwgcmVzdWx0cywgYW5kIG92ZXJyaWRlIHNpZ25hbHM6DQogICAgICAgICAgJ2J1eScgb25seSB3aGVuIHNpZ25hbCBpcyAnYnV5JyBhbmQgbnVtZXJpY2FsIHByZWRpY3Rpb24gaXMgcG9zaXRpdmUsIA0KICAgICAgICAgICdzZWxsJyBvdGhlcndpc2UNCiAgICAgICAgKyBBcHBseSBlcXVhbGx5IHdlaWdodGVkIHNjaGVtZSBmb3Igc3RvY2tzIHdpdGggJ2J1eScgc2lnbmFscw0KDQogICAgICAgIA0KICAgICsgQ2F0ZWdvcmljYWw6IHVzZSBzaWduYWxzIG9mIGZ1dHVyZSByZXR1cm5zIGFzIGRlcGVuZGVudCB2YXJpYWJsZXMNCiAgICAqKlN0ZXBzKioNCiAgICAgICAgKyBUcmFuc2xhdGUgdGhlIG51bWVyaWNhbCBhY3R1YWwgZnV0dXJlIHJldHVybnMgaW50byBzaWduYWxzIHVzaW5nIHRoZSBzYW1lIDMgY3JpdGVyaWE6DQogICAgICAgICAgKyBQZXJjZW50aWxlIA0KICAgICAgICAgICsgTW9udGhseSBNb21lbnR1bSANCiAgICAgICAgICArIDMtcGVyaW9kIE1vbWVudHVtIA0KICAgICAgICArIFVzZSBMYXNzbyB0byBkZXRlcm1pbmUgZmVhdHVyZXMgZm9yIGVhY2ggc3RvY2tzDQogICAgICAgICsgUHJlZGljdCB0aGUgc2lnbmFscyBvZiBmdXR1cmUgcmV0dXJucyBmb3IgZWFjaCBzdG9jayB1c2luZyBkaWZmZXJlbnQgTUwgYWxnb3JpdGhtcywgaW5jbHVkaW5nDQogICAgICAgICAgKyBRREENCiAgICAgICAgICArIEtOTg0KICAgICAgICAgICsgTG9naXN0aWMgTW9kZWwNCiAgICAgICAgICArIFByb2JpdCBNb2RlbA0KICAgICAgICArIEFwcGx5IGVxdWFsbHkgd2VpZ2h0ZWQgc2NoZW1lIGZvciBzdG9ja3Mgd2l0aCAnYnV5JyBzaWduYWxzDQogICAgICAgIA0KKiBCZW5jaG1hcmsgcG9ydGZvbGlvDQpUbyBpbnRlcnByZXQgYW5kIGdhaW4gdW5kZXJzdGFuZGluZyBvZiB0aGUgdXNlIG9mIG1hY2hpbmUgbGVhcm5pbmcsIHdlIHNldCBlcXVhbGx5LXdlaWdodGVkIHBvcnRmb2xpbyBhcyBhIGJlbmNobWFyay4NCiAgICArIEVXDQoNCiMjIEJlbmNobWFyayBwb3J0Zm9saW8NCldlIHVzZSBlcXVhbGx5IHdlaWdodGVkIHBvcnRmb2xpbyBhcyBhIGJlbmNobWFyayBmb3Igb3RoZXIgc3RyYXRlZ2llcy4gVGhlIHBvcnRmb2xpbyBpcyBzaW1wbHkgZGlzdHJpYnV0ZWQgdGhlIHJpc2sgb2YgZWFjaCBhc3NldCBlcXVhbGx5IGFjcm9zcyB0aGUgcG9ydGZvbGlvLiBUaGUgcG9ydGZvbGlvIHdlaWdodHMgY2FuIGJlIGNvbXB1dGVkIGFzIGl0IGhvbGRzIGFsbCBzZWxlY3RlZCBhc3NldHMgZXF1YWxseSBhcyAkdz1cZnJhY3sxfXtOfSQuDQoNCiMjIyBTZXR1cCBmdW5jdGlvbnMgdG8gY2FsY3VsYXRlIGVxdWFsIHdlaWd0aHMNCmBgYHtyIGJlbmNobWFyazEsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0Kd2VpZ2h0c19ldyA8LSBmdW5jdGlvbihyZXR1cm5zKXsNCiAgICBOID0gbGVuZ3RoKHJldHVybnNbMSxdKQ0KICAgIHJldHVybihyZXAoMS9OLE4pKQ0KfQ0KYGBgDQoNCiMjIyBDYWxjdWxhdGUgcG9ydGZvbGlvIHJldHVybg0KYGBge3IgYmVuY2htYXJrMiwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQojIEluaXRpYXRlIHN0b3JhZ2UgZm9yIHBvcnRmb2xpbyB3ZWlnaHRzDQpwb3J0X3dfZXcgPSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChkYXRlc19vb3MpLCBuY29sID0gbGVuZ3RoKHRpY2spKQ0KY29sbmFtZXMocG9ydF93X2V3KSA9IHRpY2sNCg0KDQojIEluaXRpYXRlIHN0b3JhZ2UgZm9yIHBvcnRmb2xpbyByZXR1cm5zDQpwb3J0X3JldHVybl9ldyA9IG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKGRhdGVzX29vcykpDQoNCmZvcihpIGluIDE6bGVuZ3RoKGRhdGVzX29vcykpew0KICAgIA0KICAgICMgR2V0IHRoZSBwYXN0IHJldHVybiBkYXRhDQogICAgcGFzdF9yZXR1cm4gPC0gcmV0dXJucyAlPiUgDQogICAgICBmaWx0ZXIoRGF0ZSA8IGRhdGVzX29vc1tpXSkgJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KC1EYXRlKQ0KICAgIA0KICAgICMgQ2FsY3VsYXRlIHBvcnRmb2xpbyB3ZWlnaHQNCiAgICBwb3J0X3dfZXdbaSxdIDwtIHdlaWdodHNfZXcocGFzdF9yZXR1cm4pDQogICAgDQogICAgIyBHZXQgdGhlIHJlYWxpc2VkIGRhdGENCiAgICByZWFsaXNlZF9yZXR1cm5zIDwtIHJldHVybnMgJT4lIA0KICAgICAgICBmaWx0ZXIoRGF0ZSA9PSAgZGF0ZXNfb29zW2ldKSAlPiUNCiAgICAgICAgZHBseXI6OnNlbGVjdCgtRGF0ZSkNCiAgICANCiAgICAjIENhbGN1bGF0aW5nIHJldHVybnMgZm9yIGVhY2ggcG9ydGZvbGlvDQogICAgcG9ydF9yZXR1cm5fZXdbaV0gPC0gc3VtKHBvcnRfd19ld1tpLF0gKiByZWFsaXNlZF9yZXR1cm5zKQ0KfQ0KDQpgYGANCg0KIyMjIFJlc3VsdDogUGVyZm9ybWFuY2UgbWF0cml4IGZvciBCZW5jaG1hcmsgcG9ydGZvbGlvDQpgYGB7cn0NCmFzc2V0X3JldHVybnMgPC0gZGF0YSAlPiUgDQogICAgZHBseXI6OnNlbGVjdChUaWNrLCBEYXRlLCBGX1JldHVybikgJT4lIA0KICAgIHNwcmVhZChrZXkgPSBUaWNrLCB2YWx1ZSA9IEZfUmV0dXJuKQ0KbWV0X2V3ID0gcGVyZl9tZXQocG9ydF9yZXR1cm5fZXcscG9ydF93X2V3LGFzc2V0X3JldHVybnMpDQpyb3cubmFtZXMobWV0X2V3KSA9IGMoIkVxdWFsbHkgd2VpZ2h0ZWQiKQ0KbWV0X2V3DQpgYGANCg0KIyMgU3BhcnNlIFBvcnRmb2xpbw0KDQojIyMgRWxhc3RpYyBOZXR3b3JrDQoNCioqRWxhc3RpYyBuZXQqKiBpcyBhIGZvcm0gb2YgcGVuYWxpemVkIHJlZ3Jlc3Npb24gd2hpY2ggdXNlZCB0byBjcmVhdGUgc3BhcnNlIHBvcnRmb2xpbyBiYXNlZCBvbiByZXR1cm5zIG9mIGFzc2V0cy4gIFRoZSBjb25kaXRpb24gb2YgdGhlIHJlZ3Jlc3Npb24gaXMgYXMgZm9sbG93cw0KJCQgXGhhdHtcYmV0YX1ee2VsYXN0aWN9PVx1bmRlcnNldHtcYmV0YX17XHRleHR7YXJnbWlufX1cLCBcbGVmdFx7XGZyYWN7MX17Tn1cc3VtX3tuPTF9Xk5cbGVmdCggeV97aX0tYV9pK1xzdW1fe2s9IDF9XktcYmV0YV97a314X3trLGl9XHJpZ2h0KV4yK1xsYW1iZGEgXGFscGhhIFxzdW1fe2s9IDF9Xkt8ICBcYmV0YV9rfCtcbGFtYmRhICgxLVxhbHBoYSlcc3VtX3trPSAxfV5LfCAgXGJldGFfe2t9XjJ8XHJpZ2h0XH0sJCQNCg0KIyMjIyBTZXR1cCBmb3IgZWxhc3RpYyBuZXR3b3JrIHdlaWdodCBhbmQgcmV0dXJuIGNhbGN1bGF0aW9ucw0KVGhlIG1vZGVsIHVzZSB0d28gcGFyYW1ldGVycyAkXGxhbWJkYSQgYW5kICRcYWxwaGEkLiAkXGFscGhhJCBpcyB0aGUgcGFyYW1ldGVyIHRoYXQgY2hhbmdlcyB0aGUgd2VpZ2h0IGJldHdlZW4gUmlkZ2UgcmVncmVzc2lvbiBhbmQgTEFTU08gcmVncmVzc2lvbiAoUmlkZ2U6ICRcYWxwaGEgPSAwJCwgTEFTU086ICRcYWxwaGEgPSAxJCkuICRcbGFtYmRhJCBpcyB0aGUgcGFyYW1ldGVyIHRoYXQgYWRqdXN0IHRoZSB3ZWlnaHQgb2YgdGhlIHBlbmFsaXNlZCBwYXJ0Lg0KDQpUbyBmaW5kIHBvcnRmb2xpbyB3ZWlnaHRzIHVzaW5nIEVsYXN0aWMgbmV0LCB3ZSByZWdyZXNzIHRoZSByZXR1cm5zIG9mIHRoZSBzZWxlY3RlZCBhc3NldCBvbiB0aGUgcmV0dXJucyBvZiBvdGhlciBhc3NldHMgYXMuICRyX3tpLHR9PWFfaStcc3VtX3tuXG5lcSBpfV5OXGJldGFfe259cl97bix0fStcZXBzaWxvbl97dH0kDQoNClVzaW5nIHBlbmFsaXNlZCByZWdyZXNzaW9uIHRvIGdldC4NCiQkIFx1bmRlcnNldHtcYmV0YV97aXx9fXtcdGV4dHthcmdtaW59fVwsIFxsZWZ0XHtcc3VtX3t0PTF9XlRcbGVmdCggcl97aSx0fS1hX2krXHN1bV97blxuZXEgaX1eTlxiZXRhX3tpfG59cl97bix0fVxyaWdodCleMitcbGFtYmRhIFxhbHBoYSB8fCAgXGJldGF8fF8xK1xsYW1iZGEgKDEtXGFscGhhKXx8XGJldGF8fF8yXjJccmlnaHRcfSwkJA0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0Kd2VpZ2h0c19sYXNzbyA8LSBmdW5jdGlvbihyZXR1cm5zLGFscGhhKXsgICMgVGhlIHBhcmFtZXRlcnMgYXJlIGRlZmluZWQgaGVyZQ0KICAgIHcgPC0gMCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgSW5pdGlhdGUgd2VpZ2h0cw0KICAgIGZvcihpIGluIDE6bmNvbChyZXR1cm5zKSl7ICAgICAgICAgICAgICAgICAgICAgICMgTG9vcCBvbiB0aGUgYXNzZXRzDQogICAgICAgIHkgPC0gcmV0dXJuc1ssaV0gICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEZXBlbmRlbnQgdmFyaWFibGUNCiAgICAgICAgeCA8LSByZXR1cm5zWywtaV0gICAgICAgICAgICAgICAgICAgICAgICAgICAjIEluZGVwZW5kZW50IHZhcmlhYmxlDQogICAgICAgIGZpdCA8LSBnbG1uZXQoeCx5LCBhbHBoYSA9IGFscGhhLGxhbWJkYSA9IDAuNSkNCiAgICAgICAgZXJyIDwtIHktcHJlZGljdChmaXQsIHgpICAgICAgICAgICAgICAgICAgICAjIFByZWRpY3Rpb24gZXJyb3JzDQogICAgICAgIHdbaV0gPC0gKDEtc3VtKGZpdCRiZXRhKSkvdmFyKGVycikgICAgICAgICAgIyBPdXRwdXQ6IHdlaWdodCBvZiBhc3NldCBpDQogICAgfQ0KICAgIHJldHVybih3IC8gc3VtKHcpKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTm9ybWFsaXNhdGlvbiBvZiB3ZWlnaHRzDQp9DQpgYGANCg0KIyMjIyBBbHBoYSBzZWxlY3Rpb24gYW5kIGNhbGN1bGF0aW9uIHdlaWdodHMgJiByZXR1cm5zDQoNCkVsYXN0aWMgbmV0IHBvcnRmb2xpbyB3ZWlnaHRzIGNhbiBiZSBjYWxjdWxhdGVkIHVzaW5nIHRoZSBmb2xsb3dpbmcgc3RlcHMNCjEuIFVzZSB0aGUgdmFsdWUgb2YgYWxwaGFzIHJhbmdpbmcgZnJvbSAwLCAwLjEsIDAuMiwuLi4sMSBhbmQgdXNpbmcgJFxsYW1iZGEgPSAwLjUkLg0KMi4gU2VsZWN0aW5nIHBhc3QgcmV0dXJuIGRhdGEgYW5kIHVzZSB0aGUgZnVuY3Rpb24gKip3ZWlnaHRzX2xhc3NvKiogdG8gY2FsY3VsYXRlIHByb3Rmb2xpbyB3ZWlnaHRzDQozLiBNdWx0aXBseSB0aGUgd2VpZ2h0cyB3aXRoIHJlYWxpc2VkIHJldHVybnMgdG8gZ2V0IHRoZSBwb3J0Zm9saW8gcmV0dXJucw0KNC4gU2VsZWN0IHRoZSBoaWdoZXN0IHJldHVybiBwb3J0Zm9saW8NCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KcG9ydF93X2VsYXMgPSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChkYXRlc19vb3MpLCBuY29sID0gbGVuZ3RoKHRpY2spKQ0KY29sbmFtZXMocG9ydF93X2VsYXMpID0gdGljaw0KDQpwb3J0X3JldHVybl9lbGFzID0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgoZGF0ZXNfb29zKSwgbmNvbCA9IDExKQ0KY29sbmFtZXMocG9ydF9yZXR1cm5fZWxhcykgPSBzZXEoMCwxLGJ5ID0gMC4xKQ0KDQojIGZvciAoYSBpbiAwOjEwKXsNCiMgICBhbHBoYSA9IGEvMTANCiMgICBwcmludChhKQ0KIyAgIA0KICAjIGZvcihpIGluIDE6bGVuZ3RoKGRhdGVzX29vcykpew0KICAjICAgIyBHZXQgdGhlIHBhc3QgcmV0dXJuIGRhdGENCiAgIyAgIHBhc3RfcmV0dXJuIDwtIHJldHVybnMgJT4lDQogICMgICAgIGZpbHRlcihEYXRlIDwgZGF0ZXNfb29zW2ldKSAlPiUNCiAgIyAgICAgZHBseXI6OnNlbGVjdCgtRGF0ZSkgJT4lDQogICMgICAgIGFzLm1hdHJpeCgpDQogICMgDQogICMgICBwb3J0X3dfZWxhc1tpLF0gPC0gd2VpZ2h0c19sYXNzbyhwYXN0X3JldHVybixhbHBoYSkNCiAgIyANCiAgIyAgIHJlYWxpc2VkX3JldHVybnMgPC0gcmV0dXJucyAlPiUNCiAgIyAgICAgICBmaWx0ZXIoRGF0ZSA9PSAgZGF0ZXNfb29zW2ldKSAlPiUNCiAgIyAgICAgICBkcGx5cjo6c2VsZWN0KC1EYXRlKQ0KICAjIA0KICAjICAgcG9ydF9yZXR1cm5fZWxhc1tpLChhKzEpXSA8LSBzdW0ocG9ydF93X2VsYXNbaSxdICogcmVhbGlzZWRfcmV0dXJucykNCiMgfQ0KIyB9DQoNCiN3cml0ZS5jc3YocG9ydF9yZXR1cm5fZWxhcyxmaWxlID0gInJldHVybl9lbGFzLmNzdiIpDQoNCnBvcnRfcmV0dXJuX2VsYXMgPSByZWFkLmNzdihmaWxlID0gInJldHVybl9lbGFzLmNzdiIpDQpwb3J0X3JldHVybl9lbGFzID0gcG9ydF9yZXR1cm5fZWxhc1stMV0NCg0KIyBGaW5kaW5nIGFscGhhIHRoYXQgZ2VuZXJhdGUgbGFyZ2VzdCBhdmVyYWdlIHJldHVybg0KY29sTWVhbnMocG9ydF9yZXR1cm5fZWxhcykNCnBvcnRfcmV0dXJuX2VsYXNfbWF4ID0gYXMubWF0cml4KHBvcnRfcmV0dXJuX2VsYXNbLHdoaWNoLm1heChjb2xNZWFucyhwb3J0X3JldHVybl9lbGFzKSldKQ0KDQojIEdldCB0aGUgYWxwaGEgZnJvbSB0aGUgY29sdW1uIGluZGV4DQphbHBoYSA9IHdoaWNoLm1heChjb2xNZWFucyhwb3J0X3JldHVybl9lbGFzKSkvMTANCg0KIyBHZXQgdGhlIHdlaWdodHMNCmZvcihpIGluIDE6bGVuZ3RoKGRhdGVzX29vcykpew0KICAgICMgR2V0IHRoZSBwYXN0IHJldHVybiBkYXRhDQogICAgcGFzdF9yZXR1cm4gPC0gcmV0dXJucyAlPiUNCiAgICAgIGZpbHRlcihEYXRlIDwgZGF0ZXNfb29zW2ldKSAlPiUNCiAgICAgIGRwbHlyOjpzZWxlY3QoLURhdGUpICU+JQ0KICAgICAgYXMubWF0cml4KCkNCg0KICAgIHBvcnRfd19lbGFzW2ksXSA8LSB3ZWlnaHRzX2xhc3NvKHBhc3RfcmV0dXJuLGFscGhhKQ0KfQ0KYGBgDQoNCiMjIyMgUmVzdWx0OiBQZXJmb3JtYW5jZSBtYXRyaXggZm9yIFNwYXJzZSBwb3J0Zm9saW8NCmBgYHtyfQ0KYXNzZXRfcmV0dXJucyA8LSBkYXRhICU+JSANCiAgICBkcGx5cjo6c2VsZWN0KFRpY2ssIERhdGUsIEZfUmV0dXJuKSAlPiUgDQogICAgc3ByZWFkKGtleSA9IFRpY2ssIHZhbHVlID0gRl9SZXR1cm4pIA0KDQptZXRfZWxhcyA9IHBlcmZfbWV0KHBvcnRfcmV0dXJuX2VsYXNfbWF4LHBvcnRfd19lbGFzLGFzc2V0X3JldHVybnMpDQpyb3cubmFtZXMobWV0X2VsYXMpID0gYygiU3BhcnNlIikNCm1ldF9lbGFzDQpgYGANCg0KDQojIyBTaWduYWxzDQoNCipzaWduYWxzKg0KV2Ugc2VwYXJhdGUgdGhlIHByZWRpY3RlZCByZXR1cm5zIGludG8gMyBjYXRlZ29yaWVzIGJhc2VkIG9uIHRoZSBhc3NldCBwYXN0IHJldHVybnMuDQoxLiBCVVkgc2lnbmFsICgrMSk6IElmIHRoZSBwcmVkaWN0ZWQgcmV0dXJuIG9mIGFzc2V0IGkgaXMgaGlnaGVyIHRoYW4gUDY2IG9mIGl0cyBlbnRpcmUgcGFzdCByZXR1cm5zDQoyLiBTRUxMIHNpZ25hbCAoMCk6IElmIHRoZSBwcmVkaWN0ZWQgcmV0dXJuIG9mIGFzc2V0IGkgaXMgbG93ZXIgdGhhbiBQMzMgb2YgaXRzIGVudGlyZSBwYXN0IHJldHVybnMNCjMuIEhPTEQgc2lnbmFsICgtMSk6SWYgdGhlIHByZWRpY3RlZCByZXR1cm4gb2YgYXNzZXQgaSBmYWxscyBpbiBiZXR3ZWVuIFAzMyBhbmQgUDY2IG9mIGl0cyBlbnRpcmUgcGFzdCByZXR1cm5zDQoNCiMjIyMgTEFTU08NCg0KRm9yIExBU1NPIG1ldGhvZCwgKipnbG1uZXQqKiBpcyB0byB1c2UgcGVuYWxpc2VkIHJlZ3Jlc3Npb24gdG8gcHJlZGljdCBmdXR1cmUgcmV0dXJucyB1c2luZyB0aGUgcGFzdCBmZWF0dXJlIGRhdGEgaW5jbHVkaW5nIHBhc3QgcmV0dXJucy4gVGhlIHByb2NlZHVyZSBhcmUgYXMgdGhlIGZvbGxvd2luZyBzdGVwczoNCiAgICAxLiBHZXQgdGhlIHBhc3QgZGF0YSBhcyBpbmRlcGVuZGVudCB2YXJpYWJsZXMgKHgpDQogICAgMi4gR2V0IHRoZSBwYXN0IHJldHVybnMgYXMgYSBkZXBlbmRlbnQgdmFyaWFibGUgKHkpDQogICAgMy4gVXNlIExBU1NPIGFzIGEgbW9kZWwgdG8gcHJlZGljdCB0aGUgZnV0dXJlIHJldHVybnMNCiAgICA0LiBQZXJmb3JtIGNyb3NzIHZhbGlkYXRpb24gZnJvbSB0aGUgZnVuY3Rpb24gKipjdi5nbG1uZXQqKi4gdGhlIG9iamVjdGl2ZSBpcyB0byB1c2UgdGhlIGxhbWJkYSB0aGF0IG1pbmltaXNlIHRoZSB2YXJpYW5jZSBvZiB0aGUgZXJyb3INCiAgICA1LiBVc2UgdGhlIGN1cnJlbnQgZmVhdHVyZSBkYXRhIHRvIHByZWRpY3QgdGhlIG5leHQgcGVyaW9kIHJldHVybnMNCg0KIyMjIyMgVHJhaW4gTEFTU08gbW9kZWwgDQpgYGB7cn0NCiMgdGVtcF9yZXR1cm4gPSBtYXRyaXgoTkEsIG5jb2wgPSBsZW5ndGgodGljayksIG5yb3c9bGVuZ3RoKGRhdGVzX29vcykpDQojIGNvbG5hbWVzKHRlbXBfcmV0dXJuKSA9IHRpY2sNCiMgZm9yIChpIGluIDE6bGVuZ3RoKHRpY2spKXsNCiMgICBwcmludChpKQ0KIyAgIGZvcih0IGluIDE6bGVuZ3RoKGRhdGVzX29vcykpew0KIyAgICAgcGFzdF9kYXRhID0gZGF0YSAlPiUNCiMgICAgICAgZmlsdGVyKFRpY2sgPT0gdGlja1tpXSkgJT4lDQojICAgICAgIGZpbHRlcihEYXRlIDwgZGF0ZXNfb29zW3RdKSAlPiUNCiMgICAgICAgZHBseXI6OnNlbGVjdCgtRGF0ZSwtVGljaywtQ2xvc2UpICU+JQ0KIyAgICAgICB0YWlsKC4sNDgpICU+JQ0KIyAgICAgICBhcy5tYXRyaXgoKQ0KIyAgICAgIyBHZXQgdGhlIHRyYWluaW5nIGRhdGEgKDNtb250aHMpDQojICAgICB0cmFpbl9kYXRhID0gaGVhZChwYXN0X2RhdGEsMzYpICU+JQ0KIyAgICAgICBhcy5kYXRhLmZyYW1lKCkNCiMgICAgIHhfdHJhaW4gPSB0cmFpbl9kYXRhICU+JQ0KIyAgICAgICBkcGx5cjo6c2VsZWN0KC1GX1JldHVybikgJT4lDQojICAgICAgIGFzLm1hdHJpeCgpDQojICAgICB5X3RyYWluID0gdHJhaW5fZGF0YSAlPiUNCiMgICAgICAgZHBseXI6OnNlbGVjdChGX1JldHVybikgJT4lDQojICAgICAgIGFzLm1hdHJpeA0KIyANCiMgDQojICAgICBNU0UgPSBtYXRyaXgoTkEsIG5jb2w9MTAsbnJvdz0xMSkNCiMgDQojICAgICBmb3IoYSBpbiAxOjExKXsNCiMgICAgICAgIyBBbHBoYSA9IDAsIDAuMSwgMC4yLC4uLiwxDQojICAgICAgIGFscGhhID0gKGEtMSkvMTANCiMgICAgIGZvcihsIGluIDE6MTApew0KIyAgICAgICAjIExhbWJkYSA9IDAsIDAuMSwgMC4yLC4uLiwxDQojICAgICAgIGxhbWJkYSA9IChsKS8xMA0KIyAgICAgDQojICAgICAgICMgUnVuIHBlbmFsaXNlZCByZWdyZXNzaW9uDQojICAgICAgIGZpdCA9IGdsbW5ldCh4ID0geF90cmFpbiwgeSA9IHlfdHJhaW4sIGFscGhhID0gYWxwaGEsIGxhbWJkYSA9IGxhbWJkYSkNCiMgDQojICAgICAgICMgR2V0IHRoZSB2YWxpZGF0aW9uIGRhdGEgKDNtb250aHMpDQojICAgICAgIHZhbGlkYXRlX2RhdGEgPSB0YWlsKHBhc3RfZGF0YSwxMikgJT4lDQojICAgICAgICAgYXMuZGF0YS5mcmFtZSgpIA0KIyAgICAgICB4X3ZhbGlkYXRlID0gdmFsaWRhdGVfZGF0YSAlPiUNCiMgICAgICAgICBkcGx5cjo6c2VsZWN0KC1GX1JldHVybikgJT4lDQojICAgICAgICAgYXMubWF0cml4KCkNCiMgICAgICAgeV92YWxpZGF0ZT0gdmFsaWRhdGVfZGF0YSAlPiUNCiMgICAgICAgICBkcGx5cjo6c2VsZWN0KEZfUmV0dXJuKSAlPiUNCiMgICAgICAgICBhcy5tYXRyaXgoKQ0KIyANCiMgICAgICAgIyBDYWxjdWxhdGUgTVNFDQojICAgICAgIE1TRVthLGxdID0gc3VtKCh5X3ZhbGlkYXRlIC0gcHJlZGljdChmaXQseF92YWxpZGF0ZSkpXjIpDQojIA0KIyAgICAgfQ0KIyAgIH0NCiMgDQojICMgT3B0aW1hbCBwYXJhbWV0ZXJzDQojIG1pbl9hbHBoYSA9ICh3aGljaChNU0UgPT0gbWluKE1TRSksIGFyci5pbmQgPSBUUlVFKVsxXSAtIDEpLzEwDQojIG1pbl9sYW1iZGEgPSAod2hpY2goTVNFID09IG1pbihNU0UpLCBhcnIuaW5kID0gVFJVRSlbMl0pLzEwDQojIA0KIyANCiMgDQojIGxhc3RfcGVyaW9kX3ggPSBkYXRhICU+JQ0KIyAgICAgICBmaWx0ZXIoVGljayA9PSB0aWNrW2ldKSAlPiUNCiMgICAgICAgZmlsdGVyKERhdGUgPT0gZGF0ZXNfb29zW3RdKSAlPiUNCiMgICAgICAgZHBseXI6OnNlbGVjdCgtRGF0ZSwtVGljaywtQ2xvc2UsIC1GX1JldHVybikgJT4lDQojICAgICAgIGFzLm1hdHJpeCgpDQojICAgICANCiMgICAgIGZpdDIgPSBnbG1uZXQoeCA9IHhfdHJhaW4sIHkgPSB5X3RyYWluLCBhbHBoYSA9IG1pbl9hbHBoYSwgbGFtYmRhID0gbWluX2xhbWJkYSkNCiMgICAgIHRlbXBfcmV0dXJuW3QsaV0gPC0gcHJlZGljdChmaXQyLCBsYXN0X3BlcmlvZF94KQ0KIyAgIH0NCiMgfQ0KIyANCiMgd3JpdGUuY3N2KHRlbXBfcmV0dXJuLCBmaWxlID0gInJldHVybl9sYXNzby5jc3YiKQ0KDQojIEZvciBmdXJ0aGVyIGV4ZWN1dGlvbg0KdGVtcF9yZXR1cm4gPSByZWFkLmNzdihmaWxlID0gInJldHVybl9sYXNzby5jc3YiKQ0KdGVtcF9yZXR1cm4gPSB0ZW1wX3JldHVyblstMV0NCg0KYGBgDQoNCioqIHdpdGggTEFTU08gd2l0aCBwZXJjZW50aWxlICoqDQpgYGB7ciBTaWduYWwgUGVyY2VudGlsZSBMQVNTT30NCnFfc2lnbmFsID0gbWF0cml4KE5BLCBuY29sID0gbGVuZ3RoKHRpY2spLCBucm93PWxlbmd0aChkYXRlc19vb3MpKQ0KY29sbmFtZXMocV9zaWduYWwpID0gdGljaw0KZm9yIChpIGluIDE6bGVuZ3RoKHRpY2spKXsNCiAgcHJpbnQoaSkNCiAgZm9yICh0IGluIDE6bGVuZ3RoKGRhdGVzX29vcykpew0KICAgIHBhc3RfcmV0dXJuID0gZGF0YSAlPiUNCiAgICAgIGZpbHRlcihUaWNrID09IHRpY2tbaV0pICU+JQ0KICAgICAgZmlsdGVyKERhdGUgPD0gZGF0ZXNfb29zW3RdKSAlPiUNCiAgICAgIGRwbHlyOjpzZWxlY3QoUF9SZXR1cm4pICU+JQ0KICAgICAgYXMubWF0cml4KCkNCiAgDQogIGlmICh0ZW1wX3JldHVyblt0LGldIDwgYXMubnVtZXJpYyhxdWFudGlsZShwYXN0X3JldHVybilbMl0pKSB7DQogICAgcV9zaWduYWxbdCxpXSA9IC0xDQogICAgfQ0KICANCiAgIyBSZXR1cm5zIGFib3ZlIFEzIGluZGljYXRlIEJVWSBzaWduYWwNCiAgICBlbHNlIGlmICh0ZW1wX3JldHVyblt0LGldID4gYXMubnVtZXJpYyhxdWFudGlsZShwYXN0X3JldHVybilbNF0pKSB7DQogICAgcV9zaWduYWxbdCxpXSA9IDENCiAgICB9DQogIA0KICAjIFJldHVybnMgYmV0d2VlbiBRMSBhbmQgUTMgaW5kaWNhdGUgSE9MRCBzaWduYWwgIA0KICAgIGVsc2Ugew0KICAgIHFfc2lnbmFsW3QsaV0gPSAwDQogICAgfQ0KfQ0KDQp9DQojIFRyYW5zbGF0ZSBIb2xkIHNpZ25hbA0KZm9yIChpIGluIDE6bGVuZ3RoKHRpY2spKXsNCiAgZm9yICh0IGluIDI6bGVuZ3RoKGRhdGVzX29vcykpew0KICAgIGlmKHFfc2lnbmFsW3QsaV0gPT0gMCl7DQogICAgICBxX3NpZ25hbFt0LGldID0gcV9zaWduYWxbdC0xLGldDQogICAgfQ0KICB9DQp9DQojIENoYW5nZSBTRUxMIHNpZ25hbCB0byAwIChuby1zaG9ydCBzZWxsaW5nKQ0KIyAxID0gSE9MRElORyAsIDAgPSBOT1QgSE9MRElORw0KcV9zaWduYWxbcV9zaWduYWwgPT0gLTFdIDwtIDANCg0KIyBDcmVhdGUgRVcgUG9ydGZvbGlvDQpwb3J0X3dfcGVyY2VudGlsZSA9IHFfc2lnbmFsL3Jvd1N1bXMocV9zaWduYWwpDQoNCiMgQ2FsY3VsYXRlIFBvcnRmb2xpbyByZXR1cm4NCnBvcnRfcmV0dXJuX3BlcmNlbnRpbGUgPSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChkYXRlc19vb3MpKQ0KZm9yIChpIGluIDE6bGVuZ3RoKGRhdGVzX29vcykpew0KICByZWFsaXNlZF9yZXR1cm5zID0gZGF0YSAlPiUNCiAgICBmaWx0ZXIoRGF0ZSA9PSBkYXRlc19vb3NbaV0pICU+JQ0KICAgIGRwbHlyOjpzZWxlY3QoRl9SZXR1cm4pDQogIA0KICBwb3J0X3JldHVybl9wZXJjZW50aWxlW2ldID0gc3VtKHBvcnRfd19wZXJjZW50aWxlW2ksXSAqIHJlYWxpc2VkX3JldHVybnMpDQogIA0KfQ0KYGBgDQoNCioqIHdpdGggTEFTU08gd2l0aCB0aHJlZS1tb21lbnR1bSAqKg0KYGBge3IgTW9NIHNpZ25hbCBMQVNTT30NCm1fc2lnbmFsID0gbWF0cml4KE5BLCBuY29sID0gbGVuZ3RoKHRpY2spLCBucm93PWxlbmd0aChkYXRlc19vb3MpKQ0KY29sbmFtZXMobV9zaWduYWwpID0gdGljaw0KDQpmb3IgKGkgaW4gMTpsZW5ndGgodGljaykpew0KICBwcmludChpKQ0KICBmb3IgKHQgaW4gMTpsZW5ndGgoZGF0ZXNfb29zKSl7DQogICAgdGhyZWVfTV9wYXN0X3JldHVybiA9IGRhdGEgJT4lDQogICAgICBmaWx0ZXIoVGljayA9PSB0aWNrW2ldKSAlPiUNCiAgICAgIGZpbHRlcihEYXRlIDw9IGRhdGVzX29vc1t0XSkgJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KFBfUmV0dXJuKSAlPiUNCiAgICAgIGFzLm1hdHJpeCgpICU+JQ0KICAgICAgdGFpbCgzKQ0KICAgIA0KICAgIHBfMSA9IHRocmVlX01fcGFzdF9yZXR1cm5bM10NCiAgICBwXzIgPSB0aHJlZV9NX3Bhc3RfcmV0dXJuWzJdDQogICAgcF8zID0gdGhyZWVfTV9wYXN0X3JldHVyblsxXQ0KICAgIA0KICAgIGlmKHRlbXBfcmV0dXJuW3QsaV0+MCAmIHBfMT4wICYgcF8yPjAgJiBwXzM+MCl7DQogICAgICBtX3NpZ25hbFt0LGldID0gMQ0KICAgIH0NCiAgICAgIGVsc2V7DQogICAgICBtX3NpZ25hbFt0LGldID0gMA0KICAgIH0NCiAgfQ0KfQ0KDQojIENyZWF0ZSBFVyBQb3J0Zm9saW8NCnBvcnRfd19tb20gPSBtX3NpZ25hbC9yb3dTdW1zKG1fc2lnbmFsKQ0KcG9ydF93X21vbVtpcy5uYShwb3J0X3dfbW9tKV0gPC0gMA0KDQojIENhbGN1bGF0ZSBQb3J0Zm9saW8gcmV0dXJuDQpwb3J0X3JldHVybl9tb20gPSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChkYXRlc19vb3MpKQ0KZm9yIChpIGluIDE6bGVuZ3RoKGRhdGVzX29vcykpew0KICByZWFsaXNlZF9yZXR1cm5zID0gZGF0YSAlPiUNCiAgICBmaWx0ZXIoRGF0ZSA9PSBkYXRlc19vb3NbaV0pICU+JQ0KICAgIGRwbHlyOjpzZWxlY3QoRl9SZXR1cm4pDQogIA0KICBwb3J0X3JldHVybl9tb21baV0gPSBzdW0ocG9ydF93X21vbVtpLF0gKiByZWFsaXNlZF9yZXR1cm5zKQ0KICANCn0NCmBgYA0KDQoqKiB3aXRoIExBU1NPIHdpdGggb25lLW1vbWVudHVtICoqDQpgYGB7cn0NCm0xX3NpZ25hbCA9IG1hdHJpeChOQSwgbmNvbCA9IGxlbmd0aCh0aWNrKSwgbnJvdz1sZW5ndGgoZGF0ZXNfb29zKSkNCmNvbG5hbWVzKG0xX3NpZ25hbCkgPSB0aWNrDQoNCmZvciAoaSBpbiAxOmxlbmd0aCh0aWNrKSl7DQogIHByaW50KGkpDQogIGZvciAodCBpbiAxOmxlbmd0aChkYXRlc19vb3MpKXsNCiAgICBvbmVfTV9wYXN0X3JldHVybiA9IGRhdGEgJT4lDQogICAgICBmaWx0ZXIoVGljayA9PSB0aWNrW2ldKSAlPiUNCiAgICAgIGZpbHRlcihEYXRlIDw9IGRhdGVzX29vc1t0XSkgJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KFBfUmV0dXJuKSAlPiUNCiAgICAgIGFzLm1hdHJpeCgpICU+JQ0KICAgICAgdGFpbCgxKQ0KDQogICAgaWYodGVtcF9yZXR1cm5bdCxpXT4wICYgb25lX01fcGFzdF9yZXR1cm4+MCl7DQogICAgICBtMV9zaWduYWxbdCxpXSA9IDENCiAgICB9DQogICAgICBlbHNlew0KICAgICAgbTFfc2lnbmFsW3QsaV0gPSAwDQogICAgfQ0KICB9DQp9DQoNCiMgQ3JlYXRlIEVXIFBvcnRmb2xpbw0KcG9ydF93X21vbTEgPSBtMV9zaWduYWwvcm93U3VtcyhtMV9zaWduYWwpDQpwb3J0X3dfbW9tMVtpcy5uYShwb3J0X3dfbW9tMSldIDwtIDANCg0KIyBDYWxjdWxhdGUgUG9ydGZvbGlvIHJldHVybg0KcG9ydF9yZXR1cm5fbW9tMSA9IG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKGRhdGVzX29vcykpDQpmb3IgKGkgaW4gMTpsZW5ndGgoZGF0ZXNfb29zKSl7DQogIHJlYWxpc2VkX3JldHVybnMgPSBkYXRhICU+JQ0KICAgIGZpbHRlcihEYXRlID09IGRhdGVzX29vc1tpXSkgJT4lDQogICAgZHBseXI6OnNlbGVjdChGX1JldHVybikNCiAgDQogIHBvcnRfcmV0dXJuX21vbTFbaV0gPSBzdW0ocG9ydF93X21vbTFbaSxdICogcmVhbGlzZWRfcmV0dXJucykNCiAgDQp9DQoNCg0KYGBgDQoNCiMjIyMjIFJlc3VsdDogUGVyZm9ybWFuY2UgbWF0cml4IGZvciBMQVNTTw0KYGBge3J9DQpwb3J0X25hbWVzX2xhc3NvID0gYygiTEFTU08gUGVyY2VudGlsZSIsICJMQVNTTyBNb21lbnR1bTEiLCAiTEFTU08gTW9tZW50dW0zIikNCmFzc2V0X3JldHVybnMgPC0gZGF0YSAlPiUgDQogICAgZHBseXI6OnNlbGVjdChUaWNrLCBEYXRlLCBGX1JldHVybikgJT4lIA0KICAgIHNwcmVhZChrZXkgPSBUaWNrLCB2YWx1ZSA9IEZfUmV0dXJuKQ0KDQptZXRfTEFTU09fcSA9IHBlcmZfbWV0KHBvcnRfcmV0dXJuX3BlcmNlbnRpbGUscG9ydF93X3BlcmNlbnRpbGUsYXNzZXRfcmV0dXJucykNCm1ldF9MQVNTT19tMSA9IHBlcmZfbWV0KHBvcnRfcmV0dXJuX21vbTEscG9ydF93X21vbTEsYXNzZXRfcmV0dXJucykNCm1ldF9MQVNTT19tID0gcGVyZl9tZXQocG9ydF9yZXR1cm5fbW9tLHBvcnRfd19tb20sYXNzZXRfcmV0dXJucykNCg0KbGFzc29fbWV0ID0gcmJpbmQobWV0X0xBU1NPX3EsbWV0X0xBU1NPX20xLG1ldF9MQVNTT19tKQ0Kcm93Lm5hbWVzKGxhc3NvX21ldCkgPSBwb3J0X25hbWVzX2xhc3NvDQpsYXNzb19tZXQNCmBgYA0KDQojIyMjIEstTmVhcmVzdCBOZWlnaGJvdXINClRoaXMgYWxnb3JpdGhtIGNvbnNpZGVyIHZhbHVlcyBvZiBLLW5lYXJlc3QgZGF0YSBwb2ludHMgdG8gYSBwYXJ0aWN1bGFyIG9ic2VydmF0aW9uIGluIG9yZGVyIHRvIHByZWRpY3QgdGhhdCBvYnNlcnZhdGlvbnMuIFRoZXJlZm9yZSwgdGhlIGh5cGVycGFyYW1ldGVyIGZvciB0aGlzIG1vZGVsIGlzIEsuIE5vdGUgdGhhdCwgc2luY2Ugd2UgdHJhaW4gdGhlIG1vZGVsIGZvciBlYWNoIHN0b2NrLCB0aGUgbW9kZWwgYXV0b21hdGljYWxseSB0dW5lcyB0aGUgYmVzdCBLLXZhbHVlIGZvciBlYWNoIHN0b2Nr4oCZcyBtb2RlbCByZXN1bHRpbmcgaW4gZGlmZmVyZW50IEvigJlzLg0KDQojIyMjIyBTZXR1cCBmdW5jdGlvbnMgZm9yIEtOTg0KKlNpZ25hbHMqDQpgYGB7ciBzZXR1cCBzaWduYWxzX3BlcmNlbnRpbGUsIGVjaG8gPSBUfQ0KI3BlcmNlbnRpbGUNClBfc2lnIDwtIGZ1bmN0aW9uKGRhdCxoaXN0X2RhdCxzaWdfZGF0ZSkgeyANCgkgICNkYXQ6IG9uZSBjb2x1bW4gb2YgcmV0dXJucyAocHJlZGljdGVkIGZvciBudW1lcmljYWwsIGFjdHVhbCBmb3IgY2F0ZWdvcmljYWwpDQogIAkjaGlzdF9kYXQ6IGhpc3RvcmljYWwgZGF0YSB0byBjcmVhdGUgcGVyY2VudGlsZQ0KICAgICNzaWdfZGF0ZTogZGF0ZSBhdCB3aGljaCB0aGUgc2lnbmFsIGlzIGFzc2lnbmVkDQogIHNpZyA8LSBtYXRyaXggKDAsbnJvdyA9IGxlbmd0aChkYXQpLG5jb2wgPSAxKQ0KICBmb3IgKGkgaW4gMTpsZW5ndGgoZGF0KSkgew0KICAgIFBfZGF0IDwtIGhpc3RfZGF0WzE6KHNpZ19kYXRlLTIraSldICAgICNleHBhbmRpbmcgZGF0YSB0byBjcmVhdGUgcGVyY2VudGlsZQ0KICAgIGlmIChkYXRbaV0gPj0gcXVhbnRpbGUoUF9kYXQsIC42NikpIHsNCgkgICAgc2lnW2ldID0gMQ0KICAgIH0gZWxzZSBpZiAoZGF0W2ldID49IHF1YW50aWxlKFBfZGF0LCAuMzMpKSB7DQogICAgCWlmIChpID09IDEpIHsNCiAgICAJICBzaWdbaV0gPSAwDQogICAgCX0gZWxzZSBpZiAoc2lnW2ktMV0gPT0gMSkgew0KICAgICAgICAgIHNpZ1tpXSA9IDENCiAgICAgICAgfSANCiAgICB9IGVsc2Ugew0KICAgIAlzaWdbaV0gPSAwDQogICAgfQ0KICB9DQogIHJldHVybihzaWcpDQp9DQpgYGANCg0KYGBge3Igc2V0dXAgc2lnbmFsc19Nb00sIGVjaG8gPSBUfQ0KDQojRm9yIE1vbWVudHVtIGZ1bmN0aW9ucywgdGhlIGlucHV0IGlzIGRhdGEgZnJhbWUgd2l0aCBmdXR1cmUgcmV0dXJucyBhcyBmaXJzdCBjb2x1bW4gYW5kIGN1cnJlbnQgcmV0dXJucyBhcyBzZWNvbmQgY29sdW1ucw0KDQojTW9udGhseSBNb00NCk1vTS4xX3NpZyA8LSBmdW5jdGlvbihkYXQpew0KICBzaWcgPC0gbWF0cml4KDAsbnJvdyA9IG5yb3coZGF0KSxuY29sID0gMSkNCiAgZm9yIChpIGluIDE6bnJvdyhkYXQpKSB7DQogICAgaWYgKGRhdFtpLDFdID4gZGF0W2ksMl0pIHsgICAgICAjYnV5IGlmIHByZWRpY3RlZCBmdXR1cmUgcmV0dXJuID4gY3VycmVudCByZXR1cm4NCiAgICAgIHNpZ1tpXSA9IDENCiAgICB9IGVsc2Ugew0KICAgICAgc2lnW2ldID0gMA0KICAgIH0NCiAgfQ0KICByZXR1cm4oY2JpbmQoZGF0LHNpZykpDQp9DQoNCiMgUXVhcnRlcmx5IE1vTQ0KTW9NLjNfc2lnIDwtIGZ1bmN0aW9uKGRhdCl7DQogIHNpZyA8LSBtYXRyaXgoMCxucm93ID0gbnJvdyhkYXQpLG5jb2wgPSAxKQ0KICBmb3IgKGkgaW4gNDpucm93KGRhdCkpIHsNCiAgICBpZiAoKGRhdFtpLTIsMl0gPiBkYXRbaS0zLDJdKSAmIChkYXRbaS0xLDJdID4gZGF0W2ktMiwyXSkgJiAoZGF0W2ksMl0gPiBkYXRbaS0xLDJdKSAmDQogICAgKGRhdFtpLDFdID4gZGF0W2ksMl0pKXsgDQogICAgICAgICAgICAgICAgI2J1eSBpZiBwcmVkaWN0ZWQgZnV0dXJlIHJldHVybiA+IGN1cnJlbnQgcmV0dXJuDQogICAgICAgICAgICAgICAgI2dpdmVuIHRoYXQgdGhlIHBhc3QgcmV0dXJuIGhhZCBiZWVuIG9uIHJpc2luZyBtb21lbnR1bSBmb3IgbGFzdCAzIHBlcmlvZHMNCiAgICAgIHNpZ1tpXSA9IDENCiAgICB9IGVsc2Ugew0KICAgICAgc2lnW2ldID0gMA0KICAgIH0NCiAgfQ0KICByZXN1bHQgPC0gY2JpbmQoZGF0LHNpZykNCiAgcmVzdWx0IDwtIHJlc3VsdFs0Oihucm93KGRhdCkpLF0gI3NvbWVob3cgdGhpcyBkb2VzIG5vdCBkZWxldGUgZmlyc3QgMyByb3dzDQogICNyZXN1bHQgPC0gcmVzdWx0WzQ6NjcsXQ0KICByZXR1cm4ocmVzdWx0KQ0KfQ0KYGBgDQoNCipDcm9zcy12YWxpZGF0aW9uKg0KVW5saWtlIHNwYXJzZSBwb3J0Zm9saW8gbWV0aG9kLCB3ZSBkZWFsIHdpdGggdGltZS1zZWllcyBkYXRhIHRvIHByZWRpY3QgZnV0dXJlIHJldHVybnMgaW4gdGhpcyBzZWN0aW9uLiBIZW5jZSwgd2UgbmVlZCB0byBzZXQgdGltZS1zZXJpZXMgY3Jvc3MtdmFsaWRhdGlvbiBhcyB0aW1lIHNsaWNlIHRvIG1haW50YWluIHRoZSBzdHJ1Y3R1cmUgaW4gdGhlIGRhdGEgKG9sZGVzdCB0byBuZXdlc3QpLiBUbyBiZSBjb25zaXN0ZW50IHdpdGggb3VyIG91dC1vZi1zYW1wbGUgZGF0ZSB3aGljaCBzZXBhcmF0ZXMgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cyB3aXRoIGFwcHJveGltYXRlbHkgNzAvMzAgcmF0aW8sIHdlIHdpbGwgc2V0IDM2IGFuZCAxMiBvYnNlcnZhdGlvbnMgZm9yIGVhY2ggdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gZXhhbXBsZXMgd2hlbiB3ZSBmaXQgdGhlIG1vZGVsLiANCmBgYHtyIHNldCB1cCB0cmFpbkNvbnRyb2wsIGVjaG89IFQsIG1lc3NhZ2UgPSBGQUxTRX0NCiNjYXJhdCBsaWJyYXJ5IGZvciB0cmFpbmluZw0KaWYoIXJlcXVpcmUoY2FyZXQpKXtpbnN0YWxsLnBhY2thZ2VzKCdjYXJldCcpfQ0KbGlicmFyeShjYXJldCkNCg0KI1RyYWluaW5nIHNldCBzdGFydHMgZnJvbSBmaXJzdCBkYXRlIHVudGlsIGRhdGUgYmVmb3JlIHNlcGFyYXRpb24gZGF0ZQ0KdGltZVNsaWNlc190cmFpbiA8LSBjcmVhdGVUaW1lU2xpY2VzKDE6KGxlbmd0aChkYXRlc19hbGwpLWxlbmd0aChkYXRlc19vb3MpKSwgDQogICAgICAgICAgICAgICAgICAgaW5pdGlhbFdpbmRvdyA9IDM2LCBob3Jpem9uID0gMTIsIGZpeGVkV2luZG93ID0gVFJVRSkNCmBgYA0KDQoqRGF0ZSB2YXJpYWJsZXMqDQpgYGB7ciBzZXQgdXAgb29zIGhvcml6b24gYW5kIHJldHVybnMsZWNobyA9IFR9DQpvb3Nfc3RhcnQgPC0gKChsZW5ndGgoZGF0ZXNfYWxsKS1sZW5ndGgoZGF0ZXNfb29zKSkrMSkNCm9vc19sZW5ndGggPC0gbGVuZ3RoKGRhdGVzX2FsbCkgLSBvb3Nfc3RhcnQgKyAxDQpvb3NfcmV0IDwtIGFzX3RpYmJsZShyZXR1cm5zKSAlPiUgDQogIGZpbHRlcihEYXRlID49IHNlcF9kYXRlKSAlPiUgICAgDQogIGRwbHlyOjpzZWxlY3QoLURhdGUpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogIGFzLm1hdHJpeCgpICAgIA0KYGBgDQoNCipFcXVhbGx5LXdlaWdodGVkIGNhbGN1bGF0aW9uKg0KYGBge3Igc2V0IHVwIGVxdWFsLXdlaWdodCBmdW5jdGlvbixlY2hvID0gVH0NCmNhbF9wb3J0X3dlaWdodCA8LSBmdW5jdGlvbihzaWdfZGF0KSB7DQogIHdlaWdodCA8LSBtYXRyaXgoMCwgbnJvdz0gbnJvdyhzaWdfZGF0KSxuY29sID0gbmNvbChzaWdfZGF0KSkgICAgICMgY3JlYXRlIHdlaWdodCBtYXRyaXgNCiAgZm9yIChpIGluIDE6bnJvdyhzaWdfZGF0KSkgeyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBhc3NpZ24gd2VpZ2h0cw0KICAgIGJ1eV90aGlzRGF5IDwtIHdoaWNoKHNpZ19kYXRbaSxdID09IDEpDQogICAgZm9yIChqIGluIDE6bGVuZ3RoKGJ1eV90aGlzRGF5KSkgew0KICAgICAgd2VpZ2h0W2ksYnV5X3RoaXNEYXlbal1dIDwtIDEvbGVuZ3RoKGJ1eV90aGlzRGF5KQ0KICAgIH0NCiAgfQ0KIHJldHVybiAod2VpZ2h0KQ0KfQ0KYGBgDQoNCiMjIyMjIFRyYWluIEtOTiB3aXRoIHBlcmNlbnRpbGUNCmBgYHtyIGtubl9wLCBlY2hvID0gVH0NCiMgc2lnbmFsc19rbm5fUCA8LSBtYXRyaXgoMCwgbnJvdyA9IG9vc19sZW5ndGgsIG5jb2wgPSBsZW5ndGgodGljaykpICNjcmVhdGUgdGFibGUgdG8gc3RvcmUgc2lnbmFscw0KIyBjb2xuYW1lcyhzaWduYWxzX2tubl9QKSA8LSB0aWNrDQojIGZvciAoaSBpbiAxOmxlbmd0aCh0aWNrKSl7DQojICAgYSA8LSBzZWxlY3RGZWF0dXJlKHRpY2tbaV0pDQojICAgYV9wcmVkIDwtIHRyYWluKEZfUmV0dXJuIH4gLiwgZGF0YSA9IGFbdW5saXN0KHRpbWVTbGljZXNfdHJhaW4pLF0sIA0KIyAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAia25uIiwgDQojICAgICAgICAgICAgICAgICAgIHByZVByb2MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSkNCiMgICBhX2FjdHVhbC5mdXR1cmVfdGVzdCA9IGFbb29zX3N0YXJ0Omxlbmd0aChkYXRlc19hbGwpLF0NCiMgICBhX3ByZWQuZnV0dXJlX3Rlc3QgPC0gcHJlZGljdChhX3ByZWQsIGFfYWN0dWFsLmZ1dHVyZV90ZXN0KSAgICAgIyBnZXQgcHJlZGljdGlvbg0KIyAgIGFfc2lnbmFsIDwtIFBfc2lnKGFfcHJlZC5mdXR1cmVfdGVzdCxhcy5tYXRyaXgocmV0dXJuc1t0aWNrW2ldXSksb29zX3N0YXJ0KSAgICAgICAgICAgICAgICAjIHRyYW5zZnJvbSBpbnRvIHNpZ25hbHMNCiMgICBzaWduYWxzX2tubl9QWyxpXSAgPC0gYV9zaWduYWwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBjb21iaW5lIHNpZ25hbHMgZm9yIGFsbCBzdG9ja3MNCiMgfQ0KIyB3ZWlnaHRfa25uX1AgPC0gY2FsX3BvcnRfd2VpZ2h0KHNpZ25hbHNfa25uX1ApDQojIHJldF9rbm5fUCA8LSByb3dTdW1zKHdlaWdodF9rbm5fUCpvb3NfcmV0KQ0KIyB3cml0ZS5jc3Yod2VpZ2h0X2tubl9QLCBmaWxlID0gIndlaWdodF9rbm5fUC5jc3YiKQ0KIyB3cml0ZS5jc3YocmV0X2tubl9QLCBmaWxlID0gInJldF9rbm5fUC5jc3YiKQ0KDQp3ZWlnaHRfa25uX1AgPSByZWFkLmNzdihmaWxlID0gIndlaWdodF9rbm5fUC5jc3YiKQ0Kd2VpZ2h0X2tubl9QID0gd2VpZ2h0X2tubl9QWy0xXQ0KDQpyZXRfa25uX1AgPSByZWFkLmNzdihmaWxlID0gInJldF9rbm5fUC5jc3YiKQ0KcmV0X2tubl9QID0gcmV0X2tubl9QWy0xXQ0KYGBgDQoNCiMjIyMjIFRyYWluIEtOTiB3aXRoIHBlcmNlbnRpbGUgJiBzaWduDQpgYGB7ciBrbm5fcCArIHNpZ24sIGVjaG8gPSBUfQ0KIyBzaWduYWxzX2tubl9QLnMgPC0gbWF0cml4KDAsIG5yb3cgPSBvb3NfbGVuZ3RoLCBuY29sID0gbGVuZ3RoKHRpY2spKSAjY3JlYXRlIHRhYmxlIHRvIHN0b3JlIHNpZ25hbHMNCiMgcHJlZF9rbm5fUC5zIDwtIG1hdHJpeCgwLCBucm93ID0gb29zX2xlbmd0aCwgbmNvbCA9IGxlbmd0aCh0aWNrKSkNCiMgY29sbmFtZXMoc2lnbmFsc19rbm5fUC5zKSA8LSB0aWNrDQojIGNvbG5hbWVzKHByZWRfa25uX1AucykgPC0gdGljaw0KIyBmb3IgKGkgaW4gMTpsZW5ndGgodGljaykpew0KIyAgIGEgPC0gc2VsZWN0RmVhdHVyZSh0aWNrW2ldKQ0KIyAgIGFfcHJlZCA8LSB0cmFpbihGX1JldHVybiB+IC4sIGRhdGEgPSBhW3VubGlzdCh0aW1lU2xpY2VzX3RyYWluKSxdLCANCiMgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImtubiIsIA0KIyAgICAgICAgICAgICAgICAgICBwcmVQcm9jID0gYygiY2VudGVyIiwgInNjYWxlIikpDQojICAgYV9hY3R1YWwuZnV0dXJlX3Rlc3QgPSBhW29vc19zdGFydDpsZW5ndGgoZGF0ZXNfYWxsKSxdDQojICAgYV9wcmVkLmZ1dHVyZV90ZXN0IDwtIHByZWRpY3QoYV9wcmVkLCBhX2FjdHVhbC5mdXR1cmVfdGVzdCkgICAgICMgZ2V0IHByZWRpY3Rpb24NCiMgICBhX3NpZ25hbCA8LSBQX3NpZyhhX3ByZWQuZnV0dXJlX3Rlc3QsYXMubWF0cml4KHJldHVybnNbdGlja1tpXV0pLG9vc19zdGFydCkjIHRyYW5zZnJvbSBpbnRvIHNpZ25hbHMNCiMgICBwcmVkX2tubl9QLnNbLCBpXSA8LSBhX3ByZWQuZnV0dXJlX3Rlc3QNCiMgICBmb3IgKGogaW4gMTogbGVuZ3RoKGFfc2lnbmFsKSl7DQojICAgICBpZiAoKGFfc2lnbmFsW2pdID09IDEpICYgKGFfcHJlZC5mdXR1cmVfdGVzdFtqXSA+IDApKSB7DQojICAgICAgIHNpZ25hbHNfa25uX1Auc1tqLCBpXSA9IDENCiMgICAgIH0gZWxzZSB7DQojICAgICAgIHNpZ25hbHNfa25uX1Auc1tqLCBpXSA9IDANCiMgICAgIH0NCiMgICB9DQojIH0NCiMgd2VpZ2h0X2tubl9QLnMgPC0gY2FsX3BvcnRfd2VpZ2h0KHNpZ25hbHNfa25uX1AucykNCiMgcmV0X2tubl9QLnMgPC0gcm93U3Vtcyh3ZWlnaHRfa25uX1Aucypvb3NfcmV0KQ0KIyB3cml0ZS5jc3Yod2VpZ2h0X2tubl9QLnMsIGZpbGUgPSAid2VpZ2h0X2tubl9QX3MuY3N2IikNCiMgd3JpdGUuY3N2KHJldF9rbm5fUC5zLCBmaWxlID0gInJldF9rbm5fUF9zLmNzdiIpDQoNCndlaWdodF9rbm5fUC5zID0gcmVhZC5jc3YoZmlsZSA9ICJ3ZWlnaHRfa25uX1Bfcy5jc3YiKQ0Kd2VpZ2h0X2tubl9QLnMgPSB3ZWlnaHRfa25uX1Auc1stMV0NCg0KcmV0X2tubl9QLnMgPSByZWFkLmNzdihmaWxlID0gInJldF9rbm5fUF9zLmNzdiIpDQpyZXRfa25uX1AucyA9IHJldF9rbm5fUC5zWy0xXQ0KYGBgDQoNCiMjIyMjIFRyYWluIEtOTiB3aXRoIDEtcGVyaW9kIG1vbWVudHVtIA0KYGBge3Iga25uX21vbTEsZWNobyA9IFR9DQojIHNpZ25hbHNfa25uX01vTS4xIDwtIG1hdHJpeCgwLCBucm93ID0gb29zX2xlbmd0aCwgbmNvbCA9IGxlbmd0aCh0aWNrKSkgI2NyZWF0ZSB0YWJsZSB0byBzdG9yZSBzaWduYWxzDQojIGNvbG5hbWVzKHNpZ25hbHNfa25uX01vTS4xKSA8LSB0aWNrDQojIGZvciAoaSBpbiAxOmxlbmd0aCh0aWNrKSl7DQojICAgYSA8LSBzZWxlY3RGZWF0dXJlKHRpY2tbaV0pDQojICAgYV9wcmVkIDwtIHRyYWluKEZfUmV0dXJuIH4gLiwgZGF0YSA9IGFbdW5saXN0KHRpbWVTbGljZXNfdHJhaW4pLF0sIA0KIyAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAia25uIiwgDQojICAgICAgICAgICAgICAgICAgIHByZVByb2MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSkNCiMgICBhX2FjdHVhbC5mdXR1cmVfdGVzdCA9IGFbb29zX3N0YXJ0Omxlbmd0aChkYXRlc19hbGwpLF0NCiMgICBhX3ByZWQuZnV0dXJlX3Rlc3QgPC0gcHJlZGljdChhX3ByZWQsIGFfYWN0dWFsLmZ1dHVyZV90ZXN0KSAgICAgIyBnZXQgcHJlZGljdGlvbg0KIyAgIGFfYWN0dWFsLmN1cnJlbnRfdGVzdCA8LSBhJEZfUmV0dXJuWyhvb3Nfc3RhcnQtMSk6KGxlbmd0aChkYXRlc19hbGwpLTEpXQ0KIyAgIGFfdGFibGUgPC0gY2JpbmQoYV9wcmVkLmZ1dHVyZV90ZXN0LGFfYWN0dWFsLmN1cnJlbnRfdGVzdCkNCiMgICBhX3NpZ25hbCA8LSBNb00uMV9zaWcoYV90YWJsZSkgICAgICAgICAgICAgICAgIyB0cmFuc2Zyb20gaW50byBzaWduYWxzDQojICAgc2lnbmFsc19rbm5fTW9NLjFbLGldICA8LSBhX3NpZ25hbFssM10gICAgICAgICAgIyBjb21iaW5lIHNpZ25hbHMgZm9yIGFsbCBzdG9ja3MNCiMgfQ0KIyB3ZWlnaHRfa25uX01vTS4xIDwtIGNhbF9wb3J0X3dlaWdodChzaWduYWxzX2tubl9Nb00uMSkgDQojIHJldF9rbm5fTW9NLjEgPC0gcm93U3Vtcyh3ZWlnaHRfa25uX01vTS4xKm9vc19yZXQpDQojIA0KIyB3cml0ZS5jc3Yod2VpZ2h0X2tubl9Nb00uMSwgZmlsZSA9ICJ3ZWlnaHRfa25uX01vTTEuY3N2IikNCiMgd3JpdGUuY3N2KHJldF9rbm5fTW9NLjEsIGZpbGUgPSAicmV0X2tubl9Nb00xLmNzdiIpDQoNCndlaWdodF9rbm5fTW9NLjEgPSByZWFkLmNzdihmaWxlID0gIndlaWdodF9rbm5fTW9NMS5jc3YiKQ0Kd2VpZ2h0X2tubl9Nb00uMSA9IHdlaWdodF9rbm5fTW9NLjFbLTFdDQoNCnJldF9rbm5fTW9NLjEgPSByZWFkLmNzdihmaWxlID0gInJldF9rbm5fTW9NMS5jc3YiKQ0KcmV0X2tubl9Nb00uMSA9IHJldF9rbm5fTW9NLjFbLTFdDQpgYGANCg0KIyMjIyMgVHJhaW4gS05OIHdpdGggMS1wZXJpb2QgbW9tZW50dW0gJiBzaWduDQpgYGB7ciBrbm5fbW9tMSArIHNpZ24sZWNobyA9IFR9DQojIHNpZ25hbHNfa25uX01vTS4xLnMgPC0gbWF0cml4KDAsIG5yb3cgPSBvb3NfbGVuZ3RoLCBuY29sID0gbGVuZ3RoKHRpY2spKSAjY3JlYXRlIHRhYmxlIHRvIHN0b3JlIHNpZ25hbHMNCiMgcHJlZF9rbm5fTW9NLjEucyA8LSBtYXRyaXgoMCwgbnJvdyA9IG9vc19sZW5ndGgsIG5jb2wgPSBsZW5ndGgodGljaykpDQojIGNvbG5hbWVzKHNpZ25hbHNfa25uX01vTS4xLnMpIDwtIHRpY2sNCiMgY29sbmFtZXMocHJlZF9rbm5fTW9NLjEucykgPC0gdGljaw0KIyANCiMgZm9yIChpIGluIDE6bGVuZ3RoKHRpY2spKXsNCiMgICBhIDwtIHNlbGVjdEZlYXR1cmUodGlja1tpXSkNCiMgICBhX3ByZWQgPC0gdHJhaW4oRl9SZXR1cm4gfiAuLCBkYXRhID0gYVt1bmxpc3QodGltZVNsaWNlc190cmFpbiksXSwgDQojICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJrbm4iLCANCiMgICAgICAgICAgICAgICAgICAgcHJlUHJvYyA9IGMoImNlbnRlciIsICJzY2FsZSIpKQ0KIyAgIGFfYWN0dWFsLmZ1dHVyZV90ZXN0ID0gYVtvb3Nfc3RhcnQ6bGVuZ3RoKGRhdGVzX2FsbCksXQ0KIyAgIGFfcHJlZC5mdXR1cmVfdGVzdCA8LSBwcmVkaWN0KGFfcHJlZCwgYV9hY3R1YWwuZnV0dXJlX3Rlc3QpICAgICAjIGdldCBwcmVkaWN0aW9uDQojICAgYV9hY3R1YWwuY3VycmVudF90ZXN0IDwtIGEkRl9SZXR1cm5bKG9vc19zdGFydC0xKToobGVuZ3RoKGRhdGVzX2FsbCktMSldDQojICAgYV90YWJsZSA8LSBjYmluZChhX3ByZWQuZnV0dXJlX3Rlc3QsYV9hY3R1YWwuY3VycmVudF90ZXN0KQ0KIyAgIGFfc2lnbmFsIDwtIE1vTS4xX3NpZyhhX3RhYmxlKSAgICAgICAgICAgICAgICAjIHRyYW5zZnJvbSBpbnRvIHNpZ25hbHMNCiMgICBwcmVkX2tubl9Nb00uMS5zWywgaV0gPC0gYV9wcmVkLmZ1dHVyZV90ZXN0DQojICAgZm9yIChqIGluIDE6IG5yb3coYV9zaWduYWwpKXsNCiMgICAgIGlmICgoYV9zaWduYWxbaiwzXSA9PSAxKSAmIChhX3ByZWQuZnV0dXJlX3Rlc3Rbal0gPiAwKSkgew0KIyAgICAgICBzaWduYWxzX2tubl9Nb00uMS5zW2osIGldID0gMQ0KIyAgICAgfSBlbHNlIHsNCiMgICAgICAgc2lnbmFsc19rbm5fTW9NLjEuc1tqLCBpXSA9IDANCiMgICAgIH0NCiMgICB9DQojIH0NCiMgd2VpZ2h0X2tubl9Nb00uMS5zIDwtIGNhbF9wb3J0X3dlaWdodChzaWduYWxzX2tubl9Nb00uMS5zKSANCiMgcmV0X2tubl9Nb00uMS5zIDwtIHJvd1N1bXMod2VpZ2h0X2tubl9Nb00uMS5zKm9vc19yZXQpDQojIA0KIyB3cml0ZS5jc3Yod2VpZ2h0X2tubl9Nb00uMS5zLCBmaWxlID0gIndlaWdodF9rbm5fTW9NMXMuY3N2IikNCiMgd3JpdGUuY3N2KHJldF9rbm5fTW9NLjEucywgZmlsZSA9ICJyZXRfa25uX01vTTFzLmNzdiIpDQoNCndlaWdodF9rbm5fTW9NLjEucyA9IHJlYWQuY3N2KGZpbGUgPSAid2VpZ2h0X2tubl9Nb00xcy5jc3YiKQ0Kd2VpZ2h0X2tubl9Nb00uMS5zID0gd2VpZ2h0X2tubl9Nb00uMS5zWy0xXQ0KDQpyZXRfa25uX01vTS4xLnMgPSByZWFkLmNzdihmaWxlID0gInJldF9rbm5fTW9NMXMuY3N2IikNCnJldF9rbm5fTW9NLjEucyA9IHJldF9rbm5fTW9NLjEuc1stMV0NCmBgYA0KDQojIyMjIyBUcmFpbiBLTk4gd2l0aCAzLXBlcmlvZCBtb21lbnR1bSANCmBgYHtyIGtubl9tb20zLGVjaG8gPSBUfQ0KIyBzaWduYWxzX2tubl9Nb00uMyA8LSBtYXRyaXgoMCwgbnJvdyA9IG9vc19sZW5ndGgsIG5jb2wgPSBsZW5ndGgodGljaykpICNjcmVhdGUgdGFibGUgdG8gc3RvcmUgc2lnbmFscw0KIyBjb2xuYW1lcyhzaWduYWxzX2tubl9Nb00uMykgPC0gdGljaw0KIyBmb3IgKGkgaW4gMTpsZW5ndGgodGljaykpew0KIyAgIGEgPC0gc2VsZWN0RmVhdHVyZSh0aWNrW2ldKQ0KIyAgIGFfcHJlZCA8LSB0cmFpbihGX1JldHVybiB+IC4sIGRhdGEgPSBhW3VubGlzdCh0aW1lU2xpY2VzX3RyYWluKSxdLCANCiMgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImtubiIsIA0KIyAgICAgICAgICAgICAgICAgICBwcmVQcm9jID0gYygiY2VudGVyIiwgInNjYWxlIikpDQojICAgYV9hY3R1YWwuZnV0dXJlX3Rlc3QgPSBhW29vc19zdGFydDpsZW5ndGgoZGF0ZXNfYWxsKSxdDQojICAgcHJlZCA8LSBwcmVkaWN0KGFfcHJlZCwgYV9hY3R1YWwuZnV0dXJlX3Rlc3QpICAgICAjIGdldCBwcmVkaWN0aW9uDQojICAgYV9wcmVkLmZ1dHVyZV90ZXN0IDwtIGMoMCwwLDAscHJlZCkgICAgICAgICAgICMgbWFrZSBsZW5naHQgbG9uZ2VyIGZvciBwcmV2aW91cyBhY3R1YWwgcmV0dXJucw0KIyAgIGFfYWN0dWFsLmN1cnJlbnRfdGVzdCA8LSBhJEZfUmV0dXJuWyhvb3Nfc3RhcnQtMyk6KGxlbmd0aChkYXRlc19hbGwpKV0NCiMgICBhX3RhYmxlIDwtIGNiaW5kKGFfcHJlZC5mdXR1cmVfdGVzdCxhX2FjdHVhbC5jdXJyZW50X3Rlc3QpDQojICAgYV9zaWduYWwgPC0gTW9NLjNfc2lnKGFfdGFibGUpICAgICAgICAgICAgICAgICMgdHJhbnNmcm9tIGludG8gc2lnbmFscw0KIyAgIA0KIyAgIHNpZ25hbHNfa25uX01vTS4zWyxpXSAgPC0gYV9zaWduYWxbLDNdICAgICAgICAgICMgY29tYmluZSBzaWduYWxzIGZvciBhbGwgc3RvY2tzDQojIH0NCiMgd2VpZ2h0X2tubl9Nb00uMyA8LSBjYWxfcG9ydF93ZWlnaHQoc2lnbmFsc19rbm5fTW9NLjMpDQojIHJldF9rbm5fTW9NLjMgPC1yb3dTdW1zKHdlaWdodF9rbm5fTW9NLjMqb29zX3JldCkNCiMgDQojIHdyaXRlLmNzdih3ZWlnaHRfa25uX01vTS4zLCBmaWxlID0gIndlaWdodF9rbm5fTW9NMy5jc3YiKQ0KIyB3cml0ZS5jc3YocmV0X2tubl9Nb00uMywgZmlsZSA9ICJyZXRfa25uX01vTTMuY3N2IikNCg0Kd2VpZ2h0X2tubl9Nb00uMyA9IHJlYWQuY3N2KGZpbGUgPSAid2VpZ2h0X2tubl9Nb00zLmNzdiIpDQp3ZWlnaHRfa25uX01vTS4zID0gd2VpZ2h0X2tubl9Nb00uM1stMV0NCg0KcmV0X2tubl9Nb00uMyA9IHJlYWQuY3N2KGZpbGUgPSAicmV0X2tubl9Nb00zLmNzdiIpDQpyZXRfa25uX01vTS4zID0gcmV0X2tubl9Nb00uM1stMV0NCmBgYA0KDQojIyMjIyBUcmFpbiBLTk4gd2l0aCAzLXBlcmlvZCBtb21lbnR1bSAmIHNpZ24NCmBgYHtyIGtubl9tb20zICsgc2lnbixlY2hvID0gVH0NCiMgc2lnbmFsc19rbm5fTW9NLjMucyA8LSBtYXRyaXgoMCwgbnJvdyA9IG9vc19sZW5ndGgsIG5jb2wgPSBsZW5ndGgodGljaykpICNjcmVhdGUgdGFibGUgdG8gc3RvcmUgc2lnbmFscw0KIyBjb2xuYW1lcyhzaWduYWxzX2tubl9Nb00uMy5zKSA8LSB0aWNrDQojIHByZWRfa25uX01vTS4zLnMgPC0gbWF0cml4KDAsIG5yb3cgPSBvb3NfbGVuZ3RoLCBuY29sID0gbGVuZ3RoKHRpY2spKSANCiMgY29sbmFtZXMocHJlZF9rbm5fTW9NLjMucykgPC0gdGljaw0KIyANCiMgZm9yIChpIGluIDE6bGVuZ3RoKHRpY2spKXsNCiMgICBhIDwtIHNlbGVjdEZlYXR1cmUodGlja1tpXSkNCiMgICBhX3ByZWQgPC0gdHJhaW4oRl9SZXR1cm4gfiAuLCBkYXRhID0gYVt1bmxpc3QodGltZVNsaWNlc190cmFpbiksXSwgDQojICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJrbm4iLCANCiMgICAgICAgICAgICAgICAgICAgcHJlUHJvYyA9IGMoImNlbnRlciIsICJzY2FsZSIpKQ0KIyAgIGFfYWN0dWFsLmZ1dHVyZV90ZXN0ID0gYVtvb3Nfc3RhcnQ6bGVuZ3RoKGRhdGVzX2FsbCksXQ0KIyAgIHByZWQgPC0gcHJlZGljdChhX3ByZWQsIGFfYWN0dWFsLmZ1dHVyZV90ZXN0KSAgICAgIyBnZXQgcHJlZGljdGlvbg0KIyAgIGFfcHJlZC5mdXR1cmVfdGVzdCA8LSBjKDAsMCwwLHByZWQpICAgICAgICAgICAjIG1ha2UgbGVuZ2h0IGxvbmdlciBmb3IgcHJldmlvdXMgYWN0dWFsIHJldHVybnMNCiMgICBhX2FjdHVhbC5jdXJyZW50X3Rlc3QgPC0gYSRGX1JldHVyblsob29zX3N0YXJ0LTMpOihsZW5ndGgoZGF0ZXNfYWxsKSldDQojICAgYV90YWJsZSA8LSBjYmluZChhX3ByZWQuZnV0dXJlX3Rlc3QsYV9hY3R1YWwuY3VycmVudF90ZXN0KQ0KIyAgIGFfc2lnbmFsIDwtIE1vTS4zX3NpZyhhX3RhYmxlKSAgICAgICAgICAgICAgICAjIHRyYW5zZnJvbSBpbnRvIHNpZ25hbHMNCiMgICANCiMgICBwcmVkX2tubl9Nb00uMy5zWywgaV0gPC0gYV9wcmVkLmZ1dHVyZV90ZXN0WzQ6bGVuZ3RoKGFfcHJlZC5mdXR1cmVfdGVzdCldDQojICAgZm9yIChqIGluIDE6IG5yb3coYV9zaWduYWwpKXsNCiMgICAgIGlmICgoYV9zaWduYWxbaiwgM10gPT0gMSkgJiAoYV9wcmVkLmZ1dHVyZV90ZXN0W2pdID4gMCkpIHsNCiMgICAgICAgc2lnbmFsc19rbm5fTW9NLjMuc1tqLCBpXSA9IDENCiMgICAgIH0gZWxzZSB7DQojICAgICAgIHNpZ25hbHNfa25uX01vTS4zLnNbaiwgaV0gPSAwDQojICAgICB9DQojICAgfQ0KIyB9DQojIHdlaWdodF9rbm5fTW9NLjMucyA8LSBjYWxfcG9ydF93ZWlnaHQoc2lnbmFsc19rbm5fTW9NLjMucykNCiMgcmV0X2tubl9Nb00uMy5zIDwtcm93U3Vtcyh3ZWlnaHRfa25uX01vTS4zLnMqb29zX3JldCkNCiMgDQojIHdyaXRlLmNzdih3ZWlnaHRfa25uX01vTS4zLnMsIGZpbGUgPSAid2VpZ2h0X2tubl9Nb00zcy5jc3YiKQ0KIyB3cml0ZS5jc3YocmV0X2tubl9Nb00uMy5zLCBmaWxlID0gInJldF9rbm5fTW9NM3MuY3N2IikNCg0Kd2VpZ2h0X2tubl9Nb00uMy5zID0gcmVhZC5jc3YoZmlsZSA9ICJ3ZWlnaHRfa25uX01vTTNzLmNzdiIpDQp3ZWlnaHRfa25uX01vTS4zLnMgPSB3ZWlnaHRfa25uX01vTS4zLnNbLTFdDQoNCnJldF9rbm5fTW9NLjMucyA9IHJlYWQuY3N2KGZpbGUgPSAicmV0X2tubl9Nb00zcy5jc3YiKQ0KcmV0X2tubl9Nb00uMy5zID0gcmV0X2tubl9Nb00uMy5zWy0xXQ0KYGBgDQoNCiMjIyMjIFJlc3VsdDogUGVmb3JtYW5jZSBNZXRyaWNzIGZvciBLTk4gKE51bWVyaWNhbCkNCmBgYHtyIFBNIGZvciBrbm5fbnVtZXJpY2FsfQ0KTV9rbm5fUCA8LSBwZXJmX21ldDIoYXMubWF0cml4KHJldF9rbm5fUCksYXMubWF0cml4KHdlaWdodF9rbm5fUCksZGF0ZXNfb29zLA0KICAgICAgICAgICAgICAgICAgICBwb3J0X25hbWUgPSAiS05OX1BlcmNlbnRpbGUiLHJldHVybnMpDQpNX2tubl9Nb00uMSA8LSBwZXJmX21ldDIoYXMubWF0cml4KHJldF9rbm5fTW9NLjEpLGFzLm1hdHJpeCh3ZWlnaHRfa25uX01vTS4xKSxkYXRlc19vb3MsDQogICAgICAgICAgICAgICAgICAgICAgICBwb3J0X25hbWUgPSAiS05OX01vbWVudHVtLjEiLCByZXR1cm5zKQ0KTV9rbm5fTW9NLjMgPC0gcGVyZl9tZXQyKGFzLm1hdHJpeChyZXRfa25uX01vTS4zKSxhcy5tYXRyaXgod2VpZ2h0X2tubl9Nb00uMyksZGF0ZXNfb29zLA0KICAgICAgICAgICAgICAgICAgICAgICAgcG9ydF9uYW1lID0gIktOTl9Nb21lbnR1bS4zIiwgcmV0dXJucykNCg0KTV9rbm5fUC5zIDwtIHBlcmZfbWV0Mihhcy5tYXRyaXgocmV0X2tubl9QLnMpLGFzLm1hdHJpeCh3ZWlnaHRfa25uX1AucyksZGF0ZXNfb29zLA0KICAgICAgICAgICAgICAgICAgICBwb3J0X25hbWUgPSAiS05OX1BlcmNlbnRpbGUgd2l0aCBzaWduIixyZXR1cm5zKQ0KTV9rbm5fTW9NLjEucyA8LSBwZXJmX21ldDIoYXMubWF0cml4KHJldF9rbm5fTW9NLjEucyksYXMubWF0cml4KHdlaWdodF9rbm5fTW9NLjEucyksZGF0ZXNfb29zLA0KICAgICAgICAgICAgICAgICAgICAgICAgcG9ydF9uYW1lID0gIktOTl9Nb21lbnR1bS4xIHdpdGggc2lnbiIsIHJldHVybnMpDQpNX2tubl9Nb00uMy5zIDwtIHBlcmZfbWV0Mihhcy5tYXRyaXgocmV0X2tubl9Nb00uMy5zKSxhcy5tYXRyaXgod2VpZ2h0X2tubl9Nb00uMy5zKSxkYXRlc19vb3MsDQogICAgICAgICAgICAgICAgICAgICAgICBwb3J0X25hbWUgPSAiS05OX01vbWVudHVtLjMgd2l0aCBzaWduIiwgcmV0dXJucykNCg0KDQpOdW1lcmljYWxfa25uX3dpdGgucyA8LSByYmluZChNX2tubl9QLE1fa25uX01vTS4xLE1fa25uX01vTS4zLA0KICAgICAgICAgICAgICAgICAgICAgICBNX2tubl9QLnMsTV9rbm5fTW9NLjEucyxNX2tubl9Nb00uMy5zKQ0KTnVtZXJpY2FsX2tubl93aXRoLnMNCmBgYA0KDQojIyMjIFJhbmRvbSBGb3Jlc3QNCg0KIyMjIyMgR3Jvd2luZyBhIFBvcnRmb2xpbw0KUmFuZG9tIEZvcmVzdCBpcyBhbm90aGVyIGNvbW1vbiBtYWNoaW5lIGxlYXJuaW5nIHRlY2huaXF1ZXMgd2hpY2ggZGV2ZWxvcGVkIGZyb20gYSBkZWNpc2lvbiB0cmVlLiBJdCB3b3JrcyB3ZWxsIHdpdGggYm90aCBjbGFzc2lmaWNhdGlvbiBhbmQgcmVncmVzc2lvbiBwcm9ibGVtcy4gSXQgaXMgYSBwb3dlcmZ1bCB0ZWNobmlxdWUgZHVlIHRvIGl0cyBoaWdobHkgbm9uLWxpbmVhciBtb2RlbC4gSG93ZXZlciB0aGVyZSBpcyBhIHRyYWRlLW9mZiBieSBoYXZpbmcgbWFueSBoeXBlcnBhcmFtZXRlcnMgbmFtZWx5ICoqbXRyeSoqIChudW1iZXIgb2YgcHJlZGljdG9ycyBzYW1wbGVkIGZvciBzcGxpdGluZyBhdCBlYWNoIG5vZGUuKSBhbmQgKipudHJlZSoqIChudW1iZXIgb2YgdHJlZSBpbiB0aGUgZm9yZXN0KS4gSW4gdGhpcyBjYXNlLCBzaWduYWwgY3JpdGVyaWEgKHBlcmNlbnRpbGUgb2YgbW9tZW50dW0pIGlzIGFsc28gdHJlYXQgYXMgYW5vdGhlciBoeXBlcnBhcmFtZXRlciBzaW5jZSBkaWZmZXJlbnQgYWxnb3JpdGhtcyB5aWVsZCBkaWZmZXJlbnQgcG9ydGZvbGlvIHdlaWdodC4NCg0KUmVncmVzc2lvbiByYW5kb20gZm9yZXN0IGlzIHVzZWQgaW4gdGhpcyBzZXR0aW5nLCB3aGVyZSB0aGUgdXNlciBkZWZpbmUgYSB2YWx1ZSBmb3IgaHlwZXJwYXJhbWV0ZXIuIChJbXBhY3Qgb2YgaHlwZXJwYXJhbWV0ZXIgaXMgZGlzY3Vzc2VkIGluIHRoZSBuZXh0IHNlY3Rpb24pLiBGb3IgdGhlIFJhbmRvbSBGb3Jlc3QgcG9ydGZvbGlvLCB0aGUgd2VpZ2h0IGZvciBlYWNoIGFzc2V0IGF0IGVhY2ggdGltZSBwZXJpb2Qgd2lsbCBiZSBkZXRlcm1pbmVkIGJ5IHRoZSBmb2xsb3dpbmcgcHJvY2VkdXJlczoNCg0KMS4gQXQgZWFjaCBkYXRlLCB0cmFpbiBSYW5kb20gRm9yZXN0IG1vZGVsIGJ5IHVzaW5nIHBhc3QgZGF0YS4gKE5vdGUgdGhhdCBkaWZmZXJlbnQgYXNzZXRzIGhhdmUgZGlmZmVyZW50IG1vZGVsIGR1ZSB0byBmZWF0dXJlIHNlbGVjdGlvbiBwcm9jZXNzIGZvciBlYWNoIGFzc2V0KQ0KMi4gVXNlIHRoZSBtb2RlbCB0byBnZXQgYSBwcmVkaWN0aW9uIGZvciBmdXR1cmUgcmV0dXJuIChGX1JldHVybikNCjMuIERlcml2ZSBpbnZlc3RtZW50IGRlY2lzaW9uIGZvciBlYWNoIGFzc2V0IGJ5IGFwcGx5aW5nIHNpZ25hbCBjcml0ZXJpYSAoZWl0aGVyIHBlcmNlbnRpbGUgb3IgbW9tZW50dW0pdG8gdGhlIHByZWRpY3Rpb24gDQo0LiBOb3JtYWxpc2UgcG9ydGZvbGlvIHdlaWdodCB1c2luZyBlcXVhbGx5IHdlaWdodGVkIG1ldGhvZA0KDQpUaGVuIHRoZSBzdHJhdGVneSB3aWxsIGJlIGV2YWx1YXRlZCBvbiBpdHMgcGVyZm9ybWFuY2UgbWV0cmljcyBiYXNlZCBvbiB0aGUgcG9ydGZvbGlvIHdlaWdodCBhbmQgcmV0dXJuLg0KDQpGaXJzdCwgKip3ZWlnaHRfUkYqKiBmdW5jdGlvbiBpcyBkZWZpbmVkIHRvIGNhbGN1bGF0ZWQgcG9ydGZvbGlvIHdlaWdodCB1c2luZyBSYW5kb20gRm9yZXN0IHRlY2huaXF1ZS4gVGhlIGZ1bmN0aW9uIHJlY2VpdmVzIHBhc3QgYW5kIGN1cnJlbnQgZGF0YSwgbXRyeSwgbnRyZWUsIGFuZCBzX2NyaXRlcmlhIChzX2NyaXRlcmlhKSBhcyBwYXJhbWV0ZXJzIGFuZCByZXR1cm4gdGhlIGJ1eWluZyBkZWNpc2lvbiBhcyBvdXRwdXQuDQoNCmBgYHtyIHJmIDEsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KaWYoIXJlcXVpcmUocmFuZG9tRm9yZXN0KSl7aW5zdGFsbC5wYWNrYWdlcygicmFuZG9tRm9yZXN0Iil9DQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCndlaWdodF9SRiA8LSBmdW5jdGlvbihwYXN0X2RhdGEsIGN1cnJlbnRfZGF0YSwgbXRyeSwgbnRyZWUsIHNfY3JpdGVyaWEsIHByZXZfdyl7ICMgRnVuY3Rpb24gZm9yIHdlaWdodCBjYWxjdWxhdGlvbiB1c2luZyByYW5kb20gZm9yZXN0IHRlY2huaXF1ZQ0KICAgICAgdHJhaW4gPC0gcGFzdF9kYXRhWy1jKDEpXSAjVHJhaW5pbmcgZGF0YSwgZHJvcCBkYXRlIGNvbHVtbnMNCiAgICAgIHRlc3QgPC0gY3VycmVudF9kYXRhWy1jKDEsMildICNUZXN0aW5nIGRhdGEsIGRyb3AgZGF0ZSBhbmQgRl9SZXR1cm4gY29sdW1ucw0KICAgICAgcmYuZml0IDwtIHJhbmRvbUZvcmVzdChGX1JldHVybiB+IC4sICMgR3JvdyByYW5kb20gZm9yZXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLCAjIFRyYWluaW5nIGRhdGENCiAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gbWluKG10cnksbGVuZ3RoKHRlc3QpKSwgIyBOdW1iZXIgb2YgcHJlZGljdGl2ZSB2YXJpYWJsZXMgdXNlZCBpcyA0IChvciBsZXNzIHRoYW4gaWYgdGhlcmUgaXMgbGVzcyB2YXJpYWJsZSkNCiAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IG50cmVlKSAjIE51bWJlciBvZiByYW5kb20gdHJlZXMgaW4gdGhlIGZvcmVzdA0KICAgICAgcHJlZCA8LSBwcmVkaWN0KHJmLmZpdCwgdGVzdCkgIyBQcmVkaWN0IEZfUmV0dXJuIHVzaW5nIHRoZSBmaXR0ZWQgbW9kZWwNCiAgICAgIGlmIChzX2NyaXRlcmlhID09IDEpeyAjIHBlcmNlbnRpbGUgc2lnbmFsIGNyaXRlcmlhDQogICAgICAgIHJldHVybiAoaWZlbHNlKHByZWQgPCBxdWFudGlsZShwYXN0X2RhdGEkRl9SZXR1cm4sIGMoLjMzKSksIDAsIGlmZWxzZShwcmVkIDwgcXVhbnRpbGUocGFzdF9kYXRhJEZfUmV0dXJuLCBjKC42NikpLCBwcmV2X3csIDEpKSkNCiAgICAgIH0gZWxzZSBpZiAoc19jcml0ZXJpYSA9PSAyKXsgIyBtb21lbnR1bSBzaWduYWwgY3JpdGVyaWENCiAgICAgICAgcmV0dXJuIChpZmVsc2UocHJlZCA+IDAsIChpZmVsc2UocGFzdF9kYXRhW25yb3cocGFzdF9kYXRhKS0xLF0kRl9SZXR1cm4gPiAwICYgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdF9kYXRhW25yb3cocGFzdF9kYXRhKS0yLF0kRl9SZXR1cm4gPiAwICYgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdF9kYXRhW25yb3cocGFzdF9kYXRhKS0zLF0kRl9SZXR1cm4gPiAwLCAxLCAwKSksIDApKQ0KICAgICAgfSBlbHNlIHsgIyBqdXN0IGludmVzdCBpZiByZXR1cm4gaXMgcG9zaXRpdmUNCiAgICAgICAgcmV0dXJuIChpZmVsc2UocHJlZCA+IDAsIDEsIDApKSAjIEludmVzdCBpZiByZXR1cm4gPiAwICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgfQ0KfQ0KYGBgDQoNCkZ1bmN0aW9uICoqZG9SRioqIHBlcmZvcm1zIGJhY2t0ZXN0aW5nIGV2YWx1YXRpb24gYnkgY3JlYXRpbmcgYSBwb3J0Zm9saW8gYnkgdXNpbmcgUmFuZG9tIEZvcmVzdCB0ZWNobmlxdWUsIGdpdmVuIGEgc3BlY2lmaWMgc2V0IG9mIGh5cGVycGFyYW1ldGVyIHZhbHVlcy4gVGhlIGZ1bmNpb24gcmV0dXJucyBhIHBlcmZvcm1hbmNlIG1ldHJpYyBhcyBhIHJlc3VsdC4NCmBgYHtyIHJmIDIsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KZG9SRiA8LSBmdW5jdGlvbihtdHJ5ID0gNCwgbnRyZWUgPSAxMCwgc19jcml0ZXJpYSA9IDApew0KICBwb3J0Zl93ZWlnaHRfUkYgPC0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgoZGF0ZXNfb29zKS0xLCBuY29sID0gbGVuZ3RoKHRpY2spKSAjIEluaXRpYXRlIHdlaWdodCBtYXRyaXggZm9yIFJGIHBvcnRmb2xpbw0KICBwb3J0Zl9yZXR1cm5fUkYgPC0gYygpICMgIyBJbml0aWF0ZSByZXR1cm4gbGlzdCBmb3IgUkYgcG9ydGZvbGlvDQogIHByZXZfdyA8LSAwDQogIGZvciAoaSBpbiAxOmxlbmd0aCh0aWNrKSl7ICMgUmVwZWF0IGZvciBhbGwgdGlja2VyDQogICAgdGVtcCA8LSBzZWxlY3RGZWF0dXJlKHRpY2tbaV0pICMgRmVhdHVyZSBzZWxlY3Rpb24gZm9yIHRpY2tlciBpIA0KICAgIHcgPC0gYygpICMgSW5pdGlhdGUgYW4gZW1wdHkgbGlzdCBvZiB3ZWlnaHQgZm9yIHRpY2tlciBpDQogICAgZm9yICh0IGluIDI6KGxlbmd0aChkYXRlc19vb3MpKSl7ICMgRXhwYW5kaW5kIHdpbmRvdyBmb3Igb3V0LW9mLXNhbXBsZSBkYXRlcw0KICAgICAgcGFzdF9kYXRhIDwtIHRlbXAgJT4lIGZpbHRlcihEYXRlIDwgZGF0ZXNfb29zW3QtMV0pICMgUGFzdCBkYXRhIGlzIGFsbCBkYXRhIGZyb20gdGhlIGRhdGUgYmVmb3JlIGRhdGUgdA0KICAgICAgY3VycmVudF9kYXRhIDwtIHRlbXAgJT4lIGZpbHRlcihEYXRlID09IGRhdGVzX29vc1t0LTFdKSAjIEN1cnJlbnQgZGF0YSBpcyBkYXRhIGF0IGRhdGUgdA0KICAgICAgcmVzIDwtIHdlaWdodF9SRihwYXN0X2RhdGEgPSBwYXN0X2RhdGEsIGN1cnJlbnRfZGF0YSA9IGN1cnJlbnRfZGF0YSwgbXRyeSA9IG10cnksIG50cmVlID0gbnRyZWUsIHNfY3JpdGVyaWEgPSBzX2NyaXRlcmlhLCBwcmV2X3cgPSBwcmV2X3cpICMgR2V0IHdlaWdodCB1c2luZyByYW5kb20gZm9yZXN0IG1ldGhvZCB3aXRoIHNwZWNpZmljIHBhcmFtZXRlcg0KICAgICAgdyA9IHJiaW5kKHcsIHJlcykgIyBBcHBlbmQgZGF0ZSBmb3Igc3RvY2sgaSBhdCBwZXJpb2QgdA0KICAgICAgcHJldl93IDwtIHcNCiAgICB9DQogICAgcG9ydGZfd2VpZ2h0X1JGWyxpXSA8LSB3ICMgQXBwZW5kIHdlaWdodCBmb3Igc3RvY2sgaSBmb3IgYWxsIHBlcmlvZCB0DQogIH0NCiAgcG9ydGZfd2VpZ2h0X1JGIDwtIGdldEVXKHBvcnRmX3dlaWdodF9SRikgIyBOb3JtYWxpc2Ugd2VpZ2h0IChFcXVhbGx5IHdlaWdodGVkKQ0KICBmb3IoaiBpbiAxOihsZW5ndGgoZGF0ZXNfb29zKS0xKSl7IA0KICAgIHBvcnRmX3JldHVybl9SRltqXSA8LSBzdW0ocG9ydGZfd2VpZ2h0X1JGW2osXSAqIHJldHVybnNfb29zW2osXSkgIyBDYWxjdWxhdGUgcG9ydGZvbGlvIHJldHVybg0KICB9DQogIHJldHVybiAocGVyZl9tZXQocG9ydGZfcmV0dXJuX1JGLCBwb3J0Zl93ZWlnaHRfUkYsIGFzc2V0X3JldHVybikpICMgR2V0IHBlcmZvcm1hbmNlIG1ldHJpY3MNCn0NCmBgYA0KDQpOZXh0LCBjcmVhdGUgYSBwb3J0Zm9saW8gYmFzZWQgb24gUmFuZG9tIEZvcmVzdCBzdHJhdGVneSBhbmQgZXZhbHVhdGUgdGhlIHBlcmZvcm1hbmNlDQpgYGB7ciByZiAzLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0NCmRvUkYoKQ0KYGBgDQojIyMjIyBTZW5zaXRpdml0eSBBbmFseXNpcyBmb3IgSHlwZXJwYXJhbWV0ZXJzIC0gVXNpbmcgdGhlIEdyaWRTZWFyY2gNCg0KUGVyZm9ybWFuY2UgY291bGQgYmUgaW1wcm92ZWQgYnkgdXNpbmcgdGhlIHJpZ2h0IHZhbHVlcyBvZiBoeXBlcnBhcmFtZXRlci4gRHVyaW5nIHRoZSB0cmFkaW5nIHBlcmlvZCwgKipjcm9zcy12YWxpZGF0aW9uKiogdGVjaG5pcXVlIGNvdWxkIGJlIHVzZWQgdG8gdHVuZSBoeXBlcnBhcmFtZXRlci4gQW5hbHlzaXMgYmVsb3cgZGVtb25zdHJhdGUgaG93IG11Y2ggZG9lcyBoeXBlcm1ldGVycyBpbXBhY3QgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBwb3J0Zm9saW8gYnkgYXBwbHlpbmcgKipHcmlkU2VhcmNoKiogdGVjaG5pcXVlLiBHcmlkU2VhcmNoIGNhbiBiZSBwZXJmb3JtZWQgd2l0aCB0aGUgZm9sbG93aW5nIHN0ZXA6DQoNCjEuIFNldCB1cCByYW5nZSBvZiB2YWx1ZXMgdG8gYmUgdGVzdGVkIGZvciBlYWNoIGh5cGVycGFyYW1ldGVyDQoyLiBTZXQgdXAgbGlzdCBvZiBhbGwgcG9zc2libGUgY29tYmluYXRpb25zIGZvciBhbGwgaHlwZXJwYXJhbWV0ZXJzDQozLiBFdmFsdWF0ZSB0aGUgbW9kZWwgb24gYWxsIHNldHRpbmdzIGZyb20gc3RlcCAyDQoNClRoaXMgb3BlcmF0aW9uIGNvdWxkIHNpZ25pZmljYW50bHkgY29uc3VtZSBhIGh1Z2UgcnVudGltZS4gVGhlcmVmb3JlICoqUmFuZG9tU2VhcmNoKiogY2FuIGFsc28gYmUgdXNlZCB0byByZWR1Y2UgcnVudGltZS4gVGhlIGNvbmNlcHQgb2YgUmFuZG9tU2VhcmNoIGlzIHRvIGluaXRpYWxseSByYW5kb21seSBzZXQgYSBsaXN0IG9mIGh5cGVycGFyYW1ldGVycyB0byBiZSB0ZXN0ZWQuIFRoZW4gdGhlIHNldCB0aGF0IGdpdmVzIHRoZSBiZXN0IHBlcmZvcm1hbmNlIHdpbGwgYmUgdXNlZCB0byB0dW5lZCB0aGUgaHlwZXJwYXJhbWV0ZXJzIG9uY2UgYWdhaW4gYnkgZG9pbmcgR3JpZFNlYXJjaCBhcm91bmQgdGhvc2UgcG9pbnRzLg0KDQpCZWxvdywgR3JpZFNlYXJjaCBpcyBwZXJmb3JtZWQgb24gc2VsZWN0ZWQgY29tYmluYXRpb25zIG9mIGh5cGVycGFyYW1ldGVycyBhbmQgdGhlIHJlc3VsdGluZyBhdmVyYWdlIHJldHVybiBmb3IgZWFjaCBzZXR0aW5nIGlzIHZpc3VhbGl6ZWQuDQoNCmBgYHtyIHJmIDQsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KbXRyeSA8LSBjKDYsIDcsIDgsIDksIDEwKSAjIExpc3Qgb2YgbXRyeSB0byBiZSB0ZXN0ZWQNCm50cmVlIDwtIGMoMTAsIDIwLCAzMCwgNDAsIDUwKSAjIExpc3Qgb2YgbnRyZWUgdG8gYmUgdGVzdGVkDQpzX2NyaXRlcmlhIDwtIGMoMCwgMSwgMikgIyBMaXN0IG9mIHNpZ25hbCBjcml0ZXJpYSB0byBiZSB0ZXN0ZWQNCnBhcnNfUkYgPC0gZXhwYW5kLmdyaWQobXRyeSwgbnRyZWUsIHNfY3JpdGVyaWEpICAjIEV4cGxvcmluZyBhbGwgY29tYmluYXRpb25zDQptdHJ5IDwtIHBhcnNfUkZbLDFdDQpudHJlZSA8LSBwYXJzX1JGWywyXQ0Kc19jcml0ZXJpYSA8LSBwYXJzX1JGWywzXQ0KZ3JpZF9wYXJzX1JGIDwtIGZ1bmN0aW9uKG10cnksIG50cmVlLCBzX2NyaXRlcmlhKXsgICMgUGFyYW1ldGVycyBmb3IgdGhlIGdyaWQgc2VhcmNoDQogIHByaW50KHBhc3RlKCdtdHJ5OiAnLCB0b1N0cmluZyhtdHJ5KSwgJyBudHJlZTogJywgdG9TdHJpbmcobnRyZWUpLCAnIHNfY3JpdGVyaWE6ICcsIHRvU3RyaW5nKHNfY3JpdGVyaWEpKSkNCiAgdGVtcCA8LSBkb1JGKG10cnkgPSBtdHJ5LCBudHJlZSA9IG50cmVlLCBzX2NyaXRlcmlhID0gc19jcml0ZXJpYSkNCiAgcmV0dXJuICh0ZW1wJGF2Z19yZXQpICMgUmV0dXJuIGF2ZXJhZ2UgcmV0dXJuDQp9DQoNCiMgTG9hZCByZXN1bHQgd2hpY2ggaGFzIGFscmVhZHkgYmVlbiBjb21wdXRlZCB0byByZWR1Y2UgcnVudGltZSANCmdyZCA8LSByZWFkLmNzdignZ3JkX1JGLmNzdicsIGhlYWRlcj1UUlVFLCBzZXA9IiwiKQ0KDQojIElmIGhhdmVuJ3QgY29tcHV0ZWQgYmVmb3JlLCB1bmNvbW1lbnQgdGhlIGZvbGxvd2luZyBibG9jaw0KIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSMNCiMgZ3JkIDwtIHBtYXAobGlzdChtdHJ5LCBudHJlZSwgc19jcml0ZXJpYSksICMgUGFyYW1ldGVycyBmb3IgdGhlIGdyaWQgc2VhcmNoDQojICAgICAgICAgICAgIGdyaWRfcGFyc19SRikgIyBJbnB1dCBmb3IgZnVuY3Rpb246IHRlc3QgbGFiZWxzIA0KIyBncmQgPC0gZGF0YS5mcmFtZShtdHJ5LCBudHJlZSwgc19jcml0ZXJpYSwgYXZnX3JldCA9IHVubGlzdChncmQpKQ0KIyB3cml0ZS5jc3YoZ3JkLCBmaWxlID0gImdyZF9SRi5jc3YiLCByb3cubmFtZXM9RkFMU0UpICMgS2VlcCBhcyBjc3YgZmlsZSBvbiBsb2NhbA0KIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSMNCg0KIyBncmQkc19jcml0ZXJpYSA8LSBpZmVsc2UoZ3JkJHNfY3JpdGVyaWEgPT0gMCwgIm5vbmUiLCBpZmVsc2UoZ3JkJHNfY3JpdGVyaWEgPT0gMSwgInBlcmNlbnRpbGUiLCAibW9tZW50dW0iKSkNCiMgZ3JkICU+JSBnZ3Bsb3QoYWVzKHggPSBzX2NyaXRlcmlhLCB5ID0gYXZnX3JldCwgZmlsbCA9IHNfY3JpdGVyaWEpKSArICMgUGxvdCENCiMgICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQojICAgICBmYWNldF9ncmlkKHJvd3MgPSB2YXJzKG10cnkpLCBjb2xzID0gdmFycyhudHJlZSkpDQpgYGANCg0KIyMjIyMgUmVzdWx0OiBQZWZvcm1hbmNlIE1ldHJpY3MgZm9yIFJhbmRvbSBGb3Jlc3QNCk9idmlvdXNseSwgdmFyaW91cyBjb21iaW5hdGlvbnMgb2YgaHlwYXJhbWV0ZXJzIGltcGFjdCBhdmVyYWdlIHJldHVybiBvZiB0aGUgcG9ydGZvbGlvIHNpZ25pZ2ljYW50bHkuIFRoZSBiZXN0IHNldCBvZiBoeXBlcnBhcmFtZXRlciBhcmU6IG10cnkgPSA5LCBudHJlZSA9IDIwLCBhbmQgcGVyY2VudGlsZSBzaWduYWwgY3JpdGVyaWEuIEZ1bGwgcGVyZm9ybWFuY2UgbWV0cmljcyBmb3IgdGhpcyBzZXR0aW5nIGlzIHNob3duIGJlbG93Lg0KDQoNCmBgYHtyIHJmIDUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KZG9SRihtdHJ5ID0gOSwgbnRyZWUgPSAyMCwgc19jcml0ZXJpYSA9IDEpDQpgYGANCg0KIyMjIENhdGVnb3JpY2FsIA0KQ2xhc3NpZmljYXRpb24gbW9kZWxzIGFyZSB1c2VkIHdoZW4gZGVwZW5kZW50IHZhcmlhYmxlcyBhcmUgY2F0ZWdvcmljYWwsIGZvciBleGFtcGxlOyB3aGVuIHRoZXkgYXJlIGJpbmFyeS4gSGVuY2UsIHdlIG5lZWQgdG8gdHJhbnNmb3JtIGFjdHVhbCBvdXRwdXRzIGludG8gY2F0ZWdvcmllcywgdGhlbiB1c2UgdGhlbSB0byBmaXQgdGhlIG1vZGVscy4gV2Ugd2lsbCBmb2xsb3cgdGhlIHNhbWUgY2F0ZWdvcmllcyBpbiB0aGUgcHJldmlvdXMgc2VjdGlvbiwgbmFtZWx5IHBlcmNlbnRpbGUgYW5kIG1vbWVtdHVtLg0KDQojIyMjIFFEQSAoUXVhZHJhdGljIERpc2NyaW1pbmFudCBBbmFseXNpcykNCg0KIyMjIyMgUURBLURyaXZlbiBQb3J0b2xpbw0KUURBIG9yIHF1YWRyYXRpYyBkaXNjcmltaW5hdGUgYW5hbHlzaXMgaXMgb25lIG9mIHRoZSBmYW1vdXMgbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWUgZm9yIGNsYXNzaWZpY2F0aW9uIHByZWRpY3Rpb24gd2hpY2ggcmVxdWlyZXMgbm8gaHlwZXJwYXJhbWV0ZXIuIEFwcGx5aW5nIFFEQSB0byB0aGlzIHNjZW5hcmlvLCB0aGUgcmVzcG9uc2UgdmFyaWFibGUgb3IgZnV0dXJlIHJldHVybiAoRl9SZXR1cm4pIG11c3QgYmUgYmluYXJpc2VkIGludG8gMSBmb3IgcG9zaXRpdmUgYW5kIDAgZm9yIG5lZ2F0aXZlIHJldHVybi4gVGhlcmVmb3JlIHRoZSBwcmVkaWN0aW9uIGZyb20gUURBIG1vZGVsIGlzIGEgcHJvYmFiaWxpdHkgb2YgaGF2aW5nIGEgcG9zaXRpdmUgcmV0dXJuIGluIHRoZSBuZXh0IHBlcmlvZC4gRm9yIFFEQSBwb3J0Zm9saW8sIHRoZSB3ZWlnaHQgZm9yIGVhY2ggYXNzZXQgYXQgZWFjaCB0aW1lIHBlcmlvZCB3aWxsIGJlIGRldGVybWluZWQgYnkgdGhlIGZvbGxvd2luZyBwcm9jZWR1cmVzOiANCg0KMS4gQXQgZWFjaCBkYXRlLCB0cmFpbiBRREEgbW9kZWwgYnkgdXNpbmcgcGFzdCBkYXRhLiAoTm90ZSB0aGF0IGRpZmZlcmVudCBhc3NldHMgaGF2ZSBkaWZmZXJlbnQgbW9kZWwgZHVlIHRvIGZlYXR1cmUgc2VsZWN0aW9uIHByb2Nlc3MgZm9yIGVhY2ggYXNzZXQpDQoyLiBVc2UgdGhlIG1vZGVsIHRvIGdldCBhIHByZWRpY3Rpb24gZm9yIGZ1dHVyZSByZXR1cm4gKEZfUmV0dXJuKQ0KMy4gRGVyaXZlIGludmVzdG1lbnQgZGVjaXNpb24gZm9yIGVhY2ggYXNzZXQgYnkgYXBwbHlpbmcgbW9tZW50dW0gc2lnbmFsIGNyaXRlcmlhIHRvIHRoZSBwcmVkaWN0aW9uIA0KNC4gTm9ybWFsaXNlIHBvcnRmb2xpbyB3ZWlnaHQgdXNpbmcgZXF1YWxseSB3ZWlnaHRlZCBtZXRob2QNCg0KVGhlbiB0aGUgc3RyYXRlZ3kgd2lsbCBiZSBldmFsdWF0ZWQgb24gaXRzIHBlcmZvcm1hbmNlIG1ldHJpY3MgYmFzZWQgb24gdGhlIHBvcnRmb2xpbyB3ZWlnaHQgYW5kIHJldHVybi4NCg0KRmlyc3QsICoqd2VpZ2h0X1FEQSoqIGZ1bmN0aW9uIGlzIGRlZmluZWQgdG8gY2FsY3VsYXRlZCBwb3J0Zm9saW8gd2VpZ2h0IHVzaW5nIFFEQSB0ZWNobmlxdWUuIFRoZSBmdW5jdGlvbiByZWNlaXZlcyBwYXN0IGFuZCBjdXJyZW50IGRhdGEgYW5kIGRlY2lzaW9uIHRocmVzaG9sZCBhcyBwYXJhbWV0ZXJzLiBEZWNpc2lvbiB0aHJlc2hvbGQgaXMgYSBjaG9vc2VuIHZhbHVlIGJldHdlZW4gMCBhbmQgMSB3aGljaCBpcyB1c2VkIHRvIGNoYW5nZSBwcm9iYWJpbGl0eSBvZiBoYXZpbmcgYSBwb3NpdGl2ZSBmdXR1cmUgcmV0dXJuIGludG8gYSBidXlpbmcgZGVjaXNpb24uIChJZiB0aGUgcHJvYmFiaWxpdHkgaXMgaGlnaGVyIHRoYW4gdGhlIHRocmVzaG9sZCwgdGhlbiBidXkgYW4gYXNzZXQpIExhc3RseSwgZnVuY3Rpb24gcmV0dXJucyBlaXRoZXIgMCBub3QgdG8gaW52ZXN0IG9yIDEgdG8gaW52ZXN0Lg0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQppZighcmVxdWlyZShNQVNTKSl7aW5zdGFsbC5wYWNrYWdlcygnTUFTUycpfQ0KbGlicmFyeShNQVNTKQ0Kd2VpZ2h0X1FEQSA8LSBmdW5jdGlvbihwYXN0X2RhdGEsIGN1cnJlbnRfZGF0YSwgdGhyZXNob2xkID0gMC41KXsgIyBJZiBub3QgcGFzc2luZyB0aHJlc2hvdWxkIHRoZW4gc2V0IHRvIDAuNQ0KICB5IDwtIHBhc3RfZGF0YSRGX1JldHVybiAjIFNldCBwYXN0IGRhdGEgRl9SZXR1cm4gYXMgYSByZXNwb25zZSB2YXJpYWJsZQ0KICB5IDwtIGlmZWxzZSh5ID4gMCAsIDEsIDApICMgQmluYXJpc2UgcmVzcG9uc2UgdmFyaWFibGUgKHNldCB0byAxIGlmIHRoZSByZXR1cm4gaXMgcG9zaXRpdmUgYW5kIDAgby53LikNCiAgeCA8LSBhcy5tYXRyaXgocGFzdF9kYXRhWy1jKDEsMildKSAjUmVtb3ZlIERhdGUgYW5kIEZfUmV0dXJuIGZyb20gZXhwbGFuYXRvcnkgdmFyaWFibGVzICh0cmFpbmluZyBkYXRhKQ0KICBuZXdfeCA8LSBjdXJyZW50X2RhdGFbLWMoMSwyKV0gI1JlbW92ZSBEYXRlIGFuZCBGX1JldHVybiBmcm9tIGV4cGxhbmF0b3J5IHZhcmlhYmxlcyAodGVzdGluZyBkYXRhKQ0KICBxZGEuZml0IDwtIHFkYSh4ID0geCwgZ3JvdXBpbmcgPSB5KSAjIFRyYWluIFFEQSBtb2RlbA0KICBwcmVkIDwtIHByZWRpY3QocWRhLmZpdCwgbmV3X3gpICMgQXBwbHkgbW9kZWwgb24gdGVzdGluZyBkYXRhDQogIGlmIChwcmVkJHBvc3RlcmlvclsyXSA+IHRocmVzaG9sZCl7ICMgSWYgcHJvYmFiaWxpdHkgb2YgZ2V0dGluZyBhIHBvc2l0aXZlIHJldHVybiBpcyBncmVhdGVyIHRoYW4gdGhyZXNob2xkDQogICAgcmV0dXJuIChpZmVsc2UoeVtsZW5ndGgoeSktMV0gPiAwICYgeVtsZW5ndGgoeSktMl0gPiAwICYgeVtsZW5ndGgoeSktM10gPiAwLCAxLCAwKSkgIyBJbnZlc3QgaWYgaW4gYSBnb29kIG1vbWVudHVtIA0KICB9IGVsc2UgeyAjIEVsc2UNCiAgICByZXR1cm4gKDApICMgRG8gbm90IGludmVzdC4uLg0KICB9DQp9DQpgYGANCg0KRnVuY3Rpb24gKipkb1FEQSoqIHBlcmZvcm1zIGJhY2t0ZXN0aW5nIGV2YWx1YXRpb24gYnkgY3JlYXRpbmcgYSBwb3J0Zm9saW8gYnkgdXNpbmcgUURBIHRlY2huaXF1ZSwgZ2l2ZW4gYSBzcGVjaWZpYyBkZWNpc2lvbiB0aHJlc2hvbGQuIFRoZSBmdW5jaW9uIHJldHVybiBhIHBlcmZvcm1hbmNlIG1ldHJpYyBhcyBhIHJlc3VsdC4NCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KZG9RREEgPC0gZnVuY3Rpb24odGhyZXNob2xkID0gMC41KXsNCiAgcG9ydGZfd2VpZ2h0X1FEQSA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChkYXRlc19vb3MpLTEsIG5jb2wgPSBsZW5ndGgodGljaykpICMgSW5pdGlhdGUgd2VpZ2h0IG1hdHJpeCBmb3IgUURBIHBvcnRmb2xpbw0KICBwb3J0Zl9yZXR1cm5fUURBIDwtIGMoKSAjICMgSW5pdGlhdGUgcmV0dXJuIGxpc3QgZm9yIFFEQSBwb3J0Zm9saW8NCiAgZm9yIChpIGluIDE6bGVuZ3RoKHRpY2spKXsgIyBSZXBlYXQgZm9yIGFsbCB0aWNrZXINCiAgICB0ZW1wIDwtIHNlbGVjdEZlYXR1cmUodGlja2VyID0gdGlja1tpXSkgIyBGZWF0dXJlIHNlbGVjdGlvbiBmb3IgdGlja2VyIGkgDQogICAgdyA9IGMoKSAjIEluaXRpYXRlIGFuIGVtcHR5IGxpc3Qgb2Ygd2VpZ2h0IGZvciB0aWNrZXIgaQ0KICAgIGZvciAodCBpbiAyOihsZW5ndGgoZGF0ZXNfb29zKSkpeyAjIEV4cGFuZGluZCB3aW5kb3cgZm9yIG91dC1vZi1zYW1wbGUgZGF0ZXMNCiAgICAgIHBhc3RfZGF0YSA8LSB0ZW1wICU+JSBmaWx0ZXIoRGF0ZSA8IGRhdGVzX29vc1t0LTFdKSAjIFBhc3QgZGF0YSBpcyBhbGwgZGF0YSBmcm9tIHRoZSBkYXRlIGJlZm9yZSBkYXRlIHQNCiAgICAgIGN1cnJlbnRfZGF0YSA8LSB0ZW1wICU+JSBmaWx0ZXIoRGF0ZSA9PSBkYXRlc19vb3NbdC0xXSkgIyBDdXJyZW50IGRhdGEgaXMgZGF0YSBhdCBkYXRlIHQNCiAgICAgIHJlcyA8LSB3ZWlnaHRfUURBKHBhc3RfZGF0YSA9IHBhc3RfZGF0YSwgY3VycmVudF9kYXRhID0gY3VycmVudF9kYXRhLCB0aHJlc2hvbGQgPSB0aHJlc2hvbGQpICMgR2V0IHdlaWdodCB1c2luZyBRREEgbWV0aG9kIHdpdGggc3BlY2lmaWMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhyZXNob2xkDQogICAgICB3ID0gcmJpbmQodywgcmVzKSAjIEFwcGVuZCBkYXRlIGZvciBzdG9jayBpIGF0IHBlcmlvZCB0DQogICAgfQ0KICAgIHBvcnRmX3dlaWdodF9RREFbLGldIDwtIHcgIyBBcHBlbmQgd2VpZ2h0IGZvciBzdG9jayBpIGZvciBhbGwgcGVyaW9kIHQNCiAgfQ0KICBwb3J0Zl93ZWlnaHRfUURBIDwtIGdldEVXKHBvcnRmX3dlaWdodF9RREEpICMgTm9ybWFsaXNlIHdlaWdodCAoRXF1YWxseSB3ZWlnaHRlZCkNCiAgZm9yKGogaW4gMToobGVuZ3RoKGRhdGVzX29vcyktMSkpeyANCiAgICBwb3J0Zl9yZXR1cm5fUURBW2pdIDwtIHN1bShwb3J0Zl93ZWlnaHRfUURBW2osXSAqIHJldHVybnNfb29zW2osXSkgIyBDYWxjdWxhdGUgcG9ydGZvbGlvIHJldHVybg0KICB9DQogIHJldHVybiAocGVyZl9tZXQocG9ydGZfcmV0dXJuX1FEQSwgcG9ydGZfd2VpZ2h0X1FEQSwgYXNzZXRfcmV0dXJuKSkgIyBHZXQgcGVyZm9ybWFuY2UgbWV0cmljcw0KfQ0KYGBgDQoNCk5leHQsIGNyZWF0ZSBhIHBvcnRmb2xpbyBiYXNlZCBvbiBRREEgc3RyYXRlZ3kgYW5kIGV2YWx1YXRlIHRoZSBwZXJmb3JtYW5jZQ0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpkb1FEQSgpDQpgYGANCiMjIyMjIEltcGFjdCBvZiBEZWNpc2lvbiBUaHJlc2hvbGQNCg0KSW4gcHJhY3RpY2UsIGRlY2lzaW9uIHRocmVzaG9sZCBuZWVkIHRvIGJlIHNldCBhbG9uZyB0cmFkaW5nIGRheXMuIEZvciB0aG9zZSBpbnZlc3RvcnMgd2hvIHN0cm9uZ2x5IGJlbGlldmUgdGhlIFFEQSBtb2RlbCwgdGhleSBjYW4gaGF2ZSBhIGxvdyB0aHJlc2hvbGQgd2hpY2ggaW50ZXJwcmV0IGEgbG93IHByb2JhYmlsaXR5IG9mIGhhdmluZyBhIHBvc2l0aXZlIHJldHVybiBhcyBhIGJ1eSBzaWduYWwuIENvbnZlcnNlbHksIGludmVzdG9ycyB0aGF0IGRvIG5vdCBmdWxseSB0cnVzdCB0aGUgbW9kZWwgY2FuIHNldCBhIGhpZ2ggdGhyZXNob2xkIHRvIG9ubHkgYnV5IGFuIGFzc2V0IHdoZW4gdGhlIHByb2JhYmlsaXR5IGlzIHZlcnkgaGlnaC4gSWYgdGhlIGRlY2lzaW9uIHRocmVzaG9sZCBpcyBub3Qgc2V0LCBRREEgZGVmYXVsdCB2YWx1ZSBpcyAwLjUgb3Iga25vd24gYXMgcHJlZGljdGluZyBhIGNsYXNzIHdpdGggaGlnaGVyIHByb2JhYmlsaXR5DQoNCkhvd2V2ZXIsIGRlY2lzaW9uIHRocmVzaG9sZCBjYW4gYmUgc2V0IHRvIG1heGltaXplIHRoZSBwb3J0Zm9saW8gcGVyZm9ybWFuY2UuIEJlbG93LCBkZWNpc2lvbiB0aHJlc2hvbGQgaXMgdmFyaWVkIGZyb20gMC4xIHRvIDAuOSBhbmQgdGhlIHBlcmZvcm1hbmNlIG1ldHJpY3MgYXJlIHBsb3R0ZWQgdG8gaWxsdXN0cmF0ZSB0aGUgaW1wYWN0IG9mIGRlY2lzaW9uIHRocmVzaG9sZC4gSG93ZXZlciwgaW4gcHJhY3RpY2UsIHRocmVzaG9sZCBiZWxvdyAwLjUgc2hvdWxkIG5vdCBiZSBjaG9vc2VuIHNpbmNlIHRoZXJlIGlzIGhpZ2hlciBwcm9iYWJpbHR5IG9mIGhhdmluZyBhIG5lZ2F0aXZlIHJldHVybi4NCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KaWYoIXJlcXVpcmUoZ2dwbG90Mikpe2luc3RhbGwucGFja2FnZXMoJ2dncGxvdDInKX0NCmlmKCFyZXF1aXJlKHJlc2hhcGUyKSl7aW5zdGFsbC5wYWNrYWdlcygncmVzaGFwZTInKX0NCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocmVzaGFwZTIpDQoNCnBlcmZfbWV0X1FEQSA8LSBkYXRhLmZyYW1lKG1hdHJpeChuY29sID0gNiwgbnJvdyA9IDApKSAjIEluaXRpYXRlIGRhdGFmcmFtZSBmb3IgcGVyZm9ybWFuY2UgbWV0cmljIG9mIFFEQSB3aXRoIGRpZmZlcmVudCBwYXJhbWV0ZXIgdmFsdWVzDQpjb2xuYW1lcyhwZXJmX21ldF9RREEpIDwtIGMoJ3RocmVzaG9sZCcsICdhdmdfcmV0JywgJ3ZvbCcsICdTaGFycGVfcmF0aW8nLCAnVmFSXzUnLCAndHVybicpICMgU2V0IGNvbHVtbiBuYW1lcw0KDQojIExvYWQgcmVzdWx0IHdoaWNoIGhhcyBhbHJlYWR5IGJlZW4gY29tcHV0ZWQgdG8gcmVkdWNlIHJ1bnRpbWUNCnBlcmZfbWV0X1FEQSA8LSByZWFkLmNzdignUURBXzAxLmNzdicsIGhlYWRlcj1UUlVFLCBzZXA9IiwiKQ0KDQojIElmIGhhdmVuJ3QgY29tcHV0ZWQgYmVmb3JlLCB1bmNvbW1lbnQgdGhlIGZvbGxvd2luZyBibG9jaw0KIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSMNCiMgZm9yICh0aHJlc2hvbGQgaW4gc2VxKDAuMSwgMC45LCAwLjEpKXsgIyBMb29wIHRocm91Z2ggc2V0IG9mIHRocmVzaG9sZCB2YWx1ZSAoSHlwZXJwYXJhbWV0ZXIgdHVuaW5nKQ0KIyAgIHBlcmZfbWV0X1FEQSA8LSByYmluZChwZXJmX21ldF9RREEsICBjYmluZCh0aHJlc2hvbGQsIHQodW5saXN0KGRvUURBKHRocmVzaG9sZCA9IHRocmVzaG9sZCkpKSkpICMgVHJhaW4vdGVzdCBtb2RlbCBhbmQgc3RvcmUgcGVyZi4gbWV0Lg0KIyB9DQojIHdyaXRlLmNzdihwZXJmX21ldF9RREEsIGZpbGUgPSAiUURBXzAxLmNzdiIsIHJvdy5uYW1lcz1GQUxTRSkgIyBLZWVwIGFzIGNzdiBmaWxlIG9uIGxvY2FsDQojLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIw0KDQpwbG90X2RhdGEgPC0gbWVsdChwZXJmX21ldF9RREEgICwgIGlkLnZhcnMgPSAndGhyZXNob2xkJywgdmFyaWFibGUubmFtZSA9ICdzZXJpZXMnKSAjIFBsb3QgcGVyZm9ybWFuY2UgZXZhbHVhdGlvbiBmb3IgZGlmZmVyZW50IHZhbHVlIG9mIGh5LnBhci4NCmdncGxvdChwbG90X2RhdGEsIGFlcyh0aHJlc2hvbGQsIHZhbHVlKSkgKyANCiAgZ2VvbV9saW5lKGNvbG91cnMgPSAiYmx1ZSIpICsgDQogIGZhY2V0X2dyaWQoc2VyaWVzIH4gLiwgc2NhbGUgPSAiZnJlZV95IikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAuMSwgMC45LCAwLjEpKQ0KYGBgDQoNCk9idmlvdXNseSwgdGhlIG9wdGltYWwgZGVjaXNpb24gdGhyZXNob2xkIHdoaWNoIHByb3ZpZGUgdGhlIGJlc3QgcGVyZm9ybWFuY2UgZm9yIG1vc3QgbWV0cmljcyBpcyBhYm91dCAwLjYuIEl0IHlpZWxkcyB0aGUgaGlnZXIgYXZlcmFnZSByZXR1cm4sIHNoYXJwZSByYXRpbywgYW5kIHZhbHVlLWF0LXJpc2sgd2l0aCBsb3dlciB2b2xhdGlsaXR5LiBPcHRpbWFsIGRlY2lzaW9uIHRocmVzaG9sZCBjb3VsZCBiZSBzZWFyY2ggYWdhaW4gYXQgdmFsdWVzIGFyb3VuZCAwLjYuDQoNCiMjIyMjIFJlc3VsdDogUGVmb3JtYW5jZSBNZXRyaWNzIGZvciBRREENCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0NCnBlcmZfbWV0X1FEQSA8LSBkYXRhLmZyYW1lKG1hdHJpeChuY29sID0gNiwgbnJvdyA9IDApKSAjIEluaXRpYXRlIGRhdGFmcmFtZSBmb3IgcGVyZm9ybWFuY2UgbWV0cmljIG9mIFFEQSB3aXRoIGRpZmZlcmVudCBwYXJhbWV0ZXIgdmFsdWVzDQpjb2xuYW1lcyhwZXJmX21ldF9RREEpIDwtIGMoJ3RocmVzaG9sZCcsICdhdmdfcmV0JywgJ3ZvbCcsICdTaGFycGVfcmF0aW8nLCAnVmFSXzUnLCAndHVybicpICMgU2V0IGNvbHVtbiBuYW1lcw0KDQojIExvYWQgcmVzdWx0IHdoaWNoIGhhcyBhbHJlYWR5IGJlZW4gY29tcHV0ZWQgdG8gcmVkdWNlIHJ1bnRpbWUgDQpwZXJmX21ldF9RREEgPC0gcmVhZC5jc3YoJ1FEQV8wMi5jc3YnLCBoZWFkZXI9VFJVRSwgc2VwPSIsIikNCg0KIyBJZiBoYXZlbid0IGNvbXB1dGVkIGJlZm9yZSwgdW5jb21tZW50IHRoZSBmb2xsb3dpbmcgYmxvY2sNCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0jDQojIGZvciAodGhyZXNob2xkIGluIHNlcSgwLjYsIDAuNywgMC4wMSkpeyAjIExvb3AgdGhyb3VnaCBzZXQgb2YgdGhyZXNob2xkIHZhbHVlIChIeXBlcnBhcmFtZXRlciB0dW5pbmcpDQojICAgcGVyZl9tZXRfUURBIDwtIHJiaW5kKHBlcmZfbWV0X1FEQSwgIGNiaW5kKHRocmVzaG9sZCwgdCh1bmxpc3QoZG9RREEodGhyZXNob2xkID0gdGhyZXNob2xkKSkpKSkgIyBUcmFpbi90ZXN0IG1vZGVsIGFuZCBzdG9yZSBwZXJmLiBtZXQuDQojfQ0KIyB3cml0ZS5jc3YocGVyZl9tZXRfUURBLCBmaWxlID0gIlFEQV8wMi5jc3YiLCByb3cubmFtZXM9RkFMU0UpICMgS2VlcCBhcyBjc3YgZmlsZSBvbiBsb2NhbA0KIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSMNCg0KDQpwbG90X2RhdGEgPC0gbWVsdChwZXJmX21ldF9RREEgICwgIGlkLnZhcnMgPSAndGhyZXNob2xkJywgdmFyaWFibGUubmFtZSA9ICdzZXJpZXMnKSAjIFBsb3QgcGVyZm9ybWFuY2UgZXZhbHVhdGlvbiBmb3IgZGlmZmVyZW50IHZhbHVlIG9mIGh5LnBhci4NCmdncGxvdChwbG90X2RhdGEsIGFlcyh0aHJlc2hvbGQsIHZhbHVlKSkgKyANCiAgZ2VvbV9saW5lKCkgKyANCiAgZmFjZXRfZ3JpZChzZXJpZXMgfi4sIHNjYWxlID0gImZyZWVfeSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLjYsIDAuNywgMC4wMSkpDQpgYGANCg0KIEluIGNvbmNsdXNpb24sIG9wdGltYWwgZGVjaXNpb24gdGhyZXNob2xkIGlzIGNob29zZW4gYXQgMC42NSB3aGljaCB5aWVsZHMgdGhlIGJlc3Qgb2YgbW9zdCBwZXJmb3JtYW5jZSBtZXRyaWNzLg0KIEJlbG93IGlzIHRoZSBmdWxsIHBlcmZvcm1hbmNlIG1ldHJpY3MgYXQgb3B0aW1hbCBkZWNpc2lvbiB0aHJlc2hvbGQuDQoNCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KcGVyZl9tZXRfUURBX29wdCA8LSBwZXJmX21ldF9RREEgJT4lICMgR2V0IHBlcmZvcm1hbmNlIG1ldHJpYyBmb3IgdGhyZXNob2xkID0gMC42DQogIGZpbHRlcih0aHJlc2hvbGQgPT0gMC42NSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLSh0aHJlc2hvbGQpKQ0KcGVyZl9tZXRfUURBX29wdCA8LSBjYmluZCgibWV0aG9kIiA9ICJRREEiLCBwZXJmX21ldF9RREFfb3B0KSAjIEFkZCBtZXRob2QgbmFtZQ0KcGVyZl9tZXRfUURBX29wdA0KYGBgDQoNCg0KKipJbXBvcnRhbnQqKjogT25jZSBhZ2FpbiwgZGVjaXNpb24gdGhyZXNob2xkIG5lZWQgdG8gYmUgc2V0IGFsb25nIHRyYWRpbmcgZGF5cy4gQSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gc2V0IGl0IGF0IDAuNSBvciBhZGp1c3QgaXQgdG8gc3VpdCB0aGUgaW52ZXN0b3IncyBjb25maWRlbnQgb24gdGhlIG1vZGVsLg0KDQojIyMjIEstTmVhcmVzdCBOZWlnaGJvdXINCg0KV2l0aCBLTk4gbWV0aG9kLCB3ZSBmYWN0b3Jpc2VkIHRoZSBmdXR1cmUgcmV0dXJucyB1c2luZyAzIGNyaXRlcmlhOiBwZXJjZW50aWxlIGFuZCBtb21lbnR1bSB3aXRoIDEgYW5kIDMgcGVyaW9kcywgdGhlbiB0aGUgbW9kZWwgaXMgdHJhaW5lZCB3aXRoIHRoZSBmZWF0dXJlcyBzZWxlY3RlZCBieSBMQVNTTyBhbGdvcml0aG0uIExhc3RseSwgZXF1YWwgd2VpZ2h0IGlzIGFwcGxpZWQgdG8gdGhlIHN0b2NrcyB3aXRoICdidXknIHNpZ25hbHMuDQoNCk5vbmV0aGVsZXNzLCB0aGVyZSBpcyBhIGxvc3Qgb2YgaW5mb3JtYXRpb24gd2hlbiBzdHJhdGVnaWVzIHBvcnRmb2xpbyB0aGlzIHdheS4gDQoNCjEuIEZvciBwZXJjZW50aWxlIGNhdGVnb3JpZXMsIHdlIG5lZWQgdG8gc2FjcmlmaWNlIHNvbWUgZmlyc3QgY2h1bmsgb2YgcGFzdCBkYXRhIHRvIGNyZWF0ZSB0aGUgcGVyY2VudGlsZS4gSGVyZSwgd2Ugc3RhcnQgY29tcHV0aW5nIHRoZSBzaWduYWxzIGFmdGVyIDM2IGZpcnN0IHBlcmlvZHMuIChkYXRhc2V0ID0gZGF5IDM3LTIyOSkNCg0KYGBge3IgcGVyY2VudGlsZSBvdXRwdXRzLCBlY2hvID0gVH0NCm91dHB1dHNfUCA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChkYXRlc19hbGwpLTM2LCBuY29sID0gbGVuZ3RoKHRpY2spKQ0KY29sbmFtZXMob3V0cHV0c19QKSA8LSB0aWNrDQpmb3IgKGkgaW4gMTpsZW5ndGgodGljaykpIHsNCiAgIGNsYXNzX1AgPC0gUF9zaWcoYXMubWF0cml4KHJldHVybnNbdGlja1tpXV1bMzc6bGVuZ3RoKGRhdGVzX2FsbCksXSksDQogICAgICAgICAgICAgICAgICAgIGFzLm1hdHJpeChyZXR1cm5zW3RpY2tbaV1dKSwgMzcpDQogICBvdXRwdXRzX1BbLGldID0gY2xhc3NfUA0KfQ0KYGBgDQoyLiBXZSB3aWxsIGxvc2UgdGhlIGxhc3Qgb2JzZXJ2YXRpb24gZm9yIDEgbW9tZW50dW0gY3JpdGFyaWEgYXMgdGhlcmUgaXMgbm8gZnV0dXJlIHJldHVybnMgZm9yIHRoZSBsYXN0IHBlcmlvZC4gKGRhdGFzZXQgPSBkYXkgMi0yMjkpDQpgYGB7ciBNb00xIG91dHB1dHMsIGVjaG8gPSBUfQ0Kb3V0cHV0c19Nb00uMSA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChkYXRlc19hbGwpLTEsIG5jb2wgPSBsZW5ndGgodGljaykpDQpjb2xuYW1lcyhvdXRwdXRzX01vTS4xKSA8LSB0aWNrDQpmb3IgKGkgaW4gMTpsZW5ndGgodGljaykpIHsNCiAgYV90YWJsZSA8LSBjYmluZChhcy5tYXRyaXgocmV0dXJuc1t0aWNrW2ldXVsyOihsZW5ndGgoZGF0ZXNfYWxsKSksXSksDQogICAgICAgICAgICAgICAgICBhcy5tYXRyaXgocmV0dXJuc1t0aWNrW2ldXVsxOihsZW5ndGgoZGF0ZXNfYWxsKS0xKSxdKSkNCiAgY2xhc3NfTW9NLjEgPC0gTW9NLjFfc2lnKGFfdGFibGUpDQogIG91dHB1dHNfTW9NLjFbLGldID0gY2xhc3NfTW9NLjFbLDNdDQp9DQpgYGANCjMuIEZvciAzIG1vbWVudHVtIHBlcmlvZHMsIGl0IGNvc3RzIDcgb2JzZXJ2YXRpb25zLiBXZSBsb3NlIHRoZSBmaXJzdCA0IHRvIGNvbnNpZGVyIHByZXZpb3VzIG1vbWVtdHVtIGZvciB0aGUgZmlyc3QgY2FzZSBhbmQgdGhlIGxhc3QgMyBmb3Igbm90IGhhdmluZyBmdXR1cmUgbW9tZW10dW0uIChkYXRhc2V0ID0gZGF5IDUtMjI2KQ0KYGBge3IgTW9NMyBvdXRwdXRzLCBlY2hvID0gVH0NCm91dHB1dHNfTW9NLjMgPC0gbWF0cml4KDAsIG5yb3cgPSAobGVuZ3RoKGRhdGVzX2FsbCktNyksIG5jb2wgPSBsZW5ndGgodGljaykgKQ0KY29sbmFtZXMob3V0cHV0c19Nb00uMykgPC0gdGljaw0KZm9yIChpIGluIDE6bGVuZ3RoKHRpY2spKSB7DQogIHplcm9zIDwtIG1hdHJpeChjKDAsMCwwKSwgbnJvdyA9IDMpDQogIEZfdmVjdG9yIDwtIHJiaW5kKHplcm9zLGFzLm1hdHJpeChyZXR1cm5zW3RpY2tbaV1dWzU6KGxlbmd0aChkYXRlc19hbGwpLTMpLF0pKQ0KICBQX3ZlY3RvciA8LSBhcy5tYXRyaXgocmV0dXJuc1t0aWNrW2ldXVsxOihsZW5ndGgoZGF0ZXNfYWxsKS00KSxdKQ0KICAjUF92ZWN0b3IgPC0gUF92ZWN0b3JbMToobnJvdyhQX3ZlY3RvciktMSksXQ0KICBhX3RhYmxlIDwtIGNiaW5kKEZfdmVjdG9yLCBQX3ZlY3RvcikNCiAgY2xhc3NfTW9NLjMgPC0gTW9NLjNfc2lnKGFfdGFibGUpDQogIG91dHB1dHNfTW9NLjNbLGldID0gY2xhc3NfTW9NLjNbLDNdDQp9DQpgYGANCg0KVGhlcmVmb3JlLCBmb3IgY29uc2lzdGVuY3ksIHdlIHJlZHVjZSBhbGwgZGF0YXNldCBmb3IgY2F0ZWdvcmljYWwgb3V0cHV0cyB0byBhbGwgc3RhcnQgb24gZGF5IDM3IGFuZCBlbmQgb24gZGF5IDIyNi4NCmBgYHtyfQ0Kb3V0cHV0c19QIDwtIG91dHB1dHNfUFsxOjE5MCxdDQpvdXRwdXRzX01vTS4xIDwtIG91dHB1dHNfTW9NLjFbMzY6MjI1LF0NCm91dHB1dHNfTW9NLjMgPC0gb3V0cHV0c19Nb00uM1szMzpucm93KG91dHB1dHNfTW9NLjMpLF0NCmBgYA0KDQpgYGB7ciBTZXR1cCBvb3MgaG9yaXpvbiBhbmQgcmV0dXJucyBmb3IgY2F0ZWdvcmllcyBvdXRwdXRzLCBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0Kb29zX3N0YXJ0X2NhdCA8LSAoKGxlbmd0aChkYXRlc19hbGwpLWxlbmd0aChkYXRlc19vb3MpKSsxKSAtIDM3IA0Kb29zX2xlbmd0aF9jYXQgPC0gMTkwIC0gb29zX3N0YXJ0X2NhdCArIDENCm9vc19yZXRfY2F0IDwtIGFzX3RpYmJsZShyZXR1cm5zKSAlPiUgDQogIHNsaWNlKG9vc19zdGFydF9jYXQ6MTkwKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtRGF0ZSkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgYXMubWF0cml4KCkgICAgDQoNCiNkdWUgdG8gdHJpbW1pbmcgb2YgZGF0YWZyYW1lIGZvciBjYXRlZ29yaWNhbCBvdXRwdXRzLCB0aGUgZW5kIGRhdGUgDQp0aW1lU2xpY2VzX2NhdF90cmFpbiA8LSBjcmVhdGVUaW1lU2xpY2VzKDE6b29zX3N0YXJ0X2NhdC0xLCANCiAgICAgICAgICAgICAgICAgICBpbml0aWFsV2luZG93ID0gMzYsIGhvcml6b24gPSAxMiwgZml4ZWRXaW5kb3cgPSBUUlVFKQ0KYGBgDQoNCiMjIyMjIFRyYWluIEtOTiB3aXRoIHBlcmNlbnRpbGUNCmBgYHtyIGtubl9wX2NhdCwgZWNobyA9IFR9DQojIHNpZ25hbHNfa25uX1BfY2F0IDwtIG1hdHJpeCgwLCBucm93ID0gb29zX2xlbmd0aF9jYXQsIG5jb2wgPSBsZW5ndGgodGljaykpICNjcmVhdGUgdGFibGUgdG8gc3RvcmUgc2lnbmFscw0KIyBjb2xuYW1lcyhzaWduYWxzX2tubl9QX2NhdCkgPC0gdGljaw0KIyBmb3IgKGkgaW4gMTpsZW5ndGgodGljaykpew0KIyAgIGEgPC0gc2VsZWN0RmVhdHVyZTIodGlja1tpXSxvdXRwdXRzX1ApICU+JQ0KIyAgICAgbXV0YXRlKG91dHB1dCA9IGFzLmZhY3RvcihvdXRwdXQpKSAgICAgICAgICANCiMgICBhX3ByZWQgPC0gdHJhaW4ob3V0cHV0IH4gLiwgZGF0YSA9IGFbdW5saXN0KHRpbWVTbGljZXNfdHJhaW4pLF0sIA0KIyAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAia25uIiwgDQojICAgICAgICAgICAgICAgICAgIHByZVByb2MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSkNCiMgICBhX2FjdHVhbC5mdXR1cmVfdGVzdCA9IGFbb29zX3N0YXJ0X2NhdDpucm93KGEpLF0gJT4lDQojICAgICBtdXRhdGUob3V0cHV0ID0gYXMuZmFjdG9yKG91dHB1dCkpICAgICAgICAgIA0KIyAgIGFfcHJlZC5mdXR1cmVfdGVzdCA8LSBwcmVkaWN0KGFfcHJlZCwgYV9hY3R1YWwuZnV0dXJlX3Rlc3QpICAgICAgICMgZ2V0IHByZWRpY3Rpb24NCiMgICBzaWduYWxzX2tubl9QX2NhdFssaV0gIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGFfcHJlZC5mdXR1cmVfdGVzdCkpICAgDQojICAgIyBjb21iaW5lIHNpZ25hbHMgZm9yIGFsbCBzdG9ja3MNCiMgfQ0KIyB3ZWlnaHRfa25uX1BfY2F0IDwtIGNhbF9wb3J0X3dlaWdodChzaWduYWxzX2tubl9QX2NhdCkNCiMgcmV0X2tubl9QX2NhdDwtcm93U3Vtcyh3ZWlnaHRfa25uX1BfY2F0Km9vc19yZXRfY2F0KQ0KIyANCiMgd3JpdGUuY3N2KHdlaWdodF9rbm5fUF9jYXQsIGZpbGUgPSAid2VpZ2h0X2tubl9QX2NhdC5jc3YiKQ0KIyB3cml0ZS5jc3YocmV0X2tubl9QX2NhdCwgZmlsZSA9ICJyZXRfa25uX1BfY2F0LmNzdiIpDQoNCndlaWdodF9rbm5fUF9jYXQgPSByZWFkLmNzdihmaWxlID0gIndlaWdodF9rbm5fUF9jYXQuY3N2IikNCndlaWdodF9rbm5fUF9jYXQgPSB3ZWlnaHRfa25uX1BfY2F0Wy0xXQ0KDQpyZXRfa25uX1BfY2F0ID0gcmVhZC5jc3YoZmlsZSA9ICJyZXRfa25uX1BfY2F0LmNzdiIpDQpyZXRfa25uX1BfY2F0ID0gcmV0X2tubl9QX2NhdFstMV0NCmBgYA0KDQojIyMjIyBUcmFpbiBLTk4gd2l0aCAxLXBlcmlvZCBtb21lbnR1bQ0KYGBge3Iga25uX21vbTFfY2F0LGVjaG89IFR9DQojIHNpZ25hbHNfa25uX01vTS4xX2NhdCA8LSBtYXRyaXgoMCwgbnJvdyA9IG9vc19sZW5ndGhfY2F0LCBuY29sID0gbGVuZ3RoKHRpY2spKSAjY3JlYXRlIHRhYmxlIHRvIHN0b3JlIHNpZ25hbHMNCiMgY29sbmFtZXMoc2lnbmFsc19rbm5fTW9NLjFfY2F0KSA8LSB0aWNrDQojIGZvciAoaSBpbiAxOmxlbmd0aCh0aWNrKSl7DQojICAgYSA8LSBzZWxlY3RGZWF0dXJlMih0aWNrW2ldLCBvdXRwdXRzX01vTS4xKSAlPiUNCiMgICAgIG11dGF0ZShvdXRwdXQgPSBhcy5mYWN0b3Iob3V0cHV0KSkgICAgICAgICAgDQojICAgYV9wcmVkIDwtIHRyYWluKG91dHB1dCB+IC4sIGRhdGEgPSBhW3VubGlzdCh0aW1lU2xpY2VzX3RyYWluKSxdLCANCiMgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImtubiIsIA0KIyAgICAgICAgICAgICAgICAgICBwcmVQcm9jID0gYygiY2VudGVyIiwgInNjYWxlIikpDQojICAgYV9hY3R1YWwuZnV0dXJlX3Rlc3QgPSBhW29vc19zdGFydF9jYXQ6bnJvdyhhKSxdICU+JQ0KIyAgICAgbXV0YXRlKG91dHB1dCA9IGFzLmZhY3RvcihvdXRwdXQpKSAgICAgICAgICANCiMgICBhX3ByZWQuZnV0dXJlX3Rlc3QgPC0gcHJlZGljdChhX3ByZWQsIGFfYWN0dWFsLmZ1dHVyZV90ZXN0KSAgICAgICAjIGdldCBwcmVkaWN0aW9uDQojICAgDQojICAgc2lnbmFsc19rbm5fTW9NLjFfY2F0WyxpXSAgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoYV9wcmVkLmZ1dHVyZV90ZXN0KSkgICANCiMgICAgICMgY29tYmluZSBzaWduYWxzIGZvciBhbGwgc3RvY2tzDQojICAgcHJpbnQoaSkNCiMgfQ0KIyB3ZWlnaHRfa25uX01vTS4xX2NhdCA8LSBjYWxfcG9ydF93ZWlnaHQoc2lnbmFsc19rbm5fTW9NLjFfY2F0KQ0KIyByZXRfa25uX01vTS4xX2NhdDwtcm93U3Vtcyh3ZWlnaHRfa25uX01vTS4xX2NhdCpvb3NfcmV0X2NhdCkNCiMgDQojIHdyaXRlLmNzdih3ZWlnaHRfa25uX01vTS4xX2NhdCwgZmlsZSA9ICJ3ZWlnaHRfa25uX01vTTFfY2F0LmNzdiIpDQojIHdyaXRlLmNzdihyZXRfa25uX01vTS4xX2NhdCwgZmlsZSA9ICJyZXRfa25uX01vTTFfY2F0LmNzdiIpDQoNCndlaWdodF9rbm5fTW9NLjFfY2F0ID0gcmVhZC5jc3YoZmlsZSA9ICJ3ZWlnaHRfa25uX01vTTFfY2F0LmNzdiIpDQp3ZWlnaHRfa25uX01vTS4xX2NhdCA9IHdlaWdodF9rbm5fTW9NLjFfY2F0Wy0xXQ0KDQpyZXRfa25uX01vTS4xX2NhdCA9IHJlYWQuY3N2KGZpbGUgPSAicmV0X2tubl9Nb00xX2NhdC5jc3YiKQ0KcmV0X2tubl9Nb00uMV9jYXQgPSByZXRfa25uX01vTS4xX2NhdFstMV0NCmBgYA0KDQoNCiMjIyMjIFJlc3VsdDogUGVmb3JtYW5jZSBNZXRyaWNzIGZvciBLTk4gKENhdGVnb3JpY2FsKQ0KYGBge3J9DQpNX2tubl9QX2NhdCA8LSBwZXJmX21ldDIoYXMubWF0cml4KHJldF9rbm5fUF9jYXQpLGFzLm1hdHJpeCh3ZWlnaHRfa25uX1BfY2F0KSxkYXRlc19vb3MsDQogICAgICAgICAgICAgICAgICAgIHBvcnRfbmFtZSA9ICJLTk5fUGVyY2VudGlsZSIscmV0dXJucykNCk1fa25uX01vTS4xX2NhdCA8LSBwZXJmX21ldDIoYXMubWF0cml4KHJldF9rbm5fTW9NLjFfY2F0KSxhcy5tYXRyaXgod2VpZ2h0X2tubl9Nb00uMV9jYXQpLGRhdGVzX29vcywNCiAgICAgICAgICAgICAgICAgICAgICAgIHBvcnRfbmFtZSA9ICJLTk5fTW9tZW50dW0uMSIsIHJldHVybnMpDQoNCmNhdF9rbm48LSByYmluZChNX2tubl9QX2NhdCxNX2tubl9Nb00uMV9jYXQpDQpjYXRfa25uDQpgYGANCg0KIyMjIyBMb2dpdCBNb2RlbA0KVGhpcyBtb2RlbCB0cmFuc2Zvcm0gdGhlIG91dHB1dHMgaW50byBwcm9iYWJpbGl0eS4gVGhlcmVmb3JlLCB0aGUgb3V0cHV0cyBhcmUgY2FwcGVkIGJldHdlZW4gMCBhbmQgMS4gIEhvd2V2ZXIsIGl0IHVzZXMgbm9uLW5vcm1hbCBkaXN0cmlidXRpb24gdG8gdHJhbnNmb3JtIHRoZSBvdXRwdXQuIEl0IGNvbnN1bWVzIGxlc3MgcG93ZXIgdG8gcGVyZm9ybSB3aXRob3V0IHRoZSBub3JtYWwgZGlzdHJpYnV0aW9uIGNvbnN0cmFpbnQsIGJ1dCBpdCBjb3N0cyBmYXR0ZXIgdGFpbHMuIFRoZXJlIGlzIG5vIGh5cGVycGFyYW1ldGVyIGluIHRoaXMgbW9kZWwuDQoNCiMjIyMjIFRyYWluIGxvZ2l0IG1vZGVsIHdpdGggcGVyY2VudGlsZQ0KYGBge3IgTG9naXRfcF9jYXQsIGVjaG8gPSBULCBtZXNzYWdlPSBGQUxTRSxyZXN1bHRzID0gJ2hpZGUnfQ0KIyBzaWduYWxzX2xvZ2l0X1BfY2F0IDwtIG1hdHJpeCgwLCBucm93ID0gb29zX2xlbmd0aF9jYXQsIG5jb2wgPSBsZW5ndGgodGljaykpICNjcmVhdGUgdGFibGUgdG8gc3RvcmUgc2lnbmFscw0KIyBjb2xuYW1lcyhzaWduYWxzX2xvZ2l0X1BfY2F0KSA8LSB0aWNrDQojIGZvciAoaSBpbiAxOmxlbmd0aCh0aWNrKSl7DQojICAgYSA8LSBzZWxlY3RGZWF0dXJlMih0aWNrW2ldLG91dHB1dHNfUCkgJT4lDQojICAgICBtdXRhdGUob3V0cHV0ID0gYXMuZmFjdG9yKG91dHB1dCkpICAgICAgICAgDQojICAgYV9wcmVkIDwtIHRyYWluKG91dHB1dCB+IC4sIGRhdGEgPSBhW3VubGlzdCh0aW1lU2xpY2VzX3RyYWluKSxdLCANCiMgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImdsbSIsIA0KIyAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IiksIA0KIyAgICAgICAgICAgICAgICAgICBwcmVQcm9jID0gYygiY2VudGVyIiwgInNjYWxlIikpDQojICAgYV9hY3R1YWwuZnV0dXJlX3Rlc3QgPSBhW29vc19zdGFydF9jYXQ6bnJvdyhhKSxdICU+JQ0KIyAgICAgbXV0YXRlKG91dHB1dCA9IGFzLmZhY3RvcihvdXRwdXQpKSAgICAgICAgIA0KIyAgIGFfcHJlZC5mdXR1cmVfdGVzdCA8LSBwcmVkaWN0KGFfcHJlZCwgYV9hY3R1YWwuZnV0dXJlX3Rlc3QpICAgICAgICMgZ2V0IHByZWRpY3Rpb24NCiMgICANCiMgICBzaWduYWxzX2xvZ2l0X1BfY2F0WyxpXSAgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoYV9wcmVkLmZ1dHVyZV90ZXN0KSkgICAgICAgICAgICAgIA0KIyAgICMgY29tYmluZSBzaWduYWxzIGZvciBhbGwgc3RvY2tzDQojICAgIA0KIyAgDQojIH0NCiMgd2VpZ2h0X2xvZ2l0X1BfY2F0IDwtIGNhbF9wb3J0X3dlaWdodChzaWduYWxzX2xvZ2l0X1BfY2F0KQ0KIyByZXRfbG9naXRfUF9jYXQ8LXJvd1N1bXMod2VpZ2h0X2xvZ2l0X1BfY2F0Km9vc19yZXRfY2F0KQ0KIyANCiMgd3JpdGUuY3N2KHdlaWdodF9sb2dpdF9QX2NhdCwgZmlsZSA9ICJ3ZWlnaHRfbG9naXRfUF9jYXQuY3N2IikNCiMgd3JpdGUuY3N2KHJldF9sb2dpdF9QX2NhdCwgZmlsZSA9ICJyZXRfbG9naXRfUF9jYXQuY3N2IikNCg0Kd2VpZ2h0X2xvZ2l0X1BfY2F0ID0gcmVhZC5jc3YoZmlsZSA9ICJ3ZWlnaHRfbG9naXRfUF9jYXQuY3N2IikNCndlaWdodF9sb2dpdF9QX2NhdCA9IHdlaWdodF9sb2dpdF9QX2NhdFstMV0NCg0KcmV0X2xvZ2l0X1BfY2F0ID0gcmVhZC5jc3YoZmlsZSA9ICJyZXRfbG9naXRfUF9jYXQuY3N2IikNCnJldF9sb2dpdF9QX2NhdCA9IHJldF9sb2dpdF9QX2NhdFstMV0NCmBgYA0KDQojIyMjIyBUcmFpbiBsb2dpdCBtb2RlbCB3aXRoIDEtcGVyaW9kIG1vbWVudHVtDQpgYGB7ciBMb2dpdF9tb20xX2NhdCxlY2hvID0gVCwgbWVzc2FnZT0gRkFMU0UscmVzdWx0cyA9ICdoaWRlJ30NCiMgc2lnbmFsc19sb2dpdF9Nb00uMV9jYXQgPC0gbWF0cml4KDAsIG5yb3cgPSBvb3NfbGVuZ3RoX2NhdCwgbmNvbCA9IGxlbmd0aCh0aWNrKSkgI2NyZWF0ZSB0YWJsZSB0byBzdG9yZSBzaWduYWxzDQojIGNvbG5hbWVzKHNpZ25hbHNfbG9naXRfTW9NLjFfY2F0KSA8LSB0aWNrDQojIGZvciAoaSBpbiAxOmxlbmd0aCh0aWNrKSl7DQojICAgYSA8LSBzZWxlY3RGZWF0dXJlMih0aWNrW2ldLCBvdXRwdXRzX01vTS4xKSAlPiUNCiMgICAgIG11dGF0ZShvdXRwdXQgPSBhcy5mYWN0b3Iob3V0cHV0KSkgICAgICAgICANCiMgICBhX3ByZWQgPC0gdHJhaW4ob3V0cHV0IH4gLiwgZGF0YSA9IGFbdW5saXN0KHRpbWVTbGljZXNfdHJhaW4pLF0sIA0KIyAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiZ2xtIiwgDQojICAgICAgICAgICAgICAgICAgIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSAibG9naXQiKSwgIA0KIyAgICAgICAgICAgICAgICAgICBwcmVQcm9jID0gYygiY2VudGVyIiwgInNjYWxlIikpDQojICAgYV9hY3R1YWwuZnV0dXJlX3Rlc3QgPSBhW29vc19zdGFydF9jYXQ6bnJvdyhhKSxdICAlPiUNCiMgICAgIG11dGF0ZShvdXRwdXQgPSBhcy5mYWN0b3Iob3V0cHV0KSkgICAgICAgICANCiMgICBhX3ByZWQuZnV0dXJlX3Rlc3QgPC0gcHJlZGljdChhX3ByZWQsIGFfYWN0dWFsLmZ1dHVyZV90ZXN0KSAgICAgICAjIGdldCBwcmVkaWN0aW9uDQojICAgDQojICAgc2lnbmFsc19sb2dpdF9Nb00uMV9jYXRbLGldICA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihhX3ByZWQuZnV0dXJlX3Rlc3QpKSAgICAgICAgICAgICAgDQojICAgICAjIGNvbWJpbmUgc2lnbmFscyBmb3IgYWxsIHN0b2Nrcw0KIyAgICANCiMgfQ0KIyB3ZWlnaHRfbG9naXRfTW9NLjFfY2F0IDwtIGNhbF9wb3J0X3dlaWdodChzaWduYWxzX2xvZ2l0X01vTS4xX2NhdCkNCiMgcmV0X2xvZ2l0X01vTS4xX2NhdDwtcm93U3Vtcyh3ZWlnaHRfbG9naXRfTW9NLjFfY2F0Km9vc19yZXRfY2F0KQ0KDQojIHdyaXRlLmNzdih3ZWlnaHRfbG9naXRfTW9NLjFfY2F0LCBmaWxlID0gIndlaWdodF9sb2dpdF9Nb00uMV9jYXQuY3N2IikNCiMgd3JpdGUuY3N2KHJldF9sb2dpdF9Nb00uMV9jYXQsIGZpbGUgPSAicmV0X2xvZ2l0X01vTS4xX2NhdC5jc3YiKQ0KDQp3ZWlnaHRfbG9naXRfTW9NLjFfY2F0ID0gcmVhZC5jc3YoZmlsZSA9ICJ3ZWlnaHRfbG9naXRfTW9NLjFfY2F0LmNzdiIpDQp3ZWlnaHRfbG9naXRfTW9NLjFfY2F0ID0gd2VpZ2h0X2xvZ2l0X01vTS4xX2NhdFstMV0NCg0KcmV0X2xvZ2l0X01vTS4xX2NhdCA9IHJlYWQuY3N2KGZpbGUgPSAicmV0X2xvZ2l0X01vTS4xX2NhdC5jc3YiKQ0KcmV0X2xvZ2l0X01vTS4xX2NhdCA9IHJldF9sb2dpdF9Nb00uMV9jYXRbLTFdDQpgYGANCg0KU2luY2UgdGhlIGNvbmRpdGlvbiBpcyByYXJlbHkgbWVldCwgc29tZSB0ZXN0IGZvbGRzIGNvbnRhaW4gaW5mb3JtYXRpb24gbWlzc2luZyBpbiB0cmFpbmluZyBmb2xkcy4gRm9yIHRoaXMgcmVhc29uLCB3ZSB3aWxsIHNraXAgdGhpcyBtZXRob2Qgd2l0aCB0aGUgYXNzdW1wdGlvbiB0aGF0IHRoZSBzaWduYWxzIHJlcHJlc2VudCBhIGxhY2sgaW4gZXZpZGVuY2Ugb2YgY29uc2lzdGVudCBpbmNyZWFzaW5nIG1vbWVudHVtIG92ZXIgMyBwZXJpb2RzLiBJbiBvdGhlciB3b3JkLCBpdCBjYW4gYmUgaW50ZXJwcmV0ZWQgdGhhdCAzLXBlcmlvZCBwYXN0IGluZm9ybWF0aW9uIGlzIGlycmVsZXZhbnQgaW4gZGV0ZXJtaW5pbmcgc2lnbmlmaWNhbnQgc2lnbmFscy4NCg0KRm9yIGV4YW1wbGUsIGNvbnNpZGVyaW5nIGFwcGxlIHN0b2NrIGF1dG9jb3JyZWxhdGlvbiBiZWxvdy4gVGhlIGZpZ3VyZSBzaG93cyB0aGF0IHBhc3QgaW5mb3JtYXRpb24gaXMgYmFyZWx5IGluZGljYXRpdmUsIGV4Y2VwdCBvbmx5IGZvciBsYWcgMTUtcGVyaW9kIG9ic2VydmF0aW9uIHdoaWNoIG1pZ2h0IGJlIHRvbyBmdXJ0aGVyIGF3YXkgZnJvbSB0aGUgY3VycmVudCB0aW1lIHRvIGhhdmUgYW4gZWNvbm9taWMgbWVhbmluZy4NCg0KYGBge3J9DQphY2Yob3V0cHV0c19Nb00uM1ssJ0FBUEwnXSkNCmBgYA0KDQojIyMjIyBSZXN1bHQ6IFBlcmZvcm1hbmNlIE1ldHJpY3MgZm9yIExvZ2l0IE1vZGVsDQpgYGB7cn0NCk1fbG9naXRfUF9jYXQgPC0gcGVyZl9tZXQyKGFzLm1hdHJpeChyZXRfbG9naXRfUF9jYXQpLGFzLm1hdHJpeCh3ZWlnaHRfbG9naXRfUF9jYXQpLGRhdGVzX29vcywNCiAgICAgICAgICAgICAgICAgICAgcG9ydF9uYW1lID0gImxvZ2l0X1BlcmNlbnRpbGUiLHJldHVybnMpDQpNX2xvZ2l0X01vTS4xX2NhdCA8LSBwZXJmX21ldDIoYXMubWF0cml4KHJldF9sb2dpdF9Nb00uMV9jYXQpLGFzLm1hdHJpeCh3ZWlnaHRfbG9naXRfTW9NLjFfY2F0KSxkYXRlc19vb3MsDQogICAgICAgICAgICAgICAgICAgICAgICBwb3J0X25hbWUgPSAibG9naXRfTW9tZW50dW0uMSIsIHJldHVybnMpDQpjYXRfbG9naXQgPC0gcmJpbmQoTV9sb2dpdF9QX2NhdCxNX2xvZ2l0X01vTS4xX2NhdCkNCmNhdF9sb2dpdA0KYGBgDQoNCiMjIyMgUHJvYml0IE1vZGVsDQpTaW1pbGFyIHRvIGxvZ2l0IG1vZGVsLCB0aGlzIG1vZGVsIHRyYW5zZm9ybSBhbHNvIHRoZSBvdXRwdXRzIGludG8gcHJvYmFiaWxpdHksIHdoaWNoIGNhcCBvdXRwdXRzIGJldHdlZW4gMCBhbmQgMS4gIE9uIGEgY29udHJhcnkgdG8gbG9naXQgbW9kZWwsIGl0IHVzZXMgbm9ybWFsIGRpc3RyaWJ1dGlvbiB0byB0cmFuc2Zvcm0gdGhlIG91dHB1dC4gDQoNCiMjIyMjIFRyYWluIHByb2JpdCBtb2RlbCB3aXRoIHBlcmNlbnRpbGUNCmBgYHtyIHByb2JpdF9wX2NhdCwgZWNobyA9IFQsIG1lc3NhZ2U9IEZBTFNFLHJlc3VsdHMgPSAnaGlkZSd9DQojIHNpZ25hbHNfcHJvYml0X1BfY2F0IDwtIG1hdHJpeCgwLCBucm93ID0gb29zX2xlbmd0aF9jYXQsIG5jb2wgPSBsZW5ndGgodGljaykpICNjcmVhdGUgdGFibGUgdG8gc3RvcmUgc2lnbmFscw0KIyBjb2xuYW1lcyhzaWduYWxzX3Byb2JpdF9QX2NhdCkgPC0gdGljaw0KIyBmb3IgKGkgaW4gMTpsZW5ndGgodGljaykpew0KIyAgIGEgPC0gc2VsZWN0RmVhdHVyZTIodGlja1tpXSxvdXRwdXRzX1ApICU+JQ0KIyAgICAgbXV0YXRlKG91dHB1dCA9IGFzLmZhY3RvcihvdXRwdXQpKSAgICAgICAgIA0KIyAgIGFfcHJlZCA8LSB0cmFpbihvdXRwdXQgfiAuLCBkYXRhID0gYVt1bmxpc3QodGltZVNsaWNlc190cmFpbiksXSwgDQojICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG0iLCANCiMgICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJwcm9iaXQiKSwgDQojICAgICAgICAgICAgICAgICAgIHByZVByb2MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSkNCiMgICBhX2FjdHVhbC5mdXR1cmVfdGVzdCA9IGFbb29zX3N0YXJ0X2NhdDpucm93KGEpLF0gJT4lDQojICAgICBtdXRhdGUob3V0cHV0ID0gYXMuZmFjdG9yKG91dHB1dCkpICAgICAgICAgDQojICAgYV9wcmVkLmZ1dHVyZV90ZXN0IDwtIHByZWRpY3QoYV9wcmVkLCBhX2FjdHVhbC5mdXR1cmVfdGVzdCkgICAgICAgIyBnZXQgcHJlZGljdGlvbg0KIyAgIA0KIyAgIHNpZ25hbHNfcHJvYml0X1BfY2F0WyxpXSAgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoYV9wcmVkLmZ1dHVyZV90ZXN0KSkgICAgICAgICAgICAgIA0KIyAgICAgIyBjb21iaW5lIHNpZ25hbHMgZm9yIGFsbCBzdG9ja3MNCiMgICAgDQojIH0NCiMgd2VpZ2h0X3Byb2JpdF9QX2NhdCA8LSBjYWxfcG9ydF93ZWlnaHQoc2lnbmFsc19wcm9iaXRfUF9jYXQpDQojIHJldF9wcm9iaXRfUF9jYXQ8LXJvd1N1bXMod2VpZ2h0X3Byb2JpdF9QX2NhdCpvb3NfcmV0X2NhdCkNCg0KIyB3cml0ZS5jc3Yod2VpZ2h0X3Byb2JpdF9QX2NhdCwgZmlsZSA9ICJ3ZWlnaHRfcHJvYml0X1BfY2F0LmNzdiIpDQojIHdyaXRlLmNzdihyZXRfcHJvYml0X1BfY2F0LCBmaWxlID0gInJldF9wcm9iaXRfUF9jYXQuY3N2IikNCg0Kd2VpZ2h0X3Byb2JpdF9QX2NhdCA9IHJlYWQuY3N2KGZpbGUgPSAid2VpZ2h0X3Byb2JpdF9QX2NhdC5jc3YiKQ0Kd2VpZ2h0X3Byb2JpdF9QX2NhdCA9IHdlaWdodF9wcm9iaXRfUF9jYXRbLTFdDQoNCnJldF9wcm9iaXRfUF9jYXQgPSByZWFkLmNzdihmaWxlID0gInJldF9wcm9iaXRfUF9jYXQuY3N2IikNCnJldF9wcm9iaXRfUF9jYXQgPSByZXRfcHJvYml0X1BfY2F0Wy0xXQ0KYGBgDQoNCg0KIyMjIyMgVHJhaW4gcHJvYml0IG1vZGVsIHdpdGggMS1wZXJpb2QgbW9tZW50dW0NCmBgYHtyIHByb2JpdF9tb20xX2NhdCxlY2hvID0gVCwgbWVzc2FnZT0gRkFMU0UscmVzdWx0cyA9ICdoaWRlJ30NCiMgc2lnbmFsc19wcm9iaXRfTW9NLjFfY2F0IDwtIG1hdHJpeCgwLCBucm93ID0gb29zX2xlbmd0aF9jYXQsIG5jb2wgPSBsZW5ndGgodGljaykpICNjcmVhdGUgdGFibGUgdG8gc3RvcmUgc2lnbmFscw0KIyBjb2xuYW1lcyhzaWduYWxzX3Byb2JpdF9Nb00uMV9jYXQpIDwtIHRpY2sNCiMgZm9yIChpIGluIDE6bGVuZ3RoKHRpY2spKXsNCiMgICBhIDwtIHNlbGVjdEZlYXR1cmUyKHRpY2tbaV0sIG91dHB1dHNfTW9NLjEpICU+JQ0KIyAgICAgbXV0YXRlKG91dHB1dCA9IGFzLmZhY3RvcihvdXRwdXQpKSAgICAgICAgIA0KIyAgIGFfcHJlZCA8LSB0cmFpbihvdXRwdXQgfiAuLCBkYXRhID0gYVt1bmxpc3QodGltZVNsaWNlc190cmFpbiksXSwgDQojICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG0iLCANCiMgICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJwcm9iaXQiKSwgIA0KIyAgICAgICAgICAgICAgICAgICBwcmVQcm9jID0gYygiY2VudGVyIiwgInNjYWxlIikpDQojICAgYV9hY3R1YWwuZnV0dXJlX3Rlc3QgPSBhW29vc19zdGFydF9jYXQ6bnJvdyhhKSxdJT4lDQojICAgICBtdXRhdGUob3V0cHV0ID0gYXMuZmFjdG9yKG91dHB1dCkpICAgICAgICAgDQojICAgYV9wcmVkLmZ1dHVyZV90ZXN0IDwtIHByZWRpY3QoYV9wcmVkLCBhX2FjdHVhbC5mdXR1cmVfdGVzdCkgICAgICAgIyBnZXQgcHJlZGljdGlvbg0KIyAgIA0KIyAgIHNpZ25hbHNfcHJvYml0X01vTS4xX2NhdFssaV0gPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoYV9wcmVkLmZ1dHVyZV90ZXN0KSkgICAgICAgICAgICAgIA0KIyAgICAgIyBjb21iaW5lIHNpZ25hbHMgZm9yIGFsbCBzdG9ja3MNCiMgICAgDQojIH0NCiMgd2VpZ2h0X3Byb2JpdF9Nb00uMV9jYXQgPC0gY2FsX3BvcnRfd2VpZ2h0KHNpZ25hbHNfcHJvYml0X01vTS4xX2NhdCkNCiMgcmV0X3Byb2JpdF9Nb00uMV9jYXQ8LXJvd1N1bXMod2VpZ2h0X3Byb2JpdF9Nb00uMV9jYXQqb29zX3JldF9jYXQpDQojIA0KIyB3cml0ZS5jc3Yod2VpZ2h0X3Byb2JpdF9Nb00uMV9jYXQsIGZpbGUgPSAid2VpZ2h0X3Byb2JpdF9Nb00uMV9jYXQuY3N2IikNCiMgd3JpdGUuY3N2KHJldF9wcm9iaXRfTW9NLjFfY2F0LCBmaWxlID0gInJldF9wcm9iaXRfTW9NLjFfY2F0LmNzdiIpDQoNCndlaWdodF9wcm9iaXRfTW9NLjFfY2F0ID0gcmVhZC5jc3YoZmlsZSA9ICJ3ZWlnaHRfcHJvYml0X01vTS4xX2NhdC5jc3YiKQ0Kd2VpZ2h0X3Byb2JpdF9Nb00uMV9jYXQgPSB3ZWlnaHRfcHJvYml0X01vTS4xX2NhdFstMV0NCg0KcmV0X3Byb2JpdF9Nb00uMV9jYXQgPSByZWFkLmNzdihmaWxlID0gInJldF9wcm9iaXRfTW9NLjFfY2F0LmNzdiIpDQpyZXRfcHJvYml0X01vTS4xX2NhdCA9IHJldF9wcm9iaXRfTW9NLjFfY2F0Wy0xXQ0KYGBgDQoNCmFkZGFkZCAtLT4gZm9yIHRoZSBzYW1lIHJlYXNvbiwgbm8gbW9tMyBmb3IgcHJvYml0DQoNCiMjIyMjIFJlc3VsdDogUGVyZm9ybWFuY2UgTWV0cmljcyBmb3IgUHJvYml0IE1vZGVsDQpgYGB7cn0NCk1fcHJvYml0X1BfY2F0IDwtIHBlcmZfbWV0Mihhcy5tYXRyaXgocmV0X3Byb2JpdF9QX2NhdCksYXMubWF0cml4KHdlaWdodF9wcm9iaXRfUF9jYXQpLGRhdGVzX29vcywNCiAgICAgICAgICAgICAgICAgICAgcG9ydF9uYW1lID0gInByb2JpdF9QZXJjZW50aWxlIixyZXR1cm5zKQ0KTV9wcm9iaXRfTW9NLjFfY2F0IDwtIHBlcmZfbWV0Mihhcy5tYXRyaXgocmV0X3Byb2JpdF9Nb00uMV9jYXQpLGFzLm1hdHJpeCh3ZWlnaHRfcHJvYml0X01vTS4xX2NhdCksZGF0ZXNfb29zLA0KICAgICAgICAgICAgICAgICAgICAgICAgcG9ydF9uYW1lID0gInByb2JpdF9Nb21lbnR1bS4xIiwgcmV0dXJucykNCmNhdF9wcm9iaXQgPC0gcmJpbmQoTV9wcm9iaXRfUF9jYXQsTV9wcm9iaXRfTW9NLjFfY2F0KQ0KY2F0X3Byb2JpdA0KYGBgDQoNCg0KIyBBbmFseXNpcw0KDQpUbyBhc3Nlc3MgdGhlIHBlcmZvcm1hbmNlIG9mIGVhY2ggbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWUsIHdlIGZvY3VzIG9uIHRoZSBhc3BlY3Qgb2YgcG9ydGZvbGlvLiBBY2NvcmRpbmdseSwgd2Ugc2NvcmUgZWFjaCBhc3BlY3Qgb2YgcGVyZm9ybWFuY2UgbWV0cmljcyBmcm9tIDEgdG8gMTggZm9yIDE4IG1ldGhvZHMsIHdoZXJlIDEgaXMgdGhlIGJlc3QgaW4gdGhhdCBjYXRlZ29yeSBhbmQgMTggaXMgdGhlIHdvcnN0LiBUaHVzLCB3ZSBjYWxsIHRoaXMgc2NvcmUg4oCYcmFua+KAmS4gTmFtZWx5LCByYW5rIDEgZm9yIGF2ZXJhZ2UgcmV0dXJuLCBzaGFycGUgcmF0aW8gYW5kIFZhUiBhcmUgdGhlIGhpZ2hlc3QsIHdoaWxlIHJhbmsgMSBmb3Igdm9sYXRpbGl0eSBhbmQgdHVybm92ZXIgYXJlIHRoZSBsb3dlc3QuDQoNClRhYmxlIDE6IFN1bW1hcnkgdGFibGUgZm9yIHBlcmZvcm1hbmNlIG1ldHJpY3MgJiByYW5rDQoNCg0KIVtUZXh0XSh0YWJsZTFfc3VtbWFyeVBlcmZNYXQuanBnKQ0KDQpGcm9tIHRoZSB0YWJsZSwgb3VyIGJlbmNobWFyayBwb3J0Zm9saW8gaXMgcXVpdGUgaGlnaCBpbiB0aGUgcmFuayBhdCAjNS4gVGhlIGFsbCBNTCB0ZWNobmlxdWVzIHdoaWNoIG91dHBlcmZvcm1zIGJlbmNobWFyayBhcmUgY2F0ZWdvcmljYWwgZGVwZW5kZW50IHZhcmlhYmxlIHdpdGggc2lnbmFscywgZXhjZXB0IHNwYXJzZSBwb3J0Zm9saW8gd2hpY2ggaXMgIzEuIFRodXMsIGNsZWFybHksIHNwYXJzZSBwb3J0Zm9saW8gb3V0cGVyZm9ybXMgc2lnbmFsIG1vZGVscy4gDQoNCldpdGhpbiB0aGUgc2lnbmFsIG1vZGVscywgY2F0ZWdvcmljYWwgbWV0aG9kIG91dHNoaW5lcyB0aGUgbnVtZXJpY2FsLiBDb25zaWRlciBLTk4sIHdoaWNoIGlzIHVzZWQgZm9yIGJvdGggY2F0ZWdvcmljYWwgYW5kIG51bWVyaWNhbCBvdXRwdXRzLiBJdCBwZXJmb3JtcyBwb29ybHkgaW4gdGhlIG51bWVyaWNhbCBvdXRwdXRzIGluIHNpZ25hbCBtb2RlbHMsIGFzIHNlZW4gaW4gdGhlIGJvdHRvbSByYW5rcy4gSW4gY29udHJhc3QsIGl0IGdpdmVzIHRvcC10ZW4gcmFua3MgZm9yIGNhdGVnb3JpY2FsIG9uZS4gVGhpcyBtaWdodCBiZSBiZWNhdXNlIEtOTiBhbGdvcml0aG0gdHJpZXMgdG8gc2VwYXJhdGUgb3V0cHV0cyBpbnRvIHJlZ2lvbnMgYmFzZWQgb24gZmVhdHVyZXMsIHRodXM7IGl0IGRvZXMgYSBiZXR0ZXIgam9iIGZvciBjYXRlZ29yaWNhbCBvdXRwdXRzIGFzIHRoZXJlIGFyZSBsZXNzIHJlZ2lvbnMgdG8gd29yayB3aXRoLiBUaGlzIGlzIGJlY2F1c2UgbnVtZXJpY2FsIG91dHB1dHMgaGF2ZSBtb3JlIHZhcmlvdXMgdmFsdWVzIHdoaWNoIHRoZSBtb2RlbCBuZWVkcyB0byBsZWFybiBhbmQgZml0LCBoZW5jZSB0aGVzZSBtb2RlbHMgbWlnaHQgZmFjZSBvdmVyZml0dGluZyBwcm9ibGVtcy4gDQoNClRoZSByYW5rIHNob3dzIHRoYXQgdGhlIHNpbXBsZXIgbW9kZWxzLCBpbmNsdWRpbmcgcHJvYml0IGFuZCBsb2dpdCBtb2RlbHMsIGhhdmUgbG93LWVuZCByYW5rcy4gV2hlcmVhcywgdGhlIGNvbXBsZXggbW9kZWxzLCBzdWNoIGFzIHJhbmRvbSBmb3Jlc3QgYW5kIEtOTiwgaGF2ZSBoaWdoLWVuZCByYW5rcy4gTm9uZXRoZWxlc3MsIHRoZSBpbnRlcm1lZGlhdGUgbW9kZWwgaW4gdGVybXMgb2YgY29tcGxleGl0eSwgUURBLCBwZXJmb3JtcyB0aGUgYmVzdCBhbW9uZyBjYXRlZ29yaWNhbCBtb2RlbC4gVGhpcyByZWZsZWN0cyB0aGF0IGFsdGhvdWdoIHRoZSBtb2RlbCB0ZW5kcyB0byBpbXByb3ZlIHdpdGggaGlnaGVyIGNvbXBsZXhpdHkgKGxvd2VyIHZhcmlhbmNlKSwgaG93ZXZlciBpdCB3aWxsIGZhY2Ugd2l0aCBoaWdoIHZhcmlhbmNlIGluIHRoZSBvdXQtb2Ytc2FtcGxlIHNldC4gVGhlcmVmb3JlLCBvbmx5IHNvbWUgbGV2ZWwgb2YgY29tcGxleGl0eSBpcyBhcHByb3ByaWF0ZSwgd2hlcmUgaXQgbWluaW1pc2UgYm90aCBiaWFzIGFuZCB2YXJpYW5jZS4gDQoNCkFjY29yZGluZyB0byBwZXJmb3JtYW5jZXMgb2Ygb3VyIHBvcnRmb2xpb3MsIGl0IGNhbiBiZSBhc3NpZ25lZCB0byBlYWNoIGludmVzdG9ycyB3aG8gcHJlZmVyIGRpZmZlcmVudCBsZXZlbHMgb2Ygcmlzay4gRm9yIGluc3RhbmNlLCByaXNrLWF2ZXJzaXZlIGludmVzdG9ycyB3aWxsIGJlIHdpbGxpbmcgdG8gYWxsb2NhdGUgdGhlaXIgY2FwaXRhbCBvbiBsZXNzIHZvbGF0aWxlIHBvcnRmb2xpb3Mgc3VjaCBhcyBTcGFyc2UgcG9ydGZvbGlvLiBPbiB0aGUgb3RoZXIgaGFuZCwgaW52ZXN0b3JzIHdobyBjYW4gYmVhciBtb3JlIHJpc2tzIGNhbiBjaG9vc2UgdG8gaW52ZXN0IGluIG1vcmUgcmlza3kgcG9ydGZvbGlvcyB0byBnYWluIGhpZ2hlciByZXR1cm5zIHN1Y2ggYXMgQ2F0ZWdvcmljYWwgS05OIHBvcnRmb2xpb3MuIFNwYXJzZSBwb3J0Zm9saW8gYW5kIEVxdWFsbHkgd2VpZ2h0ZWQgcG9ydGZvbGlvIGFyZSByZWNvbW1lbmRlZCBmb3IgcGFzc2l2ZSBwb3J0Zm9saW8gbWFuYWdpbmcgaW52ZXN0b3JzIHdoaWNoIHdpbGwgY29zdCBzaWduaWZpY2FudGx5IGxlc3MgdHJhbnNhbmN0aW9uIGNvc3QgdGhhbiBvdGhlciBwb3J0Zm9saW8gc3RyYXRlZ2llcy4gDQo=