0.1 Description

This R notebook is a bioinformatics pipeline to process and analyze MS based peptide/protein abundance data for the chemolithoautotroph Ralstonia eutropha (a.k.a. Cupriavidus necator).

Proteomics data was obtained using the following work flow (to be added…).

0.2 Libraries

# loading libraries
library(lattice)
library(latticeExtra)
library(latticetools)
library(tidyverse)
library(stringi)

0.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. The main proteomics data is loaded from the R ShinyProt directory that can also be found on github and interactively browsed and searched in the ShinyProt web app.

Reutropha_proteomics <- "../data/input/Ralstonia_eutropha.Rdata"
load(Reutropha_proteomics)

0.4 Overview on detected peptides and proteins

0.4.1 Total number of quantified proteins and missing proteome

In total, the following number of proteins was quantified, out of theoretical total of 6,614 proteins (Uniprot reference genome, July 31 2020). That represents roughly 81 % by number and much more by mass (see below for estimation)

n_quantified_prot <- Ralstonia_eutropha %>% pull(uniprot) %>% unique %>% length
print(n_quantified_prot)
[1] 5357
print(n_quantified_prot/6614*100)
[1] 80.99486

We can also estimate the coverage in terms of protein mass, by assuming that the 1257 missing proteins are of average mass or lower than average mass than the detected proteins. The following simple calculation simulates missing protein mass with an average abundance of the lower quantile of detected proteins. For this purpose we simply pick a standard condition, such as growth on fructose.

quantified_protein <- Ralstonia_eutropha %>%
  
  # pick a certain condition
  filter(substrate == "fructose", growthrate == 0.25) %>%
  pull(mean_intensity)

# determine quantiles of raw quantification intensity
quantified_protein %>% quantile(na.rm = TRUE)
          0%          25%          50%          75%         100% 
7.244750e+04 8.117648e+07 3.395687e+08 1.480863e+09 4.888941e+11 

Now we just simulate that the 1257 non-detected proteins have an average mass similar to that of the protein with 25% lowest abundance. The missing protein abundance will then sum up to less 1% of the total estimated proteome, meaning we can detect more than 99% of the proteome by mass.

missing_protein <- 1257 * quantile(na.rm = TRUE, quantified_protein)[2] %>% as.numeric()
missing_protein_percent <- missing_protein/(missing_protein + sum(quantified_protein, na.rm = TRUE))
paste("missing protein in % total mass:", round(missing_protein_percent*100, 3))
[1] "missing protein in % total mass: 0.88"

0.4.2 Number of quantified peptides per protein

plot_quant_pep <- xyplot(sort(n_peptides, decreasing = TRUE) ~ 
      1:length(protein),
    filter(Ralstonia_eutropha, substrate == "fructose", growthrate == 0.25),
    xlab = "protein", ylab = "n peptides",
    par.settings = custom.lattice, 
    ylim = c(0, 80), xlim = c(0, 5500),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.barplot(x, y, col = NA, fill = grey(0.8), fill_alpha = 1, ewidth = 0.6)
      xhalf = length(unique(x))/2
      panel.lines(x = c(0, xhalf, xhalf), y = c(y[xhalf], y[xhalf], 0), col = 1)
      panel.text(x = 10, y = y[xhalf]*3, pos= 4, cex = 0.8, 
        labels = paste0(round(xhalf), " proteins with >= ", y[xhalf], " peptides"))
    }
  )

print(plot_quant_pep)

0.4.3 Number of quantified peptides per protein, inverted

plot_quant_pep_2 <-  Ralstonia_eutropha %>%
  
  # rearrange n_peptides to have another type of overview
  filter(substrate == "fructose", growthrate == 0.25) %>%
  pull(n_peptides) %>% table %>% as_tibble %>%
      rename(., pep = `.`, prot = n) %>% mutate(pep = as.numeric(pep)) %>%
  
  # plot
  xyplot(prot ~ pep, .,
    xlab = "n peptides", ylab = "n proteins", ylim = c(-5, 1155),
    par.settings = custom.lattice, xlim = c(0, 80),
    panel = function(x, y,...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.barchart(x, y, horizontal = FALSE, box.width = 1,
        border = NA, col = grey(0.8), ...)
    }
  )

print(plot_quant_pep_2)

0.4.4 Number of protein quantifications per replicate

plot_quant_prot1 <- Ralstonia_eutropha %>%
  
  # protein quantifications per replicate
  gather(replicate, raw_intensity, R1:R4) %>%
  group_by(substrate, growthrate, replicate) %>%
  summarize(quant_proteins = sum(!is.na(raw_intensity))) %>%
  
  # and plot
  xyplot(quant_proteins ~ 1:length(quant_proteins), .,
    xlab = "sample", ylab = "quantified proteins",
    par.settings = custom.lattice, 
    ylim = c(0, 5500), xlim = c(0, 81),
      panel = function(x, y,...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.barplot(x, y, col = NA, fill = grey(0.8),
        fill_alpha = 1, ewidth = 0.5)
    }
  )

print(plot_quant_prot1)

0.4.5 Number of proteins quantified in every run

plot_quant_prot2 <- Ralstonia_eutropha %>%
  
  # protein quantifications per replicate
  gather(replicate, raw_intensity, R1:R4) %>%
  group_by(protein) %>%
  summarize(quant_in_runs = sum(!is.na(raw_intensity))) %>%
  pull(quant_in_runs) %>% table %>% enframe %>%
  
  # and plot
  xyplot(value ~ factor(name), .,
    xlab = "in number of runs", 
    ylab = "quantified proteins",
    par.settings = custom.lattice, 
    ylim = c(0, 3000), xlim = c(0, 79),
      panel = function(x, y,...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.barplot(x, y, col = NA, fill = grey(0.8),
        fill_alpha = 1, ewidth = 0.5)
      panel.abline(v = 70, col = 1)
      panel.text(x = 25, y = 1500, pos= 4, cex = 0.8, 
        labels = paste0(sum(y[70:79]), " proteins quantified\nin > 70 out of 80 runs"))
    }
  )

print(plot_quant_prot2)

0.5 Sample overview and quality control

0.5.1 Raw intensity per sample and replicate

Raw intensity here is the dimensionless MS ‘intensity’, that means the quantified area under the curve of MS1 spectra for peptides, summed up per protein. One replicate that was missing for condition Fructose, growth rate 0.1, R2, was temporarily replaced by R1 for this plot, because densityplot was otherwise giving an error message (because of missing values).

densityplot(~ log10(R1) + log10(R2) + log10(R3) + log10(R4) | condition, 
  Ralstonia_eutropha %>% mutate(R2 = case_when(
    condition == "FRC 0.1" ~ R1, TRUE ~ R2)), 
  auto.key = list(columns = 4), layout = c(5, 4), 
  par.settings = custom.colorblind(), xlab = "log10 intensity",
  scales = list(alternating = FALSE), as.table = TRUE,
  panel = function(x, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.superpose(x, ...)
  },
  panel.groups = function(x, ...) {
    panel.densityplot(x, plot.points = FALSE, ...)
    panel.abline(v = median(x, na.rm = TRUE), lty = 2, col = grey(0.5))
  }
)

0.5.2 Variation per sample and replicate

Log 10 median intensity versus log 10 CV.

library(hexbin)
hexbinplot(log10(CV) ~ log10(median_intensity) | condition, 
  Ralstonia_eutropha,
  layout = c(5, 4), 
  par.settings = custom.colorblind(),
  scales = list(alternating = FALSE), aspect = 0.9,
  ylim = c(-3, 0.5),
  colramp = colorRampPalette(custom.lattice()$superpose.polygon$col[3:1])
)

A densityplot of the coeffcient of variation (CV) for the four replicates per protein, broken down by sample. This result shows that the variation is considerably higher than an ‘ideal’ MS-based proteomics experiment, where average CV can be as low as 10%. Here, variation is high as 50%.

densityplot(~ CV | condition,
  Ralstonia_eutropha,
  as.table = TRUE, lwd = 2, 
  par.settings = custom.colorblind(), pch = ".",
  panel = function(x, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.densityplot(x, ...)
    panel.ablineq(v = median(x, na.rm = TRUE), adj = -0.1,
      lty = 2, col = grey(0.5), fontfamily = "FreeSans")
  }
)

0.5.3 Similarity between replicates from identifications

We can determine the overall similarity of samples (replicates and conditions) towards each other. A simple strategy for this is to use PCA or nMDS, the latter is an iterative and therefore not fully deterministic approach as it depends on the starting coordinates. However it is useful to compare how different samples or replicates ‘cluster’ together. Two replicates need to be removed from the data before, one corrupt sample that is missing (FRC_0.1_R2), and one that is an outlier (NLIM_0.05_R1).

The strategy for nMDS is to reshape all measurements per sample into a matrix and compute the ‘distance’ by a default measure. nMDS then tries to arrange each sample as a dot on a plane, taking optimal the distance to its neighbors into account. This approach might not give a perfect result and may contain contradictions indicated by the stress level.

# load required libraries
library(dendextend)
library(vegan)

# first need to rearrange raw data so that we obtain a 'wide' table/matrix
dist_mat <- Ralstonia_eutropha %>% select(uniprot, condition, R1:R4) %>%
  gather(replicate, intensity, R1:R4) %>%
  unite(condition, condition, replicate) %>%
  spread(condition, intensity) %>% 
  
  # remove missing/outlier samples and coerce to matrix
  select(-uniprot, -all_of(c("FRC 0.1_R2", "NLIM 0.05_R1"))) %>%
  as.matrix %>% t

# plot sample similarity as dendrogram
plot_cols <- custom.colorblind()$superpose.polygon$col[1:4]
cluster <- hclust(dist(dist_mat), method = "ward.D2")
plot(color_branches(cluster, col = rep(plot_cols, each = 20)[-c(26, 41)][cluster$order]))

We can see that many replicates cluster nicely together, samples also cluster predominantly by carbon/nitrogen limitation. For example, many samples from formic acid and nitrogen limitation cluster on the left side, and many samples from fructose cluster on the right side.

library(tactile)

# run nMDS analysis
NMDS <- dist_mat %>% dist %>% metaMDS
Run 0 stress 0.1716011 
Run 1 stress 0.1702878 
... New best solution
... Procrustes: rmse 0.03510426  max resid 0.2803567 
Run 2 stress 0.1716398 
Run 3 stress 0.1716012 
Run 4 stress 0.1702877 
... New best solution
... Procrustes: rmse 0.0001494851  max resid 0.001203133 
... Similar to previous best
Run 5 stress 0.1702878 
... Procrustes: rmse 0.0001786291  max resid 0.000834936 
... Similar to previous best
Run 6 stress 0.1698424 
... New best solution
... Procrustes: rmse 0.01435114  max resid 0.1129227 
Run 7 stress 0.1716401 
Run 8 stress 0.2045207 
Run 9 stress 0.1702882 
... Procrustes: rmse 0.01445371  max resid 0.1129753 
Run 10 stress 0.199167 
Run 11 stress 0.1702878 
... Procrustes: rmse 0.01441851  max resid 0.1129971 
Run 12 stress 0.1702881 
... Procrustes: rmse 0.01444127  max resid 0.1129806 
Run 13 stress 0.1716397 
Run 14 stress 0.1702881 
... Procrustes: rmse 0.01444245  max resid 0.1129879 
Run 15 stress 0.1702879 
... Procrustes: rmse 0.01442853  max resid 0.1129939 
Run 16 stress 0.1702879 
... Procrustes: rmse 0.01432394  max resid 0.1131321 
Run 17 stress 0.1716013 
Run 18 stress 0.1716398 
Run 19 stress 0.1702877 
... Procrustes: rmse 0.01439955  max resid 0.1130152 
Run 20 stress 0.1716396 
*** No convergence -- monoMDS stopping criteria:
    20: stress ratio > sratmax
# and plot result
df_nmds <- NMDS$points %>% as_tibble(rownames = "condition") %>%
  separate(condition, into = c("condition", "growth_rate", "replicate"), sep = "[ _]") %>%
  mutate(condition = recode(condition, FA = "formate",
    NLIM = "ammonium", FRC = "fructose", SUC = "succinate")) %>%
  mutate(across(matches("MDS[12]"), function(x) x/10^11)) %>%
  mutate(growth_rate = as.numeric(growth_rate)*7.5)

plot_nmds <- xyplot(MDS2 ~ MDS1, df_nmds,
  groups = condition,
  pch = 19, size = df_nmds$growth_rate, alpha = 0.7,
  par.settings = custom.colorblind(),
  panel = function(x, y, size, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.bubbleplot(x, y, z = size, ...)
    panel.key(..., cex = 0.6, corner = c(0.95, 0.05), points = FALSE)
  }
)

print(plot_nmds)

0.6 Visualizing protein abundance using genome maps

This is subfiure Figure 1 B of the manuscript:

plot_prot_per_chrom <- Ralstonia_eutropha %>% ungroup %>%
  
  # filter for one growth rate and one chromosome only
  filter(growthrate == 0.25) %>%
  
  # add a rolling mean for every condition
  group_by(substrate) %>% mutate(
    roll_massfraction =  zoo::rollapply(
      median_mass_fraction, 5, function(x) mean(x, na.rm = TRUE), 
      partial = TRUE)) %>%
  
  # sort by start position of gene
  arrange(start) %>%
  mutate(seq_type = seq_type %>% str_replace("chromosome", "Chr") %>%
    str_replace("plasmid", "pHG1") %>% factor(., unique(.)[c(2,3,1)])) %>%
  
  xyplot(roll_massfraction*100 ~ start/1000 | seq_type, .,
    par.settings = custom.colorblind(),
    layout = c(1, 3), between = list(x = 0.5, y = 0.5),
    groups = substrate, type = "l", as.table = TRUE, 
    alpha = 0.8, xlab = "genome position [kbp]", ylab = "% protein mass fraction",
    xlim = c(0, 4.05e3), ylim = c(-0.05, 0.55),
    scales = list(alternating = FALSE), 
    strip.left = TRUE, strip = FALSE, 
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.xyplot(x, y, ...)
      panel.key(..., cex = 0.7, points = FALSE, lines = TRUE,
        corner = c(0.99, 0.9), which.panel = 3)
    }
  )

print(plot_prot_per_chrom)

null device 
          1 

0.6.1 Examples of highly expressed genes

First we construct a simple generic plot function to plot different sets of genes.

plot_genes <- function(dat, y_lim, y_lab, key = TRUE) {
  
  xyplot(median_mass_fraction*100 ~ protein, dat,
    par.settings = custom.colorblind(),
    between = list(x = 0.5, y = 0.5),
    error_margin = dat$sd_massfraction*100,
    groups = substrate, as.table = TRUE, lwd = 1.5,
    xlab = "", ylab = y_lab,
    ylim = y_lim,
    scales = list(alternating = FALSE, x = list(rot = 30, cex = 0.6)),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.barplot(x, y, beside = TRUE, ...)
      if (key) panel.key(..., cex = 0.7, pch = 15, 
        corner = c(0.95, 0.95))
    }
  )
}

Then we plot first all CBB genes. This is a special case because the two copies of the CBB operon on chromsome 2 and mega plasmid are virtually identical, regarding the protein sequence. The MS quantification can therefore not differentiate from which gene the detected peptide was expressed. We therefore combine mass fractions for both copies of the CBB operon, and also simply combine the standard deviation measurements (by summing them up).

df_cbb <- Ralstonia_eutropha %>% ungroup %>%
  
  # filter for one growth rate only
  filter(growthrate == 0.25, grepl("cbb", protein)) %>%
  
  # abbreviate gene names and eliminate P(lasmid) and C(hromosomal) suffixes
  mutate(protein = stri_extract_first_regex(protein, "cbb[A-Z0-9]")) %>%
  
  # combine median and sd mass fraction for the 2 copies of each protein
  group_by(protein, substrate) %>%
  summarize(
    median_mass_fraction = sum(median_mass_fraction), 
    sd_massfraction = sum(sd_massfraction)
  ) %>% 
  
  # arrange by order of genes in operon
  mutate(protein = factor(protein, c("cbbB", "cbbA", "cbbK", "cbbG", "cbbZ", 
    "cbbT", "cbbP", "cbbF", "cbbE", "cbbY", "cbbX", "cbbS", "cbbL", "cbbR")))

# plot
plot_cbb <- plot_genes(df_cbb, y_lim = c(-0.04, 4.14), y_lab = "% protein mass fraction")

The other set that would be interesting to look at are (formate de-) hydrogenases, responsible for NADH reduction when Ralstonia grows on hydrogen or formate as energy source. Presumable formate dehydrogenases are present in R. eutropha in several operons mostly on different chromosomes (see Cramm et al., 2008). This folowwing is extracted from the review article:

  • genes for soluble, Mo-dependent S-FDH are organized in an operon of five genes fdsG, -B, -A, -C, -D on chromosome 1
  • genes for another set of S-FDH may be encoded by fdwA and fdwB on chromosome 2 (considerable seq similarity to fds genes)
  • genes for membrane-bound M-FDH are located on chromosome 1 and include three genes, fdhA1 , fdhB1 , and fdhC, which encode a catalytic subunit, an iron-sulfur subunit, and a transmembrane cytochrome b subunit, respectively. An accessory gene fdhD is present in this region. We can also find duplicates of these genes, fdhA2, fdhD2, fdhE on chromosome 2.
  • a second M-FDH gene cluster located on chromosome 2 comprises fdoG, fdoH, and fdoI. The products of these genes show only moderate similarity to the products of fdh.

Interesting side not in Cramm et al.: “S-FDH is formed only in formate-induced cells, M-FDH activity was detectable under various growth conditions [Burgdorf et al., 2001]”

df_fdh <- Ralstonia_eutropha %>% ungroup %>%
  
  # filter for one growth rate
  filter(growthrate == 0.25, grepl("fds|fdw|fdh|fdo", protein)) %>%
  
  # abbreviate gene names and eliminate P(lasmid) and C(hromosomal) suffixes
  mutate(protein = stri_extract_first_regex(protein, "fd[swho][A-Z0-9]+") %>%
    stri_replace_first(replacement = "fdsC", regex = "fdhD$")) %>%
  
  # arrange by order of genes in operon
  ungroup %>% arrange(locus_tag) %>%
  mutate(protein = factor(protein, unique(protein)))
  
# add missing information for fdoG
df_fdh[df_fdh$protein == "fdoG", "seq_type"] <- "chromosome 2"
df_fdh[df_fdh$protein == "fdoG", "strand"] <- "+"
df_fdh[df_fdh$protein == "fdoG", "start"] <- 1626225
df_fdh[df_fdh$protein == "fdoG", "end"] <- 1629314

plot_fdh <- plot_genes(df_fdh, y_lim = c(-0.01, 1.11), y_lab = "% protein mass fraction")

Add small genome map plots for cbb and other operons. For this purpose we construct a generic plotting function for genes as boxed arrows on a genome (line).

plot_genome <- function(df, xlim = NULL) {
  if (!is.null(xlim))
    xscale = list(limits = xlim)
  else 
    xscale = list()
  xyplot(end ~ start, df,
    groups = strand, cex = 0.5, lwd = 1,
    par.settings = custom.colorblind(),
    scales = list(draw = FALSE, x = xscale),
    ylim = c(-3,2), xlab = "", ylab = "",
    gene_strand = df[["strand"]],
    gene_name = df[["protein"]],
    panel = function(x, y, ...) {
      panel.geneplot(x, y, arrows = TRUE, tip = 200, ...)
    }
  )
}

Then we plot the CBB (only one, the chromosomal) and FDH operons (4 in total).

# plot CBB operon on chromosome 2
gene_plot_1 <- Ralstonia_eutropha %>%
filter(start > 1548000, start < 1564000,
  seq_type == "chromosome 2", condition == "FA 0.05") %>%
  
  # trim names again
  mutate(protein = stri_extract_first_regex(protein, "cbb[A-Z0-9]|cfxP")) %>%
  plot_genome(xlim = c(1549500, 1564500))

# plot formate dehydrogenase operons
df_fdh <- filter(df_fdh, !duplicated(protein))
gene_plot_2 <- df_fdh %>% plot_genome(xlim = c(677500, 685600))   # fds operon, S-FDH 1
gene_plot_3 <- df_fdh %>% plot_genome(xlim = c(1930000, 1935200)) # fdw operon, S-FDH 2
gene_plot_4 <- df_fdh %>% plot_genome(xlim = c(3169100, 3175000)) # fdh operon, M-FDH 1
gene_plot_5 <- df_fdh %>% plot_genome(xlim = c(1625500, 1632500)) # fdo operon, M-FDH 2
gene_plot_6 <- df_fdh %>% plot_genome(xlim = c(1648500, 1652300)) # fdh2 operon, M-FDH 1

0.6.2 Expression of hydrogenase operons

Just for interest, we can also have a look at expression of hox and hyp gene operons. HoxFUYH and HoxKGZ genes encode soluble and membrane-bound hydrogenases. Hyp genes are accessory proteins. We can plot main hox operons and their vicinity.

df_hyd <- Ralstonia_eutropha %>%
  filter(seq_type == "plasmid", growthrate == 0.25) %>%
  # arrange by order of genes in operon
  ungroup %>% arrange(locus_tag) %>%
  mutate(protein = factor(protein, unique(protein)))

# hoxFUYH = S-HYD
hyd_plot_1 <- df_hyd %>% filter(start > 75000, start < 86000) %>%
  plot_genes(, y_lim = c(0, 1.3), y_lab = "% protein mass fraction")

# hoxKGZ, MB-HYD
hyd_plot_2 <- df_hyd %>% filter(start > 0, start < 7000) %>%
  plot_genes(, y_lim = c(0, 1.3), y_lab = "% protein mass fraction")

print(hyd_plot_1, position = c(0, 0.45, 1, 1), more = TRUE)
print(hyd_plot_2, position = c(0, 0, 1, 0.55))

0.7 Draft composite figure for proteomics

Supplemental figure 2: proteome coverage

print(plot_quant_prot1, position = c(0, 0.64, 0.45, 1), more = TRUE)
print(plot_quant_pep_2, position = c(0, 0.32, 0.45, 0.68), more = TRUE)
print(plot_nmds, position = c(0.03, 0, 0.45, 0.36), more = TRUE)
print(gene_plot_1, position = c(0.48, 0.8, 1, 0.97), more = TRUE)
print(plot_cbb, position = c(0.41, 0.47, 1.04, 0.85), more = TRUE)
print(gene_plot_2, position = c(0.48, 0.33, 0.8, 0.5), more = TRUE)
print(gene_plot_4, position = c(0.77, 0.33, 1, 0.5), more = TRUE)
print(plot_fdh, position = c(0.39, 0, 1.04, 0.38), more = TRUE)
grid::grid.text(label = c("A", "B", "C", "D", "E"), x = c(0.03, 0.03, 0.03, 0.45, 0.45),
  y = c(0.98, 0.66, 0.33, 0.98, 0.48))

null device 
          1 
LS0tCnRpdGxlOiAiTVMtYmFzZWQgcHJvdGVvbWljcyBmb3IgKlIuIGV1dHJvcGhhKiIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIsICVZJylgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogCiAgICB0aGVtZTogY29zbW8KICAgIHRvYzogeWVzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKLS0tCgoKIyMgRGVzY3JpcHRpb24KClRoaXMgUiBub3RlYm9vayBpcyBhIGJpb2luZm9ybWF0aWNzIHBpcGVsaW5lIHRvICoqcHJvY2VzcyBhbmQgYW5hbHl6ZSBNUyBiYXNlZCBwZXB0aWRlL3Byb3RlaW4gYWJ1bmRhbmNlIGRhdGEqKiBmb3IgdGhlIGNoZW1vbGl0aG9hdXRvdHJvcGggKlJhbHN0b25pYSBldXRyb3BoYSogKGEuay5hLiAqQ3VwcmlhdmlkdXMgbmVjYXRvciopLgoKUHJvdGVvbWljcyBkYXRhIHdhcyBvYnRhaW5lZCB1c2luZyB0aGUgZm9sbG93aW5nIHdvcmsgZmxvdyAodG8gYmUgYWRkZWQuLi4pLgoKCiMjIExpYnJhcmllcwoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KIyBsb2FkaW5nIGxpYnJhcmllcwpsaWJyYXJ5KGxhdHRpY2UpCmxpYnJhcnkobGF0dGljZUV4dHJhKQpsaWJyYXJ5KGxhdHRpY2V0b29scykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc3RyaW5naSkKYGBgCgojIyBEYXRhIGltcG9ydAoKRGVmaW5lIHRoZSBkYXRhIHNvdXJjZSBkaXJlY3Rvcmllcy4gU29tZSBvZiB0aGVtIGFyZSBleHRlcm5hbCBpbiB0aGUgc2Vuc2Ugb2Ygbm90IGluY2x1ZGVkIGluIHRoZSBhY2NvbXBhbnlpbmcgZGF0YSBmb2xkZXIgb2YgdGhpcyBSIG5vdGVib29rLiBUaGUgbWFpbiBwcm90ZW9taWNzIGRhdGEgaXMgbG9hZGVkIGZyb20gdGhlIFIgU2hpbnlQcm90IGRpcmVjdG9yeSB0aGF0IGNhbiBhbHNvIGJlIGZvdW5kIG9uIFtnaXRodWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9tLWphaG4vU2hpbnlQcm90KSBhbmQgaW50ZXJhY3RpdmVseSBicm93c2VkIGFuZCBzZWFyY2hlZCBpbiB0aGUgW1NoaW55UHJvdCB3ZWIgYXBwXShodHRwczovL20tamFobi5zaGlueWFwcHMuaW8vU2hpbnlQcm90LykuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpSZXV0cm9waGFfcHJvdGVvbWljcyA8LSAiLi4vZGF0YS9pbnB1dC9SYWxzdG9uaWFfZXV0cm9waGEuUmRhdGEiCmxvYWQoUmV1dHJvcGhhX3Byb3Rlb21pY3MpCmBgYAoKCiMjIE92ZXJ2aWV3IG9uIGRldGVjdGVkIHBlcHRpZGVzIGFuZCBwcm90ZWlucwoKIyMjIFRvdGFsIG51bWJlciBvZiBxdWFudGlmaWVkIHByb3RlaW5zIGFuZCBtaXNzaW5nIHByb3Rlb21lCgpJbiB0b3RhbCwgdGhlIGZvbGxvd2luZyBudW1iZXIgb2YgcHJvdGVpbnMgd2FzIHF1YW50aWZpZWQsIG91dCBvZiB0aGVvcmV0aWNhbCB0b3RhbCBvZiA2LDYxNCBwcm90ZWlucyAoW1VuaXByb3QgcmVmZXJlbmNlIGdlbm9tZV0oaHR0cHM6Ly93d3cudW5pcHJvdC5vcmcvdW5pcHJvdC8/cXVlcnk9b3JnYW5pc20lM0ElMjJDdXByaWF2aWR1cytuZWNhdG9yKyUyOHN0cmFpbitBVENDKzE3Njk5KyUyRitIMTYrJTJGK0RTTSs0MjgrJTJGK1N0YW5pZXIrMzM3JTI5KyUyOFJhbHN0b25pYStldXRyb3BoYSUyOSslNUIzODE2NjYlNUQlMjIrQU5EK3Byb3Rlb21lJTNBdXAwMDAwMDgyMTAmc29ydD1zY29yZSksIEp1bHkgMzEgMjAyMCkuIFRoYXQgcmVwcmVzZW50cyByb3VnaGx5IDgxICUgYnkgbnVtYmVyIGFuZCBtdWNoIG1vcmUgYnkgbWFzcyAoc2VlIGJlbG93IGZvciBlc3RpbWF0aW9uKQoKYGBge3J9Cm5fcXVhbnRpZmllZF9wcm90IDwtIFJhbHN0b25pYV9ldXRyb3BoYSAlPiUgcHVsbCh1bmlwcm90KSAlPiUgdW5pcXVlICU+JSBsZW5ndGgKcHJpbnQobl9xdWFudGlmaWVkX3Byb3QpCnByaW50KG5fcXVhbnRpZmllZF9wcm90LzY2MTQqMTAwKQpgYGAKCldlIGNhbiBhbHNvIGVzdGltYXRlIHRoZSBjb3ZlcmFnZSBpbiB0ZXJtcyBvZiBwcm90ZWluIG1hc3MsIGJ5IGFzc3VtaW5nIHRoYXQgdGhlIDEyNTcgbWlzc2luZyBwcm90ZWlucyBhcmUgb2YgYXZlcmFnZSBtYXNzIG9yIGxvd2VyIHRoYW4gYXZlcmFnZSBtYXNzIHRoYW4gdGhlIGRldGVjdGVkIHByb3RlaW5zLiBUaGUgZm9sbG93aW5nIHNpbXBsZSBjYWxjdWxhdGlvbiBzaW11bGF0ZXMgbWlzc2luZyBwcm90ZWluIG1hc3Mgd2l0aCBhbiBhdmVyYWdlIGFidW5kYW5jZSBvZiB0aGUgbG93ZXIgcXVhbnRpbGUgb2YgZGV0ZWN0ZWQgcHJvdGVpbnMuIEZvciB0aGlzIHB1cnBvc2Ugd2Ugc2ltcGx5IHBpY2sgYSBzdGFuZGFyZCBjb25kaXRpb24sIHN1Y2ggYXMgZ3Jvd3RoIG9uIGZydWN0b3NlLgoKYGBge3J9CnF1YW50aWZpZWRfcHJvdGVpbiA8LSBSYWxzdG9uaWFfZXV0cm9waGEgJT4lCiAgCiAgIyBwaWNrIGEgY2VydGFpbiBjb25kaXRpb24KICBmaWx0ZXIoc3Vic3RyYXRlID09ICJmcnVjdG9zZSIsIGdyb3d0aHJhdGUgPT0gMC4yNSkgJT4lCiAgcHVsbChtZWFuX2ludGVuc2l0eSkKCiMgZGV0ZXJtaW5lIHF1YW50aWxlcyBvZiByYXcgcXVhbnRpZmljYXRpb24gaW50ZW5zaXR5CnF1YW50aWZpZWRfcHJvdGVpbiAlPiUgcXVhbnRpbGUobmEucm0gPSBUUlVFKQpgYGAKCk5vdyB3ZSBqdXN0IHNpbXVsYXRlIHRoYXQgdGhlIDEyNTcgbm9uLWRldGVjdGVkIHByb3RlaW5zIGhhdmUgYW4gYXZlcmFnZSBtYXNzIHNpbWlsYXIgdG8gdGhhdCBvZiB0aGUgcHJvdGVpbiB3aXRoIDI1JSBsb3dlc3QgYWJ1bmRhbmNlLiBUaGUgbWlzc2luZyBwcm90ZWluIGFidW5kYW5jZSB3aWxsIHRoZW4gc3VtIHVwIHRvIGxlc3MgMSUgb2YgdGhlIHRvdGFsIGVzdGltYXRlZCBwcm90ZW9tZSwgbWVhbmluZyB3ZSBjYW4gZGV0ZWN0IG1vcmUgdGhhbiA5OSUgb2YgdGhlIHByb3Rlb21lIGJ5IG1hc3MuCgpgYGB7cn0KbWlzc2luZ19wcm90ZWluIDwtIDEyNTcgKiBxdWFudGlsZShuYS5ybSA9IFRSVUUsIHF1YW50aWZpZWRfcHJvdGVpbilbMl0gJT4lIGFzLm51bWVyaWMoKQptaXNzaW5nX3Byb3RlaW5fcGVyY2VudCA8LSBtaXNzaW5nX3Byb3RlaW4vKG1pc3NpbmdfcHJvdGVpbiArIHN1bShxdWFudGlmaWVkX3Byb3RlaW4sIG5hLnJtID0gVFJVRSkpCnBhc3RlKCJtaXNzaW5nIHByb3RlaW4gaW4gJSB0b3RhbCBtYXNzOiIsIHJvdW5kKG1pc3NpbmdfcHJvdGVpbl9wZXJjZW50KjEwMCwgMykpCmBgYAoKIyMjIE51bWJlciBvZiBxdWFudGlmaWVkIHBlcHRpZGVzIHBlciBwcm90ZWluCgpgYGB7ciwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDIuNX0KcGxvdF9xdWFudF9wZXAgPC0geHlwbG90KHNvcnQobl9wZXB0aWRlcywgZGVjcmVhc2luZyA9IFRSVUUpIH4gCiAgICAgIDE6bGVuZ3RoKHByb3RlaW4pLAogICAgZmlsdGVyKFJhbHN0b25pYV9ldXRyb3BoYSwgc3Vic3RyYXRlID09ICJmcnVjdG9zZSIsIGdyb3d0aHJhdGUgPT0gMC4yNSksCiAgICB4bGFiID0gInByb3RlaW4iLCB5bGFiID0gIm4gcGVwdGlkZXMiLAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmxhdHRpY2UsIAogICAgeWxpbSA9IGMoMCwgODApLCB4bGltID0gYygwLCA1NTAwKSwKICAgIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgICAgcGFuZWwuYmFycGxvdCh4LCB5LCBjb2wgPSBOQSwgZmlsbCA9IGdyZXkoMC44KSwgZmlsbF9hbHBoYSA9IDEsIGV3aWR0aCA9IDAuNikKICAgICAgeGhhbGYgPSBsZW5ndGgodW5pcXVlKHgpKS8yCiAgICAgIHBhbmVsLmxpbmVzKHggPSBjKDAsIHhoYWxmLCB4aGFsZiksIHkgPSBjKHlbeGhhbGZdLCB5W3hoYWxmXSwgMCksIGNvbCA9IDEpCiAgICAgIHBhbmVsLnRleHQoeCA9IDEwLCB5ID0geVt4aGFsZl0qMywgcG9zPSA0LCBjZXggPSAwLjgsIAogICAgICAgIGxhYmVscyA9IHBhc3RlMChyb3VuZCh4aGFsZiksICIgcHJvdGVpbnMgd2l0aCA+PSAiLCB5W3hoYWxmXSwgIiBwZXB0aWRlcyIpKQogICAgfQogICkKCnByaW50KHBsb3RfcXVhbnRfcGVwKQpgYGAKCiMjIyBOdW1iZXIgb2YgcXVhbnRpZmllZCBwZXB0aWRlcyBwZXIgcHJvdGVpbiwgaW52ZXJ0ZWQKCmBgYHtyLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gMi41fQpwbG90X3F1YW50X3BlcF8yIDwtICBSYWxzdG9uaWFfZXV0cm9waGEgJT4lCiAgCiAgIyByZWFycmFuZ2Ugbl9wZXB0aWRlcyB0byBoYXZlIGFub3RoZXIgdHlwZSBvZiBvdmVydmlldwogIGZpbHRlcihzdWJzdHJhdGUgPT0gImZydWN0b3NlIiwgZ3Jvd3RocmF0ZSA9PSAwLjI1KSAlPiUKICBwdWxsKG5fcGVwdGlkZXMpICU+JSB0YWJsZSAlPiUgYXNfdGliYmxlICU+JQogICAgICByZW5hbWUoLiwgcGVwID0gYC5gLCBwcm90ID0gbikgJT4lIG11dGF0ZShwZXAgPSBhcy5udW1lcmljKHBlcCkpICU+JQogIAogICMgcGxvdAogIHh5cGxvdChwcm90IH4gcGVwLCAuLAogICAgeGxhYiA9ICJuIHBlcHRpZGVzIiwgeWxhYiA9ICJuIHByb3RlaW5zIiwgeWxpbSA9IGMoLTUsIDExNTUpLAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmxhdHRpY2UsIHhsaW0gPSBjKDAsIDgwKSwKICAgIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwuLi4pIHsKICAgICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgICBwYW5lbC5iYXJjaGFydCh4LCB5LCBob3Jpem9udGFsID0gRkFMU0UsIGJveC53aWR0aCA9IDEsCiAgICAgICAgYm9yZGVyID0gTkEsIGNvbCA9IGdyZXkoMC44KSwgLi4uKQogICAgfQogICkKCnByaW50KHBsb3RfcXVhbnRfcGVwXzIpCmBgYAoKCiMjIyBOdW1iZXIgb2YgcHJvdGVpbiBxdWFudGlmaWNhdGlvbnMgcGVyIHJlcGxpY2F0ZQoKYGBge3IsIGZpZy53aWR0aCA9IDUsIGZpZy5oZWlnaHQgPSAyLjUsIG1lc3NhZ2UgPSBGQUxTRX0KcGxvdF9xdWFudF9wcm90MSA8LSBSYWxzdG9uaWFfZXV0cm9waGEgJT4lCiAgCiAgIyBwcm90ZWluIHF1YW50aWZpY2F0aW9ucyBwZXIgcmVwbGljYXRlCiAgZ2F0aGVyKHJlcGxpY2F0ZSwgcmF3X2ludGVuc2l0eSwgUjE6UjQpICU+JQogIGdyb3VwX2J5KHN1YnN0cmF0ZSwgZ3Jvd3RocmF0ZSwgcmVwbGljYXRlKSAlPiUKICBzdW1tYXJpemUocXVhbnRfcHJvdGVpbnMgPSBzdW0oIWlzLm5hKHJhd19pbnRlbnNpdHkpKSkgJT4lCiAgCiAgIyBhbmQgcGxvdAogIHh5cGxvdChxdWFudF9wcm90ZWlucyB+IDE6bGVuZ3RoKHF1YW50X3Byb3RlaW5zKSwgLiwKICAgIHhsYWIgPSAic2FtcGxlIiwgeWxhYiA9ICJxdWFudGlmaWVkIHByb3RlaW5zIiwKICAgIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5sYXR0aWNlLCAKICAgIHlsaW0gPSBjKDAsIDU1MDApLCB4bGltID0gYygwLCA4MSksCiAgICAgIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwuLi4pIHsKICAgICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgICBwYW5lbC5iYXJwbG90KHgsIHksIGNvbCA9IE5BLCBmaWxsID0gZ3JleSgwLjgpLAogICAgICAgIGZpbGxfYWxwaGEgPSAxLCBld2lkdGggPSAwLjUpCiAgICB9CiAgKQoKcHJpbnQocGxvdF9xdWFudF9wcm90MSkKYGBgCgojIyMgTnVtYmVyIG9mIHByb3RlaW5zIHF1YW50aWZpZWQgaW4gZXZlcnkgcnVuCgoKYGBge3IsIGZpZy53aWR0aCA9IDUsIGZpZy5oZWlnaHQgPSAyLjUsIG1lc3NhZ2UgPSBGQUxTRX0KcGxvdF9xdWFudF9wcm90MiA8LSBSYWxzdG9uaWFfZXV0cm9waGEgJT4lCiAgCiAgIyBwcm90ZWluIHF1YW50aWZpY2F0aW9ucyBwZXIgcmVwbGljYXRlCiAgZ2F0aGVyKHJlcGxpY2F0ZSwgcmF3X2ludGVuc2l0eSwgUjE6UjQpICU+JQogIGdyb3VwX2J5KHByb3RlaW4pICU+JQogIHN1bW1hcml6ZShxdWFudF9pbl9ydW5zID0gc3VtKCFpcy5uYShyYXdfaW50ZW5zaXR5KSkpICU+JQogIHB1bGwocXVhbnRfaW5fcnVucykgJT4lIHRhYmxlICU+JSBlbmZyYW1lICU+JQogIAogICMgYW5kIHBsb3QKICB4eXBsb3QodmFsdWUgfiBmYWN0b3IobmFtZSksIC4sCiAgICB4bGFiID0gImluIG51bWJlciBvZiBydW5zIiwgCiAgICB5bGFiID0gInF1YW50aWZpZWQgcHJvdGVpbnMiLAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmxhdHRpY2UsIAogICAgeWxpbSA9IGMoMCwgMzAwMCksIHhsaW0gPSBjKDAsIDc5KSwKICAgICAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LC4uLikgewogICAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICAgIHBhbmVsLmJhcnBsb3QoeCwgeSwgY29sID0gTkEsIGZpbGwgPSBncmV5KDAuOCksCiAgICAgICAgZmlsbF9hbHBoYSA9IDEsIGV3aWR0aCA9IDAuNSkKICAgICAgcGFuZWwuYWJsaW5lKHYgPSA3MCwgY29sID0gMSkKICAgICAgcGFuZWwudGV4dCh4ID0gMjUsIHkgPSAxNTAwLCBwb3M9IDQsIGNleCA9IDAuOCwgCiAgICAgICAgbGFiZWxzID0gcGFzdGUwKHN1bSh5WzcwOjc5XSksICIgcHJvdGVpbnMgcXVhbnRpZmllZFxuaW4gPiA3MCBvdXQgb2YgODAgcnVucyIpKQogICAgfQogICkKCnByaW50KHBsb3RfcXVhbnRfcHJvdDIpCmBgYAoKCiMjIFNhbXBsZSBvdmVydmlldyBhbmQgcXVhbGl0eSBjb250cm9sCgoKIyMjIFJhdyBpbnRlbnNpdHkgcGVyIHNhbXBsZSBhbmQgcmVwbGljYXRlCgpSYXcgaW50ZW5zaXR5IGhlcmUgaXMgdGhlIGRpbWVuc2lvbmxlc3MgTVMgJ2ludGVuc2l0eScsIHRoYXQgbWVhbnMgdGhlIHF1YW50aWZpZWQgYXJlYSB1bmRlciB0aGUgY3VydmUgb2YgTVMxIHNwZWN0cmEgZm9yIHBlcHRpZGVzLCBzdW1tZWQgdXAgcGVyIHByb3RlaW4uIE9uZSByZXBsaWNhdGUgdGhhdCB3YXMgbWlzc2luZyBmb3IgY29uZGl0aW9uIEZydWN0b3NlLCBncm93dGggcmF0ZSAwLjEsIFIyLCB3YXMgdGVtcG9yYXJpbHkgcmVwbGFjZWQgYnkgUjEgZm9yIHRoaXMgcGxvdCwgYmVjYXVzZSBkZW5zaXR5cGxvdCB3YXMgb3RoZXJ3aXNlIGdpdmluZyBhbiBlcnJvciBtZXNzYWdlIChiZWNhdXNlIG9mIG1pc3NpbmcgdmFsdWVzKS4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmRlbnNpdHlwbG90KH4gbG9nMTAoUjEpICsgbG9nMTAoUjIpICsgbG9nMTAoUjMpICsgbG9nMTAoUjQpIHwgY29uZGl0aW9uLCAKICBSYWxzdG9uaWFfZXV0cm9waGEgJT4lIG11dGF0ZShSMiA9IGNhc2Vfd2hlbigKICAgIGNvbmRpdGlvbiA9PSAiRlJDIDAuMSIgfiBSMSwgVFJVRSB+IFIyKSksIAogIGF1dG8ua2V5ID0gbGlzdChjb2x1bW5zID0gNCksIGxheW91dCA9IGMoNSwgNCksIAogIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksIHhsYWIgPSAibG9nMTAgaW50ZW5zaXR5IiwKICBzY2FsZXMgPSBsaXN0KGFsdGVybmF0aW5nID0gRkFMU0UpLCBhcy50YWJsZSA9IFRSVUUsCiAgcGFuZWwgPSBmdW5jdGlvbih4LCAuLi4pIHsKICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgIHBhbmVsLnN1cGVycG9zZSh4LCAuLi4pCiAgfSwKICBwYW5lbC5ncm91cHMgPSBmdW5jdGlvbih4LCAuLi4pIHsKICAgIHBhbmVsLmRlbnNpdHlwbG90KHgsIHBsb3QucG9pbnRzID0gRkFMU0UsIC4uLikKICAgIHBhbmVsLmFibGluZSh2ID0gbWVkaWFuKHgsIG5hLnJtID0gVFJVRSksIGx0eSA9IDIsIGNvbCA9IGdyZXkoMC41KSkKICB9CikKYGBgCgojIyMgVmFyaWF0aW9uIHBlciBzYW1wbGUgYW5kIHJlcGxpY2F0ZQoKTG9nIDEwIG1lZGlhbiBpbnRlbnNpdHkgdmVyc3VzIGxvZyAxMCBDVi4KCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNn0KbGlicmFyeShoZXhiaW4pCmhleGJpbnBsb3QobG9nMTAoQ1YpIH4gbG9nMTAobWVkaWFuX2ludGVuc2l0eSkgfCBjb25kaXRpb24sIAogIFJhbHN0b25pYV9ldXRyb3BoYSwKICBsYXlvdXQgPSBjKDUsIDQpLCAKICBwYXIuc2V0dGluZ3MgPSBjdXN0b20uY29sb3JibGluZCgpLAogIHNjYWxlcyA9IGxpc3QoYWx0ZXJuYXRpbmcgPSBGQUxTRSksIGFzcGVjdCA9IDAuOSwKICB5bGltID0gYygtMywgMC41KSwKICBjb2xyYW1wID0gY29sb3JSYW1wUGFsZXR0ZShjdXN0b20ubGF0dGljZSgpJHN1cGVycG9zZS5wb2x5Z29uJGNvbFszOjFdKQopCmBgYAoKQSBkZW5zaXR5cGxvdCBvZiB0aGUgY29lZmZjaWVudCBvZiB2YXJpYXRpb24gKENWKSBmb3IgdGhlIGZvdXIgcmVwbGljYXRlcyBwZXIgcHJvdGVpbiwgYnJva2VuIGRvd24gYnkgc2FtcGxlLgpUaGlzIHJlc3VsdCBzaG93cyB0aGF0IHRoZSB2YXJpYXRpb24gaXMgY29uc2lkZXJhYmx5IGhpZ2hlciB0aGFuIGFuICdpZGVhbCcgTVMtYmFzZWQgcHJvdGVvbWljcyBleHBlcmltZW50LCB3aGVyZSBhdmVyYWdlIENWIGNhbiBiZSBhcyBsb3cgYXMgMTAlLiBIZXJlLCB2YXJpYXRpb24gaXMgaGlnaCBhcyA1MCUuCgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDZ9CmRlbnNpdHlwbG90KH4gQ1YgfCBjb25kaXRpb24sCiAgUmFsc3RvbmlhX2V1dHJvcGhhLAogIGFzLnRhYmxlID0gVFJVRSwgbHdkID0gMiwgCiAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwgcGNoID0gIi4iLAogIHBhbmVsID0gZnVuY3Rpb24oeCwgLi4uKSB7CiAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICBwYW5lbC5kZW5zaXR5cGxvdCh4LCAuLi4pCiAgICBwYW5lbC5hYmxpbmVxKHYgPSBtZWRpYW4oeCwgbmEucm0gPSBUUlVFKSwgYWRqID0gLTAuMSwKICAgICAgbHR5ID0gMiwgY29sID0gZ3JleSgwLjUpLCBmb250ZmFtaWx5ID0gIkZyZWVTYW5zIikKICB9CikKYGBgCiMjIyBTaW1pbGFyaXR5IGJldHdlZW4gcmVwbGljYXRlcyBmcm9tIGlkZW50aWZpY2F0aW9ucwoKV2UgY2FuIGRldGVybWluZSB0aGUgb3ZlcmFsbCBzaW1pbGFyaXR5IG9mIHNhbXBsZXMgKHJlcGxpY2F0ZXMgYW5kIGNvbmRpdGlvbnMpIHRvd2FyZHMgZWFjaCBvdGhlci4gQSBzaW1wbGUgc3RyYXRlZ3kgZm9yIHRoaXMgaXMgdG8gdXNlICoqUENBKiogb3IgKipuTURTKiosIHRoZSBsYXR0ZXIgaXMgYW4gaXRlcmF0aXZlIGFuZCB0aGVyZWZvcmUgbm90IGZ1bGx5IGRldGVybWluaXN0aWMgYXBwcm9hY2ggYXMgaXQgZGVwZW5kcyBvbiB0aGUgc3RhcnRpbmcgY29vcmRpbmF0ZXMuIEhvd2V2ZXIgaXQgaXMgdXNlZnVsIHRvIGNvbXBhcmUgaG93IGRpZmZlcmVudCBzYW1wbGVzIG9yIHJlcGxpY2F0ZXMgJ2NsdXN0ZXInIHRvZ2V0aGVyLiBUd28gcmVwbGljYXRlcyBuZWVkIHRvIGJlIHJlbW92ZWQgZnJvbSB0aGUgZGF0YSBiZWZvcmUsIG9uZSBjb3JydXB0IHNhbXBsZSB0aGF0IGlzIG1pc3NpbmcgKGBGUkNfMC4xX1IyYCksIGFuZCBvbmUgdGhhdCBpcyBhbiBvdXRsaWVyIChgTkxJTV8wLjA1X1IxYCkuCgpUaGUgc3RyYXRlZ3kgZm9yIG5NRFMgaXMgdG8gcmVzaGFwZSBhbGwgbWVhc3VyZW1lbnRzIHBlciBzYW1wbGUgaW50byBhIG1hdHJpeCBhbmQgY29tcHV0ZSB0aGUgJ2Rpc3RhbmNlJyBieSBhIGRlZmF1bHQgbWVhc3VyZS4gbk1EUyB0aGVuIHRyaWVzIHRvIGFycmFuZ2UgZWFjaCBzYW1wbGUgYXMgYSBkb3Qgb24gYSBwbGFuZSwgdGFraW5nIG9wdGltYWwgdGhlIGRpc3RhbmNlIHRvIGl0cyBuZWlnaGJvcnMgaW50byBhY2NvdW50LiBUaGlzIGFwcHJvYWNoIG1pZ2h0IG5vdCBnaXZlIGEgcGVyZmVjdCByZXN1bHQgYW5kIG1heSBjb250YWluIGNvbnRyYWRpY3Rpb25zIGluZGljYXRlZCBieSB0aGUgYHN0cmVzc2AgbGV2ZWwuCgpgYGB7ciwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA0LCBtZXNzYWdlID0gRkFMU0V9CiMgbG9hZCByZXF1aXJlZCBsaWJyYXJpZXMKbGlicmFyeShkZW5kZXh0ZW5kKQpsaWJyYXJ5KHZlZ2FuKQoKIyBmaXJzdCBuZWVkIHRvIHJlYXJyYW5nZSByYXcgZGF0YSBzbyB0aGF0IHdlIG9idGFpbiBhICd3aWRlJyB0YWJsZS9tYXRyaXgKZGlzdF9tYXQgPC0gUmFsc3RvbmlhX2V1dHJvcGhhICU+JSBzZWxlY3QodW5pcHJvdCwgY29uZGl0aW9uLCBSMTpSNCkgJT4lCiAgZ2F0aGVyKHJlcGxpY2F0ZSwgaW50ZW5zaXR5LCBSMTpSNCkgJT4lCiAgdW5pdGUoY29uZGl0aW9uLCBjb25kaXRpb24sIHJlcGxpY2F0ZSkgJT4lCiAgc3ByZWFkKGNvbmRpdGlvbiwgaW50ZW5zaXR5KSAlPiUgCiAgCiAgIyByZW1vdmUgbWlzc2luZy9vdXRsaWVyIHNhbXBsZXMgYW5kIGNvZXJjZSB0byBtYXRyaXgKICBzZWxlY3QoLXVuaXByb3QsIC1hbGxfb2YoYygiRlJDIDAuMV9SMiIsICJOTElNIDAuMDVfUjEiKSkpICU+JQogIGFzLm1hdHJpeCAlPiUgdAoKIyBwbG90IHNhbXBsZSBzaW1pbGFyaXR5IGFzIGRlbmRyb2dyYW0KcGxvdF9jb2xzIDwtIGN1c3RvbS5jb2xvcmJsaW5kKCkkc3VwZXJwb3NlLnBvbHlnb24kY29sWzE6NF0KY2x1c3RlciA8LSBoY2x1c3QoZGlzdChkaXN0X21hdCksIG1ldGhvZCA9ICJ3YXJkLkQyIikKcGxvdChjb2xvcl9icmFuY2hlcyhjbHVzdGVyLCBjb2wgPSByZXAocGxvdF9jb2xzLCBlYWNoID0gMjApWy1jKDI2LCA0MSldW2NsdXN0ZXIkb3JkZXJdKSkKYGBgCgpXZSBjYW4gc2VlIHRoYXQgbWFueSByZXBsaWNhdGVzIGNsdXN0ZXIgbmljZWx5IHRvZ2V0aGVyLCBzYW1wbGVzIGFsc28gY2x1c3RlciBwcmVkb21pbmFudGx5IGJ5IGNhcmJvbi9uaXRyb2dlbiBsaW1pdGF0aW9uLiBGb3IgZXhhbXBsZSwgbWFueSBzYW1wbGVzIGZyb20gZm9ybWljIGFjaWQgYW5kIG5pdHJvZ2VuIGxpbWl0YXRpb24gY2x1c3RlciBvbiB0aGUgbGVmdCBzaWRlLCBhbmQgbWFueSBzYW1wbGVzIGZyb20gZnJ1Y3Rvc2UgY2x1c3RlciBvbiB0aGUgcmlnaHQgc2lkZS4KCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gNCwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KHRhY3RpbGUpCgojIHJ1biBuTURTIGFuYWx5c2lzCk5NRFMgPC0gZGlzdF9tYXQgJT4lIGRpc3QgJT4lIG1ldGFNRFMKCiMgYW5kIHBsb3QgcmVzdWx0CmRmX25tZHMgPC0gTk1EUyRwb2ludHMgJT4lIGFzX3RpYmJsZShyb3duYW1lcyA9ICJjb25kaXRpb24iKSAlPiUKICBzZXBhcmF0ZShjb25kaXRpb24sIGludG8gPSBjKCJjb25kaXRpb24iLCAiZ3Jvd3RoX3JhdGUiLCAicmVwbGljYXRlIiksIHNlcCA9ICJbIF9dIikgJT4lCiAgbXV0YXRlKGNvbmRpdGlvbiA9IHJlY29kZShjb25kaXRpb24sIEZBID0gImZvcm1hdGUiLAogICAgTkxJTSA9ICJhbW1vbml1bSIsIEZSQyA9ICJmcnVjdG9zZSIsIFNVQyA9ICJzdWNjaW5hdGUiKSkgJT4lCiAgbXV0YXRlKGFjcm9zcyhtYXRjaGVzKCJNRFNbMTJdIiksIGZ1bmN0aW9uKHgpIHgvMTBeMTEpKSAlPiUKICBtdXRhdGUoZ3Jvd3RoX3JhdGUgPSBhcy5udW1lcmljKGdyb3d0aF9yYXRlKSo3LjUpCgpwbG90X25tZHMgPC0geHlwbG90KE1EUzIgfiBNRFMxLCBkZl9ubWRzLAogIGdyb3VwcyA9IGNvbmRpdGlvbiwKICBwY2ggPSAxOSwgc2l6ZSA9IGRmX25tZHMkZ3Jvd3RoX3JhdGUsIGFscGhhID0gMC43LAogIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksCiAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCBzaXplLCAuLi4pIHsKICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgIHBhbmVsLmJ1YmJsZXBsb3QoeCwgeSwgeiA9IHNpemUsIC4uLikKICAgIHBhbmVsLmtleSguLi4sIGNleCA9IDAuNiwgY29ybmVyID0gYygwLjk1LCAwLjA1KSwgcG9pbnRzID0gRkFMU0UpCiAgfQopCgpwcmludChwbG90X25tZHMpCmBgYAoKIyMgVmlzdWFsaXppbmcgcHJvdGVpbiBhYnVuZGFuY2UgdXNpbmcgZ2Vub21lIG1hcHMKClRoaXMgaXMgc3ViZml1cmUgRmlndXJlIDEgQiBvZiB0aGUgbWFudXNjcmlwdDoKCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gMy4yfQpwbG90X3Byb3RfcGVyX2Nocm9tIDwtIFJhbHN0b25pYV9ldXRyb3BoYSAlPiUgdW5ncm91cCAlPiUKICAKICAjIGZpbHRlciBmb3Igb25lIGdyb3d0aCByYXRlIGFuZCBvbmUgY2hyb21vc29tZSBvbmx5CiAgZmlsdGVyKGdyb3d0aHJhdGUgPT0gMC4yNSkgJT4lCiAgCiAgIyBhZGQgYSByb2xsaW5nIG1lYW4gZm9yIGV2ZXJ5IGNvbmRpdGlvbgogIGdyb3VwX2J5KHN1YnN0cmF0ZSkgJT4lIG11dGF0ZSgKICAgIHJvbGxfbWFzc2ZyYWN0aW9uID0gIHpvbzo6cm9sbGFwcGx5KAogICAgICBtZWRpYW5fbWFzc19mcmFjdGlvbiwgNSwgZnVuY3Rpb24oeCkgbWVhbih4LCBuYS5ybSA9IFRSVUUpLCAKICAgICAgcGFydGlhbCA9IFRSVUUpKSAlPiUKICAKICAjIHNvcnQgYnkgc3RhcnQgcG9zaXRpb24gb2YgZ2VuZQogIGFycmFuZ2Uoc3RhcnQpICU+JQogIG11dGF0ZShzZXFfdHlwZSA9IHNlcV90eXBlICU+JSBzdHJfcmVwbGFjZSgiY2hyb21vc29tZSIsICJDaHIiKSAlPiUKICAgIHN0cl9yZXBsYWNlKCJwbGFzbWlkIiwgInBIRzEiKSAlPiUgZmFjdG9yKC4sIHVuaXF1ZSguKVtjKDIsMywxKV0pKSAlPiUKICAKICB4eXBsb3Qocm9sbF9tYXNzZnJhY3Rpb24qMTAwIH4gc3RhcnQvMTAwMCB8IHNlcV90eXBlLCAuLAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwKICAgIGxheW91dCA9IGMoMSwgMyksIGJldHdlZW4gPSBsaXN0KHggPSAwLjUsIHkgPSAwLjUpLAogICAgZ3JvdXBzID0gc3Vic3RyYXRlLCB0eXBlID0gImwiLCBhcy50YWJsZSA9IFRSVUUsIAogICAgYWxwaGEgPSAwLjgsIHhsYWIgPSAiZ2Vub21lIHBvc2l0aW9uIFtrYnBdIiwgeWxhYiA9ICIlIHByb3RlaW4gbWFzcyBmcmFjdGlvbiIsCiAgICB4bGltID0gYygwLCA0LjA1ZTMpLCB5bGltID0gYygtMC4wNSwgMC41NSksCiAgICBzY2FsZXMgPSBsaXN0KGFsdGVybmF0aW5nID0gRkFMU0UpLCAKICAgIHN0cmlwLmxlZnQgPSBUUlVFLCBzdHJpcCA9IEZBTFNFLCAKICAgIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgICAgcGFuZWwueHlwbG90KHgsIHksIC4uLikKICAgICAgcGFuZWwua2V5KC4uLiwgY2V4ID0gMC43LCBwb2ludHMgPSBGQUxTRSwgbGluZXMgPSBUUlVFLAogICAgICAgIGNvcm5lciA9IGMoMC45OSwgMC45KSwgd2hpY2gucGFuZWwgPSAzKQogICAgfQogICkKCnByaW50KHBsb3RfcHJvdF9wZXJfY2hyb20pCmBgYApgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCBlY2hvID0gRkFMU0V9CnN2ZygiLi4vZmlndXJlcy9maWd1cmVfcHJvdF9vdmVydmlldy5zdmciLCB3aWR0aCA9IDguMiwgaGVpZ2h0ID0gMy4wKQpwcmludChwbG90X3Byb3RfcGVyX2Nocm9tKQpkZXYub2ZmKCkKYGBgCgojIyMgRXhhbXBsZXMgb2YgaGlnaGx5IGV4cHJlc3NlZCBnZW5lcwoKRmlyc3Qgd2UgY29uc3RydWN0IGEgc2ltcGxlIGdlbmVyaWMgcGxvdCBmdW5jdGlvbiB0byBwbG90IGRpZmZlcmVudCBzZXRzIG9mIGdlbmVzLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KcGxvdF9nZW5lcyA8LSBmdW5jdGlvbihkYXQsIHlfbGltLCB5X2xhYiwga2V5ID0gVFJVRSkgewogIAogIHh5cGxvdChtZWRpYW5fbWFzc19mcmFjdGlvbioxMDAgfiBwcm90ZWluLCBkYXQsCiAgICBwYXIuc2V0dGluZ3MgPSBjdXN0b20uY29sb3JibGluZCgpLAogICAgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksCiAgICBlcnJvcl9tYXJnaW4gPSBkYXQkc2RfbWFzc2ZyYWN0aW9uKjEwMCwKICAgIGdyb3VwcyA9IHN1YnN0cmF0ZSwgYXMudGFibGUgPSBUUlVFLCBsd2QgPSAxLjUsCiAgICB4bGFiID0gIiIsIHlsYWIgPSB5X2xhYiwKICAgIHlsaW0gPSB5X2xpbSwKICAgIHNjYWxlcyA9IGxpc3QoYWx0ZXJuYXRpbmcgPSBGQUxTRSwgeCA9IGxpc3Qocm90ID0gMzAsIGNleCA9IDAuNikpLAogICAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCAuLi4pIHsKICAgICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgICBwYW5lbC5iYXJwbG90KHgsIHksIGJlc2lkZSA9IFRSVUUsIC4uLikKICAgICAgaWYgKGtleSkgcGFuZWwua2V5KC4uLiwgY2V4ID0gMC43LCBwY2ggPSAxNSwgCiAgICAgICAgY29ybmVyID0gYygwLjk1LCAwLjk1KSkKICAgIH0KICApCn0KYGBgCgotLS0tLS0tLS0tCgpUaGVuIHdlIHBsb3QgZmlyc3QgYWxsIENCQiBnZW5lcy4gVGhpcyBpcyBhIHNwZWNpYWwgY2FzZSBiZWNhdXNlIHRoZSB0d28gY29waWVzIG9mIHRoZSBDQkIgb3Blcm9uIG9uIGNocm9tc29tZSAyIGFuZCBtZWdhIHBsYXNtaWQgYXJlIHZpcnR1YWxseSBpZGVudGljYWwsIHJlZ2FyZGluZyB0aGUgcHJvdGVpbiBzZXF1ZW5jZS4gVGhlIE1TIHF1YW50aWZpY2F0aW9uIGNhbiB0aGVyZWZvcmUgbm90IGRpZmZlcmVudGlhdGUgZnJvbSB3aGljaCBnZW5lIHRoZSBkZXRlY3RlZCBwZXB0aWRlIHdhcyBleHByZXNzZWQuIFdlIHRoZXJlZm9yZSBjb21iaW5lIG1hc3MgZnJhY3Rpb25zIGZvciBib3RoIGNvcGllcyBvZiB0aGUgQ0JCIG9wZXJvbiwgYW5kIGFsc28gc2ltcGx5IGNvbWJpbmUgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBtZWFzdXJlbWVudHMgKGJ5IHN1bW1pbmcgdGhlbSB1cCkuCgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KZGZfY2JiIDwtIFJhbHN0b25pYV9ldXRyb3BoYSAlPiUgdW5ncm91cCAlPiUKICAKICAjIGZpbHRlciBmb3Igb25lIGdyb3d0aCByYXRlIG9ubHkKICBmaWx0ZXIoZ3Jvd3RocmF0ZSA9PSAwLjI1LCBncmVwbCgiY2JiIiwgcHJvdGVpbikpICU+JQogIAogICMgYWJicmV2aWF0ZSBnZW5lIG5hbWVzIGFuZCBlbGltaW5hdGUgUChsYXNtaWQpIGFuZCBDKGhyb21vc29tYWwpIHN1ZmZpeGVzCiAgbXV0YXRlKHByb3RlaW4gPSBzdHJpX2V4dHJhY3RfZmlyc3RfcmVnZXgocHJvdGVpbiwgImNiYltBLVowLTldIikpICU+JQogIAogICMgY29tYmluZSBtZWRpYW4gYW5kIHNkIG1hc3MgZnJhY3Rpb24gZm9yIHRoZSAyIGNvcGllcyBvZiBlYWNoIHByb3RlaW4KICBncm91cF9ieShwcm90ZWluLCBzdWJzdHJhdGUpICU+JQogIHN1bW1hcml6ZSgKICAgIG1lZGlhbl9tYXNzX2ZyYWN0aW9uID0gc3VtKG1lZGlhbl9tYXNzX2ZyYWN0aW9uKSwgCiAgICBzZF9tYXNzZnJhY3Rpb24gPSBzdW0oc2RfbWFzc2ZyYWN0aW9uKQogICkgJT4lIAogIAogICMgYXJyYW5nZSBieSBvcmRlciBvZiBnZW5lcyBpbiBvcGVyb24KICBtdXRhdGUocHJvdGVpbiA9IGZhY3Rvcihwcm90ZWluLCBjKCJjYmJCIiwgImNiYkEiLCAiY2JiSyIsICJjYmJHIiwgImNiYloiLCAKICAgICJjYmJUIiwgImNiYlAiLCAiY2JiRiIsICJjYmJFIiwgImNiYlkiLCAiY2JiWCIsICJjYmJTIiwgImNiYkwiLCAiY2JiUiIpKSkKCiMgcGxvdApwbG90X2NiYiA8LSBwbG90X2dlbmVzKGRmX2NiYiwgeV9saW0gPSBjKC0wLjA0LCA0LjE0KSwgeV9sYWIgPSAiJSBwcm90ZWluIG1hc3MgZnJhY3Rpb24iKQpgYGAKCi0tLS0tLS0tLS0KClRoZSBvdGhlciBzZXQgdGhhdCB3b3VsZCBiZSBpbnRlcmVzdGluZyB0byBsb29rIGF0IGFyZSAoZm9ybWF0ZSBkZS0pIGh5ZHJvZ2VuYXNlcywgcmVzcG9uc2libGUgZm9yIE5BREggcmVkdWN0aW9uIHdoZW4gKlJhbHN0b25pYSogZ3Jvd3Mgb24gaHlkcm9nZW4gb3IgZm9ybWF0ZSBhcyBlbmVyZ3kgc291cmNlLiBQcmVzdW1hYmxlIGZvcm1hdGUgZGVoeWRyb2dlbmFzZXMgYXJlIHByZXNlbnQgaW4gKlIuIGV1dHJvcGhhKiBpbiBzZXZlcmFsIG9wZXJvbnMgbW9zdGx5IG9uIGRpZmZlcmVudCBjaHJvbW9zb21lcyAoc2VlIENyYW1tIGV0IGFsLiwgMjAwOCkuIFRoaXMgZm9sb3d3aW5nIGlzIGV4dHJhY3RlZCBmcm9tIHRoZSByZXZpZXcgYXJ0aWNsZToKCi0gZ2VuZXMgZm9yIHNvbHVibGUsIE1vLWRlcGVuZGVudCBTLUZESCBhcmUgb3JnYW5pemVkIGluIGFuIG9wZXJvbiBvZiBmaXZlIGdlbmVzIGZkc0csIC1CLCAtQSwgLUMsIC1EIG9uIGNocm9tb3NvbWUgMQotIGdlbmVzIGZvciBhbm90aGVyIHNldCBvZiBTLUZESCBtYXkgYmUgZW5jb2RlZCBieSBmZHdBIGFuZCBmZHdCIG9uIGNocm9tb3NvbWUgMiAoY29uc2lkZXJhYmxlIHNlcSBzaW1pbGFyaXR5IHRvIGZkcyBnZW5lcykKLSBnZW5lcyBmb3IgbWVtYnJhbmUtYm91bmQgTS1GREggYXJlIGxvY2F0ZWQgb24gY2hyb21vc29tZSAxIGFuZCBpbmNsdWRlIHRocmVlIGdlbmVzLCBmZGhBMSAsIGZkaEIxICwgYW5kIGZkaEMsIHdoaWNoIGVuY29kZSBhIGNhdGFseXRpYyBzdWJ1bml0LCBhbiBpcm9uLXN1bGZ1ciBzdWJ1bml0LCBhbmQgYSB0cmFuc21lbWJyYW5lIGN5dG9jaHJvbWUgYiBzdWJ1bml0LCByZXNwZWN0aXZlbHkuIEFuIGFjY2Vzc29yeSBnZW5lIGZkaEQgaXMgcHJlc2VudCBpbiB0aGlzIHJlZ2lvbi4gV2UgY2FuIGFsc28gZmluZCBkdXBsaWNhdGVzIG9mIHRoZXNlIGdlbmVzLCBmZGhBMiwgZmRoRDIsIGZkaEUgb24gY2hyb21vc29tZSAyLgotIGEgc2Vjb25kIE0tRkRIIGdlbmUgY2x1c3RlciBsb2NhdGVkIG9uIGNocm9tb3NvbWUgMiBjb21wcmlzZXMgZmRvRywgZmRvSCwgYW5kIGZkb0kuIFRoZSBwcm9kdWN0cyBvZiB0aGVzZSBnZW5lcyBzaG93IG9ubHkgbW9kZXJhdGUgc2ltaWxhcml0eSB0byB0aGUgcHJvZHVjdHMgb2YgZmRoLgoKSW50ZXJlc3Rpbmcgc2lkZSBub3QgaW4gKkNyYW1tIGV0IGFsLio6ICJTLUZESCBpcyBmb3JtZWQgb25seSBpbiBmb3JtYXRlLWluZHVjZWQgY2VsbHMsIE0tRkRIIGFjdGl2aXR5IHdhcyBkZXRlY3RhYmxlIHVuZGVyIHZhcmlvdXMgZ3Jvd3RoIGNvbmRpdGlvbnMgW0J1cmdkb3JmIGV0IGFsLiwgMjAwMV0iCgpgYGB7cn0KZGZfZmRoIDwtIFJhbHN0b25pYV9ldXRyb3BoYSAlPiUgdW5ncm91cCAlPiUKICAKICAjIGZpbHRlciBmb3Igb25lIGdyb3d0aCByYXRlCiAgZmlsdGVyKGdyb3d0aHJhdGUgPT0gMC4yNSwgZ3JlcGwoImZkc3xmZHd8ZmRofGZkbyIsIHByb3RlaW4pKSAlPiUKICAKICAjIGFiYnJldmlhdGUgZ2VuZSBuYW1lcyBhbmQgZWxpbWluYXRlIFAobGFzbWlkKSBhbmQgQyhocm9tb3NvbWFsKSBzdWZmaXhlcwogIG11dGF0ZShwcm90ZWluID0gc3RyaV9leHRyYWN0X2ZpcnN0X3JlZ2V4KHByb3RlaW4sICJmZFtzd2hvXVtBLVowLTldKyIpICU+JQogICAgc3RyaV9yZXBsYWNlX2ZpcnN0KHJlcGxhY2VtZW50ID0gImZkc0MiLCByZWdleCA9ICJmZGhEJCIpKSAlPiUKICAKICAjIGFycmFuZ2UgYnkgb3JkZXIgb2YgZ2VuZXMgaW4gb3Blcm9uCiAgdW5ncm91cCAlPiUgYXJyYW5nZShsb2N1c190YWcpICU+JQogIG11dGF0ZShwcm90ZWluID0gZmFjdG9yKHByb3RlaW4sIHVuaXF1ZShwcm90ZWluKSkpCiAgCiMgYWRkIG1pc3NpbmcgaW5mb3JtYXRpb24gZm9yIGZkb0cKZGZfZmRoW2RmX2ZkaCRwcm90ZWluID09ICJmZG9HIiwgInNlcV90eXBlIl0gPC0gImNocm9tb3NvbWUgMiIKZGZfZmRoW2RmX2ZkaCRwcm90ZWluID09ICJmZG9HIiwgInN0cmFuZCJdIDwtICIrIgpkZl9mZGhbZGZfZmRoJHByb3RlaW4gPT0gImZkb0ciLCAic3RhcnQiXSA8LSAxNjI2MjI1CmRmX2ZkaFtkZl9mZGgkcHJvdGVpbiA9PSAiZmRvRyIsICJlbmQiXSA8LSAxNjI5MzE0CgpwbG90X2ZkaCA8LSBwbG90X2dlbmVzKGRmX2ZkaCwgeV9saW0gPSBjKC0wLjAxLCAxLjExKSwgeV9sYWIgPSAiJSBwcm90ZWluIG1hc3MgZnJhY3Rpb24iKQpgYGAKCkFkZCBzbWFsbCBnZW5vbWUgbWFwIHBsb3RzIGZvciBjYmIgYW5kIG90aGVyIG9wZXJvbnMuIEZvciB0aGlzIHB1cnBvc2Ugd2UgY29uc3RydWN0IGEgZ2VuZXJpYyBwbG90dGluZyBmdW5jdGlvbiBmb3IgZ2VuZXMgYXMgYm94ZWQgYXJyb3dzIG9uIGEgZ2Vub21lIChsaW5lKS4gCgpgYGB7ciwgZmlnLndpZHRoID0gNiwgZmlnLmhlaWdodCA9IDJ9CnBsb3RfZ2Vub21lIDwtIGZ1bmN0aW9uKGRmLCB4bGltID0gTlVMTCkgewogIGlmICghaXMubnVsbCh4bGltKSkKICAgIHhzY2FsZSA9IGxpc3QobGltaXRzID0geGxpbSkKICBlbHNlIAogICAgeHNjYWxlID0gbGlzdCgpCiAgeHlwbG90KGVuZCB+IHN0YXJ0LCBkZiwKICAgIGdyb3VwcyA9IHN0cmFuZCwgY2V4ID0gMC41LCBsd2QgPSAxLAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwKICAgIHNjYWxlcyA9IGxpc3QoZHJhdyA9IEZBTFNFLCB4ID0geHNjYWxlKSwKICAgIHlsaW0gPSBjKC0zLDIpLCB4bGFiID0gIiIsIHlsYWIgPSAiIiwKICAgIGdlbmVfc3RyYW5kID0gZGZbWyJzdHJhbmQiXV0sCiAgICBnZW5lX25hbWUgPSBkZltbInByb3RlaW4iXV0sCiAgICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgICBwYW5lbC5nZW5lcGxvdCh4LCB5LCBhcnJvd3MgPSBUUlVFLCB0aXAgPSAyMDAsIC4uLikKICAgIH0KICApCn0KYGBgCgpUaGVuIHdlIHBsb3QgdGhlIENCQiAob25seSBvbmUsIHRoZSBjaHJvbW9zb21hbCkgYW5kIEZESCBvcGVyb25zICg0IGluIHRvdGFsKS4KCmBgYHtyLCBmaWcud2lkdGggPSA2LCBmaWcuaGVpZ2h0ID0gMn0KIyBwbG90IENCQiBvcGVyb24gb24gY2hyb21vc29tZSAyCmdlbmVfcGxvdF8xIDwtIFJhbHN0b25pYV9ldXRyb3BoYSAlPiUKZmlsdGVyKHN0YXJ0ID4gMTU0ODAwMCwgc3RhcnQgPCAxNTY0MDAwLAogIHNlcV90eXBlID09ICJjaHJvbW9zb21lIDIiLCBjb25kaXRpb24gPT0gIkZBIDAuMDUiKSAlPiUKICAKICAjIHRyaW0gbmFtZXMgYWdhaW4KICBtdXRhdGUocHJvdGVpbiA9IHN0cmlfZXh0cmFjdF9maXJzdF9yZWdleChwcm90ZWluLCAiY2JiW0EtWjAtOV18Y2Z4UCIpKSAlPiUKICBwbG90X2dlbm9tZSh4bGltID0gYygxNTQ5NTAwLCAxNTY0NTAwKSkKCiMgcGxvdCBmb3JtYXRlIGRlaHlkcm9nZW5hc2Ugb3Blcm9ucwpkZl9mZGggPC0gZmlsdGVyKGRmX2ZkaCwgIWR1cGxpY2F0ZWQocHJvdGVpbikpCmdlbmVfcGxvdF8yIDwtIGRmX2ZkaCAlPiUgcGxvdF9nZW5vbWUoeGxpbSA9IGMoNjc3NTAwLCA2ODU2MDApKSAgICMgZmRzIG9wZXJvbiwgUy1GREggMQpnZW5lX3Bsb3RfMyA8LSBkZl9mZGggJT4lIHBsb3RfZ2Vub21lKHhsaW0gPSBjKDE5MzAwMDAsIDE5MzUyMDApKSAjIGZkdyBvcGVyb24sIFMtRkRIIDIKZ2VuZV9wbG90XzQgPC0gZGZfZmRoICU+JSBwbG90X2dlbm9tZSh4bGltID0gYygzMTY5MTAwLCAzMTc1MDAwKSkgIyBmZGggb3Blcm9uLCBNLUZESCAxCmdlbmVfcGxvdF81IDwtIGRmX2ZkaCAlPiUgcGxvdF9nZW5vbWUoeGxpbSA9IGMoMTYyNTUwMCwgMTYzMjUwMCkpICMgZmRvIG9wZXJvbiwgTS1GREggMgpnZW5lX3Bsb3RfNiA8LSBkZl9mZGggJT4lIHBsb3RfZ2Vub21lKHhsaW0gPSBjKDE2NDg1MDAsIDE2NTIzMDApKSAjIGZkaDIgb3Blcm9uLCBNLUZESCAxCmBgYAoKIyMjIEV4cHJlc3Npb24gb2YgaHlkcm9nZW5hc2Ugb3Blcm9ucwoKSnVzdCBmb3IgaW50ZXJlc3QsIHdlIGNhbiBhbHNvIGhhdmUgYSBsb29rIGF0IGV4cHJlc3Npb24gb2YgX2hveF8gYW5kIF9oeXBfIGdlbmUgb3Blcm9ucy4gX0hveEZVWUhfIGFuZCBfSG94S0daXyBnZW5lcyBlbmNvZGUgc29sdWJsZSBhbmQgbWVtYnJhbmUtYm91bmQgaHlkcm9nZW5hc2VzLiBIeXAgZ2VuZXMgYXJlIGFjY2Vzc29yeSBwcm90ZWlucy4gV2UgY2FuIHBsb3QgbWFpbiBob3ggb3Blcm9ucyBhbmQgdGhlaXIgdmljaW5pdHkuCgpgYGB7ciwgZmlnLndpZHRoID0gNiwgZmlnLmhlaWdodCA9IDZ9CmRmX2h5ZCA8LSBSYWxzdG9uaWFfZXV0cm9waGEgJT4lCiAgZmlsdGVyKHNlcV90eXBlID09ICJwbGFzbWlkIiwgZ3Jvd3RocmF0ZSA9PSAwLjI1KSAlPiUKICAjIGFycmFuZ2UgYnkgb3JkZXIgb2YgZ2VuZXMgaW4gb3Blcm9uCiAgdW5ncm91cCAlPiUgYXJyYW5nZShsb2N1c190YWcpICU+JQogIG11dGF0ZShwcm90ZWluID0gZmFjdG9yKHByb3RlaW4sIHVuaXF1ZShwcm90ZWluKSkpCgojIGhveEZVWUggPSBTLUhZRApoeWRfcGxvdF8xIDwtIGRmX2h5ZCAlPiUgZmlsdGVyKHN0YXJ0ID4gNzUwMDAsIHN0YXJ0IDwgODYwMDApICU+JQogIHBsb3RfZ2VuZXMoLCB5X2xpbSA9IGMoMCwgMS4zKSwgeV9sYWIgPSAiJSBwcm90ZWluIG1hc3MgZnJhY3Rpb24iKQoKIyBob3hLR1osIE1CLUhZRApoeWRfcGxvdF8yIDwtIGRmX2h5ZCAlPiUgZmlsdGVyKHN0YXJ0ID4gMCwgc3RhcnQgPCA3MDAwKSAlPiUKICBwbG90X2dlbmVzKCwgeV9saW0gPSBjKDAsIDEuMyksIHlfbGFiID0gIiUgcHJvdGVpbiBtYXNzIGZyYWN0aW9uIikKCnByaW50KGh5ZF9wbG90XzEsIHBvc2l0aW9uID0gYygwLCAwLjQ1LCAxLCAxKSwgbW9yZSA9IFRSVUUpCnByaW50KGh5ZF9wbG90XzIsIHBvc2l0aW9uID0gYygwLCAwLCAxLCAwLjU1KSkKYGBgCgoKIyMgRHJhZnQgY29tcG9zaXRlIGZpZ3VyZSBmb3IgcHJvdGVvbWljcwoKKipTdXBwbGVtZW50YWwgZmlndXJlIDI6IHByb3Rlb21lIGNvdmVyYWdlKioKCmBgYHtyLCBmaWcud2lkdGggPSA1LjUsIGZpZy5oZWlnaHQgPSA2LjUsIG1lc3NhZ2UgPSBGQUxTRX0KcHJpbnQocGxvdF9xdWFudF9wcm90MSwgcG9zaXRpb24gPSBjKDAsIDAuNjQsIDAuNDUsIDEpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9xdWFudF9wZXBfMiwgcG9zaXRpb24gPSBjKDAsIDAuMzIsIDAuNDUsIDAuNjgpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9ubWRzLCBwb3NpdGlvbiA9IGMoMC4wMywgMCwgMC40NSwgMC4zNiksIG1vcmUgPSBUUlVFKQpwcmludChnZW5lX3Bsb3RfMSwgcG9zaXRpb24gPSBjKDAuNDgsIDAuOCwgMSwgMC45NyksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X2NiYiwgcG9zaXRpb24gPSBjKDAuNDEsIDAuNDcsIDEuMDQsIDAuODUpLCBtb3JlID0gVFJVRSkKcHJpbnQoZ2VuZV9wbG90XzIsIHBvc2l0aW9uID0gYygwLjQ4LCAwLjMzLCAwLjgsIDAuNSksIG1vcmUgPSBUUlVFKQpwcmludChnZW5lX3Bsb3RfNCwgcG9zaXRpb24gPSBjKDAuNzcsIDAuMzMsIDEsIDAuNSksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X2ZkaCwgcG9zaXRpb24gPSBjKDAuMzksIDAsIDEuMDQsIDAuMzgpLCBtb3JlID0gVFJVRSkKZ3JpZDo6Z3JpZC50ZXh0KGxhYmVsID0gYygiQSIsICJCIiwgIkMiLCAiRCIsICJFIiksIHggPSBjKDAuMDMsIDAuMDMsIDAuMDMsIDAuNDUsIDAuNDUpLAogIHkgPSBjKDAuOTgsIDAuNjYsIDAuMzMsIDAuOTgsIDAuNDgpKQpgYGAKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0KIyBleHBvcnQgcGxvdCBhcyBzdmcKc3ZnKCIuLi9maWd1cmVzL2ZpZ3VyZV9wcm90X3N0YXRzLnN2ZyIsIHdpZHRoID0gNS41LCBoZWlnaHQgPSA2LjUpCnByaW50KHBsb3RfcXVhbnRfcHJvdDEsIHBvc2l0aW9uID0gYygwLCAwLjY0LCAwLjQ1LCAxKSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfcXVhbnRfcGVwXzIsIHBvc2l0aW9uID0gYygwLCAwLjMyLCAwLjQ1LCAwLjY4KSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3Rfbm1kcywgcG9zaXRpb24gPSBjKDAuMDMsIDAsIDAuNDUsIDAuMzYpLCBtb3JlID0gVFJVRSkKcHJpbnQoZ2VuZV9wbG90XzEsIHBvc2l0aW9uID0gYygwLjQ4LCAwLjgsIDEsIDAuOTcpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9jYmIsIHBvc2l0aW9uID0gYygwLjQxLCAwLjQ3LCAxLjA0LCAwLjg1KSwgbW9yZSA9IFRSVUUpCnByaW50KGdlbmVfcGxvdF8yLCBwb3NpdGlvbiA9IGMoMC40OCwgMC4zMywgMC44LCAwLjUpLCBtb3JlID0gVFJVRSkKcHJpbnQoZ2VuZV9wbG90XzQsIHBvc2l0aW9uID0gYygwLjc3LCAwLjMzLCAxLCAwLjUpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9mZGgsIHBvc2l0aW9uID0gYygwLjM5LCAwLCAxLjA0LCAwLjM4KSwgbW9yZSA9IFRSVUUpCmdyaWQ6OmdyaWQudGV4dChsYWJlbCA9IGMoIkEiLCAiQiIsICJDIiwgIkQiLCAiRSIpLCB4ID0gYygwLjAzLCAwLjAzLCAwLjAzLCAwLjQ1LCAwLjQ1KSwKICB5ID0gYygwLjk4LCAwLjY2LCAwLjMzLCAwLjk4LCAwLjQ4KSkKZGV2Lm9mZigpCmBgYAoKCg==