Setup

library(ithi.utils)
load_base_libs()

library(methods)
library(xgboost)
library(splitstackshape)
library(mclust)
library(entropy)
library(class)
library(HiDimDA)
library(cluster)
library(limma)
library(gage)
library(pathview)
library(MASS)
library(pcaPP)  ## for rrlda
library(mvoutlier)  ## for rrlda
library(rrlda)

library(ithi.meta)
library(ithi.figures)
library(ithi.utils)
library(ithi.expr)

lower.triangle <- lower.tri  ## because rrlda is poorly implemented
ihc_table_path <- snakemake@input$ihc_table
nanostring_data_path <- snakemake@input$nanostring_data
nanostring_annotations_path <- snakemake@input$nanostring_annotations
molecular_subtype_file <- snakemake@input$molsubtypes
gene_expression_array_file <- snakemake@input$array_expression_file
array_known_subtype_file <- snakemake@input$known_subtypes_array

tils_for_cluster <- snakemake@params$tils_for_cluster
db_path <- snakemake@params$db
annotation_colours <- ithi.figures::get_annotation_colours()

exprs <- fread(nanostring_data_path)
nanostring_labels <- fread(nanostring_annotations_path)
ihc_table <- fread(ihc_table_path)
molsubtypes <- fread(molecular_subtype_file)

til_clusters <- ithi.figures:::get_til_clusters(ihc_table, molsubtypes, tils_for_cluster = tils_for_cluster, 
    nclusts = 3)

array_expression_raw <- fread(gene_expression_array_file) %>% as.data.frame %>% 
    tibble::column_to_rownames(var = "V1")
known_subtype_table <- fread(array_known_subtype_file)

colnames(array_expression_raw) <- ithi.meta::df_as_map(known_subtype_table, 
    colnames(array_expression_raw), from = "master_id", to = "condensed_id")

Analysis

Our goal here is to see if we can classify N/S/ES-TIL on the basis of expression profiles.

First, I’d like to emphasize that we only have the following data:

  • Nanostring expression data for a panel of 770 immune-associated + 39 molecular subtyping genes (Leong et al.) for approx. 120 samples
  • Array expression data for the entire U133A2 set of genes (approx. 20000) for approx. 40 samples

And we are unable to get sufficient quality/quantity of RNA from these samples to do full RNA-seq.

So our data likely may not be sufficient to explore this problem in any sort of depth. Also consider that we have samples from the same patient – so we’re going to have to do be careful not to overfit.

  • To do so, you’ll notice that, in cross-validating below, samples from the same patient are forced to belong to either the training or validation strata. This way, models don’t have the unfair advantage of being able to see similar expression profiles from the same patient during training.

Nanostring (full, including molecular subtyping genes)

In this file, we’ll first work off of the Nanostring data, since we have more samples with this. If we can find something good here, then that’d be ideal.

Classifier: XGBoost

X <- exprs %>% subset(select = -c(Code.Class, Accession)) %>% as.data.frame %>% 
    column_to_rownames(var = "Name") %>% t %>% as.data.frame
y <- til_clusters

rownames(X) <- rownames(X) %>% ithi.meta::map_id(from = "voa", to = "condensed_id", 
    db_path)

shared_samples <- intersect(rownames(X), y$condensed_id)

X <- X[shared_samples, ]
y <- (y %>% column_to_rownames(var = "condensed_id"))[shared_samples, , drop = FALSE]
X_lab <- X %>% rownames_to_column(var = "condensed_id")
X_lab$patient_id <- ithi.meta::map_id(X_lab$condensed_id, from = "condensed_id", 
    to = "patient_id", db_path)
X_lab$subtype <- y$cluster

create_data_object <- function(X, y, samples) {
    data <- X[samples, ] %>% as.matrix
    labels <- y[samples, , drop = FALSE]$cluster %>% plyr::mapvalues(from = c("N-TIL", 
        "S-TIL", "ES-TIL"), to = c(0:2)) %>% as.character %>% as.numeric
    
    d <- xgb.DMatrix(data = data, label = labels)
    return(d)
}

run_tilclassifier_cv <- function(X, y, X_lab, type = "xgboost") {
    reserved_patients <- X_lab$patient_id %>% unique %>% sample(size = 3)
    
    X_lab_unreserved <- subset(X_lab, !patient_id %in% reserved_patients)
    
    if (type == "xgboost") {
        X_lab_test <- splitstackshape::stratified(X_lab_unreserved, c("patient_id", 
            "subtype"), 0.3)
        test_samples <- X_lab_test$condensed_id
        train_samples <- setdiff(X_lab_unreserved$condensed_id, test_samples)
        reserved_samples <- setdiff(rownames(X), c(train_samples, test_samples))
        
        dtrain <- create_data_object(X, y, train_samples)
        dtest <- create_data_object(X, y, test_samples)
        dres <- create_data_object(X, y, reserved_samples)
        
        watchlist <- list(train = dtrain, test = dtest)
        
        xgbres <- xgb.train(params = list(num_class = 3), data = dtrain, max_depth = 2, 
            eta = 1, nthread = 2, nrounds = 500, watchlist = watchlist, objective = "multi:softmax", 
            verbose = FALSE)
        
        pred <- predict(xgbres, dres)
        dres_realvals <- y[reserved_samples, , drop = FALSE]$cluster %>% plyr::mapvalues(from = c("N-TIL", 
            "S-TIL", "ES-TIL"), to = c(0:2)) %>% as.character %>% as.numeric
    } else if (type %in% c("knn", "dlda", "lda", "rrlda")) {
        train_samples <- X_lab_unreserved$condensed_id
        
        reserved_samples <- setdiff(rownames(X), c(train_samples))
        
        X_train <- X[train_samples, ]
        X_test <- X[reserved_samples, ]
        y_train <- y[train_samples, , drop = FALSE]$cluster %>% plyr::mapvalues(from = c("N-TIL", 
            "S-TIL", "ES-TIL"), to = c(0:2)) %>% as.character %>% as.numeric
        y_test <- y[reserved_samples, , drop = FALSE]$cluster %>% plyr::mapvalues(from = c("N-TIL", 
            "S-TIL", "ES-TIL"), to = c(0:2)) %>% as.character %>% as.numeric
        
        if (type == "knn") {
            pred <- class::knn(X_train, X_test, y_train, k = 3) %>% as.character %>% 
                as.numeric
        } else if (type == "dlda") {
            model <- HiDimDA::Dlda(X_train, factor(y_train))
            
            pred <- predict(model, X_test)$class %>% unname %>% as.character %>% 
                as.numeric
        } else if (type == "lda") {
            model <- MASS::lda(X_train, factor(y_train))
            
            pred <- predict(model, X_test)$class %>% unname %>% as.character %>% 
                as.numeric
        } else if (type == "rrlda") {
            model <- rrlda::rrlda(X_train, factor(y_train))
            
            pred <- predict(model, X_test)$class %>% unname %>% as.character %>% 
                as.numeric
        }
        dres_realvals <- y_test
    }
    
    data.frame(condensed_id = reserved_samples, prediction = pred, actual = dres_realvals)
}
xgb_results <- lapply(1:10, function(i) run_tilclassifier_cv(X, y, X_lab, type = "xgboost")) %>% 
    rbind.fill

with(xgb_results, mclust::adjustedRandIndex(prediction, actual))
[1] 0.2453873
entropy::mi.empirical(with(xgb_results, table(prediction, actual)))
[1] 0.2055565

So clearly XGBoost isn’t going to cut it. ARI’s and mutual information from cross-validation is quite low (definitely < 0.5 on repeat runs). This isn’t necessarily surprising given that XGBoost is intended for more data-rich problems. That being said, we’ll attempt other methods that have been applied to smaller datasets, such as the 2 below.

Classifier: K-nearest neighbours

knn_results <- lapply(1:10, function(i) run_tilclassifier_cv(X, y, X_lab, type = "knn")) %>% 
    rbind.fill

with(knn_results, mclust::adjustedRandIndex(prediction, actual))
[1] 0.1432024
entropy::mi.empirical(with(knn_results, table(prediction, actual)))
[1] 0.1690655
with(knn_results, table(prediction, actual))
          actual
prediction  0  1  2
         0 43 12  9
         1 21 22 17
         2  7  1 28

Classifier: Diagonal LDA

dlda_results <- lapply(1:10, function(i) run_tilclassifier_cv(X, y, X_lab, type = "dlda")) %>% 
    rbind.fill

with(dlda_results, mclust::adjustedRandIndex(prediction, actual))
[1] 0.4687117
entropy::mi.empirical(with(dlda_results, table(prediction, actual)))
[1] 0.4443378
with(dlda_results, table(prediction, actual))
          actual
prediction  0  1  2
         0 60  6  3
         1 10 46 10
         2  4  6 39

So doing this with Nanostring doesn’t look like it will cut it. With mediocre ARIs and terrible mutual informations, the confidence we have to robustly predict these TIL subtypes in an external subtype is low.

Array (Affy U133A2)

Now we’ll try this from array (U133A2) expression data.

dim(array_expression_raw)
[1] 12438    63
array_expression_raw_subset <- array_expression_raw[, intersect(colnames(array_expression_raw), 
    til_clusters$condensed_id)]
dim(array_expression_raw_subset)
[1] 12438    44

We’re losing nearly a third of our dataset because we don’t have TIL subtypes for a handful of samples (IHC/Nanostring/etc. was not performed). This was because tissue samples were not available for many of the older tumour samples (i.e. those from ITH-1). This is unfortunate, but there’s not much we can do about it.

Differential expression

The first thing to do is to make sure we have a chance of getting reasonable results.

til_clusters_subset <- til_clusters %>% subset(til_clusters$condensed_id %in% 
    colnames(array_expression_raw_subset))
rownames(til_clusters_subset) <- NULL
til_clusters_subset <- til_clusters_subset %>% column_to_rownames(var = "condensed_id")
til_clusters_subset <- til_clusters_subset[colnames(array_expression_raw_subset), 
    , drop = FALSE]
til_clusters_subset$cluster <- til_clusters_subset$cluster %>% str_replace("\\-", 
    "_")
til_clusters_subset$patient_id <- ithi.meta::map_id(rownames(til_clusters_subset), 
    from = "condensed_id", to = "patient_id", db_path)

## Could (and should later) add patients to this model matrix Actually, I
## don't think there's an easy way of doing this
## (https://support.bioconductor.org/p/6655/) -- limma I don't think supports
## random effect models
design <- model.matrix(~0 + til_clusters_subset$cluster)
colnames(design) <- str_replace(colnames(design), "til_clusters_subset\\$cluster", 
    "")
colnames(design) <- str_replace(colnames(design), "til_clusters_subset\\$", 
    "")
contrast.matrix <- limma::makeContrasts((S_TIL + ES_TIL)/2 - N_TIL, ES_TIL - 
    S_TIL, levels = design)
patient_random_effect <- TRUE

if (patient_random_effect) {
    corfit <- duplicateCorrelation(array_expression_raw_subset, design, block = as.character(til_clusters_subset$patient_id))
    fit1 <- lmFit(array_expression_raw_subset, design, block = as.character(til_clusters_subset$patient_id), 
        correlation = corfit$consensus.correlation)
} else {
    fit1 <- lmFit(array_expression_raw_subset, design)
}
fit1_contrasts <- limma::contrasts.fit(fit1, contrast.matrix)
fit1_contrasts <- limma::eBayes(fit1_contrasts)
hot_genes <- limma::topTable(fit1_contrasts, coef = "(S_TIL + ES_TIL)/2 - N_TIL", 
    adjust.method = "BH", number = Inf, sort = "p")
nonstromal_genes <- limma::topTable(fit1_contrasts, coef = "ES_TIL - S_TIL", 
    adjust.method = "BH", number = Inf, sort = "p")
hot_pathways <- ithi.expr::pathway_analysis(hot_genes, gs_type = "kegg", eg_header = "EntrezGene")

hot_pathways

As expected, antigen presentation and cytotoxicity, etc. pop up pretty high on this list.

nonstromal_pathways <- ithi.expr::pathway_analysis(nonstromal_genes, gs_type = "kegg", 
    eg_header = "EntrezGene")

nonstromal_pathways

And here, we see ECM-associated pathways (focal adhesion and ECM-receptor interaction) pop up as downregulated in ES-TIL relative to S-TIL, consistent with the enrichment of C1 molecular subtype tumours in S-TIL.

Classifier

Next, we’d like to design a classifier based on the array expression data.

Do note that we are extremely restricted in our ability to train/test on this data due to the size of the dataset.

PCA-LDA

Since we’re dealing with a dataset with lots of features – that are most definitely correlated with one another – and a small dataset, we’re better off looking at methods like LDA. But first, we’ll want to reduce our feature space to a number of manageable features.

The first thing we’ll do is select genes that demonstrate variability across our dataset. We’ll start by picking 1000 genes with the highest MAD values.

n_top_genes <- 1000

mad_values <- apply(array_expression_raw_subset, 1, mad)
top_genes <- names(sort(mad_values, decreasing = TRUE))[1:n_top_genes]

Now we standardize and apply PCA.

expr_pca <- prcomp(t(array_expression_raw_subset[top_genes, ]), center = TRUE, 
    scale. = TRUE)

And finally, taking the top 30 PCs and using LDA:

num_pcs <- 30

X <- expr_pca$x[, 1:num_pcs]
y <- til_clusters

shared_samples <- intersect(rownames(X), y$condensed_id)

X <- X[shared_samples, ]
y <- (y %>% column_to_rownames(var = "condensed_id"))[shared_samples, , drop = FALSE]
X_lab <- X %>% as.data.frame %>% rownames_to_column(var = "condensed_id")
X_lab$patient_id <- ithi.meta::map_id(X_lab$condensed_id, from = "condensed_id", 
    to = "patient_id", db_path)
X_lab$subtype <- y$cluster
array_lda_results <- lapply(1:10, function(i) run_tilclassifier_cv(X, y, X_lab, 
    type = "lda")) %>% rbind.fill
with(array_lda_results, mclust::adjustedRandIndex(prediction, actual))
[1] 0.04203888
entropy::mi.empirical(with(array_lda_results, table(prediction, actual)))
[1] 0.04790647
with(array_lda_results, table(prediction, actual))
          actual
prediction  0  1  2
         0 10  8 10
         1  7  7 24
         2 16  5 13

We’re not doing so well here.

PCA-DLDA

array_dlda_results <- lapply(1:10, function(i) run_tilclassifier_cv(X, y, X_lab, 
    type = "dlda")) %>% rbind.fill
with(array_dlda_results, mclust::adjustedRandIndex(prediction, actual))
[1] 0.04161488
entropy::mi.empirical(with(array_dlda_results, table(prediction, actual)))
[1] 0.05539707
with(array_dlda_results, table(prediction, actual))
          actual
prediction  0  1  2
         0 23 12 11
         1  1  3  5
         2 18  6 23

Again, disappointing performance. But perhaps not surprising given how small this dataset is.

RRLDA

An alternative to explicitly selecting features with PCA is to regularize. Others (Huang, Cabral, and de la Torre 2015) have shown that robust regularized LDA outperforms PCA-LDA in some scenarios.

We can try this too with the rrlda package in R.

Unfortunately we can’t use all 1000 most variable genes unless we want to be waiting half an afternoon, cause this method isn’t fast.

num_top_subset <- 200

X <- t(array_expression_raw_subset[top_genes[1:num_top_subset], ])

y <- til_clusters

shared_samples <- intersect(rownames(X), y$condensed_id)

X <- X[shared_samples, ]
y <- (y %>% column_to_rownames(var = "condensed_id"))[shared_samples, , drop = FALSE]
X_lab <- X %>% as.data.frame %>% rownames_to_column(var = "condensed_id")
X_lab$patient_id <- ithi.meta::map_id(X_lab$condensed_id, from = "condensed_id", 
    to = "patient_id", db_path)
X_lab$subtype <- y$cluster
array_rrlda_results <- lapply(1:10, function(i) run_tilclassifier_cv(X, y, X_lab, 
    type = "rrlda")) %>% rbind.fill
with(array_rrlda_results, mclust::adjustedRandIndex(prediction, actual))
[1] 0.03365307
entropy::mi.empirical(with(array_rrlda_results, table(prediction, actual)))
[1] 0.03669907
with(array_rrlda_results, table(prediction, actual))
          actual
prediction  0  1  2
         0 29  7 14
         1  3  5  3
         2 27  8 27

As this shows, nothing seems to look good for the array expression data. But this is not necessarily surprising, given the lack of samples we have to work with in designing a classifier.

Other options

It’s possible that we may be able to design a classifier off of H&E data (epithelial and stromal lymphocyte counts + cancer cell-lymphocyte colocalization). To do so, we’re going to need to show:

  • That those variables can effectively classify N/S/ES-TIL
  • That cancer cell-lymphocyte colocalization adds information onto a classifier with only H&E count/density information

But that’s an analysis that’ll be described in another file, after Finn’s pipeline completes on TCGA.

LS0tCnRpdGxlOiAiQ2xhc3NpZnlpbmcgVElMIGNsdXN0ZXJzIGJ5IGV4cHJlc3Npb24iCi0tLQogICAgICAgICAgICAgICAgICAgICAgICBgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCiMjIyMjIyMjIFNuYWtlbWFrZSBoZWFkZXIgIyMjIyMjIyMKbGlicmFyeShtZXRob2RzKQpTbmFrZW1ha2UgPC0gc2V0Q2xhc3MoCiAgICAiU25ha2VtYWtlIiwKICAgIHNsb3RzID0gYygKICAgICAgICBpbnB1dCA9ICJsaXN0IiwKICAgICAgICBvdXRwdXQgPSAibGlzdCIsCiAgICAgICAgcGFyYW1zID0gImxpc3QiLAogICAgICAgIHdpbGRjYXJkcyA9ICJsaXN0IiwKICAgICAgICB0aHJlYWRzID0gIm51bWVyaWMiLAogICAgICAgIGxvZyA9ICJsaXN0IiwKICAgICAgICByZXNvdXJjZXMgPSAibGlzdCIsCiAgICAgICAgY29uZmlnID0gImxpc3QiLAogICAgICAgIHJ1bGUgPSAiY2hhcmFjdGVyIgogICAgKQopCnNuYWtlbWFrZSA8LSBTbmFrZW1ha2UoCiAgICBpbnB1dCA9IGxpc3QoJ25vdGVib29rcy90aWxjbHVzdGVyX2V4cHJjbGFzc2lmaWNhdGlvbi5SbWQnLCAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9uYW5vc3RyaW5nL3BhbmNhbmNlcl9hbm5vdGF0aW9ucy50c3YnLCAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9hcnJheS9zdWJ0eXBlcy9rbm93bl9zdWJ0eXBlcy50c3YnLCAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9hcnJheS9nZW5lX2V4cHJzX3JtYV9iYXRjaF9jb3JyZWN0ZWQudHh0JywgJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9yZXN1bHRzL25hbm9zdHJpbmdfcmVzdWx0cy9pdGhfZnVsbC9xYy9saW1tYV9xdWFudGlsZS9ub3JtYWxpemVkX2V4cHJlc3Npb25fdm9hX2xhYmVsc19maWx0ZXJlZC50c3YnLCAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvbW9sc3VidHlwZXMudHN2JywgJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2loY190YWJsZS50c3YnLCAibm90ZWJvb2siID0gJ25vdGVib29rcy90aWxjbHVzdGVyX2V4cHJjbGFzc2lmaWNhdGlvbi5SbWQnLCAibmFub3N0cmluZ19hbm5vdGF0aW9ucyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9uYW5vc3RyaW5nL3BhbmNhbmNlcl9hbm5vdGF0aW9ucy50c3YnLCAia25vd25fc3VidHlwZXNfYXJyYXkiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9kYXRhL2V4cHJlc3Npb24vYXJyYXkvc3VidHlwZXMva25vd25fc3VidHlwZXMudHN2JywgImFycmF5X2V4cHJlc3Npb25fZmlsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9hcnJheS9nZW5lX2V4cHJzX3JtYV9iYXRjaF9jb3JyZWN0ZWQudHh0JywgIm5hbm9zdHJpbmdfZGF0YSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3Jlc3VsdHMvbmFub3N0cmluZ19yZXN1bHRzL2l0aF9mdWxsL3FjL2xpbW1hX3F1YW50aWxlL25vcm1hbGl6ZWRfZXhwcmVzc2lvbl92b2FfbGFiZWxzX2ZpbHRlcmVkLnRzdicsICJtb2xzdWJ0eXBlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvbW9sc3VidHlwZXMudHN2JywgImloY190YWJsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaWhjX3RhYmxlLnRzdicpLAogICAgb3V0cHV0ID0gbGlzdCgnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvcmV2aWV3L25vdGVib29rcy9ydW4yL3RpbGNsdXN0ZXJfZXhwcmNsYXNzaWZpY2F0aW9uLm5iLmh0bWwnKSwKICAgIHBhcmFtcyA9IGxpc3QoYygnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSwgJ3RpbGNsdXN0ZXJfZXhwcmNsYXNzaWZpY2F0aW9uX2FuYWx5c2lzJywgJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9tZXRhZGF0YS9kYi9pbW11bmVfcHJvamVjdC5zcWxpdGUzJywgInRpbHNfZm9yX2NsdXN0ZXIiID0gYygnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSwgIm5hbWUiID0gJ3RpbGNsdXN0ZXJfZXhwcmNsYXNzaWZpY2F0aW9uX2FuYWx5c2lzJywgImRiIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvbWV0YWRhdGEvZGIvaW1tdW5lX3Byb2plY3Quc3FsaXRlMycpLAogICAgd2lsZGNhcmRzID0gbGlzdCgpLAogICAgdGhyZWFkcyA9IDEsCiAgICBsb2cgPSBsaXN0KCcvc2hhaGxhYi9hbHpoYW5nL2NsdXN0dG1wL3BhcGVycmV2aWV3Mi9ub3RlYm9va3MvdGlsY2x1c3Rlcl9leHByY2xhc3NpZmljYXRpb25fYW5hbHlzaXMubG9nJyksCiAgICByZXNvdXJjZXMgPSBsaXN0KCksCiAgICBjb25maWcgPSBsaXN0KCJzb21hdGljX2NvZGluZ19yZXN1bHRfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9zb21hdGljX2NvZGluZ192YXJpYW50cycsICJjbG9uZV90cmVlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvY2xvbmVzL3RyZWVfZGF0YS50c3YnLCAibmFub3N0cmluZ19hbm5vdGF0aW9ucyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9uYW5vc3RyaW5nL3BhbmNhbmNlcl9hbm5vdGF0aW9ucy50c3YnLCAibmVvZWRpdGluZ19vdXRkaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL25lb2VkaXRpbmcvcnVuNicsICJrbm93bl9zdWJ0eXBlc19hcnJheSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9hcnJheS9zdWJ0eXBlcy9rbm93bl9zdWJ0eXBlcy50c3YnLCAicmVmc2VxX2dlbmVfZmlsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2dlbm9tZS9oZzE5L3JlZnNlcV9nZW5lcy5iZWQnLCAiYnJlYWtwb2ludF90YWJsZSIgPSAnL3NoYWhsYWIvYW1jcGhlcnNvbi9wcm9qZWN0cy9pdGgzL2l0aDMvbm90ZWJvb2tzL2Jlc3Bva2UvaXRoX2JyZWFrcG9pbnRzLnRzdicsICJpdGhfaWNnY19iYyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaXRoX2ljZ2NfbWVyZ2VkX2JjLnRzdicsICJkaXN0YW5jZV9tZXRob2QiID0gJ2hvcm4nLCAidGNyX2RpdmVyc2l0eSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvbWl4Y3IvbWl4Y3JfcnVucy9pdGhfMV8yXzMvbWl4Y3I1L3Bvc3Rwcm9jZXNzL1RSQi9wb3N0ZmlsdGVyX2RpdmVyc2l0eV9zdGF0cy9kaXZlcnNpdHkuc3RyaWN0LnJlc2FtcGxlZC50eHQnLCAic252X2NsdXN0ZXJfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9jbG9uZXMvc252X2NsdXN0ZXInLCAibG9nZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2NsdXN0dG1wL3BhcGVycmV2aWV3MicsICJyb29uZXlfbXV0c2lnY3ZfZmlsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2V4dGVybmFsL290aGVyX3BhcGVycy9tbWM2Lnhsc3gnLCAic252X3RhYmxlIiA9ICcvc2hhaGxhYi9hbWNwaGVyc29uL3Byb2plY3RzL2l0aDMvaXRoMy9ub3RlYm9va3MvYmVzcG9rZS9pdGhfc252cy50c3YnLCAiYmNyX2RpdmVyc2l0eSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvbWl4Y3IvbWl4Y3JfcnVucy9pdGhfMV8yXzMvbWl4Y3I1L3Bvc3Rwcm9jZXNzL0lHSC9wb3N0ZmlsdGVyX2RpdmVyc2l0eV9zdGF0cy9kaXZlcnNpdHkuc3RyaWN0LnJlc2FtcGxlZC50eHQnLCAiaWhjX2ZlYXR1cmVzX291dHB1dCIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvaW50ZXJtZWRpYXRlcy9ydW4yL2loY19mZWF0dXJlc19vdXRwdXQudHh0JywgIml0aF9zdGF0X3R5cGVzIiA9IGMoJ2VudHJvcHknLCAncG9zdHByb2Nlc3NlZF9kaXZlcmdlbmNlJywgJ2NvbWJpbmVkX2l0aF9ub3JtYWxpemVkJywgJ3Byb3BvcnRpb25fc3ViY2xvbmFsJyksICJtb2xzdWJ0eXBlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvbW9sc3VidHlwZXMudHN2JywgInZhcmlhYmlsaXR5X3R5cGUiID0gJ3N0YWJpbGl6ZScsICJoZV9yZXN1bHRzX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2l0aGkvZmlubl9yZXN1bHRzL2hlX291dHB1dF9Ob3YyOScsICJwcmV2YWxlbmNlX3RocmVzaG9sZCIgPSAwLjAxLCAiaWNnY19zcGVjaW1lbiIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL0lDR0Mvc3BlY2ltZW4udHN2JywgInRhYmxlX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvcmV2aWV3L3RhYmxlcy9ydW4yJywgImlncGFydGl0aW9uX291dGRpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvaWdwYXJ0aXRpb24vcnVuMjInLCAidG90YWxfdGlsdHlwZXMiID0gYygnVF9DRDhfZGVuc2l0eScsICdUX0NENF9kZW5zaXR5JywgJ1RfQ0QyMF9kZW5zaXR5JywgJ1RfUGxhc21hX2RlbnNpdHknKSwgImFsbF90aWx0eXBlcyIgPSBjKCdUX0NEOF9kZW5zaXR5JywgJ1RfQ0Q0X2RlbnNpdHknLCAnVF9DRDIwX2RlbnNpdHknLCAnVF9QbGFzbWFfZGVuc2l0eScsICdFX0NEOF9kZW5zaXR5JywgJ0VfQ0Q0X2RlbnNpdHknLCAnRV9DRDIwX2RlbnNpdHknLCAnRV9QbGFzbWFfZGVuc2l0eScsICdTX0NEOF9kZW5zaXR5JywgJ1NfQ0Q0X2RlbnNpdHknLCAnU19DRDIwX2RlbnNpdHknLCAnU19QbGFzbWFfZGVuc2l0eScpLCAidGlsc19mb3JfY2x1c3RlciIgPSBjKCdFX0NEOF9kZW5zaXR5JywgJ0VfQ0Q0X2RlbnNpdHknLCAnRV9DRDIwX2RlbnNpdHknLCAnRV9QbGFzbWFfZGVuc2l0eScsICdTX0NEOF9kZW5zaXR5JywgJ1NfQ0Q0X2RlbnNpdHknLCAnU19DRDIwX2RlbnNpdHknLCAnU19QbGFzbWFfZGVuc2l0eScpLCAieGNyX3RhYmxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi94Y3JfdGFibGUudHN2JywgInRpbHNfZm9yX3ZhcmlhYmlsaXR5IiA9IGMoJ1RfQ0Q4X2RlbnNpdHknLCAnVF9DRDRfZGVuc2l0eScsICdUX0NEMjBfZGVuc2l0eScsICdUX1BsYXNtYV9kZW5zaXR5JyksICJlcGl0b3Blc191bmlxdWVfZmlsdGVyZWQiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2VwaXRvcGVzX3VuaXF1ZV9maWx0ZXJlZC50c3YnLCAiYXJyYXlfZXhwcmVzc2lvbl9maWxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvZGF0YS9leHByZXNzaW9uL2FycmF5L2dlbmVfZXhwcnNfcm1hX2JhdGNoX2NvcnJlY3RlZC50eHQnLCAibmFub3N0cmluZ19kYXRhIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcmVzdWx0cy9uYW5vc3RyaW5nX3Jlc3VsdHMvaXRoX2Z1bGwvcWMvbGltbWFfcXVhbnRpbGUvbm9ybWFsaXplZF9leHByZXNzaW9uX3ZvYV9sYWJlbHNfZmlsdGVyZWQudHN2JywgIm1tY3RtX2ZpbmFsX3BhdGllbnRfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcmVzdWx0cy9tbWN0bV9yZXN1bHRzL2l0aF9ieS1wYXRpZW50X3dpdGgtb3YnLCAiZmlubmhlX3BpcGVsaW5lX3Jlc3VsdHNfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3BpcGVsaW5lX291dHB1dHMvaXRoX2ltbXVuZS9maW5uaGUvcnVuMScsICJyZW1peHRfY2VsbHVsYXJpdHlfcGxvaWR5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9yZW1peHRfY2VsbHVsYXJpdHlfcGxvaWR5LnRzdicsICJkYiIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL21ldGFkYXRhL2RiL2ltbXVuZV9wcm9qZWN0LnNxbGl0ZTMnLCAiaWNnY19zdWJ0eXBlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL0lDR0MvaWNnY19wcmltYXJ5X3R1bW91cl9zdWJ0eXBlcy50c3YnLCAibm90ZWJvb2tfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy9yZXZpZXcvbm90ZWJvb2tzL3J1bjInLCAicGF0aWVudHNfZm9yX2Nsb25hbCIgPSBjKDEsIDIsIDMsIDQsIDcsIDksIDEwLCAxMSwgMTIsIDEzLCAxNCwgMTUsIDE2LCAxNyksICJpdGhfc3RhdHMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2l0aF9zdGF0aXN0aWNzLnRzdicsICJpbWFnZV9zdW1tYXJ5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvaXRoaS95dWFuX2hlY3JfaW1hZ2VfcmVzdWx0cy5jc3YnLCAiY2xvbmVfYnJhbmNoX2xlbmd0aHMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2Nsb25lcy9icmFuY2hfZGF0YS50c3YnLCAiY2xvbGFfcmVzdWx0X2ZpbGUiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL2Nsb2xhL3J1bjQvY2xvbGFfY29uZGVuc2VkX3Jlc3VsdHMvYmV0YS9jbG9sYV9yZXN1bHRzLnRzdicsICJjb3B5bnVtYmVyX3RhYmxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvaXRoaS9tYXN0ZXJfY29weW51bWJlcl9maWxlLnRzdicsICJjbG9uZV9wcmV2YWxlbmNlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvY2xvbmVzL2Nsb25lX2RhdGEudHN2JywgInRpbF9jbHVzdGVyc19vdXRwdXQiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL2ludGVybWVkaWF0ZXMvcnVuMi90aWxfY2x1c3RlcnNfb3V0cHV0LnR4dCcsICJiZW5jaG1hcmtkaXIiID0gJy9zaGFobGFiL2FsemhhbmcvYmVuY2htYXJrcy9wYXBlcnJldmlldzInLCAidHVtb3VyX3B1cml0eSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvdHVtb3VyX3B1cml0eS50c3YnLCAidGlsY2x1c3Rlcl9zdXBlcnZpc2VkX2lweW5iIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmV2aWV3L2lweS90aWxjbHVzdGVyX3N1cGVydmlzZWRtdWx0aWNsYXNzLmlweW5iJywgImltYWdlX3N1bW1hcnkyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvaXRoaS95dWFuX2hlY3JfaW1hZ2VfcmVzdWx0c18yLmNzdicsICJpaGNfdGFibGUiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2loY190YWJsZS50c3YnKSwKICAgIHJ1bGUgPSAndGlsY2x1c3Rlcl9leHByY2xhc3NpZmljYXRpb25fYW5hbHlzaXMnCikKIyMjIyMjIyMgT3JpZ2luYWwgc2NyaXB0ICMjIyMjIyMjIwoKICAgICAgICAgICAgICAgICAgICAgICAgYGBgCgoKIyMgU2V0dXAKCmBgYHtyIGdsb2JhbF9jaHVua19vcHRpb25zLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHRpZHk9VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9VFJVRSkgI2NhY2hlPVRSVUUKYGBgCgpgYGB7cn0KbGlicmFyeShpdGhpLnV0aWxzKQpsb2FkX2Jhc2VfbGlicygpCgpsaWJyYXJ5KG1ldGhvZHMpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShzcGxpdHN0YWNrc2hhcGUpCmxpYnJhcnkobWNsdXN0KQpsaWJyYXJ5KGVudHJvcHkpCmxpYnJhcnkoY2xhc3MpCmxpYnJhcnkoSGlEaW1EQSkKbGlicmFyeShjbHVzdGVyKQpsaWJyYXJ5KGxpbW1hKQpsaWJyYXJ5KGdhZ2UpCmxpYnJhcnkocGF0aHZpZXcpCmxpYnJhcnkoTUFTUykKbGlicmFyeShwY2FQUCkgIyMgZm9yIHJybGRhCmxpYnJhcnkobXZvdXRsaWVyKSAjIyBmb3IgcnJsZGEKbGlicmFyeShycmxkYSkKCmxpYnJhcnkoaXRoaS5tZXRhKQpsaWJyYXJ5KGl0aGkuZmlndXJlcykKbGlicmFyeShpdGhpLnV0aWxzKQpsaWJyYXJ5KGl0aGkuZXhwcikKCmxvd2VyLnRyaWFuZ2xlIDwtIGxvd2VyLnRyaSAjIyBiZWNhdXNlIHJybGRhIGlzIHBvb3JseSBpbXBsZW1lbnRlZApgYGAKCmBgYHtyfQppaGNfdGFibGVfcGF0aCA8LSBzbmFrZW1ha2VAaW5wdXQkaWhjX3RhYmxlCm5hbm9zdHJpbmdfZGF0YV9wYXRoIDwtIHNuYWtlbWFrZUBpbnB1dCRuYW5vc3RyaW5nX2RhdGEKbmFub3N0cmluZ19hbm5vdGF0aW9uc19wYXRoIDwtIHNuYWtlbWFrZUBpbnB1dCRuYW5vc3RyaW5nX2Fubm90YXRpb25zCm1vbGVjdWxhcl9zdWJ0eXBlX2ZpbGUgPC0gc25ha2VtYWtlQGlucHV0JG1vbHN1YnR5cGVzCmdlbmVfZXhwcmVzc2lvbl9hcnJheV9maWxlIDwtIHNuYWtlbWFrZUBpbnB1dCRhcnJheV9leHByZXNzaW9uX2ZpbGUgCmFycmF5X2tub3duX3N1YnR5cGVfZmlsZSA8LSBzbmFrZW1ha2VAaW5wdXQka25vd25fc3VidHlwZXNfYXJyYXkgCgp0aWxzX2Zvcl9jbHVzdGVyIDwtIHNuYWtlbWFrZUBwYXJhbXMkdGlsc19mb3JfY2x1c3RlcgpkYl9wYXRoIDwtIHNuYWtlbWFrZUBwYXJhbXMkZGIKYGBgCgpgYGB7cn0KYW5ub3RhdGlvbl9jb2xvdXJzIDwtIGl0aGkuZmlndXJlczo6Z2V0X2Fubm90YXRpb25fY29sb3VycygpCgpleHBycyA8LSBmcmVhZChuYW5vc3RyaW5nX2RhdGFfcGF0aCkKbmFub3N0cmluZ19sYWJlbHMgPC0gZnJlYWQobmFub3N0cmluZ19hbm5vdGF0aW9uc19wYXRoKQppaGNfdGFibGUgPC0gZnJlYWQoaWhjX3RhYmxlX3BhdGgpCm1vbHN1YnR5cGVzIDwtIGZyZWFkKG1vbGVjdWxhcl9zdWJ0eXBlX2ZpbGUpCgp0aWxfY2x1c3RlcnMgPC0gaXRoaS5maWd1cmVzOjo6Z2V0X3RpbF9jbHVzdGVycyhpaGNfdGFibGUsIG1vbHN1YnR5cGVzLCB0aWxzX2Zvcl9jbHVzdGVyID0gdGlsc19mb3JfY2x1c3RlciwgbmNsdXN0cyA9IDMpCgphcnJheV9leHByZXNzaW9uX3JhdyA8LSBmcmVhZChnZW5lX2V4cHJlc3Npb25fYXJyYXlfZmlsZSkgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKHZhciA9ICJWMSIpCmtub3duX3N1YnR5cGVfdGFibGUgPC0gZnJlYWQoYXJyYXlfa25vd25fc3VidHlwZV9maWxlKQoKY29sbmFtZXMoYXJyYXlfZXhwcmVzc2lvbl9yYXcpIDwtIGl0aGkubWV0YTo6ZGZfYXNfbWFwKGtub3duX3N1YnR5cGVfdGFibGUsIGNvbG5hbWVzKGFycmF5X2V4cHJlc3Npb25fcmF3KSwgZnJvbSA9ICJtYXN0ZXJfaWQiLCB0byA9ICJjb25kZW5zZWRfaWQiKQpgYGAKCiMjIEFuYWx5c2lzCgpPdXIgZ29hbCBoZXJlIGlzIHRvIHNlZSBpZiB3ZSBjYW4gY2xhc3NpZnkgTi9TL0VTLVRJTCBvbiB0aGUgYmFzaXMgb2YgZXhwcmVzc2lvbiBwcm9maWxlcy4gCgpGaXJzdCwgSSdkIGxpa2UgdG8gZW1waGFzaXplIHRoYXQgd2Ugb25seSBoYXZlIHRoZSBmb2xsb3dpbmcgZGF0YToKCiogTmFub3N0cmluZyBleHByZXNzaW9uIGRhdGEgZm9yIGEgcGFuZWwgb2YgNzcwIGltbXVuZS1hc3NvY2lhdGVkICsgMzkgbW9sZWN1bGFyIHN1YnR5cGluZyBnZW5lcyAoTGVvbmcgZXQgYWwuKSBmb3IgYXBwcm94LiAxMjAgc2FtcGxlcwoqIEFycmF5IGV4cHJlc3Npb24gZGF0YSBmb3IgdGhlIGVudGlyZSBVMTMzQTIgc2V0IG9mIGdlbmVzIChhcHByb3guIDIwMDAwKSBmb3IgYXBwcm94LiA0MCBzYW1wbGVzCgpBbmQgd2UgYXJlIHVuYWJsZSB0byBnZXQgc3VmZmljaWVudCBxdWFsaXR5L3F1YW50aXR5IG9mIFJOQSBmcm9tIHRoZXNlIHNhbXBsZXMgdG8gZG8gZnVsbCBSTkEtc2VxLiAKClNvIG91ciBkYXRhIGxpa2VseSBtYXkgbm90IGJlIHN1ZmZpY2llbnQgdG8gZXhwbG9yZSB0aGlzIHByb2JsZW0gaW4gYW55IHNvcnQgb2YgZGVwdGguIEFsc28gY29uc2lkZXIgdGhhdCB3ZSBoYXZlIHNhbXBsZXMgZnJvbSB0aGUgc2FtZSBwYXRpZW50IC0tIHNvIHdlJ3JlIGdvaW5nIHRvIGhhdmUgdG8gZG8gYmUgY2FyZWZ1bCBub3QgdG8gb3ZlcmZpdC4gCgoqIFRvIGRvIHNvLCB5b3UnbGwgbm90aWNlIHRoYXQsIGluIGNyb3NzLXZhbGlkYXRpbmcgYmVsb3csIHNhbXBsZXMgZnJvbSB0aGUgc2FtZSBwYXRpZW50IGFyZSBmb3JjZWQgdG8gYmVsb25nIHRvIGVpdGhlciB0aGUgdHJhaW5pbmcgb3IgdmFsaWRhdGlvbiBzdHJhdGEuIFRoaXMgd2F5LCBtb2RlbHMgZG9uJ3QgaGF2ZSB0aGUgdW5mYWlyIGFkdmFudGFnZSBvZiBiZWluZyBhYmxlIHRvIHNlZSBzaW1pbGFyIGV4cHJlc3Npb24gcHJvZmlsZXMgZnJvbSB0aGUgc2FtZSBwYXRpZW50IGR1cmluZyB0cmFpbmluZy4gCgoKIyMjIE5hbm9zdHJpbmcgKGZ1bGwsIGluY2x1ZGluZyBtb2xlY3VsYXIgc3VidHlwaW5nIGdlbmVzKQoKSW4gdGhpcyBmaWxlLCB3ZSdsbCBmaXJzdCB3b3JrIG9mZiBvZiB0aGUgTmFub3N0cmluZyBkYXRhLCBzaW5jZSB3ZSBoYXZlIG1vcmUgc2FtcGxlcyB3aXRoIHRoaXMuIElmIHdlIGNhbiBmaW5kIHNvbWV0aGluZyBnb29kIGhlcmUsIHRoZW4gdGhhdCdkIGJlIGlkZWFsLiAKCiMjIyMgQ2xhc3NpZmllcjogWEdCb29zdAoKYGBge3J9ClggPC0gZXhwcnMgJT4lIHN1YnNldChzZWxlY3Q9LWMoQ29kZS5DbGFzcywgQWNjZXNzaW9uKSkgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIGNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAiTmFtZSIpICU+JSB0ICU+JSBhcy5kYXRhLmZyYW1lCnkgPC0gdGlsX2NsdXN0ZXJzCgpyb3duYW1lcyhYKSA8LSByb3duYW1lcyhYKSAlPiUgaXRoaS5tZXRhOjptYXBfaWQoZnJvbSA9ICJ2b2EiLCB0byA9ICJjb25kZW5zZWRfaWQiLCBkYl9wYXRoKQoKc2hhcmVkX3NhbXBsZXMgPC0gaW50ZXJzZWN0KHJvd25hbWVzKFgpLCB5JGNvbmRlbnNlZF9pZCkKClggPC0gWFtzaGFyZWRfc2FtcGxlcyxdCnkgPC0gKHkgJT4lIGNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAiY29uZGVuc2VkX2lkIikpW3NoYXJlZF9zYW1wbGVzLCxkcm9wPUZBTFNFXSAKYGBgCgpgYGB7cn0KWF9sYWIgPC0gWCAlPiUgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJjb25kZW5zZWRfaWQiKQpYX2xhYiRwYXRpZW50X2lkIDwtIGl0aGkubWV0YTo6bWFwX2lkKFhfbGFiJGNvbmRlbnNlZF9pZCwgZnJvbSA9ICJjb25kZW5zZWRfaWQiLCB0byA9ICJwYXRpZW50X2lkIiwgZGJfcGF0aCkKWF9sYWIkc3VidHlwZSA8LSB5JGNsdXN0ZXIKCmNyZWF0ZV9kYXRhX29iamVjdCA8LSBmdW5jdGlvbihYLCB5LCBzYW1wbGVzKSB7CiAgZGF0YSA8LSBYW3NhbXBsZXMsXSAlPiUgYXMubWF0cml4IAogIGxhYmVscyA8LSB5W3NhbXBsZXMsLGRyb3A9RkFMU0VdJGNsdXN0ZXIgJT4lIHBseXI6Om1hcHZhbHVlcyhmcm9tID0gYygiTi1USUwiLCAiUy1USUwiLCAiRVMtVElMIiksIHRvID0gYygwOjIpKSAlPiUgYXMuY2hhcmFjdGVyICU+JSBhcy5udW1lcmljCiAgCiAgZCA8LSB4Z2IuRE1hdHJpeChkYXRhPWRhdGEsIGxhYmVsPWxhYmVscykKICByZXR1cm4oZCkKfQoKcnVuX3RpbGNsYXNzaWZpZXJfY3YgPC0gZnVuY3Rpb24oWCwgeSwgWF9sYWIsIHR5cGUgPSAieGdib29zdCIpIHsKICByZXNlcnZlZF9wYXRpZW50cyA8LSBYX2xhYiRwYXRpZW50X2lkICU+JSB1bmlxdWUgJT4lIHNhbXBsZShzaXplID0gMykKICAKICBYX2xhYl91bnJlc2VydmVkIDwtIHN1YnNldChYX2xhYiwgIXBhdGllbnRfaWQgJWluJSByZXNlcnZlZF9wYXRpZW50cykKICAKICBpZiAodHlwZSA9PSAieGdib29zdCIpIHsKICAgIFhfbGFiX3Rlc3QgPC0gc3BsaXRzdGFja3NoYXBlOjpzdHJhdGlmaWVkKFhfbGFiX3VucmVzZXJ2ZWQsIGMoInBhdGllbnRfaWQiLCAic3VidHlwZSIpLCAwLjMpCiAgICB0ZXN0X3NhbXBsZXMgPC0gWF9sYWJfdGVzdCRjb25kZW5zZWRfaWQKICAgIHRyYWluX3NhbXBsZXMgPC0gc2V0ZGlmZihYX2xhYl91bnJlc2VydmVkJGNvbmRlbnNlZF9pZCwgdGVzdF9zYW1wbGVzKQogICAgcmVzZXJ2ZWRfc2FtcGxlcyA8LSBzZXRkaWZmKHJvd25hbWVzKFgpLCBjKHRyYWluX3NhbXBsZXMsIHRlc3Rfc2FtcGxlcykpCiAgICAKICAgIGR0cmFpbiA8LSBjcmVhdGVfZGF0YV9vYmplY3QoWCwgeSwgdHJhaW5fc2FtcGxlcykKICAgIGR0ZXN0IDwtIGNyZWF0ZV9kYXRhX29iamVjdChYLCB5LCB0ZXN0X3NhbXBsZXMpCiAgICBkcmVzIDwtIGNyZWF0ZV9kYXRhX29iamVjdChYLCB5LCByZXNlcnZlZF9zYW1wbGVzKQogICAgCiAgICB3YXRjaGxpc3QgPC0gbGlzdCh0cmFpbj1kdHJhaW4sIHRlc3Q9ZHRlc3QpCiAgICAKICAgIHhnYnJlcyA8LSB4Z2IudHJhaW4ocGFyYW1zID0gbGlzdChudW1fY2xhc3MgPSAzKSwgZGF0YSA9IGR0cmFpbiwgbWF4X2RlcHRoID0gMiwgZXRhID0gMSwgbnRocmVhZCA9IDIsIG5yb3VuZHMgPSA1MDAsIHdhdGNobGlzdCA9IHdhdGNobGlzdCwgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRtYXgiLCB2ZXJib3NlID0gRkFMU0UpCiAgICAKICAgIHByZWQgPC0gcHJlZGljdCh4Z2JyZXMsIGRyZXMpCiAgICBkcmVzX3JlYWx2YWxzIDwtICB5W3Jlc2VydmVkX3NhbXBsZXMsLGRyb3A9RkFMU0VdJGNsdXN0ZXIgJT4lIHBseXI6Om1hcHZhbHVlcyhmcm9tID0gYygiTi1USUwiLCAiUy1USUwiLCAiRVMtVElMIiksIHRvID0gYygwOjIpKSAlPiUgYXMuY2hhcmFjdGVyICU+JSBhcy5udW1lcmljCiAgfSBlbHNlIGlmICh0eXBlICVpbiUgYygia25uIiwgImRsZGEiLCAibGRhIiwgInJybGRhIikpIHsKICAgIHRyYWluX3NhbXBsZXMgPC0gWF9sYWJfdW5yZXNlcnZlZCRjb25kZW5zZWRfaWQKICAgIAogICAgcmVzZXJ2ZWRfc2FtcGxlcyA8LSBzZXRkaWZmKHJvd25hbWVzKFgpLCBjKHRyYWluX3NhbXBsZXMpKQogICAgCiAgICBYX3RyYWluIDwtIFhbdHJhaW5fc2FtcGxlcyxdCiAgICBYX3Rlc3QgPC0gWFtyZXNlcnZlZF9zYW1wbGVzLF0KICAgIHlfdHJhaW4gPC0geVt0cmFpbl9zYW1wbGVzLCxkcm9wPUZBTFNFXSRjbHVzdGVyICU+JSBwbHlyOjptYXB2YWx1ZXMoZnJvbSA9IGMoIk4tVElMIiwgIlMtVElMIiwgIkVTLVRJTCIpLCB0byA9IGMoMDoyKSkgJT4lIGFzLmNoYXJhY3RlciAlPiUgYXMubnVtZXJpYwogICAgeV90ZXN0IDwtIHlbcmVzZXJ2ZWRfc2FtcGxlcywsZHJvcD1GQUxTRV0kY2x1c3RlciAlPiUgcGx5cjo6bWFwdmFsdWVzKGZyb20gPSBjKCJOLVRJTCIsICJTLVRJTCIsICJFUy1USUwiKSwgdG8gPSBjKDA6MikpICU+JSBhcy5jaGFyYWN0ZXIgJT4lIGFzLm51bWVyaWMKICAgIAogICAgaWYgKHR5cGUgPT0gImtubiIpIHsKICAgICAgcHJlZCA8LSBjbGFzczo6a25uKFhfdHJhaW4sIFhfdGVzdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICB5X3RyYWluLCBrID0gMykgJT4lIGFzLmNoYXJhY3RlciAlPiUgYXMubnVtZXJpYwogICAgfSBlbHNlIGlmICh0eXBlID09ICJkbGRhIikgewogICAgICBtb2RlbCA8LSBIaURpbURBOjpEbGRhKFhfdHJhaW4sIGZhY3Rvcih5X3RyYWluKSkKICAgICAgCiAgICAgIHByZWQgPC0gcHJlZGljdChtb2RlbCwgWF90ZXN0KSRjbGFzcyAlPiUgdW5uYW1lICU+JSBhcy5jaGFyYWN0ZXIgJT4lIGFzLm51bWVyaWMKICAgIH0gZWxzZSBpZiAodHlwZSA9PSAibGRhIikgewogICAgICBtb2RlbCA8LSBNQVNTOjpsZGEoWF90cmFpbiwgZmFjdG9yKHlfdHJhaW4pKQogICAgICAKICAgICAgcHJlZCA8LSBwcmVkaWN0KG1vZGVsLCBYX3Rlc3QpJGNsYXNzICU+JSB1bm5hbWUgJT4lIGFzLmNoYXJhY3RlciAlPiUgYXMubnVtZXJpYwogICAgfSBlbHNlIGlmICh0eXBlID09ICJycmxkYSIpIHsKICAgICAgbW9kZWwgPC0gcnJsZGE6OnJybGRhKFhfdHJhaW4sIGZhY3Rvcih5X3RyYWluKSkKICAgICAgCiAgICAgIHByZWQgPC0gcHJlZGljdChtb2RlbCwgWF90ZXN0KSRjbGFzcyAlPiUgdW5uYW1lICU+JSBhcy5jaGFyYWN0ZXIgJT4lIGFzLm51bWVyaWMKICAgIH0KICAgIGRyZXNfcmVhbHZhbHMgPC0geV90ZXN0CiAgfQogIAogIGRhdGEuZnJhbWUoY29uZGVuc2VkX2lkPXJlc2VydmVkX3NhbXBsZXMsIHByZWRpY3Rpb249cHJlZCwgYWN0dWFsPWRyZXNfcmVhbHZhbHMpCn0KYGBgCgpgYGB7ciwgY2FjaGUgPSBUUlVFfQp4Z2JfcmVzdWx0cyA8LSBsYXBwbHkoMToxMCwgZnVuY3Rpb24oaSkgcnVuX3RpbGNsYXNzaWZpZXJfY3YoWCwgeSwgWF9sYWIsIHR5cGUgPSAieGdib29zdCIpKSAlPiUgcmJpbmQuZmlsbAoKd2l0aCh4Z2JfcmVzdWx0cywgbWNsdXN0OjphZGp1c3RlZFJhbmRJbmRleChwcmVkaWN0aW9uLCBhY3R1YWwpKQoKZW50cm9weTo6bWkuZW1waXJpY2FsKHdpdGgoeGdiX3Jlc3VsdHMsIHRhYmxlKHByZWRpY3Rpb24sIGFjdHVhbCkpKQpgYGAKCgpTbyBjbGVhcmx5IFhHQm9vc3QgaXNuJ3QgZ29pbmcgdG8gY3V0IGl0LiBBUkkncyBhbmQgbXV0dWFsIGluZm9ybWF0aW9uIGZyb20gY3Jvc3MtdmFsaWRhdGlvbiBpcyBxdWl0ZSBsb3cgKGRlZmluaXRlbHkgPCAwLjUgb24gcmVwZWF0IHJ1bnMpLiBUaGlzIGlzbid0IG5lY2Vzc2FyaWx5IHN1cnByaXNpbmcgZ2l2ZW4gdGhhdCBYR0Jvb3N0IGlzIGludGVuZGVkIGZvciBtb3JlIGRhdGEtcmljaCBwcm9ibGVtcy4gVGhhdCBiZWluZyBzYWlkLCB3ZSdsbCBhdHRlbXB0IG90aGVyIG1ldGhvZHMgdGhhdCBoYXZlIGJlZW4gYXBwbGllZCB0byBzbWFsbGVyIGRhdGFzZXRzLCBzdWNoIGFzIHRoZSAyIGJlbG93LiAKCiMjIyMgQ2xhc3NpZmllcjogSy1uZWFyZXN0IG5laWdoYm91cnMKCmBgYHtyLCBjYWNoZT1UUlVFfQprbm5fcmVzdWx0cyA8LSBsYXBwbHkoMToxMCwgZnVuY3Rpb24oaSkgcnVuX3RpbGNsYXNzaWZpZXJfY3YoWCwgeSwgWF9sYWIsIHR5cGUgPSAia25uIikpICU+JSByYmluZC5maWxsCgp3aXRoKGtubl9yZXN1bHRzLCBtY2x1c3Q6OmFkanVzdGVkUmFuZEluZGV4KHByZWRpY3Rpb24sIGFjdHVhbCkpCgplbnRyb3B5OjptaS5lbXBpcmljYWwod2l0aChrbm5fcmVzdWx0cywgdGFibGUocHJlZGljdGlvbiwgYWN0dWFsKSkpCmBgYAoKYGBge3J9CndpdGgoa25uX3Jlc3VsdHMsIHRhYmxlKHByZWRpY3Rpb24sIGFjdHVhbCkpCmBgYAoKIyMjIyBDbGFzc2lmaWVyOiBEaWFnb25hbCBMREEKCmBgYHtyLCBjYWNoZT1UUlVFfQpkbGRhX3Jlc3VsdHMgPC0gbGFwcGx5KDE6MTAsIGZ1bmN0aW9uKGkpIHJ1bl90aWxjbGFzc2lmaWVyX2N2KFgsIHksIFhfbGFiLCB0eXBlID0gImRsZGEiKSkgJT4lIHJiaW5kLmZpbGwKCndpdGgoZGxkYV9yZXN1bHRzLCBtY2x1c3Q6OmFkanVzdGVkUmFuZEluZGV4KHByZWRpY3Rpb24sIGFjdHVhbCkpCgplbnRyb3B5OjptaS5lbXBpcmljYWwod2l0aChkbGRhX3Jlc3VsdHMsIHRhYmxlKHByZWRpY3Rpb24sIGFjdHVhbCkpKQpgYGAKCmBgYHtyfQp3aXRoKGRsZGFfcmVzdWx0cywgdGFibGUocHJlZGljdGlvbiwgYWN0dWFsKSkKYGBgCgpTbyBkb2luZyB0aGlzIHdpdGggTmFub3N0cmluZyBkb2Vzbid0IGxvb2sgbGlrZSBpdCB3aWxsIGN1dCBpdC4gV2l0aCBtZWRpb2NyZSBBUklzIGFuZCB0ZXJyaWJsZSBtdXR1YWwgaW5mb3JtYXRpb25zLCB0aGUgY29uZmlkZW5jZSB3ZSBoYXZlIHRvIHJvYnVzdGx5IHByZWRpY3QgdGhlc2UgVElMIHN1YnR5cGVzIGluIGFuIGV4dGVybmFsIHN1YnR5cGUgaXMgbG93LiAKCiMjIEFycmF5IChBZmZ5IFUxMzNBMikKCk5vdyB3ZSdsbCB0cnkgdGhpcyBmcm9tIGFycmF5IChVMTMzQTIpIGV4cHJlc3Npb24gZGF0YS4gCgpgYGB7cn0KZGltKGFycmF5X2V4cHJlc3Npb25fcmF3KQpgYGAKCmBgYHtyfQphcnJheV9leHByZXNzaW9uX3Jhd19zdWJzZXQgPC0gYXJyYXlfZXhwcmVzc2lvbl9yYXdbLGludGVyc2VjdChjb2xuYW1lcyhhcnJheV9leHByZXNzaW9uX3JhdyksIHRpbF9jbHVzdGVycyRjb25kZW5zZWRfaWQpXQpkaW0oYXJyYXlfZXhwcmVzc2lvbl9yYXdfc3Vic2V0KQpgYGAKCldlJ3JlIGxvc2luZyBuZWFybHkgYSB0aGlyZCBvZiBvdXIgZGF0YXNldCBiZWNhdXNlIHdlIGRvbid0IGhhdmUgVElMIHN1YnR5cGVzIGZvciBhIGhhbmRmdWwgb2Ygc2FtcGxlcyAoSUhDL05hbm9zdHJpbmcvZXRjLiB3YXMgbm90IHBlcmZvcm1lZCkuIFRoaXMgd2FzIGJlY2F1c2UgdGlzc3VlIHNhbXBsZXMgd2VyZSBub3QgYXZhaWxhYmxlIGZvciBtYW55IG9mIHRoZSBvbGRlciB0dW1vdXIgc2FtcGxlcyAoaS5lLiB0aG9zZSBmcm9tIElUSC0xKS4gVGhpcyBpcyB1bmZvcnR1bmF0ZSwgYnV0IHRoZXJlJ3Mgbm90IG11Y2ggd2UgY2FuIGRvIGFib3V0IGl0LiAKCiMjIyBEaWZmZXJlbnRpYWwgZXhwcmVzc2lvbgoKVGhlIGZpcnN0IHRoaW5nIHRvIGRvIGlzIHRvIG1ha2Ugc3VyZSB3ZSBoYXZlIGEgY2hhbmNlIG9mIGdldHRpbmcgcmVhc29uYWJsZSByZXN1bHRzLiAKCmBgYHtyfQp0aWxfY2x1c3RlcnNfc3Vic2V0IDwtIHRpbF9jbHVzdGVycyAlPiUgc3Vic2V0KHRpbF9jbHVzdGVycyRjb25kZW5zZWRfaWQgJWluJSBjb2xuYW1lcyhhcnJheV9leHByZXNzaW9uX3Jhd19zdWJzZXQpKQpyb3duYW1lcyh0aWxfY2x1c3RlcnNfc3Vic2V0KSA8LSBOVUxMCnRpbF9jbHVzdGVyc19zdWJzZXQgPC0gdGlsX2NsdXN0ZXJzX3N1YnNldCAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKHZhciA9ICJjb25kZW5zZWRfaWQiKQp0aWxfY2x1c3RlcnNfc3Vic2V0IDwtIHRpbF9jbHVzdGVyc19zdWJzZXRbY29sbmFtZXMoYXJyYXlfZXhwcmVzc2lvbl9yYXdfc3Vic2V0KSwsZHJvcD1GQUxTRV0KdGlsX2NsdXN0ZXJzX3N1YnNldCRjbHVzdGVyIDwtIHRpbF9jbHVzdGVyc19zdWJzZXQkY2x1c3RlciAlPiUgc3RyX3JlcGxhY2UoIlxcLSIsICJfIikKdGlsX2NsdXN0ZXJzX3N1YnNldCRwYXRpZW50X2lkIDwtIGl0aGkubWV0YTo6bWFwX2lkKHJvd25hbWVzKHRpbF9jbHVzdGVyc19zdWJzZXQpLCBmcm9tID0gImNvbmRlbnNlZF9pZCIsIHRvID0gInBhdGllbnRfaWQiLCBkYl9wYXRoKQoKIyMgQ291bGQgKGFuZCBzaG91bGQgbGF0ZXIpIGFkZCBwYXRpZW50cyB0byB0aGlzIG1vZGVsIG1hdHJpeAojIyBBY3R1YWxseSwgSSBkb24ndCB0aGluayB0aGVyZSdzIGFuIGVhc3kgd2F5IG9mIGRvaW5nIHRoaXMgKGh0dHBzOi8vc3VwcG9ydC5iaW9jb25kdWN0b3Iub3JnL3AvNjY1NS8pIC0tIGxpbW1hIEkgZG9uJ3QgdGhpbmsgc3VwcG9ydHMgcmFuZG9tIGVmZmVjdCBtb2RlbHMKZGVzaWduIDwtIG1vZGVsLm1hdHJpeCh+MCArIHRpbF9jbHVzdGVyc19zdWJzZXQkY2x1c3RlcikKY29sbmFtZXMoZGVzaWduKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhkZXNpZ24pLCAidGlsX2NsdXN0ZXJzX3N1YnNldFxcJGNsdXN0ZXIiLCAiIikKY29sbmFtZXMoZGVzaWduKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhkZXNpZ24pLCAidGlsX2NsdXN0ZXJzX3N1YnNldFxcJCIsICIiKQpjb250cmFzdC5tYXRyaXggPC0gbGltbWE6Om1ha2VDb250cmFzdHMoKFNfVElMICsgRVNfVElMKS8yIC0gTl9USUwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVTX1RJTCAtIFNfVElMLCBsZXZlbHMgPSBkZXNpZ24pCgoKYGBgCgpgYGB7cn0KcGF0aWVudF9yYW5kb21fZWZmZWN0IDwtIFRSVUUKCmlmIChwYXRpZW50X3JhbmRvbV9lZmZlY3QpIHsKICBjb3JmaXQgPC0gZHVwbGljYXRlQ29ycmVsYXRpb24oYXJyYXlfZXhwcmVzc2lvbl9yYXdfc3Vic2V0LCBkZXNpZ24sIGJsb2NrPWFzLmNoYXJhY3Rlcih0aWxfY2x1c3RlcnNfc3Vic2V0JHBhdGllbnRfaWQpKQogIGZpdDEgPC0gbG1GaXQoYXJyYXlfZXhwcmVzc2lvbl9yYXdfc3Vic2V0LCBkZXNpZ24sIGJsb2NrID0gYXMuY2hhcmFjdGVyKHRpbF9jbHVzdGVyc19zdWJzZXQkcGF0aWVudF9pZCksIGNvcnJlbGF0aW9uID0gY29yZml0JGNvbnNlbnN1cy5jb3JyZWxhdGlvbikKfSBlbHNlIHsKICBmaXQxIDwtIGxtRml0KGFycmF5X2V4cHJlc3Npb25fcmF3X3N1YnNldCwgZGVzaWduKQp9CmZpdDFfY29udHJhc3RzIDwtIGxpbW1hOjpjb250cmFzdHMuZml0KGZpdDEsIGNvbnRyYXN0Lm1hdHJpeCkKZml0MV9jb250cmFzdHMgPC0gbGltbWE6OmVCYXllcyhmaXQxX2NvbnRyYXN0cykKYGBgCgpgYGB7cn0KaG90X2dlbmVzIDwtIGxpbW1hOjp0b3BUYWJsZShmaXQxX2NvbnRyYXN0cywgY29lZiA9ICIoU19USUwgKyBFU19USUwpLzIgLSBOX1RJTCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkanVzdC5tZXRob2QgPSAiQkgiLCBudW1iZXIgPSBJbmYsIHNvcnQgPSAicCIpCm5vbnN0cm9tYWxfZ2VuZXMgPC0gbGltbWE6OnRvcFRhYmxlKGZpdDFfY29udHJhc3RzLCBjb2VmID0gIkVTX1RJTCAtIFNfVElMIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWRqdXN0Lm1ldGhvZCA9ICJCSCIsIG51bWJlciA9IEluZiwgc29ydCA9ICJwIikKYGBgCgpgYGB7cn0KaG90X3BhdGh3YXlzIDwtIGl0aGkuZXhwcjo6cGF0aHdheV9hbmFseXNpcyhob3RfZ2VuZXMsIGdzX3R5cGUgPSAia2VnZyIsIGVnX2hlYWRlciA9ICJFbnRyZXpHZW5lIikKCmhvdF9wYXRod2F5cwpgYGAKCkFzIGV4cGVjdGVkLCBhbnRpZ2VuIHByZXNlbnRhdGlvbiBhbmQgY3l0b3RveGljaXR5LCBldGMuIHBvcCB1cCBwcmV0dHkgaGlnaCBvbiB0aGlzIGxpc3QuIAoKYGBge3J9Cm5vbnN0cm9tYWxfcGF0aHdheXMgPC0gaXRoaS5leHByOjpwYXRod2F5X2FuYWx5c2lzKG5vbnN0cm9tYWxfZ2VuZXMsIGdzX3R5cGUgPSAia2VnZyIsIGVnX2hlYWRlciA9ICJFbnRyZXpHZW5lIikKCm5vbnN0cm9tYWxfcGF0aHdheXMKYGBgCgpBbmQgaGVyZSwgd2Ugc2VlIEVDTS1hc3NvY2lhdGVkIHBhdGh3YXlzIChmb2NhbCBhZGhlc2lvbiBhbmQgRUNNLXJlY2VwdG9yIGludGVyYWN0aW9uKSBwb3AgdXAgYXMgZG93bnJlZ3VsYXRlZCBpbiBFUy1USUwgcmVsYXRpdmUgdG8gUy1USUwsIGNvbnNpc3RlbnQgd2l0aCB0aGUgZW5yaWNobWVudCBvZiBDMSBtb2xlY3VsYXIgc3VidHlwZSB0dW1vdXJzIGluIFMtVElMLiAKCiMjIyBDbGFzc2lmaWVyCgpOZXh0LCB3ZSdkIGxpa2UgdG8gZGVzaWduIGEgY2xhc3NpZmllciBiYXNlZCBvbiB0aGUgYXJyYXkgZXhwcmVzc2lvbiBkYXRhLiAKCkRvIG5vdGUgdGhhdCB3ZSBhcmUgZXh0cmVtZWx5IHJlc3RyaWN0ZWQgaW4gb3VyIGFiaWxpdHkgdG8gdHJhaW4vdGVzdCBvbiB0aGlzIGRhdGEgZHVlIHRvIHRoZSBzaXplIG9mIHRoZSBkYXRhc2V0LiAKCiMjIyMgUENBLUxEQQoKU2luY2Ugd2UncmUgZGVhbGluZyB3aXRoIGEgZGF0YXNldCB3aXRoIGxvdHMgb2YgZmVhdHVyZXMgLS0gdGhhdCBhcmUgbW9zdCBkZWZpbml0ZWx5IGNvcnJlbGF0ZWQgd2l0aCBvbmUgYW5vdGhlciAtLSBhbmQgYSBzbWFsbCBkYXRhc2V0LCB3ZSdyZSBiZXR0ZXIgb2ZmIGxvb2tpbmcgYXQgbWV0aG9kcyBsaWtlIExEQS4gQnV0IGZpcnN0LCB3ZSdsbCB3YW50IHRvIHJlZHVjZSBvdXIgZmVhdHVyZSBzcGFjZSB0byBhIG51bWJlciBvZiBtYW5hZ2VhYmxlIGZlYXR1cmVzLiAKClRoZSBmaXJzdCB0aGluZyB3ZSdsbCBkbyBpcyBzZWxlY3QgZ2VuZXMgdGhhdCBkZW1vbnN0cmF0ZSB2YXJpYWJpbGl0eSBhY3Jvc3Mgb3VyIGRhdGFzZXQuIFdlJ2xsIHN0YXJ0IGJ5IHBpY2tpbmcgMTAwMCBnZW5lcyB3aXRoIHRoZSBoaWdoZXN0IE1BRCB2YWx1ZXMuIAoKYGBge3J9Cm5fdG9wX2dlbmVzIDwtIDEwMDAKCm1hZF92YWx1ZXMgPC0gYXBwbHkoYXJyYXlfZXhwcmVzc2lvbl9yYXdfc3Vic2V0LCAxLCBtYWQpCnRvcF9nZW5lcyA8LSBuYW1lcyhzb3J0KG1hZF92YWx1ZXMsIGRlY3JlYXNpbmcgPSBUUlVFKSlbMTpuX3RvcF9nZW5lc10KYGBgCgpOb3cgd2Ugc3RhbmRhcmRpemUgYW5kIGFwcGx5IFBDQS4KCmBgYHtyfQpleHByX3BjYSA8LSBwcmNvbXAodChhcnJheV9leHByZXNzaW9uX3Jhd19zdWJzZXRbdG9wX2dlbmVzLF0pLCBjZW50ZXIgPSBUUlVFLCBzY2FsZS4gPSBUUlVFKQpgYGAKCkFuZCBmaW5hbGx5LCB0YWtpbmcgdGhlIHRvcCAzMCBQQ3MgYW5kIHVzaW5nIExEQToKCmBgYHtyfQpudW1fcGNzIDwtIDMwCgpYIDwtIGV4cHJfcGNhJHhbLDE6bnVtX3Bjc10KeSA8LSB0aWxfY2x1c3RlcnMKCnNoYXJlZF9zYW1wbGVzIDwtIGludGVyc2VjdChyb3duYW1lcyhYKSwgeSRjb25kZW5zZWRfaWQpCgpYIDwtIFhbc2hhcmVkX3NhbXBsZXMsXQp5IDwtICh5ICU+JSBjb2x1bW5fdG9fcm93bmFtZXModmFyID0gImNvbmRlbnNlZF9pZCIpKVtzaGFyZWRfc2FtcGxlcywsZHJvcD1GQUxTRV0gCmBgYAoKYGBge3J9ClhfbGFiIDwtIFggJT4lIGFzLmRhdGEuZnJhbWUgJT4lIHJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiY29uZGVuc2VkX2lkIikKWF9sYWIkcGF0aWVudF9pZCA8LSBpdGhpLm1ldGE6Om1hcF9pZChYX2xhYiRjb25kZW5zZWRfaWQsIGZyb20gPSAiY29uZGVuc2VkX2lkIiwgdG8gPSAicGF0aWVudF9pZCIsIGRiX3BhdGgpClhfbGFiJHN1YnR5cGUgPC0geSRjbHVzdGVyCmBgYAoKYGBge3J9CmFycmF5X2xkYV9yZXN1bHRzIDwtIGxhcHBseSgxOjEwLCBmdW5jdGlvbihpKSBydW5fdGlsY2xhc3NpZmllcl9jdihYLCB5LCBYX2xhYiwgdHlwZSA9ICJsZGEiKSkgJT4lIHJiaW5kLmZpbGwKYGBgCgpgYGB7cn0Kd2l0aChhcnJheV9sZGFfcmVzdWx0cywgbWNsdXN0OjphZGp1c3RlZFJhbmRJbmRleChwcmVkaWN0aW9uLCBhY3R1YWwpKQoKZW50cm9weTo6bWkuZW1waXJpY2FsKHdpdGgoYXJyYXlfbGRhX3Jlc3VsdHMsIHRhYmxlKHByZWRpY3Rpb24sIGFjdHVhbCkpKQpgYGAKCmBgYHtyfQp3aXRoKGFycmF5X2xkYV9yZXN1bHRzLCB0YWJsZShwcmVkaWN0aW9uLCBhY3R1YWwpKQpgYGAKCldlJ3JlIG5vdCBkb2luZyBzbyB3ZWxsIGhlcmUuIAoKCiMjIyMgUENBLURMREEKCmBgYHtyfQphcnJheV9kbGRhX3Jlc3VsdHMgPC0gbGFwcGx5KDE6MTAsIGZ1bmN0aW9uKGkpIHJ1bl90aWxjbGFzc2lmaWVyX2N2KFgsIHksIFhfbGFiLCB0eXBlID0gImRsZGEiKSkgJT4lIHJiaW5kLmZpbGwKYGBgCgpgYGB7cn0Kd2l0aChhcnJheV9kbGRhX3Jlc3VsdHMsIG1jbHVzdDo6YWRqdXN0ZWRSYW5kSW5kZXgocHJlZGljdGlvbiwgYWN0dWFsKSkKCmVudHJvcHk6Om1pLmVtcGlyaWNhbCh3aXRoKGFycmF5X2RsZGFfcmVzdWx0cywgdGFibGUocHJlZGljdGlvbiwgYWN0dWFsKSkpCmBgYAoKYGBge3J9CndpdGgoYXJyYXlfZGxkYV9yZXN1bHRzLCB0YWJsZShwcmVkaWN0aW9uLCBhY3R1YWwpKQpgYGAKCkFnYWluLCBkaXNhcHBvaW50aW5nIHBlcmZvcm1hbmNlLiBCdXQgcGVyaGFwcyBub3Qgc3VycHJpc2luZyBnaXZlbiBob3cgc21hbGwgdGhpcyBkYXRhc2V0IGlzLiAKCiMjIyMgUlJMREEKCkFuIGFsdGVybmF0aXZlIHRvIGV4cGxpY2l0bHkgc2VsZWN0aW5nIGZlYXR1cmVzIHdpdGggUENBIGlzIHRvIHJlZ3VsYXJpemUuIE90aGVycyAoSHVhbmcsIENhYnJhbCwgYW5kIGRlIGxhIFRvcnJlIDIwMTUpIGhhdmUgc2hvd24gdGhhdCByb2J1c3QgcmVndWxhcml6ZWQgTERBIG91dHBlcmZvcm1zIFBDQS1MREEgaW4gc29tZSBzY2VuYXJpb3MuIAoKV2UgY2FuIHRyeSB0aGlzIHRvbyB3aXRoIHRoZSBycmxkYSBwYWNrYWdlIGluIFIuIAoKVW5mb3J0dW5hdGVseSB3ZSBjYW4ndCB1c2UgYWxsIDEwMDAgbW9zdCB2YXJpYWJsZSBnZW5lcyB1bmxlc3Mgd2Ugd2FudCB0byBiZSB3YWl0aW5nIGhhbGYgYW4gYWZ0ZXJub29uLCBjYXVzZSB0aGlzIG1ldGhvZCBpc24ndCBmYXN0LiAKCmBgYHtyfQpudW1fdG9wX3N1YnNldCA8LSAyMDAKClggPC0gdChhcnJheV9leHByZXNzaW9uX3Jhd19zdWJzZXRbdG9wX2dlbmVzWzE6bnVtX3RvcF9zdWJzZXRdLF0pCgp5IDwtIHRpbF9jbHVzdGVycwoKc2hhcmVkX3NhbXBsZXMgPC0gaW50ZXJzZWN0KHJvd25hbWVzKFgpLCB5JGNvbmRlbnNlZF9pZCkKClggPC0gWFtzaGFyZWRfc2FtcGxlcyxdCnkgPC0gKHkgJT4lIGNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAiY29uZGVuc2VkX2lkIikpW3NoYXJlZF9zYW1wbGVzLCxkcm9wPUZBTFNFXSAKYGBgCgpgYGB7cn0KWF9sYWIgPC0gWCAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJjb25kZW5zZWRfaWQiKQpYX2xhYiRwYXRpZW50X2lkIDwtIGl0aGkubWV0YTo6bWFwX2lkKFhfbGFiJGNvbmRlbnNlZF9pZCwgZnJvbSA9ICJjb25kZW5zZWRfaWQiLCB0byA9ICJwYXRpZW50X2lkIiwgZGJfcGF0aCkKWF9sYWIkc3VidHlwZSA8LSB5JGNsdXN0ZXIKYGBgCgpgYGB7cn0KYXJyYXlfcnJsZGFfcmVzdWx0cyA8LSBsYXBwbHkoMToxMCwgZnVuY3Rpb24oaSkgcnVuX3RpbGNsYXNzaWZpZXJfY3YoWCwgeSwgWF9sYWIsIHR5cGUgPSAicnJsZGEiKSkgJT4lIHJiaW5kLmZpbGwKYGBgCgpgYGB7cn0Kd2l0aChhcnJheV9ycmxkYV9yZXN1bHRzLCBtY2x1c3Q6OmFkanVzdGVkUmFuZEluZGV4KHByZWRpY3Rpb24sIGFjdHVhbCkpCgplbnRyb3B5OjptaS5lbXBpcmljYWwod2l0aChhcnJheV9ycmxkYV9yZXN1bHRzLCB0YWJsZShwcmVkaWN0aW9uLCBhY3R1YWwpKSkKYGBgCgpgYGB7cn0Kd2l0aChhcnJheV9ycmxkYV9yZXN1bHRzLCB0YWJsZShwcmVkaWN0aW9uLCBhY3R1YWwpKQpgYGAKCkFzIHRoaXMgc2hvd3MsIG5vdGhpbmcgc2VlbXMgdG8gbG9vayBnb29kIGZvciB0aGUgYXJyYXkgZXhwcmVzc2lvbiBkYXRhLiBCdXQgdGhpcyBpcyBub3QgbmVjZXNzYXJpbHkgc3VycHJpc2luZywgZ2l2ZW4gdGhlIGxhY2sgb2Ygc2FtcGxlcyB3ZSBoYXZlIHRvIHdvcmsgd2l0aCBpbiBkZXNpZ25pbmcgYSBjbGFzc2lmaWVyLiAKCiMjIE90aGVyIG9wdGlvbnMKCkl0J3MgcG9zc2libGUgdGhhdCB3ZSBtYXkgYmUgYWJsZSB0byBkZXNpZ24gYSBjbGFzc2lmaWVyIG9mZiBvZiBIJkUgZGF0YSAoZXBpdGhlbGlhbCBhbmQgc3Ryb21hbCBseW1waG9jeXRlIGNvdW50cyArIGNhbmNlciBjZWxsLWx5bXBob2N5dGUgY29sb2NhbGl6YXRpb24pLiBUbyBkbyBzbywgd2UncmUgZ29pbmcgdG8gbmVlZCB0byBzaG93OgoKKiBUaGF0IHRob3NlIHZhcmlhYmxlcyBjYW4gZWZmZWN0aXZlbHkgY2xhc3NpZnkgTi9TL0VTLVRJTAoqIFRoYXQgY2FuY2VyIGNlbGwtbHltcGhvY3l0ZSBjb2xvY2FsaXphdGlvbiBhZGRzIGluZm9ybWF0aW9uIG9udG8gYSBjbGFzc2lmaWVyIHdpdGggb25seSBIJkUgY291bnQvZGVuc2l0eSBpbmZvcm1hdGlvbgoKQnV0IHRoYXQncyBhbiBhbmFseXNpcyB0aGF0J2xsIGJlIGRlc2NyaWJlZCBpbiBhbm90aGVyIGZpbGUsIGFmdGVyIEZpbm4ncyBwaXBlbGluZSBjb21wbGV0ZXMgb24gVENHQS4gCgo=