Original Authors: Belinda Phipson, Anna Trigos, Matt Ritchie, Maria Doyle, Harriet Dashnow, Charity Law Based on the course RNAseq analysis in R delivered on May 11/12th 2016

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

suppressPackageStartupMessages(library(edgeR))
load("Robjects/DE.Rdata")

Overview

  • Visualising DE results
  • Getting annotation
  • Retrieving gene models
  • Exporting browser traecks
  • Visualising results with respect to genomic location

We have a list of significantly differentially expressed genes, but the only annotation we can see is the Entrez Gene ID, which is not very informative.

results <- as.data.frame(topTags(lrt.BvsL,n = Inf))
results
dim(results)
[1] 15804     5

edgeR provides a function plotSmear that allows us to visualise the results of a DE analysis. In a similar manner to the MA-plot for microarray data, this plot shows the log-fold change against log-counts per million, with DE genes highlighted:

summary(de <- decideTestsDGE(lrt.BvsL))
   [,1] 
-1  2957
0  11232
1   1615
detags <- rownames(dgeObj)[as.logical(de)]
plotSmear(lrt.BvsL, de.tags=detags)

However, on such a plot it would be nice to add labels to highlight the genes with most evidence for being DE, or our favourite genes. To perform such a task we need to map between the identifiers we have in the edgeR output and more familiar names.

Finally, we will look at sophisticated visualisations that allow us to incorporate information about the structure of a gene, level of sequencing coverage.

Adding annotation to the edgeR results

There are a number of ways to add annotation, but we will demonstrate how to do this using the org.Mm.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.

source("http://www.bioconductor.org/biocLite.R")
biocLite("org.Mm.eg.db")
# For Human
biocLite("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.Mm.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.Mm.eg.db)
 [1] "ACCNUM"       "ALIAS"        "ENSEMBL"      "ENSEMBLPROT"  "ENSEMBLTRANS" "ENTREZID"     "ENZYME"       "EVIDENCE"     "EVIDENCEALL"  "GENENAME"    
[11] "GO"           "GOALL"        "IPI"          "MGI"          "ONTOLOGY"     "ONTOLOGYALL"  "PATH"         "PFAM"         "PMID"         "PROSITE"     
[21] "REFSEQ"       "SYMBOL"       "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.Mm.eg.db)
 [1] "ACCNUM"       "ALIAS"        "ENSEMBL"      "ENSEMBLPROT"  "ENSEMBLTRANS" "ENTREZID"     "ENZYME"       "EVIDENCE"     "EVIDENCEALL"  "GENENAME"    
[11] "GO"           "GOALL"        "IPI"          "MGI"          "ONTOLOGY"     "ONTOLOGYALL"  "PATH"         "PFAM"         "PMID"         "PROSITE"     
[21] "REFSEQ"       "SYMBOL"       "UNIGENE"      "UNIPROT"     

We should see ENTREZID, 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.Mm.eg.db, keytype="ENTREZID")[1:10]
 [1] "11287" "11298" "11302" "11303" "11304" "11305" "11306" "11307" "11308" "11350"

It is a useful sanity check to make sure that the keys you want to use are all valid. We could use %in% in this case.

## Build up the query step-by-step
my.keys <- c("50916", "110308","12293")
my.keys %in% keys(org.Mm.eg.db, keytype="ENTREZID")
[1] TRUE TRUE TRUE
all(my.keys %in% keys(org.Mm.eg.db, keytype="ENTREZID"))
[1] TRUE

Let’s build up the query step by step.

## to be filled-in interactively during the class.
select(org.Mm.eg.db,

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

ann <- select(org.Mm.eg.db,keys=rownames(results),columns=c("ENTREZID","SYMBOL","GENENAME"))
'select()' returned 1:1 mapping between keys and columns
# Have a look at the annotation
ann

Let’s double check that the ENTREZID column matches exactly to our results rownames.

table(ann$ENTREZID==rownames(results))

 TRUE 
15804 

We can bind in the annotation information to the results data frame. (Please note that if the select function returns a 1:many mapping then you can’t just append the annotation to the fit object.)

results.annotated <- cbind(results, ann)
results.annotated

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.

write.csv(results.annotated,file="B.PregVsLacResults.csv",row.names=FALSE)

A note about deciding how many genes are significant: In order to decide which genes are differentially expressed, we usually take a cut-off of 0.05 on the adjusted p-value, NOT the raw p-value. This is because we are testing more than 15000 genes, and the chances of finding differentially expressed genes is very high when you do that many tests. Hence we need to control the false discovery rate, which is the adjusted p-value column in the results table. What this means is that if 100 genes are significant at a 5% false discovery rate, we are willing to accept that 5 will be false positives. Note that the decideTests function displays significant genes at 5% FDR.

Challenge

Re-visit the plotSmear plot from above and use the text function to add labels for the names of the top 200 most DE genes

Another common visualisation is the volcano plot which display a measure of significance on the y-axis and fold-change on the x-axis.

signif <- -log10(results.annotated$FDR)
plot(results.annotated$logFC,signif,pch=16)
points(results.annotated[detags,"logFC"],-log10(results.annotated[detags,"FDR"]),pch=16,col="red")

Before following up on the DE genes with further lab work, a recommended sanity check is to have a look at the expression levels of the individual samples for the genes of interest. We can quickly look at grouped expression using stripchart. We can use the normalised log expression values in the dgeCounts object (dgeCounts$counts).

library(RColorBrewer)
par(mfrow=c(1,3))
normCounts <- dgeObj$counts
# Let's look at the first gene in the topTable, Krt5, which has a rowname 50916
stripchart(normCounts["110308",]~group)
# This plot is ugly, let's make it better
stripchart(normCounts["110308",]~group,vertical=TRUE,las=2,cex.axis=0.8,pch=16,col=1:6,method="jitter")
# Let's use nicer colours
nice.col <- brewer.pal(6,name="Dark2")
stripchart(normCounts["110308",]~group,vertical=TRUE,las=2,cex.axis=0.8,pch=16,cex=1.3,col=nice.col,method="jitter",ylab="Normalised log2 expression",main="    Krt5")

An interactive version of the volcano plot above that includes the raw per sample values in a separate panel is possible via the glXYPlot function in the Glimma package.

library(Glimma)
group2 <- group
levels(group2) <- c("basal.lactate","basal.preg","basal.virgin","lum.lactate", "lum.preg", "lum.virgin")
glXYPlot(x=results$logFC, y=-log10(results$FDR),
         xlab="logFC", ylab="B", main="B.PregVsLac",
         counts=normCounts, groups=group2, status=de,
         anno=ann, id.column="ENTREZID", folder="volcano")

This function creates an html page (./volcano/XY-Plot.html) with a volcano plot on the left and a plot showing the log-CPM per sample for a selected gene on the right. A search bar is available to search for genes of interest.

Retrieving Genomic Locations

It might seem natural to add genomic locations to our annotation table, and possibly a bit odd that the org.Mm.eg.db package does not supply such mappings. In fact, there is a whole suite of package for performing this, and more-advanced queries that relate to the location of genes. These are listed on the Bioconductor annotation page and have the prefix TxDb.

The package we will be using is TxDb.Mmusculus.UCSC.mm10.knownGene. Packages are available for other organisms and genome builds. It is even possible to build your own database if one does not exist. See vignette("GenomicFeatures") for details

source("http://www.bioconductor.org/biocLite.R")
biocLite("TxDb.Mmusculus.UCSC.mm10.knownGene")

## For Humans
biocLite("TxDb.Hsapiens.UCSC.hg19.knownGene")

We load the library in the usual fashion and create a new object to save some typing. As with the org. packages, we can query what columns are available with columns,

library(TxDb.Mmusculus.UCSC.mm10.knownGene)
tx <- TxDb.Mmusculus.UCSC.mm10.knownGene
columns(tx)
 [1] "CDSCHROM"   "CDSEND"     "CDSID"      "CDSNAME"    "CDSSTART"   "CDSSTRAND"  "EXONCHROM"  "EXONEND"    "EXONID"     "EXONNAME"   "EXONRANK"  
[12] "EXONSTART"  "EXONSTRAND" "GENEID"     "TXCHROM"    "TXEND"      "TXID"       "TXNAME"     "TXSTART"    "TXSTRAND"   "TXTYPE"    

The select function is used in the same manner as the org.Mm.eg.db packages.

Challenge

Use the TxDb.Mmusculus.UCSC.mm10.knownGene package to retrieve the exon coordinates for the genes 50916, 110308, 12293

Overview of GenomicRanges

One of the real strengths of the txdb.. packages is the ability of interface with GenomicRanges, which is the object type used throughout Bioconductor to manipulate Genomic Intervals.

These object types permit us to perform common operations on intervals such as overlapping and counting. We can define the chromosome, start and end position of each region (also strand too, but not shown here).

library(GenomicRanges)
simple.range <-GRanges("1", IRanges(start=1000,end=2000))
simple.range
GRanges object with 1 range and 0 metadata columns:
      seqnames       ranges strand
         <Rle>    <IRanges>  <Rle>
  [1]        1 [1000, 2000]      *
  -------
  seqinfo: 1 sequence from an unspecified genome; no seqlengths

We don’t have to have all our ranges located on the same chromosome

chrs <- c("chr13", "chr15","chr5")
start <- c(73000000, 101000000, 15000000)
end <- c(74000000,102000000, 16000000)
my.ranges <- GRanges(rep(chrs,3), 
                     IRanges(start=rep(start,each=3),
                             end = rep(end,each=3))
)

There are a number of useful functions for calculating properties of the data (such as coverage or sorting). Not so much for RNA-seq analysis, but GenomicRanges are used throughout Bioconductor for the analysis of NGS data.

For instance, we can quickly identify overlapping regions between two GenomicRanges. However, we have to pay attention to the naming convention used for each object. seqlevelsStyle can

keys <- c("50916","110308","12293")
genePos <- select(tx, keys=keys,
       keytype = "GENEID",
       columns=c("EXONCHROM","EXONSTART","EXONEND")
      )
'select()' returned 1:many mapping between keys and columns
geneRanges <- GRanges(genePos$EXONCHROM, IRanges(genePos$EXONSTART,genePos$EXONEND), GENEID=genePos$GENEID)
geneRanges
GRanges object with 58 ranges and 1 metadata column:
       seqnames               ranges strand |      GENEID
          <Rle>            <IRanges>  <Rle> | <character>
   [1]    chr13 [73260497, 73260876]      * |       50916
   [2]    chr13 [73264848, 73264979]      * |       50916
   [3]    chr13 [73265458, 73265709]      * |       50916
   [4]    chr13 [73266596, 73266708]      * |       50916
   [5]    chr13 [73267504, 73267832]      * |       50916
   ...      ...                  ...    ... .         ...
  [54]     chr5 [16370558, 16374511]      * |       12293
  [55]     chr5 [16341990, 16342010]      * |       12293
  [56]     chr5 [16326327, 16326383]      * |       12293
  [57]     chr5 [16322539, 16322595]      * |       12293
  [58]     chr5 [16267376, 16268604]      * |       12293
  -------
  seqinfo: 3 sequences from an unspecified genome; no seqlengths
findOverlaps(my.ranges,geneRanges)
Hits object with 16 hits and 0 metadata columns:
       queryHits subjectHits
       <integer>   <integer>
   [1]         1           1
   [2]         1           2
   [3]         1           3
   [4]         1           4
   [5]         1           5
   ...       ...         ...
  [12]         5          12
  [13]         5          13
  [14]         5          14
  [15]         5          15
  [16]         9          16
  -------
  queryLength: 9 / subjectLength: 58
seqlevelsStyle(geneRanges)
[1] "UCSC"
seqlevelsStyle(simple.range)
[1] "NCBI"    "Ensembl" "MSU6"    "AGPvF"  

Retrieving Gene Coordinates as GenomicRanges

As we saw above, it is quite straightforward to translate the output of a select query into a GenomicFeatures object. However, several convenience functions exist to retrieve the structure of every gene for a given organism in one object.

The output of exonsBy is a list, where each item in the list is the exon co-ordinates of a particular gene.

exo <- exonsBy(tx,"gene")
exo
GRangesList object of length 24116:
$100009600 
GRanges object with 7 ranges and 2 metadata columns:
      seqnames               ranges strand |   exon_id   exon_name
         <Rle>            <IRanges>  <Rle> | <integer> <character>
  [1]     chr9 [21062393, 21062717]      - |    134539        <NA>
  [2]     chr9 [21062894, 21062987]      - |    134540        <NA>
  [3]     chr9 [21063314, 21063396]      - |    134541        <NA>
  [4]     chr9 [21066024, 21066377]      - |    134542        <NA>
  [5]     chr9 [21066940, 21067925]      - |    134543        <NA>
  [6]     chr9 [21068030, 21068117]      - |    134544        <NA>
  [7]     chr9 [21073075, 21075496]      - |    134546        <NA>

$100009609 
GRanges object with 6 ranges and 2 metadata columns:
      seqnames               ranges strand | exon_id exon_name
  [1]     chr7 [84940169, 84941088]      - |  109989      <NA>
  [2]     chr7 [84943141, 84943264]      - |  109990      <NA>
  [3]     chr7 [84943504, 84943722]      - |  109991      <NA>
  [4]     chr7 [84946200, 84947000]      - |  109992      <NA>
  [5]     chr7 [84947372, 84947651]      - |  109993      <NA>
  [6]     chr7 [84963816, 84964009]      - |  109994      <NA>

$100009614 
GRanges object with 1 range and 2 metadata columns:
      seqnames               ranges strand | exon_id exon_name
  [1]    chr10 [77711446, 77712009]      + |  143986      <NA>

...
<24113 more elements>
-------
seqinfo: 66 sequences (1 circular) from mm10 genome

To access the structure of a particular gene, we can use the [[ syntax with the name of the gene (Entrez gene ID) within quote marks. If we wanted to whole region that the gene spans we could use the range function.

range(exo[["110308"]])
GRanges object with 1 range and 0 metadata columns:
      seqnames                 ranges strand
         <Rle>              <IRanges>  <Rle>
  [1]    chr15 [101707070, 101712891]      -
  -------
  seqinfo: 66 sequences (1 circular) from mm10 genome

Exporting tracks

It is also possible to save the results of a Bioconductor analysis in a browser to enable interactive analysis and integration with other data types, or sharing with collaborators. For instance, we might want a browser track to indicate where our differentially-expressed genes are located. We shall use the bed format to display these locations. We will annotate the ranges with information from our analysis such as the fold-change and significance.

First we create a data frame for just the DE genes.

sigGenes <- results.annotated[detags,]
sigGenes

At the moment, we have a GenomicFeatures object that represents every exon. However, we do not need this level of granularity for the bed output, so we will collapse to a single region for each gene. First we the range function to obtain a single range for every gene and tranform to a more convenient object with unlist.

exoRanges <- unlist(range(exo))
sigRegions <- exoRanges[na.omit(match(sigGenes$ENTREZID, names(exoRanges)))]
sigRegions
GRanges object with 4393 ranges and 0 metadata columns:
         seqnames                 ranges strand
            <Rle>              <IRanges>  <Rle>
  497097     chr1     [3214482, 3671498]      -
   20671     chr1     [4490928, 4497354]      -
   58175     chr1     [4909576, 5070285]      -
   76187     chr1     [9548046, 9577968]      +
   72481     chr1     [9560833, 9631092]      -
     ...      ...                    ...    ...
  195727     chrX [161836430, 162159441]      -
  108012     chrX [163909017, 163933666]      +
   56078     chrX [163976822, 164028010]      -
   54156     chrX [166523007, 166585716]      -
  333605     chrX [167471306, 168577233]      -
  -------
  seqinfo: 66 sequences (1 circular) from mm10 genome

Rather than just representing the genomic locations, the .bed format is also able to colour each range according to some property of the analysis (e.g. direction and magnitude of change) to help highlight particular regions of interest. A score can also be displayed when a particular region is clicked-on. A useful propery of GenomicRanges is that we can attach metadata to each range using the mcols function. The metadata can be supplied in the form of a data frame.

mcols(sigRegions) <- sigGenes[match(names(sigRegions), rownames(sigGenes)),]
sigRegions
GRanges object with 4393 ranges and 8 metadata columns:
         seqnames                 ranges strand |      logFC     logCPM        LR       PValue          FDR    ENTREZID        SYMBOL
            <Rle>              <IRanges>  <Rle> |  <numeric>  <numeric> <numeric>    <numeric>    <numeric> <character>   <character>
  497097     chr1     [3214482, 3671498]      - | -10.947240  2.5236515 23.590694 1.191624e-06 0.0004377961      497097          Xkr4
   20671     chr1     [4490928, 4497354]      - |  -2.673131  1.2418640 10.587241 1.138708e-03 0.0078553551       20671         Sox17
   58175     chr1     [4909576, 5070285]      - |   4.471434  1.1240115 14.373441 1.499018e-04 0.0020196485       58175         Rgs20
   76187     chr1     [9548046, 9577968]      + |   3.033003  2.4071013  8.475630 3.599356e-03 0.0182496716       76187        Adhfe1
   72481     chr1     [9560833, 9631092]      - |   2.136618 -0.2472752  6.137581 1.323382e-02 0.0468231045       72481 2610203C22Rik
     ...      ...                    ...    ... .        ...        ...       ...          ...          ...         ...           ...
  195727     chrX [161836430, 162159441]      - |  -4.692618  3.1077765 13.912120 1.915593e-04 0.0023438662      195727           Nhs
  108012     chrX [163909017, 163933666]      + |  -2.176996  2.3209830 10.201704 1.403109e-03 0.0091706934      108012         Ap1s2
   56078     chrX [163976822, 164028010]      - |   2.588361  5.1331379  6.923093 8.508968e-03 0.0338900535       56078         Car5b
   54156     chrX [166523007, 166585716]      - |  -6.964350  4.3915759 19.444204 1.035817e-05 0.0005205134       54156         Egfl6
  333605     chrX [167471306, 168577233]      - |  -3.896071  0.6561936  6.917891 8.533757e-03 0.0339270998      333605        Frmpd4
                                                   GENENAME
                                                <character>
  497097                  X-linked Kx blood group related 4
   20671              SRY (sex determining region Y)-box 17
   58175                regulator of G-protein signaling 20
   76187          alcohol dehydrogenase, iron containing, 1
   72481                         RIKEN cDNA 2610203C22 gene
     ...                                                ...
  195727                       Nance-Horan syndrome (human)
  108012 adaptor-related protein complex 1, sigma 2 subunit
   56078               carbonic anhydrase 5b, mitochondrial
   54156                        EGF-like-domain, multiple 6
  333605                   FERM and PDZ domain containing 4
  -------
  seqinfo: 66 sequences (1 circular) from mm10 genome

The metadata we have added can also by used as a means to interrogate the ranges; as if the data were contained in a data frame.

sigRegions[order(sigRegions$LR,decreasing = TRUE)]
GRanges object with 4393 ranges and 8 metadata columns:
         seqnames                 ranges strand |     logFC    logCPM        LR       PValue          FDR    ENTREZID      SYMBOL
            <Rle>              <IRanges>  <Rle> | <numeric> <numeric> <numeric>    <numeric>    <numeric> <character> <character>
  110308    chr15 [101707070, 101712891]      - | -8.940578 10.264297  24.89789 6.044844e-07 0.0004377961      110308        Krt5
   50916    chr13 [ 73260497,  73269620]      + | -8.636503  5.749781  24.80037 6.358512e-07 0.0004377961       50916        Irx4
   12293     chr5 [ 15934691,  16374511]      + | -8.362247  6.794788  24.68526 6.749827e-07 0.0004377961       12293    Cacna2d1
   56069    chr18 [ 61687915,  61692537]      + | -8.419433  6.124377  24.41532 7.764861e-07 0.0004377961       56069       Il17b
   24117    chr10 [121034004, 121100642]      + | -9.290691  6.757163  24.32506 8.137331e-07 0.0004377961       24117        Wif1
     ...      ...                    ...    ... .       ...       ...       ...          ...          ...         ...         ...
   75723     chr9 [ 14541967,  14643529]      - | -1.373919  8.545179  5.988178   0.01440207   0.04982713       75723      Amotl1
  238384    chr12 [102129419, 102267091]      + | -3.283021 -2.062221  5.986796   0.01441336   0.04984490      238384     Slc24a4
   72543     chr2 [ 33729956,  33887946]      - | -1.146311  5.243682  5.986777   0.01441351   0.04984490       72543      Mvb12b
  268301    chr10 [ 59221922,  59226433]      + |  1.393078  4.386359  5.984774   0.01442989   0.04989062      268301      Sowahc
   20931     chr2 [ 26916421,  26920170]      + | -1.132158  4.526824  5.981059   0.01446032   0.04998488       20931       Surf2
                                                                        GENENAME
                                                                     <character>
  110308                                                               keratin 5
   50916                                Iroquois related homeobox 4 (Drosophila)
   12293              calcium channel, voltage-dependent, alpha2/delta subunit 1
   56069                                                         interleukin 17B
   24117                                                 Wnt inhibitory factor 1
     ...                                                                     ...
   75723                                                       angiomotin-like 1
  238384 solute carrier family 24 (sodium/potassium/calcium exchanger), member 4
   72543                                         multivesicular body subunit 12B
  268301                        sosondowah ankyrin repeat domain family member C
   20931                                                          surfeit gene 2
  -------
  seqinfo: 66 sequences (1 circular) from mm10 genome

For visualisation purposes, we are going to restrict the data to genes that are located on chromosomes 1 to 19 and the sex chromosomes. This can be done with the keepSeqLevels function.

seqlevels(sigRegions)
 [1] "chr1"                 "chr2"                 "chr3"                 "chr4"                 "chr5"                 "chr6"                
 [7] "chr7"                 "chr8"                 "chr9"                 "chr10"                "chr11"                "chr12"               
[13] "chr13"                "chr14"                "chr15"                "chr16"                "chr17"                "chr18"               
[19] "chr19"                "chrX"                 "chrY"                 "chrM"                 "chr1_GL456210_random" "chr1_GL456211_random"
[25] "chr1_GL456212_random" "chr1_GL456213_random" "chr1_GL456221_random" "chr4_GL456216_random" "chr4_GL456350_random" "chr4_JH584292_random"
[31] "chr4_JH584293_random" "chr4_JH584294_random" "chr4_JH584295_random" "chr5_GL456354_random" "chr5_JH584296_random" "chr5_JH584297_random"
[37] "chr5_JH584298_random" "chr5_JH584299_random" "chr7_GL456219_random" "chrX_GL456233_random" "chrY_JH584300_random" "chrY_JH584301_random"
[43] "chrY_JH584302_random" "chrY_JH584303_random" "chrUn_GL456239"       "chrUn_GL456359"       "chrUn_GL456360"       "chrUn_GL456366"      
[49] "chrUn_GL456367"       "chrUn_GL456368"       "chrUn_GL456370"       "chrUn_GL456372"       "chrUn_GL456378"       "chrUn_GL456379"      
[55] "chrUn_GL456381"       "chrUn_GL456382"       "chrUn_GL456383"       "chrUn_GL456385"       "chrUn_GL456387"       "chrUn_GL456389"      
[61] "chrUn_GL456390"       "chrUn_GL456392"       "chrUn_GL456393"       "chrUn_GL456394"       "chrUn_GL456396"       "chrUn_JH584304"      
sigRegions <- keepSeqlevels(sigRegions, paste0("chr", c(1:19,"X","Y")))

We will now create a score from the p-values that will displayed under each region, and colour scheme for the regions based on the fold-change. For the score we can use the \(-log_{10}\) of the adjusted p-value as before

Score <- -log10(sigRegions$FDR)

colorRampPalette is a useful function in base R for constructing a palette between two extremes. When choosing colour palettes, make sure they are colour blind friendly. The red / green colour scheme traditionally-applied to microarrays is a bad choice.

We will also truncate the fold-changes to between -5 and 5 to and divide this range into 10 equal bins

rbPal <-colorRampPalette(c("red", "blue"))
logfc <- pmax(sigRegions$logFC, -5)
logfc <- pmin(logfc , 5)
Col <- rbPal(10)[as.numeric(cut(logfc, breaks = 10))]

The colours and score have to be saved in the GRanges object as score and itemRgb columns respectively, and will be used to construct the browser track. The rtracklayer package can be used to import and export browsers tracks.

Now we can export the signifcant results from the DE analysis as a .bed track using rtracklayer. You can load the resulting file in IGV, if you wish.

mcols(sigRegions)$score <- Score
mcols(sigRegions)$itemRgb <- Col
sigRegions
GRanges object with 4392 ranges and 10 metadata columns:
         seqnames                 ranges strand |      logFC     logCPM        LR         PValue          FDR    ENTREZID        SYMBOL
            <Rle>              <IRanges>  <Rle> |  <numeric>  <numeric> <numeric>      <numeric>    <numeric> <character>   <character>
  497097     chr1     [3214482, 3671498]      - | -10.947240  2.5236515 23.590694 0.000001191624 0.0004377961      497097          Xkr4
   20671     chr1     [4490928, 4497354]      - |  -2.673131  1.2418640 10.587241 0.001138708102 0.0078553551       20671         Sox17
   58175     chr1     [4909576, 5070285]      - |   4.471434  1.1240115 14.373441 0.000149901775 0.0020196485       58175         Rgs20
   76187     chr1     [9548046, 9577968]      + |   3.033003  2.4071013  8.475630 0.003599356266 0.0182496716       76187        Adhfe1
   72481     chr1     [9560833, 9631092]      - |   2.136618 -0.2472752  6.137581 0.013233822204 0.0468231045       72481 2610203C22Rik
     ...      ...                    ...    ... .        ...        ...       ...            ...          ...         ...           ...
  195727     chrX [161836430, 162159441]      - |  -4.692618  3.1077765 13.912120  0.00019155930 0.0023438662      195727           Nhs
  108012     chrX [163909017, 163933666]      + |  -2.176996  2.3209830 10.201704  0.00140310913 0.0091706934      108012         Ap1s2
   56078     chrX [163976822, 164028010]      - |   2.588361  5.1331379  6.923093  0.00850896813 0.0338900535       56078         Car5b
   54156     chrX [166523007, 166585716]      - |  -6.964350  4.3915759 19.444204  0.00001035817 0.0005205134       54156         Egfl6
  333605     chrX [167471306, 168577233]      - |  -3.896071  0.6561936  6.917891  0.00853375663 0.0339270998      333605        Frmpd4
                                                   GENENAME     score     itemRgb
                                                <character> <numeric> <character>
  497097                  X-linked Kx blood group related 4  3.358728     #FF0000
   20671              SRY (sex determining region Y)-box 17  2.104834     #C60038
   58175                regulator of G-protein signaling 20  2.694724     #0000FF
   76187          alcohol dehydrogenase, iron containing, 1  1.738745     #1C00E2
   72481                         RIKEN cDNA 2610203C22 gene  1.329540     #3800C6
     ...                                                ...       ...         ...
  195727                       Nance-Horan syndrome (human)  2.630067     #FF0000
  108012 adaptor-related protein complex 1, sigma 2 subunit  2.037598     #C60038
   56078               carbonic anhydrase 5b, mitochondrial  1.469928     #3800C6
   54156                        EGF-like-domain, multiple 6  3.283568     #FF0000
  333605                   FERM and PDZ domain containing 4  1.469453     #E2001C
  -------
  seqinfo: 21 sequences from mm10 genome
library(rtracklayer)
export(sigRegions , con = "topHits.bed")

Extracting Reads

As we have been using counts as our starting point, we haven’t investigated the aligned reads from our experiment, and how they are represented. As you may be aware, aligned reads are usually stored in a bam file that can be manipulated with open-source command-line tools such as samtools and picard. Bioconductor provide a low-level interface to bam/sam files in the form of the Rsamtools package. The GenomicAlignments package can also be used to retrieve the reads mapping to a particular genomic region in an efficient manner.

library(GenomicAlignments)

In the directory bam there should be .bam files for each of the samples in the example study. The workflow to produce these files is described in a supplmentary page for the course. In brief, the raw reads (fastq) were downloaded from the Short Read Archive (SRA) and aligned with bowtie2. Each bam file was named according to the file name in SRA, but we have renamed the files according to their name in the study. An index file (.bai) has been generated for each bam file.

list.files("bam/")
 [1] "MCL1.DG.bam"               "MCL1.DG.bam.bai"           "MCL1.DH.bam"               "MCL1.DH.bam.bai"          
 [5] "MCL1.DI.bam"               "MCL1.DI.bam.bai"           "MCL1.DJ.bam"               "MCL1.DJ.bam.bai"          
 [9] "MCL1.DK.bam"               "MCL1.DK.bam.bai"           "MCL1.DL.bam"               "MCL1.DL.bam.bai"          
[13] "MCL1.LA.bam"               "MCL1.LA.bam.bai"           "MCL1.LB.bam"               "MCL1.LB.bam.bai"          
[17] "MCL1.LC.bam"               "MCL1.LC.bam.bai"           "MCL1.LD.bam"               "MCL1.LD.bam.bai"          
[21] "MCL1.LE.bam"               "MCL1.LE.bam.bai"           "MCL1.LF.bam"               "MCL1.LF.bam.bai"          
[25] "SRR1552444.sorted.bam"     "SRR1552444.sorted.bam.bai" "SRR1552445.sorted.bam"     "SRR1552445.sorted.bam.bai"
[29] "SRR1552446.sorted.bam"     "SRR1552446.sorted.bam.bai" "SRR1552447.sorted.bam"     "SRR1552447.sorted.bam.bai"
[33] "SRR1552448.sorted.bam"     "SRR1552448.sorted.bam.bai" "SRR1552449.sorted.bam"     "SRR1552449.sorted.bam.bai"
[37] "SRR1552450.sorted.bam"     "SRR1552450.sorted.bam.bai" "SRR1552451.sorted.bam"     "SRR1552451.sorted.bam.bai"
[41] "SRR1552452.sorted.bam"     "SRR1552452.sorted.bam.bai" "SRR1552453.sorted.bam"     "SRR1552453.sorted.bam.bai"
[45] "SRR1552454.sorted.bam"     "SRR1552454.sorted.bam.bai" "SRR1552455.sorted.bam"     "SRR1552455.sorted.bam.bai"

The readGAlignments function provides a simple interface to interrogate the aligned reads for a particular sample. It can also utilise the index file in order to retrieve only the reads that correspond to a specific region in an efficient manner. The output includes the genomic location of each aligned read and the CIGAR (Compact Idiosyncratic Gapped Alignment Report); where M denotes an match to the genome and I, D correspond to insertions and deletions.

my.reads <- readGAlignments(file="bam/MCL1.DG.bam",
                       param=ScanBamParam(which=generegion))
my.reads
GAlignments object with 46633 alignments and 0 metadata columns:
          seqnames strand       cigar    qwidth     start       end     width     njunc
             <Rle>  <Rle> <character> <integer> <integer> <integer> <integer> <integer>
      [1]    chr15      +        100M       100 101707065 101707164       100         0
      [2]    chr15      +        100M       100 101707065 101707164       100         0
      [3]    chr15      +        100M       100 101707066 101707165       100         0
      [4]    chr15      +        100M       100 101707066 101707165       100         0
      [5]    chr15      +        100M       100 101707067 101707166       100         0
      ...      ...    ...         ...       ...       ...       ...       ...       ...
  [46629]    chr15      +        100M       100 101712863 101712962       100         0
  [46630]    chr15      +        100M       100 101712864 101712963       100         0
  [46631]    chr15      -        100M       100 101712876 101712975       100         0
  [46632]    chr15      -        100M       100 101712883 101712982       100         0
  [46633]    chr15      +        100M       100 101712887 101712986       100         0
  -------
  seqinfo: 66 sequences from an unspecified genome

It is possible to tweak the function to retrieve other potentially-useful information from the bam file, such as the mapping quality and flag.

my.reads <- readGAlignments(file="bam/MCL1.DG.bam",
                       param=ScanBamParam(which=generegion,
                                          what=c("seq","mapq","flag")))
my.reads
GAlignments object with 46633 alignments and 3 metadata columns:
          seqnames strand       cigar    qwidth     start       end     width     njunc |                     seq      mapq      flag
             <Rle>  <Rle> <character> <integer> <integer> <integer> <integer> <integer> |          <DNAStringSet> <integer> <integer>
      [1]    chr15      +        100M       100 101707065 101707164       100         0 | TTGTTTTATT...GTTCTGCTTT        40         0
      [2]    chr15      +        100M       100 101707065 101707164       100         0 | TTTTTTTATT...GTTCTGCTTT        23         0
      [3]    chr15      +        100M       100 101707066 101707165       100         0 | TTTTTTATTA...TTCTGCTTTG        24         0
      [4]    chr15      +        100M       100 101707066 101707165       100         0 | TTTTTTATTA...TTCTGCTTTG        24         0
      [5]    chr15      +        100M       100 101707067 101707166       100         0 | TTTTTATTAT...TCTGCTTTGG        40         0
      ...      ...    ...         ...       ...       ...       ...       ...       ... .                     ...       ...       ...
  [46629]    chr15      +        100M       100 101712863 101712962       100         0 | GCGAGGTCAG...GGCAGAGGAG        42         0
  [46630]    chr15      +        100M       100 101712864 101712963       100         0 | CGAGGTCAGC...GCAGAGGAGC        42         0
  [46631]    chr15      -        100M       100 101712876 101712975       100         0 | CGTTCAACAG...TCGAGCTGTG        42        16
  [46632]    chr15      -        100M       100 101712883 101712982       100         0 | CAGGACGCTG...GTGAATGCTT        42        16
  [46633]    chr15      +        100M       100 101712887 101712986       100         0 | GCGCTGTGGG...ATGATTAGTG        42         0
  -------
  seqinfo: 66 sequences from an unspecified genome

The flag can represent useful QC information. e.g.

  • Read is unmapped
  • Read is paired / unpaired
  • Read failed QC
  • Read is a PCR duplicate (see later)

The combination of any of these properties is used to derive a numeric value, as illustrated in this useful resource

Particular attributes of the reads can be extracted and visualised

However, there are more-sophisticated visualisation options for aligned reads and range data. We will use the ggbio package, which first requires some discussion of the ggplot2 plotting package.

Brief Introduction to ggplot2

The ggplot2 package has emerged as an attractive alternative to the traditional plots provided by base R. A full overview of all capabilities of the package is available from the cheatsheet.

A simple scatter plot, equivalent to plotSmear from before, can be generated as follows:-

library(ggplot2)
ggplot(results, aes(x = logCPM, y=logFC)) + geom_point() 

In brief:-

  • results is our data frame containing the variables we wish to plot
  • aes creates a mpping between the variables in our data frame to the aesthetic proprties of the plot
    • the x-axis is mapped to logCPM, y-axis is mapped to logFC
  • geom_point specifies the particular type of plot we want (in this case a scatter plot)

The real advantage of ggplot2 is the ability to change the appearance of our plot by mapping other variables to aspects of the plot. For example, we could colour the points based on a p-value cut-off. The colours are automatically chosen by ggplot2, but we can specifiy particular values.

ggplot(results, aes(x = logCPM, y=logFC,col=FDR < 0.05)) + geom_point()

ggplot(results, aes(x = logCPM, y=logFC,col=FDR < 0.05)) + geom_point(alpha=0.4) + scale_colour_manual(values=c("black","red"))

The volcano plot can be constructed in a similar manner

ggplot(results, aes(x = logFC, y=-log10(FDR))) + geom_point()

Composing plots with ggbio

We will now take a brief look at one of the visualisation packages in Bioconductor that takes advantage of the GenomicRanges and GenomicFeatures object-types. In this section we will show a worked example of how to combine several types of genomic data on the same plot. The documentation for ggbio is very extensive and contains lots of examples.

http://www.tengfei.name/ggbio/docs/

The Gviz package is another Bioconductor package that specialising in genomic visualisations, but we will not explore this package in the course.

The Manhattan plot is a common way of visualising genome-wide results, especially when one is concerned with the results of a GWAS study and identifying strongly-associated hits.

The profile is supposed to resemble the Manhattan skyline with particular skyscrapers towering about the lower level buildings.

This type of plot is implemented as the plotGrandLinear function. We have to supply a value to display on the y-axis using the aes function, which is inherited from ggplot2. The positioning of points on the x-axis is handled automatically by ggbio, using the ranges information to get the genomic coordinates of the ranges of interest.

To stop the plots from being too cluttered we will consider the top 200 genes only.

library(ggbio)
top200 <- sigRegions[order(sigRegions$LR,decreasing = TRUE)[1:200]]
plotGrandLinear(top200 , aes(y = logFC))
using coord:genome to parse x scale

ggbio has alternated the colours of the chromosomes. However, an appealing feature of ggplot2 is the ability to map properties of your plot to variables present in your data. For example, we could create a variable to distinguish between up- and down-regulated genes. The variables used for aesthetic mapping must be present in the mcols section of your ranges object.

mcols(top200)$UpRegulated <- mcols(top200)$logFC > 0
plotGrandLinear(top200, aes(y = logFC, col = UpRegulated))
using coord:genome to parse x scale

plotGrandLinear is a special function in ggbio with preset options for the manhattan style of plot. More often, users will call the autoplot function and ggbio will choose the most appropriate layout. One such layout is the karyogram.

autoplot(top200,layout="karyogram",aes(color=UpRegulated,
                                       fill=UpRegulated))
Scale for 'x' is already present. Adding another scale for 'x', which will replace the existing scale.
Scale for 'x' is already present. Adding another scale for 'x', which will replace the existing scale.

ggbio is also able to plot the structure of genes according to a particular model represented by a GenomicFeatures object, such as the object we created earlier with the exon coordinates for each gene in the mm10 genome.

autoplot(tx, which=exo[["110308"]])
Parsing transcripts...
Parsing exons...
Parsing cds...
Parsing utrs...
------exons...
------cdss...
------introns...
------utr...
aggregating...
Done
Constructing graphics...

We can even plot the location of sequencing reads if they have been imported using readGAlignments function (or similar). We can also add some flanking region around the gene if we wish.

autoplot(bam , stat = "coverage")
extracting information...
Scale for 'x' is already present. Adding another scale for 'x', which will replace the existing scale.

Like ggplot2, ggbio plots can be saved as objects that can later be modified, or combined together to form more complicated plots. If saved in this way, the plot will only be displayed on a plotting device when we query the object. This strategy is useful when we want to add a common element (such as an ideogram) to a plot composition and don’t want to repeat the code to generate the plot every time.

Challenge

Create tracks to compare the coverage of the gene Krt5 for the samples MCL1.DG, MCL1.DH, MCL1.LA and MCL1.LB

LS0tCnRpdGxlOiAiUk5BLXNlcSBBbmFseXNpcyBpbiBSIgpzdWJ0aXRsZTogIkFubm90YXRpb24gYW5kIFZpc3VhbGlzYXRpb24gb2YgUk5BLXNlcSByZXN1bHRzIgphdXRob3I6ICJTdGVwaGFuZSBCYWxsZXJlYXUsIE1hcmsgRHVubmluZywgT3NjYXIgUnVlZGEsIEFzaGxleSBTYXdsZSIKZGF0ZTogJ2ByIGZvcm1hdChTeXMudGltZSgpLCAiTGFzdCBtb2RpZmllZDogJWQgJWIgJVkiKWAnCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwptaW51dGVzOiAzMDAKbGF5b3V0OiBwYWdlCmJpYmxpb2dyYXBoeTogcmVmLmJpYgotLS0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgoqKk9yaWdpbmFsIEF1dGhvcnM6IEJlbGluZGEgUGhpcHNvbiwgQW5uYSBUcmlnb3MsIE1hdHQgUml0Y2hpZSwgTWFyaWEgRG95bGUsIEhhcnJpZXQgRGFzaG5vdywgQ2hhcml0eSBMYXcqKgpCYXNlZCBvbiB0aGUgY291cnNlIFtSTkFzZXEgYW5hbHlzaXMgaW4gUl0oaHR0cDovL2NvbWJpbmUtYXVzdHJhbGlhLmdpdGh1Yi5pby8yMDE2LTA1LTExLVJOQXNlcS8pIGRlbGl2ZXJlZCBvbiBNYXkgMTEvMTJ0aCAyMDE2CgpCZWZvcmUgc3RhcnRpbmcgdGhpcyBzZWN0aW9uLCB3ZSB3aWxsIG1ha2Ugc3VyZSB3ZSBoYXZlIGFsbCB0aGUgcmVsZXZhbnQgb2JqZWN0cyBmcm9tIHRoZSBEaWZmZXJlbnRpYWwgRXhwcmVzc2lvbiBhbmFseXNpcyBwcmVzZW50LgoKYGBge3J9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KGVkZ2VSKSkKbG9hZCgiUm9iamVjdHMvREUuUmRhdGEiKQpgYGAKCiMgT3ZlcnZpZXcKCi0gVmlzdWFsaXNpbmcgREUgcmVzdWx0cwotIEdldHRpbmcgYW5ub3RhdGlvbgotIFJldHJpZXZpbmcgZ2VuZSBtb2RlbHMKLSBFeHBvcnRpbmcgYnJvd3NlciB0cmFlY2tzCi0gVmlzdWFsaXNpbmcgcmVzdWx0cyB3aXRoIHJlc3BlY3QgdG8gZ2Vub21pYyBsb2NhdGlvbgoKCgpXZSBoYXZlIGEgbGlzdCBvZiBzaWduaWZpY2FudGx5IGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcywgYnV0IHRoZSBvbmx5IGFubm90YXRpb24gd2UgY2FuIHNlZSBpcyB0aGUgRW50cmV6IEdlbmUgSUQsIHdoaWNoIGlzIG5vdCB2ZXJ5IGluZm9ybWF0aXZlLiAKYGBge3J9CnJlc3VsdHMgPC0gYXMuZGF0YS5mcmFtZSh0b3BUYWdzKGxydC5CdnNMLG4gPSBJbmYpKQpyZXN1bHRzCmRpbShyZXN1bHRzKQpgYGAKCmBlZGdlUmAgcHJvdmlkZXMgYSBmdW5jdGlvbiBgcGxvdFNtZWFyYCB0aGF0IGFsbG93cyB1cyB0byB2aXN1YWxpc2UgdGhlIHJlc3VsdHMgb2YgYSBERSBhbmFseXNpcy4gSW4gYSBzaW1pbGFyIG1hbm5lciB0byB0aGUgWypNQS1wbG90KiBmb3IgbWljcm9hcnJheSBkYXRhXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9NQV9wbG90KSwgdGhpcyBwbG90IHNob3dzIHRoZSBsb2ctZm9sZCBjaGFuZ2UgYWdhaW5zdCBsb2ctY291bnRzIHBlciBtaWxsaW9uLCB3aXRoIERFIGdlbmVzIGhpZ2hsaWdodGVkOgoKYGBge3J9CnN1bW1hcnkoZGUgPC0gZGVjaWRlVGVzdHNER0UobHJ0LkJ2c0wpKQpkZXRhZ3MgPC0gcm93bmFtZXMoZGdlT2JqKVthcy5sb2dpY2FsKGRlKV0KcGxvdFNtZWFyKGxydC5CdnNMLCBkZS50YWdzPWRldGFncykKYGBgCkhvd2V2ZXIsIG9uIHN1Y2ggYSBwbG90IGl0IHdvdWxkIGJlIG5pY2UgdG8gYWRkIGxhYmVscyB0byBoaWdobGlnaHQgdGhlIGdlbmVzIHdpdGggbW9zdCBldmlkZW5jZSBmb3IgYmVpbmcgREUsIG9yIG91ciBmYXZvdXJpdGUgZ2VuZXMuIFRvIHBlcmZvcm0gc3VjaCBhIHRhc2sgd2UgbmVlZCB0byBtYXAgYmV0d2VlbiB0aGUgaWRlbnRpZmllcnMgd2UgaGF2ZSBpbiB0aGUgYGVkZ2VSYCBvdXRwdXQgYW5kIG1vcmUgZmFtaWxpYXIgbmFtZXMuCgpGaW5hbGx5LCB3ZSB3aWxsIGxvb2sgYXQgc29waGlzdGljYXRlZCB2aXN1YWxpc2F0aW9ucyB0aGF0IGFsbG93IHVzIHRvIGluY29ycG9yYXRlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBzdHJ1Y3R1cmUgb2YgYSBnZW5lLCBsZXZlbCBvZiBzZXF1ZW5jaW5nIGNvdmVyYWdlLgoKIyMgQWRkaW5nIGFubm90YXRpb24gdG8gdGhlIGVkZ2VSIHJlc3VsdHMKClRoZXJlIGFyZSBhIG51bWJlciBvZiB3YXlzIHRvIGFkZCBhbm5vdGF0aW9uLCBidXQgd2Ugd2lsbCBkZW1vbnN0cmF0ZSBob3cgdG8gZG8gdGhpcyB1c2luZyB0aGUgKm9yZy5NbS5lZy5kYiogcGFja2FnZS4gVGhpcyBwYWNrYWdlIGlzIG9uZSBvZiBzZXZlcmFsICpvcmdhbmlzbS1sZXZlbCogcGFja2FnZXMgd2hpY2ggYXJlIHJlLWJ1aWx0IGV2ZXJ5IDYgbW9udGhzLiBUaGVzZSBwYWNrYWdlcyBhcmUgbGlzdGVkIG9uIHRoZSBbYW5ub3RhdGlvbiBzZWN0aW9uXShodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL0Jpb2NWaWV3cy5odG1sI19fX0Fubm90YXRpb25EYXRhKSBvZiB0aGUgQmlvY29uZHVjdG9yLCBhbmQgYXJlIGluc3RhbGxlZCBpbiB0aGUgc2FtZSB3YXkgYXMgcmVndWxhciBCaW9jb25kdWN0b3IgcGFja2FnZXMuIEFuIGFsdGVybmF0aXZlIGFwcHJvYWNoIGlzIHRvIHVzZSBgYmlvbWFSdGAsIGFuIGludGVyZmFjZSB0byB0aGUgW0Jpb01hcnRdKGh0dHA6Ly93d3cuYmlvbWFydC5vcmcvKSByZXNvdXJjZS4gQmlvTWFydCBpcyBtdWNoIG1vcmUgY29tcHJlaGVuc2l2ZSwgYnV0IHRoZSBvcmdhbmlzbSBwYWNrYWdlcyBmaXQgYmV0dGVyIGludG8gdGhlIEJpb2NvbmR1Y3RvciB3b3JrZmxvdy4KCgpgYGB7ciBldmFsPUZBTFNFfQpzb3VyY2UoImh0dHA6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy9iaW9jTGl0ZS5SIikKYmlvY0xpdGUoIm9yZy5NbS5lZy5kYiIpCiMgRm9yIEh1bWFuCmJpb2NMaXRlKCJvcmcuSHMuZWcuZGIiKQpgYGAKClRoZSBwYWNrYWdlcyBhcmUgbGFyZ2VyIGluIHNpemUgdGhhdCBCaW9jb25kdWN0b3Igc29mdHdhcmUgcGFjYWtnZXMsIGJ1dCBlc3NlbnRpYWxseSB0aGV5IGFyZSBkYXRhYmFzZXMgdGhhdCBjYW4gYmUgdXNlZCB0byBtYWtlICpvZmZsaW5lKiBxdWVyaWVzLiAKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkob3JnLk1tLmVnLmRiKQpgYGAKCgpGaXJzdCB3ZSBuZWVkIHRvIGRlY2lkZSB3aGF0IGluZm9ybWF0aW9uIHdlIHdhbnQuIEluIG9yZGVyIHRvIHNlZSB3aGF0IHdlIGNhbiBleHRyYWN0IHdlIGNhbiBydW4gdGhlIGBjb2x1bW5zYCBmdW5jdGlvbiBvbiB0aGUgYW5ub3RhdGlvbiBkYXRhYmFzZS4KCmBgYHtyfQpjb2x1bW5zKG9yZy5NbS5lZy5kYikKYGBgCgpXZSBhcmUgZ29pbmcgdG8gZmlsdGVyIHRoZSBkYXRhYmFzZSBieSBhIGtleSBvciBzZXQgb2Yga2V5cyBpbiBvcmRlciB0byBleHRyYWN0IHRoZSBpbmZvcm1hdGlvbiB3ZSB3YW50LiBWYWxpZCBuYW1lcyBmb3IgdGhlIGtleSBjYW4gYmUgcmV0cmlldmVkIHdpdGggdGhlIGBrZXl0eXBlc2AgZnVuY3Rpb24uCgpgYGB7cn0Ka2V5dHlwZXMob3JnLk1tLmVnLmRiKQpgYGAKCldlIHNob3VsZCBzZWUgYEVOVFJFWklEYCwgd2hpY2ggaXMgdGhlIHR5cGUgb2Yga2V5IHdlIGFyZSBnb2luZyB0byB1c2UgaW4gdGhpcyBjYXNlLiBJZiB3ZSBhcmUgdW5zdXJlIHdoYXQgdmFsdWVzIGFyZSBhY2NlcHRhYmxlIGZvciB0aGUga2V5LCB3ZSBjYW4gY2hlY2sgd2hhdCBrZXlzIGFyZSB2YWxpZCB3aXRoIGBrZXlzYAoKYGBge3J9CmtleXMob3JnLk1tLmVnLmRiLCBrZXl0eXBlPSJFTlRSRVpJRCIpWzE6MTBdCmBgYAoKSXQgaXMgYSB1c2VmdWwgc2FuaXR5IGNoZWNrIHRvIG1ha2Ugc3VyZSB0aGF0IHRoZSBrZXlzIHlvdSB3YW50IHRvIHVzZSBhcmUgYWxsIHZhbGlkLiBXZSBjb3VsZCB1c2UgYCVpbiVgIGluIHRoaXMgY2FzZS4KCmBgYHtyfQojIyBCdWlsZCB1cCB0aGUgcXVlcnkgc3RlcC1ieS1zdGVwCm15LmtleXMgPC0gYygiNTA5MTYiLCAiMTEwMzA4IiwiMTIyOTMiKQpteS5rZXlzICVpbiUga2V5cyhvcmcuTW0uZWcuZGIsIGtleXR5cGU9IkVOVFJFWklEIikKYWxsKG15LmtleXMgJWluJSBrZXlzKG9yZy5NbS5lZy5kYiwga2V5dHlwZT0iRU5UUkVaSUQiKSkKYGBgCgpMZXQncyBidWlsZCB1cCB0aGUgcXVlcnkgc3RlcCBieSBzdGVwLgoKYGBge3IgZXZhbD1GQUxTRX0KIyMgdG8gYmUgZmlsbGVkLWluIGludGVyYWN0aXZlbHkgZHVyaW5nIHRoZSBjbGFzcy4Kc2VsZWN0KG9yZy5NbS5lZy5kYiwKCgpgYGAKCgoKVG8gYW5ub3RhdGUgb3VyIHJlc3VsdHMsIHdlIGRlZmluaXRlbHkgd2FudCBnZW5lIHN5bWJvbHMgYW5kIHBlcmhhcHMgdGhlIGZ1bGwgZ2VuZSBuYW1lLiBMZXQncyBidWlsZCB1cCBvdXIgYW5ub3RhdGlvbiBpbmZvcm1hdGlvbiBpbiBhIHNlcGFyYXRlIGRhdGEgZnJhbWUgdXNpbmcgdGhlIGBzZWxlY3RgIGZ1bmN0aW9uLgoKYGBge3J9CmFubiA8LSBzZWxlY3Qob3JnLk1tLmVnLmRiLGtleXM9cm93bmFtZXMocmVzdWx0cyksY29sdW1ucz1jKCJFTlRSRVpJRCIsIlNZTUJPTCIsIkdFTkVOQU1FIikpCiMgSGF2ZSBhIGxvb2sgYXQgdGhlIGFubm90YXRpb24KYW5uCgpgYGAKCkxldCdzIGRvdWJsZSBjaGVjayB0aGF0IHRoZSBgRU5UUkVaSURgIGNvbHVtbiBtYXRjaGVzIGV4YWN0bHkgdG8gb3VyIGByZXN1bHRzYCByb3duYW1lcy4KCmBgYHtyfQp0YWJsZShhbm4kRU5UUkVaSUQ9PXJvd25hbWVzKHJlc3VsdHMpKQpgYGAKCldlIGNhbiBiaW5kIGluIHRoZSBhbm5vdGF0aW9uIGluZm9ybWF0aW9uIHRvIHRoZSBgcmVzdWx0c2AgZGF0YSBmcmFtZS4gKFBsZWFzZSBub3RlIHRoYXQgaWYgdGhlIGBzZWxlY3RgIGZ1bmN0aW9uIHJldHVybnMgYSAxOm1hbnkgbWFwcGluZyB0aGVuIHlvdSBjYW4ndCBqdXN0IGFwcGVuZCB0aGUgYW5ub3RhdGlvbiB0byB0aGUgZml0IG9iamVjdC4pCgpgYGB7cn0KcmVzdWx0cy5hbm5vdGF0ZWQgPC0gY2JpbmQocmVzdWx0cywgYW5uKQpyZXN1bHRzLmFubm90YXRlZAoKYGBgCgoKV2UgY2FuIHNhdmUgdGhlIHJlc3VsdHMgdGFibGUgdXNpbmcgdGhlIGB3cml0ZS5jc3ZgIGZ1bmN0aW9uLCB3aGljaCB3cml0ZXMgdGhlIHJlc3VsdHMgb3V0IHRvIGEgY3N2IGZpbGUgdGhhdCB5b3UgY2FuIG9wZW4gaW4gZXhjZWwuCgpgYGB7cn0Kd3JpdGUuY3N2KHJlc3VsdHMuYW5ub3RhdGVkLGZpbGU9IkIuUHJlZ1ZzTGFjUmVzdWx0cy5jc3YiLHJvdy5uYW1lcz1GQUxTRSkKYGBgCgoqKkEgbm90ZSBhYm91dCBkZWNpZGluZyBob3cgbWFueSBnZW5lcyBhcmUgc2lnbmlmaWNhbnQqKjogSW4gb3JkZXIgdG8gZGVjaWRlIHdoaWNoIGdlbmVzIGFyZSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQsIHdlIHVzdWFsbHkgdGFrZSBhIGN1dC1vZmYgb2YgMC4wNSBvbiB0aGUgYWRqdXN0ZWQgcC12YWx1ZSwgTk9UIHRoZSByYXcgcC12YWx1ZS4gVGhpcyBpcyBiZWNhdXNlIHdlIGFyZSB0ZXN0aW5nIG1vcmUgdGhhbiAxNTAwMCBnZW5lcywgYW5kIHRoZSBjaGFuY2VzIG9mIGZpbmRpbmcgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIGlzIHZlcnkgaGlnaCB3aGVuIHlvdSBkbyB0aGF0IG1hbnkgdGVzdHMuIEhlbmNlIHdlIG5lZWQgdG8gY29udHJvbCB0aGUgZmFsc2UgZGlzY292ZXJ5IHJhdGUsIHdoaWNoIGlzIHRoZSBhZGp1c3RlZCBwLXZhbHVlIGNvbHVtbiBpbiB0aGUgcmVzdWx0cyB0YWJsZS4gV2hhdCB0aGlzIG1lYW5zIGlzIHRoYXQgaWYgMTAwIGdlbmVzIGFyZSBzaWduaWZpY2FudCBhdCBhIDVcJSBmYWxzZSBkaXNjb3ZlcnkgcmF0ZSwgd2UgYXJlIHdpbGxpbmcgdG8gYWNjZXB0IHRoYXQgNSB3aWxsIGJlIGZhbHNlIHBvc2l0aXZlcy4gTm90ZSB0aGF0IHRoZSBgZGVjaWRlVGVzdHNgIGZ1bmN0aW9uIGRpc3BsYXlzIHNpZ25pZmljYW50IGdlbmVzIGF0IDVcJSBGRFIuCgo+ICMjIENoYWxsZW5nZSB7LmNoYWxsZW5nZX0KPgo+IFJlLXZpc2l0IHRoZSBgcGxvdFNtZWFyYCBwbG90IGZyb20gYWJvdmUgYW5kIHVzZSB0aGUgYHRleHRgIGZ1bmN0aW9uIHRvIGFkZCBsYWJlbHMgZm9yIHRoZSBuYW1lcyBvZiB0aGUgdG9wIDIwMCBtb3N0IERFIGdlbmVzCj4KCmBgYHtyLGVjaG89RkFMU0UsZmlnLmhlaWdodD01LGZpZy53aWR0aD0xMH0KCnBsb3RTbWVhcihscnQuQnZzTCwgZGUudGFncz1kZXRhZ3MpCgpOIDwtIDIwMAoKdGV4dChyZXN1bHRzLmFubm90YXRlZCRsb2dDUE1bMTpOXSxyZXN1bHRzLmFubm90YXRlZCRsb2dGQ1sxOk5dLGxhYmVscyA9IHJlc3VsdHMuYW5ub3RhdGVkJFNZTUJPTFsxOk5dLGNvbD0iYmx1ZSIpCmBgYAoKCkFub3RoZXIgY29tbW9uIHZpc3VhbGlzYXRpb24gaXMgdGhlIFsqdm9sY2FubyBwbG90Kl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvVm9sY2Fub19wbG90XyhzdGF0aXN0aWNzKSkgd2hpY2ggZGlzcGxheSBhIG1lYXN1cmUgb2Ygc2lnbmlmaWNhbmNlIG9uIHRoZSB5LWF4aXMgYW5kIGZvbGQtY2hhbmdlIG9uIHRoZSB4LWF4aXMuIAoKYGBge3IsZmlnLmhlaWdodD01LGZpZy53aWR0aD0xMH0Kc2lnbmlmIDwtIC1sb2cxMChyZXN1bHRzLmFubm90YXRlZCRGRFIpCnBsb3QocmVzdWx0cy5hbm5vdGF0ZWQkbG9nRkMsc2lnbmlmLHBjaD0xNikKcG9pbnRzKHJlc3VsdHMuYW5ub3RhdGVkW2RldGFncywibG9nRkMiXSwtbG9nMTAocmVzdWx0cy5hbm5vdGF0ZWRbZGV0YWdzLCJGRFIiXSkscGNoPTE2LGNvbD0icmVkIikKCmBgYAoKCkJlZm9yZSBmb2xsb3dpbmcgdXAgb24gdGhlIERFIGdlbmVzIHdpdGggZnVydGhlciBsYWIgd29yaywgYSByZWNvbW1lbmRlZCAqc2FuaXR5IGNoZWNrKiBpcyB0byBoYXZlIGEgbG9vayBhdCB0aGUgZXhwcmVzc2lvbiBsZXZlbHMgb2YgdGhlIGluZGl2aWR1YWwgc2FtcGxlcyBmb3IgdGhlIGdlbmVzIG9mIGludGVyZXN0LiBXZSBjYW4gcXVpY2tseSBsb29rIGF0IGdyb3VwZWQgZXhwcmVzc2lvbiB1c2luZyBgc3RyaXBjaGFydGAuIFdlIGNhbiB1c2UgdGhlIG5vcm1hbGlzZWQgbG9nIGV4cHJlc3Npb24gdmFsdWVzIGluIHRoZSAgYGRnZUNvdW50c2Agb2JqZWN0IChgZGdlQ291bnRzJGNvdW50c2ApLgoKYGBge3IsZmlnLndpZHRoPTEyLGZpZy5oZWlnaHQ9NX0KbGlicmFyeShSQ29sb3JCcmV3ZXIpCnBhcihtZnJvdz1jKDEsMykpCm5vcm1Db3VudHMgPC0gZGdlT2JqJGNvdW50cwojIExldCdzIGxvb2sgYXQgdGhlIGZpcnN0IGdlbmUgaW4gdGhlIHRvcFRhYmxlLCBLcnQ1LCB3aGljaCBoYXMgYSByb3duYW1lIDExMDMwOApzdHJpcGNoYXJ0KG5vcm1Db3VudHNbIjExMDMwOCIsXX5ncm91cCkKIyBUaGlzIHBsb3QgaXMgdWdseSwgbGV0J3MgbWFrZSBpdCBiZXR0ZXIKc3RyaXBjaGFydChub3JtQ291bnRzWyIxMTAzMDgiLF1+Z3JvdXAsdmVydGljYWw9VFJVRSxsYXM9MixjZXguYXhpcz0wLjgscGNoPTE2LGNvbD0xOjYsbWV0aG9kPSJqaXR0ZXIiKQojIExldCdzIHVzZSBuaWNlciBjb2xvdXJzCm5pY2UuY29sIDwtIGJyZXdlci5wYWwoNixuYW1lPSJEYXJrMiIpCnN0cmlwY2hhcnQobm9ybUNvdW50c1siMTEwMzA4Iixdfmdyb3VwLHZlcnRpY2FsPVRSVUUsbGFzPTIsY2V4LmF4aXM9MC44LHBjaD0xNixjZXg9MS4zLGNvbD1uaWNlLmNvbCxtZXRob2Q9ImppdHRlciIseWxhYj0iTm9ybWFsaXNlZCBsb2cyIGV4cHJlc3Npb24iLG1haW49IglLcnQ1IikKYGBgCgpBbiBpbnRlcmFjdGl2ZSB2ZXJzaW9uIG9mIHRoZSB2b2xjYW5vIHBsb3QgYWJvdmUgdGhhdCBpbmNsdWRlcyB0aGUgcmF3IHBlciBzYW1wbGUgdmFsdWVzIGluIGEgc2VwYXJhdGUgcGFuZWwgaXMgcG9zc2libGUgdmlhIHRoZSBgZ2xYWVBsb3RgIGZ1bmN0aW9uIGluIHRoZSAqR2xpbW1hKiBwYWNrYWdlLgoKCmBgYHtyfQpsaWJyYXJ5KEdsaW1tYSkKZ3JvdXAyIDwtIGdyb3VwCmxldmVscyhncm91cDIpIDwtIGMoImJhc2FsLmxhY3RhdGUiLCJiYXNhbC5wcmVnIiwiYmFzYWwudmlyZ2luIiwibHVtLmxhY3RhdGUiLCAibHVtLnByZWciLCAibHVtLnZpcmdpbiIpCmdsWFlQbG90KHg9cmVzdWx0cyRsb2dGQywgeT0tbG9nMTAocmVzdWx0cyRGRFIpLAogICAgICAgICB4bGFiPSJsb2dGQyIsIHlsYWI9IkIiLCBtYWluPSJCLlByZWdWc0xhYyIsCiAgICAgICAgIGNvdW50cz1ub3JtQ291bnRzLCBncm91cHM9Z3JvdXAyLCBzdGF0dXM9ZGUsCiAgICAgICAgIGFubm89YW5uLCBpZC5jb2x1bW49IkVOVFJFWklEIiwgZm9sZGVyPSJ2b2xjYW5vIikKYGBgCgoKVGhpcyBmdW5jdGlvbiBjcmVhdGVzIGFuIGh0bWwgcGFnZSAoLi92b2xjYW5vL1hZLVBsb3QuaHRtbCkgd2l0aCBhIHZvbGNhbm8gcGxvdCBvbiB0aGUgbGVmdCBhbmQgYSBwbG90IHNob3dpbmcgdGhlIGxvZy1DUE0gcGVyIHNhbXBsZSBmb3IgYSBzZWxlY3RlZCBnZW5lIG9uIHRoZSByaWdodC4gQSBzZWFyY2ggYmFyIGlzIGF2YWlsYWJsZSB0byBzZWFyY2ggZm9yIGdlbmVzIG9mIGludGVyZXN0LgoKCgojIyBSZXRyaWV2aW5nIEdlbm9taWMgTG9jYXRpb25zCgoKSXQgbWlnaHQgc2VlbSBuYXR1cmFsIHRvIGFkZCBnZW5vbWljIGxvY2F0aW9ucyB0byBvdXIgYW5ub3RhdGlvbiB0YWJsZSwgYW5kIHBvc3NpYmx5IGEgYml0IG9kZCB0aGF0IHRoZSBgb3JnLk1tLmVnLmRiYCBwYWNrYWdlIGRvZXMgbm90IHN1cHBseSBzdWNoIG1hcHBpbmdzLiBJbiBmYWN0LCB0aGVyZSBpcyBhIHdob2xlIHN1aXRlIG9mIHBhY2thZ2UgZm9yIHBlcmZvcm1pbmcgdGhpcywgYW5kIG1vcmUtYWR2YW5jZWQgcXVlcmllcyB0aGF0IHJlbGF0ZSB0byB0aGUgbG9jYXRpb24gb2YgZ2VuZXMuIFRoZXNlIGFyZSBsaXN0ZWQgb24gdGhlIEJpb2NvbmR1Y3RvciBbYW5ub3RhdGlvbiBwYWdlXShodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL0Jpb2NWaWV3cy5odG1sI19fX0Fubm90YXRpb25EYXRhKSBhbmQgaGF2ZSB0aGUgcHJlZml4IGBUeERiLmAKClRoZSBwYWNrYWdlIHdlIHdpbGwgYmUgdXNpbmcgaXMgYFR4RGIuTW11c2N1bHVzLlVDU0MubW0xMC5rbm93bkdlbmVgLiBQYWNrYWdlcyBhcmUgYXZhaWxhYmxlIGZvciBvdGhlciBvcmdhbmlzbXMgYW5kIGdlbm9tZSBidWlsZHMuIEl0IGlzIGV2ZW4gcG9zc2libGUgdG8gKmJ1aWxkIHlvdXIgb3duIGRhdGFiYXNlKiBpZiBvbmUgZG9lcyBub3QgZXhpc3QuIFNlZSBgdmlnbmV0dGUoIkdlbm9taWNGZWF0dXJlcyIpYCBmb3IgZGV0YWlscwoKYGBge3IgZXZhbD1GQUxTRX0Kc291cmNlKCJodHRwOi8vd3d3LmJpb2NvbmR1Y3Rvci5vcmcvYmlvY0xpdGUuUiIpCmJpb2NMaXRlKCJUeERiLk1tdXNjdWx1cy5VQ1NDLm1tMTAua25vd25HZW5lIikKCiMjIEZvciBIdW1hbnMKYmlvY0xpdGUoIlR4RGIuSHNhcGllbnMuVUNTQy5oZzE5Lmtub3duR2VuZSIpCgpgYGAKCldlIGxvYWQgdGhlIGxpYnJhcnkgaW4gdGhlIHVzdWFsIGZhc2hpb24gYW5kIGNyZWF0ZSBhIG5ldyBvYmplY3QgdG8gc2F2ZSBzb21lIHR5cGluZy4gQXMgd2l0aCB0aGUgYG9yZy5gIHBhY2thZ2VzLCB3ZSBjYW4gcXVlcnkgd2hhdCBjb2x1bW5zIGFyZSBhdmFpbGFibGUgd2l0aCBgY29sdW1uc2AsCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KFR4RGIuTW11c2N1bHVzLlVDU0MubW0xMC5rbm93bkdlbmUpCnR4IDwtIFR4RGIuTW11c2N1bHVzLlVDU0MubW0xMC5rbm93bkdlbmUKY29sdW1ucyh0eCkKYGBgCgpUaGUgYHNlbGVjdGAgZnVuY3Rpb24gaXMgdXNlZCBpbiB0aGUgc2FtZSBtYW5uZXIgYXMgdGhlIGBvcmcuTW0uZWcuZGJgIHBhY2thZ2VzLiAKCgo+ICMjIENoYWxsZW5nZSB7LmNoYWxsZW5nZX0KPgo+IFVzZSB0aGUgVHhEYi5NbXVzY3VsdXMuVUNTQy5tbTEwLmtub3duR2VuZSBwYWNrYWdlIHRvIHJldHJpZXZlIHRoZSBleG9uIGNvb3JkaW5hdGVzIGZvciB0aGUgZ2VuZXMgYDUwOTE2YCwgYDExMDMwOGAsIGAxMjI5M2AgCj4KCmBgYHtyIGVjaG89RkFMU0Usd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQprZXlzIDwtIGMoIjUwOTE2IiwiMTEwMzA4IiwiMTIyOTMiKQpzZWxlY3QodHgsIGtleXM9a2V5cywKICAgICAgIGtleXR5cGUgPSAiR0VORUlEIiwKICAgICAgIGNvbHVtbnM9YygiRVhPTkNIUk9NIiwiRVhPTlNUQVJUIiwiRVhPTkVORCIpCiAgICAgICkKCmBgYAoKIyMjIE92ZXJ2aWV3IG9mIEdlbm9taWNSYW5nZXMKCk9uZSBvZiB0aGUgcmVhbCBzdHJlbmd0aHMgb2YgdGhlIGB0eGRiLi5gIHBhY2thZ2VzIGlzIHRoZSBhYmlsaXR5IG9mIGludGVyZmFjZSB3aXRoIGBHZW5vbWljUmFuZ2VzYCwgd2hpY2ggaXMgdGhlIG9iamVjdCB0eXBlIHVzZWQgdGhyb3VnaG91dCBCaW9jb25kdWN0b3IgW3RvIG1hbmlwdWxhdGUgR2Vub21pYyBJbnRlcnZhbHNdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzM3Mzg0NTgvcGRmL3BjYmkuMTAwMzExOC5wZGYpLiAKClRoZXNlIG9iamVjdCB0eXBlcyBwZXJtaXQgdXMgdG8gcGVyZm9ybSBjb21tb24gb3BlcmF0aW9ucyBvbiBpbnRlcnZhbHMgc3VjaCBhcyBvdmVybGFwcGluZyBhbmQgY291bnRpbmcuIFdlIGNhbiBkZWZpbmUgdGhlIGNocm9tb3NvbWUsIHN0YXJ0IGFuZCBlbmQgcG9zaXRpb24gb2YgZWFjaCByZWdpb24gKGFsc28gc3RyYW5kIHRvbywgYnV0IG5vdCBzaG93biBoZXJlKS4KCmBgYHtyfQpsaWJyYXJ5KEdlbm9taWNSYW5nZXMpCnNpbXBsZS5yYW5nZSA8LUdSYW5nZXMoIjEiLCBJUmFuZ2VzKHN0YXJ0PTEwMDAsZW5kPTIwMDApKQpzaW1wbGUucmFuZ2UKCmBgYAoKV2UgZG9uJ3QgaGF2ZSB0byBoYXZlIGFsbCBvdXIgcmFuZ2VzIGxvY2F0ZWQgb24gdGhlIHNhbWUgY2hyb21vc29tZQpgYGB7cn0KY2hycyA8LSBjKCJjaHIxMyIsICJjaHIxNSIsImNocjUiKQpzdGFydCA8LSBjKDczMDAwMDAwLCAxMDEwMDAwMDAsIDE1MDAwMDAwKQplbmQgPC0gYyg3NDAwMDAwMCwxMDIwMDAwMDAsIDE2MDAwMDAwKQoKbXkucmFuZ2VzIDwtIEdSYW5nZXMocmVwKGNocnMsMyksIAogICAgICAgICAgICAgICAgICAgICBJUmFuZ2VzKHN0YXJ0PXJlcChzdGFydCxlYWNoPTMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZCA9IHJlcChlbmQsZWFjaD0zKSkKKQoKYGBgCgpUaGVyZSBhcmUgYSBudW1iZXIgb2YgdXNlZnVsIGZ1bmN0aW9ucyBmb3IgY2FsY3VsYXRpbmcgcHJvcGVydGllcyBvZiB0aGUgZGF0YSAoc3VjaCBhcyAqY292ZXJhZ2UqIG9yIHNvcnRpbmcpLiBOb3Qgc28gbXVjaCBmb3IgUk5BLXNlcSBhbmFseXNpcywgYnV0IGBHZW5vbWljUmFuZ2VzYCBhcmUgdXNlZCB0aHJvdWdob3V0IEJpb2NvbmR1Y3RvciBmb3IgdGhlIGFuYWx5c2lzIG9mIE5HUyBkYXRhLiAKCkZvciBpbnN0YW5jZSwgd2UgY2FuIHF1aWNrbHkgaWRlbnRpZnkgb3ZlcmxhcHBpbmcgcmVnaW9ucyBiZXR3ZWVuIHR3byBgR2Vub21pY1Jhbmdlc2AuIEhvd2V2ZXIsIHdlIGhhdmUgdG8gcGF5IGF0dGVudGlvbiB0byB0aGUgbmFtaW5nIGNvbnZlbnRpb24gdXNlZCBmb3IgZWFjaCBvYmplY3QuIGBzZXFsZXZlbHNTdHlsZWAgY2FuIAoKYGBge3J9CmtleXMgPC0gYygiNTA5MTYiLCIxMTAzMDgiLCIxMjI5MyIpCmdlbmVQb3MgPC0gc2VsZWN0KHR4LCBrZXlzPWtleXMsCiAgICAgICBrZXl0eXBlID0gIkdFTkVJRCIsCiAgICAgICBjb2x1bW5zPWMoIkVYT05DSFJPTSIsIkVYT05TVEFSVCIsIkVYT05FTkQiKQogICAgICApCmdlbmVSYW5nZXMgPC0gR1JhbmdlcyhnZW5lUG9zJEVYT05DSFJPTSwgSVJhbmdlcyhnZW5lUG9zJEVYT05TVEFSVCxnZW5lUG9zJEVYT05FTkQpLCBHRU5FSUQ9Z2VuZVBvcyRHRU5FSUQpCmdlbmVSYW5nZXMKCmZpbmRPdmVybGFwcyhteS5yYW5nZXMsZ2VuZVJhbmdlcykKc2VxbGV2ZWxzU3R5bGUoZ2VuZVJhbmdlcykKc2VxbGV2ZWxzU3R5bGUoc2ltcGxlLnJhbmdlKQoKYGBgCgoKIyMgUmV0cmlldmluZyBHZW5lIENvb3JkaW5hdGVzIGFzIEdlbm9taWNSYW5nZXMKCkFzIHdlIHNhdyBhYm92ZSwgaXQgaXMgcXVpdGUgc3RyYWlnaHRmb3J3YXJkIHRvIHRyYW5zbGF0ZSB0aGUgb3V0cHV0IG9mIGEgYHNlbGVjdGAgcXVlcnkgaW50byBhIGBHZW5vbWljRmVhdHVyZXNgIG9iamVjdC4gSG93ZXZlciwgc2V2ZXJhbCBjb252ZW5pZW5jZSBmdW5jdGlvbnMgZXhpc3QgdG8gcmV0cmlldmUgdGhlIHN0cnVjdHVyZSBvZiBldmVyeSBnZW5lIGZvciBhIGdpdmVuIG9yZ2FuaXNtIGluIG9uZSBvYmplY3QuIAoKVGhlIG91dHB1dCBvZiBgZXhvbnNCeWAgaXMgYSBsaXN0LCB3aGVyZSBlYWNoIGl0ZW0gaW4gdGhlIGxpc3QgaXMgdGhlIGV4b24gY28tb3JkaW5hdGVzIG9mIGEgcGFydGljdWxhciBnZW5lLiAKCmBgYHtyfQpleG8gPC0gZXhvbnNCeSh0eCwiZ2VuZSIpCmV4bwpgYGAKClRvIGFjY2VzcyB0aGUgc3RydWN0dXJlIG9mIGEgcGFydGljdWxhciBnZW5lLCB3ZSBjYW4gdXNlIHRoZSBgW1tgIHN5bnRheCB3aXRoIHRoZSBuYW1lIG9mIHRoZSBnZW5lIChFbnRyZXogZ2VuZSBJRCkgd2l0aGluIHF1b3RlIG1hcmtzLiBJZiB3ZSB3YW50ZWQgdG8gd2hvbGUgcmVnaW9uIHRoYXQgdGhlIGdlbmUgc3BhbnMgd2UgY291bGQgdXNlIHRoZSBgcmFuZ2VgIGZ1bmN0aW9uLgoKYGBge3J9CmV4b1tbIjExMDMwOCJdXQpyYW5nZShleG9bWyIxMTAzMDgiXV0pCmBgYAoKCiMjIEV4cG9ydGluZyB0cmFja3MKCkl0IGlzIGFsc28gcG9zc2libGUgdG8gc2F2ZSB0aGUgcmVzdWx0cyBvZiBhIEJpb2NvbmR1Y3RvciBhbmFseXNpcyBpbiBhIGJyb3dzZXIgdG8gZW5hYmxlIGludGVyYWN0aXZlIGFuYWx5c2lzIGFuZCBpbnRlZ3JhdGlvbiB3aXRoIG90aGVyIGRhdGEgdHlwZXMsIG9yIHNoYXJpbmcgd2l0aCBjb2xsYWJvcmF0b3JzLiBGb3IgaW5zdGFuY2UsIHdlIG1pZ2h0IHdhbnQgYSBicm93c2VyIHRyYWNrIHRvIGluZGljYXRlIHdoZXJlIG91ciBkaWZmZXJlbnRpYWxseS1leHByZXNzZWQgZ2VuZXMgYXJlIGxvY2F0ZWQuIFdlIHNoYWxsIHVzZSB0aGUgYGJlZGAgZm9ybWF0IHRvIGRpc3BsYXkgdGhlc2UgbG9jYXRpb25zLiBXZSB3aWxsIGFubm90YXRlIHRoZSByYW5nZXMgd2l0aCBpbmZvcm1hdGlvbiBmcm9tIG91ciBhbmFseXNpcyBzdWNoIGFzIHRoZSBmb2xkLWNoYW5nZSBhbmQgc2lnbmlmaWNhbmNlLgoKRmlyc3Qgd2UgY3JlYXRlIGEgZGF0YSBmcmFtZSBmb3IganVzdCB0aGUgREUgZ2VuZXMuCmBgYHtyfQpzaWdHZW5lcyA8LSByZXN1bHRzLmFubm90YXRlZFtkZXRhZ3MsXQpzaWdHZW5lcwpgYGAKCkF0IHRoZSBtb21lbnQsIHdlIGhhdmUgYSBHZW5vbWljRmVhdHVyZXMgb2JqZWN0IHRoYXQgcmVwcmVzZW50cyBldmVyeSBleG9uLiBIb3dldmVyLCB3ZSBkbyBub3QKbmVlZCB0aGlzIGxldmVsIG9mIGdyYW51bGFyaXR5IGZvciB0aGUgYmVkIG91dHB1dCwgc28gd2Ugd2lsbCBjb2xsYXBzZSB0byBhIHNpbmdsZSByZWdpb24gZm9yIGVhY2ggZ2VuZS4gRmlyc3Qgd2UgdGhlIGByYW5nZWAgZnVuY3Rpb24gdG8gb2J0YWluIGEgc2luZ2xlIHJhbmdlIGZvciBldmVyeSBnZW5lIGFuZCB0cmFuZm9ybSB0byBhIG1vcmUgY29udmVuaWVudCBvYmplY3Qgd2l0aCBgdW5saXN0YC4KYGBge3J9CmV4b1JhbmdlcyA8LSB1bmxpc3QocmFuZ2UoZXhvKSkKc2lnUmVnaW9ucyA8LSBleG9SYW5nZXNbbmEub21pdChtYXRjaChzaWdHZW5lcyRFTlRSRVpJRCwgbmFtZXMoZXhvUmFuZ2VzKSkpXQpzaWdSZWdpb25zCmBgYAoKUmF0aGVyIHRoYW4ganVzdCByZXByZXNlbnRpbmcgdGhlIGdlbm9taWMgbG9jYXRpb25zLCB0aGUgLmJlZCBmb3JtYXQgaXMgYWxzbyBhYmxlIHRvIGNvbG91ciBlYWNoIHJhbmdlCmFjY29yZGluZyB0byBzb21lIHByb3BlcnR5IG9mIHRoZSBhbmFseXNpcyAoZS5nLiBkaXJlY3Rpb24gYW5kIG1hZ25pdHVkZSBvZiBjaGFuZ2UpIHRvIGhlbHAgaGlnaGxpZ2h0CnBhcnRpY3VsYXIgcmVnaW9ucyBvZiBpbnRlcmVzdC4gQSBzY29yZSBjYW4gYWxzbyBiZSBkaXNwbGF5ZWQgd2hlbiBhIHBhcnRpY3VsYXIgcmVnaW9uIGlzIGNsaWNrZWQtb24uCkEgdXNlZnVsIHByb3Blcnkgb2YgR2Vub21pY1JhbmdlcyBpcyB0aGF0IHdlIGNhbiBhdHRhY2ggKm1ldGFkYXRhKiB0byBlYWNoIHJhbmdlIHVzaW5nIHRoZSBgbWNvbHNgCmZ1bmN0aW9uLiBUaGUgbWV0YWRhdGEgY2FuIGJlIHN1cHBsaWVkIGluIHRoZSBmb3JtIG9mIGEgZGF0YSBmcmFtZS4KCmBgYHtyfQptY29scyhzaWdSZWdpb25zKSA8LSBzaWdHZW5lc1ttYXRjaChuYW1lcyhzaWdSZWdpb25zKSwgcm93bmFtZXMoc2lnR2VuZXMpKSxdCnNpZ1JlZ2lvbnMKYGBgCgpUaGUgbWV0YWRhdGEgd2UgaGF2ZSBhZGRlZCBjYW4gYWxzbyBieSB1c2VkIGFzIGEgbWVhbnMgdG8gaW50ZXJyb2dhdGUgdGhlIHJhbmdlczsgYXMgaWYgdGhlIGRhdGEgd2VyZSBjb250YWluZWQgaW4gYSBkYXRhIGZyYW1lLgoKYGBge3J9CnNpZ1JlZ2lvbnNbb3JkZXIoc2lnUmVnaW9ucyRMUixkZWNyZWFzaW5nID0gVFJVRSldCmBgYAoKRm9yIHZpc3VhbGlzYXRpb24gcHVycG9zZXMsIHdlIGFyZSBnb2luZyB0byByZXN0cmljdCB0aGUgZGF0YSB0byBnZW5lcyB0aGF0IGFyZSBsb2NhdGVkIG9uIGNocm9tb3NvbWVzIDEgdG8gMTkgYW5kIHRoZSBzZXggY2hyb21vc29tZXMuIFRoaXMgY2FuIGJlIGRvbmUgd2l0aCB0aGUgYGtlZXBTZXFMZXZlbHNgIGZ1bmN0aW9uLgoKYGBge3J9CnNlcWxldmVscyhzaWdSZWdpb25zKQpzaWdSZWdpb25zIDwtIGtlZXBTZXFsZXZlbHMoc2lnUmVnaW9ucywgcGFzdGUwKCJjaHIiLCBjKDE6MTksIlgiLCJZIikpKQpgYGAKCldlIHdpbGwgbm93IGNyZWF0ZSBhIHNjb3JlIGZyb20gdGhlIHAtdmFsdWVzIHRoYXQgd2lsbCBkaXNwbGF5ZWQgdW5kZXIgZWFjaCByZWdpb24sIGFuZCBjb2xvdXIgc2NoZW1lCmZvciB0aGUgcmVnaW9ucyBiYXNlZCBvbiB0aGUgZm9sZC1jaGFuZ2UuIEZvciB0aGUgc2NvcmUgd2UgY2FuIHVzZSB0aGUgJC1sb2dfezEwfSQgb2YgdGhlIGFkanVzdGVkIHAtdmFsdWUgYXMgYmVmb3JlCgoKCmBgYHtyfQpTY29yZSA8LSAtbG9nMTAoc2lnUmVnaW9ucyRGRFIpCmBgYAoKYGNvbG9yUmFtcFBhbGV0dGVgIGlzIGEgdXNlZnVsIGZ1bmN0aW9uIGluIGJhc2UgUiBmb3IgY29uc3RydWN0aW5nIGEgcGFsZXR0ZSBiZXR3ZWVuIHR3byBleHRyZW1lcy4gKipXaGVuIGNob29zaW5nIGNvbG91ciBwYWxldHRlcywgbWFrZSBzdXJlIHRoZXkgYXJlIGNvbG91ciBibGluZCBmcmllbmRseSoqLiBUaGUgcmVkIC8gZ3JlZW4gY29sb3VyIHNjaGVtZSB0cmFkaXRpb25hbGx5LWFwcGxpZWQgdG8gbWljcm9hcnJheXMgaXMgYSAqKipiYWQqKiogY2hvaWNlLgoKV2Ugd2lsbCBhbHNvIHRydW5jYXRlIHRoZSBmb2xkLWNoYW5nZXMgdG8gYmV0d2VlbiAtNSBhbmQgNSB0byBhbmQgZGl2aWRlIHRoaXMgcmFuZ2UgaW50byAxMCBlcXVhbCBiaW5zCgpgYGB7cn0KcmJQYWwgPC1jb2xvclJhbXBQYWxldHRlKGMoInJlZCIsICJibHVlIikpCmxvZ2ZjIDwtIHBtYXgoc2lnUmVnaW9ucyRsb2dGQywgLTUpCmxvZ2ZjIDwtIHBtaW4obG9nZmMgLCA1KQoKQ29sIDwtIHJiUGFsKDEwKVthcy5udW1lcmljKGN1dChsb2dmYywgYnJlYWtzID0gMTApKV0KYGBgCgpUaGUgY29sb3VycyBhbmQgc2NvcmUgaGF2ZSB0byBiZSBzYXZlZCBpbiB0aGUgR1JhbmdlcyBvYmplY3QgYXMgYHNjb3JlYCBhbmQgYGl0ZW1SZ2JgIGNvbHVtbnMgcmVzcGVjdGl2ZWx5LCBhbmQgd2lsbCBiZSB1c2VkIHRvIGNvbnN0cnVjdCB0aGUgYnJvd3NlciB0cmFjay4gVGhlIHJ0cmFja2xheWVyIHBhY2thZ2UgY2FuIGJlIHVzZWQgdG8gaW1wb3J0IGFuZCBleHBvcnQgYnJvd3NlcnMgdHJhY2tzLgoKTm93IHdlIGNhbiBleHBvcnQgdGhlIHNpZ25pZmNhbnQgcmVzdWx0cyBmcm9tIHRoZSBERSBhbmFseXNpcyBhcyBhIGAuYmVkYCB0cmFjayB1c2luZyBgcnRyYWNrbGF5ZXJgLiBZb3UgY2FuIGxvYWQgdGhlIHJlc3VsdGluZyBmaWxlIGluIElHViwgaWYgeW91IHdpc2guCmBgYHtyfQptY29scyhzaWdSZWdpb25zKSRzY29yZSA8LSBTY29yZQptY29scyhzaWdSZWdpb25zKSRpdGVtUmdiIDwtIENvbApzaWdSZWdpb25zCmxpYnJhcnkocnRyYWNrbGF5ZXIpCmV4cG9ydChzaWdSZWdpb25zICwgY29uID0gInRvcEhpdHMuYmVkIikKYGBgCgojIyBFeHRyYWN0aW5nIFJlYWRzCgpBcyB3ZSBoYXZlIGJlZW4gdXNpbmcgY291bnRzIGFzIG91ciBzdGFydGluZyBwb2ludCwgd2UgaGF2ZW4ndCBpbnZlc3RpZ2F0ZWQgdGhlIGFsaWduZWQgcmVhZHMgZnJvbSBvdXIgZXhwZXJpbWVudCwgYW5kIGhvdyB0aGV5IGFyZSByZXByZXNlbnRlZC4gQXMgeW91IG1heSBiZSBhd2FyZSwgYWxpZ25lZCByZWFkcyBhcmUgdXN1YWxseSBzdG9yZWQgaW4gYSAqYmFtKiBmaWxlIHRoYXQgY2FuIGJlIG1hbmlwdWxhdGVkIHdpdGggb3Blbi1zb3VyY2UgY29tbWFuZC1saW5lIHRvb2xzIHN1Y2ggYXMgWypzYW10b29scypdKGh0dHA6Ly93d3cuaHRzbGliLm9yZy8pIGFuZCBbKnBpY2FyZCpdKGh0dHBzOi8vYnJvYWRpbnN0aXR1dGUuZ2l0aHViLmlvL3BpY2FyZC8pLiBCaW9jb25kdWN0b3IgcHJvdmlkZSBhIGxvdy1sZXZlbCBpbnRlcmZhY2UgdG8gYmFtL3NhbSBmaWxlcyBpbiB0aGUgZm9ybSBvZiB0aGUgYFJzYW10b29sc2AgcGFja2FnZS4gVGhlIGBHZW5vbWljQWxpZ25tZW50c2AgcGFja2FnZSBjYW4gYWxzbyBiZSB1c2VkIHRvIHJldHJpZXZlIHRoZSByZWFkcyBtYXBwaW5nIHRvIGEgcGFydGljdWxhciBnZW5vbWljIHJlZ2lvbiBpbiBhbiBlZmZpY2llbnQgbWFubmVyLgoKYGBge3IgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShHZW5vbWljQWxpZ25tZW50cykKYGBgCgpJbiB0aGUgZGlyZWN0b3J5IGBiYW1gIHRoZXJlIHNob3VsZCBiZSBgLmJhbWAgZmlsZXMgZm9yIGVhY2ggb2YgdGhlIHNhbXBsZXMgaW4gdGhlIGV4YW1wbGUgc3R1ZHkuIFRoZSB3b3JrZmxvdyB0byBwcm9kdWNlIHRoZXNlIGZpbGVzIGlzIGRlc2NyaWJlZCBpbiBhIFtzdXBwbG1lbnRhcnkgcGFnZV0oZ2V0dGluZy1yYXctcmVhZHMubmIuaHRtbCkgZm9yIHRoZSBjb3Vyc2UuIEluIGJyaWVmLCB0aGUgcmF3IHJlYWRzIChgZmFzdHFgKSB3ZXJlIGRvd25sb2FkZWQgZnJvbSB0aGUgU2hvcnQgUmVhZCBBcmNoaXZlIChTUkEpIGFuZCBhbGlnbmVkIHdpdGggYGJvd3RpZTJgLiBFYWNoIGJhbSBmaWxlIHdhcyBuYW1lZCBhY2NvcmRpbmcgdG8gdGhlIGZpbGUgbmFtZSBpbiBTUkEsIGJ1dCB3ZSBoYXZlIHJlbmFtZWQgdGhlIGZpbGVzIGFjY29yZGluZyB0byB0aGVpciBuYW1lIGluIHRoZSBzdHVkeS4gQW4gaW5kZXggZmlsZSAoYC5iYWlgKSBoYXMgYmVlbiBnZW5lcmF0ZWQgZm9yIGVhY2ggYmFtIGZpbGUuCgoKYGBge3J9Cmxpc3QuZmlsZXMoImJhbS8iKQpgYGAKClRoZSBgcmVhZEdBbGlnbm1lbnRzYCBmdW5jdGlvbiBwcm92aWRlcyBhIHNpbXBsZSBpbnRlcmZhY2UgdG8gaW50ZXJyb2dhdGUgdGhlIGFsaWduZWQgcmVhZHMgZm9yIGEgcGFydGljdWxhciBzYW1wbGUuIEl0IGNhbiBhbHNvIHV0aWxpc2UgdGhlICppbmRleCogZmlsZSBpbiBvcmRlciB0byByZXRyaWV2ZSBvbmx5IHRoZSByZWFkcyB0aGF0IGNvcnJlc3BvbmQgdG8gYSBzcGVjaWZpYyByZWdpb24gaW4gYW4gZWZmaWNpZW50IG1hbm5lci4gVGhlIG91dHB1dCBpbmNsdWRlcyB0aGUgZ2Vub21pYyBsb2NhdGlvbiBvZiBlYWNoIGFsaWduZWQgcmVhZCBhbmQgdGhlIENJR0FSICgqKkMqKm9tcGFjdCAqKkkqKmRpb3N5bmNyYXRpYyAqKkcqKmFwcGVkICoqQSoqbGlnbm1lbnQgKipSKiplcG9ydCk7IHdoZXJlICpNKiBkZW5vdGVzIGFuIG1hdGNoIHRvIHRoZSBnZW5vbWUgYW5kICpJKiwgKkQqIGNvcnJlc3BvbmQgdG8gaW5zZXJ0aW9ucyBhbmQgZGVsZXRpb25zLgoKYGBge3J9CmdlbmVyZWdpb24gPC0gZXhvW1siMTEwMzA4Il1dCgpteS5yZWFkcyA8LSByZWFkR0FsaWdubWVudHMoZmlsZT0iYmFtL01DTDEuREcuYmFtIiwKICAgICAgICAgICAgICAgICAgICAgICBwYXJhbT1TY2FuQmFtUGFyYW0od2hpY2g9Z2VuZXJlZ2lvbikpCm15LnJlYWRzCmBgYAoKSXQgaXMgcG9zc2libGUgdG8gdHdlYWsgdGhlIGZ1bmN0aW9uIHRvIHJldHJpZXZlIG90aGVyIHBvdGVudGlhbGx5LXVzZWZ1bCBpbmZvcm1hdGlvbiBmcm9tIHRoZSBiYW0gZmlsZSwgc3VjaCBhcyB0aGUgbWFwcGluZyBxdWFsaXR5IGFuZCBmbGFnLgoKCgpgYGB7cn0KbXkucmVhZHMgPC0gcmVhZEdBbGlnbm1lbnRzKGZpbGU9ImJhbS9NQ0wxLkRHLmJhbSIsCiAgICAgICAgICAgICAgICAgICAgICAgcGFyYW09U2NhbkJhbVBhcmFtKHdoaWNoPWdlbmVyZWdpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoYXQ9Yygic2VxIiwibWFwcSIsImZsYWciKSkpCm15LnJlYWRzCmBgYAoKVGhlIGZsYWcgY2FuIHJlcHJlc2VudCB1c2VmdWwgUUMgaW5mb3JtYXRpb24uIGUuZy4KCiAgKyBSZWFkIGlzIHVubWFwcGVkCiAgKyBSZWFkIGlzIHBhaXJlZCAvIHVucGFpcmVkCiAgKyBSZWFkIGZhaWxlZCBRQwogICsgUmVhZCBpcyBhIFBDUiBkdXBsaWNhdGUgKHNlZSBsYXRlcikKClRoZSBjb21iaW5hdGlvbiBvZiBhbnkgb2YgdGhlc2UgcHJvcGVydGllcyBpcyB1c2VkIHRvIGRlcml2ZSBhIG51bWVyaWMgdmFsdWUsIGFzIGlsbHVzdHJhdGVkIGluIHRoaXMgdXNlZnVsIFtyZXNvdXJjZV0oaHR0cHM6Ly9icm9hZGluc3RpdHV0ZS5naXRodWIuaW8vcGljYXJkL2V4cGxhaW4tZmxhZ3MuaHRtbCkKClBhcnRpY3VsYXIgYXR0cmlidXRlcyBvZiB0aGUgcmVhZHMgY2FuIGJlIGV4dHJhY3RlZCBhbmQgdmlzdWFsaXNlZAoKYGBge3J9Cmhpc3QobWNvbHMobXkucmVhZHMpJG1hcHEpCmBgYAoKSG93ZXZlciwgdGhlcmUgYXJlIG1vcmUtc29waGlzdGljYXRlZCB2aXN1YWxpc2F0aW9uIG9wdGlvbnMgZm9yIGFsaWduZWQgcmVhZHMgYW5kIHJhbmdlIGRhdGEuIFdlIHdpbGwgdXNlIHRoZSBgZ2diaW9gIHBhY2thZ2UsIHdoaWNoIGZpcnN0IHJlcXVpcmVzIHNvbWUgZGlzY3Vzc2lvbiBvZiB0aGUgYGdncGxvdDJgIHBsb3R0aW5nIHBhY2thZ2UuCgoKIyMgQnJpZWYgSW50cm9kdWN0aW9uIHRvIGdncGxvdDIKClRoZSBbYGdncGxvdDJgXShodHRwOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnLykgcGFja2FnZSBoYXMgZW1lcmdlZCBhcyBhbiBhdHRyYWN0aXZlIGFsdGVybmF0aXZlIHRvIHRoZSB0cmFkaXRpb25hbCBwbG90cyBwcm92aWRlZCBieSBiYXNlIFIuIEEgZnVsbCBvdmVydmlldyBvZiBhbGwgY2FwYWJpbGl0aWVzIG9mIHRoZSBwYWNrYWdlIGlzIGF2YWlsYWJsZSBmcm9tIHRoZSBbY2hlYXRzaGVldF0oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTUvMDMvZ2dwbG90Mi1jaGVhdHNoZWV0LnBkZikuCgpBIHNpbXBsZSBzY2F0dGVyIHBsb3QsIGVxdWl2YWxlbnQgdG8gYHBsb3RTbWVhcmAgZnJvbSBiZWZvcmUsIGNhbiBiZSBnZW5lcmF0ZWQgYXMgZm9sbG93czotCgpgYGB7cixmaWcud2lkdGg9MTIsZmlnLmhlaWdodD01fQpsaWJyYXJ5KGdncGxvdDIpCmdncGxvdChyZXN1bHRzLCBhZXMoeCA9IGxvZ0NQTSwgeT1sb2dGQykpICsgZ2VvbV9wb2ludCgpIAoKYGBgCgpJbiBicmllZjotCgotIGByZXN1bHRzYCBpcyBvdXIgZGF0YSBmcmFtZSBjb250YWluaW5nIHRoZSB2YXJpYWJsZXMgd2Ugd2lzaCB0byBwbG90Ci0gYGFlc2AgY3JlYXRlcyBhIG1wcGluZyBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMgaW4gb3VyIGRhdGEgZnJhbWUgdG8gdGhlICphZXMqdGhldGljIHByb3BydGllcyBvZiB0aGUgcGxvdAogICAgKyB0aGUgeC1heGlzIGlzIG1hcHBlZCB0byBgbG9nQ1BNYCwgeS1heGlzIGlzIG1hcHBlZCB0byBgbG9nRkNgCi0gYGdlb21fcG9pbnRgIHNwZWNpZmllcyB0aGUgcGFydGljdWxhciB0eXBlIG9mIHBsb3Qgd2Ugd2FudCAoaW4gdGhpcyBjYXNlIGEgc2NhdHRlciBwbG90KQogICAgKyBzZWUgW3RoZSBjaGVhdHNoZWV0XShodHRwczovL3d3dy5yc3R1ZGlvLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wMy9nZ3Bsb3QyLWNoZWF0c2hlZXQucGRmKSBmb3Igb3RoZXIgcGxvdCB0eXBlcwoKVGhlIHJlYWwgYWR2YW50YWdlIG9mIGBnZ3Bsb3QyYCBpcyB0aGUgYWJpbGl0eSB0byBjaGFuZ2UgdGhlIGFwcGVhcmFuY2Ugb2Ygb3VyIHBsb3QgYnkgbWFwcGluZyBvdGhlciB2YXJpYWJsZXMgdG8gYXNwZWN0cyBvZiB0aGUgcGxvdC4gRm9yIGV4YW1wbGUsIHdlIGNvdWxkIGNvbG91ciB0aGUgcG9pbnRzIGJhc2VkIG9uIGEgcC12YWx1ZSBjdXQtb2ZmLiBUaGUgY29sb3VycyBhcmUgYXV0b21hdGljYWxseSBjaG9zZW4gYnkgYGdncGxvdDJgLCBidXQgd2UgY2FuIHNwZWNpZml5IHBhcnRpY3VsYXIgdmFsdWVzLgoKYGBge3IsZmlnLndpZHRoPTEyLGZpZy5oZWlnaHQ9NX0KZ2dwbG90KHJlc3VsdHMsIGFlcyh4ID0gbG9nQ1BNLCB5PWxvZ0ZDLGNvbD1GRFIgPCAwLjA1KSkgKyBnZW9tX3BvaW50KCkKCmdncGxvdChyZXN1bHRzLCBhZXMoeCA9IGxvZ0NQTSwgeT1sb2dGQyxjb2w9RkRSIDwgMC4wNSkpICsgZ2VvbV9wb2ludChhbHBoYT0wLjQpICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygiYmxhY2siLCJyZWQiKSkKYGBgCgpUaGUgdm9sY2FubyBwbG90IGNhbiBiZSBjb25zdHJ1Y3RlZCBpbiBhIHNpbWlsYXIgbWFubmVyCgpgYGB7cixmaWcud2lkdGg9MTIsZmlnLmhlaWdodD01fQpnZ3Bsb3QocmVzdWx0cywgYWVzKHggPSBsb2dGQywgeT0tbG9nMTAoRkRSKSkpICsgZ2VvbV9wb2ludCgpCmBgYAoKCiMjIENvbXBvc2luZyBwbG90cyB3aXRoIGdnYmlvCgpXZSB3aWxsIG5vdyB0YWtlIGEgYnJpZWYgbG9vayBhdCBvbmUgb2YgdGhlIHZpc3VhbGlzYXRpb24gcGFja2FnZXMgaW4gQmlvY29uZHVjdG9yIHRoYXQgdGFrZXMgYWR2YW50YWdlCm9mIHRoZSBHZW5vbWljUmFuZ2VzIGFuZCBHZW5vbWljRmVhdHVyZXMgb2JqZWN0LXR5cGVzLiBJbiB0aGlzIHNlY3Rpb24gd2Ugd2lsbCBzaG93IGEgd29ya2VkCmV4YW1wbGUgb2YgaG93IHRvIGNvbWJpbmUgc2V2ZXJhbCB0eXBlcyBvZiBnZW5vbWljIGRhdGEgb24gdGhlIHNhbWUgcGxvdC4gVGhlIGRvY3VtZW50YXRpb24gZm9yCmdnYmlvIGlzIHZlcnkgZXh0ZW5zaXZlIGFuZCBjb250YWlucyBsb3RzIG9mIGV4YW1wbGVzLgoKaHR0cDovL3d3dy50ZW5nZmVpLm5hbWUvZ2diaW8vZG9jcy8KClRoZSBgR3ZpemAgcGFja2FnZSBpcyBhbm90aGVyIEJpb2NvbmR1Y3RvciBwYWNrYWdlIHRoYXQgc3BlY2lhbGlzaW5nIGluIGdlbm9taWMgdmlzdWFsaXNhdGlvbnMsIGJ1dCB3ZQp3aWxsIG5vdCBleHBsb3JlIHRoaXMgcGFja2FnZSBpbiB0aGUgY291cnNlLgoKVGhlIE1hbmhhdHRhbiBwbG90IGlzIGEgY29tbW9uIHdheSBvZiB2aXN1YWxpc2luZyBnZW5vbWUtd2lkZSByZXN1bHRzLCBlc3BlY2lhbGx5IHdoZW4gb25lIGlzIGNvbmNlcm5lZCB3aXRoIHRoZSByZXN1bHRzIG9mIGEgR1dBUyBzdHVkeSBhbmQgaWRlbnRpZnlpbmcgc3Ryb25nbHktYXNzb2NpYXRlZCBoaXRzLiAKClRoZSBwcm9maWxlIGlzIHN1cHBvc2VkIHRvIHJlc2VtYmxlIHRoZSBNYW5oYXR0YW4gc2t5bGluZSB3aXRoIHBhcnRpY3VsYXIgc2t5c2NyYXBlcnMgdG93ZXJpbmcgYWJvdXQgdGhlIGxvd2VyIGxldmVsIGJ1aWxkaW5ncy4KCiFbXShodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zLzEvMTIvTWFuaGF0dGFuX1Bsb3QucG5nKQpUaGlzIHR5cGUgb2YgcGxvdCBpcyBpbXBsZW1lbnRlZCBhcyB0aGUgYHBsb3RHcmFuZExpbmVhcmAgZnVuY3Rpb24uIFdlIGhhdmUgdG8gc3VwcGx5IGEgdmFsdWUgdG8gZGlzcGxheSBvbiB0aGUgeS1heGlzIHVzaW5nIHRoZSBgYWVzYCBmdW5jdGlvbiwKd2hpY2ggaXMgaW5oZXJpdGVkIGZyb20gZ2dwbG90Mi4gVGhlIHBvc2l0aW9uaW5nIG9mIHBvaW50cyBvbiB0aGUgeC1heGlzIGlzIGhhbmRsZWQgYXV0b21hdGljYWxseSBieQpnZ2JpbywgdXNpbmcgdGhlIHJhbmdlcyBpbmZvcm1hdGlvbiB0byBnZXQgdGhlIGdlbm9taWMgY29vcmRpbmF0ZXMgb2YgdGhlIHJhbmdlcyBvZiBpbnRlcmVzdC4KClRvIHN0b3AgdGhlIHBsb3RzIGZyb20gYmVpbmcgdG9vIGNsdXR0ZXJlZCB3ZSB3aWxsIGNvbnNpZGVyIHRoZSB0b3AgMjAwIGdlbmVzIG9ubHkuCgpgYGB7cixmaWcud2lkdGg9MTIsZmlnLmhlaWdodD01fQpsaWJyYXJ5KGdnYmlvKQp0b3AyMDAgPC0gc2lnUmVnaW9uc1tvcmRlcihzaWdSZWdpb25zJExSLGRlY3JlYXNpbmcgPSBUUlVFKVsxOjIwMF1dCgpwbG90R3JhbmRMaW5lYXIodG9wMjAwICwgYWVzKHkgPSBsb2dGQykpCgpgYGAKCmBnZ2Jpb2AgaGFzIGFsdGVybmF0ZWQgdGhlIGNvbG91cnMgb2YgdGhlIGNocm9tb3NvbWVzLiBIb3dldmVyLCBhbiBhcHBlYWxpbmcgZmVhdHVyZSBvZiBgZ2dwbG90MmAgaXMgdGhlIGFiaWxpdHkgdG8gbWFwIHByb3BlcnRpZXMgb2YgeW91ciBwbG90IHRvIHZhcmlhYmxlcyBwcmVzZW50IGluIHlvdXIgZGF0YS4gRm9yIGV4YW1wbGUsIHdlIGNvdWxkIGNyZWF0ZSBhIHZhcmlhYmxlIHRvIGRpc3Rpbmd1aXNoIGJldHdlZW4gdXAtIGFuZCBkb3duLXJlZ3VsYXRlZCBnZW5lcy4gVGhlIHZhcmlhYmxlcyB1c2VkIGZvciBhZXN0aGV0aWMgbWFwcGluZyBtdXN0IGJlIHByZXNlbnQgaW4gdGhlIGBtY29sc2Agc2VjdGlvbiBvZiB5b3VyIHJhbmdlcyBvYmplY3QuCgpgYGB7cixmaWcud2lkdGg9MTIsZmlnLmhlaWdodD01fQptY29scyh0b3AyMDApJFVwUmVndWxhdGVkIDwtIG1jb2xzKHRvcDIwMCkkbG9nRkMgPiAwCgpwbG90R3JhbmRMaW5lYXIodG9wMjAwLCBhZXMoeSA9IGxvZ0ZDLCBjb2wgPSBVcFJlZ3VsYXRlZCkpCmBgYAoKYHBsb3RHcmFuZExpbmVhcmAgaXMgYSBzcGVjaWFsIGZ1bmN0aW9uIGluIGBnZ2Jpb2Agd2l0aCBwcmVzZXQgb3B0aW9ucyBmb3IgdGhlIG1hbmhhdHRhbiBzdHlsZSBvZiBwbG90LiBNb3JlIG9mdGVuLCB1c2VycyB3aWxsIGNhbGwgdGhlIGBhdXRvcGxvdGAgZnVuY3Rpb24gYW5kIGBnZ2Jpb2Agd2lsbCBjaG9vc2UgdGhlIG1vc3QgYXBwcm9wcmlhdGUgbGF5b3V0LiBPbmUgc3VjaCBsYXlvdXQgaXMgdGhlICprYXJ5b2dyYW0qLiAKCmBgYHtyLGZpZy53aWR0aD0xMixmaWcuaGVpZ2h0PTV9CgphdXRvcGxvdCh0b3AyMDAsbGF5b3V0PSJrYXJ5b2dyYW0iLGFlcyhjb2xvcj1VcFJlZ3VsYXRlZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbD1VcFJlZ3VsYXRlZCkpCgpgYGAKCgoKYGdnYmlvYCBpcyBhbHNvIGFibGUgdG8gcGxvdCB0aGUgc3RydWN0dXJlIG9mIGdlbmVzIGFjY29yZGluZyB0byBhIHBhcnRpY3VsYXIgbW9kZWwgcmVwcmVzZW50ZWQgYnkgYSBgR2Vub21pY0ZlYXR1cmVzYCBvYmplY3QsIHN1Y2ggYXMgdGhlIG9iamVjdCB3ZSBjcmVhdGVkIGVhcmxpZXIgd2l0aCB0aGUgZXhvbiBjb29yZGluYXRlcyBmb3IgZWFjaCBnZW5lIGluIHRoZSBtbTEwIGdlbm9tZS4KCgpgYGB7cn0KYXV0b3Bsb3QodHgsIHdoaWNoPWV4b1tbIjExMDMwOCJdXSkKYGBgCgpXZSBjYW4gZXZlbiBwbG90IHRoZSBsb2NhdGlvbiBvZiBzZXF1ZW5jaW5nIHJlYWRzIGlmIHRoZXkgaGF2ZSBiZWVuIGltcG9ydGVkIHVzaW5nIHJlYWRHQWxpZ25tZW50cyBmdW5jdGlvbiAob3Igc2ltaWxhcikuIFdlIGNhbiBhbHNvIGFkZCBzb21lIGZsYW5raW5nIHJlZ2lvbiBhcm91bmQgdGhlIGdlbmUgaWYgd2Ugd2lzaC4KCmBgYHtyfQpteXJlZyA8LSBmbGFuayhyZWR1Y2UoZXhvW1siMTEwMzA4Il1dKSwgMTAwMCwgYm90aCA9IFQpCmJhbSA8LSByZWFkR0FsaWdubWVudHMoZmlsZT0iYmFtL01DTDEuREcuYmFtIiwKICAgICAgICAgICAgICAgICAgICAgICBwYXJhbT1TY2FuQmFtUGFyYW0od2hpY2g9bXlyZWcpLHVzZS5uYW1lcyA9IFRSVUUpCgphdXRvcGxvdChiYW0sd2hpY2g9bXlyZWcpCmBgYAoKYGBge3J9CmF1dG9wbG90KGJhbSAsIHN0YXQgPSAiY292ZXJhZ2UiKQpgYGAKTGlrZSBnZ3Bsb3QyLCBnZ2JpbyBwbG90cyBjYW4gYmUgc2F2ZWQgYXMgb2JqZWN0cyB0aGF0IGNhbiBsYXRlciBiZSBtb2RpZmllZCwgb3IgY29tYmluZWQgdG9nZXRoZXIgdG8KZm9ybSBtb3JlIGNvbXBsaWNhdGVkIHBsb3RzLiBJZiBzYXZlZCBpbiB0aGlzIHdheSwgdGhlIHBsb3Qgd2lsbCBvbmx5IGJlIGRpc3BsYXllZCBvbiBhIHBsb3R0aW5nIGRldmljZQp3aGVuIHdlIHF1ZXJ5IHRoZSBvYmplY3QuIFRoaXMgc3RyYXRlZ3kgaXMgdXNlZnVsIHdoZW4gd2Ugd2FudCB0byBhZGQgYSBjb21tb24gZWxlbWVudCAoc3VjaCBhcwphbiBpZGVvZ3JhbSkgdG8gYSBwbG90IGNvbXBvc2l0aW9uIGFuZCBkb27igJl0IHdhbnQgdG8gcmVwZWF0IHRoZSBjb2RlIHRvIGdlbmVyYXRlIHRoZSBwbG90IGV2ZXJ5IHRpbWUuCgpgYGB7cn0KI2lkUGxvdCA8LSBwbG90SWRlb2dyYW0oZ2Vub21lID0gIm1tMTAiLHN1YmNociA9ICJjaHIxIikKI2lkUGxvdApnZW5lTW9kIDwtIGF1dG9wbG90KHR4LCB3aGljaCA9IG15cmVnKQpyZWFkcy5NQ0wxLkRHIDwtIGF1dG9wbG90KGJhbSwgc3RhdCA9ICJjb3ZlcmFnZSIpICsgbGFicyh0aXRsZT0iTUNMMS5ERyIpCnRyYWNrcyhtbTEwPWdlbmVNb2QsIE1DTDEuREc9cmVhZHMuTUNMMS5ERyApIApgYGAKCj4gIyMgQ2hhbGxlbmdlIHsuY2hhbGxlbmdlfQo+Cj4gQ3JlYXRlIHRyYWNrcyB0byBjb21wYXJlIHRoZSBjb3ZlcmFnZSBvZiB0aGUgZ2VuZSBLcnQ1IGZvciB0aGUgc2FtcGxlcyBNQ0wxLkRHLCBNQ0wxLkRILCBNQ0wxLkxBIGFuZCBNQ0wxLkxCCj4KCmBgYHtyLGVjaG89RkFMU0UsZmlnLmhlaWdodD01LGZpZy53aWR0aD0xMH0KYmFtIDwtIHJlYWRHQWxpZ25tZW50cyhmaWxlPSJiYW0vTUNMMS5ERy5iYW0iLAogICAgICAgICAgICAgICAgICAgICAgIHBhcmFtPVNjYW5CYW1QYXJhbSh3aGljaD1teXJlZyksdXNlLm5hbWVzID0gVFJVRSkKcmVhZHMuTUNMMS5ERyA8LSBhdXRvcGxvdChiYW0sIHN0YXQgPSAiY292ZXJhZ2UiKQoKYmFtIDwtIHJlYWRHQWxpZ25tZW50cyhmaWxlPSJiYW0vTUNMMS5ESC5iYW0iLAogICAgICAgICAgICAgICAgICAgICAgIHBhcmFtPVNjYW5CYW1QYXJhbSh3aGljaD1teXJlZyksdXNlLm5hbWVzID0gVFJVRSkKcmVhZHMuTUNMMS5ESCA8LSBhdXRvcGxvdChiYW0sIHN0YXQgPSAiY292ZXJhZ2UiKQoKCmJhbSA8LSByZWFkR0FsaWdubWVudHMoZmlsZT0iYmFtL01DTDEuTEEuYmFtIiwKICAgICAgICAgICAgICAgICAgICAgICBwYXJhbT1TY2FuQmFtUGFyYW0od2hpY2g9bXlyZWcpLHVzZS5uYW1lcyA9IFRSVUUpCnJlYWRzLk1DTDEuTEEgPC0gYXV0b3Bsb3QoYmFtLCBzdGF0ID0gImNvdmVyYWdlIikKCmJhbSA8LSByZWFkR0FsaWdubWVudHMoZmlsZT0iYmFtL01DTDEuTEIuYmFtIiwKICAgICAgICAgICAgICAgICAgICAgICBwYXJhbT1TY2FuQmFtUGFyYW0od2hpY2g9bXlyZWcpLHVzZS5uYW1lcyA9IFRSVUUpCnJlYWRzLk1DTDEuTEIgPC0gYXV0b3Bsb3QoYmFtLCBzdGF0ID0gImNvdmVyYWdlIikKCgp0cmFja3MobW0xMD1nZW5lTW9kLCBNQ0wxLkRHPXJlYWRzLk1DTDEuREcsIE1DTDEuRGg9cmVhZHMuTUNMMS5ESCwgTUNMMS5MQT1yZWFkcy5NQ0wxLkxBLCBNQ0wxLkxCPXJlYWRzLk1DTDEuTEIpIAoKYGBgCgo=