Workflow Overview
Introduction
One of our goals in a single-cell analysis is to generate clusters
that reasonably approximate cell-types or sub-types of interest in our
samples before determining if there are differences in the proportions
of these populations or differences in gene expression within these
populations between experimental conditions.
|
A. The prior step used the filtered, normalized counts as input to a
prinicpal component analysis. The top principle components (PCs) are the
input to the clustering step. B. Cells are plotted in a
high-dimensional space (where the num dimesnions = num of included PCs,
e.g. 15 dimensions); cells with similar gene expression profiles are
closer to each other and are clustered together. C. The cells
clusters are projected from the high-dimensional space (e.g. 15
dimensions) down to two-dimensional space for visualization.
|
In this section, we will demonstrate how to generate clusters using
Seurat’s graph based clustering approach and visualize those clustering
assignments via a lower-dimensional projection of the full dataset.
Like other steps in our analysis, multiple parameters may need to be
tested and evaluated while we would expect that only the final would be
reported. Clustering is considered part of data exploration so an
iterative approach is reasonable, and often expected (source).
Objectives
- Understand the clustering process and input parameters
- Generate initial clusters using ``
- Visualize our clustering results
Clustering and projection
Now that we selected a number of PCs that we think are likely to
represent biological variation and integrated our data across
samples/batches, our next task is clustering.
An important aspect of parameter selection for clustering is to
understand the “resolution” of the underlying biology and your
experimental design:
- Is answering your biological question dependent on identifying rarer
cell types or specific subtypes?
- Or are broader cell-types more relevant to address your biological
question?
The OSCA book has a helpful
analogy comparing clustering to microscopy and points out that
“asking for an unqualified ‘best’ clustering is akin to asking for the
best magnification on a microscope without any context”.
To generate clusters, we will generate “communities” of cells using
the PCs we selected, before choosing a resolution parameter to divide
those communities into discrete clusters.
Clustering
Seurat uses a graph-based clustering approach to assign cells to
clusters using a distance metric based on the previously generated PCs,
with improvements based on work by (Xu
and Su 2015) and CyTOF data (Levine et
al. 2015) implemented in Seurat v3 and v5 and building on the
initial strategies for droplet-based single-cell technology (Macosko et
al. 2015) (source).
A key aspect of this process is that while the clusters are based on
similarity of expression between the cells, the clustering is based on
the selected PCs and not the full data set.
Image: kNN example - section on graph based
clustering (from Cambridge Bioinformatics course)
To briefly summarize, cells are embedded in a k-nearest neighbors
(kNN) graph (illustrated above) based on “the euclidean distance in PCA
space” between the cells and the edge weights between any two cells
(e.g. their “closeness”) is refined based on Jaccard similarity (source).
Additional context and sources for graph-based clustering
Cambridge
Bioinformatics’ Analysis of single cell RNA-seq data course
materials, the source of the image above, delves into kNN and other
graph based clustering methods in much greater detail, including
outlining possible downsides for these methods. To described kNN, we
have also drawn from the Ho
Lab’s description of this process for Seurat v3 as well as the HBC
materials on clustering and the OSCA
book’s more general overview of graph based clustering, which also
describes the drawbacks for these methods.
This process is performed with the FindNeighbors()
command,
using the number of principal components we selected in the previous
section.
The second step is to iteratively partition the kNN graph into
“cliques” or clusters using the Louvain modularity optimization
algorithm (for the default parameters), with the “granularity” of the
clusters set by a resolution
parameter (source).
Image: K-means clustering example (from
Cambridge Bioinformatics course)
We’ll use the FindClusters()
function,
selecting a resolution of 0.4
to start, although we could
also add other resolutions at this stage to look at in later steps. See
Waltman
and Jan van Eck (2013) for the underlying algorithms.
Again, how a “cell type” or “subtype” should be defined for your data
is important to consider in selecting a resolution - we’d start with a
higher resolution for smaller/more rare clusters and a lower resolution
for larger/more general clusters.
Then, when we look at the meta data we should see that cluster labels
have been added for each cell:
# Cluster PCAs ------------------------------------------------------------
# Create KNN graph with `FindNeighbors()`
geo_so = FindNeighbors(geo_so, dims = 1:pcs, reduction = 'integrated.sct.rpca')
# generate clusters
geo_so = FindClusters(geo_so, resolution = 0.4, cluster.name = 'integrated.sct.rpca.clusters')
# look at meta.data to see cluster labels
head(geo_so@meta.data)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
Number of nodes: 31560
Number of edges: 1047628
Running Louvain algorithm...
Maximum modularity in 10 random starts: 0.9428
Number of communities: 20
Elapsed time: 9 seconds
orig.ident nCount_RNA nFeature_RNA day replicate percent.mt nCount_SCT nFeature_SCT integrated.sct.rpca.clusters seurat_clusters
HODay0replicate1_AAACCTGAGAGAACAG-1 HO.Day0.replicate1 10234 3226 Day0 replicate1 1.240962 6062 2867 1 1
HODay0replicate1_AAACCTGGTCATGCAT-1 HO.Day0.replicate1 3158 1499 Day0 replicate1 7.536415 4607 1509 1 1
HODay0replicate1_AAACCTGTCAGAGCTT-1 HO.Day0.replicate1 13464 4102 Day0 replicate1 3.112002 5314 2370 6 6
HODay0replicate1_AAACGGGAGAGACTTA-1 HO.Day0.replicate1 577 346 Day0 replicate1 1.559792 3877 1031 10 10
HODay0replicate1_AAACGGGAGGCCCGTT-1 HO.Day0.replicate1 1189 629 Day0 replicate1 3.700589 4166 915 0 0
HODay0replicate1_AAACGGGCAACTGGCC-1 HO.Day0.replicate1 7726 2602 Day0 replicate1 2.938131 5865 2588 1 1
The result of FindNeighbors()
adds graph information to
the graphs
slot:
Image: Schematic after FindNeighbors().
The result of FindClusters()
adds two columns to the
meta.data
table and changes the active.ident
to the “seurat_clusters” column. In other words, the cells now belong to
clusters rather than to their orig.ident
.
Image: Schematic after FindClusters().
Generally it’s preferable to err on the side of too many clusters, as
they can be combined manually in later steps. In our experience, this is
another parameter that often needs to be iteratively revised and
reviewed.
Resolution parameters recommendations
More details on choosing a clustering resolution
The Seurat
clustering tutorial recommends selecting a resolution between 0.4 -
1.2 for datasets of approximately 3k cells, while the HBC
course recommends 0.4-1.4 for 3k-5k cells. However, in our
experience reasonable starting resolutions can be very dataset
dependent.
Cluster plots
To visualize the cell clusters, we can use dimensionality reduction
techniques to visualize and explore our large, high-dimensional dataset.
Two popular methods that are supported by Seurat are t-distributed
stochastic neighbor embedding (t-SNE) and Uniform Manifold Approximation
and Projection (UMAP) techniques. These techniques allow us to visualize
our high-dimensional single-cell data in 2D space and see if cells
grouped together within graph-based clusters co-localize in these
representations (source).
While we unfortunately don’t have time to compare and contrast tSNE,
and UMAP, we would highly recommend this blog post
contrasting tSNE and UMAP for illustrative examples. The Seurat
authors additionally caution that while these methods are useful for
data exploration, to avoid drawing biological conclusions solely based
on these visualizations (source).
To start this process, we’ll use the RunUMAP()
function to
calculate the UMAP reduction for our data. Notice how the previous
dimensionality choices carry through the downstream analysis and that
the number of PCs selected in the previous steps are included as an
argument.
# Create UMAP reduction ---------------------------------------------------
geo_so = RunUMAP(geo_so, dims = 1:pcs, reduction = 'integrated.sct.rpca', reduction.name = 'umap.integrated.sct.rpca')
geo_so # Notice a third reduction has been added: `umap.integrated.sct.rpca`
An object of class Seurat
46955 features across 31560 samples within 2 assays
Active assay: SCT (20466 features, 3000 variable features)
3 layers present: counts, data, scale.data
1 other assay present: RNA
3 dimensional reductions calculated: unintegrated.sct.pca, integrated.sct.rpca, umap.integrated.sct.rpca
The resulting Seurat object now has an additional
umap.integrated.sct.rpca
in the reduction
slot:
Image: Schematic after RunUMAP().
Visualizing and evaluating clustering
After we generate the UMAP reduction, we can then visualize the
results using the DimPlot()
function,
labeling our plot by the auto generated seurat_clusters
that correspond to the most recent clustering results generated.
At this stage, we want to determine if the clusters look fairly well
separated, if they seem to correspond to how cells are grouped in the
UMAP, and if the number of clusters is generally aligned with the
resolution of our biological question. Again, if there are “too many”
clusters that’s not necessarily a problem.
We can also look at the same UMAP labeled by day
to
visually inspect if the UMAP structure corresponds to the
day
.
# Visualize UMAP cluster --------------------------------------------------
# cluster ID labels
post_integration_umap_plot_clusters = DimPlot(geo_so, group.by = 'seurat_clusters', label = TRUE, reduction = 'umap.integrated.sct.rpca') + NoLegend()
post_integration_umap_plot_clusters
ggsave(filename = 'results/figures/umap_integrated_sct_clusters.png', plot = post_integration_umap_plot_clusters, width = 6, height = 6, units = 'in')
# clusters with labels, split by condition
post_integration_umap_plot_split_clusters = DimPlot(geo_so, group.by = 'seurat_clusters', split.by = 'day', label = TRUE, reduction = 'umap.integrated.sct.rpca') + NoLegend()
post_integration_umap_plot_split_clusters
ggsave(filename = 'results/figures/umap_integrated_sct_split_clusters.png', plot = post_integration_umap_plot_clusters, width = 14, height = 6, units = 'in')
# UMAP with day labels (note - we added this column to the meta-data yesterday)
post_integration_umap_plot_day = DimPlot(geo_so, group.by = 'day', label = FALSE, reduction = 'umap.integrated.sct.rpca')
post_integration_umap_plot_day
ggsave(filename = 'results/figures/umap_integrated_sct_day.png', plot = post_integration_umap_plot_day, width = 8, height = 6, units = 'in')
Similar to the PCA plots, the day
labeled UMAP can tell
us if technical sources of variation might be driving or stratifying the
clusters, which would suggest that the normalization and integration
steps should be revisted before proceeding.
Another approach is to evaluate the number of cells per cluster using
the table()
function, split by day
or split by
orig.ident
to see if the individual samples are driving any
of the UMAP structure:
# Make tables of cell counts ----------------------------------------------
# cells per cluster, split by condition
table(geo_so@meta.data$day, geo_so@meta.data$integrated.sct.rpca.clusters)
# cells per cluster per sample
table(geo_so@meta.data$orig.ident, geo_so@meta.data$integrated.sct.rpca.clusters)
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Day0 119 946 25 213 44 190 240 789 186 92 121 71 30 270 1 76 138 161 133 30
Day7 3561 1325 2919 2443 2693 1684 1237 133 597 552 542 684 650 351 570 334 97 285 93 105
Day21 1502 1320 39 325 148 484 375 449 453 452 266 166 68 123 2 135 271 40 194 18
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
HO.Day0.replicate1 31 279 10 72 20 58 64 196 50 18 23 22 11 71 0 14 22 48 34 10
HO.Day0.replicate2 15 132 8 33 7 35 24 165 29 24 20 8 7 52 1 15 25 13 13 3
HO.Day0.replicate3 48 329 2 66 4 56 90 221 64 26 36 20 5 75 0 25 41 59 52 3
HO.Day0.replicate4 25 206 5 42 13 41 62 207 43 24 42 21 7 72 0 22 50 41 34 14
HO.Day7.replicate1 521 343 1089 572 898 446 307 49 208 106 0 191 214 29 0 76 41 57 29 39
HO.Day7.replicate2 1269 285 600 658 552 522 276 12 153 163 0 197 159 242 0 101 18 87 15 15
HO.Day7.replicate3 990 530 767 759 565 454 422 63 145 187 0 194 158 57 0 129 28 96 37 45
HO.Day7.replicate4 781 167 463 454 678 262 232 9 91 96 542 102 119 23 570 28 10 45 12 6
HO.Day21.replicate1 382 415 13 85 50 152 140 131 134 131 68 56 16 47 0 40 71 13 58 2
HO.Day21.replicate2 198 233 7 64 25 85 51 140 71 68 56 25 14 24 1 33 53 5 28 5
HO.Day21.replicate3 218 199 4 65 23 97 57 117 85 78 44 24 18 20 0 22 56 7 38 6
HO.Day21.replicate4 704 473 15 111 50 150 127 61 163 175 98 61 20 32 1 40 91 15 70 5
Comparing to unintegrated data
If we had proceeded with our filtered data and only normalized our
data without doing any integration, including through the dimensionality
reduction and clustering steps and then labeled the cells with their
sample of origin, then we would see the following for our data:
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
Number of nodes: 31560
Number of edges: 998270
Running Louvain algorithm...
Maximum modularity in 10 random starts: 0.9475
Number of communities: 22
Elapsed time: 9 seconds
In the plot at left, we see that while there are distinct clusters,
those clusters seem to stratified by day. This suggests that without
integration, these batch effects could skew the biological variability
in our data. While on the right, we see little stratification within our
clusters which means the integration seems to have removed those batch
effects.
Rewind: Pre-integration evaluation clustering and visualization
(code)
Prior to integration, we could follow the same steps we’ve just run
for the integrated to see if the resulting clusters tend to be
determined by sample or condition (in this case, the day):
geo_so = FindNeighbors(geo_so, dims = 1:pcs, assay = 'RNA', reduction = 'unintegrated.sct.pca', graph.name = c('RNA_nn', 'RNA_snn'))
geo_so = FindClusters(geo_so, resolution = 0.4, graph.name = 'RNA_snn', cluster.name = 'unintegrated.sct.clusters')
geo_so = RunUMAP(geo_so, dims = 1:pcs, reduction = 'unintegrated.sct.pca', reduction.name = 'umap.unintegrated.sct.pca')
The plots above were generated with:
# Code block - show unintegrated
pre_integration_umap_plot_orig.ident = DimPlot(geo_so, group.by = 'orig.ident', label = FALSE, reduction = 'umap.unintegrated.sct.pca')
ggsave(filename = 'results/figures/umap_unintegrated_sct_orig.ident.png', plot = pre_integration_umap_plot_orig.ident, width = 8, height = 6, units = 'in')
pre_integration_umap_plot_day = DimPlot(geo_so, group.by = 'day', label = FALSE, reduction = 'umap.unintegrated.sct.pca')
ggsave(filename = 'results/figures/umap_unintegrated_sct_day.png', plot = pre_integration_umap_plot_day, width = 8, height = 6, units = 'in')
Alternative clustering resolutions
While we show a single resolution, we can generate and plot multiple
resolutions iteratively and compare between them before selecting a
clustering result for the next steps:
resolutions = c(0.4, 0.8)
for(res in resolutions) {
message(res)
cluster_column = sprintf('SCT_snn_res.%s', res)
umap_file = sprintf('results/figures/umap_integrated_sct_%s.png', res)
geo_so = FindClusters(geo_so, resolution = res)
DimPlot(geo_so, group.by = cluster_column, label = TRUE, reduction = 'umap.integrated.sct.rpca') + NoLegend()
ggsave(filename = umap_file, width = 8, height = 7, units = 'in')
}
In the results, we’ll see multiple resolutions should now be added to
the metadata slot.
head(geo_so@meta.data)
Save our progress
Before moving on to our next section, we will output our updated
Seurat object to file:
# Save Seurat object ------------------------------------------------------
saveRDS(geo_so, file = 'results/rdata/geo_so_sct_clustered.rds')
Summary
In this section we:
- Generated cluster assignments for our cells using
FindNeighbors()
and FindClusters()
- Evaluated our initial clusters using
RunUMAP
dimensional reduction and visualization
Next steps: Marker genes
These materials have been adapted and extended from materials listed
above. These are open access materials distributed under the terms of
the Creative
Commons Attribution license (CC BY 4.0), which permits unrestricted
use, distribution, and reproduction in any medium, provided the original
author and source are credited.
LS0tCnRpdGxlOiAiQ2x1c3RlcmluZyBhbmQgUHJvamVjdGlvbiIKYXV0aG9yOiAiVU0gQmlvaW5mb3JtYXRpY3MgQ29yZSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgICAgICAgaHRtbF9kb2N1bWVudDoKICAgICAgICAgICAgaW5jbHVkZXM6CiAgICAgICAgICAgICAgICBpbl9oZWFkZXI6IGhlYWRlci5odG1sCiAgICAgICAgICAgIHRoZW1lOiBwYXBlcgogICAgICAgICAgICB0b2M6IHRydWUKICAgICAgICAgICAgdG9jX2RlcHRoOiA0CiAgICAgICAgICAgIHRvY19mbG9hdDogdHJ1ZQogICAgICAgICAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICAgICAgICAgIGZpZ19jYXB0aW9uOiB0cnVlCiAgICAgICAgICAgIG1hcmtkb3duOiBHRk0KICAgICAgICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CmJvZHksIHRkIHsKICAgZm9udC1zaXplOiAxOHB4Owp9CmNvZGUucnsKICBmb250LXNpemU6IDEycHg7Cn0KcHJlIHsKICBmb250LXNpemU6IDEycHgKfQoKdGFibGUuZmlnLCB0aC5maWcsIHRkLmZpZyB7CiAgYm9yZGVyOiAxcHggc29saWQgYmxhY2s7CiAgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsKICBwYWRkaW5nOiAxNXB4Owp9Cgp0YWJsZS5maWcsIHRoLmZpZywgdGQuZmlnIHsKICBib3JkZXI6IDFweCBzb2xpZCBibGFjazsKICBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOwogIHBhZGRpbmc6IDE1cHg7Cn0KPC9zdHlsZT4KCmBgYHtyIGtsaXBweSwgZWNobz1GQUxTRSwgaW5jbHVkZT1UUlVFfQprbGlwcHk6OmtsaXBweShsYW5nID0gYygiciIsICJtYXJrZG93biIsICJiYXNoIiksIHBvc2l0aW9uID0gYygidG9wIiwgInJpZ2h0IikpCmBgYAoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kc291cmNlKCIuLi9iaW4vY2h1bmstb3B0aW9ucy5SIikKa25pdHJfZmlnX3BhdGgoIjA1LVByb2plY3Rpb25BbmRDbHVzdGVyaW5nLzA1LSIpCmBgYAoKIyBXb3JrZmxvdyBPdmVydmlldyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQoKPGJyLz4KPGltZyBzcmM9ImltYWdlcy93YXlmaW5kZXIvd2F5ZmluZGVyLnBuZyIgYWx0PSJ3YXlmaW5kZXIiIHN0eWxlPSJoZWlnaHQ6IDQwMHB4OyIvPgo8YnIvPgo8YnIvPgoKIyBJbnRyb2R1Y3Rpb24KCjwhLS0tIEdlbmVyYWwgZ29hbDogdG8gZ2VuZXJhdGUgY2x1c3RlcnMgdGhhdCByZWFzb25hYmx5IGFwcHJveGltYXRlIGNlbGwtdHlwZXMgb3Igc3ViLXR5cGVzIG9mIGludGVyZXN0IC0tLT4KT25lIG9mIG91ciBnb2FscyBpbiBhIHNpbmdsZS1jZWxsIGFuYWx5c2lzIGlzIHRvIGdlbmVyYXRlIGNsdXN0ZXJzIHRoYXQgcmVhc29uYWJseSBhcHByb3hpbWF0ZSBjZWxsLXR5cGVzIG9yIHN1Yi10eXBlcyBvZiBpbnRlcmVzdCBpbiBvdXIgc2FtcGxlcyBiZWZvcmUgZGV0ZXJtaW5pbmcgaWYgdGhlcmUgYXJlIGRpZmZlcmVuY2VzIGluIHRoZSBwcm9wb3J0aW9ucyBvZiB0aGVzZSBwb3B1bGF0aW9ucyBvciBkaWZmZXJlbmNlcyBpbiBnZW5lIGV4cHJlc3Npb24gd2l0aGluIHRoZXNlIHBvcHVsYXRpb25zIGJldHdlZW4gZXhwZXJpbWVudGFsIGNvbmRpdGlvbnMuCgo8dGFibGUgY2xhc3M9J2ZpZyc+Cjx0ciBjbGFzcz0nZmlnJz48dGQgY2xhc3M9J2ZpZyc+IVtdKGltYWdlcy9ncmFwaGljYWxfYWJzdHJhY3RzL2dyYXBoaWNhbF9hYnN0cmFjdF9jbHVzdGVyX3Byb2plY3Rpb24ucG5nKTwvdGQ+PC90cj4KPHRyIGNsYXNzPSdmaWcnPjx0ZCBjbGFzcz0nZmlnJz5BLiBUaGUgcHJpb3Igc3RlcCB1c2VkIHRoZSBmaWx0ZXJlZCwgbm9ybWFsaXplZCBjb3VudHMgYXMgaW5wdXQgdG8gYSBwcmluaWNwYWwgY29tcG9uZW50IGFuYWx5c2lzLiBUaGUgdG9wIHByaW5jaXBsZSBjb21wb25lbnRzIChQQ3MpIGFyZSB0aGUgaW5wdXQgdG8gdGhlIGNsdXN0ZXJpbmcgc3RlcC4gPGJyLz4KQi4gQ2VsbHMgYXJlIHBsb3R0ZWQgaW4gYSBoaWdoLWRpbWVuc2lvbmFsIHNwYWNlICh3aGVyZSB0aGUgbnVtIGRpbWVzbmlvbnMgPSBudW0gb2YgaW5jbHVkZWQgUENzLCBlLmcuIDE1IGRpbWVuc2lvbnMpOyBjZWxscyB3aXRoIHNpbWlsYXIgZ2VuZSBleHByZXNzaW9uIHByb2ZpbGVzIGFyZSBjbG9zZXIgdG8gZWFjaCBvdGhlciBhbmQgYXJlIGNsdXN0ZXJlZCB0b2dldGhlci4gPGJyLz4KQy4gVGhlIGNlbGxzIGNsdXN0ZXJzIGFyZSBwcm9qZWN0ZWQgZnJvbSB0aGUgaGlnaC1kaW1lbnNpb25hbCBzcGFjZSAoZS5nLiAxNSBkaW1lbnNpb25zKSBkb3duIHRvIHR3by1kaW1lbnNpb25hbCBzcGFjZSBmb3IgdmlzdWFsaXphdGlvbi4KPC90ZD48L3RyPgo8L3RhYmxlPgo8YnIvPgoKCkluIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCBkZW1vbnN0cmF0ZSBob3cgdG8gZ2VuZXJhdGUgY2x1c3RlcnMgdXNpbmcgU2V1cmF0J3MgZ3JhcGggYmFzZWQgY2x1c3RlcmluZyBhcHByb2FjaCBhbmQgdmlzdWFsaXplIHRob3NlIGNsdXN0ZXJpbmcgYXNzaWdubWVudHMgdmlhIGEgbG93ZXItZGltZW5zaW9uYWwgcHJvamVjdGlvbiBvZiB0aGUgZnVsbCBkYXRhc2V0LgoKTGlrZSBvdGhlciBzdGVwcyBpbiBvdXIgYW5hbHlzaXMsIG11bHRpcGxlIHBhcmFtZXRlcnMgbWF5IG5lZWQgdG8gYmUgdGVzdGVkIGFuZCBldmFsdWF0ZWQgd2hpbGUgd2Ugd291bGQgZXhwZWN0IHRoYXQgb25seSB0aGUgZmluYWwgd291bGQgYmUgcmVwb3J0ZWQuIENsdXN0ZXJpbmcgaXMgY29uc2lkZXJlZCBwYXJ0IG9mIGRhdGEgZXhwbG9yYXRpb24gc28gYW4gaXRlcmF0aXZlIGFwcHJvYWNoIGlzIHJlYXNvbmFibGUsIGFuZCBvZnRlbiBleHBlY3RlZCAoW3NvdXJjZV0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzLzMuMTUvT1NDQS5iYXNpYy9jbHVzdGVyaW5nLmh0bWwpKS4gCgoKIyMgT2JqZWN0aXZlcwoKLSBVbmRlcnN0YW5kIHRoZSBjbHVzdGVyaW5nIHByb2Nlc3MgYW5kIGlucHV0IHBhcmFtZXRlcnMKLSBHZW5lcmF0ZSBpbml0aWFsIGNsdXN0ZXJzIHVzaW5nIGBgIAotIFZpc3VhbGl6ZSBvdXIgY2x1c3RlcmluZyByZXN1bHRzCgotLS0KCmBgYHtyLCByZWFkX3Jkc19oaWRkZW4sIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmlmKCFleGlzdHMoJ2dlb19zbycpKSB7CiAgbGlicmFyeShTZXVyYXQpCiAgbGlicmFyeShCUENlbGxzKQogIGxpYnJhcnkodGlkeXZlcnNlKQoKICBvcHRpb25zKGZ1dHVyZS5nbG9iYWxzLm1heFNpemUgPSAxZTkpCgogIGdlb19zbyA9IHJlYWRSRFMoJ3Jlc3VsdHMvcmRhdGEvZ2VvX3NvX3NjdF9pbnRlZ3JhdGVkLnJkcycpCn0KYGBgCgojIENsdXN0ZXJpbmcgYW5kIHByb2plY3Rpb24KCk5vdyB0aGF0IHdlIHNlbGVjdGVkIGEgbnVtYmVyIG9mIFBDcyB0aGF0IHdlIHRoaW5rIGFyZSBsaWtlbHkgdG8gcmVwcmVzZW50IGJpb2xvZ2ljYWwgdmFyaWF0aW9uIGFuZCBpbnRlZ3JhdGVkIG91ciBkYXRhIGFjcm9zcyBzYW1wbGVzL2JhdGNoZXMsIG91ciBuZXh0IHRhc2sgaXMgY2x1c3RlcmluZy4gCgoKQW4gaW1wb3J0YW50IGFzcGVjdCBvZiBwYXJhbWV0ZXIgc2VsZWN0aW9uIGZvciBjbHVzdGVyaW5nIGlzIHRvIHVuZGVyc3RhbmQgdGhlICJyZXNvbHV0aW9uIiBvZiB0aGUgdW5kZXJseWluZyBiaW9sb2d5IGFuZCB5b3VyIGV4cGVyaW1lbnRhbCBkZXNpZ246ICAgCgotIElzIGFuc3dlcmluZyB5b3VyIGJpb2xvZ2ljYWwgcXVlc3Rpb24gZGVwZW5kZW50IG9uIGlkZW50aWZ5aW5nIHJhcmVyIGNlbGwgdHlwZXMgb3Igc3BlY2lmaWMgc3VidHlwZXM/ICAgCi0gT3IgYXJlIGJyb2FkZXIgY2VsbC10eXBlcyBtb3JlIHJlbGV2YW50IHRvIGFkZHJlc3MgeW91ciBiaW9sb2dpY2FsIHF1ZXN0aW9uPyAgIAoKVGhlIE9TQ0EgYm9vayBoYXMgYSBbaGVscGZ1bCBhbmFsb2d5IGNvbXBhcmluZyBjbHVzdGVyaW5nIHRvIG1pY3Jvc2NvcHldKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy8zLjE1L09TQ0EuYmFzaWMvY2x1c3RlcmluZy5odG1sI292ZXJ2aWV3LTEpIGFuZCBwb2ludHMgb3V0IHRoYXQgImFza2luZyBmb3IgYW4gdW5xdWFsaWZpZWQgJ2Jlc3QnIGNsdXN0ZXJpbmcgaXMgYWtpbiB0byBhc2tpbmcgZm9yIHRoZSBiZXN0IG1hZ25pZmljYXRpb24gb24gYSBtaWNyb3Njb3BlIHdpdGhvdXQgYW55IGNvbnRleHQiLiAKClRvIGdlbmVyYXRlIGNsdXN0ZXJzLCB3ZSB3aWxsIGdlbmVyYXRlICJjb21tdW5pdGllcyIgb2YgY2VsbHMgdXNpbmcgdGhlIFBDcyB3ZSBzZWxlY3RlZCwgYmVmb3JlIGNob29zaW5nIGEgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgdG8gZGl2aWRlIHRob3NlIGNvbW11bml0aWVzIGludG8gZGlzY3JldGUgY2x1c3RlcnMuCgo8IS0tLSBDb250cmFzdCB0aGUgcHJldmlvdXMgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIHZlcnN1cyBuZWFyZXN0IG5laWdoYm9ycyBjbHVzdGVyaW5nIGFuZCBwbG90dGluZyB0aGUgY2VsbHMgaW4gbG93ZXIgZGltZW5zaW9uYWxpdHkgd2l0aCB0aGUgY2x1c3RlciBsYWJlbHM/IC0tLT4KCiMjIENsdXN0ZXJpbmcKClNldXJhdCB1c2VzIGEgZ3JhcGgtYmFzZWQgY2x1c3RlcmluZyBhcHByb2FjaCB0byBhc3NpZ24gY2VsbHMgdG8gY2x1c3RlcnMgdXNpbmcgYSBkaXN0YW5jZSBtZXRyaWMgYmFzZWQgb24gdGhlIHByZXZpb3VzbHkgZ2VuZXJhdGVkIFBDcywgd2l0aCBpbXByb3ZlbWVudHMgYmFzZWQgb24gd29yayBieSAoW1h1IGFuZCBTdSAyMDE1XShodHRwczovL2FjYWRlbWljLm91cC5jb20vYmlvaW5mb3JtYXRpY3MvYXJ0aWNsZS8zMS8xMi8xOTc0LzIxNDUwNSkpIGFuZCBDeVRPRiBkYXRhIChbTGV2aW5lIGV0IGFsLiAyMDE1XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM0NTA4NzU3LykpIGltcGxlbWVudGVkIGluIFNldXJhdCB2MyBhbmQgdjUgYW5kIGJ1aWxkaW5nIG9uIHRoZSBpbml0aWFsIHN0cmF0ZWdpZXMgZm9yIGRyb3BsZXQtYmFzZWQgc2luZ2xlLWNlbGwgdGVjaG5vbG9neSAoW01hY29za28gZXQgYWwuIDIwMTVdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzQ0ODExMzkvKSkgIChbc291cmNlXShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L2FydGljbGVzL3BibWMza190dXRvcmlhbC5odG1sKSkuIEEga2V5IGFzcGVjdCBvZiB0aGlzIHByb2Nlc3MgaXMgdGhhdCB3aGlsZSB0aGUgY2x1c3RlcnMgYXJlIGJhc2VkIG9uIHNpbWlsYXJpdHkgb2YgZXhwcmVzc2lvbiBiZXR3ZWVuIHRoZSBjZWxscywgdGhlIGNsdXN0ZXJpbmcgaXMgYmFzZWQgb24gdGhlIHNlbGVjdGVkIFBDcyBhbmQgbm90IHRoZSBmdWxsIGRhdGEgc2V0LiAKCjxjZW50ZXI+CiFbSW1hZ2U6IGtOTiBleGFtcGxlIC0gc2VjdGlvbiBvbiBncmFwaCBiYXNlZCBjbHVzdGVyaW5nIChmcm9tIENhbWJyaWRnZSBCaW9pbmZvcm1hdGljcyBjb3Vyc2UpXSguL2ltYWdlcy9jdXJyaWN1bHVtLzA1LVByb2plY3Rpb25BbmRDbHVzdGVyaW5nL0Jpb0NlbGxHZW5lLU5lYXJlc3ROZWlnaGJvck5ldHdvcmtzLnBuZykKPC9jZW50ZXI+CgpUbyBicmllZmx5IHN1bW1hcml6ZSwgY2VsbHMgYXJlIGVtYmVkZGVkIGluIGEgay1uZWFyZXN0IG5laWdoYm9ycyAoa05OKSBncmFwaCAoaWxsdXN0cmF0ZWQgYWJvdmUpIGJhc2VkIG9uICJ0aGUgZXVjbGlkZWFuIGRpc3RhbmNlIGluIFBDQSBzcGFjZSIgYmV0d2VlbiB0aGUgY2VsbHMgYW5kIHRoZSBlZGdlIHdlaWdodHMgYmV0d2VlbiBhbnkgdHdvIGNlbGxzIChlLmcuIHRoZWlyICJjbG9zZW5lc3MiKSBpcyByZWZpbmVkIGJhc2VkIG9uIEphY2NhcmQgc2ltaWxhcml0eSAoW3NvdXJjZV0oaHR0cHM6Ly9oYmN0cmFpbmluZy5naXRodWIuaW8vc2NSTkEtc2VxX29ubGluZS9sZXNzb25zLzA3X1NDX2NsdXN0ZXJpbmdfY2VsbHNfU0NULmh0bWwpKS4KCjxkZXRhaWxzPgogICAgPHN1bW1hcnk+KkFkZGl0aW9uYWwgY29udGV4dCBhbmQgc291cmNlcyBmb3IgZ3JhcGgtYmFzZWQgY2x1c3RlcmluZyo8L3N1bW1hcnk+CiAgICBbQ2FtYnJpZGdlIEJpb2luZm9ybWF0aWNzJyBBbmFseXNpcyBvZiBzaW5nbGUgY2VsbCBSTkEtc2VxIGRhdGEgY291cnNlIG1hdGVyaWFsc10oaHR0cHM6Ly9iaW9jZWxsZ2VuLXB1YmxpYy5zdmkuZWR1LmF1L21pZ18yMDE5X3Njcm5hc2VxLXdvcmtzaG9wL2NsdXN0ZXJpbmctYW5kLWNlbGwtYW5ub3RhdGlvbi5odG1sKSwgdGhlIHNvdXJjZSBvZiB0aGUgaW1hZ2UgYWJvdmUsIGRlbHZlcyBpbnRvIGtOTiBhbmQgb3RoZXIgZ3JhcGggYmFzZWQgY2x1c3RlcmluZyBtZXRob2RzIGluIG11Y2ggZ3JlYXRlciBkZXRhaWwsIGluY2x1ZGluZyBvdXRsaW5pbmcgcG9zc2libGUgZG93bnNpZGVzIGZvciB0aGVzZSBtZXRob2RzLiAKICAgICBUbyBkZXNjcmliZWQga05OLCB3ZSBoYXZlIGFsc28gZHJhd24gZnJvbSB0aGUgW0hvIExhYidzIGRlc2NyaXB0aW9uIG9mIHRoaXMgcHJvY2VzcyBmb3IgU2V1cmF0IHYzXShodHRwczovL2hvbGFiLWhrdS5naXRodWIuaW8vRnVuZGFtZW50YWwtc2NSTkEvZG93bnN0cmVhbS5odG1sI3BlcmZvcm0tbGluZWFyLWRpbWVuc2lvbmFsLXJlZHVjdGlvbikgYXMgd2VsbCBhcyB0aGUgW0hCQyBtYXRlcmlhbHMgb24gY2x1c3RlcmluZ10oaHR0cHM6Ly9oYmN0cmFpbmluZy5naXRodWIuaW8vc2NSTkEtc2VxX29ubGluZS9sZXNzb25zLzA3X1NDX2NsdXN0ZXJpbmdfY2VsbHNfU0NULmh0bWwpIGFuZCB0aGUgW09TQ0EgYm9vaydzIG1vcmUgZ2VuZXJhbCBvdmVydmlldyBvZiBncmFwaCBiYXNlZCBjbHVzdGVyaW5nXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvYm9va3MvMy4xNS9PU0NBLmJhc2ljL2NsdXN0ZXJpbmcuaHRtbCNjbHVzdGVyaW5nLWdyYXBoKSwgd2hpY2ggYWxzbyBkZXNjcmliZXMgdGhlIGRyYXdiYWNrcyBmb3IgdGhlc2UgbWV0aG9kcy4KPC9kZXRhaWxzPgo8YnI+CgpUaGlzIHByb2Nlc3MgaXMgcGVyZm9ybWVkIHdpdGggdGhlIGBGaW5kTmVpZ2hib3JzKClgIFtjb21tYW5kXShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L3JlZmVyZW5jZS9maW5kbmVpZ2hib3JzKSwgdXNpbmcgdGhlIG51bWJlciBvZiBwcmluY2lwYWwgY29tcG9uZW50cyB3ZSBzZWxlY3RlZCBpbiB0aGUgcHJldmlvdXMgc2VjdGlvbi4KClRoZSBzZWNvbmQgc3RlcCBpcyB0byBpdGVyYXRpdmVseSBwYXJ0aXRpb24gdGhlIGtOTiBncmFwaCBpbnRvICJjbGlxdWVzIiBvciBjbHVzdGVycyB1c2luZyB0aGUgTG91dmFpbiBtb2R1bGFyaXR5IG9wdGltaXphdGlvbiBhbGdvcml0aG0gKGZvciB0aGUgZGVmYXVsdCBwYXJhbWV0ZXJzKSwgd2l0aCB0aGUgImdyYW51bGFyaXR5IiBvZiB0aGUgY2x1c3RlcnMgc2V0IGJ5IGEgYHJlc29sdXRpb25gIHBhcmFtZXRlciAoW3NvdXJjZV0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC9hcnRpY2xlcy9wYm1jM2tfdHV0b3JpYWwuaHRtbCkpLgoKIVtJbWFnZTogSy1tZWFucyBjbHVzdGVyaW5nIGV4YW1wbGUgKGZyb20gQ2FtYnJpZGdlIEJpb2luZm9ybWF0aWNzIGNvdXJzZSldKC4vaW1hZ2VzL2N1cnJpY3VsdW0vMDUtUHJvamVjdGlvbkFuZENsdXN0ZXJpbmcvSEJDLTA3LWstbWVhbnNGaWd1cmUucG5nKQoKV2UnbGwgdXNlIHRoZSBgRmluZENsdXN0ZXJzKClgIFtmdW5jdGlvbl0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC9yZWZlcmVuY2UvZmluZGNsdXN0ZXJzKSwgc2VsZWN0aW5nIGEgcmVzb2x1dGlvbiBvZiBgMC40YCB0byBzdGFydCwgYWx0aG91Z2ggd2UgY291bGQgYWxzbyBhZGQgb3RoZXIgcmVzb2x1dGlvbnMgYXQgdGhpcyBzdGFnZSB0byBsb29rIGF0IGluIGxhdGVyIHN0ZXBzLiBTZWUgW1dhbHRtYW4gYW5kIEphbiB2YW4gRWNrICgyMDEzKV0oaHR0cHM6Ly9saW5rLnNwcmluZ2VyLmNvbS9hcnRpY2xlLzEwLjExNDAvZXBqYi9lMjAxMy00MDgyOS0wKSBmb3IgdGhlIHVuZGVybHlpbmcgYWxnb3JpdGhtcy4KCkFnYWluLCBob3cgYSDigJxjZWxsIHR5cGXigJ0gb3Ig4oCcc3VidHlwZeKAnSBzaG91bGQgYmUgZGVmaW5lZCBmb3IgeW91ciBkYXRhIGlzIGltcG9ydGFudCB0byBjb25zaWRlciBpbiBzZWxlY3RpbmcgYSByZXNvbHV0aW9uIC0gd2UnZCBzdGFydCB3aXRoIGEgaGlnaGVyIHJlc29sdXRpb24gZm9yIHNtYWxsZXIvbW9yZSByYXJlIGNsdXN0ZXJzIGFuZCBhIGxvd2VyIHJlc29sdXRpb24gZm9yIGxhcmdlci9tb3JlIGdlbmVyYWwgY2x1c3RlcnMuICAKClRoZW4sIHdoZW4gd2UgbG9vayBhdCB0aGUgbWV0YSBkYXRhIHdlIHNob3VsZCBzZWUgdGhhdCBjbHVzdGVyIGxhYmVscyBoYXZlIGJlZW4gYWRkZWQgZm9yIGVhY2ggY2VsbDoKCmBgYHtyLCBmaW5kX25laWdoYm9ycywgZXZhbCA9IEZBTFNFfQojIENsdXN0ZXIgUENBcyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBDcmVhdGUgS05OIGdyYXBoIHdpdGggYEZpbmROZWlnaGJvcnMoKWAKZ2VvX3NvID0gRmluZE5laWdoYm9ycyhnZW9fc28sIGRpbXMgPSAxOnBjcywgcmVkdWN0aW9uID0gJ2ludGVncmF0ZWQuc2N0LnJwY2EnKQoKIyBnZW5lcmF0ZSBjbHVzdGVycwpnZW9fc28gPSBGaW5kQ2x1c3RlcnMoZ2VvX3NvLCByZXNvbHV0aW9uID0gMC40LCBjbHVzdGVyLm5hbWUgPSAnaW50ZWdyYXRlZC5zY3QucnBjYS5jbHVzdGVycycpCgojIGxvb2sgYXQgbWV0YS5kYXRhIHRvIHNlZSBjbHVzdGVyIGxhYmVscwpoZWFkKGdlb19zb0BtZXRhLmRhdGEpCmBgYAoKYGBge3IsIGZpbmRfbmVpZ2hib3JzX2hpZGRlbiwgY2FjaGUgPSBUUlVFLCBjYWNoZS5sYXp5ID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBlY2hvPUZBTFNFfQojIENsdXN0ZXIgUENBcyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBDcmVhdGUgS05OIGdyYXBoIHdpdGggYEZpbmROZWlnaGJvcnMoKWAKZ2VvX3NvID0gRmluZE5laWdoYm9ycyhnZW9fc28sIGRpbXMgPSAxOnBjcywgcmVkdWN0aW9uID0gJ2ludGVncmF0ZWQuc2N0LnJwY2EnKQoKIyBnZW5lcmF0ZSBjbHVzdGVycwpnZW9fc28gPSBGaW5kQ2x1c3RlcnMoZ2VvX3NvLCByZXNvbHV0aW9uID0gMC40LCBjbHVzdGVyLm5hbWUgPSAnaW50ZWdyYXRlZC5zY3QucnBjYS5jbHVzdGVycycpCgojIGxvb2sgYXQgbWV0YS5kYXRhIHRvIHNlZSBjbHVzdGVyIGxhYmVscwpoZWFkKGdlb19zb0BtZXRhLmRhdGEpCmBgYAoKVGhlIHJlc3VsdCBvZiBgRmluZE5laWdoYm9ycygpYCBhZGRzIGdyYXBoIGluZm9ybWF0aW9uIHRvIHRoZSBgZ3JhcGhzYCBzbG90OgoKIVtJbWFnZTogU2NoZW1hdGljIGFmdGVyIEZpbmROZWlnaGJvcnMoKS5dKGltYWdlcy9zZXVyYXRfc2NoZW1hdGljL1NsaWRlOC5wbmcpCgo8YnI+CgpUaGUgcmVzdWx0IG9mIGBGaW5kQ2x1c3RlcnMoKWAgYWRkcyB0d28gY29sdW1ucyB0byB0aGUgYG1ldGEuZGF0YWAgdGFibGUgYW5kIGNoYW5nZXMgdGhlIGBhY3RpdmUuaWRlbnRgIHRvIHRoZSAic2V1cmF0X2NsdXN0ZXJzIiBjb2x1bW4uIEluIG90aGVyIHdvcmRzLCB0aGUgY2VsbHMgbm93IGJlbG9uZyB0byBjbHVzdGVycyByYXRoZXIgdGhhbiB0byB0aGVpciBgb3JpZy5pZGVudGAuCgohW0ltYWdlOiBTY2hlbWF0aWMgYWZ0ZXIgRmluZENsdXN0ZXJzKCkuXShpbWFnZXMvc2V1cmF0X3NjaGVtYXRpYy9TbGlkZTkucG5nKQoKR2VuZXJhbGx5IGl0J3MgcHJlZmVyYWJsZSB0byBlcnIgb24gdGhlIHNpZGUgb2YgdG9vIG1hbnkgY2x1c3RlcnMsIGFzIHRoZXkgY2FuIGJlIGNvbWJpbmVkIG1hbnVhbGx5IGluIGxhdGVyIHN0ZXBzLiBJbiBvdXIgZXhwZXJpZW5jZSwgdGhpcyBpcyBhbm90aGVyIHBhcmFtZXRlciB0aGF0IG9mdGVuIG5lZWRzIHRvIGJlIGl0ZXJhdGl2ZWx5IHJldmlzZWQgYW5kIHJldmlld2VkLiAKCiMjIyBSZXNvbHV0aW9uIHBhcmFtZXRlcnMgcmVjb21tZW5kYXRpb25zCjxkZXRhaWxzPgogICAgPHN1bW1hcnk+Kk1vcmUgZGV0YWlscyBvbiBjaG9vc2luZyBhIGNsdXN0ZXJpbmcgcmVzb2x1dGlvbio8L3N1bW1hcnk+CiAgICBUaGUgW1NldXJhdCBjbHVzdGVyaW5nIHR1dG9yaWFsXShodHRwczovL2hvbGFiLWhrdS5naXRodWIuaW8vRnVuZGFtZW50YWwtc2NSTkEvZG93bnN0cmVhbS5odG1sI3BlcmZvcm0tbGluZWFyLWRpbWVuc2lvbmFsLXJlZHVjdGlvbikgcmVjb21tZW5kcyBzZWxlY3RpbmcgYSByZXNvbHV0aW9uIGJldHdlZW4gMC40IC0gMS4yIGZvciBkYXRhc2V0cyBvZiBhcHByb3hpbWF0ZWx5IDNrIGNlbGxzLCB3aGlsZSB0aGUgW0hCQyBjb3Vyc2VdKGh0dHBzOi8vaGJjdHJhaW5pbmcuZ2l0aHViLmlvL3NjUk5BLXNlcV9vbmxpbmUvbGVzc29ucy8wN19TQ19jbHVzdGVyaW5nX2NlbGxzX1NDVC5odG1sKSByZWNvbW1lbmRzIDAuNC0xLjQgZm9yIDNrLTVrIGNlbGxzLiBIb3dldmVyLCBpbiBvdXIgZXhwZXJpZW5jZSByZWFzb25hYmxlIHN0YXJ0aW5nIHJlc29sdXRpb25zIGNhbiBiZSB2ZXJ5IGRhdGFzZXQgZGVwZW5kZW50Lgo8L2RldGFpbHM+Cjxicj4KCiMgQ2x1c3RlciBwbG90cyAKClRvIHZpc3VhbGl6ZSB0aGUgY2VsbCBjbHVzdGVycywgd2UgY2FuIHVzZSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdGVjaG5pcXVlcyB0byB2aXN1YWxpemUgYW5kIGV4cGxvcmUgb3VyIGxhcmdlLCBoaWdoLWRpbWVuc2lvbmFsIGRhdGFzZXQuIFR3byBwb3B1bGFyIG1ldGhvZHMgdGhhdCBhcmUgc3VwcG9ydGVkIGJ5IFNldXJhdCBhcmUgdC1kaXN0cmlidXRlZCBzdG9jaGFzdGljIG5laWdoYm9yIGVtYmVkZGluZyAodC1TTkUpIGFuZCBVbmlmb3JtIE1hbmlmb2xkIEFwcHJveGltYXRpb24gYW5kIFByb2plY3Rpb24gKFVNQVApIHRlY2huaXF1ZXMuIFRoZXNlIHRlY2huaXF1ZXMgYWxsb3cgdXMgdG8gdmlzdWFsaXplIG91ciBoaWdoLWRpbWVuc2lvbmFsIHNpbmdsZS1jZWxsIGRhdGEgaW4gMkQgc3BhY2UgYW5kIHNlZSBpZiBjZWxscyBncm91cGVkIHRvZ2V0aGVyIHdpdGhpbiBncmFwaC1iYXNlZCBjbHVzdGVycyBjby1sb2NhbGl6ZSBpbiB0aGVzZSByZXByZXNlbnRhdGlvbnMgKFtzb3VyY2VdKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvYXJ0aWNsZXMvcGJtYzNrX3R1dG9yaWFsLmh0bWwjcnVuLW5vbi1saW5lYXItZGltZW5zaW9uYWwtcmVkdWN0aW9uLXVtYXB0c25lKSkuCgpXaGlsZSB3ZSB1bmZvcnR1bmF0ZWx5IGRvbid0IGhhdmUgdGltZSB0byBjb21wYXJlIGFuZCBjb250cmFzdCB0U05FLCBhbmQgVU1BUCwgd2Ugd291bGQgaGlnaGx5IHJlY29tbWVuZCBbdGhpcyBibG9nIHBvc3QgY29udHJhc3RpbmcgdFNORSBhbmQgVU1BUF0oaHR0cHM6Ly9wYWlyLWNvZGUuZ2l0aHViLmlvL3VuZGVyc3RhbmRpbmctdW1hcC8pIGZvciBpbGx1c3RyYXRpdmUgZXhhbXBsZXMuIFRoZSBTZXVyYXQgYXV0aG9ycyBhZGRpdGlvbmFsbHkgY2F1dGlvbiB0aGF0IHdoaWxlIHRoZXNlIG1ldGhvZHMgYXJlIHVzZWZ1bCBmb3IgZGF0YSBleHBsb3JhdGlvbiwgdG8gYXZvaWQgZHJhd2luZyBiaW9sb2dpY2FsIGNvbmNsdXNpb25zIHNvbGVseSBiYXNlZCBvbiB0aGVzZSB2aXN1YWxpemF0aW9ucyAoW3NvdXJjZV0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC9hcnRpY2xlcy9wYm1jM2tfdHV0b3JpYWwuaHRtbCNydW4tbm9uLWxpbmVhci1kaW1lbnNpb25hbC1yZWR1Y3Rpb24tdW1hcHRzbmUpKS4KClRvIHN0YXJ0IHRoaXMgcHJvY2Vzcywgd2UnbGwgdXNlIHRoZSBgUnVuVU1BUCgpYCBbZnVuY3Rpb25dKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvcmVmZXJlbmNlL3J1bnVtYXApIHRvIGNhbGN1bGF0ZSB0aGUgVU1BUCByZWR1Y3Rpb24gZm9yIG91ciBkYXRhLiBOb3RpY2UgaG93IHRoZSBwcmV2aW91cyBkaW1lbnNpb25hbGl0eSBjaG9pY2VzIGNhcnJ5IHRocm91Z2ggdGhlIGRvd25zdHJlYW0gYW5hbHlzaXMgYW5kIHRoYXQgdGhlIG51bWJlciBvZiBQQ3Mgc2VsZWN0ZWQgaW4gdGhlIHByZXZpb3VzIHN0ZXBzIGFyZSBpbmNsdWRlZCBhcyBhbiBhcmd1bWVudC4KCmBgYHtyLCBydW5fdW1hcCwgY2FjaGUgPSBUUlVFLCBjYWNoZS5sYXp5ID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIENyZWF0ZSBVTUFQIHJlZHVjdGlvbiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KZ2VvX3NvID0gUnVuVU1BUChnZW9fc28sIGRpbXMgPSAxOnBjcywgcmVkdWN0aW9uID0gJ2ludGVncmF0ZWQuc2N0LnJwY2EnLCByZWR1Y3Rpb24ubmFtZSA9ICd1bWFwLmludGVncmF0ZWQuc2N0LnJwY2EnKQpnZW9fc28gIyBOb3RpY2UgYSB0aGlyZCByZWR1Y3Rpb24gaGFzIGJlZW4gYWRkZWQ6IGB1bWFwLmludGVncmF0ZWQuc2N0LnJwY2FgCmBgYAoKVGhlIHJlc3VsdGluZyBTZXVyYXQgb2JqZWN0IG5vdyBoYXMgYW4gYWRkaXRpb25hbCBgdW1hcC5pbnRlZ3JhdGVkLnNjdC5ycGNhYCBpbiB0aGUgYHJlZHVjdGlvbmAgc2xvdDoKCiFbSW1hZ2U6IFNjaGVtYXRpYyBhZnRlciBSdW5VTUFQKCkuXShpbWFnZXMvc2V1cmF0X3NjaGVtYXRpYy9TbGlkZTEwLnBuZykKCiMgVmlzdWFsaXppbmcgYW5kIGV2YWx1YXRpbmcgY2x1c3RlcmluZwoKPCEtLS0gSG93IG1hbnkgY2x1c3RlcnMgc2hvdWxkIEkgZ2V0IGFuZCBob3cgZG8gSSBhZGp1c3QgdGhlIG51bWJlcj8gLS0tPgoKPCEtLS0gQWRkIGV4YW1wbGUgb2YgY2hhbmdpbmcgcmVzb2x1dGlvbj8tLS0+CgpBZnRlciB3ZSBnZW5lcmF0ZSB0aGUgVU1BUCByZWR1Y3Rpb24sIHdlIGNhbiB0aGVuIHZpc3VhbGl6ZSB0aGUgcmVzdWx0cyB1c2luZyB0aGUgYERpbVBsb3QoKWAgW2Z1bmN0aW9uXShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L3JlZmVyZW5jZS9kaW1wbG90KSwgbGFiZWxpbmcgb3VyIHBsb3QgYnkgdGhlIGF1dG8gZ2VuZXJhdGVkIGBzZXVyYXRfY2x1c3RlcnNgIHRoYXQgY29ycmVzcG9uZCB0byB0aGUgbW9zdCByZWNlbnQgY2x1c3RlcmluZyByZXN1bHRzIGdlbmVyYXRlZC4KCkF0IHRoaXMgc3RhZ2UsIHdlIHdhbnQgdG8gZGV0ZXJtaW5lIGlmIHRoZSBjbHVzdGVycyBsb29rIGZhaXJseSB3ZWxsIHNlcGFyYXRlZCwgaWYgdGhleSBzZWVtIHRvIGNvcnJlc3BvbmQgdG8gaG93IGNlbGxzIGFyZSBncm91cGVkIGluIHRoZSBVTUFQLCBhbmQgaWYgdGhlIG51bWJlciBvZiBjbHVzdGVycyBpcyBnZW5lcmFsbHkgYWxpZ25lZCB3aXRoIHRoZSByZXNvbHV0aW9uIG9mIG91ciBiaW9sb2dpY2FsIHF1ZXN0aW9uLiBBZ2FpbiwgaWYgdGhlcmUgYXJlICJ0b28gbWFueSIgY2x1c3RlcnMgdGhhdCdzIG5vdCBuZWNlc3NhcmlseSBhIHByb2JsZW0uCgpXZSBjYW4gYWxzbyBsb29rIGF0IHRoZSBzYW1lIFVNQVAgbGFiZWxlZCBieSBgZGF5YCB0byB2aXN1YWxseSBpbnNwZWN0IGlmIHRoZSBVTUFQIHN0cnVjdHVyZSBjb3JyZXNwb25kcyB0byB0aGUgYGRheWAuCgpgYGB7ciwgdW1hcF9ieV9jbHVzdGVycywgZmlnLnNob3c9J2hvbGQnfQojIFZpc3VhbGl6ZSBVTUFQIGNsdXN0ZXIgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBjbHVzdGVyIElEIGxhYmVscwpwb3N0X2ludGVncmF0aW9uX3VtYXBfcGxvdF9jbHVzdGVycyA9IERpbVBsb3QoZ2VvX3NvLCBncm91cC5ieSA9ICdzZXVyYXRfY2x1c3RlcnMnLCBsYWJlbCA9IFRSVUUsIHJlZHVjdGlvbiA9ICd1bWFwLmludGVncmF0ZWQuc2N0LnJwY2EnKSArIE5vTGVnZW5kKCkKcG9zdF9pbnRlZ3JhdGlvbl91bWFwX3Bsb3RfY2x1c3RlcnMKCmdnc2F2ZShmaWxlbmFtZSA9ICdyZXN1bHRzL2ZpZ3VyZXMvdW1hcF9pbnRlZ3JhdGVkX3NjdF9jbHVzdGVycy5wbmcnLCBwbG90ID0gcG9zdF9pbnRlZ3JhdGlvbl91bWFwX3Bsb3RfY2x1c3RlcnMsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNiwgdW5pdHMgPSAnaW4nKQoKIyBjbHVzdGVycyB3aXRoIGxhYmVscywgc3BsaXQgYnkgY29uZGl0aW9uCnBvc3RfaW50ZWdyYXRpb25fdW1hcF9wbG90X3NwbGl0X2NsdXN0ZXJzID0gRGltUGxvdChnZW9fc28sIGdyb3VwLmJ5ID0gJ3NldXJhdF9jbHVzdGVycycsIHNwbGl0LmJ5ID0gJ2RheScsIGxhYmVsID0gVFJVRSwgcmVkdWN0aW9uID0gJ3VtYXAuaW50ZWdyYXRlZC5zY3QucnBjYScpICsgTm9MZWdlbmQoKQpwb3N0X2ludGVncmF0aW9uX3VtYXBfcGxvdF9zcGxpdF9jbHVzdGVycwoKZ2dzYXZlKGZpbGVuYW1lID0gJ3Jlc3VsdHMvZmlndXJlcy91bWFwX2ludGVncmF0ZWRfc2N0X3NwbGl0X2NsdXN0ZXJzLnBuZycsIHBsb3QgPSBwb3N0X2ludGVncmF0aW9uX3VtYXBfcGxvdF9jbHVzdGVycywgd2lkdGggPSAxNCwgaGVpZ2h0ID0gNiwgdW5pdHMgPSAnaW4nKQoKIyBVTUFQIHdpdGggZGF5IGxhYmVscyAobm90ZSAtIHdlIGFkZGVkIHRoaXMgY29sdW1uIHRvIHRoZSBtZXRhLWRhdGEgeWVzdGVyZGF5KQpwb3N0X2ludGVncmF0aW9uX3VtYXBfcGxvdF9kYXkgPSBEaW1QbG90KGdlb19zbywgZ3JvdXAuYnkgPSAnZGF5JywgbGFiZWwgPSBGQUxTRSwgcmVkdWN0aW9uID0gJ3VtYXAuaW50ZWdyYXRlZC5zY3QucnBjYScpCnBvc3RfaW50ZWdyYXRpb25fdW1hcF9wbG90X2RheQoKZ2dzYXZlKGZpbGVuYW1lID0gJ3Jlc3VsdHMvZmlndXJlcy91bWFwX2ludGVncmF0ZWRfc2N0X2RheS5wbmcnLCBwbG90ID0gcG9zdF9pbnRlZ3JhdGlvbl91bWFwX3Bsb3RfZGF5LCB3aWR0aCA9IDgsIGhlaWdodCA9IDYsIHVuaXRzID0gJ2luJykKYGBgCgpTaW1pbGFyIHRvIHRoZSBQQ0EgcGxvdHMsIHRoZSBgZGF5YCBsYWJlbGVkIFVNQVAgY2FuIHRlbGwgdXMgaWYgdGVjaG5pY2FsIHNvdXJjZXMgb2YgdmFyaWF0aW9uIG1pZ2h0IGJlIGRyaXZpbmcgb3Igc3RyYXRpZnlpbmcgdGhlIGNsdXN0ZXJzLCB3aGljaCB3b3VsZCBzdWdnZXN0IHRoYXQgdGhlIG5vcm1hbGl6YXRpb24gYW5kIGludGVncmF0aW9uIHN0ZXBzIHNob3VsZCBiZSByZXZpc3RlZCBiZWZvcmUgcHJvY2VlZGluZy4KCkFub3RoZXIgYXBwcm9hY2ggaXMgdG8gZXZhbHVhdGUgdGhlIG51bWJlciBvZiBjZWxscyBwZXIgY2x1c3RlciB1c2luZyB0aGUgYHRhYmxlKClgIGZ1bmN0aW9uLCBzcGxpdCBieSBgZGF5YCBvciBzcGxpdCBieSBgb3JpZy5pZGVudGAgdG8gc2VlIGlmIHRoZSBpbmRpdmlkdWFsIHNhbXBsZXMgYXJlIGRyaXZpbmcgYW55IG9mIHRoZSBVTUFQIHN0cnVjdHVyZToKCmBgYHtyLCB0YWJsZV9ieV9kYXksIGV2YWwgPSBGQUxTRX0KIyBNYWtlIHRhYmxlcyBvZiBjZWxsIGNvdW50cyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgY2VsbHMgcGVyIGNsdXN0ZXIsIHNwbGl0IGJ5IGNvbmRpdGlvbgp0YWJsZShnZW9fc29AbWV0YS5kYXRhJGRheSwgZ2VvX3NvQG1ldGEuZGF0YSRpbnRlZ3JhdGVkLnNjdC5ycGNhLmNsdXN0ZXJzKQoKIyBjZWxscyBwZXIgY2x1c3RlciBwZXIgc2FtcGxlCnRhYmxlKGdlb19zb0BtZXRhLmRhdGEkb3JpZy5pZGVudCwgZ2VvX3NvQG1ldGEuZGF0YSRpbnRlZ3JhdGVkLnNjdC5ycGNhLmNsdXN0ZXJzKQpgYGAKCmBgYHtyLCB0YWJsZV9ieV9kYXlfaGlkZGVuLCBlY2hvID0gRkFMU0V9CiMgTWFrZSB0YWJsZXMgb2YgY2VsbCBjb3VudHMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIGNlbGxzIHBlciBjbHVzdGVyLCBzcGxpdCBieSBjb25kaXRpb24KdGFibGUoZ2VvX3NvQG1ldGEuZGF0YSRkYXksIGdlb19zb0BtZXRhLmRhdGEkaW50ZWdyYXRlZC5zY3QucnBjYS5jbHVzdGVycykKCiMgY2VsbHMgcGVyIGNsdXN0ZXIgcGVyIHNhbXBsZQp0YWJsZShnZW9fc29AbWV0YS5kYXRhJG9yaWcuaWRlbnQsIGdlb19zb0BtZXRhLmRhdGEkaW50ZWdyYXRlZC5zY3QucnBjYS5jbHVzdGVycykKYGBgCgo8IS0tIGNvbnNpZGVyIGFkZGluZyBsYXRlciAKQWRkIERpbVBsb3QgZm9yICUgbWl0b2Nob25kcmlhIHRvIHNlZSBpZiBoaWdoICUgYXJlIGFjcm9zcyBkYXRhc2V0IG9yIGxpbWl0ZWQgdG8gb25lL2ZldyBjbHVzdGVycyAocmVsYXRlZCB0byB3aGF0IE9saXZpYSB0YWxrZWQgYWJvdXQgZHVyaW5nIERheSAxKQoKIyBPdGhlciBhcHByb2FjaGVzIGZvciB2aXN1YWxpemluZyBzY1JOQS1zcSBkYXRhCmUuZy4gY29kZSBmb3IgdFNORSB2aXN1YWxpemF0aW9uIAotLT4KCiMgQ29tcGFyaW5nIHRvIHVuaW50ZWdyYXRlZCBkYXRhCgpJZiB3ZSBoYWQgcHJvY2VlZGVkIHdpdGggb3VyIGZpbHRlcmVkIGRhdGEgYW5kIG9ubHkgbm9ybWFsaXplZCBvdXIgZGF0YSB3aXRob3V0IGRvaW5nIGFueSBpbnRlZ3JhdGlvbiwgaW5jbHVkaW5nIHRocm91Z2ggdGhlIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBhbmQgY2x1c3RlcmluZyBzdGVwcyBhbmQgdGhlbiBsYWJlbGVkIHRoZSBjZWxscyB3aXRoIHRoZWlyIHNhbXBsZSBvZiBvcmlnaW4sIHRoZW4gd2Ugd291bGQgc2VlIHRoZSBmb2xsb3dpbmcgZm9yIG91ciBkYXRhOgoKPCEtLUFkZCB1cGRhdGVkIGV4YW1wbGUgb2YgVU1BUCAvIHNhbXBsZSBsYWJlbHMgZm9yIHVuaW50ZWdyYXRlZCBkYXRhIC0gbmVlZCB0byBjaGVjayB3aXRoIFJheW1vbmQgcmVnYXJkaW5nIGhvdyBiZXN0IHRvIGRvIHRoaXMgaW4gcGxhY2UgLS0+IAoKYGBge3IsIHJ1bl91bWFwX3VuaW50ZWdyYXRlZCwgZWNobyA9IEZBTFNFLCBjYWNoZSA9IFRSVUUsIGNhY2hlLmxhenkgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIG91dC5oZWlnaHQ9JzYwJSd9CiMgQ3JlYXRlIEtOTiBncmFwaCB3aXRoIGBGaW5kTmVpZ2hib3JzKClgIGZvciB1bmludGVncmF0ZWQgZGF0YQpnZW9fc28gPSBGaW5kTmVpZ2hib3JzKGdlb19zbywgZGltcyA9IDE6cGNzLCByZWR1Y3Rpb24gPSAndW5pbnRlZ3JhdGVkLnNjdC5wY2EnLCB2ZXJib3NlPUZBTFNFKQoKIyBnZW5lcmF0ZSBjbHVzdGVycyBmb3IgdW5pbnRlZ3JhdGVkIGRhdGEKZ2VvX3NvID0gRmluZENsdXN0ZXJzKGdlb19zbywgcmVzb2x1dGlvbiA9IDAuNCwgY2x1c3Rlci5uYW1lID0gJ3VuaW50ZWdyYXRlZC5zY3QucGNhLmNsdXN0ZXJzJykKCmdlb19zbyA9IFJ1blVNQVAoZ2VvX3NvLCBkaW1zID0gMTpwY3MsIHJlZHVjdGlvbiA9ICd1bmludGVncmF0ZWQuc2N0LnBjYScsIHJlZHVjdGlvbi5uYW1lID0gJ3VtYXAudW5pbnRlZ3JhdGVkLnNjdC5wY2EnKQoKcHJlX2ludGVncmF0aW9uX3VtYXBfcGxvdF9kYXkgPSBEaW1QbG90KGdlb19zbywgZ3JvdXAuYnkgPSAnZGF5JywgbGFiZWwgPSBGQUxTRSwgcmVkdWN0aW9uID0gJ3VtYXAudW5pbnRlZ3JhdGVkLnNjdC5wY2EnKQoKcHJpbnQocHJlX2ludGVncmF0aW9uX3VtYXBfcGxvdF9kYXkgKyBwb3N0X2ludGVncmF0aW9uX3VtYXBfcGxvdF9kYXkpCmBgYAoKSW4gdGhlIHBsb3QgYXQgbGVmdCwgd2Ugc2VlIHRoYXQgd2hpbGUgdGhlcmUgYXJlIGRpc3RpbmN0IGNsdXN0ZXJzLCB0aG9zZSBjbHVzdGVycyBzZWVtIHRvIHN0cmF0aWZpZWQgYnkgZGF5LiBUaGlzIHN1Z2dlc3RzIHRoYXQgd2l0aG91dCBpbnRlZ3JhdGlvbiwgdGhlc2UgYmF0Y2ggZWZmZWN0cyBjb3VsZCBza2V3IHRoZSBiaW9sb2dpY2FsIHZhcmlhYmlsaXR5IGluIG91ciBkYXRhLiBXaGlsZSBvbiB0aGUgcmlnaHQsIHdlIHNlZSBsaXR0bGUgc3RyYXRpZmljYXRpb24gd2l0aGluIG91ciBjbHVzdGVycyB3aGljaCBtZWFucyB0aGUgaW50ZWdyYXRpb24gc2VlbXMgdG8gaGF2ZSByZW1vdmVkIHRob3NlIGJhdGNoIGVmZmVjdHMuCgo8IS0tIEVkaXQgc2VjdGlvbiB0byBpbmNvcnBvcmF0ZSAtLS0+Cgo8ZGV0YWlscz4KPHN1bW1hcnk+KipSZXdpbmQ6IFByZS1pbnRlZ3JhdGlvbiBldmFsdWF0aW9uIGNsdXN0ZXJpbmcgYW5kIHZpc3VhbGl6YXRpb24gKGNvZGUpKio8L3N1bW1hcnk+ClByaW9yIHRvIGludGVncmF0aW9uLCB3ZSBjb3VsZCBmb2xsb3cgdGhlIHNhbWUgc3RlcHMgd2UndmUganVzdCBydW4gZm9yIHRoZSBpbnRlZ3JhdGVkIHRvIHNlZSBpZiB0aGUgcmVzdWx0aW5nIGNsdXN0ZXJzIHRlbmQgdG8gYmUgZGV0ZXJtaW5lZCBieSBzYW1wbGUgb3IgY29uZGl0aW9uIChpbiB0aGlzIGNhc2UsIHRoZSBkYXkpOgoKYGBge3IsIGV2YWw9RkFMU0V9Cmdlb19zbyA9IEZpbmROZWlnaGJvcnMoZ2VvX3NvLCBkaW1zID0gMTpwY3MsIGFzc2F5ID0gJ1JOQScsIHJlZHVjdGlvbiA9ICd1bmludGVncmF0ZWQuc2N0LnBjYScsIGdyYXBoLm5hbWUgPSBjKCdSTkFfbm4nLCAnUk5BX3NubicpKQpnZW9fc28gPSBGaW5kQ2x1c3RlcnMoZ2VvX3NvLCByZXNvbHV0aW9uID0gMC40LCBncmFwaC5uYW1lID0gJ1JOQV9zbm4nLCBjbHVzdGVyLm5hbWUgPSAndW5pbnRlZ3JhdGVkLnNjdC5jbHVzdGVycycpCmdlb19zbyA9IFJ1blVNQVAoZ2VvX3NvLCBkaW1zID0gMTpwY3MsIHJlZHVjdGlvbiA9ICd1bmludGVncmF0ZWQuc2N0LnBjYScsIHJlZHVjdGlvbi5uYW1lID0gJ3VtYXAudW5pbnRlZ3JhdGVkLnNjdC5wY2EnKQpgYGAKCQpUaGUgcGxvdHMgYWJvdmUgd2VyZSBnZW5lcmF0ZWQgd2l0aDoKCmBgYHtyLCBldmFsPUZBTFNFfQojIENvZGUgYmxvY2sgLSBzaG93IHVuaW50ZWdyYXRlZApwcmVfaW50ZWdyYXRpb25fdW1hcF9wbG90X29yaWcuaWRlbnQgPSBEaW1QbG90KGdlb19zbywgZ3JvdXAuYnkgPSAnb3JpZy5pZGVudCcsIGxhYmVsID0gRkFMU0UsIHJlZHVjdGlvbiA9ICd1bWFwLnVuaW50ZWdyYXRlZC5zY3QucGNhJykKZ2dzYXZlKGZpbGVuYW1lID0gJ3Jlc3VsdHMvZmlndXJlcy91bWFwX3VuaW50ZWdyYXRlZF9zY3Rfb3JpZy5pZGVudC5wbmcnLCBwbG90ID0gcHJlX2ludGVncmF0aW9uX3VtYXBfcGxvdF9vcmlnLmlkZW50LCB3aWR0aCA9IDgsIGhlaWdodCA9IDYsIHVuaXRzID0gJ2luJykKCnByZV9pbnRlZ3JhdGlvbl91bWFwX3Bsb3RfZGF5ID0gRGltUGxvdChnZW9fc28sIGdyb3VwLmJ5ID0gJ2RheScsIGxhYmVsID0gRkFMU0UsIHJlZHVjdGlvbiA9ICd1bWFwLnVuaW50ZWdyYXRlZC5zY3QucGNhJykKZ2dzYXZlKGZpbGVuYW1lID0gJ3Jlc3VsdHMvZmlndXJlcy91bWFwX3VuaW50ZWdyYXRlZF9zY3RfZGF5LnBuZycsIHBsb3QgPSBwcmVfaW50ZWdyYXRpb25fdW1hcF9wbG90X2RheSwgd2lkdGggPSA4LCBoZWlnaHQgPSA2LCB1bml0cyA9ICdpbicpCmBgYAo8L2RldGFpbHM+Cjxicj4KPGJyPgoKPGRldGFpbHM+CjxzdW1tYXJ5PioqQWx0ZXJuYXRpdmUgY2x1c3RlcmluZyByZXNvbHV0aW9ucyoqPC9zdW1tYXJ5PgpXaGlsZSB3ZSBzaG93IGEgc2luZ2xlIHJlc29sdXRpb24sIHdlIGNhbiBnZW5lcmF0ZSBhbmQgcGxvdCBtdWx0aXBsZSByZXNvbHV0aW9ucyBpdGVyYXRpdmVseSBhbmQgY29tcGFyZSBiZXR3ZWVuIHRoZW0gYmVmb3JlIHNlbGVjdGluZyBhIGNsdXN0ZXJpbmcgcmVzdWx0IGZvciB0aGUgbmV4dCBzdGVwczoKCmBgYHtyLCBldmFsPUZBTFNFfQpyZXNvbHV0aW9ucyA9IGMoMC40LCAwLjgpCgpmb3IocmVzIGluIHJlc29sdXRpb25zKSB7CiAgICBtZXNzYWdlKHJlcykKCiAgICBjbHVzdGVyX2NvbHVtbiA9IHNwcmludGYoJ1NDVF9zbm5fcmVzLiVzJywgcmVzKQogICAgdW1hcF9maWxlID0gc3ByaW50ZigncmVzdWx0cy9maWd1cmVzL3VtYXBfaW50ZWdyYXRlZF9zY3RfJXMucG5nJywgcmVzKQoKICAgIGdlb19zbyA9IEZpbmRDbHVzdGVycyhnZW9fc28sIHJlc29sdXRpb24gPSByZXMpCgogICAgRGltUGxvdChnZW9fc28sIGdyb3VwLmJ5ID0gY2x1c3Rlcl9jb2x1bW4sIGxhYmVsID0gVFJVRSwgcmVkdWN0aW9uID0gJ3VtYXAuaW50ZWdyYXRlZC5zY3QucnBjYScpICsgTm9MZWdlbmQoKQogICAgZ2dzYXZlKGZpbGVuYW1lID0gdW1hcF9maWxlLCB3aWR0aCA9IDgsIGhlaWdodCA9IDcsIHVuaXRzID0gJ2luJykKfQpgYGAKCkluIHRoZSByZXN1bHRzLCB3ZSdsbCBzZWUgbXVsdGlwbGUgcmVzb2x1dGlvbnMgc2hvdWxkIG5vdyBiZSBhZGRlZCB0byB0aGUgbWV0YWRhdGEgc2xvdC4KCmBgYHtyLCBldmFsPUZBTFNFfQpoZWFkKGdlb19zb0BtZXRhLmRhdGEpCmBgYAo8L2RldGFpbHM+Cjxicj4KPGJyPgoKIyBTYXZlIG91ciBwcm9ncmVzcwoKQmVmb3JlIG1vdmluZyBvbiB0byBvdXIgbmV4dCBzZWN0aW9uLCB3ZSB3aWxsIG91dHB1dCBvdXIgdXBkYXRlZCBTZXVyYXQgb2JqZWN0IHRvIGZpbGU6CgpgYGB7ciwgc2F2ZV9yZHNfaGlkZGVuLCBlY2hvID0gRkFMU0V9CmlmKCFmaWxlLmV4aXN0cygncmVzdWx0cy9yZGF0YS9nZW9fc29fc2N0X2NsdXN0ZXJlZC5yZHMnKSkgewogIHNhdmVSRFMoZ2VvX3NvLCBmaWxlID0gJ3Jlc3VsdHMvcmRhdGEvZ2VvX3NvX3NjdF9jbHVzdGVyZWQucmRzJykKfQpgYGAKCmBgYHtyLCBldmFsPUZBTFNFfQojIFNhdmUgU2V1cmF0IG9iamVjdCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0Kc2F2ZVJEUyhnZW9fc28sIGZpbGUgPSAncmVzdWx0cy9yZGF0YS9nZW9fc29fc2N0X2NsdXN0ZXJlZC5yZHMnKQpgYGAKCiMgU3VtbWFyeQoKSW4gdGhpcyBzZWN0aW9uIHdlOgoKLSBHZW5lcmF0ZWQgY2x1c3RlciBhc3NpZ25tZW50cyBmb3Igb3VyIGNlbGxzIHVzaW5nIGBGaW5kTmVpZ2hib3JzKClgIGFuZCBgRmluZENsdXN0ZXJzKClgCi0gRXZhbHVhdGVkIG91ciBpbml0aWFsIGNsdXN0ZXJzIHVzaW5nIGBSdW5VTUFQYCBkaW1lbnNpb25hbCByZWR1Y3Rpb24gYW5kIHZpc3VhbGl6YXRpb24KCk5leHQgc3RlcHM6IE1hcmtlciBnZW5lcwoKLS0tLQoKVGhlc2UgbWF0ZXJpYWxzIGhhdmUgYmVlbiBhZGFwdGVkIGFuZCBleHRlbmRlZCBmcm9tIG1hdGVyaWFscyBsaXN0ZWQgYWJvdmUuIFRoZXNlIGFyZSBvcGVuIGFjY2VzcyBtYXRlcmlhbHMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBbQ3JlYXRpdmUgQ29tbW9ucyBBdHRyaWJ1dGlvbiBsaWNlbnNlIChDQyBCWSA0LjApXShodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS80LjAvKSwgd2hpY2ggcGVybWl0cyB1bnJlc3RyaWN0ZWQgdXNlLCBkaXN0cmlidXRpb24sIGFuZCByZXByb2R1Y3Rpb24gaW4gYW55IG1lZGl1bSwgcHJvdmlkZWQgdGhlIG9yaWdpbmFsIGF1dGhvciBhbmQgc291cmNlIGFyZSBjcmVkaXRlZC4KCjxici8+Cjxici8+Cjxoci8+CnwgW1ByZXZpb3VzIGxlc3Nvbl0oMDQtUENBYW5kSW50ZWdyYXRpb24uaHRtbCkgfCBbVG9wIG9mIHRoaXMgbGVzc29uXSgjdG9wKSB8IFtOZXh0IGxlc3Nvbl0oMDYtTWFya2VyVmlzdWFsaXphdGlvbi5odG1sKSB8CnwgOi0tLSB8IDotLS0tOiB8IC0tLTogfAo=