1 Introduction

Advances in flow and mass cytometry now enable the simultaneous measurement of more than 50 parameters per cell in millions of cells per sample. This high dimensional data poses new demands to existing analysis techniques. Conventional gating, until now the gold standard for cytometry data analysis, faces challenges in scaling for the application of high-dimensional cytometry data. Issues with conventional gating strategies include inefficiency, subjectivity, and a significant risk of missing unknown or minor cell populations. Hence, computational methods for unbiased analysis of high-dimensional cytometry data analysis are required.
Several computational tools for cytometry data analysis are available, primarily designed for the R programming language. Examples include CATALYST / CyTOF workflow, FlowSOM, flowCore, flowAI, and diffcyt. These workflows address specific aspects of high-dimensional cytometry data analysis, including handling flow cytometry standard (FCS) files, preprocessing, dimensionality reduction, clustering based on self-organizing maps, and differential abundance analysis. However, interoperability between these packages is often limited and inefficient, and methods for advanced data analysis such as data integration and trajectory analysis are scarce.
Whereas high-dimensional cytometry data analysis methods are less established, algorithms for analyzing single-cell RNA sequencing (scRNAseq) data are highly developed and offer a host of analysis possibilities. The most commonly used framework for scRNAseq data analysis is Seurat, which covers every aspect of data analysis from preprocessing to a range of downstream analyses, including dimensionality reduction and clustering. A notable advantage of scRNAseq packages is their interoperability, since most third-party tools that offer additional analysis options, such as batch correction or trajectory analysis, integrate seamlessly into the Seurat workflow. However, these scRNAseq analysis algorithms are not readily accessible for the analysis of cytometry data.
With these challenges and possibilities in mind, we developed the R package Seumetry, a flow and mass cytometry analysis framework that offers state-of-the-art analysis options across the whole data life cycle. Seumetry includes a broad range of cytometry-specific algorithms for data handling, while it seamlessly integrates with Seurat, providing access to the latest scRNAseq analysis options.

2 Description of data

To demonstrate the functionality and workflow of Seumetry, we generated a high-dimensional spectral flow cytometry dataset comprising 39 parameters of human intestinal immune cells. The test dataset consists of immune cells isolated from the intestinal mucosa of 7 adult human donors, including two anatomical layers: epithelium and lamina propria. The data was manually pre-gated on single live cells.

3 Setup

To run the vignette, these tools need to be installed additionally.

devtools::install_github('saeyslab/CytoNorm')
BiocManager::install("EnhancedVolcano")
install.packages("pheatmap")

Load libraries

library(pheatmap)
library(EnhancedVolcano)
library(Seumetry)
library(ggplot2)
library(dplyr)

Download test dataset

# zenodo url
zenodo <- "https://zenodo.org/records/11935872/"
# create data directory
dir.create("data/fcs", recursive = TRUE, showWarnings = FALSE)
# download panel and metadata
download.file(paste0(zenodo, "files/metadata.csv?download=1"), "data/metadata.csv", quiet = TRUE)
download.file(paste0(zenodo, "files/panel.csv?download=1"), "data/panel.csv", quiet = TRUE)
# download fcs files
for(file in read.csv("data/metadata.csv")$file_name)
  download.file(paste0(zenodo, "files/", file, "?download=1"), paste0("data/fcs/", file), quiet = TRUE)

Set global ggplot2 options and define theme to make beautiful plots.

# adjust fill and color globally using options
discrete_colors <- c("#5988B2", "#C9635E", "#67976B", "#C88F67", "#A583B0",
                     "#CDB86A", "#ABCCE2", "#7F007F", "#C8A5A3", "#B9E1B8",
                     "#C5807B", "#E0CDB1", "#917C6F", "#5988B2", "#D5E8AE",
                     "#CC7E78", "#B4C9E3", "#AB8BAF", "#97C496", "#5F6E9D", 
                     "#B3CC96", "#AF8B99", "#C88F67", "#C16D6B", "#8E8E8E",
                     "#5E93C2", "#FF8FBF", "#AFD192", "#8E8E8E", "#8BCF92",
                     "#5F6E9D", "#8E6A8E", "#A88169", "#9D7BAE", "#6D8D7E",
                     "#6E6E6E", "#9D4949", "#F2E89C", "#A9FF93", "#93A9C5",
                     "#B76CA8", "#D4857B", "#4C0000", "#BDBA88", "#6E6E6E",
                     "#FFDF7F", "#B2B2B2", "#366E5E", "#C6C6C6", "#DDBE8F",
                     "#C6C6C6", "#89603A", "#9C7A8D", "#7CCCBE", "#AFFF9F")
options(ggplot2.discrete.colour = discrete_colors)
options(ggplot2.discrete.fill = discrete_colors)
# define new gradient colors
gradient_colors <- colorRampPalette(c("steelblue", "slategray2", "white",
                                      "tan1", "firebrick3"))(100)
# define a theme to match plots from other packages with Seumetry plots
set_theme <- list(theme_linedraw(),
                  theme(aspect.ratio=1,
                        panel.grid.major = element_blank(),
                        panel.grid.minor = element_blank()))

4 Metadata and panel

Metadata and panel are data.frames required by Seumetry for loading FCS files, creating a Seurat object, and preprocess the data.

metadata <- read.csv("data/metadata.csv")
panel <- read.csv("data/panel.csv")

Metadata: a data.frame with at least 2 columns: “file_name” and “sample_id”. All additional columns are used as metadata columns and are added to the Seurat object.

head(metadata)

Panel: a data.frame with at least 2 columns: “fcs_colname” and “antigen”. The fcs_colname are the names of the channels in the FCS file. Antigen is the desired name for downstream analysis. Additionally, columns for transformation are required. See below (transformation) for details.

head(panel)

5 Loading FCS files

FCS files are loaded based on *.fcs files in the folder and on “file_name” column in metadata data.frame.
Each cell will receive a unique cell ID based on sample_id provided in metadata.

fcs_fs <- create_flowset("data/fcs", metadata)
All files loaded successfully.
fcs_fs
A flowSet with 14 experiments.

column names(50): FSC.A FSC.H ... eFluor.506.A Time

It is highly recommended to save the “fcs_fs” object.

6 Create Seurat object

All FCS files will be merged and metadata will be added based on sample_id and metadata data.frame. Raw data is saved in Seurat assay “fcs”. Only Channels will be kept that are present in panel data.frame and channels will be renamed from “fcs_colname” to “antigen”. Channels that were not indicated in panel data.frame are stored in Seurat assay “unused”. All panel data are stored in slot for easy access.

seu <- create_seurat(fcs_fs, panel, metadata, derandomize = FALSE)
Warning: Data is of class matrix. Coercing to dgCMatrix.Warning: Data is of class matrix. Coercing to dgCMatrix.
seu
An object of class Seurat 
50 features across 1151826 samples within 2 assays 
Active assay: fcs (39 features, 0 variable features)
 1 layer present: counts
 1 other assay present: unused

Derandomization: For mass cytometry data, it might be desired to derandomize data, which will round intensity values up to then nearest whole number (see https://biosurf.org/cytof_data_scientist.html#34_Data_transformations). This is implemented in the create_seurat function by setting derandomize to TRUE.

# plot cellnumbers per sample
plot_cellnumber(seu)

7 Preprocessing

7.1 Bead normalisation

For mass cytometry data, bead normalization may be required. This vignette uses a flow cytometry test dataset, so bead normalization is not applied. Please refer to section “Other features” for a description of bead normalization implementation.

7.2 Compensation

Compensation is based on spillover matrices that can be provided within the FCS file or as a data.frame.

There are different options how to supply the matrix. For details see compensate_data function documentation. Here, we use option 3.
- Option 1) Use external spillover matrix directly: same compensation for all files.
- Option 2) Use spillover matrix saved in FCS files: same compensation for all files.
- Option 3) Use spillover matrix saved in FCS files: compensation matrix used from individual FCS files, thus can be different for each file.

The compensate_data function will use flowCore::compensate() to compensate the raw values and write a new assay into the Seurat object called “comp”. The compensated data are saved in “counts” slot.
Warning: if multiple spillover matrices are present in FCS files, use the correct one!

Here, we use option 3 to compensate the data.

# Check different matrices using:  
names(flowCore::spillover(fcs_fs[[1]]))
[1] "SPILL"      "spillover"  "$SPILLOVER"
# In this case, spillover matrix in column 3 is the correct one.  
seu <- compensate_data(fcs_fs, seu, fcs_matrix = 3)
Warning: Data is of class matrix. Coercing to dgCMatrix.
# Check that compensation worked.  
plot1 <- plot_cyto(seu, x = "IgD", y = "CD3", assay = "fcs",
                   slot = "counts", scale = "log", rasterize = TRUE) +
  ggtitle("Uncompensated")
plot2 <- plot_cyto(seu, x = "IgD", y = "CD3", assay = "comp",
                   slot = "counts", scale = "log", rasterize = TRUE) +
  ggtitle("Compensated")
plot1 + plot2

7.3 Transformation

The following transformations are possible: arcsinh and biexp.

Transformation is performed on “counts” data of the DefaultAssay of the Seurat object. The DefaultAssay is either “fcs”, “beadnorm”, or “comp” depending on whether bead normalisaton or compensation was performed or not.

The transform_data function will return a Seurat object with transformed data written into the “data” slot.

Both arcsinh and biexp transformation can and should be used with custom parameters. These parameters should be provided in the panel data.frame, which is stored in upon Seurat object creation.

seu <- transform_data(seu, "arcsinh")
plot1 <- plot_cyto(seu, x = "CD4", y = "CD3", assay = "fcs", slot = "counts",
                   scale = "log", rasterize = TRUE) +
    ggtitle("Untransformed")
plot2 <- plot_cyto(seu, x = "CD4", y = "CD3", assay = "comp", slot = "data",
                   rasterize = TRUE) +
    ggtitle("Arcsinh transformation")
plot1 + plot2

7.4 Downsampling

Flow- and Mass cytometry data can contain millions of cells. If the number of cells differs largely between samples, it is advisable to downsample so overall differences are not driven by individual samples with high number of cells. Furthermore, downsampling can decrease computational time, if quick data exploration is desired.
It is highly recommended to save a non-downsampled Seurat object.

# set seed for downsampling for reproducibility
set.seed(42)
# downsample using Seurats subset function
seu <- subset(seu, downsample = 20000)
seu
An object of class Seurat 
89 features across 225882 samples within 3 assays 
Active assay: comp (39 features, 0 variable features)
 2 layers present: counts, data
 2 other assays present: fcs, unused
# plot cellnumbers per sample
plot_cellnumber(seu)

8 Quality control

Cytometry is an inherently noisy technology. There will be events close to the axis or artefacts due to antibody aggregation. Seumetry provides multiple quality control tools to achieve clean data for downstream analysis.

8.1 Removal of outliers

It can occur with cytometry, that some events have extremely negative or positive values. Here, we can remove these outlier events 1) by setting a manual threshold for each channel or 2) by using an automatic removal algorithm based on isolation forest.

8.1.1 Manual removal

Use a named vector to indicate threshold for each channel. The function can take positive or negative thresholds.

# check each channel to determine if they contain outlier events
plot_cyto(seu, "CD4", "CD3", style = "2d_density", assay = "comp", slot = "data")

# manually set thresholds based on cyto plots
thresholds_neg = c("CD3" = -4,
                   "CD4" = -2)
thresholds_pos = c("CD3" = 4,
                   "CD4" = 4.5)
# remove values above or below thresholds
seu_manual = remove_outliers_manual(seu, thresholds_neg)
seu_manual = remove_outliers_manual(seu_manual, thresholds_pos, negative = FALSE)
plot_cyto(seu_manual, "CD4", "CD3", style = "2d_density", assay = "comp", slot = "data")

8.1.2 Automatic removal

Outliers are detected using an isolation forest. In ungated flow cytometry data, this algorithm will mainly remove axis-near events, dead cells, and doublets.
The threshold at which score an event is regarded an outlier can be supplied, which refers to the approximate depth it takes to isolate an observation. The threshold is usually between 0.6 and 0.8, with an default threshold of 0.7. The lower the threshold, the more cells are labelled as outliers.

The function will save two features for each cell into meta.data of the Seurat Object: 1) the isolation forest score (outlier_score) and whether an event passed the threshold or not (outlier).

# identify outliers (default threshold: 0.7)
seu <- detect_outliers(seu, score_threshold = 0.7)
Found 60 outliers, which is 0.03 percent of all cells.
plot1 <- plot_cyto(seu, x = "CD4", y = "CD3", assay = "comp", slot = "data") +
    ggtitle("Before removal")
plot2 <- plot_cyto(seu, x = "CD4", y = "CD3", assay = "comp", slot = "data",
                   style = "point", color = "outlier_score") +
    ggtitle("Outlier score")
plot3 <- plot_cyto(seu, x = "CD4", y = "CD3", assay = "comp", slot = "data",
                   style = "point", color = "outlier") +
    ggtitle("Outlier")
plot4 <- plot_cyto(subset(seu, subset = outlier == FALSE), x = "CD4", y = "CD3",
                   assay = "comp", slot = "data") +
    ggtitle("After removal")
plot1 + plot2 + plot3 + plot4

Remove outliers from Seurat object.

# remove outliers from Seurat object
seu <- subset(seu, subset = outlier == FALSE)

8.2 Removal of aggregates

With high-dimensional Flow cytometry, antibody aggregates can occur. These are usually characterized by highly co-linear events that form diagonal structures in XY plots. This algorithm can identify potentially problematic channel combinations and identify aggregates in these channels.

Here is an example of such aggregates:

plot1 <- plot_cyto(seu, x = "CD28", y = "CXCR5")
plot2 <- plot_cyto(seu, x = "CXCR3", y = "CD1C")
plot1 + plot2

The first step is to identify channel combinations that potentially contain aggregates. This is done by selecting only double positive events (default: events > 1) and running a pearson correlation. The default threshold of labelling a channel potentially containing aggregates is pearson R > 0.7. The lower the threshold, the more channels are labelled as potentially containing aggregates.

The function “detect_aggregate_channels” returns a list of 2 matrices and 1 data.frame.
- Matrix 1: pearson correlation matrix.
- Matrix 2: binary correlation matrix based on threshold of pearson R (default threshold = 0.7).
- Data.frame: contains the channel combinations that passed the pearson R threshold.

problem_channels <- detect_aggregate_channels(seu, threshold = 0.7)

These correlation matrices can be plotted, for example, using a heatmap.

pheatmap::pheatmap(problem_channels[[1]], main = "Pearson R", border_color = NA)

pheatmap::pheatmap(problem_channels[[2]], main = "Pearson R>0.7", border_color = NA)

Next, we can plot the channel combinations that potentially contain aggregates and manually assess if these channels are really problematic.

plots <- list()
for(i in 1:nrow(problem_channels[[3]]))
  plots[[i]] <- plot_cyto(seu,
                          x = problem_channels[[3]][i, "Channel_1"],
                          y = problem_channels[[3]][i, "Channel_2"])
do.call(gridExtra::grid.arrange, c(plots, ncol = 4))

rm(plots)

If a channel combination does not look like it contains aggregates, remove that row from the data.frame (problem_channels[[3]]).

Next, we can detect aggregates in the problematic channels using a modified RANSAC algorithm. Each cell will get an aggregate_score, which is the number of channel combinations in which it was labelled as an aggregate. By default, potential aggregates are only labelled real aggregates if they occur in 2 or more channel combinations. The aggregate_score and whether a cell has passed the aggregate_score threshold (default >= 2) is stored in meta.data of the Seurat object.

seu <- detect_aggregates(seu, problem_channels[[3]])
Warning: Could not find suitable model for channel combination CXCR3 vs CCR6. Try reduced min_to_fit or higher max_iteration if this warning occurs in too many channel combinations.Warning: Could not find suitable model for channel combination CXCR3 vs CRTH2. Try reduced min_to_fit or higher max_iteration if this warning occurs in too many channel combinations.Warning: Could not find suitable model for channel combination IL15-2RB vs PD1. Try reduced min_to_fit or higher max_iteration if this warning occurs in too many channel combinations.Found 2936 aggregate events, which is 1.3 percent of all cells.
# Check the aggregate removal performance
plots <- list()
for(i in 1:nrow(problem_channels[[3]]))
  plots[[i]] <- plot_cyto(seu,
                          x = problem_channels[[3]][i, "Channel_1"],
                          y = problem_channels[[3]][i, "Channel_2"],
                          style = "point",
                          color = "aggregate_score")
do.call(gridExtra::grid.arrange, c(plots, ncol = 4))

rm(plots)
plots <- list()
for(i in 1:nrow(problem_channels[[3]]))
  plots[[i]] <- plot_cyto(seu,
                          x = problem_channels[[3]][i, "Channel_1"],
                          y = problem_channels[[3]][i, "Channel_2"],
                          style = "point",
                          color = "aggregate")
do.call(gridExtra::grid.arrange, c(plots, ncol = 4))

rm(plots)

If the result is satisfactory, the aggregates can be removed.

seu <- subset(seu, subset = aggregate == FALSE)

It is possible that some aggregates still remain. In that case it is advisable to fine tune the parameters of the aggregate_channels function. Alternatively, remaining artefacts may be removed by detecting outliers again using the isolation forest.

9 Dimensionality reduction & clustering

Dimensionality reduction, clustering & visualizations are accessible via the Seurat workflow. We also integrated a more classic clustering approach for cytometry data: FlowSOM.

9.1 Dimensionality reduction

Multiple reductions are possible via the Seurat workflow: PCA, UMAP or tSNE. If UMAPs and clustering do not separate celltypes well, it can help to only select features for PCA that distinguish expected cell subsets, e.g. CD3, CD4, and CD8 if working with T cells.

Furthermore, input data and data processing can be adjusted:
- No centering (data resembles raw intensities more closely) - No scaling (preserves relationship of absolute marker intensities) - Use (selected) features directly as input

Here, all features are used for scaling, centering and PCA. For UMAP 10 PCs are used.

# scale data
seu <- Seurat::ScaleData(seu, features = row.names(seu), scale = TRUE, center = TRUE)
# run PCA for all features
seu <- Seurat::RunPCA(seu, features = row.names(seu), approx = FALSE)
# run UMAP using all PCs
seu <- Seurat::RunUMAP(seu, dims = 1:10)

Integration of multiple datasets or batch correction methods based on PCA such as CCA integration or harmony can also be used (not done in this tutorial).

All Seurat visualisations are available. For more detail, see https://satijalab.org/seurat/

plot1 <- Seurat::DimPlot(seu, group.by = "sample_id") + set_theme
Rasterizing points since number of points exceeds 100,000.
To disable this behavior set `raster=FALSE`
plot2 <- Seurat::DimPlot(seu, group.by = "layer") + set_theme
Rasterizing points since number of points exceeds 100,000.
To disable this behavior set `raster=FALSE`
plot1 + plot2

Seurat::VlnPlot(seu, features = c("CD8", "CD4"), pt.size = 0) & set_theme & RotatedAxis()
Rasterizing points since number of points exceeds 100,000.
To disable this behavior set `raster=FALSE`
Rasterizing points since number of points exceeds 100,000.
To disable this behavior set `raster=FALSE`

It is recommended to use a custom colorpalette when using UMAP to plot expression of markers.

Seurat::FeaturePlot(seu, features = c("CD8", "CD4")) &
  scale_color_gradientn(colors = gradient_colors) & set_theme
Rasterizing points since number of points exceeds 100,000.
To disable this behavior set `raster=FALSE`
Rasterizing points since number of points exceeds 100,000.
To disable this behavior set `raster=FALSE`
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

9.2 Clustering

For clustering, multiple methods such as Louvain algorithm are accessible via Seurat:

# run clustering using 10 PCs
seu <- Seurat::FindNeighbors(seu, dims = 1:10)
Computing nearest neighbor graph
Computing SNN
seu <- Seurat::FindClusters(seu, resolution = 0.5)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 222886
Number of edges: 4785090

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8505
Number of communities: 12
Elapsed time: 173 seconds
Seurat::DimPlot(seu, group.by = "seurat_clusters") + set_theme
Rasterizing points since number of points exceeds 100,000.
To disable this behavior set `raster=FALSE`

We also implemented a FlowSOM and metaclustering via FlowSOM R package

# run flowSOM and metaclustering
seu <- run_FlowSOM(seu, metaclusters = 10, xdim = 10, ydim = 10)
Seurat::DimPlot(seu, group.by = "SOM_cl") + set_theme

Seurat::DimPlot(seu, group.by = "SOM_metacl") + set_theme
Rasterizing points since number of points exceeds 100,000.
To disable this behavior set `raster=FALSE`

9.3 Cluster markers

Not only visualisations, but also other Seurat features are fully functional. For example, FindAllMarkers can be used to help annotate clusters.

markers <- Seurat::FindAllMarkers(seu, group.by = "seurat_clusters", only.pos = TRUE)
top5 = markers %>% group_by(cluster) %>% top_n(n = 5, wt = avg_log2FC)
Seurat::DotPlot(seu, unique(top5$gene), assay = "comp") + 
  scale_color_gradientn(colors = gradient_colors) +
  theme_linedraw() + theme(panel.grid.major = element_blank(),
                           panel.grid.minor = element_blank()) +
  RotatedAxis()
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

10 Differential abundance

Differential abundance (DA) analysis is implemented in the Seumetry workflow based on the edgeR package. DA is done using a generalized linear model (GLM) to model number of cells per group given attribute/feature x condition (e.g. seurat_clusters x treatment) and a likelihood ratio rest (LRT) to make a contrast between all conditions.

Using the function with “check_coef = TRUE” will return all coefficients to design the proper contrast for the DA analysis.

# first check the contrasts
differential_abundance(seu,
                       attribute = "seurat_clusters",
                       group_by = "sample_id",
                       formula = as.formula("~0+layer"),
                       check_coef = TRUE)
[1] "layerIEL" "layerLPL"

Here, we compare IEL vs LPL using a contrast of c(1, -1).

# calculate differential abundance: UC vs healthy
da_res <- differential_abundance(seu,
                                 attribute = "seurat_clusters",
                                 group_by = "sample_id",
                                 formula = as.formula("~0+layer"),
                                 contrast = c(1, -1))
head(da_res)

The results can be exported as a table and/or plotted, for example, using EnhancedVolcano.

EnhancedVolcano::EnhancedVolcano(da_res,
                                 lab = rownames(da_res),
                                 drawConnectors = TRUE,
                                 x = "logFC",
                                 y = "FDR",
                                 title = "IEL vs LPL",
                                 pCutoff = 0.1,
                                 FCcutoff = 0.1,
                                 ylim = c(0, 2.5),
                                 xlim = c(-6, 6)) + set_theme

11 Differential expression

In addition to Seurat “FindMarkers” function, Seumetry also includes a feature to find differential expression (DE) of markers based on a pseudobulk analysis. To this end, the median fluorescent intensity is calculated using the Seumetry function “median_expression”. The DE analysis is based on the limma package and a linear model is fit followed by empirical Bayes statistics for differential expression (eBayes).

de_res <- DE_pseudobulk(seu,
                        fixed_vars = "layer",
                        contrast = "layerIEL-layerLPL")
head(de_res)

The results can be exported as a table and/or plotted, for example, using EnhancedVolcano.

EnhancedVolcano::EnhancedVolcano(de_res,
                                 lab = rownames(de_res),
                                 drawConnectors = TRUE,
                                 x = "logFC",
                                 y = "P.Value",
                                 title = "IEL vs LPL",
                                 pCutoff = 0.1,
                                 FCcutoff = 0.1,
                                 xlim = c(-0.5, 0.5),
                                 ylim = c(0, 2)) + set_theme

12 Additional visualizations

12.1 Cytometry-like plots

Sometimes it is useful to create cytometry-like plots such as density plots. Seumetry includes various plotting functionalities using “plot_cyto”.

plot_cyto(seu, x = "CD3", y = "CD4")

plot_cyto(seu, x = "CD3", y = "CD4", style = "point", color = "sample_id")

plot_cyto(seu, x = "CD3", style = "density", color = "sample_id")

12.2 Sample-level PCA

A principal component analysis (PCA) based on median marker expression per sample can give a good impression of differences between conditions or replicates. Seumetry includes the “plot_pca” function to compute a sample-level PCA. The PCA can also be grouped by other factors than sample (based on Seurat Object meta.data).

plot_pca(seu)

plot_pca(seu, group_by = "sample_id", color = "layer")

12.3 Frequency plots

Seumetry includes a function to visualise the frequency or proportion of cells based on metadata columns. For example, it can be useful to assess whether the frequency of clusters is stable across different samples or differents between conditions.

plot_frequency(seu, "seurat_clusters", "sample_id")

plot_frequency(seu, "seurat_clusters", "layer")

12.4 Cell numbers

plot_cellnumber(seu, "sample_id")

13 Other features

13.1 Bead normalisation

The test dataset used throughout this vignette is a flow cytometry dataset, bead normalisation is only available for mass cytometry data. Here, we implemented bead normalisation by using a wrapper function for the normCytof() function of the CATALYST package. To showcase the bead normalisation implementation in Seumetry, we use the raw_data provided by CATALYST.

library(CATALYST)
data("raw_data")

Now we need to prepare a panel and add cell IDs to load data into Seumetry.

# prepare panel dataframe
panel_cat <- data.frame("fcs_colname" = unname(flowCore::parameters(raw_data[[1]])@data[["name"]]),
                        "antigen" = unname(flowCore::parameters(raw_data[[1]])@data[["desc"]]))
panel_cat <- panel_cat[!is.na(panel_cat$antigen),]
# give cells an ID (required for Seurat Object creation)
row.names(raw_data[[1]]@exprs) <- paste0("raw_data_1_", 1:nrow(raw_data[[1]]@exprs))
row.names(raw_data[[2]]@exprs) <- paste0("raw_data_2_", 1:nrow(raw_data[[2]]@exprs))
# create seurat + transform (cofactor 5 default)
seu_cat <- create_seurat(raw_data, panel_cat)
Warning: Data is of class matrix. Coercing to dgCMatrix.Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')Warning: Data is of class matrix. Coercing to dgCMatrix.
# to identify beads, normCytof() requires transformed data
seu_cat <- transform_data(seu_cat, "arcsinh")

Finally we can perform bead normalisation. It will use the DefaultAssay of the Seurat Object and save normalised raw intensities in “beadnorm” assay. Transformation should be done afterwards. Based on CATALYST documentation, parameter k refers to “median window used for bead smoothing” and affects visualizations only.

seu_cat <- bead_norm(seu_cat, beads = "dvs", plot_res = TRUE, k = 50)
Identifying beads...
Warning: NaNs producedWarning: NaNs producedWarning: NaNs producedWarning: NaNs producedWarning: NaNs producedWarning: NaNs producedWarning: NaNs producedWarning: NaNs producedWarning: NaNs producedWarning: NaNs producedWarning: NaNs producedWarning: NaNs producedWarning: NaNs producedWarning: NaNs producedComputing normalization factors...

Furthermore, the returned Seurat Object contains two new metadata columns:
- beads: events that likely represent beads.
- bead_doublet: events that likely represent beads OR bead-cell doublets.
To remove beads and bead doublets from the object, you can use subset.

seu_cat <- subset(seu_cat, subset = bead_doublet == FALSE)
seu_cat
An object of class Seurat 
123 features across 4851 samples within 3 assays 
Active assay: beadnorm (56 features, 0 variable features)
 2 layers present: counts, data
 2 other assays present: fcs, unused

13.2 Integration of third-party tools

13.2.1 Conversion of Seurat Object

To allow integration with a wide variety of single-cell and cytometry libraries, we implemented a function (convert_seurat) to convert Seurat Objects to SingleCellExperiment, flowFrame, and flowSet objects. These can be used, for example, with batch correction methods (cytoNorm, cyCombine, gaussNorm) or other third-party libraries (diffcyt, CATALYST).

fs <- convert_seurat(seu, to = "FS", split_by = "sample_id")
fs
A flowSet with 14 experiments.

column names(39): IL15RA CD27 ... CD11C IgD
fs <- convert_seurat(seu, to = "FF")
fs
flowFrame object 'FlowFrame from Seurat'
with 222886 cells and 39 observables:
       name               desc     range  minRange  maxRange
$P1  IL15RA              APC.A         6  -3.54119         5
$P2    CD27          APC.Cy7.A         5  -2.93624         4
$P3    CCR7     APC.Fire.810.A         5  -2.09615         4
$P4   CD127         APC.R700.A         6  -1.83344         5
$P5    CD1C  Alexa.Fluor.647.A         7  -2.89173         6
...     ...                ...       ...       ...       ...
$P35   CD19    Spark.NIR.685.A         5  -3.30207         4
$P36  CD123 Super.Bright.436.A         7  -3.30280         6
$P37    CD4     cFluor.YG584.A         5  -2.38044         4
$P38  CD11C       eFluor.450.A         6  -2.44146         5
$P39    IgD       eFluor.506.A         6  -3.25182         5
290 keywords are stored in the 'description' slot
sce <- convert_seurat(seu, to = "SCE")
sce
class: SingleCellExperiment 
dim: 39 222886 
metadata(0):
assays(1): counts
rownames(39): IL15RA CD27 ... CD11C IgD
rowData names(6): fcs_colname antigen ... biexp_pos biexp_width
colnames(222886): 1_Ileum_IEL_0 1_Ileum_IEL_6 ... 7_Ileum_LPL_8968 7_Ileum_LPL_8969
colData names(20): orig.ident nCount_fcs ... SOM_cl SOM_metacl
reducedDimNames(2): pca umap
mainExpName: NULL
altExpNames(0):

13.2.2 Example integration: CytoNorm

Here, we show an example of integrating a third-party tool. We use CytoNorm as an example, as it is widely used as a cytometry batch correction method.

CytoNorm is used for batch correction, but just to showcase the integration with Seumetry, we will use it as a donor- or sample normalization method. To this end, we will pool a subset of cells from each sample and use it as a reference sample. In a second step, we will normalize all samples based on the model trained with the reference sample.

First we create a flowSet of compensated and normalized intensities from a subset of cells from each sample. We split the samples by mucosal layer. This will be used as a reference for CytoNorm. We also create a flowSet of all samples.

# subset Seurat object
seu_sub <- subset(seu, downsample = 1000)
# create reference FlowSet
fs_ref <- convert_seurat(seu_sub, to = "FS", split_by = "layer",
                         assay = "comp", slot = "data")
# create FlowSet of all samples
fs <- convert_seurat(seu, to = "FS", split_by = "sample_id",
                     assay = "comp", slot = "data")

Next, we train the CytoNorm model.

# train model
model <- CytoNorm::CytoNorm.train(files = fs_ref,
                                  labels = c("IEL", "LPL"),
                                  channels = row.names(seu),
                                  transformList = NULL,
                                  plot = TRUE,
                                  seed = 42)
Warning: Reusing FlowSOM result previously saved at ./tmp/CytoNorm_FlowSOM.RDS. 
         If this was not intended, one can either specify another outputDir, 
         make use of the recompute parameter or move the FlowSOM object in the 
         file manager.

Finally, we can normalize all samples based on this model. CytoNorm will write FCS files for each normalized sample.

# run normalization
fs_norm <- CytoNorm::CytoNorm.normalize(model = model,
                             files = fs,
                             labels = rep(c("IEL", "LPL"), 7),
                             transformList = NULL,
                             prefix = "",
                             transformList.reverse = NULL, 
                             outputDir = "data/cytonorm")
Warning: cannot remove file 'data/cytonorm/Norm_1_Ileum_IEL_fsom10.fcs', reason 'No such file or directory'Warning: cannot remove file 'data/cytonorm/Norm_2_Ileum_IEL_fsom10.fcs', reason 'No such file or directory'Warning: cannot remove file 'data/cytonorm/Norm_3_Ileum_LPL_fsom10.fcs', reason 'No such file or directory'Warning: cannot remove file 'data/cytonorm/Norm_4_Ileum_IEL_fsom10.fcs', reason 'No such file or directory'Warning: cannot remove file 'data/cytonorm/Norm_4_Ileum_LPL_fsom10.fcs', reason 'No such file or directory'Warning: cannot remove file 'data/cytonorm/Norm_6_Ileum_LPL_fsom8.fcs', reason 'No such file or directory'

The resulting normalized FCS files can be loaded, and the intensities can be added to a new assay of the Seurat object and visualized.

# name of FCS channels was changed during conversion; adjust panel accordingly
panel_new <- panel
panel_new$fcs_colname <- panel_new$antigen
# create Seurat object from flowSet of normalized data
seu_norm <- create_seurat(fs_norm, panel_new, metadata, derandomize = FALSE)
# add normalized intensities to other Seurat object
seu[["cytonorm"]] <- CreateAssayObject(data = GetAssayData(seu_norm))
plot1 <- plot_cyto(seu, x = "CD4", assay = "comp", slot = "data",
                   style = "density", color = "sample_id") +
  ggtitle("Unnormalized")
plot2 <- plot_cyto(seu, x = "CD4", assay = "cytonorm", slot = "data",
                   style = "density", color = "sample_id") +
  ggtitle("Normalized")
plot1 + plot2

13.3 Median fluorescent intensity

The Seumetry function “median_expression” can be used similar to Seurat “AverageExpression” function, but uses the median instead of the average. This function can be useful, for example, to plot median expression per cluster or condition.

sample_mfi <- median_expression(seu, group_by = "sample_id")
head(sample_mfi)
       1_Ileum_IEL 1_Ileum_LPL 2_Ileum_IEL 2_Ileum_LPL  3_Ileum_IEL 3_Ileum_LPL 4_Ileum_IEL  4_Ileum_LPL
IL15RA -0.17231694 -0.20061548  0.08954173  0.11188327 -0.067248831 -0.09096448 -0.10834009 -0.171693316
CD27   -0.02178888 -0.05438485  0.01792439 -0.03760991  0.005206601 -0.01125733  0.02081389  0.002322309
CCR7   -0.06622177 -0.12757206 -0.03389536 -0.04036648 -0.041590060 -0.03376181 -0.04065862 -0.049216457
CD127   0.88379189  1.30317430  0.61188462  0.87371806  0.627032455  0.73614481  0.63483715  0.674048659
CD1C    0.09221767  0.11356827 -0.18390634 -0.18468277 -0.046830410 -0.03263464  0.01583101  0.095398638
CD141  -0.27618478 -0.67456049 -0.05672522 -0.13408594  0.117741572  0.15489495 -0.23034196 -0.247730611
       5_Ileum_IEL 5_Ileum_LPL  6_Ileum_IEL 6_Ileum_LPL 7_Ileum_IEL 7_Ileum_LPL
IL15RA -0.10544559 -0.15079287 -0.127468283 -0.13495539 -0.12421760 -0.21286077
CD27    0.01027567  0.09966123 -0.008577949 -0.01004190 -0.01960903 -0.02484947
CCR7   -0.08166193 -0.02445750 -0.041674662 -0.07653837 -0.06958973 -0.12145540
CD127   0.45256332  0.16745530  0.079980064  0.90290669  0.28859922  0.16958058
CD1C    0.02178680  0.27022532  0.315312894  0.27721884  0.20944315  0.32803206
CD141  -0.64149652 -0.51838910 -0.377463137 -0.42756710 -0.38601093 -0.48452998

13.4 Export of FCS files

Furthermore, sometimes it can be useful to use external software that relies on FCS files. For this purpose, Seumetry includes a function to export FCS files. This can be done, for example, to visualize cells of a specific cluster with external cytometry software.

# subset Seurat object to specific cluster
seu_sub <- subset(seu, subset = seurat_clusters == 1)
# export FCS file containing cells from this cluster
export_fcs(seu_sub, filename = "cluster_1.fcs", assay = "fcs", slot = "counts")
LS0tCnRpdGxlOiAiU2V1bWV0cnkiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQpkYXRlOiAnQ29tcGlsZWQ6IGByIFN5cy5EYXRlKClgJwotLS0KCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICB0aWR5ID0gVFJVRSwKICBtZXNzYWdlID0gRkFMU0UsCiAgd2FybmluZyA9IEZBTFNFLAogIGVycm9yID0gVFJVRQopCmdldHdkKCkKYGBgCgojIEludHJvZHVjdGlvbgpBZHZhbmNlcyBpbiBmbG93IGFuZCBtYXNzIGN5dG9tZXRyeSBub3cgZW5hYmxlIHRoZSBzaW11bHRhbmVvdXMgbWVhc3VyZW1lbnQgb2YgbW9yZSB0aGFuIDUwIHBhcmFtZXRlcnMgcGVyIGNlbGwgaW4gbWlsbGlvbnMgb2YgY2VsbHMgcGVyIHNhbXBsZS4gVGhpcyBoaWdoIGRpbWVuc2lvbmFsIGRhdGEgcG9zZXMgbmV3IGRlbWFuZHMgdG8gZXhpc3RpbmcgYW5hbHlzaXMgdGVjaG5pcXVlcy4gQ29udmVudGlvbmFsIGdhdGluZywgdW50aWwgbm93IHRoZSBnb2xkIHN0YW5kYXJkIGZvciBjeXRvbWV0cnkgZGF0YSBhbmFseXNpcywgZmFjZXMgY2hhbGxlbmdlcyBpbiBzY2FsaW5nIGZvciB0aGUgYXBwbGljYXRpb24gb2YgaGlnaC1kaW1lbnNpb25hbCBjeXRvbWV0cnkgZGF0YS4gSXNzdWVzIHdpdGggY29udmVudGlvbmFsIGdhdGluZyBzdHJhdGVnaWVzIGluY2x1ZGUgaW5lZmZpY2llbmN5LCBzdWJqZWN0aXZpdHksIGFuZCBhIHNpZ25pZmljYW50IHJpc2sgb2YgbWlzc2luZyB1bmtub3duIG9yIG1pbm9yIGNlbGwgcG9wdWxhdGlvbnMuIEhlbmNlLCBjb21wdXRhdGlvbmFsIG1ldGhvZHMgZm9yIHVuYmlhc2VkIGFuYWx5c2lzIG9mIGhpZ2gtZGltZW5zaW9uYWwgY3l0b21ldHJ5IGRhdGEgYW5hbHlzaXMgYXJlIHJlcXVpcmVkLiAgClNldmVyYWwgY29tcHV0YXRpb25hbCB0b29scyBmb3IgY3l0b21ldHJ5IGRhdGEgYW5hbHlzaXMgYXJlIGF2YWlsYWJsZSwgcHJpbWFyaWx5IGRlc2lnbmVkIGZvciB0aGUgUiBwcm9ncmFtbWluZyBsYW5ndWFnZS4gRXhhbXBsZXMgaW5jbHVkZQpbQ0FUQUxZU1RdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvaHRtbC9DQVRBTFlTVC5odG1sKSAvIApbQ3lUT0Ygd29ya2Zsb3ddKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL3dvcmtmbG93cy9odG1sL2N5dG9mV29ya2Zsb3cuaHRtbCksCltGbG93U09NXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9iaW9jL2h0bWwvRmxvd1NPTS5odG1sKSwKW2Zsb3dDb3JlXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9iaW9jL2h0bWwvZmxvd0NvcmUuaHRtbCksCltmbG93QUldKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvaHRtbC9mbG93QUkuaHRtbCksCmFuZCBbZGlmZmN5dF0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy9odG1sL2RpZmZjeXQuaHRtbCkuClRoZXNlIHdvcmtmbG93cyBhZGRyZXNzIHNwZWNpZmljIGFzcGVjdHMgb2YgaGlnaC1kaW1lbnNpb25hbCBjeXRvbWV0cnkgZGF0YSBhbmFseXNpcywgaW5jbHVkaW5nIGhhbmRsaW5nIGZsb3cgY3l0b21ldHJ5IHN0YW5kYXJkIChGQ1MpIGZpbGVzLCBwcmVwcm9jZXNzaW5nLCBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24sIGNsdXN0ZXJpbmcgYmFzZWQgb24gc2VsZi1vcmdhbml6aW5nIG1hcHMsIGFuZCBkaWZmZXJlbnRpYWwgYWJ1bmRhbmNlIGFuYWx5c2lzLiBIb3dldmVyLCBpbnRlcm9wZXJhYmlsaXR5IGJldHdlZW4gdGhlc2UgcGFja2FnZXMgaXMgb2Z0ZW4gbGltaXRlZCBhbmQgaW5lZmZpY2llbnQsIGFuZCBtZXRob2RzIGZvciBhZHZhbmNlZCBkYXRhIGFuYWx5c2lzIHN1Y2ggYXMgZGF0YSBpbnRlZ3JhdGlvbiBhbmQgdHJhamVjdG9yeSBhbmFseXNpcyBhcmUgc2NhcmNlLiAgCldoZXJlYXMgaGlnaC1kaW1lbnNpb25hbCBjeXRvbWV0cnkgZGF0YSBhbmFseXNpcyBtZXRob2RzIGFyZSBsZXNzIGVzdGFibGlzaGVkLCBhbGdvcml0aG1zIGZvciBhbmFseXppbmcgc2luZ2xlLWNlbGwgUk5BIHNlcXVlbmNpbmcgKHNjUk5Bc2VxKSBkYXRhIGFyZSBoaWdobHkgZGV2ZWxvcGVkIGFuZCBvZmZlciBhIGhvc3Qgb2YgYW5hbHlzaXMgcG9zc2liaWxpdGllcy4gVGhlIG1vc3QgY29tbW9ubHkgdXNlZCBmcmFtZXdvcmsgZm9yIHNjUk5Bc2VxIGRhdGEgYW5hbHlzaXMgaXMgW1NldXJhdF0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC8pLCB3aGljaCBjb3ZlcnMgZXZlcnkgYXNwZWN0IG9mIGRhdGEgYW5hbHlzaXMgZnJvbSBwcmVwcm9jZXNzaW5nIHRvIGEgcmFuZ2Ugb2YgZG93bnN0cmVhbSBhbmFseXNlcywgaW5jbHVkaW5nIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBhbmQgY2x1c3RlcmluZy4gQSBub3RhYmxlIGFkdmFudGFnZSBvZiBzY1JOQXNlcSBwYWNrYWdlcyBpcyB0aGVpciBpbnRlcm9wZXJhYmlsaXR5LCBzaW5jZSBtb3N0IHRoaXJkLXBhcnR5IHRvb2xzIHRoYXQgb2ZmZXIgYWRkaXRpb25hbCBhbmFseXNpcyBvcHRpb25zLCBzdWNoIGFzIGJhdGNoIGNvcnJlY3Rpb24gb3IgdHJhamVjdG9yeSBhbmFseXNpcywgaW50ZWdyYXRlIHNlYW1sZXNzbHkgaW50byB0aGUgU2V1cmF0IHdvcmtmbG93LiBIb3dldmVyLCB0aGVzZSBzY1JOQXNlcSBhbmFseXNpcyBhbGdvcml0aG1zIGFyZSBub3QgcmVhZGlseSBhY2Nlc3NpYmxlIGZvciB0aGUgYW5hbHlzaXMgb2YgY3l0b21ldHJ5IGRhdGEuICAKV2l0aCB0aGVzZSBjaGFsbGVuZ2VzIGFuZCBwb3NzaWJpbGl0aWVzIGluIG1pbmQsIHdlIGRldmVsb3BlZCB0aGUgUiBwYWNrYWdlIFNldW1ldHJ5LCBhIGZsb3cgYW5kIG1hc3MgY3l0b21ldHJ5IGFuYWx5c2lzIGZyYW1ld29yayB0aGF0IG9mZmVycyBzdGF0ZS1vZi10aGUtYXJ0IGFuYWx5c2lzIG9wdGlvbnMgYWNyb3NzIHRoZSB3aG9sZSBkYXRhIGxpZmUgY3ljbGUuIFNldW1ldHJ5IGluY2x1ZGVzIGEgYnJvYWQgcmFuZ2Ugb2YgY3l0b21ldHJ5LXNwZWNpZmljIGFsZ29yaXRobXMgZm9yIGRhdGEgaGFuZGxpbmcsIHdoaWxlIGl0IHNlYW1sZXNzbHkgaW50ZWdyYXRlcyB3aXRoIFNldXJhdCwgcHJvdmlkaW5nIGFjY2VzcyB0byB0aGUgbGF0ZXN0IHNjUk5Bc2VxIGFuYWx5c2lzIG9wdGlvbnMuICAKCiMgRGVzY3JpcHRpb24gb2YgZGF0YQpUbyBkZW1vbnN0cmF0ZSB0aGUgZnVuY3Rpb25hbGl0eSBhbmQgd29ya2Zsb3cgb2YgU2V1bWV0cnksIHdlIGdlbmVyYXRlZCBhIGhpZ2gtZGltZW5zaW9uYWwgc3BlY3RyYWwgZmxvdyBjeXRvbWV0cnkgZGF0YXNldCBjb21wcmlzaW5nIDM5IHBhcmFtZXRlcnMgb2YgaHVtYW4gaW50ZXN0aW5hbCBpbW11bmUgY2VsbHMuIFRoZSB0ZXN0IGRhdGFzZXQgY29uc2lzdHMgb2YgaW1tdW5lIGNlbGxzIGlzb2xhdGVkIGZyb20gdGhlIGludGVzdGluYWwgbXVjb3NhIG9mIDcgYWR1bHQgaHVtYW4gZG9ub3JzLCBpbmNsdWRpbmcgdHdvIGFuYXRvbWljYWwgbGF5ZXJzOiBlcGl0aGVsaXVtIGFuZCBsYW1pbmEgcHJvcHJpYS4gVGhlIGRhdGEgd2FzIG1hbnVhbGx5IHByZS1nYXRlZCBvbiBzaW5nbGUgbGl2ZSBjZWxscy4KCiMgU2V0dXAKVG8gcnVuIHRoZSB2aWduZXR0ZSwgdGhlc2UgdG9vbHMgbmVlZCB0byBiZSBpbnN0YWxsZWQgYWRkaXRpb25hbGx5LgpgYGB7ciBpbnN0YWxsLWxpYnJhcmllcywgZXZhbD1GQUxTRX0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCdzYWV5c2xhYi9DeXRvTm9ybScpCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJFbmhhbmNlZFZvbGNhbm8iKQppbnN0YWxsLnBhY2thZ2VzKCJwaGVhdG1hcCIpCmBgYAoKTG9hZCBsaWJyYXJpZXMKYGBge3Igc2V0dXB9CmxpYnJhcnkocGhlYXRtYXApCmxpYnJhcnkoRW5oYW5jZWRWb2xjYW5vKQpsaWJyYXJ5KFNldW1ldHJ5KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZHBseXIpCmBgYAoKRG93bmxvYWQgdGVzdCBkYXRhc2V0CmBgYHtyIGRvd25sb2FkLWRhdGF9CiMgemVub2RvIHVybAp6ZW5vZG8gPC0gImh0dHBzOi8vemVub2RvLm9yZy9yZWNvcmRzLzExOTM1ODcyLyIKIyBjcmVhdGUgZGF0YSBkaXJlY3RvcnkKZGlyLmNyZWF0ZSgiZGF0YS9mY3MiLCByZWN1cnNpdmUgPSBUUlVFLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKIyBkb3dubG9hZCBwYW5lbCBhbmQgbWV0YWRhdGEKZG93bmxvYWQuZmlsZShwYXN0ZTAoemVub2RvLCAiZmlsZXMvbWV0YWRhdGEuY3N2P2Rvd25sb2FkPTEiKSwgImRhdGEvbWV0YWRhdGEuY3N2IiwgcXVpZXQgPSBUUlVFKQpkb3dubG9hZC5maWxlKHBhc3RlMCh6ZW5vZG8sICJmaWxlcy9wYW5lbC5jc3Y/ZG93bmxvYWQ9MSIpLCAiZGF0YS9wYW5lbC5jc3YiLCBxdWlldCA9IFRSVUUpCiMgZG93bmxvYWQgZmNzIGZpbGVzCmZvcihmaWxlIGluIHJlYWQuY3N2KCJkYXRhL21ldGFkYXRhLmNzdiIpJGZpbGVfbmFtZSkKICBkb3dubG9hZC5maWxlKHBhc3RlMCh6ZW5vZG8sICJmaWxlcy8iLCBmaWxlLCAiP2Rvd25sb2FkPTEiKSwgcGFzdGUwKCJkYXRhL2Zjcy8iLCBmaWxlKSwgcXVpZXQgPSBUUlVFKQpgYGAKClNldCBnbG9iYWwgZ2dwbG90MiBvcHRpb25zIGFuZCBkZWZpbmUgdGhlbWUgdG8gbWFrZSBiZWF1dGlmdWwgcGxvdHMuCmBgYHtyIGdsb2JhbC1vcHRpb25zfQojIGFkanVzdCBmaWxsIGFuZCBjb2xvciBnbG9iYWxseSB1c2luZyBvcHRpb25zCmRpc2NyZXRlX2NvbG9ycyA8LSBjKCIjNTk4OEIyIiwgIiNDOTYzNUUiLCAiIzY3OTc2QiIsICIjQzg4RjY3IiwgIiNBNTgzQjAiLAogICAgICAgICAgICAgICAgICAgICAiI0NEQjg2QSIsICIjQUJDQ0UyIiwgIiM3RjAwN0YiLCAiI0M4QTVBMyIsICIjQjlFMUI4IiwKICAgICAgICAgICAgICAgICAgICAgIiNDNTgwN0IiLCAiI0UwQ0RCMSIsICIjOTE3QzZGIiwgIiM1OTg4QjIiLCAiI0Q1RThBRSIsCiAgICAgICAgICAgICAgICAgICAgICIjQ0M3RTc4IiwgIiNCNEM5RTMiLCAiI0FCOEJBRiIsICIjOTdDNDk2IiwgIiM1RjZFOUQiLCAKICAgICAgICAgICAgICAgICAgICAgIiNCM0NDOTYiLCAiI0FGOEI5OSIsICIjQzg4RjY3IiwgIiNDMTZENkIiLCAiIzhFOEU4RSIsCiAgICAgICAgICAgICAgICAgICAgICIjNUU5M0MyIiwgIiNGRjhGQkYiLCAiI0FGRDE5MiIsICIjOEU4RThFIiwgIiM4QkNGOTIiLAogICAgICAgICAgICAgICAgICAgICAiIzVGNkU5RCIsICIjOEU2QThFIiwgIiNBODgxNjkiLCAiIzlEN0JBRSIsICIjNkQ4RDdFIiwKICAgICAgICAgICAgICAgICAgICAgIiM2RTZFNkUiLCAiIzlENDk0OSIsICIjRjJFODlDIiwgIiNBOUZGOTMiLCAiIzkzQTlDNSIsCiAgICAgICAgICAgICAgICAgICAgICIjQjc2Q0E4IiwgIiNENDg1N0IiLCAiIzRDMDAwMCIsICIjQkRCQTg4IiwgIiM2RTZFNkUiLAogICAgICAgICAgICAgICAgICAgICAiI0ZGREY3RiIsICIjQjJCMkIyIiwgIiMzNjZFNUUiLCAiI0M2QzZDNiIsICIjRERCRThGIiwKICAgICAgICAgICAgICAgICAgICAgIiNDNkM2QzYiLCAiIzg5NjAzQSIsICIjOUM3QThEIiwgIiM3Q0NDQkUiLCAiI0FGRkY5RiIpCm9wdGlvbnMoZ2dwbG90Mi5kaXNjcmV0ZS5jb2xvdXIgPSBkaXNjcmV0ZV9jb2xvcnMpCm9wdGlvbnMoZ2dwbG90Mi5kaXNjcmV0ZS5maWxsID0gZGlzY3JldGVfY29sb3JzKQojIGRlZmluZSBuZXcgZ3JhZGllbnQgY29sb3JzCmdyYWRpZW50X2NvbG9ycyA8LSBjb2xvclJhbXBQYWxldHRlKGMoInN0ZWVsYmx1ZSIsICJzbGF0ZWdyYXkyIiwgIndoaXRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidGFuMSIsICJmaXJlYnJpY2szIikpKDEwMCkKIyBkZWZpbmUgYSB0aGVtZSB0byBtYXRjaCBwbG90cyBmcm9tIG90aGVyIHBhY2thZ2VzIHdpdGggU2V1bWV0cnkgcGxvdHMKc2V0X3RoZW1lIDwtIGxpc3QodGhlbWVfbGluZWRyYXcoKSwKICAgICAgICAgICAgICAgICAgdGhlbWUoYXNwZWN0LnJhdGlvPTEsCiAgICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpKQpgYGAKCiMgTWV0YWRhdGEgYW5kIHBhbmVsCk1ldGFkYXRhIGFuZCBwYW5lbCBhcmUgZGF0YS5mcmFtZXMgcmVxdWlyZWQgYnkgU2V1bWV0cnkgZm9yIGxvYWRpbmcgRkNTIGZpbGVzLApjcmVhdGluZyBhIFNldXJhdCBvYmplY3QsIGFuZCBwcmVwcm9jZXNzIHRoZSBkYXRhLgpgYGB7ciBsb2FkLW1ldGEtcGFuZWx9Cm1ldGFkYXRhIDwtIHJlYWQuY3N2KCJkYXRhL21ldGFkYXRhLmNzdiIpCnBhbmVsIDwtIHJlYWQuY3N2KCJkYXRhL3BhbmVsLmNzdiIpCmBgYAoKTWV0YWRhdGE6IGEgZGF0YS5mcmFtZSB3aXRoIGF0IGxlYXN0IDIgY29sdW1uczogImZpbGVfbmFtZSIgYW5kICJzYW1wbGVfaWQiLiBBbGwKYWRkaXRpb25hbCBjb2x1bW5zIGFyZSB1c2VkIGFzIG1ldGFkYXRhIGNvbHVtbnMgYW5kIGFyZSBhZGRlZCB0byB0aGUgU2V1cmF0IG9iamVjdC4KYGBge3IgbWV0YWRhdGF9CmhlYWQobWV0YWRhdGEpCmBgYAoKUGFuZWw6IGEgZGF0YS5mcmFtZSB3aXRoIGF0IGxlYXN0IDIgY29sdW1uczogImZjc19jb2xuYW1lIiBhbmQgImFudGlnZW4iLiBUaGUgCmZjc19jb2xuYW1lIGFyZSB0aGUgbmFtZXMgb2YgdGhlIGNoYW5uZWxzIGluIHRoZSBGQ1MgZmlsZS4gQW50aWdlbiBpcyB0aGUgZGVzaXJlZCAKbmFtZSBmb3IgZG93bnN0cmVhbSBhbmFseXNpcy4gQWRkaXRpb25hbGx5LCBjb2x1bW5zIGZvciB0cmFuc2Zvcm1hdGlvbiBhcmUgcmVxdWlyZWQuIApTZWUgYmVsb3cgKHRyYW5zZm9ybWF0aW9uKSBmb3IgZGV0YWlscy4KYGBge3IgcGFuZWx9CmhlYWQocGFuZWwpCmBgYAoKIyBMb2FkaW5nIEZDUyBmaWxlcwpGQ1MgZmlsZXMgYXJlIGxvYWRlZCBiYXNlZCBvbiAqLmZjcyBmaWxlcyBpbiB0aGUgZm9sZGVyIGFuZCBvbiAiZmlsZV9uYW1lIiBjb2x1bW4gCmluIG1ldGFkYXRhIGRhdGEuZnJhbWUuICAKRWFjaCBjZWxsIHdpbGwgcmVjZWl2ZSBhIHVuaXF1ZSBjZWxsIElEIGJhc2VkIG9uIHNhbXBsZV9pZCBwcm92aWRlZCBpbiBtZXRhZGF0YS4KYGBge3IgZmxvd3NldH0KZmNzX2ZzIDwtIGNyZWF0ZV9mbG93c2V0KCJkYXRhL2ZjcyIsIG1ldGFkYXRhKQpmY3NfZnMKYGBgCgpJdCBpcyBoaWdobHkgcmVjb21tZW5kZWQgdG8gc2F2ZSB0aGUgImZjc19mcyIgb2JqZWN0LgoKIyBDcmVhdGUgU2V1cmF0IG9iamVjdApBbGwgRkNTIGZpbGVzIHdpbGwgYmUgbWVyZ2VkIGFuZCBtZXRhZGF0YSB3aWxsIGJlIGFkZGVkIGJhc2VkIG9uIHNhbXBsZV9pZCBhbmQgCm1ldGFkYXRhIGRhdGEuZnJhbWUuIFJhdyBkYXRhIGlzIHNhdmVkIGluIFNldXJhdCBhc3NheSAiZmNzIi4gT25seSBDaGFubmVscyB3aWxsIApiZSBrZXB0IHRoYXQgYXJlIHByZXNlbnQgaW4gcGFuZWwgZGF0YS5mcmFtZSBhbmQgY2hhbm5lbHMgd2lsbCBiZSByZW5hbWVkIGZyb20gCiJmY3NfY29sbmFtZSIgdG8gImFudGlnZW4iLiBDaGFubmVscyB0aGF0IHdlcmUgbm90IGluZGljYXRlZCBpbiBwYW5lbCBkYXRhLmZyYW1lCmFyZSBzdG9yZWQgaW4gU2V1cmF0IGFzc2F5ICJ1bnVzZWQiLiBBbGwgcGFuZWwgZGF0YSBhcmUgc3RvcmVkIGluIHNldUBtaXNjIHNsb3QKZm9yIGVhc3kgYWNjZXNzLgpgYGB7ciBzZXVyYXR9CnNldSA8LSBjcmVhdGVfc2V1cmF0KGZjc19mcywgcGFuZWwsIG1ldGFkYXRhLCBkZXJhbmRvbWl6ZSA9IEZBTFNFKQpzZXUKYGBgCgpEZXJhbmRvbWl6YXRpb246IEZvciBtYXNzIGN5dG9tZXRyeSBkYXRhLCBpdCBtaWdodCBiZSBkZXNpcmVkIHRvIGRlcmFuZG9taXplIGRhdGEsIHdoaWNoCndpbGwgcm91bmQgaW50ZW5zaXR5IHZhbHVlcyB1cCB0byB0aGVuIG5lYXJlc3Qgd2hvbGUgbnVtYmVyCihzZWUgaHR0cHM6Ly9iaW9zdXJmLm9yZy9jeXRvZl9kYXRhX3NjaWVudGlzdC5odG1sIzM0X0RhdGFfdHJhbnNmb3JtYXRpb25zKS4gClRoaXMgaXMgaW1wbGVtZW50ZWQgaW4gdGhlIGNyZWF0ZV9zZXVyYXQgZnVuY3Rpb24gYnkgc2V0dGluZyBkZXJhbmRvbWl6ZSB0byBUUlVFLgoKYGBge3IgY2VsbG51bWJlcnN9CiMgcGxvdCBjZWxsbnVtYmVycyBwZXIgc2FtcGxlCnBsb3RfY2VsbG51bWJlcihzZXUpCmBgYAoKIyBQcmVwcm9jZXNzaW5nCiMjIEJlYWQgbm9ybWFsaXNhdGlvbgpGb3IgbWFzcyBjeXRvbWV0cnkgZGF0YSwgYmVhZCBub3JtYWxpemF0aW9uIG1heSBiZSByZXF1aXJlZC4gVGhpcyB2aWduZXR0ZSB1c2VzCmEgZmxvdyBjeXRvbWV0cnkgdGVzdCBkYXRhc2V0LCBzbyBiZWFkIG5vcm1hbGl6YXRpb24gaXMgbm90IGFwcGxpZWQuIFBsZWFzZSByZWZlcgp0byBzZWN0aW9uICJPdGhlciBmZWF0dXJlcyIgZm9yIGEgZGVzY3JpcHRpb24gb2YgYmVhZCBub3JtYWxpemF0aW9uIGltcGxlbWVudGF0aW9uLgoKIyMgQ29tcGVuc2F0aW9uCkNvbXBlbnNhdGlvbiBpcyBiYXNlZCBvbiBzcGlsbG92ZXIgbWF0cmljZXMgdGhhdCBjYW4gYmUgcHJvdmlkZWQgd2l0aGluIHRoZSBGQ1MgCmZpbGUgb3IgYXMgYSBkYXRhLmZyYW1lLiAgCgpUaGVyZSBhcmUgZGlmZmVyZW50IG9wdGlvbnMgaG93IHRvIHN1cHBseSB0aGUgbWF0cml4LiBGb3IgZGV0YWlscyBzZWUgY29tcGVuc2F0ZV9kYXRhIApmdW5jdGlvbiBkb2N1bWVudGF0aW9uLiBIZXJlLCB3ZSB1c2Ugb3B0aW9uIDMuICAKLSBPcHRpb24gMSkgVXNlIGV4dGVybmFsIHNwaWxsb3ZlciBtYXRyaXggZGlyZWN0bHk6IHNhbWUgY29tcGVuc2F0aW9uIGZvciBhbGwgZmlsZXMuICAKLSBPcHRpb24gMikgVXNlIHNwaWxsb3ZlciBtYXRyaXggc2F2ZWQgaW4gRkNTIGZpbGVzOiBzYW1lIGNvbXBlbnNhdGlvbiBmb3IgYWxsIGZpbGVzLiAgCi0gT3B0aW9uIDMpIFVzZSBzcGlsbG92ZXIgbWF0cml4IHNhdmVkIGluIEZDUyBmaWxlczogY29tcGVuc2F0aW9uIG1hdHJpeCB1c2VkIGZyb20KaW5kaXZpZHVhbCBGQ1MgZmlsZXMsIHRodXMgY2FuIGJlIGRpZmZlcmVudCBmb3IgZWFjaCBmaWxlLiAgCgpUaGUgY29tcGVuc2F0ZV9kYXRhIGZ1bmN0aW9uIHdpbGwgdXNlIGZsb3dDb3JlOjpjb21wZW5zYXRlKCkgdG8gY29tcGVuc2F0ZSB0aGUgcmF3IAp2YWx1ZXMgYW5kIHdyaXRlIGEgbmV3IGFzc2F5IGludG8gdGhlIFNldXJhdCBvYmplY3QgY2FsbGVkICJjb21wIi4gVGhlIGNvbXBlbnNhdGVkIApkYXRhIGFyZSBzYXZlZCBpbiAiY291bnRzIiBzbG90LiAgCldhcm5pbmc6IGlmIG11bHRpcGxlIHNwaWxsb3ZlciBtYXRyaWNlcyBhcmUgcHJlc2VudCBpbiBGQ1MgZmlsZXMsIHVzZSB0aGUgY29ycmVjdCBvbmUhICAKCkhlcmUsIHdlIHVzZSBvcHRpb24gMyB0byBjb21wZW5zYXRlIHRoZSBkYXRhLgpgYGB7ciBjb21wZW5zYXRpb259CiMgQ2hlY2sgZGlmZmVyZW50IG1hdHJpY2VzIHVzaW5nOiAgCm5hbWVzKGZsb3dDb3JlOjpzcGlsbG92ZXIoZmNzX2ZzW1sxXV0pKQojIEluIHRoaXMgY2FzZSwgc3BpbGxvdmVyIG1hdHJpeCBpbiBjb2x1bW4gMyBpcyB0aGUgY29ycmVjdCBvbmUuICAKc2V1IDwtIGNvbXBlbnNhdGVfZGF0YShmY3NfZnMsIHNldSwgZmNzX21hdHJpeCA9IDMpCmBgYAoKYGBge3IgcGxvdC1jb21wZW5zYXRpb259CiMgQ2hlY2sgdGhhdCBjb21wZW5zYXRpb24gd29ya2VkLiAgCnBsb3QxIDwtIHBsb3RfY3l0byhzZXUsIHggPSAiSWdEIiwgeSA9ICJDRDMiLCBhc3NheSA9ICJmY3MiLAogICAgICAgICAgICAgICAgICAgc2xvdCA9ICJjb3VudHMiLCBzY2FsZSA9ICJsb2ciLCByYXN0ZXJpemUgPSBUUlVFKSArCiAgZ2d0aXRsZSgiVW5jb21wZW5zYXRlZCIpCnBsb3QyIDwtIHBsb3RfY3l0byhzZXUsIHggPSAiSWdEIiwgeSA9ICJDRDMiLCBhc3NheSA9ICJjb21wIiwKICAgICAgICAgICAgICAgICAgIHNsb3QgPSAiY291bnRzIiwgc2NhbGUgPSAibG9nIiwgcmFzdGVyaXplID0gVFJVRSkgKwogIGdndGl0bGUoIkNvbXBlbnNhdGVkIikKcGxvdDEgKyBwbG90MgpgYGAKCiMjIFRyYW5zZm9ybWF0aW9uClRoZSBmb2xsb3dpbmcgdHJhbnNmb3JtYXRpb25zIGFyZSBwb3NzaWJsZTogYXJjc2luaCBhbmQgYmlleHAuICAKClRyYW5zZm9ybWF0aW9uIGlzIHBlcmZvcm1lZCBvbiAiY291bnRzIiBkYXRhIG9mIHRoZSBEZWZhdWx0QXNzYXkgb2YgdGhlIFNldXJhdCAKb2JqZWN0LiBUaGUgRGVmYXVsdEFzc2F5IGlzIGVpdGhlciAiZmNzIiwgImJlYWRub3JtIiwgb3IgImNvbXAiIGRlcGVuZGluZyBvbgp3aGV0aGVyIGJlYWQgbm9ybWFsaXNhdG9uIG9yIGNvbXBlbnNhdGlvbiB3YXMgcGVyZm9ybWVkIG9yIG5vdC4gIAogIApUaGUgdHJhbnNmb3JtX2RhdGEgZnVuY3Rpb24gd2lsbCByZXR1cm4gYSBTZXVyYXQgb2JqZWN0IHdpdGggdHJhbnNmb3JtZWQgZGF0YSAKd3JpdHRlbiBpbnRvIHRoZSAiZGF0YSIgc2xvdC4gCiAgCkJvdGggYXJjc2luaCBhbmQgYmlleHAgdHJhbnNmb3JtYXRpb24gY2FuIGFuZCBzaG91bGQgYmUgdXNlZCB3aXRoIGN1c3RvbSBwYXJhbWV0ZXJzLiAKVGhlc2UgcGFyYW1ldGVycyBzaG91bGQgYmUgcHJvdmlkZWQgaW4gdGhlIHBhbmVsIGRhdGEuZnJhbWUsIHdoaWNoIGlzIHN0b3JlZCBpbiAKc2V1QG1pc2MgdXBvbiBTZXVyYXQgb2JqZWN0IGNyZWF0aW9uLgpgYGB7ciB0cmFuc2Zvcm1hdGlvbn0Kc2V1IDwtIHRyYW5zZm9ybV9kYXRhKHNldSwgImFyY3NpbmgiKQpgYGAKCmBgYHtyIHBsb3QtdHJhbnNmb3JtYXRpb259CnBsb3QxIDwtIHBsb3RfY3l0byhzZXUsIHggPSAiQ0Q0IiwgeSA9ICJDRDMiLCBhc3NheSA9ICJmY3MiLCBzbG90ID0gImNvdW50cyIsCiAgICAgICAgICAgICAgICAgICBzY2FsZSA9ICJsb2ciLCByYXN0ZXJpemUgPSBUUlVFKSArCiAgICBnZ3RpdGxlKCJVbnRyYW5zZm9ybWVkIikKcGxvdDIgPC0gcGxvdF9jeXRvKHNldSwgeCA9ICJDRDQiLCB5ID0gIkNEMyIsIGFzc2F5ID0gImNvbXAiLCBzbG90ID0gImRhdGEiLAogICAgICAgICAgICAgICAgICAgcmFzdGVyaXplID0gVFJVRSkgKwogICAgZ2d0aXRsZSgiQXJjc2luaCB0cmFuc2Zvcm1hdGlvbiIpCnBsb3QxICsgcGxvdDIKYGBgCgojIyBEb3duc2FtcGxpbmcKRmxvdy0gYW5kIE1hc3MgY3l0b21ldHJ5IGRhdGEgY2FuIGNvbnRhaW4gbWlsbGlvbnMgb2YgY2VsbHMuIElmIHRoZSBudW1iZXIgb2YgY2VsbHMKZGlmZmVycyBsYXJnZWx5IGJldHdlZW4gc2FtcGxlcywgaXQgaXMgYWR2aXNhYmxlIHRvIGRvd25zYW1wbGUgc28gb3ZlcmFsbCBkaWZmZXJlbmNlcyBhcmUKbm90IGRyaXZlbiBieSBpbmRpdmlkdWFsIHNhbXBsZXMgd2l0aCBoaWdoIG51bWJlciBvZiBjZWxscy4gRnVydGhlcm1vcmUsIGRvd25zYW1wbGluZyAKY2FuIGRlY3JlYXNlIGNvbXB1dGF0aW9uYWwgdGltZSwgaWYgcXVpY2sgZGF0YSBleHBsb3JhdGlvbiBpcyBkZXNpcmVkLiAgCkl0IGlzIGhpZ2hseSByZWNvbW1lbmRlZCB0byBzYXZlIGEgbm9uLWRvd25zYW1wbGVkIFNldXJhdCBvYmplY3QuCmBgYHtyIGRvd25zYW1wbGluZ30KIyBzZXQgc2VlZCBmb3IgZG93bnNhbXBsaW5nIGZvciByZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoNDIpCiMgZG93bnNhbXBsZSB1c2luZyBTZXVyYXRzIHN1YnNldCBmdW5jdGlvbgpzZXUgPC0gc3Vic2V0KHNldSwgZG93bnNhbXBsZSA9IDIwMDAwKQpzZXUKIyBwbG90IGNlbGxudW1iZXJzIHBlciBzYW1wbGUKcGxvdF9jZWxsbnVtYmVyKHNldSkKYGBgCgojIFF1YWxpdHkgY29udHJvbApDeXRvbWV0cnkgaXMgYW4gaW5oZXJlbnRseSBub2lzeSB0ZWNobm9sb2d5LiBUaGVyZSB3aWxsIGJlIGV2ZW50cyBjbG9zZSAgdG8gdGhlCmF4aXMgb3IgYXJ0ZWZhY3RzIGR1ZSB0byBhbnRpYm9keSBhZ2dyZWdhdGlvbi4gU2V1bWV0cnkgcHJvdmlkZXMgbXVsdGlwbGUgCnF1YWxpdHkgY29udHJvbCB0b29scyB0byBhY2hpZXZlIGNsZWFuIGRhdGEgZm9yIGRvd25zdHJlYW0gYW5hbHlzaXMuCgojIyBSZW1vdmFsIG9mIG91dGxpZXJzCkl0IGNhbiBvY2N1ciB3aXRoIGN5dG9tZXRyeSwgdGhhdCBzb21lIGV2ZW50cyBoYXZlIGV4dHJlbWVseSBuZWdhdGl2ZSBvciAKcG9zaXRpdmUgdmFsdWVzLiBIZXJlLCB3ZSBjYW4gcmVtb3ZlIHRoZXNlIG91dGxpZXIgZXZlbnRzIDEpIGJ5IHNldHRpbmcgYSBtYW51YWwgCnRocmVzaG9sZCBmb3IgZWFjaCBjaGFubmVsIG9yIDIpIGJ5IHVzaW5nIGFuIGF1dG9tYXRpYyByZW1vdmFsIGFsZ29yaXRobSBiYXNlZCBvbiAKaXNvbGF0aW9uIGZvcmVzdC4KCiMjIyBNYW51YWwgcmVtb3ZhbApVc2UgYSBuYW1lZCB2ZWN0b3IgdG8gaW5kaWNhdGUgdGhyZXNob2xkIGZvciBlYWNoIGNoYW5uZWwuIFRoZSBmdW5jdGlvbiBjYW4gdGFrZSAKcG9zaXRpdmUgb3IgbmVnYXRpdmUgdGhyZXNob2xkcy4KYGBge3IgbWFuLW91dGxpZXItYmVmb3JlfQojIGNoZWNrIGVhY2ggY2hhbm5lbCB0byBkZXRlcm1pbmUgaWYgdGhleSBjb250YWluIG91dGxpZXIgZXZlbnRzCnBsb3RfY3l0byhzZXUsICJDRDQiLCAiQ0QzIiwgc3R5bGUgPSAiMmRfZGVuc2l0eSIsIGFzc2F5ID0gImNvbXAiLCBzbG90ID0gImRhdGEiKQpgYGAKCmBgYHtyIG1hbi1vdXRsaWVyLXJlbW92YWx9CiMgbWFudWFsbHkgc2V0IHRocmVzaG9sZHMgYmFzZWQgb24gY3l0byBwbG90cwp0aHJlc2hvbGRzX25lZyA9IGMoIkNEMyIgPSAtNCwKICAgICAgICAgICAgICAgICAgICJDRDQiID0gLTIpCnRocmVzaG9sZHNfcG9zID0gYygiQ0QzIiA9IDQsCiAgICAgICAgICAgICAgICAgICAiQ0Q0IiA9IDQuNSkKIyByZW1vdmUgdmFsdWVzIGFib3ZlIG9yIGJlbG93IHRocmVzaG9sZHMKc2V1X21hbnVhbCA9IHJlbW92ZV9vdXRsaWVyc19tYW51YWwoc2V1LCB0aHJlc2hvbGRzX25lZykKc2V1X21hbnVhbCA9IHJlbW92ZV9vdXRsaWVyc19tYW51YWwoc2V1X21hbnVhbCwgdGhyZXNob2xkc19wb3MsIG5lZ2F0aXZlID0gRkFMU0UpCmBgYAoKYGBge3IgbWFuLW91dGVybGllci1hZnRlcn0KcGxvdF9jeXRvKHNldV9tYW51YWwsICJDRDQiLCAiQ0QzIiwgc3R5bGUgPSAiMmRfZGVuc2l0eSIsIGFzc2F5ID0gImNvbXAiLCBzbG90ID0gImRhdGEiKQpgYGAKCiMjIyBBdXRvbWF0aWMgcmVtb3ZhbApPdXRsaWVycyBhcmUgZGV0ZWN0ZWQgdXNpbmcgYW4gaXNvbGF0aW9uIGZvcmVzdC4gSW4gdW5nYXRlZCBmbG93IGN5dG9tZXRyeSBkYXRhLCAKdGhpcyBhbGdvcml0aG0gd2lsbCBtYWlubHkgcmVtb3ZlIGF4aXMtbmVhciBldmVudHMsIGRlYWQgY2VsbHMsIGFuZCBkb3VibGV0cy4gIApUaGUgdGhyZXNob2xkIGF0IHdoaWNoIHNjb3JlIGFuIGV2ZW50IGlzIHJlZ2FyZGVkIGFuIG91dGxpZXIgY2FuIGJlIHN1cHBsaWVkLAp3aGljaCByZWZlcnMgdG8gdGhlIGFwcHJveGltYXRlIGRlcHRoIGl0IHRha2VzIHRvIGlzb2xhdGUgYW4gb2JzZXJ2YXRpb24uIApUaGUgdGhyZXNob2xkIGlzIHVzdWFsbHkgYmV0d2VlbiAwLjYgYW5kIDAuOCwgd2l0aCBhbiBkZWZhdWx0IHRocmVzaG9sZCBvZiAwLjcuIApUaGUgbG93ZXIgdGhlIHRocmVzaG9sZCwgdGhlIG1vcmUgY2VsbHMgYXJlIGxhYmVsbGVkIGFzIG91dGxpZXJzLiAgCiAgClRoZSBmdW5jdGlvbiB3aWxsIHNhdmUgdHdvIGZlYXR1cmVzIGZvciBlYWNoIGNlbGwgaW50byBtZXRhLmRhdGEgb2YgdGhlIApTZXVyYXQgT2JqZWN0OiAxKSB0aGUgaXNvbGF0aW9uIGZvcmVzdCBzY29yZSAob3V0bGllcl9zY29yZSkgYW5kIHdoZXRoZXIgYW4gCmV2ZW50IHBhc3NlZCB0aGUgdGhyZXNob2xkIG9yIG5vdCAob3V0bGllcikuCmBgYHtyIGF1dG8tb3V0bGllci1kZXRlY3Rpb259CiMgaWRlbnRpZnkgb3V0bGllcnMgKGRlZmF1bHQgdGhyZXNob2xkOiAwLjcpCnNldSA8LSBkZXRlY3Rfb3V0bGllcnMoc2V1LCBzY29yZV90aHJlc2hvbGQgPSAwLjcpCmBgYAoKYGBge3IgYXV0by1vdXRsaWVyLXBsb3RzfQpwbG90MSA8LSBwbG90X2N5dG8oc2V1LCB4ID0gIkNENCIsIHkgPSAiQ0QzIiwgYXNzYXkgPSAiY29tcCIsIHNsb3QgPSAiZGF0YSIpICsKICAgIGdndGl0bGUoIkJlZm9yZSByZW1vdmFsIikKcGxvdDIgPC0gcGxvdF9jeXRvKHNldSwgeCA9ICJDRDQiLCB5ID0gIkNEMyIsIGFzc2F5ID0gImNvbXAiLCBzbG90ID0gImRhdGEiLAogICAgICAgICAgICAgICAgICAgc3R5bGUgPSAicG9pbnQiLCBjb2xvciA9ICJvdXRsaWVyX3Njb3JlIikgKwogICAgZ2d0aXRsZSgiT3V0bGllciBzY29yZSIpCnBsb3QzIDwtIHBsb3RfY3l0byhzZXUsIHggPSAiQ0Q0IiwgeSA9ICJDRDMiLCBhc3NheSA9ICJjb21wIiwgc2xvdCA9ICJkYXRhIiwKICAgICAgICAgICAgICAgICAgIHN0eWxlID0gInBvaW50IiwgY29sb3IgPSAib3V0bGllciIpICsKICAgIGdndGl0bGUoIk91dGxpZXIiKQpwbG90NCA8LSBwbG90X2N5dG8oc3Vic2V0KHNldSwgc3Vic2V0ID0gb3V0bGllciA9PSBGQUxTRSksIHggPSAiQ0Q0IiwgeSA9ICJDRDMiLAogICAgICAgICAgICAgICAgICAgYXNzYXkgPSAiY29tcCIsIHNsb3QgPSAiZGF0YSIpICsKICAgIGdndGl0bGUoIkFmdGVyIHJlbW92YWwiKQpwbG90MSArIHBsb3QyICsgcGxvdDMgKyBwbG90NApgYGAKClJlbW92ZSBvdXRsaWVycyBmcm9tIFNldXJhdCBvYmplY3QuCmBgYHtyIGF1dG8tb3V0bGllci1yZW1vdmFsfQojIHJlbW92ZSBvdXRsaWVycyBmcm9tIFNldXJhdCBvYmplY3QKc2V1IDwtIHN1YnNldChzZXUsIHN1YnNldCA9IG91dGxpZXIgPT0gRkFMU0UpCmBgYAoKCiMjIFJlbW92YWwgb2YgYWdncmVnYXRlcwpXaXRoIGhpZ2gtZGltZW5zaW9uYWwgRmxvdyBjeXRvbWV0cnksIGFudGlib2R5IGFnZ3JlZ2F0ZXMgY2FuIG9jY3VyLiBUaGVzZSBhcmUgCnVzdWFsbHkgY2hhcmFjdGVyaXplZCBieSBoaWdobHkgY28tbGluZWFyIGV2ZW50cyB0aGF0IGZvcm0gZGlhZ29uYWwgc3RydWN0dXJlcyBpbiAKWFkgcGxvdHMuIFRoaXMgYWxnb3JpdGhtIGNhbiBpZGVudGlmeSBwb3RlbnRpYWxseSBwcm9ibGVtYXRpYyBjaGFubmVsIApjb21iaW5hdGlvbnMgYW5kIGlkZW50aWZ5IGFnZ3JlZ2F0ZXMgaW4gdGhlc2UgY2hhbm5lbHMuICAKICAKSGVyZSBpcyBhbiBleGFtcGxlIG9mIHN1Y2ggYWdncmVnYXRlczoKYGBge3IgYWdncmVnYXRlLWV4YW1wbGVzfQpwbG90MSA8LSBwbG90X2N5dG8oc2V1LCB4ID0gIkNEMjgiLCB5ID0gIkNYQ1I1IikKcGxvdDIgPC0gcGxvdF9jeXRvKHNldSwgeCA9ICJDWENSMyIsIHkgPSAiQ0QxQyIpCnBsb3QxICsgcGxvdDIKYGBgCgpUaGUgZmlyc3Qgc3RlcCBpcyB0byBpZGVudGlmeSBjaGFubmVsIGNvbWJpbmF0aW9ucyB0aGF0IHBvdGVudGlhbGx5IGNvbnRhaW4gCmFnZ3JlZ2F0ZXMuIFRoaXMgaXMgZG9uZSBieSBzZWxlY3Rpbmcgb25seSBkb3VibGUgcG9zaXRpdmUgZXZlbnRzIChkZWZhdWx0OiBldmVudHMgPiAxKSAKYW5kIHJ1bm5pbmcgYSBwZWFyc29uIGNvcnJlbGF0aW9uLiBUaGUgZGVmYXVsdCB0aHJlc2hvbGQgb2YgbGFiZWxsaW5nIGEgY2hhbm5lbCAKcG90ZW50aWFsbHkgY29udGFpbmluZyBhZ2dyZWdhdGVzIGlzIHBlYXJzb24gUiA+IDAuNy4gVGhlIGxvd2VyIHRoZSB0aHJlc2hvbGQsCnRoZSBtb3JlIGNoYW5uZWxzIGFyZSBsYWJlbGxlZCBhcyBwb3RlbnRpYWxseSBjb250YWluaW5nIGFnZ3JlZ2F0ZXMuICAKICAKVGhlIGZ1bmN0aW9uICJkZXRlY3RfYWdncmVnYXRlX2NoYW5uZWxzIiByZXR1cm5zIGEgbGlzdCBvZiAyIG1hdHJpY2VzIGFuZCAxIGRhdGEuZnJhbWUuICAKLSBNYXRyaXggMTogcGVhcnNvbiBjb3JyZWxhdGlvbiBtYXRyaXguICAKLSBNYXRyaXggMjogYmluYXJ5IGNvcnJlbGF0aW9uIG1hdHJpeCBiYXNlZCBvbiB0aHJlc2hvbGQgb2YgcGVhcnNvbiBSIChkZWZhdWx0IHRocmVzaG9sZCA9IDAuNykuICAKLSBEYXRhLmZyYW1lOiBjb250YWlucyB0aGUgY2hhbm5lbCBjb21iaW5hdGlvbnMgdGhhdCBwYXNzZWQgdGhlIHBlYXJzb24gUiB0aHJlc2hvbGQuIApgYGB7ciBhZ2dyZWdhdGUtY2hhbm5lbHN9CnByb2JsZW1fY2hhbm5lbHMgPC0gZGV0ZWN0X2FnZ3JlZ2F0ZV9jaGFubmVscyhzZXUsIHRocmVzaG9sZCA9IDAuNykKYGBgCgpUaGVzZSBjb3JyZWxhdGlvbiBtYXRyaWNlcyBjYW4gYmUgcGxvdHRlZCwgZm9yIGV4YW1wbGUsIHVzaW5nIGEgaGVhdG1hcC4KYGBge3IgYWdncmVnYXRlLWhlYXRtYXBzLCBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD03fQpwaGVhdG1hcDo6cGhlYXRtYXAocHJvYmxlbV9jaGFubmVsc1tbMV1dLCBtYWluID0gIlBlYXJzb24gUiIsIGJvcmRlcl9jb2xvciA9IE5BKQpwaGVhdG1hcDo6cGhlYXRtYXAocHJvYmxlbV9jaGFubmVsc1tbMl1dLCBtYWluID0gIlBlYXJzb24gUj4wLjciLCBib3JkZXJfY29sb3IgPSBOQSkKYGBgCgpOZXh0LCB3ZSBjYW4gcGxvdCB0aGUgY2hhbm5lbCBjb21iaW5hdGlvbnMgdGhhdCBwb3RlbnRpYWxseSBjb250YWluIGFnZ3JlZ2F0ZXMKYW5kIG1hbnVhbGx5IGFzc2VzcyBpZiB0aGVzZSBjaGFubmVscyBhcmUgcmVhbGx5IHByb2JsZW1hdGljLgpgYGB7ciBhZ2dyZWdhdGUtY2hhbm5lbHMtcGxvdHMsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0xMn0KcGxvdHMgPC0gbGlzdCgpCmZvcihpIGluIDE6bnJvdyhwcm9ibGVtX2NoYW5uZWxzW1szXV0pKQogIHBsb3RzW1tpXV0gPC0gcGxvdF9jeXRvKHNldSwKICAgICAgICAgICAgICAgICAgICAgICAgICB4ID0gcHJvYmxlbV9jaGFubmVsc1tbM11dW2ksICJDaGFubmVsXzEiXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gcHJvYmxlbV9jaGFubmVsc1tbM11dW2ksICJDaGFubmVsXzIiXSkKZG8uY2FsbChncmlkRXh0cmE6OmdyaWQuYXJyYW5nZSwgYyhwbG90cywgbmNvbCA9IDQpKQpybShwbG90cykKYGBgCgpJZiBhIGNoYW5uZWwgY29tYmluYXRpb24gZG9lcyBub3QgbG9vayBsaWtlIGl0IGNvbnRhaW5zIGFnZ3JlZ2F0ZXMsIHJlbW92ZSB0aGF0CnJvdyBmcm9tIHRoZSBkYXRhLmZyYW1lIChwcm9ibGVtX2NoYW5uZWxzW1szXV0pLiAgCgpOZXh0LCB3ZSBjYW4gZGV0ZWN0IGFnZ3JlZ2F0ZXMgaW4gdGhlIHByb2JsZW1hdGljIGNoYW5uZWxzIHVzaW5nIGEgbW9kaWZpZWQgClJBTlNBQyBhbGdvcml0aG0uIEVhY2ggY2VsbCB3aWxsIGdldCBhbiBhZ2dyZWdhdGVfc2NvcmUsIHdoaWNoIGlzIHRoZSBudW1iZXIgCm9mIGNoYW5uZWwgY29tYmluYXRpb25zIGluIHdoaWNoIGl0IHdhcyBsYWJlbGxlZCBhcyBhbiBhZ2dyZWdhdGUuIEJ5IGRlZmF1bHQsIApwb3RlbnRpYWwgYWdncmVnYXRlcyBhcmUgb25seSBsYWJlbGxlZCByZWFsIGFnZ3JlZ2F0ZXMgaWYgdGhleSBvY2N1ciBpbiAyIG9yIG1vcmUgCmNoYW5uZWwgY29tYmluYXRpb25zLiBUaGUgYWdncmVnYXRlX3Njb3JlIGFuZCB3aGV0aGVyIGEgY2VsbCBoYXMgcGFzc2VkIHRoZQphZ2dyZWdhdGVfc2NvcmUgdGhyZXNob2xkIChkZWZhdWx0ID49IDIpIGlzIHN0b3JlZCBpbiBtZXRhLmRhdGEgb2YgdGhlIFNldXJhdCBvYmplY3QuCmBgYHtyIGFnZ3JlZ2F0ZS1kZXRlY3Rpb259CnNldSA8LSBkZXRlY3RfYWdncmVnYXRlcyhzZXUsIHByb2JsZW1fY2hhbm5lbHNbWzNdXSkKYGBgCgpgYGB7ciBhZ2dyZWdhdGUtc2NvcmVzLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9CiMgQ2hlY2sgdGhlIGFnZ3JlZ2F0ZSByZW1vdmFsIHBlcmZvcm1hbmNlCnBsb3RzIDwtIGxpc3QoKQpmb3IoaSBpbiAxOm5yb3cocHJvYmxlbV9jaGFubmVsc1tbM11dKSkKICBwbG90c1tbaV1dIDwtIHBsb3RfY3l0byhzZXUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IHByb2JsZW1fY2hhbm5lbHNbWzNdXVtpLCAiQ2hhbm5lbF8xIl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHByb2JsZW1fY2hhbm5lbHNbWzNdXVtpLCAiQ2hhbm5lbF8yIl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGUgPSAicG9pbnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImFnZ3JlZ2F0ZV9zY29yZSIpCmRvLmNhbGwoZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UsIGMocGxvdHMsIG5jb2wgPSA0KSkKcm0ocGxvdHMpCmBgYAoKYGBge3IgYWdncmVnYXRlLWZpbmFsLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTJ9CnBsb3RzIDwtIGxpc3QoKQpmb3IoaSBpbiAxOm5yb3cocHJvYmxlbV9jaGFubmVsc1tbM11dKSkKICBwbG90c1tbaV1dIDwtIHBsb3RfY3l0byhzZXUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IHByb2JsZW1fY2hhbm5lbHNbWzNdXVtpLCAiQ2hhbm5lbF8xIl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHByb2JsZW1fY2hhbm5lbHNbWzNdXVtpLCAiQ2hhbm5lbF8yIl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGUgPSAicG9pbnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImFnZ3JlZ2F0ZSIpCmRvLmNhbGwoZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UsIGMocGxvdHMsIG5jb2wgPSA0KSkKcm0ocGxvdHMpCmBgYAoKSWYgdGhlIHJlc3VsdCBpcyBzYXRpc2ZhY3RvcnksIHRoZSBhZ2dyZWdhdGVzIGNhbiBiZSByZW1vdmVkLgpgYGB7ciBhZ2dyZWdhdGUtcmVtb3ZhbH0Kc2V1IDwtIHN1YnNldChzZXUsIHN1YnNldCA9IGFnZ3JlZ2F0ZSA9PSBGQUxTRSkKYGBgCgpJdCBpcyBwb3NzaWJsZSB0aGF0IHNvbWUgYWdncmVnYXRlcyBzdGlsbCByZW1haW4uIEluIHRoYXQgY2FzZSBpdCBpcyBhZHZpc2FibGUgdG8KZmluZSB0dW5lIHRoZSBwYXJhbWV0ZXJzIG9mIHRoZSBhZ2dyZWdhdGVfY2hhbm5lbHMgZnVuY3Rpb24uIEFsdGVybmF0aXZlbHksCnJlbWFpbmluZyBhcnRlZmFjdHMgbWF5IGJlIHJlbW92ZWQgYnkgZGV0ZWN0aW5nIG91dGxpZXJzIGFnYWluIHVzaW5nIHRoZQppc29sYXRpb24gZm9yZXN0LgoKIyBEaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gJiBjbHVzdGVyaW5nCkRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiwgY2x1c3RlcmluZyAmIHZpc3VhbGl6YXRpb25zIGFyZSBhY2Nlc3NpYmxlIHZpYSB0aGUKW1NldXJhdF0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC8pIHdvcmtmbG93LiBXZSBhbHNvCmludGVncmF0ZWQgYSBtb3JlIGNsYXNzaWMgY2x1c3RlcmluZyBhcHByb2FjaCBmb3IgY3l0b21ldHJ5IGRhdGE6IApbRmxvd1NPTV0oaHR0cHM6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvaHRtbC9GbG93U09NLmh0bWwpLgoKIyMgRGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uCk11bHRpcGxlIHJlZHVjdGlvbnMgYXJlIHBvc3NpYmxlIHZpYSB0aGUgU2V1cmF0IHdvcmtmbG93OiBQQ0EsIFVNQVAgb3IgdFNORS4KSWYgVU1BUHMgYW5kIGNsdXN0ZXJpbmcgZG8gbm90IHNlcGFyYXRlIGNlbGx0eXBlcyB3ZWxsLCBpdCBjYW4gaGVscCB0byBvbmx5CnNlbGVjdCBmZWF0dXJlcyBmb3IgUENBIHRoYXQgZGlzdGluZ3Vpc2ggZXhwZWN0ZWQgY2VsbCBzdWJzZXRzLCBlLmcuIENEMywgQ0Q0LAphbmQgQ0Q4IGlmIHdvcmtpbmcgd2l0aCBUIGNlbGxzLiAgCgpGdXJ0aGVybW9yZSwgaW5wdXQgZGF0YSBhbmQgZGF0YSBwcm9jZXNzaW5nIGNhbiBiZSBhZGp1c3RlZDogIAotIE5vIGNlbnRlcmluZyAoZGF0YSByZXNlbWJsZXMgcmF3IGludGVuc2l0aWVzIG1vcmUgY2xvc2VseSkKLSBObyBzY2FsaW5nIChwcmVzZXJ2ZXMgcmVsYXRpb25zaGlwIG9mIGFic29sdXRlIG1hcmtlciBpbnRlbnNpdGllcykKLSBVc2UgKHNlbGVjdGVkKSBmZWF0dXJlcyBkaXJlY3RseSBhcyBpbnB1dAoKSGVyZSwgYWxsIGZlYXR1cmVzIGFyZSB1c2VkIGZvciBzY2FsaW5nLCBjZW50ZXJpbmcgYW5kIFBDQS4gRm9yIFVNQVAgMTAgUENzIGFyZSB1c2VkLgpgYGB7ciB1bWFwLWNsdXN0ZXJpbmcsIGVycm9yPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30KIyBzY2FsZSBkYXRhCnNldSA8LSBTZXVyYXQ6OlNjYWxlRGF0YShzZXUsIGZlYXR1cmVzID0gcm93Lm5hbWVzKHNldSksIHNjYWxlID0gVFJVRSwgY2VudGVyID0gVFJVRSkKIyBydW4gUENBIGZvciBhbGwgZmVhdHVyZXMKc2V1IDwtIFNldXJhdDo6UnVuUENBKHNldSwgZmVhdHVyZXMgPSByb3cubmFtZXMoc2V1KSwgYXBwcm94ID0gRkFMU0UpCiMgcnVuIFVNQVAgdXNpbmcgYWxsIFBDcwpzZXUgPC0gU2V1cmF0OjpSdW5VTUFQKHNldSwgZGltcyA9IDE6MTApCmBgYAoKSW50ZWdyYXRpb24gb2YgbXVsdGlwbGUgZGF0YXNldHMgb3IgYmF0Y2ggY29ycmVjdGlvbiBtZXRob2RzIGJhc2VkIG9uIFBDQSBzdWNoCmFzIENDQSBpbnRlZ3JhdGlvbiBvciBoYXJtb255IGNhbiBhbHNvIGJlIHVzZWQgKG5vdCBkb25lIGluIHRoaXMgdHV0b3JpYWwpLgoKQWxsIFNldXJhdCB2aXN1YWxpc2F0aW9ucyBhcmUgYXZhaWxhYmxlLiAKRm9yIG1vcmUgZGV0YWlsLCBzZWUgaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC8KYGBge3Igc2V1cmF0LWRpbXBsb3QsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD00fQpwbG90MSA8LSBTZXVyYXQ6OkRpbVBsb3Qoc2V1LCBncm91cC5ieSA9ICJzYW1wbGVfaWQiKSArIHNldF90aGVtZQpwbG90MiA8LSBTZXVyYXQ6OkRpbVBsb3Qoc2V1LCBncm91cC5ieSA9ICJsYXllciIpICsgc2V0X3RoZW1lCnBsb3QxICsgcGxvdDIKYGBgCgpgYGB7ciBzZXVyYXQtdmxuLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NX0KU2V1cmF0OjpWbG5QbG90KHNldSwgZmVhdHVyZXMgPSBjKCJDRDgiLCAiQ0Q0IiksIHB0LnNpemUgPSAwKSAmIHNldF90aGVtZSAmIFJvdGF0ZWRBeGlzKCkKYGBgCgpJdCBpcyByZWNvbW1lbmRlZCB0byB1c2UgYSBjdXN0b20gY29sb3JwYWxldHRlIHdoZW4gdXNpbmcgVU1BUCB0byBwbG90IGV4cHJlc3Npb24gCm9mIG1hcmtlcnMuCmBgYHtyIHNldXJhdC1mZWF0dXJlcGxvdCwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTV9ClNldXJhdDo6RmVhdHVyZVBsb3Qoc2V1LCBmZWF0dXJlcyA9IGMoIkNEOCIsICJDRDQiKSkgJgogIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSBncmFkaWVudF9jb2xvcnMpICYgc2V0X3RoZW1lCmBgYAoKIyMgQ2x1c3RlcmluZwpGb3IgY2x1c3RlcmluZywgbXVsdGlwbGUgbWV0aG9kcyBzdWNoIGFzIExvdXZhaW4gYWxnb3JpdGhtIGFyZSBhY2Nlc3NpYmxlIHZpYQpTZXVyYXQ6CmBgYHtyIHNldXJhdC1jbHVzdGVyaW5nLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQojIHJ1biBjbHVzdGVyaW5nIHVzaW5nIDEwIFBDcwpzZXUgPC0gU2V1cmF0OjpGaW5kTmVpZ2hib3JzKHNldSwgZGltcyA9IDE6MTApCnNldSA8LSBTZXVyYXQ6OkZpbmRDbHVzdGVycyhzZXUsIHJlc29sdXRpb24gPSAwLjUpClNldXJhdDo6RGltUGxvdChzZXUsIGdyb3VwLmJ5ID0gInNldXJhdF9jbHVzdGVycyIpICsgc2V0X3RoZW1lCmBgYAoKV2UgYWxzbyBpbXBsZW1lbnRlZCBhIEZsb3dTT00gYW5kIG1ldGFjbHVzdGVyaW5nIHZpYSBGbG93U09NIFIgcGFja2FnZQpgYGB7ciBmbG93c29tLWNsdXN0ZXJpbmcsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9CiMgcnVuIGZsb3dTT00gYW5kIG1ldGFjbHVzdGVyaW5nCnNldSA8LSBydW5fRmxvd1NPTShzZXUsIG1ldGFjbHVzdGVycyA9IDEwLCB4ZGltID0gMTAsIHlkaW0gPSAxMCkKU2V1cmF0OjpEaW1QbG90KHNldSwgZ3JvdXAuYnkgPSAiU09NX2NsIikgKyBzZXRfdGhlbWUKU2V1cmF0OjpEaW1QbG90KHNldSwgZ3JvdXAuYnkgPSAiU09NX21ldGFjbCIpICsgc2V0X3RoZW1lCmBgYAoKIyMgQ2x1c3RlciBtYXJrZXJzCk5vdCBvbmx5IHZpc3VhbGlzYXRpb25zLCBidXQgYWxzbyBvdGhlciBTZXVyYXQgZmVhdHVyZXMgYXJlIGZ1bGx5IGZ1bmN0aW9uYWwuIApGb3IgZXhhbXBsZSwgRmluZEFsbE1hcmtlcnMgY2FuIGJlIHVzZWQgdG8gaGVscCBhbm5vdGF0ZSBjbHVzdGVycy4KYGBge3Igc2V1cmF0LW1hcmtlcnMsIGVycm9yPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30KbWFya2VycyA8LSBTZXVyYXQ6OkZpbmRBbGxNYXJrZXJzKHNldSwgZ3JvdXAuYnkgPSAic2V1cmF0X2NsdXN0ZXJzIiwgb25seS5wb3MgPSBUUlVFKQpgYGAKCmBgYHtyIHNldXJhdC1kb3RwbG90LCBmaWcud2lkdGg9MTEsIGZpZy5oZWlnaHQ9NH0KdG9wNSA9IG1hcmtlcnMgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSB0b3BfbihuID0gNSwgd3QgPSBhdmdfbG9nMkZDKQpTZXVyYXQ6OkRvdFBsb3Qoc2V1LCB1bmlxdWUodG9wNSRnZW5lKSwgYXNzYXkgPSAiY29tcCIpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IGdyYWRpZW50X2NvbG9ycykgKwogIHRoZW1lX2xpbmVkcmF3KCkgKyB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKSArCiAgUm90YXRlZEF4aXMoKQpgYGAKCiMgRGlmZmVyZW50aWFsIGFidW5kYW5jZQpEaWZmZXJlbnRpYWwgYWJ1bmRhbmNlIChEQSkgYW5hbHlzaXMgaXMgaW1wbGVtZW50ZWQgaW4gdGhlIFNldW1ldHJ5IHdvcmtmbG93IApiYXNlZCBvbiB0aGUgZWRnZVIgcGFja2FnZS4gREEgaXMgZG9uZSB1c2luZyBhIGdlbmVyYWxpemVkIGxpbmVhciBtb2RlbCAoR0xNKSB0byAKbW9kZWwgbnVtYmVyIG9mIGNlbGxzIHBlciBncm91cCBnaXZlbiBhdHRyaWJ1dGUvZmVhdHVyZSB4IGNvbmRpdGlvbiAKKGUuZy4gc2V1cmF0X2NsdXN0ZXJzIHggdHJlYXRtZW50KSBhbmQgYSBsaWtlbGlob29kIHJhdGlvIHJlc3QgKExSVCkgdG8gbWFrZSBhIApjb250cmFzdCBiZXR3ZWVuIGFsbCBjb25kaXRpb25zLiAgCgpVc2luZyB0aGUgZnVuY3Rpb24gd2l0aCAiY2hlY2tfY29lZiA9IFRSVUUiIHdpbGwgcmV0dXJuIGFsbCBjb2VmZmljaWVudHMgdG8gCmRlc2lnbiB0aGUgcHJvcGVyIGNvbnRyYXN0IGZvciB0aGUgREEgYW5hbHlzaXMuCmBgYHtyIGRhLXNldHVwfQojIGZpcnN0IGNoZWNrIHRoZSBjb250cmFzdHMKZGlmZmVyZW50aWFsX2FidW5kYW5jZShzZXUsCiAgICAgICAgICAgICAgICAgICAgICAgYXR0cmlidXRlID0gInNldXJhdF9jbHVzdGVycyIsCiAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkgPSAic2FtcGxlX2lkIiwKICAgICAgICAgICAgICAgICAgICAgICBmb3JtdWxhID0gYXMuZm9ybXVsYSgifjArbGF5ZXIiKSwKICAgICAgICAgICAgICAgICAgICAgICBjaGVja19jb2VmID0gVFJVRSkKYGBgCgpIZXJlLCB3ZSBjb21wYXJlIElFTCB2cyBMUEwgdXNpbmcgYSBjb250cmFzdCBvZiBjKDEsIC0xKS4KYGBge3IgZGEtYW5hbHlzaXN9CiMgY2FsY3VsYXRlIGRpZmZlcmVudGlhbCBhYnVuZGFuY2UKZGFfcmVzIDwtIGRpZmZlcmVudGlhbF9hYnVuZGFuY2Uoc2V1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdHRyaWJ1dGUgPSAic2V1cmF0X2NsdXN0ZXJzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkgPSAic2FtcGxlX2lkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9ybXVsYSA9IGFzLmZvcm11bGEoIn4wK2xheWVyIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyYXN0ID0gYygxLCAtMSkpCmhlYWQoZGFfcmVzKQpgYGAKClRoZSByZXN1bHRzIGNhbiBiZSBleHBvcnRlZCBhcyBhIHRhYmxlIGFuZC9vciBwbG90dGVkLCBmb3IgZXhhbXBsZSwgdXNpbmcgCkVuaGFuY2VkVm9sY2Fuby4KYGBge3IgZGEtdm9sY2Fub30KRW5oYW5jZWRWb2xjYW5vOjpFbmhhbmNlZFZvbGNhbm8oZGFfcmVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWIgPSByb3duYW1lcyhkYV9yZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcmF3Q29ubmVjdG9ycyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSAibG9nRkMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gIkZEUiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gIklFTCB2cyBMUEwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwQ3V0b2ZmID0gMC4xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGQ2N1dG9mZiA9IDAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWxpbSA9IGMoMCwgMi41KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeGxpbSA9IGMoLTYsIDYpKSArIHNldF90aGVtZQpgYGAKCgojIERpZmZlcmVudGlhbCBleHByZXNzaW9uCkluIGFkZGl0aW9uIHRvIFNldXJhdCAiRmluZE1hcmtlcnMiIGZ1bmN0aW9uLCBTZXVtZXRyeSBhbHNvIGluY2x1ZGVzIGEgZmVhdHVyZSAKdG8gZmluZCBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiAoREUpIG9mIG1hcmtlcnMgYmFzZWQgb24gYSBwc2V1ZG9idWxrIGFuYWx5c2lzLiAKVG8gdGhpcyBlbmQsIHRoZSBtZWRpYW4gZmx1b3Jlc2NlbnQgaW50ZW5zaXR5IGlzIGNhbGN1bGF0ZWQgdXNpbmcgdGhlIFNldW1ldHJ5IApmdW5jdGlvbiAibWVkaWFuX2V4cHJlc3Npb24iLiBUaGUgREUgYW5hbHlzaXMgaXMgYmFzZWQgb24gdGhlIGxpbW1hIHBhY2thZ2UgYW5kCmEgbGluZWFyIG1vZGVsIGlzIGZpdCBmb2xsb3dlZCBieSBlbXBpcmljYWwgQmF5ZXMgc3RhdGlzdGljcyBmb3IgZGlmZmVyZW50aWFsCmV4cHJlc3Npb24gKGVCYXllcykuCmBgYHtyIGRlLWFuYWx5c2lzfQpkZV9yZXMgPC0gREVfcHNldWRvYnVsayhzZXUsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpeGVkX3ZhcnMgPSAibGF5ZXIiLAogICAgICAgICAgICAgICAgICAgICAgICBjb250cmFzdCA9ICJsYXllcklFTC1sYXllckxQTCIpCmhlYWQoZGVfcmVzKQpgYGAKClRoZSByZXN1bHRzIGNhbiBiZSBleHBvcnRlZCBhcyBhIHRhYmxlIGFuZC9vciBwbG90dGVkLCBmb3IgZXhhbXBsZSwgdXNpbmcgCkVuaGFuY2VkVm9sY2Fuby4KYGBge3IgZGUtdm9sY2Fub30KRW5oYW5jZWRWb2xjYW5vOjpFbmhhbmNlZFZvbGNhbm8oZGVfcmVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWIgPSByb3duYW1lcyhkZV9yZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcmF3Q29ubmVjdG9ycyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSAibG9nRkMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gIlAuVmFsdWUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJJRUwgdnMgTFBMIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcEN1dG9mZiA9IDAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRkNjdXRvZmYgPSAwLjEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHhsaW0gPSBjKC0wLjUsIDAuNSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlsaW0gPSBjKDAsIDIpKSArIHNldF90aGVtZQpgYGAKCiMgQWRkaXRpb25hbCB2aXN1YWxpemF0aW9ucwojIyBDeXRvbWV0cnktbGlrZSBwbG90cwpTb21ldGltZXMgaXQgaXMgdXNlZnVsIHRvIGNyZWF0ZSBjeXRvbWV0cnktbGlrZSBwbG90cyBzdWNoIGFzIGRlbnNpdHkgcGxvdHMuIApTZXVtZXRyeSBpbmNsdWRlcyB2YXJpb3VzIHBsb3R0aW5nIGZ1bmN0aW9uYWxpdGllcyB1c2luZyAicGxvdF9jeXRvIi4KYGBge3IgY3l0by1wbG90fQpwbG90X2N5dG8oc2V1LCB4ID0gIkNEMyIsIHkgPSAiQ0Q0IikKcGxvdF9jeXRvKHNldSwgeCA9ICJDRDMiLCB5ID0gIkNENCIsIHN0eWxlID0gInBvaW50IiwgY29sb3IgPSAic2FtcGxlX2lkIikKcGxvdF9jeXRvKHNldSwgeCA9ICJDRDMiLCBzdHlsZSA9ICJkZW5zaXR5IiwgY29sb3IgPSAic2FtcGxlX2lkIikKYGBgCgojIyBTYW1wbGUtbGV2ZWwgUENBCkEgcHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpcyAoUENBKSBiYXNlZCBvbiBtZWRpYW4gbWFya2VyIGV4cHJlc3Npb24gcGVyIHNhbXBsZSAKY2FuIGdpdmUgYSBnb29kIGltcHJlc3Npb24gb2YgZGlmZmVyZW5jZXMgYmV0d2VlbiBjb25kaXRpb25zIG9yIHJlcGxpY2F0ZXMuIApTZXVtZXRyeSBpbmNsdWRlcyB0aGUgInBsb3RfcGNhIiBmdW5jdGlvbiB0byBjb21wdXRlIGEgc2FtcGxlLWxldmVsIFBDQS4gVGhlClBDQSBjYW4gYWxzbyBiZSBncm91cGVkIGJ5IG90aGVyIGZhY3RvcnMgdGhhbiBzYW1wbGUgKGJhc2VkIG9uIFNldXJhdCBPYmplY3QgbWV0YS5kYXRhKS4KYGBge3IgcGNhLXBsb3R9CnBsb3RfcGNhKHNldSkKcGxvdF9wY2Eoc2V1LCBncm91cF9ieSA9ICJzYW1wbGVfaWQiLCBjb2xvciA9ICJsYXllciIpCmBgYAoKIyMgRnJlcXVlbmN5IHBsb3RzClNldW1ldHJ5IGluY2x1ZGVzIGEgZnVuY3Rpb24gdG8gdmlzdWFsaXNlIHRoZSBmcmVxdWVuY3kgb3IgcHJvcG9ydGlvbiBvZiBjZWxscwpiYXNlZCBvbiBtZXRhZGF0YSBjb2x1bW5zLiBGb3IgZXhhbXBsZSwgaXQgY2FuIGJlIHVzZWZ1bCB0byBhc3Nlc3Mgd2hldGhlciAKdGhlIGZyZXF1ZW5jeSBvZiBjbHVzdGVycyBpcyBzdGFibGUgYWNyb3NzIGRpZmZlcmVudCBzYW1wbGVzIG9yIGRpZmZlcmVudHMgYmV0d2Vlbgpjb25kaXRpb25zLgpgYGB7ciBmcmVxdWVuY3ktcGxvdH0KcGxvdF9mcmVxdWVuY3koc2V1LCAic2V1cmF0X2NsdXN0ZXJzIiwgInNhbXBsZV9pZCIpCnBsb3RfZnJlcXVlbmN5KHNldSwgInNldXJhdF9jbHVzdGVycyIsICJsYXllciIpCmBgYAoKIyMgQ2VsbCBudW1iZXJzCmBgYHtyIGNlbGxudW1iZXItcGxvdH0KcGxvdF9jZWxsbnVtYmVyKHNldSwgInNhbXBsZV9pZCIpCmBgYAoKIyBPdGhlciBmZWF0dXJlcwojIyBCZWFkIG5vcm1hbGlzYXRpb24KVGhlIHRlc3QgZGF0YXNldCB1c2VkIHRocm91Z2hvdXQgdGhpcyB2aWduZXR0ZSBpcyBhIGZsb3cgY3l0b21ldHJ5IGRhdGFzZXQsCmJlYWQgbm9ybWFsaXNhdGlvbiBpcyBvbmx5IGF2YWlsYWJsZSBmb3IgbWFzcyBjeXRvbWV0cnkgZGF0YS4gSGVyZSwgd2UgaW1wbGVtZW50ZWQgCmJlYWQgbm9ybWFsaXNhdGlvbiBieSB1c2luZyBhIHdyYXBwZXIgZnVuY3Rpb24gZm9yIHRoZSBub3JtQ3l0b2YoKSAKZnVuY3Rpb24gb2YgdGhlIFtDQVRBTFlTVF0oaHR0cHM6Ly9naXRodWIuY29tL0hlbGVuYUxDL0NBVEFMWVNUKSBwYWNrYWdlLgpUbyBzaG93Y2FzZSB0aGUgYmVhZCBub3JtYWxpc2F0aW9uIGltcGxlbWVudGF0aW9uIGluIFNldW1ldHJ5LCB3ZSB1c2UgCnRoZSByYXdfZGF0YSBwcm92aWRlZCBieSBDQVRBTFlTVC4KYGBge3IgZ2V0LWRhdGF9CmxpYnJhcnkoQ0FUQUxZU1QpCmRhdGEoInJhd19kYXRhIikKYGBgCgpOb3cgd2UgbmVlZCB0byBwcmVwYXJlIGEgcGFuZWwgYW5kIGFkZCBjZWxsIElEcyB0byBsb2FkIGRhdGEgaW50byBTZXVtZXRyeS4KYGBge3IgcHJlcGFyZS1jYXRhbHlzdC1kYXRhfQojIHByZXBhcmUgcGFuZWwgZGF0YWZyYW1lCnBhbmVsX2NhdCA8LSBkYXRhLmZyYW1lKCJmY3NfY29sbmFtZSIgPSB1bm5hbWUoZmxvd0NvcmU6OnBhcmFtZXRlcnMocmF3X2RhdGFbWzFdXSlAZGF0YVtbIm5hbWUiXV0pLAogICAgICAgICAgICAgICAgICAgICAgICAiYW50aWdlbiIgPSB1bm5hbWUoZmxvd0NvcmU6OnBhcmFtZXRlcnMocmF3X2RhdGFbWzFdXSlAZGF0YVtbImRlc2MiXV0pKQpwYW5lbF9jYXQgPC0gcGFuZWxfY2F0WyFpcy5uYShwYW5lbF9jYXQkYW50aWdlbiksXQojIGdpdmUgY2VsbHMgYW4gSUQgKHJlcXVpcmVkIGZvciBTZXVyYXQgT2JqZWN0IGNyZWF0aW9uKQpyb3cubmFtZXMocmF3X2RhdGFbWzFdXUBleHBycykgPC0gcGFzdGUwKCJyYXdfZGF0YV8xXyIsIDE6bnJvdyhyYXdfZGF0YVtbMV1dQGV4cHJzKSkKcm93Lm5hbWVzKHJhd19kYXRhW1syXV1AZXhwcnMpIDwtIHBhc3RlMCgicmF3X2RhdGFfMl8iLCAxOm5yb3cocmF3X2RhdGFbWzJdXUBleHBycykpCiMgY3JlYXRlIHNldXJhdCArIHRyYW5zZm9ybSAoY29mYWN0b3IgNSBkZWZhdWx0KQpzZXVfY2F0IDwtIGNyZWF0ZV9zZXVyYXQocmF3X2RhdGEsIHBhbmVsX2NhdCkKIyB0byBpZGVudGlmeSBiZWFkcywgbm9ybUN5dG9mKCkgcmVxdWlyZXMgdHJhbnNmb3JtZWQgZGF0YQpzZXVfY2F0IDwtIHRyYW5zZm9ybV9kYXRhKHNldV9jYXQsICJhcmNzaW5oIikKYGBgCgpGaW5hbGx5IHdlIGNhbiBwZXJmb3JtIGJlYWQgbm9ybWFsaXNhdGlvbi4gSXQgd2lsbCB1c2UgdGhlIERlZmF1bHRBc3NheSBvZiAKdGhlIFNldXJhdCBPYmplY3QgYW5kIHNhdmUgbm9ybWFsaXNlZCByYXcgaW50ZW5zaXRpZXMgaW4gImJlYWRub3JtIiAKYXNzYXkuIFRyYW5zZm9ybWF0aW9uIHNob3VsZCBiZSBkb25lIGFmdGVyd2FyZHMuIEJhc2VkIG9uIENBVEFMWVNUIGRvY3VtZW50YXRpb24sCnBhcmFtZXRlciBrIHJlZmVycyB0byAibWVkaWFuIHdpbmRvdyB1c2VkIGZvciBiZWFkIHNtb290aGluZyIgYW5kIGFmZmVjdHMgCnZpc3VhbGl6YXRpb25zIG9ubHkuCmBgYHtyIGJlYWQtbm9ybWFsaXNhdGlvbn0Kc2V1X2NhdCA8LSBiZWFkX25vcm0oc2V1X2NhdCwgYmVhZHMgPSAiZHZzIiwgcGxvdF9yZXMgPSBUUlVFLCBrID0gNTApCmBgYAoKRnVydGhlcm1vcmUsIHRoZSByZXR1cm5lZCBTZXVyYXQgT2JqZWN0IGNvbnRhaW5zIHR3byBuZXcgbWV0YWRhdGEgY29sdW1uczogIAotIGJlYWRzOiBldmVudHMgdGhhdCBsaWtlbHkgcmVwcmVzZW50IGJlYWRzLiAgCi0gYmVhZF9kb3VibGV0OiBldmVudHMgdGhhdCBsaWtlbHkgcmVwcmVzZW50IGJlYWRzIE9SIGJlYWQtY2VsbCBkb3VibGV0cy4gIApUbyByZW1vdmUgYmVhZHMgYW5kIGJlYWQgZG91YmxldHMgZnJvbSB0aGUgb2JqZWN0LCB5b3UgY2FuIHVzZSBzdWJzZXQuCmBgYHtyIGJlYWQtcmVtb3ZhbH0Kc2V1X2NhdCA8LSBzdWJzZXQoc2V1X2NhdCwgc3Vic2V0ID0gYmVhZF9kb3VibGV0ID09IEZBTFNFKQpzZXVfY2F0CmBgYAoKIyMgSW50ZWdyYXRpb24gb2YgdGhpcmQtcGFydHkgdG9vbHMKIyMjIENvbnZlcnNpb24gb2YgU2V1cmF0IE9iamVjdApUbyBhbGxvdyBpbnRlZ3JhdGlvbiB3aXRoIGEgd2lkZSB2YXJpZXR5IG9mIHNpbmdsZS1jZWxsIGFuZCBjeXRvbWV0cnkgbGlicmFyaWVzLCB3ZSAKaW1wbGVtZW50ZWQgYSBmdW5jdGlvbiAoY29udmVydF9zZXVyYXQpIHRvIGNvbnZlcnQgU2V1cmF0IE9iamVjdHMgdG8gU2luZ2xlQ2VsbEV4cGVyaW1lbnQsIApmbG93RnJhbWUsIGFuZCBmbG93U2V0IG9iamVjdHMuIFRoZXNlIGNhbiBiZSB1c2VkLCBmb3IgZXhhbXBsZSwgd2l0aCBiYXRjaCBjb3JyZWN0aW9uIG1ldGhvZHMgCihjeXRvTm9ybSwgY3lDb21iaW5lLCBnYXVzc05vcm0pIG9yIG90aGVyIHRoaXJkLXBhcnR5IGxpYnJhcmllcyAoZGlmZmN5dCwgQ0FUQUxZU1QpLiAgCmBgYHtyIGNvbnZlcnQtc2V1cmF0LWZzfQpmcyA8LSBjb252ZXJ0X3NldXJhdChzZXUsIHRvID0gIkZTIiwgc3BsaXRfYnkgPSAic2FtcGxlX2lkIikKZnMKYGBgCgpgYGB7ciBjb252ZXJ0LXNldXJhdC1mZn0KZmYgPC0gY29udmVydF9zZXVyYXQoc2V1LCB0byA9ICJGRiIpCmZmCmBgYAoKYGBge3IgY29udmVydC1zZXVyYXQtc2NlfQpzY2UgPC0gY29udmVydF9zZXVyYXQoc2V1LCB0byA9ICJTQ0UiKQpzY2UKYGBgCgojIyMgRXhhbXBsZSBpbnRlZ3JhdGlvbjogQ3l0b05vcm0KSGVyZSwgd2Ugc2hvdyBhbiBleGFtcGxlIG9mIGludGVncmF0aW5nIGEgdGhpcmQtcGFydHkgdG9vbC4gV2UgdXNlCltDeXRvTm9ybV0oaHR0cHM6Ly9naXRodWIuY29tL3NhZXlzbGFiL0N5dG9Ob3JtKSAKYXMgYW4gZXhhbXBsZSwgYXMgaXQgaXMgd2lkZWx5IHVzZWQgYXMgYSBjeXRvbWV0cnkgYmF0Y2ggY29ycmVjdGlvbiBtZXRob2QuICAKCkN5dG9Ob3JtIGlzIHVzZWQgZm9yIGJhdGNoIGNvcnJlY3Rpb24sIGJ1dCBqdXN0IHRvIHNob3djYXNlIHRoZSBpbnRlZ3JhdGlvbiB3aXRoClNldW1ldHJ5LCB3ZSB3aWxsIHVzZSBpdCBhcyBhIGRvbm9yLSBvciBzYW1wbGUgbm9ybWFsaXphdGlvbiBtZXRob2QuIFRvIHRoaXMgZW5kLAp3ZSB3aWxsIHBvb2wgYSBzdWJzZXQgb2YgY2VsbHMgZnJvbSBlYWNoIHNhbXBsZSBhbmQgdXNlIGl0IGFzIGEgcmVmZXJlbmNlIHNhbXBsZS4KSW4gYSBzZWNvbmQgc3RlcCwgd2Ugd2lsbCBub3JtYWxpemUgYWxsIHNhbXBsZXMgYmFzZWQgb24gdGhlIG1vZGVsIHRyYWluZWQgd2l0aAp0aGUgcmVmZXJlbmNlIHNhbXBsZS4gCgpGaXJzdCB3ZSBjcmVhdGUgYSBmbG93U2V0IG9mIGNvbXBlbnNhdGVkIGFuZCBub3JtYWxpemVkIGludGVuc2l0aWVzIGZyb20gYQpzdWJzZXQgb2YgY2VsbHMgZnJvbSBlYWNoIHNhbXBsZS4gV2Ugc3BsaXQgdGhlIHNhbXBsZXMgYnkgbXVjb3NhbCBsYXllci4gVGhpcwp3aWxsIGJlICB1c2VkIGFzIGEgcmVmZXJlbmNlIGZvciBDeXRvTm9ybS4gV2UgYWxzbyBjcmVhdGUgYSBmbG93U2V0IG9mIGFsbApzYW1wbGVzLgpgYGB7ciBjbi1mbG93c2V0fQojIHN1YnNldCBTZXVyYXQgb2JqZWN0CnNldV9zdWIgPC0gc3Vic2V0KHNldSwgZG93bnNhbXBsZSA9IDEwMDApCiMgY3JlYXRlIHJlZmVyZW5jZSBGbG93U2V0CmZzX3JlZiA8LSBjb252ZXJ0X3NldXJhdChzZXVfc3ViLCB0byA9ICJGUyIsIHNwbGl0X2J5ID0gImxheWVyIiwKICAgICAgICAgICAgICAgICAgICAgICAgIGFzc2F5ID0gImNvbXAiLCBzbG90ID0gImRhdGEiKQojIGNyZWF0ZSBGbG93U2V0IG9mIGFsbCBzYW1wbGVzCmZzIDwtIGNvbnZlcnRfc2V1cmF0KHNldSwgdG8gPSAiRlMiLCBzcGxpdF9ieSA9ICJzYW1wbGVfaWQiLAogICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJjb21wIiwgc2xvdCA9ICJkYXRhIikKYGBgCgpOZXh0LCB3ZSB0cmFpbiB0aGUgQ3l0b05vcm0gbW9kZWwuCmBgYHtyIGNuLXRyYWlufQojIHRyYWluIG1vZGVsCm1vZGVsIDwtIEN5dG9Ob3JtOjpDeXRvTm9ybS50cmFpbihmaWxlcyA9IGZzX3JlZiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIklFTCIsICJMUEwiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNoYW5uZWxzID0gcm93Lm5hbWVzKHNldSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2Zvcm1MaXN0ID0gTlVMTCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3QgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDQyKQpgYGAKCkZpbmFsbHksIHdlIGNhbiBub3JtYWxpemUgYWxsIHNhbXBsZXMgYmFzZWQgb24gdGhpcyBtb2RlbC4gQ3l0b05vcm0gd2lsbCB3cml0ZSAKRkNTIGZpbGVzIGZvciBlYWNoIG5vcm1hbGl6ZWQgc2FtcGxlLgpgYGB7ciBjbi1ub3JtYWxpemV9CiMgcnVuIG5vcm1hbGl6YXRpb24KZnNfbm9ybSA8LSBDeXRvTm9ybTo6Q3l0b05vcm0ubm9ybWFsaXplKG1vZGVsID0gbW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZXMgPSBmcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSByZXAoYygiSUVMIiwgIkxQTCIpLCA3KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2Zvcm1MaXN0ID0gTlVMTCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVmaXggPSAiIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2Zvcm1MaXN0LnJldmVyc2UgPSBOVUxMLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRwdXREaXIgPSAiZGF0YS9jeXRvbm9ybSIpCmBgYAoKVGhlIHJlc3VsdGluZyBub3JtYWxpemVkIEZDUyBmaWxlcyBjYW4gYmUgbG9hZGVkLCBhbmQgdGhlIGludGVuc2l0aWVzIGNhbiBiZSAKYWRkZWQgdG8gYSBuZXcgYXNzYXkgb2YgdGhlIFNldXJhdCBvYmplY3QgYW5kIHZpc3VhbGl6ZWQuCmBgYHtyIGNuLWludGVncmF0ZX0KIyBuYW1lIG9mIEZDUyBjaGFubmVscyB3YXMgY2hhbmdlZCBkdXJpbmcgY29udmVyc2lvbjsgYWRqdXN0IHBhbmVsIGFjY29yZGluZ2x5CnBhbmVsX25ldyA8LSBwYW5lbApwYW5lbF9uZXckZmNzX2NvbG5hbWUgPC0gcGFuZWxfbmV3JGFudGlnZW4KIyBjcmVhdGUgU2V1cmF0IG9iamVjdCBmcm9tIGZsb3dTZXQgb2Ygbm9ybWFsaXplZCBkYXRhCnNldV9ub3JtIDwtIGNyZWF0ZV9zZXVyYXQoZnNfbm9ybSwgcGFuZWxfbmV3LCBtZXRhZGF0YSwgZGVyYW5kb21pemUgPSBGQUxTRSkKIyBhZGQgbm9ybWFsaXplZCBpbnRlbnNpdGllcyB0byBvdGhlciBTZXVyYXQgb2JqZWN0CnNldVtbImN5dG9ub3JtIl1dIDwtIENyZWF0ZUFzc2F5T2JqZWN0KGRhdGEgPSBHZXRBc3NheURhdGEoc2V1X25vcm0pKQpgYGAKCmBgYHtyIGNuLXZpc3VhbGl6ZSwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTV9CnBsb3QxIDwtIHBsb3RfY3l0byhzZXUsIHggPSAiQ0Q0IiwgYXNzYXkgPSAiY29tcCIsIHNsb3QgPSAiZGF0YSIsCiAgICAgICAgICAgICAgICAgICBzdHlsZSA9ICJkZW5zaXR5IiwgY29sb3IgPSAic2FtcGxlX2lkIikgKwogIGdndGl0bGUoIlVubm9ybWFsaXplZCIpCnBsb3QyIDwtIHBsb3RfY3l0byhzZXUsIHggPSAiQ0Q0IiwgYXNzYXkgPSAiY3l0b25vcm0iLCBzbG90ID0gImRhdGEiLAogICAgICAgICAgICAgICAgICAgc3R5bGUgPSAiZGVuc2l0eSIsIGNvbG9yID0gInNhbXBsZV9pZCIpICsKICBnZ3RpdGxlKCJOb3JtYWxpemVkIikKcGxvdDEgKyBwbG90MgpgYGAKCiMjIE1lZGlhbiBmbHVvcmVzY2VudCBpbnRlbnNpdHkKVGhlIFNldW1ldHJ5IGZ1bmN0aW9uICJtZWRpYW5fZXhwcmVzc2lvbiIgY2FuIGJlIHVzZWQgc2ltaWxhciB0byBTZXVyYXQgCiJBdmVyYWdlRXhwcmVzc2lvbiIgZnVuY3Rpb24sIGJ1dCB1c2VzIHRoZSBtZWRpYW4gaW5zdGVhZCBvZiB0aGUgYXZlcmFnZS4gVGhpcwpmdW5jdGlvbiBjYW4gYmUgdXNlZnVsLCBmb3IgZXhhbXBsZSwgdG8gcGxvdCBtZWRpYW4gZXhwcmVzc2lvbiBwZXIgY2x1c3RlciBvciAKY29uZGl0aW9uLgpgYGB7ciBtZWRpYW4tZXhwcmVzc2lvbn0Kc2FtcGxlX21maSA8LSBtZWRpYW5fZXhwcmVzc2lvbihzZXUsIGdyb3VwX2J5ID0gInNhbXBsZV9pZCIpCmhlYWQoc2FtcGxlX21maSkKYGBgCgojIyBFeHBvcnQgb2YgRkNTIGZpbGVzCkZ1cnRoZXJtb3JlLCBzb21ldGltZXMgaXQgY2FuIGJlIHVzZWZ1bCB0byB1c2UgZXh0ZXJuYWwgc29mdHdhcmUgdGhhdCByZWxpZXMgb24gCkZDUyBmaWxlcy4gRm9yIHRoaXMgcHVycG9zZSwgU2V1bWV0cnkgaW5jbHVkZXMgYSBmdW5jdGlvbiB0byBleHBvcnQgRkNTIGZpbGVzLiAKVGhpcyBjYW4gYmUgZG9uZSwgZm9yIGV4YW1wbGUsIHRvIHZpc3VhbGl6ZSBjZWxscyBvZiBhIHNwZWNpZmljIGNsdXN0ZXIgd2l0aCAKZXh0ZXJuYWwgY3l0b21ldHJ5IHNvZnR3YXJlLiAKYGBge3IgZXhwb3J0LWZjcywgZXZhbD1GQUxTRX0KIyBzdWJzZXQgU2V1cmF0IG9iamVjdCB0byBzcGVjaWZpYyBjbHVzdGVyCnNldV9zdWIgPC0gc3Vic2V0KHNldSwgc3Vic2V0ID0gc2V1cmF0X2NsdXN0ZXJzID09IDEpCiMgZXhwb3J0IEZDUyBmaWxlIGNvbnRhaW5pbmcgY2VsbHMgZnJvbSB0aGlzIGNsdXN0ZXIKZXhwb3J0X2ZjcyhzZXVfc3ViLCBmaWxlbmFtZSA9ICJjbHVzdGVyXzEuZmNzIiwgYXNzYXkgPSAiZmNzIiwgc2xvdCA9ICJjb3VudHMiKQpgYGAK