RStudio

Double-click on the .Rproj file which is as the root of the folder you downloaded. This should open a new session in RStudio that will start at the root of the project folder.

Following the tutorial

To follow the tutorial you have plenty of options:

  1. It is accessible online at https://rekyt.github.io/biodiversity_facets_tutorial/ click on Tutorial in the navigation bar to access the tutorial.
  2. You can access open the diversity_facets_tutorial.Rmd in RStudio to follow along.
  3. You can also open the diversity_facets_tutorial.R in RStudio to get only the code to execute exactly the codes here.

Installing needed packages

We will use some specific packages in the rest of the tutorial. To make sure you have them please run the following command:

install.packages(
  c("ade4",
    "ape",
    "FD",
    "fundiversity",
    "ggplot2",
    "ggspatial",
    "performance",
    "picante",
    "rnaturalearth",
    "sf")
)

Note: If you encounter trouble while installing software notify me at the beginning of the practical session and I’ll come to you to solve the issue

Context

We will be using the data from the following study (Döbert et al. 2017):

Döbert, T.F., Webber, B.L., Sugau, J.B., Dickinson, K.J.M. and Didham, R.K. (2017), Logging increases the functional and phylogenetic dispersion of understorey plant communities in tropical lowland rain forest. J Ecol, 105: 1235-1245. https://doi.org/10.1111/1365-2745.12794

Logging is the major cause of forest degradation in the Tropics. The effect of logging on taxonomic diversity is well known but more rarely studied on other facets of biodiversity such as functional diversity and phylogenetic diversity. Functional diversity and phylogenetic diversity should better reflect the impact of logging on ecosystem. For example logging can decrease the functional “redundancy” observed in ecosystems, meaning that some functional traits could be lost.

The tropical lowland rain forests on the island of Borneo are floristically among the most diverse systems on the planet, yet large-scale timber extraction and conversion to commercial tree plantations continue to drive their rapid degradation and loss. As is the case for the majority of tropical forests, the effects of logging on habitat quality in these forests have rarely been assessed, despite the critical implications for biodiversity conservation. Moreover, studies investigating the effects of logging on plant community dynamics across both tropical and temperate forest ecosystems have rarely focused on the understorey, despite its crucial relevance for successional trajectories.

Our goal with this tutorial is to reproduce the analyses from the paper and analyze how logging impacts the different facets of the diversity of the understorey vegetation, and to reveal to what extent similar facets give similar answers. The general goal is to familiarize yourself with the data and functions needed to compute diversity indices. As well the general principles behind them.

Associated slides

This practical session comes with some slides that cover the general context of the study as well as some basic facts regarding functional diversity indices.

downloadthis::download_link(
  link = "https://github.com/Rekyt/biodiversity_facets_tutorial/raw/main/biodiversity_facets_presentation.odp",
  button_label = "Download Context Slides",
  button_type = "danger",
  has_icon = TRUE,
  icon = "fa fa-save"
)

Loading the data

Fortunately for us, the authors of the study have shared openly the data they used in their article (Döbert et al. 2018). They are available through the Dryad platform at the following link: https://doi.org/10.5061/dryad.f77p7

Döbert, Timm F. et al. (2018), Data from: Logging increases the functional and phylogenetic dispersion of understorey plant communities in tropical lowland rainforest, Dryad, Dataset, https://doi.org/10.5061/dryad.f77p7

The fact that these data researchers provided the full dataset including all data and meta-data will help us reproduce the exact same analyses as well as additional analyses not in their paper.

Getting the data

To get the data you can follow the above-mentioned link https://doi.org/10.5061/dryad.f77p7 and click on the “Download Dataset” button available on the top right of the webpage. It will download a .zip file that you can unzip in the folder you created for the project. This will create a folder named doi_10.5061_dryad.f77p7__v1 that contains all needed data files.

Summarizing the data

The zip file contains 4 files (also available in the data/doi_10.5061_dryad.f77p7__v1/ folder):

  • README.txt which is a text file that describes the content of the other files with great precision. It details all the columns available in the other files.
  • PlotData.csv is a comma-separated file that describes characteristics for each of the sampled vegetation plots including logging metrics, environmental variables as well as taxonomic, functional and phylogenetic diversity indices (to which we’ll compare the indices we compute ourselves).
  • PlotSpeciesData.csv is a comma-separated file that contains a matrix of biomass values for the plant taxa sampled across the sampled vegetation plots.
  • SpeciesTraitData.csv contains the complete list of species sampled across all vegetation plots, with their associated traits both continuous and discrete.

We will load each of the file (apart from the phylogenetic tree) in your workspace now with the read.csv() function:

plot_data         = read.csv("data/doi_10.5061_dryad.f77p7__v1/PlotData.csv",
                             na.strings = c("NA", "na"),
                             stringsAsFactors = TRUE)
plot_species_data = read.csv("data/doi_10.5061_dryad.f77p7__v1/PlotSpeciesData.csv")
species_traits    = read.csv("data/doi_10.5061_dryad.f77p7__v1/SpeciesTraitData.csv",
                             na.strings = c("NA", "na"), stringsAsFactors = TRUE)

To describe the data we will use the str(), summary(), and dim() functions.

str(plot_data)
summary(plot_data)

str(plot_species_data[, 1:5])
summary(head(plot_species_data)[,1:5])
dim(plot_species_data)

# Transform one column for further analyses
species_traits$seed = ordered(species_traits$seed)
str(species_traits)
summary(species_traits)

Questions to you

  • Q1: How many plots were sampled?
  • Q2: How many species are there in the dataset?
  • Q3: How many traits are available?
  • Q4: How many of them are continuous? How many of them are discrete?
  • Q5: What is the most numerous family among all observed species?
  • Q6: What is the most numerous genus?

Environment variables

Forest loss proportion is one of the main driver variable. The data has been acquired across different block with different proportion of logging and compared to unlogged forest.

boxplot(forestloss17 ~ block, data = plot_data,
        xlab = "Block of plot", ylab = "Forest loss (%)",
        main = "Forest loss in funciton of block of data")

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.

Now that we computed functional diversity, its SES, and put it on the map. We can proceed similarly with phylogenetic diversity. For this whole section we will use the ape package to manipulate phylogenetic trees and the picante package to compute phylogenetic diversity indices.

Getting the phylogenetic tree

We included a copy of the phylogenetic tree used in the article (it is given in the Supplementary Information). It is named phylo_tree.nwk in the data/ folder.

We can read it with the read.tree() function in the ape package:

phylo_tree = ape::read.tree("data/doi_10.5061_dryad.f77p7__v1/phylo_tree.nwk")

phylo_tree
str(phylo_tree)

Questions from you

  • Q20: How many taxa are in the phylogenetic tree?
  • Q21: How does this number compare to the number of taxa found in the dataset?

You can visualize the taxa in the phylogenetic tree in the tip.label slot of the phylogenetic tree:

phylo_tree$tip.label

Question for you

  • Q22: What do you notice with the species names? Especially compared to the ones available in species_traits.

To solve the naming issue we’ll have to match the names used in the phylogenetic tree to the species code used in the site-species matrix. For that we’ll match the epitheton to the first code available. You do not need to understand this code and can just copy-paste it to execute it because we’re going to use it further down.

# Create an indexed list of names
phylo_names = species_traits[, c("species.code", "species")]
phylo_names$code_id = seq(nrow(phylo_names))

# Get the first species code based on species epithet
code_id_to_use = aggregate(code_id ~ species, phylo_names,
                           FUN = function(x) head(x, 1))

# Get back the data.frame of species names with the actual species.code
code_species = merge(
  code_id_to_use, phylo_names[, c("code_id", "species.code")], by = "code_id"
)

# Tidying code for edge cases
code_species$species = gsub(" ", "", code_species$species)
code_species$species = paste0(
  tolower(substr(code_species$species, 1, 1)),
  substr(code_species$species, 2, nchar(code_species$species))
)

code_species = code_species[, c("species.code", "species")]

dim(code_species)

We can now check that we have all the names of the phylogenetic tree available as codes:

length(intersect(phylo_tree$tip.label, code_species$species))

Now that we have a clear correspondance between species code and phylogenetic name we can proceed to the computation of phylogenetic diversity indices. This won’t let use reproduce exactly the same analyses as in the paper but this is the best we can do, given the data at our disposal. If all the species were determined another possibility could have to re-create a phylogenetic tree from genetic sequences available from genetic databases. This approach however needs specific skills and is a story for another time!

Visualizing the phylogenetic tree

We can visualize the phylogenetic tree to better understand the relationship between species. With more than 600 taxa, the visualization can be quite challenging and some ajustements should be made to ease the vizualition.

The easiest way to show the phylogenetic tree is to use the plot.phylo() function available through the ape package.

ape::plot.phylo(phylo_tree)

By default the function shows the phylogram type of phylogenetic tree and plot all the labels for all species. Let’s make it easier to read:

ape::plot.phylo(phylo_tree, type = "fan", show.node.label = TRUE,
                show.tip.label = FALSE, cex = 0.6)

It is still difficult too read but we can already look at how botanical are related to one another.

Computing phylogenetic diversity indices

To compute phylogenetic diversity analyses we need to combine the phylogenetic tree with the site-species matrix. We need to subset the communities by selecting only species with a defined code from the previous section.

# Initial site-species matrix
head(sp_com[, 1:5])
dim(sp_com)

# Subset of site-species matrix compatible with phylogenetic tree
sub_phylo_com = sp_com[, as.character(code_species$species.code)]
dim(sub_phylo_com)

To measure phylogenetic diversity we will compute the Mean Pairwise Distance (MPD, Webb (2000)) using the picante package. The MPD is an index that represents the average distance between all pairs of species occurring in the community. It can also be weighted by the abundance or the biomass of considered species so that more weight is given to species that show the greatest abundance.

The first data needed to compute the MPD is the phylogenetic distance between pair of species. We’ll use the cophenetic distance which represent the same relationships as a phylogenetic tree but through a distance matrix. We can use the function cophenetic.phylo() in the ape package to obtain cophenetic distances.

# Compute cophenetic distances from the phylogenetic tree
cophen_dist = ape::cophenetic.phylo(phylo_tree)

str(cophen_dist)

# We need to change the names to species codes
corres_codes = data.frame(
  species = rownames(cophen_dist)
)
corres_codes = merge(corres_codes, code_species, by = "species")
rownames(cophen_dist) = corres_codes$species.code
colnames(cophen_dist) = corres_codes$species.code

Then to compute MPD we use the mpd() function in the picante package.

# Observed Mean Pairwise Distance
# Unweighted
mpd_val_uw = picante::mpd(sub_phylo_com, cophen_dist, abundance.weighted = FALSE)
# Weighted
mpd_val_w = picante::mpd(sub_phylo_com, cophen_dist, abundance.weighted = TRUE)

# Make a nice data.frame with observed MPD values
obs_mpd = data.frame(
  plot.code = rownames(sub_phylo_com),
  mpd_unweighted = mpd_val_uw,
  mpd_weighted = mpd_val_w
)

# Add forest loss proportion and richness for each site
obs_mpd = merge(obs_mpd, plot_data[, c("plot.code", "forestloss17", "ntaxa")])

Questions for you

  • Q23: What is the relationship between the weighted and the unweighted version of the MPD?
  • Q24: What are the relationships between MPD and taxa richness? And with forest loss? Plot these relationships to visualize them and use the cor.test() function to validate your observations.

Null modeling

Because of the expected relationship between MPD and species richness, we have to perform null models in a similar fashion to what we’ve done for functional diversity indices. Because, as with functional diversity, we want to keep null sites with same total biomass and same total biomass per species as observed sites, we can perform a “swap” null model. We will use a null model that shuffle the names of the species at the tip of the phylogenetic tree.

Fortunately, compared to functional diversity, the null models are all integrated in the ses.mpd() function in the picante package. The null model we’ll use is the "taxa.labels" one. Caution: null models can be computationally challenging; for the sake of the example we’ll do only 99 iterations but as for functional diversity a version of the null models with 999 iterations is saved in the data folder.

# Set random seed for repeatability of analysis
set.seed(20210705)

# Compute null permutation of MPD
ses_mpd = picante::ses.mpd(
  sub_phylo_com, cophen_dist, null.model = "taxa.labels",
  abundance.weighted = TRUE, runs = 99
)
head(ses_mpd)

The function ses.mpd() computes many values. You can get the detail by looking at the help of the functions with ?picante::ses.mpd in the Value section.

We’ll now load the version with 999 iterations.

ses_mpd_999 = readRDS("data/null_mpd_999.Rds")

Questions for you

  • Q25: Explain what does the column mpd.obs.z means? How does this compare with the SES values we computed for functional diversity indices?
  • Q26: How does the standardized value relates with taxa richness?
  • Q27: What are the relationships between MPD values considering null models and forest loss? Visualize the relationships with the plot() function, validate your observations with the cor.test() function.

Mapping phylogenetic diversity

In order to see if there is a geographical pattern in phylogenetic diversity we can plot maps of MPD.

ses_mpd_999$plot.code = rownames(ses_mpd_999)

ggplot() +
  geom_sf(
    data = merge(subset(plot_sf, block != "og"), ses_mpd_999, by = "plot.code"),
    aes(color = mpd.obs.z)
  ) +
  scale_color_distiller(type = "div", palette = "RdYlBu",
                        name =  "SES of MPD") +
  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()

By eye at least, the pattern doesn’t seem obvious on the map. And the observed SES values seems to vary widely within each forest block.

Comparing facets

One burning question in the scientific literature and that is quite debated still is the relationship between taxonomic, functional, and phylogenetic diversity (Pavoine et al. 2013).

We can leverage on the computation we have to test the relationships between all facets of diversity. /!\ NOTE: Because we had trouble with the phylogenetic tree, we’re not strictly comparing the same subset of data, we’re going to compare them anyway for the sake of the example. The proper way would be to subset the similar sets of species and recompute functional diversity.

# Combine taxonomic, functional, and phylogenetic diversity
all_diversity = merge(
  plot_data[, c("plot.code", "ntaxa")],
  merge(
    ses_fd, ses_mpd_999[, -1], by = "plot.code"
  )
)

# Comparison of observed values
pairs(all_diversity[, c("ntaxa", "FRic", "Q", "FEve", "mpd.obs")],
      upper.panel = panel.cor)

# Comparison of SESs
pairs(all_diversity[, c("ntaxa", "ses_FRic", "ses_Q", "ses_FEve", "mpd.obs.z")],
      upper.panel = panel.cor)

Question for you

  • Q28: How are related are observed values of functional diversity and phylogenetic diversity? What about the SESs?

Finally! We now have all we need to properly build a statistical model of the relationship between diversity facets and the different co-variables. We’ll be building linear models with lm() with environmental variables and possible co-variables that may confound the effect of logging.

We first combine the diversity metrics to the environmental co-variables:

plot_div_env = merge(
  all_diversity,
  plot_data[, c(1, 6:21)],
  by = "plot.code"
)

dim(plot_div_env)
head(plot_div_env)

Single-predictor models

Then we can build models of disturbance variables on diversity metrics. Let’s build individual models with the main disturbance variables: local forest loss forestloss17, primary road density roaddensprim, and distance to primary roads roaddistprim.

First a model on taxa richness:

mod_taxa_loss = lm(ntaxa ~ forestloss17, data = plot_div_env)

mod_taxa_loss
summary(mod_taxa_loss)

We can plot the regression line from the model with the data with the following:

par(mfrow = c(1, 1))
plot(mod_taxa_loss$model$forestloss17, mod_taxa_loss$model$ntaxa,
     xlab = "Forest Loss (%)", ylab = "Taxa Richness")
abline(coef = coef(mod_taxa_loss), col = "darkred", lwd = 1)

Questions for you

  • Q29: How would you qualify the effect of forest loss on the taxa richness?
  • Q30: With the same formula build similar models with the other predictors roaddensprim and roaddistprim. How do they compare with forest loss?

We can now build similar models for both functional and phylogenetic diversity. Because we do not want to consider the potential confounding factor of taxa richness we can consider directly the SES values we carefully built with our null models.

mod_fd_loss = lm(ses_Q ~ forestloss17, data = plot_div_env)
mod_pd_loss = lm(mpd.obs.z ~ forestloss17, data = plot_div_env)

Multi-predictors models

Because of the many possible confounding variables (different local environmental conditions, difference in vegetation types) we should build a model with many more predictors.

Let’s build a complete model with all environmental predictors:

mod_taxa_all = lm(
  ntaxa ~ elevation + forestloss17 + forestloss562 + roaddenssec +
    roaddistprim + soilPC1 + soilPC2,
  data = plot_div_env
)
mod_fd_all = lm(
  ses_Q ~ elevation + forestloss17 + forestloss562 + roaddenssec +
    roaddistprim + soilPC1 + soilPC2,
  data = plot_div_env
)
mod_pd_all = lm(
  mpd.obs.z ~ elevation + forestloss17 + forestloss562 + roaddenssec +
    roaddistprim + soilPC1 + soilPC2,
  data = plot_div_env
)

Question for you

  • Q31: What can you say about the effect of the disturbances on the different diversity metrics? What are the explanatory power of our models?

To explain the issues we have with the models we can look at the model diagnostics:

par(mfrow = c(2, 2))
plot(mod_taxa_all)
# Or even better
performance::check_model(mod_taxa_all)

To go further (but it’s beyond the scope of this tutorial) we could follow the tracks of the paper:

  1. Build a generalized linear model (GLM) when working with taxa richness as it is a count data.
  2. Leverage the fact that we have a block design in our sampling data and use mixed-models to account for that (the observations are not fully independent of one another and are structured in groups).
  3. Account for some non-linear effects of some predictors (forest loss have a strong quadratic component).
  4. Perform model averaging or model selection to prune the model to the most important predictors.
Döbert, Timm F., Bruce L. Webber, John B. Sugau, Katharine J. M. Dickinson, and Raphael K. Didham. 2017. “Logging Increases the Functional and Phylogenetic Dispersion of Understorey Plant Communities in Tropical Lowland Rain Forest.” Journal of Ecology 105 (5): 1235–45. https://doi.org/10.1111/1365-2745.12794.
———. 2018. “Data from: Logging Increases the Functional and Phylogenetic Dispersion of Understorey Plant Communities in Tropical Lowland Rainforest.” Dryad. https://doi.org/10.5061/DRYAD.F77P7.
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.
Pavoine, Sandrine, Amandine Gasc, Michael B. Bonsall, and Norman W. H. Mason. 2013. “Correlations Between Phylogenetic and Functional Diversity: Mathematical Artefacts or True Ecological and Evolutionary Processes?” Journal of Vegetation Science 24 (5): 781–93. https://doi.org/10.1111/jvs.12051.
Webb, null. 2000. “Exploring the Phylogenetic Structure of Ecological Communities: An Example for Rain Forest Trees.” The American Naturalist 156 (2): 145–55. https://doi.org/10.1086/303378.
LS0tCnRpdGxlOiAiSW5zdGFsbGluZyBSZXF1aXJlZCBQYWNrYWdlcyIKb3V0cHV0OiBodG1sX2RvY3VtZW50CmJpYmxpb2dyYXBoeTogYmlibGlvZ3JhcGh5LmJpYgplZGl0b3Jfb3B0aW9uczoKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRSkKYGBgCgojIyBSU3R1ZGlvCgpEb3VibGUtY2xpY2sgb24gdGhlIGAuUnByb2pgIGZpbGUgd2hpY2ggaXMgYXMgdGhlIHJvb3Qgb2YgdGhlIGZvbGRlciB5b3UgZG93bmxvYWRlZC4gVGhpcyBzaG91bGQgb3BlbiBhIG5ldyBzZXNzaW9uIGluIFJTdHVkaW8gdGhhdCB3aWxsIHN0YXJ0IGF0IHRoZSByb290IG9mIHRoZSBwcm9qZWN0IGZvbGRlci4KCiMjIEZvbGxvd2luZyB0aGUgdHV0b3JpYWwKClRvIGZvbGxvdyB0aGUgdHV0b3JpYWwgeW91IGhhdmUgcGxlbnR5IG9mIG9wdGlvbnM6CgoxLiBJdCBpcyBhY2Nlc3NpYmxlIG9ubGluZSBhdCBodHRwczovL3Jla3l0LmdpdGh1Yi5pby9iaW9kaXZlcnNpdHlfZmFjZXRzX3R1dG9yaWFsLyBjbGljayBvbiBgVHV0b3JpYWxgIGluIHRoZSBuYXZpZ2F0aW9uIGJhciB0byBhY2Nlc3MgdGhlIHR1dG9yaWFsLgoxLiBZb3UgY2FuIGFjY2VzcyBvcGVuIHRoZSBgZGl2ZXJzaXR5X2ZhY2V0c190dXRvcmlhbC5SbWRgIGluIFJTdHVkaW8gdG8gZm9sbG93IGFsb25nLgoxLiBZb3UgY2FuIGFsc28gb3BlbiB0aGUgYGRpdmVyc2l0eV9mYWNldHNfdHV0b3JpYWwuUmAgaW4gUlN0dWRpbyB0byBnZXQgb25seSB0aGUgY29kZSB0byBleGVjdXRlIGV4YWN0bHkgdGhlIGNvZGVzIGhlcmUuCgojIyBJbnN0YWxsaW5nIG5lZWRlZCBwYWNrYWdlcwoKV2Ugd2lsbCB1c2Ugc29tZSBzcGVjaWZpYyBwYWNrYWdlcyBpbiB0aGUgcmVzdCBvZiB0aGUgdHV0b3JpYWwuIFRvIG1ha2Ugc3VyZSB5b3UgaGF2ZSB0aGVtIHBsZWFzZSBydW4gdGhlIGZvbGxvd2luZyBjb21tYW5kOgoKYGBge3IgaW5zdGFsbC1wa2dzLCBldmFsID0gRkFMU0V9Cmluc3RhbGwucGFja2FnZXMoCiAgYygiYWRlNCIsCiAgICAiYXBlIiwKICAgICJGRCIsCiAgICAiZnVuZGl2ZXJzaXR5IiwKICAgICJnZ3Bsb3QyIiwKICAgICJnZ3NwYXRpYWwiLAogICAgInBlcmZvcm1hbmNlIiwKICAgICJwaWNhbnRlIiwKICAgICJybmF0dXJhbGVhcnRoIiwKICAgICJzZiIpCikKYGBgCgo+ICoqTm90ZSoqOiBJZiB5b3UgZW5jb3VudGVyIHRyb3VibGUgd2hpbGUgaW5zdGFsbGluZyBzb2Z0d2FyZSBub3RpZnkgbWUgYXQgdGhlIGJlZ2lubmluZyBvZiB0aGUgcHJhY3RpY2FsIHNlc3Npb24gYW5kIEknbGwgY29tZSB0byB5b3UgdG8gc29sdmUgdGhlIGlzc3VlCgojIyBDb250ZXh0CgpXZSB3aWxsIGJlIHVzaW5nIHRoZSBkYXRhIGZyb20gdGhlIGZvbGxvd2luZyBzdHVkeSBbQERvYmVydF9Mb2dnaW5nXzIwMTddOgoKPiBEw7ZiZXJ0LCBULkYuLCBXZWJiZXIsIEIuTC4sIFN1Z2F1LCBKLkIuLCBEaWNraW5zb24sIEsuSi5NLiBhbmQgRGlkaGFtLCBSLksuICgyMDE3KSwgTG9nZ2luZyBpbmNyZWFzZXMgdGhlIGZ1bmN0aW9uYWwgYW5kIHBoeWxvZ2VuZXRpYyBkaXNwZXJzaW9uIG9mIHVuZGVyc3RvcmV5IHBsYW50IGNvbW11bml0aWVzIGluIHRyb3BpY2FsIGxvd2xhbmQgcmFpbiBmb3Jlc3QuIEogRWNvbCwgMTA1OiAxMjM1LTEyNDUuIGh0dHBzOi8vZG9pLm9yZy8xMC4xMTExLzEzNjUtMjc0NS4xMjc5NAoKTG9nZ2luZyBpcyB0aGUgbWFqb3IgY2F1c2Ugb2YgZm9yZXN0IGRlZ3JhZGF0aW9uIGluIHRoZSBUcm9waWNzLiBUaGUgZWZmZWN0IG9mIGxvZ2dpbmcgb24gdGF4b25vbWljIGRpdmVyc2l0eSBpcyB3ZWxsIGtub3duIGJ1dCBtb3JlIHJhcmVseSBzdHVkaWVkIG9uIG90aGVyIGZhY2V0cyBvZiBiaW9kaXZlcnNpdHkgc3VjaCBhcyBmdW5jdGlvbmFsIGRpdmVyc2l0eSBhbmQgcGh5bG9nZW5ldGljIGRpdmVyc2l0eS4gRnVuY3Rpb25hbCBkaXZlcnNpdHkgYW5kIHBoeWxvZ2VuZXRpYyBkaXZlcnNpdHkgc2hvdWxkIGJldHRlciByZWZsZWN0IHRoZSBpbXBhY3Qgb2YgbG9nZ2luZyBvbiBlY29zeXN0ZW0uIEZvciBleGFtcGxlIGxvZ2dpbmcgY2FuIGRlY3JlYXNlIHRoZSBmdW5jdGlvbmFsICJyZWR1bmRhbmN5IiBvYnNlcnZlZCBpbiBlY29zeXN0ZW1zLCBtZWFuaW5nIHRoYXQgc29tZSBmdW5jdGlvbmFsIHRyYWl0cyBjb3VsZCBiZSBsb3N0LgoKVGhlIHRyb3BpY2FsIGxvd2xhbmQgcmFpbiBmb3Jlc3RzIG9uIHRoZSBpc2xhbmQgb2YgQm9ybmVvIGFyZSBmbG9yaXN0aWNhbGx5IGFtb25nIHRoZSBtb3N0IGRpdmVyc2Ugc3lzdGVtcyBvbiB0aGUgcGxhbmV0LCB5ZXQgbGFyZ2Utc2NhbGUgdGltYmVyIGV4dHJhY3Rpb24gYW5kIGNvbnZlcnNpb24gdG8gY29tbWVyY2lhbCB0cmVlIHBsYW50YXRpb25zIGNvbnRpbnVlIHRvIGRyaXZlIHRoZWlyIHJhcGlkIGRlZ3JhZGF0aW9uIGFuZCBsb3NzLiBBcyBpcyB0aGUgY2FzZSBmb3IgdGhlIG1ham9yaXR5IG9mIHRyb3BpY2FsIGZvcmVzdHMsIHRoZSBlZmZlY3RzIG9mIGxvZ2dpbmcgb24gaGFiaXRhdCBxdWFsaXR5IGluIHRoZXNlIGZvcmVzdHMgaGF2ZSByYXJlbHkgYmVlbiBhc3Nlc3NlZCwgZGVzcGl0ZSB0aGUgY3JpdGljYWwgaW1wbGljYXRpb25zIGZvciBiaW9kaXZlcnNpdHkgY29uc2VydmF0aW9uLiBNb3Jlb3Zlciwgc3R1ZGllcyBpbnZlc3RpZ2F0aW5nIHRoZSBlZmZlY3RzIG9mIGxvZ2dpbmcgb24gcGxhbnQgY29tbXVuaXR5IGR5bmFtaWNzIGFjcm9zcyBib3RoIHRyb3BpY2FsIGFuZCB0ZW1wZXJhdGUgZm9yZXN0IGVjb3N5c3RlbXMgaGF2ZSByYXJlbHkgZm9jdXNlZCBvbiB0aGUgdW5kZXJzdG9yZXksIGRlc3BpdGUgaXRzIGNydWNpYWwgcmVsZXZhbmNlIGZvciBzdWNjZXNzaW9uYWwgdHJhamVjdG9yaWVzLgoKT3VyIGdvYWwgd2l0aCB0aGlzIHR1dG9yaWFsIGlzIHRvIHJlcHJvZHVjZSB0aGUgYW5hbHlzZXMgZnJvbSB0aGUgcGFwZXIgYW5kIGFuYWx5emUgaG93IGxvZ2dpbmcgaW1wYWN0cyB0aGUgZGlmZmVyZW50IGZhY2V0cyBvZiB0aGUgZGl2ZXJzaXR5IG9mIHRoZSB1bmRlcnN0b3JleSB2ZWdldGF0aW9uLCBhbmQgdG8gcmV2ZWFsIHRvIHdoYXQgZXh0ZW50IHNpbWlsYXIgZmFjZXRzIGdpdmUgc2ltaWxhciBhbnN3ZXJzLiBUaGUgZ2VuZXJhbCBnb2FsIGlzIHRvIGZhbWlsaWFyaXplIHlvdXJzZWxmIHdpdGggdGhlIGRhdGEgYW5kIGZ1bmN0aW9ucyBuZWVkZWQgdG8gY29tcHV0ZSBkaXZlcnNpdHkgaW5kaWNlcy4gQXMgd2VsbCB0aGUgZ2VuZXJhbCBwcmluY2lwbGVzIGJlaGluZCB0aGVtLgoKCiMjIEFzc29jaWF0ZWQgc2xpZGVzCgpUaGlzIHByYWN0aWNhbCBzZXNzaW9uIGNvbWVzIHdpdGggc29tZSBzbGlkZXMgdGhhdCBjb3ZlciB0aGUgZ2VuZXJhbCBjb250ZXh0IG9mCnRoZSBzdHVkeSBhcyB3ZWxsIGFzIHNvbWUgYmFzaWMgZmFjdHMgcmVnYXJkaW5nIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IGluZGljZXMuCgoKYGBge3IgZG93bmxvYWQtc2xpZGVzLCBldmFsID0gVFJVRX0KZG93bmxvYWR0aGlzOjpkb3dubG9hZF9saW5rKAogIGxpbmsgPSAiaHR0cHM6Ly9naXRodWIuY29tL1Jla3l0L2Jpb2RpdmVyc2l0eV9mYWNldHNfdHV0b3JpYWwvcmF3L21haW4vYmlvZGl2ZXJzaXR5X2ZhY2V0c19wcmVzZW50YXRpb24ub2RwIiwKICBidXR0b25fbGFiZWwgPSAiRG93bmxvYWQgQ29udGV4dCBTbGlkZXMiLAogIGJ1dHRvbl90eXBlID0gImRhbmdlciIsCiAgaGFzX2ljb24gPSBUUlVFLAogIGljb24gPSAiZmEgZmEtc2F2ZSIKKQpgYGAKCgojIyBMb2FkaW5nIHRoZSBkYXRhCgpGb3J0dW5hdGVseSBmb3IgdXMsIHRoZSBhdXRob3JzIG9mIHRoZSBzdHVkeSBoYXZlIHNoYXJlZCBvcGVubHkgdGhlIGRhdGEgdGhleSB1c2VkIGluIHRoZWlyIGFydGljbGUgW0BEb2JlcnRfRGF0YV8yMDE4XS4gVGhleSBhcmUgYXZhaWxhYmxlIHRocm91Z2ggdGhlIERyeWFkIHBsYXRmb3JtIGF0IHRoZSBmb2xsb3dpbmcgbGluazogaHR0cHM6Ly9kb2kub3JnLzEwLjUwNjEvZHJ5YWQuZjc3cDcKCj4gRMO2YmVydCwgVGltbSBGLiBldCBhbC4gKDIwMTgpLCBEYXRhIGZyb206IExvZ2dpbmcgaW5jcmVhc2VzIHRoZSBmdW5jdGlvbmFsIGFuZCBwaHlsb2dlbmV0aWMgZGlzcGVyc2lvbiBvZiB1bmRlcnN0b3JleSBwbGFudCBjb21tdW5pdGllcyBpbiB0cm9waWNhbCBsb3dsYW5kIHJhaW5mb3Jlc3QsIERyeWFkLCBEYXRhc2V0LCBodHRwczovL2RvaS5vcmcvMTAuNTA2MS9kcnlhZC5mNzdwNwoKVGhlIGZhY3QgdGhhdCB0aGVzZSBkYXRhIHJlc2VhcmNoZXJzIHByb3ZpZGVkIHRoZSBmdWxsIGRhdGFzZXQgaW5jbHVkaW5nIGFsbCBkYXRhIGFuZCBtZXRhLWRhdGEgd2lsbCBoZWxwIHVzIHJlcHJvZHVjZSB0aGUgZXhhY3Qgc2FtZSBhbmFseXNlcyBhcyB3ZWxsIGFzIGFkZGl0aW9uYWwgYW5hbHlzZXMgbm90IGluIHRoZWlyIHBhcGVyLgoKIyMjIEdldHRpbmcgdGhlIGRhdGEKClRvIGdldCB0aGUgZGF0YSB5b3UgY2FuIGZvbGxvdyB0aGUgYWJvdmUtbWVudGlvbmVkIGxpbmsgaHR0cHM6Ly9kb2kub3JnLzEwLjUwNjEvZHJ5YWQuZjc3cDcgYW5kIGNsaWNrIG9uIHRoZSAiRG93bmxvYWQgRGF0YXNldCIgYnV0dG9uIGF2YWlsYWJsZSBvbiB0aGUgdG9wIHJpZ2h0IG9mIHRoZSB3ZWJwYWdlLiBJdCB3aWxsIGRvd25sb2FkIGEgLnppcCBmaWxlIHRoYXQgeW91IGNhbiB1bnppcCBpbiB0aGUgZm9sZGVyIHlvdSBjcmVhdGVkIGZvciB0aGUgcHJvamVjdC4gVGhpcyB3aWxsIGNyZWF0ZSBhIGZvbGRlciBuYW1lZCBgZG9pXzEwLjUwNjFfZHJ5YWQuZjc3cDdfX3YxYCB0aGF0IGNvbnRhaW5zIGFsbCBuZWVkZWQgZGF0YSBmaWxlcy4KCmBgYHtyIGRvd25sb2FkLWRhdGEsIGVjaG8gPSBGQUxTRSwgZXZhbCA9IFRSVUV9CmRvd25sb2FkdGhpczo6ZG93bmxvYWRfbGluaygKICBsaW5rID0gImh0dHBzOi8vZGF0YWRyeWFkLm9yZy9zdGFzaC9kb3dubG9hZHMvZG93bmxvYWRfcmVzb3VyY2UvNTE3OSIsCiAgYnV0dG9uX2xhYmVsID0gIkRvd25sb2FkIE9yaWdpbmFsIERhdGEgRmlsZXMiLAogIGJ1dHRvbl90eXBlID0gImRhbmdlciIsCiAgaGFzX2ljb24gPSBUUlVFLAogIGljb24gPSAiZmEgZmEtc2F2ZSIsCiAgc2VsZl9jb250YWluZWQgPSBGQUxTRQopCmBgYAoKCiMjIyBTdW1tYXJpemluZyB0aGUgZGF0YQoKVGhlIHppcCBmaWxlIGNvbnRhaW5zIDQgZmlsZXMgKGFsc28gYXZhaWxhYmxlIGluIHRoZSBgZGF0YS9kb2lfMTAuNTA2MV9kcnlhZC5mNzdwN19fdjEvYCBmb2xkZXIpOgoKKiBgUkVBRE1FLnR4dGAgd2hpY2ggaXMgYSB0ZXh0IGZpbGUgdGhhdCBkZXNjcmliZXMgdGhlIGNvbnRlbnQgb2YgdGhlIG90aGVyIGZpbGVzIHdpdGggZ3JlYXQgcHJlY2lzaW9uLiBJdCBkZXRhaWxzIGFsbCB0aGUgY29sdW1ucyBhdmFpbGFibGUgaW4gdGhlIG90aGVyIGZpbGVzLgoqIGBQbG90RGF0YS5jc3ZgIGlzIGEgY29tbWEtc2VwYXJhdGVkIGZpbGUgdGhhdCBkZXNjcmliZXMgY2hhcmFjdGVyaXN0aWNzIGZvciBlYWNoIG9mIHRoZSBzYW1wbGVkIHZlZ2V0YXRpb24gcGxvdHMgaW5jbHVkaW5nIGxvZ2dpbmcgbWV0cmljcywgZW52aXJvbm1lbnRhbCB2YXJpYWJsZXMgYXMgd2VsbCBhcyB0YXhvbm9taWMsIGZ1bmN0aW9uYWwgYW5kIHBoeWxvZ2VuZXRpYyBkaXZlcnNpdHkgaW5kaWNlcyAodG8gd2hpY2ggd2UnbGwgY29tcGFyZSB0aGUgaW5kaWNlcyB3ZSBjb21wdXRlIG91cnNlbHZlcykuCiogYFBsb3RTcGVjaWVzRGF0YS5jc3ZgIGlzIGEgY29tbWEtc2VwYXJhdGVkIGZpbGUgdGhhdCBjb250YWlucyBhIG1hdHJpeCBvZiBiaW9tYXNzIHZhbHVlcyBmb3IgdGhlIHBsYW50IHRheGEgc2FtcGxlZCBhY3Jvc3MgdGhlIHNhbXBsZWQgdmVnZXRhdGlvbiBwbG90cy4KKiBgU3BlY2llc1RyYWl0RGF0YS5jc3ZgIGNvbnRhaW5zIHRoZSBjb21wbGV0ZSBsaXN0IG9mIHNwZWNpZXMgIHNhbXBsZWQgYWNyb3NzIGFsbCB2ZWdldGF0aW9uIHBsb3RzLCB3aXRoIHRoZWlyIGFzc29jaWF0ZWQgdHJhaXRzIGJvdGggY29udGludW91cyBhbmQgZGlzY3JldGUuCgpXZSB3aWxsIGxvYWQgZWFjaCBvZiB0aGUgZmlsZSAoYXBhcnQgZnJvbSB0aGUgcGh5bG9nZW5ldGljIHRyZWUpIGluIHlvdXIgd29ya3NwYWNlIG5vdyB3aXRoIHRoZSBgcmVhZC5jc3YoKWAgZnVuY3Rpb246CgpgYGB7ciBsb2FkaW5nLWRhdGF9CnBsb3RfZGF0YSAgICAgICAgID0gcmVhZC5jc3YoImRhdGEvZG9pXzEwLjUwNjFfZHJ5YWQuZjc3cDdfX3YxL1Bsb3REYXRhLmNzdiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEuc3RyaW5ncyA9IGMoIk5BIiwgIm5hIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUpCnBsb3Rfc3BlY2llc19kYXRhID0gcmVhZC5jc3YoImRhdGEvZG9pXzEwLjUwNjFfZHJ5YWQuZjc3cDdfX3YxL1Bsb3RTcGVjaWVzRGF0YS5jc3YiKQpzcGVjaWVzX3RyYWl0cyAgICA9IHJlYWQuY3N2KCJkYXRhL2RvaV8xMC41MDYxX2RyeWFkLmY3N3A3X192MS9TcGVjaWVzVHJhaXREYXRhLmNzdiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEuc3RyaW5ncyA9IGMoIk5BIiwgIm5hIiksIHN0cmluZ3NBc0ZhY3RvcnMgPSBUUlVFKQpgYGAKClRvIGRlc2NyaWJlIHRoZSBkYXRhIHdlIHdpbGwgdXNlIHRoZSBgc3RyKClgLCBgc3VtbWFyeSgpYCwgYW5kIGBkaW0oKWAgZnVuY3Rpb25zLgoKYGBge3Igc3RyLXN1bW1hcnktZGF0YX0Kc3RyKHBsb3RfZGF0YSkKc3VtbWFyeShwbG90X2RhdGEpCgpzdHIocGxvdF9zcGVjaWVzX2RhdGFbLCAxOjVdKQpzdW1tYXJ5KGhlYWQocGxvdF9zcGVjaWVzX2RhdGEpWywxOjVdKQpkaW0ocGxvdF9zcGVjaWVzX2RhdGEpCgojIFRyYW5zZm9ybSBvbmUgY29sdW1uIGZvciBmdXJ0aGVyIGFuYWx5c2VzCnNwZWNpZXNfdHJhaXRzJHNlZWQgPSBvcmRlcmVkKHNwZWNpZXNfdHJhaXRzJHNlZWQpCnN0cihzcGVjaWVzX3RyYWl0cykKc3VtbWFyeShzcGVjaWVzX3RyYWl0cykKYGBgCgo6Ojo6IHsucXVlc3Rpb25zfQojIyMjIFF1ZXN0aW9ucyB0byB5b3UKCiogKipRMSoqOiBIb3cgbWFueSBwbG90cyB3ZXJlIHNhbXBsZWQ/CiogKipRMioqOiBIb3cgbWFueSBzcGVjaWVzIGFyZSB0aGVyZSBpbiB0aGUgZGF0YXNldD8KKiAqKlEzKio6IEhvdyBtYW55IHRyYWl0cyBhcmUgYXZhaWxhYmxlPwoqICoqUTQqKjogSG93IG1hbnkgb2YgdGhlbSBhcmUgY29udGludW91cz8gSG93IG1hbnkgb2YgdGhlbSBhcmUgZGlzY3JldGU/CiogKipRNSoqOiBXaGF0IGlzIHRoZSBtb3N0IG51bWVyb3VzIGZhbWlseSBhbW9uZyBhbGwgb2JzZXJ2ZWQgc3BlY2llcz8KKiAqKlE2Kio6IFdoYXQgaXMgdGhlIG1vc3QgbnVtZXJvdXMgZ2VudXM/Cjo6OjoKCiMjIyBFbnZpcm9ubWVudCB2YXJpYWJsZXMKCkZvcmVzdCBsb3NzIHByb3BvcnRpb24gaXMgb25lIG9mIHRoZSBtYWluIGRyaXZlciB2YXJpYWJsZS4gVGhlIGRhdGEgaGFzIGJlZW4gYWNxdWlyZWQgYWNyb3NzIGRpZmZlcmVudCBibG9jayB3aXRoIGRpZmZlcmVudCBwcm9wb3J0aW9uIG9mIGxvZ2dpbmcgYW5kIGNvbXBhcmVkIHRvIHVubG9nZ2VkIGZvcmVzdC4KCmBgYHtyIGZvcmVzdC1ibG9ja30KYm94cGxvdChmb3Jlc3Rsb3NzMTcgfiBibG9jaywgZGF0YSA9IHBsb3RfZGF0YSwKICAgICAgICB4bGFiID0gIkJsb2NrIG9mIHBsb3QiLCB5bGFiID0gIkZvcmVzdCBsb3NzICglKSIsCiAgICAgICAgbWFpbiA9ICJGb3Jlc3QgbG9zcyBpbiBmdW5jaXRvbiBvZiBibG9jayBvZiBkYXRhIikKYGBgCgoKTm93IHRoYXQgd2UgbG9hZGVkIGFsbCB0aGUgZGF0YXNldHMgd2UgY2FuIHByb2NlZWQgdG8gY29tcHV0ZSBmdW5jdGlvbmFsIGRpdmVyc2l0eSBwZXIgcGxvdHMuCgojIyBCaW9tYXNzLXdlaWdodGVkIG1lYW4gdHJhaXRzIHBlciBwbG90CgpPbmUgZmlyc3Qgd2F5IHRvIGNvbXB1dGUgZnVuY3Rpb25hbCBkaXZlcnNpdHkgaXMgdG8gY29tcHV0ZSBtb25vLWRpbWVuc2lvbmFsIHRyYWl0IGRpdmVyc2l0eSBbQExhdm9yZWxfUHJlZGljdGluZ18yMDAyXS4gV2UgY2FuIGNvbXB1dGUgdGhlIGF2ZXJhZ2UgdHJhaXQgb2JzZXJ2ZWQgYXQgZWFjaCBwbG90IHRvIGRlc2NyaWJlZCB0aGUgZWZmZWN0IG9mIGxvZ2dpbmcgb24gdGhlIHVuZGVyc3RvcmV5IHZlZ2V0YXRpb24uIEJlY2F1c2Ugd2UncmUgaW50ZXJlc3RlZCBpbiB0aGUgYXZlcmFnZSB0cmFpdCBwb3NzZXNzZWQgYnkgdGhlIGNvbW11bml0eSB3ZSBjYW4gY29tcHV0ZSB0aGUgY29tbXVuaXR5LXdlaWdodGVkIG1lYW4gdHJhaXQgJENXTV9pJCBhcyBmb2xsb3c6CgpcYmVnaW57ZXF1YXRpb259CkNXTV9pID0gXHN1bV97aiA9IDF9XntTfSBiX3tpan0gXHRpbWVzIHRfagpcZW5ke2VxdWF0aW9ufQoKJENXTV9pJCBpcyB0aGUgY29tbXVuaXR5LXdlaWdodGVkIG1lYW4gdHJhaXQgaW4gcGxvdCAkaSQsICRTJCBpcyB0aGUgdG90YWwgbnVtYmVyIG9mIHNwZWNpZXMsICRiX3tpan0kIGlzIHRoZSBiaW9tYXNzIG9mIHNwZWNpZXMgJGokIGluIHBsb3QgJGkkLCBhbmQgJHRfaiQgaXMgdGhlIHRyYWl0IG9mIHNwZWNpZXMgJGokLgoKVG8gZG8gc28gd2Ugd2lsbCB1c2UgdGhlIGZ1bmN0aW9uIGBmdW5jdGNvbXAoKWAgaW4gdGhlIGBGRGAgcGFja2FnZSBbQExhbGliZXJ0ZV9kaXN0YW5jZWJhc2VkXzIwMTBdLiBCdXQgd2UgZmlyc3QgbmVlZCB0byBvcmdhbml6ZSBvdXIgZGF0YS4KCmBgYHtyIGRhdGEtd3JhbmdsZX0KIyBNYWtlIHNpdGUtc3BlY2llcyBkYXRhLmZyYW1lCnNwX2NvbSAgICAgICAgICAgPSBwbG90X3NwZWNpZXNfZGF0YVssIC0xXQpyb3duYW1lcyhzcF9jb20pID0gcGxvdF9zcGVjaWVzX2RhdGEkWApzcF9jb20gPSBhcy5tYXRyaXgoc3BfY29tKQoKIyBNYWtlIHN5bnRoZXNpemVkIHRyYWl0IGRhdGEuZnJhbWUKdHJhaXRzID0gc3BlY2llc190cmFpdHNbLCAtYygxOjUpXQpyb3duYW1lcyh0cmFpdHMpID0gc3BlY2llc190cmFpdHMkc3BlY2llcy5jb2RlCmBgYAoKTm93IHRoYXQgdGhlIGRhdGEgaXMgb3JnYW5pemVkIHdlIGNhbiBjb21wdXRlIHRoZSBDV00gcGVyIHNpdGUgZm9yIGFsbCB0cmFpdHM6CgpgYGB7ciBnZXQtY3dtfQojIEdldCBvbmx5IGNvbnRpbnVvdXMgQ1dNCnF1YW50aV9jd20gPSBGRDo6ZnVuY3Rjb21wKHRyYWl0c1ssIGMoImhlaWdodCIsICJzbGEiLCAid29vZC5kZW5zIildLAogICAgICAgICAgICAgICAgICAgICAgICAgICBzcF9jb20sIENXTS50eXBlID0gImRvbSIpCnF1YW50aV9jd20kcGxvdC5jb2RlID0gcm93bmFtZXMocXVhbnRpX2N3bSkKYGBgCgpUaGUgZnVuY3Rpb24gb3V0cHV0cyB0aGUgQ1dNIGFzIGV4cHJlc3NlZCBhYm92ZSBmb3IgY29udGludW91cyB0cmFpdHMuIFdlIHdpbGwgdGhlbiBtZXJnZSB0aGlzIGluZm9ybWF0aW9uIHdpdGggdGhlIENXTSB2YWx1ZXMuCgpgYGB7ciBjd20tZW52fQojIE1lcmdlIGVudmlyb25tZW50YWwgZGF0YSB3aXRoIENXTQpjd21fZW52ID0gbWVyZ2UoCiAgcXVhbnRpX2N3bSwKICBwbG90X2RhdGFbLCBjKCJwbG90LmNvZGUiLCAiYmxvY2siLCAiZm9yZXN0bG9zczE3IiwgInJvYWRkZW5zcHJpbSIpXSwKICBieSA9ICJwbG90LmNvZGUiCikKYGBgCgpXZSBjYW4gbm93IHZpc3VhbGl6ZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIENXTSBhbmQgdGhlIGVudmlyb25tZW50YWwgZ3JhZGllbnRzLgoKYGBge3IgcGxvdC1jd20tZW52fQpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KGN3bV9lbnYkZm9yZXN0bG9zczE3LCBjd21fZW52JGhlaWdodCwKICAgICB4bGFiID0gIkZvcmVzdCBsb3NzICglKSIsIHlsYWIgPSAiQmlvbWFzcy13ZWlnaHRlZCBoZWlnaHQiLAogICAgIG1haW4gPSAiQ1dNIEhlaWdodCB2cy4gZm9yZXN0IGxvc3MiKQpwbG90KGN3bV9lbnYkZm9yZXN0bG9zczE3LCBjd21fZW52JHNsYSwKICAgICB4bGFiID0gIkZvcmVzdCBsb3NzICglKSIsIHlsYWIgPSAiQmlvbWFzcy13ZWlnaHRlZCBTTEEiLAogICAgIG1haW4gPSAiQ1dNIFNMQSB2cy4gZm9yZXN0IGxvc3MiKQpwbG90KGN3bV9lbnYkZm9yZXN0bG9zczE3LCBjd21fZW52JHdvb2QuZGVucywKICAgICB4bGFiID0gIkZvcmVzdCBsb3NzICglKSIsIHlsYWIgPSAiQmlvbWFzcy13ZWlnaHRlZCB3b29kIGRlbnNpdHkiLAogICAgIG1haW4gPSAiQ1dNIFdvb2QgZGVuc2l0eSB2cy4gZm9yZXN0IGxvc3MiKQpwbG90KGN3bV9lbnYkcm9hZGRlbnNwcmltLCBjd21fZW52JGhlaWdodCwKICAgICB4bGFiID0gIlJvYWQgZGVuc2l0eSAoa20ua21eLTIpIiwgeWxhYiA9ICJCaW9tYXNzLXdlaWdodGVkIGhlaWdodCIsCiAgICAgbWFpbiA9ICJDV00gSGVpZ2h0IHZzLiByb2FkIGRlbnNpdHkiKQpgYGAKCjo6OiB7LnF1ZXN0aW9uc30KIyMjIyBRdWVzdGlvbnMgZm9yIHlvdQoKKiAqKlE3Kio6IEhvdyB3b3VsZCB5b3UgZGVzY3JpYmUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBkaWZmZXJlbnQgQ1dNcyBhbmQgZm9yZXN0IGxvc3M/CiogKipROCoqOiBDYW4geW91IHRlc3QgdGhlIGNvcnJlbGF0aW9uIHVzaW5nIHRoZSBmdW5jdGlvbiBgY29yLnRlc3QoKWAgYW5kIGRvZXMgaXQgc3VwcG9ydCB5b3VyIHByZXZpb3VzIHN0YXRlbWVudHM/CiogKipROSoqOiBIb3cgd291bGQgeW91IGRlc2NyaWJlIHRoZSB1bmRlcnN0b3JleSB2ZWdldGF0aW9uIGNoYW5nZXMgd2l0aCBpbmNyZWFzaW5nIGZvcmVzdCBsb3NzPwo6OjoKCgpgYGB7ciBjb3ItZW52LWN3bSwgaW5jbHVkZSA9IEZBTFNFfQpjb3IudGVzdChjd21fZW52JGZvcmVzdGxvc3MxNywgY3dtX2VudiRoZWlnaHQpCmNvci50ZXN0KGN3bV9lbnYkZm9yZXN0bG9zczE3LCBjd21fZW52JHNsYSkKY29yLnRlc3QoY3dtX2VudiRmb3Jlc3Rsb3NzMTcsIGN3bV9lbnYkd29vZC5kZW5zKQpjb3IudGVzdChjd21fZW52JHJvYWRkZW5zcHJpbSwgY3dtX2VudiRoZWlnaHQpCmBgYAoKUmVjb21wdXRlIHRoZSBDV00gYnkgcHJvcG9ydGlvbiBvZiBlYWNoIGNhdGVnb3J5IG9mIGVhY2ggdHJhaXQgYWxvbmcgdGhlIGVudmlyb25tZW50YWwgZ3JhZGllbnQuCgpgYGB7ciBnZXQtbm9uLXF1YW50aS1jd219Cm5vbl9xdWFudGlfY3dtID0gRkQ6OmZ1bmN0Y29tcCh0cmFpdHNbLCAtYyg1OjcpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwX2NvbSwgQ1dNLnR5cGUgPSAiYWxsIikKbm9uX3F1YW50aV9jd20kcGxvdC5jb2RlID0gcm93bmFtZXMobm9uX3F1YW50aV9jd20pCgpub25fcXVhbnRpX2N3bSA9IG1lcmdlKAogIG5vbl9xdWFudGlfY3dtLAogIHBsb3RfZGF0YVssIGMoInBsb3QuY29kZSIsICJibG9jayIsICJmb3Jlc3Rsb3NzMTciLCAicm9hZGRlbnNwcmltIildLAogIGJ5ID0gInBsb3QuY29kZSIKKQpgYGAKCldlIHVzZWQgdGhlIHNhbWUgZnVuY3Rpb24gYXMgYWJvdmUgYGZ1bmN0Y29tcCgpYCB3aXRoIHRoZSBvcHRpb24gYENXTS50eXBlID0gImFsbCJgLiBUaGUgZnVuY3Rpb24gY29tcHV0ZXMgdGhlIHN1bSBvZiBiaW9tYXNzIG9mIGVhY2ggY2F0ZWdvcnkgZm9yIGNhdGVnb3JpY2FsIHRyYWl0cy4KCmBgYHtyIGNhdGVnb3JpY2FsLWN3bX0KcGFyKG1mcm93ID0gYygxLCAxKSkKcGxvdChub25fcXVhbnRpX2N3bSRmb3Jlc3Rsb3NzMTcsIG5vbl9xdWFudGlfY3dtJHdvb2R5X25vLAogICAgIHhsYWIgPSAiRm9yZXN0IGxvc3MgKCUpIiwgeWxhYiA9ICJTdW0gb2YgYmlvbWFzcyBvZiBub24td29vZHkgc3BlY2llcyIsCiAgICAgbWFpbiA9ICJCaW9tYXNzIG9mIG5vbi13b29keSBzcGVjaWVzIHZzLiBmb3Jlc3QgbG9zcyIpCmBgYAoKOjo6IHsucXVlc3Rpb25zfQojIyMjIFF1ZXN0aW9uIGZvciB5b3UKCiogKipRMTAqKjogSG93IGRvZXMgdGhpcyBvYnNlcnZhdGlvbiBjb21wYXJlIHRvIGFib3ZlIGRlc2NyaXB0aW9uIG9mIHRoZSBjaGFuZ2Ugb2YgdW5kZXJzdG9yZXkgdmVnZXRhdGlvbiBhbG9uZyB0aGUgZm9yZXN0IGxvc3MgZ3JhZGllbnQ/Cjo6OgoKIyMgQnVpbGRpbmcgdGhlIGZ1bmN0aW9uYWwgc3BhY2UKCkJlZm9yZSBjb21wdXRpbmcgdGhlIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IGluZGljZXMgd2UgbmVlZCBmaXJzdCB0byBwbGFjZSB0aGUgc3BlY2llcyBvbiBhIGZ1bmN0aW9uYWwgc3BhY2UuClRoZSB3YXkgdG8gZG8gaXMgdG8gdmlzdWFsaXplIHRoZSBzcGVjaWVzIGNsb3VkIG9udG8gdGhlIHN5bnRoZXRpYyBheGVzIHRoYXQgcmVwcmVzZW50IHRoZWlyIHRyYWl0IHZhbHVlcy4gQmVjYXVzZSB3ZSBjYW5ub3QgdmlzdWFsaXplIHRoYXQgZGlmZmVyZW50IHRyYWl0cyAob3VyIHZpc2lvbiBpcyBzdGlsbCBsaW1pdGVkIHRvIG9ubHkgMyBkaW1lbnNpb25zISkgd2UgbmVlZCB0byB1c2UgZGltZW5zaW9uIHJlZHVjdGlvbiB0ZWNobmlxdWVzIHN1Y2ggYXMgKlByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMqIChQQ0EpLiBEaW1lbnNpb24gcmVkdWN0aW9uIHRlY2huaXF1ZXMgY29tYmluZXMgdGhlIGRpZmZlcmVudCB2YXJpYWJsZXMgdG8gZ2l2ZSBzeW50aGV0aWMgYXhlcyBhY2NvdW50aW5nIGZvciB0aGUgY29ycmVsYXRpb25zIGJldHdlZW4gdGhlIGRpZmZlcmVudCBpbnB1dCB2YXJpYWJsZXMgQmVjYXVzZSB3ZSBoYXZlIGEgZGF0YXNldCB0aGF0IGNvbnRhaW4gYm90aCBjb250aW51b3VzIGFuZCBjYXRlZ29yaWNhbCB0cmFpdCBkYXRhLCB3ZSBjYW5ub3QgdXNlIFBDQSBhbmQgd2Ugd2lsbCBoYXZlIHRvIHVzZSBhIHNsaWdobHkgZGlmZmVyZW50IHN0YXRpc3RpY2FsIHRvb2wgY2FsbGVkICpQcmluY2lwYWwgQ29vcmRpbmF0ZXMgQW5hbHlzaXMqIChQQ29BLCBhbHNvIG5hbWVkIE1ldHJpYyBEaW1lbnNpb25hbCBTY2FsaW5nKSB0aGF0IGZvbGxvdyBzaW1pbGFyIHByaW5jaXBsZXMuCgpUbyBjb21wdXRlIHRoZSBQQ29BIHdlIGZpcnN0IG5lZWQgdG8gY29tcHV0ZSBhIGRpc3RhbmNlIG1hdHJpeCB0aGF0IGV4cHJlc3NlcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGVhY2ggcGFpciBvZiBzcGVjaWVzLiBCZWNhdXNlIHdlIGhhdmUgYSBtaXh0dXJlIG9mIGNvbnRpbnVvdXMgYW5kIGNhdGVnb3JpY2FsIHRyYWl0cywgd2UgY2Fubm90IHVzZSB0aGUgRXVjbGlkZWFuIGRpc3RhbmNlIGFuZCBoYXZlIHRvIHJlc29ydCB0byB1c2UgdGhlIEdvd2VyJ3MgZGlzc2ltaWxhcml0eSBtZXRyaWMgdGhyb3VnaCB0aGUgYGRhaXN5KClgIGZ1bmN0aW9uIHdpdGggdGhlIHBhY2thZ2UgYGNsdXN0ZXJgLiAKCmBgYHtyIGdvd2VyLWRpc3NpbX0KZ293ZXJfZGlzc2ltID0gY2x1c3Rlcjo6ZGFpc3kodHJhaXRzKQpgYGAKClRvIHBlcmZvcm0gdGhlIFBDb0Egd2Ugd2lsbCBiZSB1c2luZyB0aGUgYGFkZTRgIHBhY2thZ2Ugd2l0aCB0aGUgZnVuY3Rpb24gYGR1ZGkucGNvKClgOgoKYGBge3IgYWRlNC1wY29hfQp0cmFpdF9wY29hID0gYWRlNDo6ZHVkaS5wY28oYWRlNDo6cXVhc2lldWNsaWQoZ293ZXJfZGlzc2ltKSwgbmYgPSAzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2Nhbm5mID0gRkFMU0UpCnRyYWl0X3Bjb2EKYGBgCgpUaGUgYHRyYWl0X3Bjb2FgIG9iamVjdCBjb250YWlucyB0aGUgY29vcmRpbmF0ZXMgb2YgZWFjaCBzcGVjaWVzIGFsb25nIHRoZSBkaWZmZXJlbnQgUENvQSBheGVzICh3ZSBjaG9zZSA1IHRvIGhhdmUgYSBsaW1pdCkuCldlIGNhbiB2aXN1YWxpemUgdGhlIHJlc3VsdHMgd2l0aCB0aGUgZm9sbG93aW5nIGNvbW1hbmQ6CgpgYGB7ciB2aXN1YWxpemUtcGNvYX0KYWRlNDo6c2NhdHRlcih0cmFpdF9wY29hLCBjbGFiLnJvdyA9IDApCmBgYApXZSBzZWUgdHdvIHdlbGwgc2VwYXJhdGVkIGdyb3VwcyBpbmRpY2F0aW5nIHN0cm9uZyBkaWZmZXJlbmNlcyBhbG9uZyB0aGUgdHdvIGZpcnN0IGF4ZXMgb2YgdGhlIFBDb0EuIFdlIGNhbiB2aXN1YWxpemUgdGhlIG1lYW5pbmcgb2YgdGhlIGdyb3Vwcy4gV2UgY2FuIHRyeSB0byBiZXR0ZXIgdW5kZXJzdGFuZCB0aGlzIGdyb3VwIGJ5IGxvb2tpbmcgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0cmFpdHMgYWxvbmcgdGhlc2UgZ3JvdXBzOgoKYGBge3Igd29vZHktcGNvYX0KYWRlNDo6cy5jbGFzcyh0cmFpdF9wY29hJGxpWywxOjJdLCBmYWMgPSB0cmFpdHMkcGdmKQpgYGAKCjo6OiB7LnF1ZXN0aW9uc30KIyMjIyBRdWVzdGlvbnMgZm9yIHlvdQoKKiAqKlExMSoqOiBVc2luZyB0aGUgbWV0YWRhdGEgYXZhaWxhYmxlIGluIHRoZSBgUkVBRE1FLnR4dGAgZmlsZSwgd2hhdCBpcyB0aGUgbWVhbmluZyBvZiB0aGUgYHBnZmAgY29sdW1uPwoqICoqUTEyKio6IEhvdyBkbyB5b3UgaW50ZXJwcmV0IHRoZSBQQ29BIHJlc3VsdHMgZ2l2ZW4geW91ciBhbnN3ZXIgdG8gdGhlIHByZXZpb3VzIHF1ZXN0aW9uPwo6OjoKCiMjIENvbXB1dGluZyBmdW5jdGlvbmFsIGRpdmVyc2l0eSBpbmRpY2VzCgpOb3cgdGhhdCB3ZSBoYXZlIHNwZWNpZXMgcG9zaXRpb25lZCBpbiBhIG11bHRpZGltZW5zaW9uYWwgc3BhY2Ugd2UgY2FuIGFjdHVhbGx5IGNvbXB1dGUgZGlzdGluY3QgZnVuY3Rpb25hbCBkaXZlcnNpdHkgaW5kaWNlcy4gRm9yIHRoYXQgd2UnbGwgYmUgdXNpbmcgdGhlIGBmdW5kaXZlcnNpdHlgIHBhY2thZ2UgdGhhdCBvZmZlcnMgYm90aCBmbGV4aWJpbGl0eSBhbmQgY29uc2lzdGVuY3kgdG8gY29tcHV0ZSB0aGUgaW5kaWNlcy4KCldlIHdpbGwgZmlyc3QgY29tcHV0ZSBGdW5jdGlvbmFsIFJpY2huZXNzIChGUmljKSB3aXRoIHRoZSBgZmRfZnJpYygpYCBmdW5jdGlvbjoKCmBgYHtyIGZyaWN9CnNpdGVfZnJpYyA9IGZ1bmRpdmVyc2l0eTo6ZmRfZnJpYyh0cmFpdF9wY29hJGxpLCBzcF9jb20sIHN0YW5kID0gRkFMU0UpCmBgYAoKVGhlbiB3ZSB3aWxsIGFsc28gY29tcHV0ZSBSYW8ncyBRdWFkcmF0aWMgRW50cm9weSAoUmFvJ3MgUSkgYW5kIEZ1bmN0aW9uYWwgRXZlbm5lc3MgKEZFdmUpOgoKYGBge3IgZmV2ZS1yYW9xLCBvcHRpb25zfQpzaXRlX3Jhb3EgPSBmdW5kaXZlcnNpdHk6OmZkX3Jhb3EodHJhaXRfcGNvYSRsaSwgc3BfY29tKQpzaXRlX2ZldmUgPSBmdW5kaXZlcnNpdHk6OmZkX2ZldmUodHJhaXRfcGNvYSRsaSwgc3BfY29tKQoKc2l0ZV9mZCA9IG1lcmdlKAogIG1lcmdlKHNpdGVfZnJpYywgc2l0ZV9yYW9xLCBieSA9ICJzaXRlIiksCiAgc2l0ZV9mZXZlLAogIGJ5ID0gInNpdGUiCikKc2l0ZV9mZCRwbG90LmNvZGUgPSBzaXRlX2ZkJHNpdGUKc2l0ZV9mZCA9IHNpdGVfZmRbLCAtMV0KYGBgCgpXZSBjYW4gbm93IGNvbXBhcmUgdGhlIG9ic2VydmVkIHJlbGF0aW9uc2hpcCB3aXRoIGZvcmVzdCBsb3NzOgoKYGBge3IgZmQtZm9yZXN0bG9zc30Kc2l0ZV9lbnZfZmQgPSBtZXJnZShzaXRlX2ZkLAogICAgICAgICAgICAgICAgICAgIHBsb3RfZGF0YVssIGMoInBsb3QuY29kZSIsICJmb3Jlc3Rsb3NzMTciLCAicm9hZGRlbnNwcmltIildLAogICAgICAgICAgICAgICAgICAgIGJ5ID0gInBsb3QuY29kZSIpCgpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KHNpdGVfZW52X2ZkJGZvcmVzdGxvc3MxNywgc2l0ZV9lbnZfZmQkRlJpYywKICAgICB4bGFiID0gIkZvcmVzdCBsb3NzICglKSIsIHlsYWIgPSAiRnVuY3Rpb25hbCBSaWNobmVzcyAoRlJpYykiLAogICAgIG1haW4gPSAiRnVuY3Rpb25hbCBSaWNobmVzcyB2cy4gZm9yZXN0IGxvc3MiKQpwbG90KHNpdGVfZW52X2ZkJGZvcmVzdGxvc3MxNywgc2l0ZV9lbnZfZmQkUSwKICAgICB4bGFiID0gIkZvcmVzdCBsb3NzICglKSIsIHlsYWIgPSAiUmFvJ3MgUXVhZHJhdGljIEVudHJvcHkiLAogICAgIG1haW4gPSAiUSB2cy4gZm9yZXN0IGxvc3MiKQpwbG90KHNpdGVfZW52X2ZkJGZvcmVzdGxvc3MxNywgc2l0ZV9lbnZfZmQkRkV2ZSwKICAgICB4bGFiID0gIkZvcmVzdCBsb3NzICglKSIsIHlsYWIgPSAiRnVuY3Rpb25hbCBFdmVubmVzcyAoRkV2ZSkiLAogICAgIG1haW4gPSAiRkV2ZSB2cy4gZm9yZXN0IGxvc3MiKQpwbG90KHNpdGVfZW52X2ZkJHJvYWRkZW5zcHJpbSwgc2l0ZV9lbnZfZmQkRlJpYywKICAgICB4bGFiID0gIlByaW1hcnkgUm9hZCBEZW5zaXR5IChrbS5rbV4tMikiLCB5bGFiID0gIkZ1bmN0aW9uYWwgUmljaG5lc3MgKEZSaWMpIiwKICAgICBtYWluID0gIkZSaWMgdnMuIHJvYWQgZGVuc2l0eSIpCmBgYAoKOjo6IHsucXVlc3Rpb25zfQojIyMjIFF1ZXN0aW9ucyBmb3IgeW91CgoqICoqUTEzKio6IEhvdyB3b3VsZCB5b3UgZGVzY3JpYmUgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBmdW5jdGlvbmFsIGRpdmVyc2l0eSBhbmQgZm9yZXN0IGxvc3MgYW5kIHJvYWQgZGVuc2l0eT8KKiAqKlExNCoqOiBVc2luZyB0aGUgcGxvdCBnZW5lcmF0ZWQgYnkgdGhlIGNvZGUgYmVuZWF0aCBob3cgY291bGQgeW91IGRlc2NyaWJlIHRoZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gdGhlIHRocmVlIGRpZmZlcmVudCBmdW5jdGlvbmFsIGRpdmVyc2l0eSBpbmRpY2VzIHdlIGNvbXB1dGVkPwo6OjoKCmBgYHtyIHBhaXJzLWZ1bmRpdmVyc2l0eSwgb3B0aW9uc30KcGFuZWwuY29yID0gZnVuY3Rpb24oeCwgeSwgZGlnaXRzID0gMiwgcHJlZml4ID0gIiIsIGNleC5jb3IsIC4uLikKewogIHVzciA8LSBwYXIoInVzciIpOyBvbi5leGl0KHBhcih1c3IpKQogIHBhcih1c3IgPSBjKDAsIDEsIDAsIDEpKQogIHIgPC0gYWJzKGNvcih4LCB5LCB1c2UgPSAiY29tcGxldGUub2JzIikpCiAgdHh0IDwtIGZvcm1hdChjKHIsIDAuMTIzNDU2Nzg5KSwgZGlnaXRzID0gZGlnaXRzKVsxXQogIHR4dCA8LSBwYXN0ZTAocHJlZml4LCB0eHQpCiAgaWYobWlzc2luZyhjZXguY29yKSkgY2V4LmNvciA8LSAwLjgvc3Ryd2lkdGgodHh0KQogIHRleHQoMC41LCAwLjUsIHR4dCwgY2V4ID0gY2V4LmNvciAqIHIpCn0KCnBhaXJzKH5GRXZlICsgUSArIEZSaWMsIGRhdGEgPSBzaXRlX2Vudl9mZCwgbG93ZXIucGFuZWwgPSBwYW5lbC5zbW9vdGgsCiAgICAgIHVwcGVyLnBhbmVsID0gcGFuZWwuY29yLCBnYXAgPSAwLCByb3cxYXR0b3AgPSBGQUxTRSkKYGBgCk9uZSBpc3N1ZSB3ZSdyZSBoYXZpbmcgd2l0aCBvdXIgZnVuY3Rpb25hbCBkaXZlcnNpdHkgaW5kaWNlcyBpcyBhbHNvIHRoYXQgc29tZSBvZiB0aGVtIGNvcnJlbGF0ZSB3aXRoIHNwZWNpZXMgcmljaG5lc3M6CgpgYGB7ciBwYWlycy1mZC1yaWNobmVzc30Kc2l0ZV9yaWNoX2ZkID0gbWVyZ2UoCiAgc2l0ZV9mZCwKICBwbG90X2RhdGFbLCBjKCJwbG90LmNvZGUiLCAibnRheGEiKV0sCiAgYnkgPSAicGxvdC5jb2RlIgopCgpwYWlycyhudGF4YSB+IEZSaWMgKyBGRXZlICsgUSwgZGF0YSA9IHNpdGVfcmljaF9mZCwgdXBwZXIucGFuZWwgPSBwYW5lbC5jb3IpCmBgYAoKQmVjYXVzZSB3ZSBhcmUgdXNpbmcgaW5kaWNlcyBjb21wdXRlZCB3aXRoIGJpb21hc3MgdmFsdWVzIHRoZSBpbmRpY2VzIHNob3VsZCBiZSBtb3JlIHJlbGF0ZWQgdG8gdGhlIHRvdGFsIGJpb21hc3MgdmFsdWVzIHRoYW4gc3BlY2llcyByaWNobmVzcy4gTGV0J3MgZ2V0IHRoZSB0b3RhbCBiaW9tYXNzIHZhbHVlcyBwZXIgc2l0ZSBhbmQgY29ycmVsYXRlIGl0IHdpdGggZnVuY3Rpb25hbCBkaXZlcnNpdHkgaW5kaWNlcy4KCmBgYHtyIGJpb21hc3MtZmR9CnNpdGVfYmlvbWFzcyA9IHJvd1N1bXMoc3BfY29tKQpzaXRlX2Jpb21hc3MgPSBzdGFjayhzaXRlX2Jpb21hc3MpCgpzaXRlX2Jpb21hc3MkcGxvdC5jb2RlID0gc2l0ZV9iaW9tYXNzJGluZApzaXRlX2Jpb21hc3MkdG90X2Jpb21hc3MgICA9IHNpdGVfYmlvbWFzcyR2YWx1ZXMKCnNpdGVfYmlvbWFzcyA9IHNpdGVfYmlvbWFzc1ssIGMoInBsb3QuY29kZSIsICJ0b3RfYmlvbWFzcyIpXQoKc2l0ZV9yaWNoX2ZkID0gbWVyZ2UoCiAgc2l0ZV9yaWNoX2ZkLAogIHNpdGVfYmlvbWFzcywKICBieSA9ICJwbG90LmNvZGUiCikKCnBhaXJzKHRvdF9iaW9tYXNzIH4gRlJpYyArIEZFdmUgKyBRLCBkYXRhID0gc2l0ZV9yaWNoX2ZkLAogICAgICB1cHBlci5wYW5lbCA9IHBhbmVsLmNvcikKYGBgCgo6Ojogey5xdWVzdGlvbnN9CiMjIyMgUXVlc3Rpb24gZm9yIHlvdQoKKiAqKlExNSoqOiBIb3cgZG9lcyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gaW5kaWNlcyB3aXRoIHNwZWNpZXMgcmljaG5lc3MgY29tcGFyZSB3aXRoIHRoZSBvbmUgb2JzZXJ2ZWQgd2l0aCB0b3RhbCBiaW9tYXNzIHZhbHVlcz8gKFlvdSBjYW4gdXNlIHRoZSBmdW5jdGlvbiBgY29yLnRlc3QoKWAgaWYgeW91IHdhbnQgdG8gdGVzdCB0aGUgYXNzb2NpYXRpb24pCjo6OgoKIyMgTnVsbCBtb2RlbGxpbmcKClRoZSBwcmluY2lwbGUgb2YgbnVsbCBtb2RlbGxpbmcgaXMgdG8gY3JlYXRlIHJhbmRvbSBjb21tdW5pdGllcyBmb2xsb3dpbmcgY2VydGFpbiBydWxlcyB0byBnZXQgYW4gZXhwZWN0ZWQgZGlzdHJpYnV0aW9uIG9mIGRpdmVyc2l0eSBtZXRyaWNzIHdoaWxlIGtlZXBpbmcgc29tZSBwcm9wZXJ0aWVzIG9mIHRoZSBkYXRhIGNvbnN0YW50LiBJbiBvdXIgY2FzZSwgd2Uga25vdyB0aGF0IGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IGlzIGRpcmVjdGx5IGxpbmtlZCB0byB0aGUgbnVtYmVyIG9mIHNwZWNpZXMsIHNvIHdlIHdhbnQgdG8ga2VlcCB0aGUgc3BlY2llcyByaWNobmVzcyBjb25zdGFudCB3aGlsZSBjaGFuZ2luZyB0aGUgZGlzdHJpYnV0aW9uIG9mIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5LgoKQmVjYXVzZSB0aGUgc2l0ZS1zcGVjaWVzIG1hdHJpeCBjb250YWlucyBiaW9tYXNzIHZhbHVlcyB3aGljaCBhcmUgbm90IGRpc2NyZXRlLCB0aGUgY2xhc3NpY2FsIHN3YXBwaW5nIGFsZ29yaXRobXMgd2lsbCBub3Qgd29yayB0byBtYWludGFpbiB0b3RhbCBiaW9tYXNzIHBlciBzaXRlIGFuZCBzcGVjaWVzIG92ZXJhbGwgYmlvbWFzcy4gVGhlIHNvbHV0aW9uIGlzIHRoZW4gdG8gcGVyZm9ybSBhIG51bGwgbW9kZWwgYmFzZWQgb24gdHJhaXQgdmFsdWVzIG9ubHkuIEluIHRoaXMgd2F5IGl0IHdpbGwgZ2l2ZSB1cyBhIG51bGwgZGlzdHJpYnV0aW9uIG9mIHRyYWl0IHZhbHVlcyB3aGlsZSBtYWludGFpbmluZyB0aGUgc2FtZSByaWNobmVzcyBwZXIgcGxvdCBhbmQgdGhlIHNhbWUgcmVsYXRpdmUgYmlvbWFzcyBkaXN0cmlidXRpb24uCgpUbyBkbyBzbyB3ZSdsbCBzaHVmZmxlIHRoZSB0cmFpdCB0YWJsZSBhbG9uZyBzcGVjaWVzLiAqKkNhdXRpb24qKjogaW4gb3VyIGNhc2Ugd2UgZG8gbm90IHdhbnQgdG8gYnJlYWsgdGhlIGxpbmtzIHRoYXQgZXhpc3QgYmV0d2VlbiB0cmFpdCB2YWx1ZXMsIHNvIHdlIHdpbGwgYmUgc2h1ZmZsaW5nIGVudGlyZSByb3dzIG9mIHRyYWl0cyBhbmQgbm90IHRyYWl0IGluZGl2aWR1YWxseS4gVGhpcyB3b3VsZCByZXN1bHQgaW4gYSBkaWZmZXJlbnQgbnVsbCBtb2RlbCBvdGhlcndpc2UuCgpCZWNhdXNlIHdlIHdlcmUgdXNpbmcgdGhlIFBDb0EgYXhlcyBhcyBvdXIgInN5bnRoZXRpYyB0cmFpdHMiIGFib3ZlIHdlJ2xsIHBlcmZvcm0gdGhlIHNodWZmbGluZyBiZXR3ZWVuIHNwZWNpZXMgbmFtZXMgb24gdGhlc2UgUENvQSBheGVzLgoKYGBge3IgbnVsbC10cmFpdHN9CiMgU2V0IHJhbmRvbSBzZWVkIHNvIHRoYXQgZXZlcnlib2R5IGdldHMgdGhlIHNhbWUgbnVsbCB0cmFpdHMKc2V0LnNlZWQoMjAyMTA3MDUpCgojIE51bWJlciBvZiBudWxsIHNpbXVsYXRpb25zCiMgQ0FVVElPTjogaW5jcmVhc2luZyB0aGlzIG51bWJlciBtYXkgaW5jcmVhc2UgZnV0dXJlIGNvbXB1dGF0aW9uIHRpbWUgYnkgYSBsb3QKbl9udWxsID0gOTkKCiMgUmVwZWF0IHRoZSBvcGVyYXRpb24gYXMgbWFueSB0aW1lcyBhcyBzZXQgYWJvZXYKbnVsbF90cmFpdHMgPSBsYXBwbHkoc2VxLmludChuX251bGwpLCBmdW5jdGlvbih4KSB7CiAgbnVsbF90cmFpdCA9IHRyYWl0X3Bjb2EkbGkKICAKICAjIFNodWZmbGUgc3BlY2llcyBuYW1lcwogIG51bGxfc3BlY2llcyA9IHNhbXBsZShyb3duYW1lcyh0cmFpdF9wY29hJGxpKSwgbnJvdyh0cmFpdF9wY29hJGxpKSkKICAKICAjIFJlcGxhY2Ugc3BlY2llcyBuYW1lIGluIHRhYmxlCiAgcm93bmFtZXMobnVsbF90cmFpdCkgPSBudWxsX3NwZWNpZXMKICAKICAjIERvIG5vdCBmb3JnZXQgdG8gcmV0dXJuIHRoZSBtb2RpZmllZCB0YWJsZSEKICByZXR1cm4obnVsbF90cmFpdCkKfSkKCnN0cihudWxsX3RyYWl0cywgbWF4LmwgPSAwKQpoZWFkKG51bGxfdHJhaXRzW1sxXV0pCmBgYAoKV2Ugbm93IG9idGFpbiBhIGRpc3RyaWJ1dGlvbiBvZiBudWxsIHRyYWl0cyBvbiB3aGljaCB3ZSBzdGlsbCBuZWVkIHRvIGNvbXB1dGUgZnVuY3Rpb25hbCBkaXZlcnNpdHkgaW5kaWNlcy4gV2UnbGwgYXBwbHkgc2ltaWxhciBzdGVwcyBhcyBhYm92ZSB0byBwZXJmb3JtIHRoZSBmdW5jdGlvbmFsIGRpdmVyc2l0eSBjb21wdXRhdGlvbi4gQnV0IGluIHRoaXMgY2FzZSB3ZSdsbCBoYXZlIHRvIGFwcGx5IHRoZSBzdGVwIGZvciBlYWNoIGRpc3RyaWJ1dGlvbiBvZiBudWxsIHRyYWl0LgoKYGBge3IgbnVsbC1mZH0KIyBCZXdhcmUgdGhpcyBtYWtlIHRha2UgYSBsb25nIHRpbWUKbnVsbF9mZCA9IGxhcHBseShzZXEobGVuZ3RoKG51bGxfdHJhaXRzKSksIGZ1bmN0aW9uKHkpIHsKICAKICB4ID0gbnVsbF90cmFpdHNbW3ldXQogIAogIG51bGxfZnJpYyA9IGZ1bmRpdmVyc2l0eTo6ZmRfZnJpYyh4LCBzcF9jb20sIHN0YW5kID0gRkFMU0UpCiAgbnVsbF9yYW9xID0gZnVuZGl2ZXJzaXR5OjpmZF9yYW9xKHgsIHNwX2NvbSkKICBudWxsX2ZldmUgPSBmdW5kaXZlcnNpdHk6OmZkX2ZldmUoeCwgc3BfY29tKQogIAogICMgQ29tYmluZSBhbGwgbnVsbCBmdW5jdGlvbmFsIGRpdmVyc2l0eSB2YWx1ZXMKICBudWxsX2FsbCA9IG1lcmdlKAogICAgbWVyZ2UobnVsbF9mcmljLCBudWxsX3Jhb3EsIGJ5ID0gInNpdGUiKSwgbnVsbF9mZXZlLCBieSA9ICJzaXRlIgogICkKICAKICAjIE51bGwgSW5kZXggdG8gc2VwYXJhdGUgYmV0d2VlbiBhbGwgbnVsbCBzaW11bGF0aW9ucwogIG51bGxfYWxsJG51bGxfaWQgPSB5CiAgCiAgcmV0dXJuKG51bGxfYWxsKQp9KQoKbnVsbF9mZF9hbGwgPSBkby5jYWxsKHJiaW5kLmRhdGEuZnJhbWUsIG51bGxfZmQpCmhlYWQobnVsbF9mZF9hbGwpCmBgYAoKV2Ugbm93IG9ic2VydmUgYSBsaXN0IG9mIG51bGwgZnVuY3Rpb25hbCBkaXZlcnNpdHkgbWV0cmljcyBmb3IgZWFjaCBzaXRlLgpCZWNhdXNlIGNvbXB1dGluZyBmdW5jdGlvbmFsIGRpdmVyc2l0eSBvbiBudWxsIHRyYWl0cyBpcyBjb21wdXRhdGlvbmFsbHkgaW50ZW5zaXZlLCBydW5uaW5nIG1vcmUgc2ltdWxhdGlvbnMgY2FuIHRha2UgYSBsb25nIHRpbWUuIFdlJ3ZlIGluY2x1ZGVkIGEgdmVyc2lvbiBvZiB0aGUgbnVsbCBmdW5jdGlvbmFsIGRpdmVyc2l0eSB2YWx1ZXMgd2l0aCA5OTkgc2ltdWxhdGlvbnMgaW4gdGhlIGBkYXRhL2AgZm9sZGVyLiBXZSdyZSBub3cgZ29pbmcgdG8gdXNlIHRoaXMgcHJlY29tcHV0ZWQgdmVyc2lvbiB0byBnZXQgYSBiZXR0ZXIgYXBwcm94aW1hdGlvbiBvZiB0aGUgZXhwZWN0ZWQgZGlzdHJpYnV0aW9uIHVuZGVyIHRoZSBudWxsIGh5cG90aGVzaXMuCgpgYGB7ciBudWxsLWZkLTk5OX0KbnVsbF9mZF85OTkgPSByZWFkUkRTKCJkYXRhL251bGxfZmRfOTk5LlJkcyIpCgpoZWFkKG51bGxfZmRfOTk5KQpgYGAKCldpdGggdGhpcyBudWxsIGRpc3RyaWJ1dGlvbiB3ZSBjYW4gbm93IGNvbXBhcmUgdGhlIG9ic2VydmVkIHZhbHVlcyBvZiBmdW5jdGlvbmFsIGRpdmVyc2l0eSB3aXRoIHRoZSBudWxsIG9uZXMuIExldCdzIGZvciBleGFtcGxlIGZvY3VzIG9uIHRoZSBzaXRlIGAiYTEwMGYxNzdyImA6CgpgYGB7ciBudWxsLWZkLWNvbXB9CiMgVGhlIG9ic2VydmVkIHZhbHVlIG9mIEZSaWMgZm9yIHRoZSBzaXRlCnN1YnNldChzaXRlX2ZkLCBwbG90LmNvZGUgPT0gImExMDBmMTc3ciIpJEZSaWMKCiMgVGhlIG51bGwgZGlzdHJpYnV0aW9uIG9mIEZSaWMgZm9yIHRoZSBzYW1lIHNpdGUKc3VtbWFyeShzdWJzZXQobnVsbF9mZF85OTksIHNpdGUgPT0gImExMDBmMTc3ciIpJEZSaWMpCmBgYAoKV2UgY2FuIHZpc3VhbGl6ZSB0aGlzIGNvbXBhcmlzb24gd2l0aCBhbiBoaXN0b2dyYW06CgpgYGB7ciBoaXN0LW51bGwtZnJpY30KcGFyKG1mcm93ID0gYygxLCAxKSkKIyBWaXN1YWxpemUgaGlzdG9ncmFtIG9mIG51bGwgdmFsdWVzCmhpc3Qoc3Vic2V0KG51bGxfZmRfOTk5LCBzaXRlID09ICJhMTAwZjE3N3IiKSRGUmljLAogICAgIGJyZWFrcyA9IDIwLAogICAgIHhsYWIgPSAibnVsbCBGdW5jdGlvbmFsIFJpY2huZXNzIiwKICAgICB5bGFiID0gIkZyZXF1ZW5jeSIsCiAgICAgbWFpbiA9ICJGUmljIGNvbXBhcmlzb24gZm9yIHNpdGUgJ2ExMDBmMTc3ciciKQphYmxpbmUodiA9IHN1YnNldChzaXRlX2ZkLCBwbG90LmNvZGUgPT0gImExMDBmMTc3ciIpJEZSaWMsCiAgICAgICBjb2wgPSAiZGFya3JlZCIsIGx3ZCA9IDIpCmBgYAoKOjo6IHsucXVlc3Rpb25zfQojIyMjIFF1ZXN0aW9uIGZvciB5b3UKCiogKipRMTYqKjogSG93IHdvdWxkIGRlc2NyaWJlIHZlcmJhbGx5IHRoZSBwb3NpdGlvbiBvZiB0aGUgb2JzZXJ2ZWQgdmFsdWUgb2YgRlJpYyBmb3Igc2l0ZSAiYTEwMGYxNzdyIiBjb21wYXJlZCB0byB0aGUgbnVsbCBkaXN0cmlidXRpb24/Cjo6OgoKVG8gZ2V0IGEgcHJvcGVyIGVzdGltYXRlIG9mIHRoZSByZWxhcnRpdmUgcG9zaXRpb24gb2YgdGhlIG9ic2VydmVkIHZhbHVlIGNvbXBhcmVkIHRvIHRoZSBudWxsIGRpc3RyaWJ1dGlvbiB3ZSBoYXZlIHRvIGJ1aWxkIHRoZSBFbXBpcmljYWwgQ3VtdWxhdGl2ZSBEaXN0cmlidXRpb24gRnVuY3Rpb24gKEVDREYpIHRoYXQgd2lsbCBnaXZlIHVzIHRoZSBleGFjdCBxdWFudGlsZSBvZiB0aGUgb2JzZXJ2ZWQgdmFsdWUuIFdlIHdpbGwgZG8gc28gd2l0aCB0aGUgYGVjZGYoKWAgZnVuY3Rpb246CgpgYGB7ciBlY2RmLW9uZS1zaXRlfQojIEJ1aWxkIHRoZSBFQ0RGCm9uZV9udWxsX2ZyaWNfZWNkZiA9IGVjZGYoc3Vic2V0KG51bGxfZmRfOTk5LCBzaXRlID09ICJhMTAwZjE3N3IiKSRGUmljKQoKIyBUaGVuIGFjdHVhbGx5IHVzZSBpdApvYnNfZnJpYyA9IHN1YnNldChzaXRlX2ZkLCBwbG90LmNvZGUgPT0gImExMDBmMTc3ciIpJEZSaWMKCm9uZV9udWxsX2ZyaWNfZWNkZihvYnNfZnJpYykKYGBgCgo6Ojogey5xdWVzdGlvbnN9CiMjIyMgUXVlc3Rpb24gZm9yIHlvdQoKKiAqKlExNyoqOiBXaGF0J3MgdGhlIHF1YW50aWxlIG9mIHRoZSBvYnNlcnZlZCBGUmljIHZhbHVlIGluIHRoZSBlbmQ/Cjo6OgoKVGhpcyBnaXZlcyB1cyBhbiBlbXBpcmljYWwgY29tcGFyaXNvbiBvZiB0aGUgb2JzZXJ2ZWQgdmFsdWUgd2l0aCB0aGUgbnVsbCBkaXN0cmlidXRpb24uIEhvd2V2ZXIsIGluIG1hY3JvLWVjb2xvZ3kgd2UgcHJlZmVyIHRvIGV2ZW4gc3RhbmRhcmRpemUgZnVydGhlciB0aHJvdWdoIHRoZSB1c2Ugb2YgU3RhbmRhcmRpemVkIEVmZmVjdCBTaXplcyAoU0VTKS4gQXMgaXQgaXMgZG9uZSBpbiB0aGUgYXJ0aWNsZSB3ZSBhcmUgdXNpbmcgZm9yIG91ciBhbmFseXNlcy4gVGhlc2UgYXJlIHNpbXBsZXIgdG8gY29tcHV0ZSB0aGFuIEVDREYgYW5kIHNpbXBsaWZ5IHRoZSBpbnRlcnByZXRhdGlvbi4gU0VTcyBhcmUgY29tcHV0ZWQgaW4gdGhlIGZvbGxvd2luZyB3YXk6CgokJApTRVNfaSA9IFxmcmFje1xvdmVybGluZXt5X3tcdGV4dHtudWxsfSwgaX19IC0geV97XHRleHR7b2JzfSwgaX19e1x0ZXh0e1NEfV97XHRleHR7bnVsbH0sIGl9fQokJAp3aXRoICRTRVNfaSQgdGhlIHN0YW5kYXJkaXplZCBlZmZlY3Qgc2l6ZSBvZiB0aGUgaW5kZXggYXQgc2l0ZSAkaSQsICRcb3ZlcmxpbmV7eV97XHRleHR7bnVsbH0sIGl9fSQgdGhlIGF2ZXJhZ2Ugb2JzZXJ2ZWQgdmFsdWUgYWxvbmcgdGhlIG51bGwgZGlzdHJpYnV0aW9uIG9mIHRoZSBpbmRleCBhdCBzaXRlICRpJCwgJHlfe1x0ZXh0e29ic30sIGl9JCwgYW5kICRcdGV4dHtTRH1fe1x0ZXh0e251bGx9LCBpfSQgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgbnVsbCBkaXN0cmlidXRpb24gb2YgdGhlIGluZGV4IGF0IHNpdGUgJGkkLiBUaGlzIGluZGV4IGlzIG5lZ2F0aXZlIHdoZW4gdGhlIG9ic2VydmF0aW9uIGlzIHNtYWxsZXIgdGhhbiB0aGUgYXZlcmFnZSBvZiB0aGUgbnVsbCBkaXN0cmlidXRpb24sIGFuZCBwb3NpdGl2ZSBvdGhlcndpc2UuIEluIHRoZSBsaXRlcmF0dXJlIGFuIFNFUyB2YWx1ZSB1bmRlciAtMiBvciBhYm92ZSAyIGlzIGdlbmVyYWxseSBjb25zaWRlcmVkIGFzIHNpZ25pZmljYW50LgoKKipIb3dldmVyKiosIG5vdGUgdGhhdCB0aGVyZSBhcmUgY29udHJvdmVyc2llcyBpbiB0aGUgbGl0ZXJhdHVyZSBhYm91dCB0aGUgdXNlIG9mIFNFU3MgY29tcGFyZWQgdG8gdGhlIHVzZSBvZiB0aGUgRUNERiBiZWNhdXNlIHdlJ3JlIG9ubHkgbGV2ZXJhZ2luZyBvbiB0aGUgdXNlIG9mIHRoZSBtZWFuIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIG51bGwgZGlzdHJpYnV0aW9uIGluc3RlYWQgb2YgdXNpbmcgdGhlIGVudGlyZXR5IG9mIHRoZSBkaXN0cmlidXRpb24uCgpOb3cgd2UgbmVlZCB0byBjb21wdXRlIHRoZSBhdmVyYWdlIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIG51bGwgZGlzdHJpYnV0aW9uIGZvciBlYWNoIGluZGV4IGFuZCBlYWNoIHNpdGUuIFdlIHdpbGwgZG8gc28gdXNpbmcgdGhlIGBhZ2dyZWdhdGUoKWAgZnVuY3Rpb24uCgpgYGB7ciBmZC1zZXMtYWdncmVnYXRlfQojIENvbXB1dGUgYXZlcmFnZSBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIG51bGwgZGlzdHJpYnV0aW9uCm1lYW5fbnVsbF9mZCA9IGFnZ3JlZ2F0ZSgKICBjYmluZChtZWFuX0ZSaWMgPSBGUmljLCBtZWFuX1EgPSBRLCBtZWFuX0ZFdmUgPSBGRXZlKSB+IHNpdGUsCiAgZGF0YSA9IG51bGxfZmRfOTk5LCBGVU4gPSBtZWFuLCBuYS5ybSA9IFRSVUUKKQpzZF9udWxsX2ZkICAgPSBhZ2dyZWdhdGUoCiAgY2JpbmQoc2RfRlJpYyA9IEZSaWMsIHNkX1EgPSBRLCBzZF9GRXZlID0gRkV2ZSkgfiBzaXRlLCBkYXRhID0gbnVsbF9mZF85OTksCiAgRlVOID0gc2QsIG5hLnJtID0gVFJVRQopCgojIE1lcmdlIG51bGwgbWVhbiAmIHNkIHdpdGggb2JzZXJ2ZWQgdmFsdWVzCm9ic19udWxsX2ZkID0gbWVyZ2UoCiAgc2l0ZV9mZCwKICBtZXJnZShtZWFuX251bGxfZmQsIHNkX251bGxfZmQsIGJ5ID0gInNpdGUiKSwKICBieS54ID0gInBsb3QuY29kZSIsIGJ5LnkgPSAic2l0ZSIKKQoKIyBDb21wdXRlIFNFUwpvYnNfbnVsbF9mZCRzZXNfRlJpYyA9IChvYnNfbnVsbF9mZCRtZWFuX0ZSaWMgLSBvYnNfbnVsbF9mZCRGUmljKS9vYnNfbnVsbF9mZCRzZF9GUmljCm9ic19udWxsX2ZkJHNlc19RID0gKG9ic19udWxsX2ZkJG1lYW5fUSAtIG9ic19udWxsX2ZkJFEpL29ic19udWxsX2ZkJHNkX1EKb2JzX251bGxfZmQkc2VzX0ZFdmUgPSAob2JzX251bGxfZmQkbWVhbl9GRXZlIC0gb2JzX251bGxfZmQkRkV2ZSkvb2JzX251bGxfZmQkc2RfRkV2ZQoKIyBDbGVhbmVyIHRhYmxlCnNlc19mZCA9IG9ic19udWxsX2ZkWywgYygicGxvdC5jb2RlIiwgIkZSaWMiLCAiUSIsICJGRXZlIiwgInNlc19GUmljIiwgInNlc19RIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJzZXNfRkV2ZSIpXQpgYGAKCjo6OiB7LnF1ZXN0aW9uc30KIyMjIyBRdWVzdGlvbiBmb3IgeW91CgoqICoqUTE4Kio6IFVzaW5nIHRoZSBgc3Vic2V0KClgIGZ1bmN0aW9uIHdpdGggdGhlIGdyZWF0ZXIgKG9yIGVxdWFsKSB0aGFuIGA+PWAgYW5kIHRoZSBsb3dlciAob3IgZXF1YWwpIHRoYW4gYDw9YCwgY2FuIHlvdSBkZXRlcm1pbmUgaG93IG1hbnkgc2l0ZXMgc2hvdyBhIHNpZ25pZmljYW50IGRldmlhdGlvbiBmcm9tIHRoZSBudWxsIG9ic2VydmF0aW9uPyAoYWJzb2x1dGUgU0VTID49IDIpCiogKipRMTkqKjogVXNpbmcgc2ltaWxhciBjb2RlIGFzIHVzZWQgZm9yIG9ic2VydmVkIHZhbHVlcywgd2hhdCBhcmUgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBTRVMgdmFsdWVzIGFuZCBmb3Jlc3QgbG9zcz8KOjo6CgojIyBNYXBwaW5nIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5CgpPbmUgb2YgdGhlIGpveSBvZiBkb2luZyBtYWNyby1lY29sb2d5IGlzIHRvIHdvcmsgd2l0aCBzcGF0aWFsIGRhdGEuIFNwYXRpYWwgZGF0YSBtZWFucyB0aGF0IHdlIGhhdmUgdG8gZHJhdyBtYXBzIGFuZCB0aGlzIGNhbiBoZWxwIHVuY292ZXIgc3RydWN0dXJlcyBpbiBvdXIgZGF0YS4gSW4gdGhpcyBzZWN0aW9uIG9mIHRoZSB0dXRvcmlhbCB3ZSdyZSBnb2luZyB0byB1c2UgYm90aCB0aGUgb2JzZXJ2ZWQgYW5kIFNFUyBmdW5jdGlvbmFsIGRpdmVyc2l0eSBpbmRpY2VzIHRvIGRyYXcgbWFwcyBhbmQgY29tcGFyZSB0aGVtIHRvIG1hcHMgb2Ygc3BlY2llcyByaWNobmVzcyB0byB2aXN1YWxpemUgdGhlIGdlb2dyYXBoaWNhbCBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGFzZXQuIFdlIHdlJ2xsIGJlIHVzaW5nIHRoZSBwYWNrYWdlcyBgc2ZgIGZvciBjcmVhdGluZyBhbmQgbWFuaXB1bGF0aW5nIHNwYXRpYWwgZGF0YSwgYHJuYXR1cmFsZWFydGhgIHRvIGdldCBiYWNrZ3JvdW5kIG1hcHMsIGFuZCBgZ2dwbG90MmAgdG8gc2hvdyB0aGVtLiAqKk5vdGEgQmVuZSoqOiBUaGUgZ29hbCBvZiB0aGlzIHBhcnRpY3VsYXIgc2VjdGlvbiBpcyB0byBtYWtlIG5pY2UgdmlzdWFsaXphdGlvbnMgb2Ygb3VyIGRhdGEgYW5kIHNlZSBwb3RlbnRpYWwgc3RydWN0dXJlLCBpdCBpcyBub3QgdG8gdGVhY2ggdGhlIHBhcnRpY3VsYXIgY29uY2VwdCBhcm91bmQgc3BhdGlhbCBkYXRhIGFuZCBzcGF0aWFsIHZpc3VhbGl6YXRpb24gdGhhdCBoYXZlIHRoZWlyIG93biBjaGFsbGVuZ2VzLiBJZiB5b3UgaGFkIHRyb3VibGUgaW5zdGFsbGluZyB0aGUgYHNmYCBwYWNrYWdlIHdoaWNoIG1heSBiZSBxdWl0ZSBjYXByaWNpb3VzIG9yIGlmIHlvdSBmZWVsIGxvc3QgaW4gdGhlIG1lYW5pbmcgb2YgdGhlIGNvZGUgb2YgdGhpcyBzZWN0aW9uLCBpdCdzIGZpbmUsIHlvdSBjYW4gc2tpcCBpdC4KCkxvb2tpbmcgYmFjayBhdCB0aGUgcGxvdCBsZXZlbCBkYXRhIHdlIGhhdmUgdGhlIGNvb3JkaW5hdGVzIG9mIHRoZSBwbG90IGluIFVUTSBjb29yZGluYXRlczoKCmBgYHtyIHBsb3QtY29vcmR9CmhlYWQocGxvdF9kYXRhWywgYygxLCA0LCA1KV0pCgpwbG90X3NmID0gc2Y6OnN0X2FzX3NmKAogIHBsb3RfZGF0YVssIGMoMTo3KV0sCiAgY29vcmRzID0gYygibm9ydGgiLCAiZWFzdCIpLAogIGNycyA9IHNmOjpzdF9jcnMoIitwcm9qPXV0bSArem9uZT01MCArZGF0dW09V0dTODQgK3VuaXRzPW0gK25vX2RlZnMiKQopCmBgYAoKV2UgY2FuIHJlcHJlc2VudCBhIGJhc2ljIG1hcCB0byBzZWUgdGhlIGxvY2F0aW9uIG9mIHRoZSBwbG90IGF0IHdvcmxkIHNjYWxlOgoKYGBge3Igd29ybGQtbWFwfQpsaWJyYXJ5KCJnZ3Bsb3QyIikKCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBybmF0dXJhbGVhcnRoOjpuZV9jb3VudHJpZXMocmV0dXJuY2xhc3MgPSAic2YiKSkgKwogIGdlb21fc2YoZGF0YSA9IHBsb3Rfc2YsIGFlcyhjb2xvciA9IGZvcmVzdGxvc3MxNykpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArCiAgY29vcmRfc2YoY3JzID0gc2Y6OnN0X2NycygiK3Byb2o9ZWNrNCIpKSArICAjIFNldCBwcm9qZWN0aW9uCiAgbGFicyh0aXRsZSA9ICJNYXAgb2YgdGhlIGNvbmNlcm5lZCBwbG90cyBhdCB3b3JsZCBzY2FsZSIpICsKICB0aGVtZV9idygpCmBgYAoKV2Ugc2VlIHRoYXQgYWxsIG9mIG91ciBwbG90cyBhcmUgaW5kZWVkIGluIE1hbGF5c2lhIHNvIHdlIGNhbiBmb2N1cyB0aGVyZToKCmBgYHtyIG1hbGF5c2lhLW1hcH0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHJuYXR1cmFsZWFydGg6Om5lX2NvdW50cmllcyhjb250aW5lbnQgPSAiQXNpYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybmNsYXNzID0gInNmIikpICsKICBnZW9tX3NmKGRhdGEgPSBwbG90X3NmLCBhZXMoY29sb3IgPSBmb3Jlc3Rsb3NzMTcpKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIGNvb3JkX3NmKGNycyA9IHNmOjpzdF9jcnMoMzM3NiksIHhsaW0gPSBjKC0xMDcyMDI1LjgzLCAxMDUzNDQ2LjAwKSwKICAgICAgICAgICB5bGltID0gYyg4NTQ5Ni40MywgNzY3NzUyLjQxKSkgKwogIGxhYnModGl0bGUgPSAiTWFwIG9mIHBsb3RzIGZvY3VzZWQgb24gTWFsYXlzaWEiKSArCmdnc3BhdGlhbDo6YW5ub3RhdGlvbl9zY2FsZSgpICsKICB0aGVtZV9idygpCmBgYAoKV2UgY2FuIGV2ZW4gem9vbSBldmVuIG1vcmUgb250byB0aGUgcGxvdHMgdG8gc2VlIHRoZW0gYmV0dGVyOgoKYGBge3Igem9vbS1tYXB9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBybmF0dXJhbGVhcnRoOjpuZV9jb3VudHJpZXMoY291bnRyeSA9ICJNYWxheXNpYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybmNsYXNzID0gInNmIikpICsKICBnZW9tX3NmKGRhdGEgPSBwbG90X3NmLCBhZXMoY29sb3IgPSBmb3Jlc3Rsb3NzMTcpKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIGNvb3JkX3NmKGNycyA9IHNmOjpzdF9jcnMoMzM3NiksIHhsaW0gPSBjKDgwMDAwMCwgODkwMDAwKSwKICAgICAgICAgICB5bGltID0gYyg1MDAwMDAsIDU1MDAwMCkpICsKICBsYWJzKHRpdGxlID0gIk1hcCBvZiBwbG90cyB6b29tZWQtaW4gb24gU2FiYWggcmVnaW9uIikgKwogIGdnc3BhdGlhbDo6YW5ub3RhdGlvbl9zY2FsZSgpICsKICBnZ3NwYXRpYWw6OmFubm90YXRpb25fbm9ydGhfYXJyb3cobG9jYXRpb24gPSAiYnIiKSArCiAgdGhlbWVfYncoKQpgYGAKCldlIGNhbiBldmVuIGFkZCBiYWNrZ3JvdW5kIGluZm9ybWF0aW9uIHRvIGJldHRlciBkaXN0aW5ndWlzaCB0aGUgcGxvdHMgaW4gY29udGV4dCAoYmV3YXJlIHRoaXMgd2lsbCBkb3dubG9hZCBtYXAgdGlsZXMgZnJvbSB0aGUgaW50ZXJuZXQpOgoKYGBge3IgY29udGV4dC1tYXB9CmdncGxvdCgpICsKICBnZ3NwYXRpYWw6OmFubm90YXRpb25fbWFwX3RpbGUoem9vbWluID0gLTEpICsKICBnZW9tX3NmKGRhdGEgPSBwbG90X3NmLCBhZXMoY29sb3IgPSBmb3Jlc3Rsb3NzMTcpKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIGNvb3JkX3NmKGNycyA9IHNmOjpzdF9jcnMoMzM3NiksIHhsaW0gPSBjKDgwMDAwMCwgODkwMDAwKSwKICAgICAgICAgICB5bGltID0gYyg1MDAwMDAsIDU1MDAwMCkpICsKICBsYWJzKHRpdGxlID0gIk1hcCBvZiBwbG90cyB6b29tZWQtaW4gb24gU2FiYWggcmVnaW9uIikgKwogIGdnc3BhdGlhbDo6YW5ub3RhdGlvbl9zY2FsZSgpICsKICBnZ3NwYXRpYWw6OmFubm90YXRpb25fbm9ydGhfYXJyb3cobG9jYXRpb24gPSAiYnIiKSArCiAgdGhlbWVfYncoKQpgYGAKCkJlY2F1c2Ugb2YgdGhlIGdyb3VwIG9mIHBsb3RzIG9uIHRoZSBXZXN0IHdlIGNhbid0IGNsZWFybHkgc2VlIHRoZSBkaXN0aW5jdGlvbiBiZXR3ZWVuIHBsb3RzIGxldCdzIGZvY3VzIG9uIHRoZSBvbmVzIHRoYXQgc2hvdyBhIGdyYWRpZW50IGluIGZvcmVzdCBsb3NzOgoKYGBge3IgY29udGV4dC1tYXAtMn0KZ2dwbG90KCkgKwogIGdnc3BhdGlhbDo6YW5ub3RhdGlvbl9tYXBfdGlsZSh6b29taW4gPSAtMSkgKwogIGdlb21fc2YoZGF0YSA9IHN1YnNldChwbG90X3NmLCBibG9jayAhPSAib2ciKSwKICAgICAgICAgIGFlcyhjb2xvciA9IGZvcmVzdGxvc3MxNykpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArCiAgY29vcmRfc2YoY3JzID0gc2Y6OnN0X2NycygzMzc2KSwgeGxpbSA9IGMoODc1MDAwLCA4OTAwMDApLAogICAgICAgICAgIHlsaW0gPSBjKDUxODUwMCwgNTMxMDAwKSkgKwogIGxhYnModGl0bGUgPSAiTWFwIG9mIGFsbCBwbG90cyBidXQgYmxvY2sgJ29nJyIpICsKICBnZ3NwYXRpYWw6OmFubm90YXRpb25fc2NhbGUoKSArCiAgZ2dzcGF0aWFsOjphbm5vdGF0aW9uX25vcnRoX2Fycm93KGxvY2F0aW9uID0gImJyIikgKwogIHRoZW1lX2J3KCkKYGBgCgpBbmQgd2UgY2FuIG5vdyB2aXN1YWxpemUgdGhlIG1hcCBvZiB0aGUgU0VTIG9mIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IGluZGljZXMKCmBgYHtyIGZkLW1hcH0KZ2dwbG90KCkgKwogIGdlb21fc2YoCiAgICBkYXRhID0gbWVyZ2Uoc3Vic2V0KHBsb3Rfc2YsIGJsb2NrICE9ICJvZyIpLCBzZXNfZmQsIGJ5ID0gInBsb3QuY29kZSIpLAogICAgYWVzKGNvbG9yID0gc2VzX1EpCiAgKSArCiAgc2NhbGVfY29sb3JfZGlzdGlsbGVyKHR5cGUgPSAiZGl2IiwgcGFsZXR0ZSA9ICJSZFlsQnUiLAogICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gICJTRVMgb2YgUmFvJ3MgUXVhZHJhdGljIEVudHJvcHkiKSArCiAgY29vcmRfc2YoY3JzID0gc2Y6OnN0X2NycygzMzc2KSwgeGxpbSA9IGMoODc1MDAwLCA4OTAwMDApLAogICAgICAgICAgIHlsaW0gPSBjKDUxODUwMCwgNTMxMDAwKSkgKwogIGxhYnModGl0bGUgPSAiTWFwIG9mIGFsbCBwbG90cyBidXQgYmxvY2sgJ29nJyIpICsKICBnZ3NwYXRpYWw6OmFubm90YXRpb25fc2NhbGUoKSArCiAgZ2dzcGF0aWFsOjphbm5vdGF0aW9uX25vcnRoX2Fycm93KGxvY2F0aW9uID0gImJyIikgKwogIHRoZW1lX2dyYXkoKQoKYGBgCgpFdmVuIHdpdGggYWxsIHRoaXMgZWZmb3J0IGl0IGlzIG5vdCBjbGVhciBob3cgdGhlIFNFUyB2YXJpZXMgYmV0d2VlbiBzaXRlcy4gQnV0IGF0IGxlYXN0IHlvdSdyZSBtb3JlIGluZm9ybWVkIGFib3V0IHdoZXJlIHRoZSBkYXRhIHdlJ3JlIHN0dWR5aW5nIGNvbWVzIGZyb20uCgpOb3cgdGhhdCB3ZSBjb21wdXRlZCBmdW5jdGlvbmFsIGRpdmVyc2l0eSwgaXRzIFNFUywgYW5kIHB1dCBpdCBvbiB0aGUgbWFwLiBXZSBjYW4gcHJvY2VlZCBzaW1pbGFybHkgd2l0aCBwaHlsb2dlbmV0aWMgZGl2ZXJzaXR5LiBGb3IgdGhpcyB3aG9sZSBzZWN0aW9uIHdlIHdpbGwgdXNlIHRoZSBgYXBlYCBwYWNrYWdlIHRvIG1hbmlwdWxhdGUgcGh5bG9nZW5ldGljIHRyZWVzIGFuZCB0aGUgYHBpY2FudGVgIHBhY2thZ2UgdG8gY29tcHV0ZSBwaHlsb2dlbmV0aWMgZGl2ZXJzaXR5IGluZGljZXMuCgojIyBHZXR0aW5nIHRoZSBwaHlsb2dlbmV0aWMgdHJlZQoKV2UgaW5jbHVkZWQgYSBjb3B5IG9mIHRoZSBwaHlsb2dlbmV0aWMgdHJlZSB1c2VkIGluIHRoZSBhcnRpY2xlIChpdCBpcyBnaXZlbiBpbiB0aGUgU3VwcGxlbWVudGFyeSBJbmZvcm1hdGlvbikuIEl0IGlzIG5hbWVkIGBwaHlsb190cmVlLm53a2AgaW4gdGhlIGBkYXRhL2AgZm9sZGVyLgoKYGBge3IgZG93bmxvYWQtdHJlZSwgZWNobyA9IEZBTFNFLCBldmFsID0gVFJVRX0KZG93bmxvYWR0aGlzOjpkb3dubG9hZF9saW5rKAogIGxpbmsgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL1Jla3l0L2Jpb2RpdmVyc2l0eV9mYWNldHNfdHV0b3JpYWwvMjY2YmJlYzYxMGY1NTUyNWQyZWM4ZDM2YjNmYmY5NzhjZmZhN2FhNC9kYXRhL2RvaV8xMC41MDYxX2RyeWFkLmY3N3A3X192MS9waHlsb190cmVlLm53ayIsCiAgYnV0dG9uX2xhYmVsID0gIkRvd25sb2FkIFBoeWxvZ2VuZXRpYyBUcmVlIiwKICBidXR0b25fdHlwZSA9ICJkYW5nZXIiLAogIGhhc19pY29uID0gVFJVRSwKICBpY29uID0gImZhIGZhLXNhdmUiLAogIHNlbGZfY29udGFpbmVkID0gRkFMU0UKKQpgYGAKCldlIGNhbiByZWFkIGl0IHdpdGggdGhlIGByZWFkLnRyZWUoKWAgZnVuY3Rpb24gaW4gdGhlIGBhcGVgIHBhY2thZ2U6CgpgYGB7ciBsb2FkLXRyZWV9CnBoeWxvX3RyZWUgPSBhcGU6OnJlYWQudHJlZSgiZGF0YS9kb2lfMTAuNTA2MV9kcnlhZC5mNzdwN19fdjEvcGh5bG9fdHJlZS5ud2siKQoKcGh5bG9fdHJlZQpzdHIocGh5bG9fdHJlZSkKYGBgCgo6Ojogey5xdWVzdGlvbnN9CiMjIyMgUXVlc3Rpb25zIGZyb20geW91CgoqICoqUTIwKio6IEhvdyBtYW55IHRheGEgYXJlIGluIHRoZSBwaHlsb2dlbmV0aWMgdHJlZT8KKiAqKlEyMSoqOiBIb3cgZG9lcyB0aGlzIG51bWJlciBjb21wYXJlIHRvIHRoZSBudW1iZXIgb2YgdGF4YSBmb3VuZCBpbiB0aGUgZGF0YXNldD8KOjo6CgpZb3UgY2FuIHZpc3VhbGl6ZSB0aGUgdGF4YSBpbiB0aGUgcGh5bG9nZW5ldGljIHRyZWUgaW4gdGhlIGB0aXAubGFiZWxgIHNsb3Qgb2YgdGhlIHBoeWxvZ2VuZXRpYyB0cmVlOgoKYGBge3IgcGh5bG8tc3BlY2llc30KcGh5bG9fdHJlZSR0aXAubGFiZWwKYGBgCgo6Ojogey5xdWVzdGlvbnN9CiMjIyMgUXVlc3Rpb24gZm9yIHlvdQoKKiAqKlEyMioqOiBXaGF0IGRvIHlvdSBub3RpY2Ugd2l0aCB0aGUgc3BlY2llcyBuYW1lcz8gRXNwZWNpYWxseSBjb21wYXJlZCB0byB0aGUgb25lcyBhdmFpbGFibGUgaW4gYHNwZWNpZXNfdHJhaXRzYC4KOjo6CgpUbyBzb2x2ZSB0aGUgbmFtaW5nIGlzc3VlIHdlJ2xsIGhhdmUgdG8gbWF0Y2ggdGhlIG5hbWVzIHVzZWQgaW4gdGhlIHBoeWxvZ2VuZXRpYyB0cmVlIHRvIHRoZSBzcGVjaWVzIGNvZGUgdXNlZCBpbiB0aGUgc2l0ZS1zcGVjaWVzIG1hdHJpeC4gRm9yIHRoYXQgd2UnbGwgbWF0Y2ggdGhlIGVwaXRoZXRvbiB0byB0aGUgZmlyc3QgY29kZSBhdmFpbGFibGUuIFlvdSBkbyBub3QgbmVlZCB0byB1bmRlcnN0YW5kIHRoaXMgY29kZSBhbmQgY2FuIGp1c3QgY29weS1wYXN0ZSBpdCB0byBleGVjdXRlIGl0IGJlY2F1c2Ugd2UncmUgZ29pbmcgdG8gdXNlIGl0IGZ1cnRoZXIgZG93bi4KCmBgYHtyIHBoeWxvLW5hbWUtY29ycmVzfQojIENyZWF0ZSBhbiBpbmRleGVkIGxpc3Qgb2YgbmFtZXMKcGh5bG9fbmFtZXMgPSBzcGVjaWVzX3RyYWl0c1ssIGMoInNwZWNpZXMuY29kZSIsICJzcGVjaWVzIildCnBoeWxvX25hbWVzJGNvZGVfaWQgPSBzZXEobnJvdyhwaHlsb19uYW1lcykpCgojIEdldCB0aGUgZmlyc3Qgc3BlY2llcyBjb2RlIGJhc2VkIG9uIHNwZWNpZXMgZXBpdGhldApjb2RlX2lkX3RvX3VzZSA9IGFnZ3JlZ2F0ZShjb2RlX2lkIH4gc3BlY2llcywgcGh5bG9fbmFtZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEZVTiA9IGZ1bmN0aW9uKHgpIGhlYWQoeCwgMSkpCgojIEdldCBiYWNrIHRoZSBkYXRhLmZyYW1lIG9mIHNwZWNpZXMgbmFtZXMgd2l0aCB0aGUgYWN0dWFsIHNwZWNpZXMuY29kZQpjb2RlX3NwZWNpZXMgPSBtZXJnZSgKICBjb2RlX2lkX3RvX3VzZSwgcGh5bG9fbmFtZXNbLCBjKCJjb2RlX2lkIiwgInNwZWNpZXMuY29kZSIpXSwgYnkgPSAiY29kZV9pZCIKKQoKIyBUaWR5aW5nIGNvZGUgZm9yIGVkZ2UgY2FzZXMKY29kZV9zcGVjaWVzJHNwZWNpZXMgPSBnc3ViKCIgIiwgIiIsIGNvZGVfc3BlY2llcyRzcGVjaWVzKQpjb2RlX3NwZWNpZXMkc3BlY2llcyA9IHBhc3RlMCgKICB0b2xvd2VyKHN1YnN0cihjb2RlX3NwZWNpZXMkc3BlY2llcywgMSwgMSkpLAogIHN1YnN0cihjb2RlX3NwZWNpZXMkc3BlY2llcywgMiwgbmNoYXIoY29kZV9zcGVjaWVzJHNwZWNpZXMpKQopCgpjb2RlX3NwZWNpZXMgPSBjb2RlX3NwZWNpZXNbLCBjKCJzcGVjaWVzLmNvZGUiLCAic3BlY2llcyIpXQoKZGltKGNvZGVfc3BlY2llcykKYGBgCgpXZSBjYW4gbm93IGNoZWNrIHRoYXQgd2UgaGF2ZSBhbGwgdGhlIG5hbWVzIG9mIHRoZSBwaHlsb2dlbmV0aWMgdHJlZSBhdmFpbGFibGUgYXMgY29kZXM6CgpgYGB7ciBwaHlsby1jb2RlLWludGVyc2VjdH0KbGVuZ3RoKGludGVyc2VjdChwaHlsb190cmVlJHRpcC5sYWJlbCwgY29kZV9zcGVjaWVzJHNwZWNpZXMpKQpgYGAKCk5vdyB0aGF0IHdlIGhhdmUgYSBjbGVhciBjb3JyZXNwb25kYW5jZSBiZXR3ZWVuIHNwZWNpZXMgY29kZSBhbmQgcGh5bG9nZW5ldGljIG5hbWUgd2UgY2FuIHByb2NlZWQgdG8gdGhlIGNvbXB1dGF0aW9uIG9mIHBoeWxvZ2VuZXRpYyBkaXZlcnNpdHkgaW5kaWNlcy4gVGhpcyB3b24ndCBsZXQgdXNlIHJlcHJvZHVjZSBleGFjdGx5IHRoZSBzYW1lIGFuYWx5c2VzIGFzIGluIHRoZSBwYXBlciBidXQgdGhpcyBpcyB0aGUgYmVzdCB3ZSBjYW4gZG8sIGdpdmVuIHRoZSBkYXRhIGF0IG91ciBkaXNwb3NhbC4gSWYgYWxsIHRoZSBzcGVjaWVzIHdlcmUgZGV0ZXJtaW5lZCBhbm90aGVyIHBvc3NpYmlsaXR5IGNvdWxkIGhhdmUgdG8gcmUtY3JlYXRlIGEgcGh5bG9nZW5ldGljIHRyZWUgZnJvbSBnZW5ldGljIHNlcXVlbmNlcyBhdmFpbGFibGUgZnJvbSBnZW5ldGljIGRhdGFiYXNlcy4gVGhpcyBhcHByb2FjaCBob3dldmVyIG5lZWRzIHNwZWNpZmljIHNraWxscyBhbmQgaXMgYSBzdG9yeSBmb3IgYW5vdGhlciB0aW1lIQoKIyMgVmlzdWFsaXppbmcgdGhlIHBoeWxvZ2VuZXRpYyB0cmVlCgpXZSBjYW4gdmlzdWFsaXplIHRoZSBwaHlsb2dlbmV0aWMgdHJlZSB0byBiZXR0ZXIgdW5kZXJzdGFuZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gc3BlY2llcy4gV2l0aCBtb3JlIHRoYW4gNjAwIHRheGEsIHRoZSB2aXN1YWxpemF0aW9uIGNhbiBiZSBxdWl0ZSBjaGFsbGVuZ2luZyBhbmQgc29tZSBhanVzdGVtZW50cyBzaG91bGQgYmUgbWFkZSB0byBlYXNlIHRoZSB2aXp1YWxpdGlvbi4KClRoZSBlYXNpZXN0IHdheSB0byBzaG93IHRoZSBwaHlsb2dlbmV0aWMgdHJlZSBpcyB0byB1c2UgdGhlIGBwbG90LnBoeWxvKClgIGZ1bmN0aW9uIGF2YWlsYWJsZSB0aHJvdWdoIHRoZSBgYXBlYCBwYWNrYWdlLgoKYGBge3IgcGxvdC10cmVlfQphcGU6OnBsb3QucGh5bG8ocGh5bG9fdHJlZSkKYGBgCgpCeSBkZWZhdWx0IHRoZSBmdW5jdGlvbiBzaG93cyB0aGUgcGh5bG9ncmFtIHR5cGUgb2YgcGh5bG9nZW5ldGljIHRyZWUgYW5kIHBsb3QgYWxsIHRoZSBsYWJlbHMgZm9yIGFsbCBzcGVjaWVzLiBMZXQncyBtYWtlIGl0IGVhc2llciB0byByZWFkOgoKYGBge3IgYmV0dGVyLXBsb3QtdHJlZX0KYXBlOjpwbG90LnBoeWxvKHBoeWxvX3RyZWUsIHR5cGUgPSAiZmFuIiwgc2hvdy5ub2RlLmxhYmVsID0gVFJVRSwKICAgICAgICAgICAgICAgIHNob3cudGlwLmxhYmVsID0gRkFMU0UsIGNleCA9IDAuNikKYGBgCgpJdCBpcyBzdGlsbCBkaWZmaWN1bHQgdG9vIHJlYWQgYnV0IHdlIGNhbiBhbHJlYWR5IGxvb2sgYXQgaG93IGJvdGFuaWNhbCBhcmUgcmVsYXRlZCB0byBvbmUgYW5vdGhlci4KCgojIyBDb21wdXRpbmcgcGh5bG9nZW5ldGljIGRpdmVyc2l0eSBpbmRpY2VzCgpUbyBjb21wdXRlIHBoeWxvZ2VuZXRpYyBkaXZlcnNpdHkgYW5hbHlzZXMgd2UgbmVlZCB0byBjb21iaW5lIHRoZSBwaHlsb2dlbmV0aWMgdHJlZSB3aXRoIHRoZSBzaXRlLXNwZWNpZXMgbWF0cml4LiBXZSBuZWVkIHRvIHN1YnNldCB0aGUgY29tbXVuaXRpZXMgYnkgc2VsZWN0aW5nIG9ubHkgc3BlY2llcyB3aXRoIGEgZGVmaW5lZCBjb2RlIGZyb20gdGhlIHByZXZpb3VzIHNlY3Rpb24uCgpgYGB7ciBzdWItcGh5bG8tY29tfQojIEluaXRpYWwgc2l0ZS1zcGVjaWVzIG1hdHJpeApoZWFkKHNwX2NvbVssIDE6NV0pCmRpbShzcF9jb20pCgojIFN1YnNldCBvZiBzaXRlLXNwZWNpZXMgbWF0cml4IGNvbXBhdGlibGUgd2l0aCBwaHlsb2dlbmV0aWMgdHJlZQpzdWJfcGh5bG9fY29tID0gc3BfY29tWywgYXMuY2hhcmFjdGVyKGNvZGVfc3BlY2llcyRzcGVjaWVzLmNvZGUpXQpkaW0oc3ViX3BoeWxvX2NvbSkKYGBgCgpUbyBtZWFzdXJlIHBoeWxvZ2VuZXRpYyBkaXZlcnNpdHkgd2Ugd2lsbCBjb21wdXRlIHRoZSBNZWFuIFBhaXJ3aXNlIERpc3RhbmNlIChNUEQsIEBXZWJiX0V4cGxvcmluZ18yMDAwKSB1c2luZyB0aGUgYHBpY2FudGVgIHBhY2thZ2UuIFRoZSBNUEQgaXMgYW4gaW5kZXggdGhhdCByZXByZXNlbnRzIHRoZSBhdmVyYWdlIGRpc3RhbmNlIGJldHdlZW4gYWxsIHBhaXJzIG9mIHNwZWNpZXMgb2NjdXJyaW5nIGluIHRoZSBjb21tdW5pdHkuIEl0IGNhbiBhbHNvIGJlIHdlaWdodGVkIGJ5IHRoZSBhYnVuZGFuY2Ugb3IgdGhlIGJpb21hc3Mgb2YgY29uc2lkZXJlZCBzcGVjaWVzIHNvIHRoYXQgbW9yZSB3ZWlnaHQgaXMgZ2l2ZW4gdG8gc3BlY2llcyB0aGF0IHNob3cgdGhlIGdyZWF0ZXN0IGFidW5kYW5jZS4KClRoZSBmaXJzdCBkYXRhIG5lZWRlZCB0byBjb21wdXRlIHRoZSBNUEQgaXMgdGhlIHBoeWxvZ2VuZXRpYyBkaXN0YW5jZSBiZXR3ZWVuIHBhaXIgb2Ygc3BlY2llcy4gV2UnbGwgdXNlIHRoZSBjb3BoZW5ldGljIGRpc3RhbmNlIHdoaWNoIHJlcHJlc2VudCB0aGUgc2FtZSByZWxhdGlvbnNoaXBzIGFzIGEgcGh5bG9nZW5ldGljIHRyZWUgYnV0IHRocm91Z2ggYSBkaXN0YW5jZSBtYXRyaXguIFdlIGNhbiB1c2UgdGhlIGZ1bmN0aW9uIGBjb3BoZW5ldGljLnBoeWxvKClgIGluIHRoZSBgYXBlYCBwYWNrYWdlIHRvIG9idGFpbiBjb3BoZW5ldGljIGRpc3RhbmNlcy4KCmBgYHtyIGNvcGhlbmV0aWMtZGlzdH0KIyBDb21wdXRlIGNvcGhlbmV0aWMgZGlzdGFuY2VzIGZyb20gdGhlIHBoeWxvZ2VuZXRpYyB0cmVlCmNvcGhlbl9kaXN0ID0gYXBlOjpjb3BoZW5ldGljLnBoeWxvKHBoeWxvX3RyZWUpCgpzdHIoY29waGVuX2Rpc3QpCgojIFdlIG5lZWQgdG8gY2hhbmdlIHRoZSBuYW1lcyB0byBzcGVjaWVzIGNvZGVzCmNvcnJlc19jb2RlcyA9IGRhdGEuZnJhbWUoCiAgc3BlY2llcyA9IHJvd25hbWVzKGNvcGhlbl9kaXN0KQopCmNvcnJlc19jb2RlcyA9IG1lcmdlKGNvcnJlc19jb2RlcywgY29kZV9zcGVjaWVzLCBieSA9ICJzcGVjaWVzIikKcm93bmFtZXMoY29waGVuX2Rpc3QpID0gY29ycmVzX2NvZGVzJHNwZWNpZXMuY29kZQpjb2xuYW1lcyhjb3BoZW5fZGlzdCkgPSBjb3JyZXNfY29kZXMkc3BlY2llcy5jb2RlCmBgYAoKVGhlbiB0byBjb21wdXRlIE1QRCB3ZSB1c2UgdGhlIGBtcGQoKWAgZnVuY3Rpb24gaW4gdGhlIGBwaWNhbnRlYCBwYWNrYWdlLgoKYGBge3IgbXBkfQojIE9ic2VydmVkIE1lYW4gUGFpcndpc2UgRGlzdGFuY2UKIyBVbndlaWdodGVkCm1wZF92YWxfdXcgPSBwaWNhbnRlOjptcGQoc3ViX3BoeWxvX2NvbSwgY29waGVuX2Rpc3QsIGFidW5kYW5jZS53ZWlnaHRlZCA9IEZBTFNFKQojIFdlaWdodGVkCm1wZF92YWxfdyA9IHBpY2FudGU6Om1wZChzdWJfcGh5bG9fY29tLCBjb3BoZW5fZGlzdCwgYWJ1bmRhbmNlLndlaWdodGVkID0gVFJVRSkKCiMgTWFrZSBhIG5pY2UgZGF0YS5mcmFtZSB3aXRoIG9ic2VydmVkIE1QRCB2YWx1ZXMKb2JzX21wZCA9IGRhdGEuZnJhbWUoCiAgcGxvdC5jb2RlID0gcm93bmFtZXMoc3ViX3BoeWxvX2NvbSksCiAgbXBkX3Vud2VpZ2h0ZWQgPSBtcGRfdmFsX3V3LAogIG1wZF93ZWlnaHRlZCA9IG1wZF92YWxfdwopCgojIEFkZCBmb3Jlc3QgbG9zcyBwcm9wb3J0aW9uIGFuZCByaWNobmVzcyBmb3IgZWFjaCBzaXRlCm9ic19tcGQgPSBtZXJnZShvYnNfbXBkLCBwbG90X2RhdGFbLCBjKCJwbG90LmNvZGUiLCAiZm9yZXN0bG9zczE3IiwgIm50YXhhIildKQpgYGAKCjo6OiB7LnF1ZXN0aW9uc30KIyMjIyBRdWVzdGlvbnMgZm9yIHlvdQoKKiAqKlEyMyoqOiBXaGF0IGlzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgd2VpZ2h0ZWQgYW5kIHRoZSB1bndlaWdodGVkIHZlcnNpb24gb2YgdGhlIE1QRD8KKiAqKlEyNCoqOiBXaGF0IGFyZSB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIE1QRCBhbmQgdGF4YSByaWNobmVzcz8gQW5kIHdpdGggZm9yZXN0IGxvc3M/IFBsb3QgdGhlc2UgcmVsYXRpb25zaGlwcyB0byB2aXN1YWxpemUgdGhlbSBhbmQgdXNlIHRoZSBgY29yLnRlc3QoKWAgZnVuY3Rpb24gdG8gdmFsaWRhdGUgeW91ciBvYnNlcnZhdGlvbnMuCjo6OgoKIyMgTnVsbCBtb2RlbGluZyAKCkJlY2F1c2Ugb2YgdGhlIGV4cGVjdGVkIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIE1QRCBhbmQgc3BlY2llcyByaWNobmVzcywgd2UgaGF2ZSB0byBwZXJmb3JtIG51bGwgbW9kZWxzIGluIGEgc2ltaWxhciBmYXNoaW9uIHRvIHdoYXQgd2UndmUgZG9uZSBmb3IgZnVuY3Rpb25hbCBkaXZlcnNpdHkgaW5kaWNlcy4gQmVjYXVzZSwgYXMgd2l0aCBmdW5jdGlvbmFsIGRpdmVyc2l0eSwgd2Ugd2FudCB0byBrZWVwIG51bGwgc2l0ZXMgd2l0aCBzYW1lIHRvdGFsIGJpb21hc3MgYW5kIHNhbWUgdG90YWwgYmlvbWFzcyBwZXIgc3BlY2llcyBhcyBvYnNlcnZlZCBzaXRlcywgd2UgY2FuIHBlcmZvcm0gYSAic3dhcCIgbnVsbCBtb2RlbC4gV2Ugd2lsbCB1c2UgYSBudWxsIG1vZGVsIHRoYXQgc2h1ZmZsZSB0aGUgbmFtZXMgb2YgdGhlIHNwZWNpZXMgYXQgdGhlIHRpcCBvZiB0aGUgcGh5bG9nZW5ldGljIHRyZWUuCgpGb3J0dW5hdGVseSwgY29tcGFyZWQgdG8gZnVuY3Rpb25hbCBkaXZlcnNpdHksIHRoZSBudWxsIG1vZGVscyBhcmUgYWxsIGludGVncmF0ZWQgaW4gdGhlIGBzZXMubXBkKClgIGZ1bmN0aW9uIGluIHRoZSBgcGljYW50ZWAgcGFja2FnZS4gVGhlIG51bGwgbW9kZWwgd2UnbGwgdXNlIGlzIHRoZSBgInRheGEubGFiZWxzImAgb25lLiAqKkNhdXRpb24qKjogbnVsbCBtb2RlbHMgY2FuIGJlIGNvbXB1dGF0aW9uYWxseSBjaGFsbGVuZ2luZzsgZm9yIHRoZSBzYWtlIG9mIHRoZSBleGFtcGxlIHdlJ2xsIGRvIG9ubHkgOTkgaXRlcmF0aW9ucyBidXQgYXMgZm9yIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IGEgdmVyc2lvbiBvZiB0aGUgbnVsbCBtb2RlbHMgd2l0aCA5OTkgaXRlcmF0aW9ucyBpcyBzYXZlZCBpbiB0aGUgZGF0YSBmb2xkZXIuCgpgYGB7ciBzZXMtbXBkfQojIFNldCByYW5kb20gc2VlZCBmb3IgcmVwZWF0YWJpbGl0eSBvZiBhbmFseXNpcwpzZXQuc2VlZCgyMDIxMDcwNSkKCiMgQ29tcHV0ZSBudWxsIHBlcm11dGF0aW9uIG9mIE1QRApzZXNfbXBkID0gcGljYW50ZTo6c2VzLm1wZCgKICBzdWJfcGh5bG9fY29tLCBjb3BoZW5fZGlzdCwgbnVsbC5tb2RlbCA9ICJ0YXhhLmxhYmVscyIsCiAgYWJ1bmRhbmNlLndlaWdodGVkID0gVFJVRSwgcnVucyA9IDk5CikKaGVhZChzZXNfbXBkKQpgYGAKClRoZSBmdW5jdGlvbiBgc2VzLm1wZCgpYCBjb21wdXRlcyBtYW55IHZhbHVlcy4gWW91IGNhbiBnZXQgdGhlIGRldGFpbCBieSBsb29raW5nIGF0IHRoZSBoZWxwIG9mIHRoZSBmdW5jdGlvbnMgd2l0aCBgP3BpY2FudGU6OnNlcy5tcGRgIGluIHRoZSBgVmFsdWVgIHNlY3Rpb24uCgpXZSdsbCBub3cgbG9hZCB0aGUgdmVyc2lvbiB3aXRoIDk5OSBpdGVyYXRpb25zLgoKYGBge3Igc2VzLW1wZC05OTl9CnNlc19tcGRfOTk5ID0gcmVhZFJEUygiZGF0YS9udWxsX21wZF85OTkuUmRzIikKYGBgCgo6Ojogey5xdWVzdGlvbnN9CiMjIyMgUXVlc3Rpb25zIGZvciB5b3UKCiogKipRMjUqKjogRXhwbGFpbiB3aGF0IGRvZXMgdGhlIGNvbHVtbiBgbXBkLm9icy56YCBtZWFucz8gSG93IGRvZXMgdGhpcyBjb21wYXJlIHdpdGggdGhlIFNFUyB2YWx1ZXMgd2UgY29tcHV0ZWQgZm9yIGZ1bmN0aW9uYWwgZGl2ZXJzaXR5IGluZGljZXM/CiogKipRMjYqKjogSG93IGRvZXMgdGhlIHN0YW5kYXJkaXplZCB2YWx1ZSByZWxhdGVzIHdpdGggdGF4YSByaWNobmVzcz8KKiAqKlEyNyoqOiBXaGF0IGFyZSB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIE1QRCB2YWx1ZXMgY29uc2lkZXJpbmcgbnVsbCBtb2RlbHMgYW5kIGZvcmVzdCBsb3NzPyBWaXN1YWxpemUgdGhlIHJlbGF0aW9uc2hpcHMgd2l0aCB0aGUgYHBsb3QoKWAgZnVuY3Rpb24sIHZhbGlkYXRlIHlvdXIgb2JzZXJ2YXRpb25zIHdpdGggdGhlIGBjb3IudGVzdCgpYCBmdW5jdGlvbi4KOjo6CgojIyBNYXBwaW5nIHBoeWxvZ2VuZXRpYyBkaXZlcnNpdHkKCkluIG9yZGVyIHRvIHNlZSBpZiB0aGVyZSBpcyBhIGdlb2dyYXBoaWNhbCBwYXR0ZXJuIGluIHBoeWxvZ2VuZXRpYyBkaXZlcnNpdHkgd2UgY2FuIHBsb3QgbWFwcyBvZiBNUEQuCgpgYGB7ciBtYXAtc2VzLW1wZH0Kc2VzX21wZF85OTkkcGxvdC5jb2RlID0gcm93bmFtZXMoc2VzX21wZF85OTkpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9zZigKICAgIGRhdGEgPSBtZXJnZShzdWJzZXQocGxvdF9zZiwgYmxvY2sgIT0gIm9nIiksIHNlc19tcGRfOTk5LCBieSA9ICJwbG90LmNvZGUiKSwKICAgIGFlcyhjb2xvciA9IG1wZC5vYnMueikKICApICsKICBzY2FsZV9jb2xvcl9kaXN0aWxsZXIodHlwZSA9ICJkaXYiLCBwYWxldHRlID0gIlJkWWxCdSIsCiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAgIlNFUyBvZiBNUEQiKSArCiAgY29vcmRfc2YoY3JzID0gc2Y6OnN0X2NycygzMzc2KSwgeGxpbSA9IGMoODc1MDAwLCA4OTAwMDApLAogICAgICAgICAgIHlsaW0gPSBjKDUxODUwMCwgNTMxMDAwKSkgKwogIGxhYnModGl0bGUgPSAiTWFwIG9mIGFsbCBwbG90cyBidXQgYmxvY2sgJ29nJyIpICsKICBnZ3NwYXRpYWw6OmFubm90YXRpb25fc2NhbGUoKSArCiAgZ2dzcGF0aWFsOjphbm5vdGF0aW9uX25vcnRoX2Fycm93KGxvY2F0aW9uID0gImJyIikgKwogIHRoZW1lX2dyYXkoKQpgYGAKCkJ5IGV5ZSBhdCBsZWFzdCwgdGhlIHBhdHRlcm4gZG9lc24ndCBzZWVtIG9idmlvdXMgb24gdGhlIG1hcC4gQW5kIHRoZSBvYnNlcnZlZCBTRVMgdmFsdWVzIHNlZW1zIHRvIHZhcnkgd2lkZWx5IHdpdGhpbiBlYWNoIGZvcmVzdCBibG9jay4KCiMjIENvbXBhcmluZyBmYWNldHMKCk9uZSBidXJuaW5nIHF1ZXN0aW9uIGluIHRoZSBzY2llbnRpZmljIGxpdGVyYXR1cmUgYW5kIHRoYXQgaXMgcXVpdGUgZGViYXRlZCBzdGlsbCBpcyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGF4b25vbWljLCBmdW5jdGlvbmFsLCBhbmQgcGh5bG9nZW5ldGljIGRpdmVyc2l0eSBbQFBhdm9pbmVfQ29ycmVsYXRpb25zXzIwMTNdLgoKV2UgY2FuIGxldmVyYWdlIG9uIHRoZSBjb21wdXRhdGlvbiB3ZSBoYXZlIHRvIHRlc3QgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBhbGwgZmFjZXRzIG9mIGRpdmVyc2l0eS4gKiovIVxcIE5PVEUqKjogQmVjYXVzZSB3ZSBoYWQgdHJvdWJsZSB3aXRoIHRoZSBwaHlsb2dlbmV0aWMgdHJlZSwgd2UncmUgbm90IHN0cmljdGx5IGNvbXBhcmluZyB0aGUgc2FtZSBzdWJzZXQgb2YgZGF0YSwgd2UncmUgZ29pbmcgdG8gY29tcGFyZSB0aGVtIGFueXdheSBmb3IgdGhlIHNha2Ugb2YgdGhlIGV4YW1wbGUuIFRoZSBwcm9wZXIgd2F5IHdvdWxkIGJlIHRvIHN1YnNldCB0aGUgc2ltaWxhciBzZXRzIG9mIHNwZWNpZXMgYW5kIHJlY29tcHV0ZSBmdW5jdGlvbmFsIGRpdmVyc2l0eS4KCmBgYHtyIGFsbC1kaXZlcnNpdHktZmFjZXRzfQojIENvbWJpbmUgdGF4b25vbWljLCBmdW5jdGlvbmFsLCBhbmQgcGh5bG9nZW5ldGljIGRpdmVyc2l0eQphbGxfZGl2ZXJzaXR5ID0gbWVyZ2UoCiAgcGxvdF9kYXRhWywgYygicGxvdC5jb2RlIiwgIm50YXhhIildLAogIG1lcmdlKAogICAgc2VzX2ZkLCBzZXNfbXBkXzk5OVssIC0xXSwgYnkgPSAicGxvdC5jb2RlIgogICkKKQoKIyBDb21wYXJpc29uIG9mIG9ic2VydmVkIHZhbHVlcwpwYWlycyhhbGxfZGl2ZXJzaXR5WywgYygibnRheGEiLCAiRlJpYyIsICJRIiwgIkZFdmUiLCAibXBkLm9icyIpXSwKICAgICAgdXBwZXIucGFuZWwgPSBwYW5lbC5jb3IpCgojIENvbXBhcmlzb24gb2YgU0VTcwpwYWlycyhhbGxfZGl2ZXJzaXR5WywgYygibnRheGEiLCAic2VzX0ZSaWMiLCAic2VzX1EiLCAic2VzX0ZFdmUiLCAibXBkLm9icy56IildLAogICAgICB1cHBlci5wYW5lbCA9IHBhbmVsLmNvcikKYGBgCgo6Ojogey5xdWVzdGlvbnN9CiMjIyMgUXVlc3Rpb24gZm9yIHlvdQoKKiAqKlEyOCoqOiBIb3cgYXJlIHJlbGF0ZWQgYXJlIG9ic2VydmVkIHZhbHVlcyBvZiBmdW5jdGlvbmFsIGRpdmVyc2l0eSBhbmQgcGh5bG9nZW5ldGljIGRpdmVyc2l0eT8gV2hhdCBhYm91dCB0aGUgU0VTcz8KOjo6CgpGaW5hbGx5ISBXZSBub3cgaGF2ZSBhbGwgd2UgbmVlZCB0byBwcm9wZXJseSBidWlsZCBhIHN0YXRpc3RpY2FsIG1vZGVsIG9mIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBkaXZlcnNpdHkgZmFjZXRzIGFuZCB0aGUgZGlmZmVyZW50IGNvLXZhcmlhYmxlcy4gV2UnbGwgYmUgYnVpbGRpbmcgbGluZWFyIG1vZGVscyB3aXRoIGBsbSgpYCB3aXRoIGVudmlyb25tZW50YWwgdmFyaWFibGVzIGFuZCBwb3NzaWJsZSBjby12YXJpYWJsZXMgdGhhdCBtYXkgY29uZm91bmQgdGhlIGVmZmVjdCBvZiBsb2dnaW5nLgoKV2UgZmlyc3QgY29tYmluZSB0aGUgZGl2ZXJzaXR5IG1ldHJpY3MgdG8gdGhlIGVudmlyb25tZW50YWwgY28tdmFyaWFibGVzOgoKYGBge3IgbWVyZ2UtZGl2LWVudn0KcGxvdF9kaXZfZW52ID0gbWVyZ2UoCiAgYWxsX2RpdmVyc2l0eSwKICBwbG90X2RhdGFbLCBjKDEsIDY6MjEpXSwKICBieSA9ICJwbG90LmNvZGUiCikKCmRpbShwbG90X2Rpdl9lbnYpCmhlYWQocGxvdF9kaXZfZW52KQpgYGAKCgojIyBTaW5nbGUtcHJlZGljdG9yIG1vZGVscwoKVGhlbiB3ZSBjYW4gYnVpbGQgbW9kZWxzIG9mIGRpc3R1cmJhbmNlIHZhcmlhYmxlcyBvbiBkaXZlcnNpdHkgbWV0cmljcy4KTGV0J3MgYnVpbGQgaW5kaXZpZHVhbCBtb2RlbHMgd2l0aCB0aGUgbWFpbiBkaXN0dXJiYW5jZSB2YXJpYWJsZXM6IGxvY2FsIGZvcmVzdCBsb3NzIGBmb3Jlc3Rsb3NzMTdgLCBwcmltYXJ5IHJvYWQgZGVuc2l0eSBgcm9hZGRlbnNwcmltYCwgYW5kIGRpc3RhbmNlIHRvIHByaW1hcnkgcm9hZHMgYHJvYWRkaXN0cHJpbWAuCgpGaXJzdCBhIG1vZGVsIG9uIHRheGEgcmljaG5lc3M6CgpgYGB7ciBtb2QtdGF4YS1sb3NzfQptb2RfdGF4YV9sb3NzID0gbG0obnRheGEgfiBmb3Jlc3Rsb3NzMTcsIGRhdGEgPSBwbG90X2Rpdl9lbnYpCgptb2RfdGF4YV9sb3NzCnN1bW1hcnkobW9kX3RheGFfbG9zcykKYGBgCgpXZSBjYW4gcGxvdCB0aGUgcmVncmVzc2lvbiBsaW5lIGZyb20gdGhlIG1vZGVsIHdpdGggdGhlIGRhdGEgd2l0aCB0aGUgZm9sbG93aW5nOgoKYGBge3IgcGxvdC1tb2QtdGF4YS1sb3NzfQpwYXIobWZyb3cgPSBjKDEsIDEpKQpwbG90KG1vZF90YXhhX2xvc3MkbW9kZWwkZm9yZXN0bG9zczE3LCBtb2RfdGF4YV9sb3NzJG1vZGVsJG50YXhhLAogICAgIHhsYWIgPSAiRm9yZXN0IExvc3MgKCUpIiwgeWxhYiA9ICJUYXhhIFJpY2huZXNzIikKYWJsaW5lKGNvZWYgPSBjb2VmKG1vZF90YXhhX2xvc3MpLCBjb2wgPSAiZGFya3JlZCIsIGx3ZCA9IDEpCmBgYAoKOjo6IHsucXVlc3Rpb25zfQojIyMjIFF1ZXN0aW9ucyBmb3IgeW91CgoqICoqUTI5Kio6IEhvdyB3b3VsZCB5b3UgcXVhbGlmeSB0aGUgZWZmZWN0IG9mIGZvcmVzdCBsb3NzIG9uIHRoZSB0YXhhIHJpY2huZXNzPwoqICoqUTMwKio6IFdpdGggdGhlIHNhbWUgZm9ybXVsYSBidWlsZCBzaW1pbGFyIG1vZGVscyB3aXRoIHRoZSBvdGhlciBwcmVkaWN0b3JzIGByb2FkZGVuc3ByaW1gIGFuZCBgcm9hZGRpc3RwcmltYC4gSG93IGRvIHRoZXkgY29tcGFyZSB3aXRoIGZvcmVzdCBsb3NzPwo6OjoKCmBgYHtyIG1vZC10YXhhLW90aGVyLWRpc3R1cmJhbmNlcywgaW5jbHVkZSA9IEZBTFNFfQptb2RfdGF4YV9kZW5zID0gbG0obnRheGEgfiByb2FkZGVuc3ByaW0sIGRhdGEgPSBwbG90X2Rpdl9lbnYpCm1vZF90YXhhX2Rpc3QgPSBsbShudGF4YSB+IHJvYWRkaXN0cHJpbSwgZGF0YSA9IHBsb3RfZGl2X2VudikKYGBgCgpXZSBjYW4gbm93IGJ1aWxkIHNpbWlsYXIgbW9kZWxzIGZvciBib3RoIGZ1bmN0aW9uYWwgYW5kIHBoeWxvZ2VuZXRpYyBkaXZlcnNpdHkuIEJlY2F1c2Ugd2UgZG8gbm90IHdhbnQgdG8gY29uc2lkZXIgdGhlIHBvdGVudGlhbCBjb25mb3VuZGluZyBmYWN0b3Igb2YgdGF4YSByaWNobmVzcyB3ZSBjYW4gY29uc2lkZXIgZGlyZWN0bHkgdGhlIFNFUyB2YWx1ZXMgd2UgY2FyZWZ1bGx5IGJ1aWx0IHdpdGggb3VyIG51bGwgbW9kZWxzLgoKYGBge3IgbW9kLWZkLWxvc3N9Cm1vZF9mZF9sb3NzID0gbG0oc2VzX1EgfiBmb3Jlc3Rsb3NzMTcsIGRhdGEgPSBwbG90X2Rpdl9lbnYpCm1vZF9wZF9sb3NzID0gbG0obXBkLm9icy56IH4gZm9yZXN0bG9zczE3LCBkYXRhID0gcGxvdF9kaXZfZW52KQpgYGAKCgojIyBNdWx0aS1wcmVkaWN0b3JzIG1vZGVscwoKQmVjYXVzZSBvZiB0aGUgbWFueSBwb3NzaWJsZSBjb25mb3VuZGluZyB2YXJpYWJsZXMgKGRpZmZlcmVudCBsb2NhbCBlbnZpcm9ubWVudGFsIGNvbmRpdGlvbnMsIGRpZmZlcmVuY2UgaW4gdmVnZXRhdGlvbiB0eXBlcykgd2Ugc2hvdWxkIGJ1aWxkIGEgbW9kZWwgd2l0aCBtYW55IG1vcmUgcHJlZGljdG9ycy4KCkxldCdzIGJ1aWxkIGEgY29tcGxldGUgbW9kZWwgd2l0aCBhbGwgZW52aXJvbm1lbnRhbCBwcmVkaWN0b3JzOgoKYGBge3IgbW9kLWRpdi1hbGx9Cm1vZF90YXhhX2FsbCA9IGxtKAogIG50YXhhIH4gZWxldmF0aW9uICsgZm9yZXN0bG9zczE3ICsgZm9yZXN0bG9zczU2MiArIHJvYWRkZW5zc2VjICsKICAgIHJvYWRkaXN0cHJpbSArIHNvaWxQQzEgKyBzb2lsUEMyLAogIGRhdGEgPSBwbG90X2Rpdl9lbnYKKQptb2RfZmRfYWxsID0gbG0oCiAgc2VzX1EgfiBlbGV2YXRpb24gKyBmb3Jlc3Rsb3NzMTcgKyBmb3Jlc3Rsb3NzNTYyICsgcm9hZGRlbnNzZWMgKwogICAgcm9hZGRpc3RwcmltICsgc29pbFBDMSArIHNvaWxQQzIsCiAgZGF0YSA9IHBsb3RfZGl2X2VudgopCm1vZF9wZF9hbGwgPSBsbSgKICBtcGQub2JzLnogfiBlbGV2YXRpb24gKyBmb3Jlc3Rsb3NzMTcgKyBmb3Jlc3Rsb3NzNTYyICsgcm9hZGRlbnNzZWMgKwogICAgcm9hZGRpc3RwcmltICsgc29pbFBDMSArIHNvaWxQQzIsCiAgZGF0YSA9IHBsb3RfZGl2X2VudgopCmBgYAoKOjo6IHsucXVlc3Rpb25zfQojIyMjIFF1ZXN0aW9uIGZvciB5b3UKCiogKipRMzEqKjogV2hhdCBjYW4geW91IHNheSBhYm91dCB0aGUgZWZmZWN0IG9mIHRoZSBkaXN0dXJiYW5jZXMgb24gdGhlIGRpZmZlcmVudCBkaXZlcnNpdHkgbWV0cmljcz8gV2hhdCBhcmUgdGhlIGV4cGxhbmF0b3J5IHBvd2VyIG9mIG91ciBtb2RlbHM/Cjo6OgoKVG8gZXhwbGFpbiB0aGUgaXNzdWVzIHdlIGhhdmUgd2l0aCB0aGUgbW9kZWxzIHdlIGNhbiBsb29rIGF0IHRoZSBtb2RlbCBkaWFnbm9zdGljczoKCmBgYHtyIG1vZC1kaWFnfQpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KG1vZF90YXhhX2FsbCkKIyBPciBldmVuIGJldHRlcgpwZXJmb3JtYW5jZTo6Y2hlY2tfbW9kZWwobW9kX3RheGFfYWxsKQpgYGAKClRvIGdvIGZ1cnRoZXIgKGJ1dCBpdCdzIGJleW9uZCB0aGUgc2NvcGUgb2YgdGhpcyB0dXRvcmlhbCkgd2UgY291bGQgZm9sbG93IHRoZSB0cmFja3Mgb2YgdGhlIHBhcGVyOgoKMS4gQnVpbGQgYSBnZW5lcmFsaXplZCBsaW5lYXIgbW9kZWwgKEdMTSkgd2hlbiB3b3JraW5nIHdpdGggdGF4YSByaWNobmVzcyBhcyBpdCBpcyBhIGNvdW50IGRhdGEuCjEuIExldmVyYWdlIHRoZSBmYWN0IHRoYXQgd2UgaGF2ZSBhIGJsb2NrIGRlc2lnbiBpbiBvdXIgc2FtcGxpbmcgZGF0YSBhbmQgdXNlIG1peGVkLW1vZGVscyB0byBhY2NvdW50IGZvciB0aGF0ICh0aGUgb2JzZXJ2YXRpb25zIGFyZSBub3QgZnVsbHkgaW5kZXBlbmRlbnQgb2Ygb25lIGFub3RoZXIgYW5kIGFyZSBzdHJ1Y3R1cmVkIGluIGdyb3VwcykuCjEuIEFjY291bnQgZm9yIHNvbWUgbm9uLWxpbmVhciBlZmZlY3RzIG9mIHNvbWUgcHJlZGljdG9ycyAoZm9yZXN0IGxvc3MgaGF2ZSBhIHN0cm9uZyBxdWFkcmF0aWMgY29tcG9uZW50KS4KMS4gUGVyZm9ybSBtb2RlbCBhdmVyYWdpbmcgb3IgbW9kZWwgc2VsZWN0aW9uIHRvIHBydW5lIHRoZSBtb2RlbCB0byB0aGUgbW9zdCBpbXBvcnRhbnQgcHJlZGljdG9ycy4K


License: Matthias Grenié & Marten Winter CC-BY 4.0