Introduction
This is a walkthrough/tutorial for how to cluster and analyze
time-course bulk RNA-seq data using the maSigPro
Bioconductor package in R. I created this workflow because there is just
one tutorial available online for maSigPro, and it’s fairly outdated at
this point. I hope others can use this workflow as a guide for how to
analyze ther data with maSigPro.
What is maSigPro?
maSigPro (microarray Significant Profiles) is a
method for clustering time-course RNA-seq/microarray data. It uses
polynomial regression models to analyze time-series gene expression
data. One reason why I like this package over normal hierarchical
clustering is that maSigPro has parameters that allow you to finetune
the stringency/thresholds for different parts of the gene clustering
process, particularly between conditions.
Broadly, maSigPro does the follow:
- Identifies genes with significant temporal expression changes
- Detects condition-specific (e.g. genotype) patterns
- Clusters genes by expression trajectory
- Models non-linear temporal dynamics
maSigPro use case: Experiments with multiple time
points (ideally 3+ timepoints), 2+ conditions/groups, n=3 replicates per
group. If you are interested in broad, temporal trends rather than
single timepoint pairwise comparisons between condition/genotype, then
maSigPro is particularly useful.
required dependencies
This workflow tutorial uses the fllowing required packages:
# Bioconductor packages
if (!requireNamespace("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install(c("maSigPro", "clusterProfiler", "org.Hs.eg.db"))
# CRAN packages
install.packages(c("ggplot2", "dplyr", "tidyr", "reshape2",
"patchwork", "pheatmap", "readr", "stringr"))
Publicly available dataset for this workflow
This tutorial uses the a publicly available time-course bulk RNA-seq
data from the reference below. specifically, the input for maSigPro is
normalized (can check the package vignette for more details). Briefly, I
used edgeR TMM-normalized counts per million for this analysis.
This dataset has two genotypes comparing D21 (disomic) vs T21
(trisomic) genotypes across three developmental time points (Day 6, 10,
and 17).
Data source: Martinez JL et al. Transcriptional
consequences of trisomy 21 on neural induction. Front Cell
Neurosci. 2024;18:1341141. doi:10.3389/fncel.2024.1341141
Workflow Overview
| 1 |
Load libraries and data |
| 2 |
Filter lowly expressed genes |
| 3 |
Prepare experimental design matrix |
| 4 |
Create polynomial regression design |
| 5 |
Identify temporally significant genes |
| 6 |
Detect genotype-specific patterns |
| 7 |
Extract significant genes |
| 8 |
Cluster genes by trajectory |
| 9 |
Prepare visualization data |
| 10 |
Plot cluster profiles |
| 11 |
Generate summary statistics |
| 12 |
GO enrichment analysis |
| 13 |
Expression heatmaps |
| 14 |
Save results |
Step 1: Load required libraries and data
Load the required packages and import expression data. maSigPro
requires:
- Expression matrix: Genes (rows) × Samples
(columns), counts per million (CPM) or normalized counts. You ARE able
to use raw counts (maSigPro has an internal normalization option), but I
prefer to use TMM-normalized CPM because it’s well-validated.
- Metadata: Sample information with genotype,
timepoint, and replicate columns that we will use to construct the
experimental design
# Load required packages
suppressPackageStartupMessages({
library(ggplot2)
library(dplyr)
library(tidyr)
library(reshape2)
library(patchwork)
library(pheatmap)
library(readr)
library(stringr)
library(maSigPro)
library(clusterProfiler)
library(org.Hs.eg.db)
})
# Load expression data and metadata from the publicly available dataset
counts_cpm <- readRDS("data/counts/cpm/WC24_cpm.rds")
metadata <- readRDS("data/metadata/WC24_metadata_clean.rds")
# Create output directories
fig_dir <- "results/figures/masigpro"
res_dir <- "results/masigpro"
dir.create(fig_dir, recursive = TRUE, showWarnings = FALSE)
dir.create(res_dir, recursive = TRUE, showWarnings = FALSE)
Step 2: Filter Expression Matrix
This step is optional, but I highly recommended adding some step in
which you filter or remove genes whose expression is low at any given
stage for a given condition/treatment. Here, I Rremove lowly expressed
genes to reduce noise and improve statistical power for later steps in
the maSigPro workflow. Genes must have mean CPM > 5 in at least one
genotype-timepoint combination (ie, at a specific timepoint for a set of
replicates for a genotype/stage)
min_cpm <- 5
# select relevant samples
metadata_filter <- metadata %>%
dplyr::filter(genotype %in% c("D21", "T21")) %>%
dplyr::select(sample_id, genotype, timepoint)
# Calculate mean CPM per gene per genotype-timepoint group
gene_means <- counts_cpm %>%
as.data.frame() %>%
tibble::rownames_to_column("gene_id") %>%
tidyr::pivot_longer(-gene_id, names_to = "sample_id", values_to = "cpm") %>%
dplyr::inner_join(metadata_filter, by = "sample_id") %>%
dplyr::group_by(gene_id, genotype, timepoint) %>%
dplyr::summarise(mean_cpm = mean(cpm), .groups = "drop") %>%
dplyr::group_by(gene_id) %>%
dplyr::summarise(max_mean_cpm = max(mean_cpm), .groups = "drop")
# Keep genes passing the expression threshold
keep_genes <- gene_means$gene_id[gene_means$max_mean_cpm > min_cpm]
expr_mat <- counts_cpm[keep_genes, ]
cat("Genes before filtering:", nrow(counts_cpm), "\n")
## Genes before filtering: 13826
cat("Genes after filtering:", nrow(expr_mat), "\n")
## Genes after filtering: 12407
Step 3: Prepare experimental design matrix
maSigPro requires an experimental design matrix format with columns
for:
- Time: Numeric time point values for each timepoint
in time-series
- Replicate: Identifier for each replicate
- Group columns: Binary (0 or 1) values for each
experimental group
I show the experimental matrix for maSigPro below
# prepare metadata with numeric timepoints
md <- metadata %>%
dplyr::filter(genotype %in% c("D21", "T21")) %>%
dplyr::select(sample_id, genotype, timepoint, rep) %>%
dplyr::mutate(timepoint = as.numeric(as.character(timepoint)))
# Match sample order to expression matrix
md <- md[match(colnames(expr_mat), md$sample_id), ]
# Create design matrix
edesign <- data.frame(
Time = md$timepoint,
Replicate = md$rep,
stringsAsFactors = FALSE
)
rownames(edesign) <- md$sample_id
# Add binary genotype columns (0 or 1 encoding)
genotype_matrix <- model.matrix(~ 0 + genotype, data = md)
colnames(genotype_matrix) <- gsub("genotype", "", colnames(genotype_matrix))
edesign <- cbind(edesign, as.data.frame(genotype_matrix))
rownames(edesign) <- md$sample_id
# Display design matrix
knitr::kable(edesign, caption = "Experimental Design Matrix for maSigPro")
Experimental Design Matrix for maSigPro
| BA1D6_S1 |
6 |
1 |
1 |
0 |
| BA2D6_S2 |
6 |
2 |
1 |
0 |
| BA3D6_S3 |
6 |
3 |
1 |
0 |
| BA1D10_S4 |
10 |
1 |
1 |
0 |
| BA2D10_S5 |
10 |
2 |
1 |
0 |
| BA3D10_S6 |
10 |
3 |
1 |
0 |
| BA1D17_S7 |
17 |
1 |
1 |
0 |
| BA2D17_S8 |
17 |
2 |
1 |
0 |
| BA3D17_S9 |
17 |
3 |
1 |
0 |
| MA1D6_S10 |
6 |
1 |
0 |
1 |
| MA2D6_S11 |
6 |
2 |
0 |
1 |
| MA3D6_S12 |
6 |
3 |
0 |
1 |
| MA1D10_S13 |
10 |
1 |
0 |
1 |
| MA2D10_S14 |
10 |
2 |
0 |
1 |
| MA3D10_S15 |
10 |
3 |
0 |
1 |
| MA1D17_S16 |
17 |
1 |
0 |
1 |
| MA2D17_S17 |
17 |
2 |
0 |
1 |
| MA3D17_S18 |
17 |
3 |
0 |
1 |
Step 4: Create Polynomial Regression Design
The make.design.matrix() function in maSigPro creates the regression
design matrix. The degree parameter controls the polynomial
order. Based on the vigentte, to calculate the parameter for your
specific experiment, take the total number of timepoints and subtract by
1. unless you have 5+ timepoints, I would not exceed more than n=3 for
degree, unless you expects genes to follow ‘W’ shape expression
patterns.
| 1 |
Linear |
Monotonic trends |
| 2 |
Quadratic |
Peak/trough patterns |
| 3 |
Cubic |
Complex dynamics |
Degree = 2 (quadratic) is typically fine for most
time-course experimental designs, since it models ‘U’ shape expression
dynamics, so it captures both linear trends and non-linear patterns.
poly_degree <- 2
design <- maSigPro::make.design.matrix(
edesign = edesign,
degree = poly_degree,
time.col = 1,
repl.col = 2,
group.cols = 3:4
)
design$dis <- as.data.frame(design$dis)
expr_mat <- expr_mat[, rownames(design$dis)]
# Preview the regression design matrix
knitr::kable(head(design$dis), caption = "Polynomial Regression Design Matrix (first 6 rows)")
Polynomial Regression Design Matrix (first 6 rows)
| BA1D6_S1 |
0 |
6 |
0 |
36 |
0 |
| BA2D6_S2 |
0 |
6 |
0 |
36 |
0 |
| BA3D6_S3 |
0 |
6 |
0 |
36 |
0 |
| BA1D10_S4 |
0 |
10 |
0 |
100 |
0 |
| BA2D10_S5 |
0 |
10 |
0 |
100 |
0 |
| BA3D10_S6 |
0 |
10 |
0 |
100 |
0 |
Step 5: Identify temporally significant Genes
With the design matrix made, the next step in the maSigPro workflow
is the p.vector() function. This function fits polynomial
regression models to each gene and tests for significant temporal
expression changes (regardless of genotype).
Parameters:
Q: FDR threshold for significance (default: 0.05). You
can try 0.01, though 0.05 is recommended by the authors in the original
maSigPro paper.
MT.adjust: Multiple testing correction method (“BH” =
Benjamini-Hochberg)
min.obs: Minimum observations required per gene. I
would set this parameter equal to the number of observations you have
per treatment/timepoint (ie, n=3 replicates in most cases).
q_threshold <- 0.05
fit <- maSigPro::p.vector(
data = expr_mat,
design = design$dis,
Q = q_threshold,
MT.adjust = "BH",
min.obs = nrow(md)
)
## Genes with significant temporal expression: 160362
Step 6: Detect Genotype-Specific Patterns
The next step uses the T.fit() function, which applies a
stepwise regression to identify genes with significantly different
temporal patterns between genotypes (genotype × time interaction). In
other words, this function finds differences between your two
conditions.
Note that there are several options to use for the regression method.
here is use the method used by the authors in their tutorial (backward),
though I tend to use two.ways.backward. You can try these different
methods to see method most ‘accurately’ clusters your genes temporally +
based on condition/treatment.
Stepwise regression methods in maSigPro:
- maSigPro offers several options for what regression is
backward: Start with full model, remove non-significant
terms
forward: Start empty, add significant terms
two.ways.backward / two.ways.forward:
Bidirectional selection (I tend to use this)
alpha_threshold <- 0.05
tfit <- maSigPro::T.fit(
fit,
step.method = "backward",
alfa = alpha_threshold
)
tfit$groups.vector <- design$groups.vector
tfit$edesign <- design$edesign
Step 8: Cluster Genes by Expression Trajectory
Next, we cluster genes with similar temporal expression patterns
using see.genes().
There are many options here to use for the distance metric and
clustering method. Moreover, you can set k as the number of clusters. I
won’t get into the nuances and details of how to pick the specific
distance metric, etc. The main point is: are you able to infer
biological meaning from the clusters? A cluster defined by maSigPro is
only meaningful if it has some biological meaning underlying it. One
simple way to derive whether a cluster has meaningful biological
information is the number of genes contained within it. If a cluster has
50 genes, it’s likely that your clustering resolution is too fine.
My suggestion would be to test various parameters. Ideally,
regardless of the parameters you choose, the underlying biological
insight you glean is largely the same.
Clustering parameters:
k |
Number of clusters |
dis |
Distance metric (“correlation”, “euclidean”) |
cluster.method |
Clustering algorithm (“hclust”, “kmeans”) |
k_clusters <- 10
edesign_clean <- design$edesign[, sapply(design$edesign, is.numeric)]
clusters <- maSigPro::see.genes(
data = sigs$sig.genes$T21vsD21,
edesign = edesign_clean,
k = k_clusters,
dis = "correlation",
cluster.method = "hclust",
color.mode = "gray",
show.fit = FALSE
)
# Create cluster assignment table
cl_assign <- data.frame(
gene_id = names(clusters$cut),
cluster = as.integer(clusters$cut),
stringsAsFactors = FALSE
)
# Summary of cluster sizes
cluster_summary <- cl_assign %>%
dplyr::count(cluster, name = "n_genes") %>%
dplyr::arrange(cluster)
knitr::kable(cluster_summary, caption = "Genes per Cluster")
Genes per Cluster
| 1 |
799 |
| 2 |
480 |
| 3 |
1497 |
| 4 |
361 |
| 5 |
342 |
| 6 |
496 |
| 7 |
514 |
| 8 |
829 |
| 9 |
540 |
| 10 |
115 |
Step 9: Prepare Visualization Data
Here, I convert expression data to long format and, using the
maSigPro gene clusters from the previous part of thsi workflow, prepare
to plot our data in lineplots by ‘group’. I also calculate summary
statistics for cluster profile plots.
expr_sub <- expr_mat[sig_genes, , drop = FALSE]
# Reshape to long format for plotting
expr_df <- reshape2::melt(
as.matrix(expr_sub),
varnames = c("gene_id", "sample_id"),
value.name = "cpm"
)
expr_df$genotype <- md$genotype[match(expr_df$sample_id, md$sample_id)]
expr_df$timepoint <- as.numeric(md$timepoint[match(expr_df$sample_id, md$sample_id)])
expr_df$cluster <- clusters$cut[expr_df$gene_id]
# Calculate median expression per cluster/genotype/timepoint
sum_df <- expr_df %>%
dplyr::group_by(cluster, genotype, timepoint) %>%
dplyr::summarise(median_cpm = median(cpm), .groups = "drop") %>%
dplyr::arrange(cluster, genotype, timepoint)
cluster_counts <- expr_df %>%
dplyr::group_by(cluster) %>%
dplyr::summarise(n_genes = dplyr::n_distinct(gene_id), .groups = "drop")
Step 10: Cluster Profile Visualization
I now create line plots showing median expression trajectories for
each cluster. I use median CPM here; you can use median CPM or mean CPM,
or DESeq2 mean vst counts. We have separate lines for each genotype to
show differential temporal kinetics of gene expression that are
genotype-dependent.
geno_colors <- c("D21" = "#1F78B4", "T21" = "#E31A1C")
plot_cluster <- function(df, counts_df, cl, show_legend = TRUE) {
n_genes <- counts_df$n_genes[counts_df$cluster == cl]
plot_data <- df[df$cluster == cl, ]
p <- ggplot2::ggplot(
plot_data,
ggplot2::aes(x = timepoint, y = median_cpm, color = genotype, group = genotype)
) +
ggplot2::geom_line(linewidth = 1) +
ggplot2::geom_point(size = 2.5) +
ggplot2::scale_color_manual(values = geno_colors, name = "Genotype") +
ggplot2::scale_x_continuous(
breaks = c(6, 10, 17),
labels = c("Day 6", "Day 10", "Day 17")
) +
ggplot2::scale_y_continuous(
breaks = scales::pretty_breaks(n = 6),
expand = ggplot2::expansion(mult = c(0.1, 0.05))
) +
ggplot2::labs(
title = paste0("Cluster ", cl, " (n=", n_genes, ")"),
x = "Timepoint",
y = "Median CPM"
) +
ggplot2::theme_minimal() +
ggplot2::theme(
plot.title = ggplot2::element_text(size = 11, hjust = 0.5),
axis.title.x = ggplot2::element_text(size = 10, margin = ggplot2::margin(t = 5)),
axis.title.y = ggplot2::element_text(size = 10, margin = ggplot2::margin(r = 5)),
axis.text = ggplot2::element_text(size = 9),
axis.text.x = ggplot2::element_text(angle = 45, hjust = 1),
legend.title = ggplot2::element_text(size = 10),
legend.text = ggplot2::element_text(size = 9),
legend.position = ifelse(show_legend, "right", "none")
)
return(p)
}
The following code saves individual cluster plots to files
# Save individual cluster profile plots
for (cl in sort(unique(sum_df$cluster))) {
p <- plot_cluster(sum_df, cluster_counts, cl, show_legend = TRUE)
ggplot2::ggsave(
filename = file.path(fig_dir, sprintf("cluster_%02d_profile.svg", cl)),
plot = p,
width = 6.5,
height = 4.5,
units = "in"
)
}
combine_cluster_plots <- function(df, counts_df, ncol, nrow, output_path, width, height) {
all_clusters <- sort(unique(df$cluster))
plot_list <- lapply(all_clusters, function(cl) {
plot_cluster(df, counts_df, cl, show_legend = FALSE)
})
combined <- patchwork::wrap_plots(plot_list, ncol = ncol, nrow = nrow) +
patchwork::plot_layout(guides = "collect") &
ggplot2::theme(legend.position = "right")
ggplot2::ggsave(
filename = output_path,
plot = combined,
width = width,
height = height,
units = "in"
)
return(combined)
}
combined_plot <- combine_cluster_plots(
df = sum_df,
counts_df = cluster_counts,
ncol = 2,
nrow = 5,
output_path = file.path(fig_dir, "all_clusters_combined.svg"),
width = 10,
height = 18
)
combined_plot
Step 11: Summary Statistics
I create a summary of key metrics from the maSigPro analysis here. if
I decide to look at other parameter combinations, I can compare the
results to the summary here.
summary_stats <- data.frame(
total_genes_tested = nrow(expr_mat),
significant_temporal = length(fit$SELEC),
significant_genotype_specific = length(sig_genes),
num_clusters = k_clusters,
polynomial_degree = poly_degree,
q_threshold = q_threshold,
rsq_threshold = rsq_threshold
)
readr::write_csv(summary_stats, file.path(res_dir, "summary_statistics.csv"))
Summary Statistics from maSigPro Analysis
| 12407 |
160362 |
5973 |
10 |
2 |
0.05 |
0.7 |
Step 12: Gene Ontology Enrichment
To derive biological/pathway insight from the maSigPro analysis, I
run GO overrepresentation analysis on each cluster to identify
biological processes associated with each cluster. This helps interpret
the functional significance of co-expressed gene groups. For example,
cluster 5, in which expression is decreased in T21 compared to D21,
looks potentially be some type of progenitor gene expression associated
cluster, given that the enriched GO terms are DNA replication-related
(ie, proliferation genes).
GO term analysis like this allows you to derive BROAD insight – you
will have to dig deeper into the actual genes in each cluster. For
example, are known marker of cell cycle progression in cluster 5?
etc.
A more intuitive example is cluster 6. The enriched GO terms for this
cluster appear to be related to function of mature neurons. In this
differentiation, the cells mature from neural progenitors to mature,
differentiatd neurons. Corroborating this, the lineplot for cluster 6
shows faster acquisition of expression of cluster 6 genes in T21
compared to D21. One broad insight, then, would be that T21 appears to
acquire a neuronal maturation-like transcriptional signature more
quickly over time compared to D21. again, this finding should ideally be
corroborated by your differential gene expression comparisons between
T21 and D21 at each timepoint.
maSigPro allows us to visualize this ‘temporally’ and identify the
genes driving by clustering them into modules.
go_dir_res <- file.path(res_dir, "go_enrichment")
go_dir_figs <- file.path(fig_dir, "go_enrichment")
dir.create(go_dir_res, recursive = TRUE, showWarnings = FALSE)
dir.create(go_dir_figs, recursive = TRUE, showWarnings = FALSE)
GO Enrichment Function
run_cluster_go <- function(gene_list, cluster_id) {
# Skip clusters with too few genes
if (length(gene_list) < 5) return(NULL)
# Convert gene symbols to Entrez IDs
gene_entrez <- clusterProfiler::bitr(
gene_list,
fromType = "SYMBOL",
toType = "ENTREZID",
OrgDb = org.Hs.eg.db
)
if (nrow(gene_entrez) == 0) return(NULL)
# Run GO enrichment (Biological Process)
go_result <- clusterProfiler::enrichGO(
gene = gene_entrez$ENTREZID,
OrgDb = org.Hs.eg.db,
ont = "BP",
pAdjustMethod = "BH",
pvalueCutoff = 0.05,
qvalueCutoff = 0.05,
readable = TRUE
)
if (is.null(go_result) || nrow(as.data.frame(go_result)) == 0) return(NULL)
# Simplify redundant terms
go_simplified <- clusterProfiler::simplify(go_result, cutoff = 0.7)
go_df <- as.data.frame(go_simplified)
if (nrow(go_df) == 0) return(NULL)
# Save results
saveRDS(go_df, file.path(go_dir_res, paste0("cluster_", cluster_id, "_GO_results.rds")))
readr::write_csv(go_df, file.path(go_dir_res, paste0("cluster_", cluster_id, "_GO_results.csv")))
# Create dot plot for top terms
go_top <- go_df %>%
dplyr::slice_head(n = 10) %>%
dplyr::mutate(Description = stringr::str_wrap(Description, 40))
if (nrow(go_top) > 0) {
n_genes <- length(gene_list)
p <- ggplot2::ggplot(
go_top,
ggplot2::aes(x = -log10(pvalue),
y = reorder(Description, -log10(pvalue)),
size = Count)
) +
ggplot2::geom_point(color = "#0DAD8D", alpha = 0.8) +
ggplot2::scale_size_continuous(range = c(3, 8), name = "Gene count") +
ggplot2::scale_y_discrete(position = "right") +
ggplot2::labs(
x = expression("-log"[10] * "(p-value)"),
y = NULL,
title = paste0("Cluster ", cluster_id),
subtitle = paste0("GO Biological Processes (n=", n_genes, " genes)")
) +
ggplot2::theme_minimal() +
ggplot2::theme(
plot.title = ggplot2::element_text(size = 14, hjust = 0),
plot.subtitle = ggplot2::element_text(size = 12, hjust = 0),
axis.title.x = ggplot2::element_text(size = 11),
axis.text.y = ggplot2::element_text(size = 9, hjust = 0),
axis.text.x = ggplot2::element_text(size = 10),
legend.position = "bottom"
)
ggplot2::ggsave(
file.path(go_dir_figs, paste0("cluster_", cluster_id, "_GO_dotplot.svg")),
plot = p, width = 7, height = min(8, 3 + nrow(go_top) * 0.3)
)
}
return(go_df)
}
Run GO Enrichment on All Clusters
all_cluster_go_results <- list()
for (cl in sort(unique(clusters$cut))) {
cluster_genes <- names(clusters$cut)[clusters$cut == cl]
go_res <- run_cluster_go(cluster_genes, cl)
if (!is.null(go_res)) {
all_cluster_go_results[[paste0("cluster_", cl)]] <- go_res
}
}
saveRDS(all_cluster_go_results, file.path(go_dir_res, "all_clusters_GO_results.rds"))
cat("Clusters with significant GO enrichment:", length(all_cluster_go_results), "/", k_clusters, "\n")
## Clusters with significant GO enrichment: 9 / 10
GO Results by Cluster
Cluster 2
|
Biological Process
|
P-value
|
FDR
|
Genes
|
|
cilium assembly
|
2.43e-06
|
4.87e-03
|
25
|
|
cilium organization
|
2.71e-06
|
4.87e-03
|
26
|
Cluster 3
|
Biological Process
|
P-value
|
FDR
|
Genes
|
|
cilium organization
|
1.11e-09
|
5.55e-06
|
64
|
|
cilium assembly
|
7.54e-09
|
1.89e-05
|
59
|
|
microtubule-based movement
|
1.08e-07
|
1.80e-04
|
64
|
|
macroautophagy
|
1.02e-05
|
1.27e-02
|
48
|
|
intraciliary transport
|
1.43e-05
|
1.39e-02
|
13
|
Cluster 4
|
Biological Process
|
P-value
|
FDR
|
Genes
|
|
learning
|
1.72e-05
|
4.15e-02
|
12
|
|
heart formation
|
2.39e-05
|
4.15e-02
|
6
|
Cluster 5
|
Biological Process
|
P-value
|
FDR
|
Genes
|
|
mitotic DNA replication
|
1.88e-07
|
5.73e-04
|
6
|
|
actin filament bundle assembly
|
1.61e-05
|
1.56e-02
|
12
|
|
regulation of DNA replication
|
4.40e-05
|
2.50e-02
|
10
|
|
small GTPase-mediated signal transduction
|
5.91e-05
|
2.57e-02
|
21
|
|
DNA strand elongation involved in DNA replication
|
8.15e-05
|
2.86e-02
|
4
|
Cluster 6
|
Biological Process
|
P-value
|
FDR
|
Genes
|
|
neurotransmitter secretion
|
1.53e-11
|
2.40e-08
|
21
|
|
signal release from synapse
|
1.53e-11
|
2.40e-08
|
21
|
|
synaptic vesicle cycle
|
2.48e-10
|
1.95e-07
|
24
|
|
synaptic vesicle localization
|
7.98e-09
|
5.02e-06
|
12
|
|
neurotransmitter transport
|
1.24e-08
|
6.49e-06
|
21
|
Cluster 7
|
Biological Process
|
P-value
|
FDR
|
Genes
|
|
smoothened signaling pathway
|
3.66e-09
|
1.37e-05
|
19
|
|
morphogenesis of an epithelium
|
8.00e-08
|
1.49e-04
|
34
|
|
chondrocyte differentiation
|
1.48e-07
|
1.84e-04
|
15
|
|
axon guidance
|
1.48e-06
|
9.83e-04
|
20
|
|
proteoglycan metabolic process
|
1.06e-05
|
4.40e-03
|
11
|
Cluster 8
|
Biological Process
|
P-value
|
FDR
|
Genes
|
|
rRNA metabolic process
|
1.35e-15
|
3.84e-12
|
44
|
|
ribosome biogenesis
|
1.78e-15
|
3.84e-12
|
48
|
|
rRNA processing
|
1.19e-12
|
1.28e-09
|
36
|
|
positive regulation of transcription by RNA polymerase I
|
9.15e-06
|
2.64e-03
|
9
|
|
regulation of small GTPase mediated signal transduction
|
3.20e-05
|
8.14e-03
|
28
|
Cluster 9
|
Biological Process
|
P-value
|
FDR
|
Genes
|
|
positive regulation of oligodendrocyte differentiation
|
1.55e-08
|
3.01e-05
|
9
|
|
pancreas development
|
5.61e-07
|
5.00e-04
|
12
|
|
protein localization to cell periphery
|
1.46e-05
|
5.59e-03
|
24
|
|
regulation of nervous system development
|
4.22e-05
|
1.17e-02
|
27
|
|
negative regulation of epithelial cell apoptotic process
|
6.76e-05
|
1.63e-02
|
6
|
Cluster 10
|
Biological Process
|
P-value
|
FDR
|
Genes
|
|
canonical Wnt signaling pathway
|
2.75e-06
|
2.56e-03
|
11
|
|
regionalization
|
3.12e-05
|
1.91e-02
|
11
|
|
eye morphogenesis
|
4.48e-05
|
1.99e-02
|
7
|
|
morphogenesis of an epithelial bud
|
6.05e-05
|
1.99e-02
|
3
|
|
transforming growth factor beta receptor superfamily signaling pathway
|
8.26e-05
|
1.99e-02
|
10
|
Step 13: Expression Heatmaps
Perhaps a more visually ‘intuitive’ way of understanding the temporal
expression of maSigPro-identified genes between D21 and T21 is by a
heatmap. Here, I visualize expression patterns for each cluster using
z-score normalized values in a heatmap generated by the pheatmap R
package. Samples are grouped by genotype and ordered by timepoint (so
first group of samples is D21, then T21).
The heatmaps match the lineplot trend, but I think they are visually
easier to interpret.
if (file.exists("data/counts/vst/WC24_vst_counts.rds")) {
vst_mat <- readRDS("data/counts/vst/WC24_vst_counts.rds")
} else {
vst_mat <- counts_cpm
}
heatmap_dir <- file.path(fig_dir, "heatmaps")
dir.create(heatmap_dir, recursive = TRUE, showWarnings = FALSE)
create_cluster_heatmap <- function(gene_list,
vst_mat,
meta,
cluster_id,
output_path,
width = 7,
height = 7,
show_rownames = FALSE,
show_colnames = FALSE,
cluster_rows = TRUE,
cluster_cols = FALSE,
fontsize_row = 7,
winsorize_limit = 2) {
genes_found <- intersect(gene_list, rownames(vst_mat))
if (length(genes_found) < 2) {
return(NULL)
}
# Order samples: D21 by timepoint, then T21 by timepoint
meta_d21 <- meta %>%
dplyr::filter(genotype == "D21") %>%
dplyr::arrange(timepoint)
meta_t21 <- meta %>%
dplyr::filter(genotype == "T21") %>%
dplyr::arrange(timepoint)
meta_ordered <- dplyr::bind_rows(meta_d21, meta_t21)
sample_order <- meta_ordered$sample_id
vst_subset <- vst_mat[genes_found, sample_order]
vst_zscore <- t(scale(t(vst_subset)))
vst_zscore[vst_zscore > winsorize_limit] <- winsorize_limit
vst_zscore[vst_zscore < -winsorize_limit] <- -winsorize_limit
annotation_df <- meta_ordered %>%
dplyr::select(genotype, timepoint) %>%
dplyr::mutate(
Genotype = genotype,
Timepoint = factor(paste0("D", timepoint), levels = c("D6", "D10", "D17"))
) %>%
dplyr::select(Genotype, Timepoint)
annotation_colors <- list(
Genotype = c(D21 = "#1F78B4", T21 = "#E31A1C"),
Timepoint = c(D6 = "#66C2A5", D10 = "#FC8D62", D17 = "#8DA0CB")
)
heatmap_colors <- grDevices::colorRampPalette(c(
"#053061", "#2166AC", "#4393C3", "#92C5DE",
"#D1E5F0", "#FDDBC7", "#F4A582", "#D6604D",
"#B2182B", "#67001F"
))(100)
quantile_breaks <- unique(quantile(as.vector(vst_zscore),
probs = seq(0, 1, length.out = 101),
na.rm = TRUE))
# Gap position between D21 and T21 groups
n_d21 <- nrow(meta_d21)
gap_positions <- n_d21
p <- pheatmap::pheatmap(
vst_zscore,
color = heatmap_colors,
clustering_distance_rows = "euclidean",
clustering_distance_cols = "euclidean",
clustering_method = "complete",
cluster_rows = cluster_rows,
cluster_cols = cluster_cols,
annotation_col = annotation_df,
annotation_colors = annotation_colors,
show_rownames = show_rownames,
show_colnames = show_colnames,
fontsize = 10,
fontsize_row = fontsize_row,
fontsize_col = 8,
border_color = NA,
scale = "none",
annotation_legend = TRUE,
annotation_names_col = FALSE,
legend = TRUE,
breaks = quantile_breaks,
gaps_col = gap_positions
)
grDevices::png(output_path, width = width, height = height, units = "in", res = 300)
print(p)
grDevices::dev.off()
return(p)
}
# Display heatmaps for all clusters
all_clusters <- sort(unique(clusters$cut))
for (cl in all_clusters) {
cluster_genes <- names(clusters$cut)[clusters$cut == cl]
genes_found <- intersect(cluster_genes, rownames(vst_mat))
if (length(genes_found) < 2) next
# Order samples: D21 samples by timepoint, then T21 samples by timepoint
meta_d21 <- md %>%
dplyr::filter(genotype == "D21") %>%
dplyr::arrange(timepoint)
meta_t21 <- md %>%
dplyr::filter(genotype == "T21") %>%
dplyr::arrange(timepoint)
meta_ordered <- dplyr::bind_rows(meta_d21, meta_t21)
sample_order <- meta_ordered$sample_id
vst_subset <- vst_mat[genes_found, sample_order]
vst_zscore <- t(scale(t(vst_subset)))
vst_zscore[vst_zscore > 3] <- 3
vst_zscore[vst_zscore < -3] <- -3
annotation_df <- meta_ordered %>%
dplyr::select(genotype, timepoint) %>%
dplyr::mutate(
Genotype = genotype,
Timepoint = factor(paste0("D", timepoint), levels = c("D6", "D10", "D17"))
) %>%
dplyr::select(Genotype, Timepoint)
annotation_colors <- list(
Genotype = c(D21 = "#1F78B4", T21 = "#E31A1C"),
Timepoint = c(D6 = "#66C2A5", D10 = "#FC8D62", D17 = "#8DA0CB")
)
heatmap_colors <- grDevices::colorRampPalette(c(
"#053061", "#2166AC", "#4393C3", "#92C5DE",
"#D1E5F0", "#FDDBC7", "#F4A582", "#D6604D",
"#B2182B", "#67001F"
))(100)
quantile_breaks <- unique(quantile(as.vector(vst_zscore),
probs = seq(0, 1, length.out = 101),
na.rm = TRUE))
# Gap position between D21 and T21 groups
n_d21 <- nrow(meta_d21)
gap_positions <- n_d21
n_genes <- length(cluster_genes)
show_names <- n_genes <= 50
p <- pheatmap::pheatmap(
vst_zscore,
color = heatmap_colors,
clustering_distance_rows = "euclidean",
clustering_distance_cols = "euclidean",
clustering_method = "complete",
cluster_rows = TRUE,
cluster_cols = FALSE,
annotation_col = annotation_df,
annotation_colors = annotation_colors,
show_rownames = show_names,
show_colnames = FALSE,
fontsize = 10,
fontsize_row = 7,
fontsize_col = 8,
border_color = NA,
scale = "none",
annotation_legend = TRUE,
annotation_names_col = FALSE,
legend = TRUE,
breaks = quantile_breaks,
gaps_col = gap_positions,
main = paste0("Cluster ", cl, " (n=", n_genes, " genes)")
)
print(p)
}
The following code saves heatmaps to PNG files. Set
eval=TRUE to run:
# Save heatmaps to files
for (cl in sort(unique(clusters$cut))) {
cluster_genes <- names(clusters$cut)[clusters$cut == cl]
n_genes <- length(cluster_genes)
show_names <- n_genes <= 50
height_val <- min(8, max(4, n_genes * 0.08))
create_cluster_heatmap(
gene_list = cluster_genes,
vst_mat = vst_mat,
meta = md,
cluster_id = cl,
output_path = file.path(heatmap_dir, paste0("cluster_", cl, "_heatmap.png")),
width = 5,
height = height_val,
show_rownames = show_names,
show_colnames = FALSE,
cluster_rows = TRUE,
cluster_cols = FALSE,
fontsize_row = 7,
winsorize_limit = 3
)
}
Step 14: Save Results
Finally, I save the cluster assignments and analysis outputs for
downstream use. Depending on the number of genes that are
time-dependent, maSigPro can take 15-20 minutes to run and cluster your
genes. Therefore I recommend saving the output of maSigPro immediately
so that you don’t have to re-run it every time.
# Save cluster assignments
readr::write_csv(cl_assign, file.path(res_dir, "cluster_assignments.csv"))
saveRDS(cl_assign, file.path(res_dir, "cluster_assignments.rds"))
cat("Results saved to:", res_dir, "\n")
## Results saved to: results/masigpro
cat("Figures saved to:", fig_dir, "\n")
## Figures saved to: results/figures/masigpro
Summary
In summary, maSigPro is a useful R package for identified genes with
genotype-specific temporal expression patterns and grouped them into
functionally coherent clusters.
If you have time-series bulk RNA-seq data with 3+ timepoints, and you
have two groups of samples (whether that is treatment or genotype or
some condition), then maSigPro allows you identify clusters of genes
whose expression over time is temporally .
While some maSigPro clusters are largely similar in expressio between
the two groups, I think the main biological insight here
For example, cluster 10 is enriched for the GO term ‘canonical Wnt
signaling pathway.’ In the paper that I obtained this data from, the
authors discuss Wnt signaling pathway activity as being dampened or
decreased in T21 compared to D21. Cluster 10 corroborates this claim, as
. Furthermore, we can see that this cluster enriched for Wnt signaling
pathway genes seems to be highest in expression during the neural
progenitor phase. Hence, Wnt signaling could be modulating the
transition of neural progenitor to mature neuron. Because expression of
this cluster is DECREASED in T21 relative to D21, this suggests perhaps
faster transition of neural progenitor to mature neuron in T21. As
explained before, this finding of faster acquistion of mature neuron
identity is corroborated by other clusters, such as cluster 6.
These are obviously broad claims that need further evidence to
substantiate, but the goal of maSigPro is to give you direction for what
directions/avenues you can pursue for your data.
Results Overview
| Genes tested |
12,407 |
| Significant temporal genes |
160,362 (1292.5%) |
| Genotype-specific genes |
5,973 (48.1%) |
| Clusters identified |
10 |
| Clusters with GO enrichment |
9 |
Analysis Parameters
| Expression filter |
Mean CPM > 5 |
| Polynomial degree |
2 |
| FDR threshold (p.vector) |
0.05 |
| Alpha threshold (T.fit) |
0.05 |
| R² threshold |
0.7 |
| Number of clusters |
10 |
Output Files
All results are saved to:
- Cluster assignments:
results/masigpro/cluster_assignments.csv
- Summary statistics:
results/masigpro/summary_statistics.csv
- GO enrichment:
results/masigpro/go_enrichment/
- Figures:
results/figures/masigpro/
References
Conesa A, Nueda MJ, et al. (2006). maSigPro: a method to identify
significantly differential expression profiles in time-course microarray
experiments. Bioinformatics, 22(9):1096-1102. doi:10.1093/bioinformatics/btl056
Nueda MJ, Tarazona S, Conesa A. (2014). Next maSigPro: updating
maSigPro bioconductor package for RNA-seq time series.
Bioinformatics, 30(18):2598-2602. doi:10.1093/bioinformatics/btu333
LS0tDQp0aXRsZTogIm1hU2lnUHJvIFR1dG9yaWFsIg0Kc3VidGl0bGU6ICJtYVNpZ1BybyBSIHBhY2thZ2Ugd29ya2Zsb3cgZm9yIHRpbWUtY291cnNlIFJOQS1zZXEgYW5hbHlzaXMgZm9yIGNvbmRpdGlvbi1zcGVjaWZpYyB0ZW1wb3JhbCBnZW5lIGV4cHJlc3Npb24gcGF0dGVybnMiDQphdXRob3I6ICJab2hlYiBLaGFuIg0KZGF0ZTogIlNlcHRlbWJlciAyLCAyMDI1Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogdHJ1ZQ0KICAgICAgc21vb3RoX3Njcm9sbDogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMg0KICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IGZsYXRseQ0KICAgIGhpZ2hsaWdodDogcHlnbWVudHMNCiAgICBmaWdfd2lkdGg6IDEwDQogICAgZmlnX2hlaWdodDogNg0KICAgIGZpZ19jYXB0aW9uOiB0cnVlDQogICAgZGZfcHJpbnQ6IGthYmxlDQotLS0NCg0KPHN0eWxlPg0KLyogPT09PT09PT09PSBMQVlPVVQgRklYOiBQcmV2ZW50IFRPQyBvdmVybGFwID09PT09PT09PT0gKi8NCi5jb2wtbWQtOSB7DQogIHdpZHRoOiA3NSU7DQogIG1hcmdpbi1sZWZ0OiAyNSU7DQp9DQoNCi5jb2wtbWQtMyB7DQogIHdpZHRoOiAyMiU7DQogIHBvc2l0aW9uOiBmaXhlZDsNCiAgdG9wOiA1MHB4Ow0KICBsZWZ0OiAwOw0KICBoZWlnaHQ6IGNhbGMoMTAwdmggLSA1MHB4KTsNCiAgb3ZlcmZsb3cteTogYXV0bzsNCiAgcGFkZGluZzogMTVweDsNCiAgYmFja2dyb3VuZC1jb2xvcjogI2Y4ZjlmYTsNCiAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2RlZTJlNjsNCn0NCg0KQG1lZGlhIChtYXgtd2lkdGg6IDk5MnB4KSB7DQogIC5jb2wtbWQtOSB7DQogICAgd2lkdGg6IDEwMCU7DQogICAgbWFyZ2luLWxlZnQ6IDA7DQogIH0NCiAgLmNvbC1tZC0zIHsNCiAgICBwb3NpdGlvbjogcmVsYXRpdmU7DQogICAgd2lkdGg6IDEwMCU7DQogICAgaGVpZ2h0OiBhdXRvOw0KICAgIGJvcmRlci1yaWdodDogbm9uZTsNCiAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2RlZTJlNjsNCiAgfQ0KfQ0KDQovKiA9PT09PT09PT09IFR5cG9ncmFwaHkgPT09PT09PT09PSAqLw0KYm9keSB7DQogIGZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICJTZWdvZSBVSSIsIFJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgQXJpYWwsIHNhbnMtc2VyaWY7DQogIGZvbnQtc2l6ZTogMTVweDsNCiAgbGluZS1oZWlnaHQ6IDEuNzsNCiAgY29sb3I6ICMzMzM7DQp9DQoNCi8qID09PT09PT09PT0gQ29kZSBibG9ja3MgPT09PT09PT09PSAqLw0KcHJlIHsNCiAgYmFja2dyb3VuZC1jb2xvcjogI2Y4ZjlmYTsNCiAgYm9yZGVyOiAxcHggc29saWQgI2UxZTRlODsNCiAgYm9yZGVyLXJhZGl1czogNnB4Ow0KICBwYWRkaW5nOiAxNHB4Ow0KICBmb250LXNpemU6IDEzcHg7DQogIG92ZXJmbG93LXg6IGF1dG87DQp9DQoNCmNvZGUgew0KICBmb250LWZhbWlseTogIlNGTW9uby1SZWd1bGFyIiwgTWVubG8sIE1vbmFjbywgQ29uc29sYXMsIG1vbm9zcGFjZTsNCiAgZm9udC1zaXplOiAxM3B4Ow0KfQ0KDQpwIGNvZGUsIGxpIGNvZGUgew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjFmM2Y1Ow0KICBwYWRkaW5nOiAycHggNnB4Ow0KICBib3JkZXItcmFkaXVzOiA0cHg7DQogIGZvbnQtc2l6ZTogMTJweDsNCn0NCg0KLyogPT09PT09PT09PSBUYWJsZXMgPT09PT09PT09PSAqLw0KdGFibGUgew0KICB3aWR0aDogMTAwJTsNCiAgbWFyZ2luOiAyMHB4IDA7DQogIGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7DQogIGZvbnQtc2l6ZTogMTRweDsNCn0NCg0KdGggew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjMmMzZTUwOw0KICBjb2xvcjogd2hpdGU7DQogIHBhZGRpbmc6IDEwcHggMTRweDsNCiAgdGV4dC1hbGlnbjogbGVmdDsNCiAgZm9udC13ZWlnaHQ6IDYwMDsNCn0NCg0KdGQgew0KICBwYWRkaW5nOiAxMHB4IDE0cHg7DQogIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjZGVlMmU2Ow0KfQ0KDQp0cjpudGgtY2hpbGQoZXZlbikgew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmOWZhOw0KfQ0KDQp0cjpob3ZlciB7DQogIGJhY2tncm91bmQtY29sb3I6ICNlZWYyZjU7DQp9DQoNCi8qIEdPIGVucmljaG1lbnQgdGFibGVzIC0gZGlzdGluY3Qgc3R5bGluZyAqLw0KLmdvLXRhYmxlIHsNCiAgbWFyZ2luOiAxNXB4IDA7DQp9DQoNCi5nby10YWJsZSB0aCB7DQogIGJhY2tncm91bmQtY29sb3I6ICMxOGJjOWM7DQp9DQoNCi8qID09PT09PT09PT0gU2VjdGlvbiBoZWFkZXJzID09PT09PT09PT0gKi8NCmgxIHsNCiAgY29sb3I6ICMyYzNlNTA7DQogIGZvbnQtc2l6ZTogMS44ZW07DQogIGZvbnQtd2VpZ2h0OiA2MDA7DQogIGJvcmRlci1ib3R0b206IDNweCBzb2xpZCAjMThiYzljOw0KICBwYWRkaW5nLWJvdHRvbTogMTBweDsNCiAgbWFyZ2luLXRvcDogNTBweDsNCiAgbWFyZ2luLWJvdHRvbTogMjBweDsNCn0NCg0KaDE6Zmlyc3Qtb2YtdHlwZSB7DQogIG1hcmdpbi10b3A6IDA7DQp9DQoNCmgyIHsNCiAgY29sb3I6ICMyYzNlNTA7DQogIGZvbnQtc2l6ZTogMS40ZW07DQogIGZvbnQtd2VpZ2h0OiA2MDA7DQogIG1hcmdpbi10b3A6IDM1cHg7DQogIG1hcmdpbi1ib3R0b206IDE1cHg7DQp9DQoNCmgzIHsNCiAgY29sb3I6ICMzNDQ5NWU7DQogIGZvbnQtc2l6ZTogMS4xNWVtOw0KICBmb250LXdlaWdodDogNjAwOw0KICBtYXJnaW4tdG9wOiAyNXB4Ow0KICBtYXJnaW4tYm90dG9tOiAxMHB4Ow0KfQ0KDQovKiA9PT09PT09PT09IENhbGxvdXQgYm94ZXMgPT09PT09PT09PSAqLw0KLm5vdGUtYm94IHsNCiAgYmFja2dyb3VuZC1jb2xvcjogI2U4ZjRmZDsNCiAgYm9yZGVyLWxlZnQ6IDRweCBzb2xpZCAjMjE5NkYzOw0KICBwYWRkaW5nOiAxNXB4IDE4cHg7DQogIG1hcmdpbjogMjBweCAwOw0KICBib3JkZXItcmFkaXVzOiAwIDZweCA2cHggMDsNCn0NCg0KLnRpcC1ib3ggew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjZThmNWU5Ow0KICBib3JkZXItbGVmdDogNHB4IHNvbGlkICM0Y2FmNTA7DQogIHBhZGRpbmc6IDE1cHggMThweDsNCiAgbWFyZ2luOiAyMHB4IDA7DQogIGJvcmRlci1yYWRpdXM6IDAgNnB4IDZweCAwOw0KfQ0KDQoud2FybmluZy1ib3ggew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOGUxOw0KICBib3JkZXItbGVmdDogNHB4IHNvbGlkICNmZjk4MDA7DQogIHBhZGRpbmc6IDE1cHggMThweDsNCiAgbWFyZ2luOiAyMHB4IDA7DQogIGJvcmRlci1yYWRpdXM6IDAgNnB4IDZweCAwOw0KfQ0KDQouaW5mby1ib3ggew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjNmNGY2Ow0KICBib3JkZXItbGVmdDogNHB4IHNvbGlkICM2YjcyODA7DQogIHBhZGRpbmc6IDE1cHggMThweDsNCiAgbWFyZ2luOiAyMHB4IDA7DQogIGJvcmRlci1yYWRpdXM6IDAgNnB4IDZweCAwOw0KfQ0KDQovKiA9PT09PT09PT09IEZpZ3VyZSBjYXB0aW9ucyA9PT09PT09PT09ICovDQouZmlndXJlIHsNCiAgbWFyZ2luOiAyNXB4IDA7DQp9DQoNCmZpZ2NhcHRpb24sIC5jYXB0aW9uIHsNCiAgZm9udC1zdHlsZTogaXRhbGljOw0KICBjb2xvcjogIzZjNzU3ZDsNCiAgZm9udC1zaXplOiAxM3B4Ow0KICBtYXJnaW4tdG9wOiAxMHB4Ow0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQoNCi8qID09PT09PT09PT0gVE9DIHN0eWxpbmcgPT09PT09PT09PSAqLw0KLnRvY2lmeSB7DQogIGJvcmRlcjogbm9uZTsNCiAgYm9yZGVyLXJhZGl1czogMDsNCn0NCg0KLnRvY2lmeS1oZWFkZXIgew0KICBmb250LXNpemU6IDEzcHg7DQp9DQoNCi50b2NpZnkgdWwsIC50b2NpZnkgbGkgew0KICBsaW5lLWhlaWdodDogMS44Ow0KfQ0KDQoudG9jaWZ5IC5hY3RpdmUgew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjMThiYzljICFpbXBvcnRhbnQ7DQogIGNvbG9yOiB3aGl0ZSAhaW1wb3J0YW50Ow0KfQ0KDQovKiA9PT09PT09PT09IEhvcml6b250YWwgcnVsZSA9PT09PT09PT09ICovDQpociB7DQogIGJvcmRlcjogbm9uZTsNCiAgYm9yZGVyLXRvcDogMXB4IHNvbGlkICNlMWU0ZTg7DQogIG1hcmdpbjogNDBweCAwOw0KfQ0KPC9zdHlsZT4NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KGtuaXRyKQ0KDQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIGVjaG8gPSBUUlVFLA0KICBldmFsID0gVFJVRSwNCiAgd2FybmluZyA9IEZBTFNFLA0KICBtZXNzYWdlID0gRkFMU0UsDQogIGVycm9yID0gRkFMU0UsDQogIGZpZy53aWR0aCA9IDEwLA0KICBmaWcuaGVpZ2h0ID0gNiwNCiAgZmlnLmFsaWduID0gImNlbnRlciIsDQogIG91dC53aWR0aCA9ICI5MCUiLA0KICBkcGkgPSAzMDANCikNCg0Kb3B0aW9ucyh3YXJuID0gLTEpDQpgYGANCg0KIyBJbnRyb2R1Y3Rpb24NCg0KVGhpcyBpcyBhIHdhbGt0aHJvdWdoL3R1dG9yaWFsIGZvciBob3cgdG8gY2x1c3RlciBhbmQgYW5hbHl6ZSB0aW1lLWNvdXJzZSBidWxrIFJOQS1zZXEgZGF0YSB1c2luZyB0aGUgW21hU2lnUHJvXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvbWFTaWdQcm8vKSBCaW9jb25kdWN0b3IgcGFja2FnZSBpbiBSLiBJIGNyZWF0ZWQgdGhpcyB3b3JrZmxvdyBiZWNhdXNlIHRoZXJlIGlzIGp1c3Qgb25lIHR1dG9yaWFsIGF2YWlsYWJsZSBvbmxpbmUgZm9yIG1hU2lnUHJvLCBhbmQgaXQncyBmYWlybHkgb3V0ZGF0ZWQgYXQgdGhpcyBwb2ludC4gSSBob3BlIG90aGVycyBjYW4gdXNlIHRoaXMgd29ya2Zsb3cgYXMgYSBndWlkZSBmb3IgaG93IHRvIGFuYWx5emUgdGhlciBkYXRhIHdpdGggbWFTaWdQcm8uDQoNCiMjIFdoYXQgaXMgbWFTaWdQcm8/DQoNCioqbWFTaWdQcm8qKiAobWljcm9hcnJheSBTaWduaWZpY2FudCBQcm9maWxlcykgaXMgYSBtZXRob2QgZm9yIGNsdXN0ZXJpbmcgdGltZS1jb3Vyc2UgUk5BLXNlcS9taWNyb2FycmF5IGRhdGEuIEl0IHVzZXMgcG9seW5vbWlhbCByZWdyZXNzaW9uIG1vZGVscyB0byBhbmFseXplIHRpbWUtc2VyaWVzIGdlbmUgZXhwcmVzc2lvbiBkYXRhLiBPbmUgcmVhc29uIHdoeSBJIGxpa2UgdGhpcyBwYWNrYWdlIG92ZXIgbm9ybWFsIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGlzIHRoYXQgbWFTaWdQcm8gaGFzIHBhcmFtZXRlcnMgdGhhdCBhbGxvdyB5b3UgdG8gZmluZXR1bmUgdGhlIHN0cmluZ2VuY3kvdGhyZXNob2xkcyBmb3IgZGlmZmVyZW50IHBhcnRzIG9mIHRoZSBnZW5lIGNsdXN0ZXJpbmcgcHJvY2VzcywgcGFydGljdWxhcmx5IGJldHdlZW4gY29uZGl0aW9ucy4gDQoNCkJyb2FkbHksIG1hU2lnUHJvIGRvZXMgdGhlIGZvbGxvdzoNCg0KLSBJZGVudGlmaWVzIGdlbmVzIHdpdGggc2lnbmlmaWNhbnQgdGVtcG9yYWwgZXhwcmVzc2lvbiBjaGFuZ2VzDQotIERldGVjdHMgY29uZGl0aW9uLXNwZWNpZmljIChlLmcuIGdlbm90eXBlKSBwYXR0ZXJucw0KLSBDbHVzdGVycyBnZW5lcyBieSBleHByZXNzaW9uIHRyYWplY3RvcnkNCi0gTW9kZWxzIG5vbi1saW5lYXIgdGVtcG9yYWwgZHluYW1pY3MNCg0KPGRpdiBjbGFzcz0ibm90ZS1ib3giPg0KKiptYVNpZ1BybyB1c2UgY2FzZToqKiBFeHBlcmltZW50cyB3aXRoIG11bHRpcGxlIHRpbWUgcG9pbnRzIChpZGVhbGx5IDMrIHRpbWVwb2ludHMpLCAyKyBjb25kaXRpb25zL2dyb3Vwcywgbj0zIHJlcGxpY2F0ZXMgcGVyIGdyb3VwLiBJZiB5b3UgYXJlIGludGVyZXN0ZWQgaW4gYnJvYWQsIHRlbXBvcmFsIHRyZW5kcyByYXRoZXIgdGhhbiBzaW5nbGUgdGltZXBvaW50IHBhaXJ3aXNlIGNvbXBhcmlzb25zIGJldHdlZW4gY29uZGl0aW9uL2dlbm90eXBlLCB0aGVuIG1hU2lnUHJvIGlzIHBhcnRpY3VsYXJseSB1c2VmdWwuDQo8L2Rpdj4NCg0KIyMgcmVxdWlyZWQgZGVwZW5kZW5jaWVzDQoNClRoaXMgd29ya2Zsb3cgdHV0b3JpYWwgdXNlcyB0aGUgZmxsb3dpbmcgcmVxdWlyZWQgcGFja2FnZXM6DQoNCmBgYHINCiMgQmlvY29uZHVjdG9yIHBhY2thZ2VzDQppZiAoIXJlcXVpcmVOYW1lc3BhY2UoIkJpb2NNYW5hZ2VyIiwgcXVpZXRseSA9IFRSVUUpKQ0KICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikNCg0KQmlvY01hbmFnZXI6Omluc3RhbGwoYygibWFTaWdQcm8iLCAiY2x1c3RlclByb2ZpbGVyIiwgIm9yZy5Icy5lZy5kYiIpKQ0KDQojIENSQU4gcGFja2FnZXMNCmluc3RhbGwucGFja2FnZXMoYygiZ2dwbG90MiIsICJkcGx5ciIsICJ0aWR5ciIsICJyZXNoYXBlMiIsDQogICAgICAgICAgICAgICAgICAgInBhdGNod29yayIsICJwaGVhdG1hcCIsICJyZWFkciIsICJzdHJpbmdyIikpDQpgYGANCg0KIyMgUHVibGljbHkgYXZhaWxhYmxlIGRhdGFzZXQgZm9yIHRoaXMgd29ya2Zsb3cNCg0KVGhpcyB0dXRvcmlhbCB1c2VzIHRoZSBhIHB1YmxpY2x5IGF2YWlsYWJsZSB0aW1lLWNvdXJzZSBidWxrIFJOQS1zZXEgZGF0YSBmcm9tIHRoZSByZWZlcmVuY2UgYmVsb3cuIHNwZWNpZmljYWxseSwgdGhlIGlucHV0IGZvciBtYVNpZ1BybyBpcyBub3JtYWxpemVkIChjYW4gY2hlY2sgdGhlIHBhY2thZ2UgdmlnbmV0dGUgZm9yIG1vcmUgZGV0YWlscykuIEJyaWVmbHksIEkgdXNlZCBlZGdlUiBUTU0tbm9ybWFsaXplZCBjb3VudHMgcGVyIG1pbGxpb24gZm9yIHRoaXMgYW5hbHlzaXMuDQoNClRoaXMgZGF0YXNldCBoYXMgdHdvIGdlbm90eXBlcyBjb21wYXJpbmcgRDIxIChkaXNvbWljKSB2cyBUMjEgKHRyaXNvbWljKSBnZW5vdHlwZXMgYWNyb3NzIHRocmVlIGRldmVsb3BtZW50YWwgdGltZSBwb2ludHMgKERheSA2LCAxMCwgYW5kIDE3KS4NCg0KKipEYXRhIHNvdXJjZToqKiBNYXJ0aW5leiBKTCBldCBhbC4gKlRyYW5zY3JpcHRpb25hbCBjb25zZXF1ZW5jZXMgb2YgdHJpc29teSAyMSBvbiBuZXVyYWwgaW5kdWN0aW9uLiogRnJvbnQgQ2VsbCBOZXVyb3NjaS4gMjAyNDsxODoxMzQxMTQxLiBbZG9pOjEwLjMzODkvZm5jZWwuMjAyNC4xMzQxMTQxXShodHRwczovL2RvaS5vcmcvMTAuMzM4OS9mbmNlbC4yMDI0LjEzNDExNDEpDQoNCiMjIFdvcmtmbG93IE92ZXJ2aWV3DQoNCnwgU3RlcCB8IERlc2NyaXB0aW9uIHwNCnw6LS0tLS18Oi0tLS0tLS0tLS0tLXwNCnwgMSB8IExvYWQgbGlicmFyaWVzIGFuZCBkYXRhIHwNCnwgMiB8IEZpbHRlciBsb3dseSBleHByZXNzZWQgZ2VuZXMgfA0KfCAzIHwgUHJlcGFyZSBleHBlcmltZW50YWwgZGVzaWduIG1hdHJpeCB8DQp8IDQgfCBDcmVhdGUgcG9seW5vbWlhbCByZWdyZXNzaW9uIGRlc2lnbiB8DQp8IDUgfCBJZGVudGlmeSB0ZW1wb3JhbGx5IHNpZ25pZmljYW50IGdlbmVzIHwNCnwgNiB8IERldGVjdCBnZW5vdHlwZS1zcGVjaWZpYyBwYXR0ZXJucyB8DQp8IDcgfCBFeHRyYWN0IHNpZ25pZmljYW50IGdlbmVzIHwNCnwgOCB8IENsdXN0ZXIgZ2VuZXMgYnkgdHJhamVjdG9yeSB8DQp8IDkgfCBQcmVwYXJlIHZpc3VhbGl6YXRpb24gZGF0YSB8DQp8IDEwIHwgUGxvdCBjbHVzdGVyIHByb2ZpbGVzIHwNCnwgMTEgfCBHZW5lcmF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MgfA0KfCAxMiB8IEdPIGVucmljaG1lbnQgYW5hbHlzaXMgfA0KfCAxMyB8IEV4cHJlc3Npb24gaGVhdG1hcHMgfA0KfCAxNCB8IFNhdmUgcmVzdWx0cyB8DQoNCiMgU3RlcCAxOiBMb2FkIHJlcXVpcmVkIGxpYnJhcmllcyBhbmQgZGF0YQ0KDQpMb2FkIHRoZSByZXF1aXJlZCBwYWNrYWdlcyBhbmQgaW1wb3J0IGV4cHJlc3Npb24gZGF0YS4gbWFTaWdQcm8gcmVxdWlyZXM6DQoNCi0gKipFeHByZXNzaW9uIG1hdHJpeDoqKiBHZW5lcyAocm93cykgw5cgU2FtcGxlcyAoY29sdW1ucyksIGNvdW50cyBwZXIgbWlsbGlvbiAoQ1BNKSBvciBub3JtYWxpemVkIGNvdW50cy4gWW91IEFSRSBhYmxlIHRvIHVzZSByYXcgY291bnRzIChtYVNpZ1BybyBoYXMgYW4gaW50ZXJuYWwgbm9ybWFsaXphdGlvbiBvcHRpb24pLCBidXQgSSBwcmVmZXIgdG8gdXNlIFRNTS1ub3JtYWxpemVkIENQTSBiZWNhdXNlIGl0J3Mgd2VsbC12YWxpZGF0ZWQuDQotICoqTWV0YWRhdGE6KiogU2FtcGxlIGluZm9ybWF0aW9uIHdpdGggZ2Vub3R5cGUsIHRpbWVwb2ludCwgYW5kIHJlcGxpY2F0ZSBjb2x1bW5zIHRoYXQgd2Ugd2lsbCB1c2UgdG8gY29uc3RydWN0IHRoZSBleHBlcmltZW50YWwgZGVzaWduIA0KDQpgYGB7ciBzdGVwMS1sb2FkLWRhdGEsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXJyb3I9RkFMU0V9DQojIExvYWQgcmVxdWlyZWQgcGFja2FnZXMNCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7DQogIGxpYnJhcnkoZ2dwbG90MikNCiAgbGlicmFyeShkcGx5cikNCiAgbGlicmFyeSh0aWR5cikNCiAgbGlicmFyeShyZXNoYXBlMikNCiAgbGlicmFyeShwYXRjaHdvcmspDQogIGxpYnJhcnkocGhlYXRtYXApDQogIGxpYnJhcnkocmVhZHIpDQogIGxpYnJhcnkoc3RyaW5ncikNCiAgbGlicmFyeShtYVNpZ1BybykNCiAgbGlicmFyeShjbHVzdGVyUHJvZmlsZXIpDQogIGxpYnJhcnkob3JnLkhzLmVnLmRiKQ0KfSkNCg0KIyBMb2FkIGV4cHJlc3Npb24gZGF0YSBhbmQgbWV0YWRhdGEgZnJvbSB0aGUgcHVibGljbHkgYXZhaWxhYmxlIGRhdGFzZXQNCmNvdW50c19jcG0gPC0gcmVhZFJEUygiZGF0YS9jb3VudHMvY3BtL1dDMjRfY3BtLnJkcyIpDQptZXRhZGF0YSA8LSByZWFkUkRTKCJkYXRhL21ldGFkYXRhL1dDMjRfbWV0YWRhdGFfY2xlYW4ucmRzIikNCg0KIyBDcmVhdGUgb3V0cHV0IGRpcmVjdG9yaWVzDQpmaWdfZGlyIDwtICJyZXN1bHRzL2ZpZ3VyZXMvbWFzaWdwcm8iDQpyZXNfZGlyIDwtICJyZXN1bHRzL21hc2lncHJvIg0KZGlyLmNyZWF0ZShmaWdfZGlyLCByZWN1cnNpdmUgPSBUUlVFLCBzaG93V2FybmluZ3MgPSBGQUxTRSkNCmRpci5jcmVhdGUocmVzX2RpciwgcmVjdXJzaXZlID0gVFJVRSwgc2hvd1dhcm5pbmdzID0gRkFMU0UpDQpgYGANCg0KIyMjIExvb2sgYXQgbWV0YWRhdGENCg0KSSB3aWxsIHNob3cgdGhlIGV4cGVyaW1lbnRhbCBkZXNpZ24gYmVmb3JlIHByb2NlZWRpbmcgc28gdGhhdCBpdCdzIGNsZWFyIGhvdyB0byBhZGFwdCB0aGlzIHdvcmtmbG93IHRvIHlvdXIgc3BlY2lmaWMgZXhwZXJpbWVudGFsIGRlc2lnbi4NCg0KYGBge3Igc3RlcDEtaW5zcGVjdC1tZXRhZGF0YSwgZWNobz1UUlVFfQ0KIyBWaWV3IGNvbXBsZXRlIG1ldGFkYXRhDQprbml0cjo6a2FibGUobWV0YWRhdGEsIGNhcHRpb24gPSAiU2FtcGxlIE1ldGFkYXRhIikNCg0KIyBTYW1wbGUgY291bnRzIHBlciBncm91cA0KZ2Vub190aW1lX3RhYmxlIDwtIHRhYmxlKG1ldGFkYXRhJGdlbm90eXBlLCBtZXRhZGF0YSR0aW1lcG9pbnQpDQprbml0cjo6a2FibGUoZ2Vub190aW1lX3RhYmxlLA0KICAgICAgICAgICAgIGNhcHRpb24gPSAiU2FtcGxlcyBwZXIgR2Vub3R5cGUgYW5kIFRpbWVwb2ludCIsDQogICAgICAgICAgICAgY29sLm5hbWVzID0gYygiRGF5IDYiLCAiRGF5IDEwIiwgIkRheSAxNyIpKQ0KYGBgDQoNCiMgU3RlcCAyOiBGaWx0ZXIgRXhwcmVzc2lvbiBNYXRyaXgNCg0KVGhpcyBzdGVwIGlzIG9wdGlvbmFsLCBidXQgSSBoaWdobHkgcmVjb21tZW5kZWQgYWRkaW5nIHNvbWUgc3RlcCBpbiB3aGljaCB5b3UgZmlsdGVyIG9yIHJlbW92ZSBnZW5lcyB3aG9zZSBleHByZXNzaW9uIGlzIGxvdyBhdCBhbnkgZ2l2ZW4gc3RhZ2UgZm9yIGEgZ2l2ZW4gY29uZGl0aW9uL3RyZWF0bWVudC4gSGVyZSwgSSBScmVtb3ZlIGxvd2x5IGV4cHJlc3NlZCBnZW5lcyB0byByZWR1Y2Ugbm9pc2UgYW5kIGltcHJvdmUgc3RhdGlzdGljYWwgcG93ZXIgZm9yIGxhdGVyIHN0ZXBzIGluIHRoZSBtYVNpZ1BybyB3b3JrZmxvdy4gR2VuZXMgbXVzdCBoYXZlIG1lYW4gQ1BNID4gNSBpbiBhdCBsZWFzdCBvbmUgZ2Vub3R5cGUtdGltZXBvaW50IGNvbWJpbmF0aW9uIChpZSwgYXQgYSBzcGVjaWZpYyB0aW1lcG9pbnQgZm9yIGEgc2V0IG9mIHJlcGxpY2F0ZXMgZm9yIGEgZ2Vub3R5cGUvc3RhZ2UpDQoNCmBgYHtyIHN0ZXAyLWZpbHRlciwgZWNobz1UUlVFfQ0KbWluX2NwbSA8LSA1DQoNCiMgc2VsZWN0IHJlbGV2YW50IHNhbXBsZXMNCm1ldGFkYXRhX2ZpbHRlciA8LSBtZXRhZGF0YSAlPiUNCiAgZHBseXI6OmZpbHRlcihnZW5vdHlwZSAlaW4lIGMoIkQyMSIsICJUMjEiKSkgJT4lDQogIGRwbHlyOjpzZWxlY3Qoc2FtcGxlX2lkLCBnZW5vdHlwZSwgdGltZXBvaW50KQ0KDQojIENhbGN1bGF0ZSBtZWFuIENQTSBwZXIgZ2VuZSBwZXIgZ2Vub3R5cGUtdGltZXBvaW50IGdyb3VwDQpnZW5lX21lYW5zIDwtIGNvdW50c19jcG0gJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oImdlbmVfaWQiKSAlPiUNCiAgdGlkeXI6OnBpdm90X2xvbmdlcigtZ2VuZV9pZCwgbmFtZXNfdG8gPSAic2FtcGxlX2lkIiwgdmFsdWVzX3RvID0gImNwbSIpICU+JQ0KICBkcGx5cjo6aW5uZXJfam9pbihtZXRhZGF0YV9maWx0ZXIsIGJ5ID0gInNhbXBsZV9pZCIpICU+JQ0KICBkcGx5cjo6Z3JvdXBfYnkoZ2VuZV9pZCwgZ2Vub3R5cGUsIHRpbWVwb2ludCkgJT4lDQogIGRwbHlyOjpzdW1tYXJpc2UobWVhbl9jcG0gPSBtZWFuKGNwbSksIC5ncm91cHMgPSAiZHJvcCIpICU+JQ0KICBkcGx5cjo6Z3JvdXBfYnkoZ2VuZV9pZCkgJT4lDQogIGRwbHlyOjpzdW1tYXJpc2UobWF4X21lYW5fY3BtID0gbWF4KG1lYW5fY3BtKSwgLmdyb3VwcyA9ICJkcm9wIikNCg0KIyBLZWVwIGdlbmVzIHBhc3NpbmcgdGhlIGV4cHJlc3Npb24gdGhyZXNob2xkDQprZWVwX2dlbmVzIDwtIGdlbmVfbWVhbnMkZ2VuZV9pZFtnZW5lX21lYW5zJG1heF9tZWFuX2NwbSA+IG1pbl9jcG1dDQpleHByX21hdCA8LSBjb3VudHNfY3BtW2tlZXBfZ2VuZXMsIF0NCg0KY2F0KCJHZW5lcyBiZWZvcmUgZmlsdGVyaW5nOiIsIG5yb3coY291bnRzX2NwbSksICJcbiIpDQpjYXQoIkdlbmVzIGFmdGVyIGZpbHRlcmluZzoiLCBucm93KGV4cHJfbWF0KSwgIlxuIikNCmBgYA0KDQojIFN0ZXAgMzogUHJlcGFyZSBleHBlcmltZW50YWwgZGVzaWduIG1hdHJpeA0KDQptYVNpZ1BybyByZXF1aXJlcyBhbiBleHBlcmltZW50YWwgZGVzaWduIG1hdHJpeCBmb3JtYXQgd2l0aCBjb2x1bW5zIGZvcjoNCg0KLSAqKlRpbWU6KiogTnVtZXJpYyB0aW1lIHBvaW50IHZhbHVlcyBmb3IgZWFjaCB0aW1lcG9pbnQgaW4gdGltZS1zZXJpZXMNCi0gKipSZXBsaWNhdGU6KiogSWRlbnRpZmllciBmb3IgZWFjaCByZXBsaWNhdGUNCi0gKipHcm91cCBjb2x1bW5zOioqIEJpbmFyeSAoMCBvciAxKSB2YWx1ZXMgZm9yIGVhY2ggZXhwZXJpbWVudGFsIGdyb3VwDQoNCkkgc2hvdyB0aGUgZXhwZXJpbWVudGFsIG1hdHJpeCBmb3IgbWFTaWdQcm8gYmVsb3cNCg0KYGBge3Igc3RlcDMtcHJlcGFyZS1kZXNpZ24sIGVjaG89VFJVRX0NCiMgcHJlcGFyZSBtZXRhZGF0YSB3aXRoIG51bWVyaWMgdGltZXBvaW50cw0KbWQgPC0gbWV0YWRhdGEgJT4lDQogIGRwbHlyOjpmaWx0ZXIoZ2Vub3R5cGUgJWluJSBjKCJEMjEiLCAiVDIxIikpICU+JQ0KICBkcGx5cjo6c2VsZWN0KHNhbXBsZV9pZCwgZ2Vub3R5cGUsIHRpbWVwb2ludCwgcmVwKSAlPiUNCiAgZHBseXI6Om11dGF0ZSh0aW1lcG9pbnQgPSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih0aW1lcG9pbnQpKSkNCg0KIyBNYXRjaCBzYW1wbGUgb3JkZXIgdG8gZXhwcmVzc2lvbiBtYXRyaXgNCm1kIDwtIG1kW21hdGNoKGNvbG5hbWVzKGV4cHJfbWF0KSwgbWQkc2FtcGxlX2lkKSwgXQ0KDQojIENyZWF0ZSBkZXNpZ24gbWF0cml4DQplZGVzaWduIDwtIGRhdGEuZnJhbWUoDQogIFRpbWUgPSBtZCR0aW1lcG9pbnQsDQogIFJlcGxpY2F0ZSA9IG1kJHJlcCwNCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQopDQpyb3duYW1lcyhlZGVzaWduKSA8LSBtZCRzYW1wbGVfaWQNCg0KIyBBZGQgYmluYXJ5IGdlbm90eXBlIGNvbHVtbnMgKDAgb3IgMSBlbmNvZGluZykNCmdlbm90eXBlX21hdHJpeCA8LSBtb2RlbC5tYXRyaXgofiAwICsgZ2Vub3R5cGUsIGRhdGEgPSBtZCkNCmNvbG5hbWVzKGdlbm90eXBlX21hdHJpeCkgPC0gZ3N1YigiZ2Vub3R5cGUiLCAiIiwgY29sbmFtZXMoZ2Vub3R5cGVfbWF0cml4KSkNCmVkZXNpZ24gPC0gY2JpbmQoZWRlc2lnbiwgYXMuZGF0YS5mcmFtZShnZW5vdHlwZV9tYXRyaXgpKQ0Kcm93bmFtZXMoZWRlc2lnbikgPC0gbWQkc2FtcGxlX2lkDQoNCiMgRGlzcGxheSBkZXNpZ24gbWF0cml4DQprbml0cjo6a2FibGUoZWRlc2lnbiwgY2FwdGlvbiA9ICJFeHBlcmltZW50YWwgRGVzaWduIE1hdHJpeCBmb3IgbWFTaWdQcm8iKQ0KYGBgDQoNCiMgU3RlcCA0OiBDcmVhdGUgUG9seW5vbWlhbCBSZWdyZXNzaW9uIERlc2lnbg0KDQpUaGUgbWFrZS5kZXNpZ24ubWF0cml4KCkgZnVuY3Rpb24gaW4gbWFTaWdQcm8gY3JlYXRlcyB0aGUgcmVncmVzc2lvbiBkZXNpZ24gbWF0cml4LiBUaGUgYGRlZ3JlZWAgcGFyYW1ldGVyIGNvbnRyb2xzIHRoZSBwb2x5bm9taWFsIG9yZGVyLiBCYXNlZCBvbiB0aGUgdmlnZW50dGUsIHRvIGNhbGN1bGF0ZSB0aGUgcGFyYW1ldGVyIGZvciB5b3VyIHNwZWNpZmljIGV4cGVyaW1lbnQsIHRha2UgdGhlIHRvdGFsIG51bWJlciBvZiB0aW1lcG9pbnRzIGFuZCBzdWJ0cmFjdCBieSAxLiB1bmxlc3MgeW91IGhhdmUgNSsgdGltZXBvaW50cywgSSB3b3VsZCBub3QgZXhjZWVkIG1vcmUgdGhhbiBuPTMgZm9yIGRlZ3JlZSwgdW5sZXNzIHlvdSBleHBlY3RzIGdlbmVzIHRvIGZvbGxvdyAnVycgc2hhcGUgZXhwcmVzc2lvbiBwYXR0ZXJucy4NCg0KfCBEZWdyZWUgfCBDdXJ2ZSBUeXBlIHwgVXNlIENhc2UgfA0KfDotLS0tLS0tfDotLS0tLS0tLS0tLXw6LS0tLS0tLS0tfA0KfCAxIHwgTGluZWFyIHwgTW9ub3RvbmljIHRyZW5kcyB8DQp8IDIgfCBRdWFkcmF0aWMgfCBQZWFrL3Ryb3VnaCBwYXR0ZXJucyB8DQp8IDMgfCBDdWJpYyB8IENvbXBsZXggZHluYW1pY3MgfA0KDQo8ZGl2IGNsYXNzPSJpbmZvLWJveCI+DQoqKkRlZ3JlZSA9IDIqKiAocXVhZHJhdGljKSBpcyB0eXBpY2FsbHkgZmluZSBmb3IgbW9zdCB0aW1lLWNvdXJzZSBleHBlcmltZW50YWwgZGVzaWducywgc2luY2UgaXQgbW9kZWxzICdVJyBzaGFwZSBleHByZXNzaW9uIGR5bmFtaWNzLCBzbyBpdCBjYXB0dXJlcyBib3RoIGxpbmVhciB0cmVuZHMgYW5kIG5vbi1saW5lYXIgcGF0dGVybnMuDQo8L2Rpdj4NCg0KYGBge3Igc3RlcDQtY3JlYXRlLWRlc2lnbiwgZWNobz1UUlVFfQ0KcG9seV9kZWdyZWUgPC0gMg0KDQpkZXNpZ24gPC0gbWFTaWdQcm86Om1ha2UuZGVzaWduLm1hdHJpeCgNCiAgZWRlc2lnbiA9IGVkZXNpZ24sDQogIGRlZ3JlZSA9IHBvbHlfZGVncmVlLA0KICB0aW1lLmNvbCA9IDEsDQogIHJlcGwuY29sID0gMiwNCiAgZ3JvdXAuY29scyA9IDM6NA0KKQ0KDQpkZXNpZ24kZGlzIDwtIGFzLmRhdGEuZnJhbWUoZGVzaWduJGRpcykNCmV4cHJfbWF0IDwtIGV4cHJfbWF0Wywgcm93bmFtZXMoZGVzaWduJGRpcyldDQoNCiMgUHJldmlldyB0aGUgcmVncmVzc2lvbiBkZXNpZ24gbWF0cml4DQprbml0cjo6a2FibGUoaGVhZChkZXNpZ24kZGlzKSwgY2FwdGlvbiA9ICJQb2x5bm9taWFsIFJlZ3Jlc3Npb24gRGVzaWduIE1hdHJpeCAoZmlyc3QgNiByb3dzKSIpDQpgYGANCg0KIyBTdGVwIDU6IElkZW50aWZ5IHRlbXBvcmFsbHkgc2lnbmlmaWNhbnQgR2VuZXMNCg0KV2l0aCB0aGUgZGVzaWduIG1hdHJpeCBtYWRlLCB0aGUgbmV4dCBzdGVwIGluIHRoZSBtYVNpZ1BybyB3b3JrZmxvdyBpcyB0aGUgYHAudmVjdG9yKClgIGZ1bmN0aW9uLiBUaGlzIGZ1bmN0aW9uIGZpdHMgcG9seW5vbWlhbCByZWdyZXNzaW9uIG1vZGVscyB0byBlYWNoIGdlbmUgYW5kIHRlc3RzIGZvciBzaWduaWZpY2FudCB0ZW1wb3JhbCBleHByZXNzaW9uIGNoYW5nZXMgKHJlZ2FyZGxlc3Mgb2YgZ2Vub3R5cGUpLg0KDQoqKlBhcmFtZXRlcnM6KioNCg0KLSBgUWA6IEZEUiB0aHJlc2hvbGQgZm9yIHNpZ25pZmljYW5jZSAoZGVmYXVsdDogMC4wNSkuIFlvdSBjYW4gdHJ5IDAuMDEsIHRob3VnaCAwLjA1IGlzIHJlY29tbWVuZGVkIGJ5IHRoZSBhdXRob3JzIGluIHRoZSBvcmlnaW5hbCBtYVNpZ1BybyBwYXBlci4NCi0gYE1ULmFkanVzdGA6IE11bHRpcGxlIHRlc3RpbmcgY29ycmVjdGlvbiBtZXRob2QgKCJCSCIgPSBCZW5qYW1pbmktSG9jaGJlcmcpDQotIGBtaW4ub2JzYDogTWluaW11bSBvYnNlcnZhdGlvbnMgcmVxdWlyZWQgcGVyIGdlbmUuIEkgd291bGQgc2V0IHRoaXMgcGFyYW1ldGVyIGVxdWFsIHRvIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIHlvdSBoYXZlIHBlciB0cmVhdG1lbnQvdGltZXBvaW50IChpZSwgbj0zIHJlcGxpY2F0ZXMgaW4gbW9zdCBjYXNlcykuDQoNCmBgYHtyIHN0ZXA1LXB2ZWN0b3IsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQpxX3RocmVzaG9sZCA8LSAwLjA1DQoNCmZpdCA8LSBtYVNpZ1Bybzo6cC52ZWN0b3IoDQogIGRhdGEgPSBleHByX21hdCwNCiAgZGVzaWduID0gZGVzaWduJGRpcywNCiAgUSA9IHFfdGhyZXNob2xkLA0KICBNVC5hZGp1c3QgPSAiQkgiLA0KICBtaW4ub2JzID0gbnJvdyhtZCkNCikNCmBgYA0KDQpgYGB7ciBzdGVwNS1yZXN1bHQsIGVjaG89RkFMU0V9DQpjYXQoIkdlbmVzIHdpdGggc2lnbmlmaWNhbnQgdGVtcG9yYWwgZXhwcmVzc2lvbjoiLCBsZW5ndGgoZml0JFNFTEVDKSwgIlxuIikNCmBgYA0KDQojIFN0ZXAgNjogRGV0ZWN0IEdlbm90eXBlLVNwZWNpZmljIFBhdHRlcm5zDQoNClRoZSBuZXh0IHN0ZXAgdXNlcyB0aGUgYFQuZml0KClgIGZ1bmN0aW9uLCB3aGljaCBhcHBsaWVzIGEgc3RlcHdpc2UgcmVncmVzc2lvbiB0byBpZGVudGlmeSBnZW5lcyB3aXRoIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IHRlbXBvcmFsIHBhdHRlcm5zIGJldHdlZW4gZ2Vub3R5cGVzIChnZW5vdHlwZSDDlyB0aW1lIGludGVyYWN0aW9uKS4gSW4gb3RoZXIgd29yZHMsIHRoaXMgZnVuY3Rpb24gZmluZHMgZGlmZmVyZW5jZXMgYmV0d2VlbiB5b3VyIHR3byBjb25kaXRpb25zLg0KDQpOb3RlIHRoYXQgdGhlcmUgYXJlIHNldmVyYWwgb3B0aW9ucyB0byB1c2UgZm9yIHRoZSByZWdyZXNzaW9uIG1ldGhvZC4gaGVyZSBpcyB1c2UgdGhlIG1ldGhvZCB1c2VkIGJ5IHRoZSBhdXRob3JzIGluIHRoZWlyIHR1dG9yaWFsIChiYWNrd2FyZCksIHRob3VnaCBJIHRlbmQgdG8gdXNlIHR3by53YXlzLmJhY2t3YXJkLiBZb3UgY2FuIHRyeSB0aGVzZSBkaWZmZXJlbnQgbWV0aG9kcyB0byBzZWUgbWV0aG9kIG1vc3QgJ2FjY3VyYXRlbHknIGNsdXN0ZXJzIHlvdXIgZ2VuZXMgdGVtcG9yYWxseSArIGJhc2VkIG9uIGNvbmRpdGlvbi90cmVhdG1lbnQuDQoNCioqU3RlcHdpc2UgcmVncmVzc2lvbiBtZXRob2RzIGluIG1hU2lnUHJvOioqDQoNCi0gIG1hU2lnUHJvIG9mZmVycyBzZXZlcmFsIG9wdGlvbnMgZm9yIHdoYXQgcmVncmVzc2lvbiBpcyANCi0gYGJhY2t3YXJkYDogU3RhcnQgd2l0aCBmdWxsIG1vZGVsLCByZW1vdmUgbm9uLXNpZ25pZmljYW50IHRlcm1zDQotIGBmb3J3YXJkYDogU3RhcnQgZW1wdHksIGFkZCBzaWduaWZpY2FudCB0ZXJtcw0KLSBgdHdvLndheXMuYmFja3dhcmRgIC8gYHR3by53YXlzLmZvcndhcmRgOiBCaWRpcmVjdGlvbmFsIHNlbGVjdGlvbiAoSSB0ZW5kIHRvIHVzZSB0aGlzKQ0KDQpgYGB7ciBzdGVwNi10Zml0LCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KYWxwaGFfdGhyZXNob2xkIDwtIDAuMDUNCg0KdGZpdCA8LSBtYVNpZ1Bybzo6VC5maXQoDQogIGZpdCwNCiAgc3RlcC5tZXRob2QgPSAiYmFja3dhcmQiLA0KICBhbGZhID0gYWxwaGFfdGhyZXNob2xkDQopDQoNCnRmaXQkZ3JvdXBzLnZlY3RvciA8LSBkZXNpZ24kZ3JvdXBzLnZlY3Rvcg0KdGZpdCRlZGVzaWduIDwtIGRlc2lnbiRlZGVzaWduDQpgYGANCg0KIyBTdGVwIDc6IEV4dHJhY3QgU2lnbmlmaWNhbnQgR2VuZXMNCg0KQWZ0ZXIgaWRlbnRpZnlpbmcgZ2VuZXMgdGhhdCBhcmUgdGltZS1kZXBlbmRlbnQgYW5kIGlkZW50aWZ5aW5nIGdlbmVzIHRoYXQgY2hhbmdlIHRlbXBvcmFsbHkgdGhhdCBkaWZmZXIgYmV0d2VlbiB0cmVhdG1lbnQvZ2Vub3R5cGUsIHRoZSBuZXh0IHN0ZXAgaXMgdG8gaWRlbnRpZnkgZ2VuZXMgd2l0aCBzaWduaWZpY2FudCBnZW5vdHlwZS1zcGVjaWZpYyBwYXR0ZXJucyB1c2luZyBgZ2V0LnNpZ2dlbmVzKClgLiBUaGlzIFLCsiB0aHJlc2hvbGQgZmlsdGVycyBzcGVjaWZpY2FsbHkgZm9yIGdlbmVzIHdoZXJlIHRoZSByZWdyZXNzaW9uIG1vZGVsIGV4cGxhaW5zIGEgc3Vic3RhbnRpYWwgcHJvcG9ydGlvbiBvZiB2YXJpYW5jZS4gDQoNCkluIHRoZSBvcmlnaW5hbCBtYVNpZ1BybyBwYXBlciwgdGhlIGF1dGhvcnMgbm90ZSB0aGF0IFJzcSBvZiAwLjcgcGVyZm9ybWVkIHdlbGwuIEhvd2V2ZXIsIHRoZXJlIGhhdmUgYmVlbiBzb21lIGNhc2VzIHdoZW4gUnNxIG9mIDAuNyBoYXMgcmVzdWx0ZWQgaW4gNTAwMCsgZ2VuZXMgdGhhdCBhcmUgY2x1c3RlcmVkIGF0IHRoZSBvZiB0aGlzIHdvcmtmbG93LiBUaGVyZWZvcmUsIEkgb2Z0ZW4gdXNlIGhpZ2hlciB2YWx1ZXMgKDAuNyspLCBhcyBoaWdoZXIgdmFsdWVzIHlpZWxkIG1vcmUgcmVsaWFibGUgZ2VuZXMgYnV0IGZld2VyIHJlc3VsdHMuIExvd2VyIHZhbHVlcyAoMC41LTAuNikgaW5jbHVkZSBtb3JlIGdlbmVzIGJ1dCB3aXRoIGxlc3MgY29uZmlkZW50IG1vZGVsIGZpdHMuIEFkanVzdCBiYXNlZCBvbiB5b3VyIHNhbXBsZSBzaXplIGFuZCBkb3duc3RyZWFtIGdvYWxzLCBhbmQgeW91IGNhbiBmdXJ0aGVyIGNoZWNrIHRoZSBxdWFsaXR5IG9mIHRoZSBmaXQgYnkgaW5kaXZpZHVhbGx5IGxvb2tpbmcgYXQgZ2VuZXMgd2l0aGluIGVhY2ggY2x1c3RlciB0byBhc3Nlc3MgdGhlIGZpZGVsaXR5IG9mIHRoZSBmaXR0aW5nLg0KDQpgYGB7ciBzdGVwNy1leHRyYWN0LXNpZywgZWNobz1UUlVFfQ0KcnNxX3RocmVzaG9sZCA8LSAwLjcNCg0Kc2lncyA8LSBtYVNpZ1Bybzo6Z2V0LnNpZ2dlbmVzKA0KICB0c3RlcCA9IHRmaXQsDQogIHJzcSA9IHJzcV90aHJlc2hvbGQsDQogIHZhcnMgPSAiZ3JvdXBzIg0KKQ0KDQpzaWdfZ2VuZXMgPC0gcm93bmFtZXMoc2lncyRzaWcuZ2VuZXMkVDIxdnNEMjEkc2lnLnByb2ZpbGVzKQ0KY2F0KCJHZW5lcyB3aXRoIGdlbm90eXBlLXNwZWNpZmljIHRlbXBvcmFsIHBhdHRlcm5zOiIsIGxlbmd0aChzaWdfZ2VuZXMpLCAiXG4iKQ0KYGBgDQoNCiMgU3RlcCA4OiBDbHVzdGVyIEdlbmVzIGJ5IEV4cHJlc3Npb24gVHJhamVjdG9yeQ0KDQpOZXh0LCB3ZSBjbHVzdGVyIGdlbmVzIHdpdGggc2ltaWxhciB0ZW1wb3JhbCBleHByZXNzaW9uIHBhdHRlcm5zIHVzaW5nIGBzZWUuZ2VuZXMoKWAuDQoNClRoZXJlIGFyZSBtYW55IG9wdGlvbnMgaGVyZSB0byB1c2UgZm9yIHRoZSBkaXN0YW5jZSBtZXRyaWMgYW5kIGNsdXN0ZXJpbmcgbWV0aG9kLiBNb3Jlb3ZlciwgeW91IGNhbiBzZXQgayBhcyB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzLiBJIHdvbid0IGdldCBpbnRvIHRoZSBudWFuY2VzIGFuZCBkZXRhaWxzIG9mIGhvdyB0byBwaWNrIHRoZSBzcGVjaWZpYyBkaXN0YW5jZSBtZXRyaWMsIGV0Yy4gVGhlIG1haW4gcG9pbnQgaXM6IGFyZSB5b3UgYWJsZSB0byBpbmZlciBiaW9sb2dpY2FsIG1lYW5pbmcgZnJvbSB0aGUgY2x1c3RlcnM/IEEgY2x1c3RlciBkZWZpbmVkIGJ5IG1hU2lnUHJvIGlzIG9ubHkgbWVhbmluZ2Z1bCBpZiBpdCBoYXMgc29tZSBiaW9sb2dpY2FsIG1lYW5pbmcgdW5kZXJseWluZyBpdC4gT25lIHNpbXBsZSB3YXkgdG8gZGVyaXZlIHdoZXRoZXIgYSBjbHVzdGVyIGhhcyBtZWFuaW5nZnVsIGJpb2xvZ2ljYWwgaW5mb3JtYXRpb24gaXMgdGhlIG51bWJlciBvZiBnZW5lcyBjb250YWluZWQgd2l0aGluIGl0LiBJZiBhIGNsdXN0ZXIgaGFzIDUwIGdlbmVzLCBpdCdzIGxpa2VseSB0aGF0IHlvdXIgY2x1c3RlcmluZyByZXNvbHV0aW9uIGlzIHRvbyBmaW5lLg0KDQpNeSBzdWdnZXN0aW9uIHdvdWxkIGJlIHRvIHRlc3QgdmFyaW91cyBwYXJhbWV0ZXJzLiBJZGVhbGx5LCByZWdhcmRsZXNzIG9mIHRoZSBwYXJhbWV0ZXJzIHlvdSBjaG9vc2UsIHRoZSB1bmRlcmx5aW5nIGJpb2xvZ2ljYWwgaW5zaWdodCB5b3UgZ2xlYW4gaXMgbGFyZ2VseSB0aGUgc2FtZS4gDQoNCioqQ2x1c3RlcmluZyBwYXJhbWV0ZXJzOioqDQoNCnwgUGFyYW1ldGVyIHwgRGVzY3JpcHRpb24gfA0KfDotLS0tLS0tLS0tfDotLS0tLS0tLS0tLS18DQp8IGBrYCB8IE51bWJlciBvZiBjbHVzdGVycyB8DQp8IGBkaXNgIHwgRGlzdGFuY2UgbWV0cmljICgiY29ycmVsYXRpb24iLCAiZXVjbGlkZWFuIikgfA0KfCBgY2x1c3Rlci5tZXRob2RgIHwgQ2x1c3RlcmluZyBhbGdvcml0aG0gKCJoY2x1c3QiLCAia21lYW5zIikgfA0KDQpgYGB7ciBzdGVwOC1jbHVzdGVyLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD0xMCwgZmlnLmNhcD0ibWFTaWdQcm8gY2x1c3RlciB2aXN1YWxpemF0aW9uIHNob3dpbmcgZ2VuZSBleHByZXNzaW9uIHRyYWplY3RvcmllcyBncm91cGVkIGJ5IHNpbWlsYXJpdHkuIn0NCmtfY2x1c3RlcnMgPC0gMTANCg0KZWRlc2lnbl9jbGVhbiA8LSBkZXNpZ24kZWRlc2lnblssIHNhcHBseShkZXNpZ24kZWRlc2lnbiwgaXMubnVtZXJpYyldDQoNCmNsdXN0ZXJzIDwtIG1hU2lnUHJvOjpzZWUuZ2VuZXMoDQogIGRhdGEgPSBzaWdzJHNpZy5nZW5lcyRUMjF2c0QyMSwNCiAgZWRlc2lnbiA9IGVkZXNpZ25fY2xlYW4sDQogIGsgPSBrX2NsdXN0ZXJzLA0KICBkaXMgPSAiY29ycmVsYXRpb24iLA0KICBjbHVzdGVyLm1ldGhvZCA9ICJoY2x1c3QiLA0KICBjb2xvci5tb2RlID0gImdyYXkiLA0KICBzaG93LmZpdCA9IEZBTFNFDQopDQoNCiMgQ3JlYXRlIGNsdXN0ZXIgYXNzaWdubWVudCB0YWJsZQ0KY2xfYXNzaWduIDwtIGRhdGEuZnJhbWUoDQogIGdlbmVfaWQgPSBuYW1lcyhjbHVzdGVycyRjdXQpLA0KICBjbHVzdGVyID0gYXMuaW50ZWdlcihjbHVzdGVycyRjdXQpLA0KICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCikNCg0KIyBTdW1tYXJ5IG9mIGNsdXN0ZXIgc2l6ZXMNCmNsdXN0ZXJfc3VtbWFyeSA8LSBjbF9hc3NpZ24gJT4lDQogIGRwbHlyOjpjb3VudChjbHVzdGVyLCBuYW1lID0gIm5fZ2VuZXMiKSAlPiUNCiAgZHBseXI6OmFycmFuZ2UoY2x1c3RlcikNCg0Ka25pdHI6OmthYmxlKGNsdXN0ZXJfc3VtbWFyeSwgY2FwdGlvbiA9ICJHZW5lcyBwZXIgQ2x1c3RlciIpDQpgYGANCg0KIyBTdGVwIDk6IFByZXBhcmUgVmlzdWFsaXphdGlvbiBEYXRhDQoNCkhlcmUsIEkgY29udmVydCBleHByZXNzaW9uIGRhdGEgdG8gbG9uZyBmb3JtYXQgYW5kLCB1c2luZyB0aGUgbWFTaWdQcm8gZ2VuZSBjbHVzdGVycyBmcm9tIHRoZSBwcmV2aW91cyBwYXJ0IG9mIHRoc2kgd29ya2Zsb3csIHByZXBhcmUgdG8gcGxvdCBvdXIgZGF0YSBpbiBsaW5lcGxvdHMgYnkgJ2dyb3VwJy4gSSBhbHNvIGNhbGN1bGF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIGNsdXN0ZXIgcHJvZmlsZSBwbG90cy4NCg0KYGBge3Igc3RlcDktcHJlcGFyZS12aXosIGVjaG89VFJVRX0NCmV4cHJfc3ViIDwtIGV4cHJfbWF0W3NpZ19nZW5lcywgLCBkcm9wID0gRkFMU0VdDQoNCiMgUmVzaGFwZSB0byBsb25nIGZvcm1hdCBmb3IgcGxvdHRpbmcNCmV4cHJfZGYgPC0gcmVzaGFwZTI6Om1lbHQoDQogIGFzLm1hdHJpeChleHByX3N1YiksDQogIHZhcm5hbWVzID0gYygiZ2VuZV9pZCIsICJzYW1wbGVfaWQiKSwNCiAgdmFsdWUubmFtZSA9ICJjcG0iDQopDQoNCmV4cHJfZGYkZ2Vub3R5cGUgPC0gbWQkZ2Vub3R5cGVbbWF0Y2goZXhwcl9kZiRzYW1wbGVfaWQsIG1kJHNhbXBsZV9pZCldDQpleHByX2RmJHRpbWVwb2ludCA8LSBhcy5udW1lcmljKG1kJHRpbWVwb2ludFttYXRjaChleHByX2RmJHNhbXBsZV9pZCwgbWQkc2FtcGxlX2lkKV0pDQpleHByX2RmJGNsdXN0ZXIgPC0gY2x1c3RlcnMkY3V0W2V4cHJfZGYkZ2VuZV9pZF0NCg0KIyBDYWxjdWxhdGUgbWVkaWFuIGV4cHJlc3Npb24gcGVyIGNsdXN0ZXIvZ2Vub3R5cGUvdGltZXBvaW50DQpzdW1fZGYgPC0gZXhwcl9kZiAlPiUNCiAgZHBseXI6Omdyb3VwX2J5KGNsdXN0ZXIsIGdlbm90eXBlLCB0aW1lcG9pbnQpICU+JQ0KICBkcGx5cjo6c3VtbWFyaXNlKG1lZGlhbl9jcG0gPSBtZWRpYW4oY3BtKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogIGRwbHlyOjphcnJhbmdlKGNsdXN0ZXIsIGdlbm90eXBlLCB0aW1lcG9pbnQpDQoNCmNsdXN0ZXJfY291bnRzIDwtIGV4cHJfZGYgJT4lDQogIGRwbHlyOjpncm91cF9ieShjbHVzdGVyKSAlPiUNCiAgZHBseXI6OnN1bW1hcmlzZShuX2dlbmVzID0gZHBseXI6Om5fZGlzdGluY3QoZ2VuZV9pZCksIC5ncm91cHMgPSAiZHJvcCIpDQpgYGANCg0KIyBTdGVwIDEwOiBDbHVzdGVyIFByb2ZpbGUgVmlzdWFsaXphdGlvbg0KDQpJIG5vdyBjcmVhdGUgbGluZSBwbG90cyBzaG93aW5nIG1lZGlhbiBleHByZXNzaW9uIHRyYWplY3RvcmllcyBmb3IgZWFjaCBjbHVzdGVyLiBJIHVzZSBtZWRpYW4gQ1BNIGhlcmU7IHlvdSBjYW4gdXNlIG1lZGlhbiBDUE0gb3IgbWVhbiBDUE0sIG9yIERFU2VxMiBtZWFuIHZzdCBjb3VudHMuIFdlIGhhdmUgc2VwYXJhdGUgbGluZXMgZm9yIGVhY2ggZ2Vub3R5cGUgdG8gc2hvdyBkaWZmZXJlbnRpYWwgdGVtcG9yYWwga2luZXRpY3Mgb2YgZ2VuZSBleHByZXNzaW9uIHRoYXQgYXJlIGdlbm90eXBlLWRlcGVuZGVudC4NCg0KYGBge3Igc3RlcDEwLXBsb3QtZnVuY3Rpb24sIGVjaG89VFJVRX0NCmdlbm9fY29sb3JzIDwtIGMoIkQyMSIgPSAiIzFGNzhCNCIsICJUMjEiID0gIiNFMzFBMUMiKQ0KDQpwbG90X2NsdXN0ZXIgPC0gZnVuY3Rpb24oZGYsIGNvdW50c19kZiwgY2wsIHNob3dfbGVnZW5kID0gVFJVRSkgew0KICBuX2dlbmVzIDwtIGNvdW50c19kZiRuX2dlbmVzW2NvdW50c19kZiRjbHVzdGVyID09IGNsXQ0KICBwbG90X2RhdGEgPC0gZGZbZGYkY2x1c3RlciA9PSBjbCwgXQ0KICANCiAgcCA8LSBnZ3Bsb3QyOjpnZ3Bsb3QoDQogICAgcGxvdF9kYXRhLA0KICAgIGdncGxvdDI6OmFlcyh4ID0gdGltZXBvaW50LCB5ID0gbWVkaWFuX2NwbSwgY29sb3IgPSBnZW5vdHlwZSwgZ3JvdXAgPSBnZW5vdHlwZSkNCiAgKSArDQogICAgZ2dwbG90Mjo6Z2VvbV9saW5lKGxpbmV3aWR0aCA9IDEpICsNCiAgICBnZ3Bsb3QyOjpnZW9tX3BvaW50KHNpemUgPSAyLjUpICsNCiAgICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gZ2Vub19jb2xvcnMsIG5hbWUgPSAiR2Vub3R5cGUiKSArDQogICAgZ2dwbG90Mjo6c2NhbGVfeF9jb250aW51b3VzKA0KICAgICAgYnJlYWtzID0gYyg2LCAxMCwgMTcpLA0KICAgICAgbGFiZWxzID0gYygiRGF5IDYiLCAiRGF5IDEwIiwgIkRheSAxNyIpDQogICAgKSArDQogICAgZ2dwbG90Mjo6c2NhbGVfeV9jb250aW51b3VzKA0KICAgICAgYnJlYWtzID0gc2NhbGVzOjpwcmV0dHlfYnJlYWtzKG4gPSA2KSwNCiAgICAgIGV4cGFuZCA9IGdncGxvdDI6OmV4cGFuc2lvbihtdWx0ID0gYygwLjEsIDAuMDUpKQ0KICAgICkgKw0KICAgIGdncGxvdDI6OmxhYnMoDQogICAgICB0aXRsZSA9IHBhc3RlMCgiQ2x1c3RlciAiLCBjbCwgIiAobj0iLCBuX2dlbmVzLCAiKSIpLA0KICAgICAgeCA9ICJUaW1lcG9pbnQiLA0KICAgICAgeSA9ICJNZWRpYW4gQ1BNIg0KICAgICkgKw0KICAgIGdncGxvdDI6OnRoZW1lX21pbmltYWwoKSArDQogICAgZ2dwbG90Mjo6dGhlbWUoDQogICAgICBwbG90LnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxMSwgaGp1c3QgPSAwLjUpLA0KICAgICAgYXhpcy50aXRsZS54ID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgbWFyZ2luID0gZ2dwbG90Mjo6bWFyZ2luKHQgPSA1KSksDQogICAgICBheGlzLnRpdGxlLnkgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBtYXJnaW4gPSBnZ3Bsb3QyOjptYXJnaW4ociA9IDUpKSwNCiAgICAgIGF4aXMudGV4dCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gOSksDQogICAgICBheGlzLnRleHQueCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLA0KICAgICAgbGVnZW5kLnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgICBsZWdlbmQudGV4dCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gOSksDQogICAgICBsZWdlbmQucG9zaXRpb24gPSBpZmVsc2Uoc2hvd19sZWdlbmQsICJyaWdodCIsICJub25lIikNCiAgICApDQogIA0KICByZXR1cm4ocCkNCn0NCmBgYA0KDQpUaGUgZm9sbG93aW5nIGNvZGUgc2F2ZXMgaW5kaXZpZHVhbCBjbHVzdGVyIHBsb3RzIHRvIGZpbGVzDQoNCmBgYHtyIHN0ZXAxMC1pbmRpdmlkdWFsLXBsb3RzLCBldmFsPUZBTFNFLCBlY2hvPVRSVUV9DQojIFNhdmUgaW5kaXZpZHVhbCBjbHVzdGVyIHByb2ZpbGUgcGxvdHMNCmZvciAoY2wgaW4gc29ydCh1bmlxdWUoc3VtX2RmJGNsdXN0ZXIpKSkgew0KICBwIDwtIHBsb3RfY2x1c3RlcihzdW1fZGYsIGNsdXN0ZXJfY291bnRzLCBjbCwgc2hvd19sZWdlbmQgPSBUUlVFKQ0KICBnZ3Bsb3QyOjpnZ3NhdmUoDQogICAgZmlsZW5hbWUgPSBmaWxlLnBhdGgoZmlnX2Rpciwgc3ByaW50ZigiY2x1c3Rlcl8lMDJkX3Byb2ZpbGUuc3ZnIiwgY2wpKSwNCiAgICBwbG90ID0gcCwNCiAgICB3aWR0aCA9IDYuNSwNCiAgICBoZWlnaHQgPSA0LjUsDQogICAgdW5pdHMgPSAiaW4iDQogICkNCn0NCmBgYA0KDQpgYGB7ciBzdGVwMTAtY29tYmluZWQtcGxvdCwgZWNobz1UUlVFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTgsIGZpZy5jYXA9IkNvbWJpbmVkIGNsdXN0ZXIgcHJvZmlsZSBwbG90IHNob3dpbmcgYWxsIGNsdXN0ZXJzIn0NCmNvbWJpbmVfY2x1c3Rlcl9wbG90cyA8LSBmdW5jdGlvbihkZiwgY291bnRzX2RmLCBuY29sLCBucm93LCBvdXRwdXRfcGF0aCwgd2lkdGgsIGhlaWdodCkgew0KICBhbGxfY2x1c3RlcnMgPC0gc29ydCh1bmlxdWUoZGYkY2x1c3RlcikpDQogIHBsb3RfbGlzdCA8LSBsYXBwbHkoYWxsX2NsdXN0ZXJzLCBmdW5jdGlvbihjbCkgew0KICAgIHBsb3RfY2x1c3RlcihkZiwgY291bnRzX2RmLCBjbCwgc2hvd19sZWdlbmQgPSBGQUxTRSkNCiAgfSkNCiAgDQogIGNvbWJpbmVkIDwtIHBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2xpc3QsIG5jb2wgPSBuY29sLCBucm93ID0gbnJvdykgKw0KICAgIHBhdGNod29yazo6cGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKSAmDQogICAgZ2dwbG90Mjo6dGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikNCiAgDQogIGdncGxvdDI6Omdnc2F2ZSgNCiAgICBmaWxlbmFtZSA9IG91dHB1dF9wYXRoLA0KICAgIHBsb3QgPSBjb21iaW5lZCwNCiAgICB3aWR0aCA9IHdpZHRoLA0KICAgIGhlaWdodCA9IGhlaWdodCwNCiAgICB1bml0cyA9ICJpbiINCiAgKQ0KICANCiAgcmV0dXJuKGNvbWJpbmVkKQ0KfQ0KDQpjb21iaW5lZF9wbG90IDwtIGNvbWJpbmVfY2x1c3Rlcl9wbG90cygNCiAgZGYgPSBzdW1fZGYsDQogIGNvdW50c19kZiA9IGNsdXN0ZXJfY291bnRzLA0KICBuY29sID0gMiwNCiAgbnJvdyA9IDUsDQogIG91dHB1dF9wYXRoID0gZmlsZS5wYXRoKGZpZ19kaXIsICJhbGxfY2x1c3RlcnNfY29tYmluZWQuc3ZnIiksDQogIHdpZHRoID0gMTAsDQogIGhlaWdodCA9IDE4DQopDQoNCmNvbWJpbmVkX3Bsb3QNCmBgYA0KDQojIFN0ZXAgMTE6IFN1bW1hcnkgU3RhdGlzdGljcw0KDQpJIGNyZWF0ZSBhIHN1bW1hcnkgb2Yga2V5IG1ldHJpY3MgZnJvbSB0aGUgbWFTaWdQcm8gYW5hbHlzaXMgaGVyZS4gaWYgSSBkZWNpZGUgdG8gbG9vayBhdCBvdGhlciBwYXJhbWV0ZXIgY29tYmluYXRpb25zLCBJIGNhbiBjb21wYXJlIHRoZSByZXN1bHRzIHRvIHRoZSBzdW1tYXJ5IGhlcmUuDQoNCmBgYHtyIHN0ZXAxMS1zdW1tYXJ5LCBlY2hvPVRSVUV9DQpzdW1tYXJ5X3N0YXRzIDwtIGRhdGEuZnJhbWUoDQogIHRvdGFsX2dlbmVzX3Rlc3RlZCA9IG5yb3coZXhwcl9tYXQpLA0KICBzaWduaWZpY2FudF90ZW1wb3JhbCA9IGxlbmd0aChmaXQkU0VMRUMpLA0KICBzaWduaWZpY2FudF9nZW5vdHlwZV9zcGVjaWZpYyA9IGxlbmd0aChzaWdfZ2VuZXMpLA0KICBudW1fY2x1c3RlcnMgPSBrX2NsdXN0ZXJzLA0KICBwb2x5bm9taWFsX2RlZ3JlZSA9IHBvbHlfZGVncmVlLA0KICBxX3RocmVzaG9sZCA9IHFfdGhyZXNob2xkLA0KICByc3FfdGhyZXNob2xkID0gcnNxX3RocmVzaG9sZA0KKQ0KDQpyZWFkcjo6d3JpdGVfY3N2KHN1bW1hcnlfc3RhdHMsIGZpbGUucGF0aChyZXNfZGlyLCAic3VtbWFyeV9zdGF0aXN0aWNzLmNzdiIpKQ0KYGBgDQoNCmBgYHtyIHN0ZXAxMS1kaXNwbGF5LXN1bW1hcnksIGVjaG89RkFMU0V9DQojIERpc3BsYXkgc3VtbWFyeSBhcyBmb3JtYXR0ZWQgdGFibGUNCmtuaXRyOjprYWJsZShzdW1tYXJ5X3N0YXRzLCANCiAgICAgICAgICAgICBjYXB0aW9uID0gIlN1bW1hcnkgU3RhdGlzdGljcyBmcm9tIG1hU2lnUHJvIEFuYWx5c2lzIiwNCiAgICAgICAgICAgICBkaWdpdHMgPSAyKQ0KYGBgDQoNCiMgU3RlcCAxMjogR2VuZSBPbnRvbG9neSBFbnJpY2htZW50DQoNClRvIGRlcml2ZSBiaW9sb2dpY2FsL3BhdGh3YXkgaW5zaWdodCBmcm9tIHRoZSBtYVNpZ1BybyBhbmFseXNpcywgSSBydW4gR08gb3ZlcnJlcHJlc2VudGF0aW9uIGFuYWx5c2lzIG9uIGVhY2ggY2x1c3RlciB0byBpZGVudGlmeSAgYmlvbG9naWNhbCBwcm9jZXNzZXMgYXNzb2NpYXRlZCB3aXRoIGVhY2ggY2x1c3Rlci4gVGhpcyBoZWxwcyBpbnRlcnByZXQgdGhlIGZ1bmN0aW9uYWwgc2lnbmlmaWNhbmNlIG9mIGNvLWV4cHJlc3NlZCBnZW5lIGdyb3Vwcy4gRm9yIGV4YW1wbGUsIGNsdXN0ZXIgNSwgaW4gd2hpY2ggZXhwcmVzc2lvbiBpcyBkZWNyZWFzZWQgaW4gVDIxIGNvbXBhcmVkIHRvIEQyMSwgbG9va3MgcG90ZW50aWFsbHkgYmUgc29tZSB0eXBlIG9mIHByb2dlbml0b3IgZ2VuZSBleHByZXNzaW9uIGFzc29jaWF0ZWQgY2x1c3RlciwgZ2l2ZW4gdGhhdCB0aGUgZW5yaWNoZWQgR08gdGVybXMgYXJlIEROQSByZXBsaWNhdGlvbi1yZWxhdGVkIChpZSwgcHJvbGlmZXJhdGlvbiBnZW5lcykuIA0KDQpHTyB0ZXJtIGFuYWx5c2lzIGxpa2UgdGhpcyBhbGxvd3MgeW91IHRvIGRlcml2ZSBCUk9BRCBpbnNpZ2h0IC0tIHlvdSB3aWxsIGhhdmUgdG8gZGlnIGRlZXBlciBpbnRvIHRoZSBhY3R1YWwgZ2VuZXMgaW4gZWFjaCBjbHVzdGVyLiBGb3IgZXhhbXBsZSwgYXJlIGtub3duIG1hcmtlciBvZiBjZWxsIGN5Y2xlIHByb2dyZXNzaW9uIGluIGNsdXN0ZXIgNT8gZXRjLg0KDQpBIG1vcmUgaW50dWl0aXZlIGV4YW1wbGUgaXMgY2x1c3RlciA2LiBUaGUgZW5yaWNoZWQgR08gdGVybXMgZm9yIHRoaXMgY2x1c3RlciBhcHBlYXIgdG8gYmUgcmVsYXRlZCB0byBmdW5jdGlvbiBvZiBtYXR1cmUgbmV1cm9ucy4gSW4gdGhpcyBkaWZmZXJlbnRpYXRpb24sIHRoZSBjZWxscyBtYXR1cmUgZnJvbSBuZXVyYWwgcHJvZ2VuaXRvcnMgdG8gbWF0dXJlLCBkaWZmZXJlbnRpYXRkIG5ldXJvbnMuIENvcnJvYm9yYXRpbmcgdGhpcywgdGhlIGxpbmVwbG90IGZvciBjbHVzdGVyIDYgc2hvd3MgZmFzdGVyIGFjcXVpc2l0aW9uIG9mIGV4cHJlc3Npb24gb2YgY2x1c3RlciA2IGdlbmVzIGluIFQyMSBjb21wYXJlZCB0byBEMjEuIE9uZSBicm9hZCBpbnNpZ2h0LCB0aGVuLCB3b3VsZCBiZSB0aGF0IFQyMSBhcHBlYXJzIHRvIGFjcXVpcmUgYSBuZXVyb25hbCBtYXR1cmF0aW9uLWxpa2UgdHJhbnNjcmlwdGlvbmFsIHNpZ25hdHVyZSBtb3JlIHF1aWNrbHkgb3ZlciB0aW1lIGNvbXBhcmVkIHRvIEQyMS4gYWdhaW4sIHRoaXMgZmluZGluZyBzaG91bGQgaWRlYWxseSBiZSBjb3Jyb2JvcmF0ZWQgYnkgeW91ciBkaWZmZXJlbnRpYWwgZ2VuZSBleHByZXNzaW9uIGNvbXBhcmlzb25zIGJldHdlZW4gVDIxIGFuZCBEMjEgYXQgZWFjaCB0aW1lcG9pbnQuIA0KDQptYVNpZ1BybyBhbGxvd3MgdXMgdG8gdmlzdWFsaXplIHRoaXMgJ3RlbXBvcmFsbHknIGFuZCBpZGVudGlmeSB0aGUgZ2VuZXMgZHJpdmluZyBieSBjbHVzdGVyaW5nIHRoZW0gaW50byBtb2R1bGVzLg0KDQpgYGB7ciBzdGVwMTItZ28tc2V0dXAsIGVjaG89VFJVRX0NCmdvX2Rpcl9yZXMgPC0gZmlsZS5wYXRoKHJlc19kaXIsICJnb19lbnJpY2htZW50IikNCmdvX2Rpcl9maWdzIDwtIGZpbGUucGF0aChmaWdfZGlyLCAiZ29fZW5yaWNobWVudCIpDQpkaXIuY3JlYXRlKGdvX2Rpcl9yZXMsIHJlY3Vyc2l2ZSA9IFRSVUUsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQ0KZGlyLmNyZWF0ZShnb19kaXJfZmlncywgcmVjdXJzaXZlID0gVFJVRSwgc2hvd1dhcm5pbmdzID0gRkFMU0UpDQpgYGANCg0KIyMjIEdPIEVucmljaG1lbnQgRnVuY3Rpb24NCg0KYGBge3Igc3RlcDEyLWdvLWZ1bmN0aW9uLCBlY2hvPVRSVUV9DQpydW5fY2x1c3Rlcl9nbyA8LSBmdW5jdGlvbihnZW5lX2xpc3QsIGNsdXN0ZXJfaWQpIHsNCiAgIyBTa2lwIGNsdXN0ZXJzIHdpdGggdG9vIGZldyBnZW5lcw0KICBpZiAobGVuZ3RoKGdlbmVfbGlzdCkgPCA1KSByZXR1cm4oTlVMTCkNCg0KICAjIENvbnZlcnQgZ2VuZSBzeW1ib2xzIHRvIEVudHJleiBJRHMNCiAgZ2VuZV9lbnRyZXogPC0gY2x1c3RlclByb2ZpbGVyOjpiaXRyKA0KICAgIGdlbmVfbGlzdCwNCiAgICBmcm9tVHlwZSA9ICJTWU1CT0wiLA0KICAgIHRvVHlwZSA9ICJFTlRSRVpJRCIsDQogICAgT3JnRGIgPSBvcmcuSHMuZWcuZGINCiAgKQ0KICBpZiAobnJvdyhnZW5lX2VudHJleikgPT0gMCkgcmV0dXJuKE5VTEwpDQoNCiAgIyBSdW4gR08gZW5yaWNobWVudCAoQmlvbG9naWNhbCBQcm9jZXNzKQ0KICBnb19yZXN1bHQgPC0gY2x1c3RlclByb2ZpbGVyOjplbnJpY2hHTygNCiAgICBnZW5lID0gZ2VuZV9lbnRyZXokRU5UUkVaSUQsDQogICAgT3JnRGIgPSBvcmcuSHMuZWcuZGIsDQogICAgb250ID0gIkJQIiwNCiAgICBwQWRqdXN0TWV0aG9kID0gIkJIIiwNCiAgICBwdmFsdWVDdXRvZmYgPSAwLjA1LA0KICAgIHF2YWx1ZUN1dG9mZiA9IDAuMDUsDQogICAgcmVhZGFibGUgPSBUUlVFDQogICkNCiAgaWYgKGlzLm51bGwoZ29fcmVzdWx0KSB8fCBucm93KGFzLmRhdGEuZnJhbWUoZ29fcmVzdWx0KSkgPT0gMCkgcmV0dXJuKE5VTEwpDQoNCiAgIyBTaW1wbGlmeSByZWR1bmRhbnQgdGVybXMNCiAgZ29fc2ltcGxpZmllZCA8LSBjbHVzdGVyUHJvZmlsZXI6OnNpbXBsaWZ5KGdvX3Jlc3VsdCwgY3V0b2ZmID0gMC43KQ0KICBnb19kZiA8LSBhcy5kYXRhLmZyYW1lKGdvX3NpbXBsaWZpZWQpDQogIGlmIChucm93KGdvX2RmKSA9PSAwKSByZXR1cm4oTlVMTCkNCg0KICAjIFNhdmUgcmVzdWx0cw0KICBzYXZlUkRTKGdvX2RmLCBmaWxlLnBhdGgoZ29fZGlyX3JlcywgcGFzdGUwKCJjbHVzdGVyXyIsIGNsdXN0ZXJfaWQsICJfR09fcmVzdWx0cy5yZHMiKSkpDQogIHJlYWRyOjp3cml0ZV9jc3YoZ29fZGYsIGZpbGUucGF0aChnb19kaXJfcmVzLCBwYXN0ZTAoImNsdXN0ZXJfIiwgY2x1c3Rlcl9pZCwgIl9HT19yZXN1bHRzLmNzdiIpKSkNCg0KICAjIENyZWF0ZSBkb3QgcGxvdCBmb3IgdG9wIHRlcm1zDQogIGdvX3RvcCA8LSBnb19kZiAlPiUNCiAgICBkcGx5cjo6c2xpY2VfaGVhZChuID0gMTApICU+JQ0KICAgIGRwbHlyOjptdXRhdGUoRGVzY3JpcHRpb24gPSBzdHJpbmdyOjpzdHJfd3JhcChEZXNjcmlwdGlvbiwgNDApKQ0KDQogIGlmIChucm93KGdvX3RvcCkgPiAwKSB7DQogICAgbl9nZW5lcyA8LSBsZW5ndGgoZ2VuZV9saXN0KQ0KDQogICAgcCA8LSBnZ3Bsb3QyOjpnZ3Bsb3QoDQogICAgICBnb190b3AsDQogICAgICBnZ3Bsb3QyOjphZXMoeCA9IC1sb2cxMChwdmFsdWUpLA0KICAgICAgICAgICAgICAgICAgIHkgPSByZW9yZGVyKERlc2NyaXB0aW9uLCAtbG9nMTAocHZhbHVlKSksDQogICAgICAgICAgICAgICAgICAgc2l6ZSA9IENvdW50KQ0KICAgICkgKw0KICAgICAgZ2dwbG90Mjo6Z2VvbV9wb2ludChjb2xvciA9ICIjMERBRDhEIiwgYWxwaGEgPSAwLjgpICsNCiAgICAgIGdncGxvdDI6OnNjYWxlX3NpemVfY29udGludW91cyhyYW5nZSA9IGMoMywgOCksIG5hbWUgPSAiR2VuZSBjb3VudCIpICsNCiAgICAgIGdncGxvdDI6OnNjYWxlX3lfZGlzY3JldGUocG9zaXRpb24gPSAicmlnaHQiKSArDQogICAgICBnZ3Bsb3QyOjpsYWJzKA0KICAgICAgICB4ID0gZXhwcmVzc2lvbigiLWxvZyJbMTBdICogIihwLXZhbHVlKSIpLA0KICAgICAgICB5ID0gTlVMTCwNCiAgICAgICAgdGl0bGUgPSBwYXN0ZTAoIkNsdXN0ZXIgIiwgY2x1c3Rlcl9pZCksDQogICAgICAgIHN1YnRpdGxlID0gcGFzdGUwKCJHTyBCaW9sb2dpY2FsIFByb2Nlc3NlcyAobj0iLCBuX2dlbmVzLCAiIGdlbmVzKSIpDQogICAgICApICsNCiAgICAgIGdncGxvdDI6OnRoZW1lX21pbmltYWwoKSArDQogICAgICBnZ3Bsb3QyOjp0aGVtZSgNCiAgICAgICAgcGxvdC50aXRsZSA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gMTQsIGhqdXN0ID0gMCksDQogICAgICAgIHBsb3Quc3VidGl0bGUgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBoanVzdCA9IDApLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDExKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDksIGhqdXN0ID0gMCksDQogICAgICAgIGF4aXMudGV4dC54ID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iDQogICAgICApDQoNCiAgICBnZ3Bsb3QyOjpnZ3NhdmUoDQogICAgICBmaWxlLnBhdGgoZ29fZGlyX2ZpZ3MsIHBhc3RlMCgiY2x1c3Rlcl8iLCBjbHVzdGVyX2lkLCAiX0dPX2RvdHBsb3Quc3ZnIikpLA0KICAgICAgcGxvdCA9IHAsIHdpZHRoID0gNywgaGVpZ2h0ID0gbWluKDgsIDMgKyBucm93KGdvX3RvcCkgKiAwLjMpDQogICAgKQ0KICB9DQoNCiAgcmV0dXJuKGdvX2RmKQ0KfQ0KYGBgDQoNCiMjIyBSdW4gR08gRW5yaWNobWVudCBvbiBBbGwgQ2x1c3RlcnMNCg0KYGBge3Igc3RlcDEyLXJ1bi1nbywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYWxsX2NsdXN0ZXJfZ29fcmVzdWx0cyA8LSBsaXN0KCkNCg0KZm9yIChjbCBpbiBzb3J0KHVuaXF1ZShjbHVzdGVycyRjdXQpKSkgew0KICBjbHVzdGVyX2dlbmVzIDwtIG5hbWVzKGNsdXN0ZXJzJGN1dClbY2x1c3RlcnMkY3V0ID09IGNsXQ0KICBnb19yZXMgPC0gcnVuX2NsdXN0ZXJfZ28oY2x1c3Rlcl9nZW5lcywgY2wpDQoNCiAgaWYgKCFpcy5udWxsKGdvX3JlcykpIHsNCiAgICBhbGxfY2x1c3Rlcl9nb19yZXN1bHRzW1twYXN0ZTAoImNsdXN0ZXJfIiwgY2wpXV0gPC0gZ29fcmVzDQogIH0NCn0NCg0Kc2F2ZVJEUyhhbGxfY2x1c3Rlcl9nb19yZXN1bHRzLCBmaWxlLnBhdGgoZ29fZGlyX3JlcywgImFsbF9jbHVzdGVyc19HT19yZXN1bHRzLnJkcyIpKQ0KY2F0KCJDbHVzdGVycyB3aXRoIHNpZ25pZmljYW50IEdPIGVucmljaG1lbnQ6IiwgbGVuZ3RoKGFsbF9jbHVzdGVyX2dvX3Jlc3VsdHMpLCAiLyIsIGtfY2x1c3RlcnMsICJcbiIpDQpgYGANCg0KIyMjIEdPIFJlc3VsdHMgYnkgQ2x1c3Rlcg0KDQpgYGB7ciBzdGVwMTItZ28tc3VtbWFyeSwgZWNobz1GQUxTRSwgcmVzdWx0cz0nYXNpcyd9DQojIERpc3BsYXkgR08gZW5yaWNobWVudCByZXN1bHRzIGZvciBlYWNoIGNsdXN0ZXIgd2l0aCBjbGVhbiBmb3JtYXR0aW5nDQpmb3IgKGNsdXN0ZXJfbmFtZSBpbiBuYW1lcyhhbGxfY2x1c3Rlcl9nb19yZXN1bHRzKSkgew0KICBpZiAoIWlzLm51bGwoYWxsX2NsdXN0ZXJfZ29fcmVzdWx0c1tbY2x1c3Rlcl9uYW1lXV0pKSB7DQogICAgY2x1c3Rlcl9nbyA8LSBhbGxfY2x1c3Rlcl9nb19yZXN1bHRzW1tjbHVzdGVyX25hbWVdXSAlPiUNCiAgICAgIGRwbHlyOjpzbGljZV9oZWFkKG4gPSA1KSAlPiUNCiAgICAgIGRwbHlyOjp0cmFuc211dGUoDQogICAgICAgIGBCaW9sb2dpY2FsIFByb2Nlc3NgID0gRGVzY3JpcHRpb24sDQogICAgICAgIGBQLXZhbHVlYCA9IGZvcm1hdEMocHZhbHVlLCBmb3JtYXQgPSAiZSIsIGRpZ2l0cyA9IDIpLA0KICAgICAgICBgRkRSYCA9IGZvcm1hdEMocXZhbHVlLCBmb3JtYXQgPSAiZSIsIGRpZ2l0cyA9IDIpLA0KICAgICAgICBgR2VuZXNgID0gQ291bnQNCiAgICAgICkNCg0KICAgIGlmIChucm93KGNsdXN0ZXJfZ28pID4gMCkgew0KICAgICAgY2x1c3Rlcl9udW0gPC0gZ3N1YigiY2x1c3Rlcl8iLCAiIiwgY2x1c3Rlcl9uYW1lKQ0KICAgICAgY2F0KCJcblxuKipDbHVzdGVyICIsIGNsdXN0ZXJfbnVtLCAiKipcblxuIiwgc2VwID0gIiIpDQogICAgICBwcmludChrbml0cjo6a2FibGUoY2x1c3Rlcl9nbywNCiAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQgPSAiaHRtbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgdGFibGUuYXR0ciA9ICdjbGFzcz0iZ28tdGFibGUiJywNCiAgICAgICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbiA9IGMoImwiLCAiciIsICJyIiwgInIiKSkpDQogICAgICBjYXQoIlxuXG4iKQ0KICAgIH0NCiAgfQ0KfQ0KYGBgDQoNCiMgU3RlcCAxMzogRXhwcmVzc2lvbiBIZWF0bWFwcw0KDQpQZXJoYXBzIGEgbW9yZSB2aXN1YWxseSAnaW50dWl0aXZlJyB3YXkgb2YgdW5kZXJzdGFuZGluZyB0aGUgdGVtcG9yYWwgZXhwcmVzc2lvbiBvZiBtYVNpZ1Byby1pZGVudGlmaWVkIGdlbmVzIGJldHdlZW4gRDIxIGFuZCBUMjEgaXMgYnkgYSBoZWF0bWFwLiBIZXJlLCBJIHZpc3VhbGl6ZSBleHByZXNzaW9uIHBhdHRlcm5zIGZvciBlYWNoIGNsdXN0ZXIgdXNpbmcgei1zY29yZSBub3JtYWxpemVkIHZhbHVlcyBpbiBhIGhlYXRtYXAgZ2VuZXJhdGVkIGJ5IHRoZSBwaGVhdG1hcCBSIHBhY2thZ2UuIFNhbXBsZXMgYXJlIGdyb3VwZWQgYnkgZ2Vub3R5cGUgYW5kIG9yZGVyZWQgYnkgdGltZXBvaW50IChzbyBmaXJzdCBncm91cCBvZiBzYW1wbGVzIGlzIEQyMSwgdGhlbiBUMjEpLg0KDQpUaGUgaGVhdG1hcHMgbWF0Y2ggdGhlIGxpbmVwbG90IHRyZW5kLCBidXQgSSB0aGluayB0aGV5IGFyZSB2aXN1YWxseSBlYXNpZXIgdG8gaW50ZXJwcmV0Lg0KDQpgYGB7ciBzdGVwMTMtaGVhdG1hcC1zZXR1cCwgZWNobz1UUlVFfQ0KaWYgKGZpbGUuZXhpc3RzKCJkYXRhL2NvdW50cy92c3QvV0MyNF92c3RfY291bnRzLnJkcyIpKSB7DQogIHZzdF9tYXQgPC0gcmVhZFJEUygiZGF0YS9jb3VudHMvdnN0L1dDMjRfdnN0X2NvdW50cy5yZHMiKQ0KfSBlbHNlIHsNCiAgdnN0X21hdCA8LSBjb3VudHNfY3BtDQp9DQoNCmhlYXRtYXBfZGlyIDwtIGZpbGUucGF0aChmaWdfZGlyLCAiaGVhdG1hcHMiKQ0KZGlyLmNyZWF0ZShoZWF0bWFwX2RpciwgcmVjdXJzaXZlID0gVFJVRSwgc2hvd1dhcm5pbmdzID0gRkFMU0UpDQpgYGANCg0KYGBge3Igc3RlcDEzLWhlYXRtYXAtZnVuY3Rpb24sIGVjaG89VFJVRX0NCmNyZWF0ZV9jbHVzdGVyX2hlYXRtYXAgPC0gZnVuY3Rpb24oZ2VuZV9saXN0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdnN0X21hdCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX2lkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRwdXRfcGF0aCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSA3LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQgPSA3LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X3Jvd25hbWVzID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfY29sbmFtZXMgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9jb2xzID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvbnRzaXplX3JvdyA9IDcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpbnNvcml6ZV9saW1pdCA9IDIpIHsNCiAgDQogIGdlbmVzX2ZvdW5kIDwtIGludGVyc2VjdChnZW5lX2xpc3QsIHJvd25hbWVzKHZzdF9tYXQpKQ0KICANCiAgaWYgKGxlbmd0aChnZW5lc19mb3VuZCkgPCAyKSB7DQogICAgcmV0dXJuKE5VTEwpDQogIH0NCiAgDQogICMgT3JkZXIgc2FtcGxlczogRDIxIGJ5IHRpbWVwb2ludCwgdGhlbiBUMjEgYnkgdGltZXBvaW50DQogIG1ldGFfZDIxIDwtIG1ldGEgJT4lDQogICAgZHBseXI6OmZpbHRlcihnZW5vdHlwZSA9PSAiRDIxIikgJT4lDQogICAgZHBseXI6OmFycmFuZ2UodGltZXBvaW50KQ0KICANCiAgbWV0YV90MjEgPC0gbWV0YSAlPiUNCiAgICBkcGx5cjo6ZmlsdGVyKGdlbm90eXBlID09ICJUMjEiKSAlPiUNCiAgICBkcGx5cjo6YXJyYW5nZSh0aW1lcG9pbnQpDQogIA0KICBtZXRhX29yZGVyZWQgPC0gZHBseXI6OmJpbmRfcm93cyhtZXRhX2QyMSwgbWV0YV90MjEpDQogIHNhbXBsZV9vcmRlciA8LSBtZXRhX29yZGVyZWQkc2FtcGxlX2lkDQogIA0KICB2c3Rfc3Vic2V0IDwtIHZzdF9tYXRbZ2VuZXNfZm91bmQsIHNhbXBsZV9vcmRlcl0NCiAgdnN0X3pzY29yZSA8LSB0KHNjYWxlKHQodnN0X3N1YnNldCkpKQ0KICANCiAgdnN0X3pzY29yZVt2c3RfenNjb3JlID4gd2luc29yaXplX2xpbWl0XSA8LSB3aW5zb3JpemVfbGltaXQNCiAgdnN0X3pzY29yZVt2c3RfenNjb3JlIDwgLXdpbnNvcml6ZV9saW1pdF0gPC0gLXdpbnNvcml6ZV9saW1pdA0KICANCiAgYW5ub3RhdGlvbl9kZiA8LSBtZXRhX29yZGVyZWQgJT4lDQogICAgZHBseXI6OnNlbGVjdChnZW5vdHlwZSwgdGltZXBvaW50KSAlPiUNCiAgICBkcGx5cjo6bXV0YXRlKA0KICAgICAgR2Vub3R5cGUgPSBnZW5vdHlwZSwNCiAgICAgIFRpbWVwb2ludCA9IGZhY3RvcihwYXN0ZTAoIkQiLCB0aW1lcG9pbnQpLCBsZXZlbHMgPSBjKCJENiIsICJEMTAiLCAiRDE3IikpDQogICAgKSAlPiUNCiAgICBkcGx5cjo6c2VsZWN0KEdlbm90eXBlLCBUaW1lcG9pbnQpDQogIA0KICBhbm5vdGF0aW9uX2NvbG9ycyA8LSBsaXN0KA0KICAgIEdlbm90eXBlID0gYyhEMjEgPSAiIzFGNzhCNCIsIFQyMSA9ICIjRTMxQTFDIiksDQogICAgVGltZXBvaW50ID0gYyhENiA9ICIjNjZDMkE1IiwgRDEwID0gIiNGQzhENjIiLCBEMTcgPSAiIzhEQTBDQiIpDQogICkNCiAgDQogIGhlYXRtYXBfY29sb3JzIDwtIGdyRGV2aWNlczo6Y29sb3JSYW1wUGFsZXR0ZShjKA0KICAgICIjMDUzMDYxIiwgIiMyMTY2QUMiLCAiIzQzOTNDMyIsICIjOTJDNURFIiwNCiAgICAiI0QxRTVGMCIsICIjRkREQkM3IiwgIiNGNEE1ODIiLCAiI0Q2NjA0RCIsDQogICAgIiNCMjE4MkIiLCAiIzY3MDAxRiINCiAgKSkoMTAwKQ0KICANCiAgcXVhbnRpbGVfYnJlYWtzIDwtIHVuaXF1ZShxdWFudGlsZShhcy52ZWN0b3IodnN0X3pzY29yZSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSAxMDEpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5ybSA9IFRSVUUpKQ0KICANCiAgIyBHYXAgcG9zaXRpb24gYmV0d2VlbiBEMjEgYW5kIFQyMSBncm91cHMNCiAgbl9kMjEgPC0gbnJvdyhtZXRhX2QyMSkNCiAgZ2FwX3Bvc2l0aW9ucyA8LSBuX2QyMQ0KICANCiAgcCA8LSBwaGVhdG1hcDo6cGhlYXRtYXAoDQogICAgdnN0X3pzY29yZSwNCiAgICBjb2xvciA9IGhlYXRtYXBfY29sb3JzLA0KICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9ICJldWNsaWRlYW4iLA0KICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2VfY29scyA9ICJldWNsaWRlYW4iLA0KICAgIGNsdXN0ZXJpbmdfbWV0aG9kID0gImNvbXBsZXRlIiwNCiAgICBjbHVzdGVyX3Jvd3MgPSBjbHVzdGVyX3Jvd3MsDQogICAgY2x1c3Rlcl9jb2xzID0gY2x1c3Rlcl9jb2xzLA0KICAgIGFubm90YXRpb25fY29sID0gYW5ub3RhdGlvbl9kZiwNCiAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGFubm90YXRpb25fY29sb3JzLA0KICAgIHNob3dfcm93bmFtZXMgPSBzaG93X3Jvd25hbWVzLA0KICAgIHNob3dfY29sbmFtZXMgPSBzaG93X2NvbG5hbWVzLA0KICAgIGZvbnRzaXplID0gMTAsDQogICAgZm9udHNpemVfcm93ID0gZm9udHNpemVfcm93LA0KICAgIGZvbnRzaXplX2NvbCA9IDgsDQogICAgYm9yZGVyX2NvbG9yID0gTkEsDQogICAgc2NhbGUgPSAibm9uZSIsDQogICAgICAgIGFubm90YXRpb25fbGVnZW5kID0gVFJVRSwNCiAgICBhbm5vdGF0aW9uX25hbWVzX2NvbCA9IEZBTFNFLA0KICAgIGxlZ2VuZCA9IFRSVUUsDQogICAgYnJlYWtzID0gcXVhbnRpbGVfYnJlYWtzLA0KICAgIGdhcHNfY29sID0gZ2FwX3Bvc2l0aW9ucw0KICApDQogIA0KICBnckRldmljZXM6OnBuZyhvdXRwdXRfcGF0aCwgd2lkdGggPSB3aWR0aCwgaGVpZ2h0ID0gaGVpZ2h0LCB1bml0cyA9ICJpbiIsIHJlcyA9IDMwMCkNCiAgcHJpbnQocCkNCiAgZ3JEZXZpY2VzOjpkZXYub2ZmKCkNCiAgDQogIHJldHVybihwKQ0KfQ0KYGBgDQoNCmBgYHtyIHN0ZXAxMy1kaXNwbGF5LWhlYXRtYXBzLCBlY2hvPVRSVUUsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTYsIGZpZy5jYXA9IkV4cHJlc3Npb24gaGVhdG1hcHMgZm9yIGFsbCBjbHVzdGVycyJ9DQojIERpc3BsYXkgaGVhdG1hcHMgZm9yIGFsbCBjbHVzdGVycw0KYWxsX2NsdXN0ZXJzIDwtIHNvcnQodW5pcXVlKGNsdXN0ZXJzJGN1dCkpDQoNCmZvciAoY2wgaW4gYWxsX2NsdXN0ZXJzKSB7DQogIGNsdXN0ZXJfZ2VuZXMgPC0gbmFtZXMoY2x1c3RlcnMkY3V0KVtjbHVzdGVycyRjdXQgPT0gY2xdDQogIA0KICBnZW5lc19mb3VuZCA8LSBpbnRlcnNlY3QoY2x1c3Rlcl9nZW5lcywgcm93bmFtZXModnN0X21hdCkpDQogIGlmIChsZW5ndGgoZ2VuZXNfZm91bmQpIDwgMikgbmV4dA0KICANCiAgIyBPcmRlciBzYW1wbGVzOiBEMjEgc2FtcGxlcyBieSB0aW1lcG9pbnQsIHRoZW4gVDIxIHNhbXBsZXMgYnkgdGltZXBvaW50DQogIG1ldGFfZDIxIDwtIG1kICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIoZ2Vub3R5cGUgPT0gIkQyMSIpICU+JQ0KICAgIGRwbHlyOjphcnJhbmdlKHRpbWVwb2ludCkNCiAgDQogIG1ldGFfdDIxIDwtIG1kICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIoZ2Vub3R5cGUgPT0gIlQyMSIpICU+JQ0KICAgIGRwbHlyOjphcnJhbmdlKHRpbWVwb2ludCkNCiAgDQogIG1ldGFfb3JkZXJlZCA8LSBkcGx5cjo6YmluZF9yb3dzKG1ldGFfZDIxLCBtZXRhX3QyMSkNCiAgc2FtcGxlX29yZGVyIDwtIG1ldGFfb3JkZXJlZCRzYW1wbGVfaWQNCiAgDQogIHZzdF9zdWJzZXQgPC0gdnN0X21hdFtnZW5lc19mb3VuZCwgc2FtcGxlX29yZGVyXQ0KICB2c3RfenNjb3JlIDwtIHQoc2NhbGUodCh2c3Rfc3Vic2V0KSkpDQogIA0KICB2c3RfenNjb3JlW3ZzdF96c2NvcmUgPiAzXSA8LSAzDQogIHZzdF96c2NvcmVbdnN0X3pzY29yZSA8IC0zXSA8LSAtMw0KICANCiAgYW5ub3RhdGlvbl9kZiA8LSBtZXRhX29yZGVyZWQgJT4lDQogICAgZHBseXI6OnNlbGVjdChnZW5vdHlwZSwgdGltZXBvaW50KSAlPiUNCiAgICBkcGx5cjo6bXV0YXRlKA0KICAgICAgR2Vub3R5cGUgPSBnZW5vdHlwZSwNCiAgICAgIFRpbWVwb2ludCA9IGZhY3RvcihwYXN0ZTAoIkQiLCB0aW1lcG9pbnQpLCBsZXZlbHMgPSBjKCJENiIsICJEMTAiLCAiRDE3IikpDQogICAgKSAlPiUNCiAgICBkcGx5cjo6c2VsZWN0KEdlbm90eXBlLCBUaW1lcG9pbnQpDQogIA0KICBhbm5vdGF0aW9uX2NvbG9ycyA8LSBsaXN0KA0KICAgIEdlbm90eXBlID0gYyhEMjEgPSAiIzFGNzhCNCIsIFQyMSA9ICIjRTMxQTFDIiksDQogICAgVGltZXBvaW50ID0gYyhENiA9ICIjNjZDMkE1IiwgRDEwID0gIiNGQzhENjIiLCBEMTcgPSAiIzhEQTBDQiIpDQogICkNCiAgDQogIGhlYXRtYXBfY29sb3JzIDwtIGdyRGV2aWNlczo6Y29sb3JSYW1wUGFsZXR0ZShjKA0KICAgICIjMDUzMDYxIiwgIiMyMTY2QUMiLCAiIzQzOTNDMyIsICIjOTJDNURFIiwNCiAgICAiI0QxRTVGMCIsICIjRkREQkM3IiwgIiNGNEE1ODIiLCAiI0Q2NjA0RCIsDQogICAgIiNCMjE4MkIiLCAiIzY3MDAxRiINCiAgKSkoMTAwKQ0KICANCiAgcXVhbnRpbGVfYnJlYWtzIDwtIHVuaXF1ZShxdWFudGlsZShhcy52ZWN0b3IodnN0X3pzY29yZSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSAxMDEpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5ybSA9IFRSVUUpKQ0KICANCiAgIyBHYXAgcG9zaXRpb24gYmV0d2VlbiBEMjEgYW5kIFQyMSBncm91cHMNCiAgbl9kMjEgPC0gbnJvdyhtZXRhX2QyMSkNCiAgZ2FwX3Bvc2l0aW9ucyA8LSBuX2QyMQ0KICANCiAgbl9nZW5lcyA8LSBsZW5ndGgoY2x1c3Rlcl9nZW5lcykNCiAgc2hvd19uYW1lcyA8LSBuX2dlbmVzIDw9IDUwDQogIA0KICBwIDwtIHBoZWF0bWFwOjpwaGVhdG1hcCgNCiAgICB2c3RfenNjb3JlLA0KICAgIGNvbG9yID0gaGVhdG1hcF9jb2xvcnMsDQogICAgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzID0gImV1Y2xpZGVhbiIsDQogICAgY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gImV1Y2xpZGVhbiIsDQogICAgY2x1c3RlcmluZ19tZXRob2QgPSAiY29tcGxldGUiLA0KICAgIGNsdXN0ZXJfcm93cyA9IFRSVUUsDQogICAgY2x1c3Rlcl9jb2xzID0gRkFMU0UsDQogICAgYW5ub3RhdGlvbl9jb2wgPSBhbm5vdGF0aW9uX2RmLA0KICAgIGFubm90YXRpb25fY29sb3JzID0gYW5ub3RhdGlvbl9jb2xvcnMsDQogICAgc2hvd19yb3duYW1lcyA9IHNob3dfbmFtZXMsDQogICAgc2hvd19jb2xuYW1lcyA9IEZBTFNFLA0KICAgIGZvbnRzaXplID0gMTAsDQogICAgZm9udHNpemVfcm93ID0gNywNCiAgICBmb250c2l6ZV9jb2wgPSA4LA0KICAgIGJvcmRlcl9jb2xvciA9IE5BLA0KICAgIHNjYWxlID0gIm5vbmUiLA0KICAgICAgICBhbm5vdGF0aW9uX2xlZ2VuZCA9IFRSVUUsDQogICAgYW5ub3RhdGlvbl9uYW1lc19jb2wgPSBGQUxTRSwNCiAgICBsZWdlbmQgPSBUUlVFLA0KICAgIGJyZWFrcyA9IHF1YW50aWxlX2JyZWFrcywNCiAgICBnYXBzX2NvbCA9IGdhcF9wb3NpdGlvbnMsDQogICAgbWFpbiA9IHBhc3RlMCgiQ2x1c3RlciAiLCBjbCwgIiAobj0iLCBuX2dlbmVzLCAiIGdlbmVzKSIpDQogICkNCiAgDQogIHByaW50KHApDQp9DQpgYGANCg0KVGhlIGZvbGxvd2luZyBjb2RlIHNhdmVzIGhlYXRtYXBzIHRvIFBORyBmaWxlcy4gU2V0IGBldmFsPVRSVUVgIHRvIHJ1bjoNCg0KYGBge3Igc3RlcDEzLWNyZWF0ZS1oZWF0bWFwcywgZXZhbD1GQUxTRSwgZWNobz1UUlVFfQ0KIyBTYXZlIGhlYXRtYXBzIHRvIGZpbGVzDQpmb3IgKGNsIGluIHNvcnQodW5pcXVlKGNsdXN0ZXJzJGN1dCkpKSB7DQogIGNsdXN0ZXJfZ2VuZXMgPC0gbmFtZXMoY2x1c3RlcnMkY3V0KVtjbHVzdGVycyRjdXQgPT0gY2xdDQogIA0KICBuX2dlbmVzIDwtIGxlbmd0aChjbHVzdGVyX2dlbmVzKQ0KICBzaG93X25hbWVzIDwtIG5fZ2VuZXMgPD0gNTANCiAgaGVpZ2h0X3ZhbCA8LSBtaW4oOCwgbWF4KDQsIG5fZ2VuZXMgKiAwLjA4KSkNCiAgDQogIGNyZWF0ZV9jbHVzdGVyX2hlYXRtYXAoDQogICAgZ2VuZV9saXN0ID0gY2x1c3Rlcl9nZW5lcywNCiAgICB2c3RfbWF0ID0gdnN0X21hdCwNCiAgICBtZXRhID0gbWQsDQogICAgY2x1c3Rlcl9pZCA9IGNsLA0KICAgIG91dHB1dF9wYXRoID0gZmlsZS5wYXRoKGhlYXRtYXBfZGlyLCBwYXN0ZTAoImNsdXN0ZXJfIiwgY2wsICJfaGVhdG1hcC5wbmciKSksDQogICAgd2lkdGggPSA1LA0KICAgIGhlaWdodCA9IGhlaWdodF92YWwsDQogICAgc2hvd19yb3duYW1lcyA9IHNob3dfbmFtZXMsDQogICAgc2hvd19jb2xuYW1lcyA9IEZBTFNFLA0KICAgIGNsdXN0ZXJfcm93cyA9IFRSVUUsDQogICAgY2x1c3Rlcl9jb2xzID0gRkFMU0UsDQogICAgZm9udHNpemVfcm93ID0gNywNCiAgICB3aW5zb3JpemVfbGltaXQgPSAzDQogICkNCn0NCmBgYA0KDQojIFN0ZXAgMTQ6IFNhdmUgUmVzdWx0cw0KDQpGaW5hbGx5LCBJIHNhdmUgdGhlIGNsdXN0ZXIgYXNzaWdubWVudHMgYW5kIGFuYWx5c2lzIG91dHB1dHMgZm9yIGRvd25zdHJlYW0gdXNlLiBEZXBlbmRpbmcgb24gdGhlIG51bWJlciBvZiBnZW5lcyB0aGF0IGFyZSB0aW1lLWRlcGVuZGVudCwgbWFTaWdQcm8gY2FuIHRha2UgMTUtMjAgbWludXRlcyB0byBydW4gYW5kIGNsdXN0ZXIgeW91ciBnZW5lcy4gVGhlcmVmb3JlIEkgcmVjb21tZW5kIHNhdmluZyB0aGUgb3V0cHV0IG9mIG1hU2lnUHJvIGltbWVkaWF0ZWx5IHNvIHRoYXQgeW91IGRvbid0IGhhdmUgdG8gcmUtcnVuIGl0IGV2ZXJ5IHRpbWUuDQoNCmBgYHtyIHN0ZXAxNC1zYXZlLCBlY2hvPVRSVUV9DQojIFNhdmUgY2x1c3RlciBhc3NpZ25tZW50cw0KcmVhZHI6OndyaXRlX2NzdihjbF9hc3NpZ24sIGZpbGUucGF0aChyZXNfZGlyLCAiY2x1c3Rlcl9hc3NpZ25tZW50cy5jc3YiKSkNCnNhdmVSRFMoY2xfYXNzaWduLCBmaWxlLnBhdGgocmVzX2RpciwgImNsdXN0ZXJfYXNzaWdubWVudHMucmRzIikpDQoNCmNhdCgiUmVzdWx0cyBzYXZlZCB0bzoiLCByZXNfZGlyLCAiXG4iKQ0KY2F0KCJGaWd1cmVzIHNhdmVkIHRvOiIsIGZpZ19kaXIsICJcbiIpDQpgYGANCg0KIyBTdW1tYXJ5DQoNCkluIHN1bW1hcnksIG1hU2lnUHJvIGlzIGEgdXNlZnVsIFIgcGFja2FnZSBmb3IgaWRlbnRpZmllZCBnZW5lcyB3aXRoIGdlbm90eXBlLXNwZWNpZmljIHRlbXBvcmFsIGV4cHJlc3Npb24gcGF0dGVybnMgYW5kIGdyb3VwZWQgdGhlbSBpbnRvIGZ1bmN0aW9uYWxseSBjb2hlcmVudCBjbHVzdGVycy4NCg0KSWYgeW91IGhhdmUgdGltZS1zZXJpZXMgYnVsayBSTkEtc2VxIGRhdGEgd2l0aCAzKyB0aW1lcG9pbnRzLCBhbmQgeW91IGhhdmUgdHdvIGdyb3VwcyBvZiBzYW1wbGVzICh3aGV0aGVyIHRoYXQgaXMgdHJlYXRtZW50IG9yIGdlbm90eXBlIG9yIHNvbWUgY29uZGl0aW9uKSwgdGhlbiBtYVNpZ1BybyBhbGxvd3MgeW91IGlkZW50aWZ5IGNsdXN0ZXJzIG9mIGdlbmVzIHdob3NlIGV4cHJlc3Npb24gb3ZlciB0aW1lIGlzIHRlbXBvcmFsbHkgLg0KDQpXaGlsZSBzb21lIG1hU2lnUHJvIGNsdXN0ZXJzIGFyZSBsYXJnZWx5IHNpbWlsYXIgaW4gZXhwcmVzc2lvIGJldHdlZW4gdGhlIHR3byBncm91cHMsIEkgdGhpbmsgdGhlIG1haW4gYmlvbG9naWNhbCBpbnNpZ2h0IGhlcmUgDQoNCkZvciBleGFtcGxlLCBjbHVzdGVyIDEwIGlzIGVucmljaGVkIGZvciB0aGUgR08gdGVybSAnY2Fub25pY2FsIFdudCBzaWduYWxpbmcgcGF0aHdheS4nIEluIHRoZSBwYXBlciB0aGF0IEkgb2J0YWluZWQgdGhpcyBkYXRhIGZyb20sIHRoZSBhdXRob3JzIGRpc2N1c3MgV250IHNpZ25hbGluZyBwYXRod2F5IGFjdGl2aXR5IGFzIGJlaW5nIGRhbXBlbmVkIG9yIGRlY3JlYXNlZCBpbiBUMjEgY29tcGFyZWQgdG8gRDIxLiBDbHVzdGVyIDEwIGNvcnJvYm9yYXRlcyB0aGlzIGNsYWltLCBhcyAuIEZ1cnRoZXJtb3JlLCB3ZSBjYW4gc2VlIHRoYXQgdGhpcyBjbHVzdGVyIGVucmljaGVkIGZvciBXbnQgc2lnbmFsaW5nIHBhdGh3YXkgZ2VuZXMgc2VlbXMgdG8gYmUgaGlnaGVzdCBpbiBleHByZXNzaW9uIGR1cmluZyB0aGUgbmV1cmFsIHByb2dlbml0b3IgcGhhc2UuIEhlbmNlLCBXbnQgc2lnbmFsaW5nIGNvdWxkIGJlIG1vZHVsYXRpbmcgdGhlIHRyYW5zaXRpb24gb2YgbmV1cmFsIHByb2dlbml0b3IgdG8gbWF0dXJlIG5ldXJvbi4gQmVjYXVzZSBleHByZXNzaW9uIG9mIHRoaXMgY2x1c3RlciBpcyBERUNSRUFTRUQgaW4gVDIxIHJlbGF0aXZlIHRvIEQyMSwgdGhpcyBzdWdnZXN0cyBwZXJoYXBzIGZhc3RlciB0cmFuc2l0aW9uIG9mIG5ldXJhbCBwcm9nZW5pdG9yIHRvIG1hdHVyZSBuZXVyb24gaW4gVDIxLiBBcyBleHBsYWluZWQgYmVmb3JlLCB0aGlzIGZpbmRpbmcgb2YgZmFzdGVyIGFjcXVpc3Rpb24gb2YgbWF0dXJlIG5ldXJvbiBpZGVudGl0eSBpcyBjb3Jyb2JvcmF0ZWQgYnkgb3RoZXIgY2x1c3RlcnMsIHN1Y2ggYXMgY2x1c3RlciA2LiANCg0KVGhlc2UgYXJlIG9idmlvdXNseSBicm9hZCBjbGFpbXMgdGhhdCBuZWVkIGZ1cnRoZXIgZXZpZGVuY2UgdG8gc3Vic3RhbnRpYXRlLCBidXQgdGhlIGdvYWwgb2YgbWFTaWdQcm8gaXMgdG8gZ2l2ZSB5b3UgZGlyZWN0aW9uIGZvciB3aGF0IGRpcmVjdGlvbnMvYXZlbnVlcyB5b3UgY2FuIHB1cnN1ZSBmb3IgeW91ciBkYXRhLg0KDQojIyBSZXN1bHRzIE92ZXJ2aWV3DQoNCmBgYHtyIGZpbmFsLXN1bW1hcnksIGVjaG89RkFMU0V9DQpzdW1tYXJ5X3RhYmxlIDwtIGRhdGEuZnJhbWUoDQogIE1ldHJpYyA9IGMoIkdlbmVzIHRlc3RlZCIsDQogICAgICAgICAgICAgIlNpZ25pZmljYW50IHRlbXBvcmFsIGdlbmVzIiwNCiAgICAgICAgICAgICAiR2Vub3R5cGUtc3BlY2lmaWMgZ2VuZXMiLA0KICAgICAgICAgICAgICJDbHVzdGVycyBpZGVudGlmaWVkIiwNCiAgICAgICAgICAgICAiQ2x1c3RlcnMgd2l0aCBHTyBlbnJpY2htZW50IiksDQogIFZhbHVlID0gYyhmb3JtYXQobnJvdyhleHByX21hdCksIGJpZy5tYXJrID0gIiwiKSwNCiAgICAgICAgICAgIHBhc3RlMChmb3JtYXQobGVuZ3RoKGZpdCRTRUxFQyksIGJpZy5tYXJrID0gIiwiKSwNCiAgICAgICAgICAgICAgICAgICAiICgiLCByb3VuZCgxMDAgKiBsZW5ndGgoZml0JFNFTEVDKSAvIG5yb3coZXhwcl9tYXQpLCAxKSwgIiUpIiksDQogICAgICAgICAgICBwYXN0ZTAoZm9ybWF0KGxlbmd0aChzaWdfZ2VuZXMpLCBiaWcubWFyayA9ICIsIiksDQogICAgICAgICAgICAgICAgICAgIiAoIiwgcm91bmQoMTAwICogbGVuZ3RoKHNpZ19nZW5lcykgLyBucm93KGV4cHJfbWF0KSwgMSksICIlKSIpLA0KICAgICAgICAgICAga19jbHVzdGVycywNCiAgICAgICAgICAgIGxlbmd0aChhbGxfY2x1c3Rlcl9nb19yZXN1bHRzKSkNCikNCg0Ka25pdHI6OmthYmxlKHN1bW1hcnlfdGFibGUsIGNvbC5uYW1lcyA9IGMoIk1ldHJpYyIsICJWYWx1ZSIpLCBhbGlnbiA9IGMoImwiLCAiciIpKQ0KYGBgDQoNCiMjIEFuYWx5c2lzIFBhcmFtZXRlcnMNCg0KYGBge3IgcGFyYW1zLXN1bW1hcnksIGVjaG89RkFMU0V9DQpwYXJhbXNfdGFibGUgPC0gZGF0YS5mcmFtZSgNCiAgUGFyYW1ldGVyID0gYygiRXhwcmVzc2lvbiBmaWx0ZXIiLCAiUG9seW5vbWlhbCBkZWdyZWUiLCAiRkRSIHRocmVzaG9sZCAocC52ZWN0b3IpIiwNCiAgICAgICAgICAgICAgICAiQWxwaGEgdGhyZXNob2xkIChULmZpdCkiLCAiUsKyIHRocmVzaG9sZCIsICJOdW1iZXIgb2YgY2x1c3RlcnMiKSwNCiAgVmFsdWUgPSBjKHBhc3RlMCgiTWVhbiBDUE0gPiAiLCBtaW5fY3BtKSwNCiAgICAgICAgICAgIHBvbHlfZGVncmVlLA0KICAgICAgICAgICAgcV90aHJlc2hvbGQsDQogICAgICAgICAgICBhbHBoYV90aHJlc2hvbGQsDQogICAgICAgICAgICByc3FfdGhyZXNob2xkLA0KICAgICAgICAgICAga19jbHVzdGVycykNCikNCg0Ka25pdHI6OmthYmxlKHBhcmFtc190YWJsZSwgY29sLm5hbWVzID0gYygiUGFyYW1ldGVyIiwgIlZhbHVlIiksIGFsaWduID0gYygibCIsICJyIikpDQpgYGANCg0KIyMgT3V0cHV0IEZpbGVzDQoNCkFsbCByZXN1bHRzIGFyZSBzYXZlZCB0bzoNCg0KLSAqKkNsdXN0ZXIgYXNzaWdubWVudHM6KiogYHJlc3VsdHMvbWFzaWdwcm8vY2x1c3Rlcl9hc3NpZ25tZW50cy5jc3ZgDQotICoqU3VtbWFyeSBzdGF0aXN0aWNzOioqIGByZXN1bHRzL21hc2lncHJvL3N1bW1hcnlfc3RhdGlzdGljcy5jc3ZgDQotICoqR08gZW5yaWNobWVudDoqKiBgcmVzdWx0cy9tYXNpZ3Byby9nb19lbnJpY2htZW50L2ANCi0gKipGaWd1cmVzOioqIGByZXN1bHRzL2ZpZ3VyZXMvbWFzaWdwcm8vYA0KDQojIyBSZWZlcmVuY2VzDQoNCjEuIENvbmVzYSBBLCBOdWVkYSBNSiwgZXQgYWwuICgyMDA2KS4gbWFTaWdQcm86IGEgbWV0aG9kIHRvIGlkZW50aWZ5IHNpZ25pZmljYW50bHkgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gcHJvZmlsZXMgaW4gdGltZS1jb3Vyc2UgbWljcm9hcnJheSBleHBlcmltZW50cy4gKkJpb2luZm9ybWF0aWNzKiwgMjIoOSk6MTA5Ni0xMTAyLiBbZG9pOjEwLjEwOTMvYmlvaW5mb3JtYXRpY3MvYnRsMDU2XShodHRwczovL2RvaS5vcmcvMTAuMTA5My9iaW9pbmZvcm1hdGljcy9idGwwNTYpDQoNCjIuIE51ZWRhIE1KLCBUYXJhem9uYSBTLCBDb25lc2EgQS4gKDIwMTQpLiBOZXh0IG1hU2lnUHJvOiB1cGRhdGluZyBtYVNpZ1BybyBiaW9jb25kdWN0b3IgcGFja2FnZSBmb3IgUk5BLXNlcSB0aW1lIHNlcmllcy4gKkJpb2luZm9ybWF0aWNzKiwgMzAoMTgpOjI1OTgtMjYwMi4gW2RvaToxMC4xMDkzL2Jpb2luZm9ybWF0aWNzL2J0dTMzM10oaHR0cHM6Ly9kb2kub3JnLzEwLjEwOTMvYmlvaW5mb3JtYXRpY3MvYnR1MzMzKQ0KDQojIFNlc3Npb24gSW5mb3JtYXRpb24NCg0KRm9yIHJlcHJvZHVjaWJpbGl0eSwgdGhlIGNvbXBsZXRlIFIgc2Vzc2lvbiBpbmZvcm1hdGlvbiBpcyBwcm92aWRlZCBiZWxvdy4NCg0KYGBge3Igc2Vzc2lvbi1pbmZvLCBlY2hvPVRSVUV9DQpzZXNzaW9uSW5mbygpDQpgYGANCg==