Preliminary Setup

First we load the necessary libraries, along with a set of utility functions.

library(here)
library(stringr)
library(magrittr)
library(Matrix)
library(lme4)
library(SummarizedExperiment)
library(dplyr)
library(edgeR)
library(limma)
library(ggplot2)
library(scales)
library(GGally)
library(ggalt)
library(reshape2)
library(assertthat)
library(ggfortify)
library(broom)
library(codingMatrices)
library(variancePartition)

library(doParallel)
ncores <- getOption("mc.cores", default=parallel::detectCores(logical = FALSE))
registerDoParallel(cores=ncores)
library(BiocParallel)
BiocParallel:::.registry_init() # Workaround for https://github.com/Bioconductor/BiocParallel/issues/65
register(DoparParam())

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

We also set the output directory for all plot files.

plotdir <- here("plots/RNA-seq", params$dataset)
dir.create(plotdir, recursive = TRUE, showWarnings = FALSE)

We also set the random seed for reproducibility:

set.seed(1986)

Data Loading and Preprocessing

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

sexpfile <- here("saved_data", sprintf("SummarizedExperiment_rnaseq_%s.RDS", params$dataset))
sexp <- readRDS(sexpfile)
colnames(sexp) <- colData(sexp)$SampleName

We extract the sample metadata from the SummarizedExperiment. We also tell R to use a coding matrix for each factor that puts the intercept at the mean of all factor levels when incorporating it into a design matrix.

sample.table <- colData(sexp) %>%
    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=""))
for (i in names(sample.table)) {
    if (is.factor(sample.table[[i]]) && nlevels(sample.table[[i]]) > 1) {
        contrasts(sample.table[[i]]) <- code_control_named(levels(sample.table[[i]]))
    }
}

Next we extract the count matrix from the 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")
if (all(libtype.assayNames %in% assayNames(sexp))) {
    message("Selecting stranded counts for each sample")
    sample.table %<>% mutate(count_type=libtype.assayNames[libType])
    assay(sexp, "unstranded.counts") <- assay(sexp, "counts")
    assay(sexp, "counts") <- lapply(seq_len(nrow(sample.table)), function(i) {
        message("Using ", sample.table[i,]$count_type, " for ", colnames(sexp)[i])
        assay(sexp, 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.

if (all(libtype.assayNames %in% assayNames(sexp))) {
    total.counts <- sexp %>% 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))))
}

Exploratory Analysis

Normalization & Filtering

Now we create a DGEList from the counts.

## 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

Next we take care of the initial scaling normalization for sequencing depth and composition bias. We also discard any genes with all zero counts, since there is no meaningful analysis that can be done with these genes.

## Remove all genes with zero counts in all samples
nonzero <- rowSums(dge$counts) > 0
dge %<>% .[nonzero,]
dge %<>% calcNormFactors

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

if ("length" %in% assayNames(sexp)) {
    normMat <- assay(sexp, "length") %>% divide_by(exp(rowMeans(log(.)))) %>%
        .[nonzero,]
    normCounts <- dge$counts/normMat
    lib.offsets <- log(calcNormFactors(normCounts)) + log(colSums(normCounts))
    dge$offset <- t(t(log(normMat)) + lib.offsets)
}

We plot the distribution of average log2 CPM values to verify that our chosen presence threshold is appropriate. The distribution is expected to be bimodal, with a low-abundance peak representing non-expressed genes and a high-abundance peak representing expressed genes. The chosen threshold should separate the two peaks of the bimodal distribution.

a <- aveLogCPMWithOffset(dge)
avelogcpm.presence.threshold <- -1

p <- list(
    Histogram=ggplot(data.frame(logCPM=a)) +
        aes(x=logCPM) +
        geom_histogram(aes(y=100*(..count..)/sum(..count..)), binwidth=0.25, boundary=0) +
        geom_vline(xintercept=avelogcpm.presence.threshold, color="red", linetype="dashed") +
        xlab("Average logCPM") + ylab("Percent of genes in bin") +
        coord_cartesian(xlim=quantile(a, c(0, 0.995)), ylim=c(0,10)) +
        labs(title="Average gene LogCPM distribution",
             subtitle="for genes with at least 1 read") +
        theme(plot.caption = element_text(hjust = 0)),
    ECDF=ggplot(fortify(ecdf(a))) +
        aes(x=x, y=y*100) +
        geom_step() +
        geom_vline(xintercept=avelogcpm.presence.threshold, color="red", linetype="dashed") +
        xlab("Average logCPM") + ylab("Percent of genes with smaller average logCPM") +
        coord_cartesian(xlim=quantile(a, c(0, 0.995))) +
        labs(title="Empirical Cumulative Distribution Function of gene LogCPM values",
             subtitle="for genes with at least 1 read") +
        theme(plot.caption = element_text(hjust = 0)))

ggprint(p)

The red dashed line in each plot indicates the chosen presence threshold. We now subset the DGEList to only those genes above the threshold.

dge %<>% .[aveLogCPMWithOffset(.) >= avelogcpm.presence.threshold,]

Variance & Heteroskedasticity

Now we estimate the dispersions for each gene, to get an idea of what the variability of this data set is like. In order to evaluate the effect of empirical Bayes shrinkage on the dispersions, we estimate the gene dispersions in 3 different ways: once with no inter-gene information sharing, once with ordinary shrinkage, and once with robust shrinkage, which reduces the strength of shrinkage for outlier genes whose dispersion is farthest away from the trend.

design <- model.matrix(~0 + group + donor_id, sample.table)
colnames(design) %<>% str_replace("^group", "")

dge %<>% estimateDisp(design, robust=TRUE)

message("Common dispersion: ", dge$common.dispersion)
Common dispersion: 0.634358039982791
message("BCV: ", sqrt(dge$common.dispersion))
BCV: 0.796465969130377
dge.with.eBayes <- dge %>% estimateDisp(design, robust=FALSE)
dge.with.robust.eBayes <- dge %>% estimateDisp(design, robust=TRUE)
dge.without.eBayes <- dge %>% estimateDisp(design, prior.df=0)

We now plot all 3 dispersion estimates, along with the overall average and estimated trend. Each plot includes the points from the previous plots in lighter colors for comparison.

disptable <- data.frame(
    logCPM=dge.without.eBayes$AveLogCPM,
    CommonBCV=dge.with.eBayes$common.dispersion %>% sqrt,
    TrendBCV=dge.with.eBayes$trended.dispersion %>% sqrt,
    GeneWiseBCV=dge.without.eBayes$tagwise.dispersion %>% sqrt,
    eBayesBCV=dge.with.eBayes$tagwise.dispersion %>% sqrt,
    RobustBCV=dge.with.robust.eBayes$tagwise.dispersion %>% sqrt) %>%
    cbind(dge$genes)

## Reduce the number of points to plot for each line for performance
## reasons
npoints <- c(Common=2, Trend=500)
disp.line.table <-
    disptable %>%
    select(logCPM, TrendBCV, CommonBCV) %>%
    melt(id.vars="logCPM", variable.name="DispType", value.name = "BCV") %>%
    mutate(DispType=str_replace(DispType, "BCV$", "")) %>%
    group_by(DispType) %>%
    do({
        spline(x=.$logCPM, y=.$BCV, n=npoints[.$DispType[1]]) %>% data.frame(logCPM=.$x, BCV=.$y)
    })

baseplot <- ggplot(disptable) +
    aes(x=logCPM)
raw.disp.plot <- baseplot +
    geom_point(aes(y=GeneWiseBCV), size=0.1, color="black") +
    geom_line(data=disp.line.table, aes(x=logCPM, y=BCV, group=DispType), color="white", size=1.5, alpha=0.5) +
    geom_line(data=disp.line.table, aes(x=logCPM, y=BCV, linetype=DispType), color="darkred", size=0.5) +
    scale_linetype_manual(name="Dispersion Type", values=c(Trend="solid", Common="dashed")) +
    ylab("Biological coefficient of variation") +
    ggtitle("BCV plot (Raw dispersions)")

eBayes.disp.plot <- baseplot +
    geom_point(aes(y=GeneWiseBCV), size=0.4, color="gray") +
    geom_point(aes(y=eBayesBCV), size=0.1, color="darkblue") +
    geom_line(data=disp.line.table, aes(x=logCPM, y=BCV, group=DispType), color="white", size=1.5, alpha=0.5) +
    geom_line(data=disp.line.table, aes(x=logCPM, y=BCV, linetype=DispType), color="darkred", size=0.5) +
    scale_linetype_manual(name="Dispersion Type", values=c(Trend="solid", Common="dashed")) +
    ylab("Biological coefficient of variation") +
    ggtitle("BCV plot (Raw & squeezed dispersions)")

robust.eBayes.disp.plot <- baseplot +
    geom_point(aes(y=GeneWiseBCV), size=0.4, color="gray") +
    geom_point(aes(y=eBayesBCV), size=0.4, color="deepskyblue") +
    geom_point(aes(y=RobustBCV), size=0.1, color="darkgreen") +
    geom_line(data=disp.line.table, aes(x=logCPM, y=BCV, group=DispType), color="white", size=1.5, alpha=0.5) +
    geom_line(data=disp.line.table, aes(x=logCPM, y=BCV, linetype=DispType), color="darkred", size=0.5) +
    scale_linetype_manual(name="Dispersion Type", values=c(Trend="solid", Common="dashed")) +
    ylab("Biological coefficient of variation") +
    ggtitle("BCV plot (Raw & squeezed & robust dispersions)")

p <- list(raw.disp.plot, eBayes.disp.plot, robust.eBayes.disp.plot)
ggprint(p)

Next, we use limma’s sample weight calculating methods to investigate possible quality issues. To confirm our results, we also split the samples into treatment groups and use edgeR to estimate the dispersion within each group. This is a crude method, and these group-specific dispersion estimate would be too unstable for use in a differential expression analysis, but comparing the overall mean dispersion for each sample to the sample quality weights determined by limma provides a useful sanity check.

elist.w <- voomWithQualityWeightsAndOffset(dge, design)
dbg <- estimateDispByGroup(dge, sample.table$group, sample.table$batch)

To see whether the weights are correlated with specific experimental factors, we create a boxplot of the weights and group dispersions against each relevant covariate.

covars <- sample.table %>% dplyr::select(group, time_point, donor_id, batch, cell_type)
qcmetrics <- data.frame(Weight=elist.w$sample.weights,
                        GroupBCV=sapply(dbg, `[[`, "common.dispersion")[as.character(covars$group)])

p <- ggduo.dataXY(covars, qcmetrics %>% transmute(Log2Weight=log2(Weight), GroupBCV)) +
  ggtitle("Weights and dispersions by group") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust=0.5))
ggprint(p)

We also make plots of the individual weights against each covariate, and compute the ANOVA p-value for the relationship between the weights and each covariate separately.

(Note for later: It would be good to try all covariates in the same model as random effects.)

awdf <- data.frame(covars, qcmetrics)
anovas <- lapply(colnames(covars), function(x) {
    formula <- as.formula(str_c("log2(Weight) ~ ", x))
    lm(formula, awdf) %>% aov %>% tidy %>% filter(term == x)
}) %>%
    do.call(what=rbind) %>%
    mutate(padj=p.adjust(p.value, method="BH"))
pvals <- anovas %$% setNames(p.value, term)
aw.plot.base <- ggplot(awdf) +
    aes(y=Weight) +
    scale_y_continuous(trans=log_trans(2)) +
    geom_dotplot(binaxis = "y", stackdir="center", binwidth=0.1)
p <- lapply(colnames(covars), function(x) {
    pretty.covar.name <- x %>% str_replace_all("_", " ") %>% str_to_title
    aw.plot.base + aes_string(x=x) +
        labs(title=str_c("Array weights by ", pretty.covar.name),
             subtitle=sprintf("ANOVA p-value = %0.3g", pvals[x]))
})
ggprint(p)

Batch correction

Next we perform several methods of batch correction on the voom-transformed data. First, we try direct batch subtraction using limma’s removeBatchEffect function, which fits a linear model with the batches as sum-to-zero coefficients and then subtracts the batch coefficients from the data.

elist.bc <- elist.w
# Batch is confounded with time point, so we leave it out of the design for this step
design.NoTime <- model.matrix(~ cell_type + donor_id, sample.table)
elist.bc$E %<>%
    removeBatchEffect(batch=sample.table %$% batch:donor_id, design=design.NoTime,
                      weights=elist.bc$weights)
Coefficients not estimable: batch5 batch6 batch7 
Warning: Partial NA coefficients for 17029 probe(s)

Second, we use ComBat, which performs empirical Bayes shrinkage of the batch correction parameters. First, we run ComBat in parametric prior mode in order to produce a diagnostic plot of the priors. Note that we use ComBat here to subtract both the batch and donor effects, so that the MDS plots will be more reflective of the desired biological effects.

# Right now we're just running ComBat to produce the plot, so we discard the output.
invisible(capture.output(ComBat(elist.w$E, batch=sample.table$batch, mod=design.NoTime, par.prior=TRUE, prior.plots = TRUE)))
Found2batches
Adjusting for4covariate(s) or covariate level(s)
Fitting L/S model and finding priors
Finding parametric adjustments
Adjusting the Data

cairo_pdf(file.path(plotdir, "rnaseq-ComBat-qc.pdf"))
# Run the same thing again to output the plot to the PDF file
invisible(ComBat(elist.w$E, batch=sample.table$batch, mod=design.NoTime, par.prior=TRUE, prior.plots = TRUE))
Standardizing Data across genes
invisible(dev.off())

Since the parametric fit for the variance is not a good match for the empirical distribution, we now perform the actual batch correction using the non-parametric mode of ComBat.

# Now perform the actual batch correction using non-parametric prior
design.cb <- model.matrix(~cell_type, sample.table)
elist.cb <- elist.w
elist.cb$E %<>% ComBat(batch=sample.table$batch, mod=design.NoTime, par.prior=FALSE)
Found2batches
Adjusting for4covariate(s) or covariate level(s)
Standardizing Data across genes
Fitting L/S model and finding priors
Finding nonparametric adjustments
Adjusting the Data

For each of the batch correction methods, we compute the sample distance matrix using multidimensional scaling and plot the first 3 principal coordinates. We reflect all the principal coordinates so as to make the mean of the Naive D0 samples negative in all dimensions, so that MDS plots have a greater chance of being oriented consistently with each other.

naive.d0.samples <- sample.table$group == "NaiveD0"
dmat <- suppressPlot(plotMDS(elist.w)$distance.matrix) %>% as.dist
mds <- cmdscale(dmat, k=attr(dmat, "Size") - 1, eig=TRUE)
mds$points %<>% scale(center=FALSE, scale=-sign(colMeans(.[naive.d0.samples,]))) %>%
    add.numbered.colnames("Dim") %>% data.frame(sample.table, .)
dmat.bc <- suppressPlot(plotMDS(elist.bc)$distance.matrix) %>% as.dist
mds.bc <- cmdscale(dmat.bc, k=attr(dmat, "Size") - 1, eig=TRUE)
mds.bc$points %<>% scale(center=FALSE, scale=sign(colMeans(.[naive.d0.samples,]))) %>%
    add.numbered.colnames("Dim") %>% data.frame(sample.table, .)
dmat.cb <- suppressPlot(plotMDS(elist.cb)$distance.matrix) %>% as.dist
mds.cb <- cmdscale(dmat.cb, k=attr(dmat, "Size") - 1, eig=TRUE)
mds.cb$points %<>% scale(center=FALSE, scale=sign(colMeans(.[naive.d0.samples,]))) %>%
     add.numbered.colnames("Dim") %>% data.frame(sample.table, .)

ggmdsbatch <- function(dat, dims=1:2) {
    if (length(dims) == 1) {
        dims <- dims + c(0,1)
    }
    assert_that(length(dims) == 2)
    ggplot(dat) +
        aes_string(x=str_c("Dim", dims[1]), y=str_c("Dim", dims[2])) +
        aes(color=batch, label=SampleName) +
        geom_text() +
        scale_x_continuous(expand=c(0.15, 0)) +
        coord_equal()
}

p <- list(
    ggmdsbatch(mds$points) +
        labs(title="limma voom Principal Coordinates 1 & 2",
             subtitle="No batch correction"),
    ggmdsbatch(mds.bc$points) +
        labs(title="limma voom Principal Coordinates 1 & 2",
             subtitle="After naive batch subtraction"),
    ggmdsbatch(mds.cb$points) +
        ggtitle("limma voom Principal Coordinates 1 & 2",
             subtitle="After ComBat batch correction"),
    ggmdsbatch(mds$points, dims=2:3) +
        ggtitle("limma voom Principal Coordinates 2 & 3",
             subtitle="No batch correction"),
    ggmdsbatch(mds.bc$points, dims=2:3) +
        ggtitle("limma voom Principal Coordinates 2 & 3",
             subtitle="After naive batch subtraction"),
    ggmdsbatch(mds.cb$points, dims=2:3) +
        ggtitle("limma voom Principal Coordinates 2 & 3",
             subtitle="After ComBat batch correction"))
ggprint(p)

The naive batch subtraction seems to leave one donor as an outlier for unclear reasons, while ComBat avoids this artifact, instead yielding abiologically more plausible MDS plot.

MDS Plots

Choosing ComBat as the best-looking batch correction, we make more MDS plots for this data, this time plotting the first 5 PCs and adding the experimental information to the plot as colors and shapes.

xlims <- range(unlist(mds.cb$points[c("Dim1", "Dim2")]))
ylims <- range(unlist(mds.cb$points[c("Dim2", "Dim3")]))
pbase <- ggplot(mds.cb$points) +
    aes(x=Dim1, y=Dim2, label=SampleName, color=batch, fill=time_point, shape=cell_type, linetype=donor_id, group=cell_type:donor_id) +
    geom_encircle(aes(group=time_point:cell_type, color=NULL), s_shape=0.75, expand=0.05, color=NA, alpha=0.2) +
    geom_path(color=hcl(c=0, l=45), aes(color=NULL)) +
    geom_point(size=4) +
    scale_shape_manual(values=c(Naive=21, Memory=24)) +
    scale_color_manual(values=col2hcl(c(B1="green", B2="magenta"), l=80)) +
    scale_fill_hue(l=55) +
    scale_linetype_manual(values=c("solid", "dashed", "dotdash", "twodash")) +
    guides(colour = guide_legend(override.aes = list(shape = 21)),
           fill = guide_legend(override.aes = list(shape = 21)),
           shape = guide_legend(override.aes = list(color=hcl(c=0, l=80), fill=hcl(c=0, l=55)))) +
    labs(title="limma voom Principal Coordinates 1 & 2",
         subtitle=" (after ComBat batch correction)") +
    coord_equal(xlim=xlims, ylim=ylims)
p <- list(PC12=pbase,
          PC23=pbase +
              aes(x=Dim2, y=Dim3) +
              labs(title="limma voom Principal Coordinates 2 & 3"),
          PC34=pbase +
              aes(x=Dim3, y=Dim4) +
              labs(title="limma voom Principal Coordinates 3 & 4"),
          PC45=pbase +
              aes(x=Dim4, y=Dim5) +
              labs(title="limma voom Principal Coordinates 4 & 5"))
ggprint(p)

Variance Partitioning analysis

To further investigate the sources of variance within the data, we can use the variancePartition package. We fit the model to the uncorrected data and the two corrected data sets (simple batch subtraction and ComBat) so that we can see how the percent of variance explained is affected by batch correction. We use random effects for the factors in the model since batch and time point are confounded.

# Fit all factors as random effects, since group and batch are confounded
vp.formula <- ~ (1|group) + (1|donor_id) + (1|batch)
elists <- list(NoBC=elist.w, BatSub=elist.bc, ComBat=elist.cb)
# Function is already parallelized, so don't call it in parallel
varParts <- mapply(function(...) try(fitExtractVarPartModel(...)), 
                   exprObj = elists, 
                   MoreArgs = list(
                       formula = vp.formula, 
                       data=sample.table),
                   SIMPLIFY = FALSE)
Projected run time: ~ 191 min 
Projected run time: ~ 4 min 
Projected run time: ~ 4 min 
# Collapse SVs to a single column
varTables <- list()
for (i in names(varParts)) {
    if (is(varParts[[i]], "try-error")) {
        message("Could not run variancePartition for ", i, ", probably due to collinearity of covariates.")
    } else {
        assert_that(is(varParts[[i]], "varPartResults"))
        x <- as(varParts[[i]], "data.frame")
        x.sv <- x %>% select(dplyr::matches("^SV\\d+$"))
        if (ncol(x.sv) > 0) {
            x.nosv <- x[setdiff(colnames(x), colnames(x.sv))]
            x <- data.frame(x.nosv, SV=rowSums(x.sv))
        }
        varTables[[i]] <- cbind(select(x, -Residuals), select(x, Residuals))
    }
}

The variancePartition function failed to run for the model that included both known covariates and surrogate variables. This is expected if the surrogate variables are highly correlated with any of the known covariates, which we have already observed is the case for donor ID.

p <- list()
for (i in names(varTables)) {
    incl <- str_replace_all(i, "_and_", " + ")
    p[[i]] <- plotVarPart(varTables[[i]]) +
        labs(title=str_c("Variance Partitions, ", incl))
}
ggprint(p)

The results seen here are consistent with what can be seen qualitatively from the MDS plots. With no batch correction, batch effects are a significant contributor to overall variance, which is problematic since batch and time point are confounded. With simple batch subtraction. recall that the MDS plots showed an outlier donor. This is reflected here in the very large contribution of donor ID to the variance of many genes. Lastly, the variance partition plot for ComBat looks quite reasonable, with most of the variance for a majority of genes explained by group (i.e. the combination of time point and cell type) and some smaller part explained by donor ID.

LS0tCnRpdGxlOiAiRXhwbG9yYXRpb24gb2YgQ0Q0IFJOQS1TZXEgRGF0YXNldCIKc3VidGl0bGU6ICJgciBwYXN0ZTAoJ1VzaW5nIERhdGFzZXQgJywgcGFyYW1zJGRhdGFzZXQpYCIKYXV0aG9yOiAiUnlhbiBDLiBUaG9tcHNvbiIKZGF0ZTogImByIGdzdWIoJ1xccysnLCAnICcsIGZvcm1hdChTeXMudGltZSgpLCAnJUIgJWUsICVZJykpYCIKb3V0cHV0OiBodG1sX25vdGVib29rCnBhcmFtczoKICBkYXRhc2V0OgogICAgdmFsdWU6ICJzYWxtb25faGczOC5hbmFseXNpc1NldF9rbm93bkdlbmUiCi0tLQoKIyBQcmVsaW1pbmFyeSBTZXR1cAoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0UsIGNhY2hlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHJldGluYT0yLCBjYWNoZT1UUlVFLCBhdXRvZGVwPVRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZS5leHRyYSA9IGxpc3QocGFyYW1zPXBhcmFtcyksCiAgICAgICAgICAgICAgICAgICAgICBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04LAogICAgICAgICAgICAgICAgICAgICAgY2FjaGUucGF0aCA9IHBhc3RlMCgKICAgICAgICAgICAgICAgICAgICAgICAgICBoZXJlOjpoZXJlKCJjYWNoZSIsICJybmFzZXEtZXhwbG9yZSIsIHBhcmFtcyRkYXRhc2V0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAuUGxhdGZvcm0kZmlsZS5zZXApKQpgYGAKCkZpcnN0IHdlIGxvYWQgdGhlIG5lY2Vzc2FyeSBsaWJyYXJpZXMsIGFsb25nIHdpdGggYSBzZXQgb2YgdXRpbGl0eSBmdW5jdGlvbnMuCgpgYGB7ciBsb2FkX3BhY2thZ2VzLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0KbGlicmFyeShoZXJlKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoTWF0cml4KQpsaWJyYXJ5KGxtZTQpCmxpYnJhcnkoU3VtbWFyaXplZEV4cGVyaW1lbnQpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZWRnZVIpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KGdnYWx0KQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KGFzc2VydHRoYXQpCmxpYnJhcnkoZ2dmb3J0aWZ5KQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KGNvZGluZ01hdHJpY2VzKQpsaWJyYXJ5KHZhcmlhbmNlUGFydGl0aW9uKQoKbGlicmFyeShkb1BhcmFsbGVsKQpuY29yZXMgPC0gZ2V0T3B0aW9uKCJtYy5jb3JlcyIsIGRlZmF1bHQ9cGFyYWxsZWw6OmRldGVjdENvcmVzKGxvZ2ljYWwgPSBGQUxTRSkpCnJlZ2lzdGVyRG9QYXJhbGxlbChjb3Jlcz1uY29yZXMpCmxpYnJhcnkoQmlvY1BhcmFsbGVsKQpCaW9jUGFyYWxsZWw6OjoucmVnaXN0cnlfaW5pdCgpICMgV29ya2Fyb3VuZCBmb3IgaHR0cHM6Ly9naXRodWIuY29tL0Jpb2NvbmR1Y3Rvci9CaW9jUGFyYWxsZWwvaXNzdWVzLzY1CnJlZ2lzdGVyKERvcGFyUGFyYW0oKSkKCnNvdXJjZShoZXJlKCJzY3JpcHRzL3V0aWxpdGllcy5SIikpCmBgYAoKV2UgYWxzbyBzZXQgdGhlIG91dHB1dCBkaXJlY3RvcnkgZm9yIGFsbCBwbG90IGZpbGVzLgoKYGBge3Igc2V0X3Bsb3RkaXJ9CnBsb3RkaXIgPC0gaGVyZSgicGxvdHMvUk5BLXNlcSIsIHBhcmFtcyRkYXRhc2V0KQpkaXIuY3JlYXRlKHBsb3RkaXIsIHJlY3Vyc2l2ZSA9IFRSVUUsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQpgYGAKCldlIGFsc28gc2V0IHRoZSByYW5kb20gc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5OgoKYGBge3Igc2V0X3JhbmRvbV9zZWVkfQpzZXQuc2VlZCgxOTg2KQpgYGAKCiMgRGF0YSBMb2FkaW5nIGFuZCBQcmVwcm9jZXNzaW5nCgpOb3cgd2UgbG9hZCB0aGUgUk5BLXNlcSBkYXRhIHNldCBmcm9tIGFuIFJEUyBmaWxlIGNvbnRhaW5pbmcgYSBTdW1tYXJpemVkRXhwZXJpbWVudCBvYmplY3QgYW5kIGVkaXQgaXQgdG8gdXNlIHRoZSBzYW1wbGUgbmFtZXMgYXMgY29sdW1uIG5hbWVzLgoKYGBge3IgbG9hZF9kYXRhfQpzZXhwZmlsZSA8LSBoZXJlKCJzYXZlZF9kYXRhIiwgc3ByaW50ZigiU3VtbWFyaXplZEV4cGVyaW1lbnRfcm5hc2VxXyVzLlJEUyIsIHBhcmFtcyRkYXRhc2V0KSkKc2V4cCA8LSByZWFkUkRTKHNleHBmaWxlKQpjb2xuYW1lcyhzZXhwKSA8LSBjb2xEYXRhKHNleHApJFNhbXBsZU5hbWUKYGBgCgpXZSBleHRyYWN0IHRoZSBzYW1wbGUgbWV0YWRhdGEgZnJvbSB0aGUgU3VtbWFyaXplZEV4cGVyaW1lbnQuIFdlIGFsc28gdGVsbCBSIHRvIHVzZSBhIGNvZGluZyBtYXRyaXggZm9yIGVhY2ggZmFjdG9yIHRoYXQgcHV0cyB0aGUgaW50ZXJjZXB0IGF0IHRoZSBtZWFuIG9mIGFsbCBmYWN0b3IgbGV2ZWxzIHdoZW4gaW5jb3Jwb3JhdGluZyBpdCBpbnRvIGEgZGVzaWduIG1hdHJpeC4KCmBgYHtyIGV4dHJhY3Rfc2FtcGxlbWV0YX0Kc2FtcGxlLnRhYmxlIDwtIGNvbERhdGEoc2V4cCkgJT4lCiAgICBhcy5kYXRhLmZyYW1lICU+JSBhdXRvRmFjdG9yaXplICU+JQogICAgcmVuYW1lKGJhdGNoPXRlY2huaWNhbF9iYXRjaCkgJT4lCiAgICBtdXRhdGUodGltZV9wb2ludD1mYWN0b3IoZGF5c19hZnRlcl9hY3RpdmF0aW9uKSAlPiUgYGxldmVsczwtYChzcHJpbnRmKCJEJXMiLCBsZXZlbHMoLikpKSwKICAgICAgICAgICBncm91cD1pbnRlcmFjdGlvbihjZWxsX3R5cGUsIHRpbWVfcG9pbnQsIHNlcD0iIikpCmZvciAoaSBpbiBuYW1lcyhzYW1wbGUudGFibGUpKSB7CiAgICBpZiAoaXMuZmFjdG9yKHNhbXBsZS50YWJsZVtbaV1dKSAmJiBubGV2ZWxzKHNhbXBsZS50YWJsZVtbaV1dKSA+IDEpIHsKICAgICAgICBjb250cmFzdHMoc2FtcGxlLnRhYmxlW1tpXV0pIDwtIGNvZGVfY29udHJvbF9uYW1lZChsZXZlbHMoc2FtcGxlLnRhYmxlW1tpXV0pKQogICAgfQp9CmBgYAoKTmV4dCB3ZSBleHRyYWN0IHRoZSBjb3VudCBtYXRyaXggZnJvbSB0aGUgU3VtbWFyaXplZEV4cGVyaW1lbnQuIFRoaXMgaXMgbWFkZSBtb3JlIGNvbXBsaWNhdGVkIHRoYW4gdXN1YWwgYnkgdGhlIGZhY3QgdGhhdCBoYWxmIG9mIHRoZSBzYW1wbGVzIHdlcmUgc2VxdWVuY2VkIHdpdGggYSBkaWZmZXJlbnQgcHJvdG9jb2wgdGhhbiB0aGUgb3RoZXIgaGFsZiwgYW5kIHRoZSB0d28gcHJvdG9jb2xzIHByb2R1Y2UgcmVhZHMgd2l0aCBvcHBvc2l0ZSBzdHJhbmQgb3JpZW50YXRpb25zLiBIZW5jZSwgd2UgbmVlZCB0aGUgc2Vuc2UgY291bnRzIGZvciBoYWxmIG9mIHRoZSBzYW1wbGVzIGFuZCB0aGUgYW50aXNlbnNlIGNvdW50cyBmb3IgdGhlIG90aGVyIGhhbGYuIFRoZSBhcHByb3ByaWF0ZSBzdHJhbmQgZm9yIGVhY2ggc2FtcGxlIGlzIGRvY3VtZW50ZWQgaW4gdGhlIGBsaWJUeXBlYCBjb2x1bW4gb2YgdGhlIHNhbXBsZSBtZXRhZGF0YSwgdXNpbmcgdGhlIGxpYnJhcnkgdHlwZSBhYmJyZXZpYXRpb25zIFtlc3RhYmxpc2hlZCBieSBTYWxtb25dKGh0dHA6Ly9zYWxtb24ucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0L3NhbG1vbi5odG1sI3doYXQtcy10aGlzLWxpYnR5cGUpLgoKRm9yIFN1bW1hcml6ZWRFeHBlcmltZW50cyBnZW5lcmF0ZWQgdXNpbmcgdHhpbXBvcnQsIHRoaXMgc3RlcCBpcyBza2lwcGVkLCBzaW5jZSB0aGUgcXVhbnRpZmljYXRpb24gdG9vbCBoYXMgYWxyZWFkeSBiZWVuIHRvbGQgd2hpY2ggc3RyYW5kIHRvIHVzZSBhbmQgb25seSBwcm92aWRlcyBjb3VudHMgZm9yIHRoYXQgc3RyYW5kLgoKYGBge3IgZXh0cmFjdF9jb3VudHN9CmxpYnR5cGUuYXNzYXlOYW1lcyA8LSBjKFNGPSJzZW5zZS5jb3VudHMiLCBTUj0iYW50aXNlbnNlLmNvdW50cyIpCmlmIChhbGwobGlidHlwZS5hc3NheU5hbWVzICVpbiUgYXNzYXlOYW1lcyhzZXhwKSkpIHsKICAgIG1lc3NhZ2UoIlNlbGVjdGluZyBzdHJhbmRlZCBjb3VudHMgZm9yIGVhY2ggc2FtcGxlIikKICAgIHNhbXBsZS50YWJsZSAlPD4lIG11dGF0ZShjb3VudF90eXBlPWxpYnR5cGUuYXNzYXlOYW1lc1tsaWJUeXBlXSkKICAgIGFzc2F5KHNleHAsICJ1bnN0cmFuZGVkLmNvdW50cyIpIDwtIGFzc2F5KHNleHAsICJjb3VudHMiKQogICAgYXNzYXkoc2V4cCwgImNvdW50cyIpIDwtIGxhcHBseShzZXFfbGVuKG5yb3coc2FtcGxlLnRhYmxlKSksIGZ1bmN0aW9uKGkpIHsKICAgICAgICBtZXNzYWdlKCJVc2luZyAiLCBzYW1wbGUudGFibGVbaSxdJGNvdW50X3R5cGUsICIgZm9yICIsIGNvbG5hbWVzKHNleHApW2ldKQogICAgICAgIGFzc2F5KHNleHAsIHNhbXBsZS50YWJsZVtpLF0kY291bnRfdHlwZSAlPiUgYXMuY2hhcmFjdGVyKVssaV0KICAgIH0pICU+JSBkby5jYWxsKHdoYXQ9Y2JpbmQpCn0KYGBgCgpBcyBhIHNhbml0eSBjaGVjaywgd2UgbWFrZSBzdXJlIHRoYXQgd2Ugc2VsZWN0ZWQgdGhlIHN0cmFuZCBzZW5zZSB3aXRoIHRoZSBoaWdoZXIgdG90YWwgY291bnQgZm9yIGVhY2ggc2FtcGxlLgoKYGBge3Igc3RyYW5kX3Nhbml0eV9jaGVja30KaWYgKGFsbChsaWJ0eXBlLmFzc2F5TmFtZXMgJWluJSBhc3NheU5hbWVzKHNleHApKSkgewogICAgdG90YWwuY291bnRzIDwtIHNleHAgJT4lIGFzc2F5cyAlPiUgc2FwcGx5KGNvbFN1bXMpICU+JSBkYXRhLmZyYW1lICU+JQogICAgICAgIG11dGF0ZShTYW1wbGVOYW1lPXJvdy5uYW1lcyguKSkgJT4lCiAgICAgICAgaW5uZXJfam9pbihzYW1wbGUudGFibGUsIGJ5PSJTYW1wbGVOYW1lIikKICAgIHRvdGFsLmNvdW50cyAlJCUgaW52aXNpYmxlKGFzc2VydF90aGF0KGFsbChjb3VudHMgPT0gcG1heChzZW5zZS5jb3VudHMsIGFudGlzZW5zZS5jb3VudHMpKSkpCn0KYGBgCgojIEV4cGxvcmF0b3J5IEFuYWx5c2lzCgojIyBOb3JtYWxpemF0aW9uICYgRmlsdGVyaW5nCgpOb3cgd2UgY3JlYXRlIGEgREdFTGlzdCBmcm9tIHRoZSBjb3VudHMuCgpgYGB7ciBwcmVwYXJlX2RnZWxpc3R9CiMjIEV4dHJhY3QgZ2VuZSBtZXRhZGF0YSBhbmQgY29sYXBzZSBsaXN0cwphbGwuZ2VuZS5tZXRhIDwtIG1jb2xzKHNleHApICU+JSBhcy5kYXRhLmZyYW1lCiMgQ29udmVydCBsaXN0IGNvbHVtbnMgdG8gY2hhcmFjdGVyIHZlY3RvcnMKYWxsLmdlbmUubWV0YVtdICU8PiUgbGFwcGx5KGZ1bmN0aW9uKHgpIGlmIChpcy5saXN0KHgpKSBzYXBwbHkoeCwgc3RyX2MsIGNvbGxhcHNlPSIsIikgZWxzZSB4KQpkZ2UgPC0gREdFTGlzdChjb3VudHM9YXNzYXkoc2V4cCwgImNvdW50cyIpKQpkZ2UkZ2VuZXMgPC0gYWxsLmdlbmUubWV0YQpgYGAKTmV4dCB3ZSB0YWtlIGNhcmUgb2YgdGhlIGluaXRpYWwgc2NhbGluZyBub3JtYWxpemF0aW9uIGZvciBzZXF1ZW5jaW5nIGRlcHRoIGFuZCBjb21wb3NpdGlvbiBiaWFzLiBXZSBhbHNvIGRpc2NhcmQgYW55IGdlbmVzIHdpdGggYWxsIHplcm8gY291bnRzLCBzaW5jZSB0aGVyZSBpcyBubyBtZWFuaW5nZnVsIGFuYWx5c2lzIHRoYXQgY2FuIGJlIGRvbmUgd2l0aCB0aGVzZSBnZW5lcy4KCmBgYHtyIGluaXRpYWxfbm9ybWFsaXphdGlvbn0KIyMgUmVtb3ZlIGFsbCBnZW5lcyB3aXRoIHplcm8gY291bnRzIGluIGFsbCBzYW1wbGVzCm5vbnplcm8gPC0gcm93U3VtcyhkZ2UkY291bnRzKSA+IDAKZGdlICU8PiUgLltub256ZXJvLF0KZGdlICU8PiUgY2FsY05vcm1GYWN0b3JzCmBgYAoKSW4gYWRkaXRpb24sIGlmIHRoZXJlIGlzIGEgbGVuZ3RoIGFzc2F5LCB3ZSBhbHNvIHVzZSB0aGF0IHRvIGRlcml2ZSBhbiBvZmZzZXQgbWF0cml4IHRoYXQgY29ycmVjdHMgZm9yIHNhbXBsZS1zcGVjaWZpYyBiaWFzZXMgZGV0ZWN0ZWQgYnkgU2FsbW9uIG9yIEthbGxpc3RvLgoKYGBge3IgZ2VuZXJhdGVfb2Zmc2V0c30KaWYgKCJsZW5ndGgiICVpbiUgYXNzYXlOYW1lcyhzZXhwKSkgewogICAgbm9ybU1hdCA8LSBhc3NheShzZXhwLCAibGVuZ3RoIikgJT4lIGRpdmlkZV9ieShleHAocm93TWVhbnMobG9nKC4pKSkpICU+JQogICAgICAgIC5bbm9uemVybyxdCiAgICBub3JtQ291bnRzIDwtIGRnZSRjb3VudHMvbm9ybU1hdAogICAgbGliLm9mZnNldHMgPC0gbG9nKGNhbGNOb3JtRmFjdG9ycyhub3JtQ291bnRzKSkgKyBsb2coY29sU3Vtcyhub3JtQ291bnRzKSkKICAgIGRnZSRvZmZzZXQgPC0gdCh0KGxvZyhub3JtTWF0KSkgKyBsaWIub2Zmc2V0cykKfQpgYGAKCldlIHBsb3QgdGhlIGRpc3RyaWJ1dGlvbiBvZiBhdmVyYWdlIGxvZzIgQ1BNIHZhbHVlcyB0byB2ZXJpZnkgdGhhdCBvdXIgY2hvc2VuIHByZXNlbmNlIHRocmVzaG9sZCBpcyBhcHByb3ByaWF0ZS4gVGhlIGRpc3RyaWJ1dGlvbiBpcyBleHBlY3RlZCB0byBiZSBiaW1vZGFsLCB3aXRoIGEgbG93LWFidW5kYW5jZSBwZWFrIHJlcHJlc2VudGluZyBub24tZXhwcmVzc2VkIGdlbmVzIGFuZCBhIGhpZ2gtYWJ1bmRhbmNlIHBlYWsgcmVwcmVzZW50aW5nIGV4cHJlc3NlZCBnZW5lcy4gVGhlIGNob3NlbiB0aHJlc2hvbGQgc2hvdWxkIHNlcGFyYXRlIHRoZSB0d28gcGVha3Mgb2YgdGhlIGJpbW9kYWwgZGlzdHJpYnV0aW9uLgoKYGBge3IgYXZlTG9nQ1BNX3Bsb3RzfQphIDwtIGF2ZUxvZ0NQTVdpdGhPZmZzZXQoZGdlKQphdmVsb2djcG0ucHJlc2VuY2UudGhyZXNob2xkIDwtIC0xCgpwIDwtIGxpc3QoCiAgICBIaXN0b2dyYW09Z2dwbG90KGRhdGEuZnJhbWUobG9nQ1BNPWEpKSArCiAgICAgICAgYWVzKHg9bG9nQ1BNKSArCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHk9MTAwKiguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSwgYmlud2lkdGg9MC4yNSwgYm91bmRhcnk9MCkgKwogICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1hdmVsb2djcG0ucHJlc2VuY2UudGhyZXNob2xkLCBjb2xvcj0icmVkIiwgbGluZXR5cGU9ImRhc2hlZCIpICsKICAgICAgICB4bGFiKCJBdmVyYWdlIGxvZ0NQTSIpICsgeWxhYigiUGVyY2VudCBvZiBnZW5lcyBpbiBiaW4iKSArCiAgICAgICAgY29vcmRfY2FydGVzaWFuKHhsaW09cXVhbnRpbGUoYSwgYygwLCAwLjk5NSkpLCB5bGltPWMoMCwxMCkpICsKICAgICAgICBsYWJzKHRpdGxlPSJBdmVyYWdlIGdlbmUgTG9nQ1BNIGRpc3RyaWJ1dGlvbiIsCiAgICAgICAgICAgICBzdWJ0aXRsZT0iZm9yIGdlbmVzIHdpdGggYXQgbGVhc3QgMSByZWFkIikgKwogICAgICAgIHRoZW1lKHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDApKSwKICAgIEVDREY9Z2dwbG90KGZvcnRpZnkoZWNkZihhKSkpICsKICAgICAgICBhZXMoeD14LCB5PXkqMTAwKSArCiAgICAgICAgZ2VvbV9zdGVwKCkgKwogICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1hdmVsb2djcG0ucHJlc2VuY2UudGhyZXNob2xkLCBjb2xvcj0icmVkIiwgbGluZXR5cGU9ImRhc2hlZCIpICsKICAgICAgICB4bGFiKCJBdmVyYWdlIGxvZ0NQTSIpICsgeWxhYigiUGVyY2VudCBvZiBnZW5lcyB3aXRoIHNtYWxsZXIgYXZlcmFnZSBsb2dDUE0iKSArCiAgICAgICAgY29vcmRfY2FydGVzaWFuKHhsaW09cXVhbnRpbGUoYSwgYygwLCAwLjk5NSkpKSArCiAgICAgICAgbGFicyh0aXRsZT0iRW1waXJpY2FsIEN1bXVsYXRpdmUgRGlzdHJpYnV0aW9uIEZ1bmN0aW9uIG9mIGdlbmUgTG9nQ1BNIHZhbHVlcyIsCiAgICAgICAgICAgICBzdWJ0aXRsZT0iZm9yIGdlbmVzIHdpdGggYXQgbGVhc3QgMSByZWFkIikgKwogICAgICAgIHRoZW1lKHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDApKSkKCmdncHJpbnQocCkKYGBgCgpgYGB7ciBhdmVsb2dDUE1fcGxvdHNfcGRmLCBjYWNoZT1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KZ2dwcmludChwLCBkZXZpY2U9Y2Fpcm9fcGRmKGZpbGUucGF0aChwbG90ZGlyLCAiQXZlTG9nQ1BNLXBsb3RzLnBkZiIpLCBvbmVmaWxlPVRSVUUpKQpgYGAKClRoZSByZWQgZGFzaGVkIGxpbmUgaW4gZWFjaCBwbG90IGluZGljYXRlcyB0aGUgY2hvc2VuIHByZXNlbmNlIHRocmVzaG9sZC4gV2Ugbm93IHN1YnNldCB0aGUgREdFTGlzdCB0byBvbmx5IHRob3NlIGdlbmVzIGFib3ZlIHRoZSB0aHJlc2hvbGQuCgpgYGB7ciBhYnVuZGFuY2VfZmlsdGVyX2dlbmVzfQpkZ2UgJTw+JSAuW2F2ZUxvZ0NQTVdpdGhPZmZzZXQoLikgPj0gYXZlbG9nY3BtLnByZXNlbmNlLnRocmVzaG9sZCxdCmBgYAoKIyMgVmFyaWFuY2UgJiBIZXRlcm9za2VkYXN0aWNpdHkKCk5vdyB3ZSBlc3RpbWF0ZSB0aGUgZGlzcGVyc2lvbnMgZm9yIGVhY2ggZ2VuZSwgdG8gZ2V0IGFuIGlkZWEgb2Ygd2hhdCB0aGUgdmFyaWFiaWxpdHkgb2YgdGhpcyBkYXRhIHNldCBpcyBsaWtlLiBJbiBvcmRlciB0byBldmFsdWF0ZSB0aGUgZWZmZWN0IG9mIGVtcGlyaWNhbCBCYXllcyBzaHJpbmthZ2Ugb24gdGhlIGRpc3BlcnNpb25zLCB3ZSBlc3RpbWF0ZSB0aGUgZ2VuZSBkaXNwZXJzaW9ucyBpbiAzIGRpZmZlcmVudCB3YXlzOiBvbmNlIHdpdGggbm8gaW50ZXItZ2VuZSBpbmZvcm1hdGlvbiBzaGFyaW5nLCBvbmNlIHdpdGggb3JkaW5hcnkgc2hyaW5rYWdlLCBhbmQgb25jZSB3aXRoIHJvYnVzdCBzaHJpbmthZ2UsIHdoaWNoIHJlZHVjZXMgdGhlIHN0cmVuZ3RoIG9mIHNocmlua2FnZSBmb3Igb3V0bGllciBnZW5lcyB3aG9zZSBkaXNwZXJzaW9uIGlzIGZhcnRoZXN0IGF3YXkgZnJvbSB0aGUgdHJlbmQuCgpgYGB7ciBlc3RpbWF0ZV9kaXNwfQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4wICsgZ3JvdXAgKyBkb25vcl9pZCwgc2FtcGxlLnRhYmxlKQpjb2xuYW1lcyhkZXNpZ24pICU8PiUgc3RyX3JlcGxhY2UoIl5ncm91cCIsICIiKQoKZGdlICU8PiUgZXN0aW1hdGVEaXNwKGRlc2lnbiwgcm9idXN0PVRSVUUpCgptZXNzYWdlKCJDb21tb24gZGlzcGVyc2lvbjogIiwgZGdlJGNvbW1vbi5kaXNwZXJzaW9uKQptZXNzYWdlKCJCQ1Y6ICIsIHNxcnQoZGdlJGNvbW1vbi5kaXNwZXJzaW9uKSkKCmRnZS53aXRoLmVCYXllcyA8LSBkZ2UgJT4lIGVzdGltYXRlRGlzcChkZXNpZ24sIHJvYnVzdD1GQUxTRSkKZGdlLndpdGgucm9idXN0LmVCYXllcyA8LSBkZ2UgJT4lIGVzdGltYXRlRGlzcChkZXNpZ24sIHJvYnVzdD1UUlVFKQpkZ2Uud2l0aG91dC5lQmF5ZXMgPC0gZGdlICU+JSBlc3RpbWF0ZURpc3AoZGVzaWduLCBwcmlvci5kZj0wKQpgYGAKCldlIG5vdyBwbG90IGFsbCAzIGRpc3BlcnNpb24gZXN0aW1hdGVzLCBhbG9uZyB3aXRoIHRoZSBvdmVyYWxsIGF2ZXJhZ2UgYW5kIGVzdGltYXRlZCB0cmVuZC4gRWFjaCBwbG90IGluY2x1ZGVzIHRoZSBwb2ludHMgZnJvbSB0aGUgcHJldmlvdXMgcGxvdHMgaW4gbGlnaHRlciBjb2xvcnMgZm9yIGNvbXBhcmlzb24uCgpgYGB7ciBwbG90X2Rpc3B9CmRpc3B0YWJsZSA8LSBkYXRhLmZyYW1lKAogICAgbG9nQ1BNPWRnZS53aXRob3V0LmVCYXllcyRBdmVMb2dDUE0sCiAgICBDb21tb25CQ1Y9ZGdlLndpdGguZUJheWVzJGNvbW1vbi5kaXNwZXJzaW9uICU+JSBzcXJ0LAogICAgVHJlbmRCQ1Y9ZGdlLndpdGguZUJheWVzJHRyZW5kZWQuZGlzcGVyc2lvbiAlPiUgc3FydCwKICAgIEdlbmVXaXNlQkNWPWRnZS53aXRob3V0LmVCYXllcyR0YWd3aXNlLmRpc3BlcnNpb24gJT4lIHNxcnQsCiAgICBlQmF5ZXNCQ1Y9ZGdlLndpdGguZUJheWVzJHRhZ3dpc2UuZGlzcGVyc2lvbiAlPiUgc3FydCwKICAgIFJvYnVzdEJDVj1kZ2Uud2l0aC5yb2J1c3QuZUJheWVzJHRhZ3dpc2UuZGlzcGVyc2lvbiAlPiUgc3FydCkgJT4lCiAgICBjYmluZChkZ2UkZ2VuZXMpCgojIyBSZWR1Y2UgdGhlIG51bWJlciBvZiBwb2ludHMgdG8gcGxvdCBmb3IgZWFjaCBsaW5lIGZvciBwZXJmb3JtYW5jZQojIyByZWFzb25zCm5wb2ludHMgPC0gYyhDb21tb249MiwgVHJlbmQ9NTAwKQpkaXNwLmxpbmUudGFibGUgPC0KICAgIGRpc3B0YWJsZSAlPiUKICAgIHNlbGVjdChsb2dDUE0sIFRyZW5kQkNWLCBDb21tb25CQ1YpICU+JQogICAgbWVsdChpZC52YXJzPSJsb2dDUE0iLCB2YXJpYWJsZS5uYW1lPSJEaXNwVHlwZSIsIHZhbHVlLm5hbWUgPSAiQkNWIikgJT4lCiAgICBtdXRhdGUoRGlzcFR5cGU9c3RyX3JlcGxhY2UoRGlzcFR5cGUsICJCQ1YkIiwgIiIpKSAlPiUKICAgIGdyb3VwX2J5KERpc3BUeXBlKSAlPiUKICAgIGRvKHsKICAgICAgICBzcGxpbmUoeD0uJGxvZ0NQTSwgeT0uJEJDViwgbj1ucG9pbnRzWy4kRGlzcFR5cGVbMV1dKSAlPiUgZGF0YS5mcmFtZShsb2dDUE09LiR4LCBCQ1Y9LiR5KQogICAgfSkKCmJhc2VwbG90IDwtIGdncGxvdChkaXNwdGFibGUpICsKICAgIGFlcyh4PWxvZ0NQTSkKcmF3LmRpc3AucGxvdCA8LSBiYXNlcGxvdCArCiAgICBnZW9tX3BvaW50KGFlcyh5PUdlbmVXaXNlQkNWKSwgc2l6ZT0wLjEsIGNvbG9yPSJibGFjayIpICsKICAgIGdlb21fbGluZShkYXRhPWRpc3AubGluZS50YWJsZSwgYWVzKHg9bG9nQ1BNLCB5PUJDViwgZ3JvdXA9RGlzcFR5cGUpLCBjb2xvcj0id2hpdGUiLCBzaXplPTEuNSwgYWxwaGE9MC41KSArCiAgICBnZW9tX2xpbmUoZGF0YT1kaXNwLmxpbmUudGFibGUsIGFlcyh4PWxvZ0NQTSwgeT1CQ1YsIGxpbmV0eXBlPURpc3BUeXBlKSwgY29sb3I9ImRhcmtyZWQiLCBzaXplPTAuNSkgKwogICAgc2NhbGVfbGluZXR5cGVfbWFudWFsKG5hbWU9IkRpc3BlcnNpb24gVHlwZSIsIHZhbHVlcz1jKFRyZW5kPSJzb2xpZCIsIENvbW1vbj0iZGFzaGVkIikpICsKICAgIHlsYWIoIkJpb2xvZ2ljYWwgY29lZmZpY2llbnQgb2YgdmFyaWF0aW9uIikgKwogICAgZ2d0aXRsZSgiQkNWIHBsb3QgKFJhdyBkaXNwZXJzaW9ucykiKQoKZUJheWVzLmRpc3AucGxvdCA8LSBiYXNlcGxvdCArCiAgICBnZW9tX3BvaW50KGFlcyh5PUdlbmVXaXNlQkNWKSwgc2l6ZT0wLjQsIGNvbG9yPSJncmF5IikgKwogICAgZ2VvbV9wb2ludChhZXMoeT1lQmF5ZXNCQ1YpLCBzaXplPTAuMSwgY29sb3I9ImRhcmtibHVlIikgKwogICAgZ2VvbV9saW5lKGRhdGE9ZGlzcC5saW5lLnRhYmxlLCBhZXMoeD1sb2dDUE0sIHk9QkNWLCBncm91cD1EaXNwVHlwZSksIGNvbG9yPSJ3aGl0ZSIsIHNpemU9MS41LCBhbHBoYT0wLjUpICsKICAgIGdlb21fbGluZShkYXRhPWRpc3AubGluZS50YWJsZSwgYWVzKHg9bG9nQ1BNLCB5PUJDViwgbGluZXR5cGU9RGlzcFR5cGUpLCBjb2xvcj0iZGFya3JlZCIsIHNpemU9MC41KSArCiAgICBzY2FsZV9saW5ldHlwZV9tYW51YWwobmFtZT0iRGlzcGVyc2lvbiBUeXBlIiwgdmFsdWVzPWMoVHJlbmQ9InNvbGlkIiwgQ29tbW9uPSJkYXNoZWQiKSkgKwogICAgeWxhYigiQmlvbG9naWNhbCBjb2VmZmljaWVudCBvZiB2YXJpYXRpb24iKSArCiAgICBnZ3RpdGxlKCJCQ1YgcGxvdCAoUmF3ICYgc3F1ZWV6ZWQgZGlzcGVyc2lvbnMpIikKCnJvYnVzdC5lQmF5ZXMuZGlzcC5wbG90IDwtIGJhc2VwbG90ICsKICAgIGdlb21fcG9pbnQoYWVzKHk9R2VuZVdpc2VCQ1YpLCBzaXplPTAuNCwgY29sb3I9ImdyYXkiKSArCiAgICBnZW9tX3BvaW50KGFlcyh5PWVCYXllc0JDViksIHNpemU9MC40LCBjb2xvcj0iZGVlcHNreWJsdWUiKSArCiAgICBnZW9tX3BvaW50KGFlcyh5PVJvYnVzdEJDViksIHNpemU9MC4xLCBjb2xvcj0iZGFya2dyZWVuIikgKwogICAgZ2VvbV9saW5lKGRhdGE9ZGlzcC5saW5lLnRhYmxlLCBhZXMoeD1sb2dDUE0sIHk9QkNWLCBncm91cD1EaXNwVHlwZSksIGNvbG9yPSJ3aGl0ZSIsIHNpemU9MS41LCBhbHBoYT0wLjUpICsKICAgIGdlb21fbGluZShkYXRhPWRpc3AubGluZS50YWJsZSwgYWVzKHg9bG9nQ1BNLCB5PUJDViwgbGluZXR5cGU9RGlzcFR5cGUpLCBjb2xvcj0iZGFya3JlZCIsIHNpemU9MC41KSArCiAgICBzY2FsZV9saW5ldHlwZV9tYW51YWwobmFtZT0iRGlzcGVyc2lvbiBUeXBlIiwgdmFsdWVzPWMoVHJlbmQ9InNvbGlkIiwgQ29tbW9uPSJkYXNoZWQiKSkgKwogICAgeWxhYigiQmlvbG9naWNhbCBjb2VmZmljaWVudCBvZiB2YXJpYXRpb24iKSArCiAgICBnZ3RpdGxlKCJCQ1YgcGxvdCAoUmF3ICYgc3F1ZWV6ZWQgJiByb2J1c3QgZGlzcGVyc2lvbnMpIikKCnAgPC0gbGlzdChyYXcuZGlzcC5wbG90LCBlQmF5ZXMuZGlzcC5wbG90LCByb2J1c3QuZUJheWVzLmRpc3AucGxvdCkKZ2dwcmludChwKQpgYGAKCmBgYHtyIHBsb3RfZGlzcF9wZGYsIGNhY2hlPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpnZ3ByaW50KHAsIGRldmljZT1jYWlyb19wZGYoZmlsZS5wYXRoKHBsb3RkaXIsICJkaXNwLXBsb3RzLnBkZiIpLCBvbmVmaWxlPVRSVUUpKQpyYXN0ZXJwZGYoZmlsZS5wYXRoKHBsb3RkaXIsICJkaXNwLXBsb3RzLnBkZiIpLCByZXNvbHV0aW9uPTYwMCkKYGBgCgpOZXh0LCB3ZSB1c2UgbGltbWEncyBzYW1wbGUgd2VpZ2h0IGNhbGN1bGF0aW5nIG1ldGhvZHMgdG8gaW52ZXN0aWdhdGUgcG9zc2libGUgcXVhbGl0eSBpc3N1ZXMuIFRvIGNvbmZpcm0gb3VyIHJlc3VsdHMsIHdlIGFsc28gc3BsaXQgdGhlIHNhbXBsZXMgaW50byB0cmVhdG1lbnQgZ3JvdXBzIGFuZCB1c2UgZWRnZVIgdG8gZXN0aW1hdGUgdGhlIGRpc3BlcnNpb24gd2l0aGluIGVhY2ggZ3JvdXAuIFRoaXMgaXMgYSBjcnVkZSBtZXRob2QsIGFuZCB0aGVzZSBncm91cC1zcGVjaWZpYyBkaXNwZXJzaW9uIGVzdGltYXRlIHdvdWxkIGJlIHRvbyB1bnN0YWJsZSBmb3IgdXNlIGluIGEgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMsIGJ1dCBjb21wYXJpbmcgdGhlIG92ZXJhbGwgbWVhbiBkaXNwZXJzaW9uIGZvciBlYWNoIHNhbXBsZSB0byB0aGUgc2FtcGxlIHF1YWxpdHkgd2VpZ2h0cyBkZXRlcm1pbmVkIGJ5IGxpbW1hIHByb3ZpZGVzIGEgdXNlZnVsIHNhbml0eSBjaGVjay4KCmBgYHtyIGNvbXB1dGVfcXVhbGl0eV93ZWlnaHRzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQplbGlzdC53IDwtIHZvb21XaXRoUXVhbGl0eVdlaWdodHNBbmRPZmZzZXQoZGdlLCBkZXNpZ24pCmRiZyA8LSBlc3RpbWF0ZURpc3BCeUdyb3VwKGRnZSwgc2FtcGxlLnRhYmxlJGdyb3VwLCBzYW1wbGUudGFibGUkYmF0Y2gpCmBgYAoKVG8gc2VlIHdoZXRoZXIgdGhlIHdlaWdodHMgYXJlIGNvcnJlbGF0ZWQgd2l0aCBzcGVjaWZpYyBleHBlcmltZW50YWwgZmFjdG9ycywgd2UgY3JlYXRlIGEgYm94cGxvdCBvZiB0aGUgd2VpZ2h0cyBhbmQgZ3JvdXAgZGlzcGVyc2lvbnMgYWdhaW5zdCBlYWNoIHJlbGV2YW50IGNvdmFyaWF0ZS4KCmBgYHtyIHBsb3RfcXVhbGl0eV93ZWlnaHRzfQpjb3ZhcnMgPC0gc2FtcGxlLnRhYmxlICU+JSBkcGx5cjo6c2VsZWN0KGdyb3VwLCB0aW1lX3BvaW50LCBkb25vcl9pZCwgYmF0Y2gsIGNlbGxfdHlwZSkKcWNtZXRyaWNzIDwtIGRhdGEuZnJhbWUoV2VpZ2h0PWVsaXN0Lnckc2FtcGxlLndlaWdodHMsCiAgICAgICAgICAgICAgICAgICAgICAgIEdyb3VwQkNWPXNhcHBseShkYmcsIGBbW2AsICJjb21tb24uZGlzcGVyc2lvbiIpW2FzLmNoYXJhY3Rlcihjb3ZhcnMkZ3JvdXApXSkKCnAgPC0gZ2dkdW8uZGF0YVhZKGNvdmFycywgcWNtZXRyaWNzICU+JSB0cmFuc211dGUoTG9nMldlaWdodD1sb2cyKFdlaWdodCksIEdyb3VwQkNWKSkgKwogIGdndGl0bGUoIldlaWdodHMgYW5kIGRpc3BlcnNpb25zIGJ5IGdyb3VwIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwgdmp1c3Q9MC41KSkKZ2dwcmludChwKQpgYGAKCmBgYHtyIHBsb3RfcXVhbGl0eV93ZWlnaHRzX3BkZiwgY2FjaGU9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmdncHJpbnQocCwgZGV2aWNlPWNhaXJvX3BkZihmaWxlLnBhdGgocGxvdGRpciwgInFjLXdlaWdodHMucGRmIiksIG9uZWZpbGUgPSBUUlVFKSkKYGBgCldlIGFsc28gbWFrZSBwbG90cyBvZiB0aGUgaW5kaXZpZHVhbCB3ZWlnaHRzIGFnYWluc3QgZWFjaCBjb3ZhcmlhdGUsIGFuZCBjb21wdXRlIHRoZSBBTk9WQSBwLXZhbHVlIGZvciB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHdlaWdodHMgYW5kIGVhY2ggY292YXJpYXRlIHNlcGFyYXRlbHkuCgooTm90ZSBmb3IgbGF0ZXI6IEl0IHdvdWxkIGJlIGdvb2QgdG8gdHJ5IGFsbCBjb3ZhcmlhdGVzIGluIHRoZSBzYW1lIG1vZGVsIGFzIHJhbmRvbSBlZmZlY3RzLikKCmBgYHtyIHBsb3Rfd2VpZ2h0c192c19jb3ZhcnN9CmF3ZGYgPC0gZGF0YS5mcmFtZShjb3ZhcnMsIHFjbWV0cmljcykKYW5vdmFzIDwtIGxhcHBseShjb2xuYW1lcyhjb3ZhcnMpLCBmdW5jdGlvbih4KSB7CiAgICBmb3JtdWxhIDwtIGFzLmZvcm11bGEoc3RyX2MoImxvZzIoV2VpZ2h0KSB+ICIsIHgpKQogICAgbG0oZm9ybXVsYSwgYXdkZikgJT4lIGFvdiAlPiUgdGlkeSAlPiUgZmlsdGVyKHRlcm0gPT0geCkKfSkgJT4lCiAgICBkby5jYWxsKHdoYXQ9cmJpbmQpICU+JQogICAgbXV0YXRlKHBhZGo9cC5hZGp1c3QocC52YWx1ZSwgbWV0aG9kPSJCSCIpKQpwdmFscyA8LSBhbm92YXMgJSQlIHNldE5hbWVzKHAudmFsdWUsIHRlcm0pCmF3LnBsb3QuYmFzZSA8LSBnZ3Bsb3QoYXdkZikgKwogICAgYWVzKHk9V2VpZ2h0KSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9bG9nX3RyYW5zKDIpKSArCiAgICBnZW9tX2RvdHBsb3QoYmluYXhpcyA9ICJ5Iiwgc3RhY2tkaXI9ImNlbnRlciIsIGJpbndpZHRoPTAuMSkKcCA8LSBsYXBwbHkoY29sbmFtZXMoY292YXJzKSwgZnVuY3Rpb24oeCkgewogICAgcHJldHR5LmNvdmFyLm5hbWUgPC0geCAlPiUgc3RyX3JlcGxhY2VfYWxsKCJfIiwgIiAiKSAlPiUgc3RyX3RvX3RpdGxlCiAgICBhdy5wbG90LmJhc2UgKyBhZXNfc3RyaW5nKHg9eCkgKwogICAgICAgIGxhYnModGl0bGU9c3RyX2MoIkFycmF5IHdlaWdodHMgYnkgIiwgcHJldHR5LmNvdmFyLm5hbWUpLAogICAgICAgICAgICAgc3VidGl0bGU9c3ByaW50ZigiQU5PVkEgcC12YWx1ZSA9ICUwLjNnIiwgcHZhbHNbeF0pKQp9KQpnZ3ByaW50KHApCmBgYAoKYGBge3IgcGxvdF93ZWlnaHRzX3ZzX2NvdmFyc19wZGYsIGNhY2hlPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpnZ3ByaW50KHAsIGRldmljZT1jYWlyb19wZGYoZmlsZS5wYXRoKHBsb3RkaXIsICJ3ZWlnaHRzLXZzLWNvdmFycy5wZGYiKSwgb25lZmlsZSA9IFRSVUUpKQpgYGAKCiMjIEJhdGNoIGNvcnJlY3Rpb24KCk5leHQgd2UgcGVyZm9ybSBzZXZlcmFsIG1ldGhvZHMgb2YgYmF0Y2ggY29ycmVjdGlvbiBvbiB0aGUgdm9vbS10cmFuc2Zvcm1lZCBkYXRhLiBGaXJzdCwgd2UgdHJ5IGRpcmVjdCBiYXRjaCBzdWJ0cmFjdGlvbiB1c2luZyBsaW1tYSdzIGByZW1vdmVCYXRjaEVmZmVjdGAgZnVuY3Rpb24sIHdoaWNoIGZpdHMgYSBsaW5lYXIgbW9kZWwgd2l0aCB0aGUgYmF0Y2hlcyBhcyBzdW0tdG8temVybyBjb2VmZmljaWVudHMgYW5kIHRoZW4gc3VidHJhY3RzIHRoZSBiYXRjaCBjb2VmZmljaWVudHMgZnJvbSB0aGUgZGF0YS4KCmBgYHtyIGJhdGNoX3N1YnRyYWN0fQplbGlzdC5iYyA8LSBlbGlzdC53CiMgQmF0Y2ggaXMgY29uZm91bmRlZCB3aXRoIHRpbWUgcG9pbnQsIHNvIHdlIGxlYXZlIGl0IG91dCBvZiB0aGUgZGVzaWduIGZvciB0aGlzIHN0ZXAKZGVzaWduLk5vVGltZSA8LSBtb2RlbC5tYXRyaXgofiBjZWxsX3R5cGUgKyBkb25vcl9pZCwgc2FtcGxlLnRhYmxlKQplbGlzdC5iYyRFICU8PiUKICAgIHJlbW92ZUJhdGNoRWZmZWN0KGJhdGNoPXNhbXBsZS50YWJsZSAlJCUgYmF0Y2g6ZG9ub3JfaWQsIGRlc2lnbj1kZXNpZ24uTm9UaW1lLAogICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0cz1lbGlzdC5iYyR3ZWlnaHRzKQpgYGAKClNlY29uZCwgd2UgdXNlIENvbUJhdCwgd2hpY2ggcGVyZm9ybXMgZW1waXJpY2FsIEJheWVzIHNocmlua2FnZSBvZiB0aGUgYmF0Y2ggY29ycmVjdGlvbiBwYXJhbWV0ZXJzLiBGaXJzdCwgd2UgcnVuIENvbUJhdCBpbiBwYXJhbWV0cmljIHByaW9yIG1vZGUgaW4gb3JkZXIgdG8gcHJvZHVjZSBhIGRpYWdub3N0aWMgcGxvdCBvZiB0aGUgcHJpb3JzLiBOb3RlIHRoYXQgd2UgdXNlIENvbUJhdCBoZXJlIHRvIHN1YnRyYWN0IGJvdGggdGhlIGJhdGNoIGFuZCBkb25vciBlZmZlY3RzLCBzbyB0aGF0IHRoZSBNRFMgcGxvdHMgd2lsbCBiZSBtb3JlIHJlZmxlY3RpdmUgb2YgdGhlIGRlc2lyZWQgYmlvbG9naWNhbCBlZmZlY3RzLgoKYGBge3IgY29tYmF0X3Bsb3R9CiMgUmlnaHQgbm93IHdlJ3JlIGp1c3QgcnVubmluZyBDb21CYXQgdG8gcHJvZHVjZSB0aGUgcGxvdCwgc28gd2UgZGlzY2FyZCB0aGUgb3V0cHV0LgppbnZpc2libGUoY2FwdHVyZS5vdXRwdXQoQ29tQmF0KGVsaXN0LnckRSwgYmF0Y2g9c2FtcGxlLnRhYmxlJGJhdGNoLCBtb2Q9ZGVzaWduLk5vVGltZSwgcGFyLnByaW9yPVRSVUUsIHByaW9yLnBsb3RzID0gVFJVRSkpKQpgYGAKCmBgYHtyIGNvbWJhdF9wbG90X3BkZiwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0KY2Fpcm9fcGRmKGZpbGUucGF0aChwbG90ZGlyLCAicm5hc2VxLUNvbUJhdC1xYy5wZGYiKSkKIyBSdW4gdGhlIHNhbWUgdGhpbmcgYWdhaW4gdG8gb3V0cHV0IHRoZSBwbG90IHRvIHRoZSBQREYgZmlsZQppbnZpc2libGUoQ29tQmF0KGVsaXN0LnckRSwgYmF0Y2g9c2FtcGxlLnRhYmxlJGJhdGNoLCBtb2Q9ZGVzaWduLk5vVGltZSwgcGFyLnByaW9yPVRSVUUsIHByaW9yLnBsb3RzID0gVFJVRSkpCmludmlzaWJsZShkZXYub2ZmKCkpCmBgYAoKU2luY2UgdGhlIHBhcmFtZXRyaWMgZml0IGZvciB0aGUgdmFyaWFuY2UgaXMgbm90IGEgZ29vZCBtYXRjaCBmb3IgdGhlIGVtcGlyaWNhbCBkaXN0cmlidXRpb24sIHdlIG5vdyBwZXJmb3JtIHRoZSBhY3R1YWwgYmF0Y2ggY29ycmVjdGlvbiB1c2luZyB0aGUgbm9uLXBhcmFtZXRyaWMgbW9kZSBvZiBDb21CYXQuCgpgYGB7ciBjb21iYXRfYWRqdXN0fQojIE5vdyBwZXJmb3JtIHRoZSBhY3R1YWwgYmF0Y2ggY29ycmVjdGlvbiB1c2luZyBub24tcGFyYW1ldHJpYyBwcmlvcgpkZXNpZ24uY2IgPC0gbW9kZWwubWF0cml4KH5jZWxsX3R5cGUsIHNhbXBsZS50YWJsZSkKZWxpc3QuY2IgPC0gZWxpc3QudwplbGlzdC5jYiRFICU8PiUgQ29tQmF0KGJhdGNoPXNhbXBsZS50YWJsZSRiYXRjaCwgbW9kPWRlc2lnbi5Ob1RpbWUsIHBhci5wcmlvcj1GQUxTRSkKYGBgCgpGb3IgZWFjaCBvZiB0aGUgYmF0Y2ggY29ycmVjdGlvbiBtZXRob2RzLCB3ZSBjb21wdXRlIHRoZSBzYW1wbGUgZGlzdGFuY2UgbWF0cml4IHVzaW5nIG11bHRpZGltZW5zaW9uYWwgc2NhbGluZyBhbmQgcGxvdCB0aGUgZmlyc3QgMyBwcmluY2lwYWwgY29vcmRpbmF0ZXMuIFdlIHJlZmxlY3QgYWxsIHRoZSBwcmluY2lwYWwgY29vcmRpbmF0ZXMgc28gYXMgdG8gbWFrZSB0aGUgbWVhbiBvZiB0aGUgTmFpdmUgRDAgc2FtcGxlcyBuZWdhdGl2ZSBpbiBhbGwgZGltZW5zaW9ucywgc28gdGhhdCBNRFMgcGxvdHMgaGF2ZSBhIGdyZWF0ZXIgY2hhbmNlIG9mIGJlaW5nIG9yaWVudGVkIGNvbnNpc3RlbnRseSB3aXRoIGVhY2ggb3RoZXIuCgpgYGB7ciBiYXRjaF9jb3JyZWN0aW9uX21kc19wbG90LCB3YXJuaW5nPUZBTFNFfQpuYWl2ZS5kMC5zYW1wbGVzIDwtIHNhbXBsZS50YWJsZSRncm91cCA9PSAiTmFpdmVEMCIKZG1hdCA8LSBzdXBwcmVzc1Bsb3QocGxvdE1EUyhlbGlzdC53KSRkaXN0YW5jZS5tYXRyaXgpICU+JSBhcy5kaXN0Cm1kcyA8LSBjbWRzY2FsZShkbWF0LCBrPWF0dHIoZG1hdCwgIlNpemUiKSAtIDEsIGVpZz1UUlVFKQptZHMkcG9pbnRzICU8PiUgc2NhbGUoY2VudGVyPUZBTFNFLCBzY2FsZT0tc2lnbihjb2xNZWFucyguW25haXZlLmQwLnNhbXBsZXMsXSkpKSAlPiUKICAgIGFkZC5udW1iZXJlZC5jb2xuYW1lcygiRGltIikgJT4lIGRhdGEuZnJhbWUoc2FtcGxlLnRhYmxlLCAuKQpkbWF0LmJjIDwtIHN1cHByZXNzUGxvdChwbG90TURTKGVsaXN0LmJjKSRkaXN0YW5jZS5tYXRyaXgpICU+JSBhcy5kaXN0Cm1kcy5iYyA8LSBjbWRzY2FsZShkbWF0LmJjLCBrPWF0dHIoZG1hdCwgIlNpemUiKSAtIDEsIGVpZz1UUlVFKQptZHMuYmMkcG9pbnRzICU8PiUgc2NhbGUoY2VudGVyPUZBTFNFLCBzY2FsZT1zaWduKGNvbE1lYW5zKC5bbmFpdmUuZDAuc2FtcGxlcyxdKSkpICU+JQogICAgYWRkLm51bWJlcmVkLmNvbG5hbWVzKCJEaW0iKSAlPiUgZGF0YS5mcmFtZShzYW1wbGUudGFibGUsIC4pCmRtYXQuY2IgPC0gc3VwcHJlc3NQbG90KHBsb3RNRFMoZWxpc3QuY2IpJGRpc3RhbmNlLm1hdHJpeCkgJT4lIGFzLmRpc3QKbWRzLmNiIDwtIGNtZHNjYWxlKGRtYXQuY2IsIGs9YXR0cihkbWF0LCAiU2l6ZSIpIC0gMSwgZWlnPVRSVUUpCm1kcy5jYiRwb2ludHMgJTw+JSBzY2FsZShjZW50ZXI9RkFMU0UsIHNjYWxlPXNpZ24oY29sTWVhbnMoLltuYWl2ZS5kMC5zYW1wbGVzLF0pKSkgJT4lCiAgICAgYWRkLm51bWJlcmVkLmNvbG5hbWVzKCJEaW0iKSAlPiUgZGF0YS5mcmFtZShzYW1wbGUudGFibGUsIC4pCgpnZ21kc2JhdGNoIDwtIGZ1bmN0aW9uKGRhdCwgZGltcz0xOjIpIHsKICAgIGlmIChsZW5ndGgoZGltcykgPT0gMSkgewogICAgICAgIGRpbXMgPC0gZGltcyArIGMoMCwxKQogICAgfQogICAgYXNzZXJ0X3RoYXQobGVuZ3RoKGRpbXMpID09IDIpCiAgICBnZ3Bsb3QoZGF0KSArCiAgICAgICAgYWVzX3N0cmluZyh4PXN0cl9jKCJEaW0iLCBkaW1zWzFdKSwgeT1zdHJfYygiRGltIiwgZGltc1syXSkpICsKICAgICAgICBhZXMoY29sb3I9YmF0Y2gsIGxhYmVsPVNhbXBsZU5hbWUpICsKICAgICAgICBnZW9tX3RleHQoKSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZD1jKDAuMTUsIDApKSArCiAgICAgICAgY29vcmRfZXF1YWwoKQp9CgpwIDwtIGxpc3QoCiAgICBnZ21kc2JhdGNoKG1kcyRwb2ludHMpICsKICAgICAgICBsYWJzKHRpdGxlPSJsaW1tYSB2b29tIFByaW5jaXBhbCBDb29yZGluYXRlcyAxICYgMiIsCiAgICAgICAgICAgICBzdWJ0aXRsZT0iTm8gYmF0Y2ggY29ycmVjdGlvbiIpLAogICAgZ2dtZHNiYXRjaChtZHMuYmMkcG9pbnRzKSArCiAgICAgICAgbGFicyh0aXRsZT0ibGltbWEgdm9vbSBQcmluY2lwYWwgQ29vcmRpbmF0ZXMgMSAmIDIiLAogICAgICAgICAgICAgc3VidGl0bGU9IkFmdGVyIG5haXZlIGJhdGNoIHN1YnRyYWN0aW9uIiksCiAgICBnZ21kc2JhdGNoKG1kcy5jYiRwb2ludHMpICsKICAgICAgICBnZ3RpdGxlKCJsaW1tYSB2b29tIFByaW5jaXBhbCBDb29yZGluYXRlcyAxICYgMiIsCiAgICAgICAgICAgICBzdWJ0aXRsZT0iQWZ0ZXIgQ29tQmF0IGJhdGNoIGNvcnJlY3Rpb24iKSwKICAgIGdnbWRzYmF0Y2gobWRzJHBvaW50cywgZGltcz0yOjMpICsKICAgICAgICBnZ3RpdGxlKCJsaW1tYSB2b29tIFByaW5jaXBhbCBDb29yZGluYXRlcyAyICYgMyIsCiAgICAgICAgICAgICBzdWJ0aXRsZT0iTm8gYmF0Y2ggY29ycmVjdGlvbiIpLAogICAgZ2dtZHNiYXRjaChtZHMuYmMkcG9pbnRzLCBkaW1zPTI6MykgKwogICAgICAgIGdndGl0bGUoImxpbW1hIHZvb20gUHJpbmNpcGFsIENvb3JkaW5hdGVzIDIgJiAzIiwKICAgICAgICAgICAgIHN1YnRpdGxlPSJBZnRlciBuYWl2ZSBiYXRjaCBzdWJ0cmFjdGlvbiIpLAogICAgZ2dtZHNiYXRjaChtZHMuY2IkcG9pbnRzLCBkaW1zPTI6MykgKwogICAgICAgIGdndGl0bGUoImxpbW1hIHZvb20gUHJpbmNpcGFsIENvb3JkaW5hdGVzIDIgJiAzIiwKICAgICAgICAgICAgIHN1YnRpdGxlPSJBZnRlciBDb21CYXQgYmF0Y2ggY29ycmVjdGlvbiIpKQpnZ3ByaW50KHApCmBgYAoKVGhlIG5haXZlIGJhdGNoIHN1YnRyYWN0aW9uIHNlZW1zIHRvIGxlYXZlIG9uZSBkb25vciBhcyBhbiBvdXRsaWVyIGZvciB1bmNsZWFyIHJlYXNvbnMsIHdoaWxlIENvbUJhdCBhdm9pZHMgdGhpcyBhcnRpZmFjdCwgaW5zdGVhZCB5aWVsZGluZyBhYmlvbG9naWNhbGx5IG1vcmUgcGxhdXNpYmxlIE1EUyBwbG90LgoKYGBge3IgYmF0Y2hfY29ycmVjdGlvbl9tZHNfcGxvdF9wZGYsIGNhY2hlPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpnZ3ByaW50KHAsIGRldmljZT1jYWlyb19wZGYoZmlsZS5wYXRoKHBsb3RkaXIsICJybmFzZXEtTURTUGxvdHMtQmF0Y2hDb3JyZWN0LnBkZiIpLCB3aWR0aD0xMiwgaGVpZ2h0PTEyLCBvbmVmaWxlID0gVFJVRSkpCmBgYAoKIyMgTURTIFBsb3RzCgpDaG9vc2luZyBDb21CYXQgYXMgdGhlIGJlc3QtbG9va2luZyBiYXRjaCBjb3JyZWN0aW9uLCB3ZSBtYWtlIG1vcmUgTURTIHBsb3RzIGZvciB0aGlzIGRhdGEsIHRoaXMgdGltZSBwbG90dGluZyB0aGUgZmlyc3QgNSBQQ3MgYW5kIGFkZGluZyB0aGUgZXhwZXJpbWVudGFsIGluZm9ybWF0aW9uIHRvIHRoZSBwbG90IGFzIGNvbG9ycyBhbmQgc2hhcGVzLgoKYGBge3IgbWRzX3Bsb3R9CnhsaW1zIDwtIHJhbmdlKHVubGlzdChtZHMuY2IkcG9pbnRzW2MoIkRpbTEiLCAiRGltMiIpXSkpCnlsaW1zIDwtIHJhbmdlKHVubGlzdChtZHMuY2IkcG9pbnRzW2MoIkRpbTIiLCAiRGltMyIpXSkpCnBiYXNlIDwtIGdncGxvdChtZHMuY2IkcG9pbnRzKSArCiAgICBhZXMoeD1EaW0xLCB5PURpbTIsIGxhYmVsPVNhbXBsZU5hbWUsIGNvbG9yPWJhdGNoLCBmaWxsPXRpbWVfcG9pbnQsIHNoYXBlPWNlbGxfdHlwZSwgbGluZXR5cGU9ZG9ub3JfaWQsIGdyb3VwPWNlbGxfdHlwZTpkb25vcl9pZCkgKwogICAgZ2VvbV9lbmNpcmNsZShhZXMoZ3JvdXA9dGltZV9wb2ludDpjZWxsX3R5cGUsIGNvbG9yPU5VTEwpLCBzX3NoYXBlPTAuNzUsIGV4cGFuZD0wLjA1LCBjb2xvcj1OQSwgYWxwaGE9MC4yKSArCiAgICBnZW9tX3BhdGgoY29sb3I9aGNsKGM9MCwgbD00NSksIGFlcyhjb2xvcj1OVUxMKSkgKwogICAgZ2VvbV9wb2ludChzaXplPTQpICsKICAgIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXM9YyhOYWl2ZT0yMSwgTWVtb3J5PTI0KSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jb2wyaGNsKGMoQjE9ImdyZWVuIiwgQjI9Im1hZ2VudGEiKSwgbD04MCkpICsKICAgIHNjYWxlX2ZpbGxfaHVlKGw9NTUpICsKICAgIHNjYWxlX2xpbmV0eXBlX21hbnVhbCh2YWx1ZXM9Yygic29saWQiLCAiZGFzaGVkIiwgImRvdGRhc2giLCAidHdvZGFzaCIpKSArCiAgICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2hhcGUgPSAyMSkpLAogICAgICAgICAgIGZpbGwgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaGFwZSA9IDIxKSksCiAgICAgICAgICAgc2hhcGUgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChjb2xvcj1oY2woYz0wLCBsPTgwKSwgZmlsbD1oY2woYz0wLCBsPTU1KSkpKSArCiAgICBsYWJzKHRpdGxlPSJsaW1tYSB2b29tIFByaW5jaXBhbCBDb29yZGluYXRlcyAxICYgMiIsCiAgICAgICAgIHN1YnRpdGxlPSIgKGFmdGVyIENvbUJhdCBiYXRjaCBjb3JyZWN0aW9uKSIpICsKICAgIGNvb3JkX2VxdWFsKHhsaW09eGxpbXMsIHlsaW09eWxpbXMpCnAgPC0gbGlzdChQQzEyPXBiYXNlLAogICAgICAgICAgUEMyMz1wYmFzZSArCiAgICAgICAgICAgICAgYWVzKHg9RGltMiwgeT1EaW0zKSArCiAgICAgICAgICAgICAgbGFicyh0aXRsZT0ibGltbWEgdm9vbSBQcmluY2lwYWwgQ29vcmRpbmF0ZXMgMiAmIDMiKSwKICAgICAgICAgIFBDMzQ9cGJhc2UgKwogICAgICAgICAgICAgIGFlcyh4PURpbTMsIHk9RGltNCkgKwogICAgICAgICAgICAgIGxhYnModGl0bGU9ImxpbW1hIHZvb20gUHJpbmNpcGFsIENvb3JkaW5hdGVzIDMgJiA0IiksCiAgICAgICAgICBQQzQ1PXBiYXNlICsKICAgICAgICAgICAgICBhZXMoeD1EaW00LCB5PURpbTUpICsKICAgICAgICAgICAgICBsYWJzKHRpdGxlPSJsaW1tYSB2b29tIFByaW5jaXBhbCBDb29yZGluYXRlcyA0ICYgNSIpKQpnZ3ByaW50KHApCmBgYAoKYGBge3IgbWRzX3Bsb3RfcGRmLCBjYWNoZT1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KZ2dwcmludChwLCBkZXZpY2U9Y2Fpcm9fcGRmKGZpbGUucGF0aChwbG90ZGlyLCAicm5hc2VxLU1EU1Bsb3RzLnBkZiIpLCBvbmVmaWxlID0gVFJVRSkpCmBgYAoKIyMgVmFyaWFuY2UgUGFydGl0aW9uaW5nIGFuYWx5c2lzCgpUbyBmdXJ0aGVyIGludmVzdGlnYXRlIHRoZSBzb3VyY2VzIG9mIHZhcmlhbmNlIHdpdGhpbiB0aGUgZGF0YSwgd2UgY2FuIHVzZSB0aGUgYHZhcmlhbmNlUGFydGl0aW9uYCBwYWNrYWdlLiBXZSBmaXQgdGhlIG1vZGVsIHRvIHRoZSB1bmNvcnJlY3RlZCBkYXRhIGFuZCB0aGUgdHdvIGNvcnJlY3RlZCBkYXRhIHNldHMgKHNpbXBsZSBiYXRjaCBzdWJ0cmFjdGlvbiBhbmQgQ29tQmF0KSBzbyB0aGF0IHdlIGNhbiBzZWUgaG93IHRoZSBwZXJjZW50IG9mIHZhcmlhbmNlIGV4cGxhaW5lZCBpcyBhZmZlY3RlZCBieSBiYXRjaCBjb3JyZWN0aW9uLiBXZSB1c2UgcmFuZG9tIGVmZmVjdHMgZm9yIHRoZSBmYWN0b3JzIGluIHRoZSBtb2RlbCBzaW5jZSBiYXRjaCBhbmQgdGltZSBwb2ludCBhcmUgY29uZm91bmRlZC4KCmBgYHtyIHJ1bl92cGFydH0KIyBGaXQgYWxsIGZhY3RvcnMgYXMgcmFuZG9tIGVmZmVjdHMsIHNpbmNlIGdyb3VwIGFuZCBiYXRjaCBhcmUgY29uZm91bmRlZAp2cC5mb3JtdWxhIDwtIH4gKDF8Z3JvdXApICsgKDF8ZG9ub3JfaWQpICsgKDF8YmF0Y2gpCmVsaXN0cyA8LSBsaXN0KE5vQkM9ZWxpc3QudywgQmF0U3ViPWVsaXN0LmJjLCBDb21CYXQ9ZWxpc3QuY2IpCiMgRnVuY3Rpb24gaXMgYWxyZWFkeSBwYXJhbGxlbGl6ZWQsIHNvIGRvbid0IGNhbGwgaXQgaW4gcGFyYWxsZWwKdmFyUGFydHMgPC0gbWFwcGx5KGZ1bmN0aW9uKC4uLikgdHJ5KGZpdEV4dHJhY3RWYXJQYXJ0TW9kZWwoLi4uKSksIAogICAgICAgICAgICAgICAgICAgZXhwck9iaiA9IGVsaXN0cywgCiAgICAgICAgICAgICAgICAgICBNb3JlQXJncyA9IGxpc3QoCiAgICAgICAgICAgICAgICAgICAgICAgZm9ybXVsYSA9IHZwLmZvcm11bGEsIAogICAgICAgICAgICAgICAgICAgICAgIGRhdGE9c2FtcGxlLnRhYmxlKSwKICAgICAgICAgICAgICAgICAgIFNJTVBMSUZZID0gRkFMU0UpCiMgQ29sbGFwc2UgU1ZzIHRvIGEgc2luZ2xlIGNvbHVtbgp2YXJUYWJsZXMgPC0gbGlzdCgpCmZvciAoaSBpbiBuYW1lcyh2YXJQYXJ0cykpIHsKICAgIGlmIChpcyh2YXJQYXJ0c1tbaV1dLCAidHJ5LWVycm9yIikpIHsKICAgICAgICBtZXNzYWdlKCJDb3VsZCBub3QgcnVuIHZhcmlhbmNlUGFydGl0aW9uIGZvciAiLCBpLCAiLCBwcm9iYWJseSBkdWUgdG8gY29sbGluZWFyaXR5IG9mIGNvdmFyaWF0ZXMuIikKICAgIH0gZWxzZSB7CiAgICAgICAgYXNzZXJ0X3RoYXQoaXModmFyUGFydHNbW2ldXSwgInZhclBhcnRSZXN1bHRzIikpCiAgICAgICAgeCA8LSBhcyh2YXJQYXJ0c1tbaV1dLCAiZGF0YS5mcmFtZSIpCiAgICAgICAgeC5zdiA8LSB4ICU+JSBzZWxlY3QoZHBseXI6Om1hdGNoZXMoIl5TVlxcZCskIikpCiAgICAgICAgaWYgKG5jb2woeC5zdikgPiAwKSB7CiAgICAgICAgICAgIHgubm9zdiA8LSB4W3NldGRpZmYoY29sbmFtZXMoeCksIGNvbG5hbWVzKHguc3YpKV0KICAgICAgICAgICAgeCA8LSBkYXRhLmZyYW1lKHgubm9zdiwgU1Y9cm93U3Vtcyh4LnN2KSkKICAgICAgICB9CiAgICAgICAgdmFyVGFibGVzW1tpXV0gPC0gY2JpbmQoc2VsZWN0KHgsIC1SZXNpZHVhbHMpLCBzZWxlY3QoeCwgUmVzaWR1YWxzKSkKICAgIH0KfQpgYGAKClRoZSB2YXJpYW5jZVBhcnRpdGlvbiBmdW5jdGlvbiBmYWlsZWQgdG8gcnVuIGZvciB0aGUgbW9kZWwgdGhhdCBpbmNsdWRlZCBib3RoIGtub3duIGNvdmFyaWF0ZXMgYW5kIHN1cnJvZ2F0ZSB2YXJpYWJsZXMuIFRoaXMgaXMgZXhwZWN0ZWQgaWYgdGhlIHN1cnJvZ2F0ZSB2YXJpYWJsZXMgYXJlIGhpZ2hseSBjb3JyZWxhdGVkIHdpdGggYW55IG9mIHRoZSBrbm93biBjb3ZhcmlhdGVzLCB3aGljaCB3ZSBoYXZlIGFscmVhZHkgb2JzZXJ2ZWQgaXMgdGhlIGNhc2UgZm9yIGRvbm9yIElELgoKYGBge3IgcGxvdF92cGFydH0KcCA8LSBsaXN0KCkKZm9yIChpIGluIG5hbWVzKHZhclRhYmxlcykpIHsKICAgIGluY2wgPC0gc3RyX3JlcGxhY2VfYWxsKGksICJfYW5kXyIsICIgKyAiKQogICAgcFtbaV1dIDwtIHBsb3RWYXJQYXJ0KHZhclRhYmxlc1tbaV1dKSArCiAgICAgICAgbGFicyh0aXRsZT1zdHJfYygiVmFyaWFuY2UgUGFydGl0aW9ucywgIiwgaW5jbCkpCn0KZ2dwcmludChwKQpgYGAKClRoZSByZXN1bHRzIHNlZW4gaGVyZSBhcmUgY29uc2lzdGVudCB3aXRoIHdoYXQgY2FuIGJlIHNlZW4gcXVhbGl0YXRpdmVseSBmcm9tIHRoZSBNRFMgcGxvdHMuIFdpdGggbm8gYmF0Y2ggY29ycmVjdGlvbiwgYmF0Y2ggZWZmZWN0cyBhcmUgYSBzaWduaWZpY2FudCBjb250cmlidXRvciB0byBvdmVyYWxsIHZhcmlhbmNlLCB3aGljaCBpcyBwcm9ibGVtYXRpYyBzaW5jZSBiYXRjaCBhbmQgdGltZSBwb2ludCBhcmUgY29uZm91bmRlZC4gV2l0aCBzaW1wbGUgYmF0Y2ggc3VidHJhY3Rpb24uIHJlY2FsbCB0aGF0IHRoZSBNRFMgcGxvdHMgc2hvd2VkIGFuIG91dGxpZXIgZG9ub3IuIFRoaXMgaXMgcmVmbGVjdGVkIGhlcmUgaW4gdGhlIHZlcnkgbGFyZ2UgY29udHJpYnV0aW9uIG9mIGRvbm9yIElEIHRvIHRoZSB2YXJpYW5jZSBvZiBtYW55IGdlbmVzLiBMYXN0bHksIHRoZSB2YXJpYW5jZSBwYXJ0aXRpb24gcGxvdCBmb3IgQ29tQmF0IGxvb2tzIHF1aXRlIHJlYXNvbmFibGUsIHdpdGggbW9zdCBvZiB0aGUgdmFyaWFuY2UgZm9yIGEgbWFqb3JpdHkgb2YgZ2VuZXMgZXhwbGFpbmVkIGJ5IGdyb3VwIChpLmUuIHRoZSBjb21iaW5hdGlvbiBvZiB0aW1lIHBvaW50IGFuZCBjZWxsIHR5cGUpIGFuZCBzb21lIHNtYWxsZXIgcGFydCBleHBsYWluZWQgYnkgZG9ub3IgSUQuCg==