Global DNA Methylation via Genome Skimming from Nanopore Long-read
Data
Purpose: Most methods to characterize a global value for
5’methylcytosine are either expensive, error-prone, or rely upon
bisulfite conversion which causes measurement challenges. The nanopore
instrument can natively call 5’methylation along with other
modifications. Here I present a method of determining a single
percentage value of any genome’s 5’mCpG vs. unmodified CpG sites (i.e.
global methylation) by sequencing genomes at very low coverage (i.e.
skimming) at ranges from 0.1X to 0.0001X. Separately, I extracted high
copy transposon families and assessed their methylation
independently.
Coverage Depth Estimation
Genome skimming is defined as shallow sequencing down to 0.05X
coverage of a genome (0.05X). For a typical 3 Gb mammal genome this
equates to 150 Mb of reads. Here I show that for global DNA methylation,
even 0.01X coverage of a primate genome results in an error of less than
1% difference from the true value. The sampling was bootstrapped 10
times to determine error.
Subsampling Methods
There are approximately 29 million CpG sites in the human genome and
to get sub-percentile accuracy of their methylation, a measure of
methylation at 300,000 sites will suffice. Since the genome is
non-uniform with respect to CpG density and location, converting this
number into base coverage is not uniform. Here I chose to quantify
methylation on number of total bases sequenced since that is how a
sequencer counts reads and how the user determines how much is enough
DNA sequenced for skimming or assembly or other purposes. To estimate
whether a certain level of coverage will contain enough CpG sites for
accurate methylation, it was necessary to empirically sample the reads,
quantify methylation, and repeat the subsampling by bootstrapping to be
confident that a global value is correct.
Here I used the chimp genome as one of 4 primate genomes I sequenced
to high depth. The full dataset averages 77.8883% methylation over
11.12X coverage (33,921,056,788 bp) in the mapped bam file. Subsamples
were taken at 10X, 1X, 0.1X, 0.01X and 0.001X coverage and with 10
random subsets drawn, and methylation calculated for these 10 bootstrap
replicates.
10X = 30504547470 bp = 10 * (33921056788 /
11.12). Therefore 0.899 * total bases) = 10X. Sample 10X coverage 10
times.
Determine coverage.
mosdepth -t 32 --fast-mode rhesus.barcode02 chimp.modmapped.bam
Downsample the bam file with replicates.
for i in {1..10}; do samtools view -@ 32 -b --subsample 0.899 --subsample-seed $RANDOM total-chimp.modmapped.bam > bootstrap10X/total-chimp.modmapped.bam.10X.$i.bam ; samtools index -@ 32 bootstrap10X/total-chimp.modmapped.bam.10X.$i.bam ; done
for i in bootstrap10X/*.bam; do modbam2bed -m 5mC -t 32 -e -p $i --aggregate --cpg panTro6.fa $i > $i.bed; done
- Use awk to average methylation.
for i in bootstrap10X/*.acc.bed ; do awk '$5>0 {can+=$12; mod+=$13} END{print 100*(mod/(can+mod))}' $i >> bootstrapped.10X.txt ; done
- 1X = 30504547470 bp = 1 * (33921056788 / 11.12).
Therefore 0.090 * total bases) = 1X. Sample 1X coverage 10 times.
for i in {1..10}; do samtools view -@ 32 -b --subsample 0.090 --subsample-seed $RANDOM total-chimp.modmapped.bam > bootstrap1X/total-chimp.modmapped.bam.1X.$i.bam ; samtools index -@ 32 bootstrap1X/total-chimp.modmapped.bam.1X.$i.bam ; done
- Repeat steps as in 10X for 1X .. 0.0001X
Biological and Technical Replication at Low Coverage
I used 5 biological replicates of 3 tissues from mouse to determine
intersample precision, i.e., reproducibility. I also included replicates
of high and low methylated control DNA.
Control Synthesis Methods
Low Methylation control DNA
This input was derived from mouse genomic DNA extracted from liver
tissue using a Zymo Quick DNA miniprep plus kit (cat. D4068). A small
amount of DNA was used as input to a Qiagen Repli-g whole genome
amplification kit (cat. 150023) according to instructions. The resulting
DNA amplified DNA is unmethylated, however the sample still contains a
small amount of the original mouse derived natively methylated DNA,
hence the name “low methylation control” rather than “0%”. The DNA was
then library prepped with ONT’s LSK-114 kit and sequenced on an R10.4.1
flow cell at 400 bps. Bases were called by Guppy v6.3.9 with the
dna_r9.4.1_450bps_modbases_5mc_cg_sup.cfg
model.
High Methylation Control DNA
This sample was derived from mouse genomic DNA extracted from liver
tissue using a Zymo Quick DNA miniprep plus kit (D4068). The DNA was
subjected to CpG methylase treatment to induce high levels of 5’mCpG
using a Zymo CpG methylase kit (E2010) according to instructions. Due to
enzymatic limitations, the kit will not achieve perfect 100%
methylation, hence the name “high methylation control”. The DNA was then
library prepped with ONT’s LSK-114 kit and sequenced on an R10.4.1 flow
cell at 400 bps. Bases were called by Guppy v6.3.9 with the
dna_r9.4.1_450bps_modbases_5mc_cg_sup.cfg
model.
Skimming Pipeline
Pipeline for analysis is as follows:
- Optional. If you did not choose to call live during the run with SUP
accuracy and DNA methylation detection turned on, or if you want to use
a different model, you can re-basecall with guppy post-hoc using the
original fast5 files. Guppy v6.3.8 was used here. NVIDIA GPU with CUDA
cores is highly recommended to speed up base calling.
# Actual run command
/opt/ont/guppy/bin/guppy_basecaller -i /mnt/synology/ont-sequencing-run-storage/Skimming-methylation/mouse-skim-hip-cortex-testes-reps/no_sample/20230116_1229_MN34646_FAV16314_694e180d/fast5_pass/ -s . -c dna_r10.4.1_e8.2_400bps_modbases_5mc_cg_sup.cfg --bam_out -x cuda:all --recursive --trim_adapters --barcode_kits SQK-RBK114-24 --do_read_splitting
# Basic run command
opt/ont/guppy/bin/guppy_basecaller -i /path/to/fast5_pass/ -s /output/path -c dna_r9.4.1_450bps_modbases_5mc_cg_sup.cfg --bam_out -x cuda:0
- Concatenate all the unmapped bam files containing modified base
information.
samtools cat -o total.bam *input.bam
- Map unmapped Bams to a reference. Map, convert and sort all in one
command. The
-T Mm,Ml
flag carries over the modifications
from bam to fastq. In newer bams the flags are
-T MM, ML
.
The -y
flag copies input fastq commands to output.
# Convert unmapped bam to fastq, pipe output to minimap2, pipe output to samtools for sorting and indexing without writing intermediate files to disk. (Need sufficient RAM)
samtools fastq -T MM,ML total-chimp.bam | minimap2 -y -ax map-ont panTro6.fa - -t 32 | samtools view -u - | samtools sort -@ 32 -o total-chimp.modmapped.bam; samtools index total-chimp.modmapped.bam -@ 32
- Convert to bed format. The
-d
flag limits depth to save
memory. The aggregate flag adds together bases on either strand and
creates and aggregate.bed file. The -cpg
flag limits to cpg
sites.
# Convert mapped bam file reads to .bed format to quantiate methylation by position.
modbam2bed -m 5mC -t 32 -e -p total-chimp.modmapped --aggregate --cpg panTro6.fa total-chimp.modmapped.bam > total-chimp.modmapped.bed
- Determine methylation percentage by counting all canonical (5CpG)
and modified (5mCpG) sites and dividing. Use the
mod-counts.cpg.acc.bed
file as it aggregates cytosine calls
from both palindromic sides of the opposite strands.
# For every CpG dinucleotide, add up the count of canonical unmodified cytosines (column 12) and the 5'methylated cytosines (column 13) at every mapped position. Divide the number of modified cytosines by the total number of cytosines.
# Note that modbam2bed produces a stranded and a combined "acc" file. The combined file adds together the opposite strand cytosine counts together since they represent the same palindromic cytosine. This increases power.
awk '$5>0 {can+=$12; mod+=$13} END{print 100*(mod/(can+mod))}' total-chimp.modmapped.cpg.acc.bed
Vertebrate Genomes
DNA from species representing the full vertebrate radiation were
collected through commercial suppliers and gifts from collaborators. All
samples were derived from muscle tissue and extracted using Zymo Quick
DNA Plus miniprep kits. Genomes were downloaded from Genbank with the
latest reference available for each species.
- Merge and copy the bams for each barcode.
samtools cat -o total.bam *input.bam
- Map to genome.
samtools fastq -T MM,ML total-chimp.bam | minimap2 -y -ax map-ont reference.fa - -t 32 | samtools view -u - | samtools sort -@ 32 -o animal.modmapped.bam; samtools index animal.modmapped.bam -@ 32
Use --split-prefix
if the genome is too large for
minimap2.
Convert to bed file.
modbam2bed -m 5mC -t 32 -e -p reference.modmapped --aggregate --cpg reference.fa animal.modmapped.bam > animal.modmapped.bed
- Calculate Methylation.
awk '$5>0 {can+=$12; mod+=$13} END{print 100*(mod/(can+mod))}' animal.modmapped.cpg.acc.bed
Calculate mapping efficiency.
Basic stats
bam stats --in animal.modmapped.bam --basic
Average quality
samtools stat animal.modmapped.bam | grep "average quality"
Copy to Excel and Graphpad for graphics.
Transposon Analysis
Primate LINE1 and Alu elements
These 2 elements alone account for ∼30% of the genome sequence and
are the most abundant transposable elements in humans (Lander et
al.)
All Repeats Genome-wide
0.8604273% Average methylation across all repeat types
Transposon Pipeline
Chimpanzee LINE1 and Alus Specifically
- Table Browser -> Repeats -> Table “rmsk” -> All fields from
selected table -> repeat-table.tsv
- Get all L1’s from Repeats tsv and print into bed format.
awk '$13=="L1" {print $6 "\t" $7 "\t"$8 "\t"$13}' panTro6-repeat-table.tsv > repeatL1.bed
- Intersect all CpG sites that fall within L1 coordinates and average
their methylation
bedtools intersect -a total-chimp.modmapped.cpg.acc.bed -b repeatL1.bed > repeatL1.cpgintersect.bed
awk '$5>0 {can+=$12; mod+=$13} END{print 100*(mod/(can+mod))}' repeatL1.cpgintersect.bed
84.7069% L1 methylation in the 11.12X coverage chimp
genome.
92.4767% Alu in the chimp genome.
Downsample the reads to 0.001X (3 Mb) and plot 10 bootstrap
replicates.
Use the pre-made downsampled cpg.acc.bed files in the bootstrap
directories
Make intersection files for each bootstrap.
for i in bootstrap10X/*.cpg.acc.bed ; do bedtools intersect -a $i -b repeatL1.bed > $i.cpgintersect.L1.bed ; done
Use awk to print the methylation to a file.
for i in bootstrap10X/*.cpgintersect.bed ; do awk '$5>0 {can+=$12; mod+=$13} END{print 100*(mod/(can+mod))}' $i >> L1bootstrapped.10X.txt; done
Repeat for each read level.
Repeat for Alus.
Chimpazee All Repeats
- Chimp. Make an intersection of all repeats and cpg sites with -wa
and -wb to combine beds.
bedtools intersect -a total-chimp.modmapped.cpg.acc.bed -b repeatAll.bed -wa -wb > repeatAll.cpgintersect.bed
- Mouse. Repeat as with Chimp, but use all 5 mouse hippocampus samples
that share common rows for methylation data.
Mouse Repeats
Mouse All Repeats
Download all mm39 repeats from USCS Table Browser.
Convert to bed format.
awk '{print $6 "\t" $7 "\t"$8 "\t"$13}' mm39-repeat-table.tsv > mm39-repeat-table.bed
# Remove the header line manually.
- Make an intersection of all repeats and cpg sites with -wa and -wb
to combine beds. Loop for all files.
for i in *.cpg.acc.bed ; do bedtools intersect -a $i -b mm39-repeat-table.bed -wa -wb > $i.cpgintersect.bed ; done
- Use R program
repeats-rmd.Rmd
(copied below) to
summarize methylation on a per-repeat basis and copy results into
Graphpad for analysis and visualization.
```r
# Import necessary libraries
library(tidyverse)
# Read in tsv filea
data <- read_tsv("mouse-hip.modmapped.cpg.acc.bed.cpgintersect.bed", col_names=FALSE)
# Calculate sum of column 13 divided by the sum of column 12 plus column 13
data$result <- data$X13 / (data$X12 + data$X13)
data <- data %>% drop_na()
data$result
# Separate out by factors in column 18
data_by_factor <- data %>% group_by(X18) %>% summarize(result = mean(result))
data_by_factor
write.table(data_by_factor , file = "mouse-hip.modmapped.cpg.acc.bed.cpgintersect.bed.databyfactor.tsv")
# Make a bar plot of the result
ggplot(data_by_factor, aes(x = X18, y = result)) + geom_bar(stat = "identity") +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5))
```
LS0tCnRpdGxlOiAiR2Vub21lIFNraW1taW5nIgphdXRob3I6ICJDaHJpcyBGYXVsayIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiB0aWJibGUKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKICBtYXJrZG93bjogCiAgICB3cmFwOiA3MgotLS0KCiMgR2xvYmFsIEROQSBNZXRoeWxhdGlvbiB2aWEgR2Vub21lIFNraW1taW5nIGZyb20gTmFub3BvcmUgTG9uZy1yZWFkIERhdGEKClB1cnBvc2U6IE1vc3QgbWV0aG9kcyB0byBjaGFyYWN0ZXJpemUgYSBnbG9iYWwgdmFsdWUgZm9yCjUnbWV0aHlsY3l0b3NpbmUgYXJlIGVpdGhlciBleHBlbnNpdmUsIGVycm9yLXByb25lLCBvciByZWx5IHVwb24KYmlzdWxmaXRlIGNvbnZlcnNpb24gd2hpY2ggY2F1c2VzIG1lYXN1cmVtZW50IGNoYWxsZW5nZXMuIFRoZSBuYW5vcG9yZQppbnN0cnVtZW50IGNhbiBuYXRpdmVseSBjYWxsIDUnbWV0aHlsYXRpb24gYWxvbmcgd2l0aCBvdGhlcgptb2RpZmljYXRpb25zLiBIZXJlIEkgcHJlc2VudCBhIG1ldGhvZCBvZiBkZXRlcm1pbmluZyBhIHNpbmdsZQpwZXJjZW50YWdlIHZhbHVlIG9mIGFueSBnZW5vbWUncyA1J21DcEcgdnMuIHVubW9kaWZpZWQgQ3BHIHNpdGVzIChpLmUuCmdsb2JhbCBtZXRoeWxhdGlvbikgYnkgc2VxdWVuY2luZyBnZW5vbWVzIGF0IHZlcnkgbG93IGNvdmVyYWdlIChpLmUuCnNraW1taW5nKSBhdCByYW5nZXMgZnJvbSAwLjFYIHRvIDAuMDAwMVguIFNlcGFyYXRlbHksIEkgZXh0cmFjdGVkIGhpZ2gKY29weSB0cmFuc3Bvc29uIGZhbWlsaWVzIGFuZCBhc3Nlc3NlZCB0aGVpciBtZXRoeWxhdGlvbiBpbmRlcGVuZGVudGx5LgoKIyMgQ292ZXJhZ2UgRGVwdGggRXN0aW1hdGlvbgoKR2Vub21lIHNraW1taW5nIGlzIGRlZmluZWQgYXMgc2hhbGxvdyBzZXF1ZW5jaW5nIGRvd24gdG8gMC4wNVggY292ZXJhZ2UKb2YgYSBnZW5vbWUgKDAuMDVYKS4gRm9yIGEgdHlwaWNhbCAzIEdiIG1hbW1hbCBnZW5vbWUgdGhpcyBlcXVhdGVzIHRvCjE1MCBNYiBvZiByZWFkcy4gSGVyZSBJIHNob3cgdGhhdCBmb3IgZ2xvYmFsIEROQSBtZXRoeWxhdGlvbiwgZXZlbiAwLjAxWApjb3ZlcmFnZSBvZiBhIHByaW1hdGUgZ2Vub21lIHJlc3VsdHMgaW4gYW4gZXJyb3Igb2YgbGVzcyB0aGFuIDElCmRpZmZlcmVuY2UgZnJvbSB0aGUgdHJ1ZSB2YWx1ZS4gVGhlIHNhbXBsaW5nIHdhcyBib290c3RyYXBwZWQgMTAgdGltZXMKdG8gZGV0ZXJtaW5lIGVycm9yLgoKIyMjIFN1YnNhbXBsaW5nIE1ldGhvZHMKClRoZXJlIGFyZSBhcHByb3hpbWF0ZWx5IDI5IG1pbGxpb24gQ3BHIHNpdGVzIGluIHRoZSBodW1hbiBnZW5vbWUgYW5kIHRvCmdldCBzdWItcGVyY2VudGlsZSBhY2N1cmFjeSBvZiB0aGVpciBtZXRoeWxhdGlvbiwgYSBtZWFzdXJlIG9mCm1ldGh5bGF0aW9uIGF0IDMwMCwwMDAgc2l0ZXMgd2lsbCBzdWZmaWNlLiBTaW5jZSB0aGUgZ2Vub21lIGlzCm5vbi11bmlmb3JtIHdpdGggcmVzcGVjdCB0byBDcEcgZGVuc2l0eSBhbmQgbG9jYXRpb24sIGNvbnZlcnRpbmcgdGhpcwpudW1iZXIgaW50byBiYXNlIGNvdmVyYWdlIGlzIG5vdCB1bmlmb3JtLiBIZXJlIEkgY2hvc2UgdG8gcXVhbnRpZnkKbWV0aHlsYXRpb24gb24gbnVtYmVyIG9mIHRvdGFsIGJhc2VzIHNlcXVlbmNlZCBzaW5jZSB0aGF0IGlzIGhvdyBhCnNlcXVlbmNlciBjb3VudHMgcmVhZHMgYW5kIGhvdyB0aGUgdXNlciBkZXRlcm1pbmVzIGhvdyBtdWNoIGlzIGVub3VnaApETkEgc2VxdWVuY2VkIGZvciBza2ltbWluZyBvciBhc3NlbWJseSBvciBvdGhlciBwdXJwb3Nlcy4gVG8gZXN0aW1hdGUKd2hldGhlciBhIGNlcnRhaW4gbGV2ZWwgb2YgY292ZXJhZ2Ugd2lsbCBjb250YWluIGVub3VnaCBDcEcgc2l0ZXMgZm9yCmFjY3VyYXRlIG1ldGh5bGF0aW9uLCBpdCB3YXMgbmVjZXNzYXJ5IHRvIGVtcGlyaWNhbGx5IHNhbXBsZSB0aGUgcmVhZHMsCnF1YW50aWZ5IG1ldGh5bGF0aW9uLCBhbmQgcmVwZWF0IHRoZSBzdWJzYW1wbGluZyBieSBib290c3RyYXBwaW5nIHRvIGJlCmNvbmZpZGVudCB0aGF0IGEgZ2xvYmFsIHZhbHVlIGlzIGNvcnJlY3QuCgpIZXJlIEkgdXNlZCB0aGUgY2hpbXAgZ2Vub21lIGFzIG9uZSBvZiA0IHByaW1hdGUgZ2Vub21lcyBJIHNlcXVlbmNlZCB0bwpoaWdoIGRlcHRoLiBUaGUgZnVsbCBkYXRhc2V0IGF2ZXJhZ2VzIDc3Ljg4ODMlIG1ldGh5bGF0aW9uIG92ZXIgMTEuMTJYCmNvdmVyYWdlICgzMyw5MjEsMDU2LDc4OCBicCkgaW4gdGhlIG1hcHBlZCBiYW0gZmlsZS4gU3Vic2FtcGxlcyB3ZXJlCnRha2VuIGF0IDEwWCwgMVgsIDAuMVgsIDAuMDFYIGFuZCAwLjAwMVggY292ZXJhZ2UgYW5kIHdpdGggMTAgcmFuZG9tCnN1YnNldHMgZHJhd24sIGFuZCBtZXRoeWxhdGlvbiBjYWxjdWxhdGVkIGZvciB0aGVzZSAxMCBib290c3RyYXAKcmVwbGljYXRlcy4KCi0gICAqKjEwWCoqID0gMzA1MDQ1NDc0NzAgYnAgPSAxMCBcKiAoMzM5MjEwNTY3ODggLyAxMS4xMikuIFRoZXJlZm9yZQogICAgMC44OTkgXCogdG90YWwgYmFzZXMpID0gMTBYLiBTYW1wbGUgMTBYIGNvdmVyYWdlIDEwIHRpbWVzLgoKLSAgIERldGVybWluZSBjb3ZlcmFnZS4KICAgIGBtb3NkZXB0aCAtdCAzMiAtLWZhc3QtbW9kZSByaGVzdXMuYmFyY29kZTAyIGNoaW1wLm1vZG1hcHBlZC5iYW1gCgotICAgRG93bnNhbXBsZSB0aGUgYmFtIGZpbGUgd2l0aCByZXBsaWNhdGVzLgoKYGBge2Jhc2h9CmZvciBpIGluIHsxLi4xMH07IGRvIHNhbXRvb2xzIHZpZXcgLUAgMzIgLWIgLS1zdWJzYW1wbGUgMC44OTkgLS1zdWJzYW1wbGUtc2VlZCAkUkFORE9NIHRvdGFsLWNoaW1wLm1vZG1hcHBlZC5iYW0gPiBib290c3RyYXAxMFgvdG90YWwtY2hpbXAubW9kbWFwcGVkLmJhbS4xMFguJGkuYmFtIDsgc2FtdG9vbHMgaW5kZXggLUAgMzIgYm9vdHN0cmFwMTBYL3RvdGFsLWNoaW1wLm1vZG1hcHBlZC5iYW0uMTBYLiRpLmJhbSA7IGRvbmUKYGBgCgotICAgQ29udmVydCBiYW1zIHRvIGJlZC4KCmBgYHtiYXNofQpmb3IgaSBpbiBib290c3RyYXAxMFgvKi5iYW07IGRvIG1vZGJhbTJiZWQgLW0gNW1DIC10IDMyIC1lIC1wICRpIC0tYWdncmVnYXRlIC0tY3BnIHBhblRybzYuZmEgJGkgPiAkaS5iZWQ7IGRvbmUKYGBgCgotICAgVXNlIGF3ayB0byBhdmVyYWdlIG1ldGh5bGF0aW9uLgoKYGBge2Jhc2h9CmZvciBpIGluIGJvb3RzdHJhcDEwWC8qLmFjYy5iZWQgOyBkbyBhd2sgJyQ1PjAge2Nhbis9JDEyOyBtb2QrPSQxM30gRU5Ee3ByaW50IDEwMCoobW9kLyhjYW4rbW9kKSl9JyAkaSA+PiBib290c3RyYXBwZWQuMTBYLnR4dCA7IGRvbmUKYGBgCgotICAgKioxWCoqID0gMzA1MDQ1NDc0NzAgYnAgPSAxIFwqICgzMzkyMTA1Njc4OCAvIDExLjEyKS4gVGhlcmVmb3JlCiAgICAwLjA5MCBcKiB0b3RhbCBiYXNlcykgPSAxWC4gU2FtcGxlIDFYIGNvdmVyYWdlIDEwIHRpbWVzLgoKYGBge2Jhc2h9CmZvciBpIGluIHsxLi4xMH07IGRvIHNhbXRvb2xzIHZpZXcgLUAgMzIgLWIgLS1zdWJzYW1wbGUgMC4wOTAgLS1zdWJzYW1wbGUtc2VlZCAkUkFORE9NIHRvdGFsLWNoaW1wLm1vZG1hcHBlZC5iYW0gPiBib290c3RyYXAxWC90b3RhbC1jaGltcC5tb2RtYXBwZWQuYmFtLjFYLiRpLmJhbSA7IHNhbXRvb2xzIGluZGV4IC1AIDMyIGJvb3RzdHJhcDFYL3RvdGFsLWNoaW1wLm1vZG1hcHBlZC5iYW0uMVguJGkuYmFtIDsgZG9uZQpgYGAKCi0gICBSZXBlYXQgc3RlcHMgYXMgaW4gMTBYIGZvciAxWCAuLiAwLjAwMDFYCgojIyBCaW9sb2dpY2FsIGFuZCBUZWNobmljYWwgUmVwbGljYXRpb24gYXQgTG93IENvdmVyYWdlCgpJIHVzZWQgNSBiaW9sb2dpY2FsIHJlcGxpY2F0ZXMgb2YgMyB0aXNzdWVzIGZyb20gbW91c2UgdG8gZGV0ZXJtaW5lCmludGVyc2FtcGxlIHByZWNpc2lvbiwgaS5lLiwgcmVwcm9kdWNpYmlsaXR5LiBJIGFsc28gaW5jbHVkZWQgcmVwbGljYXRlcwpvZiBoaWdoIGFuZCBsb3cgbWV0aHlsYXRlZCBjb250cm9sIEROQS4KCiMjIyBDb250cm9sIFN5bnRoZXNpcyBNZXRob2RzCgojIyMjIExvdyBNZXRoeWxhdGlvbiBjb250cm9sIEROQQoKVGhpcyBpbnB1dCB3YXMgZGVyaXZlZCBmcm9tIG1vdXNlIGdlbm9taWMgRE5BIGV4dHJhY3RlZCBmcm9tIGxpdmVyCnRpc3N1ZSB1c2luZyBhIFp5bW8gUXVpY2sgRE5BIG1pbmlwcmVwIHBsdXMga2l0IChjYXQuIEQ0MDY4KS4gQSBzbWFsbAphbW91bnQgb2YgRE5BIHdhcyB1c2VkIGFzIGlucHV0IHRvIGEgUWlhZ2VuIFJlcGxpLWcgd2hvbGUgZ2Vub21lCmFtcGxpZmljYXRpb24ga2l0IChjYXQuIDE1MDAyMykgYWNjb3JkaW5nIHRvIGluc3RydWN0aW9ucy4gVGhlIHJlc3VsdGluZwpETkEgYW1wbGlmaWVkIEROQSBpcyB1bm1ldGh5bGF0ZWQsIGhvd2V2ZXIgdGhlIHNhbXBsZSBzdGlsbCBjb250YWlucyBhCnNtYWxsIGFtb3VudCBvZiB0aGUgb3JpZ2luYWwgbW91c2UgZGVyaXZlZCBuYXRpdmVseSBtZXRoeWxhdGVkIEROQSwKaGVuY2UgdGhlIG5hbWUgImxvdyBtZXRoeWxhdGlvbiBjb250cm9sIiByYXRoZXIgdGhhbiAiMCUiLiBUaGUgRE5BIHdhcwp0aGVuIGxpYnJhcnkgcHJlcHBlZCB3aXRoIE9OVCdzIExTSy0xMTQga2l0IGFuZCBzZXF1ZW5jZWQgb24gYW4gUjEwLjQuMQpmbG93IGNlbGwgYXQgNDAwIGJwcy4gQmFzZXMgd2VyZSBjYWxsZWQgYnkgR3VwcHkgdjYuMy45IHdpdGggdGhlCmBkbmFfcjkuNC4xXzQ1MGJwc19tb2RiYXNlc181bWNfY2dfc3VwLmNmZ2AgbW9kZWwuCgojIyMjIEhpZ2ggTWV0aHlsYXRpb24gQ29udHJvbCBETkEKClRoaXMgc2FtcGxlIHdhcyBkZXJpdmVkIGZyb20gbW91c2UgZ2Vub21pYyBETkEgZXh0cmFjdGVkIGZyb20gbGl2ZXIKdGlzc3VlIHVzaW5nIGEgWnltbyBRdWljayBETkEgbWluaXByZXAgcGx1cyBraXQgKEQ0MDY4KS4gVGhlIEROQSB3YXMKc3ViamVjdGVkIHRvIENwRyBtZXRoeWxhc2UgdHJlYXRtZW50IHRvIGluZHVjZSBoaWdoIGxldmVscyBvZiA1J21DcEcKdXNpbmcgYSBaeW1vIENwRyBtZXRoeWxhc2Uga2l0IChFMjAxMCkgYWNjb3JkaW5nIHRvIGluc3RydWN0aW9ucy4gRHVlIHRvCmVuenltYXRpYyBsaW1pdGF0aW9ucywgdGhlIGtpdCB3aWxsIG5vdCBhY2hpZXZlIHBlcmZlY3QgMTAwJQptZXRoeWxhdGlvbiwgaGVuY2UgdGhlIG5hbWUgImhpZ2ggbWV0aHlsYXRpb24gY29udHJvbCIuIFRoZSBETkEgd2FzIHRoZW4KbGlicmFyeSBwcmVwcGVkIHdpdGggT05UJ3MgTFNLLTExNCBraXQgYW5kIHNlcXVlbmNlZCBvbiBhbiBSMTAuNC4xIGZsb3cKY2VsbCBhdCA0MDAgYnBzLiBCYXNlcyB3ZXJlIGNhbGxlZCBieSBHdXBweSB2Ni4zLjkgd2l0aCB0aGUKYGRuYV9yOS40LjFfNDUwYnBzX21vZGJhc2VzXzVtY19jZ19zdXAuY2ZnYCBtb2RlbC4KCiMjIyBTa2ltbWluZyBQaXBlbGluZQoKUGlwZWxpbmUgZm9yIGFuYWx5c2lzIGlzIGFzIGZvbGxvd3M6CgoxLiAgT3B0aW9uYWwuIElmIHlvdSBkaWQgbm90IGNob29zZSB0byBjYWxsIGxpdmUgZHVyaW5nIHRoZSBydW4gd2l0aCBTVVAKICAgIGFjY3VyYWN5IGFuZCBETkEgbWV0aHlsYXRpb24gZGV0ZWN0aW9uIHR1cm5lZCBvbiwgb3IgaWYgeW91IHdhbnQgdG8KICAgIHVzZSBhIGRpZmZlcmVudCBtb2RlbCwgeW91IGNhbiByZS1iYXNlY2FsbCB3aXRoIGd1cHB5IHBvc3QtaG9jIHVzaW5nCiAgICB0aGUgb3JpZ2luYWwgZmFzdDUgZmlsZXMuIEd1cHB5IHY2LjMuOCB3YXMgdXNlZCBoZXJlLiBOVklESUEgR1BVCiAgICB3aXRoIENVREEgY29yZXMgaXMgaGlnaGx5IHJlY29tbWVuZGVkIHRvIHNwZWVkIHVwIGJhc2UgY2FsbGluZy4KCmBgYHtiYXNofQojIEFjdHVhbCBydW4gY29tbWFuZAovb3B0L29udC9ndXBweS9iaW4vZ3VwcHlfYmFzZWNhbGxlciAtaSAvbW50L3N5bm9sb2d5L29udC1zZXF1ZW5jaW5nLXJ1bi1zdG9yYWdlL1NraW1taW5nLW1ldGh5bGF0aW9uL21vdXNlLXNraW0taGlwLWNvcnRleC10ZXN0ZXMtcmVwcy9ub19zYW1wbGUvMjAyMzAxMTZfMTIyOV9NTjM0NjQ2X0ZBVjE2MzE0XzY5NGUxODBkL2Zhc3Q1X3Bhc3MvIC1zIC4gLWMgZG5hX3IxMC40LjFfZTguMl80MDBicHNfbW9kYmFzZXNfNW1jX2NnX3N1cC5jZmcgLS1iYW1fb3V0IC14IGN1ZGE6YWxsIC0tcmVjdXJzaXZlIC0tdHJpbV9hZGFwdGVycyAtLWJhcmNvZGVfa2l0cyBTUUstUkJLMTE0LTI0IC0tZG9fcmVhZF9zcGxpdHRpbmcKCiMgQmFzaWMgcnVuIGNvbW1hbmQKb3B0L29udC9ndXBweS9iaW4vZ3VwcHlfYmFzZWNhbGxlciAtaSAvcGF0aC90by9mYXN0NV9wYXNzLyAtcyAvb3V0cHV0L3BhdGggLWMgZG5hX3I5LjQuMV80NTBicHNfbW9kYmFzZXNfNW1jX2NnX3N1cC5jZmcgLS1iYW1fb3V0IC14IGN1ZGE6MAoKYGBgCgoyLiAgQ29uY2F0ZW5hdGUgYWxsIHRoZSB1bm1hcHBlZCBiYW0gZmlsZXMgY29udGFpbmluZyBtb2RpZmllZCBiYXNlCiAgICBpbmZvcm1hdGlvbi4KCmBgYHtiYXNofQpzYW10b29scyBjYXQgLW8gdG90YWwuYmFtICppbnB1dC5iYW0KYGBgCgozLiAgTWFwIHVubWFwcGVkIEJhbXMgdG8gYSByZWZlcmVuY2UuIE1hcCwgY29udmVydCBhbmQgc29ydCBhbGwgaW4gb25lCiAgICBjb21tYW5kLiBUaGUgYC1UIE1tLE1sYCBmbGFnIGNhcnJpZXMgb3ZlciB0aGUgbW9kaWZpY2F0aW9ucyBmcm9tIGJhbQogICAgdG8gZmFzdHEuIEluIG5ld2VyIGJhbXMgdGhlIGZsYWdzIGFyZSBgLVQgTU0sIE1MYC5cCiAgICBUaGUgYC15YCBmbGFnIGNvcGllcyBpbnB1dCBmYXN0cSBjb21tYW5kcyB0byBvdXRwdXQuCgpgYGB7YmFzaH0KIyBDb252ZXJ0IHVubWFwcGVkIGJhbSB0byBmYXN0cSwgcGlwZSBvdXRwdXQgdG8gbWluaW1hcDIsIHBpcGUgb3V0cHV0IHRvIHNhbXRvb2xzIGZvciBzb3J0aW5nIGFuZCBpbmRleGluZyB3aXRob3V0IHdyaXRpbmcgaW50ZXJtZWRpYXRlIGZpbGVzIHRvIGRpc2suIChOZWVkIHN1ZmZpY2llbnQgUkFNKQoKc2FtdG9vbHMgZmFzdHEgLVQgTU0sTUwgdG90YWwtY2hpbXAuYmFtIHwgbWluaW1hcDIgLXkgLWF4IG1hcC1vbnQgcGFuVHJvNi5mYSAtIC10IDMyIHwgc2FtdG9vbHMgdmlldyAtdSAtIHwgc2FtdG9vbHMgc29ydCAtQCAzMiAtbyB0b3RhbC1jaGltcC5tb2RtYXBwZWQuYmFtOyBzYW10b29scyBpbmRleCB0b3RhbC1jaGltcC5tb2RtYXBwZWQuYmFtIC1AIDMyCmBgYAoKNC4gIENvbnZlcnQgdG8gYmVkIGZvcm1hdC4gVGhlIGAtZGAgZmxhZyBsaW1pdHMgZGVwdGggdG8gc2F2ZSBtZW1vcnkuCiAgICBUaGUgYWdncmVnYXRlIGZsYWcgYWRkcyB0b2dldGhlciBiYXNlcyBvbiBlaXRoZXIgc3RyYW5kIGFuZCBjcmVhdGVzCiAgICBhbmQgYWdncmVnYXRlLmJlZCBmaWxlLiBUaGUgYC1jcGdgIGZsYWcgbGltaXRzIHRvIGNwZyBzaXRlcy4KCmBgYHtiYXNofQojIENvbnZlcnQgbWFwcGVkIGJhbSBmaWxlIHJlYWRzIHRvIC5iZWQgZm9ybWF0IHRvIHF1YW50aWF0ZSBtZXRoeWxhdGlvbiBieSBwb3NpdGlvbi4KbW9kYmFtMmJlZCAtbSA1bUMgLXQgMzIgLWUgLXAgdG90YWwtY2hpbXAubW9kbWFwcGVkIC0tYWdncmVnYXRlIC0tY3BnIHBhblRybzYuZmEgdG90YWwtY2hpbXAubW9kbWFwcGVkLmJhbSA+IHRvdGFsLWNoaW1wLm1vZG1hcHBlZC5iZWQKYGBgCgo1LiAgRGV0ZXJtaW5lIG1ldGh5bGF0aW9uIHBlcmNlbnRhZ2UgYnkgY291bnRpbmcgYWxsIGNhbm9uaWNhbCAoNUNwRykKICAgIGFuZCBtb2RpZmllZCAoNW1DcEcpIHNpdGVzIGFuZCBkaXZpZGluZy4gVXNlIHRoZQogICAgYG1vZC1jb3VudHMuY3BnLmFjYy5iZWRgIGZpbGUgYXMgaXQgYWdncmVnYXRlcyBjeXRvc2luZSBjYWxscyBmcm9tCiAgICBib3RoIHBhbGluZHJvbWljIHNpZGVzIG9mIHRoZSBvcHBvc2l0ZSBzdHJhbmRzLgoKYGBge2Jhc2h9CiMgRm9yIGV2ZXJ5IENwRyBkaW51Y2xlb3RpZGUsIGFkZCB1cCB0aGUgY291bnQgb2YgY2Fub25pY2FsIHVubW9kaWZpZWQgY3l0b3NpbmVzIChjb2x1bW4gMTIpIGFuZCB0aGUgNSdtZXRoeWxhdGVkIGN5dG9zaW5lcyAoY29sdW1uIDEzKSBhdCBldmVyeSBtYXBwZWQgcG9zaXRpb24uIERpdmlkZSB0aGUgbnVtYmVyIG9mIG1vZGlmaWVkIGN5dG9zaW5lcyBieSB0aGUgdG90YWwgbnVtYmVyIG9mIGN5dG9zaW5lcy4KCiMgTm90ZSB0aGF0IG1vZGJhbTJiZWQgcHJvZHVjZXMgYSBzdHJhbmRlZCBhbmQgYSBjb21iaW5lZCAiYWNjIiBmaWxlLiBUaGUgY29tYmluZWQgZmlsZSBhZGRzIHRvZ2V0aGVyIHRoZSBvcHBvc2l0ZSBzdHJhbmQgY3l0b3NpbmUgY291bnRzIHRvZ2V0aGVyIHNpbmNlIHRoZXkgcmVwcmVzZW50IHRoZSBzYW1lIHBhbGluZHJvbWljIGN5dG9zaW5lLiBUaGlzIGluY3JlYXNlcyBwb3dlci4KCmF3ayAnJDU+MCB7Y2FuKz0kMTI7IG1vZCs9JDEzfSBFTkR7cHJpbnQgMTAwKihtb2QvKGNhbittb2QpKX0nIHRvdGFsLWNoaW1wLm1vZG1hcHBlZC5jcGcuYWNjLmJlZApgYGAKCiMgVmVydGVicmF0ZSBHZW5vbWVzCgpETkEgZnJvbSBzcGVjaWVzIHJlcHJlc2VudGluZyB0aGUgZnVsbCB2ZXJ0ZWJyYXRlIHJhZGlhdGlvbiB3ZXJlCmNvbGxlY3RlZCB0aHJvdWdoIGNvbW1lcmNpYWwgc3VwcGxpZXJzIGFuZCBnaWZ0cyBmcm9tIGNvbGxhYm9yYXRvcnMuIEFsbApzYW1wbGVzIHdlcmUgZGVyaXZlZCBmcm9tIG11c2NsZSB0aXNzdWUgYW5kIGV4dHJhY3RlZCB1c2luZyBaeW1vIFF1aWNrCkROQSBQbHVzIG1pbmlwcmVwIGtpdHMuIEdlbm9tZXMgd2VyZSBkb3dubG9hZGVkIGZyb20gR2VuYmFuayB3aXRoIHRoZQpsYXRlc3QgcmVmZXJlbmNlIGF2YWlsYWJsZSBmb3IgZWFjaCBzcGVjaWVzLgoKMS4gIE1lcmdlIGFuZCBjb3B5IHRoZSBiYW1zIGZvciBlYWNoIGJhcmNvZGUuCgpgYGB7YmFzaH0Kc2FtdG9vbHMgY2F0IC1vIHRvdGFsLmJhbSAqaW5wdXQuYmFtCmBgYAoKMi4gIE1hcCB0byBnZW5vbWUuCgpgYGB7YmFzaH0Kc2FtdG9vbHMgZmFzdHEgLVQgTU0sTUwgdG90YWwtY2hpbXAuYmFtIHwgbWluaW1hcDIgLXkgLWF4IG1hcC1vbnQgcmVmZXJlbmNlLmZhIC0gLXQgMzIgfCBzYW10b29scyB2aWV3IC11IC0gfCBzYW10b29scyBzb3J0IC1AIDMyIC1vIGFuaW1hbC5tb2RtYXBwZWQuYmFtOyBzYW10b29scyBpbmRleCBhbmltYWwubW9kbWFwcGVkLmJhbSAtQCAzMgpgYGAKCjEuICBVc2UgYC0tc3BsaXQtcHJlZml4YCBpZiB0aGUgZ2Vub21lIGlzIHRvbyBsYXJnZSBmb3IgbWluaW1hcDIuCgoyLiAgQ29udmVydCB0byBiZWQgZmlsZS4KCmBgYHtiYXNofQptb2RiYW0yYmVkIC1tIDVtQyAtdCAzMiAtZSAtcCByZWZlcmVuY2UubW9kbWFwcGVkIC0tYWdncmVnYXRlIC0tY3BnIHJlZmVyZW5jZS5mYSBhbmltYWwubW9kbWFwcGVkLmJhbSA+IGFuaW1hbC5tb2RtYXBwZWQuYmVkCmBgYAoKNC4gIENhbGN1bGF0ZSBNZXRoeWxhdGlvbi4KCmBgYHtiYXNofQphd2sgJyQ1PjAge2Nhbis9JDEyOyBtb2QrPSQxM30gRU5Ee3ByaW50IDEwMCoobW9kLyhjYW4rbW9kKSl9JyBhbmltYWwubW9kbWFwcGVkLmNwZy5hY2MuYmVkCmBgYAoKNS4gIENhbGN1bGF0ZSBtYXBwaW5nIGVmZmljaWVuY3kuCgo2LiAgQmFzaWMgc3RhdHMgYGJhbSBzdGF0cyAtLWluIGFuaW1hbC5tb2RtYXBwZWQuYmFtIC0tYmFzaWNgCgo3LiAgQXZlcmFnZSBxdWFsaXR5CiAgICBgc2FtdG9vbHMgc3RhdCBhbmltYWwubW9kbWFwcGVkLmJhbSB8IGdyZXAgImF2ZXJhZ2UgcXVhbGl0eSJgCgo4LiAgQ29weSB0byBFeGNlbCBhbmQgR3JhcGhwYWQgZm9yIGdyYXBoaWNzLgoKIyBUcmFuc3Bvc29uIEFuYWx5c2lzCgojIyBQcmltYXRlIExJTkUxIGFuZCBBbHUgZWxlbWVudHMKClRoZXNlIDIgZWxlbWVudHMgYWxvbmUgYWNjb3VudCBmb3Ig4oi8MzAlIG9mIHRoZSBnZW5vbWUgc2VxdWVuY2UgYW5kIGFyZQp0aGUgbW9zdCBhYnVuZGFudCB0cmFuc3Bvc2FibGUgZWxlbWVudHMgaW4gaHVtYW5zIChMYW5kZXIgZXQgYWwuKQoKIyMgQWxsIFJlcGVhdHMgR2Vub21lLXdpZGUKCjAuODYwNDI3MyUgQXZlcmFnZSBtZXRoeWxhdGlvbiBhY3Jvc3MgYWxsIHJlcGVhdCB0eXBlcwoKIyMgVHJhbnNwb3NvbiBQaXBlbGluZQoKIyMjIyBDaGltcGFuemVlIExJTkUxIGFuZCBBbHVzIFNwZWNpZmljYWxseQoKMS4gIFRhYmxlIEJyb3dzZXIgLVw+IFJlcGVhdHMgLVw+IFRhYmxlICJybXNrIiAtXD4gQWxsIGZpZWxkcyBmcm9tCiAgICBzZWxlY3RlZCB0YWJsZSAtXD4gcmVwZWF0LXRhYmxlLnRzdgoyLiAgR2V0IGFsbCBMMSdzIGZyb20gUmVwZWF0cyB0c3YgYW5kIHByaW50IGludG8gYmVkIGZvcm1hdC4KCmBgYHtiYXNofQphd2sgJyQxMz09IkwxIiB7cHJpbnQgJDYgIlx0IiAkNyAiXHQiJDggIlx0IiQxM30nIHBhblRybzYtcmVwZWF0LXRhYmxlLnRzdiA+IHJlcGVhdEwxLmJlZApgYGAKCjMuICBJbnRlcnNlY3QgYWxsIENwRyBzaXRlcyB0aGF0IGZhbGwgd2l0aGluIEwxIGNvb3JkaW5hdGVzIGFuZCBhdmVyYWdlCiAgICB0aGVpciBtZXRoeWxhdGlvbgoKYGBge2Jhc2h9CmJlZHRvb2xzIGludGVyc2VjdCAtYSB0b3RhbC1jaGltcC5tb2RtYXBwZWQuY3BnLmFjYy5iZWQgLWIgcmVwZWF0TDEuYmVkID4gcmVwZWF0TDEuY3BnaW50ZXJzZWN0LmJlZAphd2sgJyQ1PjAge2Nhbis9JDEyOyBtb2QrPSQxM30gRU5Ee3ByaW50IDEwMCoobW9kLyhjYW4rbW9kKSl9JyByZXBlYXRMMS5jcGdpbnRlcnNlY3QuYmVkCmBgYAoKMS4gIDg0LjcwNjklIEwxIG1ldGh5bGF0aW9uIGluIHRoZSAxMS4xMlggY292ZXJhZ2UgY2hpbXAgZ2Vub21lLgoKMi4gIDkyLjQ3NjclIEFsdSBpbiB0aGUgY2hpbXAgZ2Vub21lLgoKMy4gIERvd25zYW1wbGUgdGhlIHJlYWRzIHRvIDAuMDAxWCAoMyBNYikgYW5kIHBsb3QgMTAgYm9vdHN0cmFwCiAgICByZXBsaWNhdGVzLgoKNC4gIFVzZSB0aGUgcHJlLW1hZGUgZG93bnNhbXBsZWQgY3BnLmFjYy5iZWQgZmlsZXMgaW4gdGhlIGJvb3RzdHJhcAogICAgZGlyZWN0b3JpZXMKCjUuICBNYWtlIGludGVyc2VjdGlvbiBmaWxlcyBmb3IgZWFjaCBib290c3RyYXAuCgpgYGB7YmFzaH0KZm9yIGkgaW4gYm9vdHN0cmFwMTBYLyouY3BnLmFjYy5iZWQgOyBkbyBiZWR0b29scyBpbnRlcnNlY3QgLWEgJGkgLWIgcmVwZWF0TDEuYmVkID4gJGkuY3BnaW50ZXJzZWN0LkwxLmJlZCA7IGRvbmUKYGBgCgpVc2UgYXdrIHRvIHByaW50IHRoZSBtZXRoeWxhdGlvbiB0byBhIGZpbGUuCgpgYGB7YmFzaH0KZm9yIGkgaW4gYm9vdHN0cmFwMTBYLyouY3BnaW50ZXJzZWN0LmJlZCA7IGRvIGF3ayAnJDU+MCB7Y2FuKz0kMTI7IG1vZCs9JDEzfSBFTkR7cHJpbnQgMTAwKihtb2QvKGNhbittb2QpKX0nICRpID4+IEwxYm9vdHN0cmFwcGVkLjEwWC50eHQ7IGRvbmUKYGBgCgpSZXBlYXQgZm9yIGVhY2ggcmVhZCBsZXZlbC4KClJlcGVhdCBmb3IgQWx1cy4KCiMjIyMgQ2hpbXBhemVlIEFsbCBSZXBlYXRzCgoxLiAgQ2hpbXAuIE1ha2UgYW4gaW50ZXJzZWN0aW9uIG9mIGFsbCByZXBlYXRzIGFuZCBjcGcgc2l0ZXMgd2l0aCAtd2EKICAgIGFuZCAtd2IgdG8gY29tYmluZSBiZWRzLgoKYGBge2Jhc2h9CmJlZHRvb2xzIGludGVyc2VjdCAtYSB0b3RhbC1jaGltcC5tb2RtYXBwZWQuY3BnLmFjYy5iZWQgLWIgcmVwZWF0QWxsLmJlZCAtd2EgLXdiID4gcmVwZWF0QWxsLmNwZ2ludGVyc2VjdC5iZWQKYGBgCgoyLiAgTW91c2UuIFJlcGVhdCBhcyB3aXRoIENoaW1wLCBidXQgdXNlIGFsbCA1IG1vdXNlIGhpcHBvY2FtcHVzIHNhbXBsZXMKICAgIHRoYXQgc2hhcmUgY29tbW9uIHJvd3MgZm9yIG1ldGh5bGF0aW9uIGRhdGEuCgojIyMgTW91c2UgUmVwZWF0cwoKIyMjIyBNb3VzZSBBbGwgUmVwZWF0cwoKMS4gIERvd25sb2FkIGFsbCBtbTM5IHJlcGVhdHMgZnJvbSBVU0NTIFRhYmxlIEJyb3dzZXIuCgoyLiAgQ29udmVydCB0byBiZWQgZm9ybWF0LgoKYGBge2Jhc2h9CmF3ayAne3ByaW50ICQ2ICJcdCIgJDcgIlx0IiQ4ICJcdCIkMTN9JyBtbTM5LXJlcGVhdC10YWJsZS50c3YgPiBtbTM5LXJlcGVhdC10YWJsZS5iZWQKIyBSZW1vdmUgdGhlIGhlYWRlciBsaW5lIG1hbnVhbGx5LgpgYGAKCjMuICBNYWtlIGFuIGludGVyc2VjdGlvbiBvZiBhbGwgcmVwZWF0cyBhbmQgY3BnIHNpdGVzIHdpdGggLXdhIGFuZCAtd2IKICAgIHRvIGNvbWJpbmUgYmVkcy4gTG9vcCBmb3IgYWxsIGZpbGVzLgoKYGBge2Jhc2h9CmZvciBpIGluICouY3BnLmFjYy5iZWQgOyBkbyBiZWR0b29scyBpbnRlcnNlY3QgLWEgJGkgLWIgbW0zOS1yZXBlYXQtdGFibGUuYmVkIC13YSAtd2IgPiAkaS5jcGdpbnRlcnNlY3QuYmVkIDsgZG9uZQpgYGAKCjQuICBVc2UgUiBwcm9ncmFtIGByZXBlYXRzLXJtZC5SbWRgIChjb3BpZWQgYmVsb3cpIHRvIHN1bW1hcml6ZQogICAgbWV0aHlsYXRpb24gb24gYSBwZXItcmVwZWF0IGJhc2lzIGFuZCBjb3B5IHJlc3VsdHMgaW50byBHcmFwaHBhZCBmb3IKICAgIGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9uLgoKICAgIGBgYHtyIFN1bW1hcml6ZV9yZXBlYXRzLCBldmFsPUZBTFNFfQogICAgIyBJbXBvcnQgbmVjZXNzYXJ5IGxpYnJhcmllcwogICAgbGlicmFyeSh0aWR5dmVyc2UpCgogICAgIyBSZWFkIGluIHRzdiBmaWxlYQogICAgZGF0YSA8LSByZWFkX3RzdigibW91c2UtaGlwLm1vZG1hcHBlZC5jcGcuYWNjLmJlZC5jcGdpbnRlcnNlY3QuYmVkIiwgY29sX25hbWVzPUZBTFNFKQoKICAgICMgQ2FsY3VsYXRlIHN1bSBvZiBjb2x1bW4gMTMgZGl2aWRlZCBieSB0aGUgc3VtIG9mIGNvbHVtbiAxMiBwbHVzIGNvbHVtbiAxMwogICAgZGF0YSRyZXN1bHQgPC0gZGF0YSRYMTMgLyAoZGF0YSRYMTIgKyBkYXRhJFgxMykKICAgIGRhdGEgPC0gZGF0YSAlPiUgZHJvcF9uYSgpCiAgICBkYXRhJHJlc3VsdAoKICAgICMgU2VwYXJhdGUgb3V0IGJ5IGZhY3RvcnMgaW4gY29sdW1uIDE4CiAgICBkYXRhX2J5X2ZhY3RvciA8LSBkYXRhICU+JSBncm91cF9ieShYMTgpICU+JSBzdW1tYXJpemUocmVzdWx0ID0gbWVhbihyZXN1bHQpKQogICAgZGF0YV9ieV9mYWN0b3IKICAgIHdyaXRlLnRhYmxlKGRhdGFfYnlfZmFjdG9yICwgZmlsZSA9ICJtb3VzZS1oaXAubW9kbWFwcGVkLmNwZy5hY2MuYmVkLmNwZ2ludGVyc2VjdC5iZWQuZGF0YWJ5ZmFjdG9yLnRzdiIpCgogICAgIyBNYWtlIGEgYmFyIHBsb3Qgb2YgdGhlIHJlc3VsdAogICAgZ2dwbG90KGRhdGFfYnlfZmFjdG9yLCBhZXMoeCA9IFgxOCwgeSA9IHJlc3VsdCkpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICArIAogICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSkpCiAgICBgYGAK