Setup

library(ithi.utils)
load_base_libs()

library(methods)
library(factoextra)
library(NbClust)
library(pvclust)
library(clValid)

library(ithi.meta)
library(ithi.figures)
library(ithi.utils)
ihc_table_path <- snakemake@input$ihc_table

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

Summary

Here we’ll assess the robustness of our TIL clustering.

In terms of understanding robustness, there are 3 major aspects:

  • Internal validity
  • External validity
  • Indirect validity

Since we don’t have a gold standard to compare to, external validity is out of the question.

Internal validity refers to measures that measure intra-cluster connectedness/tightness, between-cluster distance, and stability. This includes indices like the Dunn index (Dunn 1974). Stability refers to measures that determine the consistency of clustering based on subsets of data or features. pvclust can be used to assess the stability of clustering by resampling features.

Indirect validity refers to the (e.g. biological) relevance of the resulting clusters. Based on the results of ITH comparison, we have evidence to suggest that the S-TIL/ES-TIL split is biologically relevant.

Analysis

ihc_table_subset <- subset(ihc_table, select = c("condensed_id", tils_for_cluster))
ihc_table_subset_numeric <- ihc_table_subset %>% as.data.frame %>% column_to_rownames(var = "condensed_id") %>% 
    scale

na_rows <- apply(ihc_table_subset_numeric, 1, function(x) any(is.na(x)))
ihc_table_subset_numeric <- ihc_table_subset_numeric[!na_rows, ]

dat_clipped <- ithi.utils::clip_values(ihc_table_subset_numeric, hi = 2, lo = -2)
cluster_res <- factoextra::fviz_nbclust(dat_clipped, FUNcluster = hcut, method = c("wss"), 
    diss = NULL, k.max = 10)

nbclust_res <- NbClust(dat_clipped, distance = "euclidean", min.nc = 2, max.nc = 7, 
    method = "ward.D2", alphaBeale = 0, index = "all")

*** : The Hubert index is a graphical method of determining the number of clusters.
                In the plot of Hubert index, we seek a significant knee that corresponds to a 
                significant increase of the value of the measure i.e the significant peak in Hubert
                index second differences plot. 
 

*** : The D index is a graphical method of determining the number of clusters. 
                In the plot of D index, we seek a significant knee (the significant peak in Dindex
                second differences plot) that corresponds to a significant increase of the value of
                the measure. 
 
******************************************************************* 
* Among all indices:                                                
* 8 proposed 2 as the best number of clusters 
* 8 proposed 3 as the best number of clusters 
* 1 proposed 4 as the best number of clusters 
* 1 proposed 6 as the best number of clusters 
* 5 proposed 7 as the best number of clusters 

                   ***** Conclusion *****                            
 
* According to the majority rule, the best number of clusters is  2 
 
 
******************************************************************* 
fviz_nbclust(nbclust_res)
Among all indices: 
===================
* 2 proposed  0 as the best number of clusters
* 1 proposed  1 as the best number of clusters
* 8 proposed  2 as the best number of clusters
* 8 proposed  3 as the best number of clusters
* 1 proposed  4 as the best number of clusters
* 1 proposed  6 as the best number of clusters
* 5 proposed  7 as the best number of clusters

Conclusion
=========================
* According to the majority rule, the best number of clusters is  2 .

2 and 3 clusters are both equally well supported by this barrage of methods.

The Dunn index, which is very well cited, reports 3 as the optimal number of clusters.

clValid

We’ll look at the Dunn index again with this other package.

clValid2 <- function(obj, nClust, clMethods = "hierarchical", validation = "stability", 
    maxitems = 600, metric = "euclidean", method = "average", neighbSize = 10, 
    annotation = NULL, GOcategory = "all", goTermFreq = 0.05, dropEvidence = NULL, 
    verbose = FALSE, ...) {
    clMethods <- tolower(clMethods)
    clMethods <- match.arg(clMethods, c("hierarchical", "kmeans", "diana", "fanny", 
        "som", "model", "sota", "pam", "clara", "agnes"), several.ok = TRUE)
    if ("som" %in% clMethods) {
        if (!require(kohonen)) {
            stop("package 'kohonen' required for clustering using SOM")
        }
    }
    if ("model" %in% clMethods) {
        if (!require(mclust)) {
            stop("package 'mclust' required for model-based clustering")
        }
    }
    validation <- match.arg(validation, c("stability", "internal", "biological"), 
        several.ok = TRUE)
    metric <- match.arg(metric, c("euclidean", "correlation", "manhattan"))
    method <- match.arg(method, c("ward", "ward.D2", "single", "complete", "average"))
    GOcategory <- match.arg(GOcategory, c("all", "BP", "CC", "MF"))
    switch(class(obj), matrix = mat <- obj, ExpressionSet = mat <- Biobase::exprs(obj), 
        data.frame = {
            if (any(!sapply(obj, class) %in% c("numeric", "integer"))) stop("data frame 'obj' contains non-numeric data")
            mat <- as.matrix(obj)
        }, stop("argument 'obj' must be a matrix, data.frame, or ExpressionSet object"))
    if (nrow(mat) > maxitems) {
        if (interactive()) {
            cat("\nThe number of items to be clustered is larger than 'maxitems'\n")
            cat("The memory and time required may be excessive, do you wish to continue?\n")
            cat("(y to continue, any other character to exit) ")
            ans <- tolower(substr(readLines(n = 1), 1, 1))
            if (ans != "y") {
                stop("Exiting clValid, number of items exceeds 'maxitems'")
            }
        } else {
            stop("The number of items to be clustered is larger than 'maxitems'\n  Either decrease the number of rows (items) or increase 'maxitems'\n")
        }
    }
    if ("clara" %in% clMethods & metric == "correlation") 
        warning("'clara' currently only works with 'euclidean' or 'manhattan' metrics - metric will be changed to 'euclidean'  ")
    if (is.character(annotation) & length(grep(".db", annotation)) == 0) {
        annotation <- paste(annotation, ".db", sep = "")
    }
    if (is.list(annotation)) {
        if (is.null(rownames(mat))) {
            stop("rownames of data must be present to specify biological annotation from file")
        }
        annotation <- annotationListToMatrix(annotation, genenames = rownames(mat))
    }
    if ("biological" %in% validation & is.null(annotation)) {
        stop("annotation must be specified in order to use biological validation")
    }
    if ("biological" %in% validation & is.character(annotation)) {
        if (!require(Biobase) | !require(GO.db) | !require(annotate)) {
            stop("packages 'Biobase', 'GO.db', and 'annotate' required for 2nd type of biological validation \n\nthese can be downloaded from Bioconductor (www.bioconductor.org)")
        }
    }
    if ("biological" %in% validation & is.null(rownames(mat))) {
        stop("rownames of data must be present to use biological validation")
    }
    nClust <- floor(nClust)
    if (any(nClust < 1)) 
        stop("argument 'nClust' must be a positive integer vector")
    if (metric == "correlation") 
        Dist <- as.dist(1 - cor(t(mat), use = "pairwise.complete.obs")) else Dist <- dist(mat, method = metric)
    clusterObjs <- vector("list", length(clMethods))
    names(clusterObjs) <- clMethods
    measures <- c(if ("stability" %in% validation) c("APN", "AD", "ADM", "FOM"), 
        if ("internal" %in% validation) c("Connectivity", "Dunn", "Silhouette"), 
        if ("biological" %in% validation) c("BHI", "BSI"))
    validMeasures <- array(dim = c(length(measures), length(nClust), length(clMethods)))
    dimnames(validMeasures) <- list(measures, nClust, clMethods)
    for (i in 1:length(clMethods)) {
        cvalid <- vClusters(mat, clMethods[i], nClust, validation = validation, 
            Dist = Dist, method = method, metric = metric, annotation = annotation, 
            GOcategory = GOcategory, goTermFreq = goTermFreq, neighbSize = neighbSize, 
            dropEvidence = dropEvidence, verbose = verbose, ...)
        clusterObjs[[i]] <- cvalid$clusterObj
        validMeasures[, , i] <- cvalid$measures
    }
    if (is.null(rownames(mat))) {
        rownames(mat) <- 1:nrow(mat)
        warning("rownames for data not specified, using 1:nrow(data)")
    }
    new("clValid", clusterObjs = clusterObjs, measures = validMeasures, measNames = measures, 
        clMethods = clMethods, labels = rownames(mat), nClust = nClust, validation = validation, 
        metric = metric, method = method, neighbSize = neighbSize, GOcategory = GOcategory, 
        goTermFreq = goTermFreq, annotation = annotation, call = match.call())
}

environment(clValid2) <- asNamespace("clValid")
intern <- clValid2(dat_clipped, nClust = 2:6, clMethods = "hierarchical", validation = "internal", 
    metric = "euclidean", method = "ward.D2")

as.data.frame(intern@measures)["Dunn", ]

3 is the minimum number of clusters with the maximal Dunn index.

stability <- clValid2(dat_clipped, nClust = 2:6, clMethods = "hierarchical", 
    validation = "stability", metric = "euclidean", method = "ward.D2")

pvclust

result <- pvclust(t(dat_clipped), method.dist = "euclidean", method.hclust = "ward.D2", 
    nboot = 100, r = seq(1, 5, 0.1))
Bootstrap (r = 1.0)... Done.
Bootstrap (r = 1.0)... Done.
Bootstrap (r = 1.12)... Done.
Bootstrap (r = 1.25)... Done.
Bootstrap (r = 1.38)... Done.
Bootstrap (r = 1.5)... Done.
Bootstrap (r = 1.5)... Done.
Bootstrap (r = 1.62)... Done.
Bootstrap (r = 1.75)... Done.
Bootstrap (r = 1.88)... Done.
Bootstrap (r = 2)... Done.
Bootstrap (r = 2)... Done.
Bootstrap (r = 2.12)... Done.
Bootstrap (r = 2.25)... Done.
Bootstrap (r = 2.38)... Done.
Bootstrap (r = 2.5)... Done.
Bootstrap (r = 2.5)... Done.
Bootstrap (r = 2.62)... Done.
Bootstrap (r = 2.75)... Done.
Bootstrap (r = 2.88)... Done.
Bootstrap (r = 3)... Done.
Bootstrap (r = 3)... Done.
Bootstrap (r = 3.12)... Done.
Bootstrap (r = 3.25)... Done.
Bootstrap (r = 3.38)... Done.
Bootstrap (r = 3.5)... Done.
Bootstrap (r = 3.5)... Done.
Bootstrap (r = 3.62)... Done.
Bootstrap (r = 3.75)... Done.
Bootstrap (r = 3.88)... Done.
Bootstrap (r = 4)... Done.
Bootstrap (r = 4)... Done.
Bootstrap (r = 4.12)... Done.
Bootstrap (r = 4.25)... Done.
Bootstrap (r = 4.38)... Done.
Bootstrap (r = 4.5)... Done.
Bootstrap (r = 4.5)... Done.
Bootstrap (r = 4.62)... Done.
Bootstrap (r = 4.75)... Done.
Bootstrap (r = 4.88)... Done.
Bootstrap (r = 5)... Done.
plot(result)
pvrect(result, alpha = 0.96)

pvclust seems to have a habit of doing this – it only calls branches that are very long/near the tips as being significant.

So pvclust seems to indicate that all of these clusters are stable.

Caveats:

  • Results change drastically when parameter r is changed, i.e. changing r to seq(0.5, 4.0, 0.1) instead of the default seq(0.5, 1.5, 0.1) gives you significant results for virtually every branching point.
  • The bootstrap method applied by pvclust does not make sense for this type of data (for values of r below 1). pvclust is meant to operate on data like phylogenetic data – where resampling the features of the dataset makes sense (i.e. how bootstrap confidence is defined for phylogenies). On this type of data, pvclust would be most adequately used to compare the significance of FEATURE clustering – where data can be subsampled – rather than OBSERVATION clustering. For example, in this clustergram epithelial CD8+ TIL density contributes greatly to the difference between clusters S-TIL and ES-TIL – excluding it in some resamples does not make sense and is likely the reason for the previous bullet point

The second point isn’t saying that this is invalid – but it isn’t the only way we should assess cluster tightness.

LS0tCnRpdGxlOiAiVElMIGNsdXN0ZXIgcm9idXN0bmVzcyIKLS0tCiAgICAgICAgICAgICAgICAgICAgICAgIGBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKIyMjIyMjIyMgU25ha2VtYWtlIGhlYWRlciAjIyMjIyMjIwpsaWJyYXJ5KG1ldGhvZHMpClNuYWtlbWFrZSA8LSBzZXRDbGFzcygKICAgICJTbmFrZW1ha2UiLAogICAgc2xvdHMgPSBjKAogICAgICAgIGlucHV0ID0gImxpc3QiLAogICAgICAgIG91dHB1dCA9ICJsaXN0IiwKICAgICAgICBwYXJhbXMgPSAibGlzdCIsCiAgICAgICAgd2lsZGNhcmRzID0gImxpc3QiLAogICAgICAgIHRocmVhZHMgPSAibnVtZXJpYyIsCiAgICAgICAgbG9nID0gImxpc3QiLAogICAgICAgIHJlc291cmNlcyA9ICJsaXN0IiwKICAgICAgICBjb25maWcgPSAibGlzdCIsCiAgICAgICAgcnVsZSA9ICJjaGFyYWN0ZXIiCiAgICApCikKc25ha2VtYWtlIDwtIFNuYWtlbWFrZSgKICAgIGlucHV0ID0gbGlzdCgnbm90ZWJvb2tzL3RpbGNsdXN0ZXJfcm9idXN0bmVzcy5SbWQnLCAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaWhjX3RhYmxlLnRzdicsICJub3RlYm9vayIgPSAnbm90ZWJvb2tzL3RpbGNsdXN0ZXJfcm9idXN0bmVzcy5SbWQnLCAiaWhjX3RhYmxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9paGNfdGFibGUudHN2JyksCiAgICBvdXRwdXQgPSBsaXN0KCcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy9yZXZpZXcvbm90ZWJvb2tzL3J1bjIvdGlsY2x1c3Rlcl9yb2J1c3RuZXNzLm5iLmh0bWwnKSwKICAgIHBhcmFtcyA9IGxpc3QoJ3RpbGNsdXN0ZXJfcm9idXN0bmVzc19hbmFseXNpcycsICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvbWV0YWRhdGEvZGIvaW1tdW5lX3Byb2plY3Quc3FsaXRlMycsIGMoJ0VfQ0Q4X2RlbnNpdHknLCAnRV9DRDRfZGVuc2l0eScsICdFX0NEMjBfZGVuc2l0eScsICdFX1BsYXNtYV9kZW5zaXR5JywgJ1NfQ0Q4X2RlbnNpdHknLCAnU19DRDRfZGVuc2l0eScsICdTX0NEMjBfZGVuc2l0eScsICdTX1BsYXNtYV9kZW5zaXR5JyksICJuYW1lIiA9ICd0aWxjbHVzdGVyX3JvYnVzdG5lc3NfYW5hbHlzaXMnLCAiZGIiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9tZXRhZGF0YS9kYi9pbW11bmVfcHJvamVjdC5zcWxpdGUzJywgInRpbHNfZm9yX2NsdXN0ZXIiID0gYygnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSksCiAgICB3aWxkY2FyZHMgPSBsaXN0KCksCiAgICB0aHJlYWRzID0gMSwKICAgIGxvZyA9IGxpc3QoJy9zaGFobGFiL2FsemhhbmcvY2x1c3R0bXAvcGFwZXJyZXZpZXcyL25vdGVib29rcy90aWxjbHVzdGVyX3JvYnVzdG5lc3NfYW5hbHlzaXMubG9nJyksCiAgICByZXNvdXJjZXMgPSBsaXN0KCksCiAgICBjb25maWcgPSBsaXN0KCJiY3JfZGl2ZXJzaXR5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL3BpcGVsaW5lX291dHB1dHMvaXRoX2ltbXVuZS9taXhjci9taXhjcl9ydW5zL2l0aF8xXzJfMy9taXhjcjUvcG9zdHByb2Nlc3MvSUdIL3Bvc3RmaWx0ZXJfZGl2ZXJzaXR5X3N0YXRzL2RpdmVyc2l0eS5zdHJpY3QucmVzYW1wbGVkLnR4dCcsICJicmVha3BvaW50X3RhYmxlIiA9ICcvc2hhaGxhYi9hbWNwaGVyc29uL3Byb2plY3RzL2l0aDMvaXRoMy9ub3RlYm9va3MvYmVzcG9rZS9pdGhfYnJlYWtwb2ludHMudHN2JywgImNsb25lX3RyZWVzIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9jbG9uZXMvdHJlZV9kYXRhLnRzdicsICJ0Y3JfZGl2ZXJzaXR5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL3BpcGVsaW5lX291dHB1dHMvaXRoX2ltbXVuZS9taXhjci9taXhjcl9ydW5zL2l0aF8xXzJfMy9taXhjcjUvcG9zdHByb2Nlc3MvVFJCL3Bvc3RmaWx0ZXJfZGl2ZXJzaXR5X3N0YXRzL2RpdmVyc2l0eS5zdHJpY3QucmVzYW1wbGVkLnR4dCcsICJoZV9yZXN1bHRzX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2l0aGkvZmlubl9yZXN1bHRzL2hlX291dHB1dF9Ob3YyOScsICJzb21hdGljX2NvZGluZ19yZXN1bHRfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9zb21hdGljX2NvZGluZ192YXJpYW50cycsICJkaXN0YW5jZV9tZXRob2QiID0gJ2hvcm4nLCAiY29weW51bWJlcl90YWJsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2l0aGkvbWFzdGVyX2NvcHludW1iZXJfZmlsZS50c3YnLCAiaXRoX3N0YXRzIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9pdGhfc3RhdGlzdGljcy50c3YnLCAidGlsY2x1c3Rlcl9zdXBlcnZpc2VkX2lweW5iIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmV2aWV3L2lweS90aWxjbHVzdGVyX3N1cGVydmlzZWRtdWx0aWNsYXNzLmlweW5iJywgImVwaXRvcGVzX3VuaXF1ZV9maWx0ZXJlZCIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvZXBpdG9wZXNfdW5pcXVlX2ZpbHRlcmVkLnRzdicsICJ0aWxfY2x1c3RlcnNfb3V0cHV0IiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy9pbnRlcm1lZGlhdGVzL3J1bjIvdGlsX2NsdXN0ZXJzX291dHB1dC50eHQnLCAicmVtaXh0X2NlbGx1bGFyaXR5X3Bsb2lkeSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvcmVtaXh0X2NlbGx1bGFyaXR5X3Bsb2lkeS50c3YnLCAidGFibGVfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy9yZXZpZXcvdGFibGVzL3J1bjInLCAidmFyaWFiaWxpdHlfdHlwZSIgPSAnc3RhYmlsaXplJywgIml0aF9pY2djX2JjIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9pdGhfaWNnY19tZXJnZWRfYmMudHN2JywgImFycmF5X2V4cHJlc3Npb25fZmlsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9hcnJheS9nZW5lX2V4cHJzX3JtYV9iYXRjaF9jb3JyZWN0ZWQudHh0JywgIm5hbm9zdHJpbmdfZGF0YSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3Jlc3VsdHMvbmFub3N0cmluZ19yZXN1bHRzL2l0aF9mdWxsL3FjL2xpbW1hX3F1YW50aWxlL25vcm1hbGl6ZWRfZXhwcmVzc2lvbl92b2FfbGFiZWxzX2ZpbHRlcmVkLnRzdicsICJtb2xzdWJ0eXBlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvbW9sc3VidHlwZXMudHN2JywgImxvZ2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9jbHVzdHRtcC9wYXBlcnJldmlldzInLCAiaWNnY19zdWJ0eXBlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL0lDR0MvaWNnY19wcmltYXJ5X3R1bW91cl9zdWJ0eXBlcy50c3YnLCAidG90YWxfdGlsdHlwZXMiID0gYygnVF9DRDhfZGVuc2l0eScsICdUX0NENF9kZW5zaXR5JywgJ1RfQ0QyMF9kZW5zaXR5JywgJ1RfUGxhc21hX2RlbnNpdHknKSwgInRpbHNfZm9yX3ZhcmlhYmlsaXR5IiA9IGMoJ1RfQ0Q4X2RlbnNpdHknLCAnVF9DRDRfZGVuc2l0eScsICdUX0NEMjBfZGVuc2l0eScsICdUX1BsYXNtYV9kZW5zaXR5JyksICJ0dW1vdXJfcHVyaXR5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi90dW1vdXJfcHVyaXR5LnRzdicsICJjbG9sYV9yZXN1bHRfZmlsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvY2xvbGEvcnVuNC9jbG9sYV9jb25kZW5zZWRfcmVzdWx0cy9iZXRhL2Nsb2xhX3Jlc3VsdHMudHN2JywgInRpbHNfZm9yX2NsdXN0ZXIiID0gYygnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSwgImNsb25lX2JyYW5jaF9sZW5ndGhzIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9jbG9uZXMvYnJhbmNoX2RhdGEudHN2JywgInBhdGllbnRzX2Zvcl9jbG9uYWwiID0gYygxLCAyLCAzLCA0LCA3LCA5LCAxMCwgMTEsIDEyLCAxMywgMTQsIDE1LCAxNiwgMTcpLCAibm90ZWJvb2tfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy9yZXZpZXcvbm90ZWJvb2tzL3J1bjInLCAiaWhjX2ZlYXR1cmVzX291dHB1dCIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvaW50ZXJtZWRpYXRlcy9ydW4yL2loY19mZWF0dXJlc19vdXRwdXQudHh0JywgImloY190YWJsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaWhjX3RhYmxlLnRzdicsICJmaW5uaGVfcGlwZWxpbmVfcmVzdWx0c19kaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL2Zpbm5oZS9ydW4xJywgInJlZnNlcV9nZW5lX2ZpbGUiID0gJy9zaGFobGFiL2FsemhhbmcvZGF0YS9nZW5vbWUvaGcxOS9yZWZzZXFfZ2VuZXMuYmVkJywgImNsb25lX3ByZXZhbGVuY2VzIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9jbG9uZXMvY2xvbmVfZGF0YS50c3YnLCAibW1jdG1fZmluYWxfcGF0aWVudF9kaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9yZXN1bHRzL21tY3RtX3Jlc3VsdHMvaXRoX2J5LXBhdGllbnRfd2l0aC1vdicsICJiZW5jaG1hcmtkaXIiID0gJy9zaGFobGFiL2FsemhhbmcvYmVuY2htYXJrcy9wYXBlcnJldmlldzInLCAieGNyX3RhYmxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi94Y3JfdGFibGUudHN2JywgIml0aF9zdGF0X3R5cGVzIiA9IGMoJ2VudHJvcHknLCAncG9zdHByb2Nlc3NlZF9kaXZlcmdlbmNlJywgJ2NvbWJpbmVkX2l0aF9ub3JtYWxpemVkJywgJ3Byb3BvcnRpb25fc3ViY2xvbmFsJyksICJuZW9lZGl0aW5nX291dGRpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvbmVvZWRpdGluZy9ydW42JywgInNudl90YWJsZSIgPSAnL3NoYWhsYWIvYW1jcGhlcnNvbi9wcm9qZWN0cy9pdGgzL2l0aDMvbm90ZWJvb2tzL2Jlc3Bva2UvaXRoX3NudnMudHN2JywgInJvb25leV9tdXRzaWdjdl9maWxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvZXh0ZXJuYWwvb3RoZXJfcGFwZXJzL21tYzYueGxzeCcsICJzbnZfY2x1c3Rlcl9kaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2Nsb25lcy9zbnZfY2x1c3RlcicsICJpbWFnZV9zdW1tYXJ5MiIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2l0aGkveXVhbl9oZWNyX2ltYWdlX3Jlc3VsdHNfMi5jc3YnLCAiaW1hZ2Vfc3VtbWFyeSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2l0aGkveXVhbl9oZWNyX2ltYWdlX3Jlc3VsdHMuY3N2JywgIm5hbm9zdHJpbmdfYW5ub3RhdGlvbnMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9kYXRhL2V4cHJlc3Npb24vbmFub3N0cmluZy9wYW5jYW5jZXJfYW5ub3RhdGlvbnMudHN2JywgImRiIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvbWV0YWRhdGEvZGIvaW1tdW5lX3Byb2plY3Quc3FsaXRlMycsICJwcmV2YWxlbmNlX3RocmVzaG9sZCIgPSAwLjAxLCAiaWNnY19zcGVjaW1lbiIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL0lDR0Mvc3BlY2ltZW4udHN2JywgImtub3duX3N1YnR5cGVzX2FycmF5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvZGF0YS9leHByZXNzaW9uL2FycmF5L3N1YnR5cGVzL2tub3duX3N1YnR5cGVzLnRzdicsICJhbGxfdGlsdHlwZXMiID0gYygnVF9DRDhfZGVuc2l0eScsICdUX0NENF9kZW5zaXR5JywgJ1RfQ0QyMF9kZW5zaXR5JywgJ1RfUGxhc21hX2RlbnNpdHknLCAnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSwgImlncGFydGl0aW9uX291dGRpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvaWdwYXJ0aXRpb24vcnVuMjInKSwKICAgIHJ1bGUgPSAndGlsY2x1c3Rlcl9yb2J1c3RuZXNzX2FuYWx5c2lzJwopCiMjIyMjIyMjIE9yaWdpbmFsIHNjcmlwdCAjIyMjIyMjIyMKCiAgICAgICAgICAgICAgICAgICAgICAgIGBgYAoKCiMjIFNldHVwCgpgYGB7ciBnbG9iYWxfY2h1bmtfb3B0aW9ucywgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB0aWR5PVRSVUUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPVRSVUUpICNjYWNoZT1UUlVFCmBgYAoKYGBge3J9CmxpYnJhcnkoaXRoaS51dGlscykKbG9hZF9iYXNlX2xpYnMoKQoKbGlicmFyeShtZXRob2RzKQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmxpYnJhcnkoTmJDbHVzdCkKbGlicmFyeShwdmNsdXN0KQpsaWJyYXJ5KGNsVmFsaWQpCgpsaWJyYXJ5KGl0aGkubWV0YSkKbGlicmFyeShpdGhpLmZpZ3VyZXMpCmxpYnJhcnkoaXRoaS51dGlscykKYGBgCgpgYGB7cn0KaWhjX3RhYmxlX3BhdGggPC0gc25ha2VtYWtlQGlucHV0JGloY190YWJsZQoKdGlsc19mb3JfY2x1c3RlciA8LSBzbmFrZW1ha2VAcGFyYW1zJHRpbHNfZm9yX2NsdXN0ZXIKZGJfcGF0aCA8LSBzbmFrZW1ha2VAcGFyYW1zJGRiCmBgYAoKYGBge3J9CmFubm90YXRpb25fY29sb3VycyA8LSBpdGhpLmZpZ3VyZXM6OmdldF9hbm5vdGF0aW9uX2NvbG91cnMoKQppaGNfdGFibGUgPC0gZnJlYWQoaWhjX3RhYmxlX3BhdGgpCmBgYAoKCiMjIFN1bW1hcnkKCkhlcmUgd2UnbGwgYXNzZXNzIHRoZSByb2J1c3RuZXNzIG9mIG91ciBUSUwgY2x1c3RlcmluZy4gCgpJbiB0ZXJtcyBvZiB1bmRlcnN0YW5kaW5nIHJvYnVzdG5lc3MsIHRoZXJlIGFyZSAzIG1ham9yIGFzcGVjdHM6CgoqIEludGVybmFsIHZhbGlkaXR5CiogRXh0ZXJuYWwgdmFsaWRpdHkKKiBJbmRpcmVjdCB2YWxpZGl0eQoKU2luY2Ugd2UgZG9uJ3QgaGF2ZSBhIGdvbGQgc3RhbmRhcmQgdG8gY29tcGFyZSB0bywgZXh0ZXJuYWwgdmFsaWRpdHkgaXMgb3V0IG9mIHRoZSBxdWVzdGlvbi4gCgpJbnRlcm5hbCB2YWxpZGl0eSByZWZlcnMgdG8gbWVhc3VyZXMgdGhhdCBtZWFzdXJlIGludHJhLWNsdXN0ZXIgY29ubmVjdGVkbmVzcy90aWdodG5lc3MsIGJldHdlZW4tY2x1c3RlciBkaXN0YW5jZSwgYW5kIHN0YWJpbGl0eS4gVGhpcyBpbmNsdWRlcyBpbmRpY2VzIGxpa2UgdGhlIER1bm4gaW5kZXggKER1bm4gMTk3NCkuIFN0YWJpbGl0eSByZWZlcnMgdG8gbWVhc3VyZXMgdGhhdCBkZXRlcm1pbmUgdGhlIGNvbnNpc3RlbmN5IG9mIGNsdXN0ZXJpbmcgYmFzZWQgb24gc3Vic2V0cyBvZiBkYXRhIG9yIGZlYXR1cmVzLiBwdmNsdXN0IGNhbiBiZSB1c2VkIHRvIGFzc2VzcyB0aGUgc3RhYmlsaXR5IG9mIGNsdXN0ZXJpbmcgYnkgcmVzYW1wbGluZyBmZWF0dXJlcy4gCgpJbmRpcmVjdCB2YWxpZGl0eSByZWZlcnMgdG8gdGhlIChlLmcuIGJpb2xvZ2ljYWwpIHJlbGV2YW5jZSBvZiB0aGUgcmVzdWx0aW5nIGNsdXN0ZXJzLiBCYXNlZCBvbiB0aGUgcmVzdWx0cyBvZiBJVEggY29tcGFyaXNvbiwgd2UgaGF2ZSBldmlkZW5jZSB0byBzdWdnZXN0IHRoYXQgdGhlIFMtVElML0VTLVRJTCBzcGxpdCBpcyBiaW9sb2dpY2FsbHkgcmVsZXZhbnQuIAoKIyMgQW5hbHlzaXMKCmBgYHtyfQppaGNfdGFibGVfc3Vic2V0IDwtIHN1YnNldChpaGNfdGFibGUsIHNlbGVjdCA9IGMoImNvbmRlbnNlZF9pZCIsIHRpbHNfZm9yX2NsdXN0ZXIpKQppaGNfdGFibGVfc3Vic2V0X251bWVyaWMgPC0gaWhjX3RhYmxlX3N1YnNldCAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKHZhciA9ICJjb25kZW5zZWRfaWQiKSAlPiUgc2NhbGUKCm5hX3Jvd3MgPC0gYXBwbHkoaWhjX3RhYmxlX3N1YnNldF9udW1lcmljLCAxLCBmdW5jdGlvbih4KSBhbnkoaXMubmEoeCkpKQppaGNfdGFibGVfc3Vic2V0X251bWVyaWMgPC0gaWhjX3RhYmxlX3N1YnNldF9udW1lcmljWyFuYV9yb3dzLF0KCmRhdF9jbGlwcGVkIDwtIGl0aGkudXRpbHM6OmNsaXBfdmFsdWVzKGloY190YWJsZV9zdWJzZXRfbnVtZXJpYywgaGkgPSAyLCBsbyA9IC0yKQpgYGAKCmBgYHtyfQpjbHVzdGVyX3JlcyA8LSBmYWN0b2V4dHJhOjpmdml6X25iY2x1c3QoZGF0X2NsaXBwZWQsIEZVTmNsdXN0ZXIgPSBoY3V0LCBtZXRob2QgPSBjKCJ3c3MiKSwgZGlzcyA9IE5VTEwsIGsubWF4ID0gMTApCgpuYmNsdXN0X3JlcyA8LSBOYkNsdXN0KGRhdF9jbGlwcGVkLCBkaXN0YW5jZSA9ICJldWNsaWRlYW4iLCBtaW4ubmMgPSAyLCBtYXgubmMgPSA3LCBtZXRob2QgPSAid2FyZC5EMiIsIGFscGhhQmVhbGUgPSAwLCBpbmRleCA9ICJhbGwiKQoKZnZpel9uYmNsdXN0KG5iY2x1c3RfcmVzKQpgYGAKCgoyIGFuZCAzIGNsdXN0ZXJzIGFyZSBib3RoIGVxdWFsbHkgd2VsbCBzdXBwb3J0ZWQgYnkgdGhpcyBiYXJyYWdlIG9mIG1ldGhvZHMuIAoKVGhlIER1bm4gaW5kZXgsIHdoaWNoIGlzIHZlcnkgd2VsbCBjaXRlZCwgcmVwb3J0cyAzIGFzIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycy4gCgojIyBjbFZhbGlkCgpXZSdsbCBsb29rIGF0IHRoZSBEdW5uIGluZGV4IGFnYWluIHdpdGggdGhpcyBvdGhlciBwYWNrYWdlLiAKCgpgYGB7cn0KY2xWYWxpZDIgPC0gZnVuY3Rpb24gKG9iaiwgbkNsdXN0LCBjbE1ldGhvZHMgPSAiaGllcmFyY2hpY2FsIiwgdmFsaWRhdGlvbiA9ICJzdGFiaWxpdHkiLCAKICAgIG1heGl0ZW1zID0gNjAwLCBtZXRyaWMgPSAiZXVjbGlkZWFuIiwgbWV0aG9kID0gImF2ZXJhZ2UiLCAKICAgIG5laWdoYlNpemUgPSAxMCwgYW5ub3RhdGlvbiA9IE5VTEwsIEdPY2F0ZWdvcnkgPSAiYWxsIiwgZ29UZXJtRnJlcSA9IDAuMDUsIAogICAgZHJvcEV2aWRlbmNlID0gTlVMTCwgdmVyYm9zZSA9IEZBTFNFLCAuLi4pIAp7CiAgICBjbE1ldGhvZHMgPC0gdG9sb3dlcihjbE1ldGhvZHMpCiAgICBjbE1ldGhvZHMgPC0gbWF0Y2guYXJnKGNsTWV0aG9kcywgYygiaGllcmFyY2hpY2FsIiwgImttZWFucyIsIAogICAgICAgICJkaWFuYSIsICJmYW5ueSIsICJzb20iLCAibW9kZWwiLCAic290YSIsICJwYW0iLCAiY2xhcmEiLCAKICAgICAgICAiYWduZXMiKSwgc2V2ZXJhbC5vayA9IFRSVUUpCiAgICBpZiAoInNvbSIgJWluJSBjbE1ldGhvZHMpIHsKICAgICAgICBpZiAoIXJlcXVpcmUoa29ob25lbikpIHsKICAgICAgICAgICAgc3RvcCgicGFja2FnZSAna29ob25lbicgcmVxdWlyZWQgZm9yIGNsdXN0ZXJpbmcgdXNpbmcgU09NIikKICAgICAgICB9CiAgICB9CiAgICBpZiAoIm1vZGVsIiAlaW4lIGNsTWV0aG9kcykgewogICAgICAgIGlmICghcmVxdWlyZShtY2x1c3QpKSB7CiAgICAgICAgICAgIHN0b3AoInBhY2thZ2UgJ21jbHVzdCcgcmVxdWlyZWQgZm9yIG1vZGVsLWJhc2VkIGNsdXN0ZXJpbmciKQogICAgICAgIH0KICAgIH0KICAgIHZhbGlkYXRpb24gPC0gbWF0Y2guYXJnKHZhbGlkYXRpb24sIGMoInN0YWJpbGl0eSIsICJpbnRlcm5hbCIsIAogICAgICAgICJiaW9sb2dpY2FsIiksIHNldmVyYWwub2sgPSBUUlVFKQogICAgbWV0cmljIDwtIG1hdGNoLmFyZyhtZXRyaWMsIGMoImV1Y2xpZGVhbiIsICJjb3JyZWxhdGlvbiIsIAogICAgICAgICJtYW5oYXR0YW4iKSkKICAgIG1ldGhvZCA8LSBtYXRjaC5hcmcobWV0aG9kLCBjKCJ3YXJkIiwgIndhcmQuRDIiLCAic2luZ2xlIiwgImNvbXBsZXRlIiwgCiAgICAgICAgImF2ZXJhZ2UiKSkKICAgIEdPY2F0ZWdvcnkgPC0gbWF0Y2guYXJnKEdPY2F0ZWdvcnksIGMoImFsbCIsICJCUCIsICJDQyIsIAogICAgICAgICJNRiIpKQogICAgc3dpdGNoKGNsYXNzKG9iaiksIG1hdHJpeCA9IG1hdCA8LSBvYmosIEV4cHJlc3Npb25TZXQgPSBtYXQgPC0gQmlvYmFzZTo6ZXhwcnMob2JqKSwgCiAgICAgICAgZGF0YS5mcmFtZSA9IHsKICAgICAgICAgICAgaWYgKGFueSghc2FwcGx5KG9iaiwgY2xhc3MpICVpbiUgYygibnVtZXJpYyIsICJpbnRlZ2VyIikpKSBzdG9wKCJkYXRhIGZyYW1lICdvYmonIGNvbnRhaW5zIG5vbi1udW1lcmljIGRhdGEiKQogICAgICAgICAgICBtYXQgPC0gYXMubWF0cml4KG9iaikKICAgICAgICB9LCBzdG9wKCJhcmd1bWVudCAnb2JqJyBtdXN0IGJlIGEgbWF0cml4LCBkYXRhLmZyYW1lLCBvciBFeHByZXNzaW9uU2V0IG9iamVjdCIpKQogICAgaWYgKG5yb3cobWF0KSA+IG1heGl0ZW1zKSB7CiAgICAgICAgaWYgKGludGVyYWN0aXZlKCkpIHsKICAgICAgICAgICAgY2F0KCJcblRoZSBudW1iZXIgb2YgaXRlbXMgdG8gYmUgY2x1c3RlcmVkIGlzIGxhcmdlciB0aGFuICdtYXhpdGVtcydcbiIpCiAgICAgICAgICAgIGNhdCgiVGhlIG1lbW9yeSBhbmQgdGltZSByZXF1aXJlZCBtYXkgYmUgZXhjZXNzaXZlLCBkbyB5b3Ugd2lzaCB0byBjb250aW51ZT9cbiIpCiAgICAgICAgICAgIGNhdCgiKHkgdG8gY29udGludWUsIGFueSBvdGhlciBjaGFyYWN0ZXIgdG8gZXhpdCkgIikKICAgICAgICAgICAgYW5zIDwtIHRvbG93ZXIoc3Vic3RyKHJlYWRMaW5lcyhuID0gMSksIDEsIDEpKQogICAgICAgICAgICBpZiAoYW5zICE9ICJ5IikgewogICAgICAgICAgICAgICAgc3RvcCgiRXhpdGluZyBjbFZhbGlkLCBudW1iZXIgb2YgaXRlbXMgZXhjZWVkcyAnbWF4aXRlbXMnIikKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBlbHNlIHsKICAgICAgICAgICAgc3RvcCgiVGhlIG51bWJlciBvZiBpdGVtcyB0byBiZSBjbHVzdGVyZWQgaXMgbGFyZ2VyIHRoYW4gJ21heGl0ZW1zJ1xuICBFaXRoZXIgZGVjcmVhc2UgdGhlIG51bWJlciBvZiByb3dzIChpdGVtcykgb3IgaW5jcmVhc2UgJ21heGl0ZW1zJ1xuIikKICAgICAgICB9CiAgICB9CiAgICBpZiAoImNsYXJhIiAlaW4lIGNsTWV0aG9kcyAmIG1ldHJpYyA9PSAiY29ycmVsYXRpb24iKSAKICAgICAgICB3YXJuaW5nKCInY2xhcmEnIGN1cnJlbnRseSBvbmx5IHdvcmtzIHdpdGggJ2V1Y2xpZGVhbicgb3IgJ21hbmhhdHRhbicgbWV0cmljcyAtIG1ldHJpYyB3aWxsIGJlIGNoYW5nZWQgdG8gJ2V1Y2xpZGVhbicgICIpCiAgICBpZiAoaXMuY2hhcmFjdGVyKGFubm90YXRpb24pICYgbGVuZ3RoKGdyZXAoIi5kYiIsIGFubm90YXRpb24pKSA9PSAKICAgICAgICAwKSB7CiAgICAgICAgYW5ub3RhdGlvbiA8LSBwYXN0ZShhbm5vdGF0aW9uLCAiLmRiIiwgc2VwID0gIiIpCiAgICB9CiAgICBpZiAoaXMubGlzdChhbm5vdGF0aW9uKSkgewogICAgICAgIGlmIChpcy5udWxsKHJvd25hbWVzKG1hdCkpKSB7CiAgICAgICAgICAgIHN0b3AoInJvd25hbWVzIG9mIGRhdGEgbXVzdCBiZSBwcmVzZW50IHRvIHNwZWNpZnkgYmlvbG9naWNhbCBhbm5vdGF0aW9uIGZyb20gZmlsZSIpCiAgICAgICAgfQogICAgICAgIGFubm90YXRpb24gPC0gYW5ub3RhdGlvbkxpc3RUb01hdHJpeChhbm5vdGF0aW9uLCBnZW5lbmFtZXMgPSByb3duYW1lcyhtYXQpKQogICAgfQogICAgaWYgKCJiaW9sb2dpY2FsIiAlaW4lIHZhbGlkYXRpb24gJiBpcy5udWxsKGFubm90YXRpb24pKSB7CiAgICAgICAgc3RvcCgiYW5ub3RhdGlvbiBtdXN0IGJlIHNwZWNpZmllZCBpbiBvcmRlciB0byB1c2UgYmlvbG9naWNhbCB2YWxpZGF0aW9uIikKICAgIH0KICAgIGlmICgiYmlvbG9naWNhbCIgJWluJSB2YWxpZGF0aW9uICYgaXMuY2hhcmFjdGVyKGFubm90YXRpb24pKSB7CiAgICAgICAgaWYgKCFyZXF1aXJlKEJpb2Jhc2UpIHwgIXJlcXVpcmUoR08uZGIpIHwgIXJlcXVpcmUoYW5ub3RhdGUpKSB7CiAgICAgICAgICAgIHN0b3AoInBhY2thZ2VzICdCaW9iYXNlJywgJ0dPLmRiJywgYW5kICdhbm5vdGF0ZScgcmVxdWlyZWQgZm9yIDJuZCB0eXBlIG9mIGJpb2xvZ2ljYWwgdmFsaWRhdGlvbiBcblxudGhlc2UgY2FuIGJlIGRvd25sb2FkZWQgZnJvbSBCaW9jb25kdWN0b3IgKHd3dy5iaW9jb25kdWN0b3Iub3JnKSIpCiAgICAgICAgfQogICAgfQogICAgaWYgKCJiaW9sb2dpY2FsIiAlaW4lIHZhbGlkYXRpb24gJiBpcy5udWxsKHJvd25hbWVzKG1hdCkpKSB7CiAgICAgICAgc3RvcCgicm93bmFtZXMgb2YgZGF0YSBtdXN0IGJlIHByZXNlbnQgdG8gdXNlIGJpb2xvZ2ljYWwgdmFsaWRhdGlvbiIpCiAgICB9CiAgICBuQ2x1c3QgPC0gZmxvb3IobkNsdXN0KQogICAgaWYgKGFueShuQ2x1c3QgPCAxKSkgCiAgICAgICAgc3RvcCgiYXJndW1lbnQgJ25DbHVzdCcgbXVzdCBiZSBhIHBvc2l0aXZlIGludGVnZXIgdmVjdG9yIikKICAgIGlmIChtZXRyaWMgPT0gImNvcnJlbGF0aW9uIikgCiAgICAgICAgRGlzdCA8LSBhcy5kaXN0KDEgLSBjb3IodChtYXQpLCB1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIikpCiAgICBlbHNlIERpc3QgPC0gZGlzdChtYXQsIG1ldGhvZCA9IG1ldHJpYykKICAgIGNsdXN0ZXJPYmpzIDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aChjbE1ldGhvZHMpKQogICAgbmFtZXMoY2x1c3Rlck9ianMpIDwtIGNsTWV0aG9kcwogICAgbWVhc3VyZXMgPC0gYyhpZiAoInN0YWJpbGl0eSIgJWluJSB2YWxpZGF0aW9uKSBjKCJBUE4iLCAiQUQiLCAKICAgICAgICAiQURNIiwgIkZPTSIpLCBpZiAoImludGVybmFsIiAlaW4lIHZhbGlkYXRpb24pIGMoIkNvbm5lY3Rpdml0eSIsIAogICAgICAgICJEdW5uIiwgIlNpbGhvdWV0dGUiKSwgaWYgKCJiaW9sb2dpY2FsIiAlaW4lIHZhbGlkYXRpb24pIGMoIkJISSIsIAogICAgICAgICJCU0kiKSkKICAgIHZhbGlkTWVhc3VyZXMgPC0gYXJyYXkoZGltID0gYyhsZW5ndGgobWVhc3VyZXMpLCBsZW5ndGgobkNsdXN0KSwgCiAgICAgICAgbGVuZ3RoKGNsTWV0aG9kcykpKQogICAgZGltbmFtZXModmFsaWRNZWFzdXJlcykgPC0gbGlzdChtZWFzdXJlcywgbkNsdXN0LCBjbE1ldGhvZHMpCiAgICBmb3IgKGkgaW4gMTpsZW5ndGgoY2xNZXRob2RzKSkgewogICAgICAgIGN2YWxpZCA8LSB2Q2x1c3RlcnMobWF0LCBjbE1ldGhvZHNbaV0sIG5DbHVzdCwgdmFsaWRhdGlvbiA9IHZhbGlkYXRpb24sIAogICAgICAgICAgICBEaXN0ID0gRGlzdCwgbWV0aG9kID0gbWV0aG9kLCBtZXRyaWMgPSBtZXRyaWMsIGFubm90YXRpb24gPSBhbm5vdGF0aW9uLCAKICAgICAgICAgICAgR09jYXRlZ29yeSA9IEdPY2F0ZWdvcnksIGdvVGVybUZyZXEgPSBnb1Rlcm1GcmVxLCAKICAgICAgICAgICAgbmVpZ2hiU2l6ZSA9IG5laWdoYlNpemUsIGRyb3BFdmlkZW5jZSA9IGRyb3BFdmlkZW5jZSwgCiAgICAgICAgICAgIHZlcmJvc2UgPSB2ZXJib3NlLCAuLi4pCiAgICAgICAgY2x1c3Rlck9ianNbW2ldXSA8LSBjdmFsaWQkY2x1c3Rlck9iagogICAgICAgIHZhbGlkTWVhc3VyZXNbLCAsIGldIDwtIGN2YWxpZCRtZWFzdXJlcwogICAgfQogICAgaWYgKGlzLm51bGwocm93bmFtZXMobWF0KSkpIHsKICAgICAgICByb3duYW1lcyhtYXQpIDwtIDE6bnJvdyhtYXQpCiAgICAgICAgd2FybmluZygicm93bmFtZXMgZm9yIGRhdGEgbm90IHNwZWNpZmllZCwgdXNpbmcgMTpucm93KGRhdGEpIikKICAgIH0KICAgIG5ldygiY2xWYWxpZCIsIGNsdXN0ZXJPYmpzID0gY2x1c3Rlck9ianMsIG1lYXN1cmVzID0gdmFsaWRNZWFzdXJlcywgCiAgICAgICAgbWVhc05hbWVzID0gbWVhc3VyZXMsIGNsTWV0aG9kcyA9IGNsTWV0aG9kcywgbGFiZWxzID0gcm93bmFtZXMobWF0KSwgCiAgICAgICAgbkNsdXN0ID0gbkNsdXN0LCB2YWxpZGF0aW9uID0gdmFsaWRhdGlvbiwgbWV0cmljID0gbWV0cmljLCAKICAgICAgICBtZXRob2QgPSBtZXRob2QsIG5laWdoYlNpemUgPSBuZWlnaGJTaXplLCBHT2NhdGVnb3J5ID0gR09jYXRlZ29yeSwgCiAgICAgICAgZ29UZXJtRnJlcSA9IGdvVGVybUZyZXEsIGFubm90YXRpb24gPSBhbm5vdGF0aW9uLCBjYWxsID0gbWF0Y2guY2FsbCgpKQp9CgplbnZpcm9ubWVudChjbFZhbGlkMikgPC0gYXNOYW1lc3BhY2UoJ2NsVmFsaWQnKQpgYGAKCmBgYHtyfQppbnRlcm4gPC0gY2xWYWxpZDIoZGF0X2NsaXBwZWQsIG5DbHVzdCA9IDI6NiwgY2xNZXRob2RzID0gImhpZXJhcmNoaWNhbCIsIHZhbGlkYXRpb24gPSAiaW50ZXJuYWwiLCBtZXRyaWMgPSAiZXVjbGlkZWFuIiwgbWV0aG9kID0gIndhcmQuRDIiKQoKYXMuZGF0YS5mcmFtZShpbnRlcm5AbWVhc3VyZXMpWydEdW5uJyxdCmBgYAoKMyBpcyB0aGUgbWluaW11bSBudW1iZXIgb2YgY2x1c3RlcnMgd2l0aCB0aGUgbWF4aW1hbCBEdW5uIGluZGV4LiAKCmBgYHtyfQpzdGFiaWxpdHkgPC0gY2xWYWxpZDIoZGF0X2NsaXBwZWQsIG5DbHVzdCA9IDI6NiwgY2xNZXRob2RzID0gImhpZXJhcmNoaWNhbCIsIHZhbGlkYXRpb24gPSAic3RhYmlsaXR5IiwgbWV0cmljID0gImV1Y2xpZGVhbiIsIG1ldGhvZCA9ICJ3YXJkLkQyIikKYGBgCgojIyBwdmNsdXN0CgpgYGB7cn0KcmVzdWx0IDwtIHB2Y2x1c3QodChkYXRfY2xpcHBlZCksIG1ldGhvZC5kaXN0ID0gImV1Y2xpZGVhbiIsIG1ldGhvZC5oY2x1c3QgPSAid2FyZC5EMiIsIG5ib290ID0gMTAwLCByID0gc2VxKDEuMCwgNS4wLCAwLjEpKQpgYGAKCgpgYGB7cn0KcGxvdChyZXN1bHQpCnB2cmVjdChyZXN1bHQsIGFscGhhID0gMC45NikKYGBgCgpwdmNsdXN0IHNlZW1zIHRvIGhhdmUgYSBoYWJpdCBvZiBkb2luZyB0aGlzIC0tIGl0IG9ubHkgY2FsbHMgYnJhbmNoZXMgdGhhdCBhcmUgdmVyeSBsb25nL25lYXIgdGhlIHRpcHMgYXMgYmVpbmcgc2lnbmlmaWNhbnQuIAoKU28gcHZjbHVzdCBzZWVtcyB0byBpbmRpY2F0ZSB0aGF0IGFsbCBvZiB0aGVzZSBjbHVzdGVycyBhcmUgc3RhYmxlLiAKCgpDYXZlYXRzOgoKKiBSZXN1bHRzIGNoYW5nZSBkcmFzdGljYWxseSB3aGVuIHBhcmFtZXRlciByIGlzIGNoYW5nZWQsIGkuZS4gY2hhbmdpbmcgciB0byBzZXEoMC41LCA0LjAsIDAuMSkgaW5zdGVhZCBvZiB0aGUgZGVmYXVsdCBzZXEoMC41LCAxLjUsIDAuMSkgZ2l2ZXMgeW91IHNpZ25pZmljYW50IHJlc3VsdHMgZm9yIHZpcnR1YWxseSBldmVyeSBicmFuY2hpbmcgcG9pbnQuIAoqIFRoZSBib290c3RyYXAgbWV0aG9kIGFwcGxpZWQgYnkgcHZjbHVzdCBkb2VzIG5vdCBtYWtlIHNlbnNlIGZvciB0aGlzIHR5cGUgb2YgZGF0YSAoZm9yIHZhbHVlcyBvZiByIGJlbG93IDEpLiBwdmNsdXN0IGlzIG1lYW50IHRvIG9wZXJhdGUgb24gZGF0YSBsaWtlIHBoeWxvZ2VuZXRpYyBkYXRhIC0tIHdoZXJlIHJlc2FtcGxpbmcgdGhlIGZlYXR1cmVzIG9mIHRoZSBkYXRhc2V0IG1ha2VzIHNlbnNlIChpLmUuIGhvdyBib290c3RyYXAgY29uZmlkZW5jZSBpcyBkZWZpbmVkIGZvciBwaHlsb2dlbmllcykuIE9uIHRoaXMgdHlwZSBvZiBkYXRhLCBwdmNsdXN0IHdvdWxkIGJlIG1vc3QgYWRlcXVhdGVseSB1c2VkIHRvIGNvbXBhcmUgdGhlIHNpZ25pZmljYW5jZSBvZiBGRUFUVVJFIGNsdXN0ZXJpbmcgLS0gd2hlcmUgZGF0YSBjYW4gYmUgc3Vic2FtcGxlZCAtLSByYXRoZXIgdGhhbiBPQlNFUlZBVElPTiBjbHVzdGVyaW5nLiBGb3IgZXhhbXBsZSwgaW4gdGhpcyBjbHVzdGVyZ3JhbSBlcGl0aGVsaWFsIENEOCsgVElMIGRlbnNpdHkgY29udHJpYnV0ZXMgZ3JlYXRseSB0byB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGNsdXN0ZXJzIFMtVElMIGFuZCBFUy1USUwgLS0gZXhjbHVkaW5nIGl0IGluIHNvbWUgcmVzYW1wbGVzIGRvZXMgbm90IG1ha2Ugc2Vuc2UgYW5kIGlzIGxpa2VseSB0aGUgcmVhc29uIGZvciB0aGUgcHJldmlvdXMgYnVsbGV0IHBvaW50CgpUaGUgc2Vjb25kIHBvaW50IGlzbid0IHNheWluZyB0aGF0IHRoaXMgaXMgaW52YWxpZCAtLSBidXQgaXQgaXNuJ3QgdGhlIG9ubHkgd2F5IHdlIHNob3VsZCBhc3Nlc3MgY2x1c3RlciB0aWdodG5lc3MuIAoKCg==