El presente tutorial está basado en la publicación del blog variance explained de David Robinson. El material ha sido actualizado y adaptado al objetivo del curso.
Objetivo
- Integrar las herramientas del
tidyverse
con los flujos de trabajo de Bioconductor
para el análisis de microarreglos.
Nota: Todos los outputs deben verse como líneas corridas. Disminuir el zoom de ser necesario.
Contexto
tidy data no lo es todo.
From the posts so far, one might get the impression that I think data must be tidy at every stage of an analysis. Not true! That would be an absurd and unnecessary constraint. Lots of mathematical operations are faster on matrices, such as singular value decomposition or linear regression. Jeff Leek rightfully points this out as an issue with my previous modeling gene expression post, where he remarks that the limma package is both faster and takes more statistical considerations (pooling variances among genes) into account.
Isn’t it contradictory to do these kinds of operations in a tidy analysis? Not at all. My general recommendation is laid out as follows:
As long as you’re in that Models “cloud”, you can store your data in whatever way is computationally and practically easiest. However:
- Before you model, you should use tidy tools to clean, process and wrangle your data (as shown in previous posts)
- After you’ve performed your modeling, you should turn the model into a tidy output for interpretation, visualization, and communication
This requires a new and important tool in our series on tidy bioinformatics analysis: the biobroom
package, written and maintained by my former colleagues, particularly Andy Bass and John Storey. In this post I’ll show how to use the limma
and biobroom
packages in combination to continue a tidy analysis, and consider when and how to use non-tidy data in an analysis.
Dependencies
library(tidyverse)
Loading tidyverse: ggplot2
Loading tidyverse: tibble
Loading tidyverse: tidyr
Loading tidyverse: readr
Loading tidyverse: purrr
Loading tidyverse: dplyr
Conflicts with tidy packages -------------------------------------------------------------------
filter(): dplyr, stats
lag(): dplyr, stats
Importe y limpieza de datos
library(dplyr)
library(tidyr)
original_data <- readRDS("data-raw/tidymicro.rds")
cleaned_data <- original_data %>%
separate(NAME, c("name", "BP", "MF", "systematic_name", "number"), sep = "\\|\\|") %>%
mutate_at(vars(name:systematic_name), funs(trimws)) %>%
select(-number, -GID, -YORF, -GWEIGHT) %>%
gather(sample, expression, G0.05:U0.3) %>% #dplyr::count(sample)
separate(sample, c("nutrient", "rate"), sep = 1, convert = TRUE) %>%
mutate(nutrient=forcats::fct_recode(nutrient,
"Glucose" = "G", "Leucine" = "L",
"Phosphate" = "P", "Sulfate" = "S",
"Ammonia" = "N", "Uracil" = "U")) %>%
filter(!is.na(expression),systematic_name!="") %>%
group_by(systematic_name, nutrient) %>%
filter(n()==6) %>%
ungroup() %>%
glimpse()
Observations: 195,222
Variables: 7
$ name <chr> "SFB2", "", "QRI7", "CFT2", "SSO2", "PSP2", "RIB2", "VMA13", "EDC3"...
$ BP <chr> "ER to Golgi transport", "biological process unknown", "proteolysis...
$ MF <chr> "molecular function unknown", "molecular function unknown", "metall...
$ systematic_name <chr> "YNL049C", "YNL095C", "YDL104C", "YLR115W", "YMR183C", "YML017W", "...
$ nutrient <fctr> Glucose, Glucose, Glucose, Glucose, Glucose, Glucose, Glucose, Glu...
$ rate <dbl> 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0...
$ expression <dbl> -0.24, 0.28, -0.02, -0.33, 0.05, -0.69, -0.55, -0.75, -0.24, -0.16,...
plot_expression_data
function
plot_expression_data <- function(expression_data) {
ggplot(expression_data, aes(rate, expression, color = nutrient)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
facet_wrap(~name + systematic_name, scales = "free_y")
}
limma
Aplicación de regresión lineal por observación
Why a computational biologist should not run a gene expression analysis with traditional statistics?
- Performing thousands of linear regressions with separate
lm
calls is slow. It takes about a minute on my computer. There are computational shortcuts we can take when all of our data is in the form of a gene-by-sample matrix.
- We’re not taking statistical advantage of the shared information. Modern bioinformatics approaches often “share power” across genes, by pooling variance estimates. The approach in the limma package is one notable example for microarray data, and RNA-Seq tools like
edgeR
and DESeq2
take a similar approach in their negative binomial models.
We’d like to take advantage of the sophisticated biological modeling tools in Bioconductor
. We’re thus going to convert our data into a non-tidy format (a gene by sample matrix), and run it through limma to create a linear model for each gene. Then when we want to visualize, compare, or otherwise manipulate our models, we’ll tidy the model output using biobroom.
Setup
- Most gene expression packages in
Bioconductor
expect data to be in a matrix with one row per gene and one column per sample. We would like to fit one model for each gene and nutrient combination. So let’s set it up that way using reshape2’s acast()
(We could have used tidyr’s spread
function, but acast
actually saves us a few steps by giving us a matrix with rownames, rather than a data frame, right away.)
library(reshape2)
exprs <- acast(cleaned_data, systematic_name + nutrient ~ rate,
value.var = "expression")
head(exprs)
0.05 0.1 0.15 0.2 0.25 0.3
Q0017_Glucose -0.21 -0.26 -0.17 0.00 0.10 0.10
Q0017_Leucine 0.46 0.05 0.42 0.35 0.36 0.09
Q0017_Ammonia 0.18 0.73 0.05 -0.14 -0.06 0.24
Q0017_Phosphate -0.24 -0.35 -0.45 -0.24 -0.35 0.11
Q0017_Sulfate 2.44 0.64 0.21 0.40 -0.07 0.31
Q0017_Uracil 1.88 0.03 0.15 0.04 0.14 0.32
- We then need to extract the experiment design, which in this case is just the growth rate:
rate <- as.numeric(colnames(exprs))
rate
[1] 0.05 0.10 0.15 0.20 0.25 0.30
limma
(“linear modeling of microarrays”) is one of the most popular Bioconductor
packages for performing linear-model based differential expression analyses on microarray data. With the data in this matrix form, we’re ready to use it:
library(limma)
# linear model structure:
# lmFit(data_matrix, experiment_design)
# check the experiment_design:
# model.matrix(~rate)
fit <- lmFit(exprs, model.matrix(~rate))
eb <- eBayes(fit)
#class(fit)
#summary(fit)
class(eb)
[1] "MArrayLM"
attr(,"package")
[1] "limma"
summary(eb)
Length Class Mode
coefficients 65074 -none- numeric
rank 1 -none- numeric
assign 2 -none- numeric
qr 5 qr list
df.residual 32537 -none- numeric
sigma 32537 -none- numeric
cov.coefficients 4 -none- numeric
stdev.unscaled 65074 -none- numeric
pivot 2 -none- numeric
Amean 32537 -none- numeric
method 1 -none- character
design 12 -none- numeric
df.prior 1 -none- numeric
s2.prior 1 -none- numeric
var.prior 2 -none- numeric
proportion 1 -none- numeric
s2.post 32537 -none- numeric
t 65074 -none- numeric
df.total 32537 -none- numeric
p.value 65074 -none- numeric
lods 65074 -none- numeric
F 32537 -none- numeric
F.p.value 32537 -none- numeric
- That’s a lot of outputs, and many of them are matrices of varying shapes. If you want to work with this using tidy tools (and if you’ve been listening, you hopefully do), we need to tidy it:
Tidy model
library(biobroom)
head(tidy(eb, intercept = TRUE))
Notice that this is now in one-row-per-coefficient-per-gene form, much like the output of broom’s tidy() on linear models.
Like broom, biobroom
always returns a table without rownames that we can feed into standard tidy tools like dplyr
and ggplot2
. (Note that unlike broom, biobroom requires an intercept = TRUE argument to leave the intercept term, simply because in many genomic datasets- though not ours- the intercept term is almost meaningless). biobroom
can also tidy model objects from other tools like edgeR
or DESeq2
, always giving a consistent format similar to this one.
Now all we’ve got to do split the systematic name and nutrient back up. tidyr’s separate()
can do this:
td <- eb %>%
biobroom::tidy.MArrayLM(intercept = TRUE) %>%
separate(gene, c("systematic_name","nutrient"), sep="_") %>% glimpse()
Observations: 65,074
Variables: 7
$ systematic_name <chr> "Q0017", "Q0017", "Q0017", "Q0017", "Q0017", "Q0017", "Q0045", "Q00...
$ nutrient <chr> "Glucose", "Leucine", "Ammonia", "Phosphate", "Sulfate", "Uracil", ...
$ term <chr> "(Intercept)", "(Intercept)", "(Intercept)", "(Intercept)", "(Inter...
$ estimate <dbl> -0.35333333, 0.38733333, 0.39266667, -0.44933333, 1.91400000, 1.184...
$ statistic <dbl> -3.17249838, 2.33936601, 1.59129359, -2.73252887, 3.93706135, 2.476...
$ p.value <dbl> 1.559960e-02, 5.180100e-02, 1.554594e-01, 2.915825e-02, 5.595522e-0...
$ lod <dbl> -4.0295194, -5.2780422, -6.3498508, -4.6871409, -2.9298317, -5.0718...
Analyse tidy model
td %>% count(term)
- We’ll take a brief look at those one at a time.
Intercepts
intercept_terms <- td %>% filter(term=="(Intercept)")
intercept_terms %>% head()
The p-values aren’t actually interesting to us here: they’re testing whether the intercept is equal to 0, which is not a particularly special number in terms of these normalized gene expressions. (Confidence intervals and standard errors would be).
What we’re really interested in is the value of each intercept relative to the other nutrients in that gene. For example, let’s again consider our favorite gene, LEU1.
cleaned_data %>%
filter(name=="LEU1") %>%
ggplot(aes(rate,expression,colour=nutrient))+
geom_point() +
geom_smooth(method = "lm", se = FALSE)+
geom_hline(aes(yintercept=mean(expression)),linetype="dashed")
- This gene has a low intercept term for all nutrients except leucine. I’ve marked the average intercept with a horizontal dashed line to demonstrate this. Suppose we want to look for other genes like this, with a single outlying intercept term. We could do this by centering the intercepts around the average for each gene, using a
group_by
and mutate
:
centered_intercepts <- intercept_terms %>%
group_by(systematic_name) %>%
mutate(centered_intercept = estimate - mean(estimate)) %>%
ungroup()
- Now we are interested in the most extreme cases, where the intercept is very far from the other nutrients. The
top_n
function is useful for this.
top_intercept <- centered_intercepts %>%
top_n(20, centered_intercept)
- Note that here I’m looking for cases where a single nutrient was greatly overexpressed in starvation (to look for underexpressed nutrients, we could have used
-centered_intercept
instead). We can then pull these genes out of the original data with the useful semi_join
, at which point we can graph it with our plot_expression_data
function:
cleaned_data %>%
semi_join(top_intercept, by = "systematic_name") %>%
plot_expression_data()
These certainly do look like interesting genes! We notice that some genes, like PHO11, only one nutrient is highly expressed while the rest show low expression, while other genes, such as ADH2, show varying levels of expression for each nutrient. We also notice that in most cases the highly expressed nutrient is moving back down towards the others as growth rate increases (that is, as the yeast is less starved). This makes sense, since it’s the starvation that is eliciting the unusual behavior.
What do these genes do? Beats me; I’m not a biologist, I just play one on my degree. But it certainly looks promising that PHO11 and PHO12 are both much higher expressed when phosphorus is the limiting nutrient, as well as SUL1 when sulfur is rare- and indeed each gene is involved in transport of that nutrient. (And we do see our Gene of the Week, LEU1).
Looking up the others in yeastgenome.org, we see that a lot of them are involved in transport across membranes (e.g. DAL5, GAP1, QDR2). This makes sense: the cell notices that it is missing a nutrient, and puts more energy into importing it. Notice that this would be a great way to make inferences about genes whose function we don’t yet know. (This is the focus of functional genomics).
Slopes
- Now let’s take a look at the slope terms, which shows whether each gene increased or decreased its growth rate in a particular condition.
slope_terms <- td %>% filter(term=="rate")
slope_terms %>% head()
- Here, we’ll focus a bit more on statistical significance. First we can make a histogram of the p-values. These p-values are spread across six different nutrients, so we’ll facet our histogram by those nutrients:
ggplot(slope_terms, aes(p.value)) +
geom_histogram(binwidth = .05) +
facet_wrap(~nutrient)
link para el post de @drob para la interpretación de histogramas de p_values
In this case, we can see that the tests are generally well-behaved, with a mix of nulls (genes that don’t respond to growth rate) and alternatives (genes that do). Thus, we can use p-value correction to identify significant genes.
- Or make a volcano plot, comparing statistical significance to effect size (here let’s say just on the slope terms):
td %>%
filter(term == "rate") %>%
ggplot(aes(estimate, p.value)) +
geom_point() +
facet_wrap(~ nutrient, scales = "free") +
scale_y_log10()
- We could easily perform for multiple hypothesis testing within each group, and filter for significant (say, FDR < 1%) changes:
td_filtered <- td %>%
group_by(term, nutrient) %>%
mutate(fdr = p.adjust(p.value, method = "fdr")) %>%
ungroup() %>%
filter(fdr < .01)
Or finding the top few significant changes in each group using dplyr’s top_n
:
top_3 <- td_filtered %>%
filter(term == "rate") %>%
group_by(nutrient) %>%
top_n(3, abs(estimate))
top_3
- We could join this with our original data, which would let us visualize the trends for only the most significant genes:
top_3 %>%
rename(significant_nutrient = nutrient) %>%
inner_join(cleaned_data, by = "systematic_name") %>%
mutate(highlight = nutrient == significant_nutrient) %>%
ggplot(aes(rate, expression, color = nutrient)) +
geom_point() +
geom_smooth(aes(lty = !highlight), method = "lm", se = FALSE, show.legend = FALSE) +
facet_wrap(significant_nutrient ~ systematic_name, ncol = 3, scales = "free_y")
- In short, you can once again use the suite of “tidy tools” that we’ve found powerful in genomic analyses.
Conclusion: La data está bajo tu poder
- There’s a classic proverb of computer science from Abelman & Sussman: “Programs must be written for people to read, and only incidentally for machines to execute.” I’d say this is even more true for data it is for code. Data scientists need to be very comfortable engaging with their data, not fighting with the representation.
Computer environment
devtools::session_info()
Session info ----------------------------------------------------------------------------------
setting value
version R version 3.4.1 (2017-06-30)
system x86_64, linux-gnu
ui RStudio (1.0.153)
language en_US
collate en_US.UTF-8
tz America/Lima
date 2017-08-03
Packages --------------------------------------------------------------------------------------
package * version date source
assertthat 0.2.0 2017-04-11 CRAN (R 3.4.0)
base * 3.4.1 2017-07-08 local
bindr 0.1 2016-11-13 cran (@0.1)
bindrcpp * 0.2 2017-06-17 CRAN (R 3.4.1)
Biobase 2.32.0 2017-08-02 Bioconductor
biobroom * 1.4.2 2017-08-02 Bioconductor
BiocGenerics 0.18.0 2016-06-23 Bioconductor
broom * 0.4.2 2017-02-13 CRAN (R 3.4.0)
cellranger 1.1.0 2016-07-27 CRAN (R 3.4.0)
colorspace 1.3-2 2016-12-14 CRAN (R 3.4.0)
compiler 3.4.1 2017-07-08 local
datasets * 3.4.1 2017-07-08 local
devtools 1.13.2 2017-06-02 CRAN (R 3.4.1)
digest 0.6.12 2017-01-27 CRAN (R 3.4.0)
dplyr * 0.7.2 2017-07-20 CRAN (R 3.4.1)
forcats 0.2.0 2017-01-23 CRAN (R 3.4.0)
foreign 0.8-69 2017-06-21 CRAN (R 3.4.1)
ggplot2 * 2.2.1 2016-12-30 CRAN (R 3.4.0)
glue 1.1.1 2017-06-21 CRAN (R 3.4.1)
graphics * 3.4.1 2017-07-08 local
grDevices * 3.4.1 2017-07-08 local
grid 3.4.1 2017-07-08 local
gtable 0.2.0 2016-02-26 CRAN (R 3.4.0)
haven 1.1.0 2017-07-09 CRAN (R 3.4.1)
hms 0.3 2016-11-22 CRAN (R 3.4.0)
httr 1.2.1 2016-07-03 CRAN (R 3.4.0)
jsonlite 1.5 2017-06-01 cran (@1.5)
knitr 1.16 2017-05-18 cran (@1.16)
labeling 0.3 2014-08-23 CRAN (R 3.4.0)
lattice 0.20-35 2017-03-25 CRAN (R 3.3.3)
lazyeval 0.2.0 2016-06-12 CRAN (R 3.4.0)
limma * 3.28.21 2017-08-02 Bioconductor
lubridate 1.6.0 2016-09-13 CRAN (R 3.4.0)
magrittr 1.5 2014-11-22 CRAN (R 3.4.0)
memoise 1.1.0 2017-04-21 CRAN (R 3.4.0)
methods * 3.4.1 2017-07-08 local
mnormt 1.5-5 2016-10-15 CRAN (R 3.4.0)
modelr 0.1.1 2017-07-24 CRAN (R 3.4.1)
munsell 0.4.3 2016-02-13 CRAN (R 3.4.0)
nlme 3.1-131 2017-02-06 CRAN (R 3.4.0)
parallel 3.4.1 2017-07-08 local
pkgconfig 2.0.1 2017-03-21 cran (@2.0.1)
plyr 1.8.4 2016-06-08 CRAN (R 3.4.0)
psych 1.7.5 2017-05-03 CRAN (R 3.4.0)
purrr * 0.2.2.2 2017-05-11 cran (@0.2.2.2)
R6 2.2.2 2017-06-17 CRAN (R 3.4.1)
Rcpp 0.12.12 2017-07-15 CRAN (R 3.4.1)
readr * 1.1.1 2017-05-16 CRAN (R 3.4.1)
readxl 1.0.0 2017-04-18 CRAN (R 3.4.0)
reshape2 * 1.4.2 2016-10-22 CRAN (R 3.4.0)
rlang 0.1.1 2017-05-18 cran (@0.1.1)
rvest 0.3.2 2016-06-17 CRAN (R 3.4.0)
scales 0.4.1 2016-11-09 CRAN (R 3.4.0)
stats * 3.4.1 2017-07-08 local
stringi 1.1.5 2017-04-07 CRAN (R 3.4.0)
stringr 1.2.0 2017-02-18 CRAN (R 3.4.0)
tibble * 1.3.3 2017-05-28 cran (@1.3.3)
tidyr * 0.6.3 2017-05-15 CRAN (R 3.4.1)
tidyverse * 1.1.1 2017-01-27 CRAN (R 3.4.0)
tools 3.4.1 2017-07-08 local
utils * 3.4.1 2017-07-08 local
withr 2.0.0 2017-07-28 CRAN (R 3.4.1)
xml2 1.1.1 2017-01-24 CRAN (R 3.4.0)
References
LS0tCnRpdGxlOiAiQ2FzbyBJSTogTGltcGllemEgZGUgTW9kZWxvcyBlbiBCaW9sb2fDrWEgQ29tcHV0YWNpb25hbCIKYXV0aG9yOiAiYXZhbGxlY2FtIgpkYXRlOiAnYHIgU3lzLkRhdGUoKWAnCm91dHB1dDoKICAjaHRtbF9kb2N1bWVudDoKICAjcGRmX2RvY3VtZW50OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA0CiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogeWVzCiAgICAjdGhlbWU6IHVuaXRlZAogICAgI2NvZGVfZm9sZGluZzogImhpZGUiCiAgICAjZmlnX2NhcHRpb246IFRSVUUKICAgICNudW1iZXJfc2VjdGlvbnM6IFRSVUUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpvcHRpb25zKHdpZHRoID0gOTApICMgZXhwYW5kIGxpbWl0cyBvZiBDT05TT0xFIG91dHB1dApgYGAKCkVsIHByZXNlbnRlIHR1dG9yaWFsIGVzdMOhIGJhc2FkbyBlbiBsYSAKW3B1YmxpY2FjacOzbl0oaHR0cDovL3ZhcmlhbmNlZXhwbGFpbmVkLm9yZy9yL3RpZHktZ2Vub21pY3MtYmlvYnJvb20vI2ZucmVmOnNwcmVhZCkgZGVsIApibG9nIFt2YXJpYW5jZSBleHBsYWluZWRdKGh0dHA6Ly92YXJpYW5jZWV4cGxhaW5lZC5vcmcvKSAKZGUgW0RhdmlkIFJvYmluc29uXShodHRwczovL3R3aXR0ZXIuY29tL2Ryb2IpLgpFbCBtYXRlcmlhbCBoYSBzaWRvIGFjdHVhbGl6YWRvIHkgYWRhcHRhZG8gYWwgb2JqZXRpdm8gZGVsIGN1cnNvLgoKIyMgT2JqZXRpdm8KCi0gSW50ZWdyYXIgbGFzIGhlcnJhbWllbnRhcyBkZWwgYHRpZHl2ZXJzZWAgY29uIGxvcyBmbHVqb3MgZGUgdHJhYmFqbyBkZSBgQmlvY29uZHVjdG9yYCBwYXJhIGVsIGFuw6FsaXNpcyBkZSBtaWNyb2FycmVnbG9zLgoKX19Ob3RhOl9fIFRvZG9zIGxvcyBvdXRwdXRzIGRlYmVuIHZlcnNlIGNvbW8gbMOtbmVhcyBjb3JyaWRhcy4gX0Rpc21pbnVpciBlbCB6b29tIGRlIHNlciBuZWNlc2FyaW8uXwoKIyMgQ29udGV4dG8KCl9fX3RpZHkgZGF0YV8gbm8gbG8gZXMgdG9kby5fXwoKLSBGcm9tIHRoZSBwb3N0cyBzbyBmYXIsIG9uZSBtaWdodCBnZXQgdGhlIGltcHJlc3Npb24gdGhhdCBJIHRoaW5rIGRhdGEgbXVzdCBiZSB0aWR5IGF0IGV2ZXJ5IHN0YWdlIG9mIGFuIGFuYWx5c2lzLiBOb3QgdHJ1ZSEgVGhhdCB3b3VsZCBiZSBhbiBhYnN1cmQgYW5kIHVubmVjZXNzYXJ5IGNvbnN0cmFpbnQuIExvdHMgb2YgbWF0aGVtYXRpY2FsIG9wZXJhdGlvbnMgYXJlIGZhc3RlciBvbiBtYXRyaWNlcywgc3VjaCBhcyBzaW5ndWxhciB2YWx1ZSBkZWNvbXBvc2l0aW9uIG9yIGxpbmVhciByZWdyZXNzaW9uLiBKZWZmIExlZWsgcmlnaHRmdWxseSBwb2ludHMgdGhpcyBvdXQgYXMgYW4gaXNzdWUgd2l0aCBteSBwcmV2aW91cyBtb2RlbGluZyBnZW5lIGV4cHJlc3Npb24gcG9zdCwgd2hlcmUgaGUgcmVtYXJrcyB0aGF0IHRoZSBsaW1tYSBwYWNrYWdlIGlzIGJvdGggZmFzdGVyIGFuZCB0YWtlcyBtb3JlIHN0YXRpc3RpY2FsIGNvbnNpZGVyYXRpb25zIChwb29saW5nIHZhcmlhbmNlcyBhbW9uZyBnZW5lcykgaW50byBhY2NvdW50LgoKLSBJc27igJl0IGl0IGNvbnRyYWRpY3RvcnkgdG8gZG8gdGhlc2Uga2luZHMgb2Ygb3BlcmF0aW9ucyBpbiBhIHRpZHkgYW5hbHlzaXM/IE5vdCBhdCBhbGwuIE15IGdlbmVyYWwgcmVjb21tZW5kYXRpb24gaXMgbGFpZCBvdXQgYXMgZm9sbG93czoKCiFbd29ya2Zsb3ddKGZpZ3VyZS90aWR5XzAxLmpwZykKCi0gQXMgbG9uZyBhcyB5b3XigJlyZSBpbiB0aGF0IE1vZGVscyDigJxjbG91ZOKAnSwgeW91IGNhbiBzdG9yZSB5b3VyIGRhdGEgaW4gd2hhdGV2ZXIgd2F5IGlzIGNvbXB1dGF0aW9uYWxseSBhbmQgcHJhY3RpY2FsbHkgZWFzaWVzdC4gSG93ZXZlcjoKICAgIAogICAgLSBfX0JlZm9yZSB5b3UgbW9kZWxfXywgeW91IHNob3VsZCB1c2UgdGlkeSB0b29scyB0byBjbGVhbiwgcHJvY2VzcyBhbmQgd3JhbmdsZSB5b3VyIGRhdGEgKGFzIHNob3duIGluIHByZXZpb3VzIHBvc3RzKQogICAgLSBfX0FmdGVyIHlvdeKAmXZlIHBlcmZvcm1lZCB5b3VyIG1vZGVsaW5nX18sIHlvdSBzaG91bGQgdHVybiB0aGUgbW9kZWwgaW50byBhIHRpZHkgb3V0cHV0IGZvciBpbnRlcnByZXRhdGlvbiwgdmlzdWFsaXphdGlvbiwgYW5kIGNvbW11bmljYXRpb24KCi0gVGhpcyByZXF1aXJlcyBhIG5ldyBhbmQgaW1wb3J0YW50IHRvb2wgaW4gb3VyIHNlcmllcyBvbiB0aWR5IGJpb2luZm9ybWF0aWNzIGFuYWx5c2lzOiBfX3RoZSBgYmlvYnJvb21gIHBhY2thZ2VfXywgd3JpdHRlbiBhbmQgbWFpbnRhaW5lZCBieSBteSBmb3JtZXIgY29sbGVhZ3VlcywgcGFydGljdWxhcmx5IEFuZHkgQmFzcyBhbmQgSm9obiBTdG9yZXkuIEluIHRoaXMgcG9zdCBJ4oCZbGwgc2hvdyBob3cgdG8gdXNlIHRoZSBfX2BsaW1tYWAgYW5kIGBiaW9icm9vbWAgcGFja2FnZXNfXyBpbiBjb21iaW5hdGlvbiB0byBjb250aW51ZSBhIHRpZHkgYW5hbHlzaXMsIGFuZCBjb25zaWRlciBfX3doZW4gYW5kIGhvdyB0byB1c2Ugbm9uLXRpZHkgZGF0YSBpbiBhbiBhbmFseXNpc19fLgoKIyMgRGVwZW5kZW5jaWVzCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgojIyBJbXBvcnRlIHkgbGltcGllemEgZGUgZGF0b3MKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQoKb3JpZ2luYWxfZGF0YSA8LSByZWFkUkRTKCJkYXRhLXJhdy90aWR5bWljcm8ucmRzIikKCmNsZWFuZWRfZGF0YSA8LSBvcmlnaW5hbF9kYXRhICU+JQogIHNlcGFyYXRlKE5BTUUsIGMoIm5hbWUiLCAiQlAiLCAiTUYiLCAic3lzdGVtYXRpY19uYW1lIiwgIm51bWJlciIpLCBzZXAgPSAiXFx8XFx8IikgJT4lCiAgbXV0YXRlX2F0KHZhcnMobmFtZTpzeXN0ZW1hdGljX25hbWUpLCBmdW5zKHRyaW13cykpICU+JQogIHNlbGVjdCgtbnVtYmVyLCAtR0lELCAtWU9SRiwgLUdXRUlHSFQpICU+JQogIGdhdGhlcihzYW1wbGUsIGV4cHJlc3Npb24sIEcwLjA1OlUwLjMpICU+JSAjZHBseXI6OmNvdW50KHNhbXBsZSkKICBzZXBhcmF0ZShzYW1wbGUsIGMoIm51dHJpZW50IiwgInJhdGUiKSwgc2VwID0gMSwgY29udmVydCA9IFRSVUUpICU+JSAKICBtdXRhdGUobnV0cmllbnQ9Zm9yY2F0czo6ZmN0X3JlY29kZShudXRyaWVudCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkdsdWNvc2UiID0gIkciLCAiTGV1Y2luZSIgPSAiTCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBob3NwaGF0ZSIgPSAiUCIsICJTdWxmYXRlIiA9ICJTIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQW1tb25pYSIgPSAiTiIsICJVcmFjaWwiID0gIlUiKSkgJT4lIAogIGZpbHRlcighaXMubmEoZXhwcmVzc2lvbiksc3lzdGVtYXRpY19uYW1lIT0iIikgJT4lIAogIGdyb3VwX2J5KHN5c3RlbWF0aWNfbmFtZSwgbnV0cmllbnQpICU+JQogIGZpbHRlcihuKCk9PTYpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGdsaW1wc2UoKQpgYGAKCi0gYHBsb3RfZXhwcmVzc2lvbl9kYXRhYCBmdW5jdGlvbgoKYGBge3J9CnBsb3RfZXhwcmVzc2lvbl9kYXRhIDwtIGZ1bmN0aW9uKGV4cHJlc3Npb25fZGF0YSkgewogIGdncGxvdChleHByZXNzaW9uX2RhdGEsIGFlcyhyYXRlLCBleHByZXNzaW9uLCBjb2xvciA9IG51dHJpZW50KSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UpICsKICAgIGZhY2V0X3dyYXAofm5hbWUgKyBzeXN0ZW1hdGljX25hbWUsIHNjYWxlcyA9ICJmcmVlX3kiKQp9CmBgYAoKCiMjIGxpbW1hCgpfX0FwbGljYWNpw7NuIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBvciBvYnNlcnZhY2nDs25fXwoKLSBXaHkgYSBjb21wdXRhdGlvbmFsIGJpb2xvZ2lzdCBzaG91bGQgbm90IHJ1biBhIGdlbmUgZXhwcmVzc2lvbiBhbmFseXNpcyB3aXRoIHRyYWRpdGlvbmFsIHN0YXRpc3RpY3M/CiAgICAKICAgIC0gX19QZXJmb3JtaW5nIHRob3VzYW5kcyBvZiBsaW5lYXIgcmVncmVzc2lvbnMgd2l0aCBzZXBhcmF0ZSBgbG1gIGNhbGxzIGlzIHNsb3cuX18gSXQgdGFrZXMgYWJvdXQgYSBtaW51dGUgb24gbXkgY29tcHV0ZXIuIFRoZXJlIGFyZSBjb21wdXRhdGlvbmFsIHNob3J0Y3V0cyB3ZSBjYW4gdGFrZSB3aGVuIGFsbCBvZiBvdXIgZGF0YSBpcyBpbiB0aGUgZm9ybSBvZiBhIF9nZW5lLWJ5LXNhbXBsZSBtYXRyaXhfLgogICAgLSBfX1dl4oCZcmUgbm90IHRha2luZyBzdGF0aXN0aWNhbCBhZHZhbnRhZ2Ugb2YgdGhlIHNoYXJlZCBpbmZvcm1hdGlvbi5fXyBNb2Rlcm4gYmlvaW5mb3JtYXRpY3MgYXBwcm9hY2hlcyBvZnRlbiBf4oCcc2hhcmUgcG93ZXLigJ0gYWNyb3NzIGdlbmVzLCBieSBwb29saW5nIHZhcmlhbmNlIGVzdGltYXRlc18uIFRoZSBhcHByb2FjaCBpbiB0aGUgbGltbWEgcGFja2FnZSBpcyBvbmUgbm90YWJsZSBleGFtcGxlIGZvciBfX21pY3JvYXJyYXkgZGF0YV9fLCBhbmQgX19STkEtU2VxIHRvb2xzX18gbGlrZSBgZWRnZVJgIGFuZCBgREVTZXEyYCB0YWtlIGEgc2ltaWxhciBhcHByb2FjaCBpbiB0aGVpciBuZWdhdGl2ZSBiaW5vbWlhbCBtb2RlbHMuCgotIFdl4oCZZCBsaWtlIHRvIF9fdGFrZSBhZHZhbnRhZ2Ugb2YgdGhlIHNvcGhpc3RpY2F0ZWQgYmlvbG9naWNhbCBtb2RlbGluZyB0b29scyBpbiBgQmlvY29uZHVjdG9yYF9fLiBXZeKAmXJlIHRodXMgZ29pbmcgdG8gX19jb252ZXJ0X18gb3VyIGRhdGEgaW50byBhIG5vbi10aWR5IGZvcm1hdCAoYSBnZW5lIGJ5IHNhbXBsZSBtYXRyaXgpLCBhbmQgX19ydW4gaXQgdGhyb3VnaCBsaW1tYV9fIHRvIGNyZWF0ZSBhIGxpbmVhciBtb2RlbCBmb3IgZWFjaCBnZW5lLiBUaGVuIHdoZW4gd2Ugd2FudCB0byB2aXN1YWxpemUsIGNvbXBhcmUsIG9yIG90aGVyd2lzZSBtYW5pcHVsYXRlIG91ciBtb2RlbHMsIHdl4oCZbGwgX190aWR5IHRoZSBtb2RlbF9fIG91dHB1dCB1c2luZyBiaW9icm9vbS4KCiMjIyBTZXR1cAoKLSBNb3N0IGdlbmUgZXhwcmVzc2lvbiBwYWNrYWdlcyBpbiBgQmlvY29uZHVjdG9yYCBleHBlY3QgZGF0YSB0byBiZSBpbiBhIF9fbWF0cml4X18gd2l0aCBfX29uZSByb3cgcGVyIGdlbmUgYW5kIG9uZSBjb2x1bW4gcGVyIHNhbXBsZV9fLiBXZSB3b3VsZCBsaWtlIHRvIF9fZml0IG9uZSBtb2RlbCBmb3IgZWFjaCBnZW5lIGFuZCBudXRyaWVudCBjb21iaW5hdGlvbl9fLiBTbyBsZXTigJlzIHNldCBpdCB1cCB0aGF0IHdheSB1c2luZyByZXNoYXBlMuKAmXMgYGFjYXN0KClgCgooV2UgY291bGQgaGF2ZSB1c2VkIHRpZHly4oCZcyBgc3ByZWFkYCBmdW5jdGlvbiwgYnV0IGBhY2FzdGAgYWN0dWFsbHkgc2F2ZXMgdXMgYSBmZXcgc3RlcHMgYnkgZ2l2aW5nIHVzIGEgbWF0cml4IHdpdGggcm93bmFtZXMsIHJhdGhlciB0aGFuIGEgZGF0YSBmcmFtZSwgcmlnaHQgYXdheS4pCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShyZXNoYXBlMikKZXhwcnMgPC0gYWNhc3QoY2xlYW5lZF9kYXRhLCBzeXN0ZW1hdGljX25hbWUgKyBudXRyaWVudCB+IHJhdGUsCiAgICAgICAgICAgICAgIHZhbHVlLnZhciA9ICJleHByZXNzaW9uIikKCmhlYWQoZXhwcnMpCmBgYAoKLSBXZSB0aGVuIG5lZWQgdG8gZXh0cmFjdCB0aGUgX19leHBlcmltZW50IGRlc2lnbl9fLCB3aGljaCBpbiB0aGlzIGNhc2UgaXMganVzdCB0aGUgZ3Jvd3RoIHJhdGU6CmBgYHtyfQpyYXRlIDwtIGFzLm51bWVyaWMoY29sbmFtZXMoZXhwcnMpKQpyYXRlCmBgYAoKLSBfX2BsaW1tYWAgKOKAnGxpbmVhciBtb2RlbGluZyBvZiBtaWNyb2FycmF5c+KAnSlfXyBpcyBvbmUgb2YgdGhlIG1vc3QgcG9wdWxhciBgQmlvY29uZHVjdG9yYCBwYWNrYWdlcyBmb3IgcGVyZm9ybWluZyBfX2xpbmVhci1tb2RlbCBiYXNlZCBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNlcyBvbiBtaWNyb2FycmF5IGRhdGFfXy4gV2l0aCB0aGUgZGF0YSBpbiB0aGlzIG1hdHJpeCBmb3JtLCB3ZeKAmXJlIHJlYWR5IHRvIHVzZSBpdDoKCmBgYHtyfQpsaWJyYXJ5KGxpbW1hKQoKIyBsaW5lYXIgbW9kZWwgc3RydWN0dXJlOiAKIyBsbUZpdChkYXRhX21hdHJpeCwgZXhwZXJpbWVudF9kZXNpZ24pCgojIGNoZWNrIHRoZSBleHBlcmltZW50X2Rlc2lnbjogCiMgbW9kZWwubWF0cml4KH5yYXRlKQoKZml0IDwtIGxtRml0KGV4cHJzLCBtb2RlbC5tYXRyaXgofnJhdGUpKQplYiA8LSBlQmF5ZXMoZml0KQpgYGAKCi0gVGhpcyBwZXJmb3JtcyBhIF9fbGluZWFyIHJlZ3Jlc3Npb24gZm9yIGVhY2ggZ2VuZV9fLiBUaGlzIG9wZXJhdGlvbiBpcyBib3RoIF9mYXN0ZXJfIGFuZCBtb3JlIF9zdGF0aXN0aWNhbGx5IHNvcGhpc3RpY2F0ZWRfIHRoYW4gYGxtYC4KCi0gU28gbm93IHdl4oCZdmUgcGVyZm9ybWVkIG91ciByZWdyZXNzaW9uLiBXaGF0IG91dHB1dCBkbyB3ZSBnZXQ/CmBgYHtyfQojY2xhc3MoZml0KQojc3VtbWFyeShmaXQpCmNsYXNzKGViKQpzdW1tYXJ5KGViKQpgYGAKCi0gVGhhdOKAmXMgYSBsb3Qgb2Ygb3V0cHV0cywgYW5kIG1hbnkgb2YgdGhlbSBhcmUgbWF0cmljZXMgb2YgdmFyeWluZyBzaGFwZXMuIElmIHlvdSB3YW50IHRvIHdvcmsgd2l0aCB0aGlzIHVzaW5nIF9fdGlkeSB0b29sc19fIChhbmQgaWYgeW914oCZdmUgYmVlbiBsaXN0ZW5pbmcsIHlvdSBob3BlZnVsbHkgZG8pLCB3ZSBuZWVkIHRvIHRpZHkgaXQ6CgojIyMgVGlkeSBtb2RlbAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoYmlvYnJvb20pCmhlYWQodGlkeShlYiwgaW50ZXJjZXB0ID0gVFJVRSkpCmBgYAoKLSBOb3RpY2UgdGhhdCB0aGlzIGlzIG5vdyBpbiBfX29uZS1yb3ctcGVyLWNvZWZmaWNpZW50LXBlci1nZW5lX18gZm9ybSwgbXVjaCBsaWtlIHRoZSBvdXRwdXQgb2YgYnJvb23igJlzIHRpZHkoKSBvbiBsaW5lYXIgbW9kZWxzLgoKLSBMaWtlIGJyb29tLCBgYmlvYnJvb21gIGFsd2F5cyByZXR1cm5zIGEgdGFibGUgd2l0aG91dCByb3duYW1lcyB0aGF0IF9fd2UgY2FuIGZlZWQgaW50byBzdGFuZGFyZCB0aWR5IHRvb2xzIGxpa2UgYGRwbHlyYCBhbmQgYGdncGxvdDJgX18uIChOb3RlIHRoYXQgdW5saWtlIGJyb29tLCBiaW9icm9vbSByZXF1aXJlcyBhbiBpbnRlcmNlcHQgPSBUUlVFIGFyZ3VtZW50IHRvIGxlYXZlIHRoZSBpbnRlcmNlcHQgdGVybSwgc2ltcGx5IGJlY2F1c2UgaW4gbWFueSBnZW5vbWljIGRhdGFzZXRzLSB0aG91Z2ggbm90IG91cnMtIHRoZSBpbnRlcmNlcHQgdGVybSBpcyBhbG1vc3QgbWVhbmluZ2xlc3MpLiBgYmlvYnJvb21gIGNhbiBhbHNvIHRpZHkgbW9kZWwgb2JqZWN0cyBmcm9tIG90aGVyIHRvb2xzIGxpa2UgYGVkZ2VSYCBvciBgREVTZXEyYCwgYWx3YXlzIGdpdmluZyBhIGNvbnNpc3RlbnQgZm9ybWF0IHNpbWlsYXIgdG8gdGhpcyBvbmUuCgotIE5vdyBhbGwgd2XigJl2ZSBnb3QgdG8gZG8gX19zcGxpdCB0aGUgc3lzdGVtYXRpYyBuYW1lIGFuZCBudXRyaWVudCBiYWNrIHVwX18uIHRpZHly4oCZcyBgc2VwYXJhdGUoKWAgY2FuIGRvIHRoaXM6CgpgYGB7cn0KdGQgPC0gZWIgJT4lIAogIGJpb2Jyb29tOjp0aWR5Lk1BcnJheUxNKGludGVyY2VwdCA9IFRSVUUpICU+JSAKICBzZXBhcmF0ZShnZW5lLCBjKCJzeXN0ZW1hdGljX25hbWUiLCJudXRyaWVudCIpLCBzZXA9Il8iKSAlPiUgZ2xpbXBzZSgpCmBgYAoKIyMjIEFuYWx5c2UgdGlkeSBtb2RlbAoKLSBOb3RpY2UgdGhhdCB0aGVyZSBhcmUgdHdvIHJvd3MgZm9yIGVhY2ggZ2VuZS1udXRyaWVudCBjb21iaW5hdGlvbjogYW4gX19pbnRlcmNlcHRfXyBhbmQgYSBfX3Nsb3BlX18gdGVybS4gVGhpcyBpcyBzaW1wbGlmeWluZyBlYWNoIGdlbmUtbnV0cmllbnQgY29tYmluYXRpb24gaW50byB0d28gdmFsdWVzOgogICAgCiAgICAtIF9fSW50ZXJjZXB0Ol9fIEhvdyBoaWdobHkgZXhwcmVzc2VkIHRoZSBnZW5lIGlzIHdoZW4gaXTigJlzIHN0YXJ2ZWQgb2YgdGhhdCBudXRyaWVudC4KICAgIC0gX19yYXRlOl9fIEhvdyBtdWNoIHRoZSBnZW5l4oCZcyBleHByZXNzaW9uIHJlc3BvbmRzIHRvIGFuIGluY3JlYXNpbmcgc3VwcGx5IG9mIHRoYXQgbnV0cmllbnQgKGFuZCB0aGVyZm9yZSBhbiBpbmNyZWFzaW5nIGdyb3d0aCByYXRlKQoKYGBge3J9CnRkICU+JSBjb3VudCh0ZXJtKQpgYGAKCi0gV2XigJlsbCB0YWtlIGEgYnJpZWYgbG9vayBhdCB0aG9zZSBvbmUgYXQgYSB0aW1lLgoKIyMjIyBJbnRlcmNlcHRzCmBgYHtyfQppbnRlcmNlcHRfdGVybXMgPC0gdGQgJT4lIGZpbHRlcih0ZXJtPT0iKEludGVyY2VwdCkiKSAKCmludGVyY2VwdF90ZXJtcyAlPiUgaGVhZCgpCmBgYAoKLSBUaGUgcC12YWx1ZXMgYXJlbuKAmXQgYWN0dWFsbHkgaW50ZXJlc3RpbmcgdG8gdXMgaGVyZTogX3RoZXnigJlyZSB0ZXN0aW5nIHdoZXRoZXIgdGhlIGludGVyY2VwdCBpcyBlcXVhbCB0byAwXywgd2hpY2ggaXMgbm90IGEgcGFydGljdWxhcmx5IHNwZWNpYWwgbnVtYmVyIGluIHRlcm1zIG9mIHRoZXNlIG5vcm1hbGl6ZWQgZ2VuZSBleHByZXNzaW9ucy4gKENvbmZpZGVuY2UgaW50ZXJ2YWxzIGFuZCBzdGFuZGFyZCBlcnJvcnMgd291bGQgYmUpLgoKLSBXaGF0IHdl4oCZcmUgcmVhbGx5IGludGVyZXN0ZWQgaW4gaXMgdGhlIF9fdmFsdWUgb2YgZWFjaCBpbnRlcmNlcHQgcmVsYXRpdmUgdG8gdGhlIG90aGVyIG51dHJpZW50cyBpbiB0aGF0IGdlbmVfXy4gRm9yIGV4YW1wbGUsIGxldOKAmXMgYWdhaW4gY29uc2lkZXIgb3VyIGZhdm9yaXRlIGdlbmUsIExFVTEuCgpgYGB7cn0KY2xlYW5lZF9kYXRhICU+JSAKICBmaWx0ZXIobmFtZT09IkxFVTEiKSAlPiUgCiAgZ2dwbG90KGFlcyhyYXRlLGV4cHJlc3Npb24sY29sb3VyPW51dHJpZW50KSkrCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSsKICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0PW1lYW4oZXhwcmVzc2lvbikpLGxpbmV0eXBlPSJkYXNoZWQiKQpgYGAKCi0gVGhpcyBnZW5lIGhhcyBhIGxvdyBpbnRlcmNlcHQgdGVybSBmb3IgYWxsIG51dHJpZW50cyBleGNlcHQgbGV1Y2luZS4gSeKAmXZlIG1hcmtlZCB0aGUgYXZlcmFnZSBpbnRlcmNlcHQgd2l0aCBhIGhvcml6b250YWwgZGFzaGVkIGxpbmUgdG8gZGVtb25zdHJhdGUgdGhpcy4gU3VwcG9zZSB3ZSB3YW50IHRvIGxvb2sgZm9yIG90aGVyIGdlbmVzIGxpa2UgdGhpcywgd2l0aCBhIHNpbmdsZSBvdXRseWluZyBpbnRlcmNlcHQgdGVybS4gV2UgY291bGQgZG8gdGhpcyBieSBfY2VudGVyaW5nXyB0aGUgaW50ZXJjZXB0cyBhcm91bmQgdGhlIGF2ZXJhZ2UgZm9yIGVhY2ggZ2VuZSwgX191c2luZyBhIGBncm91cF9ieWAgYW5kIGBtdXRhdGVgX186CgpgYGB7cn0KY2VudGVyZWRfaW50ZXJjZXB0cyA8LSBpbnRlcmNlcHRfdGVybXMgJT4lCiAgZ3JvdXBfYnkoc3lzdGVtYXRpY19uYW1lKSAlPiUKICBtdXRhdGUoY2VudGVyZWRfaW50ZXJjZXB0ID0gZXN0aW1hdGUgLSBtZWFuKGVzdGltYXRlKSkgJT4lCiAgdW5ncm91cCgpCmBgYAoKLSBOb3cgd2UgYXJlIGludGVyZXN0ZWQgaW4gdGhlIF9fbW9zdCBleHRyZW1lIGNhc2VzX18sIHdoZXJlIHRoZSBpbnRlcmNlcHQgaXMgdmVyeSBmYXIgZnJvbSB0aGUgb3RoZXIgbnV0cmllbnRzLiBUaGUgYHRvcF9uYCBmdW5jdGlvbiBpcyB1c2VmdWwgZm9yIHRoaXMuCgpgYGB7cn0KdG9wX2ludGVyY2VwdCA8LSBjZW50ZXJlZF9pbnRlcmNlcHRzICU+JQogIHRvcF9uKDIwLCBjZW50ZXJlZF9pbnRlcmNlcHQpCmBgYAoKLSBOb3RlIHRoYXQgaGVyZSBJ4oCZbSBsb29raW5nIGZvciBfX2Nhc2VzIHdoZXJlIGEgc2luZ2xlIG51dHJpZW50IHdhcyBncmVhdGx5IF9vdmVyZXhwcmVzc2VkXyBpbiBzdGFydmF0aW9uX18gKHRvIGxvb2sgZm9yIHVuZGVyZXhwcmVzc2VkIG51dHJpZW50cywgd2UgY291bGQgaGF2ZSB1c2VkIGAtY2VudGVyZWRfaW50ZXJjZXB0YCBpbnN0ZWFkKS4gV2UgY2FuIHRoZW4gcHVsbCB0aGVzZSBnZW5lcyBvdXQgb2YgdGhlIG9yaWdpbmFsIGRhdGEgd2l0aCB0aGUgdXNlZnVsIGBzZW1pX2pvaW5gLCBhdCB3aGljaCBwb2ludCB3ZSBjYW4gX19ncmFwaCBpdF9fIHdpdGggb3VyIGBwbG90X2V4cHJlc3Npb25fZGF0YWAgZnVuY3Rpb246CgpgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpjbGVhbmVkX2RhdGEgJT4lCiAgc2VtaV9qb2luKHRvcF9pbnRlcmNlcHQsIGJ5ID0gInN5c3RlbWF0aWNfbmFtZSIpICU+JQogIHBsb3RfZXhwcmVzc2lvbl9kYXRhKCkKYGBgCi0gVGhlc2UgY2VydGFpbmx5IGRvIGxvb2sgbGlrZSBpbnRlcmVzdGluZyBnZW5lcyEgV2Ugbm90aWNlIHRoYXQgc29tZSBnZW5lcywgbGlrZSBQSE8xMSwgb25seSBvbmUgbnV0cmllbnQgaXMgaGlnaGx5IGV4cHJlc3NlZCB3aGlsZSB0aGUgcmVzdCBzaG93IGxvdyBleHByZXNzaW9uLCB3aGlsZSBvdGhlciBnZW5lcywgc3VjaCBhcyBBREgyLCBzaG93IHZhcnlpbmcgbGV2ZWxzIG9mIGV4cHJlc3Npb24gZm9yIGVhY2ggbnV0cmllbnQuIFdlIGFsc28gbm90aWNlIHRoYXQgaW4gbW9zdCBjYXNlcyB0aGUgaGlnaGx5IGV4cHJlc3NlZCBudXRyaWVudCBpcyBtb3ZpbmcgYmFjayBkb3duIHRvd2FyZHMgdGhlIG90aGVycyBhcyBncm93dGggcmF0ZSBpbmNyZWFzZXMgKHRoYXQgaXMsIGFzIHRoZSB5ZWFzdCBpcyBsZXNzIHN0YXJ2ZWQpLiBUaGlzIG1ha2VzIHNlbnNlLCBzaW5jZSBpdOKAmXMgdGhlIHN0YXJ2YXRpb24gdGhhdCBpcyBlbGljaXRpbmcgdGhlIHVudXN1YWwgYmVoYXZpb3IuCgotIFdoYXQgZG8gdGhlc2UgZ2VuZXMgZG8/IEJlYXRzIG1lOyBJ4oCZbSBub3QgYSBiaW9sb2dpc3QsIEkganVzdCBwbGF5IG9uZSBvbiBteSBkZWdyZWUuIEJ1dCBpdCBjZXJ0YWlubHkgbG9va3MgcHJvbWlzaW5nIHRoYXQgUEhPMTEgYW5kIFBITzEyIGFyZSBib3RoIG11Y2ggaGlnaGVyIGV4cHJlc3NlZCB3aGVuIHBob3NwaG9ydXMgaXMgdGhlIGxpbWl0aW5nIG51dHJpZW50LCBhcyB3ZWxsIGFzIFNVTDEgd2hlbiBzdWxmdXIgaXMgcmFyZS0gYW5kIGluZGVlZCBlYWNoIGdlbmUgaXMgaW52b2x2ZWQgaW4gdHJhbnNwb3J0IG9mIHRoYXQgbnV0cmllbnQuIChBbmQgd2UgZG8gc2VlIG91ciBHZW5lIG9mIHRoZSBXZWVrLCBMRVUxKS4KCi0gTG9va2luZyB1cCB0aGUgb3RoZXJzIGluIHllYXN0Z2Vub21lLm9yZywgd2Ugc2VlIHRoYXQgYSBsb3Qgb2YgdGhlbSBhcmUgaW52b2x2ZWQgaW4gdHJhbnNwb3J0IGFjcm9zcyBtZW1icmFuZXMgKGUuZy4gREFMNSwgR0FQMSwgUURSMikuIFRoaXMgbWFrZXMgc2Vuc2U6IHRoZSBjZWxsIG5vdGljZXMgdGhhdCBpdCBpcyBtaXNzaW5nIGEgbnV0cmllbnQsIGFuZCBwdXRzIG1vcmUgZW5lcmd5IGludG8gaW1wb3J0aW5nIGl0LiBOb3RpY2UgdGhhdCB0aGlzIHdvdWxkIGJlIGEgZ3JlYXQgd2F5IHRvIG1ha2UgaW5mZXJlbmNlcyBhYm91dCBnZW5lcyB3aG9zZSBmdW5jdGlvbiB3ZSBkb27igJl0IHlldCBrbm93LiAoVGhpcyBpcyB0aGUgZm9jdXMgb2YgZnVuY3Rpb25hbCBnZW5vbWljcykuCgojIyMjIFNsb3BlcwoKLSBOb3cgbGV04oCZcyB0YWtlIGEgbG9vayBhdCB0aGUgc2xvcGUgdGVybXMsIHdoaWNoIHNob3dzIHdoZXRoZXIgZWFjaCBnZW5lIF9faW5jcmVhc2VkIG9yIGRlY3JlYXNlZCBpdHMgZ3Jvd3RoIHJhdGUgaW4gYSBwYXJ0aWN1bGFyIGNvbmRpdGlvbl9fLgoKYGBge3J9CnNsb3BlX3Rlcm1zIDwtIHRkICU+JSBmaWx0ZXIodGVybT09InJhdGUiKSAKCnNsb3BlX3Rlcm1zICU+JSBoZWFkKCkKYGBgCgotIEhlcmUsIHdl4oCZbGwgZm9jdXMgYSBiaXQgbW9yZSBvbiBfX3N0YXRpc3RpY2FsIHNpZ25pZmljYW5jZV9fLiBGaXJzdCB3ZSBjYW4gbWFrZSBhIGhpc3RvZ3JhbSBvZiB0aGUgcC12YWx1ZXMuIFRoZXNlIHAtdmFsdWVzIGFyZSBzcHJlYWQgYWNyb3NzIHNpeCBkaWZmZXJlbnQgbnV0cmllbnRzLCBzbyB3ZeKAmWxsIGZhY2V0IG91ciBoaXN0b2dyYW0gYnkgdGhvc2UgbnV0cmllbnRzOgoKYGBge3J9CmdncGxvdChzbG9wZV90ZXJtcywgYWVzKHAudmFsdWUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAuMDUpICsKICBmYWNldF93cmFwKH5udXRyaWVudCkKYGBgCgoqIFtsaW5rXShodHRwOi8vdmFyaWFuY2VleHBsYWluZWQub3JnL3N0YXRpc3RpY3MvaW50ZXJwcmV0aW5nLXB2YWx1ZS1oaXN0b2dyYW0vKSBwYXJhIGVsIHBvc3QgZGUgQGRyb2IgcGFyYSBsYSBpbnRlcnByZXRhY2nDs24gZGUgaGlzdG9ncmFtYXMgZGUgcF92YWx1ZXMKCi0gSW4gdGhpcyBjYXNlLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIHRlc3RzIGFyZSBnZW5lcmFsbHkgd2VsbC1iZWhhdmVkLCB3aXRoIGEgbWl4IG9mIG51bGxzIChnZW5lcyB0aGF0IGRvbuKAmXQgcmVzcG9uZCB0byBncm93dGggcmF0ZSkgYW5kIGFsdGVybmF0aXZlcyAoZ2VuZXMgdGhhdCBkbykuIFRodXMsIHdlIGNhbiB1c2UgcC12YWx1ZSBjb3JyZWN0aW9uIHRvIGlkZW50aWZ5IHNpZ25pZmljYW50IGdlbmVzLgoKPCEtLSBXZSBjYW4gbm93IHVzZSB0aGUgdGlkeSBhcHByb2FjaGVzIHRvIHZpc3VhbGl6YXRpb24gYW5kIGludGVycHJldGF0aW9uIHRoYXQgd2VyZSBleHBsb3JlZCBpbiBwcmV2aW91cyBwb3N0cy4gV2UgY291bGQgY3JlYXRlIGEgcC12YWx1ZSBoaXN0b2dyYW0gLS0+CgpgYGB7ciwgZXZhbD1GQUxTRSwgZWNobz1GQUxTRX0KZ2dwbG90KHRkLCBhZXMocC52YWx1ZSkpICsKICBnZW9tX2hpc3RvZ3JhbSgpICsKICBmYWNldF9ncmlkKHRlcm0gfiBudXRyaWVudCwgc2NhbGVzID0gImZyZWVfeSIpCmBgYAoKLSBPciBtYWtlIGEgX192b2xjYW5vIHBsb3RfXywgY29tcGFyaW5nIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSB0byBlZmZlY3Qgc2l6ZSAoaGVyZSBsZXTigJlzIHNheSBqdXN0IG9uIHRoZSBzbG9wZSB0ZXJtcyk6CgpgYGB7cn0KdGQgJT4lCiAgZmlsdGVyKHRlcm0gPT0gInJhdGUiKSAlPiUKICBnZ3Bsb3QoYWVzKGVzdGltYXRlLCBwLnZhbHVlKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZmFjZXRfd3JhcCh+IG51dHJpZW50LCBzY2FsZXMgPSAiZnJlZSIpICsKICBzY2FsZV95X2xvZzEwKCkgCmBgYAoKLSBXZSBjb3VsZCBlYXNpbHkgcGVyZm9ybSBmb3IgX19tdWx0aXBsZSBoeXBvdGhlc2lzIHRlc3RpbmdfXyB3aXRoaW4gZWFjaCBncm91cCwgYW5kIGZpbHRlciBmb3Igc2lnbmlmaWNhbnQgKHNheSwgRkRSIDwgMSUpIGNoYW5nZXM6CmBgYHtyfQp0ZF9maWx0ZXJlZCA8LSB0ZCAlPiUKICBncm91cF9ieSh0ZXJtLCBudXRyaWVudCkgJT4lCiAgbXV0YXRlKGZkciA9IHAuYWRqdXN0KHAudmFsdWUsIG1ldGhvZCA9ICJmZHIiKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGZpbHRlcihmZHIgPCAuMDEpCmBgYAoKT3IgZmluZGluZyB0aGUgX190b3AgZmV3IHNpZ25pZmljYW50IGNoYW5nZXMgaW4gZWFjaCBncm91cF9fIHVzaW5nIGRwbHly4oCZcyBgdG9wX25gOgpgYGB7cn0KdG9wXzMgPC0gdGRfZmlsdGVyZWQgJT4lCiAgZmlsdGVyKHRlcm0gPT0gInJhdGUiKSAlPiUKICBncm91cF9ieShudXRyaWVudCkgJT4lCiAgdG9wX24oMywgYWJzKGVzdGltYXRlKSkKCnRvcF8zCmBgYAoKLSBXZSBjb3VsZCBfX2pvaW5fXyB0aGlzIHdpdGggb3VyIG9yaWdpbmFsIGRhdGEsIHdoaWNoIHdvdWxkIGxldCB1cyBfX3Zpc3VhbGl6ZSB0aGUgdHJlbmRzIGZvciBvbmx5IHRoZSBtb3N0IHNpZ25pZmljYW50IGdlbmVzX186CgpgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQp0b3BfMyAlPiUKICByZW5hbWUoc2lnbmlmaWNhbnRfbnV0cmllbnQgPSBudXRyaWVudCkgJT4lCiAgaW5uZXJfam9pbihjbGVhbmVkX2RhdGEsIGJ5ID0gInN5c3RlbWF0aWNfbmFtZSIpICU+JQogIG11dGF0ZShoaWdobGlnaHQgPSBudXRyaWVudCA9PSBzaWduaWZpY2FudF9udXRyaWVudCkgJT4lCiAgZ2dwbG90KGFlcyhyYXRlLCBleHByZXNzaW9uLCBjb2xvciA9IG51dHJpZW50KSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgoYWVzKGx0eSA9ICFoaWdobGlnaHQpLCBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZmFjZXRfd3JhcChzaWduaWZpY2FudF9udXRyaWVudCB+IHN5c3RlbWF0aWNfbmFtZSwgbmNvbCA9IDMsIHNjYWxlcyA9ICJmcmVlX3kiKQpgYGAKCi0gSW4gc2hvcnQsIHlvdSBjYW4gb25jZSBhZ2FpbiB1c2UgdGhlIHN1aXRlIG9mIF9f4oCcdGlkeSB0b29sc+KAnV9fIHRoYXQgd2XigJl2ZSBmb3VuZCBfX3Bvd2VyZnVsIGluIGdlbm9taWMgYW5hbHlzZXNfXy4KCiMjIENvbmNsdXNpb246IExhIGRhdGEgZXN0w6EgYmFqbyB0dSBwb2RlcgoKLSBUaGVyZeKAmXMgYSBjbGFzc2ljIHByb3ZlcmIgb2YgY29tcHV0ZXIgc2NpZW5jZSBmcm9tIF9fQWJlbG1hbiAmIFN1c3NtYW46IOKAnFByb2dyYW1zIG11c3QgYmUgd3JpdHRlbiBmb3IgcGVvcGxlIHRvIHJlYWQsIGFuZCBvbmx5IGluY2lkZW50YWxseSBmb3IgbWFjaGluZXMgdG8gZXhlY3V0ZS7igJ1fXyBJ4oCZZCBzYXkgdGhpcyBpcyBldmVuIG1vcmUgdHJ1ZSBmb3IgZGF0YSBpdCBpcyBmb3IgY29kZS4gRGF0YSBzY2llbnRpc3RzIG5lZWQgdG8gYmUgdmVyeSBjb21mb3J0YWJsZSBlbmdhZ2luZyB3aXRoIHRoZWlyIGRhdGEsIG5vdCBmaWdodGluZyB3aXRoIHRoZSByZXByZXNlbnRhdGlvbi4KCiMjIEVYVFJBOiBHZW5lLXNldHMKCi0gVGhlc2UgcGVyLWdlbmUgbW9kZWxzIGNhbiBzdGlsbCBiZSBkaWZmaWN1bHQgdG8gaW50ZXJwcmV0IGJpb2xvZ2ljYWxseSBpZiB5b3XigJlyZSBub3QgZmFtaWxpYXIgd2l0aCB0aGUgZnVuY3Rpb25zIG9mIHNwZWNpZmljIGdlbmVzLiBXaGF0IHdlIHJlYWxseSB3YW50IGlzIGEgd2F5IHRvIHN1bW1hcml6ZSB0aGUgcmVzdWx0cyBpbnRvIOKAnGdlbmVzIGludm9sdmVkIGluIHRoaXMgYmlvbG9naWNhbCBwcm9jZXNzIGNoYW5nZWQgdGhlaXIgZXhwcmVzc2lvbi7igJ0gVGhpcyBpcyB3aGVyZSBhbm5vdGF0aW9ucyBvZiBnZW5lIHNldHMgYmVjb21lIHVzZWZ1bC4KCmBgYHtyfQpnZW5lX3NldHMgPC0gZGlzdGluY3QoY2xlYW5lZF9kYXRhLCBzeXN0ZW1hdGljX25hbWUsIEJQLCBNRikKCnRkICU+JQogIGlubmVyX2pvaW4oZ2VuZV9zZXRzKSAlPiUKICBmaWx0ZXIoQlAgPT0gImxldWNpbmUgYmlvc3ludGhlc2lzIiwgdGVybSA9PSAiKEludGVyY2VwdCkiKSAlPiUKICBtdXRhdGUobnV0cmllbnQgPSByZW9yZGVyKG51dHJpZW50LCBlc3RpbWF0ZSwgbWVkaWFuKSkgJT4lCiAgZ2dwbG90KGFlcyhudXRyaWVudCwgZXN0aW1hdGUpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHN5c3RlbWF0aWNfbmFtZSksIHZqdXN0ID0gMSwgaGp1c3QgPSAxKSArCiAgeGxhYigiTGltaXRpbmcgbnV0cmllbnQiKSArCiAgeWxhYigiSW50ZXJjZXB0IChleHByZXNzaW9uIGF0IGxvdyBjb25jZW50cmF0aW9uKSIpICsKICBnZ3RpdGxlKCJHZW5lcyBpbnZvbHZlZCBpbiBsZXVjaW5lIGJpb3N5bnRoZXNpcyIpCmBgYAoKLSBOb3RpY2UgaG93IGNsZWFyIGl0IGlzIHRoYXQgdGhlc2UgZ2VuZXMgcmVzcG9uZCB0byBsZXVjaW5lIHN0YXJ2YXRpb24gaW4gcGFydGljdWxhci4gVGhpcyBjYW4gYmUgYXBwbGllZCB0byBnZW5lIHNldHMgY29udGFpbmluZyBkb3plbnMgb3IgZXZlbiBodW5kcmVkcyBvZiBnZW5lcyB3aGlsZSBzdGlsbCBtYWtpbmcgdGhlIGdlbmVyYWwgdHJlbmQgYXBwYXJlbnQuIEZ1cnRoZXJtb3JlLCB3ZSBjb3VsZCB1c2UgdGhlc2Ugc3VtbWFyaWVzIHRvIGxvb2sgYXQgbWFueSBnZW5lIHNldHMgYXQgb25jZSwgYW5kIGV2ZW4gdXNlIHN0YXRpc3RpY2FsIHRlc3RzIHRvIGRpc2NvdmVyIG5ldyBnZW5lIHNldHMgdGhhdCByZXNwb25kIHRvIHN0YXJ2YXRpb24uCgojIyBDb21wdXRlciBlbnZpcm9ubWVudApgYGB7cn0KZGV2dG9vbHM6OnNlc3Npb25faW5mbygpCmBgYAoKIyMgUmVmZXJlbmNlcw==