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
  • Convert bams to bed.
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:

  1. 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
  1. Concatenate all the unmapped bam files containing modified base information.
samtools cat -o total.bam *input.bam
  1. 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
  1. 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
  1. 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.

  1. Merge and copy the bams for each barcode.
samtools cat -o total.bam *input.bam
  1. 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
  1. Use --split-prefix if the genome is too large for minimap2.

  2. Convert to bed file.

modbam2bed -m 5mC -t 32 -e -p reference.modmapped --aggregate --cpg reference.fa animal.modmapped.bam > animal.modmapped.bed
  1. Calculate Methylation.
awk '$5>0 {can+=$12; mod+=$13} END{print 100*(mod/(can+mod))}' animal.modmapped.cpg.acc.bed
  1. Calculate mapping efficiency.

  2. Basic stats bam stats --in animal.modmapped.bam --basic

  3. Average quality samtools stat animal.modmapped.bam | grep "average quality"

  4. 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

  1. Table Browser -> Repeats -> Table “rmsk” -> All fields from selected table -> repeat-table.tsv
  2. 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
  1. 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
  1. 84.7069% L1 methylation in the 11.12X coverage chimp genome.

  2. 92.4767% Alu in the chimp genome.

  3. Downsample the reads to 0.001X (3 Mb) and plot 10 bootstrap replicates.

  4. Use the pre-made downsampled cpg.acc.bed files in the bootstrap directories

  5. 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

  1. 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
  1. Mouse. Repeat as with Chimp, but use all 5 mouse hippocampus samples that share common rows for methylation data.

Mouse Repeats

Mouse All Repeats

  1. Download all mm39 repeats from USCS Table Browser.

  2. 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.
  1. 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
  1. 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