Now that we loaded all the datasets we can proceed to compute
functional diversity per plots.
Biomass-weighted mean traits per plot
One first way to compute functional diversity is to compute
mono-dimensional trait diversity (Lavorel and
Garnier 2002). We can compute the average trait observed at each
plot to described the effect of logging on the understorey vegetation.
Because we’re interested in the average trait possessed by the community
we can compute the community-weighted mean trait \(CWM_i\) as follow:
\[\begin{equation}
CWM_i = \sum_{j = 1}^{S} b_{ij} \times t_j
\end{equation}\]
\(CWM_i\) is the community-weighted
mean trait in plot \(i\), \(S\) is the total number of species, \(b_{ij}\) is the biomass of species \(j\) in plot \(i\), and \(t_j\) is the trait of species \(j\).
To do so we will use the function functcomp()
in the
FD
package (Laliberté and Legendre
2010). But we first need to organize our data.
# Make site-species data.frame
sp_com = plot_species_data[, -1]
rownames(sp_com) = plot_species_data$X
sp_com = as.matrix(sp_com)
# Make synthesized trait data.frame
traits = species_traits[, -c(1:5)]
rownames(traits) = species_traits$species.code
Now that the data is organized we can compute the CWM per site for
all traits:
# Get only continuous CWM
quanti_cwm = FD::functcomp(traits[, c("height", "sla", "wood.dens")],
sp_com, CWM.type = "dom")
quanti_cwm$plot.code = rownames(quanti_cwm)
The function outputs the CWM as expressed above for continuous
traits. We will then merge this information with the CWM values.
# Merge environmental data with CWM
cwm_env = merge(
quanti_cwm,
plot_data[, c("plot.code", "block", "forestloss17", "roaddensprim")],
by = "plot.code"
)
We can now visualize the relationship between the CWM and the
environmental gradients.
par(mfrow = c(2, 2))
plot(cwm_env$forestloss17, cwm_env$height,
xlab = "Forest loss (%)", ylab = "Biomass-weighted height",
main = "CWM Height vs. forest loss")
plot(cwm_env$forestloss17, cwm_env$sla,
xlab = "Forest loss (%)", ylab = "Biomass-weighted SLA",
main = "CWM SLA vs. forest loss")
plot(cwm_env$forestloss17, cwm_env$wood.dens,
xlab = "Forest loss (%)", ylab = "Biomass-weighted wood density",
main = "CWM Wood density vs. forest loss")
plot(cwm_env$roaddensprim, cwm_env$height,
xlab = "Road density (km.km^-2)", ylab = "Biomass-weighted height",
main = "CWM Height vs. road density")
Questions for you
- Q7: How would you describe the relationship between
the different CWMs and forest loss?
- Q8: Can you test the correlation using the function
cor.test()
and does it support your previous
statements?
- Q9: How would you describe the understorey
vegetation changes with increasing forest loss?
Recompute the CWM by proportion of each category of each trait along
the environmental gradient.
non_quanti_cwm = FD::functcomp(traits[, -c(5:7)],
sp_com, CWM.type = "all")
non_quanti_cwm$plot.code = rownames(non_quanti_cwm)
non_quanti_cwm = merge(
non_quanti_cwm,
plot_data[, c("plot.code", "block", "forestloss17", "roaddensprim")],
by = "plot.code"
)
We used the same function as above functcomp()
with the
option CWM.type = "all"
. The function computes the sum of
biomass of each category for categorical traits.
par(mfrow = c(1, 1))
plot(non_quanti_cwm$forestloss17, non_quanti_cwm$woody_no,
xlab = "Forest loss (%)", ylab = "Sum of biomass of non-woody species",
main = "Biomass of non-woody species vs. forest loss")
Question for you
- Q10: How does this observation compare to above
description of the change of understorey vegetation along the forest
loss gradient?
Building the functional space
Before computing the functional diversity indices we need first to
place the species on a functional space. The way to do is to visualize
the species cloud onto the synthetic axes that represent their trait
values. Because we cannot visualize that different traits (our vision is
still limited to only 3 dimensions!) we need to use dimension reduction
techniques such as Principal Component Analysis (PCA).
Dimension reduction techniques combines the different variables to give
synthetic axes accounting for the correlations between the different
input variables Because we have a dataset that contain both continuous
and categorical trait data, we cannot use PCA and we will have to use a
slighly different statistical tool called Principal Coordinates
Analysis (PCoA, also named Metric Dimensional Scaling) that follow
similar principles.
To compute the PCoA we first need to compute a distance matrix that
expresses the difference between each pair of species. Because we have a
mixture of continuous and categorical traits, we cannot use the
Euclidean distance and have to resort to use the Gower’s dissimilarity
metric through the daisy()
function with the package
cluster
.
gower_dissim = cluster::daisy(traits)
To perform the PCoA we will be using the ade4
package
with the function dudi.pco()
:
trait_pcoa = ade4::dudi.pco(ade4::quasieuclid(gower_dissim), nf = 3,
scannf = FALSE)
trait_pcoa
The trait_pcoa
object contains the coordinates of each
species along the different PCoA axes (we chose 5 to have a limit). We
can visualize the results with the following command:
ade4::scatter(trait_pcoa, clab.row = 0)
We see two well separated groups indicating strong differences along
the two first axes of the PCoA. We can visualize the meaning of the
groups. We can try to better understand this group by looking at the
distribution of traits along these groups:
ade4::s.class(trait_pcoa$li[,1:2], fac = traits$pgf)
Questions for you
- Q11: Using the metadata available in the
README.txt
file, what is the meaning of the
pgf
column?
- Q12: How do you interpret the PCoA results given
your answer to the previous question?
Computing functional diversity indices
Now that we have species positioned in a multidimensional space we
can actually compute distinct functional diversity indices. For that
we’ll be using the fundiversity
package that offers both
flexibility and consistency to compute the indices.
We will first compute Functional Richness (FRic) with the
fd_fric()
function:
site_fric = fundiversity::fd_fric(trait_pcoa$li, sp_com, stand = FALSE)
Then we will also compute Rao’s Quadratic Entropy (Rao’s Q) and
Functional Evenness (FEve):
site_raoq = fundiversity::fd_raoq(trait_pcoa$li, sp_com)
site_feve = fundiversity::fd_feve(trait_pcoa$li, sp_com)
site_fd = merge(
merge(site_fric, site_raoq, by = "site"),
site_feve,
by = "site"
)
site_fd$plot.code = site_fd$site
site_fd = site_fd[, -1]
We can now compare the observed relationship with forest loss:
site_env_fd = merge(site_fd,
plot_data[, c("plot.code", "forestloss17", "roaddensprim")],
by = "plot.code")
par(mfrow = c(2, 2))
plot(site_env_fd$forestloss17, site_env_fd$FRic,
xlab = "Forest loss (%)", ylab = "Functional Richness (FRic)",
main = "Functional Richness vs. forest loss")
plot(site_env_fd$forestloss17, site_env_fd$Q,
xlab = "Forest loss (%)", ylab = "Rao's Quadratic Entropy",
main = "Q vs. forest loss")
plot(site_env_fd$forestloss17, site_env_fd$FEve,
xlab = "Forest loss (%)", ylab = "Functional Evenness (FEve)",
main = "FEve vs. forest loss")
plot(site_env_fd$roaddensprim, site_env_fd$FRic,
xlab = "Primary Road Density (km.km^-2)", ylab = "Functional Richness (FRic)",
main = "FRic vs. road density")
Questions for you
- Q13: How would you describe the relationships
between functional diversity and forest loss and road density?
- Q14: Using the plot generated by the code beneath
how could you describe the relationships between the three different
functional diversity indices we computed?
panel.cor = function(x, y, digits = 2, prefix = "", cex.cor, ...)
{
usr <- par("usr"); on.exit(par(usr))
par(usr = c(0, 1, 0, 1))
r <- abs(cor(x, y, use = "complete.obs"))
txt <- format(c(r, 0.123456789), digits = digits)[1]
txt <- paste0(prefix, txt)
if(missing(cex.cor)) cex.cor <- 0.8/strwidth(txt)
text(0.5, 0.5, txt, cex = cex.cor * r)
}
pairs(~FEve + Q + FRic, data = site_env_fd, lower.panel = panel.smooth,
upper.panel = panel.cor, gap = 0, row1attop = FALSE)
One issue we’re having with our functional diversity indices is also
that some of them correlate with species richness:
site_rich_fd = merge(
site_fd,
plot_data[, c("plot.code", "ntaxa")],
by = "plot.code"
)
pairs(ntaxa ~ FRic + FEve + Q, data = site_rich_fd, upper.panel = panel.cor)
Because we are using indices computed with biomass values the indices
should be more related to the total biomass values than species
richness. Let’s get the total biomass values per site and correlate it
with functional diversity indices.
site_biomass = rowSums(sp_com)
site_biomass = stack(site_biomass)
site_biomass$plot.code = site_biomass$ind
site_biomass$tot_biomass = site_biomass$values
site_biomass = site_biomass[, c("plot.code", "tot_biomass")]
site_rich_fd = merge(
site_rich_fd,
site_biomass,
by = "plot.code"
)
pairs(tot_biomass ~ FRic + FEve + Q, data = site_rich_fd,
upper.panel = panel.cor)
Question for you
- Q15: How does the relationship between indices with
species richness compare with the one observed with total biomass
values? (You can use the function
cor.test()
if you want to
test the association)
Null modelling
The principle of null modelling is to create random communities
following certain rules to get an expected distribution of diversity
metrics while keeping some properties of the data constant. In our case,
we know that functional diversity is directly linked to the number of
species, so we want to keep the species richness constant while changing
the distribution of functional diversity.
Because the site-species matrix contains biomass values which are not
discrete, the classical swapping algorithms will not work to maintain
total biomass per site and species overall biomass. The solution is then
to perform a null model based on trait values only. In this way it will
give us a null distribution of trait values while maintaining the same
richness per plot and the same relative biomass distribution.
To do so we’ll shuffle the trait table along species.
Caution: in our case we do not want to break the links
that exist between trait values, so we will be shuffling entire rows of
traits and not trait individually. This would result in a different null
model otherwise.
Because we were using the PCoA axes as our “synthetic traits” above
we’ll perform the shuffling between species names on these PCoA
axes.
# Set random seed so that everybody gets the same null traits
set.seed(20210705)
# Number of null simulations
# CAUTION: increasing this number may increase future computation time by a lot
n_null = 99
# Repeat the operation as many times as set aboev
null_traits = lapply(seq.int(n_null), function(x) {
null_trait = trait_pcoa$li
# Shuffle species names
null_species = sample(rownames(trait_pcoa$li), nrow(trait_pcoa$li))
# Replace species name in table
rownames(null_trait) = null_species
# Do not forget to return the modified table!
return(null_trait)
})
str(null_traits, max.l = 0)
head(null_traits[[1]])
We now obtain a distribution of null traits on which we still need to
compute functional diversity indices. We’ll apply similar steps as above
to perform the functional diversity computation. But in this case we’ll
have to apply the step for each distribution of null trait.
# Beware this make take a long time
null_fd = lapply(seq(length(null_traits)), function(y) {
x = null_traits[[y]]
null_fric = fundiversity::fd_fric(x, sp_com, stand = FALSE)
null_raoq = fundiversity::fd_raoq(x, sp_com)
null_feve = fundiversity::fd_feve(x, sp_com)
# Combine all null functional diversity values
null_all = merge(
merge(null_fric, null_raoq, by = "site"), null_feve, by = "site"
)
# Null Index to separate between all null simulations
null_all$null_id = y
return(null_all)
})
null_fd_all = do.call(rbind.data.frame, null_fd)
head(null_fd_all)
We now observe a list of null functional diversity metrics for each
site. Because computing functional diversity on null traits is
computationally intensive, running more simulations can take a long
time. We’ve included a version of the null functional diversity values
with 999 simulations in the data/
folder. We’re now going
to use this precomputed version to get a better approximation of the
expected distribution under the null hypothesis.
null_fd_999 = readRDS("data/null_fd_999.Rds")
head(null_fd_999)
With this null distribution we can now compare the observed values of
functional diversity with the null ones. Let’s for example focus on the
site "a100f177r"
:
# The observed value of FRic for the site
subset(site_fd, plot.code == "a100f177r")$FRic
# The null distribution of FRic for the same site
summary(subset(null_fd_999, site == "a100f177r")$FRic)
We can visualize this comparison with an histogram:
par(mfrow = c(1, 1))
# Visualize histogram of null values
hist(subset(null_fd_999, site == "a100f177r")$FRic,
breaks = 20,
xlab = "null Functional Richness",
ylab = "Frequency",
main = "FRic comparison for site 'a100f177r'")
abline(v = subset(site_fd, plot.code == "a100f177r")$FRic,
col = "darkred", lwd = 2)
Question for you
- Q16: How would describe verbally the position of
the observed value of FRic for site “a100f177r” compared to the null
distribution?
To get a proper estimate of the relartive position of the observed
value compared to the null distribution we have to build the Empirical
Cumulative Distribution Function (ECDF) that will give us the exact
quantile of the observed value. We will do so with the
ecdf()
function:
# Build the ECDF
one_null_fric_ecdf = ecdf(subset(null_fd_999, site == "a100f177r")$FRic)
# Then actually use it
obs_fric = subset(site_fd, plot.code == "a100f177r")$FRic
one_null_fric_ecdf(obs_fric)
Question for you
- Q17: What’s the quantile of the observed FRic value
in the end?
This gives us an empirical comparison of the observed value with the
null distribution. However, in macro-ecology we prefer to even
standardize further through the use of Standardized Effect Sizes (SES).
As it is done in the article we are using for our analyses. These are
simpler to compute than ECDF and simplify the interpretation. SESs are
computed in the following way:
\[
SES_i = \frac{\overline{y_{\text{null}, i}} - y_{\text{obs},
i}}{\text{SD}_{\text{null}, i}}
\] with \(SES_i\) the
standardized effect size of the index at site \(i\), \(\overline{y_{\text{null}, i}}\) the average
observed value along the null distribution of the index at site \(i\), \(y_{\text{obs}, i}\), and \(\text{SD}_{\text{null}, i}\) the standard
deviation of the null distribution of the index at site \(i\). This index is negative when the
observation is smaller than the average of the null distribution, and
positive otherwise. In the literature an SES value under -2 or above 2
is generally considered as significant.
However, note that there are controversies in the
literature about the use of SESs compared to the use of the ECDF because
we’re only leveraging on the use of the mean and standard deviation of
the null distribution instead of using the entirety of the
distribution.
Now we need to compute the average and standard deviation of the null
distribution for each index and each site. We will do so using the
aggregate()
function.
# Compute average and standard deviation of null distribution
mean_null_fd = aggregate(
cbind(mean_FRic = FRic, mean_Q = Q, mean_FEve = FEve) ~ site,
data = null_fd_999, FUN = mean, na.rm = TRUE
)
sd_null_fd = aggregate(
cbind(sd_FRic = FRic, sd_Q = Q, sd_FEve = FEve) ~ site, data = null_fd_999,
FUN = sd, na.rm = TRUE
)
# Merge null mean & sd with observed values
obs_null_fd = merge(
site_fd,
merge(mean_null_fd, sd_null_fd, by = "site"),
by.x = "plot.code", by.y = "site"
)
# Compute SES
obs_null_fd$ses_FRic = (obs_null_fd$mean_FRic - obs_null_fd$FRic)/obs_null_fd$sd_FRic
obs_null_fd$ses_Q = (obs_null_fd$mean_Q - obs_null_fd$Q)/obs_null_fd$sd_Q
obs_null_fd$ses_FEve = (obs_null_fd$mean_FEve - obs_null_fd$FEve)/obs_null_fd$sd_FEve
# Cleaner table
ses_fd = obs_null_fd[, c("plot.code", "FRic", "Q", "FEve", "ses_FRic", "ses_Q",
"ses_FEve")]
Question for you
- Q18: Using the
subset()
function with
the greater (or equal) than >=
and the lower (or equal)
than <=
, can you determine how many sites show a
significant deviation from the null observation? (absolute SES >=
2)
- Q19: Using similar code as used for observed
values, what are the relationships between SES values and forest
loss?
Mapping functional diversity
One of the joy of doing macro-ecology is to work with spatial data.
Spatial data means that we have to draw maps and this can help uncover
structures in our data. In this section of the tutorial we’re going to
use both the observed and SES functional diversity indices to draw maps
and compare them to maps of species richness to visualize the
geographical structure of the dataset. We we’ll be using the packages
sf
for creating and manipulating spatial data,
rnaturalearth
to get background maps, and
ggplot2
to show them. Nota Bene: The goal
of this particular section is to make nice visualizations of our data
and see potential structure, it is not to teach the particular concept
around spatial data and spatial visualization that have their own
challenges. If you had trouble installing the sf
package
which may be quite capricious or if you feel lost in the meaning of the
code of this section, it’s fine, you can skip it.
Looking back at the plot level data we have the coordinates of the
plot in UTM coordinates:
head(plot_data[, c(1, 4, 5)])
plot_sf = sf::st_as_sf(
plot_data[, c(1:7)],
coords = c("north", "east"),
crs = sf::st_crs("+proj=utm +zone=50 +datum=WGS84 +units=m +no_defs")
)
We can represent a basic map to see the location of the plot at world
scale:
library("ggplot2")
ggplot() +
geom_sf(data = rnaturalearth::ne_countries(returnclass = "sf")) +
geom_sf(data = plot_sf, aes(color = forestloss17)) +
scale_color_viridis_c() +
coord_sf(crs = sf::st_crs("+proj=eck4")) + # Set projection
labs(title = "Map of the concerned plots at world scale") +
theme_bw()
We see that all of our plots are indeed in Malaysia so we can focus
there:
ggplot() +
geom_sf(data = rnaturalearth::ne_countries(continent = "Asia",
returnclass = "sf")) +
geom_sf(data = plot_sf, aes(color = forestloss17)) +
scale_color_viridis_c() +
coord_sf(crs = sf::st_crs(3376), xlim = c(-1072025.83, 1053446.00),
ylim = c(85496.43, 767752.41)) +
labs(title = "Map of plots focused on Malaysia") +
ggspatial::annotation_scale() +
theme_bw()
We can even zoom even more onto the plots to see them better:
ggplot() +
geom_sf(data = rnaturalearth::ne_countries(country = "Malaysia",
returnclass = "sf")) +
geom_sf(data = plot_sf, aes(color = forestloss17)) +
scale_color_viridis_c() +
coord_sf(crs = sf::st_crs(3376), xlim = c(800000, 890000),
ylim = c(500000, 550000)) +
labs(title = "Map of plots zoomed-in on Sabah region") +
ggspatial::annotation_scale() +
ggspatial::annotation_north_arrow(location = "br") +
theme_bw()
We can even add background information to better distinguish the
plots in context (beware this will download map tiles from the
internet):
ggplot() +
ggspatial::annotation_map_tile(zoomin = -1) +
geom_sf(data = plot_sf, aes(color = forestloss17)) +
scale_color_viridis_c() +
coord_sf(crs = sf::st_crs(3376), xlim = c(800000, 890000),
ylim = c(500000, 550000)) +
labs(title = "Map of plots zoomed-in on Sabah region") +
ggspatial::annotation_scale() +
ggspatial::annotation_north_arrow(location = "br") +
theme_bw()
Because of the group of plots on the West we can’t clearly see the
distinction between plots let’s focus on the ones that show a gradient
in forest loss:
ggplot() +
ggspatial::annotation_map_tile(zoomin = -1) +
geom_sf(data = subset(plot_sf, block != "og"),
aes(color = forestloss17)) +
scale_color_viridis_c() +
coord_sf(crs = sf::st_crs(3376), xlim = c(875000, 890000),
ylim = c(518500, 531000)) +
labs(title = "Map of all plots but block 'og'") +
ggspatial::annotation_scale() +
ggspatial::annotation_north_arrow(location = "br") +
theme_bw()
And we can now visualize the map of the SES of functional diversity
indices
ggplot() +
geom_sf(
data = merge(subset(plot_sf, block != "og"), ses_fd, by = "plot.code"),
aes(color = ses_Q)
) +
scale_color_distiller(type = "div", palette = "RdYlBu",
name = "SES of Rao's Quadratic Entropy") +
coord_sf(crs = sf::st_crs(3376), xlim = c(875000, 890000),
ylim = c(518500, 531000)) +
labs(title = "Map of all plots but block 'og'") +
ggspatial::annotation_scale() +
ggspatial::annotation_north_arrow(location = "br") +
theme_gray()
Even with all this effort it is not clear how the SES varies between
sites. But at least you’re more informed about where the data we’re
studying comes from.
Laliberté, Etienne, and Pierre Legendre. 2010.
“A Distance-Based
Framework for Measuring Functional Diversity from Multiple
Traits.” Ecology 91 (1): 299–305.
https://doi.org/10.1890/08-2244.1.
Lavorel, S., and E. Garnier. 2002.
“Predicting Changes in
Community Composition and Ecosystem Functioning from Plant Traits:
Revisiting the Holy Grail.” Functional
Ecology 16 (5): 545–56.
https://doi.org/10.1046/j.1365-2435.2002.00664.x.
LS0tCnRpdGxlOiAiRnVuY3Rpb25hbCBEaXZlcnNpdHkiCm91dHB1dDogaHRtbF9kb2N1bWVudApiaWJsaW9ncmFwaHk6IGJpYmxpb2dyYXBoeS5iaWIKZWRpdG9yX29wdGlvbnM6CiAgY2h1bmtfb3V0cHV0X3R5cGU6IGNvbnNvbGUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRSkKYGBgCgpOb3cgdGhhdCB3ZSBsb2FkZWQgYWxsIHRoZSBkYXRhc2V0cyB3ZSBjYW4gcHJvY2VlZCB0byBjb21wdXRlIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IHBlciBwbG90cy4KCiMjIEJpb21hc3Mtd2VpZ2h0ZWQgbWVhbiB0cmFpdHMgcGVyIHBsb3QKCk9uZSBmaXJzdCB3YXkgdG8gY29tcHV0ZSBmdW5jdGlvbmFsIGRpdmVyc2l0eSBpcyB0byBjb21wdXRlIG1vbm8tZGltZW5zaW9uYWwgdHJhaXQgZGl2ZXJzaXR5IFtATGF2b3JlbF9QcmVkaWN0aW5nXzIwMDJdLiBXZSBjYW4gY29tcHV0ZSB0aGUgYXZlcmFnZSB0cmFpdCBvYnNlcnZlZCBhdCBlYWNoIHBsb3QgdG8gZGVzY3JpYmVkIHRoZSBlZmZlY3Qgb2YgbG9nZ2luZyBvbiB0aGUgdW5kZXJzdG9yZXkgdmVnZXRhdGlvbi4gQmVjYXVzZSB3ZSdyZSBpbnRlcmVzdGVkIGluIHRoZSBhdmVyYWdlIHRyYWl0IHBvc3Nlc3NlZCBieSB0aGUgY29tbXVuaXR5IHdlIGNhbiBjb21wdXRlIHRoZSBjb21tdW5pdHktd2VpZ2h0ZWQgbWVhbiB0cmFpdCAkQ1dNX2kkIGFzIGZvbGxvdzoKClxiZWdpbntlcXVhdGlvbn0KQ1dNX2kgPSBcc3VtX3tqID0gMX1ee1N9IGJfe2lqfSBcdGltZXMgdF9qClxlbmR7ZXF1YXRpb259CgokQ1dNX2kkIGlzIHRoZSBjb21tdW5pdHktd2VpZ2h0ZWQgbWVhbiB0cmFpdCBpbiBwbG90ICRpJCwgJFMkIGlzIHRoZSB0b3RhbCBudW1iZXIgb2Ygc3BlY2llcywgJGJfe2lqfSQgaXMgdGhlIGJpb21hc3Mgb2Ygc3BlY2llcyAkaiQgaW4gcGxvdCAkaSQsIGFuZCAkdF9qJCBpcyB0aGUgdHJhaXQgb2Ygc3BlY2llcyAkaiQuCgpUbyBkbyBzbyB3ZSB3aWxsIHVzZSB0aGUgZnVuY3Rpb24gYGZ1bmN0Y29tcCgpYCBpbiB0aGUgYEZEYCBwYWNrYWdlIFtATGFsaWJlcnRlX2Rpc3RhbmNlYmFzZWRfMjAxMF0uIEJ1dCB3ZSBmaXJzdCBuZWVkIHRvIG9yZ2FuaXplIG91ciBkYXRhLgoKYGBge3IgZGF0YS13cmFuZ2xlfQojIE1ha2Ugc2l0ZS1zcGVjaWVzIGRhdGEuZnJhbWUKc3BfY29tICAgICAgICAgICA9IHBsb3Rfc3BlY2llc19kYXRhWywgLTFdCnJvd25hbWVzKHNwX2NvbSkgPSBwbG90X3NwZWNpZXNfZGF0YSRYCnNwX2NvbSA9IGFzLm1hdHJpeChzcF9jb20pCgojIE1ha2Ugc3ludGhlc2l6ZWQgdHJhaXQgZGF0YS5mcmFtZQp0cmFpdHMgPSBzcGVjaWVzX3RyYWl0c1ssIC1jKDE6NSldCnJvd25hbWVzKHRyYWl0cykgPSBzcGVjaWVzX3RyYWl0cyRzcGVjaWVzLmNvZGUKYGBgCgpOb3cgdGhhdCB0aGUgZGF0YSBpcyBvcmdhbml6ZWQgd2UgY2FuIGNvbXB1dGUgdGhlIENXTSBwZXIgc2l0ZSBmb3IgYWxsIHRyYWl0czoKCmBgYHtyIGdldC1jd219CiMgR2V0IG9ubHkgY29udGludW91cyBDV00KcXVhbnRpX2N3bSA9IEZEOjpmdW5jdGNvbXAodHJhaXRzWywgYygiaGVpZ2h0IiwgInNsYSIsICJ3b29kLmRlbnMiKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwX2NvbSwgQ1dNLnR5cGUgPSAiZG9tIikKcXVhbnRpX2N3bSRwbG90LmNvZGUgPSByb3duYW1lcyhxdWFudGlfY3dtKQpgYGAKClRoZSBmdW5jdGlvbiBvdXRwdXRzIHRoZSBDV00gYXMgZXhwcmVzc2VkIGFib3ZlIGZvciBjb250aW51b3VzIHRyYWl0cy4gV2Ugd2lsbCB0aGVuIG1lcmdlIHRoaXMgaW5mb3JtYXRpb24gd2l0aCB0aGUgQ1dNIHZhbHVlcy4KCmBgYHtyIGN3bS1lbnZ9CiMgTWVyZ2UgZW52aXJvbm1lbnRhbCBkYXRhIHdpdGggQ1dNCmN3bV9lbnYgPSBtZXJnZSgKICBxdWFudGlfY3dtLAogIHBsb3RfZGF0YVssIGMoInBsb3QuY29kZSIsICJibG9jayIsICJmb3Jlc3Rsb3NzMTciLCAicm9hZGRlbnNwcmltIildLAogIGJ5ID0gInBsb3QuY29kZSIKKQpgYGAKCldlIGNhbiBub3cgdmlzdWFsaXplIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgQ1dNIGFuZCB0aGUgZW52aXJvbm1lbnRhbCBncmFkaWVudHMuCgpgYGB7ciBwbG90LWN3bS1lbnZ9CnBhcihtZnJvdyA9IGMoMiwgMikpCnBsb3QoY3dtX2VudiRmb3Jlc3Rsb3NzMTcsIGN3bV9lbnYkaGVpZ2h0LAogICAgIHhsYWIgPSAiRm9yZXN0IGxvc3MgKCUpIiwgeWxhYiA9ICJCaW9tYXNzLXdlaWdodGVkIGhlaWdodCIsCiAgICAgbWFpbiA9ICJDV00gSGVpZ2h0IHZzLiBmb3Jlc3QgbG9zcyIpCnBsb3QoY3dtX2VudiRmb3Jlc3Rsb3NzMTcsIGN3bV9lbnYkc2xhLAogICAgIHhsYWIgPSAiRm9yZXN0IGxvc3MgKCUpIiwgeWxhYiA9ICJCaW9tYXNzLXdlaWdodGVkIFNMQSIsCiAgICAgbWFpbiA9ICJDV00gU0xBIHZzLiBmb3Jlc3QgbG9zcyIpCnBsb3QoY3dtX2VudiRmb3Jlc3Rsb3NzMTcsIGN3bV9lbnYkd29vZC5kZW5zLAogICAgIHhsYWIgPSAiRm9yZXN0IGxvc3MgKCUpIiwgeWxhYiA9ICJCaW9tYXNzLXdlaWdodGVkIHdvb2QgZGVuc2l0eSIsCiAgICAgbWFpbiA9ICJDV00gV29vZCBkZW5zaXR5IHZzLiBmb3Jlc3QgbG9zcyIpCnBsb3QoY3dtX2VudiRyb2FkZGVuc3ByaW0sIGN3bV9lbnYkaGVpZ2h0LAogICAgIHhsYWIgPSAiUm9hZCBkZW5zaXR5IChrbS5rbV4tMikiLCB5bGFiID0gIkJpb21hc3Mtd2VpZ2h0ZWQgaGVpZ2h0IiwKICAgICBtYWluID0gIkNXTSBIZWlnaHQgdnMuIHJvYWQgZGVuc2l0eSIpCmBgYAoKOjo6IHsucXVlc3Rpb25zfQojIyMjIFF1ZXN0aW9ucyBmb3IgeW91CgoqICoqUTcqKjogSG93IHdvdWxkIHlvdSBkZXNjcmliZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGRpZmZlcmVudCBDV01zIGFuZCBmb3Jlc3QgbG9zcz8KKiAqKlE4Kio6IENhbiB5b3UgdGVzdCB0aGUgY29ycmVsYXRpb24gdXNpbmcgdGhlIGZ1bmN0aW9uIGBjb3IudGVzdCgpYCBhbmQgZG9lcyBpdCBzdXBwb3J0IHlvdXIgcHJldmlvdXMgc3RhdGVtZW50cz8KKiAqKlE5Kio6IEhvdyB3b3VsZCB5b3UgZGVzY3JpYmUgdGhlIHVuZGVyc3RvcmV5IHZlZ2V0YXRpb24gY2hhbmdlcyB3aXRoIGluY3JlYXNpbmcgZm9yZXN0IGxvc3M/Cjo6OgoKCmBgYHtyIGNvci1lbnYtY3dtLCBpbmNsdWRlID0gRkFMU0V9CmNvci50ZXN0KGN3bV9lbnYkZm9yZXN0bG9zczE3LCBjd21fZW52JGhlaWdodCkKY29yLnRlc3QoY3dtX2VudiRmb3Jlc3Rsb3NzMTcsIGN3bV9lbnYkc2xhKQpjb3IudGVzdChjd21fZW52JGZvcmVzdGxvc3MxNywgY3dtX2VudiR3b29kLmRlbnMpCmNvci50ZXN0KGN3bV9lbnYkcm9hZGRlbnNwcmltLCBjd21fZW52JGhlaWdodCkKYGBgCgpSZWNvbXB1dGUgdGhlIENXTSBieSBwcm9wb3J0aW9uIG9mIGVhY2ggY2F0ZWdvcnkgb2YgZWFjaCB0cmFpdCBhbG9uZyB0aGUgZW52aXJvbm1lbnRhbCBncmFkaWVudC4KCmBgYHtyIGdldC1ub24tcXVhbnRpLWN3bX0Kbm9uX3F1YW50aV9jd20gPSBGRDo6ZnVuY3Rjb21wKHRyYWl0c1ssIC1jKDU6NyldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BfY29tLCBDV00udHlwZSA9ICJhbGwiKQpub25fcXVhbnRpX2N3bSRwbG90LmNvZGUgPSByb3duYW1lcyhub25fcXVhbnRpX2N3bSkKCm5vbl9xdWFudGlfY3dtID0gbWVyZ2UoCiAgbm9uX3F1YW50aV9jd20sCiAgcGxvdF9kYXRhWywgYygicGxvdC5jb2RlIiwgImJsb2NrIiwgImZvcmVzdGxvc3MxNyIsICJyb2FkZGVuc3ByaW0iKV0sCiAgYnkgPSAicGxvdC5jb2RlIgopCmBgYAoKV2UgdXNlZCB0aGUgc2FtZSBmdW5jdGlvbiBhcyBhYm92ZSBgZnVuY3Rjb21wKClgIHdpdGggdGhlIG9wdGlvbiBgQ1dNLnR5cGUgPSAiYWxsImAuIFRoZSBmdW5jdGlvbiBjb21wdXRlcyB0aGUgc3VtIG9mIGJpb21hc3Mgb2YgZWFjaCBjYXRlZ29yeSBmb3IgY2F0ZWdvcmljYWwgdHJhaXRzLgoKYGBge3IgY2F0ZWdvcmljYWwtY3dtfQpwYXIobWZyb3cgPSBjKDEsIDEpKQpwbG90KG5vbl9xdWFudGlfY3dtJGZvcmVzdGxvc3MxNywgbm9uX3F1YW50aV9jd20kd29vZHlfbm8sCiAgICAgeGxhYiA9ICJGb3Jlc3QgbG9zcyAoJSkiLCB5bGFiID0gIlN1bSBvZiBiaW9tYXNzIG9mIG5vbi13b29keSBzcGVjaWVzIiwKICAgICBtYWluID0gIkJpb21hc3Mgb2Ygbm9uLXdvb2R5IHNwZWNpZXMgdnMuIGZvcmVzdCBsb3NzIikKYGBgCgo6Ojogey5xdWVzdGlvbnN9CiMjIyMgUXVlc3Rpb24gZm9yIHlvdQoKKiAqKlExMCoqOiBIb3cgZG9lcyB0aGlzIG9ic2VydmF0aW9uIGNvbXBhcmUgdG8gYWJvdmUgZGVzY3JpcHRpb24gb2YgdGhlIGNoYW5nZSBvZiB1bmRlcnN0b3JleSB2ZWdldGF0aW9uIGFsb25nIHRoZSBmb3Jlc3QgbG9zcyBncmFkaWVudD8KOjo6CgojIyBCdWlsZGluZyB0aGUgZnVuY3Rpb25hbCBzcGFjZQoKQmVmb3JlIGNvbXB1dGluZyB0aGUgZnVuY3Rpb25hbCBkaXZlcnNpdHkgaW5kaWNlcyB3ZSBuZWVkIGZpcnN0IHRvIHBsYWNlIHRoZSBzcGVjaWVzIG9uIGEgZnVuY3Rpb25hbCBzcGFjZS4KVGhlIHdheSB0byBkbyBpcyB0byB2aXN1YWxpemUgdGhlIHNwZWNpZXMgY2xvdWQgb250byB0aGUgc3ludGhldGljIGF4ZXMgdGhhdCByZXByZXNlbnQgdGhlaXIgdHJhaXQgdmFsdWVzLiBCZWNhdXNlIHdlIGNhbm5vdCB2aXN1YWxpemUgdGhhdCBkaWZmZXJlbnQgdHJhaXRzIChvdXIgdmlzaW9uIGlzIHN0aWxsIGxpbWl0ZWQgdG8gb25seSAzIGRpbWVuc2lvbnMhKSB3ZSBuZWVkIHRvIHVzZSBkaW1lbnNpb24gcmVkdWN0aW9uIHRlY2huaXF1ZXMgc3VjaCBhcyAqUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcyogKFBDQSkuIERpbWVuc2lvbiByZWR1Y3Rpb24gdGVjaG5pcXVlcyBjb21iaW5lcyB0aGUgZGlmZmVyZW50IHZhcmlhYmxlcyB0byBnaXZlIHN5bnRoZXRpYyBheGVzIGFjY291bnRpbmcgZm9yIHRoZSBjb3JyZWxhdGlvbnMgYmV0d2VlbiB0aGUgZGlmZmVyZW50IGlucHV0IHZhcmlhYmxlcyBCZWNhdXNlIHdlIGhhdmUgYSBkYXRhc2V0IHRoYXQgY29udGFpbiBib3RoIGNvbnRpbnVvdXMgYW5kIGNhdGVnb3JpY2FsIHRyYWl0IGRhdGEsIHdlIGNhbm5vdCB1c2UgUENBIGFuZCB3ZSB3aWxsIGhhdmUgdG8gdXNlIGEgc2xpZ2hseSBkaWZmZXJlbnQgc3RhdGlzdGljYWwgdG9vbCBjYWxsZWQgKlByaW5jaXBhbCBDb29yZGluYXRlcyBBbmFseXNpcyogKFBDb0EsIGFsc28gbmFtZWQgTWV0cmljIERpbWVuc2lvbmFsIFNjYWxpbmcpIHRoYXQgZm9sbG93IHNpbWlsYXIgcHJpbmNpcGxlcy4KClRvIGNvbXB1dGUgdGhlIFBDb0Egd2UgZmlyc3QgbmVlZCB0byBjb21wdXRlIGEgZGlzdGFuY2UgbWF0cml4IHRoYXQgZXhwcmVzc2VzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gZWFjaCBwYWlyIG9mIHNwZWNpZXMuIEJlY2F1c2Ugd2UgaGF2ZSBhIG1peHR1cmUgb2YgY29udGludW91cyBhbmQgY2F0ZWdvcmljYWwgdHJhaXRzLCB3ZSBjYW5ub3QgdXNlIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2UgYW5kIGhhdmUgdG8gcmVzb3J0IHRvIHVzZSB0aGUgR293ZXIncyBkaXNzaW1pbGFyaXR5IG1ldHJpYyB0aHJvdWdoIHRoZSBgZGFpc3koKWAgZnVuY3Rpb24gd2l0aCB0aGUgcGFja2FnZSBgY2x1c3RlcmAuIAoKYGBge3IgZ293ZXItZGlzc2ltfQpnb3dlcl9kaXNzaW0gPSBjbHVzdGVyOjpkYWlzeSh0cmFpdHMpCmBgYAoKVG8gcGVyZm9ybSB0aGUgUENvQSB3ZSB3aWxsIGJlIHVzaW5nIHRoZSBgYWRlNGAgcGFja2FnZSB3aXRoIHRoZSBmdW5jdGlvbiBgZHVkaS5wY28oKWA6CgpgYGB7ciBhZGU0LXBjb2F9CnRyYWl0X3Bjb2EgPSBhZGU0OjpkdWRpLnBjbyhhZGU0OjpxdWFzaWV1Y2xpZChnb3dlcl9kaXNzaW0pLCBuZiA9IDMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FubmYgPSBGQUxTRSkKdHJhaXRfcGNvYQpgYGAKClRoZSBgdHJhaXRfcGNvYWAgb2JqZWN0IGNvbnRhaW5zIHRoZSBjb29yZGluYXRlcyBvZiBlYWNoIHNwZWNpZXMgYWxvbmcgdGhlIGRpZmZlcmVudCBQQ29BIGF4ZXMgKHdlIGNob3NlIDUgdG8gaGF2ZSBhIGxpbWl0KS4KV2UgY2FuIHZpc3VhbGl6ZSB0aGUgcmVzdWx0cyB3aXRoIHRoZSBmb2xsb3dpbmcgY29tbWFuZDoKCmBgYHtyIHZpc3VhbGl6ZS1wY29hfQphZGU0OjpzY2F0dGVyKHRyYWl0X3Bjb2EsIGNsYWIucm93ID0gMCkKYGBgCldlIHNlZSB0d28gd2VsbCBzZXBhcmF0ZWQgZ3JvdXBzIGluZGljYXRpbmcgc3Ryb25nIGRpZmZlcmVuY2VzIGFsb25nIHRoZSB0d28gZmlyc3QgYXhlcyBvZiB0aGUgUENvQS4gV2UgY2FuIHZpc3VhbGl6ZSB0aGUgbWVhbmluZyBvZiB0aGUgZ3JvdXBzLiBXZSBjYW4gdHJ5IHRvIGJldHRlciB1bmRlcnN0YW5kIHRoaXMgZ3JvdXAgYnkgbG9va2luZyBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRyYWl0cyBhbG9uZyB0aGVzZSBncm91cHM6CgpgYGB7ciB3b29keS1wY29hfQphZGU0OjpzLmNsYXNzKHRyYWl0X3Bjb2EkbGlbLDE6Ml0sIGZhYyA9IHRyYWl0cyRwZ2YpCmBgYAoKOjo6IHsucXVlc3Rpb25zfQojIyMjIFF1ZXN0aW9ucyBmb3IgeW91CgoqICoqUTExKio6IFVzaW5nIHRoZSBtZXRhZGF0YSBhdmFpbGFibGUgaW4gdGhlIGBSRUFETUUudHh0YCBmaWxlLCB3aGF0IGlzIHRoZSBtZWFuaW5nIG9mIHRoZSBgcGdmYCBjb2x1bW4/CiogKipRMTIqKjogSG93IGRvIHlvdSBpbnRlcnByZXQgdGhlIFBDb0EgcmVzdWx0cyBnaXZlbiB5b3VyIGFuc3dlciB0byB0aGUgcHJldmlvdXMgcXVlc3Rpb24/Cjo6OgoKIyMgQ29tcHV0aW5nIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IGluZGljZXMKCk5vdyB0aGF0IHdlIGhhdmUgc3BlY2llcyBwb3NpdGlvbmVkIGluIGEgbXVsdGlkaW1lbnNpb25hbCBzcGFjZSB3ZSBjYW4gYWN0dWFsbHkgY29tcHV0ZSBkaXN0aW5jdCBmdW5jdGlvbmFsIGRpdmVyc2l0eSBpbmRpY2VzLiBGb3IgdGhhdCB3ZSdsbCBiZSB1c2luZyB0aGUgYGZ1bmRpdmVyc2l0eWAgcGFja2FnZSB0aGF0IG9mZmVycyBib3RoIGZsZXhpYmlsaXR5IGFuZCBjb25zaXN0ZW5jeSB0byBjb21wdXRlIHRoZSBpbmRpY2VzLgoKV2Ugd2lsbCBmaXJzdCBjb21wdXRlIEZ1bmN0aW9uYWwgUmljaG5lc3MgKEZSaWMpIHdpdGggdGhlIGBmZF9mcmljKClgIGZ1bmN0aW9uOgoKYGBge3IgZnJpY30Kc2l0ZV9mcmljID0gZnVuZGl2ZXJzaXR5OjpmZF9mcmljKHRyYWl0X3Bjb2EkbGksIHNwX2NvbSwgc3RhbmQgPSBGQUxTRSkKYGBgCgpUaGVuIHdlIHdpbGwgYWxzbyBjb21wdXRlIFJhbydzIFF1YWRyYXRpYyBFbnRyb3B5IChSYW8ncyBRKSBhbmQgRnVuY3Rpb25hbCBFdmVubmVzcyAoRkV2ZSk6CgpgYGB7ciBmZXZlLXJhb3EsIG9wdGlvbnN9CnNpdGVfcmFvcSA9IGZ1bmRpdmVyc2l0eTo6ZmRfcmFvcSh0cmFpdF9wY29hJGxpLCBzcF9jb20pCnNpdGVfZmV2ZSA9IGZ1bmRpdmVyc2l0eTo6ZmRfZmV2ZSh0cmFpdF9wY29hJGxpLCBzcF9jb20pCgpzaXRlX2ZkID0gbWVyZ2UoCiAgbWVyZ2Uoc2l0ZV9mcmljLCBzaXRlX3Jhb3EsIGJ5ID0gInNpdGUiKSwKICBzaXRlX2ZldmUsCiAgYnkgPSAic2l0ZSIKKQpzaXRlX2ZkJHBsb3QuY29kZSA9IHNpdGVfZmQkc2l0ZQpzaXRlX2ZkID0gc2l0ZV9mZFssIC0xXQpgYGAKCldlIGNhbiBub3cgY29tcGFyZSB0aGUgb2JzZXJ2ZWQgcmVsYXRpb25zaGlwIHdpdGggZm9yZXN0IGxvc3M6CgpgYGB7ciBmZC1mb3Jlc3Rsb3NzfQpzaXRlX2Vudl9mZCA9IG1lcmdlKHNpdGVfZmQsCiAgICAgICAgICAgICAgICAgICAgcGxvdF9kYXRhWywgYygicGxvdC5jb2RlIiwgImZvcmVzdGxvc3MxNyIsICJyb2FkZGVuc3ByaW0iKV0sCiAgICAgICAgICAgICAgICAgICAgYnkgPSAicGxvdC5jb2RlIikKCnBhcihtZnJvdyA9IGMoMiwgMikpCnBsb3Qoc2l0ZV9lbnZfZmQkZm9yZXN0bG9zczE3LCBzaXRlX2Vudl9mZCRGUmljLAogICAgIHhsYWIgPSAiRm9yZXN0IGxvc3MgKCUpIiwgeWxhYiA9ICJGdW5jdGlvbmFsIFJpY2huZXNzIChGUmljKSIsCiAgICAgbWFpbiA9ICJGdW5jdGlvbmFsIFJpY2huZXNzIHZzLiBmb3Jlc3QgbG9zcyIpCnBsb3Qoc2l0ZV9lbnZfZmQkZm9yZXN0bG9zczE3LCBzaXRlX2Vudl9mZCRRLAogICAgIHhsYWIgPSAiRm9yZXN0IGxvc3MgKCUpIiwgeWxhYiA9ICJSYW8ncyBRdWFkcmF0aWMgRW50cm9weSIsCiAgICAgbWFpbiA9ICJRIHZzLiBmb3Jlc3QgbG9zcyIpCnBsb3Qoc2l0ZV9lbnZfZmQkZm9yZXN0bG9zczE3LCBzaXRlX2Vudl9mZCRGRXZlLAogICAgIHhsYWIgPSAiRm9yZXN0IGxvc3MgKCUpIiwgeWxhYiA9ICJGdW5jdGlvbmFsIEV2ZW5uZXNzIChGRXZlKSIsCiAgICAgbWFpbiA9ICJGRXZlIHZzLiBmb3Jlc3QgbG9zcyIpCnBsb3Qoc2l0ZV9lbnZfZmQkcm9hZGRlbnNwcmltLCBzaXRlX2Vudl9mZCRGUmljLAogICAgIHhsYWIgPSAiUHJpbWFyeSBSb2FkIERlbnNpdHkgKGttLmttXi0yKSIsIHlsYWIgPSAiRnVuY3Rpb25hbCBSaWNobmVzcyAoRlJpYykiLAogICAgIG1haW4gPSAiRlJpYyB2cy4gcm9hZCBkZW5zaXR5IikKYGBgCgo6Ojogey5xdWVzdGlvbnN9CiMjIyMgUXVlc3Rpb25zIGZvciB5b3UKCiogKipRMTMqKjogSG93IHdvdWxkIHlvdSBkZXNjcmliZSB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IGFuZCBmb3Jlc3QgbG9zcyBhbmQgcm9hZCBkZW5zaXR5PwoqICoqUTE0Kio6IFVzaW5nIHRoZSBwbG90IGdlbmVyYXRlZCBieSB0aGUgY29kZSBiZW5lYXRoIGhvdyBjb3VsZCB5b3UgZGVzY3JpYmUgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiB0aGUgdGhyZWUgZGlmZmVyZW50IGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IGluZGljZXMgd2UgY29tcHV0ZWQ/Cjo6OgoKYGBge3IgcGFpcnMtZnVuZGl2ZXJzaXR5LCBvcHRpb25zfQpwYW5lbC5jb3IgPSBmdW5jdGlvbih4LCB5LCBkaWdpdHMgPSAyLCBwcmVmaXggPSAiIiwgY2V4LmNvciwgLi4uKQp7CiAgdXNyIDwtIHBhcigidXNyIik7IG9uLmV4aXQocGFyKHVzcikpCiAgcGFyKHVzciA9IGMoMCwgMSwgMCwgMSkpCiAgciA8LSBhYnMoY29yKHgsIHksIHVzZSA9ICJjb21wbGV0ZS5vYnMiKSkKICB0eHQgPC0gZm9ybWF0KGMociwgMC4xMjM0NTY3ODkpLCBkaWdpdHMgPSBkaWdpdHMpWzFdCiAgdHh0IDwtIHBhc3RlMChwcmVmaXgsIHR4dCkKICBpZihtaXNzaW5nKGNleC5jb3IpKSBjZXguY29yIDwtIDAuOC9zdHJ3aWR0aCh0eHQpCiAgdGV4dCgwLjUsIDAuNSwgdHh0LCBjZXggPSBjZXguY29yICogcikKfQoKcGFpcnMofkZFdmUgKyBRICsgRlJpYywgZGF0YSA9IHNpdGVfZW52X2ZkLCBsb3dlci5wYW5lbCA9IHBhbmVsLnNtb290aCwKICAgICAgdXBwZXIucGFuZWwgPSBwYW5lbC5jb3IsIGdhcCA9IDAsIHJvdzFhdHRvcCA9IEZBTFNFKQpgYGAKT25lIGlzc3VlIHdlJ3JlIGhhdmluZyB3aXRoIG91ciBmdW5jdGlvbmFsIGRpdmVyc2l0eSBpbmRpY2VzIGlzIGFsc28gdGhhdCBzb21lIG9mIHRoZW0gY29ycmVsYXRlIHdpdGggc3BlY2llcyByaWNobmVzczoKCmBgYHtyIHBhaXJzLWZkLXJpY2huZXNzfQpzaXRlX3JpY2hfZmQgPSBtZXJnZSgKICBzaXRlX2ZkLAogIHBsb3RfZGF0YVssIGMoInBsb3QuY29kZSIsICJudGF4YSIpXSwKICBieSA9ICJwbG90LmNvZGUiCikKCnBhaXJzKG50YXhhIH4gRlJpYyArIEZFdmUgKyBRLCBkYXRhID0gc2l0ZV9yaWNoX2ZkLCB1cHBlci5wYW5lbCA9IHBhbmVsLmNvcikKYGBgCgpCZWNhdXNlIHdlIGFyZSB1c2luZyBpbmRpY2VzIGNvbXB1dGVkIHdpdGggYmlvbWFzcyB2YWx1ZXMgdGhlIGluZGljZXMgc2hvdWxkIGJlIG1vcmUgcmVsYXRlZCB0byB0aGUgdG90YWwgYmlvbWFzcyB2YWx1ZXMgdGhhbiBzcGVjaWVzIHJpY2huZXNzLiBMZXQncyBnZXQgdGhlIHRvdGFsIGJpb21hc3MgdmFsdWVzIHBlciBzaXRlIGFuZCBjb3JyZWxhdGUgaXQgd2l0aCBmdW5jdGlvbmFsIGRpdmVyc2l0eSBpbmRpY2VzLgoKYGBge3IgYmlvbWFzcy1mZH0Kc2l0ZV9iaW9tYXNzID0gcm93U3VtcyhzcF9jb20pCnNpdGVfYmlvbWFzcyA9IHN0YWNrKHNpdGVfYmlvbWFzcykKCnNpdGVfYmlvbWFzcyRwbG90LmNvZGUgPSBzaXRlX2Jpb21hc3MkaW5kCnNpdGVfYmlvbWFzcyR0b3RfYmlvbWFzcyAgID0gc2l0ZV9iaW9tYXNzJHZhbHVlcwoKc2l0ZV9iaW9tYXNzID0gc2l0ZV9iaW9tYXNzWywgYygicGxvdC5jb2RlIiwgInRvdF9iaW9tYXNzIildCgpzaXRlX3JpY2hfZmQgPSBtZXJnZSgKICBzaXRlX3JpY2hfZmQsCiAgc2l0ZV9iaW9tYXNzLAogIGJ5ID0gInBsb3QuY29kZSIKKQoKcGFpcnModG90X2Jpb21hc3MgfiBGUmljICsgRkV2ZSArIFEsIGRhdGEgPSBzaXRlX3JpY2hfZmQsCiAgICAgIHVwcGVyLnBhbmVsID0gcGFuZWwuY29yKQpgYGAKCjo6OiB7LnF1ZXN0aW9uc30KIyMjIyBRdWVzdGlvbiBmb3IgeW91CgoqICoqUTE1Kio6IEhvdyBkb2VzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBpbmRpY2VzIHdpdGggc3BlY2llcyByaWNobmVzcyBjb21wYXJlIHdpdGggdGhlIG9uZSBvYnNlcnZlZCB3aXRoIHRvdGFsIGJpb21hc3MgdmFsdWVzPyAoWW91IGNhbiB1c2UgdGhlIGZ1bmN0aW9uIGBjb3IudGVzdCgpYCBpZiB5b3Ugd2FudCB0byB0ZXN0IHRoZSBhc3NvY2lhdGlvbikKOjo6CgojIyBOdWxsIG1vZGVsbGluZwoKVGhlIHByaW5jaXBsZSBvZiBudWxsIG1vZGVsbGluZyBpcyB0byBjcmVhdGUgcmFuZG9tIGNvbW11bml0aWVzIGZvbGxvd2luZyBjZXJ0YWluIHJ1bGVzIHRvIGdldCBhbiBleHBlY3RlZCBkaXN0cmlidXRpb24gb2YgZGl2ZXJzaXR5IG1ldHJpY3Mgd2hpbGUga2VlcGluZyBzb21lIHByb3BlcnRpZXMgb2YgdGhlIGRhdGEgY29uc3RhbnQuIEluIG91ciBjYXNlLCB3ZSBrbm93IHRoYXQgZnVuY3Rpb25hbCBkaXZlcnNpdHkgaXMgZGlyZWN0bHkgbGlua2VkIHRvIHRoZSBudW1iZXIgb2Ygc3BlY2llcywgc28gd2Ugd2FudCB0byBrZWVwIHRoZSBzcGVjaWVzIHJpY2huZXNzIGNvbnN0YW50IHdoaWxlIGNoYW5naW5nIHRoZSBkaXN0cmlidXRpb24gb2YgZnVuY3Rpb25hbCBkaXZlcnNpdHkuCgpCZWNhdXNlIHRoZSBzaXRlLXNwZWNpZXMgbWF0cml4IGNvbnRhaW5zIGJpb21hc3MgdmFsdWVzIHdoaWNoIGFyZSBub3QgZGlzY3JldGUsIHRoZSBjbGFzc2ljYWwgc3dhcHBpbmcgYWxnb3JpdGhtcyB3aWxsIG5vdCB3b3JrIHRvIG1haW50YWluIHRvdGFsIGJpb21hc3MgcGVyIHNpdGUgYW5kIHNwZWNpZXMgb3ZlcmFsbCBiaW9tYXNzLiBUaGUgc29sdXRpb24gaXMgdGhlbiB0byBwZXJmb3JtIGEgbnVsbCBtb2RlbCBiYXNlZCBvbiB0cmFpdCB2YWx1ZXMgb25seS4gSW4gdGhpcyB3YXkgaXQgd2lsbCBnaXZlIHVzIGEgbnVsbCBkaXN0cmlidXRpb24gb2YgdHJhaXQgdmFsdWVzIHdoaWxlIG1haW50YWluaW5nIHRoZSBzYW1lIHJpY2huZXNzIHBlciBwbG90IGFuZCB0aGUgc2FtZSByZWxhdGl2ZSBiaW9tYXNzIGRpc3RyaWJ1dGlvbi4KClRvIGRvIHNvIHdlJ2xsIHNodWZmbGUgdGhlIHRyYWl0IHRhYmxlIGFsb25nIHNwZWNpZXMuICoqQ2F1dGlvbioqOiBpbiBvdXIgY2FzZSB3ZSBkbyBub3Qgd2FudCB0byBicmVhayB0aGUgbGlua3MgdGhhdCBleGlzdCBiZXR3ZWVuIHRyYWl0IHZhbHVlcywgc28gd2Ugd2lsbCBiZSBzaHVmZmxpbmcgZW50aXJlIHJvd3Mgb2YgdHJhaXRzIGFuZCBub3QgdHJhaXQgaW5kaXZpZHVhbGx5LiBUaGlzIHdvdWxkIHJlc3VsdCBpbiBhIGRpZmZlcmVudCBudWxsIG1vZGVsIG90aGVyd2lzZS4KCkJlY2F1c2Ugd2Ugd2VyZSB1c2luZyB0aGUgUENvQSBheGVzIGFzIG91ciAic3ludGhldGljIHRyYWl0cyIgYWJvdmUgd2UnbGwgcGVyZm9ybSB0aGUgc2h1ZmZsaW5nIGJldHdlZW4gc3BlY2llcyBuYW1lcyBvbiB0aGVzZSBQQ29BIGF4ZXMuCgpgYGB7ciBudWxsLXRyYWl0c30KIyBTZXQgcmFuZG9tIHNlZWQgc28gdGhhdCBldmVyeWJvZHkgZ2V0cyB0aGUgc2FtZSBudWxsIHRyYWl0cwpzZXQuc2VlZCgyMDIxMDcwNSkKCiMgTnVtYmVyIG9mIG51bGwgc2ltdWxhdGlvbnMKIyBDQVVUSU9OOiBpbmNyZWFzaW5nIHRoaXMgbnVtYmVyIG1heSBpbmNyZWFzZSBmdXR1cmUgY29tcHV0YXRpb24gdGltZSBieSBhIGxvdApuX251bGwgPSA5OQoKIyBSZXBlYXQgdGhlIG9wZXJhdGlvbiBhcyBtYW55IHRpbWVzIGFzIHNldCBhYm9ldgpudWxsX3RyYWl0cyA9IGxhcHBseShzZXEuaW50KG5fbnVsbCksIGZ1bmN0aW9uKHgpIHsKICBudWxsX3RyYWl0ID0gdHJhaXRfcGNvYSRsaQogIAogICMgU2h1ZmZsZSBzcGVjaWVzIG5hbWVzCiAgbnVsbF9zcGVjaWVzID0gc2FtcGxlKHJvd25hbWVzKHRyYWl0X3Bjb2EkbGkpLCBucm93KHRyYWl0X3Bjb2EkbGkpKQogIAogICMgUmVwbGFjZSBzcGVjaWVzIG5hbWUgaW4gdGFibGUKICByb3duYW1lcyhudWxsX3RyYWl0KSA9IG51bGxfc3BlY2llcwogIAogICMgRG8gbm90IGZvcmdldCB0byByZXR1cm4gdGhlIG1vZGlmaWVkIHRhYmxlIQogIHJldHVybihudWxsX3RyYWl0KQp9KQoKc3RyKG51bGxfdHJhaXRzLCBtYXgubCA9IDApCmhlYWQobnVsbF90cmFpdHNbWzFdXSkKYGBgCgpXZSBub3cgb2J0YWluIGEgZGlzdHJpYnV0aW9uIG9mIG51bGwgdHJhaXRzIG9uIHdoaWNoIHdlIHN0aWxsIG5lZWQgdG8gY29tcHV0ZSBmdW5jdGlvbmFsIGRpdmVyc2l0eSBpbmRpY2VzLiBXZSdsbCBhcHBseSBzaW1pbGFyIHN0ZXBzIGFzIGFib3ZlIHRvIHBlcmZvcm0gdGhlIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IGNvbXB1dGF0aW9uLiBCdXQgaW4gdGhpcyBjYXNlIHdlJ2xsIGhhdmUgdG8gYXBwbHkgdGhlIHN0ZXAgZm9yIGVhY2ggZGlzdHJpYnV0aW9uIG9mIG51bGwgdHJhaXQuCgpgYGB7ciBudWxsLWZkfQojIEJld2FyZSB0aGlzIG1ha2UgdGFrZSBhIGxvbmcgdGltZQpudWxsX2ZkID0gbGFwcGx5KHNlcShsZW5ndGgobnVsbF90cmFpdHMpKSwgZnVuY3Rpb24oeSkgewogIAogIHggPSBudWxsX3RyYWl0c1tbeV1dCiAgCiAgbnVsbF9mcmljID0gZnVuZGl2ZXJzaXR5OjpmZF9mcmljKHgsIHNwX2NvbSwgc3RhbmQgPSBGQUxTRSkKICBudWxsX3Jhb3EgPSBmdW5kaXZlcnNpdHk6OmZkX3Jhb3EoeCwgc3BfY29tKQogIG51bGxfZmV2ZSA9IGZ1bmRpdmVyc2l0eTo6ZmRfZmV2ZSh4LCBzcF9jb20pCiAgCiAgIyBDb21iaW5lIGFsbCBudWxsIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IHZhbHVlcwogIG51bGxfYWxsID0gbWVyZ2UoCiAgICBtZXJnZShudWxsX2ZyaWMsIG51bGxfcmFvcSwgYnkgPSAic2l0ZSIpLCBudWxsX2ZldmUsIGJ5ID0gInNpdGUiCiAgKQogIAogICMgTnVsbCBJbmRleCB0byBzZXBhcmF0ZSBiZXR3ZWVuIGFsbCBudWxsIHNpbXVsYXRpb25zCiAgbnVsbF9hbGwkbnVsbF9pZCA9IHkKICAKICByZXR1cm4obnVsbF9hbGwpCn0pCgpudWxsX2ZkX2FsbCA9IGRvLmNhbGwocmJpbmQuZGF0YS5mcmFtZSwgbnVsbF9mZCkKaGVhZChudWxsX2ZkX2FsbCkKYGBgCgpXZSBub3cgb2JzZXJ2ZSBhIGxpc3Qgb2YgbnVsbCBmdW5jdGlvbmFsIGRpdmVyc2l0eSBtZXRyaWNzIGZvciBlYWNoIHNpdGUuCkJlY2F1c2UgY29tcHV0aW5nIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IG9uIG51bGwgdHJhaXRzIGlzIGNvbXB1dGF0aW9uYWxseSBpbnRlbnNpdmUsIHJ1bm5pbmcgbW9yZSBzaW11bGF0aW9ucyBjYW4gdGFrZSBhIGxvbmcgdGltZS4gV2UndmUgaW5jbHVkZWQgYSB2ZXJzaW9uIG9mIHRoZSBudWxsIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IHZhbHVlcyB3aXRoIDk5OSBzaW11bGF0aW9ucyBpbiB0aGUgYGRhdGEvYCBmb2xkZXIuIFdlJ3JlIG5vdyBnb2luZyB0byB1c2UgdGhpcyBwcmVjb21wdXRlZCB2ZXJzaW9uIHRvIGdldCBhIGJldHRlciBhcHByb3hpbWF0aW9uIG9mIHRoZSBleHBlY3RlZCBkaXN0cmlidXRpb24gdW5kZXIgdGhlIG51bGwgaHlwb3RoZXNpcy4KCmBgYHtyIG51bGwtZmQtOTk5fQpudWxsX2ZkXzk5OSA9IHJlYWRSRFMoImRhdGEvbnVsbF9mZF85OTkuUmRzIikKCmhlYWQobnVsbF9mZF85OTkpCmBgYAoKV2l0aCB0aGlzIG51bGwgZGlzdHJpYnV0aW9uIHdlIGNhbiBub3cgY29tcGFyZSB0aGUgb2JzZXJ2ZWQgdmFsdWVzIG9mIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IHdpdGggdGhlIG51bGwgb25lcy4gTGV0J3MgZm9yIGV4YW1wbGUgZm9jdXMgb24gdGhlIHNpdGUgYCJhMTAwZjE3N3IiYDoKCmBgYHtyIG51bGwtZmQtY29tcH0KIyBUaGUgb2JzZXJ2ZWQgdmFsdWUgb2YgRlJpYyBmb3IgdGhlIHNpdGUKc3Vic2V0KHNpdGVfZmQsIHBsb3QuY29kZSA9PSAiYTEwMGYxNzdyIikkRlJpYwoKIyBUaGUgbnVsbCBkaXN0cmlidXRpb24gb2YgRlJpYyBmb3IgdGhlIHNhbWUgc2l0ZQpzdW1tYXJ5KHN1YnNldChudWxsX2ZkXzk5OSwgc2l0ZSA9PSAiYTEwMGYxNzdyIikkRlJpYykKYGBgCgpXZSBjYW4gdmlzdWFsaXplIHRoaXMgY29tcGFyaXNvbiB3aXRoIGFuIGhpc3RvZ3JhbToKCmBgYHtyIGhpc3QtbnVsbC1mcmljfQpwYXIobWZyb3cgPSBjKDEsIDEpKQojIFZpc3VhbGl6ZSBoaXN0b2dyYW0gb2YgbnVsbCB2YWx1ZXMKaGlzdChzdWJzZXQobnVsbF9mZF85OTksIHNpdGUgPT0gImExMDBmMTc3ciIpJEZSaWMsCiAgICAgYnJlYWtzID0gMjAsCiAgICAgeGxhYiA9ICJudWxsIEZ1bmN0aW9uYWwgUmljaG5lc3MiLAogICAgIHlsYWIgPSAiRnJlcXVlbmN5IiwKICAgICBtYWluID0gIkZSaWMgY29tcGFyaXNvbiBmb3Igc2l0ZSAnYTEwMGYxNzdyJyIpCmFibGluZSh2ID0gc3Vic2V0KHNpdGVfZmQsIHBsb3QuY29kZSA9PSAiYTEwMGYxNzdyIikkRlJpYywKICAgICAgIGNvbCA9ICJkYXJrcmVkIiwgbHdkID0gMikKYGBgCgo6Ojogey5xdWVzdGlvbnN9CiMjIyMgUXVlc3Rpb24gZm9yIHlvdQoKKiAqKlExNioqOiBIb3cgd291bGQgZGVzY3JpYmUgdmVyYmFsbHkgdGhlIHBvc2l0aW9uIG9mIHRoZSBvYnNlcnZlZCB2YWx1ZSBvZiBGUmljIGZvciBzaXRlICJhMTAwZjE3N3IiIGNvbXBhcmVkIHRvIHRoZSBudWxsIGRpc3RyaWJ1dGlvbj8KOjo6CgpUbyBnZXQgYSBwcm9wZXIgZXN0aW1hdGUgb2YgdGhlIHJlbGFydGl2ZSBwb3NpdGlvbiBvZiB0aGUgb2JzZXJ2ZWQgdmFsdWUgY29tcGFyZWQgdG8gdGhlIG51bGwgZGlzdHJpYnV0aW9uIHdlIGhhdmUgdG8gYnVpbGQgdGhlIEVtcGlyaWNhbCBDdW11bGF0aXZlIERpc3RyaWJ1dGlvbiBGdW5jdGlvbiAoRUNERikgdGhhdCB3aWxsIGdpdmUgdXMgdGhlIGV4YWN0IHF1YW50aWxlIG9mIHRoZSBvYnNlcnZlZCB2YWx1ZS4gV2Ugd2lsbCBkbyBzbyB3aXRoIHRoZSBgZWNkZigpYCBmdW5jdGlvbjoKCmBgYHtyIGVjZGYtb25lLXNpdGV9CiMgQnVpbGQgdGhlIEVDREYKb25lX251bGxfZnJpY19lY2RmID0gZWNkZihzdWJzZXQobnVsbF9mZF85OTksIHNpdGUgPT0gImExMDBmMTc3ciIpJEZSaWMpCgojIFRoZW4gYWN0dWFsbHkgdXNlIGl0Cm9ic19mcmljID0gc3Vic2V0KHNpdGVfZmQsIHBsb3QuY29kZSA9PSAiYTEwMGYxNzdyIikkRlJpYwoKb25lX251bGxfZnJpY19lY2RmKG9ic19mcmljKQpgYGAKCjo6OiB7LnF1ZXN0aW9uc30KIyMjIyBRdWVzdGlvbiBmb3IgeW91CgoqICoqUTE3Kio6IFdoYXQncyB0aGUgcXVhbnRpbGUgb2YgdGhlIG9ic2VydmVkIEZSaWMgdmFsdWUgaW4gdGhlIGVuZD8KOjo6CgpUaGlzIGdpdmVzIHVzIGFuIGVtcGlyaWNhbCBjb21wYXJpc29uIG9mIHRoZSBvYnNlcnZlZCB2YWx1ZSB3aXRoIHRoZSBudWxsIGRpc3RyaWJ1dGlvbi4gSG93ZXZlciwgaW4gbWFjcm8tZWNvbG9neSB3ZSBwcmVmZXIgdG8gZXZlbiBzdGFuZGFyZGl6ZSBmdXJ0aGVyIHRocm91Z2ggdGhlIHVzZSBvZiBTdGFuZGFyZGl6ZWQgRWZmZWN0IFNpemVzIChTRVMpLiBBcyBpdCBpcyBkb25lIGluIHRoZSBhcnRpY2xlIHdlIGFyZSB1c2luZyBmb3Igb3VyIGFuYWx5c2VzLiBUaGVzZSBhcmUgc2ltcGxlciB0byBjb21wdXRlIHRoYW4gRUNERiBhbmQgc2ltcGxpZnkgdGhlIGludGVycHJldGF0aW9uLiBTRVNzIGFyZSBjb21wdXRlZCBpbiB0aGUgZm9sbG93aW5nIHdheToKCiQkClNFU19pID0gXGZyYWN7XG92ZXJsaW5le3lfe1x0ZXh0e251bGx9LCBpfX0gLSB5X3tcdGV4dHtvYnN9LCBpfX17XHRleHR7U0R9X3tcdGV4dHtudWxsfSwgaX19CiQkCndpdGggJFNFU19pJCB0aGUgc3RhbmRhcmRpemVkIGVmZmVjdCBzaXplIG9mIHRoZSBpbmRleCBhdCBzaXRlICRpJCwgJFxvdmVybGluZXt5X3tcdGV4dHtudWxsfSwgaX19JCB0aGUgYXZlcmFnZSBvYnNlcnZlZCB2YWx1ZSBhbG9uZyB0aGUgbnVsbCBkaXN0cmlidXRpb24gb2YgdGhlIGluZGV4IGF0IHNpdGUgJGkkLCAkeV97XHRleHR7b2JzfSwgaX0kLCBhbmQgJFx0ZXh0e1NEfV97XHRleHR7bnVsbH0sIGl9JCB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoZSBudWxsIGRpc3RyaWJ1dGlvbiBvZiB0aGUgaW5kZXggYXQgc2l0ZSAkaSQuIFRoaXMgaW5kZXggaXMgbmVnYXRpdmUgd2hlbiB0aGUgb2JzZXJ2YXRpb24gaXMgc21hbGxlciB0aGFuIHRoZSBhdmVyYWdlIG9mIHRoZSBudWxsIGRpc3RyaWJ1dGlvbiwgYW5kIHBvc2l0aXZlIG90aGVyd2lzZS4gSW4gdGhlIGxpdGVyYXR1cmUgYW4gU0VTIHZhbHVlIHVuZGVyIC0yIG9yIGFib3ZlIDIgaXMgZ2VuZXJhbGx5IGNvbnNpZGVyZWQgYXMgc2lnbmlmaWNhbnQuCgoqKkhvd2V2ZXIqKiwgbm90ZSB0aGF0IHRoZXJlIGFyZSBjb250cm92ZXJzaWVzIGluIHRoZSBsaXRlcmF0dXJlIGFib3V0IHRoZSB1c2Ugb2YgU0VTcyBjb21wYXJlZCB0byB0aGUgdXNlIG9mIHRoZSBFQ0RGIGJlY2F1c2Ugd2UncmUgb25seSBsZXZlcmFnaW5nIG9uIHRoZSB1c2Ugb2YgdGhlIG1lYW4gYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgbnVsbCBkaXN0cmlidXRpb24gaW5zdGVhZCBvZiB1c2luZyB0aGUgZW50aXJldHkgb2YgdGhlIGRpc3RyaWJ1dGlvbi4KCk5vdyB3ZSBuZWVkIHRvIGNvbXB1dGUgdGhlIGF2ZXJhZ2UgYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgbnVsbCBkaXN0cmlidXRpb24gZm9yIGVhY2ggaW5kZXggYW5kIGVhY2ggc2l0ZS4gV2Ugd2lsbCBkbyBzbyB1c2luZyB0aGUgYGFnZ3JlZ2F0ZSgpYCBmdW5jdGlvbi4KCmBgYHtyIGZkLXNlcy1hZ2dyZWdhdGV9CiMgQ29tcHV0ZSBhdmVyYWdlIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gb2YgbnVsbCBkaXN0cmlidXRpb24KbWVhbl9udWxsX2ZkID0gYWdncmVnYXRlKAogIGNiaW5kKG1lYW5fRlJpYyA9IEZSaWMsIG1lYW5fUSA9IFEsIG1lYW5fRkV2ZSA9IEZFdmUpIH4gc2l0ZSwKICBkYXRhID0gbnVsbF9mZF85OTksIEZVTiA9IG1lYW4sIG5hLnJtID0gVFJVRQopCnNkX251bGxfZmQgICA9IGFnZ3JlZ2F0ZSgKICBjYmluZChzZF9GUmljID0gRlJpYywgc2RfUSA9IFEsIHNkX0ZFdmUgPSBGRXZlKSB+IHNpdGUsIGRhdGEgPSBudWxsX2ZkXzk5OSwKICBGVU4gPSBzZCwgbmEucm0gPSBUUlVFCikKCiMgTWVyZ2UgbnVsbCBtZWFuICYgc2Qgd2l0aCBvYnNlcnZlZCB2YWx1ZXMKb2JzX251bGxfZmQgPSBtZXJnZSgKICBzaXRlX2ZkLAogIG1lcmdlKG1lYW5fbnVsbF9mZCwgc2RfbnVsbF9mZCwgYnkgPSAic2l0ZSIpLAogIGJ5LnggPSAicGxvdC5jb2RlIiwgYnkueSA9ICJzaXRlIgopCgojIENvbXB1dGUgU0VTCm9ic19udWxsX2ZkJHNlc19GUmljID0gKG9ic19udWxsX2ZkJG1lYW5fRlJpYyAtIG9ic19udWxsX2ZkJEZSaWMpL29ic19udWxsX2ZkJHNkX0ZSaWMKb2JzX251bGxfZmQkc2VzX1EgPSAob2JzX251bGxfZmQkbWVhbl9RIC0gb2JzX251bGxfZmQkUSkvb2JzX251bGxfZmQkc2RfUQpvYnNfbnVsbF9mZCRzZXNfRkV2ZSA9IChvYnNfbnVsbF9mZCRtZWFuX0ZFdmUgLSBvYnNfbnVsbF9mZCRGRXZlKS9vYnNfbnVsbF9mZCRzZF9GRXZlCgojIENsZWFuZXIgdGFibGUKc2VzX2ZkID0gb2JzX251bGxfZmRbLCBjKCJwbG90LmNvZGUiLCAiRlJpYyIsICJRIiwgIkZFdmUiLCAic2VzX0ZSaWMiLCAic2VzX1EiLAogICAgICAgICAgICAgICAgICAgICAgICAgInNlc19GRXZlIildCmBgYAoKOjo6IHsucXVlc3Rpb25zfQojIyMjIFF1ZXN0aW9uIGZvciB5b3UKCiogKipRMTgqKjogVXNpbmcgdGhlIGBzdWJzZXQoKWAgZnVuY3Rpb24gd2l0aCB0aGUgZ3JlYXRlciAob3IgZXF1YWwpIHRoYW4gYD49YCBhbmQgdGhlIGxvd2VyIChvciBlcXVhbCkgdGhhbiBgPD1gLCBjYW4geW91IGRldGVybWluZSBob3cgbWFueSBzaXRlcyBzaG93IGEgc2lnbmlmaWNhbnQgZGV2aWF0aW9uIGZyb20gdGhlIG51bGwgb2JzZXJ2YXRpb24/IChhYnNvbHV0ZSBTRVMgPj0gMikKKiAqKlExOSoqOiBVc2luZyBzaW1pbGFyIGNvZGUgYXMgdXNlZCBmb3Igb2JzZXJ2ZWQgdmFsdWVzLCB3aGF0IGFyZSB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIFNFUyB2YWx1ZXMgYW5kIGZvcmVzdCBsb3NzPwo6OjoKCiMjIE1hcHBpbmcgZnVuY3Rpb25hbCBkaXZlcnNpdHkKCk9uZSBvZiB0aGUgam95IG9mIGRvaW5nIG1hY3JvLWVjb2xvZ3kgaXMgdG8gd29yayB3aXRoIHNwYXRpYWwgZGF0YS4gU3BhdGlhbCBkYXRhIG1lYW5zIHRoYXQgd2UgaGF2ZSB0byBkcmF3IG1hcHMgYW5kIHRoaXMgY2FuIGhlbHAgdW5jb3ZlciBzdHJ1Y3R1cmVzIGluIG91ciBkYXRhLiBJbiB0aGlzIHNlY3Rpb24gb2YgdGhlIHR1dG9yaWFsIHdlJ3JlIGdvaW5nIHRvIHVzZSBib3RoIHRoZSBvYnNlcnZlZCBhbmQgU0VTIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IGluZGljZXMgdG8gZHJhdyBtYXBzIGFuZCBjb21wYXJlIHRoZW0gdG8gbWFwcyBvZiBzcGVjaWVzIHJpY2huZXNzIHRvIHZpc3VhbGl6ZSB0aGUgZ2VvZ3JhcGhpY2FsIHN0cnVjdHVyZSBvZiB0aGUgZGF0YXNldC4gV2Ugd2UnbGwgYmUgdXNpbmcgdGhlIHBhY2thZ2VzIGBzZmAgZm9yIGNyZWF0aW5nIGFuZCBtYW5pcHVsYXRpbmcgc3BhdGlhbCBkYXRhLCBgcm5hdHVyYWxlYXJ0aGAgdG8gZ2V0IGJhY2tncm91bmQgbWFwcywgYW5kIGBnZ3Bsb3QyYCB0byBzaG93IHRoZW0uICoqTm90YSBCZW5lKio6IFRoZSBnb2FsIG9mIHRoaXMgcGFydGljdWxhciBzZWN0aW9uIGlzIHRvIG1ha2UgbmljZSB2aXN1YWxpemF0aW9ucyBvZiBvdXIgZGF0YSBhbmQgc2VlIHBvdGVudGlhbCBzdHJ1Y3R1cmUsIGl0IGlzIG5vdCB0byB0ZWFjaCB0aGUgcGFydGljdWxhciBjb25jZXB0IGFyb3VuZCBzcGF0aWFsIGRhdGEgYW5kIHNwYXRpYWwgdmlzdWFsaXphdGlvbiB0aGF0IGhhdmUgdGhlaXIgb3duIGNoYWxsZW5nZXMuIElmIHlvdSBoYWQgdHJvdWJsZSBpbnN0YWxsaW5nIHRoZSBgc2ZgIHBhY2thZ2Ugd2hpY2ggbWF5IGJlIHF1aXRlIGNhcHJpY2lvdXMgb3IgaWYgeW91IGZlZWwgbG9zdCBpbiB0aGUgbWVhbmluZyBvZiB0aGUgY29kZSBvZiB0aGlzIHNlY3Rpb24sIGl0J3MgZmluZSwgeW91IGNhbiBza2lwIGl0LgoKTG9va2luZyBiYWNrIGF0IHRoZSBwbG90IGxldmVsIGRhdGEgd2UgaGF2ZSB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlIHBsb3QgaW4gVVRNIGNvb3JkaW5hdGVzOgoKYGBge3IgcGxvdC1jb29yZH0KaGVhZChwbG90X2RhdGFbLCBjKDEsIDQsIDUpXSkKCnBsb3Rfc2YgPSBzZjo6c3RfYXNfc2YoCiAgcGxvdF9kYXRhWywgYygxOjcpXSwKICBjb29yZHMgPSBjKCJub3J0aCIsICJlYXN0IiksCiAgY3JzID0gc2Y6OnN0X2NycygiK3Byb2o9dXRtICt6b25lPTUwICtkYXR1bT1XR1M4NCArdW5pdHM9bSArbm9fZGVmcyIpCikKYGBgCgpXZSBjYW4gcmVwcmVzZW50IGEgYmFzaWMgbWFwIHRvIHNlZSB0aGUgbG9jYXRpb24gb2YgdGhlIHBsb3QgYXQgd29ybGQgc2NhbGU6CgpgYGB7ciB3b3JsZC1tYXB9CmxpYnJhcnkoImdncGxvdDIiKQoKZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHJuYXR1cmFsZWFydGg6Om5lX2NvdW50cmllcyhyZXR1cm5jbGFzcyA9ICJzZiIpKSArCiAgZ2VvbV9zZihkYXRhID0gcGxvdF9zZiwgYWVzKGNvbG9yID0gZm9yZXN0bG9zczE3KSkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsKICBjb29yZF9zZihjcnMgPSBzZjo6c3RfY3JzKCIrcHJvaj1lY2s0IikpICsgICMgU2V0IHByb2plY3Rpb24KICBsYWJzKHRpdGxlID0gIk1hcCBvZiB0aGUgY29uY2VybmVkIHBsb3RzIGF0IHdvcmxkIHNjYWxlIikgKwogIHRoZW1lX2J3KCkKYGBgCgpXZSBzZWUgdGhhdCBhbGwgb2Ygb3VyIHBsb3RzIGFyZSBpbmRlZWQgaW4gTWFsYXlzaWEgc28gd2UgY2FuIGZvY3VzIHRoZXJlOgoKYGBge3IgbWFsYXlzaWEtbWFwfQpnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gcm5hdHVyYWxlYXJ0aDo6bmVfY291bnRyaWVzKGNvbnRpbmVudCA9ICJBc2lhIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuY2xhc3MgPSAic2YiKSkgKwogIGdlb21fc2YoZGF0YSA9IHBsb3Rfc2YsIGFlcyhjb2xvciA9IGZvcmVzdGxvc3MxNykpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArCiAgY29vcmRfc2YoY3JzID0gc2Y6OnN0X2NycygzMzc2KSwgeGxpbSA9IGMoLTEwNzIwMjUuODMsIDEwNTM0NDYuMDApLAogICAgICAgICAgIHlsaW0gPSBjKDg1NDk2LjQzLCA3Njc3NTIuNDEpKSArCiAgbGFicyh0aXRsZSA9ICJNYXAgb2YgcGxvdHMgZm9jdXNlZCBvbiBNYWxheXNpYSIpICsKZ2dzcGF0aWFsOjphbm5vdGF0aW9uX3NjYWxlKCkgKwogIHRoZW1lX2J3KCkKYGBgCgpXZSBjYW4gZXZlbiB6b29tIGV2ZW4gbW9yZSBvbnRvIHRoZSBwbG90cyB0byBzZWUgdGhlbSBiZXR0ZXI6CgpgYGB7ciB6b29tLW1hcH0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHJuYXR1cmFsZWFydGg6Om5lX2NvdW50cmllcyhjb3VudHJ5ID0gIk1hbGF5c2lhIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuY2xhc3MgPSAic2YiKSkgKwogIGdlb21fc2YoZGF0YSA9IHBsb3Rfc2YsIGFlcyhjb2xvciA9IGZvcmVzdGxvc3MxNykpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArCiAgY29vcmRfc2YoY3JzID0gc2Y6OnN0X2NycygzMzc2KSwgeGxpbSA9IGMoODAwMDAwLCA4OTAwMDApLAogICAgICAgICAgIHlsaW0gPSBjKDUwMDAwMCwgNTUwMDAwKSkgKwogIGxhYnModGl0bGUgPSAiTWFwIG9mIHBsb3RzIHpvb21lZC1pbiBvbiBTYWJhaCByZWdpb24iKSArCiAgZ2dzcGF0aWFsOjphbm5vdGF0aW9uX3NjYWxlKCkgKwogIGdnc3BhdGlhbDo6YW5ub3RhdGlvbl9ub3J0aF9hcnJvdyhsb2NhdGlvbiA9ICJiciIpICsKICB0aGVtZV9idygpCmBgYAoKV2UgY2FuIGV2ZW4gYWRkIGJhY2tncm91bmQgaW5mb3JtYXRpb24gdG8gYmV0dGVyIGRpc3Rpbmd1aXNoIHRoZSBwbG90cyBpbiBjb250ZXh0IChiZXdhcmUgdGhpcyB3aWxsIGRvd25sb2FkIG1hcCB0aWxlcyBmcm9tIHRoZSBpbnRlcm5ldCk6CgpgYGB7ciBjb250ZXh0LW1hcH0KZ2dwbG90KCkgKwogIGdnc3BhdGlhbDo6YW5ub3RhdGlvbl9tYXBfdGlsZSh6b29taW4gPSAtMSkgKwogIGdlb21fc2YoZGF0YSA9IHBsb3Rfc2YsIGFlcyhjb2xvciA9IGZvcmVzdGxvc3MxNykpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArCiAgY29vcmRfc2YoY3JzID0gc2Y6OnN0X2NycygzMzc2KSwgeGxpbSA9IGMoODAwMDAwLCA4OTAwMDApLAogICAgICAgICAgIHlsaW0gPSBjKDUwMDAwMCwgNTUwMDAwKSkgKwogIGxhYnModGl0bGUgPSAiTWFwIG9mIHBsb3RzIHpvb21lZC1pbiBvbiBTYWJhaCByZWdpb24iKSArCiAgZ2dzcGF0aWFsOjphbm5vdGF0aW9uX3NjYWxlKCkgKwogIGdnc3BhdGlhbDo6YW5ub3RhdGlvbl9ub3J0aF9hcnJvdyhsb2NhdGlvbiA9ICJiciIpICsKICB0aGVtZV9idygpCmBgYAoKQmVjYXVzZSBvZiB0aGUgZ3JvdXAgb2YgcGxvdHMgb24gdGhlIFdlc3Qgd2UgY2FuJ3QgY2xlYXJseSBzZWUgdGhlIGRpc3RpbmN0aW9uIGJldHdlZW4gcGxvdHMgbGV0J3MgZm9jdXMgb24gdGhlIG9uZXMgdGhhdCBzaG93IGEgZ3JhZGllbnQgaW4gZm9yZXN0IGxvc3M6CgpgYGB7ciBjb250ZXh0LW1hcC0yfQpnZ3Bsb3QoKSArCiAgZ2dzcGF0aWFsOjphbm5vdGF0aW9uX21hcF90aWxlKHpvb21pbiA9IC0xKSArCiAgZ2VvbV9zZihkYXRhID0gc3Vic2V0KHBsb3Rfc2YsIGJsb2NrICE9ICJvZyIpLAogICAgICAgICAgYWVzKGNvbG9yID0gZm9yZXN0bG9zczE3KSkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsKICBjb29yZF9zZihjcnMgPSBzZjo6c3RfY3JzKDMzNzYpLCB4bGltID0gYyg4NzUwMDAsIDg5MDAwMCksCiAgICAgICAgICAgeWxpbSA9IGMoNTE4NTAwLCA1MzEwMDApKSArCiAgbGFicyh0aXRsZSA9ICJNYXAgb2YgYWxsIHBsb3RzIGJ1dCBibG9jayAnb2cnIikgKwogIGdnc3BhdGlhbDo6YW5ub3RhdGlvbl9zY2FsZSgpICsKICBnZ3NwYXRpYWw6OmFubm90YXRpb25fbm9ydGhfYXJyb3cobG9jYXRpb24gPSAiYnIiKSArCiAgdGhlbWVfYncoKQpgYGAKCkFuZCB3ZSBjYW4gbm93IHZpc3VhbGl6ZSB0aGUgbWFwIG9mIHRoZSBTRVMgb2YgZnVuY3Rpb25hbCBkaXZlcnNpdHkgaW5kaWNlcwoKYGBge3IgZmQtbWFwfQpnZ3Bsb3QoKSArCiAgZ2VvbV9zZigKICAgIGRhdGEgPSBtZXJnZShzdWJzZXQocGxvdF9zZiwgYmxvY2sgIT0gIm9nIiksIHNlc19mZCwgYnkgPSAicGxvdC5jb2RlIiksCiAgICBhZXMoY29sb3IgPSBzZXNfUSkKICApICsKICBzY2FsZV9jb2xvcl9kaXN0aWxsZXIodHlwZSA9ICJkaXYiLCBwYWxldHRlID0gIlJkWWxCdSIsCiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAgIlNFUyBvZiBSYW8ncyBRdWFkcmF0aWMgRW50cm9weSIpICsKICBjb29yZF9zZihjcnMgPSBzZjo6c3RfY3JzKDMzNzYpLCB4bGltID0gYyg4NzUwMDAsIDg5MDAwMCksCiAgICAgICAgICAgeWxpbSA9IGMoNTE4NTAwLCA1MzEwMDApKSArCiAgbGFicyh0aXRsZSA9ICJNYXAgb2YgYWxsIHBsb3RzIGJ1dCBibG9jayAnb2cnIikgKwogIGdnc3BhdGlhbDo6YW5ub3RhdGlvbl9zY2FsZSgpICsKICBnZ3NwYXRpYWw6OmFubm90YXRpb25fbm9ydGhfYXJyb3cobG9jYXRpb24gPSAiYnIiKSArCiAgdGhlbWVfZ3JheSgpCgpgYGAKCkV2ZW4gd2l0aCBhbGwgdGhpcyBlZmZvcnQgaXQgaXMgbm90IGNsZWFyIGhvdyB0aGUgU0VTIHZhcmllcyBiZXR3ZWVuIHNpdGVzLiBCdXQgYXQgbGVhc3QgeW91J3JlIG1vcmUgaW5mb3JtZWQgYWJvdXQgd2hlcmUgdGhlIGRhdGEgd2UncmUgc3R1ZHlpbmcgY29tZXMgZnJvbS4=
License: Matthias Grenié & Marten Winter CC-BY 4.0