Goals

Analyze microbiome experimental data as a phyloseq object - explore ecological metrics and identify differentially abundant taxa.


Before We Get Started

Notes

  • This is a slightly modified version of a document created by Matthew Willman.

  • To convert an Rmd (R Markdown) file to an R script, type the following in an R console: knitr::purl(input="<filename>.Rmd"). (You can download this Rmd file by clicking the Code button in the top-right of this page – but we will open it at OSC.)

Open this file in RStudio Server at OSC

  • This assumes you still have an active RStudio Server job at OSC; if not see the instructions on the previous page to start one.

  • In the Files pane, in the markdown directory, open the file 08-postASV-analysis.Rmd. That’s this file!


Getting Started

Install and Load Packages

Add our custom library again if you restarted your R session:

#.libPaths(new = '/fs/project/PAS0471/.R/4.0/')

And load the necessary packages:

packages <- c('tidyverse', 'vegan', 'phyloseq', 'decontam', 'ape',
              'DESeq2', 'microbiome', 'metagenomeSeq', 'remotes',
              'ampvis2', 'breakaway')
pacman::p_load(char = packages)

# If you wanted to install and load these packages yourself,
# first make sure you have the pacman package installed:
## install.packages('pacman')
# Then, the code above would work except for the last two packages,
# which are available from Github only. To install these, you would run:
## remotes::install_github("MadsAlbertsen/ampvis2")
## remotes::install_github("adw96/breakaway")

Set Directories

# Dir with input files:
indir <- 'analysis/ASV_inference/'

# Dir for output:
outdir <- 'analysis/postASV_analysis/'

Load the Data

This session starts with a phyloseq object similar to the one generated using DADA2 in the previous session.

ps_raw <- readRDS(file.path(indir, 'ps_16S_V4_withtree.rds'))

# This would load the object from the previous session, which doesn't contain a tree:
# ps_raw <- readRDS(file.path(indir, 'ps_V4.rds'))

Our phyloseq object is made up of the following components (slots):

  • otu_table
  • sample_data
  • tax_table
  • phy_tree

Let’s have a look at the object:

ps_raw
## phyloseq-class experiment-level object
## otu_table()   OTU Table:         [ 18038 taxa and 75 samples ]
## sample_data() Sample Data:       [ 75 samples by 7 sample variables ]
## tax_table()   Taxonomy Table:    [ 18038 taxa by 6 taxonomic ranks ]
## phy_tree()    Phylogenetic Tree: [ 18038 tips and 18036 internal nodes ]
# And the assigned taxon names:
taxa_names(ps_raw)[1:3]
## [1] "TACGAAGGGGGCTAGCGTTGCTCGGAATTACTGGGCGTAAAGGGAGCGTAGGCGGACATTTAAGTCAGGGGTGAAATCCCGGGGCTCAACCTCGGAATTGCCTTTGATACTGGGTGTCTTGAGTATGAGAGAGGTGTGTGGAACTCCGAGTGTAGAGGTGAAATTCGTAGATATTCGGAAGAACACCAGTGGCGAAGGCGACACACTGGCTCATTACTGACGCTGAGGCTCGAAAGCGTGGGGAGCAAACAGG"
## [2] "TACGTAGGGTGCAAGCGTTAATCGGAATTACTGGGCGTAAAGCGTGCGCAGGCGGTTATGTAAGACAGATGTGAAATCCCCGGGCTCAACCTGGGAACTGCATTTGTGACTGCATGGCTAGAGTACGGTAGAGGGGGATGGAATTCCGCGTGTAGCAGTGAAATGCGTAGATATGCGGAGGAACACCGATGGCGAAGGCAATCCCCTGGACCTGTACTGACGCTCATGCACGAAAGCGTGGGGAGCAAACAGG"
## [3] "TACGTAGGGTGCGAGCGTTAATCGGAATTACTGGGCGTAAAGCGTGCGCAGGCGGTTTGTTAAGACAGATGTGAAATCCCCGGGCTCAACCTGGGAACTGCATTTGTGACTGGCAGGCTAGAGTATGGCAGAGGGGGGTAGAATTCCACGTGTAGCAGTGAAATGCGTAGAGATGTGGAGGAATACCGATGGCGAAGGCAGCCCCCTGGGCCAATACTGACGCTCATGCACGAAAGCGTGGGGAGCAAACAGG"

As we can see, the taxon names are currently the associated sequences. We can create a new phyloseq component to store these sequences, then rename the ASVs so something shorter (“ASV_1”, “ASV_2”, etc.).

dna <- Biostrings::DNAStringSet(taxa_names(ps_raw))
names(dna) <- taxa_names(ps_raw)

# Merge sequence object into the phyloseq object:
ps_raw <- merge_phyloseq(ps_raw, dna)
ps_raw
## phyloseq-class experiment-level object
## otu_table()   OTU Table:         [ 18038 taxa and 75 samples ]
## sample_data() Sample Data:       [ 75 samples by 7 sample variables ]
## tax_table()   Taxonomy Table:    [ 18038 taxa by 6 taxonomic ranks ]
## phy_tree()    Phylogenetic Tree: [ 18038 tips and 18036 internal nodes ]
## refseq()      DNAStringSet:      [ 18038 reference sequences ]
# Rename ASVs:
taxa_names(ps_raw) <- paste("ASV", 1:ntaxa(ps_raw), sep = "_")
taxa_names(ps_raw)[1:3]
## [1] "ASV_1" "ASV_2" "ASV_3"

Filter taxa

Identify and Remove Contaminants

It is possible to introduce contaminating microbes during sample preparation. Before analyzing the data, we will identify and remove probable contaminants using the decontam package.

In this case, we will define a conataminant as an ASV whose abundance correlates with DNA concentration (post-PCR). Our assumption here is that each soil taxon’s abundance should be independant of DNA concentration. However, if we were to spike each sample with a contaminant, that contaminant would show a greater abundance in samples with lower DNA concentrations. To do this, we need DNA concentration measured after PCR and before pooling of the samples.

These data are in column 7 of the sample data component:

head(sample_data(ps_raw))
##        SampleID Experiment Treatment Timepoint Block Plot PCR_prod_V4.V5
## 101-S1   101-S1        HCC        T1        S1    R1 P101       0.464003
## 101-S3   101-S3        HCC        T1        S3    R1 P101       1.000000
## 101-S4   101-S4        HCC        T1        S4    R1 P101       0.629419
## 102-S3   102-S3        HCC        T4        S3    R1 P102       0.925081
## 102-S4   102-S4        HCC        T4        S4    R1 P102       0.746320
## 103-S1   103-S1        HCC        T2        S1    R1 P103       0.556443

MCIC measured our DNA concentration by comparing band intensities to a single reference band. MCIC’s DNA concentration measurements range from 0 to 1 (the reference). Zero in this case is coded as NA. The decontam package can’t handle NA’s or 0’s, so we need to change them to a small value (e.g. 0.01).

# First, we identify the NAs in the DNA measurement column:
NAs <- is.na(sample_data(ps_raw)$PCR_prod_V4.V5)

# Then, we replace NAs with 0.01:
sample_data(ps_raw)$PCR_prod_V4.V5[NAs] <- 0.01

We will use decontam::isContaminant(method = "frequency") to test each taxon against the null hypothesis: abundance is not associated with DNA concentration. Because the DNA concentration measurements are not very accurate, and we want to make sure to remove possible contaminants, we use a relaxed p-value (0.2) to reject the null hypothesis:

contam_df_freq <- isContaminant(ps_raw,
                                method = "frequency",
                                conc = "PCR_prod_V4.V5",
                                threshold = 0.2)
head(contam_df_freq)
##              freq prev     p.freq p.prev          p contaminant
## ASV_1 0.017740963   73 0.14200437     NA 0.14200437        TRUE
## ASV_2 0.013007310   73 0.08132858     NA 0.08132858        TRUE
## ASV_3 0.008321727   71 0.13124199     NA 0.13124199        TRUE
## ASV_4 0.006168516   72 0.94705975     NA 0.94705975       FALSE
## ASV_5 0.005170904   72 0.99239427     NA 0.99239427       FALSE
## ASV_6 0.005774690   37 0.62439728     NA 0.62439728       FALSE

As we can see, the top 3 ASVs are identified as probable contaminants. How many contaminants were identified (TRUE in the contaminant column) in total?

table(contam_df_freq$contaminant)
## 
## FALSE  TRUE 
## 17800   238

What are the most abundant contaminant taxa?

# Get abundance ranks for contaminant ASVs:
head(which(contam_df_freq$contaminant))
## [1]   1   2   3  11  27 149
# Check which taxa are contaminants:
ps_contam <- prune_taxa(contam_df_freq$contaminant, ps_raw)
head(tax_table(ps_contam))
## Taxonomy Table:     [6 taxa by 6 taxonomic ranks]:
##         Kingdom    Phylum           Class                
## ASV_1   "Bacteria" "Proteobacteria" "Alphaproteobacteria"
## ASV_2   "Bacteria" "Proteobacteria" "Gammaproteobacteria"
## ASV_3   "Bacteria" "Proteobacteria" "Gammaproteobacteria"
## ASV_11  "Bacteria" "Proteobacteria" "Gammaproteobacteria"
## ASV_27  "Bacteria" "Actinobacteria" "Actinobacteria"     
## ASV_149 "Bacteria" "Proteobacteria" "Alphaproteobacteria"
##         Order                   Family             
## ASV_1   "Caulobacterales"       "Caulobacteraceae" 
## ASV_2   "Betaproteobacteriales" "Burkholderiaceae" 
## ASV_3   "Betaproteobacteriales" "Burkholderiaceae" 
## ASV_11  "Betaproteobacteriales" "Burkholderiaceae" 
## ASV_27  "Micrococcales"         "Micrococcaceae"   
## ASV_149 "Rhizobiales"           "Xanthobacteraceae"
##         Genus                                       
## ASV_1   "Brevundimonas"                             
## ASV_2   "Delftia"                                   
## ASV_3   "Burkholderia-Caballeronia-Paraburkholderia"
## ASV_11  "Delftia"                                   
## ASV_27  "Renibacterium"                             
## ASV_149 NA

Let’s take a look at our tested associations for several taxa:

plot_frequency(ps_raw,
               taxa_names(ps_raw)[c(1, 2, 3, 4, 5, 11)],
               conc = "PCR_prod_V4.V5") +
  xlab("DNA Concentration (relative to single marker)")

As we can see, abundance of ASVs 1, 2, 3, and 11 are particularly high in samples with low DNA concentration.

Which samples have highest abundances of the contaminants?

asv1 <- otu_table(ps_raw)[, 1]
head(asv1[order(asv1, decreasing = TRUE)])
## OTU Table:          [1 taxa and 6 samples]
##                      taxa are columns
##        ASV_1
## 204-S3  6710
## blankM  5944
## 103-S3  4337
## 301-S3  3032
## 104-S3  1943
## 304-S3  1153
asv2 <- otu_table(ps_raw)[, 2]
head(asv2[order(asv2, decreasing = TRUE)])
## OTU Table:          [1 taxa and 6 samples]
##                      taxa are columns
##        ASV_2
## 204-S3  6261
## blankM  4403
## 103-S3  3755
## 301-S3  1707
## 104-S3   683
## 304-S3   664

How do these contaminants overlap with ASVs present in the negative control?

otu_table(ps_raw)[70:75, 1:11]
## OTU Table:          [11 taxa and 6 samples]
##                      taxa are columns
##        ASV_1 ASV_2 ASV_3 ASV_4 ASV_5 ASV_6 ASV_7 ASV_8 ASV_9 ASV_10 ASV_11
## 604-S1    25    24    25   492   136     0   139   350   137    139      5
## 604-S2    31    10    11   183   108    21   159   189    79    251      3
## 604-S3   127    72    99   212   260     3   131   132   123    141     18
## 604-S4    46    32     6   101   251     0    54    54    73     66     30
## blankM  5944  4403   169     0     0     0     0     0     0      0   1476
## H20        0     0     0     0     0     0     9     0     0      0      0

ASVs have high read counts in the extraction negative control. Thus, we can be fairly certain these are contaminants and not representative of our experimental samples.

Remove the contaminants that we identified:

ps_noncontam <- prune_taxa(!contam_df_freq$contaminant, ps_raw)

ps_noncontam
## phyloseq-class experiment-level object
## otu_table()   OTU Table:         [ 17800 taxa and 75 samples ]
## sample_data() Sample Data:       [ 75 samples by 7 sample variables ]
## tax_table()   Taxonomy Table:    [ 17800 taxa by 6 taxonomic ranks ]
## phy_tree()    Phylogenetic Tree: [ 17800 tips and 17798 internal nodes ]
## refseq()      DNAStringSet:      [ 17800 reference sequences ]

What proportion of our count data were removed as contaminants?

pre <- sum(sample_sums(ps_raw))
post <- sum(sample_sums(ps_noncontam))

(pre-post) / pre
## [1] 0.03797457

Remove non-bacterial, non-archaeal ASVs

The V4 region of 16S rRNA is conserved within certain bacteria, archaea, chloroplasts, mitochondria, and eukaryotes. Since we are only interested in bacteria and archaea here, we’ll need to remove other taxa from our object.

# Create ps subsets for chloroplast, mitochondria, and eukaryotes:
chlr <- subset_taxa(ps_noncontam, Order == "Chloroplast")
mit <- subset_taxa(ps_noncontam, Family == "Mitochondria")
euk <- subset_taxa(ps_noncontam, Kingdom == "Eukaryota")

# Get all taxon IDs that we want to remove:
bad_taxa <- c(taxa_names(chlr), taxa_names(mit), taxa_names(euk))

# Get all taxon IDs that we want to keep:
all_taxa <- taxa_names(ps_noncontam)
good_taxa <- all_taxa[!(all_taxa %in% bad_taxa)]

# Subset phyloseq object:
ps_bac_arc <- prune_taxa(good_taxa, ps_noncontam)

What proportion of ASVs were kept?

pre <- sum(sample_sums(ps_noncontam))
post <- sum(sample_sums(ps_bac_arc))
post / pre
## [1] 0.9919178

Finally, we’ll also remove non-experimental samples (e.g. negative controls). Note, though, that non-experimental samples are important for evaluating the quality of your reagents, consistency across sequencing runs, and contamination.

ps <- subset_samples(ps_bac_arc, Experiment == "HCC")

ps
## phyloseq-class experiment-level object
## otu_table()   OTU Table:         [ 17600 taxa and 73 samples ]
## sample_data() Sample Data:       [ 73 samples by 7 sample variables ]
## tax_table()   Taxonomy Table:    [ 17600 taxa by 6 taxonomic ranks ]
## phy_tree()    Phylogenetic Tree: [ 17600 tips and 17598 internal nodes ]
## refseq()      DNAStringSet:      [ 17600 reference sequences ]

Filter samples

After having filtered out unwanted taxa, we now need to filter out uninformative samples – those with low total taxon counts.

First, how many counts do we have for each sample?

sums <- sample_sums(ps)
sums[order(sums)]
## 403-S3 203-S3 103-S3 204-S3 104-S3 301-S1 301-S3 204-S1 503-S4 603-S3 302-S3 
##      0   3258   4157   7687   7841  13441  14860  14897  15004  19423  20815 
## 403-S1 502-S1 201-S3 402-S1 202-S1 603-S1 604-S4 303-S1 504-S4 404-S4 604-S3 
##  21405  23248  23283  23283  24329  25145  25568  25708  25913  25948  26196 
## 601-S4 203-S1 503-S2 102-S3 403-S4 402-S4 601-S1 201-S4 504-S1 401-S3 401-S4 
##  26259  26598  26821  27558  28119  28340  28408  29079  29106  29306  29375 
## 503-S1 204-S4 502-S4 303-S3 301-S4 204-S2 304-S4 403-S2 401-S1 202-S4 604-S1 
##  29465  29882  30181  30201  30229  30575  31209  31351  31594  31978  32157 
## 501-S4 603-S4 202-S3 604-S2 304-S3 104-S1  501S4 103-S2 301-S2 103-S4 303-S4 
##  32480  32577  33410  33618  33689  33852  33951  34076  34557  34714  35339 
## 404-S3 203-S4 602-S4 101-S3 402-S3 302-S4 501-S3 104-S4 103-S1 102-S4 502-S3 
##  36038  36261  36653  36786  36799  38114  38300  38725  38928  40180  40416 
## 304-S1 101-S1 504-S3 602-S3 101-S4 601-S3 503-S3 
##  41181  50997  52898  54757  56340  57665  67738
# Lowest total count:
min(sums)
## [1] 0
# Sample with lowest counts:
names(sums[order(sums)][1])
## [1] "403-S3"

To remove uninformative samples, we will only keep those with over 1,000 counts. We will also remove a technical replicate, though note that technical replicates are important for quality control of runs and for understanding the reproducibility of the methodology.

# Remove samples with low counts:
ps <- subset_samples(ps, sample_sums(ps) > 1000)

# Remove the technical replicate:
ps <- subset_samples(ps, SampleID != "501S4")

Let’s take a look at our current phyloseq object:

sample_data(ps)[1:10]
##        SampleID Experiment Treatment Timepoint Block Plot PCR_prod_V4.V5
## 101-S1   101-S1        HCC        T1        S1    R1 P101       0.464003
## 101-S3   101-S3        HCC        T1        S3    R1 P101       1.000000
## 101-S4   101-S4        HCC        T1        S4    R1 P101       0.629419
## 102-S3   102-S3        HCC        T4        S3    R1 P102       0.925081
## 102-S4   102-S4        HCC        T4        S4    R1 P102       0.746320
## 103-S1   103-S1        HCC        T2        S1    R1 P103       0.556443
## 103-S2   103-S2        HCC        T2        S2    R1 P103       0.741777
## 103-S3   103-S3        HCC        T2        S3    R1 P103       0.155553
## 103-S4   103-S4        HCC        T2        S4    R1 P103       0.768266
## 104-S1   104-S1        HCC        T3        S1    R1 P104       0.742849
head(t(otu_table(ps)[1:10]))
## OTU Table:          [6 taxa and 10 samples]
##                      taxa are rows
##        101-S1 101-S3 101-S4 102-S3 102-S4 103-S1 103-S2 103-S3 103-S4 104-S1
## ASV_4     556    306    241    128    122    343    493      5     81    375
## ASV_5     227    306    255    254    185    189    259     30    155    190
## ASV_7      98    165     93    278    101    130    286     41     84    116
## ASV_8     423    187    188    133    129    197    329      0     65    257
## ASV_9     153    196    260    153    119    124    162      9     66    124
## ASV_10    192    192    319     72    186     73     69      1    153    119
head(tax_table(ps))
## Taxonomy Table:     [6 taxa by 6 taxonomic ranks]:
##        Kingdom    Phylum           Class                        
## ASV_4  "Bacteria" "Acidobacteria"  "Blastocatellia_(Subgroup_4)"
## ASV_5  "Bacteria" "Proteobacteria" "Gammaproteobacteria"        
## ASV_7  "Bacteria" "Proteobacteria" "Alphaproteobacteria"        
## ASV_8  "Bacteria" "Acidobacteria"  "Blastocatellia_(Subgroup_4)"
## ASV_9  "Bacteria" "Nitrospirae"    "Nitrospira"                 
## ASV_10 "Bacteria" "Bacteroidetes"  "Bacteroidia"                
##        Order                   Family              Genus         
## ASV_4  "Pyrinomonadales"       "Pyrinomonadaceae"  "RB41"        
## ASV_5  "Betaproteobacteriales" "SC-I-84"           NA            
## ASV_7  "Rhizobiales"           "Xanthobacteraceae" "Pseudolabrys"
## ASV_8  "Pyrinomonadales"       "Pyrinomonadaceae"  "RB41"        
## ASV_9  "Nitrospirales"         "Nitrospiraceae"    "Nitrospira"  
## ASV_10 "Chitinophagales"       "Chitinophagaceae"  NA

Normalization

Normalization is currently a much-discussed issue of microbiome studies. Differences in read depth between samples often need to be corrected before analysis. Several normalization methods have been proposed, and no single method is perfect. It may be that the most appropriate method depends on the analysis.

In this tutorial, we will use:

  • Proportion-normalized data to estimate ecological metrics.

  • Stabilizing transformation normalized data to identify differentially abundant taxa.

However, for your reference, we will also present code for two other normalization methods: rarefaction and cumulative sum scaling.

For more details, read McMurdie and Holmes 2014 and McKnight et al. 2019.


Proportion Normalization

Proportion normalization involves dividing each OTU count by the total sum for each sample. The resulting count data will add up to 1 (100%) for each sample.

The microbiome::transform function can be used to easily normalize count data as proportions in a phyloseq object:

# Proportion normalization:
ps_prop <- transform(ps, "compositional")

# Have a look at the resulting OTU table:
otu_table(ps_prop)[1:5, 1:5]
## OTU Table:          [5 taxa and 5 samples]
##                      taxa are columns
##              ASV_4       ASV_5       ASV_7       ASV_8       ASV_9
## 101-S1 0.010902602 0.004451242 0.001921682 0.008294606 0.003000176
## 101-S3 0.008318382 0.008318382 0.004485402 0.005083456 0.005328114
## 101-S4 0.004277600 0.004526092 0.001650692 0.003336883 0.004614838
## 102-S3 0.004644749 0.009216924 0.010087815 0.004826185 0.005551927
## 102-S4 0.003036336 0.004604281 0.002513688 0.003210553 0.002961672
# Check what the sums for each sample are now:
head(sample_sums(ps_prop))
## 101-S1 101-S3 101-S4 102-S3 102-S4 103-S1 
##      1      1      1      1      1      1

Variance stabilizing transformation

This transformation method, available in DESeq2, normalizes data with respect to library size as well as dispersion.
Type ?vst or read Anders and Huber 2010 for more information.

# First we convert the phyloseq object to a DESeq object:
dds <- phyloseq_to_deseq2(ps, ~1)

# Then we estimate the size factors:
dds <- estimateSizeFactors(dds)

# Now can do the transformation:
dds_vst <- vst(dds, blind = TRUE)

# Have a look at the resulting counts:
vst_counts <- assay(dds_vst)
t(vst_counts)[1:5, 1:5]
##           ASV_4    ASV_5    ASV_7    ASV_8    ASV_9
## 101-S1 8.002095 6.729126 5.559152 7.611915 6.175558
## 101-S3 7.459397 7.459397 6.585002 6.761345 6.827700
## 101-S4 7.388440 7.468770 6.047047 7.035948 7.496408
## 102-S3 6.756474 7.729205 7.858050 6.810556 7.008636
## 102-S4 7.108518 7.700594 6.841187 7.187648 7.073230

Rarefaction

Rarefaction can be used to subset data such that the library depth is the same for each sample. Because sampling of the data is random, rarefaction can account for an effect of total read count on taxa richness. However, rarefaction is no longer considered to be a good way to normalize amplicon sequencing data (see McMurdie & Holmes 2014).

ps_rarefied <- rarefy_even_depth(ps,
                                 rngseed = 1,
                                 sample.size = min(sample_sums(ps)),
                                 replace = FALSE)

# Have a look at the result:
otu_table(ps_rarefied)[1:5, 1:5]
## OTU Table:          [5 taxa and 5 samples]
##                      taxa are columns
##        ASV_4 ASV_5 ASV_7 ASV_8 ASV_9
## 101-S1    24    13    12    32     7
## 101-S3    26    22    20     9    19
## 101-S4    15    16     6    12    17
## 102-S3    15    26    33    19    11
## 102-S4     6    15     5     9    18
head(sample_sums(ps_rarefied))
## 101-S1 101-S3 101-S4 102-S3 102-S4 103-S1 
##   3258   3258   3258   3258   3258   3258

Compare these sample sums to the non-rarified data sums:

head(sample_sums(ps))
## 101-S1 101-S3 101-S4 102-S3 102-S4 103-S1 
##  50997  36786  56340  27558  40180  38928

Cumulative Sum Scaling

The metagenomeSeq Cumulative Sum Scaling (CSS) normalization is another option developed for microbiome data. For more information, read Paulson et al. 2013.

# Convert the phyloseq object to a metagenomeseq object:
mgs_css <- phyloseq_to_metagenomeSeq(ps)

# Perform the Cumulative Sum Scaling:
mgs_css <- cumNorm(mgs_css)

# Extract the counts and add them to a separate phyloseq object:
css_counts <- MRcounts(mgs_css, norm = TRUE)
ps_css <- ps
otu_table(ps_css) <- otu_table(t(css_counts), taxa_are_rows = FALSE)

Now lets compare the original data to the CSS normalized data:

otu_table(ps)[1:5, 1:5]
## OTU Table:          [5 taxa and 5 samples]
##                      taxa are columns
##        ASV_4 ASV_5 ASV_7 ASV_8 ASV_9
## 101-S1   556   227    98   423   153
## 101-S3   306   306   165   187   196
## 101-S4   241   255    93   188   260
## 102-S3   128   254   278   133   153
## 102-S4   122   185   101   129   119
head(sample_sums(ps))
## 101-S1 101-S3 101-S4 102-S3 102-S4 103-S1 
##  50997  36786  56340  27558  40180  38928
otu_table(ps_css)[1:5, 1:5]
## OTU Table:          [5 taxa and 5 samples]
##                      taxa are columns
##           ASV_4    ASV_5    ASV_7    ASV_8    ASV_9
## 101-S1 86.05479 35.13388 15.16793 65.46974 23.68054
## 101-S3 51.81172 51.81172 27.93769 31.66272 33.18659
## 101-S4 37.81579 40.01255 14.59281 29.49945 40.79711
## 102-S3 23.92971 47.48551 51.97233 24.86446 28.60348
## 102-S4 19.34052 29.32784 16.01141 20.45022 18.86493
head(sample_sums(ps_css))
##   101-S1   101-S3   101-S4   102-S3   102-S4   103-S1 
## 7893.051 6228.581 8840.421 5151.991 6369.689 6386.874

Plot abundances

In this and following the examples we will use the proportions data.

# Subset to the T1 treatment
T1 <- subset_samples(ps_prop, Treatment == "T1")

# Agglomerate taxa to phylum level:
T1_phylum <- tax_glom(T1, taxrank = "Phylum", NArm = FALSE)

# Remove uncommon phyla:
T1_phylum <- subset_taxa(T1_phylum, taxa_sums(T1_phylum) > 0.1)

# Plot:
plot_bar(T1_phylum, fill = "Phylum") +
   facet_wrap(~Timepoint, scales = "free_x", nrow = 1)


Alpha diversity

Alpha diversity measures taxonomic diversity within a single population. Measures of alpha diversity include taxonomic richness (i.e. number of taxa), and indices which combine taxonomic richness with some measure of evenness.

Note: Many of these methods are greatly influenced by singleton data (i.e. taxa represented by a single count), meaning they may be unreliable if your data excludes singletons. DADA2 does not output singletons if it is run individually on each sample. Thus, if using dada2 to infer ASVs, it is advisable to use pool = TRUE when running the dada algorithm. This will allow calling per-sample singletons, but not per-study singletons.

Taxonomic richness: Rarefaction plot

Our goal in defining richness is to determine the number of unique taxa within each population (in our case, each plot). This is difficult, as our samples contain only a portion of the total richness within our plots. As sequence depth increases for each sample, so does the number of taxa. This can be illustrated with a rarefaction plot.

To make a rarefaction plot, we draw random samples from our data and count the number of unique ASVs as samples are drawn. The resulting rarefaction curve is expected to rise quickly then plateu as the most abundant taxa are represented. We can make a quick rarefaction curve plot directly from our phyloseq object of all samples using the vegan package:

rarecurve(otu_table(ps), step = 500, xlab = "Sample Size", ylab = "Taxa")

We can also split rarefaction curves by group using the ampvis2 package.

metadata <- data.frame(sample_data(ps), check.names = FALSE)

asv_table <- data.frame(t(otu_table(ps)),
                       tax_table(ps),
                       check.names = FALSE)

ampvis2 requires a Species column, which must be added to our data. If your table already has a Species column, skip this step:

asv_table$Species <- NA

Load an ampvis2 object and calculate rarefaction:

ps3 <- amp_load(asv_table, metadata)

rar_plot <- amp_rarecurve(ps3,
                          stepsize = 100,
                          facet_by = "Timepoint",
                          color_by = "Treatment") +
   ylab("Number of observed ASVs")

rar_plot

As we can see, as read depth increases for each sample, so does taxonomic richness. In all samples, increased sampling would result in increased richness. Thus, our observed sample richness is lower than the true population richness for every plot.

Further, sequence depth and total richness appear to be correlated across samples:

dp_by_asv <- data.frame(ct_sums = sample_sums(ps),
                        asvs = rowSums(otu_table(ps) != 0))

ggplot(data = dp_by_asv) +
  geom_point(aes(x =  ct_sums, y = asvs)) +
  labs(x = "Sequence depth", y= "ASV count") +
  theme_minimal()

We can test whether this correlation is significant:

cor <- cor.test(dp_by_asv$ct_sums, dp_by_asv$asvs, method = "spearman")
## Warning in cor.test.default(dp_by_asv$ct_sums, dp_by_asv$asvs, method =
## "spearman"): Cannot compute exact p-value with ties
cor
## 
##  Spearman's rank correlation rho
## 
## data:  dp_by_asv$ct_sums and dp_by_asv$asvs
## S = 17650, p-value = 7.412e-12
## alternative hypothesis: true rho is not equal to 0
## sample estimates:
##       rho 
## 0.7040636

Here we observe a significant correlation between read count and observed taxonomic richness.

Thus, a sample from a population with high actual diversity, but low read depth, could have low observed richness. Fortunately, there are methods to estimate the number of unobserved taxa. For an overview of these methods, read Bunge et al. 2014.

Frequency counts

To illustrate the difficulty of estimating population richness and importance of singletons to alpha diversity metrics, we’ll look at the frequency count distribution for our first sample: 101-S1.

We’ll count the number of singletons, doubletons, etc., then plot frequency as a function of count.

# Get the frequency count distribution for the 1st sample:
frequencytablelist <- build_frequency_count_tables(otu_table(ps))
freq_ct <- frequencytablelist[[1]]
colnames(freq_ct) <- c("Frequency", "Count")

# Plot:
ggplot(freq_ct, aes(Frequency, Count)) + 
  geom_point() + 
  scale_x_continuous(breaks = seq(0, 600, by = 50)) + 
  ggtitle("101-S1") +
  theme_minimal()

We can see there are ~3,200 taxa represented as singletons, and ~1,600 taxa as doubletons. This is a high proportion of the total number taxa are observed in this sample:

# Total number of taxa:
sum(freq_ct$Count)
## [1] 8646
# Proportion of singletons
freq_ct$Count[1] / sum(freq_ct$Count)
## [1] 0.3693037

How many more taxa might we observe if we increased our sampling?

Diversity Indices

We can use several indices to explore how evenly species are distributed within each sample. Note that ‘Observed’ is the sample taxonomic richness. The other three indices combine richness and abundance data for each taxon.

plot_richness(ps,
              x = "Timepoint",
              shape = "Treatment",
              color = "Treatment", 
              measures = c("Observed", "Shannon", "Simpson", "InvSimpson"))

Notice that several T2:S3 samples have low measures, suggesting they have low diversity (i.e. abundances are dominated by a few taxa) compared to the other samples.


Beta diversity

Beta diversity measures differences in microbial compositions between populations. If available (use ps_16S_V4_withtree.rds), we can use a phylogenetic tree and otu table to estimate the UniFrac for each sample pair.

The result is a distance matrix with height and width equal to the number of samples. UniFrac may be weighted or unweighted. Selection of weighted or unweighted will depend on the question you want to answer. Unweighted UniFrac uses only presence or absence of a taxon from the otu table (i.e. 1000 will be treated the same as 1). It is appropriate if you want to test qualitative taxa differences between samples. Weighted UniFrac uses presence as well as quantity data (i.e. 1 < 1000).

We are interested to know what changes to taxon abundance may be driven by our treatments, so we will use weighted UniFrac.

Ordination

First we’ll make a weighted UniFrac distance matrix. For UniFrac, you require a tree as part of your phyloseq object.

set.seed(100)
wUF <- phyloseq::distance(ps_prop, method = "wunifrac")

as.matrix(wUF)[1:6, 1:6]
##            101-S1     101-S3    101-S4     102-S3    102-S4     103-S1
## 101-S1 0.00000000 0.09488256 0.1767225 0.10183595 0.1973141 0.08065589
## 101-S3 0.09488256 0.00000000 0.1403436 0.06335683 0.1611763 0.10207325
## 101-S4 0.17672254 0.14034362 0.0000000 0.16667520 0.0712517 0.16746555
## 102-S3 0.10183595 0.06335683 0.1666752 0.00000000 0.1809738 0.09720146
## 102-S4 0.19731406 0.16117633 0.0712517 0.18097382 0.0000000 0.18488558
## 103-S1 0.08065589 0.10207325 0.1674656 0.09720146 0.1848856 0.00000000
# Note: If your *phyloseq* object lacks a phylogenetic tree,
# you could use a dissimilarity index that does not require one, such as Bray-Curtis:
# bray <- vegan::vegdist(otu_table(ps_prop), method = "bray")
# as.matrix(bray)[1:6,1:6]

Then perform Principal Coordinate Analysis (PCoA) on your favorite distance matrix. In this case, we are using weighted UniFrac.

do <- ordinate(ps_prop, method = "PCoA", distance = wUF)

baseplot <- plot_ordination(ps_prop, do,
                            type = "samples",
                            color = "Treatment",
                            shape = "Timepoint") +
  ggtitle("HCC ordination, all samples\nmethod=PCoA, distance=weighted UniFrac") +
  theme_bw()

# Single plot with all samples:
baseplot +
  geom_point(size = 2)

# Idem, but with sample names on the plot: 
baseplot +
  geom_point(size = 1) + 
  geom_label(aes(label = SampleID), size = 3.5, color = "black")

# Facet by time point:
baseplot +
  geom_point(size = 2) + 
  ggtitle("HCC ordination, all samples\nmethod=PCoA, distance=weighted UniFrac") +
  facet_wrap(~Timepoint, 2)

We can see some grouping by timepoint. Depending on your experiment and questions, you might want to consider other ordination or clustering approaches.

Permutational analysis of variance (PERMANOVA)

Finally, we’ll test whether there is a significant ecological level treatment effect. PERMANOVA sounds fancy, but it is just an ANOVA performed using permutations. Permutations are used to determine how data may appear if there is no treatment effect and group differences are due to random chance. Observed data are then compared to the randomized data to calculate a p-value.

Treatments are independent within Timepoints, so we will first subset the data by Timepoint:

ps_S1 <- subset_samples(ps_prop, Timepoint == "S1")
ps_S3 <- subset_samples(ps_prop, Timepoint == "S3")
ps_S4 <- subset_samples(ps_prop, Timepoint == "S4")

Subset the metadata and estimate UniFrac for S3:

metadata <- as(sample_data(ps_S3), "data.frame")
unifrac_dist <- UniFrac(ps_S3, weighted = TRUE)

Then we’ll use vegan::adonis2 to perform the PERMANOVA.

set.seed(100)  # Set the seed to make the analysis reproducible

permanova <- adonis2(unifrac_dist ~ Treatment,
                     data = metadata,
                     permutations = 10000)

permanova
## Permutation test for adonis under reduced model
## Terms added sequentially (first to last)
## Permutation: free
## Number of permutations: 10000
## 
## adonis2(formula = unifrac_dist ~ Treatment, data = metadata, permutations = 10000)
##           Df SumOfSqs      R2      F Pr(>F)   
## Treatment  3 0.067035 0.24478 2.0528 0.0035 **
## Residual  19 0.206821 0.75522                 
## Total     22 0.273856 1.00000                 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

The low p-value suggests that within the S3 timepoint (post-termination), there is a significant treatment effect.


Differential taxon abundance (DESeq)

Here, we will use DESeq2 to identify differentially abundant taxa between time points and treatments. DESeq2 was designed to analyze RNAseq datasets, which are similar to OTU/ASV data sets in that both handle large, sparse contingency tables generated from Illumina sequencing data. For more details, see McMurdie and Holmes 2014.

First, we’ll convert our non-normalized count data to a DESeq object.

dds <- phyloseq_to_deseq2(ps, ~Timepoint + Treatment)

dds
## class: DESeqDataSet 
## dim: 17600 71 
## metadata(1): version
## assays(1): counts
## rownames(17600): ASV_4 ASV_5 ... ASV_18037 ASV_18038
## rowData names(0):
## colnames(71): 101-S1 101-S3 ... 604-S3 604-S4
## colData names(7): SampleID Experiment ... Plot PCR_prod_V4.V5
colData(dds)
## DataFrame with 71 rows and 7 columns
##        SampleID Experiment Treatment Timepoint    Block     Plot PCR_prod_V4.V5
##        <factor>   <factor>  <factor>  <factor> <factor> <factor>      <numeric>
## 101-S1   101-S1        HCC        T1        S1       R1     P101       0.464003
## 101-S3   101-S3        HCC        T1        S3       R1     P101       1.000000
## 101-S4   101-S4        HCC        T1        S4       R1     P101       0.629419
## 102-S3   102-S3        HCC        T4        S3       R1     P102       0.925081
## 102-S4   102-S4        HCC        T4        S4       R1     P102       0.746320
## ...         ...        ...       ...       ...      ...      ...            ...
## 603-S4   603-S4        HCC        T3        S4       R6     P603       0.796571
## 604-S1   604-S1        HCC        T2        S1       R6     P604       0.779915
## 604-S2   604-S2        HCC        T2        S2       R6     P604       0.730354
## 604-S3   604-S3        HCC        T2        S3       R6     P604       0.926025
## 604-S4   604-S4        HCC        T2        S4       R6     P604       0.817147

There are two ways to analyze interaction effects using DESeq2. The first is to fit a multivariate model (e.g. ~A+B+A:B) and explore the model coefficients. The second is to fit a univariate model and explore pairwise contrasts. Here, we will group our two factors (Treatment and Timepoint) and use the latter approach.

# Prepare the data:
dds$Group <- factor(paste0(dds$Treatment, dds$Timepoint))
design(dds) <- formula(~Group)
dds <- dds[, -50]                    # Remove tech_rep sample
dds$Group <- droplevels(dds$Group)   # Drop tech_rep level from design matrix

# Perform the differential abundance analysis:
dds <- DESeq(dds)

results(dds)
## log2 fold change (MLE): Group T4S4 vs T1S1 
## Wald test p-value: Group T4S4 vs T1S1 
## DataFrame with 17600 rows and 6 columns
##            baseMean log2FoldChange     lfcSE       stat          pvalue
##           <numeric>      <numeric> <numeric>  <numeric>       <numeric>
## ASV_4       163.314     -0.5518849  0.325626 -1.6948453 0.0901047681701
## ASV_5       161.981      1.6123820  0.282434  5.7088821 0.0000000113721
## ASV_7       115.697      0.7160631  0.322469  2.2205629 0.0263805813492
## ASV_8       104.576      0.0111895  0.371903  0.0300873 0.9759974233061
## ASV_9       107.540      0.7551121  0.235185  3.2107216 0.0013240215163
## ...             ...            ...       ...        ...             ...
## ASV_18034 0.0339025       0.862942   7.06641  0.1221189        0.902805
## ASV_18035 0.0339025       0.862942   7.06641  0.1221189        0.902805
## ASV_18036 0.0124023       0.382212   7.06641  0.0540885        0.956865
## ASV_18037 0.0243719       0.862942   7.06641  0.1221189        0.902805
## ASV_18038 0.0243719       0.862942   7.06641  0.1221189        0.902805
##                    padj
##               <numeric>
## ASV_4     0.20919428694
## ASV_5     0.00000586761
## ASV_7     0.09404214332
## ASV_8     0.98632468985
## ASV_9     0.01382801753
## ...                 ...
## ASV_18034            NA
## ASV_18035            NA
## ASV_18036            NA
## ASV_18037            NA
## ASV_18038            NA

Let’s explore the contrast, “T1S4 : T4S4” (no cc, seedling stage : late termination, seedling stage):

res <- results(dds, contrast = c("Group", "T1S4", "T4S4"))
summary(res)
## 
## out of 17572 with nonzero total read count
## adjusted p-value < 0.1
## LFC > 0 (up)       : 0, 0%
## LFC < 0 (down)     : 1, 0.0057%
## outliers [1]       : 46, 0.26%
## low counts [2]     : 0, 0%
## (mean count < 0)
## [1] see 'cooksCutoff' argument of ?results
## [2] see 'independentFiltering' argument of ?results
# Number of taxa with significantly higher abundance in T4S4:
sum(res$padj < 0.1 & res$log2FoldChange > 0, na.rm = TRUE)
## [1] 0
# Number of taxa with significantly lower abundance in T4S4:
sum(res$padj < 0.1 & res$log2FoldChange < 0, na.rm = TRUE)
## [1] 1

We can sort results by p-value and look at the top ASVs:

res.order <- res[order(res$pvalue), ]
head(res.order)[c(1:2, 6)]
## log2 fold change (MLE): Group T1S4 vs T4S4 
##  
## DataFrame with 6 rows and 3 columns
##           baseMean log2FoldChange        padj
##          <numeric>      <numeric>   <numeric>
## ASV_3479   3.27868      -36.83123 4.44874e-12
## ASV_1740   2.85123       -3.53555 1.00000e+00
## ASV_1623   3.90335        1.91786 1.00000e+00
## ASV_336    9.93278        1.83339 1.00000e+00
## ASV_1950   3.67406       -2.23211 1.00000e+00
## ASV_487    9.63303       -1.24601 1.00000e+00

And plot counts for the first in the ordered list ASV:

plotCounts(dds, gene = res.order@rownames[1], intgroup = "Group")

We can also plot counts by sample_ID:

plotCounts(dds, gene = "ASV_48", intgroup = "Group")

The plot can also be saved and customized with ggplot:

d <- plotCounts(dds, gene = "ASV_48", intgroup = "Group", returnData = TRUE)

ggplot(d, aes(x = Group, y = count)) +
  geom_point(position = position_jitter(w = 0.1, h = 0), size = 2) +
  scale_y_log10() +
  ggtitle(expression(paste("ASV 48, log"[10], "-scale"))) +
  theme_bw()

We can also subset our differentially abundant taxa to keep those with an adjusted p-value < 0.05:

sig <- subset(res.order, padj <= 0.05)
head(sig)
## log2 fold change (MLE): Group T1S4 vs T4S4 
## Wald test p-value: Group T1S4 vs T4S4 
## DataFrame with 1 row and 6 columns
##           baseMean log2FoldChange     lfcSE      stat      pvalue        padj
##          <numeric>      <numeric> <numeric> <numeric>   <numeric>   <numeric>
## ASV_3479   3.27868       -36.8312    4.4952  -8.19345 2.53837e-16 4.44874e-12

What taxa are associated with significantly differentially abundant ASVs?

sig_asv <- rownames(sig)
sig_taxa <- tax_table(ps)[sig_asv,]
head(sig_taxa)
## Taxonomy Table:     [1 taxa by 6 taxonomic ranks]:
##          Kingdom    Phylum       Class        Order          
## ASV_3479 "Bacteria" "Firmicutes" "Clostridia" "Clostridiales"
##          Family             Genus                        
## ASV_3479 "Clostridiaceae_1" "Clostridium_sensu_stricto_9"
sig_write <- cbind(sig, sig_taxa)
write.csv(sig_write, file.path(outdir, 'DATaxa_T1S1_T1S4.csv'))

Report that we are done!

print('Done with ASV inference.')
## [1] "Done with ASV inference."

LS0tCnRpdGxlOiAiPGJyPldvcmtmbG93IHBhcnQgSUlJOjxicj5BbmFseXppbmcgQVNWIERhdGEiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6IGNlcnVsZWFuCiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQotLS0KCmBgYHtyIHNldHVwLCBlY2hvPUZBTFNFLCBwdXJsPUZBTFNFfQpyb290X2RpciA8LSAnL2hvbWUvamVsbWVyL0Ryb3Bib3gvbWNpYy90ZWFjaC93c2gvMjAyMC0xMl9taWNyb2Jpb20vT1NDL21hc3RlcicKa25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSByb290X2RpcikgIyB1bi1jb21tZW50IGF0IE9TQwoKa25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGNhY2hlID0gVFJVRSwKICBldmFsID0gVFJVRSAjIGNoYW5nZSBhcyBuZWVkZWQKICApCmBgYAoKPGJyPgoKLS0tLS0KCiMjIEdvYWxzCgpBbmFseXplIG1pY3JvYmlvbWUgZXhwZXJpbWVudGFsIGRhdGEgYXMgYSBwaHlsb3NlcSBvYmplY3QgLSBleHBsb3JlIGVjb2xvZ2ljYWwgbWV0cmljcyBhbmQgaWRlbnRpZnkgZGlmZmVyZW50aWFsbHkgYWJ1bmRhbnQgdGF4YS4KCi0tLS0tCgojIyBCZWZvcmUgV2UgR2V0IFN0YXJ0ZWQKCiMjIyBOb3RlcwoKLSBUaGlzIGlzIGEgc2xpZ2h0bHkgbW9kaWZpZWQgdmVyc2lvbiBvZiBhIGRvY3VtZW50IGNyZWF0ZWQgYnkgW01hdHRoZXcgV2lsbG1hbl0obWFpbHRvOndpbGxtYW4uMThAb3N1LmVkdSkuCgotIFRvIGNvbnZlcnQgYW4gYFJtZGAgKFIgTWFya2Rvd24pIGZpbGUgdG8gYW4gUiBzY3JpcHQsCiAgdHlwZSB0aGUgZm9sbG93aW5nIGluIGFuIFIgY29uc29sZToKICBga25pdHI6OnB1cmwoaW5wdXQ9IjxmaWxlbmFtZT4uUm1kIilgLgogIChZb3UgY2FuIGRvd25sb2FkIHRoaXMgYFJtZGAgZmlsZSBieSBjbGlja2luZyB0aGUgYENvZGVgIGJ1dHRvbgogIGluIHRoZSB0b3AtcmlnaHQgb2YgdGhpcyBwYWdlIC0tIGJ1dCB3ZSB3aWxsIG9wZW4gaXQgYXQgT1NDLikKCiMjIyBPcGVuIHRoaXMgZmlsZSBpbiBSU3R1ZGlvIFNlcnZlciBhdCBPU0MKCi0gVGhpcyBhc3N1bWVzIHlvdSBzdGlsbCBoYXZlIGFuIGFjdGl2ZSBSU3R1ZGlvIFNlcnZlciBqb2IgYXQgT1NDOwogICBpZiBub3Qgc2VlIHRoZQogICBbaW5zdHJ1Y3Rpb25zIG9uIHRoZSBwcmV2aW91cyBwYWdlXSgwNy1BU1YtaW5mZXJlbmNlLmh0bWwjc3RhcnQtYW4tcnN0dWRpby1zZXJ2ZXItam9iLWF0LW9zYykgdG8gc3RhcnQgb25lLgoKLSBJbiB0aGUgRmlsZXMgcGFuZSwgaW4gdGhlIGBtYXJrZG93bmAgZGlyZWN0b3J5LAogIG9wZW4gdGhlIGZpbGUgYDA4LXBvc3RBU1YtYW5hbHlzaXMuUm1kYC4gVGhhdCdzIHRoaXMgZmlsZSEKCiAKLS0tLS0KCiMjIEdldHRpbmcgU3RhcnRlZAoKIyMjIEluc3RhbGwgYW5kIExvYWQgUGFja2FnZXMKCkFkZCBvdXIgY3VzdG9tIGxpYnJhcnkgYWdhaW4gaWYgeW91IHJlc3RhcnRlZCB5b3VyIFIgc2Vzc2lvbjoKCmBgYHtyIGxpYmxvYWR9CiMubGliUGF0aHMobmV3ID0gJy9mcy9wcm9qZWN0L1BBUzA0NzEvLlIvNC4wLycpCmBgYAoKQW5kIGxvYWQgdGhlIG5lY2Vzc2FyeSBwYWNrYWdlczoKCmBgYHtyIHBhY2thZ2VzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwYWNrYWdlcyA8LSBjKCd0aWR5dmVyc2UnLCAndmVnYW4nLCAncGh5bG9zZXEnLCAnZGVjb250YW0nLCAnYXBlJywKICAgICAgICAgICAgICAnREVTZXEyJywgJ21pY3JvYmlvbWUnLCAnbWV0YWdlbm9tZVNlcScsICdyZW1vdGVzJywKICAgICAgICAgICAgICAnYW1wdmlzMicsICdicmVha2F3YXknKQpwYWNtYW46OnBfbG9hZChjaGFyID0gcGFja2FnZXMpCgojIElmIHlvdSB3YW50ZWQgdG8gaW5zdGFsbCBhbmQgbG9hZCB0aGVzZSBwYWNrYWdlcyB5b3Vyc2VsZiwKIyBmaXJzdCBtYWtlIHN1cmUgeW91IGhhdmUgdGhlIHBhY21hbiBwYWNrYWdlIGluc3RhbGxlZDoKIyMgaW5zdGFsbC5wYWNrYWdlcygncGFjbWFuJykKIyBUaGVuLCB0aGUgY29kZSBhYm92ZSB3b3VsZCB3b3JrIGV4Y2VwdCBmb3IgdGhlIGxhc3QgdHdvIHBhY2thZ2VzLAojIHdoaWNoIGFyZSBhdmFpbGFibGUgZnJvbSBHaXRodWIgb25seS4gVG8gaW5zdGFsbCB0aGVzZSwgeW91IHdvdWxkIHJ1bjoKIyMgcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoIk1hZHNBbGJlcnRzZW4vYW1wdmlzMiIpCiMjIHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJhZHc5Ni9icmVha2F3YXkiKQpgYGAKCiMjIyBTZXQgRGlyZWN0b3JpZXMKCmBgYHtyfQojIERpciB3aXRoIGlucHV0IGZpbGVzOgppbmRpciA8LSAnYW5hbHlzaXMvQVNWX2luZmVyZW5jZS8nCgojIERpciBmb3Igb3V0cHV0OgpvdXRkaXIgPC0gJ2FuYWx5c2lzL3Bvc3RBU1ZfYW5hbHlzaXMvJwpgYGAKCiMjIyBMb2FkIHRoZSBEYXRhCgpUaGlzIHNlc3Npb24gc3RhcnRzIHdpdGggYSAqcGh5bG9zZXEqIG9iamVjdCBzaW1pbGFyIHRvIHRoZSBvbmUgZ2VuZXJhdGVkIHVzaW5nCipEQURBMiogaW4gdGhlIFtwcmV2aW91cyBzZXNzaW9uXSgwNi1yZWFkcy10by1BU1YuUm1kKS4KCmBgYHtyIGRhdGF9CnBzX3JhdyA8LSByZWFkUkRTKGZpbGUucGF0aChpbmRpciwgJ3BzXzE2U19WNF93aXRodHJlZS5yZHMnKSkKCiMgVGhpcyB3b3VsZCBsb2FkIHRoZSBvYmplY3QgZnJvbSB0aGUgcHJldmlvdXMgc2Vzc2lvbiwgd2hpY2ggZG9lc24ndCBjb250YWluIGEgdHJlZToKIyBwc19yYXcgPC0gcmVhZFJEUyhmaWxlLnBhdGgoaW5kaXIsICdwc19WNC5yZHMnKSkKYGBgCgpPdXIgKnBoeWxvc2VxKiBvYmplY3QgaXMgbWFkZSB1cCBvZiB0aGUgZm9sbG93aW5nIGNvbXBvbmVudHMgKHNsb3RzKToKCi0gYG90dV90YWJsZWAKLSBgc2FtcGxlX2RhdGFgCi0gYHRheF90YWJsZWAKLSBgcGh5X3RyZWVgCgpMZXQncyBoYXZlIGEgbG9vayBhdCB0aGUgb2JqZWN0OgoKYGBge3IgcHN9CnBzX3JhdwoKIyBBbmQgdGhlIGFzc2lnbmVkIHRheG9uIG5hbWVzOgp0YXhhX25hbWVzKHBzX3JhdylbMTozXQpgYGAKCkFzIHdlIGNhbiBzZWUsIHRoZSB0YXhvbiBuYW1lcyBhcmUgY3VycmVudGx5IHRoZSBhc3NvY2lhdGVkIHNlcXVlbmNlcy4KV2UgY2FuIGNyZWF0ZSBhIG5ldyAqcGh5bG9zZXEqIGNvbXBvbmVudCB0byBzdG9yZSB0aGVzZSBzZXF1ZW5jZXMsCnRoZW4gcmVuYW1lIHRoZSBBU1ZzIHNvIHNvbWV0aGluZyBzaG9ydGVyICgiQVNWXzEiLCAiQVNWXzIiLCBldGMuKS4KCmBgYHtyfQpkbmEgPC0gQmlvc3RyaW5nczo6RE5BU3RyaW5nU2V0KHRheGFfbmFtZXMocHNfcmF3KSkKbmFtZXMoZG5hKSA8LSB0YXhhX25hbWVzKHBzX3JhdykKCiMgTWVyZ2Ugc2VxdWVuY2Ugb2JqZWN0IGludG8gdGhlIHBoeWxvc2VxIG9iamVjdDoKcHNfcmF3IDwtIG1lcmdlX3BoeWxvc2VxKHBzX3JhdywgZG5hKQpwc19yYXcKCiMgUmVuYW1lIEFTVnM6CnRheGFfbmFtZXMocHNfcmF3KSA8LSBwYXN0ZSgiQVNWIiwgMTpudGF4YShwc19yYXcpLCBzZXAgPSAiXyIpCnRheGFfbmFtZXMocHNfcmF3KVsxOjNdCmBgYAoKLS0tLS0KCiMjIEZpbHRlciB0YXhhCgojIyMgSWRlbnRpZnkgYW5kIFJlbW92ZSBDb250YW1pbmFudHMKCkl0IGlzIHBvc3NpYmxlIHRvIGludHJvZHVjZSBjb250YW1pbmF0aW5nIG1pY3JvYmVzIGR1cmluZyBzYW1wbGUgcHJlcGFyYXRpb24uCkJlZm9yZSBhbmFseXppbmcgdGhlIGRhdGEsIHdlIHdpbGwgaWRlbnRpZnkgYW5kIHJlbW92ZSBwcm9iYWJsZSBjb250YW1pbmFudHMgdXNpbmcKdGhlIFtkZWNvbnRhbSBwYWNrYWdlXShodHRwczovL2JlbmpqbmViLmdpdGh1Yi5pby9kZWNvbnRhbS92aWduZXR0ZXMvZGVjb250YW1faW50cm8uaHRtbCkuICAgCgpJbiB0aGlzIGNhc2UsIHdlIHdpbGwgZGVmaW5lIGEgY29uYXRhbWluYW50IGFzIGFuIEFTViB3aG9zZSBhYnVuZGFuY2UgY29ycmVsYXRlcwp3aXRoIEROQSBjb25jZW50cmF0aW9uIChwb3N0LVBDUikuCk91ciBhc3N1bXB0aW9uIGhlcmUgaXMgdGhhdCBlYWNoIHNvaWwgdGF4b24ncyBhYnVuZGFuY2Ugc2hvdWxkIGJlIGluZGVwZW5kYW50IG9mCkROQSBjb25jZW50cmF0aW9uLiBIb3dldmVyLCBpZiB3ZSB3ZXJlIHRvIHNwaWtlIGVhY2ggc2FtcGxlIHdpdGggYSBjb250YW1pbmFudCwKdGhhdCBjb250YW1pbmFudCB3b3VsZCBzaG93IGEgZ3JlYXRlciBhYnVuZGFuY2UgaW4gc2FtcGxlcyB3aXRoIGxvd2VyIEROQSBjb25jZW50cmF0aW9ucy4KVG8gZG8gdGhpcywgd2UgbmVlZCBETkEgY29uY2VudHJhdGlvbiBtZWFzdXJlZCBhZnRlciBQQ1IgYW5kIGJlZm9yZSBwb29saW5nIG9mIHRoZSBzYW1wbGVzLgoKVGhlc2UgZGF0YSBhcmUgaW4gY29sdW1uIDcgb2YgdGhlIHNhbXBsZSBkYXRhIGNvbXBvbmVudDoKCmBgYHtyIEROQV9jb25jZW50cmF0aW9ufQpoZWFkKHNhbXBsZV9kYXRhKHBzX3JhdykpCmBgYAoKTUNJQyBtZWFzdXJlZCBvdXIgRE5BIGNvbmNlbnRyYXRpb24gYnkgY29tcGFyaW5nIGJhbmQgaW50ZW5zaXRpZXMgdG8gYSBzaW5nbGUKcmVmZXJlbmNlIGJhbmQuIE1DSUMncyBETkEgY29uY2VudHJhdGlvbiBtZWFzdXJlbWVudHMgcmFuZ2UgZnJvbSAwIHRvIDEgKHRoZSByZWZlcmVuY2UpLgpaZXJvIGluIHRoaXMgY2FzZSBpcyBjb2RlZCBhcyBgTkFgLgpUaGUgKmRlY29udGFtKiBwYWNrYWdlIGNhbid0IGhhbmRsZSBOQSdzIG9yIDAncywKc28gd2UgbmVlZCB0byBjaGFuZ2UgdGhlbSB0byBhIHNtYWxsIHZhbHVlIChlLmcuIDAuMDEpLgoKYGBge3J9CiMgRmlyc3QsIHdlIGlkZW50aWZ5IHRoZSBOQXMgaW4gdGhlIEROQSBtZWFzdXJlbWVudCBjb2x1bW46Ck5BcyA8LSBpcy5uYShzYW1wbGVfZGF0YShwc19yYXcpJFBDUl9wcm9kX1Y0LlY1KQoKIyBUaGVuLCB3ZSByZXBsYWNlIE5BcyB3aXRoIDAuMDE6CnNhbXBsZV9kYXRhKHBzX3JhdykkUENSX3Byb2RfVjQuVjVbTkFzXSA8LSAwLjAxCmBgYAoKV2Ugd2lsbCB1c2UgYGRlY29udGFtOjppc0NvbnRhbWluYW50KG1ldGhvZCA9ICJmcmVxdWVuY3kiKWAgdG8gdGVzdCBlYWNoIHRheG9uCmFnYWluc3QgdGhlIG51bGwgaHlwb3RoZXNpczogYWJ1bmRhbmNlIGlzIG5vdCBhc3NvY2lhdGVkIHdpdGggRE5BIGNvbmNlbnRyYXRpb24uCkJlY2F1c2UgdGhlIEROQSBjb25jZW50cmF0aW9uIG1lYXN1cmVtZW50cyBhcmUgbm90IHZlcnkgYWNjdXJhdGUsCmFuZCB3ZSB3YW50IHRvIG1ha2Ugc3VyZSB0byByZW1vdmUgcG9zc2libGUgY29udGFtaW5hbnRzLAp3ZSB1c2UgYSByZWxheGVkIHAtdmFsdWUgKDAuMikgdG8gcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXM6CgpgYGB7ciwgY2FjaGU9VFJVRX0KY29udGFtX2RmX2ZyZXEgPC0gaXNDb250YW1pbmFudChwc19yYXcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImZyZXF1ZW5jeSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uYyA9ICJQQ1JfcHJvZF9WNC5WNSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhyZXNob2xkID0gMC4yKQpoZWFkKGNvbnRhbV9kZl9mcmVxKQpgYGAKCkFzIHdlIGNhbiBzZWUsIHRoZSB0b3AgMyBBU1ZzIGFyZSBpZGVudGlmaWVkIGFzIHByb2JhYmxlIGNvbnRhbWluYW50cy4KSG93IG1hbnkgY29udGFtaW5hbnRzIHdlcmUgaWRlbnRpZmllZCAoYFRSVUVgIGluIHRoZSBgY29udGFtaW5hbnRgIGNvbHVtbikKaW4gdG90YWw/CgpgYGB7cn0KdGFibGUoY29udGFtX2RmX2ZyZXEkY29udGFtaW5hbnQpCmBgYAoKV2hhdCBhcmUgdGhlIG1vc3QgYWJ1bmRhbnQgY29udGFtaW5hbnQgdGF4YT8KCmBgYHtyfQojIEdldCBhYnVuZGFuY2UgcmFua3MgZm9yIGNvbnRhbWluYW50IEFTVnM6CmhlYWQod2hpY2goY29udGFtX2RmX2ZyZXEkY29udGFtaW5hbnQpKQoKIyBDaGVjayB3aGljaCB0YXhhIGFyZSBjb250YW1pbmFudHM6CnBzX2NvbnRhbSA8LSBwcnVuZV90YXhhKGNvbnRhbV9kZl9mcmVxJGNvbnRhbWluYW50LCBwc19yYXcpCmhlYWQodGF4X3RhYmxlKHBzX2NvbnRhbSkpCmBgYAoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgb3VyIHRlc3RlZCBhc3NvY2lhdGlvbnMgZm9yIHNldmVyYWwgdGF4YToKCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQpwbG90X2ZyZXF1ZW5jeShwc19yYXcsCiAgICAgICAgICAgICAgIHRheGFfbmFtZXMocHNfcmF3KVtjKDEsIDIsIDMsIDQsIDUsIDExKV0sCiAgICAgICAgICAgICAgIGNvbmMgPSAiUENSX3Byb2RfVjQuVjUiKSArCiAgeGxhYigiRE5BIENvbmNlbnRyYXRpb24gKHJlbGF0aXZlIHRvIHNpbmdsZSBtYXJrZXIpIikKYGBgCiAgIApBcyB3ZSBjYW4gc2VlLCBhYnVuZGFuY2Ugb2YgQVNWcyAxLCAyLCAzLCBhbmQgMTEgYXJlIHBhcnRpY3VsYXJseSBoaWdoIGluIHNhbXBsZXMKd2l0aCBsb3cgRE5BIGNvbmNlbnRyYXRpb24uCgpXaGljaCBzYW1wbGVzIGhhdmUgaGlnaGVzdCBhYnVuZGFuY2VzIG9mIHRoZSBjb250YW1pbmFudHM/CgpgYGB7cn0KYXN2MSA8LSBvdHVfdGFibGUocHNfcmF3KVssIDFdCmhlYWQoYXN2MVtvcmRlcihhc3YxLCBkZWNyZWFzaW5nID0gVFJVRSldKQoKYXN2MiA8LSBvdHVfdGFibGUocHNfcmF3KVssIDJdCmhlYWQoYXN2MltvcmRlcihhc3YyLCBkZWNyZWFzaW5nID0gVFJVRSldKQpgYGAKCkhvdyBkbyB0aGVzZSBjb250YW1pbmFudHMgb3ZlcmxhcCB3aXRoIEFTVnMgcHJlc2VudCBpbiB0aGUgbmVnYXRpdmUgY29udHJvbD8KCmBgYHtyfQpvdHVfdGFibGUocHNfcmF3KVs3MDo3NSwgMToxMV0KYGBgCgpBU1ZzIGhhdmUgaGlnaCByZWFkIGNvdW50cyBpbiB0aGUgZXh0cmFjdGlvbiBuZWdhdGl2ZSBjb250cm9sLgpUaHVzLCB3ZSBjYW4gYmUgZmFpcmx5IGNlcnRhaW4gdGhlc2UgYXJlIGNvbnRhbWluYW50cyBhbmQgbm90IHJlcHJlc2VudGF0aXZlCm9mIG91ciBleHBlcmltZW50YWwgc2FtcGxlcy4gICAKICAgClJlbW92ZSB0aGUgY29udGFtaW5hbnRzIHRoYXQgd2UgaWRlbnRpZmllZDoKCmBgYHtyIHBydW5lX2RlY29udGFtfQpwc19ub25jb250YW0gPC0gcHJ1bmVfdGF4YSghY29udGFtX2RmX2ZyZXEkY29udGFtaW5hbnQsIHBzX3JhdykKCnBzX25vbmNvbnRhbQpgYGAKCldoYXQgcHJvcG9ydGlvbiBvZiBvdXIgY291bnQgZGF0YSB3ZXJlIHJlbW92ZWQgYXMgY29udGFtaW5hbnRzPwoKYGBge3IgY291bnRfZGVjb250YW0xfQpwcmUgPC0gc3VtKHNhbXBsZV9zdW1zKHBzX3JhdykpCnBvc3QgPC0gc3VtKHNhbXBsZV9zdW1zKHBzX25vbmNvbnRhbSkpCgoocHJlLXBvc3QpIC8gcHJlCmBgYAoKIyMjIFJlbW92ZSBub24tYmFjdGVyaWFsLCBub24tYXJjaGFlYWwgQVNWcwoKVGhlIFY0IHJlZ2lvbiBvZiAxNlMgclJOQSBpcyBjb25zZXJ2ZWQgd2l0aGluIGNlcnRhaW4gYmFjdGVyaWEsIGFyY2hhZWEsCmNobG9yb3BsYXN0cywgbWl0b2Nob25kcmlhLCBhbmQgZXVrYXJ5b3Rlcy4KU2luY2Ugd2UgYXJlIG9ubHkgaW50ZXJlc3RlZCBpbiBiYWN0ZXJpYSBhbmQgYXJjaGFlYSBoZXJlLAp3ZSdsbCBuZWVkIHRvIHJlbW92ZSBvdGhlciB0YXhhIGZyb20gb3VyIG9iamVjdC4KCmBgYHtyIHJlbW92ZV9ub25iYWN9CiMgQ3JlYXRlIHBzIHN1YnNldHMgZm9yIGNobG9yb3BsYXN0LCBtaXRvY2hvbmRyaWEsIGFuZCBldWthcnlvdGVzOgpjaGxyIDwtIHN1YnNldF90YXhhKHBzX25vbmNvbnRhbSwgT3JkZXIgPT0gIkNobG9yb3BsYXN0IikKbWl0IDwtIHN1YnNldF90YXhhKHBzX25vbmNvbnRhbSwgRmFtaWx5ID09ICJNaXRvY2hvbmRyaWEiKQpldWsgPC0gc3Vic2V0X3RheGEocHNfbm9uY29udGFtLCBLaW5nZG9tID09ICJFdWthcnlvdGEiKQoKIyBHZXQgYWxsIHRheG9uIElEcyB0aGF0IHdlIHdhbnQgdG8gcmVtb3ZlOgpiYWRfdGF4YSA8LSBjKHRheGFfbmFtZXMoY2hsciksIHRheGFfbmFtZXMobWl0KSwgdGF4YV9uYW1lcyhldWspKQoKIyBHZXQgYWxsIHRheG9uIElEcyB0aGF0IHdlIHdhbnQgdG8ga2VlcDoKYWxsX3RheGEgPC0gdGF4YV9uYW1lcyhwc19ub25jb250YW0pCmdvb2RfdGF4YSA8LSBhbGxfdGF4YVshKGFsbF90YXhhICVpbiUgYmFkX3RheGEpXQoKIyBTdWJzZXQgcGh5bG9zZXEgb2JqZWN0Ogpwc19iYWNfYXJjIDwtIHBydW5lX3RheGEoZ29vZF90YXhhLCBwc19ub25jb250YW0pCmBgYAoKV2hhdCBwcm9wb3J0aW9uIG9mIEFTVnMgd2VyZSBrZXB0PwoKYGBge3Iga2VwdF9iYWN9CnByZSA8LSBzdW0oc2FtcGxlX3N1bXMocHNfbm9uY29udGFtKSkKcG9zdCA8LSBzdW0oc2FtcGxlX3N1bXMocHNfYmFjX2FyYykpCnBvc3QgLyBwcmUKYGBgCiAgIApGaW5hbGx5LCB3ZSdsbCBhbHNvIHJlbW92ZSBub24tZXhwZXJpbWVudGFsIHNhbXBsZXMgKGUuZy4gbmVnYXRpdmUgY29udHJvbHMpLgpOb3RlLCB0aG91Z2gsIHRoYXQgbm9uLWV4cGVyaW1lbnRhbCBzYW1wbGVzIGFyZSBpbXBvcnRhbnQgZm9yIGV2YWx1YXRpbmcgdGhlCnF1YWxpdHkgb2YgeW91ciByZWFnZW50cywgY29uc2lzdGVuY3kgYWNyb3NzIHNlcXVlbmNpbmcgcnVucywgYW5kIGNvbnRhbWluYXRpb24uCgpgYGB7ciBzdWJzZXRfSENDfQpwcyA8LSBzdWJzZXRfc2FtcGxlcyhwc19iYWNfYXJjLCBFeHBlcmltZW50ID09ICJIQ0MiKQoKcHMKYGBgCgotLS0tLQoKIyMgRmlsdGVyIHNhbXBsZXMKCkFmdGVyIGhhdmluZyBmaWx0ZXJlZCBvdXQgdW53YW50ZWQgdGF4YSwKd2Ugbm93IG5lZWQgdG8gZmlsdGVyIG91dCB1bmluZm9ybWF0aXZlIHNhbXBsZXMgLS0KdGhvc2Ugd2l0aCBsb3cgdG90YWwgdGF4b24gY291bnRzLgoKRmlyc3QsIGhvdyBtYW55IGNvdW50cyBkbyB3ZSBoYXZlIGZvciBlYWNoIHNhbXBsZT8KCmBgYHtyIHN1bXN9CnN1bXMgPC0gc2FtcGxlX3N1bXMocHMpCnN1bXNbb3JkZXIoc3VtcyldCgojIExvd2VzdCB0b3RhbCBjb3VudDoKbWluKHN1bXMpCgojIFNhbXBsZSB3aXRoIGxvd2VzdCBjb3VudHM6Cm5hbWVzKHN1bXNbb3JkZXIoc3VtcyldWzFdKQpgYGAKClRvIHJlbW92ZSB1bmluZm9ybWF0aXZlIHNhbXBsZXMsIHdlIHdpbGwgb25seSBrZWVwIHRob3NlIHdpdGggb3ZlciAxLDAwMCBjb3VudHMuCldlIHdpbGwgYWxzbyByZW1vdmUgYSB0ZWNobmljYWwgcmVwbGljYXRlLAp0aG91Z2ggbm90ZSB0aGF0IHRlY2huaWNhbCByZXBsaWNhdGVzIGFyZSBpbXBvcnRhbnQgZm9yIHF1YWxpdHkgY29udHJvbCBvZiBydW5zCmFuZCBmb3IgdW5kZXJzdGFuZGluZyB0aGUgcmVwcm9kdWNpYmlsaXR5IG9mIHRoZSBtZXRob2RvbG9neS4gCgpgYGB7ciBzdWJzZXRfMWt9CiMgUmVtb3ZlIHNhbXBsZXMgd2l0aCBsb3cgY291bnRzOgpwcyA8LSBzdWJzZXRfc2FtcGxlcyhwcywgc2FtcGxlX3N1bXMocHMpID4gMTAwMCkKCiMgUmVtb3ZlIHRoZSB0ZWNobmljYWwgcmVwbGljYXRlOgpwcyA8LSBzdWJzZXRfc2FtcGxlcyhwcywgU2FtcGxlSUQgIT0gIjUwMVM0IikKYGBgCgpMZXQncyB0YWtlIGEgbG9vayBhdCBvdXIgY3VycmVudCBgcGh5bG9zZXFgIG9iamVjdDoKCmBgYHtyIGxvb2tfcHN9CnNhbXBsZV9kYXRhKHBzKVsxOjEwXQoKaGVhZCh0KG90dV90YWJsZShwcylbMToxMF0pKQoKaGVhZCh0YXhfdGFibGUocHMpKQpgYGAKCi0tLS0tCgojIyBOb3JtYWxpemF0aW9uCgpOb3JtYWxpemF0aW9uIGlzIGN1cnJlbnRseSBhIG11Y2gtZGlzY3Vzc2VkIGlzc3VlIG9mIG1pY3JvYmlvbWUgc3R1ZGllcy4KRGlmZmVyZW5jZXMgaW4gcmVhZCBkZXB0aCBiZXR3ZWVuIHNhbXBsZXMgb2Z0ZW4gbmVlZCB0byBiZSBjb3JyZWN0ZWQgYmVmb3JlIGFuYWx5c2lzLgpTZXZlcmFsIG5vcm1hbGl6YXRpb24gbWV0aG9kcyBoYXZlIGJlZW4gcHJvcG9zZWQsIGFuZCBubyBzaW5nbGUgbWV0aG9kIGlzIHBlcmZlY3QuCkl0IG1heSBiZSB0aGF0IHRoZSBtb3N0IGFwcHJvcHJpYXRlIG1ldGhvZCBkZXBlbmRzIG9uIHRoZSBhbmFseXNpcy4KCkluIHRoaXMgdHV0b3JpYWwsIHdlIHdpbGwgdXNlOgogIAogIC0gUHJvcG9ydGlvbi1ub3JtYWxpemVkIGRhdGEgdG8gZXN0aW1hdGUgZWNvbG9naWNhbCBtZXRyaWNzLgogIAogIC0gU3RhYmlsaXppbmcgdHJhbnNmb3JtYXRpb24gbm9ybWFsaXplZCBkYXRhIHRvIGlkZW50aWZ5IGRpZmZlcmVudGlhbGx5IGFidW5kYW50IHRheGEuCgpIb3dldmVyLCBmb3IgeW91ciByZWZlcmVuY2UsIHdlIHdpbGwgYWxzbyBwcmVzZW50IGNvZGUgZm9yIHR3byBvdGhlciBub3JtYWxpemF0aW9uCm1ldGhvZHM6IHJhcmVmYWN0aW9uIGFuZCBjdW11bGF0aXZlIHN1bSBzY2FsaW5nLgoKRm9yIG1vcmUgZGV0YWlscywgcmVhZApbTWNNdXJkaWUgYW5kIEhvbG1lcyAyMDE0XShodHRwczovL2R4LnBsb3Mub3JnLzEwLjEzNzEvam91cm5hbC5wY2JpLjEwMDM1MzEpCmFuZCBbTWNLbmlnaHQgZXQgYWwuIDIwMTldKGh0dHA6Ly9kb2kud2lsZXkuY29tLzEwLjExMTEvMjA0MS0yMTBYLjEzMTE1KS4KCi0tLS0tCgojIyMgUHJvcG9ydGlvbiBOb3JtYWxpemF0aW9uCgpQcm9wb3J0aW9uIG5vcm1hbGl6YXRpb24gaW52b2x2ZXMgZGl2aWRpbmcgZWFjaCBPVFUgY291bnQgYnkgdGhlIHRvdGFsIHN1bSBmb3IgZWFjaCBzYW1wbGUuClRoZSByZXN1bHRpbmcgY291bnQgZGF0YSB3aWxsIGFkZCB1cCB0byAxICgxMDAlKSBmb3IgZWFjaCBzYW1wbGUuCgpUaGUgYG1pY3JvYmlvbWU6OnRyYW5zZm9ybWAgZnVuY3Rpb24gY2FuIGJlIHVzZWQgdG8gZWFzaWx5IG5vcm1hbGl6ZSBjb3VudCBkYXRhIGFzCnByb3BvcnRpb25zIGluIGEgKnBoeWxvc2VxKiBvYmplY3Q6CgpgYGB7ciBub3JtX3Byb3BvcnRpb25zfQojIFByb3BvcnRpb24gbm9ybWFsaXphdGlvbjoKcHNfcHJvcCA8LSB0cmFuc2Zvcm0ocHMsICJjb21wb3NpdGlvbmFsIikKCiMgSGF2ZSBhIGxvb2sgYXQgdGhlIHJlc3VsdGluZyBPVFUgdGFibGU6Cm90dV90YWJsZShwc19wcm9wKVsxOjUsIDE6NV0KCiMgQ2hlY2sgd2hhdCB0aGUgc3VtcyBmb3IgZWFjaCBzYW1wbGUgYXJlIG5vdzoKaGVhZChzYW1wbGVfc3Vtcyhwc19wcm9wKSkKYGBgCgotLS0tLQoKIyMjIFZhcmlhbmNlIHN0YWJpbGl6aW5nIHRyYW5zZm9ybWF0aW9uCgpUaGlzIHRyYW5zZm9ybWF0aW9uIG1ldGhvZCwgYXZhaWxhYmxlIGluIFtERVNlcTJdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL3dvcmtmbG93cy92aWduZXR0ZXMvcm5hc2VxR2VuZS9pbnN0L2RvYy9ybmFzZXFHZW5lLmh0bWwpLApub3JtYWxpemVzIGRhdGEgd2l0aCByZXNwZWN0IHRvIGxpYnJhcnkgc2l6ZSBhcyB3ZWxsIGFzIGRpc3BlcnNpb24uICAgClR5cGUgYD92c3RgIG9yIHJlYWQgW0FuZGVycyBhbmQgSHViZXIgMjAxMF0oaHR0cDovL2R4LmRvaS5vcmcvMTAuMTE4Ni9nYi0yMDEwLTExLTEwLXIxMDYpCmZvciBtb3JlIGluZm9ybWF0aW9uLgoKYGBge3Igbm9ybV92c3QsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgRmlyc3Qgd2UgY29udmVydCB0aGUgcGh5bG9zZXEgb2JqZWN0IHRvIGEgREVTZXEgb2JqZWN0OgpkZHMgPC0gcGh5bG9zZXFfdG9fZGVzZXEyKHBzLCB+MSkKCiMgVGhlbiB3ZSBlc3RpbWF0ZSB0aGUgc2l6ZSBmYWN0b3JzOgpkZHMgPC0gZXN0aW1hdGVTaXplRmFjdG9ycyhkZHMpCgojIE5vdyBjYW4gZG8gdGhlIHRyYW5zZm9ybWF0aW9uOgpkZHNfdnN0IDwtIHZzdChkZHMsIGJsaW5kID0gVFJVRSkKCiMgSGF2ZSBhIGxvb2sgYXQgdGhlIHJlc3VsdGluZyBjb3VudHM6CnZzdF9jb3VudHMgPC0gYXNzYXkoZGRzX3ZzdCkKdCh2c3RfY291bnRzKVsxOjUsIDE6NV0KYGBgCgotLS0tLQoKIyMjIFJhcmVmYWN0aW9uCgpSYXJlZmFjdGlvbiBjYW4gYmUgdXNlZCB0byBzdWJzZXQgZGF0YSBzdWNoIHRoYXQgdGhlIGxpYnJhcnkgZGVwdGggaXMgdGhlIHNhbWUKZm9yIGVhY2ggc2FtcGxlLgpCZWNhdXNlIHNhbXBsaW5nIG9mIHRoZSBkYXRhIGlzIHJhbmRvbSwKcmFyZWZhY3Rpb24gY2FuIGFjY291bnQgZm9yIGFuIGVmZmVjdCBvZiB0b3RhbCByZWFkIGNvdW50IG9uIHRheGEgcmljaG5lc3MuCkhvd2V2ZXIsIHJhcmVmYWN0aW9uIGlzIG5vIGxvbmdlciBjb25zaWRlcmVkIHRvIGJlIGEgZ29vZCB3YXkgdG8gbm9ybWFsaXplCmFtcGxpY29uIHNlcXVlbmNpbmcgZGF0YQooc2VlIFtNY011cmRpZSAmIEhvbG1lcyAyMDE0XShodHRwczovL2R4LnBsb3Mub3JnLzEwLjEzNzEvam91cm5hbC5wY2JpLjEwMDM1MzEpKS4KCmBgYHtyLCByYXJlZnksIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnBzX3JhcmVmaWVkIDwtIHJhcmVmeV9ldmVuX2RlcHRoKHBzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBybmdzZWVkID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlLnNpemUgPSBtaW4oc2FtcGxlX3N1bXMocHMpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZSA9IEZBTFNFKQoKIyBIYXZlIGEgbG9vayBhdCB0aGUgcmVzdWx0OgpvdHVfdGFibGUocHNfcmFyZWZpZWQpWzE6NSwgMTo1XQoKaGVhZChzYW1wbGVfc3Vtcyhwc19yYXJlZmllZCkpCmBgYAoKQ29tcGFyZSB0aGVzZSBzYW1wbGUgc3VtcyB0byB0aGUgbm9uLXJhcmlmaWVkIGRhdGEgc3VtczoKCmBgYHtyLCBub24tcmFyX3N1bXN9CmhlYWQoc2FtcGxlX3N1bXMocHMpKQpgYGAKCi0tLS0tCgojIyMgQ3VtdWxhdGl2ZSBTdW0gU2NhbGluZwoKVGhlICptZXRhZ2Vub21lU2VxKiBDdW11bGF0aXZlIFN1bSBTY2FsaW5nIChDU1MpIG5vcm1hbGl6YXRpb24gaXMgYW5vdGhlciBvcHRpb24KZGV2ZWxvcGVkIGZvciBtaWNyb2Jpb21lIGRhdGEuCkZvciBtb3JlIGluZm9ybWF0aW9uLCByZWFkCltQYXVsc29uIGV0IGFsLiAyMDEzXShodHRwOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbm1ldGguMjY1OCkuCgpgYGB7ciBub3JtX2NzcywgbWVzc2FnZT1GQUxTRX0KIyBDb252ZXJ0IHRoZSBwaHlsb3NlcSBvYmplY3QgdG8gYSBtZXRhZ2Vub21lc2VxIG9iamVjdDoKbWdzX2NzcyA8LSBwaHlsb3NlcV90b19tZXRhZ2Vub21lU2VxKHBzKQoKIyBQZXJmb3JtIHRoZSBDdW11bGF0aXZlIFN1bSBTY2FsaW5nOgptZ3NfY3NzIDwtIGN1bU5vcm0obWdzX2NzcykKCiMgRXh0cmFjdCB0aGUgY291bnRzIGFuZCBhZGQgdGhlbSB0byBhIHNlcGFyYXRlIHBoeWxvc2VxIG9iamVjdDoKY3NzX2NvdW50cyA8LSBNUmNvdW50cyhtZ3NfY3NzLCBub3JtID0gVFJVRSkKcHNfY3NzIDwtIHBzCm90dV90YWJsZShwc19jc3MpIDwtIG90dV90YWJsZSh0KGNzc19jb3VudHMpLCB0YXhhX2FyZV9yb3dzID0gRkFMU0UpCmBgYAoKTm93IGxldHMgY29tcGFyZSB0aGUgb3JpZ2luYWwgZGF0YSB0byB0aGUgQ1NTIG5vcm1hbGl6ZWQgZGF0YToKCmBgYHtyIGNjc19jb21wYXJlfQpvdHVfdGFibGUocHMpWzE6NSwgMTo1XQpoZWFkKHNhbXBsZV9zdW1zKHBzKSkKCm90dV90YWJsZShwc19jc3MpWzE6NSwgMTo1XQpoZWFkKHNhbXBsZV9zdW1zKHBzX2NzcykpCmBgYAoKLS0tLS0KCiMjIFBsb3QgYWJ1bmRhbmNlcwoKSW4gdGhpcyBhbmQgZm9sbG93aW5nIHRoZSBleGFtcGxlcyB3ZSB3aWxsIHVzZSB0aGUgKipwcm9wb3J0aW9ucyoqIGRhdGEuCgpgYGB7ciBwbG90X2FidW5kYW5jZX0KIyBTdWJzZXQgdG8gdGhlIFQxIHRyZWF0bWVudApUMSA8LSBzdWJzZXRfc2FtcGxlcyhwc19wcm9wLCBUcmVhdG1lbnQgPT0gIlQxIikKCiMgQWdnbG9tZXJhdGUgdGF4YSB0byBwaHlsdW0gbGV2ZWw6ClQxX3BoeWx1bSA8LSB0YXhfZ2xvbShUMSwgdGF4cmFuayA9ICJQaHlsdW0iLCBOQXJtID0gRkFMU0UpCgojIFJlbW92ZSB1bmNvbW1vbiBwaHlsYToKVDFfcGh5bHVtIDwtIHN1YnNldF90YXhhKFQxX3BoeWx1bSwgdGF4YV9zdW1zKFQxX3BoeWx1bSkgPiAwLjEpCgojIFBsb3Q6CnBsb3RfYmFyKFQxX3BoeWx1bSwgZmlsbCA9ICJQaHlsdW0iKSArCiAgIGZhY2V0X3dyYXAoflRpbWVwb2ludCwgc2NhbGVzID0gImZyZWVfeCIsIG5yb3cgPSAxKQpgYGAKICAKLS0tLS0KCiMjIEFscGhhIGRpdmVyc2l0eQoKQWxwaGEgZGl2ZXJzaXR5IG1lYXN1cmVzIHRheG9ub21pYyBkaXZlcnNpdHkgd2l0aGluIGEgc2luZ2xlIHBvcHVsYXRpb24uCk1lYXN1cmVzIG9mIGFscGhhIGRpdmVyc2l0eSBpbmNsdWRlIHRheG9ub21pYyByaWNobmVzcyAoaS5lLiBudW1iZXIgb2YgdGF4YSksCmFuZCBpbmRpY2VzIHdoaWNoIGNvbWJpbmUgdGF4b25vbWljIHJpY2huZXNzIHdpdGggc29tZSBtZWFzdXJlIG9mIGV2ZW5uZXNzLiAgIAogICAKKk5vdGU6KiBNYW55IG9mIHRoZXNlIG1ldGhvZHMgYXJlIGdyZWF0bHkgaW5mbHVlbmNlZCBieSBzaW5nbGV0b24gZGF0YQooaS5lLiB0YXhhIHJlcHJlc2VudGVkIGJ5IGEgc2luZ2xlIGNvdW50KSwgbWVhbmluZyB0aGV5IG1heSBiZSB1bnJlbGlhYmxlIGlmIHlvdXIKZGF0YSBleGNsdWRlcyBzaW5nbGV0b25zLgpEQURBMiBkb2VzIG5vdCBvdXRwdXQgc2luZ2xldG9ucyBpZiBpdCBpcyBydW4gaW5kaXZpZHVhbGx5IG9uIGVhY2ggc2FtcGxlLgpUaHVzLCBpZiB1c2luZyBkYWRhMiB0byBpbmZlciBBU1ZzLCBpdCBpcyBhZHZpc2FibGUgdG8gdXNlIGBwb29sID0gVFJVRWAgd2hlbgpydW5uaW5nIHRoZSBkYWRhIGFsZ29yaXRobS4KVGhpcyB3aWxsIGFsbG93IGNhbGxpbmcgcGVyLXNhbXBsZSBzaW5nbGV0b25zLCBidXQgbm90IHBlci1zdHVkeSBzaW5nbGV0b25zLiAgIAogICAKIyMjIFRheG9ub21pYyByaWNobmVzczogUmFyZWZhY3Rpb24gcGxvdAoKT3VyIGdvYWwgaW4gZGVmaW5pbmcgcmljaG5lc3MgaXMgdG8gZGV0ZXJtaW5lIHRoZSBudW1iZXIgb2YgdW5pcXVlIHRheGEgd2l0aGluCmVhY2ggcG9wdWxhdGlvbiAoaW4gb3VyIGNhc2UsIGVhY2ggcGxvdCkuClRoaXMgaXMgZGlmZmljdWx0LCBhcyBvdXIgc2FtcGxlcyBjb250YWluIG9ubHkgYSBwb3J0aW9uIG9mIHRoZSB0b3RhbCByaWNobmVzcwp3aXRoaW4gb3VyIHBsb3RzLgpBcyBzZXF1ZW5jZSBkZXB0aCBpbmNyZWFzZXMgZm9yIGVhY2ggc2FtcGxlLCBzbyBkb2VzIHRoZSBudW1iZXIgb2YgdGF4YS4KVGhpcyBjYW4gYmUgaWxsdXN0cmF0ZWQgd2l0aCBhIHJhcmVmYWN0aW9uIHBsb3QuICAgCiAgIApUbyBtYWtlIGEgcmFyZWZhY3Rpb24gcGxvdCwgd2UgZHJhdyByYW5kb20gc2FtcGxlcyBmcm9tIG91ciBkYXRhIGFuZCBjb3VudCB0aGUKbnVtYmVyIG9mIHVuaXF1ZSBBU1ZzIGFzIHNhbXBsZXMgYXJlIGRyYXduLgpUaGUgcmVzdWx0aW5nIHJhcmVmYWN0aW9uIGN1cnZlIGlzIGV4cGVjdGVkIHRvIHJpc2UgcXVpY2tseSB0aGVuIHBsYXRldSBhcyB0aGUKbW9zdCBhYnVuZGFudCB0YXhhIGFyZSByZXByZXNlbnRlZC4KV2UgY2FuIG1ha2UgYSBxdWljayByYXJlZmFjdGlvbiBjdXJ2ZSBwbG90IGRpcmVjdGx5IGZyb20gb3VyIGBwaHlsb3NlcWAgb2JqZWN0IG9mCmFsbCBzYW1wbGVzIHVzaW5nIHRoZQpbYHZlZ2FuYCBwYWNrYWdlXShodHRwczovL3BlYXQtY2xhcmsuZ2l0aHViLmlvL0JJTzM4MS92ZWdhblR1dG9yaWFsLmh0bWwpOgoKYGBge3IgcmFyZWN1cnZlfQpyYXJlY3VydmUob3R1X3RhYmxlKHBzKSwgc3RlcCA9IDUwMCwgeGxhYiA9ICJTYW1wbGUgU2l6ZSIsIHlsYWIgPSAiVGF4YSIpCmBgYAogICAKV2UgY2FuIGFsc28gc3BsaXQgcmFyZWZhY3Rpb24gY3VydmVzIGJ5IGdyb3VwIHVzaW5nIHRoZQpbYGFtcHZpczJgIHBhY2thZ2VdKGh0dHBzOi8vbWFkc2FsYmVydHNlbi5naXRodWIuaW8vYW1wdmlzMi9hcnRpY2xlcy9hbXB2aXMyLmh0bWwpLgoKYGBge3IgcmFyZWZhY3Rpb24xfQptZXRhZGF0YSA8LSBkYXRhLmZyYW1lKHNhbXBsZV9kYXRhKHBzKSwgY2hlY2submFtZXMgPSBGQUxTRSkKCmFzdl90YWJsZSA8LSBkYXRhLmZyYW1lKHQob3R1X3RhYmxlKHBzKSksCiAgICAgICAgICAgICAgICAgICAgICAgdGF4X3RhYmxlKHBzKSwKICAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEZBTFNFKQpgYGAKCmBhbXB2aXMyYCByZXF1aXJlcyBhIGBTcGVjaWVzYCBjb2x1bW4sIHdoaWNoIG11c3QgYmUgYWRkZWQgdG8gb3VyIGRhdGEuCklmIHlvdXIgdGFibGUgYWxyZWFkeSBoYXMgYSBgU3BlY2llc2AgY29sdW1uLCBza2lwIHRoaXMgc3RlcDoKCmBgYHtyIGFkZF9TcGVjaWVzfQphc3ZfdGFibGUkU3BlY2llcyA8LSBOQQpgYGAKCkxvYWQgYW4gYGFtcHZpczJgIG9iamVjdCBhbmQgY2FsY3VsYXRlIHJhcmVmYWN0aW9uOgoKYGBge3IgcmFyZWN1cnZlX2FtcHZpczIsIGNhY2hlPVRSVUUsIHdhcm5pbmc9RkFMU0V9CnBzMyA8LSBhbXBfbG9hZChhc3ZfdGFibGUsIG1ldGFkYXRhKQoKcmFyX3Bsb3QgPC0gYW1wX3JhcmVjdXJ2ZShwczMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RlcHNpemUgPSAxMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgZmFjZXRfYnkgPSAiVGltZXBvaW50IiwKICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcl9ieSA9ICJUcmVhdG1lbnQiKSArCiAgIHlsYWIoIk51bWJlciBvZiBvYnNlcnZlZCBBU1ZzIikKCnJhcl9wbG90CmBgYAogICAKQXMgd2UgY2FuIHNlZSwgYXMgcmVhZCBkZXB0aCBpbmNyZWFzZXMgZm9yIGVhY2ggc2FtcGxlLCBzbyBkb2VzIHRheG9ub21pYyByaWNobmVzcy4KSW4gYWxsIHNhbXBsZXMsIGluY3JlYXNlZCBzYW1wbGluZyB3b3VsZCByZXN1bHQgaW4gaW5jcmVhc2VkIHJpY2huZXNzLgpUaHVzLCBvdXIgb2JzZXJ2ZWQgc2FtcGxlIHJpY2huZXNzIGlzIGxvd2VyIHRoYW4gdGhlIHRydWUgcG9wdWxhdGlvbiByaWNobmVzcyBmb3IKZXZlcnkgcGxvdC4KCkZ1cnRoZXIsIHNlcXVlbmNlIGRlcHRoIGFuZCB0b3RhbCByaWNobmVzcyBhcHBlYXIgdG8gYmUgY29ycmVsYXRlZCBhY3Jvc3Mgc2FtcGxlczoKCmBgYHtyIGNvcnIsIHdhcm5pbmc9RkFMU0V9CmRwX2J5X2FzdiA8LSBkYXRhLmZyYW1lKGN0X3N1bXMgPSBzYW1wbGVfc3VtcyhwcyksCiAgICAgICAgICAgICAgICAgICAgICAgIGFzdnMgPSByb3dTdW1zKG90dV90YWJsZShwcykgIT0gMCkpCgpnZ3Bsb3QoZGF0YSA9IGRwX2J5X2FzdikgKwogIGdlb21fcG9pbnQoYWVzKHggPSAgY3Rfc3VtcywgeSA9IGFzdnMpKSArCiAgbGFicyh4ID0gIlNlcXVlbmNlIGRlcHRoIiwgeT0gIkFTViBjb3VudCIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpXZSBjYW4gdGVzdCB3aGV0aGVyIHRoaXMgY29ycmVsYXRpb24gaXMgc2lnbmlmaWNhbnQ6CgpgYGB7cn0KY29yIDwtIGNvci50ZXN0KGRwX2J5X2FzdiRjdF9zdW1zLCBkcF9ieV9hc3YkYXN2cywgbWV0aG9kID0gInNwZWFybWFuIikKY29yCmBgYAoKSGVyZSB3ZSBvYnNlcnZlIGEgc2lnbmlmaWNhbnQgY29ycmVsYXRpb24gYmV0d2VlbiByZWFkIGNvdW50IGFuZCBvYnNlcnZlZAp0YXhvbm9taWMgcmljaG5lc3MuICAgCiAgIApUaHVzLCBhIHNhbXBsZSBmcm9tIGEgcG9wdWxhdGlvbiB3aXRoIGhpZ2ggYWN0dWFsIGRpdmVyc2l0eSwgYnV0IGxvdyByZWFkIGRlcHRoLApjb3VsZCBoYXZlIGxvdyBvYnNlcnZlZCByaWNobmVzcy4KRm9ydHVuYXRlbHksIHRoZXJlIGFyZSBtZXRob2RzIHRvIGVzdGltYXRlIHRoZSBudW1iZXIgb2YgdW5vYnNlcnZlZCB0YXhhLgpGb3IgYW4gb3ZlcnZpZXcgb2YgdGhlc2UgbWV0aG9kcywgcmVhZApbQnVuZ2UgZXQgYWwuIDIwMTRdKGh0dHA6Ly93d3cuYW5udWFscmV2aWV3cy5vcmcvZG9pLzEwLjExNDYvYW5udXJldi1zdGF0aXN0aWNzLTAyMjUxMy0xMTU2NTQpLgogICAKIyMjIEZyZXF1ZW5jeSBjb3VudHMKClRvIGlsbHVzdHJhdGUgdGhlIGRpZmZpY3VsdHkgb2YgZXN0aW1hdGluZyBwb3B1bGF0aW9uIHJpY2huZXNzIGFuZCBpbXBvcnRhbmNlIG9mCnNpbmdsZXRvbnMgdG8gYWxwaGEgZGl2ZXJzaXR5IG1ldHJpY3MsCndlJ2xsIGxvb2sgYXQgdGhlIGZyZXF1ZW5jeSBjb3VudCBkaXN0cmlidXRpb24gZm9yIG91ciBmaXJzdCBzYW1wbGU6IDEwMS1TMS4KCldlJ2xsIGNvdW50IHRoZSBudW1iZXIgb2Ygc2luZ2xldG9ucywgZG91YmxldG9ucywgZXRjLiwKdGhlbiBwbG90IGZyZXF1ZW5jeSBhcyBhIGZ1bmN0aW9uIG9mIGNvdW50LgoKYGBge3IgZnJlcV9jdCwgbWVzc2FnZT1GQUxTRX0KIyBHZXQgdGhlIGZyZXF1ZW5jeSBjb3VudCBkaXN0cmlidXRpb24gZm9yIHRoZSAxc3Qgc2FtcGxlOgpmcmVxdWVuY3l0YWJsZWxpc3QgPC0gYnVpbGRfZnJlcXVlbmN5X2NvdW50X3RhYmxlcyhvdHVfdGFibGUocHMpKQpmcmVxX2N0IDwtIGZyZXF1ZW5jeXRhYmxlbGlzdFtbMV1dCmNvbG5hbWVzKGZyZXFfY3QpIDwtIGMoIkZyZXF1ZW5jeSIsICJDb3VudCIpCgojIFBsb3Q6CmdncGxvdChmcmVxX2N0LCBhZXMoRnJlcXVlbmN5LCBDb3VudCkpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA2MDAsIGJ5ID0gNTApKSArIAogIGdndGl0bGUoIjEwMS1TMSIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCiAgIApXZSBjYW4gc2VlIHRoZXJlIGFyZSB+MywyMDAgdGF4YSByZXByZXNlbnRlZCBhcyBzaW5nbGV0b25zLAphbmQgfjEsNjAwIHRheGEgYXMgZG91YmxldG9ucy4gClRoaXMgaXMgYSBoaWdoIHByb3BvcnRpb24gb2YgdGhlIHRvdGFsIG51bWJlciB0YXhhIGFyZSBvYnNlcnZlZCBpbiB0aGlzIHNhbXBsZToKCmBgYHtyfQojIFRvdGFsIG51bWJlciBvZiB0YXhhOgpzdW0oZnJlcV9jdCRDb3VudCkKCiMgUHJvcG9ydGlvbiBvZiBzaW5nbGV0b25zCmZyZXFfY3QkQ291bnRbMV0gLyBzdW0oZnJlcV9jdCRDb3VudCkKYGBgCgpIb3cgbWFueSBtb3JlIHRheGEgbWlnaHQgd2Ugb2JzZXJ2ZSBpZiB3ZSBpbmNyZWFzZWQgb3VyIHNhbXBsaW5nPyAgIAoKIyMjIERpdmVyc2l0eSBJbmRpY2VzCgpXZSBjYW4gdXNlIHNldmVyYWwgaW5kaWNlcyB0byBleHBsb3JlIGhvdyBldmVubHkgc3BlY2llcyBhcmUgZGlzdHJpYnV0ZWQgd2l0aGluCmVhY2ggc2FtcGxlLiBOb3RlIHRoYXQgJ09ic2VydmVkJyBpcyB0aGUgc2FtcGxlIHRheG9ub21pYyByaWNobmVzcy4KVGhlIG90aGVyIHRocmVlIGluZGljZXMgY29tYmluZSByaWNobmVzcyBhbmQgYWJ1bmRhbmNlIGRhdGEgZm9yIGVhY2ggdGF4b24uCgpgYGB7ciBhbHBoYV9kaXZ9CnBsb3RfcmljaG5lc3MocHMsCiAgICAgICAgICAgICAgeCA9ICJUaW1lcG9pbnQiLAogICAgICAgICAgICAgIHNoYXBlID0gIlRyZWF0bWVudCIsCiAgICAgICAgICAgICAgY29sb3IgPSAiVHJlYXRtZW50IiwgCiAgICAgICAgICAgICAgbWVhc3VyZXMgPSBjKCJPYnNlcnZlZCIsICJTaGFubm9uIiwgIlNpbXBzb24iLCAiSW52U2ltcHNvbiIpKQpgYGAKICAgCk5vdGljZSB0aGF0IHNldmVyYWwgVDI6UzMgc2FtcGxlcyBoYXZlIGxvdyBtZWFzdXJlcywKc3VnZ2VzdGluZyB0aGV5IGhhdmUgbG93IGRpdmVyc2l0eSAoaS5lLiBhYnVuZGFuY2VzIGFyZSBkb21pbmF0ZWQgYnkgYSBmZXcgdGF4YSkKY29tcGFyZWQgdG8gdGhlIG90aGVyIHNhbXBsZXMuCiAKLS0tLS0KCiMjIEJldGEgZGl2ZXJzaXR5CgpCZXRhIGRpdmVyc2l0eSBtZWFzdXJlcyBkaWZmZXJlbmNlcyBpbiBtaWNyb2JpYWwgY29tcG9zaXRpb25zIGJldHdlZW4gcG9wdWxhdGlvbnMuCklmIGF2YWlsYWJsZSAodXNlIHBzXzE2U19WNF93aXRodHJlZS5yZHMpLCB3ZSBjYW4gdXNlIGEgcGh5bG9nZW5ldGljIHRyZWUgYW5kIG90dQp0YWJsZSB0byBlc3RpbWF0ZSB0aGUgW1VuaUZyYWNdKGh0dHBzOi8vZG9pLm9yZy8xMC4xMTI4L0FFTS43MS4xMi44MjI4LTgyMzUuMjAwNSkKZm9yIGVhY2ggc2FtcGxlIHBhaXIuCgpUaGUgcmVzdWx0IGlzIGEgZGlzdGFuY2UgbWF0cml4IHdpdGggaGVpZ2h0IGFuZCB3aWR0aCBlcXVhbCB0byB0aGUgbnVtYmVyIG9mIHNhbXBsZXMuClVuaUZyYWMgbWF5IGJlIFt3ZWlnaHRlZCBvciB1bndlaWdodGVkXShodHRwczovL2RvaS5vcmcvMTAuMTEyOC9BRU0uMDE5OTYtMDYpLgpTZWxlY3Rpb24gb2Ygd2VpZ2h0ZWQgb3IgdW53ZWlnaHRlZCB3aWxsIGRlcGVuZCBvbiB0aGUgcXVlc3Rpb24geW91IHdhbnQgdG8gYW5zd2VyLgpVbndlaWdodGVkIFVuaUZyYWMgdXNlcyBvbmx5IHByZXNlbmNlIG9yIGFic2VuY2Ugb2YgYSB0YXhvbiBmcm9tIHRoZSBvdHUgdGFibGUKKGkuZS4gMTAwMCB3aWxsIGJlIHRyZWF0ZWQgdGhlIHNhbWUgYXMgMSkuCkl0IGlzIGFwcHJvcHJpYXRlIGlmIHlvdSB3YW50IHRvIHRlc3QgcXVhbGl0YXRpdmUgdGF4YSBkaWZmZXJlbmNlcyBiZXR3ZWVuIHNhbXBsZXMuCldlaWdodGVkIFVuaUZyYWMgdXNlcyBwcmVzZW5jZSBhcyB3ZWxsIGFzIHF1YW50aXR5IGRhdGEgKGkuZS4gMSA8IDEwMDApLgoKV2UgYXJlIGludGVyZXN0ZWQgdG8ga25vdyB3aGF0IGNoYW5nZXMgdG8gdGF4b24gYWJ1bmRhbmNlIG1heSBiZSBkcml2ZW4gYnkgb3VyIHRyZWF0bWVudHMsIHNvIHdlIHdpbGwgdXNlIHdlaWdodGVkIFVuaUZyYWMuICAgIAoKIyMjIE9yZGluYXRpb24KCkZpcnN0IHdlJ2xsIG1ha2UgYSB3ZWlnaHRlZCBVbmlGcmFjIGRpc3RhbmNlIG1hdHJpeC4KRm9yIFVuaUZyYWMsIHlvdSByZXF1aXJlIGEgdHJlZSBhcyBwYXJ0IG9mIHlvdXIgKnBoeWxvc2VxKiBvYmplY3QuCgpgYGB7ciB3VUYsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPUZBTFNFfQpzZXQuc2VlZCgxMDApCndVRiA8LSBwaHlsb3NlcTo6ZGlzdGFuY2UocHNfcHJvcCwgbWV0aG9kID0gInd1bmlmcmFjIikKCmFzLm1hdHJpeCh3VUYpWzE6NiwgMTo2XQoKIyBOb3RlOiBJZiB5b3VyICpwaHlsb3NlcSogb2JqZWN0IGxhY2tzIGEgcGh5bG9nZW5ldGljIHRyZWUsCiMgeW91IGNvdWxkIHVzZSBhIGRpc3NpbWlsYXJpdHkgaW5kZXggdGhhdCBkb2VzIG5vdCByZXF1aXJlIG9uZSwgc3VjaCBhcyBCcmF5LUN1cnRpczoKIyBicmF5IDwtIHZlZ2FuOjp2ZWdkaXN0KG90dV90YWJsZShwc19wcm9wKSwgbWV0aG9kID0gImJyYXkiKQojIGFzLm1hdHJpeChicmF5KVsxOjYsMTo2XQpgYGAKClRoZW4gcGVyZm9ybSBQcmluY2lwYWwgQ29vcmRpbmF0ZSBBbmFseXNpcyAoUENvQSkgb24geW91ciBmYXZvcml0ZSBkaXN0YW5jZSBtYXRyaXguCkluIHRoaXMgY2FzZSwgd2UgYXJlIHVzaW5nIHdlaWdodGVkIFVuaUZyYWMuCgpgYGB7ciBvcmRpbmF0ZSwgd2FybmluZz1GQUxTRSwgY2FjaGU9RkFMU0V9CmRvIDwtIG9yZGluYXRlKHBzX3Byb3AsIG1ldGhvZCA9ICJQQ29BIiwgZGlzdGFuY2UgPSB3VUYpCgpiYXNlcGxvdCA8LSBwbG90X29yZGluYXRpb24ocHNfcHJvcCwgZG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gInNhbXBsZXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAiVHJlYXRtZW50IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlID0gIlRpbWVwb2ludCIpICsKICBnZ3RpdGxlKCJIQ0Mgb3JkaW5hdGlvbiwgYWxsIHNhbXBsZXNcbm1ldGhvZD1QQ29BLCBkaXN0YW5jZT13ZWlnaHRlZCBVbmlGcmFjIikgKwogIHRoZW1lX2J3KCkKCiMgU2luZ2xlIHBsb3Qgd2l0aCBhbGwgc2FtcGxlczoKYmFzZXBsb3QgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIpCiAgCiMgSWRlbSwgYnV0IHdpdGggc2FtcGxlIG5hbWVzIG9uIHRoZSBwbG90OiAKYmFzZXBsb3QgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDEpICsgCiAgZ2VvbV9sYWJlbChhZXMobGFiZWwgPSBTYW1wbGVJRCksIHNpemUgPSAzLjUsIGNvbG9yID0gImJsYWNrIikKCiMgRmFjZXQgYnkgdGltZSBwb2ludDoKYmFzZXBsb3QgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsgCiAgZ2d0aXRsZSgiSENDIG9yZGluYXRpb24sIGFsbCBzYW1wbGVzXG5tZXRob2Q9UENvQSwgZGlzdGFuY2U9d2VpZ2h0ZWQgVW5pRnJhYyIpICsKICBmYWNldF93cmFwKH5UaW1lcG9pbnQsIDIpCmBgYAogICAKV2UgY2FuIHNlZSBzb21lIGdyb3VwaW5nIGJ5IHRpbWVwb2ludC4KRGVwZW5kaW5nIG9uIHlvdXIgZXhwZXJpbWVudCBhbmQgcXVlc3Rpb25zLAp5b3UgbWlnaHQgd2FudCB0byBjb25zaWRlciBvdGhlciBvcmRpbmF0aW9uIG9yIGNsdXN0ZXJpbmcgYXBwcm9hY2hlcy4KCiMjIyBQZXJtdXRhdGlvbmFsIGFuYWx5c2lzIG9mIHZhcmlhbmNlIChQRVJNQU5PVkEpCgpGaW5hbGx5LCB3ZSdsbCB0ZXN0IHdoZXRoZXIgdGhlcmUgaXMgYSBzaWduaWZpY2FudCBlY29sb2dpY2FsIGxldmVsIHRyZWF0bWVudCBlZmZlY3QuCipQRVJNQU5PVkEqIHNvdW5kcyBmYW5jeSwgYnV0IGl0IGlzIGp1c3QgYW4gKkFOT1ZBKiBwZXJmb3JtZWQgdXNpbmcgcGVybXV0YXRpb25zLgpQZXJtdXRhdGlvbnMgYXJlIHVzZWQgdG8gZGV0ZXJtaW5lIGhvdyBkYXRhIG1heSBhcHBlYXIgaWYgdGhlcmUgaXMgbm8gdHJlYXRtZW50CmVmZmVjdCBhbmQgZ3JvdXAgZGlmZmVyZW5jZXMgYXJlIGR1ZSB0byByYW5kb20gY2hhbmNlLiBPYnNlcnZlZCBkYXRhIGFyZSB0aGVuCmNvbXBhcmVkIHRvIHRoZSByYW5kb21pemVkIGRhdGEgdG8gY2FsY3VsYXRlIGEgcC12YWx1ZS4gICAKICAgClRyZWF0bWVudHMgYXJlIGluZGVwZW5kZW50IHdpdGhpbiBUaW1lcG9pbnRzLApzbyB3ZSB3aWxsIGZpcnN0IHN1YnNldCB0aGUgZGF0YSBieSBUaW1lcG9pbnQ6CgpgYGB7ciBzdWJzZXRfVGltZXBvaW50cywgd2FybmluZz1GQUxTRX0KcHNfUzEgPC0gc3Vic2V0X3NhbXBsZXMocHNfcHJvcCwgVGltZXBvaW50ID09ICJTMSIpCnBzX1MzIDwtIHN1YnNldF9zYW1wbGVzKHBzX3Byb3AsIFRpbWVwb2ludCA9PSAiUzMiKQpwc19TNCA8LSBzdWJzZXRfc2FtcGxlcyhwc19wcm9wLCBUaW1lcG9pbnQgPT0gIlM0IikKYGBgCgpTdWJzZXQgdGhlIG1ldGFkYXRhIGFuZCBlc3RpbWF0ZSBVbmlGcmFjIGZvciBTMzoKCmBgYHtyIHNldF9wZXJtLCB3YXJuaW5nPUZBTFNFfQptZXRhZGF0YSA8LSBhcyhzYW1wbGVfZGF0YShwc19TMyksICJkYXRhLmZyYW1lIikKdW5pZnJhY19kaXN0IDwtIFVuaUZyYWMocHNfUzMsIHdlaWdodGVkID0gVFJVRSkKYGBgCgpUaGVuIHdlJ2xsIHVzZSBgdmVnYW46OmFkb25pczJgIHRvIHBlcmZvcm0gdGhlICpQRVJNQU5PVkEqLgoKYGBge3IgUEVSTUFOT1ZBfQpzZXQuc2VlZCgxMDApICAjIFNldCB0aGUgc2VlZCB0byBtYWtlIHRoZSBhbmFseXNpcyByZXByb2R1Y2libGUKCnBlcm1hbm92YSA8LSBhZG9uaXMyKHVuaWZyYWNfZGlzdCB+IFRyZWF0bWVudCwKICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IG1ldGFkYXRhLAogICAgICAgICAgICAgICAgICAgICBwZXJtdXRhdGlvbnMgPSAxMDAwMCkKCnBlcm1hbm92YQpgYGAKClRoZSBsb3cgcC12YWx1ZSBzdWdnZXN0cyB0aGF0IHdpdGhpbiB0aGUgUzMgdGltZXBvaW50IChwb3N0LXRlcm1pbmF0aW9uKSwKdGhlcmUgaXMgYSBzaWduaWZpY2FudCB0cmVhdG1lbnQgZWZmZWN0LiAgIAogCi0tLS0tCgojIyBEaWZmZXJlbnRpYWwgdGF4b24gYWJ1bmRhbmNlIChERVNlcSkKCkhlcmUsIHdlIHdpbGwgdXNlCltERVNlcTJdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL3dvcmtmbG93cy92aWduZXR0ZXMvcm5hc2VxR2VuZS9pbnN0L2RvYy9ybmFzZXFHZW5lLmh0bWwpCnRvIGlkZW50aWZ5IGRpZmZlcmVudGlhbGx5IGFidW5kYW50IHRheGEgYmV0d2VlbiB0aW1lIHBvaW50cyBhbmQgdHJlYXRtZW50cy4KKkRFU2VxMiogd2FzIGRlc2lnbmVkIHRvIGFuYWx5emUgUk5Bc2VxIGRhdGFzZXRzLCB3aGljaCBhcmUgc2ltaWxhciB0byBPVFUvQVNWIGRhdGEgCnNldHMgaW4gdGhhdCBib3RoIGhhbmRsZSBsYXJnZSwgc3BhcnNlIGNvbnRpbmdlbmN5IHRhYmxlcyBnZW5lcmF0ZWQgZnJvbSBJbGx1bWluYQpzZXF1ZW5jaW5nIGRhdGEuCkZvciBtb3JlIGRldGFpbHMsIHNlZQpbTWNNdXJkaWUgYW5kIEhvbG1lcyAyMDE0XShodHRwczovL2R4LnBsb3Mub3JnLzEwLjEzNzEvam91cm5hbC5wY2JpLjEwMDM1MzEpLiAgCiAgCkZpcnN0LCB3ZSdsbCBjb252ZXJ0IG91ciBub24tbm9ybWFsaXplZCBjb3VudCBkYXRhIHRvIGEgKkRFU2VxKiBvYmplY3QuCgpgYGB7ciBwczJkc3MsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmRkcyA8LSBwaHlsb3NlcV90b19kZXNlcTIocHMsIH5UaW1lcG9pbnQgKyBUcmVhdG1lbnQpCgpkZHMKY29sRGF0YShkZHMpCmBgYAoKVGhlcmUgYXJlIHR3byB3YXlzIHRvIGFuYWx5emUgaW50ZXJhY3Rpb24gZWZmZWN0cyB1c2luZyAqREVTZXEyKi4KVGhlIGZpcnN0IGlzIHRvIGZpdCBhIG11bHRpdmFyaWF0ZSBtb2RlbCAoZS5nLiB+QStCK0E6QikgYW5kIGV4cGxvcmUgdGhlIG1vZGVsCmNvZWZmaWNpZW50cy4KVGhlIHNlY29uZCBpcyB0byBmaXQgYSB1bml2YXJpYXRlIG1vZGVsIGFuZCBleHBsb3JlIHBhaXJ3aXNlIGNvbnRyYXN0cy4KSGVyZSwgd2Ugd2lsbCBncm91cCBvdXIgdHdvIGZhY3RvcnMgKFRyZWF0bWVudCBhbmQgVGltZXBvaW50KSBhbmQgdXNlIHRoZSBsYXR0ZXIKYXBwcm9hY2guCgpgYGB7ciBkZWcsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQojIFByZXBhcmUgdGhlIGRhdGE6CmRkcyRHcm91cCA8LSBmYWN0b3IocGFzdGUwKGRkcyRUcmVhdG1lbnQsIGRkcyRUaW1lcG9pbnQpKQpkZXNpZ24oZGRzKSA8LSBmb3JtdWxhKH5Hcm91cCkKZGRzIDwtIGRkc1ssIC01MF0gICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHRlY2hfcmVwIHNhbXBsZQpkZHMkR3JvdXAgPC0gZHJvcGxldmVscyhkZHMkR3JvdXApICAgIyBEcm9wIHRlY2hfcmVwIGxldmVsIGZyb20gZGVzaWduIG1hdHJpeAoKIyBQZXJmb3JtIHRoZSBkaWZmZXJlbnRpYWwgYWJ1bmRhbmNlIGFuYWx5c2lzOgpkZHMgPC0gREVTZXEoZGRzKQoKcmVzdWx0cyhkZHMpCmBgYAogICAgCkxldCdzIGV4cGxvcmUgdGhlIGNvbnRyYXN0LCAiVDFTNCA6IFQ0UzQiCihubyBjYywgc2VlZGxpbmcgc3RhZ2UgOiBsYXRlIHRlcm1pbmF0aW9uLCBzZWVkbGluZyBzdGFnZSk6CgpgYGB7cn0KcmVzIDwtIHJlc3VsdHMoZGRzLCBjb250cmFzdCA9IGMoIkdyb3VwIiwgIlQxUzQiLCAiVDRTNCIpKQpzdW1tYXJ5KHJlcykKCiMgTnVtYmVyIG9mIHRheGEgd2l0aCBzaWduaWZpY2FudGx5IGhpZ2hlciBhYnVuZGFuY2UgaW4gVDRTNDoKc3VtKHJlcyRwYWRqIDwgMC4xICYgcmVzJGxvZzJGb2xkQ2hhbmdlID4gMCwgbmEucm0gPSBUUlVFKQoKIyBOdW1iZXIgb2YgdGF4YSB3aXRoIHNpZ25pZmljYW50bHkgbG93ZXIgYWJ1bmRhbmNlIGluIFQ0UzQ6CnN1bShyZXMkcGFkaiA8IDAuMSAmIHJlcyRsb2cyRm9sZENoYW5nZSA8IDAsIG5hLnJtID0gVFJVRSkKYGBgCgpXZSBjYW4gc29ydCByZXN1bHRzIGJ5IHAtdmFsdWUgYW5kIGxvb2sgYXQgdGhlIHRvcCBBU1ZzOgoKYGBge3J9CnJlcy5vcmRlciA8LSByZXNbb3JkZXIocmVzJHB2YWx1ZSksIF0KaGVhZChyZXMub3JkZXIpW2MoMToyLCA2KV0KYGBgCgpBbmQgcGxvdCBjb3VudHMgZm9yIHRoZSBmaXJzdCBpbiB0aGUgb3JkZXJlZCBsaXN0IEFTVjoKCmBgYHtyIHBsb3RfY291bnRzfQpwbG90Q291bnRzKGRkcywgZ2VuZSA9IHJlcy5vcmRlckByb3duYW1lc1sxXSwgaW50Z3JvdXAgPSAiR3JvdXAiKQpgYGAKICAgCldlIGNhbiBhbHNvIHBsb3QgY291bnRzIGJ5IHNhbXBsZV9JRDoKCmBgYHtyIHBsb3RfY291bnRzMn0KcGxvdENvdW50cyhkZHMsIGdlbmUgPSAiQVNWXzQ4IiwgaW50Z3JvdXAgPSAiR3JvdXAiKQpgYGAKClRoZSBwbG90IGNhbiBhbHNvIGJlIHNhdmVkIGFuZCBjdXN0b21pemVkIHdpdGggZ2dwbG90OgoKYGBge3J9CmQgPC0gcGxvdENvdW50cyhkZHMsIGdlbmUgPSAiQVNWXzQ4IiwgaW50Z3JvdXAgPSAiR3JvdXAiLCByZXR1cm5EYXRhID0gVFJVRSkKCmdncGxvdChkLCBhZXMoeCA9IEdyb3VwLCB5ID0gY291bnQpKSArCiAgZ2VvbV9wb2ludChwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3ID0gMC4xLCBoID0gMCksIHNpemUgPSAyKSArCiAgc2NhbGVfeV9sb2cxMCgpICsKICBnZ3RpdGxlKGV4cHJlc3Npb24ocGFzdGUoIkFTViA0OCwgbG9nIlsxMF0sICItc2NhbGUiKSkpICsKICB0aGVtZV9idygpCmBgYAogICAKV2UgY2FuIGFsc28gc3Vic2V0IG91ciBkaWZmZXJlbnRpYWxseSBhYnVuZGFudCB0YXhhIHRvIGtlZXAgdGhvc2Ugd2l0aCBhbgphZGp1c3RlZCBwLXZhbHVlIDwgMC4wNToKCmBgYHtyIGZpbHRlcl8wNX0Kc2lnIDwtIHN1YnNldChyZXMub3JkZXIsIHBhZGogPD0gMC4wNSkKaGVhZChzaWcpCmBgYAoKV2hhdCB0YXhhIGFyZSBhc3NvY2lhdGVkIHdpdGggc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBhYnVuZGFudCBBU1ZzPwoKYGBge3Igc2lnX3RheGF9CnNpZ19hc3YgPC0gcm93bmFtZXMoc2lnKQpzaWdfdGF4YSA8LSB0YXhfdGFibGUocHMpW3NpZ19hc3YsXQpoZWFkKHNpZ190YXhhKQoKc2lnX3dyaXRlIDwtIGNiaW5kKHNpZywgc2lnX3RheGEpCndyaXRlLmNzdihzaWdfd3JpdGUsIGZpbGUucGF0aChvdXRkaXIsICdEQVRheGFfVDFTMV9UMVM0LmNzdicpKQpgYGAKClJlcG9ydCB0aGF0IHdlIGFyZSBkb25lIQoKYGBge3IgcmVwb3J0X2RvbmV9CnByaW50KCdEb25lIHdpdGggQVNWIGluZmVyZW5jZS4nKQpgYGAKCi0tLS0tCgojIyBSZXNvdXJjZXMKCi0gW2BkZWNvbnRhbWAgdHV0b3JpYWxdKGh0dHBzOi8vYmVuampuZWIuZ2l0aHViLmlvL2RlY29udGFtL3ZpZ25ldHRlcy9kZWNvbnRhbV9pbnRyby5odG1sKQoKLSBbYHZlZ2FuYCB0dXRvcmlhbF0oaHR0cHM6Ly9wZWF0LWNsYXJrLmdpdGh1Yi5pby9CSU8zODEvdmVnYW5UdXRvcmlhbC5odG1sKQoKLSBbYERFU2VxMmAgdHV0b3JpYWxdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL2RldmVsL2Jpb2MvdmlnbmV0dGVzL0RFU2VxMi9pbnN0L2RvYy9ERVNlcTIuaHRtbCkKCi0gW0ludHJvIGNvdXJzZSBvbiBtaWNyb2Jpb21lIGFuYWx5c2lzXShodHRwczovL21pYnd1cnJlcG8uZ2l0aHViLmlvL01pY3JvYmlhbC1iaW9pbmZvcm1hdGljcy1pbnRyb2R1Y3RvcnktY291cnNlLU1hdGVyaWFsLTIwMTgvKQoKLSBbVW5pRnJhYyBwdWJsaWNhdGlvbl0oaHR0cHM6Ly9kb2kub3JnLzEwLjExMjgvQUVNLjcxLjEyLjgyMjgtODIzNS4yMDA1KQotIFtBZGRpdGlvbmFsIFVuaUZyYWMgcHVibGljYXRpb25dKGh0dHBzOi8vZG9pLm9yZy8xMC4xMTI4L0FFTS4wMTk5Ni0wNikKCi0gKipBcnRpY2xlcyBvbiBkYXRhIG5vcm1hbGl6YXRpb246KiogICAKICAgLSBbV2FzdGUgbm90LCB3YW50IG5vdDogd2h5IHJhcmlmeWluZyBtaWNyb2Jpb21lIGRhdGEgaXMgaW5hZG1pc3NpYmxlXShodHRwczovL2R4LnBsb3Mub3JnLzEwLjEzNzEvam91cm5hbC5wY2JpLjEwMDM1MzEpCiAgIC0gW01pY3JvYmlvbWUgZGF0YXNldHMgYXJlIGNvbXBvc2l0aW9uYWw6IGFuZCB0aGlzIGlzIG5vdCBvcHRpb25hbF0oaHR0cDovL2pvdXJuYWwuZnJvbnRpZXJzaW4ub3JnL2FydGljbGUvMTAuMzM4OS9mbWljYi4yMDE3LjAyMjI0L2Z1bGwpCiAgIC0gW01ldGhvZHMgZm9yIG5vcm1hbGl6aW5nIG1pY3JvYmlvbWUgZGF0YTogYW4gZWNvbG9naWNhbCBwZXJzcGVjdGl2ZV0oaHR0cDovL2RvaS53aWxleS5jb20vMTAuMTExMS8yMDQxLTIxMFguMTMxMTUpCiAgIC0gW0RpZmZlcmVudGlhbCBhYnVuZGFuY2UgYW5hbHlzaXMgZm9yIG1pY3JvYmlhbCBtYXJrZXItZ2VuZSBzdXJ2ZXlzXShodHRwOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbm1ldGguMjY1OCkK