J. Taroni 2018

We’ve noted that pathways or genesets that are similar to one another, such as cell types of the myeloid lineage (e.g., neutrophils and monocytes/macrophages), can tend to get “lumped together” in PLIER models trained on training sets with smaller sample sizes or less relevant biological contexts. In contrast, the MultiPLIER model (~37K samples) tends to “separate” similar pathways into different latent variables. (See 06-sle-wb_cell_type and 07-sle_cell_type_recount2_model.)

Here, we’re interested in the notion of “pathway separation” when different datasets are used for training.

We’ll group related pathways/genesets into pathway sets. For instance, we’ll group monocyte- and macrophage-related gene sets such as SVM Monocytes and DMAP_MONO2 into a “monocyte-related pathway set.”

We define pathway separation as the following:

  • For each pathway set, at least one latent variable is significantly associated (FDR < 0.05) with a pathway in that set. We consider capturing only one pathway set to be insufficient for this evaluation. In essence, we want to make sure each set of pathways is represented in or captured by the model.
  • Each pathway set is uniquely and significantly associated with at least one latent variable. In the neutrophil vs. monocyte/macrophage example, it’s not sufficient to have a neutrophil-associated latent variable and a latent variable associated with both neutrophils and monocytes/macrophages; we also want the monocyte/macrophage set to be captured in a latent variable that is not also associated with the neutrophil set.

Set up

library(ComplexHeatmap)
Loading required package: grid
========================================
ComplexHeatmap version 1.17.1
Bioconductor page: http://bioconductor.org/packages/ComplexHeatmap/
Github page: https://github.com/jokergoo/ComplexHeatmap
Documentation: http://bioconductor.org/packages/ComplexHeatmap/

If you use it in published research, please cite:
Gu, Z. Complex heatmaps reveal patterns and correlations in multidimensional 
  genomic data. Bioinformatics 2016.
========================================

Functions

`%>%` <- dplyr::`%>%`

Custom function for detecting pathway separation

CheckPathwaySeparation <- function(plier.model, pathway.set1, pathway.set2,
                                   fdr.cutoff = 0.05) {
  # Given PLIER model and two sets of pathways, check if the model is able
  # to "separate" them -- i.e., does an LV exist that is significantly 
  # associated with pathways in one set BUT NOT the other set -- and return
  # a logical
  #
  # Args:
  #  plier.model: the output list from PLIER::PLIER
  #  pathway.set1: a character vector of one set of related pathways
  #  pathway.set2: a character vector of the other set of related pathways
  #  fdr.cutoff: what value should serve as the threshold for the "significant"
  #              associations? 0.05 by default
  # 
  # Returns: 
  #   A logical constant (TRUE or FALSE) that indicates whether or not the 
  #   conditions for pathway separation have been met for pathway.set1 and
  #   pathway.set2 
  # 
  
  # takes a vector of pathway names and the data.frame that only includes
  # significant associations between pathways and LVs (summary data.frame
  # from PLIER::PLIER)
  GetAssociatedLVs <- function(set.of.pathways, filtered.df) {
    # this collapsing step will not be problematic if there is 
    search.pattern <- paste(set.of.pathways, collapse = "|")
    search.index <- grep(search.pattern, filtered.df$pathway)
    associated.lvs <- unique(filtered.df$`LV index`[search.index])
  }
  
  # magrittr pipe
  `%>%` <- dplyr::`%>%`
  
  # filter the summary data.frame output from PLIER::PLIER to only associations
  # that meet the fdr.cutoff threshold
  sig.summary.df <- plier.model$summary %>%
    dplyr::filter(FDR < fdr.cutoff)
  
  # which LVs are associated with set 1?
  lvs.set1 <- GetAssociatedLVs(set.of.pathways = pathway.set1,
                               filtered.df = sig.summary.df)
  
  # which LVs are associated with set 2? 
  lvs.set2 <- GetAssociatedLVs(set.of.pathways = pathway.set2,
                               filtered.df = sig.summary.df)
  
  # are both sets of pathways captured?
  captured <- all(c(length(lvs.set1) > 0, length(lvs.set2) > 0))
  # if not, this doesn't qualify as separation
  if (!captured) {
    return(FALSE)
  } else {
    # is there at least one latent variable that is only associated with
    # that set -- for both sets
    set1.unique <- length(setdiff(lvs.set1, lvs.set2)) > 0
    set2.unique <- length(setdiff(lvs.set2, lvs.set1)) > 0
    # if so, return TRUE
    return(all(set1.unique, set2.unique)) 
  }
}

Files

Directory setup

# plot and result directory setup for this notebook
plot.dir <- file.path("plots", "32")
dir.create(plot.dir, recursive = TRUE, showWarnings = FALSE)
results.dir <- file.path("results", "32")
dir.create(results.dir, recursive = TRUE, showWarnings = FALSE)

All subsampling and biological context models we will be evaluating are in models/

models.dir <- "models"

Input

Models from the conditions being tested: sample size and biological context

# models with different sample sizes -- "subsampled" is in the RDS object 
# file names
size.model.files <- list.files(models.dir, pattern = "subsampled", 
                               full.names = TRUE)
size.model.files
[1] "models/subsampled_recount2_PLIER_model_1000.RDS"  "models/subsampled_recount2_PLIER_model_16000.RDS"
[3] "models/subsampled_recount2_PLIER_model_2000.RDS"  "models/subsampled_recount2_PLIER_model_32000.RDS"
[5] "models/subsampled_recount2_PLIER_model_4000.RDS"  "models/subsampled_recount2_PLIER_model_500.RDS"  
[7] "models/subsampled_recount2_PLIER_model_8000.RDS" 
# models for different biological contexts -- "accessions" is in the RDS object 
# file names
context.model.files <- list.files(models.dir, pattern = "accessions",
                                  full.names = TRUE)
context.model.files
[1] "models/recount2_blood_accessions_PLIER_model.RDS"         "models/recount2_cancer_accessions_PLIER_model.RDS"       
[3] "models/recount2_cell_line_accessions_PLIER_model.RDS"     "models/recount2_other_tissues_accessions_PLIER_model.RDS"
[5] "models/recount2_tissue_accessions_PLIER_model.RDS"       

Also want the MultiPLIER / full recount2 model

recount2.model.file <- file.path("data", "recount2_PLIER_data", 
                                 "recount_PLIER_model.RDS")

Sets of pathways for pathway separation

Type I and type II interferon

Interferon (IFN)

ifn.alpha.set <- c("REACTOME_INTERFERON_ALPHA_BETA_SIGNALING")
ifn.gamma.set <- c("REACTOME_INTERFERON_GAMMA_SIGNALING")

Myeloid lineage

neutrophil.set <- c("DMAP_GRAN3", "IRIS_Neutrophil-Resting", "SVM Neutrophils")
monocyte.set <- c("IRIS_Monocyte-Day0", "IRIS_Monocyte-Day1", 
                  "IRIS_Monocyte-Day7", "DMAP_MONO2", "SVM Monocytes",
                  "SVM Macrophages M0", "SVM Macrophages M1", 
                  "SVM Macrophages M2")

Proliferation

Molecular processes we would associate with proliferating cells, namely the G1 and G2 phases of the cell cycle.

g1.set <- c("REACTOME_G1_S_TRANSITION", "REACTOME_M_G1_TRANSITION",
            "REACTOME_APC_C_CDH1_MEDIATED_DEGRADATION_OF_CDC20_AND_OTHER_APC_C_CDH1_TARGETED_PROTEINS_IN_LATE_MITOSIS_EARLY_G1", 
            "REACTOME_CYCLIN_E_ASSOCIATED_EVENTS_DURING_G1_S_TRANSITION_", 
            "REACTOME_G1_PHASE", "REACTOME_MITOTIC_M_M_G1_PHASES",
            "REACTOME_P53_DEPENDENT_G1_DNA_DAMAGE_RESPONSE", 
            "REACTOME_MITOTIC_G1_G1_S_PHASES", 
            "REACTOME_P53_INDEPENDENT_G1_S_DNA_DAMAGE_CHECKPOINT")
g2.set <- c("REACTOME_MITOTIC_G2_G2_M_PHASES", "REACTOME_G2_M_CHECKPOINTS")

Wrapper functions

These are wrapper functions that are not intended to be used outside of the context of this notebook, i.e., we expect ifn.alpha.set, etc. to be in the global environment and we’ve essentially hard-coded this to use FDR < 0.05.

# check pathway separation of all pairs of pathways -- IFN, myeloid,
# 'proliferation'
AllPairs <- function(plier.model) {
  ifn.results <- CheckPathwaySeparation(plier.model = plier.model,
                                        pathway.set1 = ifn.alpha.set,
                                        pathway.set2 = ifn.gamma.set)
  myeloid.results <- CheckPathwaySeparation(plier.model = plier.model,
                                        pathway.set1 = neutrophil.set,
                                        pathway.set2 = monocyte.set)
  proliferation.results <- 
    CheckPathwaySeparation(plier.model = plier.model,
                           pathway.set1 = g1.set,
                           pathway.set2 = g2.set)
  return(list(IFN = ifn.results, MYELOID = myeloid.results,
              PROLIFERATION = proliferation.results))
}
# this is specifically designed for RDS files that are the output from
# scripts/subsampling_PLIER.R (e.g., have repeats, elements named PLIER) 
GetAllPairsDataFrame <- function(model.files, id.name) {
  # Given a named vector of filenames, get a data.frame of AllPairs results
  # 
  # Args:
  #   model.files: named vector of filenames
  #   id.name: what should the colname of the identifier be (e.g., 
  #            "sample_size")
  # 
  # Returns:
  #   A data.frame of AllPairs results
  
  # no names? get outta here
  if(is.null(names(model.files))) {
    stop("model.files must be a named vector!")
  }
  
  # for each file, read in the RDS (output of scripts/subsampling_PLIER.R) and
  # run AllPairs
  results.list <- lapply(model.files,  
                         function(x) {
                           # read in the list of 5 models
                           models.list <- readRDS(x)
                           lapply(models.list, 
                                  function(y) {
                                    # we need to specifically extract the 
                                    # `PLIER` element of the list and test all 
                                    # pairof pathways
                                    AllPairs(plier.model = y$PLIER)
                                  })
                         })
  
  # melt and bind the AllPairs pathway separation results, using the identifier 
  # supplied as id.name
  results.df <- dplyr::bind_rows(lapply(results.list, reshape2::melt), 
                                 .id = id.name)
  colnames(results.df)[3:4] <- c("pathway", "seed")
  
  # return the results data.frame
  return(results.df)
  
}
# given the output of AllPairsDataFrame, get it suitable shape for heatmaps
WrangleForHeatmap <- function(results.df, group.colname, group.order) {
  wrangled.df <- results.df %>%
    # for each group, pathway pair
    dplyr::group_by(!!rlang::sym(group.colname), pathway) %>%
    # count how many models where there's separation
    dplyr::summarize(model_count = sum(value)) %>%
    # spread the columns
    tidyr::spread(!!rlang::sym(group.colname), model_count) %>%
    # reorder using the vector of "levels" from group.order
    dplyr::select(c("pathway", group.order)) %>%
    # the pathway names should be rownames rather than an 
    # individual column
    tibble::column_to_rownames("pathway") %>%
    as.data.frame()
}

Sample size

Evaluations for the effect of sample size of pathway separation, with the following sample sizes: 500, 1000, 2000, 4000, 8000, 16000, 32000 We performed 5 repeats with different random seeds (see 29-train_models_different_sample_size.sh and scripts/subsampling_PLIER.R).

# we can derive useful names from the names of the model files
# themselves
names(size.model.files) <- sub(".RDS", "", sub(".*[_]", "", size.model.files))
# detect pathway separation
size.results.df <- GetAllPairsDataFrame(size.model.files,
                                        id.name = "sample_size")

We’re going to represent this as a heatmap, so we’ll need to wrangle the results into a data.frame that looks like this

500 32000
IFN 0 3
MYELOID 0 5

Where we’re counting how many of the 5 models (repeats) the pairs of pathways are separated.

We’ve written WrangleForHeatmap (see above) for this purpose.

size.df <- WrangleForHeatmap(results.df = size.results.df,
                             group.colname = "sample_size",
                             group.order = c("500", "1000", "2000", "4000", 
                                             "8000", "16000", "32000"))
Setting row names on a tibble is deprecated.
# let's take a look at the resulting data.frame
size.df
size.heatmap <- 
  ComplexHeatmap::Heatmap(as.matrix(size.df),
                          cluster_rows = FALSE,
                          cluster_columns = FALSE,
                          row_names_side = "left",
                          column_names_side = "top",
                          col = colorRampPalette(c("white", "blue3"))(6),
                          rect_gp = grid::gpar(col = "black"),
                          show_heatmap_legend = TRUE,
                          name = "number of models")
size.heatmap

Biological context

names(context.model.files) <- 
  stringr::str_match(context.model.files, "recount2_(.*?)_accessions")[, 2]
# detect for pathway separation
context.results.df <- GetAllPairsDataFrame(context.model.files,
                                           id.name = "biological_context")

Wrangle data for heatmap

context.df <- WrangleForHeatmap(results.df = context.results.df,
                                group.colname = "biological_context",
                                group.order = c("blood", "cancer", "tissue", 
                                                "cell_line", "other_tissues"))
Setting row names on a tibble is deprecated.
context.heatmap <- 
  ComplexHeatmap::Heatmap(as.matrix(context.df),
                          cluster_rows = FALSE,
                          cluster_columns = FALSE,
                          row_names_side = "left",
                          column_names_side = "top",
                          col = colorRampPalette(c("white", "blue3"))(6),
                          rect_gp = grid::gpar(col = "black"),
                          show_heatmap_legend = FALSE,
                          name = "number of models",
                          show_row_names = FALSE)

MultiPLIER

Now repeat this with the full model.

# read in the model
multiplier.model <- readRDS(recount2.model.file)
# check all pairs for separation
multiplier.results <- AllPairs(plier.model = multiplier.model)
# there's only one model -- so this is a binary outcome!
multiplier.df <- reshape2::melt(multiplier.results) %>%  # melt the results
  # the variable name is pathway
  dplyr::mutate(pathway = L1,
                MultiPLIER = as.integer(value)) %>%
  dplyr::select(c("pathway", "MultiPLIER")) %>%
  tibble::column_to_rownames("pathway") %>%
  as.data.frame()

Heatmap itself

multiplier.heatmap <- 
  ComplexHeatmap::Heatmap(as.matrix(multiplier.df),
                          cluster_rows = FALSE,
                          cluster_columns = FALSE,
                          column_names_side = "top",
                          col = "blue4",
                          rect_gp = grid::gpar(col = "black"),
                          name = "pathway separation",
                          show_heatmap_legend = TRUE,
                          show_row_names = FALSE)

Final plotting

heatmap.list <- size.heatmap + context.heatmap + multiplier.heatmap 
Heatmap/row annotation names are duplicated: number of modelsHeatmap/row annotation names are duplicated: number of models
pdf(file.path(plot.dir, "multiplier_separation.pdf"))
ComplexHeatmap::draw(heatmap.list)
dev.off()
null device 
          1 
LS0tCnRpdGxlOiAiUGF0aHdheSAnc2VwYXJhdGlvbiciCm91dHB1dDogICAKICBodG1sX25vdGVib29rOiAKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKKipKLiBUYXJvbmkgMjAxOCoqCgpXZSd2ZSBub3RlZCB0aGF0IHBhdGh3YXlzIG9yIGdlbmVzZXRzIHRoYXQgYXJlIHNpbWlsYXIgdG8gb25lIGFub3RoZXIsIHN1Y2ggYXMgCmNlbGwgdHlwZXMgb2YgdGhlIG15ZWxvaWQgbGluZWFnZSAoZS5nLiwgbmV1dHJvcGhpbHMgYW5kIG1vbm9jeXRlcy9tYWNyb3BoYWdlcyksCmNhbiB0ZW5kIHRvIGdldCAibHVtcGVkIHRvZ2V0aGVyIiBpbiBQTElFUiBtb2RlbHMgdHJhaW5lZCBvbiB0cmFpbmluZyBzZXRzIHdpdGgKc21hbGxlciBzYW1wbGUgc2l6ZXMgb3IgbGVzcyByZWxldmFudCBiaW9sb2dpY2FsIGNvbnRleHRzLgpJbiBjb250cmFzdCwgdGhlIE11bHRpUExJRVIgbW9kZWwgKH4zN0sgc2FtcGxlcykgdGVuZHMgdG8gInNlcGFyYXRlIiBzaW1pbGFyIApwYXRod2F5cyBpbnRvIGRpZmZlcmVudCBsYXRlbnQgdmFyaWFibGVzLgooU2VlIGAwNi1zbGUtd2JfY2VsbF90eXBlYCBhbmQgYDA3LXNsZV9jZWxsX3R5cGVfcmVjb3VudDJfbW9kZWxgLikKCioqSGVyZSwgd2UncmUgaW50ZXJlc3RlZCBpbiB0aGUgbm90aW9uIG9mICJwYXRod2F5IHNlcGFyYXRpb24iIHdoZW4gZGlmZmVyZW50IApkYXRhc2V0cyBhcmUgdXNlZCBmb3IgdHJhaW5pbmcuKioKCldlJ2xsIGdyb3VwIHJlbGF0ZWQgcGF0aHdheXMvZ2VuZXNldHMgaW50byBwYXRod2F5IHNldHMuCkZvciBpbnN0YW5jZSwgd2UnbGwgZ3JvdXAgbW9ub2N5dGUtIGFuZCBtYWNyb3BoYWdlLXJlbGF0ZWQgZ2VuZSBzZXRzIHN1Y2ggYXMKYFNWTSBNb25vY3l0ZXNgIGFuZCBgRE1BUF9NT05PMmAgaW50byBhICJtb25vY3l0ZS1yZWxhdGVkIHBhdGh3YXkgc2V0LiIKCldlIGRlZmluZSAqKnBhdGh3YXkgc2VwYXJhdGlvbioqIGFzIHRoZSBmb2xsb3dpbmc6CgoqIEZvciBlYWNoIHBhdGh3YXkgc2V0LCBhdCBsZWFzdCBvbmUgbGF0ZW50IHZhcmlhYmxlIGlzIHNpZ25pZmljYW50bHkKYXNzb2NpYXRlZCAoYEZEUiA8IDAuMDVgKSB3aXRoIGEgcGF0aHdheSBpbiB0aGF0IHNldC4KV2UgY29uc2lkZXIgY2FwdHVyaW5nIF9vbmx5IG9uZSBwYXRod2F5IHNldF8gdG8gYmUgaW5zdWZmaWNpZW50IGZvciB0aGlzIApldmFsdWF0aW9uLgpJbiBlc3NlbmNlLCB3ZSB3YW50IHRvIG1ha2Ugc3VyZSBlYWNoIHNldCBvZiBwYXRod2F5cyBpcyByZXByZXNlbnRlZCBpbiBvciAKY2FwdHVyZWQgYnkgdGhlIG1vZGVsLiAKKiBFYWNoIHBhdGh3YXkgc2V0IGlzIF91bmlxdWVseV8gYW5kIHNpZ25pZmljYW50bHkgYXNzb2NpYXRlZCB3aXRoIGF0IGxlYXN0IG9uZQpsYXRlbnQgdmFyaWFibGUuIApJbiB0aGUgbmV1dHJvcGhpbCB2cy4gbW9ub2N5dGUvbWFjcm9waGFnZSBleGFtcGxlLCBpdCdzIG5vdCBzdWZmaWNpZW50IHRvIGhhdmUgYQpuZXV0cm9waGlsLWFzc29jaWF0ZWQgbGF0ZW50IHZhcmlhYmxlIGFuZCBhIGxhdGVudCB2YXJpYWJsZSBhc3NvY2lhdGVkIHdpdGggCmJvdGggbmV1dHJvcGhpbHMgYW5kIG1vbm9jeXRlcy9tYWNyb3BoYWdlczsgd2UgYWxzbyB3YW50IHRoZSBtb25vY3l0ZS9tYWNyb3BoYWdlCnNldCB0byBiZSBjYXB0dXJlZCBpbiBhIGxhdGVudCB2YXJpYWJsZSB0aGF0IGlzIF9ub3QgYWxzbyBhc3NvY2lhdGVkXyB3aXRoCnRoZSBuZXV0cm9waGlsIHNldC4KCiMjIFNldCB1cAoKYGBge3J9CmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmBgYAoKIyMjIEZ1bmN0aW9ucwoKYGBge3J9CmAlPiVgIDwtIGRwbHlyOjpgJT4lYApgYGAKCiMjIyMgQ3VzdG9tIGZ1bmN0aW9uIGZvciBkZXRlY3RpbmcgcGF0aHdheSBzZXBhcmF0aW9uCgpgYGB7cn0KQ2hlY2tQYXRod2F5U2VwYXJhdGlvbiA8LSBmdW5jdGlvbihwbGllci5tb2RlbCwgcGF0aHdheS5zZXQxLCBwYXRod2F5LnNldDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmRyLmN1dG9mZiA9IDAuMDUpIHsKICAjIEdpdmVuIFBMSUVSIG1vZGVsIGFuZCB0d28gc2V0cyBvZiBwYXRod2F5cywgY2hlY2sgaWYgdGhlIG1vZGVsIGlzIGFibGUKICAjIHRvICJzZXBhcmF0ZSIgdGhlbSAtLSBpLmUuLCBkb2VzIGFuIExWIGV4aXN0IHRoYXQgaXMgc2lnbmlmaWNhbnRseSAKICAjIGFzc29jaWF0ZWQgd2l0aCBwYXRod2F5cyBpbiBvbmUgc2V0IEJVVCBOT1QgdGhlIG90aGVyIHNldCAtLSBhbmQgcmV0dXJuCiAgIyBhIGxvZ2ljYWwKICAjCiAgIyBBcmdzOgogICMgIHBsaWVyLm1vZGVsOiB0aGUgb3V0cHV0IGxpc3QgZnJvbSBQTElFUjo6UExJRVIKICAjICBwYXRod2F5LnNldDE6IGEgY2hhcmFjdGVyIHZlY3RvciBvZiBvbmUgc2V0IG9mIHJlbGF0ZWQgcGF0aHdheXMKICAjICBwYXRod2F5LnNldDI6IGEgY2hhcmFjdGVyIHZlY3RvciBvZiB0aGUgb3RoZXIgc2V0IG9mIHJlbGF0ZWQgcGF0aHdheXMKICAjICBmZHIuY3V0b2ZmOiB3aGF0IHZhbHVlIHNob3VsZCBzZXJ2ZSBhcyB0aGUgdGhyZXNob2xkIGZvciB0aGUgInNpZ25pZmljYW50IgogICMgICAgICAgICAgICAgIGFzc29jaWF0aW9ucz8gMC4wNSBieSBkZWZhdWx0CiAgIyAKICAjIFJldHVybnM6IAogICMgICBBIGxvZ2ljYWwgY29uc3RhbnQgKFRSVUUgb3IgRkFMU0UpIHRoYXQgaW5kaWNhdGVzIHdoZXRoZXIgb3Igbm90IHRoZSAKICAjICAgY29uZGl0aW9ucyBmb3IgcGF0aHdheSBzZXBhcmF0aW9uIGhhdmUgYmVlbiBtZXQgZm9yIHBhdGh3YXkuc2V0MSBhbmQKICAjICAgcGF0aHdheS5zZXQyIAogICMgCiAgCiAgIyB0YWtlcyBhIHZlY3RvciBvZiBwYXRod2F5IG5hbWVzIGFuZCB0aGUgZGF0YS5mcmFtZSB0aGF0IG9ubHkgaW5jbHVkZXMKICAjIHNpZ25pZmljYW50IGFzc29jaWF0aW9ucyBiZXR3ZWVuIHBhdGh3YXlzIGFuZCBMVnMgKHN1bW1hcnkgZGF0YS5mcmFtZQogICMgZnJvbSBQTElFUjo6UExJRVIpCiAgR2V0QXNzb2NpYXRlZExWcyA8LSBmdW5jdGlvbihzZXQub2YucGF0aHdheXMsIGZpbHRlcmVkLmRmKSB7CiAgICAjIHRoaXMgY29sbGFwc2luZyBzdGVwIHdpbGwgbm90IGJlIHByb2JsZW1hdGljIGlmIHRoZXJlIGlzIAogICAgc2VhcmNoLnBhdHRlcm4gPC0gcGFzdGUoc2V0Lm9mLnBhdGh3YXlzLCBjb2xsYXBzZSA9ICJ8IikKICAgIHNlYXJjaC5pbmRleCA8LSBncmVwKHNlYXJjaC5wYXR0ZXJuLCBmaWx0ZXJlZC5kZiRwYXRod2F5KQogICAgYXNzb2NpYXRlZC5sdnMgPC0gdW5pcXVlKGZpbHRlcmVkLmRmJGBMViBpbmRleGBbc2VhcmNoLmluZGV4XSkKICB9CiAgCiAgIyBtYWdyaXR0ciBwaXBlCiAgYCU+JWAgPC0gZHBseXI6OmAlPiVgCiAgCiAgIyBmaWx0ZXIgdGhlIHN1bW1hcnkgZGF0YS5mcmFtZSBvdXRwdXQgZnJvbSBQTElFUjo6UExJRVIgdG8gb25seSBhc3NvY2lhdGlvbnMKICAjIHRoYXQgbWVldCB0aGUgZmRyLmN1dG9mZiB0aHJlc2hvbGQKICBzaWcuc3VtbWFyeS5kZiA8LSBwbGllci5tb2RlbCRzdW1tYXJ5ICU+JQogICAgZHBseXI6OmZpbHRlcihGRFIgPCBmZHIuY3V0b2ZmKQogIAogICMgd2hpY2ggTFZzIGFyZSBhc3NvY2lhdGVkIHdpdGggc2V0IDE/CiAgbHZzLnNldDEgPC0gR2V0QXNzb2NpYXRlZExWcyhzZXQub2YucGF0aHdheXMgPSBwYXRod2F5LnNldDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXJlZC5kZiA9IHNpZy5zdW1tYXJ5LmRmKQogIAogICMgd2hpY2ggTFZzIGFyZSBhc3NvY2lhdGVkIHdpdGggc2V0IDI/IAogIGx2cy5zZXQyIDwtIEdldEFzc29jaWF0ZWRMVnMoc2V0Lm9mLnBhdGh3YXlzID0gcGF0aHdheS5zZXQyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyZWQuZGYgPSBzaWcuc3VtbWFyeS5kZikKICAKICAjIGFyZSBib3RoIHNldHMgb2YgcGF0aHdheXMgY2FwdHVyZWQ/CiAgY2FwdHVyZWQgPC0gYWxsKGMobGVuZ3RoKGx2cy5zZXQxKSA+IDAsIGxlbmd0aChsdnMuc2V0MikgPiAwKSkKICAjIGlmIG5vdCwgdGhpcyBkb2Vzbid0IHF1YWxpZnkgYXMgc2VwYXJhdGlvbgogIGlmICghY2FwdHVyZWQpIHsKICAgIHJldHVybihGQUxTRSkKICB9IGVsc2UgewogICAgIyBpcyB0aGVyZSBhdCBsZWFzdCBvbmUgbGF0ZW50IHZhcmlhYmxlIHRoYXQgaXMgb25seSBhc3NvY2lhdGVkIHdpdGgKICAgICMgdGhhdCBzZXQgLS0gZm9yIGJvdGggc2V0cwogICAgc2V0MS51bmlxdWUgPC0gbGVuZ3RoKHNldGRpZmYobHZzLnNldDEsIGx2cy5zZXQyKSkgPiAwCiAgICBzZXQyLnVuaXF1ZSA8LSBsZW5ndGgoc2V0ZGlmZihsdnMuc2V0MiwgbHZzLnNldDEpKSA+IDAKICAgICMgaWYgc28sIHJldHVybiBUUlVFCiAgICByZXR1cm4oYWxsKHNldDEudW5pcXVlLCBzZXQyLnVuaXF1ZSkpIAogIH0KfQpgYGAKCiMjIyBGaWxlcwoKIyMjIyBEaXJlY3Rvcnkgc2V0dXAKCmBgYHtyfQojIHBsb3QgYW5kIHJlc3VsdCBkaXJlY3Rvcnkgc2V0dXAgZm9yIHRoaXMgbm90ZWJvb2sKcGxvdC5kaXIgPC0gZmlsZS5wYXRoKCJwbG90cyIsICIzMiIpCmRpci5jcmVhdGUocGxvdC5kaXIsIHJlY3Vyc2l2ZSA9IFRSVUUsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQpyZXN1bHRzLmRpciA8LSBmaWxlLnBhdGgoInJlc3VsdHMiLCAiMzIiKQpkaXIuY3JlYXRlKHJlc3VsdHMuZGlyLCByZWN1cnNpdmUgPSBUUlVFLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKYGBgCgpBbGwgc3Vic2FtcGxpbmcgYW5kIGJpb2xvZ2ljYWwgY29udGV4dCBtb2RlbHMgd2Ugd2lsbCBiZSBldmFsdWF0aW5nIGFyZSBpbiAKYG1vZGVscy9gCgpgYGB7cn0KbW9kZWxzLmRpciA8LSAibW9kZWxzIgpgYGAKCiMjIyMgSW5wdXQKCk1vZGVscyBmcm9tIHRoZSBjb25kaXRpb25zIGJlaW5nIHRlc3RlZDogc2FtcGxlIHNpemUgYW5kIGJpb2xvZ2ljYWwgY29udGV4dAoKYGBge3J9CiMgbW9kZWxzIHdpdGggZGlmZmVyZW50IHNhbXBsZSBzaXplcyAtLSAic3Vic2FtcGxlZCIgaXMgaW4gdGhlIFJEUyBvYmplY3QgCiMgZmlsZSBuYW1lcwpzaXplLm1vZGVsLmZpbGVzIDwtIGxpc3QuZmlsZXMobW9kZWxzLmRpciwgcGF0dGVybiA9ICJzdWJzYW1wbGVkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsLm5hbWVzID0gVFJVRSkKc2l6ZS5tb2RlbC5maWxlcwpgYGAKCmBgYHtyfQojIG1vZGVscyBmb3IgZGlmZmVyZW50IGJpb2xvZ2ljYWwgY29udGV4dHMgLS0gImFjY2Vzc2lvbnMiIGlzIGluIHRoZSBSRFMgb2JqZWN0IAojIGZpbGUgbmFtZXMKY29udGV4dC5tb2RlbC5maWxlcyA8LSBsaXN0LmZpbGVzKG1vZGVscy5kaXIsIHBhdHRlcm4gPSAiYWNjZXNzaW9ucyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsLm5hbWVzID0gVFJVRSkKY29udGV4dC5tb2RlbC5maWxlcwpgYGAKCkFsc28gd2FudCB0aGUgTXVsdGlQTElFUiAvIGZ1bGwgYHJlY291bnQyYCBtb2RlbAoKYGBge3J9CnJlY291bnQyLm1vZGVsLmZpbGUgPC0gZmlsZS5wYXRoKCJkYXRhIiwgInJlY291bnQyX1BMSUVSX2RhdGEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInJlY291bnRfUExJRVJfbW9kZWwuUkRTIikKYGBgCgojIyBTZXRzIG9mIHBhdGh3YXlzIGZvciBwYXRod2F5IHNlcGFyYXRpb24KCiMjIyMgVHlwZSBJIGFuZCB0eXBlIElJIGludGVyZmVyb24KCkludGVyZmVyb24gKElGTikKCmBgYHtyfQppZm4uYWxwaGEuc2V0IDwtIGMoIlJFQUNUT01FX0lOVEVSRkVST05fQUxQSEFfQkVUQV9TSUdOQUxJTkciKQppZm4uZ2FtbWEuc2V0IDwtIGMoIlJFQUNUT01FX0lOVEVSRkVST05fR0FNTUFfU0lHTkFMSU5HIikKYGBgCgojIyMjIE15ZWxvaWQgbGluZWFnZQoKYGBge3J9Cm5ldXRyb3BoaWwuc2V0IDwtIGMoIkRNQVBfR1JBTjMiLCAiSVJJU19OZXV0cm9waGlsLVJlc3RpbmciLCAiU1ZNIE5ldXRyb3BoaWxzIikKbW9ub2N5dGUuc2V0IDwtIGMoIklSSVNfTW9ub2N5dGUtRGF5MCIsICJJUklTX01vbm9jeXRlLURheTEiLCAKICAgICAgICAgICAgICAgICAgIklSSVNfTW9ub2N5dGUtRGF5NyIsICJETUFQX01PTk8yIiwgIlNWTSBNb25vY3l0ZXMiLAogICAgICAgICAgICAgICAgICAiU1ZNIE1hY3JvcGhhZ2VzIE0wIiwgIlNWTSBNYWNyb3BoYWdlcyBNMSIsIAogICAgICAgICAgICAgICAgICAiU1ZNIE1hY3JvcGhhZ2VzIE0yIikKYGBgCgojIyMjIFByb2xpZmVyYXRpb24KCk1vbGVjdWxhciBwcm9jZXNzZXMgd2Ugd291bGQgYXNzb2NpYXRlIHdpdGggcHJvbGlmZXJhdGluZyBjZWxscywgbmFtZWx5IHRoZQpHMSBhbmQgRzIgcGhhc2VzIG9mIHRoZSBjZWxsIGN5Y2xlLgoKYGBge3J9CmcxLnNldCA8LSBjKCJSRUFDVE9NRV9HMV9TX1RSQU5TSVRJT04iLCAiUkVBQ1RPTUVfTV9HMV9UUkFOU0lUSU9OIiwKICAgICAgICAgICAgIlJFQUNUT01FX0FQQ19DX0NESDFfTUVESUFURURfREVHUkFEQVRJT05fT0ZfQ0RDMjBfQU5EX09USEVSX0FQQ19DX0NESDFfVEFSR0VURURfUFJPVEVJTlNfSU5fTEFURV9NSVRPU0lTX0VBUkxZX0cxIiwgCiAgICAgICAgICAgICJSRUFDVE9NRV9DWUNMSU5fRV9BU1NPQ0lBVEVEX0VWRU5UU19EVVJJTkdfRzFfU19UUkFOU0lUSU9OXyIsIAogICAgICAgICAgICAiUkVBQ1RPTUVfRzFfUEhBU0UiLCAiUkVBQ1RPTUVfTUlUT1RJQ19NX01fRzFfUEhBU0VTIiwKICAgICAgICAgICAgIlJFQUNUT01FX1A1M19ERVBFTkRFTlRfRzFfRE5BX0RBTUFHRV9SRVNQT05TRSIsIAogICAgICAgICAgICAiUkVBQ1RPTUVfTUlUT1RJQ19HMV9HMV9TX1BIQVNFUyIsIAogICAgICAgICAgICAiUkVBQ1RPTUVfUDUzX0lOREVQRU5ERU5UX0cxX1NfRE5BX0RBTUFHRV9DSEVDS1BPSU5UIikKZzIuc2V0IDwtIGMoIlJFQUNUT01FX01JVE9USUNfRzJfRzJfTV9QSEFTRVMiLCAiUkVBQ1RPTUVfRzJfTV9DSEVDS1BPSU5UUyIpCmBgYAoKIyMjIyBXcmFwcGVyIGZ1bmN0aW9ucwoKVGhlc2UgYXJlIHdyYXBwZXIgZnVuY3Rpb25zIHRoYXQgYXJlIG5vdCBpbnRlbmRlZCB0byBiZSB1c2VkIG91dHNpZGUgb2YgdGhlCmNvbnRleHQgb2YgdGhpcyBub3RlYm9vaywgaS5lLiwgd2UgZXhwZWN0IGBpZm4uYWxwaGEuc2V0YCwgZXRjLiB0byBiZQppbiB0aGUgZ2xvYmFsIGVudmlyb25tZW50IGFuZCB3ZSd2ZSBlc3NlbnRpYWxseSBoYXJkLWNvZGVkIHRoaXMgdG8gdXNlIApgRkRSIDwgMC4wNWAuCgpgYGB7cn0KIyBjaGVjayBwYXRod2F5IHNlcGFyYXRpb24gb2YgYWxsIHBhaXJzIG9mIHBhdGh3YXlzIC0tIElGTiwgbXllbG9pZCwKIyAncHJvbGlmZXJhdGlvbicKQWxsUGFpcnMgPC0gZnVuY3Rpb24ocGxpZXIubW9kZWwpIHsKICBpZm4ucmVzdWx0cyA8LSBDaGVja1BhdGh3YXlTZXBhcmF0aW9uKHBsaWVyLm1vZGVsID0gcGxpZXIubW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXRod2F5LnNldDEgPSBpZm4uYWxwaGEuc2V0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0aHdheS5zZXQyID0gaWZuLmdhbW1hLnNldCkKICBteWVsb2lkLnJlc3VsdHMgPC0gQ2hlY2tQYXRod2F5U2VwYXJhdGlvbihwbGllci5tb2RlbCA9IHBsaWVyLm1vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0aHdheS5zZXQxID0gbmV1dHJvcGhpbC5zZXQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXRod2F5LnNldDIgPSBtb25vY3l0ZS5zZXQpCiAgcHJvbGlmZXJhdGlvbi5yZXN1bHRzIDwtIAogICAgQ2hlY2tQYXRod2F5U2VwYXJhdGlvbihwbGllci5tb2RlbCA9IHBsaWVyLm1vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICBwYXRod2F5LnNldDEgPSBnMS5zZXQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdGh3YXkuc2V0MiA9IGcyLnNldCkKCiAgcmV0dXJuKGxpc3QoSUZOID0gaWZuLnJlc3VsdHMsIE1ZRUxPSUQgPSBteWVsb2lkLnJlc3VsdHMsCiAgICAgICAgICAgICAgUFJPTElGRVJBVElPTiA9IHByb2xpZmVyYXRpb24ucmVzdWx0cykpCn0KYGBgCgpgYGB7cn0KIyB0aGlzIGlzIHNwZWNpZmljYWxseSBkZXNpZ25lZCBmb3IgUkRTIGZpbGVzIHRoYXQgYXJlIHRoZSBvdXRwdXQgZnJvbQojIHNjcmlwdHMvc3Vic2FtcGxpbmdfUExJRVIuUiAoZS5nLiwgaGF2ZSByZXBlYXRzLCBlbGVtZW50cyBuYW1lZCBQTElFUikgCkdldEFsbFBhaXJzRGF0YUZyYW1lIDwtIGZ1bmN0aW9uKG1vZGVsLmZpbGVzLCBpZC5uYW1lKSB7CiAgIyBHaXZlbiBhIG5hbWVkIHZlY3RvciBvZiBmaWxlbmFtZXMsIGdldCBhIGRhdGEuZnJhbWUgb2YgQWxsUGFpcnMgcmVzdWx0cwogICMgCiAgIyBBcmdzOgogICMgICBtb2RlbC5maWxlczogbmFtZWQgdmVjdG9yIG9mIGZpbGVuYW1lcwogICMgICBpZC5uYW1lOiB3aGF0IHNob3VsZCB0aGUgY29sbmFtZSBvZiB0aGUgaWRlbnRpZmllciBiZSAoZS5nLiwgCiAgIyAgICAgICAgICAgICJzYW1wbGVfc2l6ZSIpCiAgIyAKICAjIFJldHVybnM6CiAgIyAgIEEgZGF0YS5mcmFtZSBvZiBBbGxQYWlycyByZXN1bHRzCiAgCiAgIyBubyBuYW1lcz8gZ2V0IG91dHRhIGhlcmUKICBpZihpcy5udWxsKG5hbWVzKG1vZGVsLmZpbGVzKSkpIHsKICAgIHN0b3AoIm1vZGVsLmZpbGVzIG11c3QgYmUgYSBuYW1lZCB2ZWN0b3IhIikKICB9CiAgCiAgIyBmb3IgZWFjaCBmaWxlLCByZWFkIGluIHRoZSBSRFMgKG91dHB1dCBvZiBzY3JpcHRzL3N1YnNhbXBsaW5nX1BMSUVSLlIpIGFuZAogICMgcnVuIEFsbFBhaXJzCiAgcmVzdWx0cy5saXN0IDwtIGxhcHBseShtb2RlbC5maWxlcywgIAogICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAjIHJlYWQgaW4gdGhlIGxpc3Qgb2YgNSBtb2RlbHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxzLmxpc3QgPC0gcmVhZFJEUyh4KQogICAgICAgICAgICAgICAgICAgICAgICAgICBsYXBwbHkobW9kZWxzLmxpc3QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHdlIG5lZWQgdG8gc3BlY2lmaWNhbGx5IGV4dHJhY3QgdGhlIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGBQTElFUmAgZWxlbWVudCBvZiB0aGUgbGlzdCBhbmQgdGVzdCBhbGwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgcGFpcm9mIHBhdGh3YXlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFsbFBhaXJzKHBsaWVyLm1vZGVsID0geSRQTElFUikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0pCiAgICAgICAgICAgICAgICAgICAgICAgICB9KQogIAogICMgbWVsdCBhbmQgYmluZCB0aGUgQWxsUGFpcnMgcGF0aHdheSBzZXBhcmF0aW9uIHJlc3VsdHMsIHVzaW5nIHRoZSBpZGVudGlmaWVyIAogICMgc3VwcGxpZWQgYXMgaWQubmFtZQogIHJlc3VsdHMuZGYgPC0gZHBseXI6OmJpbmRfcm93cyhsYXBwbHkocmVzdWx0cy5saXN0LCByZXNoYXBlMjo6bWVsdCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuaWQgPSBpZC5uYW1lKQogIGNvbG5hbWVzKHJlc3VsdHMuZGYpWzM6NF0gPC0gYygicGF0aHdheSIsICJzZWVkIikKICAKICAjIHJldHVybiB0aGUgcmVzdWx0cyBkYXRhLmZyYW1lCiAgcmV0dXJuKHJlc3VsdHMuZGYpCiAgCn0KYGBgCgpgYGB7cn0KIyBnaXZlbiB0aGUgb3V0cHV0IG9mIEFsbFBhaXJzRGF0YUZyYW1lLCBnZXQgaXQgc3VpdGFibGUgc2hhcGUgZm9yIGhlYXRtYXBzCldyYW5nbGVGb3JIZWF0bWFwIDwtIGZ1bmN0aW9uKHJlc3VsdHMuZGYsIGdyb3VwLmNvbG5hbWUsIGdyb3VwLm9yZGVyKSB7CiAgd3JhbmdsZWQuZGYgPC0gcmVzdWx0cy5kZiAlPiUKICAgICMgZm9yIGVhY2ggZ3JvdXAsIHBhdGh3YXkgcGFpcgogICAgZHBseXI6Omdyb3VwX2J5KCEhcmxhbmc6OnN5bShncm91cC5jb2xuYW1lKSwgcGF0aHdheSkgJT4lCiAgICAjIGNvdW50IGhvdyBtYW55IG1vZGVscyB3aGVyZSB0aGVyZSdzIHNlcGFyYXRpb24KICAgIGRwbHlyOjpzdW1tYXJpemUobW9kZWxfY291bnQgPSBzdW0odmFsdWUpKSAlPiUKICAgICMgc3ByZWFkIHRoZSBjb2x1bW5zCiAgICB0aWR5cjo6c3ByZWFkKCEhcmxhbmc6OnN5bShncm91cC5jb2xuYW1lKSwgbW9kZWxfY291bnQpICU+JQogICAgIyByZW9yZGVyIHVzaW5nIHRoZSB2ZWN0b3Igb2YgImxldmVscyIgZnJvbSBncm91cC5vcmRlcgogICAgZHBseXI6OnNlbGVjdChjKCJwYXRod2F5IiwgZ3JvdXAub3JkZXIpKSAlPiUKICAgICMgdGhlIHBhdGh3YXkgbmFtZXMgc2hvdWxkIGJlIHJvd25hbWVzIHJhdGhlciB0aGFuIGFuIAogICAgIyBpbmRpdmlkdWFsIGNvbHVtbgogICAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoInBhdGh3YXkiKSAlPiUKICAgIGFzLmRhdGEuZnJhbWUoKQp9CmBgYAoKIyMgU2FtcGxlIHNpemUKCkV2YWx1YXRpb25zIGZvciB0aGUgZWZmZWN0IG9mIHNhbXBsZSBzaXplIG9mIHBhdGh3YXkgc2VwYXJhdGlvbiwgd2l0aCB0aGUKZm9sbG93aW5nIHNhbXBsZSBzaXplczogYDUwMGAsIGAxMDAwYCwgYDIwMDBgLCBgNDAwMGAsIGA4MDAwYCwgYDE2MDAwYCwgYDMyMDAwYApXZSBwZXJmb3JtZWQgNSByZXBlYXRzIHdpdGggZGlmZmVyZW50IHJhbmRvbSBzZWVkcyAKKHNlZSBgMjktdHJhaW5fbW9kZWxzX2RpZmZlcmVudF9zYW1wbGVfc2l6ZS5zaGAgYW5kIApgc2NyaXB0cy9zdWJzYW1wbGluZ19QTElFUi5SYCkuCgpgYGB7cn0KIyB3ZSBjYW4gZGVyaXZlIHVzZWZ1bCBuYW1lcyBmcm9tIHRoZSBuYW1lcyBvZiB0aGUgbW9kZWwgZmlsZXMKIyB0aGVtc2VsdmVzCm5hbWVzKHNpemUubW9kZWwuZmlsZXMpIDwtIHN1YigiLlJEUyIsICIiLCBzdWIoIi4qW19dIiwgIiIsIHNpemUubW9kZWwuZmlsZXMpKQojIGRldGVjdCBwYXRod2F5IHNlcGFyYXRpb24Kc2l6ZS5yZXN1bHRzLmRmIDwtIEdldEFsbFBhaXJzRGF0YUZyYW1lKHNpemUubW9kZWwuZmlsZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZC5uYW1lID0gInNhbXBsZV9zaXplIikKYGBgCgpXZSdyZSBnb2luZyB0byByZXByZXNlbnQgdGhpcyBhcyBhIGhlYXRtYXAsIHNvIHdlJ2xsIG5lZWQgdG8gd3JhbmdsZSB0aGUKcmVzdWx0cyBpbnRvIGEgYGRhdGEuZnJhbWVgIHRoYXQgbG9va3MgbGlrZSB0aGlzCgp8ICAgfCA1MDAgfCAuLi4gfCAzMjAwMCB8Cnw6LTp8Oi0tLTp8Oi0tLTp8Oi0tLS0tOnwKfElGTnwgIDAgIHwgLi4uIHwgMyAgICAgfAp8TVlFTE9JRHwgIDAgIHwgLi4uIHwgNSAgIHwKCldoZXJlIHdlJ3JlIGNvdW50aW5nIGhvdyBtYW55IG9mIHRoZSA1IG1vZGVscyAocmVwZWF0cykgdGhlIHBhaXJzIG9mIHBhdGh3YXlzCmFyZSBzZXBhcmF0ZWQuCgpXZSd2ZSB3cml0dGVuIGBXcmFuZ2xlRm9ySGVhdG1hcGAgKHNlZSBhYm92ZSkgZm9yIHRoaXMgcHVycG9zZS4KCmBgYHtyfQpzaXplLmRmIDwtIFdyYW5nbGVGb3JIZWF0bWFwKHJlc3VsdHMuZGYgPSBzaXplLnJlc3VsdHMuZGYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAuY29sbmFtZSA9ICJzYW1wbGVfc2l6ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAub3JkZXIgPSBjKCI1MDAiLCAiMTAwMCIsICIyMDAwIiwgIjQwMDAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjgwMDAiLCAiMTYwMDAiLCAiMzIwMDAiKSkKIyBsZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgcmVzdWx0aW5nIGRhdGEuZnJhbWUKc2l6ZS5kZgpgYGAKCmBgYHtyfQpzaXplLmhlYXRtYXAgPC0gCiAgQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAoYXMubWF0cml4KHNpemUuZGYpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfcm93cyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfY29sdW1ucyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgIHJvd19uYW1lc19zaWRlID0gImxlZnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9uYW1lc19zaWRlID0gInRvcCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gY29sb3JSYW1wUGFsZXR0ZShjKCJ3aGl0ZSIsICJibHVlMyIpKSg2KSwKICAgICAgICAgICAgICAgICAgICAgICAgICByZWN0X2dwID0gZ3JpZDo6Z3Bhcihjb2wgPSAiYmxhY2siKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2hlYXRtYXBfbGVnZW5kID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIm51bWJlciBvZiBtb2RlbHMiKQpzaXplLmhlYXRtYXAKYGBgCgojIyBCaW9sb2dpY2FsIGNvbnRleHQKCmBgYHtyfQpuYW1lcyhjb250ZXh0Lm1vZGVsLmZpbGVzKSA8LSAKICBzdHJpbmdyOjpzdHJfbWF0Y2goY29udGV4dC5tb2RlbC5maWxlcywgInJlY291bnQyXyguKj8pX2FjY2Vzc2lvbnMiKVssIDJdCiMgZGV0ZWN0IGZvciBwYXRod2F5IHNlcGFyYXRpb24KY29udGV4dC5yZXN1bHRzLmRmIDwtIEdldEFsbFBhaXJzRGF0YUZyYW1lKGNvbnRleHQubW9kZWwuZmlsZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZC5uYW1lID0gImJpb2xvZ2ljYWxfY29udGV4dCIpCmBgYAoKV3JhbmdsZSBkYXRhIGZvciBoZWF0bWFwCgpgYGB7cn0KY29udGV4dC5kZiA8LSBXcmFuZ2xlRm9ySGVhdG1hcChyZXN1bHRzLmRmID0gY29udGV4dC5yZXN1bHRzLmRmLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwLmNvbG5hbWUgPSAiYmlvbG9naWNhbF9jb250ZXh0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cC5vcmRlciA9IGMoImJsb29kIiwgImNhbmNlciIsICJ0aXNzdWUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImNlbGxfbGluZSIsICJvdGhlcl90aXNzdWVzIikpCmBgYAoKYGBge3J9CmNvbnRleHQuaGVhdG1hcCA8LSAKICBDb21wbGV4SGVhdG1hcDo6SGVhdG1hcChhcy5tYXRyaXgoY29udGV4dC5kZiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9jb2x1bW5zID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgcm93X25hbWVzX3NpZGUgPSAibGVmdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uX25hbWVzX3NpZGUgPSAidG9wIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBjb2wgPSBjb2xvclJhbXBQYWxldHRlKGMoIndoaXRlIiwgImJsdWUzIikpKDYpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHJlY3RfZ3AgPSBncmlkOjpncGFyKGNvbCA9ICJibGFjayIpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfaGVhdG1hcF9sZWdlbmQgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIm51bWJlciBvZiBtb2RlbHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfcm93X25hbWVzID0gRkFMU0UpCmBgYAoKIyMgTXVsdGlQTElFUgoKTm93IHJlcGVhdCB0aGlzIHdpdGggdGhlIGZ1bGwgbW9kZWwuCgpgYGB7cn0KIyByZWFkIGluIHRoZSBtb2RlbAptdWx0aXBsaWVyLm1vZGVsIDwtIHJlYWRSRFMocmVjb3VudDIubW9kZWwuZmlsZSkKIyBjaGVjayBhbGwgcGFpcnMgZm9yIHNlcGFyYXRpb24KbXVsdGlwbGllci5yZXN1bHRzIDwtIEFsbFBhaXJzKHBsaWVyLm1vZGVsID0gbXVsdGlwbGllci5tb2RlbCkKIyB0aGVyZSdzIG9ubHkgb25lIG1vZGVsIC0tIHNvIHRoaXMgaXMgYSBiaW5hcnkgb3V0Y29tZSEKbXVsdGlwbGllci5kZiA8LSByZXNoYXBlMjo6bWVsdChtdWx0aXBsaWVyLnJlc3VsdHMpICU+JSAgIyBtZWx0IHRoZSByZXN1bHRzCiAgIyB0aGUgdmFyaWFibGUgbmFtZSBpcyBwYXRod2F5CiAgZHBseXI6Om11dGF0ZShwYXRod2F5ID0gTDEsCiAgICAgICAgICAgICAgICBNdWx0aVBMSUVSID0gYXMuaW50ZWdlcih2YWx1ZSkpICU+JQogIGRwbHlyOjpzZWxlY3QoYygicGF0aHdheSIsICJNdWx0aVBMSUVSIikpICU+JQogIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCJwYXRod2F5IikgJT4lCiAgYXMuZGF0YS5mcmFtZSgpCmBgYAoKSGVhdG1hcCBpdHNlbGYKCmBgYHtyfQptdWx0aXBsaWVyLmhlYXRtYXAgPC0gCiAgQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAoYXMubWF0cml4KG11bHRpcGxpZXIuZGYpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfcm93cyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfY29sdW1ucyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9uYW1lc19zaWRlID0gInRvcCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gImJsdWU0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICByZWN0X2dwID0gZ3JpZDo6Z3Bhcihjb2wgPSAiYmxhY2siKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gInBhdGh3YXkgc2VwYXJhdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvd19oZWF0bWFwX2xlZ2VuZCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvd19yb3dfbmFtZXMgPSBGQUxTRSkKYGBgCgojIyBGaW5hbCBwbG90dGluZwoKYGBge3J9CmhlYXRtYXAubGlzdCA8LSBzaXplLmhlYXRtYXAgKyBjb250ZXh0LmhlYXRtYXAgKyBtdWx0aXBsaWVyLmhlYXRtYXAgCnBkZihmaWxlLnBhdGgocGxvdC5kaXIsICJtdWx0aXBsaWVyX3NlcGFyYXRpb24ucGRmIikpCkNvbXBsZXhIZWF0bWFwOjpkcmF3KGhlYXRtYXAubGlzdCkKZGV2Lm9mZigpCmBgYAoKCg==