Introduction
Differential expression comparison is a key step to addressing the
biological question at hand for the experiment, namely what genes might
be contributing to the aberrant bone formation during healing that was
observed in this experiment.
|
A. For differential expression, we consider a single cluster at a time.
B. For the cluster of interest, the cells are partitioned into
case vs control (or other appropriate groupings) and expression compared
across all genes. C. Comparisons produce lists and visualizations
of differently expressed genes across conditions.
|
As a reminder, our data includes cells isolated from issue from day 0
(prior to injury) as controls, and days 7 and 21 post-injury as
experimental conditions.
We already introduced DE comparisons in the marker identification section of this
workshop, but here we will show how to run comparisons between
experimental conditions for each annotated cluster.
Objectives
- Run cell-level differential expression comparisons
- Run sample level differential expression comparisons
Differential Expression
For single-cell data there are generally two types approaches for
running differential expression - either a cell-level or sample-level
approach.
For cell-level comparisons, simpler statistical methods like a t-test
or the Wilcoxon rank-sum test or single-cell specific methods that
models cells individually like MAST can be
used.
As mentioned earlier, many of the tools developed for bulk RNA-seq
have been shown to have good performance for single-cell data, such as
EdgeR or DESeq2, particularly when the count data is aggregated into
sample-level “pseudobulk” values for each cluster source.
As discussed in the single-cell
best practices book and in the Ouyang
Lab’s marker gene identification materials, there are active
benchmarking efforts and threshold considerations for single-cell
data.
Standard comparisons
First we’ll run cell-level comparisons for our data for the pericyte
cluster, starting with cells from the D21 vs D7 conditions. We’ll need
to ensure our cells are labeled to reflect both the cluster and
condition identities before running our comparison using
FindMarker()
and summarizing the results:
##### Day 3 - Differential Expression Analysis
# Compare pericyte cluster D21 v D7 ---------------------------------------
# set up combined label of day + celltype & assign as identities
geo_so$day.celltype = paste(geo_so$day, geo_so$cell_type, sep = '_')
# check labels
unique(geo_so$day.celltype)
[1] "Day0_Fibroblast" "Day0_Stem Cell" "Day0_Macrophage" "Day0_Pericyte" "Day0_Endothelial"
[6] "Day0_Hematopoietic stem cell" "Day0_Monocyte" "Day0_CD8+ T Cell" "Day0_Mesenchymal stem/stromal cell" "Day0_Unknown"
[11] "Day0_Erythroid" "Day0_Muscle Satellite Cell" "Day0_Regulatory T Cell" "Day0_Inflammatory macrophage" "Day21_Mesenchymal stem/stromal cell"
[16] "Day21_Hematopoietic stem cell" "Day21_Monocyte" "Day21_Fibroblast" "Day21_Pericyte" "Day21_Endothelial"
[21] "Day21_Stem Cell" "Day21_Muscle Satellite Cell" "Day21_Macrophage" "Day21_Regulatory T Cell" "Day21_Unknown"
[26] "Day21_Erythroid" "Day21_Inflammatory macrophage" "Day21_CD8+ T Cell" "Day7_Endothelial" "Day7_Monocyte"
[31] "Day7_Erythroid" "Day7_Pericyte" "Day7_Fibroblast" "Day7_Macrophage" "Day7_Hematopoietic stem cell"
[36] "Day7_Inflammatory macrophage" "Day7_Stem Cell" "Day7_Mesenchymal stem/stromal cell" "Day7_Unknown" "Day7_CD8+ T Cell"
[41] "Day7_Regulatory T Cell" "Day7_Muscle Satellite Cell"
# Reset cell identities to the combined condition + cluster label
Idents(geo_so) = 'day.celltype'
# run comparison for D21 vs D0, using wilcoxon test
de_cell_pericyte_D21_vs_D7 = FindMarkers(
object = geo_so,
slot = 'data', test = 'wilcox',
ident.1 = 'Day21_Pericyte', ident.2 = 'Day7_Pericyte')
head(de_cell_pericyte_D21_vs_D7)
p_val avg_log2FC pct.1 pct.2 p_val_adj
Prelp 0.000000e+00 2.5221214 0.739 0.218 0.000000e+00
Fmod 0.000000e+00 2.3134918 0.872 0.470 0.000000e+00
Gm10076 0.000000e+00 -1.2106283 0.811 0.964 0.000000e+00
Tpt1 0.000000e+00 0.7932864 0.997 0.983 0.000000e+00
Cilp2 1.070023e-282 5.7574478 0.370 0.013 2.189909e-278
Wfdc1 1.079704e-277 5.7472348 0.349 0.008 2.209722e-273
# Add rownames as a column for output
de_cell_pericyte_D21_vs_D7$gene = rownames(de_cell_pericyte_D21_vs_D7)
# summarize our results
table(de_cell_pericyte_D21_vs_D7$p_val_adj < 0.05 & abs(de_cell_pericyte_D21_vs_D7$avg_log2FC) > 1.5)
FALSE TRUE
9209 520
write_csv(de_cell_pericyte_D21_vs_D7, file = 'results/tables/de_standard_pericyte_D21_vs_D7.csv')
In the first 3 lines of the above code block we can see the changes
to the schematic:
Image: Schematic after setting the
Idents().
Note - the avg_log2FC
threshold of 1.5 we use here are
quite stringent as the default log2FC threshold for the function is
0.25. However the default threshold corresponds to only a 19% difference
in RNA levels, which is quite permissive.
If there is enough time - we can also compare between cells from the
D7 and D0 conditions.
# Compare pericyte cluster D7 v D0 ----------------------------------------
de_cell_pericyte_D7_vs_D0 = FindMarkers(
object = geo_so,
slot = 'data', test = 'wilcox',
ident.1 = 'Day7_Pericyte', ident.2 = 'Day0_Pericyte')
head(de_cell_pericyte_D7_vs_D0)
p_val avg_log2FC pct.1 pct.2 p_val_adj
Chad 2.647097e-216 -9.582188 0.002 0.319 5.417549e-212
Vit 5.129542e-208 -6.306605 0.007 0.412 1.049812e-203
C7 6.987318e-205 -9.272483 0.002 0.311 1.430024e-200
Myoc 6.923596e-189 -7.914476 0.005 0.353 1.416983e-184
Cilp2 5.532521e-183 -7.225177 0.013 0.445 1.132286e-178
Ptx4 1.801157e-177 -6.682222 0.004 0.319 3.686248e-173
# Add rownames as a column for output
de_cell_pericyte_D7_vs_D0$gene = rownames(de_cell_pericyte_D7_vs_D0)
# summarize results
table(de_cell_pericyte_D7_vs_D0$p_val_adj < 0.05 & abs(de_cell_pericyte_D7_vs_D0$avg_log2FC) > 1.5)
FALSE TRUE
10335 1141
This same approach can be extended to run pairwise comparisons
between conditions for each annotated cluster of interest.
Pseudobulk comparisons
With advances in the technology as well as decreased sequencing costs
allowing for larger scale single-cell experiments (that include
replicates), along with a study by Squair et al
(2021) that highlighted the possibility of inflated false discovery
rates for the cell-level approaches since cells isolated from the same
sample are unlikely to be statistically independent source
the use of sample-level or “psuedobulk” can be advantageous.
We’ll run psuedobulk comparisons for our data for the monocyte
cluster, starting with the D21 vs D0 conditions. We’ll need to generate
the aggregated counts first (ensuring that we are grouping cells by
replicate labels), before labeling the cells to reflect the cluster and
condition. Then we will run our comparison using
FindMarker()
but specifying DESeq2 as our method before
summarizing the results:
# Create pseudobulk object -------------------------------------------------
pseudo_catch_so = AggregateExpression(geo_so, assays = 'RNA', return.seurat = TRUE, group.by = c('cell_type', 'day', 'replicate'))
# Set up labels to use for comparisons & assign as cell identities
pseudo_catch_so$day.celltype = paste(pseudo_catch_so$day, pseudo_catch_so$cell_type, sep = '_')
Idents(pseudo_catch_so) = 'day.celltype'
# Run pseudobulk comparison between Day 21 and Day 0, using DESeq2
de_pseudo_pericyte_D21_vs_D7 = FindMarkers(
object = pseudo_catch_so,
ident.1 = 'Day21_Pericyte', ident.2 = 'Day7_Pericyte',
test.use = 'DESeq2')
# Take a look at the table
head(de_pseudo_pericyte_D21_vs_D7)
p_val avg_log2FC pct.1 pct.2 p_val_adj
Cilp2 3.126004e-224 2.802487 1 1 8.280473e-220
Cd55 1.165071e-123 1.232658 1 1 3.086157e-119
Ltbp4 3.095745e-115 1.732792 1 1 8.200318e-111
Prelp 1.681209e-99 2.353650 1 1 4.453353e-95
Lbp 2.050068e-94 1.863180 1 1 5.430426e-90
Prss23 5.529743e-92 1.600028 1 1 1.464774e-87
# Add rownames as a column for output
de_pseudo_pericyte_D21_vs_D7$gene = rownames(de_pseudo_pericyte_D21_vs_D7)
# look at results, using the same thresholds
table(de_pseudo_pericyte_D21_vs_D7$p_val_adj < 0.05 & abs(de_pseudo_pericyte_D21_vs_D7$avg_log2FC) > 1.5)
FALSE TRUE
23827 52
# output results
write_csv(de_pseudo_pericyte_D21_vs_D7, file = 'results/tables/de_pseudo_pericyte_D21_vs_D7.csv')
Since we’re working with pseudobulk data, unlike in the marker
identification section, there is no percentage of cells expressing to
need to represent so we can summarize our DE results with a volcano
plot:
# Make a volcano plot of pseudobulk diffex results ------------------------
pseudo_pericyte_D21_vs_D7_volcano = ggplot(de_pseudo_pericyte_D21_vs_D7, aes(x = avg_log2FC, y = -log10(p_val))) + geom_point()
pseudo_pericyte_D21_vs_D7_volcano
ggsave(filename = 'results/figures/volcano_de_pseudo_pericyte_D21_vs_D0.png', plot = pseudo_pericyte_D21_vs_D7_volcano, width = 7, height = 7, units = 'in')
Further examining DE results
We can also overlay the expression of interesting differentially
expressed genes back onto our UMAP plots to highlight the localization
and possible function, again using the FeaturePlot
function.
# UMAP feature plot of Cd55 gene ------------------------------------------
FeaturePlot(geo_so, features = "Cd55", split.by = "day")
So we found Cd55 based on differential expression comparison in the
Pericyte population between Day 7 and Day 21 but in looking at the
Feature plot of expression, we also see high expression in a subset of
cells on Day 0. This interesting, since according to Shin
et al (2019), CD55 regulates bone mass in mice.
It also looks like there is a high percentage of expression in some
of the other precursor populations on the top right of our plots, which
is interesting and might suggest an interesting subpopulation that we
might try to identify, particularly given the role of this gene and our
interest in determining why abberant bone can form after injury.
Next steps
While looking at individual genes can reveal interesting patterns
like in the case of Cd55, it’s not a very efficient process. So after
running ‘standard’ and/or psuedobulk differential expression
comparisons, we can use the same types of tools used downstream of bulk
RNA-seq to interpret these results, such as GO term enrichment, KEGG
pathway enrichment, and GSEA with mSigDB.
LS0tCnRpdGxlOiAiRGlmZmVyZW50aWFsIEV4cHJlc3Npb24gQW5hbHlzaXMiCmF1dGhvcjogIlVNIEJpb2luZm9ybWF0aWNzIENvcmUiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogICAgICAgIGh0bWxfZG9jdW1lbnQ6CiAgICAgICAgICAgIGluY2x1ZGVzOgogICAgICAgICAgICAgICAgaW5faGVhZGVyOiBoZWFkZXIuaHRtbAogICAgICAgICAgICB0aGVtZTogcGFwZXIKICAgICAgICAgICAgdG9jOiB0cnVlCiAgICAgICAgICAgIHRvY19kZXB0aDogNAogICAgICAgICAgICB0b2NfZmxvYXQ6IHRydWUKICAgICAgICAgICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgICAgICAgICBmaWdfY2FwdGlvbjogdHJ1ZQogICAgICAgICAgICBtYXJrZG93bjogR0ZNCiAgICAgICAgICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgpib2R5LCB0ZCB7CiAgIGZvbnQtc2l6ZTogMThweDsKfQpjb2RlLnJ7CiAgZm9udC1zaXplOiAxMnB4Owp9CnByZSB7CiAgZm9udC1zaXplOiAxMnB4Cn0KCnRhYmxlLmZpZywgdGguZmlnLCB0ZC5maWcgewogIGJvcmRlcjogMXB4IHNvbGlkIGJsYWNrOwogIGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7CiAgcGFkZGluZzogMTVweDsKfQo8L3N0eWxlPgoKYGBge3Iga2xpcHB5LCBlY2hvPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmtsaXBweTo6a2xpcHB5KGxhbmcgPSBjKCJyIiwgIm1hcmtkb3duIiwgImJhc2giKSwgcG9zaXRpb24gPSBjKCJ0b3AiLCAicmlnaHQiKSkKYGBgCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQpzb3VyY2UoIi4uL2Jpbi9jaHVuay1vcHRpb25zLlIiKQprbml0cl9maWdfcGF0aCgiMDgtRGlmZmVyZW50aWFsRXhwcmVzc2lvbi8wOC0iKQpgYGAKCiMgV29ya2Zsb3cgT3ZlcnZpZXcgey51bmxpc3RlZCAudW5udW1iZXJlZH0KCjxici8+CjxpbWcgc3JjPSJpbWFnZXMvd2F5ZmluZGVyL3dheWZpbmRlci5wbmciIGFsdD0id2F5ZmluZGVyIiBzdHlsZT0iaGVpZ2h0OiA0MDBweDsiLz4KPGJyLz4KPGJyLz4KCiMgSW50cm9kdWN0aW9uCgpEaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBjb21wYXJpc29uIGlzIGEga2V5IHN0ZXAgdG8gYWRkcmVzc2luZyB0aGUgYmlvbG9naWNhbCBxdWVzdGlvbiBhdCBoYW5kIGZvciB0aGUgZXhwZXJpbWVudCwgbmFtZWx5IHdoYXQgZ2VuZXMgbWlnaHQgYmUgY29udHJpYnV0aW5nIHRvIHRoZSBhYmVycmFudCBib25lIGZvcm1hdGlvbiBkdXJpbmcgaGVhbGluZyB0aGF0IHdhcyBvYnNlcnZlZCBpbiB0aGlzIGV4cGVyaW1lbnQuCgo8dGFibGUgY2xhc3M9J2ZpZyc+Cjx0ciBjbGFzcz0nZmlnJz48dGQgY2xhc3M9J2ZpZyc+IVtdKGltYWdlcy9ncmFwaGljYWxfYWJzdHJhY3RzL2dyYXBoaWNhbF9hYnN0cmFjdF9kaWZmZXgucG5nKTwvdGQ+PC90cj4KPHRyIGNsYXNzPSdmaWcnPjx0ZCBjbGFzcz0nZmlnJz5BLiBGb3IgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24sIHdlIGNvbnNpZGVyIGEgc2luZ2xlIGNsdXN0ZXIgYXQgYSB0aW1lLiA8YnIvPgpCLiBGb3IgdGhlIGNsdXN0ZXIgb2YgaW50ZXJlc3QsIHRoZSBjZWxscyBhcmUgcGFydGl0aW9uZWQgaW50byBjYXNlIHZzIGNvbnRyb2wgKG9yIG90aGVyIGFwcHJvcHJpYXRlIGdyb3VwaW5ncykgYW5kIGV4cHJlc3Npb24gY29tcGFyZWQgYWNyb3NzIGFsbCBnZW5lcy4gPGJyLz4KQy4gQ29tcGFyaXNvbnMgcHJvZHVjZSBsaXN0cyBhbmQgdmlzdWFsaXphdGlvbnMgb2YgZGlmZmVyZW50bHkgZXhwcmVzc2VkIGdlbmVzIGFjcm9zcyBjb25kaXRpb25zLgo8L3RkPjwvdHI+CjwvdGFibGU+Cjxici8+CgpBcyBhIHJlbWluZGVyLCBvdXIgZGF0YSBpbmNsdWRlcyBjZWxscyBpc29sYXRlZCBmcm9tIGlzc3VlIGZyb20gZGF5IDAgKHByaW9yIHRvIGluanVyeSkgYXMgY29udHJvbHMsIGFuZCBkYXlzIDcgYW5kIDIxIHBvc3QtaW5qdXJ5IGFzIGV4cGVyaW1lbnRhbCBjb25kaXRpb25zLgoKIVtdKC4vaW1hZ2VzL2N1cnJpY3VsdW0vZXhwZXJpbWVudGFsX2Rlc2lnbi5qcGcpCgpXZSBhbHJlYWR5IGludHJvZHVjZWQgREUgY29tcGFyaXNvbnMgaW4gdGhlIFttYXJrZXIgaWRlbnRpZmljYXRpb24gc2VjdGlvbiBvZiB0aGlzIHdvcmtzaG9wXSgwNi1NYXJrZXJWaXN1YWxpemF0aW9uLmh0bWwpLCBidXQgaGVyZSB3ZSB3aWxsIHNob3cgaG93IHRvIHJ1biBjb21wYXJpc29ucyBiZXR3ZWVuIGV4cGVyaW1lbnRhbCBjb25kaXRpb25zIGZvciBlYWNoIGFubm90YXRlZCBjbHVzdGVyLgoKIyMgT2JqZWN0aXZlcwoKPCEtLUFkZCBzcGVjaWZpYyBnb2FscyBmb3Igc2VjdGlvbi0tPgotIFJ1biBjZWxsLWxldmVsIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGNvbXBhcmlzb25zCi0gUnVuIHNhbXBsZSBsZXZlbCBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBjb21wYXJpc29ucwoKLS0tLQoKYGBge3IsIHJlYWRfcmRzX2hpZGRlbiwgZWNobyA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KaWYoIWV4aXN0cygnZ2VvX3NvJykpIHsKICBsaWJyYXJ5KFNldXJhdCkKICBsaWJyYXJ5KEJQQ2VsbHMpCiAgbGlicmFyeSh0aWR5dmVyc2UpCgogIG9wdGlvbnMoZnV0dXJlLmdsb2JhbHMubWF4U2l6ZSA9IDFlOSkKCiAgZ2VvX3NvID0gcmVhZFJEUygncmVzdWx0cy9yZGF0YS9nZW9fc29fc2N0X2ludGVncmF0ZWRfd2l0aF9jYXRjaC5yZHMnKQp9CmBgYAoKIyBEaWZmZXJlbnRpYWwgRXhwcmVzc2lvbgoKRm9yIHNpbmdsZS1jZWxsIGRhdGEgdGhlcmUgYXJlIGdlbmVyYWxseSB0d28gdHlwZXMgYXBwcm9hY2hlcyBmb3IgcnVubmluZyBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiAtIGVpdGhlciBhIGNlbGwtbGV2ZWwgb3Igc2FtcGxlLWxldmVsIGFwcHJvYWNoLiAKCkZvciBjZWxsLWxldmVsIGNvbXBhcmlzb25zLCBzaW1wbGVyIHN0YXRpc3RpY2FsIG1ldGhvZHMgbGlrZSBhIHQtdGVzdCBvciB0aGUgV2lsY294b24gcmFuay1zdW0gdGVzdCBvciBzaW5nbGUtY2VsbCBzcGVjaWZpYyBtZXRob2RzIHRoYXQgbW9kZWxzIGNlbGxzIGluZGl2aWR1YWxseSBsaWtlIFtNQVNUXShodHRwczovL2RvaS5vcmcvMTAuMTE4Ni9zMTMwNTktMDE1LTA4NDQtNSkgY2FuIGJlIHVzZWQuIAoKQXMgbWVudGlvbmVkIGVhcmxpZXIsIG1hbnkgb2YgdGhlIHRvb2xzIGRldmVsb3BlZCBmb3IgYnVsayBSTkEtc2VxIGhhdmUgYmVlbiBzaG93biB0byBoYXZlIGdvb2QgcGVyZm9ybWFuY2UgZm9yIHNpbmdsZS1jZWxsIGRhdGEsIHN1Y2ggYXMgRWRnZVIgb3IgREVTZXEyLCBwYXJ0aWN1bGFybHkgd2hlbiB0aGUgY291bnQgZGF0YSBpcyBhZ2dyZWdhdGVkIGludG8gc2FtcGxlLWxldmVsICJwc2V1ZG9idWxrIiB2YWx1ZXMgZm9yIGVhY2ggY2x1c3RlciBbc291cmNlXShodHRwczovL3d3dy5zYy1iZXN0LXByYWN0aWNlcy5vcmcvY29uZGl0aW9ucy9kaWZmZXJlbnRpYWxfZ2VuZV9leHByZXNzaW9uLmh0bWwpLiAKCkFzIGRpc2N1c3NlZCBpbiB0aGUgW3NpbmdsZS1jZWxsIGJlc3QgcHJhY3RpY2VzIGJvb2tdKGh0dHBzOi8vd3d3LnNjLWJlc3QtcHJhY3RpY2VzLm9yZy9jb25kaXRpb25zL2RpZmZlcmVudGlhbF9nZW5lX2V4cHJlc3Npb24uaHRtbCkgYW5kIGluIHRoZSBbT3V5YW5nIExhYidzIG1hcmtlciBnZW5lIGlkZW50aWZpY2F0aW9uIG1hdGVyaWFsc10oaHR0cHM6Ly9vdXlhbmdsYWIuY29tL3NpbmdsZWNlbGwvY2x1c3QuaHRtbCNzZWM6ZGlmZmV4cHIpLCB0aGVyZSBhcmUgYWN0aXZlIGJlbmNobWFya2luZyBlZmZvcnRzIGFuZCB0aHJlc2hvbGQgY29uc2lkZXJhdGlvbnMgZm9yIHNpbmdsZS1jZWxsIGRhdGEuCgo8IS0tIGFkZCBtb3JlIGluIGRlcHRoIG1vZGVsIGNvbXBhcmlzb25zIG9wdGlvbnMgbGF0ZXI/CiMjIE1vZGVsIG9wdGlvbnMKCldpbGNveG9uIChkZWZhdWx0KSwgREVTZXEyLCBNQVNULCBldGMuCi0tPgoKIyMgU3RhbmRhcmQgY29tcGFyaXNvbnMKCkZpcnN0IHdlJ2xsIHJ1biBjZWxsLWxldmVsIGNvbXBhcmlzb25zIGZvciBvdXIgZGF0YSBmb3IgdGhlIHBlcmljeXRlIGNsdXN0ZXIsIHN0YXJ0aW5nIHdpdGggY2VsbHMgZnJvbSB0aGUgRDIxIHZzIEQ3IGNvbmRpdGlvbnMuIFdlJ2xsIG5lZWQgdG8gZW5zdXJlIG91ciBjZWxscyBhcmUgbGFiZWxlZCB0byByZWZsZWN0IGJvdGggdGhlIGNsdXN0ZXIgYW5kIGNvbmRpdGlvbiBpZGVudGl0aWVzIGJlZm9yZSBydW5uaW5nIG91ciBjb21wYXJpc29uIHVzaW5nIGBGaW5kTWFya2VyKClgIGFuZCBzdW1tYXJpemluZyB0aGUgcmVzdWx0czoKCjwhLS0gYWRkIFVNQVAgd2l0aCBmYWNldHMgYnkgZ3JvdXBzIHRvIHNob3cgd2hhdCdzIGJlaW5nIGNvbXBhcmVkPyAtLT4KCmBgYHtyLCBkZV93aWxjb3gxLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KIyMjIyMgRGF5IDMgIC0gRGlmZmVyZW50aWFsIEV4cHJlc3Npb24gQW5hbHlzaXMKCiMgQ29tcGFyZSBwZXJpY3l0ZSBjbHVzdGVyIEQyMSB2IEQ3IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIHNldCB1cCBjb21iaW5lZCBsYWJlbCBvZiBkYXkgKyBjZWxsdHlwZSAmIGFzc2lnbiBhcyBpZGVudGl0aWVzCmdlb19zbyRkYXkuY2VsbHR5cGUgPSBwYXN0ZShnZW9fc28kZGF5LCBnZW9fc28kY2VsbF90eXBlLCBzZXAgPSAnXycpCiMgY2hlY2sgbGFiZWxzCnVuaXF1ZShnZW9fc28kZGF5LmNlbGx0eXBlKQoKIyBSZXNldCBjZWxsIGlkZW50aXRpZXMgdG8gdGhlIGNvbWJpbmVkIGNvbmRpdGlvbiArIGNsdXN0ZXIgbGFiZWwKSWRlbnRzKGdlb19zbykgPSAnZGF5LmNlbGx0eXBlJwoKIyBydW4gY29tcGFyaXNvbiBmb3IgRDIxIHZzIEQwLCB1c2luZyB3aWxjb3hvbiB0ZXN0CmRlX2NlbGxfcGVyaWN5dGVfRDIxX3ZzX0Q3ID0gRmluZE1hcmtlcnMoCiAgICBvYmplY3QgPSBnZW9fc28sCiAgICBzbG90ID0gJ2RhdGEnLCB0ZXN0ID0gJ3dpbGNveCcsCiAgICBpZGVudC4xID0gJ0RheTIxX1BlcmljeXRlJywgaWRlbnQuMiA9ICdEYXk3X1BlcmljeXRlJykKCmhlYWQoZGVfY2VsbF9wZXJpY3l0ZV9EMjFfdnNfRDcpCgojIEFkZCByb3duYW1lcyBhcyBhIGNvbHVtbiBmb3Igb3V0cHV0CmRlX2NlbGxfcGVyaWN5dGVfRDIxX3ZzX0Q3JGdlbmUgPSByb3duYW1lcyhkZV9jZWxsX3BlcmljeXRlX0QyMV92c19ENykKCiMgc3VtbWFyaXplIG91ciByZXN1bHRzCnRhYmxlKGRlX2NlbGxfcGVyaWN5dGVfRDIxX3ZzX0Q3JHBfdmFsX2FkaiA8IDAuMDUgJiBhYnMoZGVfY2VsbF9wZXJpY3l0ZV9EMjFfdnNfRDckYXZnX2xvZzJGQykgPiAxLjUpCgp3cml0ZV9jc3YoZGVfY2VsbF9wZXJpY3l0ZV9EMjFfdnNfRDcsIGZpbGUgPSAncmVzdWx0cy90YWJsZXMvZGVfc3RhbmRhcmRfcGVyaWN5dGVfRDIxX3ZzX0Q3LmNzdicpCmBgYAoKSW4gdGhlIGZpcnN0IDMgbGluZXMgb2YgdGhlIGFib3ZlIGNvZGUgYmxvY2sgd2UgY2FuIHNlZSB0aGUgY2hhbmdlcyB0byB0aGUgc2NoZW1hdGljOgoKIVtJbWFnZTogU2NoZW1hdGljIGFmdGVyIHNldHRpbmcgdGhlIElkZW50cygpLl0oaW1hZ2VzL3NldXJhdF9zY2hlbWF0aWMvU2xpZGUxMy5wbmcpCgpOb3RlIC0gdGhlIGBhdmdfbG9nMkZDYCB0aHJlc2hvbGQgb2YgMS41IHdlIHVzZSBoZXJlIGFyZSBxdWl0ZSBzdHJpbmdlbnQgYXMgdGhlIGRlZmF1bHQgbG9nMkZDIHRocmVzaG9sZCBmb3IgdGhlIGZ1bmN0aW9uIGlzIDAuMjUuIEhvd2V2ZXIgdGhlIGRlZmF1bHQgdGhyZXNob2xkIGNvcnJlc3BvbmRzIHRvIG9ubHkgYSAxOSUgZGlmZmVyZW5jZSBpbiBSTkEgbGV2ZWxzLCB3aGljaCBpcyBxdWl0ZSBwZXJtaXNzaXZlLiAKCklmIHRoZXJlIGlzIGVub3VnaCB0aW1lIC0gd2UgY2FuIGFsc28gY29tcGFyZSBiZXR3ZWVuIGNlbGxzIGZyb20gdGhlIEQ3IGFuZCBEMCBjb25kaXRpb25zLgoKYGBge3IsIGRlX3dpbGNveDIsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQojIENvbXBhcmUgcGVyaWN5dGUgY2x1c3RlciBENyB2IEQwIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KZGVfY2VsbF9wZXJpY3l0ZV9EN192c19EMCA9IEZpbmRNYXJrZXJzKAogICAgb2JqZWN0ID0gZ2VvX3NvLAogICAgc2xvdCA9ICdkYXRhJywgdGVzdCA9ICd3aWxjb3gnLAogICAgaWRlbnQuMSA9ICdEYXk3X1BlcmljeXRlJywgaWRlbnQuMiA9ICdEYXkwX1BlcmljeXRlJykKCmhlYWQoZGVfY2VsbF9wZXJpY3l0ZV9EN192c19EMCkKCiMgQWRkIHJvd25hbWVzIGFzIGEgY29sdW1uIGZvciBvdXRwdXQKZGVfY2VsbF9wZXJpY3l0ZV9EN192c19EMCRnZW5lID0gcm93bmFtZXMoZGVfY2VsbF9wZXJpY3l0ZV9EN192c19EMCkKCiMgc3VtbWFyaXplIHJlc3VsdHMKdGFibGUoZGVfY2VsbF9wZXJpY3l0ZV9EN192c19EMCRwX3ZhbF9hZGogPCAwLjA1ICYgYWJzKGRlX2NlbGxfcGVyaWN5dGVfRDdfdnNfRDAkYXZnX2xvZzJGQykgPiAxLjUpCmBgYAoKVGhpcyBzYW1lIGFwcHJvYWNoIGNhbiBiZSBleHRlbmRlZCB0byBydW4gcGFpcndpc2UgY29tcGFyaXNvbnMgYmV0d2VlbiBjb25kaXRpb25zIGZvciBlYWNoIGFubm90YXRlZCBjbHVzdGVyIG9mIGludGVyZXN0LgoKPCEtLSBhZGQgY29kZSB0byBsb29wIHRocm91Z2ggYWxsIGNsdXN0ZXJzIGFuZCBnZW5lcmF0ZSBjb21wYXJpc29ucyAtLT4KCiMjIFBzZXVkb2J1bGsgY29tcGFyaXNvbnMKCjwhLS0gWzEweCBhbmFseXNpcyBndWlkZSBmb3IgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gd2l0aCBiaW9sb2dpY2FsIHJlcGxpY2F0ZXNdKGh0dHBzOi8vd3d3LjEweGdlbm9taWNzLmNvbS9hbmFseXNpcy1ndWlkZXMvZGlmZmVyZW50aWFsLWdlbmUtZXhwcmVzc2lvbi1hbmFseXNpcy1pbi1zY3JuYS1zZXEtZGF0YS1iZXR3ZWVuLWNvbmRpdGlvbnMtd2l0aC1iaW9sb2dpY2FsLXJlcGxpY2F0ZXMpIC0tPgoKPCEtLSBbT3V5YW5nXShodHRwczovL291eWFuZ2xhYi5jb20vc2luZ2xlY2VsbC9jbHVzdC5odG1sI3NlYzpkaWZmZXhwcikgRm9yIChpaSksIHNpbmdsZS1jZWxsIHN0dWRpZXMgYXJlIG5vdyBtb3JlIGNvbXBsZXgsIG9mdGVuIGluY2x1ZGluZyBzYW1wbGVzIGZyb20gbXVsdGlwbGUgZG9ub3JzLiBJdCBpcyBwb3NzaWJsZSB0aGF0IHRoZXJlIGFyZSBtb3JlIGNlbGxzIGJlaW5nIHByb2ZpbGVkIGZyb20gYSBzcGVjaWZpYyBkb25vciB0aGFuIG90aGVycyBhbmQgdGhpcyBjYW4gc2tldyB0aGUgREUgcmVzdWx0cy4gRm9yIGV4YW1wbGUsIGNvbnNpZGVyIGEgc3R1ZHkgd2hlcmUgdGhlcmUgYXJlIHRocmVlIGRpc2Vhc2VkIHNhbXBsZXMgKEQxLEQyLEQzKSBhbmQgdGhyZWUgaGVhbHRoeSBzYW1wbGVzIChIMSxIMixIM18gYW5kIHRoZXJlIGFyZSBhIGxvdCBtb3JlIGNlbGxzIGZyb20gc2FtcGxlIEQxLiBJbiB0aGlzIHNjZW5hcmlvLCBhIGdlbmUgdGhhdCBpcyBzcGVjaWZpY2FsbHkgZXhwcmVzc2VkIGluIEQxIG1heSBiZSBpZGVudGlmaWVkIGFzIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCB3aGVuIGNvbXBhcmluZyBkaXNlYXNlZCBhbmQgaGVhbHRoeSBzaW5nbGUtY2VsbHMuIEluIGZhY3QsIFNxdWFpciBldCBhbC4gaGF2ZSBzaG93biB0aGF0IGlnbm9yaW5nIGJpb2xvZ2ljYWwgcmVwbGljYXRlcyBjYW4gb2Z0ZW4gcmVzdWx0IGluIGZhbHNlIGRpc2NvdmVyaWVzIGluIHNpbmdsZS1jZWxsIERFIChTcXVhaXIgZXQgYWwuIDIwMjEpLiBUbyBjaXJjdW12ZW50IHRoaXMsIFNxdWFpciBldCBhbC4gc3VnZ2VzdGVkIHRoZSB1c2Ugb2YgcHNldWRvLWJ1bGsgcHJvZmlsZXMgd2hlcmUgdGhlIHNpbmdsZS1jZWxsIHByb2ZpbGVzIGZyb20gZWFjaCBpbmRpdmlkdWFsIGlzIGJlaW5nIGNvbGxlY3RlZCBhbmQgdGhlbiBzdWJqZWN0ZWQgdG8gYnVsayBSTkEtc2VxIGJhc2VkIERFIG1ldGhvZHMuIEFub3RoZXIgcG9zc2libGUgYXBwcm9hY2ggaXMgdG8gZG93bnNhbXBsZSB0aGUgbnVtYmVyIG9mIHNpbmdsZSBjZWxscyBzdWNoIHRoYXQgZWFjaCBpbmRpdmlkdWFsIGhhdmUgcm91Z2hseSBhIHNpbWlsYXIgbnVtYmVyIG9mIGNlbGxzLiAtLT4KCldpdGggYWR2YW5jZXMgaW4gdGhlIHRlY2hub2xvZ3kgYXMgd2VsbCBhcyBkZWNyZWFzZWQgc2VxdWVuY2luZyBjb3N0cyBhbGxvd2luZyBmb3IgbGFyZ2VyIHNjYWxlIHNpbmdsZS1jZWxsIGV4cGVyaW1lbnRzICh0aGF0IGluY2x1ZGUgcmVwbGljYXRlcyksIGFsb25nIHdpdGggYSBzdHVkeSBieSBbU3F1YWlyIGV0IGFsICgyMDIxKV0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9zNDE0NjctMDIxLTI1OTYwLTIpIHRoYXQgaGlnaGxpZ2h0ZWQgdGhlIHBvc3NpYmlsaXR5IG9mIGluZmxhdGVkIGZhbHNlIGRpc2NvdmVyeSByYXRlcyBmb3IgdGhlIGNlbGwtbGV2ZWwgYXBwcm9hY2hlcyBzaW5jZSBjZWxscyBpc29sYXRlZCBmcm9tIHRoZSBzYW1lIHNhbXBsZSBhcmUgdW5saWtlbHkgdG8gYmUgc3RhdGlzdGljYWxseSBpbmRlcGVuZGVudCBbc291cmNlXShodHRwczovL3d3dy5zYy1iZXN0LXByYWN0aWNlcy5vcmcvY29uZGl0aW9ucy9kaWZmZXJlbnRpYWxfZ2VuZV9leHByZXNzaW9uLmh0bWwpIHRoZSB1c2Ugb2Ygc2FtcGxlLWxldmVsIG9yICJwc3VlZG9idWxrIiBjYW4gYmUgYWR2YW50YWdlb3VzLiAKCjwhLS0gYWRkIFVNQVAgd2l0aCBmYWNldHMgYnkgc2FtcGxlIHRvIHNob3cgd2hhdCdzIGJlaW5nIGNvbXBhcmVkPyAtLT4KCldlJ2xsIHJ1biBwc3VlZG9idWxrIGNvbXBhcmlzb25zIGZvciBvdXIgZGF0YSBmb3IgdGhlIG1vbm9jeXRlIGNsdXN0ZXIsIHN0YXJ0aW5nIHdpdGggdGhlIEQyMSB2cyBEMCBjb25kaXRpb25zLiBXZSdsbCBuZWVkIHRvIGdlbmVyYXRlIHRoZSBhZ2dyZWdhdGVkIGNvdW50cyBmaXJzdCAoZW5zdXJpbmcgdGhhdCB3ZSBhcmUgZ3JvdXBpbmcgY2VsbHMgYnkgcmVwbGljYXRlIGxhYmVscyksIGJlZm9yZSBsYWJlbGluZyB0aGUgY2VsbHMgdG8gcmVmbGVjdCB0aGUgY2x1c3RlciBhbmQgY29uZGl0aW9uLiBUaGVuIHdlIHdpbGwgcnVuIG91ciBjb21wYXJpc29uIHVzaW5nIGBGaW5kTWFya2VyKClgIGJ1dCBzcGVjaWZ5aW5nIERFU2VxMiBhcyBvdXIgbWV0aG9kIGJlZm9yZSBzdW1tYXJpemluZyB0aGUgcmVzdWx0czoKCmBgYHtyLCBkZV9wc2V1ZG9idWxrLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KIyBDcmVhdGUgcHNldWRvYnVsayBvYmplY3QgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpwc2V1ZG9fY2F0Y2hfc28gPSBBZ2dyZWdhdGVFeHByZXNzaW9uKGdlb19zbywgYXNzYXlzID0gJ1JOQScsIHJldHVybi5zZXVyYXQgPSBUUlVFLCBncm91cC5ieSA9IGMoJ2NlbGxfdHlwZScsICdkYXknLCAncmVwbGljYXRlJykpCgojIFNldCB1cCBsYWJlbHMgdG8gdXNlIGZvciBjb21wYXJpc29ucyAmIGFzc2lnbiBhcyBjZWxsIGlkZW50aXRpZXMKcHNldWRvX2NhdGNoX3NvJGRheS5jZWxsdHlwZSA9IHBhc3RlKHBzZXVkb19jYXRjaF9zbyRkYXksIHBzZXVkb19jYXRjaF9zbyRjZWxsX3R5cGUsIHNlcCA9ICdfJykKSWRlbnRzKHBzZXVkb19jYXRjaF9zbykgPSAnZGF5LmNlbGx0eXBlJwoKIyBSdW4gcHNldWRvYnVsayBjb21wYXJpc29uIGJldHdlZW4gRGF5IDIxIGFuZCBEYXkgMCwgdXNpbmcgREVTZXEyCmRlX3BzZXVkb19wZXJpY3l0ZV9EMjFfdnNfRDcgPSBGaW5kTWFya2VycygKICAgIG9iamVjdCA9IHBzZXVkb19jYXRjaF9zbywgCiAgICBpZGVudC4xID0gJ0RheTIxX1BlcmljeXRlJywgaWRlbnQuMiA9ICdEYXk3X1BlcmljeXRlJywgCiAgICB0ZXN0LnVzZSA9ICdERVNlcTInKQoKIyBUYWtlIGEgbG9vayBhdCB0aGUgdGFibGUKaGVhZChkZV9wc2V1ZG9fcGVyaWN5dGVfRDIxX3ZzX0Q3KQoKIyBBZGQgcm93bmFtZXMgYXMgYSBjb2x1bW4gZm9yIG91dHB1dApkZV9wc2V1ZG9fcGVyaWN5dGVfRDIxX3ZzX0Q3JGdlbmUgPSByb3duYW1lcyhkZV9wc2V1ZG9fcGVyaWN5dGVfRDIxX3ZzX0Q3KQoKIyBsb29rIGF0IHJlc3VsdHMsIHVzaW5nIHRoZSBzYW1lIHRocmVzaG9sZHMKdGFibGUoZGVfcHNldWRvX3BlcmljeXRlX0QyMV92c19ENyRwX3ZhbF9hZGogPCAwLjA1ICYgYWJzKGRlX3BzZXVkb19wZXJpY3l0ZV9EMjFfdnNfRDckYXZnX2xvZzJGQykgPiAxLjUpCgojIG91dHB1dCByZXN1bHRzCndyaXRlX2NzdihkZV9wc2V1ZG9fcGVyaWN5dGVfRDIxX3ZzX0Q3LCBmaWxlID0gJ3Jlc3VsdHMvdGFibGVzL2RlX3BzZXVkb19wZXJpY3l0ZV9EMjFfdnNfRDcuY3N2JykKYGBgCgoKU2luY2Ugd2UncmUgd29ya2luZyB3aXRoIHBzZXVkb2J1bGsgZGF0YSwgdW5saWtlIGluIHRoZSBtYXJrZXIgaWRlbnRpZmljYXRpb24gc2VjdGlvbiwgdGhlcmUgaXMgbm8gcGVyY2VudGFnZSBvZiBjZWxscyBleHByZXNzaW5nIHRvIG5lZWQgdG8gcmVwcmVzZW50IHNvIHdlIGNhbiBzdW1tYXJpemUgb3VyIERFIHJlc3VsdHMgd2l0aCBhIHZvbGNhbm8gcGxvdDoKCmBgYHtyLCBkZV9wc2V1ZG9idWxrX3ZvbGNhbm8sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIE1ha2UgYSB2b2xjYW5vIHBsb3Qgb2YgcHNldWRvYnVsayBkaWZmZXggcmVzdWx0cyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KcHNldWRvX3BlcmljeXRlX0QyMV92c19EN192b2xjYW5vID0gZ2dwbG90KGRlX3BzZXVkb19wZXJpY3l0ZV9EMjFfdnNfRDcsIGFlcyh4ID0gYXZnX2xvZzJGQywgeSA9IC1sb2cxMChwX3ZhbCkpKSArIGdlb21fcG9pbnQoKQpwc2V1ZG9fcGVyaWN5dGVfRDIxX3ZzX0Q3X3ZvbGNhbm8KCmdnc2F2ZShmaWxlbmFtZSA9ICdyZXN1bHRzL2ZpZ3VyZXMvdm9sY2Fub19kZV9wc2V1ZG9fcGVyaWN5dGVfRDIxX3ZzX0QwLnBuZycsIHBsb3QgPSBwc2V1ZG9fcGVyaWN5dGVfRDIxX3ZzX0Q3X3ZvbGNhbm8sIHdpZHRoID0gNywgaGVpZ2h0ID0gNywgdW5pdHMgPSAnaW4nKQpgYGAKPCEtLS0gCiMjIyBJdGVyYXRpbmcgb3ZlciBhbGwgY2x1c3RlcnMKCkFkZCBjb2RlIHRvIGdlbmVyYXRlIHBzZXVkb2J1bGsgcmVzdWx0cyBmb3IgZWFjaCBjbHVzdGVyIGZvciBhbGwgRGF5IDcgdnMgRGF5IDIxIGNvbXBhcmlzb25zCi0tLT4KCiMjIyBGdXJ0aGVyIGV4YW1pbmluZyBERSByZXN1bHRzCgpXZSBjYW4gYWxzbyBvdmVybGF5IHRoZSBleHByZXNzaW9uIG9mIGludGVyZXN0aW5nIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyBiYWNrIG9udG8gb3VyIFVNQVAgcGxvdHMgdG8gaGlnaGxpZ2h0IHRoZSBsb2NhbGl6YXRpb24gYW5kIHBvc3NpYmxlIGZ1bmN0aW9uLCBhZ2FpbiB1c2luZyB0aGUgYEZlYXR1cmVQbG90YCBmdW5jdGlvbi4gCgpgYGB7ciwgc2hvd19ERV9mZWF0dXJlUGxvdCwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIG91dC5oZWlnaHQ9JzYwJSd9CiMgVU1BUCBmZWF0dXJlIHBsb3Qgb2YgQ2Q1NSBnZW5lIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpGZWF0dXJlUGxvdChnZW9fc28sIGZlYXR1cmVzID0gIkNkNTUiLCBzcGxpdC5ieSA9ICJkYXkiKQpgYGAKU28gd2UgZm91bmQgQ2Q1NSBiYXNlZCBvbiBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBjb21wYXJpc29uIGluIHRoZSBQZXJpY3l0ZSBwb3B1bGF0aW9uIGJldHdlZW4gRGF5IDcgYW5kIERheSAyMSBidXQgaW4gbG9va2luZyBhdCB0aGUgRmVhdHVyZSBwbG90IG9mIGV4cHJlc3Npb24sIHdlIGFsc28gc2VlIGhpZ2ggZXhwcmVzc2lvbiBpbiBhIHN1YnNldCBvZiBjZWxscyBvbiBEYXkgMC4gVGhpcyBpbnRlcmVzdGluZywgc2luY2UgYWNjb3JkaW5nIHRvIFtTaGluIGV0IGFsICgyMDE5KV0oaHR0cHM6Ly9vbmxpbmVsaWJyYXJ5LndpbGV5LmNvbS9kb2kvZnVsbC8xMC4xMDAyL2pibXIuMzg2MSksIENENTUgcmVndWxhdGVzIGJvbmUgbWFzcyBpbiBtaWNlLgoKSXQgYWxzbyBsb29rcyBsaWtlIHRoZXJlIGlzIGEgaGlnaCBwZXJjZW50YWdlIG9mIGV4cHJlc3Npb24gaW4gc29tZSBvZiB0aGUgb3RoZXIgcHJlY3Vyc29yIHBvcHVsYXRpb25zIG9uIHRoZSB0b3AgcmlnaHQgb2Ygb3VyIHBsb3RzLCB3aGljaCBpcyBpbnRlcmVzdGluZyBhbmQgbWlnaHQgc3VnZ2VzdCBhbiBpbnRlcmVzdGluZyBzdWJwb3B1bGF0aW9uIHRoYXQgd2UgbWlnaHQgdHJ5IHRvIGlkZW50aWZ5LCBwYXJ0aWN1bGFybHkgZ2l2ZW4gdGhlIHJvbGUgb2YgdGhpcyBnZW5lIGFuZCBvdXIgaW50ZXJlc3QgaW4gZGV0ZXJtaW5pbmcgd2h5IGFiYmVyYW50IGJvbmUgY2FuIGZvcm0gYWZ0ZXIgaW5qdXJ5LgoKIyMgTmV4dCBzdGVwcyAKCldoaWxlIGxvb2tpbmcgYXQgaW5kaXZpZHVhbCBnZW5lcyBjYW4gcmV2ZWFsIGludGVyZXN0aW5nIHBhdHRlcm5zIGxpa2UgaW4gdGhlIGNhc2Ugb2YgQ2Q1NSwgaXQncyBub3QgYSB2ZXJ5IGVmZmljaWVudCBwcm9jZXNzLiBTbyBhZnRlciBydW5uaW5nICdzdGFuZGFyZCcgYW5kL29yIHBzdWVkb2J1bGsgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gY29tcGFyaXNvbnMsIHdlIGNhbiB1c2UgdGhlIHNhbWUgdHlwZXMgb2YgdG9vbHMgdXNlZCBkb3duc3RyZWFtIG9mIGJ1bGsgUk5BLXNlcSB0byBpbnRlcnByZXQgdGhlc2UgcmVzdWx0cywgc3VjaCBhcyBHTyB0ZXJtIGVucmljaG1lbnQsIEtFR0cgcGF0aHdheSBlbnJpY2htZW50LCBhbmQgR1NFQSB3aXRoIG1TaWdEQi4gCgoKIyBTYXZlIG91ciBwcm9ncmVzcwoKV2UnbGwgc2F2ZSB0aGUgc2NDQVRDSCBvYmplY3QuIFRoZSBTZXVyYXQgb2JqZWN0IGhhcyBub3QgYmVlbiBjaGFuZ2VkIGluIHRoaXMgbW9kdWxlLgoKYGBge3IsIHNhdmVfcmRzX2hpZGRlbiwgZWNobyA9IEZBTFNFfQppZighZmlsZS5leGlzdHMoJ3Jlc3VsdHMvcmRhdGEvZ2VvX3NvX3NjdF9pbnRlZ3JhdGVkX3dpdGhfZmluYWwucmRzJykpIHsKICBzYXZlUkRTKGdlb19zbywgZmlsZSA9ICdyZXN1bHRzL3JkYXRhL2dlb19zb19zY3RfaW50ZWdyYXRlZF9maW5hbC5yZHMnKQp9CmBgYAoKYGBge3IsIHNhdmVfcmRzLCBldmFsPUZBTFNFfQojIFNhdmUgU2V1cmF0IG9iamVjdCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0Kc2F2ZVJEUyhnZW9fc28sIGZpbGUgPSAncmVzdWx0cy9yZGF0YS9nZW9fc29fc2N0X2ludGVncmF0ZWRfZmluYWwucmRzJykKYGBgCgotLS0KCiMgRG93bnN0cmVhbSBhcHByb2FjaGVzCgpCZXlvbmQgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gd2l0aGluIGNsdXN0ZXJzIGFuZCAsIHRoZXJlIGFyZSBtYW55IG90aGVyIHBvc3NpYmxlIGF2ZW51ZXMgb2YgZXhwbG9yYXRpb24gYW5kIGFuYWx5c2lzIGZvciBzY1JOQS1zZXEgZGF0YS4gSW5jbHVkaW5nLCBidXQgbm90IGxpbWl0ZWQgdG86CgotIEludGVncmF0aW9uIHdpdGggb3RoZXIgbW9kYWxpdGllcyAoZS5nLiBBVEFDLXNlcSwgVkRKLCBldGMuKQoKLSBTdWJjbHVzdGVyaW5nIGNsdXN0ZXJzIG9mIGludGVyZXN0LCBzZWUgW0hCQydzIHN1bW1hcnkgb24gc3ViY2x1c3RlcmluZ10oaHR0cHM6Ly9oYmN0cmFpbmluZy5naXRodWIuaW8vc2NSTkEtc2VxX29ubGluZS9sZXNzb25zL3NldXJhdF9zdWJjbHVzdGVyaW5nLmh0bWwpLCBbU2V1cmF0J3MgZmluZFN1YmNDbHVzdGVyIGRvY3VtZW50YXRpb25dKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvcmVmZXJlbmNlL2ZpbmRzdWJjbHVzdGVyKSBhbmQgW3RoaXMgcmVsYXRlZCBpc3N1ZSB0aHJlYWRdKGh0dHBzOi8vZ2l0aHViLmNvbS9zYXRpamFsYWIvc2V1cmF0L2lzc3Vlcy8xODgzKSAgCgotIFRyYWplY3Rvcnkgb3IgUHNldWRvdGltZSBhbmFseXNpcywgc2VlW091eWFuZyBMYWIncyB0cmFqZWN0b3J5IGFuYWx5c2lzXShodHRwczovL291eWFuZ2xhYi5jb20vc2luZ2xlY2VsbC9kaW1yZC5odG1sKSBvciBbQnJvYWQgSW5zdGl0dXRlJ3MgbWF0ZXJpYWxzIG9uIHRyYWplY3RvcnkgYW5hbHlzaXNdKGh0dHBzOi8vYnJvYWRpbnN0aXR1dGUuZ2l0aHViLmlvLzIwMjBfc2NXb3Jrc2hvcC90cmFqZWN0b3J5LWFuYWx5c2lzLmh0bWwpLCBhbW9uZyBvdGhlciByZXNvdXJjZXMgICAgCgotIENlbGwgLSBDZWxsIGNvbW11bmljYXRpb24gbmV0d29yayBpbmZlcmVuY2UsIHNlZSBbcmV2aWV3IGJ5IFdpbGsgZXQgYWwgKDIwMjMpXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU4Ny0wMjMtMDE3ODIteikKCi0gVmVsb2NpdHkgYW5hbHlzaXMgKG5vdGU6IG5vdCBjb21wYXRpYmxlIHdpdGggdGhlIGZsZXggcHJvYmUgYmFzZWQga2l0cyksIHNlZSBbdGhlIGNoYXB0ZXIgZnJvbSB0aGUgc2luZ2xlLWNlbGxzIGJlc3QgcHJhY3RpY2VzIGJvb2tdKGh0dHBzOi8vd3d3LnNjLWJlc3QtcHJhY3RpY2VzLm9yZy90cmFqZWN0b3JpZXMvcm5hX3ZlbG9jaXR5Lmh0bWwpIGZvciBkZXRhaWxlZCB0aGVvcnkvbW9kZWxpbmcgb3IgdGhlIFsxMHggZGF0YSB2aWduZXR0ZSBmb3IgdmVsb2N5dG9dKGh0dHBzOi8vcGtsYWIubWVkLmhhcnZhcmQuZWR1L3ZlbG9jeXRvL25vdGVib29rcy9SL1NDRzcxLm5iLmh0bWwpIGZvciBhIHByYWN0aWNhbCBleGFtcGxlIGluIFIKCldlIHdvdWxkIHJlY29tbWVuZCBsb29raW5nIGZvciBzdHVkaWVzIGluIHNpbWlsYXIgdGlzc3VlcyBvciBleHBlcmltZW50YWwgcXVlc3Rpb25zIGFuZCBzZWUgd2hhdCBraW5kIG9mIGFwcHJvYWNoZXMgYW5kIHRvb2xzIHdlcmUgdXNlZCB0byBhbnN3ZXIgcXVlc3Rpb25zIHJlbGF0ZWQgdG8geW91ciBiaW9sb2dpY2FsIHF1ZXN0aW9uCgojIFN1bW1hcnkKClJldmlld2luZyB0aGVzZSByZXN1bHRzIHNob3VsZCBhbGxvdyB1cyB0byBpZGVudGlmeSBnZW5lcyBvZiBpbnRlcmVzdCB0aGF0IGFyZSBpbXBhY3RlZCBieSBpbmp1cnkgYW5kIGluIHRoZSBjb250ZXh0IG9mIHRoZSBjZWxsLXR5cGVzIGluIHdoaWNoIHRoZXkgYXJlIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCwgZm9ybWFsaXplIHNvbWUgaHlwb3RoZXNlcyBmb3Igd2hhdCBjZWxsLXR5cGVzIG9yIGJpb2xvZ2ljYWwgcHJvY2Vzc2VzIG1pZ2h0IGJlIGNvbnRyaWJ1dGluZyB0byBhYmVycmFudCBib25lIGZvcm1hdGlvbi4gCgotLS0tCgpUaGVzZSBtYXRlcmlhbHMgaGF2ZSBiZWVuIGFkYXB0ZWQgYW5kIGV4dGVuZGVkIGZyb20gbWF0ZXJpYWxzIGxpc3RlZCBhYm92ZS4gVGhlc2UgYXJlIG9wZW4gYWNjZXNzIG1hdGVyaWFscyBkaXN0cmlidXRlZCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIFtDcmVhdGl2ZSBDb21tb25zIEF0dHJpYnV0aW9uIGxpY2Vuc2UgKENDIEJZIDQuMCldKGh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LzQuMC8pLCB3aGljaCBwZXJtaXRzIHVucmVzdHJpY3RlZCB1c2UsIGRpc3RyaWJ1dGlvbiwgYW5kIHJlcHJvZHVjdGlvbiBpbiBhbnkgbWVkaXVtLCBwcm92aWRlZCB0aGUgb3JpZ2luYWwgYXV0aG9yIGFuZCBzb3VyY2UgYXJlIGNyZWRpdGVkLgoKPGJyLz4KPGJyLz4KPGhyLz4KfCBbUHJldmlvdXMgbGVzc29uXSgwNy1DZWxsVHlwZUFubm9zLmh0bWwpIHwgW1RvcCBvZiB0aGlzIGxlc3Nvbl0oI3RvcCkgfCBbV29ya3Nob3AgV3JhcCBVcF0od29ya3Nob3Bfd3JhcF91cC5odG1sKSB8CnwgOi0tLSB8IDotLS0tOiB8IC0tLTogfAo=