Link to notebook

Link to github repo.

Install and load packages

library(dada2)
library(DECIPHER)
library(phyloseq)
library(taxonomizr)

Get sample names

(Run in bash Terminal)

cd Raw_data
ls *R1_001.fastq.gz | cut -f 1-2 -d "_" > ../samples

Take a look at the untrimmed reads

## import sample names as R variable
samples <- scan("samples", what="character")
Read 55 items
# make variable holding the file names of all the forward read fastq files. These are in a subdirectory, so I am also adding the name of the sub directory to the file name
forward_reads <- paste0("Raw_data/", samples, "_L001_R1_001.fastq.gz")
# and one with the reverse
reverse_reads <- paste0("Raw_data/", samples, "_L001_R2_001.fastq.gz")

# And plot using a tool from dada2 (checking only 5 samples for plotting purposes)
plotQualityProfile(forward_reads[1:5])

From the above you can see the reads are 150bp and the quality is good, with the R reads being poorer than the F reads.

Removing primers using cutadapt- run in Terminal

Primers are universal MiFISh primers from Miya et al. 2015. Amplify a subregion of 12S rRNA that is 163-185 bp long.

MiFISH-U-F, 5’-3’: GTCGGTAAAACTCGTGCCAGC [rev comp: GCTGGCACGAGTTTTACCGAC]
MiFISH-U-R, 5’-3’: CATAGTGGGGTATCTAATCCCAGTTTG [rev comp: CAAACTGGGATTAGATACCCCACTATG]

Cut F primer from F reads with -g option
Cut rev comp of R primer from F reads with -a option
Cut R primer from R reads with -G option
Cut rev comp of F primer from R reads with -A option

Run cutadapt to remove primers. Use min length of 100bp (bash)

cd ..
mkdir trimmed_fastq
cd Raw_data

# Run in loop

for sample in $(cat ../samples)
do
    echo "On sample: $sample"
cutadapt -g GTCGGTAAAACTCGTGCCAGC \
-a CAAACTGGGATTAGATACCCCACTATG \
-G CATAGTGGGGTATCTAATCCCAGTTTG \
-A GCTGGCACGAGTTTTACCGAC \
-m 100 \
--discard-untrimmed \
-o ../trimmed_fastq/${sample}_L001_R1_001_trimmed.fastq.gz -p ../trimmed_fastq/${sample}_L001_R2_001_trimmed.fastq.gz \
${sample}_L001_R1_001.fastq.gz ${sample}_L001_R2_001.fastq.gz \
>> ../trimmed_fastq/cutadapt_primer_trimming_stats.txt 2>&1
done

Check output, how many were filtered out after cutadapt (bash)

paste ../samples <(grep "passing" ../trimmed_fastq/cutadapt_primer_trimming_stats.txt | cut -f3 -d "(" | tr -d ")") <(grep "filtered" ../trimmed_fastq/cutadapt_primer_trimming_stats.txt | cut -f3 -d "(" | tr -d ")")

Output:

T1PosCon_S11    89.4%   80.4%
T1S10_S9    81.4%   76.6%
T1S11_S10   97.5%   83.1%
T1S1_S1 97.9%   83.2%
T1S2_S2 68.4%   68.9%
T1S3_S3 85.3%   78.9%
T1S5_S4 91.6%   81.1%
T1S6_S5 85.5%   78.4%
T1S7_S6 83.3%   78.1%
T1S8_S7 98.4%   83.1%
T1S9_S8 94.6%   82.3%
T2PosCon_S21    99.4%   83.5%
T2S10_S19   69.5%   71.7%
T2S11_S20   90.5%   80.7%
T2S1_S12    68.5%   70.6%
T2S2_S13    97.8%   83.1%
T2S3_S14    98.1%   83.2%
T2S4_S15    87.3%   79.8%
T2S5_S16    78.9%   75.9%
T2S6_S17    92.2%   81.3%
T2S9_S18    98.9%   83.5%
T3PosCon_S33    61.0%   67.4%
T3S10_S31   90.7%   80.8%
T3S11_S32   64.6%   69.7%
T3S1_S22    70.2%   72.8%
T3S2_S23    97.5%   82.3%
T3S3_S24    33.2%   45.0%
T3S4_S25    82.3%   77.7%
T3S5_S26    74.4%   74.4%
T3S6_S27    94.1%   82.0%
T3S7_S28    88.1%   79.7%
T3S8_S29    97.0%   82.7%
T3S9_S30    95.8%   82.6%
T4S10_S43   89.3%   80.2%
T4S11_S44   90.3%   80.2%
T4S1_S34    97.4%   83.0%
T4S2_S35    77.8%   76.2%
T4S3_S36    96.4%   82.6%
T4S4_S37    79.2%   74.6%
T4S5_S38    93.5%   79.5%
T4S6_S39    88.9%   79.8%
T4S7_S40    92.9%   81.6%
T4S8_S41    91.1%   81.0%
T4S9_S42    77.7%   76.2%
T5S10_S54   92.7%   81.3%
T5S11_S55   84.4%   78.4%
T5S1_S45    95.3%   82.1%
T5S2_S46    96.3%   82.2%
T5S3_S47    97.8%   83.0%
T5S4_S48    96.4%   82.4%
T5S5_S49    83.8%   78.2%
T5S6_S50    89.2%   79.8%
T5S7_S51    78.3%   75.1%
T5S8_S52    96.3%   82.3%
T5S9_S53    95.3%   82.2%

So it retained ~60-99% of reads. Looking at the cutadapt_primer_trimming_stats.txt output file, you can see the F and R primers were trimmed in almost all cases and the rev comp of each primer was trimmed in many cases too.

DADA2

Go back to R console and take a look at the trimmed reads.

forward_reads_trimmed <- paste0("trimmed_fastq/", samples, "_L001_R1_001_trimmed.fastq.gz")
reverse_reads_trimmed <- paste0("trimmed_fastq/", samples, "_L001_R2_001_trimmed.fastq.gz")

# And plot 
plotQualityProfile(forward_reads_trimmed[1:5])

plotQualityProfile(reverse_reads_trimmed[1:5])

Comparing the above to the pre-trimmed reads, they look very similar because only very few had primers.

Quality Trimming

Make a directory for filtered reads (bash)

cd ..
mkdir filtered_fastq

Make variables containing the file names for the new filtered forward and reverse reads that we will make

filtered_forward_reads <- paste0("filtered_fastq/",samples, "_L001_R1_001_filtered.fastq.gz")
filtered_reverse_reads <- paste0("filtered_fastq/",samples, "_L001_R2_001_filtered.fastq.gz")

Based on how the quality plots look, determine how much to cut from each side. Trim the F reads at 130 Trim the R reads to 120 Also I want to run this step to trim out the low-quality individual reads (set maxEE to 1 for both F and R reads). The rm.phix = TRUE option removes any leftover PhiX genomic DNA (that gets added as a standard during sequencing). Pick a min length ~shorter than the min trimmed length (in this case 100 for R reads). I also set truncQ to truncate any read that has a quality score less than 2. Multithreading for this function does not work well (even according to documentation) so needed to skip that. Takes awhile to run.

filtered_out <- filterAndTrim(forward_reads_trimmed, filtered_forward_reads,
                reverse_reads_trimmed, filtered_reverse_reads, maxEE=c(2,2),
                rm.phix=TRUE, minLen=100, truncLen=c(130,120), truncQ = 2, maxN=0)

Check out the quality profiles again.

filtered_out
                                          reads.in reads.out
T1PosCon_S11_L001_R1_001_trimmed.fastq.gz   116280    110631
T1S10_S9_L001_R1_001_trimmed.fastq.gz        38054     35699
T1S11_S10_L001_R1_001_trimmed.fastq.gz      114528    106959
T1S1_S1_L001_R1_001_trimmed.fastq.gz         92897     86350
T1S2_S2_L001_R1_001_trimmed.fastq.gz          3246      2886
T1S3_S3_L001_R1_001_trimmed.fastq.gz        100412     95273
T1S5_S4_L001_R1_001_trimmed.fastq.gz         54866     51337
T1S6_S5_L001_R1_001_trimmed.fastq.gz         56588     53872
T1S7_S6_L001_R1_001_trimmed.fastq.gz         30703      1120
T1S8_S7_L001_R1_001_trimmed.fastq.gz         39544      1423
T1S9_S8_L001_R1_001_trimmed.fastq.gz        122522    115634
T2PosCon_S21_L001_R1_001_trimmed.fastq.gz    30602     28533
T2S10_S19_L001_R1_001_trimmed.fastq.gz       71843     66868
T2S11_S20_L001_R1_001_trimmed.fastq.gz       89790     50910
T2S1_S12_L001_R1_001_trimmed.fastq.gz        31282     28923
T2S2_S13_L001_R1_001_trimmed.fastq.gz       125120    115914
T2S3_S14_L001_R1_001_trimmed.fastq.gz        69969     65473
T2S4_S15_L001_R1_001_trimmed.fastq.gz       103877     95461
T2S5_S16_L001_R1_001_trimmed.fastq.gz        49491     45322
T2S6_S17_L001_R1_001_trimmed.fastq.gz        71976     65004
T2S9_S18_L001_R1_001_trimmed.fastq.gz        94811     87795
T3PosCon_S33_L001_R1_001_trimmed.fastq.gz    58777     55669
T3S10_S31_L001_R1_001_trimmed.fastq.gz       61472     49297
T3S11_S32_L001_R1_001_trimmed.fastq.gz       26605      1017
T3S1_S22_L001_R1_001_trimmed.fastq.gz        47808     40812
T3S2_S23_L001_R1_001_trimmed.fastq.gz         8852      7454
T3S3_S24_L001_R1_001_trimmed.fastq.gz        15729     11088
T3S4_S25_L001_R1_001_trimmed.fastq.gz        66149     47754
T3S5_S26_L001_R1_001_trimmed.fastq.gz        40310     19760
T3S6_S27_L001_R1_001_trimmed.fastq.gz        82355     75931
T3S7_S28_L001_R1_001_trimmed.fastq.gz        95225     86068
T3S8_S29_L001_R1_001_trimmed.fastq.gz        19744     16024
T3S9_S30_L001_R1_001_trimmed.fastq.gz        69446     53578
T4S10_S43_L001_R1_001_trimmed.fastq.gz      106101     96830
T4S11_S44_L001_R1_001_trimmed.fastq.gz       55349     51615
T4S1_S34_L001_R1_001_trimmed.fastq.gz        54676     43731
T4S2_S35_L001_R1_001_trimmed.fastq.gz        73007     66851
T4S3_S36_L001_R1_001_trimmed.fastq.gz        37373     34194
T4S4_S37_L001_R1_001_trimmed.fastq.gz        61890     47600
T4S5_S38_L001_R1_001_trimmed.fastq.gz         1860      1425
T4S6_S39_L001_R1_001_trimmed.fastq.gz        57411     52148
T4S7_S40_L001_R1_001_trimmed.fastq.gz        64167     56900
T4S8_S41_L001_R1_001_trimmed.fastq.gz        90353     85431
T4S9_S42_L001_R1_001_trimmed.fastq.gz        86928     80022
T5S10_S54_L001_R1_001_trimmed.fastq.gz       52707     38072
T5S11_S55_L001_R1_001_trimmed.fastq.gz       55188     46556
T5S1_S45_L001_R1_001_trimmed.fastq.gz        77878     73943
T5S2_S46_L001_R1_001_trimmed.fastq.gz        38058     34351
T5S3_S47_L001_R1_001_trimmed.fastq.gz       136287    128690
T5S4_S48_L001_R1_001_trimmed.fastq.gz        62588     57790
T5S5_S49_L001_R1_001_trimmed.fastq.gz        64785     60236
T5S6_S50_L001_R1_001_trimmed.fastq.gz        32802     29323
T5S7_S51_L001_R1_001_trimmed.fastq.gz        59603     51304
T5S8_S52_L001_R1_001_trimmed.fastq.gz        58354     53312
T5S9_S53_L001_R1_001_trimmed.fastq.gz       109768    100374
plotQualityProfile(filtered_forward_reads[1:5])

plotQualityProfile(filtered_reverse_reads[1:5])

These look better. Many were retained before and after quality trimming:

Save workspace up to this point.

save.image(file = "upto_filterfastq.RData")

Run if you come back and need to reload dataset

load("upto_filterfastq.RData")

Error profiling

Next, DADA2 tries to learn the error signature of our dataset. This step takes a while

err_forward_reads <- learnErrors(filtered_forward_reads, multithread=TRUE)
err_reverse_reads <- learnErrors(filtered_reverse_reads, multithread=TRUE)

Plot the error profiles

plotErrors(err_forward_reads, nominalQ=TRUE)
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
5: In readChar(file, size, TRUE) : truncating string with embedded nuls
6: In readChar(file, size, TRUE) : truncating string with embedded nuls
7: In readChar(file, size, TRUE) : truncating string with embedded nuls
8: In readChar(file, size, TRUE) : truncating string with embedded nuls

plotErrors(err_reverse_reads, nominalQ=TRUE)

The creators of DADA2 describe this here. The profiles are the error rates for each possible transition in the read (A->C, A->G, etc). Generally in the above plots, you want to see that the black dots (observed error rates for each quality score) match well with the black lines (the estimated error rate). The red line is what is expected based on the quality score.

Backup again since this step above takes awhile
Save workspace up to this point.

save.image(file = "upto_errorprofile.RData")

Inferring ASVs

Use the dada command to infer ASVs. This step also takes awhile

Stoeckle et al. use the loess error model for error estimation, which is the default so I didn’t set that here. They also set selfConsist = true so that “the error-model was independently built for each sample.” They also turn pooling off (the default) and said that they “build an error model using a subset of the total reads from a sequencing run and provide this model to DADA2.” However, in their code, they set the error model as err=inflateErr(tperr1,3) which uses an error matrix, tperr1, from a mock community. This is used as an example in the dada2 documentation but I don’t think it should be used for real samples. Really they should have generated the model from their own data (as above). So I am keeping my own modifications here and not theirs. This error model is generated from the samples themselves, with pseudopooling across samples. According to the developers: “pooling information across samples can increase sensitivity to sequence variants that may be present at very low frequencies in multiple samples.” The dada2 package offers two types of pooling, complete sample pooling (which is computationally expensive) and pseudo-pooling, which gives the benefit of pooling but is not as intensive. This is further described at the developer’s site.

dada_forward <- dada(filtered_forward_reads, err=err_forward_reads, pool="pseudo", multithread=TRUE) 
dada_reverse <- dada(filtered_reverse_reads, err=err_reverse_reads, pool="pseudo", multithread=TRUE)

Backup again since this step above takes awhile

Save workspace up to this point.

save.image(file = "upto_inferasv.RData")

Merge inferred reads

Dada2 will merge reads wherever the overlap is identical between the F and R reads. I trimmed the F reads to 130 and R reads to 120 (150). The full amplicon size (based on primers) should be 163-185. Being conservative, use a length of 185. This means the F read is from position 1 to 130 and the R read is from position 185 to 65, leaving a region of overlap between position 65 and 130, or 65 total bp.
Since this is an estimate, leave a little wiggle room and set the minimum overlap to 30bp. Also set trimOverhang to true, which makes sure that a read doesn’t go past its opposite primer (which probably wouldn’t happpen any way due to trimming).

merged_amplicons <- mergePairs(dada_forward, filtered_forward_reads, dada_reverse, filtered_reverse_reads, trimOverhang=TRUE, minOverlap=30, verbose = TRUE)
names(merged_amplicons)
 [1] "T1PosCon_S11" "T1S10_S9"     "T1S11_S10"    "T1S1_S1"      "T1S2_S2"      "T1S3_S3"      "T1S5_S4"     
 [8] "T1S6_S5"      "T1S7_S6"      "T1S8_S7"      "T1S9_S8"      "T2PosCon_S21" "T2S10_S19"    "T2S11_S20"   
[15] "T2S1_S12"     "T2S2_S13"     "T2S3_S14"     "T2S4_S15"     "T2S5_S16"     "T2S6_S17"     "T2S9_S18"    
[22] "T3PosCon_S33" "T3S10_S31"    "T3S11_S32"    "T3S1_S22"     "T3S2_S23"     "T3S3_S24"     "T3S4_S25"    
[29] "T3S5_S26"     "T3S6_S27"     "T3S7_S28"     "T3S8_S29"     "T3S9_S30"     "T4S10_S43"    "T4S11_S44"   
[36] "T4S1_S34"     "T4S2_S35"     "T4S3_S36"     "T4S4_S37"     "T4S5_S38"     "T4S6_S39"     "T4S7_S40"    
[43] "T4S8_S41"     "T4S9_S42"     "T5S10_S54"    "T5S11_S55"    "T5S1_S45"     "T5S2_S46"     "T5S3_S47"    
[50] "T5S4_S48"     "T5S5_S49"     "T5S6_S50"     "T5S7_S51"     "T5S8_S52"     "T5S9_S53"    
# Initially these names have the full name with `fastq.gz` in the name. Change to just sample name
names(merged_amplicons) <- samples

# Check some other things
length(merged_amplicons) # one for each of our samples
[1] 55
class(merged_amplicons$T1PosCon_S11) # each element of the list is a dataframe that can be accessed and manipulated like any ordinary dataframe
[1] "data.frame"
names(merged_amplicons$T1PosCon_S11) # the names() function on a dataframe gives you the column names
[1] "sequence"  "abundance" "forward"   "reverse"   "nmatch"    "nmismatch" "nindel"    "prefer"    "accept"   
# "sequence"  "abundance" "forward"   "reverse"   "nmatch"    "nmismatch" "nindel"    "prefer"    "accept"

Back up again

save.image(file = "upto_merge.RData")

Creating a sequence table

seqtab <- makeSequenceTable(merged_amplicons)
class(seqtab) # matrix
[1] "matrix" "array" 
dim(seqtab) # 55 samples, 3512 unique ASVs
[1]   55 3512

Removing chimeras

seqtab.nochim <- removeBimeraDenovo(seqtab, verbose=TRUE) 
Identified 3121 bimeras out of 3512 input sequences.
# Identified 3121 bimeras out of 3512 input sequences.

# though we got a lot of unique sequences, we don't know if they held a lot in terms of abundance, this is one quick way to look at that
sum(seqtab.nochim)/sum(seqtab) 
[1] 0.9457455
# 0.9457455
# good, we barely lost any in terms of abundance. That means the chimeras were very low abundance ASVs

dim(seqtab.nochim) # 55 samples, 391 unique ASVs remain
[1]  55 391

Backup again since this step above takes awhile Save workspace up to this point.

save.image(file = "upto_chimera.RData")

Summary of read counts through the pipeline

# set function from Happy Belly
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
5: In readChar(file, size, TRUE) : truncating string with embedded nuls
6: In readChar(file, size, TRUE) : truncating string with embedded nuls
7: In readChar(file, size, TRUE) : truncating string with embedded nuls
8: In readChar(file, size, TRUE) : truncating string with embedded nuls
getN <- function(x) sum(getUniques(x))

# making a little table
summary_tab <- data.frame(row.names=samples, dada2_input=filtered_out[,1],
                          filtered=filtered_out[,2], dada_f=sapply(dada_forward, getN),
                          dada_r=sapply(dada_reverse, getN), merged=sapply(merged_amplicons, getN),
                          nonchim=rowSums(seqtab.nochim),
                          final_perc_reads_retained=round(rowSums(seqtab.nochim)/filtered_out[,1]*100, 1))

summary_tab

In the end, we retained abot 80-95% of input reads after the filtering steps. (Except 3 samples that didn’t merge at all.)

Next, follow some code from Stoeckle et al. for making writeFasta function and exporting the “OTU” table and fasta file.
(Note this is not at “OTU” table because these are ASVs)

# Construct  sequence table
table(nchar(colnames(seqtab.nochim)))

114 116 118 122 137 138 139 140 162 164 167 168 169 170 171 172 173 175 180 181 215 217 
  1   1   1   1   2   1   2   3   2   2   4  32 169  25   9  43  85   1   2   2   2   1 
length(unique(substr(colnames(seqtab.nochim), 1, 100)))
[1] 227
dim(seqtab.nochim)
[1]  55 391
# Make "otu_table"
seqs <- colnames(seqtab.nochim)
otab <- otu_table(seqtab.nochim, taxa_are_rows=FALSE)
colnames(otab) <- paste0("Seq_", seq(ncol(otab)))
#Write fastas to test file
writeFasta <- function(seqs, output) {

  seqsout <- mapply( function(idx, sequence) paste0(">Seq_",idx,"\n",sequence,"\n"),
                     seq(length(seqs)),
                     seqs)
  write(paste0(seqsout), file = output, sep = "")

}

seqs_for_blast <- DNAStringSet(seqs)
names(seqs_for_blast) <-  sapply(seq(length(seqs)),function(x) {paste0("Seq_",x)})

# Write the fasta sequences and the OTU table
writeFasta(seqs, "results/tax_sequences.fasta")
write.table(otab,  file="results/otutable.csv", col.names = NA)

Annotation

I am using blastn function in terminal, which can be installed with the blast package from NCBI using conda conda install blast. I already had it installed but was having trouble bc v2.9 does not work on remote host (according to this). So I downgraded my blast to v2.6 with conda install -c bioconda/label/cf201901 blast

Run blastn on remote NCBI server

Use -max_target_seqs of 1 for now. Only puts top hit in the file

blastn -query results/tax_sequences.fasta \
-db nt \
-out results/tax_sequences_blast.txt \
-remote \
-max_target_seqs 1 \
-outfmt "6 qseqid sseqid pident length mismatch evalue bitscore staxids stitle" 

The headings of the table are the following parameters from NCBI:
- seqid: query (e.g., unknown gene) sequence id
- sseqid: subject (e.g., reference genome) sequence id
- pident: percentage of identical matches
- length: alignment length (sequence overlap)
- mismatch: number of mismatches
- evalue: expect value
- bitscore: bit score
- staxids: Subject Taxonomy ID(s), separated by a ‘;’
- stitle: Subject Title

LS0tCnRpdGxlOiAiQW5hbHlzaXMgb2YgU2FyYSdzIEF0bGFudGljIGZpc2ggZGF0YXNldCIKYXV0aG9yOiAiTGl6IFN1dGVyIgpkYXRlOiAiSmFuIDQgMjAyMSIKb3V0cHV0OiBodG1sX25vdGVib29rCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQpbTGlua10oaHR0cHM6Ly9saXpzdXRlci5naXRodWIuaW8vZmlsZXMvREFEQTJfcGlwZWxpbmVfU0NNX2VETkEubmIuaHRtbCkgdG8gbm90ZWJvb2sgIAoKW0xpbmtdKGh0dHBzOi8vZ2l0aHViLmNvbS9saXpzdXRlci9TQ01fZUROQSkgdG8gZ2l0aHViIHJlcG8uCjxicj4KCgojIyMgSW5zdGFsbCBhbmQgbG9hZCBwYWNrYWdlcwoKYGBge3J9CmxpYnJhcnkoZGFkYTIpCmxpYnJhcnkoREVDSVBIRVIpCmxpYnJhcnkocGh5bG9zZXEpCmxpYnJhcnkodGF4b25vbWl6cikKYGBgCgoKIyMjIEdldCBzYW1wbGUgbmFtZXMKKFJ1biBpbiBiYXNoIFRlcm1pbmFsKSAgCmBgYHtiYXNofQpjZCBSYXdfZGF0YQpscyAqUjFfMDAxLmZhc3RxLmd6IHwgY3V0IC1mIDEtMiAtZCAiXyIgPiAuLi9zYW1wbGVzCmBgYAoKCgojIyMgVGFrZSBhIGxvb2sgYXQgdGhlIHVudHJpbW1lZCByZWFkcwpgYGB7cn0KIyMgaW1wb3J0IHNhbXBsZSBuYW1lcyBhcyBSIHZhcmlhYmxlCnNhbXBsZXMgPC0gc2Nhbigic2FtcGxlcyIsIHdoYXQ9ImNoYXJhY3RlciIpCgojIG1ha2UgdmFyaWFibGUgaG9sZGluZyB0aGUgZmlsZSBuYW1lcyBvZiBhbGwgdGhlIGZvcndhcmQgcmVhZCBmYXN0cSBmaWxlcy4gVGhlc2UgYXJlIGluIGEgc3ViZGlyZWN0b3J5LCBzbyBJIGFtIGFsc28gYWRkaW5nIHRoZSBuYW1lIG9mIHRoZSBzdWIgZGlyZWN0b3J5IHRvIHRoZSBmaWxlIG5hbWUKZm9yd2FyZF9yZWFkcyA8LSBwYXN0ZTAoIlJhd19kYXRhLyIsIHNhbXBsZXMsICJfTDAwMV9SMV8wMDEuZmFzdHEuZ3oiKQojIGFuZCBvbmUgd2l0aCB0aGUgcmV2ZXJzZQpyZXZlcnNlX3JlYWRzIDwtIHBhc3RlMCgiUmF3X2RhdGEvIiwgc2FtcGxlcywgIl9MMDAxX1IyXzAwMS5mYXN0cS5neiIpCgojIEFuZCBwbG90IHVzaW5nIGEgdG9vbCBmcm9tIGRhZGEyIChjaGVja2luZyBvbmx5IDUgc2FtcGxlcyBmb3IgcGxvdHRpbmcgcHVycG9zZXMpCnBsb3RRdWFsaXR5UHJvZmlsZShmb3J3YXJkX3JlYWRzWzE6NV0pCnBsb3RRdWFsaXR5UHJvZmlsZShyZXZlcnNlX3JlYWRzWzE6NV0pCmBgYAoKRnJvbSB0aGUgYWJvdmUgeW91IGNhbiBzZWUgdGhlIHJlYWRzIGFyZSAxNTBicCBhbmQgdGhlIHF1YWxpdHkgaXMgZ29vZCwgd2l0aCB0aGUgUiByZWFkcyBiZWluZyBwb29yZXIgdGhhbiB0aGUgRiByZWFkcy4gCgojIyMgUmVtb3ZpbmcgcHJpbWVycyB1c2luZyBjdXRhZGFwdC0gcnVuIGluIFRlcm1pbmFsCgpQcmltZXJzIGFyZSB1bml2ZXJzYWwgTWlGSVNoIHByaW1lcnMgZnJvbSBbTWl5YSBldCBhbC4gMjAxNV0oaHR0cHM6Ly9yb3lhbHNvY2lldHlwdWJsaXNoaW5nLm9yZy9kb2kvMTAuMTA5OC9yc29zLjE1MDA4OCkuIEFtcGxpZnkgYSBzdWJyZWdpb24gb2YgMTJTIHJSTkEgdGhhdCBpcyAxNjMtMTg1IGJwIGxvbmcuICAKCk1pRklTSC1VLUYsIDUnLTMnOiBHVENHR1RBQUFBQ1RDR1RHQ0NBR0MgW3JldiBjb21wOiBHQ1RHR0NBQ0dBR1RUVFRBQ0NHQUNdICAKTWlGSVNILVUtUiwgNSctMyc6IENBVEFHVEdHR0dUQVRDVEFBVENDQ0FHVFRURyBbcmV2IGNvbXA6IENBQUFDVEdHR0FUVEFHQVRBQ0NDQ0FDVEFUR10KCkN1dCBGIHByaW1lciBmcm9tIEYgcmVhZHMgd2l0aCAtZyBvcHRpb24gIApDdXQgcmV2IGNvbXAgb2YgUiBwcmltZXIgZnJvbSBGIHJlYWRzIHdpdGggLWEgb3B0aW9uICAKQ3V0IFIgcHJpbWVyIGZyb20gUiByZWFkcyB3aXRoIC1HIG9wdGlvbiAgCkN1dCByZXYgY29tcCBvZiBGIHByaW1lciBmcm9tIFIgcmVhZHMgd2l0aCAtQSBvcHRpb24gIAoKUnVuIGN1dGFkYXB0IHRvIHJlbW92ZSBwcmltZXJzLiBVc2UgbWluIGxlbmd0aCBvZiAxMDBicCAoYmFzaCkKCmBgYHtiYXNofQpjZCAuLgpta2RpciB0cmltbWVkX2Zhc3RxCmNkIFJhd19kYXRhCgojIFJ1biBpbiBsb29wCgpmb3Igc2FtcGxlIGluICQoY2F0IC4uL3NhbXBsZXMpCmRvCiAgICBlY2hvICJPbiBzYW1wbGU6ICRzYW1wbGUiCmN1dGFkYXB0IC1nIEdUQ0dHVEFBQUFDVENHVEdDQ0FHQyBcCi1hIENBQUFDVEdHR0FUVEFHQVRBQ0NDQ0FDVEFURyBcCi1HIENBVEFHVEdHR0dUQVRDVEFBVENDQ0FHVFRURyBcCi1BIEdDVEdHQ0FDR0FHVFRUVEFDQ0dBQyBcCi1tIDEwMCBcCi0tZGlzY2FyZC11bnRyaW1tZWQgXAotbyAuLi90cmltbWVkX2Zhc3RxLyR7c2FtcGxlfV9MMDAxX1IxXzAwMV90cmltbWVkLmZhc3RxLmd6IC1wIC4uL3RyaW1tZWRfZmFzdHEvJHtzYW1wbGV9X0wwMDFfUjJfMDAxX3RyaW1tZWQuZmFzdHEuZ3ogXAoke3NhbXBsZX1fTDAwMV9SMV8wMDEuZmFzdHEuZ3ogJHtzYW1wbGV9X0wwMDFfUjJfMDAxLmZhc3RxLmd6IFwKPj4gLi4vdHJpbW1lZF9mYXN0cS9jdXRhZGFwdF9wcmltZXJfdHJpbW1pbmdfc3RhdHMudHh0IDI+JjEKZG9uZQoKYGBgCgoKQ2hlY2sgb3V0cHV0LCBob3cgbWFueSB3ZXJlIGZpbHRlcmVkIG91dCBhZnRlciBjdXRhZGFwdCAoYmFzaCkKYGBge2Jhc2h9CnBhc3RlIC4uL3NhbXBsZXMgPChncmVwICJwYXNzaW5nIiAuLi90cmltbWVkX2Zhc3RxL2N1dGFkYXB0X3ByaW1lcl90cmltbWluZ19zdGF0cy50eHQgfCBjdXQgLWYzIC1kICIoIiB8IHRyIC1kICIpIikgPChncmVwICJmaWx0ZXJlZCIgLi4vdHJpbW1lZF9mYXN0cS9jdXRhZGFwdF9wcmltZXJfdHJpbW1pbmdfc3RhdHMudHh0IHwgY3V0IC1mMyAtZCAiKCIgfCB0ciAtZCAiKSIpCmBgYAoKT3V0cHV0OiAKYGBgClQxUG9zQ29uX1MxMQk4OS40JQk4MC40JQpUMVMxMF9TOQk4MS40JQk3Ni42JQpUMVMxMV9TMTAJOTcuNSUJODMuMSUKVDFTMV9TMQk5Ny45JQk4My4yJQpUMVMyX1MyCTY4LjQlCTY4LjklClQxUzNfUzMJODUuMyUJNzguOSUKVDFTNV9TNAk5MS42JQk4MS4xJQpUMVM2X1M1CTg1LjUlCTc4LjQlClQxUzdfUzYJODMuMyUJNzguMSUKVDFTOF9TNwk5OC40JQk4My4xJQpUMVM5X1M4CTk0LjYlCTgyLjMlClQyUG9zQ29uX1MyMQk5OS40JQk4My41JQpUMlMxMF9TMTkJNjkuNSUJNzEuNyUKVDJTMTFfUzIwCTkwLjUlCTgwLjclClQyUzFfUzEyCTY4LjUlCTcwLjYlClQyUzJfUzEzCTk3LjglCTgzLjElClQyUzNfUzE0CTk4LjElCTgzLjIlClQyUzRfUzE1CTg3LjMlCTc5LjglClQyUzVfUzE2CTc4LjklCTc1LjklClQyUzZfUzE3CTkyLjIlCTgxLjMlClQyUzlfUzE4CTk4LjklCTgzLjUlClQzUG9zQ29uX1MzMwk2MS4wJQk2Ny40JQpUM1MxMF9TMzEJOTAuNyUJODAuOCUKVDNTMTFfUzMyCTY0LjYlCTY5LjclClQzUzFfUzIyCTcwLjIlCTcyLjglClQzUzJfUzIzCTk3LjUlCTgyLjMlClQzUzNfUzI0CTMzLjIlCTQ1LjAlClQzUzRfUzI1CTgyLjMlCTc3LjclClQzUzVfUzI2CTc0LjQlCTc0LjQlClQzUzZfUzI3CTk0LjElCTgyLjAlClQzUzdfUzI4CTg4LjElCTc5LjclClQzUzhfUzI5CTk3LjAlCTgyLjclClQzUzlfUzMwCTk1LjglCTgyLjYlClQ0UzEwX1M0Mwk4OS4zJQk4MC4yJQpUNFMxMV9TNDQJOTAuMyUJODAuMiUKVDRTMV9TMzQJOTcuNCUJODMuMCUKVDRTMl9TMzUJNzcuOCUJNzYuMiUKVDRTM19TMzYJOTYuNCUJODIuNiUKVDRTNF9TMzcJNzkuMiUJNzQuNiUKVDRTNV9TMzgJOTMuNSUJNzkuNSUKVDRTNl9TMzkJODguOSUJNzkuOCUKVDRTN19TNDAJOTIuOSUJODEuNiUKVDRTOF9TNDEJOTEuMSUJODEuMCUKVDRTOV9TNDIJNzcuNyUJNzYuMiUKVDVTMTBfUzU0CTkyLjclCTgxLjMlClQ1UzExX1M1NQk4NC40JQk3OC40JQpUNVMxX1M0NQk5NS4zJQk4Mi4xJQpUNVMyX1M0Ngk5Ni4zJQk4Mi4yJQpUNVMzX1M0Nwk5Ny44JQk4My4wJQpUNVM0X1M0OAk5Ni40JQk4Mi40JQpUNVM1X1M0OQk4My44JQk3OC4yJQpUNVM2X1M1MAk4OS4yJQk3OS44JQpUNVM3X1M1MQk3OC4zJQk3NS4xJQpUNVM4X1M1Mgk5Ni4zJQk4Mi4zJQpUNVM5X1M1Mwk5NS4zJQk4Mi4yJQpgYGAKClNvIGl0IHJldGFpbmVkIH42MC05OSUgb2YgcmVhZHMuIExvb2tpbmcgYXQgdGhlIGBjdXRhZGFwdF9wcmltZXJfdHJpbW1pbmdfc3RhdHMudHh0YCBvdXRwdXQgZmlsZSwgeW91IGNhbiBzZWUgdGhlIEYgYW5kIFIgcHJpbWVycyB3ZXJlIHRyaW1tZWQgaW4gYWxtb3N0IGFsbCBjYXNlcyBhbmQgdGhlIHJldiBjb21wIG9mIGVhY2ggcHJpbWVyIHdhcyB0cmltbWVkIGluIG1hbnkgY2FzZXMgdG9vLgoKCiMgREFEQTIKCgpHbyBiYWNrIHRvIFIgY29uc29sZSBhbmQgdGFrZSBhIGxvb2sgYXQgdGhlIHRyaW1tZWQgcmVhZHMuCgpgYGB7cn0KZm9yd2FyZF9yZWFkc190cmltbWVkIDwtIHBhc3RlMCgidHJpbW1lZF9mYXN0cS8iLCBzYW1wbGVzLCAiX0wwMDFfUjFfMDAxX3RyaW1tZWQuZmFzdHEuZ3oiKQpyZXZlcnNlX3JlYWRzX3RyaW1tZWQgPC0gcGFzdGUwKCJ0cmltbWVkX2Zhc3RxLyIsIHNhbXBsZXMsICJfTDAwMV9SMl8wMDFfdHJpbW1lZC5mYXN0cS5neiIpCgojIEFuZCBwbG90IApwbG90UXVhbGl0eVByb2ZpbGUoZm9yd2FyZF9yZWFkc190cmltbWVkWzE6NV0pCnBsb3RRdWFsaXR5UHJvZmlsZShyZXZlcnNlX3JlYWRzX3RyaW1tZWRbMTo1XSkKYGBgCgpDb21wYXJpbmcgdGhlIGFib3ZlIHRvIHRoZSBwcmUtdHJpbW1lZCByZWFkcywgdGhleSBsb29rIHZlcnkgc2ltaWxhciBiZWNhdXNlIG9ubHkgdmVyeSBmZXcgaGFkIHByaW1lcnMuIAoKIyMjIFF1YWxpdHkgVHJpbW1pbmcKTWFrZSBhIGRpcmVjdG9yeSBmb3IgZmlsdGVyZWQgcmVhZHMgKGJhc2gpCmBgYHtiYXNofQpjZCAuLgpta2RpciBmaWx0ZXJlZF9mYXN0cQpgYGAKCgoKTWFrZSB2YXJpYWJsZXMgY29udGFpbmluZyB0aGUgZmlsZSBuYW1lcyBmb3IgdGhlIG5ldyBmaWx0ZXJlZCBmb3J3YXJkIGFuZCByZXZlcnNlIHJlYWRzIHRoYXQgd2Ugd2lsbCBtYWtlCmBgYHtyfQpmaWx0ZXJlZF9mb3J3YXJkX3JlYWRzIDwtIHBhc3RlMCgiZmlsdGVyZWRfZmFzdHEvIixzYW1wbGVzLCAiX0wwMDFfUjFfMDAxX2ZpbHRlcmVkLmZhc3RxLmd6IikKZmlsdGVyZWRfcmV2ZXJzZV9yZWFkcyA8LSBwYXN0ZTAoImZpbHRlcmVkX2Zhc3RxLyIsc2FtcGxlcywgIl9MMDAxX1IyXzAwMV9maWx0ZXJlZC5mYXN0cS5neiIpCmBgYAoKCgpCYXNlZCBvbiBob3cgdGhlIHF1YWxpdHkgcGxvdHMgbG9vaywgZGV0ZXJtaW5lIGhvdyBtdWNoIHRvIGN1dCBmcm9tIGVhY2ggc2lkZS4gVHJpbSB0aGUgRiByZWFkcyBhdCAxMzAgVHJpbSB0aGUgUiByZWFkcyB0byAxMjAgQWxzbyBJIHdhbnQgdG8gcnVuIHRoaXMgc3RlcCB0byB0cmltIG91dCB0aGUgbG93LXF1YWxpdHkgaW5kaXZpZHVhbCByZWFkcyAoc2V0IG1heEVFIHRvIDEgZm9yIGJvdGggRiBhbmQgUiByZWFkcykuIFRoZSBybS5waGl4ID0gVFJVRSBvcHRpb24gcmVtb3ZlcyBhbnkgbGVmdG92ZXIgIFBoaVggZ2Vub21pYyBETkEgKHRoYXQgZ2V0cyBhZGRlZCBhcyBhIHN0YW5kYXJkIGR1cmluZyBzZXF1ZW5jaW5nKS4gUGljayBhIG1pbiBsZW5ndGggfnNob3J0ZXIgdGhhbiB0aGUgbWluIHRyaW1tZWQgbGVuZ3RoIChpbiB0aGlzIGNhc2UgMTAwIGZvciBSIHJlYWRzKS4gSSBhbHNvIHNldCB0cnVuY1EgdG8gdHJ1bmNhdGUgYW55IHJlYWQgdGhhdCBoYXMgYSBxdWFsaXR5IHNjb3JlIGxlc3MgdGhhbiAyLiBNdWx0aXRocmVhZGluZyBmb3IgdGhpcyBmdW5jdGlvbiBkb2VzIG5vdCB3b3JrIHdlbGwgKGV2ZW4gYWNjb3JkaW5nIHRvIGRvY3VtZW50YXRpb24pIHNvIG5lZWRlZCB0byBza2lwIHRoYXQuIFRha2VzIGF3aGlsZSAgdG8gcnVuLgpgYGB7cn0KZmlsdGVyZWRfb3V0IDwtIGZpbHRlckFuZFRyaW0oZm9yd2FyZF9yZWFkc190cmltbWVkLCBmaWx0ZXJlZF9mb3J3YXJkX3JlYWRzLAogICAgICAgICAgICAgICAgcmV2ZXJzZV9yZWFkc190cmltbWVkLCBmaWx0ZXJlZF9yZXZlcnNlX3JlYWRzLCBtYXhFRT1jKDIsMiksCiAgICAgICAgICAgICAgICBybS5waGl4PVRSVUUsIG1pbkxlbj0xMDAsIHRydW5jTGVuPWMoMTMwLDEyMCksIHRydW5jUSA9IDIsIG1heE49MCkKYGBgCgoKCkNoZWNrIG91dCB0aGUgcXVhbGl0eSBwcm9maWxlcyBhZ2Fpbi4KYGBge3J9CmZpbHRlcmVkX291dAoKcGxvdFF1YWxpdHlQcm9maWxlKGZpbHRlcmVkX2ZvcndhcmRfcmVhZHNbMTo1XSkKcGxvdFF1YWxpdHlQcm9maWxlKGZpbHRlcmVkX3JldmVyc2VfcmVhZHNbMTo1XSkKYGBgCgoKVGhlc2UgbG9vayBiZXR0ZXIuIE1hbnkgd2VyZSByZXRhaW5lZCBiZWZvcmUgYW5kIGFmdGVyIHF1YWxpdHkgdHJpbW1pbmc6CgoKU2F2ZSB3b3Jrc3BhY2UgdXAgdG8gdGhpcyBwb2ludC4KYGBge3J9CnNhdmUuaW1hZ2UoZmlsZSA9ICJ1cHRvX2ZpbHRlcmZhc3RxLlJEYXRhIikKYGBgCgoKClJ1biBpZiB5b3UgY29tZSBiYWNrIGFuZCBuZWVkIHRvIHJlbG9hZCBkYXRhc2V0CmBgYHtyfQpsb2FkKCJ1cHRvX2ZpbHRlcmZhc3RxLlJEYXRhIikKYGBgCgoKIyMjIEVycm9yIHByb2ZpbGluZwpOZXh0LCBEQURBMiB0cmllcyB0byBsZWFybiB0aGUgZXJyb3Igc2lnbmF0dXJlIG9mIG91ciBkYXRhc2V0LiBUaGlzIHN0ZXAgdGFrZXMgYSB3aGlsZQpgYGB7cn0KZXJyX2ZvcndhcmRfcmVhZHMgPC0gbGVhcm5FcnJvcnMoZmlsdGVyZWRfZm9yd2FyZF9yZWFkcywgbXVsdGl0aHJlYWQ9VFJVRSkKZXJyX3JldmVyc2VfcmVhZHMgPC0gbGVhcm5FcnJvcnMoZmlsdGVyZWRfcmV2ZXJzZV9yZWFkcywgbXVsdGl0aHJlYWQ9VFJVRSkKYGBgCgoKClBsb3QgdGhlIGVycm9yIHByb2ZpbGVzCmBgYHtyfQpwbG90RXJyb3JzKGVycl9mb3J3YXJkX3JlYWRzLCBub21pbmFsUT1UUlVFKQpwbG90RXJyb3JzKGVycl9yZXZlcnNlX3JlYWRzLCBub21pbmFsUT1UUlVFKQpgYGAKClRoZSBjcmVhdG9ycyBvZiBEQURBMiBkZXNjcmliZSB0aGlzIFtoZXJlXShodHRwczovL2JlbmpqbmViLmdpdGh1Yi5pby9kYWRhMi90dXRvcmlhbC5odG1sI2xlYXJuLXRoZS1lcnJvci1yYXRlcykuIFRoZSBwcm9maWxlcyBhcmUgdGhlIGVycm9yIHJhdGVzIGZvciBlYWNoIHBvc3NpYmxlIHRyYW5zaXRpb24gaW4gdGhlIHJlYWQgKEEtPkMsIEEtPkcsIGV0YykuIEdlbmVyYWxseSBpbiB0aGUgYWJvdmUgcGxvdHMsIHlvdSB3YW50IHRvIHNlZSB0aGF0IHRoZSBibGFjayBkb3RzIChvYnNlcnZlZCBlcnJvciByYXRlcyBmb3IgZWFjaCBxdWFsaXR5IHNjb3JlKSBtYXRjaCB3ZWxsIHdpdGggdGhlIGJsYWNrIGxpbmVzICh0aGUgZXN0aW1hdGVkIGVycm9yIHJhdGUpLiBUaGUgcmVkIGxpbmUgaXMgd2hhdCBpcyBleHBlY3RlZCBiYXNlZCBvbiB0aGUgcXVhbGl0eSBzY29yZS4KCkJhY2t1cCBhZ2FpbiBzaW5jZSB0aGlzIHN0ZXAgYWJvdmUgdGFrZXMgYXdoaWxlICAKU2F2ZSB3b3Jrc3BhY2UgdXAgdG8gdGhpcyBwb2ludC4KYGBge3J9CnNhdmUuaW1hZ2UoZmlsZSA9ICJ1cHRvX2Vycm9ycHJvZmlsZS5SRGF0YSIpCmBgYAoKCgojIyMgSW5mZXJyaW5nIEFTVnMKVXNlIHRoZSBkYWRhIGNvbW1hbmQgdG8gaW5mZXIgQVNWcy4gVGhpcyBzdGVwIGFsc28gdGFrZXMgYXdoaWxlCgpTdG9lY2tsZSBldCBhbC4gdXNlIHRoZSBsb2VzcyBlcnJvciBtb2RlbCBmb3IgZXJyb3IgZXN0aW1hdGlvbiwgd2hpY2ggaXMgdGhlIGRlZmF1bHQgc28gSSBkaWRuJ3Qgc2V0IHRoYXQgaGVyZS4gVGhleSBhbHNvIHNldCBzZWxmQ29uc2lzdCA9IHRydWUgc28gdGhhdCAidGhlIGVycm9yLW1vZGVsIHdhcyBpbmRlcGVuZGVudGx5IGJ1aWx0IGZvciBlYWNoIHNhbXBsZS4iIFRoZXkgYWxzbyB0dXJuIHBvb2xpbmcgb2ZmICh0aGUgZGVmYXVsdCkgYW5kIHNhaWQgdGhhdCB0aGV5ICJidWlsZCBhbiBlcnJvciBtb2RlbCB1c2luZyBhIHN1YnNldCBvZiB0aGUgdG90YWwgcmVhZHMgZnJvbSBhIHNlcXVlbmNpbmcgcnVuIGFuZCBwcm92aWRlIHRoaXMgbW9kZWwgdG8gREFEQTIuIiBIb3dldmVyLCBpbiB0aGVpciBjb2RlLCB0aGV5IHNldCB0aGUgZXJyb3IgbW9kZWwgYXMgYGVycj1pbmZsYXRlRXJyKHRwZXJyMSwzKWAgd2hpY2ggdXNlcyBhbiBlcnJvciBtYXRyaXgsIGB0cGVycjFgLCBmcm9tIGEgbW9jayBjb21tdW5pdHkuIFRoaXMgaXMgdXNlZCBhcyBhbiBleGFtcGxlIGluIHRoZSBkYWRhMiBkb2N1bWVudGF0aW9uIGJ1dCBJIGRvbid0IHRoaW5rIGl0IHNob3VsZCBiZSB1c2VkIGZvciByZWFsIHNhbXBsZXMuIFJlYWxseSB0aGV5IHNob3VsZCBoYXZlIGdlbmVyYXRlZCB0aGUgbW9kZWwgZnJvbSB0aGVpciBvd24gZGF0YSAoYXMgYWJvdmUpLiBTbyBJIGFtIGtlZXBpbmcgbXkgb3duIG1vZGlmaWNhdGlvbnMgaGVyZSBhbmQgbm90IHRoZWlycy4gVGhpcyBlcnJvciBtb2RlbCBpcyBnZW5lcmF0ZWQgZnJvbSB0aGUgc2FtcGxlcyB0aGVtc2VsdmVzLCB3aXRoIHBzZXVkb3Bvb2xpbmcgYWNyb3NzIHNhbXBsZXMuIEFjY29yZGluZyB0byB0aGUgZGV2ZWxvcGVyczogInBvb2xpbmcgaW5mb3JtYXRpb24gYWNyb3NzIHNhbXBsZXMgY2FuIGluY3JlYXNlIHNlbnNpdGl2aXR5IHRvIHNlcXVlbmNlIHZhcmlhbnRzIHRoYXQgbWF5IGJlIHByZXNlbnQgYXQgdmVyeSBsb3cgZnJlcXVlbmNpZXMgaW4gbXVsdGlwbGUgc2FtcGxlcy4iIFRoZSBkYWRhMiBwYWNrYWdlIG9mZmVycyB0d28gdHlwZXMgb2YgcG9vbGluZywgY29tcGxldGUgc2FtcGxlIHBvb2xpbmcgKHdoaWNoIGlzIGNvbXB1dGF0aW9uYWxseSBleHBlbnNpdmUpIGFuZCBwc2V1ZG8tcG9vbGluZywgd2hpY2ggZ2l2ZXMgdGhlIGJlbmVmaXQgb2YgcG9vbGluZyBidXQgaXMgbm90IGFzIGludGVuc2l2ZS4gVGhpcyBpcyBmdXJ0aGVyIGRlc2NyaWJlZCBhdCB0aGUgZGV2ZWxvcGVyJ3MgW3NpdGVdKGh0dHBzOi8vYmVuampuZWIuZ2l0aHViLmlvL2RhZGEyL3BzZXVkby5odG1sI3BzZXVkby1wb29saW5nKS4gCmBgYHtyfQpkYWRhX2ZvcndhcmQgPC0gZGFkYShmaWx0ZXJlZF9mb3J3YXJkX3JlYWRzLCBlcnI9ZXJyX2ZvcndhcmRfcmVhZHMsIHBvb2w9InBzZXVkbyIsIG11bHRpdGhyZWFkPVRSVUUpIApkYWRhX3JldmVyc2UgPC0gZGFkYShmaWx0ZXJlZF9yZXZlcnNlX3JlYWRzLCBlcnI9ZXJyX3JldmVyc2VfcmVhZHMsIHBvb2w9InBzZXVkbyIsIG11bHRpdGhyZWFkPVRSVUUpCmBgYAoKCgpCYWNrdXAgYWdhaW4gc2luY2UgdGhpcyBzdGVwIGFib3ZlIHRha2VzIGF3aGlsZSAgCgpTYXZlIHdvcmtzcGFjZSB1cCB0byB0aGlzIHBvaW50LgpgYGB7cn0Kc2F2ZS5pbWFnZShmaWxlID0gInVwdG9faW5mZXJhc3YuUkRhdGEiKQpgYGAKCgoKIyMjIE1lcmdlIGluZmVycmVkIHJlYWRzIApEYWRhMiB3aWxsIG1lcmdlIHJlYWRzIHdoZXJldmVyIHRoZSBvdmVybGFwIGlzIGlkZW50aWNhbCBiZXR3ZWVuIHRoZSBGIGFuZCBSIHJlYWRzLiBJIHRyaW1tZWQgdGhlIEYgcmVhZHMgdG8gMTMwIGFuZCBSIHJlYWRzIHRvIDEyMCAoMTUwKS4gVGhlIGZ1bGwgYW1wbGljb24gc2l6ZSAoYmFzZWQgb24gcHJpbWVycykgc2hvdWxkIGJlIDE2My0xODUuIEJlaW5nIGNvbnNlcnZhdGl2ZSwgdXNlIGEgbGVuZ3RoIG9mIDE4NS4gVGhpcyBtZWFucyB0aGUgRiByZWFkIGlzIGZyb20gcG9zaXRpb24gMSB0byAxMzAgYW5kIHRoZSBSIHJlYWQgaXMgZnJvbSBwb3NpdGlvbiAxODUgdG8gNjUsIGxlYXZpbmcgYSByZWdpb24gb2Ygb3ZlcmxhcCBiZXR3ZWVuIHBvc2l0aW9uIDY1IGFuZCAxMzAsIG9yIDY1IHRvdGFsIGJwLiAgClNpbmNlIHRoaXMgaXMgYW4gZXN0aW1hdGUsIGxlYXZlIGEgbGl0dGxlIHdpZ2dsZSByb29tIGFuZCBzZXQgdGhlIG1pbmltdW0gb3ZlcmxhcCB0byAzMGJwLiBBbHNvIHNldCB0cmltT3ZlcmhhbmcgdG8gdHJ1ZSwgd2hpY2ggbWFrZXMgc3VyZSB0aGF0IGEgcmVhZCBkb2Vzbid0IGdvIHBhc3QgaXRzIG9wcG9zaXRlIHByaW1lciAod2hpY2ggcHJvYmFibHkgd291bGRuJ3QgaGFwcHBlbiBhbnkgd2F5IGR1ZSB0byB0cmltbWluZykuCmBgYHtyfQptZXJnZWRfYW1wbGljb25zIDwtIG1lcmdlUGFpcnMoZGFkYV9mb3J3YXJkLCBmaWx0ZXJlZF9mb3J3YXJkX3JlYWRzLCBkYWRhX3JldmVyc2UsIGZpbHRlcmVkX3JldmVyc2VfcmVhZHMsIHRyaW1PdmVyaGFuZz1UUlVFLCBtaW5PdmVybGFwPTMwLCB2ZXJib3NlID0gVFJVRSkKYGBgCgoKCmBgYHtyfQpuYW1lcyhtZXJnZWRfYW1wbGljb25zKQojIEluaXRpYWxseSB0aGVzZSBuYW1lcyBoYXZlIHRoZSBmdWxsIG5hbWUgd2l0aCBgZmFzdHEuZ3pgIGluIHRoZSBuYW1lLiBDaGFuZ2UgdG8ganVzdCBzYW1wbGUgbmFtZQpuYW1lcyhtZXJnZWRfYW1wbGljb25zKSA8LSBzYW1wbGVzCgojIENoZWNrIHNvbWUgb3RoZXIgdGhpbmdzCmxlbmd0aChtZXJnZWRfYW1wbGljb25zKSAjIG9uZSBmb3IgZWFjaCBvZiBvdXIgc2FtcGxlcwpjbGFzcyhtZXJnZWRfYW1wbGljb25zJFQxUG9zQ29uX1MxMSkgIyBlYWNoIGVsZW1lbnQgb2YgdGhlIGxpc3QgaXMgYSBkYXRhZnJhbWUgdGhhdCBjYW4gYmUgYWNjZXNzZWQgYW5kIG1hbmlwdWxhdGVkIGxpa2UgYW55IG9yZGluYXJ5IGRhdGFmcmFtZQpuYW1lcyhtZXJnZWRfYW1wbGljb25zJFQxUG9zQ29uX1MxMSkgIyB0aGUgbmFtZXMoKSBmdW5jdGlvbiBvbiBhIGRhdGFmcmFtZSBnaXZlcyB5b3UgdGhlIGNvbHVtbiBuYW1lcwojICJzZXF1ZW5jZSIgICJhYnVuZGFuY2UiICJmb3J3YXJkIiAgICJyZXZlcnNlIiAgICJubWF0Y2giICAgICJubWlzbWF0Y2giICJuaW5kZWwiICAgICJwcmVmZXIiICAgICJhY2NlcHQiCmBgYAoKCgpCYWNrIHVwIGFnYWluCmBgYHtyfQpzYXZlLmltYWdlKGZpbGUgPSAidXB0b19tZXJnZS5SRGF0YSIpCmBgYAoKCiMjIyBDcmVhdGluZyBhIHNlcXVlbmNlIHRhYmxlCmBgYHtyfQpzZXF0YWIgPC0gbWFrZVNlcXVlbmNlVGFibGUobWVyZ2VkX2FtcGxpY29ucykKY2xhc3Moc2VxdGFiKSAjIG1hdHJpeApkaW0oc2VxdGFiKSAjIDU1IHNhbXBsZXMsIDM1MTIgdW5pcXVlIEFTVnMKYGBgCgojIyMgUmVtb3ZpbmcgY2hpbWVyYXMKYGBge3J9CnNlcXRhYi5ub2NoaW0gPC0gcmVtb3ZlQmltZXJhRGVub3ZvKHNlcXRhYiwgdmVyYm9zZT1UUlVFKSAKCiMgSWRlbnRpZmllZCAzMTIxIGJpbWVyYXMgb3V0IG9mIDM1MTIgaW5wdXQgc2VxdWVuY2VzLgoKIyB0aG91Z2ggd2UgZ290IGEgbG90IG9mIHVuaXF1ZSBzZXF1ZW5jZXMsIHdlIGRvbid0IGtub3cgaWYgdGhleSBoZWxkIGEgbG90IGluIHRlcm1zIG9mIGFidW5kYW5jZSwgdGhpcyBpcyBvbmUgcXVpY2sgd2F5IHRvIGxvb2sgYXQgdGhhdApzdW0oc2VxdGFiLm5vY2hpbSkvc3VtKHNlcXRhYikgCiMgMC45NDU3NDU1CiMgZ29vZCwgd2UgYmFyZWx5IGxvc3QgYW55IGluIHRlcm1zIG9mIGFidW5kYW5jZS4gVGhhdCBtZWFucyB0aGUgY2hpbWVyYXMgd2VyZSB2ZXJ5IGxvdyBhYnVuZGFuY2UgQVNWcwoKZGltKHNlcXRhYi5ub2NoaW0pICMgNTUgc2FtcGxlcywgMzkxIHVuaXF1ZSBBU1ZzIHJlbWFpbgoKYGBgCgpCYWNrdXAgYWdhaW4gc2luY2UgdGhpcyBzdGVwIGFib3ZlIHRha2VzIGF3aGlsZQpTYXZlIHdvcmtzcGFjZSB1cCB0byB0aGlzIHBvaW50LgpgYGB7cn0Kc2F2ZS5pbWFnZShmaWxlID0gInVwdG9fY2hpbWVyYS5SRGF0YSIpCmBgYAoKCiMjIyBTdW1tYXJ5IG9mIHJlYWQgY291bnRzIHRocm91Z2ggdGhlIHBpcGVsaW5lCmBgYHtyfQojIHNldCBmdW5jdGlvbiBmcm9tIEhhcHB5IEJlbGx5CmdldE4gPC0gZnVuY3Rpb24oeCkgc3VtKGdldFVuaXF1ZXMoeCkpCgojIG1ha2luZyBhIGxpdHRsZSB0YWJsZQpzdW1tYXJ5X3RhYiA8LSBkYXRhLmZyYW1lKHJvdy5uYW1lcz1zYW1wbGVzLCBkYWRhMl9pbnB1dD1maWx0ZXJlZF9vdXRbLDFdLAogICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcmVkPWZpbHRlcmVkX291dFssMl0sIGRhZGFfZj1zYXBwbHkoZGFkYV9mb3J3YXJkLCBnZXROKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBkYWRhX3I9c2FwcGx5KGRhZGFfcmV2ZXJzZSwgZ2V0TiksIG1lcmdlZD1zYXBwbHkobWVyZ2VkX2FtcGxpY29ucywgZ2V0TiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgbm9uY2hpbT1yb3dTdW1zKHNlcXRhYi5ub2NoaW0pLAogICAgICAgICAgICAgICAgICAgICAgICAgIGZpbmFsX3BlcmNfcmVhZHNfcmV0YWluZWQ9cm91bmQocm93U3VtcyhzZXF0YWIubm9jaGltKS9maWx0ZXJlZF9vdXRbLDFdKjEwMCwgMSkpCgpzdW1tYXJ5X3RhYgpgYGAKCkluIHRoZSBlbmQsIHdlIHJldGFpbmVkIGFib3QgODAtOTUlIG9mIGlucHV0IHJlYWRzIGFmdGVyIHRoZSBmaWx0ZXJpbmcgc3RlcHMuIChFeGNlcHQgMyBzYW1wbGVzIHRoYXQgZGlkbid0IG1lcmdlIGF0IGFsbC4pCgoKTmV4dCwgZm9sbG93IHNvbWUgY29kZSBmcm9tIFtTdG9lY2tsZSBldCBhbC5dKGh0dHBzOi8vam91cm5hbHMucGxvcy5vcmcvcGxvc29uZS9hcnRpY2xlP2lkPTEwLjEzNzEvam91cm5hbC5wb25lLjAxNzUxODYjc2VjMDE0KSBmb3IgbWFraW5nIGB3cml0ZUZhc3RhYCBmdW5jdGlvbiBhbmQgZXhwb3J0aW5nIHRoZSAiT1RVIiB0YWJsZSBhbmQgZmFzdGEgZmlsZS4gIAooTm90ZSB0aGlzIGlzIG5vdCBhdCAiT1RVIiB0YWJsZSBiZWNhdXNlIHRoZXNlIGFyZSBBU1ZzKQoKYGBge3J9CiMgQ29uc3RydWN0ICBzZXF1ZW5jZSB0YWJsZQp0YWJsZShuY2hhcihjb2xuYW1lcyhzZXF0YWIubm9jaGltKSkpCmxlbmd0aCh1bmlxdWUoc3Vic3RyKGNvbG5hbWVzKHNlcXRhYi5ub2NoaW0pLCAxLCAxMDApKSkKZGltKHNlcXRhYi5ub2NoaW0pCgojIE1ha2UgIm90dV90YWJsZSIKc2VxcyA8LSBjb2xuYW1lcyhzZXF0YWIubm9jaGltKQpvdGFiIDwtIG90dV90YWJsZShzZXF0YWIubm9jaGltLCB0YXhhX2FyZV9yb3dzPUZBTFNFKQpjb2xuYW1lcyhvdGFiKSA8LSBwYXN0ZTAoIlNlcV8iLCBzZXEobmNvbChvdGFiKSkpCiNXcml0ZSBmYXN0YXMgdG8gdGVzdCBmaWxlCndyaXRlRmFzdGEgPC0gZnVuY3Rpb24oc2Vxcywgb3V0cHV0KSB7CgogIHNlcXNvdXQgPC0gbWFwcGx5KCBmdW5jdGlvbihpZHgsIHNlcXVlbmNlKSBwYXN0ZTAoIj5TZXFfIixpZHgsIlxuIixzZXF1ZW5jZSwiXG4iKSwKICAgICAgICAgICAgICAgICAgICAgc2VxKGxlbmd0aChzZXFzKSksCiAgICAgICAgICAgICAgICAgICAgIHNlcXMpCiAgd3JpdGUocGFzdGUwKHNlcXNvdXQpLCBmaWxlID0gb3V0cHV0LCBzZXAgPSAiIikKCn0KCnNlcXNfZm9yX2JsYXN0IDwtIEROQVN0cmluZ1NldChzZXFzKQpuYW1lcyhzZXFzX2Zvcl9ibGFzdCkgPC0gIHNhcHBseShzZXEobGVuZ3RoKHNlcXMpKSxmdW5jdGlvbih4KSB7cGFzdGUwKCJTZXFfIix4KX0pCgojIFdyaXRlIHRoZSBmYXN0YSBzZXF1ZW5jZXMgYW5kIHRoZSBPVFUgdGFibGUKd3JpdGVGYXN0YShzZXFzLCAicmVzdWx0cy90YXhfc2VxdWVuY2VzLmZhc3RhIikKd3JpdGUudGFibGUob3RhYiwgIGZpbGU9InJlc3VsdHMvb3R1dGFibGUuY3N2IiwgY29sLm5hbWVzID0gTkEpCmBgYAoKCgoKIyMgQW5ub3RhdGlvbgoKSSBhbSB1c2luZyBibGFzdG4gZnVuY3Rpb24gaW4gdGVybWluYWwsIHdoaWNoIGNhbiBiZSBpbnN0YWxsZWQgd2l0aCB0aGUgYmxhc3QgcGFja2FnZSBmcm9tIE5DQkkgdXNpbmcgY29uZGEgYGNvbmRhIGluc3RhbGwgYmxhc3RgLiBJIGFscmVhZHkgaGFkIGl0IGluc3RhbGxlZCBidXQgd2FzIGhhdmluZyB0cm91YmxlIGJjIHYyLjkgZG9lcyBub3Qgd29yayBvbiByZW1vdGUgaG9zdCAoYWNjb3JkaW5nIHRvIFt0aGlzXShodHRwczovL3d3dy5iaW9zdGFycy5vcmcvcC80MDA4NDEvKSkuIFNvIEkgZG93bmdyYWRlZCBteSBibGFzdCB0byB2Mi42IHdpdGggYGNvbmRhIGluc3RhbGwgLWMgYmlvY29uZGEvbGFiZWwvY2YyMDE5MDEgYmxhc3RgCgoKIyMjIyBSdW4gYmxhc3RuIG9uIHJlbW90ZSBOQ0JJIHNlcnZlcgpVc2UgLW1heF90YXJnZXRfc2VxcyBvZiAxIGZvciBub3cuIE9ubHkgcHV0cyB0b3AgaGl0IGluIHRoZSBmaWxlCmBgYHtiYXNofQpibGFzdG4gLXF1ZXJ5IHJlc3VsdHMvdGF4X3NlcXVlbmNlcy5mYXN0YSBcCi1kYiBudCBcCi1vdXQgcmVzdWx0cy90YXhfc2VxdWVuY2VzX2JsYXN0LnR4dCBcCi1yZW1vdGUgXAotbWF4X3RhcmdldF9zZXFzIDEgXAotb3V0Zm10ICI2IHFzZXFpZCBzc2VxaWQgcGlkZW50IGxlbmd0aCBtaXNtYXRjaCBldmFsdWUgYml0c2NvcmUgc3RheGlkcyBzdGl0bGUiIApgYGAKClRoZSBoZWFkaW5ncyBvZiB0aGUgdGFibGUgYXJlIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVycyBmcm9tIE5DQkk6ICAKLSBzZXFpZDoJIHF1ZXJ5IChlLmcuLCB1bmtub3duIGdlbmUpIHNlcXVlbmNlIGlkICAKLSBzc2VxaWQ6CSBzdWJqZWN0IChlLmcuLCByZWZlcmVuY2UgZ2Vub21lKSBzZXF1ZW5jZSBpZCAgCi0gcGlkZW50OgkgcGVyY2VudGFnZSBvZiBpZGVudGljYWwgbWF0Y2hlcyAgCi0gbGVuZ3RoOgkgYWxpZ25tZW50IGxlbmd0aCAoc2VxdWVuY2Ugb3ZlcmxhcCkgIAotIG1pc21hdGNoOgkgbnVtYmVyIG9mIG1pc21hdGNoZXMgIAotIGV2YWx1ZToJIGV4cGVjdCB2YWx1ZSAgCi0gYml0c2NvcmU6CSBiaXQgc2NvcmUgIAotIHN0YXhpZHM6ICAgU3ViamVjdCBUYXhvbm9teSBJRChzKSwgc2VwYXJhdGVkIGJ5IGEgJzsnICAKLSBzdGl0bGU6ICAgICAgU3ViamVjdCBUaXRsZSAgCgoKCiMjIyMgTGluayBBY2Nlc3Npb24gTnVtYmVycyB3aXRoIFRheG9ub215IGZyb20gTkNCSQpJIGZvdW5kIGFuIFIgcGFja2FnZSBmb3IgdGhpcyBjYWxsZWQgW1RheG9ub21penJdKGh0dHBzOi8vZ2l0aHViLmNvbS9zaGVycmlsbG1peC90YXhvbm9taXpyKS4KCgpEb3dubG9hZCBhbGwgdGF4IGFzc2lnbm1lbnRzIGluIE5DQkkgYW5kIG1ha2UgU1FMaXRlIGRhdGFiYXNlICh0aGlzIHRha2VzIHNldmVyYWwgaG91cnMpLiBTdG9yZSB0aGlzIGZpbGUgaW4gYSBzaGFyZWQgcGxhY2UgaW5zdGVhZCBvZiBkb3dubG9hZGluZyBpdCBlYWNoIHRpbWUgKGJlY2F1c2UgaXQncyBodWdlKQoKYGBge3J9CnByZXBhcmVEYXRhYmFzZSgnZGF0YWJhc2VzL2FjY2Vzc2lvblRheGEuc3FsJykKYGBgCgoKClJlYWQgYmxhc3QgcmVzdWx0cyBmcm9tICB0YWJsZSBpbnRvIFIgYW5kIGdpdmUgaXQgaGVhZGVycwpgYGB7cn0KYmxhc3RSZXN1bHRzPC1yZWFkX3RhYmxlMigncmVzdWx0cy90YXhfc2VxdWVuY2VzX2JsYXN0LnR4dCcsIGNvbF9uYW1lcyA9IEZBTFNFKQpibGFzdFJlc3VsdHMgPC0gdW5pdGUoYmxhc3RSZXN1bHRzLCBSZWZfU2VxX3RpdGxlLCBYOSwgWDEwLCBYMTEsIFgxMiwgWDEzLCBYMTQsIFgxNSwgc2VwPSdfJykKY29sbmFtZXMoYmxhc3RSZXN1bHRzKSA8LSBjKCJBU1ZfSUQiLCAicmVmX3NlcV9JRCIsICJQSUQiLCAiYWxubXRfbGVuIiwgIm1pc21hdGNoIiwgImV2YWwiLCAiYnNjb3JlIiwgIlJlZlNlcV9UYXhfSUQiLCAiUmVmX1NlcV90aXRsZSIpCmJsYXN0UmVzdWx0cwpgYGAKKk5PVEUqIGluIHRoZSBhYm92ZSwgCgotIFNlcXMgMjM0LCAzNjcsIDM3MCwgMzcxLCAzNzQsIDM3NSwgMzgxLCAzODIsIDM4NywgMzg4IGFyZSBub3QgbGlzdGVkIGJlY2F1c2UgdGhlcmUgd2FzIG5vIHNpZ25pZmljYW50IHNpbWlsYXJpdHkgZm91bmQgdG8gYW55dGhpbmcgaW4gYmxhc3QKLSBTZXEgMzQzIGFwcGVhcnMgaW4gbGlzdCBtdWx0aXBsZSB0aW1lcyBldmVuIHRob3VnaCB0aGVyZSB3YXMgb25seSBvbmUgZW50cnkgaW4gZmFzdGEgZmlsZS4gTm90IHN1cmUgaWYgdGhpcyB3YXMgYnVnIGluIHRoZSBgYmFzdG5gIHByb2dyYW0gd2hlcmUgaXQgcmVwb3J0ZWQgbW9yZSB0aGFuIG9uZSB0b3AgaGl0LiBCdXQgSSBjaGVja2VkIHRoZXNlIGFuZCBtb3N0IGFyZSBkdXBsaWNhdGVzIHdoaWNoIGNhbiBiYXNpY2FsbHkgYmUgaWdub3JlZC4gVGhlcmUgaXMgb25lIHdpdGggdGhlIHNhbWUgaGl0IGJ1dCBsb3dlciBQSUQsIGRlbGV0ZSB0aGlzIG9uZSB0b28uIFRoZXkgYXJlIGFsbCBjb250YW1pbmF0aW9uIChGZWxpc19jYXR1cyEpIGFueXdheSEKCkNsZWFuIHVwIGJsYXN0UmVzdWx0cyB0YWJsZToKYGBge3J9CmJsYXN0UmVzdWx0cyA8LSAgdW5pcXVlKGJsYXN0UmVzdWx0cykKcm93dG9yZW1vdmUgPC0gZmlsdGVyKGJsYXN0UmVzdWx0cywgQVNWX0lEID09ICdTZXFfMzQzJyAmIFBJRCA8PSA5OS45KQpibGFzdFJlc3VsdHMgPC0gYW50aV9qb2luKGJsYXN0UmVzdWx0cyxyb3d0b3JlbW92ZSkKYmxhc3RSZXN1bHRzCmBgYAoKCmFuZCBncmFiIGFjY2VzaW9uIG51bWJlcnMKYGBge3J9CiNncmFiIHRoZSBpbmRleCBvZiB0aGUgYWNjZXNzaW9uIG51bm1iZXJzCiMgYWNjIG51bWJlcnMgYXJlIDR0aCB8LXNlcGFyYXRlZCBmaWVsZCBmcm9tIHRoZSByZWZlcmVuY2UgbmFtZSBpbiB0aGUgc2Vjb25kIGNvbHVtbgphY2NfbWF0cml4IDwtIHVubGlzdChzdHJfc3BsaXQoYXMuY2hhcmFjdGVyKGJsYXN0UmVzdWx0c1ssMl0pLCdcXHwnKSkKaW5kIDwtIHNlcShmcm9tID0gNCwgdG8gPSBsZW5ndGgoYWNjX21hdHJpeCksIGJ5ID0gNCkKCmFjY2Vzc2lvbnMgPC0gYWNjX21hdHJpeFtpbmRdCmFjY2Vzc2lvbnMKYGBgCgoKR2V0IHRheG9ub215IElEIGZvciBlYWNoIGFjY2VzaW9uIG51bWJlcgpgYGB7cn0KdGF4YUlkPC1hY2Nlc3Npb25Ub1RheGEoYWNjZXNzaW9ucywiZGF0YWJhc2VzL2FjY2Vzc2lvblRheGEuc3FsIikKdGF4YUlkWzE6MTBdCmBgYAoKCgpBbmQgZ2V0IHRheG9ub215IGZvciB0aG9zZSB0YXggSURzCmBgYHtyfQp0YXhvbm9teV90YWJsZSA8LSBnZXRUYXhvbm9teSh0YXhhSWQsJ2RhdGFiYXNlcy9hY2Nlc3Npb25UYXhhLnNxbCcpCgp0YXhvbm9teV90YWJsZSA8LSBjYmluZCh0YXhvbm9teV90YWJsZSwgcm93bmFtZXModGF4b25vbXlfdGFibGUpKQpjb2xuYW1lcyh0YXhvbm9teV90YWJsZSlbZGltKHRheG9ub215X3RhYmxlKVsyXV0gPC0gIlJlZlNlcV9UYXhfSUQiCnJvd25hbWVzKHRheG9ub215X3RhYmxlKSA8LSBOVUxMCgp0YXhvbm9teV90YWJsZSA8LSBhcy5kYXRhLmZyYW1lKHRheG9ub215X3RhYmxlKQp0YXhvbm9teV90YWJsZQpgYGAKCgoKQXBwZW5kIHRheG9ub215IHRhYmxlIHRvIGJsYXN0IHJlc3VsdHMgdGFibGUgYW5kIGV4cG9ydCBhcyBvbmUgZmlsZQpgYGB7cn0KdGF4X3NlcXVlbmNlc19ibGFzdF90YXhvbm9teSA8LSBjYmluZChibGFzdFJlc3VsdHMsIHRheG9ub215X3RhYmxlKQp3cml0ZS50YWJsZSh0YXhfc2VxdWVuY2VzX2JsYXN0X3RheG9ub215LCBmaWxlID0gInJlc3VsdHMvdGF4X3NlcXVlbmNlc19ibGFzdF90YXhvbm9teS5jc3YiLCBzZXAgPSAiLCIsIGNvbC5uYW1lcz1OQSkKCmBgYAoKCgoKCgo=