1 Description

This R notebook is a bioinformatics pipeline to analyze protein saturation/under-utilization with a resource allocation model for the chemolithoautotroph Ralstonia eutropha (a.k.a. Cupriavidus necator).

2 Libraries

suppressPackageStartupMessages({
  library(lattice)
  library(latticeExtra)
  library(latticetools)
  library(tidyverse)
  library(stringi)
})

3 Data import

Define the data source directories. Some of them are external in the sense of not included in the accompanying data folder of this R notebook. These are located in the accompanying github repository for the resource allocation model that was used here. The resource allocation model can be found at my fork of Bacterial-RBA-models.

Reutropha_proteomics <- "../data/input/Ralstonia_eutropha.Rdata"
model_reactions <- "../data/input/model_reactions.csv"
simulation_dir <- "../data/simulation/substrate_limitation/"
source("read_rba_result.R")

Read simulation data.

# read simulation results
df_flux <- read_rba_result(list.files(simulation_dir, pattern = "fluxes_.*.tsv$", full.names = TRUE))
df_prot <- read_rba_result(list.files(simulation_dir, pattern = "proteins_.*.tsv", full.names = TRUE))
df_macr <- read_rba_result(list.files(simulation_dir, pattern = "macroprocesses_.*.tsv", full.names = TRUE))

4 Overview on substrate uptake, growth, and yield

After running a set of simulations in RBApy that simulate increasing substrate limitation, we can plot the substrate uptake rate q, yield Y in gram biomass per gram substrate, and growth rate µ. Unlike genome scale models, growth becomes limited by the maximum amount of proteins that a cell can synthesize. If cells would not be protein-limited, or proteins would catalyze reactions infinitely fast, no such limitation would take place and growth rate would scale linearly with substrate concentration. This is the situation in FBA simulation.

# rearrange some rows (mu, qS) to columns
df_macr <- df_macr %>% filter(!grepl("test_process", key)) %>% 
  spread(key, value) %>%
  
  # add type of simulation
  mutate(substrate = case_when(
    carbon_source == "for" ~ "formate",
    carbon_source == "succ" ~ "succinate",
    carbon_source == "fru" & nitrogen_conc == 18.7 ~ "fructose",
    carbon_source == "fru" & nitrogen_conc != 18.7 ~ "ammonium"
  )) %>%
  
  # add uptake rate in g/gDCW*h instead of mmol
  mutate(qS_g_gDCW_h = case_when(
    substrate == "formate" ~ qS*0.04603,
    substrate == "succinate" ~ qS*0.11809,
    substrate == "fructose" ~ qS*0.18016,
    substrate == "ammonium" ~ qS*0.05349
  ))

First we can have a look at how growth rate levels off with increasing substrate concentration in mmol/L. Note: this is not equal to substrate uptake rate. Substrate uptake rate and growth rate should have an almost linear relationship. We can log-transform the carbon concentration and fit a linear model to predict the substrate concentration required to obtain a certain substrate uptake rate and growth rate.

# copy nitrogen to carbon concentration for ammonium limitation,
# just for plotting purposes
df_macr_viz <- df_macr %>%
  mutate(carbon_conc = case_when(
    substrate == "ammonium" ~ nitrogen_conc,
    TRUE ~ carbon_conc
  ))

plot_mu_qs_lin <- xyplot(mu ~ carbon_conc | substrate, df_macr_viz,
    par.settings = custom.colorblind(),
    between = list(x = 0.5, y = 0.5),
    layout = c(4,1), lwd = 1.5, pch = 19,
    xlab = expression("S [mM]"),
    ylab = expression('µ [h'^'-1'*']'),
    scales = list(alternating = FALSE),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.xyplot(x, y, cex = 0.9, ...)
      #panel.lmlineq(x[1:4], y[1:4], fontfamily = "FreeSans", ...)
    }
  )

plot_mu_qs_log <- xyplot(log10(carbon_conc) ~ qS | substrate,
    df_macr_viz,
    par.settings = custom.colorblind(),
    between = list(x = 0.5, y = 0.5), #xlim = c(0, 5),
    layout = c(4,1), lwd = 1.5, pch = 19,
    xlab = expression('q'[S]*' mmol g DCW'^-1*'h'^-1),
    ylab =  expression('log'[10]*' S [mM]'),
    scales = list(alternating = FALSE),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.xyplot(x, y, cex = 0.9, ...)
      panel.lmlineq(x[1:6], y[1:6], fontfamily = "FreeSans", ...)
    }
  )

print(plot_mu_qs_lin, split = c(1,1,1,2), more = TRUE)
print(plot_mu_qs_log, split = c(1,2,1,2))


df_macr_viz %>%
  group_by(substrate) %>%
  # fit linear model to substrate uptake rate vs concentration
  summarize(
    slope = lm(x ~ y, data = list(x = log10(carbon_conc), y = qS))$coeff[2],
    offset = lm(x ~ y, data = list(x = log10(carbon_conc), y = qS))$coeff[1]
  ) %>% mutate(model = paste0("c = 10^(", round(offset, 3), " + ", round(slope, 3), "*qS)"))

Create a Herbert-Pirt plot for each condition (growth rate versus substrate uptake rate). This plot would show a change in yield by a ‘kink’ of the data points.

xyplot(qS_g_gDCW_h ~ mu | substrate, df_macr,
  par.settings = custom.colorblind(),
  between = list(x = 0.5, y = 0.5),
  layout = c(4,1), lwd = 1.5, pch = 19,
  ylab = expression("q"[S]*" [g h"^-1*" gDCW"^-1*"]"),
  xlab = expression('µ [h'^'-1'*']'),
  scales = list(alternating = FALSE),
  panel = function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.xyplot(x, y, cex = 0.9, ...)
    # displaying maintenance and yield coefficients
    coef <- lm(y ~ x, data.frame(x, y))$coeff
    panel.text(median(x), 2.7, 
      paste("ms =", round(coef[[1]], 3), "g h-1 g_DCW-1"), 
      col = grey(0.3), cex = 0.7)
    panel.text(median(x), 2.4, paste(expression("Yx/S ="), 
        round(1/coef[[2]], 3), "g_DCW g_S-1"), 
      col = grey(0.3), cex = 0.7)
  }
)

5 Resource allocation in terms of protein mass

To determine the true allocation of protein resources per compartment, but also the true cost of protein per process, we need to convert the predicted concentration of proteins in mmol per gDCW to g per gDCW, simply by multiplying protein concentration with the molecular weight of a protein (g/mol, converted to g/mmol). We can then also easily transform g/gDCW to mass fraction by dividing individual protein concentrations by the sum of all protein concentrations. The protein mass fraction is dimensionless. The only parameter required for this transformation is the molecular weight per protein which is available from uniprot. We can for example take the protein annotation table that is automatically downloaded during RBApy model generation.

# import downloaded Ralstonia protein annotation from uniprot
df_uniprot <- read_tsv("../data/input/uniprot.csv", col_types = cols()) %>%
  mutate(locus_tag = stri_extract_first(`Gene names`, regex = "H16_[AB][0-9]{4}|PHG[0-9]{3}"))

# merge predicted protein allocation with molecular weight info from uniprot
df_prot <- left_join(df_prot, select(df_uniprot, locus_tag, Length, Mass),
  by = c("key" = "locus_tag")) %>%
  
  # calculate predicted protein mass in g/gDCW using MW in g/mmol, and mass fraction
  group_by(simulation) %>% mutate(
    predicted_mass_g_gDCW = value * Mass / 1000)

# test if mass fractions sum to reasonable value
df_prot %>% summarize(
  predicted_mass_g_gDCW = sum(predicted_mass_g_gDCW, na.rm = TRUE))

The simulated protein mass per gDCW changes with growth rate, and it is considerably lower then the estimated ~0.65-0.68 g total protein/gDCW that was previously estimated for bacteria (see e.g. Park et al., biomass composition for Ralstonia eutropha model, or Touloupakis et al., biomass composition for cyanobacteria). One reason is that above numbers only include enzymatic proteins (the ones represented by the GSM). For machinery such as ribosomal proteins, a separate calculation needs to be performed. The model returns estimated concentration of machineries for replication, transcription, translation, and protein folding. The associated proteins can be imported from the model folder and the total mass estimated using molecular weight and subunit stoichiometry as done before for enzymatic proteins.

machinery_names <- c("replication", "transcription", "ribosome", "chaperones")
machinery_tables <- paste0("../data/simulation/macro_machines/", machinery_names, ".tsv")

df_macr <- df_macr %>% 
  
  # remove unused columns and rename important ones
  select(-P_ENZ, -P_RNADEG) %>%
  rename(replication = P_REP, transcription = P_TSC, 
    ribosome = P_TA, chaperones = P_CHP, substrate_uptake_rate = qS,
    predicted_growth_rate = mu
  ) %>%
  
  # gather individual machineries in one column
  gather(key = machine, value = predicted_mass_mmol_gDCW, 
    chaperones:transcription) %>%
  
  # round uptake rates for merging
  mutate(substrate_uptake_rate = round(substrate_uptake_rate, 3))
  

df_machinery <- lapply(machinery_tables, read_tsv, col_type = cols()) %>% 
  bind_rows(.id = "machine") %>%
  mutate(machine = recode(machine, !!!setNames(machinery_names, 1:4))) %>%
  select(-`Entry name`, -Sequence, -Cofactor, -`EC number`, -`Organism ID`, `Organism`,
    -`Catalytic activity`, -Status) %>%
  
  # join with prediction of molecular machine concentration (mmol/gDCW)
  left_join(df_macr, by = "machine") %>%
  
  # calculate predicted protein mass in g/gDCW using MW in g/mmol, and mass fraction
  group_by(simulation) %>% mutate(
    predicted_mass_g_gDCW = predicted_mass_mmol_gDCW * Stoichiometry * Mass / 1000
  )

# test if mass fractions sum to reasonable value
df_machinery %>% 
  summarize(sum_g_gDCW = sum(predicted_mass_g_gDCW, na.rm = TRUE)) %>%
  ungroup %>% head(10)

If the utilized protein for enzymes and machinery is summed up, it does not exceed ~0.25 g/gDCW. The reason for this is that up to 60% of the protein mass is not modeled (non enzymatic, NE proteome, 57.7% at µ = 0, see R notebook Ralstonia-model-constraints), and only around 68% of the total cell mass is protein. If we consider this, than total protein mass can be estimated as m = 0.25/(1-0.6) = 0.625 g/gDCW. This is much closer to the estimated 0.68 g protein/gDCW. The difference can be attributed to default values of amino acid concentration in RBApy that determine the size of the ‘protein pool’. It is important to note that the sum of utilizable proteins is not a constant value but a linear function of growth rate. The total proteome pool including non-enzymatic proteins is, however, constant.

6 Correlation between predicted and experimentally determined proteome

To compare the predicted and experimental proteome composition, we load the required proteomics data. Proteomics data are mass spectrometry measurements with label-free quantification of peptides. Protein quantification was performed by summing up all peptide intensities per annotated protein. The proteomic measurement unit, mass fraction, can be easily transformed to g/gDCW by multiplying mass fraction with the total protein mass (0.68 g/gDCW) used in RBApy simulations. Or vice versa by converting RBApy protein concentration (mmol/gDCW) to mass fraction.

To allow a fair comparison between measured and predicted data, it is necessary to aggregate (e.g. sum up) all protein abundances allocated to one reaction. The reason is that the model will only predict protein abundance of the first of a range of iso-enzymes for a particular reaction, while in reality another iso-enzyme might be more abundant (carry the majority of flux). This would lead to lower correlation between measured and predicted protein concentrations. This is not necessary for machinery proteins, they have no iso-enzymes and are used under all conditions.

6.1 Combine simulations and experimental data

The proteomics data must be merged with RBApy simulation results using matching conditions. First, proteomics data are loaded and prepared for merging.

Step 1: load proteomics data

load(Reutropha_proteomics)

# pick a condition matching simulations
Ralstonia_eutropha <- Ralstonia_eutropha %>%
  
  # round substrate uptake rate
  mutate(substrate_uptake_rate = round(substrate_uptake_rate, 3)) %>%
  
  # select only required columns
  rename(growth_rate = growthrate) %>%
  select(uniprot, locus_tag, protein, condition, substrate, substrate_uptake_rate,
    growth_rate, COG_Process, R1:R4) %>%
  
  # turn raw intensity measurements into mass in g per gDCW (assuming a 
  # total protein concentration of 0.68 g/gDCW)
  group_by(condition) %>%
  mutate(across(matches("R[1234]"), function(x) x/sum(x, na.rm = TRUE)*0.68)) %>%
  gather(key = "replicate", value = "mass_g_gDCW", R1:R4)

Step 2: Load gene reaction associations obtained from genome scale model

df_model_reactions <- read_csv(model_reactions, col_types = cols()) %>%
  
  # filter for reactions with gene associations
  select(reaction_id, reaction_name, genes, groups) %>% separate_rows(genes, sep = ", ") %>%
  filter(!is.na(genes)) %>% 
  rename(model_group = groups) %>%
  
  # add a more general groups description
  mutate(model_group_basic = case_when(
    grepl("Phenylala|Valine|Tyrosine|Glutamate|Glycine|Tryptophan|Methionine|
          Cysteine|Alanine|Histidine|Arginine|Lysine", model_group) ~ "Amino acid",
    grepl("Pentose|Calvin", model_group) ~ "PPP + Calvin cycle",
    model_group == "Citric Acid Cycle" ~ "Citric Acid Cycle",
    model_group == "Glycolysis/Gluconeogenesis" ~ "Glycolysis/Gluconeogenesis",
    model_group == "Glyoxylate and Dicarboxylate metabolism" ~ "Autotrophic energy",
    model_group == "Oxidative Phosphorylation" ~ "Oxidative Phosphorylation",
    TRUE ~ "Other"
  ))

Step 3: Select and rename conditions from RBA simulation

# add type of substrate limitation
add_cond <- function(df) {
  df %>% mutate(substrate = case_when(
    carbon_source == "succ" ~ "succinate",
    carbon_source == "for" ~ "formate",
    carbon_source == "fru" & nitrogen_conc == 18.7 ~ "fructose",
    TRUE ~ "ammonium",
  ))
}

# add substrate uptake rate
df_substrate_uptake <- df_macr %>% 
  select(simulation, substrate_uptake_rate) %>% distinct
df_prot <- df_prot %>% add_cond %>% left_join(df_substrate_uptake, by = "simulation")
df_flux <- df_flux %>% add_cond %>% left_join(df_substrate_uptake, by = "simulation")

Step 4: Merge protein measurements and predictions into master tables

The first step is to merge the tables for machinery proteins, that means proteins related to replication, transcription, translation, and protein folding. These don’t require allocation of protein mass to reactions, and merging becomes simply an operation on enzyme IDs and conditions.

# join with proteomics data
df_machinery <- df_machinery %>%
  left_join(Ralstonia_eutropha,
    by = c("Entry" = "uniprot", "substrate", "substrate_uptake_rate"))

The second table for all enzymatic proteins requires the allocation of estimated protein mass to enzymes. One option for the future is to retrieve these values directly from RBApy, but this is not implemented yet.

df_prot_comp <- df_model_reactions %>%
  
  # join with proteomics data
  left_join(Ralstonia_eutropha, by = c("genes" = "locus_tag")) %>%
  complete(nesting(genes, reaction_id, reaction_name, model_group, model_group_basic),
    nesting(condition, substrate, substrate_uptake_rate, growth_rate, replicate)) %>%
  filter(!is.na(substrate)) %>%
  
  # join with simulation data
  left_join(df_prot, by = c("genes" = "key", "substrate", "substrate_uptake_rate")) %>%
  
  # determine number of reactions per protein
  group_by(condition, genes, replicate) %>% 
  mutate(n_reactions = length(reaction_id)) %>%
  
  # calculate protein mass in g/gDCW
  ungroup %>% mutate(
    predicted_mass_g_gDCW = predicted_mass_g_gDCW/n_reactions,
    mass_g_gDCW = mass_g_gDCW/n_reactions
  ) %>%
  
  # summarize by summing up protein abundance per reaction (NA treated as zero)
  group_by(condition, reaction_id, reaction_name, model_group,
    model_group_basic, substrate, substrate_uptake_rate, growth_rate, replicate) %>% 
  summarize(
    predicted_mass_g_gDCW = sum(predicted_mass_g_gDCW, na.rm = TRUE),
    mass_g_gDCW = sum(mass_g_gDCW, na.rm = TRUE)
  ) %>%
  filter(mass_g_gDCW != 0) %>%
  
  # add predicted growth rate to experimental
  left_join(
    df_machinery %>% ungroup %>%
    select(substrate, substrate_uptake_rate, predicted_growth_rate) %>%
    filter(!duplicated(substrate_uptake_rate))
  ) %>%
  
  # add predicted fluxes per reaction and condition
  left_join(
    df_flux %>% ungroup %>%
    select(key, value, substrate, substrate_uptake_rate) %>%
    rename(reaction_id = key, flux_mmol_gDCW_h = value)
  )
`summarise()` has grouped output by 'condition', 'reaction_id', 'reaction_name', 'model_group', 'model_group_basic', 'substrate', 'substrate_uptake_rate', 'growth_rate'. You can override using the `.groups` argument.
Joining, by = c("substrate", "substrate_uptake_rate")
Joining, by = c("reaction_id", "substrate", "substrate_uptake_rate")

Now we perform a test. We check if all protein abundances allocated to reactions sum to a reasonable value as we would expect. This value would be the total enzymatic protein mass in g/gDCW, per condition and replicate, and could reach up to 0.2 g/gDCW for the simulations, and higher for the actual data (includes all additional proteins quantified in experiment, but not carrying flux in model simulations).

df_prot_comp %>% group_by(condition, replicate) %>% 
  filter(predicted_mass_g_gDCW != 0) %>%
  summarize(sum(mass_g_gDCW), sum(predicted_mass_g_gDCW))
`summarise()` has grouped output by 'condition'. You can override using the `.groups` argument.

The total predicted protein mass is lower than the measured protein mass. Therefore the following section quantifies discrepancies between model predicted and actually measured abundances. First we can inspect the top N reactions with highest average predicted protein abundance. The ratio of predicted divided by measured mass indicates that a handful of proteins are predicted to be more than 10 fold abundant compared to the measured abundance. This points towards fluxes being erroneously predicted too high for particular reactions, or k_app values being estimated too low for the estimated flux.

However, the largest discrepancies arise from under-estimation of proteins, the main cause being that the model predicts the optimal abundance for each enzyme to carry a certain flux. If fluxes are drastically reduced due to strong substrate limitation, the minimal required protein abundance to optimize growth will be much lower than the measured abundance. A bacterial cell on the other hand can not fully reduce its proteome but instead ‘suspends’ inactive enzymes.

6.2 Change of machinery proteins with growth rate

The simulation and measurement data was prepared and merged by condition in the previous sections. Now it can be plotted to e.g. compare protein allocation over growth rate. Interestingly, we see that model predictions are quite accurately reflecting the range of protein allocation for the four different machineries, see following paragraphs. This is a good confirmation of the model’s predictive power, given that the rates of these machineries were not fitted from data but taken purely from literature. There are however some deviations from the predicted ‘optimal’ proteome:

  • the most important machine is the ribosome. Prediction and experiment show a very similar increase of ribosome abundance with growth rate (slope of linear model), but the intersection (amount of unused ribosomes at zero growth) is much higher in experiment
  • chaperones show an inverse proportional relationship with growth rate contrary to model prediction. Do (some of?) these proteins have another role than just folding, like stress response?
  • transcription sector is quite stable, however, predicted enzyme mass is much lower than measured (underestimated by 1 order of magnitude , 5x10^-3 vs 5x10^-4 g/gDCW)
  • replication sector is heavily underestimated by 3 orders of magnitude (10^-3 vs 10^-6 g/gDCW)

The protein allocation for the two low-abundant machines, replication and transcription is very hard to see. We can update the Y-axis limits for this plot and re-plot it with 10-fold ‘zoom’. Abundance of replication machinery increases with growth rate from 0.0010 to a maximum of 0.0015 g/gDCW, an increase by 20-50%. For transcription, abundance increases with growth rate from 0.006 to 0.008 g/gDCW, an increase of 33%.

df_machinery_util <- df_machinery %>%
  
  # remove 1 non-quantified replicate
  filter(!(substrate == "fructose" & growth_rate == 0.1 & replicate == "R2")) %>%
  
  # summing up protein mass over all conditions
  group_by(machine, substrate, growth_rate, replicate) %>%
  summarize(
    `mass [g/gDCW]` = sum(mass_g_gDCW, na.rm = TRUE),
    `predicted mass [g/gDCW]` = sum(predicted_mass_g_gDCW, na.rm = TRUE),
    utilization = `predicted mass [g/gDCW]`/`mass [g/gDCW]`
  ) %>% ungroup %>%
  
  # replace 0 with NA and reorder factors
  mutate(across(matches("mass"), ~ na_if(.x, 0))) %>%
  mutate(machine = machine %>% factor(., unique(.)[c(2,4,3,1)]))
`summarise()` has grouped output by 'machine', 'substrate', 'growth_rate'. You can override using the `.groups` argument.
# plot mass and utilization
plot_machinery <-
  
  xyplot(`mass [g/gDCW]` + `predicted mass [g/gDCW]` ~ factor(growth_rate) | 
    machine * substrate, df_machinery_util,
    par.settings = custom.colorblind(), auto.key = list(columns = 2, cex = 0.7),
    xlab = expression("µ [h"^-1*"]"),
    ylab = expression("m"[protein]*" [g gDCW"^-1*"]"),
    scales = list(alternating = FALSE, x = list(at = c(2,4))),
    as.table = TRUE, ylim = c(-0.01, 0.12),
    between = list(x = 0.5, y = 0.5), pch = 19, lwd = 2,
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.errbars(x, y, ewidth = 0, ...)
      panel.superpose(x, y, ...)
    }, panel.groups = function(x, y, ...) {
      panel.lmline(x, y, ...)
    }
  )

6.3 Under-utilization of machinery proteins

Here, we determine the underutilized machinery fraction by taking the ratio of simulated optimal enzyme abundance and experimentally measured abundance, over all four central dogma machines.

plot_machinery_util <- df_machinery %>%
  
  # remove 1 non-quantified replicate
  filter(!(substrate == "fructose" & growth_rate == 0.1 & replicate == "R2")) %>%
  
  # summarizing protein utilization for all machines
  mutate(machine = "machines") %>%
  group_by(machine, substrate, growth_rate, replicate) %>%
  summarize(
    mass = sum(mass_g_gDCW, na.rm = TRUE),
    predicted_mass = sum(predicted_mass_g_gDCW, na.rm = TRUE),
    utilization = predicted_mass/(mass)
  ) %>%
  
  # plot. use alternating = 2 to switch axis to right side
  xyplot(utilization*100 ~ factor(growth_rate) | machine * substrate, .,
    par.settings = custom.colorblind(), 
    scales = list(x = list(at = c(2,4)), y = list(alternating = FALSE)),
    xlab = expression("µ [h"^-1*"]"), ylab = "",
    key = simpleKey("% utilization", cex = 0.7),
    as.table = TRUE, between = list(x = 0.5, y = 0.5),
    pch = 19, lwd = 2, layout = c(1, 4), ylim = c(-10, 110),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      x_mean = unique(x); y_mean = tapply(y, x, mean)
      panel.xyarea(c(0, x_mean, 6), c(0, y_mean, tail(y_mean, 1)), 
        lty = 0, col = grey(0.6, alpha = 0.5), ...)
      panel.errbars(x, y, ewidth = 0, col = grey(0.5), ...)
    }
  ) %>% useOuterStrips
`summarise()` has grouped output by 'machine', 'substrate', 'growth_rate'. You can override using the `.groups` argument.
print(useOuterStrips(plot_machinery), position = c(0, 0, 0.77, 1.027), more = TRUE)
print(plot_machinery_util, position = c(0.71, 0, 1.02, 1.027))
grid::grid.text(c("B", "C"), x = c(0.02, 0.75), y = c(0.97,0.97))

7 Under-utilization of enzymes

7.2 Central carbon metabolism, detailed utilization per enzyme

Analogously to the above analysis, we can plot protein abundance and utilization for all enzymes of central carbon metabolism (from Figure 3 D).

plot_enz_ccm <- lapply(list_ccm_enzymes, function(lst) {
  df_prot_comp %>%
  filter(reaction_id %in% lst, !is.na(condition)) %>%
  mutate(reaction_id = factor(reaction_id, lst)) %>%
  group_by(reaction_id) %>% mutate(mass_g_gDCW = scales::rescale(mass_g_gDCW)) %>%
  
  xyplot(mass_g_gDCW ~ factor(growth_rate) | reaction_id, .,
    par.settings = custom.colorblind(),
    groups = substrate, xlab = "", #expression("µ [h"^-1*"]"),
    #ylab = expression("m"[protein]*" [g gDCW"^-1*"]"),
    ylab = "relative abundance",
    layout = c(8, ifelse(length(lst) > 8, 2, 1)),
    ewidth = 0, lwd = 2, as.table = TRUE,
    between = list(x = 0.5, y = 0.5),
    scales = list(alternating = FALSE, x = list(at = c(2, 4))),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.errbars(x, y, ...)
    }
  )
})

print(plot_enz_ccm[[1]], position = c(0.0,0.67,1,1), more = TRUE)
print(plot_enz_ccm[[2]], position = c(0,0.51,1,0.75), more = TRUE)
print(plot_enz_ccm[[3]], position = c(0,0.26,1,0.59), more = TRUE)
print(plot_enz_ccm[[4]], position = c(0,0,1,0.34))
grid::grid.text(c("CBB", "ED", "PYR", "TCA"), x = c(0.03, 0.03, 0.03, 0.03), y = c(0.97, 0.71, 0.53, 0.3))

And we can add utilization for the different enzymes and conditions too.

plot_util_ccm <- lapply(list_ccm_enzymes, function(lst) {
  df_prot_comp %>%
  filter(reaction_id %in% lst, !is.na(condition)) %>%
  mutate(reaction_id = factor(reaction_id, lst)) %>%
  # calculate utilization
  mutate(percent_utilization = predicted_mass_g_gDCW/mass_g_gDCW*100) %>%
  
  xyplot(percent_utilization ~ factor(growth_rate) | reaction_id, .,
    par.settings = custom.colorblind(),
    groups = substrate,
    xlab = "", ylab = "% utilization", ylim = c(-20, 200),
    layout = c(8, ifelse(length(lst) > 8, 2, 1)),
    ewidth = 0, lwd = 2, as.table = TRUE,
    between = list(x = 0.5, y = 0.5),
    scales = list(alternating = FALSE, x = list(at = c(2, 4))),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.errbars(x, y, ...)
    }
  )
})

print(plot_util_ccm[[1]], position = c(0.0,0.67,1,1), more = TRUE)
print(plot_util_ccm[[2]], position = c(0,0.51,1,0.75), more = TRUE)
print(plot_util_ccm[[3]], position = c(0,0.26,1,0.59), more = TRUE)
print(plot_util_ccm[[4]], position = c(0,0,1,0.34))
grid::grid.text(c("CBB", "ED", "PYR", "TCA"), x = c(0.03, 0.03, 0.03, 0.03), y = c(0.97, 0.71, 0.53, 0.3))

Finally we can take a look on trends with growth rate, by fitting a linear model for each enzyme and condition over growth rate, and plotting the R or R squared per condition. The R (correlation coefficient) should indicate the trend, i.e. increasing or decreasing with growth rate for a certain substrate.

plot_lm_ccm <- lapply(list_ccm_enzymes, function(lst) {
  
  df_prot_comp %>%
  filter(reaction_id %in% lst, !is.na(condition)) %>%
  mutate(reaction_id = factor(reaction_id, lst)) %>%
  group_by(reaction_id) %>% mutate(mass_g_gDCW = scales::rescale(mass_g_gDCW)) %>%
  
  # fit linear model
  group_by(reaction_id, substrate) %>%
  summarize(
    lin_reg_slope = summary(lm(mass_g_gDCW ~ growth_rate))$coef[2],
    lin_reg_pvalue = summary(lm(mass_g_gDCW ~ growth_rate))$coef[8]
  ) %>%
  mutate(lin_reg_slope = lin_reg_slope %>% 
    if_else(. > 2.5, 2.5, .) %>% if_else(. < -2.5, -2.5, .)) %>%
  
  levelplot(lin_reg_slope ~ reaction_id * factor(substrate, unique(substrate)[c(3,2,1,4)]), .,
    par.settings = custom.colorblind(), at = seq(-2.5, 2.5, by = 0.25),
    col.regions = colorspace::diverge_hcl(20), ylab = "",
    as.table = TRUE, xlab = "",
    scales = list(x = list(cex = 0.65))
  )
})
`summarise()` has grouped output by 'reaction_id'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'reaction_id'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'reaction_id'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'reaction_id'. You can override using the `.groups` argument.
print(plot_lm_ccm[[1]], position = c(0.0,0.68,1,1), more = TRUE)
print(plot_lm_ccm[[2]], position = c(0,0.46,1,0.77), more = TRUE)
print(plot_lm_ccm[[3]], position = c(0,0.24,1,0.55), more = TRUE)
print(plot_lm_ccm[[4]], position = c(0,0,1,0.32))
grid::grid.text(c("CBB", "ED", "PYR", "TCA"), x = c(0.03, 0.03, 0.03, 0.03), y = c(0.97, 0.73, 0.5, 0.28))

7.3 Example of underutilization of enzymes from Cbb operon

Similar to machinery proteins we can also follow the simulation and actual protein abundance of all (detected) enzymes. We can see that some protein abundances nicely follow a growth-rate dependent manner, in line with predictions of higher flux. One such example are Calvin cycle enzymes for formatotrophic growth. However, for the same enzymes we see that no abundance is predicted under heterotrophic conditions because of missing flux through the pathway, but in fact the proteins are expressed in high abundance, often in a growth-rate dependent manner.

cbb_selected <- c("PGK", "GAPD", "FBA", "FBP", "TKT1", "PRUK", "RBPC")
# "TKT2", "TAh", "RPE", "RPI"

# summary table of selected Calvin cycle genes
df_calvin <- df_prot_comp %>%
  
  # remove 1 non-quantified replicate
  filter(!(substrate == "fructose" & growth_rate == 0.1 & replicate == "R2")) %>%
  
  # select only subset of reactions
  filter(reaction_id %in% cbb_selected) %>%
  mutate(reaction_id = factor(reaction_id, cbb_selected)) %>%
  rename(`mass [g/gDCW]` = mass_g_gDCW, `predicted mass [g/gDCW]` = predicted_mass_g_gDCW) %>%
  mutate(utilization = `predicted mass [g/gDCW]`/`mass [g/gDCW]`)


# plot mass and utilization
plot_calvin <- #doubleYScale(use.style = FALSE, under = TRUE,
  
  xyplot(`mass [g/gDCW]` + `predicted mass [g/gDCW]` ~ factor(growth_rate) | 
    reaction_id * substrate, df_calvin,
    par.settings = custom.colorblind(), auto.key = list(columns = 2, cex = 0.7),
    xlab = expression("µ [h"^-1*"]"), ylim = c(-0.003, 0.033), #c(-0.002, 0.032),
    ylab = expression("m"[protein]*" [g gDCW"^-1*"]"),
    scales = list(alternating = FALSE, x = list(at = c(2,4)),
      y = list(at = c(0, 0.01, 0.02, 0.03))), as.table = TRUE,
    between = list(x = 0.5, y = 0.5), lwd = 2, 
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.errbars(x, y, ewidth = 0, ...)
      panel.superpose(x, y, ...)
    }, panel.groups = function(x, y, ...) {
      panel.lmline(x, y, ...)
    }
  )

print(useOuterStrips(plot_calvin))

It is clear from the previous analysis that the cells maintain proteins even if they are not or only marginally utilized. The actually utilized proteome is minimal under strong substrate limitation. The next section will try to quantify the under-utilization of enzymes by comparing the minimal protein requirement (simulation) versus experimentally determined protein abundance. It is sufficient to determine the underutilized protein fraction (of all utilized proteins).

plot_calvin_util <- df_prot_comp %>%
  
  # filter set of enzymes
  filter(reaction_id %in% c("PGK", "GAPD", "FBA", "FBP", "TKT1", "PRUK", "RBPC")) %>%
  
  # remove 1 non-quantified replicate
  filter(!(substrate == "fructose" & growth_rate == 0.1 & replicate == "R2")) %>%
  
  # calculate protein utilization
  group_by(substrate, growth_rate, replicate) %>%
  summarize(
    mass = sum(mass_g_gDCW, na.rm = TRUE),
    predicted_mass = sum(predicted_mass_g_gDCW, na.rm = TRUE),
    percent_utilization = predicted_mass/(mass)
  ) %>%
  
  # add pseudo enzyme group
  mutate(enzyme = "CBB cycle") %>%
  
  # optionally show some summary statistics
  #summarize(across(matches("mass|util"), mean)) %>%
  #mutate(unutil_mass = mass-predicted_mass) %>%
  #summarize(max(unutil_mass)/0.68*100) #for percent: /0.68*100
  
  # plot
  xyplot(percent_utilization*100 ~ factor(growth_rate) | enzyme * substrate, .,
    par.settings = custom.colorblind(), ylim = c(-10, 110),
    scales = list(alternating = FALSE, x = list(at = c(2,4))),
    xlab = expression("µ [h"^-1*"]"), ylab = "",
    as.table = TRUE, between = list(x = 0.5, y = 0.5),
    lwd = 2, key = simpleKey("% utilization", cex = 0.7),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      x_mean = unique(x); y_mean = tapply(y, x, mean)
      panel.xyarea(c(0, x_mean, 6), c(0, y_mean, tail(y_mean, 1)), 
        lty = 0, col = grey(0.6, alpha = 0.5), ...)
      panel.errbars(x, y, ewidth = 0, col = grey(0.5), ...)
    }
  ) %>% useOuterStrips
`summarise()` has grouped output by 'substrate', 'growth_rate'. You can override using the `.groups` argument.
print(plot_calvin_util)


A final control is to check which copies of the same Cbb cycle enzymes are more abundant, or undergo stronger changes. For example, PRUK and Rubisco (RBPC) are only present on the two copies of the Calvin cycle operon, on pHG1 and Chromosome 2. But other glycolysis related enzymes have one canonical copy on chromosome 1, which seems to encode mostly “housekeeping” functions. We can look at protein abundance of the three single gene loci for each enzyme (chromosome 1, 2, and pHG1 megaplasmid). It’s important to keep in mind that most peptides for cbb genes can not be distinguished between chromosome 2 and pHG1, therefore the similar expression pattern.

plot_calvin_loci <- df_model_reactions %>% filter(reaction_id %in% c("PGK", "GAPD", "FBA", "FBP", "TKT1")) %>%
  select(reaction_id, genes) %>% rename(locus_tag = genes) %>%
  filter(locus_tag != "H16_B0278") %>%
  inner_join(Ralstonia_eutropha) %>%
  mutate(chromosome = case_when(
    grepl("H16_A", locus_tag) ~ "chromosome 1",
    grepl("H16_B", locus_tag) ~ "chromosome 2",
    grepl("PHG", locus_tag) ~ "pHG1",
  )) %>%
  
  xyplot(mass_g_gDCW ~ factor(growth_rate) | reaction_id * chromosome, .,
    groups = substrate, layout = c(5, 3),
    par.settings = custom.colorblind(), lwd = 2,
    scales = list(alternating = FALSE),
    xlab = expression("µ [h"^-1*"]"),
    ylab = expression("m"[protein]*" [g gDCW"^-1*"]"),
    as.table = TRUE, between = list(x = 0.5, y = 0.5),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.errbars(x, y, ewidth = 0, type = c("p", "l"), ...)
      panel.key(..., cex = 0.7)
    }
  )
Joining, by = "locus_tag"
print(plot_calvin_loci)

8 Expression and utilization of PHB synthesis enzymes

The pathway for PHB synthesis consists mainly of three enzymes, each one with multiple genes annotated. The first is Acetyl-CoA C-acetyltransferase (phaA), making acetoacetyl-CoA, the second one is Acetoacetyl-CoA reductase (phaB), making 3-hydroxy-butyryl-CoA, and the third is the PHB-synthase condensing 3HB monomers to PHB polymer (phaC). The mass for each enzyme is the sum of the individual protein masses allocated to this reaction.

plot_phb_mass <- df_prot_comp %>%
  filter(reaction_id %in% c("PHAS", "ACACT1r", "AACOAR")) %>%
  mutate(reaction_id = recode(reaction_id,
    PHAS = "PHAS (phaC)", ACACT1r = "ACACT1r (phaA)", AACOAR = "AACOAR (phaB)")) %>%
  
  xyplot(mass_g_gDCW ~ factor(growth_rate) | 
      factor(reaction_id, unique(reaction_id)[c(2,1,3)]), .,
    par.settings = custom.colorblind(),
    groups = substrate, xlab = "",
    ylab = expression("m"[protein]*" [g gDCW"^-1*"]"),
    layout = c(3, 1), ewidth = 0, lwd = 2, as.table = TRUE,
    between = list(x = 0.5, y = 0.5), auto.key = list(columns = 2),
    scales = list(alternating = FALSE, x = list(at = c(2, 4))),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.errbars(x, y, ...)
    }
  )

plot_phb_util <- df_prot_comp %>%
  filter(reaction_id %in% c("PHAS", "ACACT1r", "AACOAR")) %>%
  mutate(percent_utilization = predicted_mass_g_gDCW/mass_g_gDCW*100) %>%
  mutate(reaction_id = recode(reaction_id, 
    PHAS = "PHAS (phaC)", ACACT1r = "ACACT1r (phaA)", AACOAR = "AACOAR (phaB)")) %>%
  
  xyplot(percent_utilization ~ factor(growth_rate) | 
      factor(reaction_id, unique(reaction_id)[c(2,1,3)]), .,
    par.settings = custom.colorblind(),
    groups = substrate,
    xlab = expression("µ [h"^-1*"]"),
    ylab = "% utilization", ylim = c(-20, 320),
    layout = c(3, 1),
    ewidth = 0, lwd = 2, as.table = TRUE,
    between = list(x = 0.5, y = 0.5),
    scales = list(alternating = FALSE, x = list(at = c(2, 4))),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.errbars(x, y, ...)
    }
  )

print(plot_phb_mass, position = c(0,0.42,1,1), more = TRUE)
print(plot_phb_util, position = c(0.04,0,1,0.55))

9 Yield-growth rate tradeoff when Cupriavidus re-assimilates CO2?

It was experimentally observed before that R. eutropha expresses Rubisco and other cbb-operon located genes even on growth on fructose or other heterotrophic substrates. Bowien et al., 1990, show that Rubisco activity can be found on growth on fructose, but not on pyruvate. Dangel & Tabita, 2015, review the regulation by CbbR type regulators among others also in R. eutropha and mention that citrate also leads to activation of cbb expression. They hypothesize that ribulose bisphosphate (RuBP) is an activating effector while other central metabolism intermediates such as phosphoenolpyruvate (PEP) are repressing effector molecules. It was speculated in Bowien et al. that regulation by cbbR might actually be a repression-derepression rather than activation, which means that the default state would be a bound cbbR repressing the cbb operon. However it became clear that this is not the case. Shimizu et al., 2015, knocked the cbbR gene out and the result was reduced expression of cbb genes by 100 fold. This proves that cbbR is a required activator and not a repressor of cbb, otherwise cbbR deletion would lead to constitutive activation of cbb expression.

The same group speculated in their study that the additional Rubisco expression could have a benefit for carbon yield, specifically for product yield of PHB. They show that PHB from the WT contains slightly more 13C labeled mass (and total mass) than the cbbR and cbbS/L knockouts. This means that the cell would have a (product or biomass) yield advantage by Rubisco expression on fructose.

The following section tests the hypothesis of a yield-growth rate tradeoff with the resource allocation model. First simulation data from the RBA model is imported

#adjust path
mixotroph_dir <- gsub("substrate_limitation", "mixotrophy", simulation_dir)

# read simulation results
df_mixflux <- read_rba_result(list.files(mixotroph_dir, pattern = "fluxes_.*.tsv$", full.names = TRUE))
df_mixmacr <- read_rba_result(list.files(mixotroph_dir, pattern = "macroprocesses_.*.tsv", full.names = TRUE))

# rename column
df_mixflux <- df_mixflux %>% rename(CO2_refixation = sim_run) %>% filter(CO2_refixation <= 5)
df_mixmacr <- df_mixmacr %>% rename(CO2_refixation = sim_run) %>% filter(CO2_refixation <= 5)

The next task is to compare yield, growth rate, CO2 emission and other fluxes for a range of simulations where Rubisco was forced to re-fix emitted CO2 from growth on fructose. Simulations were performed for increasing flux through Rubisco from 0 to 5 mmol gDCW-1 h-1.

plot_mixo_mu <- lapply(c("mu", "yield"), function(keys) {
  
  if (keys == "mu") {
    ylabel <- expression("µ [h"^-1*"]")
    ylim <- c(0.001, 0.3)
  } else {
    ylabel <- expression("Y [gDCW gS"^-1*"]")
    ylim <- c(0.001, 0.399)
  }
  
  
  xyplot(value ~ factor(CO2_refixation),
    filter(df_mixmacr, key == keys, carbon_conc == 1.25),
    type = "b", lwd = 2, par.settings = custom.colorblind(), 
    xlab = expression("CO"[2]*" uptake [mmol h"^-1*" gDCW"^-1*"]"),
    ylab = ylabel, ylim = ylim,
    panel = function(x, y, z, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.xyplot(x, y, ...)
    }
  )
})

print(plot_mixo_mu[[1]], position = c(0,0,0.53,1), more = TRUE)
print(plot_mixo_mu[[2]], position = c(0.47,0,1,1))

There is no increase in growth rate or yield with additional CO2 fixation according to the RBA model. The yield is in fact calculated based on the growth rate µ and the substrate uptake rate qS. Yield and growth rate depend on each other in the relation Y [gDCW/gS] = µ [h-1] / qS [gS gDCW-1 h-1].

Since only the initial substrate concentration is constrained, the model could predict a lower substrate uptake rate or lower CO2 emission per biomass in order to reach a yield increase. However this was not the case under any simulation, see below for details of specific metabolites.

The model seems to predict a high-yield phenotype already. If CO2 re-fixation would have had an advantage for growth, it might have been detected earlier depending on the protein costs for the respective pathway. Before a final verdict, we can follow the fate of the fixed CO2 through the metabolism.

plot_mixo_enz <- xyplot(abs(value) ~ as.factor(CO2_refixation) | 
    factor(key, unique(key)), layout = c(2, 2),
  filter(df_mixflux, key %in% c("CO2t", "EDD", "CS", "O2t"), carbon_conc == 1.25),
  par.settings = custom.colorblind(),
  type = "b", lwd = 2, between = list(x = 0.5, y = 0.5), as.table = TRUE,
  xlab = expression("CO"[2]*" uptake [mmol h"^-1*" gDCW"^-1*"]"),
  ylab = expression("flux [mmol h"^-1*"gDCW"^-1*"]"),
  scales = list(alternating = FALSE),
  panel = function(x, y, z, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.xyplot(x, y, ...)
  }
)

print(plot_mixo_enz)

9.1 Conclusion

It becomes clear that yield and growth rate decrease, and not increase, with additional CO2 fixation, because:

  • energy requirement for CO2 fixation leads to lower flux through ED pathway, but higher flux through TCA
  • this is in order to generate the required NADH/ATP for CO2 fixation
  • respiration and O2 consumption also increases with forced mixotrophic growth
  • there is no net reduction of CO2 emission. In fact cells emit more CO2 even when they fix some of it due to increased energy requirement
  • storing some of the fixed CO2 as PHB would not increase biomass yield as additional energy requirement still persists
  • cells should not be able to make more PHB using this strategy, regardless of biomass yield. If acetyl-CoA is drained for PHB, even less energy is made available through TCA and OXPHOS.

10 Miniature RBA flux maps

Install my small R package fluctuator to overlay fluxes on SVG metabolic maps. The template map is a simplified metabolic map of C. necator’s central metabolism.

First we plot a map of the mixotrophic simulation. Then we use the same work-flow and template to plot metabolic flux maps for growth on the three main substrates, fructose, succinate and formate (maximum tested growth rate, 0.25 h^-1).

library(fluctuator)

# import map
SVG_map <- read_svg("../data/simulation/central_metabolism.svg")
#get_attributes(SVG_map, node = "key_01") %>% pull(style)

# prepare data: mixotrophic growth
df_mixflux <- df_mixflux %>% filter(CO2_refixation == 3) %>%
  # add fluxes for legend entries
  add_row(key = c("key_01", "key_12", "key_24", "key_48"), value = c(1,2,4,8)) %>%
  # add stroke width to simulation
  mutate(stroke_width = 0.2 + 0.2*sqrt(abs(value))) %>%
  inner_join(select(SVG_map@summary, label), by = c("key" = "label")) %>%
  distinct

# prepare data: carbon-limited growth
df_carbflux <- df_flux %>%
  filter(simulation %in% c(
    "for_0.487_nh4_18.7_10", "fru_1.223_nh4_18.7_0", "succ_0.662_nh4_18.7_5")) %>%
  # add fluxes for legend entries
  add_row(key = rep(c("key_01", "key_12", "key_24", "key_48"), each = 3),
    value = rep(c(1,2,4,8), each = 3), carbon_source = rep(c("for", "fru", "succ"), 4)) %>%
  # add stroke width to simulation
  mutate(stroke_width = 0.2 + 0.2*sqrt(abs(value))) %>%
  inner_join(select(SVG_map@summary, label), by = c("key" = "label")) %>%
  distinct

Plot all four conditions in a loop.

lapply(
  list(
    mutate(df_mixflux, carbon_source = "mix"),
    filter(df_carbflux, carbon_source == "fru"),
    filter(df_carbflux, carbon_source == "for"),
    filter(df_carbflux, carbon_source == "succ")
  ), FUN = function(df) {
    
    # reload base map for every instance, as the SVG is altered
    # even without assignment (python style, untypical in R)
    SVG_map <- read_svg("../data/simulation/central_metabolism.svg")
    
    # modify map
    SVG2 <- set_attributes(SVG_map,
      node = df$key, attr = "style",
      pattern = "stroke-width:[0-9]+\\.[0-9]+",
      replacement = paste0("stroke-width:", df$stroke_width))
    
    # turn non-zero fluxes into darker color
    SVG2 <- set_attributes(SVG2,
      node = filter(df, value != 0) %>% pull(key), attr = "style",
      pattern = "stroke:#b3b3b3",
      replacement = "stroke:#808080")
    
    # rescale arrow heads
    SVG2 <- set_attributes(SVG2, 
      node = grep("marker", SVG2@summary$id, value = TRUE),
      node_attr = "id",
      attr = "transform",
      pattern = "scale\\(0.2\\)",
      replacement = "scale(0.15)")
    
    SVG2 <- set_attributes(SVG2, 
      node = grep("marker", SVG2@summary$id, value = TRUE),
      node_attr = "id",
      attr = "transform",
      pattern = "scale\\(-0.2\\)",
      replacement = "scale(-0.15)")
    
    # export map as SVG again
    #write_svg(SVG2, file = paste0("../data/simulation/central_metabolism_", df$carbon_source[1], ".svg"))
  }
) %>% invisible()
Metabolic flux with CO2 refixation Metabolic flux on fructose
Metabolic flux on succinate Metabolic flux on formic acid
LS0tCnRpdGxlOiAiRW56eW1lIHV0aWxpemF0aW9uIGFuZCBzYXR1cmF0aW9uIGZvciAqUi4gZXV0cm9waGEqIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOiAKICAgIHRoZW1lOiBjb3NtbwogICAgdG9jOiB5ZXMKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQotLS0KCgojIERlc2NyaXB0aW9uCgpUaGlzIFIgbm90ZWJvb2sgaXMgYSBiaW9pbmZvcm1hdGljcyBwaXBlbGluZSB0byAqKmFuYWx5emUgcHJvdGVpbiBzYXR1cmF0aW9uL3VuZGVyLXV0aWxpemF0aW9uIHdpdGggYSByZXNvdXJjZSBhbGxvY2F0aW9uIG1vZGVsKiogZm9yIHRoZSBjaGVtb2xpdGhvYXV0b3Ryb3BoICpSYWxzdG9uaWEgZXV0cm9waGEqIChhLmsuYS4gKkN1cHJpYXZpZHVzIG5lY2F0b3IqKS4KCgojIExpYnJhcmllcwoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsKICBsaWJyYXJ5KGxhdHRpY2UpCiAgbGlicmFyeShsYXR0aWNlRXh0cmEpCiAgbGlicmFyeShsYXR0aWNldG9vbHMpCiAgbGlicmFyeSh0aWR5dmVyc2UpCiAgbGlicmFyeShzdHJpbmdpKQp9KQpgYGAKCiMgRGF0YSBpbXBvcnQKCkRlZmluZSB0aGUgZGF0YSBzb3VyY2UgZGlyZWN0b3JpZXMuIFNvbWUgb2YgdGhlbSBhcmUgZXh0ZXJuYWwgaW4gdGhlIHNlbnNlIG9mIG5vdCBpbmNsdWRlZCBpbiB0aGUgYWNjb21wYW55aW5nIGRhdGEgZm9sZGVyIG9mIHRoaXMgUiBub3RlYm9vay4gVGhlc2UgYXJlIGxvY2F0ZWQgaW4gdGhlIGFjY29tcGFueWluZyBnaXRodWIgcmVwb3NpdG9yeSBmb3IgdGhlIHJlc291cmNlIGFsbG9jYXRpb24gbW9kZWwgdGhhdCB3YXMgdXNlZCBoZXJlLiBUaGUgcmVzb3VyY2UgYWxsb2NhdGlvbiBtb2RlbCBjYW4gYmUgZm91bmQgYXQgbXkgZm9yayBvZiBbQmFjdGVyaWFsLVJCQS1tb2RlbHNdKGh0dHBzOi8vZ2l0aHViLmNvbS9tLWphaG4vQmFjdGVyaWFsLVJCQS1tb2RlbHMpLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KUmV1dHJvcGhhX3Byb3Rlb21pY3MgPC0gIi4uL2RhdGEvaW5wdXQvUmFsc3RvbmlhX2V1dHJvcGhhLlJkYXRhIgptb2RlbF9yZWFjdGlvbnMgPC0gIi4uL2RhdGEvaW5wdXQvbW9kZWxfcmVhY3Rpb25zLmNzdiIKc2ltdWxhdGlvbl9kaXIgPC0gIi4uL2RhdGEvc2ltdWxhdGlvbi9zdWJzdHJhdGVfbGltaXRhdGlvbi8iCnNvdXJjZSgicmVhZF9yYmFfcmVzdWx0LlIiKQpgYGAKCgpSZWFkIHNpbXVsYXRpb24gZGF0YS4KCmBgYHtyfQojIHJlYWQgc2ltdWxhdGlvbiByZXN1bHRzCmRmX2ZsdXggPC0gcmVhZF9yYmFfcmVzdWx0KGxpc3QuZmlsZXMoc2ltdWxhdGlvbl9kaXIsIHBhdHRlcm4gPSAiZmx1eGVzXy4qLnRzdiQiLCBmdWxsLm5hbWVzID0gVFJVRSkpCmRmX3Byb3QgPC0gcmVhZF9yYmFfcmVzdWx0KGxpc3QuZmlsZXMoc2ltdWxhdGlvbl9kaXIsIHBhdHRlcm4gPSAicHJvdGVpbnNfLioudHN2IiwgZnVsbC5uYW1lcyA9IFRSVUUpKQpkZl9tYWNyIDwtIHJlYWRfcmJhX3Jlc3VsdChsaXN0LmZpbGVzKHNpbXVsYXRpb25fZGlyLCBwYXR0ZXJuID0gIm1hY3JvcHJvY2Vzc2VzXy4qLnRzdiIsIGZ1bGwubmFtZXMgPSBUUlVFKSkKYGBgCgoKIyBPdmVydmlldyBvbiBzdWJzdHJhdGUgdXB0YWtlLCBncm93dGgsIGFuZCB5aWVsZAoKQWZ0ZXIgcnVubmluZyBhIHNldCBvZiBzaW11bGF0aW9ucyBpbiBSQkFweSB0aGF0IHNpbXVsYXRlIGluY3JlYXNpbmcgc3Vic3RyYXRlIGxpbWl0YXRpb24sIHdlIGNhbiBwbG90IHRoZSBzdWJzdHJhdGUgdXB0YWtlIHJhdGUgYHFgLCB5aWVsZCBgWWAgaW4gZ3JhbSBiaW9tYXNzIHBlciBncmFtIHN1YnN0cmF0ZSwgYW5kIGdyb3d0aCByYXRlIGDCtWAuIFVubGlrZSBnZW5vbWUgc2NhbGUgbW9kZWxzLCBncm93dGggYmVjb21lcyBsaW1pdGVkIGJ5IHRoZSBtYXhpbXVtIGFtb3VudCBvZiBwcm90ZWlucyB0aGF0IGEgY2VsbCBjYW4gc3ludGhlc2l6ZS4gSWYgY2VsbHMgd291bGQgbm90IGJlIHByb3RlaW4tbGltaXRlZCwgKm9yKiBwcm90ZWlucyB3b3VsZCBjYXRhbHl6ZSByZWFjdGlvbnMgaW5maW5pdGVseSBmYXN0LCBubyBzdWNoIGxpbWl0YXRpb24gd291bGQgdGFrZSBwbGFjZSBhbmQgZ3Jvd3RoIHJhdGUgd291bGQgc2NhbGUgbGluZWFybHkgd2l0aCBzdWJzdHJhdGUgY29uY2VudHJhdGlvbi4gVGhpcyBpcyB0aGUgc2l0dWF0aW9uIGluIEZCQSBzaW11bGF0aW9uLgoKYGBge3IsIGZpZy53aWR0aCA9IDYuNywgZmlnLmhlaWdodCA9IDYuNywgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CiMgcmVhcnJhbmdlIHNvbWUgcm93cyAobXUsIHFTKSB0byBjb2x1bW5zCmRmX21hY3IgPC0gZGZfbWFjciAlPiUgZmlsdGVyKCFncmVwbCgidGVzdF9wcm9jZXNzIiwga2V5KSkgJT4lIAogIHNwcmVhZChrZXksIHZhbHVlKSAlPiUKICAKICAjIGFkZCB0eXBlIG9mIHNpbXVsYXRpb24KICBtdXRhdGUoc3Vic3RyYXRlID0gY2FzZV93aGVuKAogICAgY2FyYm9uX3NvdXJjZSA9PSAiZm9yIiB+ICJmb3JtYXRlIiwKICAgIGNhcmJvbl9zb3VyY2UgPT0gInN1Y2MiIH4gInN1Y2NpbmF0ZSIsCiAgICBjYXJib25fc291cmNlID09ICJmcnUiICYgbml0cm9nZW5fY29uYyA9PSAxOC43IH4gImZydWN0b3NlIiwKICAgIGNhcmJvbl9zb3VyY2UgPT0gImZydSIgJiBuaXRyb2dlbl9jb25jICE9IDE4LjcgfiAiYW1tb25pdW0iCiAgKSkgJT4lCiAgCiAgIyBhZGQgdXB0YWtlIHJhdGUgaW4gZy9nRENXKmggaW5zdGVhZCBvZiBtbW9sCiAgbXV0YXRlKHFTX2dfZ0RDV19oID0gY2FzZV93aGVuKAogICAgc3Vic3RyYXRlID09ICJmb3JtYXRlIiB+IHFTKjAuMDQ2MDMsCiAgICBzdWJzdHJhdGUgPT0gInN1Y2NpbmF0ZSIgfiBxUyowLjExODA5LAogICAgc3Vic3RyYXRlID09ICJmcnVjdG9zZSIgfiBxUyowLjE4MDE2LAogICAgc3Vic3RyYXRlID09ICJhbW1vbml1bSIgfiBxUyowLjA1MzQ5CiAgKSkKYGBgCgpGaXJzdCB3ZSBjYW4gaGF2ZSBhIGxvb2sgYXQgaG93IGdyb3d0aCByYXRlIGxldmVscyBvZmYgd2l0aCBpbmNyZWFzaW5nIHN1YnN0cmF0ZSAqY29uY2VudHJhdGlvbiogaW4gbW1vbC9MLiBOb3RlOiB0aGlzIGlzIG5vdCBlcXVhbCB0byAqc3Vic3RyYXRlIHVwdGFrZSByYXRlKi4gU3Vic3RyYXRlIHVwdGFrZSByYXRlIGFuZCBncm93dGggcmF0ZSBzaG91bGQgaGF2ZSBhbiBhbG1vc3QgbGluZWFyIHJlbGF0aW9uc2hpcC4gV2UgY2FuIGxvZy10cmFuc2Zvcm0gdGhlIGNhcmJvbiBjb25jZW50cmF0aW9uIGFuZCBmaXQgYSBsaW5lYXIgbW9kZWwgdG8gcHJlZGljdCB0aGUgc3Vic3RyYXRlIGNvbmNlbnRyYXRpb24gcmVxdWlyZWQgdG8gb2J0YWluIGEgY2VydGFpbiBzdWJzdHJhdGUgdXB0YWtlIHJhdGUgYW5kIGdyb3d0aCByYXRlLgoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA2fQojIGNvcHkgbml0cm9nZW4gdG8gY2FyYm9uIGNvbmNlbnRyYXRpb24gZm9yIGFtbW9uaXVtIGxpbWl0YXRpb24sCiMganVzdCBmb3IgcGxvdHRpbmcgcHVycG9zZXMKZGZfbWFjcl92aXogPC0gZGZfbWFjciAlPiUKICBtdXRhdGUoY2FyYm9uX2NvbmMgPSBjYXNlX3doZW4oCiAgICBzdWJzdHJhdGUgPT0gImFtbW9uaXVtIiB+IG5pdHJvZ2VuX2NvbmMsCiAgICBUUlVFIH4gY2FyYm9uX2NvbmMKICApKQoKcGxvdF9tdV9xc19saW4gPC0geHlwbG90KG11IH4gY2FyYm9uX2NvbmMgfCBzdWJzdHJhdGUsIGRmX21hY3Jfdml6LAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwKICAgIGJldHdlZW4gPSBsaXN0KHggPSAwLjUsIHkgPSAwLjUpLAogICAgbGF5b3V0ID0gYyg0LDEpLCBsd2QgPSAxLjUsIHBjaCA9IDE5LAogICAgeGxhYiA9IGV4cHJlc3Npb24oIlMgW21NXSIpLAogICAgeWxhYiA9IGV4cHJlc3Npb24oJ8K1IFtoJ14nLTEnKiddJyksCiAgICBzY2FsZXMgPSBsaXN0KGFsdGVybmF0aW5nID0gRkFMU0UpLAogICAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCAuLi4pIHsKICAgICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgICBwYW5lbC54eXBsb3QoeCwgeSwgY2V4ID0gMC45LCAuLi4pCiAgICAgICNwYW5lbC5sbWxpbmVxKHhbMTo0XSwgeVsxOjRdLCBmb250ZmFtaWx5ID0gIkZyZWVTYW5zIiwgLi4uKQogICAgfQogICkKCnBsb3RfbXVfcXNfbG9nIDwtIHh5cGxvdChsb2cxMChjYXJib25fY29uYykgfiBxUyB8IHN1YnN0cmF0ZSwKICAgIGRmX21hY3Jfdml6LAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwKICAgIGJldHdlZW4gPSBsaXN0KHggPSAwLjUsIHkgPSAwLjUpLCAjeGxpbSA9IGMoMCwgNSksCiAgICBsYXlvdXQgPSBjKDQsMSksIGx3ZCA9IDEuNSwgcGNoID0gMTksCiAgICB4bGFiID0gZXhwcmVzc2lvbigncSdbU10qJyBtbW9sIGcgRENXJ14tMSonaCdeLTEpLAogICAgeWxhYiA9ICBleHByZXNzaW9uKCdsb2cnWzEwXSonIFMgW21NXScpLAogICAgc2NhbGVzID0gbGlzdChhbHRlcm5hdGluZyA9IEZBTFNFKSwKICAgIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgICAgcGFuZWwueHlwbG90KHgsIHksIGNleCA9IDAuOSwgLi4uKQogICAgICBwYW5lbC5sbWxpbmVxKHhbMTo2XSwgeVsxOjZdLCBmb250ZmFtaWx5ID0gIkZyZWVTYW5zIiwgLi4uKQogICAgfQogICkKCnByaW50KHBsb3RfbXVfcXNfbGluLCBzcGxpdCA9IGMoMSwxLDEsMiksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X211X3FzX2xvZywgc3BsaXQgPSBjKDEsMiwxLDIpKQoKZGZfbWFjcl92aXogJT4lCiAgZ3JvdXBfYnkoc3Vic3RyYXRlKSAlPiUKICAjIGZpdCBsaW5lYXIgbW9kZWwgdG8gc3Vic3RyYXRlIHVwdGFrZSByYXRlIHZzIGNvbmNlbnRyYXRpb24KICBzdW1tYXJpemUoCiAgICBzbG9wZSA9IGxtKHggfiB5LCBkYXRhID0gbGlzdCh4ID0gbG9nMTAoY2FyYm9uX2NvbmMpLCB5ID0gcVMpKSRjb2VmZlsyXSwKICAgIG9mZnNldCA9IGxtKHggfiB5LCBkYXRhID0gbGlzdCh4ID0gbG9nMTAoY2FyYm9uX2NvbmMpLCB5ID0gcVMpKSRjb2VmZlsxXQogICkgJT4lIG11dGF0ZShtb2RlbCA9IHBhc3RlMCgiYyA9IDEwXigiLCByb3VuZChvZmZzZXQsIDMpLCAiICsgIiwgcm91bmQoc2xvcGUsIDMpLCAiKnFTKSIpKQpgYGAKCkNyZWF0ZSBhIEhlcmJlcnQtUGlydCBwbG90IGZvciBlYWNoIGNvbmRpdGlvbiAoZ3Jvd3RoIHJhdGUgdmVyc3VzIHN1YnN0cmF0ZSB1cHRha2UgcmF0ZSkuIFRoaXMgcGxvdCB3b3VsZCBzaG93IGEgY2hhbmdlIGluIHlpZWxkIGJ5IGEgJ2tpbmsnIG9mIHRoZSBkYXRhIHBvaW50cy4KCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gM30KeHlwbG90KHFTX2dfZ0RDV19oIH4gbXUgfCBzdWJzdHJhdGUsIGRmX21hY3IsCiAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwKICBiZXR3ZWVuID0gbGlzdCh4ID0gMC41LCB5ID0gMC41KSwKICBsYXlvdXQgPSBjKDQsMSksIGx3ZCA9IDEuNSwgcGNoID0gMTksCiAgeWxhYiA9IGV4cHJlc3Npb24oInEiW1NdKiIgW2cgaCJeLTEqIiBnRENXIl4tMSoiXSIpLAogIHhsYWIgPSBleHByZXNzaW9uKCfCtSBbaCdeJy0xJyonXScpLAogIHNjYWxlcyA9IGxpc3QoYWx0ZXJuYXRpbmcgPSBGQUxTRSksCiAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCAuLi4pIHsKICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgIHBhbmVsLnh5cGxvdCh4LCB5LCBjZXggPSAwLjksIC4uLikKICAgICMgZGlzcGxheWluZyBtYWludGVuYW5jZSBhbmQgeWllbGQgY29lZmZpY2llbnRzCiAgICBjb2VmIDwtIGxtKHkgfiB4LCBkYXRhLmZyYW1lKHgsIHkpKSRjb2VmZgogICAgcGFuZWwudGV4dChtZWRpYW4oeCksIDIuNywgCiAgICAgIHBhc3RlKCJtcyA9Iiwgcm91bmQoY29lZltbMV1dLCAzKSwgImcgaC0xIGdfRENXLTEiKSwgCiAgICAgIGNvbCA9IGdyZXkoMC4zKSwgY2V4ID0gMC43KQogICAgcGFuZWwudGV4dChtZWRpYW4oeCksIDIuNCwgcGFzdGUoZXhwcmVzc2lvbigiWXgvUyA9IiksIAogICAgICAgIHJvdW5kKDEvY29lZltbMl1dLCAzKSwgImdfRENXIGdfUy0xIiksIAogICAgICBjb2wgPSBncmV5KDAuMyksIGNleCA9IDAuNykKICB9CikKYGBgCgoKIyBSZXNvdXJjZSBhbGxvY2F0aW9uIGluIHRlcm1zIG9mIHByb3RlaW4gbWFzcwoKVG8gZGV0ZXJtaW5lIHRoZSB0cnVlIGFsbG9jYXRpb24gb2YgcHJvdGVpbiByZXNvdXJjZXMgcGVyIGNvbXBhcnRtZW50LCBidXQgYWxzbyB0aGUgdHJ1ZSBjb3N0IG9mIHByb3RlaW4gcGVyIHByb2Nlc3MsIHdlIG5lZWQgdG8gKipjb252ZXJ0IHRoZSBwcmVkaWN0ZWQgY29uY2VudHJhdGlvbiBvZiBwcm90ZWlucyBpbiBtbW9sIHBlciBnRENXIHRvIGcgcGVyIGdEQ1cqKiwgc2ltcGx5IGJ5IG11bHRpcGx5aW5nIHByb3RlaW4gY29uY2VudHJhdGlvbiB3aXRoIHRoZSBtb2xlY3VsYXIgd2VpZ2h0IG9mIGEgcHJvdGVpbiAoZy9tb2wsIGNvbnZlcnRlZCB0byBnL21tb2wpLiBXZSBjYW4gdGhlbiBhbHNvIGVhc2lseSB0cmFuc2Zvcm0gZy9nRENXIHRvIG1hc3MgZnJhY3Rpb24gYnkgZGl2aWRpbmcgaW5kaXZpZHVhbCBwcm90ZWluIGNvbmNlbnRyYXRpb25zIGJ5IHRoZSBzdW0gb2YgYWxsIHByb3RlaW4gY29uY2VudHJhdGlvbnMuIFRoZSBwcm90ZWluIG1hc3MgZnJhY3Rpb24gaXMgZGltZW5zaW9ubGVzcy4gVGhlIG9ubHkgcGFyYW1ldGVyIHJlcXVpcmVkIGZvciB0aGlzIHRyYW5zZm9ybWF0aW9uIGlzIHRoZSBtb2xlY3VsYXIgd2VpZ2h0IHBlciBwcm90ZWluIHdoaWNoIGlzIGF2YWlsYWJsZSBmcm9tIHVuaXByb3QuIFdlIGNhbiBmb3IgZXhhbXBsZSB0YWtlIHRoZSBwcm90ZWluIGFubm90YXRpb24gdGFibGUgdGhhdCBpcyBhdXRvbWF0aWNhbGx5IGRvd25sb2FkZWQgZHVyaW5nIGBSQkFweWAgbW9kZWwgZ2VuZXJhdGlvbi4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CiMgaW1wb3J0IGRvd25sb2FkZWQgUmFsc3RvbmlhIHByb3RlaW4gYW5ub3RhdGlvbiBmcm9tIHVuaXByb3QKZGZfdW5pcHJvdCA8LSByZWFkX3RzdigiLi4vZGF0YS9pbnB1dC91bmlwcm90LmNzdiIsIGNvbF90eXBlcyA9IGNvbHMoKSkgJT4lCiAgbXV0YXRlKGxvY3VzX3RhZyA9IHN0cmlfZXh0cmFjdF9maXJzdChgR2VuZSBuYW1lc2AsIHJlZ2V4ID0gIkgxNl9bQUJdWzAtOV17NH18UEhHWzAtOV17M30iKSkKCiMgbWVyZ2UgcHJlZGljdGVkIHByb3RlaW4gYWxsb2NhdGlvbiB3aXRoIG1vbGVjdWxhciB3ZWlnaHQgaW5mbyBmcm9tIHVuaXByb3QKZGZfcHJvdCA8LSBsZWZ0X2pvaW4oZGZfcHJvdCwgc2VsZWN0KGRmX3VuaXByb3QsIGxvY3VzX3RhZywgTGVuZ3RoLCBNYXNzKSwKICBieSA9IGMoImtleSIgPSAibG9jdXNfdGFnIikpICU+JQogIAogICMgY2FsY3VsYXRlIHByZWRpY3RlZCBwcm90ZWluIG1hc3MgaW4gZy9nRENXIHVzaW5nIE1XIGluIGcvbW1vbCwgYW5kIG1hc3MgZnJhY3Rpb24KICBncm91cF9ieShzaW11bGF0aW9uKSAlPiUgbXV0YXRlKAogICAgcHJlZGljdGVkX21hc3NfZ19nRENXID0gdmFsdWUgKiBNYXNzIC8gMTAwMCkKCiMgdGVzdCBpZiBtYXNzIGZyYWN0aW9ucyBzdW0gdG8gcmVhc29uYWJsZSB2YWx1ZQpkZl9wcm90ICU+JSBzdW1tYXJpemUoCiAgcHJlZGljdGVkX21hc3NfZ19nRENXID0gc3VtKHByZWRpY3RlZF9tYXNzX2dfZ0RDVywgbmEucm0gPSBUUlVFKSkKYGBgCgotLS0tLS0tLS0tCgpUaGUgc2ltdWxhdGVkIHByb3RlaW4gbWFzcyBwZXIgZ0RDVyBjaGFuZ2VzIHdpdGggZ3Jvd3RoIHJhdGUsIGFuZCBpdCBpcyBjb25zaWRlcmFibHkgbG93ZXIgdGhlbiB0aGUgZXN0aW1hdGVkIH4wLjY1LTAuNjggZyB0b3RhbCBwcm90ZWluL2dEQ1cgdGhhdCB3YXMgcHJldmlvdXNseSBlc3RpbWF0ZWQgZm9yIGJhY3RlcmlhIChzZWUgZS5nLiBQYXJrIGV0IGFsLiwgYmlvbWFzcyBjb21wb3NpdGlvbiBmb3IgKlJhbHN0b25pYSBldXRyb3BoYSogbW9kZWwsIG9yIFRvdWxvdXBha2lzIGV0IGFsLiwgYmlvbWFzcyBjb21wb3NpdGlvbiBmb3IgY3lhbm9iYWN0ZXJpYSkuIE9uZSByZWFzb24gaXMgdGhhdCBhYm92ZSBudW1iZXJzIG9ubHkgaW5jbHVkZSBlbnp5bWF0aWMgcHJvdGVpbnMgKHRoZSBvbmVzIHJlcHJlc2VudGVkIGJ5IHRoZSBHU00pLiBGb3IgbWFjaGluZXJ5IHN1Y2ggYXMgcmlib3NvbWFsIHByb3RlaW5zLCBhIHNlcGFyYXRlIGNhbGN1bGF0aW9uIG5lZWRzIHRvIGJlIHBlcmZvcm1lZC4gVGhlIG1vZGVsIHJldHVybnMgZXN0aW1hdGVkIGNvbmNlbnRyYXRpb24gb2YgbWFjaGluZXJpZXMgZm9yIHJlcGxpY2F0aW9uLCB0cmFuc2NyaXB0aW9uLCB0cmFuc2xhdGlvbiwgYW5kIHByb3RlaW4gZm9sZGluZy4gVGhlIGFzc29jaWF0ZWQgcHJvdGVpbnMgY2FuIGJlIGltcG9ydGVkIGZyb20gdGhlIG1vZGVsIGZvbGRlciBhbmQgdGhlIHRvdGFsIG1hc3MgZXN0aW1hdGVkIHVzaW5nIG1vbGVjdWxhciB3ZWlnaHQgYW5kIHN1YnVuaXQgc3RvaWNoaW9tZXRyeSBhcyBkb25lIGJlZm9yZSBmb3IgZW56eW1hdGljIHByb3RlaW5zLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KbWFjaGluZXJ5X25hbWVzIDwtIGMoInJlcGxpY2F0aW9uIiwgInRyYW5zY3JpcHRpb24iLCAicmlib3NvbWUiLCAiY2hhcGVyb25lcyIpCm1hY2hpbmVyeV90YWJsZXMgPC0gcGFzdGUwKCIuLi9kYXRhL3NpbXVsYXRpb24vbWFjcm9fbWFjaGluZXMvIiwgbWFjaGluZXJ5X25hbWVzLCAiLnRzdiIpCgpkZl9tYWNyIDwtIGRmX21hY3IgJT4lIAogIAogICMgcmVtb3ZlIHVudXNlZCBjb2x1bW5zIGFuZCByZW5hbWUgaW1wb3J0YW50IG9uZXMKICBzZWxlY3QoLVBfRU5aLCAtUF9STkFERUcpICU+JQogIHJlbmFtZShyZXBsaWNhdGlvbiA9IFBfUkVQLCB0cmFuc2NyaXB0aW9uID0gUF9UU0MsIAogICAgcmlib3NvbWUgPSBQX1RBLCBjaGFwZXJvbmVzID0gUF9DSFAsIHN1YnN0cmF0ZV91cHRha2VfcmF0ZSA9IHFTLAogICAgcHJlZGljdGVkX2dyb3d0aF9yYXRlID0gbXUKICApICU+JQogIAogICMgZ2F0aGVyIGluZGl2aWR1YWwgbWFjaGluZXJpZXMgaW4gb25lIGNvbHVtbgogIGdhdGhlcihrZXkgPSBtYWNoaW5lLCB2YWx1ZSA9IHByZWRpY3RlZF9tYXNzX21tb2xfZ0RDVywgCiAgICBjaGFwZXJvbmVzOnRyYW5zY3JpcHRpb24pICU+JQogIAogICMgcm91bmQgdXB0YWtlIHJhdGVzIGZvciBtZXJnaW5nCiAgbXV0YXRlKHN1YnN0cmF0ZV91cHRha2VfcmF0ZSA9IHJvdW5kKHN1YnN0cmF0ZV91cHRha2VfcmF0ZSwgMykpCiAgCgpkZl9tYWNoaW5lcnkgPC0gbGFwcGx5KG1hY2hpbmVyeV90YWJsZXMsIHJlYWRfdHN2LCBjb2xfdHlwZSA9IGNvbHMoKSkgJT4lIAogIGJpbmRfcm93cyguaWQgPSAibWFjaGluZSIpICU+JQogIG11dGF0ZShtYWNoaW5lID0gcmVjb2RlKG1hY2hpbmUsICEhIXNldE5hbWVzKG1hY2hpbmVyeV9uYW1lcywgMTo0KSkpICU+JQogIHNlbGVjdCgtYEVudHJ5IG5hbWVgLCAtU2VxdWVuY2UsIC1Db2ZhY3RvciwgLWBFQyBudW1iZXJgLCAtYE9yZ2FuaXNtIElEYCwgYE9yZ2FuaXNtYCwKICAgIC1gQ2F0YWx5dGljIGFjdGl2aXR5YCwgLVN0YXR1cykgJT4lCiAgCiAgIyBqb2luIHdpdGggcHJlZGljdGlvbiBvZiBtb2xlY3VsYXIgbWFjaGluZSBjb25jZW50cmF0aW9uIChtbW9sL2dEQ1cpCiAgbGVmdF9qb2luKGRmX21hY3IsIGJ5ID0gIm1hY2hpbmUiKSAlPiUKICAKICAjIGNhbGN1bGF0ZSBwcmVkaWN0ZWQgcHJvdGVpbiBtYXNzIGluIGcvZ0RDVyB1c2luZyBNVyBpbiBnL21tb2wsIGFuZCBtYXNzIGZyYWN0aW9uCiAgZ3JvdXBfYnkoc2ltdWxhdGlvbikgJT4lIG11dGF0ZSgKICAgIHByZWRpY3RlZF9tYXNzX2dfZ0RDVyA9IHByZWRpY3RlZF9tYXNzX21tb2xfZ0RDVyAqIFN0b2ljaGlvbWV0cnkgKiBNYXNzIC8gMTAwMAogICkKCiMgdGVzdCBpZiBtYXNzIGZyYWN0aW9ucyBzdW0gdG8gcmVhc29uYWJsZSB2YWx1ZQpkZl9tYWNoaW5lcnkgJT4lIAogIHN1bW1hcml6ZShzdW1fZ19nRENXID0gc3VtKHByZWRpY3RlZF9tYXNzX2dfZ0RDVywgbmEucm0gPSBUUlVFKSkgJT4lCiAgdW5ncm91cCAlPiUgaGVhZCgxMCkKYGBgCgpJZiB0aGUgdXRpbGl6ZWQgcHJvdGVpbiBmb3IgZW56eW1lcyBhbmQgbWFjaGluZXJ5IGlzIHN1bW1lZCB1cCwgaXQgZG9lcyBub3QgZXhjZWVkIH4wLjI1IGcvZ0RDVy4gVGhlIHJlYXNvbiBmb3IgdGhpcyBpcyB0aGF0IHVwIHRvIDYwJSBvZiB0aGUgcHJvdGVpbiBtYXNzIGlzIG5vdCBtb2RlbGVkIChub24gZW56eW1hdGljLCBORSBwcm90ZW9tZSwgNTcuNyUgYXQgwrUgPSAwLCBzZWUgUiBub3RlYm9vayBgUmFsc3RvbmlhLW1vZGVsLWNvbnN0cmFpbnRzYCksIGFuZCBvbmx5IGFyb3VuZCA2OCUgb2YgdGhlIHRvdGFsIGNlbGwgbWFzcyBpcyBwcm90ZWluLiBJZiB3ZSBjb25zaWRlciB0aGlzLCB0aGFuIHRvdGFsIHByb3RlaW4gbWFzcyBjYW4gYmUgZXN0aW1hdGVkIGFzIG0gPSAwLjI1LygxLTAuNikgPSBgciByb3VuZCgwLjI1LygxLTAuNiksIDMpYCBnL2dEQ1cuIFRoaXMgaXMgbXVjaCBjbG9zZXIgdG8gdGhlIGVzdGltYXRlZCAwLjY4IGcgcHJvdGVpbi9nRENXLiBUaGUgZGlmZmVyZW5jZSBjYW4gYmUgYXR0cmlidXRlZCB0byBkZWZhdWx0IHZhbHVlcyBvZiBhbWlubyBhY2lkIGNvbmNlbnRyYXRpb24gaW4gYFJCQXB5YCB0aGF0IGRldGVybWluZSB0aGUgc2l6ZSBvZiB0aGUgJ3Byb3RlaW4gcG9vbCcuIEl0IGlzIGltcG9ydGFudCB0byBub3RlIHRoYXQgdGhlIHN1bSBvZiB1dGlsaXphYmxlIHByb3RlaW5zIGlzIG5vdCBhIGNvbnN0YW50IHZhbHVlIGJ1dCBhIGxpbmVhciBmdW5jdGlvbiBvZiBncm93dGggcmF0ZS4gVGhlIHRvdGFsIHByb3Rlb21lIHBvb2wgKmluY2x1ZGluZyBub24tZW56eW1hdGljIHByb3RlaW5zKiBpcywgaG93ZXZlciwgY29uc3RhbnQuCgoKIyBDb3JyZWxhdGlvbiBiZXR3ZWVuIHByZWRpY3RlZCBhbmQgZXhwZXJpbWVudGFsbHkgZGV0ZXJtaW5lZCBwcm90ZW9tZQoKVG8gY29tcGFyZSB0aGUgcHJlZGljdGVkIGFuZCBleHBlcmltZW50YWwgcHJvdGVvbWUgY29tcG9zaXRpb24sIHdlIGxvYWQgdGhlIHJlcXVpcmVkIHByb3Rlb21pY3MgZGF0YS4gUHJvdGVvbWljcyBkYXRhIGFyZSBtYXNzIHNwZWN0cm9tZXRyeSBtZWFzdXJlbWVudHMgd2l0aCBsYWJlbC1mcmVlIHF1YW50aWZpY2F0aW9uIG9mIHBlcHRpZGVzLiBQcm90ZWluIHF1YW50aWZpY2F0aW9uIHdhcyBwZXJmb3JtZWQgYnkgc3VtbWluZyB1cCBhbGwgcGVwdGlkZSBpbnRlbnNpdGllcyBwZXIgYW5ub3RhdGVkIHByb3RlaW4uIFRoZSBwcm90ZW9taWMgbWVhc3VyZW1lbnQgdW5pdCwgbWFzcyBmcmFjdGlvbiwgY2FuIGJlIGVhc2lseSB0cmFuc2Zvcm1lZCB0byBnL2dEQ1cgYnkgbXVsdGlwbHlpbmcgbWFzcyBmcmFjdGlvbiB3aXRoIHRoZSB0b3RhbCBwcm90ZWluIG1hc3MgKDAuNjggZy9nRENXKSB1c2VkIGluIFJCQXB5IHNpbXVsYXRpb25zLiBPciAqdmljZSB2ZXJzYSogYnkgY29udmVydGluZyBSQkFweSBwcm90ZWluIGNvbmNlbnRyYXRpb24gKG1tb2wvZ0RDVykgdG8gbWFzcyBmcmFjdGlvbi4KClRvIGFsbG93IGEgZmFpciBjb21wYXJpc29uIGJldHdlZW4gbWVhc3VyZWQgYW5kIHByZWRpY3RlZCBkYXRhLCBpdCBpcyBuZWNlc3NhcnkgdG8gYWdncmVnYXRlIChlLmcuIHN1bSB1cCkgYWxsIHByb3RlaW4gYWJ1bmRhbmNlcyBhbGxvY2F0ZWQgdG8gb25lIHJlYWN0aW9uLiBUaGUgcmVhc29uIGlzIHRoYXQgdGhlIG1vZGVsIHdpbGwgb25seSBwcmVkaWN0ICoqcHJvdGVpbiBhYnVuZGFuY2Ugb2YgdGhlIGZpcnN0IG9mIGEgcmFuZ2Ugb2YgaXNvLWVuenltZXMqKiBmb3IgYSBwYXJ0aWN1bGFyIHJlYWN0aW9uLCB3aGlsZSBpbiByZWFsaXR5IGFub3RoZXIgaXNvLWVuenltZSBtaWdodCBiZSBtb3JlIGFidW5kYW50IChjYXJyeSB0aGUgbWFqb3JpdHkgb2YgZmx1eCkuIFRoaXMgd291bGQgbGVhZCB0byBsb3dlciBjb3JyZWxhdGlvbiBiZXR3ZWVuIG1lYXN1cmVkIGFuZCBwcmVkaWN0ZWQgcHJvdGVpbiBjb25jZW50cmF0aW9ucy4gVGhpcyBpcyBub3QgbmVjZXNzYXJ5IGZvciBtYWNoaW5lcnkgcHJvdGVpbnMsIHRoZXkgaGF2ZSBubyBpc28tZW56eW1lcyBhbmQgYXJlIHVzZWQgdW5kZXIgYWxsIGNvbmRpdGlvbnMuCgoKIyMgQ29tYmluZSBzaW11bGF0aW9ucyBhbmQgZXhwZXJpbWVudGFsIGRhdGEKClRoZSBwcm90ZW9taWNzIGRhdGEgbXVzdCBiZSBtZXJnZWQgd2l0aCBgUkJBcHlgIHNpbXVsYXRpb24gcmVzdWx0cyB1c2luZyBtYXRjaGluZyBjb25kaXRpb25zLiBGaXJzdCwgcHJvdGVvbWljcyBkYXRhIGFyZSBsb2FkZWQgYW5kIHByZXBhcmVkIGZvciBtZXJnaW5nLgoKCioqU3RlcCAxOiBsb2FkIHByb3Rlb21pY3MgZGF0YSoqCgpgYGB7cn0KbG9hZChSZXV0cm9waGFfcHJvdGVvbWljcykKCiMgcGljayBhIGNvbmRpdGlvbiBtYXRjaGluZyBzaW11bGF0aW9ucwpSYWxzdG9uaWFfZXV0cm9waGEgPC0gUmFsc3RvbmlhX2V1dHJvcGhhICU+JQogIAogICMgcm91bmQgc3Vic3RyYXRlIHVwdGFrZSByYXRlCiAgbXV0YXRlKHN1YnN0cmF0ZV91cHRha2VfcmF0ZSA9IHJvdW5kKHN1YnN0cmF0ZV91cHRha2VfcmF0ZSwgMykpICU+JQogIAogICMgc2VsZWN0IG9ubHkgcmVxdWlyZWQgY29sdW1ucwogIHJlbmFtZShncm93dGhfcmF0ZSA9IGdyb3d0aHJhdGUpICU+JQogIHNlbGVjdCh1bmlwcm90LCBsb2N1c190YWcsIHByb3RlaW4sIGNvbmRpdGlvbiwgc3Vic3RyYXRlLCBzdWJzdHJhdGVfdXB0YWtlX3JhdGUsCiAgICBncm93dGhfcmF0ZSwgQ09HX1Byb2Nlc3MsIFIxOlI0KSAlPiUKICAKICAjIHR1cm4gcmF3IGludGVuc2l0eSBtZWFzdXJlbWVudHMgaW50byBtYXNzIGluIGcgcGVyIGdEQ1cgKGFzc3VtaW5nIGEgCiAgIyB0b3RhbCBwcm90ZWluIGNvbmNlbnRyYXRpb24gb2YgMC42OCBnL2dEQ1cpCiAgZ3JvdXBfYnkoY29uZGl0aW9uKSAlPiUKICBtdXRhdGUoYWNyb3NzKG1hdGNoZXMoIlJbMTIzNF0iKSwgZnVuY3Rpb24oeCkgeC9zdW0oeCwgbmEucm0gPSBUUlVFKSowLjY4KSkgJT4lCiAgZ2F0aGVyKGtleSA9ICJyZXBsaWNhdGUiLCB2YWx1ZSA9ICJtYXNzX2dfZ0RDVyIsIFIxOlI0KQpgYGAKCgoqKlN0ZXAgMjogTG9hZCBnZW5lIHJlYWN0aW9uIGFzc29jaWF0aW9ucyBvYnRhaW5lZCBmcm9tIGdlbm9tZSBzY2FsZSBtb2RlbCoqCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFfQpkZl9tb2RlbF9yZWFjdGlvbnMgPC0gcmVhZF9jc3YobW9kZWxfcmVhY3Rpb25zLCBjb2xfdHlwZXMgPSBjb2xzKCkpICU+JQogIAogICMgZmlsdGVyIGZvciByZWFjdGlvbnMgd2l0aCBnZW5lIGFzc29jaWF0aW9ucwogIHNlbGVjdChyZWFjdGlvbl9pZCwgcmVhY3Rpb25fbmFtZSwgZ2VuZXMsIGdyb3VwcykgJT4lIHNlcGFyYXRlX3Jvd3MoZ2VuZXMsIHNlcCA9ICIsICIpICU+JQogIGZpbHRlcighaXMubmEoZ2VuZXMpKSAlPiUgCiAgcmVuYW1lKG1vZGVsX2dyb3VwID0gZ3JvdXBzKSAlPiUKICAKICAjIGFkZCBhIG1vcmUgZ2VuZXJhbCBncm91cHMgZGVzY3JpcHRpb24KICBtdXRhdGUobW9kZWxfZ3JvdXBfYmFzaWMgPSBjYXNlX3doZW4oCiAgICBncmVwbCgiUGhlbnlsYWxhfFZhbGluZXxUeXJvc2luZXxHbHV0YW1hdGV8R2x5Y2luZXxUcnlwdG9waGFufE1ldGhpb25pbmV8CiAgICAgICAgICBDeXN0ZWluZXxBbGFuaW5lfEhpc3RpZGluZXxBcmdpbmluZXxMeXNpbmUiLCBtb2RlbF9ncm91cCkgfiAiQW1pbm8gYWNpZCIsCiAgICBncmVwbCgiUGVudG9zZXxDYWx2aW4iLCBtb2RlbF9ncm91cCkgfiAiUFBQICsgQ2FsdmluIGN5Y2xlIiwKICAgIG1vZGVsX2dyb3VwID09ICJDaXRyaWMgQWNpZCBDeWNsZSIgfiAiQ2l0cmljIEFjaWQgQ3ljbGUiLAogICAgbW9kZWxfZ3JvdXAgPT0gIkdseWNvbHlzaXMvR2x1Y29uZW9nZW5lc2lzIiB+ICJHbHljb2x5c2lzL0dsdWNvbmVvZ2VuZXNpcyIsCiAgICBtb2RlbF9ncm91cCA9PSAiR2x5b3h5bGF0ZSBhbmQgRGljYXJib3h5bGF0ZSBtZXRhYm9saXNtIiB+ICJBdXRvdHJvcGhpYyBlbmVyZ3kiLAogICAgbW9kZWxfZ3JvdXAgPT0gIk94aWRhdGl2ZSBQaG9zcGhvcnlsYXRpb24iIH4gIk94aWRhdGl2ZSBQaG9zcGhvcnlsYXRpb24iLAogICAgVFJVRSB+ICJPdGhlciIKICApKQpgYGAKCgoqKlN0ZXAgMzogU2VsZWN0IGFuZCByZW5hbWUgY29uZGl0aW9ucyBmcm9tIFJCQSBzaW11bGF0aW9uKioKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CiMgYWRkIHR5cGUgb2Ygc3Vic3RyYXRlIGxpbWl0YXRpb24KYWRkX2NvbmQgPC0gZnVuY3Rpb24oZGYpIHsKICBkZiAlPiUgbXV0YXRlKHN1YnN0cmF0ZSA9IGNhc2Vfd2hlbigKICAgIGNhcmJvbl9zb3VyY2UgPT0gInN1Y2MiIH4gInN1Y2NpbmF0ZSIsCiAgICBjYXJib25fc291cmNlID09ICJmb3IiIH4gImZvcm1hdGUiLAogICAgY2FyYm9uX3NvdXJjZSA9PSAiZnJ1IiAmIG5pdHJvZ2VuX2NvbmMgPT0gMTguNyB+ICJmcnVjdG9zZSIsCiAgICBUUlVFIH4gImFtbW9uaXVtIiwKICApKQp9CgojIGFkZCBzdWJzdHJhdGUgdXB0YWtlIHJhdGUKZGZfc3Vic3RyYXRlX3VwdGFrZSA8LSBkZl9tYWNyICU+JSAKICBzZWxlY3Qoc2ltdWxhdGlvbiwgc3Vic3RyYXRlX3VwdGFrZV9yYXRlKSAlPiUgZGlzdGluY3QKZGZfcHJvdCA8LSBkZl9wcm90ICU+JSBhZGRfY29uZCAlPiUgbGVmdF9qb2luKGRmX3N1YnN0cmF0ZV91cHRha2UsIGJ5ID0gInNpbXVsYXRpb24iKQpkZl9mbHV4IDwtIGRmX2ZsdXggJT4lIGFkZF9jb25kICU+JSBsZWZ0X2pvaW4oZGZfc3Vic3RyYXRlX3VwdGFrZSwgYnkgPSAic2ltdWxhdGlvbiIpCmBgYAoKCioqU3RlcCA0OiBNZXJnZSBwcm90ZWluIG1lYXN1cmVtZW50cyBhbmQgcHJlZGljdGlvbnMgaW50byBtYXN0ZXIgdGFibGVzKioKClRoZSBmaXJzdCBzdGVwIGlzIHRvIG1lcmdlIHRoZSB0YWJsZXMgZm9yIG1hY2hpbmVyeSBwcm90ZWlucywgdGhhdCBtZWFucyBwcm90ZWlucyByZWxhdGVkIHRvIHJlcGxpY2F0aW9uLCB0cmFuc2NyaXB0aW9uLCB0cmFuc2xhdGlvbiwgYW5kIHByb3RlaW4gZm9sZGluZy4gVGhlc2UgZG9uJ3QgcmVxdWlyZSBhbGxvY2F0aW9uIG9mIHByb3RlaW4gbWFzcyB0byByZWFjdGlvbnMsIGFuZCBtZXJnaW5nIGJlY29tZXMgc2ltcGx5IGFuIG9wZXJhdGlvbiBvbiBlbnp5bWUgSURzIGFuZCBjb25kaXRpb25zLgoKYGBge3J9CiMgam9pbiB3aXRoIHByb3Rlb21pY3MgZGF0YQpkZl9tYWNoaW5lcnkgPC0gZGZfbWFjaGluZXJ5ICU+JQogIGxlZnRfam9pbihSYWxzdG9uaWFfZXV0cm9waGEsCiAgICBieSA9IGMoIkVudHJ5IiA9ICJ1bmlwcm90IiwgInN1YnN0cmF0ZSIsICJzdWJzdHJhdGVfdXB0YWtlX3JhdGUiKSkKYGBgCgpUaGUgc2Vjb25kIHRhYmxlIGZvciBhbGwgZW56eW1hdGljIHByb3RlaW5zIHJlcXVpcmVzIHRoZSBhbGxvY2F0aW9uIG9mIGVzdGltYXRlZCBwcm90ZWluIG1hc3MgdG8gZW56eW1lcy4KT25lIG9wdGlvbiBmb3IgdGhlIGZ1dHVyZSBpcyB0byByZXRyaWV2ZSB0aGVzZSB2YWx1ZXMgZGlyZWN0bHkgZnJvbSBSQkFweSwgYnV0IHRoaXMgaXMgbm90IGltcGxlbWVudGVkIHlldC4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmRmX3Byb3RfY29tcCA8LSBkZl9tb2RlbF9yZWFjdGlvbnMgJT4lCiAgCiAgIyBqb2luIHdpdGggcHJvdGVvbWljcyBkYXRhCiAgbGVmdF9qb2luKFJhbHN0b25pYV9ldXRyb3BoYSwgYnkgPSBjKCJnZW5lcyIgPSAibG9jdXNfdGFnIikpICU+JQogIGNvbXBsZXRlKG5lc3RpbmcoZ2VuZXMsIHJlYWN0aW9uX2lkLCByZWFjdGlvbl9uYW1lLCBtb2RlbF9ncm91cCwgbW9kZWxfZ3JvdXBfYmFzaWMpLAogICAgbmVzdGluZyhjb25kaXRpb24sIHN1YnN0cmF0ZSwgc3Vic3RyYXRlX3VwdGFrZV9yYXRlLCBncm93dGhfcmF0ZSwgcmVwbGljYXRlKSkgJT4lCiAgZmlsdGVyKCFpcy5uYShzdWJzdHJhdGUpKSAlPiUKICAKICAjIGpvaW4gd2l0aCBzaW11bGF0aW9uIGRhdGEKICBsZWZ0X2pvaW4oZGZfcHJvdCwgYnkgPSBjKCJnZW5lcyIgPSAia2V5IiwgInN1YnN0cmF0ZSIsICJzdWJzdHJhdGVfdXB0YWtlX3JhdGUiKSkgJT4lCiAgCiAgIyBkZXRlcm1pbmUgbnVtYmVyIG9mIHJlYWN0aW9ucyBwZXIgcHJvdGVpbgogIGdyb3VwX2J5KGNvbmRpdGlvbiwgZ2VuZXMsIHJlcGxpY2F0ZSkgJT4lIAogIG11dGF0ZShuX3JlYWN0aW9ucyA9IGxlbmd0aChyZWFjdGlvbl9pZCkpICU+JQogIAogICMgY2FsY3VsYXRlIHByb3RlaW4gbWFzcyBpbiBnL2dEQ1cKICB1bmdyb3VwICU+JSBtdXRhdGUoCiAgICBwcmVkaWN0ZWRfbWFzc19nX2dEQ1cgPSBwcmVkaWN0ZWRfbWFzc19nX2dEQ1cvbl9yZWFjdGlvbnMsCiAgICBtYXNzX2dfZ0RDVyA9IG1hc3NfZ19nRENXL25fcmVhY3Rpb25zCiAgKSAlPiUKICAKICAjIHN1bW1hcml6ZSBieSBzdW1taW5nIHVwIHByb3RlaW4gYWJ1bmRhbmNlIHBlciByZWFjdGlvbiAoTkEgdHJlYXRlZCBhcyB6ZXJvKQogIGdyb3VwX2J5KGNvbmRpdGlvbiwgcmVhY3Rpb25faWQsIHJlYWN0aW9uX25hbWUsIG1vZGVsX2dyb3VwLAogICAgbW9kZWxfZ3JvdXBfYmFzaWMsIHN1YnN0cmF0ZSwgc3Vic3RyYXRlX3VwdGFrZV9yYXRlLCBncm93dGhfcmF0ZSwgcmVwbGljYXRlKSAlPiUgCiAgc3VtbWFyaXplKAogICAgcHJlZGljdGVkX21hc3NfZ19nRENXID0gc3VtKHByZWRpY3RlZF9tYXNzX2dfZ0RDVywgbmEucm0gPSBUUlVFKSwKICAgIG1hc3NfZ19nRENXID0gc3VtKG1hc3NfZ19nRENXLCBuYS5ybSA9IFRSVUUpCiAgKSAlPiUKICBmaWx0ZXIobWFzc19nX2dEQ1cgIT0gMCkgJT4lCiAgCiAgIyBhZGQgcHJlZGljdGVkIGdyb3d0aCByYXRlIHRvIGV4cGVyaW1lbnRhbAogIGxlZnRfam9pbigKICAgIGRmX21hY2hpbmVyeSAlPiUgdW5ncm91cCAlPiUKICAgIHNlbGVjdChzdWJzdHJhdGUsIHN1YnN0cmF0ZV91cHRha2VfcmF0ZSwgcHJlZGljdGVkX2dyb3d0aF9yYXRlKSAlPiUKICAgIGZpbHRlcighZHVwbGljYXRlZChzdWJzdHJhdGVfdXB0YWtlX3JhdGUpKQogICkgJT4lCiAgCiAgIyBhZGQgcHJlZGljdGVkIGZsdXhlcyBwZXIgcmVhY3Rpb24gYW5kIGNvbmRpdGlvbgogIGxlZnRfam9pbigKICAgIGRmX2ZsdXggJT4lIHVuZ3JvdXAgJT4lCiAgICBzZWxlY3Qoa2V5LCB2YWx1ZSwgc3Vic3RyYXRlLCBzdWJzdHJhdGVfdXB0YWtlX3JhdGUpICU+JQogICAgcmVuYW1lKHJlYWN0aW9uX2lkID0ga2V5LCBmbHV4X21tb2xfZ0RDV19oID0gdmFsdWUpCiAgKQpgYGAKCk5vdyB3ZSBwZXJmb3JtIGEgdGVzdC4gV2UgY2hlY2sgaWYgYWxsIHByb3RlaW4gYWJ1bmRhbmNlcyBhbGxvY2F0ZWQgdG8gcmVhY3Rpb25zIHN1bSB0byBhIHJlYXNvbmFibGUgdmFsdWUgYXMgd2Ugd291bGQgZXhwZWN0LiBUaGlzIHZhbHVlIHdvdWxkIGJlIHRoZSB0b3RhbCAqZW56eW1hdGljKiBwcm90ZWluIG1hc3MgaW4gZy9nRENXLCBwZXIgY29uZGl0aW9uIGFuZCByZXBsaWNhdGUsIGFuZCBjb3VsZCByZWFjaCB1cCB0byAwLjIgZy9nRENXIGZvciB0aGUgc2ltdWxhdGlvbnMsIGFuZCBoaWdoZXIgZm9yIHRoZSBhY3R1YWwgZGF0YSAoaW5jbHVkZXMgYWxsIGFkZGl0aW9uYWwgcHJvdGVpbnMgcXVhbnRpZmllZCBpbiBleHBlcmltZW50LCBidXQgbm90IGNhcnJ5aW5nIGZsdXggaW4gbW9kZWwgc2ltdWxhdGlvbnMpLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KZGZfcHJvdF9jb21wICU+JSBncm91cF9ieShjb25kaXRpb24sIHJlcGxpY2F0ZSkgJT4lIAogIGZpbHRlcihwcmVkaWN0ZWRfbWFzc19nX2dEQ1cgIT0gMCkgJT4lCiAgc3VtbWFyaXplKHN1bShtYXNzX2dfZ0RDVyksIHN1bShwcmVkaWN0ZWRfbWFzc19nX2dEQ1cpKQpgYGAKCi0tLS0tLS0tLS0KClRoZSB0b3RhbCBwcmVkaWN0ZWQgcHJvdGVpbiBtYXNzIGlzIGxvd2VyIHRoYW4gdGhlIG1lYXN1cmVkIHByb3RlaW4gbWFzcy4gVGhlcmVmb3JlIHRoZSBmb2xsb3dpbmcgc2VjdGlvbiBxdWFudGlmaWVzIGRpc2NyZXBhbmNpZXMgYmV0d2VlbiBtb2RlbCBwcmVkaWN0ZWQgYW5kIGFjdHVhbGx5IG1lYXN1cmVkIGFidW5kYW5jZXMuIEZpcnN0IHdlIGNhbiBpbnNwZWN0IHRoZSB0b3AgTiByZWFjdGlvbnMgd2l0aCBoaWdoZXN0ICoqYXZlcmFnZSBwcmVkaWN0ZWQgcHJvdGVpbiBhYnVuZGFuY2UqKi4gVGhlIHJhdGlvIG9mIHByZWRpY3RlZCBkaXZpZGVkIGJ5IG1lYXN1cmVkIG1hc3MgaW5kaWNhdGVzIHRoYXQgYSBoYW5kZnVsIG9mIHByb3RlaW5zIGFyZSBwcmVkaWN0ZWQgdG8gYmUgbW9yZSB0aGFuIDEwIGZvbGQgYWJ1bmRhbnQgY29tcGFyZWQgdG8gdGhlIG1lYXN1cmVkIGFidW5kYW5jZS4gVGhpcyBwb2ludHMgdG93YXJkcyBmbHV4ZXMgYmVpbmcgZXJyb25lb3VzbHkgcHJlZGljdGVkIHRvbyBoaWdoIGZvciBwYXJ0aWN1bGFyIHJlYWN0aW9ucywgb3IgKmtfYXBwKiB2YWx1ZXMgYmVpbmcgZXN0aW1hdGVkIHRvbyBsb3cgZm9yIHRoZSBlc3RpbWF0ZWQgZmx1eC4KCkhvd2V2ZXIsIHRoZSBsYXJnZXN0IGRpc2NyZXBhbmNpZXMgYXJpc2UgZnJvbSAqKnVuZGVyLWVzdGltYXRpb24gb2YgcHJvdGVpbnMqKiwgdGhlIG1haW4gY2F1c2UgYmVpbmcgdGhhdCB0aGUgbW9kZWwgcHJlZGljdHMgdGhlICoqb3B0aW1hbCBhYnVuZGFuY2UgZm9yIGVhY2ggZW56eW1lIHRvIGNhcnJ5IGEgY2VydGFpbiBmbHV4KiouIElmIGZsdXhlcyBhcmUgZHJhc3RpY2FsbHkgcmVkdWNlZCBkdWUgdG8gc3Ryb25nIHN1YnN0cmF0ZSBsaW1pdGF0aW9uLCB0aGUgbWluaW1hbCByZXF1aXJlZCBwcm90ZWluIGFidW5kYW5jZSB0byBvcHRpbWl6ZSBncm93dGggd2lsbCBiZSBtdWNoIGxvd2VyIHRoYW4gdGhlIG1lYXN1cmVkIGFidW5kYW5jZS4gQSBiYWN0ZXJpYWwgY2VsbCBvbiB0aGUgb3RoZXIgaGFuZCBjYW4gbm90IGZ1bGx5IHJlZHVjZSBpdHMgcHJvdGVvbWUgYnV0IGluc3RlYWQgJ3N1c3BlbmRzJyBpbmFjdGl2ZSBlbnp5bWVzLgoKCiMjIENoYW5nZSBvZiBtYWNoaW5lcnkgcHJvdGVpbnMgd2l0aCBncm93dGggcmF0ZQoKVGhlIHNpbXVsYXRpb24gYW5kIG1lYXN1cmVtZW50IGRhdGEgd2FzIHByZXBhcmVkIGFuZCBtZXJnZWQgYnkgY29uZGl0aW9uIGluIHRoZSBwcmV2aW91cyBzZWN0aW9ucy4gTm93IGl0IGNhbiBiZSBwbG90dGVkIHRvIGUuZy4gY29tcGFyZSBwcm90ZWluIGFsbG9jYXRpb24gb3ZlciBncm93dGggcmF0ZS4gSW50ZXJlc3RpbmdseSwgd2Ugc2VlIHRoYXQgbW9kZWwgcHJlZGljdGlvbnMgYXJlIHF1aXRlIGFjY3VyYXRlbHkgcmVmbGVjdGluZyB0aGUgcmFuZ2Ugb2YgcHJvdGVpbiBhbGxvY2F0aW9uIGZvciB0aGUgZm91ciBkaWZmZXJlbnQgbWFjaGluZXJpZXMsIHNlZSBmb2xsb3dpbmcgcGFyYWdyYXBocy4gVGhpcyBpcyBhIGdvb2QgY29uZmlybWF0aW9uIG9mIHRoZSBtb2RlbCdzIHByZWRpY3RpdmUgcG93ZXIsIGdpdmVuIHRoYXQgdGhlIHJhdGVzIG9mIHRoZXNlIG1hY2hpbmVyaWVzIHdlcmUgKm5vdCBmaXR0ZWQqIGZyb20gZGF0YSBidXQgdGFrZW4gcHVyZWx5IGZyb20gbGl0ZXJhdHVyZS4gVGhlcmUgYXJlIGhvd2V2ZXIgc29tZSBkZXZpYXRpb25zIGZyb20gdGhlIHByZWRpY3RlZCAnb3B0aW1hbCcgcHJvdGVvbWU6CgotIHRoZSBtb3N0IGltcG9ydGFudCBtYWNoaW5lIGlzIHRoZSByaWJvc29tZS4gUHJlZGljdGlvbiBhbmQgZXhwZXJpbWVudCBzaG93IGEgdmVyeSBzaW1pbGFyIGluY3JlYXNlIG9mIHJpYm9zb21lIGFidW5kYW5jZSB3aXRoIGdyb3d0aCByYXRlIChzbG9wZSBvZiBsaW5lYXIgbW9kZWwpLCBidXQgdGhlIGludGVyc2VjdGlvbiAoYW1vdW50IG9mIHVudXNlZCByaWJvc29tZXMgYXQgemVybyBncm93dGgpIGlzIG11Y2ggaGlnaGVyIGluIGV4cGVyaW1lbnQKLSBjaGFwZXJvbmVzIHNob3cgYW4gaW52ZXJzZSBwcm9wb3J0aW9uYWwgcmVsYXRpb25zaGlwIHdpdGggZ3Jvd3RoIHJhdGUgY29udHJhcnkgdG8gbW9kZWwgcHJlZGljdGlvbi4gRG8gKHNvbWUgb2Y/KSB0aGVzZSBwcm90ZWlucyBoYXZlIGFub3RoZXIgcm9sZSB0aGFuIGp1c3QgZm9sZGluZywgbGlrZSBzdHJlc3MgcmVzcG9uc2U/Ci0gdHJhbnNjcmlwdGlvbiBzZWN0b3IgaXMgcXVpdGUgc3RhYmxlLCBob3dldmVyLCBwcmVkaWN0ZWQgZW56eW1lIG1hc3MgaXMgbXVjaCBsb3dlciB0aGFuIG1lYXN1cmVkICh1bmRlcmVzdGltYXRlZCBieSAxIG9yZGVyIG9mIG1hZ25pdHVkZSAsIDV4MTBeLTMgdnMgNXgxMF4tNCBnL2dEQ1cpCi0gcmVwbGljYXRpb24gc2VjdG9yIGlzIGhlYXZpbHkgdW5kZXJlc3RpbWF0ZWQgYnkgMyBvcmRlcnMgb2YgbWFnbml0dWRlICgxMF4tMyB2cyAxMF4tNiBnL2dEQ1cpCgpUaGUgcHJvdGVpbiBhbGxvY2F0aW9uIGZvciB0aGUgdHdvIGxvdy1hYnVuZGFudCBtYWNoaW5lcywgcmVwbGljYXRpb24gYW5kIHRyYW5zY3JpcHRpb24gaXMgdmVyeSBoYXJkIHRvIHNlZS4gV2UgY2FuIHVwZGF0ZSB0aGUgWS1heGlzIGxpbWl0cyBmb3IgdGhpcyBwbG90IGFuZCByZS1wbG90IGl0IHdpdGggMTAtZm9sZCAnem9vbScuIEFidW5kYW5jZSBvZiByZXBsaWNhdGlvbiBtYWNoaW5lcnkgaW5jcmVhc2VzIHdpdGggZ3Jvd3RoIHJhdGUgZnJvbSAwLjAwMTAgdG8gYSBtYXhpbXVtIG9mIDAuMDAxNSBnL2dEQ1csIGFuIGluY3JlYXNlIGJ5IDIwLTUwJS4gRm9yIHRyYW5zY3JpcHRpb24sIGFidW5kYW5jZSBpbmNyZWFzZXMgd2l0aCBncm93dGggcmF0ZSBmcm9tIDAuMDA2IHRvIDAuMDA4IGcvZ0RDVywgYW4gaW5jcmVhc2Ugb2YgMzMlLiAKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA0fQpkZl9tYWNoaW5lcnlfdXRpbCA8LSBkZl9tYWNoaW5lcnkgJT4lCiAgCiAgIyByZW1vdmUgMSBub24tcXVhbnRpZmllZCByZXBsaWNhdGUKICBmaWx0ZXIoIShzdWJzdHJhdGUgPT0gImZydWN0b3NlIiAmIGdyb3d0aF9yYXRlID09IDAuMSAmIHJlcGxpY2F0ZSA9PSAiUjIiKSkgJT4lCiAgCiAgIyBzdW1taW5nIHVwIHByb3RlaW4gbWFzcyBvdmVyIGFsbCBjb25kaXRpb25zCiAgZ3JvdXBfYnkobWFjaGluZSwgc3Vic3RyYXRlLCBncm93dGhfcmF0ZSwgcmVwbGljYXRlKSAlPiUKICBzdW1tYXJpemUoCiAgICBgbWFzcyBbZy9nRENXXWAgPSBzdW0obWFzc19nX2dEQ1csIG5hLnJtID0gVFJVRSksCiAgICBgcHJlZGljdGVkIG1hc3MgW2cvZ0RDV11gID0gc3VtKHByZWRpY3RlZF9tYXNzX2dfZ0RDVywgbmEucm0gPSBUUlVFKSwKICAgIHV0aWxpemF0aW9uID0gYHByZWRpY3RlZCBtYXNzIFtnL2dEQ1ddYC9gbWFzcyBbZy9nRENXXWAKICApICU+JSB1bmdyb3VwICU+JQogIAogICMgcmVwbGFjZSAwIHdpdGggTkEgYW5kIHJlb3JkZXIgZmFjdG9ycwogIG11dGF0ZShhY3Jvc3MobWF0Y2hlcygibWFzcyIpLCB+IG5hX2lmKC54LCAwKSkpICU+JQogIG11dGF0ZShtYWNoaW5lID0gbWFjaGluZSAlPiUgZmFjdG9yKC4sIHVuaXF1ZSguKVtjKDIsNCwzLDEpXSkpCgojIHBsb3QgbWFzcyBhbmQgdXRpbGl6YXRpb24KcGxvdF9tYWNoaW5lcnkgPC0KICAKICB4eXBsb3QoYG1hc3MgW2cvZ0RDV11gICsgYHByZWRpY3RlZCBtYXNzIFtnL2dEQ1ddYCB+IGZhY3Rvcihncm93dGhfcmF0ZSkgfCAKICAgIG1hY2hpbmUgKiBzdWJzdHJhdGUsIGRmX21hY2hpbmVyeV91dGlsLAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwgYXV0by5rZXkgPSBsaXN0KGNvbHVtbnMgPSAyLCBjZXggPSAwLjcpLAogICAgeGxhYiA9IGV4cHJlc3Npb24oIsK1IFtoIl4tMSoiXSIpLAogICAgeWxhYiA9IGV4cHJlc3Npb24oIm0iW3Byb3RlaW5dKiIgW2cgZ0RDVyJeLTEqIl0iKSwKICAgIHNjYWxlcyA9IGxpc3QoYWx0ZXJuYXRpbmcgPSBGQUxTRSwgeCA9IGxpc3QoYXQgPSBjKDIsNCkpKSwKICAgIGFzLnRhYmxlID0gVFJVRSwgeWxpbSA9IGMoLTAuMDEsIDAuMTIpLAogICAgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksIHBjaCA9IDE5LCBsd2QgPSAyLAogICAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCAuLi4pIHsKICAgICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgICBwYW5lbC5lcnJiYXJzKHgsIHksIGV3aWR0aCA9IDAsIC4uLikKICAgICAgcGFuZWwuc3VwZXJwb3NlKHgsIHksIC4uLikKICAgIH0sIHBhbmVsLmdyb3VwcyA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgICBwYW5lbC5sbWxpbmUoeCwgeSwgLi4uKQogICAgfQogICkKYGBgCgojIyBVbmRlci11dGlsaXphdGlvbiBvZiBtYWNoaW5lcnkgcHJvdGVpbnMKCkhlcmUsIHdlIGRldGVybWluZSB0aGUgKnVuZGVydXRpbGl6ZWQgbWFjaGluZXJ5IGZyYWN0aW9uKiBieSB0YWtpbmcgdGhlIHJhdGlvIG9mIHNpbXVsYXRlZCBvcHRpbWFsIGVuenltZSBhYnVuZGFuY2UgYW5kIGV4cGVyaW1lbnRhbGx5IG1lYXN1cmVkIGFidW5kYW5jZSwgb3ZlciBhbGwgZm91ciBjZW50cmFsIGRvZ21hIG1hY2hpbmVzLgoKYGBge3IsIGZpZy53aWR0aCA9IDYuNSwgZmlnLmhlaWdodCA9IDQuOCwgbWVzc2FnZSA9IEZBTFNFfQpwbG90X21hY2hpbmVyeV91dGlsIDwtIGRmX21hY2hpbmVyeSAlPiUKICAKICAjIHJlbW92ZSAxIG5vbi1xdWFudGlmaWVkIHJlcGxpY2F0ZQogIGZpbHRlcighKHN1YnN0cmF0ZSA9PSAiZnJ1Y3Rvc2UiICYgZ3Jvd3RoX3JhdGUgPT0gMC4xICYgcmVwbGljYXRlID09ICJSMiIpKSAlPiUKICAKICAjIHN1bW1hcml6aW5nIHByb3RlaW4gdXRpbGl6YXRpb24gZm9yIGFsbCBtYWNoaW5lcwogIG11dGF0ZShtYWNoaW5lID0gIm1hY2hpbmVzIikgJT4lCiAgZ3JvdXBfYnkobWFjaGluZSwgc3Vic3RyYXRlLCBncm93dGhfcmF0ZSwgcmVwbGljYXRlKSAlPiUKICBzdW1tYXJpemUoCiAgICBtYXNzID0gc3VtKG1hc3NfZ19nRENXLCBuYS5ybSA9IFRSVUUpLAogICAgcHJlZGljdGVkX21hc3MgPSBzdW0ocHJlZGljdGVkX21hc3NfZ19nRENXLCBuYS5ybSA9IFRSVUUpLAogICAgdXRpbGl6YXRpb24gPSBwcmVkaWN0ZWRfbWFzcy8obWFzcykKICApICU+JQogIAogICMgcGxvdC4gdXNlIGFsdGVybmF0aW5nID0gMiB0byBzd2l0Y2ggYXhpcyB0byByaWdodCBzaWRlCiAgeHlwbG90KHV0aWxpemF0aW9uKjEwMCB+IGZhY3Rvcihncm93dGhfcmF0ZSkgfCBtYWNoaW5lICogc3Vic3RyYXRlLCAuLAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwgCiAgICBzY2FsZXMgPSBsaXN0KHggPSBsaXN0KGF0ID0gYygyLDQpKSwgeSA9IGxpc3QoYWx0ZXJuYXRpbmcgPSBGQUxTRSkpLAogICAgeGxhYiA9IGV4cHJlc3Npb24oIsK1IFtoIl4tMSoiXSIpLCB5bGFiID0gIiIsCiAgICBrZXkgPSBzaW1wbGVLZXkoIiUgdXRpbGl6YXRpb24iLCBjZXggPSAwLjcpLAogICAgYXMudGFibGUgPSBUUlVFLCBiZXR3ZWVuID0gbGlzdCh4ID0gMC41LCB5ID0gMC41KSwKICAgIHBjaCA9IDE5LCBsd2QgPSAyLCBsYXlvdXQgPSBjKDEsIDQpLCB5bGltID0gYygtMTAsIDExMCksCiAgICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICAgIHhfbWVhbiA9IHVuaXF1ZSh4KTsgeV9tZWFuID0gdGFwcGx5KHksIHgsIG1lYW4pCiAgICAgIHBhbmVsLnh5YXJlYShjKDAsIHhfbWVhbiwgNiksIGMoMCwgeV9tZWFuLCB0YWlsKHlfbWVhbiwgMSkpLCAKICAgICAgICBsdHkgPSAwLCBjb2wgPSBncmV5KDAuNiwgYWxwaGEgPSAwLjUpLCAuLi4pCiAgICAgIHBhbmVsLmVycmJhcnMoeCwgeSwgZXdpZHRoID0gMCwgY29sID0gZ3JleSgwLjUpLCAuLi4pCiAgICB9CiAgKSAlPiUgdXNlT3V0ZXJTdHJpcHMKCnByaW50KHVzZU91dGVyU3RyaXBzKHBsb3RfbWFjaGluZXJ5KSwgcG9zaXRpb24gPSBjKDAsIDAsIDAuNzcsIDEuMDI3KSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfbWFjaGluZXJ5X3V0aWwsIHBvc2l0aW9uID0gYygwLjcxLCAwLCAxLjAyLCAxLjAyNykpCmdyaWQ6OmdyaWQudGV4dChjKCJCIiwgIkMiKSwgeCA9IGMoMC4wMiwgMC43NSksIHkgPSBjKDAuOTcsMC45NykpCmBgYAoKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CnN2ZygiLi4vZmlndXJlcy9maWd1cmVfbWFjaGluZV91dGlsLnN2ZyIsIHdpZHRoID0gNi41LCBoZWlnaHQgPSA0LjgpCnByaW50KHVzZU91dGVyU3RyaXBzKHBsb3RfbWFjaGluZXJ5KSwgcG9zaXRpb24gPSBjKDAsIDAsIDAuNzcsIDEuMDI3KSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfbWFjaGluZXJ5X3V0aWwsIHBvc2l0aW9uID0gYygwLjcxLCAwLCAxLjAyLCAxLjAyNykpCmdyaWQ6OmdyaWQudGV4dChjKCJCIiwgIkMiKSwgeCA9IGMoMC4wMiwgMC43NSksIHkgPSBjKDAuOTcsMC45NykpCmRldi5vZmYoKQpgYGAKCgojIFVuZGVyLXV0aWxpemF0aW9uIG9mIGVuenltZXMKCiMjIEdsb2JhbCB0cmVuZHMgaW4gdXRpbGl6YXRpb24KCldlIGNhbiBzdWJkaXZpZGUgZW56eW1lcyBieSB1dGlsaXphdGlvbiBhbmQgcGxvdCBhdmVyYWdlIG1hc3MsIGF2ZXJhZ2UgdmFyaWFiaWxpdHksIGFuZCBhdmVyYWdlIGVzc2VudGlhbGl0eS4KQXZlcmFnZSBlc3NlbnRpYWxpdHkgaGVyZSBtZWFucyB0aGF0IGV2ZXJ5IHJlYWN0aW9uIHRoYXQgaXMgYXNzb2NpYXRlZCB3aXRoIGF0IGxlYXN0IG9uZSBwcm9iYWJseSBlc3NlbnRpYWwgZ2VuZXMgaXMgcHJvYmFibHkgZXNzZW50aWFsLCBhbmQgZXZlcnkgcmVhY3Rpb25zIHdpdGggYXQgbGVhc3Qgb25lIGVzc2VudGlhbCBnZW5lIGlzIGNvdW50ZWQgYXMgZXNzZW50aWFsICh0YWtpbmcgcHJlY2VkZW5jZSBvdmVyICdwcm9iYWJseSBlc3NlbnRpYWwnKS4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmRmX3V0aWwgPC0gZGZfcHJvdF9jb21wICU+JQogIAogICMgZmlsdGVyIHJlYWN0aW9ucyB3aXRob3V0IHByb3RlaW4gbWVhc3VyZW1lbnQgb3Igc3Vic3RyYXRlCiAgZmlsdGVyKCEoaXMubmEobWFzc19nX2dEQ1cpIHwgaXMubmEoc3Vic3RyYXRlKSksIGdyb3d0aF9yYXRlID09IDAuMjUpICU+JQogIAogICMgZmlyc3QgY2FsY3VsYXRlIHV0aWxpemF0aW9uIHBlciBjb25kaXRpb24KICBncm91cF9ieShtb2RlbF9ncm91cF9iYXNpYywgcmVhY3Rpb25faWQsIHJlYWN0aW9uX25hbWUsIHN1YnN0cmF0ZSkgJT4lCiAgc3VtbWFyaXplKC5ncm91cHMgPSAiZHJvcF9sYXN0IiwKICAgIG1hc3NfZ19nRENXID0gbWVhbihtYXNzX2dfZ0RDVywgbmEucm0gPSBUUlVFKSwKICAgIHByZWRpY3RlZF9tYXNzX2dfZ0RDVyA9IG1lYW4ocHJlZGljdGVkX21hc3NfZ19nRENXLCBuYS5ybSA9IFRSVUUpLAogICAgdXRpbGl6YXRpb24gPSBwcmVkaWN0ZWRfbWFzc19nX2dEQ1cvbWFzc19nX2dEQ1cKICApICU+JQogIAogICMgZGV0ZXJtaW5lIGF2ZXJhZ2UgbWFzcywgc2QgYW5kIHV0aWxpemF0aW9uIHBlciByZWFjdGlvbgogIHN1bW1hcml6ZSguZ3JvdXBzID0gImRyb3BfbGFzdCIsCiAgICBtZWFuX21hc3MgPSBtZWFuKG1hc3NfZ19nRENXLCBuYS5ybSA9IFRSVUUpLAogICAgc2RfbWFzcyA9IHNkKG1hc3NfZ19nRENXLCBuYS5ybSA9IFRSVUUpLAogICAgY3ZfbWFzcyA9IHNkX21hc3MvbWVhbl9tYXNzLAogICAgdXRpbGl6YXRpb24gPSBtZWFuKHV0aWxpemF0aW9uLCBuYS5ybSA9IFRSVUUpCiAgKSAlPiUKICAKICAjIG1hbnVhbCAnY2x1c3RlcmluZycgYmFzZWQgb24gcHJvdGVpbiB2cyBncm93dGggcmF0ZSBzbG9wZQogIG11dGF0ZSh1dGlsaXphdGlvbl9ncm91cCA9IGNhc2Vfd2hlbigKICAgIHV0aWxpemF0aW9uIDw9IDAuMzMgfiAibG93IiwKICAgIGJldHdlZW4odXRpbGl6YXRpb24sIDAuMzMsIDAuNjYpIH4gIm1vZGVyYXRlIiwKICAgIHV0aWxpemF0aW9uID4gMC42NiB+ICJoaWdoIiwKICApICU+JSBmYWN0b3IoLiwgYygibG93IiwgIm1vZGVyYXRlIiwgImhpZ2giKSkpCgoKIyBpbXBvcnQgZXNzZW50aWFsaXR5IGZyb20gVG5TZXEvQmFyU2VxIGRhdGEKZGZfdXRpbCA8LSByZWFkX2NzdigiLi4vZGF0YS9vdXRwdXQvZXNzZW50aWFsaXR5X2VzY2hlci5jc3YiLCBjb2xfdHlwZXMgPSBjb2xzKCkpICU+JQogIG11dGF0ZShlc3NlbnRpYWxpdHkgPSBlc3NlbnRpYWxpdHkgJT4lIHJlY29kZSgKICAgIGAwYCA9ICJub3QgZXNzZW50aWFsIiwgYDFgID0gInByb2JhYmx5IGVzc2VudGlhbCIsIGAyYCA9ICJlc3NlbnRpYWwiKSkgJT4lCiAgbXV0YXRlKGVzc2VudGlhbGl0eSA9IGNhc2Vfd2hlbigKICAgIGxvY3VzX3RhZyAlaW4lIHJlYWRfY3N2KCIuLi9kYXRhL291dHB1dC9lc3NlbnRpYWxpdHlfZnJ1Y3Rvc2UuY3N2IiwgY29sX3R5cGVzID0gY29scygpKSRsb2N1c190YWcgfiAiZXNzZW50aWFsIiwKICAgIFRSVUUgfiBlc3NlbnRpYWxpdHkKICApKSAlPiUKICAjIG1lcmdlIHdpdGggdXRpbGl6YXRpb24KICBpbm5lcl9qb2luKGRmX21vZGVsX3JlYWN0aW9ucyAlPiUgcmVuYW1lKGxvY3VzX3RhZyA9IGdlbmVzKSkgJT4lCiAgICBncm91cF9ieShyZWFjdGlvbl9pZCkgJT4lIHN1bW1hcml6ZShlc3NlbnRpYWxpdHkgPSBtYXgoZXNzZW50aWFsaXR5LCBuYS5ybSA9IFRSVUUpICU+JQogICAgcmVjb2RlKGAwYCA9ICJub3QgZXNzZW50aWFsIiwgYDFgID0gInByb2JhYmx5IGVzc2VudGlhbCIsIGAyYCA9ICJlc3NlbnRpYWwiKSkgJT4lCiAgcmlnaHRfam9pbihkZl91dGlsKQpgYGAKClRlc3QgaWYgbWVhbiBlbnp5bWUgbWFzcyBhbmQgZW56eW1lIHZhcmlhYmlsaXR5IChDVikgYXJlIG5vcm1hbC1kaXN0cmlidXRlZC4gVGhpcyBpcyBpbXBvcnRhbnQgZm9yIGNob29zaW5nIHRoZSAgc3RhdGlzdGljYWwgdGVzdCBmb3Igc2lnbmlmaWNhbmNlLiBXZSB1c2UgdGhlIFNoYXBpcm8tV2lsa3MgdGVzdCBmb3Igbm9ybWFsaXR5LgoKYGBge3J9CmRmX3V0aWwgJT4lIGdyb3VwX2J5KHV0aWxpemF0aW9uX2dyb3VwKSAlPiUKICBzdW1tYXJpemUoCiAgICBTaGFwaXJvX1dpbGtfcHZhbHVlX21hc3MgPSBzaGFwaXJvLnRlc3QobG9nMTAobWVhbl9tYXNzKSkkcC52YWx1ZSwKICAgIFNoYXBpcm9fV2lsa19wdmFsdWVfQ1YgPSBzaGFwaXJvLnRlc3QobG9nMTAoY3ZfbWFzcykpJHAudmFsdWUKICApCmBgYAoKTm93IHBsb3QgYXZlcmFnZSBtYXNzIGluIGcvZ0RDVywgQ1Ygb2YgbWFzcyBpbiBnL2dEQ1cgYW5kIGVzc2VudGlhbGl0eSAoc2NvcmUpIGZyb20gVG5TZXEgZXhwZXJpbWVudHMuCkFzIHNvbWUgb2YgdGhlIGRhdGEgaXMgbm90IG5vcm1hbGx5IGRpc3RyaWJ1dGVkLCB3ZSBhcHBseSBhIG5vbi1wYXJhbWV0cmljIHNpZ25pZmljYW5jZSB0ZXN0IGluc3RlYWQgLSBoZXJlLCBmb3IgY29tcGFyaXNvbiBvZiB0d28gc2FtcGxlcywgdGhlIGJlc3QgYWx0ZXJuYXRpdmUgdG8gU3R1ZGVudCdzIHQtdGVzdCBpcyB0aGUgTWFubi1XaGl0bmV5IFUtdGVzdC4gRm9yIG9uZSBzYW1wbGUgY29tcGFyaXNvbnMsIHRoZSBub24tcGFyYW1ldHJpYyBhbHRlcm5hdGl2ZSB0byB0LXRlc3Qgd291bGQgYmUgdGhlIFdpbGNveG9uIFJhbmsgU3VtIHRlc3QuCgpgYGB7ciwgZmlnLndpZHRoID0gNy41LCBmaWcuaGVpZ2h0ID0gMi41LCBtZXNzYWdlID0gRkFMU0V9CmJveHBsb3RfY29sb3JibGluZCA8LSBjdXN0b20uY29sb3JibGluZCgpCmJveHBsb3RfY29sb3JibGluZCRib3gucmVjdGFuZ2xlJGx3ZCA9IDEuNQpib3hwbG90X2NvbG9yYmxpbmQkYm94LnVtYnJlbGxhJGx3ZCA9IDEuNQoKCnBsb3RfdW5kZXJ1dGlsX21hc3MgPC0geHlwbG90KGxvZzEwKG1lYW5fbWFzcykgfiB1dGlsaXphdGlvbl9ncm91cCwKICAgIGRmX3V0aWwsCiAgICBwYXIuc2V0dGluZ3MgPSBib3hwbG90X2NvbG9yYmxpbmQsCiAgICBob3Jpem9udGFsID0gRkFMU0UsIGRvLm91dCA9IEZBTFNFLCB5bGltID0gYygtOCwgLTEpLAogICAgeGxhYiA9ICJ1dGlsaXphdGlvbiIsIHlsYWIgPSBleHByZXNzaW9uKCJtIltwcm90ZWluXSoiIFtsb2ciWzEwXSoiIGcgZ0RDVyJeLTEqIl0iKSwKICAgIHNjYWxlcyA9IGxpc3QoeCA9IGxpc3QoY2V4ID0gMC43KSksCiAgICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICAgIHBhbmVsLnZpb2xpbih4LCB5LCBsd2QgPSAwLCBjb2wgPSBncmV5KDAuNiwgMC41KSwgYm94LndpZHRoID0gMC43LCAuLi4pCiAgICAgIHBhbmVsLmJ3cGxvdCh4LCB5LCBwY2ggPSAifCIsIGJveC53aWR0aCA9IDAuMywgLi4uKQogICAgICBwYW5lbC5wdmFsdWUoeCwgeSwgY2V4ID0gMC43LCBmaXhlZF9wb3MgPSAtMC45LCB2ZXJib3NlID0gVFJVRSwgbWV0aG9kID0gIndpbGNveC50ZXN0IiwgLi4uKQogICAgICBwYW5lbC50ZXh0KHVuaXF1ZSh4KSwgLTcuNSwgbGFiZWxzID0gcGFzdGUwKCJuPSIsIHRhYmxlKHgpKSwgY2V4ID0gMC42KQogICAgfQogICkKCnBsb3RfdW5kZXJ1dGlsX2N2IDwtIHh5cGxvdChsb2cxMChjdl9tYXNzKSB+IHV0aWxpemF0aW9uX2dyb3VwLAogICAgZGZfdXRpbCwKICAgIHBhci5zZXR0aW5ncyA9IGJveHBsb3RfY29sb3JibGluZCwKICAgIGhvcml6b250YWwgPSBGQUxTRSwgZG8ub3V0ID0gRkFMU0UsIHlsaW0gPSBjKC0xLjYsMC42KSwKICAgIHhsYWIgPSAidXRpbGl6YXRpb24iLCB5bGFiID0gZXhwcmVzc2lvbigiQ1YgbSJbcHJvdGVpbl0qIiBbbG9nIlsxMF0qIl0iKSwKICAgIHNjYWxlcyA9IGxpc3QoeCA9IGxpc3QoY2V4ID0gMC43KSksCiAgICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICAgIHBhbmVsLnZpb2xpbih4LCB5LCBsd2QgPSAwLCBjb2wgPSBncmV5KDAuNiwgMC41KSwgYm94LndpZHRoID0gMC43LCAuLi4pCiAgICAgIHBhbmVsLmJ3cGxvdCh4LCB5LCBwY2ggPSAifCIsIGJveC53aWR0aCA9IDAuMywgLi4uKQogICAgICBwYW5lbC5wdmFsdWUoeCwgeSwgY2V4ID0gMC43LCBmaXhlZF9wb3MgPSAwLjY1LCB2ZXJib3NlID0gVFJVRSwgbWV0aG9kID0gIndpbGNveC50ZXN0IiwgLi4uKQogICAgICBwYW5lbC50ZXh0KHVuaXF1ZSh4KSwgLTEuNDUsIGxhYmVscyA9IHBhc3RlMCgibj0iLCB0YWJsZSh4KSksIGNleCA9IDAuNikKICAgIH0KICApCgpwbG90X3VuZGVydXRpbF9lcyA8LSB4eXBsb3Qobl9yZWFjdGlvbnMgfiB1dGlsaXphdGlvbl9ncm91cCwKICAgIGRmX3V0aWwgJT4lIGdyb3VwX2J5KHV0aWxpemF0aW9uX2dyb3VwLCBlc3NlbnRpYWxpdHkpICU+JSBzdW1tYXJpemUobl9yZWFjdGlvbnMgPSBuKCkpLAogICAgcGFyLnNldHRpbmdzID0gYm94cGxvdF9jb2xvcmJsaW5kLAogICAgY29sID0gY3VzdG9tLmNvbG9yYmxpbmQoKSRzdXBlcnBvc2UucG9seWdvbiRjb2xbYyg1LDQsMildLAogICAgZ3JvdXBzID0gZmFjdG9yKGVzc2VudGlhbGl0eSwgYygibm90IGVzc2VudGlhbCIsICJwcm9iYWJseSBlc3NlbnRpYWwiLCAiZXNzZW50aWFsIikpLAogICAgeGxhYiA9ICJ1dGlsaXphdGlvbiIsIHlsYWIgPSAibnVtYmVyIG9mIHJlYWN0aW9ucyIsIHlsaW0gPSBjKC0yMCwgNjIwKSwKICAgIGV3aWR0aCA9IDAuMSwgbHdkID0gMiwgc2NhbGVzID0gbGlzdCh4ID0gbGlzdChjZXggPSAwLjcpKSwKICAgIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgICAgcGFuZWwuYmFycGxvdCh4LCB5LCBiZXNpZGUgPSBUUlVFLCAuLi4pCiAgICAgIHBhbmVsLmtleSguLi4sIHBvaW50cyA9IEZBTFNFLCBjb3JuZXIgPSBjKDAuOTUsIDAuOTUpLCBjZXggPSAwLjcpCiAgICB9CiAgKQoKcHJpbnQocGxvdF91bmRlcnV0aWxfbWFzcywgcG9zaXRpb24gPSBjKDAsMCwwLjM1LDEuMDUpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF91bmRlcnV0aWxfY3YsIHBvc2l0aW9uID0gYygwLjMyLDAsMC42OCwxLjA1KSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfdW5kZXJ1dGlsX2VzLCBwb3NpdGlvbiA9IGMoMC42NCwwLDEsMS4wNSkpCmdyaWQ6OmdyaWQudGV4dChjKCJBIiwgIkIiLCAiQyIpLCB4ID0gYygwLjAyLDAuMzUsMC42NSksIHkgPSBjKDAuOTQsMC45NCwwLjk0KSkKYGBgCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQpzdmcoIi4uL2ZpZ3VyZXMvZmlndXJlX2VuenltZV91dGlsLnN2ZyIsIHdpZHRoID0gNy41LCBoZWlnaHQgPSAyLjUpCnByaW50KHBsb3RfdW5kZXJ1dGlsX21hc3MsIHBvc2l0aW9uID0gYygwLDAsMC4zNSwxLjA1KSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfdW5kZXJ1dGlsX2N2LCBwb3NpdGlvbiA9IGMoMC4zMiwwLDAuNjgsMS4wNSksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X3VuZGVydXRpbF9lcywgcG9zaXRpb24gPSBjKDAuNjQsMCwxLDEuMDUpKQpncmlkOjpncmlkLnRleHQoYygiQSIsICJCIiwgIkMiKSwgeCA9IGMoMC4wMiwwLjM1LDAuNjUpLCB5ID0gYygwLjk0LDAuOTQsMC45NCkpCmRldi5vZmYoKQpgYGAKCgpXZSBjYW4gdHJ5IHRvIGNvbWJpbmUgc2V2ZXJhbCB0eXBlcyBvZiBpbmZvcm1hdGlvbiBmb3IgZWFjaCByZWFjdGlvbiBpbiBjb25jaXNlIG1pbmktZmlndXJlczoKCi0gdGhlIGVzc2VudGlhbGl0eSAoc2ltcGx5IGJ5IGNvbG9yIGFyb3VuZCByZWFjdGlvbiBuYW1lKQotIHRoZSBhdmVyYWdlIHByb3RlaW4gbWFzcyBhc3NvY2lhdGVkIHdpdGggdGhlIHJlYWN0aW9uCi0gdGhlIGNoYW5nZSBvdmVyIGNvbmRpdGlvbnMgKENWKQotIHRoZSB1dGlsaXphdGlvbi9zYXR1cmF0aW9uCgpgYGB7ciwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDMuNX0KIyBlbnp5bWVzIGdyb3VwZWQgYnkgcGF0aHdheQpsaXN0X2NjbV9lbnp5bWVzIDwtIGxpc3QoCiAgY2JiID0gYygiUEdLIiwgIlRQSSIsICJHQVBEIiwgIkZCQSIsICJGQlAiLCAiVEtUMSIsICJUS1QyIiwgIlRBaCIsICJSUEUiLCAiUlBJIiwgIlBSVUsiLCAiUkJQQyIsICJGREgiKSwKICBlZCA9IGMoIlBHSSIsICJHNlBESDJyIiwgIlBHTCIsICJFREQiLCAiRURBIiksCiAgcHlyID0gYygiUEdNIiwgIkVOTyIsICJQWUsiLCAiUERIMSIsICJQREgyIiwgIlBESDMiLCAiUEMiLCAiUFBDIiwgIk1FMSIsICJNRTIiLCAiQ1MiKSwKICB0Y2EgPSBjKCJBQ09OVDEiLCAiQUNPTlQyIiwgIklDREh4IiwgIklDREh5ciIsICJBS0dESCIsICJTVUNPQVMiLCAiU1VDRGkiLCAiU1VDRDEiLCAiRlVNIiwgIk1ESCIsICJNQUxTIikKKQoKcGxvdF9taW5pZmlncyA8LSBkZl9wcm90X2NvbXAgJT4lCiAgZmlsdGVyKHJlYWN0aW9uX2lkICVpbiUgdW5saXN0KGxpc3RfY2NtX2VuenltZXMpLAogICAgZ3Jvd3RoX3JhdGUgPT0gMC4yNSkgJT4lCiAgZ3JvdXBfYnkocmVhY3Rpb25faWQsIHN1YnN0cmF0ZSkgJT4lCiAgIyBjYWxjdWxhdGUgdXRpbGl6YXRpb24KICBtdXRhdGUodXRpbGl6YXRpb24gPSBwcmVkaWN0ZWRfbWFzc19nX2dEQ1cvbWFzc19nX2dEQ1cpICU+JQogIHN1bW1hcml6ZSh1dGlsaXphdGlvbiA9IG1lYW4odXRpbGl6YXRpb24pLCBtZWFuX21hc3MgPSBtZWFuKG1hc3NfZ19nRENXKSkgJT4lCiAgIyByZXNjYWxlIGFsbCB0byBuZXcgcmFuZ2UgYmV0d2VlbiAwIGFuZCAxCiAgdW5ncm91cCAlPiUKICBtdXRhdGUoYWNyb3NzKG1hdGNoZXMoIm1hc3N8dXRpbGl6YXRpb24kIiksIHNjYWxlczo6cmVzY2FsZSkpICU+JQogIHBpdm90X2xvbmdlcihhbGxfb2YoYygibWVhbl9tYXNzIiwgInV0aWxpemF0aW9uIikpKSAlPiUKICBtdXRhdGUobmFtZSA9IGZhY3RvcihuYW1lLCBjKCJtZWFuX21hc3MiLCAidXRpbGl6YXRpb24iKSkpICU+JQogIAogIHh5cGxvdCh2YWx1ZSB+IG5hbWUgfCByZWFjdGlvbl9pZCwgLiwKICAgIGdyb3VwcyA9IHN1YnN0cmF0ZSwgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwKICAgIHhsYWIgPSAiIiwgeWxhYiA9ICIiLCBmaWxsX2FscGhhID0gMSwKICAgIGV3aWR0aCA9IDAuMDksIGx0eSA9IDEuNSwgYXMudGFibGUgPSBUUlVFLAogICAgc2NhbGVzID0gbGlzdChkcmF3ID0gRkFMU0UpLCB5bGltID0gYygtMC4xLCAxLjApLAogICAgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksCiAgICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgICBwYW5lbC5yZWN0KDAuNSwtMC4xLDIuNSwxLjEsIGNvbCA9ICJ3aGl0ZSIsIGJvcmRlciA9ICJ0cmFuc3BhcmVudCIpCiAgICAgIHBhbmVsLnJlY3QoMC41LC0wLjEsMS41LDEuMCwgY29sID0gZ3JleSgwLjkpLCBib3JkZXIgPSAidHJhbnNwYXJlbnQiKQogICAgICBwYW5lbC5iYXJwbG90KHgsIHksIGJlc2lkZSA9IFRSVUUsIC4uLikKICAgIH0KICApCgpwcmludChwbG90X21pbmlmaWdzKQpgYGAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CnN2ZygiLi4vZmlndXJlcy9maWd1cmVfbWluaWZpZ3NfdXRpbC5zdmciLCB3aWR0aCA9IDUsIGhlaWdodCA9IDMuNSkKcHJpbnQocGxvdF9taW5pZmlncykKZGV2Lm9mZigpCmBgYAoKCiMjIENlbnRyYWwgY2FyYm9uIG1ldGFib2xpc20sIGRldGFpbGVkIHV0aWxpemF0aW9uIHBlciBlbnp5bWUKCkFuYWxvZ291c2x5IHRvIHRoZSBhYm92ZSBhbmFseXNpcywgd2UgY2FuIHBsb3QgcHJvdGVpbiBhYnVuZGFuY2UgYW5kIHV0aWxpemF0aW9uIGZvciBhbGwgZW56eW1lcyBvZiBjZW50cmFsIGNhcmJvbiBtZXRhYm9saXNtIChmcm9tIEZpZ3VyZSAzIEQpLiAKCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOH0KcGxvdF9lbnpfY2NtIDwtIGxhcHBseShsaXN0X2NjbV9lbnp5bWVzLCBmdW5jdGlvbihsc3QpIHsKICBkZl9wcm90X2NvbXAgJT4lCiAgZmlsdGVyKHJlYWN0aW9uX2lkICVpbiUgbHN0LCAhaXMubmEoY29uZGl0aW9uKSkgJT4lCiAgbXV0YXRlKHJlYWN0aW9uX2lkID0gZmFjdG9yKHJlYWN0aW9uX2lkLCBsc3QpKSAlPiUKICBncm91cF9ieShyZWFjdGlvbl9pZCkgJT4lIG11dGF0ZShtYXNzX2dfZ0RDVyA9IHNjYWxlczo6cmVzY2FsZShtYXNzX2dfZ0RDVykpICU+JQogIAogIHh5cGxvdChtYXNzX2dfZ0RDVyB+IGZhY3Rvcihncm93dGhfcmF0ZSkgfCByZWFjdGlvbl9pZCwgLiwKICAgIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksCiAgICBncm91cHMgPSBzdWJzdHJhdGUsIHhsYWIgPSAiIiwgI2V4cHJlc3Npb24oIsK1IFtoIl4tMSoiXSIpLAogICAgI3lsYWIgPSBleHByZXNzaW9uKCJtIltwcm90ZWluXSoiIFtnIGdEQ1ciXi0xKiJdIiksCiAgICB5bGFiID0gInJlbGF0aXZlIGFidW5kYW5jZSIsCiAgICBsYXlvdXQgPSBjKDgsIGlmZWxzZShsZW5ndGgobHN0KSA+IDgsIDIsIDEpKSwKICAgIGV3aWR0aCA9IDAsIGx3ZCA9IDIsIGFzLnRhYmxlID0gVFJVRSwKICAgIGJldHdlZW4gPSBsaXN0KHggPSAwLjUsIHkgPSAwLjUpLAogICAgc2NhbGVzID0gbGlzdChhbHRlcm5hdGluZyA9IEZBTFNFLCB4ID0gbGlzdChhdCA9IGMoMiwgNCkpKSwKICAgIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgICAgcGFuZWwuZXJyYmFycyh4LCB5LCAuLi4pCiAgICB9CiAgKQp9KQoKcHJpbnQocGxvdF9lbnpfY2NtW1sxXV0sIHBvc2l0aW9uID0gYygwLjAsMC42NywxLDEpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9lbnpfY2NtW1syXV0sIHBvc2l0aW9uID0gYygwLDAuNTEsMSwwLjc1KSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfZW56X2NjbVtbM11dLCBwb3NpdGlvbiA9IGMoMCwwLjI2LDEsMC41OSksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X2Vuel9jY21bWzRdXSwgcG9zaXRpb24gPSBjKDAsMCwxLDAuMzQpKQpncmlkOjpncmlkLnRleHQoYygiQ0JCIiwgIkVEIiwgIlBZUiIsICJUQ0EiKSwgeCA9IGMoMC4wMywgMC4wMywgMC4wMywgMC4wMyksIHkgPSBjKDAuOTcsIDAuNzEsIDAuNTMsIDAuMykpCmBgYAoKQW5kIHdlIGNhbiBhZGQgdXRpbGl6YXRpb24gZm9yIHRoZSBkaWZmZXJlbnQgZW56eW1lcyBhbmQgY29uZGl0aW9ucyB0b28uCgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDh9CnBsb3RfdXRpbF9jY20gPC0gbGFwcGx5KGxpc3RfY2NtX2VuenltZXMsIGZ1bmN0aW9uKGxzdCkgewogIGRmX3Byb3RfY29tcCAlPiUKICBmaWx0ZXIocmVhY3Rpb25faWQgJWluJSBsc3QsICFpcy5uYShjb25kaXRpb24pKSAlPiUKICBtdXRhdGUocmVhY3Rpb25faWQgPSBmYWN0b3IocmVhY3Rpb25faWQsIGxzdCkpICU+JQogICMgY2FsY3VsYXRlIHV0aWxpemF0aW9uCiAgbXV0YXRlKHBlcmNlbnRfdXRpbGl6YXRpb24gPSBwcmVkaWN0ZWRfbWFzc19nX2dEQ1cvbWFzc19nX2dEQ1cqMTAwKSAlPiUKICAKICB4eXBsb3QocGVyY2VudF91dGlsaXphdGlvbiB+IGZhY3Rvcihncm93dGhfcmF0ZSkgfCByZWFjdGlvbl9pZCwgLiwKICAgIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksCiAgICBncm91cHMgPSBzdWJzdHJhdGUsCiAgICB4bGFiID0gIiIsIHlsYWIgPSAiJSB1dGlsaXphdGlvbiIsIHlsaW0gPSBjKC0yMCwgMjAwKSwKICAgIGxheW91dCA9IGMoOCwgaWZlbHNlKGxlbmd0aChsc3QpID4gOCwgMiwgMSkpLAogICAgZXdpZHRoID0gMCwgbHdkID0gMiwgYXMudGFibGUgPSBUUlVFLAogICAgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksCiAgICBzY2FsZXMgPSBsaXN0KGFsdGVybmF0aW5nID0gRkFMU0UsIHggPSBsaXN0KGF0ID0gYygyLCA0KSkpLAogICAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCAuLi4pIHsKICAgICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgICBwYW5lbC5lcnJiYXJzKHgsIHksIC4uLikKICAgIH0KICApCn0pCgpwcmludChwbG90X3V0aWxfY2NtW1sxXV0sIHBvc2l0aW9uID0gYygwLjAsMC42NywxLDEpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF91dGlsX2NjbVtbMl1dLCBwb3NpdGlvbiA9IGMoMCwwLjUxLDEsMC43NSksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X3V0aWxfY2NtW1szXV0sIHBvc2l0aW9uID0gYygwLDAuMjYsMSwwLjU5KSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfdXRpbF9jY21bWzRdXSwgcG9zaXRpb24gPSBjKDAsMCwxLDAuMzQpKQpncmlkOjpncmlkLnRleHQoYygiQ0JCIiwgIkVEIiwgIlBZUiIsICJUQ0EiKSwgeCA9IGMoMC4wMywgMC4wMywgMC4wMywgMC4wMyksIHkgPSBjKDAuOTcsIDAuNzEsIDAuNTMsIDAuMykpCmBgYAoKRmluYWxseSB3ZSBjYW4gdGFrZSBhIGxvb2sgb24gdHJlbmRzIHdpdGggZ3Jvd3RoIHJhdGUsIGJ5IGZpdHRpbmcgYSBsaW5lYXIgbW9kZWwgZm9yIGVhY2ggZW56eW1lIGFuZCBjb25kaXRpb24gb3ZlciBncm93dGggcmF0ZSwgYW5kIHBsb3R0aW5nIHRoZSBSIG9yIFIgc3F1YXJlZCBwZXIgY29uZGl0aW9uLiBUaGUgUiAoY29ycmVsYXRpb24gY29lZmZpY2llbnQpIHNob3VsZCBpbmRpY2F0ZSB0aGUgdHJlbmQsIGkuZS4gaW5jcmVhc2luZyBvciBkZWNyZWFzaW5nIHdpdGggZ3Jvd3RoIHJhdGUgZm9yIGEgY2VydGFpbiBzdWJzdHJhdGUuCgpgYGB7ciwgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDYsIG1lc3NhZ2UgPSBGQUxTRX0KcGxvdF9sbV9jY20gPC0gbGFwcGx5KGxpc3RfY2NtX2VuenltZXMsIGZ1bmN0aW9uKGxzdCkgewogIAogIGRmX3Byb3RfY29tcCAlPiUKICBmaWx0ZXIocmVhY3Rpb25faWQgJWluJSBsc3QsICFpcy5uYShjb25kaXRpb24pKSAlPiUKICBtdXRhdGUocmVhY3Rpb25faWQgPSBmYWN0b3IocmVhY3Rpb25faWQsIGxzdCkpICU+JQogIGdyb3VwX2J5KHJlYWN0aW9uX2lkKSAlPiUgbXV0YXRlKG1hc3NfZ19nRENXID0gc2NhbGVzOjpyZXNjYWxlKG1hc3NfZ19nRENXKSkgJT4lCiAgCiAgIyBmaXQgbGluZWFyIG1vZGVsCiAgZ3JvdXBfYnkocmVhY3Rpb25faWQsIHN1YnN0cmF0ZSkgJT4lCiAgc3VtbWFyaXplKAogICAgbGluX3JlZ19zbG9wZSA9IHN1bW1hcnkobG0obWFzc19nX2dEQ1cgfiBncm93dGhfcmF0ZSkpJGNvZWZbMl0sCiAgICBsaW5fcmVnX3B2YWx1ZSA9IHN1bW1hcnkobG0obWFzc19nX2dEQ1cgfiBncm93dGhfcmF0ZSkpJGNvZWZbOF0KICApICU+JQogIG11dGF0ZShsaW5fcmVnX3Nsb3BlID0gbGluX3JlZ19zbG9wZSAlPiUgCiAgICBpZl9lbHNlKC4gPiAyLjUsIDIuNSwgLikgJT4lIGlmX2Vsc2UoLiA8IC0yLjUsIC0yLjUsIC4pKSAlPiUKICAKICBsZXZlbHBsb3QobGluX3JlZ19zbG9wZSB+IHJlYWN0aW9uX2lkICogZmFjdG9yKHN1YnN0cmF0ZSwgdW5pcXVlKHN1YnN0cmF0ZSlbYygzLDIsMSw0KV0pLCAuLAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwgYXQgPSBzZXEoLTIuNSwgMi41LCBieSA9IDAuMjUpLAogICAgY29sLnJlZ2lvbnMgPSBjb2xvcnNwYWNlOjpkaXZlcmdlX2hjbCgyMCksIHlsYWIgPSAiIiwKICAgIGFzLnRhYmxlID0gVFJVRSwgeGxhYiA9ICIiLAogICAgc2NhbGVzID0gbGlzdCh4ID0gbGlzdChjZXggPSAwLjY1KSkKICApCn0pCgoKcHJpbnQocGxvdF9sbV9jY21bWzFdXSwgcG9zaXRpb24gPSBjKDAuMCwwLjY4LDEsMSksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X2xtX2NjbVtbMl1dLCBwb3NpdGlvbiA9IGMoMCwwLjQ2LDEsMC43NyksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X2xtX2NjbVtbM11dLCBwb3NpdGlvbiA9IGMoMCwwLjI0LDEsMC41NSksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X2xtX2NjbVtbNF1dLCBwb3NpdGlvbiA9IGMoMCwwLDEsMC4zMikpCmdyaWQ6OmdyaWQudGV4dChjKCJDQkIiLCAiRUQiLCAiUFlSIiwgIlRDQSIpLCB4ID0gYygwLjAzLCAwLjAzLCAwLjAzLCAwLjAzKSwgeSA9IGMoMC45NywgMC43MywgMC41LCAwLjI4KSkKYGBgCgojIyBFeGFtcGxlIG9mIHVuZGVydXRpbGl6YXRpb24gb2YgZW56eW1lcyBmcm9tIENiYiBvcGVyb24gCgpTaW1pbGFyIHRvIG1hY2hpbmVyeSBwcm90ZWlucyB3ZSBjYW4gYWxzbyBmb2xsb3cgdGhlIHNpbXVsYXRpb24gYW5kIGFjdHVhbCBwcm90ZWluIGFidW5kYW5jZSBvZiBhbGwgKGRldGVjdGVkKSBlbnp5bWVzLiBXZSBjYW4gc2VlIHRoYXQgc29tZSBwcm90ZWluIGFidW5kYW5jZXMgbmljZWx5IGZvbGxvdyBhIGdyb3d0aC1yYXRlIGRlcGVuZGVudCBtYW5uZXIsIGluIGxpbmUgd2l0aCBwcmVkaWN0aW9ucyBvZiBoaWdoZXIgZmx1eC4gT25lIHN1Y2ggZXhhbXBsZSBhcmUgQ2FsdmluIGN5Y2xlIGVuenltZXMgZm9yIGZvcm1hdG90cm9waGljIGdyb3d0aC4gSG93ZXZlciwgZm9yIHRoZSBzYW1lIGVuenltZXMgd2Ugc2VlIHRoYXQgbm8gYWJ1bmRhbmNlIGlzIHByZWRpY3RlZCB1bmRlciBoZXRlcm90cm9waGljIGNvbmRpdGlvbnMgYmVjYXVzZSBvZiBtaXNzaW5nIGZsdXggdGhyb3VnaCB0aGUgcGF0aHdheSwgYnV0IGluIGZhY3QgdGhlIHByb3RlaW5zIGFyZSBleHByZXNzZWQgaW4gaGlnaCBhYnVuZGFuY2UsIG9mdGVuIGluIGEgZ3Jvd3RoLXJhdGUgZGVwZW5kZW50IG1hbm5lci4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aCA9IDYsIGZpZy5oZWlnaHQgPSA0Ljh9CmNiYl9zZWxlY3RlZCA8LSBjKCJQR0siLCAiR0FQRCIsICJGQkEiLCAiRkJQIiwgIlRLVDEiLCAiUFJVSyIsICJSQlBDIikKIyAiVEtUMiIsICJUQWgiLCAiUlBFIiwgIlJQSSIKCiMgc3VtbWFyeSB0YWJsZSBvZiBzZWxlY3RlZCBDYWx2aW4gY3ljbGUgZ2VuZXMKZGZfY2FsdmluIDwtIGRmX3Byb3RfY29tcCAlPiUKICAKICAjIHJlbW92ZSAxIG5vbi1xdWFudGlmaWVkIHJlcGxpY2F0ZQogIGZpbHRlcighKHN1YnN0cmF0ZSA9PSAiZnJ1Y3Rvc2UiICYgZ3Jvd3RoX3JhdGUgPT0gMC4xICYgcmVwbGljYXRlID09ICJSMiIpKSAlPiUKICAKICAjIHNlbGVjdCBvbmx5IHN1YnNldCBvZiByZWFjdGlvbnMKICBmaWx0ZXIocmVhY3Rpb25faWQgJWluJSBjYmJfc2VsZWN0ZWQpICU+JQogIG11dGF0ZShyZWFjdGlvbl9pZCA9IGZhY3RvcihyZWFjdGlvbl9pZCwgY2JiX3NlbGVjdGVkKSkgJT4lCiAgcmVuYW1lKGBtYXNzIFtnL2dEQ1ddYCA9IG1hc3NfZ19nRENXLCBgcHJlZGljdGVkIG1hc3MgW2cvZ0RDV11gID0gcHJlZGljdGVkX21hc3NfZ19nRENXKSAlPiUKICBtdXRhdGUodXRpbGl6YXRpb24gPSBgcHJlZGljdGVkIG1hc3MgW2cvZ0RDV11gL2BtYXNzIFtnL2dEQ1ddYCkKCgojIHBsb3QgbWFzcyBhbmQgdXRpbGl6YXRpb24KcGxvdF9jYWx2aW4gPC0gI2RvdWJsZVlTY2FsZSh1c2Uuc3R5bGUgPSBGQUxTRSwgdW5kZXIgPSBUUlVFLAogIAogIHh5cGxvdChgbWFzcyBbZy9nRENXXWAgKyBgcHJlZGljdGVkIG1hc3MgW2cvZ0RDV11gIH4gZmFjdG9yKGdyb3d0aF9yYXRlKSB8IAogICAgcmVhY3Rpb25faWQgKiBzdWJzdHJhdGUsIGRmX2NhbHZpbiwKICAgIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksIGF1dG8ua2V5ID0gbGlzdChjb2x1bW5zID0gMiwgY2V4ID0gMC43KSwKICAgIHhsYWIgPSBleHByZXNzaW9uKCLCtSBbaCJeLTEqIl0iKSwgeWxpbSA9IGMoLTAuMDAzLCAwLjAzMyksICNjKC0wLjAwMiwgMC4wMzIpLAogICAgeWxhYiA9IGV4cHJlc3Npb24oIm0iW3Byb3RlaW5dKiIgW2cgZ0RDVyJeLTEqIl0iKSwKICAgIHNjYWxlcyA9IGxpc3QoYWx0ZXJuYXRpbmcgPSBGQUxTRSwgeCA9IGxpc3QoYXQgPSBjKDIsNCkpLAogICAgICB5ID0gbGlzdChhdCA9IGMoMCwgMC4wMSwgMC4wMiwgMC4wMykpKSwgYXMudGFibGUgPSBUUlVFLAogICAgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksIGx3ZCA9IDIsIAogICAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCAuLi4pIHsKICAgICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgICBwYW5lbC5lcnJiYXJzKHgsIHksIGV3aWR0aCA9IDAsIC4uLikKICAgICAgcGFuZWwuc3VwZXJwb3NlKHgsIHksIC4uLikKICAgIH0sIHBhbmVsLmdyb3VwcyA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgICBwYW5lbC5sbWxpbmUoeCwgeSwgLi4uKQogICAgfQogICkKCnByaW50KHVzZU91dGVyU3RyaXBzKHBsb3RfY2FsdmluKSkKYGBgCgpJdCBpcyBjbGVhciBmcm9tIHRoZSBwcmV2aW91cyBhbmFseXNpcyB0aGF0IHRoZSBjZWxscyBtYWludGFpbiBwcm90ZWlucyBldmVuIGlmIHRoZXkgYXJlIG5vdCBvciBvbmx5IG1hcmdpbmFsbHkgdXRpbGl6ZWQuIFRoZSBhY3R1YWxseSB1dGlsaXplZCBwcm90ZW9tZSBpcyBtaW5pbWFsIHVuZGVyIHN0cm9uZyBzdWJzdHJhdGUgbGltaXRhdGlvbi4gVGhlIG5leHQgc2VjdGlvbiB3aWxsIHRyeSB0byBxdWFudGlmeSB0aGUgKip1bmRlci11dGlsaXphdGlvbiBvZiBlbnp5bWVzKiogYnkgY29tcGFyaW5nIHRoZSBtaW5pbWFsIHByb3RlaW4gcmVxdWlyZW1lbnQgKHNpbXVsYXRpb24pIHZlcnN1cyBleHBlcmltZW50YWxseSBkZXRlcm1pbmVkIHByb3RlaW4gYWJ1bmRhbmNlLiBJdCBpcyBzdWZmaWNpZW50IHRvIGRldGVybWluZSB0aGUgdW5kZXJ1dGlsaXplZCBwcm90ZWluIGZyYWN0aW9uIChvZiBhbGwgdXRpbGl6ZWQgcHJvdGVpbnMpLgoKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aCA9IDMsIGZpZy5oZWlnaHQgPSA0fQpwbG90X2NhbHZpbl91dGlsIDwtIGRmX3Byb3RfY29tcCAlPiUKICAKICAjIGZpbHRlciBzZXQgb2YgZW56eW1lcwogIGZpbHRlcihyZWFjdGlvbl9pZCAlaW4lIGMoIlBHSyIsICJHQVBEIiwgIkZCQSIsICJGQlAiLCAiVEtUMSIsICJQUlVLIiwgIlJCUEMiKSkgJT4lCiAgCiAgIyByZW1vdmUgMSBub24tcXVhbnRpZmllZCByZXBsaWNhdGUKICBmaWx0ZXIoIShzdWJzdHJhdGUgPT0gImZydWN0b3NlIiAmIGdyb3d0aF9yYXRlID09IDAuMSAmIHJlcGxpY2F0ZSA9PSAiUjIiKSkgJT4lCiAgCiAgIyBjYWxjdWxhdGUgcHJvdGVpbiB1dGlsaXphdGlvbgogIGdyb3VwX2J5KHN1YnN0cmF0ZSwgZ3Jvd3RoX3JhdGUsIHJlcGxpY2F0ZSkgJT4lCiAgc3VtbWFyaXplKAogICAgbWFzcyA9IHN1bShtYXNzX2dfZ0RDVywgbmEucm0gPSBUUlVFKSwKICAgIHByZWRpY3RlZF9tYXNzID0gc3VtKHByZWRpY3RlZF9tYXNzX2dfZ0RDVywgbmEucm0gPSBUUlVFKSwKICAgIHBlcmNlbnRfdXRpbGl6YXRpb24gPSBwcmVkaWN0ZWRfbWFzcy8obWFzcykKICApICU+JQogIAogICMgYWRkIHBzZXVkbyBlbnp5bWUgZ3JvdXAKICBtdXRhdGUoZW56eW1lID0gIkNCQiBjeWNsZSIpICU+JQogIAogICMgb3B0aW9uYWxseSBzaG93IHNvbWUgc3VtbWFyeSBzdGF0aXN0aWNzCiAgI3N1bW1hcml6ZShhY3Jvc3MobWF0Y2hlcygibWFzc3x1dGlsIiksIG1lYW4pKSAlPiUKICAjbXV0YXRlKHVudXRpbF9tYXNzID0gbWFzcy1wcmVkaWN0ZWRfbWFzcykgJT4lCiAgI3N1bW1hcml6ZShtYXgodW51dGlsX21hc3MpLzAuNjgqMTAwKSAjZm9yIHBlcmNlbnQ6IC8wLjY4KjEwMAogIAogICMgcGxvdAogIHh5cGxvdChwZXJjZW50X3V0aWxpemF0aW9uKjEwMCB+IGZhY3Rvcihncm93dGhfcmF0ZSkgfCBlbnp5bWUgKiBzdWJzdHJhdGUsIC4sCiAgICBwYXIuc2V0dGluZ3MgPSBjdXN0b20uY29sb3JibGluZCgpLCB5bGltID0gYygtMTAsIDExMCksCiAgICBzY2FsZXMgPSBsaXN0KGFsdGVybmF0aW5nID0gRkFMU0UsIHggPSBsaXN0KGF0ID0gYygyLDQpKSksCiAgICB4bGFiID0gZXhwcmVzc2lvbigiwrUgW2giXi0xKiJdIiksIHlsYWIgPSAiIiwKICAgIGFzLnRhYmxlID0gVFJVRSwgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksCiAgICBsd2QgPSAyLCBrZXkgPSBzaW1wbGVLZXkoIiUgdXRpbGl6YXRpb24iLCBjZXggPSAwLjcpLAogICAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCAuLi4pIHsKICAgICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgICB4X21lYW4gPSB1bmlxdWUoeCk7IHlfbWVhbiA9IHRhcHBseSh5LCB4LCBtZWFuKQogICAgICBwYW5lbC54eWFyZWEoYygwLCB4X21lYW4sIDYpLCBjKDAsIHlfbWVhbiwgdGFpbCh5X21lYW4sIDEpKSwgCiAgICAgICAgbHR5ID0gMCwgY29sID0gZ3JleSgwLjYsIGFscGhhID0gMC41KSwgLi4uKQogICAgICBwYW5lbC5lcnJiYXJzKHgsIHksIGV3aWR0aCA9IDAsIGNvbCA9IGdyZXkoMC41KSwgLi4uKQogICAgfQogICkgJT4lIHVzZU91dGVyU3RyaXBzCgpwcmludChwbG90X2NhbHZpbl91dGlsKQpgYGAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CnN2ZygiLi4vZmlndXJlcy9maWd1cmVfY2FsdmluX3V0aWwuc3ZnIiwgd2lkdGggPSA3LjUsIGhlaWdodCA9IDQuOCkKcHJpbnQodXNlT3V0ZXJTdHJpcHMocGxvdF9jYWx2aW4pLCBwb3NpdGlvbiA9IGMoMCwgMCwgMC44MywgMS4wMjcpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9jYWx2aW5fdXRpbCwgcG9zaXRpb24gPSBjKDAuNzcsIDAsIDEuMDMsIDEuMDI3KSkKZ3JpZDo6Z3JpZC50ZXh0KGMoIkEiLCAiQiIpLCB4ID0gYygwLjAzLCAwLjgxKSwgeSA9IGMoMC45NywgMC45NykpCmRldi5vZmYoKQpgYGAKCi0tLS0tLS0tLS0KCkEgZmluYWwgY29udHJvbCBpcyB0byBjaGVjayB3aGljaCBjb3BpZXMgb2YgdGhlIHNhbWUgQ2JiIGN5Y2xlIGVuenltZXMgYXJlIG1vcmUgYWJ1bmRhbnQsIG9yIHVuZGVyZ28gc3Ryb25nZXIgY2hhbmdlcy4gRm9yIGV4YW1wbGUsIFBSVUsgYW5kIFJ1YmlzY28gKFJCUEMpIGFyZSBvbmx5IHByZXNlbnQgb24gdGhlIHR3byBjb3BpZXMgb2YgdGhlIENhbHZpbiBjeWNsZSBvcGVyb24sIG9uIHBIRzEgYW5kIENocm9tb3NvbWUgMi4gQnV0IG90aGVyIGdseWNvbHlzaXMgcmVsYXRlZCBlbnp5bWVzIGhhdmUgb25lIGNhbm9uaWNhbCBjb3B5IG9uIGNocm9tb3NvbWUgMSwgd2hpY2ggc2VlbXMgdG8gZW5jb2RlIG1vc3RseSAiaG91c2VrZWVwaW5nIiBmdW5jdGlvbnMuIFdlIGNhbiBsb29rIGF0IHByb3RlaW4gYWJ1bmRhbmNlIG9mIHRoZSB0aHJlZSBzaW5nbGUgZ2VuZSBsb2NpIGZvciBlYWNoIGVuenltZSAoY2hyb21vc29tZSAxLCAyLCBhbmQgcEhHMSBtZWdhcGxhc21pZCkuIEl0J3MgaW1wb3J0YW50IHRvIGtlZXAgaW4gbWluZCB0aGF0IG1vc3QgcGVwdGlkZXMgZm9yIGNiYiBnZW5lcyBjYW4gbm90IGJlIGRpc3Rpbmd1aXNoZWQgYmV0d2VlbiBjaHJvbW9zb21lIDIgYW5kIHBIRzEsIHRoZXJlZm9yZSB0aGUgc2ltaWxhciBleHByZXNzaW9uIHBhdHRlcm4uCgpgYGB7ciwgZmlnLndpZHRoID0gNy41LCBmaWcuaGVpZ2h0ID0gNS41LCBtZXNzYWdlID0gRkFMU0V9CnBsb3RfY2FsdmluX2xvY2kgPC0gZGZfbW9kZWxfcmVhY3Rpb25zICU+JSBmaWx0ZXIocmVhY3Rpb25faWQgJWluJSBjKCJQR0siLCAiR0FQRCIsICJGQkEiLCAiRkJQIiwgIlRLVDEiKSkgJT4lCiAgc2VsZWN0KHJlYWN0aW9uX2lkLCBnZW5lcykgJT4lIHJlbmFtZShsb2N1c190YWcgPSBnZW5lcykgJT4lCiAgZmlsdGVyKGxvY3VzX3RhZyAhPSAiSDE2X0IwMjc4IikgJT4lCiAgaW5uZXJfam9pbihSYWxzdG9uaWFfZXV0cm9waGEpICU+JQogIG11dGF0ZShjaHJvbW9zb21lID0gY2FzZV93aGVuKAogICAgZ3JlcGwoIkgxNl9BIiwgbG9jdXNfdGFnKSB+ICJjaHJvbW9zb21lIDEiLAogICAgZ3JlcGwoIkgxNl9CIiwgbG9jdXNfdGFnKSB+ICJjaHJvbW9zb21lIDIiLAogICAgZ3JlcGwoIlBIRyIsIGxvY3VzX3RhZykgfiAicEhHMSIsCiAgKSkgJT4lCiAgCiAgeHlwbG90KG1hc3NfZ19nRENXIH4gZmFjdG9yKGdyb3d0aF9yYXRlKSB8IHJlYWN0aW9uX2lkICogY2hyb21vc29tZSwgLiwKICAgIGdyb3VwcyA9IHN1YnN0cmF0ZSwgbGF5b3V0ID0gYyg1LCAzKSwKICAgIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksIGx3ZCA9IDIsCiAgICBzY2FsZXMgPSBsaXN0KGFsdGVybmF0aW5nID0gRkFMU0UpLAogICAgeGxhYiA9IGV4cHJlc3Npb24oIsK1IFtoIl4tMSoiXSIpLAogICAgeWxhYiA9IGV4cHJlc3Npb24oIm0iW3Byb3RlaW5dKiIgW2cgZ0RDVyJeLTEqIl0iKSwKICAgIGFzLnRhYmxlID0gVFJVRSwgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksCiAgICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICAgIHBhbmVsLmVycmJhcnMoeCwgeSwgZXdpZHRoID0gMCwgdHlwZSA9IGMoInAiLCAibCIpLCAuLi4pCiAgICAgIHBhbmVsLmtleSguLi4sIGNleCA9IDAuNykKICAgIH0KICApCgpwcmludChwbG90X2NhbHZpbl9sb2NpKQpgYGAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CnN2ZygiLi4vZmlndXJlcy9maWd1cmVfY2FsdmluX2xvY2kuc3ZnIiwgd2lkdGggPSA3LjUsIGhlaWdodCA9IDUuNSkKcHJpbnQocGxvdF9jYWx2aW5fbG9jaSkKZGV2Lm9mZigpCmBgYAoKIyBFeHByZXNzaW9uIGFuZCB1dGlsaXphdGlvbiBvZiBQSEIgc3ludGhlc2lzIGVuenltZXMKClRoZSBwYXRod2F5IGZvciBQSEIgc3ludGhlc2lzIGNvbnNpc3RzIG1haW5seSBvZiB0aHJlZSBlbnp5bWVzLCBlYWNoIG9uZSB3aXRoIG11bHRpcGxlIGdlbmVzIGFubm90YXRlZC4KVGhlIGZpcnN0IGlzIEFjZXR5bC1Db0EgQy1hY2V0eWx0cmFuc2ZlcmFzZSAocGhhQSksIG1ha2luZyBhY2V0b2FjZXR5bC1Db0EsIHRoZSBzZWNvbmQgb25lIGlzIEFjZXRvYWNldHlsLUNvQSByZWR1Y3Rhc2UgKHBoYUIpLCBtYWtpbmcgMy1oeWRyb3h5LWJ1dHlyeWwtQ29BLCBhbmQgdGhlIHRoaXJkIGlzIHRoZSBQSEItc3ludGhhc2UgY29uZGVuc2luZyAzSEIgbW9ub21lcnMgdG8gUEhCIHBvbHltZXIgKHBoYUMpLiBUaGUgbWFzcyBmb3IgZWFjaCBlbnp5bWUgaXMgdGhlIHN1bSBvZiB0aGUgaW5kaXZpZHVhbCBwcm90ZWluIG1hc3NlcyBhbGxvY2F0ZWQgdG8gdGhpcyByZWFjdGlvbi4KCmBgYHtyLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gNC41fQpwbG90X3BoYl9tYXNzIDwtIGRmX3Byb3RfY29tcCAlPiUKICBmaWx0ZXIocmVhY3Rpb25faWQgJWluJSBjKCJQSEFTIiwgIkFDQUNUMXIiLCAiQUFDT0FSIikpICU+JQogIG11dGF0ZShyZWFjdGlvbl9pZCA9IHJlY29kZShyZWFjdGlvbl9pZCwKICAgIFBIQVMgPSAiUEhBUyAocGhhQykiLCBBQ0FDVDFyID0gIkFDQUNUMXIgKHBoYUEpIiwgQUFDT0FSID0gIkFBQ09BUiAocGhhQikiKSkgJT4lCiAgCiAgeHlwbG90KG1hc3NfZ19nRENXIH4gZmFjdG9yKGdyb3d0aF9yYXRlKSB8IAogICAgICBmYWN0b3IocmVhY3Rpb25faWQsIHVuaXF1ZShyZWFjdGlvbl9pZClbYygyLDEsMyldKSwgLiwKICAgIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksCiAgICBncm91cHMgPSBzdWJzdHJhdGUsIHhsYWIgPSAiIiwKICAgIHlsYWIgPSBleHByZXNzaW9uKCJtIltwcm90ZWluXSoiIFtnIGdEQ1ciXi0xKiJdIiksCiAgICBsYXlvdXQgPSBjKDMsIDEpLCBld2lkdGggPSAwLCBsd2QgPSAyLCBhcy50YWJsZSA9IFRSVUUsCiAgICBiZXR3ZWVuID0gbGlzdCh4ID0gMC41LCB5ID0gMC41KSwgYXV0by5rZXkgPSBsaXN0KGNvbHVtbnMgPSAyKSwKICAgIHNjYWxlcyA9IGxpc3QoYWx0ZXJuYXRpbmcgPSBGQUxTRSwgeCA9IGxpc3QoYXQgPSBjKDIsIDQpKSksCiAgICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICAgIHBhbmVsLmVycmJhcnMoeCwgeSwgLi4uKQogICAgfQogICkKCnBsb3RfcGhiX3V0aWwgPC0gZGZfcHJvdF9jb21wICU+JQogIGZpbHRlcihyZWFjdGlvbl9pZCAlaW4lIGMoIlBIQVMiLCAiQUNBQ1QxciIsICJBQUNPQVIiKSkgJT4lCiAgbXV0YXRlKHBlcmNlbnRfdXRpbGl6YXRpb24gPSBwcmVkaWN0ZWRfbWFzc19nX2dEQ1cvbWFzc19nX2dEQ1cqMTAwKSAlPiUKICBtdXRhdGUocmVhY3Rpb25faWQgPSByZWNvZGUocmVhY3Rpb25faWQsIAogICAgUEhBUyA9ICJQSEFTIChwaGFDKSIsIEFDQUNUMXIgPSAiQUNBQ1QxciAocGhhQSkiLCBBQUNPQVIgPSAiQUFDT0FSIChwaGFCKSIpKSAlPiUKICAKICB4eXBsb3QocGVyY2VudF91dGlsaXphdGlvbiB+IGZhY3Rvcihncm93dGhfcmF0ZSkgfCAKICAgICAgZmFjdG9yKHJlYWN0aW9uX2lkLCB1bmlxdWUocmVhY3Rpb25faWQpW2MoMiwxLDMpXSksIC4sCiAgICBwYXIuc2V0dGluZ3MgPSBjdXN0b20uY29sb3JibGluZCgpLAogICAgZ3JvdXBzID0gc3Vic3RyYXRlLAogICAgeGxhYiA9IGV4cHJlc3Npb24oIsK1IFtoIl4tMSoiXSIpLAogICAgeWxhYiA9ICIlIHV0aWxpemF0aW9uIiwgeWxpbSA9IGMoLTIwLCAzMjApLAogICAgbGF5b3V0ID0gYygzLCAxKSwKICAgIGV3aWR0aCA9IDAsIGx3ZCA9IDIsIGFzLnRhYmxlID0gVFJVRSwKICAgIGJldHdlZW4gPSBsaXN0KHggPSAwLjUsIHkgPSAwLjUpLAogICAgc2NhbGVzID0gbGlzdChhbHRlcm5hdGluZyA9IEZBTFNFLCB4ID0gbGlzdChhdCA9IGMoMiwgNCkpKSwKICAgIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgICAgcGFuZWwuZXJyYmFycyh4LCB5LCAuLi4pCiAgICB9CiAgKQoKcHJpbnQocGxvdF9waGJfbWFzcywgcG9zaXRpb24gPSBjKDAsMC40MiwxLDEpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9waGJfdXRpbCwgcG9zaXRpb24gPSBjKDAuMDQsMCwxLDAuNTUpKQpgYGAKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kc3ZnKCIuLi9maWd1cmVzL2ZpZ3VyZV9QSEJfcGF0aHdheS5zdmciLCB3aWR0aCA9IDUsIGhlaWdodCA9IDQuNSkKcHJpbnQocGxvdF9waGJfbWFzcywgcG9zaXRpb24gPSBjKDAsMC40MiwxLDEpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9waGJfdXRpbCwgcG9zaXRpb24gPSBjKDAuMDQsMCwxLDAuNTUpKQpkZXYub2ZmKCkKYGBgCgoKIyBZaWVsZC1ncm93dGggcmF0ZSB0cmFkZW9mZiB3aGVuIEN1cHJpYXZpZHVzIHJlLWFzc2ltaWxhdGVzIENPMj8KCkl0IHdhcyBleHBlcmltZW50YWxseSBvYnNlcnZlZCBiZWZvcmUgdGhhdCAqUi4gZXV0cm9waGEqIGV4cHJlc3NlcyBSdWJpc2NvIGFuZCBvdGhlciAqY2JiKi1vcGVyb24gbG9jYXRlZCBnZW5lcyBldmVuIG9uIGdyb3d0aCBvbiBmcnVjdG9zZSBvciBvdGhlciBoZXRlcm90cm9waGljIHN1YnN0cmF0ZXMuIFtCb3dpZW4gZXQgYWwuLCAxOTkwXShodHRwOi8vZG9pLndpbGV5LmNvbS8xMC4xMDE2LzAzNzgtMTA5Nyg5MCk5MDQ5My1BKSwgc2hvdyB0aGF0IFJ1YmlzY28gYWN0aXZpdHkgY2FuIGJlIGZvdW5kIG9uIGdyb3d0aCBvbiBmcnVjdG9zZSwgYnV0IG5vdCBvbiBweXJ1dmF0ZS4gW0RhbmdlbCAmIFRhYml0YSwgMjAxNV0oaHR0cHM6Ly9qYi5hc20ub3JnL2NvbnRlbnQvMTk3LzIyLzM0ODgpLCByZXZpZXcgdGhlIHJlZ3VsYXRpb24gYnkgQ2JiUiB0eXBlIHJlZ3VsYXRvcnMgYW1vbmcgb3RoZXJzIGFsc28gaW4gKlIuIGV1dHJvcGhhKiBhbmQgbWVudGlvbiB0aGF0IGNpdHJhdGUgYWxzbyBsZWFkcyB0byBhY3RpdmF0aW9uIG9mICpjYmIqIGV4cHJlc3Npb24uIFRoZXkgaHlwb3RoZXNpemUgdGhhdCByaWJ1bG9zZSBiaXNwaG9zcGhhdGUgKFJ1QlApIGlzIGFuIGFjdGl2YXRpbmcgZWZmZWN0b3Igd2hpbGUgb3RoZXIgY2VudHJhbCBtZXRhYm9saXNtIGludGVybWVkaWF0ZXMgc3VjaCBhcyBwaG9zcGhvZW5vbHB5cnV2YXRlIChQRVApIGFyZSByZXByZXNzaW5nIGVmZmVjdG9yIG1vbGVjdWxlcy4gSXQgd2FzIHNwZWN1bGF0ZWQgaW4gQm93aWVuIGV0IGFsLiB0aGF0IHJlZ3VsYXRpb24gYnkgY2JiUiBtaWdodCBhY3R1YWxseSBiZSBhIHJlcHJlc3Npb24tZGVyZXByZXNzaW9uIHJhdGhlciB0aGFuIGFjdGl2YXRpb24sIHdoaWNoIG1lYW5zIHRoYXQgdGhlIGRlZmF1bHQgc3RhdGUgd291bGQgYmUgYSBib3VuZCBjYmJSIHJlcHJlc3NpbmcgdGhlICpjYmIqIG9wZXJvbi4gSG93ZXZlciBpdCBiZWNhbWUgY2xlYXIgdGhhdCB0aGlzIGlzIG5vdCB0aGUgY2FzZS4gW1NoaW1penUgZXQgYWwuLCAyMDE1XShodHRwOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvc3JlcDExNjE3KSwga25vY2tlZCB0aGUgY2JiUiBnZW5lIG91dCBhbmQgdGhlIHJlc3VsdCB3YXMgcmVkdWNlZCBleHByZXNzaW9uIG9mICpjYmIqIGdlbmVzIGJ5IDEwMCBmb2xkLiBUaGlzIHByb3ZlcyB0aGF0IGNiYlIgaXMgYSByZXF1aXJlZCBhY3RpdmF0b3IgYW5kIG5vdCBhIHJlcHJlc3NvciBvZiBjYmIsIG90aGVyd2lzZSBjYmJSIGRlbGV0aW9uIHdvdWxkIGxlYWQgdG8gY29uc3RpdHV0aXZlIGFjdGl2YXRpb24gb2YgY2JiIGV4cHJlc3Npb24uCgpUaGUgc2FtZSBncm91cCBzcGVjdWxhdGVkIGluIHRoZWlyIHN0dWR5IHRoYXQgdGhlIGFkZGl0aW9uYWwgUnViaXNjbyBleHByZXNzaW9uIGNvdWxkIGhhdmUgYSBiZW5lZml0IGZvciBjYXJib24geWllbGQsIHNwZWNpZmljYWxseSBmb3IgcHJvZHVjdCB5aWVsZCBvZiBQSEIuIFRoZXkgc2hvdyB0aGF0IFBIQiBmcm9tIHRoZSBXVCBjb250YWlucyBzbGlnaHRseSBtb3JlIDEzQyBsYWJlbGVkIG1hc3MgKGFuZCB0b3RhbCBtYXNzKSB0aGFuIHRoZSBjYmJSIGFuZCBjYmJTL0wga25vY2tvdXRzLiBUaGlzIG1lYW5zIHRoYXQgdGhlIGNlbGwgd291bGQgaGF2ZSBhIChwcm9kdWN0IG9yIGJpb21hc3MpIHlpZWxkIGFkdmFudGFnZSBieSBSdWJpc2NvIGV4cHJlc3Npb24gb24gZnJ1Y3Rvc2UuCgpUaGUgZm9sbG93aW5nIHNlY3Rpb24gdGVzdHMgdGhlIGh5cG90aGVzaXMgb2YgYSB5aWVsZC1ncm93dGggcmF0ZSB0cmFkZW9mZiB3aXRoIHRoZSByZXNvdXJjZSBhbGxvY2F0aW9uIG1vZGVsLiBGaXJzdCBzaW11bGF0aW9uIGRhdGEgZnJvbSB0aGUgUkJBIG1vZGVsIGlzIGltcG9ydGVkCgpgYGB7cn0KI2FkanVzdCBwYXRoCm1peG90cm9waF9kaXIgPC0gZ3N1Yigic3Vic3RyYXRlX2xpbWl0YXRpb24iLCAibWl4b3Ryb3BoeSIsIHNpbXVsYXRpb25fZGlyKQoKIyByZWFkIHNpbXVsYXRpb24gcmVzdWx0cwpkZl9taXhmbHV4IDwtIHJlYWRfcmJhX3Jlc3VsdChsaXN0LmZpbGVzKG1peG90cm9waF9kaXIsIHBhdHRlcm4gPSAiZmx1eGVzXy4qLnRzdiQiLCBmdWxsLm5hbWVzID0gVFJVRSkpCmRmX21peG1hY3IgPC0gcmVhZF9yYmFfcmVzdWx0KGxpc3QuZmlsZXMobWl4b3Ryb3BoX2RpciwgcGF0dGVybiA9ICJtYWNyb3Byb2Nlc3Nlc18uKi50c3YiLCBmdWxsLm5hbWVzID0gVFJVRSkpCgojIHJlbmFtZSBjb2x1bW4KZGZfbWl4Zmx1eCA8LSBkZl9taXhmbHV4ICU+JSByZW5hbWUoQ08yX3JlZml4YXRpb24gPSBzaW1fcnVuKSAlPiUgZmlsdGVyKENPMl9yZWZpeGF0aW9uIDw9IDUpCmRmX21peG1hY3IgPC0gZGZfbWl4bWFjciAlPiUgcmVuYW1lKENPMl9yZWZpeGF0aW9uID0gc2ltX3J1bikgJT4lIGZpbHRlcihDTzJfcmVmaXhhdGlvbiA8PSA1KQpgYGAKClRoZSBuZXh0IHRhc2sgaXMgdG8gY29tcGFyZSB5aWVsZCwgZ3Jvd3RoIHJhdGUsIENPPHN1Yj4yPC9zdWI+IGVtaXNzaW9uIGFuZCBvdGhlciBmbHV4ZXMgZm9yIGEgcmFuZ2Ugb2Ygc2ltdWxhdGlvbnMgd2hlcmUgUnViaXNjbyB3YXMgZm9yY2VkIHRvIHJlLWZpeCBlbWl0dGVkIENPPHN1Yj4yPC9zdWI+IGZyb20gZ3Jvd3RoIG9uIGZydWN0b3NlLiBTaW11bGF0aW9ucyB3ZXJlIHBlcmZvcm1lZCBmb3IgaW5jcmVhc2luZyBmbHV4IHRocm91Z2ggUnViaXNjbyBmcm9tIDAgdG8gNSBtbW9sIGdEQ1c8c3VwPi0xPC9zdXA+IGg8c3VwPi0xPC9zdXA+LgoKYGBge3IsIGZpZy53aWR0aCA9IDUsIGZpZy5oZWlnaHQgPSAyLjd9CnBsb3RfbWl4b19tdSA8LSBsYXBwbHkoYygibXUiLCAieWllbGQiKSwgZnVuY3Rpb24oa2V5cykgewogIAogIGlmIChrZXlzID09ICJtdSIpIHsKICAgIHlsYWJlbCA8LSBleHByZXNzaW9uKCLCtSBbaCJeLTEqIl0iKQogICAgeWxpbSA8LSBjKDAuMDAxLCAwLjMpCiAgfSBlbHNlIHsKICAgIHlsYWJlbCA8LSBleHByZXNzaW9uKCJZIFtnRENXIGdTIl4tMSoiXSIpCiAgICB5bGltIDwtIGMoMC4wMDEsIDAuMzk5KQogIH0KICAKICAKICB4eXBsb3QodmFsdWUgfiBmYWN0b3IoQ08yX3JlZml4YXRpb24pLAogICAgZmlsdGVyKGRmX21peG1hY3IsIGtleSA9PSBrZXlzLCBjYXJib25fY29uYyA9PSAxLjI1KSwKICAgIHR5cGUgPSAiYiIsIGx3ZCA9IDIsIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksIAogICAgeGxhYiA9IGV4cHJlc3Npb24oIkNPIlsyXSoiIHVwdGFrZSBbbW1vbCBoIl4tMSoiIGdEQ1ciXi0xKiJdIiksCiAgICB5bGFiID0geWxhYmVsLCB5bGltID0geWxpbSwKICAgIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwgeiwgLi4uKSB7CiAgICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgICAgcGFuZWwueHlwbG90KHgsIHksIC4uLikKICAgIH0KICApCn0pCgpwcmludChwbG90X21peG9fbXVbWzFdXSwgcG9zaXRpb24gPSBjKDAsMCwwLjUzLDEpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9taXhvX211W1syXV0sIHBvc2l0aW9uID0gYygwLjQ3LDAsMSwxKSkKYGBgClRoZXJlIGlzIG5vIGluY3JlYXNlIGluIGdyb3d0aCByYXRlIG9yIHlpZWxkIHdpdGggYWRkaXRpb25hbCBDTzxzdWI+Mjwvc3ViPiBmaXhhdGlvbiBhY2NvcmRpbmcgdG8gdGhlIFJCQSBtb2RlbC4gVGhlIHlpZWxkIGlzIGluIGZhY3QgY2FsY3VsYXRlZCBiYXNlZCBvbiB0aGUgZ3Jvd3RoIHJhdGUgwrUgYW5kIHRoZSBzdWJzdHJhdGUgdXB0YWtlIHJhdGUgcVMuIFlpZWxkIGFuZCBncm93dGggcmF0ZSBkZXBlbmQgb24gZWFjaCBvdGhlciBpbiB0aGUgcmVsYXRpb24gWSBbZ0RDVy9nU10gPSDCtSBbaC0xXSAvIHFTIFtnUyBnRENXLTEgaC0xXS4gCgpTaW5jZSBvbmx5IHRoZSBpbml0aWFsIHN1YnN0cmF0ZSBjb25jZW50cmF0aW9uIGlzIGNvbnN0cmFpbmVkLCB0aGUgbW9kZWwgY291bGQgcHJlZGljdCBhIGxvd2VyIHN1YnN0cmF0ZSB1cHRha2UgcmF0ZSBvciBsb3dlciBDTzxzdWI+Mjwvc3ViPiBlbWlzc2lvbiBwZXIgYmlvbWFzcyBpbiBvcmRlciB0byByZWFjaCBhIHlpZWxkIGluY3JlYXNlLiBIb3dldmVyIHRoaXMgd2FzIG5vdCB0aGUgY2FzZSB1bmRlciBhbnkgc2ltdWxhdGlvbiwgc2VlIGJlbG93IGZvciBkZXRhaWxzIG9mIHNwZWNpZmljIG1ldGFib2xpdGVzLiAKClRoZSBtb2RlbCBzZWVtcyB0byBwcmVkaWN0IGEgaGlnaC15aWVsZCBwaGVub3R5cGUgYWxyZWFkeS4gSWYgQ088c3ViPjI8L3N1Yj4gcmUtZml4YXRpb24gd291bGQgaGF2ZSBoYWQgYW4gYWR2YW50YWdlIGZvciBncm93dGgsIGl0IG1pZ2h0IGhhdmUgYmVlbiBkZXRlY3RlZCBlYXJsaWVyIGRlcGVuZGluZyBvbiB0aGUgcHJvdGVpbiBjb3N0cyBmb3IgdGhlIHJlc3BlY3RpdmUgcGF0aHdheS4gQmVmb3JlIGEgZmluYWwgdmVyZGljdCwgd2UgY2FuIGZvbGxvdyB0aGUgZmF0ZSBvZiB0aGUgZml4ZWQgQ088c3ViPjI8L3N1Yj4gdGhyb3VnaCB0aGUgbWV0YWJvbGlzbS4KCgpgYGB7ciwgZmlnLndpZHRoID0gMy44LCBmaWcuaGVpZ2h0ID0gMy44fQpwbG90X21peG9fZW56IDwtIHh5cGxvdChhYnModmFsdWUpIH4gYXMuZmFjdG9yKENPMl9yZWZpeGF0aW9uKSB8IAogICAgZmFjdG9yKGtleSwgdW5pcXVlKGtleSkpLCBsYXlvdXQgPSBjKDIsIDIpLAogIGZpbHRlcihkZl9taXhmbHV4LCBrZXkgJWluJSBjKCJDTzJ0IiwgIkVERCIsICJDUyIsICJPMnQiKSwgY2FyYm9uX2NvbmMgPT0gMS4yNSksCiAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwKICB0eXBlID0gImIiLCBsd2QgPSAyLCBiZXR3ZWVuID0gbGlzdCh4ID0gMC41LCB5ID0gMC41KSwgYXMudGFibGUgPSBUUlVFLAogIHhsYWIgPSBleHByZXNzaW9uKCJDTyJbMl0qIiB1cHRha2UgW21tb2wgaCJeLTEqIiBnRENXIl4tMSoiXSIpLAogIHlsYWIgPSBleHByZXNzaW9uKCJmbHV4IFttbW9sIGgiXi0xKiJnRENXIl4tMSoiXSIpLAogIHNjYWxlcyA9IGxpc3QoYWx0ZXJuYXRpbmcgPSBGQUxTRSksCiAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCB6LCAuLi4pIHsKICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgIHBhbmVsLnh5cGxvdCh4LCB5LCAuLi4pCiAgfQopCgpwcmludChwbG90X21peG9fZW56KQpgYGAKCmBgYHtyLCBmaWcud2lkdGggPSA3LjUsIGZpZy5oZWlnaHQgPSAzLjcsIGluY2x1ZGUgPSBGQUxTRX0Kc3ZnKCIuLi9maWd1cmVzL2ZpZ3VyZV9yZWZpeGF0aW9uLnN2ZyIsIHdpZHRoID0gNy41LCBoZWlnaHQgPSAzLjcpCnByaW50KHBsb3RfbWl4b19tdVtbMV1dLCBwb3NpdGlvbiA9IGMoMC4yOSwwLjQyLDAuNiwxLjA1KSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfbWl4b19tdVtbMl1dLCBwb3NpdGlvbiA9IGMoMC4zMCwwLDAuNiwwLjYzKSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfbWl4b19lbnosIHBvc2l0aW9uID0gYygwLjU2LDAsMS4wMywxLjA1KSkKZGV2Lm9mZigpCmBgYAoKIyMgQ29uY2x1c2lvbgoKSXQgYmVjb21lcyBjbGVhciB0aGF0IHlpZWxkIGFuZCBncm93dGggcmF0ZSBkZWNyZWFzZSwgYW5kIG5vdCBpbmNyZWFzZSwgd2l0aCBhZGRpdGlvbmFsIENPMiBmaXhhdGlvbiwgYmVjYXVzZToKCi0gZW5lcmd5IHJlcXVpcmVtZW50IGZvciBDTzIgZml4YXRpb24gbGVhZHMgdG8gbG93ZXIgZmx1eCB0aHJvdWdoIEVEIHBhdGh3YXksIGJ1dCBoaWdoZXIgZmx1eCB0aHJvdWdoIFRDQQotIHRoaXMgaXMgaW4gb3JkZXIgdG8gZ2VuZXJhdGUgdGhlIHJlcXVpcmVkIE5BREgvQVRQIGZvciBDTzIgZml4YXRpb24KLSByZXNwaXJhdGlvbiBhbmQgTzIgY29uc3VtcHRpb24gYWxzbyBpbmNyZWFzZXMgd2l0aCBmb3JjZWQgbWl4b3Ryb3BoaWMgZ3Jvd3RoCi0gdGhlcmUgaXMgbm8gbmV0IHJlZHVjdGlvbiBvZiBDTzIgZW1pc3Npb24uIEluIGZhY3QgY2VsbHMgZW1pdCBtb3JlIENPMiBldmVuIHdoZW4gdGhleSBmaXggc29tZSBvZiBpdCBkdWUgdG8gaW5jcmVhc2VkIGVuZXJneSByZXF1aXJlbWVudAotIHN0b3Jpbmcgc29tZSBvZiB0aGUgZml4ZWQgQ08yIGFzIFBIQiB3b3VsZCBub3QgaW5jcmVhc2UgYmlvbWFzcyB5aWVsZCBhcyBhZGRpdGlvbmFsIGVuZXJneSByZXF1aXJlbWVudCBzdGlsbCBwZXJzaXN0cwotIGNlbGxzIHNob3VsZCBub3QgYmUgYWJsZSB0byBtYWtlIG1vcmUgUEhCIHVzaW5nIHRoaXMgc3RyYXRlZ3ksIHJlZ2FyZGxlc3Mgb2YgYmlvbWFzcyB5aWVsZC4gSWYgYWNldHlsLUNvQSBpcyBkcmFpbmVkIGZvciBQSEIsIGV2ZW4gbGVzcyBlbmVyZ3kgaXMgbWFkZSBhdmFpbGFibGUgdGhyb3VnaCBUQ0EgYW5kIE9YUEhPUy4KCiMgTWluaWF0dXJlIFJCQSBmbHV4IG1hcHMKCkluc3RhbGwgbXkgc21hbGwgUiBwYWNrYWdlIFtmbHVjdHVhdG9yXShodHRwczovL2dpdGh1Yi5jb20vbS1qYWhuL2ZsdWN0dWF0b3IpIHRvIG92ZXJsYXkgZmx1eGVzIG9uIFNWRyBtZXRhYm9saWMgbWFwcy4KVGhlIHRlbXBsYXRlIG1hcCBpcyBhIHNpbXBsaWZpZWQgbWV0YWJvbGljIG1hcCBvZiBDLiBuZWNhdG9yJ3MgY2VudHJhbCBtZXRhYm9saXNtLgoKRmlyc3Qgd2UgcGxvdCBhIG1hcCBvZiB0aGUgbWl4b3Ryb3BoaWMgc2ltdWxhdGlvbi4gVGhlbiB3ZSB1c2UgdGhlIHNhbWUgd29yay1mbG93IGFuZCB0ZW1wbGF0ZSB0byBwbG90IG1ldGFib2xpYyBmbHV4IG1hcHMgZm9yIGdyb3d0aCBvbiB0aGUgdGhyZWUgbWFpbiBzdWJzdHJhdGVzLCBmcnVjdG9zZSwgc3VjY2luYXRlIGFuZCBmb3JtYXRlIChtYXhpbXVtIHRlc3RlZCBncm93dGggcmF0ZSwgMC4yNSBoXi0xKS4KCmBgYHtyfQpsaWJyYXJ5KGZsdWN0dWF0b3IpCgojIGltcG9ydCBtYXAKU1ZHX21hcCA8LSByZWFkX3N2ZygiLi4vZGF0YS9zaW11bGF0aW9uL2NlbnRyYWxfbWV0YWJvbGlzbS5zdmciKQojZ2V0X2F0dHJpYnV0ZXMoU1ZHX21hcCwgbm9kZSA9ICJrZXlfMDEiKSAlPiUgcHVsbChzdHlsZSkKCiMgcHJlcGFyZSBkYXRhOiBtaXhvdHJvcGhpYyBncm93dGgKZGZfbWl4Zmx1eCA8LSBkZl9taXhmbHV4ICU+JSBmaWx0ZXIoQ08yX3JlZml4YXRpb24gPT0gMykgJT4lCiAgIyBhZGQgZmx1eGVzIGZvciBsZWdlbmQgZW50cmllcwogIGFkZF9yb3coa2V5ID0gYygia2V5XzAxIiwgImtleV8xMiIsICJrZXlfMjQiLCAia2V5XzQ4IiksIHZhbHVlID0gYygxLDIsNCw4KSkgJT4lCiAgIyBhZGQgc3Ryb2tlIHdpZHRoIHRvIHNpbXVsYXRpb24KICBtdXRhdGUoc3Ryb2tlX3dpZHRoID0gMC4yICsgMC4yKnNxcnQoYWJzKHZhbHVlKSkpICU+JQogIGlubmVyX2pvaW4oc2VsZWN0KFNWR19tYXBAc3VtbWFyeSwgbGFiZWwpLCBieSA9IGMoImtleSIgPSAibGFiZWwiKSkgJT4lCiAgZGlzdGluY3QKCiMgcHJlcGFyZSBkYXRhOiBjYXJib24tbGltaXRlZCBncm93dGgKZGZfY2FyYmZsdXggPC0gZGZfZmx1eCAlPiUKICBmaWx0ZXIoc2ltdWxhdGlvbiAlaW4lIGMoCiAgICAiZm9yXzAuNDg3X25oNF8xOC43XzEwIiwgImZydV8xLjIyM19uaDRfMTguN18wIiwgInN1Y2NfMC42NjJfbmg0XzE4LjdfNSIpKSAlPiUKICAjIGFkZCBmbHV4ZXMgZm9yIGxlZ2VuZCBlbnRyaWVzCiAgYWRkX3JvdyhrZXkgPSByZXAoYygia2V5XzAxIiwgImtleV8xMiIsICJrZXlfMjQiLCAia2V5XzQ4IiksIGVhY2ggPSAzKSwKICAgIHZhbHVlID0gcmVwKGMoMSwyLDQsOCksIGVhY2ggPSAzKSwgY2FyYm9uX3NvdXJjZSA9IHJlcChjKCJmb3IiLCAiZnJ1IiwgInN1Y2MiKSwgNCkpICU+JQogICMgYWRkIHN0cm9rZSB3aWR0aCB0byBzaW11bGF0aW9uCiAgbXV0YXRlKHN0cm9rZV93aWR0aCA9IDAuMiArIDAuMipzcXJ0KGFicyh2YWx1ZSkpKSAlPiUKICBpbm5lcl9qb2luKHNlbGVjdChTVkdfbWFwQHN1bW1hcnksIGxhYmVsKSwgYnkgPSBjKCJrZXkiID0gImxhYmVsIikpICU+JQogIGRpc3RpbmN0CmBgYAoKUGxvdCBhbGwgZm91ciBjb25kaXRpb25zIGluIGEgbG9vcC4KCmBgYHtyfQpsYXBwbHkoCiAgbGlzdCgKICAgIG11dGF0ZShkZl9taXhmbHV4LCBjYXJib25fc291cmNlID0gIm1peCIpLAogICAgZmlsdGVyKGRmX2NhcmJmbHV4LCBjYXJib25fc291cmNlID09ICJmcnUiKSwKICAgIGZpbHRlcihkZl9jYXJiZmx1eCwgY2FyYm9uX3NvdXJjZSA9PSAiZm9yIiksCiAgICBmaWx0ZXIoZGZfY2FyYmZsdXgsIGNhcmJvbl9zb3VyY2UgPT0gInN1Y2MiKQogICksIEZVTiA9IGZ1bmN0aW9uKGRmKSB7CiAgICAKICAgICMgcmVsb2FkIGJhc2UgbWFwIGZvciBldmVyeSBpbnN0YW5jZSwgYXMgdGhlIFNWRyBpcyBhbHRlcmVkCiAgICAjIGV2ZW4gd2l0aG91dCBhc3NpZ25tZW50IChweXRob24gc3R5bGUsIHVudHlwaWNhbCBpbiBSKQogICAgU1ZHX21hcCA8LSByZWFkX3N2ZygiLi4vZGF0YS9zaW11bGF0aW9uL2NlbnRyYWxfbWV0YWJvbGlzbS5zdmciKQogICAgCiAgICAjIG1vZGlmeSBtYXAKICAgIFNWRzIgPC0gc2V0X2F0dHJpYnV0ZXMoU1ZHX21hcCwKICAgICAgbm9kZSA9IGRmJGtleSwgYXR0ciA9ICJzdHlsZSIsCiAgICAgIHBhdHRlcm4gPSAic3Ryb2tlLXdpZHRoOlswLTldK1xcLlswLTldKyIsCiAgICAgIHJlcGxhY2VtZW50ID0gcGFzdGUwKCJzdHJva2Utd2lkdGg6IiwgZGYkc3Ryb2tlX3dpZHRoKSkKICAgIAogICAgIyB0dXJuIG5vbi16ZXJvIGZsdXhlcyBpbnRvIGRhcmtlciBjb2xvcgogICAgU1ZHMiA8LSBzZXRfYXR0cmlidXRlcyhTVkcyLAogICAgICBub2RlID0gZmlsdGVyKGRmLCB2YWx1ZSAhPSAwKSAlPiUgcHVsbChrZXkpLCBhdHRyID0gInN0eWxlIiwKICAgICAgcGF0dGVybiA9ICJzdHJva2U6I2IzYjNiMyIsCiAgICAgIHJlcGxhY2VtZW50ID0gInN0cm9rZTojODA4MDgwIikKICAgIAogICAgIyByZXNjYWxlIGFycm93IGhlYWRzCiAgICBTVkcyIDwtIHNldF9hdHRyaWJ1dGVzKFNWRzIsIAogICAgICBub2RlID0gZ3JlcCgibWFya2VyIiwgU1ZHMkBzdW1tYXJ5JGlkLCB2YWx1ZSA9IFRSVUUpLAogICAgICBub2RlX2F0dHIgPSAiaWQiLAogICAgICBhdHRyID0gInRyYW5zZm9ybSIsCiAgICAgIHBhdHRlcm4gPSAic2NhbGVcXCgwLjJcXCkiLAogICAgICByZXBsYWNlbWVudCA9ICJzY2FsZSgwLjE1KSIpCiAgICAKICAgIFNWRzIgPC0gc2V0X2F0dHJpYnV0ZXMoU1ZHMiwgCiAgICAgIG5vZGUgPSBncmVwKCJtYXJrZXIiLCBTVkcyQHN1bW1hcnkkaWQsIHZhbHVlID0gVFJVRSksCiAgICAgIG5vZGVfYXR0ciA9ICJpZCIsCiAgICAgIGF0dHIgPSAidHJhbnNmb3JtIiwKICAgICAgcGF0dGVybiA9ICJzY2FsZVxcKC0wLjJcXCkiLAogICAgICByZXBsYWNlbWVudCA9ICJzY2FsZSgtMC4xNSkiKQogICAgCiAgICAjIGV4cG9ydCBtYXAgYXMgU1ZHIGFnYWluCiAgICAjd3JpdGVfc3ZnKFNWRzIsIGZpbGUgPSBwYXN0ZTAoIi4uL2RhdGEvc2ltdWxhdGlvbi9jZW50cmFsX21ldGFib2xpc21fIiwgZGYkY2FyYm9uX3NvdXJjZVsxXSwgIi5zdmciKSkKICB9CikgJT4lIGludmlzaWJsZSgpCgpgYGAKCk1ldGFib2xpYyBmbHV4IHdpdGggQ08yIHJlZml4YXRpb24gfCAgTWV0YWJvbGljIGZsdXggb24gZnJ1Y3Rvc2UKOi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS06fDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tOgohW10oLi4vZGF0YS9zaW11bGF0aW9uL2NlbnRyYWxfbWV0YWJvbGlzbV9taXguc3ZnKSAgfCAgIVtdKC4uL2RhdGEvc2ltdWxhdGlvbi9jZW50cmFsX21ldGFib2xpc21fZnJ1LnN2ZykKCk1ldGFib2xpYyBmbHV4IG9uIHN1Y2NpbmF0ZSB8ICBNZXRhYm9saWMgZmx1eCBvbiBmb3JtaWMgYWNpZAo6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS06CiFbXSguLi9kYXRhL3NpbXVsYXRpb24vY2VudHJhbF9tZXRhYm9saXNtX3N1Y2Muc3ZnKSAgfCAgIVtdKC4uL2RhdGEvc2ltdWxhdGlvbi9jZW50cmFsX21ldGFib2xpc21fZm9yLnN2ZykKCg==