Introduction
To interpret our initial clustering results for all cells in our
experiment, we’ll need to characterize the gene expression driving
separation between the clusters.
|
Expression profiles (A) for for each cell cluster are evaluated against
all other clusters (B) to identify marker genes, i.e. genes useful for
segregating the cells into clusters (C).
|
In this section, we will demonstrate: - How to generate “analytical”
markers based on gene expression in each cluster - How to visualize the
expression of genes of interest to aid in cluster identification and
labeling
This process can be a highly variable, from seeing well-characterized
marker genes as top markers to needing to dig through the literature and
plot the expression for multiple sets of genes of interest. Like the
previous sections (and like other areas of research), when this process
is more involved or iterative, often only the final results are
reported.
Objectives
- Determine gene markers for each of the clusters using
FindAllMarkers()
- Visualize expression across clusters for genes of interest using
DotPlot()
Cluster markers and characterization
After generating clusters, we need to perform differential expression
analysis to identify the genes that distinguish those clusters (source).
This should allow us to get visibility on some key questions for our
clusters, as highlighted
by the HBC materials, namely:
- Are there are biologically meaningful gene expression differences
between the clusters?
- Does the gene expression of the generated clusters correspond to
expected cell-types or sub-types?
- Are there clusters with similar expression that should be combined
and/or clusters that might need to be sub-clustered into smaller
populations?
Many of the differential expression (DE) tools designed for bulk
RNA-seq samples have been benchmarked for performance on scRNA-seq in Soneson and Robinson
(2018) and there are also dedicated DE tools for scRNA-seq, like MAST,
that use models that account for the expected sparse structure of
scRNA-seq data. However in our and others’ experience, the default
Wilcoxon test is often sufficient for simple pairwise DE comparisons,
while edgeR (Robinson, McCarthy, and
Smyth 2010) is recommended by the Ouyang
Lab for more complex comparison designs, such as those that include
covariates.
Image: Comparison of the performance of DE
methods applied to scRNA-seq datasets. Methods are ranked by their
average performance across all the listed criteria. Image taken from
Soneson and Robinson (2018).
Additional considerations for differential expression
The Ouyang Lab has a section
of their tutorial that discusses the methods available for
differential expression including some highlighted in the figure below,
as well as a more extensive section on threshold
considerations, while the HBC
section on marker genes identification highlights the different
types of marker identification options available via Seurat.
Marker identification
First, we’ll change identities of the cells to
“integrated.sct.rpca.clusters” explicitly with SetIdent()
.
The “seurat_clusters” column is the default column for cell identities
and changes each time a new clustering is performed. Then, we’ll ensure
that the correct resolution is selected from our Seurat object and then
we’ll use the PrepSCTFindMarkers()
function
in preparation for DE comaprisons to “reverse the individual SCT
regression model using minimum of median UMI as the sequencing depth
covariate” according to the Seurat
documentation. Remember that we’ve performed integration and
clustering to assign the cells to clusters regardless of their
experimental condition but that we now want to ensure that the data is
normalized but not with the SCTransformation scaling needed for the
previous steps.
Then we’ll run theFindAllMarkers()
function
to generate comparisons between each cluster and all other cells,
regardless of the experimental group. Note - the statistical test to
perform can be specified in FindAllMarkers()
, but the
default is a Wilcoxon test.
##### Day 3 - Marker identification and visualization
# Find empirical markers -------------------------------------------------
# Prep for cluster comparisons
geo_so = SetIdent(geo_so, value = 'integrated.sct.rpca.clusters')
geo_so = PrepSCTFindMarkers(geo_so)
# Run comparisons for each cluster to generate markers
geo_markers = FindAllMarkers(geo_so, only.pos = TRUE, min.pct = 0.05)
# Take a look at the first few rows of the result
head(geo_markers)
p_val avg_log2FC pct.1 pct.2 p_val_adj cluster gene
Col12a1 0 4.223843 0.915 0.132 0 0 Col12a1
Thbs2 0 3.851482 0.896 0.151 0 0 Thbs2
Lox 0 3.026665 0.904 0.163 0 0 Lox
Tnc 0 3.936619 0.823 0.098 0 0 Tnc
Col11a1 0 4.996025 0.753 0.062 0 0 Col11a1
Aebp1 0 2.294299 0.972 0.295 0 0 Aebp1
# Write out full cluster marker results to file
write_csv(geo_markers, file = 'results/tables/marker_genes_0.4res.csv')
Image: Schematic after SetIdent().
Seurat v5 improvements
For marker generation, Seurat v5 uses the presto
package to reduce the time required to run DE comparisons,
particularly for large datasets. For users who are not using presto,
Seurat recommends increasing the min.pct and logfc.threshold
parameterscto increase the speed of DE testing (source).
Note that over-interpretation of these results should be avoided,
since each cell is used as a replicate in these comparisons which can
lead to inflated (e.g. very low) p-values, the top markers are more
likely to be trustworthy (source).
Therefore, it’s useful to filter the results to highlight the top
positive markers (since a positive fold-change would mean that gene is
more highly expressed in the cluster compared to all other cells),
before looking at our results
# Identify marker genes for each cluster ----------------------------------
# Create table of top 5 markers per cluster (using default ranking)
top_5 = geo_markers %>% filter(p_val_adj < 0.01) %>% group_by(cluster) %>% slice_head(n = 5)
# Look at results
head(top_5, n = 10)
# A tibble: 10 × 7
# Groups: cluster [2]
p_val avg_log2FC pct.1 pct.2 p_val_adj cluster gene
<dbl> <dbl> <dbl> <dbl> <dbl> <fct> <chr>
1 0 4.22 0.915 0.132 0 0 Col12a1
2 0 3.85 0.896 0.151 0 0 Thbs2
3 0 3.03 0.904 0.163 0 0 Lox
4 0 3.94 0.823 0.098 0 0 Tnc
5 0 5.00 0.753 0.062 0 0 Col11a1
6 0 4.28 0.883 0.108 0 1 Clec3b
7 0 2.83 0.953 0.29 0 1 Serping1
8 0 3.38 0.903 0.242 0 1 Igfbp6
9 0 4.07 0.725 0.07 0 1 Tnxb
10 0 2.91 0.764 0.147 0 1 Rarres2
# Optional - Create table of top 5 markers per cluster (ranked by logFC)
top_5_by_log2FC = geo_markers %>% group_by(cluster) %>% arrange(p_val_adj, desc(avg_log2FC)) %>% slice_head(n = 5)
# Look at results after ranking
head(top_5_by_log2FC, n = 10) # notice difference in pct.1 column between tables
# A tibble: 10 × 7
# Groups: cluster [2]
p_val avg_log2FC pct.1 pct.2 p_val_adj cluster gene
<dbl> <dbl> <dbl> <dbl> <dbl> <fct> <chr>
1 0 6.32 0.127 0.004 0 0 Cilp2
2 0 5.84 0.418 0.013 0 0 Scx
3 0 5.68 0.133 0.005 0 0 Kera
4 0 5.66 0.083 0.003 0 0 Ucma
5 0 5.66 0.08 0.003 0 0 Matn3
6 0 5.22 0.347 0.018 0 1 Myoc
7 0 5.18 0.153 0.006 0 1 Inmt
8 0 5.15 0.213 0.008 0 1 C7
9 0 4.97 0.096 0.006 0 1 Dact2
10 0 4.86 0.162 0.006 0 1 Lvrn
We expect to see several columns:
gene
: gene symbol
p_val
: p-value not adjusted for multiple test
correction
avg_logFC
: average log fold change. Positive values
indicate that the gene is more highly expressed in the cluster.
pct.1
: percentage of cells where the gene is detected
in the cluster
pct.2
: percentage of cells where the gene is detected
on average across all other clusters
p_val_adj
: adjusted p-value based on bonferroni
correction using all genes in the dataset, used to determine
significance
cluster
: cluster represented by pct.1
and
for which the statistics in the row are reported
When looking at the output, it is important to prioritize marker
genes with both larger fold-change differences and larger difference
between pct.1 and pct.2, particularly if pct.1 is high (e.g. if 80% of
cells in the cluster evaluated express the gene that more reliable than
if only 20% of cells express that gene) (source).
Marker visualization
Now that we have generated a set of marker genes for our clusters, it
is useful to visualize the expression of those markers to aid in
evaluating them. While the expression of individual genes per cell can
be overlaid on our UMAPs (as with the FeaturePlot()
function),
it’s often more useful to visualize the expression of multiple genes
simultaneously. While there are multiple
methods supported by Seurat for visualizing marker gene expression,
a heatmap or a related plot called a dotplot are commonly used.
We’ll use the DotPlot()
function with
the SCT values to visualize the top 5 marker genes per cluster:
# Visualize top marker genes as dot plot ----------------------------------
top_5_sct_dot_plot = DotPlot(geo_so, features = unique(top_5$gene)) +
theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) +
labs(title = 'Top 5 Cluster Genes by FDR and avg_log2FC') + coord_flip()
top_5_sct_dot_plot
ggsave(filename = 'results/figures/markers_top_5_sct_dot_plot.png', plot = top_5_sct_dot_plot, width = 8, height = 18, units = 'in')
In the dotplot we can see that the color indicates the expression of
the gene while the size of the dot indicates the proportion of cells
expressing that gene in each cluster (source).
Using raw RNA values in Dotplots
In addition to plotting the SCT values, the raw or normalized RNA
values can be plotted as well:
# Add RNA values to dot plot ---------------------------------------------
top_5_rna_dot_plot = DotPlot(geo_so, features = unique(top_5_by_log2FC$gene), assay = 'RNA') +
theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) +
labs(title = 'Top 5 Cluster Genes by FDR and avg_log2FC') + coord_flip()
ggsave(filename = 'results/figures/markers_top_5_rna_dot_plot.png', plot = top_5_rna_dot_plot, width = 8, height = 18, units = 'in')
Before proceeding with cluster annotations, we’ll also check the
percentage of mitochondrial genes to determine if there are any clusters
(or sub populations) that might correspond to interesting cell death
patterns (or might indicate further filtering is needed):
# Check mitochondrial gene expression -------------------------------------
percent_mito_plot = FeaturePlot(geo_so, features='percent.mt')
percent_mito_plot
# save to file
ggsave(filename = 'results/figures/percent_umap_mito_plot.png', plot = percent_mito_plot, width = 6, height = 6, units = 'in')
We see that a higher % seem to be somewhat concentrated in a few
places, but if cell death might be of interest for the research
question, we’d want to consider investigating this pattern further by
splitting up the plots by day and/or waiting until after running initial
differential expression analysis to determine if these cell populations
are interesting biology or not.
Summary
Now that we have characterized the expression of both analytical
marker genes and literature / knowledge-based marker genes, we may have
a better sense of what cell-types or subtypes our clusters might
correspond to.
However, marker genes alone might not be sufficient to determine
cell-type or sub-type labels for our clusters so we will discuss other
more automated approaches to complement these results.
Next steps: Cell type prediction tools
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.
LS0tCnRpdGxlOiAiTWFya2VyIGlkZW50aWZpY2F0aW9uIGFuZCB2aXN1YWxpemF0aW9uIgphdXRob3I6ICJVTSBCaW9pbmZvcm1hdGljcyBDb3JlIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICAgICAgICBodG1sX2RvY3VtZW50OgogICAgICAgICAgICBpbmNsdWRlczoKICAgICAgICAgICAgICAgIGluX2hlYWRlcjogaGVhZGVyLmh0bWwKICAgICAgICAgICAgdGhlbWU6IHBhcGVyCiAgICAgICAgICAgIHRvYzogdHJ1ZQogICAgICAgICAgICB0b2NfZGVwdGg6IDQKICAgICAgICAgICAgdG9jX2Zsb2F0OiB0cnVlCiAgICAgICAgICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UKICAgICAgICAgICAgZmlnX2NhcHRpb246IHRydWUKICAgICAgICAgICAgbWFya2Rvd246IEdGTQogICAgICAgICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KYm9keSwgdGQgewogICBmb250LXNpemU6IDE4cHg7Cn0KY29kZS5yewogIGZvbnQtc2l6ZTogMTJweDsKfQpwcmUgewogIGZvbnQtc2l6ZTogMTJweAp9Cgp0YWJsZS5maWcsIHRoLmZpZywgdGQuZmlnIHsKICBib3JkZXI6IDFweCBzb2xpZCBibGFjazsKICBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOwogIHBhZGRpbmc6IDE1cHg7Cn0KPC9zdHlsZT4KCmBgYHtyIGtsaXBweSwgZWNobz1GQUxTRSwgaW5jbHVkZT1UUlVFfQprbGlwcHk6OmtsaXBweShsYW5nID0gYygiciIsICJtYXJrZG93biIsICJiYXNoIiksIHBvc2l0aW9uID0gYygidG9wIiwgInJpZ2h0IikpCmBgYAoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kc291cmNlKCIuLi9iaW4vY2h1bmstb3B0aW9ucy5SIikKa25pdHJfZmlnX3BhdGgoIjA2LU1hcmtlclZpc3VhbGl6YXRpb24vMDYtIikKYGBgCgojIFdvcmtmbG93IE92ZXJ2aWV3IHsudW5saXN0ZWQgLnVubnVtYmVyZWR9Cgo8YnIvPgo8aW1nIHNyYz0iaW1hZ2VzL3dheWZpbmRlci93YXlmaW5kZXIucG5nIiBhbHQ9IndheWZpbmRlciIgc3R5bGU9ImhlaWdodDogNDAwcHg7Ii8+Cjxici8+Cjxici8+CgojIEludHJvZHVjdGlvbgoKVG8gaW50ZXJwcmV0IG91ciBpbml0aWFsIGNsdXN0ZXJpbmcgcmVzdWx0cyBmb3IgYWxsIGNlbGxzIGluIG91ciBleHBlcmltZW50LCB3ZSdsbCBuZWVkIHRvIGNoYXJhY3Rlcml6ZSB0aGUgZ2VuZSBleHByZXNzaW9uIGRyaXZpbmcgc2VwYXJhdGlvbiBiZXR3ZWVuIHRoZSBjbHVzdGVycy4gCgo8dGFibGUgY2xhc3M9J2ZpZyc+Cjx0ciBjbGFzcz0nZmlnJz48dGQgY2xhc3M9J2ZpZyc+IVtdKGltYWdlcy9ncmFwaGljYWxfYWJzdHJhY3RzL2dyYXBoaWNhbF9hYnN0cmFjdF9tYXJrZXJfaWQucG5nKTwvdGQ+PC90cj4KPHRyIGNsYXNzPSdmaWcnPjx0ZCBjbGFzcz0nZmlnJz5FeHByZXNzaW9uIHByb2ZpbGVzIChBKSBmb3IgZm9yIGVhY2ggY2VsbCBjbHVzdGVyIGFyZSBldmFsdWF0ZWQgYWdhaW5zdCBhbGwgb3RoZXIgY2x1c3RlcnMgKEIpIHRvIGlkZW50aWZ5IG1hcmtlciBnZW5lcywgaS5lLiBnZW5lcyB1c2VmdWwgZm9yIHNlZ3JlZ2F0aW5nIHRoZSBjZWxscyBpbnRvIGNsdXN0ZXJzIChDKS4gCjwvdGQ+PC90cj4KPC90YWJsZT4KPGJyLz4KCkluIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCBkZW1vbnN0cmF0ZToKLSBIb3cgdG8gZ2VuZXJhdGUgImFuYWx5dGljYWwiIG1hcmtlcnMgYmFzZWQgb24gZ2VuZSBleHByZXNzaW9uIGluIGVhY2ggY2x1c3RlciAKLSBIb3cgdG8gdmlzdWFsaXplIHRoZSBleHByZXNzaW9uIG9mIGdlbmVzIG9mIGludGVyZXN0IHRvIGFpZCBpbiBjbHVzdGVyIGlkZW50aWZpY2F0aW9uIGFuZCBsYWJlbGluZwoKVGhpcyBwcm9jZXNzIGNhbiBiZSBhIGhpZ2hseSB2YXJpYWJsZSwgZnJvbSBzZWVpbmcgd2VsbC1jaGFyYWN0ZXJpemVkIG1hcmtlciBnZW5lcyBhcyB0b3AgbWFya2VycyB0byBuZWVkaW5nIHRvIGRpZyB0aHJvdWdoIHRoZSBsaXRlcmF0dXJlIGFuZCBwbG90IHRoZSBleHByZXNzaW9uIGZvciBtdWx0aXBsZSBzZXRzIG9mIGdlbmVzIG9mIGludGVyZXN0LiBMaWtlIHRoZSBwcmV2aW91cyBzZWN0aW9ucyAoYW5kIGxpa2Ugb3RoZXIgYXJlYXMgb2YgcmVzZWFyY2gpLCB3aGVuIHRoaXMgcHJvY2VzcyBpcyBtb3JlIGludm9sdmVkIG9yIGl0ZXJhdGl2ZSwgb2Z0ZW4gb25seSB0aGUgZmluYWwgcmVzdWx0cyBhcmUgcmVwb3J0ZWQuCgojIyBPYmplY3RpdmVzCjwhLS1BZGQgc3BlY2lmaWMgZ29hbHMgZm9yIHNlY3Rpb24tLT4KLSBEZXRlcm1pbmUgZ2VuZSBtYXJrZXJzIGZvciBlYWNoIG9mIHRoZSBjbHVzdGVycyB1c2luZyBgRmluZEFsbE1hcmtlcnMoKWAKLSBWaXN1YWxpemUgZXhwcmVzc2lvbiBhY3Jvc3MgY2x1c3RlcnMgZm9yIGdlbmVzIG9mIGludGVyZXN0IHVzaW5nIGBEb3RQbG90KClgCgotLS0KCmBgYHtyLCByZWFkX3Jkc19oaWRkZW4sIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmlmKCFleGlzdHMoJ2dlb19zbycpKSB7CiAgbGlicmFyeShTZXVyYXQpCiAgbGlicmFyeShCUENlbGxzKQogIGxpYnJhcnkodGlkeXZlcnNlKQoKICBvcHRpb25zKGZ1dHVyZS5nbG9iYWxzLm1heFNpemUgPSAxZTkpCgogIGdlb19zbyA9IHJlYWRSRFMoJ3Jlc3VsdHMvcmRhdGEvZ2VvX3NvX3NjdF9jbHVzdGVyZWQucmRzJykKfQpgYGAKCiMgQ2x1c3RlciBtYXJrZXJzIGFuZCBjaGFyYWN0ZXJpemF0aW9uCgpBZnRlciBnZW5lcmF0aW5nIGNsdXN0ZXJzLCB3ZSBuZWVkIHRvIHBlcmZvcm0gZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgdG8gaWRlbnRpZnkgdGhlIGdlbmVzIHRoYXQgZGlzdGluZ3Vpc2ggdGhvc2UgY2x1c3RlcnMgKFtzb3VyY2VdKGh0dHBzOi8vb3V5YW5nbGFiLmNvbS9zaW5nbGVjZWxsL2NsdXN0Lmh0bWwjaWRlbnRpZnlpbmctbWFya2VyLWdlbmVzKSkuICBUaGlzIHNob3VsZCBhbGxvdyB1cyB0byBnZXQgdmlzaWJpbGl0eSBvbiBzb21lIGtleSBxdWVzdGlvbnMgZm9yIG91ciBjbHVzdGVycywgYXMgW2hpZ2hsaWdodGVkIGJ5IHRoZSBIQkMgbWF0ZXJpYWxzXShodHRwczovL2hiY3RyYWluaW5nLmdpdGh1Yi5pby9zY1JOQS1zZXFfb25saW5lL2xlc3NvbnMvMDlfbWVyZ2VkX1NDX21hcmtlcl9pZGVudGlmaWNhdGlvbi5odG1sKSwgbmFtZWx5OgoKLSBBcmUgdGhlcmUgYXJlIGJpb2xvZ2ljYWxseSBtZWFuaW5nZnVsIGdlbmUgZXhwcmVzc2lvbiBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRoZSBjbHVzdGVycz8gCi0gRG9lcyB0aGUgZ2VuZSBleHByZXNzaW9uIG9mIHRoZSBnZW5lcmF0ZWQgY2x1c3RlcnMgY29ycmVzcG9uZCB0byBleHBlY3RlZCBjZWxsLXR5cGVzIG9yIHN1Yi10eXBlcz8gCi0gQXJlIHRoZXJlIGNsdXN0ZXJzIHdpdGggc2ltaWxhciBleHByZXNzaW9uIHRoYXQgc2hvdWxkIGJlIGNvbWJpbmVkIGFuZC9vciBjbHVzdGVycyB0aGF0IG1pZ2h0IG5lZWQgdG8gYmUgc3ViLWNsdXN0ZXJlZCBpbnRvIHNtYWxsZXIgcG9wdWxhdGlvbnM/CgpNYW55IG9mIHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiAoREUpIHRvb2xzIGRlc2lnbmVkIGZvciBidWxrIFJOQS1zZXEgc2FtcGxlcyBoYXZlIGJlZW4gYmVuY2htYXJrZWQgZm9yIHBlcmZvcm1hbmNlIG9uIHNjUk5BLXNlcSBpbiBbU29uZXNvbiBhbmQgUm9iaW5zb24gKDIwMTgpXShodHRwczovL3B1Ym1lZC5uY2JpLm5sbS5uaWguZ292LzI5NDgxNTQ5LykgYW5kIHRoZXJlIGFyZSBhbHNvIGRlZGljYXRlZCBERSB0b29scyBmb3Igc2NSTkEtc2VxLCBsaWtlIFtNQVNUXShodHRwczovL2dlbm9tZWJpb2xvZ3kuYmlvbWVkY2VudHJhbC5jb20vYXJ0aWNsZXMvMTAuMTE4Ni9zMTMwNTktMDE1LTA4NDQtNSksIHRoYXQgdXNlIG1vZGVscyB0aGF0IGFjY291bnQgZm9yIHRoZSBleHBlY3RlZCBzcGFyc2Ugc3RydWN0dXJlIG9mIHNjUk5BLXNlcSBkYXRhLiBIb3dldmVyIGluIG91ciBhbmQgb3RoZXJzJyBleHBlcmllbmNlLCB0aGUgZGVmYXVsdCBXaWxjb3hvbiB0ZXN0IGlzIG9mdGVuIHN1ZmZpY2llbnQgZm9yIHNpbXBsZSBwYWlyd2lzZSBERSBjb21wYXJpc29ucywgd2hpbGUgZWRnZVIgKFtSb2JpbnNvbiwgTWNDYXJ0aHksIGFuZCBTbXl0aCAyMDEwXShodHRwczovL3B1Ym1lZC5uY2JpLm5sbS5uaWguZ292LzE5OTEwMzA4LykpIGlzIHJlY29tbWVuZGVkIGJ5IHRoZSBbT3V5YW5nIExhYl0oW091eWFuZ10oaHR0cHM6Ly9vdXlhbmdsYWIuY29tL3NpbmdsZWNlbGwvY2x1c3QuaHRtbCNpZGVudGlmeWluZy1tYXJrZXItZ2VuZXMpKSBmb3IgbW9yZSBjb21wbGV4IGNvbXBhcmlzb24gZGVzaWducywgc3VjaCBhcyB0aG9zZSB0aGF0IGluY2x1ZGUgY292YXJpYXRlcy4KCjwhLS0gQWRkaXRpb25hbCBjb250ZXh0IGZyb20gW091eWFuZ10oaHR0cHM6Ly9vdXlhbmdsYWIuY29tL3NpbmdsZWNlbGwvY2x1c3QuaHRtbCNpZGVudGlmeWluZy1tYXJrZXItZ2VuZXMpIHJlZ2FyZGluZyB0b29sIG9wdGlvbnMgYW5kIFtIQkMgbWF0ZXJpYWxzXShodHRwczovL2hiY3RyYWluaW5nLmdpdGh1Yi5pby9zY1JOQS1zZXFfb25saW5lL2xlc3NvbnMvMDlfbWVyZ2VkX1NDX21hcmtlcl9pZGVudGlmaWNhdGlvbi5odG1sKSByZWdhcmRpbmcgRmluZE1hcmtlcnMgdnMgRmluZENvbnNlcnZlZE1hcmtlcnMsIGV0Yy4gLS0+CgohW0ltYWdlOiBDb21wYXJpc29uIG9mIHRoZSBwZXJmb3JtYW5jZSBvZiBERSBtZXRob2RzIGFwcGxpZWQgdG8gc2NSTkEtc2VxIGRhdGFzZXRzLiBNZXRob2RzIGFyZSByYW5rZWQgYnkgdGhlaXIgYXZlcmFnZSBwZXJmb3JtYW5jZSBhY3Jvc3MgYWxsIHRoZSBsaXN0ZWQgY3JpdGVyaWEuIEltYWdlIHRha2VuIGZyb20gU29uZXNvbiBhbmQgUm9iaW5zb24gKDIwMTgpLl0oLi9pbWFnZXMvY3VycmljdWx1bS8wNi1NYXJrZXJWaXN1YWxpemF0aW9uL091eWFuZ19jbHVzdC1kZUNvbXBhcmUucG5nKQoKPGRldGFpbHM+CiAgICA8c3VtbWFyeT4qQWRkaXRpb25hbCBjb25zaWRlcmF0aW9ucyBmb3IgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24qPC9zdW1tYXJ5PgogICAgVGhlIE91eWFuZyBMYWIgaGFzIGEgW3NlY3Rpb24gb2YgdGhlaXIgdHV0b3JpYWxdKGh0dHBzOi8vb3V5YW5nbGFiLmNvbS9zaW5nbGVjZWxsL2NsdXN0Lmh0bWwjc2VjOmRpZmZleHByKSB0aGF0IGRpc2N1c3NlcyB0aGUgbWV0aG9kcyBhdmFpbGFibGUgZm9yIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGluY2x1ZGluZyBzb21lIGhpZ2hsaWdodGVkIGluIHRoZSBmaWd1cmUgYmVsb3csIGFzIHdlbGwgYXMgYSBtb3JlIGV4dGVuc2l2ZSBzZWN0aW9uIG9uIFt0aHJlc2hvbGQgY29uc2lkZXJhdGlvbnNdKGh0dHBzOi8vb3V5YW5nbGFiLmNvbS9zaW5nbGVjZWxsL2NsdXN0Lmh0bWwjc2VjOmRpZmZleHByKSwgd2hpbGUgdGhlIFtIQkMgc2VjdGlvbiBvbiBtYXJrZXIgZ2VuZXMgaWRlbnRpZmljYXRpb25dKGh0dHBzOi8vaGJjdHJhaW5pbmcuZ2l0aHViLmlvL3NjUk5BLXNlcV9vbmxpbmUvbGVzc29ucy8wOV9tZXJnZWRfU0NfbWFya2VyX2lkZW50aWZpY2F0aW9uLmh0bWwpIGhpZ2hsaWdodHMgdGhlIGRpZmZlcmVudCB0eXBlcyBvZiBtYXJrZXIgaWRlbnRpZmljYXRpb24gb3B0aW9ucyBhdmFpbGFibGUgdmlhIFNldXJhdC4KPC9kZXRhaWxzPgo8YnI+CgojIyBNYXJrZXIgaWRlbnRpZmljYXRpb24KCkZpcnN0LCB3ZSdsbCBjaGFuZ2UgaWRlbnRpdGllcyBvZiB0aGUgY2VsbHMgdG8gImludGVncmF0ZWQuc2N0LnJwY2EuY2x1c3RlcnMiIGV4cGxpY2l0bHkgd2l0aCBgU2V0SWRlbnQoKWAuIFRoZSAic2V1cmF0X2NsdXN0ZXJzIiBjb2x1bW4gaXMgdGhlIGRlZmF1bHQgY29sdW1uIGZvciBjZWxsIGlkZW50aXRpZXMgYW5kIGNoYW5nZXMgZWFjaCB0aW1lIGEgbmV3IGNsdXN0ZXJpbmcgaXMgcGVyZm9ybWVkLiBUaGVuLCB3ZSdsbCBlbnN1cmUgdGhhdCB0aGUgY29ycmVjdCByZXNvbHV0aW9uIGlzIHNlbGVjdGVkIGZyb20gb3VyIFNldXJhdCBvYmplY3QgYW5kIHRoZW4gd2UnbGwgdXNlIHRoZSBgUHJlcFNDVEZpbmRNYXJrZXJzKClgIFtmdW5jdGlvbl0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC9yZWZlcmVuY2UvcHJlcHNjdGZpbmRtYXJrZXJzKSBpbiBwcmVwYXJhdGlvbiBmb3IgREUgY29tYXByaXNvbnMgdG8gInJldmVyc2UgdGhlIGluZGl2aWR1YWwgU0NUIHJlZ3Jlc3Npb24gbW9kZWwgdXNpbmcgbWluaW11bSBvZiBtZWRpYW4gVU1JIGFzIHRoZSBzZXF1ZW5jaW5nIGRlcHRoIGNvdmFyaWF0ZSIgYWNjb3JkaW5nIHRvIHRoZSBbU2V1cmF0IGRvY3VtZW50YXRpb25dKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvcmVmZXJlbmNlL3ByZXBzY3RmaW5kbWFya2VycykuIFJlbWVtYmVyIHRoYXQgd2UndmUgcGVyZm9ybWVkIGludGVncmF0aW9uIGFuZCBjbHVzdGVyaW5nIHRvIGFzc2lnbiB0aGUgY2VsbHMgdG8gY2x1c3RlcnMgcmVnYXJkbGVzcyBvZiB0aGVpciBleHBlcmltZW50YWwgY29uZGl0aW9uIGJ1dCB0aGF0IHdlIG5vdyB3YW50IHRvIGVuc3VyZSB0aGF0IHRoZSBkYXRhIGlzIG5vcm1hbGl6ZWQgYnV0IG5vdCB3aXRoIHRoZSBTQ1RyYW5zZm9ybWF0aW9uIHNjYWxpbmcgbmVlZGVkIGZvciB0aGUgcHJldmlvdXMgc3RlcHMuCgo8IS0tIGlmIHdlIHdhbnQgdG8gdXNlIHRoZSBub3JtYWxpemVkIFJOQSBkYXRhIChub3QgdGhlIGludGVncmF0ZWQgZGF0YSkgYERlZmF1bHRBc3NheShnZW9fc28pIDwtICJSTkEiYCwgYWx0aG91Z2ggbmVlZCB0byBjaGVjayBpZiB0aGF0IHN0aWxsIG1ha2VzIHNlbnNlIGZvciBTZXVyYXQgdjUgLS0+IAoKVGhlbiB3ZSdsbCBydW4gdGhlYEZpbmRBbGxNYXJrZXJzKClgIFtmdW5jdGlvbl0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC9yZWZlcmVuY2UvZmluZGFsbG1hcmtlcnMpIHRvIGdlbmVyYXRlIGNvbXBhcmlzb25zIGJldHdlZW4gZWFjaCBjbHVzdGVyIGFuZCBhbGwgb3RoZXIgY2VsbHMsIHJlZ2FyZGxlc3Mgb2YgdGhlIGV4cGVyaW1lbnRhbCBncm91cC4gTm90ZSAtIHRoZSBzdGF0aXN0aWNhbCB0ZXN0IHRvIHBlcmZvcm0gY2FuIGJlIHNwZWNpZmllZCBpbiBgRmluZEFsbE1hcmtlcnMoKWAsIGJ1dCB0aGUgZGVmYXVsdCBpcyBhIFdpbGNveG9uIHRlc3QuIAoKYGBge3IsIGZpbmRfYWxsX21hcmtlcnMsIGNhY2hlID0gVFJVRSwgY2FjaGUubGF6eSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KIyMjIyMgRGF5IDMgLSBNYXJrZXIgaWRlbnRpZmljYXRpb24gYW5kIHZpc3VhbGl6YXRpb24gCgojIEZpbmQgZW1waXJpY2FsIG1hcmtlcnMgIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBQcmVwIGZvciBjbHVzdGVyIGNvbXBhcmlzb25zCmdlb19zbyA9IFNldElkZW50KGdlb19zbywgdmFsdWUgPSAnaW50ZWdyYXRlZC5zY3QucnBjYS5jbHVzdGVycycpCmdlb19zbyA9IFByZXBTQ1RGaW5kTWFya2VycyhnZW9fc28pCgojIFJ1biBjb21wYXJpc29ucyBmb3IgZWFjaCBjbHVzdGVyIHRvIGdlbmVyYXRlIG1hcmtlcnMKZ2VvX21hcmtlcnMgPSBGaW5kQWxsTWFya2VycyhnZW9fc28sIG9ubHkucG9zID0gVFJVRSwgbWluLnBjdCA9IDAuMDUpCgojIFRha2UgYSBsb29rIGF0IHRoZSBmaXJzdCBmZXcgcm93cyBvZiB0aGUgcmVzdWx0CmhlYWQoZ2VvX21hcmtlcnMpCgojIFdyaXRlIG91dCBmdWxsIGNsdXN0ZXIgbWFya2VyIHJlc3VsdHMgdG8gZmlsZQp3cml0ZV9jc3YoZ2VvX21hcmtlcnMsIGZpbGUgPSAncmVzdWx0cy90YWJsZXMvbWFya2VyX2dlbmVzXzAuNHJlcy5jc3YnKQpgYGAKCgohW0ltYWdlOiBTY2hlbWF0aWMgYWZ0ZXIgU2V0SWRlbnQoKS5dKGltYWdlcy9zZXVyYXRfc2NoZW1hdGljL1NsaWRlMTEucG5nKQoKPCEtLS0gQ29uc2lkZXIgaG93IHRvIHJlcHJlc2VudCB0aGUgcmVzdWx0IG9mIFByZXBTQ1RGaW5kTWFya2VycygpIGluIHRoZSBzY2hlbWF0aWMuIEl0J3MgYSBsaXR0bGUgdG9vIHN1YnRsZS4uLiAtLT4KCjwhLS0tIENvbnNpZGVyIGFkZGluZyBmaWd1cmUgc2hvd2luZyBleGFtcGxlIG9mIHdoYXQncyBiZWluZyBjb21wYXJlZCAoY2lyY2xlIGNsdXN0ZXIgMSBhbmQgdGhlbiBjaXJjbGUgYWxsIG90aGVyIGNlbGxzIGZvciBleGFtcGxlKSAtLT4KCjxkZXRhaWxzPgogICAgPHN1bW1hcnk+KlNldXJhdCB2NSBpbXByb3ZlbWVudHMqPC9zdW1tYXJ5PgogICAgRm9yIG1hcmtlciBnZW5lcmF0aW9uLCBTZXVyYXQgdjUgdXNlcyB0aGUgW3ByZXN0byBwYWNrYWdlXShodHRwczovL3d3dy5iaW9yeGl2Lm9yZy9jb250ZW50LzEwLjExMDEvNjUzMjUzdjEpIHRvIHJlZHVjZSB0aGUgdGltZSByZXF1aXJlZCB0byBydW4gREUgY29tcGFyaXNvbnMsIHBhcnRpY3VsYXJseSBmb3IgbGFyZ2UgZGF0YXNldHMuIEZvciB1c2VycyB3aG8gYXJlIG5vdCB1c2luZyBwcmVzdG8sIFNldXJhdCByZWNvbW1lbmRzIGluY3JlYXNpbmcgdGhlIG1pbi5wY3QgYW5kIGxvZ2ZjLnRocmVzaG9sZCBwYXJhbWV0ZXJzY3RvIGluY3JlYXNlIHRoZSBzcGVlZCBvZiBERSB0ZXN0aW5nIChbc291cmNlXShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L2FydGljbGVzL3BibWMza190dXRvcmlhbC5odG1sI2ZpbmRpbmctZGlmZmVyZW50aWFsbHktZXhwcmVzc2VkLWZlYXR1cmVzLWNsdXN0ZXItYmlvbWFya2VycykpLgo8L2RldGFpbHM+Cjxicj4KCk5vdGUgdGhhdCBvdmVyLWludGVycHJldGF0aW9uIG9mIHRoZXNlIHJlc3VsdHMgc2hvdWxkIGJlIGF2b2lkZWQsIHNpbmNlIGVhY2ggY2VsbCBpcyB1c2VkIGFzIGEgcmVwbGljYXRlIGluIHRoZXNlIGNvbXBhcmlzb25zIHdoaWNoIGNhbiBsZWFkIHRvIGluZmxhdGVkIChlLmcuIHZlcnkgbG93KSBwLXZhbHVlcywgdGhlIHRvcCBtYXJrZXJzIGFyZSBtb3JlIGxpa2VseSB0byBiZSB0cnVzdHdvcnRoeSAoW3NvdXJjZV0oaHR0cHM6Ly9oYmN0cmFpbmluZy5naXRodWIuaW8vc2NSTkEtc2VxX29ubGluZS9sZXNzb25zLzA5X21lcmdlZF9TQ19tYXJrZXJfaWRlbnRpZmljYXRpb24uaHRtbCkpLiAKClRoZXJlZm9yZSwgaXQncyB1c2VmdWwgdG8gZmlsdGVyIHRoZSByZXN1bHRzIHRvIGhpZ2hsaWdodCB0aGUgdG9wIHBvc2l0aXZlIG1hcmtlcnMgKHNpbmNlIGEgcG9zaXRpdmUgZm9sZC1jaGFuZ2Ugd291bGQgbWVhbiB0aGF0IGdlbmUgaXMgbW9yZSBoaWdobHkgZXhwcmVzc2VkIGluIHRoZSBjbHVzdGVyIGNvbXBhcmVkIHRvIGFsbCBvdGhlciBjZWxscyksIGJlZm9yZSBsb29raW5nIGF0IG91ciByZXN1bHRzCgpgYGB7ciwgdG9wXzVfY2x1c3Rlcl9tYXJrZXJzfQojIElkZW50aWZ5IG1hcmtlciBnZW5lcyBmb3IgZWFjaCBjbHVzdGVyIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBDcmVhdGUgdGFibGUgb2YgdG9wIDUgbWFya2VycyBwZXIgY2x1c3RlciAodXNpbmcgZGVmYXVsdCByYW5raW5nKQp0b3BfNSA9IGdlb19tYXJrZXJzICU+JSBmaWx0ZXIocF92YWxfYWRqIDwgMC4wMSkgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSBzbGljZV9oZWFkKG4gPSA1KQoKIyBMb29rIGF0IHJlc3VsdHMKaGVhZCh0b3BfNSwgbiA9IDEwKQoKIyBPcHRpb25hbCAtIENyZWF0ZSB0YWJsZSBvZiB0b3AgNSBtYXJrZXJzIHBlciBjbHVzdGVyIChyYW5rZWQgYnkgbG9nRkMpCnRvcF81X2J5X2xvZzJGQyA9IGdlb19tYXJrZXJzICU+JSBncm91cF9ieShjbHVzdGVyKSAlPiUgYXJyYW5nZShwX3ZhbF9hZGosIGRlc2MoYXZnX2xvZzJGQykpICU+JSBzbGljZV9oZWFkKG4gPSA1KQoKIyBMb29rIGF0IHJlc3VsdHMgYWZ0ZXIgcmFua2luZwpoZWFkKHRvcF81X2J5X2xvZzJGQywgbiA9IDEwKSAjIG5vdGljZSBkaWZmZXJlbmNlIGluIHBjdC4xIGNvbHVtbiBiZXR3ZWVuIHRhYmxlcwpgYGAKCldlIGV4cGVjdCB0byBzZWUgc2V2ZXJhbCBjb2x1bW5zOgoKLSBgZ2VuZWA6IGdlbmUgc3ltYm9sCi0gYHBfdmFsYDogcC12YWx1ZSBub3QgYWRqdXN0ZWQgZm9yIG11bHRpcGxlIHRlc3QgY29ycmVjdGlvbgotIGBhdmdfbG9nRkNgOiBhdmVyYWdlIGxvZyBmb2xkIGNoYW5nZS4gUG9zaXRpdmUgdmFsdWVzIGluZGljYXRlIHRoYXQgdGhlIGdlbmUgaXMgbW9yZSBoaWdobHkgZXhwcmVzc2VkIGluIHRoZSBjbHVzdGVyLgotIGBwY3QuMWA6IHBlcmNlbnRhZ2Ugb2YgY2VsbHMgd2hlcmUgdGhlIGdlbmUgaXMgZGV0ZWN0ZWQgaW4gdGhlIGNsdXN0ZXIgCi0gYHBjdC4yYDogcGVyY2VudGFnZSBvZiBjZWxscyB3aGVyZSB0aGUgZ2VuZSBpcyBkZXRlY3RlZCBvbiBhdmVyYWdlIGFjcm9zcyBhbGwgb3RoZXIgY2x1c3RlcnMgCi0gYHBfdmFsX2FkamA6IGFkanVzdGVkIHAtdmFsdWUgYmFzZWQgb24gYm9uZmVycm9uaSBjb3JyZWN0aW9uIHVzaW5nIGFsbCBnZW5lcyBpbiB0aGUgZGF0YXNldCwgdXNlZCB0byBkZXRlcm1pbmUgc2lnbmlmaWNhbmNlCi0gYGNsdXN0ZXJgOiBjbHVzdGVyIHJlcHJlc2VudGVkIGJ5IGBwY3QuMWAgYW5kIGZvciB3aGljaCB0aGUgc3RhdGlzdGljcyBpbiB0aGUgcm93IGFyZSByZXBvcnRlZAoKV2hlbiBsb29raW5nIGF0IHRoZSBvdXRwdXQsIGl0IGlzIGltcG9ydGFudCB0byBwcmlvcml0aXplIG1hcmtlciBnZW5lcyB3aXRoIGJvdGggbGFyZ2VyIGZvbGQtY2hhbmdlIGRpZmZlcmVuY2VzIGFuZCBsYXJnZXIgZGlmZmVyZW5jZSBiZXR3ZWVuIHBjdC4xIGFuZCBwY3QuMiwgcGFydGljdWxhcmx5IGlmIHBjdC4xIGlzIGhpZ2ggKGUuZy4gaWYgODAlIG9mIGNlbGxzIGluIHRoZSBjbHVzdGVyIGV2YWx1YXRlZCBleHByZXNzIHRoZSBnZW5lIHRoYXQgbW9yZSByZWxpYWJsZSB0aGFuIGlmIG9ubHkgMjAlIG9mIGNlbGxzIGV4cHJlc3MgdGhhdCBnZW5lKSAoW3NvdXJjZV0oaHR0cHM6Ly9oYmN0cmFpbmluZy5naXRodWIuaW8vc2NSTkEtc2VxX29ubGluZS9sZXNzb25zLzA5X21lcmdlZF9TQ19tYXJrZXJfaWRlbnRpZmljYXRpb24uaHRtbCkpLgoKIyMgTWFya2VyIHZpc3VhbGl6YXRpb24KCk5vdyB0aGF0IHdlIGhhdmUgZ2VuZXJhdGVkIGEgc2V0IG9mIG1hcmtlciBnZW5lcyBmb3Igb3VyIGNsdXN0ZXJzLCBpdCBpcyB1c2VmdWwgdG8gdmlzdWFsaXplIHRoZSBleHByZXNzaW9uIG9mIHRob3NlIG1hcmtlcnMgdG8gYWlkIGluIGV2YWx1YXRpbmcgdGhlbS4gV2hpbGUgdGhlIGV4cHJlc3Npb24gb2YgaW5kaXZpZHVhbCBnZW5lcyBwZXIgY2VsbCBjYW4gYmUgb3ZlcmxhaWQgb24gb3VyIFVNQVBzIChhcyB3aXRoIHRoZSBgRmVhdHVyZVBsb3QoKWAgW2Z1bmN0aW9uXShodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L3JlZmVyZW5jZS9mZWF0dXJlcGxvdCkpLCBpdCdzIG9mdGVuIG1vcmUgdXNlZnVsIHRvIHZpc3VhbGl6ZSB0aGUgZXhwcmVzc2lvbiBvZiBtdWx0aXBsZSBnZW5lcyBzaW11bHRhbmVvdXNseS4gV2hpbGUgdGhlcmUgYXJlIFttdWx0aXBsZSBtZXRob2RzIHN1cHBvcnRlZCBieSBTZXVyYXRdKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvYXJ0aWNsZXMvdmlzdWFsaXphdGlvbl92aWduZXR0ZSkgZm9yICB2aXN1YWxpemluZyBtYXJrZXIgZ2VuZSBleHByZXNzaW9uLCBhIGhlYXRtYXAgb3IgYSByZWxhdGVkIHBsb3QgY2FsbGVkIGEgZG90cGxvdCBhcmUgY29tbW9ubHkgdXNlZC4gCgpXZSdsbCB1c2UgdGhlIGBEb3RQbG90KClgIFtmdW5jdGlvbl0oaHR0cHM6Ly9zYXRpamFsYWIub3JnL3NldXJhdC9yZWZlcmVuY2UvZG90cGxvdCkgd2l0aCB0aGUgU0NUIHZhbHVlcyB0byB2aXN1YWxpemUgdGhlIHRvcCA1IG1hcmtlciBnZW5lcyBwZXIgY2x1c3RlcjoKCmBgYHtyLCB0b3BfNV9kb3RfcGxvdF9zY3QsIGZpZy5oZWlnaHQgPSAxOCwgZmlnLndpZHRoID0gOH0KIyBWaXN1YWxpemUgdG9wIG1hcmtlciBnZW5lcyBhcyBkb3QgcGxvdCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnRvcF81X3NjdF9kb3RfcGxvdCA9IERvdFBsb3QoZ2VvX3NvLCBmZWF0dXJlcyA9IHVuaXF1ZSh0b3BfNSRnZW5lKSkgKyAKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjUpKSArCiAgICBsYWJzKHRpdGxlID0gJ1RvcCA1IENsdXN0ZXIgR2VuZXMgYnkgRkRSIGFuZCBhdmdfbG9nMkZDJykgKyBjb29yZF9mbGlwKCkKdG9wXzVfc2N0X2RvdF9wbG90CgpnZ3NhdmUoZmlsZW5hbWUgPSAncmVzdWx0cy9maWd1cmVzL21hcmtlcnNfdG9wXzVfc2N0X2RvdF9wbG90LnBuZycsIHBsb3QgPSB0b3BfNV9zY3RfZG90X3Bsb3QsIHdpZHRoID0gOCwgaGVpZ2h0ID0gMTgsIHVuaXRzID0gJ2luJykgCmBgYAoKSW4gdGhlIGRvdHBsb3Qgd2UgY2FuIHNlZSB0aGF0IHRoZSBjb2xvciBpbmRpY2F0ZXMgdGhlIGV4cHJlc3Npb24gb2YgdGhlIGdlbmUgd2hpbGUgdGhlIHNpemUgb2YgdGhlIGRvdCBpbmRpY2F0ZXMgdGhlIHByb3BvcnRpb24gb2YgY2VsbHMgZXhwcmVzc2luZyB0aGF0IGdlbmUgaW4gZWFjaCBjbHVzdGVyIChbc291cmNlXShodHRwczovL291eWFuZ2xhYi5jb20vc2luZ2xlY2VsbC9jbHVzdC5odG1sI3Zpc3VhbGlzaW5nLW1hcmtlci1nZW5lcykpLiAKCjxkZXRhaWxzPgogICAgPHN1bW1hcnk+KlVzaW5nIHJhdyBSTkEgdmFsdWVzIGluIERvdHBsb3RzKjwvc3VtbWFyeT4KICAgIEluIGFkZGl0aW9uIHRvIHBsb3R0aW5nIHRoZSBTQ1QgdmFsdWVzLCB0aGUgcmF3IG9yIG5vcm1hbGl6ZWQgUk5BIHZhbHVlcyBjYW4gYmUgcGxvdHRlZCBhcyB3ZWxsOgoKYGBge3IsIHRvcF81X2RvdF9wbG90X3JuYSwgZXZhbD1GQUxTRX0KIyBBZGQgUk5BIHZhbHVlcyB0byBkb3QgcGxvdCAgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnRvcF81X3JuYV9kb3RfcGxvdCA9IERvdFBsb3QoZ2VvX3NvLCBmZWF0dXJlcyA9IHVuaXF1ZSh0b3BfNV9ieV9sb2cyRkMkZ2VuZSksIGFzc2F5ID0gJ1JOQScpICsgCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41KSkgKwogICAgbGFicyh0aXRsZSA9ICdUb3AgNSBDbHVzdGVyIEdlbmVzIGJ5IEZEUiBhbmQgYXZnX2xvZzJGQycpICsgY29vcmRfZmxpcCgpCgpnZ3NhdmUoZmlsZW5hbWUgPSAncmVzdWx0cy9maWd1cmVzL21hcmtlcnNfdG9wXzVfcm5hX2RvdF9wbG90LnBuZycsIHBsb3QgPSB0b3BfNV9ybmFfZG90X3Bsb3QsIHdpZHRoID0gOCwgaGVpZ2h0ID0gMTgsIHVuaXRzID0gJ2luJykgCmBgYAoKPC9kZXRhaWxzPgo8YnI+Cjxicj4KCkJlZm9yZSBwcm9jZWVkaW5nIHdpdGggY2x1c3RlciBhbm5vdGF0aW9ucywgd2UnbGwgYWxzbyBjaGVjayB0aGUgcGVyY2VudGFnZSBvZiBtaXRvY2hvbmRyaWFsIGdlbmVzIHRvIGRldGVybWluZSBpZiB0aGVyZSBhcmUgYW55IGNsdXN0ZXJzIChvciBzdWIgcG9wdWxhdGlvbnMpIHRoYXQgbWlnaHQgY29ycmVzcG9uZCB0byBpbnRlcmVzdGluZyBjZWxsIGRlYXRoIHBhdHRlcm5zIChvciBtaWdodCBpbmRpY2F0ZSBmdXJ0aGVyIGZpbHRlcmluZyBpcyBuZWVkZWQpOgpgYGB7ciwgZWNobz1UUlVFLCBldmFsPSBUUlVFfQojIENoZWNrIG1pdG9jaG9uZHJpYWwgZ2VuZSBleHByZXNzaW9uIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KcGVyY2VudF9taXRvX3Bsb3QgPSBGZWF0dXJlUGxvdChnZW9fc28sIGZlYXR1cmVzPSdwZXJjZW50Lm10JykKcGVyY2VudF9taXRvX3Bsb3QKCiMgc2F2ZSB0byBmaWxlCmdnc2F2ZShmaWxlbmFtZSA9ICdyZXN1bHRzL2ZpZ3VyZXMvcGVyY2VudF91bWFwX21pdG9fcGxvdC5wbmcnLCBwbG90ID0gcGVyY2VudF9taXRvX3Bsb3QsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNiwgdW5pdHMgPSAnaW4nKQpgYGAKCldlIHNlZSB0aGF0IGEgaGlnaGVyICUgc2VlbSB0byBiZSBzb21ld2hhdCBjb25jZW50cmF0ZWQgaW4gYSBmZXcgcGxhY2VzLCBidXQgaWYgY2VsbCBkZWF0aCBtaWdodCBiZSBvZiBpbnRlcmVzdCBmb3IgdGhlIHJlc2VhcmNoIHF1ZXN0aW9uLCB3ZSdkIHdhbnQgdG8gY29uc2lkZXIgaW52ZXN0aWdhdGluZyB0aGlzIHBhdHRlcm4gZnVydGhlciBieSBzcGxpdHRpbmcgdXAgdGhlIHBsb3RzIGJ5IGRheSBhbmQvb3Igd2FpdGluZyB1bnRpbCBhZnRlciBydW5uaW5nIGluaXRpYWwgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgdG8gZGV0ZXJtaW5lIGlmIHRoZXNlIGNlbGwgcG9wdWxhdGlvbnMgYXJlIGludGVyZXN0aW5nIGJpb2xvZ3kgb3Igbm90LgoKCgojIFNhdmUgb3VyIHByb2dyZXNzCgpGaW5hbGx5LCB3ZSdsbCBjcmVhdGUgYW4gb3V0cHV0IGZpbGUgZm9yIG91ciB1cGRhdGVkIFNldXJhdCBvYmplY3QgYW5kIGZvciB0aGUgY2x1c3RlciBtYXJrZXIgcmVzdWx0czoKCmBgYHtyLCBzYXZlX3Jkc19oaWRkZW4sIGVjaG8gPSBGQUxTRX0KaWYoIWZpbGUuZXhpc3RzKCdyZXN1bHRzL3JkYXRhL2dlb19zb19zY3RfaW50ZWdyYXRlZF93aXRoX21hcmtlcnMucmRzJykpIHsKICBzYXZlUkRTKGdlb19zbywgZmlsZSA9ICdyZXN1bHRzL3JkYXRhL2dlb19zb19zY3RfaW50ZWdyYXRlZF93aXRoX21hcmtlcnMucmRzJykKfQoKaWYoIWZpbGUuZXhpc3RzKCdyZXN1bHRzL3JkYXRhL2dlb19tYXJrZXJzLnJkcycpKSB7CiAgc2F2ZVJEUyhnZW9fbWFya2VycywgZmlsZSA9ICdyZXN1bHRzL3JkYXRhL2dlb19tYXJrZXJzLnJkcycpCn0KYGBgCgpgYGB7ciwgc2F2ZV9yZHMsIGV2YWw9RkFMU0V9CiMgU2F2ZSBTZXVyYXQgb2JqZWN0IGFuZCBnZW5lIG1hcmtlciBkYXRhIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpzYXZlUkRTKGdlb19zbywgZmlsZSA9ICdyZXN1bHRzL3JkYXRhL2dlb19zb19zY3RfaW50ZWdyYXRlZF93aXRoX21hcmtlcnMucmRzJykKc2F2ZVJEUyhnZW9fbWFya2VycywgZmlsZSA9ICdyZXN1bHRzL3JkYXRhL2dlb19tYXJrZXJzLnJkcycpCmBgYAoKIyBTdW1tYXJ5CgpOb3cgdGhhdCB3ZSBoYXZlIGNoYXJhY3Rlcml6ZWQgdGhlIGV4cHJlc3Npb24gb2YgYm90aCBhbmFseXRpY2FsIG1hcmtlciBnZW5lcyBhbmQgbGl0ZXJhdHVyZSAvIGtub3dsZWRnZS1iYXNlZCBtYXJrZXIgZ2VuZXMsIHdlIG1heSBoYXZlIGEgYmV0dGVyIHNlbnNlIG9mIHdoYXQgY2VsbC10eXBlcyBvciBzdWJ0eXBlcyBvdXIgY2x1c3RlcnMgbWlnaHQgY29ycmVzcG9uZCB0by4gICAKCkhvd2V2ZXIsIG1hcmtlciBnZW5lcyBhbG9uZSBtaWdodCBub3QgYmUgc3VmZmljaWVudCB0byBkZXRlcm1pbmUgY2VsbC10eXBlIG9yIHN1Yi10eXBlIGxhYmVscyBmb3Igb3VyIGNsdXN0ZXJzIHNvIHdlIHdpbGwgZGlzY3VzcyBvdGhlciBtb3JlIGF1dG9tYXRlZCBhcHByb2FjaGVzIHRvIGNvbXBsZW1lbnQgdGhlc2UgcmVzdWx0cy4KCk5leHQgc3RlcHM6IENlbGwgdHlwZSBwcmVkaWN0aW9uIHRvb2xzCgotLS0tCgpUaGVzZSBtYXRlcmlhbHMgaGF2ZSBiZWVuIGFkYXB0ZWQgYW5kIGV4dGVuZGVkIGZyb20gbWF0ZXJpYWxzIGxpc3RlZCBhYm92ZS4gVGhlc2UgYXJlIG9wZW4gYWNjZXNzIG1hdGVyaWFscyBkaXN0cmlidXRlZCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIFtDcmVhdGl2ZSBDb21tb25zIEF0dHJpYnV0aW9uIGxpY2Vuc2UgKENDIEJZIDQuMCldKGh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LzQuMC8pLCB3aGljaCBwZXJtaXRzIHVucmVzdHJpY3RlZCB1c2UsIGRpc3RyaWJ1dGlvbiwgYW5kIHJlcHJvZHVjdGlvbiBpbiBhbnkgbWVkaXVtLCBwcm92aWRlZCB0aGUgb3JpZ2luYWwgYXV0aG9yIGFuZCBzb3VyY2UgYXJlIGNyZWRpdGVkLgoKPGJyLz4KPGJyLz4KPGhyLz4KfCBbUHJldmlvdXMgbGVzc29uXSgwNS1Qcm9qZWN0aW9uQW5kQ2x1c3RlcmluZy5odG1sKSB8IFtUb3Agb2YgdGhpcyBsZXNzb25dKCN0b3ApIHwgW05leHQgbGVzc29uXSgwNy1DZWxsVHlwZUFubm9zLmh0bWwpIHwKfCA6LS0tIHwgOi0tLS06IHwgLS0tOiB8Cg==