Outline

  • importing reads from a .bam file into R
  • exploring a .bam file using Bioconductor
  • useful objects and functions for dealing with genomic intervals

Processing aligned reads with R and Bioconductor

We will now look at how we can represent and access genomic intervals in R and Bioconductor which fit best within the framework of the course. There are also tools outside of R that are extremely powerful; such as bedtools which are worth considering if you want to go further with NGS analysis.

Importing aligned data

We can import reads from a .bam file into Bioconductor. However, to set expectations we are not going to be processing the entire set of reads from a whole-genome sequencing run in this manner. This can be a useful way of diving-into a particular region of interest and exploring the data.

A package that can be used to parse a .bam file is GenomicAlignments. You should notice that although the .bam file is not particularly big (~ 12.5 Million reads), but already takes a little while to read.

library(GenomicAlignments)
mybam <- readGAlignments("/data/test/paired.bam")
mybam
GAlignments object with 12581680 alignments and 0 metadata columns:
             seqnames strand       cigar    qwidth     start       end     width     njunc
                <Rle>  <Rle> <character> <integer> <integer> <integer> <integer> <integer>
         [1]        1      +       1S67M        68     10003     10069        67         0
         [2]        1      +         68M        68     10036     10103        68         0
         [3]        1      +         68M        68     10036     10103        68         0
         [4]        1      +         68M        68     10041     10108        68         0
         [5]        1      -         68M        68     10041     10108        68         0
         ...      ...    ...         ...       ...       ...       ...       ...       ...
  [12581676]   hs37d5      -         68M        68  35465836  35465903        68         0
  [12581677]   hs37d5      +         68M        68  35466096  35466163        68         0
  [12581678]   hs37d5      -    47M1D21M        68  35466133  35466201        69         0
  [12581679]   hs37d5      +         68M        68  35466270  35466337        68         0
  [12581680]   hs37d5      -         68M        68  35466357  35466424        68         0
  -------
  seqinfo: 86 sequences from an unspecified genome

readGAlignments has provided us with an object that can be manipulated using the standard vector conventions.

mybam[1:10]
GAlignments object with 10 alignments and 0 metadata columns:
       seqnames strand        cigar    qwidth     start       end     width     njunc
          <Rle>  <Rle>  <character> <integer> <integer> <integer> <integer> <integer>
   [1]        1      +        1S67M        68     10003     10069        67         0
   [2]        1      +          68M        68     10036     10103        68         0
   [3]        1      +          68M        68     10036     10103        68         0
   [4]        1      +          68M        68     10041     10108        68         0
   [5]        1      -          68M        68     10041     10108        68         0
   [6]        1      +          68M        68     10041     10108        68         0
   [7]        1      -          68M        68     10046     10113        68         0
   [8]        1      +      6M1I61M        68     10103     10169        67         0
   [9]        1      - 38M1D3M1D27M        68     10108     10177        70         0
  [10]        1      -     36M1D32M        68     10110     10178        69         0
  -------
  seqinfo: 86 sequences from an unspecified genome

There are also a number of accessor functions that can get particular items from the object; cigar to obtain the CIGAR strings, start / end to get the start and end positions, width to get the width of each read.

## space to try out some of the accessor functions

The object we have created, bam, contains only a small amount of the information available in a .bam file. If we wish we can import extra fields such as the read sequence, mapping quality and flag:-

bam <- readGAlignments("/data/test/paired.bam",param=ScanBamParam(what=c("seq","mapq","flag","isize","qual")))
bam[1:3]
GAlignments object with 3 alignments and 5 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]        1      +       1S67M        68     10003     10069        67         0 | GACCCTGACC...CTAACCCTAA         6       163
  [2]        1      +         68M        68     10036     10103        68         0 | CTAAGCCTAA...CCCGAACGCT         5        99
  [3]        1      +         68M        68     10036     10103        68         0 | CTAACCCTAA...CCCTAACCCT        14        99
          isize                    qual
      <integer>          <PhredQuality>
  [1]       105 S=<====<<>...?=<=>?>?=Q
  [2]        77 ##########...##########
  [3]       141 S=;?<>>><>...=>@@=?>@?Q
  -------
  seqinfo: 86 sequences from an unspecified genome

The command takes longer to run, but we get more detail on each of the reads. The extra fields make up the metadata for each read and can be accessed using the mcols function. If we save this metadata as an object, we can treat it as a data frame and therefore have the usual $ operator to access the columns

meta <- mcols(bam)
meta
DataFrame with 12581680 rows and 5 columns
                             seq      mapq      flag     isize                    qual
                  <DNAStringSet> <integer> <integer> <integer>          <PhredQuality>
1        GACCCTGACC...CTAACCCTAA         6       163       105 S=<====<<>...?=<=>?>?=Q
2        CTAAGCCTAA...CCCGAACGCT         5        99        77 ##########...##########
3        CTAACCCTAA...CCCTAACCCT        14        99       141 S=;?<>>><>...=>@@=?>@?Q
4        CCTAACCCTA...ACCCTAACCC        15       163       137 S==:=;=<=;...;=<>1;>>@P
5        CCTAACCCTA...ACCCTAACCC        18        83      -105 ##########...:>>>9<:>?N
...                          ...       ...       ...       ...                     ...
12581676 ATTCCATTCC...AGTGCAGATT        29       147      -183 P?1??=><?;...=<<====9<S
12581677 CACTTCATCA...CCATTCGATT        24        99       105 S=<>=><<>=...??>=?@8?=P
12581678 TTCGAATCTT...TACCATTCGA        13       147      -105 O@9==<=>??...;<>=;==7=S
12581679 ATTCGATTCC...CCATTTGATT        32       163       154 S<==6<<==>...>?>=?>?>>S
12581680 TCCGTTTGAT...ATTCCATTTG        31        83      -154 S@8>??>?=@...<>=?><>>=S

The sequences are stored in Biostrings format

meta$seq
  A DNAStringSet instance of length 12581680
           width seq
       [1]    68 GACCCTGACCCTAACCCTGACCCTGACCCTAACCCTGACCCTGACCCTAACCCTGACCCTAACCCTAA
       [2]    68 CTAAGCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTCAGCCTAACCCGAACGCT
       [3]    68 CTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCT
       [4]    68 CCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCC
       [5]    68 CCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCC
       ...   ... ...
[12581676]    68 ATTCCATTCCCTTCCTTTCTTTCTTCAGGATCTCACTCTGTCACCCAGTATGGAGTGAAGTGCAGATT
[12581677]    68 CACTTCATCACATTCCATTCGATTTCATTCTACACCATTCGAATCTTTCAATTTCATTCCATTCGATT
[12581678]    68 TTCGAATCTTTCAATTTCATTCCATTCGATTCCATTAGATTCCATTTTTTCCATTCGATACCATTCGA
[12581679]    68 ATTCGATTCCTTTTGATTCCATATGATTCAATTTGATTCAATTGGATTTGATTCAAAACCATTTGATT
[12581680]    68 TCCGTTTGATTCCATTCCATTCGATTCCGTTCCATTCCATTCCACTCCGTTCCATTTCATTCCATTTG

The flags should be valid values as explained online

table(meta$flag)

     83      99     147     163    1107    1123    1171    1187 
3074883 3127385 3127385 3074312   43835   45023   45023   43834 

Finally the mapping qualities are numeric quantities that will vary according to aligner. Mapping qualities of 0 are usually reserved for reads that map to multiple locations. Many calling algorithms will employ a filter on mapping quality; with values of 10 to 20 typically used to discard reads

summary(meta$mapq)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1.00   30.00   39.00   32.87   40.00   53.00 



Exercise

  • How many reads have all bases mapping to the genome
    • what percentage of the total number of reads is this?
  • Visualise the distribution of mapping qualities for this set of reads
  • How many reads would be removed at a cut-off of 10 or 20?
  • What would seem to be a reasonable cut-off to discard poor quality reads?
  • How many reads are classified as PCR-duplicates?
    • HINT: Use the URL mentioned above to find out the explanation of flags observed in our data



Rather than taking a whole-genome view, we often want to view the reads for a particular gene or region of interest. This we can do using the functions we have already seen.

my.reads <- which(seqnames(mybam)=="17" & start(mybam) > 7577851 & end(mybam) < 7598063)
mybam[my.reads]
GAlignments object with 224 alignments and 0 metadata columns:
        seqnames strand       cigar    qwidth     start       end     width     njunc
           <Rle>  <Rle> <character> <integer> <integer> <integer> <integer> <integer>
    [1]       17      +         76M        76   7577918   7577993        76         0
    [2]       17      -         76M        76   7577930   7578005        76         0
    [3]       17      +         76M        76   7578191   7578266        76         0
    [4]       17      +         68M        68   7578195   7578262        68         0
    [5]       17      +         68M        68   7578203   7578270        68         0
    ...      ...    ...         ...       ...       ...       ...       ...       ...
  [220]       17      -         76M        76   7596411   7596486        76         0
  [221]       17      +         68M        68   7596523   7596590        68         0
  [222]       17      -         68M        68   7596639   7596706        68         0
  [223]       17      +       67M1S        68   7597833   7597899        67         0
  [224]       17      -         68M        68   7597888   7597955        68         0
  -------
  seqinfo: 86 sequences from an unspecified genome

However, there are much more efficient ways to do such queries in Bioconductor as we will see later in the course

GenomicAlignments is part of a family of packages that provide object-types and functionality for dealing with genomic intervals; which are described in a PLoS Computational Biology paper from 2013.

The basic type of interval we can define is an IRanges object. There is an extensive list of operations that we can perform on this object

Extracting a subset of reads

We might want to focus on a subset of reads from the start, is when we want to analyse a particular gene. Provided that the .bam file has been indexed (creating a .bam.bai file in the same directory), we can very quickly jump to a particular genomic region.

First we need to define a region:-

mygene <- GRanges("17", ranges=IRanges(7577851, 7598063),strand="+")
mygene
GRanges object with 1 range and 0 metadata columns:
      seqnames             ranges strand
         <Rle>          <IRanges>  <Rle>
  [1]       17 [7577851, 7598063]      +
  -------
  seqinfo: 1 sequence from an unspecified genome; no seqlengths

The region filer can be used in conjuction with the what argument to ScanBamParam function to provide a detailed picture of the reads for your gene.

mygene.reads <- readGAlignments(file="/data/test/paired.bam",
                                param=ScanBamParam(which=mygene,
                                                   what=c("seq","mapq","flag","qual","isize")
                                                   )
                                )
mygene.reads
GAlignments object with 225 alignments and 5 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]       17      +         76M        76   7577918   7577993        76         0 | TCCGCCTGCC...GGGAGGCCCT        37       163
    [2]       17      -         76M        76   7577930   7578005        76         0 | GGCCTCCCAA...GCCTCTGTAA        31        83
    [3]       17      +         76M        76   7578191   7578266        76         0 | AGGGCACCAC...CCACTCGGAT        40        99
    [4]       17      +         68M        68   7578195   7578262        68         0 | CACCACCACA...CCTTCCACTC        40       163
    [5]       17      +         68M        68   7578203   7578270        68         0 | CACTATGTCG...TCGGATAAGA        40        99
    ...      ...    ...         ...       ...       ...       ...       ...       ... .                     ...       ...       ...
  [221]       17      +         68M        68   7596523   7596590        68         0 | CAAAAAATCA...CTAGAGGTCT        30       163
  [222]       17      -         68M        68   7596639   7596706        68         0 | CCTGTCAAAT...CCCCTGCCTC        25        83
  [223]       17      +       67M1S        68   7597833   7597899        67         0 | TTACAGGCGT...CTGGGGGGGG        28        99
  [224]       17      -         68M        68   7597888   7597955        68         0 | TGGCTGGTGT...GAATGCTTGA        35       147
  [225]       17      +         68M        68   7598046   7598113        68         0 | TGAGGTGGGA...CCATTGTACT        38       163
                           qual     isize
                 <PhredQuality> <integer>
    [1] H;9476;:9:...@?9<085==J        87
    [2] ##########...>?><><<<>S       -87
    [3] S=>>=<<><<...?>==??8??P       202
    [4] Q<;=<;=<<=...@??>??=>?S        97
    [5] M<;>;;?=>7...@@4;>:<??I       143
    ...                     ...       ...
  [221] S<<><=<;=<...?#########       183
  [222] ##########...##==:>>><L      -183
  [223] S=;;<@==8;...==?;>2%?##       122
  [224] Q>:?>6<><=...==:;<<=;=Q      -122
  [225] O===>;=>>=...##########       124
  -------
  seqinfo: 86 sequences from an unspecified genome

Data Summaries

We will now look into the couple of ways that we can summarise the data that will motivate some of the methods to come in the rest of the course.

Firstly, we can compute how many bases are observed at each position using the pileupAsGRanges convenience function from the biovizBase package.

  • the output is a summary of base counts for each position, and an overall depth
  • the function is also able to do clever things such as excluding reads with low mapping quality or PCR duplicates
library(biovizBase)
baseSummary <- pileupAsGRanges("/data/test/paired.bam",regions = mygene)                         
baseSummary
GRanges object with 4482 ranges and 7 metadata columns:
         seqnames             ranges strand |         A         C         G         T         N     depth                   bam
            <Rle>          <IRanges>  <Rle> | <integer> <integer> <integer> <integer> <integer> <numeric>                 <Rle>
     [1]       17 [7577918, 7577918]      + |         0         0         0         1         0         1 /data/test/paired.bam
     [2]       17 [7577919, 7577919]      + |         0         1         0         0         0         1 /data/test/paired.bam
     [3]       17 [7577920, 7577920]      + |         0         1         0         0         0         1 /data/test/paired.bam
     [4]       17 [7577921, 7577921]      + |         0         0         1         0         0         1 /data/test/paired.bam
     [5]       17 [7577922, 7577922]      + |         0         1         0         0         0         1 /data/test/paired.bam
     ...      ...                ...    ... .       ...       ...       ...       ...       ...       ...                   ...
  [4478]       17 [7598059, 7598059]      + |         0         0         0         1         0         1 /data/test/paired.bam
  [4479]       17 [7598060, 7598060]      + |         0         1         0         0         0         1 /data/test/paired.bam
  [4480]       17 [7598061, 7598061]      + |         0         0         1         0         0         1 /data/test/paired.bam
  [4481]       17 [7598062, 7598062]      + |         0         1         0         0         0         1 /data/test/paired.bam
  [4482]       17 [7598063, 7598063]      + |         0         0         0         1         0         1 /data/test/paired.bam
  -------
  seqinfo: 1 sequence from an unspecified genome; no seqlengths
  • the output is a GenomicRanges object with metadata corresponding to the base counts and depth
  • as before we can extract the metadata with the mcols function
meta <- mcols(baseSummary)



Exercise

  • Use the information in the meta data frame to plot the read coverage over this genomic region
  • Navigate to chr17:7577851-7598063 in IGV
    • does the plot roughly agree?
  • How many positions in this region have a total depth of over 10?



  • We can determine which base was called at each position we can use the following R magic
    • here apply is used to run the same function on each row of the data frame
    • which.max returns the index of the highest value in each row
    • mycalls is then a vector of “calls” for each base; the same length as the genomic region
mycalls <- apply(meta[,c("A","T","C","G")], 1,
      function(x) names(x)[which.max(x)]
      )
# A test to make sure that it's worked for the first few rows
mycalls[1:5]
[1] "T" "C" "C" "G" "C"
baseSummary[1:5]
GRanges object with 5 ranges and 7 metadata columns:
      seqnames             ranges strand |         A         C         G         T         N     depth                   bam
         <Rle>          <IRanges>  <Rle> | <integer> <integer> <integer> <integer> <integer> <numeric>                 <Rle>
  [1]       17 [7577918, 7577918]      + |         0         0         0         1         0         1 /data/test/paired.bam
  [2]       17 [7577919, 7577919]      + |         0         1         0         0         0         1 /data/test/paired.bam
  [3]       17 [7577920, 7577920]      + |         0         1         0         0         0         1 /data/test/paired.bam
  [4]       17 [7577921, 7577921]      + |         0         0         1         0         0         1 /data/test/paired.bam
  [5]       17 [7577922, 7577922]      + |         0         1         0         0         0         1 /data/test/paired.bam
  -------
  seqinfo: 1 sequence from an unspecified genome; no seqlengths

Another piece of information that would be useful at this point is to determine what the reference base is at each position. This can be achieved using the one of the pre-built genome packages in Bioconductor

  • we will encounter several annotation packages during the course; the full list is given on the Bioconductor website
  • you need to make sure to select the appropriate genome build
    • and organism
  • these packages are installed in the usual fashion with biocLite
    • they are much larger than standard Bioconductor software packages
    • essentially they comprise a pre-built database
### Don't run this during the course
source("https://bioconductor.org/biocLite.R")
biocLite("BSgenome.Hsapiens.UCSC.hg19")

Some basic information about the package can be obtained by typing the object name

library("BSgenome.Hsapiens.UCSC.hg19")
Loading required package: BSgenome
Loading required package: rtracklayer
hg19 <- BSgenome.Hsapiens.UCSC.hg19
hg19
Human genome:
# organism: Homo sapiens (Human)
# provider: UCSC
# provider version: hg19
# release date: Feb. 2009
# release name: Genome Reference Consortium GRCh37
# 93 sequences:
#   chr1                  chr2                  chr3                  chr4                  chr5                  chr6                 
#   chr7                  chr8                  chr9                  chr10                 chr11                 chr12                
#   chr13                 chr14                 chr15                 chr16                 chr17                 chr18                
#   chr19                 chr20                 chr21                 chr22                 chrX                  chrY                 
#   chrM                  chr1_gl000191_random  chr1_gl000192_random  chr4_ctg9_hap1        chr4_gl000193_random  chr4_gl000194_random 
#   ...                   ...                   ...                   ...                   ...                   ...                  
#   chrUn_gl000223        chrUn_gl000224        chrUn_gl000225        chrUn_gl000226        chrUn_gl000227        chrUn_gl000228       
#   chrUn_gl000229        chrUn_gl000230        chrUn_gl000231        chrUn_gl000232        chrUn_gl000233        chrUn_gl000234       
#   chrUn_gl000235        chrUn_gl000236        chrUn_gl000237        chrUn_gl000238        chrUn_gl000239        chrUn_gl000240       
#   chrUn_gl000241        chrUn_gl000242        chrUn_gl000243        chrUn_gl000244        chrUn_gl000245        chrUn_gl000246       
#   chrUn_gl000247        chrUn_gl000248        chrUn_gl000249                                                                         
# (use 'seqnames()' to see all the sequence names, use the '$' or '[[' operator to access a given sequence)
  • there is a convenient getSeq function that can obtain the genome sequence for a given genomic region
  • however, there is a problem when we try and run this using the region we created previously
    • can you work out why?
refSeq <- getSeq(hg19, baseSummary)
refSeq
  • a solution to this common headache is provided by the renameSeqlevels function
refSeq <- getSeq(hg19, renameSeqlevels(baseSummary, c("17"="chr17")))
refSeq
  A DNAStringSet instance of length 4482
       width seq
   [1]     1 T
   [2]     1 C
   [3]     1 C
   [4]     1 G
   [5]     1 C
   ...   ... ...
[4478]     1 T
[4479]     1 C
[4480]     1 G
[4481]     1 C
[4482]     1 T



Exercise

  • How many positions have a “call” different from the reference genome? HINT: what does the R code mycalls == refSeq return?
  • How many of these positions have a depth greater than 10?
  • Verify your result in IGV



Summary

We have explored the properties of bam files using Bioconductor. The techniques and types of object we have learnt about will crop-up again-and-again in the course. The vast majority of NGS analysis tools in Bioconductor will use GenomicRanges objects in some form.

Due to the high-volume of the dataset, some of the tools and pipelines we use will not be in R. However, you will still be able to interrogate the results you obtain and explore them in more detail using R.

LS0tCnRpdGxlOiAiVW5kZXJzdGFuZGluZyBBbGlnbm1lbnRzIgphdXRob3I6ICJNYXJrIER1bm5pbmciCmRhdGU6ICdgciBmb3JtYXQoU3lzLnRpbWUoKSwgIkxhc3QgbW9kaWZpZWQ6ICVkICViICVZIilgJwpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCi0tLQoKIyBPdXRsaW5lCgotIGltcG9ydGluZyByZWFkcyBmcm9tIGEgYC5iYW1gIGZpbGUgaW50byBSCi0gZXhwbG9yaW5nIGEgYC5iYW1gIGZpbGUgdXNpbmcgQmlvY29uZHVjdG9yCi0gdXNlZnVsIG9iamVjdHMgYW5kIGZ1bmN0aW9ucyBmb3IgZGVhbGluZyB3aXRoIGdlbm9taWMgaW50ZXJ2YWxzCgogCiMgPGEgbmFtZT0iYmlvYyI+PC9hPiBQcm9jZXNzaW5nIGFsaWduZWQgcmVhZHMgd2l0aCBSIGFuZCBCaW9jb25kdWN0b3IKCldlIHdpbGwgbm93IGxvb2sgYXQgaG93IHdlIGNhbiByZXByZXNlbnQgYW5kIGFjY2VzcyBnZW5vbWljIGludGVydmFscyBpbiBSIGFuZCBCaW9jb25kdWN0b3Igd2hpY2ggZml0IGJlc3Qgd2l0aGluIHRoZSBmcmFtZXdvcmsgb2YgdGhlIGNvdXJzZS4gVGhlcmUgYXJlIGFsc28gdG9vbHMgb3V0c2lkZSBvZiBSIHRoYXQgYXJlIGV4dHJlbWVseSBwb3dlcmZ1bDsgc3VjaCBhcyBbKioqYmVkdG9vbHMqKipdKGh0dHA6Ly9iZWR0b29scy5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvKSB3aGljaCBhcmUgd29ydGggY29uc2lkZXJpbmcgaWYgeW91IHdhbnQgdG8gZ28gZnVydGhlciB3aXRoIE5HUyBhbmFseXNpcy4KCgojIyBJbXBvcnRpbmcgYWxpZ25lZCBkYXRhCgpXZSBjYW4gaW1wb3J0IHJlYWRzIGZyb20gYSBgLmJhbWAgZmlsZSBpbnRvIEJpb2NvbmR1Y3Rvci4gSG93ZXZlciwgdG8gc2V0IGV4cGVjdGF0aW9ucyB3ZSBhcmUgbm90IGdvaW5nIHRvIGJlIHByb2Nlc3NpbmcgdGhlIGVudGlyZSBzZXQgb2YgcmVhZHMgZnJvbSBhIHdob2xlLWdlbm9tZSBzZXF1ZW5jaW5nIHJ1biBpbiB0aGlzIG1hbm5lci4gVGhpcyBjYW4gYmUgYSB1c2VmdWwgd2F5IG9mIGRpdmluZy1pbnRvIGEgcGFydGljdWxhciByZWdpb24gb2YgaW50ZXJlc3QgYW5kIGV4cGxvcmluZyB0aGUgZGF0YS4KCkEgcGFja2FnZSB0aGF0IGNhbiBiZSB1c2VkIHRvIHBhcnNlIGEgYC5iYW1gIGZpbGUgaXMgYEdlbm9taWNBbGlnbm1lbnRzYC4gWW91IHNob3VsZCBub3RpY2UgdGhhdCBhbHRob3VnaCB0aGUgYC5iYW1gIGZpbGUgaXMgbm90IHBhcnRpY3VsYXJseSBiaWcgKH4gMTIuNSBNaWxsaW9uIHJlYWRzKSwgYnV0IGFscmVhZHkgdGFrZXMgYSBsaXR0bGUgd2hpbGUgdG8gcmVhZC4KCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoR2Vub21pY0FsaWdubWVudHMpCm15YmFtIDwtIHJlYWRHQWxpZ25tZW50cygiL2RhdGEvdGVzdC9wYWlyZWQuYmFtIikKbXliYW0KYGBgCgoKcmVhZEdBbGlnbm1lbnRzIGhhcyBwcm92aWRlZCB1cyB3aXRoIGFuIG9iamVjdCB0aGF0IGNhbiBiZSBtYW5pcHVsYXRlZCB1c2luZyB0aGUgc3RhbmRhcmQgdmVjdG9yIGNvbnZlbnRpb25zLiAKCmBgYHtyfQpteWJhbVsxOjEwXQpgYGAKClRoZXJlIGFyZSBhbHNvIGEgbnVtYmVyIG9mICphY2Nlc3NvciogZnVuY3Rpb25zIHRoYXQgY2FuIGdldCBwYXJ0aWN1bGFyIGl0ZW1zIGZyb20gdGhlIG9iamVjdDsgYGNpZ2FyYCB0byBvYnRhaW4gdGhlIENJR0FSIHN0cmluZ3MsIGBzdGFydGAgLyBgZW5kYCB0byBnZXQgdGhlIHN0YXJ0IGFuZCBlbmQgcG9zaXRpb25zLCBgd2lkdGhgIHRvIGdldCB0aGUgd2lkdGggb2YgZWFjaCByZWFkLgoKCmBgYHtyfQojIyBzcGFjZSB0byB0cnkgb3V0IHNvbWUgb2YgdGhlIGFjY2Vzc29yIGZ1bmN0aW9ucwoKCmBgYAoKCgpUaGUgb2JqZWN0IHdlIGhhdmUgY3JlYXRlZCwgYGJhbWAsIGNvbnRhaW5zIG9ubHkgYSBzbWFsbCBhbW91bnQgb2YgdGhlIGluZm9ybWF0aW9uIGF2YWlsYWJsZSBpbiBhIGAuYmFtYCBmaWxlLiBJZiB3ZSB3aXNoIHdlIGNhbiBpbXBvcnQgZXh0cmEgZmllbGRzIHN1Y2ggYXMgdGhlIHJlYWQgc2VxdWVuY2UsIG1hcHBpbmcgcXVhbGl0eSBhbmQgZmxhZzotCgoKYGBge3J9CmJhbSA8LSByZWFkR0FsaWdubWVudHMoIi9kYXRhL3Rlc3QvcGFpcmVkLmJhbSIscGFyYW09U2NhbkJhbVBhcmFtKHdoYXQ9Yygic2VxIiwibWFwcSIsImZsYWciLCJpc2l6ZSIsInF1YWwiKSkpCmJhbVsxOjNdCmBgYAoKClRoZSBjb21tYW5kIHRha2VzIGxvbmdlciB0byBydW4sIGJ1dCB3ZSBnZXQgbW9yZSBkZXRhaWwgb24gZWFjaCBvZiB0aGUgcmVhZHMuIFRoZSBleHRyYSBmaWVsZHMgbWFrZSB1cCB0aGUgKm1ldGFkYXRhKiBmb3IgZWFjaCByZWFkIGFuZCBjYW4gYmUgYWNjZXNzZWQgdXNpbmcgdGhlIGBtY29sc2AgZnVuY3Rpb24uIElmIHdlIHNhdmUgdGhpcyBtZXRhZGF0YSBhcyBhbiBvYmplY3QsIHdlIGNhbiB0cmVhdCBpdCBhcyBhIGBkYXRhIGZyYW1lYCBhbmQgdGhlcmVmb3JlIGhhdmUgdGhlIHVzdWFsIGAkYCBvcGVyYXRvciB0byBhY2Nlc3MgdGhlIGNvbHVtbnMKCmBgYHtyfQptZXRhIDwtIG1jb2xzKGJhbSkKbWV0YQpgYGAKClRoZSBzZXF1ZW5jZXMgYXJlIHN0b3JlZCBpbiBgQmlvc3RyaW5nc2AgZm9ybWF0CmBgYHtyfQptZXRhJHNlcQpgYGAKClRoZSBmbGFncyBzaG91bGQgYmUgdmFsaWQgdmFsdWVzIGFzIFtleHBsYWluZWRdKGh0dHBzOi8vYnJvYWRpbnN0aXR1dGUuZ2l0aHViLmlvL3BpY2FyZC9leHBsYWluLWZsYWdzLmh0bWwpIG9ubGluZQoKYGBge3J9CnRhYmxlKG1ldGEkZmxhZykKYGBgCgpGaW5hbGx5IHRoZSBtYXBwaW5nIHF1YWxpdGllcyBhcmUgbnVtZXJpYyBxdWFudGl0aWVzIHRoYXQgd2lsbCB2YXJ5IGFjY29yZGluZyB0byBhbGlnbmVyLiBNYXBwaW5nIHF1YWxpdGllcyBvZiAwIGFyZSB1c3VhbGx5IHJlc2VydmVkIGZvciByZWFkcyB0aGF0IG1hcCB0byBtdWx0aXBsZSBsb2NhdGlvbnMuIE1hbnkgY2FsbGluZyBhbGdvcml0aG1zIHdpbGwgZW1wbG95IGEgZmlsdGVyIG9uIG1hcHBpbmcgcXVhbGl0eTsgd2l0aCB2YWx1ZXMgb2YgMTAgdG8gMjAgdHlwaWNhbGx5IHVzZWQgdG8gZGlzY2FyZCByZWFkcwoKCmBgYHtyfQpzdW1tYXJ5KG1ldGEkbWFwcSkKYGBgCgoqKioqKioKKioqKioqCioqKioqKgoKIyMjIEV4ZXJjaXNlCgotIEhvdyBtYW55IHJlYWRzIGhhdmUgYWxsIGJhc2VzIG1hcHBpbmcgdG8gdGhlIGdlbm9tZQogICAgKyB3aGF0IHBlcmNlbnRhZ2Ugb2YgdGhlIHRvdGFsIG51bWJlciBvZiByZWFkcyBpcyB0aGlzPwotIFZpc3VhbGlzZSB0aGUgZGlzdHJpYnV0aW9uIG9mIG1hcHBpbmcgcXVhbGl0aWVzIGZvciB0aGlzIHNldCBvZiByZWFkcwotIEhvdyBtYW55IHJlYWRzIHdvdWxkIGJlIHJlbW92ZWQgYXQgYSBjdXQtb2ZmIG9mIDEwIG9yIDIwPwotIFdoYXQgd291bGQgc2VlbSB0byBiZSBhIHJlYXNvbmFibGUgY3V0LW9mZiB0byBkaXNjYXJkIHBvb3IgcXVhbGl0eSByZWFkcz8KLSBIb3cgbWFueSByZWFkcyBhcmUgY2xhc3NpZmllZCBhcyAqUENSLWR1cGxpY2F0ZXMqPwogICAgKyBISU5UOiBVc2UgdGhlIFVSTCBtZW50aW9uZWQgYWJvdmUgdG8gZmluZCBvdXQgdGhlIGV4cGxhbmF0aW9uIG9mIGZsYWdzIG9ic2VydmVkIGluIG91ciBkYXRhCgoKCioqKioqKgoqKioqKioKKioqKioqCgpSYXRoZXIgdGhhbiB0YWtpbmcgYSB3aG9sZS1nZW5vbWUgdmlldywgd2Ugb2Z0ZW4gd2FudCB0byB2aWV3IHRoZSByZWFkcyBmb3IgYSBwYXJ0aWN1bGFyIGdlbmUgb3IgcmVnaW9uIG9mIGludGVyZXN0LiBUaGlzIHdlIGNhbiBkbyB1c2luZyB0aGUgZnVuY3Rpb25zIHdlIGhhdmUgYWxyZWFkeSBzZWVuLgoKYGBge3J9Cm15LnJlYWRzIDwtIHdoaWNoKHNlcW5hbWVzKG15YmFtKT09IjE3IiAmIHN0YXJ0KG15YmFtKSA+IDc1Nzc4NTEgJiBlbmQobXliYW0pIDwgNzU5ODA2MykKbXliYW1bbXkucmVhZHNdCmBgYAoKSG93ZXZlciwgdGhlcmUgYXJlIG11Y2ggbW9yZSBlZmZpY2llbnQgd2F5cyB0byBkbyBzdWNoIHF1ZXJpZXMgaW4gQmlvY29uZHVjdG9yIGFzIHdlIHdpbGwgc2VlIGxhdGVyIGluIHRoZSBjb3Vyc2UKCmBHZW5vbWljQWxpZ25tZW50c2AgaXMgcGFydCBvZiBhIGZhbWlseSBvZiBwYWNrYWdlcyB0aGF0IHByb3ZpZGUgb2JqZWN0LXR5cGVzIGFuZCBmdW5jdGlvbmFsaXR5IGZvciBkZWFsaW5nIHdpdGggZ2Vub21pYyBpbnRlcnZhbHM7IHdoaWNoIGFyZSBkZXNjcmliZWQgaW4gYSBbUExvUyBDb21wdXRhdGlvbmFsIEJpb2xvZ3kgcGFwZXJdKGh0dHA6Ly9qb3VybmFscy5wbG9zLm9yZy9wbG9zY29tcGJpb2wvYXJ0aWNsZT9pZD0xMC4xMzcxL2pvdXJuYWwucGNiaS4xMDAzMTE4KSBmcm9tIDIwMTMuIAoKVGhlIGJhc2ljIHR5cGUgb2YgaW50ZXJ2YWwgd2UgY2FuIGRlZmluZSBpcyBhbiBgSVJhbmdlc2Agb2JqZWN0LiBUaGVyZSBpcyBhbiBleHRlbnNpdmUgbGlzdCBvZiBvcGVyYXRpb25zIHRoYXQgd2UgY2FuIHBlcmZvcm0gb24gdGhpcyBvYmplY3QKCiFbXShpbWFnZXMvcmFuZ2VzLWZucy5wbmcpCgoKCiMjIEV4dHJhY3RpbmcgYSBzdWJzZXQgb2YgcmVhZHMKCldlIG1pZ2h0IHdhbnQgdG8gZm9jdXMgb24gYSBzdWJzZXQgb2YgcmVhZHMgZnJvbSB0aGUgc3RhcnQsIGlzIHdoZW4gd2Ugd2FudCB0byBhbmFseXNlIGEgcGFydGljdWxhciBnZW5lLiBQcm92aWRlZCB0aGF0IHRoZSBgLmJhbWAgZmlsZSBoYXMgYmVlbiBpbmRleGVkIChjcmVhdGluZyBhIGAuYmFtLmJhaWAgZmlsZSBpbiB0aGUgc2FtZSBkaXJlY3RvcnkpLCB3ZSBjYW4gKnZlcnkqIHF1aWNrbHkganVtcCB0byBhIHBhcnRpY3VsYXIgZ2Vub21pYyByZWdpb24uIAoKIEZpcnN0IHdlIG5lZWQgdG8gZGVmaW5lIGEgcmVnaW9uOi0KCmBgYHtyfQpteWdlbmUgPC0gR1JhbmdlcygiMTciLCByYW5nZXM9SVJhbmdlcyg3NTc3ODUxLCA3NTk4MDYzKSxzdHJhbmQ9IisiKQpteWdlbmUKYGBgCgpUaGUgcmVnaW9uIGZpbGVyIGNhbiBiZSB1c2VkIGluIGNvbmp1Y3Rpb24gd2l0aCB0aGUgYHdoYXRgIGFyZ3VtZW50IHRvIGBTY2FuQmFtUGFyYW1gIGZ1bmN0aW9uIHRvIHByb3ZpZGUgYSBkZXRhaWxlZCBwaWN0dXJlIG9mIHRoZSByZWFkcyBmb3IgeW91ciBnZW5lLgoKYGBge3J9Cm15Z2VuZS5yZWFkcyA8LSByZWFkR0FsaWdubWVudHMoZmlsZT0iL2RhdGEvdGVzdC9wYWlyZWQuYmFtIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbT1TY2FuQmFtUGFyYW0od2hpY2g9bXlnZW5lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aGF0PWMoInNlcSIsIm1hcHEiLCJmbGFnIiwicXVhbCIsImlzaXplIikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKbXlnZW5lLnJlYWRzCmBgYAoKIyMgRGF0YSBTdW1tYXJpZXMKCldlIHdpbGwgbm93IGxvb2sgaW50byB0aGUgY291cGxlIG9mIHdheXMgdGhhdCB3ZSBjYW4gc3VtbWFyaXNlIHRoZSBkYXRhIHRoYXQgd2lsbCBtb3RpdmF0ZSBzb21lIG9mIHRoZSBtZXRob2RzIHRvIGNvbWUgaW4gdGhlIHJlc3Qgb2YgdGhlIGNvdXJzZS4KCkZpcnN0bHksIHdlIGNhbiBjb21wdXRlIGhvdyBtYW55IGJhc2VzIGFyZSBvYnNlcnZlZCBhdCBlYWNoIHBvc2l0aW9uIHVzaW5nIHRoZSBgcGlsZXVwQXNHUmFuZ2VzYCBjb252ZW5pZW5jZSBmdW5jdGlvbiBmcm9tIHRoZSBgYmlvdml6QmFzZWAgcGFja2FnZS4KCi0gdGhlIG91dHB1dCBpcyBhIHN1bW1hcnkgb2YgYmFzZSBjb3VudHMgZm9yIGVhY2ggcG9zaXRpb24sIGFuZCBhbiBvdmVyYWxsIGRlcHRoCi0gdGhlIGZ1bmN0aW9uIGlzIGFsc28gYWJsZSB0byBkbyBjbGV2ZXIgdGhpbmdzIHN1Y2ggYXMgZXhjbHVkaW5nIHJlYWRzIHdpdGggbG93IG1hcHBpbmcgcXVhbGl0eSBvciBQQ1IgZHVwbGljYXRlcwoKCmBgYHtyfQpsaWJyYXJ5KGJpb3ZpekJhc2UpCmJhc2VTdW1tYXJ5IDwtIHBpbGV1cEFzR1JhbmdlcygiL2RhdGEvdGVzdC9wYWlyZWQuYmFtIixyZWdpb25zID0gbXlnZW5lKSAgICAgICAgICAgICAgICAgICAgICAgICAKYmFzZVN1bW1hcnkKYGBgCgotIHRoZSBvdXRwdXQgaXMgYSBgR2Vub21pY1Jhbmdlc2Agb2JqZWN0IHdpdGggKm1ldGFkYXRhKiBjb3JyZXNwb25kaW5nIHRvIHRoZSBiYXNlIGNvdW50cyBhbmQgZGVwdGgKLSBhcyBiZWZvcmUgd2UgY2FuIGV4dHJhY3QgdGhlIG1ldGFkYXRhIHdpdGggdGhlIGBtY29sc2AgZnVuY3Rpb24KCmBgYHtyfQptZXRhIDwtIG1jb2xzKGJhc2VTdW1tYXJ5KQoKYGBgCgoKKioqKioqCioqKioqKgoqKioqKioKCiMjIyBFeGVyY2lzZQoKLSBVc2UgdGhlIGluZm9ybWF0aW9uIGluIHRoZSBgbWV0YWAgZGF0YSBmcmFtZSB0byBwbG90IHRoZSByZWFkIGNvdmVyYWdlIG92ZXIgdGhpcyBnZW5vbWljIHJlZ2lvbgotIE5hdmlnYXRlIHRvIGBjaHIxNzo3NTc3ODUxLTc1OTgwNjNgIGluIElHVgogICAgKyBkb2VzIHRoZSBwbG90IHJvdWdobHkgYWdyZWU/Ci0gSG93IG1hbnkgcG9zaXRpb25zIGluIHRoaXMgcmVnaW9uIGhhdmUgYSB0b3RhbCBkZXB0aCBvZiBvdmVyIDEwPwoKYGBge3J9CgoKYGBgCgoqKioqKioKKioqKioqCioqKioqKgoKCi0gV2UgY2FuIGRldGVybWluZSB3aGljaCBiYXNlIHdhcyBjYWxsZWQgYXQgZWFjaCBwb3NpdGlvbiB3ZSBjYW4gdXNlIHRoZSBmb2xsb3dpbmcgUiBtYWdpYwogICAgKyBoZXJlIGBhcHBseWAgaXMgdXNlZCB0byBydW4gdGhlIHNhbWUgZnVuY3Rpb24gb24gZWFjaCByb3cgb2YgdGhlIGRhdGEgZnJhbWUKICAgICsgYHdoaWNoLm1heGAgcmV0dXJucyB0aGUgaW5kZXggb2YgdGhlIGhpZ2hlc3QgdmFsdWUgaW4gZWFjaCByb3cKICAgICsgYG15Y2FsbHNgIGlzIHRoZW4gYSB2ZWN0b3Igb2YgImNhbGxzIiBmb3IgZWFjaCBiYXNlOyB0aGUgc2FtZSBsZW5ndGggYXMgdGhlIGdlbm9taWMgcmVnaW9uCgpgYGB7cn0KCm15Y2FsbHMgPC0gYXBwbHkobWV0YVssYygiQSIsIlQiLCJDIiwiRyIpXSwgMSwKICAgICAgZnVuY3Rpb24oeCkgbmFtZXMoeClbd2hpY2gubWF4KHgpXQogICAgICApCgojIEEgdGVzdCB0byBtYWtlIHN1cmUgdGhhdCBpdCdzIHdvcmtlZCBmb3IgdGhlIGZpcnN0IGZldyByb3dzCm15Y2FsbHNbMTo1XQpiYXNlU3VtbWFyeVsxOjVdCmBgYAoKCkFub3RoZXIgcGllY2Ugb2YgaW5mb3JtYXRpb24gdGhhdCB3b3VsZCBiZSB1c2VmdWwgYXQgdGhpcyBwb2ludCBpcyB0byBkZXRlcm1pbmUgd2hhdCB0aGUgcmVmZXJlbmNlIGJhc2UgaXMgYXQgZWFjaCBwb3NpdGlvbi4gVGhpcyBjYW4gYmUgYWNoaWV2ZWQgdXNpbmcgdGhlIG9uZSBvZiB0aGUgcHJlLWJ1aWx0IGdlbm9tZSBwYWNrYWdlcyBpbiBCaW9jb25kdWN0b3IKCi0gd2Ugd2lsbCBlbmNvdW50ZXIgc2V2ZXJhbCBhbm5vdGF0aW9uIHBhY2thZ2VzIGR1cmluZyB0aGUgY291cnNlOyB0aGUgZnVsbCBsaXN0IGlzIGdpdmVuIG9uIHRoZSBbQmlvY29uZHVjdG9yIHdlYnNpdGVdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvQmlvY1ZpZXdzLmh0bWwjX19fQW5ub3RhdGlvbkRhdGEpCi0geW91IG5lZWQgdG8gbWFrZSBzdXJlIHRvIHNlbGVjdCB0aGUgYXBwcm9wcmlhdGUgZ2Vub21lIGJ1aWxkCiAgICArIGFuZCBvcmdhbmlzbQotIHRoZXNlIHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQgaW4gdGhlIHVzdWFsIGZhc2hpb24gd2l0aCBgYmlvY0xpdGVgCiAgICArIHRoZXkgYXJlIG11Y2ggbGFyZ2VyIHRoYW4gc3RhbmRhcmQgQmlvY29uZHVjdG9yIHNvZnR3YXJlIHBhY2thZ2VzCiAgICArIGVzc2VudGlhbGx5IHRoZXkgY29tcHJpc2UgYSBwcmUtYnVpbHQgZGF0YWJhc2UKCmBgYHtyIGV2YWw9RkFMU0V9CiMjIyBEb24ndCBydW4gdGhpcyBkdXJpbmcgdGhlIGNvdXJzZQpzb3VyY2UoImh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9iaW9jTGl0ZS5SIikKYmlvY0xpdGUoIkJTZ2Vub21lLkhzYXBpZW5zLlVDU0MuaGcxOSIpCmBgYAoKU29tZSBiYXNpYyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgcGFja2FnZSBjYW4gYmUgb2J0YWluZWQgYnkgdHlwaW5nIHRoZSBvYmplY3QgbmFtZQoKYGBge3J9CmxpYnJhcnkoIkJTZ2Vub21lLkhzYXBpZW5zLlVDU0MuaGcxOSIpCmhnMTkgPC0gQlNnZW5vbWUuSHNhcGllbnMuVUNTQy5oZzE5CmhnMTkKCmBgYAoKLSB0aGVyZSBpcyBhIGNvbnZlbmllbnQgYGdldFNlcWAgZnVuY3Rpb24gdGhhdCBjYW4gb2J0YWluIHRoZSBnZW5vbWUgc2VxdWVuY2UgZm9yIGEgZ2l2ZW4gZ2Vub21pYyByZWdpb24KLSBob3dldmVyLCB0aGVyZSBpcyBhIHByb2JsZW0gd2hlbiB3ZSB0cnkgYW5kIHJ1biB0aGlzIHVzaW5nIHRoZSByZWdpb24gd2UgY3JlYXRlZCBwcmV2aW91c2x5CiAgICArIGNhbiB5b3Ugd29yayBvdXQgd2h5PwoKYGBge3IgZXZhbD1GQUxTRX0KcmVmU2VxIDwtIGdldFNlcShoZzE5LCBiYXNlU3VtbWFyeSkKcmVmU2VxCmBgYAoKLSBhIHNvbHV0aW9uIHRvIHRoaXMgY29tbW9uIGhlYWRhY2hlIGlzIHByb3ZpZGVkIGJ5IHRoZSBgcmVuYW1lU2VxbGV2ZWxzYCBmdW5jdGlvbgoKYGBge3J9CnJlZlNlcSA8LSBnZXRTZXEoaGcxOSwgcmVuYW1lU2VxbGV2ZWxzKGJhc2VTdW1tYXJ5LCBjKCIxNyI9ImNocjE3IikpKQpyZWZTZXEKYGBgCgoKCioqKioqKgoqKioqKioKKioqKioqCgojIyMgRXhlcmNpc2UKCi0gSG93IG1hbnkgcG9zaXRpb25zIGhhdmUgYSAiY2FsbCIgZGlmZmVyZW50IGZyb20gdGhlIHJlZmVyZW5jZSBnZW5vbWU/CiAgICBISU5UOiB3aGF0IGRvZXMgdGhlIFIgY29kZSBgbXljYWxscyA9PSByZWZTZXFgIHJldHVybj8KLSBIb3cgbWFueSBvZiB0aGVzZSBwb3NpdGlvbnMgaGF2ZSBhIGRlcHRoIGdyZWF0ZXIgdGhhbiAxMD8KLSBWZXJpZnkgeW91ciByZXN1bHQgaW4gSUdWCgoKCioqKioqKgoqKioqKioKKioqKioqCgoKIyBTdW1tYXJ5CgpXZSBoYXZlIGV4cGxvcmVkIHRoZSBwcm9wZXJ0aWVzIG9mIGJhbSBmaWxlcyB1c2luZyBCaW9jb25kdWN0b3IuIFRoZSB0ZWNobmlxdWVzIGFuZCB0eXBlcyBvZiBvYmplY3Qgd2UgaGF2ZSBsZWFybnQgYWJvdXQgd2lsbCBjcm9wLXVwIGFnYWluLWFuZC1hZ2FpbiBpbiB0aGUgY291cnNlLiBUaGUgdmFzdCBtYWpvcml0eSBvZiBOR1MgYW5hbHlzaXMgdG9vbHMgaW4gQmlvY29uZHVjdG9yIHdpbGwgdXNlIGBHZW5vbWljUmFuZ2VzYCBvYmplY3RzIGluIHNvbWUgZm9ybS4gCgpEdWUgdG8gdGhlIGhpZ2gtdm9sdW1lIG9mIHRoZSBkYXRhc2V0LCBzb21lIG9mIHRoZSB0b29scyBhbmQgcGlwZWxpbmVzIHdlIHVzZSB3aWxsIG5vdCBiZSBpbiBSLiBIb3dldmVyLCB5b3Ugd2lsbCBzdGlsbCBiZSBhYmxlIHRvIGludGVycm9nYXRlIHRoZSByZXN1bHRzIHlvdSBvYnRhaW4gYW5kIGV4cGxvcmUgdGhlbSBpbiBtb3JlIGRldGFpbCB1c2luZyBSLgoKCgo=