Gene Set Testing
In the early days of microarray analysis, people were happy if they got a handful of differentially-expressed genes that they could validate or follow-up. However, with later technologies (and depending on the experimental setup) we might have thousands of statistically-significant results, which no-one has the time to follow-up. Also, we might be interested in pathways / mechanisms that are altered and not just individual genes.
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
- There is also a bunch of websites for doing the tests
- we will show how they are done in Bioconductor so the theory is clear
- We will assume we have done a differential-expression analysis, but the same techniques can be used for other situations when we have a gene list
fgsea analysis
The fgsea package is a free implementation of the Broad’s GSEA software and is described in more detail in the package vignette “fast preranked gene set enrichment analysis (GSEA)”:
The GSEA analysis is performed by:
- ranking all genes in the data set based on their correlation to the chosen phenotype,
- identifying the rank positions of all members of the gene set, and
- calculating an enrichment score (ES) that represents the difference between the observed rankings and that which would be expected assuming a random rank distribution.
“After establishing the ES for each gene set across the phenotype, GSEA reiteratively randomizes the sample labels and retests for enrichment across the random classes. By performing repeated class label randomizations, the ES for each gene set across the true classes can be compared to the ES distribution from the random classes. Those gene sets that significantly outperform iterative random class permutations are considered significant.”
The article describing the original software is available here and there is also a commentary on GSEA.
In addition to the GSEA software the Broad also provide a number of very well curated gene sets for testing against your data - the Molecular Signatures Database (MSigDB). Unfortunately, these are collections of human genes. However, these lists have been translated to mouse equivalents by the Walter+Eliza Hall Institutes Bioinformatics service and made avaialble for download. These gene sets use Entrez ID as their identifier.
library(fgsea)
Loading required package: Rcpp
load("Robjects/LvsV_annotated.rdata")
head(results.annotated)
ENSEMBL baseMean log2FoldChange lfcSE stat pvalue
1 ENSMUSG00000000381 398943.44 8.722325 0.3815879 22.94608 1.612341e-116
2 ENSMUSG00000061937 1134857.22 7.351405 0.3761569 19.72683 1.268851e-86
3 ENSMUSG00000026417 11320.43 6.078220 0.3332017 18.13304 1.748099e-73
4 ENSMUSG00000022491 514850.04 7.765956 0.4539439 17.96995 3.350319e-72
5 ENSMUSG00000061388 30555.67 8.282907 0.4740455 17.36398 1.546169e-67
6 ENSMUSG00000024903 60039.52 7.312158 0.4522371 16.27868 1.398616e-59
padj SYMBOL GENENAME ENTREZID
1 2.785480e-112 Wap whey acidic protein 22373
2 1.096033e-82 Csn1s2a casein alpha s2-like A 12993
3 1.006672e-69 Pigr polymeric immunoglobulin receptor 18703
4 1.447003e-68 Glycam1 glycosylation dependent cell adhesion molecule 1 14663
5 5.342324e-64 Csn1s2b casein alpha s2-like B 12992
6 4.027083e-56 Lao1 L-amino acid oxidase 1 100470
An appealing feature of the 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 Entrez ID.
library(dplyr)
gseaInput <- filter(results.annotated, !is.na(ENTREZID)) %>%
arrange(stat)
ranks <- pull(gseaInput,stat)
names(ranks) <- gseaInput$ENTREZID
The Walter+Eliza Hall Institutes Bioinformatics service have made mouse versions of the MSigDB datasets available for download. This should already be available in the Robjects
folder.
load("Robjects/mouse_H_v5p2.rdata")
pathways <- Mm.H
The analysis is one call to the fgsea
function. We can automatically exclude any pathways with too many or too few genes.
library(fgsea)
fgseaRes <- fgsea(pathways, ranks, minSize=15, maxSize = 500, nperm=1000)
There are duplicate gene names, fgsea may produce unexpected results
dim(fgseaRes)
[1] 50 8
#head(fgseaRes)
The results table gives the names of each pathway that was tested and the stats from doing the test. We can make this into a “tidy” object with the following code.
fgseaResTidy <- fgseaRes %>%
as_tibble() %>%
arrange(desc(NES))
# Show in a nice table:
fgseaResTidy
library(ggplot2)
ggplot(fgseaResTidy, aes(reorder(pathway, NES), NES)) +
geom_col(aes(fill=padj<0.05)) +
coord_flip() +
labs(x="Pathway", y="Normalized Enrichment Score",
title="Hallmark pathways NES from GSEA")
The enrichment plot will show where the genes belonging to a particular gene set are towards the top or the bottom of the genelist, and how the enrichment score is calculated across the dataset.
Here we show the enrichment plot for the pathway with the most positive enrichment score.
plotEnrichment(pathways[["HALLMARK_OXIDATIVE_PHOSPHORYLATION"]],
ranks)
Challenge 1
- What pathway has the most extreme negative enrichment score? How can you identify this pathway from the results table?
- Make the enrichment plot for this pathway
Visualising pathways using a heatmap
The names of genes involved in particular pathways can be extracted from the pathways
object. We can then use these genes to make a heatmap, or other visualisation, to show how these genes partition the dataset.
### Load the dds object if not present
load("Robjects/preprocessing.Rdata")
Loading required package: DESeq2
Loading required package: S4Vectors
Loading required package: stats4
Loading required package: BiocGenerics
Loading required package: parallel
Attaching package: ‘BiocGenerics’
The following objects are masked from ‘package:parallel’:
clusterApply, clusterApplyLB, clusterCall, clusterEvalQ, clusterExport,
clusterMap, parApply, parCapply, parLapply, parLapplyLB, parRapply, parSapply,
parSapplyLB
The following objects are masked from ‘package:dplyr’:
combine, intersect, setdiff, union
The following objects are masked from ‘package:stats’:
IQR, mad, sd, var, xtabs
The following objects are masked from ‘package:base’:
anyDuplicated, append, as.data.frame, basename, cbind, colnames, dirname,
do.call, duplicated, eval, evalq, Filter, Find, get, grep, grepl, intersect,
is.unsorted, lapply, Map, mapply, match, mget, order, paste, pmax, pmax.int,
pmin, pmin.int, Position, rank, rbind, Reduce, rownames, sapply, setdiff, sort,
table, tapply, union, unique, unsplit, which, which.max, which.min
Attaching package: ‘S4Vectors’
The following objects are masked from ‘package:dplyr’:
first, rename
The following object is masked from ‘package:base’:
expand.grid
Loading required package: IRanges
Attaching package: ‘IRanges’
The following objects are masked from ‘package:dplyr’:
collapse, desc, slice
Loading required package: GenomicRanges
Loading required package: GenomeInfoDb
Loading required package: SummarizedExperiment
Loading required package: Biobase
Welcome to Bioconductor
Vignettes contain introductory material; view with 'browseVignettes()'. To cite
Bioconductor, see 'citation("Biobase")', and for packages 'citation("pkgname")'.
Loading required package: DelayedArray
Loading required package: matrixStats
Attaching package: ‘matrixStats’
The following objects are masked from ‘package:Biobase’:
anyMissing, rowMedians
The following object is masked from ‘package:dplyr’:
count
Loading required package: BiocParallel
Attaching package: ‘DelayedArray’
The following objects are masked from ‘package:matrixStats’:
colMaxs, colMins, colRanges, rowMaxs, rowMins, rowRanges
The following objects are masked from ‘package:base’:
aperm, apply, rowsum
library(pheatmap)
my_genes <- filter(results.annotated, ENTREZID %in% pathways[["HALLMARK_MYC_TARGETS_V2"]]) %>%
pull(ENSEMBL)
vsd <- vst(dds)
using 'avgTxLength' from assays(dds), correcting for library size
mat <- assay(vsd)[my_genes,]
mat <- mat - rowMeans(mat)
dim(mat)
[1] 65 12
rownames(sampleinfo) <- sampleinfo$run
pheatmap(mat,
annotation_col = sampleinfo[,c("Status","CellType")])
Gene Set Testing - competitive gene set tests
GOseq analysis
GOseq is a method to conduct Gene Ontology (GO) analysis suitable for RNA-seq data as it accounts for the gene length bias in detection of over-representation (GOseq article)
From the GOseq vignette:
- GOseq first needs to quantify the length bias present in the dataset under consideration.
- This is done by calculating a Probability Weighting Function or PWF which can be thought of as a function which gives the probability that a gene will be differentially expressed (DE), based on its length alone.
- The PWF is calculated by fitting a monotonic spline to the binary data series of differential expression (1=DE, 0=Not DE) as a function of gene length.
- The PWF is used to weight the chance of selecting each gene when forming a null distribution for GO category membership.
- The fact that the PWF is calculated directly from the dataset under consideration makes this approach robust, only correcting for the length bias present in the data.
“GO analysis of RNA-seq data requires the use of random sampling in order to generate a suitable null distribution for GO category membership and calculate each category’s significance for over representation amongst DE genes. … In most cases, the Wallenius distribution can be used to approximate the true null distribution, without any significant loss in accuracy. The goseq package implements this approximation as its default option.”
First, create list of DEGs. We don’t have to be too strict about our criteria for a gene to be differentially-expressed, as too few genes will not give us many enriched pathways.
genes <- results.annotated$padj < 0.05 & !is.na(results.annotated$padj)
names(genes) <- results.annotated$ENSEMBL
Fit the Probability Weighting Function (PWF):
library(goseq)
if(!require(TxDb.Mmusculus.UCSC.mm10.knownGene)) BiocManager::install("TxDb.Mmusculus.UCSC.mm10.knownGene")
#print(supportedGeneIDs())
#print(supportedGenomes())
pwf <- nullp(genes, "mm10","ensGene")
libraries ‘/usr/local/lib/R/site-library’, ‘/usr/lib/R/site-library’ contain no packagesargument 'pattern' has length > 1 and only the first element will be usedcollapsing to unique 'x' valuescollapsing to unique 'x' valuescollapsing to unique 'x' valuescollapsing to unique 'x' valuescollapsing to unique 'x' valuescollapsing to unique 'x' valuescollapsing to unique 'x' valuescollapsing to unique 'x' valuescollapsing to unique 'x' valuescollapsing to unique 'x' valuescollapsing to unique 'x' valuescollapsing to unique 'x' values
Conduct gene set enrichment analysis:
r
r #?goseq goseq_res <- goseq(pwf, 10,,test.cats=:BP)
r goseq_res
?goseq
Analysis with clusterProfiler
clusterProfiler
is another Bioconductor package for over-representation analysis. It’s main advantage is that it provides some nice visualisation methods.
Firstly, we can identify over-represented GO terms and visualise these as a network.
library(clusterProfiler)
universe <- results.annotated %>% pull(ENTREZID)
sigGenes <- results.annotated %>%
filter(padj < 0.05, !is.na(ENTREZID)) %>% pull(ENTREZID)
enrich_go <- enrichGO(
gene= sigGenes,
OrgDb = org.Mm.eg.db,
keyType = "ENTREZID",
ont = "BP",
universe = universe,
qvalueCutoff = 0.05,
readable=TRUE
)
r
r dotplot(enrich_go)
A dot plot can show us the most enriched pathways, and the size of each.
dotplot(enrich_go)
emapplot(enrich_go)
We also perform enrichment of KEGG pathways with the clusterProfiler
package. We could do this with goseq
, but again clusterProfiler
has some nice visualisations and uses online resources rather than relying on Bioconductor annotation.
To run the analysis we can use the same list of genes as before, and need to know the kegg code for our given organism.
search_kegg_organism('mouse', by='common_name')
keg_res <- enrichKEGG(gene=sigGenes, organism="mmu")
head(keg_res,n=10)
If we want to view the network for any pathway, we can use the browseKEGG
function which will take us to the relevant page on the KEGG website.
We will choose an example of a smaller pathway to make things a bit clearer.
browseKEGG(keg_res, 'mmu03320')
The pathview
package will produce a version of this plot (png
) in your working directory.
library(pathview)
logFC <- results.annotated$log2FoldChange
names(logFC) <- results.annotated$ENTREZID
pathview(gene.data = logFC,
pathway.id = "mmu03320",
species = "mmu",
limit = list(gene=5, cpd=1))
LS0tCnRpdGxlOiAiUk5BLXNlcSBhbmFseXNpcyBpbiBSIgphdXRob3I6ICJTdGVwaGFuZSBCYWxsZXJlYXUsIE1hcmsgRHVubmluZywgT3NjYXIgUnVlZGEsIEFzaGxleSBTYXdsZSIKZGF0ZTogIkZlYnJ1YXJ5IDIwMjAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwptaW51dGVzOiAzMDAKbGF5b3V0OiBwYWdlCnN1YnRpdGxlOiBHZW5lIFNldCBUZXN0aW5nIGZvciBSTkEtc2VxCgotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKKipPcmlnaW5hbCBBdXRob3JzOiBCZWxpbmRhIFBoaXBzb24sIEFubmEgVHJpZ29zLCBNYXR0IFJpdGNoaWUsIE1hcmlhIERveWxlLCBIYXJyaWV0IERhc2hub3csIENoYXJpdHkgTGF3KiosICoqU3RlcGhhbmUgQmFsbGVyZWF1LCBPc2NhciBSdWVkYSwgQXNobGV5IFNhd2xlKioKQmFzZWQgb24gdGhlIGNvdXJzZSBbUk5Bc2VxIGFuYWx5c2lzIGluIFJdKGh0dHA6Ly9jb21iaW5lLWF1c3RyYWxpYS5naXRodWIuaW8vMjAxNi0wNS0xMS1STkFzZXEvKSBkZWxpdmVyZWQgb24gTWF5IDExLzEydGggMjAxNiBhbmQgbW9kaWZpZWQgYnkgQ2FuY2VyIFJlc2VhcmNoIFVrIENhbWJyaWRnZSBDZW50cmUgZm9yIHRoZSBbRnVuY3Rpb25hbCBHZW5vbWljcyBBdXR1bW4gU2Nob29sIDIwMTddKGh0dHBzOi8vYmlvaW5mb3JtYXRpY3MtY29yZS1zaGFyZWQtdHJhaW5pbmcuZ2l0aHViLmlvL2NydWstYXV0dW1uLXNjaG9vbC0yMDE3LykKCiMjIFJlc291cmNlcyBhbmQgZGF0YSBmaWxlcwoKVGhpcyBtYXRlcmlhbCBoYXMgYmVlbiBjcmVhdGVkIHVzaW5nIHRoZSBmb2xsb3dpbmcgcmVzb3VyY2VzOiAgCmh0dHA6Ly93d3cuc3RhdHNjaS5vcmcvc215dGgvcHVicy9RTGVkZ2VSUHJlcHJpbnQucGRmIFtATHVuMjAxNl0gIApodHRwOi8vbW9uYXNoYmlvaW5mb3JtYXRpY3NwbGF0Zm9ybS5naXRodWIuaW8vUk5Bc2VxLURFLWFuYWx5c2lzLXdpdGgtUi85OS1STkFzZXFfREVfYW5hbHlzaXNfd2l0aF9SLmh0bWwgIApodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9kZXZlbC9iaW9jL3ZpZ25ldHRlcy9ERVNlcTIvaW5zdC9kb2MvREVTZXEyLmh0bWwKaHR0cHM6Ly9iaW9jb25kdWN0b3IuZ2l0aHViLmlvL0Jpb2NXb3Jrc2hvcHMvcm5hLXNlcS1kYXRhLWFuYWx5c2lzLXdpdGgtZGVzZXEyLmh0bWwKClRoaXMgc2VjdGlvbiBhbHNvIHVzZXMgY29kZSBmcm9tIFN0ZXBoZW4gVHVybmVyJ3MgZ3VpZGUgdG8gZmdzZWEgaHR0cHM6Ly9zdGVwaGVudHVybmVyLmdpdGh1Yi5pby9kZXNlcS10by1mZ3NlYS8KCiMgR2VuZSBTZXQgVGVzdGluZwoKSW4gdGhlIGVhcmx5IGRheXMgb2YgbWljcm9hcnJheSBhbmFseXNpcywgcGVvcGxlIHdlcmUgaGFwcHkgaWYgdGhleSBnb3QgYSBoYW5kZnVsIG9mIGRpZmZlcmVudGlhbGx5LWV4cHJlc3NlZCBnZW5lcyB0aGF0IHRoZXkgY291bGQgdmFsaWRhdGUgb3IgZm9sbG93LXVwLiBIb3dldmVyLCB3aXRoIGxhdGVyIHRlY2hub2xvZ2llcyAoYW5kIGRlcGVuZGluZyBvbiB0aGUgZXhwZXJpbWVudGFsIHNldHVwKSB3ZSBtaWdodCBoYXZlIHRob3VzYW5kcyBvZiBzdGF0aXN0aWNhbGx5LXNpZ25pZmljYW50IHJlc3VsdHMsIHdoaWNoIG5vLW9uZSBoYXMgdGhlIHRpbWUgdG8gZm9sbG93LXVwLiBBbHNvLCB3ZSBtaWdodCBiZSBpbnRlcmVzdGVkIGluIHBhdGh3YXlzIC8gbWVjaGFuaXNtcyB0aGF0IGFyZSBhbHRlcmVkIGFuZCBub3QganVzdCBpbmRpdmlkdWFsIGdlbmVzLgoKSW4gdGhpcyBzZWN0aW9uIHdlIG1vdmUgdG93YXJkcyBkaXNjb3ZlcmluZyBpZiBvdXIgcmVzdWx0cyBhcmUgKioqYmlvbG9naWNhbGx5IHNpZ25pZmljYW50KioqLiBBcmUgdGhlIGdlbmVzIHRoYXQgd2UgaGF2ZSBwaWNrZWQgc3RhdGlzdGljYWwgZmx1a2VzLCBvciBhcmUgdGhlcmUgc29tZSBjb21tb25hbGl0aWVzLiAKClRoZXJlIGFyZSB0d28gZGlmZmVyZW50IGFwcHJvYWNoZXMgb25lIG1pZ2h0IHVzZSwgYW5kIHdlIHdpbGwgY292ZXIgdGhlIHRoZW9yeSBiZWhpbmQgYm90aAoKLSBUaGVyZSBpcyBhbHNvIGEgYnVuY2ggb2Ygd2Vic2l0ZXMgZm9yIGRvaW5nIHRoZSB0ZXN0cwogICAgKyB3ZSB3aWxsIHNob3cgaG93IHRoZXkgYXJlIGRvbmUgaW4gQmlvY29uZHVjdG9yIHNvIHRoZSB0aGVvcnkgaXMgY2xlYXIKLSBXZSB3aWxsIGFzc3VtZSB3ZSBoYXZlIGRvbmUgYSBkaWZmZXJlbnRpYWwtZXhwcmVzc2lvbiBhbmFseXNpcywgYnV0IHRoZSBzYW1lIHRlY2huaXF1ZXMgY2FuIGJlIHVzZWQgZm9yIG90aGVyIHNpdHVhdGlvbnMgd2hlbiB3ZSBoYXZlIGEgZ2VuZSBsaXN0CiAgICArIENoSVAtc2VxCiAgICArIFJOQS1zZXEKICAgIAoKIyMjIGZnc2VhIGFuYWx5c2lzCgpUaGUgZmdzZWEgcGFja2FnZSBpcyBhIGZyZWUgaW1wbGVtZW50YXRpb24gb2YgdGhlIEJyb2FkJ3MgR1NFQSBzb2Z0d2FyZSBhbmQgaXMgZGVzY3JpYmVkIGluIG1vcmUgZGV0YWlsIGluIHRoZSBwYWNrYWdlIFt2aWduZXR0ZV0oaHR0cDovL3d3dy5iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy92aWduZXR0ZXMvZmdzZWEvaW5zdC9kb2MvZmdzZWEtdHV0b3JpYWwuaHRtbCkgImZhc3QgcHJlcmFua2VkIGdlbmUgc2V0IGVucmljaG1lbnQgYW5hbHlzaXMgKEdTRUEpIjoKClRoZSBHU0VBIGFuYWx5c2lzIGlzIHBlcmZvcm1lZCBieToKCi0gKGkpIHJhbmtpbmcgYWxsIGdlbmVzIGluIHRoZSBkYXRhIHNldCBiYXNlZCBvbiB0aGVpciBjb3JyZWxhdGlvbiB0byB0aGUgY2hvc2VuIHBoZW5vdHlwZSwKLSAoaWkpIGlkZW50aWZ5aW5nIHRoZSByYW5rIHBvc2l0aW9ucyBvZiBhbGwgbWVtYmVycyBvZiB0aGUgZ2VuZSBzZXQsIGFuZCAKLSAoaWlpKSBjYWxjdWxhdGluZyBhbiBlbnJpY2htZW50IHNjb3JlIChFUykgdGhhdCByZXByZXNlbnRzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIG9ic2VydmVkIHJhbmtpbmdzIGFuZCB0aGF0IHdoaWNoIHdvdWxkIGJlIGV4cGVjdGVkIGFzc3VtaW5nIGEgcmFuZG9tIHJhbmsgZGlzdHJpYnV0aW9uLgoKPiAiQWZ0ZXIgZXN0YWJsaXNoaW5nIHRoZSBFUyBmb3IgZWFjaCBnZW5lIHNldCBhY3Jvc3MgdGhlIHBoZW5vdHlwZSwgR1NFQSByZWl0ZXJhdGl2ZWx5IHJhbmRvbWl6ZXMgdGhlIHNhbXBsZSBsYWJlbHMgYW5kIHJldGVzdHMgZm9yIGVucmljaG1lbnQgYWNyb3NzIHRoZSByYW5kb20gY2xhc3Nlcy4gQnkgcGVyZm9ybWluZyByZXBlYXRlZCBjbGFzcyBsYWJlbCByYW5kb21pemF0aW9ucywgdGhlIEVTIGZvciBlYWNoIGdlbmUgc2V0IGFjcm9zcyB0aGUgdHJ1ZSBjbGFzc2VzIGNhbiBiZSBjb21wYXJlZCB0byB0aGUgRVMgZGlzdHJpYnV0aW9uIGZyb20gdGhlIHJhbmRvbSBjbGFzc2VzLiBUaG9zZSBnZW5lIHNldHMgdGhhdCBzaWduaWZpY2FudGx5IG91dHBlcmZvcm0gaXRlcmF0aXZlIHJhbmRvbSBjbGFzcyBwZXJtdXRhdGlvbnMgYXJlIGNvbnNpZGVyZWQgc2lnbmlmaWNhbnQuIiAKClRoZSBhcnRpY2xlIGRlc2NyaWJpbmcgdGhlIG9yaWdpbmFsIHNvZnR3YXJlIGlzIGF2YWlsYWJsZSBbaGVyZV0oaHR0cDovL3d3dy5wbmFzLm9yZy9jb250ZW50LzEwMi80My8xNTU0NS5sb25nKSBhbmQgdGhlcmUgaXMgYWxzbyBhIFtjb21tZW50YXJ5IG9uIEdTRUFdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzEyNjYxMzEvKS4gCgpJbiBhZGRpdGlvbiB0byB0aGUgR1NFQSBzb2Z0d2FyZSB0aGUgQnJvYWQgYWxzbyBwcm92aWRlIGEgbnVtYmVyIG9mIHZlcnkgd2VsbCBjdXJhdGVkIGdlbmUgc2V0cyBmb3IgdGVzdGluZyBhZ2FpbnN0IHlvdXIgZGF0YSAtIHRoZSBNb2xlY3VsYXIgU2lnbmF0dXJlcyBEYXRhYmFzZSAoTVNpZ0RCKS4gVW5mb3J0dW5hdGVseSwgdGhlc2UgYXJlIGNvbGxlY3Rpb25zIG9mIGh1bWFuIGdlbmVzLiBIb3dldmVyLCB0aGVzZSBsaXN0cyBoYXZlIGJlZW4gdHJhbnNsYXRlZCB0byBtb3VzZSBlcXVpdmFsZW50cyBieSB0aGUgV2FsdGVyK0VsaXphIEhhbGwgSW5zdGl0dXRlcyBCaW9pbmZvcm1hdGljcyBzZXJ2aWNlIGFuZCBtYWRlIGF2YWlhbGJsZSBmb3IgZG93bmxvYWQuIFRoZXNlIGdlbmUgc2V0cyB1c2UgKkVudHJleiBJRCogYXMgdGhlaXIgaWRlbnRpZmllci4KCmBgYHtyfQpsaWJyYXJ5KGZnc2VhKQpgYGAKCgpgYGB7cn0KbG9hZCgiUm9iamVjdHMvTHZzVl9hbm5vdGF0ZWQucmRhdGEiKQpoZWFkKHJlc3VsdHMuYW5ub3RhdGVkKQpgYGAKCkFuIGFwcGVhbGluZyBmZWF0dXJlIG9mIHRoZSBtZXRob2QgaXMgdGhhdCBpdCBkb2VzIG5vdCByZXF1aXJlIHVzIHRvIGltcG9zZSBhcmJpdHJhcnkgY3V0LW9mZnMgb24gdGhlIGRhdGFzZXQgdG8gZGVjaWRlIHdoYXQgaXMgZGlmZmVyZW50aWFsbHktZXhwcmVzc2VkIG9yIG5vdC4gVGhlIHN0ZXBzIGluIHByb2R1Y2luZyB0aGUgaW5wdXQgcmVxdWlyZWQgZm9yIEdTRUEgYXJlIGkpIHJldHJpZXZpbmcgdGhlIHJhbmtlZCBzdGF0aXN0aWNzIGlpKSBuYW1pbmcgZWFjaCBvbmUgYWNjb3JkaW5nIHRvIEVudHJleiBJRC4KCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpnc2VhSW5wdXQgPC0gZmlsdGVyKHJlc3VsdHMuYW5ub3RhdGVkLCAhaXMubmEoRU5UUkVaSUQpKSAlPiUgCiAgYXJyYW5nZShzdGF0KQpyYW5rcyA8LSBwdWxsKGdzZWFJbnB1dCxzdGF0KQpuYW1lcyhyYW5rcykgPC0gZ3NlYUlucHV0JEVOVFJFWklECmBgYAoKClRoZSBXYWx0ZXIrRWxpemEgSGFsbCBJbnN0aXR1dGVzIEJpb2luZm9ybWF0aWNzIHNlcnZpY2UgaGF2ZSBtYWRlIG1vdXNlIHZlcnNpb25zIG9mIHRoZSBNU2lnREIgZGF0YXNldHMgYXZhaWxhYmxlIGZvciBkb3dubG9hZC4gVGhpcyBzaG91bGQgYWxyZWFkeSBiZSBhdmFpbGFibGUgaW4gdGhlIGBSb2JqZWN0c2AgZm9sZGVyLgoKYGBge3J9CmxvYWQoIlJvYmplY3RzL21vdXNlX0hfdjVwMi5yZGF0YSIpCnBhdGh3YXlzIDwtIE1tLkgKYGBgCgpUaGUgYW5hbHlzaXMgaXMgb25lIGNhbGwgdG8gdGhlIGBmZ3NlYWAgZnVuY3Rpb24uIFdlIGNhbiBhdXRvbWF0aWNhbGx5IGV4Y2x1ZGUgYW55IHBhdGh3YXlzIHdpdGggdG9vIG1hbnkgb3IgdG9vIGZldyBnZW5lcy4KCmBgYHtyfQpsaWJyYXJ5KGZnc2VhKQpmZ3NlYVJlcyA8LSBmZ3NlYShwYXRod2F5cywgcmFua3MsIG1pblNpemU9MTUsIG1heFNpemUgPSA1MDAsIG5wZXJtPTEwMDApCmRpbShmZ3NlYVJlcykKI2hlYWQoZmdzZWFSZXMpCmBgYAoKVGhlIHJlc3VsdHMgdGFibGUgZ2l2ZXMgdGhlIG5hbWVzIG9mIGVhY2ggcGF0aHdheSB0aGF0IHdhcyB0ZXN0ZWQgYW5kIHRoZSBzdGF0cyBmcm9tIGRvaW5nIHRoZSB0ZXN0LiBXZSBjYW4gbWFrZSB0aGlzIGludG8gYSAidGlkeSIgb2JqZWN0IHdpdGggdGhlIGZvbGxvd2luZyBjb2RlLgoKYGBge3J9CmZnc2VhUmVzVGlkeSA8LSBmZ3NlYVJlcyAlPiUKICBhc190aWJibGUoKSAlPiUKICBhcnJhbmdlKGRlc2MoTkVTKSkKCiMgU2hvdyBpbiBhIG5pY2UgdGFibGU6CmZnc2VhUmVzVGlkeSAKYGBgCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpnZ3Bsb3QoZmdzZWFSZXNUaWR5LCBhZXMocmVvcmRlcihwYXRod2F5LCBORVMpLCBORVMpKSArCiAgZ2VvbV9jb2woYWVzKGZpbGw9cGFkajwwLjA1KSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4PSJQYXRod2F5IiwgeT0iTm9ybWFsaXplZCBFbnJpY2htZW50IFNjb3JlIiwKICAgICAgIHRpdGxlPSJIYWxsbWFyayBwYXRod2F5cyBORVMgZnJvbSBHU0VBIikKYGBgCgoKClRoZSBlbnJpY2htZW50IHBsb3Qgd2lsbCBzaG93IHdoZXJlIHRoZSBnZW5lcyBiZWxvbmdpbmcgdG8gYSBwYXJ0aWN1bGFyIGdlbmUgc2V0IGFyZSB0b3dhcmRzIHRoZSB0b3Agb3IgdGhlIGJvdHRvbSBvZiB0aGUgZ2VuZWxpc3QsIGFuZCBob3cgdGhlICplbnJpY2htZW50IHNjb3JlKiBpcyBjYWxjdWxhdGVkIGFjcm9zcyB0aGUgZGF0YXNldC4KCkhlcmUgd2Ugc2hvdyB0aGUgZW5yaWNobWVudCBwbG90IGZvciB0aGUgcGF0aHdheSB3aXRoIHRoZSBtb3N0IHBvc2l0aXZlIGVucmljaG1lbnQgc2NvcmUuCgpgYGB7cn0KcGxvdEVucmljaG1lbnQocGF0aHdheXNbWyJIQUxMTUFSS19PWElEQVRJVkVfUEhPU1BIT1JZTEFUSU9OIl1dLAogICAgICAgICAgICAgICByYW5rcykKYGBgCgo+ICMjIENoYWxsZW5nZSAxey5jaGFsbGVuZ2V9Cj4gMS4gV2hhdCBwYXRod2F5IGhhcyB0aGUgbW9zdCBleHRyZW1lIG5lZ2F0aXZlIGVucmljaG1lbnQgc2NvcmU/IEhvdyBjYW4geW91IGlkZW50aWZ5IHRoaXMgcGF0aHdheSBmcm9tIHRoZSByZXN1bHRzIHRhYmxlPwo+IDIuIE1ha2UgdGhlIGVucmljaG1lbnQgcGxvdCBmb3IgdGhpcyBwYXRod2F5CgojIyMgVmlzdWFsaXNpbmcgcGF0aHdheXMgdXNpbmcgYSBoZWF0bWFwCgpUaGUgbmFtZXMgb2YgZ2VuZXMgaW52b2x2ZWQgaW4gcGFydGljdWxhciBwYXRod2F5cyBjYW4gYmUgZXh0cmFjdGVkIGZyb20gdGhlIGBwYXRod2F5c2Agb2JqZWN0LiBXZSBjYW4gdGhlbiB1c2UgdGhlc2UgZ2VuZXMgdG8gbWFrZSBhIGhlYXRtYXAsIG9yIG90aGVyIHZpc3VhbGlzYXRpb24sIHRvIHNob3cgaG93IHRoZXNlIGdlbmVzIHBhcnRpdGlvbiB0aGUgZGF0YXNldC4KCmBgYHtyfQojIyMgTG9hZCB0aGUgZGRzIG9iamVjdCBpZiBub3QgcHJlc2VudApsb2FkKCJSb2JqZWN0cy9wcmVwcm9jZXNzaW5nLlJkYXRhIikKbGlicmFyeShwaGVhdG1hcCkKbXlfZ2VuZXMgPC0gZmlsdGVyKHJlc3VsdHMuYW5ub3RhdGVkLCBFTlRSRVpJRCAlaW4lIHBhdGh3YXlzW1siSEFMTE1BUktfTVlDX1RBUkdFVFNfVjIiXV0pICU+JSAKICBwdWxsKEVOU0VNQkwpCgp2c2QgPC0gdnN0KGRkcykKbWF0IDwtIGFzc2F5KHZzZClbbXlfZ2VuZXMsXQptYXQgPC0gbWF0IC0gcm93TWVhbnMobWF0KQpkaW0obWF0KQpyb3duYW1lcyhzYW1wbGVpbmZvKSA8LSBzYW1wbGVpbmZvJHJ1bgoKcGhlYXRtYXAobWF0LAogICAgICAgICBhbm5vdGF0aW9uX2NvbCA9IHNhbXBsZWluZm9bLGMoIlN0YXR1cyIsIkNlbGxUeXBlIildKQpgYGAKCiMjIEdlbmUgU2V0IFRlc3RpbmcgLSBjb21wZXRpdGl2ZSBnZW5lIHNldCB0ZXN0cwoKIyMjIEdPc2VxIGFuYWx5c2lzCgpHT3NlcSBpcyBhIG1ldGhvZCB0byBjb25kdWN0IEdlbmUgT250b2xvZ3kgKEdPKSBhbmFseXNpcyBzdWl0YWJsZSBmb3IgUk5BLXNlcSBkYXRhIGFzIGl0IGFjY291bnRzIGZvciB0aGUgZ2VuZSBsZW5ndGggYmlhcyBpbiBkZXRlY3Rpb24gb2Ygb3Zlci1yZXByZXNlbnRhdGlvbiAoW0dPc2VxIGFydGljbGVdKGh0dHBzOi8vZ2Vub21lYmlvbG9neS5iaW9tZWRjZW50cmFsLmNvbS9hcnRpY2xlcy8xMC4xMTg2L2diLTIwMTAtMTEtMi1yMTQpKQoKRnJvbSB0aGUgW0dPc2VxIHZpZ25ldHRlXShodHRwczovL3d3dy5iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy92aWduZXR0ZXMvZ29zZXEvaW5zdC9kb2MvZ29zZXEucGRmKToKCi0gR09zZXEgZmlyc3QgbmVlZHMgdG8gcXVhbnRpZnkgdGhlIGxlbmd0aCBiaWFzIHByZXNlbnQgaW4gdGhlIGRhdGFzZXQgdW5kZXIgY29uc2lkZXJhdGlvbi4KLSBUaGlzIGlzIGRvbmUgYnkgY2FsY3VsYXRpbmcgYSBQcm9iYWJpbGl0eSBXZWlnaHRpbmcgRnVuY3Rpb24gb3IgUFdGIHdoaWNoIGNhbiBiZSB0aG91Z2h0IG9mIGFzIGEgZnVuY3Rpb24gd2hpY2ggZ2l2ZXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgYSBnZW5lIHdpbGwgYmUgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIChERSksIGJhc2VkIG9uIGl0cyBsZW5ndGggYWxvbmUuCi0gVGhlIFBXRiBpcyBjYWxjdWxhdGVkIGJ5IGZpdHRpbmcgYSBtb25vdG9uaWMgc3BsaW5lIHRvIHRoZSBiaW5hcnkgZGF0YSBzZXJpZXMgb2YgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gKDE9REUsIDA9Tm90IERFKSBhcyBhIGZ1bmN0aW9uIG9mIGdlbmUgbGVuZ3RoLgotIFRoZSBQV0YgaXMgdXNlZCB0byB3ZWlnaHQgdGhlIGNoYW5jZSBvZiBzZWxlY3RpbmcgZWFjaCBnZW5lIHdoZW4gZm9ybWluZyBhIG51bGwgZGlzdHJpYnV0aW9uIGZvciBHTyBjYXRlZ29yeSBtZW1iZXJzaGlwLgotIFRoZSBmYWN0IHRoYXQgdGhlIFBXRiBpcyBjYWxjdWxhdGVkIGRpcmVjdGx5IGZyb20gdGhlIGRhdGFzZXQgdW5kZXIgY29uc2lkZXJhdGlvbiBtYWtlcyB0aGlzIGFwcHJvYWNoIHJvYnVzdCwgb25seSBjb3JyZWN0aW5nIGZvciB0aGUgbGVuZ3RoIGJpYXMgcHJlc2VudCBpbiB0aGUgZGF0YS4KCiJHTyBhbmFseXNpcyBvZiBSTkEtc2VxIGRhdGEgcmVxdWlyZXMgdGhlIHVzZSBvZiByYW5kb20gc2FtcGxpbmcgaW4gb3JkZXIgdG8gZ2VuZXJhdGUgYSBzdWl0YWJsZSBudWxsIGRpc3RyaWJ1dGlvbiBmb3IgR08gY2F0ZWdvcnkgbWVtYmVyc2hpcCBhbmQgY2FsY3VsYXRlIGVhY2ggY2F0ZWdvcnkncyBzaWduaWZpY2FuY2UgZm9yIG92ZXIgcmVwcmVzZW50YXRpb24gYW1vbmdzdCBERSBnZW5lcy4gLi4uIEluICBtb3N0ICBjYXNlcywgIHRoZSAgV2FsbGVuaXVzIGRpc3RyaWJ1dGlvbiBjYW4gYmUgdXNlZCB0byBhcHByb3hpbWF0ZSB0aGUgdHJ1ZSBudWxsIGRpc3RyaWJ1dGlvbiwgd2l0aG91dCBhbnkgc2lnbmlmaWNhbnQgbG9zcyBpbiBhY2N1cmFjeS4gVGhlIGdvc2VxIHBhY2thZ2UgaW1wbGVtZW50cyB0aGlzIGFwcHJveGltYXRpb24gYXMgaXRzIGRlZmF1bHQgb3B0aW9uLiIKCkZpcnN0LCBjcmVhdGUgbGlzdCBvZiBERUdzLiBXZSBkb24ndCBoYXZlIHRvIGJlIHRvbyBzdHJpY3QgYWJvdXQgb3VyIGNyaXRlcmlhIGZvciBhIGdlbmUgdG8gYmUgZGlmZmVyZW50aWFsbHktZXhwcmVzc2VkLCBhcyB0b28gZmV3IGdlbmVzIHdpbGwgbm90IGdpdmUgdXMgbWFueSBlbnJpY2hlZCBwYXRod2F5cy4KCmBgYHtyfQpnZW5lcyA8LSByZXN1bHRzLmFubm90YXRlZCRwYWRqIDwgMC4wNSAmICFpcy5uYShyZXN1bHRzLmFubm90YXRlZCRwYWRqKQpuYW1lcyhnZW5lcykgPC0gcmVzdWx0cy5hbm5vdGF0ZWQkRU5TRU1CTApgYGAKCgpGaXQgdGhlIFByb2JhYmlsaXR5IFdlaWdodGluZyBGdW5jdGlvbiAoUFdGKToKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoZ29zZXEpCmlmKCFyZXF1aXJlKFR4RGIuTW11c2N1bHVzLlVDU0MubW0xMC5rbm93bkdlbmUpKSBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiVHhEYi5NbXVzY3VsdXMuVUNTQy5tbTEwLmtub3duR2VuZSIpCiNwcmludChzdXBwb3J0ZWRHZW5lSURzKCkpCiNwcmludChzdXBwb3J0ZWRHZW5vbWVzKCkpCnB3ZiA8LSBudWxscChnZW5lcywgIm1tMTAiLCJlbnNHZW5lIikKYGBgCgpDb25kdWN0IGdlbmUgc2V0IGVucmljaG1lbnQgYW5hbHlzaXM6CgpgYGB7ciByZXN1bHRzPSJoaWRlIn0KIz9nb3NlcQpnb3NlcV9yZXMgPC0gZ29zZXEocHdmLCAibW0xMCIsImVuc0dlbmUiLHRlc3QuY2F0cz0iR086QlAiKQpoZWFkKGdvc2VxX3JlcykKYGBgCgpgYGB7cn0KP2dvc2VxCgpgYGAKCgojIyBBbmFseXNpcyB3aXRoIGNsdXN0ZXJQcm9maWxlcgoKYGNsdXN0ZXJQcm9maWxlcmAgaXMgYW5vdGhlciBCaW9jb25kdWN0b3IgcGFja2FnZSBmb3Igb3Zlci1yZXByZXNlbnRhdGlvbiBhbmFseXNpcy4gSXQncyBtYWluIGFkdmFudGFnZSBpcyB0aGF0IGl0IHByb3ZpZGVzIHNvbWUgbmljZSB2aXN1YWxpc2F0aW9uIG1ldGhvZHMuCgpGaXJzdGx5LCB3ZSBjYW4gaWRlbnRpZnkgb3Zlci1yZXByZXNlbnRlZCBHTyB0ZXJtcyBhbmQgdmlzdWFsaXNlIHRoZXNlIGFzIGEgbmV0d29yay4KCmBgYHtyfQpsaWJyYXJ5KGNsdXN0ZXJQcm9maWxlcikKdW5pdmVyc2UgPC0gcmVzdWx0cy5hbm5vdGF0ZWQgJT4lIHB1bGwoRU5UUkVaSUQpCnNpZ0dlbmVzIDwtIHJlc3VsdHMuYW5ub3RhdGVkICU+JSAKICBmaWx0ZXIocGFkaiA8IDAuMDUsICFpcy5uYShFTlRSRVpJRCkpICU+JSBwdWxsKEVOVFJFWklEKQoKZW5yaWNoX2dvIDwtIGVucmljaEdPKAogIGdlbmU9IHNpZ0dlbmVzLAogIE9yZ0RiID0gb3JnLk1tLmVnLmRiLAogIGtleVR5cGUgPSAiRU5UUkVaSUQiLAogIG9udCA9ICJCUCIsCiAgdW5pdmVyc2UgPSB1bml2ZXJzZSwKICBxdmFsdWVDdXRvZmYgPSAwLjA1LAogIHJlYWRhYmxlPVRSVUUKKQoKYGBgCgpgYGB7cn0KZW5yaWNoX2dvX3RpZHkgPC0gZW5yaWNoX2dvICU+JSAKICBzbG90KCJyZXN1bHQiKSAlPiUgCiAgdGliYmxlOjphcy50aWJibGUoKSAKZW5yaWNoX2dvX3RpZHkKYGBgCgpBIGRvdCBwbG90IGNhbiBzaG93IHVzIHRoZSBtb3N0IGVucmljaGVkIHBhdGh3YXlzLCBhbmQgdGhlIHNpemUgb2YgZWFjaC4KCmBgYHtyfQpkb3RwbG90KGVucmljaF9nbykKYGBgCgpgYGB7cn0KZW1hcHBsb3QoZW5yaWNoX2dvKQpgYGAKCldlIGFsc28gcGVyZm9ybSBlbnJpY2htZW50IG9mIEtFR0cgcGF0aHdheXMgd2l0aCB0aGUgYGNsdXN0ZXJQcm9maWxlcmAgcGFja2FnZS4gV2UgY291bGQgZG8gdGhpcyB3aXRoIGBnb3NlcWAsIGJ1dCBhZ2FpbiBgY2x1c3RlclByb2ZpbGVyYCBoYXMgc29tZSBuaWNlIHZpc3VhbGlzYXRpb25zIGFuZCB1c2VzIG9ubGluZSByZXNvdXJjZXMgcmF0aGVyIHRoYW4gcmVseWluZyBvbiBCaW9jb25kdWN0b3IgYW5ub3RhdGlvbi4KClRvIHJ1biB0aGUgYW5hbHlzaXMgd2UgY2FuIHVzZSB0aGUgc2FtZSBsaXN0IG9mIGdlbmVzIGFzIGJlZm9yZSwgYW5kIG5lZWQgdG8ga25vdyB0aGUga2VnZyBjb2RlIGZvciBvdXIgZ2l2ZW4gb3JnYW5pc20uCgpgYGB7cn0Kc2VhcmNoX2tlZ2dfb3JnYW5pc20oJ21vdXNlJywgYnk9J2NvbW1vbl9uYW1lJykKa2VnX3JlcyA8LSBlbnJpY2hLRUdHKGdlbmU9c2lnR2VuZXMsIG9yZ2FuaXNtPSJtbXUiKQpgYGAKCmBgYHtyfQpoZWFkKGtlZ19yZXMsbj0xMCkKYGBgCgpJZiB3ZSB3YW50IHRvIHZpZXcgdGhlIG5ldHdvcmsgZm9yIGFueSBwYXRod2F5LCB3ZSBjYW4gdXNlIHRoZSBgYnJvd3NlS0VHR2AgZnVuY3Rpb24gd2hpY2ggd2lsbCB0YWtlIHVzIHRvIHRoZSByZWxldmFudCBwYWdlIG9uIHRoZSBLRUdHIHdlYnNpdGUuCgpXZSB3aWxsIGNob29zZSBhbiBleGFtcGxlIG9mIGEgc21hbGxlciBwYXRod2F5IHRvIG1ha2UgdGhpbmdzIGEgYml0IGNsZWFyZXIuCgpgYGB7cn0KYnJvd3NlS0VHRyhrZWdfcmVzLCAnbW11MDMzMjAnKQpgYGAKClRoZSBgcGF0aHZpZXdgIHBhY2thZ2Ugd2lsbCBwcm9kdWNlIGEgdmVyc2lvbiBvZiB0aGlzIHBsb3QgKGBwbmdgKSBpbiB5b3VyIHdvcmtpbmcgZGlyZWN0b3J5LgoKYGBge3J9CmxpYnJhcnkocGF0aHZpZXcpCmxvZ0ZDIDwtIHJlc3VsdHMuYW5ub3RhdGVkJGxvZzJGb2xkQ2hhbmdlCm5hbWVzKGxvZ0ZDKSA8LSByZXN1bHRzLmFubm90YXRlZCRFTlRSRVpJRAoKcGF0aHZpZXcoZ2VuZS5kYXRhID0gbG9nRkMsIAogICAgICAgICBwYXRod2F5LmlkID0gIm1tdTAzMzIwIiwgCiAgICAgICAgIHNwZWNpZXMgPSAibW11IiwgCiAgICAgICAgIGxpbWl0ID0gbGlzdChnZW5lPTUsIGNwZD0xKSkKYGBgCgoKIyMgQ3JlYXRpbmcgR2VuZSBsaXN0cyB0byB1c2Ugd2l0aCBhbiBvbmxpbmUgdG9vbAoKVGhlcmUgYXJlIGFsc28gbWFueSBvbmxpbmUgdG9vbHMgdGhhdCBvbmUgY291bGQgdXNlIHRvIHBlcmZvcm0gYSBnZW5lIHNldCBvciBvbnRvbG9neSBhbmFseXNpcy4gCgotIFtEQVZJRF0oaHR0cHM6Ly9kYXZpZC5uY2lmY3JmLmdvdi8pCi0gW0dlbmVUcmFpbF0oaHR0cHM6Ly9nZW5ldHJhaWwyLmJpb2luZi51bmktc2IuZGUvKQotIFtHT1JpbGxhXShodHRwOi8vY2JsLWdvcmlsbGEuY3MudGVjaG5pb24uYWMuaWwvKQoKVGhlIHRvb2xzIGdlbmVyYWxseSByZXF1aXJlIHlvdXIgaW5wdXQgZ2VuZXMgbGlzdHMgdG8gYmUgdXBsb2FkZWQgYXMgYSBzaW1wbGUgdGV4dCBmaWxlLiBJbiB0aGlzIGZpbmFsIGNoYWxsZW5nZSwgd2Ugd2lsbCBjcmVhdGUgc29tZSBmaWxlcyB0aGF0IHlvdSBtaWdodCB1c2UgaW4gb25lIG9mIHRoZXNlIHRvb2xzLgoKIyMjIEEgZmlsZSBjb250YWluaW5nIG5hbWVzIG9mIGJhY2tncm91bmQgZ2VuZXMKClRoaXMgZmlsZSBoYXMgb25lIGNvbHVtbiB3aGljaCBsaXN0cyAqKmFsbCB0aGUgZ2VuZSBuYW1lcyoqIHByZXNlbnQgaW4gdGhlIGFuYWx5c2lzLiBHZW5lIFN5bWJvbHMgYXJlIGNvbW1vbmx5IHVzZWQsIGFsdGhvdWdoIGEgdG9vbCBtYXkgYWNjZXB0IEVuc2VtYmwgb3IgUmVmc2VxIG5hbWVzCgojIyMgQSBmaWxlIGNvbnRhaW5pbmcgbmFtZXMgb2Ygc2lnbmlmaWNhbnQgZ2VuZXMKClRoaXMgZmlsZSBoYXMgb25lIGNvbHVtbiB3aGljaCBsaXN0cyB0aGUgZ2VuZXMgdGhhdCBwYXNzZWQgdGhlIHRocmVzaG9sZCBmb3Igc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIChlLmcuIHAtdmFsdWUgbGVzcyB0aGFuIDAuMDUpIGluIHlvdXIgYW5hbHlzaXMuIEdlbmUgU3ltYm9scyBhcmUgY29tbW9ubHkgdXNlZCwgYWx0aG91Z2ggYSB0b29sIG1heSBhY2NlcHQgRW5zZW1ibCBvciBSZWZzZXEgbmFtZXMKCgo+ICMjIENoYWxsZW5nZSB7LmNoYWxsZW5nZX0KPgo+IENyZWF0ZSB0d28gdGV4dCBmaWxlcyB0aGF0IGNhbiBiZSBpbXBvcnRlZCBpbnRvIG9ubGluZSB0b29scyBmb3IgZnVydGhlciBhbmFseXNpcwo+IDEuIEEgbGlzdCBvZiBiYWNrZ3JvdW5kIGdlbmVzCj4gMi4gQSBsaXN0IG9mIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcwo+IDMuIExvYWQgdGhlc2UgZmlsZXMgaW50byBHT3JpbGxhIGZvciBhbmFseXNpcwo+IEhJTlQ6IHRoZSBgd3JpdGUudGFibGVgIGZ1bmN0aW9uIGlzIGFibGUgdG8gd3JpdGUgYSBkYXRhIGZyYW1lIHRvIGEgdHh0IGZpbGUgaW4gUi4gWW91IHdpbGwgbmVlZCB0byBzZXQgdGhlIGFwcHJvcHJpYXRlIGFyZ3VtZW50cyB0byBtYWtlIHN1cmUgdGhhdCBhIHRleHQgZmlsZSB3aXRoIG9ubHkgb25lIGNvbHVtbiBpcyBjcmVhdGVkLgoKYGBge3J9CgpgYGA=