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.
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.
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()))
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.
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 seu@misc 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)

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

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)

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.
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.
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")

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

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`

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.

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

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

Additional
visualizations
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")

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")

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")

Cell numbers
plot_cellnumber(seu, "sample_id")

Other features
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
Integration of
third-party tools
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):
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

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