1 Introduction

This document provides an introduction of the ELMER.data, which contains supporting data for ELMER (Yao, L., Shen, H., Laird, P. W., Farnham, P. J., & Berman, B. P. 2015). ELMER is package using DNA methylation to identify enhancers, and correlates enhancer state with expression of nearby genes to identify one or more transcriptional targets. Transcription factor (TF) binding site analysis of enhancers is coupled with expression analysis of all TFs to infer upstream regulators. ELMER.data provide 3 necessary data for ELMER analysis:

  1. Probes information: files with DNA methylation platforms metadata retrieved from http://zwdzwd.github.io/InfiniumAnnotation (Zhou, Wanding and Laird, Peter W and Shen, Hui 2016).
  2. Probes.motif: motif occurences within \(\pm 250bp\) of probe sites on HM450K/EPIC array aligned against hg19/hg38.
  3. Union.enhancer: A comprehensive list of genomic strong enhancers.

1.1 Installing and loading ELMER.data

To install this package, start R and enter

devtools::install_github(repo = "tiagochst/ELMER.data")
library("ELMER.data")
library("GenomicRanges")

2 Contents

2.1 Probes information

Probes information were retrieved from http://zwdzwd.github.io/InfiniumAnnotation (Zhou, Wanding and Laird, Peter W and Shen, Hui 2016).

for(plat in c("450K","EPIC")) {
  for(genome in c("hg38","hg19")) {
    base <- "http://zwdzwd.io/InfiniumAnnotation/current/"
    if(plat == "EPIC") {
      annotation <- paste0(base,"EPIC/EPIC.manifest.rda")
    } else {
      annotation <- paste0(base,"hm450/hm450.manifest.rda")
    }
    if(genome == "hg38") annotation <- gsub(".rda",".hg38.rda", annotation)
    if(!file.exists(basename(annotation))) {
      if(Sys.info()["sysname"] == "Windows") mode <- "wb" else  mode <- "w"
      downloader::download(annotation, basename(annotation), mode = mode)
    }
  }
}
data("EPIC.manifest")
as.data.frame(EPIC.manifest)
data("EPIC.manifest.hg38")
as.data.frame(EPIC.manifest.hg38)
data("hm450.manifest.hg38")
as.data.frame(hm450.manifest.hg38)
data("hm450.manifest")
as.data.frame(hm450.manifest)

2.2 Probes.motif

Probes.motif provides information for motif occurences within\(\pm 250bp\) of probe sites on HM450K/EPIC array. HOMER (Heinz, Sven and Benner, Christopher and Spann, Nathanael and Bertolino, Eric and Lin, Yin C and Laslo, Peter and Cheng, Jason X and Murre, Cornelis and Singh, Harinder and Glass, Christopher K 2010) was used with a p-value < 0.0001 to scan a \(\pm 250bp\) region around each probe on HM450K/EPIC using HOCOMOCO V10 motif position weight matrices (PWMs) which provides transcription factor (TF) binding models for more than 600 human TFs. This data set is used in get.enriched.motif function in ELMER to calculate Odds Ratio of motif enrichments for a given set of probes. This data is storaged in a sparse matrix with wuth 640 columns, there is one matrix for HM450K aligned to hg19, one for HM450K aligned to hg38, one for EPIC aligned to hg19, one for EPIC aligned to hg38. Each row is each probe regions (annotation of the regions used can be found in this repository) and each column is motif from http://hocomoco.autosome.ru/ (Kulakovskiy, Ivan V and Medvedeva, Yulia A and Schaefer, Ulf and Kasianov, Artem S and Vorontsov, Ilya E and Bajic, Vladimir B and Makeev, Vsevolod 2013). The value 1 indicates the occurrence of a motif in a particular probe and 0 means no occurrence.

data("Probes.motif.hg19.450K")
dim(Probes.motif.hg19.450K)
[1] 435391    640
str(Probes.motif.hg19.450K)
Formal class 'ngCMatrix' [package "Matrix"] with 5 slots
  ..@ i       : int [1:38198775] 13 33 40 44 71 101 137 149 161 163 ...
  ..@ p       : int [1:641] 0 19923 54244 87292 122353 161278 212385 323860 464485 601197 ...
  ..@ Dim     : int [1:2] 435391 640
  ..@ Dimnames:List of 2
  .. ..$ : chr [1:435391] "cg07092448" "cg20389653" "cg13452258" "cg07344096" ...
  .. ..$ : chr [1:640] "AHR_HUMAN.H10MO.B" "AIRE_HUMAN.H10MO.C" "ALX1_HUMAN.H10MO.B" "ALX3_HUMAN.H10MO.D" ...
  ..@ factors : list()
data("Probes.motif.hg38.450K")
dim(Probes.motif.hg38.450K)
[1] 435391    640
str(Probes.motif.hg38.450K)
Formal class 'ngCMatrix' [package "Matrix"] with 5 slots
  ..@ i       : int [1:38199819] 3 28 31 36 37 65 66 77 84 85 ...
  ..@ p       : int [1:641] 0 19916 54239 87297 122364 161361 212466 323956 464581 601305 ...
  ..@ Dim     : int [1:2] 435391 640
  ..@ Dimnames:List of 2
  .. ..$ : chr [1:435391] "cg15451548" "cg07212761" "cg24463320" "cg11787186" ...
  .. ..$ : chr [1:640] "AHR_HUMAN.H10MO.B" "AIRE_HUMAN.H10MO.C" "ALX1_HUMAN.H10MO.B" "ALX3_HUMAN.H10MO.D" ...
  ..@ factors : list()
data("Probes.motif.hg19.EPIC")
dim(Probes.motif.hg19.EPIC)
[1] 786296    640
str(Probes.motif.hg19.EPIC)
Formal class 'ngCMatrix' [package "Matrix"] with 5 slots
  ..@ i       : int [1:71168136] 13 40 93 107 110 111 112 134 173 174 ...
  ..@ p       : int [1:641] 0 29135 107569 190350 276086 371235 478173 660941 848126 1056604 ...
  ..@ Dim     : int [1:2] 786296 640
  ..@ Dimnames:List of 2
  .. ..$ : chr [1:786296] "cg18818484" "cg18977839" "cg25234611" "cg11752380" ...
  .. ..$ : chr [1:640] "AHR_HUMAN.H10MO.B" "AIRE_HUMAN.H10MO.C" "ALX1_HUMAN.H10MO.B" "ALX3_HUMAN.H10MO.D" ...
  ..@ factors : list()
data("Probes.motif.hg38.EPIC")
dim(Probes.motif.hg38.EPIC)
[1] 786296    640
str(Probes.motif.hg38.EPIC)
Formal class 'ngCMatrix' [package "Matrix"] with 5 slots
  ..@ i       : int [1:71165854] 44 89 90 109 129 133 137 141 149 169 ...
  ..@ p       : int [1:641] 0 29132 107562 190310 276041 371155 478091 661176 848358 1056846 ...
  ..@ Dim     : int [1:2] 786296 640
  ..@ Dimnames:List of 2
  .. ..$ : chr [1:786296] "cg10817250" "cg06145569" "cg23606775" "cg09283376" ...
  .. ..$ : chr [1:640] "AHR_HUMAN.H10MO.B" "AIRE_HUMAN.H10MO.C" "ALX1_HUMAN.H10MO.B" "ALX3_HUMAN.H10MO.D" ...
  ..@ factors : list()

The following code was used to create the objects:

getInfiniumAnnotation <- function(plat = "450K", genome = "hg38"){
    message("Loading object: ",file)
    newenv <- new.env()
    if(plat == "EPIC" & genome == "hg19") data("EPIC.manifest", package = "ELMER.data",envir=newenv)
    if(plat == "EPIC" & genome == "hg38") data("EPIC.manifest.hg38", package = "ELMER.data",envir=newenv)
    if(plat == "450K" & genome == "hg19") data("hm450.manifest", package = "ELMER.data",envir=newenv)
    if(plat == "450K" & genome == "hg38") data("hm450.manifest.hg38", package = "ELMER.data",envir=newenv)
    annotation <- get(ls(newenv)[1],envir=newenv)   
    return(annotation)  
}
# To find for each probe the know motif we will use HOMER software (http://homer.salk.edu/homer/)
# Step:
# 1 - get DNA methylation probes annotation with the regions
# 2 - Make a bed file from it
# 3 - Execute section: Finding Instance of Specific Motifs from http://homer.salk.edu/homer/ngs/peakMotifs.html to the HOCOMOCO TF motifs
# Also, As HOMER is using more RAM than the available we will split the files in to 100k probes.
# Obs: for each probe we create a winddow of 500 bp (-size 500) around it. This might lead to false positives, but will not have false negatives.
# The false posives will be removed latter with some statistical tests.
TFBS.motif <- "http://hocomoco.autosome.ru/final_bundle/HUMAN/mono/HOCOMOCOv10_HUMAN_mono_homer_format_0.0001.motif"
if(!file.exists(basename(TFBS.motif))) downloader::download(TFBS.motif,basename(TFBS.motif))
for(plat in c("450K","EPIC")){
    for(gen in c("hg19","hg38")){
      
      file <- paste0(plat,gen,".txt")
      print(file)
      if(!file.exists(file)){
        # STEP 1
        gr <- getInfiniumAnnotation(plat = plat,genome =  gen)
        
        # This will remove masked probes. They have poor quality and might be arbitrarily positioned (Wanding Zhou)
        print(table(gr$MASK.general))
        gr <- gr[!gr$MASK.general]
        print(table(gr$MASK.general))
        
        df <- data.frame(seqnames=seqnames(gr),
                         starts=as.integer(start(gr)),
                         ends=end(gr),
                         names=names(gr),
                         scores=c(rep(".", length(gr))),
                         strands=strand(gr))
        step <- 50000 # nb of lines in each file 50K was selected to not explode RAM
        n <- nrow(df)
        for(j in 0:floor(n/step)){
          # STEP 2
          file.aux <- paste0(plat,gen,"_",j,".bed")
          if(!file.exists(gsub(".bed",".txt",file.aux))){
            end <- ifelse(((j + 1) * step) > n, n,((j + 1) * step))
            write.table(df[((j * step) + 1):end,], file = file.aux, col.names = F, quote = F,row.names = F,sep = "\t")
            
            # STEP 3
            cmd <- paste("source ~/.bash_rc; annotatePeaks.pl" ,file.aux, gen, "-m", basename(TFBS.motif), "-size 500 -cpu 12 >", gsub(".bed",".txt",file.aux))
            system(cmd)
          }
        }
        # We will merge the results from each file into one
        peaks <- NULL
        for(j in 0:floor(n/step)){
          aux <-  readr::read_tsv(paste0(plat,gen,"_",j,".txt"))
          colnames(aux)[1] <- "PeakID"
          if(is.null(peaks)) {
            peaks <- aux
          } else {
            peaks <- rbind(peaks, aux)
          }
        }
        readr::write_tsv(peaks,path=file,col_names = TRUE)
        print("DONE!")
        gc()
      }
  }
}

# This code will read the table with the motifs, save it as a sparce matrix
# and save all as a .rda that will be placed in ELMER.data
for(plat in c("450K","EPIC")){
    for(gen in c("hg19","hg38")){
        file <- paste0(plat,gen,".txt")
        motifs <- readr::read_tsv(file)
        # From 1 to 21 we have annotations then we have 640 motifs
        matrix <- Matrix::Matrix(0, nrow = nrow(motifs), ncol = 640,sparse = TRUE)
        colnames(matrix) <- gsub(" Distance From Peak\\(sequence,strand,conservation\\)","",colnames(motifs)[-c(1:21)])
        rownames(matrix) <- motifs$PeakID
        matrix[!is.na(motifs[,-c(1:21)])] <- 1
        matrix <- as(matrix, "nsparseMatrix")
        assign(paste0("Probes.motif.",gen,".",plat),matrix) # For each probe that there is a bind to the motif, we will add as 1 in the matrix. (Are hg38, hg19,450k,epic matrices equal?)
        rm(matrix)
        rm(motifs)
        gc()
    }
}
save(Probes.motif.hg19.450K, file = "Probes.motif.hg19.450K.rda", compress = "xz")
save(Probes.motif.hg38.450K, file = "Probes.motif.hg38.450K.rda", compress = "xz")
save(Probes.motif.hg19.EPIC, file = "Probes.motif.hg19.EPIC.rda", compress = "xz")
save(Probes.motif.hg38.EPIC, file = "Probes.motif.hg38.EPIC.rda", compress = "xz")
data("Probes.motif.hg19.450K")
as.data.frame(as.matrix(Probes.motif.hg19.450K[1:20,1:20]))

3 Session Information


sessionInfo()
R version 3.4.0 (2017-04-21)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS Sierra 10.12.4

Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

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

other attached packages:
[1] Biostrings_2.44.1   XVector_0.16.0      IRanges_2.10.2      S4Vectors_0.14.3    BiocGenerics_0.22.0 ELMER.data_2.0.0   

loaded via a namespace (and not attached):
 [1] Rcpp_0.12.11               compiler_3.4.0             GenomeInfoDb_1.12.1        base64enc_0.1-3            bitops_1.0-6               tools_3.4.0                zlibbioc_1.22.0            digest_0.6.12              jsonlite_1.5              
[10] evaluate_0.10              memoise_1.1.0              lattice_0.20-35            Matrix_1.2-10              DelayedArray_0.2.6         commonmark_1.2             yaml_2.1.14                GenomeInfoDbData_0.99.0    withr_1.0.2               
[19] stringr_1.2.0              roxygen2_6.0.1             xml2_1.1.1                 knitr_1.16                 devtools_1.13.2            rprojroot_1.2              grid_3.4.0                 Biobase_2.36.2             R6_2.2.1                  
[28] rmarkdown_1.5              magrittr_1.5               backports_1.1.0            htmltools_0.3.6            matrixStats_0.52.2         GenomicRanges_1.28.3       SummarizedExperiment_1.6.3 BiocStyle_2.4.0            stringi_1.1.5             
[37] RCurl_1.95-4.8            

4 References

Heinz, Sven and Benner, Christopher and Spann, Nathanael and Bertolino, Eric and Lin, Yin C and Laslo, Peter and Cheng, Jason X and Murre, Cornelis and Singh, Harinder and Glass, Christopher K. 2010. “Simple Combinations of Lineage-Determining Transcription Factors Prime Cis-Regulatory Elements Required for Macrophage and B Cell Identities.”

Kulakovskiy, Ivan V and Medvedeva, Yulia A and Schaefer, Ulf and Kasianov, Artem S and Vorontsov, Ilya E and Bajic, Vladimir B and Makeev, Vsevolod. 2013. “HOCOMOCO a Comprehensive Collection of Human Transcription Factor Binding Sites Models.”

Yao, L., Shen, H., Laird, P. W., Farnham, P. J., & Berman, B. P. 2015. “Inferring Regulatory Element Landscapes and Transcription Factor Networks from Cancer Methylomes.”

Zhou, Wanding and Laird, Peter W and Shen, Hui. 2016. “Comprehensive Characterization, Annotation and Innovative Use of Infinium DNA Methylation BeadChip Probes.”

LS0tCnRpdGxlOiAiRUxNRVIuZGF0YTogU3VwcG9ydGluZyBkYXRhIGZvciB0aGUgRUxNRVIgcGFja2FnZSIKYXV0aG9yOiAiTGlqaW5nIFlhbywgQmVuIEJlcm1hbiBbYXV0XSwgUGVnZ3kgRmFybmhhbSBbYXV0XUh1aSBTaGVuIFtjdGJdLCBQZXRlciBMYWlyZCBbY3RiXSwgU2ltb24gQ29ldHplZSBbY3RiXSwgVGlhZ28gQ2hlZHJhb3VpIFNpbHZhIFtjdGJdIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvYzogeWVzCiAgICBmaWdfY2FwdGlvbjogeWVzICAgICAKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgZWRpdG9yX29wdGlvbnM6IAogICAgCiAgICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCnJlZmVyZW5jZXM6Ci0gaWQ6IHJlZjEKICB0aXRsZTogSE9DT01PQ08gYSBjb21wcmVoZW5zaXZlIGNvbGxlY3Rpb24gb2YgaHVtYW4gdHJhbnNjcmlwdGlvbiBmYWN0b3IgYmluZGluZyBzaXRlcyBtb2RlbHMKICBhdXRob3I6IAogIC0gZmFtaWx5OiBLdWxha292c2tpeSwgSXZhbiBWIGFuZCBNZWR2ZWRldmEsIFl1bGlhIEEgYW5kIFNjaGFlZmVyLCBVbGYgYW5kIEthc2lhbm92LCBBcnRlbSBTIGFuZCBWb3JvbnRzb3YsIElseWEgRSBhbmQgQmFqaWMsIFZsYWRpbWlyIEIgYW5kIE1ha2VldiwgVnNldm9sb2QKICAgIGdpdmVuOgogIGpvdXJuYWw6IE51Y2xlaWMgYWNpZHMgcmVzZWFyY2gKICB2b2x1bWU6IDQxCiAgbnVtYmVyOiBEMQogIHBhZ2VzOiBEMTk1LS1EMjAyCiAgaXNzdWVkOgogICAgeWVhcjogMjAxMyAgICAKLSBpZDogaGVpbnoyMDEwc2ltcGxlCiAgdGl0bGU6IFNpbXBsZSBjb21iaW5hdGlvbnMgb2YgbGluZWFnZS1kZXRlcm1pbmluZyB0cmFuc2NyaXB0aW9uIGZhY3RvcnMgcHJpbWUgY2lzLXJlZ3VsYXRvcnkgZWxlbWVudHMgcmVxdWlyZWQgZm9yIG1hY3JvcGhhZ2UgYW5kIEIgY2VsbCBpZGVudGl0aWVzCiAgYXV0aG9yOiAKICAtIGZhbWlseTogSGVpbnosIFN2ZW4gYW5kIEJlbm5lciwgQ2hyaXN0b3BoZXIgYW5kIFNwYW5uLCBOYXRoYW5hZWwgYW5kIEJlcnRvbGlubywgRXJpYyBhbmQgTGluLCBZaW4gQyBhbmQgTGFzbG8sIFBldGVyIGFuZCBDaGVuZywgSmFzb24gWCBhbmQgTXVycmUsIENvcm5lbGlzIGFuZCBTaW5naCwgSGFyaW5kZXIgYW5kIEdsYXNzLCBDaHJpc3RvcGhlciBLCiAgICBnaXZlbjoKICBqb3VybmFsOiBNb2xlY3VsYXIgY2VsbAogIHZvbHVtZTogMzgKICBudW1iZXI6IDQKICBwYWdlczogNTc2LS01ODkKICBpc3N1ZWQ6CiAgICB5ZWFyOiAyMDEwICAgIAotIGlkOiBFTE1FUgogIHRpdGxlOiBJbmZlcnJpbmcgcmVndWxhdG9yeSBlbGVtZW50IGxhbmRzY2FwZXMgYW5kIHRyYW5zY3JpcHRpb24gZmFjdG9yIG5ldHdvcmtzIGZyb20gY2FuY2VyIG1ldGh5bG9tZXMKICBhdXRob3I6IAogIC0gZmFtaWx5OiBZYW8sIEwuLCBTaGVuLCBILiwgTGFpcmQsIFAuIFcuLCBGYXJuaGFtLCBQLiBKLiwgJiBCZXJtYW4sIEIuIFAuIAogICAgZ2l2ZW46CiAgam91cm5hbDogR2Vub21lIGJpb2xvZ3kKICB2b2x1bWU6IDE2CiAgbnVtYmVyOiAxCiAgcGFnZXM6IDEwNQogIGlzc3VlZDoKICAgIHllYXI6IDIwMTUgCi0gaWQ6IHdpbmdlbmRlcjIwMTR0ZmNsYXNzCiAgdGl0bGU6IFRGQ2xhc3MgYSBjbGFzc2lmaWNhdGlvbiBvZiBodW1hbiB0cmFuc2NyaXB0aW9uIGZhY3RvcnMgYW5kIHRoZWlyIHJvZGVudCBvcnRob2xvZ3MKICBhdXRob3I6IAogIC0gZmFtaWx5OiBXaW5nZW5kZXIsIEUuLCBTY2hvZXBzLCBULiwgSGF1YnJvY2ssIE0uLCAmIETDtm5pdHosIEoKICAgIGdpdmVuOgogIGpvdXJuYWw6IE51Y2xlaWMgYWNpZHMgcmVzZWFyY2gKICBwYWdlczogZ2t1MTA2NAogIGlzc3VlZDoKICAgIHllYXI6IDIwMTQgCi0gaWQ6IHpob3UyMDE2Y29tcHJlaGVuc2l2ZQogIHRpdGxlOiBDb21wcmVoZW5zaXZlIGNoYXJhY3Rlcml6YXRpb24sIGFubm90YXRpb24gYW5kIGlubm92YXRpdmUgdXNlIG9mIEluZmluaXVtIEROQSBtZXRoeWxhdGlvbiBCZWFkQ2hpcCBwcm9iZXMKICBhdXRob3I6IAogIC0gZmFtaWx5OiBaaG91LCBXYW5kaW5nIGFuZCBMYWlyZCwgUGV0ZXIgVyBhbmQgU2hlbiwgSHVpCiAgICBnaXZlbjoKICBqb3VybmFsOiBOdWNsZWljIEFjaWRzIFJlc2VhcmNoCiAgcGFnZXM6IGdrdzk2NwogIGlzc3VlZDoKICAgIHllYXI6IDIwMTYKdmlnbmV0dGU6ID4KICBcdXNlcGFja2FnZVt1dGY4XXtpbnB1dGVuY30KICAlXFZpZ25ldHRlSW5kZXhFbnRyeXtFTE1FUi5kYXRhOiBTdXBwb3J0aW5nIGRhdGEgZm9yIHRoZSBFTE1FUiBwYWNrYWdlfQogICVcVmlnbmV0dGVFbmdpbmV7a25pdHI6OnJtYXJrZG93bn0KZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tICAgCgpgYGB7ciwgZWNobyA9IEZBTFNFLGhpZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpkZXZ0b29sczo6bG9hZF9hbGwoIi4iKQpgYGAKCiMgSW50cm9kdWN0aW9uCgpUaGlzIGRvY3VtZW50IHByb3ZpZGVzIGFuIGludHJvZHVjdGlvbiBvZiB0aGUgYHIgQmlvY1N0eWxlOjpCaW9jcGtnKCJFTE1FUi5kYXRhIilgLCB3aGljaCBjb250YWlucyAKc3VwcG9ydGluZyBkYXRhIGZvciBgciBCaW9jU3R5bGU6OkJpb2Nwa2coIkVMTUVSIilgIFtARUxNRVJdLiBgciBCaW9jU3R5bGU6OkJpb2Nwa2coIkVMTUVSIilgIGlzIHBhY2thZ2UgdXNpbmcgRE5BIG1ldGh5bGF0aW9uIHRvIAppZGVudGlmeSBlbmhhbmNlcnMsIGFuZCBjb3JyZWxhdGVzIGVuaGFuY2VyIHN0YXRlIHdpdGggZXhwcmVzc2lvbiBvZiBuZWFyYnkgZ2VuZXMgCnRvIGlkZW50aWZ5IG9uZSBvciBtb3JlIHRyYW5zY3JpcHRpb25hbCB0YXJnZXRzLiBUcmFuc2NyaXB0aW9uIGZhY3RvciAoVEYpIGJpbmRpbmcgCnNpdGUgYW5hbHlzaXMgb2YgZW5oYW5jZXJzIGlzIGNvdXBsZWQgd2l0aCBleHByZXNzaW9uIGFuYWx5c2lzIG9mIGFsbCBURnMgdG8gCmluZmVyIHVwc3RyZWFtIHJlZ3VsYXRvcnMuIGByIEJpb2NTdHlsZTo6QmlvY3BrZygiRUxNRVIuZGF0YSIpYCBwcm92aWRlIDMgbmVjZXNzYXJ5IGRhdGEgZm9yIApgciBCaW9jU3R5bGU6OkJpb2Nwa2coIkVMTUVSIilgIGFuYWx5c2lzOgoKMS4gUHJvYmVzIGluZm9ybWF0aW9uOiBmaWxlcyB3aXRoIEROQSBtZXRoeWxhdGlvbiBwbGF0Zm9ybXMgbWV0YWRhdGEgIHJldHJpZXZlZCBmcm9tIFtodHRwOi8vendkendkLmdpdGh1Yi5pby9JbmZpbml1bUFubm90YXRpb25dKGh0dHA6Ly96d2R6d2QuZ2l0aHViLmlvL0luZmluaXVtQW5ub3RhdGlvbikgW0B6aG91MjAxNmNvbXByZWhlbnNpdmVdLgoyLiBQcm9iZXMubW90aWY6IG1vdGlmIG9jY3VyZW5jZXMgd2l0aGluICRccG0gMjUwYnAkIG9mIHByb2JlIHNpdGVzIG9uIEhNNDUwSy9FUElDIGFycmF5IGFsaWduZWQgYWdhaW5zdCBoZzE5L2hnMzguCjMuIFVuaW9uLmVuaGFuY2VyOiBBIGNvbXByZWhlbnNpdmUgbGlzdCBvZiBnZW5vbWljIHN0cm9uZyBlbmhhbmNlcnMuCgojIyBJbnN0YWxsaW5nIGFuZCBsb2FkaW5nIEVMTUVSLmRhdGEKClRvIGluc3RhbGwgdGhpcyBwYWNrYWdlLCBzdGFydCBSIGFuZCBlbnRlcgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKHJlcG8gPSAidGlhZ29jaHN0L0VMTUVSLmRhdGEiKQpsaWJyYXJ5KCJFTE1FUi5kYXRhIikKbGlicmFyeSgiR2Vub21pY1JhbmdlcyIpCmBgYAoKIyBDb250ZW50cwoKIyMgUHJvYmVzIGluZm9ybWF0aW9uCgpQcm9iZXMgaW5mb3JtYXRpb24gd2VyZSByZXRyaWV2ZWQgZnJvbSBbaHR0cDovL3p3ZHp3ZC5naXRodWIuaW8vSW5maW5pdW1Bbm5vdGF0aW9uXShodHRwOi8vendkendkLmdpdGh1Yi5pby9JbmZpbml1bUFubm90YXRpb24pIFtAemhvdTIwMTZjb21wcmVoZW5zaXZlXS4KCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmZvcihwbGF0IGluIGMoIjQ1MEsiLCJFUElDIikpIHsKICBmb3IoZ2Vub21lIGluIGMoImhnMzgiLCJoZzE5IikpIHsKICAgIGJhc2UgPC0gImh0dHA6Ly96d2R6d2QuaW8vSW5maW5pdW1Bbm5vdGF0aW9uL2N1cnJlbnQvIgogICAgaWYocGxhdCA9PSAiRVBJQyIpIHsKICAgICAgYW5ub3RhdGlvbiA8LSBwYXN0ZTAoYmFzZSwiRVBJQy9FUElDLm1hbmlmZXN0LnJkYSIpCiAgICB9IGVsc2UgewogICAgICBhbm5vdGF0aW9uIDwtIHBhc3RlMChiYXNlLCJobTQ1MC9obTQ1MC5tYW5pZmVzdC5yZGEiKQogICAgfQogICAgaWYoZ2Vub21lID09ICJoZzM4IikgYW5ub3RhdGlvbiA8LSBnc3ViKCIucmRhIiwiLmhnMzgucmRhIiwgYW5ub3RhdGlvbikKICAgIGlmKCFmaWxlLmV4aXN0cyhiYXNlbmFtZShhbm5vdGF0aW9uKSkpIHsKICAgICAgaWYoU3lzLmluZm8oKVsic3lzbmFtZSJdID09ICJXaW5kb3dzIikgbW9kZSA8LSAid2IiIGVsc2UgIG1vZGUgPC0gInciCiAgICAgIGRvd25sb2FkZXI6OmRvd25sb2FkKGFubm90YXRpb24sIGJhc2VuYW1lKGFubm90YXRpb24pLCBtb2RlID0gbW9kZSkKICAgIH0KICB9Cn0KYGBgCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpkYXRhKCJFUElDLm1hbmlmZXN0IikKYXMuZGF0YS5mcmFtZShFUElDLm1hbmlmZXN0KQpkYXRhKCJFUElDLm1hbmlmZXN0LmhnMzgiKQphcy5kYXRhLmZyYW1lKEVQSUMubWFuaWZlc3QuaGczOCkKZGF0YSgiaG00NTAubWFuaWZlc3QuaGczOCIpCmFzLmRhdGEuZnJhbWUoaG00NTAubWFuaWZlc3QuaGczOCkKZGF0YSgiaG00NTAubWFuaWZlc3QiKQphcy5kYXRhLmZyYW1lKGhtNDUwLm1hbmlmZXN0KQpgYGAKCiMjIFByb2Jlcy5tb3RpZgoKUHJvYmVzLm1vdGlmIHByb3ZpZGVzIGluZm9ybWF0aW9uIGZvciBtb3RpZiBvY2N1cmVuY2VzIHdpdGhpbiRccG0gMjUwYnAkIG9mIHByb2JlIApzaXRlcyBvbiBITTQ1MEsvRVBJQyBhcnJheS4gSE9NRVIgW0BoZWluejIwMTBzaW1wbGVdIHdhcyB1c2VkIHdpdGggYSBwLXZhbHVlIDwgMC4wMDAxIHRvIHNjYW4gYSAkXHBtIDI1MGJwJCAKcmVnaW9uIGFyb3VuZCBlYWNoIHByb2JlIG9uIEhNNDUwSy9FUElDIHVzaW5nIEhPQ09NT0NPIFYxMCBtb3RpZiBwb3NpdGlvbiB3ZWlnaHQgCm1hdHJpY2VzIChQV01zKSB3aGljaCBwcm92aWRlcyB0cmFuc2NyaXB0aW9uIGZhY3RvciAoVEYpIGJpbmRpbmcgbW9kZWxzIGZvciBtb3JlIHRoYW4gNjAwIGh1bWFuIFRGcy4KVGhpcyBkYXRhIHNldCBpcyB1c2VkIGluIGdldC5lbnJpY2hlZC5tb3RpZiBmdW5jdGlvbiBpbiBgciBCaW9jU3R5bGU6OkJpb2Nwa2coIkVMTUVSIilgIHRvIGNhbGN1bGF0ZSAKT2RkcyBSYXRpbyBvZiBtb3RpZiBlbnJpY2htZW50cyBmb3IgYSBnaXZlbiBzZXQgb2YgcHJvYmVzLiBUaGlzIGRhdGEgaXMgc3RvcmFnZWQgCmluIGEgc3BhcnNlIG1hdHJpeCB3aXRoIHd1dGggNjQwIGNvbHVtbnMsIHRoZXJlIGlzIG9uZSBtYXRyaXggZm9yIEhNNDUwSyBhbGlnbmVkIHRvIGhnMTksIG9uZSBmb3IgSE00NTBLICBhbGlnbmVkIHRvIGhnMzgsCm9uZSBmb3IgRVBJQyBhbGlnbmVkIHRvIGhnMTksIG9uZSBmb3IgRVBJQyAgYWxpZ25lZCB0byBoZzM4LiBFYWNoIHJvdyBpcyBlYWNoIHByb2JlIHJlZ2lvbnMgKGFubm90YXRpb24gb2YgdGhlIHJlZ2lvbnMgdXNlZCBjYW4gYmUgZm91bmQgaW4gClt0aGlzIHJlcG9zaXRvcnldKGh0dHA6Ly96d2R6d2QuZ2l0aHViLmlvL0luZmluaXVtQW5ub3RhdGlvbikpIGFuZCBlYWNoIApjb2x1bW4gaXMgbW90aWYgZnJvbSBbaHR0cDovL2hvY29tb2NvLmF1dG9zb21lLnJ1L10oSE9DT01PQ08pIFtAcmVmMV0uIFRoZSB2YWx1ZSAxIGluZGljYXRlcyB0aGUgb2NjdXJyZW5jZSAKb2YgYSBtb3RpZiBpbiBhIHBhcnRpY3VsYXIgcHJvYmUgYW5kIDAgbWVhbnMgbm8gb2NjdXJyZW5jZS4KCmBgYHtyfQpkYXRhKCJQcm9iZXMubW90aWYuaGcxOS40NTBLIikKZGltKFByb2Jlcy5tb3RpZi5oZzE5LjQ1MEspCnN0cihQcm9iZXMubW90aWYuaGcxOS40NTBLKQpgYGAKCmBgYHtyfQpkYXRhKCJQcm9iZXMubW90aWYuaGczOC40NTBLIikKZGltKFByb2Jlcy5tb3RpZi5oZzM4LjQ1MEspCnN0cihQcm9iZXMubW90aWYuaGczOC40NTBLKQpgYGAKCmBgYHtyfQpkYXRhKCJQcm9iZXMubW90aWYuaGcxOS5FUElDIikKZGltKFByb2Jlcy5tb3RpZi5oZzE5LkVQSUMpCnN0cihQcm9iZXMubW90aWYuaGcxOS5FUElDKQpgYGAKCmBgYHtyfQpkYXRhKCJQcm9iZXMubW90aWYuaGczOC5FUElDIikKZGltKFByb2Jlcy5tb3RpZi5oZzM4LkVQSUMpCnN0cihQcm9iZXMubW90aWYuaGczOC5FUElDKQpgYGAKClRoZSBmb2xsb3dpbmcgY29kZSB3YXMgdXNlZCB0byBjcmVhdGUgdGhlIG9iamVjdHM6CmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmdldEluZmluaXVtQW5ub3RhdGlvbiA8LSBmdW5jdGlvbihwbGF0ID0gIjQ1MEsiLCBnZW5vbWUgPSAiaGczOCIpewogICAgbWVzc2FnZSgiTG9hZGluZyBvYmplY3Q6ICIsZmlsZSkKICAgIG5ld2VudiA8LSBuZXcuZW52KCkKICAgIGlmKHBsYXQgPT0gIkVQSUMiICYgZ2Vub21lID09ICJoZzE5IikgZGF0YSgiRVBJQy5tYW5pZmVzdCIsIHBhY2thZ2UgPSAiRUxNRVIuZGF0YSIsZW52aXI9bmV3ZW52KQogICAgaWYocGxhdCA9PSAiRVBJQyIgJiBnZW5vbWUgPT0gImhnMzgiKSBkYXRhKCJFUElDLm1hbmlmZXN0LmhnMzgiLCBwYWNrYWdlID0gIkVMTUVSLmRhdGEiLGVudmlyPW5ld2VudikKICAgIGlmKHBsYXQgPT0gIjQ1MEsiICYgZ2Vub21lID09ICJoZzE5IikgZGF0YSgiaG00NTAubWFuaWZlc3QiLCBwYWNrYWdlID0gIkVMTUVSLmRhdGEiLGVudmlyPW5ld2VudikKICAgIGlmKHBsYXQgPT0gIjQ1MEsiICYgZ2Vub21lID09ICJoZzM4IikgZGF0YSgiaG00NTAubWFuaWZlc3QuaGczOCIsIHBhY2thZ2UgPSAiRUxNRVIuZGF0YSIsZW52aXI9bmV3ZW52KQogICAgYW5ub3RhdGlvbiA8LSBnZXQobHMobmV3ZW52KVsxXSxlbnZpcj1uZXdlbnYpICAgCiAgICByZXR1cm4oYW5ub3RhdGlvbikgIAp9CiMgVG8gZmluZCBmb3IgZWFjaCBwcm9iZSB0aGUga25vdyBtb3RpZiB3ZSB3aWxsIHVzZSBIT01FUiBzb2Z0d2FyZSAoaHR0cDovL2hvbWVyLnNhbGsuZWR1L2hvbWVyLykKIyBTdGVwOgojIDEgLSBnZXQgRE5BIG1ldGh5bGF0aW9uIHByb2JlcyBhbm5vdGF0aW9uIHdpdGggdGhlIHJlZ2lvbnMKIyAyIC0gTWFrZSBhIGJlZCBmaWxlIGZyb20gaXQKIyAzIC0gRXhlY3V0ZSBzZWN0aW9uOiBGaW5kaW5nIEluc3RhbmNlIG9mIFNwZWNpZmljIE1vdGlmcyBmcm9tIGh0dHA6Ly9ob21lci5zYWxrLmVkdS9ob21lci9uZ3MvcGVha01vdGlmcy5odG1sIHRvIHRoZSBIT0NPTU9DTyBURiBtb3RpZnMKIyBBbHNvLCBBcyBIT01FUiBpcyB1c2luZyBtb3JlIFJBTSB0aGFuIHRoZSBhdmFpbGFibGUgd2Ugd2lsbCBzcGxpdCB0aGUgZmlsZXMgaW4gdG8gMTAwayBwcm9iZXMuCiMgT2JzOiBmb3IgZWFjaCBwcm9iZSB3ZSBjcmVhdGUgYSB3aW5kZG93IG9mIDUwMCBicCAoLXNpemUgNTAwKSBhcm91bmQgaXQuIFRoaXMgbWlnaHQgbGVhZCB0byBmYWxzZSBwb3NpdGl2ZXMsIGJ1dCB3aWxsIG5vdCBoYXZlIGZhbHNlIG5lZ2F0aXZlcy4KIyBUaGUgZmFsc2UgcG9zaXZlcyB3aWxsIGJlIHJlbW92ZWQgbGF0dGVyIHdpdGggc29tZSBzdGF0aXN0aWNhbCB0ZXN0cy4KVEZCUy5tb3RpZiA8LSAiaHR0cDovL2hvY29tb2NvLmF1dG9zb21lLnJ1L2ZpbmFsX2J1bmRsZS9IVU1BTi9tb25vL0hPQ09NT0NPdjEwX0hVTUFOX21vbm9faG9tZXJfZm9ybWF0XzAuMDAwMS5tb3RpZiIKaWYoIWZpbGUuZXhpc3RzKGJhc2VuYW1lKFRGQlMubW90aWYpKSkgZG93bmxvYWRlcjo6ZG93bmxvYWQoVEZCUy5tb3RpZixiYXNlbmFtZShURkJTLm1vdGlmKSkKZm9yKHBsYXQgaW4gYygiNDUwSyIsIkVQSUMiKSl7CiAgICBmb3IoZ2VuIGluIGMoImhnMTkiLCJoZzM4IikpewogICAgICAKICAgICAgZmlsZSA8LSBwYXN0ZTAocGxhdCxnZW4sIi50eHQiKQogICAgICBwcmludChmaWxlKQogICAgICBpZighZmlsZS5leGlzdHMoZmlsZSkpewogICAgICAgICMgU1RFUCAxCiAgICAgICAgZ3IgPC0gZ2V0SW5maW5pdW1Bbm5vdGF0aW9uKHBsYXQgPSBwbGF0LGdlbm9tZSA9ICBnZW4pCiAgICAgICAgCiAgICAgICAgIyBUaGlzIHdpbGwgcmVtb3ZlIG1hc2tlZCBwcm9iZXMuIFRoZXkgaGF2ZSBwb29yIHF1YWxpdHkgYW5kIG1pZ2h0IGJlIGFyYml0cmFyaWx5IHBvc2l0aW9uZWQgKFdhbmRpbmcgWmhvdSkKICAgICAgICBwcmludCh0YWJsZShnciRNQVNLLmdlbmVyYWwpKQogICAgICAgIGdyIDwtIGdyWyFnciRNQVNLLmdlbmVyYWxdCiAgICAgICAgcHJpbnQodGFibGUoZ3IkTUFTSy5nZW5lcmFsKSkKICAgICAgICAKICAgICAgICBkZiA8LSBkYXRhLmZyYW1lKHNlcW5hbWVzPXNlcW5hbWVzKGdyKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0cz1hcy5pbnRlZ2VyKHN0YXJ0KGdyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICBlbmRzPWVuZChnciksCiAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lcz1uYW1lcyhnciksCiAgICAgICAgICAgICAgICAgICAgICAgICBzY29yZXM9YyhyZXAoIi4iLCBsZW5ndGgoZ3IpKSksCiAgICAgICAgICAgICAgICAgICAgICAgICBzdHJhbmRzPXN0cmFuZChncikpCiAgICAgICAgc3RlcCA8LSA1MDAwMCAjIG5iIG9mIGxpbmVzIGluIGVhY2ggZmlsZSA1MEsgd2FzIHNlbGVjdGVkIHRvIG5vdCBleHBsb2RlIFJBTQogICAgICAgIG4gPC0gbnJvdyhkZikKICAgICAgICBmb3IoaiBpbiAwOmZsb29yKG4vc3RlcCkpewogICAgICAgICAgIyBTVEVQIDIKICAgICAgICAgIGZpbGUuYXV4IDwtIHBhc3RlMChwbGF0LGdlbiwiXyIsaiwiLmJlZCIpCiAgICAgICAgICBpZighZmlsZS5leGlzdHMoZ3N1YigiLmJlZCIsIi50eHQiLGZpbGUuYXV4KSkpewogICAgICAgICAgICBlbmQgPC0gaWZlbHNlKCgoaiArIDEpICogc3RlcCkgPiBuLCBuLCgoaiArIDEpICogc3RlcCkpCiAgICAgICAgICAgIHdyaXRlLnRhYmxlKGRmWygoaiAqIHN0ZXApICsgMSk6ZW5kLF0sIGZpbGUgPSBmaWxlLmF1eCwgY29sLm5hbWVzID0gRiwgcXVvdGUgPSBGLHJvdy5uYW1lcyA9IEYsc2VwID0gIlx0IikKICAgICAgICAgICAgCiAgICAgICAgICAgICMgU1RFUCAzCiAgICAgICAgICAgIGNtZCA8LSBwYXN0ZSgic291cmNlIH4vLmJhc2hfcmM7IGFubm90YXRlUGVha3MucGwiICxmaWxlLmF1eCwgZ2VuLCAiLW0iLCBiYXNlbmFtZShURkJTLm1vdGlmKSwgIi1zaXplIDUwMCAtY3B1IDEyID4iLCBnc3ViKCIuYmVkIiwiLnR4dCIsZmlsZS5hdXgpKQogICAgICAgICAgICBzeXN0ZW0oY21kKQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgICAjIFdlIHdpbGwgbWVyZ2UgdGhlIHJlc3VsdHMgZnJvbSBlYWNoIGZpbGUgaW50byBvbmUKICAgICAgICBwZWFrcyA8LSBOVUxMCiAgICAgICAgZm9yKGogaW4gMDpmbG9vcihuL3N0ZXApKXsKICAgICAgICAgIGF1eCA8LSAgcmVhZHI6OnJlYWRfdHN2KHBhc3RlMChwbGF0LGdlbiwiXyIsaiwiLnR4dCIpKQogICAgICAgICAgY29sbmFtZXMoYXV4KVsxXSA8LSAiUGVha0lEIgogICAgICAgICAgaWYoaXMubnVsbChwZWFrcykpIHsKICAgICAgICAgICAgcGVha3MgPC0gYXV4CiAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBwZWFrcyA8LSByYmluZChwZWFrcywgYXV4KQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZWFkcjo6d3JpdGVfdHN2KHBlYWtzLHBhdGg9ZmlsZSxjb2xfbmFtZXMgPSBUUlVFKQogICAgICAgIHByaW50KCJET05FISIpCiAgICAgICAgZ2MoKQogICAgICB9CiAgfQp9CgojIFRoaXMgY29kZSB3aWxsIHJlYWQgdGhlIHRhYmxlIHdpdGggdGhlIG1vdGlmcywgc2F2ZSBpdCBhcyBhIHNwYXJjZSBtYXRyaXgKIyBhbmQgc2F2ZSBhbGwgYXMgYSAucmRhIHRoYXQgd2lsbCBiZSBwbGFjZWQgaW4gRUxNRVIuZGF0YQpmb3IocGxhdCBpbiBjKCI0NTBLIiwiRVBJQyIpKXsKICAgIGZvcihnZW4gaW4gYygiaGcxOSIsImhnMzgiKSl7CiAgICAgICAgZmlsZSA8LSBwYXN0ZTAocGxhdCxnZW4sIi50eHQiKQogICAgICAgIG1vdGlmcyA8LSByZWFkcjo6cmVhZF90c3YoZmlsZSkKICAgICAgICAjIEZyb20gMSB0byAyMSB3ZSBoYXZlIGFubm90YXRpb25zIHRoZW4gd2UgaGF2ZSA2NDAgbW90aWZzCiAgICAgICAgbWF0cml4IDwtIE1hdHJpeDo6TWF0cml4KDAsIG5yb3cgPSBucm93KG1vdGlmcyksIG5jb2wgPSA2NDAsc3BhcnNlID0gVFJVRSkKICAgICAgICBjb2xuYW1lcyhtYXRyaXgpIDwtIGdzdWIoIiBEaXN0YW5jZSBGcm9tIFBlYWtcXChzZXF1ZW5jZSxzdHJhbmQsY29uc2VydmF0aW9uXFwpIiwiIixjb2xuYW1lcyhtb3RpZnMpWy1jKDE6MjEpXSkKICAgICAgICByb3duYW1lcyhtYXRyaXgpIDwtIG1vdGlmcyRQZWFrSUQKICAgICAgICBtYXRyaXhbIWlzLm5hKG1vdGlmc1ssLWMoMToyMSldKV0gPC0gMQogICAgICAgIG1hdHJpeCA8LSBhcyhtYXRyaXgsICJuc3BhcnNlTWF0cml4IikKICAgICAgICBhc3NpZ24ocGFzdGUwKCJQcm9iZXMubW90aWYuIixnZW4sIi4iLHBsYXQpLG1hdHJpeCkgIyBGb3IgZWFjaCBwcm9iZSB0aGF0IHRoZXJlIGlzIGEgYmluZCB0byB0aGUgbW90aWYsIHdlIHdpbGwgYWRkIGFzIDEgaW4gdGhlIG1hdHJpeC4gKEFyZSBoZzM4LCBoZzE5LDQ1MGssZXBpYyBtYXRyaWNlcyBlcXVhbD8pCiAgICAgICAgcm0obWF0cml4KQogICAgICAgIHJtKG1vdGlmcykKICAgICAgICBnYygpCiAgICB9Cn0Kc2F2ZShQcm9iZXMubW90aWYuaGcxOS40NTBLLCBmaWxlID0gIlByb2Jlcy5tb3RpZi5oZzE5LjQ1MEsucmRhIiwgY29tcHJlc3MgPSAieHoiKQpzYXZlKFByb2Jlcy5tb3RpZi5oZzM4LjQ1MEssIGZpbGUgPSAiUHJvYmVzLm1vdGlmLmhnMzguNDUwSy5yZGEiLCBjb21wcmVzcyA9ICJ4eiIpCnNhdmUoUHJvYmVzLm1vdGlmLmhnMTkuRVBJQywgZmlsZSA9ICJQcm9iZXMubW90aWYuaGcxOS5FUElDLnJkYSIsIGNvbXByZXNzID0gInh6IikKc2F2ZShQcm9iZXMubW90aWYuaGczOC5FUElDLCBmaWxlID0gIlByb2Jlcy5tb3RpZi5oZzM4LkVQSUMucmRhIiwgY29tcHJlc3MgPSAieHoiKQpgYGAKCmBgYHtyfQpkYXRhKCJQcm9iZXMubW90aWYuaGcxOS40NTBLIikKYXMuZGF0YS5mcmFtZShhcy5tYXRyaXgoUHJvYmVzLm1vdGlmLmhnMTkuNDUwS1sxOjIwLDE6MjBdKSkKYGBgCgoKIyBTZXNzaW9uIEluZm9ybWF0aW9uCioqKioqKgpgYGB7ciBzZXNzaW9uSW5mb30Kc2Vzc2lvbkluZm8oKQpgYGAKCiMgUmVmZXJlbmNlcwo=