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

Step Description
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)

Look at metadata

I will show the experimental design before proceeding so that it’s clear how to adapt this workflow to your specific experimental design.

# View complete metadata
knitr::kable(metadata, caption = "Sample Metadata")
Sample Metadata
sample_id timepoint genotype stage rep isogenic genotype_timepoint isogenic_genotype_timepoint isogenic_genotype_timepoint_rep
BA1D6_S1 BA1D6_S1 6 D21 neuroepithelium 1 WC24 D21_D6 WC24_D21_D6 WC24_D21_D6_1
BA2D6_S2 BA2D6_S2 6 D21 neuroepithelium 2 WC24 D21_D6 WC24_D21_D6 WC24_D21_D6_2
BA3D6_S3 BA3D6_S3 6 D21 neuroepithelium 3 WC24 D21_D6 WC24_D21_D6 WC24_D21_D6_3
BA1D10_S4 BA1D10_S4 10 D21 early neuroectoderm 1 WC24 D21_D10 WC24_D21_D10 WC24_D21_D10_1
BA2D10_S5 BA2D10_S5 10 D21 early neuroectoderm 2 WC24 D21_D10 WC24_D21_D10 WC24_D21_D10_2
BA3D10_S6 BA3D10_S6 10 D21 early neuroectoderm 3 WC24 D21_D10 WC24_D21_D10 WC24_D21_D10_3
BA1D17_S7 BA1D17_S7 17 D21 definitive neuroectoderm 1 WC24 D21_D17 WC24_D21_D17 WC24_D21_D17_1
BA2D17_S8 BA2D17_S8 17 D21 definitive neuroectoderm 2 WC24 D21_D17 WC24_D21_D17 WC24_D21_D17_2
BA3D17_S9 BA3D17_S9 17 D21 definitive neuroectoderm 3 WC24 D21_D17 WC24_D21_D17 WC24_D21_D17_3
MA1D6_S10 MA1D6_S10 6 T21 neuroepithelium 1 WC24 T21_D6 WC24_T21_D6 WC24_T21_D6_1
MA2D6_S11 MA2D6_S11 6 T21 neuroepithelium 2 WC24 T21_D6 WC24_T21_D6 WC24_T21_D6_2
MA3D6_S12 MA3D6_S12 6 T21 neuroepithelium 3 WC24 T21_D6 WC24_T21_D6 WC24_T21_D6_3
MA1D10_S13 MA1D10_S13 10 T21 early neuroectoderm 1 WC24 T21_D10 WC24_T21_D10 WC24_T21_D10_1
MA2D10_S14 MA2D10_S14 10 T21 early neuroectoderm 2 WC24 T21_D10 WC24_T21_D10 WC24_T21_D10_2
MA3D10_S15 MA3D10_S15 10 T21 early neuroectoderm 3 WC24 T21_D10 WC24_T21_D10 WC24_T21_D10_3
MA1D17_S16 MA1D17_S16 17 T21 definitive neuroectoderm 1 WC24 T21_D17 WC24_T21_D17 WC24_T21_D17_1
MA2D17_S17 MA2D17_S17 17 T21 definitive neuroectoderm 2 WC24 T21_D17 WC24_T21_D17 WC24_T21_D17_2
MA3D17_S18 MA3D17_S18 17 T21 definitive neuroectoderm 3 WC24 T21_D17 WC24_T21_D17 WC24_T21_D17_3
# Sample counts per group
geno_time_table <- table(metadata$genotype, metadata$timepoint)
knitr::kable(geno_time_table,
             caption = "Samples per Genotype and Timepoint",
             col.names = c("Day 6", "Day 10", "Day 17"))
Samples per Genotype and Timepoint
Day 6 Day 10 Day 17
D21 3 3 3
T21 3 3 3

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
Time Replicate D21 T21
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.

Degree Curve Type Use Case
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)
T21vsD21 Time TimexT21 Time2 Time2xT21
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 7: Extract Significant Genes

After identifying genes that are time-dependent and identifying genes that change temporally that differ between treatment/genotype, the next step is to identify genes with significant genotype-specific patterns using get.siggenes(). This R² threshold filters specifically for genes where the regression model explains a substantial proportion of variance.

In the original maSigPro paper, the authors note that Rsq of 0.7 performed well. However, there have been some cases when Rsq of 0.7 has resulted in 5000+ genes that are clustered at the of this workflow. Therefore, I often use higher values (0.7+), as higher values yield more reliable genes but fewer results. Lower values (0.5-0.6) include more genes but with less confident model fits. Adjust based on your sample size and downstream goals, and you can further check the quality of the fit by individually looking at genes within each cluster to assess the fidelity of the fitting.

rsq_threshold <- 0.7

sigs <- maSigPro::get.siggenes(
  tstep = tfit,
  rsq = rsq_threshold,
  vars = "groups"
)

sig_genes <- rownames(sigs$sig.genes$T21vsD21$sig.profiles)
cat("Genes with genotype-specific temporal patterns:", length(sig_genes), "\n")
## Genes with genotype-specific temporal patterns: 5973

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:

Parameter Description
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
)
maSigPro cluster visualization showing gene expression trajectories grouped by similarity.

maSigPro cluster visualization showing gene expression trajectories grouped by similarity.

maSigPro cluster visualization showing gene expression trajectories grouped by similarity.

maSigPro cluster visualization showing gene expression trajectories grouped by similarity.

maSigPro cluster visualization showing gene expression trajectories grouped by similarity.

maSigPro cluster visualization showing gene expression trajectories grouped by similarity.

# 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
cluster n_genes
1 799
2 480
3 1497
4 361
5 342
6 496
7 514
8 829
9 540
10 115
maSigPro cluster visualization showing gene expression trajectories grouped by similarity.

maSigPro cluster visualization showing gene expression trajectories grouped by similarity.

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
Combined cluster profile plot showing all clusters

Combined cluster profile plot showing all clusters

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
total_genes_tested significant_temporal significant_genotype_specific num_clusters polynomial_degree q_threshold rsq_threshold
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)
}
Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

Expression heatmaps for all clusters

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

Metric Value
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

Parameter Value
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

  1. 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

  2. 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

Session Information

For reproducibility, the complete R session information is provided below.

sessionInfo()
## R version 4.4.2 (2024-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26100)
## 
## Matrix products: default
## 
## 
## locale:
## [1] LC_COLLATE=English_United States.utf8 
## [2] LC_CTYPE=English_United States.utf8   
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C                          
## [5] LC_TIME=English_United States.utf8    
## 
## time zone: America/Chicago
## tzcode source: internal
## 
## attached base packages:
## [1] stats4    stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
##  [1] org.Hs.eg.db_3.20.0    AnnotationDbi_1.68.0   IRanges_2.40.1        
##  [4] S4Vectors_0.44.0       Biobase_2.66.0         BiocGenerics_0.52.0   
##  [7] clusterProfiler_4.14.4 maSigPro_1.78.0        stringr_1.6.0         
## [10] readr_2.1.5            pheatmap_1.0.12        patchwork_1.3.2       
## [13] reshape2_1.4.4         tidyr_1.3.1            dplyr_1.1.4           
## [16] ggplot2_4.0.1          knitr_1.50            
## 
## loaded via a namespace (and not attached):
##   [1] DBI_1.2.3               gson_0.1.0              rlang_1.1.4            
##   [4] magrittr_2.0.3          DOSE_4.0.1              compiler_4.4.2         
##   [7] RSQLite_2.3.9           systemfonts_1.1.0       venn_1.12              
##  [10] png_0.1-8               vctrs_0.6.5             pkgconfig_2.0.3        
##  [13] crayon_1.5.3            fastmap_1.2.0           XVector_0.46.0         
##  [16] labeling_0.4.3          rmarkdown_2.29          tzdb_0.4.0             
##  [19] enrichplot_1.26.6       UCSC.utils_1.2.0        ragg_1.3.3             
##  [22] purrr_1.0.2             bit_4.5.0.1             xfun_0.52              
##  [25] zlibbioc_1.52.0         cachem_1.1.0            aplot_0.2.8            
##  [28] GenomeInfoDb_1.42.1     jsonlite_1.8.9          blob_1.2.4             
##  [31] BiocParallel_1.40.0     parallel_4.4.2          R6_2.6.1               
##  [34] bslib_0.9.0             stringi_1.8.4           RColorBrewer_1.1-3     
##  [37] jquerylib_0.1.4         GOSemSim_2.32.0         Rcpp_1.0.13-1          
##  [40] ggtangle_0.0.7          R.utils_2.13.0          Matrix_1.7-1           
##  [43] splines_4.4.2           igraph_2.1.2            tidyselect_1.2.1       
##  [46] qvalue_2.38.0           rstudioapi_0.17.1       dichromat_2.0-0.1      
##  [49] yaml_2.3.10             codetools_0.2-20        admisc_0.38            
##  [52] lattice_0.22-6          tibble_3.2.1            plyr_1.8.9             
##  [55] treeio_1.30.0           withr_3.0.2             KEGGREST_1.46.0        
##  [58] S7_0.2.0                evaluate_1.0.4          gridGraphics_0.5-1     
##  [61] mclust_6.1.1            Biostrings_2.74.0       pillar_1.11.1          
##  [64] ggtree_3.14.0           ggfun_0.2.0             generics_0.1.4         
##  [67] vroom_1.6.5             hms_1.1.3               scales_1.4.0           
##  [70] tidytree_0.4.6          glue_1.8.0              lazyeval_0.2.2         
##  [73] tools_4.4.2             data.table_1.16.4       fgsea_1.32.0           
##  [76] fs_1.6.5                fastmatch_1.1-4         cowplot_1.2.0          
##  [79] grid_4.4.2              ape_5.8                 colorspace_2.1-1       
##  [82] nlme_3.1-166            GenomeInfoDbData_1.2.13 cli_3.6.3              
##  [85] textshaping_0.4.1       svglite_2.1.3           gtable_0.3.6           
##  [88] R.methodsS3_1.8.2       yulab.utils_0.2.0       sass_0.4.10            
##  [91] digest_0.6.37           ggrepel_0.9.6           ggplotify_0.1.2        
##  [94] farver_2.1.2            memoise_2.0.1           htmltools_0.5.8.1      
##  [97] R.oo_1.27.1             lifecycle_1.0.4         httr_1.4.7             
## [100] GO.db_3.20.0            bit64_4.5.2             MASS_7.3-61
LS0tDQp0aXRsZTogIm1hU2lnUHJvIFR1dG9yaWFsIg0Kc3VidGl0bGU6ICJtYVNpZ1BybyBSIHBhY2thZ2Ugd29ya2Zsb3cgZm9yIHRpbWUtY291cnNlIFJOQS1zZXEgYW5hbHlzaXMgZm9yIGNvbmRpdGlvbi1zcGVjaWZpYyB0ZW1wb3JhbCBnZW5lIGV4cHJlc3Npb24gcGF0dGVybnMiDQphdXRob3I6ICJab2hlYiBLaGFuIg0KZGF0ZTogIlNlcHRlbWJlciAyLCAyMDI1Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogdHJ1ZQ0KICAgICAgc21vb3RoX3Njcm9sbDogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMg0KICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IGZsYXRseQ0KICAgIGhpZ2hsaWdodDogcHlnbWVudHMNCiAgICBmaWdfd2lkdGg6IDEwDQogICAgZmlnX2hlaWdodDogNg0KICAgIGZpZ19jYXB0aW9uOiB0cnVlDQogICAgZGZfcHJpbnQ6IGthYmxlDQotLS0NCg0KPHN0eWxlPg0KLyogPT09PT09PT09PSBMQVlPVVQgRklYOiBQcmV2ZW50IFRPQyBvdmVybGFwID09PT09PT09PT0gKi8NCi5jb2wtbWQtOSB7DQogIHdpZHRoOiA3NSU7DQogIG1hcmdpbi1sZWZ0OiAyNSU7DQp9DQoNCi5jb2wtbWQtMyB7DQogIHdpZHRoOiAyMiU7DQogIHBvc2l0aW9uOiBmaXhlZDsNCiAgdG9wOiA1MHB4Ow0KICBsZWZ0OiAwOw0KICBoZWlnaHQ6IGNhbGMoMTAwdmggLSA1MHB4KTsNCiAgb3ZlcmZsb3cteTogYXV0bzsNCiAgcGFkZGluZzogMTVweDsNCiAgYmFja2dyb3VuZC1jb2xvcjogI2Y4ZjlmYTsNCiAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2RlZTJlNjsNCn0NCg0KQG1lZGlhIChtYXgtd2lkdGg6IDk5MnB4KSB7DQogIC5jb2wtbWQtOSB7DQogICAgd2lkdGg6IDEwMCU7DQogICAgbWFyZ2luLWxlZnQ6IDA7DQogIH0NCiAgLmNvbC1tZC0zIHsNCiAgICBwb3NpdGlvbjogcmVsYXRpdmU7DQogICAgd2lkdGg6IDEwMCU7DQogICAgaGVpZ2h0OiBhdXRvOw0KICAgIGJvcmRlci1yaWdodDogbm9uZTsNCiAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2RlZTJlNjsNCiAgfQ0KfQ0KDQovKiA9PT09PT09PT09IFR5cG9ncmFwaHkgPT09PT09PT09PSAqLw0KYm9keSB7DQogIGZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICJTZWdvZSBVSSIsIFJvYm90bywgIkhlbHZldGljYSBOZXVlIiwgQXJpYWwsIHNhbnMtc2VyaWY7DQogIGZvbnQtc2l6ZTogMTVweDsNCiAgbGluZS1oZWlnaHQ6IDEuNzsNCiAgY29sb3I6ICMzMzM7DQp9DQoNCi8qID09PT09PT09PT0gQ29kZSBibG9ja3MgPT09PT09PT09PSAqLw0KcHJlIHsNCiAgYmFja2dyb3VuZC1jb2xvcjogI2Y4ZjlmYTsNCiAgYm9yZGVyOiAxcHggc29saWQgI2UxZTRlODsNCiAgYm9yZGVyLXJhZGl1czogNnB4Ow0KICBwYWRkaW5nOiAxNHB4Ow0KICBmb250LXNpemU6IDEzcHg7DQogIG92ZXJmbG93LXg6IGF1dG87DQp9DQoNCmNvZGUgew0KICBmb250LWZhbWlseTogIlNGTW9uby1SZWd1bGFyIiwgTWVubG8sIE1vbmFjbywgQ29uc29sYXMsIG1vbm9zcGFjZTsNCiAgZm9udC1zaXplOiAxM3B4Ow0KfQ0KDQpwIGNvZGUsIGxpIGNvZGUgew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjFmM2Y1Ow0KICBwYWRkaW5nOiAycHggNnB4Ow0KICBib3JkZXItcmFkaXVzOiA0cHg7DQogIGZvbnQtc2l6ZTogMTJweDsNCn0NCg0KLyogPT09PT09PT09PSBUYWJsZXMgPT09PT09PT09PSAqLw0KdGFibGUgew0KICB3aWR0aDogMTAwJTsNCiAgbWFyZ2luOiAyMHB4IDA7DQogIGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7DQogIGZvbnQtc2l6ZTogMTRweDsNCn0NCg0KdGggew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjMmMzZTUwOw0KICBjb2xvcjogd2hpdGU7DQogIHBhZGRpbmc6IDEwcHggMTRweDsNCiAgdGV4dC1hbGlnbjogbGVmdDsNCiAgZm9udC13ZWlnaHQ6IDYwMDsNCn0NCg0KdGQgew0KICBwYWRkaW5nOiAxMHB4IDE0cHg7DQogIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjZGVlMmU2Ow0KfQ0KDQp0cjpudGgtY2hpbGQoZXZlbikgew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmOWZhOw0KfQ0KDQp0cjpob3ZlciB7DQogIGJhY2tncm91bmQtY29sb3I6ICNlZWYyZjU7DQp9DQoNCi8qIEdPIGVucmljaG1lbnQgdGFibGVzIC0gZGlzdGluY3Qgc3R5bGluZyAqLw0KLmdvLXRhYmxlIHsNCiAgbWFyZ2luOiAxNXB4IDA7DQp9DQoNCi5nby10YWJsZSB0aCB7DQogIGJhY2tncm91bmQtY29sb3I6ICMxOGJjOWM7DQp9DQoNCi8qID09PT09PT09PT0gU2VjdGlvbiBoZWFkZXJzID09PT09PT09PT0gKi8NCmgxIHsNCiAgY29sb3I6ICMyYzNlNTA7DQogIGZvbnQtc2l6ZTogMS44ZW07DQogIGZvbnQtd2VpZ2h0OiA2MDA7DQogIGJvcmRlci1ib3R0b206IDNweCBzb2xpZCAjMThiYzljOw0KICBwYWRkaW5nLWJvdHRvbTogMTBweDsNCiAgbWFyZ2luLXRvcDogNTBweDsNCiAgbWFyZ2luLWJvdHRvbTogMjBweDsNCn0NCg0KaDE6Zmlyc3Qtb2YtdHlwZSB7DQogIG1hcmdpbi10b3A6IDA7DQp9DQoNCmgyIHsNCiAgY29sb3I6ICMyYzNlNTA7DQogIGZvbnQtc2l6ZTogMS40ZW07DQogIGZvbnQtd2VpZ2h0OiA2MDA7DQogIG1hcmdpbi10b3A6IDM1cHg7DQogIG1hcmdpbi1ib3R0b206IDE1cHg7DQp9DQoNCmgzIHsNCiAgY29sb3I6ICMzNDQ5NWU7DQogIGZvbnQtc2l6ZTogMS4xNWVtOw0KICBmb250LXdlaWdodDogNjAwOw0KICBtYXJnaW4tdG9wOiAyNXB4Ow0KICBtYXJnaW4tYm90dG9tOiAxMHB4Ow0KfQ0KDQovKiA9PT09PT09PT09IENhbGxvdXQgYm94ZXMgPT09PT09PT09PSAqLw0KLm5vdGUtYm94IHsNCiAgYmFja2dyb3VuZC1jb2xvcjogI2U4ZjRmZDsNCiAgYm9yZGVyLWxlZnQ6IDRweCBzb2xpZCAjMjE5NkYzOw0KICBwYWRkaW5nOiAxNXB4IDE4cHg7DQogIG1hcmdpbjogMjBweCAwOw0KICBib3JkZXItcmFkaXVzOiAwIDZweCA2cHggMDsNCn0NCg0KLnRpcC1ib3ggew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjZThmNWU5Ow0KICBib3JkZXItbGVmdDogNHB4IHNvbGlkICM0Y2FmNTA7DQogIHBhZGRpbmc6IDE1cHggMThweDsNCiAgbWFyZ2luOiAyMHB4IDA7DQogIGJvcmRlci1yYWRpdXM6IDAgNnB4IDZweCAwOw0KfQ0KDQoud2FybmluZy1ib3ggew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOGUxOw0KICBib3JkZXItbGVmdDogNHB4IHNvbGlkICNmZjk4MDA7DQogIHBhZGRpbmc6IDE1cHggMThweDsNCiAgbWFyZ2luOiAyMHB4IDA7DQogIGJvcmRlci1yYWRpdXM6IDAgNnB4IDZweCAwOw0KfQ0KDQouaW5mby1ib3ggew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjNmNGY2Ow0KICBib3JkZXItbGVmdDogNHB4IHNvbGlkICM2YjcyODA7DQogIHBhZGRpbmc6IDE1cHggMThweDsNCiAgbWFyZ2luOiAyMHB4IDA7DQogIGJvcmRlci1yYWRpdXM6IDAgNnB4IDZweCAwOw0KfQ0KDQovKiA9PT09PT09PT09IEZpZ3VyZSBjYXB0aW9ucyA9PT09PT09PT09ICovDQouZmlndXJlIHsNCiAgbWFyZ2luOiAyNXB4IDA7DQp9DQoNCmZpZ2NhcHRpb24sIC5jYXB0aW9uIHsNCiAgZm9udC1zdHlsZTogaXRhbGljOw0KICBjb2xvcjogIzZjNzU3ZDsNCiAgZm9udC1zaXplOiAxM3B4Ow0KICBtYXJnaW4tdG9wOiAxMHB4Ow0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQoNCi8qID09PT09PT09PT0gVE9DIHN0eWxpbmcgPT09PT09PT09PSAqLw0KLnRvY2lmeSB7DQogIGJvcmRlcjogbm9uZTsNCiAgYm9yZGVyLXJhZGl1czogMDsNCn0NCg0KLnRvY2lmeS1oZWFkZXIgew0KICBmb250LXNpemU6IDEzcHg7DQp9DQoNCi50b2NpZnkgdWwsIC50b2NpZnkgbGkgew0KICBsaW5lLWhlaWdodDogMS44Ow0KfQ0KDQoudG9jaWZ5IC5hY3RpdmUgew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjMThiYzljICFpbXBvcnRhbnQ7DQogIGNvbG9yOiB3aGl0ZSAhaW1wb3J0YW50Ow0KfQ0KDQovKiA9PT09PT09PT09IEhvcml6b250YWwgcnVsZSA9PT09PT09PT09ICovDQpociB7DQogIGJvcmRlcjogbm9uZTsNCiAgYm9yZGVyLXRvcDogMXB4IHNvbGlkICNlMWU0ZTg7DQogIG1hcmdpbjogNDBweCAwOw0KfQ0KPC9zdHlsZT4NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KGtuaXRyKQ0KDQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIGVjaG8gPSBUUlVFLA0KICBldmFsID0gVFJVRSwNCiAgd2FybmluZyA9IEZBTFNFLA0KICBtZXNzYWdlID0gRkFMU0UsDQogIGVycm9yID0gRkFMU0UsDQogIGZpZy53aWR0aCA9IDEwLA0KICBmaWcuaGVpZ2h0ID0gNiwNCiAgZmlnLmFsaWduID0gImNlbnRlciIsDQogIG91dC53aWR0aCA9ICI5MCUiLA0KICBkcGkgPSAzMDANCikNCg0Kb3B0aW9ucyh3YXJuID0gLTEpDQpgYGANCg0KIyBJbnRyb2R1Y3Rpb24NCg0KVGhpcyBpcyBhIHdhbGt0aHJvdWdoL3R1dG9yaWFsIGZvciBob3cgdG8gY2x1c3RlciBhbmQgYW5hbHl6ZSB0aW1lLWNvdXJzZSBidWxrIFJOQS1zZXEgZGF0YSB1c2luZyB0aGUgW21hU2lnUHJvXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvbWFTaWdQcm8vKSBCaW9jb25kdWN0b3IgcGFja2FnZSBpbiBSLiBJIGNyZWF0ZWQgdGhpcyB3b3JrZmxvdyBiZWNhdXNlIHRoZXJlIGlzIGp1c3Qgb25lIHR1dG9yaWFsIGF2YWlsYWJsZSBvbmxpbmUgZm9yIG1hU2lnUHJvLCBhbmQgaXQncyBmYWlybHkgb3V0ZGF0ZWQgYXQgdGhpcyBwb2ludC4gSSBob3BlIG90aGVycyBjYW4gdXNlIHRoaXMgd29ya2Zsb3cgYXMgYSBndWlkZSBmb3IgaG93IHRvIGFuYWx5emUgdGhlciBkYXRhIHdpdGggbWFTaWdQcm8uDQoNCiMjIFdoYXQgaXMgbWFTaWdQcm8/DQoNCioqbWFTaWdQcm8qKiAobWljcm9hcnJheSBTaWduaWZpY2FudCBQcm9maWxlcykgaXMgYSBtZXRob2QgZm9yIGNsdXN0ZXJpbmcgdGltZS1jb3Vyc2UgUk5BLXNlcS9taWNyb2FycmF5IGRhdGEuIEl0IHVzZXMgcG9seW5vbWlhbCByZWdyZXNzaW9uIG1vZGVscyB0byBhbmFseXplIHRpbWUtc2VyaWVzIGdlbmUgZXhwcmVzc2lvbiBkYXRhLiBPbmUgcmVhc29uIHdoeSBJIGxpa2UgdGhpcyBwYWNrYWdlIG92ZXIgbm9ybWFsIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGlzIHRoYXQgbWFTaWdQcm8gaGFzIHBhcmFtZXRlcnMgdGhhdCBhbGxvdyB5b3UgdG8gZmluZXR1bmUgdGhlIHN0cmluZ2VuY3kvdGhyZXNob2xkcyBmb3IgZGlmZmVyZW50IHBhcnRzIG9mIHRoZSBnZW5lIGNsdXN0ZXJpbmcgcHJvY2VzcywgcGFydGljdWxhcmx5IGJldHdlZW4gY29uZGl0aW9ucy4gDQoNCkJyb2FkbHksIG1hU2lnUHJvIGRvZXMgdGhlIGZvbGxvdzoNCg0KLSBJZGVudGlmaWVzIGdlbmVzIHdpdGggc2lnbmlmaWNhbnQgdGVtcG9yYWwgZXhwcmVzc2lvbiBjaGFuZ2VzDQotIERldGVjdHMgY29uZGl0aW9uLXNwZWNpZmljIChlLmcuIGdlbm90eXBlKSBwYXR0ZXJucw0KLSBDbHVzdGVycyBnZW5lcyBieSBleHByZXNzaW9uIHRyYWplY3RvcnkNCi0gTW9kZWxzIG5vbi1saW5lYXIgdGVtcG9yYWwgZHluYW1pY3MNCg0KPGRpdiBjbGFzcz0ibm90ZS1ib3giPg0KKiptYVNpZ1BybyB1c2UgY2FzZToqKiBFeHBlcmltZW50cyB3aXRoIG11bHRpcGxlIHRpbWUgcG9pbnRzIChpZGVhbGx5IDMrIHRpbWVwb2ludHMpLCAyKyBjb25kaXRpb25zL2dyb3Vwcywgbj0zIHJlcGxpY2F0ZXMgcGVyIGdyb3VwLiBJZiB5b3UgYXJlIGludGVyZXN0ZWQgaW4gYnJvYWQsIHRlbXBvcmFsIHRyZW5kcyByYXRoZXIgdGhhbiBzaW5nbGUgdGltZXBvaW50IHBhaXJ3aXNlIGNvbXBhcmlzb25zIGJldHdlZW4gY29uZGl0aW9uL2dlbm90eXBlLCB0aGVuIG1hU2lnUHJvIGlzIHBhcnRpY3VsYXJseSB1c2VmdWwuDQo8L2Rpdj4NCg0KIyMgcmVxdWlyZWQgZGVwZW5kZW5jaWVzDQoNClRoaXMgd29ya2Zsb3cgdHV0b3JpYWwgdXNlcyB0aGUgZmxsb3dpbmcgcmVxdWlyZWQgcGFja2FnZXM6DQoNCmBgYHINCiMgQmlvY29uZHVjdG9yIHBhY2thZ2VzDQppZiAoIXJlcXVpcmVOYW1lc3BhY2UoIkJpb2NNYW5hZ2VyIiwgcXVpZXRseSA9IFRSVUUpKQ0KICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikNCg0KQmlvY01hbmFnZXI6Omluc3RhbGwoYygibWFTaWdQcm8iLCAiY2x1c3RlclByb2ZpbGVyIiwgIm9yZy5Icy5lZy5kYiIpKQ0KDQojIENSQU4gcGFja2FnZXMNCmluc3RhbGwucGFja2FnZXMoYygiZ2dwbG90MiIsICJkcGx5ciIsICJ0aWR5ciIsICJyZXNoYXBlMiIsDQogICAgICAgICAgICAgICAgICAgInBhdGNod29yayIsICJwaGVhdG1hcCIsICJyZWFkciIsICJzdHJpbmdyIikpDQpgYGANCg0KIyMgUHVibGljbHkgYXZhaWxhYmxlIGRhdGFzZXQgZm9yIHRoaXMgd29ya2Zsb3cNCg0KVGhpcyB0dXRvcmlhbCB1c2VzIHRoZSBhIHB1YmxpY2x5IGF2YWlsYWJsZSB0aW1lLWNvdXJzZSBidWxrIFJOQS1zZXEgZGF0YSBmcm9tIHRoZSByZWZlcmVuY2UgYmVsb3cuIHNwZWNpZmljYWxseSwgdGhlIGlucHV0IGZvciBtYVNpZ1BybyBpcyBub3JtYWxpemVkIChjYW4gY2hlY2sgdGhlIHBhY2thZ2UgdmlnbmV0dGUgZm9yIG1vcmUgZGV0YWlscykuIEJyaWVmbHksIEkgdXNlZCBlZGdlUiBUTU0tbm9ybWFsaXplZCBjb3VudHMgcGVyIG1pbGxpb24gZm9yIHRoaXMgYW5hbHlzaXMuDQoNClRoaXMgZGF0YXNldCBoYXMgdHdvIGdlbm90eXBlcyBjb21wYXJpbmcgRDIxIChkaXNvbWljKSB2cyBUMjEgKHRyaXNvbWljKSBnZW5vdHlwZXMgYWNyb3NzIHRocmVlIGRldmVsb3BtZW50YWwgdGltZSBwb2ludHMgKERheSA2LCAxMCwgYW5kIDE3KS4NCg0KKipEYXRhIHNvdXJjZToqKiBNYXJ0aW5leiBKTCBldCBhbC4gKlRyYW5zY3JpcHRpb25hbCBjb25zZXF1ZW5jZXMgb2YgdHJpc29teSAyMSBvbiBuZXVyYWwgaW5kdWN0aW9uLiogRnJvbnQgQ2VsbCBOZXVyb3NjaS4gMjAyNDsxODoxMzQxMTQxLiBbZG9pOjEwLjMzODkvZm5jZWwuMjAyNC4xMzQxMTQxXShodHRwczovL2RvaS5vcmcvMTAuMzM4OS9mbmNlbC4yMDI0LjEzNDExNDEpDQoNCiMjIFdvcmtmbG93IE92ZXJ2aWV3DQoNCnwgU3RlcCB8IERlc2NyaXB0aW9uIHwNCnw6LS0tLS18Oi0tLS0tLS0tLS0tLXwNCnwgMSB8IExvYWQgbGlicmFyaWVzIGFuZCBkYXRhIHwNCnwgMiB8IEZpbHRlciBsb3dseSBleHByZXNzZWQgZ2VuZXMgfA0KfCAzIHwgUHJlcGFyZSBleHBlcmltZW50YWwgZGVzaWduIG1hdHJpeCB8DQp8IDQgfCBDcmVhdGUgcG9seW5vbWlhbCByZWdyZXNzaW9uIGRlc2lnbiB8DQp8IDUgfCBJZGVudGlmeSB0ZW1wb3JhbGx5IHNpZ25pZmljYW50IGdlbmVzIHwNCnwgNiB8IERldGVjdCBnZW5vdHlwZS1zcGVjaWZpYyBwYXR0ZXJucyB8DQp8IDcgfCBFeHRyYWN0IHNpZ25pZmljYW50IGdlbmVzIHwNCnwgOCB8IENsdXN0ZXIgZ2VuZXMgYnkgdHJhamVjdG9yeSB8DQp8IDkgfCBQcmVwYXJlIHZpc3VhbGl6YXRpb24gZGF0YSB8DQp8IDEwIHwgUGxvdCBjbHVzdGVyIHByb2ZpbGVzIHwNCnwgMTEgfCBHZW5lcmF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MgfA0KfCAxMiB8IEdPIGVucmljaG1lbnQgYW5hbHlzaXMgfA0KfCAxMyB8IEV4cHJlc3Npb24gaGVhdG1hcHMgfA0KfCAxNCB8IFNhdmUgcmVzdWx0cyB8DQoNCiMgU3RlcCAxOiBMb2FkIHJlcXVpcmVkIGxpYnJhcmllcyBhbmQgZGF0YQ0KDQpMb2FkIHRoZSByZXF1aXJlZCBwYWNrYWdlcyBhbmQgaW1wb3J0IGV4cHJlc3Npb24gZGF0YS4gbWFTaWdQcm8gcmVxdWlyZXM6DQoNCi0gKipFeHByZXNzaW9uIG1hdHJpeDoqKiBHZW5lcyAocm93cykgw5cgU2FtcGxlcyAoY29sdW1ucyksIGNvdW50cyBwZXIgbWlsbGlvbiAoQ1BNKSBvciBub3JtYWxpemVkIGNvdW50cy4gWW91IEFSRSBhYmxlIHRvIHVzZSByYXcgY291bnRzIChtYVNpZ1BybyBoYXMgYW4gaW50ZXJuYWwgbm9ybWFsaXphdGlvbiBvcHRpb24pLCBidXQgSSBwcmVmZXIgdG8gdXNlIFRNTS1ub3JtYWxpemVkIENQTSBiZWNhdXNlIGl0J3Mgd2VsbC12YWxpZGF0ZWQuDQotICoqTWV0YWRhdGE6KiogU2FtcGxlIGluZm9ybWF0aW9uIHdpdGggZ2Vub3R5cGUsIHRpbWVwb2ludCwgYW5kIHJlcGxpY2F0ZSBjb2x1bW5zIHRoYXQgd2Ugd2lsbCB1c2UgdG8gY29uc3RydWN0IHRoZSBleHBlcmltZW50YWwgZGVzaWduIA0KDQpgYGB7ciBzdGVwMS1sb2FkLWRhdGEsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXJyb3I9RkFMU0V9DQojIExvYWQgcmVxdWlyZWQgcGFja2FnZXMNCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7DQogIGxpYnJhcnkoZ2dwbG90MikNCiAgbGlicmFyeShkcGx5cikNCiAgbGlicmFyeSh0aWR5cikNCiAgbGlicmFyeShyZXNoYXBlMikNCiAgbGlicmFyeShwYXRjaHdvcmspDQogIGxpYnJhcnkocGhlYXRtYXApDQogIGxpYnJhcnkocmVhZHIpDQogIGxpYnJhcnkoc3RyaW5ncikNCiAgbGlicmFyeShtYVNpZ1BybykNCiAgbGlicmFyeShjbHVzdGVyUHJvZmlsZXIpDQogIGxpYnJhcnkob3JnLkhzLmVnLmRiKQ0KfSkNCg0KIyBMb2FkIGV4cHJlc3Npb24gZGF0YSBhbmQgbWV0YWRhdGEgZnJvbSB0aGUgcHVibGljbHkgYXZhaWxhYmxlIGRhdGFzZXQNCmNvdW50c19jcG0gPC0gcmVhZFJEUygiZGF0YS9jb3VudHMvY3BtL1dDMjRfY3BtLnJkcyIpDQptZXRhZGF0YSA8LSByZWFkUkRTKCJkYXRhL21ldGFkYXRhL1dDMjRfbWV0YWRhdGFfY2xlYW4ucmRzIikNCg0KIyBDcmVhdGUgb3V0cHV0IGRpcmVjdG9yaWVzDQpmaWdfZGlyIDwtICJyZXN1bHRzL2ZpZ3VyZXMvbWFzaWdwcm8iDQpyZXNfZGlyIDwtICJyZXN1bHRzL21hc2lncHJvIg0KZGlyLmNyZWF0ZShmaWdfZGlyLCByZWN1cnNpdmUgPSBUUlVFLCBzaG93V2FybmluZ3MgPSBGQUxTRSkNCmRpci5jcmVhdGUocmVzX2RpciwgcmVjdXJzaXZlID0gVFJVRSwgc2hvd1dhcm5pbmdzID0gRkFMU0UpDQpgYGANCg0KIyMjIExvb2sgYXQgbWV0YWRhdGENCg0KSSB3aWxsIHNob3cgdGhlIGV4cGVyaW1lbnRhbCBkZXNpZ24gYmVmb3JlIHByb2NlZWRpbmcgc28gdGhhdCBpdCdzIGNsZWFyIGhvdyB0byBhZGFwdCB0aGlzIHdvcmtmbG93IHRvIHlvdXIgc3BlY2lmaWMgZXhwZXJpbWVudGFsIGRlc2lnbi4NCg0KYGBge3Igc3RlcDEtaW5zcGVjdC1tZXRhZGF0YSwgZWNobz1UUlVFfQ0KIyBWaWV3IGNvbXBsZXRlIG1ldGFkYXRhDQprbml0cjo6a2FibGUobWV0YWRhdGEsIGNhcHRpb24gPSAiU2FtcGxlIE1ldGFkYXRhIikNCg0KIyBTYW1wbGUgY291bnRzIHBlciBncm91cA0KZ2Vub190aW1lX3RhYmxlIDwtIHRhYmxlKG1ldGFkYXRhJGdlbm90eXBlLCBtZXRhZGF0YSR0aW1lcG9pbnQpDQprbml0cjo6a2FibGUoZ2Vub190aW1lX3RhYmxlLA0KICAgICAgICAgICAgIGNhcHRpb24gPSAiU2FtcGxlcyBwZXIgR2Vub3R5cGUgYW5kIFRpbWVwb2ludCIsDQogICAgICAgICAgICAgY29sLm5hbWVzID0gYygiRGF5IDYiLCAiRGF5IDEwIiwgIkRheSAxNyIpKQ0KYGBgDQoNCiMgU3RlcCAyOiBGaWx0ZXIgRXhwcmVzc2lvbiBNYXRyaXgNCg0KVGhpcyBzdGVwIGlzIG9wdGlvbmFsLCBidXQgSSBoaWdobHkgcmVjb21tZW5kZWQgYWRkaW5nIHNvbWUgc3RlcCBpbiB3aGljaCB5b3UgZmlsdGVyIG9yIHJlbW92ZSBnZW5lcyB3aG9zZSBleHByZXNzaW9uIGlzIGxvdyBhdCBhbnkgZ2l2ZW4gc3RhZ2UgZm9yIGEgZ2l2ZW4gY29uZGl0aW9uL3RyZWF0bWVudC4gSGVyZSwgSSBScmVtb3ZlIGxvd2x5IGV4cHJlc3NlZCBnZW5lcyB0byByZWR1Y2Ugbm9pc2UgYW5kIGltcHJvdmUgc3RhdGlzdGljYWwgcG93ZXIgZm9yIGxhdGVyIHN0ZXBzIGluIHRoZSBtYVNpZ1BybyB3b3JrZmxvdy4gR2VuZXMgbXVzdCBoYXZlIG1lYW4gQ1BNID4gNSBpbiBhdCBsZWFzdCBvbmUgZ2Vub3R5cGUtdGltZXBvaW50IGNvbWJpbmF0aW9uIChpZSwgYXQgYSBzcGVjaWZpYyB0aW1lcG9pbnQgZm9yIGEgc2V0IG9mIHJlcGxpY2F0ZXMgZm9yIGEgZ2Vub3R5cGUvc3RhZ2UpDQoNCmBgYHtyIHN0ZXAyLWZpbHRlciwgZWNobz1UUlVFfQ0KbWluX2NwbSA8LSA1DQoNCiMgc2VsZWN0IHJlbGV2YW50IHNhbXBsZXMNCm1ldGFkYXRhX2ZpbHRlciA8LSBtZXRhZGF0YSAlPiUNCiAgZHBseXI6OmZpbHRlcihnZW5vdHlwZSAlaW4lIGMoIkQyMSIsICJUMjEiKSkgJT4lDQogIGRwbHlyOjpzZWxlY3Qoc2FtcGxlX2lkLCBnZW5vdHlwZSwgdGltZXBvaW50KQ0KDQojIENhbGN1bGF0ZSBtZWFuIENQTSBwZXIgZ2VuZSBwZXIgZ2Vub3R5cGUtdGltZXBvaW50IGdyb3VwDQpnZW5lX21lYW5zIDwtIGNvdW50c19jcG0gJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oImdlbmVfaWQiKSAlPiUNCiAgdGlkeXI6OnBpdm90X2xvbmdlcigtZ2VuZV9pZCwgbmFtZXNfdG8gPSAic2FtcGxlX2lkIiwgdmFsdWVzX3RvID0gImNwbSIpICU+JQ0KICBkcGx5cjo6aW5uZXJfam9pbihtZXRhZGF0YV9maWx0ZXIsIGJ5ID0gInNhbXBsZV9pZCIpICU+JQ0KICBkcGx5cjo6Z3JvdXBfYnkoZ2VuZV9pZCwgZ2Vub3R5cGUsIHRpbWVwb2ludCkgJT4lDQogIGRwbHlyOjpzdW1tYXJpc2UobWVhbl9jcG0gPSBtZWFuKGNwbSksIC5ncm91cHMgPSAiZHJvcCIpICU+JQ0KICBkcGx5cjo6Z3JvdXBfYnkoZ2VuZV9pZCkgJT4lDQogIGRwbHlyOjpzdW1tYXJpc2UobWF4X21lYW5fY3BtID0gbWF4KG1lYW5fY3BtKSwgLmdyb3VwcyA9ICJkcm9wIikNCg0KIyBLZWVwIGdlbmVzIHBhc3NpbmcgdGhlIGV4cHJlc3Npb24gdGhyZXNob2xkDQprZWVwX2dlbmVzIDwtIGdlbmVfbWVhbnMkZ2VuZV9pZFtnZW5lX21lYW5zJG1heF9tZWFuX2NwbSA+IG1pbl9jcG1dDQpleHByX21hdCA8LSBjb3VudHNfY3BtW2tlZXBfZ2VuZXMsIF0NCg0KY2F0KCJHZW5lcyBiZWZvcmUgZmlsdGVyaW5nOiIsIG5yb3coY291bnRzX2NwbSksICJcbiIpDQpjYXQoIkdlbmVzIGFmdGVyIGZpbHRlcmluZzoiLCBucm93KGV4cHJfbWF0KSwgIlxuIikNCmBgYA0KDQojIFN0ZXAgMzogUHJlcGFyZSBleHBlcmltZW50YWwgZGVzaWduIG1hdHJpeA0KDQptYVNpZ1BybyByZXF1aXJlcyBhbiBleHBlcmltZW50YWwgZGVzaWduIG1hdHJpeCBmb3JtYXQgd2l0aCBjb2x1bW5zIGZvcjoNCg0KLSAqKlRpbWU6KiogTnVtZXJpYyB0aW1lIHBvaW50IHZhbHVlcyBmb3IgZWFjaCB0aW1lcG9pbnQgaW4gdGltZS1zZXJpZXMNCi0gKipSZXBsaWNhdGU6KiogSWRlbnRpZmllciBmb3IgZWFjaCByZXBsaWNhdGUNCi0gKipHcm91cCBjb2x1bW5zOioqIEJpbmFyeSAoMCBvciAxKSB2YWx1ZXMgZm9yIGVhY2ggZXhwZXJpbWVudGFsIGdyb3VwDQoNCkkgc2hvdyB0aGUgZXhwZXJpbWVudGFsIG1hdHJpeCBmb3IgbWFTaWdQcm8gYmVsb3cNCg0KYGBge3Igc3RlcDMtcHJlcGFyZS1kZXNpZ24sIGVjaG89VFJVRX0NCiMgcHJlcGFyZSBtZXRhZGF0YSB3aXRoIG51bWVyaWMgdGltZXBvaW50cw0KbWQgPC0gbWV0YWRhdGEgJT4lDQogIGRwbHlyOjpmaWx0ZXIoZ2Vub3R5cGUgJWluJSBjKCJEMjEiLCAiVDIxIikpICU+JQ0KICBkcGx5cjo6c2VsZWN0KHNhbXBsZV9pZCwgZ2Vub3R5cGUsIHRpbWVwb2ludCwgcmVwKSAlPiUNCiAgZHBseXI6Om11dGF0ZSh0aW1lcG9pbnQgPSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih0aW1lcG9pbnQpKSkNCg0KIyBNYXRjaCBzYW1wbGUgb3JkZXIgdG8gZXhwcmVzc2lvbiBtYXRyaXgNCm1kIDwtIG1kW21hdGNoKGNvbG5hbWVzKGV4cHJfbWF0KSwgbWQkc2FtcGxlX2lkKSwgXQ0KDQojIENyZWF0ZSBkZXNpZ24gbWF0cml4DQplZGVzaWduIDwtIGRhdGEuZnJhbWUoDQogIFRpbWUgPSBtZCR0aW1lcG9pbnQsDQogIFJlcGxpY2F0ZSA9IG1kJHJlcCwNCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQopDQpyb3duYW1lcyhlZGVzaWduKSA8LSBtZCRzYW1wbGVfaWQNCg0KIyBBZGQgYmluYXJ5IGdlbm90eXBlIGNvbHVtbnMgKDAgb3IgMSBlbmNvZGluZykNCmdlbm90eXBlX21hdHJpeCA8LSBtb2RlbC5tYXRyaXgofiAwICsgZ2Vub3R5cGUsIGRhdGEgPSBtZCkNCmNvbG5hbWVzKGdlbm90eXBlX21hdHJpeCkgPC0gZ3N1YigiZ2Vub3R5cGUiLCAiIiwgY29sbmFtZXMoZ2Vub3R5cGVfbWF0cml4KSkNCmVkZXNpZ24gPC0gY2JpbmQoZWRlc2lnbiwgYXMuZGF0YS5mcmFtZShnZW5vdHlwZV9tYXRyaXgpKQ0Kcm93bmFtZXMoZWRlc2lnbikgPC0gbWQkc2FtcGxlX2lkDQoNCiMgRGlzcGxheSBkZXNpZ24gbWF0cml4DQprbml0cjo6a2FibGUoZWRlc2lnbiwgY2FwdGlvbiA9ICJFeHBlcmltZW50YWwgRGVzaWduIE1hdHJpeCBmb3IgbWFTaWdQcm8iKQ0KYGBgDQoNCiMgU3RlcCA0OiBDcmVhdGUgUG9seW5vbWlhbCBSZWdyZXNzaW9uIERlc2lnbg0KDQpUaGUgbWFrZS5kZXNpZ24ubWF0cml4KCkgZnVuY3Rpb24gaW4gbWFTaWdQcm8gY3JlYXRlcyB0aGUgcmVncmVzc2lvbiBkZXNpZ24gbWF0cml4LiBUaGUgYGRlZ3JlZWAgcGFyYW1ldGVyIGNvbnRyb2xzIHRoZSBwb2x5bm9taWFsIG9yZGVyLiBCYXNlZCBvbiB0aGUgdmlnZW50dGUsIHRvIGNhbGN1bGF0ZSB0aGUgcGFyYW1ldGVyIGZvciB5b3VyIHNwZWNpZmljIGV4cGVyaW1lbnQsIHRha2UgdGhlIHRvdGFsIG51bWJlciBvZiB0aW1lcG9pbnRzIGFuZCBzdWJ0cmFjdCBieSAxLiB1bmxlc3MgeW91IGhhdmUgNSsgdGltZXBvaW50cywgSSB3b3VsZCBub3QgZXhjZWVkIG1vcmUgdGhhbiBuPTMgZm9yIGRlZ3JlZSwgdW5sZXNzIHlvdSBleHBlY3RzIGdlbmVzIHRvIGZvbGxvdyAnVycgc2hhcGUgZXhwcmVzc2lvbiBwYXR0ZXJucy4NCg0KfCBEZWdyZWUgfCBDdXJ2ZSBUeXBlIHwgVXNlIENhc2UgfA0KfDotLS0tLS0tfDotLS0tLS0tLS0tLXw6LS0tLS0tLS0tfA0KfCAxIHwgTGluZWFyIHwgTW9ub3RvbmljIHRyZW5kcyB8DQp8IDIgfCBRdWFkcmF0aWMgfCBQZWFrL3Ryb3VnaCBwYXR0ZXJucyB8DQp8IDMgfCBDdWJpYyB8IENvbXBsZXggZHluYW1pY3MgfA0KDQo8ZGl2IGNsYXNzPSJpbmZvLWJveCI+DQoqKkRlZ3JlZSA9IDIqKiAocXVhZHJhdGljKSBpcyB0eXBpY2FsbHkgZmluZSBmb3IgbW9zdCB0aW1lLWNvdXJzZSBleHBlcmltZW50YWwgZGVzaWducywgc2luY2UgaXQgbW9kZWxzICdVJyBzaGFwZSBleHByZXNzaW9uIGR5bmFtaWNzLCBzbyBpdCBjYXB0dXJlcyBib3RoIGxpbmVhciB0cmVuZHMgYW5kIG5vbi1saW5lYXIgcGF0dGVybnMuDQo8L2Rpdj4NCg0KYGBge3Igc3RlcDQtY3JlYXRlLWRlc2lnbiwgZWNobz1UUlVFfQ0KcG9seV9kZWdyZWUgPC0gMg0KDQpkZXNpZ24gPC0gbWFTaWdQcm86Om1ha2UuZGVzaWduLm1hdHJpeCgNCiAgZWRlc2lnbiA9IGVkZXNpZ24sDQogIGRlZ3JlZSA9IHBvbHlfZGVncmVlLA0KICB0aW1lLmNvbCA9IDEsDQogIHJlcGwuY29sID0gMiwNCiAgZ3JvdXAuY29scyA9IDM6NA0KKQ0KDQpkZXNpZ24kZGlzIDwtIGFzLmRhdGEuZnJhbWUoZGVzaWduJGRpcykNCmV4cHJfbWF0IDwtIGV4cHJfbWF0Wywgcm93bmFtZXMoZGVzaWduJGRpcyldDQoNCiMgUHJldmlldyB0aGUgcmVncmVzc2lvbiBkZXNpZ24gbWF0cml4DQprbml0cjo6a2FibGUoaGVhZChkZXNpZ24kZGlzKSwgY2FwdGlvbiA9ICJQb2x5bm9taWFsIFJlZ3Jlc3Npb24gRGVzaWduIE1hdHJpeCAoZmlyc3QgNiByb3dzKSIpDQpgYGANCg0KIyBTdGVwIDU6IElkZW50aWZ5IHRlbXBvcmFsbHkgc2lnbmlmaWNhbnQgR2VuZXMNCg0KV2l0aCB0aGUgZGVzaWduIG1hdHJpeCBtYWRlLCB0aGUgbmV4dCBzdGVwIGluIHRoZSBtYVNpZ1BybyB3b3JrZmxvdyBpcyB0aGUgYHAudmVjdG9yKClgIGZ1bmN0aW9uLiBUaGlzIGZ1bmN0aW9uIGZpdHMgcG9seW5vbWlhbCByZWdyZXNzaW9uIG1vZGVscyB0byBlYWNoIGdlbmUgYW5kIHRlc3RzIGZvciBzaWduaWZpY2FudCB0ZW1wb3JhbCBleHByZXNzaW9uIGNoYW5nZXMgKHJlZ2FyZGxlc3Mgb2YgZ2Vub3R5cGUpLg0KDQoqKlBhcmFtZXRlcnM6KioNCg0KLSBgUWA6IEZEUiB0aHJlc2hvbGQgZm9yIHNpZ25pZmljYW5jZSAoZGVmYXVsdDogMC4wNSkuIFlvdSBjYW4gdHJ5IDAuMDEsIHRob3VnaCAwLjA1IGlzIHJlY29tbWVuZGVkIGJ5IHRoZSBhdXRob3JzIGluIHRoZSBvcmlnaW5hbCBtYVNpZ1BybyBwYXBlci4NCi0gYE1ULmFkanVzdGA6IE11bHRpcGxlIHRlc3RpbmcgY29ycmVjdGlvbiBtZXRob2QgKCJCSCIgPSBCZW5qYW1pbmktSG9jaGJlcmcpDQotIGBtaW4ub2JzYDogTWluaW11bSBvYnNlcnZhdGlvbnMgcmVxdWlyZWQgcGVyIGdlbmUuIEkgd291bGQgc2V0IHRoaXMgcGFyYW1ldGVyIGVxdWFsIHRvIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIHlvdSBoYXZlIHBlciB0cmVhdG1lbnQvdGltZXBvaW50IChpZSwgbj0zIHJlcGxpY2F0ZXMgaW4gbW9zdCBjYXNlcykuDQoNCmBgYHtyIHN0ZXA1LXB2ZWN0b3IsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQpxX3RocmVzaG9sZCA8LSAwLjA1DQoNCmZpdCA8LSBtYVNpZ1Bybzo6cC52ZWN0b3IoDQogIGRhdGEgPSBleHByX21hdCwNCiAgZGVzaWduID0gZGVzaWduJGRpcywNCiAgUSA9IHFfdGhyZXNob2xkLA0KICBNVC5hZGp1c3QgPSAiQkgiLA0KICBtaW4ub2JzID0gbnJvdyhtZCkNCikNCmBgYA0KDQpgYGB7ciBzdGVwNS1yZXN1bHQsIGVjaG89RkFMU0V9DQpjYXQoIkdlbmVzIHdpdGggc2lnbmlmaWNhbnQgdGVtcG9yYWwgZXhwcmVzc2lvbjoiLCBsZW5ndGgoZml0JFNFTEVDKSwgIlxuIikNCmBgYA0KDQojIFN0ZXAgNjogRGV0ZWN0IEdlbm90eXBlLVNwZWNpZmljIFBhdHRlcm5zDQoNClRoZSBuZXh0IHN0ZXAgdXNlcyB0aGUgYFQuZml0KClgIGZ1bmN0aW9uLCB3aGljaCBhcHBsaWVzIGEgc3RlcHdpc2UgcmVncmVzc2lvbiB0byBpZGVudGlmeSBnZW5lcyB3aXRoIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IHRlbXBvcmFsIHBhdHRlcm5zIGJldHdlZW4gZ2Vub3R5cGVzIChnZW5vdHlwZSDDlyB0aW1lIGludGVyYWN0aW9uKS4gSW4gb3RoZXIgd29yZHMsIHRoaXMgZnVuY3Rpb24gZmluZHMgZGlmZmVyZW5jZXMgYmV0d2VlbiB5b3VyIHR3byBjb25kaXRpb25zLg0KDQpOb3RlIHRoYXQgdGhlcmUgYXJlIHNldmVyYWwgb3B0aW9ucyB0byB1c2UgZm9yIHRoZSByZWdyZXNzaW9uIG1ldGhvZC4gaGVyZSBpcyB1c2UgdGhlIG1ldGhvZCB1c2VkIGJ5IHRoZSBhdXRob3JzIGluIHRoZWlyIHR1dG9yaWFsIChiYWNrd2FyZCksIHRob3VnaCBJIHRlbmQgdG8gdXNlIHR3by53YXlzLmJhY2t3YXJkLiBZb3UgY2FuIHRyeSB0aGVzZSBkaWZmZXJlbnQgbWV0aG9kcyB0byBzZWUgbWV0aG9kIG1vc3QgJ2FjY3VyYXRlbHknIGNsdXN0ZXJzIHlvdXIgZ2VuZXMgdGVtcG9yYWxseSArIGJhc2VkIG9uIGNvbmRpdGlvbi90cmVhdG1lbnQuDQoNCioqU3RlcHdpc2UgcmVncmVzc2lvbiBtZXRob2RzIGluIG1hU2lnUHJvOioqDQoNCi0gIG1hU2lnUHJvIG9mZmVycyBzZXZlcmFsIG9wdGlvbnMgZm9yIHdoYXQgcmVncmVzc2lvbiBpcyANCi0gYGJhY2t3YXJkYDogU3RhcnQgd2l0aCBmdWxsIG1vZGVsLCByZW1vdmUgbm9uLXNpZ25pZmljYW50IHRlcm1zDQotIGBmb3J3YXJkYDogU3RhcnQgZW1wdHksIGFkZCBzaWduaWZpY2FudCB0ZXJtcw0KLSBgdHdvLndheXMuYmFja3dhcmRgIC8gYHR3by53YXlzLmZvcndhcmRgOiBCaWRpcmVjdGlvbmFsIHNlbGVjdGlvbiAoSSB0ZW5kIHRvIHVzZSB0aGlzKQ0KDQpgYGB7ciBzdGVwNi10Zml0LCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KYWxwaGFfdGhyZXNob2xkIDwtIDAuMDUNCg0KdGZpdCA8LSBtYVNpZ1Bybzo6VC5maXQoDQogIGZpdCwNCiAgc3RlcC5tZXRob2QgPSAiYmFja3dhcmQiLA0KICBhbGZhID0gYWxwaGFfdGhyZXNob2xkDQopDQoNCnRmaXQkZ3JvdXBzLnZlY3RvciA8LSBkZXNpZ24kZ3JvdXBzLnZlY3Rvcg0KdGZpdCRlZGVzaWduIDwtIGRlc2lnbiRlZGVzaWduDQpgYGANCg0KIyBTdGVwIDc6IEV4dHJhY3QgU2lnbmlmaWNhbnQgR2VuZXMNCg0KQWZ0ZXIgaWRlbnRpZnlpbmcgZ2VuZXMgdGhhdCBhcmUgdGltZS1kZXBlbmRlbnQgYW5kIGlkZW50aWZ5aW5nIGdlbmVzIHRoYXQgY2hhbmdlIHRlbXBvcmFsbHkgdGhhdCBkaWZmZXIgYmV0d2VlbiB0cmVhdG1lbnQvZ2Vub3R5cGUsIHRoZSBuZXh0IHN0ZXAgaXMgdG8gaWRlbnRpZnkgZ2VuZXMgd2l0aCBzaWduaWZpY2FudCBnZW5vdHlwZS1zcGVjaWZpYyBwYXR0ZXJucyB1c2luZyBgZ2V0LnNpZ2dlbmVzKClgLiBUaGlzIFLCsiB0aHJlc2hvbGQgZmlsdGVycyBzcGVjaWZpY2FsbHkgZm9yIGdlbmVzIHdoZXJlIHRoZSByZWdyZXNzaW9uIG1vZGVsIGV4cGxhaW5zIGEgc3Vic3RhbnRpYWwgcHJvcG9ydGlvbiBvZiB2YXJpYW5jZS4gDQoNCkluIHRoZSBvcmlnaW5hbCBtYVNpZ1BybyBwYXBlciwgdGhlIGF1dGhvcnMgbm90ZSB0aGF0IFJzcSBvZiAwLjcgcGVyZm9ybWVkIHdlbGwuIEhvd2V2ZXIsIHRoZXJlIGhhdmUgYmVlbiBzb21lIGNhc2VzIHdoZW4gUnNxIG9mIDAuNyBoYXMgcmVzdWx0ZWQgaW4gNTAwMCsgZ2VuZXMgdGhhdCBhcmUgY2x1c3RlcmVkIGF0IHRoZSBvZiB0aGlzIHdvcmtmbG93LiBUaGVyZWZvcmUsIEkgb2Z0ZW4gdXNlIGhpZ2hlciB2YWx1ZXMgKDAuNyspLCBhcyBoaWdoZXIgdmFsdWVzIHlpZWxkIG1vcmUgcmVsaWFibGUgZ2VuZXMgYnV0IGZld2VyIHJlc3VsdHMuIExvd2VyIHZhbHVlcyAoMC41LTAuNikgaW5jbHVkZSBtb3JlIGdlbmVzIGJ1dCB3aXRoIGxlc3MgY29uZmlkZW50IG1vZGVsIGZpdHMuIEFkanVzdCBiYXNlZCBvbiB5b3VyIHNhbXBsZSBzaXplIGFuZCBkb3duc3RyZWFtIGdvYWxzLCBhbmQgeW91IGNhbiBmdXJ0aGVyIGNoZWNrIHRoZSBxdWFsaXR5IG9mIHRoZSBmaXQgYnkgaW5kaXZpZHVhbGx5IGxvb2tpbmcgYXQgZ2VuZXMgd2l0aGluIGVhY2ggY2x1c3RlciB0byBhc3Nlc3MgdGhlIGZpZGVsaXR5IG9mIHRoZSBmaXR0aW5nLg0KDQpgYGB7ciBzdGVwNy1leHRyYWN0LXNpZywgZWNobz1UUlVFfQ0KcnNxX3RocmVzaG9sZCA8LSAwLjcNCg0Kc2lncyA8LSBtYVNpZ1Bybzo6Z2V0LnNpZ2dlbmVzKA0KICB0c3RlcCA9IHRmaXQsDQogIHJzcSA9IHJzcV90aHJlc2hvbGQsDQogIHZhcnMgPSAiZ3JvdXBzIg0KKQ0KDQpzaWdfZ2VuZXMgPC0gcm93bmFtZXMoc2lncyRzaWcuZ2VuZXMkVDIxdnNEMjEkc2lnLnByb2ZpbGVzKQ0KY2F0KCJHZW5lcyB3aXRoIGdlbm90eXBlLXNwZWNpZmljIHRlbXBvcmFsIHBhdHRlcm5zOiIsIGxlbmd0aChzaWdfZ2VuZXMpLCAiXG4iKQ0KYGBgDQoNCiMgU3RlcCA4OiBDbHVzdGVyIEdlbmVzIGJ5IEV4cHJlc3Npb24gVHJhamVjdG9yeQ0KDQpOZXh0LCB3ZSBjbHVzdGVyIGdlbmVzIHdpdGggc2ltaWxhciB0ZW1wb3JhbCBleHByZXNzaW9uIHBhdHRlcm5zIHVzaW5nIGBzZWUuZ2VuZXMoKWAuDQoNClRoZXJlIGFyZSBtYW55IG9wdGlvbnMgaGVyZSB0byB1c2UgZm9yIHRoZSBkaXN0YW5jZSBtZXRyaWMgYW5kIGNsdXN0ZXJpbmcgbWV0aG9kLiBNb3Jlb3ZlciwgeW91IGNhbiBzZXQgayBhcyB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzLiBJIHdvbid0IGdldCBpbnRvIHRoZSBudWFuY2VzIGFuZCBkZXRhaWxzIG9mIGhvdyB0byBwaWNrIHRoZSBzcGVjaWZpYyBkaXN0YW5jZSBtZXRyaWMsIGV0Yy4gVGhlIG1haW4gcG9pbnQgaXM6IGFyZSB5b3UgYWJsZSB0byBpbmZlciBiaW9sb2dpY2FsIG1lYW5pbmcgZnJvbSB0aGUgY2x1c3RlcnM/IEEgY2x1c3RlciBkZWZpbmVkIGJ5IG1hU2lnUHJvIGlzIG9ubHkgbWVhbmluZ2Z1bCBpZiBpdCBoYXMgc29tZSBiaW9sb2dpY2FsIG1lYW5pbmcgdW5kZXJseWluZyBpdC4gT25lIHNpbXBsZSB3YXkgdG8gZGVyaXZlIHdoZXRoZXIgYSBjbHVzdGVyIGhhcyBtZWFuaW5nZnVsIGJpb2xvZ2ljYWwgaW5mb3JtYXRpb24gaXMgdGhlIG51bWJlciBvZiBnZW5lcyBjb250YWluZWQgd2l0aGluIGl0LiBJZiBhIGNsdXN0ZXIgaGFzIDUwIGdlbmVzLCBpdCdzIGxpa2VseSB0aGF0IHlvdXIgY2x1c3RlcmluZyByZXNvbHV0aW9uIGlzIHRvbyBmaW5lLg0KDQpNeSBzdWdnZXN0aW9uIHdvdWxkIGJlIHRvIHRlc3QgdmFyaW91cyBwYXJhbWV0ZXJzLiBJZGVhbGx5LCByZWdhcmRsZXNzIG9mIHRoZSBwYXJhbWV0ZXJzIHlvdSBjaG9vc2UsIHRoZSB1bmRlcmx5aW5nIGJpb2xvZ2ljYWwgaW5zaWdodCB5b3UgZ2xlYW4gaXMgbGFyZ2VseSB0aGUgc2FtZS4gDQoNCioqQ2x1c3RlcmluZyBwYXJhbWV0ZXJzOioqDQoNCnwgUGFyYW1ldGVyIHwgRGVzY3JpcHRpb24gfA0KfDotLS0tLS0tLS0tfDotLS0tLS0tLS0tLS18DQp8IGBrYCB8IE51bWJlciBvZiBjbHVzdGVycyB8DQp8IGBkaXNgIHwgRGlzdGFuY2UgbWV0cmljICgiY29ycmVsYXRpb24iLCAiZXVjbGlkZWFuIikgfA0KfCBgY2x1c3Rlci5tZXRob2RgIHwgQ2x1c3RlcmluZyBhbGdvcml0aG0gKCJoY2x1c3QiLCAia21lYW5zIikgfA0KDQpgYGB7ciBzdGVwOC1jbHVzdGVyLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD0xMCwgZmlnLmNhcD0ibWFTaWdQcm8gY2x1c3RlciB2aXN1YWxpemF0aW9uIHNob3dpbmcgZ2VuZSBleHByZXNzaW9uIHRyYWplY3RvcmllcyBncm91cGVkIGJ5IHNpbWlsYXJpdHkuIn0NCmtfY2x1c3RlcnMgPC0gMTANCg0KZWRlc2lnbl9jbGVhbiA8LSBkZXNpZ24kZWRlc2lnblssIHNhcHBseShkZXNpZ24kZWRlc2lnbiwgaXMubnVtZXJpYyldDQoNCmNsdXN0ZXJzIDwtIG1hU2lnUHJvOjpzZWUuZ2VuZXMoDQogIGRhdGEgPSBzaWdzJHNpZy5nZW5lcyRUMjF2c0QyMSwNCiAgZWRlc2lnbiA9IGVkZXNpZ25fY2xlYW4sDQogIGsgPSBrX2NsdXN0ZXJzLA0KICBkaXMgPSAiY29ycmVsYXRpb24iLA0KICBjbHVzdGVyLm1ldGhvZCA9ICJoY2x1c3QiLA0KICBjb2xvci5tb2RlID0gImdyYXkiLA0KICBzaG93LmZpdCA9IEZBTFNFDQopDQoNCiMgQ3JlYXRlIGNsdXN0ZXIgYXNzaWdubWVudCB0YWJsZQ0KY2xfYXNzaWduIDwtIGRhdGEuZnJhbWUoDQogIGdlbmVfaWQgPSBuYW1lcyhjbHVzdGVycyRjdXQpLA0KICBjbHVzdGVyID0gYXMuaW50ZWdlcihjbHVzdGVycyRjdXQpLA0KICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCikNCg0KIyBTdW1tYXJ5IG9mIGNsdXN0ZXIgc2l6ZXMNCmNsdXN0ZXJfc3VtbWFyeSA8LSBjbF9hc3NpZ24gJT4lDQogIGRwbHlyOjpjb3VudChjbHVzdGVyLCBuYW1lID0gIm5fZ2VuZXMiKSAlPiUNCiAgZHBseXI6OmFycmFuZ2UoY2x1c3RlcikNCg0Ka25pdHI6OmthYmxlKGNsdXN0ZXJfc3VtbWFyeSwgY2FwdGlvbiA9ICJHZW5lcyBwZXIgQ2x1c3RlciIpDQpgYGANCg0KIyBTdGVwIDk6IFByZXBhcmUgVmlzdWFsaXphdGlvbiBEYXRhDQoNCkhlcmUsIEkgY29udmVydCBleHByZXNzaW9uIGRhdGEgdG8gbG9uZyBmb3JtYXQgYW5kLCB1c2luZyB0aGUgbWFTaWdQcm8gZ2VuZSBjbHVzdGVycyBmcm9tIHRoZSBwcmV2aW91cyBwYXJ0IG9mIHRoc2kgd29ya2Zsb3csIHByZXBhcmUgdG8gcGxvdCBvdXIgZGF0YSBpbiBsaW5lcGxvdHMgYnkgJ2dyb3VwJy4gSSBhbHNvIGNhbGN1bGF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIGNsdXN0ZXIgcHJvZmlsZSBwbG90cy4NCg0KYGBge3Igc3RlcDktcHJlcGFyZS12aXosIGVjaG89VFJVRX0NCmV4cHJfc3ViIDwtIGV4cHJfbWF0W3NpZ19nZW5lcywgLCBkcm9wID0gRkFMU0VdDQoNCiMgUmVzaGFwZSB0byBsb25nIGZvcm1hdCBmb3IgcGxvdHRpbmcNCmV4cHJfZGYgPC0gcmVzaGFwZTI6Om1lbHQoDQogIGFzLm1hdHJpeChleHByX3N1YiksDQogIHZhcm5hbWVzID0gYygiZ2VuZV9pZCIsICJzYW1wbGVfaWQiKSwNCiAgdmFsdWUubmFtZSA9ICJjcG0iDQopDQoNCmV4cHJfZGYkZ2Vub3R5cGUgPC0gbWQkZ2Vub3R5cGVbbWF0Y2goZXhwcl9kZiRzYW1wbGVfaWQsIG1kJHNhbXBsZV9pZCldDQpleHByX2RmJHRpbWVwb2ludCA8LSBhcy5udW1lcmljKG1kJHRpbWVwb2ludFttYXRjaChleHByX2RmJHNhbXBsZV9pZCwgbWQkc2FtcGxlX2lkKV0pDQpleHByX2RmJGNsdXN0ZXIgPC0gY2x1c3RlcnMkY3V0W2V4cHJfZGYkZ2VuZV9pZF0NCg0KIyBDYWxjdWxhdGUgbWVkaWFuIGV4cHJlc3Npb24gcGVyIGNsdXN0ZXIvZ2Vub3R5cGUvdGltZXBvaW50DQpzdW1fZGYgPC0gZXhwcl9kZiAlPiUNCiAgZHBseXI6Omdyb3VwX2J5KGNsdXN0ZXIsIGdlbm90eXBlLCB0aW1lcG9pbnQpICU+JQ0KICBkcGx5cjo6c3VtbWFyaXNlKG1lZGlhbl9jcG0gPSBtZWRpYW4oY3BtKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogIGRwbHlyOjphcnJhbmdlKGNsdXN0ZXIsIGdlbm90eXBlLCB0aW1lcG9pbnQpDQoNCmNsdXN0ZXJfY291bnRzIDwtIGV4cHJfZGYgJT4lDQogIGRwbHlyOjpncm91cF9ieShjbHVzdGVyKSAlPiUNCiAgZHBseXI6OnN1bW1hcmlzZShuX2dlbmVzID0gZHBseXI6Om5fZGlzdGluY3QoZ2VuZV9pZCksIC5ncm91cHMgPSAiZHJvcCIpDQpgYGANCg0KIyBTdGVwIDEwOiBDbHVzdGVyIFByb2ZpbGUgVmlzdWFsaXphdGlvbg0KDQpJIG5vdyBjcmVhdGUgbGluZSBwbG90cyBzaG93aW5nIG1lZGlhbiBleHByZXNzaW9uIHRyYWplY3RvcmllcyBmb3IgZWFjaCBjbHVzdGVyLiBJIHVzZSBtZWRpYW4gQ1BNIGhlcmU7IHlvdSBjYW4gdXNlIG1lZGlhbiBDUE0gb3IgbWVhbiBDUE0sIG9yIERFU2VxMiBtZWFuIHZzdCBjb3VudHMuIFdlIGhhdmUgc2VwYXJhdGUgbGluZXMgZm9yIGVhY2ggZ2Vub3R5cGUgdG8gc2hvdyBkaWZmZXJlbnRpYWwgdGVtcG9yYWwga2luZXRpY3Mgb2YgZ2VuZSBleHByZXNzaW9uIHRoYXQgYXJlIGdlbm90eXBlLWRlcGVuZGVudC4NCg0KYGBge3Igc3RlcDEwLXBsb3QtZnVuY3Rpb24sIGVjaG89VFJVRX0NCmdlbm9fY29sb3JzIDwtIGMoIkQyMSIgPSAiIzFGNzhCNCIsICJUMjEiID0gIiNFMzFBMUMiKQ0KDQpwbG90X2NsdXN0ZXIgPC0gZnVuY3Rpb24oZGYsIGNvdW50c19kZiwgY2wsIHNob3dfbGVnZW5kID0gVFJVRSkgew0KICBuX2dlbmVzIDwtIGNvdW50c19kZiRuX2dlbmVzW2NvdW50c19kZiRjbHVzdGVyID09IGNsXQ0KICBwbG90X2RhdGEgPC0gZGZbZGYkY2x1c3RlciA9PSBjbCwgXQ0KICANCiAgcCA8LSBnZ3Bsb3QyOjpnZ3Bsb3QoDQogICAgcGxvdF9kYXRhLA0KICAgIGdncGxvdDI6OmFlcyh4ID0gdGltZXBvaW50LCB5ID0gbWVkaWFuX2NwbSwgY29sb3IgPSBnZW5vdHlwZSwgZ3JvdXAgPSBnZW5vdHlwZSkNCiAgKSArDQogICAgZ2dwbG90Mjo6Z2VvbV9saW5lKGxpbmV3aWR0aCA9IDEpICsNCiAgICBnZ3Bsb3QyOjpnZW9tX3BvaW50KHNpemUgPSAyLjUpICsNCiAgICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gZ2Vub19jb2xvcnMsIG5hbWUgPSAiR2Vub3R5cGUiKSArDQogICAgZ2dwbG90Mjo6c2NhbGVfeF9jb250aW51b3VzKA0KICAgICAgYnJlYWtzID0gYyg2LCAxMCwgMTcpLA0KICAgICAgbGFiZWxzID0gYygiRGF5IDYiLCAiRGF5IDEwIiwgIkRheSAxNyIpDQogICAgKSArDQogICAgZ2dwbG90Mjo6c2NhbGVfeV9jb250aW51b3VzKA0KICAgICAgYnJlYWtzID0gc2NhbGVzOjpwcmV0dHlfYnJlYWtzKG4gPSA2KSwNCiAgICAgIGV4cGFuZCA9IGdncGxvdDI6OmV4cGFuc2lvbihtdWx0ID0gYygwLjEsIDAuMDUpKQ0KICAgICkgKw0KICAgIGdncGxvdDI6OmxhYnMoDQogICAgICB0aXRsZSA9IHBhc3RlMCgiQ2x1c3RlciAiLCBjbCwgIiAobj0iLCBuX2dlbmVzLCAiKSIpLA0KICAgICAgeCA9ICJUaW1lcG9pbnQiLA0KICAgICAgeSA9ICJNZWRpYW4gQ1BNIg0KICAgICkgKw0KICAgIGdncGxvdDI6OnRoZW1lX21pbmltYWwoKSArDQogICAgZ2dwbG90Mjo6dGhlbWUoDQogICAgICBwbG90LnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxMSwgaGp1c3QgPSAwLjUpLA0KICAgICAgYXhpcy50aXRsZS54ID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgbWFyZ2luID0gZ2dwbG90Mjo6bWFyZ2luKHQgPSA1KSksDQogICAgICBheGlzLnRpdGxlLnkgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBtYXJnaW4gPSBnZ3Bsb3QyOjptYXJnaW4ociA9IDUpKSwNCiAgICAgIGF4aXMudGV4dCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gOSksDQogICAgICBheGlzLnRleHQueCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLA0KICAgICAgbGVnZW5kLnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgICBsZWdlbmQudGV4dCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gOSksDQogICAgICBsZWdlbmQucG9zaXRpb24gPSBpZmVsc2Uoc2hvd19sZWdlbmQsICJyaWdodCIsICJub25lIikNCiAgICApDQogIA0KICByZXR1cm4ocCkNCn0NCmBgYA0KDQpUaGUgZm9sbG93aW5nIGNvZGUgc2F2ZXMgaW5kaXZpZHVhbCBjbHVzdGVyIHBsb3RzIHRvIGZpbGVzDQoNCmBgYHtyIHN0ZXAxMC1pbmRpdmlkdWFsLXBsb3RzLCBldmFsPUZBTFNFLCBlY2hvPVRSVUV9DQojIFNhdmUgaW5kaXZpZHVhbCBjbHVzdGVyIHByb2ZpbGUgcGxvdHMNCmZvciAoY2wgaW4gc29ydCh1bmlxdWUoc3VtX2RmJGNsdXN0ZXIpKSkgew0KICBwIDwtIHBsb3RfY2x1c3RlcihzdW1fZGYsIGNsdXN0ZXJfY291bnRzLCBjbCwgc2hvd19sZWdlbmQgPSBUUlVFKQ0KICBnZ3Bsb3QyOjpnZ3NhdmUoDQogICAgZmlsZW5hbWUgPSBmaWxlLnBhdGgoZmlnX2Rpciwgc3ByaW50ZigiY2x1c3Rlcl8lMDJkX3Byb2ZpbGUuc3ZnIiwgY2wpKSwNCiAgICBwbG90ID0gcCwNCiAgICB3aWR0aCA9IDYuNSwNCiAgICBoZWlnaHQgPSA0LjUsDQogICAgdW5pdHMgPSAiaW4iDQogICkNCn0NCmBgYA0KDQpgYGB7ciBzdGVwMTAtY29tYmluZWQtcGxvdCwgZWNobz1UUlVFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTgsIGZpZy5jYXA9IkNvbWJpbmVkIGNsdXN0ZXIgcHJvZmlsZSBwbG90IHNob3dpbmcgYWxsIGNsdXN0ZXJzIn0NCmNvbWJpbmVfY2x1c3Rlcl9wbG90cyA8LSBmdW5jdGlvbihkZiwgY291bnRzX2RmLCBuY29sLCBucm93LCBvdXRwdXRfcGF0aCwgd2lkdGgsIGhlaWdodCkgew0KICBhbGxfY2x1c3RlcnMgPC0gc29ydCh1bmlxdWUoZGYkY2x1c3RlcikpDQogIHBsb3RfbGlzdCA8LSBsYXBwbHkoYWxsX2NsdXN0ZXJzLCBmdW5jdGlvbihjbCkgew0KICAgIHBsb3RfY2x1c3RlcihkZiwgY291bnRzX2RmLCBjbCwgc2hvd19sZWdlbmQgPSBGQUxTRSkNCiAgfSkNCiAgDQogIGNvbWJpbmVkIDwtIHBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2xpc3QsIG5jb2wgPSBuY29sLCBucm93ID0gbnJvdykgKw0KICAgIHBhdGNod29yazo6cGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKSAmDQogICAgZ2dwbG90Mjo6dGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikNCiAgDQogIGdncGxvdDI6Omdnc2F2ZSgNCiAgICBmaWxlbmFtZSA9IG91dHB1dF9wYXRoLA0KICAgIHBsb3QgPSBjb21iaW5lZCwNCiAgICB3aWR0aCA9IHdpZHRoLA0KICAgIGhlaWdodCA9IGhlaWdodCwNCiAgICB1bml0cyA9ICJpbiINCiAgKQ0KICANCiAgcmV0dXJuKGNvbWJpbmVkKQ0KfQ0KDQpjb21iaW5lZF9wbG90IDwtIGNvbWJpbmVfY2x1c3Rlcl9wbG90cygNCiAgZGYgPSBzdW1fZGYsDQogIGNvdW50c19kZiA9IGNsdXN0ZXJfY291bnRzLA0KICBuY29sID0gMiwNCiAgbnJvdyA9IDUsDQogIG91dHB1dF9wYXRoID0gZmlsZS5wYXRoKGZpZ19kaXIsICJhbGxfY2x1c3RlcnNfY29tYmluZWQuc3ZnIiksDQogIHdpZHRoID0gMTAsDQogIGhlaWdodCA9IDE4DQopDQoNCmNvbWJpbmVkX3Bsb3QNCmBgYA0KDQojIFN0ZXAgMTE6IFN1bW1hcnkgU3RhdGlzdGljcw0KDQpJIGNyZWF0ZSBhIHN1bW1hcnkgb2Yga2V5IG1ldHJpY3MgZnJvbSB0aGUgbWFTaWdQcm8gYW5hbHlzaXMgaGVyZS4gaWYgSSBkZWNpZGUgdG8gbG9vayBhdCBvdGhlciBwYXJhbWV0ZXIgY29tYmluYXRpb25zLCBJIGNhbiBjb21wYXJlIHRoZSByZXN1bHRzIHRvIHRoZSBzdW1tYXJ5IGhlcmUuDQoNCmBgYHtyIHN0ZXAxMS1zdW1tYXJ5LCBlY2hvPVRSVUV9DQpzdW1tYXJ5X3N0YXRzIDwtIGRhdGEuZnJhbWUoDQogIHRvdGFsX2dlbmVzX3Rlc3RlZCA9IG5yb3coZXhwcl9tYXQpLA0KICBzaWduaWZpY2FudF90ZW1wb3JhbCA9IGxlbmd0aChmaXQkU0VMRUMpLA0KICBzaWduaWZpY2FudF9nZW5vdHlwZV9zcGVjaWZpYyA9IGxlbmd0aChzaWdfZ2VuZXMpLA0KICBudW1fY2x1c3RlcnMgPSBrX2NsdXN0ZXJzLA0KICBwb2x5bm9taWFsX2RlZ3JlZSA9IHBvbHlfZGVncmVlLA0KICBxX3RocmVzaG9sZCA9IHFfdGhyZXNob2xkLA0KICByc3FfdGhyZXNob2xkID0gcnNxX3RocmVzaG9sZA0KKQ0KDQpyZWFkcjo6d3JpdGVfY3N2KHN1bW1hcnlfc3RhdHMsIGZpbGUucGF0aChyZXNfZGlyLCAic3VtbWFyeV9zdGF0aXN0aWNzLmNzdiIpKQ0KYGBgDQoNCmBgYHtyIHN0ZXAxMS1kaXNwbGF5LXN1bW1hcnksIGVjaG89RkFMU0V9DQojIERpc3BsYXkgc3VtbWFyeSBhcyBmb3JtYXR0ZWQgdGFibGUNCmtuaXRyOjprYWJsZShzdW1tYXJ5X3N0YXRzLCANCiAgICAgICAgICAgICBjYXB0aW9uID0gIlN1bW1hcnkgU3RhdGlzdGljcyBmcm9tIG1hU2lnUHJvIEFuYWx5c2lzIiwNCiAgICAgICAgICAgICBkaWdpdHMgPSAyKQ0KYGBgDQoNCiMgU3RlcCAxMjogR2VuZSBPbnRvbG9neSBFbnJpY2htZW50DQoNClRvIGRlcml2ZSBiaW9sb2dpY2FsL3BhdGh3YXkgaW5zaWdodCBmcm9tIHRoZSBtYVNpZ1BybyBhbmFseXNpcywgSSBydW4gR08gb3ZlcnJlcHJlc2VudGF0aW9uIGFuYWx5c2lzIG9uIGVhY2ggY2x1c3RlciB0byBpZGVudGlmeSAgYmlvbG9naWNhbCBwcm9jZXNzZXMgYXNzb2NpYXRlZCB3aXRoIGVhY2ggY2x1c3Rlci4gVGhpcyBoZWxwcyBpbnRlcnByZXQgdGhlIGZ1bmN0aW9uYWwgc2lnbmlmaWNhbmNlIG9mIGNvLWV4cHJlc3NlZCBnZW5lIGdyb3Vwcy4gRm9yIGV4YW1wbGUsIGNsdXN0ZXIgNSwgaW4gd2hpY2ggZXhwcmVzc2lvbiBpcyBkZWNyZWFzZWQgaW4gVDIxIGNvbXBhcmVkIHRvIEQyMSwgbG9va3MgcG90ZW50aWFsbHkgYmUgc29tZSB0eXBlIG9mIHByb2dlbml0b3IgZ2VuZSBleHByZXNzaW9uIGFzc29jaWF0ZWQgY2x1c3RlciwgZ2l2ZW4gdGhhdCB0aGUgZW5yaWNoZWQgR08gdGVybXMgYXJlIEROQSByZXBsaWNhdGlvbi1yZWxhdGVkIChpZSwgcHJvbGlmZXJhdGlvbiBnZW5lcykuIA0KDQpHTyB0ZXJtIGFuYWx5c2lzIGxpa2UgdGhpcyBhbGxvd3MgeW91IHRvIGRlcml2ZSBCUk9BRCBpbnNpZ2h0IC0tIHlvdSB3aWxsIGhhdmUgdG8gZGlnIGRlZXBlciBpbnRvIHRoZSBhY3R1YWwgZ2VuZXMgaW4gZWFjaCBjbHVzdGVyLiBGb3IgZXhhbXBsZSwgYXJlIGtub3duIG1hcmtlciBvZiBjZWxsIGN5Y2xlIHByb2dyZXNzaW9uIGluIGNsdXN0ZXIgNT8gZXRjLg0KDQpBIG1vcmUgaW50dWl0aXZlIGV4YW1wbGUgaXMgY2x1c3RlciA2LiBUaGUgZW5yaWNoZWQgR08gdGVybXMgZm9yIHRoaXMgY2x1c3RlciBhcHBlYXIgdG8gYmUgcmVsYXRlZCB0byBmdW5jdGlvbiBvZiBtYXR1cmUgbmV1cm9ucy4gSW4gdGhpcyBkaWZmZXJlbnRpYXRpb24sIHRoZSBjZWxscyBtYXR1cmUgZnJvbSBuZXVyYWwgcHJvZ2VuaXRvcnMgdG8gbWF0dXJlLCBkaWZmZXJlbnRpYXRkIG5ldXJvbnMuIENvcnJvYm9yYXRpbmcgdGhpcywgdGhlIGxpbmVwbG90IGZvciBjbHVzdGVyIDYgc2hvd3MgZmFzdGVyIGFjcXVpc2l0aW9uIG9mIGV4cHJlc3Npb24gb2YgY2x1c3RlciA2IGdlbmVzIGluIFQyMSBjb21wYXJlZCB0byBEMjEuIE9uZSBicm9hZCBpbnNpZ2h0LCB0aGVuLCB3b3VsZCBiZSB0aGF0IFQyMSBhcHBlYXJzIHRvIGFjcXVpcmUgYSBuZXVyb25hbCBtYXR1cmF0aW9uLWxpa2UgdHJhbnNjcmlwdGlvbmFsIHNpZ25hdHVyZSBtb3JlIHF1aWNrbHkgb3ZlciB0aW1lIGNvbXBhcmVkIHRvIEQyMS4gYWdhaW4sIHRoaXMgZmluZGluZyBzaG91bGQgaWRlYWxseSBiZSBjb3Jyb2JvcmF0ZWQgYnkgeW91ciBkaWZmZXJlbnRpYWwgZ2VuZSBleHByZXNzaW9uIGNvbXBhcmlzb25zIGJldHdlZW4gVDIxIGFuZCBEMjEgYXQgZWFjaCB0aW1lcG9pbnQuIA0KDQptYVNpZ1BybyBhbGxvd3MgdXMgdG8gdmlzdWFsaXplIHRoaXMgJ3RlbXBvcmFsbHknIGFuZCBpZGVudGlmeSB0aGUgZ2VuZXMgZHJpdmluZyBieSBjbHVzdGVyaW5nIHRoZW0gaW50byBtb2R1bGVzLg0KDQpgYGB7ciBzdGVwMTItZ28tc2V0dXAsIGVjaG89VFJVRX0NCmdvX2Rpcl9yZXMgPC0gZmlsZS5wYXRoKHJlc19kaXIsICJnb19lbnJpY2htZW50IikNCmdvX2Rpcl9maWdzIDwtIGZpbGUucGF0aChmaWdfZGlyLCAiZ29fZW5yaWNobWVudCIpDQpkaXIuY3JlYXRlKGdvX2Rpcl9yZXMsIHJlY3Vyc2l2ZSA9IFRSVUUsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQ0KZGlyLmNyZWF0ZShnb19kaXJfZmlncywgcmVjdXJzaXZlID0gVFJVRSwgc2hvd1dhcm5pbmdzID0gRkFMU0UpDQpgYGANCg0KIyMjIEdPIEVucmljaG1lbnQgRnVuY3Rpb24NCg0KYGBge3Igc3RlcDEyLWdvLWZ1bmN0aW9uLCBlY2hvPVRSVUV9DQpydW5fY2x1c3Rlcl9nbyA8LSBmdW5jdGlvbihnZW5lX2xpc3QsIGNsdXN0ZXJfaWQpIHsNCiAgIyBTa2lwIGNsdXN0ZXJzIHdpdGggdG9vIGZldyBnZW5lcw0KICBpZiAobGVuZ3RoKGdlbmVfbGlzdCkgPCA1KSByZXR1cm4oTlVMTCkNCg0KICAjIENvbnZlcnQgZ2VuZSBzeW1ib2xzIHRvIEVudHJleiBJRHMNCiAgZ2VuZV9lbnRyZXogPC0gY2x1c3RlclByb2ZpbGVyOjpiaXRyKA0KICAgIGdlbmVfbGlzdCwNCiAgICBmcm9tVHlwZSA9ICJTWU1CT0wiLA0KICAgIHRvVHlwZSA9ICJFTlRSRVpJRCIsDQogICAgT3JnRGIgPSBvcmcuSHMuZWcuZGINCiAgKQ0KICBpZiAobnJvdyhnZW5lX2VudHJleikgPT0gMCkgcmV0dXJuKE5VTEwpDQoNCiAgIyBSdW4gR08gZW5yaWNobWVudCAoQmlvbG9naWNhbCBQcm9jZXNzKQ0KICBnb19yZXN1bHQgPC0gY2x1c3RlclByb2ZpbGVyOjplbnJpY2hHTygNCiAgICBnZW5lID0gZ2VuZV9lbnRyZXokRU5UUkVaSUQsDQogICAgT3JnRGIgPSBvcmcuSHMuZWcuZGIsDQogICAgb250ID0gIkJQIiwNCiAgICBwQWRqdXN0TWV0aG9kID0gIkJIIiwNCiAgICBwdmFsdWVDdXRvZmYgPSAwLjA1LA0KICAgIHF2YWx1ZUN1dG9mZiA9IDAuMDUsDQogICAgcmVhZGFibGUgPSBUUlVFDQogICkNCiAgaWYgKGlzLm51bGwoZ29fcmVzdWx0KSB8fCBucm93KGFzLmRhdGEuZnJhbWUoZ29fcmVzdWx0KSkgPT0gMCkgcmV0dXJuKE5VTEwpDQoNCiAgIyBTaW1wbGlmeSByZWR1bmRhbnQgdGVybXMNCiAgZ29fc2ltcGxpZmllZCA8LSBjbHVzdGVyUHJvZmlsZXI6OnNpbXBsaWZ5KGdvX3Jlc3VsdCwgY3V0b2ZmID0gMC43KQ0KICBnb19kZiA8LSBhcy5kYXRhLmZyYW1lKGdvX3NpbXBsaWZpZWQpDQogIGlmIChucm93KGdvX2RmKSA9PSAwKSByZXR1cm4oTlVMTCkNCg0KICAjIFNhdmUgcmVzdWx0cw0KICBzYXZlUkRTKGdvX2RmLCBmaWxlLnBhdGgoZ29fZGlyX3JlcywgcGFzdGUwKCJjbHVzdGVyXyIsIGNsdXN0ZXJfaWQsICJfR09fcmVzdWx0cy5yZHMiKSkpDQogIHJlYWRyOjp3cml0ZV9jc3YoZ29fZGYsIGZpbGUucGF0aChnb19kaXJfcmVzLCBwYXN0ZTAoImNsdXN0ZXJfIiwgY2x1c3Rlcl9pZCwgIl9HT19yZXN1bHRzLmNzdiIpKSkNCg0KICAjIENyZWF0ZSBkb3QgcGxvdCBmb3IgdG9wIHRlcm1zDQogIGdvX3RvcCA8LSBnb19kZiAlPiUNCiAgICBkcGx5cjo6c2xpY2VfaGVhZChuID0gMTApICU+JQ0KICAgIGRwbHlyOjptdXRhdGUoRGVzY3JpcHRpb24gPSBzdHJpbmdyOjpzdHJfd3JhcChEZXNjcmlwdGlvbiwgNDApKQ0KDQogIGlmIChucm93KGdvX3RvcCkgPiAwKSB7DQogICAgbl9nZW5lcyA8LSBsZW5ndGgoZ2VuZV9saXN0KQ0KDQogICAgcCA8LSBnZ3Bsb3QyOjpnZ3Bsb3QoDQogICAgICBnb190b3AsDQogICAgICBnZ3Bsb3QyOjphZXMoeCA9IC1sb2cxMChwdmFsdWUpLA0KICAgICAgICAgICAgICAgICAgIHkgPSByZW9yZGVyKERlc2NyaXB0aW9uLCAtbG9nMTAocHZhbHVlKSksDQogICAgICAgICAgICAgICAgICAgc2l6ZSA9IENvdW50KQ0KICAgICkgKw0KICAgICAgZ2dwbG90Mjo6Z2VvbV9wb2ludChjb2xvciA9ICIjMERBRDhEIiwgYWxwaGEgPSAwLjgpICsNCiAgICAgIGdncGxvdDI6OnNjYWxlX3NpemVfY29udGludW91cyhyYW5nZSA9IGMoMywgOCksIG5hbWUgPSAiR2VuZSBjb3VudCIpICsNCiAgICAgIGdncGxvdDI6OnNjYWxlX3lfZGlzY3JldGUocG9zaXRpb24gPSAicmlnaHQiKSArDQogICAgICBnZ3Bsb3QyOjpsYWJzKA0KICAgICAgICB4ID0gZXhwcmVzc2lvbigiLWxvZyJbMTBdICogIihwLXZhbHVlKSIpLA0KICAgICAgICB5ID0gTlVMTCwNCiAgICAgICAgdGl0bGUgPSBwYXN0ZTAoIkNsdXN0ZXIgIiwgY2x1c3Rlcl9pZCksDQogICAgICAgIHN1YnRpdGxlID0gcGFzdGUwKCJHTyBCaW9sb2dpY2FsIFByb2Nlc3NlcyAobj0iLCBuX2dlbmVzLCAiIGdlbmVzKSIpDQogICAgICApICsNCiAgICAgIGdncGxvdDI6OnRoZW1lX21pbmltYWwoKSArDQogICAgICBnZ3Bsb3QyOjp0aGVtZSgNCiAgICAgICAgcGxvdC50aXRsZSA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gMTQsIGhqdXN0ID0gMCksDQogICAgICAgIHBsb3Quc3VidGl0bGUgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBoanVzdCA9IDApLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDExKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDksIGhqdXN0ID0gMCksDQogICAgICAgIGF4aXMudGV4dC54ID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iDQogICAgICApDQoNCiAgICBnZ3Bsb3QyOjpnZ3NhdmUoDQogICAgICBmaWxlLnBhdGgoZ29fZGlyX2ZpZ3MsIHBhc3RlMCgiY2x1c3Rlcl8iLCBjbHVzdGVyX2lkLCAiX0dPX2RvdHBsb3Quc3ZnIikpLA0KICAgICAgcGxvdCA9IHAsIHdpZHRoID0gNywgaGVpZ2h0ID0gbWluKDgsIDMgKyBucm93KGdvX3RvcCkgKiAwLjMpDQogICAgKQ0KICB9DQoNCiAgcmV0dXJuKGdvX2RmKQ0KfQ0KYGBgDQoNCiMjIyBSdW4gR08gRW5yaWNobWVudCBvbiBBbGwgQ2x1c3RlcnMNCg0KYGBge3Igc3RlcDEyLXJ1bi1nbywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYWxsX2NsdXN0ZXJfZ29fcmVzdWx0cyA8LSBsaXN0KCkNCg0KZm9yIChjbCBpbiBzb3J0KHVuaXF1ZShjbHVzdGVycyRjdXQpKSkgew0KICBjbHVzdGVyX2dlbmVzIDwtIG5hbWVzKGNsdXN0ZXJzJGN1dClbY2x1c3RlcnMkY3V0ID09IGNsXQ0KICBnb19yZXMgPC0gcnVuX2NsdXN0ZXJfZ28oY2x1c3Rlcl9nZW5lcywgY2wpDQoNCiAgaWYgKCFpcy5udWxsKGdvX3JlcykpIHsNCiAgICBhbGxfY2x1c3Rlcl9nb19yZXN1bHRzW1twYXN0ZTAoImNsdXN0ZXJfIiwgY2wpXV0gPC0gZ29fcmVzDQogIH0NCn0NCg0Kc2F2ZVJEUyhhbGxfY2x1c3Rlcl9nb19yZXN1bHRzLCBmaWxlLnBhdGgoZ29fZGlyX3JlcywgImFsbF9jbHVzdGVyc19HT19yZXN1bHRzLnJkcyIpKQ0KY2F0KCJDbHVzdGVycyB3aXRoIHNpZ25pZmljYW50IEdPIGVucmljaG1lbnQ6IiwgbGVuZ3RoKGFsbF9jbHVzdGVyX2dvX3Jlc3VsdHMpLCAiLyIsIGtfY2x1c3RlcnMsICJcbiIpDQpgYGANCg0KIyMjIEdPIFJlc3VsdHMgYnkgQ2x1c3Rlcg0KDQpgYGB7ciBzdGVwMTItZ28tc3VtbWFyeSwgZWNobz1GQUxTRSwgcmVzdWx0cz0nYXNpcyd9DQojIERpc3BsYXkgR08gZW5yaWNobWVudCByZXN1bHRzIGZvciBlYWNoIGNsdXN0ZXIgd2l0aCBjbGVhbiBmb3JtYXR0aW5nDQpmb3IgKGNsdXN0ZXJfbmFtZSBpbiBuYW1lcyhhbGxfY2x1c3Rlcl9nb19yZXN1bHRzKSkgew0KICBpZiAoIWlzLm51bGwoYWxsX2NsdXN0ZXJfZ29fcmVzdWx0c1tbY2x1c3Rlcl9uYW1lXV0pKSB7DQogICAgY2x1c3Rlcl9nbyA8LSBhbGxfY2x1c3Rlcl9nb19yZXN1bHRzW1tjbHVzdGVyX25hbWVdXSAlPiUNCiAgICAgIGRwbHlyOjpzbGljZV9oZWFkKG4gPSA1KSAlPiUNCiAgICAgIGRwbHlyOjp0cmFuc211dGUoDQogICAgICAgIGBCaW9sb2dpY2FsIFByb2Nlc3NgID0gRGVzY3JpcHRpb24sDQogICAgICAgIGBQLXZhbHVlYCA9IGZvcm1hdEMocHZhbHVlLCBmb3JtYXQgPSAiZSIsIGRpZ2l0cyA9IDIpLA0KICAgICAgICBgRkRSYCA9IGZvcm1hdEMocXZhbHVlLCBmb3JtYXQgPSAiZSIsIGRpZ2l0cyA9IDIpLA0KICAgICAgICBgR2VuZXNgID0gQ291bnQNCiAgICAgICkNCg0KICAgIGlmIChucm93KGNsdXN0ZXJfZ28pID4gMCkgew0KICAgICAgY2x1c3Rlcl9udW0gPC0gZ3N1YigiY2x1c3Rlcl8iLCAiIiwgY2x1c3Rlcl9uYW1lKQ0KICAgICAgY2F0KCJcblxuKipDbHVzdGVyICIsIGNsdXN0ZXJfbnVtLCAiKipcblxuIiwgc2VwID0gIiIpDQogICAgICBwcmludChrbml0cjo6a2FibGUoY2x1c3Rlcl9nbywNCiAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQgPSAiaHRtbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgdGFibGUuYXR0ciA9ICdjbGFzcz0iZ28tdGFibGUiJywNCiAgICAgICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbiA9IGMoImwiLCAiciIsICJyIiwgInIiKSkpDQogICAgICBjYXQoIlxuXG4iKQ0KICAgIH0NCiAgfQ0KfQ0KYGBgDQoNCiMgU3RlcCAxMzogRXhwcmVzc2lvbiBIZWF0bWFwcw0KDQpQZXJoYXBzIGEgbW9yZSB2aXN1YWxseSAnaW50dWl0aXZlJyB3YXkgb2YgdW5kZXJzdGFuZGluZyB0aGUgdGVtcG9yYWwgZXhwcmVzc2lvbiBvZiBtYVNpZ1Byby1pZGVudGlmaWVkIGdlbmVzIGJldHdlZW4gRDIxIGFuZCBUMjEgaXMgYnkgYSBoZWF0bWFwLiBIZXJlLCBJIHZpc3VhbGl6ZSBleHByZXNzaW9uIHBhdHRlcm5zIGZvciBlYWNoIGNsdXN0ZXIgdXNpbmcgei1zY29yZSBub3JtYWxpemVkIHZhbHVlcyBpbiBhIGhlYXRtYXAgZ2VuZXJhdGVkIGJ5IHRoZSBwaGVhdG1hcCBSIHBhY2thZ2UuIFNhbXBsZXMgYXJlIGdyb3VwZWQgYnkgZ2Vub3R5cGUgYW5kIG9yZGVyZWQgYnkgdGltZXBvaW50IChzbyBmaXJzdCBncm91cCBvZiBzYW1wbGVzIGlzIEQyMSwgdGhlbiBUMjEpLg0KDQpUaGUgaGVhdG1hcHMgbWF0Y2ggdGhlIGxpbmVwbG90IHRyZW5kLCBidXQgSSB0aGluayB0aGV5IGFyZSB2aXN1YWxseSBlYXNpZXIgdG8gaW50ZXJwcmV0Lg0KDQpgYGB7ciBzdGVwMTMtaGVhdG1hcC1zZXR1cCwgZWNobz1UUlVFfQ0KaWYgKGZpbGUuZXhpc3RzKCJkYXRhL2NvdW50cy92c3QvV0MyNF92c3RfY291bnRzLnJkcyIpKSB7DQogIHZzdF9tYXQgPC0gcmVhZFJEUygiZGF0YS9jb3VudHMvdnN0L1dDMjRfdnN0X2NvdW50cy5yZHMiKQ0KfSBlbHNlIHsNCiAgdnN0X21hdCA8LSBjb3VudHNfY3BtDQp9DQoNCmhlYXRtYXBfZGlyIDwtIGZpbGUucGF0aChmaWdfZGlyLCAiaGVhdG1hcHMiKQ0KZGlyLmNyZWF0ZShoZWF0bWFwX2RpciwgcmVjdXJzaXZlID0gVFJVRSwgc2hvd1dhcm5pbmdzID0gRkFMU0UpDQpgYGANCg0KYGBge3Igc3RlcDEzLWhlYXRtYXAtZnVuY3Rpb24sIGVjaG89VFJVRX0NCmNyZWF0ZV9jbHVzdGVyX2hlYXRtYXAgPC0gZnVuY3Rpb24oZ2VuZV9saXN0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdnN0X21hdCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX2lkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRwdXRfcGF0aCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSA3LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQgPSA3LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X3Jvd25hbWVzID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfY29sbmFtZXMgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9jb2xzID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvbnRzaXplX3JvdyA9IDcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpbnNvcml6ZV9saW1pdCA9IDIpIHsNCiAgDQogIGdlbmVzX2ZvdW5kIDwtIGludGVyc2VjdChnZW5lX2xpc3QsIHJvd25hbWVzKHZzdF9tYXQpKQ0KICANCiAgaWYgKGxlbmd0aChnZW5lc19mb3VuZCkgPCAyKSB7DQogICAgcmV0dXJuKE5VTEwpDQogIH0NCiAgDQogICMgT3JkZXIgc2FtcGxlczogRDIxIGJ5IHRpbWVwb2ludCwgdGhlbiBUMjEgYnkgdGltZXBvaW50DQogIG1ldGFfZDIxIDwtIG1ldGEgJT4lDQogICAgZHBseXI6OmZpbHRlcihnZW5vdHlwZSA9PSAiRDIxIikgJT4lDQogICAgZHBseXI6OmFycmFuZ2UodGltZXBvaW50KQ0KICANCiAgbWV0YV90MjEgPC0gbWV0YSAlPiUNCiAgICBkcGx5cjo6ZmlsdGVyKGdlbm90eXBlID09ICJUMjEiKSAlPiUNCiAgICBkcGx5cjo6YXJyYW5nZSh0aW1lcG9pbnQpDQogIA0KICBtZXRhX29yZGVyZWQgPC0gZHBseXI6OmJpbmRfcm93cyhtZXRhX2QyMSwgbWV0YV90MjEpDQogIHNhbXBsZV9vcmRlciA8LSBtZXRhX29yZGVyZWQkc2FtcGxlX2lkDQogIA0KICB2c3Rfc3Vic2V0IDwtIHZzdF9tYXRbZ2VuZXNfZm91bmQsIHNhbXBsZV9vcmRlcl0NCiAgdnN0X3pzY29yZSA8LSB0KHNjYWxlKHQodnN0X3N1YnNldCkpKQ0KICANCiAgdnN0X3pzY29yZVt2c3RfenNjb3JlID4gd2luc29yaXplX2xpbWl0XSA8LSB3aW5zb3JpemVfbGltaXQNCiAgdnN0X3pzY29yZVt2c3RfenNjb3JlIDwgLXdpbnNvcml6ZV9saW1pdF0gPC0gLXdpbnNvcml6ZV9saW1pdA0KICANCiAgYW5ub3RhdGlvbl9kZiA8LSBtZXRhX29yZGVyZWQgJT4lDQogICAgZHBseXI6OnNlbGVjdChnZW5vdHlwZSwgdGltZXBvaW50KSAlPiUNCiAgICBkcGx5cjo6bXV0YXRlKA0KICAgICAgR2Vub3R5cGUgPSBnZW5vdHlwZSwNCiAgICAgIFRpbWVwb2ludCA9IGZhY3RvcihwYXN0ZTAoIkQiLCB0aW1lcG9pbnQpLCBsZXZlbHMgPSBjKCJENiIsICJEMTAiLCAiRDE3IikpDQogICAgKSAlPiUNCiAgICBkcGx5cjo6c2VsZWN0KEdlbm90eXBlLCBUaW1lcG9pbnQpDQogIA0KICBhbm5vdGF0aW9uX2NvbG9ycyA8LSBsaXN0KA0KICAgIEdlbm90eXBlID0gYyhEMjEgPSAiIzFGNzhCNCIsIFQyMSA9ICIjRTMxQTFDIiksDQogICAgVGltZXBvaW50ID0gYyhENiA9ICIjNjZDMkE1IiwgRDEwID0gIiNGQzhENjIiLCBEMTcgPSAiIzhEQTBDQiIpDQogICkNCiAgDQogIGhlYXRtYXBfY29sb3JzIDwtIGdyRGV2aWNlczo6Y29sb3JSYW1wUGFsZXR0ZShjKA0KICAgICIjMDUzMDYxIiwgIiMyMTY2QUMiLCAiIzQzOTNDMyIsICIjOTJDNURFIiwNCiAgICAiI0QxRTVGMCIsICIjRkREQkM3IiwgIiNGNEE1ODIiLCAiI0Q2NjA0RCIsDQogICAgIiNCMjE4MkIiLCAiIzY3MDAxRiINCiAgKSkoMTAwKQ0KICANCiAgcXVhbnRpbGVfYnJlYWtzIDwtIHVuaXF1ZShxdWFudGlsZShhcy52ZWN0b3IodnN0X3pzY29yZSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSAxMDEpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5ybSA9IFRSVUUpKQ0KICANCiAgIyBHYXAgcG9zaXRpb24gYmV0d2VlbiBEMjEgYW5kIFQyMSBncm91cHMNCiAgbl9kMjEgPC0gbnJvdyhtZXRhX2QyMSkNCiAgZ2FwX3Bvc2l0aW9ucyA8LSBuX2QyMQ0KICANCiAgcCA8LSBwaGVhdG1hcDo6cGhlYXRtYXAoDQogICAgdnN0X3pzY29yZSwNCiAgICBjb2xvciA9IGhlYXRtYXBfY29sb3JzLA0KICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9ICJldWNsaWRlYW4iLA0KICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2VfY29scyA9ICJldWNsaWRlYW4iLA0KICAgIGNsdXN0ZXJpbmdfbWV0aG9kID0gImNvbXBsZXRlIiwNCiAgICBjbHVzdGVyX3Jvd3MgPSBjbHVzdGVyX3Jvd3MsDQogICAgY2x1c3Rlcl9jb2xzID0gY2x1c3Rlcl9jb2xzLA0KICAgIGFubm90YXRpb25fY29sID0gYW5ub3RhdGlvbl9kZiwNCiAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGFubm90YXRpb25fY29sb3JzLA0KICAgIHNob3dfcm93bmFtZXMgPSBzaG93X3Jvd25hbWVzLA0KICAgIHNob3dfY29sbmFtZXMgPSBzaG93X2NvbG5hbWVzLA0KICAgIGZvbnRzaXplID0gMTAsDQogICAgZm9udHNpemVfcm93ID0gZm9udHNpemVfcm93LA0KICAgIGZvbnRzaXplX2NvbCA9IDgsDQogICAgYm9yZGVyX2NvbG9yID0gTkEsDQogICAgc2NhbGUgPSAibm9uZSIsDQogICAgICAgIGFubm90YXRpb25fbGVnZW5kID0gVFJVRSwNCiAgICBhbm5vdGF0aW9uX25hbWVzX2NvbCA9IEZBTFNFLA0KICAgIGxlZ2VuZCA9IFRSVUUsDQogICAgYnJlYWtzID0gcXVhbnRpbGVfYnJlYWtzLA0KICAgIGdhcHNfY29sID0gZ2FwX3Bvc2l0aW9ucw0KICApDQogIA0KICBnckRldmljZXM6OnBuZyhvdXRwdXRfcGF0aCwgd2lkdGggPSB3aWR0aCwgaGVpZ2h0ID0gaGVpZ2h0LCB1bml0cyA9ICJpbiIsIHJlcyA9IDMwMCkNCiAgcHJpbnQocCkNCiAgZ3JEZXZpY2VzOjpkZXYub2ZmKCkNCiAgDQogIHJldHVybihwKQ0KfQ0KYGBgDQoNCmBgYHtyIHN0ZXAxMy1kaXNwbGF5LWhlYXRtYXBzLCBlY2hvPVRSVUUsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTYsIGZpZy5jYXA9IkV4cHJlc3Npb24gaGVhdG1hcHMgZm9yIGFsbCBjbHVzdGVycyJ9DQojIERpc3BsYXkgaGVhdG1hcHMgZm9yIGFsbCBjbHVzdGVycw0KYWxsX2NsdXN0ZXJzIDwtIHNvcnQodW5pcXVlKGNsdXN0ZXJzJGN1dCkpDQoNCmZvciAoY2wgaW4gYWxsX2NsdXN0ZXJzKSB7DQogIGNsdXN0ZXJfZ2VuZXMgPC0gbmFtZXMoY2x1c3RlcnMkY3V0KVtjbHVzdGVycyRjdXQgPT0gY2xdDQogIA0KICBnZW5lc19mb3VuZCA8LSBpbnRlcnNlY3QoY2x1c3Rlcl9nZW5lcywgcm93bmFtZXModnN0X21hdCkpDQogIGlmIChsZW5ndGgoZ2VuZXNfZm91bmQpIDwgMikgbmV4dA0KICANCiAgIyBPcmRlciBzYW1wbGVzOiBEMjEgc2FtcGxlcyBieSB0aW1lcG9pbnQsIHRoZW4gVDIxIHNhbXBsZXMgYnkgdGltZXBvaW50DQogIG1ldGFfZDIxIDwtIG1kICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIoZ2Vub3R5cGUgPT0gIkQyMSIpICU+JQ0KICAgIGRwbHlyOjphcnJhbmdlKHRpbWVwb2ludCkNCiAgDQogIG1ldGFfdDIxIDwtIG1kICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIoZ2Vub3R5cGUgPT0gIlQyMSIpICU+JQ0KICAgIGRwbHlyOjphcnJhbmdlKHRpbWVwb2ludCkNCiAgDQogIG1ldGFfb3JkZXJlZCA8LSBkcGx5cjo6YmluZF9yb3dzKG1ldGFfZDIxLCBtZXRhX3QyMSkNCiAgc2FtcGxlX29yZGVyIDwtIG1ldGFfb3JkZXJlZCRzYW1wbGVfaWQNCiAgDQogIHZzdF9zdWJzZXQgPC0gdnN0X21hdFtnZW5lc19mb3VuZCwgc2FtcGxlX29yZGVyXQ0KICB2c3RfenNjb3JlIDwtIHQoc2NhbGUodCh2c3Rfc3Vic2V0KSkpDQogIA0KICB2c3RfenNjb3JlW3ZzdF96c2NvcmUgPiAzXSA8LSAzDQogIHZzdF96c2NvcmVbdnN0X3pzY29yZSA8IC0zXSA8LSAtMw0KICANCiAgYW5ub3RhdGlvbl9kZiA8LSBtZXRhX29yZGVyZWQgJT4lDQogICAgZHBseXI6OnNlbGVjdChnZW5vdHlwZSwgdGltZXBvaW50KSAlPiUNCiAgICBkcGx5cjo6bXV0YXRlKA0KICAgICAgR2Vub3R5cGUgPSBnZW5vdHlwZSwNCiAgICAgIFRpbWVwb2ludCA9IGZhY3RvcihwYXN0ZTAoIkQiLCB0aW1lcG9pbnQpLCBsZXZlbHMgPSBjKCJENiIsICJEMTAiLCAiRDE3IikpDQogICAgKSAlPiUNCiAgICBkcGx5cjo6c2VsZWN0KEdlbm90eXBlLCBUaW1lcG9pbnQpDQogIA0KICBhbm5vdGF0aW9uX2NvbG9ycyA8LSBsaXN0KA0KICAgIEdlbm90eXBlID0gYyhEMjEgPSAiIzFGNzhCNCIsIFQyMSA9ICIjRTMxQTFDIiksDQogICAgVGltZXBvaW50ID0gYyhENiA9ICIjNjZDMkE1IiwgRDEwID0gIiNGQzhENjIiLCBEMTcgPSAiIzhEQTBDQiIpDQogICkNCiAgDQogIGhlYXRtYXBfY29sb3JzIDwtIGdyRGV2aWNlczo6Y29sb3JSYW1wUGFsZXR0ZShjKA0KICAgICIjMDUzMDYxIiwgIiMyMTY2QUMiLCAiIzQzOTNDMyIsICIjOTJDNURFIiwNCiAgICAiI0QxRTVGMCIsICIjRkREQkM3IiwgIiNGNEE1ODIiLCAiI0Q2NjA0RCIsDQogICAgIiNCMjE4MkIiLCAiIzY3MDAxRiINCiAgKSkoMTAwKQ0KICANCiAgcXVhbnRpbGVfYnJlYWtzIDwtIHVuaXF1ZShxdWFudGlsZShhcy52ZWN0b3IodnN0X3pzY29yZSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JzID0gc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSAxMDEpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5ybSA9IFRSVUUpKQ0KICANCiAgIyBHYXAgcG9zaXRpb24gYmV0d2VlbiBEMjEgYW5kIFQyMSBncm91cHMNCiAgbl9kMjEgPC0gbnJvdyhtZXRhX2QyMSkNCiAgZ2FwX3Bvc2l0aW9ucyA8LSBuX2QyMQ0KICANCiAgbl9nZW5lcyA8LSBsZW5ndGgoY2x1c3Rlcl9nZW5lcykNCiAgc2hvd19uYW1lcyA8LSBuX2dlbmVzIDw9IDUwDQogIA0KICBwIDwtIHBoZWF0bWFwOjpwaGVhdG1hcCgNCiAgICB2c3RfenNjb3JlLA0KICAgIGNvbG9yID0gaGVhdG1hcF9jb2xvcnMsDQogICAgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzID0gImV1Y2xpZGVhbiIsDQogICAgY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gImV1Y2xpZGVhbiIsDQogICAgY2x1c3RlcmluZ19tZXRob2QgPSAiY29tcGxldGUiLA0KICAgIGNsdXN0ZXJfcm93cyA9IFRSVUUsDQogICAgY2x1c3Rlcl9jb2xzID0gRkFMU0UsDQogICAgYW5ub3RhdGlvbl9jb2wgPSBhbm5vdGF0aW9uX2RmLA0KICAgIGFubm90YXRpb25fY29sb3JzID0gYW5ub3RhdGlvbl9jb2xvcnMsDQogICAgc2hvd19yb3duYW1lcyA9IHNob3dfbmFtZXMsDQogICAgc2hvd19jb2xuYW1lcyA9IEZBTFNFLA0KICAgIGZvbnRzaXplID0gMTAsDQogICAgZm9udHNpemVfcm93ID0gNywNCiAgICBmb250c2l6ZV9jb2wgPSA4LA0KICAgIGJvcmRlcl9jb2xvciA9IE5BLA0KICAgIHNjYWxlID0gIm5vbmUiLA0KICAgICAgICBhbm5vdGF0aW9uX2xlZ2VuZCA9IFRSVUUsDQogICAgYW5ub3RhdGlvbl9uYW1lc19jb2wgPSBGQUxTRSwNCiAgICBsZWdlbmQgPSBUUlVFLA0KICAgIGJyZWFrcyA9IHF1YW50aWxlX2JyZWFrcywNCiAgICBnYXBzX2NvbCA9IGdhcF9wb3NpdGlvbnMsDQogICAgbWFpbiA9IHBhc3RlMCgiQ2x1c3RlciAiLCBjbCwgIiAobj0iLCBuX2dlbmVzLCAiIGdlbmVzKSIpDQogICkNCiAgDQogIHByaW50KHApDQp9DQpgYGANCg0KVGhlIGZvbGxvd2luZyBjb2RlIHNhdmVzIGhlYXRtYXBzIHRvIFBORyBmaWxlcy4gU2V0IGBldmFsPVRSVUVgIHRvIHJ1bjoNCg0KYGBge3Igc3RlcDEzLWNyZWF0ZS1oZWF0bWFwcywgZXZhbD1GQUxTRSwgZWNobz1UUlVFfQ0KIyBTYXZlIGhlYXRtYXBzIHRvIGZpbGVzDQpmb3IgKGNsIGluIHNvcnQodW5pcXVlKGNsdXN0ZXJzJGN1dCkpKSB7DQogIGNsdXN0ZXJfZ2VuZXMgPC0gbmFtZXMoY2x1c3RlcnMkY3V0KVtjbHVzdGVycyRjdXQgPT0gY2xdDQogIA0KICBuX2dlbmVzIDwtIGxlbmd0aChjbHVzdGVyX2dlbmVzKQ0KICBzaG93X25hbWVzIDwtIG5fZ2VuZXMgPD0gNTANCiAgaGVpZ2h0X3ZhbCA8LSBtaW4oOCwgbWF4KDQsIG5fZ2VuZXMgKiAwLjA4KSkNCiAgDQogIGNyZWF0ZV9jbHVzdGVyX2hlYXRtYXAoDQogICAgZ2VuZV9saXN0ID0gY2x1c3Rlcl9nZW5lcywNCiAgICB2c3RfbWF0ID0gdnN0X21hdCwNCiAgICBtZXRhID0gbWQsDQogICAgY2x1c3Rlcl9pZCA9IGNsLA0KICAgIG91dHB1dF9wYXRoID0gZmlsZS5wYXRoKGhlYXRtYXBfZGlyLCBwYXN0ZTAoImNsdXN0ZXJfIiwgY2wsICJfaGVhdG1hcC5wbmciKSksDQogICAgd2lkdGggPSA1LA0KICAgIGhlaWdodCA9IGhlaWdodF92YWwsDQogICAgc2hvd19yb3duYW1lcyA9IHNob3dfbmFtZXMsDQogICAgc2hvd19jb2xuYW1lcyA9IEZBTFNFLA0KICAgIGNsdXN0ZXJfcm93cyA9IFRSVUUsDQogICAgY2x1c3Rlcl9jb2xzID0gRkFMU0UsDQogICAgZm9udHNpemVfcm93ID0gNywNCiAgICB3aW5zb3JpemVfbGltaXQgPSAzDQogICkNCn0NCmBgYA0KDQojIFN0ZXAgMTQ6IFNhdmUgUmVzdWx0cw0KDQpGaW5hbGx5LCBJIHNhdmUgdGhlIGNsdXN0ZXIgYXNzaWdubWVudHMgYW5kIGFuYWx5c2lzIG91dHB1dHMgZm9yIGRvd25zdHJlYW0gdXNlLiBEZXBlbmRpbmcgb24gdGhlIG51bWJlciBvZiBnZW5lcyB0aGF0IGFyZSB0aW1lLWRlcGVuZGVudCwgbWFTaWdQcm8gY2FuIHRha2UgMTUtMjAgbWludXRlcyB0byBydW4gYW5kIGNsdXN0ZXIgeW91ciBnZW5lcy4gVGhlcmVmb3JlIEkgcmVjb21tZW5kIHNhdmluZyB0aGUgb3V0cHV0IG9mIG1hU2lnUHJvIGltbWVkaWF0ZWx5IHNvIHRoYXQgeW91IGRvbid0IGhhdmUgdG8gcmUtcnVuIGl0IGV2ZXJ5IHRpbWUuDQoNCmBgYHtyIHN0ZXAxNC1zYXZlLCBlY2hvPVRSVUV9DQojIFNhdmUgY2x1c3RlciBhc3NpZ25tZW50cw0KcmVhZHI6OndyaXRlX2NzdihjbF9hc3NpZ24sIGZpbGUucGF0aChyZXNfZGlyLCAiY2x1c3Rlcl9hc3NpZ25tZW50cy5jc3YiKSkNCnNhdmVSRFMoY2xfYXNzaWduLCBmaWxlLnBhdGgocmVzX2RpciwgImNsdXN0ZXJfYXNzaWdubWVudHMucmRzIikpDQoNCmNhdCgiUmVzdWx0cyBzYXZlZCB0bzoiLCByZXNfZGlyLCAiXG4iKQ0KY2F0KCJGaWd1cmVzIHNhdmVkIHRvOiIsIGZpZ19kaXIsICJcbiIpDQpgYGANCg0KIyBTdW1tYXJ5DQoNCkluIHN1bW1hcnksIG1hU2lnUHJvIGlzIGEgdXNlZnVsIFIgcGFja2FnZSBmb3IgaWRlbnRpZmllZCBnZW5lcyB3aXRoIGdlbm90eXBlLXNwZWNpZmljIHRlbXBvcmFsIGV4cHJlc3Npb24gcGF0dGVybnMgYW5kIGdyb3VwZWQgdGhlbSBpbnRvIGZ1bmN0aW9uYWxseSBjb2hlcmVudCBjbHVzdGVycy4NCg0KSWYgeW91IGhhdmUgdGltZS1zZXJpZXMgYnVsayBSTkEtc2VxIGRhdGEgd2l0aCAzKyB0aW1lcG9pbnRzLCBhbmQgeW91IGhhdmUgdHdvIGdyb3VwcyBvZiBzYW1wbGVzICh3aGV0aGVyIHRoYXQgaXMgdHJlYXRtZW50IG9yIGdlbm90eXBlIG9yIHNvbWUgY29uZGl0aW9uKSwgdGhlbiBtYVNpZ1BybyBhbGxvd3MgeW91IGlkZW50aWZ5IGNsdXN0ZXJzIG9mIGdlbmVzIHdob3NlIGV4cHJlc3Npb24gb3ZlciB0aW1lIGlzIHRlbXBvcmFsbHkgLg0KDQpXaGlsZSBzb21lIG1hU2lnUHJvIGNsdXN0ZXJzIGFyZSBsYXJnZWx5IHNpbWlsYXIgaW4gZXhwcmVzc2lvIGJldHdlZW4gdGhlIHR3byBncm91cHMsIEkgdGhpbmsgdGhlIG1haW4gYmlvbG9naWNhbCBpbnNpZ2h0IGhlcmUgDQoNCkZvciBleGFtcGxlLCBjbHVzdGVyIDEwIGlzIGVucmljaGVkIGZvciB0aGUgR08gdGVybSAnY2Fub25pY2FsIFdudCBzaWduYWxpbmcgcGF0aHdheS4nIEluIHRoZSBwYXBlciB0aGF0IEkgb2J0YWluZWQgdGhpcyBkYXRhIGZyb20sIHRoZSBhdXRob3JzIGRpc2N1c3MgV250IHNpZ25hbGluZyBwYXRod2F5IGFjdGl2aXR5IGFzIGJlaW5nIGRhbXBlbmVkIG9yIGRlY3JlYXNlZCBpbiBUMjEgY29tcGFyZWQgdG8gRDIxLiBDbHVzdGVyIDEwIGNvcnJvYm9yYXRlcyB0aGlzIGNsYWltLCBhcyAuIEZ1cnRoZXJtb3JlLCB3ZSBjYW4gc2VlIHRoYXQgdGhpcyBjbHVzdGVyIGVucmljaGVkIGZvciBXbnQgc2lnbmFsaW5nIHBhdGh3YXkgZ2VuZXMgc2VlbXMgdG8gYmUgaGlnaGVzdCBpbiBleHByZXNzaW9uIGR1cmluZyB0aGUgbmV1cmFsIHByb2dlbml0b3IgcGhhc2UuIEhlbmNlLCBXbnQgc2lnbmFsaW5nIGNvdWxkIGJlIG1vZHVsYXRpbmcgdGhlIHRyYW5zaXRpb24gb2YgbmV1cmFsIHByb2dlbml0b3IgdG8gbWF0dXJlIG5ldXJvbi4gQmVjYXVzZSBleHByZXNzaW9uIG9mIHRoaXMgY2x1c3RlciBpcyBERUNSRUFTRUQgaW4gVDIxIHJlbGF0aXZlIHRvIEQyMSwgdGhpcyBzdWdnZXN0cyBwZXJoYXBzIGZhc3RlciB0cmFuc2l0aW9uIG9mIG5ldXJhbCBwcm9nZW5pdG9yIHRvIG1hdHVyZSBuZXVyb24gaW4gVDIxLiBBcyBleHBsYWluZWQgYmVmb3JlLCB0aGlzIGZpbmRpbmcgb2YgZmFzdGVyIGFjcXVpc3Rpb24gb2YgbWF0dXJlIG5ldXJvbiBpZGVudGl0eSBpcyBjb3Jyb2JvcmF0ZWQgYnkgb3RoZXIgY2x1c3RlcnMsIHN1Y2ggYXMgY2x1c3RlciA2LiANCg0KVGhlc2UgYXJlIG9idmlvdXNseSBicm9hZCBjbGFpbXMgdGhhdCBuZWVkIGZ1cnRoZXIgZXZpZGVuY2UgdG8gc3Vic3RhbnRpYXRlLCBidXQgdGhlIGdvYWwgb2YgbWFTaWdQcm8gaXMgdG8gZ2l2ZSB5b3UgZGlyZWN0aW9uIGZvciB3aGF0IGRpcmVjdGlvbnMvYXZlbnVlcyB5b3UgY2FuIHB1cnN1ZSBmb3IgeW91ciBkYXRhLg0KDQojIyBSZXN1bHRzIE92ZXJ2aWV3DQoNCmBgYHtyIGZpbmFsLXN1bW1hcnksIGVjaG89RkFMU0V9DQpzdW1tYXJ5X3RhYmxlIDwtIGRhdGEuZnJhbWUoDQogIE1ldHJpYyA9IGMoIkdlbmVzIHRlc3RlZCIsDQogICAgICAgICAgICAgIlNpZ25pZmljYW50IHRlbXBvcmFsIGdlbmVzIiwNCiAgICAgICAgICAgICAiR2Vub3R5cGUtc3BlY2lmaWMgZ2VuZXMiLA0KICAgICAgICAgICAgICJDbHVzdGVycyBpZGVudGlmaWVkIiwNCiAgICAgICAgICAgICAiQ2x1c3RlcnMgd2l0aCBHTyBlbnJpY2htZW50IiksDQogIFZhbHVlID0gYyhmb3JtYXQobnJvdyhleHByX21hdCksIGJpZy5tYXJrID0gIiwiKSwNCiAgICAgICAgICAgIHBhc3RlMChmb3JtYXQobGVuZ3RoKGZpdCRTRUxFQyksIGJpZy5tYXJrID0gIiwiKSwNCiAgICAgICAgICAgICAgICAgICAiICgiLCByb3VuZCgxMDAgKiBsZW5ndGgoZml0JFNFTEVDKSAvIG5yb3coZXhwcl9tYXQpLCAxKSwgIiUpIiksDQogICAgICAgICAgICBwYXN0ZTAoZm9ybWF0KGxlbmd0aChzaWdfZ2VuZXMpLCBiaWcubWFyayA9ICIsIiksDQogICAgICAgICAgICAgICAgICAgIiAoIiwgcm91bmQoMTAwICogbGVuZ3RoKHNpZ19nZW5lcykgLyBucm93KGV4cHJfbWF0KSwgMSksICIlKSIpLA0KICAgICAgICAgICAga19jbHVzdGVycywNCiAgICAgICAgICAgIGxlbmd0aChhbGxfY2x1c3Rlcl9nb19yZXN1bHRzKSkNCikNCg0Ka25pdHI6OmthYmxlKHN1bW1hcnlfdGFibGUsIGNvbC5uYW1lcyA9IGMoIk1ldHJpYyIsICJWYWx1ZSIpLCBhbGlnbiA9IGMoImwiLCAiciIpKQ0KYGBgDQoNCiMjIEFuYWx5c2lzIFBhcmFtZXRlcnMNCg0KYGBge3IgcGFyYW1zLXN1bW1hcnksIGVjaG89RkFMU0V9DQpwYXJhbXNfdGFibGUgPC0gZGF0YS5mcmFtZSgNCiAgUGFyYW1ldGVyID0gYygiRXhwcmVzc2lvbiBmaWx0ZXIiLCAiUG9seW5vbWlhbCBkZWdyZWUiLCAiRkRSIHRocmVzaG9sZCAocC52ZWN0b3IpIiwNCiAgICAgICAgICAgICAgICAiQWxwaGEgdGhyZXNob2xkIChULmZpdCkiLCAiUsKyIHRocmVzaG9sZCIsICJOdW1iZXIgb2YgY2x1c3RlcnMiKSwNCiAgVmFsdWUgPSBjKHBhc3RlMCgiTWVhbiBDUE0gPiAiLCBtaW5fY3BtKSwNCiAgICAgICAgICAgIHBvbHlfZGVncmVlLA0KICAgICAgICAgICAgcV90aHJlc2hvbGQsDQogICAgICAgICAgICBhbHBoYV90aHJlc2hvbGQsDQogICAgICAgICAgICByc3FfdGhyZXNob2xkLA0KICAgICAgICAgICAga19jbHVzdGVycykNCikNCg0Ka25pdHI6OmthYmxlKHBhcmFtc190YWJsZSwgY29sLm5hbWVzID0gYygiUGFyYW1ldGVyIiwgIlZhbHVlIiksIGFsaWduID0gYygibCIsICJyIikpDQpgYGANCg0KIyMgT3V0cHV0IEZpbGVzDQoNCkFsbCByZXN1bHRzIGFyZSBzYXZlZCB0bzoNCg0KLSAqKkNsdXN0ZXIgYXNzaWdubWVudHM6KiogYHJlc3VsdHMvbWFzaWdwcm8vY2x1c3Rlcl9hc3NpZ25tZW50cy5jc3ZgDQotICoqU3VtbWFyeSBzdGF0aXN0aWNzOioqIGByZXN1bHRzL21hc2lncHJvL3N1bW1hcnlfc3RhdGlzdGljcy5jc3ZgDQotICoqR08gZW5yaWNobWVudDoqKiBgcmVzdWx0cy9tYXNpZ3Byby9nb19lbnJpY2htZW50L2ANCi0gKipGaWd1cmVzOioqIGByZXN1bHRzL2ZpZ3VyZXMvbWFzaWdwcm8vYA0KDQojIyBSZWZlcmVuY2VzDQoNCjEuIENvbmVzYSBBLCBOdWVkYSBNSiwgZXQgYWwuICgyMDA2KS4gbWFTaWdQcm86IGEgbWV0aG9kIHRvIGlkZW50aWZ5IHNpZ25pZmljYW50bHkgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gcHJvZmlsZXMgaW4gdGltZS1jb3Vyc2UgbWljcm9hcnJheSBleHBlcmltZW50cy4gKkJpb2luZm9ybWF0aWNzKiwgMjIoOSk6MTA5Ni0xMTAyLiBbZG9pOjEwLjEwOTMvYmlvaW5mb3JtYXRpY3MvYnRsMDU2XShodHRwczovL2RvaS5vcmcvMTAuMTA5My9iaW9pbmZvcm1hdGljcy9idGwwNTYpDQoNCjIuIE51ZWRhIE1KLCBUYXJhem9uYSBTLCBDb25lc2EgQS4gKDIwMTQpLiBOZXh0IG1hU2lnUHJvOiB1cGRhdGluZyBtYVNpZ1BybyBiaW9jb25kdWN0b3IgcGFja2FnZSBmb3IgUk5BLXNlcSB0aW1lIHNlcmllcy4gKkJpb2luZm9ybWF0aWNzKiwgMzAoMTgpOjI1OTgtMjYwMi4gW2RvaToxMC4xMDkzL2Jpb2luZm9ybWF0aWNzL2J0dTMzM10oaHR0cHM6Ly9kb2kub3JnLzEwLjEwOTMvYmlvaW5mb3JtYXRpY3MvYnR1MzMzKQ0KDQojIFNlc3Npb24gSW5mb3JtYXRpb24NCg0KRm9yIHJlcHJvZHVjaWJpbGl0eSwgdGhlIGNvbXBsZXRlIFIgc2Vzc2lvbiBpbmZvcm1hdGlvbiBpcyBwcm92aWRlZCBiZWxvdy4NCg0KYGBge3Igc2Vzc2lvbi1pbmZvLCBlY2hvPVRSVUV9DQpzZXNzaW9uSW5mbygpDQpgYGANCg==