Goals

Process metabarcoding reads, post-adapter removal, including:

  • Evaluation of read quality
  • Quality trimming and filtering
  • Error correction, denoising
  • ASV inference
  • Read merging
  • Taxonomy assignment

Before We Get Started

Notes

  • This documented was adapted from Callahan et al. 2006 by Matthew Willman, with further edits by Soledad Benitez Ponce and Jelmer Poelstra.

  • To convert an Rmd (R Markdown) file to an R script, type the following in an R console: knitr::purl(input='<filename>.Rmd'). (You can download this Rmd file by clicking the Code button in the top-right of this page – but we will open it at OSC.)

  • This document deliberately does not have any R output. In case you want to check your output against a reference, you can find a document with R output here.

Start an RStudio Server job at OSC

For more detailed instructions of the first steps, see this section from our intro to R session.

  • Login to OSC at https://ondemand.osc.edu.

  • Click on Interactive Apps (top bar) > RStudio Server (Owens and Pitzer).

  • Fill out the form as shown here.

  • Once your job has started, click Connect to RStudio Server.

  • You should automatically be in your personal dir inside the dir /fs/project/PAS0471/workshops/2020-12_micro, with your RStudio Project open. You can see whether a Project is open and which one in the top-right of your screen:

    Here, the project jelmer is open. Your project name is also your username.

    If your Project isn’t open, click on the icon to open it:

  • Now, click on the markdown directory in the Files pane, and open the file 07-ASV-inference.Rmd. That’s this file!


Step 1: Getting Started

Print some information to screen for when running this script as a job:

print('Starting ASV inference script...')
Sys.time()

cat('Working directory:', getwd(), '\n')

Set the number of cores

Most dada2 functions can use multiple cores. Because we are on a cluster and we have reserved only part of a node, auto-detection of the number of cores will not be appropriate (the program will detect more cores than we have available). Therefore, we should specify the appropriate number of cores in our function call below.

We will set the number of cores here, assumes you requested 4 cores in your job submission (change if needed):

n_cores <- 4

Install and load packages

To save time, we have already installed all the necessary R packages at OSC into a custom library. To add this library for the current R session:

.libPaths(new = '/fs/project/PAS0471/.R/4.0/')

Then, load the packages:

print('Loading packages...')

packages <- c('tidyverse', 'gridExtra', 'dada2',
              'phyloseq', 'DECIPHER', 'phangorn')
pacman::p_load(char = packages)

# If you wanted to install and load these packages yourself,
# just make sure you have the pacman package installed:
## install.packages('pacman')
# Then, the code above would work, as it will install pacakes as needed.

Set the file paths

We’ll set most of the file paths upfront, which will make it easier to change things or troubleshoot.

# Dir with input fastq files:
indir <- 'data/processed/fastq_trimmed'

# Dirs for output:
filter_dir <- 'data/processed/fastq_filtered'
outdir <- 'analysis/ASV_inference'

dir.create(filter_dir, showWarnings = FALSE, recursive = TRUE)
dir.create(outdir, showWarnings = FALSE, recursive = TRUE)

# Fasta file with training data:
# (Check for an up-to-date version at <https://benjjneb.github.io/dada2/training.html>)
tax_key <- 'data/ref/silva_nr99_v138_train_set.fa' 

# File with sample metadata:
metadata_file <- 'metadata/sample_meta.txt'

Assign fastq files to forward and reverse reads

We will assign the fastq files that we processed with cutadapt to two vectors: one with files with forward reads, and one with files with reverse reads. These files can be distinguished by having “R1” (forward) and “R2” (reverse) in their names.

fastqs_raw_F <- sort(list.files(indir, pattern = '_R1_001.fastq.gz', full.names = TRUE))
fastqs_raw_R <- sort(list.files(indir, pattern = '_R2_001.fastq.gz', full.names = TRUE))

print('First fastq files:')
head(fastqs_raw_F)

Check sample IDs

We’ll get the sample IDs from the fastq file names and from a file with metadata, and will check if they are the same. First we’ll prepare the metadata:

print('Load and prepare sample metadata...')

metadata_df <- read.table(file = metadata_file, sep = "\t", header = TRUE)

colnames(metadata_df)[1] <- 'SampleID'
rownames(metadata_df) <- metadata_df$SampleID

print('First fastq files:')
head(metadata_df)

Let’s compare the sample IDs from the metadata with the fastq filenames:

print('IDs from metadata:')
metadata_df$SampleID

print('Fastq file names:')
head(basename(fastqs_raw_F))    # basename() strips the dir name from the filename

To extract the sample IDs from the fastq file names, we remove everything after “-V4-V5” from the file names using the sub() function:

# sub() arguments: sub(pattern, replacement, vector)
sampleIDs <- sub("-V4-V5_.*", "", basename(fastqs_raw_F))

We can check whether the IDs from the fastq files and the metadata dataframe are the same:

print('Are the sample IDs from the metadata and the fastq files the same?')
identical(sort(metadata_df$SampleID), sampleIDs)

print('Are any samples missing from the fastq files?')
setdiff(sort(metadata_df$SampleID), sampleIDs)

print('Are any samples missing from the metadata?')
setdiff(sampleIDs, sort(metadata_df$SampleID))

As it turns out, we don’t have sequences for three samples in the metadata.


Step 2: QC

Plot sequence quality data

DADA2 provide a function to plot the average base quality across sequence reads, plotQualityProfile(). You can generate and evaluate plots for each sample, e.g. the forward reads and reverse reads side-by-side like so:

plotQualityProfile(c(fastqs_raw_F[1], fastqs_raw_R[1]))


This code will generate a pdf file with plots for each sample:

pdf(file.path(outdir, 'error_profiles.pdf'))               # Open a pdf file
for (sample_idx in 1:length(fastqs_raw_F)) {               # Loop through samples
  print(plotQualityProfile(                                # Print plots into pdf
    c(fastqs_raw_F[sample_idx], fastqs_raw_R[sample_idx])) # F and R together
    )
}
dev.off()                                                  # Close the pdf file
    More on QC of fastq files It is a good idea to run the fastqc program on your fastq files for more extensive QC. This is a stand-alone program that is easy to run from the command-line. When you have many samples, as is often the case, fastqc’s results can moreover be nicely summarized using [multiqc](https://multiqc.info/. In the interest of time, we skipped these steps during this workshop.

Step 3: Filtering and Quality Trimming

We will now perform quality filtering (removing poor-quality reads) and trimming (removing poor-quality bases) on the fastq files using DADA2’s filterAndTrim() function.

The filterAndTrim() function will write the filtered and trimmed reads to new fastq files. Therefore, we first define the file names for the new files:

fastqs_filt_F <- file.path(filter_dir, paste0(sampleIDs, '_F_filt.fastq'))
fastqs_filt_R <- file.path(filter_dir, paste0(sampleIDs, '_R_filt.fastq'))


The truncLen argument of filterAndTrim() defines the read lengths (for forward and reverse reads, respectively) beyond which additional bases should be removed, and these values should be based on the sequence quality visualized above. The trimming length can thus be different for forward and reverse reads, which is good because reverse reads are often of worse quality.

It is also suggested to trim the first 10 nucleotides of each read (trimLeft argument), since these positions are likely to contain errors.

maxEE is an important argument that will let DADA2 trim reads based on the maximum numbers of Expected Errors (EE) given the quality scores of the reads’ bases.

print('Filtering and Trimming...')
Sys.time()  # Print the time to keep track of running time for individual steps 
filter_results <-
  filterAndTrim(fastqs_raw_F, fastqs_filt_F,
                fastqs_raw_R, fastqs_filt_R,
                truncLen = c(250,210),
                trimLeft = 10,
                maxN = 0,
                maxEE = c(2,2),
                truncQ = 2,
                rm.phix = FALSE,
                multithread = n_cores, 
                compress = FALSE, verbose = TRUE) 
print('...Done!')
Sys.time()

head(filter_results)

Step 4: Dereplication & Error Training

Next, we want to “dereplicate” the filtered fastq files. During dereplication, we condense the data by collapsing together all reads that encode the same sequence, which significantly reduces later computation times.

fastqs_derep_F <- derepFastq(fastqs_filt_F, verbose = FALSE)
fastqs_derep_R <- derepFastq(fastqs_filt_R, verbose = FALSE)

names(fastqs_derep_F) <- sampleIDs
names(fastqs_derep_R) <- sampleIDs

The DADA2 algorithm makes use of a parametric error model (err) and every amplicon dataset has a different set of error rates. The learnErrors method learns this error model from the data, by alternating estimation of the error rates and inference of sample composition until they converge on a jointly consistent solution.

print('Learning errors...')
Sys.time()

err_F <- learnErrors(fastqs_derep_F, multithread = n_cores, verbose = TRUE)
err_R <- learnErrors(fastqs_derep_R, multithread = n_cores, verbose = TRUE)

print('...Done!')
Sys.time()


We’ll plot errors to verify that error rates have been reasonable well-estimated. Pay attention to the fit between observed error rates (points) and fitted error rates (lines):

plotErrors(err_F, nominalQ = TRUE)
plotErrors(err_R, nominalQ = TRUE)

Step 5: Infer ASVs

We will now run the core dada algorithm, which infers Amplicon Sequence Variants (ASVs) from the sequences.

This step is quite computationally intensive, and for this tutorial, we will therefore perform independent inference for each sample (pool = FALSE), which will keep the computation time down.

Pooling will increase computation time, especially if you have many samples, but will improve detection of rare variants seen once or twice in an individual sample, but many times across all samples. Therefore, for your own analysis, you will likely want to use pooling, though “pseudo-pooling” is also an option.

print('Inferring ASVs (running the dada algorithm)...')
Sys.time()

dada_Fs <- dada(fastqs_derep_F, err = err_F, pool = FALSE, multithread = n_cores)
dada_Rs <- dada(fastqs_derep_R, err = err_R, pool = FALSE, multithread = n_cores)

print('...Done.')
Sys.time()

Let’s inspect one of the resulting objects:

dada_Fs[[1]]

Step 6: Merge Read Pairs

In this step, we will first merge the forward and reverse read pairs: the fragment that we amplified with our primers was short enough to generate lots of overlap among the sequences from the two directions.

mergers <- mergePairs(dada_Fs, fastqs_derep_F,
                      dada_Rs, fastqs_derep_R,
                      verbose = TRUE)


Just like tables can be saved in R using write.table or write.csv, R objects can be saved using saveRDS. The resulting rds file can then be loaded into an R environment using readRDS. This is a convenient way to save R objects that require a lot of computation time.

We should not be needing the very large dereplicated sequence objects anymore, but to be able to quickly restart our analysis from a new R session if necessary, we now save these objects to rds files. And after that, we can safely remove these objects from our environment.

saveRDS(fastqs_derep_F, file = file.path(outdir, 'fastqs_derep_F.rds'))
saveRDS(fastqs_derep_R, file = file.path(outdir, 'fastqs_derep_R.rds'))

rm(fastqs_derep_F, fastqs_derep_R) # Remove objects from environment

Step 7: Construct a Sequence Table

Next, we construct an amplicon sequence variant table (ASV) table:

seqtab_all <- makeSequenceTable(mergers)

# The dimensions of the object are the nr of samples (rows) and the nr of ASVs (columns):
dim(seqtab_all)

Let’s inspect the distribution of sequence lengths:

table(nchar(getSequences(seqtab_all)))

# If you need to remove sequences of a particular length (e.g. too long):
# seqtab2 <- seqtab[, nchar(colnames(seqtab_all)) %in% seq(250,256)]

Step 8: Remove Chimeras

Now, we will remove chimeras. The dada algorithm models and removes substitution errors, but chimeras are another importance source of spurious sequences in amplicon sequencing. Chimeras are formed during PCR amplification. When one sequence is incompletely amplified, the incomplete amplicon primes the next amplification step, yielding a spurious amplicon. The result is a sequence read which is half of one sample sequence and half another.

Fortunately, the accuracy of the sequence variants after denoising makes identifying chimeras simpler than it is when dealing with fuzzy OTUs. Chimeric sequences are identified if they can be exactly reconstructed by combining a left-segment and a right-segment from two more abundant “parent” sequences.

seqtab <- removeBimeraDenovo(seqtab_all,
                             method = 'consensus',
                             multithread = n_cores,
                             verbose = TRUE)
ncol(seqtab)

# Proportion of retained sequences:
sum(seqtab) / sum(seqtab_all)

We will save the seqtab object as an rds file:

saveRDS(seqtab, file = file.path(outdir, 'seqtab_V4.rds'))

Step 9: Generate a Summary Table

In this step, we will generate a summary table of the number of sequences processed and outputs of different steps of the pipeline.

This information is generally used to further evaluate characteristics and quality of the run, sample-to-sample variation, and resulting sequencing depth for each sample.

To get started, we will define a function getN() that will get the number of unique reads for a sample. Then, we apply getN() to each element of the dada_Fs, dada_Rs, and mergers objects, which gives us vectors with the number of unique reads for each samples, during each of these steps:

getN <- function(x) {
  sum(getUniques(x))
}

denoised_F <- sapply(dada_Fs, getN)
denoised_R <- sapply(dada_Rs, getN)
merged <- sapply(mergers, getN)

We’ll join these vectors together with the “filter_results” dataframe, and the number of nonchimeric reads:

nreads_summary <- data.frame(filter_results,
                             denoised_F,
                             denoised_R,
                             merged,
                             nonchim = rowSums(seqtab),
                             row.names = sampleIDs)
colnames(nreads_summary)[1:2] <- c('input', 'filtered')

# Have a look at the first few rows:
head(nreads_summary)

Finally, we’ll write this table to file:

write.table(nreads_summary, file = file.path(outdir, 'nreads_summary.txt'),
            sep = "\t", quote = FALSE, row.names = TRUE)

Step 10: Assign Taxonomy to ASVs

Now, we will assign taxonomy to our ASVs.

Depending on the marker gene and the data, you will have to choose the appropriate reference file for this step. Several files have been formatted for taxonomy assignments in DADA2 pipeline and are available at the DADA2 website.

print('Assigning taxa to ASVs...')
Sys.time()

taxa <- assignTaxonomy(seqtab, tax_key, multithread = n_cores)
colnames(taxa) <- c('Kingdom', 'Phylum', 'Class', 'Order', 'Family', 'Genus')

print('...Done.')
Sys.time()

Step 11: Generate Output Files

In this last step, we will generate output files from the DADA2 outputs that are formatted for downstream analysis in phyloseq. First, we will write a fasta file with the final ASV sequences. (This fasta file can also be used for phylogenetic tree inference with different R packages.)

# Prepare sequences and headers:
asv_seqs <- colnames(seqtab)
asv_headers <- paste('>ASV', 1:ncol(seqtab), sep = '_')

# Interleave headers and sequences:
asv_fasta <- c(rbind(asv_headers, asv_seqs))

# Write fasta file:
write(asv_fasta, file = file.path(outdir, 'ASVs.fa'))

Now, we build the final phyloseq object. Notes:

  • While we will not add a phylogenetic tree now, this can also be added to a phyloseq object.

  • Our metadata dataframe contains three samples that we don’t have sequences for. However, this is not a problem: phyloseq will match the sample IDs in the metadata with those in the OTU table, and disregard IDs not present in the OTU table.

ps <- phyloseq(otu_table(seqtab, taxa_are_rows = FALSE),
               sample_data(metadata_df),
               tax_table(taxa))

# Saves the phyloseq object as an .rds file (which can be imported directly by phyloseq):
saveRDS(ps, file = file.path(outdir, 'ps_V4.rds'))

Report that we are done!

print('Done with ASV inference.')

Bonus: Phylogenetic Tree Estimation

A phylogenetic tree can be estimated for the sequence data you generated. Depending on the number of ASVs recovered and the phylogenetic tree algorithm of choice, this step could take several days. Simpler trees will be less computationally intensive. Depending on the marker gene you are working on, you may or may not choose to perform this step.

This step can be conducted after Step 10, and then the phylogeny can be included in the phyloseq object in Step 11.

#seqtab<- readRDS('seqtab_V4.rds')

seqs <- getSequences(seqtab)

# This propagates to the tip labels of the tree.
# At this stage ASV labels are full ASV sequence
names(seqs) <- seqs 
alignment <- AlignSeqs(DNAStringSet(seqs),
                       anchor = NA,
                       iterations = 5,
                       refinements = 5)

print('Computing pairwise distances from ASVs...')
Sys.time()
phang.align <- phyDat(as(alignment, 'matrix'), type = 'DNA')
dm <- dist.ml(phang.align)
treeNJ <- NJ(dm) # Note, tip order is not sequence order
fit = pml(treeNJ, data = phang.align)
print('...Done.')
Sys.time()

print('Fit GTR model...')
Sys.time()
fitGTR <- update(fit, k = 4, inv = 0.2)
print('...Done.')
Sys.time()

print('Computing likelihood of tree...')
Sys.time()
fitGTR <- optim.pml(fitGTR,
                    model = 'GTR',
                    optInv = TRUE,
                    optGamma = TRUE,
                    rearrangement = 'stochastic',
                    control = pml.control(trace = 0))
print('...Done'.)
Sys.time()

Bonus: Submit script as an OSC job

Extract R code from this document:

knitr::purl(input = 'markdown/07-ASV-inference.Rmd',
            output = 'scripts/02-ASV-inference.R')

Our script 02-ASV-inference.sh with SLURM directives that will submit the R script from the shell using Rscript:

#!/bin/bash
#SBATCH --nodes=1
#SBATCH --ntasks-per-node=4
#SBATCH --time=2:00:00
#SBATCH --account=PAS0471

module load gnu/9.1.0
module load mkl/2019.0.5
module load R/4.0.2

Rscript scripts/02-ASV-inference.R

Submit the script:

sbatch scripts/02-ASV-inference.sh



LS0tCnRpdGxlOiAiPGJyPldvcmtmbG93IHBhcnQgSUk6PGJyPkFTViBJbmZlcmVuY2UgYW5kIFRheG9uIEFzc2lnbm1lbnQiCm91dHB1dDoKICBybWFya2Rvd246Omh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogY2VydWxlYW4KICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjc3M6IG15LmNzcwogICAgYW5jaG9yX3NlY3Rpb25zOiB0cnVlCmVkaXRvcl9vcHRpb25zOgogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCi0tLQoKYGBge3Igc2V0dXAsIGVjaG89RkFMU0UsIHB1cmw9RkFMU0V9CnJvb3RfZGlyIDwtICcuLicKI2tuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gcm9vdF9kaXIpCgprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgY2FjaGUgPSBUUlVFLAogIGV2YWwgPSBGQUxTRSwgIyBjaGFuZ2UgYXMgbmVlZGVkCiAgY2xhc3Muc291cmNlID0gJ3JfY29kZScsIGNsYXNzLm91dHB1dCA9ICdyX291dHB1dCcsIGNsYXNzLndhcm5pbmcgPSAncl93YXJuaW5nJywgY2xhc3MubWVzc2FnZSA9ICdyX3dhcm5pbmcnLCBjbGFzcy5lcnJvciA9ICdyX2Vycm9yJwogICkKYGBgCgo8YnI+CgotLS0tLQoKIyMgR29hbHMKClByb2Nlc3MgbWV0YWJhcmNvZGluZyByZWFkcywgcG9zdC1hZGFwdGVyIHJlbW92YWwsIGluY2x1ZGluZzoKCi0gRXZhbHVhdGlvbiBvZiByZWFkIHF1YWxpdHkKLSBRdWFsaXR5IHRyaW1taW5nIGFuZCBmaWx0ZXJpbmcKLSBFcnJvciBjb3JyZWN0aW9uLCBkZW5vaXNpbmcKLSBBU1YgaW5mZXJlbmNlCi0gUmVhZCBtZXJnaW5nCi0gVGF4b25vbXkgYXNzaWdubWVudAoKLS0tLS0KCiMjIEJlZm9yZSBXZSBHZXQgU3RhcnRlZAoKIyMjIE5vdGVzCgotIFRoaXMgZG9jdW1lbnRlZCB3YXMgYWRhcHRlZCBmcm9tCiAgW0NhbGxhaGFuIGV0IGFsLiAyMDA2XShodHRwczovL2YxMDAwcmVzZWFyY2guY29tL2FydGljbGVzLzUtMTQ5Mi92MikKICBieSBNYXR0aGV3IFdpbGxtYW4sIHdpdGggZnVydGhlciBlZGl0cyBieSBTb2xlZGFkIEJlbml0ZXogUG9uY2UgYW5kIEplbG1lciBQb2Vsc3RyYS4KICAKLSBUbyBjb252ZXJ0IGFuIGBSbWRgIChSIE1hcmtkb3duKSBmaWxlIHRvIGFuIFIgc2NyaXB0LAogIHR5cGUgdGhlIGZvbGxvd2luZyBpbiBhbiBSIGNvbnNvbGU6CiAgYGtuaXRyOjpwdXJsKGlucHV0PSc8ZmlsZW5hbWU+LlJtZCcpYC4KICAoWW91IGNhbiBkb3dubG9hZCB0aGlzIGBSbWRgIGZpbGUgYnkgY2xpY2tpbmcgdGhlIGBDb2RlYCBidXR0b24KICBpbiB0aGUgdG9wLXJpZ2h0IG9mIHRoaXMgcGFnZSAtLSBidXQgd2Ugd2lsbCBvcGVuIGl0IGF0IE9TQy4pCgotIFRoaXMgZG9jdW1lbnQgZGVsaWJlcmF0ZWx5IGRvZXMgbm90IGhhdmUgYW55IFIgb3V0cHV0LgogIEluIGNhc2UgeW91IHdhbnQgdG8gY2hlY2sgeW91ciBvdXRwdXQgYWdhaW5zdCBhIHJlZmVyZW5jZSwKICB5b3UgY2FuIGZpbmQgYSBkb2N1bWVudCB3aXRoIFIgb3V0cHV0IFtoZXJlXShhc3NldHMvMDctQVNWLWluZmVyZW5jZS5odG1sKS4KCiMjIyBTdGFydCBhbiBSU3R1ZGlvIFNlcnZlciBqb2IgYXQgT1NDCgpGb3IgbW9yZSBkZXRhaWxlZCBpbnN0cnVjdGlvbnMgb2YgdGhlIGZpcnN0IHN0ZXBzLApzZWUgW3RoaXMgc2VjdGlvbl0oMDYtUi5odG1sI3JzdHVkaW8tYXQtT1NDKSBmcm9tIG91ciBpbnRybyB0byBSIHNlc3Npb24uCgotIExvZ2luIHRvIE9TQyBhdCA8aHR0cHM6Ly9vbmRlbWFuZC5vc2MuZWR1Pi4KCi0gQ2xpY2sgb24gYEludGVyYWN0aXZlIEFwcHNgICh0b3AgYmFyKSA+IGBSU3R1ZGlvIFNlcnZlciAoT3dlbnMgYW5kIFBpdHplcilgLgoKLSBGaWxsIG91dCB0aGUgZm9ybSBhcyBzaG93biBbaGVyZV0oc2xpZGVzLzAzLU9TQy1zbGlkZXMuaHRtbCNyc3R1ZGlvX3NlcnZlcl9qb2IpLgoKLSBPbmNlIHlvdXIgam9iIGhhcyBzdGFydGVkLCBjbGljayBgQ29ubmVjdCB0byBSU3R1ZGlvIFNlcnZlcmAuCgotIFlvdSBzaG91bGQgYXV0b21hdGljYWxseSBiZSBpbiB5b3VyIHBlcnNvbmFsIGRpciBpbnNpZGUgdGhlIGRpcgogIGAvZnMvcHJvamVjdC9QQVMwNDcxL3dvcmtzaG9wcy8yMDIwLTEyX21pY3JvYCwKICB3aXRoIHlvdXIgUlN0dWRpbyBQcm9qZWN0IG9wZW4uCiAgWW91IGNhbiBzZWUgd2hldGhlciBhIFByb2plY3QgaXMgb3BlbiBhbmQgd2hpY2ggb25lCiAgaW4gdGhlIHRvcC1yaWdodCBvZiB5b3VyIHNjcmVlbjoKICAKICA8cCBhbGlnbj0iY2VudGVyIj4KICA8aW1nIHNyYz1pbWcvcnByb2otb3Blbi5wbmcgd2lkdGg9IjEzMCI+CiAgSGVyZSwgdGhlIHByb2plY3QgYGplbG1lcmAgaXMgb3Blbi4gWW91ciBwcm9qZWN0IG5hbWUgaXMgYWxzbyB5b3VyIHVzZXJuYW1lLgogIDwvcD4KCiAgSWYgeW91ciBQcm9qZWN0IGlzbid0IG9wZW4sIGNsaWNrIG9uIHRoZSBpY29uIHRvIG9wZW4gaXQ6CiAgCiAgPHAgYWxpZ249ImNlbnRlciI+CiAgPGltZyBzcmM9aW1nL3Jwcm9qLWRyb3Bkb3duLnBuZyB3aWR0aD0iMjUwIj4KICA8L3A+CgogIAotIE5vdywgY2xpY2sgb24gdGhlIGBtYXJrZG93bmAgZGlyZWN0b3J5IGluIHRoZSAqRmlsZXMqIHBhbmUsCiAgYW5kIG9wZW4gdGhlIGZpbGUgYDA3LUFTVi1pbmZlcmVuY2UuUm1kYC4gVGhhdCdzIHRoaXMgZmlsZSEKCi0tLS0tCgojIyBTdGVwIDE6IEdldHRpbmcgU3RhcnRlZAoKUHJpbnQgc29tZSBpbmZvcm1hdGlvbiB0byBzY3JlZW4gZm9yIHdoZW4gcnVubmluZyB0aGlzIHNjcmlwdCBhcyBhIGpvYjoKCmBgYHtyIGhlbGxvfQpwcmludCgnU3RhcnRpbmcgQVNWIGluZmVyZW5jZSBzY3JpcHQuLi4nKQpTeXMudGltZSgpCgpjYXQoJ1dvcmtpbmcgZGlyZWN0b3J5OicsIGdldHdkKCksICdcbicpCmBgYAoKIyMjIFNldCB0aGUgbnVtYmVyIG9mIGNvcmVzCgpNb3N0IGRhZGEyIGZ1bmN0aW9ucyBjYW4gdXNlIG11bHRpcGxlIGNvcmVzLgpCZWNhdXNlIHdlIGFyZSBvbiBhIGNsdXN0ZXIgYW5kIHdlIGhhdmUgcmVzZXJ2ZWQgb25seSBwYXJ0IG9mIGEgbm9kZSwKYXV0by1kZXRlY3Rpb24gb2YgdGhlIG51bWJlciBvZiBjb3JlcyB3aWxsIG5vdCBiZSBhcHByb3ByaWF0ZQoodGhlIHByb2dyYW0gd2lsbCBkZXRlY3QgbW9yZSBjb3JlcyB0aGFuIHdlIGhhdmUgYXZhaWxhYmxlKS4KVGhlcmVmb3JlLCB3ZSBzaG91bGQgc3BlY2lmeSB0aGUgYXBwcm9wcmlhdGUgbnVtYmVyIG9mIGNvcmVzIGluIG91ciBmdW5jdGlvbgpjYWxsIGJlbG93LgoKV2Ugd2lsbCBzZXQgdGhlIG51bWJlciBvZiBjb3JlcyBoZXJlLAphc3N1bWVzIHlvdSByZXF1ZXN0ZWQgNCBjb3JlcyBpbiB5b3VyIGpvYiBzdWJtaXNzaW9uIChjaGFuZ2UgaWYgbmVlZGVkKToKCmBgYHtyIG5jb3Jlc30Kbl9jb3JlcyA8LSA0CmBgYAoKIyMjIEluc3RhbGwgYW5kIGxvYWQgcGFja2FnZXMKClRvIHNhdmUgdGltZSwgd2UgaGF2ZSBhbHJlYWR5IGluc3RhbGxlZCBhbGwgdGhlIG5lY2Vzc2FyeSBSIHBhY2thZ2VzIGF0IE9TQwppbnRvIGEgY3VzdG9tIGxpYnJhcnkuClRvIGFkZCB0aGlzIGxpYnJhcnkgZm9yIHRoZSBjdXJyZW50IFIgc2Vzc2lvbjoKCmBgYHtyIGxpYl9sb2FkfQoubGliUGF0aHMobmV3ID0gJy9mcy9wcm9qZWN0L1BBUzA0NzEvLlIvNC4wLycpCmBgYAoKVGhlbiwgbG9hZCB0aGUgcGFja2FnZXM6CgpgYGB7ciBwYWNrYWdlX2xvYWR9CnByaW50KCdMb2FkaW5nIHBhY2thZ2VzLi4uJykKCnBhY2thZ2VzIDwtIGMoJ3RpZHl2ZXJzZScsICdncmlkRXh0cmEnLCAnZGFkYTInLAogICAgICAgICAgICAgICdwaHlsb3NlcScsICdERUNJUEhFUicsICdwaGFuZ29ybicpCnBhY21hbjo6cF9sb2FkKGNoYXIgPSBwYWNrYWdlcykKCiMgSWYgeW91IHdhbnRlZCB0byBpbnN0YWxsIGFuZCBsb2FkIHRoZXNlIHBhY2thZ2VzIHlvdXJzZWxmLAojIGp1c3QgbWFrZSBzdXJlIHlvdSBoYXZlIHRoZSBwYWNtYW4gcGFja2FnZSBpbnN0YWxsZWQ6CiMjIGluc3RhbGwucGFja2FnZXMoJ3BhY21hbicpCiMgVGhlbiwgdGhlIGNvZGUgYWJvdmUgd291bGQgd29yaywgYXMgaXQgd2lsbCBpbnN0YWxsIHBhY2FrZXMgYXMgbmVlZGVkLgpgYGAKCiMjIyBTZXQgdGhlIGZpbGUgcGF0aHMKCldlJ2xsIHNldCBtb3N0IG9mIHRoZSBmaWxlIHBhdGhzIHVwZnJvbnQsCndoaWNoIHdpbGwgbWFrZSBpdCBlYXNpZXIgdG8gY2hhbmdlIHRoaW5ncyBvciB0cm91Ymxlc2hvb3QuCgpgYGB7ciBzZXRfZGlyc30KIyBEaXIgd2l0aCBpbnB1dCBmYXN0cSBmaWxlczoKaW5kaXIgPC0gJ2RhdGEvcHJvY2Vzc2VkL2Zhc3RxX3RyaW1tZWQnCgojIERpcnMgZm9yIG91dHB1dDoKZmlsdGVyX2RpciA8LSAnZGF0YS9wcm9jZXNzZWQvZmFzdHFfZmlsdGVyZWQnCm91dGRpciA8LSAnYW5hbHlzaXMvQVNWX2luZmVyZW5jZScKCmRpci5jcmVhdGUoZmlsdGVyX2Rpciwgc2hvd1dhcm5pbmdzID0gRkFMU0UsIHJlY3Vyc2l2ZSA9IFRSVUUpCmRpci5jcmVhdGUob3V0ZGlyLCBzaG93V2FybmluZ3MgPSBGQUxTRSwgcmVjdXJzaXZlID0gVFJVRSkKCiMgRmFzdGEgZmlsZSB3aXRoIHRyYWluaW5nIGRhdGE6CiMgKENoZWNrIGZvciBhbiB1cC10by1kYXRlIHZlcnNpb24gYXQgPGh0dHBzOi8vYmVuampuZWIuZ2l0aHViLmlvL2RhZGEyL3RyYWluaW5nLmh0bWw+KQp0YXhfa2V5IDwtICdkYXRhL3JlZi9zaWx2YV9ucjk5X3YxMzhfdHJhaW5fc2V0LmZhJyAKCiMgRmlsZSB3aXRoIHNhbXBsZSBtZXRhZGF0YToKbWV0YWRhdGFfZmlsZSA8LSAnbWV0YWRhdGEvc2FtcGxlX21ldGEudHh0JwpgYGAKCiMjIyBBc3NpZ24gZmFzdHEgZmlsZXMgdG8gZm9yd2FyZCBhbmQgcmV2ZXJzZSByZWFkcyAKCldlIHdpbGwgYXNzaWduIHRoZSBmYXN0cSBmaWxlcyB0aGF0IHdlIHByb2Nlc3NlZCB3aXRoIGBjdXRhZGFwdGAgdG8gdHdvIHZlY3RvcnM6Cm9uZSB3aXRoIGZpbGVzIHdpdGggZm9yd2FyZCByZWFkcywgYW5kIG9uZSB3aXRoIGZpbGVzIHdpdGggcmV2ZXJzZSByZWFkcy4KVGhlc2UgZmlsZXMgY2FuIGJlIGRpc3Rpbmd1aXNoZWQgYnkgaGF2aW5nICJSMSIgKGZvcndhcmQpIGFuZCAiUjIiIChyZXZlcnNlKQppbiB0aGVpciBuYW1lcy4KCmBgYHtyIGZhc3RxX3BhdGhzfQpmYXN0cXNfcmF3X0YgPC0gc29ydChsaXN0LmZpbGVzKGluZGlyLCBwYXR0ZXJuID0gJ19SMV8wMDEuZmFzdHEuZ3onLCBmdWxsLm5hbWVzID0gVFJVRSkpCmZhc3Rxc19yYXdfUiA8LSBzb3J0KGxpc3QuZmlsZXMoaW5kaXIsIHBhdHRlcm4gPSAnX1IyXzAwMS5mYXN0cS5neicsIGZ1bGwubmFtZXMgPSBUUlVFKSkKCnByaW50KCdGaXJzdCBmYXN0cSBmaWxlczonKQpoZWFkKGZhc3Rxc19yYXdfRikKYGBgCgojIyMgQ2hlY2sgc2FtcGxlIElEcwoKV2UnbGwgZ2V0IHRoZSBzYW1wbGUgSURzIGZyb20gdGhlIGZhc3RxIGZpbGUgbmFtZXMgYW5kIGZyb20gYSBmaWxlIHdpdGggbWV0YWRhdGEsCmFuZCB3aWxsIGNoZWNrIGlmIHRoZXkgYXJlIHRoZSBzYW1lLiBGaXJzdCB3ZSdsbCBwcmVwYXJlIHRoZSBtZXRhZGF0YToKCmBgYHtyIG1ldGFkYXRhfQpwcmludCgnTG9hZCBhbmQgcHJlcGFyZSBzYW1wbGUgbWV0YWRhdGEuLi4nKQoKbWV0YWRhdGFfZGYgPC0gcmVhZC50YWJsZShmaWxlID0gbWV0YWRhdGFfZmlsZSwgc2VwID0gIlx0IiwgaGVhZGVyID0gVFJVRSkKCmNvbG5hbWVzKG1ldGFkYXRhX2RmKVsxXSA8LSAnU2FtcGxlSUQnCnJvd25hbWVzKG1ldGFkYXRhX2RmKSA8LSBtZXRhZGF0YV9kZiRTYW1wbGVJRAoKcHJpbnQoJ0ZpcnN0IGZhc3RxIGZpbGVzOicpCmhlYWQobWV0YWRhdGFfZGYpCmBgYAoKTGV0J3MgY29tcGFyZSB0aGUgc2FtcGxlIElEcyBmcm9tIHRoZSBtZXRhZGF0YSB3aXRoIHRoZSAqZmFzdHEqIGZpbGVuYW1lczoKCmBgYHtyIGNoZWNrX21ldGFkYXRhfQpwcmludCgnSURzIGZyb20gbWV0YWRhdGE6JykKbWV0YWRhdGFfZGYkU2FtcGxlSUQKCnByaW50KCdGYXN0cSBmaWxlIG5hbWVzOicpCmhlYWQoYmFzZW5hbWUoZmFzdHFzX3Jhd19GKSkgICAgIyBiYXNlbmFtZSgpIHN0cmlwcyB0aGUgZGlyIG5hbWUgZnJvbSB0aGUgZmlsZW5hbWUKYGBgCgpUbyBleHRyYWN0IHRoZSBzYW1wbGUgSURzIGZyb20gdGhlICpmYXN0cSogZmlsZSBuYW1lcywgd2UgcmVtb3ZlIGV2ZXJ5dGhpbmcgYWZ0ZXIKIi1WNC1WNSIgZnJvbSB0aGUgZmlsZSBuYW1lcyB1c2luZyB0aGUgYHN1YigpYCBmdW5jdGlvbjoKCmBgYHtyIHNhbXBsZUlEc30KIyBzdWIoKSBhcmd1bWVudHM6IHN1YihwYXR0ZXJuLCByZXBsYWNlbWVudCwgdmVjdG9yKQpzYW1wbGVJRHMgPC0gc3ViKCItVjQtVjVfLioiLCAiIiwgYmFzZW5hbWUoZmFzdHFzX3Jhd19GKSkKYGBgCgpXZSBjYW4gY2hlY2sgd2hldGhlciB0aGUgSURzIGZyb20gdGhlICpmYXN0cSogZmlsZXMgYW5kIHRoZSBtZXRhZGF0YSBkYXRhZnJhbWUKYXJlIHRoZSBzYW1lOgoKYGBge3IgY2hlY2tfc2FtcGxlSURzfQpwcmludCgnQXJlIHRoZSBzYW1wbGUgSURzIGZyb20gdGhlIG1ldGFkYXRhIGFuZCB0aGUgZmFzdHEgZmlsZXMgdGhlIHNhbWU/JykKaWRlbnRpY2FsKHNvcnQobWV0YWRhdGFfZGYkU2FtcGxlSUQpLCBzYW1wbGVJRHMpCgpwcmludCgnQXJlIGFueSBzYW1wbGVzIG1pc3NpbmcgZnJvbSB0aGUgZmFzdHEgZmlsZXM/JykKc2V0ZGlmZihzb3J0KG1ldGFkYXRhX2RmJFNhbXBsZUlEKSwgc2FtcGxlSURzKQoKcHJpbnQoJ0FyZSBhbnkgc2FtcGxlcyBtaXNzaW5nIGZyb20gdGhlIG1ldGFkYXRhPycpCnNldGRpZmYoc2FtcGxlSURzLCBzb3J0KG1ldGFkYXRhX2RmJFNhbXBsZUlEKSkKYGBgCgpBcyBpdCB0dXJucyBvdXQsIHdlIGRvbid0IGhhdmUgc2VxdWVuY2VzIGZvciB0aHJlZSBzYW1wbGVzIGluIHRoZSBtZXRhZGF0YS4KCi0tLS0tCgojIyBTdGVwIDI6IFFDCgojIyMgUGxvdCBzZXF1ZW5jZSBxdWFsaXR5IGRhdGEKCipEQURBMiogcHJvdmlkZSBhIGZ1bmN0aW9uIHRvIHBsb3QgdGhlIGF2ZXJhZ2UgYmFzZSBxdWFsaXR5IGFjcm9zcyBzZXF1ZW5jZSByZWFkcywKYHBsb3RRdWFsaXR5UHJvZmlsZSgpYC4gWW91IGNhbiBnZW5lcmF0ZSBhbmQgZXZhbHVhdGUgcGxvdHMgZm9yIGVhY2ggc2FtcGxlLAplLmcuIHRoZSBmb3J3YXJkIHJlYWRzIGFuZCByZXZlcnNlIHJlYWRzIHNpZGUtYnktc2lkZSBsaWtlIHNvOgoKYGBge3IgcGxvdF9xdWFsXzF9CnBsb3RRdWFsaXR5UHJvZmlsZShjKGZhc3Rxc19yYXdfRlsxXSwgZmFzdHFzX3Jhd19SWzFdKSkKYGBgCgo8YnI+CgpUaGlzIGNvZGUgd2lsbCBnZW5lcmF0ZSBhIHBkZiBmaWxlIHdpdGggcGxvdHMgZm9yIGVhY2ggc2FtcGxlOgoKYGBge3IgcGxvdF9xdWFsXzJ9CnBkZihmaWxlLnBhdGgob3V0ZGlyLCAnZXJyb3JfcHJvZmlsZXMucGRmJykpICAgICAgICAgICAgICAgIyBPcGVuIGEgcGRmIGZpbGUKZm9yIChzYW1wbGVfaWR4IGluIDE6bGVuZ3RoKGZhc3Rxc19yYXdfRikpIHsgICAgICAgICAgICAgICAjIExvb3AgdGhyb3VnaCBzYW1wbGVzCiAgcHJpbnQocGxvdFF1YWxpdHlQcm9maWxlKCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBQcmludCBwbG90cyBpbnRvIHBkZgogICAgYyhmYXN0cXNfcmF3X0Zbc2FtcGxlX2lkeF0sIGZhc3Rxc19yYXdfUltzYW1wbGVfaWR4XSkpICMgRiBhbmQgUiB0b2dldGhlcgogICAgKQp9CmRldi5vZmYoKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDbG9zZSB0aGUgcGRmIGZpbGUKYGBgCgo8ZGV0YWlscz4KPHN1bW1hcnk+CiZuYnNwOyAgYHIgaWNvbjo6ZmEoImluZm8tY2lyY2xlIilgICZuYnNwOyBNb3JlIG9uIFFDIG9mICpmYXN0cSogZmlsZXMKPC9zdW1tYXJ5PgpJdCBpcyBhIGdvb2QgaWRlYSB0byBydW4gdGhlCltgZmFzdHFjYF0oaHR0cHM6Ly93d3cuYmlvaW5mb3JtYXRpY3MuYmFicmFoYW0uYWMudWsvcHJvamVjdHMvZmFzdHFjLykKcHJvZ3JhbSBvbiB5b3VyICpmYXN0cSogZmlsZXMgZm9yIG1vcmUgZXh0ZW5zaXZlIFFDLgpUaGlzIGlzIGEgc3RhbmQtYWxvbmUgcHJvZ3JhbSB0aGF0IGlzIGVhc3kgdG8gcnVuIGZyb20gdGhlIGNvbW1hbmQtbGluZS4KV2hlbiB5b3UgaGF2ZSBtYW55IHNhbXBsZXMsIGFzIGlzIG9mdGVuIHRoZSBjYXNlLApgZmFzdHFjYCdzIHJlc3VsdHMgY2FuIG1vcmVvdmVyIGJlIG5pY2VseSBzdW1tYXJpemVkIHVzaW5nCltgbXVsdGlxY2BdKGh0dHBzOi8vbXVsdGlxYy5pbmZvLy4KSW4gdGhlIGludGVyZXN0IG9mIHRpbWUsIHdlIHNraXBwZWQgdGhlc2Ugc3RlcHMgZHVyaW5nIHRoaXMgd29ya3Nob3AuCjwvZGV0YWlscz4KCi0tLS0tCgojIyBTdGVwIDM6IEZpbHRlcmluZyBhbmQgUXVhbGl0eSBUcmltbWluZwoKV2Ugd2lsbCBub3cgcGVyZm9ybSBxdWFsaXR5IGZpbHRlcmluZyAocmVtb3ZpbmcgcG9vci1xdWFsaXR5IHJlYWRzKQphbmQgdHJpbW1pbmcgKHJlbW92aW5nIHBvb3ItcXVhbGl0eSBiYXNlcykgb24gdGhlICpmYXN0cSogZmlsZXMKdXNpbmcgKkRBREEyKidzIGBmaWx0ZXJBbmRUcmltKClgIGZ1bmN0aW9uLgoKVGhlIGBmaWx0ZXJBbmRUcmltKClgIGZ1bmN0aW9uIHdpbGwgd3JpdGUgdGhlIGZpbHRlcmVkIGFuZCB0cmltbWVkIHJlYWRzCnRvIG5ldyAqZmFzdHEqIGZpbGVzLgpUaGVyZWZvcmUsIHdlIGZpcnN0IGRlZmluZSB0aGUgZmlsZSBuYW1lcyBmb3IgdGhlIG5ldyBmaWxlczoKCmBgYHtyIGZhc3RxX2ZpbHRfcGF0aHN9CmZhc3Rxc19maWx0X0YgPC0gZmlsZS5wYXRoKGZpbHRlcl9kaXIsIHBhc3RlMChzYW1wbGVJRHMsICdfRl9maWx0LmZhc3RxJykpCmZhc3Rxc19maWx0X1IgPC0gZmlsZS5wYXRoKGZpbHRlcl9kaXIsIHBhc3RlMChzYW1wbGVJRHMsICdfUl9maWx0LmZhc3RxJykpCmBgYAoKPGJyPgoKVGhlIGB0cnVuY0xlbmAgYXJndW1lbnQgb2YgYGZpbHRlckFuZFRyaW0oKWAgZGVmaW5lcyB0aGUgcmVhZCBsZW5ndGhzCihmb3IgZm9yd2FyZCBhbmQgcmV2ZXJzZSByZWFkcywgcmVzcGVjdGl2ZWx5KQpiZXlvbmQgd2hpY2ggYWRkaXRpb25hbCBiYXNlcyBzaG91bGQgYmUgcmVtb3ZlZCwKYW5kIHRoZXNlIHZhbHVlcyBzaG91bGQgYmUgYmFzZWQgb24gdGhlIHNlcXVlbmNlIHF1YWxpdHkgdmlzdWFsaXplZCBhYm92ZS4KVGhlIHRyaW1taW5nIGxlbmd0aCBjYW4gdGh1cyBiZSBkaWZmZXJlbnQgZm9yIGZvcndhcmQgYW5kIHJldmVyc2UgcmVhZHMsCndoaWNoIGlzIGdvb2QgYmVjYXVzZSByZXZlcnNlIHJlYWRzIGFyZSBvZnRlbiBvZiB3b3JzZSBxdWFsaXR5LgoKSXQgaXMgYWxzbyBzdWdnZXN0ZWQgdG8gdHJpbSB0aGUgZmlyc3QgMTAgbnVjbGVvdGlkZXMgb2YgZWFjaCByZWFkCihgdHJpbUxlZnRgIGFyZ3VtZW50KSwKc2luY2UgdGhlc2UgcG9zaXRpb25zIGFyZSBsaWtlbHkgdG8gY29udGFpbiBlcnJvcnMuCgpgbWF4RUVgIGlzIGFuIGltcG9ydGFudCBhcmd1bWVudCB0aGF0IHdpbGwgbGV0IERBREEyIHRyaW0gcmVhZHMgYmFzZWQgb24gdGhlCm1heGltdW0gbnVtYmVycyBvZiBFeHBlY3RlZCBFcnJvcnMgKEVFKSBnaXZlbiB0aGUgcXVhbGl0eSBzY29yZXMgb2YgdGhlIHJlYWRzJwpiYXNlcy4KCmBgYHtyIGZhc3RxX2ZpbHRlcmluZ30KcHJpbnQoJ0ZpbHRlcmluZyBhbmQgVHJpbW1pbmcuLi4nKQpTeXMudGltZSgpICAjIFByaW50IHRoZSB0aW1lIHRvIGtlZXAgdHJhY2sgb2YgcnVubmluZyB0aW1lIGZvciBpbmRpdmlkdWFsIHN0ZXBzIApmaWx0ZXJfcmVzdWx0cyA8LQogIGZpbHRlckFuZFRyaW0oZmFzdHFzX3Jhd19GLCBmYXN0cXNfZmlsdF9GLAogICAgICAgICAgICAgICAgZmFzdHFzX3Jhd19SLCBmYXN0cXNfZmlsdF9SLAogICAgICAgICAgICAgICAgdHJ1bmNMZW4gPSBjKDI1MCwyMTApLAogICAgICAgICAgICAgICAgdHJpbUxlZnQgPSAxMCwKICAgICAgICAgICAgICAgIG1heE4gPSAwLAogICAgICAgICAgICAgICAgbWF4RUUgPSBjKDIsMiksCiAgICAgICAgICAgICAgICB0cnVuY1EgPSAyLAogICAgICAgICAgICAgICAgcm0ucGhpeCA9IEZBTFNFLAogICAgICAgICAgICAgICAgbXVsdGl0aHJlYWQgPSBuX2NvcmVzLCAKICAgICAgICAgICAgICAgIGNvbXByZXNzID0gRkFMU0UsIHZlcmJvc2UgPSBUUlVFKSAKcHJpbnQoJy4uLkRvbmUhJykKU3lzLnRpbWUoKQoKaGVhZChmaWx0ZXJfcmVzdWx0cykKYGBgCgotLS0tLQoKIyMgU3RlcCA0OiBEZXJlcGxpY2F0aW9uICYgRXJyb3IgVHJhaW5pbmcKCk5leHQsIHdlIHdhbnQgdG8gImRlcmVwbGljYXRlIiB0aGUgZmlsdGVyZWQgKmZhc3RxKiBmaWxlcy4KRHVyaW5nIGRlcmVwbGljYXRpb24sIHdlIGNvbmRlbnNlIHRoZSBkYXRhIGJ5IGNvbGxhcHNpbmcgdG9nZXRoZXIgYWxsIHJlYWRzIHRoYXQKZW5jb2RlIHRoZSBzYW1lIHNlcXVlbmNlLCB3aGljaCBzaWduaWZpY2FudGx5IHJlZHVjZXMgbGF0ZXIgY29tcHV0YXRpb24gdGltZXMuCgpgYGB7ciBmYXN0cV9kZXJlcH0KZmFzdHFzX2RlcmVwX0YgPC0gZGVyZXBGYXN0cShmYXN0cXNfZmlsdF9GLCB2ZXJib3NlID0gRkFMU0UpCmZhc3Rxc19kZXJlcF9SIDwtIGRlcmVwRmFzdHEoZmFzdHFzX2ZpbHRfUiwgdmVyYm9zZSA9IEZBTFNFKQoKbmFtZXMoZmFzdHFzX2RlcmVwX0YpIDwtIHNhbXBsZUlEcwpuYW1lcyhmYXN0cXNfZGVyZXBfUikgPC0gc2FtcGxlSURzCmBgYAoKYGBge3IsIGVjaG89RkFMU0V9CnNhdmVSRFMoZmFzdHFzX2RlcmVwX0YsIGZpbGUgPSBmaWxlLnBhdGgob3V0ZGlyLCAnZmFzdHFzX2RlcmVwX0YucmRzJykpCnNhdmVSRFMoZmFzdHFzX2RlcmVwX1IsIGZpbGUgPSBmaWxlLnBhdGgob3V0ZGlyLCAnZmFzdHFzX2RlcmVwX1IucmRzJykpCgojIGZhc3Rxc19kZXJlcF9GIDwtIHJlYWRSRFMoZmlsZS5wYXRoKG91dGRpciwgJ2Zhc3Rxc19kZXJlcF9GLnJkcycpKQojIGZhc3Rxc19kZXJlcF9SIDwtIHJlYWRSRFMoZmlsZS5wYXRoKG91dGRpciwgJ2Zhc3Rxc19kZXJlcF9SLnJkcycpKQpgYGAKClRoZSAqREFEQTIqIGFsZ29yaXRobSBtYWtlcyB1c2Ugb2YgYSBwYXJhbWV0cmljIGVycm9yIG1vZGVsIChgZXJyYCkgYW5kIGV2ZXJ5CmFtcGxpY29uIGRhdGFzZXQgaGFzIGEgZGlmZmVyZW50IHNldCBvZiBlcnJvciByYXRlcy4KVGhlIGBsZWFybkVycm9yc2AgbWV0aG9kIGxlYXJucyB0aGlzIGVycm9yIG1vZGVsIGZyb20gdGhlIGRhdGEsCmJ5IGFsdGVybmF0aW5nIGVzdGltYXRpb24gb2YgdGhlIGVycm9yIHJhdGVzIGFuZCBpbmZlcmVuY2Ugb2Ygc2FtcGxlIGNvbXBvc2l0aW9uCnVudGlsIHRoZXkgY29udmVyZ2Ugb24gYSBqb2ludGx5IGNvbnNpc3RlbnQgc29sdXRpb24uCgpgYGB7ciBsZWFybl9lcnJvcnN9CnByaW50KCdMZWFybmluZyBlcnJvcnMuLi4nKQpTeXMudGltZSgpCgplcnJfRiA8LSBsZWFybkVycm9ycyhmYXN0cXNfZGVyZXBfRiwgbXVsdGl0aHJlYWQgPSBuX2NvcmVzLCB2ZXJib3NlID0gVFJVRSkKZXJyX1IgPC0gbGVhcm5FcnJvcnMoZmFzdHFzX2RlcmVwX1IsIG11bHRpdGhyZWFkID0gbl9jb3JlcywgdmVyYm9zZSA9IFRSVUUpCgpwcmludCgnLi4uRG9uZSEnKQpTeXMudGltZSgpCmBgYAoKYGBge3IsIGVjaG89RkFMU0V9CnNhdmVSRFMoZXJyX0YsIGZpbGUgPSBmaWxlLnBhdGgob3V0ZGlyLCAnZXJyX0YucmRzJykpCnNhdmVSRFMoZXJyX1IsIGZpbGUgPSBmaWxlLnBhdGgob3V0ZGlyLCAnZXJyX1IucmRzJykpCiMgZXJyX0YgPC0gcmVhZFJEUyhmaWxlLnBhdGgob3V0ZGlyLCAnZXJyX0YucmRzJykpCiMgZXJyX1IgPC0gcmVhZFJEUyhmaWxlLnBhdGgob3V0ZGlyLCAnZXJyX1IucmRzJykpCmBgYAoKPGJyPgoKV2UnbGwgcGxvdCBlcnJvcnMgdG8gdmVyaWZ5IHRoYXQgZXJyb3IgcmF0ZXMgaGF2ZSBiZWVuIHJlYXNvbmFibGUgd2VsbC1lc3RpbWF0ZWQuClBheSBhdHRlbnRpb24gdG8gdGhlIGZpdCBiZXR3ZWVuIG9ic2VydmVkIGVycm9yIHJhdGVzIChwb2ludHMpIGFuZCBmaXR0ZWQgZXJyb3IKcmF0ZXMgKGxpbmVzKToKCmBgYHtyLCBwbG90X2Vycm9yc30KcGxvdEVycm9ycyhlcnJfRiwgbm9taW5hbFEgPSBUUlVFKQpwbG90RXJyb3JzKGVycl9SLCBub21pbmFsUSA9IFRSVUUpCmBgYAoKLS0tLS0KCiMjIFN0ZXAgNTogSW5mZXIgQVNWcwoKV2Ugd2lsbCBub3cgcnVuIHRoZSBjb3JlICpkYWRhKiBhbGdvcml0aG0sCndoaWNoIGluZmVycyBBbXBsaWNvbiBTZXF1ZW5jZSBWYXJpYW50cyAoQVNWcykgZnJvbSB0aGUgc2VxdWVuY2VzLgoKVGhpcyBzdGVwIGlzIHF1aXRlIGNvbXB1dGF0aW9uYWxseSBpbnRlbnNpdmUsCmFuZCBmb3IgdGhpcyB0dXRvcmlhbCwgd2Ugd2lsbCB0aGVyZWZvcmUgcGVyZm9ybSBpbmRlcGVuZGVudCBpbmZlcmVuY2UgZm9yIGVhY2gKc2FtcGxlIChgcG9vbCA9IEZBTFNFYCksIHdoaWNoIHdpbGwga2VlcCB0aGUgY29tcHV0YXRpb24gdGltZSBkb3duLgoKUG9vbGluZyB3aWxsIGluY3JlYXNlIGNvbXB1dGF0aW9uIHRpbWUsIGVzcGVjaWFsbHkgaWYgeW91IGhhdmUgbWFueSBzYW1wbGVzLApidXQgd2lsbCBpbXByb3ZlIGRldGVjdGlvbiBvZiByYXJlIHZhcmlhbnRzIHNlZW4gb25jZSBvciB0d2ljZSBpbiBhbiBpbmRpdmlkdWFsIHNhbXBsZSwKYnV0IG1hbnkgdGltZXMgYWNyb3NzIGFsbCBzYW1wbGVzLgpUaGVyZWZvcmUsIGZvciB5b3VyIG93biBhbmFseXNpcywgeW91IHdpbGwgbGlrZWx5IHdhbnQgdG8gdXNlIHBvb2xpbmcsCnRob3VnaCBbInBzZXVkby1wb29saW5nIiBpcyBhbHNvIGFuIG9wdGlvbl0oaHR0cHM6Ly9iZW5qam5lYi5naXRodWIuaW8vZGFkYTIvcHNldWRvLmh0bWwpLgoKYGBge3IgcnVuX2RhZGF9CnByaW50KCdJbmZlcnJpbmcgQVNWcyAocnVubmluZyB0aGUgZGFkYSBhbGdvcml0aG0pLi4uJykKU3lzLnRpbWUoKQoKZGFkYV9GcyA8LSBkYWRhKGZhc3Rxc19kZXJlcF9GLCBlcnIgPSBlcnJfRiwgcG9vbCA9IEZBTFNFLCBtdWx0aXRocmVhZCA9IG5fY29yZXMpCmRhZGFfUnMgPC0gZGFkYShmYXN0cXNfZGVyZXBfUiwgZXJyID0gZXJyX1IsIHBvb2wgPSBGQUxTRSwgbXVsdGl0aHJlYWQgPSBuX2NvcmVzKQoKcHJpbnQoJy4uLkRvbmUuJykKU3lzLnRpbWUoKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpzYXZlUkRTKGRhZGFfRnMsIGZpbGUgPSBmaWxlLnBhdGgob3V0ZGlyLCAnZGFkYV9Gcy5yZHMnKSkKc2F2ZVJEUyhkYWRhX1JzLCBmaWxlID0gZmlsZS5wYXRoKG91dGRpciwgJ2RhZGFfUnMucmRzJykpCiMgZGFkYV9GcyA8LSByZWFkUkRTKGZpbGUucGF0aChvdXRkaXIsICdkYWRhX0ZzLnJkcycpKQojIGRhZGFfUnMgPC0gcmVhZFJEUyhmaWxlID0gZmlsZS5wYXRoKG91dGRpciwgJ2RhZGFfUnMucmRzJykpCmBgYAoKTGV0J3MgaW5zcGVjdCBvbmUgb2YgdGhlIHJlc3VsdGluZyBvYmplY3RzOgoKYGBgYHtyLCBjaGVja19kYWRhfQpkYWRhX0ZzW1sxXV0KYGBgCgotLS0tLQoKIyMgU3RlcCA2OiBNZXJnZSBSZWFkIFBhaXJzCgpJbiB0aGlzIHN0ZXAsIHdlIHdpbGwgZmlyc3QgbWVyZ2UgdGhlIGZvcndhcmQgYW5kIHJldmVyc2UgcmVhZCBwYWlyczoKdGhlIGZyYWdtZW50IHRoYXQgd2UgYW1wbGlmaWVkIHdpdGggb3VyIHByaW1lcnMgd2FzIHNob3J0IGVub3VnaAp0byBnZW5lcmF0ZSBsb3RzIG9mIG92ZXJsYXAgYW1vbmcgdGhlIHNlcXVlbmNlcyBmcm9tIHRoZSB0d28gZGlyZWN0aW9ucy4KCmBgYHtyIG1lcmdlX3BhaXJzfQptZXJnZXJzIDwtIG1lcmdlUGFpcnMoZGFkYV9GcywgZmFzdHFzX2RlcmVwX0YsCiAgICAgICAgICAgICAgICAgICAgICBkYWRhX1JzLCBmYXN0cXNfZGVyZXBfUiwKICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBUUlVFKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpzYXZlUkRTKG1lcmdlcnMsIGZpbGUgPSBmaWxlLnBhdGgob3V0ZGlyLCAnbWVyZ2Vycy5yZHMnKSkKIyBtZXJnZXJzIDwtIHJlYWRSRFMoZmlsZS5wYXRoKG91dGRpciwgJ21lcmdlcnMucmRzJykpCmBgYAoKCjxicj4KCkp1c3QgbGlrZSB0YWJsZXMgY2FuIGJlIHNhdmVkIGluIFIgdXNpbmcgYHdyaXRlLnRhYmxlYCBvciBgd3JpdGUuY3N2YCwKUiAqb2JqZWN0cyogY2FuIGJlIHNhdmVkIHVzaW5nIGBzYXZlUkRTYC4KVGhlIHJlc3VsdGluZyAqcmRzKiBmaWxlIGNhbiB0aGVuIGJlIGxvYWRlZCBpbnRvIGFuIFIgZW52aXJvbm1lbnQgdXNpbmcgYHJlYWRSRFNgLgpUaGlzIGlzIGEgY29udmVuaWVudCB3YXkgdG8gc2F2ZSBSIG9iamVjdHMgdGhhdCByZXF1aXJlIGEgbG90IG9mIGNvbXB1dGF0aW9uIHRpbWUuCgpXZSBzaG91bGQgbm90IGJlIG5lZWRpbmcgdGhlIHZlcnkgbGFyZ2UgZGVyZXBsaWNhdGVkIHNlcXVlbmNlIG9iamVjdHMgYW55bW9yZSwKYnV0IHRvIGJlIGFibGUgdG8gcXVpY2tseSByZXN0YXJ0IG91ciBhbmFseXNpcyBmcm9tIGEgbmV3IFIgc2Vzc2lvbgppZiBuZWNlc3NhcnksIHdlIG5vdyBzYXZlIHRoZXNlIG9iamVjdHMgdG8gKnJkcyogZmlsZXMuCkFuZCBhZnRlciB0aGF0LCB3ZSBjYW4gc2FmZWx5IHJlbW92ZSB0aGVzZSBvYmplY3RzIGZyb20gb3VyIGVudmlyb25tZW50LgoKYGBge3IsIHNhdmVfUkRTfQpzYXZlUkRTKGZhc3Rxc19kZXJlcF9GLCBmaWxlID0gZmlsZS5wYXRoKG91dGRpciwgJ2Zhc3Rxc19kZXJlcF9GLnJkcycpKQpzYXZlUkRTKGZhc3Rxc19kZXJlcF9SLCBmaWxlID0gZmlsZS5wYXRoKG91dGRpciwgJ2Zhc3Rxc19kZXJlcF9SLnJkcycpKQoKcm0oZmFzdHFzX2RlcmVwX0YsIGZhc3Rxc19kZXJlcF9SKSAjIFJlbW92ZSBvYmplY3RzIGZyb20gZW52aXJvbm1lbnQKYGBgCgotLS0tLQoKIyMgU3RlcCA3OiBDb25zdHJ1Y3QgYSBTZXF1ZW5jZSBUYWJsZQoKTmV4dCwgd2UgY29uc3RydWN0IGFuIGFtcGxpY29uIHNlcXVlbmNlIHZhcmlhbnQgdGFibGUgKEFTVikgdGFibGU6CgpgYGB7ciBtYWtlX3NlcXRhYn0Kc2VxdGFiX2FsbCA8LSBtYWtlU2VxdWVuY2VUYWJsZShtZXJnZXJzKQoKIyBUaGUgZGltZW5zaW9ucyBvZiB0aGUgb2JqZWN0IGFyZSB0aGUgbnIgb2Ygc2FtcGxlcyAocm93cykgYW5kIHRoZSBuciBvZiBBU1ZzIChjb2x1bW5zKToKZGltKHNlcXRhYl9hbGwpCmBgYAoKTGV0J3MgaW5zcGVjdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHNlcXVlbmNlIGxlbmd0aHM6CgpgYGB7ciBjaGVja19zZXF0YWJ9CnRhYmxlKG5jaGFyKGdldFNlcXVlbmNlcyhzZXF0YWJfYWxsKSkpCgojIElmIHlvdSBuZWVkIHRvIHJlbW92ZSBzZXF1ZW5jZXMgb2YgYSBwYXJ0aWN1bGFyIGxlbmd0aCAoZS5nLiB0b28gbG9uZyk6CiMgc2VxdGFiMiA8LSBzZXF0YWJbLCBuY2hhcihjb2xuYW1lcyhzZXF0YWJfYWxsKSkgJWluJSBzZXEoMjUwLDI1NildCmBgYAoKCi0tLS0tCgojIyBTdGVwIDg6IFJlbW92ZSBDaGltZXJhcwoKTm93LCB3ZSB3aWxsIHJlbW92ZSBjaGltZXJhcy4KVGhlICpkYWRhKiBhbGdvcml0aG0gbW9kZWxzIGFuZCByZW1vdmVzIHN1YnN0aXR1dGlvbiBlcnJvcnMsIGJ1dCBjaGltZXJhcwphcmUgYW5vdGhlciBpbXBvcnRhbmNlIHNvdXJjZSBvZiBzcHVyaW91cyBzZXF1ZW5jZXMgaW4gYW1wbGljb24gc2VxdWVuY2luZy4KQ2hpbWVyYXMgYXJlIFtmb3JtZWQgZHVyaW5nIFBDUiBhbXBsaWZpY2F0aW9uXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUMzMDQ0ODYzLykuCldoZW4gb25lIHNlcXVlbmNlIGlzIGluY29tcGxldGVseSBhbXBsaWZpZWQsIHRoZSBpbmNvbXBsZXRlIGFtcGxpY29uIHByaW1lcyB0aGUKbmV4dCBhbXBsaWZpY2F0aW9uIHN0ZXAsIHlpZWxkaW5nIGEgc3B1cmlvdXMgYW1wbGljb24uClRoZSByZXN1bHQgaXMgYSBzZXF1ZW5jZSByZWFkIHdoaWNoIGlzIGhhbGYgb2Ygb25lIHNhbXBsZSBzZXF1ZW5jZSBhbmQgaGFsZiBhbm90aGVyLgoKRm9ydHVuYXRlbHksIHRoZSBhY2N1cmFjeSBvZiB0aGUgc2VxdWVuY2UgdmFyaWFudHMgYWZ0ZXIgZGVub2lzaW5nIG1ha2VzCmlkZW50aWZ5aW5nIGNoaW1lcmFzIHNpbXBsZXIgdGhhbiBpdCBpcyB3aGVuIGRlYWxpbmcgd2l0aCBmdXp6eSBPVFVzLgpDaGltZXJpYyBzZXF1ZW5jZXMgYXJlIGlkZW50aWZpZWQgaWYgdGhleSBjYW4gYmUgZXhhY3RseSByZWNvbnN0cnVjdGVkIGJ5CmNvbWJpbmluZyBhIGxlZnQtc2VnbWVudCBhbmQgYSByaWdodC1zZWdtZW50IGZyb20gdHdvIG1vcmUgYWJ1bmRhbnQgInBhcmVudCIgc2VxdWVuY2VzLgoKYGBge3Igcm1fY2hpbX0Kc2VxdGFiIDwtIHJlbW92ZUJpbWVyYURlbm92byhzZXF0YWJfYWxsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICdjb25zZW5zdXMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11bHRpdGhyZWFkID0gbl9jb3JlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gVFJVRSkKbmNvbChzZXF0YWIpCgojIFByb3BvcnRpb24gb2YgcmV0YWluZWQgc2VxdWVuY2VzOgpzdW0oc2VxdGFiKSAvIHN1bShzZXF0YWJfYWxsKQpgYGAKCldlIHdpbGwgc2F2ZSB0aGUgYHNlcXRhYmAgb2JqZWN0IGFzIGFuICpyZHMqIGZpbGU6CgpgYGB7ciwgc2F2ZV9SRFNfMn0Kc2F2ZVJEUyhzZXF0YWIsIGZpbGUgPSBmaWxlLnBhdGgob3V0ZGlyLCAnc2VxdGFiX1Y0LnJkcycpKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFLCBldmFsPUZBTFNFLCBwdXJsPUZBTFNFfQojIHNlcXRhYiA8LSByZWFkUkRTKGZpbGUucGF0aChvdXRkaXIsICdzZXF0YWJfVjQucmRzJykpCmBgYAoKLS0tLS0KCiMjIFN0ZXAgOTogR2VuZXJhdGUgYSBTdW1tYXJ5IFRhYmxlCgpJbiB0aGlzIHN0ZXAsIHdlIHdpbGwgZ2VuZXJhdGUgYSBzdW1tYXJ5IHRhYmxlIG9mIHRoZSBudW1iZXIgb2Ygc2VxdWVuY2VzCnByb2Nlc3NlZCBhbmQgb3V0cHV0cyBvZiBkaWZmZXJlbnQgc3RlcHMgb2YgdGhlIHBpcGVsaW5lLgoKVGhpcyBpbmZvcm1hdGlvbiBpcyBnZW5lcmFsbHkgdXNlZCB0byBmdXJ0aGVyIGV2YWx1YXRlIGNoYXJhY3RlcmlzdGljcyBhbmQKcXVhbGl0eSBvZiB0aGUgcnVuLCBzYW1wbGUtdG8tc2FtcGxlIHZhcmlhdGlvbiwKYW5kIHJlc3VsdGluZyBzZXF1ZW5jaW5nIGRlcHRoIGZvciBlYWNoIHNhbXBsZS4gCgpUbyBnZXQgc3RhcnRlZCwgd2Ugd2lsbCBkZWZpbmUgYSBmdW5jdGlvbiBgZ2V0TigpYCB0aGF0IHdpbGwgZ2V0IHRoZSBudW1iZXIgb2YKdW5pcXVlIHJlYWRzIGZvciBhIHNhbXBsZS4KVGhlbiwgd2UgYXBwbHkgYGdldE4oKWAgdG8gZWFjaCBlbGVtZW50IG9mIHRoZSBgZGFkYV9Gc2AsIGBkYWRhX1JzYCwgYW5kIGBtZXJnZXJzYApvYmplY3RzLCB3aGljaCBnaXZlcyB1cyB2ZWN0b3JzIHdpdGggdGhlIG51bWJlciBvZiB1bmlxdWUgcmVhZHMgZm9yIGVhY2ggc2FtcGxlcywKZHVyaW5nIGVhY2ggb2YgdGhlc2Ugc3RlcHM6CgpgYGB7ciBnZXRfbn0KZ2V0TiA8LSBmdW5jdGlvbih4KSB7CiAgc3VtKGdldFVuaXF1ZXMoeCkpCn0KCmRlbm9pc2VkX0YgPC0gc2FwcGx5KGRhZGFfRnMsIGdldE4pCmRlbm9pc2VkX1IgPC0gc2FwcGx5KGRhZGFfUnMsIGdldE4pCm1lcmdlZCA8LSBzYXBwbHkobWVyZ2VycywgZ2V0TikKYGBgCgpXZSdsbCBqb2luIHRoZXNlIHZlY3RvcnMgdG9nZXRoZXIgd2l0aCB0aGUgImZpbHRlcl9yZXN1bHRzIiBkYXRhZnJhbWUsCmFuZCB0aGUgbnVtYmVyIG9mIG5vbmNoaW1lcmljIHJlYWRzOgoKYGBge3Igc3VtX3RhYmxlfQpucmVhZHNfc3VtbWFyeSA8LSBkYXRhLmZyYW1lKGZpbHRlcl9yZXN1bHRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlbm9pc2VkX0YsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVub2lzZWRfUiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXJnZWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm9uY2hpbSA9IHJvd1N1bXMoc2VxdGFiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSBzYW1wbGVJRHMpCmNvbG5hbWVzKG5yZWFkc19zdW1tYXJ5KVsxOjJdIDwtIGMoJ2lucHV0JywgJ2ZpbHRlcmVkJykKCiMgSGF2ZSBhIGxvb2sgYXQgdGhlIGZpcnN0IGZldyByb3dzOgpoZWFkKG5yZWFkc19zdW1tYXJ5KQpgYGAKCkZpbmFsbHksIHdlJ2xsIHdyaXRlIHRoaXMgdGFibGUgdG8gZmlsZToKCmBgYHtyfQp3cml0ZS50YWJsZShucmVhZHNfc3VtbWFyeSwgZmlsZSA9IGZpbGUucGF0aChvdXRkaXIsICducmVhZHNfc3VtbWFyeS50eHQnKSwKICAgICAgICAgICAgc2VwID0gIlx0IiwgcXVvdGUgPSBGQUxTRSwgcm93Lm5hbWVzID0gVFJVRSkKYGBgCgotLS0tLQoKIyMgU3RlcCAxMDogQXNzaWduIFRheG9ub215IHRvIEFTVnMKCk5vdywgd2Ugd2lsbCBhc3NpZ24gdGF4b25vbXkgdG8gb3VyIEFTVnMuCgpEZXBlbmRpbmcgb24gdGhlIG1hcmtlciBnZW5lIGFuZCB0aGUgZGF0YSwKeW91IHdpbGwgaGF2ZSB0byBjaG9vc2UgdGhlIGFwcHJvcHJpYXRlIHJlZmVyZW5jZSBmaWxlIGZvciB0aGlzIHN0ZXAuClNldmVyYWwgZmlsZXMgaGF2ZSBiZWVuIGZvcm1hdHRlZCBmb3IgdGF4b25vbXkgYXNzaWdubWVudHMgaW4gKkRBREEyKiBwaXBlbGluZSBhbmQKYXJlIGF2YWlsYWJsZSBhdCB0aGUgW0RBREEyIHdlYnNpdGVdKGh0dHBzOi8vYmVuampuZWIuZ2l0aHViLmlvL2RhZGEyL2luZGV4Lmh0bWwpLgoKYGBge3IgYXNzaWduX3RheH0KcHJpbnQoJ0Fzc2lnbmluZyB0YXhhIHRvIEFTVnMuLi4nKQpTeXMudGltZSgpCgp0YXhhIDwtIGFzc2lnblRheG9ub215KHNlcXRhYiwgdGF4X2tleSwgbXVsdGl0aHJlYWQgPSBuX2NvcmVzKQpjb2xuYW1lcyh0YXhhKSA8LSBjKCdLaW5nZG9tJywgJ1BoeWx1bScsICdDbGFzcycsICdPcmRlcicsICdGYW1pbHknLCAnR2VudXMnKQoKcHJpbnQoJy4uLkRvbmUuJykKU3lzLnRpbWUoKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpzYXZlUkRTKHRheGEsIGZpbGUgPSBmaWxlLnBhdGgob3V0ZGlyLCAndGF4YS5yZHMnKSkKIyB0YXhhIDwtIHJlYWRSRFMoZmlsZS5wYXRoKG91dGRpciwgJ3RheGEucmRzJykpCmBgYAoKLS0tLS0KCiMjIFN0ZXAgMTE6IEdlbmVyYXRlIE91dHB1dCBGaWxlcwoKSW4gdGhpcyBsYXN0IHN0ZXAsCndlIHdpbGwgZ2VuZXJhdGUgb3V0cHV0IGZpbGVzIGZyb20gdGhlICpEQURBMiogb3V0cHV0cyB0aGF0IGFyZSBmb3JtYXR0ZWQKZm9yIGRvd25zdHJlYW0gYW5hbHlzaXMgaW4gKnBoeWxvc2VxKi4KRmlyc3QsIHdlIHdpbGwgd3JpdGUgYSAqZmFzdGEqIGZpbGUgd2l0aCB0aGUgZmluYWwgQVNWIHNlcXVlbmNlcy4KKFRoaXMgKmZhc3RhKiBmaWxlIGNhbiBhbHNvIGJlIHVzZWQgZm9yIHBoeWxvZ2VuZXRpYyB0cmVlIGluZmVyZW5jZQp3aXRoIGRpZmZlcmVudCBSIHBhY2thZ2VzLikKCmBgYHtyIHdyaXRlX2Zhc3RhfQojIFByZXBhcmUgc2VxdWVuY2VzIGFuZCBoZWFkZXJzOgphc3Zfc2VxcyA8LSBjb2xuYW1lcyhzZXF0YWIpCmFzdl9oZWFkZXJzIDwtIHBhc3RlKCc+QVNWJywgMTpuY29sKHNlcXRhYiksIHNlcCA9ICdfJykKCiMgSW50ZXJsZWF2ZSBoZWFkZXJzIGFuZCBzZXF1ZW5jZXM6CmFzdl9mYXN0YSA8LSBjKHJiaW5kKGFzdl9oZWFkZXJzLCBhc3Zfc2VxcykpCgojIFdyaXRlIGZhc3RhIGZpbGU6CndyaXRlKGFzdl9mYXN0YSwgZmlsZSA9IGZpbGUucGF0aChvdXRkaXIsICdBU1ZzLmZhJykpCmBgYAoKTm93LCB3ZSBidWlsZCB0aGUgZmluYWwgKnBoeWxvc2VxKiBvYmplY3QuIE5vdGVzOgoKICAtIFdoaWxlIHdlIHdpbGwgbm90IGFkZCBhIHBoeWxvZ2VuZXRpYyB0cmVlIG5vdywKICAgIHRoaXMgY2FuIGFsc28gYmUgYWRkZWQgdG8gYSAqcGh5bG9zZXEqIG9iamVjdC4KCiAgLSBPdXIgbWV0YWRhdGEgZGF0YWZyYW1lIGNvbnRhaW5zIHRocmVlIHNhbXBsZXMgdGhhdCB3ZSBkb24ndCBoYXZlIHNlcXVlbmNlcyBmb3IuCiAgICBIb3dldmVyLCB0aGlzIGlzIG5vdCBhIHByb2JsZW06ICpwaHlsb3NlcSogd2lsbCBtYXRjaCB0aGUgc2FtcGxlIElEcyBpbiB0aGUKICAgIG1ldGFkYXRhIHdpdGggdGhvc2UgaW4gdGhlIE9UVSB0YWJsZSwgYW5kIGRpc3JlZ2FyZCBJRHMgbm90IHByZXNlbnQgaW4gdGhlCiAgICBPVFUgdGFibGUuCgpgYGB7ciBta19waHlsb3NlcX0KcHMgPC0gcGh5bG9zZXEob3R1X3RhYmxlKHNlcXRhYiwgdGF4YV9hcmVfcm93cyA9IEZBTFNFKSwKICAgICAgICAgICAgICAgc2FtcGxlX2RhdGEobWV0YWRhdGFfZGYpLAogICAgICAgICAgICAgICB0YXhfdGFibGUodGF4YSkpCgojIFNhdmVzIHRoZSBwaHlsb3NlcSBvYmplY3QgYXMgYW4gLnJkcyBmaWxlICh3aGljaCBjYW4gYmUgaW1wb3J0ZWQgZGlyZWN0bHkgYnkgcGh5bG9zZXEpOgpzYXZlUkRTKHBzLCBmaWxlID0gZmlsZS5wYXRoKG91dGRpciwgJ3BzX1Y0LnJkcycpKQpgYGAKClJlcG9ydCB0aGF0IHdlIGFyZSBkb25lIQoKYGBge3IgcmVwb3J0X2RvbmV9CnByaW50KCdEb25lIHdpdGggQVNWIGluZmVyZW5jZS4nKQpgYGAKCi0tLS0tCgojIyBSZXNvdXJjZXMKCi0gW0NhbGxhaGFuIGV0IGFsLiAyMDA2OiAiQmlvY29uZHVjdG9yIFdvcmtmbG93IGZvciBNaWNyb2Jpb21lIERhdGEgQW5hbHlzaXM6IGZyb20gcmF3IHJlYWRzIHRvIGNvbW11bml0eSBhbmFseXNlcyJdKGh0dHBzOi8vZjEwMDByZXNlYXJjaC5jb20vYXJ0aWNsZXMvNS0xNDkyL3YyKQotIFtgZGFkYTJgIGRvY3VtZW50YXRpb24gYW5kIHR1dG9yaWFsc10oaHR0cHM6Ly9iZW5qam5lYi5naXRodWIuaW8vZGFkYTIvaW5kZXguaHRtbCApCi0gW1RheG9ub21pYyByZWZlcmVuY2VzIGZvciBgZGFkYTJgXShodHRwczovL2JlbmpqbmViLmdpdGh1Yi5pby9kYWRhMi90cmFpbmluZy5odG1sKQotIFtgY3V0YWRhcHRgIGRvY3VtZW50YXRpb24gYW5kIHR1dG9yaWFsc10oaHR0cHM6Ly9jdXRhZGFwdC5yZWFkdGhlZG9jcy5pby9lbi9zdGFibGUvaW5kZXguaHRtbCkKCgotLS0tLQoKIyMgQm9udXM6IFBoeWxvZ2VuZXRpYyBUcmVlIEVzdGltYXRpb24KCkEgcGh5bG9nZW5ldGljIHRyZWUgY2FuIGJlIGVzdGltYXRlZCBmb3IgdGhlIHNlcXVlbmNlIGRhdGEgeW91IGdlbmVyYXRlZC4KRGVwZW5kaW5nIG9uIHRoZSBudW1iZXIgb2YgQVNWcyByZWNvdmVyZWQgYW5kIHRoZSBwaHlsb2dlbmV0aWMgdHJlZSBhbGdvcml0aG0Kb2YgY2hvaWNlLCB0aGlzIHN0ZXAgY291bGQgdGFrZSBzZXZlcmFsIGRheXMuClNpbXBsZXIgdHJlZXMgd2lsbCBiZSBsZXNzIGNvbXB1dGF0aW9uYWxseSBpbnRlbnNpdmUuCkRlcGVuZGluZyBvbiB0aGUgbWFya2VyIGdlbmUgeW91IGFyZSB3b3JraW5nIG9uLAp5b3UgbWF5IG9yIG1heSBub3QgY2hvb3NlIHRvIHBlcmZvcm0gdGhpcyBzdGVwLgoKVGhpcyBzdGVwIGNhbiBiZSBjb25kdWN0ZWQgYWZ0ZXIgU3RlcCAxMCwKYW5kIHRoZW4gdGhlIHBoeWxvZ2VueSBjYW4gYmUgaW5jbHVkZWQgaW4gdGhlICpwaHlsb3NlcSogb2JqZWN0IGluIFN0ZXAgMTEuCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQojc2VxdGFiPC0gcmVhZFJEUygnc2VxdGFiX1Y0LnJkcycpCgpzZXFzIDwtIGdldFNlcXVlbmNlcyhzZXF0YWIpCgojIFRoaXMgcHJvcGFnYXRlcyB0byB0aGUgdGlwIGxhYmVscyBvZiB0aGUgdHJlZS4KIyBBdCB0aGlzIHN0YWdlIEFTViBsYWJlbHMgYXJlIGZ1bGwgQVNWIHNlcXVlbmNlCm5hbWVzKHNlcXMpIDwtIHNlcXMgCmFsaWdubWVudCA8LSBBbGlnblNlcXMoRE5BU3RyaW5nU2V0KHNlcXMpLAogICAgICAgICAgICAgICAgICAgICAgIGFuY2hvciA9IE5BLAogICAgICAgICAgICAgICAgICAgICAgIGl0ZXJhdGlvbnMgPSA1LAogICAgICAgICAgICAgICAgICAgICAgIHJlZmluZW1lbnRzID0gNSkKCnByaW50KCdDb21wdXRpbmcgcGFpcndpc2UgZGlzdGFuY2VzIGZyb20gQVNWcy4uLicpClN5cy50aW1lKCkKcGhhbmcuYWxpZ24gPC0gcGh5RGF0KGFzKGFsaWdubWVudCwgJ21hdHJpeCcpLCB0eXBlID0gJ0ROQScpCmRtIDwtIGRpc3QubWwocGhhbmcuYWxpZ24pCnRyZWVOSiA8LSBOSihkbSkgIyBOb3RlLCB0aXAgb3JkZXIgaXMgbm90IHNlcXVlbmNlIG9yZGVyCmZpdCA9IHBtbCh0cmVlTkosIGRhdGEgPSBwaGFuZy5hbGlnbikKcHJpbnQoJy4uLkRvbmUuJykKU3lzLnRpbWUoKQoKcHJpbnQoJ0ZpdCBHVFIgbW9kZWwuLi4nKQpTeXMudGltZSgpCmZpdEdUUiA8LSB1cGRhdGUoZml0LCBrID0gNCwgaW52ID0gMC4yKQpwcmludCgnLi4uRG9uZS4nKQpTeXMudGltZSgpCgpwcmludCgnQ29tcHV0aW5nIGxpa2VsaWhvb2Qgb2YgdHJlZS4uLicpClN5cy50aW1lKCkKZml0R1RSIDwtIG9wdGltLnBtbChmaXRHVFIsCiAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSAnR1RSJywKICAgICAgICAgICAgICAgICAgICBvcHRJbnYgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgIG9wdEdhbW1hID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICByZWFycmFuZ2VtZW50ID0gJ3N0b2NoYXN0aWMnLAogICAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBwbWwuY29udHJvbCh0cmFjZSA9IDApKQpwcmludCgnLi4uRG9uZScuKQpTeXMudGltZSgpCmBgYAoKLS0tLS0KCiMjIEJvbnVzOiBTdWJtaXQgc2NyaXB0IGFzIGFuIE9TQyBqb2IKCkV4dHJhY3QgUiBjb2RlIGZyb20gdGhpcyBkb2N1bWVudDoKYGBge3IsIGV2YWwgPSBGQUxTRX0Ka25pdHI6OnB1cmwoaW5wdXQgPSAnbWFya2Rvd24vMDctQVNWLWluZmVyZW5jZS5SbWQnLAogICAgICAgICAgICBvdXRwdXQgPSAnc2NyaXB0cy8wMi1BU1YtaW5mZXJlbmNlLlInKQpgYGAKCk91ciBzY3JpcHQgYDAyLUFTVi1pbmZlcmVuY2Uuc2hgIHdpdGggKlNMVVJNKiBkaXJlY3RpdmVzIHRoYXQgd2lsbCBzdWJtaXQgdGhlIFIKc2NyaXB0IGZyb20gdGhlIHNoZWxsIHVzaW5nIGBSc2NyaXB0YDoKCmBgYHtiYXNoLCBldmFsID0gRkFMU0V9CiMhL2Jpbi9iYXNoCiNTQkFUQ0ggLS1ub2Rlcz0xCiNTQkFUQ0ggLS1udGFza3MtcGVyLW5vZGU9NAojU0JBVENIIC0tdGltZT0yOjAwOjAwCiNTQkFUQ0ggLS1hY2NvdW50PVBBUzA0NzEKCm1vZHVsZSBsb2FkIGdudS85LjEuMAptb2R1bGUgbG9hZCBta2wvMjAxOS4wLjUKbW9kdWxlIGxvYWQgUi80LjAuMgoKUnNjcmlwdCBzY3JpcHRzLzAyLUFTVi1pbmZlcmVuY2UuUgpgYGAKClN1Ym1pdCB0aGUgc2NyaXB0OgoKYGBge2Jhc2gsIGV2YWwgPSBGQUxTRX0Kc2JhdGNoIHNjcmlwdHMvMDItQVNWLWluZmVyZW5jZS5zaApgYGAKCjxicj48YnI+Cg==