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