Alberto Dorantes Dosamantes, Ph.D. Monterrey Tech, Queretaro Campus Sep 1st, 2022

Abstract

In this workshop we will use real financial time series data to learn about a) data management such as return calculation and dataset merging, b) data visualization, c) and also we will start learning how to estimate expected stock return and expected portfolio return and risk using matrix algebra.

Data management and visualization for financial portfolios

In this section I will work on the following: a) Data management of financial data, b) return calculations, c) descriptive statistics for finance, d) visualization of financial data, and e) Portfolio formation and basic estimations.

2.1 Data management of financial data

2.1.1 Data collection

We will use the quantmod package to import real online data from Yahoo Finance. This package contains the getSymbols() function, which creates an xts (extensible time series) object in the environment with the downloaded data from the Internet. :

library(quantmod)
Loading required package: xts
Loading required package: zoo

Attaching package: ‘zoo’

The following objects are masked from ‘package:base’:

    as.Date, as.Date.numeric

Loading required package: TTR
Registered S3 method overwritten by 'quantmod':
  method            from
  as.zoo.data.frame zoo 

The getSymbols() function download online and up-to-date financial data, such as stock prices, ETF prices, interest rates, exchange rates, etc. getSymbols() allows to download this data from multiple sources: Yahoo Finance, FRED database and Oanda. These sources have thousands of finance and economic data series from many market exchanges and other macroeconomic variables of most of the countries.

You can type ?function in the console or the R Script and run it to know more about the syntax of any function. This will display the R documentation of the function in the bottom-right pane:

?getSymbols

Now, we will work with historical data of the Bitcoin cryptocurrency and the TESLA stock. We download daily quotations of these instruments from January 1, 2019 to date from Yahoo Finance:

getSymbols(c("BTC-USD","TSLA"), from="2019-01-01", src="yahoo", periodicity="daily")
Warning: BTC-USD contains missing values. Some functions will not work if objects contain missing values in the middle of the series. Consider using na.omit(), na.approx(), na.fill(), etc to remove or replace them.
[1] "BTC-USD" "TSLA"   

This function will create an xts-zoo R object for each ticker. Each object has the corresponding historical daily prices. xts stands for extensible time-series. An xts-zoo object is designed to easily manipulate time series data.

BTC-USD and TSLA are the ticker names in Yahoo Finance. The from argument is used to indicate the initial date from which you want to bring data. The to argument is the end date of the series you want to download. In this case we omit the to argument in order to download the most recent data. The src argument indicates the source of the data, in this case it is Yahoo Finance. Finally, the periodicity argument specifies the granularity of the data (daily, weekly, monthly, quarterly) also as a character vector.

You can check the content of any of these dataset with View(). When tickers have special characters, we have to make reference to the object with simple quotes (``):

You can list the FIRST 5 rows of a dataset by using head():

head(`BTC-USD`,5)
           BTC-USD.Open BTC-USD.High BTC-USD.Low BTC-USD.Close
2019-01-01     3746.713     3850.914    3707.231      3843.520
2019-01-02     3849.216     3947.981    3817.409      3943.409
2019-01-03     3931.049     3935.685    3826.223      3836.741
2019-01-04     3832.040     3865.935    3783.854      3857.718
2019-01-05     3851.974     3904.903    3836.900      3845.195
           BTC-USD.Volume BTC-USD.Adjusted
2019-01-01     4324200990         3843.520
2019-01-02     5244856836         3943.409
2019-01-03     4530215219         3836.741
2019-01-04     4847965467         3857.718
2019-01-05     5137609824         3845.195

Also, you can list the LAST 5 rows of a data set. Note that you can change number of rows you want to display.

tail(`BTC-USD`, 5)
           BTC-USD.Open BTC-USD.High BTC-USD.Low BTC-USD.Close
2022-09-01     20050.50     20198.39    19653.97      20127.14
2022-09-02     20126.07     20401.57    19814.77      19969.77
2022-09-03     19969.72     20037.01    19698.36      19832.09
2022-09-04           NA           NA          NA            NA
2022-09-05     20027.30     20027.30    19840.12      19853.44
           BTC-USD.Volume BTC-USD.Adjusted
2022-09-01    30182031010         20127.14
2022-09-02    29123998928         19969.77
2022-09-03    23613051457         19832.09
2022-09-04             NA               NA
2022-09-05    26419550208         19853.44

For each period, Yahoo Finance keeps track of the open, high, low, close (OHLC) and adjusted prices. Also, it keeps track of volume that was traded in every specific period. The adjusted prices are used for stocks, not for currencies. Adjusted prices considers dividend payments and also stock splits. Then, for the Bitcoin series we can use close of adjusted price to calculate daily returns.

Let’s see some of the benefits of using xts-zoo objects. We can, for example, select columns using any of the following functions, where x represents a generic xts zoo object:

Op(x): Extract the Opening prices of the period. Hi(x): Extract the Highest price of the period. Lo(x): Extract the Lowest price of the period. Cl(x): Extract the closing prices of the period. Vo(x): Extract the volume traded of the period. Ad(x): Extract the Adjusted prices of the period.

2.1.2 Merging and cleaning financial datasets:

We can use the merge() function to create a consolidated dataset of one or more xts-zoo objects:

prices <- merge(`BTC-USD`, TSLA)
# I can select only the adjusted prices in order to calculate returns:
adjprices <-Ad(prices) 

Now we have an xts-zoo objects with 2 columns. I can change the names of the columns with simple names:

names(adjprices)<-c("bitcoin","tesla")

Now I can make reference to the adjusted prices using these names.

In Finance, when managing daily data it is very common to have gaps in the series. What does this mean? It means that the contains some missing days. For example, for stock series there is no data for weekends or holidays. However, R deals with gaps because it recognizes that we are working with a time series object. Thus, we have a time variable with NO GAPS, which avoids problems when computing returns. However, R does not deal automatically with empty values (called NA’s). It is a good idea to have a data set free of NA’s. So, I can use the function na.omit:

adjprices <- na.omit(adjprices)

2.2 Return calculation

We can create xts-zoo objects for simple and continuously compounded returns of both instruments:

# Calculating continuously compounded daily returns:
ccreturns <- diff(log(adjprices)) 
# Calculating simple daily returns:
returns <- adjprices / lag(adjprices,n=1) - 1
# We can also calculate simple returns using the diff function:
returns2<- diff(adjprices) / lag(adjprices,n=1)

The diff function works with xts to calculate the difference between the value of one period minus the previous one. The lag function gets the previous value of the time series.

We can calculate holding returns for any time period for each instrument (HPR). For example, if we want to calculate the whole period return from the first day of the series to the most recent one, we can do the following:

HPR1<- 100* as.vector(last(adjprices)) / as.vector(first(adjprices)) - 1 
HPR1
[1]  505.4088 1305.9618
cat("The holding period return for Bitcoin is: ", HPR1[1], "%")
The holding period return for Bitcoin is:  505.4088 %
cat("The holding period return for Bitcoin is: ", HPR1[2], "%")
The holding period return for Bitcoin is:  1305.962 %

We could also use the continuously compounded returns to calculate the same holding simple returns:

HPR2<-100*exp(colSums(ccreturns,na.rm=TRUE)) - 1
HPR2
  bitcoin     tesla 
 505.4088 1305.9618 

2.3 Visualization of financial data

One of the advantages of the quantmod package is that it has some built-in data visualization capabilities. For example, the chartSeries function is a plotting tool designed to create standard financial charts given a time series object. The chartSeries function also includes some arguments that allows the user to modify the cosmetics of the plot such as the theme argument.

Let’s see the performance of Bitcoin prices:

chartSeries(`BTC-USD`, theme = ("white"))

Let’s see the performance of TESLA prices:

chartSeries(TSLA, theme = ("black"))

We can see exponential growth of both, the Bitcoin prices and Tesla prices for the last days. If you had invested in Bitcoin or Tesla in July 2020 and has sold your position early January 2021, you would have multiplied your investment by around 4 times (300% period return)!

We can visualize the daily returns over time:

plot(returns$bitcoin)

plot(returns$tesla)

With this plot we can appreciate the daily volatility of each instrument over time. Volatility is a measure of how disperse the returns move up and down from its mean; it is basically the standard deviation of returns.

2.4 Basics of Portfolio analysis

We will use the PerformanceAnalytics and related packages for this section:

# Load the packages:
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ─────────────────────── tidyverse 1.3.2 ──✔ ggplot2 3.3.6      ✔ purrr   0.3.4 
✔ tibble  3.1.8      ✔ dplyr   1.0.10
✔ tidyr   1.2.0      ✔ stringr 1.4.1 
✔ readr   2.1.2      ✔ forcats 0.5.2 ── Conflicts ────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::first()  masks xts::first()
✖ dplyr::lag()    masks stats::lag()
✖ dplyr::last()   masks xts::last()
library(PerformanceAnalytics)

Attaching package: ‘PerformanceAnalytics’

The following object is masked _by_ ‘.GlobalEnv’:

    prices

The following object is masked from ‘package:graphics’:

    legend
library(highcharter)
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Highcharts (www.highcharts.com) is a Highsoft software product which is
not free for commercial and Governmental use

We will do an exercise of a simple portfolio composed by 5 US stocks: Tesla, Microsoft, Nextera, WalMart and Pfizer. You can change the tickers of the stocks as you wish as long as Yahoo Finance has data for your tickers. I selected stocks from the industries: high-tech, clean energies, pharmaceutical, and retail.

2.4.1 Automation of data collection and data management

With the following code we will automatically download data from the 5 stocks, merge the data and calculate returns:

symbols <- c("TSLA","MSFT","NEE","WMT","PFE")

prices <- getSymbols(symbols,src = 'yahoo',
             periodicity = "monthly",
             from = "2018-01-01",
             auto.assign = TRUE,
             warnings = FALSE) %>%
  map(~Ad(get(.))) %>%
  reduce(merge) %>%
  `colnames<-`(symbols)

If you see I am doing several data management process in sequential process. The %>% is used to indicate the separation of each data management process. The previous code does the following:

Download price data of the 5 stocks

Apply (map) the adjusted function to all xts-zoo datasets in order to get the adjusted stock prices

Do the merge of all the xts-zoo objects into one object

Finally rename the columns of the integrated object, and return the object as prices.

2.4.2 Return calculations

We calculate returns of all stocks using the Return.calculate function:

Returns<-Return.calculate(prices) %>%
  na.omit() 

The na.omit function was also applied to clean the dataset and drop the rows with NA values.

We calculate the continuous compounded return (also called log returns) of all stocks:

returns<-Return.calculate(prices,method = "log") %>%
         na.omit() 
# We could calculate this using the diff and log functions:
returns2<-na.omit(diff(log(prices)))
# The returns and returns2 objects will have exactly the same returns

2.4.3 Descriptive statistics of returns

We calculate descriptive statistics for the returns of all stocks:

table.Stats(returns) 

We can see arithmetic, geometric mean, median of returns. Also we can see risk measures such as standard deviation, variance. Finally we can see skewness and kurtosis, which are important financial measures to understand the data.

2.4.4 Visualization of risk and return of stocks

We can do a Box plot of the returns to better appreciate historical median return and risk by looking at the median, the quartiles Q1 and Q3, volatility and extreme values of these returns:

chart.Boxplot(returns)

It is easy to see that Tesla is the stock with the highest risk. The red circles show the mean, the mid line is the median (50 percentile), the boxes include the 50% of the data from the Q1 (25 percentile) to the Q3 (75 percentile). The dots are considered extreme values for in the context of its own distribution.

Now we can start evaluating the performance over time for each stock by looking at how much $ we would have made if we had invested $1.00 in each stock at the beginning of the time periods:

charts.PerformanceSummary(Returns, 
                          main = "Performance of $1.00 over time",
                          wealth.index = TRUE)

Since Tesla has had an extraordinary performance for the last months, it is hard to appreciate the performance of the rest of the stocks. So, we can drop Tesla from the plot:

charts.PerformanceSummary(Returns[,2:5], 
                        main = "Performance of $1.00 over time",
                          wealth.index = TRUE)

2.5 Portfolio formation

I start creating the weights of 2 portfolios. One will be an equally-weighted portfolio, and the other will be an aggressive portfolio assigning high weights to risky stocks, and low weights to conservative stocks.

I start creating a vector of weights for the equally weighted portfolio:

w_ew = rep(0.2,5)
# The rep function repeats a value n times

Now I create a vector of weights for the aggressive portfolio. I will assign the following weights:

Tesla: 40% Microsoft: 30% Nextera: 20% WalMart:0% Pfizer: 10%

w_aggressive = c(0.4,0.3,0.2,0,0.1)

2.6 Portfolio return calculation

We calculate the historical return of the equally-weighted portfolio using the Return.portfolio function:

portfolio_returns_ew <-
  Return.portfolio(Returns,
                   weights = w_ew) %>%
  `colnames<-`("returns")

We calculate the historical return of the aggressive portfolio using the Return.portfolio function:

portfolio_returns_ag <-
  Return.portfolio(Returns,
                   weights = w_aggressive) %>%
  `colnames<-`("returns")

With the portfolio returns I can plot a performance chart of each portfolio:

charts.PerformanceSummary(portfolio_returns_ew, 
                          main = "Equally-weighted Portfolio")

charts.PerformanceSummary(portfolio_returns_ag, 
                          main = "Aggressive Portfolio Performance")

We finally do a dynamic plot of historical monthly returns of each portfolio using the highchart function from the highcharter package:

highchart(type = "stock") %>% 
  hc_title(text = "Equally-weighted Portfolio") %>% 
  hc_add_series(portfolio_returns_ew$returns, 
                name = "Monthly Returns", 
                color = "cornflowerblue") %>% 
  hc_add_theme(hc_theme_flat()) %>% 
  hc_navigator(enabled = FALSE) %>% 
  hc_scrollbar(enabled = FALSE) %>% 
  hc_legend(enabled = TRUE) %>% 
  hc_exporting(enabled = TRUE)

We can interact with the plot to zoom in and out, and selecting time periods.

We do the same for the aggressive portfolio:

highchart(type = "stock") %>% 
  hc_title(text = "Aggressive Portfolio") %>% 
  hc_add_series(portfolio_returns_ag$returns, 
                name = "Monthly Returns", 
                color = "cornflowerblue") %>% 
  hc_add_theme(hc_theme_flat()) %>% 
  hc_navigator(enabled = FALSE) %>% 
  hc_scrollbar(enabled = FALSE) %>% 
  hc_legend(enabled = TRUE) %>% 
  hc_exporting(enabled = TRUE)

3 Introduction to Portfolio Theory

Regardless the approach you use to download and manipulate the source data, once that your data frame of monthly returns is ready for analysis you will be in a good position to create the Variance-covariance matrix which is needed to compute the expected risk of the portfolio.

The first step is to transform the data frame into a matrix using the as.matrix function. We save the result in a matrix class object called ret.mat.

ret.mat <- as.matrix(Returns)

We compute the Var-Covariance matrix. The var function receives a matrix of returns as parameter. The returns of stock 1 are in column 1, returns of stock 2 are in column 2, and so on. This function calculates the Variance-Covariance Matrix of stock returns.

Remember that the Variance-Covariance matrix contains Variances of stock returns in the diagonal, and it contains the covariances of pairs of stock returns in the non-diagonals. Also, this matrix is symetric, since Cov(Reti, Retj) = Cov(Retj,Reti). More formally:

For example, the variance-covariance matrix of 2 variables X1 and X2 contains the variance of each variable in its diagonal and covariances between the variables in its non-diagonal terms. In other words, the elements in the upright part of the matrix are repeated in the down-left part of the matrix. More formally:

∑cov=[Var(X1)Cov(X2,X1)Cov(X1,X2)Var(X2)]

COV <- var(ret.mat)

We can see the results of the Var-Covar matrix as shown below:

COV
            TSLA        MSFT          NEE          WMT
TSLA 0.044324810 0.007089377 0.0036288008 0.0043913507
MSFT 0.007089377 0.003480521 0.0012656788 0.0012728561
NEE  0.003628801 0.001265679 0.0036179444 0.0008644815
WMT  0.004391351 0.001272856 0.0008644815 0.0031130716
PFE  0.003429573 0.001350195 0.0018184272 0.0011935499
             PFE
TSLA 0.003429573
MSFT 0.001350195
NEE  0.001818427
WMT  0.001193550
PFE  0.005696879

We can also calculate the Correlation Matrix to better understand the relationships of all different pairs of stock returns.

cor(ret.mat)
          TSLA      MSFT       NEE       WMT       PFE
TSLA 1.0000000 0.5707718 0.2865555 0.3738351 0.2158232
MSFT 0.5707718 1.0000000 0.3566731 0.3866897 0.3032187
NEE  0.2865555 0.3566731 1.0000000 0.2575907 0.4005405
WMT  0.3738351 0.3866897 0.2575907 1.0000000 0.2834181
PFE  0.2158232 0.3032187 0.4005405 0.2834181 1.0000000

Remember that the correlation between 2 stock returns can be any value between -1 and 1. When the correlation is close to 0, it means that both stocks have no relationship. If the correlation is close to 1 it means that both stock returns move in a very similar way in the same direction.

We calculate the expected simple returns of the stocks in another matrix using the apply function. It is important to double check that the second parameter is equal to 2. This means that we are applying the function mean by column. If we want to apply the mean function by raw, we have to specify the second parameter as 1.

ER<- exp(apply(ret.mat, 2, mean)) - 1

This is the simple way to calculate the expected return of each stock according to Markowitz theory. Remember that the expected return of one stock is simply the Geometric average return of its historical returns.

To calculate this expected return we have 2 methods:

Calculate the products of Gross Returns of each period, substract 1, and then apply the N root to get the geometric average.

Get the arithmetic average of continuously compounded returns and then convert this amount to simple returns by raising e to this average and then substract 1.

We have estimated the geometric average using the method 2, but not only for one stock, but for all stocks in the the matrix ret.mat. The result will be a vector of geometric returns that will be saved in er. We can estimate the vector Mr of expected returns as:

MR=exp[Mr]=exp⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢r1¯r2¯…rN¯⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥−1

We can see the expected simple returns by calling the er object.

ER
       TSLA        MSFT         NEE         WMT         PFE 
0.064287042 0.020448711 0.017360339 0.007022978 0.010445553 

We calculate the variance, standard deviation, skewness and kurtosis of all asset returns (by columns). We do this just to see descriptive statistics of the stock returns.

apply(ret.mat, 2, var)
       TSLA        MSFT         NEE         WMT         PFE 
0.044324810 0.003480521 0.003617944 0.003113072 0.005696879 
apply(ret.mat, 2, sd)
      TSLA       MSFT        NEE        WMT        PFE 
0.21053458 0.05899594 0.06014935 0.05579491 0.07547767 
apply(ret.mat, 2, skewness)
      TSLA       MSFT        NEE        WMT        PFE 
 1.0645506  0.1130058 -0.6246087 -0.6591064  0.5710369 
apply(ret.mat, 2, kurtosis)
      TSLA       MSFT        NEE        WMT        PFE 
 0.8277288 -0.2992434  1.5910755  0.8327515  0.2933593 

Remember what is skewness and kurtosis. Skewness is a measure of the shape of the probability distribution of the returns while kurtosis gives us information about how much the stock has had extreme negative and positive returns. In other words, kurtosis helps us to identify how big are the fat-tails of the return distribution. We can complement this with the histogram of returns to better appreciate the level of risk of the stock.

4 Calculation of expected variance and risk of a Portfolio

Assume that you invest in a portfolio with the following weights: Apple 30%, Walmart 10%, Microsoft 30%, GE 10% and Tesla 20%. Create a vector with these 5 weights:

W <- c(0.30,0.10,0.30,0.10,0.20)

According to Portfolio Theory, the expected risk of a portfolio P is calculated as the squared root of the Portfolio Variance.

SD(P)=(Var(P))−−−−−−−−√

The portfolio variance can be calculated with the following matrix multiplication:

Var(P)=W′∗∑cov∗W where:

W is the weight vector of the stocks, and W′ is the tranposed matrix of weights.

We now can estimate the expected return and expected risk of this portfolio.

Now I am ready to start estimating the expected return and risk of the portfolio using Matrix Algebra:

ERP1 <- t(W)%*%ER
ERP1
           [,1]
[1,] 0.02933049

The vector ERP1 will have one value with the expected return of the portfolio. The t function is the transpose function of a vector or matrix. The result of multiplying the vector transposed times the vector of expected returns of the stocks is the same as calculating a weighted average.

Now I can estimate the expected risk of the portfolio using Matrix Albegra:

EVARPORT <- t(W)%*%COV%*%W
ERISK <- sqrt(EVARPORT)
ERISK
           [,1]
[1,] 0.08267686

With this matrix multiplication I am applying Markowitz theory to estimate the expected variance of the portfolio. You can review why this is the case in the Notes about Portfolio Theory. Make sure you understand why we do this matrix multiplication.

LS0tCnRpdGxlOiAiRmluYW5jZSBQcm9ncmFtbWluZyAtIFdvcmtzaG9wIDQiCmF1dGhvcjogU3RlZmFuIFNjaHdlaXR6ZXIgQTAxMjA5NzU1Cm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCkFsYmVydG8gRG9yYW50ZXMgRG9zYW1hbnRlcywgUGguRC4KTW9udGVycmV5IFRlY2gsIFF1ZXJldGFybyBDYW1wdXMKU2VwIDFzdCwgMjAyMgoKIyBBYnN0cmFjdApJbiB0aGlzIHdvcmtzaG9wIHdlIHdpbGwgdXNlIHJlYWwgZmluYW5jaWFsIHRpbWUgc2VyaWVzIGRhdGEgdG8gbGVhcm4gYWJvdXQgYSkgZGF0YSBtYW5hZ2VtZW50IHN1Y2ggYXMgcmV0dXJuIGNhbGN1bGF0aW9uIGFuZCBkYXRhc2V0IG1lcmdpbmcsIGIpIGRhdGEgdmlzdWFsaXphdGlvbiwgYykgYW5kIGFsc28gd2Ugd2lsbCBzdGFydCBsZWFybmluZyBob3cgdG8gZXN0aW1hdGUgZXhwZWN0ZWQgc3RvY2sgcmV0dXJuIGFuZCBleHBlY3RlZCBwb3J0Zm9saW8gcmV0dXJuIGFuZCByaXNrIHVzaW5nIG1hdHJpeCBhbGdlYnJhLgoKIyBEYXRhIG1hbmFnZW1lbnQgYW5kIHZpc3VhbGl6YXRpb24gZm9yIGZpbmFuY2lhbCBwb3J0Zm9saW9zCkluIHRoaXMgc2VjdGlvbiBJIHdpbGwgd29yayBvbiB0aGUgZm9sbG93aW5nOiBhKSBEYXRhIG1hbmFnZW1lbnQgb2YgZmluYW5jaWFsIGRhdGEsIGIpIHJldHVybiBjYWxjdWxhdGlvbnMsIGMpIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MgZm9yIGZpbmFuY2UsIGQpIHZpc3VhbGl6YXRpb24gb2YgZmluYW5jaWFsIGRhdGEsIGFuZCBlKSBQb3J0Zm9saW8gZm9ybWF0aW9uIGFuZCBiYXNpYyBlc3RpbWF0aW9ucy4KCiMgMi4xIERhdGEgbWFuYWdlbWVudCBvZiBmaW5hbmNpYWwgZGF0YQojIyAyLjEuMSBEYXRhIGNvbGxlY3Rpb24KV2Ugd2lsbCB1c2UgdGhlIHF1YW50bW9kIHBhY2thZ2UgdG8gaW1wb3J0IHJlYWwgb25saW5lIGRhdGEgZnJvbSBZYWhvbyBGaW5hbmNlLiBUaGlzIHBhY2thZ2UgY29udGFpbnMgdGhlIGdldFN5bWJvbHMoKSBmdW5jdGlvbiwgd2hpY2ggY3JlYXRlcyBhbiB4dHMgKGV4dGVuc2libGUgdGltZSBzZXJpZXMpIG9iamVjdCBpbiB0aGUgZW52aXJvbm1lbnQgd2l0aCB0aGUgZG93bmxvYWRlZCBkYXRhIGZyb20gdGhlIEludGVybmV0LiA6CgpgYGB7cn0KbGlicmFyeShxdWFudG1vZCkKYGBgCgpUaGUgZ2V0U3ltYm9scygpIGZ1bmN0aW9uIGRvd25sb2FkIG9ubGluZSBhbmQgdXAtdG8tZGF0ZSBmaW5hbmNpYWwgZGF0YSwgc3VjaCBhcyBzdG9jayBwcmljZXMsIEVURiBwcmljZXMsIGludGVyZXN0IHJhdGVzLCBleGNoYW5nZSByYXRlcywgZXRjLiBnZXRTeW1ib2xzKCkgYWxsb3dzIHRvIGRvd25sb2FkIHRoaXMgZGF0YSBmcm9tIG11bHRpcGxlIHNvdXJjZXM6IFlhaG9vIEZpbmFuY2UsIEZSRUQgZGF0YWJhc2UgYW5kIE9hbmRhLiBUaGVzZSBzb3VyY2VzIGhhdmUgdGhvdXNhbmRzIG9mIGZpbmFuY2UgYW5kIGVjb25vbWljIGRhdGEgc2VyaWVzIGZyb20gbWFueSBtYXJrZXQgZXhjaGFuZ2VzIGFuZCBvdGhlciBtYWNyb2Vjb25vbWljIHZhcmlhYmxlcyBvZiBtb3N0IG9mIHRoZSBjb3VudHJpZXMuCgpZb3UgY2FuIHR5cGUgP2Z1bmN0aW9uIGluIHRoZSBjb25zb2xlIG9yIHRoZSBSIFNjcmlwdCBhbmQgcnVuIGl0IHRvIGtub3cgbW9yZSBhYm91dCB0aGUgc3ludGF4IG9mIGFueSBmdW5jdGlvbi4gVGhpcyB3aWxsIGRpc3BsYXkgdGhlIFIgZG9jdW1lbnRhdGlvbiBvZiB0aGUgZnVuY3Rpb24gaW4gdGhlIGJvdHRvbS1yaWdodCBwYW5lOgoKYGBge3J9Cj9nZXRTeW1ib2xzCmBgYAoKTm93LCB3ZSB3aWxsIHdvcmsgd2l0aCBoaXN0b3JpY2FsIGRhdGEgb2YgdGhlIEJpdGNvaW4gY3J5cHRvY3VycmVuY3kgYW5kIHRoZSBURVNMQSBzdG9jay4gV2UgZG93bmxvYWQgZGFpbHkgcXVvdGF0aW9ucyBvZiB0aGVzZSBpbnN0cnVtZW50cyBmcm9tIEphbnVhcnkgMSwgMjAxOSB0byBkYXRlIGZyb20gWWFob28gRmluYW5jZToKCmBgYHtyfQpnZXRTeW1ib2xzKGMoIkJUQy1VU0QiLCJUU0xBIiksIGZyb209IjIwMTktMDEtMDEiLCBzcmM9InlhaG9vIiwgcGVyaW9kaWNpdHk9ImRhaWx5IikKYGBgCgpUaGlzIGZ1bmN0aW9uIHdpbGwgY3JlYXRlIGFuIHh0cy16b28gUiBvYmplY3QgZm9yIGVhY2ggdGlja2VyLiBFYWNoIG9iamVjdCBoYXMgdGhlIGNvcnJlc3BvbmRpbmcgaGlzdG9yaWNhbCBkYWlseSBwcmljZXMuIHh0cyBzdGFuZHMgZm9yIGV4dGVuc2libGUgdGltZS1zZXJpZXMuIEFuIHh0cy16b28gb2JqZWN0IGlzIGRlc2lnbmVkIHRvIGVhc2lseSBtYW5pcHVsYXRlIHRpbWUgc2VyaWVzIGRhdGEuCgpCVEMtVVNEIGFuZCBUU0xBIGFyZSB0aGUgdGlja2VyIG5hbWVzIGluIFlhaG9vIEZpbmFuY2UuIFRoZSBmcm9tIGFyZ3VtZW50IGlzIHVzZWQgdG8gaW5kaWNhdGUgdGhlIGluaXRpYWwgZGF0ZSBmcm9tIHdoaWNoIHlvdSB3YW50IHRvIGJyaW5nIGRhdGEuIFRoZSB0byBhcmd1bWVudCBpcyB0aGUgZW5kIGRhdGUgb2YgdGhlIHNlcmllcyB5b3Ugd2FudCB0byBkb3dubG9hZC4gSW4gdGhpcyBjYXNlIHdlIG9taXQgdGhlIHRvIGFyZ3VtZW50IGluIG9yZGVyIHRvIGRvd25sb2FkIHRoZSBtb3N0IHJlY2VudCBkYXRhLiBUaGUgc3JjIGFyZ3VtZW50IGluZGljYXRlcyB0aGUgc291cmNlIG9mIHRoZSBkYXRhLCBpbiB0aGlzIGNhc2UgaXQgaXMgWWFob28gRmluYW5jZS4gRmluYWxseSwgdGhlIHBlcmlvZGljaXR5IGFyZ3VtZW50IHNwZWNpZmllcyB0aGUgZ3JhbnVsYXJpdHkgb2YgdGhlIGRhdGEgKGRhaWx5LCB3ZWVrbHksIG1vbnRobHksIHF1YXJ0ZXJseSkgYWxzbyBhcyBhIGNoYXJhY3RlciB2ZWN0b3IuCgpZb3UgY2FuIGNoZWNrIHRoZSBjb250ZW50IG9mIGFueSBvZiB0aGVzZSBkYXRhc2V0IHdpdGggVmlldygpLiBXaGVuIHRpY2tlcnMgaGF2ZSBzcGVjaWFsIGNoYXJhY3RlcnMsIHdlIGhhdmUgdG8gbWFrZSByZWZlcmVuY2UgdG8gdGhlIG9iamVjdCB3aXRoIHNpbXBsZSBxdW90ZXMgKGBgKToKCllvdSBjYW4gbGlzdCB0aGUgRklSU1QgNSByb3dzIG9mIGEgZGF0YXNldCBieSB1c2luZyBoZWFkKCk6CgpgYGB7cn0KaGVhZChgQlRDLVVTRGAsNSkKYGBgCkFsc28sIHlvdSBjYW4gbGlzdCB0aGUgTEFTVCA1IHJvd3Mgb2YgYSBkYXRhIHNldC4gTm90ZSB0aGF0IHlvdSBjYW4gY2hhbmdlIG51bWJlciBvZiByb3dzIHlvdSB3YW50IHRvIGRpc3BsYXkuCgpgYGB7cn0KdGFpbChgQlRDLVVTRGAsIDUpCmBgYAoKRm9yIGVhY2ggcGVyaW9kLCBZYWhvbyBGaW5hbmNlIGtlZXBzIHRyYWNrIG9mIHRoZSBvcGVuLCBoaWdoLCBsb3csIGNsb3NlIChPSExDKSBhbmQgYWRqdXN0ZWQgcHJpY2VzLiBBbHNvLCBpdCBrZWVwcyB0cmFjayBvZiB2b2x1bWUgdGhhdCB3YXMgdHJhZGVkIGluIGV2ZXJ5IHNwZWNpZmljIHBlcmlvZC4gVGhlIGFkanVzdGVkIHByaWNlcyBhcmUgdXNlZCBmb3Igc3RvY2tzLCBub3QgZm9yIGN1cnJlbmNpZXMuIEFkanVzdGVkIHByaWNlcyBjb25zaWRlcnMgZGl2aWRlbmQgcGF5bWVudHMgYW5kIGFsc28gc3RvY2sgc3BsaXRzLiBUaGVuLCBmb3IgdGhlIEJpdGNvaW4gc2VyaWVzIHdlIGNhbiB1c2UgY2xvc2Ugb2YgYWRqdXN0ZWQgcHJpY2UgdG8gY2FsY3VsYXRlIGRhaWx5IHJldHVybnMuCgpMZXTigJlzIHNlZSBzb21lIG9mIHRoZSBiZW5lZml0cyBvZiB1c2luZyB4dHMtem9vIG9iamVjdHMuIFdlIGNhbiwgZm9yIGV4YW1wbGUsIHNlbGVjdCBjb2x1bW5zIHVzaW5nIGFueSBvZiB0aGUgZm9sbG93aW5nIGZ1bmN0aW9ucywgd2hlcmUgeCByZXByZXNlbnRzIGEgZ2VuZXJpYyB4dHMgem9vIG9iamVjdDoKCk9wKHgpOiBFeHRyYWN0IHRoZSBPcGVuaW5nIHByaWNlcyBvZiB0aGUgcGVyaW9kLgpIaSh4KTogRXh0cmFjdCB0aGUgSGlnaGVzdCBwcmljZSBvZiB0aGUgcGVyaW9kLgpMbyh4KTogRXh0cmFjdCB0aGUgTG93ZXN0IHByaWNlIG9mIHRoZSBwZXJpb2QuCkNsKHgpOiBFeHRyYWN0IHRoZSBjbG9zaW5nIHByaWNlcyBvZiB0aGUgcGVyaW9kLgpWbyh4KTogRXh0cmFjdCB0aGUgdm9sdW1lIHRyYWRlZCBvZiB0aGUgcGVyaW9kLgpBZCh4KTogRXh0cmFjdCB0aGUgQWRqdXN0ZWQgcHJpY2VzIG9mIHRoZSBwZXJpb2QuCgoKIyMgMi4xLjIgTWVyZ2luZyBhbmQgY2xlYW5pbmcgZmluYW5jaWFsIGRhdGFzZXRzOgpXZSBjYW4gdXNlIHRoZSBtZXJnZSgpIGZ1bmN0aW9uIHRvIGNyZWF0ZSBhIGNvbnNvbGlkYXRlZCBkYXRhc2V0IG9mIG9uZSBvciBtb3JlIHh0cy16b28gb2JqZWN0czoKCmBgYHtyfQpwcmljZXMgPC0gbWVyZ2UoYEJUQy1VU0RgLCBUU0xBKQojIEkgY2FuIHNlbGVjdCBvbmx5IHRoZSBhZGp1c3RlZCBwcmljZXMgaW4gb3JkZXIgdG8gY2FsY3VsYXRlIHJldHVybnM6CmFkanByaWNlcyA8LUFkKHByaWNlcykgCmBgYAoKTm93IHdlIGhhdmUgYW4geHRzLXpvbyBvYmplY3RzIHdpdGggMiBjb2x1bW5zLiBJIGNhbiBjaGFuZ2UgdGhlIG5hbWVzIG9mIHRoZSBjb2x1bW5zIHdpdGggc2ltcGxlIG5hbWVzOgoKYGBge3J9Cm5hbWVzKGFkanByaWNlcyk8LWMoImJpdGNvaW4iLCJ0ZXNsYSIpCmBgYAoKTm93IEkgY2FuIG1ha2UgcmVmZXJlbmNlIHRvIHRoZSBhZGp1c3RlZCBwcmljZXMgdXNpbmcgdGhlc2UgbmFtZXMuCgpJbiBGaW5hbmNlLCB3aGVuIG1hbmFnaW5nIGRhaWx5IGRhdGEgaXQgaXMgdmVyeSBjb21tb24gdG8gaGF2ZSBnYXBzIGluIHRoZSBzZXJpZXMuIFdoYXQgZG9lcyB0aGlzIG1lYW4/IEl0IG1lYW5zIHRoYXQgdGhlIGNvbnRhaW5zIHNvbWUgbWlzc2luZyBkYXlzLiBGb3IgZXhhbXBsZSwgZm9yIHN0b2NrIHNlcmllcyB0aGVyZSBpcyBubyBkYXRhIGZvciB3ZWVrZW5kcyBvciBob2xpZGF5cy4gSG93ZXZlciwgUiBkZWFscyB3aXRoIGdhcHMgYmVjYXVzZSBpdCByZWNvZ25pemVzIHRoYXQgd2UgYXJlIHdvcmtpbmcgd2l0aCBhIHRpbWUgc2VyaWVzIG9iamVjdC4gVGh1cywgd2UgaGF2ZSBhIHRpbWUgdmFyaWFibGUgd2l0aCBOTyBHQVBTLCB3aGljaCBhdm9pZHMgcHJvYmxlbXMgd2hlbiBjb21wdXRpbmcgcmV0dXJucy4gSG93ZXZlciwgUiBkb2VzIG5vdCBkZWFsIGF1dG9tYXRpY2FsbHkgd2l0aCBlbXB0eSB2YWx1ZXMgKGNhbGxlZCBOQeKAmXMpLiBJdCBpcyBhIGdvb2QgaWRlYSB0byBoYXZlIGEgZGF0YSBzZXQgZnJlZSBvZiBOQeKAmXMuIFNvLCBJIGNhbiB1c2UgdGhlIGZ1bmN0aW9uIG5hLm9taXQ6CgpgYGB7cn0KYWRqcHJpY2VzIDwtIG5hLm9taXQoYWRqcHJpY2VzKQpgYGAKCiMjIDIuMiBSZXR1cm4gY2FsY3VsYXRpb24KCldlIGNhbiBjcmVhdGUgeHRzLXpvbyBvYmplY3RzIGZvciBzaW1wbGUgYW5kIGNvbnRpbnVvdXNseSBjb21wb3VuZGVkIHJldHVybnMgb2YgYm90aCBpbnN0cnVtZW50czoKCmBgYHtyfQojIENhbGN1bGF0aW5nIGNvbnRpbnVvdXNseSBjb21wb3VuZGVkIGRhaWx5IHJldHVybnM6CmNjcmV0dXJucyA8LSBkaWZmKGxvZyhhZGpwcmljZXMpKSAKIyBDYWxjdWxhdGluZyBzaW1wbGUgZGFpbHkgcmV0dXJuczoKcmV0dXJucyA8LSBhZGpwcmljZXMgLyBsYWcoYWRqcHJpY2VzLG49MSkgLSAxCiMgV2UgY2FuIGFsc28gY2FsY3VsYXRlIHNpbXBsZSByZXR1cm5zIHVzaW5nIHRoZSBkaWZmIGZ1bmN0aW9uOgpyZXR1cm5zMjwtIGRpZmYoYWRqcHJpY2VzKSAvIGxhZyhhZGpwcmljZXMsbj0xKQpgYGAKClRoZSBkaWZmIGZ1bmN0aW9uIHdvcmtzIHdpdGggeHRzIHRvIGNhbGN1bGF0ZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB2YWx1ZSBvZiBvbmUgcGVyaW9kIG1pbnVzIHRoZSBwcmV2aW91cyBvbmUuIFRoZSBsYWcgZnVuY3Rpb24gZ2V0cyB0aGUgcHJldmlvdXMgdmFsdWUgb2YgdGhlIHRpbWUgc2VyaWVzLgoKV2UgY2FuIGNhbGN1bGF0ZSBob2xkaW5nIHJldHVybnMgZm9yIGFueSB0aW1lIHBlcmlvZCBmb3IgZWFjaCBpbnN0cnVtZW50IChIUFIpLiBGb3IgZXhhbXBsZSwgaWYgd2Ugd2FudCB0byBjYWxjdWxhdGUgdGhlIHdob2xlIHBlcmlvZCByZXR1cm4gZnJvbSB0aGUgZmlyc3QgZGF5IG9mIHRoZSBzZXJpZXMgdG8gdGhlIG1vc3QgcmVjZW50IG9uZSwgd2UgY2FuIGRvIHRoZSBmb2xsb3dpbmc6CgpgYGB7cn0KSFBSMTwtIDEwMCogYXMudmVjdG9yKGxhc3QoYWRqcHJpY2VzKSkgLyBhcy52ZWN0b3IoZmlyc3QoYWRqcHJpY2VzKSkgLSAxIApIUFIxCmBgYApgYGB7cn0KY2F0KCJUaGUgaG9sZGluZyBwZXJpb2QgcmV0dXJuIGZvciBCaXRjb2luIGlzOiAiLCBIUFIxWzFdLCAiJSIpCmBgYApgYGB7cn0KY2F0KCJUaGUgaG9sZGluZyBwZXJpb2QgcmV0dXJuIGZvciBCaXRjb2luIGlzOiAiLCBIUFIxWzJdLCAiJSIpCmBgYApXZSBjb3VsZCBhbHNvIHVzZSB0aGUgY29udGludW91c2x5IGNvbXBvdW5kZWQgcmV0dXJucyB0byBjYWxjdWxhdGUgdGhlIHNhbWUgaG9sZGluZyBzaW1wbGUgcmV0dXJuczoKCmBgYHtyfQpIUFIyPC0xMDAqZXhwKGNvbFN1bXMoY2NyZXR1cm5zLG5hLnJtPVRSVUUpKSAtIDEKSFBSMgpgYGAKIyMgMi4zIFZpc3VhbGl6YXRpb24gb2YgZmluYW5jaWFsIGRhdGEKCk9uZSBvZiB0aGUgYWR2YW50YWdlcyBvZiB0aGUgcXVhbnRtb2QgcGFja2FnZSBpcyB0aGF0IGl0IGhhcyBzb21lIGJ1aWx0LWluIGRhdGEgdmlzdWFsaXphdGlvbiBjYXBhYmlsaXRpZXMuIEZvciBleGFtcGxlLCB0aGUgY2hhcnRTZXJpZXMgZnVuY3Rpb24gaXMgYSBwbG90dGluZyB0b29sIGRlc2lnbmVkIHRvIGNyZWF0ZSBzdGFuZGFyZCBmaW5hbmNpYWwgY2hhcnRzIGdpdmVuIGEgdGltZSBzZXJpZXMgb2JqZWN0LiBUaGUgY2hhcnRTZXJpZXMgZnVuY3Rpb24gYWxzbyBpbmNsdWRlcyBzb21lIGFyZ3VtZW50cyB0aGF0IGFsbG93cyB0aGUgdXNlciB0byBtb2RpZnkgdGhlIGNvc21ldGljcyBvZiB0aGUgcGxvdCBzdWNoIGFzIHRoZSB0aGVtZSBhcmd1bWVudC4KCkxldOKAmXMgc2VlIHRoZSBwZXJmb3JtYW5jZSBvZiBCaXRjb2luIHByaWNlczoKCmBgYHtyfQpjaGFydFNlcmllcyhgQlRDLVVTRGAsIHRoZW1lID0gKCJ3aGl0ZSIpKQpgYGAKTGV04oCZcyBzZWUgdGhlIHBlcmZvcm1hbmNlIG9mIFRFU0xBIHByaWNlczoKYGBge3J9CmNoYXJ0U2VyaWVzKFRTTEEsIHRoZW1lID0gKCJibGFjayIpKQpgYGAKV2UgY2FuIHNlZSBleHBvbmVudGlhbCBncm93dGggb2YgYm90aCwgdGhlIEJpdGNvaW4gcHJpY2VzIGFuZCBUZXNsYSBwcmljZXMgZm9yIHRoZSBsYXN0IGRheXMuIElmIHlvdSBoYWQgaW52ZXN0ZWQgaW4gQml0Y29pbiBvciBUZXNsYSBpbiBKdWx5IDIwMjAgYW5kIGhhcyBzb2xkIHlvdXIgcG9zaXRpb24gZWFybHkgSmFudWFyeSAyMDIxLCB5b3Ugd291bGQgaGF2ZSBtdWx0aXBsaWVkIHlvdXIgaW52ZXN0bWVudCBieSBhcm91bmQgNCB0aW1lcyAoMzAwJSBwZXJpb2QgcmV0dXJuKSEKCldlIGNhbiB2aXN1YWxpemUgdGhlIGRhaWx5IHJldHVybnMgb3ZlciB0aW1lOgoKYGBge3J9CnBsb3QocmV0dXJucyRiaXRjb2luKQpgYGAKCmBgYHtyfQpwbG90KHJldHVybnMkdGVzbGEpCmBgYApXaXRoIHRoaXMgcGxvdCB3ZSBjYW4gYXBwcmVjaWF0ZSB0aGUgZGFpbHkgdm9sYXRpbGl0eSBvZiBlYWNoIGluc3RydW1lbnQgb3ZlciB0aW1lLiBWb2xhdGlsaXR5IGlzIGEgbWVhc3VyZSBvZiBob3cgZGlzcGVyc2UgdGhlIHJldHVybnMgbW92ZSB1cCBhbmQgZG93biBmcm9tIGl0cyBtZWFuOyBpdCBpcyBiYXNpY2FsbHkgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiByZXR1cm5zLgoKIyMgMi40IEJhc2ljcyBvZiBQb3J0Zm9saW8gYW5hbHlzaXMKV2Ugd2lsbCB1c2UgdGhlIFBlcmZvcm1hbmNlQW5hbHl0aWNzIGFuZCByZWxhdGVkIHBhY2thZ2VzIGZvciB0aGlzIHNlY3Rpb246CgpgYGB7cn0KIyBMb2FkIHRoZSBwYWNrYWdlczoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoUGVyZm9ybWFuY2VBbmFseXRpY3MpCmxpYnJhcnkoaGlnaGNoYXJ0ZXIpCmBgYAoKV2Ugd2lsbCBkbyBhbiBleGVyY2lzZSBvZiBhIHNpbXBsZSBwb3J0Zm9saW8gY29tcG9zZWQgYnkgNSBVUyBzdG9ja3M6IFRlc2xhLCBNaWNyb3NvZnQsIE5leHRlcmEsIFdhbE1hcnQgYW5kIFBmaXplci4gWW91IGNhbiBjaGFuZ2UgdGhlIHRpY2tlcnMgb2YgdGhlIHN0b2NrcyBhcyB5b3Ugd2lzaCBhcyBsb25nIGFzIFlhaG9vIEZpbmFuY2UgaGFzIGRhdGEgZm9yIHlvdXIgdGlja2Vycy4gSSBzZWxlY3RlZCBzdG9ja3MgZnJvbSB0aGUgaW5kdXN0cmllczogaGlnaC10ZWNoLCBjbGVhbiBlbmVyZ2llcywgcGhhcm1hY2V1dGljYWwsIGFuZCByZXRhaWwuCgojIyAyLjQuMSBBdXRvbWF0aW9uIG9mIGRhdGEgY29sbGVjdGlvbiBhbmQgZGF0YSBtYW5hZ2VtZW50CldpdGggdGhlIGZvbGxvd2luZyBjb2RlIHdlIHdpbGwgYXV0b21hdGljYWxseSBkb3dubG9hZCBkYXRhIGZyb20gdGhlIDUgc3RvY2tzLCBtZXJnZSB0aGUgZGF0YSBhbmQgY2FsY3VsYXRlIHJldHVybnM6CgpgYGB7cn0Kc3ltYm9scyA8LSBjKCJUU0xBIiwiTVNGVCIsIk5FRSIsIldNVCIsIlBGRSIpCgpwcmljZXMgPC0gZ2V0U3ltYm9scyhzeW1ib2xzLHNyYyA9ICd5YWhvbycsCiAgICAgICAgICAgICBwZXJpb2RpY2l0eSA9ICJtb250aGx5IiwKICAgICAgICAgICAgIGZyb20gPSAiMjAxOC0wMS0wMSIsCiAgICAgICAgICAgICBhdXRvLmFzc2lnbiA9IFRSVUUsCiAgICAgICAgICAgICB3YXJuaW5ncyA9IEZBTFNFKSAlPiUKICBtYXAofkFkKGdldCguKSkpICU+JQogIHJlZHVjZShtZXJnZSkgJT4lCiAgYGNvbG5hbWVzPC1gKHN5bWJvbHMpCmBgYApJZiB5b3Ugc2VlIEkgYW0gZG9pbmcgc2V2ZXJhbCBkYXRhIG1hbmFnZW1lbnQgcHJvY2VzcyBpbiBzZXF1ZW50aWFsIHByb2Nlc3MuIFRoZSAlPiUgaXMgdXNlZCB0byBpbmRpY2F0ZSB0aGUgc2VwYXJhdGlvbiBvZiBlYWNoIGRhdGEgbWFuYWdlbWVudCBwcm9jZXNzLiBUaGUgcHJldmlvdXMgY29kZSBkb2VzIHRoZSBmb2xsb3dpbmc6CgpEb3dubG9hZCBwcmljZSBkYXRhIG9mIHRoZSA1IHN0b2NrcwoKQXBwbHkgKG1hcCkgdGhlIGFkanVzdGVkIGZ1bmN0aW9uIHRvIGFsbCB4dHMtem9vIGRhdGFzZXRzIGluIG9yZGVyIHRvIGdldCB0aGUgYWRqdXN0ZWQgc3RvY2sgcHJpY2VzCgpEbyB0aGUgbWVyZ2Ugb2YgYWxsIHRoZSB4dHMtem9vIG9iamVjdHMgaW50byBvbmUgb2JqZWN0CgpGaW5hbGx5IHJlbmFtZSB0aGUgY29sdW1ucyBvZiB0aGUgaW50ZWdyYXRlZCBvYmplY3QsIGFuZCByZXR1cm4gdGhlIG9iamVjdCBhcyBwcmljZXMuCgojIyAyLjQuMiBSZXR1cm4gY2FsY3VsYXRpb25zCgpXZSBjYWxjdWxhdGUgcmV0dXJucyBvZiBhbGwgc3RvY2tzIHVzaW5nIHRoZSBSZXR1cm4uY2FsY3VsYXRlIGZ1bmN0aW9uOgoKYGBge3J9ClJldHVybnM8LVJldHVybi5jYWxjdWxhdGUocHJpY2VzKSAlPiUKICBuYS5vbWl0KCkgCmBgYAoKVGhlIG5hLm9taXQgZnVuY3Rpb24gd2FzIGFsc28gYXBwbGllZCB0byBjbGVhbiB0aGUgZGF0YXNldCBhbmQgZHJvcCB0aGUgcm93cyB3aXRoIE5BIHZhbHVlcy4KCldlIGNhbGN1bGF0ZSB0aGUgY29udGludW91cyBjb21wb3VuZGVkIHJldHVybiAoYWxzbyBjYWxsZWQgbG9nIHJldHVybnMpIG9mIGFsbCBzdG9ja3M6CgpgYGB7cn0KcmV0dXJuczwtUmV0dXJuLmNhbGN1bGF0ZShwcmljZXMsbWV0aG9kID0gImxvZyIpICU+JQogICAgICAgICBuYS5vbWl0KCkgCiMgV2UgY291bGQgY2FsY3VsYXRlIHRoaXMgdXNpbmcgdGhlIGRpZmYgYW5kIGxvZyBmdW5jdGlvbnM6CnJldHVybnMyPC1uYS5vbWl0KGRpZmYobG9nKHByaWNlcykpKQojIFRoZSByZXR1cm5zIGFuZCByZXR1cm5zMiBvYmplY3RzIHdpbGwgaGF2ZSBleGFjdGx5IHRoZSBzYW1lIHJldHVybnMKYGBgCgojIyAyLjQuMyBEZXNjcmlwdGl2ZSBzdGF0aXN0aWNzIG9mIHJldHVybnMKV2UgY2FsY3VsYXRlIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MgZm9yIHRoZSByZXR1cm5zIG9mIGFsbCBzdG9ja3M6CgpgYGB7cn0KdGFibGUuU3RhdHMocmV0dXJucykgCmBgYApXZSBjYW4gc2VlIGFyaXRobWV0aWMsIGdlb21ldHJpYyBtZWFuLCBtZWRpYW4gb2YgcmV0dXJucy4gQWxzbyB3ZSBjYW4gc2VlIHJpc2sgbWVhc3VyZXMgc3VjaCBhcyBzdGFuZGFyZCBkZXZpYXRpb24sIHZhcmlhbmNlLiBGaW5hbGx5IHdlIGNhbiBzZWUgc2tld25lc3MgYW5kIGt1cnRvc2lzLCB3aGljaCBhcmUgaW1wb3J0YW50IGZpbmFuY2lhbCBtZWFzdXJlcyB0byB1bmRlcnN0YW5kIHRoZSBkYXRhLgoKIyMgMi40LjQgVmlzdWFsaXphdGlvbiBvZiByaXNrIGFuZCByZXR1cm4gb2Ygc3RvY2tzCldlIGNhbiBkbyBhIEJveCBwbG90IG9mIHRoZSByZXR1cm5zIHRvIGJldHRlciBhcHByZWNpYXRlIGhpc3RvcmljYWwgbWVkaWFuIHJldHVybiBhbmQgcmlzayBieSBsb29raW5nIGF0IHRoZSBtZWRpYW4sIHRoZSBxdWFydGlsZXMgUTEgYW5kIFEzLCB2b2xhdGlsaXR5IGFuZCBleHRyZW1lIHZhbHVlcyBvZiB0aGVzZSByZXR1cm5zOgoKYGBge3J9CmNoYXJ0LkJveHBsb3QocmV0dXJucykKYGBgCkl0IGlzIGVhc3kgdG8gc2VlIHRoYXQgVGVzbGEgaXMgdGhlIHN0b2NrIHdpdGggdGhlIGhpZ2hlc3Qgcmlzay4gVGhlIHJlZCBjaXJjbGVzIHNob3cgdGhlIG1lYW4sIHRoZSBtaWQgbGluZSBpcyB0aGUgbWVkaWFuICg1MCBwZXJjZW50aWxlKSwgdGhlIGJveGVzIGluY2x1ZGUgdGhlIDUwJSBvZiB0aGUgZGF0YSBmcm9tIHRoZSBRMSAoMjUgcGVyY2VudGlsZSkgdG8gdGhlIFEzICg3NSBwZXJjZW50aWxlKS4gVGhlIGRvdHMgYXJlIGNvbnNpZGVyZWQgZXh0cmVtZSB2YWx1ZXMgZm9yIGluIHRoZSBjb250ZXh0IG9mIGl0cyBvd24gZGlzdHJpYnV0aW9uLgoKTm93IHdlIGNhbiBzdGFydCBldmFsdWF0aW5nIHRoZSBwZXJmb3JtYW5jZSBvdmVyIHRpbWUgZm9yIGVhY2ggc3RvY2sgYnkgbG9va2luZyBhdCBob3cgbXVjaCAkIHdlIHdvdWxkIGhhdmUgbWFkZSBpZiB3ZSBoYWQgaW52ZXN0ZWQgJDEuMDAgaW4gZWFjaCBzdG9jayBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZSB0aW1lIHBlcmlvZHM6CgpgYGB7cn0KY2hhcnRzLlBlcmZvcm1hbmNlU3VtbWFyeShSZXR1cm5zLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBtYWluID0gIlBlcmZvcm1hbmNlIG9mICQxLjAwIG92ZXIgdGltZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2VhbHRoLmluZGV4ID0gVFJVRSkKYGBgCgpTaW5jZSBUZXNsYSBoYXMgaGFkIGFuIGV4dHJhb3JkaW5hcnkgcGVyZm9ybWFuY2UgZm9yIHRoZSBsYXN0IG1vbnRocywgaXQgaXMgaGFyZCB0byBhcHByZWNpYXRlIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgcmVzdCBvZiB0aGUgc3RvY2tzLiBTbywgd2UgY2FuIGRyb3AgVGVzbGEgZnJvbSB0aGUgcGxvdDoKCmBgYHtyfQpjaGFydHMuUGVyZm9ybWFuY2VTdW1tYXJ5KFJldHVybnNbLDI6NV0sIAogICAgICAgICAgICAgICAgICAgICAgICBtYWluID0gIlBlcmZvcm1hbmNlIG9mICQxLjAwIG92ZXIgdGltZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2VhbHRoLmluZGV4ID0gVFJVRSkKYGBgCgojIyAyLjUgUG9ydGZvbGlvIGZvcm1hdGlvbgoKSSBzdGFydCBjcmVhdGluZyB0aGUgd2VpZ2h0cyBvZiAyIHBvcnRmb2xpb3MuIE9uZSB3aWxsIGJlIGFuIGVxdWFsbHktd2VpZ2h0ZWQgcG9ydGZvbGlvLCBhbmQgdGhlIG90aGVyIHdpbGwgYmUgYW4gYWdncmVzc2l2ZSBwb3J0Zm9saW8gYXNzaWduaW5nIGhpZ2ggd2VpZ2h0cyB0byByaXNreSBzdG9ja3MsIGFuZCBsb3cgd2VpZ2h0cyB0byBjb25zZXJ2YXRpdmUgc3RvY2tzLgoKSSBzdGFydCBjcmVhdGluZyBhIHZlY3RvciBvZiB3ZWlnaHRzIGZvciB0aGUgZXF1YWxseSB3ZWlnaHRlZCBwb3J0Zm9saW86CgpgYGB7cn0Kd19ldyA9IHJlcCgwLjIsNSkKIyBUaGUgcmVwIGZ1bmN0aW9uIHJlcGVhdHMgYSB2YWx1ZSBuIHRpbWVzCmBgYAoKTm93IEkgY3JlYXRlIGEgdmVjdG9yIG9mIHdlaWdodHMgZm9yIHRoZSBhZ2dyZXNzaXZlIHBvcnRmb2xpby4gSSB3aWxsIGFzc2lnbiB0aGUgZm9sbG93aW5nIHdlaWdodHM6CgpUZXNsYTogNDAlIE1pY3Jvc29mdDogMzAlIE5leHRlcmE6IDIwJSBXYWxNYXJ0OjAlIFBmaXplcjogMTAlCgpgYGB7cn0Kd19hZ2dyZXNzaXZlID0gYygwLjQsMC4zLDAuMiwwLDAuMSkKYGBgCgojIyAyLjYgUG9ydGZvbGlvIHJldHVybiBjYWxjdWxhdGlvbgoKV2UgY2FsY3VsYXRlIHRoZSBoaXN0b3JpY2FsIHJldHVybiBvZiB0aGUgZXF1YWxseS13ZWlnaHRlZCBwb3J0Zm9saW8gdXNpbmcgdGhlIFJldHVybi5wb3J0Zm9saW8gZnVuY3Rpb246CgpgYGB7cn0KcG9ydGZvbGlvX3JldHVybnNfZXcgPC0KICBSZXR1cm4ucG9ydGZvbGlvKFJldHVybnMsCiAgICAgICAgICAgICAgICAgICB3ZWlnaHRzID0gd19ldykgJT4lCiAgYGNvbG5hbWVzPC1gKCJyZXR1cm5zIikKYGBgCgpXZSBjYWxjdWxhdGUgdGhlIGhpc3RvcmljYWwgcmV0dXJuIG9mIHRoZSBhZ2dyZXNzaXZlIHBvcnRmb2xpbyB1c2luZyB0aGUgUmV0dXJuLnBvcnRmb2xpbyBmdW5jdGlvbjoKCmBgYHtyfQpwb3J0Zm9saW9fcmV0dXJuc19hZyA8LQogIFJldHVybi5wb3J0Zm9saW8oUmV0dXJucywKICAgICAgICAgICAgICAgICAgIHdlaWdodHMgPSB3X2FnZ3Jlc3NpdmUpICU+JQogIGBjb2xuYW1lczwtYCgicmV0dXJucyIpCmBgYAoKV2l0aCB0aGUgcG9ydGZvbGlvIHJldHVybnMgSSBjYW4gcGxvdCBhIHBlcmZvcm1hbmNlIGNoYXJ0IG9mIGVhY2ggcG9ydGZvbGlvOgoKYGBge3J9CmNoYXJ0cy5QZXJmb3JtYW5jZVN1bW1hcnkocG9ydGZvbGlvX3JldHVybnNfZXcsIAogICAgICAgICAgICAgICAgICAgICAgICAgIG1haW4gPSAiRXF1YWxseS13ZWlnaHRlZCBQb3J0Zm9saW8iKQpgYGAKCmBgYHtyfQpjaGFydHMuUGVyZm9ybWFuY2VTdW1tYXJ5KHBvcnRmb2xpb19yZXR1cm5zX2FnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBtYWluID0gIkFnZ3Jlc3NpdmUgUG9ydGZvbGlvIFBlcmZvcm1hbmNlIikKYGBgCldlIGZpbmFsbHkgZG8gYSBkeW5hbWljIHBsb3Qgb2YgaGlzdG9yaWNhbCBtb250aGx5IHJldHVybnMgb2YgZWFjaCBwb3J0Zm9saW8gdXNpbmcgdGhlIGhpZ2hjaGFydCBmdW5jdGlvbiBmcm9tIHRoZSBoaWdoY2hhcnRlciBwYWNrYWdlOgoKYGBge3J9CmhpZ2hjaGFydCh0eXBlID0gInN0b2NrIikgJT4lIAogIGhjX3RpdGxlKHRleHQgPSAiRXF1YWxseS13ZWlnaHRlZCBQb3J0Zm9saW8iKSAlPiUgCiAgaGNfYWRkX3Nlcmllcyhwb3J0Zm9saW9fcmV0dXJuc19ldyRyZXR1cm5zLCAKICAgICAgICAgICAgICAgIG5hbWUgPSAiTW9udGhseSBSZXR1cm5zIiwgCiAgICAgICAgICAgICAgICBjb2xvciA9ICJjb3JuZmxvd2VyYmx1ZSIpICU+JSAKICBoY19hZGRfdGhlbWUoaGNfdGhlbWVfZmxhdCgpKSAlPiUgCiAgaGNfbmF2aWdhdG9yKGVuYWJsZWQgPSBGQUxTRSkgJT4lIAogIGhjX3Njcm9sbGJhcihlbmFibGVkID0gRkFMU0UpICU+JSAKICBoY19sZWdlbmQoZW5hYmxlZCA9IFRSVUUpICU+JSAKICBoY19leHBvcnRpbmcoZW5hYmxlZCA9IFRSVUUpCmBgYApXZSBjYW4gaW50ZXJhY3Qgd2l0aCB0aGUgcGxvdCB0byB6b29tIGluIGFuZCBvdXQsIGFuZCBzZWxlY3RpbmcgdGltZSBwZXJpb2RzLgoKV2UgZG8gdGhlIHNhbWUgZm9yIHRoZSBhZ2dyZXNzaXZlIHBvcnRmb2xpbzoKCmBgYHtyfQpoaWdoY2hhcnQodHlwZSA9ICJzdG9jayIpICU+JSAKICBoY190aXRsZSh0ZXh0ID0gIkFnZ3Jlc3NpdmUgUG9ydGZvbGlvIikgJT4lIAogIGhjX2FkZF9zZXJpZXMocG9ydGZvbGlvX3JldHVybnNfYWckcmV0dXJucywgCiAgICAgICAgICAgICAgICBuYW1lID0gIk1vbnRobHkgUmV0dXJucyIsIAogICAgICAgICAgICAgICAgY29sb3IgPSAiY29ybmZsb3dlcmJsdWUiKSAlPiUgCiAgaGNfYWRkX3RoZW1lKGhjX3RoZW1lX2ZsYXQoKSkgJT4lIAogIGhjX25hdmlnYXRvcihlbmFibGVkID0gRkFMU0UpICU+JSAKICBoY19zY3JvbGxiYXIoZW5hYmxlZCA9IEZBTFNFKSAlPiUgCiAgaGNfbGVnZW5kKGVuYWJsZWQgPSBUUlVFKSAlPiUgCiAgaGNfZXhwb3J0aW5nKGVuYWJsZWQgPSBUUlVFKQpgYGAKCiMgMyBJbnRyb2R1Y3Rpb24gdG8gUG9ydGZvbGlvIFRoZW9yeQoKUmVnYXJkbGVzcyB0aGUgYXBwcm9hY2ggeW91IHVzZSB0byBkb3dubG9hZCBhbmQgbWFuaXB1bGF0ZSB0aGUgc291cmNlIGRhdGEsIG9uY2UgdGhhdCB5b3VyIGRhdGEgZnJhbWUgb2YgbW9udGhseSByZXR1cm5zIGlzIHJlYWR5IGZvciBhbmFseXNpcyB5b3Ugd2lsbCBiZSBpbiBhIGdvb2QgcG9zaXRpb24gdG8gY3JlYXRlIHRoZSBWYXJpYW5jZS1jb3ZhcmlhbmNlIG1hdHJpeCB3aGljaCBpcyBuZWVkZWQgdG8gY29tcHV0ZSB0aGUgZXhwZWN0ZWQgcmlzayBvZiB0aGUgcG9ydGZvbGlvLgoKVGhlIGZpcnN0IHN0ZXAgaXMgdG8gdHJhbnNmb3JtIHRoZSBkYXRhIGZyYW1lIGludG8gYSBtYXRyaXggdXNpbmcgdGhlIGFzLm1hdHJpeCBmdW5jdGlvbi4gV2Ugc2F2ZSB0aGUgcmVzdWx0IGluIGEgbWF0cml4IGNsYXNzIG9iamVjdCBjYWxsZWQgcmV0Lm1hdC4KCmBgYHtyfQpyZXQubWF0IDwtIGFzLm1hdHJpeChSZXR1cm5zKQpgYGAKCldlIGNvbXB1dGUgdGhlIFZhci1Db3ZhcmlhbmNlIG1hdHJpeC4gVGhlIHZhciBmdW5jdGlvbiByZWNlaXZlcyBhIG1hdHJpeCBvZiByZXR1cm5zIGFzIHBhcmFtZXRlci4gVGhlIHJldHVybnMgb2Ygc3RvY2sgMSBhcmUgaW4gY29sdW1uIDEsIHJldHVybnMgb2Ygc3RvY2sgMiBhcmUgaW4gY29sdW1uIDIsIGFuZCBzbyBvbi4gVGhpcyBmdW5jdGlvbiBjYWxjdWxhdGVzIHRoZSBWYXJpYW5jZS1Db3ZhcmlhbmNlIE1hdHJpeCBvZiBzdG9jayByZXR1cm5zLgoKUmVtZW1iZXIgdGhhdCB0aGUgVmFyaWFuY2UtQ292YXJpYW5jZSBtYXRyaXggY29udGFpbnMgVmFyaWFuY2VzIG9mIHN0b2NrIHJldHVybnMgaW4gdGhlIGRpYWdvbmFsLCBhbmQgaXQgY29udGFpbnMgdGhlIGNvdmFyaWFuY2VzIG9mIHBhaXJzIG9mIHN0b2NrIHJldHVybnMgaW4gdGhlIG5vbi1kaWFnb25hbHMuIEFsc28sIHRoaXMgbWF0cml4IGlzIHN5bWV0cmljLCBzaW5jZSBDb3YoUmV0aSwgUmV0aikgPSBDb3YoUmV0aixSZXRpKS4gTW9yZSBmb3JtYWxseToKCkZvciBleGFtcGxlLCB0aGUgdmFyaWFuY2UtY292YXJpYW5jZSBtYXRyaXggb2YgMiB2YXJpYWJsZXMgWDEgYW5kIFgyIGNvbnRhaW5zIHRoZSB2YXJpYW5jZSBvZiBlYWNoIHZhcmlhYmxlIGluIGl0cyBkaWFnb25hbCBhbmQgY292YXJpYW5jZXMgYmV0d2VlbiB0aGUgdmFyaWFibGVzIGluIGl0cyBub24tZGlhZ29uYWwgdGVybXMuIEluIG90aGVyIHdvcmRzLCB0aGUgZWxlbWVudHMgaW4gdGhlIHVwcmlnaHQgcGFydCBvZiB0aGUgbWF0cml4IGFyZSByZXBlYXRlZCBpbiB0aGUgZG93bi1sZWZ0IHBhcnQgb2YgdGhlIG1hdHJpeC4gTW9yZSBmb3JtYWxseToKCuKIkWNvdj1bVmFyKFgxKUNvdihYMixYMSlDb3YoWDEsWDIpVmFyKFgyKV0KCmBgYHtyfQpDT1YgPC0gdmFyKHJldC5tYXQpCmBgYAoKV2UgY2FuIHNlZSB0aGUgcmVzdWx0cyBvZiB0aGUgVmFyLUNvdmFyIG1hdHJpeCBhcyBzaG93biBiZWxvdzoKCmBgYHtyfQpDT1YKYGBgCgpXZSBjYW4gYWxzbyBjYWxjdWxhdGUgdGhlIENvcnJlbGF0aW9uIE1hdHJpeCB0byBiZXR0ZXIgdW5kZXJzdGFuZCB0aGUgcmVsYXRpb25zaGlwcyBvZiBhbGwgZGlmZmVyZW50IHBhaXJzIG9mIHN0b2NrIHJldHVybnMuCgpgYGB7cn0KY29yKHJldC5tYXQpCmBgYAoKUmVtZW1iZXIgdGhhdCB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiAyIHN0b2NrIHJldHVybnMgY2FuIGJlIGFueSB2YWx1ZSBiZXR3ZWVuIC0xIGFuZCAxLiBXaGVuIHRoZSBjb3JyZWxhdGlvbiBpcyBjbG9zZSB0byAwLCBpdCBtZWFucyB0aGF0IGJvdGggc3RvY2tzIGhhdmUgbm8gcmVsYXRpb25zaGlwLiBJZiB0aGUgY29ycmVsYXRpb24gaXMgY2xvc2UgdG8gMSBpdCBtZWFucyB0aGF0IGJvdGggc3RvY2sgcmV0dXJucyBtb3ZlIGluIGEgdmVyeSBzaW1pbGFyIHdheSBpbiB0aGUgc2FtZSBkaXJlY3Rpb24uCgpXZSBjYWxjdWxhdGUgdGhlIGV4cGVjdGVkIHNpbXBsZSByZXR1cm5zIG9mIHRoZSBzdG9ja3MgaW4gYW5vdGhlciBtYXRyaXggdXNpbmcgdGhlIGFwcGx5IGZ1bmN0aW9uLiBJdCBpcyBpbXBvcnRhbnQgdG8gZG91YmxlIGNoZWNrIHRoYXQgdGhlIHNlY29uZCBwYXJhbWV0ZXIgaXMgZXF1YWwgdG8gMi4gVGhpcyBtZWFucyB0aGF0IHdlIGFyZSBhcHBseWluZyB0aGUgZnVuY3Rpb24gbWVhbiBieSBjb2x1bW4uIElmIHdlIHdhbnQgdG8gYXBwbHkgdGhlIG1lYW4gZnVuY3Rpb24gYnkgcmF3LCB3ZSBoYXZlIHRvIHNwZWNpZnkgdGhlIHNlY29uZCBwYXJhbWV0ZXIgYXMgMS4KCmBgYHtyfQpFUjwtIGV4cChhcHBseShyZXQubWF0LCAyLCBtZWFuKSkgLSAxCmBgYAoKVGhpcyBpcyB0aGUgc2ltcGxlIHdheSB0byBjYWxjdWxhdGUgdGhlIGV4cGVjdGVkIHJldHVybiBvZiBlYWNoIHN0b2NrIGFjY29yZGluZyB0byBNYXJrb3dpdHogdGhlb3J5LiBSZW1lbWJlciB0aGF0IHRoZSBleHBlY3RlZCByZXR1cm4gb2Ygb25lIHN0b2NrIGlzIHNpbXBseSB0aGUgR2VvbWV0cmljIGF2ZXJhZ2UgcmV0dXJuIG9mIGl0cyBoaXN0b3JpY2FsIHJldHVybnMuCgpUbyBjYWxjdWxhdGUgdGhpcyBleHBlY3RlZCByZXR1cm4gd2UgaGF2ZSAyIG1ldGhvZHM6CgpDYWxjdWxhdGUgdGhlIHByb2R1Y3RzIG9mIEdyb3NzIFJldHVybnMgb2YgZWFjaCBwZXJpb2QsIHN1YnN0cmFjdCAxLCBhbmQgdGhlbiBhcHBseSB0aGUgTiByb290IHRvIGdldCB0aGUgZ2VvbWV0cmljIGF2ZXJhZ2UuCgpHZXQgdGhlIGFyaXRobWV0aWMgYXZlcmFnZSBvZiBjb250aW51b3VzbHkgY29tcG91bmRlZCByZXR1cm5zIGFuZCB0aGVuIGNvbnZlcnQgdGhpcyBhbW91bnQgdG8gc2ltcGxlIHJldHVybnMgYnkgcmFpc2luZyBlIHRvIHRoaXMgYXZlcmFnZSBhbmQgdGhlbiBzdWJzdHJhY3QgMS4KCldlIGhhdmUgZXN0aW1hdGVkIHRoZSBnZW9tZXRyaWMgYXZlcmFnZSB1c2luZyB0aGUgbWV0aG9kIDIsIGJ1dCBub3Qgb25seSBmb3Igb25lIHN0b2NrLCBidXQgZm9yIGFsbCBzdG9ja3MgaW4gdGhlIHRoZSBtYXRyaXggcmV0Lm1hdC4gVGhlIHJlc3VsdCB3aWxsIGJlIGEgdmVjdG9yIG9mIGdlb21ldHJpYyByZXR1cm5zIHRoYXQgd2lsbCBiZSBzYXZlZCBpbiBlci4gV2UgY2FuIGVzdGltYXRlIHRoZSB2ZWN0b3IgTXIgb2YgZXhwZWN0ZWQgcmV0dXJucyBhczoKCk1SPWV4cFtNcl09ZXhw4o6h4o6j4o6i4o6i4o6i4o6i4o6i4o6i4o6i4o6icjHCr3Iywq8uLi5yTsKv4o6k4o6m4o6l4o6l4o6l4o6l4o6l4o6l4o6l4o6l4oiSMQoKV2UgY2FuIHNlZSB0aGUgZXhwZWN0ZWQgc2ltcGxlIHJldHVybnMgYnkgY2FsbGluZyB0aGUgZXIgb2JqZWN0LgoKYGBge3J9CkVSCmBgYAoKV2UgY2FsY3VsYXRlIHRoZSB2YXJpYW5jZSwgc3RhbmRhcmQgZGV2aWF0aW9uLCBza2V3bmVzcyBhbmQga3VydG9zaXMgb2YgYWxsIGFzc2V0IHJldHVybnMgKGJ5IGNvbHVtbnMpLiBXZSBkbyB0aGlzIGp1c3QgdG8gc2VlIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3Mgb2YgdGhlIHN0b2NrIHJldHVybnMuCgpgYGB7cn0KYXBwbHkocmV0Lm1hdCwgMiwgdmFyKQpgYGAKYGBge3J9CmFwcGx5KHJldC5tYXQsIDIsIHNkKQpgYGAKYGBge3J9CmFwcGx5KHJldC5tYXQsIDIsIHNrZXduZXNzKQpgYGAKYGBge3J9CmFwcGx5KHJldC5tYXQsIDIsIGt1cnRvc2lzKQpgYGAKUmVtZW1iZXIgd2hhdCBpcyBza2V3bmVzcyBhbmQga3VydG9zaXMuIFNrZXduZXNzIGlzIGEgbWVhc3VyZSBvZiB0aGUgc2hhcGUgb2YgdGhlIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbiBvZiB0aGUgcmV0dXJucyB3aGlsZSBrdXJ0b3NpcyBnaXZlcyB1cyBpbmZvcm1hdGlvbiBhYm91dCBob3cgbXVjaCB0aGUgc3RvY2sgaGFzIGhhZCBleHRyZW1lIG5lZ2F0aXZlIGFuZCBwb3NpdGl2ZSByZXR1cm5zLiBJbiBvdGhlciB3b3Jkcywga3VydG9zaXMgaGVscHMgdXMgdG8gaWRlbnRpZnkgaG93IGJpZyBhcmUgdGhlIGZhdC10YWlscyBvZiB0aGUgcmV0dXJuIGRpc3RyaWJ1dGlvbi4gV2UgY2FuIGNvbXBsZW1lbnQgdGhpcyB3aXRoIHRoZSBoaXN0b2dyYW0gb2YgcmV0dXJucyB0byBiZXR0ZXIgYXBwcmVjaWF0ZSB0aGUgbGV2ZWwgb2YgcmlzayBvZiB0aGUgc3RvY2suCgojIDQgQ2FsY3VsYXRpb24gb2YgZXhwZWN0ZWQgdmFyaWFuY2UgYW5kIHJpc2sgb2YgYSBQb3J0Zm9saW8KQXNzdW1lIHRoYXQgeW91IGludmVzdCBpbiBhIHBvcnRmb2xpbyB3aXRoIHRoZSBmb2xsb3dpbmcgd2VpZ2h0czogQXBwbGUgMzAlLCBXYWxtYXJ0IDEwJSwgTWljcm9zb2Z0IDMwJSwgR0UgMTAlIGFuZCBUZXNsYSAyMCUuIENyZWF0ZSBhIHZlY3RvciB3aXRoIHRoZXNlIDUgd2VpZ2h0czoKCmBgYHtyfQpXIDwtIGMoMC4zMCwwLjEwLDAuMzAsMC4xMCwwLjIwKQpgYGAKCkFjY29yZGluZyB0byBQb3J0Zm9saW8gVGhlb3J5LCB0aGUgZXhwZWN0ZWQgcmlzayBvZiBhIHBvcnRmb2xpbyBQIGlzIGNhbGN1bGF0ZWQgYXMgdGhlIHNxdWFyZWQgcm9vdCBvZiB0aGUgUG9ydGZvbGlvIFZhcmlhbmNlLgoKU0QoUCk9KFZhcihQKSniiJLiiJLiiJLiiJLiiJLiiJLiiJLiiJLiiJoKClRoZSBwb3J0Zm9saW8gdmFyaWFuY2UgY2FuIGJlIGNhbGN1bGF0ZWQgd2l0aCB0aGUgZm9sbG93aW5nIG1hdHJpeCBtdWx0aXBsaWNhdGlvbjoKClZhcihQKT1X4oCy4oiX4oiRY2924oiXVwp3aGVyZToKClcgaXMgdGhlIHdlaWdodCB2ZWN0b3Igb2YgdGhlIHN0b2NrcywgYW5kIFfigLIgaXMgdGhlIHRyYW5wb3NlZCBtYXRyaXggb2Ygd2VpZ2h0cy4KCldlIG5vdyBjYW4gZXN0aW1hdGUgdGhlIGV4cGVjdGVkIHJldHVybiBhbmQgZXhwZWN0ZWQgcmlzayBvZiB0aGlzIHBvcnRmb2xpby4KCk5vdyBJIGFtIHJlYWR5IHRvIHN0YXJ0IGVzdGltYXRpbmcgdGhlIGV4cGVjdGVkIHJldHVybiBhbmQgcmlzayBvZiB0aGUgcG9ydGZvbGlvIHVzaW5nIE1hdHJpeCBBbGdlYnJhOgoKYGBge3J9CkVSUDEgPC0gdChXKSUqJUVSCkVSUDEKYGBgCgpUaGUgdmVjdG9yIEVSUDEgd2lsbCBoYXZlIG9uZSB2YWx1ZSB3aXRoIHRoZSBleHBlY3RlZCByZXR1cm4gb2YgdGhlIHBvcnRmb2xpby4gVGhlIHQgZnVuY3Rpb24gaXMgdGhlIHRyYW5zcG9zZSBmdW5jdGlvbiBvZiBhIHZlY3RvciBvciBtYXRyaXguIFRoZSByZXN1bHQgb2YgbXVsdGlwbHlpbmcgdGhlIHZlY3RvciB0cmFuc3Bvc2VkIHRpbWVzIHRoZSB2ZWN0b3Igb2YgZXhwZWN0ZWQgcmV0dXJucyBvZiB0aGUgc3RvY2tzIGlzIHRoZSBzYW1lIGFzIGNhbGN1bGF0aW5nIGEgd2VpZ2h0ZWQgYXZlcmFnZS4KCk5vdyBJIGNhbiBlc3RpbWF0ZSB0aGUgZXhwZWN0ZWQgcmlzayBvZiB0aGUgcG9ydGZvbGlvIHVzaW5nIE1hdHJpeCBBbGJlZ3JhOgoKYGBge3J9CkVWQVJQT1JUIDwtIHQoVyklKiVDT1YlKiVXCkVSSVNLIDwtIHNxcnQoRVZBUlBPUlQpCkVSSVNLCmBgYApXaXRoIHRoaXMgbWF0cml4IG11bHRpcGxpY2F0aW9uIEkgYW0gYXBwbHlpbmcgTWFya293aXR6IHRoZW9yeSB0byBlc3RpbWF0ZSB0aGUgZXhwZWN0ZWQgdmFyaWFuY2Ugb2YgdGhlIHBvcnRmb2xpby4gWW91IGNhbiByZXZpZXcgd2h5IHRoaXMgaXMgdGhlIGNhc2UgaW4gdGhlIE5vdGVzIGFib3V0IFBvcnRmb2xpbyBUaGVvcnkuIE1ha2Ugc3VyZSB5b3UgdW5kZXJzdGFuZCB3aHkgd2UgZG8gdGhpcyBtYXRyaXggbXVsdGlwbGljYXRpb24uCg==