library(VariantAnnotation)
library(openxlsx)
library(tidyverse)
library(RColorBrewer)
library(ggrepel)
library(cowplot)
wd <- here::here()

Load VCF files

VCF files are loaded into a list. Names of the “samples”:

vcf.list <- list()
vcf.filenames <- c("GATK" = "LL.annotated.vcf.gz", "PB" = "pb-LL.annotated.vcf")
for(i in seq_along(vcf.filenames)){
  vcf.dir <- file.path(wd, "VCF")
  vcf.filepath <- file.path(vcf.dir, vcf.filenames[[i]])
  
  vcf.list[[names(vcf.filenames)[[i]]]] <- readVcf(vcf.filepath, "hg38")
}
names(vcf.list)
[1] "GATK" "PB"  

Sub-setting only mutations that passed the initial upstream quality filter and were not unmapped:

vcf.list.qc <-
  lapply(vcf.list, function(x)
    x[rowRanges(x)$FILTER == "PASS"])
vcf.list.qc <-
  lapply(vcf.list.qc, function(x)
    x[!(grepl(names(x), pattern = "chrUn_")),])

lapply(vcf.list.qc, dim)
$GATK
[1] 2605    2

$PB
[1] 3007    2

Get only gene symbol, variant class and type from the functional annotation of the VCF.

vcf.list.anno[[1]] %>% colnames()
[1] "Gencode_43_hugoSymbol"            "Gencode_43_variantClassification" "Gencode_43_variantType"          

Construct a data frame with extracted values.

snv.list <- list()
for(sample.name in names(vcf.list.qc)) {
  vcf <- vcf.list.qc[[sample.name]]
  df.anno <- vcf.list.anno[[sample.name]]
  snv.list[[sample.name]] <- as.data.frame(geno(vcf)[["AF"]]) %>%
    setNames(., c("AF_CTRL", "AF_PDLC")) %>%
    mutate_all( ~ unlist(.)) %>%
    cbind(., df.anno) %>%
    mutate(
      DP = info(vcf)[["DP"]],
      GERMQ = info(vcf)[["GERMQ"]],
      NALOD = info(vcf)[["NALOD"]] %>% unlist(),
      NLOD = info(vcf)[["NLOD"]] %>% unlist(),
      TLOD = info(vcf)[["TLOD"]] %>% unlist()
    ) %>%
    rownames_to_column("Variant") %>%
    mutate(Position_hg38 = word(Variant, 1, sep = "_"),
           .before = "Variant") %>%
    mutate(Variant = word(Variant, 2, sep = "_"))
}
lapply(snv.list, head)
$GATK

$PB
NA

The list of SNV data frames is bound into one data frame with a sample name column.

snv.df <- bind_rows(snv.list, .id = "sample")
head(snv.df)

Now we can compare the samples side by side.

A plot of allelic frequency for all the SNV in the data frame.

We can look for the SNV in a particular gene:

snv.df %>% 
  filter(Gencode_43_hugoSymbol == "TP53")

Variant filters

GATK filter tool provides VCF with columns of data by which the SNV could be further filtered to remove germline variants and sequencing artifacts.

DP is an approximate read depth. We want to remove variants under a certain DP threshold.

GERMQ - The phred-scaled posterior probability that the alternate allele(s) are NOT germline variants.

NALOD (Normal Allele Log Odds) is negative log 10 odds of artifact in normal with same allele fraction as tumor. 5% chance is NALOD ~ 1.3

NLOD (Normal Log Odds) is log odds that the variant is not present in the normal sample

TLOD (Tumor Log Odds) is a log odds ratio that represents the likelihood that the observed tumor allele depth (coverage) is due to a real mutation.

Variant origin filters for base GATK analysis

filters = list(
  "DP" = 30,
  "GERMQ" = 90,
  "NALOD" = -log10(0.05),
  "NLOD" = 6,
  "TLOD" = 30,
  "AF_CTRL" = 0.025,
  "AF_TUMOR" = 0.1
)

snv.list[["GATK"]] %>% 
  ggplot(.,
       aes(y = AF_CTRL, x = DP)) +
  geom_point(aes(color = GERMQ),
             shape = 1, alpha = 0.75,
             size = 1.5, stroke = 1
             ) +
  scale_colour_steps2(
    high = "green",
    midpoint = 50,
    mid = "blue",
    low = "blue",
    breaks = filters[["GERMQ"]]
  ) +
  scale_x_continuous(trans = "log10") +
  scale_y_continuous(labels = scales::percent) +
  geom_hline(yintercept = filters[["AF_CTRL"]], linetype = "dashed") +
  geom_vline(xintercept = filters[["DP"]], linetype = "dashed") +
  labs(y = "CTRL AF, %")


snv.list[["GATK"]] %>% 
  dplyr::filter(
    GERMQ >= filters[["GERMQ"]],
    DP >= filters[["DP"]]
    ) %>% 
  ggplot(.,
       aes(x = TLOD,
           y = NLOD)) +
  geom_density_2d(color = "black") +
  geom_point(
    aes(color = NALOD,
        shape = GERMQ
        ), size = 2, stroke = 1) +
  scale_y_continuous(trans = "log10") +
  scale_x_continuous(trans = "log10") +
  scale_colour_steps2(
    high = "green",
    midpoint = filters[["NALOD"]] / 2,
    mid = "blue",
    low = "blue",
    breaks = filters[["NALOD"]]
  ) +
  scale_shape_binned(breaks = c(filters[["GERMQ"]]), solid = FALSE) +
  geom_hline(yintercept = filters[["NLOD"]], linetype = 2) +
  geom_vline(xintercept = filters[["TLOD"]], linetype = 2)

First plot shows thresholds based on CTRL allelic frequency, sequencing depth and GERMQ score. The SNV which pass these thresholds are shown in the second plot. They are thresholded using NLOD, NALOD and TLOD scores.

Variant origin filters for Parabricks GATK analysis

filters = list(
  "DP" = 30,
  "GERMQ" = 90,
  "NALOD" = -log10(0.05),
  "NLOD" = 6,
  "TLOD" = 30,
  "AF_CTRL" = 0.025,
  "AF_TUMOR" = 0.1
)

snv.list[["PB"]] %>% 
  ggplot(.,
       aes(y = AF_CTRL, x = DP)) +
  geom_point(aes(color = GERMQ),
             shape = 1, alpha = 0.75,
             size = 1.5, stroke = 1
             ) +
  scale_colour_steps2(
    high = "green",
    midpoint = 50,
    mid = "blue",
    low = "blue",
    breaks = filters[["GERMQ"]]
  ) +
  scale_x_continuous(trans = "log10") +
  scale_y_continuous(labels = scales::percent) +
  geom_hline(yintercept = filters[["AF_CTRL"]], linetype = "dashed") +
  geom_vline(xintercept = filters[["DP"]], linetype = "dashed") +
  labs(y = "CTRL AF, %")


snv.list[["PB"]] %>% 
  dplyr::filter(
    GERMQ >= filters[["GERMQ"]],
    DP >= filters[["DP"]]
    ) %>% 
  ggplot(.,
       aes(x = TLOD,
           y = NLOD)) +
  geom_density_2d(color = "black") +
  geom_point(
    aes(color = NALOD,
        shape = GERMQ
        ), size = 2, stroke = 1) +
  scale_y_continuous(trans = "log10") +
  scale_x_continuous(trans = "log10") +
  scale_colour_steps2(
    high = "green",
    midpoint = filters[["NALOD"]] / 2,
    mid = "blue",
    low = "blue",
    breaks = filters[["NALOD"]]
  ) +
  scale_shape_binned(breaks = c(filters[["GERMQ"]]), solid = FALSE) +
  geom_hline(yintercept = filters[["NLOD"]], linetype = 2) +
  geom_vline(xintercept = filters[["TLOD"]], linetype = 2)

First plot shows thresholds based on CTRL allelic frequency, sequencing depth and GERMQ score. The SNV which pass these thresholds are shown in the second plot. They are thresholded using NLOD, NALOD and TLOD scores.

Subsetting the VCF object

Constructing a filter vector for vcf_qc object:

colnames(snv.df)
 [1] "sample"                           "Position_hg38"                   
 [3] "Variant"                          "AF_CTRL"                         
 [5] "AF_PDLC"                          "Gencode_43_hugoSymbol"           
 [7] "Gencode_43_variantClassification" "Gencode_43_variantType"          
 [9] "DP"                               "GERMQ"                           
[11] "NALOD"                            "NLOD"                            
[13] "TLOD"                            

Rather than just filtering, we annotate the SNV based on first reason for filtering out.

snv.filter.df <- snv.df %>%
  mutate(
    second_filter = case_when(
      DP <= filters[["DP"]] ~ "low_DP",
      GERMQ <= filters[["GERMQ"]] ~ "low_GERMQ",
      AF_CTRL > filters[["AF_CTRL"]] ~ "high_CTRL_AF",
      AF_PDLC < filters[["AF_TUMOR"]] ~ "low_Tumor_AF",
      NALOD < filters[["NALOD"]] ~   "low_NALOD",
      NLOD < filters[["NLOD"]] ~     "low_NLOD",
      TLOD < filters[["TLOD"]] ~     "low_TLOD",
      TRUE ~ "PASS"
    )
  )

snv.filter.df %>% 
  filter(second_filter == "PASS")

Again, we can check for the presence of a specific mutation in the dataset:

snv.filter.df %>% 
  filter(Gencode_43_hugoSymbol == "TP53")
c(
  "low_DP" = "red",
  "low_GERMQ" = "orange",
  "low_NALOD" = "purple",
  "low_NLOD" = "violet",
  "low_TLOD" = "firebrick",
  "high_CTRL_AF" = "coral2",
  "low_Tumor_AF" = "blueviolet",
  "PASS" = "green"
) -> pal.colors

snv.filter.df %>%
  pivot_longer(cols = contains("AF"), values_to = "AF") %>%
  ggplot(aes(x = name, y = AF)) +
  ggbeeswarm::geom_quasirandom(aes(color = second_filter)) +
  geom_hline(yintercept = filters[["AF_CTRL"]], linetype = "dashed") +
  theme_bw() + labs(x = NULL) +
  scale_color_manual(values = pal.colors) +
  scale_y_continuous(labels = scales::percent) +
  facet_wrap(~ sample)

Shawarma plots of allelic frequencies for all mutations (including filtered out).

snv.filter.df %>%
  filter(second_filter == "PASS") %>% 
  pivot_longer(cols = contains("AF"), values_to = "AF") %>%
  ggplot(aes(x = name, y = AF)) +
  ggbeeswarm::geom_quasirandom(aes(color = second_filter)) +
  geom_hline(yintercept = filters[["AF_CTRL"]], linetype = "dashed") +
  theme_bw() + labs(x = NULL) +
  scale_color_manual(values = pal.colors) +
  scale_y_continuous(labels = scales::percent) +
  facet_wrap(~ sample)

Same plot but only SNV which passed the filters.

Data frame of all filtered SNVs:

snv.filter.df.wide <- snv.filter.df %>%
  filter(second_filter == "PASS") %>% 
  dplyr::select(sample, Position_hg38, Variant, AF_PDLC,
         Gencode_43_hugoSymbol, Gencode_43_variantClassification) %>% 
  pivot_wider(names_from = "sample", values_from = "AF_PDLC")
snv.filter.df.wide

Data frame of missense filtered SNVs:

snv.filter.df.wide %>% 
  filter(Gencode_43_variantClassification == "MISSENSE")

NB: Not all mutations detected by PB-GATK were detected by GATK!

dot_position = position_jitter(height = 0,
                               width = 0.25,
                               seed = 413)

snv.filter.df %>%
  filter(second_filter == "PASS") %>% 
  
  ggplot() +
  lemon::geom_pointline(
    aes(
      x = sample,
      y = AF_PDLC,
      group = Position_hg38,
      fill = Gencode_43_variantClassification
    ),
    linecolor = "gray30",
    linetype = 3,
    position = dot_position,
    shape = 21,
    size = 2
  ) +
  geom_point(
    aes(
      x = sample,
      y = AF_PDLC,
      group = Position_hg38,
      fill = Gencode_43_variantClassification
    ),
    position = dot_position,
    shape = 21,
    size = 2
  ) +
  geom_hline(yintercept = 0.05, linetype = "dashed") +
  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
  theme_bw() +
  labs(x = NULL, y = "Allelic frequency",
       fill = "SNV type")

Visual comparison of allelic frequencies for both runs. Only filtered SNV present in both samples are plotted.

dot_position = position_jitter(height = 0,
                               width = 0.25,
                               seed = 413)

snv.filter.df %>%
  filter(second_filter == "PASS") %>%
  filter(Gencode_43_variantClassification == "MISSENSE") %>%
  ggplot() +
  lemon::geom_pointline(
    aes(x = sample,
        y = AF_PDLC,
        group = Position_hg38),
    linecolor = "gray30",
    linetype = 3,
    position = dot_position,
    shape = 21,
    size = 2,
    fill = "gray90"
  ) +
  
  geom_hline(yintercept = 0.05, linetype = "dashed") +
  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
  theme_bw() +
  labs(x = NULL, y = "Allelic frequency",
       fill = "SNV type")

Visual comparison of allelic frequencies for missence mutations present in both samples.

Save the variant table

output_dir <- "~/test_data/SNVcalls/VCF"

output_file_xlsx <- paste0("Filtered_SNV.xlsx")
output_path_xlsx <- file.path(output_dir, output_file_xlsx)

list(
  "Total SNV" = snv.filter.df,
  "Filtered SNV" = snv.filter.df %>% 
    filter(second_filter == "PASS") %>% 
    dplyr::select(-second_filter)
) %>% 
  write.xlsx(., output_path_xlsx, overwrite = T)

Session info

sessionInfo()
R version 4.3.1 (2023-06-16)
Platform: x86_64-conda-linux-gnu (64-bit)
Running under: Ubuntu 22.04.3 LTS

Matrix products: default
BLAS/LAPACK: /home/bench-user/.apps/conda/lib/libopenblasp-r0.3.25.so;  LAPACK version 3.11.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8       
 [4] LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
[10] LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

time zone: Etc/UTC
tzcode source: system (glibc)

attached base packages:
[1] stats4    stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] cowplot_1.1.2               ggrepel_0.9.4               RColorBrewer_1.1-3         
 [4] lubridate_1.9.3             forcats_1.0.0               stringr_1.5.1              
 [7] dplyr_1.1.4                 purrr_1.0.2                 readr_2.1.4                
[10] tidyr_1.3.0                 tibble_3.2.1                ggplot2_3.4.4              
[13] tidyverse_2.0.0             openxlsx_4.2.5.2            VariantAnnotation_1.48.1   
[16] Rsamtools_2.18.0            Biostrings_2.70.1           XVector_0.42.0             
[19] SummarizedExperiment_1.32.0 Biobase_2.62.0              GenomicRanges_1.54.1       
[22] GenomeInfoDb_1.38.1         IRanges_2.36.0              S4Vectors_0.40.2           
[25] MatrixGenerics_1.14.0       matrixStats_1.2.0           BiocGenerics_0.48.1        

loaded via a namespace (and not attached):
 [1] DBI_1.2.0                bitops_1.0-7             gridExtra_2.3            biomaRt_2.58.0          
 [5] rlang_1.1.2              magrittr_2.0.3           compiler_4.3.1           RSQLite_2.3.4           
 [9] GenomicFeatures_1.54.1   png_0.1-8                vctrs_0.6.5              pkgconfig_2.0.3         
[13] crayon_1.5.2             fastmap_1.1.1            dbplyr_2.4.0             lemon_0.4.7             
[17] labeling_0.4.3           utf8_1.2.4               rmarkdown_2.25           tzdb_0.4.0              
[21] ggbeeswarm_0.7.2         bit_4.0.5                xfun_0.41                zlibbioc_1.48.0         
[25] cachem_1.0.8             progress_1.2.3           blob_1.2.4               DelayedArray_0.28.0     
[29] BiocParallel_1.36.0      parallel_4.3.1           prettyunits_1.2.0        R6_2.5.1                
[33] stringi_1.8.3            rtracklayer_1.62.0       Rcpp_1.0.11              knitr_1.45              
[37] timechange_0.2.0         Matrix_1.6-4             tidyselect_1.2.0         rstudioapi_0.15.0       
[41] abind_1.4-5              yaml_2.3.8               codetools_0.2-19         curl_5.1.0              
[45] lattice_0.22-5           plyr_1.8.9               withr_2.5.2              KEGGREST_1.42.0         
[49] evaluate_0.23            isoband_0.2.7            BiocFileCache_2.10.1     zip_2.3.0               
[53] xml2_1.3.6               pillar_1.9.0             filelock_1.0.3           generics_0.1.3          
[57] rprojroot_2.0.4          RCurl_1.98-1.13          hms_1.1.3                munsell_0.5.0           
[61] scales_1.3.0             glue_1.6.2               tools_4.3.1              BiocIO_1.12.0           
[65] BSgenome_1.70.1          GenomicAlignments_1.38.0 XML_3.99-0.16            grid_4.3.1              
[69] AnnotationDbi_1.64.1     colorspace_2.1-0         GenomeInfoDbData_1.2.11  beeswarm_0.4.0          
[73] restfulr_0.0.15          vipor_0.4.7              cli_3.6.2                rappdirs_0.3.3          
[77] fansi_1.0.6              S4Arrays_1.2.0           gtable_0.3.4             digest_0.6.33           
[81] SparseArray_1.2.2        farver_2.1.1             rjson_0.2.21             memoise_2.0.1           
[85] htmltools_0.5.7          lifecycle_1.0.4          httr_1.4.7               here_1.0.1              
[89] MASS_7.3-60              bit64_4.0.5             
LS0tCnRpdGxlOiAiUUMgYW5kIGZpbHRlcmluZyBvZiBhbm5vdGF0ZWQgVkNGIGZpbGVzIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0b2NfZGVwdGg6IDMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdGhlbWU6IHVuaXRlZAotLS0KCmBgYHtyIHNldHVwfQpsaWJyYXJ5KFZhcmlhbnRBbm5vdGF0aW9uKQpsaWJyYXJ5KG9wZW54bHN4KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShjb3dwbG90KQp3ZCA8LSBoZXJlOjpoZXJlKCkKYGBgCgojIExvYWQgVkNGIGZpbGVzCgpWQ0YgZmlsZXMgYXJlIGxvYWRlZCBpbnRvIGEgbGlzdC4gTmFtZXMgb2YgdGhlICJzYW1wbGVzIjoKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CnZjZi5saXN0IDwtIGxpc3QoKQp2Y2YuZmlsZW5hbWVzIDwtIGMoIkdBVEsiID0gIkxMLmFubm90YXRlZC52Y2YuZ3oiLCAiUEIiID0gInBiLUxMLmFubm90YXRlZC52Y2YiKQpmb3IoaSBpbiBzZXFfYWxvbmcodmNmLmZpbGVuYW1lcykpewogIHZjZi5kaXIgPC0gZmlsZS5wYXRoKHdkLCAiVkNGIikKICB2Y2YuZmlsZXBhdGggPC0gZmlsZS5wYXRoKHZjZi5kaXIsIHZjZi5maWxlbmFtZXNbW2ldXSkKICAKICB2Y2YubGlzdFtbbmFtZXModmNmLmZpbGVuYW1lcylbW2ldXV1dIDwtIHJlYWRWY2YodmNmLmZpbGVwYXRoLCAiaGczOCIpCn0KbmFtZXModmNmLmxpc3QpCmBgYAoKU3ViLXNldHRpbmcgb25seSBtdXRhdGlvbnMgdGhhdCBwYXNzZWQgdGhlIGluaXRpYWwgdXBzdHJlYW0gcXVhbGl0eSBmaWx0ZXIgYW5kIHdlcmUgbm90IHVubWFwcGVkOgoKYGBge3J9CnZjZi5saXN0LnFjIDwtCiAgbGFwcGx5KHZjZi5saXN0LCBmdW5jdGlvbih4KQogICAgeFtyb3dSYW5nZXMoeCkkRklMVEVSID09ICJQQVNTIl0pCnZjZi5saXN0LnFjIDwtCiAgbGFwcGx5KHZjZi5saXN0LnFjLCBmdW5jdGlvbih4KQogICAgeFshKGdyZXBsKG5hbWVzKHgpLCBwYXR0ZXJuID0gImNoclVuXyIpKSxdKQoKbGFwcGx5KHZjZi5saXN0LnFjLCBkaW0pCmBgYAoKR2V0IG9ubHkgZ2VuZSBzeW1ib2wsIHZhcmlhbnQgY2xhc3MgYW5kIHR5cGUgZnJvbSB0aGUgZnVuY3Rpb25hbCBhbm5vdGF0aW9uIG9mIHRoZSBWQ0YuCgpgYGB7cn0KZXh0cmFjdEZVTkNPVEFUSU9OIDwtCiAgZnVuY3Rpb24odmNmLAogICAgICAgICAgIGZpZWxkcyA9IGMoCiAgICAgICAgICAgICAiR2VuY29kZV80M19odWdvU3ltYm9sIiwKICAgICAgICAgICAgICJHZW5jb2RlXzQzX3ZhcmlhbnRDbGFzc2lmaWNhdGlvbiIsCiAgICAgICAgICAgICAiR2VuY29kZV80M192YXJpYW50VHlwZSIKICAgICAgICAgICApKSB7CiAgICBhbm5vdGF0aW9uX2NvbG5hbWVzIDwtCiAgICAgIGluZm8odmNmQG1ldGFkYXRhJGhlYWRlcilbIkZVTkNPVEFUSU9OIiwgIkRlc2NyaXB0aW9uIl0gJT4lCiAgICAgIHN0cmluZ3I6OnN0cl9yZW1vdmUoIkZ1bmN0aW9uYWwgYW5ub3RhdGlvbiBmcm9tIHRoZSBGdW5jb3RhdG9yIHRvb2wuICBGdW5jb3RhdGlvbiBmaWVsZHMgYXJlOiAiKSAlPiUKICAgICAgc3RyaW5ncjo6c3RyX3NwbGl0KHBhdHRlcm4gPSAiXFx8IikgJT4lCiAgICAgIHVubGlzdCgpCiAgICAKICAgIGFzLmRhdGEuZnJhbWUoaW5mbyh2Y2YpW1siRlVOQ09UQVRJT04iXV0pICU+JQogICAgICBkcGx5cjo6c2VsZWN0KCJGVU5DT1RBVElPTiIgPSB2YWx1ZSkgJT4lCiAgICAgIGRwbHlyOjptdXRhdGUoCiAgICAgICAgRlVOQ09UQVRJT04gPSB3b3JkKEZVTkNPVEFUSU9OLCAxLCBzZXAgPSAiXFxdIiksCiAgICAgICAgRlVOQ09UQVRJT04gPSBzdHJfcmVtb3ZlX2FsbChGVU5DT1RBVElPTiwgIltcXFtcXF1dIikKICAgICAgKSAlPiUKICAgICAgdGlkeXI6OnNlcGFyYXRlKEZVTkNPVEFUSU9OLAogICAgICAgICAgICAgICAgICAgICAgaW50byA9IGFubm90YXRpb25fY29sbmFtZXMsCiAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXFx8IiwKICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSAicmlnaHQiKSAlPiUKICAgICAgZHBseXI6OnNlbGVjdChhbGxfb2YoZmllbGRzKSkKICB9Cgp2Y2YubGlzdC5hbm5vIDwtIGxhcHBseSh2Y2YubGlzdC5xYywgZnVuY3Rpb24oeCkgZXh0cmFjdEZVTkNPVEFUSU9OKHgpKQoKdmNmLmxpc3QuYW5ub1tbMV1dICU+JSBjb2xuYW1lcygpCmBgYAoKQ29uc3RydWN0IGEgZGF0YSBmcmFtZSB3aXRoIGV4dHJhY3RlZCB2YWx1ZXMuCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NX0Kc252Lmxpc3QgPC0gbGlzdCgpCmZvcihzYW1wbGUubmFtZSBpbiBuYW1lcyh2Y2YubGlzdC5xYykpIHsKICB2Y2YgPC0gdmNmLmxpc3QucWNbW3NhbXBsZS5uYW1lXV0KICBkZi5hbm5vIDwtIHZjZi5saXN0LmFubm9bW3NhbXBsZS5uYW1lXV0KICBzbnYubGlzdFtbc2FtcGxlLm5hbWVdXSA8LSBhcy5kYXRhLmZyYW1lKGdlbm8odmNmKVtbIkFGIl1dKSAlPiUKICAgIAogICAgIyBzaW5jZSB0aGUgYmlvc2FtcGxlIGlzIHRoZSBzYW1lLCB3ZSByZW5hbWUgaXRzIEFGIGZpZWxkIGFzIFBEQ0wgLSBwYXRpZW50IGRlcml2ZWQgY2VsbCBsaW5lCiAgICBzZXROYW1lcyguLCBjKCJBRl9DVFJMIiwgIkFGX1BETEMiKSkgJT4lCiAgICAKICAgIG11dGF0ZV9hbGwoIH4gdW5saXN0KC4pKSAlPiUKICAgIGNiaW5kKC4sIGRmLmFubm8pICU+JQogICAgbXV0YXRlKAogICAgICBEUCA9IGluZm8odmNmKVtbIkRQIl1dLAogICAgICBHRVJNUSA9IGluZm8odmNmKVtbIkdFUk1RIl1dLAogICAgICBOQUxPRCA9IGluZm8odmNmKVtbIk5BTE9EIl1dICU+JSB1bmxpc3QoKSwKICAgICAgTkxPRCA9IGluZm8odmNmKVtbIk5MT0QiXV0gJT4lIHVubGlzdCgpLAogICAgICBUTE9EID0gaW5mbyh2Y2YpW1siVExPRCJdXSAlPiUgdW5saXN0KCkKICAgICkgJT4lCiAgICByb3duYW1lc190b19jb2x1bW4oIlZhcmlhbnQiKSAlPiUKICAgIG11dGF0ZShQb3NpdGlvbl9oZzM4ID0gd29yZChWYXJpYW50LCAxLCBzZXAgPSAiXyIpLAogICAgICAgICAgIC5iZWZvcmUgPSAiVmFyaWFudCIpICU+JQogICAgbXV0YXRlKFZhcmlhbnQgPSB3b3JkKFZhcmlhbnQsIDIsIHNlcCA9ICJfIikpCn0KbGFwcGx5KHNudi5saXN0LCBoZWFkKQpgYGAKClRoZSBsaXN0IG9mIFNOViBkYXRhIGZyYW1lcyBpcyBib3VuZCBpbnRvIG9uZSBkYXRhIGZyYW1lIHdpdGggYSBzYW1wbGUgbmFtZSBjb2x1bW4uCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0Kc252LmRmIDwtIGJpbmRfcm93cyhzbnYubGlzdCwgLmlkID0gInNhbXBsZSIpCmhlYWQoc252LmRmKQpgYGAKCk5vdyB3ZSBjYW4gY29tcGFyZSB0aGUgc2FtcGxlcyBzaWRlIGJ5IHNpZGUuCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KZ2dwbG90KHNudi5kZiwKICAgICAgIGFlcyh5ID0gQUZfUERMQywKICAgICAgICAgICB4ID0gc2FtcGxlKSkgKwogIGdlb21faml0dGVyKGFlcyhjb2xvciA9IEdlbmNvZGVfNDNfdmFyaWFudENsYXNzaWZpY2F0aW9uKSkgKwogIHlsaW0oMCwgMSkgKwogIHRoZW1lX2J3KCkKYGBgCgpBIHBsb3Qgb2YgYWxsZWxpYyBmcmVxdWVuY3kgZm9yIGFsbCB0aGUgU05WIGluIHRoZSBkYXRhIGZyYW1lLgogIApXZSBjYW4gbG9vayBmb3IgdGhlIFNOViBpbiBhIHBhcnRpY3VsYXIgZ2VuZToKCmBgYHtyfQpzbnYuZGYgJT4lIAogIGZpbHRlcihHZW5jb2RlXzQzX2h1Z29TeW1ib2wgPT0gIlRQNTMiKQpgYGAKCiMgVmFyaWFudCBmaWx0ZXJzCgpHQVRLIGZpbHRlciB0b29sIHByb3ZpZGVzIFZDRiB3aXRoIGNvbHVtbnMgb2YgZGF0YSBieSB3aGljaCB0aGUgU05WIGNvdWxkIGJlIGZ1cnRoZXIgZmlsdGVyZWQgdG8gcmVtb3ZlIGdlcm1saW5lIHZhcmlhbnRzIGFuZCBzZXF1ZW5jaW5nIGFydGlmYWN0cy4KCioqRFAqKiBpcyBhbiBhcHByb3hpbWF0ZSByZWFkIGRlcHRoLiBXZSB3YW50IHRvIHJlbW92ZSB2YXJpYW50cyB1bmRlciBhIGNlcnRhaW4gRFAgdGhyZXNob2xkLgoKKipHRVJNUSoqIC0gVGhlIHBocmVkLXNjYWxlZCBwb3N0ZXJpb3IgcHJvYmFiaWxpdHkgdGhhdCB0aGUgYWx0ZXJuYXRlIGFsbGVsZShzKSBhcmUgTk9UIGdlcm1saW5lIHZhcmlhbnRzLgoKKipOQUxPRCoqIChOb3JtYWwgQWxsZWxlIExvZyBPZGRzKSBpcyBuZWdhdGl2ZSBsb2cgMTAgb2RkcyBvZiBhcnRpZmFjdCBpbiBub3JtYWwgd2l0aCBzYW1lIGFsbGVsZSBmcmFjdGlvbiBhcyB0dW1vci4KNSUgY2hhbmNlIGlzIE5BTE9EIH4gMS4zCgoqKk5MT0QqKiAoTm9ybWFsIExvZyBPZGRzKSBpcyBsb2cgb2RkcyB0aGF0IHRoZSB2YXJpYW50IGlzIG5vdCBwcmVzZW50IGluIHRoZSBub3JtYWwgc2FtcGxlCgoqKlRMT0QqKiAoVHVtb3IgTG9nIE9kZHMpIGlzIGEgbG9nIG9kZHMgcmF0aW8gdGhhdCByZXByZXNlbnRzIHRoZSBsaWtlbGlob29kIHRoYXQgdGhlIG9ic2VydmVkIHR1bW9yIGFsbGVsZSBkZXB0aCAoY292ZXJhZ2UpIGlzIGR1ZSB0byBhIHJlYWwgbXV0YXRpb24uCgojIyBWYXJpYW50IG9yaWdpbiBmaWx0ZXJzIGZvciBiYXNlIEdBVEsgYW5hbHlzaXMKCmBgYHtyLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD01fQpmaWx0ZXJzID0gbGlzdCgKICAiRFAiID0gMzAsCiAgIkdFUk1RIiA9IDkwLAogICJOQUxPRCIgPSAtbG9nMTAoMC4wNSksCiAgIk5MT0QiID0gNiwKICAiVExPRCIgPSAzMCwKICAiQUZfQ1RSTCIgPSAwLjAyNSwKICAiQUZfVFVNT1IiID0gMC4xCikKCnNudi5saXN0W1siR0FUSyJdXSAlPiUgCiAgZ2dwbG90KC4sCiAgICAgICBhZXMoeSA9IEFGX0NUUkwsIHggPSBEUCkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IEdFUk1RKSwKICAgICAgICAgICAgIHNoYXBlID0gMSwgYWxwaGEgPSAwLjc1LAogICAgICAgICAgICAgc2l6ZSA9IDEuNSwgc3Ryb2tlID0gMQogICAgICAgICAgICAgKSArCiAgc2NhbGVfY29sb3VyX3N0ZXBzMigKICAgIGhpZ2ggPSAiZ3JlZW4iLAogICAgbWlkcG9pbnQgPSA1MCwKICAgIG1pZCA9ICJibHVlIiwKICAgIGxvdyA9ICJibHVlIiwKICAgIGJyZWFrcyA9IGZpbHRlcnNbWyJHRVJNUSJdXQogICkgKwogIHNjYWxlX3hfY29udGludW91cyh0cmFucyA9ICJsb2cxMCIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gZmlsdGVyc1tbIkFGX0NUUkwiXV0sIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBmaWx0ZXJzW1siRFAiXV0sIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKHkgPSAiQ1RSTCBBRiwgJSIpCgpzbnYubGlzdFtbIkdBVEsiXV0gJT4lIAogIGRwbHlyOjpmaWx0ZXIoCiAgICBHRVJNUSA+PSBmaWx0ZXJzW1siR0VSTVEiXV0sCiAgICBEUCA+PSBmaWx0ZXJzW1siRFAiXV0KICAgICkgJT4lIAogIGdncGxvdCguLAogICAgICAgYWVzKHggPSBUTE9ELAogICAgICAgICAgIHkgPSBOTE9EKSkgKwogIGdlb21fZGVuc2l0eV8yZChjb2xvciA9ICJibGFjayIpICsKICBnZW9tX3BvaW50KAogICAgYWVzKGNvbG9yID0gTkFMT0QsCiAgICAgICAgc2hhcGUgPSBHRVJNUQogICAgICAgICksIHNpemUgPSAyLCBzdHJva2UgPSAxKSArCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIikgKwogIHNjYWxlX3hfY29udGludW91cyh0cmFucyA9ICJsb2cxMCIpICsKICBzY2FsZV9jb2xvdXJfc3RlcHMyKAogICAgaGlnaCA9ICJncmVlbiIsCiAgICBtaWRwb2ludCA9IGZpbHRlcnNbWyJOQUxPRCJdXSAvIDIsCiAgICBtaWQgPSAiYmx1ZSIsCiAgICBsb3cgPSAiYmx1ZSIsCiAgICBicmVha3MgPSBmaWx0ZXJzW1siTkFMT0QiXV0KICApICsKICBzY2FsZV9zaGFwZV9iaW5uZWQoYnJlYWtzID0gYyhmaWx0ZXJzW1siR0VSTVEiXV0pLCBzb2xpZCA9IEZBTFNFKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gZmlsdGVyc1tbIk5MT0QiXV0sIGxpbmV0eXBlID0gMikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGZpbHRlcnNbWyJUTE9EIl1dLCBsaW5ldHlwZSA9IDIpCmBgYAoKRmlyc3QgcGxvdCBzaG93cyB0aHJlc2hvbGRzIGJhc2VkIG9uIENUUkwgYWxsZWxpYyBmcmVxdWVuY3ksIHNlcXVlbmNpbmcgZGVwdGggYW5kIEdFUk1RIHNjb3JlLgpUaGUgU05WIHdoaWNoIHBhc3MgdGhlc2UgdGhyZXNob2xkcyBhcmUgc2hvd24gaW4gdGhlIHNlY29uZCBwbG90LiBUaGV5IGFyZSB0aHJlc2hvbGRlZCB1c2luZyBOTE9ELCBOQUxPRCBhbmQgVExPRCBzY29yZXMuCgojIyBWYXJpYW50IG9yaWdpbiBmaWx0ZXJzIGZvciBQYXJhYnJpY2tzIEdBVEsgYW5hbHlzaXMKCmBgYHtyLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD01fQpmaWx0ZXJzID0gbGlzdCgKICAiRFAiID0gMzAsCiAgIkdFUk1RIiA9IDkwLAogICJOQUxPRCIgPSAtbG9nMTAoMC4wNSksCiAgIk5MT0QiID0gNiwKICAiVExPRCIgPSAzMCwKICAiQUZfQ1RSTCIgPSAwLjAyNSwKICAiQUZfVFVNT1IiID0gMC4xCikKCnNudi5saXN0W1siUEIiXV0gJT4lIAogIGdncGxvdCguLAogICAgICAgYWVzKHkgPSBBRl9DVFJMLCB4ID0gRFApKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBHRVJNUSksCiAgICAgICAgICAgICBzaGFwZSA9IDEsIGFscGhhID0gMC43NSwKICAgICAgICAgICAgIHNpemUgPSAxLjUsIHN0cm9rZSA9IDEKICAgICAgICAgICAgICkgKwogIHNjYWxlX2NvbG91cl9zdGVwczIoCiAgICBoaWdoID0gImdyZWVuIiwKICAgIG1pZHBvaW50ID0gNTAsCiAgICBtaWQgPSAiYmx1ZSIsCiAgICBsb3cgPSAiYmx1ZSIsCiAgICBicmVha3MgPSBmaWx0ZXJzW1siR0VSTVEiXV0KICApICsKICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMTAiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IGZpbHRlcnNbWyJBRl9DVFJMIl1dLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gZmlsdGVyc1tbIkRQIl1dLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicyh5ID0gIkNUUkwgQUYsICUiKQoKc252Lmxpc3RbWyJQQiJdXSAlPiUgCiAgZHBseXI6OmZpbHRlcigKICAgIEdFUk1RID49IGZpbHRlcnNbWyJHRVJNUSJdXSwKICAgIERQID49IGZpbHRlcnNbWyJEUCJdXQogICAgKSAlPiUgCiAgZ2dwbG90KC4sCiAgICAgICBhZXMoeCA9IFRMT0QsCiAgICAgICAgICAgeSA9IE5MT0QpKSArCiAgZ2VvbV9kZW5zaXR5XzJkKGNvbG9yID0gImJsYWNrIikgKwogIGdlb21fcG9pbnQoCiAgICBhZXMoY29sb3IgPSBOQUxPRCwKICAgICAgICBzaGFwZSA9IEdFUk1RCiAgICAgICAgKSwgc2l6ZSA9IDIsIHN0cm9rZSA9IDEpICsKICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMTAiKSArCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIikgKwogIHNjYWxlX2NvbG91cl9zdGVwczIoCiAgICBoaWdoID0gImdyZWVuIiwKICAgIG1pZHBvaW50ID0gZmlsdGVyc1tbIk5BTE9EIl1dIC8gMiwKICAgIG1pZCA9ICJibHVlIiwKICAgIGxvdyA9ICJibHVlIiwKICAgIGJyZWFrcyA9IGZpbHRlcnNbWyJOQUxPRCJdXQogICkgKwogIHNjYWxlX3NoYXBlX2Jpbm5lZChicmVha3MgPSBjKGZpbHRlcnNbWyJHRVJNUSJdXSksIHNvbGlkID0gRkFMU0UpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBmaWx0ZXJzW1siTkxPRCJdXSwgbGluZXR5cGUgPSAyKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gZmlsdGVyc1tbIlRMT0QiXV0sIGxpbmV0eXBlID0gMikKYGBgCgpGaXJzdCBwbG90IHNob3dzIHRocmVzaG9sZHMgYmFzZWQgb24gQ1RSTCBhbGxlbGljIGZyZXF1ZW5jeSwgc2VxdWVuY2luZyBkZXB0aCBhbmQgR0VSTVEgc2NvcmUuClRoZSBTTlYgd2hpY2ggcGFzcyB0aGVzZSB0aHJlc2hvbGRzIGFyZSBzaG93biBpbiB0aGUgc2Vjb25kIHBsb3QuIFRoZXkgYXJlIHRocmVzaG9sZGVkIHVzaW5nIE5MT0QsIE5BTE9EIGFuZCBUTE9EIHNjb3Jlcy4KCiMgU3Vic2V0dGluZyB0aGUgVkNGIG9iamVjdAoKQ29uc3RydWN0aW5nIGEgZmlsdGVyIHZlY3RvciBmb3IgYHZjZl9xY2Agb2JqZWN0OgoKYGBge3J9CmNvbG5hbWVzKHNudi5kZikKYGBgCgpSYXRoZXIgdGhhbiBqdXN0IGZpbHRlcmluZywgd2UgYW5ub3RhdGUgdGhlIFNOViBiYXNlZCBvbiBmaXJzdCByZWFzb24gZm9yIGZpbHRlcmluZyBvdXQuCgpgYGB7cn0Kc252LmZpbHRlci5kZiA8LSBzbnYuZGYgJT4lCiAgbXV0YXRlKAogICAgc2Vjb25kX2ZpbHRlciA9IGNhc2Vfd2hlbigKICAgICAgRFAgPD0gZmlsdGVyc1tbIkRQIl1dIH4gImxvd19EUCIsCiAgICAgIEdFUk1RIDw9IGZpbHRlcnNbWyJHRVJNUSJdXSB+ICJsb3dfR0VSTVEiLAogICAgICBBRl9DVFJMID4gZmlsdGVyc1tbIkFGX0NUUkwiXV0gfiAiaGlnaF9DVFJMX0FGIiwKICAgICAgQUZfUERMQyA8IGZpbHRlcnNbWyJBRl9UVU1PUiJdXSB+ICJsb3dfVHVtb3JfQUYiLAogICAgICBOQUxPRCA8IGZpbHRlcnNbWyJOQUxPRCJdXSB+ICAgImxvd19OQUxPRCIsCiAgICAgIE5MT0QgPCBmaWx0ZXJzW1siTkxPRCJdXSB+ICAgICAibG93X05MT0QiLAogICAgICBUTE9EIDwgZmlsdGVyc1tbIlRMT0QiXV0gfiAgICAgImxvd19UTE9EIiwKICAgICAgVFJVRSB+ICJQQVNTIgogICAgKQogICkKCnNudi5maWx0ZXIuZGYgJT4lIAogIGZpbHRlcihzZWNvbmRfZmlsdGVyID09ICJQQVNTIikKYGBgCgpBZ2Fpbiwgd2UgY2FuIGNoZWNrIGZvciB0aGUgcHJlc2VuY2Ugb2YgYSBzcGVjaWZpYyBtdXRhdGlvbiBpbiB0aGUgZGF0YXNldDoKCmBgYHtyfQpzbnYuZmlsdGVyLmRmICU+JSAKICBmaWx0ZXIoR2VuY29kZV80M19odWdvU3ltYm9sID09ICJUUDUzIikKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NX0KYygKICAibG93X0RQIiA9ICJyZWQiLAogICJsb3dfR0VSTVEiID0gIm9yYW5nZSIsCiAgImxvd19OQUxPRCIgPSAicHVycGxlIiwKICAibG93X05MT0QiID0gInZpb2xldCIsCiAgImxvd19UTE9EIiA9ICJmaXJlYnJpY2siLAogICJoaWdoX0NUUkxfQUYiID0gImNvcmFsMiIsCiAgImxvd19UdW1vcl9BRiIgPSAiYmx1ZXZpb2xldCIsCiAgIlBBU1MiID0gImdyZWVuIgopIC0+IHBhbC5jb2xvcnMKCnNudi5maWx0ZXIuZGYgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjb250YWlucygiQUYiKSwgdmFsdWVzX3RvID0gIkFGIikgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbmFtZSwgeSA9IEFGKSkgKwogIGdnYmVlc3dhcm06Omdlb21fcXVhc2lyYW5kb20oYWVzKGNvbG9yID0gc2Vjb25kX2ZpbHRlcikpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBmaWx0ZXJzW1siQUZfQ1RSTCJdXSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIHRoZW1lX2J3KCkgKyBsYWJzKHggPSBOVUxMKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbC5jb2xvcnMpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArCiAgZmFjZXRfd3JhcCh+IHNhbXBsZSkKYGBgCgpTaGF3YXJtYSBwbG90cyBvZiBhbGxlbGljIGZyZXF1ZW5jaWVzIGZvciBhbGwgbXV0YXRpb25zIChpbmNsdWRpbmcgZmlsdGVyZWQgb3V0KS4KCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD01fQpzbnYuZmlsdGVyLmRmICU+JQogIGZpbHRlcihzZWNvbmRfZmlsdGVyID09ICJQQVNTIikgJT4lIAogIHBpdm90X2xvbmdlcihjb2xzID0gY29udGFpbnMoIkFGIiksIHZhbHVlc190byA9ICJBRiIpICU+JQogIGdncGxvdChhZXMoeCA9IG5hbWUsIHkgPSBBRikpICsKICBnZ2JlZXN3YXJtOjpnZW9tX3F1YXNpcmFuZG9tKGFlcyhjb2xvciA9IHNlY29uZF9maWx0ZXIpKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gZmlsdGVyc1tbIkFGX0NUUkwiXV0sIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICB0aGVtZV9idygpICsgbGFicyh4ID0gTlVMTCkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBwYWwuY29sb3JzKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogIGZhY2V0X3dyYXAofiBzYW1wbGUpCmBgYAoKU2FtZSBwbG90IGJ1dCBvbmx5IFNOViB3aGljaCBwYXNzZWQgdGhlIGZpbHRlcnMuCgpEYXRhIGZyYW1lIG9mIGFsbCBmaWx0ZXJlZCBTTlZzOgoKYGBge3J9CnNudi5maWx0ZXIuZGYud2lkZSA8LSBzbnYuZmlsdGVyLmRmICU+JQogIGZpbHRlcihzZWNvbmRfZmlsdGVyID09ICJQQVNTIikgJT4lIAogIGRwbHlyOjpzZWxlY3Qoc2FtcGxlLCBQb3NpdGlvbl9oZzM4LCBWYXJpYW50LCBBRl9QRExDLAogICAgICAgICBHZW5jb2RlXzQzX2h1Z29TeW1ib2wsIEdlbmNvZGVfNDNfdmFyaWFudENsYXNzaWZpY2F0aW9uKSAlPiUgCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9ICJzYW1wbGUiLCB2YWx1ZXNfZnJvbSA9ICJBRl9QRExDIikKc252LmZpbHRlci5kZi53aWRlCmBgYAoKRGF0YSBmcmFtZSBvZiBtaXNzZW5zZSBmaWx0ZXJlZCBTTlZzOgoKYGBge3J9CnNudi5maWx0ZXIuZGYud2lkZSAlPiUgCiAgZmlsdGVyKEdlbmNvZGVfNDNfdmFyaWFudENsYXNzaWZpY2F0aW9uID09ICJNSVNTRU5TRSIpCmBgYAoKTkI6IE5vdCBhbGwgbXV0YXRpb25zIGRldGVjdGVkIGJ5IFBCLUdBVEsgd2VyZSBkZXRlY3RlZCBieSBHQVRLIQoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01fQpkb3RfcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIoaGVpZ2h0ID0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gMC4yNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSA0MTMpCgpzbnYuZmlsdGVyLmRmICU+JQogIGZpbHRlcihzZWNvbmRfZmlsdGVyID09ICJQQVNTIikgJT4lIAogIAogIGdncGxvdCgpICsKICBsZW1vbjo6Z2VvbV9wb2ludGxpbmUoCiAgICBhZXMoCiAgICAgIHggPSBzYW1wbGUsCiAgICAgIHkgPSBBRl9QRExDLAogICAgICBncm91cCA9IFBvc2l0aW9uX2hnMzgsCiAgICAgIGZpbGwgPSBHZW5jb2RlXzQzX3ZhcmlhbnRDbGFzc2lmaWNhdGlvbgogICAgKSwKICAgIGxpbmVjb2xvciA9ICJncmF5MzAiLAogICAgbGluZXR5cGUgPSAzLAogICAgcG9zaXRpb24gPSBkb3RfcG9zaXRpb24sCiAgICBzaGFwZSA9IDIxLAogICAgc2l6ZSA9IDIKICApICsKICBnZW9tX3BvaW50KAogICAgYWVzKAogICAgICB4ID0gc2FtcGxlLAogICAgICB5ID0gQUZfUERMQywKICAgICAgZ3JvdXAgPSBQb3NpdGlvbl9oZzM4LAogICAgICBmaWxsID0gR2VuY29kZV80M192YXJpYW50Q2xhc3NpZmljYXRpb24KICAgICksCiAgICBwb3NpdGlvbiA9IGRvdF9wb3NpdGlvbiwKICAgIHNoYXBlID0gMjEsCiAgICBzaXplID0gMgogICkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuMDUsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50LCBsaW1pdHMgPSBjKDAsIDEpKSArCiAgdGhlbWVfYncoKSArCiAgbGFicyh4ID0gTlVMTCwgeSA9ICJBbGxlbGljIGZyZXF1ZW5jeSIsCiAgICAgICBmaWxsID0gIlNOViB0eXBlIikKYGBgCgpWaXN1YWwgY29tcGFyaXNvbiBvZiBhbGxlbGljIGZyZXF1ZW5jaWVzIGZvciBib3RoIHJ1bnMuIE9ubHkgZmlsdGVyZWQgU05WIHByZXNlbnQgaW4gYm90aCBzYW1wbGVzIGFyZSBwbG90dGVkLgoKYGBge3IsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9CmRvdF9wb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcihoZWlnaHQgPSAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSAwLjI1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDQxMykKCnNudi5maWx0ZXIuZGYgJT4lCiAgZmlsdGVyKHNlY29uZF9maWx0ZXIgPT0gIlBBU1MiKSAlPiUKICBmaWx0ZXIoR2VuY29kZV80M192YXJpYW50Q2xhc3NpZmljYXRpb24gPT0gIk1JU1NFTlNFIikgJT4lCiAgZ2dwbG90KCkgKwogIGxlbW9uOjpnZW9tX3BvaW50bGluZSgKICAgIGFlcyh4ID0gc2FtcGxlLAogICAgICAgIHkgPSBBRl9QRExDLAogICAgICAgIGdyb3VwID0gUG9zaXRpb25faGczOCksCiAgICBsaW5lY29sb3IgPSAiZ3JheTMwIiwKICAgIGxpbmV0eXBlID0gMywKICAgIHBvc2l0aW9uID0gZG90X3Bvc2l0aW9uLAogICAgc2hhcGUgPSAyMSwKICAgIHNpemUgPSAyLAogICAgZmlsbCA9ICJncmF5OTAiCiAgKSArCiAgCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC4wNSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsIGxpbWl0cyA9IGMoMCwgMSkpICsKICB0aGVtZV9idygpICsKICBsYWJzKHggPSBOVUxMLCB5ID0gIkFsbGVsaWMgZnJlcXVlbmN5IiwKICAgICAgIGZpbGwgPSAiU05WIHR5cGUiKQpgYGAKClZpc3VhbCBjb21wYXJpc29uIG9mIGFsbGVsaWMgZnJlcXVlbmNpZXMgZm9yIG1pc3NlbmNlIG11dGF0aW9ucyBwcmVzZW50IGluIGJvdGggc2FtcGxlcy4KCiMgU2F2ZSB0aGUgdmFyaWFudCB0YWJsZQoKYGBge3J9Cm91dHB1dF9kaXIgPC0gIn4vdGVzdF9kYXRhL1NOVmNhbGxzL1ZDRiIKCm91dHB1dF9maWxlX3hsc3ggPC0gcGFzdGUwKCJGaWx0ZXJlZF9TTlYueGxzeCIpCm91dHB1dF9wYXRoX3hsc3ggPC0gZmlsZS5wYXRoKG91dHB1dF9kaXIsIG91dHB1dF9maWxlX3hsc3gpCgpsaXN0KAogICJUb3RhbCBTTlYiID0gc252LmZpbHRlci5kZiwKICAiRmlsdGVyZWQgU05WIiA9IHNudi5maWx0ZXIuZGYgJT4lIAogICAgZmlsdGVyKHNlY29uZF9maWx0ZXIgPT0gIlBBU1MiKSAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KC1zZWNvbmRfZmlsdGVyKQopICU+JSAKICB3cml0ZS54bHN4KC4sIG91dHB1dF9wYXRoX3hsc3gsIG92ZXJ3cml0ZSA9IFQpCmBgYAoKIyBTZXNzaW9uIGluZm8KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoK