Learning outcomes

  • Construction and interpretation of common visualisations for RNA-seq
    • volcano plots
    • heatmaps
  • 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")

If you did not save the results from comparing TGF to CTR, you can use an object from the course materials

library(DESeq2)
results_tgf <- readRDS("Robjects/results_TGF_vs_CTR_annotated_BACKUP.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