Preliminary Setup

First we load the necessary libraries.

library(here)
library(stringr)
library(magrittr)
library(openxlsx)
library(SummarizedExperiment)
library(dplyr)
library(edgeR)
library(limma)
library(sva)
library(ggplot2)
library(scales)
library(GGally)
library(ggalt)
library(reshape2)
library(assertthat)
library(ggfortify)
library(broom)

library(doParallel)
ncores <- getOption("mc.cores", default=parallel::detectCores(logical = FALSE))
registerDoParallel(cores=ncores)
library(BiocParallel)
register(DoparParam())

source(here("scripts/utilities.R"))

Data Loading and Preprocessing

Now we’ll load the RNA-seq data set from an RDS file containing a SummarizedExperiment object, and modify it to use the sample names as column names.

datasets <- c("hisat2_grch38_snp_tran_ensembl.85",
              "hisat2_grch38_snp_tran_knownGene",
              "kallisto_hg38.analysisSet_ensembl.85",
              "kallisto_hg38.analysisSet_knownGene",
              "salmon_hg38.analysisSet_ensembl.85",
              "salmon_hg38.analysisSet_knownGene",
              "shoal_hg38.analysisSet_ensembl.85",
              "shoal_hg38.analysisSet_knownGene",
              "star_hg38.analysisSet_ensembl.85",
              "star_hg38.analysisSet_knownGene")
datasetmeta <- datasets %>% str_match("^([^_]+)_(.+)_([^_]+)$") %>%
    as_data_frame %>% setNames(c("dataset", "program", "genome", "transcriptome"))
assert_that(all(table(datasetmeta$program) == 2))
[1] TRUE
assert_that(all(table(datasetmeta$transcriptome) == 5))
[1] TRUE
sexp.files <- setNames(here("saved_data",
                            sprintf("SummarizedExperiment_rnaseq_%s.RDS", datasets)),
                       datasets)
sexps <- bplapply(sexp.files, readRDS)
for (dset in names(sexps)) {
    sexps[[dset]] %<>% set_colnames(.$SampleName)
}

We extract the sample metadata from the SummarizedExperiment (we only do this once since it’s the same for all datasets).

sample.table <- colData(sexps[[1]]) %>%
    as.data.frame %>% autoFactorize %>%
    rename(batch=technical_batch) %>%
    mutate(time_point=factor(days_after_activation) %>% `levels<-`(sprintf("D%s", levels(.))),
           group=interaction(cell_type, time_point, sep=""))

Next we extract the count matrix from each SummarizedExperiment. This is made more complicated than usual by the fact that half of the samples were sequenced with a different protocol than the other half, and the two protocols produce reads with opposite strand orientations. Hence, we need the sense counts for half of the samples and the antisense counts for the other half. The appropriate strand for each sample is documented in the libType column of the sample metadata, using the library type abbreviations established by Salmon.

For SummarizedExperiments generated using tximport, this step is skipped, since the quantification tool has already been told which strand to use and only provides counts for that strand.

libtype.assayNames <- c(SF="sense.counts", SR="antisense.counts")
for (dset in seq_along(sexps)) {
    if (all(libtype.assayNames %in% assayNames(sexps[[dset]]))) {
        sample.table %<>% mutate(count_type=libtype.assayNames[libType])
        assay(sexps[[dset]], "unstranded.counts") <- assay(sexps[[dset]], "counts")
        assay(sexps[[dset]], "counts") <- lapply(seq_len(nrow(sample.table)), function(i) {
            assay(sexps[[dset]], sample.table[i,]$count_type %>% as.character)[,i]
        }) %>% do.call(what=cbind)
    }
}

As a sanity check, we make sure that we selected the strand sense with the higher total count for each sample.

for (dset in seq_along(sexps)) {
    if (all(libtype.assayNames %in% assayNames(sexps[[dset]]))) {
        total.counts <- sexps[[dset]] %>% assays %>% sapply(colSums) %>% data.frame %>%
            mutate(SampleName=row.names(.)) %>%
            inner_join(sample.table, by="SampleName")
        total.counts %$% invisible(assert_that(all(counts == pmax(sense.counts, antisense.counts))))
    }
}

Now we create a DGEList from each count matrix, and perform the initial scaling normalization. In addition, if there is a length assay, we also use that to derive an offset matrix that corrects for sample-specific biases detected by Salmon or shoal.

dges <- bplapply(sexps, function(sexp) {
    ## Extract gene metadata and colapse lists
    all.gene.meta <- mcols(sexp) %>% as.data.frame
    # Convert list columns to character vectors
    all.gene.meta[] %<>% lapply(function(x) if (is.list(x)) sapply(x, str_c, collapse=",") else x)
    dge <- DGEList(counts=assay(sexp, "counts"))
    dge$genes <- all.gene.meta
    rownames(dge$genes) <- rownames(dge)
    dge %<>% calcNormFactors
    if ("length" %in% assayNames(sexp)) {
        normMat <- assay(sexp, "length") %>% divide_by(exp(rowMeans(log(.))))
        normCounts <- dge$counts/normMat
        lib.offsets <- log(calcNormFactors(normCounts)) + log(colSums(normCounts))
        dge$offset <- t(t(log(normMat)) + lib.offsets)
    }
    dge
})

Finally, we compute the normalized logCPM matrix for each dataset, and convert it into a tidy format. We also compute the normalized logCPM without taking into account the offsets derived from gene length normalization.

logcpm.matrices <- bplapply(dges, cpmWithOffset, log=TRUE)
logcpm.matrices.nooffset <- bplapply(dges, cpm, log=TRUE)
logcpm <- bplapply(dges, function(dge) {
    cpm.mat <- cpmWithOffset(dge, log=TRUE)
    cpm.df <- melt(cpm.mat, varnames=c("GeneID", "Sample"), value.name = "logCPM") %>%
        mutate(GeneID=as.character(GeneID), Sample=as.character(Sample))
    genes <- dge$genes %>% mutate(GeneID=rownames(.))
    inner_join(genes[c("GeneID", "ENTREZID", "ENSEMBL")], cpm.df, by="GeneID") %>%
        select(-GeneID)
})
logcpm.nooffset <- bplapply(dges, function(dge) {
    cpm.mat <- cpm(dge, log=TRUE)
    cpm.df <- melt(cpm.mat, varnames=c("GeneID", "Sample"), value.name = "logCPM") %>%
        mutate(GeneID=as.character(GeneID), Sample=as.character(Sample))
    genes <- dge$genes %>% mutate(GeneID=rownames(.))
    inner_join(genes[c("GeneID", "ENTREZID", "ENSEMBL")], cpm.df, by="GeneID") %>%
        select(-GeneID)
})

Comparative Analysis

Now we can begin comparing the different methods of quantification. First, we will compare Ensembl vs UCSC annotations for each quantification method.

Ensembl vs UCSC

First, we format the logCPM tables into tidy format, and merge the tables of both datasets (Ensembl and UCSC knownGene) for each alignment/quantification program. The merging is performed only for genes with a one-to-one relationship between Ensembl and Entrez gene IDs.

logcpm.tables <- datasetmeta %>%
    arrange(transcriptome) %>%
    group_by(program) %>% do({
        df <- .
        assert_that(nrow(df) == 2)
        dsetA <- df$dataset[1]
        dsetB <- df$dataset[2]
        txA <- df$transcriptome[1]
        txB <- df$transcriptome[2]
        logcpm_A <- logcpm[[dsetA]] %>% 
            rename(logCPM_A=logCPM) %>%
            filter(lengths(ENTREZID) == 1, lengths(ENSEMBL) == 1) %>%
            mutate(ENTREZID=unlist(ENTREZID),
                   ENSEMBL=unlist(ENSEMBL))
        logcpm_B <- logcpm[[dsetB]] %>%
            rename(logCPM_B=logCPM) %>%
            filter(lengths(ENTREZID) == 1, lengths(ENSEMBL) == 1) %>%
            mutate(ENTREZID=unlist(ENTREZID),
                   ENSEMBL=unlist(ENSEMBL))
        logcpm_merged <- inner_join(logcpm_A, logcpm_B)
        data_frame(program=df$program[1], 
                   genome=df$genome[1],
                   datasetA=dsetA, datasetB=dsetB,
                   transcriptomeA=txA, transcriptomeB=txB,
                   logcpm=list(logcpm_merged))
    })
# Print the correlations
logcpm.tables %>% group_by_(names(.)[1:5]) %>% do({
    data.frame(Correlation=.$logcpm[[1]] %$% cor(logCPM_A, logCPM_B))
})

Now we plot the logCPM for Ensembl vs UCSC, for each quantification method (program + transcriptome).

# Use the same x and y limits for all plots
limits <- logcpm.tables$logcpm %>% lapply(. %$% c(range(logCPM_A), range(logCPM_B))) %>% unlist %>% range
logcpm.plots <- logcpm.tables %>% group_by(program) %>% do({
    df <- .
    assert_that(nrow(df) == 1)
    xlabel <- str_c("Gene logCPM in ", df$datasetA)
    ylabel <- str_c("Gene logCPM in ", df$datasetB)
    ptitle <- sprintf("logCPM, %s vs %s for %s on %s genome", df$transcriptomeA, df$transcriptomeB, df$program, df$genome)
    tab <- df$logcpm[[1]] %>% filter(logCPM_A != min(logCPM_A) | logCPM_B != min(logCPM_B))
    p <- ggplot(tab) + 
        aes(x=logCPM_A, y=logCPM_B) +
        geom_point(size=0.2, alpha=0.2) +
        geom_abline(slope=1, intercept=0, color="red", linetype="dashed") +
        coord_fixed(xlim=limits, ylim=limits) +
        labs(title=ptitle, 
             subtitle="Genes matched by unique Entrez & Ensembl IDs; identity line (red) plotted red for reference",
             x=xlabel, y=ylabel)
    cbind(.[c("program", "genome", "datasetA", "datasetB", "transcriptomeA", "transcriptomeB")],
          data_frame(plot=list(p)))
})
ggprint(logcpm.plots$plot)

Overall concordance is very high for all methods. The alignment-based methods (STAR and HISAT2) have slightly higher correlation, which is matched by the apparently tighter spread of points about the identity line in the plots. This is expected since these methods discard ambiguous reads, and are therefore biased toward unambiguously-defined genes, which are the same genes more likely to be annotated consistently between Entrez and Ensembl.

Interestingly, the correlation for Shoal is lower than for Salmon, despite the fact that the Shoal plot looks like it has a tighter spread and the fact that Shoal is meant to regularize Salmon’s quantification results, which would be expected to increase the correlation. Perhaps Shoal decreases the overall correlation by spreading many points away from the bottom left corner, where previously all the genes with zero counts were exactly overlaid on a single point. (This kind of effect is why pearson correlations on gene abundances is rarely informative.)

STAR vs HISAT2

The two count-based methods differ only in the aligner that was used, so any discrepancy between them represents a difference in which reads they could align unambiguously.

logcpm_A <- logcpm$hisat2_grch38_snp_tran_ensembl.85 %>% 
    select(ENSEMBL, Sample, logCPM) %>%
    rename(logCPM_A=logCPM)
logcpm_B <- logcpm$star_hg38.analysisSet_ensembl.85 %>% 
    select(ENSEMBL, Sample, logCPM) %>%
    rename(logCPM_B=logCPM)
logcpm.merged <- inner_join(logcpm_A, logcpm_B) %>%
    filter(logCPM_A != min(logCPM_A) | logCPM_B != min(logCPM_B))
limits <- logcpm.merged %>% select(logCPM_A, logCPM_B) %>% lapply(range) %>% unlist %>% range
p <- ggplot(logcpm.merged) +
    aes(x=logCPM_A, y=logCPM_B) +
    geom_point(size=0.2, alpha=0.2) +
    geom_abline(slope=1, intercept=0, color="red", linetype="dashed") +
    coord_fixed(xlim=limits, ylim=limits) +
    labs(title="logCPM, STAR vs HISAT2", 
         subtitle="Identity line (red) plotted red for reference",
         x="logCPM by HISAT2", y="logCPM by STAR")
ggprint(p)

Effect of gene length normalization

We now compare Salmon, Shoal, and Kallisto with and without normalizing for gene length. This will show how much or how little the gene length normalization affects the abundance estimation.

logcpm.plots <- datasetmeta %>%
    filter(transcriptome == "ensembl.85", program %in% c("salmon", "shoal", "kallisto")) %>% 
    group_by(dataset, program) %>% do({
        assert_that(nrow(.) == 1)
        dset <- .$dataset
        program <- .$program
        logcpm_A <- logcpm.nooffset[[dset]] %>% 
            select(ENSEMBL, Sample, logCPM) %>%
            rename(logCPM_A=logCPM)
        logcpm_B <- logcpm[[dset]] %>% 
            select(ENSEMBL, Sample, logCPM) %>%
            rename(logCPM_B=logCPM)
        logcpm.merged <- inner_join(logcpm_A, logcpm_B) %>%
            filter(logCPM_A != min(logCPM_A) | logCPM_B != min(logCPM_B))
        limits <- logcpm.merged %>% select(logCPM_A, logCPM_B) %>% lapply(range) %>% unlist %>% range
        p <- ggplot(logcpm.merged) +
            aes(x=logCPM_A, y=logCPM_B) +
            geom_point(size=0.2, alpha=0.2) +
            geom_abline(slope=1, intercept=0, color="red", linetype="dashed") +
            coord_fixed(xlim=limits, ylim=limits) +
            labs(title=sprintf("%s logCPM, length-normalized vs non-length-normalized", program), 
                 subtitle="Identity line (red) plotted red for reference",
                 x="logCPM without gene length normalization", y="logCPM with gene length normalization")
        data_frame(plot=list(p))
    })
ggprint(logcpm.plots$plot)

STAR vs Salmon

We now compare STAR, a count-based method, to Salmon, an alignment-free method. We use the Salmon results with no gene length normalization for an apples-to-apples comparison.

logcpm_A <- logcpm$star_hg38.analysisSet_ensembl.85 %>% 
    select(ENSEMBL, Sample, logCPM) %>%
    rename(logCPM_A=logCPM)
logcpm_B <- logcpm.nooffset$salmon_hg38.analysisSet_ensembl.85 %>% 
    select(ENSEMBL, Sample, logCPM) %>%
    rename(logCPM_B=logCPM)
logcpm.merged <- inner_join(logcpm_A, logcpm_B) %>%
    filter(logCPM_A != min(logCPM_A) | logCPM_B != min(logCPM_B))
limits <- logcpm.merged %>% select(logCPM_A, logCPM_B) %>% lapply(range) %>% unlist %>% range
p <- ggplot(logcpm.merged) +
        aes(x=logCPM_A, y=logCPM_B) +
        geom_point(size=0.2, alpha=0.2) +
        geom_abline(slope=1, intercept=0, color="red", linetype="dashed") +
        coord_fixed(xlim=limits, ylim=limits) +
        labs(title="logCPM, Salmon vs STAR", 
             subtitle="Identity line (red) plotted red for reference",
             x="logCPM by STAR", y="logCPM by Salmon")
ggprint(p)

Salmon vs Kallisto

The next comparison is between Salmon and Kallisto, two conceptually very similar pseudoalignment-based methods.

logcpm_A <- logcpm$kallisto_hg38.analysisSet_ensembl.85 %>% 
    select(ENSEMBL, Sample, logCPM) %>%
    rename(logCPM_A=logCPM)
logcpm_B <- logcpm$salmon_hg38.analysisSet_ensembl.85 %>% 
    select(ENSEMBL, Sample, logCPM) %>%
    rename(logCPM_B=logCPM)
logcpm.merged <- inner_join(logcpm_A, logcpm_B) %>%
    filter(logCPM_A != min(logCPM_A) | logCPM_B != min(logCPM_B))
limits <- logcpm.merged %>% select(logCPM_A, logCPM_B) %>% lapply(range) %>% unlist %>% range
p <- ggplot(logcpm.merged) +
        aes(x=logCPM_A, y=logCPM_B) +
        geom_point(size=0.2, alpha=0.2) +
        geom_abline(slope=1, intercept=0, color="red", linetype="dashed") +
        coord_fixed(xlim=limits, ylim=limits) +
        labs(title="logCPM, Salmon vs Kallisto", 
             subtitle="Identity line (red) plotted red for reference",
             x="logCPM by Kallisto", y="logCPM by Salmon")
ggprint(p)

Salmon vs Shoal

Last, we compare the basic Salmon results to the same results regularized by Shoal.

logcpm_A <- logcpm$salmon_hg38.analysisSet_ensembl.85 %>% 
    select(ENSEMBL, Sample, logCPM) %>%
    rename(logCPM_A=logCPM)
logcpm_B <- logcpm$shoal_hg38.analysisSet_ensembl.85 %>% 
    select(ENSEMBL, Sample, logCPM) %>%
    rename(logCPM_B=logCPM)
logcpm.merged <- inner_join(logcpm_A, logcpm_B) %>%
    filter(logCPM_A != min(logCPM_A) | logCPM_B != min(logCPM_B))
limits <- logcpm.merged %>% select(logCPM_A, logCPM_B) %>% lapply(range) %>% unlist %>% range
p <- ggplot(logcpm.merged) +
        aes(x=logCPM_A, y=logCPM_B) +
        geom_point(size=0.2, alpha=0.2) +
        geom_abline(slope=1, intercept=0, color="red", linetype="dashed") +
        coord_fixed(xlim=limits, ylim=limits) +
        labs(title="logCPM, Shoal vs Salmon", 
             subtitle="Identity line (red) plotted red for reference",
             x="logCPM by Salmon", y="logCPM by Shoal")
ggprint(p)

LS0tCnRpdGxlOiAiQ29tcGFyaXNvbiBvZiBRdWFudGlmaWNhdGlvbiBtZXRob2RzIGZvciBDRDQgUk5BLVNlcSBEYXRhc2V0IgphdXRob3I6ICJSeWFuIEMuIFRob21wc29uIgpkYXRlOiAiYHIgZ3N1YignXFxzKycsICcgJywgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZSwgJVknKSlgIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIFByZWxpbWluYXJ5IFNldHVwCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgcmV0aW5hPTIsIGNhY2hlPVRSVUUsIGF1dG9kZXA9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgIGNhY2hlLmV4dHJhID0gbGlzdChwYXJhbXM9cGFyYW1zKSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTgsCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZS5wYXRoID0gcGFzdGUwKGhlcmU6OmhlcmUoImNhY2hlIiwgInJuYXNlcS1jb21wYXJlIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5QbGF0Zm9ybSRmaWxlLnNlcCkpCm9wdGlvbnMoZHBseXIuc2hvd19wcm9ncmVzcz1GQUxTRSkKYGBgCgpGaXJzdCB3ZSBsb2FkIHRoZSBuZWNlc3NhcnkgbGlicmFyaWVzLgoKYGBge3IgbG9hZF9wYWNrYWdlcywgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmxpYnJhcnkoaGVyZSkKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KG1hZ3JpdHRyKQpsaWJyYXJ5KG9wZW54bHN4KQpsaWJyYXJ5KFN1bW1hcml6ZWRFeHBlcmltZW50KQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGVkZ2VSKQpsaWJyYXJ5KGxpbW1hKQpsaWJyYXJ5KHN2YSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHNjYWxlcykKbGlicmFyeShHR2FsbHkpCmxpYnJhcnkoZ2dhbHQpCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkoYXNzZXJ0dGhhdCkKbGlicmFyeShnZ2ZvcnRpZnkpCmxpYnJhcnkoYnJvb20pCgpsaWJyYXJ5KGRvUGFyYWxsZWwpCm5jb3JlcyA8LSBnZXRPcHRpb24oIm1jLmNvcmVzIiwgZGVmYXVsdD1wYXJhbGxlbDo6ZGV0ZWN0Q29yZXMobG9naWNhbCA9IEZBTFNFKSkKcmVnaXN0ZXJEb1BhcmFsbGVsKGNvcmVzPW5jb3JlcykKbGlicmFyeShCaW9jUGFyYWxsZWwpCnJlZ2lzdGVyKERvcGFyUGFyYW0oKSkKCnNvdXJjZShoZXJlKCJzY3JpcHRzL3V0aWxpdGllcy5SIikpCmBgYAoKIyBEYXRhIExvYWRpbmcgYW5kIFByZXByb2Nlc3NpbmcKCk5vdyB3ZSdsbCBsb2FkIHRoZSBSTkEtc2VxIGRhdGEgc2V0IGZyb20gYW4gUkRTIGZpbGUgY29udGFpbmluZyBhIFN1bW1hcml6ZWRFeHBlcmltZW50IG9iamVjdCwgYW5kIG1vZGlmeSBpdCB0byB1c2UgdGhlIHNhbXBsZSBuYW1lcyBhcyBjb2x1bW4gbmFtZXMuCgpgYGB7ciBsb2FkX2RhdGF9CmRhdGFzZXRzIDwtIGMoImhpc2F0Ml9ncmNoMzhfc25wX3RyYW5fZW5zZW1ibC44NSIsCiAgICAgICAgICAgICAgImhpc2F0Ml9ncmNoMzhfc25wX3RyYW5fa25vd25HZW5lIiwKICAgICAgICAgICAgICAia2FsbGlzdG9faGczOC5hbmFseXNpc1NldF9lbnNlbWJsLjg1IiwKICAgICAgICAgICAgICAia2FsbGlzdG9faGczOC5hbmFseXNpc1NldF9rbm93bkdlbmUiLAogICAgICAgICAgICAgICJzYWxtb25faGczOC5hbmFseXNpc1NldF9lbnNlbWJsLjg1IiwKICAgICAgICAgICAgICAic2FsbW9uX2hnMzguYW5hbHlzaXNTZXRfa25vd25HZW5lIiwKICAgICAgICAgICAgICAic2hvYWxfaGczOC5hbmFseXNpc1NldF9lbnNlbWJsLjg1IiwKICAgICAgICAgICAgICAic2hvYWxfaGczOC5hbmFseXNpc1NldF9rbm93bkdlbmUiLAogICAgICAgICAgICAgICJzdGFyX2hnMzguYW5hbHlzaXNTZXRfZW5zZW1ibC44NSIsCiAgICAgICAgICAgICAgInN0YXJfaGczOC5hbmFseXNpc1NldF9rbm93bkdlbmUiKQpkYXRhc2V0bWV0YSA8LSBkYXRhc2V0cyAlPiUgc3RyX21hdGNoKCJeKFteX10rKV8oLispXyhbXl9dKykkIikgJT4lCiAgICBhc19kYXRhX2ZyYW1lICU+JSBzZXROYW1lcyhjKCJkYXRhc2V0IiwgInByb2dyYW0iLCAiZ2Vub21lIiwgInRyYW5zY3JpcHRvbWUiKSkKYXNzZXJ0X3RoYXQoYWxsKHRhYmxlKGRhdGFzZXRtZXRhJHByb2dyYW0pID09IDIpKQphc3NlcnRfdGhhdChhbGwodGFibGUoZGF0YXNldG1ldGEkdHJhbnNjcmlwdG9tZSkgPT0gNSkpCnNleHAuZmlsZXMgPC0gc2V0TmFtZXMoaGVyZSgic2F2ZWRfZGF0YSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCJTdW1tYXJpemVkRXhwZXJpbWVudF9ybmFzZXFfJXMuUkRTIiwgZGF0YXNldHMpKSwKICAgICAgICAgICAgICAgICAgICAgICBkYXRhc2V0cykKc2V4cHMgPC0gYnBsYXBwbHkoc2V4cC5maWxlcywgcmVhZFJEUykKZm9yIChkc2V0IGluIG5hbWVzKHNleHBzKSkgewogICAgc2V4cHNbW2RzZXRdXSAlPD4lIHNldF9jb2xuYW1lcyguJFNhbXBsZU5hbWUpCn0KYGBgCgpXZSBleHRyYWN0IHRoZSBzYW1wbGUgbWV0YWRhdGEgZnJvbSB0aGUgU3VtbWFyaXplZEV4cGVyaW1lbnQgKHdlIG9ubHkgZG8gdGhpcyBvbmNlIHNpbmNlIGl0J3MgdGhlIHNhbWUgZm9yIGFsbCBkYXRhc2V0cykuCgpgYGB7ciBleHRyYWN0X3NhbXBsZW1ldGF9CnNhbXBsZS50YWJsZSA8LSBjb2xEYXRhKHNleHBzW1sxXV0pICU+JQogICAgYXMuZGF0YS5mcmFtZSAlPiUgYXV0b0ZhY3Rvcml6ZSAlPiUKICAgIHJlbmFtZShiYXRjaD10ZWNobmljYWxfYmF0Y2gpICU+JQogICAgbXV0YXRlKHRpbWVfcG9pbnQ9ZmFjdG9yKGRheXNfYWZ0ZXJfYWN0aXZhdGlvbikgJT4lIGBsZXZlbHM8LWAoc3ByaW50ZigiRCVzIiwgbGV2ZWxzKC4pKSksCiAgICAgICAgICAgZ3JvdXA9aW50ZXJhY3Rpb24oY2VsbF90eXBlLCB0aW1lX3BvaW50LCBzZXA9IiIpKQpgYGAKCk5leHQgd2UgZXh0cmFjdCB0aGUgY291bnQgbWF0cml4IGZyb20gZWFjaCBTdW1tYXJpemVkRXhwZXJpbWVudC4gVGhpcyBpcyBtYWRlIG1vcmUgY29tcGxpY2F0ZWQgdGhhbiB1c3VhbCBieSB0aGUgZmFjdCB0aGF0IGhhbGYgb2YgdGhlIHNhbXBsZXMgd2VyZSBzZXF1ZW5jZWQgd2l0aCBhIGRpZmZlcmVudCBwcm90b2NvbCB0aGFuIHRoZSBvdGhlciBoYWxmLCBhbmQgdGhlIHR3byBwcm90b2NvbHMgcHJvZHVjZSByZWFkcyB3aXRoIG9wcG9zaXRlIHN0cmFuZCBvcmllbnRhdGlvbnMuIEhlbmNlLCB3ZSBuZWVkIHRoZSBzZW5zZSBjb3VudHMgZm9yIGhhbGYgb2YgdGhlIHNhbXBsZXMgYW5kIHRoZSBhbnRpc2Vuc2UgY291bnRzIGZvciB0aGUgb3RoZXIgaGFsZi4gVGhlIGFwcHJvcHJpYXRlIHN0cmFuZCBmb3IgZWFjaCBzYW1wbGUgaXMgZG9jdW1lbnRlZCBpbiB0aGUgYGxpYlR5cGVgIGNvbHVtbiBvZiB0aGUgc2FtcGxlIG1ldGFkYXRhLCB1c2luZyB0aGUgbGlicmFyeSB0eXBlIGFiYnJldmlhdGlvbnMgW2VzdGFibGlzaGVkIGJ5IFNhbG1vbl0oaHR0cDovL3NhbG1vbi5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3Qvc2FsbW9uLmh0bWwjd2hhdC1zLXRoaXMtbGlidHlwZSkuCgpGb3IgU3VtbWFyaXplZEV4cGVyaW1lbnRzIGdlbmVyYXRlZCB1c2luZyB0eGltcG9ydCwgdGhpcyBzdGVwIGlzIHNraXBwZWQsIHNpbmNlIHRoZSBxdWFudGlmaWNhdGlvbiB0b29sIGhhcyBhbHJlYWR5IGJlZW4gdG9sZCB3aGljaCBzdHJhbmQgdG8gdXNlIGFuZCBvbmx5IHByb3ZpZGVzIGNvdW50cyBmb3IgdGhhdCBzdHJhbmQuCgpgYGB7ciBleHRyYWN0X2NvdW50c30KbGlidHlwZS5hc3NheU5hbWVzIDwtIGMoU0Y9InNlbnNlLmNvdW50cyIsIFNSPSJhbnRpc2Vuc2UuY291bnRzIikKZm9yIChkc2V0IGluIHNlcV9hbG9uZyhzZXhwcykpIHsKICAgIGlmIChhbGwobGlidHlwZS5hc3NheU5hbWVzICVpbiUgYXNzYXlOYW1lcyhzZXhwc1tbZHNldF1dKSkpIHsKICAgICAgICBzYW1wbGUudGFibGUgJTw+JSBtdXRhdGUoY291bnRfdHlwZT1saWJ0eXBlLmFzc2F5TmFtZXNbbGliVHlwZV0pCiAgICAgICAgYXNzYXkoc2V4cHNbW2RzZXRdXSwgInVuc3RyYW5kZWQuY291bnRzIikgPC0gYXNzYXkoc2V4cHNbW2RzZXRdXSwgImNvdW50cyIpCiAgICAgICAgYXNzYXkoc2V4cHNbW2RzZXRdXSwgImNvdW50cyIpIDwtIGxhcHBseShzZXFfbGVuKG5yb3coc2FtcGxlLnRhYmxlKSksIGZ1bmN0aW9uKGkpIHsKICAgICAgICAgICAgYXNzYXkoc2V4cHNbW2RzZXRdXSwgc2FtcGxlLnRhYmxlW2ksXSRjb3VudF90eXBlICU+JSBhcy5jaGFyYWN0ZXIpWyxpXQogICAgICAgIH0pICU+JSBkby5jYWxsKHdoYXQ9Y2JpbmQpCiAgICB9Cn0KYGBgCgpBcyBhIHNhbml0eSBjaGVjaywgd2UgbWFrZSBzdXJlIHRoYXQgd2Ugc2VsZWN0ZWQgdGhlIHN0cmFuZCBzZW5zZSB3aXRoIHRoZSBoaWdoZXIgdG90YWwgY291bnQgZm9yIGVhY2ggc2FtcGxlLgoKYGBge3Igc3RyYW5kX3Nhbml0eV9jaGVjaywgbWVzc2FnZT1GQUxTRX0KZm9yIChkc2V0IGluIHNlcV9hbG9uZyhzZXhwcykpIHsKICAgIGlmIChhbGwobGlidHlwZS5hc3NheU5hbWVzICVpbiUgYXNzYXlOYW1lcyhzZXhwc1tbZHNldF1dKSkpIHsKICAgICAgICB0b3RhbC5jb3VudHMgPC0gc2V4cHNbW2RzZXRdXSAlPiUgYXNzYXlzICU+JSBzYXBwbHkoY29sU3VtcykgJT4lIGRhdGEuZnJhbWUgJT4lCiAgICAgICAgICAgIG11dGF0ZShTYW1wbGVOYW1lPXJvdy5uYW1lcyguKSkgJT4lCiAgICAgICAgICAgIGlubmVyX2pvaW4oc2FtcGxlLnRhYmxlLCBieT0iU2FtcGxlTmFtZSIpCiAgICAgICAgdG90YWwuY291bnRzICUkJSBpbnZpc2libGUoYXNzZXJ0X3RoYXQoYWxsKGNvdW50cyA9PSBwbWF4KHNlbnNlLmNvdW50cywgYW50aXNlbnNlLmNvdW50cykpKSkKICAgIH0KfQpgYGAKCk5vdyB3ZSBjcmVhdGUgYSBER0VMaXN0IGZyb20gZWFjaCBjb3VudCBtYXRyaXgsIGFuZCBwZXJmb3JtIHRoZSBpbml0aWFsIHNjYWxpbmcgbm9ybWFsaXphdGlvbi4gSW4gYWRkaXRpb24sIGlmIHRoZXJlIGlzIGEgbGVuZ3RoIGFzc2F5LCB3ZSBhbHNvIHVzZSB0aGF0IHRvIGRlcml2ZSBhbiBvZmZzZXQgbWF0cml4IHRoYXQgY29ycmVjdHMgZm9yIHNhbXBsZS1zcGVjaWZpYyBiaWFzZXMgZGV0ZWN0ZWQgYnkgU2FsbW9uIG9yIHNob2FsLgoKYGBge3IgcHJlcGFyZV9kZ2VsaXN0fQpkZ2VzIDwtIGJwbGFwcGx5KHNleHBzLCBmdW5jdGlvbihzZXhwKSB7CiAgICAjIyBFeHRyYWN0IGdlbmUgbWV0YWRhdGEgYW5kIGNvbGFwc2UgbGlzdHMKICAgIGFsbC5nZW5lLm1ldGEgPC0gbWNvbHMoc2V4cCkgJT4lIGFzLmRhdGEuZnJhbWUKICAgICMgQ29udmVydCBsaXN0IGNvbHVtbnMgdG8gY2hhcmFjdGVyIHZlY3RvcnMKICAgIGFsbC5nZW5lLm1ldGFbXSAlPD4lIGxhcHBseShmdW5jdGlvbih4KSBpZiAoaXMubGlzdCh4KSkgc2FwcGx5KHgsIHN0cl9jLCBjb2xsYXBzZT0iLCIpIGVsc2UgeCkKICAgIGRnZSA8LSBER0VMaXN0KGNvdW50cz1hc3NheShzZXhwLCAiY291bnRzIikpCiAgICBkZ2UkZ2VuZXMgPC0gYWxsLmdlbmUubWV0YQogICAgcm93bmFtZXMoZGdlJGdlbmVzKSA8LSByb3duYW1lcyhkZ2UpCiAgICBkZ2UgJTw+JSBjYWxjTm9ybUZhY3RvcnMKICAgIGlmICgibGVuZ3RoIiAlaW4lIGFzc2F5TmFtZXMoc2V4cCkpIHsKICAgICAgICBub3JtTWF0IDwtIGFzc2F5KHNleHAsICJsZW5ndGgiKSAlPiUgZGl2aWRlX2J5KGV4cChyb3dNZWFucyhsb2coLikpKSkKICAgICAgICBub3JtQ291bnRzIDwtIGRnZSRjb3VudHMvbm9ybU1hdAogICAgICAgIGxpYi5vZmZzZXRzIDwtIGxvZyhjYWxjTm9ybUZhY3RvcnMobm9ybUNvdW50cykpICsgbG9nKGNvbFN1bXMobm9ybUNvdW50cykpCiAgICAgICAgZGdlJG9mZnNldCA8LSB0KHQobG9nKG5vcm1NYXQpKSArIGxpYi5vZmZzZXRzKQogICAgfQogICAgZGdlCn0pCmBgYAoKRmluYWxseSwgd2UgY29tcHV0ZSB0aGUgbm9ybWFsaXplZCBsb2dDUE0gbWF0cml4IGZvciBlYWNoIGRhdGFzZXQsIGFuZCBjb252ZXJ0IGl0IGludG8gYSB0aWR5IGZvcm1hdC4gV2UgYWxzbyBjb21wdXRlIHRoZSBub3JtYWxpemVkIGxvZ0NQTSB3aXRob3V0IHRha2luZyBpbnRvIGFjY291bnQgdGhlIG9mZnNldHMgZGVyaXZlZCBmcm9tIGdlbmUgbGVuZ3RoIG5vcm1hbGl6YXRpb24uIAoKYGBge3IgcHJlcGFyZV9sb2dDUE0sIG1lc3NhZ2U9RkFMU0V9CmxvZ2NwbS5tYXRyaWNlcyA8LSBicGxhcHBseShkZ2VzLCBjcG1XaXRoT2Zmc2V0LCBsb2c9VFJVRSkKbG9nY3BtLm1hdHJpY2VzLm5vb2Zmc2V0IDwtIGJwbGFwcGx5KGRnZXMsIGNwbSwgbG9nPVRSVUUpCmxvZ2NwbSA8LSBicGxhcHBseShkZ2VzLCBmdW5jdGlvbihkZ2UpIHsKICAgIGNwbS5tYXQgPC0gY3BtV2l0aE9mZnNldChkZ2UsIGxvZz1UUlVFKQogICAgY3BtLmRmIDwtIG1lbHQoY3BtLm1hdCwgdmFybmFtZXM9YygiR2VuZUlEIiwgIlNhbXBsZSIpLCB2YWx1ZS5uYW1lID0gImxvZ0NQTSIpICU+JQogICAgICAgIG11dGF0ZShHZW5lSUQ9YXMuY2hhcmFjdGVyKEdlbmVJRCksIFNhbXBsZT1hcy5jaGFyYWN0ZXIoU2FtcGxlKSkKICAgIGdlbmVzIDwtIGRnZSRnZW5lcyAlPiUgbXV0YXRlKEdlbmVJRD1yb3duYW1lcyguKSkKICAgIGlubmVyX2pvaW4oZ2VuZXNbYygiR2VuZUlEIiwgIkVOVFJFWklEIiwgIkVOU0VNQkwiKV0sIGNwbS5kZiwgYnk9IkdlbmVJRCIpICU+JQogICAgICAgIHNlbGVjdCgtR2VuZUlEKQp9KQpsb2djcG0ubm9vZmZzZXQgPC0gYnBsYXBwbHkoZGdlcywgZnVuY3Rpb24oZGdlKSB7CiAgICBjcG0ubWF0IDwtIGNwbShkZ2UsIGxvZz1UUlVFKQogICAgY3BtLmRmIDwtIG1lbHQoY3BtLm1hdCwgdmFybmFtZXM9YygiR2VuZUlEIiwgIlNhbXBsZSIpLCB2YWx1ZS5uYW1lID0gImxvZ0NQTSIpICU+JQogICAgICAgIG11dGF0ZShHZW5lSUQ9YXMuY2hhcmFjdGVyKEdlbmVJRCksIFNhbXBsZT1hcy5jaGFyYWN0ZXIoU2FtcGxlKSkKICAgIGdlbmVzIDwtIGRnZSRnZW5lcyAlPiUgbXV0YXRlKEdlbmVJRD1yb3duYW1lcyguKSkKICAgIGlubmVyX2pvaW4oZ2VuZXNbYygiR2VuZUlEIiwgIkVOVFJFWklEIiwgIkVOU0VNQkwiKV0sIGNwbS5kZiwgYnk9IkdlbmVJRCIpICU+JQogICAgICAgIHNlbGVjdCgtR2VuZUlEKQp9KQpgYGAKICAgIAojIENvbXBhcmF0aXZlIEFuYWx5c2lzCiAgICAKTm93IHdlIGNhbiBiZWdpbiBjb21wYXJpbmcgdGhlIGRpZmZlcmVudCBtZXRob2RzIG9mIHF1YW50aWZpY2F0aW9uLiBGaXJzdCwgd2Ugd2lsbCBjb21wYXJlIEVuc2VtYmwgdnMgVUNTQyBhbm5vdGF0aW9ucyBmb3IgZWFjaCBxdWFudGlmaWNhdGlvbiBtZXRob2QuIAogICAgCiMjIEVuc2VtYmwgdnMgVUNTQwogICAgCkZpcnN0LCB3ZSBmb3JtYXQgdGhlIGxvZ0NQTSB0YWJsZXMgaW50byB0aWR5IGZvcm1hdCwgYW5kIG1lcmdlIHRoZSB0YWJsZXMgb2YgYm90aCBkYXRhc2V0cyAoRW5zZW1ibCBhbmQgVUNTQyBrbm93bkdlbmUpIGZvciBlYWNoIGFsaWdubWVudC9xdWFudGlmaWNhdGlvbiBwcm9ncmFtLiBUaGUgbWVyZ2luZyBpcyBwZXJmb3JtZWQgb25seSBmb3IgZ2VuZXMgd2l0aCBhIG9uZS10by1vbmUgcmVsYXRpb25zaGlwIGJldHdlZW4gRW5zZW1ibCBhbmQgRW50cmV6IGdlbmUgSURzLgogICAgCmBgYHtyIGNvbXB1dGVfZW5zZW1ibF92c191Y3NjLCBtZXNzYWdlPUZBTFNFfQpsb2djcG0udGFibGVzIDwtIGRhdGFzZXRtZXRhICU+JQogICAgYXJyYW5nZSh0cmFuc2NyaXB0b21lKSAlPiUKICAgIGdyb3VwX2J5KHByb2dyYW0pICU+JSBkbyh7CiAgICAgICAgZGYgPC0gLgogICAgICAgIGFzc2VydF90aGF0KG5yb3coZGYpID09IDIpCiAgICAgICAgZHNldEEgPC0gZGYkZGF0YXNldFsxXQogICAgICAgIGRzZXRCIDwtIGRmJGRhdGFzZXRbMl0KICAgICAgICB0eEEgPC0gZGYkdHJhbnNjcmlwdG9tZVsxXQogICAgICAgIHR4QiA8LSBkZiR0cmFuc2NyaXB0b21lWzJdCiAgICAgICAgbG9nY3BtX0EgPC0gbG9nY3BtW1tkc2V0QV1dICU+JSAKICAgICAgICAgICAgcmVuYW1lKGxvZ0NQTV9BPWxvZ0NQTSkgJT4lCiAgICAgICAgICAgIGZpbHRlcihsZW5ndGhzKEVOVFJFWklEKSA9PSAxLCBsZW5ndGhzKEVOU0VNQkwpID09IDEpICU+JQogICAgICAgICAgICBtdXRhdGUoRU5UUkVaSUQ9dW5saXN0KEVOVFJFWklEKSwKICAgICAgICAgICAgICAgICAgIEVOU0VNQkw9dW5saXN0KEVOU0VNQkwpKQogICAgICAgIGxvZ2NwbV9CIDwtIGxvZ2NwbVtbZHNldEJdXSAlPiUKICAgICAgICAgICAgcmVuYW1lKGxvZ0NQTV9CPWxvZ0NQTSkgJT4lCiAgICAgICAgICAgIGZpbHRlcihsZW5ndGhzKEVOVFJFWklEKSA9PSAxLCBsZW5ndGhzKEVOU0VNQkwpID09IDEpICU+JQogICAgICAgICAgICBtdXRhdGUoRU5UUkVaSUQ9dW5saXN0KEVOVFJFWklEKSwKICAgICAgICAgICAgICAgICAgIEVOU0VNQkw9dW5saXN0KEVOU0VNQkwpKQogICAgICAgIGxvZ2NwbV9tZXJnZWQgPC0gaW5uZXJfam9pbihsb2djcG1fQSwgbG9nY3BtX0IpCiAgICAgICAgZGF0YV9mcmFtZShwcm9ncmFtPWRmJHByb2dyYW1bMV0sIAogICAgICAgICAgICAgICAgICAgZ2Vub21lPWRmJGdlbm9tZVsxXSwKICAgICAgICAgICAgICAgICAgIGRhdGFzZXRBPWRzZXRBLCBkYXRhc2V0Qj1kc2V0QiwKICAgICAgICAgICAgICAgICAgIHRyYW5zY3JpcHRvbWVBPXR4QSwgdHJhbnNjcmlwdG9tZUI9dHhCLAogICAgICAgICAgICAgICAgICAgbG9nY3BtPWxpc3QobG9nY3BtX21lcmdlZCkpCiAgICB9KQojIFByaW50IHRoZSBjb3JyZWxhdGlvbnMKbG9nY3BtLnRhYmxlcyAlPiUgZ3JvdXBfYnlfKG5hbWVzKC4pWzE6NV0pICU+JSBkbyh7CiAgICBkYXRhLmZyYW1lKENvcnJlbGF0aW9uPS4kbG9nY3BtW1sxXV0gJSQlIGNvcihsb2dDUE1fQSwgbG9nQ1BNX0IpKQp9KQpgYGAKCk5vdyB3ZSBwbG90IHRoZSBsb2dDUE0gZm9yIEVuc2VtYmwgdnMgVUNTQywgZm9yIGVhY2ggcXVhbnRpZmljYXRpb24gbWV0aG9kIChwcm9ncmFtICsgdHJhbnNjcmlwdG9tZSkuCgpgYGB7ciBwbG90X2Vuc2VtYmxfdnNfdWNzY30KIyBVc2UgdGhlIHNhbWUgeCBhbmQgeSBsaW1pdHMgZm9yIGFsbCBwbG90cwpsaW1pdHMgPC0gbG9nY3BtLnRhYmxlcyRsb2djcG0gJT4lIGxhcHBseSguICUkJSBjKHJhbmdlKGxvZ0NQTV9BKSwgcmFuZ2UobG9nQ1BNX0IpKSkgJT4lIHVubGlzdCAlPiUgcmFuZ2UKbG9nY3BtLnBsb3RzIDwtIGxvZ2NwbS50YWJsZXMgJT4lIGdyb3VwX2J5KHByb2dyYW0pICU+JSBkbyh7CiAgICBkZiA8LSAuCiAgICBhc3NlcnRfdGhhdChucm93KGRmKSA9PSAxKQogICAgeGxhYmVsIDwtIHN0cl9jKCJHZW5lIGxvZ0NQTSBpbiAiLCBkZiRkYXRhc2V0QSkKICAgIHlsYWJlbCA8LSBzdHJfYygiR2VuZSBsb2dDUE0gaW4gIiwgZGYkZGF0YXNldEIpCiAgICBwdGl0bGUgPC0gc3ByaW50ZigibG9nQ1BNLCAlcyB2cyAlcyBmb3IgJXMgb24gJXMgZ2Vub21lIiwgZGYkdHJhbnNjcmlwdG9tZUEsIGRmJHRyYW5zY3JpcHRvbWVCLCBkZiRwcm9ncmFtLCBkZiRnZW5vbWUpCiAgICB0YWIgPC0gZGYkbG9nY3BtW1sxXV0gJT4lIGZpbHRlcihsb2dDUE1fQSAhPSBtaW4obG9nQ1BNX0EpIHwgbG9nQ1BNX0IgIT0gbWluKGxvZ0NQTV9CKSkKICAgIHAgPC0gZ2dwbG90KHRhYikgKyAKICAgICAgICBhZXMoeD1sb2dDUE1fQSwgeT1sb2dDUE1fQikgKwogICAgICAgIGdlb21fcG9pbnQoc2l6ZT0wLjIsIGFscGhhPTAuMikgKwogICAgICAgIGdlb21fYWJsaW5lKHNsb3BlPTEsIGludGVyY2VwdD0wLCBjb2xvcj0icmVkIiwgbGluZXR5cGU9ImRhc2hlZCIpICsKICAgICAgICBjb29yZF9maXhlZCh4bGltPWxpbWl0cywgeWxpbT1saW1pdHMpICsKICAgICAgICBsYWJzKHRpdGxlPXB0aXRsZSwgCiAgICAgICAgICAgICBzdWJ0aXRsZT0iR2VuZXMgbWF0Y2hlZCBieSB1bmlxdWUgRW50cmV6ICYgRW5zZW1ibCBJRHM7IGlkZW50aXR5IGxpbmUgKHJlZCkgcGxvdHRlZCByZWQgZm9yIHJlZmVyZW5jZSIsCiAgICAgICAgICAgICB4PXhsYWJlbCwgeT15bGFiZWwpCiAgICBjYmluZCguW2MoInByb2dyYW0iLCAiZ2Vub21lIiwgImRhdGFzZXRBIiwgImRhdGFzZXRCIiwgInRyYW5zY3JpcHRvbWVBIiwgInRyYW5zY3JpcHRvbWVCIildLAogICAgICAgICAgZGF0YV9mcmFtZShwbG90PWxpc3QocCkpKQp9KQpnZ3ByaW50KGxvZ2NwbS5wbG90cyRwbG90KQpgYGAKCk92ZXJhbGwgY29uY29yZGFuY2UgaXMgdmVyeSBoaWdoIGZvciBhbGwgbWV0aG9kcy4gVGhlIGFsaWdubWVudC1iYXNlZCBtZXRob2RzIChTVEFSIGFuZCBISVNBVDIpIGhhdmUgc2xpZ2h0bHkgaGlnaGVyIGNvcnJlbGF0aW9uLCB3aGljaCBpcyBtYXRjaGVkIGJ5IHRoZSBhcHBhcmVudGx5IHRpZ2h0ZXIgc3ByZWFkIG9mIHBvaW50cyBhYm91dCB0aGUgaWRlbnRpdHkgbGluZSBpbiB0aGUgcGxvdHMuIFRoaXMgaXMgZXhwZWN0ZWQgc2luY2UgdGhlc2UgbWV0aG9kcyBkaXNjYXJkIGFtYmlndW91cyByZWFkcywgYW5kIGFyZSB0aGVyZWZvcmUgYmlhc2VkIHRvd2FyZCB1bmFtYmlndW91c2x5LWRlZmluZWQgZ2VuZXMsIHdoaWNoIGFyZSB0aGUgc2FtZSBnZW5lcyBtb3JlIGxpa2VseSB0byBiZSBhbm5vdGF0ZWQgY29uc2lzdGVudGx5IGJldHdlZW4gRW50cmV6IGFuZCBFbnNlbWJsLgoKSW50ZXJlc3RpbmdseSwgdGhlIGNvcnJlbGF0aW9uIGZvciBTaG9hbCBpcyBsb3dlciB0aGFuIGZvciBTYWxtb24sIGRlc3BpdGUgdGhlIGZhY3QgdGhhdCB0aGUgU2hvYWwgcGxvdCBsb29rcyBsaWtlIGl0IGhhcyBhIHRpZ2h0ZXIgc3ByZWFkIGFuZCB0aGUgZmFjdCB0aGF0IFNob2FsIGlzIG1lYW50IHRvIHJlZ3VsYXJpemUgU2FsbW9uJ3MgcXVhbnRpZmljYXRpb24gcmVzdWx0cywgd2hpY2ggd291bGQgYmUgZXhwZWN0ZWQgdG8gaW5jcmVhc2UgdGhlIGNvcnJlbGF0aW9uLiBQZXJoYXBzIFNob2FsIGRlY3JlYXNlcyB0aGUgb3ZlcmFsbCBjb3JyZWxhdGlvbiBieSBzcHJlYWRpbmcgbWFueSBwb2ludHMgYXdheSBmcm9tIHRoZSBib3R0b20gbGVmdCBjb3JuZXIsIHdoZXJlIHByZXZpb3VzbHkgYWxsIHRoZSBnZW5lcyB3aXRoIHplcm8gY291bnRzIHdlcmUgZXhhY3RseSBvdmVybGFpZCBvbiBhIHNpbmdsZSBwb2ludC4gKFRoaXMga2luZCBvZiBlZmZlY3QgaXMgd2h5IHBlYXJzb24gY29ycmVsYXRpb25zIG9uIGdlbmUgYWJ1bmRhbmNlcyBpcyByYXJlbHkgaW5mb3JtYXRpdmUuKSAKCiMjIFNUQVIgdnMgSElTQVQyCgpUaGUgdHdvIGNvdW50LWJhc2VkIG1ldGhvZHMgZGlmZmVyIG9ubHkgaW4gdGhlIGFsaWduZXIgdGhhdCB3YXMgdXNlZCwgc28gYW55IGRpc2NyZXBhbmN5IGJldHdlZW4gdGhlbSByZXByZXNlbnRzIGEgZGlmZmVyZW5jZSBpbiB3aGljaCByZWFkcyB0aGV5IGNvdWxkIGFsaWduIHVuYW1iaWd1b3VzbHkuCgpgYGB7ciBwbG90X3N0YXJfdnNfaGlzYXQyLCBtZXNzYWdlPUZBTFNFfQpsb2djcG1fQSA8LSBsb2djcG0kaGlzYXQyX2dyY2gzOF9zbnBfdHJhbl9lbnNlbWJsLjg1ICU+JSAKICAgIHNlbGVjdChFTlNFTUJMLCBTYW1wbGUsIGxvZ0NQTSkgJT4lCiAgICByZW5hbWUobG9nQ1BNX0E9bG9nQ1BNKQpsb2djcG1fQiA8LSBsb2djcG0kc3Rhcl9oZzM4LmFuYWx5c2lzU2V0X2Vuc2VtYmwuODUgJT4lIAogICAgc2VsZWN0KEVOU0VNQkwsIFNhbXBsZSwgbG9nQ1BNKSAlPiUKICAgIHJlbmFtZShsb2dDUE1fQj1sb2dDUE0pCmxvZ2NwbS5tZXJnZWQgPC0gaW5uZXJfam9pbihsb2djcG1fQSwgbG9nY3BtX0IpICU+JQogICAgZmlsdGVyKGxvZ0NQTV9BICE9IG1pbihsb2dDUE1fQSkgfCBsb2dDUE1fQiAhPSBtaW4obG9nQ1BNX0IpKQpsaW1pdHMgPC0gbG9nY3BtLm1lcmdlZCAlPiUgc2VsZWN0KGxvZ0NQTV9BLCBsb2dDUE1fQikgJT4lIGxhcHBseShyYW5nZSkgJT4lIHVubGlzdCAlPiUgcmFuZ2UKcCA8LSBnZ3Bsb3QobG9nY3BtLm1lcmdlZCkgKwogICAgYWVzKHg9bG9nQ1BNX0EsIHk9bG9nQ1BNX0IpICsKICAgIGdlb21fcG9pbnQoc2l6ZT0wLjIsIGFscGhhPTAuMikgKwogICAgZ2VvbV9hYmxpbmUoc2xvcGU9MSwgaW50ZXJjZXB0PTAsIGNvbG9yPSJyZWQiLCBsaW5ldHlwZT0iZGFzaGVkIikgKwogICAgY29vcmRfZml4ZWQoeGxpbT1saW1pdHMsIHlsaW09bGltaXRzKSArCiAgICBsYWJzKHRpdGxlPSJsb2dDUE0sIFNUQVIgdnMgSElTQVQyIiwgCiAgICAgICAgIHN1YnRpdGxlPSJJZGVudGl0eSBsaW5lIChyZWQpIHBsb3R0ZWQgcmVkIGZvciByZWZlcmVuY2UiLAogICAgICAgICB4PSJsb2dDUE0gYnkgSElTQVQyIiwgeT0ibG9nQ1BNIGJ5IFNUQVIiKQpnZ3ByaW50KHApCmBgYAojIyBFZmZlY3Qgb2YgZ2VuZSBsZW5ndGggbm9ybWFsaXphdGlvbgoKV2Ugbm93IGNvbXBhcmUgU2FsbW9uLCBTaG9hbCwgYW5kIEthbGxpc3RvIHdpdGggYW5kIHdpdGhvdXQgbm9ybWFsaXppbmcgZm9yIGdlbmUgbGVuZ3RoLiBUaGlzIHdpbGwgc2hvdyBob3cgbXVjaCBvciBob3cgbGl0dGxlIHRoZSBnZW5lIGxlbmd0aCBub3JtYWxpemF0aW9uIGFmZmVjdHMgdGhlIGFidW5kYW5jZSBlc3RpbWF0aW9uLgoKYGBge3IgcGxvdF9sZW5ndGhfbm9ybV9lZmZlY3QsIG1lc3NhZ2U9RkFMU0V9CmxvZ2NwbS5wbG90cyA8LSBkYXRhc2V0bWV0YSAlPiUKICAgIGZpbHRlcih0cmFuc2NyaXB0b21lID09ICJlbnNlbWJsLjg1IiwgcHJvZ3JhbSAlaW4lIGMoInNhbG1vbiIsICJzaG9hbCIsICJrYWxsaXN0byIpKSAlPiUgCiAgICBncm91cF9ieShkYXRhc2V0LCBwcm9ncmFtKSAlPiUgZG8oewogICAgICAgIGFzc2VydF90aGF0KG5yb3coLikgPT0gMSkKICAgICAgICBkc2V0IDwtIC4kZGF0YXNldAogICAgICAgIHByb2dyYW0gPC0gLiRwcm9ncmFtCiAgICAgICAgbG9nY3BtX0EgPC0gbG9nY3BtLm5vb2Zmc2V0W1tkc2V0XV0gJT4lIAogICAgICAgICAgICBzZWxlY3QoRU5TRU1CTCwgU2FtcGxlLCBsb2dDUE0pICU+JQogICAgICAgICAgICByZW5hbWUobG9nQ1BNX0E9bG9nQ1BNKQogICAgICAgIGxvZ2NwbV9CIDwtIGxvZ2NwbVtbZHNldF1dICU+JSAKICAgICAgICAgICAgc2VsZWN0KEVOU0VNQkwsIFNhbXBsZSwgbG9nQ1BNKSAlPiUKICAgICAgICAgICAgcmVuYW1lKGxvZ0NQTV9CPWxvZ0NQTSkKICAgICAgICBsb2djcG0ubWVyZ2VkIDwtIGlubmVyX2pvaW4obG9nY3BtX0EsIGxvZ2NwbV9CKSAlPiUKICAgICAgICAgICAgZmlsdGVyKGxvZ0NQTV9BICE9IG1pbihsb2dDUE1fQSkgfCBsb2dDUE1fQiAhPSBtaW4obG9nQ1BNX0IpKQogICAgICAgIGxpbWl0cyA8LSBsb2djcG0ubWVyZ2VkICU+JSBzZWxlY3QobG9nQ1BNX0EsIGxvZ0NQTV9CKSAlPiUgbGFwcGx5KHJhbmdlKSAlPiUgdW5saXN0ICU+JSByYW5nZQogICAgICAgIHAgPC0gZ2dwbG90KGxvZ2NwbS5tZXJnZWQpICsKICAgICAgICAgICAgYWVzKHg9bG9nQ1BNX0EsIHk9bG9nQ1BNX0IpICsKICAgICAgICAgICAgZ2VvbV9wb2ludChzaXplPTAuMiwgYWxwaGE9MC4yKSArCiAgICAgICAgICAgIGdlb21fYWJsaW5lKHNsb3BlPTEsIGludGVyY2VwdD0wLCBjb2xvcj0icmVkIiwgbGluZXR5cGU9ImRhc2hlZCIpICsKICAgICAgICAgICAgY29vcmRfZml4ZWQoeGxpbT1saW1pdHMsIHlsaW09bGltaXRzKSArCiAgICAgICAgICAgIGxhYnModGl0bGU9c3ByaW50ZigiJXMgbG9nQ1BNLCBsZW5ndGgtbm9ybWFsaXplZCB2cyBub24tbGVuZ3RoLW5vcm1hbGl6ZWQiLCBwcm9ncmFtKSwgCiAgICAgICAgICAgICAgICAgc3VidGl0bGU9IklkZW50aXR5IGxpbmUgKHJlZCkgcGxvdHRlZCByZWQgZm9yIHJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgeD0ibG9nQ1BNIHdpdGhvdXQgZ2VuZSBsZW5ndGggbm9ybWFsaXphdGlvbiIsIHk9ImxvZ0NQTSB3aXRoIGdlbmUgbGVuZ3RoIG5vcm1hbGl6YXRpb24iKQogICAgICAgIGRhdGFfZnJhbWUocGxvdD1saXN0KHApKQogICAgfSkKZ2dwcmludChsb2djcG0ucGxvdHMkcGxvdCkKYGBgCgojIyBTVEFSIHZzIFNhbG1vbgoKV2Ugbm93IGNvbXBhcmUgU1RBUiwgYSBjb3VudC1iYXNlZCBtZXRob2QsIHRvIFNhbG1vbiwgYW4gYWxpZ25tZW50LWZyZWUgbWV0aG9kLiBXZSB1c2UgdGhlIFNhbG1vbiByZXN1bHRzIHdpdGggbm8gZ2VuZSBsZW5ndGggbm9ybWFsaXphdGlvbiBmb3IgYW4gYXBwbGVzLXRvLWFwcGxlcyBjb21wYXJpc29uLgoKYGBge3IgcGxvdF9zdGFyX3ZzX3NhbG1vbiwgbWVzc2FnZT1GQUxTRX0KbG9nY3BtX0EgPC0gbG9nY3BtJHN0YXJfaGczOC5hbmFseXNpc1NldF9lbnNlbWJsLjg1ICU+JSAKICAgIHNlbGVjdChFTlNFTUJMLCBTYW1wbGUsIGxvZ0NQTSkgJT4lCiAgICByZW5hbWUobG9nQ1BNX0E9bG9nQ1BNKQpsb2djcG1fQiA8LSBsb2djcG0ubm9vZmZzZXQkc2FsbW9uX2hnMzguYW5hbHlzaXNTZXRfZW5zZW1ibC44NSAlPiUgCiAgICBzZWxlY3QoRU5TRU1CTCwgU2FtcGxlLCBsb2dDUE0pICU+JQogICAgcmVuYW1lKGxvZ0NQTV9CPWxvZ0NQTSkKbG9nY3BtLm1lcmdlZCA8LSBpbm5lcl9qb2luKGxvZ2NwbV9BLCBsb2djcG1fQikgJT4lCiAgICBmaWx0ZXIobG9nQ1BNX0EgIT0gbWluKGxvZ0NQTV9BKSB8IGxvZ0NQTV9CICE9IG1pbihsb2dDUE1fQikpCmxpbWl0cyA8LSBsb2djcG0ubWVyZ2VkICU+JSBzZWxlY3QobG9nQ1BNX0EsIGxvZ0NQTV9CKSAlPiUgbGFwcGx5KHJhbmdlKSAlPiUgdW5saXN0ICU+JSByYW5nZQpwIDwtIGdncGxvdChsb2djcG0ubWVyZ2VkKSArCiAgICAgICAgYWVzKHg9bG9nQ1BNX0EsIHk9bG9nQ1BNX0IpICsKICAgICAgICBnZW9tX3BvaW50KHNpemU9MC4yLCBhbHBoYT0wLjIpICsKICAgICAgICBnZW9tX2FibGluZShzbG9wZT0xLCBpbnRlcmNlcHQ9MCwgY29sb3I9InJlZCIsIGxpbmV0eXBlPSJkYXNoZWQiKSArCiAgICAgICAgY29vcmRfZml4ZWQoeGxpbT1saW1pdHMsIHlsaW09bGltaXRzKSArCiAgICAgICAgbGFicyh0aXRsZT0ibG9nQ1BNLCBTYWxtb24gdnMgU1RBUiIsIAogICAgICAgICAgICAgc3VidGl0bGU9IklkZW50aXR5IGxpbmUgKHJlZCkgcGxvdHRlZCByZWQgZm9yIHJlZmVyZW5jZSIsCiAgICAgICAgICAgICB4PSJsb2dDUE0gYnkgU1RBUiIsIHk9ImxvZ0NQTSBieSBTYWxtb24iKQpnZ3ByaW50KHApCmBgYAoKIyMgU2FsbW9uIHZzIEthbGxpc3RvCgpUaGUgbmV4dCBjb21wYXJpc29uIGlzIGJldHdlZW4gU2FsbW9uIGFuZCBLYWxsaXN0bywgdHdvIGNvbmNlcHR1YWxseSB2ZXJ5IHNpbWlsYXIgcHNldWRvYWxpZ25tZW50LWJhc2VkIG1ldGhvZHMuCgpgYGB7ciBwbG90X3NhbG1vbl92c19rYWxsaXN0bywgbWVzc2FnZT1GQUxTRX0KbG9nY3BtX0EgPC0gbG9nY3BtJGthbGxpc3RvX2hnMzguYW5hbHlzaXNTZXRfZW5zZW1ibC44NSAlPiUgCiAgICBzZWxlY3QoRU5TRU1CTCwgU2FtcGxlLCBsb2dDUE0pICU+JQogICAgcmVuYW1lKGxvZ0NQTV9BPWxvZ0NQTSkKbG9nY3BtX0IgPC0gbG9nY3BtJHNhbG1vbl9oZzM4LmFuYWx5c2lzU2V0X2Vuc2VtYmwuODUgJT4lIAogICAgc2VsZWN0KEVOU0VNQkwsIFNhbXBsZSwgbG9nQ1BNKSAlPiUKICAgIHJlbmFtZShsb2dDUE1fQj1sb2dDUE0pCmxvZ2NwbS5tZXJnZWQgPC0gaW5uZXJfam9pbihsb2djcG1fQSwgbG9nY3BtX0IpICU+JQogICAgZmlsdGVyKGxvZ0NQTV9BICE9IG1pbihsb2dDUE1fQSkgfCBsb2dDUE1fQiAhPSBtaW4obG9nQ1BNX0IpKQpsaW1pdHMgPC0gbG9nY3BtLm1lcmdlZCAlPiUgc2VsZWN0KGxvZ0NQTV9BLCBsb2dDUE1fQikgJT4lIGxhcHBseShyYW5nZSkgJT4lIHVubGlzdCAlPiUgcmFuZ2UKcCA8LSBnZ3Bsb3QobG9nY3BtLm1lcmdlZCkgKwogICAgICAgIGFlcyh4PWxvZ0NQTV9BLCB5PWxvZ0NQTV9CKSArCiAgICAgICAgZ2VvbV9wb2ludChzaXplPTAuMiwgYWxwaGE9MC4yKSArCiAgICAgICAgZ2VvbV9hYmxpbmUoc2xvcGU9MSwgaW50ZXJjZXB0PTAsIGNvbG9yPSJyZWQiLCBsaW5ldHlwZT0iZGFzaGVkIikgKwogICAgICAgIGNvb3JkX2ZpeGVkKHhsaW09bGltaXRzLCB5bGltPWxpbWl0cykgKwogICAgICAgIGxhYnModGl0bGU9ImxvZ0NQTSwgU2FsbW9uIHZzIEthbGxpc3RvIiwgCiAgICAgICAgICAgICBzdWJ0aXRsZT0iSWRlbnRpdHkgbGluZSAocmVkKSBwbG90dGVkIHJlZCBmb3IgcmVmZXJlbmNlIiwKICAgICAgICAgICAgIHg9ImxvZ0NQTSBieSBLYWxsaXN0byIsIHk9ImxvZ0NQTSBieSBTYWxtb24iKQpnZ3ByaW50KHApCmBgYAoKIyMgU2FsbW9uIHZzIFNob2FsCgpMYXN0LCB3ZSBjb21wYXJlIHRoZSBiYXNpYyBTYWxtb24gcmVzdWx0cyB0byB0aGUgc2FtZSByZXN1bHRzIHJlZ3VsYXJpemVkIGJ5IFNob2FsLgoKYGBge3IgcGxvdF9zaG9hbF92c19zYWxtb24sIG1lc3NhZ2U9RkFMU0V9CmxvZ2NwbV9BIDwtIGxvZ2NwbSRzYWxtb25faGczOC5hbmFseXNpc1NldF9lbnNlbWJsLjg1ICU+JSAKICAgIHNlbGVjdChFTlNFTUJMLCBTYW1wbGUsIGxvZ0NQTSkgJT4lCiAgICByZW5hbWUobG9nQ1BNX0E9bG9nQ1BNKQpsb2djcG1fQiA8LSBsb2djcG0kc2hvYWxfaGczOC5hbmFseXNpc1NldF9lbnNlbWJsLjg1ICU+JSAKICAgIHNlbGVjdChFTlNFTUJMLCBTYW1wbGUsIGxvZ0NQTSkgJT4lCiAgICByZW5hbWUobG9nQ1BNX0I9bG9nQ1BNKQpsb2djcG0ubWVyZ2VkIDwtIGlubmVyX2pvaW4obG9nY3BtX0EsIGxvZ2NwbV9CKSAlPiUKICAgIGZpbHRlcihsb2dDUE1fQSAhPSBtaW4obG9nQ1BNX0EpIHwgbG9nQ1BNX0IgIT0gbWluKGxvZ0NQTV9CKSkKbGltaXRzIDwtIGxvZ2NwbS5tZXJnZWQgJT4lIHNlbGVjdChsb2dDUE1fQSwgbG9nQ1BNX0IpICU+JSBsYXBwbHkocmFuZ2UpICU+JSB1bmxpc3QgJT4lIHJhbmdlCnAgPC0gZ2dwbG90KGxvZ2NwbS5tZXJnZWQpICsKICAgICAgICBhZXMoeD1sb2dDUE1fQSwgeT1sb2dDUE1fQikgKwogICAgICAgIGdlb21fcG9pbnQoc2l6ZT0wLjIsIGFscGhhPTAuMikgKwogICAgICAgIGdlb21fYWJsaW5lKHNsb3BlPTEsIGludGVyY2VwdD0wLCBjb2xvcj0icmVkIiwgbGluZXR5cGU9ImRhc2hlZCIpICsKICAgICAgICBjb29yZF9maXhlZCh4bGltPWxpbWl0cywgeWxpbT1saW1pdHMpICsKICAgICAgICBsYWJzKHRpdGxlPSJsb2dDUE0sIFNob2FsIHZzIFNhbG1vbiIsIAogICAgICAgICAgICAgc3VidGl0bGU9IklkZW50aXR5IGxpbmUgKHJlZCkgcGxvdHRlZCByZWQgZm9yIHJlZmVyZW5jZSIsCiAgICAgICAgICAgICB4PSJsb2dDUE0gYnkgU2FsbW9uIiwgeT0ibG9nQ1BNIGJ5IFNob2FsIikKZ2dwcmludChwKQpgYGAK