I. Preliminaries

Loading libraries

library("tidyverse")
library("tibble")
library("msigdbr")
library("ggplot2")
library("TCGAbiolinks")
library("RNAseqQC")
library("DESeq2")
library("ensembldb")
library("purrr")
library("magrittr")
library("vsn")
library("matrixStats")
library("dplyr")
library("grex")
library("biomaRt")

II. Downloading TCGA miRNA expression data

Download miRNA expression data from The Cancer Genome Atlas (TCGA): - TCGA-COAD refers to the biospecimen data for colon adenocarcinoma.

query_tumor <- GDCquery(
  project = "TCGA-COAD",
  data.category = "Transcriptome Profiling",
  data.type = "miRNA Expression Quantification",
  experimental.strategy = "miRNA-Seq",
  access = "open",
  sample.type = "Primary Tumor"
)
tumor <- getResults(query_tumor)
tumor
query_normal <- GDCquery(
  project = "TCGA-COAD",
  data.category = "Transcriptome Profiling",
  data.type = "miRNA Expression Quantification",
  experimental.strategy = "miRNA-Seq",
  access = "open",
  sample.type = "Solid Tissue Normal"
)
normal <- getResults(query_normal)
normal

Consider only samples with both normal and malignant tissues.

submitter_ids <- inner_join(tumor, normal, by = "cases.submitter_id") %>%
  dplyr::select(cases.submitter_id)
tumor <- tumor %>%
  dplyr::filter(cases.submitter_id %in% submitter_ids$cases.submitter_id)
normal <- normal %>%
  dplyr::filter(cases.submitter_id %in% submitter_ids$cases.submitter_id)

samples <- rbind(tumor, normal)
invisible(unique(samples$sample_type))
samples

Download only samples with both normal and malignant tissues.

To impose this filtering, we set the barcode argument of GDCquery to samples$sample.submitter_id (which was generated in the previous cell).

query_coad <- GDCquery(
  project = "TCGA-COAD",
  data.category = "Transcriptome Profiling",
  data.type = "miRNA Expression Quantification",
  experimental.strategy = "miRNA-Seq",
  access = "open",
  sample.type = c("Solid Tissue Normal", "Primary Tumor"),
  barcode = as.list(samples$sample.submitter_id)
)

If this is your first time running this notebook (i.e., you have not yet downloaded the results of the query in the previous block), uncomment the code block below.

# GDCdownload(query_coad)

Running the code block above should generate and populate a directory named GDCdata.

III. Data preprocessing

Construct the RNA-seq count matrix.

tcga_coad_data <- GDCprepare(query_coad, summarizedExperiment = TRUE)
rownames(tcga_coad_data) <- tcga_coad_data$miRNA_ID
count_matrix <- tcga_coad_data[, colnames(tcga_coad_data)[grep("count", colnames(tcga_coad_data))]]
colnames(count_matrix) <- gsub("read_count_", "", colnames(count_matrix))

# Remove duplicate entries
count_matrix_df <- data.frame(count_matrix)
count_matrix_df <- count_matrix_df[!duplicated(count_matrix_df), ]
count_matrix <- data.matrix(count_matrix_df)
rownames(count_matrix) <- cleanid(rownames(count_matrix))
count_matrix <- count_matrix[!(duplicated(rownames(count_matrix)) | duplicated(rownames(count_matrix), fromLast = TRUE)), ]

head(count_matrix[1:5, 1:4])
             TCGA.A6.2671.01A.01T.1409.13 TCGA.A6.2683.01A.01T.0822.13 TCGA.A6.2680.01A.01T.1409.13 TCGA.AA.3520.01A.01T.0822.13
hsa-let-7a-1                        11763                        50288                         6055                         4503
hsa-let-7a-2                        11992                        50537                         5945                         4589
hsa-let-7a-3                        11822                        51098                         6018                         4619
hsa-let-7b                          15937                       143822                         9392                        19505
hsa-let-7c                           1488                         3943                          141                          751

Format the samples table so that it can be fed as input to DESeq2.

rownames(samples) <- samples$cases
samples <- samples %>%
  dplyr::select(case = "cases.submitter_id", type = "sample_type")
samples$type <- str_replace(samples$type, "Solid Tissue Normal", "normal")
samples$type <- str_replace(samples$type, "Primary Tumor", "tumor")

DESeq2 requires the row names of samples should be identical to the column names of count_matrix.

colnames(count_matrix) <- gsub(x = colnames(count_matrix), pattern = "\\.", replacement = "-")
count_matrix <- count_matrix[, rownames(samples)]

# Sanity check
all(colnames(count_matrix) == rownames(samples))

IV. Differential miRNA expression analysis

References:

Construct the DESeqDataSet object.

dds <- DESeqDataSetFromMatrix(
  countData = count_matrix,
  colData = samples,
  design = ~type
)
Warning: some variables in design formula are characters, converting to factors

Quality Control

Display quality control (QC) plots (refer to https://cran.r-project.org/web/packages/RNAseqQC/vignettes/introduction.html)

  • Total sample counts
    • Total number of counts for each sample
    • We typically expect all samples to have total counts within the same order of magnitude
  • Library complexity
    • What fraction of counts is taken up by what fraction of genes
    • Samples showing a different library complexity than the rest might be considered low quality
  • Gene detection
    • Number of detected genes for each sample

CAVEAT: There seem to be some outliers — but we defer handling them for now!

plot_total_counts(dds)

plot_library_complexity(dds)

plot_gene_detection(dds)

Perform miRNA filtering.

We determined min_count empirically by looking at the red trend line in the variance stabilization plot. Ideally, this trend line should be flat (i.e., stable).

dds <- filter_genes(dds, min_count = 10)

Transform the read counts.

From https://chipster.csc.fi/manual/deseq2-transform.html:
You can use the resulting transformed values only for visualization and clustering, not for differential expression analysis which needs raw counts.

dds <- estimateSizeFactors(dds)
nsub <- sum(rowMeans(counts(dds, normalized = TRUE)) > 10)
vsd <- vst(dds, nsub = nsub)
mean_sd_plot(vsd)

Check the clustering of the samples.

If you encounter the error Error in loadNamespace(x) : there is no package called 'ComplexHeatmap', uncomment and run the following code block:

# install.packages("devtools", dependencies = TRUE)
# devtools::install_github("jokergoo/ComplexHeatmap")
set.seed(42)
plot_sample_clustering(vsd, anno_vars = c("type"), distance = "euclidean")

Perform principal component analysis (PCA).

plot_pca(vsd, PC_x = 1, PC_y = 2, shape_by = "type")

Regulated Cell Death

Refer to 1. Exploratory Data Analysis - MSigDB Gene Sets + GTEx TPM.Rmd for more detailed documentation on obtaining the gene sets.

Mapping miRNAs to target mRNAs

We are going to refer to miRDB to for the miRNA-mRNA mapping: https://academic.oup.com/nar/article/48/D1/D127/5557729

Download and uncompress miRDB v6.0 by running:

wget https://mirdb.org/download/miRDB_v6.0_prediction_result.txt.gz -P data/
gzip -d miRDB_v6.0_prediction_result.txt.gz

The first column of the dataset lists the miRNAs, while the second column lists the mRNAs (more specifically, the RefSeq IDs of the mRNAs). However, our gene sets list the mRNAs of interest (i.e., those involved in regulated cell death) using their gene symbols.
Hence, we need to perform some preprocessing to convert the gene symbols to RefSeq IDs.

mart <- useMart(biomart = "ensembl", dataset = "hsapiens_gene_ensembl")

Alternative miRNA-mRNA mapping tools/databases:

Necroptosis

Fetch the necroptosis gene set.

necroptosis.genes <- msigdbr(species = "human", category = "C5", subcategory = "GO:BP") %>%
  dplyr::filter(gs_name == "GOBP_NECROPTOTIC_SIGNALING_PATHWAY")
necroptosis.genes

Get the gene symbols of the genes included in the necroptosis gene set, and convert them to RefSeq IDs.

rcd_gene_symbols <- necroptosis.genes$gene_symbol
rcd_refseq <- getBM(attributes = c("refseq_mrna", "hgnc_symbol"), filters = "hgnc_symbol", values = rcd_gene_symbols, mart = mart)
rcd_refseq

Write the RefSeq IDs of the mRNAs of interest to a file.

rcd_mrna_file <- "temp/necroptosis-genes-refseq.txt"
rcd_refseq.unique_ids <- unique(unlist(rcd_refseq$refseq_mrna))
rcd_refseq.unique_ids <- rcd_refseq.unique_ids[!sapply(rcd_refseq.unique_ids, identical, "")]

# Regenerate the file every time
if (file.exists(rcd_mrna_file)) {
  file.remove(rcd_mrna_file)
}
invisible(lapply(rcd_refseq.unique_ids, write, rcd_mrna_file, append = TRUE, ncolumns = 1))

Run the following Python script to fetch the miRNAs targeting the mRNAs of interest:

python util/get-mirna.py --mrna-list temp/necroptosis-genes-refseq.txt --output temp/necroptosis-mirna.txt

Running this script should generate a file named necroptosis-mirna.txt inside the temp directory.

Fetch the miRNAs from the said file.

necroptosis.mirna <- read.table("temp/necroptosis-mirna.txt")
necroptosis.mirna <- unique(unlist(necroptosis.mirna$V1))

Filter the genes to include only those in the necroptosis gene set.

coad_necroptosis <- count_matrix[rownames(count_matrix) %in% necroptosis.mirna, ]
coad_necroptosis <- coad_necroptosis[, rownames(samples)]

# Check if all samples in the counts dataframe are in the samples dataframe
all(colnames(coad_necroptosis) == rownames(samples))

Perform differential miRNA expression analysis.

dds <- DESeqDataSetFromMatrix(
  countData = coad_necroptosis,
  colData = samples,
  design = ~type
)
Warning: some variables in design formula are characters, converting to factors
dds <- filter_genes(dds, min_count = 10)
dds$type <- relevel(dds$type, ref = "normal")
dds <- DESeq(dds)
estimating size factors
estimating dispersions
gene-wise dispersion estimates
mean-dispersion relationship
final dispersion estimates
fitting model and testing
-- replacing outliers and refitting for 5 genes
-- DESeq argument 'minReplicatesForReplace' = 7 
-- original counts are preserved in counts(dds)
estimating dispersions
fitting model and testing
res <- results(dds)
summary(res)

out of 82 with nonzero total read count
adjusted p-value < 0.1
LFC > 0 (up)       : 40, 49%
LFC < 0 (down)     : 36, 44%
outliers [1]       : 0, 0%
low counts [2]     : 0, 0%
(mean count < 5)
[1] see 'cooksCutoff' argument of ?results
[2] see 'independentFiltering' argument of ?results

Prettify the display of results.

deseq.results <- res
deseq.bbl.data <- data.frame(
  row.names = rownames(deseq.results),
  baseMean = deseq.results$baseMean,
  log2FoldChange = deseq.results$log2FoldChange,
  lfcSE = deseq.results$lfcSE,
  stat = deseq.results$stat,
  pvalue = deseq.results$pvalue,
  padj = deseq.results$padj,
  cancer_type = "Colon"
)
deseq.bbl.data

Filter based on p-value and log fold change cutoffs.

deseq.bbl.data.filtered <- dplyr::filter(deseq.bbl.data, abs(log2FoldChange) >= 1.5 & padj < 0.05)
deseq.bbl.data.filtered

Plot the results.

ggplot(deseq.bbl.data.filtered, aes(x = cancer_type, y = rownames(deseq.bbl.data.filtered), size = padj, fill = log2FoldChange)) +
  geom_point(alpha = 0.5, shape = 21, color = "black") +
  scale_size(trans = "reverse") +
  scale_fill_gradient2(low = "blue", mid = "white", high = "red", limits = c(min(deseq.bbl.data.filtered$log2FoldChange),max(deseq.bbl.data.filtered$log2FoldChange))) +
  theme_minimal() +
  theme(legend.position = "bottom") +
  theme(legend.position = "bottom") +
  labs(size = "Adjusted p-value", fill = "log2 FC", x = "Cancer type", y = "miRNA")

Ferroptosis

Fetch the ferroptosis gene set.

ferroptosis.genes <- msigdbr(species = "human", category = "C2", subcategory = "CP:WIKIPATHWAYS") %>%
  dplyr::filter(gs_name == "WP_FERROPTOSIS")
ferroptosis.genes

Get the gene symbols of the genes included in the ferroptosis gene set, and convert them to RefSeq IDs.

rcd_gene_symbols <- ferroptosis.genes$gene_symbol
rcd_refseq <- getBM(attributes = c("refseq_mrna", "hgnc_symbol"), filters = "hgnc_symbol", values = rcd_gene_symbols, mart = mart)
rcd_refseq

Write the RefSeq IDs of the mRNAs of interest to a file.

rcd_mrna_file <- "temp/ferroptosis-genes-refseq.txt"
rcd_refseq.unique_ids <- unique(unlist(rcd_refseq$refseq_mrna))
rcd_refseq.unique_ids <- rcd_refseq.unique_ids[!sapply(rcd_refseq.unique_ids, identical, "")]

# Regenerate the file every time
if (file.exists(rcd_mrna_file)) {
  file.remove(rcd_mrna_file)
}
invisible(lapply(rcd_refseq.unique_ids, write, rcd_mrna_file, append = TRUE, ncolumns = 1))

Run the following Python script to fetch the miRNAs targeting the mRNAs of interest:

python util/get-mirna.py --mrna-list temp/ferroptosis-genes-refseq.txt --output temp/ferroptosis-mirna.txt

Running this script should generate a file named ferroptosis-mirna.txt inside the temp directory.

Fetch the miRNAs from the said file.

ferroptosis.mirna <- read.table("temp/ferroptosis-mirna.txt")
ferroptosis.mirna <- unique(unlist(ferroptosis.mirna$V1))

Filter the genes to include only those in the ferroptosis gene set.

coad_ferroptosis <- count_matrix[rownames(count_matrix) %in% ferroptosis.mirna, ]
coad_ferroptosis <- coad_ferroptosis[, rownames(samples)]

# Check if all samples in the counts dataframe are in the samples dataframe
all(colnames(coad_ferroptosis) == rownames(samples))

Perform differential miRNA expression analysis.

dds <- DESeqDataSetFromMatrix(
  countData = coad_ferroptosis,
  colData = samples,
  design = ~type
)
Warning: some variables in design formula are characters, converting to factors
dds <- filter_genes(dds, min_count = 10)
dds$type <- relevel(dds$type, ref = "normal")
dds <- DESeq(dds)
estimating size factors
estimating dispersions
gene-wise dispersion estimates
mean-dispersion relationship
final dispersion estimates
fitting model and testing
-- replacing outliers and refitting for 13 genes
-- DESeq argument 'minReplicatesForReplace' = 7 
-- original counts are preserved in counts(dds)
estimating dispersions
fitting model and testing
res <- results(dds)
summary(res)

out of 256 with nonzero total read count
adjusted p-value < 0.1
LFC > 0 (up)       : 114, 45%
LFC < 0 (down)     : 100, 39%
outliers [1]       : 0, 0%
low counts [2]     : 0, 0%
(mean count < 4)
[1] see 'cooksCutoff' argument of ?results
[2] see 'independentFiltering' argument of ?results

Prettify the display of results.

deseq.results <- res
deseq.bbl.data <- data.frame(
  row.names = rownames(deseq.results),
  baseMean = deseq.results$baseMean,
  log2FoldChange = deseq.results$log2FoldChange,
  lfcSE = deseq.results$lfcSE,
  stat = deseq.results$stat,
  pvalue = deseq.results$pvalue,
  padj = deseq.results$padj,
  cancer_type = "Colon"
)
deseq.bbl.data

Filter based on p-value and log fold change cutoffs.

deseq.bbl.data.filtered <- dplyr::filter(deseq.bbl.data, abs(log2FoldChange) >= 1.5 & padj < 0.05)
deseq.bbl.data.filtered

Plot the results.

ggplot(deseq.bbl.data.filtered, aes(x = cancer_type, y = rownames(deseq.bbl.data.filtered), size = padj, fill = log2FoldChange)) +
  geom_point(alpha = 0.5, shape = 21, color = "black") +
  scale_size(trans = "reverse") +
  scale_fill_gradient2(low = "blue", mid = "white", high = "red", limits = c(min(deseq.bbl.data.filtered$log2FoldChange),max(deseq.bbl.data.filtered$log2FoldChange))) +
  theme_minimal() +
  theme(legend.position = "bottom") +
  theme(legend.position = "bottom") +
  labs(size = "Adjusted p-value", fill = "log2 FC", x = "Cancer type", y = "miRNA")

Pyroptosis

Fetch the pyroptosis gene set.

pyroptosis.genes <- msigdbr(species = "human", category = "C2", subcategory = "CP:REACTOME") %>%
  dplyr::filter(gs_name == "REACTOME_PYROPTOSIS")
pyroptosis.genes

Get the gene symbols of the genes included in the pyroptosis gene set, and convert them to RefSeq IDs.

rcd_gene_symbols <- pyroptosis.genes$gene_symbol
rcd_refseq <- getBM(attributes = c("refseq_mrna", "hgnc_symbol"), filters = "hgnc_symbol", values = rcd_gene_symbols, mart = mart)
rcd_refseq

Write the RefSeq IDs of the mRNAs of interest to a file.

rcd_mrna_file <- "temp/pyroptosis-genes-refseq.txt"
rcd_refseq.unique_ids <- unique(unlist(rcd_refseq$refseq_mrna))
rcd_refseq.unique_ids <- rcd_refseq.unique_ids[!sapply(rcd_refseq.unique_ids, identical, "")]

# Regenerate the file every time
if (file.exists(rcd_mrna_file)) {
  file.remove(rcd_mrna_file)
}
invisible(lapply(rcd_refseq.unique_ids, write, rcd_mrna_file, append = TRUE, ncolumns = 1))

Run the following Python script to fetch the miRNAs targeting the mRNAs of interest:

python util/get-mirna.py --mrna-list temp/pyroptosis-genes-refseq.txt --output temp/pyroptosis-mirna.txt

Running this script should generate a file named pyroptosis-mirna.txt inside the temp directory.

Fetch the miRNAs from the said file.

pyroptosis.mirna <- read.table("temp/pyroptosis-mirna.txt")
pyroptosis.mirna <- unique(unlist(pyroptosis.mirna$V1))

Filter the genes to include only those in the pyroptosis gene set.

coad_pyroptosis <- count_matrix[rownames(count_matrix) %in% pyroptosis.mirna, ]
coad_pyroptosis <- coad_pyroptosis[, rownames(samples)]

# Check if all samples in the counts dataframe are in the samples dataframe
all(colnames(coad_pyroptosis) == rownames(samples))

Perform differential miRNA expression analysis.

dds <- DESeqDataSetFromMatrix(
  countData = coad_pyroptosis,
  colData = samples,
  design = ~type
)
Warning: some variables in design formula are characters, converting to factors
dds <- filter_genes(dds, min_count = 10)
dds$type <- relevel(dds$type, ref = "normal")
dds <- DESeq(dds)
estimating size factors
estimating dispersions
gene-wise dispersion estimates
mean-dispersion relationship
final dispersion estimates
fitting model and testing
-- replacing outliers and refitting for 9 genes
-- DESeq argument 'minReplicatesForReplace' = 7 
-- original counts are preserved in counts(dds)
estimating dispersions
fitting model and testing
res <- results(dds)
summary(res)

out of 184 with nonzero total read count
adjusted p-value < 0.1
LFC > 0 (up)       : 84, 46%
LFC < 0 (down)     : 71, 39%
outliers [1]       : 0, 0%
low counts [2]     : 0, 0%
(mean count < 4)
[1] see 'cooksCutoff' argument of ?results
[2] see 'independentFiltering' argument of ?results

Prettify the display of results.

deseq.results <- res
deseq.bbl.data <- data.frame(
  row.names = rownames(deseq.results),
  baseMean = deseq.results$baseMean,
  log2FoldChange = deseq.results$log2FoldChange,
  lfcSE = deseq.results$lfcSE,
  stat = deseq.results$stat,
  pvalue = deseq.results$pvalue,
  padj = deseq.results$padj,
  cancer_type = "Colon"
)
deseq.bbl.data

Filter based on p-value and log fold change cutoffs.

deseq.bbl.data.filtered <- dplyr::filter(deseq.bbl.data, abs(log2FoldChange) >= 1.5 & padj < 0.05)
deseq.bbl.data.filtered

Plot the results.

ggplot(deseq.bbl.data.filtered, aes(x = cancer_type, y = rownames(deseq.bbl.data.filtered), size = padj, fill = log2FoldChange)) +
  geom_point(alpha = 0.5, shape = 21, color = "black") +
  scale_size(trans = "reverse") +
  scale_fill_gradient2(low = "blue", mid = "white", high = "red", limits = c(min(deseq.bbl.data.filtered$log2FoldChange),max(deseq.bbl.data.filtered$log2FoldChange))) +
  theme_minimal() +
  theme(legend.position = "bottom") +
  theme(legend.position = "bottom") +
  labs(size = "Adjusted p-value", fill = "log2 FC", x = "Cancer type", y = "miRNA")


  1. De La Salle University, Manila, Philippines, ↩︎

  2. De La Salle University, Manila, Philippines, ↩︎

LS0tDQp0aXRsZTogIkRpZmZlcmVudGlhbCBtaVJOQSBFeHByZXNzaW9uIEFuYWx5c2lzIg0Kc3VidGl0bGU6ICJDb2xvcmVjdGFsIENhbmNlciB8IE5lY3JvcHRvc2lzLCBGZXJyb3B0b3NpcyAmIFB5cm9wdG9zaXMiDQphdXRob3I6IA0KICAtIE1hcmsgRWR3YXJkIE0uIEdvbnphbGVzXltEZSBMYSBTYWxsZSBVbml2ZXJzaXR5LCBNYW5pbGEsIFBoaWxpcHBpbmVzLCBnb256YWxlcy5tYXJrZWR3YXJkQGdtYWlsLmNvbV0NCiAgLSBEci4gQW5pc2ggTS5TLiBTaHJlc3RoYV5bRGUgTGEgU2FsbGUgVW5pdmVyc2l0eSwgTWFuaWxhLCBQaGlsaXBwaW5lcywgYW5pc2guc2hyZXN0aGFAZGxzdS5lZHUucGhdDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIyBJLiBQcmVsaW1pbmFyaWVzDQoNCiMjIyBMb2FkaW5nIGxpYnJhcmllcw0KDQpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkoInRpZHl2ZXJzZSIpDQpsaWJyYXJ5KCJ0aWJibGUiKQ0KbGlicmFyeSgibXNpZ2RiciIpDQpsaWJyYXJ5KCJnZ3Bsb3QyIikNCmxpYnJhcnkoIlRDR0FiaW9saW5rcyIpDQpsaWJyYXJ5KCJSTkFzZXFRQyIpDQpsaWJyYXJ5KCJERVNlcTIiKQ0KbGlicmFyeSgiZW5zZW1ibGRiIikNCmxpYnJhcnkoInB1cnJyIikNCmxpYnJhcnkoIm1hZ3JpdHRyIikNCmxpYnJhcnkoInZzbiIpDQpsaWJyYXJ5KCJtYXRyaXhTdGF0cyIpDQpsaWJyYXJ5KCJkcGx5ciIpDQpsaWJyYXJ5KCJncmV4IikNCmxpYnJhcnkoImJpb21hUnQiKQ0KYGBgDQoNCiMjIElJLiBEb3dubG9hZGluZyBUQ0dBIG1pUk5BIGV4cHJlc3Npb24gZGF0YSANCg0KRG93bmxvYWQgbWlSTkEgZXhwcmVzc2lvbiBkYXRhIGZyb20gVGhlIENhbmNlciBHZW5vbWUgQXRsYXMgKFRDR0EpOg0KLSBgVENHQS1DT0FEYCByZWZlcnMgdG8gdGhlIGJpb3NwZWNpbWVuIGRhdGEgZm9yIGNvbG9uIGFkZW5vY2FyY2lub21hLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnF1ZXJ5X3R1bW9yIDwtIEdEQ3F1ZXJ5KA0KICBwcm9qZWN0ID0gIlRDR0EtQ09BRCIsDQogIGRhdGEuY2F0ZWdvcnkgPSAiVHJhbnNjcmlwdG9tZSBQcm9maWxpbmciLA0KICBkYXRhLnR5cGUgPSAibWlSTkEgRXhwcmVzc2lvbiBRdWFudGlmaWNhdGlvbiIsDQogIGV4cGVyaW1lbnRhbC5zdHJhdGVneSA9ICJtaVJOQS1TZXEiLA0KICBhY2Nlc3MgPSAib3BlbiIsDQogIHNhbXBsZS50eXBlID0gIlByaW1hcnkgVHVtb3IiDQopDQp0dW1vciA8LSBnZXRSZXN1bHRzKHF1ZXJ5X3R1bW9yKQ0KdHVtb3INCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnF1ZXJ5X25vcm1hbCA8LSBHRENxdWVyeSgNCiAgcHJvamVjdCA9ICJUQ0dBLUNPQUQiLA0KICBkYXRhLmNhdGVnb3J5ID0gIlRyYW5zY3JpcHRvbWUgUHJvZmlsaW5nIiwNCiAgZGF0YS50eXBlID0gIm1pUk5BIEV4cHJlc3Npb24gUXVhbnRpZmljYXRpb24iLA0KICBleHBlcmltZW50YWwuc3RyYXRlZ3kgPSAibWlSTkEtU2VxIiwNCiAgYWNjZXNzID0gIm9wZW4iLA0KICBzYW1wbGUudHlwZSA9ICJTb2xpZCBUaXNzdWUgTm9ybWFsIg0KKQ0Kbm9ybWFsIDwtIGdldFJlc3VsdHMocXVlcnlfbm9ybWFsKQ0Kbm9ybWFsDQpgYGANCkNvbnNpZGVyIG9ubHkgc2FtcGxlcyB3aXRoIGJvdGggbm9ybWFsIGFuZCBtYWxpZ25hbnQgdGlzc3Vlcy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpzdWJtaXR0ZXJfaWRzIDwtIGlubmVyX2pvaW4odHVtb3IsIG5vcm1hbCwgYnkgPSAiY2FzZXMuc3VibWl0dGVyX2lkIikgJT4lDQogIGRwbHlyOjpzZWxlY3QoY2FzZXMuc3VibWl0dGVyX2lkKQ0KdHVtb3IgPC0gdHVtb3IgJT4lDQogIGRwbHlyOjpmaWx0ZXIoY2FzZXMuc3VibWl0dGVyX2lkICVpbiUgc3VibWl0dGVyX2lkcyRjYXNlcy5zdWJtaXR0ZXJfaWQpDQpub3JtYWwgPC0gbm9ybWFsICU+JQ0KICBkcGx5cjo6ZmlsdGVyKGNhc2VzLnN1Ym1pdHRlcl9pZCAlaW4lIHN1Ym1pdHRlcl9pZHMkY2FzZXMuc3VibWl0dGVyX2lkKQ0KDQpzYW1wbGVzIDwtIHJiaW5kKHR1bW9yLCBub3JtYWwpDQppbnZpc2libGUodW5pcXVlKHNhbXBsZXMkc2FtcGxlX3R5cGUpKQ0Kc2FtcGxlcw0KYGBgDQoNCkRvd25sb2FkIG9ubHkgc2FtcGxlcyB3aXRoIGJvdGggbm9ybWFsIGFuZCBtYWxpZ25hbnQgdGlzc3Vlcy4NCg0KVG8gaW1wb3NlIHRoaXMgZmlsdGVyaW5nLCB3ZSBzZXQgdGhlIGBiYXJjb2RlYCBhcmd1bWVudCBvZiBgR0RDcXVlcnlgIHRvIGBzYW1wbGVzJHNhbXBsZS5zdWJtaXR0ZXJfaWRgICh3aGljaCB3YXMgZ2VuZXJhdGVkIGluIHRoZSBwcmV2aW91cyBjZWxsKS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpxdWVyeV9jb2FkIDwtIEdEQ3F1ZXJ5KA0KICBwcm9qZWN0ID0gIlRDR0EtQ09BRCIsDQogIGRhdGEuY2F0ZWdvcnkgPSAiVHJhbnNjcmlwdG9tZSBQcm9maWxpbmciLA0KICBkYXRhLnR5cGUgPSAibWlSTkEgRXhwcmVzc2lvbiBRdWFudGlmaWNhdGlvbiIsDQogIGV4cGVyaW1lbnRhbC5zdHJhdGVneSA9ICJtaVJOQS1TZXEiLA0KICBhY2Nlc3MgPSAib3BlbiIsDQogIHNhbXBsZS50eXBlID0gYygiU29saWQgVGlzc3VlIE5vcm1hbCIsICJQcmltYXJ5IFR1bW9yIiksDQogIGJhcmNvZGUgPSBhcy5saXN0KHNhbXBsZXMkc2FtcGxlLnN1Ym1pdHRlcl9pZCkNCikNCmBgYA0KSWYgdGhpcyBpcyB5b3VyIGZpcnN0IHRpbWUgcnVubmluZyB0aGlzIG5vdGVib29rIChpLmUuLCB5b3UgaGF2ZSBub3QgeWV0IGRvd25sb2FkZWQgdGhlIHJlc3VsdHMgb2YgdGhlIHF1ZXJ5IGluIHRoZSBwcmV2aW91cyBibG9jayksIHVuY29tbWVudCB0aGUgY29kZSBibG9jayBiZWxvdy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQojIEdEQ2Rvd25sb2FkKHF1ZXJ5X2NvYWQpDQpgYGANCg0KUnVubmluZyB0aGUgY29kZSBibG9jayBhYm92ZSBzaG91bGQgZ2VuZXJhdGUgYW5kIHBvcHVsYXRlIGEgZGlyZWN0b3J5IG5hbWVkIGBHRENkYXRhYC4NCg0KIyMgSUlJLiBEYXRhIHByZXByb2Nlc3NpbmcNCg0KQ29uc3RydWN0IHRoZSBSTkEtc2VxIGNvdW50IG1hdHJpeC4NCg0KYGBge3IsIGVjaG8gPSBUUlVFLCByZXN1bHRzPSJoaWRlIn0NCnRjZ2FfY29hZF9kYXRhIDwtIEdEQ3ByZXBhcmUocXVlcnlfY29hZCwgc3VtbWFyaXplZEV4cGVyaW1lbnQgPSBUUlVFKQ0KYGBgDQoNCmBgYHtyfQ0Kcm93bmFtZXModGNnYV9jb2FkX2RhdGEpIDwtIHRjZ2FfY29hZF9kYXRhJG1pUk5BX0lEDQpjb3VudF9tYXRyaXggPC0gdGNnYV9jb2FkX2RhdGFbLCBjb2xuYW1lcyh0Y2dhX2NvYWRfZGF0YSlbZ3JlcCgiY291bnQiLCBjb2xuYW1lcyh0Y2dhX2NvYWRfZGF0YSkpXV0NCmNvbG5hbWVzKGNvdW50X21hdHJpeCkgPC0gZ3N1YigicmVhZF9jb3VudF8iLCAiIiwgY29sbmFtZXMoY291bnRfbWF0cml4KSkNCg0KIyBSZW1vdmUgZHVwbGljYXRlIGVudHJpZXMNCmNvdW50X21hdHJpeF9kZiA8LSBkYXRhLmZyYW1lKGNvdW50X21hdHJpeCkNCmNvdW50X21hdHJpeF9kZiA8LSBjb3VudF9tYXRyaXhfZGZbIWR1cGxpY2F0ZWQoY291bnRfbWF0cml4X2RmKSwgXQ0KY291bnRfbWF0cml4IDwtIGRhdGEubWF0cml4KGNvdW50X21hdHJpeF9kZikNCnJvd25hbWVzKGNvdW50X21hdHJpeCkgPC0gY2xlYW5pZChyb3duYW1lcyhjb3VudF9tYXRyaXgpKQ0KY291bnRfbWF0cml4IDwtIGNvdW50X21hdHJpeFshKGR1cGxpY2F0ZWQocm93bmFtZXMoY291bnRfbWF0cml4KSkgfCBkdXBsaWNhdGVkKHJvd25hbWVzKGNvdW50X21hdHJpeCksIGZyb21MYXN0ID0gVFJVRSkpLCBdDQoNCmhlYWQoY291bnRfbWF0cml4WzE6NSwgMTo0XSkNCmBgYA0KRm9ybWF0IHRoZSBgc2FtcGxlc2AgdGFibGUgc28gdGhhdCBpdCBjYW4gYmUgZmVkIGFzIGlucHV0IHRvIERFU2VxMi4NCg0KYGBge3J9DQpyb3duYW1lcyhzYW1wbGVzKSA8LSBzYW1wbGVzJGNhc2VzDQpzYW1wbGVzIDwtIHNhbXBsZXMgJT4lDQogIGRwbHlyOjpzZWxlY3QoY2FzZSA9ICJjYXNlcy5zdWJtaXR0ZXJfaWQiLCB0eXBlID0gInNhbXBsZV90eXBlIikNCnNhbXBsZXMkdHlwZSA8LSBzdHJfcmVwbGFjZShzYW1wbGVzJHR5cGUsICJTb2xpZCBUaXNzdWUgTm9ybWFsIiwgIm5vcm1hbCIpDQpzYW1wbGVzJHR5cGUgPC0gc3RyX3JlcGxhY2Uoc2FtcGxlcyR0eXBlLCAiUHJpbWFyeSBUdW1vciIsICJ0dW1vciIpDQpgYGANCg0KREVTZXEyIHJlcXVpcmVzIHRoZSByb3cgbmFtZXMgb2YgYHNhbXBsZXNgIHNob3VsZCBiZSBpZGVudGljYWwgdG8gdGhlIGNvbHVtbiBuYW1lcyBvZiBgY291bnRfbWF0cml4YC4NCg0KYGBge3IsIGVjaG8gPSBUUlVFLCByZXN1bHRzPSJoaWRlIn0NCmNvbG5hbWVzKGNvdW50X21hdHJpeCkgPC0gZ3N1Yih4ID0gY29sbmFtZXMoY291bnRfbWF0cml4KSwgcGF0dGVybiA9ICJcXC4iLCByZXBsYWNlbWVudCA9ICItIikNCmNvdW50X21hdHJpeCA8LSBjb3VudF9tYXRyaXhbLCByb3duYW1lcyhzYW1wbGVzKV0NCg0KIyBTYW5pdHkgY2hlY2sNCmFsbChjb2xuYW1lcyhjb3VudF9tYXRyaXgpID09IHJvd25hbWVzKHNhbXBsZXMpKQ0KYGBgDQoNCiMjIElWLiBEaWZmZXJlbnRpYWwgbWlSTkEgZXhwcmVzc2lvbiBhbmFseXNpcw0KDQpSZWZlcmVuY2VzOiANCg0KLSBPZmZpY2lhbCBkb2N1bWVudGF0aW9uOiBodHRwczovL3d3dy5iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy92aWduZXR0ZXMvREVTZXEyL2luc3QvZG9jL0RFU2VxMi5odG1sDQotIEdvb2QgYmFsYW5jZSBvZiB0aGVvcnkgYW5kIGhhbmRzLW9uOiBodHRwczovL2hiY3RyYWluaW5nLmdpdGh1Yi5pby9ER0Vfd29ya3Nob3AvbGVzc29ucy8wNF9ER0VfREVTZXEyX2FuYWx5c2lzLmh0bWwNCi0gUXVhbGl0eSBjb250cm9sOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvUk5Bc2VxUUMvdmlnbmV0dGVzL2ludHJvZHVjdGlvbi5odG1sDQoNCkNvbnN0cnVjdCB0aGUgYERFU2VxRGF0YVNldGAgb2JqZWN0Lg0KDQpgYGB7cn0NCmRkcyA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KA0KICBjb3VudERhdGEgPSBjb3VudF9tYXRyaXgsDQogIGNvbERhdGEgPSBzYW1wbGVzLA0KICBkZXNpZ24gPSB+dHlwZQ0KKQ0KYGBgDQoNCiMjIyBRdWFsaXR5IENvbnRyb2wNCg0KRGlzcGxheSBxdWFsaXR5IGNvbnRyb2wgKFFDKSBwbG90cyAocmVmZXIgdG8gaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL1JOQXNlcVFDL3ZpZ25ldHRlcy9pbnRyb2R1Y3Rpb24uaHRtbCkNCg0KLSBUb3RhbCBzYW1wbGUgY291bnRzICANCiAgLSBUb3RhbCBudW1iZXIgb2YgY291bnRzIGZvciBlYWNoIHNhbXBsZQ0KICAtIFdlIHR5cGljYWxseSBleHBlY3QgYWxsIHNhbXBsZXMgdG8gaGF2ZSB0b3RhbCBjb3VudHMgd2l0aGluIHRoZSBzYW1lIG9yZGVyIG9mIG1hZ25pdHVkZQ0KICANCi0gTGlicmFyeSBjb21wbGV4aXR5DQogIC0gV2hhdCBmcmFjdGlvbiBvZiBjb3VudHMgaXMgdGFrZW4gdXAgYnkgd2hhdCBmcmFjdGlvbiBvZiBnZW5lcw0KICAtIFNhbXBsZXMgc2hvd2luZyBhIGRpZmZlcmVudCBsaWJyYXJ5IGNvbXBsZXhpdHkgdGhhbiB0aGUgcmVzdCBtaWdodCBiZSBjb25zaWRlcmVkIGxvdyBxdWFsaXR5DQogIA0KLSBHZW5lIGRldGVjdGlvbg0KICAtIE51bWJlciBvZiBkZXRlY3RlZCBnZW5lcyBmb3IgZWFjaCBzYW1wbGUNCiAgDQoqKkNBVkVBVDoqKiBUaGVyZSBzZWVtIHRvIGJlIHNvbWUgb3V0bGllcnMg4oCUIGJ1dCB3ZSBkZWZlciBoYW5kbGluZyB0aGVtIGZvciBub3chDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KcGxvdF90b3RhbF9jb3VudHMoZGRzKQ0KcGxvdF9saWJyYXJ5X2NvbXBsZXhpdHkoZGRzKQ0KcGxvdF9nZW5lX2RldGVjdGlvbihkZHMpDQpgYGANClBlcmZvcm0gbWlSTkEgZmlsdGVyaW5nLg0KDQpXZSBkZXRlcm1pbmVkIGBtaW5fY291bnRgIGVtcGlyaWNhbGx5IGJ5IGxvb2tpbmcgYXQgdGhlIHJlZCB0cmVuZCBsaW5lIGluIHRoZSB2YXJpYW5jZSBzdGFiaWxpemF0aW9uIHBsb3QuIElkZWFsbHksIHRoaXMgdHJlbmQgbGluZSBzaG91bGQgYmUgZmxhdCAoaS5lLiwgc3RhYmxlKS4NCg0KYGBge3J9DQpkZHMgPC0gZmlsdGVyX2dlbmVzKGRkcywgbWluX2NvdW50ID0gMTApDQpgYGANCg0KVHJhbnNmb3JtIHRoZSByZWFkIGNvdW50cy4NCg0KRnJvbSBodHRwczovL2NoaXBzdGVyLmNzYy5maS9tYW51YWwvZGVzZXEyLXRyYW5zZm9ybS5odG1sOiA8YnI+DQpZb3UgY2FuIHVzZSB0aGUgcmVzdWx0aW5nIHRyYW5zZm9ybWVkIHZhbHVlcyBvbmx5IGZvciB2aXN1YWxpemF0aW9uIGFuZCBjbHVzdGVyaW5nLCBub3QgZm9yIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzIHdoaWNoIG5lZWRzIHJhdyBjb3VudHMuDQoNCmBgYHtyfQ0KZGRzIDwtIGVzdGltYXRlU2l6ZUZhY3RvcnMoZGRzKQ0KbnN1YiA8LSBzdW0ocm93TWVhbnMoY291bnRzKGRkcywgbm9ybWFsaXplZCA9IFRSVUUpKSA+IDEwKQ0KdnNkIDwtIHZzdChkZHMsIG5zdWIgPSBuc3ViKQ0KbWVhbl9zZF9wbG90KHZzZCkNCmBgYA0KDQpDaGVjayB0aGUgY2x1c3RlcmluZyBvZiB0aGUgc2FtcGxlcy4gDQoNCklmIHlvdSBlbmNvdW50ZXIgdGhlIGVycm9yIGBFcnJvciBpbiBsb2FkTmFtZXNwYWNlKHgpIDogdGhlcmUgaXMgbm8gcGFja2FnZSBjYWxsZWQgJ0NvbXBsZXhIZWF0bWFwJ2AsIHVuY29tbWVudCBhbmQgcnVuIHRoZSBmb2xsb3dpbmcgY29kZSBibG9jazoNCg0KYGBge3J9DQojIGluc3RhbGwucGFja2FnZXMoImRldnRvb2xzIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkNCiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJqb2tlcmdvby9Db21wbGV4SGVhdG1hcCIpDQpgYGANCg0KYGBge3IsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTE1fQ0Kc2V0LnNlZWQoNDIpDQpwbG90X3NhbXBsZV9jbHVzdGVyaW5nKHZzZCwgYW5ub192YXJzID0gYygidHlwZSIpLCBkaXN0YW5jZSA9ICJldWNsaWRlYW4iKQ0KYGBgDQoNClBlcmZvcm0gcHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpcyAoUENBKS4NCg0KYGBge3J9DQpwbG90X3BjYSh2c2QsIFBDX3ggPSAxLCBQQ195ID0gMiwgc2hhcGVfYnkgPSAidHlwZSIpDQpgYGANCiMjIyBSZWd1bGF0ZWQgQ2VsbCBEZWF0aA0KDQpSZWZlciB0byBgMS4gRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyAtIE1TaWdEQiBHZW5lIFNldHMgKyBHVEV4IFRQTS5ybWRgIGZvciBtb3JlIGRldGFpbGVkIGRvY3VtZW50YXRpb24gb24gb2J0YWluaW5nIHRoZSBnZW5lIHNldHMuDQoNCiMjIyMgTWFwcGluZyBtaVJOQXMgdG8gdGFyZ2V0IG1STkFzDQoNCldlIGFyZSBnb2luZyB0byByZWZlciB0byBtaVJEQiB0byBmb3IgdGhlIG1pUk5BLW1STkEgbWFwcGluZzogaHR0cHM6Ly9hY2FkZW1pYy5vdXAuY29tL25hci9hcnRpY2xlLzQ4L0QxL0QxMjcvNTU1NzcyOQ0KDQpEb3dubG9hZCBhbmQgdW5jb21wcmVzcyBtaVJEQiB2Ni4wIGJ5IHJ1bm5pbmc6DQpgYGANCndnZXQgaHR0cHM6Ly9taXJkYi5vcmcvZG93bmxvYWQvbWlSREJfdjYuMF9wcmVkaWN0aW9uX3Jlc3VsdC50eHQuZ3ogLVAgZGF0YS8NCmd6aXAgLWQgbWlSREJfdjYuMF9wcmVkaWN0aW9uX3Jlc3VsdC50eHQuZ3oNCmBgYA0KDQpUaGUgZmlyc3QgY29sdW1uIG9mIHRoZSBkYXRhc2V0IGxpc3RzIHRoZSBtaVJOQXMsIHdoaWxlIHRoZSBzZWNvbmQgY29sdW1uIGxpc3RzIHRoZSBtUk5BcyAobW9yZSBzcGVjaWZpY2FsbHksIHRoZSBSZWZTZXEgSURzIG9mIHRoZSBtUk5BcykuDQpIb3dldmVyLCBvdXIgZ2VuZSBzZXRzIGxpc3QgdGhlIG1STkFzIG9mIGludGVyZXN0IChpLmUuLCB0aG9zZSBpbnZvbHZlZCBpbiByZWd1bGF0ZWQgY2VsbCBkZWF0aCkgdXNpbmcgdGhlaXIgZ2VuZSBzeW1ib2xzLiA8YnI+DQpIZW5jZSwgd2UgbmVlZCB0byBwZXJmb3JtIHNvbWUgcHJlcHJvY2Vzc2luZyB0byBjb252ZXJ0IHRoZSBnZW5lIHN5bWJvbHMgdG8gUmVmU2VxIElEcy4NCg0KYGBge3J9DQptYXJ0IDwtIHVzZU1hcnQoYmlvbWFydCA9ICJlbnNlbWJsIiwgZGF0YXNldCA9ICJoc2FwaWVuc19nZW5lX2Vuc2VtYmwiKQ0KYGBgDQoNCkFsdGVybmF0aXZlIG1pUk5BLW1STkEgbWFwcGluZyB0b29scy9kYXRhYmFzZXM6DQoNCi0gbWlyVGFyUm5hU2VxOiBodHRwczovL2JtY2dlbm9taWNzLmJpb21lZGNlbnRyYWwuY29tL2FydGljbGVzLzEwLjExODYvczEyODY0LTAyMi0wODU1OC13DQotIG11bHRpTWlSOiBodHRwczovL2FjYWRlbWljLm91cC5jb20vbmFyL2FydGljbGUvNDIvMTcvZTEzMy8yOTAyNTA0DQoNCiMjIyMgTmVjcm9wdG9zaXMNCg0KRmV0Y2ggdGhlIG5lY3JvcHRvc2lzIGdlbmUgc2V0Lg0KDQpgYGB7cn0NCm5lY3JvcHRvc2lzLmdlbmVzIDwtIG1zaWdkYnIoc3BlY2llcyA9ICJodW1hbiIsIGNhdGVnb3J5ID0gIkM1Iiwgc3ViY2F0ZWdvcnkgPSAiR086QlAiKSAlPiUNCiAgZHBseXI6OmZpbHRlcihnc19uYW1lID09ICJHT0JQX05FQ1JPUFRPVElDX1NJR05BTElOR19QQVRIV0FZIikNCm5lY3JvcHRvc2lzLmdlbmVzDQpgYGANCg0KR2V0IHRoZSBnZW5lIHN5bWJvbHMgb2YgdGhlIGdlbmVzIGluY2x1ZGVkIGluIHRoZSBuZWNyb3B0b3NpcyBnZW5lIHNldCwgYW5kIGNvbnZlcnQgdGhlbSB0byBSZWZTZXEgSURzLg0KDQpgYGB7cn0NCnJjZF9nZW5lX3N5bWJvbHMgPC0gbmVjcm9wdG9zaXMuZ2VuZXMkZ2VuZV9zeW1ib2wNCnJjZF9yZWZzZXEgPC0gZ2V0Qk0oYXR0cmlidXRlcyA9IGMoInJlZnNlcV9tcm5hIiwgImhnbmNfc3ltYm9sIiksIGZpbHRlcnMgPSAiaGduY19zeW1ib2wiLCB2YWx1ZXMgPSByY2RfZ2VuZV9zeW1ib2xzLCBtYXJ0ID0gbWFydCkNCnJjZF9yZWZzZXENCmBgYA0KDQpXcml0ZSB0aGUgUmVmU2VxIElEcyBvZiB0aGUgbVJOQXMgb2YgaW50ZXJlc3QgdG8gYSBmaWxlLg0KDQpgYGB7ciwgZWNobyA9IFRSVUUsIHJlc3VsdHM9ImhpZGUifQ0KcmNkX21ybmFfZmlsZSA8LSAidGVtcC9uZWNyb3B0b3Npcy1nZW5lcy1yZWZzZXEudHh0Ig0KcmNkX3JlZnNlcS51bmlxdWVfaWRzIDwtIHVuaXF1ZSh1bmxpc3QocmNkX3JlZnNlcSRyZWZzZXFfbXJuYSkpDQpyY2RfcmVmc2VxLnVuaXF1ZV9pZHMgPC0gcmNkX3JlZnNlcS51bmlxdWVfaWRzWyFzYXBwbHkocmNkX3JlZnNlcS51bmlxdWVfaWRzLCBpZGVudGljYWwsICIiKV0NCg0KIyBSZWdlbmVyYXRlIHRoZSBmaWxlIGV2ZXJ5IHRpbWUNCmlmIChmaWxlLmV4aXN0cyhyY2RfbXJuYV9maWxlKSkgew0KICBmaWxlLnJlbW92ZShyY2RfbXJuYV9maWxlKQ0KfQ0KDQppbnZpc2libGUobGFwcGx5KHJjZF9yZWZzZXEudW5pcXVlX2lkcywgd3JpdGUsIHJjZF9tcm5hX2ZpbGUsIGFwcGVuZCA9IFRSVUUsIG5jb2x1bW5zID0gMSkpDQpgYGANCg0KUnVuIHRoZSBmb2xsb3dpbmcgUHl0aG9uIHNjcmlwdCB0byBmZXRjaCB0aGUgbWlSTkFzIHRhcmdldGluZyB0aGUgbVJOQXMgb2YgaW50ZXJlc3Q6DQpgYGANCnB5dGhvbiB1dGlsL2dldC1taXJuYS5weSAtLW1ybmEtbGlzdCB0ZW1wL25lY3JvcHRvc2lzLWdlbmVzLXJlZnNlcS50eHQgLS1vdXRwdXQgdGVtcC9uZWNyb3B0b3Npcy1taXJuYS50eHQNCmBgYA0KDQpSdW5uaW5nIHRoaXMgc2NyaXB0IHNob3VsZCBnZW5lcmF0ZSBhIGZpbGUgbmFtZWQgYG5lY3JvcHRvc2lzLW1pcm5hLnR4dGAgaW5zaWRlIHRoZSBgdGVtcGAgZGlyZWN0b3J5Lg0KDQpGZXRjaCB0aGUgbWlSTkFzIGZyb20gdGhlIHNhaWQgZmlsZS4NCg0KYGBge3J9DQpuZWNyb3B0b3Npcy5taXJuYSA8LSByZWFkLnRhYmxlKCJ0ZW1wL25lY3JvcHRvc2lzLW1pcm5hLnR4dCIpDQpuZWNyb3B0b3Npcy5taXJuYSA8LSB1bmlxdWUodW5saXN0KG5lY3JvcHRvc2lzLm1pcm5hJFYxKSkNCmBgYA0KDQpGaWx0ZXIgdGhlIGdlbmVzIHRvIGluY2x1ZGUgb25seSB0aG9zZSBpbiB0aGUgbmVjcm9wdG9zaXMgZ2VuZSBzZXQuDQoNCmBgYHtyLCBlY2hvID0gVFJVRSwgcmVzdWx0cz0iaGlkZSJ9DQpjb2FkX25lY3JvcHRvc2lzIDwtIGNvdW50X21hdHJpeFtyb3duYW1lcyhjb3VudF9tYXRyaXgpICVpbiUgbmVjcm9wdG9zaXMubWlybmEsIF0NCmNvYWRfbmVjcm9wdG9zaXMgPC0gY29hZF9uZWNyb3B0b3Npc1ssIHJvd25hbWVzKHNhbXBsZXMpXQ0KDQojIENoZWNrIGlmIGFsbCBzYW1wbGVzIGluIHRoZSBjb3VudHMgZGF0YWZyYW1lIGFyZSBpbiB0aGUgc2FtcGxlcyBkYXRhZnJhbWUNCmFsbChjb2xuYW1lcyhjb2FkX25lY3JvcHRvc2lzKSA9PSByb3duYW1lcyhzYW1wbGVzKSkNCmBgYA0KDQpQZXJmb3JtIGRpZmZlcmVudGlhbCBtaVJOQSBleHByZXNzaW9uIGFuYWx5c2lzLg0KDQpgYGB7cn0NCmRkcyA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KA0KICBjb3VudERhdGEgPSBjb2FkX25lY3JvcHRvc2lzLA0KICBjb2xEYXRhID0gc2FtcGxlcywNCiAgZGVzaWduID0gfnR5cGUNCikNCmRkcyA8LSBmaWx0ZXJfZ2VuZXMoZGRzLCBtaW5fY291bnQgPSAxMCkNCmRkcyR0eXBlIDwtIHJlbGV2ZWwoZGRzJHR5cGUsIHJlZiA9ICJub3JtYWwiKQ0KZGRzIDwtIERFU2VxKGRkcykNCnJlcyA8LSByZXN1bHRzKGRkcykNCnN1bW1hcnkocmVzKQ0KYGBgDQoNClByZXR0aWZ5IHRoZSBkaXNwbGF5IG9mIHJlc3VsdHMuDQoNCmBgYHtyfQ0KZGVzZXEucmVzdWx0cyA8LSByZXMNCmRlc2VxLmJibC5kYXRhIDwtIGRhdGEuZnJhbWUoDQogIHJvdy5uYW1lcyA9IHJvd25hbWVzKGRlc2VxLnJlc3VsdHMpLA0KICBiYXNlTWVhbiA9IGRlc2VxLnJlc3VsdHMkYmFzZU1lYW4sDQogIGxvZzJGb2xkQ2hhbmdlID0gZGVzZXEucmVzdWx0cyRsb2cyRm9sZENoYW5nZSwNCiAgbGZjU0UgPSBkZXNlcS5yZXN1bHRzJGxmY1NFLA0KICBzdGF0ID0gZGVzZXEucmVzdWx0cyRzdGF0LA0KICBwdmFsdWUgPSBkZXNlcS5yZXN1bHRzJHB2YWx1ZSwNCiAgcGFkaiA9IGRlc2VxLnJlc3VsdHMkcGFkaiwNCiAgY2FuY2VyX3R5cGUgPSAiQ29sb24iDQopDQpkZXNlcS5iYmwuZGF0YQ0KYGBgDQoNCkZpbHRlciBiYXNlZCBvbiBwLXZhbHVlIGFuZCBsb2cgZm9sZCBjaGFuZ2UgY3V0b2Zmcy4NCg0KYGBge3J9DQpkZXNlcS5iYmwuZGF0YS5maWx0ZXJlZCA8LSBkcGx5cjo6ZmlsdGVyKGRlc2VxLmJibC5kYXRhLCBhYnMobG9nMkZvbGRDaGFuZ2UpID49IDEuNSAmIHBhZGogPCAwLjA1KQ0KZGVzZXEuYmJsLmRhdGEuZmlsdGVyZWQNCmBgYA0KDQpQbG90IHRoZSByZXN1bHRzLg0KDQpgYGB7ciwgZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9MTV9DQpnZ3Bsb3QoZGVzZXEuYmJsLmRhdGEuZmlsdGVyZWQsIGFlcyh4ID0gY2FuY2VyX3R5cGUsIHkgPSByb3duYW1lcyhkZXNlcS5iYmwuZGF0YS5maWx0ZXJlZCksIHNpemUgPSBwYWRqLCBmaWxsID0gbG9nMkZvbGRDaGFuZ2UpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUsIHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIikgKw0KICBzY2FsZV9zaXplKHRyYW5zID0gInJldmVyc2UiKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICJibHVlIiwgbWlkID0gIndoaXRlIiwgaGlnaCA9ICJyZWQiLCBsaW1pdHMgPSBjKG1pbihkZXNlcS5iYmwuZGF0YS5maWx0ZXJlZCRsb2cyRm9sZENoYW5nZSksbWF4KGRlc2VxLmJibC5kYXRhLmZpbHRlcmVkJGxvZzJGb2xkQ2hhbmdlKSkpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsNCiAgbGFicyhzaXplID0gIkFkanVzdGVkIHAtdmFsdWUiLCBmaWxsID0gImxvZzIgRkMiLCB4ID0gIkNhbmNlciB0eXBlIiwgeSA9ICJtaVJOQSIpDQpgYGANCiMjIyMgRmVycm9wdG9zaXMNCg0KRmV0Y2ggdGhlIGZlcnJvcHRvc2lzIGdlbmUgc2V0Lg0KDQpgYGB7cn0NCmZlcnJvcHRvc2lzLmdlbmVzIDwtIG1zaWdkYnIoc3BlY2llcyA9ICJodW1hbiIsIGNhdGVnb3J5ID0gIkMyIiwgc3ViY2F0ZWdvcnkgPSAiQ1A6V0lLSVBBVEhXQVlTIikgJT4lDQogIGRwbHlyOjpmaWx0ZXIoZ3NfbmFtZSA9PSAiV1BfRkVSUk9QVE9TSVMiKQ0KZmVycm9wdG9zaXMuZ2VuZXMNCmBgYA0KDQpHZXQgdGhlIGdlbmUgc3ltYm9scyBvZiB0aGUgZ2VuZXMgaW5jbHVkZWQgaW4gdGhlIGZlcnJvcHRvc2lzIGdlbmUgc2V0LCBhbmQgY29udmVydCB0aGVtIHRvIFJlZlNlcSBJRHMuDQoNCmBgYHtyfQ0KcmNkX2dlbmVfc3ltYm9scyA8LSBmZXJyb3B0b3Npcy5nZW5lcyRnZW5lX3N5bWJvbA0KcmNkX3JlZnNlcSA8LSBnZXRCTShhdHRyaWJ1dGVzID0gYygicmVmc2VxX21ybmEiLCAiaGduY19zeW1ib2wiKSwgZmlsdGVycyA9ICJoZ25jX3N5bWJvbCIsIHZhbHVlcyA9IHJjZF9nZW5lX3N5bWJvbHMsIG1hcnQgPSBtYXJ0KQ0KcmNkX3JlZnNlcQ0KYGBgDQoNCldyaXRlIHRoZSBSZWZTZXEgSURzIG9mIHRoZSBtUk5BcyBvZiBpbnRlcmVzdCB0byBhIGZpbGUuDQoNCmBgYHtyLCBlY2hvID0gVFJVRSwgcmVzdWx0cz0iaGlkZSJ9DQpyY2RfbXJuYV9maWxlIDwtICJ0ZW1wL2ZlcnJvcHRvc2lzLWdlbmVzLXJlZnNlcS50eHQiDQpyY2RfcmVmc2VxLnVuaXF1ZV9pZHMgPC0gdW5pcXVlKHVubGlzdChyY2RfcmVmc2VxJHJlZnNlcV9tcm5hKSkNCnJjZF9yZWZzZXEudW5pcXVlX2lkcyA8LSByY2RfcmVmc2VxLnVuaXF1ZV9pZHNbIXNhcHBseShyY2RfcmVmc2VxLnVuaXF1ZV9pZHMsIGlkZW50aWNhbCwgIiIpXQ0KDQojIFJlZ2VuZXJhdGUgdGhlIGZpbGUgZXZlcnkgdGltZQ0KaWYgKGZpbGUuZXhpc3RzKHJjZF9tcm5hX2ZpbGUpKSB7DQogIGZpbGUucmVtb3ZlKHJjZF9tcm5hX2ZpbGUpDQp9DQoNCmludmlzaWJsZShsYXBwbHkocmNkX3JlZnNlcS51bmlxdWVfaWRzLCB3cml0ZSwgcmNkX21ybmFfZmlsZSwgYXBwZW5kID0gVFJVRSwgbmNvbHVtbnMgPSAxKSkNCmBgYA0KUnVuIHRoZSBmb2xsb3dpbmcgUHl0aG9uIHNjcmlwdCB0byBmZXRjaCB0aGUgbWlSTkFzIHRhcmdldGluZyB0aGUgbVJOQXMgb2YgaW50ZXJlc3Q6DQoNCmBgYA0KcHl0aG9uIHV0aWwvZ2V0LW1pcm5hLnB5IC0tbXJuYS1saXN0IHRlbXAvZmVycm9wdG9zaXMtZ2VuZXMtcmVmc2VxLnR4dCAtLW91dHB1dCB0ZW1wL2ZlcnJvcHRvc2lzLW1pcm5hLnR4dA0KYGBgDQoNClJ1bm5pbmcgdGhpcyBzY3JpcHQgc2hvdWxkIGdlbmVyYXRlIGEgZmlsZSBuYW1lZCBgZmVycm9wdG9zaXMtbWlybmEudHh0YCBpbnNpZGUgdGhlIGB0ZW1wYCBkaXJlY3RvcnkuDQoNCkZldGNoIHRoZSBtaVJOQXMgZnJvbSB0aGUgc2FpZCBmaWxlLg0KDQpgYGB7cn0NCmZlcnJvcHRvc2lzLm1pcm5hIDwtIHJlYWQudGFibGUoInRlbXAvZmVycm9wdG9zaXMtbWlybmEudHh0IikNCmZlcnJvcHRvc2lzLm1pcm5hIDwtIHVuaXF1ZSh1bmxpc3QoZmVycm9wdG9zaXMubWlybmEkVjEpKQ0KYGBgDQoNCkZpbHRlciB0aGUgZ2VuZXMgdG8gaW5jbHVkZSBvbmx5IHRob3NlIGluIHRoZSBmZXJyb3B0b3NpcyBnZW5lIHNldC4NCg0KYGBge3IsIGVjaG8gPSBUUlVFLCByZXN1bHRzPSJoaWRlIn0NCmNvYWRfZmVycm9wdG9zaXMgPC0gY291bnRfbWF0cml4W3Jvd25hbWVzKGNvdW50X21hdHJpeCkgJWluJSBmZXJyb3B0b3Npcy5taXJuYSwgXQ0KY29hZF9mZXJyb3B0b3NpcyA8LSBjb2FkX2ZlcnJvcHRvc2lzWywgcm93bmFtZXMoc2FtcGxlcyldDQoNCiMgQ2hlY2sgaWYgYWxsIHNhbXBsZXMgaW4gdGhlIGNvdW50cyBkYXRhZnJhbWUgYXJlIGluIHRoZSBzYW1wbGVzIGRhdGFmcmFtZQ0KYWxsKGNvbG5hbWVzKGNvYWRfZmVycm9wdG9zaXMpID09IHJvd25hbWVzKHNhbXBsZXMpKQ0KYGBgDQpQZXJmb3JtIGRpZmZlcmVudGlhbCBtaVJOQSBleHByZXNzaW9uIGFuYWx5c2lzLg0KDQpgYGB7cn0NCmRkcyA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KA0KICBjb3VudERhdGEgPSBjb2FkX2ZlcnJvcHRvc2lzLA0KICBjb2xEYXRhID0gc2FtcGxlcywNCiAgZGVzaWduID0gfnR5cGUNCikNCmRkcyA8LSBmaWx0ZXJfZ2VuZXMoZGRzLCBtaW5fY291bnQgPSAxMCkNCmRkcyR0eXBlIDwtIHJlbGV2ZWwoZGRzJHR5cGUsIHJlZiA9ICJub3JtYWwiKQ0KZGRzIDwtIERFU2VxKGRkcykNCnJlcyA8LSByZXN1bHRzKGRkcykNCnN1bW1hcnkocmVzKQ0KYGBgDQpQcmV0dGlmeSB0aGUgZGlzcGxheSBvZiByZXN1bHRzLg0KDQpgYGB7cn0NCmRlc2VxLnJlc3VsdHMgPC0gcmVzDQpkZXNlcS5iYmwuZGF0YSA8LSBkYXRhLmZyYW1lKA0KICByb3cubmFtZXMgPSByb3duYW1lcyhkZXNlcS5yZXN1bHRzKSwNCiAgYmFzZU1lYW4gPSBkZXNlcS5yZXN1bHRzJGJhc2VNZWFuLA0KICBsb2cyRm9sZENoYW5nZSA9IGRlc2VxLnJlc3VsdHMkbG9nMkZvbGRDaGFuZ2UsDQogIGxmY1NFID0gZGVzZXEucmVzdWx0cyRsZmNTRSwNCiAgc3RhdCA9IGRlc2VxLnJlc3VsdHMkc3RhdCwNCiAgcHZhbHVlID0gZGVzZXEucmVzdWx0cyRwdmFsdWUsDQogIHBhZGogPSBkZXNlcS5yZXN1bHRzJHBhZGosDQogIGNhbmNlcl90eXBlID0gIkNvbG9uIg0KKQ0KZGVzZXEuYmJsLmRhdGENCmBgYA0KDQpGaWx0ZXIgYmFzZWQgb24gcC12YWx1ZSBhbmQgbG9nIGZvbGQgY2hhbmdlIGN1dG9mZnMuDQoNCmBgYHtyfQ0KZGVzZXEuYmJsLmRhdGEuZmlsdGVyZWQgPC0gZHBseXI6OmZpbHRlcihkZXNlcS5iYmwuZGF0YSwgYWJzKGxvZzJGb2xkQ2hhbmdlKSA+PSAxLjUgJiBwYWRqIDwgMC4wNSkNCmRlc2VxLmJibC5kYXRhLmZpbHRlcmVkDQpgYGANCg0KUGxvdCB0aGUgcmVzdWx0cy4NCg0KYGBge3IsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTQ1fQ0KZ2dwbG90KGRlc2VxLmJibC5kYXRhLmZpbHRlcmVkLCBhZXMoeCA9IGNhbmNlcl90eXBlLCB5ID0gcm93bmFtZXMoZGVzZXEuYmJsLmRhdGEuZmlsdGVyZWQpLCBzaXplID0gcGFkaiwgZmlsbCA9IGxvZzJGb2xkQ2hhbmdlKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC41LCBzaGFwZSA9IDIxLCBjb2xvciA9ICJibGFjayIpICsNCiAgc2NhbGVfc2l6ZSh0cmFucyA9ICJyZXZlcnNlIikgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiYmx1ZSIsIG1pZCA9ICJ3aGl0ZSIsIGhpZ2ggPSAicmVkIiwgbGltaXRzID0gYyhtaW4oZGVzZXEuYmJsLmRhdGEuZmlsdGVyZWQkbG9nMkZvbGRDaGFuZ2UpLG1heChkZXNlcS5iYmwuZGF0YS5maWx0ZXJlZCRsb2cyRm9sZENoYW5nZSkpKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArDQogIGxhYnMoc2l6ZSA9ICJBZGp1c3RlZCBwLXZhbHVlIiwgZmlsbCA9ICJsb2cyIEZDIiwgeCA9ICJDYW5jZXIgdHlwZSIsIHkgPSAibWlSTkEiKQ0KYGBgDQoNCiMjIyMgUHlyb3B0b3Npcw0KDQpGZXRjaCB0aGUgcHlyb3B0b3NpcyBnZW5lIHNldC4NCg0KYGBge3J9DQpweXJvcHRvc2lzLmdlbmVzIDwtIG1zaWdkYnIoc3BlY2llcyA9ICJodW1hbiIsIGNhdGVnb3J5ID0gIkMyIiwgc3ViY2F0ZWdvcnkgPSAiQ1A6UkVBQ1RPTUUiKSAlPiUNCiAgZHBseXI6OmZpbHRlcihnc19uYW1lID09ICJSRUFDVE9NRV9QWVJPUFRPU0lTIikNCnB5cm9wdG9zaXMuZ2VuZXMNCmBgYA0KDQpHZXQgdGhlIGdlbmUgc3ltYm9scyBvZiB0aGUgZ2VuZXMgaW5jbHVkZWQgaW4gdGhlIHB5cm9wdG9zaXMgZ2VuZSBzZXQsIGFuZCBjb252ZXJ0IHRoZW0gdG8gUmVmU2VxIElEcy4NCg0KYGBge3J9DQpyY2RfZ2VuZV9zeW1ib2xzIDwtIHB5cm9wdG9zaXMuZ2VuZXMkZ2VuZV9zeW1ib2wNCnJjZF9yZWZzZXEgPC0gZ2V0Qk0oYXR0cmlidXRlcyA9IGMoInJlZnNlcV9tcm5hIiwgImhnbmNfc3ltYm9sIiksIGZpbHRlcnMgPSAiaGduY19zeW1ib2wiLCB2YWx1ZXMgPSByY2RfZ2VuZV9zeW1ib2xzLCBtYXJ0ID0gbWFydCkNCnJjZF9yZWZzZXENCmBgYA0KV3JpdGUgdGhlIFJlZlNlcSBJRHMgb2YgdGhlIG1STkFzIG9mIGludGVyZXN0IHRvIGEgZmlsZS4NCg0KYGBge3IsIGVjaG8gPSBUUlVFLCByZXN1bHRzPSJoaWRlIn0NCnJjZF9tcm5hX2ZpbGUgPC0gInRlbXAvcHlyb3B0b3Npcy1nZW5lcy1yZWZzZXEudHh0Ig0KcmNkX3JlZnNlcS51bmlxdWVfaWRzIDwtIHVuaXF1ZSh1bmxpc3QocmNkX3JlZnNlcSRyZWZzZXFfbXJuYSkpDQpyY2RfcmVmc2VxLnVuaXF1ZV9pZHMgPC0gcmNkX3JlZnNlcS51bmlxdWVfaWRzWyFzYXBwbHkocmNkX3JlZnNlcS51bmlxdWVfaWRzLCBpZGVudGljYWwsICIiKV0NCg0KIyBSZWdlbmVyYXRlIHRoZSBmaWxlIGV2ZXJ5IHRpbWUNCmlmIChmaWxlLmV4aXN0cyhyY2RfbXJuYV9maWxlKSkgew0KICBmaWxlLnJlbW92ZShyY2RfbXJuYV9maWxlKQ0KfQ0KDQppbnZpc2libGUobGFwcGx5KHJjZF9yZWZzZXEudW5pcXVlX2lkcywgd3JpdGUsIHJjZF9tcm5hX2ZpbGUsIGFwcGVuZCA9IFRSVUUsIG5jb2x1bW5zID0gMSkpDQpgYGANCg0KUnVuIHRoZSBmb2xsb3dpbmcgUHl0aG9uIHNjcmlwdCB0byBmZXRjaCB0aGUgbWlSTkFzIHRhcmdldGluZyB0aGUgbVJOQXMgb2YgaW50ZXJlc3Q6DQoNCmBgYA0KcHl0aG9uIHV0aWwvZ2V0LW1pcm5hLnB5IC0tbXJuYS1saXN0IHRlbXAvcHlyb3B0b3Npcy1nZW5lcy1yZWZzZXEudHh0IC0tb3V0cHV0IHRlbXAvcHlyb3B0b3Npcy1taXJuYS50eHQNCmBgYA0KDQpSdW5uaW5nIHRoaXMgc2NyaXB0IHNob3VsZCBnZW5lcmF0ZSBhIGZpbGUgbmFtZWQgYHB5cm9wdG9zaXMtbWlybmEudHh0YCBpbnNpZGUgdGhlIGB0ZW1wYCBkaXJlY3RvcnkuDQoNCkZldGNoIHRoZSBtaVJOQXMgZnJvbSB0aGUgc2FpZCBmaWxlLg0KDQpgYGB7cn0NCnB5cm9wdG9zaXMubWlybmEgPC0gcmVhZC50YWJsZSgidGVtcC9weXJvcHRvc2lzLW1pcm5hLnR4dCIpDQpweXJvcHRvc2lzLm1pcm5hIDwtIHVuaXF1ZSh1bmxpc3QocHlyb3B0b3Npcy5taXJuYSRWMSkpDQpgYGANCg0KRmlsdGVyIHRoZSBnZW5lcyB0byBpbmNsdWRlIG9ubHkgdGhvc2UgaW4gdGhlIHB5cm9wdG9zaXMgZ2VuZSBzZXQuDQoNCmBgYHtyLCBlY2hvID0gVFJVRSwgcmVzdWx0cz0iaGlkZSJ9DQpjb2FkX3B5cm9wdG9zaXMgPC0gY291bnRfbWF0cml4W3Jvd25hbWVzKGNvdW50X21hdHJpeCkgJWluJSBweXJvcHRvc2lzLm1pcm5hLCBdDQpjb2FkX3B5cm9wdG9zaXMgPC0gY29hZF9weXJvcHRvc2lzWywgcm93bmFtZXMoc2FtcGxlcyldDQoNCiMgQ2hlY2sgaWYgYWxsIHNhbXBsZXMgaW4gdGhlIGNvdW50cyBkYXRhZnJhbWUgYXJlIGluIHRoZSBzYW1wbGVzIGRhdGFmcmFtZQ0KYWxsKGNvbG5hbWVzKGNvYWRfcHlyb3B0b3NpcykgPT0gcm93bmFtZXMoc2FtcGxlcykpDQpgYGANCg0KUGVyZm9ybSBkaWZmZXJlbnRpYWwgbWlSTkEgZXhwcmVzc2lvbiBhbmFseXNpcy4NCg0KYGBge3J9DQpkZHMgPC0gREVTZXFEYXRhU2V0RnJvbU1hdHJpeCgNCiAgY291bnREYXRhID0gY29hZF9weXJvcHRvc2lzLA0KICBjb2xEYXRhID0gc2FtcGxlcywNCiAgZGVzaWduID0gfnR5cGUNCikNCmRkcyA8LSBmaWx0ZXJfZ2VuZXMoZGRzLCBtaW5fY291bnQgPSAxMCkNCmRkcyR0eXBlIDwtIHJlbGV2ZWwoZGRzJHR5cGUsIHJlZiA9ICJub3JtYWwiKQ0KZGRzIDwtIERFU2VxKGRkcykNCnJlcyA8LSByZXN1bHRzKGRkcykNCnN1bW1hcnkocmVzKQ0KYGBgDQpQcmV0dGlmeSB0aGUgZGlzcGxheSBvZiByZXN1bHRzLg0KDQpgYGB7cn0NCmRlc2VxLnJlc3VsdHMgPC0gcmVzDQpkZXNlcS5iYmwuZGF0YSA8LSBkYXRhLmZyYW1lKA0KICByb3cubmFtZXMgPSByb3duYW1lcyhkZXNlcS5yZXN1bHRzKSwNCiAgYmFzZU1lYW4gPSBkZXNlcS5yZXN1bHRzJGJhc2VNZWFuLA0KICBsb2cyRm9sZENoYW5nZSA9IGRlc2VxLnJlc3VsdHMkbG9nMkZvbGRDaGFuZ2UsDQogIGxmY1NFID0gZGVzZXEucmVzdWx0cyRsZmNTRSwNCiAgc3RhdCA9IGRlc2VxLnJlc3VsdHMkc3RhdCwNCiAgcHZhbHVlID0gZGVzZXEucmVzdWx0cyRwdmFsdWUsDQogIHBhZGogPSBkZXNlcS5yZXN1bHRzJHBhZGosDQogIGNhbmNlcl90eXBlID0gIkNvbG9uIg0KKQ0KZGVzZXEuYmJsLmRhdGENCmBgYA0KDQpGaWx0ZXIgYmFzZWQgb24gcC12YWx1ZSBhbmQgbG9nIGZvbGQgY2hhbmdlIGN1dG9mZnMuDQoNCmBgYHtyfQ0KZGVzZXEuYmJsLmRhdGEuZmlsdGVyZWQgPC0gZHBseXI6OmZpbHRlcihkZXNlcS5iYmwuZGF0YSwgYWJzKGxvZzJGb2xkQ2hhbmdlKSA+PSAxLjUgJiBwYWRqIDwgMC4wNSkNCmRlc2VxLmJibC5kYXRhLmZpbHRlcmVkDQpgYGANCg0KDQpQbG90IHRoZSByZXN1bHRzLg0KDQpgYGB7ciwgZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9MzB9DQpnZ3Bsb3QoZGVzZXEuYmJsLmRhdGEuZmlsdGVyZWQsIGFlcyh4ID0gY2FuY2VyX3R5cGUsIHkgPSByb3duYW1lcyhkZXNlcS5iYmwuZGF0YS5maWx0ZXJlZCksIHNpemUgPSBwYWRqLCBmaWxsID0gbG9nMkZvbGRDaGFuZ2UpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUsIHNoYXBlID0gMjEsIGNvbG9yID0gImJsYWNrIikgKw0KICBzY2FsZV9zaXplKHRyYW5zID0gInJldmVyc2UiKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICJibHVlIiwgbWlkID0gIndoaXRlIiwgaGlnaCA9ICJyZWQiLCBsaW1pdHMgPSBjKG1pbihkZXNlcS5iYmwuZGF0YS5maWx0ZXJlZCRsb2cyRm9sZENoYW5nZSksbWF4KGRlc2VxLmJibC5kYXRhLmZpbHRlcmVkJGxvZzJGb2xkQ2hhbmdlKSkpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsNCiAgbGFicyhzaXplID0gIkFkanVzdGVkIHAtdmFsdWUiLCBmaWxsID0gImxvZzIgRkMiLCB4ID0gIkNhbmNlciB0eXBlIiwgeSA9ICJtaVJOQSIpDQpgYGA=