Install & Load packages
#Set required packages
.cran_packages <- c("ggplot2",
"gridExtra",
"tidyverse",
"scales",
"stringdist",
"patchwork",
"seqinr",
"viridis",
"ape",
"data.table",
"RColorBrewer",
"vegan",
"geiger")
.bioc_packages <- c("dada2",
"phyloseq",
"DECIPHER",
"Biostrings",
"ShortRead",
"Biostrings",
"ALDEx2")
.inst <- .cran_packages %in% installed.packages()
if(any(!.inst)) {
install.packages(.cran_packages[!.inst])
}
.inst <- .bioc_packages %in% installed.packages()
if(any(!.inst)) {
if (!requireNamespace("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install(.bioc_packages[!.inst], ask = F)
}
#Load all packages
sapply(c(.cran_packages,.bioc_packages), require, character.only = TRUE)
## ggplot2 gridExtra tidyverse scales stringdist patchwork
## TRUE TRUE TRUE TRUE TRUE TRUE
## seqinr viridis ape data.table RColorBrewer vegan
## TRUE TRUE TRUE TRUE TRUE TRUE
## geiger dada2 phyloseq DECIPHER Biostrings ShortRead
## TRUE TRUE TRUE TRUE TRUE TRUE
## Biostrings ALDEx2
## TRUE TRUE
#Install and load github packages
devtools::install_github("alexpiper/seqateurs")
library(seqateurs)
devtools::install_github("tobiasgf/lulu")
library(lulu)
devtools::install_github("mikemc/speedyseq")
library(speedyseq)
devtools::install_github('ggloor/CoDaSeq/CoDaSeq')
library(CoDaSeq)
options(stringsAsFactors = FALSE)
Quality Control
Split files by seqrun
## R
samdf <- read.csv("sample_data/sample_info.csv")
run1 <- samdf %>%
filter(seqrun==1) %>%
pull(SampleID) %>%
as.character()
write_lines(run1,path="Run1.txt")
run2 <- samdf %>%
filter(seqrun==2) %>%
pull(SampleID) %>%
as.character()
write_lines(run2,path="Run2.txt")
run3 <- samdf %>%
filter(seqrun==3) %>%
pull(SampleID) %>%
as.character()
write_lines(run3,path="Run3.txt")
## BASH
mkdir run_1
cat Run1.txt | while read i; do
payload=$(ls | grep $i)
echo $payload
mv $payload run_1
done
mkdir run_2
cat Run2.txt | while read i; do
payload=$(ls | grep $i)
echo $payload
mv $payload run_2
done
mkdir run_3
cat Run3.txt | while read i; do
payload=$(ls | grep $i)
echo $payload
mv $payload run_3
done
Sequencer ID & fastq structure for each run
run_1 - @M01895:3:000000000-AFED3:1:1101:11856:1005 1:N:0:89
run_2 - @M00598:140:000000000-ALTW6:1:1104:15177:9425 2:N:0:120
run_3 - @M00933:9:000000000-BFR4B:1:1101:13542:1780 1:N:0:86
Sequence quality control
library(ShortRead)
runs <- dir("data/", pattern="run_")
for (i in seq(along=runs)){
path <- paste0("data/", runs[i])
#Plot number of reads
dat <- as.data.frame(countLines(dirPath=path, pattern=".fastq")) %>%
rownames_to_column() %>%
`colnames<-`(c("Sample", "Reads")) %>%
filter(str_detect(Sample,"R1"))
#Plot pooling
gg.pooling <- ggplot(data=dat, aes(x=Sample,y=Reads),stat="identity") +
geom_bar(aes(fill=Reads),stat="identity") +
scale_fill_viridis(name = "Reads", begin=0.1) +
theme(axis.text.x = element_text(angle=90, hjust=1), plot.title=element_text(hjust = 0.5), plot.subtitle =element_text(hjust = 0.5))+
geom_hline(aes(yintercept = mean(Reads))) +
xlab("sample name")+
ylab("Number of reads") +
labs(title= paste0("Pooling for : ", runs[i]), subtitle = paste0("Total Reads: ", sum(dat$Reads), " Average reads: ", sprintf("%.0f",mean(dat$Reads))," Standard deviation: ", sprintf("%.0f",sd(dat$Reads)))) +
coord_flip()
plot(gg.pooling)
}
Trim primers
DADA2 requires Non-biological nucleotides i.e. primers, adapters, linkers, etc to be removed. Prior to begining this workflow, samples were demultiplexed and illumina adapters were removed by the MiSeq software, however primer sequences still remain in the reads and must be removed prior to use with the DADA2 algorithm.
Primer sequences:
16S F CCTACGGGNGGCWGCAG
16S R GACTACHVGGGTATCTAATCC
All reads were trimmed to 300bp, then primers were removed. All reads for which primers were not detected were removed using the maxlength function.
#Install bbmap
seqateurs::bbmap_install()
#Loop over runs - Maxlength set to remove any untrimmed reads
runs <- dir("data/", pattern="run_")
for (i in seq(along=runs)){
path <- paste0("data/",runs[i])
#Trim forward primers - Set a maxlength to remove all those that werent trimemd
fastqFs <- sort(list.files(path, pattern="_R1", full.names = TRUE))
fastqRs <- sort(list.files(path, pattern="_R2_", full.names = TRUE))
bbtools_trim(install="bin/bbmap", fwd=fastqFs,rev=fastqRs, primers=c("CCTACGGGNGGCWGCAG","GACTACHVGGGTATCTAATCC"), copyundefined=TRUE, outpath="trimmed",ktrim="l", ordered=TRUE, mink=FALSE, hdist=2, overwrite=TRUE, samelength=TRUE, forcetrimright = 300, maxlength = 285)
}
Plot read quality & lengths
runs <- dir("data/", pattern="run_")
readcounts <- vector("list", length=length(runs))
for (i in seq(along=runs)){
path <- paste0("data/",runs[i],"/trimmed" )
filtFs <- sort(list.files(path, pattern="_R1_", full.names = TRUE))
filtRs <- sort(list.files(path, pattern="_R2_", full.names = TRUE))
p1 <- plotQualityProfile(filtFs, aggregate = TRUE) + ggtitle(paste0(runs[i]," Forward Reads"))
p2 <- plotQualityProfile(filtRs, aggregate = TRUE) + ggtitle(paste0(runs[i]," Reverse Reads"))
#output plots
dir.create("output/figures/")
pdf(paste0("output/figures/",runs[i],"_prefilt_quality.pdf"), width = 11, height = 8 , paper="a4r")
plot(p1+p2)
dev.off()
#Get lengths
readcounts[[i]] <- cbind(width(readFastq(file.path(path, fastqFs))), width(readFastq(file.path(path, fastqRs))))
}
The max expected error function is used as the primary quality filter, and all reads containing N bases were removed
In order to reduce the amount of reverse reads violating the MaxEE filter, the reverse reads were truncated at 200 to remove the quality crash that is typical of illumina sequencers
Total amplicon = 465bp Sequencing = 2x300bp = 600bp Primers = 17bp + 21bp = 38bp Read overlap = 600 - 465 - 38 = 97bp
reverse should potentiall be reduced further - (from 230 to 200)
runs <- dir("data/", pattern="run_")
filtered_out <- vector("list", length=length(runs))
readlengths <- vector("list", length=length(runs))
for (i in 1:length(runs)){
path <- paste0("data/",runs[i],"/trimmed/")
filtpath <- file.path(path, "filtered")
dir.create(filtpath)
fastqFs <- sort(list.files(path, pattern="R1_001.*"))
fastqRs <- sort(list.files(path, pattern="R2_001.*"))
if(length(fastqFs) != length(fastqRs)) stop(paste0("Forward and reverse files for ",runs[i]," do not match."))
filtered_out[[i]] <- (filterAndTrim(fwd=file.path(path, fastqFs), filt=file.path(filtpath, fastqFs),
rev=file.path(path, fastqRs), filt.rev=file.path(filtpath, fastqRs),
maxEE=c(2,3),truncQ = 0,truncLen=c(280,200), maxN = 0, rm.phix=TRUE, compress=TRUE, verbose=TRUE))
# post filtering plot
filtFs <- sort(list.files(filtpath, pattern="R1_001.*", full.names = TRUE))
filtRs <- sort(list.files(filtpath, pattern="R2_001.*", full.names = TRUE))
p1 <- plotQualityProfile(filtFs, aggregate = TRUE) + ggtitle(paste0(runs[i]," Forward Reads"))
p2 <- plotQualityProfile(filtRs, aggregate = TRUE) + ggtitle(paste0(runs[i]," Reverse Reads"))
#output plots
dir.create("output/figures/")
pdf(paste0("output/figures/",runs[i],"_postfilt_quality.pdf"), width = 11, height = 8 , paper="a4r")
plot(p1+p2)
dev.off()
#Get lengths post filter
readlengths[[i]] <- cbind(width(readFastq(file.path(filtFs))), width(readFastq(file.path(filtRs))))
}
print(filtered_out)
Sequence processing
Infer sequence variants for each run
runs <- dir("data/", pattern="run_")
set.seed(100)
for (i in seq(along=runs)){
path <- paste0("data/",runs[i],"/trimmed/" )
filtpath <- file.path(path, "filtered")
filtFs <- list.files(filtpath, pattern="R1_001.*", full.names = TRUE)
filtRs <- list.files(filtpath, pattern="R2_001.*", full.names = TRUE)
# Learn error rates from samples
errF <- learnErrors(filtFs, multithread=TRUE, randomize=TRUE)
errR <- learnErrors(filtRs, multithread=TRUE, randomize=TRUE)
##Print error plots to see how well the algorithm modelled the errors in the different runs
print(plotErrors(errF, nominalQ=TRUE)+ ggtitle(paste0(runs[i]," Forward Reads")))
print(plotErrors(errR, nominalQ=TRUE)+ ggtitle(paste0(runs[i]," Reverse Reads")))
#Error inference and merger of reads - Using pseudo pooling for increased sensitivity
dadaFs <- dada(filtFs, err=errF, multithread=TRUE, pool="pseudo")
dadaRs <- dada(filtRs, err=errR, multithread=TRUE, pool="pseudo")
mergers <- mergePairs(dadaFs, filtFs, dadaRs, filtRs, verbose=TRUE, minOverlap = 20, trimOverhang = TRUE )
# Construct sequence table
seqtab <- makeSequenceTable(mergers)
saveRDS(seqtab, paste0("output/rds/", runs[i], "_seqtab.rds"))
}
Merge Runs, Remove Chimeras
Now that the sequence tables are created for each run, they need to be merged into a larger table representing the entire study. Looking at the length of the sequences, we see some off target amplification. There are 3 large peaks, the first one at 280, second at 402bp, and third at 427. The 427bp peak contains the majority of the sequences and is the expected size, while the 402bp peak contains wolbachia which have a 24bp deletion. We will cut the sequences to between 400 and 450, which should take into account any length variation, and remove the 280bp peak (which is the length of the forward read and most likely artefactual). Following this, chimeric sequences are identified and removed using removeBimeraDenovo
set.seed(606)
seqtabs <- list.files("output/rds/", pattern="seqtab.rds", full.names = TRUE)
st.all <- mergeSequenceTables(tables=seqtabs)
#Remove chimeras
seqtab.nochim <- removeBimeraDenovo(st.all, method="consensus", multithread=TRUE, verbose=TRUE)
#Check output of chimera removal
print(paste(sum(seqtab.nochim)/sum(st.all),"of the abundance remaining after chimera removal"))
#Check complexity
hist(seqComplexity(seqtab.nochim), 100)
#Look at seqlengths
plot(table(nchar(getSequences(seqtab.nochim))))
#cut to expected size
#seqtab.nochim <- seqtab.nochim[,nchar(colnames(seqtab.nochim)) %in% 400:435]
#seqtab_cut <- seqtab.nochim[,nchar(colnames(seqtab.nochim)) %in% 400:435]
# Count hpw many this removed
print(paste(sum(seqtab.nochim)/sum(st.all),"of the abundance remaining after cutting"))
#Fix names -removing read name, sample number etcc
rownames(seqtab.nochim) <- rownames(seqtab.nochim) %>%
str_split_fixed("_",n=Inf) %>%
as_tibble() %>%
unite(col=SampleID, c("V1","V2"),sep="_") %>%
pull(SampleID)
dir.create("output/rds/")
saveRDS(seqtab.nochim, "output/rds/seqtab_nocut.rds")
# summarise cleanup
cleanup <- st.all %>%
as.data.frame() %>%
pivot_longer( everything(),
names_to = "OTU",
values_to = "Abundance") %>%
group_by(OTU) %>%
summarise(Abundance = sum(Abundance)) %>%
mutate(length = nchar(OTU)) %>%
mutate(type = case_when(
!OTU %in% getSequences(seqtab.nochim) ~ "Chimera",
TRUE ~ "Real"
))
# Output length distribution plots
gg.abundance <- ggplot(cleanup, aes(x=length, y=Abundance, fill=type))+
geom_bar(stat="identity") +
ggtitle("Abundance of sequences") +
geom_vline(xintercept = 400, colour="red") +
geom_vline(xintercept = 435, colour="red")
gg.unique <- ggplot(cleanup, aes(x=length, fill=type))+
geom_histogram(binwidth = 1) +
ggtitle("Number of unique sequences")+
geom_vline(xintercept = 400, colour="red") +
geom_vline(xintercept = 435, colour="red")
plot(gg.abundance / gg.unique)
pdf(paste0("output/logs/seqtab_length_dist.pdf"), width = 11, height = 8 , paper="a4r")
plot(gg.abundance / gg.unique)
try(dev.off(), silent=TRUE)
Assign taxonomy with IDTAXA
We will use the IDTAXA algorithm of Murali et al 2018 - https://doi.org/10.1186/s40168-018-0521-5 Folllowing phylum to genus assignment with IDTAXA, we will also use exact matching with a reference database to assign to species level.
#seqtab_final <- readRDS("output/rds/seqtab_final.rds")
seqtab_final <- readRDS("output/rds/seqtab_nocut.rds")
# Load trainingset
load("reference/SILVA_SSU_r138_2019.RData")
# Create a DNAStringSet from the ASVs
dna <- DNAStringSet(getSequences(seqtab_final))
# Assign taxonomy
library(DECIPHER)
ids <- IdTaxa(dna, trainingSet, processors=8, threshold = 60, verbose=TRUE)
saveRDS(ids, "ids_nocut.rds")
# Output plot of ids
pdf(paste0("figs/idtaxa.pdf"), width = 11, height = 8 , paper="a4r")
plot(ids)
try(dev.off(), silent=TRUE)
#Convert the output object of class "Taxa" to a matrix analogous to the output from assignTaxonomy
tax <- t(sapply(ids, function(x) {
taxa <- paste0(x$taxon,"_", x$confidence)
taxa[startsWith(taxa, "unclassified_")] <- NA
taxa
})) %>%
purrr::map(unlist) %>%
stringi::stri_list2matrix(byrow=TRUE, fill=NA) %>%
magrittr::set_colnames(c("root", "domain", "phylum", "class", "order", "family", "genus")) %>%
magrittr::set_rownames(getSequences(seqtab_final)) %>%
as.data.frame() %>%
mutate_all(str_replace,pattern="(?:.(?!_))+$", replacement="") %>%
#seqateurs::na_to_unclassified() %>% #Propagate high order ranks to unassigned ASV'
magrittr::set_rownames(getSequences(seqtab_final)) %>%
as.matrix()
# Write taxonomy table to disk
saveRDS(tax, "output/rds/tax_IdTaxa_nocut.rds")
#Add species using exact matching
exact <- assignSpecies(seqtab_final, "reference/silva_species_assignment_v138.fa.gz", allowMultiple =TRUE, tryRC = TRUE, verbose = FALSE)
exact <- exact %>%
as.data.frame(stringsAsFactors=FALSE) %>%
rownames_to_column("otu") %>%
mutate(species = case_when(!is.na(Species) ~ paste0(Genus,"_",Species)))
tax_exact <- tax %>%
as.data.frame(stringsAsFactors=FALSE) %>%
rownames_to_column("otu") %>%
left_join(exact %>% select(otu, species), by="otu") %>%
magrittr::set_rownames(.$otu) %>%
select(-otu) %>%
na_to_unclassified() %>% #Propagate high order ranks to unassigned ASV'
as.matrix()
saveRDS(tax_exact, "output/rds/tax_IdTaxaExact_nocut.rds")
Compare assignment to blast top hit
seqtab_final <- readRDS("output/rds/seqtab_nocut.rds")
tax <- readRDS("output/rds/tax_IdTaxaExact_nocut.rds")
seqs <- insect::char2dna(colnames(seqtab_final))
names(seqs) <- colnames(seqtab_final)
out <- blast_top_hit(query=seqs, db="reference/silva_species_assignment_v138.fa.gz", threshold=60 )
saveRDS(out, "output/rds/blast_top_hit.rds")
# Blast counts only the aligned subsections when calculating % identity
# Therefore we need to set a minimum and maximum alignment length allowed
out <- readRDS("output/rds/blast_top_hit.rds")
hist(out$length)
allowed_lengths <- 220:500
joint <- out %>%
dplyr::select(OTU = qseqid, acc, blastspp = Species, pident, length, evalue) %>%
left_join(tax %>%
seqateurs::unclassified_to_na(rownames=FALSE) %>%
mutate(lowest = seqateurs::lowest_classified(.)), by="OTU") %>%
dplyr::filter(length %in% allowed_lengths)
#Write out comparison between BLAST and Heiarchial assignment
write_csv(joint, "output/csv/tax_assignment_comparison.csv")
gg.tophit <- joint %>%
dplyr::select(pident, rank = lowest) %>%
mutate(rank = factor(rank, levels = c("root", "domain", "phylum", "class", "order", "family", "genus", "species"))) %>%
ggplot(aes(x=pident, fill=rank))+
geom_histogram(colour="black", binwidth = 1, position = "stack") +
labs(title = "Top hit identity distribution",
x = "BLAST top hit % identity",
y = "OTUs") +
scale_x_continuous(breaks=seq(60,100,2)) +
scale_fill_brewer(name = "Taxonomic \nAssignment", palette = "Spectral")
gg.tophit
pdf(paste0("output/figs/top_hit_tax_assignment.pdf"), width = 11, height = 8 , paper="a4r")
gg.tophit
try(dev.off(), silent=TRUE)
Make Phyloseq object
Following taxonomic assignment, the sequence table and taxonomic table are merged into a single phyloseq object alongside the sample info csv.
# Load data
seqtab <- t(readRDS("output/rds/seqtab_nocut.rds"))
#seqtab <- readRDS("output/rds/seqtab_curated.rds")
tax <- readRDS("output/rds/tax_IdTaxaExact_nocut.rds")
seqs <- DNAStringSet(rownames(seqtab))
names(seqs) <- seqs
samdf <- read_csv("sample_data/Sample_info3.csv") %>%
as.data.frame(stringsAsFactors=FALSE) %>%
magrittr::set_rownames(.$SampleID)
# Make phyloseq object
ps <- phyloseq(tax_table(tax),
sample_data(samdf),
otu_table(t(seqtab), taxa_are_rows = FALSE),
refseq(seqs))
if(nrow(seqtab) > nrow(sample_data(ps))){warning("Warning: All samples not included in phyloseq object, check sample names match the sample metadata")}
#Rename taxa
#taxa_names(ps) <- paste0("SV", seq(ntaxa(ps)),"-",tax_table(ps)[,8])
##save phyloseq object
saveRDS(ps, "output/rds/ps.rds")
#Output tables of results
dir.create("output/csv")
dir.create("output/otu_tables/unfiltered/", recursive = TRUE)
##Export raw csv
speedyseq::psmelt(ps) %>%
dplyr::filter(Abundance > 0) %>%
write.csv(file = "output/otu_tables/rawdata.csv")
# Export species level summary
seqateurs::summarise_taxa(ps, "species", "SampleID") %>%
spread(key="SampleID", value="totalRA") %>%
write.csv(file = "output/otu_tables/unfiltered/spp_sum.csv")
# Export genus level summary
seqateurs::summarise_taxa(ps, "genus", "SampleID") %>%
spread(key="SampleID", value="totalRA") %>%
write.csv(file = "output/otu_tables/unfiltered/gen_sum.csv")
#Check carsonella presence
cars <- speedyseq::psmelt(ps) %>%
filter(Abundance > 0) %>%
group_by(psyllid_spp) %>%
summarise(n = count(genus=="Candidatus Carsonella", na.rm = TRUE))
test <- speedyseq::psmelt(ps) %>%
filter(Abundance > 0) %>%
filter(genus=="Candidatus Carsonella")
Taxon Filtering
Remove all non-bacterial taxa, mitochondria, chloroplast and cyanobacteria. We also remove all taxa contained within the blank sample from other samples? - Check these
get_taxa_unique(ps, "domain")
ntaxa(ps) # Check the number of taxa prior to removal
ps0 <- ps %>%
subset_taxa(
domain == "Bacteria" &
family != "Mitochondria" &
order != "Chloroplast" &
phylum != "Cyanobacteria"
)
#Check taxa were removed
ntaxa(ps0)
get_taxa_unique(ps0, "phylum")
get_taxa_unique(ps0, "class")
get_taxa_unique(ps0, "family")
Remove outlier Samples
Detecting and potentially removing samples outliers (those samples with underlying data that do not conform to experimental or biological expectations) can be useful for minimizing technical variance. This can be caused by a number of reasons, including low-reads assigned to that sample. In this case we remove all samples below 1000 reads, as these include all samples contributing to lower than usual ASV counts.
Rarefaction curves are useful to assess sensitivity of sample size to observed alpha-diversity estimates.
## Remove mocks
rm_mocks <- c("mockA_S51", "MockEven_S193", "Mock_S192", "PCRctrl_S191", "MockStaggered_S194", "PCRctrl_S191", "91_S167")
#check mocks
ps1 <- ps0 %>%
subset_samples(!sample_names(ps0) %in% rm_mocks) %>% #Remove mocks
filter_taxa(function(x) mean(x) > 0, TRUE) #Drop missing taxa from table
message((nsamples(ps0) - nsamples(ps1)), " outlier samples dropped")
#Plot rarefaction curve
out <- rarecurve(otu_table(ps1), step=100)
rare <- lapply(out, function(x){
b <- as.data.frame(x)
b <- data.frame(OTU = b[,1], count = rownames(b))
b$count <- as.numeric(gsub("N", "", b$count))
return(b)
})
names(rare) <- sample_names(ps1)
rare <- map_dfr(rare, function(x){
z <- data.frame(x)
return(z)
}, .id = "sample")
# read threshold for sample removal
threshold = 1000
gg.rare <- ggplot(data = rare)+
geom_line(aes(x = count, y = OTU, group=sample), alpha=0.5)+
geom_point(data = rare %>%
group_by(sample) %>%
top_n(1, count),
aes(x = count, y = OTU, colour=(count > threshold))) +
scale_x_continuous(labels = scales::label_number_si()) +
geom_vline(xintercept=threshold, linetype="dashed") +
labs(colour = "Sample kept?") +
xlab("Sequence reads") +
ylab("Observed ASV's")
#Write out figure
pdf(file="figs/rarefaction.pdf", width = 11, height = 8 , paper="a4r")
plot(gg.rare)
try(dev.off(), silent=TRUE)
#Remove all samples under the minimum read threshold
ps2 <- prune_samples(sample_sums(ps1)>=threshold, ps1)
ps2 <- filter_taxa(ps2, function(x) mean(x) > 0, TRUE) #Drop missing taxa from table
message(nsamples(ps1) - nsamples(ps2), " Samples and ", ntaxa(ps1) - ntaxa(ps2), " taxa under read threshold Dropped")
Compare distances pre and post filtering
seqtab_final <- readRDS("output/rds/seqtab_nocut.rds")
#Get pre_filt distances
pre_filt <- as.matrix(zCompositions::cmultRepl(seqtab_final, method="BL", output="p-counts"))
pre_filt_dist <- as.matrix(vegdist(CoDaSeq::codaSeq.clr(pre_filt), method="euclidean"))
# Get post_filt distances
otutab <- otu_table(ps2) %>%
as("matrix")
#otutab <- t(seqtab)
post_filt <- as.matrix(zCompositions::cmultRepl(otutab, method="BL", output="p-counts"))
post_filt_dist <- as.matrix(vegdist(CoDaSeq::codaSeq.clr(post_filt), method="euclidean"))
#Subset to only common samples
subsample <- intersect(colnames(pre_filt_dist), colnames(post_filt_dist))
as.data.frame(vegan::mantel(pre_filt_dist[subsample, subsample], post_filt_dist[subsample, subsample])[c("statistic","signif","permutations")])
Output fastas
#Write out fasta and align with silva online
seqs <- refseq(ps2)
writeXStringSet(seqs, "output/curated_asv.fasta", width=1000)
Align with SINA
#Create a virtual environment
module load Python/3.8.2-GCCcore-9.3.0
virtualenv ~/SINA
source ~/SINA/bin/activate
#Install sina
wget https://github.com/epruesse/SINA/releases/download/v1.7.0/sina-1.7.0-linux.tar.gz
tar xf sina-1.7.0-linux.tar.gz
cd sina-1.7.0-linux
#Get referenceDB
wget https://www.arb-silva.de/fileadmin/silva_databases/release_138/ARB_files/SILVA_138_SSURef_NR99_05_01_20_opt.arb.gz
#Run sina
~/sina-1.7.0-linux/sina --search --add-relatives=5 --meta-fmt=header --fields=acc \
-i /group/pathogens/Alexp/Metabarcoding/Psyllid_microbiome/output/curated_asv.fasta \
-r /group/pathogens/Alexp/Metabarcoding/Psyllid_microbiome/reference/SILVA_138_SSURef_NR99_05_01_20_opt.arb \
-o /group/pathogens/Alexp/Metabarcoding/Psyllid_microbiome/output/aligned_asv.fasta
Create phylogenetic tree with fasttreee
module load FastTree
FastTree -gtr -cat 20 -quote -nt /group/pathogens/Alexp/Metabarcoding/Psyllid_microbiome/output/aligned_asv.fasta > /group/pathogens/Alexp/Metabarcoding/Psyllid_microbiome/output/sina_tree
Date tree with PATHd8
# Date usign congruify
#tree <- read.tree("arb-silva.de_2020-07-28_id860849/arb-silva.de_2020-07-28_id860849.tree")
tree <- read.tree("output/sina_tree")
tree$tip.label <- tree$tip.label %>%
str_split_fixed("\\[", n=Inf) %>%
as.data.frame() %>%
unite(V1, V2, col="name") %>%
mutate(name = name %>%
str_remove("_align.*$") %>%
str_remove("^.*_acc=") %>%
str_remove("\\]$")) %>%
pull(name)
tree_pruned <- drop.tip(tree, tree$tip.label[duplicated(tree$tip.label)])
#Reference tree
ref_tree <- read.tree("bin/Dated trees/Bacteria_16S_SILVA_97sim_FastTree_PATHd8.tre")
ref_tree$tip.label <- ref_tree$tip.label %>%
str_extract("^(.*?)\\.") %>%
str_remove("\\.$")
#2881 congruent tips
table(tree$tip.label %in% ref_tree$tip.label)
ref_tree_pruned <- drop.tip(ref_tree, c(ref_tree$tip.label[!ref_tree$tip.label %in% tree$tip.label],ref_tree$tip.label[duplicated(ref_tree$tip.label)]))
table(tree_pruned$tip.label %in% ref_tree_pruned$tip.label)
res <- congruify.phylo(reference=ref_tree_pruned, target=tree_pruned, scale="PATHd8")
tree2 <- drop.tip(res$phy, res$phy$tip.label[!res$phy$tip.label %in% names(refseq(ps2))])
write.tree(tree2, "output/phytree.nwk")
Merge technical replicates
With only 16 samples replicated, and only on the first 2 sequencing runs i dont think there is a way to explicitly take into account replicate variability, therefore all replicates were merged
This can be done with DADA2 - mergeSequenceTables(st1, st2, st3, repeats = “sum”
# Merge replicates
ps.merged <- ps2 %>%
merge_samples(group = "Sample_Name", fun="sum")
#This loses the sample metadata - Need to add it agian
sample_data(ps.merged) <- sample_data(ps2) %>%
as("matrix") %>%
as.data.frame() %>%
filter(!duplicated(Sample_Name)) %>%
magrittr::set_rownames(.$Sample_Name)
seqs <- refseq(ps2)
tree <- read.tree("output/phytree.nwk")
#make new phyloseq object
ps2 <- phyloseq(tax_table(ps.merged),
sample_data(ps.merged),
otu_table(otu_table(ps.merged), taxa_are_rows = FALSE),
refseq(seqs),
phy_tree(tree))
saveRDS(ps2, "output/rds/ps2.rds")
LS0tDQp0aXRsZTogIlBzeWxsaWQgbWljcm9iaW9tZSINCnN1YnRpdGxlOiAiQmlvaW5mb3JtYXRpYyBzZXF1ZW5jZSBwcm9jZXNzaW5nIg0KYXV0aG9yOiAiQWxleGFuZGVyIFBpcGVyIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGluY2x1ZGVzOg0KICAgICAgYWZ0ZXJfYm9keTogZm9vdGVyLmh0bWwNCiAgICBoaWdobGlnaHRlcjogbnVsbA0KICAgIHRoZW1lOiAiZmxhdGx5Ig0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KZWRpdG9yX29wdGlvbnM6IA0KICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQ0KLS0tDQoNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQojIEtuaXRyIGdsb2JhbCBzZXR1cCAtIGNoYW5nZSBldmFsIHRvIHRydWUgdG8gcnVuIGNvZGUNCmxpYnJhcnkoa25pdHIpDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGV2YWw9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsZXJyb3I9RkFMU0UsZmlnLnNob3cgPSAiaG9sZCIsIGZpZy5rZWVwID0gImFsbCIpDQpvcHRzX2NodW5rJHNldChkZXYgPSAncG5nJykNCmBgYA0KDQojIEluc3RhbGwgJiBMb2FkIHBhY2thZ2VzIA0KDQpgYGB7ciBMb2FkIHBhY2thZ2VzLCBldmFsPVRSVUUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFfSANCiNTZXQgcmVxdWlyZWQgcGFja2FnZXMNCi5jcmFuX3BhY2thZ2VzIDwtIGMoImdncGxvdDIiLCANCiAgICAgICAgICAgICAgICAgICAgImdyaWRFeHRyYSIsDQogICAgICAgICAgICAgICAgICAgICJ0aWR5dmVyc2UiLCANCiAgICAgICAgICAgICAgICAgICAgInNjYWxlcyIsIA0KICAgICAgICAgICAgICAgICAgICAic3RyaW5nZGlzdCIsIA0KICAgICAgICAgICAgICAgICAgICAicGF0Y2h3b3JrIiwNCiAgICAgICAgICAgICAgICAgICAgInNlcWluciIsDQogICAgICAgICAgICAgICAgICAgICJ2aXJpZGlzIiwgDQogICAgICAgICAgICAgICAgICAgICJhcGUiLCANCiAgICAgICAgICAgICAgICAgICAgImRhdGEudGFibGUiLA0KICAgICAgICAgICAgICAgICAgICAiUkNvbG9yQnJld2VyIiwNCiAgICAgICAgICAgICAgICAgICAgInZlZ2FuIiwNCiAgICAgICAgICAgICAgICAgICAgImdlaWdlciIpDQouYmlvY19wYWNrYWdlcyA8LSBjKCJkYWRhMiIsDQogICAgICAgICAgICAgICAgICAgICJwaHlsb3NlcSIsIA0KICAgICAgICAgICAgICAgICAgICAiREVDSVBIRVIiLA0KICAgICAgICAgICAgICAgICAgICAiQmlvc3RyaW5ncyIsDQogICAgICAgICAgICAgICAgICAgICJTaG9ydFJlYWQiLA0KICAgICAgICAgICAgICAgICAgICAiQmlvc3RyaW5ncyIsDQogICAgICAgICAgICAgICAgICAgICJBTERFeDIiKQ0KDQouaW5zdCA8LSAuY3Jhbl9wYWNrYWdlcyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpDQppZihhbnkoIS5pbnN0KSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcyguY3Jhbl9wYWNrYWdlc1shLmluc3RdKQ0KfQ0KLmluc3QgPC0gLmJpb2NfcGFja2FnZXMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKQ0KaWYoYW55KCEuaW5zdCkpIHsNCiAgaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSkNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJCaW9jTWFuYWdlciIpDQogIEJpb2NNYW5hZ2VyOjppbnN0YWxsKC5iaW9jX3BhY2thZ2VzWyEuaW5zdF0sIGFzayA9IEYpDQp9DQoNCiNMb2FkIGFsbCBwYWNrYWdlcw0Kc2FwcGx5KGMoLmNyYW5fcGFja2FnZXMsLmJpb2NfcGFja2FnZXMpLCByZXF1aXJlLCBjaGFyYWN0ZXIub25seSA9IFRSVUUpDQoNCiNJbnN0YWxsIGFuZCBsb2FkIGdpdGh1YiBwYWNrYWdlcw0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJhbGV4cGlwZXIvc2VxYXRldXJzIikNCmxpYnJhcnkoc2VxYXRldXJzKQ0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJ0b2JpYXNnZi9sdWx1IikNCmxpYnJhcnkobHVsdSkNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigibWlrZW1jL3NwZWVkeXNlcSIpDQpsaWJyYXJ5KHNwZWVkeXNlcSkNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YignZ2dsb29yL0NvRGFTZXEvQ29EYVNlcScpDQpsaWJyYXJ5KENvRGFTZXEpDQoNCm9wdGlvbnMoc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KYGBgDQoNCiMgUXVhbGl0eSBDb250cm9sDQoNCiMjIFNwbGl0IGZpbGVzIGJ5IHNlcXJ1bg0KYGBge3IgU3BsaXQgZmlsZXN9DQojIyBSDQpzYW1kZiA8LSByZWFkLmNzdigic2FtcGxlX2RhdGEvc2FtcGxlX2luZm8uY3N2IikNCnJ1bjEgPC0gc2FtZGYgJT4lDQogIGZpbHRlcihzZXFydW49PTEpICU+JQ0KICBwdWxsKFNhbXBsZUlEKSAlPiUNCiAgYXMuY2hhcmFjdGVyKCkNCndyaXRlX2xpbmVzKHJ1bjEscGF0aD0iUnVuMS50eHQiKQ0KDQpydW4yIDwtIHNhbWRmICU+JQ0KICBmaWx0ZXIoc2VxcnVuPT0yKSAlPiUNCiAgcHVsbChTYW1wbGVJRCkgJT4lDQogIGFzLmNoYXJhY3RlcigpDQp3cml0ZV9saW5lcyhydW4yLHBhdGg9IlJ1bjIudHh0IikNCg0KcnVuMyA8LSBzYW1kZiAlPiUNCiAgZmlsdGVyKHNlcXJ1bj09MykgJT4lDQogIHB1bGwoU2FtcGxlSUQpICU+JQ0KICBhcy5jaGFyYWN0ZXIoKQ0Kd3JpdGVfbGluZXMocnVuMyxwYXRoPSJSdW4zLnR4dCIpDQpgYGANCg0KDQpgYGB7YmFzaCBzcGxpdCBmaWxlc30NCiMjIEJBU0gNCm1rZGlyIHJ1bl8xDQpjYXQgUnVuMS50eHQgfCB3aGlsZSByZWFkIGk7IGRvDQpwYXlsb2FkPSQobHMgfCBncmVwICRpKQ0KZWNobyAkcGF5bG9hZA0KbXYgJHBheWxvYWQgcnVuXzENCmRvbmUNCg0KbWtkaXIgcnVuXzINCmNhdCBSdW4yLnR4dCB8IHdoaWxlIHJlYWQgaTsgZG8NCnBheWxvYWQ9JChscyB8IGdyZXAgJGkpDQplY2hvICRwYXlsb2FkDQptdiAkcGF5bG9hZCBydW5fMg0KZG9uZQ0KDQpta2RpciBydW5fMw0KY2F0IFJ1bjMudHh0IHwgd2hpbGUgcmVhZCBpOyBkbw0KcGF5bG9hZD0kKGxzIHwgZ3JlcCAkaSkNCmVjaG8gJHBheWxvYWQNCm12ICRwYXlsb2FkIHJ1bl8zDQpkb25lDQoNCmBgYA0KDQpTZXF1ZW5jZXIgSUQgJiBmYXN0cSBzdHJ1Y3R1cmUgZm9yIGVhY2ggcnVuDQoNCiAgICBydW5fMSAtIEBNMDE4OTU6MzowMDAwMDAwMDAtQUZFRDM6MToxMTAxOjExODU2OjEwMDUgMTpOOjA6ODkNCiAgICBydW5fMiAtIEBNMDA1OTg6MTQwOjAwMDAwMDAwMC1BTFRXNjoxOjExMDQ6MTUxNzc6OTQyNSAyOk46MDoxMjANCiAgICBydW5fMyAtIEBNMDA5MzM6OTowMDAwMDAwMDAtQkZSNEI6MToxMTAxOjEzNTQyOjE3ODAgMTpOOjA6ODYNCg0KIyMgU2VxdWVuY2UgcXVhbGl0eSBjb250cm9sDQpgYGB7ciBRQ30NCmxpYnJhcnkoU2hvcnRSZWFkKQ0KcnVucyA8LSBkaXIoImRhdGEvIiwgcGF0dGVybj0icnVuXyIpDQoNCmZvciAoaSBpbiBzZXEoYWxvbmc9cnVucykpew0KcGF0aCA8LSBwYXN0ZTAoImRhdGEvIiwgcnVuc1tpXSkNCg0KI1Bsb3QgbnVtYmVyIG9mIHJlYWRzDQpkYXQgPC0gYXMuZGF0YS5mcmFtZShjb3VudExpbmVzKGRpclBhdGg9cGF0aCwgcGF0dGVybj0iLmZhc3RxIikpICU+JQ0KICByb3duYW1lc190b19jb2x1bW4oKSAgJT4lDQogICBgY29sbmFtZXM8LWAoYygiU2FtcGxlIiwgIlJlYWRzIikpICU+JQ0KICBmaWx0ZXIoc3RyX2RldGVjdChTYW1wbGUsIlIxIikpDQoNCiNQbG90IHBvb2xpbmcNCmdnLnBvb2xpbmcgPC0gZ2dwbG90KGRhdGE9ZGF0LCBhZXMoeD1TYW1wbGUseT1SZWFkcyksc3RhdD0iaWRlbnRpdHkiKSArIA0KICBnZW9tX2JhcihhZXMoZmlsbD1SZWFkcyksc3RhdD0iaWRlbnRpdHkiKSAgKyANCiAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWUgPSAiUmVhZHMiLCBiZWdpbj0wLjEpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xKSwgcGxvdC50aXRsZT1lbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLCBwbG90LnN1YnRpdGxlID1lbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSsgDQogIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQgPSBtZWFuKFJlYWRzKSkpICArDQogIHhsYWIoInNhbXBsZSBuYW1lIikrDQogIHlsYWIoIk51bWJlciBvZiByZWFkcyIpICsgDQogIGxhYnModGl0bGU9IHBhc3RlMCgiUG9vbGluZyBmb3IgOiAiLCBydW5zW2ldKSwgc3VidGl0bGUgPSBwYXN0ZTAoIlRvdGFsIFJlYWRzOiAiLCBzdW0oZGF0JFJlYWRzKSwgIiBBdmVyYWdlIHJlYWRzOiAiLCAgc3ByaW50ZigiJS4wZiIsbWVhbihkYXQkUmVhZHMpKSwiIFN0YW5kYXJkIGRldmlhdGlvbjogIiwgc3ByaW50ZigiJS4wZiIsc2QoZGF0JFJlYWRzKSkpKSArDQogIGNvb3JkX2ZsaXAoKQ0KDQpwbG90KGdnLnBvb2xpbmcpDQp9DQpgYGANCg0KIyMgVHJpbSBwcmltZXJzDQoNCkRBREEyIHJlcXVpcmVzIE5vbi1iaW9sb2dpY2FsIG51Y2xlb3RpZGVzIGkuZS4gcHJpbWVycywgYWRhcHRlcnMsIGxpbmtlcnMsIGV0YyB0byBiZSByZW1vdmVkLiBQcmlvciB0byBiZWdpbmluZyB0aGlzIHdvcmtmbG93LCBzYW1wbGVzIHdlcmUgZGVtdWx0aXBsZXhlZCBhbmQgaWxsdW1pbmEgYWRhcHRlcnMgd2VyZSByZW1vdmVkIGJ5IHRoZSBNaVNlcSBzb2Z0d2FyZSwgaG93ZXZlciBwcmltZXIgc2VxdWVuY2VzIHN0aWxsIHJlbWFpbiBpbiB0aGUgcmVhZHMgYW5kIG11c3QgYmUgcmVtb3ZlZCBwcmlvciB0byB1c2Ugd2l0aCB0aGUgREFEQTIgYWxnb3JpdGhtLg0KDQpQcmltZXIgc2VxdWVuY2VzOg0KDQogICAgMTZTIEYgQ0NUQUNHR0dOR0dDV0dDQUcNCiAgICAxNlMgUiBHQUNUQUNIVkdHR1RBVENUQUFUQ0MNCg0KQWxsIHJlYWRzIHdlcmUgdHJpbW1lZCB0byAzMDBicCwgdGhlbiBwcmltZXJzIHdlcmUgcmVtb3ZlZC4gQWxsIHJlYWRzIGZvciB3aGljaCBwcmltZXJzIHdlcmUgbm90IGRldGVjdGVkIHdlcmUgcmVtb3ZlZCB1c2luZyB0aGUgbWF4bGVuZ3RoIGZ1bmN0aW9uLg0KDQpgYGB7ciB0cmltIHByaW1lcnMsbWVzc2FnZT1GQUxTRX0NCiNJbnN0YWxsIGJibWFwDQpzZXFhdGV1cnM6OmJibWFwX2luc3RhbGwoKQ0KDQojTG9vcCBvdmVyIHJ1bnMgLSBNYXhsZW5ndGggc2V0IHRvIHJlbW92ZSBhbnkgdW50cmltbWVkIHJlYWRzDQpydW5zIDwtIGRpcigiZGF0YS8iLCBwYXR0ZXJuPSJydW5fIikNCg0KZm9yIChpIGluIHNlcShhbG9uZz1ydW5zKSl7DQogIHBhdGggPC0gcGFzdGUwKCJkYXRhLyIscnVuc1tpXSkNCiAgDQogICNUcmltIGZvcndhcmQgcHJpbWVycyAtIFNldCBhIG1heGxlbmd0aCB0byByZW1vdmUgYWxsIHRob3NlIHRoYXQgd2VyZW50IHRyaW1lbWQNCiAgZmFzdHFGcyA8LSBzb3J0KGxpc3QuZmlsZXMocGF0aCwgcGF0dGVybj0iX1IxIiwgZnVsbC5uYW1lcyA9IFRSVUUpKQ0KICBmYXN0cVJzIDwtIHNvcnQobGlzdC5maWxlcyhwYXRoLCBwYXR0ZXJuPSJfUjJfIiwgZnVsbC5uYW1lcyA9IFRSVUUpKQ0KICANCiAgYmJ0b29sc190cmltKGluc3RhbGw9ImJpbi9iYm1hcCIsIGZ3ZD1mYXN0cUZzLHJldj1mYXN0cVJzLCBwcmltZXJzPWMoIkNDVEFDR0dHTkdHQ1dHQ0FHIiwiR0FDVEFDSFZHR0dUQVRDVEFBVENDIiksIGNvcHl1bmRlZmluZWQ9VFJVRSwgb3V0cGF0aD0idHJpbW1lZCIsa3RyaW09ImwiLCBvcmRlcmVkPVRSVUUsIG1pbms9RkFMU0UsIGhkaXN0PTIsIG92ZXJ3cml0ZT1UUlVFLCBzYW1lbGVuZ3RoPVRSVUUsIGZvcmNldHJpbXJpZ2h0ID0gMzAwLCBtYXhsZW5ndGggPSAyODUpDQp9DQpgYGANCg0KIyMgUGxvdCByZWFkIHF1YWxpdHkgJiBsZW5ndGhzDQoNCmBgYHtyIFFBIHBsb3R9DQpydW5zIDwtIGRpcigiZGF0YS8iLCBwYXR0ZXJuPSJydW5fIikNCnJlYWRjb3VudHMgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoPWxlbmd0aChydW5zKSkNCg0KZm9yIChpIGluIHNlcShhbG9uZz1ydW5zKSl7DQogcGF0aCA8LSBwYXN0ZTAoImRhdGEvIixydW5zW2ldLCIvdHJpbW1lZCIgKQ0KDQogIGZpbHRGcyA8LSBzb3J0KGxpc3QuZmlsZXMocGF0aCwgcGF0dGVybj0iX1IxXyIsIGZ1bGwubmFtZXMgPSBUUlVFKSkNCiAgZmlsdFJzIDwtIHNvcnQobGlzdC5maWxlcyhwYXRoLCBwYXR0ZXJuPSJfUjJfIiwgZnVsbC5uYW1lcyA9IFRSVUUpKQ0KICBwMSA8LSBwbG90UXVhbGl0eVByb2ZpbGUoZmlsdEZzLCBhZ2dyZWdhdGUgPSBUUlVFKSArIGdndGl0bGUocGFzdGUwKHJ1bnNbaV0sIiBGb3J3YXJkIFJlYWRzIikpIA0KICBwMiA8LSBwbG90UXVhbGl0eVByb2ZpbGUoZmlsdFJzLCBhZ2dyZWdhdGUgPSBUUlVFKSArIGdndGl0bGUocGFzdGUwKHJ1bnNbaV0sIiBSZXZlcnNlIFJlYWRzIikpDQogIA0KICAjb3V0cHV0IHBsb3RzDQogIGRpci5jcmVhdGUoIm91dHB1dC9maWd1cmVzLyIpDQogIHBkZihwYXN0ZTAoIm91dHB1dC9maWd1cmVzLyIscnVuc1tpXSwiX3ByZWZpbHRfcXVhbGl0eS5wZGYiKSwgd2lkdGggPSAxMSwgaGVpZ2h0ID0gOCAsIHBhcGVyPSJhNHIiKQ0KICBwbG90KHAxK3AyKQ0KICBkZXYub2ZmKCkNCiAgDQogICNHZXQgbGVuZ3Rocw0KICByZWFkY291bnRzW1tpXV0gPC0gY2JpbmQod2lkdGgocmVhZEZhc3RxKGZpbGUucGF0aChwYXRoLCBmYXN0cUZzKSkpLCB3aWR0aChyZWFkRmFzdHEoZmlsZS5wYXRoKHBhdGgsIGZhc3RxUnMpKSkpDQp9DQpgYGANCg0KVGhlIG1heCBleHBlY3RlZCBlcnJvciBmdW5jdGlvbiBpcyB1c2VkIGFzIHRoZSBwcmltYXJ5IHF1YWxpdHkgZmlsdGVyLCBhbmQgYWxsIHJlYWRzIGNvbnRhaW5pbmcgTiBiYXNlcyB3ZXJlIHJlbW92ZWQNCg0KSW4gb3JkZXIgdG8gcmVkdWNlIHRoZSBhbW91bnQgb2YgIHJldmVyc2UgcmVhZHMgdmlvbGF0aW5nIHRoZSBNYXhFRSBmaWx0ZXIsIHRoZSByZXZlcnNlIHJlYWRzIHdlcmUgdHJ1bmNhdGVkIGF0IDIwMCB0byByZW1vdmUgdGhlIHF1YWxpdHkgY3Jhc2ggdGhhdCBpcyB0eXBpY2FsIG9mIGlsbHVtaW5hIHNlcXVlbmNlcnMNCg0KVG90YWwgYW1wbGljb24gPSA0NjVicCANClNlcXVlbmNpbmcgPSAyeDMwMGJwID0gNjAwYnANClByaW1lcnMgPSAxN2JwICsgMjFicCA9IDM4YnANClJlYWQgb3ZlcmxhcCA9IDYwMCAtIDQ2NSAtIDM4ID0gOTdicA0KDQpyZXZlcnNlIHNob3VsZCBwb3RlbnRpYWxsIGJlIHJlZHVjZWQgZnVydGhlciAtIChmcm9tIDIzMCB0byAyMDApDQoNCmBgYHtyIGZpbHRlciBhbmQgdHJpbX0NCnJ1bnMgPC0gZGlyKCJkYXRhLyIsIHBhdHRlcm49InJ1bl8iKQ0KZmlsdGVyZWRfb3V0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aD1sZW5ndGgocnVucykpDQpyZWFkbGVuZ3RocyA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGg9bGVuZ3RoKHJ1bnMpKQ0KDQpmb3IgKGkgaW4gMTpsZW5ndGgocnVucykpew0KICBwYXRoIDwtIHBhc3RlMCgiZGF0YS8iLHJ1bnNbaV0sIi90cmltbWVkLyIpIA0KICBmaWx0cGF0aCA8LSBmaWxlLnBhdGgocGF0aCwgImZpbHRlcmVkIikNCiAgZGlyLmNyZWF0ZShmaWx0cGF0aCkNCiAgZmFzdHFGcyA8LSBzb3J0KGxpc3QuZmlsZXMocGF0aCwgcGF0dGVybj0iUjFfMDAxLioiKSkNCiAgZmFzdHFScyA8LSBzb3J0KGxpc3QuZmlsZXMocGF0aCwgcGF0dGVybj0iUjJfMDAxLioiKSkNCiAgDQogIGlmKGxlbmd0aChmYXN0cUZzKSAhPSBsZW5ndGgoZmFzdHFScykpIHN0b3AocGFzdGUwKCJGb3J3YXJkIGFuZCByZXZlcnNlIGZpbGVzIGZvciAiLHJ1bnNbaV0sIiBkbyBub3QgbWF0Y2guIikpDQogIA0KICBmaWx0ZXJlZF9vdXRbW2ldXSA8LSAoZmlsdGVyQW5kVHJpbShmd2Q9ZmlsZS5wYXRoKHBhdGgsIGZhc3RxRnMpLCBmaWx0PWZpbGUucGF0aChmaWx0cGF0aCwgZmFzdHFGcyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldj1maWxlLnBhdGgocGF0aCwgZmFzdHFScyksIGZpbHQucmV2PWZpbGUucGF0aChmaWx0cGF0aCwgZmFzdHFScyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heEVFPWMoMiwzKSx0cnVuY1EgPSAwLHRydW5jTGVuPWMoMjgwLDIwMCksIG1heE4gPSAwLCAgcm0ucGhpeD1UUlVFLCBjb21wcmVzcz1UUlVFLCB2ZXJib3NlPVRSVUUpKQ0KICANCiAgIyBwb3N0IGZpbHRlcmluZyBwbG90DQogIGZpbHRGcyA8LSBzb3J0KGxpc3QuZmlsZXMoZmlsdHBhdGgsIHBhdHRlcm49IlIxXzAwMS4qIiwgZnVsbC5uYW1lcyA9IFRSVUUpKQ0KICBmaWx0UnMgPC0gc29ydChsaXN0LmZpbGVzKGZpbHRwYXRoLCBwYXR0ZXJuPSJSMl8wMDEuKiIsIGZ1bGwubmFtZXMgPSBUUlVFKSkNCiAgcDEgPC0gcGxvdFF1YWxpdHlQcm9maWxlKGZpbHRGcywgYWdncmVnYXRlID0gVFJVRSkgKyBnZ3RpdGxlKHBhc3RlMChydW5zW2ldLCIgRm9yd2FyZCBSZWFkcyIpKSANCiAgcDIgPC0gcGxvdFF1YWxpdHlQcm9maWxlKGZpbHRScywgYWdncmVnYXRlID0gVFJVRSkgKyBnZ3RpdGxlKHBhc3RlMChydW5zW2ldLCIgUmV2ZXJzZSBSZWFkcyIpKQ0KICANCiAgI291dHB1dCBwbG90cw0KICBkaXIuY3JlYXRlKCJvdXRwdXQvZmlndXJlcy8iKQ0KICBwZGYocGFzdGUwKCJvdXRwdXQvZmlndXJlcy8iLHJ1bnNbaV0sIl9wb3N0ZmlsdF9xdWFsaXR5LnBkZiIpLCB3aWR0aCA9IDExLCBoZWlnaHQgPSA4ICwgcGFwZXI9ImE0ciIpDQogIHBsb3QocDErcDIpDQogIGRldi5vZmYoKQ0KICANCiAgI0dldCBsZW5ndGhzIHBvc3QgZmlsdGVyDQogIHJlYWRsZW5ndGhzW1tpXV0gPC0gY2JpbmQod2lkdGgocmVhZEZhc3RxKGZpbGUucGF0aChmaWx0RnMpKSksIHdpZHRoKHJlYWRGYXN0cShmaWxlLnBhdGgoZmlsdFJzKSkpKQ0KfQ0KcHJpbnQoZmlsdGVyZWRfb3V0KQ0KYGBgDQoNCiMgU2VxdWVuY2UgcHJvY2Vzc2luZw0KDQojIyBJbmZlciBzZXF1ZW5jZSB2YXJpYW50cyBmb3IgZWFjaCBydW4NCg0KYGBge3IgTGVhcm4gZXJyb3IgcmF0ZXMgfQ0KcnVucyA8LSBkaXIoImRhdGEvIiwgcGF0dGVybj0icnVuXyIpDQpzZXQuc2VlZCgxMDApDQoNCmZvciAoaSBpbiBzZXEoYWxvbmc9cnVucykpew0KIHBhdGggPC0gcGFzdGUwKCJkYXRhLyIscnVuc1tpXSwiL3RyaW1tZWQvIiApDQogIGZpbHRwYXRoIDwtIGZpbGUucGF0aChwYXRoLCAiZmlsdGVyZWQiKQ0KICANCiAgZmlsdEZzIDwtIGxpc3QuZmlsZXMoZmlsdHBhdGgsIHBhdHRlcm49IlIxXzAwMS4qIiwgZnVsbC5uYW1lcyA9IFRSVUUpDQogIGZpbHRScyA8LSBsaXN0LmZpbGVzKGZpbHRwYXRoLCBwYXR0ZXJuPSJSMl8wMDEuKiIsIGZ1bGwubmFtZXMgPSBUUlVFKQ0KICANCiAgIyBMZWFybiBlcnJvciByYXRlcyBmcm9tIHNhbXBsZXMNCiAgZXJyRiA8LSBsZWFybkVycm9ycyhmaWx0RnMsIG11bHRpdGhyZWFkPVRSVUUsIHJhbmRvbWl6ZT1UUlVFKQ0KICBlcnJSIDwtIGxlYXJuRXJyb3JzKGZpbHRScywgbXVsdGl0aHJlYWQ9VFJVRSwgcmFuZG9taXplPVRSVUUpDQogIA0KICAjI1ByaW50IGVycm9yIHBsb3RzIHRvIHNlZSBob3cgd2VsbCB0aGUgYWxnb3JpdGhtIG1vZGVsbGVkIHRoZSBlcnJvcnMgaW4gdGhlIGRpZmZlcmVudCBydW5zDQogIHByaW50KHBsb3RFcnJvcnMoZXJyRiwgbm9taW5hbFE9VFJVRSkrIGdndGl0bGUocGFzdGUwKHJ1bnNbaV0sIiBGb3J3YXJkIFJlYWRzIikpKQ0KICBwcmludChwbG90RXJyb3JzKGVyclIsIG5vbWluYWxRPVRSVUUpKyBnZ3RpdGxlKHBhc3RlMChydW5zW2ldLCIgUmV2ZXJzZSBSZWFkcyIpKSkNCiAgDQogICNFcnJvciBpbmZlcmVuY2UgYW5kIG1lcmdlciBvZiByZWFkcyAtIFVzaW5nIHBzZXVkbyBwb29saW5nIGZvciBpbmNyZWFzZWQgc2Vuc2l0aXZpdHkNCiAgZGFkYUZzIDwtIGRhZGEoZmlsdEZzLCBlcnI9ZXJyRiwgbXVsdGl0aHJlYWQ9VFJVRSwgcG9vbD0icHNldWRvIikNCiAgZGFkYVJzIDwtIGRhZGEoZmlsdFJzLCBlcnI9ZXJyUiwgbXVsdGl0aHJlYWQ9VFJVRSwgcG9vbD0icHNldWRvIikNCiAgbWVyZ2VycyA8LSBtZXJnZVBhaXJzKGRhZGFGcywgZmlsdEZzLCBkYWRhUnMsIGZpbHRScywgdmVyYm9zZT1UUlVFLCBtaW5PdmVybGFwID0gMjAsIHRyaW1PdmVyaGFuZyA9IFRSVUUgKQ0KICANCiAgIyBDb25zdHJ1Y3Qgc2VxdWVuY2UgdGFibGUNCiAgc2VxdGFiIDwtIG1ha2VTZXF1ZW5jZVRhYmxlKG1lcmdlcnMpDQogc2F2ZVJEUyhzZXF0YWIsIHBhc3RlMCgib3V0cHV0L3Jkcy8iLCBydW5zW2ldLCAiX3NlcXRhYi5yZHMiKSkNCn0NCmBgYA0KDQojIyBNZXJnZSBSdW5zLCBSZW1vdmUgQ2hpbWVyYXMNCg0KTm93IHRoYXQgdGhlIHNlcXVlbmNlIHRhYmxlcyBhcmUgY3JlYXRlZCBmb3IgZWFjaCBydW4sIHRoZXkgbmVlZCB0byBiZSBtZXJnZWQgaW50byBhIGxhcmdlciB0YWJsZSByZXByZXNlbnRpbmcgdGhlIGVudGlyZSBzdHVkeS4gDQpMb29raW5nIGF0IHRoZSBsZW5ndGggb2YgdGhlIHNlcXVlbmNlcywgd2Ugc2VlIHNvbWUgb2ZmIHRhcmdldCBhbXBsaWZpY2F0aW9uLiBUaGVyZSBhcmUgMyBsYXJnZSBwZWFrcywgdGhlIGZpcnN0IG9uZSBhdCAyODAsIHNlY29uZCBhdCA0MDJicCwgYW5kIHRoaXJkIGF0IDQyNy4gVGhlIDQyN2JwIHBlYWsgY29udGFpbnMgdGhlIG1ham9yaXR5IG9mIHRoZSBzZXF1ZW5jZXMgYW5kIGlzIHRoZSBleHBlY3RlZCBzaXplLCB3aGlsZSB0aGUgNDAyYnAgcGVhayBjb250YWlucyB3b2xiYWNoaWEgd2hpY2ggaGF2ZSBhIDI0YnAgZGVsZXRpb24uIFdlIHdpbGwgY3V0IHRoZSBzZXF1ZW5jZXMgdG8gYmV0d2VlbiA0MDAgYW5kIDQ1MCwgd2hpY2ggc2hvdWxkIHRha2UgaW50byBhY2NvdW50IGFueSBsZW5ndGggdmFyaWF0aW9uLCBhbmQgcmVtb3ZlIHRoZSAyODBicCBwZWFrICh3aGljaCBpcyB0aGUgbGVuZ3RoIG9mIHRoZSBmb3J3YXJkIHJlYWQgYW5kIG1vc3QgbGlrZWx5IGFydGVmYWN0dWFsKS4gRm9sbG93aW5nIHRoaXMsIGNoaW1lcmljIHNlcXVlbmNlcyBhcmUgaWRlbnRpZmllZCBhbmQgcmVtb3ZlZCB1c2luZyByZW1vdmVCaW1lcmFEZW5vdm8NCg0KYGBge3IgbWVyZ2UgcnVucyBhbmQgcmVtb3ZlIGNoaW1lcmFzfQ0Kc2V0LnNlZWQoNjA2KQ0Kc2VxdGFicyA8LSBsaXN0LmZpbGVzKCJvdXRwdXQvcmRzLyIsIHBhdHRlcm49InNlcXRhYi5yZHMiLCBmdWxsLm5hbWVzID0gVFJVRSkNCnN0LmFsbCA8LSBtZXJnZVNlcXVlbmNlVGFibGVzKHRhYmxlcz1zZXF0YWJzKQ0KDQojUmVtb3ZlIGNoaW1lcmFzDQpzZXF0YWIubm9jaGltIDwtIHJlbW92ZUJpbWVyYURlbm92byhzdC5hbGwsIG1ldGhvZD0iY29uc2Vuc3VzIiwgbXVsdGl0aHJlYWQ9VFJVRSwgdmVyYm9zZT1UUlVFKQ0KDQojQ2hlY2sgb3V0cHV0IG9mIGNoaW1lcmEgcmVtb3ZhbA0KcHJpbnQocGFzdGUoc3VtKHNlcXRhYi5ub2NoaW0pL3N1bShzdC5hbGwpLCJvZiB0aGUgYWJ1bmRhbmNlIHJlbWFpbmluZyBhZnRlciBjaGltZXJhIHJlbW92YWwiKSkNCg0KI0NoZWNrIGNvbXBsZXhpdHkNCmhpc3Qoc2VxQ29tcGxleGl0eShzZXF0YWIubm9jaGltKSwgMTAwKQ0KDQojTG9vayBhdCBzZXFsZW5ndGhzDQpwbG90KHRhYmxlKG5jaGFyKGdldFNlcXVlbmNlcyhzZXF0YWIubm9jaGltKSkpKQ0KDQojY3V0IHRvIGV4cGVjdGVkIHNpemUNCiNzZXF0YWIubm9jaGltIDwtIHNlcXRhYi5ub2NoaW1bLG5jaGFyKGNvbG5hbWVzKHNlcXRhYi5ub2NoaW0pKSAlaW4lIDQwMDo0MzVdDQojc2VxdGFiX2N1dCA8LSBzZXF0YWIubm9jaGltWyxuY2hhcihjb2xuYW1lcyhzZXF0YWIubm9jaGltKSkgJWluJSA0MDA6NDM1XQ0KIyBDb3VudCBocHcgbWFueSB0aGlzIHJlbW92ZWQNCg0KcHJpbnQocGFzdGUoc3VtKHNlcXRhYi5ub2NoaW0pL3N1bShzdC5hbGwpLCJvZiB0aGUgYWJ1bmRhbmNlIHJlbWFpbmluZyBhZnRlciBjdXR0aW5nIikpDQoNCiNGaXggbmFtZXMgLXJlbW92aW5nIHJlYWQgbmFtZSwgc2FtcGxlIG51bWJlciBldGNjDQpyb3duYW1lcyhzZXF0YWIubm9jaGltKSA8LSByb3duYW1lcyhzZXF0YWIubm9jaGltKSAlPiUgDQogIHN0cl9zcGxpdF9maXhlZCgiXyIsbj1JbmYpICU+JQ0KICAgIGFzX3RpYmJsZSgpICU+JQ0KICB1bml0ZShjb2w9U2FtcGxlSUQsIGMoIlYxIiwiVjIiKSxzZXA9Il8iKSAlPiUNCiAgcHVsbChTYW1wbGVJRCkNCg0KZGlyLmNyZWF0ZSgib3V0cHV0L3Jkcy8iKQ0Kc2F2ZVJEUyhzZXF0YWIubm9jaGltLCAib3V0cHV0L3Jkcy9zZXF0YWJfbm9jdXQucmRzIikNCg0KIyBzdW1tYXJpc2UgY2xlYW51cA0KY2xlYW51cCA8LSBzdC5hbGwgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKCBldmVyeXRoaW5nKCksDQogICAgbmFtZXNfdG8gPSAiT1RVIiwNCiAgICB2YWx1ZXNfdG8gPSAiQWJ1bmRhbmNlIikgJT4lDQogIGdyb3VwX2J5KE9UVSkgJT4lDQogIHN1bW1hcmlzZShBYnVuZGFuY2UgPSBzdW0oQWJ1bmRhbmNlKSkgJT4lDQogIG11dGF0ZShsZW5ndGggID0gbmNoYXIoT1RVKSkgJT4lDQogIG11dGF0ZSh0eXBlID0gY2FzZV93aGVuKA0KICAgICFPVFUgJWluJSBnZXRTZXF1ZW5jZXMoc2VxdGFiLm5vY2hpbSkgfiAiQ2hpbWVyYSIsDQogICAgVFJVRSB+ICJSZWFsIg0KICApKSANCg0KIyBPdXRwdXQgbGVuZ3RoIGRpc3RyaWJ1dGlvbiBwbG90cw0KZ2cuYWJ1bmRhbmNlIDwtIGdncGxvdChjbGVhbnVwLCBhZXMoeD1sZW5ndGgsIHk9QWJ1bmRhbmNlLCBmaWxsPXR5cGUpKSsNCiAgICAgICAgICAgICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArIA0KICAgICAgICAgICAgICBnZ3RpdGxlKCJBYnVuZGFuY2Ugb2Ygc2VxdWVuY2VzIikgKw0KICAgICAgICAgICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA0MDAsIGNvbG91cj0icmVkIikgKw0KICAgICAgICAgICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA0MzUsIGNvbG91cj0icmVkIikgDQoNCmdnLnVuaXF1ZSA8LSBnZ3Bsb3QoY2xlYW51cCwgYWVzKHg9bGVuZ3RoLCBmaWxsPXR5cGUpKSsNCiAgICAgICAgICAgIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSkgKyANCiAgICAgICAgICAgIGdndGl0bGUoIk51bWJlciBvZiB1bmlxdWUgc2VxdWVuY2VzIikrDQogICAgICAgICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDQwMCwgY29sb3VyPSJyZWQiKSArDQogICAgICAgICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDQzNSwgY29sb3VyPSJyZWQiKSANCg0KcGxvdChnZy5hYnVuZGFuY2UgLyBnZy51bmlxdWUpDQoNCnBkZihwYXN0ZTAoIm91dHB1dC9sb2dzL3NlcXRhYl9sZW5ndGhfZGlzdC5wZGYiKSwgd2lkdGggPSAxMSwgaGVpZ2h0ID0gOCAsIHBhcGVyPSJhNHIiKQ0KICBwbG90KGdnLmFidW5kYW5jZSAvIGdnLnVuaXF1ZSkNCnRyeShkZXYub2ZmKCksIHNpbGVudD1UUlVFKQ0KYGBgDQoNCiMgQXNzaWduIHRheG9ub215IHdpdGggSURUQVhBDQoNCldlIHdpbGwgdXNlIHRoZSBJRFRBWEEgYWxnb3JpdGhtIG9mIE11cmFsaSBldCBhbCAyMDE4IC0gaHR0cHM6Ly9kb2kub3JnLzEwLjExODYvczQwMTY4LTAxOC0wNTIxLTUgRm9sbGxvd2luZyBwaHlsdW0gdG8gZ2VudXMgYXNzaWdubWVudCB3aXRoIElEVEFYQSwgd2Ugd2lsbCBhbHNvIHVzZSBleGFjdCBtYXRjaGluZyB3aXRoIGEgcmVmZXJlbmNlIGRhdGFiYXNlIHRvIGFzc2lnbiB0byBzcGVjaWVzIGxldmVsLg0KDQpgYGB7ciBJRFRBWEF9DQojc2VxdGFiX2ZpbmFsIDwtIHJlYWRSRFMoIm91dHB1dC9yZHMvc2VxdGFiX2ZpbmFsLnJkcyIpDQpzZXF0YWJfZmluYWwgPC0gcmVhZFJEUygib3V0cHV0L3Jkcy9zZXF0YWJfbm9jdXQucmRzIikNCg0KIyBMb2FkIHRyYWluaW5nc2V0DQpsb2FkKCJyZWZlcmVuY2UvU0lMVkFfU1NVX3IxMzhfMjAxOS5SRGF0YSIpDQoNCiMgQ3JlYXRlIGEgRE5BU3RyaW5nU2V0IGZyb20gdGhlIEFTVnMNCmRuYSA8LSBETkFTdHJpbmdTZXQoZ2V0U2VxdWVuY2VzKHNlcXRhYl9maW5hbCkpIA0KDQojIEFzc2lnbiB0YXhvbm9teQ0KbGlicmFyeShERUNJUEhFUikNCmlkcyA8LSBJZFRheGEoZG5hLCB0cmFpbmluZ1NldCwgcHJvY2Vzc29ycz04LCB0aHJlc2hvbGQgPSA2MCwgdmVyYm9zZT1UUlVFKSAgDQoNCnNhdmVSRFMoaWRzLCAiaWRzX25vY3V0LnJkcyIpDQojIE91dHB1dCBwbG90IG9mIGlkcw0KcGRmKHBhc3RlMCgiZmlncy9pZHRheGEucGRmIiksIHdpZHRoID0gMTEsIGhlaWdodCA9IDggLCBwYXBlcj0iYTRyIikNCiAgcGxvdChpZHMpDQp0cnkoZGV2Lm9mZigpLCBzaWxlbnQ9VFJVRSkNCg0KI0NvbnZlcnQgdGhlIG91dHB1dCBvYmplY3Qgb2YgY2xhc3MgIlRheGEiIHRvIGEgbWF0cml4IGFuYWxvZ291cyB0byB0aGUgb3V0cHV0IGZyb20gYXNzaWduVGF4b25vbXkNCnRheCA8LSB0KHNhcHBseShpZHMsIGZ1bmN0aW9uKHgpIHsNCiAgICB0YXhhIDwtIHBhc3RlMCh4JHRheG9uLCJfIiwgeCRjb25maWRlbmNlKQ0KICAgIHRheGFbc3RhcnRzV2l0aCh0YXhhLCAidW5jbGFzc2lmaWVkXyIpXSA8LSBOQQ0KICAgIHRheGENCiAgfSkpICU+JQ0KICBwdXJycjo6bWFwKHVubGlzdCkgJT4lDQogIHN0cmluZ2k6OnN0cmlfbGlzdDJtYXRyaXgoYnlyb3c9VFJVRSwgZmlsbD1OQSkgJT4lDQogIG1hZ3JpdHRyOjpzZXRfY29sbmFtZXMoYygicm9vdCIsICJkb21haW4iLCAicGh5bHVtIiwgImNsYXNzIiwgIm9yZGVyIiwgImZhbWlseSIsICJnZW51cyIpKSAlPiUNCiAgbWFncml0dHI6OnNldF9yb3duYW1lcyhnZXRTZXF1ZW5jZXMoc2VxdGFiX2ZpbmFsKSkgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAgJT4lDQogIG11dGF0ZV9hbGwoc3RyX3JlcGxhY2UscGF0dGVybj0iKD86Lig/IV8pKSskIiwgcmVwbGFjZW1lbnQ9IiIpICU+JQ0KICAjc2VxYXRldXJzOjpuYV90b191bmNsYXNzaWZpZWQoKSAlPiUgI1Byb3BhZ2F0ZSBoaWdoIG9yZGVyIHJhbmtzIHRvIHVuYXNzaWduZWQgQVNWJw0KICBtYWdyaXR0cjo6c2V0X3Jvd25hbWVzKGdldFNlcXVlbmNlcyhzZXF0YWJfZmluYWwpKSAlPiUNCiAgYXMubWF0cml4KCkNCg0KIyBXcml0ZSB0YXhvbm9teSB0YWJsZSB0byBkaXNrDQpzYXZlUkRTKHRheCwgIm91dHB1dC9yZHMvdGF4X0lkVGF4YV9ub2N1dC5yZHMiKSANCg0KI0FkZCBzcGVjaWVzIHVzaW5nIGV4YWN0IG1hdGNoaW5nDQpleGFjdCA8LSBhc3NpZ25TcGVjaWVzKHNlcXRhYl9maW5hbCwgInJlZmVyZW5jZS9zaWx2YV9zcGVjaWVzX2Fzc2lnbm1lbnRfdjEzOC5mYS5neiIsIGFsbG93TXVsdGlwbGUgPVRSVUUsIHRyeVJDID0gVFJVRSwgdmVyYm9zZSA9IEZBTFNFKQ0KDQpleGFjdCA8LSBleGFjdCAlPiUNCiAgYXMuZGF0YS5mcmFtZShzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKSAlPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCJvdHUiKSAlPiUNCiAgbXV0YXRlKHNwZWNpZXMgPSAgY2FzZV93aGVuKCFpcy5uYShTcGVjaWVzKSB+ICBwYXN0ZTAoR2VudXMsIl8iLFNwZWNpZXMpKSkNCg0KdGF4X2V4YWN0IDwtIHRheCAlPiUNCiAgYXMuZGF0YS5mcmFtZShzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKSAlPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCJvdHUiKSAlPiUNCiAgbGVmdF9qb2luKGV4YWN0ICU+JSBzZWxlY3Qob3R1LCBzcGVjaWVzKSwgYnk9Im90dSIpICU+JQ0KICBtYWdyaXR0cjo6c2V0X3Jvd25hbWVzKC4kb3R1KSAlPiUNCiAgc2VsZWN0KC1vdHUpICU+JQ0KICBuYV90b191bmNsYXNzaWZpZWQoKSAlPiUgI1Byb3BhZ2F0ZSBoaWdoIG9yZGVyIHJhbmtzIHRvIHVuYXNzaWduZWQgQVNWJw0KICBhcy5tYXRyaXgoKQ0KDQpzYXZlUkRTKHRheF9leGFjdCwgIm91dHB1dC9yZHMvdGF4X0lkVGF4YUV4YWN0X25vY3V0LnJkcyIpIA0KYGBgDQoNCg0KIyBDb21wYXJlIGFzc2lnbm1lbnQgdG8gYmxhc3QgdG9wIGhpdA0KYGBge3IgQ29tcGFyZSB0byBibGFzdCB0b3AgaGl0fQ0Kc2VxdGFiX2ZpbmFsIDwtIHJlYWRSRFMoIm91dHB1dC9yZHMvc2VxdGFiX25vY3V0LnJkcyIpDQp0YXggPC0gcmVhZFJEUygib3V0cHV0L3Jkcy90YXhfSWRUYXhhRXhhY3Rfbm9jdXQucmRzIikNCg0Kc2VxcyA8LSBpbnNlY3Q6OmNoYXIyZG5hKGNvbG5hbWVzKHNlcXRhYl9maW5hbCkpDQpuYW1lcyhzZXFzKSA8LSBjb2xuYW1lcyhzZXF0YWJfZmluYWwpDQoNCm91dCA8LSBibGFzdF90b3BfaGl0KHF1ZXJ5PXNlcXMsIGRiPSJyZWZlcmVuY2Uvc2lsdmFfc3BlY2llc19hc3NpZ25tZW50X3YxMzguZmEuZ3oiLCB0aHJlc2hvbGQ9NjAgKQ0Kc2F2ZVJEUyhvdXQsICJvdXRwdXQvcmRzL2JsYXN0X3RvcF9oaXQucmRzIikNCg0KIyBCbGFzdCBjb3VudHMgb25seSB0aGUgYWxpZ25lZCBzdWJzZWN0aW9ucyB3aGVuIGNhbGN1bGF0aW5nICUgaWRlbnRpdHkNCiMgVGhlcmVmb3JlIHdlIG5lZWQgdG8gc2V0IGEgbWluaW11bSBhbmQgbWF4aW11bSBhbGlnbm1lbnQgbGVuZ3RoIGFsbG93ZWQNCm91dCA8LSByZWFkUkRTKCJvdXRwdXQvcmRzL2JsYXN0X3RvcF9oaXQucmRzIikNCmhpc3Qob3V0JGxlbmd0aCkNCmFsbG93ZWRfbGVuZ3RocyA8LSAyMjA6NTAwDQoNCmpvaW50IDwtIG91dCAlPiUgDQogIGRwbHlyOjpzZWxlY3QoT1RVID0gcXNlcWlkLCBhY2MsIGJsYXN0c3BwID0gU3BlY2llcywgcGlkZW50LCBsZW5ndGgsIGV2YWx1ZSkgJT4lDQogIGxlZnRfam9pbih0YXggJT4lIA0KICAgICAgICAgICAgICBzZXFhdGV1cnM6OnVuY2xhc3NpZmllZF90b19uYShyb3duYW1lcz1GQUxTRSkgJT4lDQogICAgICAgICAgICAgIG11dGF0ZShsb3dlc3QgPSBzZXFhdGV1cnM6Omxvd2VzdF9jbGFzc2lmaWVkKC4pKSwgYnk9Ik9UVSIpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKGxlbmd0aCAlaW4lIGFsbG93ZWRfbGVuZ3RocykgDQoNCiNXcml0ZSBvdXQgY29tcGFyaXNvbiBiZXR3ZWVuIEJMQVNUIGFuZCBIZWlhcmNoaWFsIGFzc2lnbm1lbnQNCndyaXRlX2Nzdihqb2ludCwgIm91dHB1dC9jc3YvdGF4X2Fzc2lnbm1lbnRfY29tcGFyaXNvbi5jc3YiKQ0KDQpnZy50b3BoaXQgPC0gam9pbnQgJT4lDQogIGRwbHlyOjpzZWxlY3QocGlkZW50LCByYW5rID0gbG93ZXN0KSAlPiUNCiAgbXV0YXRlKHJhbmsgPSBmYWN0b3IocmFuaywgbGV2ZWxzID0gYygicm9vdCIsICJkb21haW4iLCAicGh5bHVtIiwgImNsYXNzIiwgIm9yZGVyIiwgImZhbWlseSIsICJnZW51cyIsICJzcGVjaWVzIikpKSAlPiUNCiAgZ2dwbG90KGFlcyh4PXBpZGVudCwgZmlsbD1yYW5rKSkrIA0KICBnZW9tX2hpc3RvZ3JhbShjb2xvdXI9ImJsYWNrIiwgYmlud2lkdGggPSAxLCBwb3NpdGlvbiA9ICJzdGFjayIpICsgDQogIGxhYnModGl0bGUgPSAiVG9wIGhpdCBpZGVudGl0eSBkaXN0cmlidXRpb24iLA0KICAgICAgIHggPSAiQkxBU1QgdG9wIGhpdCAlIGlkZW50aXR5IiwNCiAgICAgICB5ID0gIk9UVXMiKSArIA0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSg2MCwxMDAsMikpICsNCiAgc2NhbGVfZmlsbF9icmV3ZXIobmFtZSA9ICJUYXhvbm9taWMgXG5Bc3NpZ25tZW50IiwgcGFsZXR0ZSA9ICJTcGVjdHJhbCIpDQoNCmdnLnRvcGhpdA0KDQpwZGYocGFzdGUwKCJvdXRwdXQvZmlncy90b3BfaGl0X3RheF9hc3NpZ25tZW50LnBkZiIpLCB3aWR0aCA9IDExLCBoZWlnaHQgPSA4ICwgcGFwZXI9ImE0ciIpDQogIGdnLnRvcGhpdA0KdHJ5KGRldi5vZmYoKSwgc2lsZW50PVRSVUUpDQoNCmBgYA0KDQojIE1ha2UgUGh5bG9zZXEgb2JqZWN0DQoNCkZvbGxvd2luZyB0YXhvbm9taWMgYXNzaWdubWVudCwgdGhlIHNlcXVlbmNlIHRhYmxlIGFuZCB0YXhvbm9taWMgdGFibGUgYXJlIG1lcmdlZCBpbnRvIGEgc2luZ2xlIHBoeWxvc2VxIG9iamVjdCBhbG9uZ3NpZGUgdGhlIHNhbXBsZSBpbmZvIGNzdi4NCg0KYGBge3IgY3JlYXRlIFBTLCBldmFsID0gRkFMU0V9DQojIExvYWQgZGF0YQ0Kc2VxdGFiIDwtIHQocmVhZFJEUygib3V0cHV0L3Jkcy9zZXF0YWJfbm9jdXQucmRzIikpDQojc2VxdGFiIDwtIHJlYWRSRFMoIm91dHB1dC9yZHMvc2VxdGFiX2N1cmF0ZWQucmRzIikNCnRheCA8LSByZWFkUkRTKCJvdXRwdXQvcmRzL3RheF9JZFRheGFFeGFjdF9ub2N1dC5yZHMiKQ0KDQpzZXFzIDwtIEROQVN0cmluZ1NldChyb3duYW1lcyhzZXF0YWIpKQ0KbmFtZXMoc2VxcykgPC0gc2Vxcw0Kc2FtZGYgPC0gcmVhZF9jc3YoInNhbXBsZV9kYXRhL1NhbXBsZV9pbmZvMy5jc3YiKSAlPiUNCiAgYXMuZGF0YS5mcmFtZShzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKSAlPiUNCiAgbWFncml0dHI6OnNldF9yb3duYW1lcyguJFNhbXBsZUlEKSANCg0KIyBNYWtlIHBoeWxvc2VxIG9iamVjdA0KcHMgPC0gcGh5bG9zZXEodGF4X3RhYmxlKHRheCksDQogICAgICAgICAgICAgICBzYW1wbGVfZGF0YShzYW1kZiksDQogICAgICAgICAgICAgICBvdHVfdGFibGUodChzZXF0YWIpLCB0YXhhX2FyZV9yb3dzID0gRkFMU0UpLA0KICAgICAgICAgICAgICAgcmVmc2VxKHNlcXMpKQ0KDQppZihucm93KHNlcXRhYikgPiBucm93KHNhbXBsZV9kYXRhKHBzKSkpe3dhcm5pbmcoIldhcm5pbmc6IEFsbCBzYW1wbGVzIG5vdCBpbmNsdWRlZCBpbiBwaHlsb3NlcSBvYmplY3QsIGNoZWNrIHNhbXBsZSBuYW1lcyBtYXRjaCB0aGUgc2FtcGxlIG1ldGFkYXRhIil9DQoNCiNSZW5hbWUgdGF4YQ0KI3RheGFfbmFtZXMocHMpIDwtIHBhc3RlMCgiU1YiLCBzZXEobnRheGEocHMpKSwiLSIsdGF4X3RhYmxlKHBzKVssOF0pDQoNCiMjc2F2ZSBwaHlsb3NlcSBvYmplY3QNCnNhdmVSRFMocHMsICJvdXRwdXQvcmRzL3BzLnJkcyIpDQoNCiNPdXRwdXQgdGFibGVzIG9mIHJlc3VsdHMNCmRpci5jcmVhdGUoIm91dHB1dC9jc3YiKQ0KZGlyLmNyZWF0ZSgib3V0cHV0L290dV90YWJsZXMvdW5maWx0ZXJlZC8iLCByZWN1cnNpdmUgPSBUUlVFKQ0KDQojI0V4cG9ydCByYXcgY3N2DQpzcGVlZHlzZXE6OnBzbWVsdChwcykgJT4lDQogIGRwbHlyOjpmaWx0ZXIoQWJ1bmRhbmNlID4gMCkgJT4lDQogIHdyaXRlLmNzdihmaWxlID0gIm91dHB1dC9vdHVfdGFibGVzL3Jhd2RhdGEuY3N2IikNCg0KIyBFeHBvcnQgc3BlY2llcyBsZXZlbCBzdW1tYXJ5DQpzZXFhdGV1cnM6OnN1bW1hcmlzZV90YXhhKHBzLCAic3BlY2llcyIsICJTYW1wbGVJRCIpICU+JQ0KICBzcHJlYWQoa2V5PSJTYW1wbGVJRCIsIHZhbHVlPSJ0b3RhbFJBIikgJT4lDQogIHdyaXRlLmNzdihmaWxlID0gIm91dHB1dC9vdHVfdGFibGVzL3VuZmlsdGVyZWQvc3BwX3N1bS5jc3YiKQ0KDQojIEV4cG9ydCBnZW51cyBsZXZlbCBzdW1tYXJ5DQpzZXFhdGV1cnM6OnN1bW1hcmlzZV90YXhhKHBzLCAiZ2VudXMiLCAiU2FtcGxlSUQiKSAlPiUNCiAgc3ByZWFkKGtleT0iU2FtcGxlSUQiLCB2YWx1ZT0idG90YWxSQSIpICU+JQ0KICB3cml0ZS5jc3YoZmlsZSA9ICJvdXRwdXQvb3R1X3RhYmxlcy91bmZpbHRlcmVkL2dlbl9zdW0uY3N2IikNCg0KI0NoZWNrIGNhcnNvbmVsbGEgcHJlc2VuY2UNCmNhcnMgPC0gc3BlZWR5c2VxOjpwc21lbHQocHMpICU+JQ0KICBmaWx0ZXIoQWJ1bmRhbmNlID4gMCkgJT4lDQogIGdyb3VwX2J5KHBzeWxsaWRfc3BwKSAlPiUNCiAgc3VtbWFyaXNlKG4gPSBjb3VudChnZW51cz09IkNhbmRpZGF0dXMgQ2Fyc29uZWxsYSIsIG5hLnJtID0gVFJVRSkpDQoNCnRlc3QgPC0gc3BlZWR5c2VxOjpwc21lbHQocHMpICU+JQ0KICBmaWx0ZXIoQWJ1bmRhbmNlID4gMCkgJT4lDQogIGZpbHRlcihnZW51cz09IkNhbmRpZGF0dXMgQ2Fyc29uZWxsYSIpDQpgYGANCg0KDQojIyBUYXhvbiBGaWx0ZXJpbmcNCg0KUmVtb3ZlIGFsbCBub24tYmFjdGVyaWFsIHRheGEsIG1pdG9jaG9uZHJpYSwgY2hsb3JvcGxhc3QgYW5kIGN5YW5vYmFjdGVyaWEuDQpXZSBhbHNvIHJlbW92ZSBhbGwgdGF4YSBjb250YWluZWQgd2l0aGluIHRoZSBibGFuayBzYW1wbGUgZnJvbSBvdGhlciBzYW1wbGVzPyAtIENoZWNrIHRoZXNlDQoNCmBgYHtyIHRheG9uLWNsZWFuaW5nfQ0KZ2V0X3RheGFfdW5pcXVlKHBzLCAiZG9tYWluIikNCm50YXhhKHBzKSAjIENoZWNrIHRoZSBudW1iZXIgb2YgdGF4YSBwcmlvciB0byByZW1vdmFsDQpwczAgPC0gcHMgJT4lDQogIHN1YnNldF90YXhhKA0KICAgIGRvbWFpbiA9PSAiQmFjdGVyaWEiICYgDQogICAgZmFtaWx5ICAhPSAiTWl0b2Nob25kcmlhIiAmDQogICAgb3JkZXIgICAhPSAiQ2hsb3JvcGxhc3QiICYNCiAgICBwaHlsdW0gIT0gIkN5YW5vYmFjdGVyaWEiDQogICkNCiNDaGVjayB0YXhhIHdlcmUgcmVtb3ZlZA0KbnRheGEocHMwKQ0KZ2V0X3RheGFfdW5pcXVlKHBzMCwgInBoeWx1bSIpDQpnZXRfdGF4YV91bmlxdWUocHMwLCAiY2xhc3MiKQ0KZ2V0X3RheGFfdW5pcXVlKHBzMCwgImZhbWlseSIpDQpgYGANCg0KIyMgUmVtb3ZlIG91dGxpZXIgU2FtcGxlcw0KDQpEZXRlY3RpbmcgYW5kIHBvdGVudGlhbGx5IHJlbW92aW5nIHNhbXBsZXMgb3V0bGllcnMgKHRob3NlIHNhbXBsZXMgd2l0aCB1bmRlcmx5aW5nIGRhdGEgdGhhdCBkbyBub3QgY29uZm9ybSB0byBleHBlcmltZW50YWwgb3IgYmlvbG9naWNhbCBleHBlY3RhdGlvbnMpIGNhbiBiZSB1c2VmdWwgZm9yIG1pbmltaXppbmcgdGVjaG5pY2FsIHZhcmlhbmNlLiBUaGlzIGNhbiBiZSBjYXVzZWQgYnkgYSBudW1iZXIgb2YgcmVhc29ucywgaW5jbHVkaW5nIGxvdy1yZWFkcyBhc3NpZ25lZCB0byB0aGF0IHNhbXBsZS4gSW4gdGhpcyBjYXNlIHdlIHJlbW92ZSBhbGwgc2FtcGxlcyBiZWxvdyAxMDAwIHJlYWRzLCBhcyB0aGVzZSBpbmNsdWRlIGFsbCBzYW1wbGVzIGNvbnRyaWJ1dGluZyB0byBsb3dlciB0aGFuIHVzdWFsIEFTViBjb3VudHMuDQoNClJhcmVmYWN0aW9uIGN1cnZlcyBhcmUgdXNlZnVsCXRvCWFzc2VzcwlzZW5zaXRpdml0eQlvZglzYW1wbGUJc2l6ZQl0bwlvYnNlcnZlZAlhbHBoYS1kaXZlcnNpdHkgZXN0aW1hdGVzLg0KYGBge3IgcmVtb3ZlIG91dGxpZXJzfQ0KIyMgUmVtb3ZlIG1vY2tzDQpybV9tb2NrcyA8LSBjKCJtb2NrQV9TNTEiLCAiTW9ja0V2ZW5fUzE5MyIsICJNb2NrX1MxOTIiLCAiUENSY3RybF9TMTkxIiwgIk1vY2tTdGFnZ2VyZWRfUzE5NCIsICJQQ1JjdHJsX1MxOTEiLCAiOTFfUzE2NyIpDQoNCiNjaGVjayBtb2Nrcw0KcHMxIDwtIHBzMCAlPiUgDQogIHN1YnNldF9zYW1wbGVzKCFzYW1wbGVfbmFtZXMocHMwKSAlaW4lIHJtX21vY2tzKSAlPiUgI1JlbW92ZSBtb2Nrcw0KICBmaWx0ZXJfdGF4YShmdW5jdGlvbih4KSBtZWFuKHgpID4gMCwgVFJVRSkgI0Ryb3AgbWlzc2luZyB0YXhhIGZyb20gdGFibGUgDQoNCm1lc3NhZ2UoKG5zYW1wbGVzKHBzMCkgLSBuc2FtcGxlcyhwczEpKSwgIiBvdXRsaWVyIHNhbXBsZXMgZHJvcHBlZCIpDQoNCiNQbG90IHJhcmVmYWN0aW9uIGN1cnZlDQpvdXQgPC0gcmFyZWN1cnZlKG90dV90YWJsZShwczEpLCBzdGVwPTEwMCkNCg0KcmFyZSA8LSBsYXBwbHkob3V0LCBmdW5jdGlvbih4KXsNCiAgYiA8LSBhcy5kYXRhLmZyYW1lKHgpDQogIGIgPC0gZGF0YS5mcmFtZShPVFUgPSBiWywxXSwgY291bnQgPSByb3duYW1lcyhiKSkNCiAgYiRjb3VudCA8LSBhcy5udW1lcmljKGdzdWIoIk4iLCAiIiwgIGIkY291bnQpKQ0KICByZXR1cm4oYikNCn0pDQpuYW1lcyhyYXJlKSA8LSBzYW1wbGVfbmFtZXMocHMxKQ0KDQpyYXJlIDwtIG1hcF9kZnIocmFyZSwgZnVuY3Rpb24oeCl7DQogIHogPC0gZGF0YS5mcmFtZSh4KQ0KICByZXR1cm4oeikNCn0sIC5pZCA9ICJzYW1wbGUiKQ0KDQojIHJlYWQgdGhyZXNob2xkIGZvciBzYW1wbGUgcmVtb3ZhbA0KdGhyZXNob2xkID0gMTAwMA0KDQpnZy5yYXJlIDwtIGdncGxvdChkYXRhID0gcmFyZSkrDQogIGdlb21fbGluZShhZXMoeCA9IGNvdW50LCB5ID0gT1RVLCBncm91cD1zYW1wbGUpLCBhbHBoYT0wLjUpKw0KICBnZW9tX3BvaW50KGRhdGEgPSByYXJlICU+JSANCiAgICAgICAgICAgICAgIGdyb3VwX2J5KHNhbXBsZSkgJT4lIA0KICAgICAgICAgICAgICAgdG9wX24oMSwgY291bnQpLA0KICAgICAgICAgICAgIGFlcyh4ID0gY291bnQsIHkgPSBPVFUsIGNvbG91cj0oY291bnQgPiB0aHJlc2hvbGQpKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gIHNjYWxlczo6bGFiZWxfbnVtYmVyX3NpKCkpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PXRocmVzaG9sZCwgbGluZXR5cGU9ImRhc2hlZCIpICsNCiAgbGFicyhjb2xvdXIgPSAiU2FtcGxlIGtlcHQ/IikgKw0KICB4bGFiKCJTZXF1ZW5jZSByZWFkcyIpICsNCiAgeWxhYigiT2JzZXJ2ZWQgQVNWJ3MiKQ0KDQojV3JpdGUgb3V0IGZpZ3VyZQ0KcGRmKGZpbGU9ImZpZ3MvcmFyZWZhY3Rpb24ucGRmIiwgd2lkdGggPSAxMSwgaGVpZ2h0ID0gOCAsIHBhcGVyPSJhNHIiKQ0KICBwbG90KGdnLnJhcmUpDQp0cnkoZGV2Lm9mZigpLCBzaWxlbnQ9VFJVRSkNCg0KI1JlbW92ZSBhbGwgc2FtcGxlcyB1bmRlciB0aGUgbWluaW11bSByZWFkIHRocmVzaG9sZCANCnBzMiA8LSBwcnVuZV9zYW1wbGVzKHNhbXBsZV9zdW1zKHBzMSk+PXRocmVzaG9sZCwgcHMxKSANCnBzMiA8LSBmaWx0ZXJfdGF4YShwczIsIGZ1bmN0aW9uKHgpIG1lYW4oeCkgPiAwLCBUUlVFKSAjRHJvcCBtaXNzaW5nIHRheGEgZnJvbSB0YWJsZQ0KbWVzc2FnZShuc2FtcGxlcyhwczEpIC0gbnNhbXBsZXMocHMyKSwgIiBTYW1wbGVzIGFuZCAiLCBudGF4YShwczEpIC0gbnRheGEocHMyKSwgIiB0YXhhIHVuZGVyIHJlYWQgdGhyZXNob2xkIERyb3BwZWQiKQ0KYGBgDQoNCg0KIyMgQ29tcGFyZSBkaXN0YW5jZXMgcHJlIGFuZCBwb3N0IGZpbHRlcmluZw0KDQpgYGB7ciBjb21wYXJlIGRpc3RzfQ0Kc2VxdGFiX2ZpbmFsIDwtIHJlYWRSRFMoIm91dHB1dC9yZHMvc2VxdGFiX25vY3V0LnJkcyIpDQojR2V0IHByZV9maWx0IGRpc3RhbmNlcw0KcHJlX2ZpbHQgPC0gYXMubWF0cml4KHpDb21wb3NpdGlvbnM6OmNtdWx0UmVwbChzZXF0YWJfZmluYWwsIG1ldGhvZD0iQkwiLCBvdXRwdXQ9InAtY291bnRzIikpDQpwcmVfZmlsdF9kaXN0IDwtIGFzLm1hdHJpeCh2ZWdkaXN0KENvRGFTZXE6OmNvZGFTZXEuY2xyKHByZV9maWx0KSwgbWV0aG9kPSJldWNsaWRlYW4iKSkNCg0KIyBHZXQgcG9zdF9maWx0IGRpc3RhbmNlcw0Kb3R1dGFiIDwtIG90dV90YWJsZShwczIpICU+JQ0KICBhcygibWF0cml4IikNCg0KI290dXRhYiA8LSB0KHNlcXRhYikNCnBvc3RfZmlsdCA8LSBhcy5tYXRyaXgoekNvbXBvc2l0aW9uczo6Y211bHRSZXBsKG90dXRhYiwgbWV0aG9kPSJCTCIsIG91dHB1dD0icC1jb3VudHMiKSkNCnBvc3RfZmlsdF9kaXN0IDwtIGFzLm1hdHJpeCh2ZWdkaXN0KENvRGFTZXE6OmNvZGFTZXEuY2xyKHBvc3RfZmlsdCksIG1ldGhvZD0iZXVjbGlkZWFuIikpDQoNCiNTdWJzZXQgdG8gb25seSBjb21tb24gc2FtcGxlcw0Kc3Vic2FtcGxlIDwtIGludGVyc2VjdChjb2xuYW1lcyhwcmVfZmlsdF9kaXN0KSwgY29sbmFtZXMocG9zdF9maWx0X2Rpc3QpKQ0KYXMuZGF0YS5mcmFtZSh2ZWdhbjo6bWFudGVsKHByZV9maWx0X2Rpc3Rbc3Vic2FtcGxlLCBzdWJzYW1wbGVdLCBwb3N0X2ZpbHRfZGlzdFtzdWJzYW1wbGUsIHN1YnNhbXBsZV0pW2MoInN0YXRpc3RpYyIsInNpZ25pZiIsInBlcm11dGF0aW9ucyIpXSkNCmBgYA0KDQojIyMgT3V0cHV0IGZhc3Rhcw0KDQpgYGB7ciBTYXZlIGZhc3Rhc30NCiNXcml0ZSBvdXQgZmFzdGEgYW5kIGFsaWduIHdpdGggc2lsdmEgb25saW5lDQpzZXFzIDwtIHJlZnNlcShwczIpDQp3cml0ZVhTdHJpbmdTZXQoc2VxcywgIm91dHB1dC9jdXJhdGVkX2Fzdi5mYXN0YSIsIHdpZHRoPTEwMDApDQpgYGANCg0KIyMgQWxpZ24gd2l0aCBTSU5BDQoNCmBgYHtiYXNoIGFsaWdufQ0KI0NyZWF0ZSBhIHZpcnR1YWwgZW52aXJvbm1lbnQNCm1vZHVsZSBsb2FkIFB5dGhvbi8zLjguMi1HQ0Njb3JlLTkuMy4wIA0KdmlydHVhbGVudiB+L1NJTkENCnNvdXJjZSB+L1NJTkEvYmluL2FjdGl2YXRlDQoNCiNJbnN0YWxsIHNpbmENCndnZXQgaHR0cHM6Ly9naXRodWIuY29tL2VwcnVlc3NlL1NJTkEvcmVsZWFzZXMvZG93bmxvYWQvdjEuNy4wL3NpbmEtMS43LjAtbGludXgudGFyLmd6DQp0YXIgeGYgc2luYS0xLjcuMC1saW51eC50YXIuZ3oNCmNkIHNpbmEtMS43LjAtbGludXgNCg0KI0dldCByZWZlcmVuY2VEQg0Kd2dldCBodHRwczovL3d3dy5hcmItc2lsdmEuZGUvZmlsZWFkbWluL3NpbHZhX2RhdGFiYXNlcy9yZWxlYXNlXzEzOC9BUkJfZmlsZXMvU0lMVkFfMTM4X1NTVVJlZl9OUjk5XzA1XzAxXzIwX29wdC5hcmIuZ3oNCg0KDQojUnVuIHNpbmENCn4vc2luYS0xLjcuMC1saW51eC9zaW5hIC0tc2VhcmNoIC0tYWRkLXJlbGF0aXZlcz01IC0tbWV0YS1mbXQ9aGVhZGVyIC0tZmllbGRzPWFjYyBcDQotaSAvZ3JvdXAvcGF0aG9nZW5zL0FsZXhwL01ldGFiYXJjb2RpbmcvUHN5bGxpZF9taWNyb2Jpb21lL291dHB1dC9jdXJhdGVkX2Fzdi5mYXN0YSBcDQotciAvZ3JvdXAvcGF0aG9nZW5zL0FsZXhwL01ldGFiYXJjb2RpbmcvUHN5bGxpZF9taWNyb2Jpb21lL3JlZmVyZW5jZS9TSUxWQV8xMzhfU1NVUmVmX05SOTlfMDVfMDFfMjBfb3B0LmFyYiBcDQotbyAvZ3JvdXAvcGF0aG9nZW5zL0FsZXhwL01ldGFiYXJjb2RpbmcvUHN5bGxpZF9taWNyb2Jpb21lL291dHB1dC9hbGlnbmVkX2Fzdi5mYXN0YQ0KYGBgDQoNCiMjIENyZWF0ZSBwaHlsb2dlbmV0aWMgdHJlZSB3aXRoIGZhc3R0cmVlZQ0KYGBge2Jhc2ggZmFzdHJlZX0NCm1vZHVsZSBsb2FkIEZhc3RUcmVlDQpGYXN0VHJlZSAtZ3RyIC1jYXQgMjAgLXF1b3RlIC1udCAvZ3JvdXAvcGF0aG9nZW5zL0FsZXhwL01ldGFiYXJjb2RpbmcvUHN5bGxpZF9taWNyb2Jpb21lL291dHB1dC9hbGlnbmVkX2Fzdi5mYXN0YSA+IC9ncm91cC9wYXRob2dlbnMvQWxleHAvTWV0YWJhcmNvZGluZy9Qc3lsbGlkX21pY3JvYmlvbWUvb3V0cHV0L3NpbmFfdHJlZSANCmBgYA0KDQojIyBEYXRlIHRyZWUgd2l0aCBQQVRIZDgNCg0KYGBge3IgUEFUSGQ4fQ0KIyBEYXRlIHVzaWduIGNvbmdydWlmeQ0KI3RyZWUgPC0gcmVhZC50cmVlKCJhcmItc2lsdmEuZGVfMjAyMC0wNy0yOF9pZDg2MDg0OS9hcmItc2lsdmEuZGVfMjAyMC0wNy0yOF9pZDg2MDg0OS50cmVlIikNCnRyZWUgPC0gcmVhZC50cmVlKCJvdXRwdXQvc2luYV90cmVlIikNCnRyZWUkdGlwLmxhYmVsIDwtIHRyZWUkdGlwLmxhYmVsICU+JQ0KICBzdHJfc3BsaXRfZml4ZWQoIlxcWyIsIG49SW5mKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICB1bml0ZShWMSwgVjIsIGNvbD0ibmFtZSIpICU+JQ0KICBtdXRhdGUobmFtZSA9IG5hbWUgJT4lDQogICAgICAgICAgIHN0cl9yZW1vdmUoIl9hbGlnbi4qJCIpICU+JQ0KICAgICAgICAgICBzdHJfcmVtb3ZlKCJeLipfYWNjPSIpICU+JQ0KICAgICAgICAgICBzdHJfcmVtb3ZlKCJcXF0kIikpICU+JQ0KICBwdWxsKG5hbWUpDQoNCnRyZWVfcHJ1bmVkIDwtIGRyb3AudGlwKHRyZWUsIHRyZWUkdGlwLmxhYmVsW2R1cGxpY2F0ZWQodHJlZSR0aXAubGFiZWwpXSkNCg0KI1JlZmVyZW5jZSB0cmVlDQpyZWZfdHJlZSA8LSByZWFkLnRyZWUoImJpbi9EYXRlZCB0cmVlcy9CYWN0ZXJpYV8xNlNfU0lMVkFfOTdzaW1fRmFzdFRyZWVfUEFUSGQ4LnRyZSIpDQoNCnJlZl90cmVlJHRpcC5sYWJlbCA8LSByZWZfdHJlZSR0aXAubGFiZWwgJT4lIA0KICBzdHJfZXh0cmFjdCgiXiguKj8pXFwuIikgJT4lDQogIHN0cl9yZW1vdmUoIlxcLiQiKQ0KDQojMjg4MSAgY29uZ3J1ZW50IHRpcHMNCnRhYmxlKHRyZWUkdGlwLmxhYmVsICVpbiUgcmVmX3RyZWUkdGlwLmxhYmVsKQ0KDQpyZWZfdHJlZV9wcnVuZWQgPC0gZHJvcC50aXAocmVmX3RyZWUsIGMocmVmX3RyZWUkdGlwLmxhYmVsWyFyZWZfdHJlZSR0aXAubGFiZWwgJWluJSB0cmVlJHRpcC5sYWJlbF0scmVmX3RyZWUkdGlwLmxhYmVsW2R1cGxpY2F0ZWQocmVmX3RyZWUkdGlwLmxhYmVsKV0pKQ0KDQp0YWJsZSh0cmVlX3BydW5lZCR0aXAubGFiZWwgJWluJSByZWZfdHJlZV9wcnVuZWQkdGlwLmxhYmVsKQ0KDQpyZXMgPC0gY29uZ3J1aWZ5LnBoeWxvKHJlZmVyZW5jZT1yZWZfdHJlZV9wcnVuZWQsIHRhcmdldD10cmVlX3BydW5lZCwgc2NhbGU9IlBBVEhkOCIpDQoNCnRyZWUyIDwtIGRyb3AudGlwKHJlcyRwaHksIHJlcyRwaHkkdGlwLmxhYmVsWyFyZXMkcGh5JHRpcC5sYWJlbCAlaW4lIG5hbWVzKHJlZnNlcShwczIpKV0pDQp3cml0ZS50cmVlKHRyZWUyLCAib3V0cHV0L3BoeXRyZWUubndrIikNCmBgYA0KDQoNCiMjIE1lcmdlIHRlY2huaWNhbCByZXBsaWNhdGVzDQoNCldpdGggb25seSAxNiBzYW1wbGVzIHJlcGxpY2F0ZWQsIGFuZCBvbmx5IG9uIHRoZSBmaXJzdCAyIHNlcXVlbmNpbmcgcnVucyBpIGRvbnQgdGhpbmsgdGhlcmUgaXMgYSB3YXkgdG8gZXhwbGljaXRseSB0YWtlIGludG8gYWNjb3VudCByZXBsaWNhdGUgdmFyaWFiaWxpdHksIHRoZXJlZm9yZSBhbGwgcmVwbGljYXRlcyB3ZXJlIG1lcmdlZA0KDQpUaGlzIGNhbiBiZSBkb25lIHdpdGggREFEQTIgLSBtZXJnZVNlcXVlbmNlVGFibGVzKHN0MSwgc3QyLCBzdDMsIHJlcGVhdHMgPSAic3VtIg0KDQpgYGB7ciByZXBsaWNhdGVzfQ0KIyBNZXJnZSByZXBsaWNhdGVzDQpwcy5tZXJnZWQgPC0gcHMyICU+JQ0KICAgIG1lcmdlX3NhbXBsZXMoZ3JvdXAgPSAiU2FtcGxlX05hbWUiLCBmdW49InN1bSIpDQoNCg0KI1RoaXMgbG9zZXMgdGhlIHNhbXBsZSBtZXRhZGF0YSAtIE5lZWQgdG8gYWRkIGl0IGFnaWFuDQpzYW1wbGVfZGF0YShwcy5tZXJnZWQpIDwtIHNhbXBsZV9kYXRhKHBzMikgJT4lDQogIGFzKCJtYXRyaXgiKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBmaWx0ZXIoIWR1cGxpY2F0ZWQoU2FtcGxlX05hbWUpKSAlPiUNCiAgbWFncml0dHI6OnNldF9yb3duYW1lcyguJFNhbXBsZV9OYW1lKQ0KDQpzZXFzIDwtIHJlZnNlcShwczIpDQp0cmVlIDwtIHJlYWQudHJlZSgib3V0cHV0L3BoeXRyZWUubndrIikNCg0KI21ha2UgbmV3IHBoeWxvc2VxIG9iamVjdA0KcHMyIDwtIHBoeWxvc2VxKHRheF90YWJsZShwcy5tZXJnZWQpLA0KICAgICAgICAgICAgICAgc2FtcGxlX2RhdGEocHMubWVyZ2VkKSwNCiAgICAgICAgICAgICAgIG90dV90YWJsZShvdHVfdGFibGUocHMubWVyZ2VkKSwgdGF4YV9hcmVfcm93cyA9IEZBTFNFKSwNCiAgICAgICAgICAgICAgIHJlZnNlcShzZXFzKSwNCiAgICAgICAgICAgICAgIHBoeV90cmVlKHRyZWUpKQ0KDQoNCnNhdmVSRFMocHMyLCAib3V0cHV0L3Jkcy9wczIucmRzIikNCmBgYA0K
Copyright (C) 2020 Alexander M Piper
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see https://www.gnu.org/licenses/
alexander.piper@agriculture.vic.gov.au