1 Stepping through the original STAMP protocol

In this document I am going to rerun the stamp process as close to exactly as possible to the original. I will put in this the actual commands I ran and then copy/paste the R as provided by the authors with modifications required to make it work.

2 Raw Read Trimming

The trimming process is via cutadapt, the original authors used reaper, but that is just a wrapper for cutadapt. Here is an example of my trimming:

The last adapter is the most important for this. The others may likely be just removed.

mkdir -p outputs/cutadapt && \
  less r1.fastq.xz | cutadapt -  \
    -a ACAGGTTGGATGATAAGTCCCCGGTCTGACACATCTCCCTAT -a AGATCGGAAGAGCACACGTCTGAAC -b AGATCGGAAGAGCACACGTCTGAAC  -a GTCATAGCTGTTT \
    -e 0.1 -n 3 \
    -m 8 -M 42 \
    --too-short-output=outputs/cutadapt/r1_tooshort.fastq \
    --too-long-output=outputs/cutadapt/r1_toolong.fastq \
    --untrimmed-output=outputs/cutadapt/r1_untrimmed.fastq \
     2>outputs/cutadapt.err 1>r1-trimmed_ca.fastq
## r1.fastq.xz: No such file or directory

3 Qiime usage to count reads/index

This step baffles me a little. The paper states that qiime was used with a 99% identity filter. However, after filtering the remaining index reads are only 9<=x<=13 nucleotides. It seems to me a simpler and more precise method to just take the first 9 nt of each index read and count the reads which are identical. With that in mind, here is an implementation of their qiime workflow:

## qiime needs a fasta input, here is a quick bash function to do it from:
## https://www.ecseq.com/support/ngs-snippets/convert-fastq-to-fasta-on-the-command-line
fq2fa() {
    paste - - - - < ${1} | cut -f 1,2 | sed 's/^@/>/' | tr "\t" "\n" > ${2}
}

fq2fa r1-trimmed_ca.fastq a1.fasta
## Repeat that for each sample, I dumped all the fasta files into one directory
for i in *.fasta; do
    pick_otus.py -i ${i} -o qiime
done
## Then I made a directory 'txt' into which I moved all the qiime text files.
## Error in running command bash

4 Begin their R scripts

4.1 Tally the qiime results

This is from Qiime_Tally_Data_STAMP_CAUTI.R. I am making changes to it to make it a bit less offensive to my eyes.

This is such an abomination. Also, I don’t think it can work on any R which is newer than ~ 5 years old because of the way it is calling read.table(). Its intention is to read in the .txt files produced by pick_otus.py and output a matrix with two columns per sample, the first of which contains the sequence of the index followed by the number of OTUs detected by qiime.

4.1.1 Original

## This R script will take the output files containing representative STAMP barcode sequences and
## their individual abundance in the sequencer output from all samples  that
## you sequenced and copies them in a single .csv file in the format that is
## expected from the Nb estimation script

txt_dir <- "txt"
output_file <- "qiime_tally.csv"

files <- list.files(txt_dir)
names <- files

v.length <- rep(NA, length(names))

for(f in 1:length(files)) {
  file <- file.path(txt_dir, files[f])
  mtrx <- read.table(file, sep="\t", header=FALSE, fill=TRUE)
  colnames(mtrx) <- c(paste0(files[f], "_seq"), files[f])

  ## eval(parse(text=paste("Data=as.matrix(read.table('",file,"', fill=TRUE, sep='\t'))",sep="")))
  ## colnames(Data)=c(paste(names[f],"_seq",sep=""),names[f])
  ## name=paste("Matrix",names[f],sep="")
  ## assign(name,Data)
  ## name=paste("Matrix",names[f],sep="")
  ## assign(name,Data)
  ## v.length[f]=nrow(Data)
}
max <- max(v.length) + 1
fill.up <- max - v.length

FillUpMatrix <- matrix(data=NA, nrow=fill.up[1], ncol=2)
## Error in matrix(data = NA, nrow = fill.up[1], ncol = 2): invalid 'nrow' value (too large or NA)
eval(parse(text=paste("OutputMatrix=rbind(Matrix",names[1],",FillUpMatrix)",sep="")))
## Error in eval(quote(list(...)), env): object 'Matrixa1_otus.txt' not found
for(ff in 2:length(files)) {
  eval(parse(text=paste("MatrixX=Matrix", names[ff], sep="")))
  FillUpMatrix <- matrix(data=NA, nrow=fill.up[ff], ncol=2)
  Matrix <- rbind(MatrixX, FillUpMatrix)
  OutputMarix <- cbind(OutputMatrix,Matrix)
}
## Error in eval(parse(text = paste("MatrixX=Matrix", names[ff], sep = ""))): object 'Matrixa2_otus.txt' not found
write.csv(OutputMatrix, file=outputfilename)
## Error in is.data.frame(x): object 'OutputMatrix' not found

4.1.2 Reimplemented

One caveat, I am using a more recent qiime than the authors, which provides the output of pick_otus.py in a somewhat different format (either that or they used a different mode, though I tested out 3 of them and did not get the input format they expected from any of them). Thus my names are not going to be the index sequences, but instead just ‘denovo0’ through ‘denovoNNN’.

txt_dir <- "txt"
output_file <- "qiime_tally.csv"
files <- list.files(txt_dir)
## These two lines recreate the column names used by the authors.
count_names <- gsub(x=files, pattern="_otus", replacement="")
seq_names <- paste0(count_names, "_seq")
final_df <- data.frame()
for (f in 1:length(files)) {
  filename <- files[f]
  message("Starting to read: ", filename, ".")
  count_name <- gsub(x=filename, pattern="_otus", replacement="")
  sequence_name <- paste0(count_name, "_seq")
  column_names <- c(sequence_name, count_name)
  input <- file.path(txt_dir, filename)
  lines <- readLines(input)
  pair <- as.data.frame(matrix(nrow=length(lines), ncol=2))
  colnames(pair) <- column_names
  for (l in 1:length(lines)) {
    line <- lines[l]
    split <- strsplit(x=line, split="\t")[[1]]
    pair[l, 1] <- split[1]
    pair[l, 2] <- length(split) - 1
  }
  rownames(pair) <- pair[[1]]
  if (ncol(final_df) == 0) {
    final_df <- pair
  } else {
    final_df <- merge(final_df, pair, by="row.names", all=TRUE)
    rownames(final_df) <- final_df[["Row.names"]]
    final_df[["Row.names"]] <- NULL
  }
}
## Starting to read: a1_otus.txt.
## Starting to read: a2_otus.txt.
## Starting to read: a3_otus.txt.
## Starting to read: a4_otus.txt.
## Starting to read: a5_otus.txt.
## Starting to read: a6_otus.txt.
## Starting to read: a7_otus.txt.
## Starting to read: a8_otus.txt.
## Starting to read: b2_otus.txt.
## Starting to read: b3_otus.txt.
## Starting to read: b4_otus.txt.
## Starting to read: b5_otus.txt.
## Starting to read: b6_otus.txt.
## Starting to read: b7_otus.txt.
## Starting to read: b8_otus.txt.
## Starting to read: c1_otus.txt.
## Starting to read: c2_otus.txt.
## Starting to read: c3_otus.txt.
## Starting to read: c4_otus.txt.
## Starting to read: c5_otus.txt.
## Starting to read: c6_otus.txt.
## Starting to read: c7_otus.txt.
## Starting to read: c8_otus.txt.

The above block produces a matrix in the same format as the qiime_tally_data… script. The following block will provide a similar matrix using a different method: I chose to take the first 9 nucleotides of each barcode and write out the number of times each sequence occurs with a simple perl script. Here is an example loop which iterates over every sample directory. Note that my trimmer produces xz compressed output files, I use a less preprocessor to read all of my inputs.

cd preprocessing
for d in $(find . -type d); do
    cd ${d}
    ../count_idx.pl r1-trimmed_ca.fastq.xz
done
## Error in running command bash

The above script produces text files named ‘idx_count.txt’ which I compressed. The next block reads these and creates a matrix similar to what is above with a couple of interesting differences. My intention is to dump these blocks into a small R package to make processing this data easier. Thus, it is using a sample sheet in a format similar to what we use for other data types in our lab, and processing it in a similar fashion.

setwd("/mnt/sshfs/cbcbsub01/fs/cbcb-lab/nelsayed/scratch/atb/dnaseq/paeruginosa_stamp_2020")
meta <- extract_metadata(metadata="sample_sheets/all_samples.xlsx")

sample_df <- data.frame()
all_counts <- data.table::data.table()
cutoff <- 3
meta[["observed"]] <- 0
meta[["passed"]] <- 0
meta[["rejected"]] <- 0
meta[["max"]] <- 0
for (s in rownames(meta)) {
  table <- meta[s, "indextable"]
  sampleid <- meta[s, "sampleid"]
  element <- read.csv(file=table, sep="\t", header=FALSE)
  colnames(element) <- c("idx", "number")
  ## This tmp allocation is a little dumb, but for the moment
  ## I will use it to create a full matrix of barcodes.
  tmp <- data.table::as.data.table(element)
  colnames(tmp) <- c("idx", sampleid)
  if (ncol(all_counts) == 0) {
    all_counts <- tmp
  } else {
    all_counts <- merge(all_counts, tmp, by="idx", all=TRUE)
  }
  element[["dilution"]] <- meta[s, "dilution"]
  element[["sample"]] <- meta[s, "condition"]
  element[["sampleid"]] <- sampleid
  element[["idx"]] <- NULL
  meta[s, "observed"] <- nrow(element)
  meta[s, "reads"] <- sum(element[["number"]])
  meta[s, "max"] <- max(element[["number"]])
  keep_idx <- element[["number"]] >= cutoff
  meta[s, "passed"] <- sum(keep_idx)
  meta[s, "rejected"] <- sum(!keep_idx)
  sample_df <- rbind(sample_df, element)
}

write.csv(file="my_tag_counts.csv", x=all_counts)

all_mtrx <- as.data.frame(all_counts)
rownames(all_mtrx) <- all_mtrx[["idx"]]
all_mtrx[["idx"]] <- NULL
na_idx <- is.na(all_mtrx)
all_mtrx[na_idx] <- 0
all_mtrx <- edgeR::cpm(all_mtrx)
## Here is an aggressive possibility for filtering
null_rows <- rowSums(all_mtrx) < ncol(all_mtrx)
aggressive <- all_mtrx[!null_rows, ]
## Here is an arbitrary possibility for filtering
null_rows <- rowSums(all_mtrx) < 1
arbitrary <- all_mtrx[!null_rows, ]

meta[["aggressive"]] <- 0
meta[["arbitrary"]] <- 0
for (col in colnames(all_mtrx)) {
  agg_idx <- aggressive[, col] > 0
  meta[col, "aggressive"] <- sum(agg_idx)
  arb_idx <- arbitrary[, col] > 0
  meta[col, "arbitrary"] <- sum(arb_idx)
}


library(ggplot2)
ggplot(data=sample_df, mapping=aes_string(fill="sampleid")) +
  geom_violin(mapping=aes_string(x="sampleid", y="number")) +
  scale_y_log10()

observed <- ggplot(data=meta,
                   mapping=aes_string(x="dilution", y="observed",
                                      fill="condition", colour="condition")) +
  scale_shape_manual(values=21) +
  geom_point(size=3) +
  scale_x_log10()
observed

passed <- ggplot(data=meta,
                 mapping=aes_string(x="dilution", y="passed",
                                    fill="condition", colour="condition")) +
  scale_shape_manual(values=21) +
  geom_point(size=3) +
  scale_x_log10()
passed

arb <- ggplot(data=meta,
                 mapping=aes_string(x="dilution", y="arbitrary",
                                    fill="condition", colour="condition")) +
  scale_shape_manual(values=21) +
  geom_point(size=3) +
  scale_x_log10() +
  geom_smooth(method="lm")
arb
## `geom_smooth()` using formula 'y ~ x'

5 Describe this relationship

In the following block, I am feeding the tag and dilution data by sample to glm and looking at the result.

meta[["log"]] <- log10(meta[["dilution"]])
lm_result <- glm(formula=log ~ arbitrary, data=meta)
lm_result
## 
## Call:  glm(formula = log ~ arbitrary, data = meta)
## 
## Coefficients:
## (Intercept)    arbitrary  
##    0.731329     0.000832  
## 
## Degrees of Freedom: 22 Total (i.e. Null);  21 Residual
## Null Deviance:       113 
## Residual Deviance: 33.1  AIC: 79.6
summary(lm_result)
## 
## Call:
## glm(formula = log ~ arbitrary, data = meta)
## 
## Deviance Residuals: 
##    Min      1Q  Median      3Q     Max  
## -1.880  -0.535  -0.128   0.290   4.391  
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 0.731329   0.608720    1.20     0.24    
## arbitrary   0.000832   0.000117    7.13  4.9e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for gaussian family taken to be 1.575)
## 
##     Null deviance: 113.217  on 22  degrees of freedom
## Residual deviance:  33.071  on 21  degrees of freedom
## AIC: 79.62
## 
## Number of Fisher Scoring iterations: 2
coef(lm_result)
## (Intercept)   arbitrary 
##   0.7313287   0.0008322

6 Predict new bacterial loads from made-up tags

Now, let us pretend that we performed the experiment on two actual samples, and in them we got 1500 and 3000 tags respectively. How many bacteria do we think there were in each sample?

test_data <- data.frame(row.names=c("t1", "t2"))
test_data[["arbitrary"]] <- c(1500, 3000)
num_found <- predict.glm(object=lm_result, newdata=test_data, type="response")

10 ^ num_found
##      t1      t2 
##   95.43 1690.67
pander::pander(sessionInfo())
message(paste0("This is hpgltools commit: ", get_git_commit()))
this_save <- paste0(gsub(pattern="\\.Rmd", replace="", x=rmd_file), "-v", ver, ".rda.xz")
message(paste0("Saving to ", this_save))
tmp <- sm(saveme(filename=this_save))
LS0tCnRpdGxlOiAiU3RlcHBpbmcgdGhyb3VnaCB0aGUgb3JpZ2luYWwgc3RhbXAgcHJvdG9jb2wuIgphdXRob3I6ICJhdGIgYWJlbGV3QGdtYWlsLmNvbSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgZmlnX2NhcHRpb246IHRydWUKICAgIGZpZ19oZWlnaHQ6IDcKICAgIGZpZ193aWR0aDogNwogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAga2VlcF9tZDogZmFsc2UKICAgIG1vZGU6IHNlbGZjb250YWluZWQKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgc2VsZl9jb250YWluZWQ6IHRydWUKICAgIHRoZW1lOiByZWFkYWJsZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgICAgc21vb3RoX3Njcm9sbDogZmFsc2UKICBybWRmb3JtYXRzOjpyZWFkdGhlZG93bjoKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBmaWdfY2FwdGlvbjogdHJ1ZQogICAgZmlnX2hlaWdodDogNwogICAgZmlnX3dpZHRoOiA3CiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICB3aWR0aDogMzAwCiAgICBrZWVwX21kOiBmYWxzZQogICAgbW9kZTogc2VsZmNvbnRhaW5lZAogICAgdG9jX2Zsb2F0OiB0cnVlCiAgQmlvY1N0eWxlOjpodG1sX2RvY3VtZW50OgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBmaWdfY2FwdGlvbjogdHJ1ZQogICAgZmlnX2hlaWdodDogNwogICAgZmlnX3dpZHRoOiA3CiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICBrZWVwX21kOiBmYWxzZQogICAgbW9kZTogc2VsZmNvbnRhaW5lZAogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KYm9keSwgdGQgewogIGZvbnQtc2l6ZTogMTZweDsKfQpjb2RlLnJ7CiAgZm9udC1zaXplOiAxNnB4Owp9CnByZSB7CiBmb250LXNpemU6IDE2cHgKfQo8L3N0eWxlPgoKYGBge3Igb3B0aW9ucywgaW5jbHVkZT1GQUxTRX0KbGlicmFyeSgiaHBnbHRvb2xzIikKdHQgPC0gZGV2dG9vbHM6OmxvYWRfYWxsKCIvZGF0YS9ocGdsdG9vbHMiKQprbml0cjo6b3B0c19rbml0JHNldCh3aWR0aD0xMjAsCiAgICAgICAgICAgICAgICAgICAgIHByb2dyZXNzPVRSVUUsCiAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2U9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgZWNobz1UUlVFKQprbml0cjo6b3B0c19jaHVuayRzZXQoZXJyb3I9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgIGRwaT05NikKb2xkX29wdGlvbnMgPC0gb3B0aW9ucyhkaWdpdHM9NCwKICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgIGtuaXRyLmR1cGxpY2F0ZS5sYWJlbD0iYWxsb3ciKQpnZ3Bsb3QyOjp0aGVtZV9zZXQoZ2dwbG90Mjo6dGhlbWVfYncoYmFzZV9zaXplPTEwKSkKcnVuZGF0ZSA8LSBmb3JtYXQoU3lzLkRhdGUoKSwgZm9ybWF0PSIlWSVtJWQiKQpwcmV2aW91c19maWxlIDwtICIiCnZlciA8LSAiMjAyMDEyIgoKIyN0bXAgPC0gc20obG9hZG1lKGZpbGVuYW1lPXBhc3RlMChnc3ViKHBhdHRlcm49IlxcLlJtZCIsIHJlcGxhY2U9IiIsIHg9cHJldmlvdXNfZmlsZSksICItdiIsIHZlciwgIi5yZGEueHoiKSkpCnJtZF9maWxlIDwtICJjb21iaW5lZC5SbWQiCmBgYAoKIyBTdGVwcGluZyB0aHJvdWdoIHRoZSBvcmlnaW5hbCBTVEFNUCBwcm90b2NvbAoKSW4gdGhpcyBkb2N1bWVudCBJIGFtIGdvaW5nIHRvIHJlcnVuIHRoZSBzdGFtcCBwcm9jZXNzIGFzIGNsb3NlIHRvIGV4YWN0bHkgYXMKcG9zc2libGUgdG8gdGhlIG9yaWdpbmFsLiAgSSB3aWxsIHB1dCBpbiB0aGlzIHRoZSBhY3R1YWwgY29tbWFuZHMgSSByYW4gYW5kIHRoZW4KY29weS9wYXN0ZSB0aGUgUiBhcyBwcm92aWRlZCBieSB0aGUgYXV0aG9ycyB3aXRoIG1vZGlmaWNhdGlvbnMgcmVxdWlyZWQgdG8gbWFrZQppdCB3b3JrLgoKIyBSYXcgUmVhZCBUcmltbWluZwoKVGhlIHRyaW1taW5nIHByb2Nlc3MgaXMgdmlhIGN1dGFkYXB0LCB0aGUgb3JpZ2luYWwgYXV0aG9ycyB1c2VkIHJlYXBlciwgYnV0IHRoYXQKaXMganVzdCBhIHdyYXBwZXIgZm9yIGN1dGFkYXB0LiAgSGVyZSBpcyBhbiBleGFtcGxlIG9mIG15IHRyaW1taW5nOgoKVGhlIGxhc3QgYWRhcHRlciBpcyB0aGUgbW9zdCBpbXBvcnRhbnQgZm9yIHRoaXMuICBUaGUgb3RoZXJzIG1heSBsaWtlbHkgYmUganVzdCByZW1vdmVkLgoKYGBge2Jhc2ggY3V0YWRhcHR9Cm1rZGlyIC1wIG91dHB1dHMvY3V0YWRhcHQgJiYgXAogIGxlc3MgcjEuZmFzdHEueHogfCBjdXRhZGFwdCAtICBcCiAgICAtYSBBQ0FHR1RUR0dBVEdBVEFBR1RDQ0NDR0dUQ1RHQUNBQ0FUQ1RDQ0NUQVQgLWEgQUdBVENHR0FBR0FHQ0FDQUNHVENUR0FBQyAtYiBBR0FUQ0dHQUFHQUdDQUNBQ0dUQ1RHQUFDICAtYSBHVENBVEFHQ1RHVFRUIFwKICAgIC1lIDAuMSAtbiAzIFwKICAgIC1tIDggLU0gNDIgXAogICAgLS10b28tc2hvcnQtb3V0cHV0PW91dHB1dHMvY3V0YWRhcHQvcjFfdG9vc2hvcnQuZmFzdHEgXAogICAgLS10b28tbG9uZy1vdXRwdXQ9b3V0cHV0cy9jdXRhZGFwdC9yMV90b29sb25nLmZhc3RxIFwKICAgIC0tdW50cmltbWVkLW91dHB1dD1vdXRwdXRzL2N1dGFkYXB0L3IxX3VudHJpbW1lZC5mYXN0cSBcCiAgICAgMj5vdXRwdXRzL2N1dGFkYXB0LmVyciAxPnIxLXRyaW1tZWRfY2EuZmFzdHEKYGBgCgojIFFpaW1lIHVzYWdlIHRvIGNvdW50IHJlYWRzL2luZGV4CgpUaGlzIHN0ZXAgYmFmZmxlcyBtZSBhIGxpdHRsZS4gIFRoZSBwYXBlciBzdGF0ZXMgdGhhdCBxaWltZSB3YXMgdXNlZCB3aXRoIGEgOTklCmlkZW50aXR5IGZpbHRlci4gIEhvd2V2ZXIsIGFmdGVyIGZpbHRlcmluZyB0aGUgcmVtYWluaW5nIGluZGV4IHJlYWRzIGFyZSBvbmx5Cjk8PXg8PTEzIG51Y2xlb3RpZGVzLiAgSXQgc2VlbXMgdG8gbWUgYSBzaW1wbGVyIGFuZCBtb3JlIHByZWNpc2UgbWV0aG9kIHRvIGp1c3QKdGFrZSB0aGUgZmlyc3QgOSBudCBvZiBlYWNoIGluZGV4IHJlYWQgYW5kIGNvdW50IHRoZSByZWFkcyB3aGljaCBhcmUgaWRlbnRpY2FsLgpXaXRoIHRoYXQgaW4gbWluZCwgaGVyZSBpcyBhbiBpbXBsZW1lbnRhdGlvbiBvZiB0aGVpciBxaWltZSB3b3JrZmxvdzoKCmBgYHtiYXNoIHFpaW1lfQojIyBxaWltZSBuZWVkcyBhIGZhc3RhIGlucHV0LCBoZXJlIGlzIGEgcXVpY2sgYmFzaCBmdW5jdGlvbiB0byBkbyBpdCBmcm9tOgojIyBodHRwczovL3d3dy5lY3NlcS5jb20vc3VwcG9ydC9uZ3Mtc25pcHBldHMvY29udmVydC1mYXN0cS10by1mYXN0YS1vbi10aGUtY29tbWFuZC1saW5lCmZxMmZhKCkgewogICAgcGFzdGUgLSAtIC0gLSA8ICR7MX0gfCBjdXQgLWYgMSwyIHwgc2VkICdzL15ALz4vJyB8IHRyICJcdCIgIlxuIiA+ICR7Mn0KfQoKZnEyZmEgcjEtdHJpbW1lZF9jYS5mYXN0cSBhMS5mYXN0YQojIyBSZXBlYXQgdGhhdCBmb3IgZWFjaCBzYW1wbGUsIEkgZHVtcGVkIGFsbCB0aGUgZmFzdGEgZmlsZXMgaW50byBvbmUgZGlyZWN0b3J5CmZvciBpIGluICouZmFzdGE7IGRvCiAgICBwaWNrX290dXMucHkgLWkgJHtpfSAtbyBxaWltZQpkb25lCiMjIFRoZW4gSSBtYWRlIGEgZGlyZWN0b3J5ICd0eHQnIGludG8gd2hpY2ggSSBtb3ZlZCBhbGwgdGhlIHFpaW1lIHRleHQgZmlsZXMuCmBgYAoKIyBCZWdpbiB0aGVpciBSIHNjcmlwdHMKCiMjIFRhbGx5IHRoZSBxaWltZSByZXN1bHRzCgpUaGlzIGlzIGZyb20gUWlpbWVfVGFsbHlfRGF0YV9TVEFNUF9DQVVUSS5SLiAgSSBhbSBtYWtpbmcgY2hhbmdlcyB0byBpdCB0byBtYWtlCml0IGEgYml0IGxlc3Mgb2ZmZW5zaXZlIHRvIG15IGV5ZXMuCgpUaGlzIGlzIHN1Y2ggYW4gYWJvbWluYXRpb24uICBBbHNvLCBJIGRvbid0IHRoaW5rIGl0IGNhbiB3b3JrIG9uIGFueSBSIHdoaWNoIGlzCm5ld2VyIHRoYW4gfiA1IHllYXJzIG9sZCBiZWNhdXNlIG9mIHRoZSB3YXkgaXQgaXMgY2FsbGluZyByZWFkLnRhYmxlKCkuICBJdHMKaW50ZW50aW9uIGlzIHRvIHJlYWQgaW4gdGhlIC50eHQgZmlsZXMgcHJvZHVjZWQgYnkgcGlja19vdHVzLnB5IGFuZCBvdXRwdXQgYQptYXRyaXggd2l0aCB0d28gY29sdW1ucyBwZXIgc2FtcGxlLCB0aGUgZmlyc3Qgb2Ygd2hpY2ggY29udGFpbnMgdGhlIHNlcXVlbmNlIG9mCnRoZSBpbmRleCBmb2xsb3dlZCBieSB0aGUgbnVtYmVyIG9mIE9UVXMgZGV0ZWN0ZWQgYnkgcWlpbWUuCgojIyMgT3JpZ2luYWwKCmBgYHtyIHRhbGx5fQojIyBUaGlzIFIgc2NyaXB0IHdpbGwgdGFrZSB0aGUgb3V0cHV0IGZpbGVzIGNvbnRhaW5pbmcgcmVwcmVzZW50YXRpdmUgU1RBTVAgYmFyY29kZSBzZXF1ZW5jZXMgYW5kCiMjIHRoZWlyIGluZGl2aWR1YWwgYWJ1bmRhbmNlIGluIHRoZSBzZXF1ZW5jZXIgb3V0cHV0IGZyb20gYWxsIHNhbXBsZXMgIHRoYXQKIyMgeW91IHNlcXVlbmNlZCBhbmQgY29waWVzIHRoZW0gaW4gYSBzaW5nbGUgLmNzdiBmaWxlIGluIHRoZSBmb3JtYXQgdGhhdCBpcwojIyBleHBlY3RlZCBmcm9tIHRoZSBOYiBlc3RpbWF0aW9uIHNjcmlwdAoKdHh0X2RpciA8LSAidHh0IgpvdXRwdXRfZmlsZSA8LSAicWlpbWVfdGFsbHkuY3N2IgoKZmlsZXMgPC0gbGlzdC5maWxlcyh0eHRfZGlyKQpuYW1lcyA8LSBmaWxlcwoKdi5sZW5ndGggPC0gcmVwKE5BLCBsZW5ndGgobmFtZXMpKQoKZm9yKGYgaW4gMTpsZW5ndGgoZmlsZXMpKSB7CiAgZmlsZSA8LSBmaWxlLnBhdGgodHh0X2RpciwgZmlsZXNbZl0pCiAgbXRyeCA8LSByZWFkLnRhYmxlKGZpbGUsIHNlcD0iXHQiLCBoZWFkZXI9RkFMU0UsIGZpbGw9VFJVRSkKICBjb2xuYW1lcyhtdHJ4KSA8LSBjKHBhc3RlMChmaWxlc1tmXSwgIl9zZXEiKSwgZmlsZXNbZl0pCgogICMjIGV2YWwocGFyc2UodGV4dD1wYXN0ZSgiRGF0YT1hcy5tYXRyaXgocmVhZC50YWJsZSgnIixmaWxlLCInLCBmaWxsPVRSVUUsIHNlcD0nXHQnKSkiLHNlcD0iIikpKQogICMjIGNvbG5hbWVzKERhdGEpPWMocGFzdGUobmFtZXNbZl0sIl9zZXEiLHNlcD0iIiksbmFtZXNbZl0pCiAgIyMgbmFtZT1wYXN0ZSgiTWF0cml4IixuYW1lc1tmXSxzZXA9IiIpCiAgIyMgYXNzaWduKG5hbWUsRGF0YSkKICAjIyBuYW1lPXBhc3RlKCJNYXRyaXgiLG5hbWVzW2ZdLHNlcD0iIikKICAjIyBhc3NpZ24obmFtZSxEYXRhKQogICMjIHYubGVuZ3RoW2ZdPW5yb3coRGF0YSkKfQptYXggPC0gbWF4KHYubGVuZ3RoKSArIDEKZmlsbC51cCA8LSBtYXggLSB2Lmxlbmd0aAoKRmlsbFVwTWF0cml4IDwtIG1hdHJpeChkYXRhPU5BLCBucm93PWZpbGwudXBbMV0sIG5jb2w9MikKCmV2YWwocGFyc2UodGV4dD1wYXN0ZSgiT3V0cHV0TWF0cml4PXJiaW5kKE1hdHJpeCIsbmFtZXNbMV0sIixGaWxsVXBNYXRyaXgpIixzZXA9IiIpKSkKCmZvcihmZiBpbiAyOmxlbmd0aChmaWxlcykpIHsKICBldmFsKHBhcnNlKHRleHQ9cGFzdGUoIk1hdHJpeFg9TWF0cml4IiwgbmFtZXNbZmZdLCBzZXA9IiIpKSkKICBGaWxsVXBNYXRyaXggPC0gbWF0cml4KGRhdGE9TkEsIG5yb3c9ZmlsbC51cFtmZl0sIG5jb2w9MikKICBNYXRyaXggPC0gcmJpbmQoTWF0cml4WCwgRmlsbFVwTWF0cml4KQogIE91dHB1dE1hcml4IDwtIGNiaW5kKE91dHB1dE1hdHJpeCxNYXRyaXgpCn0KCndyaXRlLmNzdihPdXRwdXRNYXRyaXgsIGZpbGU9b3V0cHV0ZmlsZW5hbWUpCmBgYAoKIyMjIFJlaW1wbGVtZW50ZWQKCk9uZSBjYXZlYXQsIEkgYW0gdXNpbmcgYSBtb3JlIHJlY2VudCBxaWltZSB0aGFuIHRoZSBhdXRob3JzLCB3aGljaCBwcm92aWRlcyB0aGUKb3V0cHV0IG9mIHBpY2tfb3R1cy5weSBpbiBhIHNvbWV3aGF0IGRpZmZlcmVudCBmb3JtYXQgKGVpdGhlciB0aGF0IG9yIHRoZXkgdXNlZAphIGRpZmZlcmVudCBtb2RlLCB0aG91Z2ggSSB0ZXN0ZWQgb3V0IDMgb2YgdGhlbSBhbmQgZGlkIG5vdCBnZXQgdGhlIGlucHV0IGZvcm1hdAp0aGV5IGV4cGVjdGVkIGZyb20gYW55IG9mIHRoZW0pLiAgVGh1cyBteSBuYW1lcyBhcmUgbm90IGdvaW5nIHRvIGJlIHRoZSBpbmRleApzZXF1ZW5jZXMsIGJ1dCBpbnN0ZWFkIGp1c3QgJ2Rlbm92bzAnIHRocm91Z2ggJ2Rlbm92b05OTicuCgpgYGB7ciBzYW5lX3FpaW1lX3RhbGx5fQp0eHRfZGlyIDwtICJ0eHQiCm91dHB1dF9maWxlIDwtICJxaWltZV90YWxseS5jc3YiCmZpbGVzIDwtIGxpc3QuZmlsZXModHh0X2RpcikKIyMgVGhlc2UgdHdvIGxpbmVzIHJlY3JlYXRlIHRoZSBjb2x1bW4gbmFtZXMgdXNlZCBieSB0aGUgYXV0aG9ycy4KY291bnRfbmFtZXMgPC0gZ3N1Yih4PWZpbGVzLCBwYXR0ZXJuPSJfb3R1cyIsIHJlcGxhY2VtZW50PSIiKQpzZXFfbmFtZXMgPC0gcGFzdGUwKGNvdW50X25hbWVzLCAiX3NlcSIpCmZpbmFsX2RmIDwtIGRhdGEuZnJhbWUoKQpmb3IgKGYgaW4gMTpsZW5ndGgoZmlsZXMpKSB7CiAgZmlsZW5hbWUgPC0gZmlsZXNbZl0KICBtZXNzYWdlKCJTdGFydGluZyB0byByZWFkOiAiLCBmaWxlbmFtZSwgIi4iKQogIGNvdW50X25hbWUgPC0gZ3N1Yih4PWZpbGVuYW1lLCBwYXR0ZXJuPSJfb3R1cyIsIHJlcGxhY2VtZW50PSIiKQogIHNlcXVlbmNlX25hbWUgPC0gcGFzdGUwKGNvdW50X25hbWUsICJfc2VxIikKICBjb2x1bW5fbmFtZXMgPC0gYyhzZXF1ZW5jZV9uYW1lLCBjb3VudF9uYW1lKQogIGlucHV0IDwtIGZpbGUucGF0aCh0eHRfZGlyLCBmaWxlbmFtZSkKICBsaW5lcyA8LSByZWFkTGluZXMoaW5wdXQpCiAgcGFpciA8LSBhcy5kYXRhLmZyYW1lKG1hdHJpeChucm93PWxlbmd0aChsaW5lcyksIG5jb2w9MikpCiAgY29sbmFtZXMocGFpcikgPC0gY29sdW1uX25hbWVzCiAgZm9yIChsIGluIDE6bGVuZ3RoKGxpbmVzKSkgewogICAgbGluZSA8LSBsaW5lc1tsXQogICAgc3BsaXQgPC0gc3Ryc3BsaXQoeD1saW5lLCBzcGxpdD0iXHQiKVtbMV1dCiAgICBwYWlyW2wsIDFdIDwtIHNwbGl0WzFdCiAgICBwYWlyW2wsIDJdIDwtIGxlbmd0aChzcGxpdCkgLSAxCiAgfQogIHJvd25hbWVzKHBhaXIpIDwtIHBhaXJbWzFdXQogIGlmIChuY29sKGZpbmFsX2RmKSA9PSAwKSB7CiAgICBmaW5hbF9kZiA8LSBwYWlyCiAgfSBlbHNlIHsKICAgIGZpbmFsX2RmIDwtIG1lcmdlKGZpbmFsX2RmLCBwYWlyLCBieT0icm93Lm5hbWVzIiwgYWxsPVRSVUUpCiAgICByb3duYW1lcyhmaW5hbF9kZikgPC0gZmluYWxfZGZbWyJSb3cubmFtZXMiXV0KICAgIGZpbmFsX2RmW1siUm93Lm5hbWVzIl1dIDwtIE5VTEwKICB9Cn0KYGBgCgpUaGUgYWJvdmUgYmxvY2sgcHJvZHVjZXMgYSBtYXRyaXggaW4gdGhlIHNhbWUgZm9ybWF0IGFzIHRoZQpxaWltZV90YWxseV9kYXRhLi4uIHNjcmlwdC4gIFRoZSBmb2xsb3dpbmcgYmxvY2sgd2lsbCBwcm92aWRlIGEgc2ltaWxhciBtYXRyaXgKdXNpbmcgYSBkaWZmZXJlbnQgbWV0aG9kOiBJIGNob3NlIHRvIHRha2UgdGhlIGZpcnN0IDkgbnVjbGVvdGlkZXMgb2YgZWFjaApiYXJjb2RlIGFuZCB3cml0ZSBvdXQgdGhlIG51bWJlciBvZiB0aW1lcyBlYWNoIHNlcXVlbmNlIG9jY3VycyB3aXRoIGEgc2ltcGxlCnBlcmwgc2NyaXB0LiAgSGVyZSBpcyBhbiBleGFtcGxlIGxvb3Agd2hpY2ggaXRlcmF0ZXMgb3ZlciBldmVyeSBzYW1wbGUKZGlyZWN0b3J5LiAgTm90ZSB0aGF0IG15IHRyaW1tZXIgcHJvZHVjZXMgeHogY29tcHJlc3NlZCBvdXRwdXQgZmlsZXMsIEkgdXNlIGEKbGVzcyBwcmVwcm9jZXNzb3IgdG8gcmVhZCBhbGwgb2YgbXkgaW5wdXRzLgoKYGBge2Jhc2ggbXlfdGFsbHl9CmNkIHByZXByb2Nlc3NpbmcKZm9yIGQgaW4gJChmaW5kIC4gLXR5cGUgZCk7IGRvCiAgICBjZCAke2R9CiAgICAuLi9jb3VudF9pZHgucGwgcjEtdHJpbW1lZF9jYS5mYXN0cS54egpkb25lCmBgYAoKVGhlIGFib3ZlIHNjcmlwdCBwcm9kdWNlcyB0ZXh0IGZpbGVzIG5hbWVkICdpZHhfY291bnQudHh0JyB3aGljaCBJIGNvbXByZXNzZWQuClRoZSBuZXh0IGJsb2NrIHJlYWRzIHRoZXNlIGFuZCBjcmVhdGVzIGEgbWF0cml4IHNpbWlsYXIgdG8gd2hhdCBpcyBhYm92ZSB3aXRoIGEKY291cGxlIG9mIGludGVyZXN0aW5nIGRpZmZlcmVuY2VzLiAgTXkgaW50ZW50aW9uIGlzIHRvIGR1bXAgdGhlc2UgYmxvY2tzIGludG8gYQpzbWFsbCBSIHBhY2thZ2UgdG8gbWFrZSBwcm9jZXNzaW5nIHRoaXMgZGF0YSBlYXNpZXIuICBUaHVzLCBpdCBpcyB1c2luZyBhIHNhbXBsZQpzaGVldCBpbiBhIGZvcm1hdCBzaW1pbGFyIHRvIHdoYXQgd2UgdXNlIGZvciBvdGhlciBkYXRhIHR5cGVzIGluIG91ciBsYWIsIGFuZApwcm9jZXNzaW5nIGl0IGluIGEgc2ltaWxhciBmYXNoaW9uLgoKYGBge3IgbXlfbWF0cml4fQpzZXR3ZCgiL21udC9zc2hmcy9jYmNic3ViMDEvZnMvY2JjYi1sYWIvbmVsc2F5ZWQvc2NyYXRjaC9hdGIvZG5hc2VxL3BhZXJ1Z2lub3NhX3N0YW1wXzIwMjAiKQptZXRhIDwtIGV4dHJhY3RfbWV0YWRhdGEobWV0YWRhdGE9InNhbXBsZV9zaGVldHMvYWxsX3NhbXBsZXMueGxzeCIpCgpzYW1wbGVfZGYgPC0gZGF0YS5mcmFtZSgpCmFsbF9jb3VudHMgPC0gZGF0YS50YWJsZTo6ZGF0YS50YWJsZSgpCmN1dG9mZiA8LSAzCm1ldGFbWyJvYnNlcnZlZCJdXSA8LSAwCm1ldGFbWyJwYXNzZWQiXV0gPC0gMAptZXRhW1sicmVqZWN0ZWQiXV0gPC0gMAptZXRhW1sibWF4Il1dIDwtIDAKZm9yIChzIGluIHJvd25hbWVzKG1ldGEpKSB7CiAgdGFibGUgPC0gbWV0YVtzLCAiaW5kZXh0YWJsZSJdCiAgc2FtcGxlaWQgPC0gbWV0YVtzLCAic2FtcGxlaWQiXQogIGVsZW1lbnQgPC0gcmVhZC5jc3YoZmlsZT10YWJsZSwgc2VwPSJcdCIsIGhlYWRlcj1GQUxTRSkKICBjb2xuYW1lcyhlbGVtZW50KSA8LSBjKCJpZHgiLCAibnVtYmVyIikKICAjIyBUaGlzIHRtcCBhbGxvY2F0aW9uIGlzIGEgbGl0dGxlIGR1bWIsIGJ1dCBmb3IgdGhlIG1vbWVudAogICMjIEkgd2lsbCB1c2UgaXQgdG8gY3JlYXRlIGEgZnVsbCBtYXRyaXggb2YgYmFyY29kZXMuCiAgdG1wIDwtIGRhdGEudGFibGU6OmFzLmRhdGEudGFibGUoZWxlbWVudCkKICBjb2xuYW1lcyh0bXApIDwtIGMoImlkeCIsIHNhbXBsZWlkKQogIGlmIChuY29sKGFsbF9jb3VudHMpID09IDApIHsKICAgIGFsbF9jb3VudHMgPC0gdG1wCiAgfSBlbHNlIHsKICAgIGFsbF9jb3VudHMgPC0gbWVyZ2UoYWxsX2NvdW50cywgdG1wLCBieT0iaWR4IiwgYWxsPVRSVUUpCiAgfQogIGVsZW1lbnRbWyJkaWx1dGlvbiJdXSA8LSBtZXRhW3MsICJkaWx1dGlvbiJdCiAgZWxlbWVudFtbInNhbXBsZSJdXSA8LSBtZXRhW3MsICJjb25kaXRpb24iXQogIGVsZW1lbnRbWyJzYW1wbGVpZCJdXSA8LSBzYW1wbGVpZAogIGVsZW1lbnRbWyJpZHgiXV0gPC0gTlVMTAogIG1ldGFbcywgIm9ic2VydmVkIl0gPC0gbnJvdyhlbGVtZW50KQogIG1ldGFbcywgInJlYWRzIl0gPC0gc3VtKGVsZW1lbnRbWyJudW1iZXIiXV0pCiAgbWV0YVtzLCAibWF4Il0gPC0gbWF4KGVsZW1lbnRbWyJudW1iZXIiXV0pCiAga2VlcF9pZHggPC0gZWxlbWVudFtbIm51bWJlciJdXSA+PSBjdXRvZmYKICBtZXRhW3MsICJwYXNzZWQiXSA8LSBzdW0oa2VlcF9pZHgpCiAgbWV0YVtzLCAicmVqZWN0ZWQiXSA8LSBzdW0oIWtlZXBfaWR4KQogIHNhbXBsZV9kZiA8LSByYmluZChzYW1wbGVfZGYsIGVsZW1lbnQpCn0KCndyaXRlLmNzdihmaWxlPSJteV90YWdfY291bnRzLmNzdiIsIHg9YWxsX2NvdW50cykKCmFsbF9tdHJ4IDwtIGFzLmRhdGEuZnJhbWUoYWxsX2NvdW50cykKcm93bmFtZXMoYWxsX210cngpIDwtIGFsbF9tdHJ4W1siaWR4Il1dCmFsbF9tdHJ4W1siaWR4Il1dIDwtIE5VTEwKbmFfaWR4IDwtIGlzLm5hKGFsbF9tdHJ4KQphbGxfbXRyeFtuYV9pZHhdIDwtIDAKYWxsX210cnggPC0gZWRnZVI6OmNwbShhbGxfbXRyeCkKIyMgSGVyZSBpcyBhbiBhZ2dyZXNzaXZlIHBvc3NpYmlsaXR5IGZvciBmaWx0ZXJpbmcKbnVsbF9yb3dzIDwtIHJvd1N1bXMoYWxsX210cngpIDwgbmNvbChhbGxfbXRyeCkKYWdncmVzc2l2ZSA8LSBhbGxfbXRyeFshbnVsbF9yb3dzLCBdCiMjIEhlcmUgaXMgYW4gYXJiaXRyYXJ5IHBvc3NpYmlsaXR5IGZvciBmaWx0ZXJpbmcKbnVsbF9yb3dzIDwtIHJvd1N1bXMoYWxsX210cngpIDwgMQphcmJpdHJhcnkgPC0gYWxsX210cnhbIW51bGxfcm93cywgXQoKbWV0YVtbImFnZ3Jlc3NpdmUiXV0gPC0gMAptZXRhW1siYXJiaXRyYXJ5Il1dIDwtIDAKZm9yIChjb2wgaW4gY29sbmFtZXMoYWxsX210cngpKSB7CiAgYWdnX2lkeCA8LSBhZ2dyZXNzaXZlWywgY29sXSA+IDAKICBtZXRhW2NvbCwgImFnZ3Jlc3NpdmUiXSA8LSBzdW0oYWdnX2lkeCkKICBhcmJfaWR4IDwtIGFyYml0cmFyeVssIGNvbF0gPiAwCiAgbWV0YVtjb2wsICJhcmJpdHJhcnkiXSA8LSBzdW0oYXJiX2lkeCkKfQoKCmxpYnJhcnkoZ2dwbG90MikKZ2dwbG90KGRhdGE9c2FtcGxlX2RmLCBtYXBwaW5nPWFlc19zdHJpbmcoZmlsbD0ic2FtcGxlaWQiKSkgKwogIGdlb21fdmlvbGluKG1hcHBpbmc9YWVzX3N0cmluZyh4PSJzYW1wbGVpZCIsIHk9Im51bWJlciIpKSArCiAgc2NhbGVfeV9sb2cxMCgpCgpvYnNlcnZlZCA8LSBnZ3Bsb3QoZGF0YT1tZXRhLAogICAgICAgICAgICAgICAgICAgbWFwcGluZz1hZXNfc3RyaW5nKHg9ImRpbHV0aW9uIiwgeT0ib2JzZXJ2ZWQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGw9ImNvbmRpdGlvbiIsIGNvbG91cj0iY29uZGl0aW9uIikpICsKICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzPTIxKSArCiAgZ2VvbV9wb2ludChzaXplPTMpICsKICBzY2FsZV94X2xvZzEwKCkKb2JzZXJ2ZWQKCnBhc3NlZCA8LSBnZ3Bsb3QoZGF0YT1tZXRhLAogICAgICAgICAgICAgICAgIG1hcHBpbmc9YWVzX3N0cmluZyh4PSJkaWx1dGlvbiIsIHk9InBhc3NlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGw9ImNvbmRpdGlvbiIsIGNvbG91cj0iY29uZGl0aW9uIikpICsKICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzPTIxKSArCiAgZ2VvbV9wb2ludChzaXplPTMpICsKICBzY2FsZV94X2xvZzEwKCkKcGFzc2VkCgphcmIgPC0gZ2dwbG90KGRhdGE9bWV0YSwKICAgICAgICAgICAgICAgICBtYXBwaW5nPWFlc19zdHJpbmcoeD0iZGlsdXRpb24iLCB5PSJhcmJpdHJhcnkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsPSJjb25kaXRpb24iLCBjb2xvdXI9ImNvbmRpdGlvbiIpKSArCiAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcz0yMSkgKwogIGdlb21fcG9pbnQoc2l6ZT0zKSArCiAgc2NhbGVfeF9sb2cxMCgpICsKICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIikKYXJiCmBgYAoKIyBEZXNjcmliZSB0aGlzIHJlbGF0aW9uc2hpcAoKSW4gdGhlIGZvbGxvd2luZyBibG9jaywgSSBhbSBmZWVkaW5nIHRoZSB0YWcgYW5kIGRpbHV0aW9uIGRhdGEgYnkgc2FtcGxlIHRvIGdsbQphbmQgbG9va2luZyBhdCB0aGUgcmVzdWx0LgoKYGBge3IgZ2xtfQptZXRhW1sibG9nIl1dIDwtIGxvZzEwKG1ldGFbWyJkaWx1dGlvbiJdXSkKbG1fcmVzdWx0IDwtIGdsbShmb3JtdWxhPWxvZyB+IGFyYml0cmFyeSwgZGF0YT1tZXRhKQpsbV9yZXN1bHQKc3VtbWFyeShsbV9yZXN1bHQpCmNvZWYobG1fcmVzdWx0KQpgYGAKCiMgUHJlZGljdCBuZXcgYmFjdGVyaWFsIGxvYWRzIGZyb20gbWFkZS11cCB0YWdzCgpOb3csIGxldCB1cyBwcmV0ZW5kIHRoYXQgd2UgcGVyZm9ybWVkIHRoZSBleHBlcmltZW50IG9uIHR3byBhY3R1YWwgc2FtcGxlcywgYW5kCmluIHRoZW0gd2UgZ290IDE1MDAgYW5kIDMwMDAgdGFncyByZXNwZWN0aXZlbHkuICBIb3cgbWFueSBiYWN0ZXJpYSBkbyB3ZSB0aGluawp0aGVyZSB3ZXJlIGluIGVhY2ggc2FtcGxlPwoKYGBge3IgZ2xtX3ByZWRpY3R9CnRlc3RfZGF0YSA8LSBkYXRhLmZyYW1lKHJvdy5uYW1lcz1jKCJ0MSIsICJ0MiIpKQp0ZXN0X2RhdGFbWyJhcmJpdHJhcnkiXV0gPC0gYygxNTAwLCAzMDAwKQpudW1fZm91bmQgPC0gcHJlZGljdC5nbG0ob2JqZWN0PWxtX3Jlc3VsdCwgbmV3ZGF0YT10ZXN0X2RhdGEsIHR5cGU9InJlc3BvbnNlIikKCjEwIF4gbnVtX2ZvdW5kCmBgYAoKCmBgYHtyIHNhdmVtZSwgZXZhbD1GQUxTRX0KcGFuZGVyOjpwYW5kZXIoc2Vzc2lvbkluZm8oKSkKbWVzc2FnZShwYXN0ZTAoIlRoaXMgaXMgaHBnbHRvb2xzIGNvbW1pdDogIiwgZ2V0X2dpdF9jb21taXQoKSkpCnRoaXNfc2F2ZSA8LSBwYXN0ZTAoZ3N1YihwYXR0ZXJuPSJcXC5SbWQiLCByZXBsYWNlPSIiLCB4PXJtZF9maWxlKSwgIi12IiwgdmVyLCAiLnJkYS54eiIpCm1lc3NhZ2UocGFzdGUwKCJTYXZpbmcgdG8gIiwgdGhpc19zYXZlKSkKdG1wIDwtIHNtKHNhdmVtZShmaWxlbmFtZT10aGlzX3NhdmUpKQpgYGAK