Objectives
This notebook will demonstrate how to:
- Prepare tabular data of gene-level statistics for use with Gene Set
Enrichment Analysis (GSEA), including how to remove duplicate gene
identifiers
- Perform GSEA with the
clusterProfiler
package
- Visualize GSEA results with the
enrichplot
package
In this notebook, we will perform Gene Set Enrichment Analysis (GSEA)
on the neuroblastoma cell line differential gene expression (DGE)
results we generated during the RNA-seq module.
To refresh our memory, we analyzed data from Harenza et al.
(2017) and specifically tested for DGE between with MYCN
amplified cell lines and non-amplified cell lines using
DESeq2
. We have a table of results that contains our log2
fold changes and adjusted p-values.
GSEA is a functional class scoring (FCS) approach to pathway analysis
that was first introduced in Subramanian et
al. (2005). The rationale behind FCS approaches is that small
changes in individual genes that participate in the same biological
process or pathway can be significant and of biological interest. FCS
methods are better suited for identifying these pathways that show
coordinated changes than ORA. In ORA, we pick a cutoff that
typically only captures genes with large individual
changes.
There are 3 general steps in FCS methods (Khatri et
al. 2012):
- Calculate a gene-level statistic (we’ll use log2 fold change from
DESeq2 here)
- Gene-level statistics are aggregated into a pathway-level
statistic
- Assess the statistical significance of the pathway-level
statistic
Set up
Libraries
# Package to run GSEA
library(clusterProfiler)
clusterProfiler v4.12.0 For help: https://yulab-smu.top/biomedical-knowledge-mining-book/
If you use clusterProfiler in published research, please cite:
T Wu, E Hu, S Xu, M Chen, P Guo, Z Dai, T Feng, L Zhou, W Tang, L Zhan, X Fu, S Liu, X Bo, and G Yu. clusterProfiler 4.0: A universal enrichment tool for interpreting omics data. The Innovation. 2021, 2(3):100141
Attaching package: 'clusterProfiler'
The following object is masked from 'package:stats':
filter
# Package that contains the MSigDB gene sets in tidy format
library(msigdbr)
Directories and Files
Directories
# Where the DGE results are stored
input_dir <- file.path("..", "RNA-seq", "results", "NB-cell")
# We will create a directory to specifically hold our GSEA results if it does
# not yet exist
output_dir <- file.path("results", "NB-cell")
if (!dir.exists(output_dir)) {
dir.create(output_dir, recursive = TRUE)
}
Output files
# GSEA pathway-level scores and statistics
gsea_results_file <- file.path(output_dir,
"NB-cell_gsea_results.tsv")
Gene Set Enrichment Analysis
Adapted from refine.bio
examples
Figure 1. Subramanian et
al. (2005).
GSEA calculates a pathway-level metric, called an enrichment score
(sometimes abbreviated as ES), by ranking genes by a gene-level
statistic. This score reflects whether or not a gene set or pathway is
over-represented at the top or bottom of the gene rankings (Subramanian et
al. 2005; Yu)
Specifically, all genes are ranked from most positive to most
negative based on their statistic and a running sum is calculated:
Starting with the most highly ranked genes, the running sum increases
for each gene in the pathway and decreases for each gene not in the
pathway. The enrichment score for a pathway is the running sum’s maximum
deviation from zero. GSEA also assesses statistical significance of the
scores for each pathway through permutation testing. As a result, each
input pathway will have a p-value associated with it that is then
corrected for multiple hypothesis testing (Subramanian et
al. 2005; Yu).
The implementation of GSEA we use in here examples requires a gene
list ordered by some statistic and input gene sets. When you use
previously computed gene-level statistics with GSEA, it is called GSEA
pre-ranked.
Gene sets
In the previous notebook, we used KEGG pathways for
over-representation analysis. We identified pathways that were
significantly over-represented and found that the significant pathways
shared genes. FCS methods analyze each pathway independently and
essentially ignore this overlap between gene sets.
MSigDB includes a collection called Hallmark gene sets (Liberzon et
al. 2015) Here’s an excerpt of the collection
description:
Hallmark gene sets summarize and represent specific well-defined
biological states or processes and display coherent expression. These
gene sets were generated by a computational methodology based on
identifying gene set overlaps and retaining genes that display
coordinate expression. The hallmarks reduce noise and redundancy and
provide a better delineated biological space for GSEA.
We’ll use the Hallmark collection for GSEA. Notably, there are only
50 gene sets included in this collection. The fewer gene sets we test,
the lower our multiple hypothesis testing burden.
We can retrieve only the Hallmark gene sets by specifying
category = "H"
to the msigdbr()
function.
hs_hallmark_df <- msigdbr(species = "Homo sapiens",
category = "H")
Differential gene expression results
# Read in the DGE results
dge_results_df <- readr::read_tsv(dge_results_file)
Rows: 24912 Columns: 7
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (2): gene_id, gene_symbol
dbl (5): baseMean, log2FoldChange, lfcSE, pvalue, padj
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Let’s take a peek at the contents of this file.
head(dge_results_df)
Since this data frame of DGE results includes gene symbols, we do not
need to perform any kind of gene identifier conversion. We do, however,
need to check for duplicate gene symbols. We can accomplish this with
duplicated()
, which returns a logical vector (e.g.,
TRUE
or FALSE
). The function
sum()
will count TRUE
values as 1s and
FALSE
as 0s, so using it with duplicated()
will count the number of duplicate values.
sum(duplicated(dge_results_df$gene_symbol))
[1] 926
This will cause a problem when we go to run GSEA.
Removing duplicates
The GSEA approach requires on discriminating between genes that are
in a gene set and those that are not. Practically speaking, gene sets
are just collections of gene identifiers! When the function we use for
GSEA pre-ranked gets a list with duplicated gene identifiers, it can
produce unexpected results.
Compared to the total number of genes that are in our results, there
are not a lot of duplicates but we’ll still need to make a decision
about how to handle them.
Let’s get a vector of the duplicated gene symbols so we can use it to
explore our filtering steps.
duplicated_gene_symbols <- dge_results_df |>
dplyr::filter(duplicated(gene_symbol)) |>
dplyr::pull(gene_symbol)
Now we’ll look at the values for the the duplicated gene symbols.
dge_results_df |>
dplyr::filter(gene_symbol %in% duplicated_gene_symbols) |>
dplyr::arrange(gene_symbol)
We can see that the associated values vary for each row.
Let’s keep the gene symbols associated with the higher absolute value
of the log2 fold change.
Retaining the instance of the gene symbols with the higher absolute
value of a gene-level statistic means that we will retain the value that
is likely to be more highly- or lowly-ranked or, put another way, the
values less likely to be towards the middle of the ranked gene list. We
should keep this decision in mind when interpreting our results. For
example, if all the duplicate identifiers happened to be in a particular
gene set, we may get an overly optimistic view of how perturbed that
gene set is because we preferentially selected instances of the
identifier that have a higher absolute value of the statistic used for
ranking.
In the next chunk, we are going to filter out the duplicated rows
using the dplyr::distinct()
function after sorting by
absolute value of the log2 fold change. This will keep the first row
with the duplicated value thus keeping the row with the largest absolute
value.
filtered_dge_df <- dge_results_df |>
# Sort so that the highest absolute values of the log2 fold change are at the
# top
dplyr::arrange(dplyr::desc(abs(log2FoldChange))) |>
# Filter out the duplicated rows using `dplyr::distinct()`
dplyr::distinct(gene_symbol, .keep_all = TRUE)
Let’s see what happened to our duplicate identifiers.
# Subset to & arrange by gene symbols that were duplicated in the original
# data frame of results
filtered_dge_df |>
dplyr::filter(gene_symbol %in% duplicated_gene_symbols) |>
dplyr::arrange(gene_symbol)
Now we’re ready to prep our pre-ranked list for GSEA.
Pre-ranked list
The GSEA()
function takes a pre-ranked (sorted) named
vector of statistics, where the names in the vector are gene
identifiers. This is step 1 – gene-level statistics.
lfc_vector <- filtered_dge_df |>
# Extract a vector of `log2FoldChange` named by `gene_symbol`
dplyr::pull(log2FoldChange, name = gene_symbol)
lfc_vector <- sort(lfc_vector, decreasing = TRUE)
Let’s look at the top ranked values.
# Look at first entries of the log2 fold change vector
head(lfc_vector)
GAGE12D GABBR1 FABP5P7 KIAA0355 MNX1 GAGE2A
23.620487 23.130027 22.682631 22.562940 6.813764 6.801245
And the bottom of the list.
# Look at the last entries of the log2 fold change vector
tail(lfc_vector)
MUC15 TFPI2 LENG1 MT1M DAZ2 CSPG4P4Y
-8.017004 -8.768669 -22.754783 -25.318887 -25.444428 -26.095612
Run GSEA
Now for the analysis!
We can use the GSEA()
function to perform GSEA with any
generic set of gene sets, but there are several functions for using
specific, commonly used gene sets (e.g., gseKEGG()
).
gsea_results <- GSEA(geneList = lfc_vector, # ordered ranked gene list
minGSSize = 25, # minimum gene set size
maxGSSize = 500, # maximum gene set set
pvalueCutoff = 0.05,
pAdjustMethod = "BH", # correction for multiple hypothesis testing
TERM2GENE = dplyr::select(hs_hallmark_df,
gs_name,
gene_symbol))
using 'fgsea' for GSEA analysis, please cite Korotkevich et al (2019).
preparing geneSet collections...
GSEA analysis...
Warning in preparePathwaysAndStats(pathways, stats, minSize, maxSize, gseaParam, : There are ties in the preranked stats (0.1% of the list).
The order of those tied genes will be arbitrary, which may produce unexpected results.
leading edge analysis...
done...
The warning about ties means that there are multiple genes that have
the same log2 fold change value. This percentage is small and unlikely
to impact our results. A large number of ties might tell us there’s
something wrong with our DGE results (Ballereau
et al. 2018).
Let’s take a look at the GSEA results.
View(gsea_results@result |>
dplyr::arrange(dplyr::desc(NES))
)
Normalized enrichment scores (NES) are enrichment scores that are
scaled to make gene sets that contain different number of genes
comparable.
Let’s write these results to file.
gsea_results@result |> readr::write_tsv(gsea_results_file)
Visualizing GSEA results
We can visualize GSEA results for individual pathways or gene sets
using enrichplot::gseaplot()
. Let’s take a look at 3
different pathways – one with a highly positive NES, one with a highly
negative NES, and one that was not a significant result – to get more
insight into how ES are calculated.
Highly Positive NES
The gene set HALLMARK_MYC_TARGETS_V1
had high positive
log2 fold changes. Recall a positive log2 fold change means a it had a
higher expression value in MYCN amplified cell lines.
enrichplot::gseaplot(gsea_results,
geneSetID = "HALLMARK_MYC_TARGETS_V1",
title = "HALLMARK_MYC_TARGETS_V1",
color.line = "#0066FF")
Notice how the genes that are in the gene set, indicated by the black
bars, tend to be on the left side of the graph indicating that they have
positive gene-level scores.
Highly Negative NES
The gene set HALLMARK_INTERFERON_ALPHA_RESPONSE
had a
highly negative NES.
enrichplot::gseaplot(gsea_results,
geneSetID = "HALLMARK_INTERFERON_ALPHA_RESPONSE",
title = "HALLMARK_INTERFERON_ALPHA_RESPONSE",
color.line = "#0066FF")
This gene set shows the opposite pattern – genes in the pathway tend
to be on the right side of the graph.
A non-significant result
The @results
slot will only show gene sets that pass the
pvalueCutoff
threshold we supplied to GSEA()
,
but we can plot any gene set so long as we know its name. Let’s look at
HALLMARK_P53_PATHWAY
, which was not in the results we
viewed earlier.
enrichplot::gseaplot(gsea_results,
geneSetID = "HALLMARK_P53_PATHWAY",
title = "HALLMARK_P53_PATHWAY",
color.line = "#0066FF")
Genes in the pathway are distributed more evenly throughout the
ranked list, resulting in a more “middling” score.
Note: The plots returned by enrichplot::gseaplot
are
ggplots, so we could use ggplot2::ggsave()
to save them to
file if we wanted to.
Session Info
sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.4 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so; LAPACK version 3.10.0
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
[3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
[5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
[7] LC_PAPER=en_US.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
time zone: Etc/UTC
tzcode source: system (glibc)
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] msigdbr_7.5.1 clusterProfiler_4.12.0 optparse_1.7.5
loaded via a namespace (and not attached):
[1] DBI_1.2.2 gson_0.1.0 shadowtext_0.1.3
[4] gridExtra_2.3 rlang_1.1.3 magrittr_2.0.3
[7] DOSE_3.30.0 compiler_4.4.1 RSQLite_2.3.6
[10] png_0.1-8 vctrs_0.6.5 reshape2_1.4.4
[13] stringr_1.5.1 pkgconfig_2.0.3 crayon_1.5.2
[16] fastmap_1.1.1 XVector_0.44.0 labeling_0.4.3
[19] ggraph_2.2.1 utf8_1.2.4 HDO.db_0.99.1
[22] rmarkdown_2.26 tzdb_0.4.0 enrichplot_1.24.0
[25] UCSC.utils_1.0.0 purrr_1.0.2 bit_4.0.5
[28] xfun_0.43 zlibbioc_1.50.0 cachem_1.0.8
[31] aplot_0.2.2 GenomeInfoDb_1.40.0 jsonlite_1.8.8
[34] blob_1.2.4 highr_0.10 BiocParallel_1.38.0
[37] tweenr_2.0.3 parallel_4.4.1 R6_2.5.1
[40] bslib_0.7.0 stringi_1.8.3 RColorBrewer_1.1-3
[43] jquerylib_0.1.4 GOSemSim_2.30.0 Rcpp_1.0.12
[46] knitr_1.46 readr_2.1.5 IRanges_2.38.0
[49] Matrix_1.7-0 splines_4.4.1 igraph_2.0.3
[52] tidyselect_1.2.1 qvalue_2.36.0 yaml_2.3.8
[55] viridis_0.6.5 codetools_0.2-20 lattice_0.22-6
[58] tibble_3.2.1 plyr_1.8.9 treeio_1.28.0
[61] Biobase_2.64.0 withr_3.0.0 KEGGREST_1.44.0
[64] evaluate_0.23 gridGraphics_0.5-1 scatterpie_0.2.2
[67] getopt_1.20.4 polyclip_1.10-6 Biostrings_2.72.0
[70] ggtree_3.12.0 pillar_1.9.0 stats4_4.4.1
[73] ggfun_0.1.4 generics_0.1.3 vroom_1.6.5
[76] hms_1.1.3 S4Vectors_0.42.0 ggplot2_3.5.1
[79] tidytree_0.4.6 munsell_0.5.1 scales_1.3.0
[82] glue_1.7.0 lazyeval_0.2.2 tools_4.4.1
[85] data.table_1.15.4 fgsea_1.30.0 babelgene_22.9
[88] fs_1.6.4 graphlayouts_1.1.1 fastmatch_1.1-4
[91] tidygraph_1.3.1 cowplot_1.1.3 grid_4.4.1
[94] ape_5.8 tidyr_1.3.1 AnnotationDbi_1.66.0
[97] colorspace_2.1-0 nlme_3.1-164 patchwork_1.2.0
[100] GenomeInfoDbData_1.2.12
[ reached getOption("max.print") -- omitted 20 entries ]
LS0tCnRpdGxlOiAiUGF0aHdheSBhbmFseXNpczogR2VuZSBTZXQgRW5yaWNobWVudCBBbmFseXNpcyAoR1NFQSkiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKYXV0aG9yOiBDQ0RMIGZvciBBTFNGCmRhdGU6IDIwMjAKLS0tCgojIyBPYmplY3RpdmVzCgpUaGlzIG5vdGVib29rIHdpbGwgZGVtb25zdHJhdGUgaG93IHRvOgoKLSBQcmVwYXJlIHRhYnVsYXIgZGF0YSBvZiBnZW5lLWxldmVsIHN0YXRpc3RpY3MgZm9yIHVzZSB3aXRoIEdlbmUgU2V0IEVucmljaG1lbnQgQW5hbHlzaXMgKEdTRUEpLCBpbmNsdWRpbmcgaG93IHRvIHJlbW92ZSBkdXBsaWNhdGUgZ2VuZSBpZGVudGlmaWVycwotIFBlcmZvcm0gR1NFQSB3aXRoIHRoZSBgY2x1c3RlclByb2ZpbGVyYCBwYWNrYWdlCi0gVmlzdWFsaXplIEdTRUEgcmVzdWx0cyB3aXRoIHRoZSBgZW5yaWNocGxvdGAgcGFja2FnZQoKLS0tCgpJbiB0aGlzIG5vdGVib29rLCB3ZSB3aWxsIHBlcmZvcm0gR2VuZSBTZXQgRW5yaWNobWVudCBBbmFseXNpcyAoR1NFQSkgb24gdGhlIG5ldXJvYmxhc3RvbWEgY2VsbCBsaW5lIGRpZmZlcmVudGlhbCBnZW5lIGV4cHJlc3Npb24gKERHRSkgcmVzdWx0cyB3ZSBnZW5lcmF0ZWQgZHVyaW5nIHRoZSBSTkEtc2VxIG1vZHVsZS4KClRvIHJlZnJlc2ggb3VyIG1lbW9yeSwgd2UgYW5hbHl6ZWQgZGF0YSBmcm9tIFtIYXJlbnphICpldCBhbC4qICgyMDE3KV0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvc2RhdGEuMjAxNy4zMykgYW5kIHNwZWNpZmljYWxseSB0ZXN0ZWQgZm9yIERHRSBiZXR3ZWVuIHdpdGggX01ZQ05fIGFtcGxpZmllZCBjZWxsIGxpbmVzIGFuZCBub24tYW1wbGlmaWVkIGNlbGwgbGluZXMgdXNpbmcgYERFU2VxMmAuCldlIGhhdmUgYSB0YWJsZSBvZiByZXN1bHRzIHRoYXQgY29udGFpbnMgb3VyIGxvZzIgZm9sZCBjaGFuZ2VzIGFuZCBhZGp1c3RlZCBwLXZhbHVlcy4KCkdTRUEgaXMgYSBmdW5jdGlvbmFsIGNsYXNzIHNjb3JpbmcgKEZDUykgYXBwcm9hY2ggdG8gcGF0aHdheSBhbmFseXNpcyB0aGF0IHdhcyBmaXJzdCBpbnRyb2R1Y2VkIGluIFtTdWJyYW1hbmlhbiBfZXQgYWwuXyAoMjAwNSldKGh0dHBzOi8vZG9pLm9yZy8xMC4xMDczL3BuYXMuMDUwNjU4MDEwMikuClRoZSByYXRpb25hbGUgYmVoaW5kIEZDUyBhcHByb2FjaGVzIGlzIHRoYXQgc21hbGwgY2hhbmdlcyBpbiBpbmRpdmlkdWFsIGdlbmVzIHRoYXQgcGFydGljaXBhdGUgaW4gdGhlIHNhbWUgYmlvbG9naWNhbCBwcm9jZXNzIG9yIHBhdGh3YXkgY2FuIGJlIHNpZ25pZmljYW50IGFuZCBvZiBiaW9sb2dpY2FsIGludGVyZXN0LgpGQ1MgbWV0aG9kcyBhcmUgYmV0dGVyIHN1aXRlZCBmb3IgaWRlbnRpZnlpbmcgdGhlc2UgcGF0aHdheXMgdGhhdCBzaG93IGNvb3JkaW5hdGVkIGNoYW5nZXMgdGhhbiBPUkEuCkluIE9SQSwgd2UgcGljayBhIGN1dG9mZiB0aGF0IF90eXBpY2FsbHlfIG9ubHkgY2FwdHVyZXMgZ2VuZXMgd2l0aCBsYXJnZSBpbmRpdmlkdWFsIGNoYW5nZXMuCgpUaGVyZSBhcmUgMyBnZW5lcmFsIHN0ZXBzIGluIEZDUyBtZXRob2RzIChbS2hhdHJpIF9ldCBhbC5fIDIwMTJdKCBodHRwczovL2RvaS5vcmcvMTAuMTM3MS9qb3VybmFsLnBjYmkuMTAwMjM3NSkpOgoKMS4gQ2FsY3VsYXRlIGEgZ2VuZS1sZXZlbCBzdGF0aXN0aWMgKHdlJ2xsIHVzZSBsb2cyIGZvbGQgY2hhbmdlIGZyb20gREVTZXEyIGhlcmUpCjIuIEdlbmUtbGV2ZWwgc3RhdGlzdGljcyBhcmUgYWdncmVnYXRlZCBpbnRvIGEgcGF0aHdheS1sZXZlbCBzdGF0aXN0aWMKMy4gQXNzZXNzIHRoZSBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2Ugb2YgdGhlIHBhdGh3YXktbGV2ZWwgc3RhdGlzdGljCgojIyMjIE90aGVyIHJlc291cmNlcwoKKiBGb3IgYW5vdGhlciBleGFtcGxlIHVzaW5nIGBjbHVzdGVyUHJvZmlsZXJgIGZvciBHU0VBLCBzZWUgW19JbnRybyB0byBER0U6IEZ1bmN0aW9uYWwgQW5hbHlzaXMuXyBmcm9tIEhhcnZhcmQgQ2hhbiBCaW9pbmZvcm1hdGljcyBDb3JlIFRyYWluaW5nLl0oaHR0cHM6Ly9oYmN0cmFpbmluZy5naXRodWIuaW8vREdFX3dvcmtzaG9wL2xlc3NvbnMvMDlfZnVuY3Rpb25hbF9hbmFseXNpcy5odG1sKQoqIFRoZSB3YXkgd2UnbGwgdXNlIGBjbHVzdGVyUHJvZmlsZXJgIGhlcmUgdXNlcyBgZmdzZWFgIChGYXN0IEdlbmUgU2V0IEVucmljaG1lbnQgQW5hbHlzaXMpIHVuZGVyIHRoZSBob29kLgpZb3UgY2FuIHJlYWQgbW9yZSBhYm91dCBmZ3NlYSBpbiBbS29yb3RrZXZpY2ggX2V0IGFsLl8gKDIwMjEpXShodHRwczovL2RvaS5vcmcvMTAuMTEwMS8wNjAwMTIpLgoqIFtyZWZpbmUuYmlvIGV4YW1wbGVzIEdlbmUgc2V0IGVucmljaG1lbnQgYW5hbHlzaXMgLSBSTkEtc2VxXShodHRwczovL2FsZXhzbGVtb25hZGUuZ2l0aHViLmlvL3JlZmluZWJpby1leGFtcGxlcy8wMy1ybmFzZXEvcGF0aHdheS1hbmFseXNpc19ybmFzZXFfMDJfZ3NlYS5odG1sKSBmcm9tIHdoaWNoIHRoaXMgbWF0ZXJpYWwgaGFzIGJlZW4gYWRhcHRlZC4KCiMjIFNldCB1cAoKIyMjIExpYnJhcmllcwoKYGBge3IgbGlicmFyaWVzfQojIFBhY2thZ2UgdG8gcnVuIEdTRUEKbGlicmFyeShjbHVzdGVyUHJvZmlsZXIpCiMgUGFja2FnZSB0aGF0IGNvbnRhaW5zIHRoZSBNU2lnREIgZ2VuZSBzZXRzIGluIHRpZHkgZm9ybWF0CmxpYnJhcnkobXNpZ2RicikKYGBgCgojIyMgRGlyZWN0b3JpZXMgYW5kIEZpbGVzCgojIyMjIERpcmVjdG9yaWVzCgpgYGB7cn0KIyBXaGVyZSB0aGUgREdFIHJlc3VsdHMgYXJlIHN0b3JlZAppbnB1dF9kaXIgPC0gZmlsZS5wYXRoKCIuLiIsICJSTkEtc2VxIiwgInJlc3VsdHMiLCAiTkItY2VsbCIpCgojIFdlIHdpbGwgY3JlYXRlIGEgZGlyZWN0b3J5IHRvIHNwZWNpZmljYWxseSBob2xkIG91ciBHU0VBIHJlc3VsdHMgaWYgaXQgZG9lcwojIG5vdCB5ZXQgZXhpc3QKb3V0cHV0X2RpciA8LSBmaWxlLnBhdGgoInJlc3VsdHMiLCAiTkItY2VsbCIpCmlmICghZGlyLmV4aXN0cyhvdXRwdXRfZGlyKSkgewogIGRpci5jcmVhdGUob3V0cHV0X2RpciwgcmVjdXJzaXZlID0gVFJVRSkKfQpgYGAKCiMjIyMgSW5wdXQgZmlsZXMKCmBgYHtyIGlucHV0X2ZpbGVzfQojIERHRSByZXN1bHRzCmRnZV9yZXN1bHRzX2ZpbGUgPC0gZmlsZS5wYXRoKGlucHV0X2RpciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkItY2VsbF9ERVNlcV9hbXBsaWZpZWRfdl9ub25hbXBsaWZpZWRfcmVzdWx0cy50c3YiKQpgYGAKCiMjIyMgT3V0cHV0IGZpbGVzCgpgYGB7ciBvdXRwdXRfZmlsZXN9CiMgR1NFQSBwYXRod2F5LWxldmVsIHNjb3JlcyBhbmQgc3RhdGlzdGljcwpnc2VhX3Jlc3VsdHNfZmlsZSA8LSBmaWxlLnBhdGgob3V0cHV0X2RpciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJOQi1jZWxsX2dzZWFfcmVzdWx0cy50c3YiKQpgYGAKCiMjIEdlbmUgU2V0IEVucmljaG1lbnQgQW5hbHlzaXMKCl9BZGFwdGVkIGZyb20gW3JlZmluZS5iaW8gZXhhbXBsZXNdKGh0dHBzOi8vZ2l0aHViLmNvbS9BbGV4c0xlbW9uYWRlL3JlZmluZWJpby1leGFtcGxlcy9ibG9iLzMzY2RlZmY2NmQ1N2Y5ZmU4ZWU0ZmNiNTE1NmFlYTRhYzJkY2UwN2YvMDMtcm5hc2VxL3BhdGh3YXktYW5hbHlzaXNfcm5hc2VxXzAyX2dzZWEuUm1kKV8KCiFbXShkaWFncmFtcy9zdWJyYW1hbmlhbl9maWcxLmpwZykKCioqRmlndXJlIDEuIFtTdWJyYW1hbmlhbiBfZXQgYWwuXyAoMjAwNSldKGh0dHBzOi8vZG9pLm9yZy8xMC4xMDczL3BuYXMuMDUwNjU4MDEwMikuKioKCkdTRUEgY2FsY3VsYXRlcyBhIHBhdGh3YXktbGV2ZWwgbWV0cmljLCBjYWxsZWQgYW4gZW5yaWNobWVudCBzY29yZSAoc29tZXRpbWVzIGFiYnJldmlhdGVkIGFzIEVTKSwgYnkgcmFua2luZyBnZW5lcyBieSBhIGdlbmUtbGV2ZWwgc3RhdGlzdGljLgpUaGlzIHNjb3JlIHJlZmxlY3RzIHdoZXRoZXIgb3Igbm90IGEgZ2VuZSBzZXQgb3IgcGF0aHdheSBpcyBvdmVyLXJlcHJlc2VudGVkIGF0IHRoZSB0b3Agb3IgYm90dG9tIG9mIHRoZSBnZW5lIHJhbmtpbmdzIChbU3VicmFtYW5pYW4gX2V0IGFsLl8gMjAwNV0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwNzMvcG5hcy4wNTA2NTgwMTAyKTsgW1l1XShodHRwOi8veXVsYWItc211LnRvcC9jbHVzdGVyUHJvZmlsZXItYm9vay9jaGFwdGVyMi5odG1sI2dlbmUtc2V0LWVucmljaG1lbnQtYW5hbHlzaXMpKQoKU3BlY2lmaWNhbGx5LCBhbGwgZ2VuZXMgYXJlIHJhbmtlZCBmcm9tIG1vc3QgcG9zaXRpdmUgdG8gbW9zdCBuZWdhdGl2ZSBiYXNlZCBvbiB0aGVpciBzdGF0aXN0aWMgYW5kIGEgcnVubmluZyBzdW0gaXMgY2FsY3VsYXRlZDoKU3RhcnRpbmcgd2l0aCB0aGUgbW9zdCBoaWdobHkgcmFua2VkIGdlbmVzLCB0aGUgcnVubmluZyBzdW0gaW5jcmVhc2VzIGZvciBlYWNoIGdlbmUgaW4gdGhlIHBhdGh3YXkgYW5kIGRlY3JlYXNlcyBmb3IgZWFjaCBnZW5lIG5vdCBpbiB0aGUgcGF0aHdheS4KVGhlIGVucmljaG1lbnQgc2NvcmUgZm9yIGEgcGF0aHdheSBpcyB0aGUgcnVubmluZyBzdW0ncyBtYXhpbXVtIGRldmlhdGlvbiBmcm9tIHplcm8uCkdTRUEgYWxzbyBhc3Nlc3NlcyBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2Ugb2YgdGhlIHNjb3JlcyBmb3IgZWFjaCBwYXRod2F5IHRocm91Z2ggcGVybXV0YXRpb24gdGVzdGluZy4KQXMgYSByZXN1bHQsIGVhY2ggaW5wdXQgcGF0aHdheSB3aWxsIGhhdmUgYSBwLXZhbHVlIGFzc29jaWF0ZWQgd2l0aCBpdCB0aGF0IGlzIHRoZW4gY29ycmVjdGVkIGZvciBtdWx0aXBsZSBoeXBvdGhlc2lzIHRlc3RpbmcgKFtTdWJyYW1hbmlhbiBfZXQgYWwuXyAyMDA1XShodHRwczovL2RvaS5vcmcvMTAuMTA3My9wbmFzLjA1MDY1ODAxMDIpOyBbWXVdKGh0dHA6Ly95dWxhYi1zbXUudG9wL2NsdXN0ZXJQcm9maWxlci1ib29rL2NoYXB0ZXIyLmh0bWwjZ2VuZS1zZXQtZW5yaWNobWVudC1hbmFseXNpcykpLgoKVGhlIGltcGxlbWVudGF0aW9uIG9mIEdTRUEgd2UgdXNlIGluIGhlcmUgZXhhbXBsZXMgcmVxdWlyZXMgYSBnZW5lIGxpc3Qgb3JkZXJlZCBieSBzb21lIHN0YXRpc3RpYyBhbmQgaW5wdXQgZ2VuZSBzZXRzLgpXaGVuIHlvdSB1c2UgcHJldmlvdXNseSBjb21wdXRlZCBnZW5lLWxldmVsIHN0YXRpc3RpY3Mgd2l0aCBHU0VBLCBpdCBpcyBjYWxsZWQgR1NFQSBwcmUtcmFua2VkLgoKIyMjIEdlbmUgc2V0cwoKSW4gdGhlIHByZXZpb3VzIG5vdGVib29rLCB3ZSB1c2VkIEtFR0cgcGF0aHdheXMgZm9yIG92ZXItcmVwcmVzZW50YXRpb24gYW5hbHlzaXMuCldlIGlkZW50aWZpZWQgcGF0aHdheXMgdGhhdCB3ZXJlIHNpZ25pZmljYW50bHkgb3Zlci1yZXByZXNlbnRlZCBhbmQgZm91bmQgdGhhdCB0aGUgc2lnbmlmaWNhbnQgcGF0aHdheXMgc2hhcmVkIGdlbmVzLgpGQ1MgbWV0aG9kcyBhbmFseXplIGVhY2ggcGF0aHdheSBpbmRlcGVuZGVudGx5IGFuZCBlc3NlbnRpYWxseSBpZ25vcmUgdGhpcyBvdmVybGFwIGJldHdlZW4gZ2VuZSBzZXRzLgoKTVNpZ0RCIGluY2x1ZGVzIGEgY29sbGVjdGlvbiBjYWxsZWQgSGFsbG1hcmsgZ2VuZSBzZXRzIChbTGliZXJ6b24gKmV0IGFsLiogMjAxNV0oaHR0cHM6Ly9keC5kb2kub3JnLzEwLjEwMTYlMkZqLmNlbHMuMjAxNS4xMi4wMDQpKQpIZXJlJ3MgYW4gZXhjZXJwdCBvZiB0aGUgW2NvbGxlY3Rpb24gZGVzY3JpcHRpb25dKGh0dHBzOi8vd3d3LmdzZWEtbXNpZ2RiLm9yZy9nc2VhL21zaWdkYi9jb2xsZWN0aW9uX2RldGFpbHMuanNwI0gpOgoKPiBIYWxsbWFyayBnZW5lIHNldHMgc3VtbWFyaXplIGFuZCByZXByZXNlbnQgc3BlY2lmaWMgd2VsbC1kZWZpbmVkIGJpb2xvZ2ljYWwgc3RhdGVzIG9yIHByb2Nlc3NlcyBhbmQgZGlzcGxheSBjb2hlcmVudCBleHByZXNzaW9uLiBUaGVzZSBnZW5lIHNldHMgd2VyZSBnZW5lcmF0ZWQgYnkgYSBjb21wdXRhdGlvbmFsIG1ldGhvZG9sb2d5IGJhc2VkIG9uIGlkZW50aWZ5aW5nIGdlbmUgc2V0IG92ZXJsYXBzIGFuZCByZXRhaW5pbmcgZ2VuZXMgdGhhdCBkaXNwbGF5IGNvb3JkaW5hdGUgZXhwcmVzc2lvbi4gVGhlIGhhbGxtYXJrcyByZWR1Y2Ugbm9pc2UgYW5kIHJlZHVuZGFuY3kgYW5kIHByb3ZpZGUgYSBiZXR0ZXIgZGVsaW5lYXRlZCBiaW9sb2dpY2FsIHNwYWNlIGZvciBHU0VBLgoKV2UnbGwgdXNlIHRoZSBIYWxsbWFyayBjb2xsZWN0aW9uIGZvciBHU0VBLgpOb3RhYmx5LCB0aGVyZSBhcmUgb25seSA1MCBnZW5lIHNldHMgaW5jbHVkZWQgaW4gdGhpcyBjb2xsZWN0aW9uLgpUaGUgZmV3ZXIgZ2VuZSBzZXRzIHdlIHRlc3QsIHRoZSBsb3dlciBvdXIgbXVsdGlwbGUgaHlwb3RoZXNpcyB0ZXN0aW5nIGJ1cmRlbi4KCldlIGNhbiByZXRyaWV2ZSBvbmx5IHRoZSBIYWxsbWFyayBnZW5lIHNldHMgYnkgc3BlY2lmeWluZyBgY2F0ZWdvcnkgPSAiSCJgIHRvIHRoZSBgbXNpZ2RicigpYCBmdW5jdGlvbi4KCmBgYHtyIGhhbGxtYXJrX3NldHMsIGxpdmUgPSBUUlVFfQpoc19oYWxsbWFya19kZiA8LSBtc2lnZGJyKHNwZWNpZXMgPSAiSG9tbyBzYXBpZW5zIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBjYXRlZ29yeSA9ICJIIikKYGBgCgojIyBEaWZmZXJlbnRpYWwgZ2VuZSBleHByZXNzaW9uIHJlc3VsdHMKCmBgYHtyIHJlYWRfaW5fZGdlLCBsaXZlID0gVFJVRX0KIyBSZWFkIGluIHRoZSBER0UgcmVzdWx0cwpkZ2VfcmVzdWx0c19kZiA8LSByZWFkcjo6cmVhZF90c3YoZGdlX3Jlc3VsdHNfZmlsZSkKYGBgCkxldCdzIHRha2UgYSBwZWVrIGF0IHRoZSBjb250ZW50cyBvZiB0aGlzIGZpbGUuCgpgYGB7ciBoZWFkX2RnZSwgbGl2ZSA9IFRSVUV9CmhlYWQoZGdlX3Jlc3VsdHNfZGYpCmBgYAoKU2luY2UgdGhpcyBkYXRhIGZyYW1lIG9mIERHRSByZXN1bHRzIGluY2x1ZGVzIGdlbmUgc3ltYm9scywgd2UgZG8gbm90IG5lZWQgdG8gcGVyZm9ybSBhbnkga2luZCBvZiBnZW5lIGlkZW50aWZpZXIgY29udmVyc2lvbi4KV2UgZG8sIGhvd2V2ZXIsIG5lZWQgdG8gY2hlY2sgZm9yIGR1cGxpY2F0ZSBnZW5lIHN5bWJvbHMuCldlIGNhbiBhY2NvbXBsaXNoIHRoaXMgd2l0aCBgZHVwbGljYXRlZCgpYCwgd2hpY2ggcmV0dXJucyBhIGxvZ2ljYWwgdmVjdG9yIChlLmcuLCBgVFJVRWAgb3IgYEZBTFNFYCkuClRoZSBmdW5jdGlvbiBgc3VtKClgIHdpbGwgY291bnQgYFRSVUVgIHZhbHVlcyBhcyAxcyBhbmQgYEZBTFNFYCBhcyAwcywgc28gdXNpbmcgaXQgd2l0aCBgZHVwbGljYXRlZCgpYCB3aWxsIGNvdW50IHRoZSBudW1iZXIgb2YgZHVwbGljYXRlIHZhbHVlcy4KCmBgYHtyIGFueV9kdXBsaWNhdGVkLCBsaXZlID0gVFJVRX0Kc3VtKGR1cGxpY2F0ZWQoZGdlX3Jlc3VsdHNfZGYkZ2VuZV9zeW1ib2wpKQpgYGAKClRoaXMgd2lsbCBjYXVzZSBhIHByb2JsZW0gd2hlbiB3ZSBnbyB0byBydW4gR1NFQS4KCiMjIyBSZW1vdmluZyBkdXBsaWNhdGVzCgpUaGUgR1NFQSBhcHByb2FjaCByZXF1aXJlcyBvbiBkaXNjcmltaW5hdGluZyBiZXR3ZWVuIGdlbmVzIHRoYXQgYXJlIGluIGEgZ2VuZSBzZXQgYW5kIHRob3NlIHRoYXQgYXJlIG5vdC4KUHJhY3RpY2FsbHkgc3BlYWtpbmcsIGdlbmUgc2V0cyBhcmUganVzdCBjb2xsZWN0aW9ucyBvZiBnZW5lIGlkZW50aWZpZXJzIQpXaGVuIHRoZSBmdW5jdGlvbiB3ZSB1c2UgZm9yIEdTRUEgcHJlLXJhbmtlZCBnZXRzIGEgbGlzdCB3aXRoIGR1cGxpY2F0ZWQgZ2VuZSBpZGVudGlmaWVycywgaXQgY2FuIHByb2R1Y2UgdW5leHBlY3RlZCByZXN1bHRzLgoKQ29tcGFyZWQgdG8gdGhlIHRvdGFsIG51bWJlciBvZiBnZW5lcyB0aGF0IGFyZSBpbiBvdXIgcmVzdWx0cywgdGhlcmUgYXJlIG5vdCBhIGxvdCBvZiBkdXBsaWNhdGVzIGJ1dCB3ZSdsbCBzdGlsbCBuZWVkIHRvIG1ha2UgYSBkZWNpc2lvbiBhYm91dCBob3cgdG8gaGFuZGxlIHRoZW0uCgpMZXQncyBnZXQgYSB2ZWN0b3Igb2YgdGhlIGR1cGxpY2F0ZWQgZ2VuZSBzeW1ib2xzIHNvIHdlIGNhbiB1c2UgaXQgdG8gZXhwbG9yZSBvdXIgZmlsdGVyaW5nIHN0ZXBzLgoKYGBge3IgZ2VuZV9kdXBzLCBsaXZlID0gVFJVRX0KZHVwbGljYXRlZF9nZW5lX3N5bWJvbHMgPC0gZGdlX3Jlc3VsdHNfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKGR1cGxpY2F0ZWQoZ2VuZV9zeW1ib2wpKSB8PgogIGRwbHlyOjpwdWxsKGdlbmVfc3ltYm9sKQpgYGAKCk5vdyB3ZSdsbCBsb29rIGF0IHRoZSB2YWx1ZXMgZm9yIHRoZSB0aGUgZHVwbGljYXRlZCBnZW5lIHN5bWJvbHMuCgpgYGB7ciBzaG93X2dlbmVfZHVwc30KZGdlX3Jlc3VsdHNfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKGdlbmVfc3ltYm9sICVpbiUgZHVwbGljYXRlZF9nZW5lX3N5bWJvbHMpIHw+CiAgZHBseXI6OmFycmFuZ2UoZ2VuZV9zeW1ib2wpCmBgYAoKV2UgY2FuIHNlZSB0aGF0IHRoZSBhc3NvY2lhdGVkIHZhbHVlcyB2YXJ5IGZvciBlYWNoIHJvdy4KCkxldCdzIGtlZXAgdGhlIGdlbmUgc3ltYm9scyBhc3NvY2lhdGVkIHdpdGggdGhlIGhpZ2hlciBhYnNvbHV0ZSB2YWx1ZSBvZiB0aGUgbG9nMiBmb2xkIGNoYW5nZS4KClJldGFpbmluZyB0aGUgaW5zdGFuY2Ugb2YgdGhlIGdlbmUgc3ltYm9scyB3aXRoIHRoZSBoaWdoZXIgYWJzb2x1dGUgdmFsdWUgb2YgYSBnZW5lLWxldmVsIHN0YXRpc3RpYyBtZWFucyB0aGF0IHdlIHdpbGwgcmV0YWluIHRoZSB2YWx1ZSB0aGF0IGlzIGxpa2VseSB0byBiZSBtb3JlIGhpZ2hseS0gb3IgbG93bHktcmFua2VkIG9yLCBwdXQgYW5vdGhlciB3YXksIHRoZSB2YWx1ZXMgbGVzcyBsaWtlbHkgdG8gYmUgdG93YXJkcyB0aGUgbWlkZGxlIG9mIHRoZSByYW5rZWQgZ2VuZSBsaXN0LgpXZSBzaG91bGQga2VlcCB0aGlzIGRlY2lzaW9uIGluIG1pbmQgd2hlbiBpbnRlcnByZXRpbmcgb3VyIHJlc3VsdHMuCkZvciBleGFtcGxlLCBpZiBhbGwgdGhlIGR1cGxpY2F0ZSBpZGVudGlmaWVycyBoYXBwZW5lZCB0byBiZSBpbiBhIHBhcnRpY3VsYXIgZ2VuZSBzZXQsIHdlIG1heSBnZXQgYW4gb3Zlcmx5IG9wdGltaXN0aWMgdmlldyBvZiBob3cgcGVydHVyYmVkIHRoYXQgZ2VuZSBzZXQgaXMgYmVjYXVzZSB3ZSBwcmVmZXJlbnRpYWxseSBzZWxlY3RlZCBpbnN0YW5jZXMgb2YgdGhlIGlkZW50aWZpZXIgdGhhdCBoYXZlIGEgaGlnaGVyIGFic29sdXRlIHZhbHVlIG9mIHRoZSBzdGF0aXN0aWMgdXNlZCBmb3IgcmFua2luZy4KCkluIHRoZSBuZXh0IGNodW5rLCB3ZSBhcmUgZ29pbmcgdG8gZmlsdGVyIG91dCB0aGUgZHVwbGljYXRlZCByb3dzIHVzaW5nIHRoZSBgZHBseXI6OmRpc3RpbmN0KClgIGZ1bmN0aW9uIGFmdGVyIHNvcnRpbmcgYnkgYWJzb2x1dGUgdmFsdWUgb2YgdGhlIGxvZzIgZm9sZCBjaGFuZ2UuClRoaXMgd2lsbCBrZWVwIHRoZSBmaXJzdCByb3cgd2l0aCB0aGUgZHVwbGljYXRlZCB2YWx1ZSB0aHVzIGtlZXBpbmcgdGhlIHJvdyB3aXRoIHRoZSBsYXJnZXN0IGFic29sdXRlIHZhbHVlLgoKYGBge3IgZmlsdGVyX2RnZX0KZmlsdGVyZWRfZGdlX2RmIDwtIGRnZV9yZXN1bHRzX2RmIHw+CiAgIyBTb3J0IHNvIHRoYXQgdGhlIGhpZ2hlc3QgYWJzb2x1dGUgdmFsdWVzIG9mIHRoZSBsb2cyIGZvbGQgY2hhbmdlIGFyZSBhdCB0aGUKICAjIHRvcAogIGRwbHlyOjphcnJhbmdlKGRwbHlyOjpkZXNjKGFicyhsb2cyRm9sZENoYW5nZSkpKSB8PgogICMgRmlsdGVyIG91dCB0aGUgZHVwbGljYXRlZCByb3dzIHVzaW5nIGBkcGx5cjo6ZGlzdGluY3QoKWAKICBkcGx5cjo6ZGlzdGluY3QoZ2VuZV9zeW1ib2wsIC5rZWVwX2FsbCA9IFRSVUUpCmBgYAoKTGV0J3Mgc2VlIHdoYXQgaGFwcGVuZWQgdG8gb3VyIGR1cGxpY2F0ZSBpZGVudGlmaWVycy4KCmBgYHtyIHNob3dfZmlsdGVyZWRfZGdlLCBsaXZlID0gVFJVRX0KIyBTdWJzZXQgdG8gJiBhcnJhbmdlIGJ5IGdlbmUgc3ltYm9scyB0aGF0IHdlcmUgZHVwbGljYXRlZCBpbiB0aGUgb3JpZ2luYWwKIyBkYXRhIGZyYW1lIG9mIHJlc3VsdHMKZmlsdGVyZWRfZGdlX2RmIHw+CiAgZHBseXI6OmZpbHRlcihnZW5lX3N5bWJvbCAlaW4lIGR1cGxpY2F0ZWRfZ2VuZV9zeW1ib2xzKSB8PgogIGRwbHlyOjphcnJhbmdlKGdlbmVfc3ltYm9sKQpgYGAKCk5vdyB3ZSdyZSByZWFkeSB0byBwcmVwIG91ciBwcmUtcmFua2VkIGxpc3QgZm9yIEdTRUEuCgojIyMgUHJlLXJhbmtlZCBsaXN0CgpUaGUgYEdTRUEoKWAgZnVuY3Rpb24gdGFrZXMgYSBwcmUtcmFua2VkIChzb3J0ZWQpIG5hbWVkIHZlY3RvciBvZiBzdGF0aXN0aWNzLCB3aGVyZSB0aGUgbmFtZXMgaW4gdGhlIHZlY3RvciBhcmUgZ2VuZSBpZGVudGlmaWVycy4KVGhpcyBpcyBzdGVwIDEgLS0gZ2VuZS1sZXZlbCBzdGF0aXN0aWNzLgoKYGBge3IgbGZjX3ZlY3Rvcn0KbGZjX3ZlY3RvciA8LSBmaWx0ZXJlZF9kZ2VfZGYgfD4KICAjIEV4dHJhY3QgYSB2ZWN0b3Igb2YgYGxvZzJGb2xkQ2hhbmdlYCBuYW1lZCBieSBgZ2VuZV9zeW1ib2xgCiAgZHBseXI6OnB1bGwobG9nMkZvbGRDaGFuZ2UsIG5hbWUgPSBnZW5lX3N5bWJvbCkKbGZjX3ZlY3RvciA8LSBzb3J0KGxmY192ZWN0b3IsIGRlY3JlYXNpbmcgPSBUUlVFKQpgYGAKCkxldCdzIGxvb2sgYXQgdGhlIHRvcCByYW5rZWQgdmFsdWVzLgoKYGBge3IgaGVhZF9sZmMsIGxpdmUgPSBUUlVFfQojIExvb2sgYXQgZmlyc3QgZW50cmllcyBvZiB0aGUgbG9nMiBmb2xkIGNoYW5nZSB2ZWN0b3IKaGVhZChsZmNfdmVjdG9yKQpgYGAKCkFuZCB0aGUgYm90dG9tIG9mIHRoZSBsaXN0LgoKYGBge3IgdGFpbF9sZmMsIGxpdmUgPSBUUlVFfQojIExvb2sgYXQgdGhlIGxhc3QgZW50cmllcyBvZiB0aGUgbG9nMiBmb2xkIGNoYW5nZSB2ZWN0b3IKdGFpbChsZmNfdmVjdG9yKQpgYGAKCiMjIFJ1biBHU0VBCgpOb3cgZm9yIHRoZSBhbmFseXNpcyEKCldlIGNhbiB1c2UgdGhlIGBHU0VBKClgIGZ1bmN0aW9uIHRvIHBlcmZvcm0gR1NFQSB3aXRoIGFueSBnZW5lcmljIHNldCBvZiBnZW5lIHNldHMsIGJ1dCB0aGVyZSBhcmUgc2V2ZXJhbCBmdW5jdGlvbnMgZm9yIHVzaW5nIHNwZWNpZmljLCBjb21tb25seSB1c2VkIGdlbmUgc2V0cyAoZS5nLiwgYGdzZUtFR0coKWApLgoKYGBge3IgcnVuX2dzZWF9CmdzZWFfcmVzdWx0cyA8LSBHU0VBKGdlbmVMaXN0ID0gbGZjX3ZlY3RvciwgICMgb3JkZXJlZCByYW5rZWQgZ2VuZSBsaXN0CiAgICAgICAgICAgICAgICAgICAgIG1pbkdTU2l6ZSA9IDI1LCAgIyBtaW5pbXVtIGdlbmUgc2V0IHNpemUKICAgICAgICAgICAgICAgICAgICAgbWF4R1NTaXplID0gNTAwLCAgIyBtYXhpbXVtIGdlbmUgc2V0IHNldAogICAgICAgICAgICAgICAgICAgICBwdmFsdWVDdXRvZmYgPSAwLjA1LAogICAgICAgICAgICAgICAgICAgICBwQWRqdXN0TWV0aG9kID0gIkJIIiwgICMgY29ycmVjdGlvbiBmb3IgbXVsdGlwbGUgaHlwb3RoZXNpcyB0ZXN0aW5nCiAgICAgICAgICAgICAgICAgICAgIFRFUk0yR0VORSA9IGRwbHlyOjpzZWxlY3QoaHNfaGFsbG1hcmtfZGYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3NfbmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX3N5bWJvbCkpCmBgYAoKVGhlIHdhcm5pbmcgYWJvdXQgdGllcyBtZWFucyB0aGF0IHRoZXJlIGFyZSBtdWx0aXBsZSBnZW5lcyB0aGF0IGhhdmUgdGhlIHNhbWUgbG9nMiBmb2xkIGNoYW5nZSB2YWx1ZS4KVGhpcyBwZXJjZW50YWdlIGlzIHNtYWxsIGFuZCB1bmxpa2VseSB0byBpbXBhY3Qgb3VyIHJlc3VsdHMuCkEgbGFyZ2UgbnVtYmVyIG9mIHRpZXMgbWlnaHQgdGVsbCB1cyB0aGVyZSdzIHNvbWV0aGluZyB3cm9uZyB3aXRoIG91ciBER0UgcmVzdWx0cyAoW0JhbGxlcmVhdSBfZXQgYWwuXyAyMDE4XShodHRwczovL2Jpb2luZm9ybWF0aWNzLWNvcmUtc2hhcmVkLXRyYWluaW5nLmdpdGh1Yi5pby9jcnVrLXN1bW1lci1zY2hvb2wtMjAxOC9STkFTZXEyMDE4L2h0bWwvMDZfR2VuZV9zZXRfdGVzdGluZy5uYi5odG1sKSkuCgpMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgR1NFQSByZXN1bHRzLgoKYGBge3Igdmlld19nc2VhLCBsaXZlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQpWaWV3KGdzZWFfcmVzdWx0c0ByZXN1bHQgfD4KICAgICAgIGRwbHlyOjphcnJhbmdlKGRwbHlyOjpkZXNjKE5FUykpCikKYGBgCgpOb3JtYWxpemVkIGVucmljaG1lbnQgc2NvcmVzIChORVMpIGFyZSBlbnJpY2htZW50IHNjb3JlcyB0aGF0IGFyZSBzY2FsZWQgdG8gbWFrZSBnZW5lIHNldHMgdGhhdCBjb250YWluIGRpZmZlcmVudCBudW1iZXIgb2YgZ2VuZXMgY29tcGFyYWJsZS4KCkxldCdzIHdyaXRlIHRoZXNlIHJlc3VsdHMgdG8gZmlsZS4KCmBgYHtyIHdyaXRlX2dzZWF9CmdzZWFfcmVzdWx0c0ByZXN1bHQgfD4gcmVhZHI6OndyaXRlX3Rzdihnc2VhX3Jlc3VsdHNfZmlsZSkKYGBgCgojIyMgVmlzdWFsaXppbmcgR1NFQSByZXN1bHRzCgpXZSBjYW4gdmlzdWFsaXplIEdTRUEgcmVzdWx0cyBmb3IgaW5kaXZpZHVhbCBwYXRod2F5cyBvciBnZW5lIHNldHMgdXNpbmcgYGVucmljaHBsb3Q6OmdzZWFwbG90KClgLgpMZXQncyB0YWtlIGEgbG9vayBhdCAzIGRpZmZlcmVudCBwYXRod2F5cyAtLSBvbmUgd2l0aCBhIGhpZ2hseSBwb3NpdGl2ZSBORVMsIG9uZSB3aXRoIGEgaGlnaGx5IG5lZ2F0aXZlIE5FUywgYW5kIG9uZSB0aGF0IHdhcyBub3QgYSBzaWduaWZpY2FudCByZXN1bHQgLS0gdG8gZ2V0IG1vcmUgaW5zaWdodCBpbnRvIGhvdyBFUyBhcmUgY2FsY3VsYXRlZC4KCiMjIyMgSGlnaGx5IFBvc2l0aXZlIE5FUwoKVGhlIGdlbmUgc2V0IGBIQUxMTUFSS19NWUNfVEFSR0VUU19WMWAgaGFkIGhpZ2ggcG9zaXRpdmUgbG9nMiBmb2xkIGNoYW5nZXMuClJlY2FsbCBhIHBvc2l0aXZlIGxvZzIgZm9sZCBjaGFuZ2UgbWVhbnMgYSBpdCBoYWQgYSBoaWdoZXIgZXhwcmVzc2lvbiB2YWx1ZSBpbiBfTVlDTl8gYW1wbGlmaWVkIGNlbGwgbGluZXMuCgpgYGB7ciBteWNfdjF9CmVucmljaHBsb3Q6OmdzZWFwbG90KGdzZWFfcmVzdWx0cywKICAgICAgICAgICAgICAgICAgICAgZ2VuZVNldElEID0gIkhBTExNQVJLX01ZQ19UQVJHRVRTX1YxIiwKICAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiSEFMTE1BUktfTVlDX1RBUkdFVFNfVjEiLAogICAgICAgICAgICAgICAgICAgICBjb2xvci5saW5lID0gIiMwMDY2RkYiKQpgYGAKCk5vdGljZSBob3cgdGhlIGdlbmVzIHRoYXQgYXJlIGluIHRoZSBnZW5lIHNldCwgaW5kaWNhdGVkIGJ5IHRoZSBibGFjayBiYXJzLCB0ZW5kIHRvIGJlIG9uIHRoZSBsZWZ0IHNpZGUgb2YgdGhlIGdyYXBoIGluZGljYXRpbmcgdGhhdCB0aGV5IGhhdmUgcG9zaXRpdmUgZ2VuZS1sZXZlbCBzY29yZXMuCgojIyMjIEhpZ2hseSBOZWdhdGl2ZSBORVMKClRoZSBnZW5lIHNldCBgSEFMTE1BUktfSU5URVJGRVJPTl9BTFBIQV9SRVNQT05TRWAgaGFkIGEgaGlnaGx5IG5lZ2F0aXZlIE5FUy4KCmBgYHtyIGluZmxhbW1hdG9yeX0KZW5yaWNocGxvdDo6Z3NlYXBsb3QoZ3NlYV9yZXN1bHRzLAogICAgICAgICAgICAgICAgICAgICBnZW5lU2V0SUQgPSAiSEFMTE1BUktfSU5URVJGRVJPTl9BTFBIQV9SRVNQT05TRSIsCiAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gIkhBTExNQVJLX0lOVEVSRkVST05fQUxQSEFfUkVTUE9OU0UiLAogICAgICAgICAgICAgICAgICAgICBjb2xvci5saW5lID0gIiMwMDY2RkYiKQpgYGAKClRoaXMgZ2VuZSBzZXQgc2hvd3MgdGhlIG9wcG9zaXRlIHBhdHRlcm4gLS0gZ2VuZXMgaW4gdGhlIHBhdGh3YXkgdGVuZCB0byBiZSBvbiB0aGUgcmlnaHQgc2lkZSBvZiB0aGUgZ3JhcGguCgojIyMjIEEgbm9uLXNpZ25pZmljYW50IHJlc3VsdAoKVGhlIGBAcmVzdWx0c2Agc2xvdCB3aWxsIG9ubHkgc2hvdyBnZW5lIHNldHMgdGhhdCBwYXNzIHRoZSBgcHZhbHVlQ3V0b2ZmYCB0aHJlc2hvbGQgd2Ugc3VwcGxpZWQgdG8gYEdTRUEoKWAsIGJ1dCB3ZSBjYW4gcGxvdCBhbnkgZ2VuZSBzZXQgc28gbG9uZyBhcyB3ZSBrbm93IGl0cyBuYW1lLgpMZXQncyBsb29rIGF0IGBIQUxMTUFSS19QNTNfUEFUSFdBWWAsIHdoaWNoIHdhcyBub3QgaW4gdGhlIHJlc3VsdHMgd2Ugdmlld2VkIGVhcmxpZXIuCgpgYGB7ciBwNTMsIGxpdmUgPSBUUlVFfQplbnJpY2hwbG90Ojpnc2VhcGxvdChnc2VhX3Jlc3VsdHMsCiAgICAgICAgICAgICAgICAgICAgIGdlbmVTZXRJRCA9ICJIQUxMTUFSS19QNTNfUEFUSFdBWSIsCiAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gIkhBTExNQVJLX1A1M19QQVRIV0FZIiwKICAgICAgICAgICAgICAgICAgICAgY29sb3IubGluZSA9ICIjMDA2NkZGIikKYGBgCgpHZW5lcyBpbiB0aGUgcGF0aHdheSBhcmUgZGlzdHJpYnV0ZWQgbW9yZSBldmVubHkgdGhyb3VnaG91dCB0aGUgcmFua2VkIGxpc3QsIHJlc3VsdGluZyBpbiBhIG1vcmUgIm1pZGRsaW5nIiBzY29yZS4KCipOb3RlOiBUaGUgcGxvdHMgcmV0dXJuZWQgYnkgYGVucmljaHBsb3Q6OmdzZWFwbG90YCBhcmUgZ2dwbG90cywgc28gd2UgY291bGQgdXNlIGBnZ3Bsb3QyOjpnZ3NhdmUoKWAgdG8gc2F2ZSB0aGVtIHRvIGZpbGUgaWYgd2Ugd2FudGVkIHRvLioKCiMjIFNlc3Npb24gSW5mbwoKYGBge3Igc2Vzc2lvbl9pbmZvfQpzZXNzaW9uSW5mbygpCmBgYAo=