The purpose of this document is to generate, from the data included here, many of the figures, tables, and supplementary documents present in the StateHub / StatePaintR paper. All code is included to generate the figures and tables, see the “Code” button on the top right of the page to download the .Rmd file that generates this document.


What is StateHub / StatePaintR?

Genome annotation is critical to understand the function of disease variants, especially for clinical applications. To meet this need there are segmentations available from public consortia reflecting varying unsupervised approaches to functional annotation based on epigenetics data, but there remains a need for transparent, reproducible, and easily interpreted genomic maps of the functional biology of chromatin. We introduce here methods for defining chromatin state with a combinatorial epigenomic model using an annotation tool, StatePaintR and a website database, StateHub. Annotations are fully documented with change history and versioning, authorship information, and original source files. The tool calculates quantitative state scores based on genome-wide ranking, allowing prioritization and enrichment testing, facilitating quantitative analysis. StateHub hosts annotation tracks for major public consortia as a resource, and allows users to submit their own alternative models.

A preprint is available on bioRxiv

Setup of StatePaintR environment

We begin by loading the required packages, not all of these are required to simply run StatePaintR, but they will be necessary for doing some of the analysis that follows.

library(GenomicRanges)
library(biomaRt)
library(rtracklayer)
library(RColorBrewer)
library(ggplot2)
library(stringr)
library(tidyr)
library(dplyr)
library(httr)
library(readr)
library(devtools)

And then we install StatePaintR, currently from GitHub.

install_github("Simon-Coetzee/StatePaintR")
library(StatePaintR)

Finally we download all of the data required to run this vignette:

download.file("https://s3-us-west-2.amazonaws.com/statehub-trackhub/statepaintr_data.tar.gz", "statepaintr_data.tar.gz")
trying URL 'https://s3-us-west-2.amazonaws.com/statehub-trackhub/statepaintr_data.tar.gz'
Content type 'application/gzip' length 115305878 bytes (110.0 MB)
==================================================
downloaded 110.0 MB
untar("statepaintr_data.tar.gz", compressed = "gzip")
data.frame(FILES = list.files("data", all.files = FALSE, recursive = TRUE))

With This complete, we begin analysis.

Brief overview of running StatePaintR

The process of running StatePaintR falls into three basic steps. ## Download the decision matrix We first download the decision matrix by indicating the model’s unique ID as indicated on the statehub website.

decisionmatrix <- get.decision.matrix(search = "5813b67f46e0fb06b493ceb0")

Table 3 - Performance of enhancer predictions

VISTA validated enhancers

In order to determine a basis for true positive enhancers we use enhancers validated by the VISTA enhancer browser. We can begin by reading the VISTA data from a bed file. The original data comes from the ENCODE annotation file set ENCSR964TTF

vista.enhancers <- read_tsv("data/VISTA_invivo_tested.bed", col_names = FALSE)
vista.enhancers

This gives us the genomic locations of vista enhancers, in columns 1:3; then the name of the enhancer in 4. Column 5 is True - 1 or False 0 indicating if the enhancer was found to have activity. Columns 7 and 8 are redundant with 2 and 3, column 9 assigns a color based upon column 5. Column 10 indicates the tissues wherein the activity of the enhancer was observed. Since this is a little bit messy, we clean up the format, and covert it to a GenomicRanges object for further analysis.

# remove commas and spaces
vista.validation <- stringr::str_replace(vista.enhancers$X10,
                                         pattern = "ganglion, cranial",
                                         replacement = "ganglion-cranial")
# split on the remaining commas in order retrieve all of the tissues
vista.validation <- stringr::str_split(vista.validation, ",")
# remove parenthesis and clarifiying terms
vista.validation <- lapply(vista.validation, function(x) {
  x <- stringr::str_replace(x, " \\(.*\\)", "")
  x <- stringr::str_trim(x, side = "both")
  return(x)
})
# remove brackets and scores
vista.validation <- lapply(vista.validation, function(x) {
  x <- stringr::str_replace(x, "\\[.*\\]", "")
})
# create an empty matrix with rows representing enhancers, 
# columns representing tissues that they could be active in
vista.enhancers.matrix <- matrix(0L,
                                 nrow = nrow(vista.enhancers),
                                 ncol = length(unique(unlist(vista.validation))))
colnames(vista.enhancers.matrix) <- unique(unlist(vista.validation))
# if an enhancer was found to have activity by VISTA, 
# indicate that with a 1 in the appropriate tissue
for (enhancer in seq_along(vista.validation)) {
  vista.enhancers.matrix[enhancer, vista.validation[[enhancer]]] <- 1L
}
# create our Genomic Ranges object
vista.enhancers <- GRanges(seqnames = vista.enhancers$X1,
                   ranges = IRanges(start = vista.enhancers$X2,
                                    end = vista.enhancers$X3),
                   name = vista.enhancers$X4,
                   validated = vista.enhancers$X5,
                   seqinfo = Seqinfo(genome = "mm10"))
mcols(vista.enhancers) <- cbind(as.data.frame(mcols(vista.enhancers)),
                                vista.enhancers.matrix)
vista.enhancers[, 1:4]
GRanges object with 2554 ranges and 4 metadata columns:
         seqnames                 ranges strand |        name validated  negative neural tube
            <Rle>              <IRanges>  <Rle> | <character> <integer> <integer>   <integer>
     [1]     chr1   [ 5021600,  5024300]      * |       912:2         0         1           0
     [2]     chr1   [ 6729232,  6730317]      * |       698:1         1         0           1
     [3]     chr1   [ 9648223,  9650965]      * |      1546:2         1         0           1
     [4]     chr1   [11026040, 11028544]      * |       663:2         0         1           0
     [5]     chr1   [12614812, 12617322]      * |       664:2         0         1           0
     ...      ...                    ...    ... .         ...       ...       ...         ...
  [2550]     chrX [109969648, 109970362]      * |       871:1         0         1           0
  [2551]     chrX [110076960, 110081419]      * |       426:1         1         0           0
  [2552]     chrX [110318086, 110319120]      * |       554:1         0         1           0
  [2553]     chrX [110377387, 110378071]      * |       585:1         0         1           0
  [2554]     chrX [110816983, 110818738]      * |      1029:1         0         1           0
  -------
  seqinfo: 66 sequences (1 circular) from mm10 genome

We will also create a column that represents the amalgamation of several different tissues: ear, eye, branchial arch, nose, and facial mesenchyme, that we will be using as the validation set for ChIP-Seq conducted in the embryonic facial prominence.

face.vista <- c("ear", "eye", "branchial arch", "nose", "facial mesenchyme")
e.f.p <- rowSums(as.matrix(mcols(vista.enhancers)[, face.vista])) > 0
mcols(vista.enhancers)[, "embryonic facial prominence"] <- as.integer(e.f.p)
vista.enhancers[, 27]
GRanges object with 2554 ranges and 1 metadata column:
         seqnames                 ranges strand | embryonic facial prominence
            <Rle>              <IRanges>  <Rle> |                   <integer>
     [1]     chr1   [ 5021600,  5024300]      * |                           0
     [2]     chr1   [ 6729232,  6730317]      * |                           0
     [3]     chr1   [ 9648223,  9650965]      * |                           0
     [4]     chr1   [11026040, 11028544]      * |                           0
     [5]     chr1   [12614812, 12617322]      * |                           0
     ...      ...                    ...    ... .                         ...
  [2550]     chrX [109969648, 109970362]      * |                           0
  [2551]     chrX [110076960, 110081419]      * |                           0
  [2552]     chrX [110318086, 110319120]      * |                           0
  [2553]     chrX [110377387, 110378071]      * |                           0
  [2554]     chrX [110816983, 110818738]      * |                           0
  -------
  seqinfo: 66 sequences (1 circular) from mm10 genome

ENCODE ChIP-Seq and DNase-seq data

Using the data made available by the ENCODE project we can retrieve data on multiple Histone ChIP-Seq and sometimes DNase-seq for relevant tissues in order to make enhancer predictions.

Dataset Accession Number
embryonic mouse neural tube (11.5 day) ENCSR215ZYV
embryonic mouse midbrain (11.5 day) ENCSR843IAS
embryonic mouse hindbrain (11.5 day) ENCSR501OPC
embryonic mouse limb (11.5 day) ENCSR283NCE
embryonic mouse heart (11.5 day) ENCSR016LTR

Narrowpeak calls for this data and our manifest are included with this document. In order to use DNase-seq when available, we used the IDR process to merge replicates.

manifest <- "data/mouse.idr/IDR.Manifest.txt"
read.table(manifest,
           sep = "\t",
           stringsAsFactors = FALSE,
           header = TRUE)

Acquiring a StateHub model

We can begin our enhancer predictions by segmenting the genome for these samples. We have our manifest, now all we need is a decisionMatrix from StateHub to define the rules for segmenting the genome.

dm <- get.decision.matrix("5813b67f46e0fb06b493ceb0")
dm
 ID: 5813b67f46e0fb06b493ceb0 
 Name: Focused Poised Promoter Model 
 Author: Ben Berman - Cedars-Sinai Center for Bioinformatics and Functional Genomics 
 Revision: Oct 28, 2016 11:24:11 PM 
 Description: This model has focused poised regions that require narrow H3K27me3 regions to be present to be called. 

Our decision matrix is downloaded, but in order to score our enhancers we make some modifications. We want to exclude the “Regulatory” mark from our scoring process, and we want keep DNase-seq peaks intact as the core of our features.

dm <- doNotScore(dm, "Regulatory")
dm <- doNotSplit(dm, "Core")
dm
 ID: 5813b67f46e0fb06b493ceb0 
 Name: Focused Poised Promoter Model 
 Author: Ben Berman - Cedars-Sinai Center for Bioinformatics and Functional Genomics 
 Revision: Oct 28, 2016 11:24:11 PM 
 Description: This model has focused poised regions that require narrow H3K27me3 regions to be present to be called. 
 Not Scoring: Regulatory* 
 Not Splitting: [Core] 

Running StatePaintR to get enhancer predictions and scores

With preparation complete, we can now segment the genome for our samples defined in the manifest.

mouse.embryo.states <- PaintStates(manifest = manifest, 
                                   decisionMatrix = dm, 
                                   scoreStates = TRUE,
                                   progress = FALSE)
names(mouse.embryo.states) <- str_replace(names(mouse.embryo.states), " embryo", "")
mouse.embryo.states$heart
GRanges object with 497638 ranges and 3 metadata columns:
                 seqnames             ranges strand |         name       state     score
                    <Rle>          <IRanges>  <Rle> |  <character> <character> <numeric>
       [1]           chr1 [3008951, 3009184]      * | heart embryo         HET       964
       [2]           chr1 [3047354, 3047644]      * | heart embryo         HET       951
       [3]           chr1 [3120458, 3120776]      * | heart embryo         EWR         0
       [4]           chr1 [3212581, 3212786]      * | heart embryo         EWR         0
       [5]           chr1 [3301150, 3301344]      * | heart embryo         HET       951
       ...            ...                ...    ... .          ...         ...       ...
  [497634] chrUn_JH584304     [65053, 66952]      * | heart embryo         HET       986
  [497635] chrUn_JH584304     [67363, 68071]      * | heart embryo         HET       989
  [497636] chrUn_JH584304     [68072, 68265]      * | heart embryo         TRS       756
  [497637] chrUn_JH584304     [68266, 68579]      * | heart embryo         HET       989
  [497638] chrUn_JH584304     [98007, 98405]      * | heart embryo         HET       945
  -------
  seqinfo: 66 sequences (1 circular) from mm10 genome

Comparing Predictions to VISTA

All of our model tuning was done on a selection of 100 valid enhancers for each tissue type, so we’ll exclude those 100 from our subsequent comparisons.

seed <- 42
train.enhancers <- list()
test.enhancers <- list()
set.seed(seed)
for (tissue in names(mouse.embryo.states)) {
  vista.enhancers.train <- vista.enhancers[, tissue]
  vista.enhancers.train <- sample(which(mcols(vista.enhancers.train)[, tissue] == 1L),
                                  size = 100)
  train.enhancers <- c(train.enhancers, list(vista.enhancers.train))
  names(train.enhancers)[length(train.enhancers)] <- tissue
  vista.enhancers.test <- c(1:length(vista.enhancers))[-vista.enhancers.train]
  test.enhancers <- c(test.enhancers, list(vista.enhancers.test))
  names(test.enhancers)[length(test.enhancers)] <- tissue
}

Evaluation of our models, and the external enhancer predictions against which we compare, is done with Precision-Recall-Gain Curves 1.

StatePaintR Predictions

Here we generate the StatePaintR predictions, using the states EAR, EARC, AR, and ARC as our predicted enhancers. This also prepares the data for plotting in ggplot2 by extracting the precision gain and recall gain, and the convex hull. Additionally we look at the area under the precision recall gain curve to get an idea of the accuracy.

plot.data.sp <- PRG(states = mouse.embryo.states,
                    comparison = vista.enhancers,
                    state.select = c("EARC", "ARC", "AR", "EAR"),
                    comparison.select = test.enhancers)
plot.data.sp$auprg
      heart   hindbrain        limb    midbrain neural tube 
  0.8775036   0.7903943   0.8565165   0.8369033   0.8443555 

Load Enhancer Predictions

ENCODE v3 Enhancer-like regions

We can so something similar for evaluating ENCODE candiate enhancer calls We used the following data from the ENCODE data portal:

Enhancer-like regions Accession Number
using DNase and H3K27ac for neural tube (11.5 day) ENCFF786KUB
using DNase and H3K27ac for midbrain (11.5 day) ENCFF733UJT
using DNase and H3K27ac for hindbrain (11.5 day) ENCFF324INM
using DNase and H3K27ac for limb (11.5 day) ENCFF520EGD
using H3K27ac for heart (11.5 day) ENCSR312DDF

So we begin by downloading this data, and coverting it into GRanges objects

encode.enhancers <- c("neural tube" = "ENCFF786KUB", 
                      midbrain      = "ENCFF733UJT", 
                      hindbrain     = "ENCFF324INM",
                      limb          = "ENCFF520EGD",
                      heart         = "ENCFF435VGC")
encode.enhancers <- sapply(encode.enhancers, function(x) {
  encode <- "https://www.encodeproject.org"
  x <- content(GET(encode, path = x))$href
  return(paste0(encode, x))
})
encode.enhancers <- lapply(encode.enhancers, function(x) {
  x <- read_tsv(x, col_names = FALSE)
  x$X5 <- nrow(x):1
  x <- GRanges(seqnames = x$X1,
               ranges = IRanges(start = x$X2,
                                end = x$X3),
               score = x$X5,
               seqinfo = Seqinfo(genome = "mm10"))
  return(x)
})
encode.enhancers$heart
GRanges object with 33492 ranges and 1 metadata column:
          seqnames                 ranges strand |     score
             <Rle>              <IRanges>  <Rle> | <integer>
      [1]     chr4 [155233365, 155237530]      * |     33492
      [2]    chr15 [ 77034916,  77040989]      * |     33491
      [3]    chr17 [ 46854219,  46859321]      * |     33490
      [4]     chr3 [ 87149351,  87155573]      * |     33489
      [5]    chr18 [ 75497565,  75507425]      * |     33488
      ...      ...                    ...    ... .       ...
  [33488]    chr17 [ 26123469,  26123919]      * |         5
  [33489]    chr11 [ 18888501,  18888937]      * |         4
  [33490]     chr5 [113650662, 113651045]      * |         3
  [33491]    chr14 [ 33412655,  33413007]      * |         2
  [33492]    chr17 [ 43568116,  43568477]      * |         1
  -------
  seqinfo: 66 sequences (1 circular) from mm10 genome

REPTILE Enhancer Predictions

Regulatory element prediction based on tissue-specific local epigenetic marks (REPTILE) is described in Improved regulatory element prediction based on tissue-specific local epigenomic signatures, and integrates histone modification and whole-genome cytosine DNA methylation profiles to identify the precise location of enhancers. This paper also includes results for DELTA, RFECS, and CSI-ANN which we will be compairing against.

reptile.enhancers <- c("neural tube" = "NT", 
                       midbrain      = "MB", 
                       hindbrain     = "HB",
                       limb          = "LM",
                       heart         = "HT")
reptile.enhancers <- lapply(reptile.enhancers, function(x) {
  file.path <- file.path("data", "enhancer_predictions", "REPTILE", paste0("REPTILE_pred_E11_5_", x, ".bed"))
  x <- read_tsv(file.path, col_names = FALSE)
  x <- GRanges(seqnames = x$X1,
               ranges = IRanges(start = x$X2,
                                end = x$X3),
               score = x$X5,
               enhancername = x$X4)
  return(x)
  
})
reptile.enhancers$heart
GRanges object with 51551 ranges and 2 metadata columns:
          seqnames                 ranges strand |     score    enhancername
             <Rle>              <IRanges>  <Rle> | <numeric>     <character>
      [1]     chr1     [3120334, 3120785]      * |    0.5165           dmr_7
      [2]     chr1     [4412200, 4414200]      * |    0.5075     bin_2671882
      [3]     chr1     [4432585, 4433294]      * |    0.6330 dmr_117,dmr_118
      [4]     chr1     [4493100, 4495100]      * |    0.5530     bin_2672691
      [5]     chr1     [4571100, 4573100]      * |    0.5015     bin_2673471
      ...      ...                    ...    ... .       ...             ...
  [51547]     chrX [169910900, 169912900]      * |    0.5300     bin_1699109
  [51548]     chrX [169935400, 169937400]      * |    0.5985     bin_1699354
  [51549]     chrY [   808500,    810500]      * |    0.6395     bin_1718398
  [51550]     chrY [  1010400,   1012400]      * |    0.5320     bin_1720417
  [51551]     chrY [  1284200,   1286200]      * |    0.5055     bin_1723155
  -------
  seqinfo: 21 sequences from an unspecified genome; no seqlengths

DELTA Enhancer Predictions

DELTA (Distal Enhancer Locating Tool based on AdaBoost) is described in DELTA: A Distal Enhancer Locating Tool Based on AdaBoost Algorithm and Shape Features of Chromatin Modifications and defines a set of non-redundant shape features of histone modifications, which shows high consistency across cell types and can greatly reduce the dimensionality of feature vectors which is then integrated with a machine-learning algorithm AdaBoost to predict enhancers.

delta.enhancers <- c("neural tube" = "NT", 
                       midbrain      = "MB", 
                       hindbrain     = "HB",
                       limb          = "LM",
                       heart         = "HT")
delta.enhancers <- lapply(delta.enhancers, function(x) {
  file.path <- file.path("data", "enhancer_predictions", "DELTA", paste0("DELTA_pred_E11_5_", x, ".bed"))
  x <- read_tsv(file.path, col_names = FALSE)
  x <- GRanges(seqnames = x$X1,
               ranges = IRanges(start = x$X2,
                                end = x$X3),
               score = x$X5,
               enhancername = x$X4)
  return(x)
  
})
delta.enhancers$heart
GRanges object with 43911 ranges and 2 metadata columns:
          seqnames                 ranges strand |      score   enhancername
             <Rle>              <IRanges>  <Rle> |  <numeric>    <character>
      [1]     chr1     [4426300, 4428400]      * | 0.06853420    delta_44273
      [2]     chr1     [4614300, 4616400]      * | 0.05295439    delta_46153
      [3]     chr1     [4857000, 4859100]      * | 0.05446597    delta_48580
      [4]     chr1     [5220300, 5222400]      * | 0.05524229    delta_52213
      [5]     chr1     [6213700, 6215800]      * | 0.15449180    delta_62147
      ...      ...                    ...    ... .        ...            ...
  [43907]     chrX [169983200, 169985300]      * | 0.34102207 delta_26327305
  [43908]     chrX [169988600, 169990700]      * | 0.10003010 delta_26327359
  [43909]     chrX [170008800, 170010900]      * | 0.08205701 delta_26327561
  [43910]     chrX [170017200, 170019300]      * | 0.26976139 delta_26327645
  [43911]     chrY [ 90739300,  90741400]      * | 0.21821156 delta_27245179
  -------
  seqinfo: 21 sequences from an unspecified genome; no seqlengths

RFECS Enhancer Predictions

RFECS (Random Forest based Enhancer identification from Chromatin States) is described in RFECS: A Random-Forest Based Algorithm for Enhancer Identification from Chromatin State and is used to predict genome-wide enhancers based on their similarity to the histone modification profiles of p300 binding sites.

rfecs.enhancers <- c("neural tube" = "NT", 
                       midbrain      = "MB", 
                       hindbrain     = "HB",
                       limb          = "LM",
                       heart         = "HT")
rfecs.enhancers <- lapply(rfecs.enhancers, function(x) {
  file.path <- file.path("data", "enhancer_predictions", "RFECS", paste0("RFECS_pred_E11_5_", x, ".bed"))
  x <- read_tsv(file.path, col_names = FALSE)
  x <- GRanges(seqnames = x$X1,
               ranges = IRanges(start = x$X2,
                                end = x$X3),
               score = x$X5,
               enhancername = x$X4)
  return(x)
  
})
rfecs.enhancers$heart
GRanges object with 44039 ranges and 2 metadata columns:
          seqnames                 ranges strand |     score enhancername
             <Rle>              <IRanges>  <Rle> | <numeric>  <character>
      [1]     chr1     [4491500, 4493500]      * |  0.246154  rfecs_18957
      [2]     chr1     [4496000, 4498000]      * |  0.353846  rfecs_18958
      [3]     chr1     [4570700, 4572700]      * |  0.415385  rfecs_18959
      [4]     chr1     [4622200, 4624200]      * |  0.292308  rfecs_18960
      [5]     chr1     [4807400, 4809400]      * |  0.261538  rfecs_18961
      ...      ...                    ...    ... .       ...          ...
  [44035]     chrX [170001400, 170003400]      * |  0.400000  rfecs_44034
  [44036]     chrX [170004700, 170006700]      * |  0.261538  rfecs_44035
  [44037]     chrX [170009000, 170011000]      * |  0.584615  rfecs_44036
  [44038]     chrX [170672400, 170674400]      * |  0.861538  rfecs_44037
  [44039]     chrX [170757400, 170759400]      * |  0.953846  rfecs_44038
  -------
  seqinfo: 20 sequences from an unspecified genome; no seqlengths

CSI-ANN Enhancer Predictions

CSI-ANN (chromatin signature identification by artificial neural network) is described in Discover regulatory DNA elements using chromatin signatures and artificial neural network and is a framework that consists of a data transformation and a feature extraction step followed by a classification step using time-delay neural network.

csiann.enhancers <- c("neural tube" = "NT", 
                       midbrain      = "MB", 
                       hindbrain     = "HB",
                       limb          = "LM",
                       heart         = "HT")
csiann.enhancers <- lapply(csiann.enhancers, function(x) {
  file.path <- file.path("data", "enhancer_predictions", "CSIANN", paste0("CSIANN_pred_E11_5_", x, ".bed"))
  x <- read_tsv(file.path, col_names = FALSE)
  x <- GRanges(seqnames = x$X1,
               ranges = IRanges(start = x$X2,
                                end = x$X3),
               score = x$X5,
               enhancername = x$X4)
  return(x)
  
})
csiann.enhancers$heart
GRanges object with 63158 ranges and 2 metadata columns:
          seqnames                 ranges strand |     score enhancername
             <Rle>              <IRanges>  <Rle> | <numeric>  <character>
      [1]     chr1     [3670001, 3672001]      * | 0.8655596     csiann_0
      [2]     chr1     [4412001, 4414001]      * | 0.8594556     csiann_1
      [3]     chr1     [4491601, 4493601]      * | 0.8655598     csiann_2
      [4]     chr1     [4571001, 4573001]      * | 0.8655591     csiann_3
      [5]     chr1     [4622601, 4624601]      * | 0.8651950     csiann_4
      ...      ...                    ...    ... .       ...          ...
  [63154]     chrX [170016801, 170018801]      * | 0.8654488 csiann_63153
  [63155]     chrX [170018201, 170020201]      * | 0.8655517 csiann_63154
  [63156]     chrY [  1244401,   1246401]      * | 0.8640187 csiann_63155
  [63157]     chrY [  1285201,   1287201]      * | 0.8629719 csiann_63156
  [63158]     chrY [ 90738801,  90740801]      * | 0.8655591 csiann_63157
  -------
  seqinfo: 21 sequences from an unspecified genome; no seqlengths

EnhancerFinder enhancer predictions

Another system of enhancer prediction that we may compare against is EnhancerFinder, a two-step method for distinguishing developmental enhancers from the genomic background and then predicting their tissue specificity. Among the Supporting Information for the paper are the enhancer predictions made for brain, limb, and heart. These however are indicated in hg19 genomic coordinates, so we will have to get the hg19 coordinates for the VISTA database as well.

vista.human <- read_tsv("data/VISTA_invivo_tested_hg19.bed", col_names = FALSE)
vista.human.validation <- stringr::str_replace(vista.human$X10, "ganglion, cranial", "ganglion-cranial")
vista.human.validation <- stringr::str_split(vista.human.validation, ",")
vista.human.validation <- lapply(vista.human.validation, function(x) {
  x <- stringr::str_replace(x, " \\(.*\\)", "")
  x <- stringr::str_trim(x, side = "both")
  return(x)
})
vista.human.validation <- lapply(vista.human.validation, function(x) {
  x <- stringr::str_replace(x, "\\[.*\\]", "")
})
vista.human.matrix <- matrix(0L, 
                             nrow = nrow(vista.human), 
                             ncol = length(unique(unlist(vista.human.validation))))
colnames(vista.human.matrix) <- unique(unlist(vista.human.validation))
for(enhancer in seq_along(vista.human.validation)) {
  vista.human.matrix[enhancer, vista.human.validation[[enhancer]]] <- 1L
}
library(GenomicRanges)
vista.human <- GRanges(seqnames = vista.human$X1,
                       ranges = IRanges(start = vista.human$X2,
                                        end = vista.human$X3),
                       name = vista.human$X4,
                       validated = vista.human$X5,
                       seqinfo = Seqinfo(genome = "hg19"))
mcols(vista.human) <- cbind(as.data.frame(mcols(vista.human)), vista.human.matrix)
vista.human[, 1:4]
GRanges object with 2555 ranges and 4 metadata columns:
         seqnames                 ranges strand |        name validated     heart forebrain
            <Rle>              <IRanges>  <Rle> | <character> <integer> <integer> <integer>
     [1]     chr1     [2142309, 2144864]      * |       318:2         1         1         0
     [2]     chr1     [2808410, 2809683]      * |      1088:2         1         0         1
     [3]     chr1     [2883700, 2888173]      * |      1492:2         1         0         0
     [4]     chr1     [3027197, 3029379]      * |      1560:2         0         0         0
     [5]     chr1     [3190581, 3191428]      * |       705:1         1         0         0
     ...      ...                    ...    ... .         ...       ...       ...       ...
  [2551]     chrX [139593502, 139594774]      * |       770:1         0         0         0
  [2552]     chrX [139674499, 139675403]      * |       588:1         0         0         0
  [2553]     chrX [147829016, 147830159]      * |       856:1         0         0         0
  [2554]     chrX [150407692, 150409052]      * |      1746:1         1         0         1
  [2555]     chrX [153593214, 153596578]      * |      2491:1         0         0         0
  -------
  seqinfo: 93 sequences (1 circular) from hg19 genome

The enhancer predictions that EnhancerFinder generates only has a single category for brain so we will compare it to both hindbrain and midbrain, they also made no predictions for neural tube, so we will be excluding EnhancerFinder from that prediction. Beyond that, the procedure is similar to what we did for ENCODE. Starting with creating a GRanges object for the predictions

download.file("http://dx.doi.org/10.1371/journal.pcbi.1003677.s018", destfile = "data/enhancerfinder.predictions.zip")
trying URL 'http://dx.doi.org/10.1371/journal.pcbi.1003677.s018'
downloaded 1.9 MB
unzip("data/enhancerfinder.predictions.zip", exdir = file.path("data", "enhancer_predictions", "enhancerfinder"))
l.ef.enhancer <- read.delim(file.path("data", "enhancer_predictions", "enhancerfinder", "enhancerfinder_limb_hg19.bed"))
h.ef.enhancer <- read.delim(file.path("data", "enhancer_predictions", "enhancerfinder", "enhancerfinder_heart_hg19.bed"))
b.ef.enhancer <- read.delim(file.path("data", "enhancer_predictions", "enhancerfinder", "enhancerfinder_brain_hg19.bed"))
enhancerfinder.enhancers <- list(h.ef.enhancer, b.ef.enhancer, l.ef.enhancer, b.ef.enhancer)
enhancerfinder.enhancers <- lapply(enhancerfinder.enhancers, function(x) {
  x <- GRanges(seqnames = x$X.chromosome,
               ranges = IRanges(start = x$start,
                                end = x$end),
               score = x$MKL_scores)
})
names(enhancerfinder.enhancers) <- c("heart", "hindbrain", "limb", "midbrain")
enhancerfinder.enhancers$heart
GRanges object with 19051 ranges and 1 metadata column:
          seqnames                 ranges strand |     score
             <Rle>              <IRanges>  <Rle> | <numeric>
      [1]     chr1       [839000, 840500]      * |     0.273
      [2]     chr1       [856000, 857500]      * |     0.221
      [3]     chr1       [939000, 942500]      * |     1.679
      [4]     chr1       [954000, 955500]      * |     0.525
      [5]     chr1       [956000, 957500]      * |     0.418
      ...      ...                    ...    ... .       ...
  [19047]     chrX [153963280, 153964780]      * |     0.293
  [19048]     chrX [153989280, 153990780]      * |     0.297
  [19049]     chrY [ 10036000,  10037500]      * |     0.271
  [19050]     chrY [ 13470000,  13471500]      * |     0.299
  [19051]     chrY [ 13477000,  13479500]      * |     0.401
  -------
  seqinfo: 24 sequences from an unspecified genome; no seqlengths

Comparing External Enhancer Data Sets to Vista

As with StatePaintR compare this data to the VISTA dataset.

ENCODE v3 Enhancer-like regions

plot.data.encode <- PRG(states = encode.enhancers,
                        comparison = vista.enhancers,
                        comparison.select = test.enhancers)
plot.data.encode$auprg
neural tube    midbrain   hindbrain        limb       heart 
  0.8160415   0.8228843   0.7969034   0.8530691   0.8815817 

REPTILE

plot.data.reptile <- PRG(states = reptile.enhancers,
                        comparison = vista.enhancers,
                        comparison.select = test.enhancers)
plot.data.reptile$auprg
neural tube    midbrain   hindbrain        limb       heart 
  0.8622531   0.8710326   0.7628318   0.8937956   0.9175436 

DELTA

plot.data.delta <- PRG(states = delta.enhancers,
                       comparison = vista.enhancers,
                       comparison.select = test.enhancers)
plot.data.delta$auprg
neural tube    midbrain   hindbrain        limb       heart 
  0.8091653   0.8442490   0.7575233   0.8396032   0.9343298 

RFECS

plot.data.rfecs <- PRG(states = rfecs.enhancers,
                       comparison = vista.enhancers,
                       comparison.select = test.enhancers)
plot.data.rfecs$auprg
neural tube    midbrain   hindbrain        limb       heart 
  0.7918588   0.8462961   0.7762617   0.8544358   0.9222974 

CSI-ANN

plot.data.csiann <- PRG(states = csiann.enhancers,
                        comparison = vista.enhancers,
                        comparison.select = test.enhancers)
plot.data.csiann$auprg
neural tube    midbrain   hindbrain        limb       heart 
  0.7187654   0.6819687   0.6236748   0.6873185   0.8400370 

EnhancerFinder

For enhancer finder - accounting for it being in hg19, rather than in mm10 like the previous comparisons

plot.data.enhancerfinder <- PRG(states = enhancerfinder.enhancers,
                        comparison = vista.human,
                        comparison.select = test.enhancers)
plot.data.enhancerfinder$auprg
    heart hindbrain      limb  midbrain 
0.8209410 0.6310684 0.6700646 0.5935917 

Supplemental Figure 5 - Visualization of Predictions of VISTA Validated enhancers

Visualization of Predictions of VISTA Validated enhancers

We begin by doing a little bit of cleaning up the data to prepare for plotting with ggplot2

plot.data.encode$precision.recall.gain$SOURCE <- "ENCODE"
plot.data.sp$precision.recall.gain$SOURCE <- "StatePaintR"
plot.data.reptile$precision.recall.gain$SOURCE <- "REPTILE"
plot.data.delta$precision.recall.gain$SOURCE <- "DELTA"
plot.data.rfecs$precision.recall.gain$SOURCE <- "RFECS"
plot.data.csiann$precision.recall.gain$SOURCE <- "CSIANN"
plot.data.enhancerfinder$precision.recall.gain$SOURCE <- "EnhancerFinder"
precision.recall.gain <- rbind.data.frame(plot.data.encode$precision.recall.gain, 
                                          plot.data.sp$precision.recall.gain,
                                          plot.data.reptile$precision.recall.gain,
                                          plot.data.delta$precision.recall.gain,
                                          plot.data.rfecs$precision.recall.gain,
                                          plot.data.csiann$precision.recall.gain,
                                          plot.data.enhancerfinder$precision.recall.gain)
plot.data.encode$convex.hull$SOURCE <- "ENCODE"
plot.data.sp$convex.hull$SOURCE <- "StatePaintR"
plot.data.reptile$convex.hull$SOURCE <- "REPTILE"
plot.data.delta$convex.hull$SOURCE <- "DELTA"
plot.data.rfecs$convex.hull$SOURCE <- "RFECS"
plot.data.csiann$convex.hull$SOURCE <- "CSIANN"
plot.data.enhancerfinder$convex.hull$SOURCE <- "EnhancerFinder"
convex.hull <- rbind.data.frame(plot.data.encode$convex.hull, 
                                plot.data.sp$convex.hull,
                                plot.data.reptile$convex.hull,
                                plot.data.delta$convex.hull,
                                plot.data.rfecs$convex.hull,
                                plot.data.csiann$convex.hull,
                                plot.data.enhancerfinder$convex.hull)
comboplot <- ggplot(precision.recall.gain, aes(y = PRECISION, x = RECALL, group = SOURCE)) +
  geom_line(aes(color = SOURCE)) +
  geom_point(aes(color = SOURCE)) +
  coord_cartesian(xlim=c(0,1), ylim = c(0.4,1)) +
  scale_color_brewer(palette = "Dark2") +
  geom_line(data = convex.hull, aes(y = PRECISION, 
                                    x = RECALL, 
                                    group = SOURCE, 
                                    color = SOURCE), linetype = 2) +
  theme_grey() +
  ylab("Precision Gain") + xlab("Recall Gain") + theme(aspect.ratio=1) +
  facet_wrap( ~ TISSUE, ncol = 3)
comboplot

Area Under the Precision-Recall-Gain Curve

From all of this analysis we generate the Area Under the Precision-Recall-Gain Curve (AUPRG) which conveys an expected F1 score on a harmonic scale. Which is Table 3.

auprg <- data.frame(source = c("ENCODE", "StatePaintR", 
                               "REPTILE", "DELTA", "RFECS", 
                               "CSIANN", "EnhancerFinder"),
                    "neural tube" = NA,
                    midbrain = NA,
                    hindbrain = NA,
                    limb = NA,
                    heart = NA,
                    check.names = FALSE)
auprg[auprg$source == "ENCODE", names(plot.data.encode$auprg)] <- plot.data.encode$auprg
auprg[auprg$source == "REPTILE", names(plot.data.reptile$auprg)] <- plot.data.reptile$auprg
auprg[auprg$source == "DELTA", names(plot.data.delta$auprg)] <- plot.data.delta$auprg
auprg[auprg$source == "RFECS", names(plot.data.rfecs$auprg)] <- plot.data.rfecs$auprg
auprg[auprg$source == "CSIANN", names(plot.data.csiann$auprg)] <- plot.data.csiann$auprg
auprg[auprg$source == "StatePaintR", names(plot.data.sp$auprg)] <- plot.data.sp$auprg
auprg[auprg$source == "EnhancerFinder", names(plot.data.enhancerfinder$auprg)] <- plot.data.enhancerfinder$auprg
auprg$ave_auprg <- rowSums(auprg[, -1], na.rm = T)/5
average_rank <- sapply(auprg[, c("neural tube", 
                                 "midbrain", 
                                 "hindbrain", 
                                 "limb", 
                                 "heart")], function(x) {rank(1 - x)})
auprg$ave_rank <- sapply(split(average_rank, 1:nrow(average_rank)), mean)
pauprg <- auprg
pauprg[, -1] <- signif(pauprg[, -1], digits = 2)
pauprg

Figure 3 - Locus- and tissue-specific enrichment of Parkinson’s GWAS variants

pd.enrichment <- read.delim("data/PD.enrichment.27461410.txt", sep = "\t", header = TRUE)
pd.enrichment
enrich.plot <- ggplot(pd.enrichment, aes(name, difference, group = color)) +
  geom_pointrange(aes(ymin = lower, ymax = upper, color = color), fatten = 1, size = 1.2) +
  theme_minimal() +
  scale_color_identity() +
  geom_hline(yintercept = 0, color = "#ececec", alpha = 0.2) +
  theme(axis.text.x = element_blank(),
        legend.position="none",
        panel.grid.major.x = element_blank(), panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        strip.text.y = element_text(angle = 0, hjust = 0, vjust = 0.5, size = rel(1.5)),
        strip.text.x = element_text(angle = 270, hjust = 0.5, vjust = 1, size = rel(1.5))) +
  scale_x_discrete(name = "Sample") +
  scale_y_continuous(name = "difference", breaks =  c(0, 0.5, 1)) +
  ggtitle("Enrichment of PD GWAS variants") +
  coord_cartesian(ylim = c(-0.2,1)) +
  facet_grid(locus ~ type, scales = "free_x", space = "free_x", switch = "x") +
  annotate("rect", xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=0,alpha=0.1,fill="black")
enrich.plot

Figure 4 - Example of model comparisons.

Comparing Models

Enrichment calculations were done as in section above using either of two different state models (Model 1 and Model 2) from StateHub, “Default” and “Focused Poised Promoter”, which differ in the treatment of poised promoters. Each plot is made using the same y axis range for comparison and emphasizes that one model is clearly more selective than the other. Both models clearly detect enrichment of hypermethylated probes in the poised state. Model 2 is more selective than model 1 in its definition of poised promoter.

enrichment.out <- read_tsv("data/methylation.enrichment.txt")
enrichment.out <- enrichment.out[enrichment.out$type != "ENCODE2012", ]
enrichment.out

Model 1 - Default Model

Click the buttons above to change models.

In Model 1, we assign any promoters lacking active marks to the poised state.

model1.plot <- ggplot(enrichment.out[enrichment.out$model == "hyper1", ], aes(name, oddsratio, group = color)) +
  geom_pointrange(aes(ymin = odds.lower, ymax = odds.upper, color = color), fatten = 1, size = 1.2) +
  theme_minimal() +
  scale_color_identity() +
  geom_hline(yintercept = 1, color = "#000000", alpha = 0.1) +
  theme(axis.text.x = element_blank(),
        legend.position="none",
        panel.grid.major.x = element_blank(), panel.grid.minor = element_blank(), panel.background = element_blank(),
        strip.text.y = element_text(angle = 0, hjust = 0, vjust = 0.5, size = rel(1.5)),
        strip.text.x = element_text(angle = 270, hjust = 0.5, vjust = 1, size = rel(1.5))) +
  scale_x_discrete(name = "Sample") +
  scale_y_continuous(name = "Odds Ratio") +
  coord_cartesian(ylim = c(0,12)) +
  facet_grid(state ~ type, scales = "free_x", space = "free_x", switch = "x") +
  annotate("rect", xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=1,alpha=0.1,fill="black")
model1.plot

Model 2 - Focused Poised Promoter

Click the buttons above to change models.

In this model, enhancers with H3K4me1 and promoters with H3K4me3 overlapping narrow regions of H3K27me3 are called poised (EPR and PPR), but those without H3K27me3 are called weak (EWR and PWR)

model2.plot <- ggplot(enrichment.out[enrichment.out$model == "hyper2", ], aes(name, oddsratio, group = color)) +
  geom_pointrange(aes(ymin = odds.lower, ymax = odds.upper, color = color), fatten = 1, size = 1.2) +
  theme_minimal() +
  scale_color_identity() +
  geom_hline(yintercept = 1, color = "#000000", alpha = 0.1) +
  theme(axis.text.x = element_blank(),
        legend.position="none",
        panel.grid.major.x = element_blank(), panel.grid.minor = element_blank(), panel.background = element_blank(),
        strip.text.y = element_text(angle = 0, hjust = 0, vjust = 0.5, size = rel(1.5)),
        strip.text.x = element_text(angle = 270, hjust = 0.5, vjust = 1, size = rel(1.5))) +
  scale_x_discrete(name = "Sample") +
  scale_y_continuous(name = "Odds Ratio") +
  coord_cartesian(ylim = c(0,12)) +
  facet_grid(state ~ type, scales = "free_x", space = "free_x", switch = "x") +
  annotate("rect", xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=1,alpha=0.1,fill="black")
model2.plot

Figure 5 - Enrichment in genomic annotations

HMEC segmentation

StatePaintR

hmec.states <- PaintStates(manifest = "data/manifest.hmec.txt",
                           decisionMatrix = dm,
                           progress = FALSE)
ExportStatePaintR(states = hmec.states,
                  output.dir = "data/HMEC/")

Ensembl Annotations

# Active and Inactive Enhancers in HMEC
mart <- useMart(host = "grch37.ensembl.org", 
                biomart = "ENSEMBL_MART_FUNCGEN", 
                dataset = "hsapiens_regulatory_feature")
hmec.enhancers <- getBM(filters = c("regulatory_feature_type_name", "epigenome_name"),
                        values = list(c("Enhancer"), c("HMEC")),
                        attributes = c("chromosome_name", "chromosome_start",
                                       "chromosome_end", "feature_type_name",
                                       "regulatory_stable_id", "activity"),
                        mart = mart)
hmec.enhancers <- hmec.enhancers[order(hmec.enhancers$chromosome_name,
                                       hmec.enhancers$chromosome_start), ]
hmec.enhancers$chromosome_name <- paste0("chr", hmec.enhancers$chromosome_name)
head(hmec.enhancers, n = 5)
hmec.enhancer.gff <- data.frame(chr = hmec.enhancers$chromosome_name, 
                                source = "ensembl_hsapiens_regulatory_feature", 
                                feature = paste("Enhancer", hmec.enhancers$activity, sep = "_"), 
                                start = hmec.enhancers$chromosome_start,
                                end = hmec.enhancers$chromosome_end,
                                score = 1000,
                                strand = ".",
                                frame = ".",
                                group = hmec.enhancers$activity)
head(hmec.enhancer.gff, n = 5)
write.table(hmec.enhancer.gff[hmec.enhancer.gff$group == "ACTIVE", ], 
            file = "data/active.hmec.enhancers.gff", 
            sep = "\t", 
            quote = FALSE,
            row.names = FALSE,
            col.names = FALSE)
write.table(hmec.enhancer.gff[hmec.enhancer.gff$group == "INACTIVE", ], 
            file = "data/inactive.hmec.enhancers.gff", 
            sep = "\t", 
            quote = FALSE,
            row.names = FALSE,
            col.names = FALSE)
# Gencode Gene Models
download.file(url = "ftp://ftp.sanger.ac.uk/pub/gencode/Gencode_human/release_19/gencode.v19.annotation.gtf.gz",
              destfile = "data/gencode.v19.annotation.gtf.gz", quiet = TRUE)

Segtools Analysis

In order to generate the plots to show segmentation enrichment in other genomic regions, we use segtools

grep -v '#' data/HMEC/e117.7mark.segmentation.bed | \
  sed '1d' > data/HMEC/e117.7mark.segmentation.noheader.bed
segtools-aggregation \
  --noplot \
  -o hmec.gene.statepaintr \
  --mode gene \
  --normalize \
  data/HMEC/e117.7mark.segmentation.noheader.bed \
  data/gencode.v19.annotation.gtf.gz
segtools-aggregation \
  --noplot \
  -o data/hmec.active.enhancer.statepaintr \
  --group \
  --mode region \
  --normalize \
  data/HMEC/e117.7mark.segmentation.noheader.bed \
  data/active.hmec.enhancers.gff
segtools-aggregation \
  --noplot \
  -o data/hmec.inactive.enhancer.statepaintr \
  --group \
  --mode region \
  --normalize \
  data/HMEC/e117.7mark.segmentation.noheader.bed \
  data/inactive.hmec.enhancers.gff

Plotting

First we need to parse the output of segtools to prepare for plotting

segtools.files <- c("Active Enhancer" = 
                      "data/hmec.active.enhancer.statepaintr/feature_aggregation.tab",
                    "Inactive Enhancer" = 
                      "data/hmec.inactive.enhancer.statepaintr/feature_aggregation.tab",
                    "Gene Body" = 
                      "data/hmec.gene.statepaintr/feature_aggregation.tab")
plots <- list()
for (file in seq_along(segtools.files)) {
  # read the file
  data <- read_tsv(segtools.files[file], comment = "#")
  # set component order
  component.order <- unique(data$component)
  # parse header for normalization
  # header contains metadata about segments
  header <- readLines(segtools.files[file], n = 1)
  header <- str_replace(header, "#", "") %>% str_split(pattern = " ", simplify = TRUE)
  header <- header[ -1 ]
  header.names <- sapply(str_split(header, pattern = "="), "[", 1)
  header <- as.integer(sapply(str_split(header, pattern = "="), "[", 2))
  # create normalized values
  norm.values <- data.frame(Segment = header.names, Total = header, stringsAsFactors = FALSE)
  num.features <- which(norm.values$Segment %in% "num_features")
  norm.values <- norm.values[ -num.features, ]
  norm.values$Random <- norm.values$Total / sum(norm.values$Total)
  # Set up data for plotting  
  data <- gather(data, Segment, Value, 4:ncol(data))
  data <- left_join(data, norm.values)
  norm.values2 <- group_by(data, offset, component) %>% summarise(Offset.Total = sum(Value))
  data <- left_join(data, norm.values2)
  data <- mutate(data, Norm.Value = (Value / Offset.Total) + 1)
  data <- mutate(data, Enrichment = log2(Norm.Value / (Random + 1)))
  data[is.nan(data$Enrichment), "Enrichment"] <- 0
  data$component <- factor(data$component, levels = component.order)
  data$color <- "#998ec3"
  data[ data$Enrichment > 0, "color"] <- "#f1a340"
  if (names(segtools.files[file]) == "Gene Body") {
    data <- data[data$component %in% c("5' flanking: 500 bp",
                                       "initial exon (399 bp)",
                                       "initial intron (13590 bp)",
                                       "internal exons (154 bp)",
                                       "internal introns (5794 bp)",
                                       "terminal exon (886 bp)",
                                       "terminal intron (6991 bp)",
                                       "3' flanking: 500 bp"), ]
  }
  # create plots
  segplot <- ggplot(data, aes(x = offset, fill = color)) +
    geom_area(aes(y = Enrichment)) +
    facet_grid(Segment ~ component, scales = "free_x") +
    scale_fill_identity() +
    geom_hline(yintercept = 0, size = 0.2, color = "grey50") +
    scale_y_continuous(breaks=c(-0.5, 0, 0.5)) +
    coord_cartesian(ylim = c(-1, 1)) +
    theme_minimal() +
    ggtitle(names(segtools.files[file])) + 
    theme(axis.text.x = element_blank(),
          legend.position="none",
          strip.background = element_rect(fill = "grey80", colour = "grey50", size = 0.2),
          panel.grid = element_blank(),
          panel.background = element_rect(color = "grey50"),
          strip.text.y = element_text(angle = 0, hjust = 0, vjust = 0.5),
          axis.title.x = element_blank())
  plot <- list(segplot)
  names(plot) <- names(segtools.files[file])
  plots <- c(plots, plot)
}

Active Enhancers

plots$`Active Enhancer`

Inactive Enhancers

plots$`Inactive Enhancer`

Gene Bodies

plots$`Gene Body`

Supplemental Figures

Evaluate Score Combination Method - Supplementary Figure 2

Using a benchmarking function, we can compare how StatePaintR performs using a variety of score combination methods, including finding the mean, the median, and the maximum of the scores of the features that make up the segmentations. We can compare these three score combination methods against 100 vista enhancers that were excluded above, in each category, when comparing our predictions, and those of other enhancer prediction algorithms to VISTA

dm <- get.decision.matrix("5813b67f46e0fb06b493ceb0")
dm <- doNotSplit(dm, "Core")
mouse.embryo.states <- StatePaintR:::PaintStatesBenchmark(manifest = manifest, 
                                                          decisionMatrix = dm, 
                                                          scoreStates = TRUE,
                                                          progress = FALSE)
plot.data <- list()
names(mouse.embryo.states) <- str_replace(names(mouse.embryo.states), " embryo", "")
for (tissue in names(mouse.embryo.states)) {
  predicted.scores <- mouse.embryo.states[[tissue]]
  vista.enhancers.test <- vista.enhancers
  mcols(vista.enhancers.test) <- data.frame(FOUND = mcols(vista.enhancers.test)[, tissue])
  vista.enhancers.test <- vista.enhancers.test[c(which(mcols(vista.enhancers.test)[, "FOUND"] != 1), train.enhancers[[tissue]]),]
  predicted.scores <- predicted.scores[predicted.scores$state %in% c("EARC", "ARC", "AR", "EAR"), ]
  for (scoring in c("median", "mean", "max")) {
    predicted.scores <- predicted.scores[order(predicted.scores[, scoring], decreasing = TRUE), ]
    olaps <- findOverlaps(vista.enhancers.test, predicted.scores, select = "first")
    mcols(vista.enhancers.test)$score <- 0
    mcols(vista.enhancers.test)[which(!is.na(olaps)), "score"] <- mcols(predicted.scores[olaps[!is.na(olaps)]])[, scoring]
    prg_curve <- StatePaintR:::create_prg_curve(mcols(vista.enhancers.test)$FOUND, mcols(vista.enhancers.test)$score)
    auprg = StatePaintR:::calc_auprg(prg_curve)
    convex_hull = StatePaintR:::prg_convex_hull(prg_curve)
    plot.tissue <- list(list(tissue = tissue, scoringm = scoring, curve = prg_curve, auprg = auprg, hull = convex_hull))
    names(plot.tissue) <- tissue
    plot.data <- c(plot.data, plot.tissue)
  }
}
gg.tissue <- lapply(plot.data, function(y) {
  y <- data.frame(TISSUE = y$tissue, METHOD = y$scoringm, PRECISION = y$curve$precision_gain, RECALL = y$curve$recall_gain)
  return(y)
})
gg.tissue <- do.call("rbind", gg.tissue)

Using the scores generated above we can see the AUPRG for each scoring method / tissue combination

auprg <- sapply(plot.data, function(x) x[c("scoringm", "auprg")])
data.frame(tissue = colnames(auprg), scoring.method = unlist(auprg["scoringm", ]), auprg = unlist(auprg["auprg", ]))

We can also generate the plot, which shows that while relying on the maximum score does not necessarily give the best AUPRG, it does have good performance towards it’s tail at with high recall gain.

sfigure.2 <- ggplot(gg.tissue, aes(y = PRECISION, x = RECALL, group = METHOD)) +
  geom_line(aes(color = METHOD)) +
  geom_point(aes(color = METHOD)) +
  coord_cartesian(xlim=c(0,1), ylim = c(0,1)) +
  scale_color_brewer(palette = "Set2") +
  theme_grey() + theme(aspect.ratio=1) +
  ggtitle("Supplementary Figure 2 - Combining Scores") +
  ylab("Precision Gain") + xlab("Recall Gain") +
  facet_wrap( ~ TISSUE, ncol = 3)
sfigure.2

Evaluate Scoring - Supplementary Figure 3

We can download the focused poised promoter model, and then modify the manner in which it scores segments. In the abstraction layer we can declare that a class of feature must not be split into sub-features, by surrounding it in brackets, i.e., [Core]. Additionally we can specify that the scores from a specific feature should not be used in scoring segments, by following it with a star * like so: Active* or [Core]*. Based on this we can generate four different version of scoring based upon the same segmentation rules. A version called noactive does not use the score from the active mark, nocore does not use the score from the core mark, noregulatory ignores the score from the regulatory mark, and all uses the score from all marks.

x <- get.decision.matrix("5813b67f46e0fb06b493ceb0")
x <- doNotSplit(x, "Core")
#onescore
noactive.x <- x
noactive.x <- doNotScore(noactive.x, "Active")
nocore.x <- x
nocore.x <- doNotScore(nocore.x, "Core")
noregulatory.x <- x
noregulatory.x <- doNotScore(noregulatory.x, "Regulatory")
#twoscores
all.x <- x
dms <- list(noactive.x, nocore.x, noregulatory.x, all.x)
names(dms) <- c("noactive", "nocore", "noregulatory", "all")
dms
$noactive
 ID: 5813b67f46e0fb06b493ceb0 
 Name: Focused Poised Promoter Model 
 Author: Ben Berman - Cedars-Sinai Center for Bioinformatics and Functional Genomics 
 Revision: Oct 28, 2016 11:24:11 PM 
 Description: This model has focused poised regions that require narrow H3K27me3 regions to be present to be called. 
 Not Scoring: Active* 
 Not Splitting: [Core] 

$nocore
 ID: 5813b67f46e0fb06b493ceb0 
 Name: Focused Poised Promoter Model 
 Author: Ben Berman - Cedars-Sinai Center for Bioinformatics and Functional Genomics 
 Revision: Oct 28, 2016 11:24:11 PM 
 Description: This model has focused poised regions that require narrow H3K27me3 regions to be present to be called. 
 Not Scoring: [Core]* 
 Not Splitting: [Core]* 

$noregulatory
 ID: 5813b67f46e0fb06b493ceb0 
 Name: Focused Poised Promoter Model 
 Author: Ben Berman - Cedars-Sinai Center for Bioinformatics and Functional Genomics 
 Revision: Oct 28, 2016 11:24:11 PM 
 Description: This model has focused poised regions that require narrow H3K27me3 regions to be present to be called. 
 Not Scoring: Regulatory* 
 Not Splitting: [Core] 

$all
 ID: 5813b67f46e0fb06b493ceb0 
 Name: Focused Poised Promoter Model 
 Author: Ben Berman - Cedars-Sinai Center for Bioinformatics and Functional Genomics 
 Revision: Oct 28, 2016 11:24:11 PM 
 Description: This model has focused poised regions that require narrow H3K27me3 regions to be present to be called. 
 Not Splitting: [Core] 

We can compare these four scoring models against the 100 vista enhancers that were excluded above, in each category, when comparing our predictions, and those of other enhancer prediction algorithms to VISTA

states <- dms
states <- lapply(states, function(x) {NA})
for (dm in seq_along(dms)) {
  plot.data <- list()
  mouse.embryo.states <- PaintStates(manifest = manifest, 
                                     decisionMatrix = dms[[dm]], 
                                     scoreStates = TRUE,
                                     progress = FALSE)
  names(mouse.embryo.states) <- str_replace(names(mouse.embryo.states), " embryo", "")
  for (tissue in names(mouse.embryo.states)) {
    predicted.scores <- mouse.embryo.states[[tissue]]
    vista.enhancers.test <- vista.enhancers
    mcols(vista.enhancers.test) <- data.frame(FOUND=mcols(vista.enhancers.test)[, tissue])
    vista.enhancers.test <- vista.enhancers.test[c(which(mcols(vista.enhancers.test)[, "FOUND"] != 1), train.enhancers[[tissue]]),]
    predicted.scores <- predicted.scores[predicted.scores$state %in% c("EARC", "ARC", "AR", "EAR"), ]
    predicted.scores <- predicted.scores[order(predicted.scores$score, decreasing = TRUE), ]
    olaps <- findOverlaps(vista.enhancers.test, predicted.scores, select = "first")
    mcols(vista.enhancers.test)$score <- 0
    mcols(vista.enhancers.test)[which(!is.na(olaps)), "score"] <- mcols(predicted.scores[olaps[!is.na(olaps)]])$score
    prg_curve <- StatePaintR:::create_prg_curve(mcols(vista.enhancers.test)$FOUND, mcols(vista.enhancers.test)$score)
    auprg = StatePaintR:::calc_auprg(prg_curve)
    convex_hull = StatePaintR:::prg_convex_hull(prg_curve)
    plot.tissue <- list(list(tissue = tissue, curve=prg_curve, auprg = auprg, hull = convex_hull))
    names(plot.tissue) <- tissue
    plot.data <- c(plot.data, plot.tissue)
  }
  sapply(plot.data, function(x) x$auprg)
  gg.tissue <- lapply(plot.data, function(x) {
    x <- data.frame(TISSUE = x$tissue, PRECISION = x$curve$precision_gain, RECALL = x$curve$recall_gain)
    return(x)
  })
  gg.tissue.sp <- do.call("rbind", gg.tissue)
  gg.tissue.sp$DM <- names(dms)[dm]
  states[[dm]] <- gg.tissue.sp
}
gg.tissue <- do.call("rbind", states)

We can then plot these four scoring models across the tissue specific enhancers that we interrogated from VISTA to see which combination of scores serves to best predict enhancers.

sfigure3 <- ggplot(gg.tissue, aes(y = PRECISION, x = RECALL, group = DM)) +
  geom_line(aes(color = DM)) +
  geom_point(aes(color = DM)) +
  coord_cartesian(xlim=c(0,1), ylim = c(0,1)) +
  scale_color_brewer(palette = "Set1") +
  theme_grey() + theme(aspect.ratio=1) +
  ggtitle("Supplementary Figure 3 - Scoring Features") +
  ylab("Precision Gain") + xlab("Recall Gain") +
  facet_wrap( ~ TISSUE, ncol = 3)
sfigure3

Based upon this plot we decided to go forward without including the Regulatory mark in our scoring.

Evaluate Features that Comprise Enhancers - Supplementary Figure 4

We considered which combinations of segments would provide the best description of an active enhancer as found in VISTA. We generated five groups of marks. Our active Active Enhancer group incorporated EAR, AR, EARC, and ARC, our Active Promoter group consists of PAR and PARC, the Silenced group is SCR and HET, Poised and Weak Enhancer consists of EPR, EPRC, EWR, EWRC, and our Poised and Weak Promoter group is PPR, PPRC, PWR, PWRC, PPWR, and PPWRC. We evaluated each of these combinations against the 100 vista enhancers that were excluded above, in each category, when comparing our predictions, and those of other enhancer prediction algorithms to VISTA

dm <- get.decision.matrix("5813b67f46e0fb06b493ceb0")
dm <- doNotSplit(dm, "Core")
dm <- doNotScore(dm, "Regulatory")
dm <- doNotScore(dm, "Promoter")
segments <- list(A.Enhancer = c("EAR", "AR", "EARC", "ARC"),
                 A.Promoter = c("PAR", "PARC"),
                 Silenced = c("SCR", "HET"),
                 PW.Enhancer = c("EPR", "EPRC", "EWR", "EWRC"),
                 PW.Promoter = c("PPR", "PPRC", "PWR", "PWRC", "PPWR", "PPWRC"))
mouse.embryo.states <- PaintStates(manifest = manifest, 
                                   decisionMatrix = dm, 
                                   scoreStates = TRUE,
                                   progress = FALSE)
names(mouse.embryo.states) <- str_replace(names(mouse.embryo.states), " embryo", "")
for (segment in seq_along(segments)) {
  plot.data <- list()
  for (tissue in names(mouse.embryo.states)) {
    predicted.scores <- mouse.embryo.states[[tissue]]
    vista.enhancers.test <- vista.enhancers
    mcols(vista.enhancers.test) <- data.frame(FOUND=mcols(vista.enhancers.test)[, tissue])
    vista.enhancers.test <- vista.enhancers.test[c(which(mcols(vista.enhancers.test)[, "FOUND"] != 1), train.enhancers[[tissue]]),]
    predicted.scores <- predicted.scores[predicted.scores$state %in% segments[[segment]], ]
    predicted.scores <- predicted.scores[order(predicted.scores$score, decreasing = TRUE), ]
    olaps <- findOverlaps(vista.enhancers.test, predicted.scores, select = "first")
    mcols(vista.enhancers.test)$score <- 0
    mcols(vista.enhancers.test)[which(!is.na(olaps)), "score"] <- mcols(predicted.scores[olaps[!is.na(olaps)]])$score
    prg_curve <- StatePaintR:::create_prg_curve(mcols(vista.enhancers.test)$FOUND, mcols(vista.enhancers.test)$score)
    auprg = StatePaintR:::calc_auprg(prg_curve)
    convex_hull = StatePaintR:::prg_convex_hull(prg_curve)
    plot.tissue <- list(list(tissue = tissue, curve=prg_curve, auprg = auprg, hull = convex_hull))
    names(plot.tissue) <- tissue
    plot.data <- c(plot.data, plot.tissue)
  }
  sapply(plot.data, function(x) x$auprg)
  gg.tissue <- lapply(plot.data, function(x) {
    x <- data.frame(TISSUE = x$tissue, PRECISION = x$curve$precision_gain, RECALL = x$curve$recall_gain)
    return(x)
  })
  gg.tissue <- do.call("rbind", gg.tissue)
  gg.tissue$SEGMENTS <- names(segments)[segment]
  segments[[segment]] <- gg.tissue
}
gg.tissue <- do.call("rbind", segments)

By Plotting the Precision Recall Gain curves, we see that, as expected our Active Enhancer group has the best accuracy across tissue types.

sfigure.4 <- ggplot(gg.tissue, aes(y = PRECISION, x = RECALL, group = SEGMENTS)) +
  geom_line(aes(color = SEGMENTS)) +
  geom_point(aes(color = SEGMENTS)) +
  coord_cartesian(xlim=c(0,1), ylim = c(0,1)) +
  scale_color_brewer(palette = "Paired") +
  theme_grey() + theme(aspect.ratio = 1) +
  ggtitle("Supplementary Figure 4 - Scoring Segments") +
  ylab("Precision Gain") + xlab("Recall Gain") +
  facet_wrap( ~ TISSUE, ncol = 3)
sfigure.4


  1. From the abstract of the paper: “Precision-Recall analysis abounds in applications of binary classification where true negatives do not add value and hence should not affect assessment of the classifier’s performance. Perhaps inspired by the many advantages of receiver operating characteristic (ROC) curves and the area under such curves for accuracy-based performance assessment, many researchers have taken to report Precision-Recall (PR) curves and associated areas as performance metric. We demonstrate in this paper that this practice is fraught with difficulties, mainly because of incoherent scale assumptions – e.g., the area under a PR curve takes the arithmetic mean of precision values whereas the Fβ score applies the harmonic mean. We show how to fix this by plotting PR curves in a different coordinate system, and demonstrate that the new Precision-Recall-Gain curves inherit all key advantages of ROC curves. In particular, the area under Precision-Recall-Gain curves conveys an expected F1 score on a harmonic scale, and the convex hull of a Precision-Recall-Gain curve allows us to calibrate the classifier’s scores so as to determine, for each operating point on the convex hull, the interval of β values for which the point optimizes Fβ. We demonstrate experimentally that the area under traditional PR curves can easily favour models with lower expected F1 score than others, and so the use of Precision-Recall-Gain curves will result in better model selection.”

LS0tCnRpdGxlOiAiU3RhdGVIdWIgLyBTdGF0ZVBhaW50UiBGaWd1cmVzIGFuZCBBbmFseXNpcyIKYXV0aG9yOiBTaW1vbiBHLiBDb2V0emVlLCBaYWNoYXJ5IFJhbWphbiwgSHV5IFEuIERpbmgsIEJlbmphbWluIFAuIEJlcm1hbiwgRGVubmlzCiAgSi4gSGF6ZWxldHQKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICB0aGVtZTogam91cm5hbAogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBmYWxzZQotLS0KCioqKgoKVGhlIHB1cnBvc2Ugb2YgdGhpcyBkb2N1bWVudCBpcyB0byBnZW5lcmF0ZSwgZnJvbSB0aGUgZGF0YSBpbmNsdWRlZCBoZXJlLCBtYW55IG9mIHRoZSBmaWd1cmVzLCB0YWJsZXMsIGFuZCBzdXBwbGVtZW50YXJ5IGRvY3VtZW50cyBwcmVzZW50IGluIHRoZSAqU3RhdGVIdWIgLyBTdGF0ZVBhaW50UiogcGFwZXIuIEFsbCBjb2RlIGlzIGluY2x1ZGVkIHRvIGdlbmVyYXRlIHRoZSBmaWd1cmVzIGFuZCB0YWJsZXMsIHNlZSB0aGUgKiJDb2RlIiogYnV0dG9uIG9uIHRoZSB0b3AgcmlnaHQgb2YgdGhlIHBhZ2UgdG8gZG93bmxvYWQgdGhlIGAuUm1kYCBmaWxlIHRoYXQgZ2VuZXJhdGVzIHRoaXMgZG9jdW1lbnQuCgoqKioKCiMgV2hhdCBpcyAqU3RhdGVIdWIgLyBTdGF0ZVBhaW50Uj8qCkdlbm9tZSBhbm5vdGF0aW9uIGlzIGNyaXRpY2FsIHRvIHVuZGVyc3RhbmQgdGhlIGZ1bmN0aW9uIG9mIGRpc2Vhc2UgdmFyaWFudHMsIGVzcGVjaWFsbHkgZm9yIGNsaW5pY2FsIGFwcGxpY2F0aW9ucy4gVG8gbWVldCB0aGlzIG5lZWQgdGhlcmUgYXJlIHNlZ21lbnRhdGlvbnMgYXZhaWxhYmxlIGZyb20gcHVibGljIGNvbnNvcnRpYSByZWZsZWN0aW5nIHZhcnlpbmcgdW5zdXBlcnZpc2VkIGFwcHJvYWNoZXMgdG8gZnVuY3Rpb25hbCBhbm5vdGF0aW9uIGJhc2VkIG9uIGVwaWdlbmV0aWNzIGRhdGEsIGJ1dCB0aGVyZSByZW1haW5zIGEgbmVlZCBmb3IgdHJhbnNwYXJlbnQsIHJlcHJvZHVjaWJsZSwgYW5kIGVhc2lseSBpbnRlcnByZXRlZCBnZW5vbWljIG1hcHMgb2YgdGhlIGZ1bmN0aW9uYWwgYmlvbG9neSBvZiBjaHJvbWF0aW4uIFdlIGludHJvZHVjZSBoZXJlIG1ldGhvZHMgZm9yIGRlZmluaW5nIGNocm9tYXRpbiBzdGF0ZSB3aXRoIGEgY29tYmluYXRvcmlhbCBlcGlnZW5vbWljIG1vZGVsIHVzaW5nIGFuIGFubm90YXRpb24gdG9vbCwgU3RhdGVQYWludFIgYW5kIGEgd2Vic2l0ZSBkYXRhYmFzZSwgU3RhdGVIdWIuIEFubm90YXRpb25zIGFyZSBmdWxseSBkb2N1bWVudGVkIHdpdGggY2hhbmdlIGhpc3RvcnkgYW5kIHZlcnNpb25pbmcsIGF1dGhvcnNoaXAgaW5mb3JtYXRpb24sIGFuZCBvcmlnaW5hbCBzb3VyY2UgZmlsZXMuIFRoZSB0b29sIGNhbGN1bGF0ZXMgcXVhbnRpdGF0aXZlIHN0YXRlIHNjb3JlcyBiYXNlZCBvbiBnZW5vbWUtd2lkZSByYW5raW5nLCBhbGxvd2luZyBwcmlvcml0aXphdGlvbiBhbmQgZW5yaWNobWVudCB0ZXN0aW5nLCBmYWNpbGl0YXRpbmcgcXVhbnRpdGF0aXZlIGFuYWx5c2lzLiBTdGF0ZUh1YiBob3N0cyBhbm5vdGF0aW9uIHRyYWNrcyBmb3IgbWFqb3IgcHVibGljIGNvbnNvcnRpYSBhcyBhIHJlc291cmNlLCBhbmQgYWxsb3dzIHVzZXJzIHRvIHN1Ym1pdCB0aGVpciBvd24gYWx0ZXJuYXRpdmUgbW9kZWxzLgoKQSBwcmVwcmludCBpcyBhdmFpbGFibGUgb24gW2Jpb1J4aXZdKGh0dHA6Ly9iaW9yeGl2Lm9yZy9jb250ZW50L2Vhcmx5LzIwMTcvMDQvMTUvMTI3NzIwKQoKIyBTZXR1cCBvZiBTdGF0ZVBhaW50UiBlbnZpcm9ubWVudAoKV2UgYmVnaW4gYnkgbG9hZGluZyB0aGUgcmVxdWlyZWQgcGFja2FnZXMsIG5vdCBhbGwgb2YgdGhlc2UgYXJlIHJlcXVpcmVkIHRvIHNpbXBseSBydW4gU3RhdGVQYWludFIsIGJ1dCB0aGV5IHdpbGwgYmUgbmVjZXNzYXJ5IGZvciBkb2luZyBzb21lIG9mIHRoZSBhbmFseXNpcyB0aGF0IGZvbGxvd3MuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KEdlbm9taWNSYW5nZXMpCmxpYnJhcnkoYmlvbWFSdCkKbGlicmFyeShydHJhY2tsYXllcikKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGh0dHIpCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoZGV2dG9vbHMpCmBgYAoKQW5kIHRoZW4gd2UgaW5zdGFsbCBTdGF0ZVBhaW50UiwgY3VycmVudGx5IGZyb20gW0dpdEh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL1NpbW9uLUNvZXR6ZWUvU3RhdGVQYWludHIpLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgZXZhbCA9IEZBTFNFfQppbnN0YWxsX2dpdGh1YigiU2ltb24tQ29ldHplZS9TdGF0ZVBhaW50UiIpCmxpYnJhcnkoU3RhdGVQYWludFIpCmBgYAoKYGBge3IsIGVjaG8gPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KFN0YXRlUGFpbnRSKQpgYGAKCkZpbmFsbHkgd2UgZG93bmxvYWQgYWxsIG9mIHRoZSBkYXRhIHJlcXVpcmVkIHRvIHJ1biB0aGlzIHZpZ25ldHRlOgoKYGBge3J9CmRvd25sb2FkLmZpbGUoImh0dHBzOi8vczMtdXMtd2VzdC0yLmFtYXpvbmF3cy5jb20vc3RhdGVodWItdHJhY2todWIvc3RhdGVwYWludHJfZGF0YS50YXIuZ3oiLCAic3RhdGVwYWludHJfZGF0YS50YXIuZ3oiKQp1bnRhcigic3RhdGVwYWludHJfZGF0YS50YXIuZ3oiLCBjb21wcmVzc2VkID0gImd6aXAiKQpkYXRhLmZyYW1lKEZJTEVTID0gbGlzdC5maWxlcygiZGF0YSIsIGFsbC5maWxlcyA9IEZBTFNFLCByZWN1cnNpdmUgPSBUUlVFKSkKYGBgCgpXaXRoIFRoaXMgY29tcGxldGUsIHdlIGJlZ2luIGFuYWx5c2lzLgoKIyBCcmllZiBvdmVydmlldyBvZiBydW5uaW5nIFN0YXRlUGFpbnRSClRoZSBwcm9jZXNzIG9mIHJ1bm5pbmcgU3RhdGVQYWludFIgZmFsbHMgaW50byB0aHJlZSBiYXNpYyBzdGVwcy4KIyMgRG93bmxvYWQgdGhlIGRlY2lzaW9uIG1hdHJpeApXZSBmaXJzdCBkb3dubG9hZCB0aGUgZGVjaXNpb24gbWF0cml4IGJ5IGluZGljYXRpbmcgdGhlIG1vZGVsJ3MgdW5pcXVlIElEIGFzIGluZGljYXRlZCBvbiB0aGUgc3RhdGVodWIgd2Vic2l0ZS4KYGBge3IsIGV2YWwgPSBGQUxTRX0KZGVjaXNpb25tYXRyaXggPC0gZ2V0LmRlY2lzaW9uLm1hdHJpeChzZWFyY2ggPSAiNTgxM2I2N2Y0NmUwZmIwNmI0OTNjZWIwIikKYGBgCgojIFRhYmxlIDMgLSBQZXJmb3JtYW5jZSBvZiBlbmhhbmNlciBwcmVkaWN0aW9ucwojIyBWSVNUQSB2YWxpZGF0ZWQgZW5oYW5jZXJzCkluIG9yZGVyIHRvIGRldGVybWluZSBhIGJhc2lzIGZvciB0cnVlIHBvc2l0aXZlIGVuaGFuY2VycyB3ZSB1c2UgZW5oYW5jZXJzIHZhbGlkYXRlZCBieSB0aGUgW1ZJU1RBIGVuaGFuY2VyIGJyb3dzZXJdKGh0dHBzOi8vZW5oYW5jZXIubGJsLmdvdi8pLgpXZSBjYW4gYmVnaW4gYnkgcmVhZGluZyB0aGUgVklTVEEgZGF0YSBmcm9tIGEgYmVkIGZpbGUuIFRoZSBvcmlnaW5hbCBkYXRhIGNvbWVzIGZyb20gdGhlIEVOQ09ERSBbYW5ub3RhdGlvbiBmaWxlIHNldCBFTkNTUjk2NFRURl0oaHR0cHM6Ly93d3cuZW5jb2RlcHJvamVjdC5vcmcvYW5ub3RhdGlvbnMvRU5DU1I5NjRUVEYvKQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQp2aXN0YS5lbmhhbmNlcnMgPC0gcmVhZF90c3YoImRhdGEvVklTVEFfaW52aXZvX3Rlc3RlZC5iZWQiLCBjb2xfbmFtZXMgPSBGQUxTRSkKdmlzdGEuZW5oYW5jZXJzCmBgYApUaGlzIGdpdmVzIHVzIHRoZSBnZW5vbWljIGxvY2F0aW9ucyBvZiB2aXN0YSBlbmhhbmNlcnMsIGluIGNvbHVtbnMgMTozOyB0aGVuIHRoZSBuYW1lIG9mIHRoZSBlbmhhbmNlciBpbiA0LiBDb2x1bW4gNSBpcyBUcnVlIC0gYDFgIG9yIEZhbHNlIGAwYCBpbmRpY2F0aW5nIGlmIHRoZSBlbmhhbmNlciB3YXMgZm91bmQgdG8gaGF2ZSBhY3Rpdml0eS4gQ29sdW1ucyA3IGFuZCA4IGFyZSByZWR1bmRhbnQgd2l0aCAyIGFuZCAzLCBjb2x1bW4gOSBhc3NpZ25zIGEgY29sb3IgYmFzZWQgdXBvbiBjb2x1bW4gNS4gQ29sdW1uIDEwIGluZGljYXRlcyB0aGUgdGlzc3VlcyB3aGVyZWluIHRoZSBhY3Rpdml0eSBvZiB0aGUgZW5oYW5jZXIgd2FzIG9ic2VydmVkLiBTaW5jZSB0aGlzIGlzIGEgbGl0dGxlIGJpdCBtZXNzeSwgd2UgY2xlYW4gdXAgdGhlIGZvcm1hdCwgYW5kIGNvdmVydCBpdCB0byBhIGBHZW5vbWljUmFuZ2VzYCBvYmplY3QgZm9yIGZ1cnRoZXIgYW5hbHlzaXMuCgpgYGB7cn0KIyByZW1vdmUgY29tbWFzIGFuZCBzcGFjZXMKdmlzdGEudmFsaWRhdGlvbiA8LSBzdHJpbmdyOjpzdHJfcmVwbGFjZSh2aXN0YS5lbmhhbmNlcnMkWDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSAiZ2FuZ2xpb24sIGNyYW5pYWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2VtZW50ID0gImdhbmdsaW9uLWNyYW5pYWwiKQojIHNwbGl0IG9uIHRoZSByZW1haW5pbmcgY29tbWFzIGluIG9yZGVyIHJldHJpZXZlIGFsbCBvZiB0aGUgdGlzc3Vlcwp2aXN0YS52YWxpZGF0aW9uIDwtIHN0cmluZ3I6OnN0cl9zcGxpdCh2aXN0YS52YWxpZGF0aW9uLCAiLCIpCiMgcmVtb3ZlIHBhcmVudGhlc2lzIGFuZCBjbGFyaWZpeWluZyB0ZXJtcwp2aXN0YS52YWxpZGF0aW9uIDwtIGxhcHBseSh2aXN0YS52YWxpZGF0aW9uLCBmdW5jdGlvbih4KSB7CiAgeCA8LSBzdHJpbmdyOjpzdHJfcmVwbGFjZSh4LCAiIFxcKC4qXFwpIiwgIiIpCiAgeCA8LSBzdHJpbmdyOjpzdHJfdHJpbSh4LCBzaWRlID0gImJvdGgiKQogIHJldHVybih4KQp9KQojIHJlbW92ZSBicmFja2V0cyBhbmQgc2NvcmVzCnZpc3RhLnZhbGlkYXRpb24gPC0gbGFwcGx5KHZpc3RhLnZhbGlkYXRpb24sIGZ1bmN0aW9uKHgpIHsKICB4IDwtIHN0cmluZ3I6OnN0cl9yZXBsYWNlKHgsICJcXFsuKlxcXSIsICIiKQp9KQojIGNyZWF0ZSBhbiBlbXB0eSBtYXRyaXggd2l0aCByb3dzIHJlcHJlc2VudGluZyBlbmhhbmNlcnMsIAojIGNvbHVtbnMgcmVwcmVzZW50aW5nIHRpc3N1ZXMgdGhhdCB0aGV5IGNvdWxkIGJlIGFjdGl2ZSBpbgp2aXN0YS5lbmhhbmNlcnMubWF0cml4IDwtIG1hdHJpeCgwTCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdyA9IG5yb3codmlzdGEuZW5oYW5jZXJzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IGxlbmd0aCh1bmlxdWUodW5saXN0KHZpc3RhLnZhbGlkYXRpb24pKSkpCmNvbG5hbWVzKHZpc3RhLmVuaGFuY2Vycy5tYXRyaXgpIDwtIHVuaXF1ZSh1bmxpc3QodmlzdGEudmFsaWRhdGlvbikpCiMgaWYgYW4gZW5oYW5jZXIgd2FzIGZvdW5kIHRvIGhhdmUgYWN0aXZpdHkgYnkgVklTVEEsIAojIGluZGljYXRlIHRoYXQgd2l0aCBhIDEgaW4gdGhlIGFwcHJvcHJpYXRlIHRpc3N1ZQpmb3IgKGVuaGFuY2VyIGluIHNlcV9hbG9uZyh2aXN0YS52YWxpZGF0aW9uKSkgewogIHZpc3RhLmVuaGFuY2Vycy5tYXRyaXhbZW5oYW5jZXIsIHZpc3RhLnZhbGlkYXRpb25bW2VuaGFuY2VyXV1dIDwtIDFMCn0KIyBjcmVhdGUgb3VyIEdlbm9taWMgUmFuZ2VzIG9iamVjdAp2aXN0YS5lbmhhbmNlcnMgPC0gR1JhbmdlcyhzZXFuYW1lcyA9IHZpc3RhLmVuaGFuY2VycyRYMSwKICAgICAgICAgICAgICAgICAgIHJhbmdlcyA9IElSYW5nZXMoc3RhcnQgPSB2aXN0YS5lbmhhbmNlcnMkWDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZCA9IHZpc3RhLmVuaGFuY2VycyRYMyksCiAgICAgICAgICAgICAgICAgICBuYW1lID0gdmlzdGEuZW5oYW5jZXJzJFg0LAogICAgICAgICAgICAgICAgICAgdmFsaWRhdGVkID0gdmlzdGEuZW5oYW5jZXJzJFg1LAogICAgICAgICAgICAgICAgICAgc2VxaW5mbyA9IFNlcWluZm8oZ2Vub21lID0gIm1tMTAiKSkKbWNvbHModmlzdGEuZW5oYW5jZXJzKSA8LSBjYmluZChhcy5kYXRhLmZyYW1lKG1jb2xzKHZpc3RhLmVuaGFuY2VycykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpc3RhLmVuaGFuY2Vycy5tYXRyaXgpCnZpc3RhLmVuaGFuY2Vyc1ssIDE6NF0KYGBgCgpXZSB3aWxsIGFsc28gY3JlYXRlIGEgY29sdW1uIHRoYXQgcmVwcmVzZW50cyB0aGUgYW1hbGdhbWF0aW9uIG9mIHNldmVyYWwgZGlmZmVyZW50IHRpc3N1ZXM6ICplYXIqLCAqZXllKiwgKmJyYW5jaGlhbCBhcmNoKiwgKm5vc2UqLCBhbmQgKmZhY2lhbCBtZXNlbmNoeW1lKiwgdGhhdCB3ZSB3aWxsIGJlIHVzaW5nIGFzIHRoZSB2YWxpZGF0aW9uIHNldCBmb3IgQ2hJUC1TZXEgY29uZHVjdGVkIGluIHRoZSAqZW1icnlvbmljIGZhY2lhbCBwcm9taW5lbmNlKi4KCmBgYHtyfQpmYWNlLnZpc3RhIDwtIGMoImVhciIsICJleWUiLCAiYnJhbmNoaWFsIGFyY2giLCAibm9zZSIsICJmYWNpYWwgbWVzZW5jaHltZSIpCmUuZi5wIDwtIHJvd1N1bXMoYXMubWF0cml4KG1jb2xzKHZpc3RhLmVuaGFuY2VycylbLCBmYWNlLnZpc3RhXSkpID4gMAptY29scyh2aXN0YS5lbmhhbmNlcnMpWywgImVtYnJ5b25pYyBmYWNpYWwgcHJvbWluZW5jZSJdIDwtIGFzLmludGVnZXIoZS5mLnApCnZpc3RhLmVuaGFuY2Vyc1ssIDI3XQpgYGAKCiMjIEVOQ09ERSBDaElQLVNlcSBhbmQgRE5hc2Utc2VxIGRhdGEKVXNpbmcgdGhlIGRhdGEgbWFkZSBhdmFpbGFibGUgYnkgdGhlIEVOQ09ERSBwcm9qZWN0IHdlIGNhbiByZXRyaWV2ZSBkYXRhIG9uIG11bHRpcGxlIEhpc3RvbmUgQ2hJUC1TZXEgYW5kIHNvbWV0aW1lcyBETmFzZS1zZXEgZm9yIHJlbGV2YW50IHRpc3N1ZXMgaW4gb3JkZXIgdG8gbWFrZSBlbmhhbmNlciBwcmVkaWN0aW9ucy4KCnwgRGF0YXNldCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBBY2Nlc3Npb24gTnVtYmVyICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwKfCBlbWJyeW9uaWMgbW91c2UgbmV1cmFsIHR1YmUgKDExLjUgZGF5KSB8IFtFTkNTUjIxNVpZVl0oaHR0cHM6Ly93d3cuZW5jb2RlcHJvamVjdC5vcmcvcmVmZXJlbmNlLWVwaWdlbm9tZXMvRU5DU1IyMTVaWVYvKSB8CnwgZW1icnlvbmljIG1vdXNlIG1pZGJyYWluICgxMS41IGRheSkgICAgfCBbRU5DU1I4NDNJQVNdKGh0dHBzOi8vd3d3LmVuY29kZXByb2plY3Qub3JnL3JlZmVyZW5jZS1lcGlnZW5vbWVzL0VOQ1NSODQzSUFTLykgfAp8IGVtYnJ5b25pYyBtb3VzZSBoaW5kYnJhaW4gKDExLjUgZGF5KSAgIHwgW0VOQ1NSNTAxT1BDXShodHRwczovL3d3dy5lbmNvZGVwcm9qZWN0Lm9yZy9yZWZlcmVuY2UtZXBpZ2Vub21lcy9FTkNTUjUwMU9QQy8pIHwKfCBlbWJyeW9uaWMgbW91c2UgbGltYiAoMTEuNSBkYXkpICAgICAgICB8IFtFTkNTUjI4M05DRV0oaHR0cHM6Ly93d3cuZW5jb2RlcHJvamVjdC5vcmcvcmVmZXJlbmNlLWVwaWdlbm9tZXMvRU5DU1IyODNOQ0UvKSB8CnwgZW1icnlvbmljIG1vdXNlIGhlYXJ0ICgxMS41IGRheSkgICAgICAgfCBbRU5DU1IwMTZMVFJdKGh0dHBzOi8vd3d3LmVuY29kZXByb2plY3Qub3JnL3JlZmVyZW5jZS1lcGlnZW5vbWVzL0VOQ1NSMDE2TFRSLykgfAoKW05hcnJvd3BlYWtdKGh0dHBzOi8vZ2Vub21lLnVjc2MuZWR1L0ZBUS9GQVFmb3JtYXQjZm9ybWF0MTIpIGNhbGxzIGZvciB0aGlzIGRhdGEgYW5kIG91ciBtYW5pZmVzdCBhcmUgaW5jbHVkZWQgd2l0aCB0aGlzIGRvY3VtZW50LiBJbiBvcmRlciB0byB1c2UgRE5hc2Utc2VxIHdoZW4gYXZhaWxhYmxlLCB3ZSB1c2VkIHRoZSBbSURSXShodHRwczovL3d3dy5lbmNvZGVwcm9qZWN0Lm9yZy9zb2Z0d2FyZS9pZHIvKSBwcm9jZXNzIHRvIG1lcmdlIHJlcGxpY2F0ZXMuCmBgYHtyfQptYW5pZmVzdCA8LSAiZGF0YS9tb3VzZS5pZHIvSURSLk1hbmlmZXN0LnR4dCIKcmVhZC50YWJsZShtYW5pZmVzdCwKICAgICAgICAgICBzZXAgPSAiXHQiLAogICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSwKICAgICAgICAgICBoZWFkZXIgPSBUUlVFKQpgYGAKIyMgQWNxdWlyaW5nIGEgU3RhdGVIdWIgbW9kZWwKV2UgY2FuIGJlZ2luIG91ciBlbmhhbmNlciBwcmVkaWN0aW9ucyBieSBzZWdtZW50aW5nIHRoZSBnZW5vbWUgZm9yIHRoZXNlIHNhbXBsZXMuIFdlIGhhdmUgb3VyIG1hbmlmZXN0LCBub3cgYWxsIHdlIG5lZWQgaXMgYSBgZGVjaXNpb25NYXRyaXhgIGZyb20gU3RhdGVIdWIgdG8gZGVmaW5lIHRoZSBydWxlcyBmb3Igc2VnbWVudGluZyB0aGUgZ2Vub21lLgpgYGB7cn0KZG0gPC0gZ2V0LmRlY2lzaW9uLm1hdHJpeCgiNTgxM2I2N2Y0NmUwZmIwNmI0OTNjZWIwIikKZG0KYGBgCk91ciBkZWNpc2lvbiBtYXRyaXggaXMgZG93bmxvYWRlZCwgYnV0IGluIG9yZGVyIHRvIHNjb3JlIG91ciBlbmhhbmNlcnMgd2UgbWFrZSBzb21lIG1vZGlmaWNhdGlvbnMuIFdlIHdhbnQgdG8gZXhjbHVkZSB0aGUgIlJlZ3VsYXRvcnkiIG1hcmsgZnJvbSBvdXIgc2NvcmluZyBwcm9jZXNzLCBhbmQgd2Ugd2FudCBrZWVwIEROYXNlLXNlcSBwZWFrcyBpbnRhY3QgYXMgdGhlIGNvcmUgb2Ygb3VyIGZlYXR1cmVzLgpgYGB7cn0KZG0gPC0gZG9Ob3RTY29yZShkbSwgIlJlZ3VsYXRvcnkiKQpkbSA8LSBkb05vdFNwbGl0KGRtLCAiQ29yZSIpCmRtCmBgYAojIyBSdW5uaW5nIFN0YXRlUGFpbnRSIHRvIGdldCBlbmhhbmNlciBwcmVkaWN0aW9ucyBhbmQgc2NvcmVzCldpdGggcHJlcGFyYXRpb24gY29tcGxldGUsIHdlIGNhbiBub3cgc2VnbWVudCB0aGUgZ2Vub21lIGZvciBvdXIgc2FtcGxlcyBkZWZpbmVkIGluIHRoZSBtYW5pZmVzdC4KYGBge3J9Cm1vdXNlLmVtYnJ5by5zdGF0ZXMgPC0gUGFpbnRTdGF0ZXMobWFuaWZlc3QgPSBtYW5pZmVzdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVjaXNpb25NYXRyaXggPSBkbSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NvcmVTdGF0ZXMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2dyZXNzID0gRkFMU0UpCm5hbWVzKG1vdXNlLmVtYnJ5by5zdGF0ZXMpIDwtIHN0cl9yZXBsYWNlKG5hbWVzKG1vdXNlLmVtYnJ5by5zdGF0ZXMpLCAiIGVtYnJ5byIsICIiKQptb3VzZS5lbWJyeW8uc3RhdGVzJGhlYXJ0CmBgYAoKIyMgQ29tcGFyaW5nIFByZWRpY3Rpb25zIHRvIFZJU1RBIHsjdHJhaW5fc3Vic2V0fQoKQWxsIG9mIG91ciBtb2RlbCB0dW5pbmcgd2FzIGRvbmUgb24gYSBzZWxlY3Rpb24gb2YgMTAwIHZhbGlkIGVuaGFuY2VycyBmb3IgZWFjaCB0aXNzdWUgdHlwZSwgc28gd2UnbGwgZXhjbHVkZSB0aG9zZSAxMDAgZnJvbSBvdXIgc3Vic2VxdWVudCBjb21wYXJpc29ucy4KYGBge3J9CnNlZWQgPC0gNDIKdHJhaW4uZW5oYW5jZXJzIDwtIGxpc3QoKQp0ZXN0LmVuaGFuY2VycyA8LSBsaXN0KCkKc2V0LnNlZWQoc2VlZCkKZm9yICh0aXNzdWUgaW4gbmFtZXMobW91c2UuZW1icnlvLnN0YXRlcykpIHsKICB2aXN0YS5lbmhhbmNlcnMudHJhaW4gPC0gdmlzdGEuZW5oYW5jZXJzWywgdGlzc3VlXQogIHZpc3RhLmVuaGFuY2Vycy50cmFpbiA8LSBzYW1wbGUod2hpY2gobWNvbHModmlzdGEuZW5oYW5jZXJzLnRyYWluKVssIHRpc3N1ZV0gPT0gMUwpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDEwMCkKICB0cmFpbi5lbmhhbmNlcnMgPC0gYyh0cmFpbi5lbmhhbmNlcnMsIGxpc3QodmlzdGEuZW5oYW5jZXJzLnRyYWluKSkKICBuYW1lcyh0cmFpbi5lbmhhbmNlcnMpW2xlbmd0aCh0cmFpbi5lbmhhbmNlcnMpXSA8LSB0aXNzdWUKICB2aXN0YS5lbmhhbmNlcnMudGVzdCA8LSBjKDE6bGVuZ3RoKHZpc3RhLmVuaGFuY2VycykpWy12aXN0YS5lbmhhbmNlcnMudHJhaW5dCiAgdGVzdC5lbmhhbmNlcnMgPC0gYyh0ZXN0LmVuaGFuY2VycywgbGlzdCh2aXN0YS5lbmhhbmNlcnMudGVzdCkpCiAgbmFtZXModGVzdC5lbmhhbmNlcnMpW2xlbmd0aCh0ZXN0LmVuaGFuY2VycyldIDwtIHRpc3N1ZQp9CgpgYGAKCkV2YWx1YXRpb24gb2Ygb3VyIG1vZGVscywgYW5kIHRoZSBleHRlcm5hbCBlbmhhbmNlciBwcmVkaWN0aW9ucyBhZ2FpbnN0IHdoaWNoIHdlIGNvbXBhcmUsIGlzIGRvbmUgd2l0aCBbUHJlY2lzaW9uLVJlY2FsbC1HYWluIEN1cnZlc10oaHR0cDovL3d3dy5jcy5icmlzLmFjLnVrL35mbGFjaC9QUkdjdXJ2ZXMvKSBbXjFdLgoKW14xXTogRnJvbSB0aGUgYWJzdHJhY3Qgb2YgdGhlIFtwYXBlcl0oaHR0cDovL3BhcGVycy5uaXBzLmNjL3BhcGVyLzU4NjctcHJlY2lzaW9uLXJlY2FsbC1nYWluLWN1cnZlcy1wci1hbmFseXNpcy1kb25lLXJpZ2h0KTogIlByZWNpc2lvbi1SZWNhbGwgYW5hbHlzaXMgYWJvdW5kcyBpbiBhcHBsaWNhdGlvbnMgb2YgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHdoZXJlIHRydWUgbmVnYXRpdmVzIGRvIG5vdCBhZGQgdmFsdWUgYW5kIGhlbmNlIHNob3VsZCBub3QgYWZmZWN0IGFzc2Vzc21lbnQgb2YgdGhlIGNsYXNzaWZpZXIncyBwZXJmb3JtYW5jZS4gUGVyaGFwcyBpbnNwaXJlZCBieSB0aGUgbWFueSBhZHZhbnRhZ2VzIG9mIHJlY2VpdmVyIG9wZXJhdGluZyBjaGFyYWN0ZXJpc3RpYyAoUk9DKSBjdXJ2ZXMgYW5kIHRoZSBhcmVhIHVuZGVyIHN1Y2ggY3VydmVzIGZvciBhY2N1cmFjeS1iYXNlZCBwZXJmb3JtYW5jZSBhc3Nlc3NtZW50LCBtYW55IHJlc2VhcmNoZXJzIGhhdmUgdGFrZW4gdG8gcmVwb3J0IFByZWNpc2lvbi1SZWNhbGwgKFBSKSBjdXJ2ZXMgYW5kIGFzc29jaWF0ZWQgYXJlYXMgYXMgcGVyZm9ybWFuY2UgbWV0cmljLiBXZSBkZW1vbnN0cmF0ZSBpbiB0aGlzIHBhcGVyIHRoYXQgdGhpcyBwcmFjdGljZSBpcyBmcmF1Z2h0IHdpdGggZGlmZmljdWx0aWVzLCBtYWlubHkgYmVjYXVzZSBvZiBpbmNvaGVyZW50IHNjYWxlIGFzc3VtcHRpb25zIC0tIGUuZy4sIHRoZSBhcmVhIHVuZGVyIGEgUFIgY3VydmUgdGFrZXMgdGhlIGFyaXRobWV0aWMgbWVhbiBvZiBwcmVjaXNpb24gdmFsdWVzIHdoZXJlYXMgdGhlIEYmYmV0YTsgc2NvcmUgYXBwbGllcyB0aGUgaGFybW9uaWMgbWVhbi4gV2Ugc2hvdyBob3cgdG8gZml4IHRoaXMgYnkgcGxvdHRpbmcgUFIgY3VydmVzIGluIGEgZGlmZmVyZW50IGNvb3JkaW5hdGUgc3lzdGVtLCBhbmQgZGVtb25zdHJhdGUgdGhhdCB0aGUgbmV3IFByZWNpc2lvbi1SZWNhbGwtR2FpbiBjdXJ2ZXMgaW5oZXJpdCBhbGwga2V5IGFkdmFudGFnZXMgb2YgUk9DIGN1cnZlcy4gSW4gcGFydGljdWxhciwgdGhlIGFyZWEgdW5kZXIgUHJlY2lzaW9uLVJlY2FsbC1HYWluIGN1cnZlcyBjb252ZXlzIGFuIGV4cGVjdGVkIEYxIHNjb3JlIG9uIGEgaGFybW9uaWMgc2NhbGUsIGFuZCB0aGUgY29udmV4IGh1bGwgb2YgYSBQcmVjaXNpb24tUmVjYWxsLUdhaW4gY3VydmUgYWxsb3dzIHVzIHRvIGNhbGlicmF0ZSB0aGUgY2xhc3NpZmllcidzIHNjb3JlcyBzbyBhcyB0byBkZXRlcm1pbmUsIGZvciBlYWNoIG9wZXJhdGluZyBwb2ludCBvbiB0aGUgY29udmV4IGh1bGwsIHRoZSBpbnRlcnZhbCBvZiAmYmV0YTsgdmFsdWVzIGZvciB3aGljaCB0aGUgcG9pbnQgb3B0aW1pemVzIEYmYmV0YTsuIFdlIGRlbW9uc3RyYXRlIGV4cGVyaW1lbnRhbGx5IHRoYXQgdGhlIGFyZWEgdW5kZXIgdHJhZGl0aW9uYWwgUFIgY3VydmVzIGNhbiBlYXNpbHkgZmF2b3VyIG1vZGVscyB3aXRoIGxvd2VyIGV4cGVjdGVkIEYxIHNjb3JlIHRoYW4gb3RoZXJzLCBhbmQgc28gdGhlIHVzZSBvZiBQcmVjaXNpb24tUmVjYWxsLUdhaW4gY3VydmVzIHdpbGwgcmVzdWx0IGluIGJldHRlciBtb2RlbCBzZWxlY3Rpb24uIgoKIyMjIFN0YXRlUGFpbnRSIFByZWRpY3Rpb25zCkhlcmUgd2UgZ2VuZXJhdGUgdGhlIFN0YXRlUGFpbnRSIHByZWRpY3Rpb25zLCB1c2luZyB0aGUgc3RhdGVzICpFQVIqLCAqRUFSQyosICpBUiosIGFuZCAqQVJDKiBhcyBvdXIgcHJlZGljdGVkIGVuaGFuY2Vycy4gVGhpcyBhbHNvIHByZXBhcmVzIHRoZSBkYXRhIGZvciBwbG90dGluZyBpbiBnZ3Bsb3QyIGJ5IGV4dHJhY3RpbmcgdGhlIHByZWNpc2lvbiBnYWluIGFuZCByZWNhbGwgZ2FpbiwgYW5kIHRoZSBjb252ZXggaHVsbC4gQWRkaXRpb25hbGx5IHdlIGxvb2sgYXQgdGhlIGFyZWEgdW5kZXIgdGhlIHByZWNpc2lvbiByZWNhbGwgZ2FpbiBjdXJ2ZSB0byBnZXQgYW4gaWRlYSBvZiB0aGUgYWNjdXJhY3kuCmBgYHtyfQpwbG90LmRhdGEuc3AgPC0gUFJHKHN0YXRlcyA9IG1vdXNlLmVtYnJ5by5zdGF0ZXMsCiAgICAgICAgICAgICAgICAgICAgY29tcGFyaXNvbiA9IHZpc3RhLmVuaGFuY2VycywKICAgICAgICAgICAgICAgICAgICBzdGF0ZS5zZWxlY3QgPSBjKCJFQVJDIiwgIkFSQyIsICJBUiIsICJFQVIiKSwKICAgICAgICAgICAgICAgICAgICBjb21wYXJpc29uLnNlbGVjdCA9IHRlc3QuZW5oYW5jZXJzKQpwbG90LmRhdGEuc3AkYXVwcmcKYGBgCgojIyMgTG9hZCBFbmhhbmNlciBQcmVkaWN0aW9ucwojIyMjIEVOQ09ERSB2MyBFbmhhbmNlci1saWtlIHJlZ2lvbnMKV2UgY2FuIHNvIHNvbWV0aGluZyBzaW1pbGFyIGZvciBldmFsdWF0aW5nIEVOQ09ERSBbY2FuZGlhdGUgZW5oYW5jZXIgY2FsbHNdKGh0dHA6Ly96bGFiLWFubm90YXRpb25zLnVtYXNzbWVkLmVkdS9lbmhhbmNlcnMvKQpXZSB1c2VkIHRoZSBmb2xsb3dpbmcgZGF0YSBmcm9tIHRoZSBFTkNPREUgZGF0YSBwb3J0YWw6Cgp8IEVuaGFuY2VyLWxpa2UgcmVnaW9ucyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgQWNjZXNzaW9uIE51bWJlciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18CnwgdXNpbmcgRE5hc2UgYW5kIEgzSzI3YWMgZm9yIG5ldXJhbCB0dWJlICgxMS41IGRheSkgfCBbRU5DRkY3ODZLVUJdKGh0dHBzOi8vd3d3LmVuY29kZXByb2plY3Qub3JnL0VOQ0ZGNzg2S1VCLykgfAp8IHVzaW5nIEROYXNlIGFuZCBIM0syN2FjIGZvciBtaWRicmFpbiAoMTEuNSBkYXkpICAgIHwgW0VOQ0ZGNzMzVUpUXShodHRwczovL3d3dy5lbmNvZGVwcm9qZWN0Lm9yZy9FTkNGRjczM1VKVC8pIHwKfCB1c2luZyBETmFzZSBhbmQgSDNLMjdhYyBmb3IgaGluZGJyYWluICgxMS41IGRheSkgICB8IFtFTkNGRjMyNElOTV0oaHR0cHM6Ly93d3cuZW5jb2RlcHJvamVjdC5vcmcvRU5DRkYzMjRJTk0vKSB8CnwgdXNpbmcgRE5hc2UgYW5kIEgzSzI3YWMgZm9yIGxpbWIgKDExLjUgZGF5KSAgICAgICAgfCBbRU5DRkY1MjBFR0RdKGh0dHBzOi8vd3d3LmVuY29kZXByb2plY3Qub3JnL0VOQ0ZGNTIwRUdELykgfAp8IHVzaW5nIEgzSzI3YWMgZm9yIGhlYXJ0ICgxMS41IGRheSkgICAgICAgICAgICAgICAgIHwgW0VOQ1NSMzEyRERGXShodHRwczovL3d3dy5lbmNvZGVwcm9qZWN0Lm9yZy9FTkNTUjMxMkRERi8pIHwKClNvIHdlIGJlZ2luIGJ5IGRvd25sb2FkaW5nIHRoaXMgZGF0YSwgYW5kIGNvdmVydGluZyBpdCBpbnRvIEdSYW5nZXMgb2JqZWN0cwpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQplbmNvZGUuZW5oYW5jZXJzIDwtIGMoIm5ldXJhbCB0dWJlIiA9ICJFTkNGRjc4NktVQiIsIAogICAgICAgICAgICAgICAgICAgICAgbWlkYnJhaW4gICAgICA9ICJFTkNGRjczM1VKVCIsIAogICAgICAgICAgICAgICAgICAgICAgaGluZGJyYWluICAgICA9ICJFTkNGRjMyNElOTSIsCiAgICAgICAgICAgICAgICAgICAgICBsaW1iICAgICAgICAgID0gIkVOQ0ZGNTIwRUdEIiwKICAgICAgICAgICAgICAgICAgICAgIGhlYXJ0ICAgICAgICAgPSAiRU5DRkY0MzVWR0MiKQplbmNvZGUuZW5oYW5jZXJzIDwtIHNhcHBseShlbmNvZGUuZW5oYW5jZXJzLCBmdW5jdGlvbih4KSB7CiAgZW5jb2RlIDwtICJodHRwczovL3d3dy5lbmNvZGVwcm9qZWN0Lm9yZyIKICB4IDwtIGNvbnRlbnQoR0VUKGVuY29kZSwgcGF0aCA9IHgpKSRocmVmCiAgcmV0dXJuKHBhc3RlMChlbmNvZGUsIHgpKQp9KQoKZW5jb2RlLmVuaGFuY2VycyA8LSBsYXBwbHkoZW5jb2RlLmVuaGFuY2VycywgZnVuY3Rpb24oeCkgewogIHggPC0gcmVhZF90c3YoeCwgY29sX25hbWVzID0gRkFMU0UpCiAgeCRYNSA8LSBucm93KHgpOjEKICB4IDwtIEdSYW5nZXMoc2VxbmFtZXMgPSB4JFgxLAogICAgICAgICAgICAgICByYW5nZXMgPSBJUmFuZ2VzKHN0YXJ0ID0geCRYMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmQgPSB4JFgzKSwKICAgICAgICAgICAgICAgc2NvcmUgPSB4JFg1LAogICAgICAgICAgICAgICBzZXFpbmZvID0gU2VxaW5mbyhnZW5vbWUgPSAibW0xMCIpKQogIHJldHVybih4KQp9KQplbmNvZGUuZW5oYW5jZXJzJGhlYXJ0CmBgYAoKCiMjIyMgUkVQVElMRSBFbmhhbmNlciBQcmVkaWN0aW9ucwoKUmVndWxhdG9yeSBlbGVtZW50IHByZWRpY3Rpb24gYmFzZWQgb24gdGlzc3VlLXNwZWNpZmljIGxvY2FsIGVwaWdlbmV0aWMgbWFya3MgKFJFUFRJTEUpIGlzIGRlc2NyaWJlZCBpbiAqW0ltcHJvdmVkIHJlZ3VsYXRvcnkgZWxlbWVudCBwcmVkaWN0aW9uIGJhc2VkIG9uIHRpc3N1ZS1zcGVjaWZpYyBsb2NhbCBlcGlnZW5vbWljIHNpZ25hdHVyZXNdKGh0dHA6Ly9kb2kub3JnLzEwLjEwNzMvcG5hcy4xNjE4MzUzMTE0KSosIGFuZCBpbnRlZ3JhdGVzIGhpc3RvbmUgbW9kaWZpY2F0aW9uIGFuZCB3aG9sZS1nZW5vbWUgY3l0b3NpbmUgRE5BIG1ldGh5bGF0aW9uIHByb2ZpbGVzIHRvIGlkZW50aWZ5IHRoZSBwcmVjaXNlIGxvY2F0aW9uIG9mIGVuaGFuY2Vycy4gVGhpcyBwYXBlciBhbHNvIGluY2x1ZGVzIHJlc3VsdHMgZm9yIERFTFRBLCBSRkVDUywgYW5kIENTSS1BTk4gd2hpY2ggd2Ugd2lsbCBiZSBjb21wYWlyaW5nIGFnYWluc3QuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpyZXB0aWxlLmVuaGFuY2VycyA8LSBjKCJuZXVyYWwgdHViZSIgPSAiTlQiLCAKICAgICAgICAgICAgICAgICAgICAgICBtaWRicmFpbiAgICAgID0gIk1CIiwgCiAgICAgICAgICAgICAgICAgICAgICAgaGluZGJyYWluICAgICA9ICJIQiIsCiAgICAgICAgICAgICAgICAgICAgICAgbGltYiAgICAgICAgICA9ICJMTSIsCiAgICAgICAgICAgICAgICAgICAgICAgaGVhcnQgICAgICAgICA9ICJIVCIpCnJlcHRpbGUuZW5oYW5jZXJzIDwtIGxhcHBseShyZXB0aWxlLmVuaGFuY2VycywgZnVuY3Rpb24oeCkgewogIGZpbGUucGF0aCA8LSBmaWxlLnBhdGgoImRhdGEiLCAiZW5oYW5jZXJfcHJlZGljdGlvbnMiLCAiUkVQVElMRSIsIHBhc3RlMCgiUkVQVElMRV9wcmVkX0UxMV81XyIsIHgsICIuYmVkIikpCiAgeCA8LSByZWFkX3RzdihmaWxlLnBhdGgsIGNvbF9uYW1lcyA9IEZBTFNFKQogIHggPC0gR1JhbmdlcyhzZXFuYW1lcyA9IHgkWDEsCiAgICAgICAgICAgICAgIHJhbmdlcyA9IElSYW5nZXMoc3RhcnQgPSB4JFgyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZCA9IHgkWDMpLAogICAgICAgICAgICAgICBzY29yZSA9IHgkWDUsCiAgICAgICAgICAgICAgIGVuaGFuY2VybmFtZSA9IHgkWDQpCiAgcmV0dXJuKHgpCiAgCn0pCnJlcHRpbGUuZW5oYW5jZXJzJGhlYXJ0CmBgYAoKCiMjIyMgREVMVEEgRW5oYW5jZXIgUHJlZGljdGlvbnMKREVMVEEgKERpc3RhbCBFbmhhbmNlciBMb2NhdGluZyBUb29sIGJhc2VkIG9uIEFkYUJvb3N0KSBpcyBkZXNjcmliZWQgaW4gKltERUxUQTogQSBEaXN0YWwgRW5oYW5jZXIgTG9jYXRpbmcgVG9vbCBCYXNlZCBvbiBBZGFCb29zdCBBbGdvcml0aG0gYW5kIFNoYXBlIEZlYXR1cmVzIG9mIENocm9tYXRpbiBNb2RpZmljYXRpb25zXShodHRwOi8vZHguZG9pLm9yZy8xMC4xMzcxJTJGam91cm5hbC5wb25lLjAxMzA2MjIpKiBhbmQgZGVmaW5lcyBhIHNldCBvZiBub24tcmVkdW5kYW50IHNoYXBlIGZlYXR1cmVzIG9mIGhpc3RvbmUgbW9kaWZpY2F0aW9ucywgd2hpY2ggc2hvd3MgaGlnaCBjb25zaXN0ZW5jeSBhY3Jvc3MgY2VsbCB0eXBlcyBhbmQgY2FuIGdyZWF0bHkgcmVkdWNlIHRoZSBkaW1lbnNpb25hbGl0eSBvZiBmZWF0dXJlIHZlY3RvcnMgd2hpY2ggaXMgdGhlbiBpbnRlZ3JhdGVkIHdpdGggYSBtYWNoaW5lLWxlYXJuaW5nIGFsZ29yaXRobSBBZGFCb29zdCB0byBwcmVkaWN0IGVuaGFuY2Vycy4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmRlbHRhLmVuaGFuY2VycyA8LSBjKCJuZXVyYWwgdHViZSIgPSAiTlQiLCAKICAgICAgICAgICAgICAgICAgICAgICBtaWRicmFpbiAgICAgID0gIk1CIiwgCiAgICAgICAgICAgICAgICAgICAgICAgaGluZGJyYWluICAgICA9ICJIQiIsCiAgICAgICAgICAgICAgICAgICAgICAgbGltYiAgICAgICAgICA9ICJMTSIsCiAgICAgICAgICAgICAgICAgICAgICAgaGVhcnQgICAgICAgICA9ICJIVCIpCmRlbHRhLmVuaGFuY2VycyA8LSBsYXBwbHkoZGVsdGEuZW5oYW5jZXJzLCBmdW5jdGlvbih4KSB7CiAgZmlsZS5wYXRoIDwtIGZpbGUucGF0aCgiZGF0YSIsICJlbmhhbmNlcl9wcmVkaWN0aW9ucyIsICJERUxUQSIsIHBhc3RlMCgiREVMVEFfcHJlZF9FMTFfNV8iLCB4LCAiLmJlZCIpKQogIHggPC0gcmVhZF90c3YoZmlsZS5wYXRoLCBjb2xfbmFtZXMgPSBGQUxTRSkKICB4IDwtIEdSYW5nZXMoc2VxbmFtZXMgPSB4JFgxLAogICAgICAgICAgICAgICByYW5nZXMgPSBJUmFuZ2VzKHN0YXJ0ID0geCRYMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmQgPSB4JFgzKSwKICAgICAgICAgICAgICAgc2NvcmUgPSB4JFg1LAogICAgICAgICAgICAgICBlbmhhbmNlcm5hbWUgPSB4JFg0KQogIHJldHVybih4KQogIAp9KQpkZWx0YS5lbmhhbmNlcnMkaGVhcnQKYGBgCgojIyMjIFJGRUNTIEVuaGFuY2VyIFByZWRpY3Rpb25zCgpSRkVDUyAoUmFuZG9tIEZvcmVzdCBiYXNlZCBFbmhhbmNlciBpZGVudGlmaWNhdGlvbiBmcm9tIENocm9tYXRpbiBTdGF0ZXMpIGlzIGRlc2NyaWJlZCBpbiAqW1JGRUNTOiBBIFJhbmRvbS1Gb3Jlc3QgQmFzZWQgQWxnb3JpdGhtIGZvciBFbmhhbmNlciBJZGVudGlmaWNhdGlvbiBmcm9tIENocm9tYXRpbiBTdGF0ZV0oaHR0cDovL2R4LmRvaS5vcmcvMTAuMTM3MSUyRmpvdXJuYWwucGNiaS4xMDAyOTY4KSogYW5kIGlzIHVzZWQgdG8gcHJlZGljdCBnZW5vbWUtd2lkZSBlbmhhbmNlcnMgYmFzZWQgb24gdGhlaXIgc2ltaWxhcml0eSB0byB0aGUgaGlzdG9uZSBtb2RpZmljYXRpb24gcHJvZmlsZXMgb2YgcDMwMCBiaW5kaW5nIHNpdGVzLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KcmZlY3MuZW5oYW5jZXJzIDwtIGMoIm5ldXJhbCB0dWJlIiA9ICJOVCIsIAogICAgICAgICAgICAgICAgICAgICAgIG1pZGJyYWluICAgICAgPSAiTUIiLCAKICAgICAgICAgICAgICAgICAgICAgICBoaW5kYnJhaW4gICAgID0gIkhCIiwKICAgICAgICAgICAgICAgICAgICAgICBsaW1iICAgICAgICAgID0gIkxNIiwKICAgICAgICAgICAgICAgICAgICAgICBoZWFydCAgICAgICAgID0gIkhUIikKcmZlY3MuZW5oYW5jZXJzIDwtIGxhcHBseShyZmVjcy5lbmhhbmNlcnMsIGZ1bmN0aW9uKHgpIHsKICBmaWxlLnBhdGggPC0gZmlsZS5wYXRoKCJkYXRhIiwgImVuaGFuY2VyX3ByZWRpY3Rpb25zIiwgIlJGRUNTIiwgcGFzdGUwKCJSRkVDU19wcmVkX0UxMV81XyIsIHgsICIuYmVkIikpCiAgeCA8LSByZWFkX3RzdihmaWxlLnBhdGgsIGNvbF9uYW1lcyA9IEZBTFNFKQogIHggPC0gR1JhbmdlcyhzZXFuYW1lcyA9IHgkWDEsCiAgICAgICAgICAgICAgIHJhbmdlcyA9IElSYW5nZXMoc3RhcnQgPSB4JFgyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZCA9IHgkWDMpLAogICAgICAgICAgICAgICBzY29yZSA9IHgkWDUsCiAgICAgICAgICAgICAgIGVuaGFuY2VybmFtZSA9IHgkWDQpCiAgcmV0dXJuKHgpCiAgCn0pCnJmZWNzLmVuaGFuY2VycyRoZWFydApgYGAKCiMjIyMgQ1NJLUFOTiBFbmhhbmNlciBQcmVkaWN0aW9ucwoKQ1NJLUFOTiAoY2hyb21hdGluIHNpZ25hdHVyZSBpZGVudGlmaWNhdGlvbiBieSBhcnRpZmljaWFsIG5ldXJhbCBuZXR3b3JrKSBpcyBkZXNjcmliZWQgaW4gKltEaXNjb3ZlciByZWd1bGF0b3J5IEROQSBlbGVtZW50cyB1c2luZyBjaHJvbWF0aW4gc2lnbmF0dXJlcyBhbmQgYXJ0aWZpY2lhbCBuZXVyYWwgbmV0d29ya10oaHR0cDovL2R4LmRvaS5vcmcvMTAuMTA5My9iaW9pbmZvcm1hdGljcy9idHEyNDgpKiBhbmQgaXMgYSBmcmFtZXdvcmsgdGhhdCBjb25zaXN0cyBvZiBhIGRhdGEgdHJhbnNmb3JtYXRpb24gYW5kIGEgZmVhdHVyZSBleHRyYWN0aW9uIHN0ZXAgZm9sbG93ZWQgYnkgYSBjbGFzc2lmaWNhdGlvbiBzdGVwIHVzaW5nIHRpbWUtZGVsYXkgbmV1cmFsIG5ldHdvcmsuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpjc2lhbm4uZW5oYW5jZXJzIDwtIGMoIm5ldXJhbCB0dWJlIiA9ICJOVCIsIAogICAgICAgICAgICAgICAgICAgICAgIG1pZGJyYWluICAgICAgPSAiTUIiLCAKICAgICAgICAgICAgICAgICAgICAgICBoaW5kYnJhaW4gICAgID0gIkhCIiwKICAgICAgICAgICAgICAgICAgICAgICBsaW1iICAgICAgICAgID0gIkxNIiwKICAgICAgICAgICAgICAgICAgICAgICBoZWFydCAgICAgICAgID0gIkhUIikKY3NpYW5uLmVuaGFuY2VycyA8LSBsYXBwbHkoY3NpYW5uLmVuaGFuY2VycywgZnVuY3Rpb24oeCkgewogIGZpbGUucGF0aCA8LSBmaWxlLnBhdGgoImRhdGEiLCAiZW5oYW5jZXJfcHJlZGljdGlvbnMiLCAiQ1NJQU5OIiwgcGFzdGUwKCJDU0lBTk5fcHJlZF9FMTFfNV8iLCB4LCAiLmJlZCIpKQogIHggPC0gcmVhZF90c3YoZmlsZS5wYXRoLCBjb2xfbmFtZXMgPSBGQUxTRSkKICB4IDwtIEdSYW5nZXMoc2VxbmFtZXMgPSB4JFgxLAogICAgICAgICAgICAgICByYW5nZXMgPSBJUmFuZ2VzKHN0YXJ0ID0geCRYMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmQgPSB4JFgzKSwKICAgICAgICAgICAgICAgc2NvcmUgPSB4JFg1LAogICAgICAgICAgICAgICBlbmhhbmNlcm5hbWUgPSB4JFg0KQogIHJldHVybih4KQogIAp9KQpjc2lhbm4uZW5oYW5jZXJzJGhlYXJ0CmBgYAoKIyMjIyBFbmhhbmNlckZpbmRlciBlbmhhbmNlciBwcmVkaWN0aW9ucwpBbm90aGVyIHN5c3RlbSBvZiBlbmhhbmNlciBwcmVkaWN0aW9uIHRoYXQgd2UgbWF5IGNvbXBhcmUgYWdhaW5zdCBpcyBbRW5oYW5jZXJGaW5kZXJdKGh0dHA6Ly9keC5kb2kub3JnLzEwLjEzNzEvam91cm5hbC5wY2JpLjEwMDM2NzcpLCBhIHR3by1zdGVwIG1ldGhvZCBmb3IgZGlzdGluZ3Vpc2hpbmcgZGV2ZWxvcG1lbnRhbCBlbmhhbmNlcnMgZnJvbSB0aGUgZ2Vub21pYyBiYWNrZ3JvdW5kIGFuZCB0aGVuIHByZWRpY3RpbmcgdGhlaXIgdGlzc3VlIHNwZWNpZmljaXR5LiBBbW9uZyB0aGUgU3VwcG9ydGluZyBJbmZvcm1hdGlvbiBmb3IgdGhlIHBhcGVyIGFyZSB0aGUgZW5oYW5jZXIgcHJlZGljdGlvbnMgbWFkZSBmb3IgKmJyYWluKiwgKmxpbWIqLCBhbmQgKmhlYXJ0Ki4gVGhlc2UgaG93ZXZlciBhcmUgaW5kaWNhdGVkIGluIGhnMTkgZ2Vub21pYyBjb29yZGluYXRlcywgc28gd2Ugd2lsbCBoYXZlIHRvIGdldCB0aGUgaGcxOSBjb29yZGluYXRlcyBmb3IgdGhlIFZJU1RBIGRhdGFiYXNlIGFzIHdlbGwuCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CnZpc3RhLmh1bWFuIDwtIHJlYWRfdHN2KCJkYXRhL1ZJU1RBX2ludml2b190ZXN0ZWRfaGcxOS5iZWQiLCBjb2xfbmFtZXMgPSBGQUxTRSkKdmlzdGEuaHVtYW4udmFsaWRhdGlvbiA8LSBzdHJpbmdyOjpzdHJfcmVwbGFjZSh2aXN0YS5odW1hbiRYMTAsICJnYW5nbGlvbiwgY3JhbmlhbCIsICJnYW5nbGlvbi1jcmFuaWFsIikKdmlzdGEuaHVtYW4udmFsaWRhdGlvbiA8LSBzdHJpbmdyOjpzdHJfc3BsaXQodmlzdGEuaHVtYW4udmFsaWRhdGlvbiwgIiwiKQp2aXN0YS5odW1hbi52YWxpZGF0aW9uIDwtIGxhcHBseSh2aXN0YS5odW1hbi52YWxpZGF0aW9uLCBmdW5jdGlvbih4KSB7CiAgeCA8LSBzdHJpbmdyOjpzdHJfcmVwbGFjZSh4LCAiIFxcKC4qXFwpIiwgIiIpCiAgeCA8LSBzdHJpbmdyOjpzdHJfdHJpbSh4LCBzaWRlID0gImJvdGgiKQogIHJldHVybih4KQp9KQp2aXN0YS5odW1hbi52YWxpZGF0aW9uIDwtIGxhcHBseSh2aXN0YS5odW1hbi52YWxpZGF0aW9uLCBmdW5jdGlvbih4KSB7CiAgeCA8LSBzdHJpbmdyOjpzdHJfcmVwbGFjZSh4LCAiXFxbLipcXF0iLCAiIikKfSkKdmlzdGEuaHVtYW4ubWF0cml4IDwtIG1hdHJpeCgwTCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdyA9IG5yb3codmlzdGEuaHVtYW4pLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gbGVuZ3RoKHVuaXF1ZSh1bmxpc3QodmlzdGEuaHVtYW4udmFsaWRhdGlvbikpKSkKY29sbmFtZXModmlzdGEuaHVtYW4ubWF0cml4KSA8LSB1bmlxdWUodW5saXN0KHZpc3RhLmh1bWFuLnZhbGlkYXRpb24pKQpmb3IoZW5oYW5jZXIgaW4gc2VxX2Fsb25nKHZpc3RhLmh1bWFuLnZhbGlkYXRpb24pKSB7CiAgdmlzdGEuaHVtYW4ubWF0cml4W2VuaGFuY2VyLCB2aXN0YS5odW1hbi52YWxpZGF0aW9uW1tlbmhhbmNlcl1dXSA8LSAxTAp9CgpsaWJyYXJ5KEdlbm9taWNSYW5nZXMpCnZpc3RhLmh1bWFuIDwtIEdSYW5nZXMoc2VxbmFtZXMgPSB2aXN0YS5odW1hbiRYMSwKICAgICAgICAgICAgICAgICAgICAgICByYW5nZXMgPSBJUmFuZ2VzKHN0YXJ0ID0gdmlzdGEuaHVtYW4kWDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmQgPSB2aXN0YS5odW1hbiRYMyksCiAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9IHZpc3RhLmh1bWFuJFg0LAogICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRlZCA9IHZpc3RhLmh1bWFuJFg1LAogICAgICAgICAgICAgICAgICAgICAgIHNlcWluZm8gPSBTZXFpbmZvKGdlbm9tZSA9ICJoZzE5IikpCgptY29scyh2aXN0YS5odW1hbikgPC0gY2JpbmQoYXMuZGF0YS5mcmFtZShtY29scyh2aXN0YS5odW1hbikpLCB2aXN0YS5odW1hbi5tYXRyaXgpCnZpc3RhLmh1bWFuWywgMTo0XQpgYGAKVGhlIGVuaGFuY2VyIHByZWRpY3Rpb25zIHRoYXQgRW5oYW5jZXJGaW5kZXIgZ2VuZXJhdGVzIG9ubHkgaGFzIGEgc2luZ2xlIGNhdGVnb3J5IGZvciAqYnJhaW4qIHNvIHdlIHdpbGwgY29tcGFyZSBpdCB0byBib3RoICpoaW5kYnJhaW4qIGFuZCAqbWlkYnJhaW4qLCB0aGV5IGFsc28gbWFkZSBubyBwcmVkaWN0aW9ucyBmb3IgbmV1cmFsIHR1YmUsIHNvIHdlIHdpbGwgYmUgZXhjbHVkaW5nIEVuaGFuY2VyRmluZGVyIGZyb20gdGhhdCBwcmVkaWN0aW9uLiBCZXlvbmQgdGhhdCwgdGhlIHByb2NlZHVyZSBpcyBzaW1pbGFyIHRvIHdoYXQgd2UgZGlkIGZvciBFTkNPREUuIFN0YXJ0aW5nIHdpdGggY3JlYXRpbmcgYSBHUmFuZ2VzIG9iamVjdCBmb3IgdGhlIHByZWRpY3Rpb25zCmBgYHtyfQpkb3dubG9hZC5maWxlKCJodHRwOi8vZHguZG9pLm9yZy8xMC4xMzcxL2pvdXJuYWwucGNiaS4xMDAzNjc3LnMwMTgiLCBkZXN0ZmlsZSA9ICJkYXRhL2VuaGFuY2VyZmluZGVyLnByZWRpY3Rpb25zLnppcCIpCnVuemlwKCJkYXRhL2VuaGFuY2VyZmluZGVyLnByZWRpY3Rpb25zLnppcCIsIGV4ZGlyID0gZmlsZS5wYXRoKCJkYXRhIiwgImVuaGFuY2VyX3ByZWRpY3Rpb25zIiwgImVuaGFuY2VyZmluZGVyIikpCmwuZWYuZW5oYW5jZXIgPC0gcmVhZC5kZWxpbShmaWxlLnBhdGgoImRhdGEiLCAiZW5oYW5jZXJfcHJlZGljdGlvbnMiLCAiZW5oYW5jZXJmaW5kZXIiLCAiZW5oYW5jZXJmaW5kZXJfbGltYl9oZzE5LmJlZCIpKQpoLmVmLmVuaGFuY2VyIDwtIHJlYWQuZGVsaW0oZmlsZS5wYXRoKCJkYXRhIiwgImVuaGFuY2VyX3ByZWRpY3Rpb25zIiwgImVuaGFuY2VyZmluZGVyIiwgImVuaGFuY2VyZmluZGVyX2hlYXJ0X2hnMTkuYmVkIikpCmIuZWYuZW5oYW5jZXIgPC0gcmVhZC5kZWxpbShmaWxlLnBhdGgoImRhdGEiLCAiZW5oYW5jZXJfcHJlZGljdGlvbnMiLCAiZW5oYW5jZXJmaW5kZXIiLCAiZW5oYW5jZXJmaW5kZXJfYnJhaW5faGcxOS5iZWQiKSkKZW5oYW5jZXJmaW5kZXIuZW5oYW5jZXJzIDwtIGxpc3QoaC5lZi5lbmhhbmNlciwgYi5lZi5lbmhhbmNlciwgbC5lZi5lbmhhbmNlciwgYi5lZi5lbmhhbmNlcikKZW5oYW5jZXJmaW5kZXIuZW5oYW5jZXJzIDwtIGxhcHBseShlbmhhbmNlcmZpbmRlci5lbmhhbmNlcnMsIGZ1bmN0aW9uKHgpIHsKICB4IDwtIEdSYW5nZXMoc2VxbmFtZXMgPSB4JFguY2hyb21vc29tZSwKICAgICAgICAgICAgICAgcmFuZ2VzID0gSVJhbmdlcyhzdGFydCA9IHgkc3RhcnQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZW5kID0geCRlbmQpLAogICAgICAgICAgICAgICBzY29yZSA9IHgkTUtMX3Njb3JlcykKfSkKbmFtZXMoZW5oYW5jZXJmaW5kZXIuZW5oYW5jZXJzKSA8LSBjKCJoZWFydCIsICJoaW5kYnJhaW4iLCAibGltYiIsICJtaWRicmFpbiIpCmVuaGFuY2VyZmluZGVyLmVuaGFuY2VycyRoZWFydApgYGAKCiMjIyBDb21wYXJpbmcgRXh0ZXJuYWwgRW5oYW5jZXIgRGF0YSBTZXRzIHRvIFZpc3RhCgpBcyB3aXRoICpTdGF0ZVBhaW50UiogY29tcGFyZSB0aGlzIGRhdGEgdG8gdGhlIFZJU1RBIGRhdGFzZXQuCgojIyMjIEVOQ09ERSB2MyBFbmhhbmNlci1saWtlIHJlZ2lvbnMKYGBge3J9CnBsb3QuZGF0YS5lbmNvZGUgPC0gUFJHKHN0YXRlcyA9IGVuY29kZS5lbmhhbmNlcnMsCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbXBhcmlzb24gPSB2aXN0YS5lbmhhbmNlcnMsCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbXBhcmlzb24uc2VsZWN0ID0gdGVzdC5lbmhhbmNlcnMpCnBsb3QuZGF0YS5lbmNvZGUkYXVwcmcKYGBgCgojIyMjIFJFUFRJTEUKYGBge3J9CnBsb3QuZGF0YS5yZXB0aWxlIDwtIFBSRyhzdGF0ZXMgPSByZXB0aWxlLmVuaGFuY2VycywKICAgICAgICAgICAgICAgICAgICAgICAgY29tcGFyaXNvbiA9IHZpc3RhLmVuaGFuY2VycywKICAgICAgICAgICAgICAgICAgICAgICAgY29tcGFyaXNvbi5zZWxlY3QgPSB0ZXN0LmVuaGFuY2VycykKcGxvdC5kYXRhLnJlcHRpbGUkYXVwcmcKYGBgCgojIyMjIERFTFRBCmBgYHtyfQpwbG90LmRhdGEuZGVsdGEgPC0gUFJHKHN0YXRlcyA9IGRlbHRhLmVuaGFuY2VycywKICAgICAgICAgICAgICAgICAgICAgICBjb21wYXJpc29uID0gdmlzdGEuZW5oYW5jZXJzLAogICAgICAgICAgICAgICAgICAgICAgIGNvbXBhcmlzb24uc2VsZWN0ID0gdGVzdC5lbmhhbmNlcnMpCnBsb3QuZGF0YS5kZWx0YSRhdXByZwpgYGAKCiMjIyMgUkZFQ1MKYGBge3J9CnBsb3QuZGF0YS5yZmVjcyA8LSBQUkcoc3RhdGVzID0gcmZlY3MuZW5oYW5jZXJzLAogICAgICAgICAgICAgICAgICAgICAgIGNvbXBhcmlzb24gPSB2aXN0YS5lbmhhbmNlcnMsCiAgICAgICAgICAgICAgICAgICAgICAgY29tcGFyaXNvbi5zZWxlY3QgPSB0ZXN0LmVuaGFuY2VycykKcGxvdC5kYXRhLnJmZWNzJGF1cHJnCmBgYAoKIyMjIyBDU0ktQU5OCmBgYHtyfQpwbG90LmRhdGEuY3NpYW5uIDwtIFBSRyhzdGF0ZXMgPSBjc2lhbm4uZW5oYW5jZXJzLAogICAgICAgICAgICAgICAgICAgICAgICBjb21wYXJpc29uID0gdmlzdGEuZW5oYW5jZXJzLAogICAgICAgICAgICAgICAgICAgICAgICBjb21wYXJpc29uLnNlbGVjdCA9IHRlc3QuZW5oYW5jZXJzKQpwbG90LmRhdGEuY3NpYW5uJGF1cHJnCmBgYAoKIyMjIyBFbmhhbmNlckZpbmRlcgpGb3IgZW5oYW5jZXIgZmluZGVyIC0gYWNjb3VudGluZyBmb3IgaXQgYmVpbmcgaW4gKmhnMTkqLCByYXRoZXIgdGhhbiBpbiAqbW0xMCogbGlrZSB0aGUgcHJldmlvdXMgY29tcGFyaXNvbnMKYGBge3J9CnBsb3QuZGF0YS5lbmhhbmNlcmZpbmRlciA8LSBQUkcoc3RhdGVzID0gZW5oYW5jZXJmaW5kZXIuZW5oYW5jZXJzLAogICAgICAgICAgICAgICAgICAgICAgICBjb21wYXJpc29uID0gdmlzdGEuaHVtYW4sCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbXBhcmlzb24uc2VsZWN0ID0gdGVzdC5lbmhhbmNlcnMpCnBsb3QuZGF0YS5lbmhhbmNlcmZpbmRlciRhdXByZwpgYGAKCiMjIFN1cHBsZW1lbnRhbCBGaWd1cmUgNSAtIFZpc3VhbGl6YXRpb24gb2YgUHJlZGljdGlvbnMgb2YgVklTVEEgVmFsaWRhdGVkIGVuaGFuY2VycwojIyBWaXN1YWxpemF0aW9uIG9mIFByZWRpY3Rpb25zIG9mIFZJU1RBIFZhbGlkYXRlZCBlbmhhbmNlcnMKV2UgYmVnaW4gYnkgZG9pbmcgYSBsaXR0bGUgYml0IG9mIGNsZWFuaW5nIHVwIHRoZSBkYXRhIHRvIHByZXBhcmUgZm9yIHBsb3R0aW5nIHdpdGggZ2dwbG90MgpgYGB7ciwgZmlnX2hlaWdodD01LCBmaWdfd2lkdGg9N30KcGxvdC5kYXRhLmVuY29kZSRwcmVjaXNpb24ucmVjYWxsLmdhaW4kU09VUkNFIDwtICJFTkNPREUiCnBsb3QuZGF0YS5zcCRwcmVjaXNpb24ucmVjYWxsLmdhaW4kU09VUkNFIDwtICJTdGF0ZVBhaW50UiIKcGxvdC5kYXRhLnJlcHRpbGUkcHJlY2lzaW9uLnJlY2FsbC5nYWluJFNPVVJDRSA8LSAiUkVQVElMRSIKcGxvdC5kYXRhLmRlbHRhJHByZWNpc2lvbi5yZWNhbGwuZ2FpbiRTT1VSQ0UgPC0gIkRFTFRBIgpwbG90LmRhdGEucmZlY3MkcHJlY2lzaW9uLnJlY2FsbC5nYWluJFNPVVJDRSA8LSAiUkZFQ1MiCnBsb3QuZGF0YS5jc2lhbm4kcHJlY2lzaW9uLnJlY2FsbC5nYWluJFNPVVJDRSA8LSAiQ1NJQU5OIgpwbG90LmRhdGEuZW5oYW5jZXJmaW5kZXIkcHJlY2lzaW9uLnJlY2FsbC5nYWluJFNPVVJDRSA8LSAiRW5oYW5jZXJGaW5kZXIiCgpwcmVjaXNpb24ucmVjYWxsLmdhaW4gPC0gcmJpbmQuZGF0YS5mcmFtZShwbG90LmRhdGEuZW5jb2RlJHByZWNpc2lvbi5yZWNhbGwuZ2FpbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3QuZGF0YS5zcCRwcmVjaXNpb24ucmVjYWxsLmdhaW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3QuZGF0YS5yZXB0aWxlJHByZWNpc2lvbi5yZWNhbGwuZ2FpbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdC5kYXRhLmRlbHRhJHByZWNpc2lvbi5yZWNhbGwuZ2FpbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdC5kYXRhLnJmZWNzJHByZWNpc2lvbi5yZWNhbGwuZ2FpbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdC5kYXRhLmNzaWFubiRwcmVjaXNpb24ucmVjYWxsLmdhaW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3QuZGF0YS5lbmhhbmNlcmZpbmRlciRwcmVjaXNpb24ucmVjYWxsLmdhaW4pCgpwbG90LmRhdGEuZW5jb2RlJGNvbnZleC5odWxsJFNPVVJDRSA8LSAiRU5DT0RFIgpwbG90LmRhdGEuc3AkY29udmV4Lmh1bGwkU09VUkNFIDwtICJTdGF0ZVBhaW50UiIKcGxvdC5kYXRhLnJlcHRpbGUkY29udmV4Lmh1bGwkU09VUkNFIDwtICJSRVBUSUxFIgpwbG90LmRhdGEuZGVsdGEkY29udmV4Lmh1bGwkU09VUkNFIDwtICJERUxUQSIKcGxvdC5kYXRhLnJmZWNzJGNvbnZleC5odWxsJFNPVVJDRSA8LSAiUkZFQ1MiCnBsb3QuZGF0YS5jc2lhbm4kY29udmV4Lmh1bGwkU09VUkNFIDwtICJDU0lBTk4iCnBsb3QuZGF0YS5lbmhhbmNlcmZpbmRlciRjb252ZXguaHVsbCRTT1VSQ0UgPC0gIkVuaGFuY2VyRmluZGVyIgoKY29udmV4Lmh1bGwgPC0gcmJpbmQuZGF0YS5mcmFtZShwbG90LmRhdGEuZW5jb2RlJGNvbnZleC5odWxsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90LmRhdGEuc3AkY29udmV4Lmh1bGwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdC5kYXRhLnJlcHRpbGUkY29udmV4Lmh1bGwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdC5kYXRhLmRlbHRhJGNvbnZleC5odWxsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3QuZGF0YS5yZmVjcyRjb252ZXguaHVsbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90LmRhdGEuY3NpYW5uJGNvbnZleC5odWxsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3QuZGF0YS5lbmhhbmNlcmZpbmRlciRjb252ZXguaHVsbCkKCmNvbWJvcGxvdCA8LSBnZ3Bsb3QocHJlY2lzaW9uLnJlY2FsbC5nYWluLCBhZXMoeSA9IFBSRUNJU0lPTiwgeCA9IFJFQ0FMTCwgZ3JvdXAgPSBTT1VSQ0UpKSArCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IFNPVVJDRSkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IFNPVVJDRSkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeGxpbT1jKDAsMSksIHlsaW0gPSBjKDAuNCwxKSkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIikgKwogIGdlb21fbGluZShkYXRhID0gY29udmV4Lmh1bGwsIGFlcyh5ID0gUFJFQ0lTSU9OLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IFJFQ0FMTCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gU09VUkNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBTT1VSQ0UpLCBsaW5ldHlwZSA9IDIpICsKICB0aGVtZV9ncmV5KCkgKwogIHlsYWIoIlByZWNpc2lvbiBHYWluIikgKyB4bGFiKCJSZWNhbGwgR2FpbiIpICsgdGhlbWUoYXNwZWN0LnJhdGlvPTEpICsKICBmYWNldF93cmFwKCB+IFRJU1NVRSwgbmNvbCA9IDMpCmNvbWJvcGxvdApgYGAKCiMjIEFyZWEgVW5kZXIgdGhlIFByZWNpc2lvbi1SZWNhbGwtR2FpbiBDdXJ2ZQpGcm9tIGFsbCBvZiB0aGlzIGFuYWx5c2lzIHdlIGdlbmVyYXRlIHRoZSBBcmVhIFVuZGVyIHRoZSBQcmVjaXNpb24tUmVjYWxsLUdhaW4gQ3VydmUgKEFVUFJHKSB3aGljaCBjb252ZXlzIGFuIGV4cGVjdGVkIEYxIHNjb3JlIG9uIGEgaGFybW9uaWMgc2NhbGUuIFdoaWNoIGlzIFRhYmxlIDMuCmBgYHtyfQphdXByZyA8LSBkYXRhLmZyYW1lKHNvdXJjZSA9IGMoIkVOQ09ERSIsICJTdGF0ZVBhaW50UiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJFUFRJTEUiLCAiREVMVEEiLCAiUkZFQ1MiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDU0lBTk4iLCAiRW5oYW5jZXJGaW5kZXIiKSwKICAgICAgICAgICAgICAgICAgICAibmV1cmFsIHR1YmUiID0gTkEsCiAgICAgICAgICAgICAgICAgICAgbWlkYnJhaW4gPSBOQSwKICAgICAgICAgICAgICAgICAgICBoaW5kYnJhaW4gPSBOQSwKICAgICAgICAgICAgICAgICAgICBsaW1iID0gTkEsCiAgICAgICAgICAgICAgICAgICAgaGVhcnQgPSBOQSwKICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEZBTFNFKQoKYXVwcmdbYXVwcmckc291cmNlID09ICJFTkNPREUiLCBuYW1lcyhwbG90LmRhdGEuZW5jb2RlJGF1cHJnKV0gPC0gcGxvdC5kYXRhLmVuY29kZSRhdXByZwphdXByZ1thdXByZyRzb3VyY2UgPT0gIlJFUFRJTEUiLCBuYW1lcyhwbG90LmRhdGEucmVwdGlsZSRhdXByZyldIDwtIHBsb3QuZGF0YS5yZXB0aWxlJGF1cHJnCmF1cHJnW2F1cHJnJHNvdXJjZSA9PSAiREVMVEEiLCBuYW1lcyhwbG90LmRhdGEuZGVsdGEkYXVwcmcpXSA8LSBwbG90LmRhdGEuZGVsdGEkYXVwcmcKYXVwcmdbYXVwcmckc291cmNlID09ICJSRkVDUyIsIG5hbWVzKHBsb3QuZGF0YS5yZmVjcyRhdXByZyldIDwtIHBsb3QuZGF0YS5yZmVjcyRhdXByZwphdXByZ1thdXByZyRzb3VyY2UgPT0gIkNTSUFOTiIsIG5hbWVzKHBsb3QuZGF0YS5jc2lhbm4kYXVwcmcpXSA8LSBwbG90LmRhdGEuY3NpYW5uJGF1cHJnCmF1cHJnW2F1cHJnJHNvdXJjZSA9PSAiU3RhdGVQYWludFIiLCBuYW1lcyhwbG90LmRhdGEuc3AkYXVwcmcpXSA8LSBwbG90LmRhdGEuc3AkYXVwcmcKYXVwcmdbYXVwcmckc291cmNlID09ICJFbmhhbmNlckZpbmRlciIsIG5hbWVzKHBsb3QuZGF0YS5lbmhhbmNlcmZpbmRlciRhdXByZyldIDwtIHBsb3QuZGF0YS5lbmhhbmNlcmZpbmRlciRhdXByZwoKYXVwcmckYXZlX2F1cHJnIDwtIHJvd1N1bXMoYXVwcmdbLCAtMV0sIG5hLnJtID0gVCkvNQphdmVyYWdlX3JhbmsgPC0gc2FwcGx5KGF1cHJnWywgYygibmV1cmFsIHR1YmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm1pZGJyYWluIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJoaW5kYnJhaW4iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxpbWIiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImhlYXJ0IildLCBmdW5jdGlvbih4KSB7cmFuaygxIC0geCl9KQphdXByZyRhdmVfcmFuayA8LSBzYXBwbHkoc3BsaXQoYXZlcmFnZV9yYW5rLCAxOm5yb3coYXZlcmFnZV9yYW5rKSksIG1lYW4pCnBhdXByZyA8LSBhdXByZwpwYXVwcmdbLCAtMV0gPC0gc2lnbmlmKHBhdXByZ1ssIC0xXSwgZGlnaXRzID0gMikKcGF1cHJnCmBgYAoKI0ZpZ3VyZSAzIC0gTG9jdXMtIGFuZCB0aXNzdWUtc3BlY2lmaWMgZW5yaWNobWVudCBvZiBQYXJraW5zb24ncyBHV0FTIHZhcmlhbnRzCmBgYHtyfQpwZC5lbnJpY2htZW50IDwtIHJlYWQuZGVsaW0oImRhdGEvUEQuZW5yaWNobWVudC4yNzQ2MTQxMC50eHQiLCBzZXAgPSAiXHQiLCBoZWFkZXIgPSBUUlVFKQpwZC5lbnJpY2htZW50CmVucmljaC5wbG90IDwtIGdncGxvdChwZC5lbnJpY2htZW50LCBhZXMobmFtZSwgZGlmZmVyZW5jZSwgZ3JvdXAgPSBjb2xvcikpICsKICBnZW9tX3BvaW50cmFuZ2UoYWVzKHltaW4gPSBsb3dlciwgeW1heCA9IHVwcGVyLCBjb2xvciA9IGNvbG9yKSwgZmF0dGVuID0gMSwgc2l6ZSA9IDEuMikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgc2NhbGVfY29sb3JfaWRlbnRpdHkoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAiI2VjZWNlYyIsIGFscGhhID0gMC4yKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdCA9IDAsIHZqdXN0ID0gMC41LCBzaXplID0gcmVsKDEuNSkpLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDI3MCwgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMSwgc2l6ZSA9IHJlbCgxLjUpKSkgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJTYW1wbGUiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWUgPSAiZGlmZmVyZW5jZSIsIGJyZWFrcyA9ICBjKDAsIDAuNSwgMSkpICsKICBnZ3RpdGxlKCJFbnJpY2htZW50IG9mIFBEIEdXQVMgdmFyaWFudHMiKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKC0wLjIsMSkpICsKICBmYWNldF9ncmlkKGxvY3VzIH4gdHlwZSwgc2NhbGVzID0gImZyZWVfeCIsIHNwYWNlID0gImZyZWVfeCIsIHN3aXRjaCA9ICJ4IikgKwogIGFubm90YXRlKCJyZWN0IiwgeG1pbj0tSW5mLCB4bWF4PUluZiwgeW1pbj0tSW5mLCB5bWF4PTAsYWxwaGE9MC4xLGZpbGw9ImJsYWNrIikKZW5yaWNoLnBsb3QKYGBgCgojRmlndXJlIDQgLSBFeGFtcGxlIG9mIG1vZGVsIGNvbXBhcmlzb25zLgpgYGB7ciBlY2hvPUZBTFNFLCBldmFsID0gRkFMU0UsIGluY2x1ZGUgPSBGQUxTRX0KaHlwZXIgPC0gaW1wb3J0LmJlZCgiaHlwZXJfY29vcnMuYmVkIikKYmFja2dyb3VuZCA8LSBpbXBvcnQuYmVkKCJiYWNrZ3JvdW5kX2Nvb3JzLmJlZCIpCmxvYWQoIm1vZGVsMi5mb2N1c2VkLnBvaXNlZC5yZGEiKSAjIG1vZGVsMgpsb2FkKCJtb2RlbDEuZGVmYXVsdC5yZGEiKSAjIG1vZGVsMQoKCnNlZ3MgPC0gYygiRVBSIiwgIlBQUiIpCmh5cGVyLm1vZGVsMSA8LSBsYXBwbHkoc2VncywgZnVuY3Rpb24oc2VnLCBtb2RlbCA9IG1vZGVsMSwgbXkuZm9yZWdyb3VuZCA9IGh5cGVyLCBteS5iYWNrZ3JvdW5kID0gYmFja2dyb3VuZCkgewogIGVzZWcgPC0gZW5yaWNoLnNlZ21lbnRzKGZnID0gbXkuZm9yZWdyb3VuZCwKICAgICAgICAgICAgICAgICAgICAgICAgICBiZyA9IG15LmJhY2tncm91bmQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZSA9IG1vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSAic2ltdWxhdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZS5zZWFyY2ggPSBzZWcpCiAgfSkKbmFtZXMoaHlwZXIubW9kZWwxKSA8LSBzZWdzCmh5cGVyLm1vZGVsMiA8LSBsYXBwbHkoc2VncywgZnVuY3Rpb24oc2VnLCBtb2RlbCA9IG1vZGVsMiwgbXkuZm9yZWdyb3VuZCA9IGh5cGVyLCBteS5iYWNrZ3JvdW5kID0gYmFja2dyb3VuZCkgewogIGVzZWcgPC0gZW5yaWNoLnNlZ21lbnRzKGZnID0gbXkuZm9yZWdyb3VuZCwKICAgICAgICAgICAgICAgICAgICAgICAgICBiZyA9IG15LmJhY2tncm91bmQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZSA9IG1vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSAic2ltdWxhdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZS5zZWFyY2ggPSBzZWcpCiAgfSkKbmFtZXMoaHlwZXIubW9kZWwyKSA8LSBzZWdzCmBgYApgYGB7ciBlY2hvPUZBTFNFLCBldmFsID0gRkFMU0UsIGluY2x1ZGUgPSBGQUxTRX0KZW5yaWNobWVudC5vdXQgPC0gZGF0YS5mcmFtZShzYW1wbGUgPSBjaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZy5yYXRpbyA9IG51bWVyaWMoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9iYWJpbGl0eSA9IG51bWVyaWMoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvZGRzcmF0aW8gPSBudW1lcmljKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2Rkcy5sb3dlciA9IG51bWVyaWMoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvZGRzLnVwcGVyID0gbnVtZXJpYygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpZmZlcmVuY2UgPSBudW1lcmljKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG93ZXIgPSBudW1lcmljKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXBwZXIgPSBudW1lcmljKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmcueSA9IG51bWVyaWMoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZy5uID0gbnVtZXJpYygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJnLnkgPSBudW1lcmljKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmcubiA9IG51bWVyaWMoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gY2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBjaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gY2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVzY3JpcHRpb24gPSBjaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaWcgPSBjaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGF0ZSA9IGNoYXJhY3RlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gY2hhcmFjdGVyKCkpCnRlc3RkYXRhIDwtIGxpc3QoaHlwZXIxID0gaHlwZXIubW9kZWwxLCBoeXBlcjIgPSBoeXBlci5tb2RlbDIpCmZvciAoZW5yaWNobWVudCBpbiBzZXFfYWxvbmcodGVzdGRhdGEpKSB7CiAgZm9yKHNlZ21lbnQgaW4gc2VxX2Fsb25nKHRlc3RkYXRhW1sxXV0pKSB7CiAgICBteS5lbnJpY2ggPC0gdGVzdGRhdGFbW2VucmljaG1lbnRdXVtbc2VnbWVudF1dJGVucmljaG1lbnQKICAgIHJvd25hbWVzKG15LmVucmljaCkgPC0gTlVMTAogICAgbG9hZCgifi9vd25DbG91ZC9yZW1jLmNlbGx0eXBlLnRyYW5zbGF0ZS5yZGEiKQogICAgY2VsbHR5cGUua2V5IDwtIGNlbGx0eXBlLmNvbG9yWywgYygxLDMsNCw1LDExKV07IGNvbG5hbWVzKGNlbGx0eXBlLmtleSkgPC0gcGFzdGUwKCJWIiwgMTo1KQogICAgbXkuZW5yaWNoIDwtIGRwbHlyOjpsZWZ0X2pvaW4obXkuZW5yaWNoLCBjZWxsdHlwZS5rZXksIGJ5ID0gYygic2FtcGxlIiA9ICJWMSIpKQogICAgbXkuZW5yaWNoIDwtIGRwbHlyOjpyZW5hbWUobXkuZW5yaWNoLCB0eXBlID0gVjIpCiAgICBteS5lbnJpY2ggPC0gZHBseXI6OnJlbmFtZShteS5lbnJpY2gsIGNvbG9yID0gVjMpCiAgICBteS5lbnJpY2ggPC0gZHBseXI6OnJlbmFtZShteS5lbnJpY2gsIG5hbWUgPSBWNCkKICAgIG15LmVucmljaCA8LSBkcGx5cjo6cmVuYW1lKG15LmVucmljaCwgZGVzY3JpcHRpb24gPSBWNSkKCiAgICBteS5lbnJpY2gkc2lnIDwtICJub3Quc2lnbmlmaWNhbnQiCgogICAgbXkuZW5yaWNoWyhteS5lbnJpY2gkcHJvYmFiaWxpdHkgPiAwLjk3NSB8IG15LmVucmljaCRwcm9iYWJpbGl0eSA8IDAuMDI1KSAmIG15LmVucmljaCRmZy5zdWNjZXNzID4gMCwgInNpZyJdIDwtICJzaWduaWZpY2FudC5lIgoKICAgIG15LmVucmljaCA8LSBteS5lbnJpY2hbb3JkZXIobXkuZW5yaWNoJGNvbG9yKSwgXQogICAgbXkuZW5yaWNoW215LmVucmljaCRzaWcgPT0gIm5vdC5zaWduaWZpY2FudCIsICJjb2xvciJdIDwtICIjZWNlY2VjIgogICAgbXkuZW5yaWNoJG1vZGVsIDwtIG5hbWVzKHRlc3RkYXRhKVtlbnJpY2htZW50XQogICAgbXkuZW5yaWNoJHN0YXRlIDwtIG5hbWVzKHRlc3RkYXRhW1tlbnJpY2htZW50XV0pW3NlZ21lbnRdCiAgICBlbnJpY2htZW50Lm91dCA8LSByYmluZChlbnJpY2htZW50Lm91dCwgbXkuZW5yaWNoKQogIH0KfQplbnJpY2htZW50Lm91dCA8LSBlbnJpY2htZW50Lm91dFtvcmRlcihlbnJpY2htZW50Lm91dCRwcm9iYWJpbGl0eSwgZW5yaWNobWVudC5vdXQkZGlmZmVyZW5jZSwgZGVjcmVhc2luZyA9IFRSVUUpLCBdCmVucmljaG1lbnQub3V0Cgogd3JpdGUudGFibGUoeCA9IGVucmljaG1lbnQub3V0LAogICAgICAgICAgICAgZmlsZSA9ICJkYXRhL21ldGh5bGF0aW9uLmVucmljaG1lbnQudHh0IiwKICAgICAgICAgICAgIHNlcCA9ICJcdCIsCiAgICAgICAgICAgICBxdW90ZSA9IEZBTFNFLAogICAgICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UpCmBgYAojIyBDb21wYXJpbmcgTW9kZWxzIHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfSAKRW5yaWNobWVudCBjYWxjdWxhdGlvbnMgd2VyZSBkb25lIGFzIGluIHNlY3Rpb24gYWJvdmUgdXNpbmcgZWl0aGVyIG9mIHR3byBkaWZmZXJlbnQgc3RhdGUgbW9kZWxzIChNb2RlbCAxIGFuZCBNb2RlbCAyKSBmcm9tIFN0YXRlSHViLCAiRGVmYXVsdCIgYW5kICJGb2N1c2VkIFBvaXNlZCBQcm9tb3RlciIsIHdoaWNoIGRpZmZlciBpbiB0aGUgdHJlYXRtZW50IG9mIHBvaXNlZCBwcm9tb3RlcnMuIEVhY2ggcGxvdCBpcyBtYWRlIHVzaW5nIHRoZSBzYW1lIHkgYXhpcyByYW5nZSBmb3IgY29tcGFyaXNvbiBhbmQgZW1waGFzaXplcyB0aGF0IG9uZSBtb2RlbCBpcyBjbGVhcmx5IG1vcmUgc2VsZWN0aXZlIHRoYW4gdGhlIG90aGVyLiBCb3RoIG1vZGVscyBjbGVhcmx5IGRldGVjdCBlbnJpY2htZW50IG9mIGh5cGVybWV0aHlsYXRlZCBwcm9iZXMgaW4gdGhlIHBvaXNlZCBzdGF0ZS4gTW9kZWwgMiBpcyBtb3JlIHNlbGVjdGl2ZSB0aGFuIG1vZGVsIDEgaW4gaXRzIGRlZmluaXRpb24gb2YgcG9pc2VkIHByb21vdGVyLgpgYGB7ciBtZXNzYWdlID0gRkFMU0V9CmVucmljaG1lbnQub3V0IDwtIHJlYWRfdHN2KCJkYXRhL21ldGh5bGF0aW9uLmVucmljaG1lbnQudHh0IikKZW5yaWNobWVudC5vdXQgPC0gZW5yaWNobWVudC5vdXRbZW5yaWNobWVudC5vdXQkdHlwZSAhPSAiRU5DT0RFMjAxMiIsIF0KZW5yaWNobWVudC5vdXQKYGBgCiMjIyBNb2RlbCAxIC0gRGVmYXVsdCBNb2RlbAoqQ2xpY2sgdGhlIGJ1dHRvbnMgYWJvdmUgdG8gY2hhbmdlIG1vZGVscy4qCgpJbiBNb2RlbCAxLCB3ZSBhc3NpZ24gYW55IHByb21vdGVycyBsYWNraW5nIGFjdGl2ZSBtYXJrcyB0byB0aGUgcG9pc2VkIHN0YXRlLgpgYGB7cn0KbW9kZWwxLnBsb3QgPC0gZ2dwbG90KGVucmljaG1lbnQub3V0W2VucmljaG1lbnQub3V0JG1vZGVsID09ICJoeXBlcjEiLCBdLCBhZXMobmFtZSwgb2Rkc3JhdGlvLCBncm91cCA9IGNvbG9yKSkgKwogIGdlb21fcG9pbnRyYW5nZShhZXMoeW1pbiA9IG9kZHMubG93ZXIsIHltYXggPSBvZGRzLnVwcGVyLCBjb2xvciA9IGNvbG9yKSwgZmF0dGVuID0gMSwgc2l6ZSA9IDEuMikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgc2NhbGVfY29sb3JfaWRlbnRpdHkoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgY29sb3IgPSAiIzAwMDAwMCIsIGFscGhhID0gMC4xKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAudGV4dC55ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgaGp1c3QgPSAwLCB2anVzdCA9IDAuNSwgc2l6ZSA9IHJlbCgxLjUpKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAyNzAsIGhqdXN0ID0gMC41LCB2anVzdCA9IDEsIHNpemUgPSByZWwoMS41KSkpICsKICBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiU2FtcGxlIikgKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gIk9kZHMgUmF0aW8iKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDAsMTIpKSArCiAgZmFjZXRfZ3JpZChzdGF0ZSB+IHR5cGUsIHNjYWxlcyA9ICJmcmVlX3giLCBzcGFjZSA9ICJmcmVlX3giLCBzd2l0Y2ggPSAieCIpICsKICBhbm5vdGF0ZSgicmVjdCIsIHhtaW49LUluZiwgeG1heD1JbmYsIHltaW49LUluZiwgeW1heD0xLGFscGhhPTAuMSxmaWxsPSJibGFjayIpCm1vZGVsMS5wbG90CmBgYAojIyMgTW9kZWwgMiAtIEZvY3VzZWQgUG9pc2VkIFByb21vdGVyCipDbGljayB0aGUgYnV0dG9ucyBhYm92ZSB0byBjaGFuZ2UgbW9kZWxzLioKCkluIHRoaXMgbW9kZWwsIGVuaGFuY2VycyB3aXRoIEgzSzRtZTEgYW5kIHByb21vdGVycyB3aXRoIEgzSzRtZTMgb3ZlcmxhcHBpbmcgbmFycm93IHJlZ2lvbnMgb2YgSDNLMjdtZTMgYXJlIGNhbGxlZCBwb2lzZWQgKEVQUiBhbmQgUFBSKSwgYnV0IHRob3NlIHdpdGhvdXQgSDNLMjdtZTMgYXJlIGNhbGxlZCB3ZWFrIChFV1IgYW5kIFBXUikKYGBge3J9Cm1vZGVsMi5wbG90IDwtIGdncGxvdChlbnJpY2htZW50Lm91dFtlbnJpY2htZW50Lm91dCRtb2RlbCA9PSAiaHlwZXIyIiwgXSwgYWVzKG5hbWUsIG9kZHNyYXRpbywgZ3JvdXAgPSBjb2xvcikpICsKICBnZW9tX3BvaW50cmFuZ2UoYWVzKHltaW4gPSBvZGRzLmxvd2VyLCB5bWF4ID0gb2Rkcy51cHBlciwgY29sb3IgPSBjb2xvciksIGZhdHRlbiA9IDEsIHNpemUgPSAxLjIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIGNvbG9yID0gIiMwMDAwMDAiLCBhbHBoYSA9IDAuMSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIsCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIGhqdXN0ID0gMCwgdmp1c3QgPSAwLjUsIHNpemUgPSByZWwoMS41KSksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMjcwLCBoanVzdCA9IDAuNSwgdmp1c3QgPSAxLCBzaXplID0gcmVsKDEuNSkpKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lID0gIlNhbXBsZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJPZGRzIFJhdGlvIikgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLDEyKSkgKwogIGZhY2V0X2dyaWQoc3RhdGUgfiB0eXBlLCBzY2FsZXMgPSAiZnJlZV94Iiwgc3BhY2UgPSAiZnJlZV94Iiwgc3dpdGNoID0gIngiKSArCiAgYW5ub3RhdGUoInJlY3QiLCB4bWluPS1JbmYsIHhtYXg9SW5mLCB5bWluPS1JbmYsIHltYXg9MSxhbHBoYT0wLjEsZmlsbD0iYmxhY2siKQptb2RlbDIucGxvdApgYGAKCiNGaWd1cmUgNSAtIEVucmljaG1lbnQgaW4gZ2Vub21pYyBhbm5vdGF0aW9ucwojIyBITUVDIHNlZ21lbnRhdGlvbgojIyMgU3RhdGVQYWludFIKYGBge3IsIG1lc3NhZ2UgPSBUUlVFLCByZXN1bHRzID0gImhpZGUifQpobWVjLnN0YXRlcyA8LSBQYWludFN0YXRlcyhtYW5pZmVzdCA9ICJkYXRhL21hbmlmZXN0LmhtZWMudHh0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVjaXNpb25NYXRyaXggPSBkbSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZ3Jlc3MgPSBGQUxTRSkKRXhwb3J0U3RhdGVQYWludFIoc3RhdGVzID0gaG1lYy5zdGF0ZXMsCiAgICAgICAgICAgICAgICAgIG91dHB1dC5kaXIgPSAiZGF0YS9ITUVDLyIpCmBgYAojIyMgRW5zZW1ibCBBbm5vdGF0aW9ucwpgYGB7cn0KIyBBY3RpdmUgYW5kIEluYWN0aXZlIEVuaGFuY2VycyBpbiBITUVDCm1hcnQgPC0gdXNlTWFydChob3N0ID0gImdyY2gzNy5lbnNlbWJsLm9yZyIsIAogICAgICAgICAgICAgICAgYmlvbWFydCA9ICJFTlNFTUJMX01BUlRfRlVOQ0dFTiIsIAogICAgICAgICAgICAgICAgZGF0YXNldCA9ICJoc2FwaWVuc19yZWd1bGF0b3J5X2ZlYXR1cmUiKQpobWVjLmVuaGFuY2VycyA8LSBnZXRCTShmaWx0ZXJzID0gYygicmVndWxhdG9yeV9mZWF0dXJlX3R5cGVfbmFtZSIsICJlcGlnZW5vbWVfbmFtZSIpLAogICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBsaXN0KGMoIkVuaGFuY2VyIiksIGMoIkhNRUMiKSksCiAgICAgICAgICAgICAgICAgICAgICAgIGF0dHJpYnV0ZXMgPSBjKCJjaHJvbW9zb21lX25hbWUiLCAiY2hyb21vc29tZV9zdGFydCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjaHJvbW9zb21lX2VuZCIsICJmZWF0dXJlX3R5cGVfbmFtZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJyZWd1bGF0b3J5X3N0YWJsZV9pZCIsICJhY3Rpdml0eSIpLAogICAgICAgICAgICAgICAgICAgICAgICBtYXJ0ID0gbWFydCkKaG1lYy5lbmhhbmNlcnMgPC0gaG1lYy5lbmhhbmNlcnNbb3JkZXIoaG1lYy5lbmhhbmNlcnMkY2hyb21vc29tZV9uYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBobWVjLmVuaGFuY2VycyRjaHJvbW9zb21lX3N0YXJ0KSwgXQpobWVjLmVuaGFuY2VycyRjaHJvbW9zb21lX25hbWUgPC0gcGFzdGUwKCJjaHIiLCBobWVjLmVuaGFuY2VycyRjaHJvbW9zb21lX25hbWUpCmhlYWQoaG1lYy5lbmhhbmNlcnMsIG4gPSA1KQpobWVjLmVuaGFuY2VyLmdmZiA8LSBkYXRhLmZyYW1lKGNociA9IGhtZWMuZW5oYW5jZXJzJGNocm9tb3NvbWVfbmFtZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc291cmNlID0gImVuc2VtYmxfaHNhcGllbnNfcmVndWxhdG9yeV9mZWF0dXJlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZSA9IHBhc3RlKCJFbmhhbmNlciIsIGhtZWMuZW5oYW5jZXJzJGFjdGl2aXR5LCBzZXAgPSAiXyIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFydCA9IGhtZWMuZW5oYW5jZXJzJGNocm9tb3NvbWVfc3RhcnQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZW5kID0gaG1lYy5lbmhhbmNlcnMkY2hyb21vc29tZV9lbmQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NvcmUgPSAxMDAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmFuZCA9ICIuIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcmFtZSA9ICIuIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IGhtZWMuZW5oYW5jZXJzJGFjdGl2aXR5KQpoZWFkKGhtZWMuZW5oYW5jZXIuZ2ZmLCBuID0gNSkKd3JpdGUudGFibGUoaG1lYy5lbmhhbmNlci5nZmZbaG1lYy5lbmhhbmNlci5nZmYkZ3JvdXAgPT0gIkFDVElWRSIsIF0sIAogICAgICAgICAgICBmaWxlID0gImRhdGEvYWN0aXZlLmhtZWMuZW5oYW5jZXJzLmdmZiIsIAogICAgICAgICAgICBzZXAgPSAiXHQiLCAKICAgICAgICAgICAgcXVvdGUgPSBGQUxTRSwKICAgICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UsCiAgICAgICAgICAgIGNvbC5uYW1lcyA9IEZBTFNFKQp3cml0ZS50YWJsZShobWVjLmVuaGFuY2VyLmdmZltobWVjLmVuaGFuY2VyLmdmZiRncm91cCA9PSAiSU5BQ1RJVkUiLCBdLCAKICAgICAgICAgICAgZmlsZSA9ICJkYXRhL2luYWN0aXZlLmhtZWMuZW5oYW5jZXJzLmdmZiIsIAogICAgICAgICAgICBzZXAgPSAiXHQiLCAKICAgICAgICAgICAgcXVvdGUgPSBGQUxTRSwKICAgICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UsCiAgICAgICAgICAgIGNvbC5uYW1lcyA9IEZBTFNFKQojIEdlbmNvZGUgR2VuZSBNb2RlbHMKZG93bmxvYWQuZmlsZSh1cmwgPSAiZnRwOi8vZnRwLnNhbmdlci5hYy51ay9wdWIvZ2VuY29kZS9HZW5jb2RlX2h1bWFuL3JlbGVhc2VfMTkvZ2VuY29kZS52MTkuYW5ub3RhdGlvbi5ndGYuZ3oiLAogICAgICAgICAgICAgIGRlc3RmaWxlID0gImRhdGEvZ2VuY29kZS52MTkuYW5ub3RhdGlvbi5ndGYuZ3oiLCBxdWlldCA9IFRSVUUpCmBgYAojIyBTZWd0b29scyBBbmFseXNpcwpJbiBvcmRlciB0byBnZW5lcmF0ZSB0aGUgcGxvdHMgdG8gc2hvdyBzZWdtZW50YXRpb24gZW5yaWNobWVudCBpbiBvdGhlciBnZW5vbWljIHJlZ2lvbnMsIHdlIHVzZSBbc2VndG9vbHNdKGh0dHBzOi8vd3d3LnBtZ2Vub21pY3MuY2EvaG9mZm1hbmxhYi9wcm9qL3NlZ3Rvb2xzLykKYGBge3IgZW5naW5lID0gJ2Jhc2gnLCBldmFsID0gRkFMU0V9CmdyZXAgLXYgJyMnIGRhdGEvSE1FQy9lMTE3LjdtYXJrLnNlZ21lbnRhdGlvbi5iZWQgfCBcCiAgc2VkICcxZCcgPiBkYXRhL0hNRUMvZTExNy43bWFyay5zZWdtZW50YXRpb24ubm9oZWFkZXIuYmVkCnNlZ3Rvb2xzLWFnZ3JlZ2F0aW9uIFwKICAtLW5vcGxvdCBcCiAgLW8gaG1lYy5nZW5lLnN0YXRlcGFpbnRyIFwKICAtLW1vZGUgZ2VuZSBcCiAgLS1ub3JtYWxpemUgXAogIGRhdGEvSE1FQy9lMTE3LjdtYXJrLnNlZ21lbnRhdGlvbi5ub2hlYWRlci5iZWQgXAogIGRhdGEvZ2VuY29kZS52MTkuYW5ub3RhdGlvbi5ndGYuZ3oKc2VndG9vbHMtYWdncmVnYXRpb24gXAogIC0tbm9wbG90IFwKICAtbyBkYXRhL2htZWMuYWN0aXZlLmVuaGFuY2VyLnN0YXRlcGFpbnRyIFwKICAtLWdyb3VwIFwKICAtLW1vZGUgcmVnaW9uIFwKICAtLW5vcm1hbGl6ZSBcCiAgZGF0YS9ITUVDL2UxMTcuN21hcmsuc2VnbWVudGF0aW9uLm5vaGVhZGVyLmJlZCBcCiAgZGF0YS9hY3RpdmUuaG1lYy5lbmhhbmNlcnMuZ2ZmCnNlZ3Rvb2xzLWFnZ3JlZ2F0aW9uIFwKICAtLW5vcGxvdCBcCiAgLW8gZGF0YS9obWVjLmluYWN0aXZlLmVuaGFuY2VyLnN0YXRlcGFpbnRyIFwKICAtLWdyb3VwIFwKICAtLW1vZGUgcmVnaW9uIFwKICAtLW5vcm1hbGl6ZSBcCiAgZGF0YS9ITUVDL2UxMTcuN21hcmsuc2VnbWVudGF0aW9uLm5vaGVhZGVyLmJlZCBcCiAgZGF0YS9pbmFjdGl2ZS5obWVjLmVuaGFuY2Vycy5nZmYKYGBgCiMjIFBsb3R0aW5nCkZpcnN0IHdlIG5lZWQgdG8gcGFyc2UgdGhlIG91dHB1dCBvZiBzZWd0b29scyB0byBwcmVwYXJlIGZvciBwbG90dGluZwpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpzZWd0b29scy5maWxlcyA8LSBjKCJBY3RpdmUgRW5oYW5jZXIiID0gCiAgICAgICAgICAgICAgICAgICAgICAiZGF0YS9obWVjLmFjdGl2ZS5lbmhhbmNlci5zdGF0ZXBhaW50ci9mZWF0dXJlX2FnZ3JlZ2F0aW9uLnRhYiIsCiAgICAgICAgICAgICAgICAgICAgIkluYWN0aXZlIEVuaGFuY2VyIiA9IAogICAgICAgICAgICAgICAgICAgICAgImRhdGEvaG1lYy5pbmFjdGl2ZS5lbmhhbmNlci5zdGF0ZXBhaW50ci9mZWF0dXJlX2FnZ3JlZ2F0aW9uLnRhYiIsCiAgICAgICAgICAgICAgICAgICAgIkdlbmUgQm9keSIgPSAKICAgICAgICAgICAgICAgICAgICAgICJkYXRhL2htZWMuZ2VuZS5zdGF0ZXBhaW50ci9mZWF0dXJlX2FnZ3JlZ2F0aW9uLnRhYiIpCnBsb3RzIDwtIGxpc3QoKQpmb3IgKGZpbGUgaW4gc2VxX2Fsb25nKHNlZ3Rvb2xzLmZpbGVzKSkgewogICMgcmVhZCB0aGUgZmlsZQogIGRhdGEgPC0gcmVhZF90c3Yoc2VndG9vbHMuZmlsZXNbZmlsZV0sIGNvbW1lbnQgPSAiIyIpCiAgIyBzZXQgY29tcG9uZW50IG9yZGVyCiAgY29tcG9uZW50Lm9yZGVyIDwtIHVuaXF1ZShkYXRhJGNvbXBvbmVudCkKICAjIHBhcnNlIGhlYWRlciBmb3Igbm9ybWFsaXphdGlvbgogICMgaGVhZGVyIGNvbnRhaW5zIG1ldGFkYXRhIGFib3V0IHNlZ21lbnRzCiAgaGVhZGVyIDwtIHJlYWRMaW5lcyhzZWd0b29scy5maWxlc1tmaWxlXSwgbiA9IDEpCiAgaGVhZGVyIDwtIHN0cl9yZXBsYWNlKGhlYWRlciwgIiMiLCAiIikgJT4lIHN0cl9zcGxpdChwYXR0ZXJuID0gIiAiLCBzaW1wbGlmeSA9IFRSVUUpCiAgaGVhZGVyIDwtIGhlYWRlclsgLTEgXQogIGhlYWRlci5uYW1lcyA8LSBzYXBwbHkoc3RyX3NwbGl0KGhlYWRlciwgcGF0dGVybiA9ICI9IiksICJbIiwgMSkKICBoZWFkZXIgPC0gYXMuaW50ZWdlcihzYXBwbHkoc3RyX3NwbGl0KGhlYWRlciwgcGF0dGVybiA9ICI9IiksICJbIiwgMikpCiAgIyBjcmVhdGUgbm9ybWFsaXplZCB2YWx1ZXMKICBub3JtLnZhbHVlcyA8LSBkYXRhLmZyYW1lKFNlZ21lbnQgPSBoZWFkZXIubmFtZXMsIFRvdGFsID0gaGVhZGVyLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCiAgbnVtLmZlYXR1cmVzIDwtIHdoaWNoKG5vcm0udmFsdWVzJFNlZ21lbnQgJWluJSAibnVtX2ZlYXR1cmVzIikKICBub3JtLnZhbHVlcyA8LSBub3JtLnZhbHVlc1sgLW51bS5mZWF0dXJlcywgXQogIG5vcm0udmFsdWVzJFJhbmRvbSA8LSBub3JtLnZhbHVlcyRUb3RhbCAvIHN1bShub3JtLnZhbHVlcyRUb3RhbCkKICAjIFNldCB1cCBkYXRhIGZvciBwbG90dGluZyAgCiAgZGF0YSA8LSBnYXRoZXIoZGF0YSwgU2VnbWVudCwgVmFsdWUsIDQ6bmNvbChkYXRhKSkKICBkYXRhIDwtIGxlZnRfam9pbihkYXRhLCBub3JtLnZhbHVlcykKICBub3JtLnZhbHVlczIgPC0gZ3JvdXBfYnkoZGF0YSwgb2Zmc2V0LCBjb21wb25lbnQpICU+JSBzdW1tYXJpc2UoT2Zmc2V0LlRvdGFsID0gc3VtKFZhbHVlKSkKICBkYXRhIDwtIGxlZnRfam9pbihkYXRhLCBub3JtLnZhbHVlczIpCiAgZGF0YSA8LSBtdXRhdGUoZGF0YSwgTm9ybS5WYWx1ZSA9IChWYWx1ZSAvIE9mZnNldC5Ub3RhbCkgKyAxKQogIGRhdGEgPC0gbXV0YXRlKGRhdGEsIEVucmljaG1lbnQgPSBsb2cyKE5vcm0uVmFsdWUgLyAoUmFuZG9tICsgMSkpKQogIGRhdGFbaXMubmFuKGRhdGEkRW5yaWNobWVudCksICJFbnJpY2htZW50Il0gPC0gMAogIGRhdGEkY29tcG9uZW50IDwtIGZhY3RvcihkYXRhJGNvbXBvbmVudCwgbGV2ZWxzID0gY29tcG9uZW50Lm9yZGVyKQogIGRhdGEkY29sb3IgPC0gIiM5OThlYzMiCiAgZGF0YVsgZGF0YSRFbnJpY2htZW50ID4gMCwgImNvbG9yIl0gPC0gIiNmMWEzNDAiCiAgaWYgKG5hbWVzKHNlZ3Rvb2xzLmZpbGVzW2ZpbGVdKSA9PSAiR2VuZSBCb2R5IikgewogICAgZGF0YSA8LSBkYXRhW2RhdGEkY29tcG9uZW50ICVpbiUgYygiNScgZmxhbmtpbmc6IDUwMCBicCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpbml0aWFsIGV4b24gKDM5OSBicCkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaW5pdGlhbCBpbnRyb24gKDEzNTkwIGJwKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbCBleG9ucyAoMTU0IGJwKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcm5hbCBpbnRyb25zICg1Nzk0IGJwKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0ZXJtaW5hbCBleG9uICg4ODYgYnApIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRlcm1pbmFsIGludHJvbiAoNjk5MSBicCkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMycgZmxhbmtpbmc6IDUwMCBicCIpLCBdCiAgfQogICMgY3JlYXRlIHBsb3RzCiAgc2VncGxvdCA8LSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBvZmZzZXQsIGZpbGwgPSBjb2xvcikpICsKICAgIGdlb21fYXJlYShhZXMoeSA9IEVucmljaG1lbnQpKSArCiAgICBmYWNldF9ncmlkKFNlZ21lbnQgfiBjb21wb25lbnQsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICBzY2FsZV9maWxsX2lkZW50aXR5KCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZSA9IDAuMiwgY29sb3IgPSAiZ3JleTUwIikgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1jKC0wLjUsIDAsIDAuNSkpICsKICAgIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygtMSwgMSkpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICBnZ3RpdGxlKG5hbWVzKHNlZ3Rvb2xzLmZpbGVzW2ZpbGVdKSkgKyAKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJncmV5ODAiLCBjb2xvdXIgPSAiZ3JleTUwIiwgc2l6ZSA9IDAuMiksCiAgICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvciA9ICJncmV5NTAiKSwKICAgICAgICAgIHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIGhqdXN0ID0gMCwgdmp1c3QgPSAwLjUpLAogICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKQogIHBsb3QgPC0gbGlzdChzZWdwbG90KQogIG5hbWVzKHBsb3QpIDwtIG5hbWVzKHNlZ3Rvb2xzLmZpbGVzW2ZpbGVdKQogIHBsb3RzIDwtIGMocGxvdHMsIHBsb3QpCn0KYGBgCiMjIyMgQWN0aXZlIEVuaGFuY2VycwpgYGB7ciwgZmlnLndpZHRoPTV9CnBsb3RzJGBBY3RpdmUgRW5oYW5jZXJgCmBgYAojIyMjIEluYWN0aXZlIEVuaGFuY2VycwpgYGB7ciwgZmlnLndpZHRoPTV9CnBsb3RzJGBJbmFjdGl2ZSBFbmhhbmNlcmAKYGBgCiMjIyMgR2VuZSBCb2RpZXMKYGBge3IsIGZpZy53aWR0aD03fQpwbG90cyRgR2VuZSBCb2R5YApgYGAKCiMgU3VwcGxlbWVudGFsIEZpZ3VyZXMKIyMgRXZhbHVhdGUgU2NvcmUgQ29tYmluYXRpb24gTWV0aG9kIC0gU3VwcGxlbWVudGFyeSBGaWd1cmUgMgpVc2luZyBhIGJlbmNobWFya2luZyBmdW5jdGlvbiwgd2UgY2FuIGNvbXBhcmUgaG93IFN0YXRlUGFpbnRSIHBlcmZvcm1zIHVzaW5nIGEgdmFyaWV0eSBvZiBzY29yZSBjb21iaW5hdGlvbiBtZXRob2RzLCBpbmNsdWRpbmcgZmluZGluZyB0aGUgbWVhbiwgdGhlIG1lZGlhbiwgYW5kIHRoZSBtYXhpbXVtIG9mIHRoZSBzY29yZXMgb2YgdGhlIGZlYXR1cmVzIHRoYXQgbWFrZSB1cCB0aGUgc2VnbWVudGF0aW9ucy4gV2UgY2FuIGNvbXBhcmUgdGhlc2UgdGhyZWUgc2NvcmUgY29tYmluYXRpb24gbWV0aG9kcyBhZ2FpbnN0IDEwMCB2aXN0YSBlbmhhbmNlcnMgdGhhdCB3ZXJlIGV4Y2x1ZGVkIGFib3ZlLCBpbiBlYWNoIGNhdGVnb3J5LCBbd2hlbiBjb21wYXJpbmcgb3VyIHByZWRpY3Rpb25zLCBhbmQgdGhvc2Ugb2Ygb3RoZXIgZW5oYW5jZXIgcHJlZGljdGlvbiBhbGdvcml0aG1zIHRvIFZJU1RBXSgjdHJhaW5fc3Vic2V0KQpgYGB7cn0KZG0gPC0gZ2V0LmRlY2lzaW9uLm1hdHJpeCgiNTgxM2I2N2Y0NmUwZmIwNmI0OTNjZWIwIikKZG0gPC0gZG9Ob3RTcGxpdChkbSwgIkNvcmUiKQptb3VzZS5lbWJyeW8uc3RhdGVzIDwtIFN0YXRlUGFpbnRSOjo6UGFpbnRTdGF0ZXNCZW5jaG1hcmsobWFuaWZlc3QgPSBtYW5pZmVzdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWNpc2lvbk1hdHJpeCA9IGRtLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjb3JlU3RhdGVzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2dyZXNzID0gRkFMU0UpCnBsb3QuZGF0YSA8LSBsaXN0KCkKbmFtZXMobW91c2UuZW1icnlvLnN0YXRlcykgPC0gc3RyX3JlcGxhY2UobmFtZXMobW91c2UuZW1icnlvLnN0YXRlcyksICIgZW1icnlvIiwgIiIpCmZvciAodGlzc3VlIGluIG5hbWVzKG1vdXNlLmVtYnJ5by5zdGF0ZXMpKSB7CiAgcHJlZGljdGVkLnNjb3JlcyA8LSBtb3VzZS5lbWJyeW8uc3RhdGVzW1t0aXNzdWVdXQogIHZpc3RhLmVuaGFuY2Vycy50ZXN0IDwtIHZpc3RhLmVuaGFuY2VycwogIG1jb2xzKHZpc3RhLmVuaGFuY2Vycy50ZXN0KSA8LSBkYXRhLmZyYW1lKEZPVU5EID0gbWNvbHModmlzdGEuZW5oYW5jZXJzLnRlc3QpWywgdGlzc3VlXSkKICB2aXN0YS5lbmhhbmNlcnMudGVzdCA8LSB2aXN0YS5lbmhhbmNlcnMudGVzdFtjKHdoaWNoKG1jb2xzKHZpc3RhLmVuaGFuY2Vycy50ZXN0KVssICJGT1VORCJdICE9IDEpLCB0cmFpbi5lbmhhbmNlcnNbW3Rpc3N1ZV1dKSxdCiAgcHJlZGljdGVkLnNjb3JlcyA8LSBwcmVkaWN0ZWQuc2NvcmVzW3ByZWRpY3RlZC5zY29yZXMkc3RhdGUgJWluJSBjKCJFQVJDIiwgIkFSQyIsICJBUiIsICJFQVIiKSwgXQogIGZvciAoc2NvcmluZyBpbiBjKCJtZWRpYW4iLCAibWVhbiIsICJtYXgiKSkgewogICAgcHJlZGljdGVkLnNjb3JlcyA8LSBwcmVkaWN0ZWQuc2NvcmVzW29yZGVyKHByZWRpY3RlZC5zY29yZXNbLCBzY29yaW5nXSwgZGVjcmVhc2luZyA9IFRSVUUpLCBdCiAgICBvbGFwcyA8LSBmaW5kT3ZlcmxhcHModmlzdGEuZW5oYW5jZXJzLnRlc3QsIHByZWRpY3RlZC5zY29yZXMsIHNlbGVjdCA9ICJmaXJzdCIpCiAgICBtY29scyh2aXN0YS5lbmhhbmNlcnMudGVzdCkkc2NvcmUgPC0gMAogICAgbWNvbHModmlzdGEuZW5oYW5jZXJzLnRlc3QpW3doaWNoKCFpcy5uYShvbGFwcykpLCAic2NvcmUiXSA8LSBtY29scyhwcmVkaWN0ZWQuc2NvcmVzW29sYXBzWyFpcy5uYShvbGFwcyldXSlbLCBzY29yaW5nXQogICAgcHJnX2N1cnZlIDwtIFN0YXRlUGFpbnRSOjo6Y3JlYXRlX3ByZ19jdXJ2ZShtY29scyh2aXN0YS5lbmhhbmNlcnMudGVzdCkkRk9VTkQsIG1jb2xzKHZpc3RhLmVuaGFuY2Vycy50ZXN0KSRzY29yZSkKICAgIGF1cHJnID0gU3RhdGVQYWludFI6OjpjYWxjX2F1cHJnKHByZ19jdXJ2ZSkKICAgIGNvbnZleF9odWxsID0gU3RhdGVQYWludFI6OjpwcmdfY29udmV4X2h1bGwocHJnX2N1cnZlKQogICAgcGxvdC50aXNzdWUgPC0gbGlzdChsaXN0KHRpc3N1ZSA9IHRpc3N1ZSwgc2NvcmluZ20gPSBzY29yaW5nLCBjdXJ2ZSA9IHByZ19jdXJ2ZSwgYXVwcmcgPSBhdXByZywgaHVsbCA9IGNvbnZleF9odWxsKSkKICAgIG5hbWVzKHBsb3QudGlzc3VlKSA8LSB0aXNzdWUKICAgIHBsb3QuZGF0YSA8LSBjKHBsb3QuZGF0YSwgcGxvdC50aXNzdWUpCiAgfQp9CgpnZy50aXNzdWUgPC0gbGFwcGx5KHBsb3QuZGF0YSwgZnVuY3Rpb24oeSkgewogIHkgPC0gZGF0YS5mcmFtZShUSVNTVUUgPSB5JHRpc3N1ZSwgTUVUSE9EID0geSRzY29yaW5nbSwgUFJFQ0lTSU9OID0geSRjdXJ2ZSRwcmVjaXNpb25fZ2FpbiwgUkVDQUxMID0geSRjdXJ2ZSRyZWNhbGxfZ2FpbikKICByZXR1cm4oeSkKfSkKZ2cudGlzc3VlIDwtIGRvLmNhbGwoInJiaW5kIiwgZ2cudGlzc3VlKQpgYGAKClVzaW5nIHRoZSBzY29yZXMgZ2VuZXJhdGVkIGFib3ZlIHdlIGNhbiBzZWUgdGhlIEFVUFJHIGZvciBlYWNoIHNjb3JpbmcgbWV0aG9kIC8gdGlzc3VlIGNvbWJpbmF0aW9uCgpgYGB7cn0KYXVwcmcgPC0gc2FwcGx5KHBsb3QuZGF0YSwgZnVuY3Rpb24oeCkgeFtjKCJzY29yaW5nbSIsICJhdXByZyIpXSkKZGF0YS5mcmFtZSh0aXNzdWUgPSBjb2xuYW1lcyhhdXByZyksIHNjb3JpbmcubWV0aG9kID0gdW5saXN0KGF1cHJnWyJzY29yaW5nbSIsIF0pLCBhdXByZyA9IHVubGlzdChhdXByZ1siYXVwcmciLCBdKSkKYGBgCgpXZSBjYW4gYWxzbyBnZW5lcmF0ZSB0aGUgcGxvdCwgd2hpY2ggc2hvd3MgdGhhdCB3aGlsZSByZWx5aW5nIG9uIHRoZSBtYXhpbXVtIHNjb3JlIGRvZXMgbm90IG5lY2Vzc2FyaWx5IGdpdmUgdGhlIGJlc3QgQVVQUkcsIGl0IGRvZXMgaGF2ZSBnb29kIHBlcmZvcm1hbmNlIHRvd2FyZHMgaXQncyB0YWlsIGF0IHdpdGggaGlnaCByZWNhbGwgZ2Fpbi4KCmBgYHtyfQpzZmlndXJlLjIgPC0gZ2dwbG90KGdnLnRpc3N1ZSwgYWVzKHkgPSBQUkVDSVNJT04sIHggPSBSRUNBTEwsIGdyb3VwID0gTUVUSE9EKSkgKwogIGdlb21fbGluZShhZXMoY29sb3IgPSBNRVRIT0QpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBNRVRIT0QpKSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW09YygwLDEpLCB5bGltID0gYygwLDEpKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MiIpICsKICB0aGVtZV9ncmV5KCkgKyB0aGVtZShhc3BlY3QucmF0aW89MSkgKwogIGdndGl0bGUoIlN1cHBsZW1lbnRhcnkgRmlndXJlIDIgLSBDb21iaW5pbmcgU2NvcmVzIikgKwogIHlsYWIoIlByZWNpc2lvbiBHYWluIikgKyB4bGFiKCJSZWNhbGwgR2FpbiIpICsKICBmYWNldF93cmFwKCB+IFRJU1NVRSwgbmNvbCA9IDMpCnNmaWd1cmUuMgpgYGAKCiMjIEV2YWx1YXRlIFNjb3JpbmcgLSBTdXBwbGVtZW50YXJ5IEZpZ3VyZSAzCldlIGNhbiBkb3dubG9hZCB0aGUgZm9jdXNlZCBwb2lzZWQgcHJvbW90ZXIgbW9kZWwsIGFuZCB0aGVuIG1vZGlmeSB0aGUgbWFubmVyIGluIHdoaWNoIGl0IHNjb3JlcyBzZWdtZW50cy4gSW4gdGhlIGFic3RyYWN0aW9uIGxheWVyIHdlIGNhbiBkZWNsYXJlIHRoYXQgYSBjbGFzcyBvZiBmZWF0dXJlIG11c3Qgbm90IGJlIHNwbGl0IGludG8gc3ViLWZlYXR1cmVzLCBieSBzdXJyb3VuZGluZyBpdCBpbiBicmFja2V0cywgaS5lLiwgYFtDb3JlXWAuIEFkZGl0aW9uYWxseSB3ZSBjYW4gc3BlY2lmeSB0aGF0IHRoZSBzY29yZXMgZnJvbSBhIHNwZWNpZmljIGZlYXR1cmUgc2hvdWxkIG5vdCBiZSB1c2VkIGluIHNjb3Jpbmcgc2VnbWVudHMsIGJ5IGZvbGxvd2luZyBpdCB3aXRoIGEgc3RhciBgKmAgbGlrZSBzbzogYEFjdGl2ZSpgIG9yIGBbQ29yZV0qYC4gQmFzZWQgb24gdGhpcyB3ZSBjYW4gZ2VuZXJhdGUgZm91ciBkaWZmZXJlbnQgdmVyc2lvbiBvZiBzY29yaW5nIGJhc2VkIHVwb24gdGhlIHNhbWUgc2VnbWVudGF0aW9uIHJ1bGVzLiBBIHZlcnNpb24gY2FsbGVkIGBub2FjdGl2ZWAgZG9lcyBub3QgdXNlIHRoZSBzY29yZSBmcm9tIHRoZSBhY3RpdmUgbWFyaywgYG5vY29yZWAgZG9lcyBub3QgdXNlIHRoZSBzY29yZSBmcm9tIHRoZSBjb3JlIG1hcmssIGBub3JlZ3VsYXRvcnlgIGlnbm9yZXMgdGhlIHNjb3JlIGZyb20gdGhlIHJlZ3VsYXRvcnkgbWFyaywgYW5kIGBhbGxgIHVzZXMgdGhlIHNjb3JlIGZyb20gYWxsIG1hcmtzLgpgYGB7cn0KeCA8LSBnZXQuZGVjaXNpb24ubWF0cml4KCI1ODEzYjY3ZjQ2ZTBmYjA2YjQ5M2NlYjAiKQp4IDwtIGRvTm90U3BsaXQoeCwgIkNvcmUiKQojb25lc2NvcmUKbm9hY3RpdmUueCA8LSB4Cm5vYWN0aXZlLnggPC0gZG9Ob3RTY29yZShub2FjdGl2ZS54LCAiQWN0aXZlIikKbm9jb3JlLnggPC0geApub2NvcmUueCA8LSBkb05vdFNjb3JlKG5vY29yZS54LCAiQ29yZSIpCm5vcmVndWxhdG9yeS54IDwtIHgKbm9yZWd1bGF0b3J5LnggPC0gZG9Ob3RTY29yZShub3JlZ3VsYXRvcnkueCwgIlJlZ3VsYXRvcnkiKQojdHdvc2NvcmVzCmFsbC54IDwtIHgKZG1zIDwtIGxpc3Qobm9hY3RpdmUueCwgbm9jb3JlLngsIG5vcmVndWxhdG9yeS54LCBhbGwueCkKbmFtZXMoZG1zKSA8LSBjKCJub2FjdGl2ZSIsICJub2NvcmUiLCAibm9yZWd1bGF0b3J5IiwgImFsbCIpCmRtcwpgYGAKV2UgY2FuIGNvbXBhcmUgdGhlc2UgZm91ciBzY29yaW5nIG1vZGVscyBhZ2FpbnN0IHRoZSAxMDAgdmlzdGEgZW5oYW5jZXJzIHRoYXQgd2VyZSBleGNsdWRlZCBhYm92ZSwgaW4gZWFjaCBjYXRlZ29yeSwgW3doZW4gY29tcGFyaW5nIG91ciBwcmVkaWN0aW9ucywgYW5kIHRob3NlIG9mIG90aGVyIGVuaGFuY2VyIHByZWRpY3Rpb24gYWxnb3JpdGhtcyB0byBWSVNUQV0oI3RyYWluX3N1YnNldCkKYGBge3J9CnN0YXRlcyA8LSBkbXMKc3RhdGVzIDwtIGxhcHBseShzdGF0ZXMsIGZ1bmN0aW9uKHgpIHtOQX0pCmZvciAoZG0gaW4gc2VxX2Fsb25nKGRtcykpIHsKICBwbG90LmRhdGEgPC0gbGlzdCgpCiAgbW91c2UuZW1icnlvLnN0YXRlcyA8LSBQYWludFN0YXRlcyhtYW5pZmVzdCA9IG1hbmlmZXN0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlY2lzaW9uTWF0cml4ID0gZG1zW1tkbV1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjb3JlU3RhdGVzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2dyZXNzID0gRkFMU0UpCiAgbmFtZXMobW91c2UuZW1icnlvLnN0YXRlcykgPC0gc3RyX3JlcGxhY2UobmFtZXMobW91c2UuZW1icnlvLnN0YXRlcyksICIgZW1icnlvIiwgIiIpCiAgZm9yICh0aXNzdWUgaW4gbmFtZXMobW91c2UuZW1icnlvLnN0YXRlcykpIHsKICAgIHByZWRpY3RlZC5zY29yZXMgPC0gbW91c2UuZW1icnlvLnN0YXRlc1tbdGlzc3VlXV0KICAgIHZpc3RhLmVuaGFuY2Vycy50ZXN0IDwtIHZpc3RhLmVuaGFuY2VycwogICAgbWNvbHModmlzdGEuZW5oYW5jZXJzLnRlc3QpIDwtIGRhdGEuZnJhbWUoRk9VTkQ9bWNvbHModmlzdGEuZW5oYW5jZXJzLnRlc3QpWywgdGlzc3VlXSkKICAgIHZpc3RhLmVuaGFuY2Vycy50ZXN0IDwtIHZpc3RhLmVuaGFuY2Vycy50ZXN0W2Mod2hpY2gobWNvbHModmlzdGEuZW5oYW5jZXJzLnRlc3QpWywgIkZPVU5EIl0gIT0gMSksIHRyYWluLmVuaGFuY2Vyc1tbdGlzc3VlXV0pLF0KICAgIHByZWRpY3RlZC5zY29yZXMgPC0gcHJlZGljdGVkLnNjb3Jlc1twcmVkaWN0ZWQuc2NvcmVzJHN0YXRlICVpbiUgYygiRUFSQyIsICJBUkMiLCAiQVIiLCAiRUFSIiksIF0KICAgIHByZWRpY3RlZC5zY29yZXMgPC0gcHJlZGljdGVkLnNjb3Jlc1tvcmRlcihwcmVkaWN0ZWQuc2NvcmVzJHNjb3JlLCBkZWNyZWFzaW5nID0gVFJVRSksIF0KICAgIG9sYXBzIDwtIGZpbmRPdmVybGFwcyh2aXN0YS5lbmhhbmNlcnMudGVzdCwgcHJlZGljdGVkLnNjb3Jlcywgc2VsZWN0ID0gImZpcnN0IikKICAgIG1jb2xzKHZpc3RhLmVuaGFuY2Vycy50ZXN0KSRzY29yZSA8LSAwCiAgICBtY29scyh2aXN0YS5lbmhhbmNlcnMudGVzdClbd2hpY2goIWlzLm5hKG9sYXBzKSksICJzY29yZSJdIDwtIG1jb2xzKHByZWRpY3RlZC5zY29yZXNbb2xhcHNbIWlzLm5hKG9sYXBzKV1dKSRzY29yZQogICAgcHJnX2N1cnZlIDwtIFN0YXRlUGFpbnRSOjo6Y3JlYXRlX3ByZ19jdXJ2ZShtY29scyh2aXN0YS5lbmhhbmNlcnMudGVzdCkkRk9VTkQsIG1jb2xzKHZpc3RhLmVuaGFuY2Vycy50ZXN0KSRzY29yZSkKICAgIGF1cHJnID0gU3RhdGVQYWludFI6OjpjYWxjX2F1cHJnKHByZ19jdXJ2ZSkKICAgIGNvbnZleF9odWxsID0gU3RhdGVQYWludFI6OjpwcmdfY29udmV4X2h1bGwocHJnX2N1cnZlKQogICAgcGxvdC50aXNzdWUgPC0gbGlzdChsaXN0KHRpc3N1ZSA9IHRpc3N1ZSwgY3VydmU9cHJnX2N1cnZlLCBhdXByZyA9IGF1cHJnLCBodWxsID0gY29udmV4X2h1bGwpKQogICAgbmFtZXMocGxvdC50aXNzdWUpIDwtIHRpc3N1ZQogICAgcGxvdC5kYXRhIDwtIGMocGxvdC5kYXRhLCBwbG90LnRpc3N1ZSkKICB9CiAgc2FwcGx5KHBsb3QuZGF0YSwgZnVuY3Rpb24oeCkgeCRhdXByZykKICBnZy50aXNzdWUgPC0gbGFwcGx5KHBsb3QuZGF0YSwgZnVuY3Rpb24oeCkgewogICAgeCA8LSBkYXRhLmZyYW1lKFRJU1NVRSA9IHgkdGlzc3VlLCBQUkVDSVNJT04gPSB4JGN1cnZlJHByZWNpc2lvbl9nYWluLCBSRUNBTEwgPSB4JGN1cnZlJHJlY2FsbF9nYWluKQogICAgcmV0dXJuKHgpCiAgfSkKICBnZy50aXNzdWUuc3AgPC0gZG8uY2FsbCgicmJpbmQiLCBnZy50aXNzdWUpCiAgZ2cudGlzc3VlLnNwJERNIDwtIG5hbWVzKGRtcylbZG1dCiAgc3RhdGVzW1tkbV1dIDwtIGdnLnRpc3N1ZS5zcAp9CmdnLnRpc3N1ZSA8LSBkby5jYWxsKCJyYmluZCIsIHN0YXRlcykKYGBgCldlIGNhbiB0aGVuIHBsb3QgdGhlc2UgZm91ciBzY29yaW5nIG1vZGVscyBhY3Jvc3MgdGhlIHRpc3N1ZSBzcGVjaWZpYyBlbmhhbmNlcnMgdGhhdCB3ZSBpbnRlcnJvZ2F0ZWQgZnJvbSBWSVNUQSB0byBzZWUgd2hpY2ggY29tYmluYXRpb24gb2Ygc2NvcmVzIHNlcnZlcyB0byBiZXN0IHByZWRpY3QgZW5oYW5jZXJzLgpgYGB7cn0Kc2ZpZ3VyZTMgPC0gZ2dwbG90KGdnLnRpc3N1ZSwgYWVzKHkgPSBQUkVDSVNJT04sIHggPSBSRUNBTEwsIGdyb3VwID0gRE0pKSArCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IERNKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gRE0pKSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW09YygwLDEpLCB5bGltID0gYygwLDEpKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICB0aGVtZV9ncmV5KCkgKyB0aGVtZShhc3BlY3QucmF0aW89MSkgKwogIGdndGl0bGUoIlN1cHBsZW1lbnRhcnkgRmlndXJlIDMgLSBTY29yaW5nIEZlYXR1cmVzIikgKwogIHlsYWIoIlByZWNpc2lvbiBHYWluIikgKyB4bGFiKCJSZWNhbGwgR2FpbiIpICsKICBmYWNldF93cmFwKCB+IFRJU1NVRSwgbmNvbCA9IDMpCnNmaWd1cmUzCmBgYApCYXNlZCB1cG9uIHRoaXMgcGxvdCB3ZSBkZWNpZGVkIHRvIGdvIGZvcndhcmQgd2l0aG91dCBpbmNsdWRpbmcgdGhlIFJlZ3VsYXRvcnkgbWFyayBpbiBvdXIgc2NvcmluZy4KCiMjIEV2YWx1YXRlIEZlYXR1cmVzIHRoYXQgQ29tcHJpc2UgRW5oYW5jZXJzIC0gIFN1cHBsZW1lbnRhcnkgRmlndXJlIDQKV2UgY29uc2lkZXJlZCB3aGljaCBjb21iaW5hdGlvbnMgb2Ygc2VnbWVudHMgd291bGQgcHJvdmlkZSB0aGUgYmVzdCBkZXNjcmlwdGlvbiBvZiBhbiBhY3RpdmUgZW5oYW5jZXIgYXMgZm91bmQgaW4gVklTVEEuIFdlIGdlbmVyYXRlZCBmaXZlIGdyb3VwcyBvZiBtYXJrcy4gT3VyIGFjdGl2ZSAqQWN0aXZlIEVuaGFuY2VyKiBncm91cCBpbmNvcnBvcmF0ZWQgYEVBUmAsIGBBUmAsIGBFQVJDYCwgYW5kIGBBUkNgLCBvdXIgKkFjdGl2ZSBQcm9tb3RlciogZ3JvdXAgY29uc2lzdHMgb2YgYFBBUmAgYW5kIGBQQVJDYCwgdGhlIGBTaWxlbmNlZGAgZ3JvdXAgaXMgYFNDUmAgYW5kIGBIRVRgLCAqUG9pc2VkIGFuZCBXZWFrIEVuaGFuY2VyKiBjb25zaXN0cyBvZiBgRVBSYCwgYEVQUkNgLCBgRVdSYCwgYEVXUkNgLCBhbmQgb3VyICpQb2lzZWQgYW5kIFdlYWsgUHJvbW90ZXIqIGdyb3VwIGlzIGBQUFJgLCBgUFBSQ2AsIGBQV1JgLCBgUFdSQ2AsIGBQUFdSYCwgYW5kIGBQUFdSQ2AuIFdlIGV2YWx1YXRlZCBlYWNoIG9mIHRoZXNlIGNvbWJpbmF0aW9ucyBhZ2FpbnN0IHRoZSAxMDAgdmlzdGEgZW5oYW5jZXJzIHRoYXQgd2VyZSBleGNsdWRlZCBhYm92ZSwgaW4gZWFjaCBjYXRlZ29yeSwgW3doZW4gY29tcGFyaW5nIG91ciBwcmVkaWN0aW9ucywgYW5kIHRob3NlIG9mIG90aGVyIGVuaGFuY2VyIHByZWRpY3Rpb24gYWxnb3JpdGhtcyB0byBWSVNUQV0oI3RyYWluX3N1YnNldCkKYGBge3J9CmRtIDwtIGdldC5kZWNpc2lvbi5tYXRyaXgoIjU4MTNiNjdmNDZlMGZiMDZiNDkzY2ViMCIpCmRtIDwtIGRvTm90U3BsaXQoZG0sICJDb3JlIikKZG0gPC0gZG9Ob3RTY29yZShkbSwgIlJlZ3VsYXRvcnkiKQpkbSA8LSBkb05vdFNjb3JlKGRtLCAiUHJvbW90ZXIiKQoKc2VnbWVudHMgPC0gbGlzdChBLkVuaGFuY2VyID0gYygiRUFSIiwgIkFSIiwgIkVBUkMiLCAiQVJDIiksCiAgICAgICAgICAgICAgICAgQS5Qcm9tb3RlciA9IGMoIlBBUiIsICJQQVJDIiksCiAgICAgICAgICAgICAgICAgU2lsZW5jZWQgPSBjKCJTQ1IiLCAiSEVUIiksCiAgICAgICAgICAgICAgICAgUFcuRW5oYW5jZXIgPSBjKCJFUFIiLCAiRVBSQyIsICJFV1IiLCAiRVdSQyIpLAogICAgICAgICAgICAgICAgIFBXLlByb21vdGVyID0gYygiUFBSIiwgIlBQUkMiLCAiUFdSIiwgIlBXUkMiLCAiUFBXUiIsICJQUFdSQyIpKQoKbW91c2UuZW1icnlvLnN0YXRlcyA8LSBQYWludFN0YXRlcyhtYW5pZmVzdCA9IG1hbmlmZXN0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWNpc2lvbk1hdHJpeCA9IGRtLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY29yZVN0YXRlcyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZ3Jlc3MgPSBGQUxTRSkKbmFtZXMobW91c2UuZW1icnlvLnN0YXRlcykgPC0gc3RyX3JlcGxhY2UobmFtZXMobW91c2UuZW1icnlvLnN0YXRlcyksICIgZW1icnlvIiwgIiIpCmZvciAoc2VnbWVudCBpbiBzZXFfYWxvbmcoc2VnbWVudHMpKSB7CiAgcGxvdC5kYXRhIDwtIGxpc3QoKQogIGZvciAodGlzc3VlIGluIG5hbWVzKG1vdXNlLmVtYnJ5by5zdGF0ZXMpKSB7CiAgICBwcmVkaWN0ZWQuc2NvcmVzIDwtIG1vdXNlLmVtYnJ5by5zdGF0ZXNbW3Rpc3N1ZV1dCiAgICB2aXN0YS5lbmhhbmNlcnMudGVzdCA8LSB2aXN0YS5lbmhhbmNlcnMKICAgIG1jb2xzKHZpc3RhLmVuaGFuY2Vycy50ZXN0KSA8LSBkYXRhLmZyYW1lKEZPVU5EPW1jb2xzKHZpc3RhLmVuaGFuY2Vycy50ZXN0KVssIHRpc3N1ZV0pCiAgICB2aXN0YS5lbmhhbmNlcnMudGVzdCA8LSB2aXN0YS5lbmhhbmNlcnMudGVzdFtjKHdoaWNoKG1jb2xzKHZpc3RhLmVuaGFuY2Vycy50ZXN0KVssICJGT1VORCJdICE9IDEpLCB0cmFpbi5lbmhhbmNlcnNbW3Rpc3N1ZV1dKSxdCiAgICBwcmVkaWN0ZWQuc2NvcmVzIDwtIHByZWRpY3RlZC5zY29yZXNbcHJlZGljdGVkLnNjb3JlcyRzdGF0ZSAlaW4lIHNlZ21lbnRzW1tzZWdtZW50XV0sIF0KICAgIHByZWRpY3RlZC5zY29yZXMgPC0gcHJlZGljdGVkLnNjb3Jlc1tvcmRlcihwcmVkaWN0ZWQuc2NvcmVzJHNjb3JlLCBkZWNyZWFzaW5nID0gVFJVRSksIF0KICAgIG9sYXBzIDwtIGZpbmRPdmVybGFwcyh2aXN0YS5lbmhhbmNlcnMudGVzdCwgcHJlZGljdGVkLnNjb3Jlcywgc2VsZWN0ID0gImZpcnN0IikKICAgIG1jb2xzKHZpc3RhLmVuaGFuY2Vycy50ZXN0KSRzY29yZSA8LSAwCiAgICBtY29scyh2aXN0YS5lbmhhbmNlcnMudGVzdClbd2hpY2goIWlzLm5hKG9sYXBzKSksICJzY29yZSJdIDwtIG1jb2xzKHByZWRpY3RlZC5zY29yZXNbb2xhcHNbIWlzLm5hKG9sYXBzKV1dKSRzY29yZQogICAgcHJnX2N1cnZlIDwtIFN0YXRlUGFpbnRSOjo6Y3JlYXRlX3ByZ19jdXJ2ZShtY29scyh2aXN0YS5lbmhhbmNlcnMudGVzdCkkRk9VTkQsIG1jb2xzKHZpc3RhLmVuaGFuY2Vycy50ZXN0KSRzY29yZSkKICAgIGF1cHJnID0gU3RhdGVQYWludFI6OjpjYWxjX2F1cHJnKHByZ19jdXJ2ZSkKICAgIGNvbnZleF9odWxsID0gU3RhdGVQYWludFI6OjpwcmdfY29udmV4X2h1bGwocHJnX2N1cnZlKQogICAgcGxvdC50aXNzdWUgPC0gbGlzdChsaXN0KHRpc3N1ZSA9IHRpc3N1ZSwgY3VydmU9cHJnX2N1cnZlLCBhdXByZyA9IGF1cHJnLCBodWxsID0gY29udmV4X2h1bGwpKQogICAgbmFtZXMocGxvdC50aXNzdWUpIDwtIHRpc3N1ZQogICAgcGxvdC5kYXRhIDwtIGMocGxvdC5kYXRhLCBwbG90LnRpc3N1ZSkKICB9CiAgc2FwcGx5KHBsb3QuZGF0YSwgZnVuY3Rpb24oeCkgeCRhdXByZykKICBnZy50aXNzdWUgPC0gbGFwcGx5KHBsb3QuZGF0YSwgZnVuY3Rpb24oeCkgewogICAgeCA8LSBkYXRhLmZyYW1lKFRJU1NVRSA9IHgkdGlzc3VlLCBQUkVDSVNJT04gPSB4JGN1cnZlJHByZWNpc2lvbl9nYWluLCBSRUNBTEwgPSB4JGN1cnZlJHJlY2FsbF9nYWluKQogICAgcmV0dXJuKHgpCiAgfSkKICBnZy50aXNzdWUgPC0gZG8uY2FsbCgicmJpbmQiLCBnZy50aXNzdWUpCiAgZ2cudGlzc3VlJFNFR01FTlRTIDwtIG5hbWVzKHNlZ21lbnRzKVtzZWdtZW50XQogIHNlZ21lbnRzW1tzZWdtZW50XV0gPC0gZ2cudGlzc3VlCn0KZ2cudGlzc3VlIDwtIGRvLmNhbGwoInJiaW5kIiwgc2VnbWVudHMpCmBgYApCeSBQbG90dGluZyB0aGUgUHJlY2lzaW9uIFJlY2FsbCBHYWluIGN1cnZlcywgd2Ugc2VlIHRoYXQsIGFzIGV4cGVjdGVkIG91ciAqQWN0aXZlIEVuaGFuY2VyKiBncm91cCBoYXMgdGhlIGJlc3QgYWNjdXJhY3kgYWNyb3NzIHRpc3N1ZSB0eXBlcy4KYGBge3J9CnNmaWd1cmUuNCA8LSBnZ3Bsb3QoZ2cudGlzc3VlLCBhZXMoeSA9IFBSRUNJU0lPTiwgeCA9IFJFQ0FMTCwgZ3JvdXAgPSBTRUdNRU5UUykpICsKICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gU0VHTUVOVFMpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBTRUdNRU5UUykpICsKICBjb29yZF9jYXJ0ZXNpYW4oeGxpbT1jKDAsMSksIHlsaW0gPSBjKDAsMSkpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJQYWlyZWQiKSArCiAgdGhlbWVfZ3JleSgpICsgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKwogIGdndGl0bGUoIlN1cHBsZW1lbnRhcnkgRmlndXJlIDQgLSBTY29yaW5nIFNlZ21lbnRzIikgKwogIHlsYWIoIlByZWNpc2lvbiBHYWluIikgKyB4bGFiKCJSZWNhbGwgR2FpbiIpICsKICBmYWNldF93cmFwKCB+IFRJU1NVRSwgbmNvbCA9IDMpCnNmaWd1cmUuNApgYGAK