Load packages

suppressMessages(library(dplyr))
suppressMessages(library(umap))
suppressMessages(library(ggplot2))
suppressMessages(library(devtools))
suppressMessages(library(gdata))

Directories and Files

Directories

# Data directory
data_dir <- file.path("~/Documents/GitHub/jharenza.github.io/openPBTA-notebooks/mb-subtypes/data/")
pbta_data_dir <- file.path("~/OpenPBTA-analysis/data/release-v16-20200320/")
# Create a results directory
results_dir <- "results"
if (!dir.exists(results_dir)) {
  dir.create(results_dir, recursive = TRUE)
}

Input Filepaths

pbta_rnaseq_file <- file.path(pbta_data_dir, "pbta-gene-expression-rsem-fpkm-collapsed.stranded.rds")
pbta_rnaseq_file_full <- file.path(pbta_data_dir, "pbta-gene-expression-rsem-fpkm.stranded.rds")
pbta_hist_file <- file.path(pbta_data_dir, "pbta-histologies.tsv")
pptc_annot_file <- file.path(data_dir, "pptc_annot.tsv")
pptc_hist_file <- file.path(data_dir, "pptc-pdx-clinical-web.txt")
classifier_path_file <- file.path(data_dir, "rathi_results.tsv")

Read in files

# PBTA
pbta_fpkm_collapsed_mat <- readRDS(pbta_rnaseq_file)
pbta_fpkm_full_mat <- readRDS(pbta_rnaseq_file_full)
pbta_histologies_df <- read.delim(pbta_hist_file, sep = "\t", header = T)
# PPTC
load(file.path(data_dir, "2019-02-14-pptc-STAR_cufflinks_hg19_matrix_244.rda"), verbose = T)
Loading objects:
  rna.mat
pptc_subtypes_df <- read.delim(pptc_annot_file, sep = "\t", header = T)
# Rename PPTC df, remove gene name column, subset MB samples
mv(from = "rna.mat", to = "pptc_fpkm_df")
pptc_fpkm_df$gene_short_name <- NULL
pptc_mb_df <- pptc_fpkm_df %>%
  select(pptc_subtypes_df$Sample)
# Michael Taylor (MB classifier training set), rename
load(file.path(data_dir, "mt_rnaseq.RData"), verbose = T)
Loading objects:
  mt_mat
  mt_annot
mv(from = "mt_mat", to = "mt_fpkm_df")
head(mt_fpkm_df)
head(mt_annot)
# MM2S PBTA classifier results, MB classifier results, pathology data
# MM2S Paper: https://scfbm.biomedcentral.com/articles/10.1186/s13029-016-0053-y
pbta_combined_subtypes <- read.delim(classifier_path_file, sep = "\t", header = T)
# How many of each subtype identified by pathology?
table(pbta_combined_subtypes$pathology_subtype)

Group 3 or 4      Group 4      non-WNT          SHH          WNT 
          11            2            1           12            6 

PBTA wrangling

# Only pull out sample identifiers (KidsFirst biospecimen identifiers) that correspond to medulloblastoma samples
medulloblastoma_samples <- pbta_histologies_df %>%
  filter(short_histology == "Medulloblastoma") %>%
  filter(experimental_strategy == "RNA-Seq") %>%
  filter(RNA_library == "stranded") %>%
  pull(Kids_First_Biospecimen_ID)
# Calculate N
length(medulloblastoma_samples)
[1] 121
# Collect subtypes and count
mb_molecular_subtype_df <- pbta_histologies_df %>%
  filter(short_histology == "Medulloblastoma") %>%
  filter(experimental_strategy == "RNA-Seq") %>%
  filter(RNA_library == "stranded") %>%
  select(Kids_First_Biospecimen_ID, molecular_subtype, seq_center)
# Calculate N
nrow(mb_molecular_subtype_df)
[1] 121
# How many per subtype?
table(droplevels(mb_molecular_subtype_df$molecular_subtype))

Group3 Group4    SHH    WNT 
    15     67     28     11 

Create matrices of only MB samples

# select PBTA MB samples only
pbta_mb_coll_df <- pbta_fpkm_collapsed_mat %>% 
  select(medulloblastoma_samples)
pbta_mb_full_df <- pbta_fpkm_full_mat %>% 
  select(medulloblastoma_samples)
# select PPTC MB samples only
pptc_mb_df <- pptc_fpkm_df %>% 
  select(pptc_subtypes_df$Sample)
length(pptc_subtypes_df$Sample)
[1] 18
# How many per subtype?
table(pptc_subtypes_df$class_subtype)

Group3 Group4    SHH    WNT 
     7      2      7      2 
table(pptc_subtypes_df$path_subtype)

Group 3 Group 4     SHH     WNT 
      5       1       6       1 
pptc_subtypes_df
# 12/13 classified correctly - too few samples for clustering
12/13*100
[1] 92.30769
# How many in MT dataset?
length(mt_annot$Sample)
[1] 97
table(mt_annot$Subgroup)

Group3 Group4    SHH    WNT 
    19     25     50      3 
# convert to matrix
pbta_mb_coll_mat <- as.matrix(pbta_mb_coll_df)
pbta_mb_full_mat <- as.matrix(pbta_mb_full_df)
mt_fpkm_mat <- as.matrix(mt_fpkm_df)

Transform matrices

log2 matrix

pbta_coll_log2 <- log2(pbta_mb_coll_mat)
pbta_full_log2 <- log2(pbta_mb_full_mat)
mt_mat_log2 <- log2(mt_fpkm_mat)

Calculate variance for OpenPBTA collapsed data

gene_variance <- matrixStats::rowVars(pbta_coll_log2)
# Find the value that we'll use as a threshold to filter the top 5%
variance_threshold <- quantile(gene_variance, 0.95, na.rm = T)
# Row indices of high variance genes
high_variance_index <- which(gene_variance > variance_threshold)

Set seed for reproducible UMAP results

set.seed(2020)

OpenPBTA stranded RNA-Seq (N = 121) UMAP clustering

Using collapsed matrix

# expects features (genes) to be columns, so we have to use t()
umap_results <- umap::umap(t(pbta_coll_log2[high_variance_index, ]))
# Make a data frame of the layout results and join with molecular subtype 
umap_plot_df <- data.frame(umap_results$layout) %>%
  tibble::rownames_to_column("Kids_First_Biospecimen_ID") %>%
  inner_join(mb_molecular_subtype_df)
Joining, by = "Kids_First_Biospecimen_ID"
Column `Kids_First_Biospecimen_ID` joining character vector and factor, coercing into character vector
# Plot by subtype
umap_plot_df %>%
  ggplot(aes(x = X1, 
             y = X2,
             color = molecular_subtype)) +
  geom_point(size = 3, alpha = 0.7) +
  theme_bw() +
  xlab("UMAP1") +
  ylab("UMAP2")

# Plot by sequencing center - is this having any effect? (Not really)
umap_plot_df %>%
  ggplot(aes(x = X1, 
             y = X2,
             color = seq_center)) +
  geom_point(size = 3, alpha = 0.7) +
  theme_bw() +
  xlab("UMAP1") +
  ylab("UMAP2")

Merge MM2S classifier UMAP

Will use this later

pbta_results_mer <- merge(umap_plot_df, pbta_combined_subtypes, by = "Kids_First_Biospecimen_ID")
head(pbta_results_mer)

Calculate variance for OpenPBTA non-collapsed data

Does this give similar results?

gene_variance <- matrixStats::rowVars(pbta_full_log2)
variance_threshold <- quantile(gene_variance, 0.95, na.rm = T)
high_variance_index <- which(gene_variance > variance_threshold)

OpenPBTA stranded RNA-Seq (N = 121) UMAP clustering - full matrix

umap_results <- umap::umap(t(pbta_full_log2[high_variance_index, ]))
umap_plot_df <- data.frame(umap_results$layout) %>%
  tibble::rownames_to_column("Kids_First_Biospecimen_ID") %>%
  inner_join(mb_molecular_subtype_df)
Joining, by = "Kids_First_Biospecimen_ID"
Column `Kids_First_Biospecimen_ID` joining character vector and factor, coercing into character vector
umap_plot_df %>%
  ggplot(aes(x = X1, 
             y = X2,
             color = molecular_subtype)) +
  geom_point(size = 3, alpha = 0.7) +
  theme_bw() +
  xlab("UMAP1") +
  ylab("UMAP2")

  • Yes, will proceed only with the collapsed matrix.

Exploration

Are the subtypes that don’t classify by unsupervised clustering the same subtypes that did not match pathology subtypes?

pbta_results_mer %>%
  ggplot(aes(x = X1, 
             y = X2,
             color = pathology_subtype,
             shape = mb_classifier_prediction)) +
  geom_point(size = 3, alpha = 0.7) +
  theme_bw() +
  xlab("UMAP1") +
  ylab("UMAP2")

Misclassified samples:

  • For the 3 samples within the WNT cluster not classified as WNT, two were WNT by pathology and the other was not noted by pathology.
  • For 1 sample classified as WNT that clusterd with SHH, pathology was not noted.
  • For the 6 samples within the SHH cluster that were classified as Group 4, 3 were noted as SHH by pathology.
  • 1 was SHH by pathology.
  • Within this cluster, one sample was classified as SHH but pathology noted as WNT.
  • The other 3 samples were not noted by pathology.
  • Two samples classified as WNT present in the Group 3/4 cluster were not noted by pathology.
  • 6 samples were classified as SHH but are in the Group 3/4 cluster. 2 of these were SHH by pathology, and the rest were not noted. # Examine accuracy for this dataset
# Subset for only samples that had pathology results
subtypes_calc <- subset(pbta_combined_subtypes, !is.na(pathology_subtype)) 
# convert to character to perform ifelse on factors with different levels
subtypes_calc$pathology_subtype <- as.character(subtypes_calc$pathology_subtype)
subtypes_calc$mb_classifier_prediction <- as.character(subtypes_calc$mb_classifier_prediction)
# Create column to calculate matches
subtypes_calc$mbclass_v_path <- ifelse(subtypes_calc$pathology_subtype == subtypes_calc$mb_classifier_prediction, "true", 
                                       ifelse(subtypes_calc$pathology_subtype == "Group 3 or 4" & subtypes_calc$mb_classifier_prediction == "Group3", "true", 
                                              ifelse(subtypes_calc$pathology_subtype == "Group 3 or 4" & subtypes_calc$mb_classifier_prediction == "Group4", "true", "false")))
# How many were predicted correctly (true)?
table(subtypes_calc$mbclass_v_path)

false  true 
    6    26 
# Percent of classifications that match MB classifier calls
(26/(6+26))*100
[1] 81.25
# Create column to calculate matches for MM2S classifier
subtypes_calc$MM2S_v_path <- ifelse(subtypes_calc$pathology_subtype == subtypes_calc$MM2S_prediction, "true", 
                                       ifelse(subtypes_calc$pathology_subtype == "Group 3 or 4" & subtypes_calc$MM2S_prediction == "Group3", "true", 
                                              ifelse(subtypes_calc$pathology_subtype == "Group 3 or 4" & subtypes_calc$MM2S_prediction == "Group4", "true", "false")))
table(subtypes_calc$MM2S_v_path)

false  true 
    7    25 
# Percent of classifications that match MM2S classifier calls
(25/(7+25))*100
[1] 78.125

plot MM2S predictions

First, combine matrix with subtypes

gene_variance <- matrixStats::rowVars(pbta_coll_log2)
variance_threshold <- quantile(gene_variance, 0.95, na.rm = T)
high_variance_index <- which(gene_variance > variance_threshold)
umap_results <- umap::umap(t(pbta_coll_log2[high_variance_index, ]))
umap_plot_df <- data.frame(umap_results$layout) %>%
  tibble::rownames_to_column("Kids_First_Biospecimen_ID") %>%
  inner_join(pbta_combined_subtypes)
Joining, by = "Kids_First_Biospecimen_ID"
Column `Kids_First_Biospecimen_ID` joining character vector and factor, coercing into character vector

Does this data look better?

umap_plot_df %>%
  ggplot(aes(x = X1, 
             y = X2,
             color = MM2S_prediction,
             shape = mb_classifier_prediction)) +
  geom_point(size = 3, alpha = 0.7) +
  theme_bw() +
  xlab("UMAP1") +
  ylab("UMAP2")

Calculate variance for Michael Taylor RNA-Seq data

gene_variance <- matrixStats::rowVars(mt_mat_log2)
variance_threshold <- quantile(gene_variance, 0.95, na.rm = T)
high_variance_index <- which(gene_variance > variance_threshold)

Michael Taylor (N = 97) UMAP clustering

umap_results <- umap::umap(t(mt_mat_log2[high_variance_index, ]))
umap_plot_df <- data.frame(umap_results$layout) %>%
  tibble::rownames_to_column("Sample") %>%
  inner_join(mt_annot)
Joining, by = "Sample"
# Plot
umap_plot_df %>%
  ggplot(aes(x = X1, 
             y = X2,
             color = Subgroup)) +
  geom_point(size = 3, alpha = 0.7) +
  theme_bw() +
  xlab("UMAP1") +
  ylab("UMAP2")

Observation:

  • It looks like there are too few WNT samples (N = 3) to enable unsupervised clustering of those samples in this dataset.

Rerun of OpenPBTA stranded RNA-Seq (N = 121) UMAP clustering

  • Turns out the initial FPKM values were not log2 transformed, thus MB subtypes in pbta-histologies.tsv from MB classifier are not accurate.
# Replot UMAP with new subtype labels
gene_variance <- matrixStats::rowVars(pbta_coll_log2)
variance_threshold <- quantile(gene_variance, 0.95, na.rm = T)
high_variance_index <- which(gene_variance > variance_threshold)
umap_results <- umap::umap(t(pbta_coll_log2[high_variance_index, ]))
# Make a data frame of the layout results and join with molecular subtype 
umap_plot_df <- data.frame(umap_results$layout) %>%
  tibble::rownames_to_column("Kids_First_Biospecimen_ID") %>%
  inner_join(pbta_combined_subtypes)
Joining, by = "Kids_First_Biospecimen_ID"
Column `Kids_First_Biospecimen_ID` joining character vector and factor, coercing into character vector
# Plot by subtype
umap_plot_df %>%
  ggplot(aes(x = X1, 
             y = X2,
             color = mb_classifier_prediction)) +
  geom_point(size = 3, alpha = 0.7) +
  theme_bw() +
  xlab("UMAP1") +
  ylab("UMAP2")

  • This looks A LOT better!

session info

session_info()
─ Session info ───────────────────────────────────────────────────────────────────────────────────────────────────────

─ Packages ───────────────────────────────────────────────────────────────────────────────────────────────────────────
 package     * version date       lib source        
 askpass       1.1     2019-01-13 [1] CRAN (R 3.6.0)
 assertthat    0.2.1   2019-03-21 [1] CRAN (R 3.6.0)
 backports     1.1.5   2019-10-02 [1] CRAN (R 3.6.0)
 base64enc     0.1-3   2015-07-28 [1] CRAN (R 3.6.0)
 callr         3.3.2   2019-09-22 [1] CRAN (R 3.6.0)
 cli           1.1.0   2019-03-19 [1] CRAN (R 3.6.0)
 colorspace    1.4-1   2019-03-18 [1] CRAN (R 3.6.0)
 crayon        1.3.4   2017-09-16 [1] CRAN (R 3.6.0)
 desc          1.2.0   2018-05-01 [1] CRAN (R 3.6.0)
 devtools    * 2.2.1   2019-09-24 [1] CRAN (R 3.6.0)
 digest        0.6.22  2019-10-21 [1] CRAN (R 3.6.1)
 dplyr       * 0.8.3   2019-07-04 [1] CRAN (R 3.6.0)
 ellipsis      0.3.0   2019-09-20 [1] CRAN (R 3.6.0)
 evaluate      0.14    2019-05-28 [1] CRAN (R 3.6.0)
 fs            1.3.1   2019-05-06 [1] CRAN (R 3.6.0)
 gdata       * 2.18.0  2017-06-06 [1] CRAN (R 3.6.0)
 ggplot2     * 3.2.1   2019-08-10 [1] CRAN (R 3.6.0)
 glue          1.3.1   2019-03-12 [1] CRAN (R 3.6.0)
 gtable        0.3.0   2019-03-25 [1] CRAN (R 3.6.0)
 gtools        3.8.1   2018-06-26 [1] CRAN (R 3.6.0)
 htmltools     0.4.0   2019-10-04 [1] CRAN (R 3.6.0)
 jsonlite      1.6     2018-12-07 [1] CRAN (R 3.6.0)
 knitr         1.25    2019-09-18 [1] CRAN (R 3.6.0)
 labeling      0.3     2014-08-23 [1] CRAN (R 3.6.0)
 lattice       0.20-38 2018-11-04 [1] CRAN (R 3.6.1)
 lazyeval      0.2.2   2019-03-15 [1] CRAN (R 3.6.0)
 lifecycle     0.2.0   2020-03-06 [1] CRAN (R 3.6.0)
 magrittr      1.5     2014-11-22 [1] CRAN (R 3.6.0)
 Matrix        1.2-17  2019-03-22 [1] CRAN (R 3.6.1)
 matrixStats   0.55.0  2019-09-07 [1] CRAN (R 3.6.0)
 memoise       1.1.0   2017-04-21 [1] CRAN (R 3.6.0)
 munsell       0.5.0   2018-06-12 [1] CRAN (R 3.6.0)
 openssl       1.4.1   2019-07-18 [1] CRAN (R 3.6.0)
 pillar        1.4.6   2020-07-10 [1] CRAN (R 3.6.2)
 pkgbuild      1.0.6   2019-10-09 [1] CRAN (R 3.6.0)
 pkgconfig     2.0.3   2019-09-22 [1] CRAN (R 3.6.0)
 pkgload       1.0.2   2018-10-29 [1] CRAN (R 3.6.0)
 prettyunits   1.0.2   2015-07-13 [1] CRAN (R 3.6.0)
 processx      3.4.1   2019-07-18 [1] CRAN (R 3.6.0)
 ps            1.3.0   2018-12-21 [1] CRAN (R 3.6.0)
 purrr         0.3.3   2019-10-18 [1] CRAN (R 3.6.0)
 R6            2.4.0   2019-02-14 [1] CRAN (R 3.6.0)
 Rcpp          1.0.5   2020-07-06 [1] CRAN (R 3.6.2)
 remotes       2.1.0   2019-06-24 [1] CRAN (R 3.6.0)
 reticulate    1.16    2020-05-27 [1] CRAN (R 3.6.2)
 rlang         0.4.7   2020-07-09 [1] CRAN (R 3.6.2)
 rmarkdown     1.16    2019-10-01 [1] CRAN (R 3.6.0)
 rprojroot     1.3-2   2018-01-03 [1] CRAN (R 3.6.0)
 RSpectra      0.16-0  2019-12-01 [1] CRAN (R 3.6.0)
 rstudioapi    0.10    2019-03-19 [1] CRAN (R 3.6.0)
 scales        1.0.0   2018-08-09 [1] CRAN (R 3.6.0)
 sessioninfo   1.1.1   2018-11-05 [1] CRAN (R 3.6.0)
 stringi       1.4.3   2019-03-12 [1] CRAN (R 3.6.0)
 stringr       1.4.0   2019-02-10 [1] CRAN (R 3.6.0)
 testthat      2.2.1   2019-07-25 [1] CRAN (R 3.6.0)
 tibble        3.0.3   2020-07-10 [1] CRAN (R 3.6.2)
 tidyselect    1.1.0   2020-05-11 [1] CRAN (R 3.6.2)
 umap        * 0.2.6.0 2020-06-16 [1] CRAN (R 3.6.2)
 usethis     * 1.5.1   2019-07-04 [1] CRAN (R 3.6.0)
 vctrs         0.3.2   2020-07-15 [1] CRAN (R 3.6.2)
 withr         2.1.2   2018-03-15 [1] CRAN (R 3.6.0)
 xfun          0.10    2019-10-01 [1] CRAN (R 3.6.0)
 yaml          2.2.0   2018-07-25 [1] CRAN (R 3.6.0)

[1] /Library/Frameworks/R.framework/Versions/3.6/Resources/library
LS0tCnRpdGxlOiAiRXhwbG9yZSBtZWR1bGxvYmxhc3RvbWEgc3VidHlwZSBjYWxscyBpbiB0aGUgY29udGV4dCBvZiB1bnN1cGVydmlzZWQgY2x1c3RlcmluZyIKb3V0cHV0OiBodG1sX25vdGVib29rICAgIAp0b2M6IFRSVUUKdG9jX2Zsb2F0OiBUUlVFCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQpgYGB7ciBnbG9iYWxfb3B0aW9ucywgaW5jbHVkZT1GfQprbml0cjo6b3B0c19jaHVuayRzZXQoZXJyb3IgPSBGLCBlY2hvID0gVCwgd2FybmluZyA9IFQsIG1lc3NhZ2UgPSBUKQpgYGAKCgojIExvYWQgcGFja2FnZXMKYGBge3J9CnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShkcGx5cikpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeSh1bWFwKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGdncGxvdDIpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoZGV2dG9vbHMpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoZ2RhdGEpKQpgYGAKCiMgRGlyZWN0b3JpZXMgYW5kIEZpbGVzCiMjIERpcmVjdG9yaWVzCmBgYHtyfQojIERhdGEgZGlyZWN0b3J5CmRhdGFfZGlyIDwtIGZpbGUucGF0aCgifi9Eb2N1bWVudHMvR2l0SHViL2poYXJlbnphLmdpdGh1Yi5pby9vcGVuUEJUQS1ub3RlYm9va3MvbWItc3VidHlwZXMvZGF0YS8iKQpwYnRhX2RhdGFfZGlyIDwtIGZpbGUucGF0aCgifi9PcGVuUEJUQS1hbmFseXNpcy9kYXRhL3JlbGVhc2UtdjE2LTIwMjAwMzIwLyIpCiMgQ3JlYXRlIGEgcmVzdWx0cyBkaXJlY3RvcnkKcmVzdWx0c19kaXIgPC0gInJlc3VsdHMiCmlmICghZGlyLmV4aXN0cyhyZXN1bHRzX2RpcikpIHsKICBkaXIuY3JlYXRlKHJlc3VsdHNfZGlyLCByZWN1cnNpdmUgPSBUUlVFKQp9CmBgYAoKIyMgSW5wdXQgRmlsZXBhdGhzCmBgYHtyfQpwYnRhX3JuYXNlcV9maWxlIDwtIGZpbGUucGF0aChwYnRhX2RhdGFfZGlyLCAicGJ0YS1nZW5lLWV4cHJlc3Npb24tcnNlbS1mcGttLWNvbGxhcHNlZC5zdHJhbmRlZC5yZHMiKQpwYnRhX3JuYXNlcV9maWxlX2Z1bGwgPC0gZmlsZS5wYXRoKHBidGFfZGF0YV9kaXIsICJwYnRhLWdlbmUtZXhwcmVzc2lvbi1yc2VtLWZwa20uc3RyYW5kZWQucmRzIikKcGJ0YV9oaXN0X2ZpbGUgPC0gZmlsZS5wYXRoKHBidGFfZGF0YV9kaXIsICJwYnRhLWhpc3RvbG9naWVzLnRzdiIpCnBwdGNfYW5ub3RfZmlsZSA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJwcHRjX2Fubm90LnRzdiIpCnBwdGNfaGlzdF9maWxlIDwtIGZpbGUucGF0aChkYXRhX2RpciwgInBwdGMtcGR4LWNsaW5pY2FsLXdlYi50eHQiKQpjbGFzc2lmaWVyX3BhdGhfZmlsZSA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJyYXRoaV9yZXN1bHRzLnRzdiIpCmBgYAojIyBSZWFkIGluIGZpbGVzCmBgYHtyfQojIFBCVEEKcGJ0YV9mcGttX2NvbGxhcHNlZF9tYXQgPC0gcmVhZFJEUyhwYnRhX3JuYXNlcV9maWxlKQpwYnRhX2Zwa21fZnVsbF9tYXQgPC0gcmVhZFJEUyhwYnRhX3JuYXNlcV9maWxlX2Z1bGwpCnBidGFfaGlzdG9sb2dpZXNfZGYgPC0gcmVhZC5kZWxpbShwYnRhX2hpc3RfZmlsZSwgc2VwID0gIlx0IiwgaGVhZGVyID0gVCkKIyBQUFRDCmxvYWQoZmlsZS5wYXRoKGRhdGFfZGlyLCAiMjAxOS0wMi0xNC1wcHRjLVNUQVJfY3VmZmxpbmtzX2hnMTlfbWF0cml4XzI0NC5yZGEiKSwgdmVyYm9zZSA9IFQpCnBwdGNfc3VidHlwZXNfZGYgPC0gcmVhZC5kZWxpbShwcHRjX2Fubm90X2ZpbGUsIHNlcCA9ICJcdCIsIGhlYWRlciA9IFQpCiMgUmVuYW1lIFBQVEMgZGYsIHJlbW92ZSBnZW5lIG5hbWUgY29sdW1uLCBzdWJzZXQgTUIgc2FtcGxlcwptdihmcm9tID0gInJuYS5tYXQiLCB0byA9ICJwcHRjX2Zwa21fZGYiKQpwcHRjX2Zwa21fZGYkZ2VuZV9zaG9ydF9uYW1lIDwtIE5VTEwKcHB0Y19tYl9kZiA8LSBwcHRjX2Zwa21fZGYgJT4lCiAgc2VsZWN0KHBwdGNfc3VidHlwZXNfZGYkU2FtcGxlKQoKIyBNaWNoYWVsIFRheWxvciAoTUIgY2xhc3NpZmllciB0cmFpbmluZyBzZXQpLCByZW5hbWUKbG9hZChmaWxlLnBhdGgoZGF0YV9kaXIsICJtdF9ybmFzZXEuUkRhdGEiKSwgdmVyYm9zZSA9IFQpCm12KGZyb20gPSAibXRfbWF0IiwgdG8gPSAibXRfZnBrbV9kZiIpCmhlYWQobXRfZnBrbV9kZikKaGVhZChtdF9hbm5vdCkKCiMgTU0yUyBQQlRBIGNsYXNzaWZpZXIgcmVzdWx0cywgTUIgY2xhc3NpZmllciByZXN1bHRzLCBwYXRob2xvZ3kgZGF0YQojIE1NMlMgUGFwZXI6IGh0dHBzOi8vc2NmYm0uYmlvbWVkY2VudHJhbC5jb20vYXJ0aWNsZXMvMTAuMTE4Ni9zMTMwMjktMDE2LTAwNTMteQpwYnRhX2NvbWJpbmVkX3N1YnR5cGVzIDwtIHJlYWQuZGVsaW0oY2xhc3NpZmllcl9wYXRoX2ZpbGUsIHNlcCA9ICJcdCIsIGhlYWRlciA9IFQpCiMgSG93IG1hbnkgb2YgZWFjaCBzdWJ0eXBlIGlkZW50aWZpZWQgYnkgcGF0aG9sb2d5Pwp0YWJsZShwYnRhX2NvbWJpbmVkX3N1YnR5cGVzJHBhdGhvbG9neV9zdWJ0eXBlKQpgYGAKCiMgUEJUQSB3cmFuZ2xpbmcKYGBge3J9CiMgT25seSBwdWxsIG91dCBzYW1wbGUgaWRlbnRpZmllcnMgKEtpZHNGaXJzdCBiaW9zcGVjaW1lbiBpZGVudGlmaWVycykgdGhhdCBjb3JyZXNwb25kIHRvIG1lZHVsbG9ibGFzdG9tYSBzYW1wbGVzCm1lZHVsbG9ibGFzdG9tYV9zYW1wbGVzIDwtIHBidGFfaGlzdG9sb2dpZXNfZGYgJT4lCiAgZmlsdGVyKHNob3J0X2hpc3RvbG9neSA9PSAiTWVkdWxsb2JsYXN0b21hIikgJT4lCiAgZmlsdGVyKGV4cGVyaW1lbnRhbF9zdHJhdGVneSA9PSAiUk5BLVNlcSIpICU+JQogIGZpbHRlcihSTkFfbGlicmFyeSA9PSAic3RyYW5kZWQiKSAlPiUKICBwdWxsKEtpZHNfRmlyc3RfQmlvc3BlY2ltZW5fSUQpCiMgQ2FsY3VsYXRlIE4KbGVuZ3RoKG1lZHVsbG9ibGFzdG9tYV9zYW1wbGVzKQojIENvbGxlY3Qgc3VidHlwZXMgYW5kIGNvdW50Cm1iX21vbGVjdWxhcl9zdWJ0eXBlX2RmIDwtIHBidGFfaGlzdG9sb2dpZXNfZGYgJT4lCiAgZmlsdGVyKHNob3J0X2hpc3RvbG9neSA9PSAiTWVkdWxsb2JsYXN0b21hIikgJT4lCiAgZmlsdGVyKGV4cGVyaW1lbnRhbF9zdHJhdGVneSA9PSAiUk5BLVNlcSIpICU+JQogIGZpbHRlcihSTkFfbGlicmFyeSA9PSAic3RyYW5kZWQiKSAlPiUKICBzZWxlY3QoS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCwgbW9sZWN1bGFyX3N1YnR5cGUsIHNlcV9jZW50ZXIpCiMgQ2FsY3VsYXRlIE4KbnJvdyhtYl9tb2xlY3VsYXJfc3VidHlwZV9kZikKIyBIb3cgbWFueSBwZXIgc3VidHlwZT8KdGFibGUoZHJvcGxldmVscyhtYl9tb2xlY3VsYXJfc3VidHlwZV9kZiRtb2xlY3VsYXJfc3VidHlwZSkpCmBgYAojIENyZWF0ZSBtYXRyaWNlcyBvZiBvbmx5IE1CIHNhbXBsZXMKYGBge3J9CiMgc2VsZWN0IFBCVEEgTUIgc2FtcGxlcyBvbmx5CnBidGFfbWJfY29sbF9kZiA8LSBwYnRhX2Zwa21fY29sbGFwc2VkX21hdCAlPiUgCiAgc2VsZWN0KG1lZHVsbG9ibGFzdG9tYV9zYW1wbGVzKQpwYnRhX21iX2Z1bGxfZGYgPC0gcGJ0YV9mcGttX2Z1bGxfbWF0ICU+JSAKICBzZWxlY3QobWVkdWxsb2JsYXN0b21hX3NhbXBsZXMpCiMgc2VsZWN0IFBQVEMgTUIgc2FtcGxlcyBvbmx5CnBwdGNfbWJfZGYgPC0gcHB0Y19mcGttX2RmICU+JSAKICBzZWxlY3QocHB0Y19zdWJ0eXBlc19kZiRTYW1wbGUpCmxlbmd0aChwcHRjX3N1YnR5cGVzX2RmJFNhbXBsZSkKIyBIb3cgbWFueSBwZXIgc3VidHlwZT8KdGFibGUocHB0Y19zdWJ0eXBlc19kZiRjbGFzc19zdWJ0eXBlKQp0YWJsZShwcHRjX3N1YnR5cGVzX2RmJHBhdGhfc3VidHlwZSkKcHB0Y19zdWJ0eXBlc19kZgojIDEyLzEzIGNsYXNzaWZpZWQgY29ycmVjdGx5IC0gdG9vIGZldyBzYW1wbGVzIGZvciBjbHVzdGVyaW5nCjEyLzEzKjEwMAoKIyBIb3cgbWFueSBpbiBNVCBkYXRhc2V0PwpsZW5ndGgobXRfYW5ub3QkU2FtcGxlKQp0YWJsZShtdF9hbm5vdCRTdWJncm91cCkKCiMgY29udmVydCB0byBtYXRyaXgKcGJ0YV9tYl9jb2xsX21hdCA8LSBhcy5tYXRyaXgocGJ0YV9tYl9jb2xsX2RmKQpwYnRhX21iX2Z1bGxfbWF0IDwtIGFzLm1hdHJpeChwYnRhX21iX2Z1bGxfZGYpCm10X2Zwa21fbWF0IDwtIGFzLm1hdHJpeChtdF9mcGttX2RmKQpgYGAKIyBUcmFuc2Zvcm0gbWF0cmljZXMKIyMgbG9nMiBtYXRyaXgKYGBge3J9CnBidGFfY29sbF9sb2cyIDwtIGxvZzIocGJ0YV9tYl9jb2xsX21hdCkKcGJ0YV9mdWxsX2xvZzIgPC0gbG9nMihwYnRhX21iX2Z1bGxfbWF0KQptdF9tYXRfbG9nMiA8LSBsb2cyKG10X2Zwa21fbWF0KQpgYGAKIyBDYWxjdWxhdGUgdmFyaWFuY2UgZm9yIE9wZW5QQlRBIGNvbGxhcHNlZCBkYXRhCmBgYHtyfQpnZW5lX3ZhcmlhbmNlIDwtIG1hdHJpeFN0YXRzOjpyb3dWYXJzKHBidGFfY29sbF9sb2cyKQojIEZpbmQgdGhlIHZhbHVlIHRoYXQgd2UnbGwgdXNlIGFzIGEgdGhyZXNob2xkIHRvIGZpbHRlciB0aGUgdG9wIDUlCnZhcmlhbmNlX3RocmVzaG9sZCA8LSBxdWFudGlsZShnZW5lX3ZhcmlhbmNlLCAwLjk1LCBuYS5ybSA9IFQpCiMgUm93IGluZGljZXMgb2YgaGlnaCB2YXJpYW5jZSBnZW5lcwpoaWdoX3ZhcmlhbmNlX2luZGV4IDwtIHdoaWNoKGdlbmVfdmFyaWFuY2UgPiB2YXJpYW5jZV90aHJlc2hvbGQpCmBgYAojIFNldCBzZWVkIGZvciByZXByb2R1Y2libGUgVU1BUCByZXN1bHRzCmBgYHtyfQpzZXQuc2VlZCgyMDIwKQpgYGAKCiMgT3BlblBCVEEgc3RyYW5kZWQgUk5BLVNlcSAoTiA9IDEyMSkgVU1BUCBjbHVzdGVyaW5nCiMjIFVzaW5nIGNvbGxhcHNlZCBtYXRyaXgKYGBge3J9CiMgZXhwZWN0cyBmZWF0dXJlcyAoZ2VuZXMpIHRvIGJlIGNvbHVtbnMsIHNvIHdlIGhhdmUgdG8gdXNlIHQoKQp1bWFwX3Jlc3VsdHMgPC0gdW1hcDo6dW1hcCh0KHBidGFfY29sbF9sb2cyW2hpZ2hfdmFyaWFuY2VfaW5kZXgsIF0pKQojIE1ha2UgYSBkYXRhIGZyYW1lIG9mIHRoZSBsYXlvdXQgcmVzdWx0cyBhbmQgam9pbiB3aXRoIG1vbGVjdWxhciBzdWJ0eXBlIAp1bWFwX3Bsb3RfZGYgPC0gZGF0YS5mcmFtZSh1bWFwX3Jlc3VsdHMkbGF5b3V0KSAlPiUKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCIpICU+JQogIGlubmVyX2pvaW4obWJfbW9sZWN1bGFyX3N1YnR5cGVfZGYpCiMgUGxvdCBieSBzdWJ0eXBlCnVtYXBfcGxvdF9kZiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBYMSwgCiAgICAgICAgICAgICB5ID0gWDIsCiAgICAgICAgICAgICBjb2xvciA9IG1vbGVjdWxhcl9zdWJ0eXBlKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDMsIGFscGhhID0gMC43KSArCiAgdGhlbWVfYncoKSArCiAgeGxhYigiVU1BUDEiKSArCiAgeWxhYigiVU1BUDIiKQpgYGAKYGBge3J9CiMgUGxvdCBieSBzZXF1ZW5jaW5nIGNlbnRlciAtIGlzIHRoaXMgaGF2aW5nIGFueSBlZmZlY3Q/IChOb3QgcmVhbGx5KQp1bWFwX3Bsb3RfZGYgJT4lCiAgZ2dwbG90KGFlcyh4ID0gWDEsIAogICAgICAgICAgICAgeSA9IFgyLAogICAgICAgICAgICAgY29sb3IgPSBzZXFfY2VudGVyKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDMsIGFscGhhID0gMC43KSArCiAgdGhlbWVfYncoKSArCiAgeGxhYigiVU1BUDEiKSArCiAgeWxhYigiVU1BUDIiKQpgYGAKIyBNZXJnZSBNTTJTIGNsYXNzaWZpZXIgVU1BUAojIyBXaWxsIHVzZSB0aGlzIGxhdGVyCmBgYHtyfQpwYnRhX3Jlc3VsdHNfbWVyIDwtIG1lcmdlKHVtYXBfcGxvdF9kZiwgcGJ0YV9jb21iaW5lZF9zdWJ0eXBlcywgYnkgPSAiS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCIpCmhlYWQocGJ0YV9yZXN1bHRzX21lcikKYGBgCiMgQ2FsY3VsYXRlIHZhcmlhbmNlIGZvciBPcGVuUEJUQSBub24tY29sbGFwc2VkIGRhdGEKIyMgRG9lcyB0aGlzIGdpdmUgc2ltaWxhciByZXN1bHRzPwoKYGBge3J9CmdlbmVfdmFyaWFuY2UgPC0gbWF0cml4U3RhdHM6OnJvd1ZhcnMocGJ0YV9mdWxsX2xvZzIpCnZhcmlhbmNlX3RocmVzaG9sZCA8LSBxdWFudGlsZShnZW5lX3ZhcmlhbmNlLCAwLjk1LCBuYS5ybSA9IFQpCmhpZ2hfdmFyaWFuY2VfaW5kZXggPC0gd2hpY2goZ2VuZV92YXJpYW5jZSA+IHZhcmlhbmNlX3RocmVzaG9sZCkKYGBgCiMgT3BlblBCVEEgc3RyYW5kZWQgUk5BLVNlcSAoTiA9IDEyMSkgVU1BUCBjbHVzdGVyaW5nIC0gZnVsbCBtYXRyaXgKCmBgYHtyfQp1bWFwX3Jlc3VsdHMgPC0gdW1hcDo6dW1hcCh0KHBidGFfZnVsbF9sb2cyW2hpZ2hfdmFyaWFuY2VfaW5kZXgsIF0pKQp1bWFwX3Bsb3RfZGYgPC0gZGF0YS5mcmFtZSh1bWFwX3Jlc3VsdHMkbGF5b3V0KSAlPiUKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCIpICU+JQogIGlubmVyX2pvaW4obWJfbW9sZWN1bGFyX3N1YnR5cGVfZGYpCnVtYXBfcGxvdF9kZiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBYMSwgCiAgICAgICAgICAgICB5ID0gWDIsCiAgICAgICAgICAgICBjb2xvciA9IG1vbGVjdWxhcl9zdWJ0eXBlKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDMsIGFscGhhID0gMC43KSArCiAgdGhlbWVfYncoKSArCiAgeGxhYigiVU1BUDEiKSArCiAgeWxhYigiVU1BUDIiKQpgYGAKLSBZZXMsIHdpbGwgcHJvY2VlZCBvbmx5IHdpdGggdGhlIGNvbGxhcHNlZCBtYXRyaXguCgojIEV4cGxvcmF0aW9uCiMjIEFyZSB0aGUgc3VidHlwZXMgdGhhdCBkb24ndCBjbGFzc2lmeSBieSB1bnN1cGVydmlzZWQgY2x1c3RlcmluZyB0aGUgc2FtZSBzdWJ0eXBlcyB0aGF0IGRpZCBub3QgbWF0Y2ggcGF0aG9sb2d5IHN1YnR5cGVzPwpgYGB7cn0KcGJ0YV9yZXN1bHRzX21lciAlPiUKICBnZ3Bsb3QoYWVzKHggPSBYMSwgCiAgICAgICAgICAgICB5ID0gWDIsCiAgICAgICAgICAgICBjb2xvciA9IHBhdGhvbG9neV9zdWJ0eXBlLAogICAgICAgICAgICAgc2hhcGUgPSBtYl9jbGFzc2lmaWVyX3ByZWRpY3Rpb24pKSArCiAgZ2VvbV9wb2ludChzaXplID0gMywgYWxwaGEgPSAwLjcpICsKICB0aGVtZV9idygpICsKICB4bGFiKCJVTUFQMSIpICsKICB5bGFiKCJVTUFQMiIpCmBgYAojIyBNaXNjbGFzc2lmaWVkIHNhbXBsZXM6Ci0gRm9yIHRoZSAzIHNhbXBsZXMgd2l0aGluIHRoZSBXTlQgY2x1c3RlciBub3QgY2xhc3NpZmllZCBhcyBXTlQsIHR3byB3ZXJlIFdOVCBieSBwYXRob2xvZ3kgYW5kIHRoZSBvdGhlciB3YXMgbm90IG5vdGVkIGJ5IHBhdGhvbG9neS4gCi0gRm9yIDEgc2FtcGxlIGNsYXNzaWZpZWQgYXMgV05UIHRoYXQgY2x1c3RlcmQgd2l0aCBTSEgsIHBhdGhvbG9neSB3YXMgbm90IG5vdGVkLiAKLSBGb3IgdGhlIDYgc2FtcGxlcyB3aXRoaW4gdGhlIFNISCBjbHVzdGVyIHRoYXQgd2VyZSBjbGFzc2lmaWVkIGFzIEdyb3VwIDQsIDMgd2VyZSBub3RlZCBhcyBTSEggYnkgcGF0aG9sb2d5LiAKLSAxIHdhcyBTSEggYnkgcGF0aG9sb2d5LiAKLSBXaXRoaW4gdGhpcyBjbHVzdGVyLCBvbmUgc2FtcGxlIHdhcyBjbGFzc2lmaWVkIGFzIFNISCBidXQgcGF0aG9sb2d5IG5vdGVkIGFzIFdOVC4gCi0gVGhlIG90aGVyIDMgc2FtcGxlcyB3ZXJlIG5vdCBub3RlZCBieSBwYXRob2xvZ3kuIAotIFR3byBzYW1wbGVzIGNsYXNzaWZpZWQgYXMgV05UIHByZXNlbnQgaW4gdGhlIEdyb3VwIDMvNCBjbHVzdGVyIHdlcmUgbm90IG5vdGVkIGJ5IHBhdGhvbG9neS4gCi0gNiBzYW1wbGVzIHdlcmUgY2xhc3NpZmllZCBhcyBTSEggYnV0IGFyZSBpbiB0aGUgR3JvdXAgMy80IGNsdXN0ZXIuIDIgb2YgdGhlc2Ugd2VyZSBTSEggYnkgcGF0aG9sb2d5LCBhbmQgdGhlIHJlc3Qgd2VyZSBub3Qgbm90ZWQuCiMgRXhhbWluZSBhY2N1cmFjeSBmb3IgdGhpcyBkYXRhc2V0CmBgYHtyfQojIFN1YnNldCBmb3Igb25seSBzYW1wbGVzIHRoYXQgaGFkIHBhdGhvbG9neSByZXN1bHRzCnN1YnR5cGVzX2NhbGMgPC0gc3Vic2V0KHBidGFfY29tYmluZWRfc3VidHlwZXMsICFpcy5uYShwYXRob2xvZ3lfc3VidHlwZSkpIAojIGNvbnZlcnQgdG8gY2hhcmFjdGVyIHRvIHBlcmZvcm0gaWZlbHNlIG9uIGZhY3RvcnMgd2l0aCBkaWZmZXJlbnQgbGV2ZWxzCnN1YnR5cGVzX2NhbGMkcGF0aG9sb2d5X3N1YnR5cGUgPC0gYXMuY2hhcmFjdGVyKHN1YnR5cGVzX2NhbGMkcGF0aG9sb2d5X3N1YnR5cGUpCnN1YnR5cGVzX2NhbGMkbWJfY2xhc3NpZmllcl9wcmVkaWN0aW9uIDwtIGFzLmNoYXJhY3RlcihzdWJ0eXBlc19jYWxjJG1iX2NsYXNzaWZpZXJfcHJlZGljdGlvbikKIyBDcmVhdGUgY29sdW1uIHRvIGNhbGN1bGF0ZSBtYXRjaGVzCnN1YnR5cGVzX2NhbGMkbWJjbGFzc192X3BhdGggPC0gaWZlbHNlKHN1YnR5cGVzX2NhbGMkcGF0aG9sb2d5X3N1YnR5cGUgPT0gc3VidHlwZXNfY2FsYyRtYl9jbGFzc2lmaWVyX3ByZWRpY3Rpb24sICJ0cnVlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShzdWJ0eXBlc19jYWxjJHBhdGhvbG9neV9zdWJ0eXBlID09ICJHcm91cCAzIG9yIDQiICYgc3VidHlwZXNfY2FsYyRtYl9jbGFzc2lmaWVyX3ByZWRpY3Rpb24gPT0gIkdyb3VwMyIsICJ0cnVlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2Uoc3VidHlwZXNfY2FsYyRwYXRob2xvZ3lfc3VidHlwZSA9PSAiR3JvdXAgMyBvciA0IiAmIHN1YnR5cGVzX2NhbGMkbWJfY2xhc3NpZmllcl9wcmVkaWN0aW9uID09ICJHcm91cDQiLCAidHJ1ZSIsICJmYWxzZSIpKSkKIyBIb3cgbWFueSB3ZXJlIHByZWRpY3RlZCBjb3JyZWN0bHkgKHRydWUpPwp0YWJsZShzdWJ0eXBlc19jYWxjJG1iY2xhc3Nfdl9wYXRoKQojIFBlcmNlbnQgb2YgY2xhc3NpZmljYXRpb25zIHRoYXQgbWF0Y2ggTUIgY2xhc3NpZmllciBjYWxscwooMjYvKDYrMjYpKSoxMDAKIyBDcmVhdGUgY29sdW1uIHRvIGNhbGN1bGF0ZSBtYXRjaGVzIGZvciBNTTJTIGNsYXNzaWZpZXIKc3VidHlwZXNfY2FsYyRNTTJTX3ZfcGF0aCA8LSBpZmVsc2Uoc3VidHlwZXNfY2FsYyRwYXRob2xvZ3lfc3VidHlwZSA9PSBzdWJ0eXBlc19jYWxjJE1NMlNfcHJlZGljdGlvbiwgInRydWUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHN1YnR5cGVzX2NhbGMkcGF0aG9sb2d5X3N1YnR5cGUgPT0gIkdyb3VwIDMgb3IgNCIgJiBzdWJ0eXBlc19jYWxjJE1NMlNfcHJlZGljdGlvbiA9PSAiR3JvdXAzIiwgInRydWUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShzdWJ0eXBlc19jYWxjJHBhdGhvbG9neV9zdWJ0eXBlID09ICJHcm91cCAzIG9yIDQiICYgc3VidHlwZXNfY2FsYyRNTTJTX3ByZWRpY3Rpb24gPT0gIkdyb3VwNCIsICJ0cnVlIiwgImZhbHNlIikpKQp0YWJsZShzdWJ0eXBlc19jYWxjJE1NMlNfdl9wYXRoKQojIFBlcmNlbnQgb2YgY2xhc3NpZmljYXRpb25zIHRoYXQgbWF0Y2ggTU0yUyBjbGFzc2lmaWVyIGNhbGxzCigyNS8oNysyNSkpKjEwMApgYGAKCiMgcGxvdCBNTTJTIHByZWRpY3Rpb25zCiMjIEZpcnN0LCBjb21iaW5lIG1hdHJpeCB3aXRoIHN1YnR5cGVzCgpgYGB7cn0KZ2VuZV92YXJpYW5jZSA8LSBtYXRyaXhTdGF0czo6cm93VmFycyhwYnRhX2NvbGxfbG9nMikKdmFyaWFuY2VfdGhyZXNob2xkIDwtIHF1YW50aWxlKGdlbmVfdmFyaWFuY2UsIDAuOTUsIG5hLnJtID0gVCkKaGlnaF92YXJpYW5jZV9pbmRleCA8LSB3aGljaChnZW5lX3ZhcmlhbmNlID4gdmFyaWFuY2VfdGhyZXNob2xkKQp1bWFwX3Jlc3VsdHMgPC0gdW1hcDo6dW1hcCh0KHBidGFfY29sbF9sb2cyW2hpZ2hfdmFyaWFuY2VfaW5kZXgsIF0pKQp1bWFwX3Bsb3RfZGYgPC0gZGF0YS5mcmFtZSh1bWFwX3Jlc3VsdHMkbGF5b3V0KSAlPiUKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiS2lkc19GaXJzdF9CaW9zcGVjaW1lbl9JRCIpICU+JQogIGlubmVyX2pvaW4ocGJ0YV9jb21iaW5lZF9zdWJ0eXBlcykKYGBgICAKIyMgRG9lcyB0aGlzIGRhdGEgbG9vayBiZXR0ZXI/CmBgYHtyfQp1bWFwX3Bsb3RfZGYgJT4lCiAgZ2dwbG90KGFlcyh4ID0gWDEsIAogICAgICAgICAgICAgeSA9IFgyLAogICAgICAgICAgICAgY29sb3IgPSBNTTJTX3ByZWRpY3Rpb24sCiAgICAgICAgICAgICBzaGFwZSA9IG1iX2NsYXNzaWZpZXJfcHJlZGljdGlvbikpICsKICBnZW9tX3BvaW50KHNpemUgPSAzLCBhbHBoYSA9IDAuNykgKwogIHRoZW1lX2J3KCkgKwogIHhsYWIoIlVNQVAxIikgKwogIHlsYWIoIlVNQVAyIikKYGBgCgoKIyBDYWxjdWxhdGUgdmFyaWFuY2UgZm9yIE1pY2hhZWwgVGF5bG9yIFJOQS1TZXEgZGF0YQpgYGB7cn0KZ2VuZV92YXJpYW5jZSA8LSBtYXRyaXhTdGF0czo6cm93VmFycyhtdF9tYXRfbG9nMikKdmFyaWFuY2VfdGhyZXNob2xkIDwtIHF1YW50aWxlKGdlbmVfdmFyaWFuY2UsIDAuOTUsIG5hLnJtID0gVCkKaGlnaF92YXJpYW5jZV9pbmRleCA8LSB3aGljaChnZW5lX3ZhcmlhbmNlID4gdmFyaWFuY2VfdGhyZXNob2xkKQpgYGAKCiMgTWljaGFlbCBUYXlsb3IgKE4gPSA5NykgVU1BUCBjbHVzdGVyaW5nIApgYGB7cn0KdW1hcF9yZXN1bHRzIDwtIHVtYXA6OnVtYXAodChtdF9tYXRfbG9nMltoaWdoX3ZhcmlhbmNlX2luZGV4LCBdKSkKdW1hcF9wbG90X2RmIDwtIGRhdGEuZnJhbWUodW1hcF9yZXN1bHRzJGxheW91dCkgJT4lCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oIlNhbXBsZSIpICU+JQogIGlubmVyX2pvaW4obXRfYW5ub3QpCiMgUGxvdAp1bWFwX3Bsb3RfZGYgJT4lCiAgZ2dwbG90KGFlcyh4ID0gWDEsIAogICAgICAgICAgICAgeSA9IFgyLAogICAgICAgICAgICAgY29sb3IgPSBTdWJncm91cCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAzLCBhbHBoYSA9IDAuNykgKwogIHRoZW1lX2J3KCkgKwogIHhsYWIoIlVNQVAxIikgKwogIHlsYWIoIlVNQVAyIikKYGBgCiMjIE9ic2VydmF0aW9uOgotIEl0IGxvb2tzIGxpa2UgdGhlcmUgYXJlIHRvbyBmZXcgV05UIHNhbXBsZXMgKE4gPSAzKSB0byBlbmFibGUgdW5zdXBlcnZpc2VkIGNsdXN0ZXJpbmcgb2YgdGhvc2Ugc2FtcGxlcyBpbiB0aGlzIGRhdGFzZXQuCgojIFJlcnVuIG9mIE9wZW5QQlRBIHN0cmFuZGVkIFJOQS1TZXEgKE4gPSAxMjEpIFVNQVAgY2x1c3RlcmluZwotIFR1cm5zIG91dCB0aGUgaW5pdGlhbCBGUEtNIHZhbHVlcyB3ZXJlIG5vdCBsb2cyIHRyYW5zZm9ybWVkLCB0aHVzIE1CIHN1YnR5cGVzIGluIHBidGEtaGlzdG9sb2dpZXMudHN2IGZyb20gTUIgY2xhc3NpZmllciBhcmUgbm90IGFjY3VyYXRlLgpgYGB7cn0KIyBSZXBsb3QgVU1BUCB3aXRoIG5ldyBzdWJ0eXBlIGxhYmVscwpnZW5lX3ZhcmlhbmNlIDwtIG1hdHJpeFN0YXRzOjpyb3dWYXJzKHBidGFfY29sbF9sb2cyKQp2YXJpYW5jZV90aHJlc2hvbGQgPC0gcXVhbnRpbGUoZ2VuZV92YXJpYW5jZSwgMC45NSwgbmEucm0gPSBUKQpoaWdoX3ZhcmlhbmNlX2luZGV4IDwtIHdoaWNoKGdlbmVfdmFyaWFuY2UgPiB2YXJpYW5jZV90aHJlc2hvbGQpCnVtYXBfcmVzdWx0cyA8LSB1bWFwOjp1bWFwKHQocGJ0YV9jb2xsX2xvZzJbaGlnaF92YXJpYW5jZV9pbmRleCwgXSkpCiMgTWFrZSBhIGRhdGEgZnJhbWUgb2YgdGhlIGxheW91dCByZXN1bHRzIGFuZCBqb2luIHdpdGggbW9sZWN1bGFyIHN1YnR5cGUgCnVtYXBfcGxvdF9kZiA8LSBkYXRhLmZyYW1lKHVtYXBfcmVzdWx0cyRsYXlvdXQpICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJLaWRzX0ZpcnN0X0Jpb3NwZWNpbWVuX0lEIikgJT4lCiAgaW5uZXJfam9pbihwYnRhX2NvbWJpbmVkX3N1YnR5cGVzKQojIFBsb3QgYnkgc3VidHlwZQp1bWFwX3Bsb3RfZGYgJT4lCiAgZ2dwbG90KGFlcyh4ID0gWDEsIAogICAgICAgICAgICAgeSA9IFgyLAogICAgICAgICAgICAgY29sb3IgPSBtYl9jbGFzc2lmaWVyX3ByZWRpY3Rpb24pKSArCiAgZ2VvbV9wb2ludChzaXplID0gMywgYWxwaGEgPSAwLjcpICsKICB0aGVtZV9idygpICsKICB4bGFiKCJVTUFQMSIpICsKICB5bGFiKCJVTUFQMiIpCmBgYAotICBUaGlzIGxvb2tzIEEgTE9UIGJldHRlciEKCiMgc2Vzc2lvbiBpbmZvCmBgYHtyfQpzZXNzaW9uX2luZm8oKQpgYGAK