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.

promoter.sexps %<-% {
    bplapply(here("saved_data",
                  glue_data(params, "promoter-counts_{genome}_{transcriptome}_{promoter_datasets}.RDS")),
             readRDS) %>% 
        setNames(names(params$promoter_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(promoter.sexps)) {
    promoter.sexps[[i]] %<>%
        set_colnames(colData(.)$SRA_run)
    colData(promoter.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), promoter.sexps)

Normalization and filtering

Abundance filtering

The first filter is an abundance filter, which just re-uses the filtering criteria previously defined in other reports.

presence.thresholds <- list(RNA=-1, H3K4me2=0.407, H3K4me3=1.29, H3K27me3=0.993)
#presence.thresholds <- list(RNA=-1, H3K4me2=1+0.407, H3K4me3=1+1.29, H3K27me3=0.5+0.993)
# Filter RNA-seq at average logCPM >= -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
For data type H3K4me2, keeping 35059 features out of 94652 (37%) with aveLogCPM >= 0.407
For data type H3K4me3, keeping 21153 features out of 94652 (22.3%) with aveLogCPM >= 1.29
For data type H3K27me3, keeping 32617 features out of 84739 (38.5%) with aveLogCPM >= 0.993

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=20000, H3K4me3=10000, H3K27me3=20000)
#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 20000 features with the highest dispersions out of 35059

For data type H3K4me3, keeping the top 10000 features with the highest dispersions out of 21153

For data type H3K27me3, keeping the top 20000 features with the highest dispersions out of 32617

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 20000 10000 20000 
 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+promoter.hdf5")),
    rdsFile = here("saved_data", "mofa", glue_data(params, "mofa-model_{genome}_{transcriptome}_rna+promoter.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+promoter_explore{i}.hdf5")),
        rdsFile = here("saved_data", "mofa", glue_data(params, "mofa-model_{genome}_{transcriptome}_rna+promoter_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)
    ## TODO: saveRDS
}
Checking data options...
No data options specified, using default...
Checking training options...
Checking model options...
Storing input views in /tmp/RtmpJJ4oPi/mofatemp734183cffa5/mofarun1_73416bd1d4e4...
Checking data options...
No data options specified, using default...
Checking training options...
Checking model options...
Storing input views in /tmp/RtmpJJ4oPi/mofatemp734183cffa5/mofarun2_7341d741467...
Checking data options...
No data options specified, using default...
Checking training options...
Checking model options...
Storing input views in /tmp/RtmpJJ4oPi/mofatemp734183cffa5/mofarun3_7341273b37cf...
Checking data options...
No data options specified, using default...
Checking training options...
Checking model options...
Storing input views in /tmp/RtmpJJ4oPi/mofatemp734183cffa5/mofarun4_73412b26d72f...
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/RtmpJJ4oPi/mofatemp734183cffa5/mofarun_73411a337cf1...
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)
LS0tCnRpdGxlOiAiTU9GQSBNb2RlbCBGaXR0aW5nIGZvciBSTkEtc2VxIGFuZCBQcm9tb3RlciBIaXN0b25lIENoSVAtc2VxIGRhdGEiCmF1dGhvcjogIlJ5YW4gQy4gVGhvbXBzb24iCmRhdGU6ICdgciBnc3ViKCJcXHMrIiwgIiAiLCBmb3JtYXQoU3lzLnRpbWUoKSwgIiVCICVlLCAlWSIpKWAnCm91dHB1dDoKICAgIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQKICAgIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKcGFyYW1zOgogICAgbW9mYV9ydW5zOiA0CiAgICBnZW5vbWU6CiAgICAgICAgdmFsdWU6IGhnMzguYW5hbHlzaXNTZXQKICAgIHRyYW5zY3JpcHRvbWU6CiAgICAgICAgdmFsdWU6IGVuc2VtYmwuODUKICAgIHJuYV9kYXRhc2V0OgogICAgICAgIHZhbHVlOiAic2hvYWxfaGczOC5hbmFseXNpc1NldCIKICAgIHByb21vdGVyX2RhdGFzZXRzOgogICAgICAgIHZhbHVlOiAhciBjKEgzSzRtZTIgPSAiMWticC1yYWRpdXNfMTQ3YnAtcmVhZHNfSDNLNG1lMiIsIAogICAgICAgICAgICAgICAgICAgIEgzSzRtZTMgPSAiMWticC1yYWRpdXNfMTQ3YnAtcmVhZHNfSDNLNG1lMyIsIAogICAgICAgICAgICAgICAgICAgIEgzSzI3bWUzID0gIjIuNWticC1yYWRpdXNfMTQ3YnAtcmVhZHNfSDNLMjdtZTMiKQogICAgcGVha19kYXRhc2V0czoKICAgICAgICB2YWx1ZTogIXIgYyhIM0s0bWUyID0gIkgzSzRtZTJfMTQ3YnAtcmVhZHMiLCAKICAgICAgICAgICAgICAgICAgICBIM0s0bWUzID0gIkgzSzRtZTNfMTQ3YnAtcmVhZHMiLCAKICAgICAgICAgICAgICAgICAgICBIM0syN21lMyA9ICJIM0syN21lM18xNDdicC1yZWFkcyIpCiAgICB3aW5kb3dfZGF0YXNldHM6CiAgICAgICAgdmFsdWU6ICFyIGMoSDNLNG1lMiA9ICI1MDBicC13aW5kb3dzXzE0N2JwLXJlYWRzX0gzSzRtZTIiLCAKICAgICAgICAgICAgICAgICAgICBIM0s0bWUzID0gIjUwMGJwLXdpbmRvd3NfMTQ3YnAtcmVhZHNfSDNLNG1lMyIsIAogICAgICAgICAgICAgICAgICAgIEgzSzI3bWUzID0gIjUwMGJwLXdpbmRvd3NfMTQ3YnAtcmVhZHNfSDNLMjdtZTMiKQotLS0KCiMgUHJlbGltaW5hcnkgc2V0dXAKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFLCBjYWNoZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCByZXRpbmE9MiwgY2FjaGU9VFJVRSwgYXV0b2RlcD1UUlVFLAogICAgICAgICAgICAgICAgICAgICAgY2FjaGUuZXh0cmEgPSBsaXN0KHBhcmFtcz1wYXJhbXMpLAogICAgICAgICAgICAgICAgICAgICAgIyBodHRwczovL2dpdGh1Yi5jb20veWlodWkva25pdHIvaXNzdWVzLzU3MgogICAgICAgICAgICAgICAgICAgICAgY2FjaGUubGF6eT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTgsCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZS5wYXRoID0gcGFzdGUwKAogICAgICAgICAgICAgICAgICAgICAgICAgIGhlcmU6OmhlcmUoImNhY2hlIiwgInByb21vdGVyLW1vZmEtcnVuIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgLlBsYXRmb3JtJGZpbGUuc2VwKSkKYGBgCgpGaXJzdCB3ZSBsb2FkIHRoZSBuZWNlc3NhcnkgbGlicmFyaWVzLCBhbG9uZyB3aXRoIGEgc2V0IG9mIHV0aWxpdHkgZnVuY3Rpb25zLgoKYGBge3IgbG9hZF9wYWNrYWdlcywgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShnbHVlKQpsaWJyYXJ5KG1hZ3JpdHRyKQpsaWJyYXJ5KG9wZW54bHN4KQpsaWJyYXJ5KFN1bW1hcml6ZWRFeHBlcmltZW50KQpsaWJyYXJ5KE11bHRpQXNzYXlFeHBlcmltZW50KQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGVkZ2VSKQpsaWJyYXJ5KGxpbW1hKQpsaWJyYXJ5KERFU2VxMikKbGlicmFyeShjc2F3KQpsaWJyYXJ5KHN2YSkKbGlicmFyeShNT0ZBdG9vbHMpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KGdnYWx0KQpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KHNwbGluZXMpCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkoYXNzZXJ0dGhhdCkKbGlicmFyeShnZ2ZvcnRpZnkpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkoa3MpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KGhlcmUpCgpsaWJyYXJ5KEJTZ2Vub21lLkhzYXBpZW5zLlVDU0MuaGczOCkKCmxpYnJhcnkoZG9QYXJhbGxlbCkKbmNvcmVzIDwtIGdldE9wdGlvbigibWMuY29yZXMiLCBkZWZhdWx0PXBhcmFsbGVsOjpkZXRlY3RDb3Jlcyhsb2dpY2FsID0gRkFMU0UpKQpvcHRpb25zKG1jLmNvcmVzPW5jb3JlcykKcmVnaXN0ZXJEb1BhcmFsbGVsKGNvcmVzPW5jb3JlcykKbGlicmFyeShCaW9jUGFyYWxsZWwpCnJlZ2lzdGVyKFNlcmlhbFBhcmFtKCkpCnJlZ2lzdGVyKERvcGFyUGFyYW0oKSkKCm9wdGlvbnMoZnV0dXJlLmdsb2JhbHMubWF4U2l6ZT00ICogMTAyNF4zKQpsaWJyYXJ5KGZ1dHVyZSkKcGxhbihtdWx0aWNvcmUpCgojIEV4cG9ydCBlbnZpcm9ubWVudCB2YXJpYWJsZXMgdG8gbWFrZSBOdW1QeSByZXNwZWN0IHRoZSBkZXNpcmVkIGxldmVsIG9mIAojIHBhcmFsbGVsaXNtLiAoRGlmZmVyZW50IHZhcmlhYmxlcyBhcmUgcmVxdWlyZWQgZm9yIGRpZmZlcmVudCBsaWJyYXJpZXMuKQpTeXMuc2V0ZW52KE1LTF9OVU1fVEhSRUFEUyA9IG5jb3JlcywKICAgICAgICAgICBOVU1FWFBSX05VTV9USFJFQURTID0gbmNvcmVzLAogICAgICAgICAgIE9NUF9OVU1fVEhSRUFEUyA9IG5jb3JlcykKCnNvdXJjZShoZXJlKCJzY3JpcHRzL3V0aWxpdGllcy5SIikpCgojIFJlcXVpcmVkIGluIG9yZGVyIHRvIHVzZSBER0VMaXN0IG9iamVjdHMgd2l0aCBmdXR1cmUKbGVuZ3RoLkRHRUxpc3QgPC0gZnVuY3Rpb24oeCkgewogICAgbGVuZ3RoKHVuY2xhc3MoeCkpCn0KYGBgCgojIERhdGEgbG9hZGluZwoKRmlyc3Qgd2UgbG9hZCBhbGwgdGhlIGNvdW50IGRhdGEgc2V0cyB0aGF0IHdlIHdpbGwgYmUgdXNpbmcuCgpgYGB7ciBsb2FkX2NvdW50c30KcHJvbW90ZXIuc2V4cHMgJTwtJSB7CiAgICBicGxhcHBseShoZXJlKCJzYXZlZF9kYXRhIiwKICAgICAgICAgICAgICAgICAgZ2x1ZV9kYXRhKHBhcmFtcywgInByb21vdGVyLWNvdW50c197Z2Vub21lfV97dHJhbnNjcmlwdG9tZX1fe3Byb21vdGVyX2RhdGFzZXRzfS5SRFMiKSksCiAgICAgICAgICAgICByZWFkUkRTKSAlPiUgCiAgICAgICAgc2V0TmFtZXMobmFtZXMocGFyYW1zJHByb21vdGVyX2RhdGFzZXRzKSkKfQpybmFzZXEuc2V4cCAlPC0lIHsKICAgIHJlYWRSRFMoaGVyZSgic2F2ZWRfZGF0YSIsIGdsdWVfZGF0YShwYXJhbXMsICJTdW1tYXJpemVkRXhwZXJpbWVudF9ybmFzZXFfe3JuYV9kYXRhc2V0fV97dHJhbnNjcmlwdG9tZX0uUkRTIikpKSAgJT4lCiAgICBzZXRfY29sbmFtZXMoY29sRGF0YSguKSRTUkFfcnVuKQp9CmZvciAoaSBpbiBuYW1lcyhwcm9tb3Rlci5zZXhwcykpIHsKICAgIHByb21vdGVyLnNleHBzW1tpXV0gJTw+JQogICAgICAgIHNldF9jb2xuYW1lcyhjb2xEYXRhKC4pJFNSQV9ydW4pCiAgICBjb2xEYXRhKHByb21vdGVyLnNleHBzW1tpXV0pICU8PiUKICAgICAgICB0cmFuc2Zvcm0odGltZV9wb2ludCA9IHN0cl9yZXBsYWNlKHRpbWVfcG9pbnQsICJEYXkiLCAiRCIpKSAlPiUKICAgICAgICB0cmFuc2Zvcm0oZG9ub3JfaWQgPSBzdHJfcmVwbGFjZShkb25vcl9pZCwgIl5EbioiLCAiRG4iKSkgJT4lCiAgICAgICAgdHJhbnNmb3JtKFByaW1hcnlTYW1wbGUgPSBnbHVlKCJ7Y2VsbF90eXBlfS17dGltZV9wb2ludH0te2Rvbm9yX2lkfSIpKQp9CmNvbERhdGEocm5hc2VxLnNleHApICU8PiUgCiAgICB0cmFuc2Zvcm0odGltZV9wb2ludCA9IGdsdWUoIkR7ZGF5c19hZnRlcl9hY3RpdmF0aW9ufSIpKSAlPiUKICAgIHRyYW5zZm9ybShkb25vcl9pZCA9IHN0cl9yZXBsYWNlKGRvbm9yX2lkLCAiXkRuKiIsICJEbiIpKSAlPiUKICAgIHRyYW5zZm9ybShQcmltYXJ5U2FtcGxlID0gZ2x1ZSgie2NlbGxfdHlwZX0te3RpbWVfcG9pbnR9LXtkb25vcl9pZH0iKSkKCiMgRml4IHVwIHN0cmFuZGVkIFJOQS1zZXEgY291bnRzLCBpZiBuZWVkZWQKbGlidHlwZS5hc3NheU5hbWVzIDwtIGMoU0Y9InNlbnNlLmNvdW50cyIsIFNSPSJhbnRpc2Vuc2UuY291bnRzIikKaWYgKGFsbChsaWJ0eXBlLmFzc2F5TmFtZXMgJWluJSBhc3NheU5hbWVzKHJuYXNlcS5zZXhwKSkpIHsKICAgIG1lc3NhZ2UoIlNlbGVjdGluZyBzdHJhbmRlZCBjb3VudHMgZm9yIGVhY2ggc2FtcGxlIikKICAgIHNhbXBsZS50YWJsZSAlPD4lIG11dGF0ZShjb3VudF90eXBlPWxpYnR5cGUuYXNzYXlOYW1lc1tsaWJUeXBlXSkKICAgIGFzc2F5KHJuYXNlcS5zZXhwLCAidW5zdHJhbmRlZC5jb3VudHMiKSA8LSBhc3NheShybmFzZXEuc2V4cCwgImNvdW50cyIpCiAgICBhc3NheShybmFzZXEuc2V4cCwgImNvdW50cyIpIDwtIGxhcHBseShzZXFfbGVuKG5yb3coc2FtcGxlLnRhYmxlKSksIGZ1bmN0aW9uKGkpIHsKICAgICAgICBtZXNzYWdlKCJVc2luZyAiLCBzYW1wbGUudGFibGVbaSxdJGNvdW50X3R5cGUsICIgZm9yICIsIGNvbG5hbWVzKHJuYXNlcS5zZXhwKVtpXSkKICAgICAgICBhc3NheShybmFzZXEuc2V4cCwgc2FtcGxlLnRhYmxlW2ksXSRjb3VudF90eXBlICU+JSBhcy5jaGFyYWN0ZXIpWyxpXQogICAgfSkgJT4lIGRvLmNhbGwod2hhdD1jYmluZCkKICAgIHRvdGFsLmNvdW50cyA8LSBzZXhwICU+JSBhc3NheXMgJT4lIHNhcHBseShjb2xTdW1zKSAlPiUgZGF0YS5mcmFtZSAlPiUKICAgICAgICBtdXRhdGUoU2FtcGxlTmFtZT1yb3cubmFtZXMoLikpICU+JQogICAgICAgIGlubmVyX2pvaW4oc2FtcGxlLnRhYmxlLCBieT0iU2FtcGxlTmFtZSIpCiAgICB0b3RhbC5jb3VudHMgJSQlIGludmlzaWJsZShhc3NlcnRfdGhhdChhbGwoY291bnRzID09IHBtYXgoc2Vuc2UuY291bnRzLCBhbnRpc2Vuc2UuY291bnRzKSkpKQp9CgpzZXhwcyA8LSBjKGxpc3QoUk5BPXJuYXNlcS5zZXhwKSwgcHJvbW90ZXIuc2V4cHMpCmBgYAoKIyBOb3JtYWxpemF0aW9uIGFuZCBmaWx0ZXJpbmcKCiMjIEFidW5kYW5jZSBmaWx0ZXJpbmcKClRoZSBmaXJzdCBmaWx0ZXIgaXMgYW4gYWJ1bmRhbmNlIGZpbHRlciwgd2hpY2gganVzdCByZS11c2VzIHRoZSBmaWx0ZXJpbmcgY3JpdGVyaWEgcHJldmlvdXNseSBkZWZpbmVkIGluIG90aGVyIHJlcG9ydHMuCgpgYGB7ciBmaWx0ZXJfYWJ1bmRhbmNlfQpwcmVzZW5jZS50aHJlc2hvbGRzIDwtIGxpc3QoUk5BPS0xLCBIM0s0bWUyPTAuNDA3LCBIM0s0bWUzPTEuMjksIEgzSzI3bWUzPTAuOTkzKQojcHJlc2VuY2UudGhyZXNob2xkcyA8LSBsaXN0KFJOQT0tMSwgSDNLNG1lMj0xKzAuNDA3LCBIM0s0bWUzPTErMS4yOSwgSDNLMjdtZTM9MC41KzAuOTkzKQojIEZpbHRlciBSTkEtc2VxIGF0IGF2ZXJhZ2UgbG9nQ1BNID49IC0xCmZvciAoaSBpbiBuYW1lcyhwcmVzZW5jZS50aHJlc2hvbGRzKSkgewogICAgc3VwcHJlc3NXYXJuaW5ncyhwcmVzZW50IDwtIGF2ZUxvZ0NQTVdpdGhPZmZzZXQoYXNER0VMaXN0KHNleHBzW1tpXV0pKSA+PSBwcmVzZW5jZS50aHJlc2hvbGRzW1tpXV0pCiAgICBudW0uZmVhdHVyZXMgPC0gbnJvdyhzZXhwc1tbaV1dKQogICAgbnVtLmtlcHQgPC0gc3VtKHByZXNlbnQpCiAgICBwZXJjZW50LmtlcHQgPC0gbnVtLmtlcHQgLyBudW0uZmVhdHVyZXMgKiAxMDAKICAgIG1lc3NhZ2UoZ2x1ZSgiRm9yIGRhdGEgdHlwZSB7aX0sIGtlZXBpbmcge251bS5rZXB0fSBmZWF0dXJlcyBvdXQgb2Yge251bS5mZWF0dXJlc30gKHtmb3JtYXQocGVyY2VudC5rZXB0LCBkaWdpdHM9Myl9JSkgd2l0aCBhdmVMb2dDUE0gPj0ge3ByZXNlbmNlLnRocmVzaG9sZHNbW2ldXX0iKSkKICAgIHNleHBzW1tpXV0gJTw+JSAuW3ByZXNlbnQsXQogICAgcm0ocHJlc2VudCkKfQpgYGAKCiMjIENhbGN1bGF0aW5nIG5vcm1hbGl6YXRpb24gZmFjdG9ycwoKV2UgbmVlZCB0byBub3JtYWxpemUgYW5kIGZpbHRlciBhbGwgZGF0YSBzZXRzIGJlZm9yZSBmZWVkaW5nIHRoZW0gdG8gTU9GQS4gV2UgYmVnaW4gYnkgY29tcHV0aW5nIG5vcm1hbGl6YXRpb24gZmFjdG9ycy4gKHBvdGVudGlhbGx5IHdpdGggb2Zmc2V0cyBkZXJpdmVkIGZyb20gZWZmZWN0aXZlIGdlbmUgbGVuZ3RocyBmb3IgUk5BLXNlcS4pCgpgYGB7ciBub3JtYWxpemVfZGF0YX0KZm9yIChpIGluIG5hbWVzKHNleHBzKSkgewogICAgaWYgKCEgInRvdGFscyIgJWluJSBjb2xuYW1lcyhjb2xEYXRhKHNleHBzW1tpXV0pKSkgewogICAgICAgIGNvbERhdGEoc2V4cHNbW2ldXSkkdG90YWxzIDwtIGNvbFN1bXMoYXNzYXkoc2V4cHNbW2ldXSwgImNvdW50cyIpKQogICAgfQogICAgY29sRGF0YShzZXhwc1tbaV1dKSRub3JtLmZhY3RvcnMgPC0gY2FsY05vcm1GYWN0b3JzKGFzREdFTGlzdChzZXhwc1tbaV1dKSkkc2FtcGxlcyRub3JtLmZhY3RvcnMKICAgIAogICAgaWYgKCJsZW5ndGgiICVpbiUgYXNzYXlOYW1lcyhzZXhwc1tbaV1dKSkgewogICAgICAgIG5vcm1NYXQgPC0gYXNzYXkoc2V4cHNbW2ldXSwgImxlbmd0aCIpICU+JSBkaXZpZGVfYnkoZXhwKHJvd01lYW5zKGxvZyguKSkpKQogICAgICAgIG5vcm1Db3VudHMgPC0gYXNzYXkoc2V4cHNbW2ldXSwgImNvdW50cyIpL25vcm1NYXQKICAgICAgICBsaWIub2Zmc2V0cyA8LSBsb2coY2FsY05vcm1GYWN0b3JzKG5vcm1Db3VudHMpKSArIGxvZyhjb2xTdW1zKG5vcm1Db3VudHMpKQogICAgICAgIGFzc2F5KHNleHBzW1tpXV0sICJvZmZzZXQiKSA8LSB0KHQobG9nKG5vcm1NYXQpKSArIGxpYi5vZmZzZXRzKQogICAgfQp9CmBgYAoKIyMgT3V0bGllciBzYW1wbGUgZmlsdGVyaW5nCgpOZXh0LCB3ZSBpZGVudGlmeSBwb3NzaWJsZSBvdXRsaWVyIHNhbXBsZXMgaW4gdGhlIGRhdGEuIEZvciBvdXIgcHVycG9zZXMsIHRoZXNlIGFyZSBkZWZpbmVkIGFzIHNhbXBsZXMgZm9yIHdoaWNoIHRoZSBwZXJjZW50YWdlIG9mIHplcm8gY291bnRzIGlzIDMgc3RhbmRhcmQgZGV2aWF0aW9ucyBiZWxvdyB0aGUgbWVhbiBmb3IgYWxsIHNhbXBsZXMuIFRoaXMgY3JpdGVyaW9uIHdhcyBkZXRlcm1pbmVkIHRocm91Z2ggZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcy4gV2UgZHJvcCB0aGVzZSBzYW1wbGVzIGJlZm9yZSBydW5uaW5nIE1PRkEuCgpgYGB7ciBmaWx0ZXJfb3V0bGllcl9zYW1wbGVzfQpvdXRsaWVyLnNhbXBsZXMgPC0gc2V4cHMgJT4lIAogICAgbGFwcGx5KC4gJT4lIAogICAgICAgICAgICAgICBzZXRfY29sbmFtZXMoY29sRGF0YSguKSRQcmltYXJ5U2FtcGxlKSAlPiUKICAgICAgICAgICAgICAgYXNzYXkoImNvdW50cyIpICU+JQogICAgICAgICAgICAgICBpc19ncmVhdGVyX3RoYW4oMCkgJT4lIAogICAgICAgICAgICAgICBjb2xNZWFucyAlPiUKICAgICAgICAgICAgICAgLlsuIDwgbWVhbiguKSAtIHNkKC4pICogM10gJT4lIAogICAgICAgICAgICAgICBuYW1lcykKZm9yIChpIGluIG5hbWVzKHNleHBzKSkgIHsKICAgIG91dGxpZXJzIDwtIHNleHBzW1tpXV0kUHJpbWFyeVNhbXBsZSAlaW4lIG91dGxpZXIuc2FtcGxlc1tbaV1dCiAgICBpZiAoYW55KG91dGxpZXJzKSkgewogICAgICAgIG1lc3NhZ2UoZ2x1ZSgiUmVtb3Zpbmcgb3V0IHtzdW0ob3V0bGllcnMpfSBvdXRsaWVyIHNhbXBsZXtpZmVsc2Uoc3VtKG91dGxpZXJzKSA9PSAxLCAnJywgJ3MnKX0gZnJvbSB7aX0gZGF0YS4iKSkKICAgIH0KICAgIHNleHBzW1tpXV0gJTw+JSAuWywhb3V0bGllcnNdCn0KYGBgCgojIyBWYXJpYW5jZSBmaWx0ZXJpbmcKClRoZSBuZXh0IGZpbHRlciBpcyBhIHZhcmlhbmNlIGZpbHRlciwgc2VsZWN0aW5nIE4gZ2VuZXMvcGVha3MgZnJvbSBlYWNoIGRhdGEgc2V0IHdpdGggdGhlIGxhcmdlc3QgZGlzcGVyc2lvbnMuIFRoZSB2YWx1ZSBvZiBOIGZvciBlYWNoIGRhdGEgc2V0IGlzIGRldGVybWluZWQgYmFzZWQgb24gdGhlIG51bWJlciBvZiBzaWduaWZpY2FudGx5IGRpZmZlcmVudGlhbGx5IGFidW5kYW50IGZlYXR1cmVzIGlkZW50aWZpZWQgaW4gcHJldmlvdXMgYW5hbHlzZXMuCgpgYGB7ciBmaWx0ZXJfdmFyaWFuY2V9Cm51bS5rZWVwIDwtIGxpc3QoUk5BPTEwMDAwLCBIM0s0bWUyPTIwMDAwLCBIM0s0bWUzPTEwMDAwLCBIM0syN21lMz0yMDAwMCkKI251bS5rZWVwIDwtIGxpc3QoUk5BPTEwMDAwLCBIM0s0bWUyPTEwMDAwLCBIM0s0bWUzPTEwMDAwLCBIM0syN21lMz0xMDAwMCkKZm9yIChpIGluIG5hbWVzKG51bS5rZWVwKSkgewogICAgaWYgKG51bS5rZWVwW1tpXV0gPj0gbnJvdyhzZXhwc1tbaV1dKSkgewogICAgICAgIG1lc3NhZ2UoZ2x1ZSgiRm9yIGRhdGEgdHlwZSB7aX0sIG5vIHZhcmlhbmNlIGZpbHRlciBpcyBuZWVkZWQ7IGtlZXBpbmcgYWxsIHtucm93KHNleHBzW1tpXV0pfSBmZWF0dXJlcyIpKQogICAgfSBlbHNlIHsKICAgICAgICBhc3NlcnRfdGhhdChudW0ua2VlcFtbaV1dIDw9IG5yb3coc2V4cHNbW2ldXSkpCiAgICAgICAgZCA8LSBhc0RHRUxpc3Qoc2V4cHNbW2ldXSkKICAgICAgICBkZXNpZ24gPC0gbWF0cml4KDEsIG5yb3c9bmNvbChkKSwgbmNvbD0xKQogICAgICAgIGQgPC0gZXN0aW1hdGVEaXNwKGQsIGRlc2lnbiwgcHJpb3IuZGYgPSAwKQogICAgICAgIHBsb3RCQ1YoZCwgbWFpbiA9IGdsdWUoIkJDViBwbG90IGZvciB7aX0iKSkKICAgICAgICBvc3RhdCA8LSBucm93KGQpIC0gbnVtLmtlZXBbW2ldXSArIDEKICAgICAgICBkaXNwLnRocmVzaG9sZCA8LSBkJHRhZ3dpc2UuZGlzcGVyc2lvbiAlPiUgc29ydChwYXJ0aWFsPW9zdGF0KSAlPiUgLltvc3RhdF0KICAgICAgICBtZXNzYWdlKGdsdWUoIkZvciBkYXRhIHR5cGUge2l9LCBrZWVwaW5nIHRoZSB0b3Age251bS5rZWVwW1tpXV19IGZlYXR1cmVzIHdpdGggdGhlIGhpZ2hlc3QgZGlzcGVyc2lvbnMgb3V0IG9mIHtucm93KGQpfSIpKQogICAgICAgIGtlZXAgPC0gZCR0YWd3aXNlLmRpc3BlcnNpb24gPj0gZGlzcC50aHJlc2hvbGQKICAgICAgICBhc3NlcnRfdGhhdChzdW0oa2VlcCkgPT0gbnVtLmtlZXBbW2ldXSkKICAgICAgICBzZXhwc1tbaV1dICU8PiUgLltrZWVwLF0KICAgIH0KfQpgYGAKCiMjIERhdGEgVHJhbnNmb3JtYXRpb24KCk5vdyB3ZSB0cmFuc2Zvcm0gYWxsIHRoZSBmaWx0ZXJlZCBkYXRhc2V0cyB0byBsb2cyIENQTSwgdXNpbmcgYSBwcmlvciBjb3VudCBvZiAyLCBzaW5jZSB0aGF0IGlzIHdoYXQgcGxvdE1EUyB1c2VzLCBhbmQgTU9GQSBpcyBhbm90aGVyIGZhY3RvciBhbmFseXNpcyBtZXRob2Qgc2ltaWxhciB0byBNRFMgb3IgUENBLgoKYGBge3IgbG9nY3BtX3RyYW5zZm9ybX0KbG9nY3BtbGlzdCA8LSBsYXBwbHkoc2V4cHMsIC4gJT4lIGFzREdFTGlzdCAlPiUgY3BtV2l0aE9mZnNldChsb2c9VFJVRSwgcHJpb3IuY291bnQ9MikpCmBgYAoKIyMgUHJlcGFyaW5nIHRoZSBkYXRhIGZvciBNT0ZBCgpGaW5hbGx5LCB3ZSBhcmUgcmVhZHkgdG8gY29tYmluZSBhbGwgZmlsdGVyZWQsIHRyYW5zZm9ybWVkIGRhdGFzZXRzIGludG8gYSBNdWx0aUFzc2F5RXhwZXJpbWVudC4KCmBgYHtyIGJ1aWxkX01BRX0KbWFrZS5zYW1wbGVtYXAgPC0gZnVuY3Rpb24oZXhwbGlzdCwgcHJpbWFyeV9jb2xuYW1lPSJwcmltYXJ5IikgewogICAgbWFwcyA8LSBsYXBwbHkoZXhwbGlzdCwgLiAlPiUge2RhdGEuZnJhbWUocHJpbWFyeT1jb2xEYXRhKC4pW1twcmltYXJ5X2NvbG5hbWVdXSwgY29sbmFtZT1jb2xuYW1lcyguKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSl9KQogICAgeCA8LSBsaXN0VG9NYXAobWFwcykKfQpiaW9zYW1wbGUubWV0YSA8LSBjb2xEYXRhKHJuYXNlcS5zZXhwKVtjKCJjZWxsX3R5cGUiLCAiYWN0aXZhdGVkIiwgInRpbWVfcG9pbnQiLCAiZGF5c19hZnRlcl9hY3RpdmF0aW9uIiwgImRvbm9yX2lkIildICU+JQogICAgdHJhbnNmb3JtKAogICAgICAgIHRpbWVfcG9pbnQgPSBmYWN0b3IodGltZV9wb2ludCwgbGV2ZWxzPXVuaXF1ZSh0aW1lX3BvaW50W29yZGVyKGRheXNfYWZ0ZXJfYWN0aXZhdGlvbildKSksCiAgICAgICAgZG9ub3JfaWQgPSBmYWN0b3IoZG9ub3JfaWQpLAogICAgICAgIFByaW1hcnlTYW1wbGUgPSBnbHVlKCJ7Y2VsbF90eXBlfS17dGltZV9wb2ludH0te2Rvbm9yX2lkfSIpKSAlPiUKICAgIHNldF9yb3duYW1lcyguJFByaW1hcnlTYW1wbGUpCnNtYXAgPC0gbWFrZS5zYW1wbGVtYXAoc2V4cHMsICJQcmltYXJ5U2FtcGxlIikKIyBTaW5jZSBNT0ZBIGlzIHJlbGF0ZWQgdG8gTURTLCB3ZSB1c2UgdGhlIHNhbWUgcHJpb3IgY291bnQgYXMgcGxvdE1EUwptYWUgPC0gTXVsdGlBc3NheUV4cGVyaW1lbnQoZXhwZXJpbWVudHMgPSBsb2djcG1saXN0LCBzYW1wbGVNYXAgPSBzbWFwLCBjb2xEYXRhID0gYmlvc2FtcGxlLm1ldGEpCmBgYAoKV2UgcnVuIE1PRkEgc2V2ZXJhbCB0aW1lcyB3aXRoIGRpZmZlcmVudCByYW5kb20gc2VlZHMgc28gdGhhdCB3ZSBjYW4gdmVyaWZ5IHRoYXQgaXQgY29uc2lzdGVudGx5IGNvbnZlcmdlcyB0byB0aGUgc2FtZSByZXN1bHQuIFRoZW4gd2UgcnVuIGl0IG9uY2Ugd2l0aCBhIHRpZ2h0ZXIgdG9sZXJhbmNlIGJvdW5kIHRvIG9idGFpbiB0aGUgZmluYWwgbW9kZWwuCgpgYGB7ciBwcmVwYXJlX21vZmFfb3B0aW9uc30KbW9mYSA8LSBjcmVhdGVNT0ZBb2JqZWN0KG1hZSkKTW9kZWxPcHRpb25zIDwtIGdldERlZmF1bHRNb2RlbE9wdHMobW9mYSkKVHJhaW5PcHRpb25zLmZpbmFsIDwtIGdldERlZmF1bHRUcmFpbk9wdHMoKSAlPiUgdHJhbnNmb3JtKG1heGl0ZXI9MzAwMDApCiMgTG9vc2VyIHRvbGVyYW5jZSBib3VuZCBmb3IgZXhwbG9yYXRpb24KVHJhaW5PcHRpb25zLmV4cGxvcmUgPC0gZ2V0RGVmYXVsdFRyYWluT3B0cygpICU+JSB0cmFuc2Zvcm0odG9sZXJhbmNlPTAuMSkKCnRtcGQgPC0gdGVtcGZpbGUodG1wZGlyID0gdGVtcGRpcigpLCBwYXR0ZXJuID0gIm1vZmF0ZW1wIikKRGlyT3B0aW9ucy5maW5hbCA8LSBsaXN0KAogICAgZGF0YURpciA9IHRlbXBmaWxlKHRtcGRpciA9IHRtcGQsIHBhdHRlcm4gPSAibW9mYXJ1bl8iKSwKICAgIG91dEZpbGUgPSBoZXJlKCJzYXZlZF9kYXRhIiwgIm1vZmEiLCBnbHVlX2RhdGEocGFyYW1zLCAibW9mYS1tb2RlbF97Z2Vub21lfV97dHJhbnNjcmlwdG9tZX1fcm5hK3Byb21vdGVyLmhkZjUiKSksCiAgICByZHNGaWxlID0gaGVyZSgic2F2ZWRfZGF0YSIsICJtb2ZhIiwgZ2x1ZV9kYXRhKHBhcmFtcywgIm1vZmEtbW9kZWxfe2dlbm9tZX1fe3RyYW5zY3JpcHRvbWV9X3JuYStwcm9tb3Rlci5SRFMiKSkKKQpEaXJPcHRpb25zLmV4cGxvcmVMaXN0IDwtIGxhcHBseShzZXFfbGVuKHBhcmFtcyRtb2ZhX3J1bnMpLCBmdW5jdGlvbihpKSB7CiAgICBsaXN0KAogICAgICAgIGRhdGFEaXIgPSB0ZW1wZmlsZSh0bXBkaXIgPSB0bXBkLCBwYXR0ZXJuID0gc3RyX2MoIm1vZmFydW4iLCBpLCAiXyIpKSwKICAgICAgICBvdXRGaWxlID0gaGVyZSgic2F2ZWRfZGF0YSIsICJtb2ZhIiwgZ2x1ZV9kYXRhKHBhcmFtcywgIm1vZmEtbW9kZWxfe2dlbm9tZX1fe3RyYW5zY3JpcHRvbWV9X3JuYStwcm9tb3Rlcl9leHBsb3Jle2l9LmhkZjUiKSksCiAgICAgICAgcmRzRmlsZSA9IGhlcmUoInNhdmVkX2RhdGEiLCAibW9mYSIsIGdsdWVfZGF0YShwYXJhbXMsICJtb2ZhLW1vZGVsX3tnZW5vbWV9X3t0cmFuc2NyaXB0b21lfV9ybmErcHJvbW90ZXJfZXhwbG9yZXtpfS5SRFMiKSkKICAgICkKfSkKZmluYWwucmFuZG9tLnNlZWQgPC0gMTk4NgpleHBsb3JlLnJhbmRvbS5zZWVkcyA8LSBmaW5hbC5yYW5kb20uc2VlZCArIHNlcV9sZW4ocGFyYW1zJG1vZmFfcnVucykKYGBgCgojIE1vZGVsIGZpdHRpbmcKCmBgYHtyIGZpdF9leHBsb3JlX21vZGVsc30KIyBNT0ZBIGlzIGFscmVhZHkgcGFyYWxsZWxpemVkLCBzbyB3ZSBydW4gc2VxdWVudGlhbGx5Cm1vZmEuZXhwbG9yZSA8LSBsaXN0KCkKZm9yIChpIGluIHNlcV9sZW4ocGFyYW1zJG1vZmFfcnVucykpIHsKICAgIERpck9wdCA8LSBEaXJPcHRpb25zLmV4cGxvcmVMaXN0W1tpXV0KICAgIHNlZWQgPC0gZXhwbG9yZS5yYW5kb20uc2VlZHNbaV0KICAgIGRpci5jcmVhdGUoRGlyT3B0JGRhdGFEaXIsIHJlY3Vyc2l2ZT1UUlVFKQogICAgbW9mYS5leHBsb3JlW1tpXV0gPC0gbW9mYSAlPiUgCiAgICAgICAgcHJlcGFyZU1PRkEoRGlyT3B0aW9ucyA9IERpck9wdCwgTW9kZWxPcHRpb25zID0gTW9kZWxPcHRpb25zLCBUcmFpbk9wdGlvbnMgPSBUcmFpbk9wdGlvbnMuZXhwbG9yZSkgJT4lCiAgICAgICAgcnVuTU9GQShEaXJPcHRpb25zID0gRGlyT3B0LCBzZWVkPXNlZWQpCiAgICBzYXZlUkRTKG1vZmEuZXhwbG9yZVtbaV1dLCBEaXJPcHQkcmRzRmlsZSkKICAgICMjIFRPRE86IHNhdmVSRFMKfQp1bmxpbmsodG1wZCwgcmVjdXJzaXZlPVRSVUUpCmBgYAoKYGBge3IgZml0X2ZpbmFsX21vZGVsfQpkaXIuY3JlYXRlKERpck9wdGlvbnMuZmluYWwkZGF0YURpciwgcmVjdXJzaXZlPVRSVUUpCm1vZmEuZmluYWwgPC0gbW9mYSAlPiUKICAgIHByZXBhcmVNT0ZBKERpck9wdGlvbnMgPSBEaXJPcHRpb25zLmZpbmFsLCBNb2RlbE9wdGlvbnMgPSBNb2RlbE9wdGlvbnMsIFRyYWluT3B0aW9ucyA9IFRyYWluT3B0aW9ucy5maW5hbCkgJT4lCiAgICBydW5NT0ZBKERpck9wdGlvbnMgPSBEaXJPcHRpb25zLmZpbmFsLCBzZWVkPWZpbmFsLnJhbmRvbS5zZWVkKQpzYXZlUkRTKG1vZmEuZmluYWwsIERpck9wdGlvbnMuZmluYWwkcmRzRmlsZSkKdW5saW5rKHRtcGQsIHJlY3Vyc2l2ZT1UUlVFKQojbW9mYS5maW5hbCA8LSBtb2ZhLmV4cGxvcmVbWzFdXQojIyBUT0RPOiBzYXZlUkRTCmBgYAoKIyBCYXNpYyBtb2RlbCBRQwoKQWZ0ZXIgdGhlIHJ1biBmaW5pc2hlcy4gV2UgcHJvZHVjZSBhIGZldyBiYXNpYyBRQyBwbG90cy4gRmlyc3QsIHdlIHBsb3QgdGhlIHZhcmlhbmNlIGV4cGxhaW5lZCBieSBlYWNoIGZhY3RvciBpbiBlYWNoIG1vZGVsLgoKYGBge3IgcGxvdF9yMiwgd2FybmluZyA9IEZBTFNFfQpyMmxpc3QgPC0gbGFwcGx5KG1vZmEuZXhwbG9yZSwgY2FsY3VsYXRlVmFyaWFuY2VFeHBsYWluZWQpCnIyLmZpbmFsIDwtIGNhbGN1bGF0ZVZhcmlhbmNlRXhwbGFpbmVkKG1vZmEuZmluYWwpCmBgYAoKV2UgYWxzbyBtYWtlIGEgcGxvdCBjb21wYXJpbmcgdGhlIGZhY3RvcnMgYmV0d2VlbiBtdWx0aXBsZSBtb2RlbHMsIHRvIHZlcmlmeSB0aGF0IGVhY2ggbW9kZWwgaXMgZGlzY292ZXJpbmcgcm91Z2hseSB0aGUgc2FtZSBzZXQgb2YgZmFjdG9ycy4KCmBgYHtyIGNvbXBhcmVfbW9kZWxzfQojaW52aXNpYmxlKGNvbXBhcmVNb2RlbHMobW9mYS5leHBsb3JlKSkKYGBgCgpTaW5jZSBhbGwgdGhlIG1vZGVscyBzZWVtIHRvIGJlIGRpc2NvdmVyaW5nIHRoZSBzYW1lIGZhY3RvcnMsIHdlIGNhbiB1c2UgYW55IG1vZGVsIHdlIGNob29zZS4gRm9yIHRoZSBmaXJzdCBtb2RlbCwgd2UgbWFrZSBhICJiZWUgc3dhcm0gcGxvdCIgb2YgZWFjaCBmYWN0b3I6CgpgYGB7ciBiZWVfc3dhcm1fcGxvdHN9CiMgcCA8LSBsaXN0KAojICAgICBUaW1lUG9pbnQ9cGxvdEZhY3RvckJlZXN3YXJtKAojICAgICAgICAgbW9mYS5maW5hbCwgCiMgICAgICAgICBmYWN0b3JzID0gc2VxX2xlbihuY29sKGdldEZhY3RvcnMobW9mYS5maW5hbCwgaW5jbHVkZV9pbnRlcmNlcHQgPSBGQUxTRSkpKSwgCiMgICAgICAgICBjb2xvcl9ieSA9ICJ0aW1lX3BvaW50IikgKyAKIyAgICAgICAgIGFlcyh4PTApICsgeGxhYigiIikgKyB5bGFiKCJGYWN0b3IgVmFsdWUiKSArIAojICAgICAgICAgZmFjZXRfd3JhcCh+ZmFjdG9yLCBzY2FsZXM9ImZyZWUiKSArIAojICAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1OVUxMKSwKIyAgICAgQ2VsbFR5cGU9cGxvdEZhY3RvckJlZXN3YXJtKAojICAgICAgICAgbW9mYS5maW5hbCwgCiMgICAgICAgICBmYWN0b3JzID0gc2VxX2xlbihuY29sKGdldEZhY3RvcnMobW9mYS5maW5hbCwgaW5jbHVkZV9pbnRlcmNlcHQgPSBGQUxTRSkpKSwgCiMgICAgICAgICBjb2xvcl9ieSA9ICJjZWxsX3R5cGUiKSArIAojICAgICAgICAgYWVzKHg9MCkgKyB4bGFiKCIiKSArIHlsYWIoIkZhY3RvciBWYWx1ZSIpICsgCiMgICAgICAgICBmYWNldF93cmFwKH5mYWN0b3IsIHNjYWxlcz0iZnJlZSIpICsgCiMgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPU5VTEwpLAojICAgICBEb25vcj1wbG90RmFjdG9yQmVlc3dhcm0oCiMgICAgICAgICBtb2ZhLmZpbmFsLCAKIyAgICAgICAgIGZhY3RvcnMgPSBzZXFfbGVuKG5jb2woZ2V0RmFjdG9ycyhtb2ZhLmZpbmFsLCBpbmNsdWRlX2ludGVyY2VwdCA9IEZBTFNFKSkpLCAKIyAgICAgICAgIGNvbG9yX2J5ID0gImRvbm9yX2lkIikgKyAKIyAgICAgICAgIGFlcyh4PTApICsgeGxhYigiIikgKyB5bGFiKCJGYWN0b3IgVmFsdWUiKSArIAojICAgICAgICAgZmFjZXRfd3JhcCh+ZmFjdG9yLCBzY2FsZXM9ImZyZWUiKSArIAojICAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1OVUxMKSkKIyBnZ3ByaW50KHApCmBgYAo=