Original Authors: Belinda Phipson, Anna Trigos, Matt Ritchie, Maria Doyle, Harriet Dashnow, Charity Law, Stephane Ballereau, Oscar Rueda, Ashley Sawle Based on the course RNAseq analysis in R delivered on May 11/12th 2016 and modified by Cancer Research Uk Cambridge Centre for the Functional Genomics Autumn School 2017

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
    • ChIP-seq
    • RNA-seq

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:

    1. ranking all genes in the data set based on their correlation to the chosen phenotype,
    1. identifying the rank positions of all members of the gene set, and
    1. 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)
library(tidyverse)
results_annotated <- read_csv("tumour_vs_normal_DESeq_annotated.csv")

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.

gseaInput <-  results_annotated %>% 
  filter(!is.na(ENTREZID), !is.na(stat)) %>% 
  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.

download.file("http://bioinf.wehi.edu.au/software/MSigDB/human_H_v5p2.rdata", destfile = "Robjects/human_H_v5p2.rdata")
trying URL 'http://bioinf.wehi.edu.au/software/MSigDB/human_H_v5p2.rdata'
Content type 'text/plain; charset=UTF-8' length 22168 bytes (21 KB)
==================================================
downloaded 21 KB
load("Robjects/human_H_v5p2.rdata")
pathways <- Hs.H

Conduct analysis:

fgseaRes <- fgsea(pathways, ranks, minSize=15, maxSize = 500, nperm=1000)
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_MYC_TARGETS_V1"]],
               ranks)

names(pathways)
 [1] "HALLMARK_TNFA_SIGNALING_VIA_NFKB"          
 [2] "HALLMARK_HYPOXIA"                          
 [3] "HALLMARK_CHOLESTEROL_HOMEOSTASIS"          
 [4] "HALLMARK_MITOTIC_SPINDLE"                  
 [5] "HALLMARK_WNT_BETA_CATENIN_SIGNALING"       
 [6] "HALLMARK_TGF_BETA_SIGNALING"               
 [7] "HALLMARK_IL6_JAK_STAT3_SIGNALING"          
 [8] "HALLMARK_DNA_REPAIR"                       
 [9] "HALLMARK_G2M_CHECKPOINT"                   
[10] "HALLMARK_APOPTOSIS"                        
[11] "HALLMARK_NOTCH_SIGNALING"                  
[12] "HALLMARK_ADIPOGENESIS"                     
[13] "HALLMARK_ESTROGEN_RESPONSE_EARLY"          
[14] "HALLMARK_ESTROGEN_RESPONSE_LATE"           
[15] "HALLMARK_ANDROGEN_RESPONSE"                
[16] "HALLMARK_MYOGENESIS"                       
[17] "HALLMARK_PROTEIN_SECRETION"                
[18] "HALLMARK_INTERFERON_ALPHA_RESPONSE"        
[19] "HALLMARK_INTERFERON_GAMMA_RESPONSE"        
[20] "HALLMARK_APICAL_JUNCTION"                  
[21] "HALLMARK_APICAL_SURFACE"                   
[22] "HALLMARK_HEDGEHOG_SIGNALING"               
[23] "HALLMARK_COMPLEMENT"                       
[24] "HALLMARK_UNFOLDED_PROTEIN_RESPONSE"        
[25] "HALLMARK_PI3K_AKT_MTOR_SIGNALING"          
[26] "HALLMARK_MTORC1_SIGNALING"                 
[27] "HALLMARK_E2F_TARGETS"                      
[28] "HALLMARK_MYC_TARGETS_V1"                   
[29] "HALLMARK_MYC_TARGETS_V2"                   
[30] "HALLMARK_EPITHELIAL_MESENCHYMAL_TRANSITION"
[31] "HALLMARK_INFLAMMATORY_RESPONSE"            
[32] "HALLMARK_XENOBIOTIC_METABOLISM"            
[33] "HALLMARK_FATTY_ACID_METABOLISM"            
[34] "HALLMARK_OXIDATIVE_PHOSPHORYLATION"        
[35] "HALLMARK_GLYCOLYSIS"                       
[36] "HALLMARK_REACTIVE_OXIGEN_SPECIES_PATHWAY"  
[37] "HALLMARK_P53_PATHWAY"                      
[38] "HALLMARK_UV_RESPONSE_UP"                   
[39] "HALLMARK_UV_RESPONSE_DN"                   
[40] "HALLMARK_ANGIOGENESIS"                     
[41] "HALLMARK_HEME_METABOLISM"                  
[42] "HALLMARK_COAGULATION"                      
[43] "HALLMARK_IL2_STAT5_SIGNALING"              
[44] "HALLMARK_BILE_ACID_METABOLISM"             
[45] "HALLMARK_PEROXISOME"                       
[46] "HALLMARK_ALLOGRAFT_REJECTION"              
[47] "HALLMARK_SPERMATOGENESIS"                  
[48] "HALLMARK_KRAS_SIGNALING_UP"                
[49] "HALLMARK_KRAS_SIGNALING_DN"                
[50] "HALLMARK_PANCREAS_BETA_CELLS"              

Challenge 1

  1. What pathway has the most extreme negative enrichment score? How can you identify this pathway from the results table?
  2. Make the enrichment plot for this pathway

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.

dds <- readRDS("Robjects/dds.rds")
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, colMeans, colnames, colSums, dirname, do.call,
    duplicated, eval, evalq, Filter, Find, get, grep,
    grepl, intersect, is.unsorted, lapply, lengths, Map,
    mapply, match, mget, order, paste, pmax, pmax.int,
    pmin, pmin.int, Position, rank, rbind, Reduce,
    rowMeans, rownames, rowSums, 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:tidyr’:

    expand

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

The following object is masked from ‘package:purrr’:

    reduce

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 object is masked from ‘package:purrr’:

    simplify

The following objects are masked from ‘package:base’:

    aperm, apply
library(DESeq2)
library(pheatmap)
my_genes <- filter(results_annotated, ENTREZID %in% pathways[["HALLMARK_MYC_TARGETS_V1"]]) %>% 
  pull(GeneID)
vsd <- vst(dds)
mat <- assay(vsd)[my_genes,]
mat <- mat - rowMeans(mat)
dim(mat)
[1] 197  27
sampleinfo <- data.frame(colData(dds))[,c("Batch","Status","gleason_score")]
colnames(mat) <- rownames(sampleinfo) <- dds$ID
pheatmap(mat,
         annotation_col = sampleinfo)

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.

Mapping between gene identifiers and pathways is done via pre-built Bioconductor packages. In our dataset, each gene that we have tested has a valid gene symbol. Therefore we will tell goseq to use the gene symbol (the GeneID) column in the annotated data frame to map to pathways and calculate gene lengths.

genes <- results_annotated$padj < 0.05 & !is.na(results_annotated$padj)
names(genes) <- results_annotated$GeneID

The nullp will fit a model to account for gene length biases in our data. In order to use the human genome version hg38 we need the TxDb.Hsapiens.UCSC.hg38.knownGene package to be installed.

BiocManager::install("TxDb.Hsapiens.UCSC.hg38.knownGene")
library(goseq)
pwf <- nullp(genes, "hg38","geneSymbol")

Then we conduct gene set enrichment analysis with the goseq function.

goseq_res <- goseq(pwf, "hg38","geneSymbol",test.cats="GO:BP")
head(goseq_res)

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(ENSEMBL)
sigGenes <- results_annotated %>% 
  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
)
enrich_go_tidy <- enrich_go %>% 
  slot("result") %>% 
  tibble::as.tibble() 
enrich_go_tidy

A dot plot can show us the most enriched pathways, and the size of each.

dotplot(enrich_go)

emapplot(enrich_go)

Creating Gene lists to use with an online tool

There are also many online tools that one could use to perform a gene set or ontology analysis.

The tools generally require your input genes lists to be uploaded as a simple text file. In this final challenge, we will create some files that you might use in one of these tools.

A file containing names of background genes

This file has one column which lists all the gene names present in the analysis. Gene Symbols are commonly used, although a tool may accept Ensembl or Refseq names

A file containing names of significant genes

This file has one column which lists the genes that passed the threshold for statistical significance (e.g. p-value less than 0.05) in your analysis. Gene Symbols are commonly used, although a tool may accept Ensembl or Refseq names

Challenge

Create two text files that can be imported into online tools for further analysis 1. A list of background genes 2. A list of differentially expressed genes 3. Load these files into GOrilla for analysis HINT: the write.table function is able to write a data frame to a txt file in R. You will need to set the appropriate arguments to make sure that a text file with only one column is created.

LS0tCnRpdGxlOiAiUk5BLXNlcSBhbmFseXNpcyBpbiBSIgphdXRob3I6ICJTdGVwaGFuZSBCYWxsZXJlYXUsIE1hcmsgRHVubmluZywgT3NjYXIgUnVlZGEsIEFzaGxleSBTYXdsZSIKZGF0ZTogRmVicnVhcnkgMjAyMApvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKbWludXRlczogMzAwCmxheW91dDogcGFnZQpzdWJ0aXRsZTogR2VuZSBTZXQgVGVzdGluZyBmb3IgUk5BLXNlcQoKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlPUZBTFNFLHdhcm5pbmcgPSBGQUxTRSkKYGBgCgoqKk9yaWdpbmFsIEF1dGhvcnM6IEJlbGluZGEgUGhpcHNvbiwgQW5uYSBUcmlnb3MsIE1hdHQgUml0Y2hpZSwgTWFyaWEgRG95bGUsIEhhcnJpZXQgRGFzaG5vdywgQ2hhcml0eSBMYXcqKiwgKipTdGVwaGFuZSBCYWxsZXJlYXUsIE9zY2FyIFJ1ZWRhLCBBc2hsZXkgU2F3bGUqKgpCYXNlZCBvbiB0aGUgY291cnNlIFtSTkFzZXEgYW5hbHlzaXMgaW4gUl0oaHR0cDovL2NvbWJpbmUtYXVzdHJhbGlhLmdpdGh1Yi5pby8yMDE2LTA1LTExLVJOQXNlcS8pIGRlbGl2ZXJlZCBvbiBNYXkgMTEvMTJ0aCAyMDE2IGFuZCBtb2RpZmllZCBieSBDYW5jZXIgUmVzZWFyY2ggVWsgQ2FtYnJpZGdlIENlbnRyZSBmb3IgdGhlIFtGdW5jdGlvbmFsIEdlbm9taWNzIEF1dHVtbiBTY2hvb2wgMjAxN10oaHR0cHM6Ly9iaW9pbmZvcm1hdGljcy1jb3JlLXNoYXJlZC10cmFpbmluZy5naXRodWIuaW8vY3J1ay1hdXR1bW4tc2Nob29sLTIwMTcvKQoKIyMgUmVzb3VyY2VzIGFuZCBkYXRhIGZpbGVzCgpUaGlzIG1hdGVyaWFsIGhhcyBiZWVuIGNyZWF0ZWQgdXNpbmcgdGhlIGZvbGxvd2luZyByZXNvdXJjZXM6ICAKCgotIGh0dHA6Ly9tb25hc2hiaW9pbmZvcm1hdGljc3BsYXRmb3JtLmdpdGh1Yi5pby9STkFzZXEtREUtYW5hbHlzaXMtd2l0aC1SLzk5LVJOQXNlcV9ERV9hbmFseXNpc193aXRoX1IuaHRtbCAgCi0gaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvZGV2ZWwvYmlvYy92aWduZXR0ZXMvREVTZXEyL2luc3QvZG9jL0RFU2VxMi5odG1sCi0gaHR0cHM6Ly9iaW9jb25kdWN0b3IuZ2l0aHViLmlvL0Jpb2NXb3Jrc2hvcHMvcm5hLXNlcS1kYXRhLWFuYWx5c2lzLXdpdGgtZGVzZXEyLmh0bWwKCgpUaGlzIHNlY3Rpb24gYWxzbyB1c2VzIGNvZGUgZnJvbSBTdGVwaGVuIFR1cm5lcidzIGd1aWRlIHRvIGZnc2VhIGh0dHBzOi8vc3RlcGhlbnR1cm5lci5naXRodWIuaW8vZGVzZXEtdG8tZmdzZWEvCgojIEdlbmUgU2V0IFRlc3RpbmcKCkluIHRoZSBlYXJseSBkYXlzIG9mIG1pY3JvYXJyYXkgYW5hbHlzaXMsIHBlb3BsZSB3ZXJlIGhhcHB5IGlmIHRoZXkgZ290IGEgaGFuZGZ1bCBvZiBkaWZmZXJlbnRpYWxseS1leHByZXNzZWQgZ2VuZXMgdGhhdCB0aGV5IGNvdWxkIHZhbGlkYXRlIG9yIGZvbGxvdy11cC4gSG93ZXZlciwgd2l0aCBsYXRlciB0ZWNobm9sb2dpZXMgKGFuZCBkZXBlbmRpbmcgb24gdGhlIGV4cGVyaW1lbnRhbCBzZXR1cCkgd2UgbWlnaHQgaGF2ZSB0aG91c2FuZHMgb2Ygc3RhdGlzdGljYWxseS1zaWduaWZpY2FudCByZXN1bHRzLCB3aGljaCBuby1vbmUgaGFzIHRoZSB0aW1lIHRvIGZvbGxvdy11cC4gQWxzbywgd2UgbWlnaHQgYmUgaW50ZXJlc3RlZCBpbiBwYXRod2F5cyAvIG1lY2hhbmlzbXMgdGhhdCBhcmUgYWx0ZXJlZCBhbmQgbm90IGp1c3QgaW5kaXZpZHVhbCBnZW5lcy4KCkluIHRoaXMgc2VjdGlvbiB3ZSBtb3ZlIHRvd2FyZHMgZGlzY292ZXJpbmcgaWYgb3VyIHJlc3VsdHMgYXJlICoqKmJpb2xvZ2ljYWxseSBzaWduaWZpY2FudCoqKi4gQXJlIHRoZSBnZW5lcyB0aGF0IHdlIGhhdmUgcGlja2VkIHN0YXRpc3RpY2FsIGZsdWtlcywgb3IgYXJlIHRoZXJlIHNvbWUgY29tbW9uYWxpdGllcy4gCgpUaGVyZSBhcmUgdHdvIGRpZmZlcmVudCBhcHByb2FjaGVzIG9uZSBtaWdodCB1c2UsIGFuZCB3ZSB3aWxsIGNvdmVyIHRoZSB0aGVvcnkgYmVoaW5kIGJvdGgKCi0gVGhlcmUgaXMgYWxzbyBhIGJ1bmNoIG9mIHdlYnNpdGVzIGZvciBkb2luZyB0aGUgdGVzdHMKICAgICsgd2Ugd2lsbCBzaG93IGhvdyB0aGV5IGFyZSBkb25lIGluIEJpb2NvbmR1Y3RvciBzbyB0aGUgdGhlb3J5IGlzIGNsZWFyCi0gV2Ugd2lsbCBhc3N1bWUgd2UgaGF2ZSBkb25lIGEgZGlmZmVyZW50aWFsLWV4cHJlc3Npb24gYW5hbHlzaXMsIGJ1dCB0aGUgc2FtZSB0ZWNobmlxdWVzIGNhbiBiZSB1c2VkIGZvciBvdGhlciBzaXR1YXRpb25zIHdoZW4gd2UgaGF2ZSBhIGdlbmUgbGlzdAogICAgKyBDaElQLXNlcQogICAgKyBSTkEtc2VxCiAgICAKCiMjIyBmZ3NlYSBhbmFseXNpcwoKVGhlIGZnc2VhIHBhY2thZ2UgaXMgYSBmcmVlIGltcGxlbWVudGF0aW9uIG9mIHRoZSBCcm9hZCdzIEdTRUEgc29mdHdhcmUgYW5kIGlzIGRlc2NyaWJlZCBpbiBtb3JlIGRldGFpbCBpbiB0aGUgcGFja2FnZSBbdmlnbmV0dGVdKGh0dHA6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvdmlnbmV0dGVzL2Znc2VhL2luc3QvZG9jL2Znc2VhLXR1dG9yaWFsLmh0bWwpICJmYXN0IHByZXJhbmtlZCBnZW5lIHNldCBlbnJpY2htZW50IGFuYWx5c2lzIChHU0VBKSI6CgpUaGUgR1NFQSBhbmFseXNpcyBpcyBwZXJmb3JtZWQgYnk6CgotIChpKSByYW5raW5nIGFsbCBnZW5lcyBpbiB0aGUgZGF0YSBzZXQgYmFzZWQgb24gdGhlaXIgY29ycmVsYXRpb24gdG8gdGhlIGNob3NlbiBwaGVub3R5cGUsCi0gKGlpKSBpZGVudGlmeWluZyB0aGUgcmFuayBwb3NpdGlvbnMgb2YgYWxsIG1lbWJlcnMgb2YgdGhlIGdlbmUgc2V0LCBhbmQgCi0gKGlpaSkgY2FsY3VsYXRpbmcgYW4gZW5yaWNobWVudCBzY29yZSAoRVMpIHRoYXQgcmVwcmVzZW50cyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBvYnNlcnZlZCByYW5raW5ncyBhbmQgdGhhdCB3aGljaCB3b3VsZCBiZSBleHBlY3RlZCBhc3N1bWluZyBhIHJhbmRvbSByYW5rIGRpc3RyaWJ1dGlvbi4KCj4gIkFmdGVyIGVzdGFibGlzaGluZyB0aGUgRVMgZm9yIGVhY2ggZ2VuZSBzZXQgYWNyb3NzIHRoZSBwaGVub3R5cGUsIEdTRUEgcmVpdGVyYXRpdmVseSByYW5kb21pemVzIHRoZSBzYW1wbGUgbGFiZWxzIGFuZCByZXRlc3RzIGZvciBlbnJpY2htZW50IGFjcm9zcyB0aGUgcmFuZG9tIGNsYXNzZXMuIEJ5IHBlcmZvcm1pbmcgcmVwZWF0ZWQgY2xhc3MgbGFiZWwgcmFuZG9taXphdGlvbnMsIHRoZSBFUyBmb3IgZWFjaCBnZW5lIHNldCBhY3Jvc3MgdGhlIHRydWUgY2xhc3NlcyBjYW4gYmUgY29tcGFyZWQgdG8gdGhlIEVTIGRpc3RyaWJ1dGlvbiBmcm9tIHRoZSByYW5kb20gY2xhc3Nlcy4gVGhvc2UgZ2VuZSBzZXRzIHRoYXQgc2lnbmlmaWNhbnRseSBvdXRwZXJmb3JtIGl0ZXJhdGl2ZSByYW5kb20gY2xhc3MgcGVybXV0YXRpb25zIGFyZSBjb25zaWRlcmVkIHNpZ25pZmljYW50LiIgCgpUaGUgYXJ0aWNsZSBkZXNjcmliaW5nIHRoZSBvcmlnaW5hbCBzb2Z0d2FyZSBpcyBhdmFpbGFibGUgW2hlcmVdKGh0dHA6Ly93d3cucG5hcy5vcmcvY29udGVudC8xMDIvNDMvMTU1NDUubG9uZykgYW5kIHRoZXJlIGlzIGFsc28gYSBbY29tbWVudGFyeSBvbiBHU0VBXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUMxMjY2MTMxLykuIAoKSW4gYWRkaXRpb24gdG8gdGhlIEdTRUEgc29mdHdhcmUgdGhlIEJyb2FkIGFsc28gcHJvdmlkZSBhIG51bWJlciBvZiB2ZXJ5IHdlbGwgY3VyYXRlZCBnZW5lIHNldHMgZm9yIHRlc3RpbmcgYWdhaW5zdCB5b3VyIGRhdGEgLSB0aGUgTW9sZWN1bGFyIFNpZ25hdHVyZXMgRGF0YWJhc2UgKE1TaWdEQikuIFVuZm9ydHVuYXRlbHksIHRoZXNlIGFyZSBjb2xsZWN0aW9ucyBvZiBodW1hbiBnZW5lcy4gSG93ZXZlciwgdGhlc2UgbGlzdHMgaGF2ZSBiZWVuIHRyYW5zbGF0ZWQgdG8gbW91c2UgZXF1aXZhbGVudHMgYnkgdGhlIFdhbHRlcitFbGl6YSBIYWxsIEluc3RpdHV0ZXMgQmlvaW5mb3JtYXRpY3Mgc2VydmljZSBhbmQgbWFkZSBhdmFpYWxibGUgZm9yIGRvd25sb2FkLiBUaGVzZSBnZW5lIHNldHMgdXNlICpFbnRyZXogSUQqIGFzIHRoZWlyIGlkZW50aWZpZXIuCgpgYGB7cn0KbGlicmFyeShmZ3NlYSkKYGBgCgoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpyZXN1bHRzX2Fubm90YXRlZCA8LSByZWFkX2NzdigidHVtb3VyX3ZzX25vcm1hbF9ERVNlcV9hbm5vdGF0ZWQuY3N2IikKYGBgCgpBbiBhcHBlYWxpbmcgZmVhdHVyZSBvZiB0aGUgbWV0aG9kIGlzIHRoYXQgaXQgZG9lcyBub3QgcmVxdWlyZSB1cyB0byBpbXBvc2UgYXJiaXRyYXJ5IGN1dC1vZmZzIG9uIHRoZSBkYXRhc2V0IHRvIGRlY2lkZSB3aGF0IGlzIGRpZmZlcmVudGlhbGx5LWV4cHJlc3NlZCBvciBub3QuIFRoZSBzdGVwcyBpbiBwcm9kdWNpbmcgdGhlIGlucHV0IHJlcXVpcmVkIGZvciBHU0VBIGFyZSBpKSByZXRyaWV2aW5nIHRoZSByYW5rZWQgc3RhdGlzdGljcyBpaSkgbmFtaW5nIGVhY2ggb25lIGFjY29yZGluZyB0byBFbnRyZXogSUQuCgpgYGB7cn0KCmdzZWFJbnB1dCA8LSAgcmVzdWx0c19hbm5vdGF0ZWQgJT4lIAogIGZpbHRlcighaXMubmEoRU5UUkVaSUQpLCAhaXMubmEoc3RhdCkpICU+JSAKICBhcnJhbmdlKHN0YXQpCnJhbmtzIDwtIHB1bGwoZ3NlYUlucHV0LHN0YXQpCm5hbWVzKHJhbmtzKSA8LSBnc2VhSW5wdXQkRU5UUkVaSUQKYGBgCgoKVGhlIFdhbHRlcitFbGl6YSBIYWxsIEluc3RpdHV0ZXMgQmlvaW5mb3JtYXRpY3Mgc2VydmljZSBoYXZlIG1hZGUgbW91c2UgdmVyc2lvbnMgb2YgdGhlIE1TaWdEQiBkYXRhc2V0cyBhdmFpbGFibGUgZm9yIGRvd25sb2FkLiBUaGlzIHNob3VsZCBhbHJlYWR5IGJlIGF2YWlsYWJsZSBpbiB0aGUgYFJvYmplY3RzYCBmb2xkZXIuCgpgYGB7cn0KZG93bmxvYWQuZmlsZSgiaHR0cDovL2Jpb2luZi53ZWhpLmVkdS5hdS9zb2Z0d2FyZS9NU2lnREIvaHVtYW5fSF92NXAyLnJkYXRhIiwgZGVzdGZpbGUgPSAiUm9iamVjdHMvaHVtYW5fSF92NXAyLnJkYXRhIikKbG9hZCgiUm9iamVjdHMvaHVtYW5fSF92NXAyLnJkYXRhIikKcGF0aHdheXMgPC0gSHMuSApgYGAKCkNvbmR1Y3QgYW5hbHlzaXM6CgpgYGB7cn0KZmdzZWFSZXMgPC0gZmdzZWEocGF0aHdheXMsIHJhbmtzLCBtaW5TaXplPTE1LCBtYXhTaXplID0gNTAwLCBucGVybT0xMDAwKQpkaW0oZmdzZWFSZXMpCiNoZWFkKGZnc2VhUmVzKQpgYGAKClRoZSByZXN1bHRzIHRhYmxlIGdpdmVzIHRoZSBuYW1lcyBvZiBlYWNoIHBhdGh3YXkgdGhhdCB3YXMgdGVzdGVkIGFuZCB0aGUgc3RhdHMgZnJvbSBkb2luZyB0aGUgdGVzdC4gV2UgY2FuIG1ha2UgdGhpcyBpbnRvIGEgInRpZHkiIG9iamVjdCB3aXRoIHRoZSBmb2xsb3dpbmcgY29kZS4KCmBgYHtyfQpmZ3NlYVJlc1RpZHkgPC0gZmdzZWFSZXMgJT4lCiAgYXNfdGliYmxlKCkgJT4lCiAgYXJyYW5nZShkZXNjKE5FUykpCgojIFNob3cgaW4gYSBuaWNlIHRhYmxlOgpmZ3NlYVJlc1RpZHkgCmBgYAoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKZ2dwbG90KGZnc2VhUmVzVGlkeSwgYWVzKHJlb3JkZXIocGF0aHdheSwgTkVTKSwgTkVTKSkgKwogIGdlb21fY29sKGFlcyhmaWxsPXBhZGo8MC4wNSkpICsKICBjb29yZF9mbGlwKCkgKwogIGxhYnMoeD0iUGF0aHdheSIsIHk9Ik5vcm1hbGl6ZWQgRW5yaWNobWVudCBTY29yZSIsCiAgICAgICB0aXRsZT0iSGFsbG1hcmsgcGF0aHdheXMgTkVTIGZyb20gR1NFQSIpCmBgYAoKCgpUaGUgZW5yaWNobWVudCBwbG90IHdpbGwgc2hvdyB3aGVyZSB0aGUgZ2VuZXMgYmVsb25naW5nIHRvIGEgcGFydGljdWxhciBnZW5lIHNldCBhcmUgdG93YXJkcyB0aGUgdG9wIG9yIHRoZSBib3R0b20gb2YgdGhlIGdlbmVsaXN0LCBhbmQgaG93IHRoZSAqZW5yaWNobWVudCBzY29yZSogaXMgY2FsY3VsYXRlZCBhY3Jvc3MgdGhlIGRhdGFzZXQuCgpIZXJlIHdlIHNob3cgdGhlIGVucmljaG1lbnQgcGxvdCBmb3IgdGhlIHBhdGh3YXkgd2l0aCB0aGUgbW9zdCBwb3NpdGl2ZSBlbnJpY2htZW50IHNjb3JlLgoKYGBge3J9CnBsb3RFbnJpY2htZW50KHBhdGh3YXlzW1siSEFMTE1BUktfTVlDX1RBUkdFVFNfVjEiXV0sCiAgICAgICAgICAgICAgIHJhbmtzKQpuYW1lcyhwYXRod2F5cykKYGBgCgo+ICMjIENoYWxsZW5nZSAxey5jaGFsbGVuZ2V9Cj4gMS4gV2hhdCBwYXRod2F5IGhhcyB0aGUgbW9zdCBleHRyZW1lIG5lZ2F0aXZlIGVucmljaG1lbnQgc2NvcmU/IEhvdyBjYW4geW91IGlkZW50aWZ5IHRoaXMgcGF0aHdheSBmcm9tIHRoZSByZXN1bHRzIHRhYmxlPwo+IDIuIE1ha2UgdGhlIGVucmljaG1lbnQgcGxvdCBmb3IgdGhpcyBwYXRod2F5CgoKVGhlIG5hbWVzIG9mIGdlbmVzIGludm9sdmVkIGluIHBhcnRpY3VsYXIgcGF0aHdheXMgY2FuIGJlIGV4dHJhY3RlZCBmcm9tIHRoZSBgcGF0aHdheXNgIG9iamVjdC4gV2UgY2FuIHRoZW4gdXNlIHRoZXNlIGdlbmVzIHRvIG1ha2UgYSBoZWF0bWFwLCBvciBvdGhlciB2aXN1YWxpc2F0aW9uLCB0byBzaG93IGhvdyB0aGVzZSBnZW5lcyBwYXJ0aXRpb24gdGhlIGRhdGFzZXQuCgpgYGB7cn0KZGRzIDwtIHJlYWRSRFMoIlJvYmplY3RzL2Rkcy5yZHMiKQpsaWJyYXJ5KERFU2VxMikKbGlicmFyeShwaGVhdG1hcCkKbXlfZ2VuZXMgPC0gZmlsdGVyKHJlc3VsdHNfYW5ub3RhdGVkLCBFTlRSRVpJRCAlaW4lIHBhdGh3YXlzW1siSEFMTE1BUktfTVlDX1RBUkdFVFNfVjEiXV0pICU+JSAKICBwdWxsKEdlbmVJRCkKCnZzZCA8LSB2c3QoZGRzKQptYXQgPC0gYXNzYXkodnNkKVtteV9nZW5lcyxdCm1hdCA8LSBtYXQgLSByb3dNZWFucyhtYXQpCmRpbShtYXQpCnNhbXBsZWluZm8gPC0gZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpWyxjKCJCYXRjaCIsIlN0YXR1cyIsImdsZWFzb25fc2NvcmUiKV0KY29sbmFtZXMobWF0KSA8LSByb3duYW1lcyhzYW1wbGVpbmZvKSA8LSBkZHMkSUQKCnBoZWF0bWFwKG1hdCwKICAgICAgICAgYW5ub3RhdGlvbl9jb2wgPSBzYW1wbGVpbmZvKQpgYGAKCiMjIEdlbmUgU2V0IFRlc3RpbmcgLSBjb21wZXRpdGl2ZSBnZW5lIHNldCB0ZXN0cwoKIyMjIEdPc2VxIGFuYWx5c2lzCgpHT3NlcSBpcyBhIG1ldGhvZCB0byBjb25kdWN0IEdlbmUgT250b2xvZ3kgKEdPKSBhbmFseXNpcyBzdWl0YWJsZSBmb3IgUk5BLXNlcSBkYXRhIGFzIGl0IGFjY291bnRzIGZvciB0aGUgZ2VuZSBsZW5ndGggYmlhcyBpbiBkZXRlY3Rpb24gb2Ygb3Zlci1yZXByZXNlbnRhdGlvbiAoW0dPc2VxIGFydGljbGVdKGh0dHBzOi8vZ2Vub21lYmlvbG9neS5iaW9tZWRjZW50cmFsLmNvbS9hcnRpY2xlcy8xMC4xMTg2L2diLTIwMTAtMTEtMi1yMTQpKQoKRnJvbSB0aGUgW0dPc2VxIHZpZ25ldHRlXShodHRwczovL3d3dy5iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy92aWduZXR0ZXMvZ29zZXEvaW5zdC9kb2MvZ29zZXEucGRmKToKCi0gR09zZXEgZmlyc3QgbmVlZHMgdG8gcXVhbnRpZnkgdGhlIGxlbmd0aCBiaWFzIHByZXNlbnQgaW4gdGhlIGRhdGFzZXQgdW5kZXIgY29uc2lkZXJhdGlvbi4KLSBUaGlzIGlzIGRvbmUgYnkgY2FsY3VsYXRpbmcgYSBQcm9iYWJpbGl0eSBXZWlnaHRpbmcgRnVuY3Rpb24gb3IgUFdGIHdoaWNoIGNhbiBiZSB0aG91Z2h0IG9mIGFzIGEgZnVuY3Rpb24gd2hpY2ggZ2l2ZXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgYSBnZW5lIHdpbGwgYmUgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIChERSksIGJhc2VkIG9uIGl0cyBsZW5ndGggYWxvbmUuCi0gVGhlIFBXRiBpcyBjYWxjdWxhdGVkIGJ5IGZpdHRpbmcgYSBtb25vdG9uaWMgc3BsaW5lIHRvIHRoZSBiaW5hcnkgZGF0YSBzZXJpZXMgb2YgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gKDE9REUsIDA9Tm90IERFKSBhcyBhIGZ1bmN0aW9uIG9mIGdlbmUgbGVuZ3RoLgotIFRoZSBQV0YgaXMgdXNlZCB0byB3ZWlnaHQgdGhlIGNoYW5jZSBvZiBzZWxlY3RpbmcgZWFjaCBnZW5lIHdoZW4gZm9ybWluZyBhIG51bGwgZGlzdHJpYnV0aW9uIGZvciBHTyBjYXRlZ29yeSBtZW1iZXJzaGlwLgotIFRoZSBmYWN0IHRoYXQgdGhlIFBXRiBpcyBjYWxjdWxhdGVkIGRpcmVjdGx5IGZyb20gdGhlIGRhdGFzZXQgdW5kZXIgY29uc2lkZXJhdGlvbiBtYWtlcyB0aGlzIGFwcHJvYWNoIHJvYnVzdCwgb25seSBjb3JyZWN0aW5nIGZvciB0aGUgbGVuZ3RoIGJpYXMgcHJlc2VudCBpbiB0aGUgZGF0YS4KCiJHTyBhbmFseXNpcyBvZiBSTkEtc2VxIGRhdGEgcmVxdWlyZXMgdGhlIHVzZSBvZiByYW5kb20gc2FtcGxpbmcgaW4gb3JkZXIgdG8gZ2VuZXJhdGUgYSBzdWl0YWJsZSBudWxsIGRpc3RyaWJ1dGlvbiBmb3IgR08gY2F0ZWdvcnkgbWVtYmVyc2hpcCBhbmQgY2FsY3VsYXRlIGVhY2ggY2F0ZWdvcnkncyBzaWduaWZpY2FuY2UgZm9yIG92ZXIgcmVwcmVzZW50YXRpb24gYW1vbmdzdCBERSBnZW5lcy4gLi4uIEluICBtb3N0ICBjYXNlcywgIHRoZSAgV2FsbGVuaXVzIGRpc3RyaWJ1dGlvbiBjYW4gYmUgdXNlZCB0byBhcHByb3hpbWF0ZSB0aGUgdHJ1ZSBudWxsIGRpc3RyaWJ1dGlvbiwgd2l0aG91dCBhbnkgc2lnbmlmaWNhbnQgbG9zcyBpbiBhY2N1cmFjeS4gVGhlIGdvc2VxIHBhY2thZ2UgaW1wbGVtZW50cyB0aGlzIGFwcHJveGltYXRpb24gYXMgaXRzIGRlZmF1bHQgb3B0aW9uLiIKCkZpcnN0LCBjcmVhdGUgbGlzdCBvZiBERUdzLiBXZSBkb24ndCBoYXZlIHRvIGJlIHRvbyBzdHJpY3QgYWJvdXQgb3VyIGNyaXRlcmlhIGZvciBhIGdlbmUgdG8gYmUgZGlmZmVyZW50aWFsbHktZXhwcmVzc2VkLCBhcyB0b28gZmV3IGdlbmVzIHdpbGwgbm90IGdpdmUgdXMgbWFueSBlbnJpY2hlZCBwYXRod2F5cy4gCgpNYXBwaW5nIGJldHdlZW4gZ2VuZSBpZGVudGlmaWVycyBhbmQgcGF0aHdheXMgaXMgZG9uZSB2aWEgcHJlLWJ1aWx0IEJpb2NvbmR1Y3RvciBwYWNrYWdlcy4gSW4gb3VyIGRhdGFzZXQsIGVhY2ggZ2VuZSB0aGF0IHdlIGhhdmUgdGVzdGVkIGhhcyBhIHZhbGlkIGdlbmUgc3ltYm9sLiBUaGVyZWZvcmUgd2Ugd2lsbCB0ZWxsIGBnb3NlcWAgdG8gdXNlIHRoZSBnZW5lIHN5bWJvbCAodGhlIGBHZW5lSURgKSBjb2x1bW4gaW4gdGhlIGFubm90YXRlZCBkYXRhIGZyYW1lIHRvIG1hcCB0byBwYXRod2F5cyBhbmQgY2FsY3VsYXRlIGdlbmUgbGVuZ3Rocy4KCmBgYHtyfQpnZW5lcyA8LSByZXN1bHRzX2Fubm90YXRlZCRwYWRqIDwgMC4wNSAmICFpcy5uYShyZXN1bHRzX2Fubm90YXRlZCRwYWRqKQpuYW1lcyhnZW5lcykgPC0gcmVzdWx0c19hbm5vdGF0ZWQkR2VuZUlECmBgYAoKClRoZSBgbnVsbHBgIHdpbGwgZml0IGEgbW9kZWwgdG8gYWNjb3VudCBmb3IgZ2VuZSBsZW5ndGggYmlhc2VzIGluIG91ciBkYXRhLiBJbiBvcmRlciB0byB1c2UgdGhlIGh1bWFuIGdlbm9tZSB2ZXJzaW9uIGhnMzggd2UgbmVlZCB0aGUgYFR4RGIuSHNhcGllbnMuVUNTQy5oZzM4Lmtub3duR2VuZWAgcGFja2FnZSB0byBiZSBpbnN0YWxsZWQuCgpgYGB7ciBldmFsPUZBTFNFfQpCaW9jTWFuYWdlcjo6aW5zdGFsbCgiVHhEYi5Ic2FwaWVucy5VQ1NDLmhnMzgua25vd25HZW5lIikKYGBgCgoKYGBge3J9CmxpYnJhcnkoZ29zZXEpCnB3ZiA8LSBudWxscChnZW5lcywgImhnMzgiLCJnZW5lU3ltYm9sIikKYGBgCgpUaGVuIHdlIGNvbmR1Y3QgZ2VuZSBzZXQgZW5yaWNobWVudCBhbmFseXNpcyB3aXRoIHRoZSBgZ29zZXFgIGZ1bmN0aW9uLgoKYGBge3IgcmVzdWx0cz0iaGlkZSJ9Cgpnb3NlcV9yZXMgPC0gZ29zZXEocHdmLCAiaGczOCIsImdlbmVTeW1ib2wiLHRlc3QuY2F0cz0iR086QlAiKQpoZWFkKGdvc2VxX3JlcykKYGBgCgojIyBBbmFseXNpcyB3aXRoIGNsdXN0ZXJQcm9maWxlcgoKYGNsdXN0ZXJQcm9maWxlcmAgaXMgYW5vdGhlciBCaW9jb25kdWN0b3IgcGFja2FnZSBmb3Igb3Zlci1yZXByZXNlbnRhdGlvbiBhbmFseXNpcy4gSXQncyBtYWluIGFkdmFudGFnZSBpcyB0aGF0IGl0IHByb3ZpZGVzIHNvbWUgbmljZSB2aXN1YWxpc2F0aW9uIG1ldGhvZHMuCgpGaXJzdGx5LCB3ZSBjYW4gaWRlbnRpZnkgb3Zlci1yZXByZXNlbnRlZCBHTyB0ZXJtcyBhbmQgdmlzdWFsaXNlIHRoZXNlIGFzIGEgbmV0d29yay4KCmBgYHtyfQpsaWJyYXJ5KGNsdXN0ZXJQcm9maWxlcikKdW5pdmVyc2UgPC0gcmVzdWx0c19hbm5vdGF0ZWQgJT4lIHB1bGwoRU5TRU1CTCkKc2lnR2VuZXMgPC0gcmVzdWx0c19hbm5vdGF0ZWQgJT4lIAogIGZpbHRlcihwYWRqIDwgMC4wNSwgIWlzLm5hKEVOU0VNQkwpKSAlPiUgcHVsbChFTlNFTUJMKQoKZW5yaWNoX2dvIDwtIGVucmljaEdPKAogIGdlbmU9IHNpZ0dlbmVzLAogIE9yZ0RiID0gb3JnLkhzLmVnLmRiLAogIGtleVR5cGUgPSAiRU5TRU1CTCIsCiAgb250ID0gIkJQIiwKICB1bml2ZXJzZSA9IHVuaXZlcnNlLAogIHF2YWx1ZUN1dG9mZiA9IDAuMDUsCiAgcmVhZGFibGU9VFJVRQopCgpgYGAKCmBgYHtyfQplbnJpY2hfZ29fdGlkeSA8LSBlbnJpY2hfZ28gJT4lIAogIHNsb3QoInJlc3VsdCIpICU+JSAKICB0aWJibGU6OmFzLnRpYmJsZSgpIAplbnJpY2hfZ29fdGlkeQpgYGAKCkEgZG90IHBsb3QgY2FuIHNob3cgdXMgdGhlIG1vc3QgZW5yaWNoZWQgcGF0aHdheXMsIGFuZCB0aGUgc2l6ZSBvZiBlYWNoLgoKYGBge3J9CmRvdHBsb3QoZW5yaWNoX2dvKQpgYGAKCmBgYHtyfQplbWFwcGxvdChlbnJpY2hfZ28pCmBgYAoKCgojIyBDcmVhdGluZyBHZW5lIGxpc3RzIHRvIHVzZSB3aXRoIGFuIG9ubGluZSB0b29sCgpUaGVyZSBhcmUgYWxzbyBtYW55IG9ubGluZSB0b29scyB0aGF0IG9uZSBjb3VsZCB1c2UgdG8gcGVyZm9ybSBhIGdlbmUgc2V0IG9yIG9udG9sb2d5IGFuYWx5c2lzLiAKCi0gW0RBVklEXShodHRwczovL2RhdmlkLm5jaWZjcmYuZ292LykKLSBbR2VuZVRyYWlsXShodHRwczovL2dlbmV0cmFpbDIuYmlvaW5mLnVuaS1zYi5kZS8pCi0gW0dPUmlsbGFdKGh0dHA6Ly9jYmwtZ29yaWxsYS5jcy50ZWNobmlvbi5hYy5pbC8pCgpUaGUgdG9vbHMgZ2VuZXJhbGx5IHJlcXVpcmUgeW91ciBpbnB1dCBnZW5lcyBsaXN0cyB0byBiZSB1cGxvYWRlZCBhcyBhIHNpbXBsZSB0ZXh0IGZpbGUuIEluIHRoaXMgZmluYWwgY2hhbGxlbmdlLCB3ZSB3aWxsIGNyZWF0ZSBzb21lIGZpbGVzIHRoYXQgeW91IG1pZ2h0IHVzZSBpbiBvbmUgb2YgdGhlc2UgdG9vbHMuCgojIyMgQSBmaWxlIGNvbnRhaW5pbmcgbmFtZXMgb2YgYmFja2dyb3VuZCBnZW5lcwoKVGhpcyBmaWxlIGhhcyBvbmUgY29sdW1uIHdoaWNoIGxpc3RzICoqYWxsIHRoZSBnZW5lIG5hbWVzKiogcHJlc2VudCBpbiB0aGUgYW5hbHlzaXMuIEdlbmUgU3ltYm9scyBhcmUgY29tbW9ubHkgdXNlZCwgYWx0aG91Z2ggYSB0b29sIG1heSBhY2NlcHQgRW5zZW1ibCBvciBSZWZzZXEgbmFtZXMKCiMjIyBBIGZpbGUgY29udGFpbmluZyBuYW1lcyBvZiBzaWduaWZpY2FudCBnZW5lcwoKVGhpcyBmaWxlIGhhcyBvbmUgY29sdW1uIHdoaWNoIGxpc3RzIHRoZSBnZW5lcyB0aGF0IHBhc3NlZCB0aGUgdGhyZXNob2xkIGZvciBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2UgKGUuZy4gcC12YWx1ZSBsZXNzIHRoYW4gMC4wNSkgaW4geW91ciBhbmFseXNpcy4gR2VuZSBTeW1ib2xzIGFyZSBjb21tb25seSB1c2VkLCBhbHRob3VnaCBhIHRvb2wgbWF5IGFjY2VwdCBFbnNlbWJsIG9yIFJlZnNlcSBuYW1lcwoKCj4gIyMgQ2hhbGxlbmdlIHsuY2hhbGxlbmdlfQo+Cj4gQ3JlYXRlIHR3byB0ZXh0IGZpbGVzIHRoYXQgY2FuIGJlIGltcG9ydGVkIGludG8gb25saW5lIHRvb2xzIGZvciBmdXJ0aGVyIGFuYWx5c2lzCj4gMS4gQSBsaXN0IG9mIGJhY2tncm91bmQgZ2VuZXMKPiAyLiBBIGxpc3Qgb2YgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzCj4gMy4gTG9hZCB0aGVzZSBmaWxlcyBpbnRvIEdPcmlsbGEgZm9yIGFuYWx5c2lzCj4gSElOVDogdGhlIGB3cml0ZS50YWJsZWAgZnVuY3Rpb24gaXMgYWJsZSB0byB3cml0ZSBhIGRhdGEgZnJhbWUgdG8gYSB0eHQgZmlsZSBpbiBSLiBZb3Ugd2lsbCBuZWVkIHRvIHNldCB0aGUgYXBwcm9wcmlhdGUgYXJndW1lbnRzIHRvIG1ha2Ugc3VyZSB0aGF0IGEgdGV4dCBmaWxlIHdpdGggb25seSBvbmUgY29sdW1uIGlzIGNyZWF0ZWQuCgpgYGB7cn0KCmBgYA==