The goal of this data science project is to build a model which can accurately predict cryptocurrency and stock prices solely based on previous time series data. No external predictors will be used. The forecasting methods to be used are:

  • Naive forecast
  • Holt-Winters
  • Arima
  • h2o’s autoML (automated machine learning models)
  • Meta’s (formerly Facebook’s) Prophet

1 Setup and Packages

setwd(dirname(rstudioapi::getActiveDocumentContext()$path))

knitr::opts_chunk$set(cache = T, warning = F, message = F)

if(require(pacman)==FALSE) install.packages("pacman") # package manager
## Loading required package: pacman
pacman::p_load(tidyverse, # general functions
               hrbrthemes, reactable,
               magrittr, # for 2-way pipe
               tidyquant, # for time series data
               rvest, # stock names
               data.table, DT, 
               h2o, rsample, prophet, # model building
               forecast, timetk, imputeTS, fpp2, # time series helpers
               doParallel, foreach, furrr) # parallelization

2 Get Data & Pre-processing

2.1 Stocks

To get the stock names I will scrape the S&P 500 Wikipedia page.

content = read_html("https://en.wikipedia.org/w/index.php?title=List_of_S%26P_500_companies&oldid=1014924736")
tables <- content %>% html_table(fill = TRUE)
table <- tables[[1]]
sp_list <- table[-1,c(1,7)]
names(sp_list)[2]='AddedDate'

To ensure the time series has no empty values, I am filtering stocks which were created after 2009. Also, I am removing Berkshire Hathaway because it was giving problems. After, I will keep just 60 stocks.

sp_list %<>% filter(AddedDate < '2010-01-01')
sp_list = sp_list[-49,]
sp_list %<>% filter(Symbol != "BF.B") 
stock_list = sp_list$Symbol[1:60]

Below I am setting the date ranges for the time series.

beg = as_date("2017-01-01")
end = as_date("2020-01-01")
stocks = tq_get(x = stock_list,
                from = beg,
                to = end, periodicity = "weekly") # getting the time series data

stocks %<>% select(symbol, date, adjusted)
head(stocks)
## # A tibble: 6 x 3
##   symbol date       adjusted
##   <chr>  <date>        <dbl>
## 1 ABT    2017-01-01     37.0
## 2 ABT    2017-01-08     37.2
## 3 ABT    2017-01-15     37.0
## 4 ABT    2017-01-22     37.5
## 5 ABT    2017-01-29     39.1
## 6 ABT    2017-02-05     39.1

2.2 Indexes

Now, I am getting the data for 5 stock market indexes.

ind_list = c('^DJI','^NYA','^GSPC','^IXIC','^N225')
ind = tq_get(x = ind_list, from = "2015-01-01", to = "2018-01-01", periodicity = "weekly") %>% select(symbol,date,adjusted)
head(ind)
## # A tibble: 6 x 3
##   symbol date       adjusted
##   <chr>  <date>        <dbl>
## 1 ^DJI   2015-01-01   17585.
## 2 ^DJI   2015-01-08   17427.
## 3 ^DJI   2015-01-15   17554.
## 4 ^DJI   2015-01-22   17191.
## 5 ^DJI   2015-01-29   17673.
## 6 ^DJI   2015-02-05   17862.

2.3 Crypto

To get the cryptocurrency data, I am using the CryptoCompare API. First I need to define some variables for the query, includes he cryptocurrencies I want.

pacman::p_load(jsonlite)
base = "https://min-api.cryptocompare.com/data/v2/histoday?fsym="

name = c("BTC","ETH","BNB","USDT","SOL","DOT","ADA","XRP","DOGE","SHIB","LTC","WBTC","UNI","BUSD","MATIC","MANA","ALGO","LINK","THETA","SAND","HNT","MIOTA","CAKE","MKR","NEO","BTT","EGLD","VET","ICP","XLM","AVAX","CRO","ATOM","NEAR","RUNE")

crypto_list = name

t = "&tsym=USD"

agg = "&aggregate=days"

lim = "&limit=360"

key  = "&api_key=7bf4448fc2818704eace0e470e4f87eb108e90e760bef562f1df81687616b040"

Here I am preparing the urls which I will query in the next code chunk.

urls = c(paste0(base,name[1],t,lim,key),paste0(base,name[2],t,lim,key),paste0(base,name[3],t,lim,key),
         paste0(base,name[4],t,lim,key),paste0(base,name[5],t,lim,key),paste0(base,name[6],t,lim,key),
         paste0(base,name[7],t,lim,key),paste0(base,name[8],t,lim,key),paste0(base,name[9],t,lim,key),
         paste0(base,name[10],t,lim,key),paste0(base,name[11],t,lim,key),paste0(base,name[12],t,lim,key),
         paste0(base,name[13],t,lim,key),paste0(base,name[14],t,lim,key),paste0(base,name[15],t,lim,key),
         paste0(base,name[16],t,lim,key),paste0(base,name[17],t,lim,key),paste0(base,name[18],t,lim,key),
         paste0(base,name[19],t,lim,key),paste0(base,name[20],t,lim,key),paste0(base,name[21],t,lim,key),
         paste0(base,name[22],t,lim,key),paste0(base,name[23],t,lim,key),paste0(base,name[24],t,lim,key),
         paste0(base,name[25],t,lim,key),paste0(base,name[26],t,lim,key),paste0(base,name[27],t,lim,key),
         paste0(base,name[28],t,lim,key),paste0(base,name[29],t,lim,key),paste0(base,name[30],t,lim,key),paste0(base,name[31],t,lim,key),paste0(base,name[32],t,lim,key),paste0(base,name[33],t,lim,key),paste0(base,name[34],t,lim,key),paste0(base,name[35],t,lim,key)
)

Now I will make 35 API calls at once for the data.

cryptolist = map(.x = urls, .f = fromJSON)

Here I convert the data types so I can format them neatly.

unlist_cc = function(x){
  temp = x$Data$Data
}
crypto = map_df(.x = cryptolist, .f = unlist_cc)

The last step for the crypto data is to format it in a readable format.

crypto %<>% select(time, close)
crypto$time %<>% as_datetime() %>% as_date()
crypto$symbol = rep(name, each = (nrow(crypto)/length(crypto_list))) 
crypto %<>% rename(date = time)
crypto %<>% relocate(symbol,.before=date)
head(crypto)
##   symbol       date    close
## 1    BTC 2021-04-21 53803.25
## 2    BTC 2021-04-22 51717.61
## 3    BTC 2021-04-23 51178.03
## 4    BTC 2021-04-24 50115.99
## 5    BTC 2021-04-25 49120.97
## 6    BTC 2021-04-26 54062.29

For the sake of managing my R environment, I remove objects which are no longer useful for the upcoming analysis.

The final pre-processing step is renaming the price variable for consistency.

stocks %<>% rename(price = adjusted)
ind %<>% rename(price = adjusted)
crypto %<>% rename(price = close)

3 Rolling Origins

For time series data, it is not appropriate to split time series data into a training and validation set in which the first 80% (or other amount) of the dates are used for training. Rolling origin evaluation fixes this by first partitioning the data, but validation is done sequentially on each date starting at the beginning of the validation partition.

3.1 Functions

Since I am working with 100 time series, I must apply functions to each stock/crypto. Unfortunately, any apply function will not work for this project since I must aggregate results and run other functions within the loops. The first loop function is a rolling origin function which aggregates results.

rolling = function(dat, dat_list, initial){
  
  results = data.frame(splits = c(), id = c())
  
  for (i in dat_list){ 
    temp = filter(dat, symbol == i)
    temp = na_interpolation(temp)
    temp_res = rolling_origin(temp, initial = which(temp$date == as.Date(initial)),
                              assess = 1,
                              cumulative = TRUE) 
    results = rbind(results, temp_res)
  }
  return(results)
}

Here we extract the training and validation data for the models.

getinfo = function(df, dat_list){
  df %<>% mutate(
  symbol = rep(dat_list, each = (nrow(df)/length(dat_list))),
  data = map(.x = splits, .f = analysis),
  trainDates = map(.x = data, .f = extract2, 'date'),
  trainPrice =  map(.x = data, .f = extract2, 'price'),
  target_data = map(.x = splits, .f = assessment),
  targetDate = map(.x = target_data, .f = extract2, 'date'),
  targetPrice =  map_dbl(.x = target_data, .f = extract2, 'price'))
  return(df)
}

Since I created functions above, now all I have to do is pass each group of data (stocks, indexes, crypto) through the functions.

3.2 Stocks

initial_s = stocks$date[.8*nrow(stocks)/length(stock_list)]
new_stocks = rolling(stocks, stock_list, initial_s)
new_stocks = getinfo(new_stocks, stock_list)
head(new_stocks)
## # A tibble: 6 x 9
##   splits    id     symbol data     trainDates trainPrice target_data  targetDate
##   <list>    <chr>  <chr>  <list>   <list>     <list>     <list>       <list>    
## 1 <split [~ Slice~ ABT    <tibble~ <date [12~ <dbl [125~ <tibble [1 ~ <date [1]>
## 2 <split [~ Slice~ ABT    <tibble~ <date [12~ <dbl [126~ <tibble [1 ~ <date [1]>
## 3 <split [~ Slice~ ABT    <tibble~ <date [12~ <dbl [127~ <tibble [1 ~ <date [1]>
## 4 <split [~ Slice~ ABT    <tibble~ <date [12~ <dbl [128~ <tibble [1 ~ <date [1]>
## 5 <split [~ Slice~ ABT    <tibble~ <date [12~ <dbl [129~ <tibble [1 ~ <date [1]>
## 6 <split [~ Slice~ ABT    <tibble~ <date [13~ <dbl [130~ <tibble [1 ~ <date [1]>
## # ... with 1 more variable: targetPrice <dbl>

3.3 Indexes

initial_i = ind$date[.8*nrow(ind)/length(ind_list)]
new_index = rolling(ind, ind_list, initial_i)
new_index = getinfo(new_index, ind_list)
head(new_index)
## # A tibble: 6 x 9
##   splits    id     symbol data     trainDates trainPrice target_data  targetDate
##   <list>    <chr>  <chr>  <list>   <list>     <list>     <list>       <list>    
## 1 <split [~ Slice~ ^DJI   <tibble~ <date [12~ <dbl [125~ <tibble [1 ~ <date [1]>
## 2 <split [~ Slice~ ^DJI   <tibble~ <date [12~ <dbl [126~ <tibble [1 ~ <date [1]>
## 3 <split [~ Slice~ ^DJI   <tibble~ <date [12~ <dbl [127~ <tibble [1 ~ <date [1]>
## 4 <split [~ Slice~ ^DJI   <tibble~ <date [12~ <dbl [128~ <tibble [1 ~ <date [1]>
## 5 <split [~ Slice~ ^DJI   <tibble~ <date [12~ <dbl [129~ <tibble [1 ~ <date [1]>
## 6 <split [~ Slice~ ^DJI   <tibble~ <date [13~ <dbl [130~ <tibble [1 ~ <date [1]>
## # ... with 1 more variable: targetPrice <dbl>

3.4 Crypto

initial_c = crypto$date[.8*nrow(crypto)/length(crypto_list)]
new_crypto = rolling(crypto, crypto_list, initial_c)
new_crypto = getinfo(new_crypto, crypto_list)
head(new_crypto)
## # A tibble: 6 x 9
##   splits    id     symbol data     trainDates  trainPrice target_data targetDate
##   <list>    <chr>  <chr>  <list>   <list>      <list>     <list>      <list>    
## 1 <split [~ Slice~ BTC    <df [28~ <date [288~ <dbl [288~ <df [1 x 3~ <date [1]>
## 2 <split [~ Slice~ BTC    <df [28~ <date [289~ <dbl [289~ <df [1 x 3~ <date [1]>
## 3 <split [~ Slice~ BTC    <df [29~ <date [290~ <dbl [290~ <df [1 x 3~ <date [1]>
## 4 <split [~ Slice~ BTC    <df [29~ <date [291~ <dbl [291~ <df [1 x 3~ <date [1]>
## 5 <split [~ Slice~ BTC    <df [29~ <date [292~ <dbl [292~ <df [1 x 3~ <date [1]>
## 6 <split [~ Slice~ BTC    <df [29~ <date [293~ <dbl [293~ <df [1 x 3~ <date [1]>
## # ... with 1 more variable: targetPrice <dbl>

4 Forecasting

In this section, I will apply naive, holt-winters, arima, autoML, and prophet forecasts to each time series. Later I will compare the results of each model.

4.1 Naive, SES, and ARIMA

4.1.1 Function

This function applies naive, holt-winters, and arima forecasts to all the time series. I explain later why we cannot apply this simply to the other two models.

NHA = function(df){
  plan(multiprocess)
  df %<>% mutate(
  Naive = future_map(.x = trainPrice, .f = naive, h = 1) %>% 
    map_dbl(.f = extract2, "mean"),
  Holt = future_map(.x = trainPrice, .f = holt, h = 1) %>% 
    map_dbl(.f = extract2, "mean"),
  Arima = future_map(.x = trainPrice, .f = auto.arima) %>% 
    map(.f = forecast, h = 1) %>% 
    map_dbl(.f = extract2, "mean")
)
  return(df)
}

4.1.2 Stocks

new_stocks = NHA(new_stocks)

4.1.3 Indexes

new_index = NHA(new_index)

4.1.4 Crypto

new_crypto = NHA(new_crypto)

4.2 Prophet

For some odd reason, the prophet documentation states that in order to use the model, the dates in the time series must be named “ds” and the actual values must be named “y”. If you use any other labeling, R throws errors. Because of this, I had to create a function which converts the columns names and then applies the prophet function.

4.2.1 Function

prophetloop = function(nf){
  prophetfun = function(y, dates, targetDate){
    pacman::p_load(tidyverse, magrittr, prophet)
    df = as.data.frame(cbind(dates, y)) %>% 
      rename(ds=1,y=2)
    df = unnest(df, cols = c(ds,y))
    model = prophet(df, yearly.seasonality = "auto",
                    weekly.seasonality = "auto")
    targetDate %<>% as.data.frame() %>% 
      rename(ds=1)
    pred = predict(model, targetDate)
    res = pred$yhat
    return(res)
  }
  
  i = 1
  
  for (i in 1:nrow(nf)){
    val = prophetfun(y = nf[i,]$trainPrice, dates = nf[i,]$trainDates, 
                targetDate = nf[i,]$targetDate)
    nf[i,]$Prophet = val
    i = i + 1
  }
  return(nf)
}

Here I simply apply the custom prophet function to the three sets of time series.

4.2.2 Stocks

new_stocks['Prophet'] = NA
new_stocks = prophetloop(new_stocks)

4.2.3 Indexes

new_index['Prophet'] = NA
new_index = prophetloop(new_index)

4.2.4 Crypto

new_crypto['Prophet'] = NA
new_crypto = prophetloop(new_crypto)

4.3 AutoML Function (Applied Separately)

AutoML is easily the hardest model to get running for my many time series. This is because autoML does not like to loop more than 30 times in a single h2o connection. As a result, I applied this function in a separate r file four times (for a total of 100 time series). I then appended all the data and brought it back into this file.

Here is the function which creates new variables from just the time series data itself (no external data) so that the machine learning models have more data for prediction.

feat_df = tk_get_timeseries_signature(unique(data$date)) %>% 
    na.omit()
nums = unlist(lapply(feat_df, is.numeric))
feat_df = feat_df[,nums]
feat_df = feat_df[c(TRUE, lapply(feat_df[-1], var, na.rm = TRUE) != 0)]

Since autoML relies on an API, I had to first initiate autoML session and then convert my data to meet the autoML model input requirements. From there, I compile the model metrics.

automl = function(data, data_list, minutes){
  
  feat_df = tk_get_timeseries_signature(unique(data$date)) %>% 
    na.omit()
  nums = unlist(lapply(feat_df, is.numeric))
  feat_df = feat_df[,nums]
  feat_df = feat_df[c(TRUE, lapply(feat_df[-1], var, na.rm = TRUE) != 0)]
  feat_df = cbind(unique(data$date)[-1], feat_df) %>% rename(index=1)
  feat_df %<>% select(-year.iso)
  
  results = data.frame(Symbol = c(), MSE = c(),
                       MAE = c(), RMSE = c())
  
  h2o.init()
  
  for (i in data_list){
    df = filter(data, symbol == i)
    df = merge(df, feat_df, by.x = "date", by.y = "index")
    df %<>% select(-date)
    df %<>% mutate(lag1 = lag(price), lag2 = lag(price, k = 2),
                   lag3 = lag(price, k = 3))
    traindf = df[1:floor(.8*nrow(df)),] %>% as.h2o()
    validdf = df[ceiling(.8*nrow(df)):floor(nrow(df)*.9),] %>% as.h2o()
    testdf = df[ceiling(nrow(df)*.9):nrow(df),] %>% as.h2o()
    
    x = 3:(ncol(df))
    
    h2o.init()
    
    model = h2o.automl(x = x, y = 'price',
                       training_frame = traindf,
                       validation_frame = validdf,
                       leaderboard_frame = testdf,
                       nfolds = 5,
                       stopping_metric = "AUTO",
                       max_runtime_secs = (minutes * 60))
    
    best = model@leader
    
    autoML_pred = h2o.predict(best, newdata = testdf)
    error = h2o.performance(best, newdata = testdf)
    MSE = error@metrics$MSE
    RMSE = error@metrics$RMSE
    MAE = error@metrics$mae
    
    res = cbind(i, MSE, RMSE, MAE)
    results = rbind(results, res)
  } 
  
  results %<>% rename(Symbol = i)
  return(results)
}

4.4 Stocks

autoML_stocks = automl(stocks, stock_list, .5)

4.5 Index

autoML_index = automl(ind, ind_list, 1)

4.6 Crypto

autoML_crypto = automl(crypto, crypto_list, .5)

4.7 Function

Below I calculate the error for all models except autoML (that was done within the autoML custom function) in one function.

res1 = function(df){
  results =  df %>% mutate(
  errorNaive = targetPrice - Naive,
  peNaive = errorNaive / targetPrice,
  errorHolt = targetPrice - Holt,
  peHolt = errorHolt / targetPrice,
  errorArima = targetPrice - Arima,
  peArima = errorArima / targetPrice,
  errorProphet = targetPrice - Prophet,
  peProphet = errorProphet / targetPrice
)
  results %<>% select(type, symbol, errorNaive, peNaive, errorHolt, peHolt, errorArima, peArima, errorProphet, peProphet)
  
  return(results)
}

This chunk merges the error dataframe completed in the last step with all the stock/crypto names.

res_nhap = rbind(new_stocks, new_index, new_crypto)
res_nhap$type = c(rep('Stock',nrow(new_stocks)), rep('Index',nrow(new_index)), rep('Crypto',nrow(new_crypto)))
res_nhap = res1(res_nhap)

5 Results for Naive, Holt, Arima, and Prophet

To compare the results for the models (excluding autoML), I calculate the mean absolute error (MAE), mean absolute percent error (MAPE), and root mean square error (RMSE).

options(scipen=999)
results_nhap = res_nhap %>%  group_by(symbol) %>% mutate(
  MAE_Naive = mean(abs(errorNaive)),
  MAPE_Naive = mean(abs(peNaive))*100,
  RMSE_Naive = sqrt(mean(errorNaive^2)),
  MAE_Holt = mean(abs(errorHolt)),
  MAPE_Holt = mean(abs(peHolt))*100,
  RMSE_Holt = sqrt(mean(errorHolt^2)),
  MAE_Arima = mean(abs(errorArima)),
  MAPE_Arima = mean(abs(peArima))*100,
  RMSE_Arima = sqrt(mean(errorArima^2)),
  MAE_Prophet = mean(abs(errorProphet)),
  MAPE_Prophet = mean(abs(peProphet))*100,
  RMSE_Prophet = sqrt(mean(errorProphet^2))
  ) %>% select(c(1,2,11:22)) %>% unique()

6 Aggregating Results

For apples-to-apples comparison for all models, I will use RMSE, since autoML does not calculate MAPE.

autoML_Results = readRDS('autoML_Results.rds') %>% select(-type,-MSE) %>% rename(RMSE_autoML=RMSE, MAE_autoML=MAE)
autoML_Results$RMSE_autoML %<>% as.numeric()
autoML_Results$MAE_autoML %<>% as.numeric()
final_results = merge(results_nhap, autoML_Results, by.x = 'symbol', by.y = 'Symbol') %>% mutate(across(is.numeric, base::round, digits=3))

6.1 Detailed View

Here is a chart with all accuracy metrics with their respective stock/cryptocurrency.

head(final_results)
##   symbol  type MAE_Naive MAPE_Naive RMSE_Naive MAE_Holt MAPE_Holt RMSE_Holt
## 1   ^DJI Index   162.350      0.709    202.206  143.243     0.626   184.834
## 2  ^GSPC Index    13.934      0.549     17.719   13.145     0.518    16.580
## 3  ^IXIC Index    62.455      0.956     75.063   62.036     0.950    72.137
## 4  ^N225 Index   258.030      1.212    343.636  257.229     1.211   333.674
## 5   ^NYA Index    71.135      0.585     90.486   67.413     0.554    87.601
## 6      A Stock     1.438      1.979      1.757    1.403     1.935     1.730
##   MAE_Arima MAPE_Arima RMSE_Arima MAE_Prophet MAPE_Prophet RMSE_Prophet
## 1   153.128      0.671    190.596     301.782        1.332      360.628
## 2    13.542      0.534     17.401      25.158        0.993       30.525
## 3    62.610      0.959     73.002      56.591        0.872       69.022
## 4   258.030      1.212    343.636     432.946        2.075      553.617
## 5    71.135      0.585     90.486     162.773        1.339      212.442
## 6     1.432      1.972      1.752       3.097        4.222        3.890
##   RMSE_autoML MAE_autoML
## 1     346.167    281.519
## 2      49.915     44.011
## 3     154.802    134.157
## 4     690.358    608.160
## 5     402.793    380.504
## 6       1.339      1.139
# All RMSEs
RMSE = final_results %>% select(symbol,type,starts_with('RMSE')) %>%  pivot_longer(3:7) %>% rename(RMSE = value)
RMSE$name %<>% str_replace_all("RMSE_","")

Below are some data visualizations comparing the accuracy of the models.

RMSE %>%  group_by(name) %>% summarize(RMSE=mean(RMSE)) %>% arrange(RMSE) %>% ggplot(aes(x = reorder(name,-RMSE), y = RMSE, fill = name)) + geom_col() + scale_fill_viridis_d() + coord_flip() + xlab('Forecasting Method') + ggtitle('Average Error by Forecasting Method') + labs(caption = "RMSE used because MAPE was not available for autoML") + geom_text(aes(label=base::round(RMSE,3)), nudge_y = 20) + theme_minimal()

The Arima, Naive, and Holt forecasts are essentially all the same (with the difference likely being due to the sample size of 100). I conclude that since no forecast can improve upon the naive forecast, none of these methods (with my selected parameters) are appropriate for predicting future values of stocks and cryptocurrencies.

It is likely that autoML or Prophet could be improved by feeding more variables (some of which might supply useful information) or adjusting model parameters.

RMSE %>% ggplot(aes(x = name, y = RMSE, fill = name)) + geom_boxplot()  + theme_minimal() + coord_flip() + ylim(0,40) + scale_fill_viridis_d() + ggtitle('Error Distribution by Model') + xlab('Model') + theme(legend.position = "none")

RMSE %>% ggplot(aes(x = type, y = RMSE, fill = type)) + geom_violin() + coord_flip() + ylim(0,40) + scale_fill_viridis_d(begin = .2) + theme(legend.position = "none") + xlab('Time Series Type') + ggtitle('Error Distribution by Type of Time Series') + theme_minimal()


7 Winners

Note: There were a few ties between lowest RMSEs for a few stocks. Each tie was between Arima and Naive. Each tie will still be counted as +1 for both.

windf = RMSE %>% group_by(symbol) %>% summarize(RMSE = min(RMSE))
winners = merge(windf, RMSE, by.x = c('symbol','RMSE'), by.y = c('symbol','RMSE')) %>% select(name)
winners_summary = winners %>% count(winners$name) %>% rename(Model=1,NumberWins=2)
winners_summary %>% ggplot(aes(x=reorder(Model,NumberWins),y=NumberWins,fill=Model)) + geom_col() + scale_fill_viridis_d() + coord_flip() + theme_minimal() + ggtitle('Number of Wins for Each Model') + xlab('Model') + theme(legend.position = "none") + geom_text(aes(label=NumberWins), nudge_y = 2)

LS0tDQp0aXRsZTogIlByZWRpY3RpbmcgQ3J5cHRjdXJyZW5jeS9TdG9jayBQcmljZXMgVXNpbmcgVGltZSBTZXJpZXMgRm9yZWNhc3RpbmcgTW9kZWxzIg0KYXV0aG9yOiAiQWxleGFuZGVyIEZvcnRtYW4iDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIG51bWJlcl9zZWN0aW9uczogVFJVRQ0KICAgIHBhZ2VkX2RmOiBUUlVFDQogICAgY29kZV9mb2xkaW5nOiAic2hvdyINCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQogICAgc2VsZl9jb250YWluZWQ6IFRSVUUNCiAgICBtb2RlOiBzZWxmY29udGFpbmVkDQogICAgdGhlbWU6IGZsYXRseQ0KLS0tDQoNClRoZSBnb2FsIG9mIHRoaXMgZGF0YSBzY2llbmNlIHByb2plY3QgaXMgdG8gYnVpbGQgYSBtb2RlbCB3aGljaCBjYW4gYWNjdXJhdGVseSBwcmVkaWN0IGNyeXB0b2N1cnJlbmN5IGFuZCBzdG9jayBwcmljZXMgc29sZWx5IGJhc2VkIG9uIHByZXZpb3VzIHRpbWUgc2VyaWVzIGRhdGEuIE5vIGV4dGVybmFsIHByZWRpY3RvcnMgd2lsbCBiZSB1c2VkLiBUaGUgZm9yZWNhc3RpbmcgbWV0aG9kcyB0byBiZSB1c2VkIGFyZToNCg0KICArIE5haXZlIGZvcmVjYXN0DQogICsgSG9sdC1XaW50ZXJzDQogICsgQXJpbWENCiAgKyBoMm8ncyBhdXRvTUwgKGF1dG9tYXRlZCBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscykNCiAgKyBNZXRhJ3MgKGZvcm1lcmx5IEZhY2Vib29rJ3MpIFByb3BoZXQNCg0KIyBTZXR1cCBhbmQgUGFja2FnZXMNCg0KYGBge3Igc2V0dXB9DQpzZXR3ZChkaXJuYW1lKHJzdHVkaW9hcGk6OmdldEFjdGl2ZURvY3VtZW50Q29udGV4dCgpJHBhdGgpKQ0KDQprbml0cjo6b3B0c19jaHVuayRzZXQoY2FjaGUgPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEYpDQoNCmlmKHJlcXVpcmUocGFjbWFuKT09RkFMU0UpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpICMgcGFja2FnZSBtYW5hZ2VyDQoNCnBhY21hbjo6cF9sb2FkKHRpZHl2ZXJzZSwgIyBnZW5lcmFsIGZ1bmN0aW9ucw0KICAgICAgICAgICAgICAgaHJicnRoZW1lcywgcmVhY3RhYmxlLA0KICAgICAgICAgICAgICAgbWFncml0dHIsICMgZm9yIDItd2F5IHBpcGUNCiAgICAgICAgICAgICAgIHRpZHlxdWFudCwgIyBmb3IgdGltZSBzZXJpZXMgZGF0YQ0KICAgICAgICAgICAgICAgcnZlc3QsICMgc3RvY2sgbmFtZXMNCiAgICAgICAgICAgICAgIGRhdGEudGFibGUsIERULCANCiAgICAgICAgICAgICAgIGgybywgcnNhbXBsZSwgcHJvcGhldCwgIyBtb2RlbCBidWlsZGluZw0KICAgICAgICAgICAgICAgZm9yZWNhc3QsIHRpbWV0aywgaW1wdXRlVFMsIGZwcDIsICMgdGltZSBzZXJpZXMgaGVscGVycw0KICAgICAgICAgICAgICAgZG9QYXJhbGxlbCwgZm9yZWFjaCwgZnVycnIpICMgcGFyYWxsZWxpemF0aW9uDQpgYGANCg0KIyBHZXQgRGF0YSAmIFByZS1wcm9jZXNzaW5nDQoNCiMjIFN0b2Nrcw0KDQpUbyBnZXQgdGhlIHN0b2NrIG5hbWVzIEkgd2lsbCBzY3JhcGUgdGhlIFMmUCA1MDAgV2lraXBlZGlhIHBhZ2UuDQoNCmBgYHtyIHNjcmFwaW5nfQ0KY29udGVudCA9IHJlYWRfaHRtbCgiaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3cvaW5kZXgucGhwP3RpdGxlPUxpc3Rfb2ZfUyUyNlBfNTAwX2NvbXBhbmllcyZvbGRpZD0xMDE0OTI0NzM2IikNCnRhYmxlcyA8LSBjb250ZW50ICU+JSBodG1sX3RhYmxlKGZpbGwgPSBUUlVFKQ0KdGFibGUgPC0gdGFibGVzW1sxXV0NCnNwX2xpc3QgPC0gdGFibGVbLTEsYygxLDcpXQ0KbmFtZXMoc3BfbGlzdClbMl09J0FkZGVkRGF0ZScNCmBgYA0KDQpUbyBlbnN1cmUgdGhlIHRpbWUgc2VyaWVzIGhhcyBubyBlbXB0eSB2YWx1ZXMsIEkgYW0gZmlsdGVyaW5nIHN0b2NrcyB3aGljaCB3ZXJlIGNyZWF0ZWQgYWZ0ZXIgMjAwOS4gQWxzbywgSSBhbSByZW1vdmluZyBCZXJrc2hpcmUgSGF0aGF3YXkgYmVjYXVzZSBpdCB3YXMgZ2l2aW5nIHByb2JsZW1zLiBBZnRlciwgSSB3aWxsIGtlZXAganVzdCA2MCBzdG9ja3MuDQoNCmBgYHtyIGZpbHRlciBzdG9ja2xpc3R9DQpzcF9saXN0ICU8PiUgZmlsdGVyKEFkZGVkRGF0ZSA8ICcyMDEwLTAxLTAxJykNCnNwX2xpc3QgPSBzcF9saXN0Wy00OSxdDQpzcF9saXN0ICU8PiUgZmlsdGVyKFN5bWJvbCAhPSAiQkYuQiIpIA0Kc3RvY2tfbGlzdCA9IHNwX2xpc3QkU3ltYm9sWzE6NjBdDQpgYGANCg0KQmVsb3cgSSBhbSBzZXR0aW5nIHRoZSBkYXRlIHJhbmdlcyBmb3IgdGhlIHRpbWUgc2VyaWVzLg0KDQpgYGB7ciBzZXQgZGF0ZXN9DQpiZWcgPSBhc19kYXRlKCIyMDE3LTAxLTAxIikNCmVuZCA9IGFzX2RhdGUoIjIwMjAtMDEtMDEiKQ0KYGBgDQoNCg0KYGBge3IsIHdhcm5pbmcgPSBGfQ0Kc3RvY2tzID0gdHFfZ2V0KHggPSBzdG9ja19saXN0LA0KICAgICAgICAgICAgICAgIGZyb20gPSBiZWcsDQogICAgICAgICAgICAgICAgdG8gPSBlbmQsIHBlcmlvZGljaXR5ID0gIndlZWtseSIpICMgZ2V0dGluZyB0aGUgdGltZSBzZXJpZXMgZGF0YQ0KDQpzdG9ja3MgJTw+JSBzZWxlY3Qoc3ltYm9sLCBkYXRlLCBhZGp1c3RlZCkNCmBgYA0KDQpgYGB7ciBzdW1tYXJ5c3RvY2tzfQ0KaGVhZChzdG9ja3MpDQpgYGANCg0KDQojIyBJbmRleGVzDQoNCk5vdywgSSBhbSBnZXR0aW5nIHRoZSBkYXRhIGZvciA1IHN0b2NrIG1hcmtldCBpbmRleGVzLg0KDQpgYGB7ciBpbmRleCBsaXN0fQ0KaW5kX2xpc3QgPSBjKCdeREpJJywnXk5ZQScsJ15HU1BDJywnXklYSUMnLCdeTjIyNScpDQppbmQgPSB0cV9nZXQoeCA9IGluZF9saXN0LCBmcm9tID0gIjIwMTUtMDEtMDEiLCB0byA9ICIyMDE4LTAxLTAxIiwgcGVyaW9kaWNpdHkgPSAid2Vla2x5IikgJT4lIHNlbGVjdChzeW1ib2wsZGF0ZSxhZGp1c3RlZCkNCmBgYA0KDQpgYGB7ciBzdW1tYXJ5aW5kZXh9DQpoZWFkKGluZCkNCmBgYA0KDQoNCiMjIENyeXB0bw0KDQpUbyBnZXQgdGhlIGNyeXB0b2N1cnJlbmN5IGRhdGEsIEkgYW0gdXNpbmcgdGhlIENyeXB0b0NvbXBhcmUgQVBJLiBGaXJzdCBJIG5lZWQgdG8gZGVmaW5lIHNvbWUgdmFyaWFibGVzIGZvciB0aGUgcXVlcnksIGluY2x1ZGVzIGhlIGNyeXB0b2N1cnJlbmNpZXMgSSB3YW50Lg0KDQpgYGB7ciBhcGl9DQpwYWNtYW46OnBfbG9hZChqc29ubGl0ZSkNCmJhc2UgPSAiaHR0cHM6Ly9taW4tYXBpLmNyeXB0b2NvbXBhcmUuY29tL2RhdGEvdjIvaGlzdG9kYXk/ZnN5bT0iDQoNCm5hbWUgPSBjKCJCVEMiLCJFVEgiLCJCTkIiLCJVU0RUIiwiU09MIiwiRE9UIiwiQURBIiwiWFJQIiwiRE9HRSIsIlNISUIiLCJMVEMiLCJXQlRDIiwiVU5JIiwiQlVTRCIsIk1BVElDIiwiTUFOQSIsIkFMR08iLCJMSU5LIiwiVEhFVEEiLCJTQU5EIiwiSE5UIiwiTUlPVEEiLCJDQUtFIiwiTUtSIiwiTkVPIiwiQlRUIiwiRUdMRCIsIlZFVCIsIklDUCIsIlhMTSIsIkFWQVgiLCJDUk8iLCJBVE9NIiwiTkVBUiIsIlJVTkUiKQ0KDQpjcnlwdG9fbGlzdCA9IG5hbWUNCg0KdCA9ICImdHN5bT1VU0QiDQoNCmFnZyA9ICImYWdncmVnYXRlPWRheXMiDQoNCmxpbSA9ICImbGltaXQ9MzYwIg0KDQprZXkgID0gIiZhcGlfa2V5PTdiZjQ0NDhmYzI4MTg3MDRlYWNlMGU0NzBlNGY4N2ViMTA4ZTkwZTc2MGJlZjU2MmYxZGY4MTY4NzYxNmIwNDAiDQpgYGANCg0KSGVyZSBJIGFtIHByZXBhcmluZyB0aGUgdXJscyB3aGljaCBJIHdpbGwgcXVlcnkgaW4gdGhlIG5leHQgY29kZSBjaHVuay4NCg0KYGBge3IgdXJsc30NCnVybHMgPSBjKHBhc3RlMChiYXNlLG5hbWVbMV0sdCxsaW0sa2V5KSxwYXN0ZTAoYmFzZSxuYW1lWzJdLHQsbGltLGtleSkscGFzdGUwKGJhc2UsbmFtZVszXSx0LGxpbSxrZXkpLA0KICAgICAgICAgcGFzdGUwKGJhc2UsbmFtZVs0XSx0LGxpbSxrZXkpLHBhc3RlMChiYXNlLG5hbWVbNV0sdCxsaW0sa2V5KSxwYXN0ZTAoYmFzZSxuYW1lWzZdLHQsbGltLGtleSksDQogICAgICAgICBwYXN0ZTAoYmFzZSxuYW1lWzddLHQsbGltLGtleSkscGFzdGUwKGJhc2UsbmFtZVs4XSx0LGxpbSxrZXkpLHBhc3RlMChiYXNlLG5hbWVbOV0sdCxsaW0sa2V5KSwNCiAgICAgICAgIHBhc3RlMChiYXNlLG5hbWVbMTBdLHQsbGltLGtleSkscGFzdGUwKGJhc2UsbmFtZVsxMV0sdCxsaW0sa2V5KSxwYXN0ZTAoYmFzZSxuYW1lWzEyXSx0LGxpbSxrZXkpLA0KICAgICAgICAgcGFzdGUwKGJhc2UsbmFtZVsxM10sdCxsaW0sa2V5KSxwYXN0ZTAoYmFzZSxuYW1lWzE0XSx0LGxpbSxrZXkpLHBhc3RlMChiYXNlLG5hbWVbMTVdLHQsbGltLGtleSksDQogICAgICAgICBwYXN0ZTAoYmFzZSxuYW1lWzE2XSx0LGxpbSxrZXkpLHBhc3RlMChiYXNlLG5hbWVbMTddLHQsbGltLGtleSkscGFzdGUwKGJhc2UsbmFtZVsxOF0sdCxsaW0sa2V5KSwNCiAgICAgICAgIHBhc3RlMChiYXNlLG5hbWVbMTldLHQsbGltLGtleSkscGFzdGUwKGJhc2UsbmFtZVsyMF0sdCxsaW0sa2V5KSxwYXN0ZTAoYmFzZSxuYW1lWzIxXSx0LGxpbSxrZXkpLA0KICAgICAgICAgcGFzdGUwKGJhc2UsbmFtZVsyMl0sdCxsaW0sa2V5KSxwYXN0ZTAoYmFzZSxuYW1lWzIzXSx0LGxpbSxrZXkpLHBhc3RlMChiYXNlLG5hbWVbMjRdLHQsbGltLGtleSksDQogICAgICAgICBwYXN0ZTAoYmFzZSxuYW1lWzI1XSx0LGxpbSxrZXkpLHBhc3RlMChiYXNlLG5hbWVbMjZdLHQsbGltLGtleSkscGFzdGUwKGJhc2UsbmFtZVsyN10sdCxsaW0sa2V5KSwNCiAgICAgICAgIHBhc3RlMChiYXNlLG5hbWVbMjhdLHQsbGltLGtleSkscGFzdGUwKGJhc2UsbmFtZVsyOV0sdCxsaW0sa2V5KSxwYXN0ZTAoYmFzZSxuYW1lWzMwXSx0LGxpbSxrZXkpLHBhc3RlMChiYXNlLG5hbWVbMzFdLHQsbGltLGtleSkscGFzdGUwKGJhc2UsbmFtZVszMl0sdCxsaW0sa2V5KSxwYXN0ZTAoYmFzZSxuYW1lWzMzXSx0LGxpbSxrZXkpLHBhc3RlMChiYXNlLG5hbWVbMzRdLHQsbGltLGtleSkscGFzdGUwKGJhc2UsbmFtZVszNV0sdCxsaW0sa2V5KQ0KKQ0KYGBgDQoNCk5vdyBJIHdpbGwgbWFrZSAzNSBBUEkgY2FsbHMgYXQgb25jZSBmb3IgdGhlIGRhdGEuDQoNCmBgYHtyIGNyeXB0byBleHRyYWN0IGxvb3AsIGV2YWwgPSBUfQ0KY3J5cHRvbGlzdCA9IG1hcCgueCA9IHVybHMsIC5mID0gZnJvbUpTT04pDQpgYGANCg0KSGVyZSBJIGNvbnZlcnQgdGhlIGRhdGEgdHlwZXMgc28gSSBjYW4gZm9ybWF0IHRoZW0gbmVhdGx5Lg0KDQpgYGB7ciB1bmxpc3QgY3J5cHRvLCBldmFsID0gVH0NCnVubGlzdF9jYyA9IGZ1bmN0aW9uKHgpew0KICB0ZW1wID0geCREYXRhJERhdGENCn0NCmNyeXB0byA9IG1hcF9kZigueCA9IGNyeXB0b2xpc3QsIC5mID0gdW5saXN0X2NjKQ0KYGBgDQoNClRoZSBsYXN0IHN0ZXAgZm9yIHRoZSBjcnlwdG8gZGF0YSBpcyB0byBmb3JtYXQgaXQgaW4gYSByZWFkYWJsZSBmb3JtYXQuDQoNCmBgYHtyIG9yZ2FuaXplIGNyeXB0b30NCmNyeXB0byAlPD4lIHNlbGVjdCh0aW1lLCBjbG9zZSkNCmNyeXB0byR0aW1lICU8PiUgYXNfZGF0ZXRpbWUoKSAlPiUgYXNfZGF0ZSgpDQpjcnlwdG8kc3ltYm9sID0gcmVwKG5hbWUsIGVhY2ggPSAobnJvdyhjcnlwdG8pL2xlbmd0aChjcnlwdG9fbGlzdCkpKSANCmNyeXB0byAlPD4lIHJlbmFtZShkYXRlID0gdGltZSkNCmNyeXB0byAlPD4lIHJlbG9jYXRlKHN5bWJvbCwuYmVmb3JlPWRhdGUpDQpgYGANCg0KYGBge3Igc3VtbWFyeWNyeXB0b30NCmhlYWQoY3J5cHRvKQ0KYGBgDQoNCkZvciB0aGUgc2FrZSBvZiBtYW5hZ2luZyBteSBSIGVudmlyb25tZW50LCBJIHJlbW92ZSBvYmplY3RzIHdoaWNoIGFyZSBubyBsb25nZXIgdXNlZnVsIGZvciB0aGUgdXBjb21pbmcgYW5hbHlzaXMuDQoNCmBgYHtyIHJlbW92ZSBvYmplY3RzIGZyb20gZW52LCBpbmNsdWRlID0gRn0NCnJtKGNvbnRlbnQsIGNyeXB0b2xpc3QsIHNwX2xpc3QsIHRhYmxlLCB0YWJsZXMsIGFnZywgYmFzZSwga2V5LCBsaW0sIG5hbWUsIHQsIHVybHMpDQpgYGANCg0KVGhlIGZpbmFsIHByZS1wcm9jZXNzaW5nIHN0ZXAgaXMgcmVuYW1pbmcgdGhlIHByaWNlIHZhcmlhYmxlIGZvciBjb25zaXN0ZW5jeS4NCg0KYGBge3IgcmVuYW1lIGFkanVzdGVkfQ0Kc3RvY2tzICU8PiUgcmVuYW1lKHByaWNlID0gYWRqdXN0ZWQpDQppbmQgJTw+JSByZW5hbWUocHJpY2UgPSBhZGp1c3RlZCkNCmNyeXB0byAlPD4lIHJlbmFtZShwcmljZSA9IGNsb3NlKQ0KYGBgDQoNCi0tLQ0KDQojIFJvbGxpbmcgT3JpZ2lucw0KDQpGb3IgdGltZSBzZXJpZXMgZGF0YSwgaXQgaXMgbm90IGFwcHJvcHJpYXRlIHRvIHNwbGl0IHRpbWUgc2VyaWVzIGRhdGEgaW50byBhIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHNldCBpbiB3aGljaCB0aGUgZmlyc3QgODAlIChvciBvdGhlciBhbW91bnQpIG9mIHRoZSBkYXRlcyBhcmUgdXNlZCBmb3IgdHJhaW5pbmcuIFJvbGxpbmcgb3JpZ2luIGV2YWx1YXRpb24gZml4ZXMgdGhpcyBieSBmaXJzdCBwYXJ0aXRpb25pbmcgdGhlIGRhdGEsIGJ1dCB2YWxpZGF0aW9uIGlzIGRvbmUgc2VxdWVudGlhbGx5IG9uIGVhY2ggZGF0ZSBzdGFydGluZyBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZSB2YWxpZGF0aW9uIHBhcnRpdGlvbi4gDQoNCiMjIEZ1bmN0aW9ucw0KDQpTaW5jZSBJIGFtIHdvcmtpbmcgd2l0aCAxMDAgdGltZSBzZXJpZXMsIEkgbXVzdCBhcHBseSBmdW5jdGlvbnMgdG8gZWFjaCBzdG9jay9jcnlwdG8uIFVuZm9ydHVuYXRlbHksIGFueSBhcHBseSBmdW5jdGlvbiB3aWxsIG5vdCB3b3JrIGZvciB0aGlzIHByb2plY3Qgc2luY2UgSSBtdXN0IGFnZ3JlZ2F0ZSByZXN1bHRzIGFuZCBydW4gb3RoZXIgZnVuY3Rpb25zIHdpdGhpbiB0aGUgbG9vcHMuIFRoZSBmaXJzdCBsb29wIGZ1bmN0aW9uIGlzIGEgcm9sbGluZyBvcmlnaW4gZnVuY3Rpb24gd2hpY2ggYWdncmVnYXRlcyByZXN1bHRzLg0KDQpgYGB7ciByb2xsaW5nb3JpZ2lucyBmdW59DQpyb2xsaW5nID0gZnVuY3Rpb24oZGF0LCBkYXRfbGlzdCwgaW5pdGlhbCl7DQogIA0KICByZXN1bHRzID0gZGF0YS5mcmFtZShzcGxpdHMgPSBjKCksIGlkID0gYygpKQ0KICANCiAgZm9yIChpIGluIGRhdF9saXN0KXsgDQogICAgdGVtcCA9IGZpbHRlcihkYXQsIHN5bWJvbCA9PSBpKQ0KICAgIHRlbXAgPSBuYV9pbnRlcnBvbGF0aW9uKHRlbXApDQogICAgdGVtcF9yZXMgPSByb2xsaW5nX29yaWdpbih0ZW1wLCBpbml0aWFsID0gd2hpY2godGVtcCRkYXRlID09IGFzLkRhdGUoaW5pdGlhbCkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXNzZXNzID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1bXVsYXRpdmUgPSBUUlVFKSANCiAgICByZXN1bHRzID0gcmJpbmQocmVzdWx0cywgdGVtcF9yZXMpDQogIH0NCiAgcmV0dXJuKHJlc3VsdHMpDQp9DQpgYGANCg0KSGVyZSB3ZSBleHRyYWN0IHRoZSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBkYXRhIGZvciB0aGUgbW9kZWxzLg0KDQpgYGB7ciBleHRyYWN0IGluZm99DQpnZXRpbmZvID0gZnVuY3Rpb24oZGYsIGRhdF9saXN0KXsNCiAgZGYgJTw+JSBtdXRhdGUoDQogIHN5bWJvbCA9IHJlcChkYXRfbGlzdCwgZWFjaCA9IChucm93KGRmKS9sZW5ndGgoZGF0X2xpc3QpKSksDQogIGRhdGEgPSBtYXAoLnggPSBzcGxpdHMsIC5mID0gYW5hbHlzaXMpLA0KICB0cmFpbkRhdGVzID0gbWFwKC54ID0gZGF0YSwgLmYgPSBleHRyYWN0MiwgJ2RhdGUnKSwNCiAgdHJhaW5QcmljZSA9ICBtYXAoLnggPSBkYXRhLCAuZiA9IGV4dHJhY3QyLCAncHJpY2UnKSwNCiAgdGFyZ2V0X2RhdGEgPSBtYXAoLnggPSBzcGxpdHMsIC5mID0gYXNzZXNzbWVudCksDQogIHRhcmdldERhdGUgPSBtYXAoLnggPSB0YXJnZXRfZGF0YSwgLmYgPSBleHRyYWN0MiwgJ2RhdGUnKSwNCiAgdGFyZ2V0UHJpY2UgPSAgbWFwX2RibCgueCA9IHRhcmdldF9kYXRhLCAuZiA9IGV4dHJhY3QyLCAncHJpY2UnKSkNCiAgcmV0dXJuKGRmKQ0KfQ0KYGBgDQoNClNpbmNlIEkgY3JlYXRlZCBmdW5jdGlvbnMgYWJvdmUsIG5vdyBhbGwgSSBoYXZlIHRvIGRvIGlzIHBhc3MgZWFjaCBncm91cCBvZiBkYXRhIChzdG9ja3MsIGluZGV4ZXMsIGNyeXB0bykgdGhyb3VnaCB0aGUgZnVuY3Rpb25zLg0KDQojIyBTdG9ja3MNCg0KYGBge3IgaW5mbyBmb3Igc3RvY2tzfQ0KaW5pdGlhbF9zID0gc3RvY2tzJGRhdGVbLjgqbnJvdyhzdG9ja3MpL2xlbmd0aChzdG9ja19saXN0KV0NCm5ld19zdG9ja3MgPSByb2xsaW5nKHN0b2Nrcywgc3RvY2tfbGlzdCwgaW5pdGlhbF9zKQ0KbmV3X3N0b2NrcyA9IGdldGluZm8obmV3X3N0b2Nrcywgc3RvY2tfbGlzdCkNCmBgYA0KDQpgYGB7cn0NCmhlYWQobmV3X3N0b2NrcykNCmBgYA0KDQoNCiMjIEluZGV4ZXMNCg0KYGBge3IgaW5mbyBmb3IgaW5kZXh9DQppbml0aWFsX2kgPSBpbmQkZGF0ZVsuOCpucm93KGluZCkvbGVuZ3RoKGluZF9saXN0KV0NCm5ld19pbmRleCA9IHJvbGxpbmcoaW5kLCBpbmRfbGlzdCwgaW5pdGlhbF9pKQ0KbmV3X2luZGV4ID0gZ2V0aW5mbyhuZXdfaW5kZXgsIGluZF9saXN0KQ0KYGBgDQoNCmBgYHtyfQ0KaGVhZChuZXdfaW5kZXgpDQpgYGANCg0KIyMgQ3J5cHRvDQoNCmBgYHtyIGluZm8gZm9yIGNyeXB0b30NCmluaXRpYWxfYyA9IGNyeXB0byRkYXRlWy44Km5yb3coY3J5cHRvKS9sZW5ndGgoY3J5cHRvX2xpc3QpXQ0KbmV3X2NyeXB0byA9IHJvbGxpbmcoY3J5cHRvLCBjcnlwdG9fbGlzdCwgaW5pdGlhbF9jKQ0KbmV3X2NyeXB0byA9IGdldGluZm8obmV3X2NyeXB0bywgY3J5cHRvX2xpc3QpDQpgYGANCg0KYGBge3J9DQpoZWFkKG5ld19jcnlwdG8pDQpgYGANCg0KLS0tDQoNCiMgRm9yZWNhc3RpbmcNCg0KSW4gdGhpcyBzZWN0aW9uLCBJIHdpbGwgYXBwbHkgbmFpdmUsIGhvbHQtd2ludGVycywgYXJpbWEsIGF1dG9NTCwgYW5kIHByb3BoZXQgZm9yZWNhc3RzIHRvIGVhY2ggdGltZSBzZXJpZXMuIExhdGVyIEkgd2lsbCBjb21wYXJlIHRoZSByZXN1bHRzIG9mIGVhY2ggbW9kZWwuDQoNCiMjIE5haXZlLCBTRVMsIGFuZCBBUklNQQ0KDQojIyMgRnVuY3Rpb24NCg0KVGhpcyBmdW5jdGlvbiBhcHBsaWVzIG5haXZlLCBob2x0LXdpbnRlcnMsIGFuZCBhcmltYSBmb3JlY2FzdHMgdG8gYWxsIHRoZSB0aW1lIHNlcmllcy4gSSBleHBsYWluIGxhdGVyIHdoeSB3ZSBjYW5ub3QgYXBwbHkgdGhpcyBzaW1wbHkgdG8gdGhlIG90aGVyIHR3byBtb2RlbHMuDQoNCmBgYHtyIE5IQX0NCk5IQSA9IGZ1bmN0aW9uKGRmKXsNCiAgcGxhbihtdWx0aXByb2Nlc3MpDQogIGRmICU8PiUgbXV0YXRlKA0KICBOYWl2ZSA9IGZ1dHVyZV9tYXAoLnggPSB0cmFpblByaWNlLCAuZiA9IG5haXZlLCBoID0gMSkgJT4lIA0KICAgIG1hcF9kYmwoLmYgPSBleHRyYWN0MiwgIm1lYW4iKSwNCiAgSG9sdCA9IGZ1dHVyZV9tYXAoLnggPSB0cmFpblByaWNlLCAuZiA9IGhvbHQsIGggPSAxKSAlPiUgDQogICAgbWFwX2RibCguZiA9IGV4dHJhY3QyLCAibWVhbiIpLA0KICBBcmltYSA9IGZ1dHVyZV9tYXAoLnggPSB0cmFpblByaWNlLCAuZiA9IGF1dG8uYXJpbWEpICU+JSANCiAgICBtYXAoLmYgPSBmb3JlY2FzdCwgaCA9IDEpICU+JSANCiAgICBtYXBfZGJsKC5mID0gZXh0cmFjdDIsICJtZWFuIikNCikNCiAgcmV0dXJuKGRmKQ0KfQ0KYGBgDQoNCiMjIyBTdG9ja3MNCg0KYGBge3Igc3RvY2tzIE5IQX0NCm5ld19zdG9ja3MgPSBOSEEobmV3X3N0b2NrcykNCmBgYA0KDQojIyMgSW5kZXhlcw0KDQpgYGB7ciBpbmRleGVzIE5IQX0NCm5ld19pbmRleCA9IE5IQShuZXdfaW5kZXgpDQpgYGANCg0KIyMjIENyeXB0bw0KDQpgYGB7ciBjcnlwdG8gTkhBfQ0KbmV3X2NyeXB0byA9IE5IQShuZXdfY3J5cHRvKQ0KYGBgDQoNCiMjIFByb3BoZXQNCg0KRm9yIHNvbWUgb2RkIHJlYXNvbiwgdGhlIHByb3BoZXQgZG9jdW1lbnRhdGlvbiBzdGF0ZXMgdGhhdCBpbiBvcmRlciB0byB1c2UgdGhlIG1vZGVsLCB0aGUgZGF0ZXMgaW4gdGhlIHRpbWUgc2VyaWVzIG11c3QgYmUgbmFtZWQgImRzIiBhbmQgdGhlIGFjdHVhbCB2YWx1ZXMgbXVzdCBiZSBuYW1lZCAieSIuIElmIHlvdSB1c2UgYW55IG90aGVyIGxhYmVsaW5nLCBSIHRocm93cyBlcnJvcnMuIEJlY2F1c2Ugb2YgdGhpcywgSSBoYWQgdG8gY3JlYXRlIGEgZnVuY3Rpb24gd2hpY2ggY29udmVydHMgdGhlIGNvbHVtbnMgbmFtZXMgYW5kIHRoZW4gYXBwbGllcyB0aGUgcHJvcGhldCBmdW5jdGlvbi4NCg0KIyMjIEZ1bmN0aW9uDQoNCmBgYHtyIHByb3BoZXRsb29wfQ0KcHJvcGhldGxvb3AgPSBmdW5jdGlvbihuZil7DQogIHByb3BoZXRmdW4gPSBmdW5jdGlvbih5LCBkYXRlcywgdGFyZ2V0RGF0ZSl7DQogICAgcGFjbWFuOjpwX2xvYWQodGlkeXZlcnNlLCBtYWdyaXR0ciwgcHJvcGhldCkNCiAgICBkZiA9IGFzLmRhdGEuZnJhbWUoY2JpbmQoZGF0ZXMsIHkpKSAlPiUgDQogICAgICByZW5hbWUoZHM9MSx5PTIpDQogICAgZGYgPSB1bm5lc3QoZGYsIGNvbHMgPSBjKGRzLHkpKQ0KICAgIG1vZGVsID0gcHJvcGhldChkZiwgeWVhcmx5LnNlYXNvbmFsaXR5ID0gImF1dG8iLA0KICAgICAgICAgICAgICAgICAgICB3ZWVrbHkuc2Vhc29uYWxpdHkgPSAiYXV0byIpDQogICAgdGFyZ2V0RGF0ZSAlPD4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogICAgICByZW5hbWUoZHM9MSkNCiAgICBwcmVkID0gcHJlZGljdChtb2RlbCwgdGFyZ2V0RGF0ZSkNCiAgICByZXMgPSBwcmVkJHloYXQNCiAgICByZXR1cm4ocmVzKQ0KICB9DQogIA0KICBpID0gMQ0KICANCiAgZm9yIChpIGluIDE6bnJvdyhuZikpew0KICAgIHZhbCA9IHByb3BoZXRmdW4oeSA9IG5mW2ksXSR0cmFpblByaWNlLCBkYXRlcyA9IG5mW2ksXSR0cmFpbkRhdGVzLCANCiAgICAgICAgICAgICAgICB0YXJnZXREYXRlID0gbmZbaSxdJHRhcmdldERhdGUpDQogICAgbmZbaSxdJFByb3BoZXQgPSB2YWwNCiAgICBpID0gaSArIDENCiAgfQ0KICByZXR1cm4obmYpDQp9DQpgYGANCg0KSGVyZSBJIHNpbXBseSBhcHBseSB0aGUgY3VzdG9tIHByb3BoZXQgZnVuY3Rpb24gdG8gdGhlIHRocmVlIHNldHMgb2YgdGltZSBzZXJpZXMuDQoNCiMjIyBTdG9ja3MNCg0KYGBge3Igc3RvY2tzIHByb3BoZXRsb29wfQ0KbmV3X3N0b2Nrc1snUHJvcGhldCddID0gTkENCm5ld19zdG9ja3MgPSBwcm9waGV0bG9vcChuZXdfc3RvY2tzKQ0KYGBgDQoNCg0KIyMjIEluZGV4ZXMNCg0KYGBge3IgaW5kZXggcHJvcGhldGxvb3B9DQpuZXdfaW5kZXhbJ1Byb3BoZXQnXSA9IE5BDQpuZXdfaW5kZXggPSBwcm9waGV0bG9vcChuZXdfaW5kZXgpDQpgYGANCg0KIyMjIENyeXB0bw0KDQpgYGB7ciBjcnlwdG8gcHJvcGhldGxvb3B9DQpuZXdfY3J5cHRvWydQcm9waGV0J10gPSBOQQ0KbmV3X2NyeXB0byA9IHByb3BoZXRsb29wKG5ld19jcnlwdG8pDQpgYGANCg0KDQoNCmBgYHtyLCBldmFsID0gRiwgaW5jbHVkZT1GfQ0Kc2F2ZVJEUyhuZXdfc3RvY2tzLCAnbmV3X3N0b2Nrcy5yZHMnKQ0Kc2F2ZVJEUyhuZXdfaW5kZXgsICduZXdfaW5kZXgucmRzJykNCnNhdmVSRFMobmV3X2NyeXB0bywgJ25ld19jcnlwdG8ucmRzJykNCmBgYA0KDQoNCiMjIEF1dG9NTCBGdW5jdGlvbiAoQXBwbGllZCBTZXBhcmF0ZWx5KQ0KDQpBdXRvTUwgaXMgZWFzaWx5IHRoZSBoYXJkZXN0IG1vZGVsIHRvIGdldCBydW5uaW5nIGZvciBteSBtYW55IHRpbWUgc2VyaWVzLiBUaGlzIGlzIGJlY2F1c2UgYXV0b01MIGRvZXMgbm90IGxpa2UgdG8gbG9vcCBtb3JlIHRoYW4gMzAgdGltZXMgaW4gYSBzaW5nbGUgaDJvIGNvbm5lY3Rpb24uIEFzIGEgcmVzdWx0LCBJIGFwcGxpZWQgdGhpcyBmdW5jdGlvbiBpbiBhIHNlcGFyYXRlIHIgZmlsZSBmb3VyIHRpbWVzIChmb3IgYSB0b3RhbCBvZiAxMDAgdGltZSBzZXJpZXMpLiBJIHRoZW4gYXBwZW5kZWQgYWxsIHRoZSBkYXRhIGFuZCBicm91Z2h0IGl0IGJhY2sgaW50byB0aGlzIGZpbGUuDQoNCkhlcmUgaXMgdGhlIGZ1bmN0aW9uIHdoaWNoIGNyZWF0ZXMgbmV3IHZhcmlhYmxlcyBmcm9tIGp1c3QgdGhlIHRpbWUgc2VyaWVzIGRhdGEgaXRzZWxmIChubyBleHRlcm5hbCBkYXRhKSBzbyB0aGF0IHRoZSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyBoYXZlIG1vcmUgZGF0YSBmb3IgcHJlZGljdGlvbi4NCg0KYGBge3IgZmVhdHVyZXMsIGV2YWwgPSBGfQ0KZmVhdF9kZiA9IHRrX2dldF90aW1lc2VyaWVzX3NpZ25hdHVyZSh1bmlxdWUoZGF0YSRkYXRlKSkgJT4lIA0KICAgIG5hLm9taXQoKQ0KbnVtcyA9IHVubGlzdChsYXBwbHkoZmVhdF9kZiwgaXMubnVtZXJpYykpDQpmZWF0X2RmID0gZmVhdF9kZlssbnVtc10NCmZlYXRfZGYgPSBmZWF0X2RmW2MoVFJVRSwgbGFwcGx5KGZlYXRfZGZbLTFdLCB2YXIsIG5hLnJtID0gVFJVRSkgIT0gMCldDQpgYGANCg0KU2luY2UgYXV0b01MIHJlbGllcyBvbiBhbiBBUEksIEkgaGFkIHRvIGZpcnN0IGluaXRpYXRlIGF1dG9NTCBzZXNzaW9uIGFuZCB0aGVuIGNvbnZlcnQgbXkgZGF0YSB0byBtZWV0IHRoZSBhdXRvTUwgbW9kZWwgaW5wdXQgcmVxdWlyZW1lbnRzLiBGcm9tIHRoZXJlLCBJIGNvbXBpbGUgdGhlIG1vZGVsIG1ldHJpY3MuDQoNCmBgYHtyIGF1dG9tbCBmdW59DQphdXRvbWwgPSBmdW5jdGlvbihkYXRhLCBkYXRhX2xpc3QsIG1pbnV0ZXMpew0KICANCiAgZmVhdF9kZiA9IHRrX2dldF90aW1lc2VyaWVzX3NpZ25hdHVyZSh1bmlxdWUoZGF0YSRkYXRlKSkgJT4lIA0KICAgIG5hLm9taXQoKQ0KICBudW1zID0gdW5saXN0KGxhcHBseShmZWF0X2RmLCBpcy5udW1lcmljKSkNCiAgZmVhdF9kZiA9IGZlYXRfZGZbLG51bXNdDQogIGZlYXRfZGYgPSBmZWF0X2RmW2MoVFJVRSwgbGFwcGx5KGZlYXRfZGZbLTFdLCB2YXIsIG5hLnJtID0gVFJVRSkgIT0gMCldDQogIGZlYXRfZGYgPSBjYmluZCh1bmlxdWUoZGF0YSRkYXRlKVstMV0sIGZlYXRfZGYpICU+JSByZW5hbWUoaW5kZXg9MSkNCiAgZmVhdF9kZiAlPD4lIHNlbGVjdCgteWVhci5pc28pDQogIA0KICByZXN1bHRzID0gZGF0YS5mcmFtZShTeW1ib2wgPSBjKCksIE1TRSA9IGMoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgTUFFID0gYygpLCBSTVNFID0gYygpKQ0KICANCiAgaDJvLmluaXQoKQ0KICANCiAgZm9yIChpIGluIGRhdGFfbGlzdCl7DQogICAgZGYgPSBmaWx0ZXIoZGF0YSwgc3ltYm9sID09IGkpDQogICAgZGYgPSBtZXJnZShkZiwgZmVhdF9kZiwgYnkueCA9ICJkYXRlIiwgYnkueSA9ICJpbmRleCIpDQogICAgZGYgJTw+JSBzZWxlY3QoLWRhdGUpDQogICAgZGYgJTw+JSBtdXRhdGUobGFnMSA9IGxhZyhwcmljZSksIGxhZzIgPSBsYWcocHJpY2UsIGsgPSAyKSwNCiAgICAgICAgICAgICAgICAgICBsYWczID0gbGFnKHByaWNlLCBrID0gMykpDQogICAgdHJhaW5kZiA9IGRmWzE6Zmxvb3IoLjgqbnJvdyhkZikpLF0gJT4lIGFzLmgybygpDQogICAgdmFsaWRkZiA9IGRmW2NlaWxpbmcoLjgqbnJvdyhkZikpOmZsb29yKG5yb3coZGYpKi45KSxdICU+JSBhcy5oMm8oKQ0KICAgIHRlc3RkZiA9IGRmW2NlaWxpbmcobnJvdyhkZikqLjkpOm5yb3coZGYpLF0gJT4lIGFzLmgybygpDQogICAgDQogICAgeCA9IDM6KG5jb2woZGYpKQ0KICAgIA0KICAgIGgyby5pbml0KCkNCiAgICANCiAgICBtb2RlbCA9IGgyby5hdXRvbWwoeCA9IHgsIHkgPSAncHJpY2UnLA0KICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluZGYsDQogICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB2YWxpZGRmLA0KICAgICAgICAgICAgICAgICAgICAgICBsZWFkZXJib2FyZF9mcmFtZSA9IHRlc3RkZiwNCiAgICAgICAgICAgICAgICAgICAgICAgbmZvbGRzID0gNSwNCiAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfbWV0cmljID0gIkFVVE8iLA0KICAgICAgICAgICAgICAgICAgICAgICBtYXhfcnVudGltZV9zZWNzID0gKG1pbnV0ZXMgKiA2MCkpDQogICAgDQogICAgYmVzdCA9IG1vZGVsQGxlYWRlcg0KICAgIA0KICAgIGF1dG9NTF9wcmVkID0gaDJvLnByZWRpY3QoYmVzdCwgbmV3ZGF0YSA9IHRlc3RkZikNCiAgICBlcnJvciA9IGgyby5wZXJmb3JtYW5jZShiZXN0LCBuZXdkYXRhID0gdGVzdGRmKQ0KICAgIE1TRSA9IGVycm9yQG1ldHJpY3MkTVNFDQogICAgUk1TRSA9IGVycm9yQG1ldHJpY3MkUk1TRQ0KICAgIE1BRSA9IGVycm9yQG1ldHJpY3MkbWFlDQogICAgDQogICAgcmVzID0gY2JpbmQoaSwgTVNFLCBSTVNFLCBNQUUpDQogICAgcmVzdWx0cyA9IHJiaW5kKHJlc3VsdHMsIHJlcykNCiAgfSANCiAgDQogIHJlc3VsdHMgJTw+JSByZW5hbWUoU3ltYm9sID0gaSkNCiAgcmV0dXJuKHJlc3VsdHMpDQp9DQpgYGANCg0KIyMgU3RvY2tzDQoNCmBgYHtyIGF1dG9tbCBzdG9ja3MsIGV2YWwgPSBGLCBtZXNzYWdlID0gRn0NCmF1dG9NTF9zdG9ja3MgPSBhdXRvbWwoc3RvY2tzLCBzdG9ja19saXN0LCAuNSkNCmBgYA0KDQojIyBJbmRleA0KDQpgYGB7ciBhdXRvbWwgaW5kZXgsIGV2YWwgPSBGfQ0KYXV0b01MX2luZGV4ID0gYXV0b21sKGluZCwgaW5kX2xpc3QsIDEpDQpgYGANCg0KIyMgQ3J5cHRvDQoNCmBgYHtyIGF1dG9tbCBjcnlwdG8sIGV2YWwgPSBGfQ0KYXV0b01MX2NyeXB0byA9IGF1dG9tbChjcnlwdG8sIGNyeXB0b19saXN0LCAuNSkNCmBgYA0KDQoNCg0KIyMgRnVuY3Rpb24NCg0KQmVsb3cgSSBjYWxjdWxhdGUgdGhlIGVycm9yIGZvciBhbGwgbW9kZWxzIGV4Y2VwdCBhdXRvTUwgKHRoYXQgd2FzIGRvbmUgd2l0aGluIHRoZSBhdXRvTUwgY3VzdG9tIGZ1bmN0aW9uKSBpbiBvbmUgZnVuY3Rpb24uIA0KDQpgYGB7ciBlcnJvcnN9DQpyZXMxID0gZnVuY3Rpb24oZGYpew0KICByZXN1bHRzID0gIGRmICU+JSBtdXRhdGUoDQogIGVycm9yTmFpdmUgPSB0YXJnZXRQcmljZSAtIE5haXZlLA0KICBwZU5haXZlID0gZXJyb3JOYWl2ZSAvIHRhcmdldFByaWNlLA0KICBlcnJvckhvbHQgPSB0YXJnZXRQcmljZSAtIEhvbHQsDQogIHBlSG9sdCA9IGVycm9ySG9sdCAvIHRhcmdldFByaWNlLA0KICBlcnJvckFyaW1hID0gdGFyZ2V0UHJpY2UgLSBBcmltYSwNCiAgcGVBcmltYSA9IGVycm9yQXJpbWEgLyB0YXJnZXRQcmljZSwNCiAgZXJyb3JQcm9waGV0ID0gdGFyZ2V0UHJpY2UgLSBQcm9waGV0LA0KICBwZVByb3BoZXQgPSBlcnJvclByb3BoZXQgLyB0YXJnZXRQcmljZQ0KKQ0KICByZXN1bHRzICU8PiUgc2VsZWN0KHR5cGUsIHN5bWJvbCwgZXJyb3JOYWl2ZSwgcGVOYWl2ZSwgZXJyb3JIb2x0LCBwZUhvbHQsIGVycm9yQXJpbWEsIHBlQXJpbWEsIGVycm9yUHJvcGhldCwgcGVQcm9waGV0KQ0KICANCiAgcmV0dXJuKHJlc3VsdHMpDQp9DQpgYGANCg0KVGhpcyBjaHVuayBtZXJnZXMgdGhlIGVycm9yIGRhdGFmcmFtZSBjb21wbGV0ZWQgaW4gdGhlIGxhc3Qgc3RlcCB3aXRoIGFsbCB0aGUgc3RvY2svY3J5cHRvIG5hbWVzLg0KDQpgYGB7ciBjbGVhbiBlcnJvciBmcmFtZX0NCnJlc19uaGFwID0gcmJpbmQobmV3X3N0b2NrcywgbmV3X2luZGV4LCBuZXdfY3J5cHRvKQ0KcmVzX25oYXAkdHlwZSA9IGMocmVwKCdTdG9jaycsbnJvdyhuZXdfc3RvY2tzKSksIHJlcCgnSW5kZXgnLG5yb3cobmV3X2luZGV4KSksIHJlcCgnQ3J5cHRvJyxucm93KG5ld19jcnlwdG8pKSkNCnJlc19uaGFwID0gcmVzMShyZXNfbmhhcCkNCmBgYA0KDQoNCi0tLQ0KDQojIFJlc3VsdHMgZm9yIE5haXZlLCBIb2x0LCBBcmltYSwgYW5kIFByb3BoZXQNCg0KVG8gY29tcGFyZSB0aGUgcmVzdWx0cyBmb3IgdGhlIG1vZGVscyAoZXhjbHVkaW5nIGF1dG9NTCksIEkgY2FsY3VsYXRlIHRoZSBtZWFuIGFic29sdXRlIGVycm9yIChNQUUpLCBtZWFuIGFic29sdXRlIHBlcmNlbnQgZXJyb3IgKE1BUEUpLCBhbmQgcm9vdCBtZWFuIHNxdWFyZSBlcnJvciAoUk1TRSkuDQoNCmBgYHtyIGFjY3VyYWN5fQ0Kb3B0aW9ucyhzY2lwZW49OTk5KQ0KcmVzdWx0c19uaGFwID0gcmVzX25oYXAgJT4lICBncm91cF9ieShzeW1ib2wpICU+JSBtdXRhdGUoDQogIE1BRV9OYWl2ZSA9IG1lYW4oYWJzKGVycm9yTmFpdmUpKSwNCiAgTUFQRV9OYWl2ZSA9IG1lYW4oYWJzKHBlTmFpdmUpKSoxMDAsDQogIFJNU0VfTmFpdmUgPSBzcXJ0KG1lYW4oZXJyb3JOYWl2ZV4yKSksDQogIE1BRV9Ib2x0ID0gbWVhbihhYnMoZXJyb3JIb2x0KSksDQogIE1BUEVfSG9sdCA9IG1lYW4oYWJzKHBlSG9sdCkpKjEwMCwNCiAgUk1TRV9Ib2x0ID0gc3FydChtZWFuKGVycm9ySG9sdF4yKSksDQogIE1BRV9BcmltYSA9IG1lYW4oYWJzKGVycm9yQXJpbWEpKSwNCiAgTUFQRV9BcmltYSA9IG1lYW4oYWJzKHBlQXJpbWEpKSoxMDAsDQogIFJNU0VfQXJpbWEgPSBzcXJ0KG1lYW4oZXJyb3JBcmltYV4yKSksDQogIE1BRV9Qcm9waGV0ID0gbWVhbihhYnMoZXJyb3JQcm9waGV0KSksDQogIE1BUEVfUHJvcGhldCA9IG1lYW4oYWJzKHBlUHJvcGhldCkpKjEwMCwNCiAgUk1TRV9Qcm9waGV0ID0gc3FydChtZWFuKGVycm9yUHJvcGhldF4yKSkNCiAgKSAlPiUgc2VsZWN0KGMoMSwyLDExOjIyKSkgJT4lIHVuaXF1ZSgpDQpgYGANCg0KLS0tDQoNCiMgQWdncmVnYXRpbmcgUmVzdWx0cw0KDQpGb3IgYXBwbGVzLXRvLWFwcGxlcyBjb21wYXJpc29uIGZvciBhbGwgbW9kZWxzLCBJIHdpbGwgdXNlIFJNU0UsIHNpbmNlIGF1dG9NTCBkb2VzIG5vdCBjYWxjdWxhdGUgTUFQRS4NCg0KYGBge3IgYWdncmVnYXRlfQ0KYXV0b01MX1Jlc3VsdHMgPSByZWFkUkRTKCdhdXRvTUxfUmVzdWx0cy5yZHMnKSAlPiUgc2VsZWN0KC10eXBlLC1NU0UpICU+JSByZW5hbWUoUk1TRV9hdXRvTUw9Uk1TRSwgTUFFX2F1dG9NTD1NQUUpDQphdXRvTUxfUmVzdWx0cyRSTVNFX2F1dG9NTCAlPD4lIGFzLm51bWVyaWMoKQ0KYXV0b01MX1Jlc3VsdHMkTUFFX2F1dG9NTCAlPD4lIGFzLm51bWVyaWMoKQ0KZmluYWxfcmVzdWx0cyA9IG1lcmdlKHJlc3VsdHNfbmhhcCwgYXV0b01MX1Jlc3VsdHMsIGJ5LnggPSAnc3ltYm9sJywgYnkueSA9ICdTeW1ib2wnKSAlPiUgbXV0YXRlKGFjcm9zcyhpcy5udW1lcmljLCBiYXNlOjpyb3VuZCwgZGlnaXRzPTMpKQ0KYGBgDQoNCg0KIyMgRGV0YWlsZWQgVmlldw0KDQpIZXJlIGlzIGEgY2hhcnQgd2l0aCBhbGwgYWNjdXJhY3kgbWV0cmljcyB3aXRoIHRoZWlyIHJlc3BlY3RpdmUgc3RvY2svY3J5cHRvY3VycmVuY3kuDQoNCmBgYHtyIHRhYmxlfQ0KaGVhZChmaW5hbF9yZXN1bHRzKQ0KYGBgDQoNCg0KDQpgYGB7ciBSTVNFfQ0KIyBBbGwgUk1TRXMNClJNU0UgPSBmaW5hbF9yZXN1bHRzICU+JSBzZWxlY3Qoc3ltYm9sLHR5cGUsc3RhcnRzX3dpdGgoJ1JNU0UnKSkgJT4lICBwaXZvdF9sb25nZXIoMzo3KSAlPiUgcmVuYW1lKFJNU0UgPSB2YWx1ZSkNClJNU0UkbmFtZSAlPD4lIHN0cl9yZXBsYWNlX2FsbCgiUk1TRV8iLCIiKQ0KYGBgDQoNCg0KQmVsb3cgYXJlIHNvbWUgZGF0YSB2aXN1YWxpemF0aW9ucyBjb21wYXJpbmcgdGhlIGFjY3VyYWN5IG9mIHRoZSBtb2RlbHMuDQoNCg0KYGBge3IgYmFycGxvdH0NClJNU0UgJT4lICBncm91cF9ieShuYW1lKSAlPiUgc3VtbWFyaXplKFJNU0U9bWVhbihSTVNFKSkgJT4lIGFycmFuZ2UoUk1TRSkgJT4lIGdncGxvdChhZXMoeCA9IHJlb3JkZXIobmFtZSwtUk1TRSksIHkgPSBSTVNFLCBmaWxsID0gbmFtZSkpICsgZ2VvbV9jb2woKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgKyBjb29yZF9mbGlwKCkgKyB4bGFiKCdGb3JlY2FzdGluZyBNZXRob2QnKSArIGdndGl0bGUoJ0F2ZXJhZ2UgRXJyb3IgYnkgRm9yZWNhc3RpbmcgTWV0aG9kJykgKyBsYWJzKGNhcHRpb24gPSAiUk1TRSB1c2VkIGJlY2F1c2UgTUFQRSB3YXMgbm90IGF2YWlsYWJsZSBmb3IgYXV0b01MIikgKyBnZW9tX3RleHQoYWVzKGxhYmVsPWJhc2U6OnJvdW5kKFJNU0UsMykpLCBudWRnZV95ID0gMjApICsgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KVGhlIEFyaW1hLCBOYWl2ZSwgYW5kIEhvbHQgZm9yZWNhc3RzIGFyZSBlc3NlbnRpYWxseSBhbGwgdGhlIHNhbWUgKHdpdGggdGhlIGRpZmZlcmVuY2UgbGlrZWx5IGJlaW5nIGR1ZSB0byB0aGUgc2FtcGxlIHNpemUgb2YgMTAwKS4gSSBjb25jbHVkZSB0aGF0IHNpbmNlIG5vIGZvcmVjYXN0IGNhbiBpbXByb3ZlIHVwb24gdGhlIG5haXZlIGZvcmVjYXN0LCBub25lIG9mIHRoZXNlIG1ldGhvZHMgKHdpdGggbXkgc2VsZWN0ZWQgcGFyYW1ldGVycykgYXJlIGFwcHJvcHJpYXRlIGZvciBwcmVkaWN0aW5nIGZ1dHVyZSB2YWx1ZXMgb2Ygc3RvY2tzIGFuZCBjcnlwdG9jdXJyZW5jaWVzLg0KDQpJdCBpcyBsaWtlbHkgdGhhdCBhdXRvTUwgb3IgUHJvcGhldCBjb3VsZCBiZSBpbXByb3ZlZCBieSBmZWVkaW5nIG1vcmUgdmFyaWFibGVzIChzb21lIG9mIHdoaWNoIG1pZ2h0IHN1cHBseSB1c2VmdWwgaW5mb3JtYXRpb24pIG9yIGFkanVzdGluZyBtb2RlbCBwYXJhbWV0ZXJzLg0KDQoNCmBgYHtyIGJveHBsb3R9DQpSTVNFICU+JSBnZ3Bsb3QoYWVzKHggPSBuYW1lLCB5ID0gUk1TRSwgZmlsbCA9IG5hbWUpKSArIGdlb21fYm94cGxvdCgpICArIHRoZW1lX21pbmltYWwoKSArIGNvb3JkX2ZsaXAoKSArIHlsaW0oMCw0MCkgKyBzY2FsZV9maWxsX3ZpcmlkaXNfZCgpICsgZ2d0aXRsZSgnRXJyb3IgRGlzdHJpYnV0aW9uIGJ5IE1vZGVsJykgKyB4bGFiKCdNb2RlbCcpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KYGBgDQoNCmBgYHtyIHZpb2xpbnBsb3R9DQpSTVNFICU+JSBnZ3Bsb3QoYWVzKHggPSB0eXBlLCB5ID0gUk1TRSwgZmlsbCA9IHR5cGUpKSArIGdlb21fdmlvbGluKCkgKyBjb29yZF9mbGlwKCkgKyB5bGltKDAsNDApICsgc2NhbGVfZmlsbF92aXJpZGlzX2QoYmVnaW4gPSAuMikgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgeGxhYignVGltZSBTZXJpZXMgVHlwZScpICsgZ2d0aXRsZSgnRXJyb3IgRGlzdHJpYnV0aW9uIGJ5IFR5cGUgb2YgVGltZSBTZXJpZXMnKSArIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCi0tLQ0KDQojIFdpbm5lcnMNCg0KTm90ZTogVGhlcmUgd2VyZSBhIGZldyB0aWVzIGJldHdlZW4gbG93ZXN0IFJNU0VzIGZvciBhIGZldyBzdG9ja3MuIEVhY2ggdGllIHdhcyBiZXR3ZWVuIEFyaW1hIGFuZCBOYWl2ZS4gRWFjaCB0aWUgd2lsbCBzdGlsbCBiZSBjb3VudGVkIGFzICsxIGZvciBib3RoLiANCg0KYGBge3J9DQp3aW5kZiA9IFJNU0UgJT4lIGdyb3VwX2J5KHN5bWJvbCkgJT4lIHN1bW1hcml6ZShSTVNFID0gbWluKFJNU0UpKQ0Kd2lubmVycyA9IG1lcmdlKHdpbmRmLCBSTVNFLCBieS54ID0gYygnc3ltYm9sJywnUk1TRScpLCBieS55ID0gYygnc3ltYm9sJywnUk1TRScpKSAlPiUgc2VsZWN0KG5hbWUpDQp3aW5uZXJzX3N1bW1hcnkgPSB3aW5uZXJzICU+JSBjb3VudCh3aW5uZXJzJG5hbWUpICU+JSByZW5hbWUoTW9kZWw9MSxOdW1iZXJXaW5zPTIpDQpgYGANCg0KYGBge3J9DQp3aW5uZXJzX3N1bW1hcnkgJT4lIGdncGxvdChhZXMoeD1yZW9yZGVyKE1vZGVsLE51bWJlcldpbnMpLHk9TnVtYmVyV2lucyxmaWxsPU1vZGVsKSkgKyBnZW9tX2NvbCgpICsgc2NhbGVfZmlsbF92aXJpZGlzX2QoKSArIGNvb3JkX2ZsaXAoKSArIHRoZW1lX21pbmltYWwoKSArIGdndGl0bGUoJ051bWJlciBvZiBXaW5zIGZvciBFYWNoIE1vZGVsJykgKyB4bGFiKCdNb2RlbCcpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIGdlb21fdGV4dChhZXMobGFiZWw9TnVtYmVyV2lucyksIG51ZGdlX3kgPSAyKQ0KYGBgDQoNCg==