Overview
The following article builds on earlier codes from my RPubs corner and aims to replicate
standard baseline portfolio research. By doing so, I hope the following
initiative will encourage future reproducible research in portfolio
selection. Specifically, the article builds on the empirical design
conducted by Kan, Wang, and Zhou (2022), which is considered one of the
typical frameworks used in the literature. For brevity, I refer to the
paper/authors as KWZ. The analysis below replicates a subset of KWZ
results, focusing on six public data sets and eleven portfolio rules.
Nonetheless, the experiment can be generalized to additional data sets
and portfolios. This generalization, however, presumes that the user is
willing to take an extra effort to (1) add new data sets in the same
format to the data_list object and (2) follow the logic
behind the primary decision rule function and incorporate the rule as an
additional vector of weights. I will discuss these adjustments as we
proceed below.
Libraries
In terms of implementation, I utilize seven libraries
library(xts) # for time series manipulation
library(lubridate) # for date manipulation
library(parallel) # for parallel processing across data sets
library(readxl) # reading excel files to load data
library(ggplot2) # for visualization
library(zipfR) # needed for one of the decision rules
library(RiskPortfolios) # used for covariance matrix shrinkage
rm(list = ls())
The first is used for downloading data and working with time series.
The second is for date manipulation and formatting. The third can be
efficiently utilized on Linux machines to perform parallel computing
using functional programming, e.g., working with lapply.
The fourth one is to read xlsx formatted files, whereas the fifth is
used for visualization. The last two are used in the implementation of
the decision rules. Specifically, I utilize the library
zipfR to implement the incomplete beta function as advised
by KWZ. Finally, I employ the linear shrinkage for covariance matrices
from the RiskPortfolios library.
Getting the Data
KWZ rely on eight different data sources to evaluate different
decision rules. Four of these come from Ken French’s website, two come
from Robert Novy-Marx’s website, and the remaining are constructed from
the CRSP database. In this article, I focus on the first six data sets
which are publicly available: 1. 10 momentum portfolios (Momentum);
January 1927 to December 2018 2. 25 Fama-French \(5 \times 5\) size and book-to-market ranked
portfolios (Size-B/M); from January 1927 to December 2018 3. 25
portfolios formed on operating profitability and investment (OP-Inv);
from July 1963 to December 2018 4. 49 industry portfolios (Industry);
from July 1969 to December 2018 5. 16 low turnover anomalies by
Novy-Marx and Velikov (2016); from July 1963 to December 2013 6. 46 all
turnover anomalies by Novy-Marx and Velikov (2016); from July 1973 to
December 2013 (after dropping missing values)
With some minimal tweaking, we can download the Fama-French data in
the following manner and put the four data sets into a single list as
follows:
# MOMETUM - January 1927 to December 2018
FF_file <- "https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/10_Portfolios_Prior_12_2_CSV.zip"
temp <- tempfile()
download.file(FF_file,temp)
trying URL 'https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/10_Portfolios_Prior_12_2_CSV.zip'
Content type 'application/x-zip-compressed' length 113166 bytes (110 KB)
==================================================
downloaded 110 KB
unz_files <- unzip(temp)
ds <- read.csv(unz_files,skip = 10,stringsAsFactors = F)
flag_obs <- grep("Average Equal",ds[,1],ignore.case = T)
ds <- ds[1:(flag_obs-1),]
ds <- data.frame(apply(ds,2,as.numeric))
names(ds)[1] <- "date"
ds$date <- ds$date*100 + 1
ds$date <- ymd(ds$date)
ds$date <- ceiling_date(ds$date,"m") - 1
ds[,-1] <- ds[,-1]/100
ds <- ds[ds$date >= "1927-01-01",]
ds <- ds[ds$date <= "2018-12-31",]
ds1 <- ds
# 25BM - January 1927 to December 2018
FF_file <- "https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/25_Portfolios_5x5_CSV.zip"
temp <- tempfile()
download.file(FF_file,temp)
trying URL 'https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/25_Portfolios_5x5_CSV.zip'
Content type 'application/x-zip-compressed' length 525935 bytes (513 KB)
==================================================
downloaded 513 KB
unz_files <- unzip(temp)
ds <- read.csv(unz_files,skip = 15,stringsAsFactors = F)
flag_obs <- grep("Average Equal",ds[,1],ignore.case = T)
ds <- ds[1:(flag_obs-1),]
ds <- data.frame(apply(ds,2,as.numeric))
names(ds)[1] <- "date"
ds$date <- ds$date*100 + 1
ds$date <- ymd(ds$date)
ds$date <- ceiling_date(ds$date,"m") - 1
ds[,-1] <- ds[,-1]/100
ds <- ds[ds$date >= "1927-01-01",]
ds <- ds[ds$date <= "2018-12-31",]
ds2 <- ds
# 25OP-Inv July 1963 to December 2018
FF_file <- "https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/25_Portfolios_OP_INV_5x5_CSV.zip"
temp <- tempfile()
download.file(FF_file,temp)
trying URL 'https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/25_Portfolios_OP_INV_5x5_CSV.zip'
Content type 'application/x-zip-compressed' length 361461 bytes (352 KB)
==================================================
downloaded 352 KB
unz_files <- unzip(temp)
ds <- read.csv(unz_files,skip = 24,stringsAsFactors = F)
flag_obs <- grep("Average Equal",ds[,1],ignore.case = T)
ds <- ds[1:(flag_obs-1),]
ds <- data.frame(apply(ds,2,as.numeric))
names(ds)[1] <- "date"
ds$date <- ds$date*100 + 1
ds$date <- ymd(ds$date)
ds$date <- ceiling_date(ds$date,"m") - 1
ds[,-1] <- ds[,-1]/100
ds <- ds[ds$date >= "1963-07-01",]
ds <- ds[ds$date <= "2018-12-31",]
ds3 <- ds
# 49 Industry - July 1969 to December 2018
FF_file <- "https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/49_Industry_Portfolios_CSV.zip"
temp <- tempfile()
download.file(FF_file,temp)
trying URL 'https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/49_Industry_Portfolios_CSV.zip'
Content type 'application/x-zip-compressed' length 469708 bytes (458 KB)
==================================================
downloaded 458 KB
unz_files <- unzip(temp)
ds <- read.csv(unz_files,skip = 11,stringsAsFactors = F)
flag_obs <- grep("Average Equal",ds[,1],ignore.case = T)
ds <- ds[1:(flag_obs-1),]
ds <- data.frame(apply(ds,2,as.numeric))
names(ds)[1] <- "date"
ds$date <- ds$date*100 + 1
ds$date <- ymd(ds$date)
ds$date <- ceiling_date(ds$date,"m") - 1
ds[,-1] <- ds[,-1]/100
ds <- ds[ds$date >= "1969-07-01",]
ds <- ds[ds$date <= "2018-12-31",]
ds4 <- ds
The other data sets can be downloaded from Robert Novy-Marx’s website
and prepared efficiently. Note that the data covers different decile
portfolios constructed based on different characteristics. Similar to
KWZ, the first data set focuses on the low turnover anomaly strategies,
which correspond to
low_to_strat <- c("Size","Gross Profitability",
"Value","ValProf",
"Accruals","Asset Growth",
"Investment","Piotroski's F-score")
low_to_strat
[1] "Size" "Gross Profitability" "Value" "ValProf"
[5] "Accruals" "Asset Growth" "Investment" "Piotroski's F-score"
The data can be downloaded and prepared in the following way:
file.i <- "http://rnm.simon.rochester.edu/data_lib/ToAatTC/Simple_Strategies_Returns.xlsx"
temp <- tempfile()
download.file(file.i,temp)
trying URL 'http://rnm.simon.rochester.edu/data_lib/ToAatTC/Simple_Strategies_Returns.xlsx'
Content type 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' length 3649949 bytes (3.5 MB)
==================================================
downloaded 3.5 MB
ds_sheets_all <- sort(excel_sheets(temp))
ds_sheets <- ds_sheets_all[ds_sheets_all %in% low_to_strat]
NM_data_list <- list()
for (sheet_i in ds_sheets) {
ds_i <- data.frame(read_xlsx(temp,sheet = sheet_i))
ds_i <- ds_i[,c(1,2,ncol(ds_i))]
names(ds_i)[-1] <- paste(sheet_i,names(ds_i)[-1],sep = "_")
ds_i$date <- ds_i$Month
ds_i$date <- ymd(ds_i$date*100 + 1)
ds_i$date <- ceiling_date(ds_i$date,"m") - 1
ds_i$Month <- NULL
ds_i[,1:2] <- ds_i[,1:2]/100
NM_data_list <- c(NM_data_list,list(ds_i))
}
NM_data_LT <- Reduce(merge,NM_data_list)
NM_data_LT <- na.omit(NM_data_LT)
Note that even though we consider eight characteristics, we have 16
portfolios. Similar to KWZ, I include both the long (top decile) and
short (bottom decile) portfolios
dim(NM_data_LT)
[1] 606 17
KWZ also considers all 23 anomalies corresponding to low, medium, and
high turnover strategies. Same as above, we consider top and bottom
deciles, resulting in 46 assets.
mid_to_strat <- c("Net Issuance (rebal.-A)","Return-on-book equity","Failure Probability",
"ValMomProf","ValMom","Idiosyncratic Volatility",
"Momentum","PEAD (SUE)","PEAD (CAR3)")
high_to_start <- c("Industry Momentum","Industry Relative Reversals","High-frequency Combo",
"Short-run Reversals","Seasonality","IRR (Low Vol)")
all_to_start <- unique(sort(c(low_to_strat,mid_to_strat,high_to_start)))
length(all_to_start)
[1] 23
Let us repeat the same loop as before to create long and short
portfolios:
ds_sheets <- ds_sheets_all[ds_sheets_all %in% all_to_start]
NM_data_list <- list()
for (sheet_i in ds_sheets) {
ds_i <- data.frame(read_xlsx(temp,sheet = sheet_i))
ds_i <- ds_i[,c(1,2,ncol(ds_i))]
names(ds_i)[-1] <- paste(sheet_i,names(ds_i)[-1],sep = "_")
ds_i$date <- ds_i$Month
ds_i$date <- ymd(ds_i$date*100 + 1)
ds_i$date <- ceiling_date(ds_i$date,"m") - 1
ds_i$Month <- NULL
ds_i[,1:2] <- ds_i[,1:2]/100
NM_data_list <- c(NM_data_list,list(ds_i))
}
Quitting from lines 515-540 (main_2023_01_04.Rmd)
NM_data_ALL <- Reduce(merge,NM_data_list)
NM_data_ALL <- na.omit(NM_data_ALL)
dim(NM_data_ALL)
[1] 486 47
Finally, we put the data altogether in a single list, so we can
easily generalize the analysis for any given element retrieved from the
list.
ds_list <- list(ds1,ds2,ds3,ds4,NM_data_LT,NM_data_ALL)
names(ds_list) <- c("MOM10","BM25","OPIN25","IND49","NMV16","NMV46")
sapply(ds_list,nrow)
MOM10 BM25 OPIN25 IND49 NMV16 NMV46
1104 1104 666 594 606 486
Each data has a different number of observations. Therefore, it is
preferable to ensure all data have the exact dates or cover the same
period for consistent comparison. For instance, this could help us
understand why specific decision rules work for one data set but not
another. Nonetheless, in this example, I will follow the analysis by KWZ
for reproducibility.
As a final tweak, let us sort the order of the data sets in line with
the order reported in all main tables by KWZ, which is as follows:
ds_list <- ds_list[c("MOM10","BM25","OPIN25","NMV16","NMV46","IND49")]
sapply(ds_list,ncol) - 1
MOM10 BM25 OPIN25 NMV16 NMV46 IND49
10 25 25 16 46 49
The only two data sets missing are the IVOL (\(N = 10\)) and Stocks (\(N = 100\)), where \(N\) denotes the number of assets. These two
data sets are user-based, such that the replication exercise is
subjected to additional degrees of freedom depending on the researcher’s
ability to replicate the data. In contrast, the above six data sets are
taken for granted, given the publicly available data libraries.
Therefore, we focus on the above six publicly available data sets for
the sake of reproducibility.
Before we move to the main analysis, note that KWZ consider the
excess returns on all portfolios. To do so, let us download the
risk-free rate using the Fama-French three factors data:
FF_file <- "https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_CSV.zip"
temp <- tempfile()
download.file(FF_file,temp)
trying URL 'https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_CSV.zip'
Content type 'application/x-zip-compressed' length 12648 bytes (12 KB)
==================================================
downloaded 12 KB
unz_files <- unzip(temp)
ds <- read.csv(unz_files,skip = 3)
flag_obs <- grep("Annual",ds[,1],ignore.case = T)
ds <- ds[1:(flag_obs-1),]
names(ds)[1] <- "date"
ds <- data.frame(apply(ds, 2, as.numeric))
ds$date <- ceiling_date(ymd(ds$date*100+ 01),"m")-1
ds <- ds[,c("date","RF")]
ds$RF <- ds$RF/100
ds_rf <- ds
rm(ds)
Now that we have the risk-free rate let us merge with the data list
and adjust the returns accordingly using the following function:
adjust_rf <- function(ds_i) {
ds_i <- merge(ds_i,ds_rf, by = "date")
RF <- ds_i$RF
ds_i[,-1] <- ds_i[,-1] - RF
ds_i$RF <- NULL
return(ds_i)
}
We can adjust the data in the following manner:
ds_list <- lapply(ds_list,adjust_rf)
saveRDS(ds_list,"ds_list.RDS") # save data just in case
Finally, we are set to begin our main investigation.
Decision Rules
We consider 11 decision rules in total. Without any position
constraints, most decision rules studied by KWZ can be implemented using
closed-form solutions based on inputs/estimates. However, in the case of
short-sales constraints, we need to solve for optimal portfolios
numerically. As a reference, I covered the numerical optimization in one
of my earlier posts - see link.
The following codes/functions are the building blocks to performing
numerical portfolio optimization:
U <- function(X,Mu,Sigma,gamma) {
u1 <- t(X)%*%(Mu)
u2 <- t(X)%*%Sigma%*%X
total <- u1 - (gamma/2)*u2
return(c(total))
}
G <- function(X,Mu,Sigma,gamma) {
total <- Mu - gamma*Sigma%*%X
return(total)
}
U2 <- function(X,Sigma) {
u2 <- t(X)%*%Sigma%*%X
return(c(u2))
}
G2 <- function(X,Sigma) {
g2 <- Sigma%*%X
total <- g2
return(total)
}
# add constraints
BC_f <- function(d) {
# sum to one constraint
A <- matrix(1,1,d)
A <- rbind(A,-A)
B <- c(0.99999,-1.00001)
# short-sales constraints
A2 <- diag(rep(1,d))
B2 <- rep(0,d)
A2 <- rbind(A,A2)
B2 <- c(B,B2)
# stack altogether in a list
BC1 <- list(A,B)
BC2 <- list(A2,B2)
list(BC1,BC2)
}
Specifically, U denotes the objective function that
captures the mean-variance preference, whereas gamma
denotes the risk aversion level. To result in faster convergence, I also
define the gradient function, denoted by G. Hence, for a
given mean vector and covariance matrix, we can train the algorithm to
seek the optimal point using gradient descent, e.g., Newton’s method.
The U2 and G2 follow suit, whereas the main
difference is that the objective is to minimize the portfolio variance
rather than optimize a mean-variance trade-off. Finally, the
BC_f function is written to impose constraints on the
optimization problem. By design, the base function
constrOptim takes constraints in a linear form, such that
it can be written as \[
\begin{aligned}\min_{\mathbf{x}}\quad & f(\mathbf{x})\\
\textrm{s.t.}\quad & \mathbf{A}\mathbf{x}\geq\mathbf{b}\\
\\
\end{aligned}
\] In this regard, the BC_f function returns the
relevant \(\mathbf{A}\) and \(\mathbf{b}\) matrices to reflect two
constraints: \[
\mathbf{1}^{\top}\mathbf{x} = 1 \\
x_i \geq 0 \, \forall i=1,...,d
\] For instance, the naive portfolio (i.e., the equally weighted)
confirms both constraints:
d <- 10
A <- BC_f(d)[[2]][[1]]
b <- BC_f(d)[[2]][[2]]
x <- rep(1/d,d)
all(A%*%x >= b)
[1] TRUE
In this regard, we can easily add the A and
b into the optimization function to comply with these two
constraints.
Before we proceed to the decision rule, it is worth noting that any
decision rule can be viewed as a mapping function of the data into
portfolio weights, i.e., \(f:\mathcal{D}\rightarrow \mathbf{w}\),
where \(\mathcal{D}\) is the data as
well as other inputs, \(f\) is the
mapping function (decision rule), and \(\mathbf{w}\) is the corresponding portfolio
weights for that decision rule. For instance, in the case of the
mean-variance portfolio, the data is mapped into the mean vector and
covariance matrix, which are eventually used as the primary inputs to
determine the optimal portfolio weights. In this case, the main inputs
also require a given risk aversion level. Hence, to implement different
decision rules, we need to write a general function that takes a data
subset R_sub with different inputs and returns the
portfolio weights. Based on the outcome, we can evaluate different
decision rules over time, corresponding to a backtesting procedure. In
the end, such a function maps the data and other preferences into future
allocations. We define such a function as follows
DR_function <- function(R_sub,gamma,sample_size,TC) {
# takes data object R_sub - see below
R_sub2 <- tail(R_sub,sample_size)
R_sub2 <- R_sub2[,-1]
d <- ncol(R_sub2)
Mu <- apply(R_sub2,2,mean)
Sigma <- var(R_sub2)*(d-1)/d # for MLE estimate divide by d
Sig_inv <- solve(Sigma)
e <- rep(1,d)
# first portfolio is the global minimum variance portfolio. KWZ denote it by $\hat{w}_{g,t}$
GMV <- Sig_inv%*%e/sum(Sig_inv)
B_mat <- Sig_inv%*%( diag(d) - e%*%t(GMV) )
# the second is the plug-in portfolio. KWZ denote it by $\hat{w}_{z,t}$
MV <- GMV + (1/gamma)*B_mat%*%Mu
# the unbiased estimate
MV_UB <- GMV + (sample_size - d - 1)/(gamma*sample_size)*B_mat%*%Mu
# Naive Portfolio
Naive <- rep(1/d,d)
BC <- BC_f(d)[[2]] # 2 for no short-sales and 1 for yes
A <- BC[[1]]
B <- BC[[2]]
# initial guess is based on naive portfolio
X0 <- Naive
X_opt <- constrOptim(X0,
function(x) -U(x,Mu,Sigma,gamma),
grad = function(x) -G(x,Mu,Sigma,gamma),
ui = A,ci = B)
X1 <- X_opt$par
MV_NS <- X1/sum(X1)
X_opt <- constrOptim(X0,function(x) U2(x,Sigma),
grad = function(x) G2(x,Sigma),
ui = A,ci = B)
X1 <- X_opt$par
GMV_NS <- X1/sum(X1)
# Volatility Timing: the first strategy proposed by Kirby and Ostdiek (2012)
KO_VT <- (1/diag(Sigma))^4 # footnote in Table 2 states that eta = 4
KO_VT <- KO_VT/sum(KO_VT)
# Reward-to-Risk Timing: The second strategy proposed by Kirby and Ostdiek (2012)
Mu_plus <- Mu
Mu_plus[Mu_plus < 0] <- 0
KO_RT <- (Mu_plus/diag(Sigma))^4 # footnote in Table 2 states that eta = 4
KO_RT <- KO_RT/sum(KO_RT)
### add KWZ decision rule
N <- d
T <- sample_size
# the proposed way to estimate
psi2hat <- t(MV) %*% Mu;
psi2hat <- ((T-N-1)*psi2hat-(N-1))/T +
((2*(psi2hat)^((N-1)/2)*((1+psi2hat)^(-(T-2)/2)))/(T*Ibeta(psi2hat/(1+psi2hat),(N-1)/2,(T-N+1)/2)));
psi2hat <- as.numeric(psi2hat)
kappaE <- (((T-N)*(T-N-3))/(T*(T-2)))*(psi2hat/(psi2hat+(N-1)/T));
KWZ <- GMV*(1-kappaE) + MV*(kappaE)
# Ledoit and Wolf (2004)
m_i <- list(type = "diag")
Sigma <- covEstimation(as.matrix(R_sub2),control = m_i)
Sig_inv <- solve(Sigma)
e <- rep(1,d)
# update the GMV based on the cov estimate
GMV_LW_2004 <- Sig_inv%*%e/sum(Sig_inv)
B_mat <- Sig_inv%*%( diag(d) - e%*%t(GMV_LW_2004))
MV_LW_2004 <- GMV_LW_2004 + (1/gamma)*B_mat%*%Mu
# do the same for the KWZ decision rule
KWZ_LW_2004 <- GMV_LW_2004*(1-kappaE) + MV_LW_2004*(kappaE)
# organize in a similar order as in KWZ
list(KWZ = KWZ, MV = MV, MV_UB = MV_UB,
KWZ_LW_2004 = KWZ_LW_2004, MV_LW_2004 = MV_LW_2004,
MV_NS = MV_NS,
GMV = GMV,GMV_NS = GMV_NS,
Naive = Naive,
KO_VT = KO_VT,KO_RT = KO_RT)
}
For brevity, I skip the discussion of the above decision rules.
However, note that the function returns eleven decision rules in a list.
For summary, I provide below the mathematical definition of reach rule
from KWZ with respect to each definition used in the
DR_function function:
Note that as long as the function organizes the portfolio weights in
a single list as above, the following backtesting procedure can be
generalized to accommodate other decision rules and estimation
methods.
Backtesting
We can run the DR_function on a rolling basis to perform
backtesting. Nonetheless, note that three main inputs are needed to
reduplicate the performance results: 1. The risk aversion level denoted
by \(\gamma\) 2. Transactions cost
denoted by TC 3. Sample size \(T\)
Similar to the baseline analysis from KWZ (see, e.g., Tables 1 and
2), I set
gamma <- 3
TC <- 0/(100^2) # to reflect basis points
sample_size <- 120
In the following, I utilize the DR_function and write a
single main function that takes the data index and the above three
inputs to evaluate the decision rules in terms of out-of-sample
returns.
Personal Note: It is worth mentioning that the
function was not written directly. Instead, I started with a single data
and a few commands, which were then generalized. This is probably the
best practice of writing functions to avoid potential coding
errors/issues.
main_run_portfolio_fun <- function(choose_data,gamma,sample_size,TC) {
# choose the data - in total we have six different sets
ds <- ds_list[[choose_data]]
months_tot <- sort(unique(ds$date))
# store data
results_all <- data.frame()
# store portfolio weights - relevant for portfolio turnover and TC
W_ALL_list <- list()
# run a loop from T until the end-1 of data
for(i in sample_size:(length(months_tot) - 1)) {
# current month
month_i <- months_tot[i]
# next month, which is unknown during portfolio construction
month_i_plus <- months_tot[i+1]
# for tracking backtesting
if( month(month_i_plus) == 12) {
cat("this is month ", as.character(month_i_plus),"\n")
}
# define the in-sample data
R_sub <- ds[ds$date <= month_i,]
run_DR_fun <- DR_function(R_sub,gamma,sample_size,TC)
# keep track of weights and stack in matrix
W_1_list <- lapply(run_DR_fun,t)
W_1_list <- lapply(W_1_list,function(x) data.frame(month_i_plus,x) )
W_ALL_list <- c(W_ALL_list,list(W_1_list))
R_next <- ds[ds$date == month_i_plus,-1]
R_port <- lapply(W_1_list, function(x) sum(x[,-1]*R_next) )
R_port2 <- data.frame(date = month_i_plus,Reduce(cbind,R_port))
names(R_port2)[-1] <- names(R_port)
results_all <- rbind(results_all,R_port2)
}
list(port_ret = results_all, weights = W_ALL_list)
}
The loop in the above function computes two main objects. The first
one contains the out-of-sample portfolio returns
results_all, whereas the other is a list of all portfolio
weights over time denoted by W_ALL_list. Given the former,
we can easily compute gross returns and other performance measures. For
the latter, we can consider performance in terms of net transaction cost
and other portfolio characteristics related to its stability (e.g.,
turnover) or diversification. As mentioned above, note that the function
is generalized to evaluate multiple decision rules simultaneously by
utilizing the lapply base function.
In the following, let us consider the gross returns and replicate
some of the results of 2 from KWZ, which does not consider transactions
cost. Specifically, Tables 1 and 2 from KWZ report the certainty
equivalent returns (CER) and the Sharpe ratio for different decision
rules (rows) and data sets (columns). In our case, we have eleven
decision rules and six data sets. Given that the data is public, we
should get identical results as the decision rules do not depend on a
black box model or hidden validation that could result in additional
degrees of freedom.
However, researchers should be aware of one issue with the
Fama-French data. It appears that the same time series of factor returns
changes over time due to ex-post adjustments in the construction of the
factor. This issue has been raised by Akey, Robertson, and Simutin
(2022), which the authors refer to as “Noisy Factors.” Specifically, the
authors find that “factor returns differ substantially depending on when
the data were downloaded, and only a small portion of these retroactive
changes is explained by revisions in the underlying data.” Nonetheless,
this issue should result in a slight difference given that KWZ was
conducted post-2018.
To implement, I utilize main_run_portfolio_fun across
all data sets and compute the time series returns of our decision rules
in the following manner:
run_experiment <- mclapply(1:length(ds_list),
function(data_index) main_run_portfolio_fun(data_index,gamma,sample_size,TC = 0),
mc.cores = detectCores() )
Quitting from lines 515-540 (main_2023_01_04.Rmd)
Note that the above command runs the backtesting experiment across
all data sets using three main inputs, specifying the risk aversion,
sample size, and transactions cost. Given the portfolio returns from
each data experiment, we can easily compute the certainty equivalent
returns (CER) and Sharpe ratios (SR) for each portfolio-data
specification:
sr_fun <- function(x) round(mean(x)/sd(x),4)
cer_fun <- function(x) round(mean(x) - (gamma/2)*var(x),4)
port_ret_list <- lapply(run_experiment,function(x) x$port_ret)
MS_CER <- sapply(port_ret_list, function(x) apply(x[,-1],2,cer_fun))
MS_SR <- sapply(port_ret_list, function(x) apply(x[,-1],2,sr_fun))
For a consistent comparison, I copy the CER and the Sharpe ratios
from Tables 1 and 2, respectively, of KWZ that correspond to the 6 data
sets and the 11 decision rules studied in this article:
Table1_KWZ <- "KWZ 0.0098 0.0102 0.0064 0.0060 0.0081 0.0259 0.0022 -0.0068
MV -0.0063 -0.0635 -0.0141 -0.0705 -0.0270 -1.2294 -0.3998 -27.0937
MV_UB -0.0013 -0.0284 -0.0075 -0.0361 -0.0147 -0.3039 -0.1278 -0.7464
KWZ_LW_2004 0.0120 0.0112 0.0100 0.0083 0.0100 0.0695 0.0042 0.0020
MV_LW_2004 0.0070 -0.0009 0.0048 -0.0219 0.0013 -0.0444 -0.1243 -0.5255
MV_NS 0.0060 0.0050 0.0028 0.0045 0.0048 0.0090 0.0003 0.0000
GMV 0.0051 0.0060 0.0020 0.0050 0.0050 0.0035 0.0025 -0.0056
GMV_NS 0.0035 0.0041 0.0041 0.0049 0.0025 0.0059 0.0042 0.0027
Naive 0.0026 0.0036 0.0022 0.0034 0.0022 0.0013 0.0039 0.0024
KO_VT 0.0036 0.0043 0.0041 0.0045 0.0030 0.0046 0.0049 0.0037
KO_RT 0.0047 0.0045 0.0034 0.0049 0.0046 0.0061 0.0038 0.0019"
Table2_KWZ <- "KWZ 0.2521 0.2479 0.2452 0.1916 0.2238 0.5778 0.1194 0.0234
MV 0.2375 0.1999 0.2387 0.1470 0.1896 0.5614 0.0610 -0.0482
MV_UB 0.2405 0.2069 0.2400 0.1534 0.1940 0.5648 0.0671 -0.0428
KWZ_LW_2004 0.2693 0.2838 0.2456 0.2312 0.2591 0.6925 0.1661 0.1093
MV_LW_2004 0.2545 0.2393 0.2383 0.1792 0.2118 0.6558 0.0728 -0.0138
MV_NS 0.1905 0.1734 0.1299 0.1644 0.1700 0.2495 0.0996 0.0943
GMV 0.1823 0.2085 0.1088 0.1813 0.1843 0.1478 0.1227 0.0369
GMV_NS 0.1450 0.1595 0.1639 0.1816 0.1236 0.2188 0.1717 0.1291
Naive 0.1276 0.1484 0.1224 0.1426 0.1209 0.1034 0.1527 0.1205
KO_VT 0.1466 0.1636 0.1619 0.1704 0.1338 0.1751 0.1902 0.1584
KO_RT 0.1695 0.1651 0.1438 0.1768 0.1665 0.2046 0.1534 0.1078"
To process in a readable format, I write the following function that
takes the above content and loads it into a readable matrix
read_table_fun <- function(KWZ_table) {
x <- scan(textConnection(KWZ_table),what = character(),sep = "\n")
x <- strsplit(x," ")
x_names <- sapply(x,function(x) x[1] )
X <- t(sapply(x,function(x) as.numeric(x[-1]) ))
colnames(X) <- c("MOM10","BM25", "IVOL", "OPIN25", "NMV16", "NMV46", "IND49","Stocks")
# keep relevant data sets
X <- X[,c("MOM10","BM25","OPIN25","NMV16","NMV46","IND49")]
rownames(X) <- x_names
KWZ_table <- X
return(KWZ_table)
}
Table1_KWZ <- read_table_fun(Table1_KWZ)
Read 11 items
Table2_KWZ <- read_table_fun(Table2_KWZ)
Read 11 items
As a summary, I plot my results versus those by KWZ using the
ggplot2 library. Let us summarize all results in a
data.frame that is user-friendly to input into
ggplot:
ds_plot <- data.frame()
for (r in 1:nrow(MS_CER)) {
ds_plot_r <- data.frame(KWZ = Table1_KWZ[r,], MS = MS_CER[r,],
Data = names(ds_list), Portfolio = rownames(MS_CER)[r] )
ds_plot <- rbind(ds_plot,ds_plot_r)
}
# save data
ds_plot1 <- ds_plot
ds_plot1$Metric <- "CER"
ds_plot <- data.frame()
for (r in 1:nrow(MS_SR)) {
ds_plot_r <- data.frame(KWZ = Table2_KWZ[r,], MS = MS_SR[r,],
Data = names(ds_list), Portfolio = rownames(MS_SR)[r] )
ds_plot <- rbind(ds_plot,ds_plot_r)
}
ds_plot2 <- ds_plot
ds_plot2$Metric <- "SR"
# stack data altogether
ds_plot <- rbind(ds_plot1,ds_plot2)
CER: Table 1
Now we are ready to plot the data. Since the CER and SR correspond to
different performance metrics, we visualize each separately. For the
CER, we have:
p <- ggplot(ds_plot[ds_plot$Metric == "CER",], aes(y = KWZ, x = MS,colour = Portfolio,shape = Data))
p <- p + geom_point()
p <- p + geom_abline(slope = 1, intercept = 0)
p <- p + ggtitle("CER: Replicating Table 1 from KWZ")
p

Overall, the results seem consistent, with some slight variations in
some cases. The most significant deviation comes from the MV decision
rule, which also results in the worst performance. This is most evident
in the NVM46 data set.
SR: Table 2
Let us repeat the same plot for the or the SR:
p <- ggplot(ds_plot[ds_plot$Metric == "SR",], aes(y = KWZ, x = MS,colour = Portfolio,shape = Data))
p <- p + geom_point()
p <- p + geom_abline(slope = 1, intercept = 0)
p <- p + ggtitle("SR: Replicating Table 2 from KWZ")
p

We observe that most decision rules result in SR between 0 and 0.4,
whereas in a few cases, the SR is greater than 0.5. This is mainly the
case for the NMV46 data. Nonetheless, we observe a good consistency
between the original findings by KWZ and ours. There are, however, a few
slight deviations. We expect identical results for the MV, GMV, and
Naive decision rules since we are using closed-form solutions, and there
are no degrees of freedom for an error. For the short sales, there are
some degrees of freedom in terms of initial weights and how the
optimization algorithm used in R differs from the one used by KWZ, which
could be built using different software. The same applies to
shrinkage-based techniques.
Additionally, such deviations could be attributed to the fact that
the Fama-French data is subjected to changes over time, as pointed out
by Akey, Robertson, and Simutin (2022). To check the last point made
above, let us focus on NMV16 and NMV46 data sets:
ds_plot_NVM <- ds_plot[ds_plot$Data %in% c("NMV16","NMV46") & ds_plot$Metric == "SR",]
p <- ggplot(ds_plot_NVM, aes(y = KWZ, x = MS,colour = Portfolio,shape = Data))
p <- p + geom_point()
p <- p + geom_abline(slope = 1, intercept = 0)
p <- p + ggtitle("SR: Replicating Table 2 from KWZ")
p

As conjectured above, the closed-form solution portfolios are
consistent with KWZ. However, we note a slight difference in the
decision rules with no short sales, which requires numerical
optimization. A similar observation follows when we compare the
shrinkage-based techniques.
Turnover: Table 3
Now, let us evaluate the portfolio turnover of each decision rule. To
do so, I follow the same metric used by KWZ - see Equation (60) from the
paper. The following nested loop computes the turnover across different
data sources and decision rules:
MS_TO <- c()
for (data_i in 1:length(ds_list)) {
TO_vec <- c()
for (decision_rule in rownames(MS_SR)) {
W_list_data <- run_experiment[[data_i]]$weights
W_list_data_DR <- lapply(W_list_data, function(x) data.frame(x[names(x) %in% decision_rule]) )
W_mat <- Reduce(rbind,W_list_data_DR)
R_mat <- ds_list[[data_i]]
W_mat <- W_mat[-nrow(W_mat),]
R_mat <- R_mat[R_mat$date %in% W_mat[,1],]
W_mat_adj <- W_mat[,-1]*(1 + R_mat[,-1])
W_mat_adj <- t(apply(W_mat_adj,1,function(x) x/sum(x))) # adjust positions to equal one
TO <- W_mat[-1,-1] - W_mat_adj[-nrow(W_mat_adj),]
TO <- apply(TO,1,function(x) sum(abs(x)) )
TO_mean <- mean(TO)
TO_vec <- c(TO_vec,TO_mean)
}
MS_TO <- cbind(MS_TO,TO_vec)
}
rownames(MS_TO) <- rownames(MS_SR)
colnames(MS_TO) <- names(ds_list)
The above code takes into consideration that positions do change over
time. For instance, one may conjecture that the naive portfolio has zero
turnover since its weights are constant over time; however, this is not
the case. Its positive turnover comes mainly from the fact that the
portfolio is re-balanced each month to maintain equal weights after
considering the gross returns and, hence, changes in the portfolio
position. To better understand how it is computed, consider the
following example:
d <- 5
W_1 <- rep(1/d,d)
W_1_adj <- W_1*(1+rnorm(d,0,0.06))
W_2 <- W_1
rbind(W_1_adj,W_2)
[,1] [,2] [,3] [,4] [,5]
W_1_adj 0.2119116 0.1857479 0.1863003 0.1977841 0.2065548
W_2 0.2000000 0.2000000 0.2000000 0.2000000 0.2000000
As returns are realized, the initial positions change as well. In
order to bring back to equal weights, the portfolio manager needs to
re-balance, resulting in a turnover of
sum(abs(W_2 - W_1_adj))
[1] 0.04863401
The operations of this simple example are conducted for each month,
but as a summary, it is common to report the average over time. This is
done for all data sets and decision rules.
For a consistent comparison, I collect the data from KWZ (Table
3):
Table3_KWZ <- "KWZ 2.0157 3.5212 2.9934 1.9283 2.8730 34.0907 1.2583 3.8888
MV 5.0765 54.3620 7.1199 16.3314 11.2294 434.8788 91.2610 1275.5124
MV_UB 4.3229 14.5806 6.5314 9.7989 8.6225 152.8220 40.1044 224.0102
KWZ_LW_2004 1.0061 1.2745 1.0879 1.1136 0.9959 5.2506 0.5241 0.6093
MV_LW_2004 2.4207 6.6031 2.6552 7.2740 3.5400 50.8673 15.2265 212.4453
MV_NS 0.1066 0.2393 0.1808 0.1945 0.1456 0.0558 0.2092 0.2648
GMV 0.2770 0.7665 0.2640 0.5413 0.4911 1.5337 0.8227 3.7827
GMV_NS 0.0817 0.0691 0.0088 0.0774 0.0559 0.0375 0.0733 0.1440
Naive 0.0176 0.0182 0.0172 0.0199 0.0197 0.0227 0.0341 0.0648
KO_VT 0.0281 0.0309 0.0188 0.0353 0.0347 0.0373 0.0481 0.0706
KO_RT 0.0746 0.0767 0.0886 0.1086 0.0959 0.0832 0.1369 0.1591"
Table3_KWZ <- read_table_fun(Table3_KWZ)
Read 11 items
Same as before let us organize the results in a user-friendly data
frame that we can input efficiently into ggplot:
ds_plot_TO <- data.frame()
for (r in 1:nrow(MS_TO)) {
ds_plot_r <- data.frame(KWZ = Table3_KWZ[r,], MS = MS_TO[r,],
Data = names(ds_list), Portfolio = rownames(MS_TO)[r] )
ds_plot_TO <- rbind(ds_plot_TO,ds_plot_r)
}
ds_plot_TO$KWZ <- log(ds_plot_TO$KWZ)
ds_plot_TO$MS <- log(ds_plot_TO$MS)
p <- ggplot(ds_plot_TO, aes(y = KWZ, x = MS,colour = Portfolio,shape = Data))
p <- p + geom_point()
p <- p + geom_abline(slope = 1, intercept = 0)
p <- p + ggtitle("Turnover: Replicating Table 3 from KWZ")
p

Note that I use the log scale to control for fixed effects associated
with data and decision rules. For instance, the MV decision rule
exhibits huge unrealistic positions. This evidence is consistent with
the previous literature on estimation error in portfolio selection.
Overall, we find consistent results with KWZ. The major flags stem from
those with short sales constraints. To check the sensitivity with
respect to the Fama-French data, let us repeat the same exercise as we
did with the SR results, where we keep the NMV16 and NMV46 data
sets:
ds_plot_TO_sub <- ds_plot_TO[ds_plot_TO$Data %in% c("NMV16","NMV46"),]
p <- ggplot(ds_plot_TO_sub, aes(y = KWZ, x = MS,colour = Portfolio,shape = Data))
p <- p + geom_point()
p <- p + geom_abline(slope = 1, intercept = 0)
p <- p + ggtitle("Turnover: Replicating Table 3 from KWZ")
p

Interestingly, we still witness slight variations; however, our
results seem consistent overall with KWZ. Similar to the Sharpe ratio
performance results, we note that these deviations mainly stem from the
rules that depend on numerical optimization or rely on non-base R
packages.
References
- Akey, P., Robertson, A., & Simutin, M. (2022). Noisy factors.
Rotman School of Management Working Paper Forthcoming.
- Kan, R., Wang, X., & Zhou, G. (2022). Optimal portfolio choice
with estimation risk: No risk-free asset case. Management Science,
68(3), 2047-2068.
- Kirby, C., & Ostdiek, B. (2012). It’s all in the timing: simple
active portfolio strategies that outperform naive diversification.
Journal of Financial and Quantitative Analysis, 47(2), 437-467.
- Novy-Marx, R., & Velikov, M. (2016). A taxonomy of anomalies and
their trading costs. The Review of Financial Studies, 29(1),
104-147.
LS0tCnRpdGxlOiAiUmVwcm9kdWNpYmxlIFJlc2VhcmNoIGluIFBvcnRmb2xpbyBTZWxlY3Rpb24iCmF1dGhvcjogIk1hamVlZCBTaW1hYW4iCmRhdGU6ICJKYW51YXJ5IDQsIDIwMjMiCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogIHBkZl9kb2N1bWVudDogZGVmYXVsdApmaWdfd2lkdGg6IDUwCi0tLQoKIyBPdmVydmlldwpUaGUgZm9sbG93aW5nIGFydGljbGUgYnVpbGRzIG9uIGVhcmxpZXIgY29kZXMgZnJvbSBteSBbUlB1YnMgY29ybmVyXShodHRwczovL3JwdWJzLmNvbS9zaW1hYW44NCkgYW5kIGFpbXMgdG8gcmVwbGljYXRlIHN0YW5kYXJkIGJhc2VsaW5lIHBvcnRmb2xpbyByZXNlYXJjaC4gQnkgZG9pbmcgc28sIEkgaG9wZSB0aGUgZm9sbG93aW5nIGluaXRpYXRpdmUgd2lsbCBlbmNvdXJhZ2UgZnV0dXJlIHJlcHJvZHVjaWJsZSByZXNlYXJjaCBpbiBwb3J0Zm9saW8gc2VsZWN0aW9uLiBTcGVjaWZpY2FsbHksIHRoZSBhcnRpY2xlIGJ1aWxkcyBvbiB0aGUgZW1waXJpY2FsIGRlc2lnbiBjb25kdWN0ZWQgYnkgIEthbiwgV2FuZywgYW5kIFpob3UgKDIwMjIpLCB3aGljaCBpcyBjb25zaWRlcmVkIG9uZSBvZiB0aGUgdHlwaWNhbCBmcmFtZXdvcmtzIHVzZWQgaW4gdGhlIGxpdGVyYXR1cmUuIEZvciBicmV2aXR5LCBJIHJlZmVyIHRvIHRoZSBwYXBlci9hdXRob3JzIGFzIEtXWi4gVGhlIGFuYWx5c2lzIGJlbG93IHJlcGxpY2F0ZXMgYSBzdWJzZXQgb2YgS1daIHJlc3VsdHMsIGZvY3VzaW5nIG9uIHNpeCBwdWJsaWMgZGF0YSBzZXRzIGFuZCBlbGV2ZW4gcG9ydGZvbGlvIHJ1bGVzLiBOb25ldGhlbGVzcywgdGhlIGV4cGVyaW1lbnQgY2FuIGJlIGdlbmVyYWxpemVkIHRvIGFkZGl0aW9uYWwgZGF0YSBzZXRzIGFuZCBwb3J0Zm9saW9zLiBUaGlzIGdlbmVyYWxpemF0aW9uLCBob3dldmVyLCBwcmVzdW1lcyB0aGF0IHRoZSB1c2VyIGlzIHdpbGxpbmcgdG8gdGFrZSBhbiBleHRyYSBlZmZvcnQgdG8gKDEpIGFkZCBuZXcgZGF0YSBzZXRzIGluIHRoZSBzYW1lIGZvcm1hdCB0byB0aGUgYGRhdGFfbGlzdGAgb2JqZWN0IGFuZCAoMikgZm9sbG93IHRoZSBsb2dpYyBiZWhpbmQgdGhlIHByaW1hcnkgZGVjaXNpb24gcnVsZSBmdW5jdGlvbiBhbmQgaW5jb3Jwb3JhdGUgdGhlIHJ1bGUgYXMgYW4gYWRkaXRpb25hbCB2ZWN0b3Igb2Ygd2VpZ2h0cy4gSSB3aWxsIGRpc2N1c3MgdGhlc2UgYWRqdXN0bWVudHMgYXMgd2UgcHJvY2VlZCBiZWxvdy4KCiMgTGlicmFyaWVzCkluIHRlcm1zIG9mIGltcGxlbWVudGF0aW9uLCBJIHV0aWxpemUgc2V2ZW4gbGlicmFyaWVzICAKYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHh0cykgIyBmb3IgdGltZSBzZXJpZXMgbWFuaXB1bGF0aW9uCmxpYnJhcnkobHVicmlkYXRlKSAjIGZvciBkYXRlIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KHBhcmFsbGVsKSAjIGZvciBwYXJhbGxlbCBwcm9jZXNzaW5nIGFjcm9zcyBkYXRhIHNldHMKbGlicmFyeShyZWFkeGwpICMgcmVhZGluZyBleGNlbCBmaWxlcyB0byBsb2FkIGRhdGEKbGlicmFyeShnZ3Bsb3QyKSAjIGZvciB2aXN1YWxpemF0aW9uCmxpYnJhcnkoemlwZlIpICMgbmVlZGVkIGZvciBvbmUgb2YgdGhlIGRlY2lzaW9uIHJ1bGVzCmxpYnJhcnkoUmlza1BvcnRmb2xpb3MpICMgdXNlZCBmb3IgY292YXJpYW5jZSBtYXRyaXggc2hyaW5rYWdlCgpybShsaXN0ID0gbHMoKSkKYGBgClRoZSBmaXJzdCBpcyB1c2VkIGZvciBkb3dubG9hZGluZyBkYXRhIGFuZCB3b3JraW5nIHdpdGggdGltZSBzZXJpZXMuIFRoZSBzZWNvbmQgaXMgZm9yIGRhdGUgbWFuaXB1bGF0aW9uIGFuZCBmb3JtYXR0aW5nLiBUaGUgdGhpcmQgY2FuIGJlIGVmZmljaWVudGx5IHV0aWxpemVkIG9uIExpbnV4IG1hY2hpbmVzIHRvIHBlcmZvcm0gcGFyYWxsZWwgY29tcHV0aW5nIHVzaW5nIGZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcsIGUuZy4sIHdvcmtpbmcgd2l0aCBgbGFwcGx5YC4gVGhlIGZvdXJ0aCBvbmUgaXMgdG8gcmVhZCB4bHN4IGZvcm1hdHRlZCBmaWxlcywgd2hlcmVhcyB0aGUgZmlmdGggaXMgdXNlZCBmb3IgdmlzdWFsaXphdGlvbi4gVGhlIGxhc3QgdHdvIGFyZSB1c2VkIGluIHRoZSBpbXBsZW1lbnRhdGlvbiBvZiB0aGUgZGVjaXNpb24gcnVsZXMuIFNwZWNpZmljYWxseSwgSSB1dGlsaXplIHRoZSBsaWJyYXJ5IGB6aXBmUmAgIHRvIGltcGxlbWVudCB0aGUgaW5jb21wbGV0ZSBiZXRhIGZ1bmN0aW9uIGFzIGFkdmlzZWQgYnkgS1daLiBGaW5hbGx5LCBJIGVtcGxveSB0aGUgbGluZWFyIHNocmlua2FnZSBmb3IgY292YXJpYW5jZSBtYXRyaWNlcyBmcm9tIHRoZSBgUmlza1BvcnRmb2xpb3NgIGxpYnJhcnkuIAoKCiMgR2V0dGluZyB0aGUgRGF0YQpLV1ogcmVseSBvbiBlaWdodCBkaWZmZXJlbnQgZGF0YSBzb3VyY2VzIHRvIGV2YWx1YXRlIGRpZmZlcmVudCBkZWNpc2lvbiBydWxlcy4gRm91ciBvZiB0aGVzZSBjb21lIGZyb20gS2VuIEZyZW5jaCdzIHdlYnNpdGUsIHR3byBjb21lIGZyb20gUm9iZXJ0IE5vdnktTWFyeCdzIHdlYnNpdGUsIGFuZCB0aGUgcmVtYWluaW5nIGFyZSBjb25zdHJ1Y3RlZCBmcm9tIHRoZSBDUlNQIGRhdGFiYXNlLiBJbiB0aGlzIGFydGljbGUsIEkgZm9jdXMgb24gdGhlIGZpcnN0IHNpeCBkYXRhIHNldHMgd2hpY2ggYXJlIHB1YmxpY2x5IGF2YWlsYWJsZToKMS4gMTAgbW9tZW50dW0gcG9ydGZvbGlvcyAoTW9tZW50dW0pOyBKYW51YXJ5IDE5MjcgdG8gRGVjZW1iZXIgMjAxOCAKMi4gMjUgRmFtYS1GcmVuY2ggJDUgXHRpbWVzIDUkIHNpemUgYW5kIGJvb2stdG8tbWFya2V0IHJhbmtlZCBwb3J0Zm9saW9zIChTaXplLUIvTSk7IGZyb20gSmFudWFyeSAxOTI3IHRvIERlY2VtYmVyIDIwMTggCjMuIDI1IHBvcnRmb2xpb3MgZm9ybWVkIG9uIG9wZXJhdGluZyBwcm9maXRhYmlsaXR5IGFuZCBpbnZlc3RtZW50IChPUC1JbnYpOyBmcm9tIEp1bHkgMTk2MyB0byBEZWNlbWJlciAyMDE4IAo0LiA0OSBpbmR1c3RyeSBwb3J0Zm9saW9zIChJbmR1c3RyeSk7IGZyb20gSnVseSAxOTY5IHRvIERlY2VtYmVyIDIwMTgKNS4gMTYgbG93IHR1cm5vdmVyIGFub21hbGllcyBieSBOb3Z5LU1hcnggYW5kIFZlbGlrb3YgKDIwMTYpOyBmcm9tIEp1bHkgMTk2MyB0byBEZWNlbWJlciAyMDEzCjYuIDQ2IGFsbCB0dXJub3ZlciBhbm9tYWxpZXMgYnkgTm92eS1NYXJ4IGFuZCBWZWxpa292ICgyMDE2KTsgZnJvbSBKdWx5IDE5NzMgdG8gRGVjZW1iZXIgMjAxMyAoYWZ0ZXIgZHJvcHBpbmcgbWlzc2luZyB2YWx1ZXMpCgoKV2l0aCBzb21lIG1pbmltYWwgdHdlYWtpbmcsIHdlIGNhbiBkb3dubG9hZCB0aGUgRmFtYS1GcmVuY2ggZGF0YSBpbiB0aGUgZm9sbG93aW5nIG1hbm5lciBhbmQgcHV0IHRoZSBmb3VyIGRhdGEgc2V0cyBpbnRvIGEgc2luZ2xlIGxpc3QgYXMgZm9sbG93czoKYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQojIE1PTUVUVU0gLSAgSmFudWFyeSAxOTI3IHRvIERlY2VtYmVyIDIwMTggCkZGX2ZpbGUgPC0gImh0dHBzOi8vbWJhLnR1Y2suZGFydG1vdXRoLmVkdS9wYWdlcy9mYWN1bHR5L2tlbi5mcmVuY2gvZnRwLzEwX1BvcnRmb2xpb3NfUHJpb3JfMTJfMl9DU1YuemlwIgp0ZW1wIDwtIHRlbXBmaWxlKCkKZG93bmxvYWQuZmlsZShGRl9maWxlLHRlbXApCnVuel9maWxlcyA8LSB1bnppcCh0ZW1wKQpkcyA8LSByZWFkLmNzdih1bnpfZmlsZXMsc2tpcCA9IDEwLHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpmbGFnX29icyA8LSBncmVwKCJBdmVyYWdlIEVxdWFsIixkc1ssMV0saWdub3JlLmNhc2UgPSBUKQpkcyA8LSBkc1sxOihmbGFnX29icy0xKSxdCmRzIDwtIGRhdGEuZnJhbWUoYXBwbHkoZHMsMixhcy5udW1lcmljKSkKbmFtZXMoZHMpWzFdIDwtICJkYXRlIgpkcyRkYXRlIDwtIGRzJGRhdGUqMTAwICArIDEKZHMkZGF0ZSA8LSB5bWQoZHMkZGF0ZSkKZHMkZGF0ZSA8LSBjZWlsaW5nX2RhdGUoZHMkZGF0ZSwibSIpIC0gMQpkc1ssLTFdIDwtIGRzWywtMV0vMTAwCmRzIDwtIGRzW2RzJGRhdGUgPj0gIjE5MjctMDEtMDEiLF0KZHMgPC0gZHNbZHMkZGF0ZSA8PSAiMjAxOC0xMi0zMSIsXQpkczEgPC0gZHMKCiMgMjVCTSAtIEphbnVhcnkgMTkyNyB0byBEZWNlbWJlciAyMDE4IApGRl9maWxlIDwtICJodHRwczovL21iYS50dWNrLmRhcnRtb3V0aC5lZHUvcGFnZXMvZmFjdWx0eS9rZW4uZnJlbmNoL2Z0cC8yNV9Qb3J0Zm9saW9zXzV4NV9DU1YuemlwIgp0ZW1wIDwtIHRlbXBmaWxlKCkKZG93bmxvYWQuZmlsZShGRl9maWxlLHRlbXApCnVuel9maWxlcyA8LSB1bnppcCh0ZW1wKQpkcyA8LSByZWFkLmNzdih1bnpfZmlsZXMsc2tpcCA9IDE1LHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpmbGFnX29icyA8LSBncmVwKCJBdmVyYWdlIEVxdWFsIixkc1ssMV0saWdub3JlLmNhc2UgPSBUKQpkcyA8LSBkc1sxOihmbGFnX29icy0xKSxdCmRzIDwtIGRhdGEuZnJhbWUoYXBwbHkoZHMsMixhcy5udW1lcmljKSkKbmFtZXMoZHMpWzFdIDwtICJkYXRlIgpkcyRkYXRlIDwtIGRzJGRhdGUqMTAwICArIDEKZHMkZGF0ZSA8LSB5bWQoZHMkZGF0ZSkKZHMkZGF0ZSA8LSBjZWlsaW5nX2RhdGUoZHMkZGF0ZSwibSIpIC0gMQpkc1ssLTFdIDwtIGRzWywtMV0vMTAwCmRzIDwtIGRzW2RzJGRhdGUgPj0gIjE5MjctMDEtMDEiLF0KZHMgPC0gZHNbZHMkZGF0ZSA8PSAiMjAxOC0xMi0zMSIsXQpkczIgPC0gZHMKCiMgMjVPUC1JbnYgSnVseSAxOTYzIHRvIERlY2VtYmVyIDIwMTggCkZGX2ZpbGUgPC0gImh0dHBzOi8vbWJhLnR1Y2suZGFydG1vdXRoLmVkdS9wYWdlcy9mYWN1bHR5L2tlbi5mcmVuY2gvZnRwLzI1X1BvcnRmb2xpb3NfT1BfSU5WXzV4NV9DU1YuemlwIgp0ZW1wIDwtIHRlbXBmaWxlKCkKZG93bmxvYWQuZmlsZShGRl9maWxlLHRlbXApCnVuel9maWxlcyA8LSB1bnppcCh0ZW1wKQpkcyA8LSByZWFkLmNzdih1bnpfZmlsZXMsc2tpcCA9IDI0LHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpmbGFnX29icyA8LSBncmVwKCJBdmVyYWdlIEVxdWFsIixkc1ssMV0saWdub3JlLmNhc2UgPSBUKQpkcyA8LSBkc1sxOihmbGFnX29icy0xKSxdCmRzIDwtIGRhdGEuZnJhbWUoYXBwbHkoZHMsMixhcy5udW1lcmljKSkKbmFtZXMoZHMpWzFdIDwtICJkYXRlIgpkcyRkYXRlIDwtIGRzJGRhdGUqMTAwICArIDEKZHMkZGF0ZSA8LSB5bWQoZHMkZGF0ZSkKZHMkZGF0ZSA8LSBjZWlsaW5nX2RhdGUoZHMkZGF0ZSwibSIpIC0gMQpkc1ssLTFdIDwtIGRzWywtMV0vMTAwCmRzIDwtIGRzW2RzJGRhdGUgPj0gIjE5NjMtMDctMDEiLF0KZHMgPC0gZHNbZHMkZGF0ZSA8PSAiMjAxOC0xMi0zMSIsXQpkczMgPC0gZHMKCiMgNDkgSW5kdXN0cnkgLSAgSnVseSAxOTY5IHRvIERlY2VtYmVyIDIwMTggCkZGX2ZpbGUgPC0gImh0dHBzOi8vbWJhLnR1Y2suZGFydG1vdXRoLmVkdS9wYWdlcy9mYWN1bHR5L2tlbi5mcmVuY2gvZnRwLzQ5X0luZHVzdHJ5X1BvcnRmb2xpb3NfQ1NWLnppcCIKdGVtcCA8LSB0ZW1wZmlsZSgpCmRvd25sb2FkLmZpbGUoRkZfZmlsZSx0ZW1wKQp1bnpfZmlsZXMgPC0gdW56aXAodGVtcCkKZHMgPC0gcmVhZC5jc3YodW56X2ZpbGVzLHNraXAgPSAxMSxzdHJpbmdzQXNGYWN0b3JzID0gRikKZmxhZ19vYnMgPC0gZ3JlcCgiQXZlcmFnZSBFcXVhbCIsZHNbLDFdLGlnbm9yZS5jYXNlID0gVCkKZHMgPC0gZHNbMTooZmxhZ19vYnMtMSksXQpkcyA8LSBkYXRhLmZyYW1lKGFwcGx5KGRzLDIsYXMubnVtZXJpYykpCm5hbWVzKGRzKVsxXSA8LSAiZGF0ZSIKZHMkZGF0ZSA8LSBkcyRkYXRlKjEwMCAgKyAxCmRzJGRhdGUgPC0geW1kKGRzJGRhdGUpCmRzJGRhdGUgPC0gY2VpbGluZ19kYXRlKGRzJGRhdGUsIm0iKSAtIDEKZHNbLC0xXSA8LSBkc1ssLTFdLzEwMApkcyA8LSBkc1tkcyRkYXRlID49ICIxOTY5LTA3LTAxIixdCmRzIDwtIGRzW2RzJGRhdGUgPD0gIjIwMTgtMTItMzEiLF0KZHM0IDwtIGRzCmBgYApUaGUgb3RoZXIgZGF0YSBzZXRzIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gUm9iZXJ0IE5vdnktTWFyeCdzIHdlYnNpdGUgYW5kIHByZXBhcmVkIGVmZmljaWVudGx5LiBOb3RlIHRoYXQgdGhlIGRhdGEgY292ZXJzIGRpZmZlcmVudCBkZWNpbGUgcG9ydGZvbGlvcyBjb25zdHJ1Y3RlZCBiYXNlZCBvbiBkaWZmZXJlbnQgY2hhcmFjdGVyaXN0aWNzLiBTaW1pbGFyIHRvIEtXWiwgdGhlIGZpcnN0IGRhdGEgc2V0IGZvY3VzZXMgb24gdGhlIGxvdyB0dXJub3ZlciBhbm9tYWx5IHN0cmF0ZWdpZXMsIHdoaWNoIGNvcnJlc3BvbmQgdG8KYGBge3J9Cmxvd190b19zdHJhdCA8LSBjKCJTaXplIiwiR3Jvc3MgUHJvZml0YWJpbGl0eSIsCiAgICAgICAgICAgICAgICAgICJWYWx1ZSIsIlZhbFByb2YiLAogICAgICAgICAgICAgICAgICAiQWNjcnVhbHMiLCJBc3NldCBHcm93dGgiLAogICAgICAgICAgICAgICAgICAiSW52ZXN0bWVudCIsIlBpb3Ryb3NraSdzIEYtc2NvcmUiKQoKbG93X3RvX3N0cmF0CmBgYAoKVGhlIGRhdGEgY2FuIGJlIGRvd25sb2FkZWQgYW5kIHByZXBhcmVkIGluIHRoZSBmb2xsb3dpbmcgd2F5OiAKYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpmaWxlLmkgPC0gImh0dHA6Ly9ybm0uc2ltb24ucm9jaGVzdGVyLmVkdS9kYXRhX2xpYi9Ub0FhdFRDL1NpbXBsZV9TdHJhdGVnaWVzX1JldHVybnMueGxzeCIKdGVtcCA8LSB0ZW1wZmlsZSgpCmRvd25sb2FkLmZpbGUoZmlsZS5pLHRlbXApCmRzX3NoZWV0c19hbGwgPC0gc29ydChleGNlbF9zaGVldHModGVtcCkpCmRzX3NoZWV0cyA8LSBkc19zaGVldHNfYWxsW2RzX3NoZWV0c19hbGwgJWluJSBsb3dfdG9fc3RyYXRdCk5NX2RhdGFfbGlzdCA8LSBsaXN0KCkKZm9yIChzaGVldF9pIGluIGRzX3NoZWV0cykgewogIGRzX2kgPC0gZGF0YS5mcmFtZShyZWFkX3hsc3godGVtcCxzaGVldCA9IHNoZWV0X2kpKQogIGRzX2kgPC0gZHNfaVssYygxLDIsbmNvbChkc19pKSldCiAgbmFtZXMoZHNfaSlbLTFdIDwtIHBhc3RlKHNoZWV0X2ksbmFtZXMoZHNfaSlbLTFdLHNlcCA9ICJfIikKICBkc19pJGRhdGUgPC0gZHNfaSRNb250aAogIGRzX2kkZGF0ZSA8LSB5bWQoZHNfaSRkYXRlKjEwMCArIDEpCiAgZHNfaSRkYXRlIDwtIGNlaWxpbmdfZGF0ZShkc19pJGRhdGUsIm0iKSAtIDEKICBkc19pJE1vbnRoIDwtIE5VTEwKICBkc19pWywxOjJdIDwtIGRzX2lbLDE6Ml0vMTAwCiAgTk1fZGF0YV9saXN0IDwtIGMoTk1fZGF0YV9saXN0LGxpc3QoZHNfaSkpCn0KCk5NX2RhdGFfTFQgPC0gUmVkdWNlKG1lcmdlLE5NX2RhdGFfbGlzdCkKTk1fZGF0YV9MVCA8LSBuYS5vbWl0KE5NX2RhdGFfTFQpCmBgYApOb3RlIHRoYXQgZXZlbiB0aG91Z2ggd2UgY29uc2lkZXIgZWlnaHQgY2hhcmFjdGVyaXN0aWNzLCB3ZSBoYXZlIDE2IHBvcnRmb2xpb3MuIFNpbWlsYXIgdG8gS1daLCBJIGluY2x1ZGUgYm90aCB0aGUgbG9uZyAodG9wIGRlY2lsZSkgYW5kIHNob3J0IChib3R0b20gZGVjaWxlKSBwb3J0Zm9saW9zCmBgYHtyfQpkaW0oTk1fZGF0YV9MVCkKYGBgCgpLV1ogYWxzbyBjb25zaWRlcnMgYWxsIDIzIGFub21hbGllcyBjb3JyZXNwb25kaW5nIHRvIGxvdywgbWVkaXVtLCBhbmQgaGlnaCB0dXJub3ZlciBzdHJhdGVnaWVzLiBTYW1lIGFzIGFib3ZlLCB3ZSBjb25zaWRlciB0b3AgYW5kIGJvdHRvbSBkZWNpbGVzLCByZXN1bHRpbmcgaW4gNDYgYXNzZXRzLiAKYGBge3J9Cm1pZF90b19zdHJhdCA8LSBjKCJOZXQgSXNzdWFuY2UgKHJlYmFsLi1BKSIsIlJldHVybi1vbi1ib29rIGVxdWl0eSIsIkZhaWx1cmUgUHJvYmFiaWxpdHkiLAogICAgICAgICAgICAgICAgICAiVmFsTW9tUHJvZiIsIlZhbE1vbSIsIklkaW9zeW5jcmF0aWMgVm9sYXRpbGl0eSIsCiAgICAgICAgICAgICAgICAgICJNb21lbnR1bSIsIlBFQUQgKFNVRSkiLCJQRUFEIChDQVIzKSIpCmhpZ2hfdG9fc3RhcnQgPC0gYygiSW5kdXN0cnkgTW9tZW50dW0iLCJJbmR1c3RyeSBSZWxhdGl2ZSBSZXZlcnNhbHMiLCJIaWdoLWZyZXF1ZW5jeSBDb21ibyIsCiAgICAgICAgICAgICAgICAgICAiU2hvcnQtcnVuIFJldmVyc2FscyIsIlNlYXNvbmFsaXR5IiwiSVJSIChMb3cgVm9sKSIpCmFsbF90b19zdGFydCA8LSB1bmlxdWUoc29ydChjKGxvd190b19zdHJhdCxtaWRfdG9fc3RyYXQsaGlnaF90b19zdGFydCkpKQpsZW5ndGgoYWxsX3RvX3N0YXJ0KQpgYGAKTGV0IHVzIHJlcGVhdCB0aGUgc2FtZSBsb29wIGFzIGJlZm9yZSB0byBjcmVhdGUgbG9uZyBhbmQgc2hvcnQgcG9ydGZvbGlvczoKYGBge3J9CmRzX3NoZWV0cyA8LSBkc19zaGVldHNfYWxsW2RzX3NoZWV0c19hbGwgJWluJSBhbGxfdG9fc3RhcnRdCk5NX2RhdGFfbGlzdCA8LSBsaXN0KCkKZm9yIChzaGVldF9pIGluIGRzX3NoZWV0cykgewogIGRzX2kgPC0gZGF0YS5mcmFtZShyZWFkX3hsc3godGVtcCxzaGVldCA9IHNoZWV0X2kpKQogIGRzX2kgPC0gZHNfaVssYygxLDIsbmNvbChkc19pKSldCiAgbmFtZXMoZHNfaSlbLTFdIDwtIHBhc3RlKHNoZWV0X2ksbmFtZXMoZHNfaSlbLTFdLHNlcCA9ICJfIikKICBkc19pJGRhdGUgPC0gZHNfaSRNb250aAogIGRzX2kkZGF0ZSA8LSB5bWQoZHNfaSRkYXRlKjEwMCArIDEpCiAgZHNfaSRkYXRlIDwtIGNlaWxpbmdfZGF0ZShkc19pJGRhdGUsIm0iKSAtIDEKICBkc19pJE1vbnRoIDwtIE5VTEwKICBkc19pWywxOjJdIDwtIGRzX2lbLDE6Ml0vMTAwCiAgTk1fZGF0YV9saXN0IDwtIGMoTk1fZGF0YV9saXN0LGxpc3QoZHNfaSkpCn0KCk5NX2RhdGFfQUxMIDwtIFJlZHVjZShtZXJnZSxOTV9kYXRhX2xpc3QpCk5NX2RhdGFfQUxMIDwtIG5hLm9taXQoTk1fZGF0YV9BTEwpCmRpbShOTV9kYXRhX0FMTCkKYGBgCgpGaW5hbGx5LCB3ZSBwdXQgdGhlIGRhdGEgYWx0b2dldGhlciBpbiBhIHNpbmdsZSBsaXN0LCBzbyB3ZSBjYW4gZWFzaWx5IGdlbmVyYWxpemUgdGhlIGFuYWx5c2lzIGZvciBhbnkgZ2l2ZW4gZWxlbWVudCByZXRyaWV2ZWQgZnJvbSB0aGUgbGlzdC4KYGBge3J9CmRzX2xpc3QgPC0gbGlzdChkczEsZHMyLGRzMyxkczQsTk1fZGF0YV9MVCxOTV9kYXRhX0FMTCkKbmFtZXMoZHNfbGlzdCkgPC0gYygiTU9NMTAiLCJCTTI1IiwiT1BJTjI1IiwiSU5ENDkiLCJOTVYxNiIsIk5NVjQ2IikKc2FwcGx5KGRzX2xpc3QsbnJvdykKYGBgCkVhY2ggZGF0YSBoYXMgYSBkaWZmZXJlbnQgbnVtYmVyIG9mIG9ic2VydmF0aW9ucy4gVGhlcmVmb3JlLCBpdCBpcyBwcmVmZXJhYmxlIHRvIGVuc3VyZSBhbGwgZGF0YSBoYXZlIHRoZSBleGFjdCBkYXRlcyBvciBjb3ZlciB0aGUgc2FtZSBwZXJpb2QgZm9yIGNvbnNpc3RlbnQgY29tcGFyaXNvbi4gRm9yIGluc3RhbmNlLCB0aGlzIGNvdWxkIGhlbHAgdXMgdW5kZXJzdGFuZCB3aHkgc3BlY2lmaWMgZGVjaXNpb24gcnVsZXMgd29yayBmb3Igb25lIGRhdGEgc2V0IGJ1dCBub3QgYW5vdGhlci4gTm9uZXRoZWxlc3MsIGluIHRoaXMgZXhhbXBsZSwgSSB3aWxsIGZvbGxvdyB0aGUgYW5hbHlzaXMgYnkgS1daIGZvciByZXByb2R1Y2liaWxpdHkuIAoKQXMgYSBmaW5hbCB0d2VhaywgbGV0IHVzIHNvcnQgdGhlIG9yZGVyIG9mIHRoZSBkYXRhIHNldHMgaW4gbGluZSB3aXRoIHRoZSBvcmRlciByZXBvcnRlZCBpbiBhbGwgbWFpbiB0YWJsZXMgYnkgS1daLCB3aGljaCBpcyBhcyBmb2xsb3dzOgpgYGB7cn0KZHNfbGlzdCA8LSBkc19saXN0W2MoIk1PTTEwIiwiQk0yNSIsIk9QSU4yNSIsIk5NVjE2IiwiTk1WNDYiLCJJTkQ0OSIpXQpzYXBwbHkoZHNfbGlzdCxuY29sKSAtIDEKYGBgClRoZSBvbmx5IHR3byBkYXRhIHNldHMgbWlzc2luZyBhcmUgdGhlIElWT0wgKCROID0gMTAkKSBhbmQgIFN0b2NrcyAoJE4gPSAxMDAkKSwgd2hlcmUgJE4kIGRlbm90ZXMgdGhlIG51bWJlciBvZiBhc3NldHMuIFRoZXNlIHR3byBkYXRhIHNldHMgYXJlIHVzZXItYmFzZWQsIHN1Y2ggdGhhdCB0aGUgcmVwbGljYXRpb24gZXhlcmNpc2UgaXMgc3ViamVjdGVkIHRvIGFkZGl0aW9uYWwgZGVncmVlcyBvZiBmcmVlZG9tIGRlcGVuZGluZyBvbiB0aGUgcmVzZWFyY2hlcidzIGFiaWxpdHkgdG8gcmVwbGljYXRlIHRoZSBkYXRhLiBJbiBjb250cmFzdCwgdGhlIGFib3ZlIHNpeCBkYXRhIHNldHMgYXJlIHRha2VuIGZvciBncmFudGVkLCBnaXZlbiB0aGUgcHVibGljbHkgYXZhaWxhYmxlIGRhdGEgbGlicmFyaWVzLiBUaGVyZWZvcmUsIHdlIGZvY3VzIG9uIHRoZSBhYm92ZSBzaXggcHVibGljbHkgYXZhaWxhYmxlIGRhdGEgc2V0cyBmb3IgdGhlIHNha2Ugb2YgcmVwcm9kdWNpYmlsaXR5LgoKCkJlZm9yZSB3ZSBtb3ZlIHRvIHRoZSBtYWluIGFuYWx5c2lzLCBub3RlIHRoYXQgS1daIGNvbnNpZGVyIHRoZSBleGNlc3MgcmV0dXJucyBvbiBhbGwgcG9ydGZvbGlvcy4gVG8gZG8gc28sIGxldCB1cyBkb3dubG9hZCB0aGUgcmlzay1mcmVlIHJhdGUgdXNpbmcgdGhlIEZhbWEtRnJlbmNoIHRocmVlIGZhY3RvcnMgZGF0YToKYGBge3J9CkZGX2ZpbGUgPC0gImh0dHBzOi8vbWJhLnR1Y2suZGFydG1vdXRoLmVkdS9wYWdlcy9mYWN1bHR5L2tlbi5mcmVuY2gvZnRwL0YtRl9SZXNlYXJjaF9EYXRhX0ZhY3RvcnNfQ1NWLnppcCIKdGVtcCA8LSB0ZW1wZmlsZSgpCmRvd25sb2FkLmZpbGUoRkZfZmlsZSx0ZW1wKQp1bnpfZmlsZXMgPC0gdW56aXAodGVtcCkKZHMgPC0gcmVhZC5jc3YodW56X2ZpbGVzLHNraXAgPSAzKQpmbGFnX29icyA8LSBncmVwKCJBbm51YWwiLGRzWywxXSxpZ25vcmUuY2FzZSA9IFQpCmRzIDwtIGRzWzE6KGZsYWdfb2JzLTEpLF0KbmFtZXMoZHMpWzFdIDwtICJkYXRlIgpkcyA8LSBkYXRhLmZyYW1lKGFwcGx5KGRzLCAyLCBhcy5udW1lcmljKSkKZHMkZGF0ZSA8LSBjZWlsaW5nX2RhdGUoeW1kKGRzJGRhdGUqMTAwKyAwMSksIm0iKS0xCmRzIDwtIGRzWyxjKCJkYXRlIiwiUkYiKV0KZHMkUkYgPC0gZHMkUkYvMTAwCmRzX3JmIDwtIGRzCnJtKGRzKQpgYGAKTm93IHRoYXQgd2UgaGF2ZSB0aGUgcmlzay1mcmVlIHJhdGUgbGV0IHVzIG1lcmdlIHdpdGggdGhlIGRhdGEgbGlzdCBhbmQgYWRqdXN0IHRoZSByZXR1cm5zIGFjY29yZGluZ2x5IHVzaW5nIHRoZSBmb2xsb3dpbmcgZnVuY3Rpb246CmBgYHtyfQphZGp1c3RfcmYgPC0gZnVuY3Rpb24oZHNfaSkgewogIGRzX2kgPC0gbWVyZ2UoZHNfaSxkc19yZiwgYnkgPSAiZGF0ZSIpCiAgUkYgPC0gZHNfaSRSRgogIGRzX2lbLC0xXSA8LSBkc19pWywtMV0gLSBSRgogIGRzX2kkUkYgPC0gTlVMTAogIHJldHVybihkc19pKQp9CmBgYApXZSBjYW4gYWRqdXN0IHRoZSBkYXRhIGluIHRoZSBmb2xsb3dpbmcgbWFubmVyOgpgYGB7cn0KZHNfbGlzdCA8LSBsYXBwbHkoZHNfbGlzdCxhZGp1c3RfcmYpCnNhdmVSRFMoZHNfbGlzdCwiZHNfbGlzdC5SRFMiKSAjIHNhdmUgZGF0YSBqdXN0IGluIGNhc2UKYGBgCkZpbmFsbHksIHdlIGFyZSBzZXQgdG8gYmVnaW4gb3VyIG1haW4gaW52ZXN0aWdhdGlvbi4KCgojIERlY2lzaW9uIFJ1bGVzCldlIGNvbnNpZGVyIDExIGRlY2lzaW9uIHJ1bGVzIGluIHRvdGFsLiBXaXRob3V0IGFueSBwb3NpdGlvbiBjb25zdHJhaW50cywgbW9zdCBkZWNpc2lvbiBydWxlcyBzdHVkaWVkIGJ5IEtXWiBjYW4gYmUgaW1wbGVtZW50ZWQgdXNpbmcgY2xvc2VkLWZvcm0gc29sdXRpb25zIGJhc2VkIG9uIGlucHV0cy9lc3RpbWF0ZXMuIEhvd2V2ZXIsIGluIHRoZSBjYXNlIG9mIHNob3J0LXNhbGVzIGNvbnN0cmFpbnRzLCB3ZSBuZWVkIHRvIHNvbHZlIGZvciBvcHRpbWFsIHBvcnRmb2xpb3MgbnVtZXJpY2FsbHkuIEFzIGEgcmVmZXJlbmNlLCBJIGNvdmVyZWQgdGhlIG51bWVyaWNhbCBvcHRpbWl6YXRpb24gaW4gb25lIG9mIG15IGVhcmxpZXIgcG9zdHMgLSBzZWUgIFtsaW5rXShodHRwczovL3JwdWJzLmNvbS9zaW1hYW44NC9wb3J0X29wdCkuCgpUaGUgZm9sbG93aW5nIGNvZGVzL2Z1bmN0aW9ucyBhcmUgdGhlIGJ1aWxkaW5nIGJsb2NrcyB0byBwZXJmb3JtaW5nIG51bWVyaWNhbCBwb3J0Zm9saW8gb3B0aW1pemF0aW9uOgpgYGB7cn0KVSA8LSBmdW5jdGlvbihYLE11LFNpZ21hLGdhbW1hKSB7CiAgdTEgPC0gdChYKSUqJShNdSkKICB1MiA8LSB0KFgpJSolU2lnbWElKiVYCiAgdG90YWwgPC0gdTEgLSAoZ2FtbWEvMikqdTIKICByZXR1cm4oYyh0b3RhbCkpCn0KCkcgPC0gZnVuY3Rpb24oWCxNdSxTaWdtYSxnYW1tYSkgewogIHRvdGFsIDwtICBNdSAtIGdhbW1hKlNpZ21hJSolWAogIHJldHVybih0b3RhbCkKfQoKVTIgPC0gZnVuY3Rpb24oWCxTaWdtYSkgewogIHUyIDwtIHQoWCklKiVTaWdtYSUqJVgKICByZXR1cm4oYyh1MikpCn0KCgpHMiA8LSBmdW5jdGlvbihYLFNpZ21hKSB7CiAgZzIgPC0gU2lnbWElKiVYCiAgdG90YWwgPC0gZzIKICByZXR1cm4odG90YWwpCn0KCiMgYWRkIGNvbnN0cmFpbnRzCkJDX2YgPC0gZnVuY3Rpb24oZCkgewogICMgc3VtIHRvIG9uZSBjb25zdHJhaW50CiAgQSA8LSBtYXRyaXgoMSwxLGQpCiAgQSA8LSByYmluZChBLC1BKQogIEIgPC0gYygwLjk5OTk5LC0xLjAwMDAxKQogIAogICMgc2hvcnQtc2FsZXMgY29uc3RyYWludHMKICBBMiA8LSBkaWFnKHJlcCgxLGQpKQogIEIyIDwtIHJlcCgwLGQpCiAgQTIgPC0gcmJpbmQoQSxBMikKICBCMiA8LSBjKEIsQjIpCiAgCiAgIyBzdGFjayBhbHRvZ2V0aGVyIGluIGEgbGlzdAogIEJDMSA8LSBsaXN0KEEsQikKICBCQzIgPC0gbGlzdChBMixCMikKICBsaXN0KEJDMSxCQzIpCn0KYGBgClNwZWNpZmljYWxseSwgYFVgIGRlbm90ZXMgdGhlIG9iamVjdGl2ZSBmdW5jdGlvbiB0aGF0IGNhcHR1cmVzIHRoZSBtZWFuLXZhcmlhbmNlIHByZWZlcmVuY2UsIHdoZXJlYXMgYGdhbW1hYCBkZW5vdGVzIHRoZSByaXNrIGF2ZXJzaW9uIGxldmVsLiBUbyByZXN1bHQgaW4gZmFzdGVyIGNvbnZlcmdlbmNlLCBJIGFsc28gZGVmaW5lIHRoZSBncmFkaWVudCBmdW5jdGlvbiwgZGVub3RlZCBieSBgR2AuIEhlbmNlLCBmb3IgYSBnaXZlbiBtZWFuIHZlY3RvciBhbmQgY292YXJpYW5jZSBtYXRyaXgsIHdlIGNhbiB0cmFpbiB0aGUgYWxnb3JpdGhtIHRvIHNlZWsgdGhlIG9wdGltYWwgcG9pbnQgdXNpbmcgZ3JhZGllbnQgZGVzY2VudCwgZS5nLiwgTmV3dG9uJ3MgbWV0aG9kLiBUaGUgYFUyYCBhbmQgYEcyYCBmb2xsb3cgc3VpdCwgd2hlcmVhcyB0aGUgbWFpbiBkaWZmZXJlbmNlIGlzIHRoYXQgdGhlIG9iamVjdGl2ZSBpcyB0byBtaW5pbWl6ZSB0aGUgcG9ydGZvbGlvIHZhcmlhbmNlIHJhdGhlciB0aGFuIG9wdGltaXplIGEgbWVhbi12YXJpYW5jZSB0cmFkZS1vZmYuIEZpbmFsbHksIHRoZSBgQkNfZmAgZnVuY3Rpb24gaXMgd3JpdHRlbiB0byBpbXBvc2UgY29uc3RyYWludHMgb24gdGhlIG9wdGltaXphdGlvbiBwcm9ibGVtLiBCeSBkZXNpZ24sIHRoZSBiYXNlIGZ1bmN0aW9uIGBjb25zdHJPcHRpbWAgdGFrZXMgY29uc3RyYWludHMgaW4gYSBsaW5lYXIgZm9ybSwgc3VjaCB0aGF0IGl0IGNhbiBiZSB3cml0dGVuIGFzIAokJApcYmVnaW57YWxpZ25lZH1cbWluX3tcbWF0aGJme3h9fVxxdWFkICYgZihcbWF0aGJme3h9KVxcClx0ZXh0cm17cy50Ln1ccXVhZCAmIFxtYXRoYmZ7QX1cbWF0aGJme3h9XGdlcVxtYXRoYmZ7Yn1cXApcXApcZW5ke2FsaWduZWR9CiQkCkluIHRoaXMgcmVnYXJkLCB0aGUgYEJDX2ZgIGZ1bmN0aW9uIHJldHVybnMgdGhlIHJlbGV2YW50ICRcbWF0aGJme0F9JCBhbmQgJFxtYXRoYmZ7Yn0kIG1hdHJpY2VzIHRvIHJlZmxlY3QgdHdvIGNvbnN0cmFpbnRzOgokJApcbWF0aGJmezF9XntcdG9wfVxtYXRoYmZ7eH0gPSAxIFxcCnhfaSBcZ2VxIDAgXCwgXGZvcmFsbCBpPTEsLi4uLGQKJCQKRm9yIGluc3RhbmNlLCB0aGUgbmFpdmUgcG9ydGZvbGlvIChpLmUuLCB0aGUgZXF1YWxseSB3ZWlnaHRlZCkgY29uZmlybXMgYm90aCBjb25zdHJhaW50czoKYGBge3J9CmQgPC0gMTAKQSA8LSBCQ19mKGQpW1syXV1bWzFdXQpiIDwtIEJDX2YoZClbWzJdXVtbMl1dCnggPC0gcmVwKDEvZCxkKQphbGwoQSUqJXggPj0gYikKYGBgCkluIHRoaXMgcmVnYXJkLCB3ZSBjYW4gZWFzaWx5IGFkZCB0aGUgYEFgIGFuZCBgYmAgaW50byB0aGUgb3B0aW1pemF0aW9uIGZ1bmN0aW9uIHRvIGNvbXBseSB3aXRoIHRoZXNlIHR3byBjb25zdHJhaW50cy4gCgpCZWZvcmUgd2UgcHJvY2VlZCB0byB0aGUgZGVjaXNpb24gcnVsZSwgaXQgaXMgd29ydGggbm90aW5nIHRoYXQgYW55IGRlY2lzaW9uIHJ1bGUgY2FuIGJlIHZpZXdlZCBhcyBhIG1hcHBpbmcgZnVuY3Rpb24gb2YgdGhlIGRhdGEgaW50byBwb3J0Zm9saW8gd2VpZ2h0cywgaS5lLiwgJGY6XG1hdGhjYWx7RH1ccmlnaHRhcnJvdyBcbWF0aGJme3d9JCwgd2hlcmUgJFxtYXRoY2Fse0R9JCBpcyB0aGUgZGF0YSBhcyB3ZWxsIGFzIG90aGVyIGlucHV0cywgJGYkIGlzIHRoZSBtYXBwaW5nIGZ1bmN0aW9uIChkZWNpc2lvbiBydWxlKSwgYW5kICRcbWF0aGJme3d9JCBpcyB0aGUgY29ycmVzcG9uZGluZyBwb3J0Zm9saW8gd2VpZ2h0cyBmb3IgdGhhdCBkZWNpc2lvbiBydWxlLiBGb3IgaW5zdGFuY2UsIGluIHRoZSBjYXNlIG9mIHRoZSBtZWFuLXZhcmlhbmNlIHBvcnRmb2xpbywgdGhlIGRhdGEgaXMgbWFwcGVkIGludG8gdGhlIG1lYW4gdmVjdG9yIGFuZCBjb3ZhcmlhbmNlIG1hdHJpeCwgd2hpY2ggYXJlIGV2ZW50dWFsbHkgdXNlZCBhcyB0aGUgcHJpbWFyeSBpbnB1dHMgdG8gZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIHBvcnRmb2xpbyB3ZWlnaHRzLiBJbiB0aGlzIGNhc2UsIHRoZSBtYWluIGlucHV0cyBhbHNvIHJlcXVpcmUgYSBnaXZlbiByaXNrIGF2ZXJzaW9uIGxldmVsLiBIZW5jZSwgdG8gaW1wbGVtZW50IGRpZmZlcmVudCBkZWNpc2lvbiBydWxlcywgd2UgbmVlZCB0byB3cml0ZSBhIGdlbmVyYWwgZnVuY3Rpb24gdGhhdCB0YWtlcyBhIGRhdGEgc3Vic2V0IGBSX3N1YmAgd2l0aCBkaWZmZXJlbnQgaW5wdXRzIGFuZCByZXR1cm5zIHRoZSBwb3J0Zm9saW8gd2VpZ2h0cy4gQmFzZWQgb24gdGhlIG91dGNvbWUsIHdlIGNhbiBldmFsdWF0ZSBkaWZmZXJlbnQgZGVjaXNpb24gcnVsZXMgb3ZlciB0aW1lLCBjb3JyZXNwb25kaW5nIHRvIGEgYmFja3Rlc3RpbmcgcHJvY2VkdXJlLiBJbiB0aGUgZW5kLCBzdWNoIGEgZnVuY3Rpb24gbWFwcyB0aGUgZGF0YSBhbmQgb3RoZXIgcHJlZmVyZW5jZXMgaW50byBmdXR1cmUgYWxsb2NhdGlvbnMuIFdlIGRlZmluZSBzdWNoIGEgZnVuY3Rpb24gYXMgZm9sbG93cwpgYGB7cn0KRFJfZnVuY3Rpb24gPC0gZnVuY3Rpb24oUl9zdWIsZ2FtbWEsc2FtcGxlX3NpemUsVEMpIHsKICAKICAjIHRha2VzIGRhdGEgIG9iamVjdCBSX3N1YiAtIHNlZSBiZWxvdwogIFJfc3ViMiA8LSB0YWlsKFJfc3ViLHNhbXBsZV9zaXplKQogIFJfc3ViMiA8LSBSX3N1YjJbLC0xXQogIGQgPC0gbmNvbChSX3N1YjIpCiAgTXUgPC0gYXBwbHkoUl9zdWIyLDIsbWVhbikKICBTaWdtYSA8LSB2YXIoUl9zdWIyKSooZC0xKS9kICMgZm9yIE1MRSBlc3RpbWF0ZSBkaXZpZGUgYnkgZAogIFNpZ19pbnYgPC0gc29sdmUoU2lnbWEpCiAgZSA8LSByZXAoMSxkKQogIAogICMgZmlyc3QgcG9ydGZvbGlvIGlzIHRoZSBnbG9iYWwgbWluaW11bSB2YXJpYW5jZSBwb3J0Zm9saW8uIEtXWiBkZW5vdGUgaXQgYnkgJFxoYXR7d31fe2csdH0kCiAgR01WIDwtIFNpZ19pbnYlKiVlL3N1bShTaWdfaW52KQogIEJfbWF0IDwtIFNpZ19pbnYlKiUoIGRpYWcoZCkgLSBlJSoldChHTVYpICApCiAgIyB0aGUgc2Vjb25kIGlzIHRoZSBwbHVnLWluIHBvcnRmb2xpby4gS1daIGRlbm90ZSBpdCBieSAkXGhhdHt3fV97eix0fSQKICBNViA8LSBHTVYgKyAoMS9nYW1tYSkqQl9tYXQlKiVNdQogIAogICMgdGhlIHVuYmlhc2VkIGVzdGltYXRlCiAgTVZfVUIgPC0gR01WICsgKHNhbXBsZV9zaXplIC0gZCAtIDEpLyhnYW1tYSpzYW1wbGVfc2l6ZSkqQl9tYXQlKiVNdQogIAogICMgTmFpdmUgUG9ydGZvbGlvCiAgTmFpdmUgPC0gcmVwKDEvZCxkKQogIAogIEJDIDwtIEJDX2YoZClbWzJdXSAjIDIgZm9yIG5vIHNob3J0LXNhbGVzIGFuZCAxIGZvciB5ZXMKICBBIDwtIEJDW1sxXV0KICBCIDwtIEJDW1syXV0KICAKICAjIGluaXRpYWwgZ3Vlc3MgaXMgYmFzZWQgb24gbmFpdmUgcG9ydGZvbGlvCiAgWDAgPC0gTmFpdmUKICBYX29wdCA8LSBjb25zdHJPcHRpbShYMCwKICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSAtVSh4LE11LFNpZ21hLGdhbW1hKSwKICAgICAgICAgICAgICAgICAgICAgICBncmFkID0gZnVuY3Rpb24oeCkgLUcoeCxNdSxTaWdtYSxnYW1tYSksCiAgICAgICAgICAgICAgICAgICAgICAgdWkgPSBBLGNpID0gQikKICBYMSA8LSBYX29wdCRwYXIKICBNVl9OUyA8LSBYMS9zdW0oWDEpCiAgCiAgWF9vcHQgPC0gY29uc3RyT3B0aW0oWDAsZnVuY3Rpb24oeCkgVTIoeCxTaWdtYSksCiAgICAgICAgICAgICAgICAgICAgICAgZ3JhZCA9IGZ1bmN0aW9uKHgpIEcyKHgsU2lnbWEpLAogICAgICAgICAgICAgICAgICAgICAgIHVpID0gQSxjaSA9IEIpCiAgWDEgPC0gWF9vcHQkcGFyCiAgR01WX05TIDwtIFgxL3N1bShYMSkKICAKICAjIFZvbGF0aWxpdHkgVGltaW5nOiB0aGUgZmlyc3Qgc3RyYXRlZ3kgcHJvcG9zZWQgYnkgS2lyYnkgYW5kIE9zdGRpZWsgKDIwMTIpIAogIEtPX1ZUIDwtICgxL2RpYWcoU2lnbWEpKV40ICMgZm9vdG5vdGUgaW4gVGFibGUgMiBzdGF0ZXMgdGhhdCBldGEgPSA0CiAgS09fVlQgPC0gS09fVlQvc3VtKEtPX1ZUKQogIAogICMgUmV3YXJkLXRvLVJpc2sgVGltaW5nOiBUaGUgc2Vjb25kIHN0cmF0ZWd5IHByb3Bvc2VkIGJ5IEtpcmJ5IGFuZCBPc3RkaWVrICgyMDEyKSAKICBNdV9wbHVzIDwtIE11CiAgTXVfcGx1c1tNdV9wbHVzIDwgMF0gPC0gMAogIEtPX1JUIDwtIChNdV9wbHVzL2RpYWcoU2lnbWEpKV40ICMgZm9vdG5vdGUgaW4gVGFibGUgMiBzdGF0ZXMgdGhhdCBldGEgPSA0CiAgS09fUlQgPC0gS09fUlQvc3VtKEtPX1JUKQogIAogICMjIyBhZGQgS1daIGRlY2lzaW9uIHJ1bGUKICBOIDwtIGQKICBUIDwtIHNhbXBsZV9zaXplCiAgIyB0aGUgcHJvcG9zZWQgd2F5IHRvIGVzdGltYXRlIAogIHBzaTJoYXQgPC0gdChNVikgJSolIE11OwogIHBzaTJoYXQgPC0gKChULU4tMSkqcHNpMmhhdC0oTi0xKSkvVCArCiAgICAoKDIqKHBzaTJoYXQpXigoTi0xKS8yKSooKDErcHNpMmhhdCleKC0oVC0yKS8yKSkpLyhUKkliZXRhKHBzaTJoYXQvKDErcHNpMmhhdCksKE4tMSkvMiwoVC1OKzEpLzIpKSk7CiAgcHNpMmhhdCA8LSBhcy5udW1lcmljKHBzaTJoYXQpCiAgCiAga2FwcGFFIDwtICgoKFQtTikqKFQtTi0zKSkvKFQqKFQtMikpKSoocHNpMmhhdC8ocHNpMmhhdCsoTi0xKS9UKSk7CiAgS1daIDwtICBHTVYqKDEta2FwcGFFKSArIE1WKihrYXBwYUUpCiAgCiAgIyBMZWRvaXQgYW5kIFdvbGYgKDIwMDQpCiAgbV9pIDwtIGxpc3QodHlwZSA9ICJkaWFnIikKICBTaWdtYSA8LSBjb3ZFc3RpbWF0aW9uKGFzLm1hdHJpeChSX3N1YjIpLGNvbnRyb2wgPSBtX2kpCiAgU2lnX2ludiA8LSBzb2x2ZShTaWdtYSkKICBlIDwtIHJlcCgxLGQpCiAgCiAgIyB1cGRhdGUgdGhlIEdNViBiYXNlZCBvbiB0aGUgY292IGVzdGltYXRlCiAgR01WX0xXXzIwMDQgPC0gU2lnX2ludiUqJWUvc3VtKFNpZ19pbnYpCiAgQl9tYXQgPC0gU2lnX2ludiUqJSggZGlhZyhkKSAtIGUlKiV0KEdNVl9MV18yMDA0KSkKICBNVl9MV18yMDA0IDwtIEdNVl9MV18yMDA0ICsgKDEvZ2FtbWEpKkJfbWF0JSolTXUKICAKICAjIGRvIHRoZSBzYW1lIGZvciB0aGUgS1daIGRlY2lzaW9uIHJ1bGUKICBLV1pfTFdfMjAwNCA8LSAgR01WX0xXXzIwMDQqKDEta2FwcGFFKSArIE1WX0xXXzIwMDQqKGthcHBhRSkKICAKICAjIG9yZ2FuaXplIGluIGEgc2ltaWxhciBvcmRlciBhcyBpbiBLV1oKICBsaXN0KEtXWiA9IEtXWiwgTVYgPSBNViwgTVZfVUIgPSBNVl9VQiwKICAgICAgIEtXWl9MV18yMDA0ID0gS1daX0xXXzIwMDQsIE1WX0xXXzIwMDQgPSBNVl9MV18yMDA0LAogICAgICAgTVZfTlMgPSBNVl9OUywKICAgICAgIEdNViA9IEdNVixHTVZfTlMgPSBHTVZfTlMsIAogICAgICAgTmFpdmUgPSBOYWl2ZSwKICAgICAgIEtPX1ZUID0gS09fVlQsS09fUlQgPSBLT19SVCkKfQoKYGBgCgpGb3IgYnJldml0eSwgSSBza2lwIHRoZSBkaXNjdXNzaW9uIG9mIHRoZSBhYm92ZSBkZWNpc2lvbiBydWxlcy4gSG93ZXZlciwgbm90ZSB0aGF0IHRoZSBmdW5jdGlvbiByZXR1cm5zIGVsZXZlbiBkZWNpc2lvbiBydWxlcyBpbiBhIGxpc3QuIEZvciBzdW1tYXJ5LCBJIHByb3ZpZGUgYmVsb3cgdGhlIG1hdGhlbWF0aWNhbCBkZWZpbml0aW9uIG9mIHJlYWNoIHJ1bGUgZnJvbSBLV1ogd2l0aCByZXNwZWN0IHRvIGVhY2ggZGVmaW5pdGlvbiB1c2VkIGluIHRoZSBgRFJfZnVuY3Rpb25gIGZ1bmN0aW9uOiAKPCEtLSBcYmVnaW57dGFibGV9W10gLS0+CjwhLS0gXGZvb3Rub3Rlc2l6ZSAtLT4KPCEtLSBcYmVnaW57dGFidWxhcn17bGxsfSAtLT4KPCEtLSBcaGxpbmUgLS0+CjwhLS0gXGhsaW5lIC0tPgo8IS0tIFx0ZXh0YmZ7UiBOb3RhdGlvbiB9ICAgJiBcdGV4dGJme0RlZmluaXRpb259ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYgXHRleHRiZntNYXRoZW1hdGljYWwgRGVmaW5pdGlvbn0gXFwgLS0+CjwhLS0gXGhsaW5lIC0tPgo8IS0tIFxobGluZSAtLT4KCjwhLS0gS1daICAgICAgICAgICAmIEtXWiBDbG9zZWQtRm9ybSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiAkXGhhdHt3fV97cSx0fSQgICAgICAgICAgICAgICAgXFwgLS0+CjwhLS0gTVYgICAgICAgICAgICAmIE1lYW4tVmFyaWFuY2UgUGx1ZyBpbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiAkXGhhdHt3fV97cCx0fSQgICAgICAgICAgICAgICAgXFwgLS0+CjwhLS0gTVZcX1VCICAgICAgICAmIE1lYW4tVmFyaWFuY2UgVW5iaWFzZWQgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiAkXGhhdHt3fV97dSx0fSQgICAgICAgICAgICAgICAgXFwgLS0+CjwhLS0gS1daXF9MV1xfMjAwNCAmIEtXWiBDbG9zZWQtRm9ybSB3aXRoIExpbmVhciBTaHJpbmthZ2UgICAgICAgICAgICAgJiAkXGhhdHt3fV57TFcyMDA0fV97cSx0fSQgICAgICAgXFwgLS0+CjwhLS0gTVZcX0xXXF8yMDA0ICAmIE1lYW4tVmFyaWFuY2Ugd2l0aCBMaW5lYXIgU2hyaW5rYWdlICAgICAgICAgICAgICAgJiAkXGhhdHt3fV57TFcyMDA0fV97cCx0fSQgICAgICAgXFwgLS0+CjwhLS0gTVZcX05TICAgICAgICAmIE1lYW4tVmFyaWFuY2UgUGx1Z2luIHdpdGhvdXQgU2hvcnQtU2FsZXMgICAgICAgICAmICRcaGF0e3d9XntOU31fe3AsdH0kICAgICAgICAgICBcXCAtLT4KPCEtLSBHTVYgICAgICAgICAgICYgR2xvYmFsIE1pbmltdW0gVmFyaWFuY2UgIFBsdWdpbiAgICAgICAgICAgICAgICAgICAgICAgICAmICRcaGF0e3d9X3tnLHR9JCAgICAgICAgICAgICAgICBcXCAtLT4KPCEtLSBHTVZcX05TICAgICAgICYgR2xvYmFsIE1pbmltdW0gVmFyaWFuY2UgUGx1Z2luIHdpdGhvdXQgU2hvcnQtU2FsZXMgICAgICAgJiAkXGhhdHt3fV97Zyx0fV57TlN9JCAgICAgICAgICAgXFwgLS0+CjwhLS0gTmFpdmUgICAgICAgICAmIEVxdWFsbHkgV2VpZ2h0ZWQgUG9ydGZvbGlvICAgICAgICAgICAgICAgICAgICAgICAgJiAkMS9OJCAgICAgICAgICAgICAgICAgICAgICAgICAgXFwgLS0+CjwhLS0gS09cX1ZUICAgICAgICAmIFZvbGF0aWxpdHkgVGltaW5nIGJ5IEtpcmJ5IGFuZCBPc3RkaWVrICgyMDEyKSAgICAgJiAkS09fe1ZUfSQgICAgICAgICAgICAgICAgICAgICAgXFwgLS0+CjwhLS0gS09cX1JUICAgICAgICAmIFJld2FyZC10by1SaXNrIFRpbWluZyBieSBLaXJieSBhbmQgT3N0ZGllayAoMjAxMikgJiAkS09fe1JUfSQgICBcXCAgICAgICAgICAgICAgICAgICAgXGhsaW5lIC0tPgo8IS0tIFxobGluZSAtLT4KPCEtLSBcZW5ke3RhYnVsYXJ9IC0tPgo8IS0tIFxlbmR7dGFibGV9IC0tPgohWyoqQ2FwdGlvbioqOiBUaGUgYWJvdmUgdGFibGUgZGVmaW5lcyB0aGUgZGVjaXNpb24gcnVsZXMgdXNlZCBpbiB0aGlzIGFydGljbGUgaW4gbGluZSB3aXRoIHRoZSBtYXRoZW1hdGljYWwgZGVmaW5pdGlvbnMgZnJvbSBLV1ouXShLV1pfRFJfZGVmLnBuZykKCgpOb3RlIHRoYXQgYXMgbG9uZyBhcyB0aGUgZnVuY3Rpb24gb3JnYW5pemVzIHRoZSBwb3J0Zm9saW8gd2VpZ2h0cyBpbiBhIHNpbmdsZSBsaXN0IGFzIGFib3ZlLCB0aGUgZm9sbG93aW5nIGJhY2t0ZXN0aW5nIHByb2NlZHVyZSBjYW4gYmUgZ2VuZXJhbGl6ZWQgdG8gYWNjb21tb2RhdGUgb3RoZXIgZGVjaXNpb24gcnVsZXMgYW5kIGVzdGltYXRpb24gbWV0aG9kcy4KCgojIEJhY2t0ZXN0aW5nCldlIGNhbiBydW4gdGhlIGBEUl9mdW5jdGlvbmAgb24gYSByb2xsaW5nIGJhc2lzIHRvIHBlcmZvcm0gYmFja3Rlc3RpbmcuIE5vbmV0aGVsZXNzLCBub3RlIHRoYXQgIHRocmVlIG1haW4gaW5wdXRzIGFyZSBuZWVkZWQgdG8gcmVkdXBsaWNhdGUgdGhlIHBlcmZvcm1hbmNlIHJlc3VsdHM6CjEuIFRoZSByaXNrIGF2ZXJzaW9uIGxldmVsIGRlbm90ZWQgYnkgJFxnYW1tYSQKMi4gVHJhbnNhY3Rpb25zIGNvc3QgZGVub3RlZCBieSBgVENgCjMuIFNhbXBsZSBzaXplICRUJAoKU2ltaWxhciB0byB0aGUgYmFzZWxpbmUgYW5hbHlzaXMgZnJvbSBLV1ogKHNlZSwgZS5nLiwgVGFibGVzIDEgYW5kIDIpLCBJIHNldCAKYGBge3J9CmdhbW1hIDwtIDMKVEMgPC0gMC8oMTAwXjIpICMgdG8gcmVmbGVjdCBiYXNpcyBwb2ludHMKc2FtcGxlX3NpemUgPC0gMTIwCmBgYApJbiB0aGUgZm9sbG93aW5nLCBJIHV0aWxpemUgdGhlIGBEUl9mdW5jdGlvbmAgYW5kIHdyaXRlIGEgc2luZ2xlIG1haW4gZnVuY3Rpb24gdGhhdCB0YWtlcyB0aGUgZGF0YSBpbmRleCBhbmQgdGhlIGFib3ZlIHRocmVlIGlucHV0cyB0byBldmFsdWF0ZSB0aGUgZGVjaXNpb24gcnVsZXMgaW4gdGVybXMgb2Ygb3V0LW9mLXNhbXBsZSByZXR1cm5zLgoKKipQZXJzb25hbCBOb3RlOioqIEl0IGlzIHdvcnRoIG1lbnRpb25pbmcgdGhhdCB0aGUgZnVuY3Rpb24gd2FzIG5vdCB3cml0dGVuIGRpcmVjdGx5LiBJbnN0ZWFkLCBJIHN0YXJ0ZWQgd2l0aCBhIHNpbmdsZSBkYXRhIGFuZCBhIGZldyBjb21tYW5kcywgd2hpY2ggd2VyZSB0aGVuIGdlbmVyYWxpemVkLiBUaGlzIGlzIHByb2JhYmx5IHRoZSBiZXN0IHByYWN0aWNlIG9mIHdyaXRpbmcgZnVuY3Rpb25zIHRvIGF2b2lkIHBvdGVudGlhbCBjb2RpbmcgZXJyb3JzL2lzc3Vlcy4gCgpgYGB7cn0KbWFpbl9ydW5fcG9ydGZvbGlvX2Z1biA8LSBmdW5jdGlvbihjaG9vc2VfZGF0YSxnYW1tYSxzYW1wbGVfc2l6ZSxUQykgewogIAogICMgY2hvb3NlIHRoZSBkYXRhIC0gaW4gdG90YWwgd2UgaGF2ZSBzaXggZGlmZmVyZW50IHNldHMKICBkcyA8LSBkc19saXN0W1tjaG9vc2VfZGF0YV1dCiAgbW9udGhzX3RvdCA8LSBzb3J0KHVuaXF1ZShkcyRkYXRlKSkKICAKICAjIHN0b3JlIGRhdGEKICByZXN1bHRzX2FsbCA8LSBkYXRhLmZyYW1lKCkgCiAgCiAgIyBzdG9yZSBwb3J0Zm9saW8gd2VpZ2h0cyAtIHJlbGV2YW50IGZvciBwb3J0Zm9saW8gdHVybm92ZXIgYW5kIFRDCiAgV19BTExfbGlzdCA8LSBsaXN0KCkKICAKICAjIHJ1biBhIGxvb3AgZnJvbSBUIHVudGlsIHRoZSBlbmQtMSBvZiBkYXRhCiAgZm9yKGkgaW4gc2FtcGxlX3NpemU6KGxlbmd0aChtb250aHNfdG90KSAtIDEpKSB7CiAgICAKICAgICMgY3VycmVudCBtb250aCAKICAgIG1vbnRoX2kgPC0gbW9udGhzX3RvdFtpXQogICAgIyBuZXh0IG1vbnRoLCB3aGljaCBpcyB1bmtub3duIGR1cmluZyBwb3J0Zm9saW8gY29uc3RydWN0aW9uCiAgICBtb250aF9pX3BsdXMgPC0gbW9udGhzX3RvdFtpKzFdCiAgICAKICAgICMgZm9yIHRyYWNraW5nIGJhY2t0ZXN0aW5nCiAgICBpZiggbW9udGgobW9udGhfaV9wbHVzKSA9PSAxMikgewogICAgICBjYXQoInRoaXMgaXMgbW9udGggIiwgYXMuY2hhcmFjdGVyKG1vbnRoX2lfcGx1cyksIlxuIikKICAgIH0KICAgIAogICAgIyBkZWZpbmUgdGhlIGluLXNhbXBsZSBkYXRhCiAgICBSX3N1YiA8LSBkc1tkcyRkYXRlIDw9IG1vbnRoX2ksXQogICAgcnVuX0RSX2Z1biA8LSBEUl9mdW5jdGlvbihSX3N1YixnYW1tYSxzYW1wbGVfc2l6ZSxUQykKICAgIAogICAgCiAgICAjIGtlZXAgdHJhY2sgb2Ygd2VpZ2h0cyBhbmQgc3RhY2sgaW4gbWF0cml4CiAgICBXXzFfbGlzdCA8LSBsYXBwbHkocnVuX0RSX2Z1bix0KQogICAgV18xX2xpc3QgPC0gbGFwcGx5KFdfMV9saXN0LGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUobW9udGhfaV9wbHVzLHgpICkKICAgIFdfQUxMX2xpc3QgPC0gYyhXX0FMTF9saXN0LGxpc3QoV18xX2xpc3QpKQogICAgCiAgICBSX25leHQgPC0gZHNbZHMkZGF0ZSA9PSBtb250aF9pX3BsdXMsLTFdCiAgICBSX3BvcnQgPC0gbGFwcGx5KFdfMV9saXN0LCBmdW5jdGlvbih4KSAgc3VtKHhbLC0xXSpSX25leHQpICApCiAgICBSX3BvcnQyIDwtIGRhdGEuZnJhbWUoZGF0ZSA9IG1vbnRoX2lfcGx1cyxSZWR1Y2UoY2JpbmQsUl9wb3J0KSkKICAgIG5hbWVzKFJfcG9ydDIpWy0xXSA8LSBuYW1lcyhSX3BvcnQpCiAgICByZXN1bHRzX2FsbCA8LSByYmluZChyZXN1bHRzX2FsbCxSX3BvcnQyKQogIH0KICAKICBsaXN0KHBvcnRfcmV0ID0gcmVzdWx0c19hbGwsIHdlaWdodHMgPSBXX0FMTF9saXN0KQp9CgpgYGAKClRoZSBsb29wIGluIHRoZSBhYm92ZSBmdW5jdGlvbiBjb21wdXRlcyB0d28gbWFpbiBvYmplY3RzLiBUaGUgZmlyc3Qgb25lIGNvbnRhaW5zIHRoZSBvdXQtb2Ytc2FtcGxlIHBvcnRmb2xpbyByZXR1cm5zIGByZXN1bHRzX2FsbGAsIHdoZXJlYXMgdGhlIG90aGVyIGlzIGEgbGlzdCBvZiBhbGwgcG9ydGZvbGlvIHdlaWdodHMgb3ZlciB0aW1lIGRlbm90ZWQgYnkgYFdfQUxMX2xpc3RgLiBHaXZlbiB0aGUgZm9ybWVyLCB3ZSBjYW4gZWFzaWx5IGNvbXB1dGUgZ3Jvc3MgcmV0dXJucyBhbmQgb3RoZXIgcGVyZm9ybWFuY2UgbWVhc3VyZXMuIEZvciB0aGUgbGF0dGVyLCB3ZSBjYW4gY29uc2lkZXIgcGVyZm9ybWFuY2UgaW4gdGVybXMgb2YgbmV0IHRyYW5zYWN0aW9uIGNvc3QgYW5kIG90aGVyIHBvcnRmb2xpbyBjaGFyYWN0ZXJpc3RpY3MgcmVsYXRlZCB0byBpdHMgc3RhYmlsaXR5IChlLmcuLCB0dXJub3Zlcikgb3IgZGl2ZXJzaWZpY2F0aW9uLiBBcyBtZW50aW9uZWQgYWJvdmUsIG5vdGUgdGhhdCB0aGUgZnVuY3Rpb24gaXMgZ2VuZXJhbGl6ZWQgdG8gZXZhbHVhdGUgbXVsdGlwbGUgZGVjaXNpb24gcnVsZXMgc2ltdWx0YW5lb3VzbHkgYnkgdXRpbGl6aW5nIHRoZSBgbGFwcGx5YCBiYXNlIGZ1bmN0aW9uLgoKSW4gdGhlIGZvbGxvd2luZywgbGV0IHVzIGNvbnNpZGVyIHRoZSBncm9zcyByZXR1cm5zIGFuZCByZXBsaWNhdGUgc29tZSBvZiB0aGUgcmVzdWx0cyBvZiAyIGZyb20gS1daLCB3aGljaCBkb2VzIG5vdCBjb25zaWRlciB0cmFuc2FjdGlvbnMgY29zdC4gU3BlY2lmaWNhbGx5LCBUYWJsZXMgMSBhbmQgMiBmcm9tIEtXWiByZXBvcnQgdGhlIGNlcnRhaW50eSBlcXVpdmFsZW50IHJldHVybnMgKENFUikgYW5kIHRoZSBTaGFycGUgcmF0aW8gZm9yIGRpZmZlcmVudCBkZWNpc2lvbiBydWxlcyAocm93cykgYW5kIGRhdGEgc2V0cyAoY29sdW1ucykuIEluIG91ciBjYXNlLCB3ZSBoYXZlIGVsZXZlbiBkZWNpc2lvbiBydWxlcyBhbmQgc2l4IGRhdGEgc2V0cy4gR2l2ZW4gdGhhdCB0aGUgZGF0YSBpcyBwdWJsaWMsIHdlIHNob3VsZCBnZXQgaWRlbnRpY2FsIHJlc3VsdHMgYXMgdGhlIGRlY2lzaW9uIHJ1bGVzIGRvIG5vdCBkZXBlbmQgb24gYSBibGFjayBib3ggbW9kZWwgb3IgaGlkZGVuIHZhbGlkYXRpb24gdGhhdCBjb3VsZCByZXN1bHQgaW4gYWRkaXRpb25hbCBkZWdyZWVzIG9mIGZyZWVkb20uIAoKSG93ZXZlciwgcmVzZWFyY2hlcnMgc2hvdWxkIGJlIGF3YXJlIG9mIG9uZSBpc3N1ZSB3aXRoIHRoZSBGYW1hLUZyZW5jaCBkYXRhLiBJdCBhcHBlYXJzIHRoYXQgdGhlIHNhbWUgdGltZSBzZXJpZXMgb2YgZmFjdG9yIHJldHVybnMgY2hhbmdlcyBvdmVyIHRpbWUgZHVlIHRvIGV4LXBvc3QgYWRqdXN0bWVudHMgaW4gdGhlIGNvbnN0cnVjdGlvbiBvZiB0aGUgZmFjdG9yLiBUaGlzIGlzc3VlIGhhcyBiZWVuIHJhaXNlZCBieSBBa2V5LCBSb2JlcnRzb24sIGFuZCBTaW11dGluICgyMDIyKSwgd2hpY2ggdGhlIGF1dGhvcnMgcmVmZXIgdG8gIGFzICJOb2lzeSBGYWN0b3JzLiIgU3BlY2lmaWNhbGx5LCB0aGUgYXV0aG9ycyBmaW5kIHRoYXQgImZhY3RvciByZXR1cm5zIGRpZmZlciBzdWJzdGFudGlhbGx5IGRlcGVuZGluZyBvbiB3aGVuIHRoZSBkYXRhIHdlcmUgZG93bmxvYWRlZCwgYW5kIG9ubHkgYSBzbWFsbCBwb3J0aW9uIG9mIHRoZXNlIHJldHJvYWN0aXZlIGNoYW5nZXMgaXMgZXhwbGFpbmVkIGJ5IHJldmlzaW9ucyBpbiB0aGUgdW5kZXJseWluZyBkYXRhLiIgTm9uZXRoZWxlc3MsIHRoaXMgaXNzdWUgc2hvdWxkIHJlc3VsdCBpbiBhIHNsaWdodCBkaWZmZXJlbmNlIGdpdmVuIHRoYXQgS1daIHdhcyBjb25kdWN0ZWQgcG9zdC0yMDE4LiAKClRvIGltcGxlbWVudCwgSSB1dGlsaXplIGBtYWluX3J1bl9wb3J0Zm9saW9fZnVuYCBhY3Jvc3MgYWxsIGRhdGEgc2V0cyBhbmQgY29tcHV0ZSB0aGUgdGltZSBzZXJpZXMgcmV0dXJucyBvZiBvdXIgZGVjaXNpb24gcnVsZXMgaW4gdGhlIGZvbGxvd2luZyBtYW5uZXI6CmBgYHtyfQpydW5fZXhwZXJpbWVudCA8LSBtY2xhcHBseSgxOmxlbmd0aChkc19saXN0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oZGF0YV9pbmRleCkgbWFpbl9ydW5fcG9ydGZvbGlvX2Z1bihkYXRhX2luZGV4LGdhbW1hLHNhbXBsZV9zaXplLFRDID0gMCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1jLmNvcmVzID0gZGV0ZWN0Q29yZXMoKSApCmBgYApOb3RlIHRoYXQgdGhlIGFib3ZlIGNvbW1hbmQgIHJ1bnMgdGhlIGJhY2t0ZXN0aW5nIGV4cGVyaW1lbnQgYWNyb3NzIGFsbCBkYXRhIHNldHMgdXNpbmcgdGhyZWUgbWFpbiBpbnB1dHMsIHNwZWNpZnlpbmcgdGhlIHJpc2sgYXZlcnNpb24sIHNhbXBsZSBzaXplLCBhbmQgdHJhbnNhY3Rpb25zIGNvc3QuIEdpdmVuIHRoZSBwb3J0Zm9saW8gcmV0dXJucyBmcm9tIGVhY2ggZGF0YSBleHBlcmltZW50LCB3ZSBjYW4gZWFzaWx5IGNvbXB1dGUgdGhlICAgY2VydGFpbnR5IGVxdWl2YWxlbnQgcmV0dXJucyAoQ0VSKSBhbmQgU2hhcnBlIHJhdGlvcyAoU1IpIGZvciBlYWNoIHBvcnRmb2xpby1kYXRhIHNwZWNpZmljYXRpb246CmBgYHtyfQpzcl9mdW4gPC0gZnVuY3Rpb24oeCkgcm91bmQobWVhbih4KS9zZCh4KSw0KQpjZXJfZnVuIDwtIGZ1bmN0aW9uKHgpIHJvdW5kKG1lYW4oeCkgLSAoZ2FtbWEvMikqdmFyKHgpLDQpCgpwb3J0X3JldF9saXN0IDwtIGxhcHBseShydW5fZXhwZXJpbWVudCxmdW5jdGlvbih4KSB4JHBvcnRfcmV0KQpNU19DRVIgPC0gc2FwcGx5KHBvcnRfcmV0X2xpc3QsIGZ1bmN0aW9uKHgpIGFwcGx5KHhbLC0xXSwyLGNlcl9mdW4pKQpNU19TUiA8LSBzYXBwbHkocG9ydF9yZXRfbGlzdCwgZnVuY3Rpb24oeCkgYXBwbHkoeFssLTFdLDIsc3JfZnVuKSkKYGBgCgpGb3IgYSBjb25zaXN0ZW50IGNvbXBhcmlzb24sIEkgY29weSB0aGUgQ0VSIGFuZCB0aGUgU2hhcnBlIHJhdGlvcyBmcm9tIFRhYmxlcyAxIGFuZCAyLCByZXNwZWN0aXZlbHksIG9mIEtXWiB0aGF0IGNvcnJlc3BvbmQgdG8gdGhlIDYgZGF0YSBzZXRzIGFuZCB0aGUgMTEgZGVjaXNpb24gcnVsZXMgc3R1ZGllZCBpbiB0aGlzIGFydGljbGU6CmBgYHtyfQpUYWJsZTFfS1daIDwtICJLV1ogMC4wMDk4IDAuMDEwMiAwLjAwNjQgMC4wMDYwIDAuMDA4MSAwLjAyNTkgMC4wMDIyIC0wLjAwNjgKTVYgLTAuMDA2MyAtMC4wNjM1IC0wLjAxNDEgLTAuMDcwNSAtMC4wMjcwIC0xLjIyOTQgLTAuMzk5OCAtMjcuMDkzNwpNVl9VQiAtMC4wMDEzIC0wLjAyODQgLTAuMDA3NSAtMC4wMzYxIC0wLjAxNDcgLTAuMzAzOSAtMC4xMjc4IC0wLjc0NjQKS1daX0xXXzIwMDQgMC4wMTIwIDAuMDExMiAwLjAxMDAgMC4wMDgzIDAuMDEwMCAwLjA2OTUgMC4wMDQyIDAuMDAyMApNVl9MV18yMDA0IDAuMDA3MCAtMC4wMDA5IDAuMDA0OCAtMC4wMjE5IDAuMDAxMyAtMC4wNDQ0IC0wLjEyNDMgLTAuNTI1NQpNVl9OUyAwLjAwNjAgMC4wMDUwIDAuMDAyOCAwLjAwNDUgMC4wMDQ4IDAuMDA5MCAwLjAwMDMgMC4wMDAwCkdNViAwLjAwNTEgMC4wMDYwIDAuMDAyMCAwLjAwNTAgMC4wMDUwIDAuMDAzNSAwLjAwMjUgLTAuMDA1NgpHTVZfTlMgMC4wMDM1IDAuMDA0MSAwLjAwNDEgMC4wMDQ5IDAuMDAyNSAwLjAwNTkgMC4wMDQyIDAuMDAyNwpOYWl2ZSAwLjAwMjYgMC4wMDM2IDAuMDAyMiAwLjAwMzQgMC4wMDIyIDAuMDAxMyAwLjAwMzkgMC4wMDI0CktPX1ZUIDAuMDAzNiAwLjAwNDMgMC4wMDQxIDAuMDA0NSAwLjAwMzAgMC4wMDQ2IDAuMDA0OSAwLjAwMzcKS09fUlQgMC4wMDQ3IDAuMDA0NSAwLjAwMzQgMC4wMDQ5IDAuMDA0NiAwLjAwNjEgMC4wMDM4IDAuMDAxOSIKCgpUYWJsZTJfS1daIDwtICJLV1ogMC4yNTIxIDAuMjQ3OSAwLjI0NTIgMC4xOTE2IDAuMjIzOCAwLjU3NzggMC4xMTk0IDAuMDIzNApNViAwLjIzNzUgMC4xOTk5IDAuMjM4NyAwLjE0NzAgMC4xODk2IDAuNTYxNCAwLjA2MTAgLTAuMDQ4MgpNVl9VQiAwLjI0MDUgMC4yMDY5IDAuMjQwMCAwLjE1MzQgMC4xOTQwIDAuNTY0OCAwLjA2NzEgLTAuMDQyOApLV1pfTFdfMjAwNCAwLjI2OTMgMC4yODM4IDAuMjQ1NiAwLjIzMTIgMC4yNTkxIDAuNjkyNSAwLjE2NjEgMC4xMDkzCk1WX0xXXzIwMDQgMC4yNTQ1IDAuMjM5MyAwLjIzODMgMC4xNzkyIDAuMjExOCAwLjY1NTggMC4wNzI4IC0wLjAxMzgKTVZfTlMgMC4xOTA1IDAuMTczNCAwLjEyOTkgMC4xNjQ0IDAuMTcwMCAwLjI0OTUgMC4wOTk2IDAuMDk0MwpHTVYgMC4xODIzIDAuMjA4NSAwLjEwODggMC4xODEzIDAuMTg0MyAwLjE0NzggMC4xMjI3IDAuMDM2OQpHTVZfTlMgMC4xNDUwIDAuMTU5NSAwLjE2MzkgMC4xODE2IDAuMTIzNiAwLjIxODggMC4xNzE3IDAuMTI5MQpOYWl2ZSAwLjEyNzYgMC4xNDg0IDAuMTIyNCAwLjE0MjYgMC4xMjA5IDAuMTAzNCAwLjE1MjcgMC4xMjA1CktPX1ZUIDAuMTQ2NiAwLjE2MzYgMC4xNjE5IDAuMTcwNCAwLjEzMzggMC4xNzUxIDAuMTkwMiAwLjE1ODQKS09fUlQgMC4xNjk1IDAuMTY1MSAwLjE0MzggMC4xNzY4IDAuMTY2NSAwLjIwNDYgMC4xNTM0IDAuMTA3OCIKCmBgYApUbyBwcm9jZXNzIGluIGEgcmVhZGFibGUgZm9ybWF0LCBJIHdyaXRlIHRoZSBmb2xsb3dpbmcgZnVuY3Rpb24gdGhhdCB0YWtlcyB0aGUgYWJvdmUgY29udGVudCBhbmQgbG9hZHMgaXQgaW50byBhIHJlYWRhYmxlIG1hdHJpeApgYGB7cn0KcmVhZF90YWJsZV9mdW4gPC0gZnVuY3Rpb24oS1daX3RhYmxlKSB7CiAgeCA8LSBzY2FuKHRleHRDb25uZWN0aW9uKEtXWl90YWJsZSksd2hhdCA9IGNoYXJhY3RlcigpLHNlcCA9ICJcbiIpCiAgeCA8LSBzdHJzcGxpdCh4LCIgIikKICB4X25hbWVzIDwtIHNhcHBseSh4LGZ1bmN0aW9uKHgpIHhbMV0gKQogIFggPC0gdChzYXBwbHkoeCxmdW5jdGlvbih4KSBhcy5udW1lcmljKHhbLTFdKSApKQogIGNvbG5hbWVzKFgpIDwtIGMoIk1PTTEwIiwiQk0yNSIsICAiSVZPTCIsICJPUElOMjUiLCAiTk1WMTYiLCAiTk1WNDYiLCAiSU5ENDkiLCJTdG9ja3MiKQogICMga2VlcCByZWxldmFudCBkYXRhIHNldHMKICBYIDwtIFhbLGMoIk1PTTEwIiwiQk0yNSIsIk9QSU4yNSIsIk5NVjE2IiwiTk1WNDYiLCJJTkQ0OSIpXQogIHJvd25hbWVzKFgpIDwtIHhfbmFtZXMKICBLV1pfdGFibGUgPC0gWAogIHJldHVybihLV1pfdGFibGUpCn0KClRhYmxlMV9LV1ogPC0gcmVhZF90YWJsZV9mdW4oVGFibGUxX0tXWikKVGFibGUyX0tXWiA8LSByZWFkX3RhYmxlX2Z1bihUYWJsZTJfS1daKQpgYGAKCkFzIGEgc3VtbWFyeSwgSSBwbG90IG15IHJlc3VsdHMgdmVyc3VzIHRob3NlIGJ5IEtXWiB1c2luZyB0aGUgYGdncGxvdDJgIGxpYnJhcnkuIExldCB1cyBzdW1tYXJpemUgYWxsIHJlc3VsdHMgaW4gYSBgZGF0YS5mcmFtZWAgdGhhdCBpcyB1c2VyLWZyaWVuZGx5IHRvIGlucHV0IGludG8gYGdncGxvdGA6CmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KZHNfcGxvdCA8LSBkYXRhLmZyYW1lKCkKZm9yIChyIGluIDE6bnJvdyhNU19DRVIpKSB7CiAgZHNfcGxvdF9yIDwtIGRhdGEuZnJhbWUoS1daID0gVGFibGUxX0tXWltyLF0sIE1TID0gTVNfQ0VSW3IsXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgRGF0YSA9IG5hbWVzKGRzX2xpc3QpLCBQb3J0Zm9saW8gPSByb3duYW1lcyhNU19DRVIpW3JdICkKICBkc19wbG90IDwtIHJiaW5kKGRzX3Bsb3QsZHNfcGxvdF9yKQp9CgojIHNhdmUgZGF0YQpkc19wbG90MSA8LSBkc19wbG90CmRzX3Bsb3QxJE1ldHJpYyA8LSAiQ0VSIgoKZHNfcGxvdCA8LSBkYXRhLmZyYW1lKCkKZm9yIChyIGluIDE6bnJvdyhNU19TUikpIHsKICBkc19wbG90X3IgPC0gZGF0YS5mcmFtZShLV1ogPSBUYWJsZTJfS1daW3IsXSwgTVMgPSBNU19TUltyLF0sIAogICAgICAgICAgICAgICAgICAgICAgICAgIERhdGEgPSBuYW1lcyhkc19saXN0KSwgUG9ydGZvbGlvID0gcm93bmFtZXMoTVNfU1IpW3JdICkKICBkc19wbG90IDwtIHJiaW5kKGRzX3Bsb3QsZHNfcGxvdF9yKQp9CmRzX3Bsb3QyIDwtIGRzX3Bsb3QKZHNfcGxvdDIkTWV0cmljIDwtICJTUiIKCiMgc3RhY2sgZGF0YSBhbHRvZ2V0aGVyCmRzX3Bsb3QgPC0gcmJpbmQoZHNfcGxvdDEsZHNfcGxvdDIpCgpgYGAKCiMjIENFUjogVGFibGUgMQpOb3cgd2UgYXJlIHJlYWR5IHRvIHBsb3QgdGhlIGRhdGEuIFNpbmNlIHRoZSBDRVIgYW5kIFNSIGNvcnJlc3BvbmQgdG8gZGlmZmVyZW50IHBlcmZvcm1hbmNlIG1ldHJpY3MsIHdlIHZpc3VhbGl6ZSBlYWNoIHNlcGFyYXRlbHkuIEZvciB0aGUgQ0VSLCB3ZSBoYXZlOgpgYGB7cn0KcCA8LSBnZ3Bsb3QoZHNfcGxvdFtkc19wbG90JE1ldHJpYyA9PSAiQ0VSIixdLCBhZXMoeSA9IEtXWiwgeCA9IE1TLGNvbG91ciA9IFBvcnRmb2xpbyxzaGFwZSA9IERhdGEpKQpwIDwtIHAgKyBnZW9tX3BvaW50KCkKcCA8LSBwICsgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwKQpwIDwtIHAgKyBnZ3RpdGxlKCJDRVI6IFJlcGxpY2F0aW5nIFRhYmxlIDEgZnJvbSBLV1oiKQpwCmBgYApPdmVyYWxsLCB0aGUgcmVzdWx0cyBzZWVtIGNvbnNpc3RlbnQsIHdpdGggc29tZSBzbGlnaHQgdmFyaWF0aW9ucyBpbiBzb21lIGNhc2VzLiBUaGUgbW9zdCBzaWduaWZpY2FudCBkZXZpYXRpb24gY29tZXMgZnJvbSB0aGUgTVYgZGVjaXNpb24gcnVsZSwgd2hpY2ggYWxzbyByZXN1bHRzIGluIHRoZSB3b3JzdCBwZXJmb3JtYW5jZS4gVGhpcyBpcyBtb3N0IGV2aWRlbnQgaW4gdGhlIE5WTTQ2IGRhdGEgc2V0LiAKCiMjIFNSOiBUYWJsZSAyCkxldCB1cyByZXBlYXQgdGhlIHNhbWUgcGxvdCBmb3IgdGhlIG9yIHRoZSBTUjoKYGBge3J9CnAgPC0gZ2dwbG90KGRzX3Bsb3RbZHNfcGxvdCRNZXRyaWMgPT0gIlNSIixdLCBhZXMoeSA9IEtXWiwgeCA9IE1TLGNvbG91ciA9IFBvcnRmb2xpbyxzaGFwZSA9IERhdGEpKQpwIDwtIHAgKyBnZW9tX3BvaW50KCkKcCA8LSBwICsgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwKQpwIDwtIHAgKyBnZ3RpdGxlKCJTUjogUmVwbGljYXRpbmcgVGFibGUgMiBmcm9tIEtXWiIpCnAKYGBgCldlIG9ic2VydmUgdGhhdCBtb3N0IGRlY2lzaW9uIHJ1bGVzIHJlc3VsdCBpbiAgU1IgYmV0d2VlbiAwIGFuZCAwLjQsIHdoZXJlYXMgaW4gYSBmZXcgY2FzZXMsIHRoZSBTUiBpcyBncmVhdGVyIHRoYW4gMC41LiBUaGlzIGlzIG1haW5seSB0aGUgY2FzZSBmb3IgdGhlIE5NVjQ2IGRhdGEuIE5vbmV0aGVsZXNzLCB3ZSBvYnNlcnZlIGEgZ29vZCBjb25zaXN0ZW5jeSBiZXR3ZWVuIHRoZSBvcmlnaW5hbCBmaW5kaW5ncyBieSBLV1ogYW5kIG91cnMuIFRoZXJlIGFyZSwgaG93ZXZlciwgYSBmZXcgc2xpZ2h0IGRldmlhdGlvbnMuIFdlIGV4cGVjdCBpZGVudGljYWwgcmVzdWx0cyBmb3IgdGhlIE1WLCBHTVYsIGFuZCBOYWl2ZSBkZWNpc2lvbiBydWxlcyBzaW5jZSB3ZSBhcmUgdXNpbmcgY2xvc2VkLWZvcm0gc29sdXRpb25zLCBhbmQgdGhlcmUgYXJlIG5vIGRlZ3JlZXMgb2YgZnJlZWRvbSBmb3IgYW4gZXJyb3IuIEZvciB0aGUgc2hvcnQgc2FsZXMsICB0aGVyZSBhcmUgc29tZSBkZWdyZWVzIG9mIGZyZWVkb20gaW4gdGVybXMgb2YgaW5pdGlhbCB3ZWlnaHRzIGFuZCBob3cgdGhlIG9wdGltaXphdGlvbiBhbGdvcml0aG0gdXNlZCBpbiBSIGRpZmZlcnMgZnJvbSB0aGUgb25lIHVzZWQgYnkgS1daLCB3aGljaCBjb3VsZCBiZSBidWlsdCB1c2luZyBkaWZmZXJlbnQgc29mdHdhcmUuIFRoZSBzYW1lIGFwcGxpZXMgdG8gc2hyaW5rYWdlLWJhc2VkIHRlY2huaXF1ZXMuCgpBZGRpdGlvbmFsbHksIHN1Y2ggZGV2aWF0aW9ucyBjb3VsZCBiZSBhdHRyaWJ1dGVkIHRvIHRoZSBmYWN0IHRoYXQgdGhlIEZhbWEtRnJlbmNoIGRhdGEgaXMgc3ViamVjdGVkIHRvIGNoYW5nZXMgb3ZlciB0aW1lLCBhcyBwb2ludGVkIG91dCBieSBBa2V5LCBSb2JlcnRzb24sIGFuZCBTaW11dGluICgyMDIyKS4gIFRvIGNoZWNrIHRoZSBsYXN0IHBvaW50IG1hZGUgYWJvdmUsIGxldCB1cyBmb2N1cyBvbiBOTVYxNiBhbmQgTk1WNDYgZGF0YSBzZXRzOgpgYGB7cn0KZHNfcGxvdF9OVk0gPC0gZHNfcGxvdFtkc19wbG90JERhdGEgJWluJSBjKCJOTVYxNiIsIk5NVjQ2IikgJiBkc19wbG90JE1ldHJpYyA9PSAiU1IiLF0KcCA8LSBnZ3Bsb3QoZHNfcGxvdF9OVk0sIGFlcyh5ID0gS1daLCB4ID0gTVMsY29sb3VyID0gUG9ydGZvbGlvLHNoYXBlID0gRGF0YSkpCnAgPC0gcCArIGdlb21fcG9pbnQoKQpwIDwtIHAgKyBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDApCnAgPC0gcCArIGdndGl0bGUoIlNSOiBSZXBsaWNhdGluZyBUYWJsZSAyIGZyb20gS1daIikKcApgYGAKQXMgY29uamVjdHVyZWQgYWJvdmUsIHRoZSBjbG9zZWQtZm9ybSBzb2x1dGlvbiBwb3J0Zm9saW9zIGFyZSBjb25zaXN0ZW50IHdpdGggS1daLiBIb3dldmVyLCB3ZSBub3RlIGEgc2xpZ2h0IGRpZmZlcmVuY2UgaW4gdGhlIGRlY2lzaW9uIHJ1bGVzIHdpdGggbm8gc2hvcnQgc2FsZXMsIHdoaWNoIHJlcXVpcmVzIG51bWVyaWNhbCBvcHRpbWl6YXRpb24uIEEgc2ltaWxhciBvYnNlcnZhdGlvbiBmb2xsb3dzIHdoZW4gd2UgY29tcGFyZSB0aGUgc2hyaW5rYWdlLWJhc2VkIHRlY2huaXF1ZXMuCgojIyBUdXJub3ZlcjogVGFibGUgMwpOb3csIGxldCB1cyBldmFsdWF0ZSB0aGUgcG9ydGZvbGlvIHR1cm5vdmVyIG9mIGVhY2ggZGVjaXNpb24gcnVsZS4gVG8gZG8gc28sIEkgZm9sbG93IHRoZSBzYW1lIG1ldHJpYyB1c2VkIGJ5IEtXWiAtIHNlZSBFcXVhdGlvbiAoNjApIGZyb20gdGhlIHBhcGVyLiBUaGUgZm9sbG93aW5nIG5lc3RlZCBsb29wIGNvbXB1dGVzIHRoZSB0dXJub3ZlciBhY3Jvc3MgIGRpZmZlcmVudCBkYXRhIHNvdXJjZXMgYW5kIGRlY2lzaW9uIHJ1bGVzOgpgYGB7cn0KTVNfVE8gPC0gYygpCmZvciAoZGF0YV9pIGluIDE6bGVuZ3RoKGRzX2xpc3QpKSB7CiAgVE9fdmVjIDwtIGMoKQogIGZvciAoZGVjaXNpb25fcnVsZSBpbiByb3duYW1lcyhNU19TUikpIHsKICAgIFdfbGlzdF9kYXRhIDwtIHJ1bl9leHBlcmltZW50W1tkYXRhX2ldXSR3ZWlnaHRzCiAgICBXX2xpc3RfZGF0YV9EUiA8LSBsYXBwbHkoV19saXN0X2RhdGEsIGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeFtuYW1lcyh4KSAlaW4lIGRlY2lzaW9uX3J1bGVdKSApCiAgICBXX21hdCA8LSBSZWR1Y2UocmJpbmQsV19saXN0X2RhdGFfRFIpCiAgICBSX21hdCA8LSBkc19saXN0W1tkYXRhX2ldXQogICAgV19tYXQgPC0gV19tYXRbLW5yb3coV19tYXQpLF0KICAgIFJfbWF0IDwtIFJfbWF0W1JfbWF0JGRhdGUgJWluJSBXX21hdFssMV0sXQogICAgV19tYXRfYWRqIDwtIFdfbWF0WywtMV0qKDEgKyBSX21hdFssLTFdKQogICAgV19tYXRfYWRqIDwtIHQoYXBwbHkoV19tYXRfYWRqLDEsZnVuY3Rpb24oeCkgeC9zdW0oeCkpKSAjIGFkanVzdCBwb3NpdGlvbnMgdG8gZXF1YWwgb25lCiAgICBUTyA8LSBXX21hdFstMSwtMV0gLSBXX21hdF9hZGpbLW5yb3coV19tYXRfYWRqKSxdCiAgICBUTyA8LSBhcHBseShUTywxLGZ1bmN0aW9uKHgpIHN1bShhYnMoeCkpICkKICAgIFRPX21lYW4gPC0gbWVhbihUTykKICAgIFRPX3ZlYyA8LSBjKFRPX3ZlYyxUT19tZWFuKQogIH0KICBNU19UTyA8LSBjYmluZChNU19UTyxUT192ZWMpCn0Kcm93bmFtZXMoTVNfVE8pIDwtIHJvd25hbWVzKE1TX1NSKQpjb2xuYW1lcyhNU19UTykgPC0gbmFtZXMoZHNfbGlzdCkKYGBgClRoZSBhYm92ZSBjb2RlIHRha2VzIGludG8gY29uc2lkZXJhdGlvbiB0aGF0IHBvc2l0aW9ucyBkbyBjaGFuZ2Ugb3ZlciB0aW1lLiBGb3IgaW5zdGFuY2UsIG9uZSBtYXkgY29uamVjdHVyZSB0aGF0IHRoZSBuYWl2ZSBwb3J0Zm9saW8gaGFzIHplcm8gdHVybm92ZXIgc2luY2UgaXRzIHdlaWdodHMgYXJlIGNvbnN0YW50IG92ZXIgdGltZTsgaG93ZXZlciwgdGhpcyBpcyBub3QgdGhlIGNhc2UuIEl0cyBwb3NpdGl2ZSB0dXJub3ZlciBjb21lcyBtYWlubHkgZnJvbSB0aGUgZmFjdCB0aGF0IHRoZSBwb3J0Zm9saW8gaXMgcmUtYmFsYW5jZWQgZWFjaCBtb250aCB0byBtYWludGFpbiBlcXVhbCB3ZWlnaHRzIGFmdGVyIGNvbnNpZGVyaW5nIHRoZSBncm9zcyByZXR1cm5zIGFuZCwgaGVuY2UsIGNoYW5nZXMgaW4gdGhlIHBvcnRmb2xpbyBwb3NpdGlvbi4gVG8gYmV0dGVyIHVuZGVyc3RhbmQgaG93IGl0IGlzIGNvbXB1dGVkLCBjb25zaWRlciB0aGUgZm9sbG93aW5nIGV4YW1wbGU6IApgYGB7cn0KZCA8LSA1CldfMSA8LSByZXAoMS9kLGQpCldfMV9hZGogPC0gV18xKigxK3Jub3JtKGQsMCwwLjA2KSkgCldfMiA8LSBXXzEKcmJpbmQoV18xX2FkaixXXzIpCmBgYApBcyByZXR1cm5zIGFyZSByZWFsaXplZCwgdGhlIGluaXRpYWwgcG9zaXRpb25zIGNoYW5nZSBhcyB3ZWxsLiBJbiBvcmRlciB0byBicmluZyBiYWNrIHRvIGVxdWFsIHdlaWdodHMsIHRoZSBwb3J0Zm9saW8gbWFuYWdlciBuZWVkcyB0byByZS1iYWxhbmNlLCByZXN1bHRpbmcgaW4gYSB0dXJub3ZlciBvZgpgYGB7cn0Kc3VtKGFicyhXXzIgLSBXXzFfYWRqKSkKYGBgClRoZSBvcGVyYXRpb25zIG9mIHRoaXMgc2ltcGxlIGV4YW1wbGUgYXJlIGNvbmR1Y3RlZCBmb3IgZWFjaCBtb250aCwgYnV0IGFzIGEgc3VtbWFyeSwgaXQgaXMgY29tbW9uIHRvIHJlcG9ydCB0aGUgYXZlcmFnZSBvdmVyIHRpbWUuIFRoaXMgaXMgZG9uZSBmb3IgYWxsIGRhdGEgc2V0cyBhbmQgZGVjaXNpb24gcnVsZXMuCgpGb3IgYSBjb25zaXN0ZW50IGNvbXBhcmlzb24sIEkgY29sbGVjdCB0aGUgZGF0YSBmcm9tIEtXWiAoVGFibGUgMyk6CmBgYHtyfQpUYWJsZTNfS1daIDwtICJLV1ogMi4wMTU3IDMuNTIxMiAyLjk5MzQgMS45MjgzIDIuODczMCAzNC4wOTA3IDEuMjU4MyAzLjg4ODgKTVYgNS4wNzY1IDU0LjM2MjAgNy4xMTk5IDE2LjMzMTQgMTEuMjI5NCA0MzQuODc4OCA5MS4yNjEwIDEyNzUuNTEyNApNVl9VQiA0LjMyMjkgMTQuNTgwNiA2LjUzMTQgOS43OTg5IDguNjIyNSAxNTIuODIyMCA0MC4xMDQ0IDIyNC4wMTAyCktXWl9MV18yMDA0IDEuMDA2MSAxLjI3NDUgMS4wODc5IDEuMTEzNiAwLjk5NTkgNS4yNTA2IDAuNTI0MSAwLjYwOTMKTVZfTFdfMjAwNCAyLjQyMDcgNi42MDMxIDIuNjU1MiA3LjI3NDAgMy41NDAwIDUwLjg2NzMgMTUuMjI2NSAyMTIuNDQ1MwpNVl9OUyAwLjEwNjYgMC4yMzkzIDAuMTgwOCAwLjE5NDUgMC4xNDU2IDAuMDU1OCAwLjIwOTIgMC4yNjQ4CkdNViAwLjI3NzAgMC43NjY1IDAuMjY0MCAwLjU0MTMgMC40OTExIDEuNTMzNyAwLjgyMjcgMy43ODI3CkdNVl9OUyAwLjA4MTcgMC4wNjkxIDAuMDA4OCAwLjA3NzQgMC4wNTU5IDAuMDM3NSAwLjA3MzMgMC4xNDQwCk5haXZlIDAuMDE3NiAwLjAxODIgMC4wMTcyIDAuMDE5OSAwLjAxOTcgMC4wMjI3IDAuMDM0MSAwLjA2NDgKS09fVlQgMC4wMjgxIDAuMDMwOSAwLjAxODggMC4wMzUzIDAuMDM0NyAwLjAzNzMgMC4wNDgxIDAuMDcwNgpLT19SVCAwLjA3NDYgMC4wNzY3IDAuMDg4NiAwLjEwODYgMC4wOTU5IDAuMDgzMiAwLjEzNjkgMC4xNTkxIgoKVGFibGUzX0tXWiA8LSByZWFkX3RhYmxlX2Z1bihUYWJsZTNfS1daKQpgYGAKU2FtZSBhcyBiZWZvcmUgbGV0IHVzIG9yZ2FuaXplIHRoZSByZXN1bHRzIGluIGEgdXNlci1mcmllbmRseSBkYXRhIGZyYW1lIHRoYXQgd2UgY2FuIGlucHV0IGVmZmljaWVudGx5IGludG8gYGdncGxvdGA6CmBgYHtyfQpkc19wbG90X1RPIDwtIGRhdGEuZnJhbWUoKQpmb3IgKHIgaW4gMTpucm93KE1TX1RPKSkgewogIGRzX3Bsb3RfciA8LSBkYXRhLmZyYW1lKEtXWiA9IFRhYmxlM19LV1pbcixdLCBNUyA9IE1TX1RPW3IsXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgRGF0YSA9IG5hbWVzKGRzX2xpc3QpLCBQb3J0Zm9saW8gPSByb3duYW1lcyhNU19UTylbcl0gKQogIGRzX3Bsb3RfVE8gPC0gcmJpbmQoZHNfcGxvdF9UTyxkc19wbG90X3IpCn0KCmRzX3Bsb3RfVE8kS1daIDwtIGxvZyhkc19wbG90X1RPJEtXWikKZHNfcGxvdF9UTyRNUyA8LSBsb2coZHNfcGxvdF9UTyRNUykKCnAgPC0gZ2dwbG90KGRzX3Bsb3RfVE8sIGFlcyh5ID0gS1daLCB4ID0gTVMsY29sb3VyID0gUG9ydGZvbGlvLHNoYXBlID0gRGF0YSkpCnAgPC0gcCArIGdlb21fcG9pbnQoKQpwIDwtIHAgKyBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDApCnAgPC0gcCArIGdndGl0bGUoIlR1cm5vdmVyOiBSZXBsaWNhdGluZyBUYWJsZSAzIGZyb20gS1daIikKcApgYGAKTm90ZSB0aGF0IEkgdXNlIHRoZSBsb2cgc2NhbGUgdG8gY29udHJvbCBmb3IgZml4ZWQgZWZmZWN0cyBhc3NvY2lhdGVkIHdpdGggZGF0YSBhbmQgZGVjaXNpb24gcnVsZXMuIEZvciBpbnN0YW5jZSwgdGhlIE1WIGRlY2lzaW9uIHJ1bGUgZXhoaWJpdHMgaHVnZSB1bnJlYWxpc3RpYyBwb3NpdGlvbnMuIFRoaXMgZXZpZGVuY2UgaXMgY29uc2lzdGVudCB3aXRoIHRoZSBwcmV2aW91cyBsaXRlcmF0dXJlIG9uIGVzdGltYXRpb24gZXJyb3IgaW4gcG9ydGZvbGlvIHNlbGVjdGlvbi4gT3ZlcmFsbCwgd2UgZmluZCBjb25zaXN0ZW50IHJlc3VsdHMgd2l0aCBLV1ouIFRoZSBtYWpvciBmbGFncyBzdGVtIGZyb20gdGhvc2Ugd2l0aCBzaG9ydCBzYWxlcyBjb25zdHJhaW50cy4gVG8gY2hlY2sgdGhlIHNlbnNpdGl2aXR5IHdpdGggcmVzcGVjdCB0byB0aGUgRmFtYS1GcmVuY2ggZGF0YSwgbGV0IHVzIHJlcGVhdCB0aGUgc2FtZSBleGVyY2lzZSBhcyB3ZSBkaWQgd2l0aCB0aGUgU1IgcmVzdWx0cywgd2hlcmUgd2Uga2VlcCB0aGUgTk1WMTYgYW5kIE5NVjQ2IGRhdGEgc2V0czoKYGBge3J9CmRzX3Bsb3RfVE9fc3ViIDwtIGRzX3Bsb3RfVE9bZHNfcGxvdF9UTyREYXRhICVpbiUgYygiTk1WMTYiLCJOTVY0NiIpLF0KcCA8LSBnZ3Bsb3QoZHNfcGxvdF9UT19zdWIsIGFlcyh5ID0gS1daLCB4ID0gTVMsY29sb3VyID0gUG9ydGZvbGlvLHNoYXBlID0gRGF0YSkpCnAgPC0gcCArIGdlb21fcG9pbnQoKQpwIDwtIHAgKyBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDApCnAgPC0gcCArIGdndGl0bGUoIlR1cm5vdmVyOiBSZXBsaWNhdGluZyBUYWJsZSAzIGZyb20gS1daIikKcApgYGAKSW50ZXJlc3RpbmdseSwgd2Ugc3RpbGwgd2l0bmVzcyBzbGlnaHQgdmFyaWF0aW9uczsgaG93ZXZlciwgb3VyIHJlc3VsdHMgc2VlbSBjb25zaXN0ZW50IG92ZXJhbGwgd2l0aCBLV1ouIFNpbWlsYXIgdG8gdGhlIFNoYXJwZSByYXRpbyBwZXJmb3JtYW5jZSByZXN1bHRzLCB3ZSBub3RlIHRoYXQgdGhlc2UgZGV2aWF0aW9ucyBtYWlubHkgc3RlbSBmcm9tIHRoZSBydWxlcyB0aGF0IGRlcGVuZCBvbiBudW1lcmljYWwgb3B0aW1pemF0aW9uIG9yIHJlbHkgb24gbm9uLWJhc2UgUiBwYWNrYWdlcy4KCgojIENvbmNsdWRpbmcgUmVtYXJrcwpUaGlzIGFydGljbGUgaXMgZGVzaWduZWQgdG8gaGVscCBmdXR1cmUgcmVzZWFyY2hlcnMgdG8gZXZhbHVhdGUgZGlmZmVyZW50IHBvcnRmb2xpbyBydWxlcyBlYXNpbHkgYW5kIGVuY291cmFnZSByZXByb2R1Y2libGUgcmVzZWFyY2ggaW4gcG9ydGZvbGlvIHNlbGVjdGlvbi4gVGhlIHBhcGVyIGJ5IEtXWiBzZXRzIGFuIGV4Y2VsbGVudCBncm91bmQgdG8gYXR0YWluIHRoaXMgZ29hbC4gV2hpbGUgdGhlIGFuYWx5c2lzIGhlcmUgY292ZXJzIGEgc3Vic2V0IG9mIHRoZSB0ZXN0cy9yZXN1bHRzIGJ5IHRoZSBhdXRob3JzLCB0aGUgYWJvdmUgZW1waXJpY2FsIGFuYWx5c2lzIGNvdmVycyBzdGFuZGFyZCBiZW5jaG1hcmtzIHRoYXQgZnV0dXJlIHJlc2VhcmNoZXJzIHNob3VsZCB0YWtlIGludG8gY29uc2lkZXJhdGlvbiBpbiB0aGVpciAicHVyc3VpdCBvZiB0aGUgcGVyZmVjdCBwb3J0Zm9saW8uIiAgT3ZlcmFsbCwgdGhlIGFydGljbGUgZmluZHMgY29uc2lzdGVudCByZXN1bHRzIHdpdGggdGhlIG9yaWdpbmFsIHJlc2VhcmNoLCBkZXNwaXRlIHBvdGVudGlhbCBkaWZmZXJlbmNlcyBpbiB0aGUgc29mdHdhcmUuIFN1Y2ggZGlmZmVyZW5jZXMgbWF5IGFsc28gYmUgYXR0cmlidXRlZCB0byB0aGUgdGltZSBsYXBzZSBzaW5jZSB0aGUgZW1waXJpY2FsIGFuYWx5c2lzIGJ5IEtXWiB3YXMgY29uZHVjdGVkLCB3aGljaCBnb2VzIHRvIHRoZSAibm9pc3kgZmFjdG9ycyIgYXJndW1lbnQgYnkgIEFrZXksIFJvYmVydHNvbiwgYW5kIFNpbXV0aW4gKDIwMjIpLiBBbm90aGVyIGNyaXRpY2FsIHBhcnQgdGhhdCBuZWVkcyB0byBiZSB0YWtlbiBpbnRvIGFjY291bnQgaXMgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlLiBJdCBpcyBjb21tb24gdG8gdXRpbGl6ZSBhIGJvb3RzdHJhcCBtZXRob2RvbG9neSB0byBkZXRlcm1pbmUgd2hldGhlciBvbmUgcnVsZSBzaWduaWZpY2FudGx5IG91dHBlcmZvcm1zIGFub3RoZXIuIEkgbGVhdmUgdGhpcyBmb3IgZnV0dXJlIGV4YW1pbmF0aW9uLiAKCgojIFJlZmVyZW5jZXMKMS4gQWtleSwgUC4sIFJvYmVydHNvbiwgQS4sICYgU2ltdXRpbiwgTS4gKDIwMjIpLiBOb2lzeSBmYWN0b3JzLiBSb3RtYW4gU2Nob29sIG9mIE1hbmFnZW1lbnQgV29ya2luZyBQYXBlciBGb3J0aGNvbWluZy4KMi4gS2FuLCBSLiwgV2FuZywgWC4sICYgWmhvdSwgRy4gKDIwMjIpLiBPcHRpbWFsIHBvcnRmb2xpbyBjaG9pY2Ugd2l0aCBlc3RpbWF0aW9uIHJpc2s6IE5vIHJpc2stZnJlZSBhc3NldCBjYXNlLiBNYW5hZ2VtZW50IFNjaWVuY2UsIDY4KDMpLCAyMDQ3LTIwNjguCjMuIEtpcmJ5LCBDLiwgJiBPc3RkaWVrLCBCLiAoMjAxMikuIEl04oCZcyBhbGwgaW4gdGhlIHRpbWluZzogc2ltcGxlIGFjdGl2ZSBwb3J0Zm9saW8gc3RyYXRlZ2llcyB0aGF0IG91dHBlcmZvcm0gbmFpdmUgZGl2ZXJzaWZpY2F0aW9uLiBKb3VybmFsIG9mIEZpbmFuY2lhbCBhbmQgUXVhbnRpdGF0aXZlIEFuYWx5c2lzLCA0NygyKSwgNDM3LTQ2Ny4KNC4gTm92eS1NYXJ4LCBSLiwgJiBWZWxpa292LCBNLiAoMjAxNikuIEEgdGF4b25vbXkgb2YgYW5vbWFsaWVzIGFuZCB0aGVpciB0cmFkaW5nIGNvc3RzLiBUaGUgUmV2aWV3IG9mIEZpbmFuY2lhbCBTdHVkaWVzLCAyOSgxKSwgMTA0LTE0Ny4KCg==