Preliminary setup

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

library(stringr)
library(glue)
library(magrittr)
library(openxlsx)
library(SummarizedExperiment)
library(MultiAssayExperiment)
library(dplyr)
library(edgeR)
library(limma)
library(DESeq2)
library(csaw)
library(sva)
library(MOFAtools)
library(ggplot2)
library(scales)
library(GGally)
library(ggalt)
library(ggthemes)
library(splines)
library(reshape2)
library(assertthat)
library(ggfortify)
library(broom)
library(ks)
library(RColorBrewer)
library(here)

library(BSgenome.Hsapiens.UCSC.hg38)

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

options(future.globals.maxSize=4 * 1024^3)
library(future)
plan(multicore)

# Export environment variables to make NumPy respect the desired level of
# parallelism. (Different variables are required for different libraries.)
Sys.setenv(MKL_NUM_THREADS = ncores,
           NUMEXPR_NUM_THREADS = ncores,
           OMP_NUM_THREADS = ncores)

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

# Required in order to use DGEList objects with future
length.DGEList <- function(x) {
    length(unclass(x))
}

Data loading

First we load all the count data sets that we will be using.

peak.sexps %<-% {
    bplapply(here("saved_data",
                  glue_data(params, "peak-counts_{genome}_epic_{peak_datasets}.RDS")),
             readRDS) %>%
        setNames(names(params$peak_datasets))
}
rnaseq.sexp %<-% {
    readRDS(here("saved_data", glue_data(params, "SummarizedExperiment_rnaseq_{rna_dataset}_{transcriptome}.RDS")))  %>%
    set_colnames(colData(.)$SRA_run)
}
for (i in names(peak.sexps)) {
    peak.sexps[[i]] %<>%
        .[,colData(.)$chip_antibody != "input"] %>%
        set_colnames(colData(.)$SRA_run) %>%
        set_rownames(rowData(.)$name)
    colData(peak.sexps[[i]]) %<>%
        transform(time_point = str_replace(time_point, "Day", "D")) %>%
        transform(donor_id = str_replace(donor_id, "^Dn*", "Dn")) %>%
        transform(PrimarySample = glue("{cell_type}-{time_point}-{donor_id}"))
}
colData(rnaseq.sexp) %<>%
    transform(time_point = glue("D{days_after_activation}")) %>%
    transform(donor_id = str_replace(donor_id, "^Dn*", "Dn")) %>%
    transform(PrimarySample = glue("{cell_type}-{time_point}-{donor_id}"))

# Fix up stranded RNA-seq counts, if needed
libtype.assayNames <- c(SF="sense.counts", SR="antisense.counts")
if (all(libtype.assayNames %in% assayNames(rnaseq.sexp))) {
    message("Selecting stranded counts for each sample")
    sample.table %<>% mutate(count_type=libtype.assayNames[libType])
    assay(rnaseq.sexp, "unstranded.counts") <- assay(rnaseq.sexp, "counts")
    assay(rnaseq.sexp, "counts") <- lapply(seq_len(nrow(sample.table)), function(i) {
        message("Using ", sample.table[i,]$count_type, " for ", colnames(rnaseq.sexp)[i])
        assay(rnaseq.sexp, sample.table[i,]$count_type %>% as.character)[,i]
    }) %>% do.call(what=cbind)
    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))))
}

sexps <- c(list(RNA=rnaseq.sexp), peak.sexps)

Normalization and filtering

Abundance filtering

First, we filter the RNA-seq data by abundance. The first filter is an abundance filter, which just re-uses the filtering criteria previously defined in other reports.

presence.thresholds <- list(RNA=-1)
for (i in names(presence.thresholds)) {
    suppressWarnings(present <- aveLogCPMWithOffset(asDGEList(sexps[[i]])) >= presence.thresholds[[i]])
    num.features <- nrow(sexps[[i]])
    num.kept <- sum(present)
    percent.kept <- num.kept / num.features * 100
    message(glue("For data type {i}, keeping {num.kept} features out of {num.features} ({format(percent.kept, digits=3)}%) with aveLogCPM >= {presence.thresholds[[i]]}"))
    sexps[[i]] %<>% .[present,]
    rm(present)
}
For data type RNA, keeping 17299 features out of 58051 (29.8%) with aveLogCPM >= -1

Peak Filtering by IDR

For peaks, an abundance filter is not appropriate. Instead we filter by IDR.

idr.threshold <- 0.2
std.chr <- extractSeqlevels("Homo sapiens", "UCSC") %>% setdiff("chrM")
genome.size <- seqinfo(BSgenome.Hsapiens.UCSC.hg38) %>%
    as.data.frame %>% .[std.chr,] %$%
    seqlengths %>% as.numeric %>% sum

for (i in names(params$peak_datasets)) {
    sexp <- sexps[[i]]
    allpeaks <- rowRanges(sexp)
    peaks <- allpeaks[allpeaks$qValue >= -log10(idr.threshold)]
    pct.covered <- width(peaks) %>% sum %>% divide_by(genome.size) %>% multiply_by(100)
    filtered.sexp <- sexp %>% subsetByOverlaps(peaks)
    mean.pct.reads <- filtered.sexp %>% assay("counts") %>%
        colSums %>% divide_by(colData(sexp)$totals) %>% multiply_by(100) %>%
        mean
    message(glue("For histone mark {i}, selected {length(peaks)} peaks at an IDR threshold of {format(idr.threshold, digits=3)}, with an average width of {round(mean(width(peaks)))} nucleotides and covering a total of {format(pct.covered, digits=3)}% of the genome, containing on average {format(mean.pct.reads, digits=3)}% of reads."))
    sexps[[i]] <- filtered.sexp
}
For histone mark H3K4me2, selected 21631 peaks at an IDR threshold of 0.2, with an average width of 3512 nucleotides and covering a total of 2.46% of the genome, containing on average 17% of reads.
For histone mark H3K4me3, selected 10467 peaks at an IDR threshold of 0.2, with an average width of 3105 nucleotides and covering a total of 1.05% of the genome, containing on average 10.4% of reads.
For histone mark H3K27me3, selected 24278 peaks at an IDR threshold of 0.2, with an average width of 15710 nucleotides and covering a total of 12.4% of the genome, containing on average 24.3% of reads.

Calculating normalization factors

We need to normalize and filter all data sets before feeding them to MOFA. We begin by computing normalization factors. (potentially with offsets derived from effective gene lengths for RNA-seq.)

for (i in names(sexps)) {
    if (! "totals" %in% colnames(colData(sexps[[i]]))) {
        colData(sexps[[i]])$totals <- colSums(assay(sexps[[i]], "counts"))
    }
    colData(sexps[[i]])$norm.factors <- calcNormFactors(asDGEList(sexps[[i]]))$samples$norm.factors

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

Outlier sample filtering

Next, we identify possible outlier samples in the data. For our purposes, these are defined as samples for which the percentage of zero counts is 3 standard deviations below the mean for all samples. This criterion was determined through exploratory data analysis. We drop these samples before running MOFA.

outlier.samples <- sexps %>%
    lapply(. %>%
               set_colnames(colData(.)$PrimarySample) %>%
               assay("counts") %>%
               is_greater_than(0) %>%
               colMeans %>%
               .[. < mean(.) - sd(.) * 3] %>%
               names)
for (i in names(sexps))  {
    outliers <- sexps[[i]]$PrimarySample %in% outlier.samples[[i]]
    if (any(outliers)) {
        message(glue("Removing out {sum(outliers)} outlier sample{ifelse(sum(outliers) == 1, '', 's')} from {i} data."))
    }
    sexps[[i]] %<>% .[,!outliers]
}
Removing out 1 outlier sample from H3K4me2 data.
Removing out 1 outlier sample from H3K4me3 data.
Removing out 1 outlier sample from H3K27me3 data.

Variance filtering

The next filter is a variance filter, selecting N genes/peaks from each data set with the largest dispersions. The value of N for each data set is determined based on the number of significantly differentially abundant features identified in previous analyses.

num.keep <- list(RNA=10000, H3K4me2=13000, H3K4me3=6000, H3K27me3=11000)
#num.keep <- list(RNA=10000, H3K4me2=10000, H3K4me3=10000, H3K27me3=10000)
for (i in names(num.keep)) {
    if (num.keep[[i]] >= nrow(sexps[[i]])) {
        message(glue("For data type {i}, no variance filter is needed; keeping all {nrow(sexps[[i]])} features"))
    } else {
        assert_that(num.keep[[i]] <= nrow(sexps[[i]]))
        d <- asDGEList(sexps[[i]])
        design <- matrix(1, nrow=ncol(d), ncol=1)
        d <- estimateDisp(d, design, prior.df = 0)
        plotBCV(d, main = glue("BCV plot for {i}"))
        ostat <- nrow(d) - num.keep[[i]] + 1
        disp.threshold <- d$tagwise.dispersion %>% sort(partial=ostat) %>% .[ostat]
        message(glue("For data type {i}, keeping the top {num.keep[[i]]} features with the highest dispersions out of {nrow(d)}"))
        keep <- d$tagwise.dispersion >= disp.threshold
        assert_that(sum(keep) == num.keep[[i]])
        sexps[[i]] %<>% .[keep,]
    }
}
For data type RNA, keeping the top 10000 features with the highest dispersions out of 17299

For data type H3K4me2, keeping the top 13000 features with the highest dispersions out of 24026

For data type H3K4me3, keeping the top 6000 features with the highest dispersions out of 12684

For data type H3K27me3, keeping the top 11000 features with the highest dispersions out of 24945

Data Transformation

Now we transform all the filtered datasets to log2 CPM, using a prior count of 2, since that is what plotMDS uses, and MOFA is another factor analysis method similar to MDS or PCA.

logcpmlist <- lapply(sexps, . %>% asDGEList %>% cpmWithOffset(log=TRUE, prior.count=2))

Preparing the data for MOFA

Finally, we are ready to combine all filtered, transformed datasets into a MultiAssayExperiment.

make.samplemap <- function(explist, primary_colname="primary") {
    maps <- lapply(explist, . %>% {data.frame(primary=colData(.)[[primary_colname]], colname=colnames(.),
                                              stringsAsFactors = FALSE)})
    x <- listToMap(maps)
}
biosample.meta <- colData(rnaseq.sexp)[c("cell_type", "activated", "time_point", "days_after_activation", "donor_id")] %>%
    transform(
        time_point = factor(time_point, levels=unique(time_point[order(days_after_activation)])),
        donor_id = factor(donor_id),
        PrimarySample = glue("{cell_type}-{time_point}-{donor_id}")) %>%
    set_rownames(.$PrimarySample)
smap <- make.samplemap(sexps, "PrimarySample")
# Since MOFA is related to MDS, we use the same prior count as plotMDS
mae <- MultiAssayExperiment(experiments = logcpmlist, sampleMap = smap, colData = biosample.meta)

We run MOFA several times with different random seeds so that we can verify that it consistently converges to the same result. Then we run it once with a tighter tolerance bound to obtain the final model.

mofa <- createMOFAobject(mae)
Creating MOFA object from a MultiAssayExperiment object...
Untrained MOFA model with the following characteristics: 
 Number of views: 4 
 View names: RNA H3K4me2 H3K4me3 H3K27me3 
 Number of features per view: 10000 13000 6000 11000 
 Number of samples: 32 
ModelOptions <- getDefaultModelOpts(mofa)
Likelihoods guessed automatically: gaussian gaussian gaussian gaussian
TrainOptions.final <- getDefaultTrainOpts() %>% transform(maxiter=30000)
# Looser tolerance bound for exploration
TrainOptions.explore <- getDefaultTrainOpts() %>% transform(tolerance=0.1)

tmpd <- tempfile(tmpdir = tempdir(), pattern = "mofatemp")
DirOptions.final <- list(
    dataDir = tempfile(tmpdir = tmpd, pattern = "mofarun_"),
    outFile = here("saved_data", "mofa", glue_data(params, "mofa-model_{genome}_{transcriptome}_rna+peak.hdf5")),
    rdsFile = here("saved_data", "mofa", glue_data(params, "mofa-model_{genome}_{transcriptome}_rna+peak.RDS"))
)
DirOptions.exploreList <- lapply(seq_len(params$mofa_runs), function(i) {
    list(
        dataDir = tempfile(tmpdir = tmpd, pattern = str_c("mofarun", i, "_")),
        outFile = here("saved_data", "mofa", glue_data(params, "mofa-model_{genome}_{transcriptome}_rna+peak_explore{i}.hdf5")),
        rdsFile = here("saved_data", "mofa", glue_data(params, "mofa-model_{genome}_{transcriptome}_rna+peak_explore{i}.RDS"))
    )
})
final.random.seed <- 1986
explore.random.seeds <- final.random.seed + seq_len(params$mofa_runs)

Model fitting

# MOFA is already parallelized, so we run sequentially
mofa.explore <- list()
for (i in seq_len(params$mofa_runs)) {
    DirOpt <- DirOptions.exploreList[[i]]
    seed <- explore.random.seeds[i]
    dir.create(DirOpt$dataDir, recursive=TRUE)
    mofa.explore[[i]] <- mofa %>%
        prepareMOFA(DirOptions = DirOpt, ModelOptions = ModelOptions, TrainOptions = TrainOptions.explore) %>%
        runMOFA(DirOptions = DirOpt, seed=seed)
    saveRDS(mofa.explore[[i]], DirOpt$rdsFile)
}
Checking data options...
No data options specified, using default...
Checking training options...
Checking model options...
Storing input views in /tmp/RtmpeSz872/mofatemp2bfc68c57ebe/mofarun1_2bfc72ac46e4...
Checking data options...
No data options specified, using default...
Checking training options...
Checking model options...
Storing input views in /tmp/RtmpeSz872/mofatemp2bfc68c57ebe/mofarun2_2bfc3020e576...
Checking data options...
No data options specified, using default...
Checking training options...
Checking model options...
Storing input views in /tmp/RtmpeSz872/mofatemp2bfc68c57ebe/mofarun3_2bfc21fef2f0...
Checking data options...
No data options specified, using default...
Checking training options...
Checking model options...
Storing input views in /tmp/RtmpeSz872/mofatemp2bfc68c57ebe/mofarun4_2bfc5a352cc8...
unlink(tmpd, recursive=TRUE)
dir.create(DirOptions.final$dataDir, recursive=TRUE)
mofa.final <- mofa %>%
    prepareMOFA(DirOptions = DirOptions.final, ModelOptions = ModelOptions, TrainOptions = TrainOptions.final) %>%
    runMOFA(DirOptions = DirOptions.final, seed=final.random.seed)
Checking data options...
No data options specified, using default...
Checking training options...
Checking model options...
Storing input views in /tmp/RtmpeSz872/mofatemp2bfc68c57ebe/mofarun_2bfc5a557d4b...
saveRDS(mofa.final, DirOptions.final$rdsFile)
unlink(tmpd, recursive=TRUE)
#mofa.final <- mofa.explore[[1]]
## TODO: saveRDS

Basic model QC

After the run finishes. We produce a few basic QC plots. First, we plot the variance explained by each factor in each model.

r2list <- lapply(mofa.explore, calculateVarianceExplained)
r2.final <- calculateVarianceExplained(mofa.final)

We also make a plot comparing the factors between multiple models, to verify that each model is discovering roughly the same set of factors.

invisible(compareModels(mofa.explore))

Since all the models seem to be discovering the same factors, we can use any model we choose. For the first model, we make a “bee swarm plot” of each factor:

# p <- list(
#     TimePoint=plotFactorBeeswarm(
#         mofa.final,
#         factors = seq_len(ncol(getFactors(mofa.final, include_intercept = FALSE))),
#         color_by = "time_point") +
#         aes(x=0) + xlab("") + ylab("Factor Value") +
#         facet_wrap(~factor, scales="free") +
#         scale_x_continuous(breaks=NULL),
#     CellType=plotFactorBeeswarm(
#         mofa.final,
#         factors = seq_len(ncol(getFactors(mofa.final, include_intercept = FALSE))),
#         color_by = "cell_type") +
#         aes(x=0) + xlab("") + ylab("Factor Value") +
#         facet_wrap(~factor, scales="free") +
#         scale_x_continuous(breaks=NULL),
#     Donor=plotFactorBeeswarm(
#         mofa.final,
#         factors = seq_len(ncol(getFactors(mofa.final, include_intercept = FALSE))),
#         color_by = "donor_id") +
#         aes(x=0) + xlab("") + ylab("Factor Value") +
#         facet_wrap(~factor, scales="free") +
#         scale_x_continuous(breaks=NULL))
# ggprint(p)
LS0tCnRpdGxlOiAiTU9GQSBtb2RlbCBmaXR0aW5nIGZvciBSTkEtc2VxIGFuZCBwZWFrIGhpc3RvbmUgQ2hJUC1zZXEgZGF0YSIKYXV0aG9yOiAiUnlhbiBDLiBUaG9tcHNvbiIKZGF0ZTogJ2ByIGdzdWIoIlxccysiLCAiICIsIGZvcm1hdChTeXMudGltZSgpLCAiJUIgJWUsICVZIikpYCcKb3V0cHV0OgogICAgaHRtbF9kb2N1bWVudDogZGVmYXVsdAogICAgaHRtbF9ub3RlYm9vazogZGVmYXVsdApwYXJhbXM6CiAgICBtb2ZhX3J1bnM6IDQKICAgIGdlbm9tZToKICAgICAgICB2YWx1ZTogaGczOC5hbmFseXNpc1NldAogICAgdHJhbnNjcmlwdG9tZToKICAgICAgICB2YWx1ZTogZW5zZW1ibC44NQogICAgcm5hX2RhdGFzZXQ6CiAgICAgICAgdmFsdWU6ICJzaG9hbF9oZzM4LmFuYWx5c2lzU2V0IgogICAgcHJvbW90ZXJfZGF0YXNldHM6CiAgICAgICAgdmFsdWU6ICFyIGMoSDNLNG1lMiA9ICIxa2JwLXJhZGl1c18xNDdicC1yZWFkc19IM0s0bWUyIiwKICAgICAgICAgICAgICAgICAgICBIM0s0bWUzID0gIjFrYnAtcmFkaXVzXzE0N2JwLXJlYWRzX0gzSzRtZTMiLAogICAgICAgICAgICAgICAgICAgIEgzSzI3bWUzID0gIjIuNWticC1yYWRpdXNfMTQ3YnAtcmVhZHNfSDNLMjdtZTMiKQogICAgcGVha19kYXRhc2V0czoKICAgICAgICB2YWx1ZTogIXIgYyhIM0s0bWUyID0gIkgzSzRtZTJfMTQ3YnAtcmVhZHMiLAogICAgICAgICAgICAgICAgICAgIEgzSzRtZTMgPSAiSDNLNG1lM18xNDdicC1yZWFkcyIsCiAgICAgICAgICAgICAgICAgICAgSDNLMjdtZTMgPSAiSDNLMjdtZTNfMTQ3YnAtcmVhZHMiKQogICAgd2luZG93X2RhdGFzZXRzOgogICAgICAgIHZhbHVlOiAhciBjKEgzSzRtZTIgPSAiNTAwYnAtd2luZG93c18xNDdicC1yZWFkc19IM0s0bWUyIiwKICAgICAgICAgICAgICAgICAgICBIM0s0bWUzID0gIjUwMGJwLXdpbmRvd3NfMTQ3YnAtcmVhZHNfSDNLNG1lMyIsCiAgICAgICAgICAgICAgICAgICAgSDNLMjdtZTMgPSAiNTAwYnAtd2luZG93c18xNDdicC1yZWFkc19IM0syN21lMyIpCi0tLQoKIyBQcmVsaW1pbmFyeSBzZXR1cAoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0UsIGNhY2hlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHJldGluYT0yLCBjYWNoZT1UUlVFLCBhdXRvZGVwPVRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZS5leHRyYSA9IGxpc3QocGFyYW1zPXBhcmFtcyksCiAgICAgICAgICAgICAgICAgICAgICAjIGh0dHBzOi8vZ2l0aHViLmNvbS95aWh1aS9rbml0ci9pc3N1ZXMvNTcyCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZS5sYXp5PUZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OCwKICAgICAgICAgICAgICAgICAgICAgIGNhY2hlLnBhdGggPSBwYXN0ZTAoCiAgICAgICAgICAgICAgICAgICAgICAgICAgaGVyZTo6aGVyZSgiY2FjaGUiLCAicGVhay1tb2ZhLXJ1biIpLAogICAgICAgICAgICAgICAgICAgICAgICAgIC5QbGF0Zm9ybSRmaWxlLnNlcCkpCmBgYAoKRmlyc3Qgd2UgbG9hZCB0aGUgbmVjZXNzYXJ5IGxpYnJhcmllcywgYWxvbmcgd2l0aCBhIHNldCBvZiB1dGlsaXR5IGZ1bmN0aW9ucy4KCmBgYHtyIGxvYWRfcGFja2FnZXMsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoZ2x1ZSkKbGlicmFyeShtYWdyaXR0cikKbGlicmFyeShvcGVueGxzeCkKbGlicmFyeShTdW1tYXJpemVkRXhwZXJpbWVudCkKbGlicmFyeShNdWx0aUFzc2F5RXhwZXJpbWVudCkKbGlicmFyeShkcGx5cikKbGlicmFyeShlZGdlUikKbGlicmFyeShsaW1tYSkKbGlicmFyeShERVNlcTIpCmxpYnJhcnkoY3NhdykKbGlicmFyeShzdmEpCmxpYnJhcnkoTU9GQXRvb2xzKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KEdHYWxseSkKbGlicmFyeShnZ2FsdCkKbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeShzcGxpbmVzKQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KGFzc2VydHRoYXQpCmxpYnJhcnkoZ2dmb3J0aWZ5KQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KGtzKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeShoZXJlKQoKbGlicmFyeShCU2dlbm9tZS5Ic2FwaWVucy5VQ1NDLmhnMzgpCgpsaWJyYXJ5KGRvUGFyYWxsZWwpCm5jb3JlcyA8LSBnZXRPcHRpb24oIm1jLmNvcmVzIiwgZGVmYXVsdD1wYXJhbGxlbDo6ZGV0ZWN0Q29yZXMobG9naWNhbCA9IEZBTFNFKSkKb3B0aW9ucyhtYy5jb3Jlcz1uY29yZXMpCnJlZ2lzdGVyRG9QYXJhbGxlbChjb3Jlcz1uY29yZXMpCmxpYnJhcnkoQmlvY1BhcmFsbGVsKQpyZWdpc3RlcihTZXJpYWxQYXJhbSgpKQpyZWdpc3RlcihEb3BhclBhcmFtKCkpCgpvcHRpb25zKGZ1dHVyZS5nbG9iYWxzLm1heFNpemU9NCAqIDEwMjReMykKbGlicmFyeShmdXR1cmUpCnBsYW4obXVsdGljb3JlKQoKIyBFeHBvcnQgZW52aXJvbm1lbnQgdmFyaWFibGVzIHRvIG1ha2UgTnVtUHkgcmVzcGVjdCB0aGUgZGVzaXJlZCBsZXZlbCBvZgojIHBhcmFsbGVsaXNtLiAoRGlmZmVyZW50IHZhcmlhYmxlcyBhcmUgcmVxdWlyZWQgZm9yIGRpZmZlcmVudCBsaWJyYXJpZXMuKQpTeXMuc2V0ZW52KE1LTF9OVU1fVEhSRUFEUyA9IG5jb3JlcywKICAgICAgICAgICBOVU1FWFBSX05VTV9USFJFQURTID0gbmNvcmVzLAogICAgICAgICAgIE9NUF9OVU1fVEhSRUFEUyA9IG5jb3JlcykKCnNvdXJjZShoZXJlKCJzY3JpcHRzL3V0aWxpdGllcy5SIikpCgojIFJlcXVpcmVkIGluIG9yZGVyIHRvIHVzZSBER0VMaXN0IG9iamVjdHMgd2l0aCBmdXR1cmUKbGVuZ3RoLkRHRUxpc3QgPC0gZnVuY3Rpb24oeCkgewogICAgbGVuZ3RoKHVuY2xhc3MoeCkpCn0KYGBgCgojIERhdGEgbG9hZGluZwoKRmlyc3Qgd2UgbG9hZCBhbGwgdGhlIGNvdW50IGRhdGEgc2V0cyB0aGF0IHdlIHdpbGwgYmUgdXNpbmcuCgpgYGB7ciBsb2FkX2NvdW50c30KcGVhay5zZXhwcyAlPC0lIHsKICAgIGJwbGFwcGx5KGhlcmUoInNhdmVkX2RhdGEiLAogICAgICAgICAgICAgICAgICBnbHVlX2RhdGEocGFyYW1zLCAicGVhay1jb3VudHNfe2dlbm9tZX1fZXBpY197cGVha19kYXRhc2V0c30uUkRTIikpLAogICAgICAgICAgICAgcmVhZFJEUykgJT4lCiAgICAgICAgc2V0TmFtZXMobmFtZXMocGFyYW1zJHBlYWtfZGF0YXNldHMpKQp9CnJuYXNlcS5zZXhwICU8LSUgewogICAgcmVhZFJEUyhoZXJlKCJzYXZlZF9kYXRhIiwgZ2x1ZV9kYXRhKHBhcmFtcywgIlN1bW1hcml6ZWRFeHBlcmltZW50X3JuYXNlcV97cm5hX2RhdGFzZXR9X3t0cmFuc2NyaXB0b21lfS5SRFMiKSkpICAlPiUKICAgIHNldF9jb2xuYW1lcyhjb2xEYXRhKC4pJFNSQV9ydW4pCn0KZm9yIChpIGluIG5hbWVzKHBlYWsuc2V4cHMpKSB7CiAgICBwZWFrLnNleHBzW1tpXV0gJTw+JQogICAgICAgIC5bLGNvbERhdGEoLikkY2hpcF9hbnRpYm9keSAhPSAiaW5wdXQiXSAlPiUKICAgICAgICBzZXRfY29sbmFtZXMoY29sRGF0YSguKSRTUkFfcnVuKSAlPiUKICAgICAgICBzZXRfcm93bmFtZXMocm93RGF0YSguKSRuYW1lKQogICAgY29sRGF0YShwZWFrLnNleHBzW1tpXV0pICU8PiUKICAgICAgICB0cmFuc2Zvcm0odGltZV9wb2ludCA9IHN0cl9yZXBsYWNlKHRpbWVfcG9pbnQsICJEYXkiLCAiRCIpKSAlPiUKICAgICAgICB0cmFuc2Zvcm0oZG9ub3JfaWQgPSBzdHJfcmVwbGFjZShkb25vcl9pZCwgIl5EbioiLCAiRG4iKSkgJT4lCiAgICAgICAgdHJhbnNmb3JtKFByaW1hcnlTYW1wbGUgPSBnbHVlKCJ7Y2VsbF90eXBlfS17dGltZV9wb2ludH0te2Rvbm9yX2lkfSIpKQp9CmNvbERhdGEocm5hc2VxLnNleHApICU8PiUKICAgIHRyYW5zZm9ybSh0aW1lX3BvaW50ID0gZ2x1ZSgiRHtkYXlzX2FmdGVyX2FjdGl2YXRpb259IikpICU+JQogICAgdHJhbnNmb3JtKGRvbm9yX2lkID0gc3RyX3JlcGxhY2UoZG9ub3JfaWQsICJeRG4qIiwgIkRuIikpICU+JQogICAgdHJhbnNmb3JtKFByaW1hcnlTYW1wbGUgPSBnbHVlKCJ7Y2VsbF90eXBlfS17dGltZV9wb2ludH0te2Rvbm9yX2lkfSIpKQoKIyBGaXggdXAgc3RyYW5kZWQgUk5BLXNlcSBjb3VudHMsIGlmIG5lZWRlZApsaWJ0eXBlLmFzc2F5TmFtZXMgPC0gYyhTRj0ic2Vuc2UuY291bnRzIiwgU1I9ImFudGlzZW5zZS5jb3VudHMiKQppZiAoYWxsKGxpYnR5cGUuYXNzYXlOYW1lcyAlaW4lIGFzc2F5TmFtZXMocm5hc2VxLnNleHApKSkgewogICAgbWVzc2FnZSgiU2VsZWN0aW5nIHN0cmFuZGVkIGNvdW50cyBmb3IgZWFjaCBzYW1wbGUiKQogICAgc2FtcGxlLnRhYmxlICU8PiUgbXV0YXRlKGNvdW50X3R5cGU9bGlidHlwZS5hc3NheU5hbWVzW2xpYlR5cGVdKQogICAgYXNzYXkocm5hc2VxLnNleHAsICJ1bnN0cmFuZGVkLmNvdW50cyIpIDwtIGFzc2F5KHJuYXNlcS5zZXhwLCAiY291bnRzIikKICAgIGFzc2F5KHJuYXNlcS5zZXhwLCAiY291bnRzIikgPC0gbGFwcGx5KHNlcV9sZW4obnJvdyhzYW1wbGUudGFibGUpKSwgZnVuY3Rpb24oaSkgewogICAgICAgIG1lc3NhZ2UoIlVzaW5nICIsIHNhbXBsZS50YWJsZVtpLF0kY291bnRfdHlwZSwgIiBmb3IgIiwgY29sbmFtZXMocm5hc2VxLnNleHApW2ldKQogICAgICAgIGFzc2F5KHJuYXNlcS5zZXhwLCBzYW1wbGUudGFibGVbaSxdJGNvdW50X3R5cGUgJT4lIGFzLmNoYXJhY3RlcilbLGldCiAgICB9KSAlPiUgZG8uY2FsbCh3aGF0PWNiaW5kKQogICAgdG90YWwuY291bnRzIDwtIHNleHAgJT4lIGFzc2F5cyAlPiUgc2FwcGx5KGNvbFN1bXMpICU+JSBkYXRhLmZyYW1lICU+JQogICAgICAgIG11dGF0ZShTYW1wbGVOYW1lPXJvdy5uYW1lcyguKSkgJT4lCiAgICAgICAgaW5uZXJfam9pbihzYW1wbGUudGFibGUsIGJ5PSJTYW1wbGVOYW1lIikKICAgIHRvdGFsLmNvdW50cyAlJCUgaW52aXNpYmxlKGFzc2VydF90aGF0KGFsbChjb3VudHMgPT0gcG1heChzZW5zZS5jb3VudHMsIGFudGlzZW5zZS5jb3VudHMpKSkpCn0KCnNleHBzIDwtIGMobGlzdChSTkE9cm5hc2VxLnNleHApLCBwZWFrLnNleHBzKQpgYGAKCiMgTm9ybWFsaXphdGlvbiBhbmQgZmlsdGVyaW5nCgojIyBBYnVuZGFuY2UgZmlsdGVyaW5nCgpGaXJzdCwgd2UgZmlsdGVyIHRoZSBSTkEtc2VxIGRhdGEgYnkgYWJ1bmRhbmNlLgpUaGUgZmlyc3QgZmlsdGVyIGlzIGFuIGFidW5kYW5jZSBmaWx0ZXIsIHdoaWNoIGp1c3QgcmUtdXNlcyB0aGUgZmlsdGVyaW5nIGNyaXRlcmlhIHByZXZpb3VzbHkgZGVmaW5lZCBpbiBvdGhlciByZXBvcnRzLgoKYGBge3IgZmlsdGVyX2FidW5kYW5jZX0KcHJlc2VuY2UudGhyZXNob2xkcyA8LSBsaXN0KFJOQT0tMSkKZm9yIChpIGluIG5hbWVzKHByZXNlbmNlLnRocmVzaG9sZHMpKSB7CiAgICBzdXBwcmVzc1dhcm5pbmdzKHByZXNlbnQgPC0gYXZlTG9nQ1BNV2l0aE9mZnNldChhc0RHRUxpc3Qoc2V4cHNbW2ldXSkpID49IHByZXNlbmNlLnRocmVzaG9sZHNbW2ldXSkKICAgIG51bS5mZWF0dXJlcyA8LSBucm93KHNleHBzW1tpXV0pCiAgICBudW0ua2VwdCA8LSBzdW0ocHJlc2VudCkKICAgIHBlcmNlbnQua2VwdCA8LSBudW0ua2VwdCAvIG51bS5mZWF0dXJlcyAqIDEwMAogICAgbWVzc2FnZShnbHVlKCJGb3IgZGF0YSB0eXBlIHtpfSwga2VlcGluZyB7bnVtLmtlcHR9IGZlYXR1cmVzIG91dCBvZiB7bnVtLmZlYXR1cmVzfSAoe2Zvcm1hdChwZXJjZW50LmtlcHQsIGRpZ2l0cz0zKX0lKSB3aXRoIGF2ZUxvZ0NQTSA+PSB7cHJlc2VuY2UudGhyZXNob2xkc1tbaV1dfSIpKQogICAgc2V4cHNbW2ldXSAlPD4lIC5bcHJlc2VudCxdCiAgICBybShwcmVzZW50KQp9CmBgYAoKIyMgUGVhayBGaWx0ZXJpbmcgYnkgSURSCgpGb3IgcGVha3MsIGFuIGFidW5kYW5jZSBmaWx0ZXIgaXMgbm90IGFwcHJvcHJpYXRlLiBJbnN0ZWFkIHdlIGZpbHRlciBieSBJRFIuCgpgYGB7ciBmaWx0ZXJfaWRyfQppZHIudGhyZXNob2xkIDwtIDAuMgpzdGQuY2hyIDwtIGV4dHJhY3RTZXFsZXZlbHMoIkhvbW8gc2FwaWVucyIsICJVQ1NDIikgJT4lIHNldGRpZmYoImNock0iKQpnZW5vbWUuc2l6ZSA8LSBzZXFpbmZvKEJTZ2Vub21lLkhzYXBpZW5zLlVDU0MuaGczOCkgJT4lCiAgICBhcy5kYXRhLmZyYW1lICU+JSAuW3N0ZC5jaHIsXSAlJCUKICAgIHNlcWxlbmd0aHMgJT4lIGFzLm51bWVyaWMgJT4lIHN1bQoKZm9yIChpIGluIG5hbWVzKHBhcmFtcyRwZWFrX2RhdGFzZXRzKSkgewogICAgc2V4cCA8LSBzZXhwc1tbaV1dCiAgICBhbGxwZWFrcyA8LSByb3dSYW5nZXMoc2V4cCkKICAgIHBlYWtzIDwtIGFsbHBlYWtzW2FsbHBlYWtzJHFWYWx1ZSA+PSAtbG9nMTAoaWRyLnRocmVzaG9sZCldCiAgICBwY3QuY292ZXJlZCA8LSB3aWR0aChwZWFrcykgJT4lIHN1bSAlPiUgZGl2aWRlX2J5KGdlbm9tZS5zaXplKSAlPiUgbXVsdGlwbHlfYnkoMTAwKQogICAgZmlsdGVyZWQuc2V4cCA8LSBzZXhwICU+JSBzdWJzZXRCeU92ZXJsYXBzKHBlYWtzKQogICAgbWVhbi5wY3QucmVhZHMgPC0gZmlsdGVyZWQuc2V4cCAlPiUgYXNzYXkoImNvdW50cyIpICU+JQogICAgICAgIGNvbFN1bXMgJT4lIGRpdmlkZV9ieShjb2xEYXRhKHNleHApJHRvdGFscykgJT4lIG11bHRpcGx5X2J5KDEwMCkgJT4lCiAgICAgICAgbWVhbgogICAgbWVzc2FnZShnbHVlKCJGb3IgaGlzdG9uZSBtYXJrIHtpfSwgc2VsZWN0ZWQge2xlbmd0aChwZWFrcyl9IHBlYWtzIGF0IGFuIElEUiB0aHJlc2hvbGQgb2Yge2Zvcm1hdChpZHIudGhyZXNob2xkLCBkaWdpdHM9Myl9LCB3aXRoIGFuIGF2ZXJhZ2Ugd2lkdGggb2Yge3JvdW5kKG1lYW4od2lkdGgocGVha3MpKSl9IG51Y2xlb3RpZGVzIGFuZCBjb3ZlcmluZyBhIHRvdGFsIG9mIHtmb3JtYXQocGN0LmNvdmVyZWQsIGRpZ2l0cz0zKX0lIG9mIHRoZSBnZW5vbWUsIGNvbnRhaW5pbmcgb24gYXZlcmFnZSB7Zm9ybWF0KG1lYW4ucGN0LnJlYWRzLCBkaWdpdHM9Myl9JSBvZiByZWFkcy4iKSkKICAgIHNleHBzW1tpXV0gPC0gZmlsdGVyZWQuc2V4cAp9CmBgYAoKIyMgQ2FsY3VsYXRpbmcgbm9ybWFsaXphdGlvbiBmYWN0b3JzCgpXZSBuZWVkIHRvIG5vcm1hbGl6ZSBhbmQgZmlsdGVyIGFsbCBkYXRhIHNldHMgYmVmb3JlIGZlZWRpbmcgdGhlbSB0byBNT0ZBLiBXZSBiZWdpbiBieSBjb21wdXRpbmcgbm9ybWFsaXphdGlvbiBmYWN0b3JzLiAocG90ZW50aWFsbHkgd2l0aCBvZmZzZXRzIGRlcml2ZWQgZnJvbSBlZmZlY3RpdmUgZ2VuZSBsZW5ndGhzIGZvciBSTkEtc2VxLikKCmBgYHtyIG5vcm1hbGl6ZV9kYXRhfQpmb3IgKGkgaW4gbmFtZXMoc2V4cHMpKSB7CiAgICBpZiAoISAidG90YWxzIiAlaW4lIGNvbG5hbWVzKGNvbERhdGEoc2V4cHNbW2ldXSkpKSB7CiAgICAgICAgY29sRGF0YShzZXhwc1tbaV1dKSR0b3RhbHMgPC0gY29sU3Vtcyhhc3NheShzZXhwc1tbaV1dLCAiY291bnRzIikpCiAgICB9CiAgICBjb2xEYXRhKHNleHBzW1tpXV0pJG5vcm0uZmFjdG9ycyA8LSBjYWxjTm9ybUZhY3RvcnMoYXNER0VMaXN0KHNleHBzW1tpXV0pKSRzYW1wbGVzJG5vcm0uZmFjdG9ycwoKICAgIGlmICgibGVuZ3RoIiAlaW4lIGFzc2F5TmFtZXMoc2V4cHNbW2ldXSkpIHsKICAgICAgICBub3JtTWF0IDwtIGFzc2F5KHNleHBzW1tpXV0sICJsZW5ndGgiKSAlPiUgZGl2aWRlX2J5KGV4cChyb3dNZWFucyhsb2coLikpKSkKICAgICAgICBub3JtQ291bnRzIDwtIGFzc2F5KHNleHBzW1tpXV0sICJjb3VudHMiKS9ub3JtTWF0CiAgICAgICAgbGliLm9mZnNldHMgPC0gbG9nKGNhbGNOb3JtRmFjdG9ycyhub3JtQ291bnRzKSkgKyBsb2coY29sU3Vtcyhub3JtQ291bnRzKSkKICAgICAgICBhc3NheShzZXhwc1tbaV1dLCAib2Zmc2V0IikgPC0gdCh0KGxvZyhub3JtTWF0KSkgKyBsaWIub2Zmc2V0cykKICAgIH0KfQpgYGAKCiMjIE91dGxpZXIgc2FtcGxlIGZpbHRlcmluZwoKTmV4dCwgd2UgaWRlbnRpZnkgcG9zc2libGUgb3V0bGllciBzYW1wbGVzIGluIHRoZSBkYXRhLiBGb3Igb3VyIHB1cnBvc2VzLCB0aGVzZSBhcmUgZGVmaW5lZCBhcyBzYW1wbGVzIGZvciB3aGljaCB0aGUgcGVyY2VudGFnZSBvZiB6ZXJvIGNvdW50cyBpcyAzIHN0YW5kYXJkIGRldmlhdGlvbnMgYmVsb3cgdGhlIG1lYW4gZm9yIGFsbCBzYW1wbGVzLiBUaGlzIGNyaXRlcmlvbiB3YXMgZGV0ZXJtaW5lZCB0aHJvdWdoIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMuIFdlIGRyb3AgdGhlc2Ugc2FtcGxlcyBiZWZvcmUgcnVubmluZyBNT0ZBLgoKYGBge3IgZmlsdGVyX291dGxpZXJfc2FtcGxlc30Kb3V0bGllci5zYW1wbGVzIDwtIHNleHBzICU+JQogICAgbGFwcGx5KC4gJT4lCiAgICAgICAgICAgICAgIHNldF9jb2xuYW1lcyhjb2xEYXRhKC4pJFByaW1hcnlTYW1wbGUpICU+JQogICAgICAgICAgICAgICBhc3NheSgiY291bnRzIikgJT4lCiAgICAgICAgICAgICAgIGlzX2dyZWF0ZXJfdGhhbigwKSAlPiUKICAgICAgICAgICAgICAgY29sTWVhbnMgJT4lCiAgICAgICAgICAgICAgIC5bLiA8IG1lYW4oLikgLSBzZCguKSAqIDNdICU+JQogICAgICAgICAgICAgICBuYW1lcykKZm9yIChpIGluIG5hbWVzKHNleHBzKSkgIHsKICAgIG91dGxpZXJzIDwtIHNleHBzW1tpXV0kUHJpbWFyeVNhbXBsZSAlaW4lIG91dGxpZXIuc2FtcGxlc1tbaV1dCiAgICBpZiAoYW55KG91dGxpZXJzKSkgewogICAgICAgIG1lc3NhZ2UoZ2x1ZSgiUmVtb3Zpbmcgb3V0IHtzdW0ob3V0bGllcnMpfSBvdXRsaWVyIHNhbXBsZXtpZmVsc2Uoc3VtKG91dGxpZXJzKSA9PSAxLCAnJywgJ3MnKX0gZnJvbSB7aX0gZGF0YS4iKSkKICAgIH0KICAgIHNleHBzW1tpXV0gJTw+JSAuWywhb3V0bGllcnNdCn0KYGBgCgojIyBWYXJpYW5jZSBmaWx0ZXJpbmcKClRoZSBuZXh0IGZpbHRlciBpcyBhIHZhcmlhbmNlIGZpbHRlciwgc2VsZWN0aW5nIE4gZ2VuZXMvcGVha3MgZnJvbSBlYWNoIGRhdGEgc2V0IHdpdGggdGhlIGxhcmdlc3QgZGlzcGVyc2lvbnMuIFRoZSB2YWx1ZSBvZiBOIGZvciBlYWNoIGRhdGEgc2V0IGlzIGRldGVybWluZWQgYmFzZWQgb24gdGhlIG51bWJlciBvZiBzaWduaWZpY2FudGx5IGRpZmZlcmVudGlhbGx5IGFidW5kYW50IGZlYXR1cmVzIGlkZW50aWZpZWQgaW4gcHJldmlvdXMgYW5hbHlzZXMuCgpgYGB7ciBmaWx0ZXJfdmFyaWFuY2V9Cm51bS5rZWVwIDwtIGxpc3QoUk5BPTEwMDAwLCBIM0s0bWUyPTEzMDAwLCBIM0s0bWUzPTYwMDAsIEgzSzI3bWUzPTExMDAwKQojbnVtLmtlZXAgPC0gbGlzdChSTkE9MTAwMDAsIEgzSzRtZTI9MTAwMDAsIEgzSzRtZTM9MTAwMDAsIEgzSzI3bWUzPTEwMDAwKQpmb3IgKGkgaW4gbmFtZXMobnVtLmtlZXApKSB7CiAgICBpZiAobnVtLmtlZXBbW2ldXSA+PSBucm93KHNleHBzW1tpXV0pKSB7CiAgICAgICAgbWVzc2FnZShnbHVlKCJGb3IgZGF0YSB0eXBlIHtpfSwgbm8gdmFyaWFuY2UgZmlsdGVyIGlzIG5lZWRlZDsga2VlcGluZyBhbGwge25yb3coc2V4cHNbW2ldXSl9IGZlYXR1cmVzIikpCiAgICB9IGVsc2UgewogICAgICAgIGFzc2VydF90aGF0KG51bS5rZWVwW1tpXV0gPD0gbnJvdyhzZXhwc1tbaV1dKSkKICAgICAgICBkIDwtIGFzREdFTGlzdChzZXhwc1tbaV1dKQogICAgICAgIGRlc2lnbiA8LSBtYXRyaXgoMSwgbnJvdz1uY29sKGQpLCBuY29sPTEpCiAgICAgICAgZCA8LSBlc3RpbWF0ZURpc3AoZCwgZGVzaWduLCBwcmlvci5kZiA9IDApCiAgICAgICAgcGxvdEJDVihkLCBtYWluID0gZ2x1ZSgiQkNWIHBsb3QgZm9yIHtpfSIpKQogICAgICAgIG9zdGF0IDwtIG5yb3coZCkgLSBudW0ua2VlcFtbaV1dICsgMQogICAgICAgIGRpc3AudGhyZXNob2xkIDwtIGQkdGFnd2lzZS5kaXNwZXJzaW9uICU+JSBzb3J0KHBhcnRpYWw9b3N0YXQpICU+JSAuW29zdGF0XQogICAgICAgIG1lc3NhZ2UoZ2x1ZSgiRm9yIGRhdGEgdHlwZSB7aX0sIGtlZXBpbmcgdGhlIHRvcCB7bnVtLmtlZXBbW2ldXX0gZmVhdHVyZXMgd2l0aCB0aGUgaGlnaGVzdCBkaXNwZXJzaW9ucyBvdXQgb2Yge25yb3coZCl9IikpCiAgICAgICAga2VlcCA8LSBkJHRhZ3dpc2UuZGlzcGVyc2lvbiA+PSBkaXNwLnRocmVzaG9sZAogICAgICAgIGFzc2VydF90aGF0KHN1bShrZWVwKSA9PSBudW0ua2VlcFtbaV1dKQogICAgICAgIHNleHBzW1tpXV0gJTw+JSAuW2tlZXAsXQogICAgfQp9CmBgYAoKIyMgRGF0YSBUcmFuc2Zvcm1hdGlvbgoKTm93IHdlIHRyYW5zZm9ybSBhbGwgdGhlIGZpbHRlcmVkIGRhdGFzZXRzIHRvIGxvZzIgQ1BNLCB1c2luZyBhIHByaW9yIGNvdW50IG9mIDIsIHNpbmNlIHRoYXQgaXMgd2hhdCBwbG90TURTIHVzZXMsIGFuZCBNT0ZBIGlzIGFub3RoZXIgZmFjdG9yIGFuYWx5c2lzIG1ldGhvZCBzaW1pbGFyIHRvIE1EUyBvciBQQ0EuCgpgYGB7ciBsb2djcG1fdHJhbnNmb3JtfQpsb2djcG1saXN0IDwtIGxhcHBseShzZXhwcywgLiAlPiUgYXNER0VMaXN0ICU+JSBjcG1XaXRoT2Zmc2V0KGxvZz1UUlVFLCBwcmlvci5jb3VudD0yKSkKYGBgCgojIyBQcmVwYXJpbmcgdGhlIGRhdGEgZm9yIE1PRkEKCkZpbmFsbHksIHdlIGFyZSByZWFkeSB0byBjb21iaW5lIGFsbCBmaWx0ZXJlZCwgdHJhbnNmb3JtZWQgZGF0YXNldHMgaW50byBhIE11bHRpQXNzYXlFeHBlcmltZW50LgoKYGBge3IgYnVpbGRfTUFFfQptYWtlLnNhbXBsZW1hcCA8LSBmdW5jdGlvbihleHBsaXN0LCBwcmltYXJ5X2NvbG5hbWU9InByaW1hcnkiKSB7CiAgICBtYXBzIDwtIGxhcHBseShleHBsaXN0LCAuICU+JSB7ZGF0YS5mcmFtZShwcmltYXJ5PWNvbERhdGEoLilbW3ByaW1hcnlfY29sbmFtZV1dLCBjb2xuYW1lPWNvbG5hbWVzKC4pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKX0pCiAgICB4IDwtIGxpc3RUb01hcChtYXBzKQp9CmJpb3NhbXBsZS5tZXRhIDwtIGNvbERhdGEocm5hc2VxLnNleHApW2MoImNlbGxfdHlwZSIsICJhY3RpdmF0ZWQiLCAidGltZV9wb2ludCIsICJkYXlzX2FmdGVyX2FjdGl2YXRpb24iLCAiZG9ub3JfaWQiKV0gJT4lCiAgICB0cmFuc2Zvcm0oCiAgICAgICAgdGltZV9wb2ludCA9IGZhY3Rvcih0aW1lX3BvaW50LCBsZXZlbHM9dW5pcXVlKHRpbWVfcG9pbnRbb3JkZXIoZGF5c19hZnRlcl9hY3RpdmF0aW9uKV0pKSwKICAgICAgICBkb25vcl9pZCA9IGZhY3Rvcihkb25vcl9pZCksCiAgICAgICAgUHJpbWFyeVNhbXBsZSA9IGdsdWUoIntjZWxsX3R5cGV9LXt0aW1lX3BvaW50fS17ZG9ub3JfaWR9IikpICU+JQogICAgc2V0X3Jvd25hbWVzKC4kUHJpbWFyeVNhbXBsZSkKc21hcCA8LSBtYWtlLnNhbXBsZW1hcChzZXhwcywgIlByaW1hcnlTYW1wbGUiKQojIFNpbmNlIE1PRkEgaXMgcmVsYXRlZCB0byBNRFMsIHdlIHVzZSB0aGUgc2FtZSBwcmlvciBjb3VudCBhcyBwbG90TURTCm1hZSA8LSBNdWx0aUFzc2F5RXhwZXJpbWVudChleHBlcmltZW50cyA9IGxvZ2NwbWxpc3QsIHNhbXBsZU1hcCA9IHNtYXAsIGNvbERhdGEgPSBiaW9zYW1wbGUubWV0YSkKYGBgCgpXZSBydW4gTU9GQSBzZXZlcmFsIHRpbWVzIHdpdGggZGlmZmVyZW50IHJhbmRvbSBzZWVkcyBzbyB0aGF0IHdlIGNhbiB2ZXJpZnkgdGhhdCBpdCBjb25zaXN0ZW50bHkgY29udmVyZ2VzIHRvIHRoZSBzYW1lIHJlc3VsdC4gVGhlbiB3ZSBydW4gaXQgb25jZSB3aXRoIGEgdGlnaHRlciB0b2xlcmFuY2UgYm91bmQgdG8gb2J0YWluIHRoZSBmaW5hbCBtb2RlbC4KCmBgYHtyIHByZXBhcmVfbW9mYV9vcHRpb25zfQptb2ZhIDwtIGNyZWF0ZU1PRkFvYmplY3QobWFlKQpNb2RlbE9wdGlvbnMgPC0gZ2V0RGVmYXVsdE1vZGVsT3B0cyhtb2ZhKQpUcmFpbk9wdGlvbnMuZmluYWwgPC0gZ2V0RGVmYXVsdFRyYWluT3B0cygpICU+JSB0cmFuc2Zvcm0obWF4aXRlcj0zMDAwMCkKIyBMb29zZXIgdG9sZXJhbmNlIGJvdW5kIGZvciBleHBsb3JhdGlvbgpUcmFpbk9wdGlvbnMuZXhwbG9yZSA8LSBnZXREZWZhdWx0VHJhaW5PcHRzKCkgJT4lIHRyYW5zZm9ybSh0b2xlcmFuY2U9MC4xKQoKdG1wZCA8LSB0ZW1wZmlsZSh0bXBkaXIgPSB0ZW1wZGlyKCksIHBhdHRlcm4gPSAibW9mYXRlbXAiKQpEaXJPcHRpb25zLmZpbmFsIDwtIGxpc3QoCiAgICBkYXRhRGlyID0gdGVtcGZpbGUodG1wZGlyID0gdG1wZCwgcGF0dGVybiA9ICJtb2ZhcnVuXyIpLAogICAgb3V0RmlsZSA9IGhlcmUoInNhdmVkX2RhdGEiLCAibW9mYSIsIGdsdWVfZGF0YShwYXJhbXMsICJtb2ZhLW1vZGVsX3tnZW5vbWV9X3t0cmFuc2NyaXB0b21lfV9ybmErcGVhay5oZGY1IikpLAogICAgcmRzRmlsZSA9IGhlcmUoInNhdmVkX2RhdGEiLCAibW9mYSIsIGdsdWVfZGF0YShwYXJhbXMsICJtb2ZhLW1vZGVsX3tnZW5vbWV9X3t0cmFuc2NyaXB0b21lfV9ybmErcGVhay5SRFMiKSkKKQpEaXJPcHRpb25zLmV4cGxvcmVMaXN0IDwtIGxhcHBseShzZXFfbGVuKHBhcmFtcyRtb2ZhX3J1bnMpLCBmdW5jdGlvbihpKSB7CiAgICBsaXN0KAogICAgICAgIGRhdGFEaXIgPSB0ZW1wZmlsZSh0bXBkaXIgPSB0bXBkLCBwYXR0ZXJuID0gc3RyX2MoIm1vZmFydW4iLCBpLCAiXyIpKSwKICAgICAgICBvdXRGaWxlID0gaGVyZSgic2F2ZWRfZGF0YSIsICJtb2ZhIiwgZ2x1ZV9kYXRhKHBhcmFtcywgIm1vZmEtbW9kZWxfe2dlbm9tZX1fe3RyYW5zY3JpcHRvbWV9X3JuYStwZWFrX2V4cGxvcmV7aX0uaGRmNSIpKSwKICAgICAgICByZHNGaWxlID0gaGVyZSgic2F2ZWRfZGF0YSIsICJtb2ZhIiwgZ2x1ZV9kYXRhKHBhcmFtcywgIm1vZmEtbW9kZWxfe2dlbm9tZX1fe3RyYW5zY3JpcHRvbWV9X3JuYStwZWFrX2V4cGxvcmV7aX0uUkRTIikpCiAgICApCn0pCmZpbmFsLnJhbmRvbS5zZWVkIDwtIDE5ODYKZXhwbG9yZS5yYW5kb20uc2VlZHMgPC0gZmluYWwucmFuZG9tLnNlZWQgKyBzZXFfbGVuKHBhcmFtcyRtb2ZhX3J1bnMpCmBgYAoKIyBNb2RlbCBmaXR0aW5nCgpgYGB7ciBmaXRfZXhwbG9yZV9tb2RlbHN9CiMgTU9GQSBpcyBhbHJlYWR5IHBhcmFsbGVsaXplZCwgc28gd2UgcnVuIHNlcXVlbnRpYWxseQptb2ZhLmV4cGxvcmUgPC0gbGlzdCgpCmZvciAoaSBpbiBzZXFfbGVuKHBhcmFtcyRtb2ZhX3J1bnMpKSB7CiAgICBEaXJPcHQgPC0gRGlyT3B0aW9ucy5leHBsb3JlTGlzdFtbaV1dCiAgICBzZWVkIDwtIGV4cGxvcmUucmFuZG9tLnNlZWRzW2ldCiAgICBkaXIuY3JlYXRlKERpck9wdCRkYXRhRGlyLCByZWN1cnNpdmU9VFJVRSkKICAgIG1vZmEuZXhwbG9yZVtbaV1dIDwtIG1vZmEgJT4lCiAgICAgICAgcHJlcGFyZU1PRkEoRGlyT3B0aW9ucyA9IERpck9wdCwgTW9kZWxPcHRpb25zID0gTW9kZWxPcHRpb25zLCBUcmFpbk9wdGlvbnMgPSBUcmFpbk9wdGlvbnMuZXhwbG9yZSkgJT4lCiAgICAgICAgcnVuTU9GQShEaXJPcHRpb25zID0gRGlyT3B0LCBzZWVkPXNlZWQpCiAgICBzYXZlUkRTKG1vZmEuZXhwbG9yZVtbaV1dLCBEaXJPcHQkcmRzRmlsZSkKfQp1bmxpbmsodG1wZCwgcmVjdXJzaXZlPVRSVUUpCmBgYAoKYGBge3IgZml0X2ZpbmFsX21vZGVsfQpkaXIuY3JlYXRlKERpck9wdGlvbnMuZmluYWwkZGF0YURpciwgcmVjdXJzaXZlPVRSVUUpCm1vZmEuZmluYWwgPC0gbW9mYSAlPiUKICAgIHByZXBhcmVNT0ZBKERpck9wdGlvbnMgPSBEaXJPcHRpb25zLmZpbmFsLCBNb2RlbE9wdGlvbnMgPSBNb2RlbE9wdGlvbnMsIFRyYWluT3B0aW9ucyA9IFRyYWluT3B0aW9ucy5maW5hbCkgJT4lCiAgICBydW5NT0ZBKERpck9wdGlvbnMgPSBEaXJPcHRpb25zLmZpbmFsLCBzZWVkPWZpbmFsLnJhbmRvbS5zZWVkKQpzYXZlUkRTKG1vZmEuZmluYWwsIERpck9wdGlvbnMuZmluYWwkcmRzRmlsZSkKdW5saW5rKHRtcGQsIHJlY3Vyc2l2ZT1UUlVFKQojbW9mYS5maW5hbCA8LSBtb2ZhLmV4cGxvcmVbWzFdXQojIyBUT0RPOiBzYXZlUkRTCmBgYAoKIyBCYXNpYyBtb2RlbCBRQwoKQWZ0ZXIgdGhlIHJ1biBmaW5pc2hlcy4gV2UgcHJvZHVjZSBhIGZldyBiYXNpYyBRQyBwbG90cy4gRmlyc3QsIHdlIHBsb3QgdGhlIHZhcmlhbmNlIGV4cGxhaW5lZCBieSBlYWNoIGZhY3RvciBpbiBlYWNoIG1vZGVsLgoKYGBge3IgcGxvdF9yMiwgd2FybmluZyA9IEZBTFNFfQpyMmxpc3QgPC0gbGFwcGx5KG1vZmEuZXhwbG9yZSwgY2FsY3VsYXRlVmFyaWFuY2VFeHBsYWluZWQpCnIyLmZpbmFsIDwtIGNhbGN1bGF0ZVZhcmlhbmNlRXhwbGFpbmVkKG1vZmEuZmluYWwpCmBgYAoKV2UgYWxzbyBtYWtlIGEgcGxvdCBjb21wYXJpbmcgdGhlIGZhY3RvcnMgYmV0d2VlbiBtdWx0aXBsZSBtb2RlbHMsIHRvIHZlcmlmeSB0aGF0IGVhY2ggbW9kZWwgaXMgZGlzY292ZXJpbmcgcm91Z2hseSB0aGUgc2FtZSBzZXQgb2YgZmFjdG9ycy4KCmBgYHtyIGNvbXBhcmVfbW9kZWxzfQppbnZpc2libGUoY29tcGFyZU1vZGVscyhtb2ZhLmV4cGxvcmUpKQpgYGAKClNpbmNlIGFsbCB0aGUgbW9kZWxzIHNlZW0gdG8gYmUgZGlzY292ZXJpbmcgdGhlIHNhbWUgZmFjdG9ycywgd2UgY2FuIHVzZSBhbnkgbW9kZWwgd2UgY2hvb3NlLiBGb3IgdGhlIGZpcnN0IG1vZGVsLCB3ZSBtYWtlIGEgImJlZSBzd2FybSBwbG90IiBvZiBlYWNoIGZhY3RvcjoKCmBgYHtyIGJlZV9zd2FybV9wbG90c30KIyBwIDwtIGxpc3QoCiMgICAgIFRpbWVQb2ludD1wbG90RmFjdG9yQmVlc3dhcm0oCiMgICAgICAgICBtb2ZhLmZpbmFsLAojICAgICAgICAgZmFjdG9ycyA9IHNlcV9sZW4obmNvbChnZXRGYWN0b3JzKG1vZmEuZmluYWwsIGluY2x1ZGVfaW50ZXJjZXB0ID0gRkFMU0UpKSksCiMgICAgICAgICBjb2xvcl9ieSA9ICJ0aW1lX3BvaW50IikgKwojICAgICAgICAgYWVzKHg9MCkgKyB4bGFiKCIiKSArIHlsYWIoIkZhY3RvciBWYWx1ZSIpICsKIyAgICAgICAgIGZhY2V0X3dyYXAofmZhY3Rvciwgc2NhbGVzPSJmcmVlIikgKwojICAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1OVUxMKSwKIyAgICAgQ2VsbFR5cGU9cGxvdEZhY3RvckJlZXN3YXJtKAojICAgICAgICAgbW9mYS5maW5hbCwKIyAgICAgICAgIGZhY3RvcnMgPSBzZXFfbGVuKG5jb2woZ2V0RmFjdG9ycyhtb2ZhLmZpbmFsLCBpbmNsdWRlX2ludGVyY2VwdCA9IEZBTFNFKSkpLAojICAgICAgICAgY29sb3JfYnkgPSAiY2VsbF90eXBlIikgKwojICAgICAgICAgYWVzKHg9MCkgKyB4bGFiKCIiKSArIHlsYWIoIkZhY3RvciBWYWx1ZSIpICsKIyAgICAgICAgIGZhY2V0X3dyYXAofmZhY3Rvciwgc2NhbGVzPSJmcmVlIikgKwojICAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1OVUxMKSwKIyAgICAgRG9ub3I9cGxvdEZhY3RvckJlZXN3YXJtKAojICAgICAgICAgbW9mYS5maW5hbCwKIyAgICAgICAgIGZhY3RvcnMgPSBzZXFfbGVuKG5jb2woZ2V0RmFjdG9ycyhtb2ZhLmZpbmFsLCBpbmNsdWRlX2ludGVyY2VwdCA9IEZBTFNFKSkpLAojICAgICAgICAgY29sb3JfYnkgPSAiZG9ub3JfaWQiKSArCiMgICAgICAgICBhZXMoeD0wKSArIHhsYWIoIiIpICsgeWxhYigiRmFjdG9yIFZhbHVlIikgKwojICAgICAgICAgZmFjZXRfd3JhcCh+ZmFjdG9yLCBzY2FsZXM9ImZyZWUiKSArCiMgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPU5VTEwpKQojIGdncHJpbnQocCkKYGBgCg==