Introduction
This is an experimental UMI processing pipeline. This is still a work
in progress, use at your own risk.
Clone the pipeRline github repository
The first step is to clone this github repository, which contains the
required code and directory structure to run the pipeline. To do this,
you will need Git installed on your
computer. If you are running the pipeline in Rstudio, it is best to
create a new project from the github repository. This can be done by
going file > new project > version control >
git then adding https://github.com/alexpiper/piperline.git then change
the name of the project to whatever you are working with.
Alternatively, the repository can be cloned using the command line,
change the ‘folder-name’ to the desired name
# Change into the main directory you wish to make the project in
cd metabarcoding
# Clone the repository
git clone https://github.com/alexpiper/piperline.git folder-name
Updating the pipeline
To update to the latest version of the pipeline, run the below code
in the terminal.
git pull
Demultiplex MiSeq run
#load module
module load bcl2fastq2/2.20.0-foss-2018b
#raise amount of available file handles
ulimit -n 4000
###Run1
#Set up input and outputs
inputdir=/group/sequencing/241101_M03633_0027_000000000-LM4H4 #CHANGE TO YOUR SEQ RUN
outputdir=/group/pathogens/IAWS/Personal/Alexp/lachlan_umi/data/LM4H4 #CHANGE TO YOUR DATA FOLDER RUN
samplesheet=/group/pathogens/IAWS/Personal/Alexp/lachlan_umi/SampleSheet_LM4H4.csv #CHANGE TO YOUR SAMPLESHEET
# convert samplesheet to unix format
dos2unix $samplesheet
#Demultiplex
bcl2fastq -p 12 --runfolder-dir $inputdir \
--output-dir $outputdir \
--sample-sheet $samplesheet \
--no-lane-splitting --barcode-mismatches 1
# Copy other necessary files and move fastqs
cd $outputdir
cp -r $inputdir/InterOp $outputdir
cp $inputdir/RunInfo.xml $outputdir
cp $inputdir/[Rr]unParameters.xml $outputdir
cp $samplesheet $outputdir
mv **/*.fastq.gz $outputdir
# Append fcid to start of sample names if missing
fcid=$(echo $inputdir | sed 's/^.*-//')
for i in *.fastq.gz; do
if ! [[ $i == $fcid* ]]; then
new=$(echo ${fcid} ${i}) #append together
new=$(echo ${new// /_}) #remove any white space
mv -v "$i" "$new"
fi
done
Copy files to “data” directory on local PC to run
Install and load R packages and setup directories
library(tidyverse)
library(ShortRead)
library(stringdist)
library(dada2)
library(DECIPHER)
library(Biostrings)
library(phyloseq)
devtools::install_packages("alexpiper/taxreturn")
devtools::install_packages("alexpiper/seqateurs")
library(taxreturn)
library(seqateurs)
# Source ancillary functions
source("R/functions.R")
source("R/umi_functions.R")
Trim primers and extract UMIs
input_dir <- "data/LM4H4/"
input_files <- fs::dir_ls(input_dir, glob="*R1_001.fastq.gz")
output_dir <- "data/LM4H4/trimmed"
# Create empty vector for tracking number of reads trimmed
primer_trimming <- vector("list", length = length(input_files))
# Loop through input files
for(f in 1:length(input_files)){
fwd <- input_files[f] # Foward read path
rev <- input_files[f] %>% str_replace("_R1_", "_R2_") # Reverse read path
fwd_out <- paste0(output_dir,"/", basename(fwd) %>% str_replace("\\.fastq", "\\.trimmed\\.fastq")) #Trimmed forward output path
rev_out <- paste0(output_dir,"/", basename(rev) %>% str_replace("\\.fastq", "\\.trimmed\\.fastq")) #Trimmed forward output path
# Trim primers and put UMI info in read header
primer_trimming[[f]] <- trim_umi_primers(
fwd=fwd,
rev=rev,
fwd_out=fwd_out,
rev_out=rev_out,
for_primer_seq="GGDACWGGWTGAACWGTWTAYCCHCC",
rev_primer_seq="GTRATWGCHCCDGCTARWACWGG",
for_umi_length=6,
rev_umi_length=6,
n = 1e6,
qualityType = "Auto",
check_paired = TRUE,
id.field = NULL,
max_mismatch=0,
id.sep = "\\s",
compress =TRUE,
quiet=FALSE
)
}
Filter reads
input_dir <- "data/LM4H4/trimmed"
input_files <- fs::dir_ls(input_dir, glob="*R1_001.trimmed.fastq.gz")
output_dir <- "data/LM4H4/filtered"
# Create empty vector for tracking number of reads trimmed
read_filtering <- vector("list", length = length(input_files))
# Loop through input files
for(f in 1:length(input_files)){
fwd <- input_files[f] # Foward read path
rev <- input_files[f] %>% str_replace("_R1_", "_R2_") # Reverse read path
fwd_out <- paste0(output_dir,"/", basename(fwd) %>% str_replace("\\.trimmed\\.fastq", "\\.filtered\\.fastq"))
rev_out <- paste0(output_dir,"/", basename(rev) %>% str_replace("\\.trimmed\\.fastq", "\\.filtered\\.fastq"))
# Run read filter
read_filtering[[f]] <- dada2::filterAndTrim(
fwd = fwd,
rev = rev,
filt = fwd_out,
filt.rev = rev_out,
minLen = 100,
maxLen = Inf,
maxEE = 1,
truncLen = 0,
trimLeft = 0,
trimRight = 0,
rm.phix = TRUE,
multithread = FALSE,
compress = TRUE,
verbose = TRUE) %>%
as_tibble() %>%
dplyr::rename(filter_input = reads.in,
filter_output = reads.out)
}
Merge unique reads by overlap
input_dir <- "data/LM4H4/filtered"
input_files <- fs::dir_ls(input_dir, glob="*R1_001.filtered.fastq.gz")
output_dir <- "data/LM4H4/merged"
# Create empty vector for tracking number of reads merged
read_merging <- vector("list", length = length(input_files))
# Loop through input files
for(f in 1:length(input_files)){
fwd <- input_files[f] # Foward read path
rev <- input_files[f] %>% str_replace("_R1_", "_R2_") # Reverse read path
out <- paste0(output_dir,"/", basename(fwd) %>% str_replace("\\.filtered\\.fastq", "\\.merged\\.fasta"))
read_merging[[f]] <- merge_paired_reads(
fwd=fwd,
rev=rev,
out=out, #Output path = fasta not fastq
prefer=1, # Which strand to prefer when making consensus 1 = fwd, 2 = rev
maxMismatch=2,
minOverlap=20, # Minimum overlap between forward and reverse
trimOverhang=TRUE, # Trim overhanging sequence past the end of amplicon
n = 1e6,
qualityType = "Auto"
)
}
UMI QC and filtering
input_dir <- "data/LM4H4/merged"
input_files <- fs::dir_ls(input_dir, glob="*.merged.fasta.gz")
# Read in sequence data and extract UMI sequences from the header
umi_data <- input_files %>%
purrr::map_dfr(~{
seqs <- Biostrings::readDNAStringSet(.x)
umis <- enframe(as.character(seqs), name="id", value="sequence") %>%
mutate(umi = id %>% str_remove("^.*:")) %>%
mutate(sample_id = basename(.x)%>% str_remove("_S.*$")) %>%
dplyr::select(-id)
})
# Summarise by each UMI family
umi_summary <- umi_data%>%
group_by(umi, sample_id)%>%
summarise(
n_seqs = n(),
n_unique_seqs = n_distinct(sequence),
sequences = list(unique(sequence)),
mean_distance = ifelse(
n_unique_seqs > 1,
mean(stringdist::stringdistmatrix(sequence, sequence, method="lv")),
0
)) %>%
ungroup()
# Plot number of sequences per UMI
umi_summary %>%
ggplot(aes(x = n_seqs)) +
geom_histogram()+
facet_wrap(sample_id~.)+
labs(x = "Total number of sequences per UMI")
# Plot number of unique sequences per UMI
umi_summary %>%
ggplot(aes(x = n_unique_seqs)) +
geom_histogram()+
facet_wrap(sample_id~.)+
labs(x = "Number of unique sequences within each UMI")
# Plot mean edit distance between seqs when UMIs have more than one unique
umi_summary %>%
filter(mean_distance > 0)%>%
ggplot(aes(x = mean_distance))+
geom_histogram() +
facet_wrap(sample_id~.)+
labs(x = "Mean edit distance between distinct sequences in each UMI")
# NOTE: Seems to be that some umis may have multiple taxa
# Explore this a bit further
umis_with_divergent_sequences <- umi_summary %>%
filter(mean_distance > 5)
# Align and view them
BrowseSeqs(DECIPHER::AlignSeqs(DNAStringSet(umis_with_divergent_sequences$sequences[[1]])))
# NOTE: i recommend doing some blast searches etc to confirm if these are truly multiple taxa
Filter UMIs and create consensus seqs
# Filter UMI seqs
filtered_sequences <- umi_summary %>%
filter(
mean_distance < 5, # Keep those with less than 5bp differnces between unqiue seqs - play around with this parameter
n_seqs > 1, # Keep those with more than one sequence - play around with this parameter
#n_unique_seqs < 5 #play around with this parameter
)
# Create consensus
umi_consensus <- filtered_sequences %>%
ungroup()%>%
group_by(umi, sample_id) %>%
mutate(consensus = purrr::map_chr(sequences, ~{
if(length(.x) == 1){ # Only a single sequence is present within the UMI group
return(.x)
} else {
# Create a consensus matrix
aln <- DECIPHER::AlignSeqs(DNAStringSet(.x),verbose = FALSE)
consensus_matrix <- consensusMatrix(aln)
# Get the most common base at each position
consensus_seq <- apply(consensus_matrix, 2, function(col) {
# Find the base with the highest frequency
base <- names(which.max(col))
if (col[base] == 0) {
return("N") # or any character to indicate ambiguity if no base is found
}
return(base)
})
return(paste(consensus_seq, collapse = "") %>% str_remove_all("-"))
}
}))
# See what the consensus sequences look like visually
BrowseSeqs(AlignSeqs(DNAStringSet(umi_consensus$consensus[1:100])))
Create sequence table and filter ASVs
# Create sequence table from consensus UMI sequences
seqtab <- umi_consensus %>%
mutate(consensus = consensus %>% str_remove_all("-")) %>% # remove any gaps in sequences
group_by(sample_id, consensus) %>%
summarise(Abundance = n()) %>%
pivot_wider(names_from=consensus,
values_from=Abundance,
values_fill = list(Abundance = 0)) %>%
column_to_rownames("sample_id")%>%
as.matrix()
# Save unfiltered seqtab
saveRDS(seqtab, "output/rds/seqtab.rds")
# Remove sequences that were only seen a few times across the dataset - Play with this aparameter
seqtab_n1 <- seqtab[,colSums(seqtab) > 10] #minimum 10 molecules observed
# Check how many were removed
seqs_rem <- length(colnames(seqtab_n1))/length(colnames(seqtab))
abund_rem <- sum(seqtab_n1)/sum(seqtab)
message(paste(round(seqs_rem*100,2), "% of initial sequences and ",round(abund_rem*100,2), "% initial abundance remaining after abundance filtering"))
# Remove chimeras
seqtab_nochim <- removeBimeraDenovo(seqtab_n1, method="consensus")
# Check how many were removed
seqs_rem <- length(colnames(seqtab_nochim))/length(colnames(seqtab))
abund_rem <- sum(seqtab_nochim)/sum(seqtab)
message(paste(round(seqs_rem*100,2), "% of initial sequences and ",round(abund_rem*100,2), "% initial abundance remaining after chimera removal"))
# Remove asvs outside expected size
expected_size <- 205
min_length = expected_size - 10
max_length = expected_size + 10
seqtab_cut <- seqtab_nochim[,nchar(colnames(seqtab_nochim)) %in% min_length:max_length]
# Check how many were removed
seqs_rem <- length(colnames(seqtab_cut))/length(colnames(seqtab))
abund_rem <- sum(seqtab_cut)/sum(seqtab)
message(paste(round(seqs_rem*100,2), "% of initial sequences and ",round(abund_rem*100,2), "% initial abundance remaining after length filtering"))
# Align against phmm
phmm_model <- readRDS("reference/folmer_fullength_model.rds")
# Subset phmm to our primer sequences
phmm_model <- taxreturn::subset_model(phmm_model, primers = c("GGDACWGGWTGAACWGTWTAYCCHCC","GTRATWGCHCCDGCTARWACWGG"))
seqs <- DNAStringSet(colnames(seqtab_cut))
names(seqs) <- colnames(seqtab_cut)
phmm_filt <- taxreturn::map_to_model(
seqs, model = phmm_model, min_score = 100, min_length = 100,
shave = FALSE, check_frame = check_frame, kmer_threshold = 0.5, k=5, extra = "fill")
seqtab_phmm <- seqtab_cut[,colnames(seqtab_cut) %in% names(phmm_filt)]
# Check how many were removed
seqs_rem <- length(colnames(seqtab_phmm))/length(colnames(seqtab))
abund_rem <- sum(seqtab_phmm)/sum(seqtab)
message(paste(round(seqs_rem*100,2), "% of initial sequences and ",round(abund_rem*100,2), "% of initial abundance remaining after PHMM filtering"))
#Filter sequences containing stop codons
seqs <- DNAStringSet(colnames(seqtab_phmm))
names(seqs) <- colnames(seqtab_phmm)
codon_filt <- taxreturn::codon_filter(seqs, genetic_code = genetic_code)
seqtab_final <- seqtab_phmm[,colnames(seqtab_phmm) %in% names(codon_filt)]
# Check how many were removed
seqs_rem <- length(colnames(seqtab_final))/length(colnames(seqtab))
abund_rem <- sum(seqtab_final)/sum(seqtab)
message(paste(round(seqs_rem*100,2), "% of initial sequences and ",round(abund_rem*100,2), "% of initial abundance remaining after checking reading frame"))
# Save final filtered seqtab
saveRDS(seqtab_final, "output/rds/seqtab_final.rds")
Assign taxonomy using IDTAXA
# Create a DNAStringSet from the ASVs
seqs <- DNAStringSet(getSequences(seqtab_final))
# Load the relevent db
trainingSet <- readRDS("reference/idtaxa_bftrimmed.rds")
ranks = c("Root","Kingdom", "Phylum","Class", "Order", "Family", "Genus","Species")
# Classify
ids <- DECIPHER::IdTaxa(seqs, trainingSet, processors=1, threshold = 60, verbose=TRUE, strand = "top")
#Convert the output object of class "Taxa" to a matrix analogous to the output from assignTaxonomy
taxtab <- ids %>%
purrr::map_dfr(function(x){
taxa <- paste0(x$taxon,"_", x$confidence)
taxa[startsWith(taxa, "unclassified_")] <- NA
data.frame(t(taxa)) %>%
magrittr::set_colnames(ranks[1:ncol(.)])
}) %>%
mutate_all(stringr::str_replace,pattern="(?:.(?!_))+$", replacement="") %>%
magrittr::set_rownames(getSequences(seqtab_final))
Create phyloseq object
samdf <- read_csv("sample_data.csv")
ps <- phyloseq(tax_table(taxtab),
sample_data(samdf),
otu_table(seqtab, taxa_are_rows = FALSE),
refseq(seqs))
saveRDS(ps, "output/rds/ps.rds")
# Filter to only insects
ps0 <- ps0 %>%
subset_taxa_new(
rank = "Class",
value = "Insecta"
) %>%
filter_taxa(function(x) mean(x) > 0, TRUE) # Drop zeros
# Filter taxa under 0.1% relative abundance
min_taxa_ra = 1e-4
ps1 <- phyloseq::transform_sample_counts(ps0, function(OTU, ab = min_taxa_ra ){
ifelse((OTU / sum(OTU)) <= ab, 0, OTU)
})
# Save filtered phyloseq object
saveRDS(ps1, "output/rds/ps_filtered.rds")
# Export species level summary of filtered results
ps1 %>%
phyloseq::psmelt() %>%
filter(Abundance > 0) %>%
left_join(refseq(ps) %>% as.character() %>% enframe(name="OTU", value="sequence")) %>%
dplyr::select(OTU, sequence, rank_names(ps1), sample_id, Abundance ) %>%
pivot_wider(names_from = sample_id,
values_from = Abundance,
values_fill = list(Abundance = 0)) %>%
write.csv("output/results/summary_filtered.csv")
LS0tDQp0aXRsZTogIlBpcGVSbGluZSINCnN1YnRpdGxlOiAiVU1JIHBpcGVsaW5lIChFeHBlcmltZW50YWwpIg0KYXV0aG9yOiAiQS5NLiBQaXBlciINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDoNCiAgDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgaGlnaGxpZ2h0ZXI6IG51bGwNCiAgICB0aGVtZTogImZsYXRseSINCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiANCiAgICAgIGNvbGxhcHNlZDogZmFsc2UNCiAgICAgIHNtb290aF9zY3JvbGw6IHRydWUNCiAgICBkZl9wcmludDogcGFnZWQgICAgDQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KZWRpdG9yX29wdGlvbnM6IA0KICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBLbml0ciBnbG9iYWwgc2V0dXAgLSBjaGFuZ2UgZXZhbCB0byB0cnVlIHRvIHJ1biBjb2RlDQpsaWJyYXJ5KGtuaXRyKQ0KDQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZXZhbD1GQUxTRSwgbWVzc2FnZT1GQUxTRSxlcnJvcj1GQUxTRSwgZmlnLnNob3cgPSAiaG9sZCIsIGZpZy5rZWVwID0gImFsbCIpDQpvcHRzX2NodW5rJHNldChkZXYgPSAncG5nJykNCmBgYA0KDQojIEludHJvZHVjdGlvbg0KDQpUaGlzIGlzIGFuIGV4cGVyaW1lbnRhbCBVTUkgcHJvY2Vzc2luZyBwaXBlbGluZS4gVGhpcyBpcyBzdGlsbCBhIHdvcmsgaW4gcHJvZ3Jlc3MsIHVzZSBhdCB5b3VyIG93biByaXNrLg0KDQojIENsb25lIHRoZSBwaXBlUmxpbmUgZ2l0aHViIHJlcG9zaXRvcnkNCg0KVGhlIGZpcnN0IHN0ZXAgaXMgdG8gY2xvbmUgdGhpcyBnaXRodWIgcmVwb3NpdG9yeSwgd2hpY2ggY29udGFpbnMgdGhlIHJlcXVpcmVkIGNvZGUgYW5kIGRpcmVjdG9yeSBzdHJ1Y3R1cmUgdG8gcnVuIHRoZSBwaXBlbGluZS4gVG8gZG8gdGhpcywgeW91IHdpbGwgbmVlZCBbR2l0XShodHRwczovL2dpdC1zY20uY29tLykgaW5zdGFsbGVkIG9uIHlvdXIgY29tcHV0ZXIuIElmIHlvdSBhcmUgcnVubmluZyB0aGUgcGlwZWxpbmUgaW4gUnN0dWRpbywgaXQgaXMgYmVzdCB0byBjcmVhdGUgYSBuZXcgcHJvamVjdCBmcm9tIHRoZSBnaXRodWIgcmVwb3NpdG9yeS4gVGhpcyBjYW4gYmUgZG9uZSBieSBnb2luZyAqKmZpbGUgPiBuZXcgcHJvamVjdCA+IHZlcnNpb24gY29udHJvbCA+IGdpdCoqIHRoZW4gYWRkaW5nIGh0dHBzOi8vZ2l0aHViLmNvbS9hbGV4cGlwZXIvcGlwZXJsaW5lLmdpdCB0aGVuIGNoYW5nZSB0aGUgbmFtZSBvZiB0aGUgcHJvamVjdCB0byB3aGF0ZXZlciB5b3UgYXJlIHdvcmtpbmcgd2l0aC4gDQoNCkFsdGVybmF0aXZlbHksIHRoZSByZXBvc2l0b3J5IGNhbiBiZSBjbG9uZWQgdXNpbmcgdGhlIGNvbW1hbmQgbGluZSwgY2hhbmdlIHRoZSAnZm9sZGVyLW5hbWUnIHRvIHRoZSBkZXNpcmVkIG5hbWUNCmBgYHtiYXNofQ0KIyBDaGFuZ2UgaW50byB0aGUgbWFpbiBkaXJlY3RvcnkgeW91IHdpc2ggdG8gbWFrZSB0aGUgcHJvamVjdCBpbg0KY2QgbWV0YWJhcmNvZGluZw0KDQojIENsb25lIHRoZSByZXBvc2l0b3J5DQpnaXQgY2xvbmUgaHR0cHM6Ly9naXRodWIuY29tL2FsZXhwaXBlci9waXBlcmxpbmUuZ2l0IGZvbGRlci1uYW1lDQpgYGANCg0KIyMgVXBkYXRpbmcgdGhlIHBpcGVsaW5lDQoNClRvIHVwZGF0ZSB0byB0aGUgbGF0ZXN0IHZlcnNpb24gb2YgdGhlIHBpcGVsaW5lLCBydW4gdGhlIGJlbG93IGNvZGUgaW4gdGhlIHRlcm1pbmFsLg0KDQpgYGB7YmFzaH0NCmdpdCBwdWxsDQpgYGANCg0KDQojIERlbXVsdGlwbGV4IE1pU2VxIHJ1bg0KDQpgYGB7YmFzaCBkZW11bHRpcGxleCAxIG1pc21hdGNofQ0KI2xvYWQgbW9kdWxlDQptb2R1bGUgbG9hZCBiY2wyZmFzdHEyLzIuMjAuMC1mb3NzLTIwMThiDQoNCiNyYWlzZSBhbW91bnQgb2YgYXZhaWxhYmxlIGZpbGUgaGFuZGxlcw0KdWxpbWl0IC1uIDQwMDANCg0KIyMjUnVuMQ0KDQojU2V0IHVwIGlucHV0IGFuZCBvdXRwdXRzDQppbnB1dGRpcj0vZ3JvdXAvc2VxdWVuY2luZy8yNDExMDFfTTAzNjMzXzAwMjdfMDAwMDAwMDAwLUxNNEg0ICNDSEFOR0UgVE8gWU9VUiBTRVEgUlVODQpvdXRwdXRkaXI9L2dyb3VwL3BhdGhvZ2Vucy9JQVdTL1BlcnNvbmFsL0FsZXhwL2xhY2hsYW5fdW1pL2RhdGEvTE00SDQgI0NIQU5HRSBUTyBZT1VSIERBVEEgRk9MREVSIFJVTg0Kc2FtcGxlc2hlZXQ9L2dyb3VwL3BhdGhvZ2Vucy9JQVdTL1BlcnNvbmFsL0FsZXhwL2xhY2hsYW5fdW1pL1NhbXBsZVNoZWV0X0xNNEg0LmNzdiAjQ0hBTkdFIFRPIFlPVVIgU0FNUExFU0hFRVQNCg0KIyBjb252ZXJ0IHNhbXBsZXNoZWV0IHRvIHVuaXggZm9ybWF0DQpkb3MydW5peCAkc2FtcGxlc2hlZXQNCg0KI0RlbXVsdGlwbGV4DQpiY2wyZmFzdHEgLXAgMTIgLS1ydW5mb2xkZXItZGlyICRpbnB1dGRpciBcDQotLW91dHB1dC1kaXIgJG91dHB1dGRpciBcDQotLXNhbXBsZS1zaGVldCAkc2FtcGxlc2hlZXQgXA0KLS1uby1sYW5lLXNwbGl0dGluZyAtLWJhcmNvZGUtbWlzbWF0Y2hlcyAxDQoNCiMgQ29weSBvdGhlciBuZWNlc3NhcnkgZmlsZXMgYW5kIG1vdmUgZmFzdHFzDQpjZCAkb3V0cHV0ZGlyDQpjcCAtciAkaW5wdXRkaXIvSW50ZXJPcCAkb3V0cHV0ZGlyDQpjcCAkaW5wdXRkaXIvUnVuSW5mby54bWwgJG91dHB1dGRpcg0KY3AgJGlucHV0ZGlyL1tScl11blBhcmFtZXRlcnMueG1sICRvdXRwdXRkaXINCmNwICRzYW1wbGVzaGVldCAkb3V0cHV0ZGlyDQptdiAqKi8qLmZhc3RxLmd6ICRvdXRwdXRkaXINCg0KIyBBcHBlbmQgZmNpZCB0byBzdGFydCBvZiBzYW1wbGUgbmFtZXMgaWYgbWlzc2luZw0KZmNpZD0kKGVjaG8gJGlucHV0ZGlyIHwgc2VkICdzL14uKi0vLycpDQpmb3IgaSBpbiAqLmZhc3RxLmd6OyBkbw0KICBpZiAhIFtbICRpID09ICRmY2lkKiBdXTsgdGhlbg0KICBuZXc9JChlY2hvICR7ZmNpZH0gJHtpfSkgI2FwcGVuZCB0b2dldGhlcg0KICBuZXc9JChlY2hvICR7bmV3Ly8gL199KSAjcmVtb3ZlIGFueSB3aGl0ZSBzcGFjZQ0KICBtdiAtdiAiJGkiICIkbmV3Ig0KICBmaQ0KZG9uZQ0KDQpgYGANCg0KQ29weSBmaWxlcyB0byAiZGF0YSIgZGlyZWN0b3J5IG9uIGxvY2FsIFBDIHRvIHJ1bg0KDQojIEluc3RhbGwgYW5kIGxvYWQgUiBwYWNrYWdlcyBhbmQgc2V0dXAgZGlyZWN0b3JpZXMNCg0KYGBge3IgTWFudWFsIGluc3RhbGx9IA0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KFNob3J0UmVhZCkNCmxpYnJhcnkoc3RyaW5nZGlzdCkNCmxpYnJhcnkoZGFkYTIpDQpsaWJyYXJ5KERFQ0lQSEVSKQ0KbGlicmFyeShCaW9zdHJpbmdzKQ0KbGlicmFyeShwaHlsb3NlcSkNCg0KZGV2dG9vbHM6Omluc3RhbGxfcGFja2FnZXMoImFsZXhwaXBlci90YXhyZXR1cm4iKQ0KZGV2dG9vbHM6Omluc3RhbGxfcGFja2FnZXMoImFsZXhwaXBlci9zZXFhdGV1cnMiKQ0KbGlicmFyeSh0YXhyZXR1cm4pDQpsaWJyYXJ5KHNlcWF0ZXVycykNCg0KIyBTb3VyY2UgYW5jaWxsYXJ5IGZ1bmN0aW9ucw0Kc291cmNlKCJSL2Z1bmN0aW9ucy5SIikNCnNvdXJjZSgiUi91bWlfZnVuY3Rpb25zLlIiKQ0KYGBgDQoNCg0KIyBUcmltIHByaW1lcnMgYW5kIGV4dHJhY3QgVU1Jcw0KYGBge1J9DQppbnB1dF9kaXIgPC0gImRhdGEvTE00SDQvIg0KaW5wdXRfZmlsZXMgPC0gZnM6OmRpcl9scyhpbnB1dF9kaXIsIGdsb2I9IipSMV8wMDEuZmFzdHEuZ3oiKQ0Kb3V0cHV0X2RpciA8LSAiZGF0YS9MTTRINC90cmltbWVkIg0KIyBDcmVhdGUgZW1wdHkgdmVjdG9yIGZvciB0cmFja2luZyBudW1iZXIgb2YgcmVhZHMgdHJpbW1lZA0KcHJpbWVyX3RyaW1taW5nIDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChpbnB1dF9maWxlcykpDQoNCiMgTG9vcCB0aHJvdWdoIGlucHV0IGZpbGVzDQpmb3IoZiBpbiAxOmxlbmd0aChpbnB1dF9maWxlcykpew0KICBmd2QgPC0gaW5wdXRfZmlsZXNbZl0gIyBGb3dhcmQgcmVhZCBwYXRoDQogIHJldiA8LSBpbnB1dF9maWxlc1tmXSAlPiUgc3RyX3JlcGxhY2UoIl9SMV8iLCAiX1IyXyIpICMgUmV2ZXJzZSByZWFkIHBhdGgNCiAgZndkX291dCA8LSBwYXN0ZTAob3V0cHV0X2RpciwiLyIsIGJhc2VuYW1lKGZ3ZCkgJT4lIHN0cl9yZXBsYWNlKCJcXC5mYXN0cSIsICJcXC50cmltbWVkXFwuZmFzdHEiKSkgI1RyaW1tZWQgZm9yd2FyZCBvdXRwdXQgcGF0aA0KICByZXZfb3V0IDwtIHBhc3RlMChvdXRwdXRfZGlyLCIvIiwgYmFzZW5hbWUocmV2KSAlPiUgc3RyX3JlcGxhY2UoIlxcLmZhc3RxIiwgIlxcLnRyaW1tZWRcXC5mYXN0cSIpKSAjVHJpbW1lZCBmb3J3YXJkIG91dHB1dCBwYXRoDQogICAgDQogICMgVHJpbSBwcmltZXJzIGFuZCBwdXQgVU1JIGluZm8gaW4gcmVhZCBoZWFkZXINCiAgcHJpbWVyX3RyaW1taW5nW1tmXV0gPC0gdHJpbV91bWlfcHJpbWVycygNCiAgICBmd2Q9ZndkLA0KICAgIHJldj1yZXYsDQogICAgZndkX291dD1md2Rfb3V0LCANCiAgICByZXZfb3V0PXJldl9vdXQsIA0KICAgIGZvcl9wcmltZXJfc2VxPSJHR0RBQ1dHR1dUR0FBQ1dHVFdUQVlDQ0hDQyIsDQogICAgcmV2X3ByaW1lcl9zZXE9IkdUUkFUV0dDSENDREdDVEFSV0FDV0dHIiwNCiAgICBmb3JfdW1pX2xlbmd0aD02LA0KICAgIHJldl91bWlfbGVuZ3RoPTYsDQogICAgbiA9IDFlNiwNCiAgICBxdWFsaXR5VHlwZSA9ICJBdXRvIiwNCiAgICBjaGVja19wYWlyZWQgPSBUUlVFLA0KICAgIGlkLmZpZWxkID0gTlVMTCwNCiAgICBtYXhfbWlzbWF0Y2g9MCwNCiAgICBpZC5zZXAgPSAiXFxzIiwNCiAgICBjb21wcmVzcyA9VFJVRSwNCiAgICBxdWlldD1GQUxTRQ0KICApDQp9DQoNCmBgYA0KDQojIEZpbHRlciByZWFkcw0KYGBge3J9DQppbnB1dF9kaXIgPC0gImRhdGEvTE00SDQvdHJpbW1lZCINCmlucHV0X2ZpbGVzIDwtIGZzOjpkaXJfbHMoaW5wdXRfZGlyLCBnbG9iPSIqUjFfMDAxLnRyaW1tZWQuZmFzdHEuZ3oiKQ0Kb3V0cHV0X2RpciA8LSAiZGF0YS9MTTRINC9maWx0ZXJlZCINCg0KIyBDcmVhdGUgZW1wdHkgdmVjdG9yIGZvciB0cmFja2luZyBudW1iZXIgb2YgcmVhZHMgdHJpbW1lZA0KcmVhZF9maWx0ZXJpbmcgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKGlucHV0X2ZpbGVzKSkNCg0KIyBMb29wIHRocm91Z2ggaW5wdXQgZmlsZXMNCmZvcihmIGluIDE6bGVuZ3RoKGlucHV0X2ZpbGVzKSl7DQogIGZ3ZCA8LSBpbnB1dF9maWxlc1tmXSAjIEZvd2FyZCByZWFkIHBhdGgNCiAgcmV2IDwtIGlucHV0X2ZpbGVzW2ZdICU+JSBzdHJfcmVwbGFjZSgiX1IxXyIsICJfUjJfIikgIyBSZXZlcnNlIHJlYWQgcGF0aA0KICBmd2Rfb3V0IDwtIHBhc3RlMChvdXRwdXRfZGlyLCIvIiwgYmFzZW5hbWUoZndkKSAlPiUgc3RyX3JlcGxhY2UoIlxcLnRyaW1tZWRcXC5mYXN0cSIsICJcXC5maWx0ZXJlZFxcLmZhc3RxIikpDQogIHJldl9vdXQgPC0gcGFzdGUwKG91dHB1dF9kaXIsIi8iLCBiYXNlbmFtZShyZXYpICU+JSBzdHJfcmVwbGFjZSgiXFwudHJpbW1lZFxcLmZhc3RxIiwgIlxcLmZpbHRlcmVkXFwuZmFzdHEiKSkNCiAgICANCiAgIyBSdW4gcmVhZCBmaWx0ZXINCiAgcmVhZF9maWx0ZXJpbmdbW2ZdXSA8LSBkYWRhMjo6ZmlsdGVyQW5kVHJpbSgNCiAgICBmd2QgPSBmd2QsIA0KICAgIHJldiA9IHJldiwgDQogICAgZmlsdCA9IGZ3ZF9vdXQsDQogICAgZmlsdC5yZXYgPSByZXZfb3V0LA0KICAgIG1pbkxlbiA9IDEwMCwNCiAgICBtYXhMZW4gPSBJbmYsDQogICAgbWF4RUUgPSAxLA0KICAgIHRydW5jTGVuID0gMCwNCiAgICB0cmltTGVmdCA9IDAsDQogICAgdHJpbVJpZ2h0ID0gMCwgDQogICAgcm0ucGhpeCA9IFRSVUUsIA0KICAgIG11bHRpdGhyZWFkID0gRkFMU0UsIA0KICAgIGNvbXByZXNzID0gVFJVRSwgDQogICAgdmVyYm9zZSA9IFRSVUUpICU+JSANCiAgICBhc190aWJibGUoKSAlPiUNCiAgICBkcGx5cjo6cmVuYW1lKGZpbHRlcl9pbnB1dCA9IHJlYWRzLmluLA0KICAgICAgICAgICAgICAgICAgZmlsdGVyX291dHB1dCA9IHJlYWRzLm91dCkgDQp9DQoNCmBgYA0KDQoNCg0KIyBNZXJnZSB1bmlxdWUgcmVhZHMgYnkgb3ZlcmxhcA0KDQpgYGB7cn0NCmlucHV0X2RpciA8LSAiZGF0YS9MTTRINC9maWx0ZXJlZCINCmlucHV0X2ZpbGVzIDwtIGZzOjpkaXJfbHMoaW5wdXRfZGlyLCBnbG9iPSIqUjFfMDAxLmZpbHRlcmVkLmZhc3RxLmd6IikNCm91dHB1dF9kaXIgPC0gImRhdGEvTE00SDQvbWVyZ2VkIg0KDQojIENyZWF0ZSBlbXB0eSB2ZWN0b3IgZm9yIHRyYWNraW5nIG51bWJlciBvZiByZWFkcyBtZXJnZWQNCnJlYWRfbWVyZ2luZyA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGggPSBsZW5ndGgoaW5wdXRfZmlsZXMpKQ0KDQojIExvb3AgdGhyb3VnaCBpbnB1dCBmaWxlcw0KZm9yKGYgaW4gMTpsZW5ndGgoaW5wdXRfZmlsZXMpKXsNCiAgDQogIGZ3ZCA8LSBpbnB1dF9maWxlc1tmXSAjIEZvd2FyZCByZWFkIHBhdGgNCiAgcmV2IDwtIGlucHV0X2ZpbGVzW2ZdICU+JSBzdHJfcmVwbGFjZSgiX1IxXyIsICJfUjJfIikgIyBSZXZlcnNlIHJlYWQgcGF0aA0KICBvdXQgPC0gcGFzdGUwKG91dHB1dF9kaXIsIi8iLCBiYXNlbmFtZShmd2QpICU+JSBzdHJfcmVwbGFjZSgiXFwuZmlsdGVyZWRcXC5mYXN0cSIsICJcXC5tZXJnZWRcXC5mYXN0YSIpKQ0KICAgIA0KICByZWFkX21lcmdpbmdbW2ZdXSA8LSBtZXJnZV9wYWlyZWRfcmVhZHMoDQogICAgZndkPWZ3ZCwNCiAgICByZXY9cmV2LA0KICAgIG91dD1vdXQsICNPdXRwdXQgcGF0aCA9IGZhc3RhIG5vdCBmYXN0cQ0KICAgIHByZWZlcj0xLCAjIFdoaWNoIHN0cmFuZCB0byBwcmVmZXIgd2hlbiBtYWtpbmcgY29uc2Vuc3VzIDEgPSBmd2QsIDIgPSByZXYNCiAgICBtYXhNaXNtYXRjaD0yLCANCiAgICBtaW5PdmVybGFwPTIwLCAjIE1pbmltdW0gb3ZlcmxhcCBiZXR3ZWVuIGZvcndhcmQgYW5kIHJldmVyc2UNCiAgICB0cmltT3Zlcmhhbmc9VFJVRSwgIyBUcmltIG92ZXJoYW5naW5nIHNlcXVlbmNlIHBhc3QgdGhlIGVuZCBvZiBhbXBsaWNvbg0KICAgIG4gPSAxZTYsDQogICAgcXVhbGl0eVR5cGUgPSAiQXV0byINCiAgKSAgICAgICAgICAgICAgICAgICAgDQp9DQoNCmBgYA0KDQojIFVNSSBRQyBhbmQgZmlsdGVyaW5nDQoNCmBgYHtyfQ0KaW5wdXRfZGlyIDwtICJkYXRhL0xNNEg0L21lcmdlZCINCmlucHV0X2ZpbGVzIDwtIGZzOjpkaXJfbHMoaW5wdXRfZGlyLCBnbG9iPSIqLm1lcmdlZC5mYXN0YS5neiIpDQoNCiMgUmVhZCBpbiBzZXF1ZW5jZSBkYXRhIGFuZCBleHRyYWN0IFVNSSBzZXF1ZW5jZXMgZnJvbSB0aGUgaGVhZGVyDQp1bWlfZGF0YSA8LSBpbnB1dF9maWxlcyAlPiUNCiAgcHVycnI6Om1hcF9kZnIofnsNCiAgICBzZXFzIDwtIEJpb3N0cmluZ3M6OnJlYWRETkFTdHJpbmdTZXQoLngpDQogICAgdW1pcyA8LSBlbmZyYW1lKGFzLmNoYXJhY3RlcihzZXFzKSwgbmFtZT0iaWQiLCB2YWx1ZT0ic2VxdWVuY2UiKSAlPiUNCiAgICAgIG11dGF0ZSh1bWkgPSBpZCAlPiUgc3RyX3JlbW92ZSgiXi4qOiIpKSAgJT4lDQogICAgICBtdXRhdGUoc2FtcGxlX2lkID0gYmFzZW5hbWUoLngpJT4lIHN0cl9yZW1vdmUoIl9TLiokIikpICU+JQ0KICAgICAgZHBseXI6OnNlbGVjdCgtaWQpDQogIH0pDQoNCiMgU3VtbWFyaXNlIGJ5IGVhY2ggVU1JIGZhbWlseQ0KdW1pX3N1bW1hcnkgPC0gdW1pX2RhdGElPiUNCiAgZ3JvdXBfYnkodW1pLCBzYW1wbGVfaWQpJT4lDQogIHN1bW1hcmlzZSgNCiAgICBuX3NlcXMgPSBuKCksDQogICAgbl91bmlxdWVfc2VxcyA9IG5fZGlzdGluY3Qoc2VxdWVuY2UpLA0KICAgIHNlcXVlbmNlcyA9IGxpc3QodW5pcXVlKHNlcXVlbmNlKSksDQogICAgbWVhbl9kaXN0YW5jZSA9IGlmZWxzZSgNCiAgICAgIG5fdW5pcXVlX3NlcXMgPiAxLA0KICAgICAgbWVhbihzdHJpbmdkaXN0OjpzdHJpbmdkaXN0bWF0cml4KHNlcXVlbmNlLCBzZXF1ZW5jZSwgbWV0aG9kPSJsdiIpKSwgIA0KICAgICAgMA0KICAgICAgKSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQojIFBsb3QgbnVtYmVyIG9mIHNlcXVlbmNlcyBwZXIgVU1JDQp1bWlfc3VtbWFyeSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gbl9zZXFzKSkgKw0KICBnZW9tX2hpc3RvZ3JhbSgpKw0KICBmYWNldF93cmFwKHNhbXBsZV9pZH4uKSsNCiAgbGFicyh4ID0gIlRvdGFsIG51bWJlciBvZiBzZXF1ZW5jZXMgcGVyIFVNSSIpDQoNCiMgUGxvdCBudW1iZXIgb2YgdW5pcXVlIHNlcXVlbmNlcyBwZXIgVU1JDQp1bWlfc3VtbWFyeSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gbl91bmlxdWVfc2VxcykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oKSsNCiAgZmFjZXRfd3JhcChzYW1wbGVfaWR+LikrDQogIGxhYnMoeCA9ICJOdW1iZXIgb2YgdW5pcXVlIHNlcXVlbmNlcyB3aXRoaW4gZWFjaCBVTUkiKQ0KDQojIFBsb3QgbWVhbiBlZGl0IGRpc3RhbmNlIGJldHdlZW4gc2VxcyB3aGVuIFVNSXMgaGF2ZSBtb3JlIHRoYW4gb25lIHVuaXF1ZSANCnVtaV9zdW1tYXJ5ICU+JQ0KICBmaWx0ZXIobWVhbl9kaXN0YW5jZSA+IDApJT4lDQogIGdncGxvdChhZXMoeCA9IG1lYW5fZGlzdGFuY2UpKSsNCiAgZ2VvbV9oaXN0b2dyYW0oKSArDQogIGZhY2V0X3dyYXAoc2FtcGxlX2lkfi4pKw0KICBsYWJzKHggPSAiTWVhbiBlZGl0IGRpc3RhbmNlIGJldHdlZW4gZGlzdGluY3Qgc2VxdWVuY2VzIGluIGVhY2ggVU1JIikNCg0KDQojIE5PVEU6IFNlZW1zIHRvIGJlIHRoYXQgc29tZSB1bWlzIG1heSBoYXZlIG11bHRpcGxlIHRheGENCg0KIyBFeHBsb3JlIHRoaXMgYSBiaXQgZnVydGhlcg0KdW1pc193aXRoX2RpdmVyZ2VudF9zZXF1ZW5jZXMgPC0gdW1pX3N1bW1hcnkgJT4lDQogIGZpbHRlcihtZWFuX2Rpc3RhbmNlID4gNSkNCg0KIyBBbGlnbiBhbmQgdmlldyB0aGVtIA0KQnJvd3NlU2VxcyhERUNJUEhFUjo6QWxpZ25TZXFzKEROQVN0cmluZ1NldCh1bWlzX3dpdGhfZGl2ZXJnZW50X3NlcXVlbmNlcyRzZXF1ZW5jZXNbWzFdXSkpKQ0KDQojIE5PVEU6IGkgcmVjb21tZW5kIGRvaW5nIHNvbWUgYmxhc3Qgc2VhcmNoZXMgZXRjIHRvIGNvbmZpcm0gaWYgdGhlc2UgYXJlIHRydWx5IG11bHRpcGxlIHRheGENCg0KYGBgDQoNCiMgRmlsdGVyIFVNSXMgYW5kIGNyZWF0ZSBjb25zZW5zdXMgc2Vxcw0KDQpgYGB7Un0NCiMgRmlsdGVyIFVNSSBzZXFzDQpmaWx0ZXJlZF9zZXF1ZW5jZXMgPC0gdW1pX3N1bW1hcnkgJT4lDQogIGZpbHRlcigNCiAgICBtZWFuX2Rpc3RhbmNlIDwgNSwgIyBLZWVwIHRob3NlIHdpdGggbGVzcyB0aGFuIDVicCBkaWZmZXJuY2VzIGJldHdlZW4gdW5xaXVlIHNlcXMgLSBwbGF5IGFyb3VuZCB3aXRoIHRoaXMgcGFyYW1ldGVyDQogICAgbl9zZXFzID4gMSwgIyBLZWVwIHRob3NlIHdpdGggbW9yZSB0aGFuIG9uZSBzZXF1ZW5jZSAgLSBwbGF5IGFyb3VuZCB3aXRoIHRoaXMgcGFyYW1ldGVyDQogICAgI25fdW5pcXVlX3NlcXMgPCA1ICNwbGF5IGFyb3VuZCB3aXRoIHRoaXMgcGFyYW1ldGVyDQogICkgDQoNCiMgQ3JlYXRlIGNvbnNlbnN1cw0KdW1pX2NvbnNlbnN1cyA8LSBmaWx0ZXJlZF9zZXF1ZW5jZXMgJT4lDQogIHVuZ3JvdXAoKSU+JQ0KICBncm91cF9ieSh1bWksIHNhbXBsZV9pZCkgJT4lDQogIG11dGF0ZShjb25zZW5zdXMgPSBwdXJycjo6bWFwX2NocihzZXF1ZW5jZXMsIH57DQogICAgaWYobGVuZ3RoKC54KSA9PSAxKXsgIyBPbmx5IGEgc2luZ2xlIHNlcXVlbmNlIGlzIHByZXNlbnQgd2l0aGluIHRoZSBVTUkgZ3JvdXANCiAgICAgIHJldHVybigueCkNCiAgICB9IGVsc2Ugew0KICAgICAgIyBDcmVhdGUgYSBjb25zZW5zdXMgbWF0cml4DQogICAgICBhbG4gPC0gREVDSVBIRVI6OkFsaWduU2VxcyhETkFTdHJpbmdTZXQoLngpLHZlcmJvc2UgPSBGQUxTRSkNCiAgICAgIGNvbnNlbnN1c19tYXRyaXggPC0gY29uc2Vuc3VzTWF0cml4KGFsbikNCg0KICAgICAgIyBHZXQgdGhlIG1vc3QgY29tbW9uIGJhc2UgYXQgZWFjaCBwb3NpdGlvbg0KICAgICAgY29uc2Vuc3VzX3NlcSA8LSBhcHBseShjb25zZW5zdXNfbWF0cml4LCAyLCBmdW5jdGlvbihjb2wpIHsNCiAgICAgICAgICAjIEZpbmQgdGhlIGJhc2Ugd2l0aCB0aGUgaGlnaGVzdCBmcmVxdWVuY3kNCiAgICAgICAgICBiYXNlIDwtIG5hbWVzKHdoaWNoLm1heChjb2wpKQ0KICAgICAgICAgIGlmIChjb2xbYmFzZV0gPT0gMCkgew0KICAgICAgICAgICAgICByZXR1cm4oIk4iKSAgIyBvciBhbnkgY2hhcmFjdGVyIHRvIGluZGljYXRlIGFtYmlndWl0eSBpZiBubyBiYXNlIGlzIGZvdW5kDQogICAgICAgICAgfQ0KICAgICAgICAgIHJldHVybihiYXNlKQ0KICAgICAgfSkNCiAgICAgIHJldHVybihwYXN0ZShjb25zZW5zdXNfc2VxLCBjb2xsYXBzZSA9ICIiKSAlPiUgc3RyX3JlbW92ZV9hbGwoIi0iKSkNCiAgICB9DQogICAgfSkpDQoNCiMgU2VlIHdoYXQgdGhlIGNvbnNlbnN1cyBzZXF1ZW5jZXMgbG9vayBsaWtlIHZpc3VhbGx5DQpCcm93c2VTZXFzKEFsaWduU2VxcyhETkFTdHJpbmdTZXQodW1pX2NvbnNlbnN1cyRjb25zZW5zdXNbMToxMDBdKSkpDQoNCmBgYA0KDQoNCiMgQ3JlYXRlIHNlcXVlbmNlIHRhYmxlIGFuZCBmaWx0ZXIgQVNWcw0KYGBge3J9DQojIENyZWF0ZSBzZXF1ZW5jZSB0YWJsZSBmcm9tIGNvbnNlbnN1cyBVTUkgc2VxdWVuY2VzDQpzZXF0YWIgPC0gdW1pX2NvbnNlbnN1cyAlPiUNCiAgbXV0YXRlKGNvbnNlbnN1cyA9IGNvbnNlbnN1cyAlPiUgc3RyX3JlbW92ZV9hbGwoIi0iKSkgJT4lICMgcmVtb3ZlIGFueSBnYXBzIGluIHNlcXVlbmNlcw0KICBncm91cF9ieShzYW1wbGVfaWQsIGNvbnNlbnN1cykgJT4lDQogIHN1bW1hcmlzZShBYnVuZGFuY2UgPSBuKCkpICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tPWNvbnNlbnN1cywNCiAgICAgICAgICAgICAgdmFsdWVzX2Zyb209QWJ1bmRhbmNlLA0KICAgICAgICAgICAgICB2YWx1ZXNfZmlsbCA9IGxpc3QoQWJ1bmRhbmNlID0gMCkpICU+JQ0KICBjb2x1bW5fdG9fcm93bmFtZXMoInNhbXBsZV9pZCIpJT4lDQogIGFzLm1hdHJpeCgpDQoNCiMgU2F2ZSB1bmZpbHRlcmVkIHNlcXRhYg0Kc2F2ZVJEUyhzZXF0YWIsICJvdXRwdXQvcmRzL3NlcXRhYi5yZHMiKQ0KDQojIFJlbW92ZSBzZXF1ZW5jZXMgdGhhdCB3ZXJlIG9ubHkgc2VlbiBhIGZldyB0aW1lcyBhY3Jvc3MgdGhlIGRhdGFzZXQgLSBQbGF5IHdpdGggdGhpcyBhcGFyYW1ldGVyDQpzZXF0YWJfbjEgPC0gc2VxdGFiWyxjb2xTdW1zKHNlcXRhYikgPiAxMF0gI21pbmltdW0gMTAgbW9sZWN1bGVzIG9ic2VydmVkDQoNCiMgQ2hlY2sgaG93IG1hbnkgd2VyZSByZW1vdmVkDQpzZXFzX3JlbSA8LSBsZW5ndGgoY29sbmFtZXMoc2VxdGFiX24xKSkvbGVuZ3RoKGNvbG5hbWVzKHNlcXRhYikpDQphYnVuZF9yZW0gPC0gc3VtKHNlcXRhYl9uMSkvc3VtKHNlcXRhYikNCm1lc3NhZ2UocGFzdGUocm91bmQoc2Vxc19yZW0qMTAwLDIpLCAiJSBvZiBpbml0aWFsIHNlcXVlbmNlcyBhbmQgIixyb3VuZChhYnVuZF9yZW0qMTAwLDIpLCAiJSBpbml0aWFsIGFidW5kYW5jZSByZW1haW5pbmcgYWZ0ZXIgYWJ1bmRhbmNlIGZpbHRlcmluZyIpKQ0KDQojIFJlbW92ZSBjaGltZXJhcw0Kc2VxdGFiX25vY2hpbSA8LSByZW1vdmVCaW1lcmFEZW5vdm8oc2VxdGFiX24xLCBtZXRob2Q9ImNvbnNlbnN1cyIpDQoNCiMgQ2hlY2sgaG93IG1hbnkgd2VyZSByZW1vdmVkDQpzZXFzX3JlbSA8LSBsZW5ndGgoY29sbmFtZXMoc2VxdGFiX25vY2hpbSkpL2xlbmd0aChjb2xuYW1lcyhzZXF0YWIpKQ0KYWJ1bmRfcmVtIDwtIHN1bShzZXF0YWJfbm9jaGltKS9zdW0oc2VxdGFiKQ0KbWVzc2FnZShwYXN0ZShyb3VuZChzZXFzX3JlbSoxMDAsMiksICIlIG9mIGluaXRpYWwgc2VxdWVuY2VzIGFuZCAiLHJvdW5kKGFidW5kX3JlbSoxMDAsMiksICIlIGluaXRpYWwgYWJ1bmRhbmNlIHJlbWFpbmluZyBhZnRlciBjaGltZXJhIHJlbW92YWwiKSkNCiAgDQojIFJlbW92ZSBhc3ZzIG91dHNpZGUgZXhwZWN0ZWQgc2l6ZQ0KZXhwZWN0ZWRfc2l6ZSA8LSAyMDUNCm1pbl9sZW5ndGggPSBleHBlY3RlZF9zaXplIC0gMTANCm1heF9sZW5ndGggPSBleHBlY3RlZF9zaXplICsgMTANCnNlcXRhYl9jdXQgPC0gc2VxdGFiX25vY2hpbVssbmNoYXIoY29sbmFtZXMoc2VxdGFiX25vY2hpbSkpICVpbiUgbWluX2xlbmd0aDptYXhfbGVuZ3RoXQ0KDQojIENoZWNrIGhvdyBtYW55IHdlcmUgcmVtb3ZlZA0Kc2Vxc19yZW0gPC0gbGVuZ3RoKGNvbG5hbWVzKHNlcXRhYl9jdXQpKS9sZW5ndGgoY29sbmFtZXMoc2VxdGFiKSkNCmFidW5kX3JlbSA8LSBzdW0oc2VxdGFiX2N1dCkvc3VtKHNlcXRhYikNCm1lc3NhZ2UocGFzdGUocm91bmQoc2Vxc19yZW0qMTAwLDIpLCAiJSBvZiBpbml0aWFsIHNlcXVlbmNlcyBhbmQgIixyb3VuZChhYnVuZF9yZW0qMTAwLDIpLCAiJSBpbml0aWFsIGFidW5kYW5jZSByZW1haW5pbmcgYWZ0ZXIgbGVuZ3RoIGZpbHRlcmluZyIpKQ0KDQojIEFsaWduIGFnYWluc3QgcGhtbQ0KcGhtbV9tb2RlbCA8LSByZWFkUkRTKCJyZWZlcmVuY2UvZm9sbWVyX2Z1bGxlbmd0aF9tb2RlbC5yZHMiKQ0KDQojIFN1YnNldCBwaG1tIHRvIG91ciBwcmltZXIgc2VxdWVuY2VzDQpwaG1tX21vZGVsIDwtIHRheHJldHVybjo6c3Vic2V0X21vZGVsKHBobW1fbW9kZWwsIHByaW1lcnMgPSBjKCJHR0RBQ1dHR1dUR0FBQ1dHVFdUQVlDQ0hDQyIsIkdUUkFUV0dDSENDREdDVEFSV0FDV0dHIikpDQoNCnNlcXMgPC0gRE5BU3RyaW5nU2V0KGNvbG5hbWVzKHNlcXRhYl9jdXQpKQ0KbmFtZXMoc2VxcykgPC0gY29sbmFtZXMoc2VxdGFiX2N1dCkNCnBobW1fZmlsdCA8LSB0YXhyZXR1cm46Om1hcF90b19tb2RlbCgNCiAgc2VxcywgbW9kZWwgPSBwaG1tX21vZGVsLCBtaW5fc2NvcmUgPSAxMDAsIG1pbl9sZW5ndGggPSAxMDAsDQogIHNoYXZlID0gRkFMU0UsIGNoZWNrX2ZyYW1lID0gY2hlY2tfZnJhbWUsIGttZXJfdGhyZXNob2xkID0gMC41LCBrPTUsIGV4dHJhID0gImZpbGwiKQ0KICAgIA0Kc2VxdGFiX3BobW0gPC0gc2VxdGFiX2N1dFssY29sbmFtZXMoc2VxdGFiX2N1dCkgJWluJSBuYW1lcyhwaG1tX2ZpbHQpXQ0KICAgIA0KIyBDaGVjayBob3cgbWFueSB3ZXJlIHJlbW92ZWQNCnNlcXNfcmVtIDwtIGxlbmd0aChjb2xuYW1lcyhzZXF0YWJfcGhtbSkpL2xlbmd0aChjb2xuYW1lcyhzZXF0YWIpKQ0KYWJ1bmRfcmVtIDwtIHN1bShzZXF0YWJfcGhtbSkvc3VtKHNlcXRhYikNCm1lc3NhZ2UocGFzdGUocm91bmQoc2Vxc19yZW0qMTAwLDIpLCAiJSBvZiBpbml0aWFsIHNlcXVlbmNlcyBhbmQgIixyb3VuZChhYnVuZF9yZW0qMTAwLDIpLCAiJSBvZiBpbml0aWFsIGFidW5kYW5jZSByZW1haW5pbmcgYWZ0ZXIgUEhNTSBmaWx0ZXJpbmciKSkNCg0KI0ZpbHRlciBzZXF1ZW5jZXMgY29udGFpbmluZyBzdG9wIGNvZG9ucw0Kc2VxcyA8LSBETkFTdHJpbmdTZXQoY29sbmFtZXMoc2VxdGFiX3BobW0pKQ0KbmFtZXMoc2VxcykgPC0gY29sbmFtZXMoc2VxdGFiX3BobW0pDQpjb2Rvbl9maWx0IDwtIHRheHJldHVybjo6Y29kb25fZmlsdGVyKHNlcXMsIGdlbmV0aWNfY29kZSA9IGdlbmV0aWNfY29kZSkgDQpzZXF0YWJfZmluYWwgPC0gc2VxdGFiX3BobW1bLGNvbG5hbWVzKHNlcXRhYl9waG1tKSAlaW4lIG5hbWVzKGNvZG9uX2ZpbHQpXQ0KDQojIENoZWNrIGhvdyBtYW55IHdlcmUgcmVtb3ZlZA0Kc2Vxc19yZW0gPC0gbGVuZ3RoKGNvbG5hbWVzKHNlcXRhYl9maW5hbCkpL2xlbmd0aChjb2xuYW1lcyhzZXF0YWIpKQ0KYWJ1bmRfcmVtIDwtIHN1bShzZXF0YWJfZmluYWwpL3N1bShzZXF0YWIpDQptZXNzYWdlKHBhc3RlKHJvdW5kKHNlcXNfcmVtKjEwMCwyKSwgIiUgb2YgaW5pdGlhbCBzZXF1ZW5jZXMgYW5kICIscm91bmQoYWJ1bmRfcmVtKjEwMCwyKSwgIiUgb2YgaW5pdGlhbCBhYnVuZGFuY2UgcmVtYWluaW5nIGFmdGVyIGNoZWNraW5nIHJlYWRpbmcgZnJhbWUiKSkNCg0KIyBTYXZlIGZpbmFsIGZpbHRlcmVkIHNlcXRhYg0Kc2F2ZVJEUyhzZXF0YWJfZmluYWwsICJvdXRwdXQvcmRzL3NlcXRhYl9maW5hbC5yZHMiKQ0KDQpgYGANCg0KIyBBc3NpZ24gdGF4b25vbXkgdXNpbmcgSURUQVhBDQoNCmBgYHtyfQ0KIyBDcmVhdGUgYSBETkFTdHJpbmdTZXQgZnJvbSB0aGUgQVNWcw0Kc2VxcyA8LSBETkFTdHJpbmdTZXQoZ2V0U2VxdWVuY2VzKHNlcXRhYl9maW5hbCkpIA0KDQojIExvYWQgdGhlIHJlbGV2ZW50IGRiDQp0cmFpbmluZ1NldCA8LSByZWFkUkRTKCJyZWZlcmVuY2UvaWR0YXhhX2JmdHJpbW1lZC5yZHMiKQ0KcmFua3MgPSBjKCJSb290IiwiS2luZ2RvbSIsICJQaHlsdW0iLCJDbGFzcyIsICJPcmRlciIsICJGYW1pbHkiLCAiR2VudXMiLCJTcGVjaWVzIikNCg0KIyBDbGFzc2lmeSANCmlkcyA8LSBERUNJUEhFUjo6SWRUYXhhKHNlcXMsIHRyYWluaW5nU2V0LCBwcm9jZXNzb3JzPTEsIHRocmVzaG9sZCA9IDYwLCB2ZXJib3NlPVRSVUUsIHN0cmFuZCA9ICJ0b3AiKSANCiAgICANCiNDb252ZXJ0IHRoZSBvdXRwdXQgb2JqZWN0IG9mIGNsYXNzICJUYXhhIiB0byBhIG1hdHJpeCBhbmFsb2dvdXMgdG8gdGhlIG91dHB1dCBmcm9tIGFzc2lnblRheG9ub215DQp0YXh0YWIgPC0gaWRzICU+JQ0KICBwdXJycjo6bWFwX2RmcihmdW5jdGlvbih4KXsNCiAgICB0YXhhIDwtIHBhc3RlMCh4JHRheG9uLCJfIiwgeCRjb25maWRlbmNlKQ0KICAgIHRheGFbc3RhcnRzV2l0aCh0YXhhLCAidW5jbGFzc2lmaWVkXyIpXSA8LSBOQQ0KICAgIGRhdGEuZnJhbWUodCh0YXhhKSkgJT4lDQogICAgbWFncml0dHI6OnNldF9jb2xuYW1lcyhyYW5rc1sxOm5jb2woLildKQ0KICB9KSAlPiUNCiAgbXV0YXRlX2FsbChzdHJpbmdyOjpzdHJfcmVwbGFjZSxwYXR0ZXJuPSIoPzouKD8hXykpKyQiLCByZXBsYWNlbWVudD0iIikgJT4lDQogIG1hZ3JpdHRyOjpzZXRfcm93bmFtZXMoZ2V0U2VxdWVuY2VzKHNlcXRhYl9maW5hbCkpDQoNCmBgYA0KDQoNCiMgQ3JlYXRlIHBoeWxvc2VxIG9iamVjdA0KYGBge3J9DQpzYW1kZiA8LSByZWFkX2Nzdigic2FtcGxlX2RhdGEuY3N2IikNCg0KcHMgPC0gcGh5bG9zZXEodGF4X3RhYmxlKHRheHRhYiksDQogICAgICAgICAgICAgICAgICAgc2FtcGxlX2RhdGEoc2FtZGYpLA0KICAgICAgICAgICAgICAgICAgIG90dV90YWJsZShzZXF0YWIsIHRheGFfYXJlX3Jvd3MgPSBGQUxTRSksDQogICAgICAgICAgICAgICAgICAgcmVmc2VxKHNlcXMpKQ0KDQpzYXZlUkRTKHBzLCAib3V0cHV0L3Jkcy9wcy5yZHMiKQ0KDQojIEZpbHRlciB0byBvbmx5IGluc2VjdHMNCnBzMCA8LSBwczAgJT4lDQogICAgc3Vic2V0X3RheGFfbmV3KA0KICAgICAgcmFuayA9ICJDbGFzcyIsDQogICAgICB2YWx1ZSA9ICJJbnNlY3RhIg0KICAgICkgJT4lDQogICAgZmlsdGVyX3RheGEoZnVuY3Rpb24oeCkgbWVhbih4KSA+IDAsIFRSVUUpICMgRHJvcCB6ZXJvcw0KDQojIEZpbHRlciB0YXhhIHVuZGVyIDAuMSUgcmVsYXRpdmUgYWJ1bmRhbmNlDQptaW5fdGF4YV9yYSA9IDFlLTQNCnBzMSA8LSBwaHlsb3NlcTo6dHJhbnNmb3JtX3NhbXBsZV9jb3VudHMocHMwLCBmdW5jdGlvbihPVFUsIGFiID0gbWluX3RheGFfcmEgKXsNCiAgICAgIGlmZWxzZSgoT1RVIC8gc3VtKE9UVSkpIDw9IGFiLCAgMCwgT1RVKSANCiAgICB9KQ0KDQojIFNhdmUgZmlsdGVyZWQgcGh5bG9zZXEgb2JqZWN0DQpzYXZlUkRTKHBzMSwgIm91dHB1dC9yZHMvcHNfZmlsdGVyZWQucmRzIikNCg0KIyBFeHBvcnQgc3BlY2llcyBsZXZlbCBzdW1tYXJ5IG9mIGZpbHRlcmVkIHJlc3VsdHMNCnBzMSAlPiUNCiAgcGh5bG9zZXE6OnBzbWVsdCgpICU+JQ0KICBmaWx0ZXIoQWJ1bmRhbmNlID4gMCkgJT4lDQogIGxlZnRfam9pbihyZWZzZXEocHMpICU+JSBhcy5jaGFyYWN0ZXIoKSAlPiUgZW5mcmFtZShuYW1lPSJPVFUiLCB2YWx1ZT0ic2VxdWVuY2UiKSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoT1RVLCBzZXF1ZW5jZSwgcmFua19uYW1lcyhwczEpLCBzYW1wbGVfaWQsIEFidW5kYW5jZSApICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gc2FtcGxlX2lkLA0KICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IEFidW5kYW5jZSwNCiAgICAgICAgICAgICAgdmFsdWVzX2ZpbGwgPSBsaXN0KEFidW5kYW5jZSA9IDApKSAlPiUNCiAgd3JpdGUuY3N2KCJvdXRwdXQvcmVzdWx0cy9zdW1tYXJ5X2ZpbHRlcmVkLmNzdiIpDQoNCmBgYA0KDQoNCg==