1 Description

This R notebook is a bioinformatics pipeline to collect constraints for a genome scale, resource allocation model in the chemolithoautotroph Ralstonia eutropha (a.k.a. Cupriavidus necator).

A resource allocation model can be coarse-grained (few symbolic reactions) or have genome scale detail (all known biochemical reactions and their associated genes). However, both types of models need to be constrained by a set of parameters to make realistic predictions. Depending on the model frame work, constraints can be equality constraints (example: turnover number of an enzyme E kcatE = 100 s-1), or inequality constraints (0 s-1 <= kcatE <= 100 s-1).

This notebook has the purpose to collect constant and growth-rate dependent constraints as they are used in RBA models. In RBApy, apparent enzyme efficiencies (kapp), protein abundance, molecular machine abundance (protein/macromolecule complexes), and fluxes can be constrained. RBApy has the following possibilities for custom constraints.

Different types of data were used to constrain the resource allocation model. The primary data is protein abundance determined by mass spectrometry for R. eutropha using different growth rates and carbon sources. This data is used to estimate and constrain kapp, enzyme abundance, and non-enzyme protein abundance.

2 Libraries

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

3 kapp estimation using proteomics/fluxomics

3.1 Background

One approach to estimate kapp values is to use enzyme abundance data and estimated or measured fluxes. The kapp is a parameter that links enzyme fluxes to enzyme abundance, so that flux v = kapp * [E]. The kapp includes also the saturation s of the enzyme, so that kapp ~ kcat * s. The saturation is between 0 and 1 so that kapp is lower or equal to kcat.

However, the idea is that the kapp parameter is kept constant for different conditions, while the flux (and the enzyme abundance) is allowed to change. We can then find different scenarios for enzyme saturation states:

  • the flux through an enzyme and therefore its abundance increases in the model, but not in measured enzyme abundance: –> kapp is higher than estimated, enzyme not saturated
  • the flux through an enzyme and therefore its abundance increases in the model, and in measured enzyme abundance: –> kapp is correctly estimated, enzyme is saturated

3.2 Fluxomics and proteomics input

To estimate kapp from available data, we only need to reformulate the simplified rate equation to: kapp = v / [E]. For this purpose, we need two types of information:

  1. enzyme abundance in mmol gDCW-1 for known substrate uptakes rates (chemostat cultivations, maximum growth rate 0.25 h-1). The maximum tested growth rate was chosen to obtain realistic enzyme saturation. Protein abundance data was obtained by MS measurements. The relative protein mass fraction was determined by dividing MS intensity per protein by sum of all intensities. The mass fraction (g/g total protein) was then converted to protein concentration in mmol/gDCW by multiplying it with estimated protein concentration of 0.65 g protein/gDCW (done previously), and then dividing by molar mass of each protein (g/mol).
  2. flux per enzyme, in mmol h-1 gDCW-1 for the corresponding substrate uptake rates under point 1. This information is obtained from flux sampling (FBA) simulations that were constrained to realistic flux distributions using data from Alagesan et al., 2017. Input and output fluxes (for exchange reactions) were determined from chemostat cultivations.

Determine enzyme abundance for standard conditions. Import Ralstonia mass fractions for the four tested conditions, fructose, succinate, formate, and ammonium (N-) limitation (with C-source fructose). Growth rate in all conditions was fixed to µ = 0.25 h-1, the maximum growth rate used in chemostat experiments.

# import proteomics data
load("../data/input/Ralstonia_eutropha.Rdata")

# simplify condition strings
Ralstonia_eutropha <- Ralstonia_eutropha %>% ungroup %>%
  mutate(condition = sapply(condition, function(x){
    unlist(stri_extract_all_words(x))[1]}
  ))

# make new data frame to merge fluxomics and proteomics data
df_flux_protein <- Ralstonia_eutropha %>%
  
  # select only required columns and filter for highest mu
  mutate(condition = recode(condition, `FA` = "formate", `NLIM` = "ammonium", `FRC` = "fructose", `SUC` = "succinate")) %>%
  select(condition, locus_tag, growthrate, mass_g_per_gDCW, MolWeight) %>%
  filter(growthrate == 0.25) %>% 
  
  # calculate protein concentration in mmol/gDCW.
  # MW in kDa must be converted to g/mol, and concentration to mmol
  mutate(
    conc_mmol_gDCW = mass_g_per_gDCW * 1000 / (MolWeight * 1000),
    conc_mmol_gDCW = replace_na(conc_mmol_gDCW, 0)
  )

head(df_flux_protein)

# export to csv
for (cond in unique(df_flux_protein$condition)) {
  filter(df_flux_protein, condition == cond) %>% 
    select(-condition) %>%
    write_csv(paste0("../data/simulation/kapp_fitting/protein_concentration_", cond, ".csv"))
}

Determine reaction fluxes for same condition. Several approaches were tested to obtain minimal and maximal fluxes per enzyme. The best approach turned out to be flux sampling with the additional constraint of reaching at least 95% of the maximum growth rate found by FBA. Flux sampling with 100 iterations was performed using COBRApy. We can see that there is a threshold of standard deviation ~ 1 above which variation gets very high for some reactions. These are artificial cycles. The exception to this is N-limitation that can have higher flux variability due to the surmount carbon supply (fructose is not limiting).

# import flux sampling results for four conditions, max tested growth rate
df_sampling <- lapply(list.files("../data/simulation/kapp_fitting/", pattern = "^FSA", full.names = TRUE), 
  read_csv, col_types = cols()) %>% bind_rows(.id = "condition") %>%
  mutate(condition = recode(condition, `1` = "formate", `2` = "succinate", 
    `3` = "ammonium", `4` = "fructose")) %>%
  select(-2) %>% 
  gather(key = "reaction_id", value = "flux", -condition) %>%
  
  group_by(condition, reaction_id) %>% summarize(
    flux_mmol_gDCW_h = median(flux), 
    sd_flux = sd(flux), 
    min_flux = min(flux), 
    max_flux = max(flux),
    CV = abs(sd_flux/flux_mmol_gDCW_h)) %>%
  filter(!grepl("EX_", reaction_id)) %>%
  arrange(desc(sd_flux)) %>%
  
  # join with single optimal solution from loopless FBA.
  # Will help us to identify free running cycles in FSA
  left_join(
    lapply(list.files("../data/simulation/kapp_fitting/", pattern = "^FBA", full.names = TRUE),
    read_csv, col_types = cols()) %>% bind_rows(.id = "condition") %>%
    mutate(condition = recode(condition, `1` = "formate", `2` = "succinate", 
      `3` = "ammonium", `4` = "fructose")) %>%
    rename(reaction_id = X1, flux_mmol_gDCW_h_FBA = loopless)
  )
`summarise()` has grouped output by 'condition'. You can override using the `.groups` argument.
Joining, by = c("condition", "reaction_id")
  
# plot results; 
plot_sampling <- lapply(list(A = c(-50, 550), B = c(-3, 28)), function(ylim) {
  xyplot(sd_flux ~ seq_along(sd_flux) | condition,
    filter(df_sampling, sd_flux != 0), 
    between = list(x = 0.5, y = 0.5),
    groups = condition, xlab = NULL, ylim = ylim,
    scales = list(alternating = FALSE),
    par.settings = custom.colorblind(), layout = c(4, 1),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.abline(h = 1, lty = 2, col = grey(0.5))
      panel.xyplot(x, y, ...)
      panel.key(..., points = FALSE, corner = c(0.95,0.95))
    }
  )
})

print(plot_sampling[[1]], split = c(1,1,1,2), more = TRUE)
print(plot_sampling[[2]], split = c(1,2,1,2))

# process flux distribution, e.g. by removing extremely high fluxes
df_sampling2 <- df_sampling %>%
  
  # Filter out summary and outdated reactions
  filter(!reaction_id %in% c("Biomass", "Maintenance", "Protein", "Carbohydrate", 
    "Phospholipid", "   Cofactors_and_vitamins", "CBBCYC", "PYK1", "PYK2", "PYK3",
    "DHFR2", "DHFR3", "DHFR2p", "DHFR3p")) %>%
  
  # replace extreme fluxes with a min and max estimated from loopless FBA
  mutate(
    min_flux = if_else(sd_flux > 1, -abs(flux_mmol_gDCW_h_FBA), min_flux),
    max_flux = if_else(sd_flux > 1,  abs(flux_mmol_gDCW_h_FBA), max_flux)
  ) %>%
  
  # add an error margin to the sampled min and max fluxes, to help the solver
  # find a feasible solution
  mutate(
    min_flux = min_flux-(abs(min_flux)*1), 
    max_flux = max_flux+(abs(max_flux)*1)
  ) %>%
  
  # re-formatting of table
  arrange(desc(abs(max_flux))) %>%
  mutate(reaction_id = paste0("R_", reaction_id))

head(df_sampling2)

# export to csv
for (cond in unique(df_sampling2$condition)) {
  filter(df_sampling2, condition == cond) %>% ungroup %>%
    select(-condition) %>%
    write_csv(paste0("../data/simulation/kapp_fitting/model_flux_sampling_", cond, ".csv"))
}

3.3 Determine kapp values

The final step is to merge both datasets by computing the enzyme abundance allocated to each reaction. The estimated kapp is then determined by dividing the flux through the enzyme abundance available for this reaction. This step was not performed in this R notebook but using the RBA built-in functions from the RBApy estim folder. Briefly, flux boundaries and protein concentration data was exported and serves as input for the script kapp.py (link). This script performs a series of FBA and RBA simulations constrained by the input fluxes and protein concentrations. It then gives an estimation of the kapp for a particular condition.

4 Fraction of protein per compartment

The RBA model takes as another input parameter (or constraint) the fraction of protein per compartment. This constraint is important as it allows the cell to have only a limited amount of protein in cytoplasm or membrane compartments, for example. This constraint can be constant or it can be growth rate dependent e.g. by a linear relationship.

The first step is to prepare a table with protein abundance and localization. Protein abundance can be in any unit according to the RBApy manual, but it’s best to use mol fraction instead of mass fraction, as all other RBApy functions also use mmol. The mol fraction is already available in the processed data set. The built-in estim functions don’t seem to be well supported in python3 and raise errors. We therefore do the estimation manually by fitting linear models to the growth rate-protein abundance relationship. The idea is similar to the RBA estim function but less complicated. We focus on the standard condition as a training data set (fructose as carbon source, no NH4+ limitation).

df_protein <- Ralstonia_eutropha %>% 
  
  # select only required columns and spread to long format
  select(condition, locus_tag, growthrate, Psortb_localization, mol_fraction) %>%
  set_names(c("condition", "protein", "growthrate", "location", "mol_fraction")) %>%
  mutate(condition = recode(condition, `FA` = "formate", `NLIM` = "ammonium", `FRC` = "fructose", `SUC` = "succinate")) %>%
  
  # match localization names to model, simplify
  mutate(location = recode(location, Unknown = "Cytoplasm", Cytoplasmic = "Cytoplasm")) %>%
  mutate(location = replace(location, location != "Cytoplasm", "Cell_membrane"))

head(df_protein)

Now we can summarize the data by taking the sum of mol fraction over condition and localization. A simple approach to finding linear functions, where all proteins of all locations sum to one for a specific growth rate, would be to fit linear models for all compartments except one (e.g. cytoplasmic proteins, the biggest compartment). This one will then get a linear model fitted from the residual protein mass. We would expect the error for Cytoplasm to be negligibly small as it is the biggest compartment. However it turned out that the linear models fitted to both compartments perfectly sum to one already (see below). An estimation from residual protein fraction is therefore not necessary.

# First retrieve the proteins that are not part of the model, which is needed
# to  calculate non-enzymatic fraction later on
model_genes <- c(
  read_csv("../data/input/model_reactions.csv", col_types = cols()) %>% separate_rows(genes, sep = ", ") %>%
    filter(!duplicated(genes)) %>% pull(genes),
  read_tsv("../data/simulation/macro_machines/ribosome.tsv", col_types = cols())[["Gene names"]],
  read_tsv("../data/simulation/macro_machines/chaperones.tsv", col_types = cols())[["Gene names"]],
  read_tsv("../data/simulation/macro_machines/transcription.tsv", col_types = cols())[["Gene names"]],
  read_tsv("../data/simulation/macro_machines/replication.tsv", col_types = cols())[["Gene names"]]
)
Warning: Missing column names filled in: 'X1' [1]
# extract locus_tags only
model_genes <- model_genes %>% stri_extract_first_regex(
  pattern = "H16_[A-Z][0-9]{4}|PHG[0-9]{3}")

# add new NE_protein column
df_prot_per_comp <- df_protein %>%
  mutate(NE_protein = !protein %in% model_genes) %>%
  group_by(condition, location, growthrate)

The following plot shows a slight increase in cytoplasmic proteins and decrease in cell membrane proteins with growth rate.

plot_prot_comp <- xyplot(prot_per_compartment ~ growthrate, 
  df_prot_per_comp %>%
    summarize(prot_per_compartment = sum(mol_fraction, na.rm = TRUE)),
  groups = location, ylim = c(-0.1, 1.1),
  par.settings = custom.colorblind(), cex = 0.7,
  scales = list(alternating = FALSE),
  between = list(x = 0.5, y = 0.5),
  xlab = expression("µ [h"^-1*"]"),
  ylab = "protein mass fraction",
  panel = function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.superpose(x, y, ...)
    panel.key(corner = c(0.1, 0.55), ...)
  }, panel.groups = function(x, y, ...) {
    panel.xyplot(x, y, ...)
    panel.lmlineq(x, y, r.squared = TRUE,
      pos = 3, offset = 1, ...)
  }
)
`summarise()` has grouped output by 'condition', 'location'. You can override using the `.groups` argument.
print(plot_prot_comp)

5 Fraction of non-enzymatic protein per compartment

The previous calculation determined how the total protein pool is distributed over compartments. In this section, we will go one level deeper and determine the fraction of non-enzymatic protein per compartment, analogously to the previous calculation. That means every compartment’s protein pool is further subdivided into two sectors, enzymatic and non-enzymatic proteins, and these sectors can have –again– a linear dependency on growth rate. According to the RBApy manual, the proteins that are considered non-enzymatic are all proteins not acting as enzymes or molecular machines in the model (such as ribosomes, chaperones, DNA polymerase and so on). Non-enzymatic (NE) proteins are therefore all proteins not captured by the RBA model at all. The fraction of NE proteins per compartment is a percentage of the proteins for that compartment only (each compartment sums to 1).

# determine NE protein per compartment
df_ne_prot_per_comp <- df_prot_per_comp %>% group_by(NE_protein, .add = TRUE) %>%
  summarize(prot_per_compartment = sum(mol_fraction, na.rm = TRUE)) %>%
  summarize(ne_prot_per_compartment = 
    prot_per_compartment[2]/sum(prot_per_compartment)
  )
`summarise()` has grouped output by 'condition', 'location', 'growthrate'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'condition', 'location'. You can override using the `.groups` argument.
  
# the fraction of NE protein per compartment decreases with growth rate
plot_prot_NE <- xyplot(ne_prot_per_compartment ~ growthrate,
  df_ne_prot_per_comp,
  groups = location, ylim = c(-0.1, 1.1),
  par.settings = custom.colorblind(),
  scales = list(alternating = FALSE),
  between = list(x = 0.5, y = 0.5), cex = 0.7,
  xlab = expression("µ [h"^-1*"]"),
  ylab = "NE protein mass fraction",
  panel = function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.superpose(x, y, ...)
    panel.key(corner = c(0.1, 0.1), ...)
  }, panel.groups = function(x, y, ...) {
    panel.xyplot(x, y, ...)
    panel.lmlineq(x, y, r.squared = TRUE,
      pos = 3, offset = 1, ...)
  }
)

print(plot_prot_NE)


Summary: Regardless of the cultivation condition or substrate limitation, it is possible to see growth rate dependent trends. Overall, the fraction of cytoplasmic proteins increases slightly with growth rate while membrane-associated proteins decrease. This makes sense as often expression of transporters is down-regulated if bacteria don’t need to scavenge substrates. We also see that the absolute majority of proteins is located in the cytoplasm (> 85% under all conditions).

The non-enzymatic fraction of proteins (proteins not covered by the model) decreases with growth rate from around 60% o 50% for cytoplasmic proteins, but is relatively constant for membrane associated ones. Most membrane-associated proteins (90% of mol fraction) are not covered by the model. The following figure summarizes all growth rate dependent trends.

plot_prot_pie <- df_prot_per_comp %>% group_by(NE_protein, .add = TRUE) %>%
  summarize(prot_per_compartment = sum(mol_fraction, na.rm = TRUE)) %>%
  ungroup %>% mutate(
    location = recode(location, Cytoplasm = "CP", Cell_membrane = "CM"),
    location = paste(location, ifelse(NE_protein, "NE", "E"), sep ="-")) %>%
  filter(growthrate == 0.25) %>%
  #filter(condition == "FRC") %>%
  
  xyplot( ~ prot_per_compartment | condition, .,
    groups = factor(location), scales = list(draw = FALSE),
    xlab = "mol fraction of proteome, per compartment",
    par.settings = custom.colorblind(), layout = c(4,1),
    between = list(x = 0.5, y = 0.5),
    cex = 0.6, border = grey(0.3),
    panel = function(x, y, ...) {
      panel.piechart(x, diameter_inner = 0.1, 
        diameter_sector = 0.15, ...)
    }
  )
`summarise()` has grouped output by 'condition', 'location', 'growthrate'. You can override using the `.groups` argument.
print(plot_prot_pie)


6 Combining estimated kapp values to global consensus kapp

The purpose of this section is to merge different kapp estimations from different conditions to obtain a consensus kapp estimation. The reasoning here is that kapp is estimated from dividing the (sampled or measured) metabolic flux through an enzyme by its abundance, providing an estimate of enzyme efficiency. This efficiency depends also on the saturation of the enzyme. Estimation of kapp was therefore performed for highest available growth rates/fluxes for four different conditions, in order to obtain enzyme efficiency at the highest saturation among the tested conditions.

Example: Formate dehydrogenase is not used under growth on fructose (no or low efficiency) while it is used under growth on formate (high efficiency). The maximum kapp should therefore be taken from formate condition.

6.1 Import kapp estimation data obtained from RBA estim

# data directory
kapp_calib_dir <- "../data/simulation/kapp_fitting/"

# setting global parameter
max_saturation = 0.75

# load kapp estimation from different conditions
conditions <- c("ammonium", "formate", "fructose", "succinate")
kapp_files <- paste0(kapp_calib_dir, "kapp_estimate_", conditions, ".csv")

df_kapp <- lapply(kapp_files, function(df) {
  read_tsv(df, col_names = c("reaction_id", "kapp_forward", "kapp_reverse"))}) %>% 
  bind_rows(.id = "condition") %>%
  
  # change condition names
  mutate(condition = recode(condition, !!!(conditions %>% setNames(1:4)))) %>%
  
  # add a saturation factor S, increasing kapp by 1/S
  mutate_at(vars(contains("kapp_")), function(x) x/max_saturation) %>%
  
  # apply median normalization to account for different in silico growth 
  # rates in FBA simulation (different total flux)
  group_by(condition) %>% 
  mutate(kapp_forward = kapp_forward*(median(.[["kapp_forward"]])/median(kapp_forward))) %>%
  mutate(kapp_reverse = kapp_forward) %>%
  
  # sort decreasingly by average kapp
  group_by(reaction_id) %>% 
  mutate(kapp_mean = mean(kapp_forward)) %>%
  arrange(desc(kapp_mean))

── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────
cols(
  reaction_id = col_character(),
  kapp_forward = col_double(),
  kapp_reverse = col_double()
)


── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────
cols(
  reaction_id = col_character(),
  kapp_forward = col_double(),
  kapp_reverse = col_double()
)


── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────
cols(
  reaction_id = col_character(),
  kapp_forward = col_double(),
  kapp_reverse = col_double()
)


── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────
cols(
  reaction_id = col_character(),
  kapp_forward = col_double(),
  kapp_reverse = col_double()
)
head(df_kapp)
# plot kapp value distribution per condition
plot_kapp_dist <- lapply(c("condition", "all_conditions"), function(cond) {
  xyplot(log10(kapp_forward) ~ as.numeric(factor(reaction_id, unique(reaction_id))) | get(cond), 
    df_kapp %>% mutate(all_conditions = "all conditions"), 
    groups = condition, cex = 0.7, pch = 19,
    xlab = "number of reactions", ylab = expression("log"[10]*"  k"[app]*" [h"^-1*"]"),
    as.table = TRUE, between = list(x = 0.5, y = 0.5),
    par.settings = custom.colorblind(),
    scales =list(alternating = FALSE),
    panel = function(x, y, ...){
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.xyplot(x, y, ...)
      med = median(y)
      panel.abline(h = med, lty = 2, lwd = 2, col = grey(0.5))
      if (cond == "all_conditions") {
        panel.text(grid::unit(175, "npc"), med+3,
          labels = paste0("median kapp = ", round(10^med), " [h-1]"))
        panel.key(..., corner = c(0.98, 0.95))
      }
    }
  )
})

print(plot_kapp_dist[[2]])

6.2 Manual adjustment of kapp for selected reactions

We import a table with correction factors for kapp values that have been estimated incorrectly. This set of kapp values was determined by comparing the correlation between predicted and measured protein abundance. Proteins that are more than 10 fold away from measured abundance are candidates for manual correction. Other reactions that need correction are missing kapp values for important transporters. These are also included in the following section.

# set of adjusted kapp values for outlier enzymes not covered in kapp estimation
kapp_median <- median(df_kapp$kapp_forward)
kapp_extra <- read_csv(paste0(kapp_calib_dir, "kapp_extra.csv"))

── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────
cols(
  reaction_id = col_character(),
  ratio_mol_fraction = col_double()
)
# print mean and sd of log-transformed kapp distribution
df_kapp$kapp_forward %>% log10 %>% mean
[1] 3.813431
df_kapp$kapp_forward %>% log10 %>% sd
[1] 1.115881
# kapp estimation for reactions where information is missing
kapp_missing <- kapp_extra %>%
  filter(!(reaction_id %in% df_kapp$reaction_id)) %>%
  mutate(
    kapp_forward = ratio_mol_fraction*kapp_median,
    kapp_reverse = kapp_forward) %>%
  select(-ratio_mol_fraction)

# kapp estimation for transporters
kapp_transport <- data.frame(stringsAsFactors = FALSE,
  reaction_id = c("R_H2Ot_enzyme", "R_CO2t_enzyme", "R_O2t_emzyme", "R_SO4t_enzyme",
    "R_PIt2r_enzyme", "R_Ktr_enzyme", "R_NAt3_15_enzyme", "R_FE2abc_enzyme",
    "R_MG2t_enzyme", "R_COBALTt5_enzyme", "R_FORt_enzyme", "R_NH4t_enzyme", 
    "R_FRUabc_enzyme", "R_SUCCt2r_enzyme", "R_FRUpts2_enzyme", 
    "R_SUCCabc_enzyme", "R_SUCFUMt_enzyme"),
  kapp_forward = c(rep(1000000, 11), rep(100000, 3), rep(0, 3))
  ) %>% mutate(kapp_reverse = kapp_forward)

The final consensus kapp table is prepared by adding missing kapps for transport reactions and correcting several falsely estimated kapps. The table is exported as *.csv file. RBApy requires the table in a specific format:

  • tab-separated values, no header
  • units in 1/h instead of 1/s (x 3600)
  • reaction ID in column 1, format (R_)ID_enzyme or ID_transporter
  • max kkat in column 2
  • min kkat in column 3 (backward efficiency, can be identical)
  • no NA values (only complete rows)
# Select maximum as aggregation metric
df_kapp_export <- df_kapp %>% group_by(reaction_id) %>%
  summarize(
    kapp_forward = max(kapp_forward),
    kapp_reverse = max(kapp_reverse)
  ) %>%
  
  # add missing kapps
  filter(!(reaction_id %in% c("R_HEXf_enzyme","R_FRUabc_enzyme"))) %>%
  bind_rows(kapp_missing) %>%
  bind_rows(kapp_transport) %>%
  
  # update kapp estimation for some reactions where estimation was inaccurate
  left_join(filter(kapp_extra, reaction_id %in% df_kapp$reaction_id)) %>%
  mutate(ratio_mol_fraction = replace_na(ratio_mol_fraction, 1)) %>%
  mutate(
    kapp_forward = kapp_forward * ratio_mol_fraction,
    kapp_reverse = kapp_reverse * ratio_mol_fraction
  ) %>%
  select(-ratio_mol_fraction)
Joining, by = "reaction_id"
# export final result
write_tsv(df_kapp_export, "../data/simulation/kapp_fitting/kapp_consensus.csv", col_names = FALSE)

Finally we can create a summary figure for the parameters constraining the RBA model. The first is kapp estimation and the two other constraints are proteome mass fraction per compartment and proteome mass fraction of enzymatic (all RBA model reactions) and non-enzymatic proteins, per compartment.

print(plot_kapp_dist[[2]], position = c(0, 0.51, 1, 1), more = TRUE)
print(plot_prot_comp, position = c(0, 0, 0.5, 0.55), more = TRUE)
print(plot_prot_NE, position = c(0.5, 0, 1, 0.55), more = TRUE)
grid::grid.text(c("A", "B", "C"), x = c(0.02, 0.02, 0.5), 
  y = c(0.97, 0.5, 0.5))

LS0tCnRpdGxlOiAiQ29uc3RyYWludHMgZm9yIGFuICpSLiBldXRyb3BoYSogcmVzb3VyY2UgYWxsb2NhdGlvbiBtb2RlbCIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIsICVZJylgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogCiAgICB0aGVtZTogY29zbW8KICAgIHRvYzogeWVzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKLS0tCgoKIyBEZXNjcmlwdGlvbgoKVGhpcyBSIG5vdGVib29rIGlzIGEgYmlvaW5mb3JtYXRpY3MgcGlwZWxpbmUgdG8gKipjb2xsZWN0IGNvbnN0cmFpbnRzIGZvciBhIGdlbm9tZSBzY2FsZSwgcmVzb3VyY2UgYWxsb2NhdGlvbiBtb2RlbCoqIGluIHRoZSBjaGVtb2xpdGhvYXV0b3Ryb3BoICpSYWxzdG9uaWEgZXV0cm9waGEqIChhLmsuYS4gKkN1cHJpYXZpZHVzIG5lY2F0b3IqKS4KCkEgcmVzb3VyY2UgYWxsb2NhdGlvbiBtb2RlbCBjYW4gYmUgY29hcnNlLWdyYWluZWQgKGZldyBzeW1ib2xpYyByZWFjdGlvbnMpIG9yIGhhdmUgZ2Vub21lIHNjYWxlIGRldGFpbCAoYWxsIGtub3duIGJpb2NoZW1pY2FsIHJlYWN0aW9ucyBhbmQgdGhlaXIgYXNzb2NpYXRlZCBnZW5lcykuIEhvd2V2ZXIsIGJvdGggdHlwZXMgb2YgbW9kZWxzIG5lZWQgdG8gYmUgY29uc3RyYWluZWQgYnkgYSBzZXQgb2YgcGFyYW1ldGVycyB0byBtYWtlIHJlYWxpc3RpYyBwcmVkaWN0aW9ucy4gRGVwZW5kaW5nIG9uIHRoZSBtb2RlbCBmcmFtZSB3b3JrLCBjb25zdHJhaW50cyBjYW4gYmUgZXF1YWxpdHkgY29uc3RyYWludHMgKGV4YW1wbGU6IHR1cm5vdmVyIG51bWJlciBvZiBhbiBlbnp5bWUgRSBrY2F0PHN1Yj5FPC9zdWI+ID0gMTAwIHM8c3VwPi0xPC9zdXA+KSwgb3IgaW5lcXVhbGl0eSBjb25zdHJhaW50cyAoMCBzPHN1cD4tMTwvc3VwPiA8PSBrY2F0PHN1Yj5FPC9zdWI+IDw9IDEwMCBzPHN1cD4tMTwvc3VwPikuIAoKVGhpcyBub3RlYm9vayBoYXMgdGhlIHB1cnBvc2UgdG8gY29sbGVjdCAqKmNvbnN0YW50IGFuZCBncm93dGgtcmF0ZSBkZXBlbmRlbnQgY29uc3RyYWludHMqKiBhcyB0aGV5IGFyZSB1c2VkIGluIFtSQkEgbW9kZWxzXShodHRwczovL3N5c2Jpb2lucmEuZ2l0aHViLmlvL1JCQXB5LykuIEluIFJCQXB5LCBhcHBhcmVudCBlbnp5bWUgZWZmaWNpZW5jaWVzIChrPHN1Yj5hcHA8L3N1Yj4pLCBwcm90ZWluIGFidW5kYW5jZSwgbW9sZWN1bGFyIG1hY2hpbmUgYWJ1bmRhbmNlIChwcm90ZWluL21hY3JvbW9sZWN1bGUgY29tcGxleGVzKSwgYW5kIGZsdXhlcyBjYW4gYmUgY29uc3RyYWluZWQuIFJCQXB5IGhhcyB0aGUgZm9sbG93aW5nIHBvc3NpYmlsaXRpZXMgZm9yIGN1c3RvbSBjb25zdHJhaW50cy4KCi0gY29uc3RhbnRzIChleGFtcGxlOiBgQSA9IDAuMWApCi0gbGluZWFyIHJlbGF0aW9uc2hpcCwgZS5nLiB3aXRoIGdyb3d0aCByYXRlIMK1IChleGFtcGxlOiBgQiA9IDIgKiDCtSArIDAuMWApCi0gTWljaGFlbGlzLU1lbnRoZW4gbGlrZSBraW5ldGljcyBmb3IgazxzdWI+YXBwPC9zdWI+IChleGFtcGxlOiBga2FwcCA9IGtjYXQgKiBbU10gLyAoW1NdICsgS20pYCkKCkRpZmZlcmVudCB0eXBlcyBvZiBkYXRhIHdlcmUgdXNlZCB0byBjb25zdHJhaW4gdGhlIHJlc291cmNlIGFsbG9jYXRpb24gbW9kZWwuIFRoZSBwcmltYXJ5IGRhdGEgaXMgKipwcm90ZWluIGFidW5kYW5jZSoqIGRldGVybWluZWQgYnkgbWFzcyBzcGVjdHJvbWV0cnkgZm9yICpSLiBldXRyb3BoYSogdXNpbmcgZGlmZmVyZW50IGdyb3d0aCByYXRlcyBhbmQgY2FyYm9uIHNvdXJjZXMuIFRoaXMgZGF0YSBpcyB1c2VkIHRvIGVzdGltYXRlIGFuZCBjb25zdHJhaW4gIGs8c3ViPmFwcDwvc3ViPiwgZW56eW1lIGFidW5kYW5jZSwgYW5kIG5vbi1lbnp5bWUgcHJvdGVpbiBhYnVuZGFuY2UuCgoKIyBMaWJyYXJpZXMKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShsYXR0aWNlKQogIGxpYnJhcnkobGF0dGljZUV4dHJhKQogIGxpYnJhcnkobGF0dGljZXRvb2xzKQogIGxpYnJhcnkodGlkeXZlcnNlKQogIGxpYnJhcnkoc3RyaW5naSkKfSkKYGBgCgoKIyBrPHN1Yj5hcHA8L3N1Yj4gZXN0aW1hdGlvbiB1c2luZyBwcm90ZW9taWNzL2ZsdXhvbWljcwoKIyMgQmFja2dyb3VuZAoKT25lIGFwcHJvYWNoIHRvIGVzdGltYXRlIGs8c3ViPmFwcDwvc3ViPiB2YWx1ZXMgaXMgdG8gdXNlIGVuenltZSBhYnVuZGFuY2UgZGF0YSBhbmQgZXN0aW1hdGVkIG9yIG1lYXN1cmVkIGZsdXhlcy4gVGhlIGs8c3ViPmFwcDwvc3ViPiBpcyBhIHBhcmFtZXRlciB0aGF0IGxpbmtzIGVuenltZSBmbHV4ZXMgdG8gZW56eW1lIGFidW5kYW5jZSwgc28gdGhhdCBmbHV4IHYgPSBrPHN1Yj5hcHA8L3N1Yj4gKiBbRV0uIFRoZSBrPHN1Yj5hcHA8L3N1Yj4gaW5jbHVkZXMgYWxzbyB0aGUgc2F0dXJhdGlvbiBzIG9mIHRoZSBlbnp5bWUsIHNvIHRoYXQgazxzdWI+YXBwPC9zdWI+IH4gazxzdWI+Y2F0PC9zdWI+ICogcy4gVGhlIHNhdHVyYXRpb24gaXMgYmV0d2VlbiAwIGFuZCAxIHNvIHRoYXQgazxzdWI+YXBwPC9zdWI+IGlzIGxvd2VyIG9yIGVxdWFsIHRvIGs8c3ViPmNhdDwvc3ViPi4KCkhvd2V2ZXIsIHRoZSBpZGVhIGlzIHRoYXQgdGhlIGs8c3ViPmFwcDwvc3ViPiBwYXJhbWV0ZXIgaXMga2VwdCBjb25zdGFudCBmb3IgZGlmZmVyZW50IGNvbmRpdGlvbnMsIHdoaWxlIHRoZSBmbHV4IChhbmQgdGhlIGVuenltZSBhYnVuZGFuY2UpIGlzIGFsbG93ZWQgdG8gY2hhbmdlLiBXZSBjYW4gdGhlbiBmaW5kIGRpZmZlcmVudCBzY2VuYXJpb3MgZm9yIGVuenltZSBzYXR1cmF0aW9uIHN0YXRlczoKCi0gdGhlIGZsdXggdGhyb3VnaCBhbiBlbnp5bWUgYW5kIHRoZXJlZm9yZSBpdHMgYWJ1bmRhbmNlIGluY3JlYXNlcyBpbiB0aGUgbW9kZWwsICoqYnV0IG5vdCBpbiBtZWFzdXJlZCBlbnp5bWUgYWJ1bmRhbmNlKio6IC0tPiBrPHN1Yj5hcHA8L3N1Yj4gaXMgaGlnaGVyIHRoYW4gZXN0aW1hdGVkLCAqKmVuenltZSBub3Qgc2F0dXJhdGVkKioKLSB0aGUgZmx1eCB0aHJvdWdoIGFuIGVuenltZSBhbmQgdGhlcmVmb3JlIGl0cyBhYnVuZGFuY2UgaW5jcmVhc2VzIGluIHRoZSBtb2RlbCwgKiphbmQgaW4gbWVhc3VyZWQgZW56eW1lIGFidW5kYW5jZSoqOiAtLT4gazxzdWI+YXBwPC9zdWI+IGlzIGNvcnJlY3RseSBlc3RpbWF0ZWQsICoqZW56eW1lIGlzIHNhdHVyYXRlZCoqCgojIyBGbHV4b21pY3MgYW5kIHByb3Rlb21pY3MgaW5wdXQKClRvIGVzdGltYXRlIGs8c3ViPmFwcDwvc3ViPiBmcm9tIGF2YWlsYWJsZSBkYXRhLCB3ZSBvbmx5IG5lZWQgdG8gcmVmb3JtdWxhdGUgdGhlIHNpbXBsaWZpZWQgcmF0ZSBlcXVhdGlvbiB0bzogIGs8c3ViPmFwcDwvc3ViPiA9ICB2IC8gW0VdLiBGb3IgdGhpcyBwdXJwb3NlLCB3ZSBuZWVkIHR3byB0eXBlcyBvZiBpbmZvcm1hdGlvbjoKCjEuICoqZW56eW1lIGFidW5kYW5jZSBpbiBtbW9sIGdEQ1c8c3VwPi0xPC9zdXA+KiogZm9yIGtub3duIHN1YnN0cmF0ZSB1cHRha2VzIHJhdGVzIChjaGVtb3N0YXQgY3VsdGl2YXRpb25zLCBtYXhpbXVtIGdyb3d0aCByYXRlIDAuMjUgaDxzdXA+LTE8L3N1cD4pLiBUaGUgbWF4aW11bSB0ZXN0ZWQgZ3Jvd3RoIHJhdGUgd2FzIGNob3NlbiB0byBvYnRhaW4gcmVhbGlzdGljIGVuenltZSBzYXR1cmF0aW9uLiBQcm90ZWluIGFidW5kYW5jZSBkYXRhIHdhcyBvYnRhaW5lZCBieSBNUyBtZWFzdXJlbWVudHMuIFRoZSByZWxhdGl2ZSBwcm90ZWluICptYXNzIGZyYWN0aW9uKiB3YXMgZGV0ZXJtaW5lZCBieSBkaXZpZGluZyBNUyBpbnRlbnNpdHkgcGVyIHByb3RlaW4gYnkgc3VtIG9mIGFsbCBpbnRlbnNpdGllcy4gVGhlICptYXNzIGZyYWN0aW9uKiAoZy9nIHRvdGFsIHByb3RlaW4pIHdhcyB0aGVuIGNvbnZlcnRlZCB0byBwcm90ZWluIGNvbmNlbnRyYXRpb24gaW4gKm1tb2wvZ0RDVyogYnkgbXVsdGlwbHlpbmcgaXQgd2l0aCBlc3RpbWF0ZWQgcHJvdGVpbiBjb25jZW50cmF0aW9uIG9mIDAuNjUgZyBwcm90ZWluL2dEQ1cgKGRvbmUgcHJldmlvdXNseSksIGFuZCB0aGVuIGRpdmlkaW5nIGJ5IG1vbGFyIG1hc3Mgb2YgZWFjaCBwcm90ZWluIChnL21vbCkuCjIuICoqZmx1eCBwZXIgZW56eW1lLCBpbiBtbW9sIGg8c3VwPi0xPC9zdXA+IGdEQ1c8c3VwPi0xPC9zdXA+KiogZm9yIHRoZSBjb3JyZXNwb25kaW5nIHN1YnN0cmF0ZSB1cHRha2UgcmF0ZXMgdW5kZXIgcG9pbnQgMS4gVGhpcyBpbmZvcm1hdGlvbiBpcyBvYnRhaW5lZCBmcm9tIGZsdXggc2FtcGxpbmcgKEZCQSkgc2ltdWxhdGlvbnMgdGhhdCB3ZXJlIGNvbnN0cmFpbmVkIHRvIHJlYWxpc3RpYyBmbHV4IGRpc3RyaWJ1dGlvbnMgdXNpbmcgZGF0YSBmcm9tIFtBbGFnZXNhbiAqZXQgYWwqLiwgMjAxN10oaHR0cDovL2R4LmRvaS5vcmcvMTAuMTAwNy9zMTEzMDYtMDE3LTEzMDIteikuIElucHV0IGFuZCBvdXRwdXQgZmx1eGVzIChmb3IgZXhjaGFuZ2UgcmVhY3Rpb25zKSB3ZXJlIGRldGVybWluZWQgZnJvbSBjaGVtb3N0YXQgY3VsdGl2YXRpb25zLgoKLS0tLS0tLS0tLQoKKipEZXRlcm1pbmUgZW56eW1lIGFidW5kYW5jZSBmb3Igc3RhbmRhcmQgY29uZGl0aW9ucy4qKiBJbXBvcnQgKlJhbHN0b25pYSogbWFzcyBmcmFjdGlvbnMgZm9yIHRoZSBmb3VyIHRlc3RlZCBjb25kaXRpb25zLCBmcnVjdG9zZSwgc3VjY2luYXRlLCBmb3JtYXRlLCBhbmQgYW1tb25pdW0gKE4tKSBsaW1pdGF0aW9uICh3aXRoIEMtc291cmNlIGZydWN0b3NlKS4gR3Jvd3RoIHJhdGUgaW4gYWxsIGNvbmRpdGlvbnMgd2FzIGZpeGVkIHRvIMK1ID0gMC4yNSBoPHN1cD4tMTwvc3VwPiwgdGhlIG1heGltdW0gZ3Jvd3RoIHJhdGUgdXNlZCBpbiBjaGVtb3N0YXQgZXhwZXJpbWVudHMuCgpgYGB7cn0KIyBpbXBvcnQgcHJvdGVvbWljcyBkYXRhCmxvYWQoIi4uL2RhdGEvaW5wdXQvUmFsc3RvbmlhX2V1dHJvcGhhLlJkYXRhIikKCiMgc2ltcGxpZnkgY29uZGl0aW9uIHN0cmluZ3MKUmFsc3RvbmlhX2V1dHJvcGhhIDwtIFJhbHN0b25pYV9ldXRyb3BoYSAlPiUgdW5ncm91cCAlPiUKICBtdXRhdGUoY29uZGl0aW9uID0gc2FwcGx5KGNvbmRpdGlvbiwgZnVuY3Rpb24oeCl7CiAgICB1bmxpc3Qoc3RyaV9leHRyYWN0X2FsbF93b3Jkcyh4KSlbMV19CiAgKSkKCiMgbWFrZSBuZXcgZGF0YSBmcmFtZSB0byBtZXJnZSBmbHV4b21pY3MgYW5kIHByb3Rlb21pY3MgZGF0YQpkZl9mbHV4X3Byb3RlaW4gPC0gUmFsc3RvbmlhX2V1dHJvcGhhICU+JQogIAogICMgc2VsZWN0IG9ubHkgcmVxdWlyZWQgY29sdW1ucyBhbmQgZmlsdGVyIGZvciBoaWdoZXN0IG11CiAgbXV0YXRlKGNvbmRpdGlvbiA9IHJlY29kZShjb25kaXRpb24sIGBGQWAgPSAiZm9ybWF0ZSIsIGBOTElNYCA9ICJhbW1vbml1bSIsIGBGUkNgID0gImZydWN0b3NlIiwgYFNVQ2AgPSAic3VjY2luYXRlIikpICU+JQogIHNlbGVjdChjb25kaXRpb24sIGxvY3VzX3RhZywgZ3Jvd3RocmF0ZSwgbWFzc19nX3Blcl9nRENXLCBNb2xXZWlnaHQpICU+JQogIGZpbHRlcihncm93dGhyYXRlID09IDAuMjUpICU+JSAKICAKICAjIGNhbGN1bGF0ZSBwcm90ZWluIGNvbmNlbnRyYXRpb24gaW4gbW1vbC9nRENXLgogICMgTVcgaW4ga0RhIG11c3QgYmUgY29udmVydGVkIHRvIGcvbW9sLCBhbmQgY29uY2VudHJhdGlvbiB0byBtbW9sCiAgbXV0YXRlKAogICAgY29uY19tbW9sX2dEQ1cgPSBtYXNzX2dfcGVyX2dEQ1cgKiAxMDAwIC8gKE1vbFdlaWdodCAqIDEwMDApLAogICAgY29uY19tbW9sX2dEQ1cgPSByZXBsYWNlX25hKGNvbmNfbW1vbF9nRENXLCAwKQogICkKCmhlYWQoZGZfZmx1eF9wcm90ZWluKQoKIyBleHBvcnQgdG8gY3N2CmZvciAoY29uZCBpbiB1bmlxdWUoZGZfZmx1eF9wcm90ZWluJGNvbmRpdGlvbikpIHsKICBmaWx0ZXIoZGZfZmx1eF9wcm90ZWluLCBjb25kaXRpb24gPT0gY29uZCkgJT4lIAogICAgc2VsZWN0KC1jb25kaXRpb24pICU+JQogICAgd3JpdGVfY3N2KHBhc3RlMCgiLi4vZGF0YS9zaW11bGF0aW9uL2thcHBfZml0dGluZy9wcm90ZWluX2NvbmNlbnRyYXRpb25fIiwgY29uZCwgIi5jc3YiKSkKfQpgYGAKCi0tLS0tLS0tLS0KCioqRGV0ZXJtaW5lIHJlYWN0aW9uIGZsdXhlcyBmb3Igc2FtZSBjb25kaXRpb24uKiogU2V2ZXJhbCBhcHByb2FjaGVzIHdlcmUgdGVzdGVkIHRvIG9idGFpbiBtaW5pbWFsIGFuZCBtYXhpbWFsIGZsdXhlcyBwZXIgZW56eW1lLiBUaGUgYmVzdCBhcHByb2FjaCB0dXJuZWQgb3V0IHRvIGJlIGZsdXggc2FtcGxpbmcgd2l0aCB0aGUgYWRkaXRpb25hbCBjb25zdHJhaW50IG9mIHJlYWNoaW5nIGF0IGxlYXN0IDk1JSBvZiB0aGUgbWF4aW11bSBncm93dGggcmF0ZSBmb3VuZCBieSBGQkEuIEZsdXggc2FtcGxpbmcgd2l0aCAxMDAgaXRlcmF0aW9ucyB3YXMgcGVyZm9ybWVkIHVzaW5nIENPQlJBcHkuIFdlIGNhbiBzZWUgdGhhdCB0aGVyZSBpcyBhIHRocmVzaG9sZCBvZiBzdGFuZGFyZCBkZXZpYXRpb24gfiAxIGFib3ZlIHdoaWNoIHZhcmlhdGlvbiBnZXRzIHZlcnkgaGlnaCBmb3Igc29tZSByZWFjdGlvbnMuIFRoZXNlIGFyZSBhcnRpZmljaWFsIGN5Y2xlcy4gVGhlIGV4Y2VwdGlvbiB0byB0aGlzIGlzIE4tbGltaXRhdGlvbiB0aGF0IGNhbiBoYXZlIGhpZ2hlciBmbHV4IHZhcmlhYmlsaXR5IGR1ZSB0byB0aGUgc3VybW91bnQgY2FyYm9uIHN1cHBseSAoZnJ1Y3Rvc2UgaXMgbm90IGxpbWl0aW5nKS4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDUuNX0KIyBpbXBvcnQgZmx1eCBzYW1wbGluZyByZXN1bHRzIGZvciBmb3VyIGNvbmRpdGlvbnMsIG1heCB0ZXN0ZWQgZ3Jvd3RoIHJhdGUKZGZfc2FtcGxpbmcgPC0gbGFwcGx5KGxpc3QuZmlsZXMoIi4uL2RhdGEvc2ltdWxhdGlvbi9rYXBwX2ZpdHRpbmcvIiwgcGF0dGVybiA9ICJeRlNBIiwgZnVsbC5uYW1lcyA9IFRSVUUpLCAKICByZWFkX2NzdiwgY29sX3R5cGVzID0gY29scygpKSAlPiUgYmluZF9yb3dzKC5pZCA9ICJjb25kaXRpb24iKSAlPiUKICBtdXRhdGUoY29uZGl0aW9uID0gcmVjb2RlKGNvbmRpdGlvbiwgYDFgID0gImZvcm1hdGUiLCBgMmAgPSAic3VjY2luYXRlIiwgCiAgICBgM2AgPSAiYW1tb25pdW0iLCBgNGAgPSAiZnJ1Y3Rvc2UiKSkgJT4lCiAgc2VsZWN0KC0yKSAlPiUgCiAgZ2F0aGVyKGtleSA9ICJyZWFjdGlvbl9pZCIsIHZhbHVlID0gImZsdXgiLCAtY29uZGl0aW9uKSAlPiUKICAKICBncm91cF9ieShjb25kaXRpb24sIHJlYWN0aW9uX2lkKSAlPiUgc3VtbWFyaXplKAogICAgZmx1eF9tbW9sX2dEQ1dfaCA9IG1lZGlhbihmbHV4KSwgCiAgICBzZF9mbHV4ID0gc2QoZmx1eCksIAogICAgbWluX2ZsdXggPSBtaW4oZmx1eCksIAogICAgbWF4X2ZsdXggPSBtYXgoZmx1eCksCiAgICBDViA9IGFicyhzZF9mbHV4L2ZsdXhfbW1vbF9nRENXX2gpKSAlPiUKICBmaWx0ZXIoIWdyZXBsKCJFWF8iLCByZWFjdGlvbl9pZCkpICU+JQogIGFycmFuZ2UoZGVzYyhzZF9mbHV4KSkgJT4lCiAgCiAgIyBqb2luIHdpdGggc2luZ2xlIG9wdGltYWwgc29sdXRpb24gZnJvbSBsb29wbGVzcyBGQkEuCiAgIyBXaWxsIGhlbHAgdXMgdG8gaWRlbnRpZnkgZnJlZSBydW5uaW5nIGN5Y2xlcyBpbiBGU0EKICBsZWZ0X2pvaW4oCiAgICBsYXBwbHkobGlzdC5maWxlcygiLi4vZGF0YS9zaW11bGF0aW9uL2thcHBfZml0dGluZy8iLCBwYXR0ZXJuID0gIl5GQkEiLCBmdWxsLm5hbWVzID0gVFJVRSksCiAgICByZWFkX2NzdiwgY29sX3R5cGVzID0gY29scygpKSAlPiUgYmluZF9yb3dzKC5pZCA9ICJjb25kaXRpb24iKSAlPiUKICAgIG11dGF0ZShjb25kaXRpb24gPSByZWNvZGUoY29uZGl0aW9uLCBgMWAgPSAiZm9ybWF0ZSIsIGAyYCA9ICJzdWNjaW5hdGUiLCAKICAgICAgYDNgID0gImFtbW9uaXVtIiwgYDRgID0gImZydWN0b3NlIikpICU+JQogICAgcmVuYW1lKHJlYWN0aW9uX2lkID0gWDEsIGZsdXhfbW1vbF9nRENXX2hfRkJBID0gbG9vcGxlc3MpCiAgKQoKICAKIyBwbG90IHJlc3VsdHM7IApwbG90X3NhbXBsaW5nIDwtIGxhcHBseShsaXN0KEEgPSBjKC01MCwgNTUwKSwgQiA9IGMoLTMsIDI4KSksIGZ1bmN0aW9uKHlsaW0pIHsKICB4eXBsb3Qoc2RfZmx1eCB+IHNlcV9hbG9uZyhzZF9mbHV4KSB8IGNvbmRpdGlvbiwKICAgIGZpbHRlcihkZl9zYW1wbGluZywgc2RfZmx1eCAhPSAwKSwgCiAgICBiZXR3ZWVuID0gbGlzdCh4ID0gMC41LCB5ID0gMC41KSwKICAgIGdyb3VwcyA9IGNvbmRpdGlvbiwgeGxhYiA9IE5VTEwsIHlsaW0gPSB5bGltLAogICAgc2NhbGVzID0gbGlzdChhbHRlcm5hdGluZyA9IEZBTFNFKSwKICAgIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksIGxheW91dCA9IGMoNCwgMSksCiAgICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICAgIHBhbmVsLmFibGluZShoID0gMSwgbHR5ID0gMiwgY29sID0gZ3JleSgwLjUpKQogICAgICBwYW5lbC54eXBsb3QoeCwgeSwgLi4uKQogICAgICBwYW5lbC5rZXkoLi4uLCBwb2ludHMgPSBGQUxTRSwgY29ybmVyID0gYygwLjk1LDAuOTUpKQogICAgfQogICkKfSkKCnByaW50KHBsb3Rfc2FtcGxpbmdbWzFdXSwgc3BsaXQgPSBjKDEsMSwxLDIpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9zYW1wbGluZ1tbMl1dLCBzcGxpdCA9IGMoMSwyLDEsMikpCmBgYAoKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CiMgcHJvY2VzcyBmbHV4IGRpc3RyaWJ1dGlvbiwgZS5nLiBieSByZW1vdmluZyBleHRyZW1lbHkgaGlnaCBmbHV4ZXMKZGZfc2FtcGxpbmcyIDwtIGRmX3NhbXBsaW5nICU+JQogIAogICMgRmlsdGVyIG91dCBzdW1tYXJ5IGFuZCBvdXRkYXRlZCByZWFjdGlvbnMKICBmaWx0ZXIoIXJlYWN0aW9uX2lkICVpbiUgYygiQmlvbWFzcyIsICJNYWludGVuYW5jZSIsICJQcm90ZWluIiwgIkNhcmJvaHlkcmF0ZSIsIAogICAgIlBob3NwaG9saXBpZCIsICIJQ29mYWN0b3JzX2FuZF92aXRhbWlucyIsICJDQkJDWUMiLCAiUFlLMSIsICJQWUsyIiwgIlBZSzMiLAogICAgIkRIRlIyIiwgIkRIRlIzIiwgIkRIRlIycCIsICJESEZSM3AiKSkgJT4lCiAgCiAgIyByZXBsYWNlIGV4dHJlbWUgZmx1eGVzIHdpdGggYSBtaW4gYW5kIG1heCBlc3RpbWF0ZWQgZnJvbSBsb29wbGVzcyBGQkEKICBtdXRhdGUoCiAgICBtaW5fZmx1eCA9IGlmX2Vsc2Uoc2RfZmx1eCA+IDEsIC1hYnMoZmx1eF9tbW9sX2dEQ1dfaF9GQkEpLCBtaW5fZmx1eCksCiAgICBtYXhfZmx1eCA9IGlmX2Vsc2Uoc2RfZmx1eCA+IDEsICBhYnMoZmx1eF9tbW9sX2dEQ1dfaF9GQkEpLCBtYXhfZmx1eCkKICApICU+JQogIAogICMgYWRkIGFuIGVycm9yIG1hcmdpbiB0byB0aGUgc2FtcGxlZCBtaW4gYW5kIG1heCBmbHV4ZXMsIHRvIGhlbHAgdGhlIHNvbHZlcgogICMgZmluZCBhIGZlYXNpYmxlIHNvbHV0aW9uCiAgbXV0YXRlKAogICAgbWluX2ZsdXggPSBtaW5fZmx1eC0oYWJzKG1pbl9mbHV4KSoxKSwgCiAgICBtYXhfZmx1eCA9IG1heF9mbHV4KyhhYnMobWF4X2ZsdXgpKjEpCiAgKSAlPiUKICAKICAjIHJlLWZvcm1hdHRpbmcgb2YgdGFibGUKICBhcnJhbmdlKGRlc2MoYWJzKG1heF9mbHV4KSkpICU+JQogIG11dGF0ZShyZWFjdGlvbl9pZCA9IHBhc3RlMCgiUl8iLCByZWFjdGlvbl9pZCkpCgpoZWFkKGRmX3NhbXBsaW5nMikKCiMgZXhwb3J0IHRvIGNzdgpmb3IgKGNvbmQgaW4gdW5pcXVlKGRmX3NhbXBsaW5nMiRjb25kaXRpb24pKSB7CiAgZmlsdGVyKGRmX3NhbXBsaW5nMiwgY29uZGl0aW9uID09IGNvbmQpICU+JSB1bmdyb3VwICU+JQogICAgc2VsZWN0KC1jb25kaXRpb24pICU+JQogICAgd3JpdGVfY3N2KHBhc3RlMCgiLi4vZGF0YS9zaW11bGF0aW9uL2thcHBfZml0dGluZy9tb2RlbF9mbHV4X3NhbXBsaW5nXyIsIGNvbmQsICIuY3N2IikpCn0KYGBgCgoKIyMgRGV0ZXJtaW5lIGs8c3ViPmFwcDwvc3ViPiB2YWx1ZXMKClRoZSBmaW5hbCBzdGVwIGlzIHRvIG1lcmdlIGJvdGggZGF0YXNldHMgYnkgY29tcHV0aW5nIHRoZSBlbnp5bWUgYWJ1bmRhbmNlIGFsbG9jYXRlZCB0byBlYWNoIHJlYWN0aW9uLiBUaGUgZXN0aW1hdGVkIGs8c3ViPmFwcDwvc3ViPiBpcyB0aGVuIGRldGVybWluZWQgYnkgZGl2aWRpbmcgdGhlIGZsdXggdGhyb3VnaCB0aGUgZW56eW1lIGFidW5kYW5jZSBhdmFpbGFibGUgZm9yIHRoaXMgcmVhY3Rpb24uIFRoaXMgc3RlcCB3YXMgbm90IHBlcmZvcm1lZCBpbiB0aGlzIFIgbm90ZWJvb2sgYnV0IHVzaW5nIHRoZSBSQkEgYnVpbHQtaW4gZnVuY3Rpb25zIGZyb20gdGhlIGBSQkFweSBlc3RpbWAgZm9sZGVyLiBCcmllZmx5LCBmbHV4IGJvdW5kYXJpZXMgYW5kIHByb3RlaW4gY29uY2VudHJhdGlvbiBkYXRhIHdhcyBleHBvcnRlZCBhbmQgc2VydmVzIGFzIGlucHV0IGZvciB0aGUgc2NyaXB0IGBrYXBwLnB5YCAoW2xpbmtdKGh0dHBzOi8vZ2l0aHViLmNvbS9tLWphaG4vQmFjdGVyaWFsLVJCQS1tb2RlbHMvdHJlZS9tYXN0ZXIvUmFsc3RvbmlhLWV1dHJvcGhhLUgxNikpLiBUaGlzIHNjcmlwdCBwZXJmb3JtcyBhIHNlcmllcyBvZiBGQkEgYW5kIFJCQSBzaW11bGF0aW9ucyBjb25zdHJhaW5lZCBieSB0aGUgaW5wdXQgZmx1eGVzIGFuZCBwcm90ZWluIGNvbmNlbnRyYXRpb25zLiBJdCB0aGVuIGdpdmVzIGFuIGVzdGltYXRpb24gb2YgdGhlIGs8c3ViPmFwcDwvc3ViPiBmb3IgYSBwYXJ0aWN1bGFyIGNvbmRpdGlvbi4KCiMgRnJhY3Rpb24gb2YgcHJvdGVpbiBwZXIgY29tcGFydG1lbnQKClRoZSBSQkEgbW9kZWwgdGFrZXMgYXMgYW5vdGhlciBpbnB1dCBwYXJhbWV0ZXIgKG9yIGNvbnN0cmFpbnQpIHRoZSBmcmFjdGlvbiBvZiBwcm90ZWluIHBlciBjb21wYXJ0bWVudC4gVGhpcyBjb25zdHJhaW50IGlzIGltcG9ydGFudCBhcyBpdCBhbGxvd3MgdGhlIGNlbGwgdG8gaGF2ZSBvbmx5IGEgbGltaXRlZCBhbW91bnQgb2YgcHJvdGVpbiBpbiBjeXRvcGxhc20gb3IgbWVtYnJhbmUgY29tcGFydG1lbnRzLCBmb3IgZXhhbXBsZS4gVGhpcyBjb25zdHJhaW50IGNhbiBiZSBjb25zdGFudCBvciBpdCBjYW4gYmUgZ3Jvd3RoIHJhdGUgZGVwZW5kZW50IGUuZy4gYnkgYSBsaW5lYXIgcmVsYXRpb25zaGlwLgoKVGhlIGZpcnN0IHN0ZXAgaXMgdG8gcHJlcGFyZSBhICoqdGFibGUgd2l0aCBwcm90ZWluIGFidW5kYW5jZSBhbmQgbG9jYWxpemF0aW9uKiouIFByb3RlaW4gYWJ1bmRhbmNlIGNhbiBiZSBpbiBhbnkgdW5pdCBhY2NvcmRpbmcgdG8gdGhlIFJCQXB5IG1hbnVhbCwgYnV0IGl0J3MgYmVzdCB0byB1c2UgYG1vbCBmcmFjdGlvbmAgaW5zdGVhZCBvZiBgbWFzcyBmcmFjdGlvbmAsIGFzIGFsbCBvdGhlciBSQkFweSBmdW5jdGlvbnMgYWxzbyB1c2UgYG1tb2xgLiBUaGUgYG1vbCBmcmFjdGlvbmAgaXMgYWxyZWFkeSBhdmFpbGFibGUgaW4gdGhlIHByb2Nlc3NlZCBkYXRhIHNldC4gVGhlIGJ1aWx0LWluIGBlc3RpbWAgZnVuY3Rpb25zIGRvbid0IHNlZW0gdG8gYmUgd2VsbCBzdXBwb3J0ZWQgaW4gcHl0aG9uMyBhbmQgcmFpc2UgZXJyb3JzLiBXZSB0aGVyZWZvcmUgZG8gdGhlIGVzdGltYXRpb24gbWFudWFsbHkgYnkgZml0dGluZyBsaW5lYXIgbW9kZWxzIHRvIHRoZSBncm93dGggcmF0ZS1wcm90ZWluIGFidW5kYW5jZSByZWxhdGlvbnNoaXAuIFRoZSBpZGVhIGlzIHNpbWlsYXIgdG8gdGhlIFJCQSBgZXN0aW1gIGZ1bmN0aW9uIGJ1dCBsZXNzIGNvbXBsaWNhdGVkLiBXZSBmb2N1cyBvbiB0aGUgc3RhbmRhcmQgY29uZGl0aW9uIGFzIGEgKip0cmFpbmluZyBkYXRhIHNldCoqIChmcnVjdG9zZSBhcyBjYXJib24gc291cmNlLCBubyBOSDQrIGxpbWl0YXRpb24pLgoKYGBge3J9CmRmX3Byb3RlaW4gPC0gUmFsc3RvbmlhX2V1dHJvcGhhICU+JSAKICAKICAjIHNlbGVjdCBvbmx5IHJlcXVpcmVkIGNvbHVtbnMgYW5kIHNwcmVhZCB0byBsb25nIGZvcm1hdAogIHNlbGVjdChjb25kaXRpb24sIGxvY3VzX3RhZywgZ3Jvd3RocmF0ZSwgUHNvcnRiX2xvY2FsaXphdGlvbiwgbW9sX2ZyYWN0aW9uKSAlPiUKICBzZXRfbmFtZXMoYygiY29uZGl0aW9uIiwgInByb3RlaW4iLCAiZ3Jvd3RocmF0ZSIsICJsb2NhdGlvbiIsICJtb2xfZnJhY3Rpb24iKSkgJT4lCiAgbXV0YXRlKGNvbmRpdGlvbiA9IHJlY29kZShjb25kaXRpb24sIGBGQWAgPSAiZm9ybWF0ZSIsIGBOTElNYCA9ICJhbW1vbml1bSIsIGBGUkNgID0gImZydWN0b3NlIiwgYFNVQ2AgPSAic3VjY2luYXRlIikpICU+JQogIAogICMgbWF0Y2ggbG9jYWxpemF0aW9uIG5hbWVzIHRvIG1vZGVsLCBzaW1wbGlmeQogIG11dGF0ZShsb2NhdGlvbiA9IHJlY29kZShsb2NhdGlvbiwgVW5rbm93biA9ICJDeXRvcGxhc20iLCBDeXRvcGxhc21pYyA9ICJDeXRvcGxhc20iKSkgJT4lCiAgbXV0YXRlKGxvY2F0aW9uID0gcmVwbGFjZShsb2NhdGlvbiwgbG9jYXRpb24gIT0gIkN5dG9wbGFzbSIsICJDZWxsX21lbWJyYW5lIikpCgpoZWFkKGRmX3Byb3RlaW4pCmBgYAoKTm93IHdlIGNhbiBzdW1tYXJpemUgdGhlIGRhdGEgYnkgdGFraW5nIHRoZSBzdW0gb2YgYG1vbCBmcmFjdGlvbmAgb3ZlciBjb25kaXRpb24gYW5kIGxvY2FsaXphdGlvbi4gQSBzaW1wbGUgYXBwcm9hY2ggdG8gZmluZGluZyBsaW5lYXIgZnVuY3Rpb25zLCB3aGVyZSBhbGwgcHJvdGVpbnMgb2YgYWxsIGxvY2F0aW9ucyBzdW0gdG8gb25lIGZvciBhIHNwZWNpZmljIGdyb3d0aCByYXRlLCB3b3VsZCBiZSB0byBmaXQgbGluZWFyIG1vZGVscyBmb3IgYWxsIGNvbXBhcnRtZW50cyBleGNlcHQgb25lIChlLmcuIGN5dG9wbGFzbWljIHByb3RlaW5zLCB0aGUgYmlnZ2VzdCBjb21wYXJ0bWVudCkuIFRoaXMgb25lIHdpbGwgdGhlbiBnZXQgYSBsaW5lYXIgbW9kZWwgZml0dGVkIGZyb20gdGhlIHJlc2lkdWFsIHByb3RlaW4gbWFzcy4gV2Ugd291bGQgZXhwZWN0IHRoZSBlcnJvciBmb3IgYEN5dG9wbGFzbWAgdG8gYmUgbmVnbGlnaWJseSBzbWFsbCBhcyBpdCBpcyB0aGUgYmlnZ2VzdCBjb21wYXJ0bWVudC4gSG93ZXZlciBpdCB0dXJuZWQgb3V0IHRoYXQgdGhlIGxpbmVhciBtb2RlbHMgZml0dGVkIHRvIGJvdGggY29tcGFydG1lbnRzIHBlcmZlY3RseSBzdW0gdG8gb25lIGFscmVhZHkgKHNlZSBiZWxvdykuIEFuIGVzdGltYXRpb24gZnJvbSByZXNpZHVhbCBwcm90ZWluIGZyYWN0aW9uIGlzIHRoZXJlZm9yZSBub3QgbmVjZXNzYXJ5LgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KIyBGaXJzdCByZXRyaWV2ZSB0aGUgcHJvdGVpbnMgdGhhdCBhcmUgbm90IHBhcnQgb2YgdGhlIG1vZGVsLCB3aGljaCBpcyBuZWVkZWQKIyB0byAgY2FsY3VsYXRlIG5vbi1lbnp5bWF0aWMgZnJhY3Rpb24gbGF0ZXIgb24KbW9kZWxfZ2VuZXMgPC0gYygKICByZWFkX2NzdigiLi4vZGF0YS9pbnB1dC9tb2RlbF9yZWFjdGlvbnMuY3N2IiwgY29sX3R5cGVzID0gY29scygpKSAlPiUgc2VwYXJhdGVfcm93cyhnZW5lcywgc2VwID0gIiwgIikgJT4lCiAgICBmaWx0ZXIoIWR1cGxpY2F0ZWQoZ2VuZXMpKSAlPiUgcHVsbChnZW5lcyksCiAgcmVhZF90c3YoIi4uL2RhdGEvc2ltdWxhdGlvbi9tYWNyb19tYWNoaW5lcy9yaWJvc29tZS50c3YiLCBjb2xfdHlwZXMgPSBjb2xzKCkpW1siR2VuZSBuYW1lcyJdXSwKICByZWFkX3RzdigiLi4vZGF0YS9zaW11bGF0aW9uL21hY3JvX21hY2hpbmVzL2NoYXBlcm9uZXMudHN2IiwgY29sX3R5cGVzID0gY29scygpKVtbIkdlbmUgbmFtZXMiXV0sCiAgcmVhZF90c3YoIi4uL2RhdGEvc2ltdWxhdGlvbi9tYWNyb19tYWNoaW5lcy90cmFuc2NyaXB0aW9uLnRzdiIsIGNvbF90eXBlcyA9IGNvbHMoKSlbWyJHZW5lIG5hbWVzIl1dLAogIHJlYWRfdHN2KCIuLi9kYXRhL3NpbXVsYXRpb24vbWFjcm9fbWFjaGluZXMvcmVwbGljYXRpb24udHN2IiwgY29sX3R5cGVzID0gY29scygpKVtbIkdlbmUgbmFtZXMiXV0KKQoKIyBleHRyYWN0IGxvY3VzX3RhZ3Mgb25seQptb2RlbF9nZW5lcyA8LSBtb2RlbF9nZW5lcyAlPiUgc3RyaV9leHRyYWN0X2ZpcnN0X3JlZ2V4KAogIHBhdHRlcm4gPSAiSDE2X1tBLVpdWzAtOV17NH18UEhHWzAtOV17M30iKQoKIyBhZGQgbmV3IE5FX3Byb3RlaW4gY29sdW1uCmRmX3Byb3RfcGVyX2NvbXAgPC0gZGZfcHJvdGVpbiAlPiUKICBtdXRhdGUoTkVfcHJvdGVpbiA9ICFwcm90ZWluICVpbiUgbW9kZWxfZ2VuZXMpICU+JQogIGdyb3VwX2J5KGNvbmRpdGlvbiwgbG9jYXRpb24sIGdyb3d0aHJhdGUpCmBgYAoKVGhlIGZvbGxvd2luZyBwbG90IHNob3dzIGEgc2xpZ2h0IGluY3JlYXNlIGluIGN5dG9wbGFzbWljIHByb3RlaW5zIGFuZCBkZWNyZWFzZSBpbiBjZWxsIG1lbWJyYW5lIHByb3RlaW5zIHdpdGggZ3Jvd3RoIHJhdGUuCgpgYGB7ciwgZmlnLndpZHRoID0gMywgZmlnLmhlaWdodCA9IDMuMiwgbWVzc2FnZSA9IEZBTFNFfQpwbG90X3Byb3RfY29tcCA8LSB4eXBsb3QocHJvdF9wZXJfY29tcGFydG1lbnQgfiBncm93dGhyYXRlLCAKICBkZl9wcm90X3Blcl9jb21wICU+JQogICAgc3VtbWFyaXplKHByb3RfcGVyX2NvbXBhcnRtZW50ID0gc3VtKG1vbF9mcmFjdGlvbiwgbmEucm0gPSBUUlVFKSksCiAgZ3JvdXBzID0gbG9jYXRpb24sIHlsaW0gPSBjKC0wLjEsIDEuMSksCiAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwgY2V4ID0gMC43LAogIHNjYWxlcyA9IGxpc3QoYWx0ZXJuYXRpbmcgPSBGQUxTRSksCiAgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksCiAgeGxhYiA9IGV4cHJlc3Npb24oIsK1IFtoIl4tMSoiXSIpLAogIHlsYWIgPSAicHJvdGVpbiBtYXNzIGZyYWN0aW9uIiwKICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgcGFuZWwuc3VwZXJwb3NlKHgsIHksIC4uLikKICAgIHBhbmVsLmtleShjb3JuZXIgPSBjKDAuMSwgMC41NSksIC4uLikKICB9LCBwYW5lbC5ncm91cHMgPSBmdW5jdGlvbih4LCB5LCAuLi4pIHsKICAgIHBhbmVsLnh5cGxvdCh4LCB5LCAuLi4pCiAgICBwYW5lbC5sbWxpbmVxKHgsIHksIHIuc3F1YXJlZCA9IFRSVUUsCiAgICAgIHBvcyA9IDMsIG9mZnNldCA9IDEsIC4uLikKICB9CikKCnByaW50KHBsb3RfcHJvdF9jb21wKQpgYGAKCgojIEZyYWN0aW9uIG9mIG5vbi1lbnp5bWF0aWMgcHJvdGVpbiBwZXIgY29tcGFydG1lbnQKClRoZSBwcmV2aW91cyBjYWxjdWxhdGlvbiBkZXRlcm1pbmVkIGhvdyB0aGUgdG90YWwgcHJvdGVpbiBwb29sIGlzIGRpc3RyaWJ1dGVkIG92ZXIgY29tcGFydG1lbnRzLiBJbiB0aGlzIHNlY3Rpb24sIHdlIHdpbGwgZ28gb25lIGxldmVsIGRlZXBlciBhbmQgZGV0ZXJtaW5lIHRoZSBmcmFjdGlvbiBvZiAqKm5vbi1lbnp5bWF0aWMgcHJvdGVpbioqIHBlciBjb21wYXJ0bWVudCwgYW5hbG9nb3VzbHkgdG8gdGhlIHByZXZpb3VzIGNhbGN1bGF0aW9uLiBUaGF0IG1lYW5zIGV2ZXJ5IGNvbXBhcnRtZW50J3MgcHJvdGVpbiBwb29sIGlzIGZ1cnRoZXIgc3ViZGl2aWRlZCBpbnRvIHR3byBzZWN0b3JzLCBlbnp5bWF0aWMgYW5kIG5vbi1lbnp5bWF0aWMgcHJvdGVpbnMsIGFuZCB0aGVzZSBzZWN0b3JzIGNhbiBoYXZlIC0tYWdhaW4tLSBhIGxpbmVhciBkZXBlbmRlbmN5IG9uIGdyb3d0aCByYXRlLiBBY2NvcmRpbmcgdG8gdGhlIFJCQXB5IG1hbnVhbCwgdGhlIHByb3RlaW5zIHRoYXQgYXJlICoqY29uc2lkZXJlZCBub24tZW56eW1hdGljIGFyZSBhbGwgcHJvdGVpbnMgbm90IGFjdGluZyBhcyBlbnp5bWVzIG9yIG1vbGVjdWxhciBtYWNoaW5lcyoqIGluIHRoZSBtb2RlbCAoc3VjaCBhcyByaWJvc29tZXMsIGNoYXBlcm9uZXMsIEROQSBwb2x5bWVyYXNlIGFuZCBzbyBvbikuIE5vbi1lbnp5bWF0aWMgKE5FKSBwcm90ZWlucyBhcmUgdGhlcmVmb3JlIGFsbCBwcm90ZWlucyBub3QgY2FwdHVyZWQgYnkgdGhlIFJCQSBtb2RlbCBhdCBhbGwuIFRoZSBmcmFjdGlvbiBvZiBORSBwcm90ZWlucyBwZXIgY29tcGFydG1lbnQgaXMgYSBwZXJjZW50YWdlIG9mIHRoZSBwcm90ZWlucyBmb3IgdGhhdCBjb21wYXJ0bWVudCBvbmx5IChlYWNoIGNvbXBhcnRtZW50IHN1bXMgdG8gMSkuCgpgYGB7ciwgZmlnLndpZHRoID0gMywgZmlnLmhlaWdodCA9IDMuMiwgbWVzc2FnZSA9IEZBTFNFfQojIGRldGVybWluZSBORSBwcm90ZWluIHBlciBjb21wYXJ0bWVudApkZl9uZV9wcm90X3Blcl9jb21wIDwtIGRmX3Byb3RfcGVyX2NvbXAgJT4lIGdyb3VwX2J5KE5FX3Byb3RlaW4sIC5hZGQgPSBUUlVFKSAlPiUKICBzdW1tYXJpemUocHJvdF9wZXJfY29tcGFydG1lbnQgPSBzdW0obW9sX2ZyYWN0aW9uLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBzdW1tYXJpemUobmVfcHJvdF9wZXJfY29tcGFydG1lbnQgPSAKICAgIHByb3RfcGVyX2NvbXBhcnRtZW50WzJdL3N1bShwcm90X3Blcl9jb21wYXJ0bWVudCkKICApCiAgCiMgdGhlIGZyYWN0aW9uIG9mIE5FIHByb3RlaW4gcGVyIGNvbXBhcnRtZW50IGRlY3JlYXNlcyB3aXRoIGdyb3d0aCByYXRlCnBsb3RfcHJvdF9ORSA8LSB4eXBsb3QobmVfcHJvdF9wZXJfY29tcGFydG1lbnQgfiBncm93dGhyYXRlLAogIGRmX25lX3Byb3RfcGVyX2NvbXAsCiAgZ3JvdXBzID0gbG9jYXRpb24sIHlsaW0gPSBjKC0wLjEsIDEuMSksCiAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwKICBzY2FsZXMgPSBsaXN0KGFsdGVybmF0aW5nID0gRkFMU0UpLAogIGJldHdlZW4gPSBsaXN0KHggPSAwLjUsIHkgPSAwLjUpLCBjZXggPSAwLjcsCiAgeGxhYiA9IGV4cHJlc3Npb24oIsK1IFtoIl4tMSoiXSIpLAogIHlsYWIgPSAiTkUgcHJvdGVpbiBtYXNzIGZyYWN0aW9uIiwKICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgcGFuZWwuc3VwZXJwb3NlKHgsIHksIC4uLikKICAgIHBhbmVsLmtleShjb3JuZXIgPSBjKDAuMSwgMC4xKSwgLi4uKQogIH0sIHBhbmVsLmdyb3VwcyA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgcGFuZWwueHlwbG90KHgsIHksIC4uLikKICAgIHBhbmVsLmxtbGluZXEoeCwgeSwgci5zcXVhcmVkID0gVFJVRSwKICAgICAgcG9zID0gMywgb2Zmc2V0ID0gMSwgLi4uKQogIH0KKQoKcHJpbnQocGxvdF9wcm90X05FKQpgYGAKCi0tLS0tLS0tLS0KCioqU3VtbWFyeToqKgpSZWdhcmRsZXNzIG9mIHRoZSBjdWx0aXZhdGlvbiBjb25kaXRpb24gb3Igc3Vic3RyYXRlIGxpbWl0YXRpb24sIGl0IGlzIHBvc3NpYmxlIHRvIHNlZSBncm93dGggcmF0ZSBkZXBlbmRlbnQgdHJlbmRzLiBPdmVyYWxsLCB0aGUgZnJhY3Rpb24gb2YgY3l0b3BsYXNtaWMgcHJvdGVpbnMgaW5jcmVhc2VzIHNsaWdodGx5IHdpdGggZ3Jvd3RoIHJhdGUgd2hpbGUgbWVtYnJhbmUtYXNzb2NpYXRlZCBwcm90ZWlucyBkZWNyZWFzZS4gVGhpcyBtYWtlcyBzZW5zZSBhcyBvZnRlbiBleHByZXNzaW9uIG9mIHRyYW5zcG9ydGVycyBpcyBkb3duLXJlZ3VsYXRlZCBpZiBiYWN0ZXJpYSBkb24ndCBuZWVkIHRvIHNjYXZlbmdlIHN1YnN0cmF0ZXMuIFdlIGFsc28gc2VlIHRoYXQgdGhlIGFic29sdXRlIG1ham9yaXR5IG9mIHByb3RlaW5zIGlzIGxvY2F0ZWQgaW4gdGhlIGN5dG9wbGFzbSAoPiA4NSUgdW5kZXIgYWxsIGNvbmRpdGlvbnMpLgoKVGhlIG5vbi1lbnp5bWF0aWMgZnJhY3Rpb24gb2YgcHJvdGVpbnMgKHByb3RlaW5zICpub3QgY292ZXJlZCogYnkgdGhlIG1vZGVsKSBkZWNyZWFzZXMgd2l0aCBncm93dGggcmF0ZSBmcm9tIGFyb3VuZCA2MCUgbyA1MCUgZm9yIGN5dG9wbGFzbWljIHByb3RlaW5zLCBidXQgaXMgcmVsYXRpdmVseSBjb25zdGFudCBmb3IgbWVtYnJhbmUgYXNzb2NpYXRlZCBvbmVzLiBNb3N0IG1lbWJyYW5lLWFzc29jaWF0ZWQgcHJvdGVpbnMgKDkwJSBvZiBtb2wgZnJhY3Rpb24pIGFyZSBub3QgY292ZXJlZCBieSB0aGUgbW9kZWwuIFRoZSBmb2xsb3dpbmcgZmlndXJlIHN1bW1hcml6ZXMgYWxsIGdyb3d0aCByYXRlIGRlcGVuZGVudCB0cmVuZHMuCgpgYGB7ciwgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDIuNCwgbWVzc2FnZSA9IEZBTFNFfQpwbG90X3Byb3RfcGllIDwtIGRmX3Byb3RfcGVyX2NvbXAgJT4lIGdyb3VwX2J5KE5FX3Byb3RlaW4sIC5hZGQgPSBUUlVFKSAlPiUKICBzdW1tYXJpemUocHJvdF9wZXJfY29tcGFydG1lbnQgPSBzdW0obW9sX2ZyYWN0aW9uLCBuYS5ybSA9IFRSVUUpKSAlPiUKICB1bmdyb3VwICU+JSBtdXRhdGUoCiAgICBsb2NhdGlvbiA9IHJlY29kZShsb2NhdGlvbiwgQ3l0b3BsYXNtID0gIkNQIiwgQ2VsbF9tZW1icmFuZSA9ICJDTSIpLAogICAgbG9jYXRpb24gPSBwYXN0ZShsb2NhdGlvbiwgaWZlbHNlKE5FX3Byb3RlaW4sICJORSIsICJFIiksIHNlcCA9Ii0iKSkgJT4lCiAgZmlsdGVyKGdyb3d0aHJhdGUgPT0gMC4yNSkgJT4lCiAgI2ZpbHRlcihjb25kaXRpb24gPT0gIkZSQyIpICU+JQogIAogIHh5cGxvdCggfiBwcm90X3Blcl9jb21wYXJ0bWVudCB8IGNvbmRpdGlvbiwgLiwKICAgIGdyb3VwcyA9IGZhY3Rvcihsb2NhdGlvbiksIHNjYWxlcyA9IGxpc3QoZHJhdyA9IEZBTFNFKSwKICAgIHhsYWIgPSAibW9sIGZyYWN0aW9uIG9mIHByb3Rlb21lLCBwZXIgY29tcGFydG1lbnQiLAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwgbGF5b3V0ID0gYyg0LDEpLAogICAgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksCiAgICBjZXggPSAwLjYsIGJvcmRlciA9IGdyZXkoMC4zKSwKICAgIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICAgIHBhbmVsLnBpZWNoYXJ0KHgsIGRpYW1ldGVyX2lubmVyID0gMC4xLCAKICAgICAgICBkaWFtZXRlcl9zZWN0b3IgPSAwLjE1LCAuLi4pCiAgICB9CiAgKQoKcHJpbnQocGxvdF9wcm90X3BpZSkKYGBgCgotLS0tLS0tLS0tCgojIENvbWJpbmluZyBlc3RpbWF0ZWQgazxzdWI+YXBwPC9zdWI+IHZhbHVlcyB0byBnbG9iYWwgY29uc2Vuc3VzIGs8c3ViPmFwcDwvc3ViPgoKVGhlIHB1cnBvc2Ugb2YgdGhpcyBzZWN0aW9uIGlzIHRvIG1lcmdlIGRpZmZlcmVudCBrPHN1Yj5hcHA8L3N1Yj4gZXN0aW1hdGlvbnMgZnJvbSBkaWZmZXJlbnQgY29uZGl0aW9ucyB0byBvYnRhaW4gYSAqKmNvbnNlbnN1cyBrPHN1Yj5hcHA8L3N1Yj4gZXN0aW1hdGlvbioqLiBUaGUgcmVhc29uaW5nIGhlcmUgaXMgdGhhdCBrPHN1Yj5hcHA8L3N1Yj4gaXMgZXN0aW1hdGVkIGZyb20gZGl2aWRpbmcgdGhlIChzYW1wbGVkIG9yIG1lYXN1cmVkKSBtZXRhYm9saWMgZmx1eCB0aHJvdWdoIGFuIGVuenltZSBieSBpdHMgYWJ1bmRhbmNlLCBwcm92aWRpbmcgYW4gZXN0aW1hdGUgb2YgZW56eW1lIGVmZmljaWVuY3kuIFRoaXMgZWZmaWNpZW5jeSBkZXBlbmRzIGFsc28gb24gdGhlIHNhdHVyYXRpb24gb2YgdGhlIGVuenltZS4gRXN0aW1hdGlvbiBvZiBrPHN1Yj5hcHA8L3N1Yj4gd2FzIHRoZXJlZm9yZSBwZXJmb3JtZWQgZm9yIGhpZ2hlc3QgYXZhaWxhYmxlIGdyb3d0aCByYXRlcy9mbHV4ZXMgZm9yIGZvdXIgZGlmZmVyZW50IGNvbmRpdGlvbnMsIGluIG9yZGVyIHRvIG9idGFpbiBlbnp5bWUgZWZmaWNpZW5jeSBhdCB0aGUgaGlnaGVzdCBzYXR1cmF0aW9uIGFtb25nIHRoZSB0ZXN0ZWQgY29uZGl0aW9ucy4KCkV4YW1wbGU6IEZvcm1hdGUgZGVoeWRyb2dlbmFzZSBpcyBub3QgdXNlZCB1bmRlciBncm93dGggb24gZnJ1Y3Rvc2UgKG5vIG9yIGxvdyBlZmZpY2llbmN5KSB3aGlsZSBpdCBpcyB1c2VkIHVuZGVyIGdyb3d0aCBvbiBmb3JtYXRlIChoaWdoIGVmZmljaWVuY3kpLiBUaGUgbWF4aW11bSBrPHN1Yj5hcHA8L3N1Yj4gc2hvdWxkIHRoZXJlZm9yZSBiZSB0YWtlbiBmcm9tIGZvcm1hdGUgY29uZGl0aW9uLgoKIyMgSW1wb3J0IGs8c3ViPmFwcDwvc3ViPiBlc3RpbWF0aW9uIGRhdGEgb2J0YWluZWQgZnJvbSBgUkJBIGVzdGltYAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KIyBkYXRhIGRpcmVjdG9yeQprYXBwX2NhbGliX2RpciA8LSAiLi4vZGF0YS9zaW11bGF0aW9uL2thcHBfZml0dGluZy8iCgojIHNldHRpbmcgZ2xvYmFsIHBhcmFtZXRlcgptYXhfc2F0dXJhdGlvbiA9IDAuNzUKCiMgbG9hZCBrYXBwIGVzdGltYXRpb24gZnJvbSBkaWZmZXJlbnQgY29uZGl0aW9ucwpjb25kaXRpb25zIDwtIGMoImFtbW9uaXVtIiwgImZvcm1hdGUiLCAiZnJ1Y3Rvc2UiLCAic3VjY2luYXRlIikKa2FwcF9maWxlcyA8LSBwYXN0ZTAoa2FwcF9jYWxpYl9kaXIsICJrYXBwX2VzdGltYXRlXyIsIGNvbmRpdGlvbnMsICIuY3N2IikKCmRmX2thcHAgPC0gbGFwcGx5KGthcHBfZmlsZXMsIGZ1bmN0aW9uKGRmKSB7CiAgcmVhZF90c3YoZGYsIGNvbF9uYW1lcyA9IGMoInJlYWN0aW9uX2lkIiwgImthcHBfZm9yd2FyZCIsICJrYXBwX3JldmVyc2UiKSl9KSAlPiUgCiAgYmluZF9yb3dzKC5pZCA9ICJjb25kaXRpb24iKSAlPiUKICAKICAjIGNoYW5nZSBjb25kaXRpb24gbmFtZXMKICBtdXRhdGUoY29uZGl0aW9uID0gcmVjb2RlKGNvbmRpdGlvbiwgISEhKGNvbmRpdGlvbnMgJT4lIHNldE5hbWVzKDE6NCkpKSkgJT4lCiAgCiAgIyBhZGQgYSBzYXR1cmF0aW9uIGZhY3RvciBTLCBpbmNyZWFzaW5nIGthcHAgYnkgMS9TCiAgbXV0YXRlX2F0KHZhcnMoY29udGFpbnMoImthcHBfIikpLCBmdW5jdGlvbih4KSB4L21heF9zYXR1cmF0aW9uKSAlPiUKICAKICAjIGFwcGx5IG1lZGlhbiBub3JtYWxpemF0aW9uIHRvIGFjY291bnQgZm9yIGRpZmZlcmVudCBpbiBzaWxpY28gZ3Jvd3RoIAogICMgcmF0ZXMgaW4gRkJBIHNpbXVsYXRpb24gKGRpZmZlcmVudCB0b3RhbCBmbHV4KQogIGdyb3VwX2J5KGNvbmRpdGlvbikgJT4lIAogIG11dGF0ZShrYXBwX2ZvcndhcmQgPSBrYXBwX2ZvcndhcmQqKG1lZGlhbiguW1sia2FwcF9mb3J3YXJkIl1dKS9tZWRpYW4oa2FwcF9mb3J3YXJkKSkpICU+JQogIG11dGF0ZShrYXBwX3JldmVyc2UgPSBrYXBwX2ZvcndhcmQpICU+JQogIAogICMgc29ydCBkZWNyZWFzaW5nbHkgYnkgYXZlcmFnZSBrYXBwCiAgZ3JvdXBfYnkocmVhY3Rpb25faWQpICU+JSAKICBtdXRhdGUoa2FwcF9tZWFuID0gbWVhbihrYXBwX2ZvcndhcmQpKSAlPiUKICBhcnJhbmdlKGRlc2Moa2FwcF9tZWFuKSkKCmhlYWQoZGZfa2FwcCkKYGBgCgoKYGBge3IsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSA3fQojIHBsb3Qga2FwcCB2YWx1ZSBkaXN0cmlidXRpb24gcGVyIGNvbmRpdGlvbgpwbG90X2thcHBfZGlzdCA8LSBsYXBwbHkoYygiY29uZGl0aW9uIiwgImFsbF9jb25kaXRpb25zIiksIGZ1bmN0aW9uKGNvbmQpIHsKICB4eXBsb3QobG9nMTAoa2FwcF9mb3J3YXJkKSB+IGFzLm51bWVyaWMoZmFjdG9yKHJlYWN0aW9uX2lkLCB1bmlxdWUocmVhY3Rpb25faWQpKSkgfCBnZXQoY29uZCksIAogICAgZGZfa2FwcCAlPiUgbXV0YXRlKGFsbF9jb25kaXRpb25zID0gImFsbCBjb25kaXRpb25zIiksIAogICAgZ3JvdXBzID0gY29uZGl0aW9uLCBjZXggPSAwLjcsIHBjaCA9IDE5LAogICAgeGxhYiA9ICJudW1iZXIgb2YgcmVhY3Rpb25zIiwgeWxhYiA9IGV4cHJlc3Npb24oImxvZyJbMTBdKiIgIGsiW2FwcF0qIiBbaCJeLTEqIl0iKSwKICAgIGFzLnRhYmxlID0gVFJVRSwgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksCiAgICBwYXIuc2V0dGluZ3MgPSBjdXN0b20uY29sb3JibGluZCgpLAogICAgc2NhbGVzID1saXN0KGFsdGVybmF0aW5nID0gRkFMU0UpLAogICAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCAuLi4pewogICAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICAgIHBhbmVsLnh5cGxvdCh4LCB5LCAuLi4pCiAgICAgIG1lZCA9IG1lZGlhbih5KQogICAgICBwYW5lbC5hYmxpbmUoaCA9IG1lZCwgbHR5ID0gMiwgbHdkID0gMiwgY29sID0gZ3JleSgwLjUpKQogICAgICBpZiAoY29uZCA9PSAiYWxsX2NvbmRpdGlvbnMiKSB7CiAgICAgICAgcGFuZWwudGV4dChncmlkOjp1bml0KDE3NSwgIm5wYyIpLCBtZWQrMywKICAgICAgICAgIGxhYmVscyA9IHBhc3RlMCgibWVkaWFuIGthcHAgPSAiLCByb3VuZCgxMF5tZWQpLCAiIFtoLTFdIikpCiAgICAgICAgcGFuZWwua2V5KC4uLiwgY29ybmVyID0gYygwLjk4LCAwLjk1KSkKICAgICAgfQogICAgfQogICkKfSkKCnByaW50KHBsb3Rfa2FwcF9kaXN0W1syXV0pCmBgYAoKCgojIyBNYW51YWwgYWRqdXN0bWVudCBvZiBrPHN1Yj5hcHA8L3N1Yj4gZm9yIHNlbGVjdGVkIHJlYWN0aW9ucwoKV2UgaW1wb3J0IGEgdGFibGUgd2l0aCBjb3JyZWN0aW9uIGZhY3RvcnMgZm9yIGs8c3ViPmFwcDwvc3ViPiB2YWx1ZXMgdGhhdCBoYXZlIGJlZW4gZXN0aW1hdGVkIGluY29ycmVjdGx5LiBUaGlzIHNldCBvZiBrPHN1Yj5hcHA8L3N1Yj4gdmFsdWVzIHdhcyBkZXRlcm1pbmVkIGJ5IGNvbXBhcmluZyB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBwcmVkaWN0ZWQgYW5kIG1lYXN1cmVkIHByb3RlaW4gYWJ1bmRhbmNlLiBQcm90ZWlucyB0aGF0IGFyZSBtb3JlIHRoYW4gMTAgZm9sZCBhd2F5IGZyb20gbWVhc3VyZWQgYWJ1bmRhbmNlIGFyZSBjYW5kaWRhdGVzIGZvciBtYW51YWwgY29ycmVjdGlvbi4gT3RoZXIgcmVhY3Rpb25zIHRoYXQgbmVlZCBjb3JyZWN0aW9uIGFyZSBtaXNzaW5nIGs8c3ViPmFwcDwvc3ViPiB2YWx1ZXMgZm9yIGltcG9ydGFudCB0cmFuc3BvcnRlcnMuIFRoZXNlIGFyZSBhbHNvIGluY2x1ZGVkIGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbi4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CiMgc2V0IG9mIGFkanVzdGVkIGthcHAgdmFsdWVzIGZvciBvdXRsaWVyIGVuenltZXMgbm90IGNvdmVyZWQgaW4ga2FwcCBlc3RpbWF0aW9uCmthcHBfbWVkaWFuIDwtIG1lZGlhbihkZl9rYXBwJGthcHBfZm9yd2FyZCkKa2FwcF9leHRyYSA8LSByZWFkX2NzdihwYXN0ZTAoa2FwcF9jYWxpYl9kaXIsICJrYXBwX2V4dHJhLmNzdiIpKQoKIyBwcmludCBtZWFuIGFuZCBzZCBvZiBsb2ctdHJhbnNmb3JtZWQga2FwcCBkaXN0cmlidXRpb24KZGZfa2FwcCRrYXBwX2ZvcndhcmQgJT4lIGxvZzEwICU+JSBtZWFuCmRmX2thcHAka2FwcF9mb3J3YXJkICU+JSBsb2cxMCAlPiUgc2QKCiMga2FwcCBlc3RpbWF0aW9uIGZvciByZWFjdGlvbnMgd2hlcmUgaW5mb3JtYXRpb24gaXMgbWlzc2luZwprYXBwX21pc3NpbmcgPC0ga2FwcF9leHRyYSAlPiUKICBmaWx0ZXIoIShyZWFjdGlvbl9pZCAlaW4lIGRmX2thcHAkcmVhY3Rpb25faWQpKSAlPiUKICBtdXRhdGUoCiAgICBrYXBwX2ZvcndhcmQgPSByYXRpb19tb2xfZnJhY3Rpb24qa2FwcF9tZWRpYW4sCiAgICBrYXBwX3JldmVyc2UgPSBrYXBwX2ZvcndhcmQpICU+JQogIHNlbGVjdCgtcmF0aW9fbW9sX2ZyYWN0aW9uKQoKIyBrYXBwIGVzdGltYXRpb24gZm9yIHRyYW5zcG9ydGVycwprYXBwX3RyYW5zcG9ydCA8LSBkYXRhLmZyYW1lKHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSwKICByZWFjdGlvbl9pZCA9IGMoIlJfSDJPdF9lbnp5bWUiLCAiUl9DTzJ0X2VuenltZSIsICJSX08ydF9lbXp5bWUiLCAiUl9TTzR0X2VuenltZSIsCiAgICAiUl9QSXQycl9lbnp5bWUiLCAiUl9LdHJfZW56eW1lIiwgIlJfTkF0M18xNV9lbnp5bWUiLCAiUl9GRTJhYmNfZW56eW1lIiwKICAgICJSX01HMnRfZW56eW1lIiwgIlJfQ09CQUxUdDVfZW56eW1lIiwgIlJfRk9SdF9lbnp5bWUiLCAiUl9OSDR0X2VuenltZSIsIAogICAgIlJfRlJVYWJjX2VuenltZSIsICJSX1NVQ0N0MnJfZW56eW1lIiwgIlJfRlJVcHRzMl9lbnp5bWUiLCAKICAgICJSX1NVQ0NhYmNfZW56eW1lIiwgIlJfU1VDRlVNdF9lbnp5bWUiKSwKICBrYXBwX2ZvcndhcmQgPSBjKHJlcCgxMDAwMDAwLCAxMSksIHJlcCgxMDAwMDAsIDMpLCByZXAoMCwgMykpCiAgKSAlPiUgbXV0YXRlKGthcHBfcmV2ZXJzZSA9IGthcHBfZm9yd2FyZCkKYGBgCgpUaGUgZmluYWwgY29uc2Vuc3VzIGthcHAgdGFibGUgaXMgcHJlcGFyZWQgYnkgYWRkaW5nIG1pc3Npbmcga2FwcHMgZm9yIHRyYW5zcG9ydCByZWFjdGlvbnMgYW5kIGNvcnJlY3Rpbmcgc2V2ZXJhbCBmYWxzZWx5IGVzdGltYXRlZCBrYXBwcy4gVGhlIHRhYmxlIGlzIGV4cG9ydGVkIGFzIGAqLmNzdmAgZmlsZS4gYFJCQXB5YCByZXF1aXJlcyB0aGUgdGFibGUgaW4gYSBzcGVjaWZpYyBmb3JtYXQ6CgotIHRhYi1zZXBhcmF0ZWQgdmFsdWVzLCBubyBoZWFkZXIKLSB1bml0cyBpbiBgMS9oYCBpbnN0ZWFkIG9mIGAxL3NgICh4IDM2MDApCi0gcmVhY3Rpb24gSUQgaW4gY29sdW1uIDEsIGZvcm1hdCAoYFJfYClgSURfZW56eW1lYCBvciBgSURfdHJhbnNwb3J0ZXJgCi0gbWF4IGs8c3ViPmthdDwvc3ViPiBpbiBjb2x1bW4gMgotIG1pbiBrPHN1Yj5rYXQ8L3N1Yj4gaW4gY29sdW1uIDMgKGJhY2t3YXJkIGVmZmljaWVuY3ksIGNhbiBiZSBpZGVudGljYWwpCi0gbm8gTkEgdmFsdWVzIChvbmx5IGNvbXBsZXRlIHJvd3MpCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQojIFNlbGVjdCBtYXhpbXVtIGFzIGFnZ3JlZ2F0aW9uIG1ldHJpYwpkZl9rYXBwX2V4cG9ydCA8LSBkZl9rYXBwICU+JSBncm91cF9ieShyZWFjdGlvbl9pZCkgJT4lCiAgc3VtbWFyaXplKAogICAga2FwcF9mb3J3YXJkID0gbWF4KGthcHBfZm9yd2FyZCksCiAgICBrYXBwX3JldmVyc2UgPSBtYXgoa2FwcF9yZXZlcnNlKQogICkgJT4lCiAgCiAgIyBhZGQgbWlzc2luZyBrYXBwcwogIGZpbHRlcighKHJlYWN0aW9uX2lkICVpbiUgYygiUl9IRVhmX2VuenltZSIsIlJfRlJVYWJjX2VuenltZSIpKSkgJT4lCiAgYmluZF9yb3dzKGthcHBfbWlzc2luZykgJT4lCiAgYmluZF9yb3dzKGthcHBfdHJhbnNwb3J0KSAlPiUKICAKICAjIHVwZGF0ZSBrYXBwIGVzdGltYXRpb24gZm9yIHNvbWUgcmVhY3Rpb25zIHdoZXJlIGVzdGltYXRpb24gd2FzIGluYWNjdXJhdGUKICBsZWZ0X2pvaW4oZmlsdGVyKGthcHBfZXh0cmEsIHJlYWN0aW9uX2lkICVpbiUgZGZfa2FwcCRyZWFjdGlvbl9pZCkpICU+JQogIG11dGF0ZShyYXRpb19tb2xfZnJhY3Rpb24gPSByZXBsYWNlX25hKHJhdGlvX21vbF9mcmFjdGlvbiwgMSkpICU+JQogIG11dGF0ZSgKICAgIGthcHBfZm9yd2FyZCA9IGthcHBfZm9yd2FyZCAqIHJhdGlvX21vbF9mcmFjdGlvbiwKICAgIGthcHBfcmV2ZXJzZSA9IGthcHBfcmV2ZXJzZSAqIHJhdGlvX21vbF9mcmFjdGlvbgogICkgJT4lCiAgc2VsZWN0KC1yYXRpb19tb2xfZnJhY3Rpb24pCgojIGV4cG9ydCBmaW5hbCByZXN1bHQKd3JpdGVfdHN2KGRmX2thcHBfZXhwb3J0LCAiLi4vZGF0YS9zaW11bGF0aW9uL2thcHBfZml0dGluZy9rYXBwX2NvbnNlbnN1cy5jc3YiLCBjb2xfbmFtZXMgPSBGQUxTRSkKYGBgCgpGaW5hbGx5IHdlIGNhbiBjcmVhdGUgYSBzdW1tYXJ5IGZpZ3VyZSBmb3IgdGhlIHBhcmFtZXRlcnMgY29uc3RyYWluaW5nIHRoZSBSQkEgbW9kZWwuIFRoZSBmaXJzdCBpcyBrPHN1Yj5hcHA8L3N1Yj4gZXN0aW1hdGlvbiBhbmQgdGhlIHR3byBvdGhlciBjb25zdHJhaW50cyBhcmUgcHJvdGVvbWUgbWFzcyBmcmFjdGlvbiBwZXIgY29tcGFydG1lbnQgYW5kIHByb3Rlb21lIG1hc3MgZnJhY3Rpb24gb2YgZW56eW1hdGljIChhbGwgUkJBIG1vZGVsIHJlYWN0aW9ucykgYW5kIG5vbi1lbnp5bWF0aWMgcHJvdGVpbnMsIHBlciBjb21wYXJ0bWVudC4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIGZpZy53aWR0aCA9IDYsIGZpZy5oZWlnaHQgPSA1Ljh9CnByaW50KHBsb3Rfa2FwcF9kaXN0W1syXV0sIHBvc2l0aW9uID0gYygwLCAwLjUxLCAxLCAxKSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfcHJvdF9jb21wLCBwb3NpdGlvbiA9IGMoMCwgMCwgMC41LCAwLjU1KSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfcHJvdF9ORSwgcG9zaXRpb24gPSBjKDAuNSwgMCwgMSwgMC41NSksIG1vcmUgPSBUUlVFKQpncmlkOjpncmlkLnRleHQoYygiQSIsICJCIiwgIkMiKSwgeCA9IGMoMC4wMiwgMC4wMiwgMC41KSwgCiAgeSA9IGMoMC45NywgMC41LCAwLjUpKQpgYGAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CnN2ZygiLi4vZmlndXJlcy9maWd1cmVfbW9kZWxfY29uc3RyYWludHMuc3ZnIiwgd2lkdGggPSA2LCBoZWlnaHQgPSA1LjgpCnByaW50KHBsb3Rfa2FwcF9kaXN0W1syXV0sIHBvc2l0aW9uID0gYygwLCAwLjUxLCAxLCAxKSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfcHJvdF9jb21wLCBwb3NpdGlvbiA9IGMoMCwgMCwgMC41LCAwLjU1KSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfcHJvdF9ORSwgcG9zaXRpb24gPSBjKDAuNSwgMCwgMSwgMC41NSksIG1vcmUgPSBUUlVFKQpncmlkOjpncmlkLnRleHQoYygiQSIsICJCIiwgIkMiKSwgeCA9IGMoMC4wMiwgMC4wMiwgMC41KSwgCiAgeSA9IGMoMC45NywgMC41LCAwLjUpKQpkZXYub2ZmKCkKYGBgCg==