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 1: Energy
- Factor 2: Bank
- Factor 3: Utility
- Factor 4: High Tech
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
- FA is a model for the covariance/correlation structure, not just a
data-reduction device.
- The number of factors should be chosen using both eigenvalue
diagnostics and post-fit residual checks.
- Rotation can be helpful for interpretation.
- PCA and FA may look similar in practice, but their objectives are
different.
LS0tCnRpdGxlOiAiTGVjdHVyZSA0IEZhY3RvciBBbmFseXNpcyBMaXZlIERlbW86IDEwIFN0b2NrcyIKYXV0aG9yOiAiU1RBVCAyNDYyMCA9IFNUQVQgMzI5MDAsIEZJTk0gMzQ3MDAiCmRhdGU6ICJTcHJpbmcgMjAyNiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBmaWcud2lkdGggPSA3LjUsCiAgZmlnLmhlaWdodCA9IDQuNSwKICBtZXNzYWdlID0gRkFMU0UsCiAgd2FybmluZyA9IEZBTFNFCikKb3B0aW9ucyhkaWdpdHMgPSA0KQpgYGAKCiMjIEdvYWwKCkluIHRoaXMgbm90ZWJvb2ssIHdlIHdhbGsgdGhyb3VnaCBhIGZ1bGwgZmFjdG9yLWFuYWx5c2lzIHdvcmtmbG93IHVzaW5nIHRoZSAxMCBzdG9ja3MsIGRvd25sb2FkZWQgdGhyb3VnaCB0aGUgYHF1YW50bW9kYCBwYWNrYWdlLgogCiMjIFBhY2thZ2VzCgpgYGB7ciBwYWNrYWdlc30KbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeShwdXJycikKCmxpYnJhcnkocXVhbnRtb2QpCmBgYAoKIyMgRG93bmxvYWQgdGhlIDEwIHN0b2NrcyBkYXRhIGFuZCBkYXRhIHByZXByb2Nlc3NpbmcKCgpXZSB3aWxsIGdldCBtb250aGx5IHJldHVybnMgKHBlcmNlbnQgXCUpIGZvciAxMCBzdG9ja3MgKEpQTW9yZ2FuLCBCT0EsIFdlbGxzRmFyZ28sIEV4eG9uLCBDaGV2cm9uLCBDb25vY29QaGlsbGlwcywgQXBwbGUsIE1pY3Jvc29mdCwgTmV4dEVyYSBFbmVyZ3ksIER1a2UgRW5lcmd5KSBmcm9tIDIwMTAtMjAyNi4gRWFjaCBjb2x1bW4gaXMgc3RvY2ssIGFuZCBlYWNoIHJvdyBpcyBvbmUgbW9udGguIAoKCmBgYHtyIGdldC1kYXRhfQp0aWNrZXJzIDwtIGMoCiAgIyBCYW5rcwogICJKUE0iLCAiQkFDIiwgIldGQyIsCiAgIyBFbmVyZ3kKICAiWE9NIiwgIkNWWCIsICJDT1AiLAogICMgVGVjaAogICJBQVBMIiwgIk1TRlQiLAogICMgVXRpbGl0aWVzCiAgIk5FRSIsICJEVUsiCikKCgpnZXRTeW1ib2xzKHRpY2tlcnMsIGZyb20gPSAiMjAxMC0wMS0wMSIsIGF1dG8uYXNzaWduID0gVFJVRSkKCnByaWNlc19saXN0IDwtIGxhcHBseSh0aWNrZXJzLCBmdW5jdGlvbih0aykgewogIEFkKGdldCh0aykpCn0pCgpwcmljZXMgPC0gZG8uY2FsbChtZXJnZSwgcHJpY2VzX2xpc3QpCmNvbG5hbWVzKHByaWNlcykgPC0gdGlja2VycwoKcmV0dXJuc19saXN0IDwtIGxhcHBseShjb2xuYW1lcyhwcmljZXMpLCBmdW5jdGlvbih0aykgewogIG1vbnRobHlSZXR1cm4ocHJpY2VzWywgdGtdLCB0eXBlID0gImxvZyIpCn0pCgpyZXR1cm5zIDwtIGRvLmNhbGwobWVyZ2UsIHJldHVybnNfbGlzdCkKY29sbmFtZXMocmV0dXJucykgPC0gY29sbmFtZXMocHJpY2VzKQoKcmV0dXJucyA8LSBuYS5vbWl0KHJldHVybnMpCgpyZXR1cm5zIDwtIHJldHVybnMgKiAxMDAKYGBgCgojIyMgVGltZS1zZXJpZXMgdmlldwoKYGBge3IgdGltZS1zZXJpZXMtbG9uZ30KcGxvdF9kZiA8LSByZXR1cm5zICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICByb3duYW1lc190b19jb2x1bW4odmFyID0gImRhdGUiKSAlPiUKICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoZGF0ZSkpICU+JQogIHBpdm90X2xvbmdlcigKICAgIGNvbHMgPSAtZGF0ZSwKICAgIG5hbWVzX3RvID0gInN0b2NrIiwKICAgIHZhbHVlc190byA9ICJyZXQiCiAgKQoKZ2dwbG90KHBsb3RfZGYsIGFlcyh4ID0gZGF0ZSwgeSA9IHJldCwgY29sb3IgPSBzdG9jaykpICsKICBnZW9tX2xpbmUoYWxwaGEgPSAwLjcsIGxpbmV3aWR0aCA9IDAuNSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiTW9udGhseSBzdG9jayByZXR1cm5zIiwKICAgIHggPSBOVUxMLAogICAgeSA9ICJNb250aGx5IHJldHVybiAoJSkiLAogICAgY29sb3IgPSAiU3RvY2siCiAgKQpgYGAKCgojIyMgQ29ycmVsYXRpb24gbWF0cml4CgpGYWN0b3IgYW5hbHlzaXMgaXMgYWJvdXQgZXhwbGFpbmluZyBjb21tb24gZGVwZW5kZW5jZSwgc28gdGhlIGNvcnJlbGF0aW9uIG1hdHJpeCBpcyBhIG5hdHVyYWwgc3RhcnRpbmcgcG9pbnQuCgpgYGB7ciBvYnNlcnZlZC1jb3JyZWxhdGlvbnN9ClggPC0gcmV0dXJucwoKY29yX21hdCA8LSBjb3IoWCkKY29yX2RmIDwtIGFzLmRhdGEuZnJhbWUoYXMudGFibGUoY29yX21hdCkpCgpwX2NvciA8LSBnZ3Bsb3QoY29yX2RmLCBhZXMoVmFyMiwgVmFyMSwgZmlsbCA9IEZyZXEpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICIjMjE2NkFDIiwgbWlkID0gIndoaXRlIiwgaGlnaCA9ICIjQjIxODJCIiwgbWlkcG9pbnQgPSAwKSArCiAgY29vcmRfZXF1YWwoKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIkluZHVzdHJ5IHJldHVybiBjb3JyZWxhdGlvbnMiLCB4ID0gTlVMTCwgeSA9IE5VTEwsIGZpbGwgPSAiQ29yciIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQoKcF9jb3IKYGBgCldlIHNlZSBoZXJlIHRoYXQgdGhlIGNvcnJlbGF0aW9ucyBhcmUgbW9zdGx5IHBvc2l0aXZlLCBhbmQgdGhlcmUgYXJlIGNsdXN0ZXJpbmcgc3RydWN0dXJlIGFzIGV4cGVjdGVkLCBpbmRpY2F0aW5nIHRoZSBwcmVzZW5jZSBvZiBhIGZldyBsYXRlbnQgZmFjdG9ycy4KCgojIyMgVmlzdWFsaXplIGVhY2ggc3RvY2sgbWFyZ2luYWxseQoKYGBge3J9ClhfbG9uZyA8LSBhcy5kYXRhLmZyYW1lKFgpICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJTdG9jayIsCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJSZXR1cm4iKQoKZ2dwbG90KFhfbG9uZywgYWVzKHggPSBTdG9jaywgeSA9IFJldHVybikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgbGFicyh0aXRsZSA9ICJNb250aGx5IFJldHVybnMgYnkgU3RvY2siLAogICAgICAgeSA9ICJSZXR1cm4gKCUpIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKYGBge3J9CnZhcnMgPC0gYXBwbHkoWCwgMiwgdmFyLCBuYS5ybSA9IFRSVUUpCiMgdmFyaWFuY2UgdGFibGUKdmFyX2RmIDwtIGRhdGEuZnJhbWUoCiAgSW5kdXN0cnkgPSBjb2xuYW1lcyhYKSwKICBWYXJpYW5jZSA9IHZhcnMKKQoKZ2dwbG90KHZhcl9kZiwgYWVzKHggPSBJbmR1c3RyeSwgeSA9IFZhcmlhbmNlKSkgKwogIGdlb21fY29sKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsKICBsYWJzKHRpdGxlID0gIlZhcmlhbmNlIGJ5IFN0b2NrIikKYGBgCldlIHNlZSBtb2RlcmF0ZSBtYXJnaW5hbCB2b2xhdGlsaXRpZXMgZGlmZmVyZW5jZXMgYWNyb3NzIHN0b2Nrcy4KCiMjIENob29zaW5nIHRoZSBudW1iZXIgb2YgZmFjdG9ycwoKIyMjIFNjcmVlIHBsb3QKCldlIGZpcnN0IGxvb2sgYXQgdGhlIGVpZ2VudmFsdWVzIG9mIHRoZSBzYW1wbGUgY29ycmVsYXRpb24gbWF0cml4LiAKCmBgYHtyIHNjcmVlfQpYX3N0ZCA8LSBzY2FsZShYLCBjZW50ZXIgPSBUUlVFLCBzY2FsZSA9IFRSVUUpCnBjYV9mdWxsIDwtIHByY29tcChYX3N0ZCwgY2VudGVyID0gRkFMU0UsIHNjYWxlLiA9IEZBTFNFKQplaWcgPC0gcGNhX2Z1bGwkc2Rldl4yCnNjcmVlX2RmIDwtIHRpYmJsZShjb21wb25lbnQgPSBzZXFfYWxvbmcoZWlnKSwgZWlnZW52YWx1ZSA9IGVpZykKCmdncGxvdChzY3JlZV9kZiwgYWVzKGNvbXBvbmVudCwgZWlnZW52YWx1ZSkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludChzaXplID0gMikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNjcmVlX2RmJGNvbXBvbmVudCkgKwogIGxhYnModGl0bGUgPSAiU2NyZWUgcGxvdCAoY29ycmVsYXRpb24gbWF0cml4KSIsCiAgICAgICB4ID0gIkNvbXBvbmVudCAvIGZhY3RvciBpbmRleCIsCiAgICAgICB5ID0gIkVpZ2VudmFsdWUiKQpgYGAKCldlIGRvIG5vdCBzZWUgYSBjbGVhciBlbGJvdy4KCiMjIyBQYXJhbGxlbCBhbmFseXNpcwoKV2UgY29tcGFyZSB0aGUgb2JzZXJ2ZWQgZWlnZW52YWx1ZXMgd2l0aCB0aG9zZSBmcm9tIHJhbmRvbSBub2lzZSBtYXRyaWNlcyBoYXZpbmcgdGhlIHNhbWUgZGltZW5zaW9ucy4KCmBgYHtyIHBhcmFsbGVsLWFuYWx5c2lzfQpzZXQuc2VlZCgyNDYyMCkKQiA8LSA1MDAKbiA8LSBucm93KFhfc3RkKQpwIDwtIG5jb2woWF9zdGQpCgpyYW5kX2VpZ3MgPC0gcmVwbGljYXRlKEIsIHsKICBYX3JhbmQgPC0gbWF0cml4KHJub3JtKG4gKiBwKSwgbnJvdyA9IG4sIG5jb2wgPSBwKQogIGVpZ2VuKGNvcihYX3JhbmQpLCBzeW1tZXRyaWMgPSBUUlVFLCBvbmx5LnZhbHVlcyA9IFRSVUUpJHZhbHVlcwp9KQoKcGFfbWVhbiA8LSByb3dNZWFucyhyYW5kX2VpZ3MpCnBhX3E5NSA8LSBhcHBseShyYW5kX2VpZ3MsIDEsIHF1YW50aWxlLCBwcm9icyA9IDAuOTUpCgpwYV9kZiA8LSB0aWJibGUoCiAgayA9IHNlcV9sZW4ocCksCiAgb2JzZXJ2ZWQgPSBlaWcsCiAgbnVsbF9tZWFuID0gcGFfbWVhbiwKICBudWxsX3E5NSA9IHBhX3E5NQopICU+JQogIHBpdm90X2xvbmdlcigtaywgbmFtZXNfdG8gPSAic2VyaWVzIiwgdmFsdWVzX3RvID0gInZhbHVlIikKCmdncGxvdChwYV9kZiwgYWVzKGssIHZhbHVlLCBjb2xvciA9IHNlcmllcykpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludChzaXplID0gMS44KSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxX2xlbihwKSkgKwogIGxhYnModGl0bGUgPSAiUGFyYWxsZWwgYW5hbHlzaXMiLCB4ID0gIkluZGV4IiwgeSA9ICJFaWdlbnZhbHVlIiwgY29sb3IgPSBOVUxMKQpgYGAKCkEgc2ltcGxlIHJ1bGUgaXMgdG8ga2VlcCBmYWN0b3JzIHdob3NlIG9ic2VydmVkIGVpZ2VudmFsdWVzIGV4Y2VlZCB0aGUgbnVsbCBiZW5jaG1hcmsuCgpgYGB7ciBwYS1jb3VudH0KcGFfbSA8LSBzdW0oZWlnID4gcGFfcTk1KQpwYV9tCmBgYAoKUEEgc3VnZ2VzdHMgMyBmYWN0b3JzLgoKIyMjIEZpdCBzZXZlcmFsIEZBIG1vZGVscwoKV2UgZml0IHNldmVyYWwgY2FuZGlkYXRlIG1vZGVscyBhbmQgY29tcGFyZSB0aGVpciByZXNpZHVhbCBmaXQuIEhlcmUgd2UgdXNlIGBmYWN0YW5hbCgpYCBvbiB0aGUgZGF0YSBtYXRyaXguCgpgYGB7ciBmaXQtZmEtZ3JpZH0KZml0X2dyaWQgPC0gZnVuY3Rpb24oWCwgbV92YWx1ZXMgPSAxOjUpIHsKICBtYXAobV92YWx1ZXMsIH4gZmFjdGFuYWwoWCwgZmFjdG9ycyA9IC54LCByb3RhdGlvbiA9ICJub25lIiwgc2NvcmVzID0gInJlZ3Jlc3Npb24iKSkgJT4lCiAgICBzZXROYW1lcyhwYXN0ZTAoIm0iLCBtX3ZhbHVlcykpCn0KCmZhX2ZpdHMgPC0gZml0X2dyaWQoWCwgbV92YWx1ZXMgPSAxOjUpCmZhX2ZpdHMkbTIKYGBgCgojIyMgUmVzaWR1YWwgY29ycmVsYXRpb24gZGlhZ25vc3RpY3MKCkFmdGVyIGZpdHRpbmcgRkEsIHdlIGNvbXBhcmUgdGhlIG9ic2VydmVkIGNvdmFyaWFuY2UgKG9yIGNvcnJlbGF0aW9uKSB3aXRoIHRoZSBtb2RlbC1pbXBsaWVkIG9uZToKXFsKXGhhdCBcU2lnbWEgPSBcaGF0IEwgXGhhdCBMXlx0b3AgKyBcaGF0XFBzaS4KXF0KClRvIGFzc2VzcyBtb2RlbCBmaXQsIHdlIGV4YW1pbmUgdGhlIHJlc2lkdWFsIG1hdHJpeDoKXFsKXHRleHR7UmVzaWR1YWx9ID0gUiAtIFxoYXQgUi4KXF0KV2UgY29udmVydCBjb3ZhcmlhbmNlIG1hdHJpeCB0byBjb3JyZWxhdGlvbiBtYXRyaXggdXNpbmcgdGhlIGBjb3YyY29yYCBmdW5jdGlvbi4KV2Ugc3VtbWFyaXplIHJlc2lkdWFsIGZpdCB1c2luZyB0aGUgb2ZmLWRpYWdvbmFsIFJNU1IuCgpgYGB7ciByZXNpZHVhbC1kaWFnbm9zdGljc30KZmFfcmVzaWR1YWxfY29yIDwtIGZ1bmN0aW9uKGZpdCwgUykgewogIEwgPC0gdW5jbGFzcyhmaXQkbG9hZGluZ3MpCiAgUHNpIDwtIGRpYWcoZml0JHVuaXF1ZW5lc3NlcykKICBTaWdtYV9oYXQgPC0gTCAlKiUgdChMKSArIFBzaQoKICBSX29icyA8LSBjb3YyY29yKFMpCiAgUl9oYXQgPC0gY292MmNvcihTaWdtYV9oYXQpCgogIFJfb2JzIC0gUl9oYXQKfQoKcm1zcl9vZmZkaWFnIDwtIGZ1bmN0aW9uKE0pIHsKICBpZHggPC0gcm93KE0pIDwgY29sKE0pCiAgc3FydChtZWFuKE1baWR4XV4yKSkKfQoKZGlhZ190YmwgPC0gaW1hcF9kZnIoZmFfZml0cywgZnVuY3Rpb24oZml0LCBubSkgewogIHJlc2lkIDwtIGZhX3Jlc2lkdWFsX2NvcihmaXQsIGNvcihYX3N0ZCkpCiAgdGliYmxlKAogICAgbW9kZWwgPSBubSwKICAgIG0gPSBmaXQkZmFjdG9ycywKICAgIHJtc3IgPSBybXNyX29mZmRpYWcocmVzaWQpLAogICAgbWF4X2Fic19yZXNpZCA9IG1heChhYnMocmVzaWRbcm93KHJlc2lkKSA8IGNvbChyZXNpZCldKSkKICApCn0pCgpkaWFnX3RibApgYGAKCiMjIENob29zZSBhIHdvcmtpbmcgbW9kZWwKCgpMZXQncyBmaXJzdCB1c2UgMyBmYWN0b3JzIGZvciBGQS4KCiMjIyBJbnNwZWN0IHRoZSBjaG9zZW4gRkEgbW9kZWwKClRoZSBSIG91dHB1dCBhbHNvIHNob3dzIHlvdSB0aGUgcC12YWx1ZSBmcm9tIGxpa2VsaWhvb2QtcmF0aW8gdGVzdCwgdGVzdGluZyB3aGV0aGVyIGhhdmluZyB0aGVzZSBmYWN0b3JzIGFyZSBlbm91Z2ggYXNzdW1pbmcgZGF0YSBjb21pbmcgZnJvbSBhIEdhdXNzaWFuIGZhY3RvciBhbmFseXNpcyBtb2RlbC4KCmBgYHtyIGZpdC1jaG9zZW59Cm1fc3RhciA8LSAzCmZhX3JhdyA8LSBmYWN0YW5hbChYLCBmYWN0b3JzID0gbV9zdGFyLCByb3RhdGlvbiA9ICJub25lIiwgc2NvcmVzID0gInJlZ3Jlc3Npb24iKQpmYV9yb3QgPC0gZmFjdGFuYWwoWCwgZmFjdG9ycyA9IG1fc3Rhciwgcm90YXRpb24gPSAidmFyaW1heCIsIHNjb3JlcyA9ICJyZWdyZXNzaW9uIikKCmZhX3JvdApgYGAKCiMjIyBVbmlxdWVuZXNzZXMgYW5kIGNvbW11bmFsaXRpZXMKCmBgYHtyIHVuaXF1ZW5lc3Nlcy1jb21tdW5hbGl0aWVzfQpmYV9zdW1tYXJ5IDwtIHRpYmJsZSgKICBpbmR1c3RyeSA9IG5hbWVzKGZhX3JvdCR1bmlxdWVuZXNzZXMpLAogIHVuaXF1ZW5lc3MgPSBhcy5udW1lcmljKGZhX3JvdCR1bmlxdWVuZXNzZXMpLAogIGNvbW11bmFsaXR5ID0gMSAtIHVuaXF1ZW5lc3MKKSAlPiUKICBhcnJhbmdlKGRlc2MoY29tbXVuYWxpdHkpKQoKZmFfc3VtbWFyeQpgYGAKCkludGVycHJldGF0aW9uOgoKLSBBICoqaGlnaCBjb21tdW5hbGl0eSoqIG1lYW5zIHRoYXQgdGhlIGluZHVzdHJ5J3MgcmV0dXJuIHZhcmlhdGlvbiBpcyBsYXJnZWx5IGV4cGxhaW5lZCBieSB0aGUgY29tbW9uIGZhY3RvcnMuCi0gQSAqKmhpZ2ggdW5pcXVlbmVzcyoqIG1lYW5zIHRoYXQgdGhlIGluZHVzdHJ5IHJldGFpbnMgYSBsb3Qgb2YgaWRpb3N5bmNyYXRpYyB2YXJpYXRpb24gYWZ0ZXIgYWNjb3VudGluZyBmb3IgY29tbW9uIGZhY3RvcnMuCgpXZSBjYW4gc2VlIHRoYXQgYE1TRlRgIGFuZCBgQUFQTGAgc3RpbGwgaGF2ZSBsYXJnZSB1bmlxdWVuZXNzLCBhbHNvIHRoZSBwLXZhbHVlIGlzIHN0aWxsIHZlcnkgc21hbGwuIFNvIG1heWJlIHdlIGFjdHVhbGx5IG5lZWQgbW9yZSBmYWN0b3JzLCB3aGljaCBpcyBjb25zaXN0ZW50IHdpdGggb3VyIGludHVpdGlvbi4gCgoKYGBge3IgcmVzaWR1YWwtaGVhdG1hcH0KcmVzaWRfbWF0IDwtIGZhX3Jlc2lkdWFsX2NvcihmYV9yb3QsIGNvcihYX3N0ZCkpCnJlc2lkX2RmIDwtIGFzLmRhdGEuZnJhbWUoYXMudGFibGUocmVzaWRfbWF0KSkKCmdncGxvdChyZXNpZF9kZiwgYWVzKFZhcjIsIFZhcjEsIGZpbGwgPSBGcmVxKSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiIzIxNjZBQyIsIG1pZCA9ICJ3aGl0ZSIsIGhpZ2ggPSAiI0IyMTgyQiIsIG1pZHBvaW50ID0gMCkgKwogIGNvb3JkX2VxdWFsKCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh0aXRsZSA9IHBhc3RlKCJSZXNpZHVhbCBjb3JyZWxhdGlvbnM6IEZBIHdpdGgiLCBtX3N0YXIsICJmYWN0b3JzIiksCiAgICAgICB4ID0gTlVMTCwgeSA9IE5VTEwsIGZpbGwgPSAiUmVzaWR1YWwiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKYGBgClRoaXMgcGxvdCBoZWxwcyBkZWNpZGUgd2hldGhlciB0aGUgY2hvc2VuIGZhY3RvciBtb2RlbCBpcyBzdGlsbCBtaXNzaW5nIHN5c3RlbWF0aWMgc3RydWN0dXJlLiBXZSBzZWUgdGhhdCBgTVNGVGAgYW5kIGBBQVBMYCBzdGlsbCBoYXZlIHN0cm9uZyByZXNpZHVhbCBjb3JyZWxhdGlvbiwgdGhhdCBpcyBldmlkZW5jZSB0aGF0IGBtYCBtYXkgc3RpbGwgYmUgdG9vIHNtYWxsIG9yIHRoYXQgdGhlIGZhY3RvciBtb2RlbCBpcyBub3QgY2FwdHVyaW5nIHRoZSBkZXBlbmRlbmNlIHBhdHRlcm4gd2VsbC4KCiMjIFJldmlzZWQgZmFjdG9yIG1vZGVsOiBhZGRpbmcgYSBmb3VydGggZmFjdG9yCgpgYGB7ciBmaXQtbmV3fQptX3N0YXIgPC0gbV9zdGFyICsgMQpmYV9yYXcgPC0gZmFjdGFuYWwoWCwgZmFjdG9ycyA9IG1fc3Rhciwgcm90YXRpb24gPSAibm9uZSIsIHNjb3JlcyA9ICJyZWdyZXNzaW9uIikKZmFfcm90IDwtIGZhY3RhbmFsKFgsIGZhY3RvcnMgPSBtX3N0YXIsIHJvdGF0aW9uID0gInZhcmltYXgiLCBzY29yZXMgPSAicmVncmVzc2lvbiIpCgpmYV9yb3QKYGBgCgpgYGB7ciB1bmlxdWVuZXNzZXMtY29tbXVuYWxpdGllcy1uZXd9CmZhX3N1bW1hcnkgPC0gdGliYmxlKAogIGluZHVzdHJ5ID0gbmFtZXMoZmFfcm90JHVuaXF1ZW5lc3NlcyksCiAgdW5pcXVlbmVzcyA9IGFzLm51bWVyaWMoZmFfcm90JHVuaXF1ZW5lc3NlcyksCiAgY29tbXVuYWxpdHkgPSAxIC0gdW5pcXVlbmVzcwopICU+JQogIGFycmFuZ2UoZGVzYyhjb21tdW5hbGl0eSkpCgpmYV9zdW1tYXJ5CmBgYAoKYGBge3IgcmVzaWR1YWwtaGVhdG1hcC1uZXd9CnJlc2lkX21hdCA8LSBmYV9yZXNpZHVhbF9jb3IoZmFfcm90LCBjb3IoWF9zdGQpKQpyZXNpZF9kZiA8LSBhcy5kYXRhLmZyYW1lKGFzLnRhYmxlKHJlc2lkX21hdCkpCgpnZ3Bsb3QocmVzaWRfZGYsIGFlcyhWYXIyLCBWYXIxLCBmaWxsID0gRnJlcSkpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gIiMyMTY2QUMiLCBtaWQgPSAid2hpdGUiLCBoaWdoID0gIiNCMjE4MkIiLCBtaWRwb2ludCA9IDApICsKICBjb29yZF9lcXVhbCgpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUmVzaWR1YWwgY29ycmVsYXRpb25zOiBGQSB3aXRoIiwgbV9zdGFyLCAiZmFjdG9ycyIpLAogICAgICAgeCA9IE5VTEwsIHkgPSBOVUxMLCBmaWxsID0gIlJlc2lkdWFsIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKVGhlIGZvdXJ0aCBmYWN0b3Igc2VlbXMgdG8gbWFpbmx5IGV4cGxhaW4gYE1TRlRgLCBidXQgdGhlIHRlc3QgZG9lcyBiZWNvbWUgaW5zaWduaWZpY2FudCwgYW5kIHRoZXJlIGFyZSBubyByZW1haW5pbmcgbGFyZ2UgY29ycmVsYXRpb25zIGxlZnQuIFNvIHdlIHNob3VsZCBzdGlsbCBpbmNsdWRlIHRoaXMgZmFjdG9yLgoKCiMjIFJvdGF0aW9uOiB1bnJvdGF0ZWQgdnMgdmFyaW1heCBsb2FkaW5ncwoKYGBge3IgbG9hZGluZy1oZWxwZXJzfQpsb2FkaW5nc190b19kZiA8LSBmdW5jdGlvbihmaXQsIGxhYmVsKSB7CiAgYXMuZGF0YS5mcmFtZSh1bmNsYXNzKGZpdCRsb2FkaW5ncykpICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oInN0b2NrIikgJT4lCiAgICBwaXZvdF9sb25nZXIoLXN0b2NrLCBuYW1lc190byA9ICJmYWN0b3IiLCB2YWx1ZXNfdG8gPSAibG9hZGluZyIpICU+JQogICAgbXV0YXRlKG1ldGhvZCA9IGxhYmVsKQp9Cgpsb2FkX2RmIDwtIGJpbmRfcm93cygKICBsb2FkaW5nc190b19kZihmYV9yYXcsICJVbnJvdGF0ZWQgRkEiKSwKICBsb2FkaW5nc190b19kZihmYV9yb3QsICJWYXJpbWF4IEZBIikKKQoKbG9hZF9kZiRzdG9jayA8LSBmYWN0b3IobG9hZF9kZiRzdG9jaywgbGV2ZWxzID0gcm93bmFtZXMoZmFfcmF3JGxvYWRpbmdzKSkKYGBgCgpgYGB7ciBsb2FkaW5ncy1oZWF0bWFwfQpnZ3Bsb3QobG9hZF9kZiwgYWVzKGZhY3Rvciwgc3RvY2ssIGZpbGwgPSBsb2FkaW5nKSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiIzIxNjZBQyIsIG1pZCA9ICJ3aGl0ZSIsIGhpZ2ggPSAiI0IyMTgyQiIsIG1pZHBvaW50ID0gMCkgKwogIGZhY2V0X3dyYXAofiBtZXRob2QsIG5yb3cgPSAxKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIkZBIGxvYWRpbmdzOiB1bnJvdGF0ZWQgdnMgdmFyaW1heCIsCiAgICAgICB4ID0gTlVMTCwgeSA9IE5VTEwsIGZpbGwgPSAiTG9hZGluZyIpCmBgYApJbiBwcmFjdGljZSwgdGhlIHVucm90YXRlZCBsb2FkaW5ncyBvZnRlbiBsb29rIGRpZmZ1c2U6IG1hbnkgc3RvY2tzIG1heSBsb2FkIG1vZGVyYXRlbHkgb24gbXVsdGlwbGUgZmFjdG9ycy4gQWZ0ZXIgdmFyaW1heCByb3RhdGlvbiwgdGhlIHBhdHRlcm4gaXMgdXN1YWxseSBlYXNpZXIgdG8gbmFycmF0ZSBiZWNhdXNlIHNvbWUgZmFjdG9ycyBiZWNvbWUgbW9yZSBjb25jZW50cmF0ZWQgb24gcGFydGljdWxhciBncm91cHMgb2Ygc3RvY2tzLgoKLSBGYWN0b3IgMTogRW5lcmd5Ci0gRmFjdG9yIDI6IEJhbmsKLSBGYWN0b3IgMzogVXRpbGl0eQotIEZhY3RvciA0OiBIaWdoIFRlY2gKCiMjIEZhY3RvciBzY29yZXMKClRoZSByZWdyZXNzaW9uIHNjb3JlcyBwcm92aWRlIGVzdGltYXRlZCBsYXRlbnQtZmFjdG9yIHJlYWxpemF0aW9ucyBvdmVyIHRpbWUuCgpgYGB7ciBmYWN0b3Itc2NvcmVzfQpGaGF0IDwtIGZhX3JvdCRzY29yZXMgJT4lCiAgICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgICAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJkYXRlIikgJT4lCiAgICAgIG11dGF0ZShkYXRlID0gYXMuRGF0ZShkYXRlKSkKCgpGaGF0X2xvbmcgPC0gRmhhdCAlPiUKICBwaXZvdF9sb25nZXIoLWRhdGUsIG5hbWVzX3RvID0gImZhY3RvciIsIHZhbHVlc190byA9ICJzY29yZSIpCgpnZ3Bsb3QoRmhhdF9sb25nLCBhZXMoZGF0ZSwgc2NvcmUsIGNvbG9yID0gZmFjdG9yKSkgKwogIGdlb21fbGluZShsaW5ld2lkdGggPSAwLjUpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArCiAgbGFicyh0aXRsZSA9ICJFc3RpbWF0ZWQgZmFjdG9yIHNjb3JlcyBvdmVyIHRpbWUiLAogICAgICAgeCA9IE5VTEwsIHkgPSAiU2NvcmUiLCBjb2xvciA9ICJGYWN0b3IiKQpgYGAKCiMjIENvbXBhcmlzb24gd2l0aCBQQ0EKCldlIG5vdyBjb21wYXJlIEZBIHdpdGggUENBIG9uIHRoZSBzdGFuZGFyZGl6ZWQgbWF0cml4LgoKYGBge3IgcGNhLWZpdH0KcGNhIDwtIHByY29tcChYX3N0ZCwgY2VudGVyID0gRkFMU0UsIHNjYWxlLiA9IEZBTFNFKQpyb3VuZChwY2Ekcm90YXRpb25bLCAxOm1fc3Rhcl0sIDMpCmBgYAoKIyMjIFBDQSB2cyBGQSBsb2FkaW5ncyBoZWF0bWFwCgpgYGB7ciBwY2EtdnMtZmEtbG9hZGluZ3N9CnBjYV9sb2FkX2RmIDwtIGFzLmRhdGEuZnJhbWUocGNhJHJvdGF0aW9uWywgMTptX3N0YXIsIGRyb3AgPSBGQUxTRV0pICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJzdG9jayIpICU+JQogIHBpdm90X2xvbmdlcigtc3RvY2ssIG5hbWVzX3RvID0gImZhY3RvciIsIHZhbHVlc190byA9ICJsb2FkaW5nIikgJT4lCiAgbXV0YXRlKG1ldGhvZCA9ICJQQ0EiKQoKZmFfcm90X2RmIDwtIGxvYWRpbmdzX3RvX2RmKGZhX3JvdCwgIkZBICh2YXJpbWF4KSIpCnBsb3RfbG9hZF9jb21wYXJlIDwtIGJpbmRfcm93cyhwY2FfbG9hZF9kZiwgZmFfcm90X2RmKQpwbG90X2xvYWRfY29tcGFyZSRzdG9jayA8LSBmYWN0b3IocGxvdF9sb2FkX2NvbXBhcmUkc3RvY2ssIGxldmVscyA9IHJvd25hbWVzKGZhX3JvdCRsb2FkaW5ncykpCgpnZ3Bsb3QocGxvdF9sb2FkX2NvbXBhcmUsIGFlcyhmYWN0b3IsIHN0b2NrLCBmaWxsID0gbG9hZGluZykpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gIiMyMTY2QUMiLCBtaWQgPSAid2hpdGUiLCBoaWdoID0gIiNCMjE4MkIiLCBtaWRwb2ludCA9IDApICsKICBmYWNldF93cmFwKH4gbWV0aG9kLCBucm93ID0gMSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh0aXRsZSA9ICJQQ0EgbG9hZGluZ3MgdnMgcm90YXRlZCBGQSBsb2FkaW5ncyIsCiAgICAgICB4ID0gTlVMTCwgeSA9IE5VTEwsIGZpbGwgPSAiTG9hZGluZyIpCmBgYApQQ0EgbG9hZGluZ3MgZGVzY3JpYmUgb3J0aG9nb25hbCB2YXJpYW5jZS1tYXhpbWl6aW5nIGRpcmVjdGlvbnMsIHdoaWxlIEZBIGxvYWRpbmdzIGRlc2NyaWJlIGhvdyBvYnNlcnZlZCB2YXJpYWJsZXMgZGVwZW5kIG9uIGxhdGVudCBjb21tb24gZmFjdG9ycyBwbHVzIHVuaXF1ZW5lc3Nlcy4gQWZ0ZXIgcm90YXRpb24sIEZBIGdpdmVzIGNsZWFyZXIgY2x1c3RlcmluZyBzdHJ1Y3R1cmUgb2YgdGhlIHZhcmlhYmxlcy4gSW4gY29udHJhc3QsIFBDQSBsZWFybnMgdGhlIHNoYXJlZCBtYXJrZXQgZWZmZWN0IGFzIHRoZSBmaXJzdCBQQy4KCiMjIEtleSB0YWtlYXdheXMKCi0gRkEgaXMgYSBtb2RlbCBmb3IgdGhlIGNvdmFyaWFuY2UvY29ycmVsYXRpb24gc3RydWN0dXJlLCBub3QganVzdCBhIGRhdGEtcmVkdWN0aW9uIGRldmljZS4KLSBUaGUgbnVtYmVyIG9mIGZhY3RvcnMgc2hvdWxkIGJlIGNob3NlbiB1c2luZyBib3RoICoqZWlnZW52YWx1ZSBkaWFnbm9zdGljcyoqIGFuZCAqKnBvc3QtZml0IHJlc2lkdWFsIGNoZWNrcyoqLgotIFJvdGF0aW9uIGNhbiBiZSBoZWxwZnVsIGZvciBpbnRlcnByZXRhdGlvbi4KLSBQQ0EgYW5kIEZBIG1heSBsb29rIHNpbWlsYXIgaW4gcHJhY3RpY2UsIGJ1dCB0aGVpciBvYmplY3RpdmVzIGFyZSBkaWZmZXJlbnQuCg==