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

Resources and data files

This material has been created using the following resources:

Before starting this section, we will make sure we have all the relevant objects from the Differential Expression analysis present.

suppressPackageStartupMessages(library(DESeq2))
de_status <- readRDS("Robjects/de_status.rds")
de_gleason <- readRDS("Robjects/de_gleason.rds")
dds <- readRDS("Robjects/dds.rds")

Overview

  • Visualising DE results
  • Customising plots with ggplot2
  • Filtering and exporting tables with dplyr
  • Getting annotation
  • Retrieving gene models

We can now have a list of genes ordered according to their evidence for being differentially-expressed.

library(dplyr)
library(tibble)
results_status <- as.data.frame(results(de_status)) %>% 
  rownames_to_column("GeneID")
  
results_ordered <- arrange(results_status, padj)

In DESeq2, the function plotMA shows the log2 fold changes attributable to a given variable over the mean of normalized counts for all the samples in the DESeqDataSet. Points will be colored red if the adjusted p value is less than 0.1. Points which fall out of the window are plotted as open triangles pointing either up or down.

The log2 fold change for a particular comparison is plotted on the y-axis and the average of the counts normalized by size factor is shown on the x-axis (“M” for minus, because a log ratio is equal to log minus log, and “A” for average). Each gene is represented with a dot. Genes with an adjusted p value below a threshold (here 0.1, the default) are shown in red.

Both limma and DESeq2 have a function called plotMA, and R can sometimes pick the wrong function. To explictly use the DESeq2 function you can use:-

DESeq2::plotMA(de_status)

MA-plots often display a fanning-effect at the left-hand side (genes with low numbers of counts) due to the high variability of the measurements for these genes. For more informative visualization and more accurate ranking of genes by effect size (the log fold change may sometimes be referred to as an effect size), the DESeq2 authors recommend “shrinking” the log fold-changes which is available in DESeq2’s lfcShrink function. This results in more stable fold change values. The p-values are unaffected.

de_shrunk <- lfcShrink(de_status,contrast = c("Status","Tumour","Normal"))
DESeq2::plotMA(de_shrunk)

Another common plot for displaying the results of a differential expression analysis is a volcano plot

results_ordered %>% 
  ggplot(aes(x =log2FoldChange, y = -log10(padj))) + geom_point()

It can also be useful to examine the counts of reads for a single gene across the groups. A simple function for making this plot is plotCounts, which normalizes counts by sequencing depth and adds a pseudocount of 1/2 to allow for log scale plotting. The counts are grouped by the variables in intgroup, where more than one variable can be specified. Here we specify the gene which had the smallest p value from the results table created above. You can select the gene to plot by rowname or by numeric index:-

plotCounts(dds, "MIR4508",intgroup = c("Status"))

If we want greater control over how to visualise the data, we can use the plotCounts function to return the count data, but not actually produce the plot:-

plotCounts(dds, "MIR4508",intgroup = c("Status"),returnData=TRUE)
                                  count Status
TCGA.CH.5753.01A.11R.1580.07  61.435388 Tumour
TCGA.CH.5761.01A.11R.1580.07 204.621241 Tumour
TCGA.EJ.5507.01A.01R.1580.07 183.095180 Tumour
TCGA.FC.A8O0.01A.41R.A37L.07 479.611539 Tumour
TCGA.G9.6371.01A.11R.1789.07  91.793648 Tumour
TCGA.G9.6499.01A.12R.1965.07 328.732132 Tumour
TCGA.H9.A6BX.01A.31R.A311.07  89.189164 Tumour
TCGA.HC.7077.01A.11R.1965.07 202.857879 Tumour
TCGA.HC.7081.01A.11R.1965.07 301.699014 Tumour
TCGA.HC.7209.01A.11R.2118.07 212.938435 Tumour
TCGA.HC.7748.01A.11R.2118.07 126.892571 Tumour
TCGA.HC.A631.01A.11R.A29R.07 228.360080 Tumour
TCGA.HC.A632.01A.11R.A29R.07 123.860760 Tumour
TCGA.J4.A83K.01A.11R.A352.07 694.901430 Tumour
TCGA.KK.A6E8.01A.11R.A31N.07 259.133677 Tumour
TCGA.QU.A6IO.01A.11R.A31N.07 751.216078 Tumour
TCGA.QU.A6IP.01A.11R.A31N.07 153.528481 Tumour
TCGA.VP.A87H.01A.11R.A352.07 559.539652 Tumour
TCGA.YL.A8SJ.01B.11R.A37L.07 154.172447 Tumour
TCGA.ZG.A8QX.01A.11R.A37L.07 264.086903 Tumour
TCGA.CH.5761.11A.01R.1580.07   2.761284 Normal
TCGA.EJ.7314.11A.01R.2118.07   1.599503 Normal
TCGA.EJ.7327.11A.01R.2118.07   2.083821 Normal
TCGA.G9.6356.11A.01R.1789.07   6.504027 Normal
TCGA.G9.6384.11A.01R.1858.07   0.500000 Normal
TCGA.G9.6499.11A.02R.1965.07   6.301939 Normal
TCGA.HC.8260.11A.01R.2263.07   1.446933 Normal

Challenge 1

  1. Use the option returnData=TRUE to get a data frame containing the counts of MIR4508 in tumours or normals. Visualise these data using ggplot2 (see plot A below).
  2. Repeat the volcano plot from above, but use a different colour to indicate which genes are significant with an adjusted p-value less than 0.05. See plot B below
  3. (Optional) The argument intgroup= can be used to retrieve and plot data from multiple variables of interest in the data. Use the value intgroup=c("Status","Batch") and compare the counts between status and batches. See plot C below.

Adding annotation to the DESeq2 results

There are a number of ways to add annotation, but we will demonstrate how to do this using the org.Hs.eg.db package. This package is one of several organism-level packages which are re-built every 6 months. These packages are listed on the annotation section of the Bioconductor, and are installed in the same way as regular Bioconductor packages. An alternative approach is to use biomaRt, an interface to the BioMart resource. BioMart is much more comprehensive, but the organism packages fit better into the Bioconductor workflow.

### Only execute when you need to install the package
library(BiocManager)
install("org.Hs.eg.db")

The packages are larger in size that Bioconductor software pacakges, but essentially they are databases that can be used to make offline queries.

library(org.Hs.eg.db)

First we need to decide what information we want. In order to see what we can extract we can run the columns function on the annotation database.

columns(org.Hs.eg.db)
 [1] "ACCNUM"       "ALIAS"        "ENSEMBL"     
 [4] "ENSEMBLPROT"  "ENSEMBLTRANS" "ENTREZID"    
 [7] "ENZYME"       "EVIDENCE"     "EVIDENCEALL" 
[10] "GENENAME"     "GO"           "GOALL"       
[13] "IPI"          "MAP"          "OMIM"        
[16] "ONTOLOGY"     "ONTOLOGYALL"  "PATH"        
[19] "PFAM"         "PMID"         "PROSITE"     
[22] "REFSEQ"       "SYMBOL"       "UCSCKG"      
[25] "UNIGENE"      "UNIPROT"     

We are going to filter the database by a key or set of keys in order to extract the information we want. Valid names for the key can be retrieved with the keytypes function.

keytypes(org.Hs.eg.db)
 [1] "ACCNUM"       "ALIAS"        "ENSEMBL"     
 [4] "ENSEMBLPROT"  "ENSEMBLTRANS" "ENTREZID"    
 [7] "ENZYME"       "EVIDENCE"     "EVIDENCEALL" 
[10] "GENENAME"     "GO"           "GOALL"       
[13] "IPI"          "MAP"          "OMIM"        
[16] "ONTOLOGY"     "ONTOLOGYALL"  "PATH"        
[19] "PFAM"         "PMID"         "PROSITE"     
[22] "REFSEQ"       "SYMBOL"       "UCSCKG"      
[25] "UNIGENE"      "UNIPROT"     

We should see SYMBOL, which is the type of key we are going to use in this case. If we are unsure what values are acceptable for the key, we can check what keys are valid with keys

keys(org.Hs.eg.db, keytype="SYMBOL")[1:10]
 [1] "A1BG"     "A2M"      "A2MP1"    "NAT1"    
 [5] "NAT2"     "NATP"     "SERPINA3" "AADAC"   
 [9] "AAMP"     "AANAT"   

For the top gene in our analysis the call to the function would be:-

select(org.Hs.eg.db, keys="MIR4508",
       keytype = "SYMBOL",columns=c("SYMBOL","GENENAME")
)


### In case of errors, try
### AnnotationDBI::select(org.Hs.eg.db, keys="MIR4508",
###       keytype = "SYMBOL",columns=c("SYMBOL","GENENAME")
###)

To annotate our results, we definitely want gene symbols and perhaps the full gene name. Let’s build up our annotation information into a new data frame using the select function.

anno <- AnnotationDbi::select(org.Hs.eg.db,keys=results_ordered$GeneID,
              columns=c("SYMBOL","GENENAME"),
              keytype="SYMBOL")
# Have a look at the annotation
head(anno)
   SYMBOL
1 MIR4508
2  SEMA5B
3   HSPA6
4  NKX2-3
5   HOXC4
6   HOXC5
                                      GENENAME
1                                microRNA 4508
2                                semaphorin 5B
3 heat shock protein family A (Hsp70) member 6
4                               NK2 homeobox 3
5                                  homeobox C4
6                                  homeobox C5

However, we have a problem that the resulting data frame has more rows than our results table. This is due to the one-to-many relationships that often occur when mapping between various identifiers.

dim(anno)
[1] 23372     2
dim(results_ordered)
[1] 23368     7

Such duplicated entries can be identified using the duplicated function.

dup_ids <- anno$SYMBOL[duplicated(anno$SYMBOL)]
filter(anno, SYMBOL %in% dup_ids) %>% 
  arrange(SYMBOL)
  SYMBOL
1    HBD
2    HBD
3  MEMO1
4  MEMO1
5   MMD2
6   MMD2
7    TEC
8    TEC
                                             GENENAME
1                            hemoglobin subunit delta
2                       hypophosphatemic bone disease
3                Methylation modifier for class I HLA
4                         mediator of cell motility 1
5 monocyte to macrophage differentiation associated 2
6                        Miyoshi muscular dystrophy 2
7                         tec protein tyrosine kinase
8           transient erythroblastopenia of childhood

Fortunately, there are not too many so hopefully we won’t lose too much information if we discard the entries that are duplicated. The first occurence of the duplicated ID will still be included in the table.

anno <- AnnotationDbi::select(org.Hs.eg.db,keys=results_ordered$GeneID,
              columns=c("ENSEMBL","SYMBOL","GENENAME","ENTREZID"),
              keytype="SYMBOL") %>% 
  filter(!duplicated(SYMBOL))
dim(anno)
[1] 23368     4

We can bind in the annotation information to the results data frame.

anno <- dplyr::rename(anno, GeneID = SYMBOL)
results_annotated <- left_join(results_ordered, anno,by="GeneID")

We can save the results table using the write.csv function, which writes the results out to a csv file that you can open in excel.

## create a directory for the results. don't give a warning if it already exists
dir.create("results",showWarnings = FALSE)
write.csv(results_annotated,file="tumour_vs_normal_DESeq_annotated.csv",row.names=FALSE)

We 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.

Here we will take the top 10 genes from the differential expression analysis and produce a heatmap. 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.

library(pheatmap)
top_genes <- results_ordered$GeneID[1:10]
vsd <- vst(dds)
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("Status","gleason_score")])
pheatmap(assay(vsd)[top_genes,],
         annotation_col = sampleInfo)

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()

Challenge 2

  1. Repeat the same heatmap as above, but for the top 75 most differentially-expressed genes in the contrast between Gleason grades 9 and 6
  2. Save the plot to a pdf file

Annotating plots with gene names

Now that we have an annotated table of results, we can add the gene names to some of the other plots we have created. 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_annotated %>% 
  ggplot(aes(x = log2FoldChange, y = -log10(padj), label=GeneID)) + 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 <- results_annotated$ENSEMBL[1:N]
results_annotated %>% 
  mutate(Label = ifelse(ENSEMBL %in% top_genes, GeneID, "")) %>%  
  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")
N <- 10
top_genes <- results_annotated$ENSEMBL[1:N]
results_annotated %>% 
  mutate(Label = ifelse(ENSEMBL %in% top_genes, GeneID, "")) %>%  
  ggplot(aes(x = log2FoldChange, y = -log10(padj), label=Label)) + geom_point(alpha=0.4) + geom_text(col="blue")

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()
               biomart               version
1 ENSEMBL_MART_ENSEMBL      Ensembl Genes 99
2   ENSEMBL_MART_MOUSE      Mouse strains 99
3     ENSEMBL_MART_SNP  Ensembl Variation 99
4 ENSEMBL_MART_FUNCGEN Ensembl Regulation 99
ensembl=useMart("ENSEMBL_MART_ENSEMBL")
# list the available datasets (species). Replace human with the name of your organism
listDatasets(ensembl) %>% filter(grepl("Human",description))
                dataset              description
1 hsapiens_gene_ensembl Human genes (GRCh38.p13)
     version
1 GRCh38.p13
ensembl = useDataset("hsapiens_gene_ensembl", mart=ensembl)

Queries to biomaRt are constructed in a similar way to the queries we performed with the org.Mm.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.Mm.eg.db package.

listFilters(ensembl) %>% 
    filter(grepl("symbol",name))
               name
1       hgnc_symbol
2 uniprot_gn_symbol
                                 description
1                 HGNC symbol(s) [e.g. A1BG]
2 UniProtKB Gene Name symbol(s) [e.g. 5HT1A]
listAttributes(ensembl) %>% 
    filter(grepl("description",name))
                         name
1                 description
2       phenotype_description
3      goslim_goa_description
4        mim_gene_description
5      mim_morbid_description
6      entrezgene_description
7        wikigene_description
8          family_description
9  interpro_short_description
10       interpro_description
11                description
12                description
13                description
14         source_description
15                description
16 somatic_source_description
17                description
                  description         page
1            Gene description feature_page
2       Phenotype description feature_page
3      GOSlim GOA Description feature_page
4        MIM gene description feature_page
5      MIM morbid description feature_page
6       NCBI gene description feature_page
7        WikiGene description feature_page
8  Ensembl Family Description feature_page
9  Interpro Short Description feature_page
10       Interpro Description feature_page
11           Gene description    structure
12           Gene description     homologs
13           Gene description          snp
14 Variant source description          snp
15           Gene description  snp_somatic
16 Variant source description  snp_somatic
17           Gene description    sequences

An advantage over the org.. packages is that positional information can be retrieved

attributeNames <- c('ensembl_gene_id', 'entrezgene_id','description')
getBM(attributes = attributeNames,
      filters = "hgnc_symbol",
      values=top_genes,
      mart=ensembl)
[1] ensembl_gene_id entrezgene_id   description    
<0 rows> (or 0-length row.names)

Challenge 3

  1. Use biomaRt to create an data frame containing the entrezgene, Ensembl ID and genomic coordinates (chromosome, start, end) for the Ensembl IDs in the DESeq2 results
  2. Remove duplicates entries from the new data frame
  3. Join the biomaRt annotation to the DESeq2 results to produce a data frame with differential expression results and annotation
  4. Write the joined data frame to a csv file

Interactive graphs and tables

It is often useful to be able to explore our results in an interactive manner; searching for our favourite genes of interest and plotting on-the-fly whether they are statistically significant in our dataset or not.

Such a visualisation is possible with the Glimma Bioconductor package.

It takes our DESeq2 results object, annotation table and normalized counts, and produces a HTML page including a sortable results table, MA-plot and scatter plot. Particular genes can be searched among the table and their expression patterns can be displayed. Alternatively we can click on particular point in the plot and display their stats.

## Repeat the annotation, as the previous annotation table was created using an ordered results table
anno <- AnnotationDbi::select(org.Hs.eg.db,keys=rownames(dds),
              columns=c("SYMBOL","GENENAME","ENSEMBL"),
              keytype="SYMBOL") %>% 
  dplyr::rename(GeneID = SYMBOL) %>% 
  filter(!duplicated(GeneID))
## Make sure we have normalised counts before proceeding
dds <- estimateSizeFactors(dds)
## Load the Glimma package and create the report
library(Glimma)
glMDPlot(de_status,
         anno,
         groups = colData(dds)$Status,
         counts = counts(dds,normalized=TRUE),
         transform = TRUE,
         side.main = "GeneID")
LS0tCnRpdGxlOiAiUk5BLXNlcSBBbmFseXNpcyBpbiBSIgpzdWJ0aXRsZTogIkFubm90YXRpb24gYW5kIFZpc3VhbGlzYXRpb24gb2YgUk5BLXNlcSByZXN1bHRzIgphdXRob3I6ICJNYXJrIER1bm5pbmciCmRhdGU6IEZlYnJ1YXJ5IDIwMjAKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCm1pbnV0ZXM6IDMwMApsYXlvdXQ6IHBhZ2UKLS0tCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFLGZpZy53aWR0aD0xMikKbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKKipPcmlnaW5hbCBBdXRob3JzOiBCZWxpbmRhIFBoaXBzb24sIEFubmEgVHJpZ29zLCBNYXR0IFJpdGNoaWUsIE1hcmlhIERveWxlLCBIYXJyaWV0IERhc2hub3csIENoYXJpdHkgTGF3KiosICoqU3RlcGhhbmUgQmFsbGVyZWF1LCBPc2NhciBSdWVkYSwgQXNobGV5IFNhd2xlKioKQmFzZWQgb24gdGhlIGNvdXJzZSBbUk5Bc2VxIGFuYWx5c2lzIGluIFJdKGh0dHA6Ly9jb21iaW5lLWF1c3RyYWxpYS5naXRodWIuaW8vMjAxNi0wNS0xMS1STkFzZXEvKSBkZWxpdmVyZWQgb24gTWF5IDExLzEydGggMjAxNgoKIyMgUmVzb3VyY2VzIGFuZCBkYXRhIGZpbGVzCgpUaGlzIG1hdGVyaWFsIGhhcyBiZWVuIGNyZWF0ZWQgdXNpbmcgdGhlIGZvbGxvd2luZyByZXNvdXJjZXM6ICAKCgotIGh0dHA6Ly9tb25hc2hiaW9pbmZvcm1hdGljc3BsYXRmb3JtLmdpdGh1Yi5pby9STkFzZXEtREUtYW5hbHlzaXMtd2l0aC1SLzk5LVJOQXNlcV9ERV9hbmFseXNpc193aXRoX1IuaHRtbCAgCi0gaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvZGV2ZWwvYmlvYy92aWduZXR0ZXMvREVTZXEyL2luc3QvZG9jL0RFU2VxMi5odG1sCi0gaHR0cHM6Ly9iaW9jb25kdWN0b3IuZ2l0aHViLmlvL0Jpb2NXb3Jrc2hvcHMvcm5hLXNlcS1kYXRhLWFuYWx5c2lzLXdpdGgtZGVzZXEyLmh0bWwKCgoKQmVmb3JlIHN0YXJ0aW5nIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCBtYWtlIHN1cmUgd2UgaGF2ZSBhbGwgdGhlIHJlbGV2YW50IG9iamVjdHMgZnJvbSB0aGUgRGlmZmVyZW50aWFsIEV4cHJlc3Npb24gYW5hbHlzaXMgcHJlc2VudC4KCmBgYHtyfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShERVNlcTIpKQoKZGVfc3RhdHVzIDwtIHJlYWRSRFMoIlJvYmplY3RzL2RlX3N0YXR1cy5yZHMiKQpkZV9nbGVhc29uIDwtIHJlYWRSRFMoIlJvYmplY3RzL2RlX2dsZWFzb24ucmRzIikKZGRzIDwtIHJlYWRSRFMoIlJvYmplY3RzL2Rkcy5yZHMiKQpgYGAKCiMgT3ZlcnZpZXcKCi0gVmlzdWFsaXNpbmcgREUgcmVzdWx0cwotIEN1c3RvbWlzaW5nIHBsb3RzIHdpdGggZ2dwbG90MgotIEZpbHRlcmluZyBhbmQgZXhwb3J0aW5nIHRhYmxlcyB3aXRoIGRwbHlyCi0gR2V0dGluZyBhbm5vdGF0aW9uCi0gUmV0cmlldmluZyBnZW5lIG1vZGVscwoKCgoKV2UgY2FuIG5vdyBoYXZlIGEgbGlzdCBvZiBnZW5lcyBvcmRlcmVkIGFjY29yZGluZyB0byB0aGVpciBldmlkZW5jZSBmb3IgYmVpbmcgZGlmZmVyZW50aWFsbHktZXhwcmVzc2VkLgoKYGBge3IgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWJibGUpCgpyZXN1bHRzX3N0YXR1cyA8LSBhcy5kYXRhLmZyYW1lKHJlc3VsdHMoZGVfc3RhdHVzKSkgJT4lIAogIHJvd25hbWVzX3RvX2NvbHVtbigiR2VuZUlEIikKICAKCnJlc3VsdHNfb3JkZXJlZCA8LSBhcnJhbmdlKHJlc3VsdHNfc3RhdHVzLCBwYWRqKQoKYGBgCgpJbiBgREVTZXEyYCwgdGhlIGZ1bmN0aW9uIHBsb3RNQSBzaG93cyB0aGUgbG9nMiBmb2xkIGNoYW5nZXMgYXR0cmlidXRhYmxlIHRvIGEgZ2l2ZW4gdmFyaWFibGUgb3ZlciB0aGUgbWVhbiBvZiBub3JtYWxpemVkIGNvdW50cyBmb3IgYWxsIHRoZSBzYW1wbGVzIGluIHRoZSBERVNlcURhdGFTZXQuIFBvaW50cyB3aWxsIGJlIGNvbG9yZWQgcmVkIGlmIHRoZSBhZGp1c3RlZCBwIHZhbHVlIGlzIGxlc3MgdGhhbiAwLjEuIFBvaW50cyB3aGljaCBmYWxsIG91dCBvZiB0aGUgd2luZG93IGFyZSBwbG90dGVkIGFzIG9wZW4gdHJpYW5nbGVzIHBvaW50aW5nIGVpdGhlciB1cCBvciBkb3duLgoKVGhlIGxvZzIgZm9sZCBjaGFuZ2UgZm9yIGEgcGFydGljdWxhciBjb21wYXJpc29uIGlzIHBsb3R0ZWQgb24gdGhlIHktYXhpcyBhbmQgdGhlIGF2ZXJhZ2Ugb2YgdGhlIGNvdW50cyBub3JtYWxpemVkIGJ5IHNpemUgZmFjdG9yIGlzIHNob3duIG9uIHRoZSB4LWF4aXMgKCJNIiBmb3IgbWludXMsIGJlY2F1c2UgYSBsb2cgcmF0aW8gaXMgZXF1YWwgdG8gbG9nIG1pbnVzIGxvZywgYW5kICJBIiBmb3IgYXZlcmFnZSkuIEVhY2ggZ2VuZSBpcyByZXByZXNlbnRlZCB3aXRoIGEgZG90LiBHZW5lcyB3aXRoIGFuIGFkanVzdGVkIHAgdmFsdWUgYmVsb3cgYSB0aHJlc2hvbGQgKGhlcmUgMC4xLCB0aGUgZGVmYXVsdCkgYXJlIHNob3duIGluIHJlZC4KCkJvdGggYGxpbW1hYCBhbmQgYERFU2VxMmAgaGF2ZSBhIGZ1bmN0aW9uIGNhbGxlZCBgcGxvdE1BYCwgYW5kIFIgY2FuIHNvbWV0aW1lcyBwaWNrIHRoZSB3cm9uZyBmdW5jdGlvbi4gVG8gZXhwbGljdGx5IHVzZSB0aGUgYERFU2VxMmAgZnVuY3Rpb24geW91IGNhbiB1c2U6LQoKYGBge3J9CkRFU2VxMjo6cGxvdE1BKGRlX3N0YXR1cykKYGBgCgoKTUEtcGxvdHMgb2Z0ZW4gZGlzcGxheSBhIGZhbm5pbmctZWZmZWN0IGF0IHRoZSBsZWZ0LWhhbmQgc2lkZSAoZ2VuZXMgd2l0aCBsb3cgbnVtYmVycyBvZiBjb3VudHMpIGR1ZSB0byB0aGUgaGlnaCB2YXJpYWJpbGl0eSBvZiB0aGUgbWVhc3VyZW1lbnRzIGZvciB0aGVzZSBnZW5lcy4gRm9yIG1vcmUgaW5mb3JtYXRpdmUgdmlzdWFsaXphdGlvbiBhbmQgbW9yZSBhY2N1cmF0ZSByYW5raW5nIG9mIGdlbmVzIGJ5IGVmZmVjdCBzaXplICh0aGUgbG9nIGZvbGQgY2hhbmdlIG1heSBzb21ldGltZXMgYmUgcmVmZXJyZWQgdG8gYXMgYW4gZWZmZWN0IHNpemUpLCB0aGUgYERFU2VxMmAgYXV0aG9ycyByZWNvbW1lbmQgInNocmlua2luZyIgdGhlIGxvZyBmb2xkLWNoYW5nZXMgd2hpY2ggaXMgYXZhaWxhYmxlIGluIERFU2VxMuKAmXMgYGxmY1Nocmlua2AgZnVuY3Rpb24uIFRoaXMgcmVzdWx0cyBpbiBtb3JlIHN0YWJsZSBmb2xkIGNoYW5nZSB2YWx1ZXMuIFRoZSBwLXZhbHVlcyBhcmUgdW5hZmZlY3RlZC4KCmBgYHtyfQpkZV9zaHJ1bmsgPC0gbGZjU2hyaW5rKGRlX3N0YXR1cyxjb250cmFzdCA9IGMoIlN0YXR1cyIsIlR1bW91ciIsIk5vcm1hbCIpKQpERVNlcTI6OnBsb3RNQShkZV9zaHJ1bmspCmBgYAoKCkFub3RoZXIgY29tbW9uIHBsb3QgZm9yIGRpc3BsYXlpbmcgdGhlIHJlc3VsdHMgb2YgYSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyBpcyBhICp2b2xjYW5vIHBsb3QqCgpgYGB7cn0KcmVzdWx0c19vcmRlcmVkICU+JSAKICBnZ3Bsb3QoYWVzKHggPWxvZzJGb2xkQ2hhbmdlLCB5ID0gLWxvZzEwKHBhZGopKSkgKyBnZW9tX3BvaW50KCkKCmBgYAoKCkl0IGNhbiBhbHNvIGJlIHVzZWZ1bCB0byBleGFtaW5lIHRoZSBjb3VudHMgb2YgcmVhZHMgZm9yIGEgc2luZ2xlIGdlbmUgYWNyb3NzIHRoZSBncm91cHMuIEEgc2ltcGxlIGZ1bmN0aW9uIGZvciBtYWtpbmcgdGhpcyBwbG90IGlzIGBwbG90Q291bnRzYCwgd2hpY2ggbm9ybWFsaXplcyBjb3VudHMgYnkgc2VxdWVuY2luZyBkZXB0aCBhbmQgYWRkcyBhIHBzZXVkb2NvdW50IG9mIDEvMiB0byBhbGxvdyBmb3IgbG9nIHNjYWxlIHBsb3R0aW5nLiBUaGUgY291bnRzIGFyZSBncm91cGVkIGJ5IHRoZSB2YXJpYWJsZXMgaW4gIGBpbnRncm91cGAsIHdoZXJlIG1vcmUgdGhhbiBvbmUgdmFyaWFibGUgY2FuIGJlIHNwZWNpZmllZC4gSGVyZSB3ZSBzcGVjaWZ5IHRoZSBnZW5lIHdoaWNoIGhhZCB0aGUgc21hbGxlc3QgcCB2YWx1ZSBmcm9tIHRoZSByZXN1bHRzIHRhYmxlIGNyZWF0ZWQgYWJvdmUuIFlvdSBjYW4gc2VsZWN0IHRoZSBnZW5lIHRvIHBsb3QgYnkgcm93bmFtZSBvciBieSBudW1lcmljIGluZGV4Oi0KCmBgYHtyfQpwbG90Q291bnRzKGRkcywgIk1JUjQ1MDgiLGludGdyb3VwID0gYygiU3RhdHVzIikpCmBgYAoKCklmIHdlIHdhbnQgZ3JlYXRlciBjb250cm9sIG92ZXIgaG93IHRvIHZpc3VhbGlzZSB0aGUgZGF0YSwgd2UgY2FuIHVzZSB0aGUgYHBsb3RDb3VudHNgIGZ1bmN0aW9uIHRvIHJldHVybiB0aGUgY291bnQgZGF0YSwgYnV0IG5vdCBhY3R1YWxseSBwcm9kdWNlIHRoZSBwbG90Oi0KCmBgYHtyfQpwbG90Q291bnRzKGRkcywgIk1JUjQ1MDgiLGludGdyb3VwID0gYygiU3RhdHVzIikscmV0dXJuRGF0YT1UUlVFKQpgYGAKCgo+ICMjIENoYWxsZW5nZSAxIHsuY2hhbGxlbmdlfQo+Cj4gMS4gVXNlIHRoZSBvcHRpb24gYHJldHVybkRhdGE9VFJVRWAgdG8gZ2V0IGEgZGF0YSBmcmFtZSBjb250YWluaW5nIHRoZSBjb3VudHMgb2YgYE1JUjQ1MDhgIGluIHR1bW91cnMgb3Igbm9ybWFscy4gVmlzdWFsaXNlIHRoZXNlIGRhdGEgdXNpbmcgYGdncGxvdDJgIChzZWUgcGxvdCBBIGJlbG93KS4gCj4gMi4gUmVwZWF0IHRoZSB2b2xjYW5vIHBsb3QgZnJvbSBhYm92ZSwgYnV0IHVzZSBhIGRpZmZlcmVudCBjb2xvdXIgdG8gaW5kaWNhdGUgd2hpY2ggZ2VuZXMgYXJlIHNpZ25pZmljYW50IHdpdGggYW4gYWRqdXN0ZWQgcC12YWx1ZSBsZXNzIHRoYW4gMC4wNS4gU2VlIHBsb3QgQiBiZWxvdwo+IDMuIChPcHRpb25hbCkgVGhlIGFyZ3VtZW50IGBpbnRncm91cD1gIGNhbiBiZSB1c2VkIHRvIHJldHJpZXZlIGFuZCBwbG90IGRhdGEgZnJvbSBtdWx0aXBsZSB2YXJpYWJsZXMgb2YgaW50ZXJlc3QgaW4gdGhlIGRhdGEuIFVzZSB0aGUgdmFsdWUgYGludGdyb3VwPWMoIlN0YXR1cyIsIkJhdGNoIilgIGFuZCBjb21wYXJlIHRoZSBjb3VudHMgYmV0d2VlbiBzdGF0dXMgYW5kIGJhdGNoZXMuIFNlZSBwbG90IEMgYmVsb3cuCgpgYGB7ciBlY2hvPUZBTFNFfQpwMSA8LSBwbG90Q291bnRzKGRkcywgIk1JUjQ1MDgiLGludGdyb3VwID0gIlN0YXR1cyIscmV0dXJuRGF0YSA9IFRSVUUpICU+JSAgIGdncGxvdChhZXMoeCA9IFN0YXR1cywgeSA9IGNvdW50LGNvbD1TdGF0dXMpKSArIGdlb21faml0dGVyKHdpZHRoPTAuMSkgCnAyIDwtIHJlc3VsdHNfb3JkZXJlZCAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gbG9nMkZvbGRDaGFuZ2UsIHkgPSAtbG9nMTAocGFkaiksIGNvbD1wYWRqIDwgMC4wNSkpICsgZ2VvbV9wb2ludCgpCnAzIDwtIHBsb3RDb3VudHMoZGRzLCAiTUlSNDUwOCIsaW50Z3JvdXAgPSBjKCJTdGF0dXMiLCJCYXRjaCIpLHJldHVybkRhdGEgPSBUUlVFKSAlPiUgICBnZ3Bsb3QoYWVzKHggPSBTdGF0dXMsIHkgPSBjb3VudCxjb2w9U3RhdHVzKSkgKyBnZW9tX2ppdHRlcih3aWR0aD0wLjEpICsgZmFjZXRfd3JhcCh+QmF0Y2gpCgpjb3dwbG90OjpwbG90X2dyaWQocDEscDIscDMsbGFiZWxzPUxFVFRFUlNbMTozXSkKYGBgCgoKIyMgQWRkaW5nIGFubm90YXRpb24gdG8gdGhlIERFU2VxMiByZXN1bHRzCgpUaGVyZSBhcmUgYSBudW1iZXIgb2Ygd2F5cyB0byBhZGQgYW5ub3RhdGlvbiwgYnV0IHdlIHdpbGwgZGVtb25zdHJhdGUgaG93IHRvIGRvIHRoaXMgdXNpbmcgdGhlICpvcmcuSHMuZWcuZGIqIHBhY2thZ2UuIFRoaXMgcGFja2FnZSBpcyBvbmUgb2Ygc2V2ZXJhbCAqb3JnYW5pc20tbGV2ZWwqIHBhY2thZ2VzIHdoaWNoIGFyZSByZS1idWlsdCBldmVyeSA2IG1vbnRocy4gVGhlc2UgcGFja2FnZXMgYXJlIGxpc3RlZCBvbiB0aGUgW2Fubm90YXRpb24gc2VjdGlvbl0oaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9CaW9jVmlld3MuaHRtbCNfX19Bbm5vdGF0aW9uRGF0YSkgb2YgdGhlIEJpb2NvbmR1Y3RvciwgYW5kIGFyZSBpbnN0YWxsZWQgaW4gdGhlIHNhbWUgd2F5IGFzIHJlZ3VsYXIgQmlvY29uZHVjdG9yIHBhY2thZ2VzLiBBbiBhbHRlcm5hdGl2ZSBhcHByb2FjaCBpcyB0byB1c2UgYGJpb21hUnRgLCBhbiBpbnRlcmZhY2UgdG8gdGhlIFtCaW9NYXJ0XShodHRwOi8vd3d3LmJpb21hcnQub3JnLykgcmVzb3VyY2UuIEJpb01hcnQgaXMgbXVjaCBtb3JlIGNvbXByZWhlbnNpdmUsIGJ1dCB0aGUgb3JnYW5pc20gcGFja2FnZXMgZml0IGJldHRlciBpbnRvIHRoZSBCaW9jb25kdWN0b3Igd29ya2Zsb3cuCgoKYGBge3IgZXZhbD1GQUxTRX0KIyMjIE9ubHkgZXhlY3V0ZSB3aGVuIHlvdSBuZWVkIHRvIGluc3RhbGwgdGhlIHBhY2thZ2UKbGlicmFyeShCaW9jTWFuYWdlcikKaW5zdGFsbCgib3JnLkhzLmVnLmRiIikKYGBgCgpUaGUgcGFja2FnZXMgYXJlIGxhcmdlciBpbiBzaXplIHRoYXQgQmlvY29uZHVjdG9yIHNvZnR3YXJlIHBhY2FrZ2VzLCBidXQgZXNzZW50aWFsbHkgdGhleSBhcmUgZGF0YWJhc2VzIHRoYXQgY2FuIGJlIHVzZWQgdG8gbWFrZSAqb2ZmbGluZSogcXVlcmllcy4gCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KG9yZy5Icy5lZy5kYikKYGBgCgoKRmlyc3Qgd2UgbmVlZCB0byBkZWNpZGUgd2hhdCBpbmZvcm1hdGlvbiB3ZSB3YW50LiBJbiBvcmRlciB0byBzZWUgd2hhdCB3ZSBjYW4gZXh0cmFjdCB3ZSBjYW4gcnVuIHRoZSBgY29sdW1uc2AgZnVuY3Rpb24gb24gdGhlIGFubm90YXRpb24gZGF0YWJhc2UuCgpgYGB7cn0KY29sdW1ucyhvcmcuSHMuZWcuZGIpCmBgYAoKV2UgYXJlIGdvaW5nIHRvIGZpbHRlciB0aGUgZGF0YWJhc2UgYnkgYSBrZXkgb3Igc2V0IG9mIGtleXMgaW4gb3JkZXIgdG8gZXh0cmFjdCB0aGUgaW5mb3JtYXRpb24gd2Ugd2FudC4gVmFsaWQgbmFtZXMgZm9yIHRoZSBrZXkgY2FuIGJlIHJldHJpZXZlZCB3aXRoIHRoZSBga2V5dHlwZXNgIGZ1bmN0aW9uLgoKYGBge3J9CmtleXR5cGVzKG9yZy5Icy5lZy5kYikKYGBgCgpXZSBzaG91bGQgc2VlIGBTWU1CT0xgLCB3aGljaCBpcyB0aGUgdHlwZSBvZiBrZXkgd2UgYXJlIGdvaW5nIHRvIHVzZSBpbiB0aGlzIGNhc2UuIElmIHdlIGFyZSB1bnN1cmUgd2hhdCB2YWx1ZXMgYXJlIGFjY2VwdGFibGUgZm9yIHRoZSBrZXksIHdlIGNhbiBjaGVjayB3aGF0IGtleXMgYXJlIHZhbGlkIHdpdGggYGtleXNgCgpgYGB7cn0Ka2V5cyhvcmcuSHMuZWcuZGIsIGtleXR5cGU9IlNZTUJPTCIpWzE6MTBdCmBgYAoKCgpGb3IgdGhlIHRvcCBnZW5lIGluIG91ciBhbmFseXNpcyB0aGUgY2FsbCB0byB0aGUgZnVuY3Rpb24gd291bGQgYmU6LQoKYGBge3IgZXZhbD1GQUxTRX0Kc2VsZWN0KG9yZy5Icy5lZy5kYiwga2V5cz0iTUlSNDUwOCIsCiAgICAgICBrZXl0eXBlID0gIlNZTUJPTCIsY29sdW1ucz1jKCJTWU1CT0wiLCJHRU5FTkFNRSIpCikKCgojIyMgSW4gY2FzZSBvZiBlcnJvcnMsIHRyeQojIyMgQW5ub3RhdGlvbkRCSTo6c2VsZWN0KG9yZy5Icy5lZy5kYiwga2V5cz0iTUlSNDUwOCIsCiMjIyAgICAgICBrZXl0eXBlID0gIlNZTUJPTCIsY29sdW1ucz1jKCJTWU1CT0wiLCJHRU5FTkFNRSIpCiMjIykKCmBgYAoKCgpUbyBhbm5vdGF0ZSBvdXIgcmVzdWx0cywgd2UgZGVmaW5pdGVseSB3YW50IGdlbmUgc3ltYm9scyBhbmQgcGVyaGFwcyB0aGUgZnVsbCBnZW5lIG5hbWUuIExldCdzIGJ1aWxkIHVwIG91ciBhbm5vdGF0aW9uIGluZm9ybWF0aW9uIGludG8gYSBuZXcgZGF0YSBmcmFtZSB1c2luZyB0aGUgYHNlbGVjdGAgZnVuY3Rpb24uCgpgYGB7cn0KYW5ubyA8LSBBbm5vdGF0aW9uRGJpOjpzZWxlY3Qob3JnLkhzLmVnLmRiLGtleXM9cmVzdWx0c19vcmRlcmVkJEdlbmVJRCwKICAgICAgICAgICAgICBjb2x1bW5zPWMoIlNZTUJPTCIsIkdFTkVOQU1FIiksCiAgICAgICAgICAgICAga2V5dHlwZT0iU1lNQk9MIikKIyBIYXZlIGEgbG9vayBhdCB0aGUgYW5ub3RhdGlvbgpoZWFkKGFubm8pCgpgYGAKCkhvd2V2ZXIsIHdlIGhhdmUgYSBwcm9ibGVtIHRoYXQgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lIGhhcyBtb3JlIHJvd3MgdGhhbiBvdXIgcmVzdWx0cyB0YWJsZS4gVGhpcyBpcyBkdWUgdG8gdGhlICpvbmUtdG8tbWFueSogcmVsYXRpb25zaGlwcyB0aGF0IG9mdGVuIG9jY3VyIHdoZW4gbWFwcGluZyBiZXR3ZWVuIHZhcmlvdXMgaWRlbnRpZmllcnMuCgpgYGB7cn0KZGltKGFubm8pCmRpbShyZXN1bHRzX29yZGVyZWQpCmBgYAoKU3VjaCBkdXBsaWNhdGVkIGVudHJpZXMgY2FuIGJlIGlkZW50aWZpZWQgdXNpbmcgdGhlIGBkdXBsaWNhdGVkYCBmdW5jdGlvbi4gCgpgYGB7cn0KZHVwX2lkcyA8LSBhbm5vJFNZTUJPTFtkdXBsaWNhdGVkKGFubm8kU1lNQk9MKV0KZmlsdGVyKGFubm8sIFNZTUJPTCAlaW4lIGR1cF9pZHMpICU+JSAKICBhcnJhbmdlKFNZTUJPTCkKCmBgYAoKRm9ydHVuYXRlbHksIHRoZXJlIGFyZSBub3QgdG9vIG1hbnkgc28gaG9wZWZ1bGx5IHdlIHdvbid0IGxvc2UgdG9vIG11Y2ggaW5mb3JtYXRpb24gaWYgd2UgZGlzY2FyZCB0aGUgZW50cmllcyB0aGF0IGFyZSBkdXBsaWNhdGVkLiBUaGUgZmlyc3Qgb2NjdXJlbmNlIG9mIHRoZSBkdXBsaWNhdGVkIElEIHdpbGwgc3RpbGwgYmUgaW5jbHVkZWQgaW4gdGhlIHRhYmxlLgoKYGBge3J9CmFubm8gPC0gQW5ub3RhdGlvbkRiaTo6c2VsZWN0KG9yZy5Icy5lZy5kYixrZXlzPXJlc3VsdHNfb3JkZXJlZCRHZW5lSUQsCiAgICAgICAgICAgICAgY29sdW1ucz1jKCJFTlNFTUJMIiwiU1lNQk9MIiwiR0VORU5BTUUiLCJFTlRSRVpJRCIpLAogICAgICAgICAgICAgIGtleXR5cGU9IlNZTUJPTCIpICU+JSAKICBmaWx0ZXIoIWR1cGxpY2F0ZWQoU1lNQk9MKSkKZGltKGFubm8pCmBgYAoKCldlIGNhbiBiaW5kIGluIHRoZSBhbm5vdGF0aW9uIGluZm9ybWF0aW9uIHRvIHRoZSBgcmVzdWx0c2AgZGF0YSBmcmFtZS4gCgpgYGB7cn0KYW5ubyA8LSBkcGx5cjo6cmVuYW1lKGFubm8sIEdlbmVJRCA9IFNZTUJPTCkKcmVzdWx0c19hbm5vdGF0ZWQgPC0gbGVmdF9qb2luKHJlc3VsdHNfb3JkZXJlZCwgYW5ubyxieT0iR2VuZUlEIikKCgpgYGAKCgpXZSBjYW4gc2F2ZSB0aGUgcmVzdWx0cyB0YWJsZSB1c2luZyB0aGUgYHdyaXRlLmNzdmAgZnVuY3Rpb24sIHdoaWNoIHdyaXRlcyB0aGUgcmVzdWx0cyBvdXQgdG8gYSBjc3YgZmlsZSB0aGF0IHlvdSBjYW4gb3BlbiBpbiBleGNlbC4KCmBgYHtyfQojIyBjcmVhdGUgYSBkaXJlY3RvcnkgZm9yIHRoZSByZXN1bHRzLiBkb24ndCBnaXZlIGEgd2FybmluZyBpZiBpdCBhbHJlYWR5IGV4aXN0cwpkaXIuY3JlYXRlKCJyZXN1bHRzIixzaG93V2FybmluZ3MgPSBGQUxTRSkKd3JpdGUuY3N2KHJlc3VsdHNfYW5ub3RhdGVkLGZpbGU9InR1bW91cl92c19ub3JtYWxfREVTZXFfYW5ub3RhdGVkLmNzdiIscm93Lm5hbWVzPUZBTFNFKQpgYGAKCgoKV2UgaGF2ZSBhbHJlYWR5IHNlZW4gdGhlIHVzZSBvZiBhIGhlYXRtYXAgYXMgYSBxdWFsaXR5IGFzc2Vzc21lbnQgdG9vbCB0byB2aXN1YWxpc2UgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHNhbXBsZXMgaW4gYW4gZXhwZXJpbWVudC4gQW5vdGhlciBjb21tb24gdXNlLWNhc2UgZm9yIHN1Y2ggYSBwbG90IGlzIHRvIHZpc3VhbGlzZSB0aGUgcmVzdWx0cyBvZiBhIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzLgoKSGVyZSB3ZSB3aWxsIHRha2UgdGhlIHRvcCAxMCBnZW5lcyBmcm9tIHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyBhbmQgcHJvZHVjZSBhIGhlYXRtYXAuIFRoZSBkZWZhdWx0IGNvbG91ciBwYWxldHRlIGdvZXMgZnJvbSBsb3cgZXhwcmVzc2lvbiBpbiBibHVlIHRvIGhpZ2ggZXhwcmVzc2lvbiBpbiByZWQsIHdoaWNoIGlzIGEgZ29vZCBhbHRlcm5hdGl2ZSB0byB0aGUgdHJhZGl0aW9uYWwgcmVkL2dyZWVuIGhlYXRtYXBzIHdoaWNoIGFyZSBub3Qgc3VpdGFibGUgZm9yIHRob3NlIHdpdGggZm9ybXMgb2YgY29sb3VyLWJsaW5kbmVzcy4KCmBgYHtyfQpsaWJyYXJ5KHBoZWF0bWFwKQp0b3BfZ2VuZXMgPC0gcmVzdWx0c19vcmRlcmVkJEdlbmVJRFsxOjEwXQoKdnNkIDwtIHZzdChkZHMpCnBoZWF0bWFwKGFzc2F5KHZzZClbdG9wX2dlbmVzLF0pCgoKYGBgCgpUaGUgaGVhdG1hcCBpcyBtb3JlIGluZm9ybWF0aXZlIGlmIHdlIGFkZCBjb2xvdXJzIHVuZGVybmVhdGggdGhlIHNhbXBsZSBkZW5kcm9ncmFtIHRvIGluZGljYXRlIHdoaWNoIHNhbXBsZSBncm91cCBlYWNoIHNhbXBsZSBiZWxvbmdzIHRvLiBUaGlzIHdlIGNhbiBkbyBieSBjcmVhdGluZyBhIGRhdGEgZnJhbWUgY29udGFpbmluZyBtZXRhZGF0YSBmb3IgZWFjaCBvZiB0aGUgc2FtcGxlcyBpbiBvdXIgZGF0YXNldC4gV2l0aCB0aGUgYERFU2VxMmAgd29ya2Zsb3cgd2UgaGF2ZSBhbHJlYWR5IGNyZWF0ZWQgc3VjaCBhIGRhdGEgZnJhbWUuIFdlIGhhdmUgdG8gbWFrZSBzdXJlIHRoZSB0aGUgcm93bmFtZXMgb2YgdGhlIGRhdGEgZnJhbWUgYXJlIHRoZSBzYW1lIGFzIHRoZSBjb2x1bW4gbmFtZXMgb2YgdGhlIGNvdW50cyBtYXRyaXguCgpgYGB7cn0Kc2FtcGxlSW5mbyA8LSBhcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKVssYygiU3RhdHVzIiwiZ2xlYXNvbl9zY29yZSIpXSkKCnBoZWF0bWFwKGFzc2F5KHZzZClbdG9wX2dlbmVzLF0sCiAgICAgICAgIGFubm90YXRpb25fY29sID0gc2FtcGxlSW5mbykKYGBgCgpBbnkgcGxvdCB3ZSBjcmVhdGUgaW4gUlN0dWRpbyBjYW4gYmUgc2F2ZWQgYXMgYSBwbmcgb3IgcGRmIGZpbGUuIFdlIHVzZSB0aGUgYHBuZ2Agb3IgYHBkZmAgZnVuY3Rpb24gdG8gY3JlYXRlIGEgZmlsZSBmb3IgdGhlIHBsb3QgdG8gYmUgc2F2ZWQgaW50byBhbmQgcnVuIHRoZSByZXN0IG9mIHRoZSBjb2RlIGFzIG5vcm1hbC4gVGhlIHBsb3QgZG9lcyBub3QgZ2V0IGRpc3BsYXllZCBpbiBSU3R1ZGlvLCBidXQgcHJpbnRlZCB0byB0aGUgc3BlY2lmaWVkIGZpbGUuIAoKYGBge3J9CgpwbmcoImhlYXRtYXBfdG9wMTBfZ2VuZXMucG5nIix3aWR0aD04MDAsaGVpZ2h0PTgwMCkKcGhlYXRtYXAoYXNzYXkodnNkKVt0b3BfZ2VuZXMsXSwKICAgICAgICAgYW5ub3RhdGlvbl9jb2wgPSBzYW1wbGVJbmZvKQojIGRldi5vZmYoKQpgYGAKCgo+ICMjIENoYWxsZW5nZSAyey5jaGFsbGVuZ2V9Cj4gMS4gUmVwZWF0IHRoZSBzYW1lIGhlYXRtYXAgYXMgYWJvdmUsIGJ1dCBmb3IgdGhlIHRvcCA3NSBtb3N0IGRpZmZlcmVudGlhbGx5LWV4cHJlc3NlZCBnZW5lcyBpbiB0aGUgY29udHJhc3QgYmV0d2VlbiBHbGVhc29uIGdyYWRlcyA5IGFuZCA2Cj4gMi4gU2F2ZSB0aGUgcGxvdCB0byBhIHBkZiBmaWxlCgojIyMgQW5ub3RhdGluZyBwbG90cyB3aXRoIGdlbmUgbmFtZXMKCgpOb3cgdGhhdCB3ZSBoYXZlIGFuIGFubm90YXRlZCB0YWJsZSBvZiByZXN1bHRzLCB3ZSBjYW4gYWRkIHRoZSBnZW5lIG5hbWVzIHRvIHNvbWUgb2YgdGhlIG90aGVyIHBsb3RzIHdlIGhhdmUgY3JlYXRlZC4gVGhpcyBzaG91bGQgYmUgc3RyYWlnaHRmb3J3YXJkIGFzIGdncGxvdDIgaGFzIGEgYGxhYmVsYCBhZXN0aGV0aWMgdGhhdCBjYW4gYmUgbWFwcGVkIHRvIGNvbHVtbnMgaW4gYSBkYXRhIGZyYW1lLiBUaGUgYGdlb21fdGV4dGAgcGxvdCB3aWxsIHRoZW4gZGlzcGxheSB0aGUgbGFiZWxzLiBIb3dldmVyLCB0aGUgZm9sbG93aW5nIHBsb3QgaXMgYSBiaXQgY3Jvd2RlZC4KCmBgYHtyfQojIyBOb3QgYSBnb29kIGlkZWEgdG8gcnVuIHRoaXMhIQpyZXN1bHRzX2Fubm90YXRlZCAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gbG9nMkZvbGRDaGFuZ2UsIHkgPSAtbG9nMTAocGFkaiksIGxhYmVsPUdlbmVJRCkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV90ZXh0KCkKYGBgCgoKVGhlIHByb2JsZW0gaGVyZSBpcyB0aGF0IGdncGxvdDIgaXMgdHJ5aW5nIHRvIGxhYmVsIGV2ZXJ5IHBvaW50IHdpdGggYSBuYW1lOyBub3QgcXVpdGUgd2hhdCB3ZSB3YW50LiBUaGUgdHJpY2sgaXMgdG8gY3JlYXRlIGEgbGFiZWwgdGhhdCBpcyBibGFuayBmb3IgbW9zdCBnZW5lcyBhbmQgb25seSBsYWJlbHMgdGhlIHBvaW50cyB3ZSBhcmUgaW50ZXJlc3RlZCBpbi4gVGhlIGBpZmVsc2VgIGZ1bmN0aW9uIGluIFIgaXMgYSBjb252ZW5pZW50IHdheSB0byBzZXQgdGhlIGVudHJpZXMgaW4gYSB2ZWN0b3IgYmFzZWQgb24gYSAqbG9naWNhbCogZXhwcmVzc2lvbi4gSW4gdGhpcyBjYXNlLCBtYWtlIHRoZSB2YWx1ZXMgaW4gYExhYmVsYCB0aGUgc2FtZSBhcyB0aGUgZ2VuZSBzeW1ib2wgaWYgdGhlIGdlbmUgaXMgaW4gb3VyIGxpc3Qgb2YgInRvcCBnZW5lcyIuIE90aGVyd2lzZSwgcG9pbnRzIGdldCBsYWJlbGVkIHdpdGggYSBibGFuayBzdHJpbmcgYCIiYC4KCkZvciBjbGFyaXR5LCB3ZSBhbHNvIG1ha2UgdGhlIHBvaW50cyBzbGlnaHRseSB0cmFuc3BhcmVudCBhbmQgdXNlIGEgZGlmZmVyZW50IGNvbG91ciBmb3IgdGhlIHRleHQuCgpgYGB7cn0KTiA8LSAxMAp0b3BfZ2VuZXMgPC0gcmVzdWx0c19hbm5vdGF0ZWQkRU5TRU1CTFsxOk5dCnJlc3VsdHNfYW5ub3RhdGVkICU+JSAKICBtdXRhdGUoTGFiZWwgPSBpZmVsc2UoRU5TRU1CTCAlaW4lIHRvcF9nZW5lcywgR2VuZUlELCAiIikpICU+JSAgCiAgZ2dwbG90KGFlcyh4ID0gbG9nMkZvbGRDaGFuZ2UsIHkgPSAtbG9nMTAocGFkaiksIGxhYmVsPUxhYmVsKSkgKyBnZW9tX3BvaW50KGFscGhhPTAuNCkgKyBnZW9tX3RleHQoY29sPSJibHVlIikKYGBgCgpGaW5hbGx5LCBhIHNsaWdodGx5IGJldHRlciBwb3NpdGlvbmluZyBvZiB0ZXh0IGlzIGdpdmVuIGJ5IHRoZSBgZ2dyZXBlbGAgcGFja2FnZS4KCmBgYHtyfQppZighcmVxdWlyZShnZ3JlcGVsKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dyZXBlbCIpCgpOIDwtIDEwCnRvcF9nZW5lcyA8LSByZXN1bHRzX2Fubm90YXRlZCRFTlNFTUJMWzE6Tl0KcmVzdWx0c19hbm5vdGF0ZWQgJT4lIAogIG11dGF0ZShMYWJlbCA9IGlmZWxzZShFTlNFTUJMICVpbiUgdG9wX2dlbmVzLCBHZW5lSUQsICIiKSkgJT4lICAKICBnZ3Bsb3QoYWVzKHggPSBsb2cyRm9sZENoYW5nZSwgeSA9IC1sb2cxMChwYWRqKSwgbGFiZWw9TGFiZWwpKSArIGdlb21fcG9pbnQoYWxwaGE9MC40KSArIGdlb21fdGV4dChjb2w9ImJsdWUiKQpgYGAKCiMjIyBBbm5vdGF0aW9uIHdpdGggdGhlIGJpb21hUnQgcmVzb3VyY2UKClRoZSBCaW9jb25kdWN0b3IgcGFja2FnZSBoYXZlIHRoZSBjb252ZW5pZW5jZSBvZiBiZWluZyBhYmxlIHRvIG1ha2UgcXVlcmllcyBvZmZsaW5lLiBIb3dldmVyLCB0aGV5IGFyZSBvbmx5IGF2YWlsYWJsZSBmb3IgY2VydGFpbiBvcmdhbmlzbXMuIElmIHlvdXIgb3JnYW5pc20gZG9lcyBub3QgaGF2ZSBhbiBgb3JnLlhYLmVnLmRiYCBwYWNrYWdlIGxpc3RlZCBvbiB0aGUgQmlvY29uZHVjdG9yIGFubm90YXRpb24gcGFnZSAoaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9CaW9jVmlld3MuaHRtbCNfX19Bbm5vdGF0aW9uRGF0YSksIGFuIGFsdGVybmF0aXZlIGlzIHRvIHVzZSBiaW9tYVJ0IHdoaWNoIHByb3ZpZGVzIGFuIGludGVyZmFjZSB0byB0aGUgcG9wdWxhciBiaW9tYXJ0IGFubm90YXRpb24gcmVzb3VyY2UuIAoKVGhlIGZpcnN0IHN0ZXAgaXMgdG8gZmluZCB0aGUgbmFtZSBvZiBhIGRhdGFiYXNlIHRoYXQgeW91IHdhbnQgdG8gY29ubmVjdCB0bwoKYGBge3J9CmxpYnJhcnkoYmlvbWFSdCkKbGlzdE1hcnRzKCkKZW5zZW1ibD11c2VNYXJ0KCJFTlNFTUJMX01BUlRfRU5TRU1CTCIpCiMgbGlzdCB0aGUgYXZhaWxhYmxlIGRhdGFzZXRzIChzcGVjaWVzKS4gUmVwbGFjZSBodW1hbiB3aXRoIHRoZSBuYW1lIG9mIHlvdXIgb3JnYW5pc20KbGlzdERhdGFzZXRzKGVuc2VtYmwpICU+JSBmaWx0ZXIoZ3JlcGwoIkh1bWFuIixkZXNjcmlwdGlvbikpCgpgYGAKCmBgYHtyfQplbnNlbWJsID0gdXNlRGF0YXNldCgiaHNhcGllbnNfZ2VuZV9lbnNlbWJsIiwgbWFydD1lbnNlbWJsKQpgYGAKClF1ZXJpZXMgdG8gYGJpb21hUnRgIGFyZSBjb25zdHJ1Y3RlZCBpbiBhIHNpbWlsYXIgd2F5IHRvIHRoZSBxdWVyaWVzIHdlIHBlcmZvcm1lZCB3aXRoIHRoZSBgb3JnLk1tLmVnLmRiYCBwYWNrYWdlLiBJbnN0ZWFkIG9mIGBrZXlzYCB3ZSBoYXZlIGBmaWx0ZXJzYCwgYW5kIGluc3RlYWQgb2YgYGNvbHVtbnNgIHdlIGhhdmUgYXR0cmlidXRlcy4gVGhlIGxpc3Qgb2YgYWNjZXB0YWJsZSB2YWx1ZXMgaXMgbXVjaCBtb3JlIGNvbXByZWhlbnNpdmUgdGhhdCBmb3IgdGhlIGBvcmcuTW0uZWcuZGJgIHBhY2thZ2UuCgpgYGB7cn0KbGlzdEZpbHRlcnMoZW5zZW1ibCkgJT4lIAogICAgZmlsdGVyKGdyZXBsKCJzeW1ib2wiLG5hbWUpKQpgYGAKCgpgYGB7cn0KbGlzdEF0dHJpYnV0ZXMoZW5zZW1ibCkgJT4lIAogICAgZmlsdGVyKGdyZXBsKCJkZXNjcmlwdGlvbiIsbmFtZSkpCmBgYAoKQW4gYWR2YW50YWdlIG92ZXIgdGhlIGBvcmcuLmAgcGFja2FnZXMgaXMgdGhhdCBwb3NpdGlvbmFsIGluZm9ybWF0aW9uIGNhbiBiZSByZXRyaWV2ZWQKCmBgYHtyfQphdHRyaWJ1dGVOYW1lcyA8LSBjKCdlbnNlbWJsX2dlbmVfaWQnLCAnZW50cmV6Z2VuZV9pZCcsJ2Rlc2NyaXB0aW9uJykKCmdldEJNKGF0dHJpYnV0ZXMgPSBhdHRyaWJ1dGVOYW1lcywKICAgICAgZmlsdGVycyA9ICJoZ25jX3N5bWJvbCIsCiAgICAgIHZhbHVlcz10b3BfZ2VuZXMsCiAgICAgIG1hcnQ9ZW5zZW1ibCkKYGBgCgo+ICMjIENoYWxsZW5nZSAzey5jaGFsbGVuZ2V9Cj4gMS4gVXNlIGJpb21hUnQgdG8gY3JlYXRlIGFuIGRhdGEgZnJhbWUgY29udGFpbmluZyB0aGUgZW50cmV6Z2VuZSwgRW5zZW1ibCBJRCBhbmQgZ2Vub21pYyBjb29yZGluYXRlcyAoY2hyb21vc29tZSwgc3RhcnQsIGVuZCkgZm9yIHRoZSBFbnNlbWJsIElEcyBpbiB0aGUgREVTZXEyIHJlc3VsdHMKPiAyLiBSZW1vdmUgZHVwbGljYXRlcyBlbnRyaWVzIGZyb20gdGhlIG5ldyBkYXRhIGZyYW1lCj4gMy4gSm9pbiB0aGUgYmlvbWFSdCBhbm5vdGF0aW9uIHRvIHRoZSBERVNlcTIgcmVzdWx0cyB0byBwcm9kdWNlIGEgZGF0YSBmcmFtZSB3aXRoIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIHJlc3VsdHMgYW5kIGFubm90YXRpb24KPiA0LiBXcml0ZSB0aGUgam9pbmVkIGRhdGEgZnJhbWUgdG8gYSBjc3YgZmlsZQoKYGBge3J9CgpgYGAKCiMjIyBJbnRlcmFjdGl2ZSBncmFwaHMgYW5kIHRhYmxlcwoKSXQgaXMgb2Z0ZW4gdXNlZnVsIHRvIGJlIGFibGUgdG8gZXhwbG9yZSBvdXIgcmVzdWx0cyBpbiBhbiBpbnRlcmFjdGl2ZSBtYW5uZXI7IHNlYXJjaGluZyBmb3Igb3VyIGZhdm91cml0ZSBnZW5lcyBvZiBpbnRlcmVzdCBhbmQgcGxvdHRpbmcgb24tdGhlLWZseSB3aGV0aGVyIHRoZXkgYXJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgaW4gb3VyIGRhdGFzZXQgb3Igbm90LgoKU3VjaCBhIHZpc3VhbGlzYXRpb24gaXMgcG9zc2libGUgd2l0aCB0aGUgW0dsaW1tYV0oaHR0cHM6Ly9hY2FkZW1pYy5vdXAuY29tL2Jpb2luZm9ybWF0aWNzL2FydGljbGUtbG9va3VwL2RvaS8xMC4xMDkzL2Jpb2luZm9ybWF0aWNzL2J0eDA5NCkgQmlvY29uZHVjdG9yIHBhY2thZ2UuIAoKSXQgdGFrZXMgb3VyIGBERVNlcTJgIHJlc3VsdHMgb2JqZWN0LCBhbm5vdGF0aW9uIHRhYmxlIGFuZCBub3JtYWxpemVkIGNvdW50cywgYW5kIHByb2R1Y2VzIGEgSFRNTCBwYWdlIGluY2x1ZGluZyBhIHNvcnRhYmxlIHJlc3VsdHMgdGFibGUsIE1BLXBsb3QgYW5kIHNjYXR0ZXIgcGxvdC4gUGFydGljdWxhciBnZW5lcyBjYW4gYmUgc2VhcmNoZWQgYW1vbmcgdGhlIHRhYmxlIGFuZCB0aGVpciBleHByZXNzaW9uIHBhdHRlcm5zIGNhbiBiZSBkaXNwbGF5ZWQuIEFsdGVybmF0aXZlbHkgd2UgY2FuIGNsaWNrIG9uIHBhcnRpY3VsYXIgcG9pbnQgaW4gdGhlIHBsb3QgYW5kIGRpc3BsYXkgdGhlaXIgc3RhdHMuCgoKCmBgYHtyfQoKIyMgUmVwZWF0IHRoZSBhbm5vdGF0aW9uLCBhcyB0aGUgcHJldmlvdXMgYW5ub3RhdGlvbiB0YWJsZSB3YXMgY3JlYXRlZCB1c2luZyBhbiBvcmRlcmVkIHJlc3VsdHMgdGFibGUKCmFubm8gPC0gQW5ub3RhdGlvbkRiaTo6c2VsZWN0KG9yZy5Icy5lZy5kYixrZXlzPXJvd25hbWVzKGRkcyksCiAgICAgICAgICAgICAgY29sdW1ucz1jKCJTWU1CT0wiLCJHRU5FTkFNRSIsIkVOU0VNQkwiKSwKICAgICAgICAgICAgICBrZXl0eXBlPSJTWU1CT0wiKSAlPiUgCiAgZHBseXI6OnJlbmFtZShHZW5lSUQgPSBTWU1CT0wpICU+JSAKICBmaWx0ZXIoIWR1cGxpY2F0ZWQoR2VuZUlEKSkKCmBgYAoKYGBge3J9CiMjIE1ha2Ugc3VyZSB3ZSBoYXZlIG5vcm1hbGlzZWQgY291bnRzIGJlZm9yZSBwcm9jZWVkaW5nCmRkcyA8LSBlc3RpbWF0ZVNpemVGYWN0b3JzKGRkcykKYGBgCgpgYGB7cn0KIyMgTG9hZCB0aGUgR2xpbW1hIHBhY2thZ2UgYW5kIGNyZWF0ZSB0aGUgcmVwb3J0CmxpYnJhcnkoR2xpbW1hKQpnbE1EUGxvdChkZV9zdGF0dXMsCiAgICAgICAgIGFubm8sCiAgICAgICAgIGdyb3VwcyA9IGNvbERhdGEoZGRzKSRTdGF0dXMsCiAgICAgICAgIGNvdW50cyA9IGNvdW50cyhkZHMsbm9ybWFsaXplZD1UUlVFKSwKICAgICAgICAgdHJhbnNmb3JtID0gVFJVRSwKICAgICAgICAgc2lkZS5tYWluID0gIkdlbmVJRCIpCmBgYAoKCg==