J. Taroni 2018

Part of the motivation behind using PLIER (particularly in a systemic lupus erythematosus whole blood setting) is that we can automatically extract information about cell type proportion variation. One of the datasets included in the SLE WB compendium comes from Banchereau, et al.. The original publication contained some interesting findings about neutrophils and plasmablasts. In this notebook, we explore whether latent variables from the PLIER model trained on the SLE WB compendium associated with these cell type (gene sets) show similar trends to what was found by Banchereau, et al. By extension, this also tells us whether the cell type-specific patterns are retained following all the processing we did in greenelab/rheum-plier-data/sle-wb.

Functions and directory set up

`%>%` <- dplyr::`%>%`
library(PLIER)
# custom functions
source(file.path("util", "plier_util.R"))
# plot and result directory setup for this notebook
plot.dir <- file.path("plots", "06")
dir.create(plot.dir, recursive = TRUE, showWarnings = FALSE)
results.dir <- file.path("results", "06")
dir.create(results.dir, recursive = TRUE, showWarnings = FALSE)

Load data

PLIER model

# SLE WB model trained in notebook 05
model.file <- file.path("results", "05", "SLE-WB_PLIER_model.RDS")
plier.results <- readRDS(model.file)
# summary data.frame (pathway to LV info like AUC, FDR)
sle.summary <- plier.results$summary
# LVs
b.matrix <- plier.results$B

Banchereau, et al. Sample Data Relationship File

# phenotype information 
# read in sample data relationship file
file.65391 <- file.path("data", "sample_info", "E-GEOD-65391.sdrf.txt")
e.65391.sdrf <- readr::read_tsv(file.65391)
Duplicated column names deduplicated: 'Term Source REF' => 'Term Source REF_1' [31], 'Term Accession Number' => 'Term Accession Number_1' [32], 'Characteristics [nephritis_class]' => 'Characteristics [nephritis_class]_1' [62], 'Term Source REF' => 'Term Source REF_2' [70], 'Term Accession Number' => 'Term Accession Number_2' [71], 'Term Source REF' => 'Term Source REF_3' [73], 'Term Accession Number' => 'Term Accession Number_3' [74], 'Term Source REF' => 'Term Source REF_4' [82], 'Term Accession Number' => 'Term Accession Number_4' [83], 'Term Source REF' => 'Term Source REF_5' [89], 'Term Accession Number' => 'Term Accession Number_5' [90], 'Term Source REF' => 'Term Source REF_6' [92], 'Term Accession Number' => 'Term Accession Number_6' [93], 'Term Source REF' => 'Term Source REF_7' [108], 'Protocol REF' => 'Protocol REF_1' [109], 'Term Source REF' => 'Term Source REF_8' [110], 'Protocol REF' => 'Protocol REF_2' [111], 'Term Source REF' => 'Term Source REF_9' [112], 'Protocol REF' => 'Protocol REF_3' [115], 'Term Source REF' => 'Term Source REF_10' [116], 'Protocol REF' => 'Protocol REF_4' [119], 'Term Source REF' => 'Term Source REF_11' [120], 'Term Source REF' => 'Term Source REF_12' [123], 'Protocol REF' => 'Protocol REF_5' [125], 'Term Source REF' => 'Term Source REF_13' [126], 'Protocol REF' => 'Protocol REF_6' [127], 'Term Source REF' => 'Term Source REF_14' [128], 'Term Source REF' => 'Term Source REF_15' [155], 'Term Accession Number' => 'Term Accession Number_7' [156], 'Term Source REF' => 'Term Source REF_16' [199], 'Term Accession Number' => 'Term Accession Number_8' [200], 'Term Source REF' => 'Term Source REF_17' [206], 'Term Accession Number' => 'Term Accession Number_9' [207], 'Term Source REF' => 'Term Source REF_18' [209], 'Term Accession Number' => 'Term Accession Number_10' [210]Parsed with column specification:
cols(
  .default = col_character(),
  `Characteristics [age]` = col_double(),
  `Characteristics [batch]` = col_integer(),
  `Characteristics [batch_replicate]` = col_logical(),
  `Characteristics [visit_count]` = col_integer(),
  `FactorValue [age]` = col_double(),
  `FactorValue [batch]` = col_integer(),
  `FactorValue [batch_replicate]` = col_logical(),
  `FactorValue [visit_count]` = col_integer()
)
See spec(...) for full column specifications.
# use Sample instead of Source Name
colnames(e.65391.sdrf)[1] <- c("Sample")
# get rid of trailing " 1" so they match the sample names from B matrix
e.65391.sdrf$Sample <- gsub(" 1", "", e.65391.sdrf$Sample)

Neutrophil signature

LVs associated with neutrophil gene sets

sle.summary %>%
  dplyr::filter(grepl("Neutrophil", pathway), 
                FDR < 0.05)

Based on these results, we select LV2,LV27, LV34, and LV87 for further exploration. If these latent variables are also strongly associated with other pathways or cell types, that is not desirable. That could make intepretation more difficult.

sle.summary %>%
  dplyr::filter(`LV index` %in% c(2, 27, 34, 87)) %>%
  dplyr::filter(FDR < 0.05) %>%
  dplyr::arrange(`LV index`, desc(AUC))

Some notes on these results:

  • LV2 looks like it captures the myeloid lineage in a broad sense, as a monocyte gene set, SVM Monocytes, and a neutrophil progenitor gene set, DMAP_GRAN2 (reported to be a neutrophilic metamyelocyte signature in the DMAP paper).

  • LV27 and LV34 also look like they may capture some information about monocytes because of their association with the DMAP_MONO2 gene set.

  • LV87 looks the most “neutrophil-specific” and therefore, the most desirable for this kind of analysis.

U matrix

PLIER::plotU(plierRes = plier.results,
             pval.cutoff = 1e-06,
             indexCol = c(2, 27, 34, 87),
             top = 10)

png(file.path(plot.dir, "SLE_WB_neutrophil_Uplot.png"), 
    res = 300, width = 7, height = 7, units = "in")
PLIER::plotU(plierRes = plier.results,
             pval.cutoff = 1e-06,
             indexCol = c(2, 27, 34, 87),
             top = 10)
dev.off()
null device 
          1 

Compare to neutrophil count from Banchereau, et al.

neutrophil.count.df <- e.65391.sdrf[, c("Sample", 
                                        "Characteristics [neutrophil_count]")]
colnames(neutrophil.count.df)[2] <- c("Neutrophil.Count")
neutrophil.count.df <- 
  neutrophil.count.df %>%
  dplyr::filter(Neutrophil.Count != "Data Not Available") %>%
  dplyr::filter(Neutrophil.Count != "Not Applicable")
# combine with neutrophil latent variables
neutro.lv.df <- as.data.frame(cbind(colnames(b.matrix), 
                                    t(b.matrix[c(2, 27, 34, 87), ])))
colnames(neutro.lv.df) <- c("Sample", "LV2", "LV27", "LV34", "LV87")
neutro.df <- dplyr::inner_join(neutro.lv.df, neutrophil.count.df,
                               by = "Sample") %>%
  dplyr::mutate(LV2 = as.numeric(as.character(LV2)),
                LV27 = as.numeric(as.character(LV27)),
                LV34 = as.numeric(as.character(LV34)),
                LV87 = as.numeric(as.character(LV87)),
                Neutrophil.Count = as.numeric(as.character(Neutrophil.Count)))
Column `Sample` joining factor and character vector, coercing into character vector
neutro.df
# write to file, will use to compare to results with recount2
count.file <- file.path(results.dir, 
                        "Banchereau_count_SLE_model_neutrophil_LV.tsv")
readr::write_tsv(neutro.df, path = count.file)
# function for scatter plots, given lv (a string indicating the LV of interest)
# make a scatter plot where the LV is the x variable, neutrophil count is the
# y variable & fit a line with geom_smooth(method = "lm")
# also will annotate the plot with the supplied r-squared value (rsq arg)
# where the text is placed is automatically chosen from the x and y values
# needs to be used in this global environment
LVScatter <- function(lv, rsq) {
  y.var <- "Neutrophil.Count"
  
  # calculate where to put the r-squared value
  x.range <- max(neutro.df[, lv]) - min(neutro.df[, lv])
  x.coord <- min(neutro.df[, lv]) + (x.range * 0.15)
  y.range <- max(neutro.df[, y.var]) - min(neutro.df[, y.var])
  y.coord <- max(neutro.df[, y.var]) - (y.range * 0.15)
  
  ggplot2::ggplot(neutro.df, ggplot2::aes_string(x = lv, y = y.var)) +
    ggplot2::geom_point(alpha = 0.7) +
    ggplot2::geom_smooth(method = "lm") +
    ggplot2::theme_bw() +
    ggplot2::labs(y = "Neutrophil Count") +
    ggplot2::theme(legend.position = "none", 
                   text = ggplot2::element_text(size = 15)) +
    ggplot2::annotate("text", x = x.coord, y = y.coord, 
                      label = paste("r-squared =", rsq))
}

LV2

summary(lm(neutro.df$Neutrophil.Count ~ neutro.df$LV2))

Call:
lm(formula = neutro.df$Neutrophil.Count ~ neutro.df$LV2)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.3999 -1.4245 -0.4961  0.9463 13.9831 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)    3.81000    0.08434   45.18   <2e-16 ***
neutro.df$LV2  8.43183    0.77393   10.89   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.246 on 851 degrees of freedom
Multiple R-squared:  0.1224,    Adjusted R-squared:  0.1214 
F-statistic: 118.7 on 1 and 851 DF,  p-value: < 2.2e-16
LVScatter(lv = "LV2", rsq = 0.12)

LV27

summary(lm(neutro.df$Neutrophil.Count ~ neutro.df$LV27))

Call:
lm(formula = neutro.df$Neutrophil.Count ~ neutro.df$LV27)

Residuals:
    Min      1Q  Median      3Q     Max 
-5.0670 -1.0934 -0.1809  0.7934 12.2135 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)     3.99216    0.06501   61.41   <2e-16 ***
neutro.df$LV27  4.66659    0.20285   23.00   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.882 on 851 degrees of freedom
Multiple R-squared:  0.3834,    Adjusted R-squared:  0.3827 
F-statistic: 529.2 on 1 and 851 DF,  p-value: < 2.2e-16
LVScatter(lv = "LV27", rsq = 0.38)

LV34

summary(lm(neutro.df$Neutrophil.Count ~ neutro.df$LV34))

Call:
lm(formula = neutro.df$Neutrophil.Count ~ neutro.df$LV34)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.9315 -1.2393 -0.2902  0.8375 11.4418 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)     4.03189    0.06972   57.83   <2e-16 ***
neutro.df$LV34  4.11942    0.22162   18.59   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.022 on 851 degrees of freedom
Multiple R-squared:  0.2888,    Adjusted R-squared:  0.2879 
F-statistic: 345.5 on 1 and 851 DF,  p-value: < 2.2e-16
LVScatter(lv = "LV34", rsq = 0.29)

LV87

summary(lm(neutro.df$Neutrophil.Count ~ neutro.df$LV87))

Call:
lm(formula = neutro.df$Neutrophil.Count ~ neutro.df$LV87)

Residuals:
    Min      1Q  Median      3Q     Max 
-3.9316 -1.2844 -0.3642  0.7306 12.5832 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)     4.09067    0.06936   58.98   <2e-16 ***
neutro.df$LV87  3.37195    0.18090   18.64   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.02 on 851 degrees of freedom
Multiple R-squared:  0.2899,    Adjusted R-squared:  0.2891 
F-statistic: 347.5 on 1 and 851 DF,  p-value: < 2.2e-16
LVScatter(lv = "LV87", rsq = 0.29)

When comparing the results to the recount2 model, we’ll pursue LV27 because it’s the best performing and LV87 because of its lack of overlap with monocyte signatures. So, we’ll save the plots for these two LVs.

plot.file <- file.path(plot.dir, "Banchereau_neutrophil_count_LV27_scatter.png")
ggplot2::ggsave(plot.file, plot = LVScatter("LV27", rsq = 0.38))
Saving 7 x 7 in image
plot.file <- file.path(plot.dir, "Banchereau_neutrophil_count_LV87_scatter.png")
ggplot2::ggsave(plot.file, plot = LVScatter("LV87", rsq = 0.29))

Plasma cell signature

In Banchereau, et al., the authors demonstrated that plasmablast counts were different between patients stratified by 3 disease activity (DA) groups.

While there were no plasmablast gene sets given to PLIER during training, we did use plasma cell gene sets (isolated from PBMCs) (A plasmablast is a plasma cell precursor that is typically found in a germinal center.)

LVs associated with plasma cell gene sets

What LVs, if any, are significantly associated with the plasma cell gene sets?

sle.summary %>%
  dplyr::filter(grepl("Plasma", pathway), 
                FDR < 0.05)
sle.summary %>% 
  dplyr::filter(`LV index` %in% c(52, 136),
                FDR < 0.05) %>%
  dplyr::arrange(desc(AUC))

U matrix

PLIER::plotU(plierRes = plier.results,
             pval.cutoff = 1e-06,
             indexCol = c(52, 136),
             top = 10)

png(file.path(plot.dir, "SLE_WB_plasma_cell_Uplot.png"), 
    res = 300, width = 7, height = 7, units = "in")
PLIER::plotU(plierRes = plier.results,
             pval.cutoff = 1e-06,
             indexCol = c(52, 136),
             top = 10)
dev.off()
null device 
          1 

Banchereau, et al. disease activity groups

Disease activity was defined based on SLEDAI (SLE Disease Activity Index, Bombardier, et al. 1992.). Banchereau, et al. defined the disease activity groups as follows:

Samples were categorized as DA1 (SLEDAI: 0–2), DA2 (SLEDAI: 3–7), or DA3 (SLEDAI > 7), based on SLEDAI distribution across the cohort.

# get DA information from the sample-data relationship file
da.group.df <- e.65391.sdrf[, c("Sample", "Characteristics [disease_activity]")]
colnames(da.group.df)[2] <- "Disease.Activity"
da.group.df <- da.group.df %>%
              dplyr::filter(Disease.Activity != "Not Applicable")
# plasma cell LV
plasma.lv.df <- as.data.frame(cbind(colnames(b.matrix), 
                                    t(b.matrix[c(52, 136), ])))
colnames(plasma.lv.df) <- c("Sample", "LV52", "LV136")
# join
plasma.df <- dplyr::inner_join(plasma.lv.df, da.group.df) %>%
  dplyr::mutate(Disease.Activity = dplyr::recode(Disease.Activity, 
                                                 `1` = "DA1", 
                                                 `2` = "DA2",
                                                 `3` = "DA3"),
                Disease.Activity = factor(Disease.Activity),
                LV52 = as.numeric(as.character(LV52)),
                LV136 = as.numeric(as.character(LV136))) 
Joining, by = "Sample"
Column `Sample` joining factor and character vector, coercing into character vector
head(plasma.df)
# write to file, will be used to compare to recount2 model
plasma.file <- file.path(results.dir, 
                         "Banchereau_DA_group_SLE_model_plasma_cell_LVs.tsv")
readr::write_tsv(plasma.df, path = plasma.file)

Plotting

plasma.df %>%
  ggplot2::ggplot(ggplot2::aes(x = Disease.Activity, 
                               y = LV52)) +
  ggplot2::geom_boxplot(notch = TRUE) +
  ggplot2::theme_bw() +
  ggplot2::labs(x = "Disease Activity") +
  ggplot2::theme(text = ggplot2::element_text(size = 15))

plot.file <- file.path(plot.dir, "Banchereau_DA_group_LV52_boxplot.png")
ggplot2::ggsave(filename = plot.file, plot = ggplot2::last_plot())
Saving 7 x 7 in image
# check for statistical significance 
# (pairwise t-test is consistent with original publication as far as I can tell)
pairwise.t.test(x = plasma.df$LV52, 
                g = plasma.df$Disease.Activity, 
                p.adjust.method = "bonferroni")

    Pairwise comparisons using t tests with pooled SD 

data:  plasma.df$LV52 and plasma.df$Disease.Activity 

    DA1     DA2    
DA2 0.012   -      
DA3 < 2e-16 6.5e-08

P value adjustment method: bonferroni 
plasma.df %>%
  ggplot2::ggplot(ggplot2::aes(x = Disease.Activity, 
                               y = LV136)) +
  ggplot2::geom_boxplot(notch = TRUE) +
  ggplot2::theme_bw() +
  ggplot2::labs(x = "Disease Activity") +
  ggplot2::theme(text = ggplot2::element_text(size = 15))

plot.file <- file.path(plot.dir, "Banchereau_DA_group_LV136_boxplot.png")
ggplot2::ggsave(filename = plot.file, plot = ggplot2::last_plot())
Saving 7 x 7 in image
# check for statistical significance 
# (pairwise t-test is consistent with original publication as far as I can tell)
pairwise.t.test(x = plasma.df$LV136, 
                g = plasma.df$Disease.Activity, 
                p.adjust.method = "bonferroni")

    Pairwise comparisons using t tests with pooled SD 

data:  plasma.df$LV136 and plasma.df$Disease.Activity 

    DA1   DA2  
DA2 0.236 -    
DA3 0.018 1.000

P value adjustment method: bonferroni 
LS0tCnRpdGxlOiAiU0xFIFdCIFBMSUVSIGNlbGwgdHlwZSBhbmFseXNlcyIKb3V0cHV0OiAgIAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCioqSi4gVGFyb25pIDIwMTgqKgoKUGFydCBvZiB0aGUgbW90aXZhdGlvbiBiZWhpbmQgdXNpbmcgUExJRVIgKHBhcnRpY3VsYXJseSBpbiBhIHN5c3RlbWljIGx1cHVzIAplcnl0aGVtYXRvc3VzIHdob2xlIGJsb29kIHNldHRpbmcpIGlzIHRoYXQgd2UgY2FuIGF1dG9tYXRpY2FsbHkgZXh0cmFjdCAKaW5mb3JtYXRpb24gYWJvdXQgY2VsbCB0eXBlIHByb3BvcnRpb24gdmFyaWF0aW9uLiAKT25lIG9mIHRoZSBkYXRhc2V0cyBpbmNsdWRlZCBpbiB0aGUgU0xFIFdCIGNvbXBlbmRpdW0gY29tZXMgZnJvbSAKW0JhbmNoZXJlYXUsIGV0IGFsLl0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMTYvai5jZWxsLjIwMTYuMDMuMDA4KS4KVGhlIG9yaWdpbmFsIHB1YmxpY2F0aW9uIGNvbnRhaW5lZCBzb21lIGludGVyZXN0aW5nIGZpbmRpbmdzIGFib3V0IG5ldXRyb3BoaWxzIAphbmQgcGxhc21hYmxhc3RzLgpJbiB0aGlzIG5vdGVib29rLCB3ZSBleHBsb3JlIHdoZXRoZXIgbGF0ZW50IHZhcmlhYmxlcyBmcm9tIHRoZSBQTElFUiBtb2RlbCAKdHJhaW5lZCBvbiB0aGUgU0xFIFdCIGNvbXBlbmRpdW0gYXNzb2NpYXRlZCB3aXRoIHRoZXNlIGNlbGwgdHlwZSAoZ2VuZSBzZXRzKSAKc2hvdyBzaW1pbGFyIHRyZW5kcyB0byB3aGF0IHdhcyBmb3VuZCBieSBCYW5jaGVyZWF1LCBldCBhbC4KQnkgZXh0ZW5zaW9uLCB0aGlzIGFsc28gdGVsbHMgdXMgd2hldGhlciB0aGUgY2VsbCB0eXBlLXNwZWNpZmljIHBhdHRlcm5zIGFyZSAKcmV0YWluZWQgZm9sbG93aW5nIGFsbCB0aGUgcHJvY2Vzc2luZyB3ZSBkaWQgaW4gCltgZ3JlZW5lbGFiL3JoZXVtLXBsaWVyLWRhdGEvc2xlLXdiYF0oaHR0cHM6Ly9naXRodWIuY29tL2dyZWVuZWxhYi9yaGV1bS1wbGllci1kYXRhL3RyZWUvNGJlNTQ3NTUzZjI0ZmVjYWM5ZTJmNWMyYjQ2OWExN2Y5ZGYyNTNmMC9zbGUtd2IpLgoKIyMgRnVuY3Rpb25zIGFuZCBkaXJlY3Rvcnkgc2V0IHVwCgpgYGB7cn0KYCU+JWAgPC0gZHBseXI6OmAlPiVgCmxpYnJhcnkoUExJRVIpCgojIGN1c3RvbSBmdW5jdGlvbnMKc291cmNlKGZpbGUucGF0aCgidXRpbCIsICJwbGllcl91dGlsLlIiKSkKYGBgCgpgYGB7cn0KIyBwbG90IGFuZCByZXN1bHQgZGlyZWN0b3J5IHNldHVwIGZvciB0aGlzIG5vdGVib29rCnBsb3QuZGlyIDwtIGZpbGUucGF0aCgicGxvdHMiLCAiMDYiKQpkaXIuY3JlYXRlKHBsb3QuZGlyLCByZWN1cnNpdmUgPSBUUlVFLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKcmVzdWx0cy5kaXIgPC0gZmlsZS5wYXRoKCJyZXN1bHRzIiwgIjA2IikKZGlyLmNyZWF0ZShyZXN1bHRzLmRpciwgcmVjdXJzaXZlID0gVFJVRSwgc2hvd1dhcm5pbmdzID0gRkFMU0UpCmBgYAoKCiMjIExvYWQgZGF0YQoKIyMjIFBMSUVSIG1vZGVsCmBgYHtyfQojIFNMRSBXQiBtb2RlbCB0cmFpbmVkIGluIG5vdGVib29rIDA1Cm1vZGVsLmZpbGUgPC0gZmlsZS5wYXRoKCJyZXN1bHRzIiwgIjA1IiwgIlNMRS1XQl9QTElFUl9tb2RlbC5SRFMiKQpwbGllci5yZXN1bHRzIDwtIHJlYWRSRFMobW9kZWwuZmlsZSkKIyBzdW1tYXJ5IGRhdGEuZnJhbWUgKHBhdGh3YXkgdG8gTFYgaW5mbyBsaWtlIEFVQywgRkRSKQpzbGUuc3VtbWFyeSA8LSBwbGllci5yZXN1bHRzJHN1bW1hcnkKIyBMVnMKYi5tYXRyaXggPC0gcGxpZXIucmVzdWx0cyRCCmBgYAoKIyMjIEJhbmNoZXJlYXUsIGV0IGFsLiBTYW1wbGUgRGF0YSBSZWxhdGlvbnNoaXAgRmlsZQpgYGB7cn0KIyBwaGVub3R5cGUgaW5mb3JtYXRpb24gCiMgcmVhZCBpbiBzYW1wbGUgZGF0YSByZWxhdGlvbnNoaXAgZmlsZQpmaWxlLjY1MzkxIDwtIGZpbGUucGF0aCgiZGF0YSIsICJzYW1wbGVfaW5mbyIsICJFLUdFT0QtNjUzOTEuc2RyZi50eHQiKQplLjY1MzkxLnNkcmYgPC0gcmVhZHI6OnJlYWRfdHN2KGZpbGUuNjUzOTEpCiMgdXNlIFNhbXBsZSBpbnN0ZWFkIG9mIFNvdXJjZSBOYW1lCmNvbG5hbWVzKGUuNjUzOTEuc2RyZilbMV0gPC0gYygiU2FtcGxlIikKIyBnZXQgcmlkIG9mIHRyYWlsaW5nICIgMSIgc28gdGhleSBtYXRjaCB0aGUgc2FtcGxlIG5hbWVzIGZyb20gQiBtYXRyaXgKZS42NTM5MS5zZHJmJFNhbXBsZSA8LSBnc3ViKCIgMSIsICIiLCBlLjY1MzkxLnNkcmYkU2FtcGxlKQpgYGAKCiMjIE5ldXRyb3BoaWwgc2lnbmF0dXJlCgojIyMgTFZzIGFzc29jaWF0ZWQgd2l0aCBuZXV0cm9waGlsIGdlbmUgc2V0cwoKYGBge3J9CnNsZS5zdW1tYXJ5ICU+JQogIGRwbHlyOjpmaWx0ZXIoZ3JlcGwoIk5ldXRyb3BoaWwiLCBwYXRod2F5KSwgCiAgICAgICAgICAgICAgICBGRFIgPCAwLjA1KQpgYGAKQmFzZWQgb24gdGhlc2UgcmVzdWx0cywgd2Ugc2VsZWN0IGBMVjJgLGBMVjI3YCwgYExWMzRgLCBhbmQgYExWODdgIApmb3IgZnVydGhlciBleHBsb3JhdGlvbi4KSWYgdGhlc2UgbGF0ZW50IHZhcmlhYmxlcyBhcmUgYWxzbyBzdHJvbmdseSBhc3NvY2lhdGVkIHdpdGggX290aGVyXyBwYXRod2F5cyBvciAKY2VsbCB0eXBlcywgdGhhdCBpcyBub3QgZGVzaXJhYmxlLiAKVGhhdCBjb3VsZCBtYWtlIGludGVwcmV0YXRpb24gbW9yZSBkaWZmaWN1bHQuCgpgYGB7cn0Kc2xlLnN1bW1hcnkgJT4lCiAgZHBseXI6OmZpbHRlcihgTFYgaW5kZXhgICVpbiUgYygyLCAyNywgMzQsIDg3KSkgJT4lCiAgZHBseXI6OmZpbHRlcihGRFIgPCAwLjA1KSAlPiUKICBkcGx5cjo6YXJyYW5nZShgTFYgaW5kZXhgLCBkZXNjKEFVQykpCmBgYAoKU29tZSBub3RlcyBvbiB0aGVzZSByZXN1bHRzOiAKCiogYExWMmAgbG9va3MgbGlrZSBpdCBjYXB0dXJlcyB0aGUgbXllbG9pZCBsaW5lYWdlIGluIGEgYnJvYWQgc2Vuc2UsIGFzIGEKbW9ub2N5dGUgZ2VuZSBzZXQsIGBTVk0gTW9ub2N5dGVzYCwgYW5kIGEgbmV1dHJvcGhpbCBwcm9nZW5pdG9yIGdlbmUgc2V0LApgRE1BUF9HUkFOMmAgKHJlcG9ydGVkIHRvIGJlIGEgbmV1dHJvcGhpbGljIG1ldGFteWVsb2N5dGUgc2lnbmF0dXJlIGluCnRoZSBbRE1BUCBwYXBlcl0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMTYvai5jZWxsLjIwMTEuMDEuMDA0KSkuCgoqIGBMVjI3YCBhbmQgYExWMzRgIGFsc28gbG9vayBsaWtlIHRoZXkgbWF5IGNhcHR1cmUgc29tZSBpbmZvcm1hdGlvbiBhYm91dAptb25vY3l0ZXMgYmVjYXVzZSBvZiB0aGVpciBhc3NvY2lhdGlvbiB3aXRoIHRoZSBgRE1BUF9NT05PMmAgZ2VuZSBzZXQuCgoqIGBMVjg3YCBsb29rcyB0aGUgbW9zdCAibmV1dHJvcGhpbC1zcGVjaWZpYyIgYW5kIHRoZXJlZm9yZSwgdGhlIG1vc3QgZGVzaXJhYmxlCmZvciB0aGlzIGtpbmQgb2YgYW5hbHlzaXMuCgojIyMjIFUgbWF0cml4CgpgYGB7cn0KUExJRVI6OnBsb3RVKHBsaWVyUmVzID0gcGxpZXIucmVzdWx0cywKICAgICAgICAgICAgIHB2YWwuY3V0b2ZmID0gMWUtMDYsCiAgICAgICAgICAgICBpbmRleENvbCA9IGMoMiwgMjcsIDM0LCA4NyksCiAgICAgICAgICAgICB0b3AgPSAxMCkKYGBgCmBgYHtyfQpwbmcoZmlsZS5wYXRoKHBsb3QuZGlyLCAiU0xFX1dCX25ldXRyb3BoaWxfVXBsb3QucG5nIiksIAogICAgcmVzID0gMzAwLCB3aWR0aCA9IDcsIGhlaWdodCA9IDcsIHVuaXRzID0gImluIikKUExJRVI6OnBsb3RVKHBsaWVyUmVzID0gcGxpZXIucmVzdWx0cywKICAgICAgICAgICAgIHB2YWwuY3V0b2ZmID0gMWUtMDYsCiAgICAgICAgICAgICBpbmRleENvbCA9IGMoMiwgMjcsIDM0LCA4NyksCiAgICAgICAgICAgICB0b3AgPSAxMCkKZGV2Lm9mZigpCmBgYAoKIyMjIENvbXBhcmUgdG8gbmV1dHJvcGhpbCBjb3VudCBmcm9tIEJhbmNoZXJlYXUsIGV0IGFsLiAKCmBgYHtyfQpuZXV0cm9waGlsLmNvdW50LmRmIDwtIGUuNjUzOTEuc2RyZlssIGMoIlNhbXBsZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNoYXJhY3RlcmlzdGljcyBbbmV1dHJvcGhpbF9jb3VudF0iKV0KY29sbmFtZXMobmV1dHJvcGhpbC5jb3VudC5kZilbMl0gPC0gYygiTmV1dHJvcGhpbC5Db3VudCIpCm5ldXRyb3BoaWwuY291bnQuZGYgPC0gCiAgbmV1dHJvcGhpbC5jb3VudC5kZiAlPiUKICBkcGx5cjo6ZmlsdGVyKE5ldXRyb3BoaWwuQ291bnQgIT0gIkRhdGEgTm90IEF2YWlsYWJsZSIpICU+JQogIGRwbHlyOjpmaWx0ZXIoTmV1dHJvcGhpbC5Db3VudCAhPSAiTm90IEFwcGxpY2FibGUiKQoKYGBgCgoKYGBge3J9CiMgY29tYmluZSB3aXRoIG5ldXRyb3BoaWwgbGF0ZW50IHZhcmlhYmxlcwpuZXV0cm8ubHYuZGYgPC0gYXMuZGF0YS5mcmFtZShjYmluZChjb2xuYW1lcyhiLm1hdHJpeCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0KGIubWF0cml4W2MoMiwgMjcsIDM0LCA4NyksIF0pKSkKY29sbmFtZXMobmV1dHJvLmx2LmRmKSA8LSBjKCJTYW1wbGUiLCAiTFYyIiwgIkxWMjciLCAiTFYzNCIsICJMVjg3IikKCm5ldXRyby5kZiA8LSBkcGx5cjo6aW5uZXJfam9pbihuZXV0cm8ubHYuZGYsIG5ldXRyb3BoaWwuY291bnQuZGYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJTYW1wbGUiKSAlPiUKICBkcGx5cjo6bXV0YXRlKExWMiA9IGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKExWMikpLAogICAgICAgICAgICAgICAgTFYyNyA9IGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKExWMjcpKSwKICAgICAgICAgICAgICAgIExWMzQgPSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihMVjM0KSksCiAgICAgICAgICAgICAgICBMVjg3ID0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoTFY4NykpLAogICAgICAgICAgICAgICAgTmV1dHJvcGhpbC5Db3VudCA9IGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKE5ldXRyb3BoaWwuQ291bnQpKSkKbmV1dHJvLmRmCmBgYAoKYGBge3J9CiMgd3JpdGUgdG8gZmlsZSwgd2lsbCB1c2UgdG8gY29tcGFyZSB0byByZXN1bHRzIHdpdGggcmVjb3VudDIKY291bnQuZmlsZSA8LSBmaWxlLnBhdGgocmVzdWx0cy5kaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAiQmFuY2hlcmVhdV9jb3VudF9TTEVfbW9kZWxfbmV1dHJvcGhpbF9MVi50c3YiKQpyZWFkcjo6d3JpdGVfdHN2KG5ldXRyby5kZiwgcGF0aCA9IGNvdW50LmZpbGUpCmBgYAoKCmBgYHtyfQojIGZ1bmN0aW9uIGZvciBzY2F0dGVyIHBsb3RzLCBnaXZlbiBsdiAoYSBzdHJpbmcgaW5kaWNhdGluZyB0aGUgTFYgb2YgaW50ZXJlc3QpCiMgbWFrZSBhIHNjYXR0ZXIgcGxvdCB3aGVyZSB0aGUgTFYgaXMgdGhlIHggdmFyaWFibGUsIG5ldXRyb3BoaWwgY291bnQgaXMgdGhlCiMgeSB2YXJpYWJsZSAmIGZpdCBhIGxpbmUgd2l0aCBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQojIGFsc28gd2lsbCBhbm5vdGF0ZSB0aGUgcGxvdCB3aXRoIHRoZSBzdXBwbGllZCByLXNxdWFyZWQgdmFsdWUgKHJzcSBhcmcpCiMgd2hlcmUgdGhlIHRleHQgaXMgcGxhY2VkIGlzIGF1dG9tYXRpY2FsbHkgY2hvc2VuIGZyb20gdGhlIHggYW5kIHkgdmFsdWVzCiMgbmVlZHMgdG8gYmUgdXNlZCBpbiB0aGlzIGdsb2JhbCBlbnZpcm9ubWVudApMVlNjYXR0ZXIgPC0gZnVuY3Rpb24obHYsIHJzcSkgewogIHkudmFyIDwtICJOZXV0cm9waGlsLkNvdW50IgogIAogICMgY2FsY3VsYXRlIHdoZXJlIHRvIHB1dCB0aGUgci1zcXVhcmVkIHZhbHVlCiAgeC5yYW5nZSA8LSBtYXgobmV1dHJvLmRmWywgbHZdKSAtIG1pbihuZXV0cm8uZGZbLCBsdl0pCiAgeC5jb29yZCA8LSBtaW4obmV1dHJvLmRmWywgbHZdKSArICh4LnJhbmdlICogMC4xNSkKICB5LnJhbmdlIDwtIG1heChuZXV0cm8uZGZbLCB5LnZhcl0pIC0gbWluKG5ldXRyby5kZlssIHkudmFyXSkKICB5LmNvb3JkIDwtIG1heChuZXV0cm8uZGZbLCB5LnZhcl0pIC0gKHkucmFuZ2UgKiAwLjE1KQogIAogIGdncGxvdDI6OmdncGxvdChuZXV0cm8uZGYsIGdncGxvdDI6OmFlc19zdHJpbmcoeCA9IGx2LCB5ID0geS52YXIpKSArCiAgICBnZ3Bsb3QyOjpnZW9tX3BvaW50KGFscGhhID0gMC43KSArCiAgICBnZ3Bsb3QyOjpnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKSArCiAgICBnZ3Bsb3QyOjp0aGVtZV9idygpICsKICAgIGdncGxvdDI6OmxhYnMoeSA9ICJOZXV0cm9waGlsIENvdW50IikgKwogICAgZ2dwbG90Mjo6dGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCAKICAgICAgICAgICAgICAgICAgIHRleHQgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDE1KSkgKwogICAgZ2dwbG90Mjo6YW5ub3RhdGUoInRleHQiLCB4ID0geC5jb29yZCwgeSA9IHkuY29vcmQsIAogICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBwYXN0ZSgici1zcXVhcmVkID0iLCByc3EpKQp9CmBgYAoKIyMjIyBMVjIKCmBgYHtyfQpzdW1tYXJ5KGxtKG5ldXRyby5kZiROZXV0cm9waGlsLkNvdW50IH4gbmV1dHJvLmRmJExWMikpCmBgYAoKYGBge3J9CkxWU2NhdHRlcihsdiA9ICJMVjIiLCByc3EgPSAwLjEyKQpgYGAKCgoKIyMjIyBMVjI3CgpgYGB7cn0Kc3VtbWFyeShsbShuZXV0cm8uZGYkTmV1dHJvcGhpbC5Db3VudCB+IG5ldXRyby5kZiRMVjI3KSkKYGBgCgpgYGB7cn0KTFZTY2F0dGVyKGx2ID0gIkxWMjciLCByc3EgPSAwLjM4KQpgYGAKCgojIyMjIExWMzQKCmBgYHtyfQpzdW1tYXJ5KGxtKG5ldXRyby5kZiROZXV0cm9waGlsLkNvdW50IH4gbmV1dHJvLmRmJExWMzQpKQpgYGAKCmBgYHtyfQpMVlNjYXR0ZXIobHYgPSAiTFYzNCIsIHJzcSA9IDAuMjkpCmBgYAoKIyMjIyBMVjg3CgpgYGB7cn0Kc3VtbWFyeShsbShuZXV0cm8uZGYkTmV1dHJvcGhpbC5Db3VudCB+IG5ldXRyby5kZiRMVjg3KSkKYGBgCgpgYGB7cn0KTFZTY2F0dGVyKGx2ID0gIkxWODciLCByc3EgPSAwLjI5KQpgYGAKCldoZW4gY29tcGFyaW5nIHRoZSByZXN1bHRzIHRvIHRoZSByZWNvdW50MiBtb2RlbCwgd2UnbGwgcHVyc3VlIGBMVjI3YCBiZWNhdXNlIAppdCdzIHRoZSBiZXN0IHBlcmZvcm1pbmcgYW5kIGBMVjg3YCBiZWNhdXNlIG9mIGl0cyBsYWNrIG9mIG92ZXJsYXAgd2l0aAptb25vY3l0ZSBzaWduYXR1cmVzLgpTbywgd2UnbGwgc2F2ZSB0aGUgcGxvdHMgZm9yIHRoZXNlIHR3byBMVnMuCgpgYGB7cn0KcGxvdC5maWxlIDwtIGZpbGUucGF0aChwbG90LmRpciwgIkJhbmNoZXJlYXVfbmV1dHJvcGhpbF9jb3VudF9MVjI3X3NjYXR0ZXIucG5nIikKZ2dwbG90Mjo6Z2dzYXZlKHBsb3QuZmlsZSwgcGxvdCA9IExWU2NhdHRlcigiTFYyNyIsIHJzcSA9IDAuMzgpKQpwbG90LmZpbGUgPC0gZmlsZS5wYXRoKHBsb3QuZGlyLCAiQmFuY2hlcmVhdV9uZXV0cm9waGlsX2NvdW50X0xWODdfc2NhdHRlci5wbmciKQpnZ3Bsb3QyOjpnZ3NhdmUocGxvdC5maWxlLCBwbG90ID0gTFZTY2F0dGVyKCJMVjg3IiwgcnNxID0gMC4yOSkpCmBgYAoKIyMgUGxhc21hIGNlbGwgc2lnbmF0dXJlCgpJbiBCYW5jaGVyZWF1LCBldCBhbC4sIHRoZSBhdXRob3JzIGRlbW9uc3RyYXRlZCB0aGF0IHBsYXNtYWJsYXN0IGNvdW50cyB3ZXJlIApkaWZmZXJlbnQgYmV0d2VlbiBwYXRpZW50cyBzdHJhdGlmaWVkIGJ5IDMgZGlzZWFzZSBhY3Rpdml0eSAoREEpIGdyb3Vwcy4gCgpXaGlsZSB0aGVyZSB3ZXJlIG5vIF9wbGFzbWFibGFzdF8gZ2VuZSBzZXRzIGdpdmVuIHRvIFBMSUVSIGR1cmluZyB0cmFpbmluZywgd2UgCmRpZCB1c2UgX3BsYXNtYSBjZWxsXyBnZW5lIHNldHMgKGlzb2xhdGVkIGZyb20gUEJNQ3MpCihBIHBsYXNtYWJsYXN0IGlzIGEgcGxhc21hIGNlbGwgcHJlY3Vyc29yIHRoYXQgaXMgdHlwaWNhbGx5IGZvdW5kIGluIGEKZ2VybWluYWwgY2VudGVyLikKCiMjIyBMVnMgYXNzb2NpYXRlZCB3aXRoIHBsYXNtYSBjZWxsIGdlbmUgc2V0cwoKV2hhdCBMVnMsIGlmIGFueSwgYXJlIHNpZ25pZmljYW50bHkgYXNzb2NpYXRlZCB3aXRoIHRoZSBwbGFzbWEgY2VsbCBnZW5lIHNldHM/CgpgYGB7cn0Kc2xlLnN1bW1hcnkgJT4lCiAgZHBseXI6OmZpbHRlcihncmVwbCgiUGxhc21hIiwgcGF0aHdheSksIAogICAgICAgICAgICAgICAgRkRSIDwgMC4wNSkKYGBgCmBgYHtyfQpzbGUuc3VtbWFyeSAlPiUgCiAgZHBseXI6OmZpbHRlcihgTFYgaW5kZXhgICVpbiUgYyg1MiwgMTM2KSwKICAgICAgICAgICAgICAgIEZEUiA8IDAuMDUpICU+JQogIGRwbHlyOjphcnJhbmdlKGRlc2MoQVVDKSkKYGBgCgojIyMjIFUgbWF0cml4CmBgYHtyfQpQTElFUjo6cGxvdFUocGxpZXJSZXMgPSBwbGllci5yZXN1bHRzLAogICAgICAgICAgICAgcHZhbC5jdXRvZmYgPSAxZS0wNiwKICAgICAgICAgICAgIGluZGV4Q29sID0gYyg1MiwgMTM2KSwKICAgICAgICAgICAgIHRvcCA9IDEwKQpgYGAKCmBgYHtyfQpwbmcoZmlsZS5wYXRoKHBsb3QuZGlyLCAiU0xFX1dCX3BsYXNtYV9jZWxsX1VwbG90LnBuZyIpLCAKICAgIHJlcyA9IDMwMCwgd2lkdGggPSA3LCBoZWlnaHQgPSA3LCB1bml0cyA9ICJpbiIpClBMSUVSOjpwbG90VShwbGllclJlcyA9IHBsaWVyLnJlc3VsdHMsCiAgICAgICAgICAgICBwdmFsLmN1dG9mZiA9IDFlLTA2LAogICAgICAgICAgICAgaW5kZXhDb2wgPSBjKDUyLCAxMzYpLAogICAgICAgICAgICAgdG9wID0gMTApCmRldi5vZmYoKQpgYGAKCgojIyMgQmFuY2hlcmVhdSwgZXQgYWwuIGRpc2Vhc2UgYWN0aXZpdHkgZ3JvdXBzCgpEaXNlYXNlIGFjdGl2aXR5IHdhcyBkZWZpbmVkIGJhc2VkIG9uIFNMRURBSSAoU0xFIERpc2Vhc2UgQWN0aXZpdHkgSW5kZXgsCltCb21iYXJkaWVyLCBldCBhbC4gMTk5Mi5dKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcHVibWVkLzE1OTk1MjApKS4gCltCYW5jaGVyZWF1LCBldCBhbC5dKGh0dHBzOi8vZG9pLm9yZy8xMC4xMDE2L2ouY2VsbC4yMDE2LjAzLjAwOCkKZGVmaW5lZCB0aGUgZGlzZWFzZSBhY3Rpdml0eSBncm91cHMgYXMgZm9sbG93czoKCj4gU2FtcGxlcyB3ZXJlIGNhdGVnb3JpemVkIGFzIERBMSAoU0xFREFJOiAw4oCTMiksIERBMiAoU0xFREFJOiAz4oCTNyksIG9yIERBMyAKKFNMRURBSSA+IDcpLCBiYXNlZCBvbiBTTEVEQUkgZGlzdHJpYnV0aW9uIGFjcm9zcyB0aGUgY29ob3J0LiAKCmBgYHtyfQojIGdldCBEQSBpbmZvcm1hdGlvbiBmcm9tIHRoZSBzYW1wbGUtZGF0YSByZWxhdGlvbnNoaXAgZmlsZQpkYS5ncm91cC5kZiA8LSBlLjY1MzkxLnNkcmZbLCBjKCJTYW1wbGUiLCAiQ2hhcmFjdGVyaXN0aWNzIFtkaXNlYXNlX2FjdGl2aXR5XSIpXQpjb2xuYW1lcyhkYS5ncm91cC5kZilbMl0gPC0gIkRpc2Vhc2UuQWN0aXZpdHkiCmRhLmdyb3VwLmRmIDwtIGRhLmdyb3VwLmRmICU+JQogICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRGlzZWFzZS5BY3Rpdml0eSAhPSAiTm90IEFwcGxpY2FibGUiKQoKIyBwbGFzbWEgY2VsbCBMVgpwbGFzbWEubHYuZGYgPC0gYXMuZGF0YS5mcmFtZShjYmluZChjb2xuYW1lcyhiLm1hdHJpeCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0KGIubWF0cml4W2MoNTIsIDEzNiksIF0pKSkKY29sbmFtZXMocGxhc21hLmx2LmRmKSA8LSBjKCJTYW1wbGUiLCAiTFY1MiIsICJMVjEzNiIpCgojIGpvaW4KcGxhc21hLmRmIDwtIGRwbHlyOjppbm5lcl9qb2luKHBsYXNtYS5sdi5kZiwgZGEuZ3JvdXAuZGYpICU+JQogIGRwbHlyOjptdXRhdGUoRGlzZWFzZS5BY3Rpdml0eSA9IGRwbHlyOjpyZWNvZGUoRGlzZWFzZS5BY3Rpdml0eSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgMWAgPSAiREExIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgMmAgPSAiREEyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGAzYCA9ICJEQTMiKSwKICAgICAgICAgICAgICAgIERpc2Vhc2UuQWN0aXZpdHkgPSBmYWN0b3IoRGlzZWFzZS5BY3Rpdml0eSksCiAgICAgICAgICAgICAgICBMVjUyID0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoTFY1MikpLAogICAgICAgICAgICAgICAgTFYxMzYgPSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihMVjEzNikpKSAKaGVhZChwbGFzbWEuZGYpCmBgYAoKYGBge3J9CiMgd3JpdGUgdG8gZmlsZSwgd2lsbCBiZSB1c2VkIHRvIGNvbXBhcmUgdG8gcmVjb3VudDIgbW9kZWwKcGxhc21hLmZpbGUgPC0gZmlsZS5wYXRoKHJlc3VsdHMuZGlyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICJCYW5jaGVyZWF1X0RBX2dyb3VwX1NMRV9tb2RlbF9wbGFzbWFfY2VsbF9MVnMudHN2IikKcmVhZHI6OndyaXRlX3RzdihwbGFzbWEuZGYsIHBhdGggPSBwbGFzbWEuZmlsZSkKYGBgCgojIyMjIFBsb3R0aW5nCmBgYHtyfQpwbGFzbWEuZGYgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KGdncGxvdDI6OmFlcyh4ID0gRGlzZWFzZS5BY3Rpdml0eSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gTFY1MikpICsKICBnZ3Bsb3QyOjpnZW9tX2JveHBsb3Qobm90Y2ggPSBUUlVFKSArCiAgZ2dwbG90Mjo6dGhlbWVfYncoKSArCiAgZ2dwbG90Mjo6bGFicyh4ID0gIkRpc2Vhc2UgQWN0aXZpdHkiKSArCiAgZ2dwbG90Mjo6dGhlbWUodGV4dCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gMTUpKQpgYGAKCmBgYHtyfQpwbG90LmZpbGUgPC0gZmlsZS5wYXRoKHBsb3QuZGlyLCAiQmFuY2hlcmVhdV9EQV9ncm91cF9MVjUyX2JveHBsb3QucG5nIikKZ2dwbG90Mjo6Z2dzYXZlKGZpbGVuYW1lID0gcGxvdC5maWxlLCBwbG90ID0gZ2dwbG90Mjo6bGFzdF9wbG90KCkpCmBgYAoKYGBge3J9CiMgY2hlY2sgZm9yIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSAKIyAocGFpcndpc2UgdC10ZXN0IGlzIGNvbnNpc3RlbnQgd2l0aCBvcmlnaW5hbCBwdWJsaWNhdGlvbiBhcyBmYXIgYXMgSSBjYW4gdGVsbCkKcGFpcndpc2UudC50ZXN0KHggPSBwbGFzbWEuZGYkTFY1MiwgCiAgICAgICAgICAgICAgICBnID0gcGxhc21hLmRmJERpc2Vhc2UuQWN0aXZpdHksIAogICAgICAgICAgICAgICAgcC5hZGp1c3QubWV0aG9kID0gImJvbmZlcnJvbmkiKQpgYGAKCmBgYHtyfQpwbGFzbWEuZGYgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KGdncGxvdDI6OmFlcyh4ID0gRGlzZWFzZS5BY3Rpdml0eSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gTFYxMzYpKSArCiAgZ2dwbG90Mjo6Z2VvbV9ib3hwbG90KG5vdGNoID0gVFJVRSkgKwogIGdncGxvdDI6OnRoZW1lX2J3KCkgKwogIGdncGxvdDI6OmxhYnMoeCA9ICJEaXNlYXNlIEFjdGl2aXR5IikgKwogIGdncGxvdDI6OnRoZW1lKHRleHQgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDE1KSkKYGBgCgpgYGB7cn0KcGxvdC5maWxlIDwtIGZpbGUucGF0aChwbG90LmRpciwgIkJhbmNoZXJlYXVfREFfZ3JvdXBfTFYxMzZfYm94cGxvdC5wbmciKQpnZ3Bsb3QyOjpnZ3NhdmUoZmlsZW5hbWUgPSBwbG90LmZpbGUsIHBsb3QgPSBnZ3Bsb3QyOjpsYXN0X3Bsb3QoKSkKYGBgCgpgYGB7cn0KIyBjaGVjayBmb3Igc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIAojIChwYWlyd2lzZSB0LXRlc3QgaXMgY29uc2lzdGVudCB3aXRoIG9yaWdpbmFsIHB1YmxpY2F0aW9uIGFzIGZhciBhcyBJIGNhbiB0ZWxsKQpwYWlyd2lzZS50LnRlc3QoeCA9IHBsYXNtYS5kZiRMVjEzNiwgCiAgICAgICAgICAgICAgICBnID0gcGxhc21hLmRmJERpc2Vhc2UuQWN0aXZpdHksIAogICAgICAgICAgICAgICAgcC5hZGp1c3QubWV0aG9kID0gImJvbmZlcnJvbmkiKQpgYGA=