Setup

library(ithi.utils)
load_base_libs()
library(ithi.meta)
library(ithi.spatial)
library(ithi.external)
he_results_dir <- snakemake@input$he_results_dir
tumour_purity_file <- snakemake@input$tumour_purity_file
ihc_table_path <- snakemake@input$ihc_table
molecular_subtype_file <- snakemake@input$molsubtypes

image_summary_file <- snakemake@input$image_summary
image_summary_file2 <- snakemake@input$image_summary2
finnhe_pipeline_results_dir <- snakemake@input$finnhe_pipeline_results_dir

db_path <- snakemake@params$db
tils_for_cluster <- snakemake@params$tils_for_cluster

Analysis

These are the results from the H&E image classifier. We’re going to see if we can derive any features of interest to separate N/S/ES-TIL samples off of this.

Tumour/Stromal area

annotation_colours <- ithi.figures::get_annotation_colours()

hotspot_stats_file <- file.path(finnhe_pipeline_results_dir, "overlap_stats_combined.tsv")
detection_summary_file <- file.path(finnhe_pipeline_results_dir, "detection_summary_combined.tsv")
segmented_detection_summary_file <- file.path(finnhe_pipeline_results_dir, "segmented_detection_summary_combined.tsv")

tumour_purity <- fread(tumour_purity_file)
annotation_measurement_files <- list.files(he_results_dir, pattern = "_annotation_measurements.txt", 
    full.names = TRUE, recursive = FALSE)
detection_measurement_files <- list.files(he_results_dir, pattern = "_detection_measurements.txt", 
    full.names = TRUE, recursive = FALSE)

ihc_table <- fread(ihc_table_path)
molsubtypes <- fread(molecular_subtype_file)

hotspot_stats <- fread(hotspot_stats_file)

til_clusters <- ithi.figures:::get_til_clusters(ihc_table, molsubtypes, tils_for_cluster = tils_for_cluster, 
    nclusts = 3)
read_annotation_measurements <- function(annotation_measurement_file) {
    annotations <- data.table::fread(annotation_measurement_file)
    annotations <- annotations %>% plyr::rename(c(`Centroid X µm` = "centroid_x", 
        `Centroid Y µm` = "centroid_y", `Area µm^2` = "area", `Perimeter µm` = "perimeter"))
    annotations$area_proportion <- annotations$area/(annotations$area[annotations$Class == 
        "Stroma"] + annotations$area[annotations$Class == "Tumor"])
    return(annotations)
}

read_detection_measurements <- function(detection_measurement_file) {
    detections <- data.table::fread(detection_measurement_file)
    
}
annotation_table <- lapply(annotation_measurement_files, function(f) {
    raw_id <- parse_sample_ids(f)
    
    annotations <- read_annotation_measurements(f)
    data.frame(voa = raw_id, annotations)
}) %>% rbind.fill

annotation_table$condensed_id <- ithi.meta::map_id(annotation_table$voa, from = "voa", 
    to = "condensed_id", db_path)

Let’s check that the cellularity estimates from this are approximately correlated with those from WGS.

We don’t expect them to be exact, of course, given differences in cell size, i.e. DNA content-to-area ratio, but it should still be approximately correlated.

annotation_purity <- annotation_table %>% plyr::join(tumour_purity, type = "inner")
annotation_purity_tumour <- subset(annotation_purity, Class == "Tumor")
annotation_purity_tumour$patient_id <- as.character(annotation_purity_tumour$patient_id) %>% 
    ithi.meta::factor_id(type = "patient_id", db_path)
correlate_and_scatterplot <- function(df, xvar, yvar, xlab, ylab, annotation_colours, 
    log = "none") {
    df <- data.frame(df, stringsAsFactors = FALSE)
    corres <- cor.test(df[, xvar], df[, yvar], method = "spearman")
    corres_text <- as.character(as.expression(substitute(rho == sprho * "," * 
        ~~italic(P) == p, list(sprho = format(corres$estimate, digits = 3), 
        p = format(corres$p.value, digits = 3)))))
    
    p <- ggplot(df, aes_string(x = xvar, y = yvar)) + geom_point(aes(colour = patient_id)) + 
        theme_bw() + theme_Publication() + theme_nature() + scale_colour_manual(values = annotation_colours$patient_id) + 
        xlab(xlab) + ylab(ylab) + annotate(geom = "text", label = corres_text, 
        x = Inf, y = Inf, hjust = 1, vjust = 1, parse = TRUE)
    
    if (log == "x" | log == "xy") {
        p <- p + scale_x_continuous(trans = "log10", breaks = ithi.utils::log_scale_breaks(), 
            labels = ithi.utils::log_scale_labels())
    }
    
    if (log == "y" | log == "xy") {
        p <- p + scale_y_continuous(trans = "log10", breaks = ithi.utils::log_scale_breaks(), 
            labels = ithi.utils::log_scale_labels())
    }
    
    return(p)
}
correlate_and_scatterplot(annotation_purity_tumour, xvar = "tumour_content", 
    yvar = "area_proportion", xlab = "WGS cellularity", ylab = "% Area (H&E)", 
    annotation_colours)

They’re definitely correlated, but quite far from perfect. To some extent, we expected this, though.

Better method: tumour cells vs. all others

NOTE: This method assumes that non-lymphocytes in epithelial areas are tumour cells.

segmented_detections <- fread(segmented_detection_summary_file)
segmented_detection_summary <- segmented_detections %>% group_by(condensed_id) %>% 
    summarise(cellularity_he = nonlympho[masktype == "tumour"]/sum(nonlympho + 
        lympho))

segmented_detection_summary_purity <- segmented_detection_summary %>% plyr::join(tumour_purity, 
    type = "inner")
segmented_detection_summary_purity$patient_id <- ithi.meta::factor_id(segmented_detection_summary_purity$patient_id, 
    type = "patient_id", db_path)
correlate_and_scatterplot(segmented_detection_summary_purity, xvar = "tumour_content", 
    yvar = "cellularity_he", xlab = "WGS cellularity", ylab = "% Tumour cells (H&E)", 
    annotation_colours)

Compare: previous results (Yinyin’s lab)

image_summary1 <- read_he_image_summary(image_summary_file, db_path)
image_summary2 <- read_he_image_summary(image_summary_file2, db_path)

image_summary <- plyr::rbind.fill(list(image_summary1, image_summary2))

yinyin_cellularity <- image_summary %>% plyr::join(tumour_purity, type = "inner") %>% 
    plyr::rename(c(TumourCellRatio = "area_proportion"))
correlate_and_scatterplot(yinyin_cellularity, xvar = "tumour_content", yvar = "area_proportion", 
    xlab = "WGS cellularity", ylab = "% Area (H&E)", annotation_colours)

Correlation is a bit better. However, critically, we should note that the area estimates from this method were closer (in terms of their absolute values) to the real GWS cellularity estimates.

Question: Finn, for your results, is area computed with respect to the whole slide (i.e. a rectangle) or with respect to only those areas that have tissue? I’m assuming it’s the former and that’s why the highest samples only have ~40% area.

Lymphocyte proportion

Next thing to check is whether or not lymphocyte proportion is comparable to our measures from bulk expression/IHC.

detection_summary <- fread(detection_summary_file)
ihc_table$T_total_density_approx <- with(ihc_table, (T_CD8_count + T_CD4_count + 
    T_CD20_count + T_Plasma_count)/RUN1_T_area_pct)

detection_ihc <- detection_summary %>% plyr::join(ihc_table, type = "inner")
detection_ihc$patient_id <- ithi.meta::factor_id(detection_ihc$patient_id, type = "patient_id", 
    db_path)
correlate_and_scatterplot(detection_ihc, xvar = "T_total_density_approx", yvar = "lympho_prop", 
    xlab = "Total TIL density (approx)", ylab = "Lymphocyte proportion (H&E)", 
    annotation_colours, log = "x")

Cell parameters

Still have to do an exploration of what the cell parameters look like for each class of cell. More of a QC check.

Getis-Ord Gi Hotspots

hotspot_stats_til_clusters <- merge(hotspot_stats, til_clusters)
hotspot_stats_til_clusters_melted <- reshape2::melt(hotspot_stats_til_clusters, 
    id.vars = c("condensed_id", "mask", "cluster"), measure.vars = c("fc", "fi", 
        "fci"), variable.name = "measure", value.name = "stat")
pvals <- compute_pvals_subsets(hotspot_stats_til_clusters_melted, facet_vars = c("mask", 
    "measure"), formula = stat ~ cluster, corfun = kruskal.test)
ggplot(hotspot_stats_til_clusters_melted, aes(x = cluster, y = stat)) + geom_boxplot(outlier.size = -1) + 
    geom_jitter(position = position_jitter(width = 0.2, height = 0), alpha = 0.3) + 
    theme_bw() + facet_grid(measure ~ mask, scales = "free") + theme_Publication() + 
    theme_nature() + geom_text(data = pvals, aes(x = Inf, y = Inf, label = p.adj.text), 
    hjust = 1.1, vjust = 1.5, size = 2.5, parse = TRUE)

These statistics are:

  • fc = proportion of NonLympho hotspots that are also Lympho hotspots
  • fi = proportion of Lympho hotspots that are also NonLympho hotspots
  • fci = proportion of total mask area covered by NonLympho-Lympho hotspots (intersection)

The key here is to look at the tumour column – this corresponds to epithelial area, which is what we’re interested in.

P-values are adjusted for multiple testing w.r.t. all areas, including vascular and whitespace, but since those weren’t part of our hypothesis we can exclude those from the multiple testing in a future iteration.

A key assumption is that non-lymphocytes are cancer cells in epithelial areas and fibroblasts in stromal areas. Finn, do you think we can say this? Or would it be possible to include 3 classes in the RF model, i.e. cancer, lymphocyte, and other?

Key observations are:

  • no differences between TIL clusters for stromal – not unexpected as we don’t really have a strong reason to suspect a fibroblast-immune cell interaction that differs between TIL subtypes (you might be able to make a case for an S-TIL hypothesis, but this doesn’t seem to be supported by the data)
  • S-TIL has lower cancer-lymphocyte clustering than N-TIL or ES-TIL. Surprisingly enough N-TIL has comparable levels to ES-TIL. Doesn’t affect our interpretation TOO much as N-TILs have fewer TIL to start with, and this type of hotspot analysis looks for RELATIVE hotspots. So if you have only 5 TIL across a slide, seeing 2 of them close to each other may result in an area being called a hotspot in that slide.

Followups

One of the reviewers pointed out the idea of classifying N/S/ES-TIL for TCGA/ICGC samples on the basis of expression profiles. While we haven’t been able to do this accurately, it might be possible to do this off of the H&E images, e.g. from the Cancer Digital Slide Archive (which I’ve checked has TCGA-OV). This would allow us to validate our other measures on a larger cohort.

This is the next step for this line of analysis.

LS0tCnRpdGxlOiAicVBhdGggaG90c3BvdCBhbmFseXNpcyIKLS0tCiAgICAgICAgICAgICAgICAgICAgICAgIGBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKIyMjIyMjIyMgU25ha2VtYWtlIGhlYWRlciAjIyMjIyMjIwpsaWJyYXJ5KG1ldGhvZHMpClNuYWtlbWFrZSA8LSBzZXRDbGFzcygKICAgICJTbmFrZW1ha2UiLAogICAgc2xvdHMgPSBjKAogICAgICAgIGlucHV0ID0gImxpc3QiLAogICAgICAgIG91dHB1dCA9ICJsaXN0IiwKICAgICAgICBwYXJhbXMgPSAibGlzdCIsCiAgICAgICAgd2lsZGNhcmRzID0gImxpc3QiLAogICAgICAgIHRocmVhZHMgPSAibnVtZXJpYyIsCiAgICAgICAgbG9nID0gImxpc3QiLAogICAgICAgIHJlc291cmNlcyA9ICJsaXN0IiwKICAgICAgICBjb25maWcgPSAibGlzdCIsCiAgICAgICAgcnVsZSA9ICJjaGFyYWN0ZXIiCiAgICApCikKc25ha2VtYWtlIDwtIFNuYWtlbWFrZSgKICAgIGlucHV0ID0gbGlzdCgnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2l0aGkvZmlubl9yZXN1bHRzL2hlX291dHB1dF9Ob3YyOScsICdub3RlYm9va3MvcXBhdGhfaG90c3BvdHMuUm1kJywgJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL2Zpbm5oZS9ydW4xJywgJy9zaGFobGFiL2FsemhhbmcvZGF0YS9pdGhpL3l1YW5faGVjcl9pbWFnZV9yZXN1bHRzXzIuY3N2JywgJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL21vbHN1YnR5cGVzLnRzdicsICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9paGNfdGFibGUudHN2JywgJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL3R1bW91cl9wdXJpdHkudHN2JywgJy9zaGFobGFiL2FsemhhbmcvZGF0YS9pdGhpL3l1YW5faGVjcl9pbWFnZV9yZXN1bHRzLmNzdicsICJoZV9yZXN1bHRzX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2l0aGkvZmlubl9yZXN1bHRzL2hlX291dHB1dF9Ob3YyOScsICJub3RlYm9vayIgPSAnbm90ZWJvb2tzL3FwYXRoX2hvdHNwb3RzLlJtZCcsICJmaW5uaGVfcGlwZWxpbmVfcmVzdWx0c19kaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL2Zpbm5oZS9ydW4xJywgImltYWdlX3N1bW1hcnkyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvaXRoaS95dWFuX2hlY3JfaW1hZ2VfcmVzdWx0c18yLmNzdicsICJtb2xzdWJ0eXBlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvbW9sc3VidHlwZXMudHN2JywgImloY190YWJsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaWhjX3RhYmxlLnRzdicsICJ0dW1vdXJfcHVyaXR5X2ZpbGUiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL3R1bW91cl9wdXJpdHkudHN2JywgImltYWdlX3N1bW1hcnkiID0gJy9zaGFobGFiL2FsemhhbmcvZGF0YS9pdGhpL3l1YW5faGVjcl9pbWFnZV9yZXN1bHRzLmNzdicpLAogICAgb3V0cHV0ID0gbGlzdCgnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvcmV2aWV3L25vdGVib29rcy9ydW4yL3FwYXRoX2hvdHNwb3RzLm5iLmh0bWwnKSwKICAgIHBhcmFtcyA9IGxpc3QoYygnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSwgJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9tZXRhZGF0YS9kYi9pbW11bmVfcHJvamVjdC5zcWxpdGUzJywgJ3FwYXRoX2hvdHNwb3RzX2FuYWx5c2lzJywgInRpbHNfZm9yX2NsdXN0ZXIiID0gYygnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSwgImRiIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvbWV0YWRhdGEvZGIvaW1tdW5lX3Byb2plY3Quc3FsaXRlMycsICJuYW1lIiA9ICdxcGF0aF9ob3RzcG90c19hbmFseXNpcycpLAogICAgd2lsZGNhcmRzID0gbGlzdCgpLAogICAgdGhyZWFkcyA9IDEsCiAgICBsb2cgPSBsaXN0KCcvc2hhaGxhYi9hbHpoYW5nL2NsdXN0dG1wL3BhcGVycmV2aWV3Mi9ub3RlYm9va3MvcXBhdGhfaG90c3BvdHNfYW5hbHlzaXMubG9nJyksCiAgICByZXNvdXJjZXMgPSBsaXN0KCksCiAgICBjb25maWcgPSBsaXN0KCJuYW5vc3RyaW5nX2RhdGEiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9yZXN1bHRzL25hbm9zdHJpbmdfcmVzdWx0cy9pdGhfZnVsbC9xYy9saW1tYV9xdWFudGlsZS9ub3JtYWxpemVkX2V4cHJlc3Npb25fdm9hX2xhYmVsc19maWx0ZXJlZC50c3YnLCAidGlsX2NsdXN0ZXJzX291dHB1dCIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvaW50ZXJtZWRpYXRlcy9ydW4yL3RpbF9jbHVzdGVyc19vdXRwdXQudHh0JywgImlncGFydGl0aW9uX291dGRpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvaWdwYXJ0aXRpb24vcnVuMjInLCAiaWNnY19zdWJ0eXBlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL0lDR0MvaWNnY19wcmltYXJ5X3R1bW91cl9zdWJ0eXBlcy50c3YnLCAiYXJyYXlfZXhwcmVzc2lvbl9maWxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvZGF0YS9leHByZXNzaW9uL2FycmF5L2dlbmVfZXhwcnNfcm1hX2JhdGNoX2NvcnJlY3RlZC50eHQnLCAiY2xvbmVfYnJhbmNoX2xlbmd0aHMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2Nsb25lcy9icmFuY2hfZGF0YS50c3YnLCAiYnJlYWtwb2ludF90YWJsZSIgPSAnL3NoYWhsYWIvYW1jcGhlcnNvbi9wcm9qZWN0cy9pdGgzL2l0aDMvbm90ZWJvb2tzL2Jlc3Bva2UvaXRoX2JyZWFrcG9pbnRzLnRzdicsICJhbGxfdGlsdHlwZXMiID0gYygnVF9DRDhfZGVuc2l0eScsICdUX0NENF9kZW5zaXR5JywgJ1RfQ0QyMF9kZW5zaXR5JywgJ1RfUGxhc21hX2RlbnNpdHknLCAnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSwgIml0aF9zdGF0cyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaXRoX3N0YXRpc3RpY3MudHN2JywgImJjcl9kaXZlcnNpdHkiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL21peGNyL21peGNyX3J1bnMvaXRoXzFfMl8zL21peGNyNS9wb3N0cHJvY2Vzcy9JR0gvcG9zdGZpbHRlcl9kaXZlcnNpdHlfc3RhdHMvZGl2ZXJzaXR5LnN0cmljdC5yZXNhbXBsZWQudHh0JywgImloY19mZWF0dXJlc19vdXRwdXQiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL2ludGVybWVkaWF0ZXMvcnVuMi9paGNfZmVhdHVyZXNfb3V0cHV0LnR4dCcsICJzb21hdGljX2NvZGluZ19yZXN1bHRfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9zb21hdGljX2NvZGluZ192YXJpYW50cycsICJiZW5jaG1hcmtkaXIiID0gJy9zaGFobGFiL2FsemhhbmcvYmVuY2htYXJrcy9wYXBlcnJldmlldzInLCAidG90YWxfdGlsdHlwZXMiID0gYygnVF9DRDhfZGVuc2l0eScsICdUX0NENF9kZW5zaXR5JywgJ1RfQ0QyMF9kZW5zaXR5JywgJ1RfUGxhc21hX2RlbnNpdHknKSwgImZpbm5oZV9waXBlbGluZV9yZXN1bHRzX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvZmlubmhlL3J1bjEnLCAiY2xvbGFfcmVzdWx0X2ZpbGUiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL2Nsb2xhL3J1bjQvY2xvbGFfY29uZGVuc2VkX3Jlc3VsdHMvYmV0YS9jbG9sYV9yZXN1bHRzLnRzdicsICJtb2xzdWJ0eXBlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvbW9sc3VidHlwZXMudHN2JywgIm1tY3RtX2ZpbmFsX3BhdGllbnRfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcmVzdWx0cy9tbWN0bV9yZXN1bHRzL2l0aF9ieS1wYXRpZW50X3dpdGgtb3YnLCAidGlsc19mb3JfY2x1c3RlciIgPSBjKCdFX0NEOF9kZW5zaXR5JywgJ0VfQ0Q0X2RlbnNpdHknLCAnRV9DRDIwX2RlbnNpdHknLCAnRV9QbGFzbWFfZGVuc2l0eScsICdTX0NEOF9kZW5zaXR5JywgJ1NfQ0Q0X2RlbnNpdHknLCAnU19DRDIwX2RlbnNpdHknLCAnU19QbGFzbWFfZGVuc2l0eScpLCAidmFyaWFiaWxpdHlfdHlwZSIgPSAnc3RhYmlsaXplJywgInByZXZhbGVuY2VfdGhyZXNob2xkIiA9IDAuMDEsICJoZV9yZXN1bHRzX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2l0aGkvZmlubl9yZXN1bHRzL2hlX291dHB1dF9Ob3YyOScsICJkaXN0YW5jZV9tZXRob2QiID0gJ2hvcm4nLCAidGlsc19mb3JfdmFyaWFiaWxpdHkiID0gYygnVF9DRDhfZGVuc2l0eScsICdUX0NENF9kZW5zaXR5JywgJ1RfQ0QyMF9kZW5zaXR5JywgJ1RfUGxhc21hX2RlbnNpdHknKSwgInhjcl90YWJsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIveGNyX3RhYmxlLnRzdicsICJjbG9uZV90cmVlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvY2xvbmVzL3RyZWVfZGF0YS50c3YnLCAibmVvZWRpdGluZ19vdXRkaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL25lb2VkaXRpbmcvcnVuNicsICJpbWFnZV9zdW1tYXJ5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvaXRoaS95dWFuX2hlY3JfaW1hZ2VfcmVzdWx0cy5jc3YnLCAicmVtaXh0X2NlbGx1bGFyaXR5X3Bsb2lkeSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvcmVtaXh0X2NlbGx1bGFyaXR5X3Bsb2lkeS50c3YnLCAibG9nZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2NsdXN0dG1wL3BhcGVycmV2aWV3MicsICJpdGhfaWNnY19iYyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaXRoX2ljZ2NfbWVyZ2VkX2JjLnRzdicsICJ0Y3JfZGl2ZXJzaXR5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL3BpcGVsaW5lX291dHB1dHMvaXRoX2ltbXVuZS9taXhjci9taXhjcl9ydW5zL2l0aF8xXzJfMy9taXhjcjUvcG9zdHByb2Nlc3MvVFJCL3Bvc3RmaWx0ZXJfZGl2ZXJzaXR5X3N0YXRzL2RpdmVyc2l0eS5zdHJpY3QucmVzYW1wbGVkLnR4dCcsICJpdGhfc3RhdF90eXBlcyIgPSBjKCdlbnRyb3B5JywgJ3Bvc3Rwcm9jZXNzZWRfZGl2ZXJnZW5jZScsICdjb21iaW5lZF9pdGhfbm9ybWFsaXplZCcsICdwcm9wb3J0aW9uX3N1YmNsb25hbCcpLCAicmVmc2VxX2dlbmVfZmlsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2dlbm9tZS9oZzE5L3JlZnNlcV9nZW5lcy5iZWQnLCAiZGIiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9tZXRhZGF0YS9kYi9pbW11bmVfcHJvamVjdC5zcWxpdGUzJywgInNudl9jbHVzdGVyX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvY2xvbmVzL3Nudl9jbHVzdGVyJywgIm5vdGVib29rX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvcmV2aWV3L25vdGVib29rcy9ydW4yJywgImltYWdlX3N1bW1hcnkyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvaXRoaS95dWFuX2hlY3JfaW1hZ2VfcmVzdWx0c18yLmNzdicsICJrbm93bl9zdWJ0eXBlc19hcnJheSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9hcnJheS9zdWJ0eXBlcy9rbm93bl9zdWJ0eXBlcy50c3YnLCAibmFub3N0cmluZ19hbm5vdGF0aW9ucyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9uYW5vc3RyaW5nL3BhbmNhbmNlcl9hbm5vdGF0aW9ucy50c3YnLCAiaWNnY19zcGVjaW1lbiIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL0lDR0Mvc3BlY2ltZW4udHN2JywgImNsb25lX3ByZXZhbGVuY2VzIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9jbG9uZXMvY2xvbmVfZGF0YS50c3YnLCAiY29weW51bWJlcl90YWJsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2l0aGkvbWFzdGVyX2NvcHludW1iZXJfZmlsZS50c3YnLCAicm9vbmV5X211dHNpZ2N2X2ZpbGUiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9leHRlcm5hbC9vdGhlcl9wYXBlcnMvbW1jNi54bHN4JywgImloY190YWJsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaWhjX3RhYmxlLnRzdicsICJ0YWJsZV9kaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3Jldmlldy90YWJsZXMvcnVuMicsICJlcGl0b3Blc191bmlxdWVfZmlsdGVyZWQiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2VwaXRvcGVzX3VuaXF1ZV9maWx0ZXJlZC50c3YnLCAidHVtb3VyX3B1cml0eSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvdHVtb3VyX3B1cml0eS50c3YnLCAicGF0aWVudHNfZm9yX2Nsb25hbCIgPSBjKDEsIDIsIDMsIDQsIDcsIDksIDEwLCAxMSwgMTIsIDEzLCAxNCwgMTUsIDE2LCAxNyksICJ0aWxjbHVzdGVyX3N1cGVydmlzZWRfaXB5bmIiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXZpZXcvaXB5L3RpbGNsdXN0ZXJfc3VwZXJ2aXNlZG11bHRpY2xhc3MuaXB5bmInLCAic252X3RhYmxlIiA9ICcvc2hhaGxhYi9hbWNwaGVyc29uL3Byb2plY3RzL2l0aDMvaXRoMy9ub3RlYm9va3MvYmVzcG9rZS9pdGhfc252cy50c3YnKSwKICAgIHJ1bGUgPSAncXBhdGhfaG90c3BvdHNfYW5hbHlzaXMnCikKIyMjIyMjIyMgT3JpZ2luYWwgc2NyaXB0ICMjIyMjIyMjIwoKICAgICAgICAgICAgICAgICAgICAgICAgYGBgCgoKIyMgU2V0dXAKCmBgYHtyIGdsb2JhbF9jaHVua19vcHRpb25zLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHRpZHk9VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9VFJVRSkgI2NhY2hlPVRSVUUKYGBgCgpgYGB7cn0KbGlicmFyeShpdGhpLnV0aWxzKQpsb2FkX2Jhc2VfbGlicygpCmxpYnJhcnkoaXRoaS5tZXRhKQpsaWJyYXJ5KGl0aGkuc3BhdGlhbCkKbGlicmFyeShpdGhpLmV4dGVybmFsKQpgYGAKCmBgYHtyLCBldmFsID0gVFJVRX0KaGVfcmVzdWx0c19kaXIgPC0gc25ha2VtYWtlQGlucHV0JGhlX3Jlc3VsdHNfZGlyCnR1bW91cl9wdXJpdHlfZmlsZSA8LSBzbmFrZW1ha2VAaW5wdXQkdHVtb3VyX3B1cml0eV9maWxlCmloY190YWJsZV9wYXRoIDwtIHNuYWtlbWFrZUBpbnB1dCRpaGNfdGFibGUKbW9sZWN1bGFyX3N1YnR5cGVfZmlsZSA8LSBzbmFrZW1ha2VAaW5wdXQkbW9sc3VidHlwZXMKCmltYWdlX3N1bW1hcnlfZmlsZSA8LSBzbmFrZW1ha2VAaW5wdXQkaW1hZ2Vfc3VtbWFyeQppbWFnZV9zdW1tYXJ5X2ZpbGUyIDwtIHNuYWtlbWFrZUBpbnB1dCRpbWFnZV9zdW1tYXJ5MgpmaW5uaGVfcGlwZWxpbmVfcmVzdWx0c19kaXIgPC0gc25ha2VtYWtlQGlucHV0JGZpbm5oZV9waXBlbGluZV9yZXN1bHRzX2RpcgoKZGJfcGF0aCA8LSBzbmFrZW1ha2VAcGFyYW1zJGRiCnRpbHNfZm9yX2NsdXN0ZXIgPC0gc25ha2VtYWtlQHBhcmFtcyR0aWxzX2Zvcl9jbHVzdGVyCmBgYAoKIyMgQW5hbHlzaXMKClRoZXNlIGFyZSB0aGUgcmVzdWx0cyBmcm9tIHRoZSBIJkUgaW1hZ2UgY2xhc3NpZmllci4gV2UncmUgZ29pbmcgdG8gc2VlIGlmIHdlIGNhbiBkZXJpdmUgYW55IGZlYXR1cmVzIG9mIGludGVyZXN0IHRvIHNlcGFyYXRlIE4vUy9FUy1USUwgc2FtcGxlcyBvZmYgb2YgdGhpcy4gCgojIyMgVHVtb3VyL1N0cm9tYWwgYXJlYQoKYGBge3J9CmFubm90YXRpb25fY29sb3VycyA8LSBpdGhpLmZpZ3VyZXM6OmdldF9hbm5vdGF0aW9uX2NvbG91cnMoKQoKaG90c3BvdF9zdGF0c19maWxlIDwtIGZpbGUucGF0aChmaW5uaGVfcGlwZWxpbmVfcmVzdWx0c19kaXIsICJvdmVybGFwX3N0YXRzX2NvbWJpbmVkLnRzdiIpCmRldGVjdGlvbl9zdW1tYXJ5X2ZpbGUgPC0gZmlsZS5wYXRoKGZpbm5oZV9waXBlbGluZV9yZXN1bHRzX2RpciwgImRldGVjdGlvbl9zdW1tYXJ5X2NvbWJpbmVkLnRzdiIpCnNlZ21lbnRlZF9kZXRlY3Rpb25fc3VtbWFyeV9maWxlIDwtIGZpbGUucGF0aChmaW5uaGVfcGlwZWxpbmVfcmVzdWx0c19kaXIsICJzZWdtZW50ZWRfZGV0ZWN0aW9uX3N1bW1hcnlfY29tYmluZWQudHN2IikKCnR1bW91cl9wdXJpdHkgPC0gZnJlYWQodHVtb3VyX3B1cml0eV9maWxlKQphbm5vdGF0aW9uX21lYXN1cmVtZW50X2ZpbGVzIDwtIGxpc3QuZmlsZXMoaGVfcmVzdWx0c19kaXIsIHBhdHRlcm4gPSAiX2Fubm90YXRpb25fbWVhc3VyZW1lbnRzLnR4dCIsIGZ1bGwubmFtZXMgPSBUUlVFLCByZWN1cnNpdmUgPSBGQUxTRSkKZGV0ZWN0aW9uX21lYXN1cmVtZW50X2ZpbGVzIDwtIGxpc3QuZmlsZXMoaGVfcmVzdWx0c19kaXIsIHBhdHRlcm4gPSAiX2RldGVjdGlvbl9tZWFzdXJlbWVudHMudHh0IiwgZnVsbC5uYW1lcyA9IFRSVUUsIHJlY3Vyc2l2ZSA9IEZBTFNFKQoKaWhjX3RhYmxlIDwtIGZyZWFkKGloY190YWJsZV9wYXRoKQptb2xzdWJ0eXBlcyA8LSBmcmVhZChtb2xlY3VsYXJfc3VidHlwZV9maWxlKQoKaG90c3BvdF9zdGF0cyA8LSBmcmVhZChob3RzcG90X3N0YXRzX2ZpbGUpCgp0aWxfY2x1c3RlcnMgPC0gaXRoaS5maWd1cmVzOjo6Z2V0X3RpbF9jbHVzdGVycyhpaGNfdGFibGUsIG1vbHN1YnR5cGVzLCB0aWxzX2Zvcl9jbHVzdGVyID0gdGlsc19mb3JfY2x1c3RlciwgbmNsdXN0cyA9IDMpCmBgYAoKYGBge3J9CnJlYWRfYW5ub3RhdGlvbl9tZWFzdXJlbWVudHMgPC0gZnVuY3Rpb24oYW5ub3RhdGlvbl9tZWFzdXJlbWVudF9maWxlKSB7CiAgYW5ub3RhdGlvbnMgPC0gZGF0YS50YWJsZTo6ZnJlYWQoYW5ub3RhdGlvbl9tZWFzdXJlbWVudF9maWxlKQogIGFubm90YXRpb25zIDwtIGFubm90YXRpb25zICU+JSBwbHlyOjpyZW5hbWUoYygnQ2VudHJvaWQgWCDCtW0nPSdjZW50cm9pZF94JywgJ0NlbnRyb2lkIFkgwrVtJz0nY2VudHJvaWRfeScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdBcmVhIMK1bV4yJz0nYXJlYScsICdQZXJpbWV0ZXIgwrVtJz0ncGVyaW1ldGVyJykpCiAgYW5ub3RhdGlvbnMkYXJlYV9wcm9wb3J0aW9uIDwtIGFubm90YXRpb25zJGFyZWEvKGFubm90YXRpb25zJGFyZWFbYW5ub3RhdGlvbnMkQ2xhc3MgPT0gIlN0cm9tYSJdICsgYW5ub3RhdGlvbnMkYXJlYVthbm5vdGF0aW9ucyRDbGFzcyA9PSAiVHVtb3IiXSkKICByZXR1cm4oYW5ub3RhdGlvbnMpCn0KCnJlYWRfZGV0ZWN0aW9uX21lYXN1cmVtZW50cyA8LSBmdW5jdGlvbihkZXRlY3Rpb25fbWVhc3VyZW1lbnRfZmlsZSkgewogIGRldGVjdGlvbnMgPC0gZGF0YS50YWJsZTo6ZnJlYWQoZGV0ZWN0aW9uX21lYXN1cmVtZW50X2ZpbGUpCiAgCn0KYGBgCgpgYGB7cn0KYW5ub3RhdGlvbl90YWJsZSA8LSBsYXBwbHkoYW5ub3RhdGlvbl9tZWFzdXJlbWVudF9maWxlcywgZnVuY3Rpb24oZikgewogIHJhd19pZCA8LSBwYXJzZV9zYW1wbGVfaWRzKGYpCiAgCiAgYW5ub3RhdGlvbnMgPC0gcmVhZF9hbm5vdGF0aW9uX21lYXN1cmVtZW50cyhmKQogIGRhdGEuZnJhbWUodm9hPXJhd19pZCwgYW5ub3RhdGlvbnMpCn0pICU+JSByYmluZC5maWxsCgphbm5vdGF0aW9uX3RhYmxlJGNvbmRlbnNlZF9pZCA8LSBpdGhpLm1ldGE6Om1hcF9pZChhbm5vdGF0aW9uX3RhYmxlJHZvYSwgZnJvbSA9ICJ2b2EiLCB0byA9ICJjb25kZW5zZWRfaWQiLCBkYl9wYXRoKQpgYGAKCkxldCdzIGNoZWNrIHRoYXQgdGhlIGNlbGx1bGFyaXR5IGVzdGltYXRlcyBmcm9tIHRoaXMgYXJlIGFwcHJveGltYXRlbHkgY29ycmVsYXRlZCB3aXRoIHRob3NlIGZyb20gV0dTLiAKCldlIGRvbid0IGV4cGVjdCB0aGVtIHRvIGJlIGV4YWN0LCBvZiBjb3Vyc2UsIGdpdmVuIGRpZmZlcmVuY2VzIGluIGNlbGwgc2l6ZSwgaS5lLiBETkEgY29udGVudC10by1hcmVhIHJhdGlvLCBidXQgaXQgc2hvdWxkIHN0aWxsIGJlIGFwcHJveGltYXRlbHkgY29ycmVsYXRlZC4gCgoKCmBgYHtyfQphbm5vdGF0aW9uX3B1cml0eSA8LSBhbm5vdGF0aW9uX3RhYmxlICU+JSBwbHlyOjpqb2luKHR1bW91cl9wdXJpdHksIHR5cGUgPSAiaW5uZXIiKQpgYGAKCmBgYHtyfQphbm5vdGF0aW9uX3B1cml0eV90dW1vdXIgPC0gc3Vic2V0KGFubm90YXRpb25fcHVyaXR5LCBDbGFzcyA9PSAiVHVtb3IiKQphbm5vdGF0aW9uX3B1cml0eV90dW1vdXIkcGF0aWVudF9pZCA8LSBhcy5jaGFyYWN0ZXIoYW5ub3RhdGlvbl9wdXJpdHlfdHVtb3VyJHBhdGllbnRfaWQpICU+JSAKICBpdGhpLm1ldGE6OmZhY3Rvcl9pZCh0eXBlID0gInBhdGllbnRfaWQiLCBkYl9wYXRoKQpgYGAKCmBgYHtyfQpjb3JyZWxhdGVfYW5kX3NjYXR0ZXJwbG90IDwtIGZ1bmN0aW9uKGRmLCB4dmFyLCB5dmFyLCB4bGFiLCB5bGFiLCBhbm5vdGF0aW9uX2NvbG91cnMsIGxvZyA9ICJub25lIikgewogIGRmIDwtIGRhdGEuZnJhbWUoZGYsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKICBjb3JyZXMgPC0gY29yLnRlc3QoZGZbLHh2YXJdLCBkZlsseXZhcl0sIG1ldGhvZCA9ICJzcGVhcm1hbiIpCiAgY29ycmVzX3RleHQgPC1hcy5jaGFyYWN0ZXIoYXMuZXhwcmVzc2lvbihzdWJzdGl0dXRlKHJobz09c3ByaG8qIiwiKn5+aXRhbGljKFApPT1wLCBsaXN0KHNwcmhvPWZvcm1hdChjb3JyZXMkZXN0aW1hdGUsIGRpZ2l0cyA9IDMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHA9Zm9ybWF0KGNvcnJlcyRwLnZhbHVlLCBkaWdpdHM9MykpKSkpCiAgCiAgcCA8LSBnZ3Bsb3QoZGYsIGFlc19zdHJpbmcoeD14dmFyLCB5ID0geXZhcikpICsgZ2VvbV9wb2ludChhZXMoY29sb3VyPXBhdGllbnRfaWQpKSArIAogICAgdGhlbWVfYncoKSArIHRoZW1lX1B1YmxpY2F0aW9uKCkgKyB0aGVtZV9uYXR1cmUoKSArIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYW5ub3RhdGlvbl9jb2xvdXJzJHBhdGllbnRfaWQpICsgCiAgICB4bGFiKHhsYWIpICsgeWxhYih5bGFiKSArIAogICAgYW5ub3RhdGUoZ2VvbSA9ICJ0ZXh0IiwgbGFiZWwgPSBjb3JyZXNfdGV4dCwgeCA9IEluZiwgeSA9IEluZiwgaGp1c3QgPSAxLCB2anVzdCA9IDEsIHBhcnNlID0gVFJVRSkKICAKICBpZiAobG9nID09ICJ4IiB8IGxvZyA9PSAieHkiKSB7CiAgICBwIDwtIHAgKyBzY2FsZV94X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMTAiLCBicmVha3MgPSBpdGhpLnV0aWxzOjpsb2dfc2NhbGVfYnJlYWtzKCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGl0aGkudXRpbHM6OmxvZ19zY2FsZV9sYWJlbHMoKSkKICB9CiAgCiAgaWYgKGxvZyA9PSAieSIgfCBsb2cgPT0gInh5IikgewogICAgcCA8LSBwICsgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIiwgYnJlYWtzID0gaXRoaS51dGlsczo6bG9nX3NjYWxlX2JyZWFrcygpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBpdGhpLnV0aWxzOjpsb2dfc2NhbGVfbGFiZWxzKCkpCiAgfQogIAogIHJldHVybihwKQp9CmBgYAoKYGBge3J9CmNvcnJlbGF0ZV9hbmRfc2NhdHRlcnBsb3QoYW5ub3RhdGlvbl9wdXJpdHlfdHVtb3VyLCB4dmFyID0gInR1bW91cl9jb250ZW50IiwgeXZhciA9ICJhcmVhX3Byb3BvcnRpb24iLCB4bGFiID0gIldHUyBjZWxsdWxhcml0eSIsIHlsYWIgPSAiJSBBcmVhIChIJkUpIiwgYW5ub3RhdGlvbl9jb2xvdXJzKQpgYGAKClRoZXkncmUgZGVmaW5pdGVseSBjb3JyZWxhdGVkLCBidXQgcXVpdGUgZmFyIGZyb20gcGVyZmVjdC4gVG8gc29tZSBleHRlbnQsIHdlIGV4cGVjdGVkIHRoaXMsIHRob3VnaC4gCgoKIyMjIyBCZXR0ZXIgbWV0aG9kOiB0dW1vdXIgY2VsbHMgdnMuIGFsbCBvdGhlcnMKCioqTk9URSoqOiBUaGlzIG1ldGhvZCBhc3N1bWVzIHRoYXQgbm9uLWx5bXBob2N5dGVzIGluIGVwaXRoZWxpYWwgYXJlYXMgYXJlIHR1bW91ciBjZWxscy4gCgpgYGB7cn0Kc2VnbWVudGVkX2RldGVjdGlvbnMgPC0gZnJlYWQoc2VnbWVudGVkX2RldGVjdGlvbl9zdW1tYXJ5X2ZpbGUpCnNlZ21lbnRlZF9kZXRlY3Rpb25fc3VtbWFyeSA8LSBzZWdtZW50ZWRfZGV0ZWN0aW9ucyAlPiUgZ3JvdXBfYnkoY29uZGVuc2VkX2lkKSAlPiUgc3VtbWFyaXNlKGNlbGx1bGFyaXR5X2hlID0gbm9ubHltcGhvW21hc2t0eXBlID09ICJ0dW1vdXIiXS9zdW0obm9ubHltcGhvICsgbHltcGhvKSkKCnNlZ21lbnRlZF9kZXRlY3Rpb25fc3VtbWFyeV9wdXJpdHkgPC0gc2VnbWVudGVkX2RldGVjdGlvbl9zdW1tYXJ5ICU+JSBwbHlyOjpqb2luKHR1bW91cl9wdXJpdHksIHR5cGUgPSAiaW5uZXIiKQpzZWdtZW50ZWRfZGV0ZWN0aW9uX3N1bW1hcnlfcHVyaXR5JHBhdGllbnRfaWQgPC0gaXRoaS5tZXRhOjpmYWN0b3JfaWQoc2VnbWVudGVkX2RldGVjdGlvbl9zdW1tYXJ5X3B1cml0eSRwYXRpZW50X2lkLCB0eXBlID0gInBhdGllbnRfaWQiLCBkYl9wYXRoKQpgYGAKCmBgYHtyfQpjb3JyZWxhdGVfYW5kX3NjYXR0ZXJwbG90KHNlZ21lbnRlZF9kZXRlY3Rpb25fc3VtbWFyeV9wdXJpdHksIHh2YXIgPSAidHVtb3VyX2NvbnRlbnQiLCB5dmFyID0gImNlbGx1bGFyaXR5X2hlIiwgeGxhYiA9ICJXR1MgY2VsbHVsYXJpdHkiLCB5bGFiID0gIiUgVHVtb3VyIGNlbGxzIChIJkUpIiwgYW5ub3RhdGlvbl9jb2xvdXJzKQpgYGAKCgojIyMjIENvbXBhcmU6IHByZXZpb3VzIHJlc3VsdHMgKFlpbnlpbidzIGxhYikKCmBgYHtyfQppbWFnZV9zdW1tYXJ5MSA8LSByZWFkX2hlX2ltYWdlX3N1bW1hcnkoaW1hZ2Vfc3VtbWFyeV9maWxlLCBkYl9wYXRoKSAKaW1hZ2Vfc3VtbWFyeTIgPC0gcmVhZF9oZV9pbWFnZV9zdW1tYXJ5KGltYWdlX3N1bW1hcnlfZmlsZTIsIGRiX3BhdGgpCgppbWFnZV9zdW1tYXJ5IDwtIHBseXI6OnJiaW5kLmZpbGwobGlzdChpbWFnZV9zdW1tYXJ5MSwgaW1hZ2Vfc3VtbWFyeTIpKQoKeWlueWluX2NlbGx1bGFyaXR5IDwtIGltYWdlX3N1bW1hcnkgJT4lIHBseXI6OmpvaW4odHVtb3VyX3B1cml0eSwgdHlwZSA9ICdpbm5lcicpICU+JSBwbHlyOjpyZW5hbWUoYygnVHVtb3VyQ2VsbFJhdGlvJz0nYXJlYV9wcm9wb3J0aW9uJykpCmBgYAoKYGBge3J9CmNvcnJlbGF0ZV9hbmRfc2NhdHRlcnBsb3QoeWlueWluX2NlbGx1bGFyaXR5LCB4dmFyID0gInR1bW91cl9jb250ZW50IiwgeXZhciA9ICJhcmVhX3Byb3BvcnRpb24iLCB4bGFiID0gIldHUyBjZWxsdWxhcml0eSIsIHlsYWIgPSAiJSBBcmVhIChIJkUpIiwgYW5ub3RhdGlvbl9jb2xvdXJzKQpgYGAKCkNvcnJlbGF0aW9uIGlzIGEgYml0IGJldHRlci4gSG93ZXZlciwgY3JpdGljYWxseSwgd2Ugc2hvdWxkIG5vdGUgdGhhdCB0aGUgYXJlYSBlc3RpbWF0ZXMgZnJvbSB0aGlzIG1ldGhvZCB3ZXJlIGNsb3NlciAoaW4gdGVybXMgb2YgdGhlaXIgYWJzb2x1dGUgdmFsdWVzKSB0byB0aGUgcmVhbCBHV1MgY2VsbHVsYXJpdHkgZXN0aW1hdGVzLiAKCioqUXVlc3Rpb24qKjogRmlubiwgZm9yIHlvdXIgcmVzdWx0cywgaXMgYXJlYSBjb21wdXRlZCB3aXRoIHJlc3BlY3QgdG8gdGhlIHdob2xlIHNsaWRlIChpLmUuIGEgcmVjdGFuZ2xlKSBvciB3aXRoIHJlc3BlY3QgdG8gb25seSB0aG9zZSBhcmVhcyB0aGF0IGhhdmUgdGlzc3VlPyBJJ20gYXNzdW1pbmcgaXQncyB0aGUgZm9ybWVyIGFuZCB0aGF0J3Mgd2h5IHRoZSBoaWdoZXN0IHNhbXBsZXMgb25seSBoYXZlIH40MFwlIGFyZWEuIAoKIyMjIEx5bXBob2N5dGUgcHJvcG9ydGlvbgoKTmV4dCB0aGluZyB0byBjaGVjayBpcyB3aGV0aGVyIG9yIG5vdCBseW1waG9jeXRlIHByb3BvcnRpb24gaXMgY29tcGFyYWJsZSB0byBvdXIgbWVhc3VyZXMgZnJvbSBidWxrIGV4cHJlc3Npb24vSUhDLiAKCmBgYHtyfQpkZXRlY3Rpb25fc3VtbWFyeSA8LSBmcmVhZChkZXRlY3Rpb25fc3VtbWFyeV9maWxlKQpgYGAKCmBgYHtyfQppaGNfdGFibGUkVF90b3RhbF9kZW5zaXR5X2FwcHJveCA8LSB3aXRoKGloY190YWJsZSwgKFRfQ0Q4X2NvdW50ICsgVF9DRDRfY291bnQgKyBUX0NEMjBfY291bnQgKyBUX1BsYXNtYV9jb3VudCkvUlVOMV9UX2FyZWFfcGN0KQoKZGV0ZWN0aW9uX2loYyA8LSBkZXRlY3Rpb25fc3VtbWFyeSAlPiUgcGx5cjo6am9pbihpaGNfdGFibGUsIHR5cGUgPSAnaW5uZXInKQpkZXRlY3Rpb25faWhjJHBhdGllbnRfaWQgPC0gaXRoaS5tZXRhOjpmYWN0b3JfaWQoZGV0ZWN0aW9uX2loYyRwYXRpZW50X2lkLCB0eXBlID0gInBhdGllbnRfaWQiLCBkYl9wYXRoKQpgYGAKCmBgYHtyfQpjb3JyZWxhdGVfYW5kX3NjYXR0ZXJwbG90KGRldGVjdGlvbl9paGMsIHh2YXIgPSAiVF90b3RhbF9kZW5zaXR5X2FwcHJveCIsIHl2YXIgPSAibHltcGhvX3Byb3AiLCB4bGFiID0gIlRvdGFsIFRJTCBkZW5zaXR5IChhcHByb3gpIiwgeWxhYiA9ICJMeW1waG9jeXRlIHByb3BvcnRpb24gKEgmRSkiLCBhbm5vdGF0aW9uX2NvbG91cnMsIGxvZyA9ICJ4IikKYGBgCgojIyMgQ2VsbCBwYXJhbWV0ZXJzCgpTdGlsbCBoYXZlIHRvIGRvIGFuIGV4cGxvcmF0aW9uIG9mIHdoYXQgdGhlIGNlbGwgcGFyYW1ldGVycyBsb29rIGxpa2UgZm9yIGVhY2ggY2xhc3Mgb2YgY2VsbC4gTW9yZSBvZiBhIFFDIGNoZWNrLiAKCiMjIyBHZXRpcy1PcmQgR2kgSG90c3BvdHMKCmBgYHtyfQpob3RzcG90X3N0YXRzX3RpbF9jbHVzdGVycyA8LSBtZXJnZShob3RzcG90X3N0YXRzLCB0aWxfY2x1c3RlcnMpCmhvdHNwb3Rfc3RhdHNfdGlsX2NsdXN0ZXJzX21lbHRlZCA8LSByZXNoYXBlMjo6bWVsdChob3RzcG90X3N0YXRzX3RpbF9jbHVzdGVycywgaWQudmFycyA9IGMoImNvbmRlbnNlZF9pZCIsICJtYXNrIiwgImNsdXN0ZXIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lYXN1cmUudmFycyA9IGMoImZjIiwgImZpIiwgImZjaSIpLCB2YXJpYWJsZS5uYW1lID0gIm1lYXN1cmUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUubmFtZSA9ICJzdGF0IikKYGBgCgpgYGB7cn0KcHZhbHMgPC0gY29tcHV0ZV9wdmFsc19zdWJzZXRzKGhvdHNwb3Rfc3RhdHNfdGlsX2NsdXN0ZXJzX21lbHRlZCwgZmFjZXRfdmFycyA9IGMoIm1hc2siLCAibWVhc3VyZSIpLCBmb3JtdWxhID0gc3RhdCB+IGNsdXN0ZXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb3JmdW4gPSBrcnVza2FsLnRlc3QpCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQgPSA1LCBmaWcud2lkdGggPSA3fQpnZ3Bsb3QoaG90c3BvdF9zdGF0c190aWxfY2x1c3RlcnNfbWVsdGVkLCBhZXMoeD1jbHVzdGVyLCB5ID0gc3RhdCkpICsgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2l6ZSA9IC0xKSArIGdlb21faml0dGVyKHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4yLCBoZWlnaHQgPSAwKSwgYWxwaGEgPSAwLjMpICsgdGhlbWVfYncoKSArIGZhY2V0X2dyaWQobWVhc3VyZSB+IG1hc2ssIHNjYWxlcyA9ICJmcmVlIikgKyAKICB0aGVtZV9QdWJsaWNhdGlvbigpICsgdGhlbWVfbmF0dXJlKCkgKyBnZW9tX3RleHQoZGF0YT1wdmFscywgYWVzKHg9SW5mLCB5PUluZiwgbGFiZWw9cC5hZGoudGV4dCksIGhqdXN0PTEuMSwgdmp1c3Q9MS41LHNpemU9Mi41LHBhcnNlPVRSVUUpCmBgYAoKVGhlc2Ugc3RhdGlzdGljcyBhcmU6CgoqIGZjID0gcHJvcG9ydGlvbiBvZiBOb25MeW1waG8gaG90c3BvdHMgdGhhdCBhcmUgYWxzbyBMeW1waG8gaG90c3BvdHMKKiBmaSA9IHByb3BvcnRpb24gb2YgTHltcGhvIGhvdHNwb3RzIHRoYXQgYXJlIGFsc28gTm9uTHltcGhvIGhvdHNwb3RzCiogZmNpID0gcHJvcG9ydGlvbiBvZiB0b3RhbCBtYXNrIGFyZWEgY292ZXJlZCBieSBOb25MeW1waG8tTHltcGhvIGhvdHNwb3RzIChpbnRlcnNlY3Rpb24pCgpUaGUga2V5IGhlcmUgaXMgdG8gbG9vayBhdCB0aGUgdHVtb3VyIGNvbHVtbiAtLSB0aGlzIGNvcnJlc3BvbmRzIHRvIGVwaXRoZWxpYWwgYXJlYSwgd2hpY2ggaXMgd2hhdCB3ZSdyZSBpbnRlcmVzdGVkIGluLiAKClAtdmFsdWVzIGFyZSBhZGp1c3RlZCBmb3IgbXVsdGlwbGUgdGVzdGluZyB3LnIudC4gYWxsIGFyZWFzLCBpbmNsdWRpbmcgdmFzY3VsYXIgYW5kIHdoaXRlc3BhY2UsIGJ1dCBzaW5jZSB0aG9zZSB3ZXJlbid0IHBhcnQgb2Ygb3VyIGh5cG90aGVzaXMgd2UgY2FuIGV4Y2x1ZGUgdGhvc2UgZnJvbSB0aGUgbXVsdGlwbGUgdGVzdGluZyBpbiBhIGZ1dHVyZSBpdGVyYXRpb24uIAoKQSBrZXkgYXNzdW1wdGlvbiBpcyB0aGF0IG5vbi1seW1waG9jeXRlcyBhcmUgY2FuY2VyIGNlbGxzIGluIGVwaXRoZWxpYWwgYXJlYXMgYW5kIGZpYnJvYmxhc3RzIGluIHN0cm9tYWwgYXJlYXMuIEZpbm4sIGRvIHlvdSB0aGluayB3ZSBjYW4gc2F5IHRoaXM/IE9yIHdvdWxkIGl0IGJlIHBvc3NpYmxlIHRvIGluY2x1ZGUgMyBjbGFzc2VzIGluIHRoZSBSRiBtb2RlbCwgaS5lLiBjYW5jZXIsIGx5bXBob2N5dGUsIGFuZCBvdGhlcj8gCgpLZXkgb2JzZXJ2YXRpb25zIGFyZToKCiogbm8gZGlmZmVyZW5jZXMgYmV0d2VlbiBUSUwgY2x1c3RlcnMgZm9yIHN0cm9tYWwgLS0gbm90IHVuZXhwZWN0ZWQgYXMgd2UgZG9uJ3QgcmVhbGx5IGhhdmUgYSBzdHJvbmcgcmVhc29uIHRvIHN1c3BlY3QgYSBmaWJyb2JsYXN0LWltbXVuZSBjZWxsIGludGVyYWN0aW9uIHRoYXQgZGlmZmVycyBiZXR3ZWVuIFRJTCBzdWJ0eXBlcyAoeW91IG1pZ2h0IGJlIGFibGUgdG8gbWFrZSBhIGNhc2UgZm9yIGFuIFMtVElMIGh5cG90aGVzaXMsIGJ1dCB0aGlzIGRvZXNuJ3Qgc2VlbSB0byBiZSBzdXBwb3J0ZWQgYnkgdGhlIGRhdGEpCiogUy1USUwgaGFzIGxvd2VyIGNhbmNlci1seW1waG9jeXRlIGNsdXN0ZXJpbmcgdGhhbiBOLVRJTCBvciBFUy1USUwuIFN1cnByaXNpbmdseSBlbm91Z2ggTi1USUwgaGFzIGNvbXBhcmFibGUgbGV2ZWxzIHRvIEVTLVRJTC4gRG9lc24ndCBhZmZlY3Qgb3VyIGludGVycHJldGF0aW9uIFRPTyBtdWNoIGFzIE4tVElMcyBoYXZlIGZld2VyIFRJTCB0byBzdGFydCB3aXRoLCBhbmQgdGhpcyB0eXBlIG9mIGhvdHNwb3QgYW5hbHlzaXMgbG9va3MgZm9yIFJFTEFUSVZFIGhvdHNwb3RzLiBTbyBpZiB5b3UgaGF2ZSBvbmx5IDUgVElMIGFjcm9zcyBhIHNsaWRlLCBzZWVpbmcgMiBvZiB0aGVtIGNsb3NlIHRvIGVhY2ggb3RoZXIgbWF5IHJlc3VsdCBpbiBhbiBhcmVhIGJlaW5nIGNhbGxlZCBhIGhvdHNwb3QgaW4gdGhhdCBzbGlkZS4gCgoKIyMjIEZvbGxvd3VwcwoKT25lIG9mIHRoZSByZXZpZXdlcnMgcG9pbnRlZCBvdXQgdGhlIGlkZWEgb2YgY2xhc3NpZnlpbmcgTi9TL0VTLVRJTCBmb3IgVENHQS9JQ0dDIHNhbXBsZXMgb24gdGhlIGJhc2lzIG9mIGV4cHJlc3Npb24gcHJvZmlsZXMuIFdoaWxlIHdlIGhhdmVuJ3QgYmVlbiBhYmxlIHRvIGRvIHRoaXMgYWNjdXJhdGVseSwgaXQgbWlnaHQgYmUgcG9zc2libGUgdG8gZG8gdGhpcyBvZmYgb2YgdGhlIEgmRSBpbWFnZXMsIGUuZy4gZnJvbSB0aGUgQ2FuY2VyIERpZ2l0YWwgU2xpZGUgQXJjaGl2ZSAod2hpY2ggSSd2ZSBjaGVja2VkIGhhcyBUQ0dBLU9WKS4gVGhpcyB3b3VsZCBhbGxvdyB1cyB0byB2YWxpZGF0ZSBvdXIgb3RoZXIgbWVhc3VyZXMgb24gYSBsYXJnZXIgY29ob3J0LgoKVGhpcyBpcyB0aGUgbmV4dCBzdGVwIGZvciB0aGlzIGxpbmUgb2YgYW5hbHlzaXMuIAoKCgo=