suppressPackageStartupMessages({
  library(VariantAnnotation)
  library(tidyverse)
  library(RColorBrewer)
  library(ggrepel)
  library(cowplot)
})
Warning: package ‘VariantAnnotation’ was built under R version 4.3.2Warning: package ‘BiocGenerics’ was built under R version 4.3.2Warning: package ‘MatrixGenerics’ was built under R version 4.3.2Warning: package ‘matrixStats’ was built under R version 4.3.2Warning: package ‘GenomeInfoDb’ was built under R version 4.3.2Warning: package ‘S4Vectors’ was built under R version 4.3.2Warning: package ‘IRanges’ was built under R version 4.3.2Warning: package ‘GenomicRanges’ was built under R version 4.3.2Warning: package ‘SummarizedExperiment’ was built under R version 4.3.2Warning: package ‘Biobase’ was built under R version 4.3.2Warning: package ‘Rsamtools’ was built under R version 4.3.2Warning: package ‘Biostrings’ was built under R version 4.3.2Warning: package ‘XVector’ was built under R version 4.3.2Warning: package ‘tidyr’ was built under R version 4.3.2Warning: package ‘readr’ was built under R version 4.3.2Warning: package ‘dplyr’ was built under R version 4.3.2Warning: package ‘stringr’ was built under R version 4.3.2Warning: package ‘ggrepel’ was built under R version 4.3.2Warning: package ‘cowplot’ was built under R version 4.3.2

Load and process VCF files

First, we need to load the VCF files using readVcf function from VariantAnnotation package. We load both classic GATK and Parabricks GATK VCFs as objects into a named list.

vcf.filenames <- c("classic GATK" = "~/WES_data/WES_tumor.annotated.vcf",
                   "Parabricks GATK" = "~/WES_data/PB_WES_tumor.annotated.vcf")

vcf.list <- lapply(vcf.filenames, readVcf, genome = "hg38")

Here’s what looking at dimensions of each element of this list gives us:

lapply(vcf.list, dim)
$`classic GATK`
[1] 7480    2

$`Parabricks GATK`
[1] 8994    2

First value is the number of mutations and the second is the number of samples (normal and tumor). That is a lot of variants, luckly, we applied a Mutect2 filter. Let’s subset the VCF objects leaving only variants which passed the filter:

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

# Also, filter only variants mapped correctly to a chromosome:
vcf.list.qc <-
  lapply(vcf.list.qc, function(x)
    x[!(grepl(names(x), pattern = "chrUn_")),])

lapply(vcf.list.qc, dim)
$`classic GATK`
[1] 2603    2

$`Parabricks GATK`
[1] 3146    2

That’s substantially less variants to worry about, but still a lot.

Next block of code will extract only the relevant fields and columns from the VCF objects, creating a data frame where each row is a detected variant. It will include positional information and annotations of interest (gene, type of SNV, allelic frequence) along with additional filter information which we will use to further prune the variants.

extractFUNCOTATION <-
  function(vcf,
           fields = c(
             "Gencode_34_hugoSymbol",
             "Gencode_34_variantClassification",
             "Gencode_34_variantType"
           )) {
    annotation_colnames <-
      info(vcf@metadata$header)["FUNCOTATION", "Description"] %>%
      stringr::str_remove("Functional annotation from the Funcotator tool.  Funcotation fields are: ") %>%
      stringr::str_split(pattern = "\\|") %>%
      unlist()
    
    as.data.frame(info(vcf)[["FUNCOTATION"]]) %>%
      dplyr::select("FUNCOTATION" = value) %>%
      dplyr::mutate(
        FUNCOTATION = word(FUNCOTATION, 1, sep = "\\]"),
        FUNCOTATION = str_remove_all(FUNCOTATION, "[\\[\\]]")
      ) %>%
      tidyr::separate(FUNCOTATION,
                      into = annotation_colnames,
                      sep = "\\|",
                      fill = "right") %>%
      dplyr::select(all_of(fields))
  }

vcf.list.anno <- lapply(vcf.list.qc, function(x) extractFUNCOTATION(x))

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"]]) %>%
    
    # since the sample is the same in both VCF, we rename the AF columns equally
    setNames(., c("AF_CTRL", "AF_TUMOR")) %>%
    
    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, 10) # Print only first 10 rows
$`classic GATK`

$`Parabricks GATK`
NA

Variant filters

GATK FilterMutectCalls gives the these values for each processed variant:

  • DP is the read depth. The bigger it is - the more statistical power we have to say that the observed read is real.

  • GERMQ - The phred-scaled posterior probability that the alternate alleles are NOT germline variants. The higher the better.

  • NALOD (Normal Allele Log Odds) Negative log 10 odds of the variant being an an artifact in the normal sample with the same allele fraction as the tumor sample for each alternate allele.

  • NLOD (Normal Log Odds) is log odds that the variant is not present in the normal sample. Confidence that the variant is not a germline variant.

  • TLOD (Tumor Log Odds) Log odds that the variant is present in the tumor sample relative to the expected noise.

We can threshold them to further filter the VCF, removing germline variants and sequencing artifacts. Along with it, we threshold by allelic frequence - variant should be present in more than 10% of tumor reads and less than 2.5% of normal DNA reads.

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

lapply(names(snv.list),
       function(name)
         ggplot(snv.list[[name]],
                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, %", title = name))
[[1]]

[[2]]

The first plots shows DP, normal allelic frequency and GERMQ thresholds. Variants in the lower right quadrant in green pass it.

lapply(names(snv.list),
       function(name)
         snv.list[[name]] %>%
         dplyr::filter(GERMQ >= filters[["GERMQ"]],
                       DP >= filters[["DP"]]) %>%
         mutate(
           NALOD_threshold = case_when(
             NALOD > filters[["NALOD"]] ~ "PASS",
             TRUE ~ "Too low"
           )
         ) %>% 
         ggplot(.,
                aes(x = TLOD,
                    y = NLOD)) +
         geom_density_2d(color = "black") +
         geom_point(
           aes(color = NALOD_threshold),
           size = 2,
           stroke = 1
         ) +
         scale_color_manual(values = c("PASS" = "#5ec962", "Too low" = "#440154")) +
         scale_y_continuous(trans = "log10") +
         scale_x_continuous(trans = "log10") +
         geom_hline(yintercept = filters[["NLOD"]], linetype = 2) +
         geom_vline(xintercept = filters[["TLOD"]], linetype = 2) +
         labs(title = name))
[[1]]

[[2]]

The SNV which pass the first plot thresholds are shown here. They are thresholded using NLOD, NALOD and TLOD scores.

Apply the variant filters

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

filtered.snv.list <- lapply(snv.list, function(x)
  mutate(
    x,
    second_filter = case_when(
      DP <= filters[["DP"]] ~ "low_DP",
      GERMQ <= filters[["GERMQ"]] ~ "low_GERMQ",
      AF_CTRL > filters[["AF_CTRL"]] ~ "high_CTRL_AF",
      AF_TUMOR < filters[["AF_TUMOR"]] ~ "low_Tumor_AF",
      NALOD < filters[["NALOD"]] ~   "low_NALOD",
      NLOD < filters[["NLOD"]] ~     "low_NLOD",
      TLOD < filters[["TLOD"]] ~     "low_TLOD",
      TRUE ~ "PASS"
    )
  ))

lapply(filtered.snv.list, filter, second_filter == "PASS") %>% 
  lapply(., dim)
$`classic GATK`
[1] 536  13

$`Parabricks GATK`
[1] 587  13
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

lapply(names(filtered.snv.list),
       function(name)
         pivot_longer(filtered.snv.list[[name]], 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) +
         labs(title = name))
[[1]]

[[2]]

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

lapply(names(filtered.snv.list),
       function(name)
         filter(filtered.snv.list[[name]], 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) +
         labs(title = name))
[[1]]

[[2]]

Same plot but only SNV which passed the filters.

Compare the allelic frequencies for SNV detected in both samples

Let’s combine the filtered.snv.list into one dataframe and inspect the overlap of detected mutations:

combined.snv.df <- filtered.snv.list %>% 
  bind_rows(.id = "pipeline") %>% 
  filter(second_filter == "PASS") %>% 
  dplyr::select(pipeline, Position_hg38, Variant, AF_TUMOR,
         Gencode_34_hugoSymbol, Gencode_34_variantClassification) %>% 
  pivot_wider(names_from = "pipeline", values_from = "AF_TUMOR")

combined.snv.df

Variants that were not detected by Parabricks but only by classic GATK:

combined.snv.df %>% 
  filter(!is.na(`classic GATK`) & is.na(`Parabricks GATK`))

And vice versa:

combined.snv.df %>% 
  filter(is.na(`classic GATK`) & !is.na(`Parabricks GATK`))

Parabricks GATK detected 53 more and ‘missed’ two mutations.

One of the most commonly mutated genes in tumors is TP53. Using filter by gene name we can check the presence of TP53 mutations.

Finally, lets compare the allelic frequencies for the variants detected by both pipelines:

combined.snv.df %>%
  ggplot(aes(x = `classic GATK`, y = `Parabricks GATK`)) +
  geom_point(
    aes(
      fill = Gencode_34_variantClassification
    ),
    shape = 21,
    size = 2
  ) +
  theme_bw() +
  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
  scale_x_continuous(labels = scales::percent, limits = c(0, 1))

We can limit the variant types to show only the biologically revevant mutations:

relevant.snv.df <- combined.snv.df %>%
  filter(Gencode_34_variantClassification %in% c("MISSENSE", "NONSENSE",
                                                 "IN_FRAME_DEL", "IN_FRAME_INS",
                                                 "FRAME_SHIFT_DEL", "FRAME_SHIFT_INS")
         )


  ggplot(relevant.snv.df,
         aes(x = `classic GATK`, y = `Parabricks GATK`)) +
  geom_point(
    aes(
      fill = Gencode_34_variantClassification
    ),
    shape = 21,
    size = 2
  ) + 
  geom_text_repel( # add text labels for some of the top AF mutations
    data = filter(relevant.snv.df, `classic GATK` > 0.9 & `Parabricks GATK` > 0.9),
    aes(label = Gencode_34_hugoSymbol), max.overlaps = 30, nudge_y = -0.2, nudge_x = -0.6
  ) +
  theme_bw() +
  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
  scale_x_continuous(labels = scales::percent, limits = c(0, 1))

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.26.so;  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               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.3               ggrepel_0.9.5               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.5                
[10] tidyr_1.3.1                 tibble_3.2.1                ggplot2_3.4.4              
[13] tidyverse_2.0.0             VariantAnnotation_1.48.1    Rsamtools_2.18.0           
[16] Biostrings_2.70.1           XVector_0.42.0              SummarizedExperiment_1.32.0
[19] Biobase_2.62.0              GenomicRanges_1.54.1        GenomeInfoDb_1.38.1        
[22] IRanges_2.36.0              S4Vectors_0.40.2            MatrixGenerics_1.14.0      
[25] matrixStats_1.2.0           BiocGenerics_0.48.1        

loaded via a namespace (and not attached):
 [1] DBI_1.2.1                bitops_1.0-7             biomaRt_2.58.0           rlang_1.1.3             
 [5] magrittr_2.0.3           compiler_4.3.1           RSQLite_2.3.4            GenomicFeatures_1.54.1  
 [9] png_0.1-8                vctrs_0.6.5              pkgconfig_2.0.3          crayon_1.5.2            
[13] fastmap_1.1.1            dbplyr_2.4.0             labeling_0.4.3           utf8_1.2.4              
[17] tzdb_0.4.0               ggbeeswarm_0.7.2         bit_4.0.5                xfun_0.41               
[21] zlibbioc_1.48.0          cachem_1.0.8             progress_1.2.3           blob_1.2.4              
[25] DelayedArray_0.28.0      BiocParallel_1.36.0      parallel_4.3.1           prettyunits_1.2.0       
[29] R6_2.5.1                 stringi_1.8.3            rtracklayer_1.62.0       Rcpp_1.0.12             
[33] knitr_1.45               Matrix_1.6-5             timechange_0.3.0         tidyselect_1.2.0        
[37] rstudioapi_0.15.0        abind_1.4-5              yaml_2.3.8               codetools_0.2-19        
[41] curl_5.1.0               lattice_0.22-5           withr_3.0.0              KEGGREST_1.42.0         
[45] isoband_0.2.7            BiocFileCache_2.10.1     xml2_1.3.6               pillar_1.9.0            
[49] filelock_1.0.3           generics_0.1.3           RCurl_1.98-1.14          hms_1.1.3               
[53] munsell_0.5.0            scales_1.3.0             glue_1.7.0               tools_4.3.1             
[57] BiocIO_1.12.0            BSgenome_1.70.1          GenomicAlignments_1.38.0 XML_3.99-0.16.1         
[61] grid_4.3.1               AnnotationDbi_1.64.1     colorspace_2.1-0         GenomeInfoDbData_1.2.11 
[65] beeswarm_0.4.0           restfulr_0.0.15          vipor_0.4.7              cli_3.6.2               
[69] rappdirs_0.3.3           fansi_1.0.6              S4Arrays_1.2.0           gtable_0.3.4            
[73] digest_0.6.34            SparseArray_1.2.2        farver_2.1.1             rjson_0.2.21            
[77] memoise_2.0.1            lifecycle_1.0.4          httr_1.4.7               MASS_7.3-60             
[81] bit64_4.0.5             
LS0tCnRpdGxlOiAiUUMgYW5kIGZpbHRlcmluZyBvZiBhbm5vdGF0ZWQgVkNGIGZpbGVzIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0b2NfZGVwdGg6IDMKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgdGhlbWU6IHVuaXRlZAotLS0KCmBgYHtyIHNldHVwfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoVmFyaWFudEFubm90YXRpb24pCiAgbGlicmFyeSh0aWR5dmVyc2UpCiAgbGlicmFyeShSQ29sb3JCcmV3ZXIpCiAgbGlicmFyeShnZ3JlcGVsKQogIGxpYnJhcnkoY293cGxvdCkKfSkKYGBgCgojIExvYWQgYW5kIHByb2Nlc3MgVkNGIGZpbGVzCgpGaXJzdCwgd2UgbmVlZCB0byBsb2FkIHRoZSBWQ0YgZmlsZXMgdXNpbmcgYHJlYWRWY2ZgIGZ1bmN0aW9uIGZyb20gYFZhcmlhbnRBbm5vdGF0aW9uYCBwYWNrYWdlLiBXZSBsb2FkIGJvdGggY2xhc3NpYyBHQVRLIGFuZCBQYXJhYnJpY2tzIEdBVEsgVkNGcyBhcyBvYmplY3RzIGludG8gYSBuYW1lZCBsaXN0LgoKYGBge3Igd2FybmluZz1GQUxTRX0KdmNmLmZpbGVuYW1lcyA8LSBjKCJjbGFzc2ljIEdBVEsiID0gIn4vV0VTX2RhdGEvV0VTX3R1bW9yLmFubm90YXRlZC52Y2YiLAogICAgICAgICAgICAgICAgICAgIlBhcmFicmlja3MgR0FUSyIgPSAifi9XRVNfZGF0YS9QQl9XRVNfdHVtb3IuYW5ub3RhdGVkLnZjZiIpCgp2Y2YubGlzdCA8LSBsYXBwbHkodmNmLmZpbGVuYW1lcywgcmVhZFZjZiwgZ2Vub21lID0gImhnMzgiKQpgYGAKCkhlcmUncyB3aGF0IGxvb2tpbmcgYXQgZGltZW5zaW9ucyBvZiBlYWNoIGVsZW1lbnQgb2YgdGhpcyBsaXN0IGdpdmVzIHVzOgoKYGBge3Igd2FybmluZz1GQUxTRX0KbGFwcGx5KHZjZi5saXN0LCBkaW0pCmBgYAoKRmlyc3QgdmFsdWUgaXMgdGhlIG51bWJlciBvZiBtdXRhdGlvbnMgYW5kIHRoZSBzZWNvbmQgaXMgdGhlIG51bWJlciBvZiBzYW1wbGVzIChub3JtYWwgYW5kIHR1bW9yKS4gVGhhdCBpcyBhIGxvdCBvZiB2YXJpYW50cywgbHVja2x5LCB3ZSBhcHBsaWVkIGEgTXV0ZWN0MiBmaWx0ZXIuIExldCdzIHN1YnNldCB0aGUgVkNGIG9iamVjdHMgbGVhdmluZyBvbmx5IHZhcmlhbnRzIHdoaWNoIHBhc3NlZCB0aGUgZmlsdGVyOgoKYGBge3J9CnZjZi5saXN0LnFjIDwtCiAgbGFwcGx5KHZjZi5saXN0LCBmdW5jdGlvbih4KQogICAgeFtyb3dSYW5nZXMoeCkkRklMVEVSID09ICJQQVNTIl0pCgojIEFsc28sIGZpbHRlciBvbmx5IHZhcmlhbnRzIG1hcHBlZCBjb3JyZWN0bHkgdG8gYSBjaHJvbW9zb21lOgp2Y2YubGlzdC5xYyA8LQogIGxhcHBseSh2Y2YubGlzdC5xYywgZnVuY3Rpb24oeCkKICAgIHhbIShncmVwbChuYW1lcyh4KSwgcGF0dGVybiA9ICJjaHJVbl8iKSksXSkKCmxhcHBseSh2Y2YubGlzdC5xYywgZGltKQpgYGAKClRoYXQncyBzdWJzdGFudGlhbGx5IGxlc3MgdmFyaWFudHMgdG8gd29ycnkgYWJvdXQsIGJ1dCBzdGlsbCBhIGxvdC4KCk5leHQgYmxvY2sgb2YgY29kZSB3aWxsIGV4dHJhY3Qgb25seSB0aGUgcmVsZXZhbnQgZmllbGRzIGFuZCBjb2x1bW5zIGZyb20gdGhlIFZDRiBvYmplY3RzLCBjcmVhdGluZyBhIGRhdGEgZnJhbWUgd2hlcmUgZWFjaCByb3cgaXMgYSBkZXRlY3RlZCB2YXJpYW50LiBJdCB3aWxsIGluY2x1ZGUgcG9zaXRpb25hbCBpbmZvcm1hdGlvbiBhbmQgYW5ub3RhdGlvbnMgb2YgaW50ZXJlc3QgKGdlbmUsIHR5cGUgb2YgU05WLCBhbGxlbGljIGZyZXF1ZW5jZSkgYWxvbmcgd2l0aCBhZGRpdGlvbmFsIGZpbHRlciBpbmZvcm1hdGlvbiB3aGljaCB3ZSB3aWxsIHVzZSB0byBmdXJ0aGVyIHBydW5lIHRoZSB2YXJpYW50cy4gCgpgYGB7cn0KZXh0cmFjdEZVTkNPVEFUSU9OIDwtCiAgZnVuY3Rpb24odmNmLAogICAgICAgICAgIGZpZWxkcyA9IGMoCiAgICAgICAgICAgICAiR2VuY29kZV8zNF9odWdvU3ltYm9sIiwKICAgICAgICAgICAgICJHZW5jb2RlXzM0X3ZhcmlhbnRDbGFzc2lmaWNhdGlvbiIsCiAgICAgICAgICAgICAiR2VuY29kZV8zNF92YXJpYW50VHlwZSIKICAgICAgICAgICApKSB7CiAgICBhbm5vdGF0aW9uX2NvbG5hbWVzIDwtCiAgICAgIGluZm8odmNmQG1ldGFkYXRhJGhlYWRlcilbIkZVTkNPVEFUSU9OIiwgIkRlc2NyaXB0aW9uIl0gJT4lCiAgICAgIHN0cmluZ3I6OnN0cl9yZW1vdmUoIkZ1bmN0aW9uYWwgYW5ub3RhdGlvbiBmcm9tIHRoZSBGdW5jb3RhdG9yIHRvb2wuICBGdW5jb3RhdGlvbiBmaWVsZHMgYXJlOiAiKSAlPiUKICAgICAgc3RyaW5ncjo6c3RyX3NwbGl0KHBhdHRlcm4gPSAiXFx8IikgJT4lCiAgICAgIHVubGlzdCgpCiAgICAKICAgIGFzLmRhdGEuZnJhbWUoaW5mbyh2Y2YpW1siRlVOQ09UQVRJT04iXV0pICU+JQogICAgICBkcGx5cjo6c2VsZWN0KCJGVU5DT1RBVElPTiIgPSB2YWx1ZSkgJT4lCiAgICAgIGRwbHlyOjptdXRhdGUoCiAgICAgICAgRlVOQ09UQVRJT04gPSB3b3JkKEZVTkNPVEFUSU9OLCAxLCBzZXAgPSAiXFxdIiksCiAgICAgICAgRlVOQ09UQVRJT04gPSBzdHJfcmVtb3ZlX2FsbChGVU5DT1RBVElPTiwgIltcXFtcXF1dIikKICAgICAgKSAlPiUKICAgICAgdGlkeXI6OnNlcGFyYXRlKEZVTkNPVEFUSU9OLAogICAgICAgICAgICAgICAgICAgICAgaW50byA9IGFubm90YXRpb25fY29sbmFtZXMsCiAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXFx8IiwKICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSAicmlnaHQiKSAlPiUKICAgICAgZHBseXI6OnNlbGVjdChhbGxfb2YoZmllbGRzKSkKICB9Cgp2Y2YubGlzdC5hbm5vIDwtIGxhcHBseSh2Y2YubGlzdC5xYywgZnVuY3Rpb24oeCkgZXh0cmFjdEZVTkNPVEFUSU9OKHgpKQoKc252Lmxpc3QgPC0gbGlzdCgpCmZvcihzYW1wbGUubmFtZSBpbiBuYW1lcyh2Y2YubGlzdC5xYykpIHsKICB2Y2YgPC0gdmNmLmxpc3QucWNbW3NhbXBsZS5uYW1lXV0KICBkZi5hbm5vIDwtIHZjZi5saXN0LmFubm9bW3NhbXBsZS5uYW1lXV0KICBzbnYubGlzdFtbc2FtcGxlLm5hbWVdXSA8LSBhcy5kYXRhLmZyYW1lKGdlbm8odmNmKVtbIkFGIl1dKSAlPiUKICAgIAogICAgIyBzaW5jZSB0aGUgc2FtcGxlIGlzIHRoZSBzYW1lIGluIGJvdGggVkNGLCB3ZSByZW5hbWUgdGhlIEFGIGNvbHVtbnMgZXF1YWxseQogICAgc2V0TmFtZXMoLiwgYygiQUZfQ1RSTCIsICJBRl9UVU1PUiIpKSAlPiUKICAgIAogICAgbXV0YXRlX2FsbCggfiB1bmxpc3QoLikpICU+JQogICAgY2JpbmQoLiwgZGYuYW5ubykgJT4lCiAgICBtdXRhdGUoCiAgICAgIERQID0gaW5mbyh2Y2YpW1siRFAiXV0sCiAgICAgIEdFUk1RID0gaW5mbyh2Y2YpW1siR0VSTVEiXV0sCiAgICAgIE5BTE9EID0gaW5mbyh2Y2YpW1siTkFMT0QiXV0gJT4lIHVubGlzdCgpLAogICAgICBOTE9EID0gaW5mbyh2Y2YpW1siTkxPRCJdXSAlPiUgdW5saXN0KCksCiAgICAgIFRMT0QgPSBpbmZvKHZjZilbWyJUTE9EIl1dICU+JSB1bmxpc3QoKQogICAgKSAlPiUKICAgIHJvd25hbWVzX3RvX2NvbHVtbigiVmFyaWFudCIpICU+JQogICAgbXV0YXRlKFBvc2l0aW9uX2hnMzggPSB3b3JkKFZhcmlhbnQsIDEsIHNlcCA9ICJfIiksCiAgICAgICAgICAgLmJlZm9yZSA9ICJWYXJpYW50IikgJT4lCiAgICBtdXRhdGUoVmFyaWFudCA9IHdvcmQoVmFyaWFudCwgMiwgc2VwID0gIl8iKSkKfQoKbGFwcGx5KHNudi5saXN0LCBoZWFkLCAxMCkgIyBQcmludCBvbmx5IGZpcnN0IDEwIHJvd3MKYGBgCgojIFZhcmlhbnQgZmlsdGVycwoKR0FUSyBgRmlsdGVyTXV0ZWN0Q2FsbHNgIGdpdmVzIHRoZSB0aGVzZSB2YWx1ZXMgZm9yIGVhY2ggcHJvY2Vzc2VkIHZhcmlhbnQ6IAoKKiAqKkRQKiogaXMgdGhlIHJlYWQgZGVwdGguIFRoZSBiaWdnZXIgaXQgaXMgLSB0aGUgbW9yZSBzdGF0aXN0aWNhbCBwb3dlciB3ZSBoYXZlIHRvIHNheSB0aGF0IHRoZSBvYnNlcnZlZCByZWFkIGlzIHJlYWwuCgoqICoqR0VSTVEqKiAtIFRoZSBwaHJlZC1zY2FsZWQgcG9zdGVyaW9yIHByb2JhYmlsaXR5IHRoYXQgdGhlIGFsdGVybmF0ZSBhbGxlbGVzIGFyZSBOT1QgZ2VybWxpbmUgdmFyaWFudHMuIFRoZSBoaWdoZXIgdGhlIGJldHRlci4KCiogKipOQUxPRCoqIChOb3JtYWwgQWxsZWxlIExvZyBPZGRzKSBOZWdhdGl2ZSBsb2cgMTAgb2RkcyBvZiB0aGUgdmFyaWFudCBiZWluZyBhbiBhbiBhcnRpZmFjdCBpbiB0aGUgbm9ybWFsIHNhbXBsZSB3aXRoIHRoZSBzYW1lIGFsbGVsZSBmcmFjdGlvbiBhcyB0aGUgdHVtb3Igc2FtcGxlIGZvciBlYWNoIGFsdGVybmF0ZSBhbGxlbGUuCgoqICoqTkxPRCoqIChOb3JtYWwgTG9nIE9kZHMpIGlzIGxvZyBvZGRzIHRoYXQgdGhlIHZhcmlhbnQgaXMgbm90IHByZXNlbnQgaW4gdGhlIG5vcm1hbCBzYW1wbGUuIENvbmZpZGVuY2UgdGhhdCB0aGUgdmFyaWFudCBpcyBub3QgYSBnZXJtbGluZSB2YXJpYW50LgoKKiAqKlRMT0QqKiAoVHVtb3IgTG9nIE9kZHMpIExvZyBvZGRzIHRoYXQgdGhlIHZhcmlhbnQgaXMgcHJlc2VudCBpbiB0aGUgdHVtb3Igc2FtcGxlIHJlbGF0aXZlIHRvIHRoZSBleHBlY3RlZCBub2lzZS4KCldlIGNhbiB0aHJlc2hvbGQgdGhlbSB0byBmdXJ0aGVyIGZpbHRlciB0aGUgVkNGLCByZW1vdmluZyBnZXJtbGluZSB2YXJpYW50cyBhbmQgc2VxdWVuY2luZyBhcnRpZmFjdHMuIEFsb25nIHdpdGggaXQsIHdlIHRocmVzaG9sZCBieSBhbGxlbGljIGZyZXF1ZW5jZSAtIHZhcmlhbnQgc2hvdWxkIGJlIHByZXNlbnQgaW4gbW9yZSB0aGFuIDEwJSBvZiB0dW1vciByZWFkcyBhbmQgbGVzcyB0aGFuIDIuNSUgb2Ygbm9ybWFsIEROQSByZWFkcy4KCmBgYHtyLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD01fQpmaWx0ZXJzID0gbGlzdCgKICAiRFAiID0gMzAsCiAgIkdFUk1RIiA9IDkwLAogICJOQUxPRCIgPSAtbG9nMTAoMC4wNSksCiAgIk5MT0QiID0gNiwKICAiVExPRCIgPSAzMCwKICAKICAiQUZfQ1RSTCIgPSAwLjAyNSwKICAiQUZfVFVNT1IiID0gMC4xCikKCmxhcHBseShuYW1lcyhzbnYubGlzdCksCiAgICAgICBmdW5jdGlvbihuYW1lKQogICAgICAgICBnZ3Bsb3Qoc252Lmxpc3RbW25hbWVdXSwKICAgICAgICAgICAgICAgIGFlcyh5ID0gQUZfQ1RSTCwgeCA9IERQKSkgKwogICAgICAgICBnZW9tX3BvaW50KAogICAgICAgICAgIGFlcyhjb2xvciA9IEdFUk1RKSwKICAgICAgICAgICBzaGFwZSA9IDEsCiAgICAgICAgICAgYWxwaGEgPSAwLjc1LAogICAgICAgICAgIHNpemUgPSAxLjUsCiAgICAgICAgICAgc3Ryb2tlID0gMQogICAgICAgICApICsKICAgICAgICAgc2NhbGVfY29sb3VyX3N0ZXBzMigKICAgICAgICAgICBoaWdoID0gImdyZWVuIiwKICAgICAgICAgICBtaWRwb2ludCA9IDUwLAogICAgICAgICAgIG1pZCA9ICJibHVlIiwKICAgICAgICAgICBsb3cgPSAiYmx1ZSIsCiAgICAgICAgICAgYnJlYWtzID0gZmlsdGVyc1tbIkdFUk1RIl1dCiAgICAgICAgICkgKwogICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMTAiKSArCiAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsKICAgICAgICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gZmlsdGVyc1tbIkFGX0NUUkwiXV0sIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICAgICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gZmlsdGVyc1tbIkRQIl1dLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgICAgICAgIGxhYnMoeSA9ICJDVFJMIEFGLCAlIiwgdGl0bGUgPSBuYW1lKSkKYGBgCgpUaGUgZmlyc3QgcGxvdHMgc2hvd3MgRFAsIG5vcm1hbCBhbGxlbGljIGZyZXF1ZW5jeSBhbmQgR0VSTVEgdGhyZXNob2xkcy4gVmFyaWFudHMgaW4gdGhlIGxvd2VyIHJpZ2h0IHF1YWRyYW50IGluIGdyZWVuIHBhc3MgaXQuCgpgYGB7ciwgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9NX0KbGFwcGx5KG5hbWVzKHNudi5saXN0KSwKICAgICAgIGZ1bmN0aW9uKG5hbWUpCiAgICAgICAgIHNudi5saXN0W1tuYW1lXV0gJT4lCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoR0VSTVEgPj0gZmlsdGVyc1tbIkdFUk1RIl1dLAogICAgICAgICAgICAgICAgICAgICAgIERQID49IGZpbHRlcnNbWyJEUCJdXSkgJT4lCiAgICAgICAgIG11dGF0ZSgKICAgICAgICAgICBOQUxPRF90aHJlc2hvbGQgPSBjYXNlX3doZW4oCiAgICAgICAgICAgICBOQUxPRCA+IGZpbHRlcnNbWyJOQUxPRCJdXSB+ICJQQVNTIiwKICAgICAgICAgICAgIFRSVUUgfiAiVG9vIGxvdyIKICAgICAgICAgICApCiAgICAgICAgICkgJT4lIAogICAgICAgICBnZ3Bsb3QoLiwKICAgICAgICAgICAgICAgIGFlcyh4ID0gVExPRCwKICAgICAgICAgICAgICAgICAgICB5ID0gTkxPRCkpICsKICAgICAgICAgZ2VvbV9kZW5zaXR5XzJkKGNvbG9yID0gImJsYWNrIikgKwogICAgICAgICBnZW9tX3BvaW50KAogICAgICAgICAgIGFlcyhjb2xvciA9IE5BTE9EX3RocmVzaG9sZCksCiAgICAgICAgICAgc2l6ZSA9IDIsCiAgICAgICAgICAgc3Ryb2tlID0gMQogICAgICAgICApICsKICAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIlBBU1MiID0gIiM1ZWM5NjIiLCAiVG9vIGxvdyIgPSAiIzQ0MDE1NCIpKSArCiAgICAgICAgIHNjYWxlX3lfY29udGludW91cyh0cmFucyA9ICJsb2cxMCIpICsKICAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIikgKwogICAgICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBmaWx0ZXJzW1siTkxPRCJdXSwgbGluZXR5cGUgPSAyKSArCiAgICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGZpbHRlcnNbWyJUTE9EIl1dLCBsaW5ldHlwZSA9IDIpICsKICAgICAgICAgbGFicyh0aXRsZSA9IG5hbWUpKQpgYGAKClRoZSBTTlYgd2hpY2ggcGFzcyB0aGUgZmlyc3QgcGxvdCB0aHJlc2hvbGRzIGFyZSBzaG93biBoZXJlLiBUaGV5IGFyZSB0aHJlc2hvbGRlZCB1c2luZyBOTE9ELCBOQUxPRCBhbmQgVExPRCBzY29yZXMuCgojIyBBcHBseSB0aGUgdmFyaWFudCBmaWx0ZXJzCgpSYXRoZXIgdGhhbiBqdXN0IGZpbHRlcmluZywgd2UgYW5ub3RhdGUgdGhlIFNOViBiYXNlZCBvbiBmaXJzdCByZWFzb24gZm9yIGZpbHRlcmluZyBvdXQuCgpgYGB7cn0KZmlsdGVyZWQuc252Lmxpc3QgPC0gbGFwcGx5KHNudi5saXN0LCBmdW5jdGlvbih4KQogIG11dGF0ZSgKICAgIHgsCiAgICBzZWNvbmRfZmlsdGVyID0gY2FzZV93aGVuKAogICAgICBEUCA8PSBmaWx0ZXJzW1siRFAiXV0gfiAibG93X0RQIiwKICAgICAgR0VSTVEgPD0gZmlsdGVyc1tbIkdFUk1RIl1dIH4gImxvd19HRVJNUSIsCiAgICAgIEFGX0NUUkwgPiBmaWx0ZXJzW1siQUZfQ1RSTCJdXSB+ICJoaWdoX0NUUkxfQUYiLAogICAgICBBRl9UVU1PUiA8IGZpbHRlcnNbWyJBRl9UVU1PUiJdXSB+ICJsb3dfVHVtb3JfQUYiLAogICAgICBOQUxPRCA8IGZpbHRlcnNbWyJOQUxPRCJdXSB+ICAgImxvd19OQUxPRCIsCiAgICAgIE5MT0QgPCBmaWx0ZXJzW1siTkxPRCJdXSB+ICAgICAibG93X05MT0QiLAogICAgICBUTE9EIDwgZmlsdGVyc1tbIlRMT0QiXV0gfiAgICAgImxvd19UTE9EIiwKICAgICAgVFJVRSB+ICJQQVNTIgogICAgKQogICkpCgpsYXBwbHkoZmlsdGVyZWQuc252Lmxpc3QsIGZpbHRlciwgc2Vjb25kX2ZpbHRlciA9PSAiUEFTUyIpICU+JSAKICBsYXBwbHkoLiwgZGltKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD01fQpjKAogICJsb3dfRFAiID0gInJlZCIsCiAgImxvd19HRVJNUSIgPSAib3JhbmdlIiwKICAibG93X05BTE9EIiA9ICJwdXJwbGUiLAogICJsb3dfTkxPRCIgPSAidmlvbGV0IiwKICAibG93X1RMT0QiID0gImZpcmVicmljayIsCiAgImhpZ2hfQ1RSTF9BRiIgPSAiY29yYWwyIiwKICAibG93X1R1bW9yX0FGIiA9ICJibHVldmlvbGV0IiwKICAiUEFTUyIgPSAiZ3JlZW4iCikgLT4gcGFsLmNvbG9ycwoKbGFwcGx5KG5hbWVzKGZpbHRlcmVkLnNudi5saXN0KSwKICAgICAgIGZ1bmN0aW9uKG5hbWUpCiAgICAgICAgIHBpdm90X2xvbmdlcihmaWx0ZXJlZC5zbnYubGlzdFtbbmFtZV1dLCBjb2xzID0gY29udGFpbnMoIkFGIiksIHZhbHVlc190byA9ICJBRiIpICU+JQogICAgICAgICBnZ3Bsb3QoYWVzKHggPSBuYW1lLCB5ID0gQUYpKSArCiAgICAgICAgIGdnYmVlc3dhcm06Omdlb21fcXVhc2lyYW5kb20oYWVzKGNvbG9yID0gc2Vjb25kX2ZpbHRlcikpICsKICAgICAgICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gZmlsdGVyc1tbIkFGX0NUUkwiXV0sIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICAgICAgICAgdGhlbWVfYncoKSArIGxhYnMoeCA9IE5VTEwpICsKICAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbC5jb2xvcnMpICsKICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogICAgICAgICBsYWJzKHRpdGxlID0gbmFtZSkpCmBgYAoKQmVlc3dhcm0gcGxvdHMgb2YgYWxsZWxpYyBmcmVxdWVuY2llcyBmb3IgYWxsIG11dGF0aW9ucyAoaW5jbHVkaW5nIGZpbHRlcmVkIG91dCkuCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NX0KbGFwcGx5KG5hbWVzKGZpbHRlcmVkLnNudi5saXN0KSwKICAgICAgIGZ1bmN0aW9uKG5hbWUpCiAgICAgICAgIGZpbHRlcihmaWx0ZXJlZC5zbnYubGlzdFtbbmFtZV1dLCBzZWNvbmRfZmlsdGVyID09ICJQQVNTIikgJT4lCiAgICAgICAgIHBpdm90X2xvbmdlcihjb2xzID0gY29udGFpbnMoIkFGIiksIHZhbHVlc190byA9ICJBRiIpICU+JQogICAgICAgICBnZ3Bsb3QoYWVzKHggPSBuYW1lLCB5ID0gQUYpKSArCiAgICAgICAgIGdnYmVlc3dhcm06Omdlb21fcXVhc2lyYW5kb20oYWVzKGNvbG9yID0gc2Vjb25kX2ZpbHRlcikpICsKICAgICAgICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gZmlsdGVyc1tbIkFGX0NUUkwiXV0sIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICAgICAgICAgdGhlbWVfYncoKSArIGxhYnMoeCA9IE5VTEwpICsKICAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbC5jb2xvcnMpICsKICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogICAgICAgICBsYWJzKHRpdGxlID0gbmFtZSkpCmBgYAoKU2FtZSBwbG90IGJ1dCBvbmx5IFNOViB3aGljaCBwYXNzZWQgdGhlIGZpbHRlcnMuCgojIyBDb21wYXJlIHRoZSBhbGxlbGljIGZyZXF1ZW5jaWVzIGZvciBTTlYgZGV0ZWN0ZWQgaW4gYm90aCBzYW1wbGVzCgpMZXQncyBjb21iaW5lIHRoZSBgZmlsdGVyZWQuc252Lmxpc3RgIGludG8gb25lIGRhdGFmcmFtZSBhbmQgaW5zcGVjdCB0aGUgb3ZlcmxhcCBvZiBkZXRlY3RlZCBtdXRhdGlvbnM6CgpgYGB7cn0KY29tYmluZWQuc252LmRmIDwtIGZpbHRlcmVkLnNudi5saXN0ICU+JSAKICBiaW5kX3Jvd3MoLmlkID0gInBpcGVsaW5lIikgJT4lIAogIGZpbHRlcihzZWNvbmRfZmlsdGVyID09ICJQQVNTIikgJT4lIAogIGRwbHlyOjpzZWxlY3QocGlwZWxpbmUsIFBvc2l0aW9uX2hnMzgsIFZhcmlhbnQsIEFGX1RVTU9SLAogICAgICAgICBHZW5jb2RlXzM0X2h1Z29TeW1ib2wsIEdlbmNvZGVfMzRfdmFyaWFudENsYXNzaWZpY2F0aW9uKSAlPiUgCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9ICJwaXBlbGluZSIsIHZhbHVlc19mcm9tID0gIkFGX1RVTU9SIikKCmNvbWJpbmVkLnNudi5kZgpgYGAKClZhcmlhbnRzIHRoYXQgd2VyZSBub3QgZGV0ZWN0ZWQgYnkgUGFyYWJyaWNrcyBidXQgb25seSBieSBjbGFzc2ljIEdBVEs6CgpgYGB7cn0KY29tYmluZWQuc252LmRmICU+JSAKICBmaWx0ZXIoIWlzLm5hKGBjbGFzc2ljIEdBVEtgKSAmIGlzLm5hKGBQYXJhYnJpY2tzIEdBVEtgKSkKYGBgCgpBbmQgdmljZSB2ZXJzYToKCmBgYHtyfQpjb21iaW5lZC5zbnYuZGYgJT4lIAogIGZpbHRlcihpcy5uYShgY2xhc3NpYyBHQVRLYCkgJiAhaXMubmEoYFBhcmFicmlja3MgR0FUS2ApKQpgYGAKClBhcmFicmlja3MgR0FUSyBkZXRlY3RlZCA1MyBtb3JlIGFuZCAnbWlzc2VkJyB0d28gbXV0YXRpb25zLgoKT25lIG9mIHRoZSBtb3N0IGNvbW1vbmx5IG11dGF0ZWQgZ2VuZXMgaW4gdHVtb3JzIGlzICpUUDUzKi4gVXNpbmcgZmlsdGVyIGJ5IGdlbmUgbmFtZSB3ZSBjYW4gY2hlY2sgdGhlIHByZXNlbmNlIG9mICpUUDUzKiBtdXRhdGlvbnMuCgpgYGB7cn0KZmlsdGVyKGNvbWJpbmVkLnNudi5kZiwgR2VuY29kZV8zNF9odWdvU3ltYm9sID09ICJUUDUzIikKYGBgCgpGaW5hbGx5LCBsZXRzIGNvbXBhcmUgdGhlIGFsbGVsaWMgZnJlcXVlbmNpZXMgZm9yIHRoZSB2YXJpYW50cyBkZXRlY3RlZCBieSBib3RoIHBpcGVsaW5lczoKCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD01fQpjb21iaW5lZC5zbnYuZGYgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYGNsYXNzaWMgR0FUS2AsIHkgPSBgUGFyYWJyaWNrcyBHQVRLYCkpICsKICBnZW9tX3BvaW50KAogICAgYWVzKAogICAgICBmaWxsID0gR2VuY29kZV8zNF92YXJpYW50Q2xhc3NpZmljYXRpb24KICAgICksCiAgICBzaGFwZSA9IDIxLAogICAgc2l6ZSA9IDIKICApICsKICB0aGVtZV9idygpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50LCBsaW1pdHMgPSBjKDAsIDEpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwgbGltaXRzID0gYygwLCAxKSkKYGBgCgpXZSBjYW4gbGltaXQgdGhlIHZhcmlhbnQgdHlwZXMgdG8gc2hvdyBvbmx5IHRoZSBiaW9sb2dpY2FsbHkgcmV2ZXZhbnQgbXV0YXRpb25zOgoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTV9CnJlbGV2YW50LnNudi5kZiA8LSBjb21iaW5lZC5zbnYuZGYgJT4lCiAgZmlsdGVyKEdlbmNvZGVfMzRfdmFyaWFudENsYXNzaWZpY2F0aW9uICVpbiUgYygiTUlTU0VOU0UiLCAiTk9OU0VOU0UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIklOX0ZSQU1FX0RFTCIsICJJTl9GUkFNRV9JTlMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZSQU1FX1NISUZUX0RFTCIsICJGUkFNRV9TSElGVF9JTlMiKQogICAgICAgICApCgoKICBnZ3Bsb3QocmVsZXZhbnQuc252LmRmLAogICAgICAgICBhZXMoeCA9IGBjbGFzc2ljIEdBVEtgLCB5ID0gYFBhcmFicmlja3MgR0FUS2ApKSArCiAgZ2VvbV9wb2ludCgKICAgIGFlcygKICAgICAgZmlsbCA9IEdlbmNvZGVfMzRfdmFyaWFudENsYXNzaWZpY2F0aW9uCiAgICApLAogICAgc2hhcGUgPSAyMSwKICAgIHNpemUgPSAyCiAgKSArIAogIGdlb21fdGV4dF9yZXBlbCggIyBhZGQgdGV4dCBsYWJlbHMgZm9yIHNvbWUgb2YgdGhlIHRvcCBBRiBtdXRhdGlvbnMKICAgIGRhdGEgPSBmaWx0ZXIocmVsZXZhbnQuc252LmRmLCBgY2xhc3NpYyBHQVRLYCA+IDAuOSAmIGBQYXJhYnJpY2tzIEdBVEtgID4gMC45KSwKICAgIGFlcyhsYWJlbCA9IEdlbmNvZGVfMzRfaHVnb1N5bWJvbCksIG1heC5vdmVybGFwcyA9IDMwLCBudWRnZV95ID0gLTAuMiwgbnVkZ2VfeCA9IC0wLjYKICApICsKICB0aGVtZV9idygpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50LCBsaW1pdHMgPSBjKDAsIDEpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwgbGltaXRzID0gYygwLCAxKSkKYGBgCgojIFNlc3Npb24gaW5mbwoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCgo=