Intro

The purpose of this analysis is to compare as many as possible tools and methods that we have in our disposal in order to classify the synergies observed in the dataset that was published by (Flobak et al. 2019). Note that throughout this analysis the term SINTEF dataset will refer to the first screen dataset from that paper.

By methods we refer to mathematical models like Bliss and HSA (Highest Single Agent) used for the synergy classification as well as to the data processing methodology for identification and extraction of the synergies from the dataset.

Currently, this analysis is mostly based on the use of the rbbt tool (Vázquez et al. 2010) with its specified workflows and tasks for finding the observed synergies of the SINTEF dataset using various parameters as input. We provide a docker image with rbbt and its workflows in the Zenodo dataset DOI as well as documentation guidelines on how to use it.

Also, in the last section we are going to validate the method used in (Flobak et al. 2019) to derive statistically significant synergies and compare it with the results from the rbbt-based analysis.

Summary

  • Raw data analysis shows that Bliss-related methods produce higher F1-Scores overall (matching closer the Gold-Standard synergies per cell line) than the HSA-related ones. This difference in favor of Bliss:
    • Is independent of the cell line (see Bliss vs HSA (Seperate Tasks)).
    • It depends on the task that is used: ConPropExcess results (F1-Scores) are higher for Bliss than for HSA, compared to the other 2 tasks where there is no statistically significant difference.
  • Bliss correlates with HSA when comparing same tasks (with same parameters + same cell line):
    • The more sophisticated the task (complexity increases in tasks as: Avg < ConExcess < ConPropExcess), the higher the correlation between Bliss and HSA.
    • In the Avg and ConExcess tasks, HSA has a larger percentage of F1-Scores (∼5% and ∼9% more than Bliss respectively).
    • In the ConPropExcess task, Bliss has a larger percentage of F1-Scores (∼13% more compared to HSA).
    • The regression lines are below the y=x line: no matter the cell line or task, there are always some Bliss-related methods that produce higher F1-Scores than the corresponding HSA ones.
    • The two synergy methods correlate also in the parameter space
  • The best average F1-Score across all 8 cell lines was produced by the ConExcess method (using HSA).
  • The best combination of parameters (p_value_bonferroni and mean_bliss_threshold) for the Bliss-all-combinations method (from (Flobak et al. 2019)) that result in the highest average F1-Score across all 8 cell lines, still perform lower than the highest ones attained using the rbbt tasks.
    • This best parameter combination comes extremely close to the values chosen (arbitrarily) by the authors of (Flobak et al. 2019) to derive the synergies in a file given in the SI of that paper (bliss_synergies.tsv).

Input

Loading libraries:

library(usefun)
library(dplyr)
library(tidyr)
library(readr)
library(tibble)
library(plotly)
library(DT)
library(ggpubr)
library(RColorBrewer)

Loading helping functions from fun.R:

library(stringr)

# Rbbt setup
rbbt.singularity.image = "~/soft/rbbt.SINTEF.img" # change appropriately
rbbt.singularity.cmd = paste('singularity exec -e', rbbt.singularity.image)
container_path.R = system(paste(rbbt.singularity.cmd, 'rbbt_Rutil.rb'), intern = TRUE)[1]
rbbt_source.R = paste(system(paste(rbbt.singularity.cmd,'cat', container_path.R), intern = TRUE), collapse="\n")
eval(parse(text=rbbt_source.R)) # Eval the code
rbbt.ruby.exec = function(code) {
  rbbt.ruby.exec.singularity(code, rbbt.singularity.image)
}

#' Run a synergy SINTEF task
#'
#' Use this function to call rbbt SINTEF workflow tasks that produce
#' synergies as a result given task-specific parameters.
#'
#' @param task 1 of 4 options: 'GS', 'Avg', 'ConExcess' or 'ConPropExcess'
#' @param cell.line name of the cell line
#'
#' @importFrom stringr str_replace_all
run.sintef.task = function(task, cell.line, ...) {
  arguments = list(...)

  if (task == "GS") {
    synergies = rbbt.job(workflow = "SINTEF", task = "synergy_classification_by_GS", cell_line = cell.line, log = "0", type = "array")
  } else if (task == "Avg") {
    synergies = rbbt.job(workflow = "SINTEF", task = "synergy_classification_by_avg", cell_line = cell.line, ci_method = arguments$ci_method, excess_threshold = arguments$excess_threshold, log = "0", type = "array")
  } else if (task == "ConExcess") {
    synergies = rbbt.job(workflow = "SINTEF", task = "synergy_classification_by_consecutive_excesses", cell_line = cell.line, ci_method = arguments$ci_method, consecutive_excess_count = arguments$consecutive_excess_count, excess_threshold = arguments$excess_threshold, log = "0", type = "array")
  } else if (task == "ConPropExcess") {
    synergies = rbbt.job(workflow = "SINTEF", task = "synergy_classification_by_consecutive_proportional_excesses", cell_line = cell.line, ci_method = arguments$ci_method, consecutive_excess_count = arguments$consecutive_excess_count, excess_proportional_threshold = arguments$excess_proportional_threshold, log = "0", type = "array")
  }

  # always remove `SF` drug
  synergies = synergies[!grepl(pattern = "SF", x = synergies)]
  synergies = str_replace_all(string = synergies, pattern = "~", replacement = "-")

  return(synergies)
}

#' Scoring function (F1 score)
#'
#' @param gset = vector of gold-standard synergies
#' @param mset = vector of method-produced synergies
get.score = function(gset, mset) {
  hits = sum(mset %in% gset)
  misses = sum(!mset %in% gset)

  gset.len = length(gset)
  mset.len = length(mset)

  score = (hits - misses + mset.len)/(gset.len + mset.len)
  return(score)
}

Rbbt: Synergies

Aim

The rbbt bioinformatics toolkit has several workflows and the most interesting for our investigation are the SINTEF and CombinationIndex - the later published as a standalone in (Flobak et al. 2017).

So, using the rbbt tool and the SINTEF workflow, we are going to computationally define the synergies in the SINTEF dataset (8 cell lines) and check which methods/tasks (and input parameters) come closer to our gold-curation standard.

Data Preprocessing

The SINTEF workflow provides several tasks to calculate the observed synergies for the cell lines of the SINTEF data paper and all these tasks use internally the CombinationIndex tasks bliss and hsa. The last two tasks take a lot of time to calculate - around \(1\) hour per cell line - but they are independent in the sense that the results do not change (they depend only on the SINTEF dataset).

So the data from these two tasks needs to be pre-calculated before we proceed further with the analysis. We provide either the data pre-calculated (and a way to extract it for use in the analysis) or a way to calculate it yourself (DIY):

  1. Download the two datasets from the zenodo dataset DOI

Move the two archived files to ~/.rbbt/var/jobs/SINTEF (where the job files are stored) and run:

tar xzvf sintef.combination.index.bliss.tar.gz
tar xzvf sintef.combination.index.hsa.tar.gz

Thus, two directories named bliss and hsa, will be created (with the respective results inside).

  1. Compute the bliss and hsa task results from the SINTEF dataset (DIY way)

Given an rbbt.SINTEF.image (it’s included in the Zenodo dataset), and a SINTEF workflow at least at commit 947307b, you can use the run_cimbinator.sh script to produce the data results.

Synergy Methods

In December 2019, we renamed some of the tasks in the SINTEF workflow in order to better understand what these methods actually do and to also add some new ones. We now have the following methods (rbbt tasks) for calculating synergy scores in the SINTEF dataset (the SINTEF workflow has to be at least on commit 2c5ea):

  1. Gold-Standard (GS)1
    • Command: rbbt workflow task SINTEF synergy_classification_by_GS -h
    • Example: ... --cell_line=A498 | grep -v SF
  2. Averaged scores for Bliss and HSA (AvgBliss, AvgHSA)2
    • Command: rbbt workflow task SINTEF synergy_classification_by_avg -h
    • Example: ... --ci_method=hsa --excess_threshold=-0.10 --cell_line=AGS | grep -v SF
  3. Consecutive excess method for Bliss and HSA (ConExcessBliss, ConExcessHSA)3
    • Command: rbbt workflow task SINTEF synergy_classification_by_consecutive_excesses -h
    • Example: ... --ci_method=hsa --consecutive_excess_count=2 --excess_threshold=-0.15 --cell_line=AGS
  4. Consecutive proportional excess method for Bliss and HSA (ConPropExcessBliss, ConPropExcessHSA)4
    • Command: rbbt workflow task SINTEF synergy_classification_by_consecutive_proportional_excesses -h
    • Example: ... --ci_method=bliss --consecutive_excess_count=2 --excess_proportional_threshold=0.2 --cell_line=AGS

Details

  • The GS method is the result of curation - visual inspection of 3 dose response curves for each drug combination case from the SINTEF dataset: 2 of the single-drugs and 1 for the combination.
  • The AvgBliss and AvgHSA tasks compute the average scores for Bliss and HSA methods for each drug combination (using CImbinator, one score per combination) and a synergy is called when this average value is lower than the given excess_threshold.
  • The ConExcessBliss and ConExcessHSA tasks call as a synergy a drug combination where there is at least consecutive_excess_count amount of consecutive points of the non-interaction curve (Bliss or HSA) that are lower than the corresponding points of the combined response curve by a value/distance of excess_threshold.
  • The ConPropExcessBliss and ConPropExcessHSA tasks call as a synergy a drug combination where there is at least consecutive_excess_count amount of consecutive points of the non-interaction curve (Bliss or HSA) that are lower than the corresponding points of the combined response curve by a threshold value that is determined from the proportion (excess_proportional_threshold) of the excess relative to the magnitude of the combined response.

Gold-Standard Synergies

So, for each respective cell line we get the gold-curated standard (GS) synergies (already pre-calculated using this script):

gs.list = readRDS(file = "gs_list.rds")

And these are:

cell.lines = c("A498", "AGS", "Colo205", "DU145", "MDA-MB-468", "SF295", "SW620", "UACC62")

gs.print = function() {
  pretty_print_string("")
  for (cell.line in cell.lines) {
    pretty_print_bold_string(string = cell.line, with.gt = FALSE)
    print_empty_line(html.output = TRUE)
    pretty_print_vector_values(gs.list[[cell.line]], with.gt = FALSE, vector.values.str = "synergies")
    print_empty_line(html.output = TRUE)
    print_empty_line(html.output = TRUE)
  }
}

gs.print()

A498
10 synergies: BI-PI, AK-BI, PD-G2, AK-PD, PI-D1, BI-PD, AK-G4, PD-P5, AK-60, 5Z-G2

AGS
6 synergies: AK-BI, PI-D1, BI-D1, PI-G2, PD-PI, 5Z-PI

Colo205
3 synergies: AK-BI, AK-60, BI-PI

DU145
1 synergie: AK-BI

MDA-MB-468
10 synergies: AK-BI, BI-PI, AK-PI, PI-JN, PI-D1, PI-D4, CT-PI, PD-PI, AK-PD, PD-JN

SF295
3 synergies: AK-BI, PI-JN, PI-P5

SW620
3 synergies: AK-BI, PI-JN, PI-G2

UACC62
5 synergies: AK-BI, BI-PI, PI-D1, PI-G2, CT-PI

Scoring Synergy Methods

As we saw above, for every cell line we have a set of GS-synergies. Also, every SINTEF task defined - referred for simplicity here as a method - given different parameters, will produce a different set of synergies where some of them will be in the respective GS set and some maybe not.

We want to define a matching score that will tell us how close the calculated synergy set of a method is to the respective GS one. Some definitions first:

  • For a particular cell line, we define the set of gold-standard synergies as \(gset\) and the set of calculated synergies from a method with a given parameterization as \(mset\).
  • The number of synergies in each set will be defined as \(l_{gset}\ge0\) and \(l_{mset}\ge0\) respectively.
  • The number of common synergies between the two sets are equal to the number of \(hits\) (the number of synergies that the method got right) and the number of synergies which the calculated method got wrong are the \(misses\) (so they are part of the \(l_{mset}\) but not part of \(l_{gset}\)).

So, it must be pretty obvious that our hits-and-misses score should try to maximize the \(hits\) and minimize the \(misses\), which mathematically can be expressed by this equation: \(max(hits-misses)\). Observe that the extreme values for this difference are (\(max\) case where the sets compared are exactly the same: \(mset=gset\)):

\[max_{hm}=max(hits-misses)=l_{gset}-0=l_{gset}\]

and in the case where \(mset \cap gset=\emptyset\):

\[min_{hm}=min(hits-misses)=0-max(misses)=-l_{mset}\]

So, given the 2 sets \(gset\) and \(mset\), with some \(hits\) and \(misses\) as defined above, we produce the normalized \(score_{hm}\in[0,1]\) of how well the method’s produced set matches the gold-standard set as:

\[score_{hm}=\frac{(hits-misses)-min_{hm}}{max_{hm}-min_{hm}}=\frac{hits-misses+l_{mset}}{l_{gset}+l_{mset}}\]

# for the examples
gset = c("a","b","c")
mset1 = c("a","b","c")
mset2 = c("b","c","d")
mset3 = c("c","d","e")
mset4 = c("b","c","e","f","g","j","o")
mset5 = c("c","d","e","f","g","j","o")
mset6 = c("d","e")

Consider the following examples to understand how the formula works (capital letters represent different drug combinations):

  • \(gset=\{A,B,C\}\), \(mset1=\{A,B,C\}\), \(score_{hm}=1\)
  • \(gset=\{A,B,C\}\), \(mset2=\{B,C,D\}\), \(score_{hm}=0.6666667\)
  • \(gset=\{A,B,C\}\), \(mset3=\{C,D,E\}\), \(score_{hm}=0.3333333\)
  • \(gset=\{A,B,C\}\), \(mset4=\{B,C,E,F,G,J,O\}\), \(score_{hm}=0.4\)
  • \(gset=\{A,B,C\}\), \(mset5=\{C,D,E,F,G,J,O\}\), \(score_{hm}=0.2\)
  • \(gset=\{A,B,C\}\), \(mset6=\{D,E\}\), \(score_{hm}=0\)

For the advanced reader, note that the hits-and-misses \(score_{hm}\), is actually the F1-score (!), since considering that \(hits=TP\), \(misses=FP\), \(l_{mset}=TP+FP\) and \(l_{gset}=P=TP+FN\), we have that: \[score_{hm}=\frac{hits-misses+l_{mset}}{l_{gset}+l_{mset}}=\frac{TP-FP+TP+FP}{TP+FN+TP+FP}=\frac{2\times TP}{2\times TP+FN+FP}=F_1\]

Rbbt tasks Analysis

To perform a complete search on the methods parameter space and derive a score for each method (with a specific parameter combination in each case), run this script. We have already run it for efficiency and now simply load the result object to continue with the analysis:

res.data = as_tibble(readRDS(file = "res_data.rds"))

So, the results consist of the F1-scores for every possible combination of:

  • \(3\) SINTEF tasks (Avg, ConExcess, ConPropExcess)
  • \(2\) ci_methods (Bliss and HSA)

and parameters (applicable to specific tasks):

  • \(5\) consecutive_excess_count’s (1-5)
  • \(51\) excess_threshold’s (from \(0\) to \(-0.5\) with a step of \(-0.01\)) and
  • \(33\) excess_proportional_threshold’s (from \(0\) to \(0.8\) with a \(0.025\) step).

Bliss vs HSA (Raw data)

Let’s compare from the raw data (so not matching them exactly parameter-to-parameter) the two mathematical models that were used in the calculation of synergies. First, we will do that across all possible tasks, cell lines and parameters:

ggboxplot(data = res.data, x = "ci_method", y = "f1.score", 
  title = "Bliss vs HSA across all cell lines, tasks and parameters",
  xlab = "Synergy Method", ylab = "F1-Score", color = "ci_method",
  add = "jitter", palette = "Set1", shape = "ci_method") + 
  stat_compare_means(label.x = 1.35)

Seems that there is a small difference in favor of the bliss-related tasks

Let’s check if the same trend can be seen when we split the raw data per cell line:

ggboxplot(data = res.data, x = "cell_line", y = "f1.score", fill = "ci_method",
  title = "Bliss vs HSA per cell line across all tasks and parameters",
  xlab = "Cell lines", ylab = "F1-Score", palette = "Set1") + 
  stat_compare_means(aes(group = ci_method), label = "p.signif") +
  theme(axis.text.x = element_text(size = 11))

  • For 5 out of 8 cell lines there is no task with any combination of parameters which managed to match exactly the synergies from the curation gold-standard (no F1-Score == 1 for some cell lines).
  • The statistical difference between Bliss and HSA is variant across the cell lines with at least half of them showing no difference at all.
Grouping the data per task we have:
ggboxplot(data = res.data, x = "task", y = "f1.score", fill = "ci_method",
  title = "Bliss vs HSA per SINTEF task, across all cell lines and parameters",
  xlab = "Tasks", ylab = "F1-Score", palette = "Set1") + 
  stat_compare_means(aes(group = ci_method), label = "p.signif")

  • There are no parameter combinations in the ConPropExcess task for any cell line that produce synergies matching exactly the curated standard synergies in that cell line (F1-Score equal to \(1\)).
  • There is no statistical difference between Bliss and HSA in the Avg and ConExcess tasks.
  • Bliss has a statistical significant difference from HSA in the ConPropExcess task.

Bliss vs HSA (Seperate Tasks)

We will now compare Bliss vs HSA synergy methods for each different task in separate graphs. This will allow us to observe if in each task there is a small Bliss advantage over HSA, or if it actually depends more on each cell line:

Avg task

ggboxplot(data = res.data %>% filter(task == "Avg"), x = "cell_line", y = "f1.score", 
  fill = "ci_method", title = "Bliss vs HSA per cell line (Task: Avg)",
  xlab = "Cell lines", ylab = "F1-Score", palette = "Set1") + 
  stat_compare_means(aes(group = ci_method), label = "p.signif") +
  theme(axis.text.x = element_text(size = 11))

ConExcess task

ggboxplot(data = res.data %>% filter(task == "ConExcess"), x = "cell_line", y = "f1.score", 
  fill = "ci_method", title = "Bliss vs HSA per cell line (Task: ConExcess)",
  xlab = "Cell lines", ylab = "F1-Score", palette = "Set1") +
  stat_compare_means(aes(group = ci_method), label = "p.signif") +
  theme(axis.text.x = element_text(size = 11))

ConPropExcess task

ggboxplot(data = res.data %>% filter(task == "ConPropExcess"), x = "cell_line", y = "f1.score", 
  fill = "ci_method", title = "Bliss vs HSA per cell line (Task: ConPropExcess)",
  xlab = "Cell lines", ylab = "F1-Score", palette = "Set1") + 
  stat_compare_means(aes(group = ci_method), label = "p.signif") +
  theme(axis.text.x = element_text(size = 11))

  • There is no strong difference between Bliss vs HSA methods in the same SINTEF task across all cell lines. So the choice of cell line seems to not play an important role.
  • But no matter the task, there is most of the times a Bliss method that will outperform any HSA-based one, in any given cell line.

Bliss vs HSA (Correlation)

Now we tidy our raw data, in order to compare homogenous data points - i.e. coming from the same task, parameters and cell line, only different synergy method (Bliss vs HSA).

Avg task

data.task.avg = res.data %>% 
  filter(task == "Avg") %>% 
  group_by(ci_method) %>% 
  arrange(ci_method, excess_threshold, cell_line) %>%
  transmute(cell_line, excess_threshold, f1.score) %>% 
  group_split() %>% 
  bind_cols()

# count Bliss vs HSA F1-Scores
task.avg.counts = data.task.avg %>% 
  transmute(equal = (f1.score == f1.score1), less = (f1.score < f1.score1), larger = (f1.score > f1.score1)) %>% 
  summarise(equal = sum(equal), less = sum(less), larger = sum(larger))

ggscatter(data = data.task.avg, x = "f1.score", y = "f1.score1", color = "cell_line",
  xlab = "Bliss F1-Scores", ylab = "HSA F1-Scores",
  title = "'Avg' task (Kendall Correlation)",
  add = "reg.line", add.params = list(color = "blue", fill = "lightgray"),
  conf.int = TRUE, cor.coef = TRUE, 
  cor.coeff.args = list(method = "kendall", label.x.npc = 0.7, label.y.npc = 0.1)) + 
  geom_segment(x = 0, y = 0, xend = 1, yend = 1, size = 0.3, linetype = "dashed") +
  annotate(geom = "text", label = "y = x", x = 0.93, y = 1) +
  annotate(geom = "text", label = paste0("Bliss < HSA: ", signif(100*task.avg.counts$less/sum(task.avg.counts), digits = 2), "%"), x = 0.15, y = 1) +
  annotate(geom = "text", label = paste0("Bliss = HSA: ", signif(100*task.avg.counts$equal/sum(task.avg.counts), digits = 2), "%"), x = 0.15, y = 0.9) +
  annotate(geom = "text", label = paste0("Bliss > HSA: ", signif(100*task.avg.counts$larger/sum(task.avg.counts), digits = 2), "%"), x = 0.15, y = 0.8)

ConExcess task

data.task.con.excess = res.data %>% 
  filter(task == "ConExcess") %>% 
  group_by(ci_method) %>% 
  arrange(ci_method, consecutive_excess_count, excess_threshold, cell_line) %>%
  transmute(consecutive_excess_count, excess_threshold, cell_line, f1.score) %>% 
  group_split() %>% 
  bind_cols()

# count Bliss vs HSA F1-Scores
task.con.excess.counts = data.task.con.excess %>% 
  transmute(equal = (f1.score == f1.score1), less = (f1.score < f1.score1), larger = (f1.score > f1.score1)) %>% 
  summarise(equal = sum(equal), less = sum(less), larger = sum(larger))

ggscatter(data = data.task.con.excess, x = "f1.score", y = "f1.score1", color = "cell_line",
  xlab = "Bliss F1-Scores", ylab = "HSA F1-Scores",
  title = "'ConExcess' task (Kendall Correlation)",
  add = "reg.line", add.params = list(color = "blue", fill = "lightgray"),
  conf.int = TRUE, cor.coef = TRUE, 
  cor.coeff.args = list(method = "kendall", label.x.npc = 0.7, label.y.npc = 0.1)) + 
  geom_segment(x = 0, y = 0, xend = 1, yend = 1, size = 0.3, linetype = "dashed") +
  annotate(geom = "text", label = "y = x", x = 0.93, y = 1) + 
  annotate(geom = "text", label = paste0("Bliss < HSA: ", signif(100*task.con.excess.counts$less/sum(task.con.excess.counts), digits = 2), "%"), x = 0.15, y = 1) +
  annotate(geom = "text", label = paste0("Bliss = HSA: ", signif(100*task.con.excess.counts$equal/sum(task.con.excess.counts), digits = 2), "%"), x = 0.15, y = 0.9) +
  annotate(geom = "text", label = paste0("Bliss > HSA: ", signif(100*task.con.excess.counts$larger/sum(task.con.excess.counts), digits = 2), "%"), x = 0.15, y = 0.8)

ConPropExcess task

data.task.con.prop.excess = res.data %>% 
  filter(task == "ConPropExcess") %>% 
  group_by(ci_method) %>% 
  arrange(ci_method, consecutive_excess_count, excess_proportional_threshold, cell_line) %>%
  transmute(consecutive_excess_count, excess_proportional_threshold, cell_line, f1.score) %>% 
  group_split() %>% 
  bind_cols()

# count Bliss vs HSA F1-Scores
task.con.prop.excess.counts = data.task.con.prop.excess %>% 
  transmute(equal = (f1.score == f1.score1), less = (f1.score < f1.score1), larger = (f1.score > f1.score1)) %>% 
  summarise(equal = sum(equal), less = sum(less), larger = sum(larger))

ggscatter(data = data.task.con.prop.excess, x = "f1.score", y = "f1.score1", color = "cell_line",
  xlab = "Bliss F1-Scores", ylab = "HSA F1-Scores",
  title = "'ConPropExcess' task (Kendall Correlation)",
  add = "reg.line", add.params = list(color = "blue", fill = "lightgray"),
  conf.int = TRUE, cor.coef = TRUE, 
  cor.coeff.args = list(method = "kendall", label.x.npc = 0.7, label.y.npc = 0.1)) + 
  geom_segment(x = 0, y = 0, xend = 0.8, yend = 0.8, size = 0.3, linetype = "dashed") +
  annotate(geom = "text", label = "y = x", x = 0.73, y = 0.8) +
  annotate(geom = "text", label = paste0("Bliss < HSA: ", signif(100*task.con.prop.excess.counts$less/sum(task.con.prop.excess.counts), digits = 2), "%"), x = 0.15, y = 0.8) +
  annotate(geom = "text", label = paste0("Bliss = HSA: ", signif(100*task.con.prop.excess.counts$equal/sum(task.con.prop.excess.counts), digits = 2), "%"), x = 0.15, y = 0.7) +
  annotate(geom = "text", label = paste0("Bliss > HSA: ", signif(100*task.con.prop.excess.counts$larger/sum(task.con.prop.excess.counts), digits = 2), "%"), x = 0.15, y = 0.6)

  • Bliss results correlate with HSA ones more on ConPropExcess task, less on the ConExcess task and even less on the Avg task. So the more sophisticated the method is, the larger the correlation between Bliss and HSA (more common F1-Scores).
  • In more than half of the data points collected across all cell lines and task parameters, Bliss produces an equal F1-Score with the HSA-based methods.
  • Where the results between Bliss and HSA differ:
    • In the Avg and ConExcess tasks, HSA has a larger percentage of F1-Scores (\(\sim5\%\) and \(\sim 9\%\) difference respectively).
    • In the ConPropExcess task, Bliss has a larger percentage of F1-Scores (\(\sim 13\%\) difference).
  • The above reflects the results of this figure which shows Bliss having a statistical significant difference from HSA for the ConPropExcess task. This also means that the value differences of the F1-Scores between HSA and Bliss are larger in magnitude in the ConPropExcess task compared to the other two tasks (since the difference percentages somewhat negate each other across the 3 tasks but there is a difference in the overall data in favor of Bliss).
  • In all 3 graphs above, the regression line is below the y=x line which means that there are higher Bliss values produced by some methods (no matter the task or cell line): these (outlier-sort-of) Bliss results outperform the HSA similar ones.

Highest F1-Scored Task

Our job now is to find the task with the set of parameters that will give the best (HSA or Bliss-based) F1-score across all the cell lines tested - i.e. the task that best fits the curated GS across all cell lines.

So we will aggragate the results across the 8 cell lines by producing the average F1-Score for each specific synergy task with all its combination of parameters (for both Bliss and HSA).

We calculate the average F1-Scores per task and find the maximum ones across all cell lines:

# Avg task
avg.f1.task.avg = res.data %>% 
  filter(task == "Avg") %>% 
  group_by(ci_method, excess_threshold) %>% 
  summarise(avg.f1.score = mean(f1.score)) %>%
  ungroup() %>%
  add_column(task = "Avg", .before = 1)
## `summarise()` regrouping output by 'ci_method' (override with `.groups` argument)
data.list = list()
data.list[[1]] = 
  avg.f1.task.avg %>% 
  group_by(ci_method) %>% 
  filter(avg.f1.score == max(avg.f1.score)) %>%
  ungroup()

# ConExcess task
avg.f1.task.con.excess = res.data %>% 
  filter(task == "ConExcess") %>% 
  group_by(ci_method, consecutive_excess_count, excess_threshold) %>% 
  summarise(avg.f1.score = mean(f1.score)) %>%
  ungroup() %>%
  add_column(task = "ConExcess", .before = 1)
## `summarise()` regrouping output by 'ci_method', 'consecutive_excess_count' (override with `.groups` argument)
data.list[[2]] = 
  avg.f1.task.con.excess %>% 
  group_by(ci_method) %>% 
  filter(avg.f1.score == max(avg.f1.score)) %>%
  ungroup()

# ConPropExcess task
avg.f1.task.con.prop.excess = res.data %>% 
  filter(task == "ConPropExcess") %>% 
  group_by(ci_method, consecutive_excess_count, excess_proportional_threshold) %>% 
  summarise(avg.f1.score = mean(f1.score)) %>%
  ungroup() %>%
  add_column(task = "ConPropExcess", .before = 1)
## `summarise()` regrouping output by 'ci_method', 'consecutive_excess_count' (override with `.groups` argument)
data.list[[3]] = 
  avg.f1.task.con.prop.excess %>% 
  group_by(ci_method) %>% 
  filter(avg.f1.score == max(avg.f1.score)) %>%
  ungroup()

best.avg.f1.scores = bind_rows(data.list) %>% 
  select(task, ci_method, avg.f1.score, excess_threshold, consecutive_excess_count, excess_proportional_threshold)
max.avg.f1.score = max(best.avg.f1.scores$avg.f1.score)

# color avg.f1.scores
f1.breaks = quantile(best.avg.f1.scores %>% pull(avg.f1.score), probs = seq(.05, .95, .05))
f1.colors = round(seq(255, 40, length.out = length(f1.breaks) + 1), 0) %>%
  {paste0("rgb(255,", ., ",", ., ")")} # red

options(persistent = TRUE)

caption.title = "Highest average F1-Score Tasks with respective parameters"
datatable(data = best.avg.f1.scores,
  options = list(dom = "t", # just show the table
  order = list(list(3, 'desc')),
  columnDefs = list(list(className = 'dt-center', targets = 3:6))),
  caption = htmltools::tags$caption(caption.title, style="color:#dd4814; font-size: 18px")) %>%
  formatStyle(columns = c("avg.f1.score"), backgroundColor = styleInterval(f1.breaks, f1.colors)) %>%
  formatRound(3:6, digits = 3)

As we expected from the results of an earlier graph, the ConPropExcess task produces the lowest among the maximum average F1-Scores across all tasks tested (and it’s Bliss F1-Score is larger than the respective HSA).

The best average F1-Score across all 8 cell lines was produced by the ConExcess method. The rbbt command line parameters to produce the synergies per given cell line using the best task are:

rbbt workflow task SINTEF synergy_classification_by_consecutive_excesses --consecutive_excess_count=2 --excess_threshold=-0.17 --ci_method=hsa --cell_line=<NAME> | grep -v SF


The per-cell-line F1-Scores produced by the best task as found above are:
best.task.data = res.data %>%
  filter(task == "ConExcess", consecutive_excess_count == 2, excess_threshold == -0.17, ci_method == "hsa") %>%
  select(cell_line, f1.score)
datatable(data = best.task.data, options = list(dom = "t"))

For the AGS cell line we have a perfect synergy assement: the best task results match exactly the same synergies as the curated Gold-Standard for AGS!

GS vs Best Task

We have executed the run_best_task.R script in order to get the synergies found with this best chosen task and parameters. We load this list of synergies:

# 'best.list' = best list (of synergies) vs 'gs.list' = gold standard list (of synergies)
best.list = readRDS(file = "best_data.rds")

So, for AGS the results are the same but what about the other cell lines that didn’t have a perfect F1-Score? We will now take a closer look at the FP and FN synergies produced by the best task for each cell line:

best.task.print = function() {
  pretty_print_string("")
  for (cell.line in cell.lines) {
    pretty_print_bold_string(string = cell.line, with.gt = FALSE)
    print_empty_line(html.output = TRUE)
    
    fp = best.list[[cell.line]][!best.list[[cell.line]] %in% gs.list[[cell.line]]]
    fn = gs.list[[cell.line]][!gs.list[[cell.line]] %in% best.list[[cell.line]]]
    
    pretty_print_vector_values(vec = fp, vector.values.str = "False Positives", with.gt = FALSE)
    print_empty_line(html.output = TRUE)
    
    pretty_print_vector_values(vec = fn, vector.values.str = "False Negatives", with.gt = FALSE)
    print_empty_line(html.output = TRUE)
    print_empty_line(html.output = TRUE)
  }
}
best.task.print()

A498
0 False Positives:
5 False Negatives: PI-D1, BI-PD, AK-G4, AK-60, 5Z-G2

AGS
0 False Positives:
0 False Negatives:

Colo205
0 False Positives:
2 False Negatives: AK-60, BI-PI

DU145
4 False Positives: PD-PI, PI-D1, JN-D1, G2-P5
0 False Negatives:

MDA-MB-468
5 False Positives: AK-JN, JN-D1, BI-P5, D1-P5, G4-P5
2 False Negatives: PI-D4, CT-PI

SF295
3 False Positives: BI-PI, PI-D1, PI-G2
1 False Negative: AK-BI

SW620
2 False Positives: BI-PI, PI-D1
1 False Negative: PI-JN

UACC62
2 False Positives: PI-JN, G2-P5
0 False Negatives:

In total, there are \(16\) false positives (synergies the best method got wrong) and \(11\) false negatives (synergies the best method didn’t get at all) comparing to the curation gold-standard synergies.

This is intriguing since the best method ConExcess represents a kind of visual mathematical method for synergy identification: at least \(2\) consecutive points have to be less than the \(-0.17\) (threshold) from the non-interaction line, which holds true for all the \(FP\) synergies (and not for the \(FN\) ones) but this differs to what the human eye captures as synergy or not!

Parameter Space Plots

We now visualize the parameter space for each task and per ci_method (Bliss vs HSA):

Avg task

# title font style
my.title.font = list(family = "Courier New, monospace", size = 18, color = "black")

plot_ly(data = avg.f1.task.avg, x = ~excess_threshold, y = ~avg.f1.score, width = 800,
  type = "scatter", mode = "lines+markers", color = ~ci_method, colors = "Set1") %>%
  layout(title = list(text = "Avg Task - Mean F1-Score across 8 cell lines", font = my.title.font),
    xaxis = list(title = "Excess Threshold", zeroline = FALSE),
    yaxis = list(title = "Mean F1-Score")) %>%
  config(displayModeBar = FALSE)
## Warning: `arrange_()` is deprecated as of dplyr 0.7.0.
## Please use `arrange()` instead.
## See vignette('programming') for more help
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_warnings()` to see where this warning was generated.

ConExcess task

# font style
my.colorbar.font = list(family = "Courier New, monospace", size = 14, color = "black")

fig1 = plot_ly(avg.f1.task.con.excess %>% filter(ci_method == "bliss"), x = ~consecutive_excess_count, 
  y = ~excess_threshold, z = ~avg.f1.score, zmin = 0, zmax = max.avg.f1.score, width = 1000,
  colorscale = "Jet", type = "contour", contours = list(showlabels = TRUE)) %>% 
  colorbar(title = list(text = "Mean F1-Score \n(8 cell lines)", font = my.colorbar.font), 
    tickmode = "linear", tick0 = 0, dtick = 0.1) %>%
  layout(annotations = list(
    list(x = 0.5, y = 1, xanchor = "center", yanchor = "bottom", align = "center", 
      xref='paper', yref='paper', showarrow = FALSE, 
      font = my.title.font, text = "ConExcess Task - Bliss")))

fig2 = plot_ly(avg.f1.task.con.excess %>% filter(ci_method == "hsa"), x = ~consecutive_excess_count, 
  y = ~excess_threshold, z = ~avg.f1.score, zmin = 0, zmax = max.avg.f1.score, width = 1000,
  colorscale = "Jet", type = "contour", contours = list(showlabels = TRUE), 
  showscale = FALSE) %>% layout(annotations = list(
    list(x = 0.5, y = 1, xanchor = "center", yanchor = "bottom", align = "center", 
      xref='paper', yref='paper', showarrow = FALSE, 
      font = my.title.font, text = "ConExcess Task - HSA")))

subplot(fig1, fig2, titleX = TRUE, titleY = TRUE, margin = 0.05) %>% 
  config(displayModeBar = FALSE)

ConPropExcess task

fig1 = plot_ly(avg.f1.task.con.prop.excess %>% filter(ci_method == "bliss"), x = ~consecutive_excess_count, 
  y = ~excess_proportional_threshold, z = ~avg.f1.score, zmin = 0, zmax = max.avg.f1.score, width = 1000,
  colorscale = "Jet", type = "contour", contours = list(showlabels = TRUE)) %>% 
  colorbar(title = list(text = "Mean F1-Score \n(8 cell lines)", font = my.colorbar.font), 
    tickmode = "linear", tick0 = 0, dtick = 0.1) %>%
  layout(annotations = list(
    list(x = 0.5, y = 1, xanchor = "center", yanchor = "bottom", align = "center", 
      xref='paper', yref='paper', showarrow = FALSE, 
      font = my.title.font, text = "ConPropExcess Task - Bliss")))

fig2 = plot_ly(avg.f1.task.con.prop.excess %>% filter(ci_method == "hsa"), x = ~consecutive_excess_count, 
  y = ~excess_proportional_threshold, z = ~avg.f1.score, zmin = 0, zmax = max.avg.f1.score, width = 1000,
  colorscale = "Jet", type = "contour", contours = list(showlabels = TRUE), 
  showscale = FALSE) %>% # no colorbar 
  layout(annotations = list(
    list(x = 0.5, y = 1, xanchor = "center", yanchor = "bottom", align = "center", 
      xref='paper', yref='paper', showarrow = FALSE, 
      font = my.title.font, text = "ConPropExcess Task - HSA")))

subplot(fig1, fig2, titleX = TRUE, titleY = TRUE, margin = 0.05) %>% 
  config(displayModeBar = FALSE)

  • The ConExcess method is the most verbose with regards to it’s parameter space: a larger amount of parameter combinations can be attributed to higher mean F1-Scores.
  • The mountain-peaks in each task (where the tasks attain their maximum values) are close in the same (1D for the Avg task, 2D for the other 2 tasks) parameter sub-space when comparing Bliss vs HSA - i.e. the synergy methods correlate in the parameter-space.
  • The small statistical advantage that Bliss had over HSA in the raw data can still be seen in the 2D contour plots of the Avg and ConPropExcess task when comparing the average F1-Scores across all cell lines, but in the end the HSA synergy method in the ConExcess task resulted in the best result with the given parameter space.

Bliss-all-combinations method

The Bliss-all-combinations method (I arbitrarily chose the name) was the method that Barbara and Asmund used in (Flobak et al. 2019) on the data from the first screen to find and visualize the synergies across all 8 cell lines. The method is described in the text and Asmund wrote an R script that calculates the average Bliss excesses and significance scores (p_values_Bliss.R in the supplementary material - generates the bliss_significance.tsv file). Combinations with average Bliss excess values \(<−0.08\) and p-value \(<0.05\) (the Bonferroni-corrected) were classified as synergies (results were put into the bliss_synergies.tsv file in the SI of the paper).

The two threshold values above (average bliss excess and the corrected p-value) were more-or-less intuitively chosen (a good-enough p-value + an OK negative threshold so to speak). We now want to compare the synergies found with this method (across different combinations of the aforementioned parameters) with the GS synergies in each cell line and see which come closer to the curation-chosen ones.

We first load the data from the bliss_significance.tsv file and tidy ’em up a bit:

bliss.all.comb.data = drop_na(read_tsv(file = "bliss_significance.tsv"))
bliss.synergies = read_tsv(file = "bliss_synergies.tsv")

# Fix the cell line names
bliss.all.comb.data = bliss.all.comb.data %>% 
  mutate(cell_line = case_when(
    cell_line == "SW-620" ~ "SW620", 
    cell_line == "COLO 205" ~ "Colo205",
    cell_line == "DU-145" ~ "DU145",
    cell_line == "SF-295" ~ "SF295",
    cell_line == "UACC-62" ~ "UACC62",
    TRUE ~ cell_line
))
bliss.synergies = bliss.synergies %>% 
  mutate(cell_line = case_when(
    cell_line == "SW-620" ~ "SW620", 
    cell_line == "COLO 205" ~ "Colo205",
    cell_line == "DU-145" ~ "DU145",
    cell_line == "SF-295" ~ "SF295",
    cell_line == "UACC-62" ~ "UACC62",
    TRUE ~ cell_line
))

# Remove rows with SF drug in the combination
bliss.all.comb.data = bliss.all.comb.data %>% filter(!grepl('SF', drugcombo))
bliss.synergies = bliss.synergies %>% filter(!grepl('SF', drugcombo))

# Fix the drug names (add a dash between the drug names)
bliss.all.comb.data = bliss.all.comb.data %>% 
  mutate(drug_combo = str_replace(drugcombo, " ", "-")) %>% 
  select(cell_line, drug_combo, mean.bliss.estimate, p.value.bonferroni)

# below results are the same
# cell.line = "AGS"
# bliss.all.comb.data %>% filter(p.value.bonferroni < 0.05, mean.bliss.estimate < -0.08, cell_line == cell.line)
# bliss.synergies %>% filter(cell_line == cell.line, !grepl('SF', drugcombo))

Next, we chose the parameter space for the two thresholds of interest:

  • \(14\) corrected p_value_bonferroni from \(0.00001\) to \(0.05\) and the maximum one in the dataset (equal to \(1\))
  • \(42\) mean_bliss_threshold values from \(\sim -0.5\) to \(\sim +0.42\) with a step of \(\sim 0.0227\)

We calculate for each combination of the above two parameters and for each cell line, the synergies found from this dataset and compare it with the GS synergies to derive an F1-Score for that parameter combination. Then we aggregate the results across the 8 cell lines per each parameter combination, to find the average F1-Score per (p_value_bonferroni, mean_bliss_threshold) combination:

p.values = c(0.00001, 0.00005, 0.0001, 0.0005, 0.001, 0.005, 0.01, 0.02, 0.03, 0.04, 0.05, 0.1, 0.5, 1)
bliss.thresholds = seq(from = min(bliss.all.comb.data$mean.bliss.estimate), to = max(bliss.all.comb.data$mean.bliss.estimate), length.out = 42)

data.list = list()
index = 1

for (cell.line in cell.lines) {                                                 
  gset = gs.list[[cell.line]]
  for (p.value in p.values) {
    for (threshold in bliss.thresholds) {
      mset = bliss.all.comb.data %>% 
        filter(p.value.bonferroni <= p.value, mean.bliss.estimate <= threshold, cell_line == cell.line) %>%
        pull(drug_combo)
      data.list[[index]] = tibble(cell_line = cell.line, p_value_bonferroni = p.value, mean_bliss_threshold = threshold, f1.score = get.score(gset, mset))
      index = index + 1
    }
  }
}

bliss.all.comb.res = bind_rows(data.list)

# Find avg F1-Score across all cell lines
avg.f1.bliss.all = bliss.all.comb.res %>% 
  group_by(p_value_bonferroni, mean_bliss_threshold) %>% 
  summarise(avg.f1.score = mean(f1.score)) %>% 
  ungroup()
caption.title = "Average F1-Score across 8 cell lines (Bliss-All-Combinations)"
datatable(data = avg.f1.bliss.all, options = list(
  order = list(list(3, 'desc'))),
  caption = htmltools::tags$caption(caption.title, style="color:#dd4814; font-size: 18px")) %>%
  formatRound(2:3, digits = 3)
  • Observing the results in the table above, we see the the first sensible result (i.e. with a p_value_bonferroni \(<0.05\), so somewhat trustable :) that holds the 4th largest F1-Score (\(0.453\)), has a mean_bliss_threshold \(\le -0.096\). Thus the choice of Barbara and Asmund in (Flobak et al. 2019) for mean_bliss_threshold \(<−0.08\) and p_value_bonferroni \(<0.05\) to derive the synergies was surely one of the best choices they could make (call it intuition or eye-balling, this choice is now mathematically substantiated)!
  • Even the largest average F1-Score, cannot match the results of the highest scoring rbbt tasks as seen in the previous section.

Visualizing the results of the above table using a contour plot we have:

plot_ly(data = avg.f1.bliss.all, x = ~-log10(p_value_bonferroni), 
  y = ~mean_bliss_threshold, z = ~avg.f1.score, zmin = 0, zmax = max.avg.f1.score, #width = 1000,
  colorscale = "Jet", type = "contour", contours = list(showlabels = TRUE)) %>% 
  colorbar(title = list(text = "Mean F1-Score \n(8 cell lines)", font = my.colorbar.font), 
    tickmode = "linear", tick0 = 0, dtick = 0.1) %>%
  layout(annotations = list(
    list(x = 0.5, y = 1, xanchor = "center", yanchor = "bottom", align = "center", 
      xref='paper', yref='paper', showarrow = FALSE, 
      font = my.title.font, text = "Bliss-All-Combinations Method"))) %>% 
  config(displayModeBar = FALSE)

The larger the p_value_bonferroni (the values to the right of the above plot), the harder is to get a good value for the average F1-Score across all cell lines.

References

Flobak, Åsmund, Barbara Niederdorfer, Vu To Nakstad, Liv Thommesen, Geir Klinkenberg, and Astrid Lægreid. 2019. “A high-throughput drug combination screen of targeted small molecule inhibitors in cancer cell lines.” Scientific Data 6 (1): 237. https://doi.org/10.1038/s41597-019-0255-7.

Flobak, Åsmund, Miguel Vazquez, Astrid Lægreid, and Alfonso Valencia. 2017. “CImbinator: a web-based tool for drug synergy analysis in small- and large-scale datasets.” Edited by Jonathan Wren. Bioinformatics 33 (15): 2410–2. https://doi.org/10.1093/bioinformatics/btx161.

Vázquez, Miguel, Rubén Nogales, Pedro Carmona, Alberto Pascual, and Juan Pavón. 2010. “Rbbt: A Framework for Fast Bioinformatics Development with Ruby.” In, 201–8. Springer, Berlin, Heidelberg. https://doi.org/10.1007/978-3-642-13214-8_26.


  1. The Gold standard is actually taken from an online file that has the results from a manual/visual inspection of the SINTEF dataset (the dose-response curves) and it’s a consensus of three curators: Asmund Flobak, Barbara Niederdorfer and Evelina Folkesson. The synergy_classification_by_GS task from the rbbt SINTEF workflow has to be at least on the 2c5ea commit to take the latest version of that file↩︎

  2. Computed from the bliss and hsa CombinationIndex task results and not taken directly from Barbara’s file any more as it was done with the observed_synergies_averaged task (i.e. on the SINTEF workflow commit d1a2e5 and before)↩︎

  3. Also uses the bliss and hsa CombinationIndex task results↩︎

  4. Also uses the bliss and hsa CombinationIndex task results↩︎

LS0tCnRpdGxlOiAiU0lOVEVGLWRhdGFzZXQgT2JzZXJ2ZWQgU3luZXJnaWVzIgphdXRob3I6ICJbSm9obiBab2JvbGFzXShodHRwczovL2dpdGh1Yi5jb20vYmJsb2Rmb24pIgpkYXRlOiAiTGFzdCB1cGRhdGVkOiBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCLCAlWScpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjc3M6IHN0eWxlLmNzcwogICAgdGhlbWU6IHVuaXRlZAogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgICAgc21vb3RoX3Njcm9sbDogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKYmlibGlvZ3JhcGh5OiByZWZlcmVuY2VzLmJpYgpsaW5rLWNpdGF0aW9uczogdHJ1ZQotLS0KCiMgSW50cm8gey19Cgo6Ojogey5ibHVlLWJveH0KVGhlIHB1cnBvc2Ugb2YgdGhpcyBhbmFseXNpcyBpcyB0byBjb21wYXJlIGFzIG1hbnkgYXMgcG9zc2libGUgKip0b29scyBhbmQgbWV0aG9kcyoqIHRoYXQgd2UgaGF2ZSBpbiBvdXIgZGlzcG9zYWwgaW4gb3JkZXIgdG8gY2xhc3NpZnkgdGhlIHN5bmVyZ2llcyBvYnNlcnZlZCBpbiB0aGUgZGF0YXNldCB0aGF0IHdhcyBwdWJsaXNoZWQgYnkgW0BGbG9iYWsyMDE5XS4gCk5vdGUgdGhhdCB0aHJvdWdob3V0IHRoaXMgYW5hbHlzaXMgdGhlIHRlcm0gKlNJTlRFRiBkYXRhc2V0KiB3aWxsIHJlZmVyIHRvIHRoZSBmaXJzdCBzY3JlZW4gZGF0YXNldCBmcm9tIHRoYXQgcGFwZXIuCjo6OgoKQnkgKiptZXRob2RzKiogd2UgcmVmZXIgdG8gbWF0aGVtYXRpY2FsIG1vZGVscyBsaWtlICoqQmxpc3MgYW5kIEhTQSAoSGlnaGVzdCBTaW5nbGUgQWdlbnQpKiogdXNlZCBmb3IgdGhlIHN5bmVyZ3kgY2xhc3NpZmljYXRpb24gYXMgd2VsbCBhcyB0byB0aGUgZGF0YSBwcm9jZXNzaW5nIG1ldGhvZG9sb2d5IGZvciBpZGVudGlmaWNhdGlvbiBhbmQgZXh0cmFjdGlvbiBvZiB0aGUgc3luZXJnaWVzIGZyb20gdGhlIGRhdGFzZXQuCgpDdXJyZW50bHksIHRoaXMgYW5hbHlzaXMgaXMgbW9zdGx5IGJhc2VkIG9uIHRoZSB1c2Ugb2YgdGhlIFtyYmJ0XShodHRwOi8vbWlraXN2YXouZ2l0aHViLmlvL3JiYnQvKSB0b29sIFtAVmF6cXVlejIwMTBdIHdpdGggaXRzIHNwZWNpZmllZCB3b3JrZmxvd3MgYW5kIHRhc2tzIGZvciBmaW5kaW5nIHRoZSBvYnNlcnZlZCBzeW5lcmdpZXMgb2YgdGhlIFNJTlRFRiBkYXRhc2V0IHVzaW5nIHZhcmlvdXMgcGFyYW1ldGVycyBhcyBpbnB1dC4KV2UgcHJvdmlkZSBhICoqZG9ja2VyIGltYWdlKiogd2l0aCByYmJ0IGFuZCBpdHMgd29ya2Zsb3dzIGluIHRoZSBaZW5vZG8gZGF0YXNldCBbIVtET0ldKGh0dHBzOi8vemVub2RvLm9yZy9iYWRnZS9ET0kvMTAuNTI4MS96ZW5vZG8uNDAyMjgwMS5zdmcpXShodHRwczovL2RvaS5vcmcvMTAuNTI4MS96ZW5vZG8uNDAyMjgwMSkgYXMgd2VsbCBhcyBbZG9jdW1lbnRhdGlvbiBndWlkZWxpbmVzXShodHRwOi8vdGlueS5jYy9yYmJ0LWRvYykgb24gaG93IHRvIHVzZSBpdC4KCkFsc28sIGluIHRoZSBbbGFzdCBzZWN0aW9uXSgjYmxpc3MtYWxsLWNvbWJpbmF0aW9ucy1tZXRob2QpIHdlIGFyZSBnb2luZyB0byB2YWxpZGF0ZSB0aGUgbWV0aG9kIHVzZWQgaW4gW0BGbG9iYWsyMDE5XSB0byBkZXJpdmUgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBzeW5lcmdpZXMgYW5kIGNvbXBhcmUgaXQgd2l0aCB0aGUgcmVzdWx0cyBmcm9tIHRoZSByYmJ0LWJhc2VkIGFuYWx5c2lzLgoKIyBTdW1tYXJ5IHstfQoKOjo6IHsuZ3JlZW4tYm94fQotIFJhdyBkYXRhIGFuYWx5c2lzIHNob3dzIHRoYXQgQmxpc3MtcmVsYXRlZCBtZXRob2RzIHByb2R1Y2UgW2hpZ2hlciBGMS1TY29yZXMgb3ZlcmFsbF0oI2J2c2EpIChtYXRjaGluZyBjbG9zZXIgdGhlIEdvbGQtU3RhbmRhcmQgc3luZXJnaWVzIHBlciBjZWxsIGxpbmUpIHRoYW4gdGhlIEhTQS1yZWxhdGVkIG9uZXMuClRoaXMgZGlmZmVyZW5jZSBpbiBmYXZvciBvZiBCbGlzczoKICAtIElzICoqaW5kZXBlbmRlbnQgb2YgdGhlIGNlbGwgbGluZSoqIChzZWUgW0JsaXNzIHZzIEhTQSAoU2VwZXJhdGUgVGFza3MpXSkuCiAgLSBJdCAqKmRlcGVuZHMgb24gdGhlIHRhc2sqKiB0aGF0IGlzIFt1c2VkXSgjYnZocHQpOiBgQ29uUHJvcEV4Y2Vzc2AgcmVzdWx0cyAoRjEtU2NvcmVzKSBhcmUgaGlnaGVyIGZvciBCbGlzcyB0aGFuIGZvciBIU0EsIGNvbXBhcmVkIHRvIHRoZSBvdGhlciAyIHRhc2tzIHdoZXJlIHRoZXJlIGlzIG5vIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZS4KLSBbQmxpc3MgY29ycmVsYXRlcyB3aXRoIEhTQV0oI2JsaXNzLXZzLWhzYS1jb3JyZWxhdGlvbikgd2hlbiBjb21wYXJpbmcgc2FtZSB0YXNrcyAod2l0aCBzYW1lIHBhcmFtZXRlcnMgKyBzYW1lIGNlbGwgbGluZSk6CiAgLSBUaGUgKiptb3JlIHNvcGhpc3RpY2F0ZWQgdGhlIHRhc2sqKiAoY29tcGxleGl0eSBpbmNyZWFzZXMgaW4gdGFza3MgYXM6IGBBdmdgIDwgYENvbkV4Y2Vzc2AgPCBgQ29uUHJvcEV4Y2Vzc2ApLCB0aGUgKipoaWdoZXIgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gQmxpc3MgYW5kIEhTQSoqLgogIC0gSW4gdGhlIGBBdmdgIGFuZCBgQ29uRXhjZXNzIHRhc2tzYCwgSFNBIGhhcyBhIGxhcmdlciBwZXJjZW50YWdlIG9mIEYxLVNjb3JlcyAo4oi8NSUgYW5kIOKIvDklIG1vcmUgdGhhbiBCbGlzcyByZXNwZWN0aXZlbHkpLgogIC0gSW4gdGhlIGBDb25Qcm9wRXhjZXNzYCB0YXNrLCBCbGlzcyBoYXMgYSBsYXJnZXIgcGVyY2VudGFnZSBvZiBGMS1TY29yZXMgKOKIvDEzJSBtb3JlIGNvbXBhcmVkIHRvIEhTQSkuCiAgLSBUaGUgcmVncmVzc2lvbiBsaW5lcyBhcmUgYmVsb3cgdGhlIGB5PXhgIGxpbmU6IG5vIG1hdHRlciB0aGUgY2VsbCBsaW5lIG9yIHRhc2ssICoqdGhlcmUgYXJlIGFsd2F5cyBzb21lIEJsaXNzLXJlbGF0ZWQgbWV0aG9kcyB0aGF0IHByb2R1Y2UgaGlnaGVyIEYxLVNjb3JlcyB0aGFuIHRoZSBjb3JyZXNwb25kaW5nIEhTQSBvbmVzKiouCiAgLSBUaGUgdHdvIHN5bmVyZ3kgbWV0aG9kcyBbY29ycmVsYXRlIGFsc28gaW4gdGhlIHBhcmFtZXRlciBzcGFjZV0oI3BhcmFtZXRlci1zcGFjZS1wbG90cykKLSBUaGUgYmVzdCBhdmVyYWdlIFtGMS1TY29yZV0oI2hpZ2hlc3QtZjEtc2NvcmVkLXRhc2spICoqYWNyb3NzIGFsbCA4IGNlbGwgbGluZXMqKiB3YXMgcHJvZHVjZWQgYnkgdGhlIGBDb25FeGNlc3NgIFttZXRob2RdKCNzeW5lcmd5LW1ldGhvZHMpICh1c2luZyAqKkhTQSoqKS4KICAtIEZvciB0aGUgYEFHU2AgY2VsbCBsaW5lIHdlIGhhdmUgYSAqKnBlcmZlY3Qgc3luZXJneSBhc3NlbWVudCoqOiB0aGUgW2Jlc3QgdGFzayByZXN1bHRzIG1hdGNoIGV4YWN0bHldKCNidGljbGYxKSB0aGUgc2FtZSBzeW5lcmdpZXMgYXMgdGhlIGN1cmF0ZWQgR29sZC1TdGFuZGFyZCBmb3IgYEFHU2AhCi0gVGhlIGJlc3QgY29tYmluYXRpb24gb2YgW3BhcmFtZXRlcnNdKCNibGlzc0FsbFRibCkgKGBwX3ZhbHVlX2JvbmZlcnJvbmlgIGFuZCBgbWVhbl9ibGlzc190aHJlc2hvbGRgKSBmb3IgdGhlICpCbGlzcy1hbGwtY29tYmluYXRpb25zKiBtZXRob2QgKGZyb20gW0BGbG9iYWsyMDE5XSkgdGhhdCByZXN1bHQgaW4gdGhlIGhpZ2hlc3QgYXZlcmFnZSBGMS1TY29yZSBhY3Jvc3MgYWxsIDggY2VsbCBsaW5lcywgKipzdGlsbCBwZXJmb3JtIGxvd2VyIHRoYW4gdGhlIGhpZ2hlc3Qgb25lcyBhdHRhaW5lZCB1c2luZyB0aGUgcmJidCB0YXNrcyoqLgogIC0gVGhpcyBiZXN0IHBhcmFtZXRlciBjb21iaW5hdGlvbiBjb21lcyBleHRyZW1lbHkgY2xvc2UgdG8gdGhlIHZhbHVlcyBjaG9zZW4gKGFyYml0cmFyaWx5KSBieSB0aGUgYXV0aG9ycyBvZiBbQEZsb2JhazIwMTldIHRvIGRlcml2ZSB0aGUgc3luZXJnaWVzIGluIGEgZmlsZSBnaXZlbiBpbiB0aGUgU0kgb2YgdGhhdCBwYXBlciAoW2JsaXNzX3N5bmVyZ2llcy50c3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9kcnVnbG9naWNzL3NpbnRlZi1vYnMtc3luZXJnaWVzL2Jsb2IvbWFzdGVyL2JsaXNzX3N5bmVyZ2llcy50c3YpKS4KOjo6CgojIElucHV0IHstfQoKTG9hZGluZyBsaWJyYXJpZXM6CmBgYHtyIExvYWQgbGlicmFyaWVzLCBtZXNzYWdlID0gRkFMU0V9CmxpYnJhcnkodXNlZnVuKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeShwbG90bHkpCmxpYnJhcnkoRFQpCmxpYnJhcnkoZ2dwdWJyKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKYGBgCgpMb2FkaW5nIGhlbHBpbmcgZnVuY3Rpb25zIGZyb20gW2Z1bi5SXShodHRwczovL2dpdGh1Yi5jb20vZHJ1Z2xvZ2ljcy9zaW50ZWYtb2JzLXN5bmVyZ2llcy9ibG9iL21hc3Rlci9mdW4uUik6CmBgYHtyIHJlYWQgZnVuY3Rpb25zLCBjb2RlPXJlYWRMaW5lcygnZnVuLlInKX0KYGBgCgojIFJiYnQ6IFN5bmVyZ2llcyB7LX0KCiMjIEFpbSAKClRoZSBgcmJidGAgYmlvaW5mb3JtYXRpY3MgdG9vbGtpdCBoYXMgc2V2ZXJhbCB3b3JrZmxvd3MgYW5kIHRoZSBtb3N0IGludGVyZXN0aW5nIGZvciBvdXIgaW52ZXN0aWdhdGlvbiBhcmUgdGhlIFtgU0lOVEVGYF0oaHR0cHM6Ly9naXRodWIuY29tL1JiYnQtV29ya2Zsb3dzL1NJTlRFRikgYW5kIFtgQ29tYmluYXRpb25JbmRleGBdKGh0dHBzOi8vZ2l0aHViLmNvbS9SYmJ0LVdvcmtmbG93cy9jb21iaW5hdGlvbl9pbmRleCkgLSB0aGUgbGF0ZXIgcHVibGlzaGVkIGFzIGEgc3RhbmRhbG9uZSBpbiBbQEZsb2JhazIwMTddLgoKOjo6IHsuaW5mby1ib3ggLm5vdGV9ClNvLCB1c2luZyB0aGUgYHJiYnRgIHRvb2wgYW5kIHRoZSBgU0lOVEVGYCB3b3JrZmxvdywgd2UgYXJlIGdvaW5nIHRvIGNvbXB1dGF0aW9uYWxseSBkZWZpbmUgdGhlIHN5bmVyZ2llcyBpbiB0aGUgU0lOVEVGIGRhdGFzZXQgKDggY2VsbCBsaW5lcykgYW5kIGNoZWNrICoqd2hpY2ggbWV0aG9kcy90YXNrcyAoYW5kIGlucHV0IHBhcmFtZXRlcnMpIGNvbWUgY2xvc2VyKiogdG8gb3VyIFtnb2xkLWN1cmF0aW9uIHN0YW5kYXJkXSgjc3luZXJneS1tZXRob2RzKS4KOjo6CgojIyBEYXRhIFByZXByb2Nlc3Npbmcgey0jemVub2RvLWRhdGF9CgpUaGUgYFNJTlRFRmAgd29ya2Zsb3cgcHJvdmlkZXMgc2V2ZXJhbCAqdGFza3MqIHRvIGNhbGN1bGF0ZSB0aGUgb2JzZXJ2ZWQgc3luZXJnaWVzIGZvciB0aGUgY2VsbCBsaW5lcyBvZiB0aGUgU0lOVEVGIGRhdGEgcGFwZXIgYW5kIGFsbCB0aGVzZSB0YXNrcyB1c2UgaW50ZXJuYWxseSB0aGUgYENvbWJpbmF0aW9uSW5kZXhgIHRhc2tzIGBibGlzc2AgYW5kIGBoc2FgLgpUaGUgbGFzdCB0d28gdGFza3MgdGFrZSBhIGxvdCBvZiB0aW1lIHRvIGNhbGN1bGF0ZSAtIGFyb3VuZCAkMSQgaG91ciAqcGVyIGNlbGwgbGluZSogLSBidXQgdGhleSBhcmUgaW5kZXBlbmRlbnQgaW4gdGhlIHNlbnNlIHRoYXQgdGhlIHJlc3VsdHMgZG8gbm90IGNoYW5nZSAodGhleSBkZXBlbmQgb25seSBvbiB0aGUgU0lOVEVGIGRhdGFzZXQpLiAKClNvIHRoZSBkYXRhIGZyb20gdGhlc2UgdHdvIHRhc2tzIG5lZWRzIHRvIGJlIHByZS1jYWxjdWxhdGVkIGJlZm9yZSB3ZSBwcm9jZWVkIGZ1cnRoZXIgd2l0aCB0aGUgYW5hbHlzaXMuIApXZSBwcm92aWRlIGVpdGhlciAqKnRoZSBkYXRhIHByZS1jYWxjdWxhdGVkKiogKGFuZCBhIHdheSB0byBleHRyYWN0IGl0IGZvciB1c2UgaW4gdGhlIGFuYWx5c2lzKSBvciAqKmEgd2F5IHRvIGNhbGN1bGF0ZSBpdCB5b3Vyc2VsZiAoRElZKSoqOgoKMS4gRG93bmxvYWQgdGhlIHR3byBkYXRhc2V0cyBmcm9tIHRoZSB6ZW5vZG8gZGF0YXNldCBbIVtET0ldKGh0dHBzOi8vemVub2RvLm9yZy9iYWRnZS9ET0kvMTAuNTI4MS96ZW5vZG8uNDAyMjgwMS5zdmcpXShodHRwczovL2RvaS5vcmcvMTAuNTI4MS96ZW5vZG8uNDAyMjgwMSkKCiAgTW92ZSB0aGUgdHdvIGFyY2hpdmVkIGZpbGVzIHRvIGB+Ly5yYmJ0L3Zhci9qb2JzL1NJTlRFRmAgKHdoZXJlIHRoZSBqb2IgZmlsZXMgYXJlIHN0b3JlZCkgYW5kIHJ1bjoKYGBgCnRhciB4enZmIHNpbnRlZi5jb21iaW5hdGlvbi5pbmRleC5ibGlzcy50YXIuZ3oKdGFyIHh6dmYgc2ludGVmLmNvbWJpbmF0aW9uLmluZGV4LmhzYS50YXIuZ3oKYGBgCiAgVGh1cywgdHdvIGRpcmVjdG9yaWVzIG5hbWVkIGBibGlzc2AgYW5kIGBoc2FgLCB3aWxsIGJlIGNyZWF0ZWQgKHdpdGggdGhlIHJlc3BlY3RpdmUgcmVzdWx0cyBpbnNpZGUpLgoKMi4gQ29tcHV0ZSB0aGUgYGJsaXNzYCBhbmQgYGhzYWAgdGFzayByZXN1bHRzIGZyb20gdGhlIFNJTlRFRiBkYXRhc2V0IChESVkgd2F5KQoKICBHaXZlbiBhbiBgcmJidC5TSU5URUYuaW1hZ2VgIChpdCdzIGluY2x1ZGVkIGluIHRoZSBaZW5vZG8gZGF0YXNldCksIGFuZCBhIGBTSU5URUZgIHdvcmtmbG93IGF0IGxlYXN0IGF0IGNvbW1pdCBgOTQ3MzA3YmAsIHlvdSBjYW4gdXNlIHRoZSBbcnVuX2NpbWJpbmF0b3Iuc2hdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9kcnVnbG9naWNzL3NpbnRlZi1vYnMtc3luZXJnaWVzL21hc3Rlci9ydW5fY2ltYmluYXRvci5zaCkgc2NyaXB0IHRvIHByb2R1Y2UgdGhlIGRhdGEgcmVzdWx0cy4KCiMjIFN5bmVyZ3kgTWV0aG9kcyB7LX0KCkluIERlY2VtYmVyIDIwMTksIHdlIHJlbmFtZWQgc29tZSBvZiB0aGUgdGFza3MgaW4gdGhlIGBTSU5URUZgIHdvcmtmbG93IGluIG9yZGVyIHRvIGJldHRlciB1bmRlcnN0YW5kIHdoYXQgdGhlc2UgbWV0aG9kcyBhY3R1YWxseSBkbyBhbmQgdG8gYWxzbyBhZGQgc29tZSBuZXcgb25lcy4gCldlIG5vdyBoYXZlICoqdGhlIGZvbGxvd2luZyBtZXRob2RzIChyYmJ0IHRhc2tzKSBmb3IgY2FsY3VsYXRpbmcgc3luZXJneSBzY29yZXMgaW4gdGhlIFNJTlRFRiBkYXRhc2V0KiogKHRoZSBgU0lOVEVGYCB3b3JrZmxvdyBoYXMgdG8gYmUgYXQgbGVhc3Qgb24gY29tbWl0IGAyYzVlYWApOgoKMS4gKipHb2xkLVN0YW5kYXJkIChHUykqKl5bVGhlICpHb2xkIHN0YW5kYXJkKiBpcyBhY3R1YWxseSB0YWtlbiBmcm9tIFthbiBvbmxpbmUgZmlsZV0oaHR0cDovL3RpbnkuY2Mvc2ludGVmR1MpIHRoYXQgaGFzIHRoZSByZXN1bHRzIGZyb20gYSBtYW51YWwvdmlzdWFsIGluc3BlY3Rpb24gb2YgdGhlIFNJTlRFRiBkYXRhc2V0ICh0aGUgZG9zZS1yZXNwb25zZSBjdXJ2ZXMpIGFuZCBpdCdzIGEgY29uc2Vuc3VzIG9mIHRocmVlIGN1cmF0b3JzOiAqKkFzbXVuZCBGbG9iYWssIEJhcmJhcmEgTmllZGVyZG9yZmVyIGFuZCBFdmVsaW5hIEZvbGtlc3NvbioqLiBUaGUgYHN5bmVyZ3lfY2xhc3NpZmljYXRpb25fYnlfR1NgIHRhc2sgZnJvbSB0aGUgcmJidCBgU0lOVEVGYCB3b3JrZmxvdyBoYXMgdG8gYmUgYXQgbGVhc3Qgb24gdGhlIGAyYzVlYWAgY29tbWl0IHRvIHRha2UgdGhlIGxhdGVzdCB2ZXJzaW9uIG9mIHRoYXQgZmlsZV0KICAgIC0gQ29tbWFuZDogYHJiYnQgd29ya2Zsb3cgdGFzayBTSU5URUYgc3luZXJneV9jbGFzc2lmaWNhdGlvbl9ieV9HUyAtaGAKICAgIC0gRXhhbXBsZTogYC4uLiAtLWNlbGxfbGluZT1BNDk4IHwgZ3JlcCAtdiBTRmAKMi4gKipBdmVyYWdlZCBzY29yZXMgZm9yIEJsaXNzIGFuZCBIU0EgKEF2Z0JsaXNzLCBBdmdIU0EpKipeW0NvbXB1dGVkIGZyb20gdGhlIGBibGlzc2AgYW5kIGBoc2FgIFtDb21iaW5hdGlvbkluZGV4IHRhc2sgcmVzdWx0c10oI3plbm9kby1kYXRhKSBhbmQgbm90IHRha2VuIGRpcmVjdGx5IGZyb20gQmFyYmFyYSdzIGZpbGUgYW55IG1vcmUgYXMgaXQgd2FzIGRvbmUgd2l0aCB0aGUgYG9ic2VydmVkX3N5bmVyZ2llc19hdmVyYWdlZGAgdGFzayAoaS5lLiBvbiB0aGUgU0lOVEVGIHdvcmtmbG93IGNvbW1pdCBgZDFhMmU1YCBhbmQgYmVmb3JlKV0KICAgIC0gQ29tbWFuZDogYHJiYnQgd29ya2Zsb3cgdGFzayBTSU5URUYgc3luZXJneV9jbGFzc2lmaWNhdGlvbl9ieV9hdmcgLWhgCiAgICAtIEV4YW1wbGU6IGAuLi4gLS1jaV9tZXRob2Q9aHNhIC0tZXhjZXNzX3RocmVzaG9sZD0tMC4xMCAtLWNlbGxfbGluZT1BR1MgfCBncmVwIC12IFNGYAozLiAqKkNvbnNlY3V0aXZlIGV4Y2VzcyBtZXRob2QgZm9yIEJsaXNzIGFuZCBIU0EgKENvbkV4Y2Vzc0JsaXNzLCBDb25FeGNlc3NIU0EpKipeW0Fsc28gdXNlcyB0aGUgYGJsaXNzYCBhbmQgYGhzYWAgW0NvbWJpbmF0aW9uSW5kZXggdGFzayByZXN1bHRzXSgjemVub2RvLWRhdGEpXQogICAgLSBDb21tYW5kOiBgcmJidCB3b3JrZmxvdyB0YXNrIFNJTlRFRiBzeW5lcmd5X2NsYXNzaWZpY2F0aW9uX2J5X2NvbnNlY3V0aXZlX2V4Y2Vzc2VzIC1oYAogICAgLSBFeGFtcGxlOiBgLi4uIC0tY2lfbWV0aG9kPWhzYSAtLWNvbnNlY3V0aXZlX2V4Y2Vzc19jb3VudD0yIC0tZXhjZXNzX3RocmVzaG9sZD0tMC4xNSAtLWNlbGxfbGluZT1BR1NgCjQuICoqQ29uc2VjdXRpdmUgcHJvcG9ydGlvbmFsIGV4Y2VzcyBtZXRob2QgZm9yIEJsaXNzIGFuZCBIU0EgKENvblByb3BFeGNlc3NCbGlzcywgQ29uUHJvcEV4Y2Vzc0hTQSkqKl5bQWxzbyB1c2VzIHRoZSBgYmxpc3NgIGFuZCBgaHNhYCBbQ29tYmluYXRpb25JbmRleCB0YXNrIHJlc3VsdHNdKCN6ZW5vZG8tZGF0YSldCiAgICAtIENvbW1hbmQ6IGByYmJ0IHdvcmtmbG93IHRhc2sgU0lOVEVGIHN5bmVyZ3lfY2xhc3NpZmljYXRpb25fYnlfY29uc2VjdXRpdmVfcHJvcG9ydGlvbmFsX2V4Y2Vzc2VzIC1oYAogICAgLSBFeGFtcGxlOiBgLi4uIC0tY2lfbWV0aG9kPWJsaXNzIC0tY29uc2VjdXRpdmVfZXhjZXNzX2NvdW50PTIgLS1leGNlc3NfcHJvcG9ydGlvbmFsX3RocmVzaG9sZD0wLjIgLS1jZWxsX2xpbmU9QUdTYAoKIyMjIERldGFpbHMKCjo6OiB7Lm9yYW5nZS1ib3h9Ci0gVGhlICoqR1MqKiBtZXRob2QgaXMgdGhlIHJlc3VsdCBvZiBjdXJhdGlvbiAtIHZpc3VhbCBpbnNwZWN0aW9uIG9mIDMgZG9zZSByZXNwb25zZSBjdXJ2ZXMgZm9yIGVhY2ggZHJ1ZyBjb21iaW5hdGlvbiBjYXNlIGZyb20gdGhlIFNJTlRFRiBkYXRhc2V0OiAyIG9mIHRoZSBzaW5nbGUtZHJ1Z3MgYW5kIDEgZm9yIHRoZSBjb21iaW5hdGlvbi4KLSBUaGUgKipBdmdCbGlzcyoqIGFuZCAqKkF2Z0hTQSoqIHRhc2tzIGNvbXB1dGUgdGhlIGF2ZXJhZ2Ugc2NvcmVzIGZvciBCbGlzcyBhbmQgSFNBIG1ldGhvZHMgZm9yIGVhY2ggZHJ1ZyBjb21iaW5hdGlvbiAodXNpbmcgQ0ltYmluYXRvciwgb25lIHNjb3JlIHBlciBjb21iaW5hdGlvbikgYW5kIGEgc3luZXJneSBpcyBjYWxsZWQgd2hlbiB0aGlzIGF2ZXJhZ2UgdmFsdWUgaXMgbG93ZXIgdGhhbiB0aGUgZ2l2ZW4gYGV4Y2Vzc190aHJlc2hvbGRgLgotIFRoZSAqKkNvbkV4Y2Vzc0JsaXNzKiogYW5kICoqQ29uRXhjZXNzSFNBKiogdGFza3MgY2FsbCBhcyBhIHN5bmVyZ3kgYSBkcnVnIGNvbWJpbmF0aW9uIHdoZXJlIHRoZXJlIGlzIGF0IGxlYXN0IGBjb25zZWN1dGl2ZV9leGNlc3NfY291bnRgIGFtb3VudCBvZiBjb25zZWN1dGl2ZSBwb2ludHMgb2YgdGhlIG5vbi1pbnRlcmFjdGlvbiBjdXJ2ZSAoQmxpc3Mgb3IgSFNBKSB0aGF0IGFyZSBsb3dlciB0aGFuIHRoZSBjb3JyZXNwb25kaW5nIHBvaW50cyBvZiB0aGUgY29tYmluZWQgcmVzcG9uc2UgY3VydmUgYnkgYSB2YWx1ZS9kaXN0YW5jZSBvZiBgZXhjZXNzX3RocmVzaG9sZGAuCi0gVGhlICoqQ29uUHJvcEV4Y2Vzc0JsaXNzKiogYW5kICoqQ29uUHJvcEV4Y2Vzc0hTQSoqIHRhc2tzIGNhbGwgYXMgYSBzeW5lcmd5IGEgZHJ1ZyBjb21iaW5hdGlvbiB3aGVyZSB0aGVyZSBpcyBhdCBsZWFzdCBgY29uc2VjdXRpdmVfZXhjZXNzX2NvdW50YCBhbW91bnQgb2YgY29uc2VjdXRpdmUgcG9pbnRzIG9mIHRoZSBub24taW50ZXJhY3Rpb24gY3VydmUgKEJsaXNzIG9yIEhTQSkgdGhhdCBhcmUgbG93ZXIgdGhhbiB0aGUgY29ycmVzcG9uZGluZyBwb2ludHMgb2YgdGhlIGNvbWJpbmVkIHJlc3BvbnNlIGN1cnZlIGJ5IGEgdGhyZXNob2xkIHZhbHVlIHRoYXQgaXMgZGV0ZXJtaW5lZCBmcm9tIHRoZSAqcHJvcG9ydGlvbiogKGBleGNlc3NfcHJvcG9ydGlvbmFsX3RocmVzaG9sZGApIG9mIHRoZSBleGNlc3MgcmVsYXRpdmUgdG8gdGhlIG1hZ25pdHVkZSBvZiB0aGUgY29tYmluZWQgcmVzcG9uc2UuCjo6OgoKYGBge3IgQ2hlY2tpbmcgdGhlIG9ubGluZSBzeW5lcmd5IGdvbGQgc3RhbmRhcmQgZmlsZSwgaW5jbHVkZT1GQUxTRSwgZXZhbD1GQUxTRX0KZGYgPSBhc190aWJibGUocmVhZC50YWJsZShmaWxlID0gIm9ubGluZV9zeW5lcmd5X2dzLnRzdiIsIGhlYWRlciA9IFRSVUUsIHNlcCA9ICJcdCIpKQoKZGYgJT4lIAogIGZpbHRlcihDZWxsbGluZSA9PSAiQ29sbzIwNSIsIERydWcuQSAhPSAiU0YiLCBEcnVnLkIgIT0gIlNGIiwgR29sZC5TdGFuZGFyZCA9PSAiU3luZXJneSIpICU+JSAKICBzZWxlY3QoRHJ1Zy5BLCBEcnVnLkIsIEdvbGQuU3RhbmRhcmQpCmBgYAoKIyMgR29sZC1TdGFuZGFyZCBTeW5lcmdpZXMgey19CgpTbywgZm9yIGVhY2ggcmVzcGVjdGl2ZSBjZWxsIGxpbmUgd2UgZ2V0IHRoZSAqZ29sZC1jdXJhdGVkKiBzdGFuZGFyZCAoR1MpIHN5bmVyZ2llcyAoYWxyZWFkeSBwcmUtY2FsY3VsYXRlZCB1c2luZyBbdGhpcyBzY3JpcHRdKGh0dHBzOi8vZ2l0aHViLmNvbS9kcnVnbG9naWNzL3NpbnRlZi1vYnMtc3luZXJnaWVzL2Jsb2IvbWFzdGVyL3J1bl9yYmJ0X3NpbnRlZl9zeW5lcmd5X3Rhc2tzLlIpKToKYGBge3IgR1MgU3luZXJnaWVzIGNvbXB1dGV9CmdzLmxpc3QgPSByZWFkUkRTKGZpbGUgPSAiZ3NfbGlzdC5yZHMiKQpgYGAKCkFuZCB0aGVzZSBhcmU6CmBgYHtyIEdTIFN5bmVyZ2llcyBwZXIgY2VsbCBsaW5lLCByZXN1bHRzPSdhc2lzJ30KY2VsbC5saW5lcyA9IGMoIkE0OTgiLCAiQUdTIiwgIkNvbG8yMDUiLCAiRFUxNDUiLCAiTURBLU1CLTQ2OCIsICJTRjI5NSIsICJTVzYyMCIsICJVQUNDNjIiKQoKZ3MucHJpbnQgPSBmdW5jdGlvbigpIHsKICBwcmV0dHlfcHJpbnRfc3RyaW5nKCIiKQogIGZvciAoY2VsbC5saW5lIGluIGNlbGwubGluZXMpIHsKICAgIHByZXR0eV9wcmludF9ib2xkX3N0cmluZyhzdHJpbmcgPSBjZWxsLmxpbmUsIHdpdGguZ3QgPSBGQUxTRSkKICAgIHByaW50X2VtcHR5X2xpbmUoaHRtbC5vdXRwdXQgPSBUUlVFKQogICAgcHJldHR5X3ByaW50X3ZlY3Rvcl92YWx1ZXMoZ3MubGlzdFtbY2VsbC5saW5lXV0sIHdpdGguZ3QgPSBGQUxTRSwgdmVjdG9yLnZhbHVlcy5zdHIgPSAic3luZXJnaWVzIikKICAgIHByaW50X2VtcHR5X2xpbmUoaHRtbC5vdXRwdXQgPSBUUlVFKQogICAgcHJpbnRfZW1wdHlfbGluZShodG1sLm91dHB1dCA9IFRSVUUpCiAgfQp9Cgpncy5wcmludCgpCmBgYAoKIyMgU2NvcmluZyBTeW5lcmd5IE1ldGhvZHMgey19CgpBcyB3ZSBzYXcgYWJvdmUsIGZvciBldmVyeSBjZWxsIGxpbmUgd2UgaGF2ZSBhIHNldCBvZiBHUy1zeW5lcmdpZXMuIApBbHNvLCBldmVyeSBgU0lOVEVGYCB0YXNrIFtkZWZpbmVkXSgjc3luZXJneS1tZXRob2RzKSAtIHJlZmVycmVkIGZvciBzaW1wbGljaXR5IGhlcmUgYXMgYSAqKm1ldGhvZCoqIC0gZ2l2ZW4gZGlmZmVyZW50IHBhcmFtZXRlcnMsIHdpbGwgcHJvZHVjZSBhIGRpZmZlcmVudCBzZXQgb2Ygc3luZXJnaWVzIHdoZXJlIHNvbWUgb2YgdGhlbSB3aWxsIGJlIGluIHRoZSByZXNwZWN0aXZlIEdTIHNldCBhbmQgc29tZSBtYXliZSBub3QuCgpXZSB3YW50IHRvIGRlZmluZSBhICptYXRjaGluZyBzY29yZSogdGhhdCB3aWxsIHRlbGwgdXMgKipob3cgY2xvc2UgdGhlIGNhbGN1bGF0ZWQgc3luZXJneSBzZXQgb2YgYSBtZXRob2QgaXMgdG8gdGhlIHJlc3BlY3RpdmUgR1Mgb25lKiouClNvbWUgZGVmaW5pdGlvbnMgZmlyc3Q6CgotIEZvciBhIHBhcnRpY3VsYXIgY2VsbCBsaW5lLCB3ZSBkZWZpbmUgdGhlIHNldCBvZiBnb2xkLXN0YW5kYXJkIHN5bmVyZ2llcyBhcyAkZ3NldCQgYW5kIHRoZSBzZXQgb2YgY2FsY3VsYXRlZCBzeW5lcmdpZXMgZnJvbSBhIG1ldGhvZCB3aXRoIGEgZ2l2ZW4gcGFyYW1ldGVyaXphdGlvbiBhcyAkbXNldCQuIAotIFRoZSBudW1iZXIgb2Ygc3luZXJnaWVzIGluIGVhY2ggc2V0IHdpbGwgYmUgZGVmaW5lZCBhcyAkbF97Z3NldH1cZ2UwJCBhbmQgJGxfe21zZXR9XGdlMCQgcmVzcGVjdGl2ZWx5LgotIFRoZSBudW1iZXIgb2YgY29tbW9uIHN5bmVyZ2llcyBiZXR3ZWVuIHRoZSB0d28gc2V0cyBhcmUgZXF1YWwgdG8gdGhlIG51bWJlciBvZiAkaGl0cyQgKHRoZSBudW1iZXIgb2Ygc3luZXJnaWVzIHRoYXQgdGhlIG1ldGhvZCBnb3QgKnJpZ2h0KikgYW5kIHRoZSBudW1iZXIgb2Ygc3luZXJnaWVzIHdoaWNoIHRoZSBjYWxjdWxhdGVkIG1ldGhvZCBnb3QgKndyb25nKiBhcmUgdGhlICRtaXNzZXMkIChzbyB0aGV5IGFyZSBwYXJ0IG9mIHRoZSAkbF97bXNldH0kIGJ1dCBub3QgcGFydCBvZiAkbF97Z3NldH0kKS4KClNvLCBpdCBtdXN0IGJlIHByZXR0eSBvYnZpb3VzIHRoYXQgb3VyICpoaXRzLWFuZC1taXNzZXMqIHNjb3JlIHNob3VsZCB0cnkgdG8gKiptYXhpbWl6ZSB0aGUgJGhpdHMkIGFuZCBtaW5pbWl6ZSB0aGUgJG1pc3NlcyQqKiwgd2hpY2ggbWF0aGVtYXRpY2FsbHkgY2FuIGJlIGV4cHJlc3NlZCBieSB0aGlzIGVxdWF0aW9uOiAkbWF4KGhpdHMtbWlzc2VzKSQuCk9ic2VydmUgdGhhdCB0aGUgZXh0cmVtZSB2YWx1ZXMgZm9yIHRoaXMgZGlmZmVyZW5jZSBhcmUgKCRtYXgkIGNhc2Ugd2hlcmUgdGhlIHNldHMgY29tcGFyZWQgYXJlIGV4YWN0bHkgdGhlIHNhbWU6ICRtc2V0PWdzZXQkKToKCiQkbWF4X3tobX09bWF4KGhpdHMtbWlzc2VzKT1sX3tnc2V0fS0wPWxfe2dzZXR9JCQgCgphbmQgaW4gdGhlIGNhc2Ugd2hlcmUgJG1zZXQgXGNhcCBnc2V0PVxlbXB0eXNldCQ6CgokJG1pbl97aG19PW1pbihoaXRzLW1pc3Nlcyk9MC1tYXgobWlzc2VzKT0tbF97bXNldH0kJAoKU28sIGdpdmVuIHRoZSAyIHNldHMgJGdzZXQkIGFuZCAkbXNldCQsIHdpdGggc29tZSAkaGl0cyQgYW5kICRtaXNzZXMkIGFzIGRlZmluZWQgYWJvdmUsIHdlIHByb2R1Y2UgdGhlIG5vcm1hbGl6ZWQgJHNjb3JlX3tobX1caW5bMCwxXSQgb2YgaG93IHdlbGwgdGhlIG1ldGhvZCdzIHByb2R1Y2VkIHNldCBtYXRjaGVzIHRoZSBnb2xkLXN0YW5kYXJkIHNldCBhczoKCiQkc2NvcmVfe2htfT1cZnJhY3soaGl0cy1taXNzZXMpLW1pbl97aG19fXttYXhfe2htfS1taW5fe2htfX09XGZyYWN7aGl0cy1taXNzZXMrbF97bXNldH19e2xfe2dzZXR9K2xfe21zZXR9fSQkCgpgYGB7ciBFeGFtcGxlIGdzZXRzIGFuZCBtc2V0c30KIyBmb3IgdGhlIGV4YW1wbGVzCmdzZXQgPSBjKCJhIiwiYiIsImMiKQptc2V0MSA9IGMoImEiLCJiIiwiYyIpCm1zZXQyID0gYygiYiIsImMiLCJkIikKbXNldDMgPSBjKCJjIiwiZCIsImUiKQptc2V0NCA9IGMoImIiLCJjIiwiZSIsImYiLCJnIiwiaiIsIm8iKQptc2V0NSA9IGMoImMiLCJkIiwiZSIsImYiLCJnIiwiaiIsIm8iKQptc2V0NiA9IGMoImQiLCJlIikKYGBgCgpDb25zaWRlciB0aGUgZm9sbG93aW5nIGV4YW1wbGVzIHRvIHVuZGVyc3RhbmQgaG93IHRoZSBmb3JtdWxhIHdvcmtzIChjYXBpdGFsIGxldHRlcnMgcmVwcmVzZW50IGRpZmZlcmVudCBkcnVnIGNvbWJpbmF0aW9ucyk6CgotICRnc2V0PVx7QSxCLENcfSQsICRtc2V0MT1ce0EsQixDXH0kLCAkc2NvcmVfe2htfT1gciBnZXQuc2NvcmUoZ3NldCwgbXNldDEpYCQKLSAkZ3NldD1ce0EsQixDXH0kLCAkbXNldDI9XHtCLEMsRFx9JCwgJHNjb3JlX3tobX09YHIgZ2V0LnNjb3JlKGdzZXQsIG1zZXQyKWAkCi0gJGdzZXQ9XHtBLEIsQ1x9JCwgJG1zZXQzPVx7QyxELEVcfSQsICRzY29yZV97aG19PWByIGdldC5zY29yZShnc2V0LCBtc2V0MylgJAotICRnc2V0PVx7QSxCLENcfSQsICRtc2V0ND1ce0IsQyxFLEYsRyxKLE9cfSQsICRzY29yZV97aG19PWByIGdldC5zY29yZShnc2V0LCBtc2V0NClgJAotICRnc2V0PVx7QSxCLENcfSQsICRtc2V0NT1ce0MsRCxFLEYsRyxKLE9cfSQsICRzY29yZV97aG19PWByIGdldC5zY29yZShnc2V0LCBtc2V0NSlgJAotICRnc2V0PVx7QSxCLENcfSQsICRtc2V0Nj1ce0QsRVx9JCwgJHNjb3JlX3tobX09YHIgZ2V0LnNjb3JlKGdzZXQsIG1zZXQ2KWAkCgo6Ojogey5pbmZvLWJveCAubm90ZX0KRm9yIHRoZSBhZHZhbmNlZCByZWFkZXIsIG5vdGUgdGhhdCB0aGUgKmhpdHMtYW5kLW1pc3NlcyogJHNjb3JlX3tobX0kLCBpcyBhY3R1YWxseSB0aGUgKipbRjEtc2NvcmVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0YxX3Njb3JlKSoqICghKSwgc2luY2UgY29uc2lkZXJpbmcgdGhhdCAkaGl0cz1UUCQsICRtaXNzZXM9RlAkLCAkbF97bXNldH09VFArRlAkIGFuZCAkbF97Z3NldH09UD1UUCtGTiQsIHdlIGhhdmUgdGhhdDoKJCRzY29yZV97aG19PVxmcmFje2hpdHMtbWlzc2VzK2xfe21zZXR9fXtsX3tnc2V0fStsX3ttc2V0fX09XGZyYWN7VFAtRlArVFArRlB9e1RQK0ZOK1RQK0ZQfT1cZnJhY3syXHRpbWVzIFRQfXsyXHRpbWVzIFRQK0ZOK0ZQfT1GXzEkJAo6OjoKCiMgUmJidCB0YXNrcyBBbmFseXNpcyB7LX0KClRvIHBlcmZvcm0gYSBjb21wbGV0ZSBzZWFyY2ggb24gdGhlIFttZXRob2RzXSgjc3luZXJneS1tZXRob2RzKSBwYXJhbWV0ZXIgc3BhY2UgYW5kIGRlcml2ZSBhIHNjb3JlIGZvciBlYWNoIG1ldGhvZCAod2l0aCBhIHNwZWNpZmljIHBhcmFtZXRlciBjb21iaW5hdGlvbiBpbiBlYWNoIGNhc2UpLCBydW4gW3RoaXMgc2NyaXB0XShodHRwczovL2dpdGh1Yi5jb20vZHJ1Z2xvZ2ljcy9zaW50ZWYtb2JzLXN5bmVyZ2llcy9ibG9iL21hc3Rlci9ydW5fcmJidF9zaW50ZWZfc3luZXJneV90YXNrcy5SKS4KV2UgaGF2ZSBhbHJlYWR5IHJ1biBpdCBmb3IgZWZmaWNpZW5jeSBhbmQgbm93IHNpbXBseSBsb2FkIHRoZSByZXN1bHQgb2JqZWN0IHRvIGNvbnRpbnVlIHdpdGggdGhlIGFuYWx5c2lzOgpgYGB7ciBMb2FkIHJlc3VsdHN9CnJlcy5kYXRhID0gYXNfdGliYmxlKHJlYWRSRFMoZmlsZSA9ICJyZXNfZGF0YS5yZHMiKSkKYGBgCgpTbywgdGhlIHJlc3VsdHMgY29uc2lzdCBvZiB0aGUgW0YxLXNjb3Jlc10oI3Njb3Jpbmctc3luZXJneS1tZXRob2RzKSBmb3IgZXZlcnkgcG9zc2libGUgY29tYmluYXRpb24gb2Y6CgotICQzJCBbU0lOVEVGIHRhc2tzXSgjc3luZXJneS1tZXRob2RzKSAoKipBdmcsIENvbkV4Y2VzcywgQ29uUHJvcEV4Y2VzcyoqKQotICQyJCAqY2lfbWV0aG9kcyogKCoqQmxpc3MgYW5kIEhTQSoqKSAKCmFuZCBwYXJhbWV0ZXJzIChhcHBsaWNhYmxlIHRvIHNwZWNpZmljIHRhc2tzKTogCgotICQ1JCAqY29uc2VjdXRpdmVfZXhjZXNzX2NvdW50KidzICgxLTUpCi0gJDUxJCAqZXhjZXNzX3RocmVzaG9sZConcyAoZnJvbSAkMCQgdG8gJC0wLjUkIHdpdGggYSBzdGVwIG9mICQtMC4wMSQpIGFuZAotICQzMyQgKmV4Y2Vzc19wcm9wb3J0aW9uYWxfdGhyZXNob2xkKidzIChmcm9tICQwJCB0byAkMC44JCB3aXRoIGEgJDAuMDI1JCBzdGVwKS4KCiMjIEJsaXNzIHZzIEhTQSAoUmF3IGRhdGEpCgpMZXQncyBjb21wYXJlICoqZnJvbSB0aGUgcmF3IGRhdGEqKiAoc28gbm90IG1hdGNoaW5nIHRoZW0gZXhhY3RseSBwYXJhbWV0ZXItdG8tcGFyYW1ldGVyKSB0aGUgdHdvIG1hdGhlbWF0aWNhbCBtb2RlbHMgdGhhdCB3ZXJlIHVzZWQgaW4gdGhlIGNhbGN1bGF0aW9uIG9mIHN5bmVyZ2llcy4KRmlyc3QsIHdlIHdpbGwgZG8gdGhhdCBhY3Jvc3MgYWxsIHBvc3NpYmxlIHRhc2tzLCBjZWxsIGxpbmVzIGFuZCBwYXJhbWV0ZXJzOgoKPGRpdiBpZD0iYnZzYSI+PC9kaXY+CmBgYHtyIEJsaXNzIHZzIEhTQSBhbGwsIGNhY2hlPVRSVUUsIGZpZy5hbGlnbj0iY2VudGVyIn0KZ2dib3hwbG90KGRhdGEgPSByZXMuZGF0YSwgeCA9ICJjaV9tZXRob2QiLCB5ID0gImYxLnNjb3JlIiwgCiAgdGl0bGUgPSAiQmxpc3MgdnMgSFNBIGFjcm9zcyBhbGwgY2VsbCBsaW5lcywgdGFza3MgYW5kIHBhcmFtZXRlcnMiLAogIHhsYWIgPSAiU3luZXJneSBNZXRob2QiLCB5bGFiID0gIkYxLVNjb3JlIiwgY29sb3IgPSAiY2lfbWV0aG9kIiwKICBhZGQgPSAiaml0dGVyIiwgcGFsZXR0ZSA9ICJTZXQxIiwgc2hhcGUgPSAiY2lfbWV0aG9kIikgKyAKICBzdGF0X2NvbXBhcmVfbWVhbnMobGFiZWwueCA9IDEuMzUpCmBgYAoKOjo6IHsuYmx1ZS1ib3h9ClNlZW1zIHRoYXQgdGhlcmUgaXMgYSAqKnNtYWxsIGRpZmZlcmVuY2UgaW4gZmF2b3Igb2YgdGhlIGJsaXNzLXJlbGF0ZWQgdGFza3MqKgo6OjoKCkxldCdzIGNoZWNrIGlmIHRoZSBzYW1lIHRyZW5kIGNhbiBiZSBzZWVuIHdoZW4gd2Ugc3BsaXQgdGhlIHJhdyBkYXRhIHBlciBjZWxsIGxpbmU6Cgo8ZGl2IGlkPSJidmhwY2wiPjwvZGl2PgpgYGB7ciBCbGlzcyB2cyBIU0EgcGVyIGNlbGwgbGluZSwgY2FjaGU9VFJVRSwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduPSJjZW50ZXIifQpnZ2JveHBsb3QoZGF0YSA9IHJlcy5kYXRhLCB4ID0gImNlbGxfbGluZSIsIHkgPSAiZjEuc2NvcmUiLCBmaWxsID0gImNpX21ldGhvZCIsCiAgdGl0bGUgPSAiQmxpc3MgdnMgSFNBIHBlciBjZWxsIGxpbmUgYWNyb3NzIGFsbCB0YXNrcyBhbmQgcGFyYW1ldGVycyIsCiAgeGxhYiA9ICJDZWxsIGxpbmVzIiwgeWxhYiA9ICJGMS1TY29yZSIsIHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgc3RhdF9jb21wYXJlX21lYW5zKGFlcyhncm91cCA9IGNpX21ldGhvZCksIGxhYmVsID0gInAuc2lnbmlmIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSkpCmBgYAoKOjo6IHsuYmx1ZS1ib3h9Ci0gRm9yIDUgb3V0IG9mIDggY2VsbCBsaW5lcyB0aGVyZSBpcyBubyB0YXNrIHdpdGggYW55IGNvbWJpbmF0aW9uIG9mIHBhcmFtZXRlcnMgd2hpY2ggbWFuYWdlZCB0byBtYXRjaCBleGFjdGx5IHRoZSBzeW5lcmdpZXMgZnJvbSB0aGUgY3VyYXRpb24gZ29sZC1zdGFuZGFyZCAoKipubyBgRjEtU2NvcmUgPT0gMWAgZm9yIHNvbWUgY2VsbCBsaW5lcyoqKS4KLSBUaGUgc3RhdGlzdGljYWwgZGlmZmVyZW5jZSBiZXR3ZWVuIEJsaXNzIGFuZCBIU0EgaXMgdmFyaWFudCBhY3Jvc3MgdGhlIGNlbGwgbGluZXMgd2l0aCAqKmF0IGxlYXN0IGhhbGYgb2YgdGhlbSBzaG93aW5nIG5vIGRpZmZlcmVuY2UgYXQgYWxsKiouCjo6OgoKR3JvdXBpbmcgdGhlIGRhdGEgcGVyIHRhc2sgd2UgaGF2ZToKPGRpdiBpZD0iYnZocHQiPjwvZGl2PgpgYGB7ciBCbGlzcyB2cyBIU0EgcGVyIHRhc2ssIGNhY2hlPVRSVUUsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0iY2VudGVyIn0KZ2dib3hwbG90KGRhdGEgPSByZXMuZGF0YSwgeCA9ICJ0YXNrIiwgeSA9ICJmMS5zY29yZSIsIGZpbGwgPSAiY2lfbWV0aG9kIiwKICB0aXRsZSA9ICJCbGlzcyB2cyBIU0EgcGVyIFNJTlRFRiB0YXNrLCBhY3Jvc3MgYWxsIGNlbGwgbGluZXMgYW5kIHBhcmFtZXRlcnMiLAogIHhsYWIgPSAiVGFza3MiLCB5bGFiID0gIkYxLVNjb3JlIiwgcGFsZXR0ZSA9ICJTZXQxIikgKyAKICBzdGF0X2NvbXBhcmVfbWVhbnMoYWVzKGdyb3VwID0gY2lfbWV0aG9kKSwgbGFiZWwgPSAicC5zaWduaWYiKQpgYGAKCjo6OiB7LmJsdWUtYm94fQotIFRoZXJlIGFyZSBubyBwYXJhbWV0ZXIgY29tYmluYXRpb25zIGluIHRoZSBgQ29uUHJvcEV4Y2Vzc2AgdGFzayBmb3IgYW55IGNlbGwgbGluZSB0aGF0IHByb2R1Y2Ugc3luZXJnaWVzIG1hdGNoaW5nIGV4YWN0bHkgdGhlIGN1cmF0ZWQgc3RhbmRhcmQgc3luZXJnaWVzIGluIHRoYXQgY2VsbCBsaW5lIChgRjEtU2NvcmVgIGVxdWFsIHRvICQxJCkuCi0gVGhlcmUgaXMgKipubyBzdGF0aXN0aWNhbCBkaWZmZXJlbmNlIGJldHdlZW4gQmxpc3MgYW5kIEhTQSoqIGluIHRoZSBgQXZnYCBhbmQgYENvbkV4Y2Vzc2AgdGFza3MuCi0gKipCbGlzcyBoYXMgYSBzdGF0aXN0aWNhbCBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGZyb20gSFNBKiogaW4gdGhlIGBDb25Qcm9wRXhjZXNzYCB0YXNrLgo6OjoKCiMjIEJsaXNzIHZzIEhTQSAoU2VwZXJhdGUgVGFza3MpIHsudGFic2V0fQoKV2Ugd2lsbCBub3cgY29tcGFyZSBCbGlzcyB2cyBIU0Egc3luZXJneSBtZXRob2RzIGZvciBlYWNoIGRpZmZlcmVudCB0YXNrIGluIHNlcGFyYXRlIGdyYXBocy4KVGhpcyB3aWxsIGFsbG93IHVzIHRvIG9ic2VydmUgKippZiBpbiBlYWNoIHRhc2sgdGhlcmUgaXMgYSBzbWFsbCBCbGlzcyBhZHZhbnRhZ2Ugb3ZlciBIU0EsIG9yIGlmIGl0IGFjdHVhbGx5IGRlcGVuZHMgbW9yZSBvbiBlYWNoIGNlbGwgbGluZSoqOgoKIyMjIEF2ZyB0YXNrCgpgYGB7ciBCbGlzcyB2cyBIU0EgcGVyIGNlbGwgbGluZSAoQXZnIHRhc2spLCBjYWNoZT1UUlVFLCBmaWcuYWxpZ249ImNlbnRlciIsIHdhcm5pbmc9RkFMU0V9IApnZ2JveHBsb3QoZGF0YSA9IHJlcy5kYXRhICU+JSBmaWx0ZXIodGFzayA9PSAiQXZnIiksIHggPSAiY2VsbF9saW5lIiwgeSA9ICJmMS5zY29yZSIsIAogIGZpbGwgPSAiY2lfbWV0aG9kIiwgdGl0bGUgPSAiQmxpc3MgdnMgSFNBIHBlciBjZWxsIGxpbmUgKFRhc2s6IEF2ZykiLAogIHhsYWIgPSAiQ2VsbCBsaW5lcyIsIHlsYWIgPSAiRjEtU2NvcmUiLCBwYWxldHRlID0gIlNldDEiKSArIAogIHN0YXRfY29tcGFyZV9tZWFucyhhZXMoZ3JvdXAgPSBjaV9tZXRob2QpLCBsYWJlbCA9ICJwLnNpZ25pZiIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpKQpgYGAKCiMjIyBDb25FeGNlc3MgdGFzawoKYGBge3IgQmxpc3MgdnMgSFNBIHBlciBjZWxsIGxpbmUgKENvbkV4Y2VzcyB0YXNrKSwgY2FjaGU9VFJVRSwgZmlnLmFsaWduPSJjZW50ZXIiLCB3YXJuaW5nPUZBTFNFfQpnZ2JveHBsb3QoZGF0YSA9IHJlcy5kYXRhICU+JSBmaWx0ZXIodGFzayA9PSAiQ29uRXhjZXNzIiksIHggPSAiY2VsbF9saW5lIiwgeSA9ICJmMS5zY29yZSIsIAogIGZpbGwgPSAiY2lfbWV0aG9kIiwgdGl0bGUgPSAiQmxpc3MgdnMgSFNBIHBlciBjZWxsIGxpbmUgKFRhc2s6IENvbkV4Y2VzcykiLAogIHhsYWIgPSAiQ2VsbCBsaW5lcyIsIHlsYWIgPSAiRjEtU2NvcmUiLCBwYWxldHRlID0gIlNldDEiKSArCiAgc3RhdF9jb21wYXJlX21lYW5zKGFlcyhncm91cCA9IGNpX21ldGhvZCksIGxhYmVsID0gInAuc2lnbmlmIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSkpCmBgYAoKIyMjIENvblByb3BFeGNlc3MgdGFzawoKYGBge3IgQmxpc3MgdnMgSFNBIHBlciBjZWxsIGxpbmUgKENvblByb3BFeGNlc3MgdGFzayksIGNhY2hlPVRSVUUsIGZpZy5hbGlnbj0iY2VudGVyIiwgd2FybmluZz1GQUxTRX0KZ2dib3hwbG90KGRhdGEgPSByZXMuZGF0YSAlPiUgZmlsdGVyKHRhc2sgPT0gIkNvblByb3BFeGNlc3MiKSwgeCA9ICJjZWxsX2xpbmUiLCB5ID0gImYxLnNjb3JlIiwgCiAgZmlsbCA9ICJjaV9tZXRob2QiLCB0aXRsZSA9ICJCbGlzcyB2cyBIU0EgcGVyIGNlbGwgbGluZSAoVGFzazogQ29uUHJvcEV4Y2VzcykiLAogIHhsYWIgPSAiQ2VsbCBsaW5lcyIsIHlsYWIgPSAiRjEtU2NvcmUiLCBwYWxldHRlID0gIlNldDEiKSArIAogIHN0YXRfY29tcGFyZV9tZWFucyhhZXMoZ3JvdXAgPSBjaV9tZXRob2QpLCBsYWJlbCA9ICJwLnNpZ25pZiIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpKQpgYGAKCiMjIHstfQoKOjo6IHsuYmx1ZS1ib3h9Ci0gVGhlcmUgaXMgKipubyBzdHJvbmcgZGlmZmVyZW5jZSBiZXR3ZWVuIEJsaXNzIHZzIEhTQSBtZXRob2RzIGluIHRoZSBzYW1lIFNJTlRFRiB0YXNrIGFjcm9zcyBhbGwgY2VsbCBsaW5lcyoqLgpTbyB0aGUgY2hvaWNlIG9mIGNlbGwgbGluZSBzZWVtcyB0byBub3QgcGxheSBhbiBpbXBvcnRhbnQgcm9sZS4KLSBCdXQgbm8gbWF0dGVyIHRoZSB0YXNrLCAqKnRoZXJlIGlzIG1vc3Qgb2YgdGhlIHRpbWVzIGEgQmxpc3MgbWV0aG9kIHRoYXQgd2lsbCBvdXRwZXJmb3JtIGFueSBIU0EtYmFzZWQgb25lLCBpbiBhbnkgZ2l2ZW4gY2VsbCBsaW5lKiouCjo6OgoKIyMgQmxpc3MgdnMgSFNBIChDb3JyZWxhdGlvbikgey50YWJzZXR9CgpOb3cgd2UgdGlkeSBvdXIgcmF3IGRhdGEsIGluIG9yZGVyIHRvIGNvbXBhcmUgKipob21vZ2Vub3VzIGRhdGEgcG9pbnRzKiogLSBpLmUuIGNvbWluZyBmcm9tIHRoZSBzYW1lIHRhc2ssIHBhcmFtZXRlcnMgYW5kIGNlbGwgbGluZSwgb25seSBkaWZmZXJlbnQgc3luZXJneSBtZXRob2QgKEJsaXNzIHZzIEhTQSkuCgojIyMgQXZnIHRhc2sKCmBgYHtyIEJsaXNzIHZzIEhTQSBjb3JyZWxhdGlvbiAoQXZnIHRhc2spLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD02LCBmaWcuYWxpZ249ImNlbnRlciIsIGNhY2hlPVRSVUV9CmRhdGEudGFzay5hdmcgPSByZXMuZGF0YSAlPiUgCiAgZmlsdGVyKHRhc2sgPT0gIkF2ZyIpICU+JSAKICBncm91cF9ieShjaV9tZXRob2QpICU+JSAKICBhcnJhbmdlKGNpX21ldGhvZCwgZXhjZXNzX3RocmVzaG9sZCwgY2VsbF9saW5lKSAlPiUKICB0cmFuc211dGUoY2VsbF9saW5lLCBleGNlc3NfdGhyZXNob2xkLCBmMS5zY29yZSkgJT4lIAogIGdyb3VwX3NwbGl0KCkgJT4lIAogIGJpbmRfY29scygpCgojIGNvdW50IEJsaXNzIHZzIEhTQSBGMS1TY29yZXMKdGFzay5hdmcuY291bnRzID0gZGF0YS50YXNrLmF2ZyAlPiUgCiAgdHJhbnNtdXRlKGVxdWFsID0gKGYxLnNjb3JlID09IGYxLnNjb3JlMSksIGxlc3MgPSAoZjEuc2NvcmUgPCBmMS5zY29yZTEpLCBsYXJnZXIgPSAoZjEuc2NvcmUgPiBmMS5zY29yZTEpKSAlPiUgCiAgc3VtbWFyaXNlKGVxdWFsID0gc3VtKGVxdWFsKSwgbGVzcyA9IHN1bShsZXNzKSwgbGFyZ2VyID0gc3VtKGxhcmdlcikpCgpnZ3NjYXR0ZXIoZGF0YSA9IGRhdGEudGFzay5hdmcsIHggPSAiZjEuc2NvcmUiLCB5ID0gImYxLnNjb3JlMSIsIGNvbG9yID0gImNlbGxfbGluZSIsCiAgeGxhYiA9ICJCbGlzcyBGMS1TY29yZXMiLCB5bGFiID0gIkhTQSBGMS1TY29yZXMiLAogIHRpdGxlID0gIidBdmcnIHRhc2sgKEtlbmRhbGwgQ29ycmVsYXRpb24pIiwKICBhZGQgPSAicmVnLmxpbmUiLCBhZGQucGFyYW1zID0gbGlzdChjb2xvciA9ICJibHVlIiwgZmlsbCA9ICJsaWdodGdyYXkiKSwKICBjb25mLmludCA9IFRSVUUsIGNvci5jb2VmID0gVFJVRSwgCiAgY29yLmNvZWZmLmFyZ3MgPSBsaXN0KG1ldGhvZCA9ICJrZW5kYWxsIiwgbGFiZWwueC5ucGMgPSAwLjcsIGxhYmVsLnkubnBjID0gMC4xKSkgKyAKICBnZW9tX3NlZ21lbnQoeCA9IDAsIHkgPSAwLCB4ZW5kID0gMSwgeWVuZCA9IDEsIHNpemUgPSAwLjMsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBhbm5vdGF0ZShnZW9tID0gInRleHQiLCBsYWJlbCA9ICJ5ID0geCIsIHggPSAwLjkzLCB5ID0gMSkgKwogIGFubm90YXRlKGdlb20gPSAidGV4dCIsIGxhYmVsID0gcGFzdGUwKCJCbGlzcyA8IEhTQTogIiwgc2lnbmlmKDEwMCp0YXNrLmF2Zy5jb3VudHMkbGVzcy9zdW0odGFzay5hdmcuY291bnRzKSwgZGlnaXRzID0gMiksICIlIiksIHggPSAwLjE1LCB5ID0gMSkgKwogIGFubm90YXRlKGdlb20gPSAidGV4dCIsIGxhYmVsID0gcGFzdGUwKCJCbGlzcyA9IEhTQTogIiwgc2lnbmlmKDEwMCp0YXNrLmF2Zy5jb3VudHMkZXF1YWwvc3VtKHRhc2suYXZnLmNvdW50cyksIGRpZ2l0cyA9IDIpLCAiJSIpLCB4ID0gMC4xNSwgeSA9IDAuOSkgKwogIGFubm90YXRlKGdlb20gPSAidGV4dCIsIGxhYmVsID0gcGFzdGUwKCJCbGlzcyA+IEhTQTogIiwgc2lnbmlmKDEwMCp0YXNrLmF2Zy5jb3VudHMkbGFyZ2VyL3N1bSh0YXNrLmF2Zy5jb3VudHMpLCBkaWdpdHMgPSAyKSwgIiUiKSwgeCA9IDAuMTUsIHkgPSAwLjgpCmBgYAoKIyMjIENvbkV4Y2VzcyB0YXNrCgpgYGB7ciBCbGlzcyB2cyBIU0EgY29ycmVsYXRpb24gKENvbkV4Y2VzcyB0YXNrKSwgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9NiwgZmlnLmFsaWduPSJjZW50ZXIiLCBjYWNoZT1UUlVFfQpkYXRhLnRhc2suY29uLmV4Y2VzcyA9IHJlcy5kYXRhICU+JSAKICBmaWx0ZXIodGFzayA9PSAiQ29uRXhjZXNzIikgJT4lIAogIGdyb3VwX2J5KGNpX21ldGhvZCkgJT4lIAogIGFycmFuZ2UoY2lfbWV0aG9kLCBjb25zZWN1dGl2ZV9leGNlc3NfY291bnQsIGV4Y2Vzc190aHJlc2hvbGQsIGNlbGxfbGluZSkgJT4lCiAgdHJhbnNtdXRlKGNvbnNlY3V0aXZlX2V4Y2Vzc19jb3VudCwgZXhjZXNzX3RocmVzaG9sZCwgY2VsbF9saW5lLCBmMS5zY29yZSkgJT4lIAogIGdyb3VwX3NwbGl0KCkgJT4lIAogIGJpbmRfY29scygpCgojIGNvdW50IEJsaXNzIHZzIEhTQSBGMS1TY29yZXMKdGFzay5jb24uZXhjZXNzLmNvdW50cyA9IGRhdGEudGFzay5jb24uZXhjZXNzICU+JSAKICB0cmFuc211dGUoZXF1YWwgPSAoZjEuc2NvcmUgPT0gZjEuc2NvcmUxKSwgbGVzcyA9IChmMS5zY29yZSA8IGYxLnNjb3JlMSksIGxhcmdlciA9IChmMS5zY29yZSA+IGYxLnNjb3JlMSkpICU+JSAKICBzdW1tYXJpc2UoZXF1YWwgPSBzdW0oZXF1YWwpLCBsZXNzID0gc3VtKGxlc3MpLCBsYXJnZXIgPSBzdW0obGFyZ2VyKSkKCmdnc2NhdHRlcihkYXRhID0gZGF0YS50YXNrLmNvbi5leGNlc3MsIHggPSAiZjEuc2NvcmUiLCB5ID0gImYxLnNjb3JlMSIsIGNvbG9yID0gImNlbGxfbGluZSIsCiAgeGxhYiA9ICJCbGlzcyBGMS1TY29yZXMiLCB5bGFiID0gIkhTQSBGMS1TY29yZXMiLAogIHRpdGxlID0gIidDb25FeGNlc3MnIHRhc2sgKEtlbmRhbGwgQ29ycmVsYXRpb24pIiwKICBhZGQgPSAicmVnLmxpbmUiLCBhZGQucGFyYW1zID0gbGlzdChjb2xvciA9ICJibHVlIiwgZmlsbCA9ICJsaWdodGdyYXkiKSwKICBjb25mLmludCA9IFRSVUUsIGNvci5jb2VmID0gVFJVRSwgCiAgY29yLmNvZWZmLmFyZ3MgPSBsaXN0KG1ldGhvZCA9ICJrZW5kYWxsIiwgbGFiZWwueC5ucGMgPSAwLjcsIGxhYmVsLnkubnBjID0gMC4xKSkgKyAKICBnZW9tX3NlZ21lbnQoeCA9IDAsIHkgPSAwLCB4ZW5kID0gMSwgeWVuZCA9IDEsIHNpemUgPSAwLjMsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBhbm5vdGF0ZShnZW9tID0gInRleHQiLCBsYWJlbCA9ICJ5ID0geCIsIHggPSAwLjkzLCB5ID0gMSkgKyAKICBhbm5vdGF0ZShnZW9tID0gInRleHQiLCBsYWJlbCA9IHBhc3RlMCgiQmxpc3MgPCBIU0E6ICIsIHNpZ25pZigxMDAqdGFzay5jb24uZXhjZXNzLmNvdW50cyRsZXNzL3N1bSh0YXNrLmNvbi5leGNlc3MuY291bnRzKSwgZGlnaXRzID0gMiksICIlIiksIHggPSAwLjE1LCB5ID0gMSkgKwogIGFubm90YXRlKGdlb20gPSAidGV4dCIsIGxhYmVsID0gcGFzdGUwKCJCbGlzcyA9IEhTQTogIiwgc2lnbmlmKDEwMCp0YXNrLmNvbi5leGNlc3MuY291bnRzJGVxdWFsL3N1bSh0YXNrLmNvbi5leGNlc3MuY291bnRzKSwgZGlnaXRzID0gMiksICIlIiksIHggPSAwLjE1LCB5ID0gMC45KSArCiAgYW5ub3RhdGUoZ2VvbSA9ICJ0ZXh0IiwgbGFiZWwgPSBwYXN0ZTAoIkJsaXNzID4gSFNBOiAiLCBzaWduaWYoMTAwKnRhc2suY29uLmV4Y2Vzcy5jb3VudHMkbGFyZ2VyL3N1bSh0YXNrLmNvbi5leGNlc3MuY291bnRzKSwgZGlnaXRzID0gMiksICIlIiksIHggPSAwLjE1LCB5ID0gMC44KQpgYGAKCiMjIyBDb25Qcm9wRXhjZXNzIHRhc2sKCmBgYHtyIEJsaXNzIHZzIEhTQSBjb3JyZWxhdGlvbiAoQ29uUHJvcEV4Y2VzcyB0YXNrKSwgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9NiwgZmlnLmFsaWduPSJjZW50ZXIiLCBjYWNoZT1UUlVFfQpkYXRhLnRhc2suY29uLnByb3AuZXhjZXNzID0gcmVzLmRhdGEgJT4lIAogIGZpbHRlcih0YXNrID09ICJDb25Qcm9wRXhjZXNzIikgJT4lIAogIGdyb3VwX2J5KGNpX21ldGhvZCkgJT4lIAogIGFycmFuZ2UoY2lfbWV0aG9kLCBjb25zZWN1dGl2ZV9leGNlc3NfY291bnQsIGV4Y2Vzc19wcm9wb3J0aW9uYWxfdGhyZXNob2xkLCBjZWxsX2xpbmUpICU+JQogIHRyYW5zbXV0ZShjb25zZWN1dGl2ZV9leGNlc3NfY291bnQsIGV4Y2Vzc19wcm9wb3J0aW9uYWxfdGhyZXNob2xkLCBjZWxsX2xpbmUsIGYxLnNjb3JlKSAlPiUgCiAgZ3JvdXBfc3BsaXQoKSAlPiUgCiAgYmluZF9jb2xzKCkKCiMgY291bnQgQmxpc3MgdnMgSFNBIEYxLVNjb3Jlcwp0YXNrLmNvbi5wcm9wLmV4Y2Vzcy5jb3VudHMgPSBkYXRhLnRhc2suY29uLnByb3AuZXhjZXNzICU+JSAKICB0cmFuc211dGUoZXF1YWwgPSAoZjEuc2NvcmUgPT0gZjEuc2NvcmUxKSwgbGVzcyA9IChmMS5zY29yZSA8IGYxLnNjb3JlMSksIGxhcmdlciA9IChmMS5zY29yZSA+IGYxLnNjb3JlMSkpICU+JSAKICBzdW1tYXJpc2UoZXF1YWwgPSBzdW0oZXF1YWwpLCBsZXNzID0gc3VtKGxlc3MpLCBsYXJnZXIgPSBzdW0obGFyZ2VyKSkKCmdnc2NhdHRlcihkYXRhID0gZGF0YS50YXNrLmNvbi5wcm9wLmV4Y2VzcywgeCA9ICJmMS5zY29yZSIsIHkgPSAiZjEuc2NvcmUxIiwgY29sb3IgPSAiY2VsbF9saW5lIiwKICB4bGFiID0gIkJsaXNzIEYxLVNjb3JlcyIsIHlsYWIgPSAiSFNBIEYxLVNjb3JlcyIsCiAgdGl0bGUgPSAiJ0NvblByb3BFeGNlc3MnIHRhc2sgKEtlbmRhbGwgQ29ycmVsYXRpb24pIiwKICBhZGQgPSAicmVnLmxpbmUiLCBhZGQucGFyYW1zID0gbGlzdChjb2xvciA9ICJibHVlIiwgZmlsbCA9ICJsaWdodGdyYXkiKSwKICBjb25mLmludCA9IFRSVUUsIGNvci5jb2VmID0gVFJVRSwgCiAgY29yLmNvZWZmLmFyZ3MgPSBsaXN0KG1ldGhvZCA9ICJrZW5kYWxsIiwgbGFiZWwueC5ucGMgPSAwLjcsIGxhYmVsLnkubnBjID0gMC4xKSkgKyAKICBnZW9tX3NlZ21lbnQoeCA9IDAsIHkgPSAwLCB4ZW5kID0gMC44LCB5ZW5kID0gMC44LCBzaXplID0gMC4zLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgYW5ub3RhdGUoZ2VvbSA9ICJ0ZXh0IiwgbGFiZWwgPSAieSA9IHgiLCB4ID0gMC43MywgeSA9IDAuOCkgKwogIGFubm90YXRlKGdlb20gPSAidGV4dCIsIGxhYmVsID0gcGFzdGUwKCJCbGlzcyA8IEhTQTogIiwgc2lnbmlmKDEwMCp0YXNrLmNvbi5wcm9wLmV4Y2Vzcy5jb3VudHMkbGVzcy9zdW0odGFzay5jb24ucHJvcC5leGNlc3MuY291bnRzKSwgZGlnaXRzID0gMiksICIlIiksIHggPSAwLjE1LCB5ID0gMC44KSArCiAgYW5ub3RhdGUoZ2VvbSA9ICJ0ZXh0IiwgbGFiZWwgPSBwYXN0ZTAoIkJsaXNzID0gSFNBOiAiLCBzaWduaWYoMTAwKnRhc2suY29uLnByb3AuZXhjZXNzLmNvdW50cyRlcXVhbC9zdW0odGFzay5jb24ucHJvcC5leGNlc3MuY291bnRzKSwgZGlnaXRzID0gMiksICIlIiksIHggPSAwLjE1LCB5ID0gMC43KSArCiAgYW5ub3RhdGUoZ2VvbSA9ICJ0ZXh0IiwgbGFiZWwgPSBwYXN0ZTAoIkJsaXNzID4gSFNBOiAiLCBzaWduaWYoMTAwKnRhc2suY29uLnByb3AuZXhjZXNzLmNvdW50cyRsYXJnZXIvc3VtKHRhc2suY29uLnByb3AuZXhjZXNzLmNvdW50cyksIGRpZ2l0cyA9IDIpLCAiJSIpLCB4ID0gMC4xNSwgeSA9IDAuNikKYGBgCgojIyB7LX0KCjo6OiB7LmJsdWUtYm94fQotIEJsaXNzIHJlc3VsdHMgY29ycmVsYXRlIHdpdGggSFNBIG9uZXMgbW9yZSBvbiBgQ29uUHJvcEV4Y2Vzc2AgdGFzaywgbGVzcyBvbiB0aGUgYENvbkV4Y2Vzc2AgdGFzayBhbmQgZXZlbiBsZXNzIG9uIHRoZSBgQXZnYCB0YXNrLiAKU28gdGhlICoqbW9yZSBzb3BoaXN0aWNhdGVkKiogdGhlIG1ldGhvZCBpcywgdGhlICoqbGFyZ2VyIHRoZSBjb3JyZWxhdGlvbioqIGJldHdlZW4gQmxpc3MgYW5kIEhTQSAobW9yZSBjb21tb24gRjEtU2NvcmVzKS4KLSAqKkluIG1vcmUgdGhhbiBoYWxmIG9mIHRoZSBkYXRhIHBvaW50cyoqIGNvbGxlY3RlZCBhY3Jvc3MgYWxsIGNlbGwgbGluZXMgYW5kIHRhc2sgcGFyYW1ldGVycywgKipCbGlzcyBwcm9kdWNlcyBhbiBlcXVhbCBGMS1TY29yZSB3aXRoIHRoZSBIU0EtYmFzZWQgbWV0aG9kcyoqLgotIFdoZXJlIHRoZSByZXN1bHRzIGJldHdlZW4gQmxpc3MgYW5kIEhTQSBkaWZmZXI6IAogIC0gSW4gdGhlIGBBdmdgIGFuZCBgQ29uRXhjZXNzYCB0YXNrcywgKipIU0EgaGFzIGEgbGFyZ2VyIHBlcmNlbnRhZ2Ugb2YgRjEtU2NvcmVzKiogKCRcc2ltNVwlJCBhbmQgJFxzaW0gOVwlJCBkaWZmZXJlbmNlIHJlc3BlY3RpdmVseSkuCiAgLSBJbiB0aGUgYENvblByb3BFeGNlc3NgIHRhc2ssICoqQmxpc3MgaGFzIGEgbGFyZ2VyIHBlcmNlbnRhZ2Ugb2YgRjEtU2NvcmVzKiogKCRcc2ltIDEzXCUkIGRpZmZlcmVuY2UpLgotIFRoZSBhYm92ZSByZWZsZWN0cyB0aGUgcmVzdWx0cyBvZiBbdGhpcyBmaWd1cmVdKCNidmhwdCkgd2hpY2ggc2hvd3MgQmxpc3MgaGF2aW5nIGEgc3RhdGlzdGljYWwgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBmcm9tIEhTQSBmb3IgdGhlIGBDb25Qcm9wRXhjZXNzYCB0YXNrLgogIFRoaXMgYWxzbyBtZWFucyB0aGF0IHRoZSB2YWx1ZSBkaWZmZXJlbmNlcyBvZiB0aGUgRjEtU2NvcmVzIGJldHdlZW4gSFNBIGFuZCBCbGlzcyBhcmUgbGFyZ2VyIGluIG1hZ25pdHVkZSBpbiB0aGUgYENvblByb3BFeGNlc3NgIHRhc2sgY29tcGFyZWQgdG8gdGhlIG90aGVyIHR3byB0YXNrcwogIChzaW5jZSB0aGUgZGlmZmVyZW5jZSBwZXJjZW50YWdlcyBzb21ld2hhdCBuZWdhdGUgZWFjaCBvdGhlciBhY3Jvc3MgdGhlIDMgdGFza3MgYnV0IHRoZXJlIGlzIGEgZGlmZmVyZW5jZSBpbiB0aGUgW292ZXJhbGwgZGF0YSBpbiBmYXZvciBvZiBCbGlzc10oI2J2c2EpKS4KLSBJbiBhbGwgMyBncmFwaHMgYWJvdmUsICoqdGhlIHJlZ3Jlc3Npb24gbGluZSBpcyBiZWxvdyB0aGUgYHk9eGAgbGluZSoqIHdoaWNoIG1lYW5zIHRoYXQgdGhlcmUgYXJlIGhpZ2hlciBCbGlzcyB2YWx1ZXMgcHJvZHVjZWQgYnkgc29tZSBtZXRob2RzIChubyBtYXR0ZXIgdGhlIHRhc2sgb3IgY2VsbCBsaW5lKTogdGhlc2UgKioob3V0bGllci1zb3J0LW9mKSBCbGlzcyByZXN1bHRzIG91dHBlcmZvcm0gdGhlIEhTQSBzaW1pbGFyIG9uZXMqKi4KOjo6CgojIyBIaWdoZXN0IEYxLVNjb3JlZCBUYXNrCgo6Ojogey5pbmZvLWJveCAuaW1wb3J0YW50fQpPdXIgam9iIG5vdyBpcyB0byBmaW5kIHRoZSB0YXNrIHdpdGggdGhlIHNldCBvZiBwYXJhbWV0ZXJzIHRoYXQgd2lsbCBnaXZlIHRoZSAqKmJlc3QgKEhTQSBvciBCbGlzcy1iYXNlZCkgRjEtc2NvcmUgYWNyb3NzIGFsbCB0aGUgY2VsbCBsaW5lcyoqIHRlc3RlZCAtIGkuZS4gdGhlIHRhc2sgdGhhdCBiZXN0ICpmaXRzKiB0aGUgY3VyYXRlZCBHUyBhY3Jvc3MgYWxsIGNlbGwgbGluZXMuCgpTbyB3ZSB3aWxsIGFnZ3JhZ2F0ZSB0aGUgcmVzdWx0cyBhY3Jvc3MgdGhlIDggY2VsbCBsaW5lcyBieSBwcm9kdWNpbmcgKip0aGUgYXZlcmFnZSBGMS1TY29yZSoqIGZvciBlYWNoIHNwZWNpZmljIHN5bmVyZ3kgdGFzayB3aXRoIGFsbCBpdHMgY29tYmluYXRpb24gb2YgcGFyYW1ldGVycyAoZm9yIGJvdGggQmxpc3MgYW5kIEhTQSkuCjo6OgoKV2UgY2FsY3VsYXRlIHRoZSBhdmVyYWdlIEYxLVNjb3JlcyBwZXIgdGFzayBhbmQgZmluZCB0aGUgbWF4aW11bSBvbmVzIGFjcm9zcyBhbGwgY2VsbCBsaW5lczoKYGBge3IgRmluZCBmaXR0ZXN0IHRhc2t9CiMgQXZnIHRhc2sKYXZnLmYxLnRhc2suYXZnID0gcmVzLmRhdGEgJT4lIAogIGZpbHRlcih0YXNrID09ICJBdmciKSAlPiUgCiAgZ3JvdXBfYnkoY2lfbWV0aG9kLCBleGNlc3NfdGhyZXNob2xkKSAlPiUgCiAgc3VtbWFyaXNlKGF2Zy5mMS5zY29yZSA9IG1lYW4oZjEuc2NvcmUpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgYWRkX2NvbHVtbih0YXNrID0gIkF2ZyIsIC5iZWZvcmUgPSAxKQoKZGF0YS5saXN0ID0gbGlzdCgpCmRhdGEubGlzdFtbMV1dID0gCiAgYXZnLmYxLnRhc2suYXZnICU+JSAKICBncm91cF9ieShjaV9tZXRob2QpICU+JSAKICBmaWx0ZXIoYXZnLmYxLnNjb3JlID09IG1heChhdmcuZjEuc2NvcmUpKSAlPiUKICB1bmdyb3VwKCkKCiMgQ29uRXhjZXNzIHRhc2sKYXZnLmYxLnRhc2suY29uLmV4Y2VzcyA9IHJlcy5kYXRhICU+JSAKICBmaWx0ZXIodGFzayA9PSAiQ29uRXhjZXNzIikgJT4lIAogIGdyb3VwX2J5KGNpX21ldGhvZCwgY29uc2VjdXRpdmVfZXhjZXNzX2NvdW50LCBleGNlc3NfdGhyZXNob2xkKSAlPiUgCiAgc3VtbWFyaXNlKGF2Zy5mMS5zY29yZSA9IG1lYW4oZjEuc2NvcmUpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgYWRkX2NvbHVtbih0YXNrID0gIkNvbkV4Y2VzcyIsIC5iZWZvcmUgPSAxKQoKZGF0YS5saXN0W1syXV0gPSAKICBhdmcuZjEudGFzay5jb24uZXhjZXNzICU+JSAKICBncm91cF9ieShjaV9tZXRob2QpICU+JSAKICBmaWx0ZXIoYXZnLmYxLnNjb3JlID09IG1heChhdmcuZjEuc2NvcmUpKSAlPiUKICB1bmdyb3VwKCkKCiMgQ29uUHJvcEV4Y2VzcyB0YXNrCmF2Zy5mMS50YXNrLmNvbi5wcm9wLmV4Y2VzcyA9IHJlcy5kYXRhICU+JSAKICBmaWx0ZXIodGFzayA9PSAiQ29uUHJvcEV4Y2VzcyIpICU+JSAKICBncm91cF9ieShjaV9tZXRob2QsIGNvbnNlY3V0aXZlX2V4Y2Vzc19jb3VudCwgZXhjZXNzX3Byb3BvcnRpb25hbF90aHJlc2hvbGQpICU+JSAKICBzdW1tYXJpc2UoYXZnLmYxLnNjb3JlID0gbWVhbihmMS5zY29yZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBhZGRfY29sdW1uKHRhc2sgPSAiQ29uUHJvcEV4Y2VzcyIsIC5iZWZvcmUgPSAxKQoKZGF0YS5saXN0W1szXV0gPSAKICBhdmcuZjEudGFzay5jb24ucHJvcC5leGNlc3MgJT4lIAogIGdyb3VwX2J5KGNpX21ldGhvZCkgJT4lIAogIGZpbHRlcihhdmcuZjEuc2NvcmUgPT0gbWF4KGF2Zy5mMS5zY29yZSkpICU+JQogIHVuZ3JvdXAoKQoKYmVzdC5hdmcuZjEuc2NvcmVzID0gYmluZF9yb3dzKGRhdGEubGlzdCkgJT4lIAogIHNlbGVjdCh0YXNrLCBjaV9tZXRob2QsIGF2Zy5mMS5zY29yZSwgZXhjZXNzX3RocmVzaG9sZCwgY29uc2VjdXRpdmVfZXhjZXNzX2NvdW50LCBleGNlc3NfcHJvcG9ydGlvbmFsX3RocmVzaG9sZCkKbWF4LmF2Zy5mMS5zY29yZSA9IG1heChiZXN0LmF2Zy5mMS5zY29yZXMkYXZnLmYxLnNjb3JlKQoKIyBjb2xvciBhdmcuZjEuc2NvcmVzCmYxLmJyZWFrcyA9IHF1YW50aWxlKGJlc3QuYXZnLmYxLnNjb3JlcyAlPiUgcHVsbChhdmcuZjEuc2NvcmUpLCBwcm9icyA9IHNlcSguMDUsIC45NSwgLjA1KSkKZjEuY29sb3JzID0gcm91bmQoc2VxKDI1NSwgNDAsIGxlbmd0aC5vdXQgPSBsZW5ndGgoZjEuYnJlYWtzKSArIDEpLCAwKSAlPiUKICB7cGFzdGUwKCJyZ2IoMjU1LCIsIC4sICIsIiwgLiwgIikiKX0gIyByZWQKCm9wdGlvbnMocGVyc2lzdGVudCA9IFRSVUUpCgpjYXB0aW9uLnRpdGxlID0gIkhpZ2hlc3QgYXZlcmFnZSBGMS1TY29yZSBUYXNrcyB3aXRoIHJlc3BlY3RpdmUgcGFyYW1ldGVycyIKZGF0YXRhYmxlKGRhdGEgPSBiZXN0LmF2Zy5mMS5zY29yZXMsCiAgb3B0aW9ucyA9IGxpc3QoZG9tID0gInQiLCAjIGp1c3Qgc2hvdyB0aGUgdGFibGUKICBvcmRlciA9IGxpc3QobGlzdCgzLCAnZGVzYycpKSwKICBjb2x1bW5EZWZzID0gbGlzdChsaXN0KGNsYXNzTmFtZSA9ICdkdC1jZW50ZXInLCB0YXJnZXRzID0gMzo2KSkpLAogIGNhcHRpb24gPSBodG1sdG9vbHM6OnRhZ3MkY2FwdGlvbihjYXB0aW9uLnRpdGxlLCBzdHlsZT0iY29sb3I6I2RkNDgxNDsgZm9udC1zaXplOiAxOHB4IikpICU+JQogIGZvcm1hdFN0eWxlKGNvbHVtbnMgPSBjKCJhdmcuZjEuc2NvcmUiKSwgYmFja2dyb3VuZENvbG9yID0gc3R5bGVJbnRlcnZhbChmMS5icmVha3MsIGYxLmNvbG9ycykpICU+JQogIGZvcm1hdFJvdW5kKDM6NiwgZGlnaXRzID0gMykKYGBgCgpBcyB3ZSBleHBlY3RlZCBmcm9tIHRoZSByZXN1bHRzIG9mIGFuIFtlYXJsaWVyIGdyYXBoXSgjYnZocHQpLCB0aGUgYENvblByb3BFeGNlc3NgIHRhc2sgcHJvZHVjZXMgdGhlIGxvd2VzdCBhbW9uZyB0aGUgbWF4aW11bSBhdmVyYWdlIEYxLVNjb3JlcyBhY3Jvc3MgYWxsIHRhc2tzIHRlc3RlZCAoYW5kIGl0J3MgQmxpc3MgRjEtU2NvcmUgaXMgbGFyZ2VyIHRoYW4gdGhlIHJlc3BlY3RpdmUgSFNBKS4KCioqVGhlIGJlc3QgYXZlcmFnZSBGMS1TY29yZSoqIGFjcm9zcyBhbGwgOCBjZWxsIGxpbmVzIHdhcyBwcm9kdWNlZCBieSB0aGUgYENvbkV4Y2Vzc2AgW21ldGhvZF0oI3N5bmVyZ3ktbWV0aG9kcykuClRoZSBgcmJidGAgY29tbWFuZCBsaW5lIHBhcmFtZXRlcnMgdG8gcHJvZHVjZSB0aGUgc3luZXJnaWVzIHBlciBnaXZlbiBjZWxsIGxpbmUgdXNpbmcgdGhlIGJlc3QgdGFzayBhcmU6CgpgYGAKcmJidCB3b3JrZmxvdyB0YXNrIFNJTlRFRiBzeW5lcmd5X2NsYXNzaWZpY2F0aW9uX2J5X2NvbnNlY3V0aXZlX2V4Y2Vzc2VzIC0tY29uc2VjdXRpdmVfZXhjZXNzX2NvdW50PTIgLS1leGNlc3NfdGhyZXNob2xkPS0wLjE3IC0tY2lfbWV0aG9kPWhzYSAtLWNlbGxfbGluZT08TkFNRT4gfCBncmVwIC12IFNGCmBgYAo8YnI+CgpUaGUgcGVyLWNlbGwtbGluZSBGMS1TY29yZXMgcHJvZHVjZWQgYnkgdGhlIGJlc3QgdGFzayBhcyBmb3VuZCBhYm92ZSBhcmU6CjxkaXYgaWQ9ImJ0aWNsZjEiPjwvZGl2PgpgYGB7ciBCZXN0IFRhc2sgSW5kaXZpZHVhbCBDZWxsIGxpbmUgRjEtU2NvcmVzfQpiZXN0LnRhc2suZGF0YSA9IHJlcy5kYXRhICU+JQogIGZpbHRlcih0YXNrID09ICJDb25FeGNlc3MiLCBjb25zZWN1dGl2ZV9leGNlc3NfY291bnQgPT0gMiwgZXhjZXNzX3RocmVzaG9sZCA9PSAtMC4xNywgY2lfbWV0aG9kID09ICJoc2EiKSAlPiUKICBzZWxlY3QoY2VsbF9saW5lLCBmMS5zY29yZSkKZGF0YXRhYmxlKGRhdGEgPSBiZXN0LnRhc2suZGF0YSwgb3B0aW9ucyA9IGxpc3QoZG9tID0gInQiKSkKYGBgCgo6Ojogey5ncmVlbi1ib3h9CkZvciB0aGUgYEFHU2AgY2VsbCBsaW5lIHdlIGhhdmUgYSAqKnBlcmZlY3Qgc3luZXJneSBhc3NlbWVudCoqOiB0aGUgYmVzdCB0YXNrIHJlc3VsdHMgbWF0Y2ggZXhhY3RseSB0aGUgc2FtZSBzeW5lcmdpZXMgYXMgdGhlIGN1cmF0ZWQgR29sZC1TdGFuZGFyZCBmb3IgYEFHU2AhCjo6OgoKIyMgR1MgdnMgQmVzdCBUYXNrCgpXZSBoYXZlIGV4ZWN1dGVkIHRoZSBbcnVuX2Jlc3RfdGFzay5SXShodHRwczovL2dpdGh1Yi5jb20vZHJ1Z2xvZ2ljcy9zaW50ZWYtb2JzLXN5bmVyZ2llcy9ibG9iL21hc3Rlci9ydW5fYmVzdF90YXNrLlIpIHNjcmlwdCBpbiBvcmRlciB0byBnZXQgdGhlIHN5bmVyZ2llcyBmb3VuZCB3aXRoIHRoaXMgYmVzdCBjaG9zZW4gdGFzayBhbmQgcGFyYW1ldGVycy4KV2UgbG9hZCB0aGlzIGxpc3Qgb2Ygc3luZXJnaWVzOgpgYGB7ciBCZXN0IHRhc2sgc3luZXJnaWVzfQojICdiZXN0Lmxpc3QnID0gYmVzdCBsaXN0IChvZiBzeW5lcmdpZXMpIHZzICdncy5saXN0JyA9IGdvbGQgc3RhbmRhcmQgbGlzdCAob2Ygc3luZXJnaWVzKQpiZXN0Lmxpc3QgPSByZWFkUkRTKGZpbGUgPSAiYmVzdF9kYXRhLnJkcyIpCmBgYAoKU28sIGZvciBgQUdTYCB0aGUgcmVzdWx0cyBhcmUgdGhlIHNhbWUgYnV0IHdoYXQgYWJvdXQgdGhlIG90aGVyIGNlbGwgbGluZXMgdGhhdCBkaWRuJ3QgaGF2ZSBhIHBlcmZlY3QgRjEtU2NvcmU/CldlIHdpbGwgbm93IHRha2UgYSBjbG9zZXIgbG9vayBhdCB0aGUgYEZQYCBhbmQgYEZOYCBzeW5lcmdpZXMgcHJvZHVjZWQgYnkgdGhlIGJlc3QgdGFzayBmb3IgZWFjaCBjZWxsIGxpbmU6CmBgYHtyIEZQIGFuZCBGTiBzeW5lcmdpZXMgZm9yIGJlc3QgdGFzaywgcmVzdWx0cz0nYXNpcyd9CmJlc3QudGFzay5wcmludCA9IGZ1bmN0aW9uKCkgewogIHByZXR0eV9wcmludF9zdHJpbmcoIiIpCiAgZm9yIChjZWxsLmxpbmUgaW4gY2VsbC5saW5lcykgewogICAgcHJldHR5X3ByaW50X2JvbGRfc3RyaW5nKHN0cmluZyA9IGNlbGwubGluZSwgd2l0aC5ndCA9IEZBTFNFKQogICAgcHJpbnRfZW1wdHlfbGluZShodG1sLm91dHB1dCA9IFRSVUUpCiAgICAKICAgIGZwID0gYmVzdC5saXN0W1tjZWxsLmxpbmVdXVshYmVzdC5saXN0W1tjZWxsLmxpbmVdXSAlaW4lIGdzLmxpc3RbW2NlbGwubGluZV1dXQogICAgZm4gPSBncy5saXN0W1tjZWxsLmxpbmVdXVshZ3MubGlzdFtbY2VsbC5saW5lXV0gJWluJSBiZXN0Lmxpc3RbW2NlbGwubGluZV1dXQogICAgCiAgICBwcmV0dHlfcHJpbnRfdmVjdG9yX3ZhbHVlcyh2ZWMgPSBmcCwgdmVjdG9yLnZhbHVlcy5zdHIgPSAiRmFsc2UgUG9zaXRpdmVzIiwgd2l0aC5ndCA9IEZBTFNFKQogICAgcHJpbnRfZW1wdHlfbGluZShodG1sLm91dHB1dCA9IFRSVUUpCiAgICAKICAgIHByZXR0eV9wcmludF92ZWN0b3JfdmFsdWVzKHZlYyA9IGZuLCB2ZWN0b3IudmFsdWVzLnN0ciA9ICJGYWxzZSBOZWdhdGl2ZXMiLCB3aXRoLmd0ID0gRkFMU0UpCiAgICBwcmludF9lbXB0eV9saW5lKGh0bWwub3V0cHV0ID0gVFJVRSkKICAgIHByaW50X2VtcHR5X2xpbmUoaHRtbC5vdXRwdXQgPSBUUlVFKQogIH0KfQpiZXN0LnRhc2sucHJpbnQoKQpgYGAKCjo6OiB7LmJsdWUtYm94fQpJbiB0b3RhbCwgdGhlcmUgYXJlICQxNiQgZmFsc2UgcG9zaXRpdmVzIChzeW5lcmdpZXMgdGhlIGJlc3QgbWV0aG9kIGdvdCB3cm9uZykgYW5kICQxMSQgZmFsc2UgbmVnYXRpdmVzIChzeW5lcmdpZXMgdGhlIGJlc3QgbWV0aG9kIGRpZG4ndCBnZXQgYXQgYWxsKSBjb21wYXJpbmcgdG8gdGhlIGN1cmF0aW9uIGdvbGQtc3RhbmRhcmQgc3luZXJnaWVzLgoKVGhpcyBpcyBpbnRyaWd1aW5nIHNpbmNlIHRoZSBiZXN0IG1ldGhvZCBgQ29uRXhjZXNzYCByZXByZXNlbnRzIGEga2luZCBvZiAqKnZpc3VhbCBtYXRoZW1hdGljYWwgbWV0aG9kKiogZm9yIHN5bmVyZ3kgaWRlbnRpZmljYXRpb246IGF0IGxlYXN0ICQyJCBjb25zZWN1dGl2ZSBwb2ludHMgaGF2ZSB0byBiZSBsZXNzIHRoYW4gdGhlICQtMC4xNyQgKHRocmVzaG9sZCkgZnJvbSB0aGUgbm9uLWludGVyYWN0aW9uIGxpbmUsIHdoaWNoIGhvbGRzIHRydWUgZm9yIGFsbCB0aGUgJEZQJCBzeW5lcmdpZXMgKGFuZCBub3QgZm9yIHRoZSAkRk4kIG9uZXMpIGJ1dCB0aGlzIGRpZmZlcnMgdG8gd2hhdCB0aGUgKmh1bWFuIGV5ZSogY2FwdHVyZXMgYXMgc3luZXJneSBvciBub3QhCjo6OgoKIyMgUGFyYW1ldGVyIFNwYWNlIFBsb3RzIHsudGFic2V0fQoKV2Ugbm93IHZpc3VhbGl6ZSB0aGUgKipwYXJhbWV0ZXIgc3BhY2UqKiBmb3IgZWFjaCB0YXNrIGFuZCBwZXIgKmNpX21ldGhvZCogKEJsaXNzIHZzIEhTQSk6CgojIyMgQXZnIHRhc2sKCmBgYHtyIFBhcmFtZXRlciBTcGFjZSAtIEF2ZyB0YXNrfQojIHRpdGxlIGZvbnQgc3R5bGUKbXkudGl0bGUuZm9udCA9IGxpc3QoZmFtaWx5ID0gIkNvdXJpZXIgTmV3LCBtb25vc3BhY2UiLCBzaXplID0gMTgsIGNvbG9yID0gImJsYWNrIikKCnBsb3RfbHkoZGF0YSA9IGF2Zy5mMS50YXNrLmF2ZywgeCA9IH5leGNlc3NfdGhyZXNob2xkLCB5ID0gfmF2Zy5mMS5zY29yZSwgd2lkdGggPSA4MDAsCiAgdHlwZSA9ICJzY2F0dGVyIiwgbW9kZSA9ICJsaW5lcyttYXJrZXJzIiwgY29sb3IgPSB+Y2lfbWV0aG9kLCBjb2xvcnMgPSAiU2V0MSIpICU+JQogIGxheW91dCh0aXRsZSA9IGxpc3QodGV4dCA9ICJBdmcgVGFzayAtIE1lYW4gRjEtU2NvcmUgYWNyb3NzIDggY2VsbCBsaW5lcyIsIGZvbnQgPSBteS50aXRsZS5mb250KSwKICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJFeGNlc3MgVGhyZXNob2xkIiwgemVyb2xpbmUgPSBGQUxTRSksCiAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTWVhbiBGMS1TY29yZSIpKSAlPiUKICBjb25maWcoZGlzcGxheU1vZGVCYXIgPSBGQUxTRSkKYGBgCgojIyMgQ29uRXhjZXNzIHRhc2sKCmBgYHtyIFBhcmFtZXRlciBDb250b3VyIHBsb3RzIC0gQ29uRXhjZXNzIFRhc2t9CiMgZm9udCBzdHlsZQpteS5jb2xvcmJhci5mb250ID0gbGlzdChmYW1pbHkgPSAiQ291cmllciBOZXcsIG1vbm9zcGFjZSIsIHNpemUgPSAxNCwgY29sb3IgPSAiYmxhY2siKQoKZmlnMSA9IHBsb3RfbHkoYXZnLmYxLnRhc2suY29uLmV4Y2VzcyAlPiUgZmlsdGVyKGNpX21ldGhvZCA9PSAiYmxpc3MiKSwgeCA9IH5jb25zZWN1dGl2ZV9leGNlc3NfY291bnQsIAogIHkgPSB+ZXhjZXNzX3RocmVzaG9sZCwgeiA9IH5hdmcuZjEuc2NvcmUsIHptaW4gPSAwLCB6bWF4ID0gbWF4LmF2Zy5mMS5zY29yZSwgd2lkdGggPSAxMDAwLAogIGNvbG9yc2NhbGUgPSAiSmV0IiwgdHlwZSA9ICJjb250b3VyIiwgY29udG91cnMgPSBsaXN0KHNob3dsYWJlbHMgPSBUUlVFKSkgJT4lIAogIGNvbG9yYmFyKHRpdGxlID0gbGlzdCh0ZXh0ID0gIk1lYW4gRjEtU2NvcmUgXG4oOCBjZWxsIGxpbmVzKSIsIGZvbnQgPSBteS5jb2xvcmJhci5mb250KSwgCiAgICB0aWNrbW9kZSA9ICJsaW5lYXIiLCB0aWNrMCA9IDAsIGR0aWNrID0gMC4xKSAlPiUKICBsYXlvdXQoYW5ub3RhdGlvbnMgPSBsaXN0KAogICAgbGlzdCh4ID0gMC41LCB5ID0gMSwgeGFuY2hvciA9ICJjZW50ZXIiLCB5YW5jaG9yID0gImJvdHRvbSIsIGFsaWduID0gImNlbnRlciIsIAogICAgICB4cmVmPSdwYXBlcicsIHlyZWY9J3BhcGVyJywgc2hvd2Fycm93ID0gRkFMU0UsIAogICAgICBmb250ID0gbXkudGl0bGUuZm9udCwgdGV4dCA9ICJDb25FeGNlc3MgVGFzayAtIEJsaXNzIikpKQoKZmlnMiA9IHBsb3RfbHkoYXZnLmYxLnRhc2suY29uLmV4Y2VzcyAlPiUgZmlsdGVyKGNpX21ldGhvZCA9PSAiaHNhIiksIHggPSB+Y29uc2VjdXRpdmVfZXhjZXNzX2NvdW50LCAKICB5ID0gfmV4Y2Vzc190aHJlc2hvbGQsIHogPSB+YXZnLmYxLnNjb3JlLCB6bWluID0gMCwgem1heCA9IG1heC5hdmcuZjEuc2NvcmUsIHdpZHRoID0gMTAwMCwKICBjb2xvcnNjYWxlID0gIkpldCIsIHR5cGUgPSAiY29udG91ciIsIGNvbnRvdXJzID0gbGlzdChzaG93bGFiZWxzID0gVFJVRSksIAogIHNob3dzY2FsZSA9IEZBTFNFKSAlPiUgbGF5b3V0KGFubm90YXRpb25zID0gbGlzdCgKICAgIGxpc3QoeCA9IDAuNSwgeSA9IDEsIHhhbmNob3IgPSAiY2VudGVyIiwgeWFuY2hvciA9ICJib3R0b20iLCBhbGlnbiA9ICJjZW50ZXIiLCAKICAgICAgeHJlZj0ncGFwZXInLCB5cmVmPSdwYXBlcicsIHNob3dhcnJvdyA9IEZBTFNFLCAKICAgICAgZm9udCA9IG15LnRpdGxlLmZvbnQsIHRleHQgPSAiQ29uRXhjZXNzIFRhc2sgLSBIU0EiKSkpCgpzdWJwbG90KGZpZzEsIGZpZzIsIHRpdGxlWCA9IFRSVUUsIHRpdGxlWSA9IFRSVUUsIG1hcmdpbiA9IDAuMDUpICU+JSAKICBjb25maWcoZGlzcGxheU1vZGVCYXIgPSBGQUxTRSkKYGBgCgojIyMgQ29uUHJvcEV4Y2VzcyB0YXNrCgpgYGB7ciBQYXJhbWV0ZXIgQ29udG91ciBwbG90cyAtIENvblByb3BFeGNlc3MgVGFza30KZmlnMSA9IHBsb3RfbHkoYXZnLmYxLnRhc2suY29uLnByb3AuZXhjZXNzICU+JSBmaWx0ZXIoY2lfbWV0aG9kID09ICJibGlzcyIpLCB4ID0gfmNvbnNlY3V0aXZlX2V4Y2Vzc19jb3VudCwgCiAgeSA9IH5leGNlc3NfcHJvcG9ydGlvbmFsX3RocmVzaG9sZCwgeiA9IH5hdmcuZjEuc2NvcmUsIHptaW4gPSAwLCB6bWF4ID0gbWF4LmF2Zy5mMS5zY29yZSwgd2lkdGggPSAxMDAwLAogIGNvbG9yc2NhbGUgPSAiSmV0IiwgdHlwZSA9ICJjb250b3VyIiwgY29udG91cnMgPSBsaXN0KHNob3dsYWJlbHMgPSBUUlVFKSkgJT4lIAogIGNvbG9yYmFyKHRpdGxlID0gbGlzdCh0ZXh0ID0gIk1lYW4gRjEtU2NvcmUgXG4oOCBjZWxsIGxpbmVzKSIsIGZvbnQgPSBteS5jb2xvcmJhci5mb250KSwgCiAgICB0aWNrbW9kZSA9ICJsaW5lYXIiLCB0aWNrMCA9IDAsIGR0aWNrID0gMC4xKSAlPiUKICBsYXlvdXQoYW5ub3RhdGlvbnMgPSBsaXN0KAogICAgbGlzdCh4ID0gMC41LCB5ID0gMSwgeGFuY2hvciA9ICJjZW50ZXIiLCB5YW5jaG9yID0gImJvdHRvbSIsIGFsaWduID0gImNlbnRlciIsIAogICAgICB4cmVmPSdwYXBlcicsIHlyZWY9J3BhcGVyJywgc2hvd2Fycm93ID0gRkFMU0UsIAogICAgICBmb250ID0gbXkudGl0bGUuZm9udCwgdGV4dCA9ICJDb25Qcm9wRXhjZXNzIFRhc2sgLSBCbGlzcyIpKSkKCmZpZzIgPSBwbG90X2x5KGF2Zy5mMS50YXNrLmNvbi5wcm9wLmV4Y2VzcyAlPiUgZmlsdGVyKGNpX21ldGhvZCA9PSAiaHNhIiksIHggPSB+Y29uc2VjdXRpdmVfZXhjZXNzX2NvdW50LCAKICB5ID0gfmV4Y2Vzc19wcm9wb3J0aW9uYWxfdGhyZXNob2xkLCB6ID0gfmF2Zy5mMS5zY29yZSwgem1pbiA9IDAsIHptYXggPSBtYXguYXZnLmYxLnNjb3JlLCB3aWR0aCA9IDEwMDAsCiAgY29sb3JzY2FsZSA9ICJKZXQiLCB0eXBlID0gImNvbnRvdXIiLCBjb250b3VycyA9IGxpc3Qoc2hvd2xhYmVscyA9IFRSVUUpLCAKICBzaG93c2NhbGUgPSBGQUxTRSkgJT4lICMgbm8gY29sb3JiYXIgCiAgbGF5b3V0KGFubm90YXRpb25zID0gbGlzdCgKICAgIGxpc3QoeCA9IDAuNSwgeSA9IDEsIHhhbmNob3IgPSAiY2VudGVyIiwgeWFuY2hvciA9ICJib3R0b20iLCBhbGlnbiA9ICJjZW50ZXIiLCAKICAgICAgeHJlZj0ncGFwZXInLCB5cmVmPSdwYXBlcicsIHNob3dhcnJvdyA9IEZBTFNFLCAKICAgICAgZm9udCA9IG15LnRpdGxlLmZvbnQsIHRleHQgPSAiQ29uUHJvcEV4Y2VzcyBUYXNrIC0gSFNBIikpKQoKc3VicGxvdChmaWcxLCBmaWcyLCB0aXRsZVggPSBUUlVFLCB0aXRsZVkgPSBUUlVFLCBtYXJnaW4gPSAwLjA1KSAlPiUgCiAgY29uZmlnKGRpc3BsYXlNb2RlQmFyID0gRkFMU0UpCmBgYAoKIyMgey19Cgo6Ojogey5ibHVlLWJveH0KLSBUaGUgYENvbkV4Y2Vzc2AgbWV0aG9kIGlzIHRoZSBtb3N0ICp2ZXJib3NlKiB3aXRoIHJlZ2FyZHMgdG8gaXQncyBwYXJhbWV0ZXIgc3BhY2U6IGEgbGFyZ2VyIGFtb3VudCBvZiBwYXJhbWV0ZXIgY29tYmluYXRpb25zIGNhbiBiZSBhdHRyaWJ1dGVkIHRvIGhpZ2hlciBtZWFuIEYxLVNjb3Jlcy4KLSBUaGUgKm1vdW50YWluLXBlYWtzKiBpbiBlYWNoIHRhc2sgKHdoZXJlIHRoZSB0YXNrcyBhdHRhaW4gdGhlaXIgbWF4aW11bSB2YWx1ZXMpIGFyZSBjbG9zZSBpbiB0aGUgc2FtZSAoMUQgZm9yIHRoZSBgQXZnYCB0YXNrLCAyRCBmb3IgdGhlIG90aGVyIDIgdGFza3MpIHBhcmFtZXRlciBzdWItc3BhY2Ugd2hlbiBjb21wYXJpbmcgQmxpc3MgdnMgSFNBIC0gaS5lLiAqKnRoZSBzeW5lcmd5IG1ldGhvZHMgY29ycmVsYXRlIGluIHRoZSBwYXJhbWV0ZXItc3BhY2UqKi4KLSBUaGUgc21hbGwgc3RhdGlzdGljYWwgYWR2YW50YWdlIHRoYXQgQmxpc3MgaGFkIG92ZXIgSFNBIGluIHRoZSByYXcgZGF0YSBjYW4gc3RpbGwgYmUgc2VlbiBpbiB0aGUgMkQgY29udG91ciBwbG90cyBvZiB0aGUgYEF2Z2AgYW5kIGBDb25Qcm9wRXhjZXNzYCB0YXNrIHdoZW4gKipjb21wYXJpbmcgdGhlIGF2ZXJhZ2UgRjEtU2NvcmVzIGFjcm9zcyBhbGwgY2VsbCBsaW5lcyoqLCBidXQgaW4gdGhlIGVuZCB0aGUgSFNBIHN5bmVyZ3kgbWV0aG9kIGluIHRoZSBgQ29uRXhjZXNzYCB0YXNrIHJlc3VsdGVkIGluIHRoZSBiZXN0IHJlc3VsdCB3aXRoIHRoZSBnaXZlbiBwYXJhbWV0ZXIgc3BhY2UuCjo6OgoKIyBCbGlzcy1hbGwtY29tYmluYXRpb25zIG1ldGhvZCB7LX0KClRoZSAqKkJsaXNzLWFsbC1jb21iaW5hdGlvbnMgbWV0aG9kKiogKEkgYXJiaXRyYXJpbHkgY2hvc2UgdGhlIG5hbWUpIHdhcyB0aGUgbWV0aG9kIHRoYXQgQmFyYmFyYSBhbmQgQXNtdW5kIHVzZWQgaW4gW0BGbG9iYWsyMDE5XSBvbiB0aGUgZGF0YSBmcm9tIHRoZSBmaXJzdCBzY3JlZW4gdG8gZmluZCBhbmQgdmlzdWFsaXplIHRoZSBzeW5lcmdpZXMgYWNyb3NzIGFsbCA4IGNlbGwgbGluZXMuIApUaGUgbWV0aG9kIGlzIGRlc2NyaWJlZCBpbiB0aGUgdGV4dCBhbmQgQXNtdW5kIHdyb3RlIGFuICpSKiBzY3JpcHQgdGhhdCBjYWxjdWxhdGVzIHRoZSAqKmF2ZXJhZ2UgQmxpc3MgZXhjZXNzZXMqKiBhbmQgKipzaWduaWZpY2FuY2Ugc2NvcmVzKiogKCpwX3ZhbHVlc19CbGlzcy5SKiBpbiB0aGUgc3VwcGxlbWVudGFyeSBtYXRlcmlhbCAtIGdlbmVyYXRlcyB0aGUgW2JsaXNzX3NpZ25pZmljYW5jZS50c3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9kcnVnbG9naWNzL3NpbnRlZi1vYnMtc3luZXJnaWVzL2Jsb2IvbWFzdGVyL2JsaXNzX3NpZ25pZmljYW5jZS50c3YpIGZpbGUpLiAKQ29tYmluYXRpb25zIHdpdGggKiphdmVyYWdlIEJsaXNzIGV4Y2VzcyB2YWx1ZXMqKiAkPOKIkjAuMDgkIGFuZCAqcC12YWx1ZSogJDwwLjA1JCAodGhlICpCb25mZXJyb25pKi1jb3JyZWN0ZWQpIHdlcmUgY2xhc3NpZmllZCBhcyBzeW5lcmdpZXMgKHJlc3VsdHMgd2VyZSBwdXQgaW50byB0aGUgW2JsaXNzX3N5bmVyZ2llcy50c3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9kcnVnbG9naWNzL3NpbnRlZi1vYnMtc3luZXJnaWVzL2Jsb2IvbWFzdGVyL2JsaXNzX3N5bmVyZ2llcy50c3YpIGZpbGUgaW4gdGhlIFNJIG9mIHRoZSBwYXBlcikuCgpUaGUgdHdvIHRocmVzaG9sZCB2YWx1ZXMgYWJvdmUgKCphdmVyYWdlIGJsaXNzIGV4Y2VzcyogYW5kIHRoZSBjb3JyZWN0ZWQgKnAtdmFsdWUqKSB3ZXJlIG1vcmUtb3ItbGVzcyAqKmludHVpdGl2ZWx5IGNob3NlbioqIChhIGdvb2QtZW5vdWdoIHAtdmFsdWUgKyBhbiBPSyBuZWdhdGl2ZSB0aHJlc2hvbGQgc28gdG8gc3BlYWspLiAKV2Ugbm93IHdhbnQgdG8gY29tcGFyZSB0aGUgc3luZXJnaWVzIGZvdW5kIHdpdGggdGhpcyBtZXRob2QgKGFjcm9zcyBkaWZmZXJlbnQgY29tYmluYXRpb25zIG9mIHRoZSBhZm9yZW1lbnRpb25lZCBwYXJhbWV0ZXJzKSB3aXRoIHRoZSBbR1Mgc3luZXJnaWVzXSgjZ29sZC1zdGFuZGFyZC1zeW5lcmdpZXMpIGluIGVhY2ggY2VsbCBsaW5lIGFuZCBzZWUgKip3aGljaCBjb21lIGNsb3NlciB0byB0aGUgY3VyYXRpb24tY2hvc2VuIG9uZXMqKi4KCldlIGZpcnN0IGxvYWQgdGhlIGRhdGEgZnJvbSB0aGUgW2JsaXNzX3NpZ25pZmljYW5jZS50c3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9kcnVnbG9naWNzL3NpbnRlZi1vYnMtc3luZXJnaWVzL2Jsb2IvbWFzdGVyL2JsaXNzX3NpZ25pZmljYW5jZS50c3YpIGZpbGUgYW5kIHRpZHkgJ2VtIHVwIGEgYml0OgpgYGB7ciBsb2FkIGFsbC1jb21iaW5hdGlvbnMtbWV0aG9kIGRhdGEsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPVRSVUV9CmJsaXNzLmFsbC5jb21iLmRhdGEgPSBkcm9wX25hKHJlYWRfdHN2KGZpbGUgPSAiYmxpc3Nfc2lnbmlmaWNhbmNlLnRzdiIpKQpibGlzcy5zeW5lcmdpZXMgPSByZWFkX3RzdihmaWxlID0gImJsaXNzX3N5bmVyZ2llcy50c3YiKQoKIyBGaXggdGhlIGNlbGwgbGluZSBuYW1lcwpibGlzcy5hbGwuY29tYi5kYXRhID0gYmxpc3MuYWxsLmNvbWIuZGF0YSAlPiUgCiAgbXV0YXRlKGNlbGxfbGluZSA9IGNhc2Vfd2hlbigKICAgIGNlbGxfbGluZSA9PSAiU1ctNjIwIiB+ICJTVzYyMCIsIAogICAgY2VsbF9saW5lID09ICJDT0xPIDIwNSIgfiAiQ29sbzIwNSIsCiAgICBjZWxsX2xpbmUgPT0gIkRVLTE0NSIgfiAiRFUxNDUiLAogICAgY2VsbF9saW5lID09ICJTRi0yOTUiIH4gIlNGMjk1IiwKICAgIGNlbGxfbGluZSA9PSAiVUFDQy02MiIgfiAiVUFDQzYyIiwKICAgIFRSVUUgfiBjZWxsX2xpbmUKKSkKYmxpc3Muc3luZXJnaWVzID0gYmxpc3Muc3luZXJnaWVzICU+JSAKICBtdXRhdGUoY2VsbF9saW5lID0gY2FzZV93aGVuKAogICAgY2VsbF9saW5lID09ICJTVy02MjAiIH4gIlNXNjIwIiwgCiAgICBjZWxsX2xpbmUgPT0gIkNPTE8gMjA1IiB+ICJDb2xvMjA1IiwKICAgIGNlbGxfbGluZSA9PSAiRFUtMTQ1IiB+ICJEVTE0NSIsCiAgICBjZWxsX2xpbmUgPT0gIlNGLTI5NSIgfiAiU0YyOTUiLAogICAgY2VsbF9saW5lID09ICJVQUNDLTYyIiB+ICJVQUNDNjIiLAogICAgVFJVRSB+IGNlbGxfbGluZQopKQoKIyBSZW1vdmUgcm93cyB3aXRoIFNGIGRydWcgaW4gdGhlIGNvbWJpbmF0aW9uCmJsaXNzLmFsbC5jb21iLmRhdGEgPSBibGlzcy5hbGwuY29tYi5kYXRhICU+JSBmaWx0ZXIoIWdyZXBsKCdTRicsIGRydWdjb21ibykpCmJsaXNzLnN5bmVyZ2llcyA9IGJsaXNzLnN5bmVyZ2llcyAlPiUgZmlsdGVyKCFncmVwbCgnU0YnLCBkcnVnY29tYm8pKQoKIyBGaXggdGhlIGRydWcgbmFtZXMgKGFkZCBhIGRhc2ggYmV0d2VlbiB0aGUgZHJ1ZyBuYW1lcykKYmxpc3MuYWxsLmNvbWIuZGF0YSA9IGJsaXNzLmFsbC5jb21iLmRhdGEgJT4lIAogIG11dGF0ZShkcnVnX2NvbWJvID0gc3RyX3JlcGxhY2UoZHJ1Z2NvbWJvLCAiICIsICItIikpICU+JSAKICBzZWxlY3QoY2VsbF9saW5lLCBkcnVnX2NvbWJvLCBtZWFuLmJsaXNzLmVzdGltYXRlLCBwLnZhbHVlLmJvbmZlcnJvbmkpCgojIGJlbG93IHJlc3VsdHMgYXJlIHRoZSBzYW1lCiMgY2VsbC5saW5lID0gIkFHUyIKIyBibGlzcy5hbGwuY29tYi5kYXRhICU+JSBmaWx0ZXIocC52YWx1ZS5ib25mZXJyb25pIDwgMC4wNSwgbWVhbi5ibGlzcy5lc3RpbWF0ZSA8IC0wLjA4LCBjZWxsX2xpbmUgPT0gY2VsbC5saW5lKQojIGJsaXNzLnN5bmVyZ2llcyAlPiUgZmlsdGVyKGNlbGxfbGluZSA9PSBjZWxsLmxpbmUsICFncmVwbCgnU0YnLCBkcnVnY29tYm8pKQpgYGAKCk5leHQsIHdlIGNob3NlIHRoZSBwYXJhbWV0ZXIgc3BhY2UgZm9yIHRoZSB0d28gdGhyZXNob2xkcyBvZiBpbnRlcmVzdDoKCi0gJDE0JCBjb3JyZWN0ZWQgYHBfdmFsdWVfYm9uZmVycm9uaWAgZnJvbSAkMC4wMDAwMSQgdG8gJDAuMDUkIGFuZCB0aGUgbWF4aW11bSBvbmUgaW4gdGhlIGRhdGFzZXQgKGVxdWFsIHRvICQxJCkKLSAkNDIkIGBtZWFuX2JsaXNzX3RocmVzaG9sZGAgdmFsdWVzIGZyb20gJFxzaW0gLTAuNSQgdG8gJFxzaW0gKzAuNDIkIHdpdGggYSBzdGVwIG9mICRcc2ltIDAuMDIyNyQKCldlIGNhbGN1bGF0ZSBmb3IgZWFjaCBjb21iaW5hdGlvbiBvZiB0aGUgYWJvdmUgdHdvIHBhcmFtZXRlcnMgYW5kIGZvciBlYWNoIGNlbGwgbGluZSwgdGhlIHN5bmVyZ2llcyBmb3VuZCBmcm9tIHRoaXMgZGF0YXNldCBhbmQgY29tcGFyZSBpdCB3aXRoIHRoZSBHUyBzeW5lcmdpZXMgdG8gZGVyaXZlIGFuIFtGMS1TY29yZV0oI3Njb3Jpbmctc3luZXJneS1tZXRob2RzKSBmb3IgdGhhdCBwYXJhbWV0ZXIgY29tYmluYXRpb24uIFRoZW4gd2UgYWdncmVnYXRlIHRoZSByZXN1bHRzIGFjcm9zcyB0aGUgOCBjZWxsIGxpbmVzIHBlciBlYWNoIHBhcmFtZXRlciBjb21iaW5hdGlvbiwgdG8gZmluZCB0aGUgYXZlcmFnZSBGMS1TY29yZSBwZXIgKGBwX3ZhbHVlX2JvbmZlcnJvbmlgLCBgbWVhbl9ibGlzc190aHJlc2hvbGRgKSBjb21iaW5hdGlvbjoKYGBge3IgUGFyYW1ldGVyIFNwYWNlIFNlYXJjaCBhbGwtYmxpc3MtY29tYiBtZXRob2QsIGNhY2hlPVRSVUV9CnAudmFsdWVzID0gYygwLjAwMDAxLCAwLjAwMDA1LCAwLjAwMDEsIDAuMDAwNSwgMC4wMDEsIDAuMDA1LCAwLjAxLCAwLjAyLCAwLjAzLCAwLjA0LCAwLjA1LCAwLjEsIDAuNSwgMSkKYmxpc3MudGhyZXNob2xkcyA9IHNlcShmcm9tID0gbWluKGJsaXNzLmFsbC5jb21iLmRhdGEkbWVhbi5ibGlzcy5lc3RpbWF0ZSksIHRvID0gbWF4KGJsaXNzLmFsbC5jb21iLmRhdGEkbWVhbi5ibGlzcy5lc3RpbWF0ZSksIGxlbmd0aC5vdXQgPSA0MikKCmRhdGEubGlzdCA9IGxpc3QoKQppbmRleCA9IDEKCmZvciAoY2VsbC5saW5lIGluIGNlbGwubGluZXMpIHsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgZ3NldCA9IGdzLmxpc3RbW2NlbGwubGluZV1dCiAgZm9yIChwLnZhbHVlIGluIHAudmFsdWVzKSB7CiAgICBmb3IgKHRocmVzaG9sZCBpbiBibGlzcy50aHJlc2hvbGRzKSB7CiAgICAgIG1zZXQgPSBibGlzcy5hbGwuY29tYi5kYXRhICU+JSAKICAgICAgICBmaWx0ZXIocC52YWx1ZS5ib25mZXJyb25pIDw9IHAudmFsdWUsIG1lYW4uYmxpc3MuZXN0aW1hdGUgPD0gdGhyZXNob2xkLCBjZWxsX2xpbmUgPT0gY2VsbC5saW5lKSAlPiUKICAgICAgICBwdWxsKGRydWdfY29tYm8pCiAgICAgIGRhdGEubGlzdFtbaW5kZXhdXSA9IHRpYmJsZShjZWxsX2xpbmUgPSBjZWxsLmxpbmUsIHBfdmFsdWVfYm9uZmVycm9uaSA9IHAudmFsdWUsIG1lYW5fYmxpc3NfdGhyZXNob2xkID0gdGhyZXNob2xkLCBmMS5zY29yZSA9IGdldC5zY29yZShnc2V0LCBtc2V0KSkKICAgICAgaW5kZXggPSBpbmRleCArIDEKICAgIH0KICB9Cn0KCmJsaXNzLmFsbC5jb21iLnJlcyA9IGJpbmRfcm93cyhkYXRhLmxpc3QpCgojIEZpbmQgYXZnIEYxLVNjb3JlIGFjcm9zcyBhbGwgY2VsbCBsaW5lcwphdmcuZjEuYmxpc3MuYWxsID0gYmxpc3MuYWxsLmNvbWIucmVzICU+JSAKICBncm91cF9ieShwX3ZhbHVlX2JvbmZlcnJvbmksIG1lYW5fYmxpc3NfdGhyZXNob2xkKSAlPiUgCiAgc3VtbWFyaXNlKGF2Zy5mMS5zY29yZSA9IG1lYW4oZjEuc2NvcmUpKSAlPiUgCiAgdW5ncm91cCgpCmBgYAoKPGRpdiBpZD0iYmxpc3NBbGxUYmwiPjwvZGl2PgpgYGB7ciBQYXJhbWV0ZXIgU3BhY2UgUmVzdWx0cyBhbGwtYmxpc3MtY29tYiBtZXRob2R9CmNhcHRpb24udGl0bGUgPSAiQXZlcmFnZSBGMS1TY29yZSBhY3Jvc3MgOCBjZWxsIGxpbmVzIChCbGlzcy1BbGwtQ29tYmluYXRpb25zKSIKZGF0YXRhYmxlKGRhdGEgPSBhdmcuZjEuYmxpc3MuYWxsLCBvcHRpb25zID0gbGlzdCgKICBvcmRlciA9IGxpc3QobGlzdCgzLCAnZGVzYycpKSksCiAgY2FwdGlvbiA9IGh0bWx0b29sczo6dGFncyRjYXB0aW9uKGNhcHRpb24udGl0bGUsIHN0eWxlPSJjb2xvcjojZGQ0ODE0OyBmb250LXNpemU6IDE4cHgiKSkgJT4lCiAgZm9ybWF0Um91bmQoMjozLCBkaWdpdHMgPSAzKQpgYGAKCjo6OiB7LmJsdWUtYm94fQotIE9ic2VydmluZyB0aGUgcmVzdWx0cyBpbiB0aGUgdGFibGUgYWJvdmUsIHdlIHNlZSB0aGUgdGhlIGZpcnN0ICpzZW5zaWJsZSogcmVzdWx0IChpLmUuIHdpdGggYSBgcF92YWx1ZV9ib25mZXJyb25pYCAkPDAuMDUkLCBzbyBzb21ld2hhdCB0cnVzdGFibGUgOikgdGhhdCAqKmhvbGRzIHRoZSA0dGggbGFyZ2VzdCBGMS1TY29yZSoqICgkMC40NTMkKSwgaGFzIGEgYG1lYW5fYmxpc3NfdGhyZXNob2xkYCAkXGxlIC0wLjA5NiQuIApUaHVzIHRoZSBjaG9pY2Ugb2YgQmFyYmFyYSBhbmQgQXNtdW5kIGluIFtARmxvYmFrMjAxOV0gZm9yIGBtZWFuX2JsaXNzX3RocmVzaG9sZGAgJDziiJIwLjA4JCBhbmQgYHBfdmFsdWVfYm9uZmVycm9uaWAgJDwwLjA1JCB0byBkZXJpdmUgdGhlIHN5bmVyZ2llcyB3YXMgc3VyZWx5IG9uZSBvZiB0aGUgYmVzdCBjaG9pY2VzIHRoZXkgY291bGQgbWFrZSAoY2FsbCBpdCBpbnR1aXRpb24gb3IgZXllLWJhbGxpbmcsIHRoaXMgY2hvaWNlIGlzIG5vdyBtYXRoZW1hdGljYWxseSBzdWJzdGFudGlhdGVkKSEKLSBFdmVuIHRoZSBsYXJnZXN0IGF2ZXJhZ2UgRjEtU2NvcmUsICoqY2Fubm90IG1hdGNoIHRoZSByZXN1bHRzIG9mIHRoZSBoaWdoZXN0IHNjb3JpbmcgcmJidCB0YXNrcyoqIGFzIHNlZW4gaW4gdGhlIFtwcmV2aW91cyBzZWN0aW9uXSgjaGlnaGVzdC1mMS1zY29yZWQtdGFzaykuCjo6OgoKVmlzdWFsaXppbmcgdGhlIHJlc3VsdHMgb2YgdGhlIGFib3ZlIHRhYmxlIHVzaW5nIGEgY29udG91ciBwbG90IHdlIGhhdmU6CgpgYGB7ciBDb250b3VyIHBsb3QgYWxsLWJsaXNzLWNvbWIgbWV0aG9kfQpwbG90X2x5KGRhdGEgPSBhdmcuZjEuYmxpc3MuYWxsLCB4ID0gfi1sb2cxMChwX3ZhbHVlX2JvbmZlcnJvbmkpLCAKICB5ID0gfm1lYW5fYmxpc3NfdGhyZXNob2xkLCB6ID0gfmF2Zy5mMS5zY29yZSwgem1pbiA9IDAsIHptYXggPSBtYXguYXZnLmYxLnNjb3JlLCAjd2lkdGggPSAxMDAwLAogIGNvbG9yc2NhbGUgPSAiSmV0IiwgdHlwZSA9ICJjb250b3VyIiwgY29udG91cnMgPSBsaXN0KHNob3dsYWJlbHMgPSBUUlVFKSkgJT4lIAogIGNvbG9yYmFyKHRpdGxlID0gbGlzdCh0ZXh0ID0gIk1lYW4gRjEtU2NvcmUgXG4oOCBjZWxsIGxpbmVzKSIsIGZvbnQgPSBteS5jb2xvcmJhci5mb250KSwgCiAgICB0aWNrbW9kZSA9ICJsaW5lYXIiLCB0aWNrMCA9IDAsIGR0aWNrID0gMC4xKSAlPiUKICBsYXlvdXQoYW5ub3RhdGlvbnMgPSBsaXN0KAogICAgbGlzdCh4ID0gMC41LCB5ID0gMSwgeGFuY2hvciA9ICJjZW50ZXIiLCB5YW5jaG9yID0gImJvdHRvbSIsIGFsaWduID0gImNlbnRlciIsIAogICAgICB4cmVmPSdwYXBlcicsIHlyZWY9J3BhcGVyJywgc2hvd2Fycm93ID0gRkFMU0UsIAogICAgICBmb250ID0gbXkudGl0bGUuZm9udCwgdGV4dCA9ICJCbGlzcy1BbGwtQ29tYmluYXRpb25zIE1ldGhvZCIpKSkgJT4lIAogIGNvbmZpZyhkaXNwbGF5TW9kZUJhciA9IEZBTFNFKQpgYGAKCjo6OiB7LmJsdWUtYm94fQpUaGUgbGFyZ2VyIHRoZSBgcF92YWx1ZV9ib25mZXJyb25pYCAodGhlIHZhbHVlcyB0byB0aGUgKnJpZ2h0KiBvZiB0aGUgYWJvdmUgcGxvdCksIHRoZSBoYXJkZXIgaXMgdG8gZ2V0IGEgZ29vZCB2YWx1ZSBmb3IgdGhlIGF2ZXJhZ2UgRjEtU2NvcmUgYWNyb3NzIGFsbCBjZWxsIGxpbmVzLgo6OjoKCiMgUmVmZXJlbmNlcyB7LX0K