Objectives

This notebook will demonstrate how to:

  • Identify when Gene Set Variation Analysis (GSVA) is well-suited for an analysis
  • Perform GSVA on transformed RNA-seq data with the GSVA package
  • Explore the dependence of GSVA scores on gene set size with random gene sets

So far every pathway analysis method we’ve covered relies on some information about groups of samples in our data. For over-representation analysis (ORA), we created gene sets from two different two group comparisons. In the Gene Set Enrichment Analysis (GSEA) example, we used statistics from a differential gene expression (DGE) analysis where we compared MYCN amplified cell lines to non-amplified cell lines; we needed that amplification status information.

What if we’re less sure about groups in our data or we want to analyze our data in a more unsupervised manner?

In this notebook we will cover a method called Gene Set Variation Analysis (GSVA) (Hänzelmann et al. 2013) that allows us to calculate gene set or pathway scores on a per-sample basis.

We like this quote from the GSVA paper (Hänzelmann et al. 2013) to set the stage:

While [gene set enrichment] methods are generally regarded as end points of a bioinformatic analysis, GSVA constitutes a starting point to build pathway-centric models of biology.

Rather than contextualizing some results you already have from another analysis like DGE, GSVA is designed to provide an estimate of pathway variation for each of the samples in an experiment. Note that these scores will depend on the samples included in the dataset when you run GSVA; if you added more samples and reran GSVA, you would expect the scores to change.

Set up

Libraries

# Gene Set Variation Analysis
library(GSVA)
Warning: replacing previous import 'S4Arrays::makeNindexFromArrayViewport' by
'DelayedArray::makeNindexFromArrayViewport' when loading 'HDF5Array'
Warning: replacing previous import 'S4Arrays::makeNindexFromArrayViewport' by
'DelayedArray::makeNindexFromArrayViewport' when loading 'SummarizedExperiment'

Directories and files

Directories

# We have some medulloblastoma data from the OpenPBTA project that we've
# prepared ahead of time
input_dir <- file.path("data", "open-pbta")

# Create a directory specifically for the results using this dataset
output_dir <- file.path("results", "open-pbta")
if (!dir.exists(output_dir)) {
  dir.create(output_dir, recursive = TRUE)
}

Input

We have VST transformed RNA-seq data, annotated with gene symbols, that has been collapsed such that there are no duplicated gene identifiers (see setup).

rnaseq_file <- file.path(input_dir, "medulloblastoma_vst_collapsed.tsv")

Output

gsva_results_file <- file.path(output_dir, "medulloblastoma_gsva_results.tsv")

Gene sets

The function that we will use to run GSVA wants the gene sets to be in a list, rather than a tidy data frame that we used with clusterProfiler (although it does accept other formats).

We’re going to take this opportunity to introduce a different format that gene sets are often distributed in called GMT (Gene Matrix Transposed).

We’re going to read in the Hallmark collection file directly from MSigDB, rather than using msigdbr like we did in earlier notebooks.

The RNA-seq data uses gene symbols, so we need gene sets that use gene symbols, too.

# R can often read in data from a URL
hallmarks_url <- "https://data.broadinstitute.org/gsea-msigdb/msigdb/release/7.1/h.all.v7.1.symbols.gmt"

# QuSAGE is another pathway analysis method, the qusage package has a function
# for reading GMT files and turning them into a list
hallmarks_list <- qusage::read.gmt(hallmarks_url)

What does this list look like?

head(hallmarks_list)

RNA-seq data

We have VST transformed RNA-seq data, which is on a log2-like scale. These data are from the Open Pediatric Brain Tumor Atlas (OpenPBTA) OpenPBTA is a collaborative project organized by the CCDL and the Center for Data-Driven Discovery in Biomedicine (D3b) at the Children’s Hospital of Philadelphia conducted openly on GitHub.

You can read more about the project here.

We’re only working with the medulloblastoma samples in this example.

rnaseq_df <- readr::read_tsv(rnaseq_file)
Rows: 49569 Columns: 122
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr   (1): gene_symbol
dbl (121): BS_09Z7TC35, BS_1AYRM596, BS_1BWP5MCT, BS_1QXEC43H, BS_1TWCV047, ...

ℹ 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.
# What does the RNA-seq data frame look like?
rnaseq_df[1:5, 1:5]

For GSVA, we need a matrix.

rnaseq_mat <- rnaseq_df |>
  tibble::column_to_rownames("gene_symbol") |>
  as.matrix()

Note: If we had duplicate gene symbols here, we couldn’t set them as rownames.

GSVA

Figure 1 from Hänzelmann et al. (2013).

You may notice that GSVA has some commonalities with GSEA. Rather than ranking genes based on some statistic we selected ahead of time, GSVA fits a model and ranks genes based on their expression level relative to the sample distribution. This is a way of asking if a gene i is highly or lowly expressed in a sample j in the context of this experiment and ranking accordingly (Hänzelmann et al. 2013). The pathway-level score calculated is a way of asking how genes within a gene set vary as compared to genes that are outside of that gene set (Malhotra. 2018). (This is sometimes called a competitive test in gene set enrichment literature.) The intuition here is that we will get pathway-level scores for each sample that indicate if genes in a pathway vary concordantly in one direction (overexpressed or underexpressed relative to the overall population) (Hänzelmann et al. 2013).

The output is a gene set by sample matrix of GSVA scores.

Perform GSVA

The main work for GSVA is done by the gsva() function, which can actually do a variety of different enrichment score calculations. To specify that we want the algorithm used by Hänzelmann et al. (2013), we will pass as the first argument a call to the gsvaParam() function, which is where we will put our data, gene sets, and other parameters specific to that algorithm.

gsva_results <- gsva(
  gsvaParam(
    exprData = rnaseq_mat,
    geneSets = hallmarks_list,
    # Minimum gene set size
    minSize = 15,
    # Maximum gene set size
    maxSize = 500,
    # Kernel for estimation 
    # Gaussian for our transformed data on log2-like scale
    kcdf = "Gaussian",
    # enrichment score is the difference between largest positive and negative
    maxDiff = TRUE
  ),
  # Use 4 cores for multiprocessing
  BPPARAM = BiocParallel::MulticoreParam(4)
)
Warning in .filterGenes(dataMatrix, removeConstant = removeConstant,
removeNzConstant = removeNzConstant): 945 genes with constant values throughout
the samples.
Warning in .filterGenes(dataMatrix, removeConstant = removeConstant,
removeNzConstant = removeNzConstant): Genes with constant values are discarded.
Setting parallel calculations through a MulticoreParam back-end with workers=4 and tasks=100.
Estimating GSVA scores for 50 gene sets.
Estimating ECDFs with Gaussian kernels
Estimating ECDFs in parallel on 4 cores

Note: the gsvaParam() documentation says we can use kcdf = "Gaussian" if we had RNA-seq log-CPMs, log-RPKMs or log-TPMs, but we would use kcdf = "Poisson" on integer counts.

# Let's explore what the output of gsva() looks like
gsva_results[1:5, 1:5]
                                    BS_09Z7TC35 BS_1AYRM596 BS_1BWP5MCT
HALLMARK_TNFA_SIGNALING_VIA_NFKB    -0.44911439   0.5208667  -0.5609193
HALLMARK_HYPOXIA                    -0.38297104   0.2436910  -0.5058759
HALLMARK_CHOLESTEROL_HOMEOSTASIS    -0.26534735   0.1054224  -0.5180933
HALLMARK_MITOTIC_SPINDLE             0.12727006   0.2339489  -0.4338076
HALLMARK_WNT_BETA_CATENIN_SIGNALING -0.09287646   0.1602124  -0.4427594
                                    BS_1QXEC43H BS_1TWCV047
HALLMARK_TNFA_SIGNALING_VIA_NFKB     0.32300630  -0.1468081
HALLMARK_HYPOXIA                     0.36247083  -0.2971559
HALLMARK_CHOLESTEROL_HOMEOSTASIS     0.32418657  -0.4561386
HALLMARK_MITOTIC_SPINDLE            -0.07023068  -0.1861483
HALLMARK_WNT_BETA_CATENIN_SIGNALING  0.41372523  -0.1152437

A note on gene set size

Often the scores of gene set enrichment methods are not comparable between gene sets of different sizes. Let’s do an experiment using randomly generated gene sets to explore this idea a bit more.

We need to get a collection of all possible genes we will sample from to create random gene sets. Because we’re doing some random sampling, we need to set a seed for this to be reproducible.

# Use all the gene symbols in the dataset as the pool of possible genes
all_genes <- rownames(rnaseq_mat)

# Set a seed for reproducibility
set.seed(2020)

Our minimum gene set size earlier was 15 genes and our maximum gene set size was 500 genes. We’ll use the same minimum and maximum values for our random gene sets and some values in between.

# Make a list of integers that indicate the random gene set sizes
gene_set_size <- list(15, 25, 50, 100, 250, 500)

For each gene set size, we will generate 100 random gene sets.

# Set number of replicates
nreps <- 100
# Generate 100 random gene sets of each size
random_gene_sets <- rep(gene_set_size, nreps) |>  # Repeat gene sizes so we run `nreps` times
  purrr::map(
    # Sample the vector of all genes, choosing the number of items specified
    # in the element of gene set size
    ~ base::sample(x = all_genes,
                   size = .x)
  )

The Hallmarks list we used earlier stored the gene set names as the name of the list, so let’s add names to our random gene sets that indicate what size they are and so gsva() doesn’t get upset.

# We will include the size of the gene set in the gene set name
# Start by taking the length of each pathway and appending "pathway_" to that
# number
lengths_vector <- random_gene_sets |>
  # Get the length of each gene set (number of genes)
  purrr::map(~ length(.x)) |>
  # Make it "pathway_<gene set size>"
  purrr::map(~ paste0("pathway_", .x)) |>
  # Return a vector
  purrr::flatten_chr()

# Add the names in lengths_vector to the list - "pathway_<gene set size>"
random_gene_sets <- random_gene_sets |>
  # make.names() appends a "version" if something is not unique
  purrr::set_names(nm = make.names(lengths_vector, unique = TRUE))

Run GSVA on our dataset with the same parameters as before, but now with random gene sets.

random_gsva_results <- gsva(
  gsvaParam(
    exprData = rnaseq_mat,
    geneSets = random_gene_sets,
    minSize = 15,
    maxSize = 500,
    kcdf = "Gaussian",
    maxDiff = TRUE
  ),
  # Use 4 cores for multiprocessing
  BPPARAM = BiocParallel::MulticoreParam(4)
)
Warning in .filterGenes(dataMatrix, removeConstant = removeConstant,
removeNzConstant = removeNzConstant): 945 genes with constant values throughout
the samples.
Warning in .filterGenes(dataMatrix, removeConstant = removeConstant,
removeNzConstant = removeNzConstant): Genes with constant values are discarded.
Setting parallel calculations through a MulticoreParam back-end with workers=4 and tasks=100.
Estimating GSVA scores for 579 gene sets.
Estimating ECDFs with Gaussian kernels
Estimating ECDFs in parallel on 4 cores

Now let’s make a plot to look at the distribution of scores from random gene sets. First we need to get this data in an appropriate format for ggplot2.

# The random results are a matrix
random_long_df <- random_gsva_results |>
  data.frame() |>
  # Gene set names are rownames
  tibble::rownames_to_column("gene_set") |>
  # Get into long format
  tidyr::pivot_longer(
    cols = -gene_set,
    names_to = "Kids_First_Biospecimen_ID",
    values_to = "gsva_score"
  ) |>
  # Remove the .version added by make.names()
  dplyr::mutate(gene_set = stringr::str_remove(gene_set, "\\..*")) |>
  # Add a column that keeps track of the gene set size
  dplyr::mutate(gene_set_size = stringr::word(gene_set, 2, sep = "_")) |>
  # We want to plot smallest no. genes -> largest no. genes
  dplyr::mutate(gene_set_size = factor(gene_set_size,
                                       levels = c(15, 25, 50, 100, 250, 500)))

Let’s make a violin plot so we can look at the distribution of scores by gene set size.

# Violin plot comparing GSVA scores of different random gene set sizes
random_long_df |>
  ggplot2::ggplot(ggplot2::aes(x = gene_set_size,
                               y = gsva_score)) +
  # Make a violin plot that's a pretty blue!
  ggplot2::geom_violin(fill = "#99CCFF", alpha = 0.5) +
  # Add a point with the mean value
  ggplot2::stat_summary(
    geom = "point",
    fun = "mean",
    # Change the aesthetics of the points
    size = 3,
    color = "#0066CC",
    shape = 18
  ) +
  # Flip the axes
  ggplot2::coord_flip() +
  ggplot2::labs(title = "Random Gene Set GSVA Scores",
                x = "gene set size",
                y = "GSVA score") +
  ggplot2::theme_bw()

What do you notice about these distributions? How might you use this information to inform your interpretation of GSVA scores?

How can you use these scores?

If you did have groups information for your samples, you could test for pathway scores differences between groups. Here’s an example of that from the OpenPBTA project itself!

You can also visualize this matrix in a heatmap. Here’s a figure from the OpenPBTA project, where the middle panel is a heatmap of GSVA scores that were significantly different between histologies.

Code here.

Write results to file

gsva_results |>
  as.data.frame() |>
  tibble::rownames_to_column("pathway") |>
  readr::write_tsv(file = gsva_results_file)

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] GSVA_1.52.0    optparse_1.7.5

loaded via a namespace (and not attached):
  [1] DBI_1.2.2                   GSEABase_1.66.0            
  [3] rlang_1.1.3                 magrittr_2.0.3             
  [5] matrixStats_1.3.0           compiler_4.4.1             
  [7] RSQLite_2.3.6               png_0.1-8                  
  [9] vctrs_0.6.5                 stringr_1.5.1              
 [11] pkgconfig_2.0.3             SpatialExperiment_1.14.0   
 [13] crayon_1.5.2                fastmap_1.1.1              
 [15] magick_2.8.3                XVector_0.44.0             
 [17] labeling_0.4.3              utf8_1.2.4                 
 [19] rmarkdown_2.26              tzdb_0.4.0                 
 [21] graph_1.82.0                UCSC.utils_1.0.0           
 [23] purrr_1.0.2                 bit_4.0.5                  
 [25] xfun_0.43                   zlibbioc_1.50.0            
 [27] cachem_1.0.8                beachmat_2.20.0            
 [29] GenomeInfoDb_1.40.0         jsonlite_1.8.8             
 [31] blob_1.2.4                  highr_0.10                 
 [33] rhdf5filters_1.16.0         DelayedArray_0.30.0        
 [35] Rhdf5lib_1.26.0             BiocParallel_1.38.0        
 [37] irlba_2.3.5.1               parallel_4.4.1             
 [39] R6_2.5.1                    bslib_0.7.0                
 [41] stringi_1.8.3               limma_3.60.0               
 [43] GenomicRanges_1.56.0        jquerylib_0.1.4            
 [45] estimability_1.5            Rcpp_1.0.12                
 [47] SummarizedExperiment_1.34.0 knitr_1.46                 
 [49] readr_2.1.5                 IRanges_2.38.0             
 [51] tidyselect_1.2.1            Matrix_1.7-0               
 [53] abind_1.4-5                 yaml_2.3.8                 
 [55] codetools_0.2-20            lattice_0.22-6             
 [57] tibble_3.2.1                withr_3.0.0                
 [59] Biobase_2.64.0              KEGGREST_1.44.0            
 [61] coda_0.19-4.1               evaluate_0.23              
 [63] getopt_1.20.4               Biostrings_2.72.0          
 [65] pillar_1.9.0                MatrixGenerics_1.16.0      
 [67] stats4_4.4.1                generics_0.1.3             
 [69] vroom_1.6.5                 ggplot2_3.5.1              
 [71] S4Vectors_0.42.0            hms_1.1.3                  
 [73] munsell_0.5.1               scales_1.3.0               
 [75] sparseMatrixStats_1.16.0    xtable_1.8-4               
 [77] glue_1.7.0                  emmeans_1.10.1             
 [79] tools_4.4.1                 ScaledMatrix_1.12.0        
 [81] annotate_1.82.0             mvtnorm_1.2-4              
 [83] XML_3.99-0.16.1             rhdf5_2.48.0               
 [85] grid_4.4.1                  tidyr_1.3.1                
 [87] colorspace_2.1-0            AnnotationDbi_1.66.0       
 [89] SingleCellExperiment_1.26.0 nlme_3.1-164               
 [91] GenomeInfoDbData_1.2.12     BiocSingular_1.20.0        
 [93] qusage_2.38.0               HDF5Array_1.32.0           
 [95] cli_3.6.2                   rsvd_1.0.5                 
 [97] fansi_1.0.6                 S4Arrays_1.4.0             
 [99] dplyr_1.1.4                 gtable_0.3.5               
 [ reached getOption("max.print") -- omitted 13 entries ]
LS0tCnRpdGxlOiAiUGF0aHdheSBhbmFseXNpczogR2VuZSBTZXQgVmFyaWF0aW9uIEFuYWx5c2lzIChHU1ZBKSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQphdXRob3I6IENDREwgZm9yIEFMU0YKZGF0ZTogMjAyNAotLS0KCiMjIE9iamVjdGl2ZXMKClRoaXMgbm90ZWJvb2sgd2lsbCBkZW1vbnN0cmF0ZSBob3cgdG86CgotIElkZW50aWZ5IHdoZW4gR2VuZSBTZXQgVmFyaWF0aW9uIEFuYWx5c2lzIChHU1ZBKSBpcyB3ZWxsLXN1aXRlZCBmb3IgYW4gYW5hbHlzaXMKLSBQZXJmb3JtIEdTVkEgb24gdHJhbnNmb3JtZWQgUk5BLXNlcSBkYXRhIHdpdGggdGhlIGBHU1ZBYCBwYWNrYWdlCi0gRXhwbG9yZSB0aGUgZGVwZW5kZW5jZSBvZiBHU1ZBIHNjb3JlcyBvbiBnZW5lIHNldCBzaXplIHdpdGggcmFuZG9tIGdlbmUgc2V0cwoKLS0tCgpTbyBmYXIgZXZlcnkgcGF0aHdheSBhbmFseXNpcyBtZXRob2Qgd2UndmUgY292ZXJlZCByZWxpZXMgb24gc29tZSBpbmZvcm1hdGlvbiBhYm91dCBncm91cHMgb2Ygc2FtcGxlcyBpbiBvdXIgZGF0YS4KRm9yIG92ZXItcmVwcmVzZW50YXRpb24gYW5hbHlzaXMgKE9SQSksIHdlIGNyZWF0ZWQgZ2VuZSBzZXRzIGZyb20gdHdvIGRpZmZlcmVudCB0d28gZ3JvdXAgY29tcGFyaXNvbnMuCkluIHRoZSBHZW5lIFNldCBFbnJpY2htZW50IEFuYWx5c2lzIChHU0VBKSBleGFtcGxlLCB3ZSB1c2VkIHN0YXRpc3RpY3MgZnJvbSBhIGRpZmZlcmVudGlhbCBnZW5lIGV4cHJlc3Npb24gKERHRSkgYW5hbHlzaXMgd2hlcmUgd2UgY29tcGFyZWQgX01ZQ05fIGFtcGxpZmllZCBjZWxsIGxpbmVzIHRvIG5vbi1hbXBsaWZpZWQgY2VsbCBsaW5lczsgd2UgbmVlZGVkIHRoYXQgYW1wbGlmaWNhdGlvbiBzdGF0dXMgaW5mb3JtYXRpb24uCgpXaGF0IGlmIHdlJ3JlIGxlc3Mgc3VyZSBhYm91dCBncm91cHMgaW4gb3VyIGRhdGEgb3Igd2Ugd2FudCB0byBhbmFseXplIG91ciBkYXRhIGluIGEgbW9yZSB1bnN1cGVydmlzZWQgbWFubmVyPwoKSW4gdGhpcyBub3RlYm9vayB3ZSB3aWxsIGNvdmVyIGEgbWV0aG9kIGNhbGxlZCBHZW5lIFNldCBWYXJpYXRpb24gQW5hbHlzaXMgKEdTVkEpIChbSMOkbnplbG1hbm4gX2V0IGFsLl8gMjAxM10oaHR0cHM6Ly9kb2kub3JnLzEwLjExODYvMTQ3MS0yMTA1LTE0LTcpKSB0aGF0IGFsbG93cyB1cyB0byBjYWxjdWxhdGUgZ2VuZSBzZXQgb3IgcGF0aHdheSBzY29yZXMgb24gYSBwZXItc2FtcGxlIGJhc2lzLgoKV2UgbGlrZSB0aGlzIHF1b3RlIGZyb20gdGhlIEdTVkEgcGFwZXIgKFtIw6RuemVsbWFubiBfZXQgYWwuXyAyMDEzXShodHRwczovL2RvaS5vcmcvMTAuMTE4Ni8xNDcxLTIxMDUtMTQtNykpIHRvIHNldCB0aGUgc3RhZ2U6Cgo+IFdoaWxlIFtnZW5lIHNldCBlbnJpY2htZW50XSBtZXRob2RzIGFyZSBnZW5lcmFsbHkgcmVnYXJkZWQgYXMgZW5kIHBvaW50cyBvZiBhIGJpb2luZm9ybWF0aWMgYW5hbHlzaXMsIEdTVkEgY29uc3RpdHV0ZXMgYSBzdGFydGluZyBwb2ludCB0byBidWlsZCBwYXRod2F5LWNlbnRyaWMgbW9kZWxzIG9mIGJpb2xvZ3kuCgpSYXRoZXIgdGhhbiBjb250ZXh0dWFsaXppbmcgc29tZSByZXN1bHRzIHlvdSBfYWxyZWFkeSBoYXZlXyBmcm9tIGFub3RoZXIgYW5hbHlzaXMgbGlrZSBER0UsIEdTVkEgaXMgZGVzaWduZWQgdG8gcHJvdmlkZSBhbiBlc3RpbWF0ZSBvZiBwYXRod2F5IHZhcmlhdGlvbiBmb3IgZWFjaCBvZiB0aGUgc2FtcGxlcyBpbiBhbiBleHBlcmltZW50LgpOb3RlIHRoYXQgdGhlc2Ugc2NvcmVzIHdpbGwgZGVwZW5kIG9uIHRoZSBzYW1wbGVzIGluY2x1ZGVkIGluIHRoZSBkYXRhc2V0IHdoZW4geW91IHJ1biBHU1ZBOyBpZiB5b3UgYWRkZWQgbW9yZSBzYW1wbGVzIGFuZCByZXJhbiBHU1ZBLCB5b3Ugd291bGQgZXhwZWN0IHRoZSBzY29yZXMgdG8gY2hhbmdlLgoKIyMjIyBPdGhlciByZXNvdXJjZXMKCiogW01hbGhvdHJhLiBfRGVjb2RpbmcgR2VuZSBTZXQgVmFyaWF0aW9uIEFuYWx5c2lzXy4gKDIwMTgpXShodHRwczovL3Rvd2FyZHNkYXRhc2NpZW5jZS5jb20vZGVjb2RpbmctZ2VuZS1zZXQtdmFyaWF0aW9uLWFuYWx5c2lzLTgxOTNhMGNmZGEzKQoKIyMgU2V0IHVwCgojIyMgTGlicmFyaWVzCgpgYGB7ciBsaWJyYXJpZXN9CiMgR2VuZSBTZXQgVmFyaWF0aW9uIEFuYWx5c2lzCmxpYnJhcnkoR1NWQSkKYGBgCgojIyMgRGlyZWN0b3JpZXMgYW5kIGZpbGVzCgojIyMjIERpcmVjdG9yaWVzCgpgYGB7ciBkaXJlY3Rvcmllc30KIyBXZSBoYXZlIHNvbWUgbWVkdWxsb2JsYXN0b21hIGRhdGEgZnJvbSB0aGUgT3BlblBCVEEgcHJvamVjdCB0aGF0IHdlJ3ZlCiMgcHJlcGFyZWQgYWhlYWQgb2YgdGltZQppbnB1dF9kaXIgPC0gZmlsZS5wYXRoKCJkYXRhIiwgIm9wZW4tcGJ0YSIpCgojIENyZWF0ZSBhIGRpcmVjdG9yeSBzcGVjaWZpY2FsbHkgZm9yIHRoZSByZXN1bHRzIHVzaW5nIHRoaXMgZGF0YXNldApvdXRwdXRfZGlyIDwtIGZpbGUucGF0aCgicmVzdWx0cyIsICJvcGVuLXBidGEiKQppZiAoIWRpci5leGlzdHMob3V0cHV0X2RpcikpIHsKICBkaXIuY3JlYXRlKG91dHB1dF9kaXIsIHJlY3Vyc2l2ZSA9IFRSVUUpCn0KYGBgCgojIyMjIElucHV0CgpXZSBoYXZlIFZTVCB0cmFuc2Zvcm1lZCBSTkEtc2VxIGRhdGEsIGFubm90YXRlZCB3aXRoIGdlbmUgc3ltYm9scywgdGhhdCBoYXMgYmVlbiBjb2xsYXBzZWQgc3VjaCB0aGF0IHRoZXJlIGFyZSBubyBkdXBsaWNhdGVkIGdlbmUgaWRlbnRpZmllcnMgKHNlZSBgc2V0dXBgKS4KCmBgYHtyIGlucHV0X2ZpbGV9CnJuYXNlcV9maWxlIDwtIGZpbGUucGF0aChpbnB1dF9kaXIsICJtZWR1bGxvYmxhc3RvbWFfdnN0X2NvbGxhcHNlZC50c3YiKQpgYGAKCiMjIyMgT3V0cHV0CgpgYGB7ciBvdXRwdXRfZmlsZSwgbGl2ZSA9IFRSVUV9CmdzdmFfcmVzdWx0c19maWxlIDwtIGZpbGUucGF0aChvdXRwdXRfZGlyLCAibWVkdWxsb2JsYXN0b21hX2dzdmFfcmVzdWx0cy50c3YiKQpgYGAKCiMjIEdlbmUgc2V0cwoKVGhlIGZ1bmN0aW9uIHRoYXQgd2Ugd2lsbCB1c2UgdG8gcnVuIEdTVkEgd2FudHMgdGhlIGdlbmUgc2V0cyB0byBiZSBpbiBhIGxpc3QsIHJhdGhlciB0aGFuIGEgdGlkeSBkYXRhIGZyYW1lIHRoYXQgd2UgdXNlZCB3aXRoIGBjbHVzdGVyUHJvZmlsZXJgIChhbHRob3VnaCBpdCBkb2VzIGFjY2VwdCBvdGhlciBmb3JtYXRzKS4KCldlJ3JlIGdvaW5nIHRvIHRha2UgdGhpcyBvcHBvcnR1bml0eSB0byBpbnRyb2R1Y2UgYSBkaWZmZXJlbnQgZm9ybWF0IHRoYXQgZ2VuZSBzZXRzIGFyZSBvZnRlbiBkaXN0cmlidXRlZCBpbiBjYWxsZWQgW0dNVCAoR2VuZSBNYXRyaXggVHJhbnNwb3NlZCldKGh0dHBzOi8vc29mdHdhcmUuYnJvYWRpbnN0aXR1dGUub3JnL2NhbmNlci9zb2Z0d2FyZS9nc2VhL3dpa2kvaW5kZXgucGhwL0RhdGFfZm9ybWF0cyNHTVQ6X0dlbmVfTWF0cml4X1RyYW5zcG9zZWRfZmlsZV9mb3JtYXRfLjI4LjJBLmdtdC4yOSkuCgpXZSdyZSBnb2luZyB0byByZWFkIGluIHRoZSBIYWxsbWFyayBjb2xsZWN0aW9uIGZpbGUgZGlyZWN0bHkgZnJvbSBbTVNpZ0RCXShodHRwczovL3d3dy5nc2VhLW1zaWdkYi5vcmcvZ3NlYS9tc2lnZGIvaW5kZXguanNwKSwgcmF0aGVyIHRoYW4gdXNpbmcgYG1zaWdkYnJgIGxpa2Ugd2UgZGlkIGluIGVhcmxpZXIgbm90ZWJvb2tzLgoKVGhlIFJOQS1zZXEgZGF0YSB1c2VzIGdlbmUgc3ltYm9scywgc28gd2UgbmVlZCBnZW5lIHNldHMgdGhhdCB1c2UgZ2VuZSBzeW1ib2xzLCB0b28uCgpgYGB7ciBnbXR9CiMgUiBjYW4gb2Z0ZW4gcmVhZCBpbiBkYXRhIGZyb20gYSBVUkwKaGFsbG1hcmtzX3VybCA8LSAiaHR0cHM6Ly9kYXRhLmJyb2FkaW5zdGl0dXRlLm9yZy9nc2VhLW1zaWdkYi9tc2lnZGIvcmVsZWFzZS83LjEvaC5hbGwudjcuMS5zeW1ib2xzLmdtdCIKCiMgUXVTQUdFIGlzIGFub3RoZXIgcGF0aHdheSBhbmFseXNpcyBtZXRob2QsIHRoZSBxdXNhZ2UgcGFja2FnZSBoYXMgYSBmdW5jdGlvbgojIGZvciByZWFkaW5nIEdNVCBmaWxlcyBhbmQgdHVybmluZyB0aGVtIGludG8gYSBsaXN0CmhhbGxtYXJrc19saXN0IDwtIHF1c2FnZTo6cmVhZC5nbXQoaGFsbG1hcmtzX3VybCkKYGBgCgpXaGF0IGRvZXMgdGhpcyBsaXN0IGxvb2sgbGlrZT8KCmBgYHtyIGhlYWRfaGFsbG1hcmssIGV2YWwgPSBGQUxTRX0KaGVhZChoYWxsbWFya3NfbGlzdCkKYGBgCgojIyBSTkEtc2VxIGRhdGEKCldlIGhhdmUgVlNUIHRyYW5zZm9ybWVkIFJOQS1zZXEgZGF0YSwgd2hpY2ggaXMgb24gYSBsb2cyLWxpa2Ugc2NhbGUuClRoZXNlIGRhdGEgYXJlIGZyb20gdGhlIE9wZW4gUGVkaWF0cmljIEJyYWluIFR1bW9yIEF0bGFzIChPcGVuUEJUQSkKT3BlblBCVEEgaXMgYSBjb2xsYWJvcmF0aXZlIHByb2plY3Qgb3JnYW5pemVkIGJ5IHRoZSBDQ0RMIGFuZCB0aGUgQ2VudGVyIGZvciBEYXRhLURyaXZlbiBEaXNjb3ZlcnkgaW4gQmlvbWVkaWNpbmUgKEQzYikgYXQgdGhlIENoaWxkcmVuJ3MgSG9zcGl0YWwgb2YgUGhpbGFkZWxwaGlhIGNvbmR1Y3RlZCBvcGVubHkgb24gR2l0SHViLgoKWW91IGNhbiByZWFkIG1vcmUgYWJvdXQgdGhlIHByb2plY3QgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9hbGV4c2xlbW9uYWRlL29wZW5wYnRhLWFuYWx5c2lzLyNvcGVucGJ0YS1hbmFseXNpcykuCgpXZSdyZSBvbmx5IHdvcmtpbmcgd2l0aCB0aGUgbWVkdWxsb2JsYXN0b21hIHNhbXBsZXMgaW4gdGhpcyBleGFtcGxlLgoKYGBge3IgcmVhZF9pbl9ybmFzZXF9CnJuYXNlcV9kZiA8LSByZWFkcjo6cmVhZF90c3Yocm5hc2VxX2ZpbGUpCmBgYAoKYGBge3IgcGVla19ybmFzZXEsIGxpdmUgPSBUUlVFfQojIFdoYXQgZG9lcyB0aGUgUk5BLXNlcSBkYXRhIGZyYW1lIGxvb2sgbGlrZT8Kcm5hc2VxX2RmWzE6NSwgMTo1XQpgYGAKCkZvciBHU1ZBLCB3ZSBuZWVkIGEgbWF0cml4LgoKYGBge3Igcm5hc2VxX21hdCwgbGl2ZSA9IFRSVUV9CnJuYXNlcV9tYXQgPC0gcm5hc2VxX2RmIHw+CiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoImdlbmVfc3ltYm9sIikgfD4KICBhcy5tYXRyaXgoKQpgYGAKCipOb3RlOiBJZiB3ZSBoYWQgZHVwbGljYXRlIGdlbmUgc3ltYm9scyBoZXJlLCB3ZSBjb3VsZG4ndCBzZXQgdGhlbSBhcyByb3duYW1lcy4qCgojIyBHU1ZBCgohW10oZGlhZ3JhbXMvaGFuemVsbWFubl9maWcxLmpwZykKCioqRmlndXJlIDEgZnJvbSBbSMOkbnplbG1hbm4gX2V0IGFsLl8gKDIwMTMpXShodHRwczovL2RvaS5vcmcvMTAuMTE4Ni8xNDcxLTIxMDUtMTQtNykuKioKCllvdSBtYXkgbm90aWNlIHRoYXQgR1NWQSBoYXMgc29tZSBjb21tb25hbGl0aWVzIHdpdGggR1NFQS4KUmF0aGVyIHRoYW4gcmFua2luZyBnZW5lcyBiYXNlZCBvbiBzb21lIHN0YXRpc3RpYyBfd2VfIHNlbGVjdGVkIGFoZWFkIG9mIHRpbWUsIEdTVkEgZml0cyBhIG1vZGVsIGFuZCByYW5rcyBnZW5lcyBiYXNlZCBvbiB0aGVpciBleHByZXNzaW9uIGxldmVsIHJlbGF0aXZlIHRvIHRoZSBzYW1wbGUgZGlzdHJpYnV0aW9uLgpUaGlzIGlzIGEgd2F5IG9mIGFza2luZyBpZiBhIGdlbmUgX2lfIGlzIGhpZ2hseSBvciBsb3dseSBleHByZXNzZWQgaW4gYSBzYW1wbGUgX2pfIGluIHRoZSBjb250ZXh0IG9mIHRoaXMgZXhwZXJpbWVudCBhbmQgcmFua2luZyBhY2NvcmRpbmdseSAoW0jDpG56ZWxtYW5uIF9ldCBhbC5fIDIwMTNdKGh0dHBzOi8vZG9pLm9yZy8xMC4xMTg2LzE0NzEtMjEwNS0xNC03KSkuClRoZSBwYXRod2F5LWxldmVsIHNjb3JlIGNhbGN1bGF0ZWQgaXMgYSB3YXkgb2YgYXNraW5nIGhvdyBnZW5lcyBfd2l0aGluXyBhIGdlbmUgc2V0IHZhcnkgYXMgY29tcGFyZWQgdG8gZ2VuZXMgdGhhdCBhcmUgX291dHNpZGVfIG9mIHRoYXQgZ2VuZSBzZXQgKFtNYWxob3RyYS4gMjAxOF0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2RlY29kaW5nLWdlbmUtc2V0LXZhcmlhdGlvbi1hbmFseXNpcy04MTkzYTBjZmRhMykpLgooVGhpcyBpcyBzb21ldGltZXMgY2FsbGVkIGEgY29tcGV0aXRpdmUgdGVzdCBpbiBnZW5lIHNldCBlbnJpY2htZW50IGxpdGVyYXR1cmUuKQpUaGUgaW50dWl0aW9uIGhlcmUgaXMgdGhhdCB3ZSB3aWxsIGdldCBwYXRod2F5LWxldmVsIHNjb3JlcyBmb3IgZWFjaCBzYW1wbGUgdGhhdCBpbmRpY2F0ZSBpZiBnZW5lcyBpbiBhIHBhdGh3YXkgdmFyeSBjb25jb3JkYW50bHkgaW4gb25lIGRpcmVjdGlvbiAob3ZlcmV4cHJlc3NlZCBvciB1bmRlcmV4cHJlc3NlZCByZWxhdGl2ZSB0byB0aGUgb3ZlcmFsbCBwb3B1bGF0aW9uKSAoW0jDpG56ZWxtYW5uIF9ldCBhbC5fIDIwMTNdKGh0dHBzOi8vZG9pLm9yZy8xMC4xMTg2LzE0NzEtMjEwNS0xNC03KSkuCgpUaGUgb3V0cHV0IGlzIGEgZ2VuZSBzZXQgYnkgc2FtcGxlIG1hdHJpeCBvZiBHU1ZBIHNjb3Jlcy4KCiMjIyBQZXJmb3JtIEdTVkEKClRoZSBtYWluIHdvcmsgZm9yIEdTVkEgaXMgZG9uZSBieSB0aGUgYGdzdmEoKWAgZnVuY3Rpb24sIHdoaWNoIGNhbiBhY3R1YWxseSBkbyBhIHZhcmlldHkgb2YgZGlmZmVyZW50IGVucmljaG1lbnQgc2NvcmUgY2FsY3VsYXRpb25zLgpUbyBzcGVjaWZ5IHRoYXQgd2Ugd2FudCB0aGUgYWxnb3JpdGhtIHVzZWQgYnkgW0jDpG56ZWxtYW5uIF9ldCBhbC5fICgyMDEzKV0oaHR0cHM6Ly9kb2kub3JnLzEwLjExODYvMTQ3MS0yMTA1LTE0LTcpLCB3ZSB3aWxsIHBhc3MgYXMgdGhlIGZpcnN0IGFyZ3VtZW50IGEgY2FsbCB0byB0aGUgYGdzdmFQYXJhbSgpYCBmdW5jdGlvbiwgd2hpY2ggaXMgd2hlcmUgd2Ugd2lsbCBwdXQgb3VyIGRhdGEsIGdlbmUgc2V0cywgYW5kIG90aGVyIHBhcmFtZXRlcnMgc3BlY2lmaWMgdG8gdGhhdCBhbGdvcml0aG0uCgpgYGB7ciBydW5fZ3N2YX0KZ3N2YV9yZXN1bHRzIDwtIGdzdmEoCiAgZ3N2YVBhcmFtKAogICAgZXhwckRhdGEgPSBybmFzZXFfbWF0LAogICAgZ2VuZVNldHMgPSBoYWxsbWFya3NfbGlzdCwKICAgICMgTWluaW11bSBnZW5lIHNldCBzaXplCiAgICBtaW5TaXplID0gMTUsCiAgICAjIE1heGltdW0gZ2VuZSBzZXQgc2l6ZQogICAgbWF4U2l6ZSA9IDUwMCwKICAgICMgS2VybmVsIGZvciBlc3RpbWF0aW9uIAogICAgIyBHYXVzc2lhbiBmb3Igb3VyIHRyYW5zZm9ybWVkIGRhdGEgb24gbG9nMi1saWtlIHNjYWxlCiAgICBrY2RmID0gIkdhdXNzaWFuIiwKICAgICMgZW5yaWNobWVudCBzY29yZSBpcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGxhcmdlc3QgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlCiAgICBtYXhEaWZmID0gVFJVRQogICksCiAgIyBVc2UgNCBjb3JlcyBmb3IgbXVsdGlwcm9jZXNzaW5nCiAgQlBQQVJBTSA9IEJpb2NQYXJhbGxlbDo6TXVsdGljb3JlUGFyYW0oNCkKKQpgYGAKCioqTm90ZTogdGhlIGBnc3ZhUGFyYW0oKWAgZG9jdW1lbnRhdGlvbiBzYXlzIHdlIGNhbiB1c2UgYGtjZGYgPSAiR2F1c3NpYW4iYCBpZiB3ZSBoYWQgUk5BLXNlcSBsb2ctQ1BNcywgbG9nLVJQS01zIG9yIGxvZy1UUE1zLCBidXQgd2Ugd291bGQgdXNlIGBrY2RmID0gIlBvaXNzb24iYCBvbiBpbnRlZ2VyIGNvdW50cy4qKgoKYGBge3IgZ3N2YV9wZWVrfQojIExldCdzIGV4cGxvcmUgd2hhdCB0aGUgb3V0cHV0IG9mIGdzdmEoKSBsb29rcyBsaWtlCmdzdmFfcmVzdWx0c1sxOjUsIDE6NV0KYGBgCgojIyMgQSBub3RlIG9uIGdlbmUgc2V0IHNpemUKCk9mdGVuIHRoZSBzY29yZXMgb2YgZ2VuZSBzZXQgZW5yaWNobWVudCBtZXRob2RzIGFyZSBub3QgY29tcGFyYWJsZSBiZXR3ZWVuIGdlbmUgc2V0cyBvZiBkaWZmZXJlbnQgc2l6ZXMuCkxldCdzIGRvIGFuIGV4cGVyaW1lbnQgdXNpbmcgcmFuZG9tbHkgZ2VuZXJhdGVkIGdlbmUgc2V0cyB0byBleHBsb3JlIHRoaXMgaWRlYSBhIGJpdCBtb3JlLgoKV2UgbmVlZCB0byBnZXQgYSBjb2xsZWN0aW9uIG9mIGFsbCBwb3NzaWJsZSBnZW5lcyB3ZSB3aWxsIHNhbXBsZSBmcm9tIHRvIGNyZWF0ZSByYW5kb20gZ2VuZSBzZXRzLgpCZWNhdXNlIHdlJ3JlIGRvaW5nIHNvbWUgcmFuZG9tIHNhbXBsaW5nLCB3ZSBuZWVkIHRvIHNldCBhIHNlZWQgZm9yIHRoaXMgdG8gYmUgcmVwcm9kdWNpYmxlLgoKYGBge3IgcmFuZG9tX3NldHVwfQojIFVzZSBhbGwgdGhlIGdlbmUgc3ltYm9scyBpbiB0aGUgZGF0YXNldCBhcyB0aGUgcG9vbCBvZiBwb3NzaWJsZSBnZW5lcwphbGxfZ2VuZXMgPC0gcm93bmFtZXMocm5hc2VxX21hdCkKCiMgU2V0IGEgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5CnNldC5zZWVkKDIwMjApCmBgYAoKT3VyIG1pbmltdW0gZ2VuZSBzZXQgc2l6ZSBlYXJsaWVyIHdhcyAxNSBnZW5lcyBhbmQgb3VyIG1heGltdW0gZ2VuZSBzZXQgc2l6ZSB3YXMgNTAwIGdlbmVzLgpXZSdsbCB1c2UgdGhlIHNhbWUgbWluaW11bSBhbmQgbWF4aW11bSB2YWx1ZXMgZm9yIG91ciByYW5kb20gZ2VuZSBzZXRzIGFuZCBzb21lIHZhbHVlcyBpbiBiZXR3ZWVuLgoKYGBge3IgZ2VuZV9zZXRfc2l6ZXN9CiMgTWFrZSBhIGxpc3Qgb2YgaW50ZWdlcnMgdGhhdCBpbmRpY2F0ZSB0aGUgcmFuZG9tIGdlbmUgc2V0IHNpemVzCmdlbmVfc2V0X3NpemUgPC0gbGlzdCgxNSwgMjUsIDUwLCAxMDAsIDI1MCwgNTAwKQpgYGAKCkZvciBlYWNoIGdlbmUgc2V0IHNpemUsIHdlIHdpbGwgZ2VuZXJhdGUgMTAwIHJhbmRvbSBnZW5lIHNldHMuCgpgYGB7ciByYW5kb21fZ2VuZV9zZXRzfQojIFNldCBudW1iZXIgb2YgcmVwbGljYXRlcwpucmVwcyA8LSAxMDAKIyBHZW5lcmF0ZSAxMDAgcmFuZG9tIGdlbmUgc2V0cyBvZiBlYWNoIHNpemUKcmFuZG9tX2dlbmVfc2V0cyA8LSByZXAoZ2VuZV9zZXRfc2l6ZSwgbnJlcHMpIHw+ICAjIFJlcGVhdCBnZW5lIHNpemVzIHNvIHdlIHJ1biBgbnJlcHNgIHRpbWVzCiAgcHVycnI6Om1hcCgKICAgICMgU2FtcGxlIHRoZSB2ZWN0b3Igb2YgYWxsIGdlbmVzLCBjaG9vc2luZyB0aGUgbnVtYmVyIG9mIGl0ZW1zIHNwZWNpZmllZAogICAgIyBpbiB0aGUgZWxlbWVudCBvZiBnZW5lIHNldCBzaXplCiAgICB+IGJhc2U6OnNhbXBsZSh4ID0gYWxsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgc2l6ZSA9IC54KQogICkKYGBgCgpUaGUgSGFsbG1hcmtzIGxpc3Qgd2UgdXNlZCBlYXJsaWVyIHN0b3JlZCB0aGUgZ2VuZSBzZXQgbmFtZXMgYXMgdGhlIG5hbWUgb2YgdGhlIGxpc3QsIHNvIGxldCdzIGFkZCBuYW1lcyB0byBvdXIgcmFuZG9tIGdlbmUgc2V0cyB0aGF0IGluZGljYXRlIHdoYXQgc2l6ZSB0aGV5IGFyZSBhbmQgc28gYGdzdmEoKWAgZG9lc24ndCBnZXQgdXBzZXQuCgpgYGB7ciBuYW1lX3JhbmRvbV9nZW5lX3NldHN9CiMgV2Ugd2lsbCBpbmNsdWRlIHRoZSBzaXplIG9mIHRoZSBnZW5lIHNldCBpbiB0aGUgZ2VuZSBzZXQgbmFtZQojIFN0YXJ0IGJ5IHRha2luZyB0aGUgbGVuZ3RoIG9mIGVhY2ggcGF0aHdheSBhbmQgYXBwZW5kaW5nICJwYXRod2F5XyIgdG8gdGhhdAojIG51bWJlcgpsZW5ndGhzX3ZlY3RvciA8LSByYW5kb21fZ2VuZV9zZXRzIHw+CiAgIyBHZXQgdGhlIGxlbmd0aCBvZiBlYWNoIGdlbmUgc2V0IChudW1iZXIgb2YgZ2VuZXMpCiAgcHVycnI6Om1hcCh+IGxlbmd0aCgueCkpIHw+CiAgIyBNYWtlIGl0ICJwYXRod2F5XzxnZW5lIHNldCBzaXplPiIKICBwdXJycjo6bWFwKH4gcGFzdGUwKCJwYXRod2F5XyIsIC54KSkgfD4KICAjIFJldHVybiBhIHZlY3RvcgogIHB1cnJyOjpmbGF0dGVuX2NocigpCgojIEFkZCB0aGUgbmFtZXMgaW4gbGVuZ3Roc192ZWN0b3IgdG8gdGhlIGxpc3QgLSAicGF0aHdheV88Z2VuZSBzZXQgc2l6ZT4iCnJhbmRvbV9nZW5lX3NldHMgPC0gcmFuZG9tX2dlbmVfc2V0cyB8PgogICMgbWFrZS5uYW1lcygpIGFwcGVuZHMgYSAidmVyc2lvbiIgaWYgc29tZXRoaW5nIGlzIG5vdCB1bmlxdWUKICBwdXJycjo6c2V0X25hbWVzKG5tID0gbWFrZS5uYW1lcyhsZW5ndGhzX3ZlY3RvciwgdW5pcXVlID0gVFJVRSkpCmBgYAoKUnVuIEdTVkEgb24gb3VyIGRhdGFzZXQgd2l0aCB0aGUgc2FtZSBwYXJhbWV0ZXJzIGFzIGJlZm9yZSwgYnV0IG5vdyB3aXRoIHJhbmRvbSBnZW5lIHNldHMuCgpgYGB7ciByYW5kb21fZ3N2YSwgbGl2ZSA9IFRSVUV9CnJhbmRvbV9nc3ZhX3Jlc3VsdHMgPC0gZ3N2YSgKICBnc3ZhUGFyYW0oCiAgICBleHByRGF0YSA9IHJuYXNlcV9tYXQsCiAgICBnZW5lU2V0cyA9IHJhbmRvbV9nZW5lX3NldHMsCiAgICBtaW5TaXplID0gMTUsCiAgICBtYXhTaXplID0gNTAwLAogICAga2NkZiA9ICJHYXVzc2lhbiIsCiAgICBtYXhEaWZmID0gVFJVRQogICksCiAgIyBVc2UgNCBjb3JlcyBmb3IgbXVsdGlwcm9jZXNzaW5nCiAgQlBQQVJBTSA9IEJpb2NQYXJhbGxlbDo6TXVsdGljb3JlUGFyYW0oNCkKKQpgYGAKCk5vdyBsZXQncyBtYWtlIGEgcGxvdCB0byBsb29rIGF0IHRoZSBkaXN0cmlidXRpb24gb2Ygc2NvcmVzIGZyb20gcmFuZG9tIGdlbmUgc2V0cy4KRmlyc3Qgd2UgbmVlZCB0byBnZXQgdGhpcyBkYXRhIGluIGFuIGFwcHJvcHJpYXRlIGZvcm1hdCBmb3IgYGdncGxvdDJgLgoKYGBge3IgbG9uZ2VyX3JhbmRvbV9nc3ZhfQojIFRoZSByYW5kb20gcmVzdWx0cyBhcmUgYSBtYXRyaXgKcmFuZG9tX2xvbmdfZGYgPC0gcmFuZG9tX2dzdmFfcmVzdWx0cyB8PgogIGRhdGEuZnJhbWUoKSB8PgogICMgR2VuZSBzZXQgbmFtZXMgYXJlIHJvd25hbWVzCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oImdlbmVfc2V0IikgfD4KICAjIEdldCBpbnRvIGxvbmcgZm9ybWF0CiAgdGlkeXI6OnBpdm90X2xvbmdlcigKICAgIGNvbHMgPSAtZ2VuZV9zZXQsCiAgICBuYW1lc190byA9ICJLaWRzX0ZpcnN0X0Jpb3NwZWNpbWVuX0lEIiwKICAgIHZhbHVlc190byA9ICJnc3ZhX3Njb3JlIgogICkgfD4KICAjIFJlbW92ZSB0aGUgLnZlcnNpb24gYWRkZWQgYnkgbWFrZS5uYW1lcygpCiAgZHBseXI6Om11dGF0ZShnZW5lX3NldCA9IHN0cmluZ3I6OnN0cl9yZW1vdmUoZ2VuZV9zZXQsICJcXC4uKiIpKSB8PgogICMgQWRkIGEgY29sdW1uIHRoYXQga2VlcHMgdHJhY2sgb2YgdGhlIGdlbmUgc2V0IHNpemUKICBkcGx5cjo6bXV0YXRlKGdlbmVfc2V0X3NpemUgPSBzdHJpbmdyOjp3b3JkKGdlbmVfc2V0LCAyLCBzZXAgPSAiXyIpKSB8PgogICMgV2Ugd2FudCB0byBwbG90IHNtYWxsZXN0IG5vLiBnZW5lcyAtPiBsYXJnZXN0IG5vLiBnZW5lcwogIGRwbHlyOjptdXRhdGUoZ2VuZV9zZXRfc2l6ZSA9IGZhY3RvcihnZW5lX3NldF9zaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKDE1LCAyNSwgNTAsIDEwMCwgMjUwLCA1MDApKSkKCmBgYAoKTGV0J3MgbWFrZSBhIHZpb2xpbiBwbG90IHNvIHdlIGNhbiBsb29rIGF0IHRoZSBkaXN0cmlidXRpb24gb2Ygc2NvcmVzIGJ5IGdlbmUgc2V0IHNpemUuCgpgYGB7ciByYW5kb21fdmlvbGlufQojIFZpb2xpbiBwbG90IGNvbXBhcmluZyBHU1ZBIHNjb3JlcyBvZiBkaWZmZXJlbnQgcmFuZG9tIGdlbmUgc2V0IHNpemVzCnJhbmRvbV9sb25nX2RmIHw+CiAgZ2dwbG90Mjo6Z2dwbG90KGdncGxvdDI6OmFlcyh4ID0gZ2VuZV9zZXRfc2l6ZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBnc3ZhX3Njb3JlKSkgKwogICMgTWFrZSBhIHZpb2xpbiBwbG90IHRoYXQncyBhIHByZXR0eSBibHVlIQogIGdncGxvdDI6Omdlb21fdmlvbGluKGZpbGwgPSAiIzk5Q0NGRiIsIGFscGhhID0gMC41KSArCiAgIyBBZGQgYSBwb2ludCB3aXRoIHRoZSBtZWFuIHZhbHVlCiAgZ2dwbG90Mjo6c3RhdF9zdW1tYXJ5KAogICAgZ2VvbSA9ICJwb2ludCIsCiAgICBmdW4gPSAibWVhbiIsCiAgICAjIENoYW5nZSB0aGUgYWVzdGhldGljcyBvZiB0aGUgcG9pbnRzCiAgICBzaXplID0gMywKICAgIGNvbG9yID0gIiMwMDY2Q0MiLAogICAgc2hhcGUgPSAxOAogICkgKwogICMgRmxpcCB0aGUgYXhlcwogIGdncGxvdDI6OmNvb3JkX2ZsaXAoKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJSYW5kb20gR2VuZSBTZXQgR1NWQSBTY29yZXMiLAogICAgICAgICAgICAgICAgeCA9ICJnZW5lIHNldCBzaXplIiwKICAgICAgICAgICAgICAgIHkgPSAiR1NWQSBzY29yZSIpICsKICBnZ3Bsb3QyOjp0aGVtZV9idygpCmBgYAoKV2hhdCBkbyB5b3Ugbm90aWNlIGFib3V0IHRoZXNlIGRpc3RyaWJ1dGlvbnM/CkhvdyBtaWdodCB5b3UgdXNlIHRoaXMgaW5mb3JtYXRpb24gdG8gaW5mb3JtIHlvdXIgaW50ZXJwcmV0YXRpb24gb2YgR1NWQSBzY29yZXM/CgojIyMgSG93IGNhbiB5b3UgdXNlIHRoZXNlIHNjb3Jlcz8KCklmIHlvdSBkaWQgaGF2ZSBncm91cHMgaW5mb3JtYXRpb24gZm9yIHlvdXIgc2FtcGxlcywgeW91IGNvdWxkIHRlc3QgZm9yIHBhdGh3YXkgc2NvcmVzIGRpZmZlcmVuY2VzIGJldHdlZW4gZ3JvdXBzLgpIZXJlJ3MgW2FuIGV4YW1wbGVdKGh0dHBzOi8vaHRtbHByZXZpZXcuZ2l0aHViLmlvLz9odHRwczovL2dpdGh1Yi5jb20vQWxleHNMZW1vbmFkZS9PcGVuUEJUQS1hbmFseXNpcy9ibG9iLzliNDRiZjFjMTg2YjMxMjZiMTZkYmU1Yjg3NzU2YjNlYWUzZmVlYzIvYW5hbHlzZXMvZ2VuZS1zZXQtZW5yaWNobWVudC1hbmFseXNpcy8wMi1tb2RlbC1nc2VhLm5iLmh0bWwpIG9mIHRoYXQgZnJvbSB0aGUgT3BlblBCVEEgcHJvamVjdCBpdHNlbGYhCgpZb3UgY2FuIGFsc28gdmlzdWFsaXplIHRoaXMgbWF0cml4IGluIGEgaGVhdG1hcC4KSGVyZSdzIGEgZmlndXJlIGZyb20gdGhlIE9wZW5QQlRBIHByb2plY3QsIHdoZXJlIHRoZSBtaWRkbGUgcGFuZWwgaXMgYSBoZWF0bWFwIG9mIEdTVkEgc2NvcmVzIHRoYXQgd2VyZSBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBiZXR3ZWVuIGhpc3RvbG9naWVzLgoKIVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9BbGV4c0xlbW9uYWRlL09wZW5QQlRBLWFuYWx5c2lzLzliNDRiZjFjMTg2YjMxMjZiMTZkYmU1Yjg3NzU2YjNlYWUzZmVlYzIvZmlndXJlcy9wbmdzL3RyYW5zY3JpcHRvbWljLW92ZXJ2aWV3LnBuZykKClsqQ29kZSBoZXJlLipdKGh0dHBzOi8vZ2l0aHViLmNvbS9BbGV4c0xlbW9uYWRlL09wZW5QQlRBLWFuYWx5c2lzL2Jsb2IvOWI0NGJmMWMxODZiMzEyNmIxNmRiZTViODc3NTZiM2VhZTNmZWVjMi9maWd1cmVzL3NjcmlwdHMvdHJhbnNjcmlwdG9taWMtb3ZlcnZpZXcuUiNMMTA2KQoKIyMjIFdyaXRlIHJlc3VsdHMgdG8gZmlsZQoKYGBge3Igd3JpdGVfZ3N2YV9yZXN1bHRzfQpnc3ZhX3Jlc3VsdHMgfD4KICBhcy5kYXRhLmZyYW1lKCkgfD4KICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigicGF0aHdheSIpIHw+CiAgcmVhZHI6OndyaXRlX3RzdihmaWxlID0gZ3N2YV9yZXN1bHRzX2ZpbGUpCmBgYAoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uX2luZm99CnNlc3Npb25JbmZvKCkKYGBgCg==