Learning outcomes
- Construction and interpretation of common visualisations for RNA-seq
- Customising plots to show gene sets of interest
- Rationale behind over-representation and enrichment analyses
- Identifying enriched and over-represented pathways
We can now have a list of genes ordered according to their evidence
for being differentially-expressed. You should have saved a
results_TGF_vs_CTR_annotated.rds
object in the previous
session.
library(DESeq2)
results_tgf <- readRDS("Robjects/results_TGF_vs_CTR_annotated.rds")
Further Visualisation
Now we have annotated our results, we can start to explore some
common visualisation techniques. In the process we will hope to gain
more insights into our results
The Volcano Plot
A common plot for displaying the results of a differential expression
analysis is a volcano plot. It is a scatter plot that
shows statistical significance and the magnitude of difference between
conditions. They are used to identify which genes are the most
significant and are also changing by the most amount.
The data we need for the plot is contained in our
results_tgf
data frame. This basic plot displays a point
for every gene, but does not take advantage of some of the other columns
in the data frame.
library(ggplot2)
results_tgf %>%
ggplot(aes(x = log2FoldChange, y = -log10(padj))) + geom_point()
One modification is to colour the points according to whether each
gene is significant in the analysis. The indicator of significance can
be a new column in the data frame that we create on-the-fly using the
pipe operator.
results_tgf %>%
mutate(Significant = padj < 0.05 & abs(log2FoldChange) > 2) %>%
ggplot(aes(x = log2FoldChange, y = -log10(padj), col=Significant)) + geom_point()
We can also add the gene names to the plot. This should be
straightforward as ggplot2 has a label
aesthetic that can
be mapped to columns in a data frame. The geom_text
plot
will then display the labels. However, the following plot is a bit
crowded.
## Not a good idea to run this!!
results_tgf %>%
ggplot(aes(x = log2FoldChange, y = -log10(padj), label=SYMBOL)) + geom_point() + geom_text()
The problem here is that ggplot2 is trying to label every point with
a name; not quite what we want. The trick is to create a label that is
blank for most genes and only labels the points we are interested in.
The ifelse
function in R is a convenient way to set the
entries in a vector based on a logical expression. In this
case, make the values in Label
the same as the gene symbol
if the gene is in our list of “top genes”. Otherwise, points get labeled
with a blank string ""
.
For clarity, we also make the points slightly transparent and use a
different colour for the text.
N <- 10
top_genes <- dplyr::slice(results_tgf, 1:N) %>% pull(ENSEMBL)
results_tgf %>%
mutate(Label = ifelse(ENSEMBL %in% top_genes, SYMBOL, "")) %>%
ggplot(aes(x = log2FoldChange, y = -log10(padj), label=Label)) + geom_point(alpha=0.4) + geom_text(col="blue")
Finally, a slightly better positioning of text is given by the
ggrepel
package.
if(!require(ggrepel)) install.packages("ggrepel")
results_tgf %>%
mutate(Label = ifelse(ENSEMBL %in% top_genes, SYMBOL, "")) %>%
ggplot(aes(x = log2FoldChange, y = -log10(padj), label=Label)) + geom_point(alpha=0.4) + geom_text_repel(col="blue")
The labeling of genes on the plot is not restricted to the most
significant genes. We could use a similar approach to label any set of
genes that we are interested in.
For instance, we could hypothesise that Extra-Cellular Matrix (ECM)
genes play are important for the transformation in cells treated with
TGF. We could use the volcano plot to demonstrate whether genes
belonging to this pathway show evidence for being
differentially-expressed
Exercise
- Which genes belong to the ECM pathway (GO:0030198)? Get their
ENSEMBL
IDs using
the AnnotationDbi::select
function
- Produce a volcano plot (as above) with points coloured according to
whether a gene belongs to the ECM pathway
- there will be too many genes in the pathway to be able to label
their names
Heatmaps
You may have already seen the use of a heatmap as a quality
assessment tool to visualise the relationship between samples in an
experiment. Another common use-case for such a plot is to visualise the
results of a differential expression analysis. Although
ggplot2
has a geom_tile
function to make
heatmaps, specialised packages such as pheatmaps
offer more
functionality such as clustering the samples.
The counts we are visualising are the variance-stablised
counts, which are more appropriate for visualisation.
Here we will take the top 10 genes from the differential expression
analysis and produce a heatmap with the pheatmap
package.
We can take advantage of the fact the our counts table contains Ensembl
gene names in the rows. Standard subset operations in R can then be
used.
The default colour palette goes from low expression in blue to high
expression in red, which is a good alternative to the traditional
red/green heatmaps which are not suitable for those with forms of
colour-blindness.
dds <- readRDS("Robjects/dds_BACKUP.rds")
# pheatmap is a specialised package to make heatmaps
library(pheatmap)
top_genes <- dplyr::slice(results_tgf, 1:10) %>% pull(ENSEMBL)
vsd <- vst(dds)
# top_genes is a vector containing ENSEMBL names of the genes we want to see in the heatmap
pheatmap(assay(vsd)[top_genes,])
The heatmap is more informative if we add colours underneath the
sample dendrogram to indicate which sample group each sample belongs to.
This we can do by creating a data frame containing metadata for each of
the samples in our dataset. With the DESeq2
workflow we
have already created such a data frame. We have to make sure the the
rownames of the data frame are the same as the column names of the
counts matrix.
sampleInfo <- as.data.frame(colData(dds)[,c("condition","Treated")])
pheatmap(assay(vsd)[top_genes,],
annotation_col = sampleInfo,
scale="row")
Any plot we create in RStudio can be saved as a png or pdf file. We
use the png
or pdf
function to create a file
for the plot to be saved into and run the rest of the code as normal.
The plot does not get displayed in RStudio, but printed to the specified
file.
png("heatmap_top10_genes.png",width=800,height=800)
pheatmap(assay(vsd)[top_genes,],
annotation_col = sampleInfo)
# dev.off()
There are many arguments to explore in pheatmap
. For
example, we might want to use a specific order to the rows and columns
rather than using clustering. A useful option is to specific our own
labels for the rows (genes). The default is to use the rownames of the
count matrix. In our cases these are Ensembl IDs and not easy to
interpret.
gene_labels <- dplyr::slice(results_tgf, 1:N) %>% pull(SYMBOL)
pheatmap(assay(vsd)[top_genes,],
annotation_col = sampleInfo,
labels_row = gene_labels,
scale="row")
Given the nature of how the genes were selected for the heatmap, we
shouldn’t be surprised by the good separation that it demonstrates.
Exercise
- Produce a heatmap using the top 30 genes with the most extreme
log2 Fold-Change
- HINT: The
abs
function can be used to convert all
negative values to positive.
- Label the heatmap with the gene
SYMBOL
of the
genes
- Is this heatmap as effective as separating the samples into
groups?
Pathways analysis
In this section we move towards discovering if our results are
biologically significant. Are the genes that
we have picked statistical flukes, or are there some commonalities.
There are two different approaches one might use, and we will cover
the theory behind both.
Threshold-based Gene Set Testing
For a particular pathway we need to calculate how many genes were
identified as differentially-expressed and compare to how many we
would be expect by chance. Or in other words, if we repeatedly
generated a list of differentially-expressed genes at random how many
genes from this pathway would be expect to see.
For the ECM pathway we can extract all genes as follows:-
## The pull function from dplyr is used to extract a particular column
library(org.Hs.eg.db)
pathway_genes <- AnnotationDbi::select(org.Hs.eg.db,
keys = "GO:0030198",
keytype = "GO",
columns="ENSEMBL") %>% pull(ENSEMBL)
We can then annotate each gene in our results according to whether it
belongs to this pathway, and whether it is differentially-expressed.
go_table <- mutate(results_tgf,
inPathway = ENSEMBL %in% pathway_genes,
isDE = padj < 0.05 & abs(log2FoldChange) > 1)
go_table
Cross-tabulating the two new columns gives a basis for a statistical
test
table(go_table$inPathway, go_table$isDE)
The Fisher’s exact test or chi-squared test (as seen here) can then
be used
chisq.test(table(go_table$inPathway, go_table$isDE))
In reality it would be impractical to test all possible pathways in
this manner, so there are a number of Bioconductor packages that
automate the process
Analysis with clusterProfiler
clusterProfiler
is a Bioconductor package for
over-representation analysis. It’s main advantage is that it provides
some nice visualisation methods.
The main function is enrichGO
which requires the IDs of
genes found to be differentially-expressed (sigGenes
) and
the IDs of all genes in the dataset (universe
). It
uses the org.Hs.eg.db
package to map between gene names and
biological pathways.
library(clusterProfiler)
universe <- results_tgf %>% pull(ENSEMBL)
sigGenes <- results_tgf %>%
filter(padj < 0.05, !is.na(ENSEMBL)) %>% pull(ENSEMBL)
enrich_go <- enrichGO(
gene= sigGenes,
OrgDb = org.Hs.eg.db,
keyType = "ENSEMBL",
ont = "BP",
universe = universe,
qvalueCutoff = 0.05,
readable=TRUE
)
The result of enrichGo
can be turned into a data frame
for easier interpretation.
enrich_go %>% data.frame
A dot plot can show us the most enriched pathways, and the size of
each.
dotplot(enrich_go,showCategory=20)
Relationships between the identified categories can be found using
emapplot
.
enrich_go <- enrichplot::pairwise_termsim(enrich_go)
emapplot(enrich_go)
Overlaps between gene sets can also be visualised using an “Upset
plot” - an alternative to a venn diagram.
enrichplot::upsetplot(enrich_go)
Gene set enrichment analysis (GSEA)
An appealing feature of the GSEA method is that it
does not require us to impose arbitrary cut-offs on the dataset to
decide what is differentially-expressed or not. The steps in producing
the input required for GSEA are i) retrieving the ranked statistics ii)
naming each one according to a chosen identifier (ENSEMBL
or ENTREZID
for example).
The clusterProfiler
package also includes an
implementation of the GSEA algorithm, and the function works in much the
same way as enrichGO
from above.
ranked_genes <- results_tgf %>%
arrange(desc(stat)) %>%
filter(!is.na(stat))
geneList <- pull(ranked_genes, stat)
names(geneList) <- pull(ranked_genes, ENSEMBL)
gse_GO <- gseGO(geneList = geneList,
OrgDb = org.Hs.eg.db,
ont = "BP",keyType = "ENSEMBL")
gse_GO %>% as.data.frame
An overview of the results can be provided by a “ridge plot”. This
allows comparison of the test statistics for each of the top enriched
pathways.
ridgeplot(gse_GO)
An upset plot can still be produced, but this time the distribution
of statistics for overlapping categories can be produced.
enrichplot::upsetplot(gse_GO)
The results confirm that the ECM pathway has many
differentially-expressed genes (more than we would expect by chance).
Moreover, there is a tendancy for these genes to be up-regulated; as
indicated by the high positive enrichment score. Another way to
visualise the GSEA results, that is typically produced from the GSEA
java app, is the so-called enrichment plot.
gseaplot(gse_GO,geneSetID = "GO:0030198")
The enrichment plot for a gene set with a high negative enrichment
score reveals a different pattern.
gseaplot(gse_GO,geneSetID = "GO:0002283")
Exercise
- In addition to enriched GO terms,
clusterProfiler
can
also find enriched KEGG terms
using the enrichKEGG
function. There are a couple of
changes that are required from enrichGO
ENTREZID
have to be used as the identifer type
- the user must input an appropriate organism
code. The code for humans is
hsa
.
- Use the
enrichKEGG
function to identify enriched KEGG
terms in the analysis.
- (Optional) If you have time, use the gseKEGG to perform GSEA using
KEGG terms.
Other visualisation methods using the clusterProfiler output can be
found here:-
https://yulab-smu.top/biomedical-knowledge-mining-book/enrichplot.html
Interactive Visualisation
At this point we might be faced with lots tables and plots that we
have to try and digest in order to gain insights into our data. This can
be over-whelming.
One way to navigate our results is using the GeneTonic package. This
will produce an interactive interface to our dataset that we can use to
explore the results.
The inputs are as follows (most of which we already have, or can
easily generate)
- a
DESeqDataSet
object.
- a
DESeq
results (i.e. generated using the
DESeq
function followed by results
)
- an enrichment results table
- an Annotation table giving a mapping between the gene identifers
used in our dataset to something more recognisable
We already have a dds
object, and can re-compute the
differential results with a few commands. Note the
GeneTonic
requires the results
output and NOT
the results data frame that we have been working.
library(GeneTonic)
design(dds) <- ~condition
de <- DESeq(dds)
##Don't use the tidy=TRUE option so the output stays as a DESeq object
res_de <- results(de,contrast = c("condition", "TGF","CTR"))
GeneTonic
is compatible with many types of enrichment
analysis, but they first have to be converted into common format via a
shake_
function. clusterProfiler
is one of the
tools that is already supported and the associated function to use is
shake_enrichResult
.
res_enrich <- shake_enrichResult(enrich_go)
Finally, we need a table with two columns that can be used to map our
gene ids (ENSEMBL
) to a more-interpretable naming scheme.
We have already generated such a table to accompany our differential
expression results. The annotation table required by
GeneTonic
needs to be only columns with specific column
names.
anno_df <- AnnotationDbi::select(org.Hs.eg.db,keys=rownames(dds),columns="SYMBOL",keytype = "ENSEMBL") %>%
dplyr::rename(gene_id = ENSEMBL,gene_name=SYMBOL)
We can now run the GeneTonic
function. If sucessful,
this should open up a new RStudio window which can also be opened in a
web browser.
GeneTonic(dds,
res_de,
res_enrich,
anno_df)
Appendix: Annotation with the biomaRt resource
The Bioconductor package have the convenience of being able to make
queries offline. However, they are only available for certain organisms.
If your organism does not have an org.XX.eg.db
package
listed on the Bioconductor annotation page (http://bioconductor.org/packages/release/BiocViews.html#___AnnotationData),
an alternative is to use biomaRt which provides an interface to the
popular biomart annotation resource.
The first step is to find the name of a database that you want to
connect to.
library(biomaRt)
listMarts()
ensembl=useMart("ENSEMBL_MART_ENSEMBL")
# list the available datasets (species). Replace human with the name of your organism
listDatasets(ensembl) %>% filter(grepl("Human",description))
ensembl = useDataset("hsapiens_gene_ensembl", mart=ensembl)
Queries to biomaRt
are constructed in a similar way to
the queries we performed with the org.Hs.eg.db
package.
Instead of keys
we have filters
, and instead
of columns
we have attributes. The list of acceptable
values is much more comprehensive that for the org.Hs.eg.db
package.
listFilters(ensembl) %>%
filter(grepl("ensembl",name))
listAttributes(ensembl) %>%
filter(grepl("gene",name))
An advantage over the org..
packages is that positional
information can be retrieved
attributeNames <- c('ensembl_gene_id', 'entrezgene_id', 'external_gene_name', "chromosome_name","start_position","end_position")
getBM(attributes = attributeNames,
filters = "ensembl_gene_id",
values=top_genes,
mart=ensembl)
LS0tCnRpdGxlOiAiQW5hbHlzaXMgb2YgUk5BLXNlcSBkYXRhIGluIFIiCmF1dGhvcjogIk1vZHVsZSBDb29yZGluYXRvciBNYXJrIER1bm5pbmciCmRhdGU6ICdgciBmb3JtYXQoU3lzLnRpbWUoKSwgIkxhc3QgbW9kaWZpZWQ6ICVkICViICVZIilgJwpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBjc3M6IHN0eWxlc2hlZXRzL3N0eWxlcy5jc3MKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFKQpsaWJyYXJ5KGRwbHlyKQpgYGAKCgoKIyBMZWFybmluZyBvdXRjb21lcwoKCi0gQ29uc3RydWN0aW9uIGFuZCBpbnRlcnByZXRhdGlvbiBvZiBjb21tb24gdmlzdWFsaXNhdGlvbnMgZm9yIFJOQS1zZXEKICAgICsgdm9sY2FubyBwbG90cwogICAgKyBoZWF0bWFwcwotIEN1c3RvbWlzaW5nIHBsb3RzIHRvIHNob3cgZ2VuZSBzZXRzIG9mIGludGVyZXN0Ci0gUmF0aW9uYWxlIGJlaGluZCBvdmVyLXJlcHJlc2VudGF0aW9uIGFuZCBlbnJpY2htZW50IGFuYWx5c2VzCi0gSWRlbnRpZnlpbmcgZW5yaWNoZWQgYW5kIG92ZXItcmVwcmVzZW50ZWQgcGF0aHdheXMKCldlIGNhbiBub3cgaGF2ZSBhIGxpc3Qgb2YgZ2VuZXMgb3JkZXJlZCBhY2NvcmRpbmcgdG8gdGhlaXIgZXZpZGVuY2UgZm9yIGJlaW5nIGRpZmZlcmVudGlhbGx5LWV4cHJlc3NlZC4gWW91IHNob3VsZCBoYXZlIHNhdmVkIGEgYHJlc3VsdHNfVEdGX3ZzX0NUUl9hbm5vdGF0ZWQucmRzYCBvYmplY3QgaW4gdGhlIHByZXZpb3VzIHNlc3Npb24uIAoKYGBge3IgZXZhbD1GQUxTRX0KbGlicmFyeShERVNlcTIpCnJlc3VsdHNfdGdmICA8LSByZWFkUkRTKCJSb2JqZWN0cy9yZXN1bHRzX1RHRl92c19DVFJfYW5ub3RhdGVkLnJkcyIpCmBgYAoKCjxkaXYgY2xhc3M9ImluZm9ybWF0aW9uIj4KSWYgeW91IGRpZCBub3Qgc2F2ZSB0aGUgcmVzdWx0cyBmcm9tIGNvbXBhcmluZyBgVEdGYCB0byBgQ1RSYCwgeW91IGNhbiB1c2UgYW4gb2JqZWN0IGZyb20gdGhlIGNvdXJzZSBtYXRlcmlhbHMKYGBge3J9CmxpYnJhcnkoREVTZXEyKQpyZXN1bHRzX3RnZiA8LSByZWFkUkRTKCJSb2JqZWN0cy9yZXN1bHRzX1RHRl92c19DVFJfYW5ub3RhdGVkX0JBQ0tVUC5yZHMiKQpgYGAKCgo8L2Rpdj4KCgojIEZ1cnRoZXIgVmlzdWFsaXNhdGlvbgoKTm93IHdlIGhhdmUgYW5ub3RhdGVkIG91ciByZXN1bHRzLCB3ZSBjYW4gc3RhcnQgdG8gZXhwbG9yZSBzb21lIGNvbW1vbiB2aXN1YWxpc2F0aW9uIHRlY2huaXF1ZXMuIEluIHRoZSBwcm9jZXNzIHdlIHdpbGwgaG9wZSB0byBnYWluIG1vcmUgaW5zaWdodHMgaW50byBvdXIgcmVzdWx0cwoKIyMgVGhlIFZvbGNhbm8gUGxvdAoKQSBjb21tb24gcGxvdCBmb3IgZGlzcGxheWluZyB0aGUgcmVzdWx0cyBvZiBhIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzIGlzIGEgKnZvbGNhbm8gcGxvdCouIEl0IGlzIGEgKnNjYXR0ZXIgcGxvdCogdGhhdCBzaG93cyBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2UgYW5kIHRoZSBtYWduaXR1ZGUgb2YgZGlmZmVyZW5jZSBiZXR3ZWVuIGNvbmRpdGlvbnMuIFRoZXkgYXJlIHVzZWQgdG8gaWRlbnRpZnkgd2hpY2ggZ2VuZXMgYXJlIHRoZSBtb3N0IHNpZ25pZmljYW50IGFuZCBhcmUgYWxzbyBjaGFuZ2luZyBieSB0aGUgbW9zdCBhbW91bnQuCgpUaGUgZGF0YSB3ZSBuZWVkIGZvciB0aGUgcGxvdCBpcyBjb250YWluZWQgaW4gb3VyIGByZXN1bHRzX3RnZmAgZGF0YSBmcmFtZS4gVGhpcyBiYXNpYyBwbG90IGRpc3BsYXlzIGEgcG9pbnQgZm9yIGV2ZXJ5IGdlbmUsIGJ1dCBkb2VzIG5vdCB0YWtlIGFkdmFudGFnZSBvZiBzb21lIG9mIHRoZSBvdGhlciBjb2x1bW5zIGluIHRoZSBkYXRhIGZyYW1lLgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKcmVzdWx0c190Z2YgJT4lIAogIGdncGxvdChhZXMoeCA9IGxvZzJGb2xkQ2hhbmdlLCB5ID0gLWxvZzEwKHBhZGopKSkgKyBnZW9tX3BvaW50KCkKYGBgCgoKT25lIG1vZGlmaWNhdGlvbiBpcyB0byBjb2xvdXIgdGhlIHBvaW50cyBhY2NvcmRpbmcgdG8gd2hldGhlciBlYWNoIGdlbmUgaXMgc2lnbmlmaWNhbnQgaW4gdGhlIGFuYWx5c2lzLiBUaGUgaW5kaWNhdG9yIG9mIHNpZ25pZmljYW5jZSBjYW4gYmUgYSBuZXcgY29sdW1uIGluIHRoZSBkYXRhIGZyYW1lIHRoYXQgd2UgY3JlYXRlIG9uLXRoZS1mbHkgdXNpbmcgdGhlIHBpcGUgb3BlcmF0b3IuCgpgYGB7cn0KcmVzdWx0c190Z2YgJT4lIAogIG11dGF0ZShTaWduaWZpY2FudCA9IHBhZGogPCAwLjA1ICYgYWJzKGxvZzJGb2xkQ2hhbmdlKSA+IDIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBsb2cyRm9sZENoYW5nZSwgeSA9IC1sb2cxMChwYWRqKSwgY29sPVNpZ25pZmljYW50KSkgKyBnZW9tX3BvaW50KCkKYGBgCgoKV2UgY2FuIGFsc28gYWRkIHRoZSBnZW5lIG5hbWVzIHRvIHRoZSBwbG90LiBUaGlzIHNob3VsZCBiZSBzdHJhaWdodGZvcndhcmQgYXMgZ2dwbG90MiBoYXMgYSBgbGFiZWxgIGFlc3RoZXRpYyB0aGF0IGNhbiBiZSBtYXBwZWQgdG8gY29sdW1ucyBpbiBhIGRhdGEgZnJhbWUuIFRoZSBgZ2VvbV90ZXh0YCBwbG90IHdpbGwgdGhlbiBkaXNwbGF5IHRoZSBsYWJlbHMuIEhvd2V2ZXIsIHRoZSBmb2xsb3dpbmcgcGxvdCBpcyBhIGJpdCBjcm93ZGVkLgoKYGBge3J9CiMjIE5vdCBhIGdvb2QgaWRlYSB0byBydW4gdGhpcyEhCnJlc3VsdHNfdGdmICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBsb2cyRm9sZENoYW5nZSwgeSA9IC1sb2cxMChwYWRqKSwgbGFiZWw9U1lNQk9MKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX3RleHQoKQpgYGAKCgpUaGUgcHJvYmxlbSBoZXJlIGlzIHRoYXQgZ2dwbG90MiBpcyB0cnlpbmcgdG8gbGFiZWwgZXZlcnkgcG9pbnQgd2l0aCBhIG5hbWU7IG5vdCBxdWl0ZSB3aGF0IHdlIHdhbnQuIFRoZSB0cmljayBpcyB0byBjcmVhdGUgYSBsYWJlbCB0aGF0IGlzIGJsYW5rIGZvciBtb3N0IGdlbmVzIGFuZCBvbmx5IGxhYmVscyB0aGUgcG9pbnRzIHdlIGFyZSBpbnRlcmVzdGVkIGluLiBUaGUgYGlmZWxzZWAgZnVuY3Rpb24gaW4gUiBpcyBhIGNvbnZlbmllbnQgd2F5IHRvIHNldCB0aGUgZW50cmllcyBpbiBhIHZlY3RvciBiYXNlZCBvbiBhICpsb2dpY2FsKiBleHByZXNzaW9uLiBJbiB0aGlzIGNhc2UsIG1ha2UgdGhlIHZhbHVlcyBpbiBgTGFiZWxgIHRoZSBzYW1lIGFzIHRoZSBnZW5lIHN5bWJvbCBpZiB0aGUgZ2VuZSBpcyBpbiBvdXIgbGlzdCBvZiAidG9wIGdlbmVzIi4gT3RoZXJ3aXNlLCBwb2ludHMgZ2V0IGxhYmVsZWQgd2l0aCBhIGJsYW5rIHN0cmluZyBgIiJgLgoKRm9yIGNsYXJpdHksIHdlIGFsc28gbWFrZSB0aGUgcG9pbnRzIHNsaWdodGx5IHRyYW5zcGFyZW50IGFuZCB1c2UgYSBkaWZmZXJlbnQgY29sb3VyIGZvciB0aGUgdGV4dC4KCmBgYHtyfQpOIDwtIDEwCnRvcF9nZW5lcyA8LSBkcGx5cjo6c2xpY2UocmVzdWx0c190Z2YsIDE6TikgJT4lIHB1bGwoRU5TRU1CTCkKCnJlc3VsdHNfdGdmICU+JSAKICBtdXRhdGUoTGFiZWwgPSBpZmVsc2UoRU5TRU1CTCAlaW4lIHRvcF9nZW5lcywgU1lNQk9MLCAiIikpICU+JSAgCiAgZ2dwbG90KGFlcyh4ID0gbG9nMkZvbGRDaGFuZ2UsIHkgPSAtbG9nMTAocGFkaiksIGxhYmVsPUxhYmVsKSkgKyBnZW9tX3BvaW50KGFscGhhPTAuNCkgKyBnZW9tX3RleHQoY29sPSJibHVlIikKYGBgCgpGaW5hbGx5LCBhIHNsaWdodGx5IGJldHRlciBwb3NpdGlvbmluZyBvZiB0ZXh0IGlzIGdpdmVuIGJ5IHRoZSBgZ2dyZXBlbGAgcGFja2FnZS4KCmBgYHtyfQppZighcmVxdWlyZShnZ3JlcGVsKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dyZXBlbCIpCgpyZXN1bHRzX3RnZiAlPiUgCiAgbXV0YXRlKExhYmVsID0gaWZlbHNlKEVOU0VNQkwgJWluJSB0b3BfZ2VuZXMsIFNZTUJPTCwgIiIpKSAlPiUgIAogIGdncGxvdChhZXMoeCA9IGxvZzJGb2xkQ2hhbmdlLCB5ID0gLWxvZzEwKHBhZGopLCBsYWJlbD1MYWJlbCkpICsgZ2VvbV9wb2ludChhbHBoYT0wLjQpICsgZ2VvbV90ZXh0X3JlcGVsKGNvbD0iYmx1ZSIpCmBgYAoKVGhlIGxhYmVsaW5nIG9mIGdlbmVzIG9uIHRoZSBwbG90IGlzIG5vdCByZXN0cmljdGVkIHRvIHRoZSBtb3N0IHNpZ25pZmljYW50IGdlbmVzLiBXZSBjb3VsZCB1c2UgYSBzaW1pbGFyIGFwcHJvYWNoIHRvIGxhYmVsIGFueSBzZXQgb2YgZ2VuZXMgdGhhdCB3ZSBhcmUgaW50ZXJlc3RlZCBpbi4KCkZvciBpbnN0YW5jZSwgd2UgY291bGQgaHlwb3RoZXNpc2UgdGhhdCBFeHRyYS1DZWxsdWxhciBNYXRyaXggKEVDTSkgZ2VuZXMgcGxheSBhcmUgaW1wb3J0YW50IGZvciB0aGUgdHJhbnNmb3JtYXRpb24gaW4gY2VsbHMgdHJlYXRlZCB3aXRoIFRHRi4gV2UgY291bGQgdXNlIHRoZSB2b2xjYW5vIHBsb3QgdG8gZGVtb25zdHJhdGUgd2hldGhlciBnZW5lcyBiZWxvbmdpbmcgdG8gdGhpcyBwYXRod2F5IHNob3cgZXZpZGVuY2UgZm9yIGJlaW5nIGRpZmZlcmVudGlhbGx5LWV4cHJlc3NlZAoKCiMgRXhlcmNpc2UKCjxkaXYgY2xhc3M9ImV4ZXJjaXNlIj4KCi0gV2hpY2ggZ2VuZXMgYmVsb25nIHRvIHRoZSBFQ00gcGF0aHdheSAoR086MDAzMDE5OCk/IEdldCB0aGVpciBgRU5TRU1CTGAgSURzIHVzaW5nIHRoZSBgQW5ub3RhdGlvbkRiaTo6c2VsZWN0YCBmdW5jdGlvbgotIFByb2R1Y2UgYSB2b2xjYW5vIHBsb3QgKGFzIGFib3ZlKSB3aXRoIHBvaW50cyBjb2xvdXJlZCBhY2NvcmRpbmcgdG8gd2hldGhlciBhIGdlbmUgYmVsb25ncyB0byB0aGUgRUNNIHBhdGh3YXkKICArIHRoZXJlIHdpbGwgYmUgdG9vIG1hbnkgZ2VuZXMgaW4gdGhlIHBhdGh3YXkgdG8gYmUgYWJsZSB0byBsYWJlbCB0aGVpciBuYW1lcwo8L2Rpdj4KCmBgYHtyfQoKYGBgCgoKIyMgSGVhdG1hcHMKCllvdSBtYXkgaGF2ZSBhbHJlYWR5IHNlZW4gdGhlIHVzZSBvZiBhIGhlYXRtYXAgYXMgYSBxdWFsaXR5IGFzc2Vzc21lbnQgdG9vbCB0byB2aXN1YWxpc2UgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHNhbXBsZXMgaW4gYW4gZXhwZXJpbWVudC4gQW5vdGhlciBjb21tb24gdXNlLWNhc2UgZm9yIHN1Y2ggYSBwbG90IGlzIHRvIHZpc3VhbGlzZSB0aGUgcmVzdWx0cyBvZiBhIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzLiBBbHRob3VnaCBgZ2dwbG90MmAgaGFzIGEgYGdlb21fdGlsZWAgZnVuY3Rpb24gdG8gbWFrZSBoZWF0bWFwcywgc3BlY2lhbGlzZWQgcGFja2FnZXMgc3VjaCBhcyBgcGhlYXRtYXBzYCBvZmZlciBtb3JlIGZ1bmN0aW9uYWxpdHkgc3VjaCBhcyBjbHVzdGVyaW5nIHRoZSBzYW1wbGVzLgoKVGhlIGNvdW50cyB3ZSBhcmUgdmlzdWFsaXNpbmcgYXJlIHRoZSAqdmFyaWFuY2Utc3RhYmxpc2VkKiBjb3VudHMsIHdoaWNoIGFyZSBtb3JlIGFwcHJvcHJpYXRlIGZvciB2aXN1YWxpc2F0aW9uLgoKSGVyZSB3ZSB3aWxsIHRha2UgdGhlIHRvcCAxMCBnZW5lcyBmcm9tIHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyBhbmQgcHJvZHVjZSBhIGhlYXRtYXAgd2l0aCB0aGUgYHBoZWF0bWFwYCBwYWNrYWdlLiBXZSBjYW4gdGFrZSBhZHZhbnRhZ2Ugb2YgdGhlIGZhY3QgdGhlIG91ciBjb3VudHMgdGFibGUgY29udGFpbnMgRW5zZW1ibCBnZW5lIG5hbWVzIGluIHRoZSByb3dzLiBTdGFuZGFyZCBzdWJzZXQgb3BlcmF0aW9ucyBpbiBSIGNhbiB0aGVuIGJlIHVzZWQuCgoKVGhlIGRlZmF1bHQgY29sb3VyIHBhbGV0dGUgZ29lcyBmcm9tIGxvdyBleHByZXNzaW9uIGluIGJsdWUgdG8gaGlnaCBleHByZXNzaW9uIGluIHJlZCwgd2hpY2ggaXMgYSBnb29kIGFsdGVybmF0aXZlIHRvIHRoZSB0cmFkaXRpb25hbCByZWQvZ3JlZW4gaGVhdG1hcHMgd2hpY2ggYXJlIG5vdCBzdWl0YWJsZSBmb3IgdGhvc2Ugd2l0aCBmb3JtcyBvZiBjb2xvdXItYmxpbmRuZXNzLgoKCgoKYGBge3J9CmRkcyA8LSByZWFkUkRTKCJSb2JqZWN0cy9kZHNfQkFDS1VQLnJkcyIpCmBgYAoKCmBgYHtyfQojIHBoZWF0bWFwIGlzIGEgc3BlY2lhbGlzZWQgcGFja2FnZSB0byBtYWtlIGhlYXRtYXBzCmxpYnJhcnkocGhlYXRtYXApCnRvcF9nZW5lcyA8LSBkcGx5cjo6c2xpY2UocmVzdWx0c190Z2YsIDE6MTApICU+JSBwdWxsKEVOU0VNQkwpCnZzZCA8LSB2c3QoZGRzKQoKIyB0b3BfZ2VuZXMgaXMgYSB2ZWN0b3IgY29udGFpbmluZyBFTlNFTUJMIG5hbWVzIG9mIHRoZSBnZW5lcyB3ZSB3YW50IHRvIHNlZSBpbiB0aGUgaGVhdG1hcAoKcGhlYXRtYXAoYXNzYXkodnNkKVt0b3BfZ2VuZXMsXSkKYGBgCgpUaGUgaGVhdG1hcCBpcyBtb3JlIGluZm9ybWF0aXZlIGlmIHdlIGFkZCBjb2xvdXJzIHVuZGVybmVhdGggdGhlIHNhbXBsZSBkZW5kcm9ncmFtIHRvIGluZGljYXRlIHdoaWNoIHNhbXBsZSBncm91cCBlYWNoIHNhbXBsZSBiZWxvbmdzIHRvLiBUaGlzIHdlIGNhbiBkbyBieSBjcmVhdGluZyBhIGRhdGEgZnJhbWUgY29udGFpbmluZyBtZXRhZGF0YSBmb3IgZWFjaCBvZiB0aGUgc2FtcGxlcyBpbiBvdXIgZGF0YXNldC4gV2l0aCB0aGUgYERFU2VxMmAgd29ya2Zsb3cgd2UgaGF2ZSBhbHJlYWR5IGNyZWF0ZWQgc3VjaCBhIGRhdGEgZnJhbWUuIFdlIGhhdmUgdG8gbWFrZSBzdXJlIHRoZSB0aGUgcm93bmFtZXMgb2YgdGhlIGRhdGEgZnJhbWUgYXJlIHRoZSBzYW1lIGFzIHRoZSBjb2x1bW4gbmFtZXMgb2YgdGhlIGNvdW50cyBtYXRyaXguCgpgYGB7cn0Kc2FtcGxlSW5mbyA8LSBhcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKVssYygiY29uZGl0aW9uIiwiVHJlYXRlZCIpXSkKCnBoZWF0bWFwKGFzc2F5KHZzZClbdG9wX2dlbmVzLF0sCiAgICAgICAgIGFubm90YXRpb25fY29sID0gc2FtcGxlSW5mbywKICAgICAgICAgc2NhbGU9InJvdyIpCmBgYAoKQW55IHBsb3Qgd2UgY3JlYXRlIGluIFJTdHVkaW8gY2FuIGJlIHNhdmVkIGFzIGEgcG5nIG9yIHBkZiBmaWxlLiBXZSB1c2UgdGhlIGBwbmdgIG9yIGBwZGZgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBhIGZpbGUgZm9yIHRoZSBwbG90IHRvIGJlIHNhdmVkIGludG8gYW5kIHJ1biB0aGUgcmVzdCBvZiB0aGUgY29kZSBhcyBub3JtYWwuIFRoZSBwbG90IGRvZXMgbm90IGdldCBkaXNwbGF5ZWQgaW4gUlN0dWRpbywgYnV0IHByaW50ZWQgdG8gdGhlIHNwZWNpZmllZCBmaWxlLiAKCmBgYHtyfQpwbmcoImhlYXRtYXBfdG9wMTBfZ2VuZXMucG5nIix3aWR0aD04MDAsaGVpZ2h0PTgwMCkKcGhlYXRtYXAoYXNzYXkodnNkKVt0b3BfZ2VuZXMsXSwKICAgICAgICAgYW5ub3RhdGlvbl9jb2wgPSBzYW1wbGVJbmZvKQojIGRldi5vZmYoKQpgYGAKClRoZXJlIGFyZSBtYW55IGFyZ3VtZW50cyB0byBleHBsb3JlIGluIGBwaGVhdG1hcGAuIEZvciBleGFtcGxlLCB3ZSBtaWdodCB3YW50IHRvIHVzZSBhIHNwZWNpZmljIG9yZGVyIHRvIHRoZSByb3dzIGFuZCBjb2x1bW5zIHJhdGhlciB0aGFuIHVzaW5nIGNsdXN0ZXJpbmcuIEEgdXNlZnVsIG9wdGlvbiBpcyB0byBzcGVjaWZpYyBvdXIgb3duIGxhYmVscyBmb3IgdGhlIHJvd3MgKGdlbmVzKS4gVGhlIGRlZmF1bHQgaXMgdG8gdXNlIHRoZSByb3duYW1lcyBvZiB0aGUgY291bnQgbWF0cml4LiBJbiBvdXIgY2FzZXMgdGhlc2UgYXJlIEVuc2VtYmwgSURzIGFuZCBub3QgZWFzeSB0byBpbnRlcnByZXQuCgpgYGB7cn0KZ2VuZV9sYWJlbHMgPC0gZHBseXI6OnNsaWNlKHJlc3VsdHNfdGdmLCAxOk4pICU+JSBwdWxsKFNZTUJPTCkKCnBoZWF0bWFwKGFzc2F5KHZzZClbdG9wX2dlbmVzLF0sCiAgICAgICAgIGFubm90YXRpb25fY29sID0gc2FtcGxlSW5mbywKICAgICAgICAgbGFiZWxzX3JvdyA9IGdlbmVfbGFiZWxzLAogICAgICAgICBzY2FsZT0icm93IikKYGBgCgpHaXZlbiB0aGUgbmF0dXJlIG9mIGhvdyB0aGUgZ2VuZXMgd2VyZSBzZWxlY3RlZCBmb3IgdGhlIGhlYXRtYXAsIHdlIHNob3VsZG4ndCBiZSBzdXJwcmlzZWQgYnkgdGhlIGdvb2Qgc2VwYXJhdGlvbiB0aGF0IGl0IGRlbW9uc3RyYXRlcy4KCiMgRXhlcmNpc2UKCjxkaXYgY2xhc3M9ImV4ZXJjaXNlIj4KCi0gUHJvZHVjZSBhIGhlYXRtYXAgdXNpbmcgdGhlIHRvcCAzMCBnZW5lcyB3aXRoIHRoZSBtb3N0IGV4dHJlbWUgKmxvZzIgRm9sZC1DaGFuZ2UqCiAgKyBISU5UOiBUaGUgYGFic2AgZnVuY3Rpb24gY2FuIGJlIHVzZWQgdG8gY29udmVydCBhbGwgbmVnYXRpdmUgdmFsdWVzIHRvIHBvc2l0aXZlLgotIExhYmVsIHRoZSBoZWF0bWFwIHdpdGggdGhlIGdlbmUgYFNZTUJPTGAgb2YgdGhlIGdlbmVzCi0gSXMgdGhpcyBoZWF0bWFwIGFzIGVmZmVjdGl2ZSBhcyBzZXBhcmF0aW5nIHRoZSBzYW1wbGVzIGludG8gZ3JvdXBzPwoKYGBge3IgZXZhbD1GQUxTRX0KCgoKCmBgYAogIAo8L2Rpdj4KCiMgUGF0aHdheXMgYW5hbHlzaXMKCkluIHRoaXMgc2VjdGlvbiB3ZSBtb3ZlIHRvd2FyZHMgZGlzY292ZXJpbmcgaWYgb3VyIHJlc3VsdHMgYXJlICoqKmJpb2xvZ2ljYWxseSBzaWduaWZpY2FudCoqKi4gQXJlIHRoZSBnZW5lcyB0aGF0IHdlIGhhdmUgcGlja2VkIHN0YXRpc3RpY2FsIGZsdWtlcywgb3IgYXJlIHRoZXJlIHNvbWUgY29tbW9uYWxpdGllcy4gCgpUaGVyZSBhcmUgdHdvIGRpZmZlcmVudCBhcHByb2FjaGVzIG9uZSBtaWdodCB1c2UsIGFuZCB3ZSB3aWxsIGNvdmVyIHRoZSB0aGVvcnkgYmVoaW5kIGJvdGguCgojIyBUaHJlc2hvbGQtYmFzZWQgR2VuZSBTZXQgVGVzdGluZwoKIEZvciBhIHBhcnRpY3VsYXIgcGF0aHdheSB3ZSBuZWVkIHRvIGNhbGN1bGF0ZSBob3cgbWFueSBnZW5lcyB3ZXJlIGlkZW50aWZpZWQgYXMgZGlmZmVyZW50aWFsbHktZXhwcmVzc2VkIGFuZCBjb21wYXJlIHRvICpob3cgbWFueSB3ZSB3b3VsZCBiZSBleHBlY3QgYnkgY2hhbmNlKi4gT3IgaW4gb3RoZXIgd29yZHMsIGlmIHdlIHJlcGVhdGVkbHkgZ2VuZXJhdGVkIGEgbGlzdCBvZiBkaWZmZXJlbnRpYWxseS1leHByZXNzZWQgZ2VuZXMgYXQgcmFuZG9tIGhvdyBtYW55IGdlbmVzIGZyb20gdGhpcyBwYXRod2F5IHdvdWxkIGJlIGV4cGVjdCB0byBzZWUuCgpGb3IgdGhlIEVDTSBwYXRod2F5IHdlIGNhbiBleHRyYWN0IGFsbCBnZW5lcyBhcyBmb2xsb3dzOi0KCmBgYHtyfQojIyBUaGUgcHVsbCBmdW5jdGlvbiBmcm9tIGRwbHlyIGlzIHVzZWQgdG8gZXh0cmFjdCBhIHBhcnRpY3VsYXIgY29sdW1uCmxpYnJhcnkob3JnLkhzLmVnLmRiKQpwYXRod2F5X2dlbmVzIDwtIEFubm90YXRpb25EYmk6OnNlbGVjdChvcmcuSHMuZWcuZGIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleXMgPSAiR086MDAzMDE5OCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleXR5cGUgPSAiR08iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5zPSJFTlNFTUJMIikgJT4lIHB1bGwoRU5TRU1CTCkKYGBgCgpXZSBjYW4gdGhlbiBhbm5vdGF0ZSBlYWNoIGdlbmUgaW4gb3VyIHJlc3VsdHMgYWNjb3JkaW5nIHRvIHdoZXRoZXIgaXQgYmVsb25ncyB0byB0aGlzIHBhdGh3YXksIGFuZCB3aGV0aGVyIGl0IGlzIGRpZmZlcmVudGlhbGx5LWV4cHJlc3NlZC4KCmBgYHtyfQpnb190YWJsZSA8LSBtdXRhdGUocmVzdWx0c190Z2YsIAogICAgICAgICAgICAgICAgICAgaW5QYXRod2F5ID0gRU5TRU1CTCAlaW4lIHBhdGh3YXlfZ2VuZXMsCiAgICAgICAgICAgICAgICAgICBpc0RFID0gcGFkaiA8IDAuMDUgJiBhYnMobG9nMkZvbGRDaGFuZ2UpID4gMSkKZ29fdGFibGUKYGBgCgpDcm9zcy10YWJ1bGF0aW5nIHRoZSB0d28gbmV3IGNvbHVtbnMgZ2l2ZXMgYSBiYXNpcyBmb3IgYSBzdGF0aXN0aWNhbCB0ZXN0CgpgYGB7cn0KdGFibGUoZ29fdGFibGUkaW5QYXRod2F5LCBnb190YWJsZSRpc0RFKQpgYGAKClRoZSBGaXNoZXIncyBleGFjdCB0ZXN0IG9yIGNoaS1zcXVhcmVkIHRlc3QgKGFzIHNlZW4gaGVyZSkgY2FuIHRoZW4gYmUgdXNlZAoKYGBge3J9CmNoaXNxLnRlc3QodGFibGUoZ29fdGFibGUkaW5QYXRod2F5LCBnb190YWJsZSRpc0RFKSkKYGBgCiAgICAKSW4gcmVhbGl0eSBpdCB3b3VsZCBiZSBpbXByYWN0aWNhbCB0byB0ZXN0IGFsbCBwb3NzaWJsZSBwYXRod2F5cyBpbiB0aGlzIG1hbm5lciwgc28gdGhlcmUgYXJlIGEgbnVtYmVyIG9mIEJpb2NvbmR1Y3RvciBwYWNrYWdlcyB0aGF0IGF1dG9tYXRlIHRoZSBwcm9jZXNzCgoKIyMjIEFuYWx5c2lzIHdpdGggY2x1c3RlclByb2ZpbGVyCgpgY2x1c3RlclByb2ZpbGVyYCBpcyBhIEJpb2NvbmR1Y3RvciBwYWNrYWdlIGZvciBvdmVyLXJlcHJlc2VudGF0aW9uIGFuYWx5c2lzLiBJdCdzIG1haW4gYWR2YW50YWdlIGlzIHRoYXQgaXQgcHJvdmlkZXMgc29tZSBuaWNlIHZpc3VhbGlzYXRpb24gbWV0aG9kcy4KClRoZSBtYWluIGZ1bmN0aW9uIGlzIGBlbnJpY2hHT2Agd2hpY2ggcmVxdWlyZXMgdGhlIElEcyBvZiBnZW5lcyBmb3VuZCB0byBiZSBkaWZmZXJlbnRpYWxseS1leHByZXNzZWQgKGBzaWdHZW5lc2ApIGFuZCB0aGUgSURzIG9mICphbGwqIGdlbmVzIGluIHRoZSBkYXRhc2V0IChgdW5pdmVyc2VgKS4gSXQgdXNlcyB0aGUgYG9yZy5Icy5lZy5kYmAgcGFja2FnZSB0byBtYXAgYmV0d2VlbiBnZW5lIG5hbWVzIGFuZCBiaW9sb2dpY2FsIHBhdGh3YXlzLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShjbHVzdGVyUHJvZmlsZXIpCnVuaXZlcnNlIDwtIHJlc3VsdHNfdGdmICU+JSBwdWxsKEVOU0VNQkwpCnNpZ0dlbmVzIDwtIHJlc3VsdHNfdGdmICU+JSAKICBmaWx0ZXIocGFkaiA8IDAuMDUsICFpcy5uYShFTlNFTUJMKSkgJT4lIHB1bGwoRU5TRU1CTCkKCmVucmljaF9nbyA8LSBlbnJpY2hHTygKICBnZW5lPSBzaWdHZW5lcywKICBPcmdEYiA9IG9yZy5Icy5lZy5kYiwKICBrZXlUeXBlID0gIkVOU0VNQkwiLAogIG9udCA9ICJCUCIsCiAgdW5pdmVyc2UgPSB1bml2ZXJzZSwKICBxdmFsdWVDdXRvZmYgPSAwLjA1LAogIHJlYWRhYmxlPVRSVUUKKQoKYGBgCgpUaGUgcmVzdWx0IG9mIGBlbnJpY2hHb2AgY2FuIGJlIHR1cm5lZCBpbnRvIGEgZGF0YSBmcmFtZSBmb3IgZWFzaWVyIGludGVycHJldGF0aW9uLgoKYGBge3J9CmVucmljaF9nbyAlPiUgZGF0YS5mcmFtZQpgYGAKCkEgZG90IHBsb3QgY2FuIHNob3cgdXMgdGhlIG1vc3QgZW5yaWNoZWQgcGF0aHdheXMsIGFuZCB0aGUgc2l6ZSBvZiBlYWNoLgoKYGBge3J9CmRvdHBsb3QoZW5yaWNoX2dvLHNob3dDYXRlZ29yeT0yMCkKYGBgCgpSZWxhdGlvbnNoaXBzIGJldHdlZW4gdGhlIGlkZW50aWZpZWQgY2F0ZWdvcmllcyBjYW4gYmUgZm91bmQgdXNpbmcgYGVtYXBwbG90YC4KCmBgYHtyfQplbnJpY2hfZ28gPC0gZW5yaWNocGxvdDo6cGFpcndpc2VfdGVybXNpbShlbnJpY2hfZ28pCmVtYXBwbG90KGVucmljaF9nbykKYGBgCgpPdmVybGFwcyBiZXR3ZWVuIGdlbmUgc2V0cyBjYW4gYWxzbyBiZSB2aXN1YWxpc2VkIHVzaW5nIGFuICJVcHNldCBwbG90IiAtIGFuIGFsdGVybmF0aXZlIHRvIGEgdmVubiBkaWFncmFtLgoKYGBge3J9CmVucmljaHBsb3Q6OnVwc2V0cGxvdChlbnJpY2hfZ28pCmBgYAoKIyMgR2VuZSBzZXQgZW5yaWNobWVudCBhbmFseXNpcyAoR1NFQSkKCgoKQW4gYXBwZWFsaW5nIGZlYXR1cmUgb2YgdGhlICoqR1NFQSoqIG1ldGhvZCBpcyB0aGF0IGl0IGRvZXMgbm90IHJlcXVpcmUgdXMgdG8gaW1wb3NlIGFyYml0cmFyeSBjdXQtb2ZmcyBvbiB0aGUgZGF0YXNldCB0byBkZWNpZGUgd2hhdCBpcyBkaWZmZXJlbnRpYWxseS1leHByZXNzZWQgb3Igbm90LiBUaGUgc3RlcHMgaW4gcHJvZHVjaW5nIHRoZSBpbnB1dCByZXF1aXJlZCBmb3IgR1NFQSBhcmUgaSkgcmV0cmlldmluZyB0aGUgcmFua2VkIHN0YXRpc3RpY3MgaWkpIG5hbWluZyBlYWNoIG9uZSBhY2NvcmRpbmcgdG8gYSBjaG9zZW4gaWRlbnRpZmllciAoYEVOU0VNQkxgIG9yIGBFTlRSRVpJRGAgZm9yIGV4YW1wbGUpLgoKVGhlIGBjbHVzdGVyUHJvZmlsZXJgIHBhY2thZ2UgYWxzbyBpbmNsdWRlcyBhbiBpbXBsZW1lbnRhdGlvbiBvZiB0aGUgR1NFQSBhbGdvcml0aG0sIGFuZCB0aGUgZnVuY3Rpb24gd29ya3MgaW4gbXVjaCB0aGUgc2FtZSB3YXkgYXMgYGVucmljaEdPYCBmcm9tIGFib3ZlLgoKCmBgYHtyfQoKcmFua2VkX2dlbmVzIDwtIHJlc3VsdHNfdGdmICU+JSAKICBhcnJhbmdlKGRlc2Moc3RhdCkpICU+JSAKICBmaWx0ZXIoIWlzLm5hKHN0YXQpKQogIApnZW5lTGlzdCA8LSBwdWxsKHJhbmtlZF9nZW5lcywgc3RhdCkKbmFtZXMoZ2VuZUxpc3QpIDwtIHB1bGwocmFua2VkX2dlbmVzLCBFTlNFTUJMKQogIApnc2VfR08gIDwtIGdzZUdPKGdlbmVMaXN0ID0gZ2VuZUxpc3QsCiAgICAgICAgT3JnRGIgPSBvcmcuSHMuZWcuZGIsCiAgICAgICAgb250ID0gIkJQIixrZXlUeXBlID0gIkVOU0VNQkwiKQoKYGBgCgpgYGB7cn0KZ3NlX0dPICU+JSBhcy5kYXRhLmZyYW1lCmBgYAoKQW4gb3ZlcnZpZXcgb2YgdGhlIHJlc3VsdHMgY2FuIGJlIHByb3ZpZGVkIGJ5IGEgInJpZGdlIHBsb3QiLiBUaGlzIGFsbG93cyBjb21wYXJpc29uIG9mIHRoZSB0ZXN0IHN0YXRpc3RpY3MgZm9yIGVhY2ggb2YgdGhlIHRvcCBlbnJpY2hlZCBwYXRod2F5cy4gCgpgYGB7cn0KcmlkZ2VwbG90KGdzZV9HTykKYGBgCkFuIHVwc2V0IHBsb3QgY2FuIHN0aWxsIGJlIHByb2R1Y2VkLCBidXQgdGhpcyB0aW1lIHRoZSBkaXN0cmlidXRpb24gb2Ygc3RhdGlzdGljcyBmb3Igb3ZlcmxhcHBpbmcgY2F0ZWdvcmllcyBjYW4gYmUgcHJvZHVjZWQuCgpgYGB7cn0KZW5yaWNocGxvdDo6dXBzZXRwbG90KGdzZV9HTykKYGBgCgpUaGUgcmVzdWx0cyBjb25maXJtIHRoYXQgdGhlIEVDTSBwYXRod2F5IGhhcyBtYW55IGRpZmZlcmVudGlhbGx5LWV4cHJlc3NlZCBnZW5lcyAobW9yZSB0aGFuIHdlIHdvdWxkIGV4cGVjdCBieSBjaGFuY2UpLiBNb3Jlb3ZlciwgdGhlcmUgaXMgYSB0ZW5kYW5jeSBmb3IgdGhlc2UgZ2VuZXMgdG8gYmUgdXAtcmVndWxhdGVkOyBhcyBpbmRpY2F0ZWQgYnkgdGhlIGhpZ2ggcG9zaXRpdmUgZW5yaWNobWVudCBzY29yZS4gQW5vdGhlciB3YXkgdG8gdmlzdWFsaXNlIHRoZSBHU0VBIHJlc3VsdHMsIHRoYXQgaXMgdHlwaWNhbGx5IHByb2R1Y2VkIGZyb20gdGhlIEdTRUEgamF2YSBhcHAsIGlzIHRoZSBzby1jYWxsZWQgZW5yaWNobWVudCBwbG90LiAKCmBgYHtyfQpnc2VhcGxvdChnc2VfR08sZ2VuZVNldElEID0gIkdPOjAwMzAxOTgiKQpgYGAKVGhlIGVucmljaG1lbnQgcGxvdCBmb3IgYSBnZW5lIHNldCB3aXRoIGEgaGlnaCBuZWdhdGl2ZSBlbnJpY2htZW50IHNjb3JlIHJldmVhbHMgYSBkaWZmZXJlbnQgcGF0dGVybi4KCmBgYHtyfQpnc2VhcGxvdChnc2VfR08sZ2VuZVNldElEID0gIkdPOjAwMDIyODMiKQpgYGAKCiMgRXhlcmNpc2UKCjxkaXYgY2xhc3M9ImV4ZXJjaXNlIj4KCi0gSW4gYWRkaXRpb24gdG8gZW5yaWNoZWQgR08gdGVybXMsIGBjbHVzdGVyUHJvZmlsZXJgIGNhbiBhbHNvIGZpbmQgZW5yaWNoZWQgW0tFR0ddKGh0dHBzOi8vd3d3Lmdlbm9tZS5qcC9rZWdnLykgdGVybXMgdXNpbmcgdGhlIGBlbnJpY2hLRUdHYCBmdW5jdGlvbi4gVGhlcmUgYXJlIGEgY291cGxlIG9mIGNoYW5nZXMgdGhhdCBhcmUgcmVxdWlyZWQgZnJvbSBgZW5yaWNoR09gCiAgLSBgRU5UUkVaSURgIGhhdmUgdG8gYmUgdXNlZCBhcyB0aGUgaWRlbnRpZmVyIHR5cGUKICAtIHRoZSB1c2VyIG11c3QgaW5wdXQgYW4gYXBwcm9wcmlhdGUgW29yZ2FuaXNtIGNvZGVdKGh0dHBzOi8vd3d3Lmdlbm9tZS5qcC9rZWdnL2NhdGFsb2cvb3JnX2xpc3QuaHRtbCkuIFRoZSBjb2RlIGZvciBodW1hbnMgaXMgYGhzYWAuCi0gVXNlIHRoZSBgZW5yaWNoS0VHR2AgZnVuY3Rpb24gdG8gaWRlbnRpZnkgZW5yaWNoZWQgS0VHRyB0ZXJtcyBpbiB0aGUgYW5hbHlzaXMuCi0gKE9wdGlvbmFsKSBJZiB5b3UgaGF2ZSB0aW1lLCB1c2UgdGhlIGdzZUtFR0cgdG8gcGVyZm9ybSBHU0VBIHVzaW5nIEtFR0cgdGVybXMuCgo8L2Rpdj4KCmBgYHtyfQoKYGBgCgpPdGhlciB2aXN1YWxpc2F0aW9uIG1ldGhvZHMgdXNpbmcgdGhlIGNsdXN0ZXJQcm9maWxlciBvdXRwdXQgY2FuIGJlIGZvdW5kIGhlcmU6LQoKaHR0cHM6Ly95dWxhYi1zbXUudG9wL2Jpb21lZGljYWwta25vd2xlZGdlLW1pbmluZy1ib29rL2VucmljaHBsb3QuaHRtbAoKCiMgSW50ZXJhY3RpdmUgVmlzdWFsaXNhdGlvbgoKQXQgdGhpcyBwb2ludCB3ZSBtaWdodCBiZSBmYWNlZCB3aXRoIGxvdHMgdGFibGVzIGFuZCBwbG90cyB0aGF0IHdlIGhhdmUgdG8gdHJ5IGFuZCBkaWdlc3QgaW4gb3JkZXIgdG8gZ2FpbiBpbnNpZ2h0cyBpbnRvIG91ciBkYXRhLiBUaGlzIGNhbiBiZSBvdmVyLXdoZWxtaW5nLgoKT25lIHdheSB0byBuYXZpZ2F0ZSBvdXIgcmVzdWx0cyBpcyB1c2luZyB0aGUgR2VuZVRvbmljIHBhY2thZ2UuIFRoaXMgd2lsbCBwcm9kdWNlIGFuIGludGVyYWN0aXZlIGludGVyZmFjZSB0byBvdXIgZGF0YXNldCB0aGF0IHdlIGNhbiB1c2UgdG8gZXhwbG9yZSB0aGUgcmVzdWx0cy4KCi0gW0dlbmVUb25pYyBVc2VyIEd1aWRlXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9iaW9jL3ZpZ25ldHRlcy9HZW5lVG9uaWMvaW5zdC9kb2MvR2VuZVRvbmljX21hbnVhbC5odG1sKQoKVGhlIGlucHV0cyBhcmUgYXMgZm9sbG93cyAobW9zdCBvZiB3aGljaCB3ZSBhbHJlYWR5IGhhdmUsIG9yIGNhbiBlYXNpbHkgZ2VuZXJhdGUpCgotIGEgYERFU2VxRGF0YVNldGAgb2JqZWN0LgotIGEgYERFU2VxYCByZXN1bHRzIChpLmUuIGdlbmVyYXRlZCB1c2luZyB0aGUgYERFU2VxYCBmdW5jdGlvbiBmb2xsb3dlZCBieSBgcmVzdWx0c2ApCi0gYW4gZW5yaWNobWVudCByZXN1bHRzIHRhYmxlIAotIGFuIEFubm90YXRpb24gdGFibGUgZ2l2aW5nIGEgbWFwcGluZyBiZXR3ZWVuIHRoZSBnZW5lIGlkZW50aWZlcnMgdXNlZCBpbiBvdXIgZGF0YXNldCB0byBzb21ldGhpbmcgbW9yZSByZWNvZ25pc2FibGUKCldlIGFscmVhZHkgaGF2ZSBhIGBkZHNgIG9iamVjdCwgYW5kIGNhbiByZS1jb21wdXRlIHRoZSBkaWZmZXJlbnRpYWwgcmVzdWx0cyB3aXRoIGEgZmV3IGNvbW1hbmRzLiBOb3RlIHRoZSBgR2VuZVRvbmljYCByZXF1aXJlcyB0aGUgYHJlc3VsdHNgIG91dHB1dCBhbmQgTk9UIHRoZSByZXN1bHRzIGRhdGEgZnJhbWUgdGhhdCB3ZSBoYXZlIGJlZW4gd29ya2luZy4KCmBgYHtyfQpsaWJyYXJ5KEdlbmVUb25pYykKZGVzaWduKGRkcykgPC0gfmNvbmRpdGlvbgpkZSA8LSBERVNlcShkZHMpCiMjRG9uJ3QgdXNlIHRoZSB0aWR5PVRSVUUgb3B0aW9uIHNvIHRoZSBvdXRwdXQgc3RheXMgYXMgYSBERVNlcSBvYmplY3QKcmVzX2RlIDwtIHJlc3VsdHMoZGUsY29udHJhc3QgPSBjKCJjb25kaXRpb24iLCAiVEdGIiwiQ1RSIikpCmBgYAoKYEdlbmVUb25pY2AgaXMgY29tcGF0aWJsZSB3aXRoIG1hbnkgdHlwZXMgb2YgZW5yaWNobWVudCBhbmFseXNpcywgYnV0IHRoZXkgZmlyc3QgaGF2ZSB0byBiZSBjb252ZXJ0ZWQgaW50byBjb21tb24gZm9ybWF0IHZpYSBhIGBzaGFrZV9gIGZ1bmN0aW9uLiBgY2x1c3RlclByb2ZpbGVyYCBpcyBvbmUgb2YgdGhlIHRvb2xzIHRoYXQgaXMgYWxyZWFkeSBzdXBwb3J0ZWQgYW5kIHRoZSBhc3NvY2lhdGVkIGZ1bmN0aW9uIHRvIHVzZSBpcyBgc2hha2VfZW5yaWNoUmVzdWx0YC4KCmBgYHtyfQpyZXNfZW5yaWNoIDwtIHNoYWtlX2VucmljaFJlc3VsdChlbnJpY2hfZ28pCmBgYAoKRmluYWxseSwgd2UgbmVlZCBhIHRhYmxlIHdpdGggdHdvIGNvbHVtbnMgdGhhdCBjYW4gYmUgdXNlZCB0byBtYXAgb3VyIGdlbmUgaWRzIChgRU5TRU1CTGApIHRvIGEgbW9yZS1pbnRlcnByZXRhYmxlIG5hbWluZyBzY2hlbWUuIFdlIGhhdmUgYWxyZWFkeSBnZW5lcmF0ZWQgc3VjaCBhIHRhYmxlIHRvIGFjY29tcGFueSBvdXIgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gcmVzdWx0cy4gVGhlIGFubm90YXRpb24gdGFibGUgcmVxdWlyZWQgYnkgYEdlbmVUb25pY2AgbmVlZHMgdG8gYmUgb25seSBjb2x1bW5zIHdpdGggc3BlY2lmaWMgY29sdW1uIG5hbWVzLgoKYGBge3J9CmFubm9fZGYgPC0gQW5ub3RhdGlvbkRiaTo6c2VsZWN0KG9yZy5Icy5lZy5kYixrZXlzPXJvd25hbWVzKGRkcyksY29sdW1ucz0iU1lNQk9MIixrZXl0eXBlID0gIkVOU0VNQkwiKSAgJT4lIAogIGRwbHlyOjpyZW5hbWUoZ2VuZV9pZCA9IEVOU0VNQkwsZ2VuZV9uYW1lPVNZTUJPTCkKYGBgCgpXZSBjYW4gbm93IHJ1biB0aGUgYEdlbmVUb25pY2AgZnVuY3Rpb24uIElmIHN1Y2Vzc2Z1bCwgdGhpcyBzaG91bGQgb3BlbiB1cCBhIG5ldyBSU3R1ZGlvIHdpbmRvdyB3aGljaCBjYW4gYWxzbyBiZSBvcGVuZWQgaW4gYSB3ZWIgYnJvd3Nlci4KCgoKYGBge3Igd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFLGV2YWw9RkFMU0V9CkdlbmVUb25pYyhkZHMsCiAgcmVzX2RlLAogIHJlc19lbnJpY2gsCiAgYW5ub19kZikKYGBgCgohW10oaW1hZ2VzL2d0X2hvbWUucG5nKQoKCiMgQXBwZW5kaXg6IEFubm90YXRpb24gd2l0aCB0aGUgYmlvbWFSdCByZXNvdXJjZQoKVGhlIEJpb2NvbmR1Y3RvciBwYWNrYWdlIGhhdmUgdGhlIGNvbnZlbmllbmNlIG9mIGJlaW5nIGFibGUgdG8gbWFrZSBxdWVyaWVzIG9mZmxpbmUuIEhvd2V2ZXIsIHRoZXkgYXJlIG9ubHkgYXZhaWxhYmxlIGZvciBjZXJ0YWluIG9yZ2FuaXNtcy4gSWYgeW91ciBvcmdhbmlzbSBkb2VzIG5vdCBoYXZlIGFuIGBvcmcuWFguZWcuZGJgIHBhY2thZ2UgbGlzdGVkIG9uIHRoZSBCaW9jb25kdWN0b3IgYW5ub3RhdGlvbiBwYWdlIChodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL0Jpb2NWaWV3cy5odG1sI19fX0Fubm90YXRpb25EYXRhKSwgYW4gYWx0ZXJuYXRpdmUgaXMgdG8gdXNlIGJpb21hUnQgd2hpY2ggcHJvdmlkZXMgYW4gaW50ZXJmYWNlIHRvIHRoZSBwb3B1bGFyIGJpb21hcnQgYW5ub3RhdGlvbiByZXNvdXJjZS4gCgpUaGUgZmlyc3Qgc3RlcCBpcyB0byBmaW5kIHRoZSBuYW1lIG9mIGEgZGF0YWJhc2UgdGhhdCB5b3Ugd2FudCB0byBjb25uZWN0IHRvLgoKYGBge3IgZXZhbD1GQUxTRX0KbGlicmFyeShiaW9tYVJ0KQpsaXN0TWFydHMoKQplbnNlbWJsPXVzZU1hcnQoIkVOU0VNQkxfTUFSVF9FTlNFTUJMIikKIyBsaXN0IHRoZSBhdmFpbGFibGUgZGF0YXNldHMgKHNwZWNpZXMpLiBSZXBsYWNlIGh1bWFuIHdpdGggdGhlIG5hbWUgb2YgeW91ciBvcmdhbmlzbQpsaXN0RGF0YXNldHMoZW5zZW1ibCkgJT4lIGZpbHRlcihncmVwbCgiSHVtYW4iLGRlc2NyaXB0aW9uKSkKYGBgCgpgYGB7ciBldmFsPUZBTFNFfQplbnNlbWJsID0gdXNlRGF0YXNldCgiaHNhcGllbnNfZ2VuZV9lbnNlbWJsIiwgbWFydD1lbnNlbWJsKQpgYGAKClF1ZXJpZXMgdG8gYGJpb21hUnRgIGFyZSBjb25zdHJ1Y3RlZCBpbiBhIHNpbWlsYXIgd2F5IHRvIHRoZSBxdWVyaWVzIHdlIHBlcmZvcm1lZCB3aXRoIHRoZSBgb3JnLkhzLmVnLmRiYCBwYWNrYWdlLiBJbnN0ZWFkIG9mIGBrZXlzYCB3ZSBoYXZlIGBmaWx0ZXJzYCwgYW5kIGluc3RlYWQgb2YgYGNvbHVtbnNgIHdlIGhhdmUgYXR0cmlidXRlcy4gVGhlIGxpc3Qgb2YgYWNjZXB0YWJsZSB2YWx1ZXMgaXMgbXVjaCBtb3JlIGNvbXByZWhlbnNpdmUgdGhhdCBmb3IgdGhlIGBvcmcuSHMuZWcuZGJgIHBhY2thZ2UuCgpgYGB7ciBldmFsPUZBTFNFfQpsaXN0RmlsdGVycyhlbnNlbWJsKSAlPiUgCiAgICBmaWx0ZXIoZ3JlcGwoImVuc2VtYmwiLG5hbWUpKQpgYGAKCgpgYGB7ciBldmFsPUZBTFNFfQpsaXN0QXR0cmlidXRlcyhlbnNlbWJsKSAlPiUgCiAgICBmaWx0ZXIoZ3JlcGwoImdlbmUiLG5hbWUpKQpgYGAKCkFuIGFkdmFudGFnZSBvdmVyIHRoZSBgb3JnLi5gIHBhY2thZ2VzIGlzIHRoYXQgcG9zaXRpb25hbCBpbmZvcm1hdGlvbiBjYW4gYmUgcmV0cmlldmVkCgpgYGB7ciBldmFsPUZBTFNFfQphdHRyaWJ1dGVOYW1lcyA8LSBjKCdlbnNlbWJsX2dlbmVfaWQnLCAnZW50cmV6Z2VuZV9pZCcsICdleHRlcm5hbF9nZW5lX25hbWUnLCAiY2hyb21vc29tZV9uYW1lIiwic3RhcnRfcG9zaXRpb24iLCJlbmRfcG9zaXRpb24iKQoKZ2V0Qk0oYXR0cmlidXRlcyA9IGF0dHJpYnV0ZU5hbWVzLAogICAgICBmaWx0ZXJzID0gImVuc2VtYmxfZ2VuZV9pZCIsCiAgICAgIHZhbHVlcz10b3BfZ2VuZXMsCiAgICAgIG1hcnQ9ZW5zZW1ibCkKYGBgCgoK