Goal

In this notebook, we walk through a full factor-analysis workflow using the 10 stocks, downloaded through the quantmod package.

Packages

library(dplyr)
library(tidyr)
library(ggplot2)
library(tibble)
library(purrr)

library(quantmod)

Download the 10 stocks data and data preprocessing

We will get monthly returns (percent %) for 10 stocks (JPMorgan, BOA, WellsFargo, Exxon, Chevron, ConocoPhillips, Apple, Microsoft, NextEra Energy, Duke Energy) from 2010-2026. Each column is stock, and each row is one month.

tickers <- c(
  # Banks
  "JPM", "BAC", "WFC",
  # Energy
  "XOM", "CVX", "COP",
  # Tech
  "AAPL", "MSFT",
  # Utilities
  "NEE", "DUK"
)


getSymbols(tickers, from = "2010-01-01", auto.assign = TRUE)
 [1] "JPM"  "BAC"  "WFC"  "XOM"  "CVX"  "COP"  "AAPL" "MSFT" "NEE"  "DUK" 
prices_list <- lapply(tickers, function(tk) {
  Ad(get(tk))
})

prices <- do.call(merge, prices_list)
colnames(prices) <- tickers

returns_list <- lapply(colnames(prices), function(tk) {
  monthlyReturn(prices[, tk], type = "log")
})

returns <- do.call(merge, returns_list)
colnames(returns) <- colnames(prices)

returns <- na.omit(returns)

returns <- returns * 100

Time-series view

plot_df <- returns %>%
  as.data.frame() %>%
  rownames_to_column(var = "date") %>%
  mutate(date = as.Date(date)) %>%
  pivot_longer(
    cols = -date,
    names_to = "stock",
    values_to = "ret"
  )

ggplot(plot_df, aes(x = date, y = ret, color = stock)) +
  geom_line(alpha = 0.7, linewidth = 0.5) +
  theme_minimal() +
  theme(legend.position = "bottom") +
  labs(
    title = "Monthly stock returns",
    x = NULL,
    y = "Monthly return (%)",
    color = "Stock"
  )

Correlation matrix

Factor analysis is about explaining common dependence, so the correlation matrix is a natural starting point.

X <- returns

cor_mat <- cor(X)
cor_df <- as.data.frame(as.table(cor_mat))

p_cor <- ggplot(cor_df, aes(Var2, Var1, fill = Freq)) +
  geom_tile() +
  scale_fill_gradient2(low = "#2166AC", mid = "white", high = "#B2182B", midpoint = 0) +
  coord_equal() +
  theme_minimal() +
  labs(title = "Industry return correlations", x = NULL, y = NULL, fill = "Corr") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

p_cor

We see here that the correlations are mostly positive, and there are clustering structure as expected, indicating the presence of a few latent factors.

Visualize each stock marginally

X_long <- as.data.frame(X) %>%
  pivot_longer(cols = everything(),
               names_to = "Stock",
               values_to = "Return")

ggplot(X_long, aes(x = Stock, y = Return)) +
  geom_boxplot() +
  labs(title = "Monthly Returns by Stock",
       y = "Return (%)") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

vars <- apply(X, 2, var, na.rm = TRUE)
# variance table
var_df <- data.frame(
  Industry = colnames(X),
  Variance = vars
)

ggplot(var_df, aes(x = Industry, y = Variance)) +
  geom_col() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(title = "Variance by Stock")

We see moderate marginal volatilities differences across stocks.

Choosing the number of factors

Scree plot

We first look at the eigenvalues of the sample correlation matrix.

X_std <- scale(X, center = TRUE, scale = TRUE)
pca_full <- prcomp(X_std, center = FALSE, scale. = FALSE)
eig <- pca_full$sdev^2
scree_df <- tibble(component = seq_along(eig), eigenvalue = eig)

ggplot(scree_df, aes(component, eigenvalue)) +
  geom_line() +
  geom_point(size = 2) +
  theme_minimal() +
  scale_x_continuous(breaks = scree_df$component) +
  labs(title = "Scree plot (correlation matrix)",
       x = "Component / factor index",
       y = "Eigenvalue")

We do not see a clear elbow.

Parallel analysis

We compare the observed eigenvalues with those from random noise matrices having the same dimensions.

set.seed(24620)
B <- 500
n <- nrow(X_std)
p <- ncol(X_std)

rand_eigs <- replicate(B, {
  X_rand <- matrix(rnorm(n * p), nrow = n, ncol = p)
  eigen(cor(X_rand), symmetric = TRUE, only.values = TRUE)$values
})

pa_mean <- rowMeans(rand_eigs)
pa_q95 <- apply(rand_eigs, 1, quantile, probs = 0.95)

pa_df <- tibble(
  k = seq_len(p),
  observed = eig,
  null_mean = pa_mean,
  null_q95 = pa_q95
) %>%
  pivot_longer(-k, names_to = "series", values_to = "value")

ggplot(pa_df, aes(k, value, color = series)) +
  geom_line() +
  geom_point(size = 1.8) +
  theme_minimal() +
  scale_x_continuous(breaks = seq_len(p)) +
  labs(title = "Parallel analysis", x = "Index", y = "Eigenvalue", color = NULL)

A simple rule is to keep factors whose observed eigenvalues exceed the null benchmark.

pa_m <- sum(eig > pa_q95)
pa_m
[1] 3

PA suggests 3 factors.

Fit several FA models

We fit several candidate models and compare their residual fit. Here we use factanal() on the data matrix.

fit_grid <- function(X, m_values = 1:5) {
  map(m_values, ~ factanal(X, factors = .x, rotation = "none", scores = "regression")) %>%
    setNames(paste0("m", m_values))
}

fa_fits <- fit_grid(X, m_values = 1:5)
fa_fits$m2

Call:
factanal(x = X, factors = .x, scores = "regression", rotation = "none")

Uniquenesses:
  JPM   BAC   WFC   XOM   CVX   COP  AAPL  MSFT   NEE   DUK 
0.158 0.119 0.341 0.169 0.121 0.251 0.878 0.776 0.991 0.943 

Loadings:
     Factor1 Factor2
JPM   0.826   0.400 
BAC   0.807   0.480 
WFC   0.776   0.240 
XOM   0.797  -0.441 
CVX   0.841  -0.415 
COP   0.796  -0.340 
AAPL  0.325   0.129 
MSFT  0.394   0.262 
NEE                 
DUK   0.151  -0.186 

               Factor1 Factor2
SS loadings      4.203   1.050
Proportion Var   0.420   0.105
Cumulative Var   0.420   0.525

Test of the hypothesis that 2 factors are sufficient.
The chi square statistic is 182.2 on 26 degrees of freedom.
The p-value is 2.14e-25 

Residual correlation diagnostics

After fitting FA, we compare the observed covariance (or correlation) with the model-implied one: \[ \hat \Sigma = \hat L \hat L^\top + \hat\Psi. \]

To assess model fit, we examine the residual matrix: \[ \text{Residual} = R - \hat R. \] We convert covariance matrix to correlation matrix using the cov2cor function. We summarize residual fit using the off-diagonal RMSR.

fa_residual_cor <- function(fit, S) {
  L <- unclass(fit$loadings)
  Psi <- diag(fit$uniquenesses)
  Sigma_hat <- L %*% t(L) + Psi

  R_obs <- cov2cor(S)
  R_hat <- cov2cor(Sigma_hat)

  R_obs - R_hat
}

rmsr_offdiag <- function(M) {
  idx <- row(M) < col(M)
  sqrt(mean(M[idx]^2))
}

diag_tbl <- imap_dfr(fa_fits, function(fit, nm) {
  resid <- fa_residual_cor(fit, cor(X_std))
  tibble(
    model = nm,
    m = fit$factors,
    rmsr = rmsr_offdiag(resid),
    max_abs_resid = max(abs(resid[row(resid) < col(resid)]))
  )
})

diag_tbl

Choose a working model

Let’s first use 3 factors for FA.

Inspect the chosen FA model

The R output also shows you the p-value from likelihood-ratio test, testing whether having these factors are enough assuming data coming from a Gaussian factor analysis model.

m_star <- 3
fa_raw <- factanal(X, factors = m_star, rotation = "none", scores = "regression")
fa_rot <- factanal(X, factors = m_star, rotation = "varimax", scores = "regression")

fa_rot

Call:
factanal(x = X, factors = m_star, scores = "regression", rotation = "varimax")

Uniquenesses:
  JPM   BAC   WFC   XOM   CVX   COP  AAPL  MSFT   NEE   DUK 
0.156 0.120 0.333 0.172 0.113 0.228 0.844 0.744 0.069 0.535 

Loadings:
     Factor1 Factor2 Factor3
JPM   0.864   0.313         
BAC   0.903   0.251         
WFC   0.706   0.403         
XOM   0.240   0.874         
CVX   0.293   0.879   0.171 
COP   0.302   0.825         
AAPL  0.335   0.115   0.175 
MSFT  0.477           0.149 
NEE   0.141           0.952 
DUK           0.146   0.666 

               Factor1 Factor2 Factor3
SS loadings      2.653   2.585   1.447
Proportion Var   0.265   0.259   0.145
Cumulative Var   0.265   0.524   0.669

Test of the hypothesis that 3 factors are sufficient.
The chi square statistic is 49.49 on 18 degrees of freedom.
The p-value is 9.01e-05 

Uniquenesses and communalities

fa_summary <- tibble(
  industry = names(fa_rot$uniquenesses),
  uniqueness = as.numeric(fa_rot$uniquenesses),
  communality = 1 - uniqueness
) %>%
  arrange(desc(communality))

fa_summary

Interpretation:

  • A high communality means that the industry’s return variation is largely explained by the common factors.
  • A high uniqueness means that the industry retains a lot of idiosyncratic variation after accounting for common factors.

We can see that MSFT and AAPL still have large uniqueness, also the p-value is still very small. So maybe we actually need more factors, which is consistent with our intuition.

resid_mat <- fa_residual_cor(fa_rot, cor(X_std))
resid_df <- as.data.frame(as.table(resid_mat))

ggplot(resid_df, aes(Var2, Var1, fill = Freq)) +
  geom_tile() +
  scale_fill_gradient2(low = "#2166AC", mid = "white", high = "#B2182B", midpoint = 0) +
  coord_equal() +
  theme_minimal() +
  labs(title = paste("Residual correlations: FA with", m_star, "factors"),
       x = NULL, y = NULL, fill = "Residual") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

This plot helps decide whether the chosen factor model is still missing systematic structure. We see that MSFT and AAPL still have strong residual correlation, that is evidence that m may still be too small or that the factor model is not capturing the dependence pattern well.

Revised factor model: adding a fourth factor

m_star <- m_star + 1
fa_raw <- factanal(X, factors = m_star, rotation = "none", scores = "regression")
fa_rot <- factanal(X, factors = m_star, rotation = "varimax", scores = "regression")

fa_rot

Call:
factanal(x = X, factors = m_star, scores = "regression", rotation = "varimax")

Uniquenesses:
  JPM   BAC   WFC   XOM   CVX   COP  AAPL  MSFT   NEE   DUK 
0.166 0.104 0.333 0.173 0.109 0.229 0.732 0.005 0.037 0.545 

Loadings:
     Factor1 Factor2 Factor3 Factor4
JPM   0.304   0.817           0.267 
BAC   0.236   0.888           0.228 
WFC   0.393   0.693           0.174 
XOM   0.872   0.238                 
CVX   0.879   0.264   0.164   0.146 
COP   0.819   0.307                 
AAPL  0.113   0.203   0.138   0.442 
MSFT          0.211           0.972 
NEE                   0.966   0.153 
DUK   0.157           0.654         

               Factor1 Factor2 Factor3 Factor4
SS loadings      2.551   2.245   1.420   1.350
Proportion Var   0.255   0.225   0.142   0.135
Cumulative Var   0.255   0.480   0.622   0.757

Test of the hypothesis that 4 factors are sufficient.
The chi square statistic is 16.58 on 11 degrees of freedom.
The p-value is 0.121 
fa_summary <- tibble(
  industry = names(fa_rot$uniquenesses),
  uniqueness = as.numeric(fa_rot$uniquenesses),
  communality = 1 - uniqueness
) %>%
  arrange(desc(communality))

fa_summary
resid_mat <- fa_residual_cor(fa_rot, cor(X_std))
resid_df <- as.data.frame(as.table(resid_mat))

ggplot(resid_df, aes(Var2, Var1, fill = Freq)) +
  geom_tile() +
  scale_fill_gradient2(low = "#2166AC", mid = "white", high = "#B2182B", midpoint = 0) +
  coord_equal() +
  theme_minimal() +
  labs(title = paste("Residual correlations: FA with", m_star, "factors"),
       x = NULL, y = NULL, fill = "Residual") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

The fourth factor seems to mainly explain MSFT, but the test does become insignificant, and there are no remaining large correlations left. So we should still include this factor.

Rotation: unrotated vs varimax loadings

loadings_to_df <- function(fit, label) {
  as.data.frame(unclass(fit$loadings)) %>%
    tibble::rownames_to_column("stock") %>%
    pivot_longer(-stock, names_to = "factor", values_to = "loading") %>%
    mutate(method = label)
}

load_df <- bind_rows(
  loadings_to_df(fa_raw, "Unrotated FA"),
  loadings_to_df(fa_rot, "Varimax FA")
)

load_df$stock <- factor(load_df$stock, levels = rownames(fa_raw$loadings))
ggplot(load_df, aes(factor, stock, fill = loading)) +
  geom_tile() +
  scale_fill_gradient2(low = "#2166AC", mid = "white", high = "#B2182B", midpoint = 0) +
  facet_wrap(~ method, nrow = 1) +
  theme_minimal() +
  labs(title = "FA loadings: unrotated vs varimax",
       x = NULL, y = NULL, fill = "Loading")

In practice, the unrotated loadings often look diffuse: many stocks may load moderately on multiple factors. After varimax rotation, the pattern is usually easier to narrate because some factors become more concentrated on particular groups of stocks.

Factor scores

The regression scores provide estimated latent-factor realizations over time.

Fhat <- fa_rot$scores %>%
      as.data.frame() %>%
      rownames_to_column(var = "date") %>%
      mutate(date = as.Date(date))


Fhat_long <- Fhat %>%
  pivot_longer(-date, names_to = "factor", values_to = "score")

ggplot(Fhat_long, aes(date, score, color = factor)) +
  geom_line(linewidth = 0.5) +
  theme_minimal() +
  theme(legend.position = "bottom") +
  labs(title = "Estimated factor scores over time",
       x = NULL, y = "Score", color = "Factor")

Comparison with PCA

We now compare FA with PCA on the standardized matrix.

pca <- prcomp(X_std, center = FALSE, scale. = FALSE)
round(pca$rotation[, 1:m_star], 3)
       PC1    PC2    PC3    PC4
JPM  0.395 -0.061  0.228 -0.359
BAC  0.383 -0.092  0.273 -0.360
WFC  0.384 -0.140  0.115 -0.306
XOM  0.373 -0.075 -0.391  0.211
CVX  0.393 -0.008 -0.351  0.193
COP  0.375 -0.168 -0.319  0.224
AAPL 0.208  0.226  0.393  0.616
MSFT 0.238  0.195  0.487  0.266
NEE  0.078  0.683 -0.045 -0.165
DUK  0.098  0.616 -0.297 -0.196

PCA vs FA loadings heatmap

pca_load_df <- as.data.frame(pca$rotation[, 1:m_star, drop = FALSE]) %>%
  tibble::rownames_to_column("stock") %>%
  pivot_longer(-stock, names_to = "factor", values_to = "loading") %>%
  mutate(method = "PCA")

fa_rot_df <- loadings_to_df(fa_rot, "FA (varimax)")
plot_load_compare <- bind_rows(pca_load_df, fa_rot_df)
plot_load_compare$stock <- factor(plot_load_compare$stock, levels = rownames(fa_rot$loadings))

ggplot(plot_load_compare, aes(factor, stock, fill = loading)) +
  geom_tile() +
  scale_fill_gradient2(low = "#2166AC", mid = "white", high = "#B2182B", midpoint = 0) +
  facet_wrap(~ method, nrow = 1) +
  theme_minimal() +
  labs(title = "PCA loadings vs rotated FA loadings",
       x = NULL, y = NULL, fill = "Loading")

PCA loadings describe orthogonal variance-maximizing directions, while FA loadings describe how observed variables depend on latent common factors plus uniquenesses. After rotation, FA gives clearer clustering structure of the variables. In contrast, PCA learns the shared market effect as the first PC.

Key takeaways

LS0tCnRpdGxlOiAiTGVjdHVyZSA0IEZhY3RvciBBbmFseXNpcyBMaXZlIERlbW86IDEwIFN0b2NrcyIKYXV0aG9yOiAiU1RBVCAyNDYyMCA9IFNUQVQgMzI5MDAsIEZJTk0gMzQ3MDAiCmRhdGU6ICJTcHJpbmcgMjAyNiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBmaWcud2lkdGggPSA3LjUsCiAgZmlnLmhlaWdodCA9IDQuNSwKICBtZXNzYWdlID0gRkFMU0UsCiAgd2FybmluZyA9IEZBTFNFCikKb3B0aW9ucyhkaWdpdHMgPSA0KQpgYGAKCiMjIEdvYWwKCkluIHRoaXMgbm90ZWJvb2ssIHdlIHdhbGsgdGhyb3VnaCBhIGZ1bGwgZmFjdG9yLWFuYWx5c2lzIHdvcmtmbG93IHVzaW5nIHRoZSAxMCBzdG9ja3MsIGRvd25sb2FkZWQgdGhyb3VnaCB0aGUgYHF1YW50bW9kYCBwYWNrYWdlLgogCiMjIFBhY2thZ2VzCgpgYGB7ciBwYWNrYWdlc30KbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeShwdXJycikKCmxpYnJhcnkocXVhbnRtb2QpCmBgYAoKIyMgRG93bmxvYWQgdGhlIDEwIHN0b2NrcyBkYXRhIGFuZCBkYXRhIHByZXByb2Nlc3NpbmcKCgpXZSB3aWxsIGdldCBtb250aGx5IHJldHVybnMgKHBlcmNlbnQgXCUpIGZvciAxMCBzdG9ja3MgKEpQTW9yZ2FuLCBCT0EsIFdlbGxzRmFyZ28sIEV4eG9uLCBDaGV2cm9uLCBDb25vY29QaGlsbGlwcywgQXBwbGUsIE1pY3Jvc29mdCwgTmV4dEVyYSBFbmVyZ3ksIER1a2UgRW5lcmd5KSBmcm9tIDIwMTAtMjAyNi4gRWFjaCBjb2x1bW4gaXMgc3RvY2ssIGFuZCBlYWNoIHJvdyBpcyBvbmUgbW9udGguIAoKCmBgYHtyIGdldC1kYXRhfQp0aWNrZXJzIDwtIGMoCiAgIyBCYW5rcwogICJKUE0iLCAiQkFDIiwgIldGQyIsCiAgIyBFbmVyZ3kKICAiWE9NIiwgIkNWWCIsICJDT1AiLAogICMgVGVjaAogICJBQVBMIiwgIk1TRlQiLAogICMgVXRpbGl0aWVzCiAgIk5FRSIsICJEVUsiCikKCgpnZXRTeW1ib2xzKHRpY2tlcnMsIGZyb20gPSAiMjAxMC0wMS0wMSIsIGF1dG8uYXNzaWduID0gVFJVRSkKCnByaWNlc19saXN0IDwtIGxhcHBseSh0aWNrZXJzLCBmdW5jdGlvbih0aykgewogIEFkKGdldCh0aykpCn0pCgpwcmljZXMgPC0gZG8uY2FsbChtZXJnZSwgcHJpY2VzX2xpc3QpCmNvbG5hbWVzKHByaWNlcykgPC0gdGlja2VycwoKcmV0dXJuc19saXN0IDwtIGxhcHBseShjb2xuYW1lcyhwcmljZXMpLCBmdW5jdGlvbih0aykgewogIG1vbnRobHlSZXR1cm4ocHJpY2VzWywgdGtdLCB0eXBlID0gImxvZyIpCn0pCgpyZXR1cm5zIDwtIGRvLmNhbGwobWVyZ2UsIHJldHVybnNfbGlzdCkKY29sbmFtZXMocmV0dXJucykgPC0gY29sbmFtZXMocHJpY2VzKQoKcmV0dXJucyA8LSBuYS5vbWl0KHJldHVybnMpCgpyZXR1cm5zIDwtIHJldHVybnMgKiAxMDAKYGBgCgojIyMgVGltZS1zZXJpZXMgdmlldwoKYGBge3IgdGltZS1zZXJpZXMtbG9uZ30KcGxvdF9kZiA8LSByZXR1cm5zICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICByb3duYW1lc190b19jb2x1bW4odmFyID0gImRhdGUiKSAlPiUKICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoZGF0ZSkpICU+JQogIHBpdm90X2xvbmdlcigKICAgIGNvbHMgPSAtZGF0ZSwKICAgIG5hbWVzX3RvID0gInN0b2NrIiwKICAgIHZhbHVlc190byA9ICJyZXQiCiAgKQoKZ2dwbG90KHBsb3RfZGYsIGFlcyh4ID0gZGF0ZSwgeSA9IHJldCwgY29sb3IgPSBzdG9jaykpICsKICBnZW9tX2xpbmUoYWxwaGEgPSAwLjcsIGxpbmV3aWR0aCA9IDAuNSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiTW9udGhseSBzdG9jayByZXR1cm5zIiwKICAgIHggPSBOVUxMLAogICAgeSA9ICJNb250aGx5IHJldHVybiAoJSkiLAogICAgY29sb3IgPSAiU3RvY2siCiAgKQpgYGAKCgojIyMgQ29ycmVsYXRpb24gbWF0cml4CgpGYWN0b3IgYW5hbHlzaXMgaXMgYWJvdXQgZXhwbGFpbmluZyBjb21tb24gZGVwZW5kZW5jZSwgc28gdGhlIGNvcnJlbGF0aW9uIG1hdHJpeCBpcyBhIG5hdHVyYWwgc3RhcnRpbmcgcG9pbnQuCgpgYGB7ciBvYnNlcnZlZC1jb3JyZWxhdGlvbnN9ClggPC0gcmV0dXJucwoKY29yX21hdCA8LSBjb3IoWCkKY29yX2RmIDwtIGFzLmRhdGEuZnJhbWUoYXMudGFibGUoY29yX21hdCkpCgpwX2NvciA8LSBnZ3Bsb3QoY29yX2RmLCBhZXMoVmFyMiwgVmFyMSwgZmlsbCA9IEZyZXEpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICIjMjE2NkFDIiwgbWlkID0gIndoaXRlIiwgaGlnaCA9ICIjQjIxODJCIiwgbWlkcG9pbnQgPSAwKSArCiAgY29vcmRfZXF1YWwoKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIkluZHVzdHJ5IHJldHVybiBjb3JyZWxhdGlvbnMiLCB4ID0gTlVMTCwgeSA9IE5VTEwsIGZpbGwgPSAiQ29yciIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQoKcF9jb3IKYGBgCldlIHNlZSBoZXJlIHRoYXQgdGhlIGNvcnJlbGF0aW9ucyBhcmUgbW9zdGx5IHBvc2l0aXZlLCBhbmQgdGhlcmUgYXJlIGNsdXN0ZXJpbmcgc3RydWN0dXJlIGFzIGV4cGVjdGVkLCBpbmRpY2F0aW5nIHRoZSBwcmVzZW5jZSBvZiBhIGZldyBsYXRlbnQgZmFjdG9ycy4KCgojIyMgVmlzdWFsaXplIGVhY2ggc3RvY2sgbWFyZ2luYWxseQoKYGBge3J9ClhfbG9uZyA8LSBhcy5kYXRhLmZyYW1lKFgpICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJTdG9jayIsCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJSZXR1cm4iKQoKZ2dwbG90KFhfbG9uZywgYWVzKHggPSBTdG9jaywgeSA9IFJldHVybikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgbGFicyh0aXRsZSA9ICJNb250aGx5IFJldHVybnMgYnkgU3RvY2siLAogICAgICAgeSA9ICJSZXR1cm4gKCUpIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKYGBge3J9CnZhcnMgPC0gYXBwbHkoWCwgMiwgdmFyLCBuYS5ybSA9IFRSVUUpCiMgdmFyaWFuY2UgdGFibGUKdmFyX2RmIDwtIGRhdGEuZnJhbWUoCiAgSW5kdXN0cnkgPSBjb2xuYW1lcyhYKSwKICBWYXJpYW5jZSA9IHZhcnMKKQoKZ2dwbG90KHZhcl9kZiwgYWVzKHggPSBJbmR1c3RyeSwgeSA9IFZhcmlhbmNlKSkgKwogIGdlb21fY29sKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsKICBsYWJzKHRpdGxlID0gIlZhcmlhbmNlIGJ5IFN0b2NrIikKYGBgCldlIHNlZSBtb2RlcmF0ZSBtYXJnaW5hbCB2b2xhdGlsaXRpZXMgZGlmZmVyZW5jZXMgYWNyb3NzIHN0b2Nrcy4KCiMjIENob29zaW5nIHRoZSBudW1iZXIgb2YgZmFjdG9ycwoKIyMjIFNjcmVlIHBsb3QKCldlIGZpcnN0IGxvb2sgYXQgdGhlIGVpZ2VudmFsdWVzIG9mIHRoZSBzYW1wbGUgY29ycmVsYXRpb24gbWF0cml4LiAKCmBgYHtyIHNjcmVlfQpYX3N0ZCA8LSBzY2FsZShYLCBjZW50ZXIgPSBUUlVFLCBzY2FsZSA9IFRSVUUpCnBjYV9mdWxsIDwtIHByY29tcChYX3N0ZCwgY2VudGVyID0gRkFMU0UsIHNjYWxlLiA9IEZBTFNFKQplaWcgPC0gcGNhX2Z1bGwkc2Rldl4yCnNjcmVlX2RmIDwtIHRpYmJsZShjb21wb25lbnQgPSBzZXFfYWxvbmcoZWlnKSwgZWlnZW52YWx1ZSA9IGVpZykKCmdncGxvdChzY3JlZV9kZiwgYWVzKGNvbXBvbmVudCwgZWlnZW52YWx1ZSkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludChzaXplID0gMikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNjcmVlX2RmJGNvbXBvbmVudCkgKwogIGxhYnModGl0bGUgPSAiU2NyZWUgcGxvdCAoY29ycmVsYXRpb24gbWF0cml4KSIsCiAgICAgICB4ID0gIkNvbXBvbmVudCAvIGZhY3RvciBpbmRleCIsCiAgICAgICB5ID0gIkVpZ2VudmFsdWUiKQpgYGAKCldlIGRvIG5vdCBzZWUgYSBjbGVhciBlbGJvdy4KCiMjIyBQYXJhbGxlbCBhbmFseXNpcwoKV2UgY29tcGFyZSB0aGUgb2JzZXJ2ZWQgZWlnZW52YWx1ZXMgd2l0aCB0aG9zZSBmcm9tIHJhbmRvbSBub2lzZSBtYXRyaWNlcyBoYXZpbmcgdGhlIHNhbWUgZGltZW5zaW9ucy4KCmBgYHtyIHBhcmFsbGVsLWFuYWx5c2lzfQpzZXQuc2VlZCgyNDYyMCkKQiA8LSA1MDAKbiA8LSBucm93KFhfc3RkKQpwIDwtIG5jb2woWF9zdGQpCgpyYW5kX2VpZ3MgPC0gcmVwbGljYXRlKEIsIHsKICBYX3JhbmQgPC0gbWF0cml4KHJub3JtKG4gKiBwKSwgbnJvdyA9IG4sIG5jb2wgPSBwKQogIGVpZ2VuKGNvcihYX3JhbmQpLCBzeW1tZXRyaWMgPSBUUlVFLCBvbmx5LnZhbHVlcyA9IFRSVUUpJHZhbHVlcwp9KQoKcGFfbWVhbiA8LSByb3dNZWFucyhyYW5kX2VpZ3MpCnBhX3E5NSA8LSBhcHBseShyYW5kX2VpZ3MsIDEsIHF1YW50aWxlLCBwcm9icyA9IDAuOTUpCgpwYV9kZiA8LSB0aWJibGUoCiAgayA9IHNlcV9sZW4ocCksCiAgb2JzZXJ2ZWQgPSBlaWcsCiAgbnVsbF9tZWFuID0gcGFfbWVhbiwKICBudWxsX3E5NSA9IHBhX3E5NQopICU+JQogIHBpdm90X2xvbmdlcigtaywgbmFtZXNfdG8gPSAic2VyaWVzIiwgdmFsdWVzX3RvID0gInZhbHVlIikKCmdncGxvdChwYV9kZiwgYWVzKGssIHZhbHVlLCBjb2xvciA9IHNlcmllcykpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludChzaXplID0gMS44KSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxX2xlbihwKSkgKwogIGxhYnModGl0bGUgPSAiUGFyYWxsZWwgYW5hbHlzaXMiLCB4ID0gIkluZGV4IiwgeSA9ICJFaWdlbnZhbHVlIiwgY29sb3IgPSBOVUxMKQpgYGAKCkEgc2ltcGxlIHJ1bGUgaXMgdG8ga2VlcCBmYWN0b3JzIHdob3NlIG9ic2VydmVkIGVpZ2VudmFsdWVzIGV4Y2VlZCB0aGUgbnVsbCBiZW5jaG1hcmsuCgpgYGB7ciBwYS1jb3VudH0KcGFfbSA8LSBzdW0oZWlnID4gcGFfcTk1KQpwYV9tCmBgYAoKUEEgc3VnZ2VzdHMgMyBmYWN0b3JzLgoKIyMjIEZpdCBzZXZlcmFsIEZBIG1vZGVscwoKV2UgZml0IHNldmVyYWwgY2FuZGlkYXRlIG1vZGVscyBhbmQgY29tcGFyZSB0aGVpciByZXNpZHVhbCBmaXQuIEhlcmUgd2UgdXNlIGBmYWN0YW5hbCgpYCBvbiB0aGUgZGF0YSBtYXRyaXguCgpgYGB7ciBmaXQtZmEtZ3JpZH0KZml0X2dyaWQgPC0gZnVuY3Rpb24oWCwgbV92YWx1ZXMgPSAxOjUpIHsKICBtYXAobV92YWx1ZXMsIH4gZmFjdGFuYWwoWCwgZmFjdG9ycyA9IC54LCByb3RhdGlvbiA9ICJub25lIiwgc2NvcmVzID0gInJlZ3Jlc3Npb24iKSkgJT4lCiAgICBzZXROYW1lcyhwYXN0ZTAoIm0iLCBtX3ZhbHVlcykpCn0KCmZhX2ZpdHMgPC0gZml0X2dyaWQoWCwgbV92YWx1ZXMgPSAxOjUpCmZhX2ZpdHMkbTIKYGBgCgojIyMgUmVzaWR1YWwgY29ycmVsYXRpb24gZGlhZ25vc3RpY3MKCkFmdGVyIGZpdHRpbmcgRkEsIHdlIGNvbXBhcmUgdGhlIG9ic2VydmVkIGNvdmFyaWFuY2UgKG9yIGNvcnJlbGF0aW9uKSB3aXRoIHRoZSBtb2RlbC1pbXBsaWVkIG9uZToKXFsKXGhhdCBcU2lnbWEgPSBcaGF0IEwgXGhhdCBMXlx0b3AgKyBcaGF0XFBzaS4KXF0KClRvIGFzc2VzcyBtb2RlbCBmaXQsIHdlIGV4YW1pbmUgdGhlIHJlc2lkdWFsIG1hdHJpeDoKXFsKXHRleHR7UmVzaWR1YWx9ID0gUiAtIFxoYXQgUi4KXF0KV2UgY29udmVydCBjb3ZhcmlhbmNlIG1hdHJpeCB0byBjb3JyZWxhdGlvbiBtYXRyaXggdXNpbmcgdGhlIGBjb3YyY29yYCBmdW5jdGlvbi4KV2Ugc3VtbWFyaXplIHJlc2lkdWFsIGZpdCB1c2luZyB0aGUgb2ZmLWRpYWdvbmFsIFJNU1IuCgpgYGB7ciByZXNpZHVhbC1kaWFnbm9zdGljc30KZmFfcmVzaWR1YWxfY29yIDwtIGZ1bmN0aW9uKGZpdCwgUykgewogIEwgPC0gdW5jbGFzcyhmaXQkbG9hZGluZ3MpCiAgUHNpIDwtIGRpYWcoZml0JHVuaXF1ZW5lc3NlcykKICBTaWdtYV9oYXQgPC0gTCAlKiUgdChMKSArIFBzaQoKICBSX29icyA8LSBjb3YyY29yKFMpCiAgUl9oYXQgPC0gY292MmNvcihTaWdtYV9oYXQpCgogIFJfb2JzIC0gUl9oYXQKfQoKcm1zcl9vZmZkaWFnIDwtIGZ1bmN0aW9uKE0pIHsKICBpZHggPC0gcm93KE0pIDwgY29sKE0pCiAgc3FydChtZWFuKE1baWR4XV4yKSkKfQoKZGlhZ190YmwgPC0gaW1hcF9kZnIoZmFfZml0cywgZnVuY3Rpb24oZml0LCBubSkgewogIHJlc2lkIDwtIGZhX3Jlc2lkdWFsX2NvcihmaXQsIGNvcihYX3N0ZCkpCiAgdGliYmxlKAogICAgbW9kZWwgPSBubSwKICAgIG0gPSBmaXQkZmFjdG9ycywKICAgIHJtc3IgPSBybXNyX29mZmRpYWcocmVzaWQpLAogICAgbWF4X2Fic19yZXNpZCA9IG1heChhYnMocmVzaWRbcm93KHJlc2lkKSA8IGNvbChyZXNpZCldKSkKICApCn0pCgpkaWFnX3RibApgYGAKCiMjIENob29zZSBhIHdvcmtpbmcgbW9kZWwKCgpMZXQncyBmaXJzdCB1c2UgMyBmYWN0b3JzIGZvciBGQS4KCiMjIyBJbnNwZWN0IHRoZSBjaG9zZW4gRkEgbW9kZWwKClRoZSBSIG91dHB1dCBhbHNvIHNob3dzIHlvdSB0aGUgcC12YWx1ZSBmcm9tIGxpa2VsaWhvb2QtcmF0aW8gdGVzdCwgdGVzdGluZyB3aGV0aGVyIGhhdmluZyB0aGVzZSBmYWN0b3JzIGFyZSBlbm91Z2ggYXNzdW1pbmcgZGF0YSBjb21pbmcgZnJvbSBhIEdhdXNzaWFuIGZhY3RvciBhbmFseXNpcyBtb2RlbC4KCmBgYHtyIGZpdC1jaG9zZW59Cm1fc3RhciA8LSAzCmZhX3JhdyA8LSBmYWN0YW5hbChYLCBmYWN0b3JzID0gbV9zdGFyLCByb3RhdGlvbiA9ICJub25lIiwgc2NvcmVzID0gInJlZ3Jlc3Npb24iKQpmYV9yb3QgPC0gZmFjdGFuYWwoWCwgZmFjdG9ycyA9IG1fc3Rhciwgcm90YXRpb24gPSAidmFyaW1heCIsIHNjb3JlcyA9ICJyZWdyZXNzaW9uIikKCmZhX3JvdApgYGAKCiMjIyBVbmlxdWVuZXNzZXMgYW5kIGNvbW11bmFsaXRpZXMKCmBgYHtyIHVuaXF1ZW5lc3Nlcy1jb21tdW5hbGl0aWVzfQpmYV9zdW1tYXJ5IDwtIHRpYmJsZSgKICBpbmR1c3RyeSA9IG5hbWVzKGZhX3JvdCR1bmlxdWVuZXNzZXMpLAogIHVuaXF1ZW5lc3MgPSBhcy5udW1lcmljKGZhX3JvdCR1bmlxdWVuZXNzZXMpLAogIGNvbW11bmFsaXR5ID0gMSAtIHVuaXF1ZW5lc3MKKSAlPiUKICBhcnJhbmdlKGRlc2MoY29tbXVuYWxpdHkpKQoKZmFfc3VtbWFyeQpgYGAKCkludGVycHJldGF0aW9uOgoKLSBBICoqaGlnaCBjb21tdW5hbGl0eSoqIG1lYW5zIHRoYXQgdGhlIGluZHVzdHJ5J3MgcmV0dXJuIHZhcmlhdGlvbiBpcyBsYXJnZWx5IGV4cGxhaW5lZCBieSB0aGUgY29tbW9uIGZhY3RvcnMuCi0gQSAqKmhpZ2ggdW5pcXVlbmVzcyoqIG1lYW5zIHRoYXQgdGhlIGluZHVzdHJ5IHJldGFpbnMgYSBsb3Qgb2YgaWRpb3N5bmNyYXRpYyB2YXJpYXRpb24gYWZ0ZXIgYWNjb3VudGluZyBmb3IgY29tbW9uIGZhY3RvcnMuCgpXZSBjYW4gc2VlIHRoYXQgYE1TRlRgIGFuZCBgQUFQTGAgc3RpbGwgaGF2ZSBsYXJnZSB1bmlxdWVuZXNzLCBhbHNvIHRoZSBwLXZhbHVlIGlzIHN0aWxsIHZlcnkgc21hbGwuIFNvIG1heWJlIHdlIGFjdHVhbGx5IG5lZWQgbW9yZSBmYWN0b3JzLCB3aGljaCBpcyBjb25zaXN0ZW50IHdpdGggb3VyIGludHVpdGlvbi4gCgoKYGBge3IgcmVzaWR1YWwtaGVhdG1hcH0KcmVzaWRfbWF0IDwtIGZhX3Jlc2lkdWFsX2NvcihmYV9yb3QsIGNvcihYX3N0ZCkpCnJlc2lkX2RmIDwtIGFzLmRhdGEuZnJhbWUoYXMudGFibGUocmVzaWRfbWF0KSkKCmdncGxvdChyZXNpZF9kZiwgYWVzKFZhcjIsIFZhcjEsIGZpbGwgPSBGcmVxKSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiIzIxNjZBQyIsIG1pZCA9ICJ3aGl0ZSIsIGhpZ2ggPSAiI0IyMTgyQiIsIG1pZHBvaW50ID0gMCkgKwogIGNvb3JkX2VxdWFsKCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh0aXRsZSA9IHBhc3RlKCJSZXNpZHVhbCBjb3JyZWxhdGlvbnM6IEZBIHdpdGgiLCBtX3N0YXIsICJmYWN0b3JzIiksCiAgICAgICB4ID0gTlVMTCwgeSA9IE5VTEwsIGZpbGwgPSAiUmVzaWR1YWwiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKYGBgClRoaXMgcGxvdCBoZWxwcyBkZWNpZGUgd2hldGhlciB0aGUgY2hvc2VuIGZhY3RvciBtb2RlbCBpcyBzdGlsbCBtaXNzaW5nIHN5c3RlbWF0aWMgc3RydWN0dXJlLiBXZSBzZWUgdGhhdCBgTVNGVGAgYW5kIGBBQVBMYCBzdGlsbCBoYXZlIHN0cm9uZyByZXNpZHVhbCBjb3JyZWxhdGlvbiwgdGhhdCBpcyBldmlkZW5jZSB0aGF0IGBtYCBtYXkgc3RpbGwgYmUgdG9vIHNtYWxsIG9yIHRoYXQgdGhlIGZhY3RvciBtb2RlbCBpcyBub3QgY2FwdHVyaW5nIHRoZSBkZXBlbmRlbmNlIHBhdHRlcm4gd2VsbC4KCiMjIFJldmlzZWQgZmFjdG9yIG1vZGVsOiBhZGRpbmcgYSBmb3VydGggZmFjdG9yCgpgYGB7ciBmaXQtbmV3fQptX3N0YXIgPC0gbV9zdGFyICsgMQpmYV9yYXcgPC0gZmFjdGFuYWwoWCwgZmFjdG9ycyA9IG1fc3Rhciwgcm90YXRpb24gPSAibm9uZSIsIHNjb3JlcyA9ICJyZWdyZXNzaW9uIikKZmFfcm90IDwtIGZhY3RhbmFsKFgsIGZhY3RvcnMgPSBtX3N0YXIsIHJvdGF0aW9uID0gInZhcmltYXgiLCBzY29yZXMgPSAicmVncmVzc2lvbiIpCgpmYV9yb3QKYGBgCgpgYGB7ciB1bmlxdWVuZXNzZXMtY29tbXVuYWxpdGllcy1uZXd9CmZhX3N1bW1hcnkgPC0gdGliYmxlKAogIGluZHVzdHJ5ID0gbmFtZXMoZmFfcm90JHVuaXF1ZW5lc3NlcyksCiAgdW5pcXVlbmVzcyA9IGFzLm51bWVyaWMoZmFfcm90JHVuaXF1ZW5lc3NlcyksCiAgY29tbXVuYWxpdHkgPSAxIC0gdW5pcXVlbmVzcwopICU+JQogIGFycmFuZ2UoZGVzYyhjb21tdW5hbGl0eSkpCgpmYV9zdW1tYXJ5CmBgYAoKYGBge3IgcmVzaWR1YWwtaGVhdG1hcC1uZXd9CnJlc2lkX21hdCA8LSBmYV9yZXNpZHVhbF9jb3IoZmFfcm90LCBjb3IoWF9zdGQpKQpyZXNpZF9kZiA8LSBhcy5kYXRhLmZyYW1lKGFzLnRhYmxlKHJlc2lkX21hdCkpCgpnZ3Bsb3QocmVzaWRfZGYsIGFlcyhWYXIyLCBWYXIxLCBmaWxsID0gRnJlcSkpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gIiMyMTY2QUMiLCBtaWQgPSAid2hpdGUiLCBoaWdoID0gIiNCMjE4MkIiLCBtaWRwb2ludCA9IDApICsKICBjb29yZF9lcXVhbCgpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUmVzaWR1YWwgY29ycmVsYXRpb25zOiBGQSB3aXRoIiwgbV9zdGFyLCAiZmFjdG9ycyIpLAogICAgICAgeCA9IE5VTEwsIHkgPSBOVUxMLCBmaWxsID0gIlJlc2lkdWFsIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKVGhlIGZvdXJ0aCBmYWN0b3Igc2VlbXMgdG8gbWFpbmx5IGV4cGxhaW4gYE1TRlRgLCBidXQgdGhlIHRlc3QgZG9lcyBiZWNvbWUgaW5zaWduaWZpY2FudCwgYW5kIHRoZXJlIGFyZSBubyByZW1haW5pbmcgbGFyZ2UgY29ycmVsYXRpb25zIGxlZnQuIFNvIHdlIHNob3VsZCBzdGlsbCBpbmNsdWRlIHRoaXMgZmFjdG9yLgoKCiMjIFJvdGF0aW9uOiB1bnJvdGF0ZWQgdnMgdmFyaW1heCBsb2FkaW5ncwoKYGBge3IgbG9hZGluZy1oZWxwZXJzfQpsb2FkaW5nc190b19kZiA8LSBmdW5jdGlvbihmaXQsIGxhYmVsKSB7CiAgYXMuZGF0YS5mcmFtZSh1bmNsYXNzKGZpdCRsb2FkaW5ncykpICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oInN0b2NrIikgJT4lCiAgICBwaXZvdF9sb25nZXIoLXN0b2NrLCBuYW1lc190byA9ICJmYWN0b3IiLCB2YWx1ZXNfdG8gPSAibG9hZGluZyIpICU+JQogICAgbXV0YXRlKG1ldGhvZCA9IGxhYmVsKQp9Cgpsb2FkX2RmIDwtIGJpbmRfcm93cygKICBsb2FkaW5nc190b19kZihmYV9yYXcsICJVbnJvdGF0ZWQgRkEiKSwKICBsb2FkaW5nc190b19kZihmYV9yb3QsICJWYXJpbWF4IEZBIikKKQoKbG9hZF9kZiRzdG9jayA8LSBmYWN0b3IobG9hZF9kZiRzdG9jaywgbGV2ZWxzID0gcm93bmFtZXMoZmFfcmF3JGxvYWRpbmdzKSkKYGBgCgpgYGB7ciBsb2FkaW5ncy1oZWF0bWFwfQpnZ3Bsb3QobG9hZF9kZiwgYWVzKGZhY3Rvciwgc3RvY2ssIGZpbGwgPSBsb2FkaW5nKSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiIzIxNjZBQyIsIG1pZCA9ICJ3aGl0ZSIsIGhpZ2ggPSAiI0IyMTgyQiIsIG1pZHBvaW50ID0gMCkgKwogIGZhY2V0X3dyYXAofiBtZXRob2QsIG5yb3cgPSAxKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIkZBIGxvYWRpbmdzOiB1bnJvdGF0ZWQgdnMgdmFyaW1heCIsCiAgICAgICB4ID0gTlVMTCwgeSA9IE5VTEwsIGZpbGwgPSAiTG9hZGluZyIpCmBgYApJbiBwcmFjdGljZSwgdGhlIHVucm90YXRlZCBsb2FkaW5ncyBvZnRlbiBsb29rIGRpZmZ1c2U6IG1hbnkgc3RvY2tzIG1heSBsb2FkIG1vZGVyYXRlbHkgb24gbXVsdGlwbGUgZmFjdG9ycy4gQWZ0ZXIgdmFyaW1heCByb3RhdGlvbiwgdGhlIHBhdHRlcm4gaXMgdXN1YWxseSBlYXNpZXIgdG8gbmFycmF0ZSBiZWNhdXNlIHNvbWUgZmFjdG9ycyBiZWNvbWUgbW9yZSBjb25jZW50cmF0ZWQgb24gcGFydGljdWxhciBncm91cHMgb2Ygc3RvY2tzLgoKLSBGYWN0b3IgMTogRW5lcmd5Ci0gRmFjdG9yIDI6IEJhbmsKLSBGYWN0b3IgMzogVXRpbGl0eQotIEZhY3RvciA0OiBIaWdoIFRlY2gKCiMjIEZhY3RvciBzY29yZXMKClRoZSByZWdyZXNzaW9uIHNjb3JlcyBwcm92aWRlIGVzdGltYXRlZCBsYXRlbnQtZmFjdG9yIHJlYWxpemF0aW9ucyBvdmVyIHRpbWUuCgpgYGB7ciBmYWN0b3Itc2NvcmVzfQpGaGF0IDwtIGZhX3JvdCRzY29yZXMgJT4lCiAgICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgICAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJkYXRlIikgJT4lCiAgICAgIG11dGF0ZShkYXRlID0gYXMuRGF0ZShkYXRlKSkKCgpGaGF0X2xvbmcgPC0gRmhhdCAlPiUKICBwaXZvdF9sb25nZXIoLWRhdGUsIG5hbWVzX3RvID0gImZhY3RvciIsIHZhbHVlc190byA9ICJzY29yZSIpCgpnZ3Bsb3QoRmhhdF9sb25nLCBhZXMoZGF0ZSwgc2NvcmUsIGNvbG9yID0gZmFjdG9yKSkgKwogIGdlb21fbGluZShsaW5ld2lkdGggPSAwLjUpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArCiAgbGFicyh0aXRsZSA9ICJFc3RpbWF0ZWQgZmFjdG9yIHNjb3JlcyBvdmVyIHRpbWUiLAogICAgICAgeCA9IE5VTEwsIHkgPSAiU2NvcmUiLCBjb2xvciA9ICJGYWN0b3IiKQpgYGAKCiMjIENvbXBhcmlzb24gd2l0aCBQQ0EKCldlIG5vdyBjb21wYXJlIEZBIHdpdGggUENBIG9uIHRoZSBzdGFuZGFyZGl6ZWQgbWF0cml4LgoKYGBge3IgcGNhLWZpdH0KcGNhIDwtIHByY29tcChYX3N0ZCwgY2VudGVyID0gRkFMU0UsIHNjYWxlLiA9IEZBTFNFKQpyb3VuZChwY2Ekcm90YXRpb25bLCAxOm1fc3Rhcl0sIDMpCmBgYAoKIyMjIFBDQSB2cyBGQSBsb2FkaW5ncyBoZWF0bWFwCgpgYGB7ciBwY2EtdnMtZmEtbG9hZGluZ3N9CnBjYV9sb2FkX2RmIDwtIGFzLmRhdGEuZnJhbWUocGNhJHJvdGF0aW9uWywgMTptX3N0YXIsIGRyb3AgPSBGQUxTRV0pICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJzdG9jayIpICU+JQogIHBpdm90X2xvbmdlcigtc3RvY2ssIG5hbWVzX3RvID0gImZhY3RvciIsIHZhbHVlc190byA9ICJsb2FkaW5nIikgJT4lCiAgbXV0YXRlKG1ldGhvZCA9ICJQQ0EiKQoKZmFfcm90X2RmIDwtIGxvYWRpbmdzX3RvX2RmKGZhX3JvdCwgIkZBICh2YXJpbWF4KSIpCnBsb3RfbG9hZF9jb21wYXJlIDwtIGJpbmRfcm93cyhwY2FfbG9hZF9kZiwgZmFfcm90X2RmKQpwbG90X2xvYWRfY29tcGFyZSRzdG9jayA8LSBmYWN0b3IocGxvdF9sb2FkX2NvbXBhcmUkc3RvY2ssIGxldmVscyA9IHJvd25hbWVzKGZhX3JvdCRsb2FkaW5ncykpCgpnZ3Bsb3QocGxvdF9sb2FkX2NvbXBhcmUsIGFlcyhmYWN0b3IsIHN0b2NrLCBmaWxsID0gbG9hZGluZykpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gIiMyMTY2QUMiLCBtaWQgPSAid2hpdGUiLCBoaWdoID0gIiNCMjE4MkIiLCBtaWRwb2ludCA9IDApICsKICBmYWNldF93cmFwKH4gbWV0aG9kLCBucm93ID0gMSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh0aXRsZSA9ICJQQ0EgbG9hZGluZ3MgdnMgcm90YXRlZCBGQSBsb2FkaW5ncyIsCiAgICAgICB4ID0gTlVMTCwgeSA9IE5VTEwsIGZpbGwgPSAiTG9hZGluZyIpCmBgYApQQ0EgbG9hZGluZ3MgZGVzY3JpYmUgb3J0aG9nb25hbCB2YXJpYW5jZS1tYXhpbWl6aW5nIGRpcmVjdGlvbnMsIHdoaWxlIEZBIGxvYWRpbmdzIGRlc2NyaWJlIGhvdyBvYnNlcnZlZCB2YXJpYWJsZXMgZGVwZW5kIG9uIGxhdGVudCBjb21tb24gZmFjdG9ycyBwbHVzIHVuaXF1ZW5lc3Nlcy4gQWZ0ZXIgcm90YXRpb24sIEZBIGdpdmVzIGNsZWFyZXIgY2x1c3RlcmluZyBzdHJ1Y3R1cmUgb2YgdGhlIHZhcmlhYmxlcy4gSW4gY29udHJhc3QsIFBDQSBsZWFybnMgdGhlIHNoYXJlZCBtYXJrZXQgZWZmZWN0IGFzIHRoZSBmaXJzdCBQQy4KCiMjIEtleSB0YWtlYXdheXMKCi0gRkEgaXMgYSBtb2RlbCBmb3IgdGhlIGNvdmFyaWFuY2UvY29ycmVsYXRpb24gc3RydWN0dXJlLCBub3QganVzdCBhIGRhdGEtcmVkdWN0aW9uIGRldmljZS4KLSBUaGUgbnVtYmVyIG9mIGZhY3RvcnMgc2hvdWxkIGJlIGNob3NlbiB1c2luZyBib3RoICoqZWlnZW52YWx1ZSBkaWFnbm9zdGljcyoqIGFuZCAqKnBvc3QtZml0IHJlc2lkdWFsIGNoZWNrcyoqLgotIFJvdGF0aW9uIGNhbiBiZSBoZWxwZnVsIGZvciBpbnRlcnByZXRhdGlvbi4KLSBQQ0EgYW5kIEZBIG1heSBsb29rIHNpbWlsYXIgaW4gcHJhY3RpY2UsIGJ1dCB0aGVpciBvYmplY3RpdmVzIGFyZSBkaWZmZXJlbnQuCg==