# Knitr global setup - change eval to true to run code
library(knitr)
knitr::opts_chunk$set(echo = TRUE, eval=FALSE, message=FALSE, error=FALSE,fig.show = "hold", fig.keep = "all")
opts_chunk$set(dev = 'png')
# Load packages

#Set required packages
.cran_packages <- c("tidyverse", 
                    "patchwork", 
                    "vegan", 
                    "seqinr",
                    "ape", 
                    "sp",
                    "maptools",
                    "rgeos",
                    "data.table", 
                    "RColorBrewer",
                    "ggtree", 
                    "castor", 
                    "picante",
                    "phylosignal", 
                    "adephylo",
                    "dendextend",
                    "paco",
                    "tidytext",
                    "phytools",
                    "ecodist")
.bioc_packages <- c("dada2",
                    "microbiome",
                    "phyloseq", 
                    "DECIPHER",
                    "Biostrings",
                    "ShortRead", 
                    "philr",
                    "ALDEx2")

# Install all missing packages
.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)

# Github packages
devtools::install_github("alexpiper/taxreturn")
library(taxreturn)
devtools::install_github("alexpiper/seqateurs")
library(seqateurs)
devtools::install_github("mikemc/speedyseq")
library(speedyseq)
devtools::install_github('ggloor/CoDaSeq/CoDaSeq')
library(CoDaSeq)
devtools::install_github("easystats/report")
library(report)

devtools::install_github("pmartinezarbizu/pairwiseAdonis/pairwiseAdonis")
library(pairwiseAdonis)

#Source internal functions
source('R/BDTT.R')
source('R/phylosymbiosis.R')
source('R/helper_functions.R')
source('R/themes.R')
options(stringsAsFactors = FALSE)

Read in phyloseq object

ps2 <- readRDS("output/rds/ps2.rds")

# export filtered
dir.create("output/otu_tables/filtered")
seqateurs::summarise_taxa(ps2, "species", "SampleID") %>%
  spread(key="SampleID", value="totalRA") %>%
  write.csv(file = "output/otu_tables/filtered/filtered_spp_sum.csv")

seqateurs::summarise_taxa(ps2, "genus", "SampleID") %>%
  spread(key="SampleID", value="totalRA") %>%
  write.csv(file = "output/otu_tables/filtered/filtered_gen_sum.csv")

#Rename taxa - only keep first 30 characters
taxa_names(ps2) <- substr(paste0("SV", seq(ntaxa(ps2)),"-",tax_table(ps2)[,7]), 1,30)

#Check carsonella presence
cars <- speedyseq::psmelt(ps2) %>%
  filter(Abundance > 0) %>%
  group_by(psyllid_spp) %>%
  summarise(n = count(genus=="Candidatus Carsonella", na.rm = TRUE))

Create species merged table

# Merge species for beta diversity
ps.sppmerged <- ps2 %>%
    merge_samples(group = "psyllid_spp", fun=mean)

#This loses the sample metadata - Need to add it agian
sample_data(ps.sppmerged) <- sample_data(ps2) %>%
  as("matrix") %>%
  as.data.frame() %>%
  filter(!duplicated(psyllid_spp)) %>%
  magrittr::set_rownames(.$psyllid_spp)

seqs <- refseq(ps2)
tree <- phy_tree(ps2)
#make new phyloseq object
ps3 <- phyloseq(tax_table(ps.sppmerged),
               sample_data(ps.sppmerged),
               otu_table(otu_table(ps.sppmerged), taxa_are_rows = FALSE),
               refseq(seqs),
               phy_tree(tree))

Read in psyllid phylogeny

psyllid_tree <- read.tree(text=readLines("sample_data/psyllid_beast_tree.nwk"))

# Match names with sample sheet
psyllid_tree$tip.label <- psyllid_tree$tip.label %>%
  str_squish() %>%
  str_replace_all(pattern="\\.", replacement=" ") %>%
  str_replace_all(pattern="Acizzia hakae", replacement="Acizzia hakeae") %>%
  str_replace_all(pattern="POLLENISLAND", replacement="POLLEN ISLAND") %>%
  str_replace_all(pattern="Ctenarytaina fuchsiae$", replacement="Ctenarytaina fuchsia A") %>%
  str_replace_all(pattern="Ctenarytaina fuchsiaeB", replacement="Ctenarytaina fuchsia B") %>%
  str_replace_all(pattern="Ctenarytaina fuchsiaeC", replacement="Ctenarytaina fuchsia C") %>%
  str_replace_all(pattern="Ctenarytaina clavata", replacement="Ctenarytaina clavata sp ") %>%
  str_replace_all(pattern="Ctenarytaina clavata sp $", replacement="Ctenarytaina clavata sp A") %>%
  str_replace_all(pattern="Ctenarytaina sp$", replacement="Ctenarytaina sp ") %>%
  str_replace_all(pattern="Ctenarytaina spA", replacement="Ctenarytaina sp A") %>%
    str_replace_all(pattern="Ctenarytaina spB", replacement="Ctenarytaina sp B") %>%
  str_replace_all(pattern="Ctenarytaina unknown", replacement="Ctenarytaina insularis") %>%  
  str_replace_all(pattern="Psylla apicalisA", replacement="Psylla frodobagginsi") %>%
  str_replace_all(pattern="Psylla apicalisB", replacement="Psylla apicalis") %>%
  str_replace_all(pattern="carmichaeliae", replacement="carmichaeliae ") %>%
  str_replace_all(pattern="Trioza sp", replacement="Trioza sp ") %>%
  str_replace_all(pattern="Trioza acutaB", replacement="Trioza acuta B") %>%
  str_replace_all(pattern="Trioza gourlay", replacement="Trioza gourlayi") %>%
  str_replace_all(pattern="BRENDAMAY", replacement="BRENDA MAY") %>%
  str_replace_all(pattern="PRICES", replacement="PRICES VALLEY") %>%  
  str_replace_all(pattern="Acizzia sp", replacement="Acizzia errabunda") %>% 
  str_replace_all(pattern="Trioza ", replacement="Powellia ") %>%
  str_replace_all(pattern="Triozid sp", replacement="Casuarinicola sp") %>%
  str_replace_all(pattern="Powellia adventicia", replacement="Trioza adventicia") %>%
  str_replace_all(pattern="Powellia curta", replacement="Trioza curta") %>%
  str_replace_all(pattern=" ", replacement="_") %>%
  trimws(which="right")


# Subset to only those in sample data
setdiff(psyllid_tree$tip.label,sample_data(ps2)$psyllid_spp)
setdiff(sample_data(ps2)$psyllid_spp, psyllid_tree$tip.label)

psyllid_tree$tip.label[!psyllid_tree$tip.label %in% sample_data(ps2)$psyllid_spp]
psyllid_tree$tip.label[!sample_data(ps2)$psyllid_spp %in% psyllid_tree$tip.label ]
pruned.tree <- drop.tip(psyllid_tree, psyllid_tree$tip.label[!psyllid_tree$tip.label %in% sample_data(ps2)$psyllid_spp] )

Summary statistics

# N unique species and samples
speedyseq::psmelt(ps2) %>%
  summarise(ntaxa= n_distinct(psyllid_spp), n_samples = n_distinct(Sample_Name), n_hostplants = n_distinct(hostplant_spp))

# Spread of reads
speedyseq::psmelt(ps2) %>%
  group_by(Sample_Name) %>%
  summarise(Abundance = sum(Abundance)) %>%
  ungroup() %>%
  summarise(mean = mean(Abundance), 
            se = sd(Abundance)/sqrt(length(Abundance)),
            max = max(Abundance),
            min = min(Abundance))

# Spread of ASVs
speedyseq::psmelt(ps2) %>%
  group_by(Sample_Name) %>%
  dplyr::filter(Abundance > 0) %>%
  summarise(counts = n_distinct(OTU)) %>%
  ungroup() %>%
  summarise(mean = mean(counts), 
            se = sd(counts)/sqrt(length(counts)),
            max = max(counts),
            min = min(counts))

#Fraction of reads assigned to each taxonomic rank
speedyseq::psmelt(ps2) %>%
  gather("Rank","Name",rank_names(ps2)) %>%
  group_by(Rank) %>% 
  mutate(Name = replace(Name, str_detect(Name, "__"),NA)) %>% # This line turns the "__" we added to lower ranks back to NA's
  dplyr::summarise(Reads_classified = sum(Abundance * !is.na(Name))) %>%
  mutate(Frac_reads = Reads_classified / sum(sample_sums(ps2))) %>%
  mutate(Rank = factor(Rank, rank_names(ps2))) %>%
  arrange(Rank)

#Fraction of ASV's assigned to each taxonomic rank
tax_table(ps2) %>%
  as("matrix") %>%
  as_tibble(rownames="OTU") %>%
  gather("Rank","Name",rank_names(ps2)) %>%
  group_by(Rank) %>%
  mutate(Name = replace(Name, str_detect(Name, "__"), NA)) %>% # This line turns the "__" we added to lower ranks back to NA's
  dplyr::summarise(OTUs_classified = sum(!is.na(Name))) %>%
  mutate(Frac_OTUs = OTUs_classified / ntaxa(ps2)) %>%
  mutate(Rank = factor(Rank, rank_names(ps2))) %>%
  arrange(Rank)

# Unique taxa at each rank
speedyseq::psmelt(ps2) %>%
  dplyr::select(rank_names(ps2)) %>%
  pivot_longer(everything(),
               names_to = "Rank",
               values_to = "value") %>%
  mutate(value = case_when(
    str_detect(value, "__") ~ as.character(NA),
    !str_detect(value, "__") ~ value
  )) %>%
  drop_na() %>%
  group_by(Rank) %>%
  summarise_all(funs(n_distinct)) %>%
  mutate(Rank = factor(Rank, rank_names(ps2))) %>%
  arrange(Rank)

# Each different phylum ranked by its overall relative abundance
sample_data(ps2) %>%
  as("matrix") %>%
  as.data.frame() %>%
  pull(psyllid_spp) %>%
  table() %>%
  sort()
# Transform to per sample relative abundance, then transform to whole dataset relative abundance

Prevalence / Abundance summary

View prevalence of different phyla across the dataset

# Calculate taxon prevalence across the data set at OTU level
prevdf <- apply(X = otu_table(ps2), MARGIN = ifelse(taxa_are_rows(ps2), yes = 1, no = 2), FUN = function(x){sum(x > 0)})
prevdf <- data.frame(Prevalence = prevdf, 
                     TotalAbundance = taxa_sums(ps2),
                     tax_table(ps2))
#Prevalence plot
gg.prev <- subset(prevdf, phylum %in% get_taxa_unique(ps2, "phylum")) %>%
  ggplot(aes(TotalAbundance, Prevalence / nsamples(ps2),color=order)) +
  geom_point(size = 3, alpha = 0.7) +
  scale_x_log10() +
  xlab("Total Abundance") + ylab("Prevalence [Frac. Samples]") +
  facet_wrap(~phylum) +
  theme(legend.position="none") +
  ggtitle("Phylum Prevalence in All Samples\nColored by Order")

pdf(file="figs/prevalence.pdf", width = 11, height = 8 , paper="a4r")
  plot(gg.prev)
try(dev.off(), silent=TRUE)
  
  
# Prevalecne at phylum
ps.phylum <- speedyseq::tax_glom(ps2, taxrank="phylum")
prevdf_phylum <- apply(X = otu_table(ps.phylum ), MARGIN = ifelse(taxa_are_rows(ps.phylum ), yes = 1, no = 2), FUN = function(x){sum(x > 0)})
prevdf_phylum <- data.frame(Prevalence = prevdf_phylum, 
                     TotalAbundance = taxa_sums(ps.phylum),
                     tax_table(ps.phylum)) %>%
  dplyr::mutate(RA = TotalAbundance / sum(TotalAbundance)) %>%
  remove_rownames() %>%
  magrittr::set_rownames(.$phylum) %>%
  dplyr::select(-rank_names(ps.phylum))

# Prevalence within Proteobacteria
ps.prot <- subset_taxa(ps2, phylum=="Proteobacteria") %>%
          speedyseq::tax_glom(taxrank="order")
prevdf_prot <- apply(X = otu_table(ps.prot ), MARGIN = ifelse(taxa_are_rows(ps.prot ), yes = 1, no = 2), FUN = function(x){sum(x > 0)})
prevdf_prot <- data.frame(Prevalence = prevdf_prot, 
                     TotalAbundance = taxa_sums(ps.prot),
                     tax_table(ps.prot)) %>%
  dplyr::mutate(RA = TotalAbundance / sum(TotalAbundance)) %>%
  remove_rownames() %>%
  magrittr::set_rownames(.$order) %>%
  dplyr::select(-rank_names(ps.prot))
  
# Genus Prevalence
ps.gen <- speedyseq::tax_glom(ps2, taxrank="genus") 
prevdf_gen <- apply(X = otu_table(ps.gen ), MARGIN = ifelse(taxa_are_rows(ps.gen ), yes = 1, no = 2), FUN = function(x){sum(x > 0)})
prevdf_gen <- data.frame(Prevalence = prevdf_gen, 
                     TotalAbundance = taxa_sums(ps.gen),
                     tax_table(ps.gen)) %>%
  dplyr::mutate(RA = TotalAbundance / sum(TotalAbundance)) %>%
  remove_rownames() %>%
  mutate(genus = make.unique(genus)) %>%
  magrittr::set_rownames(.$genus) %>%
  dplyr::select(-rank_names(ps.gen))

# Genus Prevalence across species rather than specimens
ps.gen <- speedyseq::tax_glom(ps3, taxrank="genus") 
prevdf_gen <- apply(X = otu_table(ps.gen ), MARGIN = ifelse(taxa_are_rows(ps.gen ), yes = 1, no = 2), FUN = function(x){sum(x > 0)})
prevdf_gen <- data.frame(Prevalence = prevdf_gen, 
                     TotalAbundance = taxa_sums(ps.gen),
                     tax_table(ps.gen)) %>%
  dplyr::mutate(RA = TotalAbundance / sum(TotalAbundance)) %>%
  remove_rownames() %>%
  mutate(genus = make.unique(genus)) %>%
  magrittr::set_rownames(.$genus) %>%
  dplyr::select(-rank_names(ps.gen))

# Prevalence of symbionts across psyllid species
speedyseq::psmelt(ps2) %>%
  mutate(total_spp  = n_distinct(psyllid_spp), total_specimen = n_distinct(Sample_Name)) %>%
  filter(Abundance > 0) %>%
  filter(genus %in% c("Candidatus Carsonella", "Arsenophonus", "Sodalis")) %>%
  group_by(genus, total_spp, total_specimen)%>%
  summarise(n_species = n_distinct(psyllid_spp), n_specimen = n_distinct(Sample_Name)) %>%
  ungroup()%>%
  mutate(prop_species = n_species / total_spp,
         prop_specimen = n_specimen / total_specimen)

# Number of symbiont OTUs per psyllid species
speedyseq::psmelt(ps2) %>%
  filter(Abundance > 0) %>%
  filter(genus %in% c("Candidatus Carsonella", "Arsenophonus", "Sodalis")) %>%
  group_by(psyllid_spp, genus) %>%
  summarise(n = n_distinct(OTU)) %>%
  ggplot(aes(x = psyllid_spp, y = n, fill=genus))+
  geom_col(show.legend = FALSE)+
  facet_grid(genus~.)+
  theme(axis.text.x = element_text(angle=45, hjust=1)) +
  labs(x = "Psyllid Species",
       y = "Number of distinct ASVs")

se <- function(x) sqrt(var(x)/length(x))

# Mean abundance of  genera
genera_abund <- speedyseq::psmelt(ps2) %>%
  filter(Abundance > 0) %>%
  group_by(SampleID) %>%
    mutate_at(vars(Abundance), ~ . / sum(.) ) %>%
  ungroup %>%
  group_by(genus) %>%
  summarise(mean_ra = mean(Abundance), upper = max(Abundance), lower = min(Abundance), se = se(Abundance)) 

Alpha diversity metrics

dir.create("output/alpha")
# Get richness measures
richness <- phyloseq::estimate_richness(ps2, measures=c("Shannon")) %>%
  rownames_to_column("Sample_Name") %>%
  mutate(Sample_Name = Sample_Name %>% 
           str_remove("^X") %>%
           str_replace_all("\\.", " "))

#Set number of randomisations for calculating significance
# Calculate Faith's PD-index & Species richness - with Standard errors
#sespd <- picante::ses.pd(as(phyloseq::otu_table(ps2), "matrix"),  phyloseq::phy_tree(ps2), null.model = "taxa.labels", include.root = F, runs = 99)

pd <- picante::pd(as(phyloseq::otu_table(ps2), "matrix"),  phyloseq::phy_tree(ps2), include.root = FALSE)

# Join together
div_table <- pd %>%
  rownames_to_column("Sample_Name") %>%
  dplyr::select(Sample_Name, alpha = SR, pd = PD) %>%
  left_join(richness, by="Sample_Name") %>%
  left_join(sample_data(ps2) %>% 
              as("matrix") %>%
              as.data.frame() %>%
              filter(!duplicated(Sample_Name)) %>%
              dplyr::select(Sample_Name, psyllid_spp, psyllid_genus, psyllid_family, hostplant_spp, seqrun, genus_geo),
            by = "Sample_Name") 

# Summarise means
div_table %>%
  summarise_if(is.numeric, mean)



# Difference between species for alpha diversity ANOVA
report::report(aov(alpha ~seqrun+psyllid_family+psyllid_genus+psyllid_spp, data=div_table))
report::report(aov(Shannon ~seqrun+psyllid_family+psyllid_genus+psyllid_spp, data=div_table))
report::report(aov(pd ~seqrun+psyllid_family+psyllid_genus+psyllid_spp, data=div_table))

# Difference between all genera for alpha diversity ANOVA
report::report(aov(alpha ~seqrun+psyllid_family+psyllid_genus, data=div_table))
report::report(aov(Shannon ~seqrun+psyllid_family+psyllid_genus, data=div_table))
report::report(aov(pd ~seqrun+psyllid_family+psyllid_genus, data=div_table))

# Difference between genus/geography factors ANOVA
report::report(aov(alpha ~seqrun+psyllid_family+genus_geo, data=div_table))
report::report(aov(Shannon ~seqrun+psyllid_family+genus_geo, data=div_table))
report::report(aov(pd ~seqrun+psyllid_family+genus_geo, data=div_table))

## Major genera only
# Difference between all major genera for alpha diversity ANOVA
div_table2 <- div_table %>%
  dplyr::filter(psyllid_genus %in% c("Powellia", "Ctenarytaina", "Psylla"))

mg_div <- bind_rows(broom::tidy(TukeyHSD(aov(alpha ~seqrun+psyllid_family+psyllid_genus, 
                                   data=div_table2))) %>% mutate(type="Richness"),
          broom::tidy(TukeyHSD(aov(Shannon ~seqrun+psyllid_family+psyllid_genus,
                                   data=div_table2))) %>% mutate(type="Shannon"),
          broom::tidy(TukeyHSD(aov(pd ~seqrun+psyllid_family+psyllid_genus,
                                   data=div_table2))) %>% mutate(type="Phylogenetic"),
          broom::tidy(TukeyHSD(aov(alpha ~seqrun+psyllid_family+genus_geo,
                                   data=div_table2))) %>% mutate(type="Richness"),
          broom::tidy(TukeyHSD(aov(alpha ~seqrun+psyllid_family+genus_geo,
                                   data=div_table2))) %>% mutate(type="Shannon"),
          broom::tidy(TukeyHSD(aov(pd ~seqrun+psyllid_family+genus_geo,
                                   data=div_table2))) %>% mutate(type="Phylogenetic")
          )
write_csv(mg_div, "output/alpha/major_genera_alpha.csv")

# Difference between major genera only ANOVA
report::report(aov(alpha ~seqrun+psyllid_family+psyllid_genus, data=div_table2))
report::report(aov(Shannon ~seqrun+psyllid_family+psyllid_genus, data=div_table2))
report::report(aov(pd ~seqrun+psyllid_family+psyllid_genus, data=div_table2))

# Difference between between major genera/geography factors ANOVA
report::report(aov(alpha ~seqrun+psyllid_family+genus_geo, data=div_table2))
report::report(aov(Shannon ~seqrun+psyllid_family+genus_geo, data=div_table2))
report::report(aov(alpha ~seqrun+psyllid_family+genus_geo, data=div_table2))

# Association with phylogeny
dat <- div_table  %>%
  filter(psyllid_spp %in% pruned.tree$tip.label) %>% #Subset to common 
  group_by(psyllid_spp) %>%
  dplyr::select(-where(is.character)) %>%
  summarise_all(mean) %>%
  arrange(match(psyllid_spp, pruned.tree$tip.label)) %>%
  as.data.frame() %>%
  magrittr::set_rownames(.$psyllid_spp) %>%
  dplyr::select(-psyllid_spp)

# Add positive and negative controls
dat$random <- rnorm(length(dat$alpha), sd = 10) #Random association
dat$bm <- rTraitCont(pruned.tree) #Brownian motion

# Make phylosignal object and measure signal between univariate traits.
p4d <- phylobase::phylo4d(pruned.tree, dat) 
signal <- phylosignal::phyloSignal(p4d = p4d, methods = c("I", "Lambda", "K"), reps = 999)%>%
  as.data.frame() %>%
  rownames_to_column("measure")

# print phylogenetic signal
signal
write_csv(signal, "output/alpha/phylosignal.csv")

# Locate signal
lipa <- lipaMoran(p4d, reps=999)
lipa.p4d <- lipaMoran(p4d, as.p4d = TRUE, reps=999)
barplot.phylo4d(lipa.p4d, bar.col = (lipa$p.value < 0.05) + 1, center = FALSE, scale = FALSE) + title("Non-rarefied")

#write out lipa
lipa_out <- cbind(lipa$lipa %>%
                    as.data.frame %>%
                    rename_all(funs(paste0(., "_stat"))),
                  lipa$p.value %>%
                    as.data.frame %>%
                    rename_all(funs(paste0(., "_pval")))
                               )%>%
  rownames_to_column("psyllid_spp")
write_csv(lipa_out, "output/alpha/lipa.csv")

Rarefied

See if the pattern holds even with rarefaction to lowest sample

# Rarefied richness
ps2_rare <- rarefy_even_depth(ps2, sample.size = min(sample_sums(ps2)),
  rngseed = 666, replace = TRUE, trimOTUs = TRUE, verbose = TRUE)

# Get richness measures
richness_rare <- phyloseq::estimate_richness(ps2_rare, measures=c("Shannon")) %>%
  rownames_to_column("Sample_Name") %>%
  mutate(Sample_Name = Sample_Name %>% 
           str_remove("^X") %>%
           str_replace_all("\\.", " "))

#Set number of randomisations for calculating significance
# Calculate Faith's PD-index & Species richness - with Standard errors
#sespd_rare <- picante::ses.pd(as(phyloseq::otu_table(ps2_rare), "matrix"),  phyloseq::phy_tree(ps2_rare), null.model = #"taxa.labels", include.root = F, runs = 99)

pd_rare <- picante::pd(as(phyloseq::otu_table(ps2_rare), "matrix"),  phyloseq::phy_tree(ps2_rare), include.root = FALSE)

# Join together
div_table_rare <- pd_rare %>%
  rownames_to_column("Sample_Name") %>%
  dplyr::select(Sample_Name, alpha = SR, pd = PD) %>%
  left_join(richness, by="Sample_Name") %>%
  left_join(sample_data(ps2_rare) %>% 
              as("matrix") %>%
              as.data.frame() %>%
              filter(!duplicated(Sample_Name)) %>%
              dplyr::select(Sample_Name, psyllid_spp, psyllid_genus, genus_geo),
            by = "Sample_Name") 

# Summarise means
div_table_rare %>%
  summarise_if(is.numeric, mean)

# Difference between all major genera for alpha diversity ANOVA
div_table_rare2 <- div_table_rare %>%
  dplyr::filter(psyllid_genus %in% c("Powellia", "Ctenarytaina", "Psylla"))

mg_div_rare <- bind_rows(
  broom::tidy(TukeyHSD(aov(alpha ~seqrun+psyllid_family+psyllid_genus,
                           data=div_table_rare2))) %>% mutate(type="Richness"),
  broom::tidy(TukeyHSD(aov(Shannon ~seqrun+psyllid_family+psyllid_genus,
                           data=div_table_rare2))) %>% mutate(type="Shannon"),
  broom::tidy(TukeyHSD(aov(pd ~seqrun+psyllid_family+psyllid_genus,
                           data=div_table_rare2))) %>% mutate(type="Phylogenetic"),
  broom::tidy(TukeyHSD(aov(alpha ~seqrun+psyllid_family+genus_geo,
                           data=div_table_rare2))) %>% mutate(type="Richness"),
  broom::tidy(TukeyHSD(aov(alpha ~seqrun+psyllid_family+genus_geo,
                           data=div_table_rare2))) %>% mutate(type="Shannon"),
  broom::tidy(TukeyHSD(aov(pd ~seqrun+psyllid_family+genus_geo,
                           data=div_table_rare2))) %>% mutate(type="Phylogenetic")
          )
write_csv(mg_div_rare, "output/alpha/major_genera_alpha_rarefied.csv")

# Difference between genus factors ANOVA
report::report(aov(alpha ~seqrun+psyllid_family+psyllid_genus, data=div_table_rare2))
report::report(aov(Shannon ~seqrun+psyllid_family+psyllid_genus, data=div_table_rare2))
report::report(aov(pd ~seqrun+psyllid_family+psyllid_genus, data=div_table_rare2))

# Difference between genus/geography factors ANOVA
report::report(aov(alpha ~seqrun+psyllid_family+genus_geo, data=div_table_rare2))
report::report(aov(Shannon ~seqrun+psyllid_family+genus_geo, data=div_table_rare2))
report::report(aov(alpha ~seqrun+psyllid_family+genus_geo, data=div_table_rare2))


dat <- div_table_rare  %>%
  filter(psyllid_spp %in% pruned.tree$tip.label) %>% #Subset to common 
  group_by(psyllid_spp) %>%
  dplyr::select(-where(is.character)) %>%
  summarise_all(mean) %>%
  arrange(match(psyllid_spp, pruned.tree$tip.label)) %>%
  as.data.frame() %>%
  magrittr::set_rownames(.$psyllid_spp) %>%
  dplyr::select(-psyllid_spp)

# Add positive and negative controls
dat$random <- rnorm(length(dat$alpha), sd = 10) #Random association
dat$bm <- rTraitCont(pruned.tree) #Brownian motion

# Make phylosignal object and measure signal between univariate traits.
p4d <- phylobase::phylo4d(pruned.tree, dat) 
signal_rare <- phylosignal::phyloSignal(p4d = p4d, methods = c("I", "Lambda", "K"), reps = 999) %>%
  as.data.frame() %>%
  rownames_to_column("measure")

# print phylogenetic signal
signal_rare
write_csv(signal_rare, "output/alpha/phylosignal_rarefied.csv")

# Locate signal
lipa <- lipaMoran(p4d, reps=999)
lipa.p4d <- lipaMoran(p4d, as.p4d = TRUE, reps=999)
barplot.phylo4d(lipa.p4d, bar.col = (lipa$p.value < 0.05) + 1, center = FALSE, scale = FALSE) + title("Rarefied")

#write out lipa
lipa_out_rare <- cbind(lipa$lipa %>%
                    as.data.frame %>%
                    rename_all(funs(paste0(., "_stat"))),
                  lipa$p.value %>%
                    as.data.frame %>%
                    rename_all(funs(paste0(., "_pval")))
                               )%>%
  rownames_to_column("psyllid_spp")
write_csv(lipa_out_rare, "output/alpha/lipa_rarefied.csv")    

Alpha no Gammaproteobacteria

# Rarefied richness
ps2_subset <- ps2 %>%
 subset_taxa(class != "Gammaproteobacteria") %>% #is this working?
 filter_taxa(function(x) mean(x) > 0, TRUE)#Drop missing taxa from table
ps2_subset <- prune_samples(sample_sums(ps2_subset) >0 , ps2_subset)
message(nsamples(ps2) - nsamples(ps2_subset), " Samples and ", ntaxa(ps2) - ntaxa(ps2_subset), " taxa Dropped")

# Get richness measures
richness_subset <- phyloseq::estimate_richness(ps2_subset, measures=c("Shannon")) %>%
  rownames_to_column("Sample_Name") %>%
  mutate(Sample_Name = Sample_Name %>% 
           str_remove("^X") %>%
           str_replace_all("\\.", " "))

#Set number of randomisations for calculating significance
# Calculate Faith's PD-index & Species richness - with Standard errors
#sespd_subset <- picante::ses.pd(as(phyloseq::otu_table(ps2_subset), "matrix"),  phyloseq::phy_tree(ps2_subset), null.model = "taxa.labels", include.root = F, runs = 99)

pd_subset <- picante::pd(as(phyloseq::otu_table(ps2_subset), "matrix"),  phyloseq::phy_tree(ps2_subset), include.root = FALSE)

# Join together
div_table_subset <- pd_subset %>%
  rownames_to_column("Sample_Name") %>%
  dplyr::select(Sample_Name, alpha = SR, pd = PD) %>%
  left_join(richness, by="Sample_Name") %>%
  left_join(sample_data(ps2_subset) %>% 
              as("matrix") %>%
              as.data.frame() %>%
              filter(!duplicated(Sample_Name)) %>%
              dplyr::select(Sample_Name, psyllid_spp, psyllid_genus, genus_geo),
            by = "Sample_Name") 

# Summarise means
div_table_subset %>%
  summarise_if(is.numeric, ~mean(.x, na.rm=TRUE))

# Difference between all major genera for alpha diversity ANOVA
div_table_subset2 <- div_table_subset %>%
  dplyr::filter(psyllid_genus %in% c("Powellia", "Ctenarytaina", "Psylla"))

mg_div_subset <- bind_rows(
  broom::tidy(TukeyHSD(aov(alpha ~psyllid_genus, data=div_table_subset2))) %>% mutate(type="Richness"),
  broom::tidy(TukeyHSD(aov(Shannon ~psyllid_genus, data=div_table_subset2))) %>% mutate(type="Shannon"),
  broom::tidy(TukeyHSD(aov(pd ~psyllid_genus, data=div_table_subset2))) %>% mutate(type="Phylogenetic"),
  broom::tidy(TukeyHSD(aov(alpha ~genus_geo, data=div_table_subset2))) %>% mutate(type="Richness"),
  broom::tidy(TukeyHSD(aov(alpha ~genus_geo, data=div_table_subset2))) %>% mutate(type="Shannon"),
  broom::tidy(TukeyHSD(aov(pd ~genus_geo, data=div_table_subset2))) %>% mutate(type="Phylogenetic")
          )
write_csv(mg_div_subset, "output/alpha/major_genera_alpha_nogamma.csv")

# Difference between genus factors ANOVA
report::report(aov(alpha ~psyllid_genus, data=div_table_subset2))
report::report(aov(Shannon ~psyllid_genus, data=div_table_subset2))
report::report(aov(pd ~psyllid_genus, data=div_table_subset2))


# Difference between genus/geography factors ANOVA
report::report(aov(alpha ~genus_geo, data=div_table_subset2))
report::report(aov(Shannon ~genus_geo, data=div_table_subset2))
report::report(aov(alpha ~genus_geo, data=div_table_subset2))

Beta diversity

Microbe distances

ps2_dist <- ps2
#ps2_dist <- ps2_filt

# Get OTU tables
otutab <- otu_table(ps2_dist)
#Impute zeroes for compositional distances
otutab_n0 <- as.matrix(zCompositions::cmultRepl(otutab, method="BL", output="p-counts"))

#Root & label phylogenetic tree
phy_tree(ps2_dist) <- multi2di(phy_tree(ps2_dist))
phy_tree(ps2_dist) <- makeNodeLabel(phy_tree(ps2_dist), method="number", prefix='n')
name.balance(phy_tree(ps2_dist), tax_table(ps2_dist), 'n1')

#Calculate different distance metrics
metrics <- c("Bray", "Jaccard", "Aitchison","Philr", "Unifrac", "WUnifrac")  
distlist <- vector("list", length=length(metrics))
names(distlist) <- metrics

distlist$Jaccard <- as.matrix(vegdist(otutab, method="jac",binary = T))
distlist$Bray <- as.matrix(vegdist(otutab, method="bray"))
distlist$Aitchison <- as.matrix(vegdist(CoDaSeq::codaSeq.clr(otutab_n0), method="euclidean"))
distlist$Philr <- as.matrix(vegdist(philr::philr(otutab_n0, phy_tree(ps2_dist),
                                                part.weights='enorm.x.gm.counts',
                                                ilr.weights='blw.sqrt'), method="euclidean", na.rm=TRUE))
distlist$Unifrac <- as.matrix(phyloseq::UniFrac(ps2_dist, weighted=FALSE, parallel = TRUE))
distlist$WUnifrac <- as.matrix(phyloseq::UniFrac(ps2_dist, weighted=TRUE, parallel = TRUE))

# Create low abundance filtered dataset
filterfun1 <- function(x){
  x[(x / sum(x)) < (1e-4)] <- 0
  return(x)
}
ps2_filt  <- transform_sample_counts(ps2, fun = filterfun1) %>%
  filter_taxa(function(x) mean(x) > 0, TRUE) #Drop missing taxa from table

print(paste0((ntaxa(ps2)-ntaxa(ps2_filt)), " taxa under threshold removed"))

# Get OTU tables
otutab <- otu_table(ps2_filt)
#Impute zeroes for compositional distances
otutab_n0 <- as.matrix(zCompositions::cmultRepl(otutab, method="BL", output="p-counts"))
#Root & label phylogenetic tree
phy_tree(ps2_filt) <- multi2di(phy_tree(ps2_filt))
phy_tree(ps2_filt) <- makeNodeLabel(phy_tree(ps2_filt), method="number", prefix='n')
name.balance(phy_tree(ps2_filt), tax_table(ps2_filt), 'n1')

#Calculate different distance metrics
metrics <- c("Bray", "Jaccard", "Aitchison","Philr", "Unifrac", "WUnifrac")  
distlist_filt <- vector("list", length=length(metrics))
names(distlist_filt) <- metrics

distlist_filt$Jaccard <- as.matrix(vegdist(otutab, method="jac",binary = T))
distlist_filt$Bray <- as.matrix(vegdist(otutab, method="bray"))
distlist_filt$Aitchison <- as.matrix(vegdist(CoDaSeq::codaSeq.clr(otutab_n0), method="euclidean"))
distlist_filt$Philr <- as.matrix(vegdist(philr::philr(otutab_n0, phy_tree(ps2_filt),
                                                part.weights='enorm.x.gm.counts',
                                                ilr.weights='blw.sqrt'), method="euclidean", na.rm=TRUE))
distlist_filt$Unifrac <- as.matrix(phyloseq::UniFrac(ps2_filt, weighted=FALSE, parallel = TRUE))
distlist_filt$WUnifrac <- as.matrix(phyloseq::UniFrac(ps2_filt, weighted=TRUE, parallel = TRUE))

# Create dataset without gammaproteoba
ps2_subset <- ps2 %>%
 subset_taxa(class != "Gammaproteobacteria") %>% #is this working?
 filter_taxa(function(x) mean(x) > 0, TRUE)#Drop missing taxa from table
ps2_subset <- prune_samples(sample_sums(ps2_subset) >0 , ps2_subset)
message(nsamples(ps2) - nsamples(ps2_subset), " Samples and ", ntaxa(ps2) - ntaxa(ps2_subset), " taxa Dropped")

# Get OTU tables
otutab_subset <- otu_table(ps2_subset)
#Impute zeroes for compositional distances
otutab_subset_n0 <- as.matrix(zCompositions::cmultRepl(otutab_subset, method="BL", output="p-counts"))
#Root phylogenetic tree
phy_tree(ps2_subset) <- multi2di(phy_tree(ps2_subset))
phy_tree(ps2_subset) <- makeNodeLabel(phy_tree(ps2_subset), method="number", prefix='n')
name.balance(phy_tree(ps2_subset), tax_table(ps2_subset), 'n1')

#Calculate different distance metrics
metrics <- c("Bray", "Jaccard", "Aitchison","Philr", "Unifrac", "WUnifrac")  
distlist_subset <- vector("list", length=length(metrics))
names(distlist_subset) <- metrics

distlist_subset$Jaccard <- as.matrix(vegdist(otutab_subset, method="jac",binary = T))
distlist_subset$Bray <- as.matrix(vegdist(otutab_subset, method="bray"))
distlist_subset$Aitchison <- as.matrix(vegdist(CoDaSeq::codaSeq.clr(otutab_subset_n0), method="euclidean"))
distlist_subset$Philr <- as.matrix(vegdist(philr::philr(otutab_subset_n0, phy_tree(ps2_subset),
                                                part.weights='enorm.x.gm.counts',
                                                ilr.weights='blw.sqrt'), method="euclidean", na.rm=TRUE))
distlist_subset$Unifrac <- as.matrix(phyloseq::UniFrac(ps2_subset, weighted=FALSE, parallel = TRUE))
distlist_subset$WUnifrac <- as.matrix(phyloseq::UniFrac(ps2_subset, weighted=TRUE, parallel = TRUE))

# Mantel test to check concordance of beta diversity pre and post filtering

purrr::map2(distlist, distlist_filt,~{
  subsample <- intersect(colnames(.x), colnames(.y))
  as.data.frame(vegan::mantel(.x[subsample, subsample], .y[subsample, subsample])[c("statistic","signif","permutations")])
}) %>%
  bind_rows(.id="dist")

# Mantel test to check concordance of beta diversity pre and post subset

purrr::map2(distlist, distlist_subset,~{
  subsample <- intersect(colnames(.x), colnames(.y))
  as.data.frame(vegan::mantel(.x[subsample, subsample], .y[subsample, subsample])[c("statistic","signif","permutations")])
}) %>%
  bind_rows(.id="dist")

Adonis & Betadisper

# ADONIS is constructed heirarchially to marginalise techical variance then moving down the taxonomic ranks 

# Adonis test
metadata <- sample_data(ps2) %>%
  as("data.frame")
adonis_results <- distlist %>%
  purrr::map(function(x) {
    y <- as.dist(x[metadata$Sample_Name, metadata$Sample_Name])
    bind_rows(
    broom::tidy(adonis2(y~seqrun+psyllid_family+psyllid_genus+psyllid_spp, method="euclidean", data=metadata, 
                       permutations=999, by="terms")) %>% 
      mutate(test = paste(term[!term %in% c("Residual", "Total")], collapse="-")),
    #broom::tidy(adonis2(y~seqrun+hostplant_spp+psyllid_spp, method="euclidean", data=metadata, 
     #                  permutations=999, by="margin")) %>% 
    #  mutate(test = paste(term[!term %in% c("Residual", "Total")], collapse="-")),
    broom::tidy(adonis2(y~seqrun+hostplant_spp, method="euclidean", data=metadata,
                       permutations=999, by="terms")) %>% 
      mutate(test = paste(term[!term %in% c("Residual", "Total")], collapse="-"))
    )
})  %>%
  bind_rows(.id="dist")

# Check homogeneity
betadisper_results <- distlist %>%
  purrr::map(function(x) {
    y <- as.dist(x[metadata$Sample_Name, metadata$Sample_Name])
  bind_rows(
    as_tibble(permutest(vegan::betadisper(y, metadata$psyllid_spp))$tab, rownames="term") %>%
      mutate(test="psyllid_spp"),
    as_tibble(permutest(vegan::betadisper(y, metadata$hostplant_spp))$tab, rownames="term")  %>%
      mutate(test="hostplant_spp"),
  )
})  %>%
  bind_rows(.id="dist")

dir.create("output/beta")
write_csv(adonis_results, "output/beta/adonis_fulldata.csv")
write_csv(betadisper_results, "output/beta/betadisper_fulldata.csv")

Same tree dissimilarities

Look at the similarities in the microbiome of the psyllid specimens collected from the same host plant

hostplant_metadata <- metadata %>% mutate(ingroup = case_when(
  Sample_Name %in% c("94","107", "113","93","106", "112") ~ "fraxini-fraxinicola",
  Sample_Name %in% c("200big", "201big", "200small", "201small") ~ "apicalis-frodobagginsi",
  TRUE ~ "other"
  ))

broom::tidy(adonis2(distlist$Aitchison~ingroup + psyllid_spp, method="euclidean",
                   data=hostplant_metadata))

pairwise.adonis2(distlist$Aitchison~ingroup + psyllid_spp, method="euclidean",
                   data=hostplant_metadata)

Barplot

# Plot tree
p <- ggtree(pruned.tree) + geom_tiplab(align=TRUE) + geom_nodelab(geom='label') +
    scale_x_continuous(expand=c(0, 0.1)) 

# Plot bar
ps3_bar <- ps3 %>%
  speedyseq::tax_glom(taxrank = "order") %>%           # agglomerate at Order level
  transform_sample_counts(function(x) {x/sum(x)} ) %>% # Transform to rel. abundance
  speedyseq::psmelt() %>%
  mutate(plotlabel = phylum) %>%
  mutate(plotlabel = case_when(
    Abundance >= 0.01 & phylum=="Proteobacteria" ~ paste0("P - ", order), # Change this to whatever taxrank we want
    Abundance >= 0.01 & !phylum=="Proteobacteria"~ phylum ,
    Abundance < 0.01 ~ "NA"
    )) %>%
  dplyr::na_if("NA") %>%
  dplyr::select(psyllid_spp, plotlabel, phylum, order, Abundance) %>%
  left_join(p$data %>%
              as_data_frame %>%
              dplyr::filter(isTip) %>%
              dplyr::select(y, label) %>%
              dplyr::rename(psyllid_spp=label)) 

gg.bar <- ggplot(ps3_bar, aes(x=y, y=Abundance, fill=plotlabel)) +
  geom_col()  + 
  coord_flip()+
  scale_fill_manual(values=colorRampPalette(brewer.pal(9, "Set1"))(length(unique(ps3_bar$plotlabel))-1), na.value="grey") + 
    base_theme  +
    theme(legend.position = "bottom",
      #panel.grid.major.x = element_line(colour="grey92", size=0.5, linetype="dashed"),
      strip.background = element_rect(fill = "grey92", 
                    colour = "black", size = 1),
      axis.text.y = element_blank(),
      axis.ticks.y=element_blank(),
      axis.title.y=element_blank()) +
  scale_y_continuous(expand=c(0,0), labels = scales::percent)+
  scale_x_continuous(expand=c(0,0)) +
  labs(x = NULL ,
       y = "Relative Abundance",
       fill = NULL)

# Make richness plots
gg.rich <-  div_table %>%
  group_by(psyllid_spp) %>%
  summarise(alpha=mean(alpha), pd=mean(pd)/10e+7, Shannon=mean(Shannon)) %>%
  ungroup() %>%
  pivot_longer(-psyllid_spp, names_to="measure",
               values_to = "value")  %>%
  left_join(lipa_out %>% 
              dplyr::select(psyllid_spp, alpha_pval, pd_pval, Shannon_pval) %>%
              pivot_longer(-psyllid_spp,
                           names_to="measure",
               values_to = "pval") %>%
              mutate(measure = str_remove(measure, "_pval"))) %>%
  left_join(p$data %>%
              as_data_frame %>%
              dplyr::filter(isTip) %>%
              dplyr::select(y, label) %>%
              dplyr::rename(psyllid_spp=label)) %>%
  mutate_at(vars(measure), funs(factor(., levels=c("alpha","Shannon","pd")))) %>%
  ggplot(aes(x=y, y=value, fill=pval<0.05)) + 
    geom_col() +
    facet_grid(~measure, scales="free") + 
    coord_flip()+
    base_theme  +
    theme(legend.position = "bottom",
    panel.grid.major.x = element_line(colour="grey92", size=0.5, linetype="dashed"),
   # strip.background = element_rect(fill = "grey92", 
    #              colour = "black", size = 1),
    axis.text.y = element_blank(),
    axis.ticks.y=element_blank(),
    axis.title.y=element_blank(),
    axis.text.x = element_text(angle=45, hjust=1)) +
    scale_fill_manual(values=c("darkgray", "darkred")) +
    scale_x_continuous(breaks=scales::pretty_breaks(n=1))+
  scale_y_continuous(expand=c(0,0))+
  scale_x_continuous(expand=c(0,0))

## Collection_hist
gg.spp <- sample_data(ps2) %>%
  as("matrix") %>%
  as.data.frame() %>%
  dplyr::rename(label = psyllid_spp) %>%
  group_by(label) %>%
  summarise(n_species = n()) %>%
  left_join(p$data %>%
  filter(isTip) %>% dplyr::select(c(label, y))) %>%
  filter(!is.na(y)) %>%
  ggplot(aes(x=y, y=1, fill=n_species)) +
    geom_tile() +
    geom_text(aes(label=n_species))+
    coord_flip() +
    theme_void() +
    theme(legend.position = "bottom") +
    scale_fill_distiller(palette = "Reds", direction=1)+
  scale_y_continuous(expand=c(0,0))+
  scale_x_continuous(expand=c(0,0))

# Make tree with no underscores in name
p2 <- p
p2$data$label <- str_replace(p2$data$label, "_", " ")

#Arrange
Fig1 <- p2 + gg.spp + gg.bar + gg.rich + plot_layout(nrow=1, widths=c(1,0.08,2,0.6))  

pdf(file="figs/Beta.pdf", width = 8, height = 11 , paper="a4")
  plot(Fig1)
try(dev.off(), silent=TRUE)

Phylosymbiosis

Prepare distance matrices

## Psyllid phylogeny cophenetic distance matrix
phylo.dist <- cophenetic(pruned.tree) %>%
   sqrt() %>%
  as.data.frame() %>%
  rownames_to_column("psyllid_spp.x") %>%
  pivot_longer(cols=-psyllid_spp.x,
               names_to="psyllid_spp.y",
               values_to = "dist") %>%
  right_join(sample_data(ps2) %>%
               as("matrix") %>%
               as.data.frame() %>%
              dplyr::select(Sample_Name, psyllid_spp) %>%
              dplyr::rename(psyllid_spp.x = psyllid_spp, Sample_Name.x = Sample_Name),
            by="psyllid_spp.x")%>%
  right_join(sample_data(ps2) %>%
               as("matrix") %>%
               as.data.frame() %>%
              dplyr::select(Sample_Name, psyllid_spp) %>%
              dplyr::rename(psyllid_spp.y = psyllid_spp, Sample_Name.y = Sample_Name),
            by="psyllid_spp.y") %>%
  filter(!is.na(dist)) %>%
  dplyr::select(-psyllid_spp.y, -psyllid_spp.x) %>%
  pivot_wider(names_from = Sample_Name.y, values_from = dist)  %>%
  column_to_rownames("Sample_Name.x") %>%
  as.matrix()

plant.tree <- read.tree("sample_data/plant_tree.nwk")
plant.dist <- cophenetic(plant.tree) %>%
  sqrt() %>%
  as.data.frame() %>%
  rownames_to_column("hostplant_spp.x") %>%
  pivot_longer(cols=-hostplant_spp.x,
               names_to="hostplant_spp.y",
               values_to = "dist") %>%
  right_join(sample_data(ps2) %>%
               as("matrix") %>%
               as.data.frame() %>%
              dplyr::select(Sample_Name, hostplant_spp) %>%
              dplyr::rename(hostplant_spp.x = hostplant_spp, Sample_Name.x = Sample_Name),
            by="hostplant_spp.x")%>%
  right_join(sample_data(ps2) %>%
               as("matrix") %>%
               as.data.frame() %>%
              dplyr::select(Sample_Name, hostplant_spp) %>%
              dplyr::rename(hostplant_spp.y = hostplant_spp, Sample_Name.y = Sample_Name),
            by="hostplant_spp.y") %>%
  filter(!is.na(dist))%>%
  dplyr::select(-hostplant_spp.y, -hostplant_spp.x) %>%
  pivot_wider(names_from = Sample_Name.y, values_from = dist) %>%
  column_to_rownames("Sample_Name.x") %>%
  as.matrix()

# Spatial distance matrix
envData <- sample_data(ps2) %>%
  as("matrix") %>%
  as.data.frame() %>%
  dplyr::select(long, lat) %>%
  mutate(long = as.numeric(long), lat=as.numeric(lat))%>%
  drop_na() 
  
spat.dist <- spDists(as.matrix(envData), longlat=TRUE) %>%
  as.data.frame() %>%
  magrittr::set_rownames(rownames(envData) %>% str_replace(pattern="\\_S(.*)$",replacement="") %>% make.unique()) %>%
  magrittr::set_colnames(rownames(envData) %>% str_replace(pattern="\\_S(.*)$",replacement="") %>% make.unique()) %>%
  rownames_to_column("SampleID") %>%
  mutate(SampleID = SampleID %>% str_replace(pattern="\\_S(.*)$",replacement="") %>% make.unique()) %>%
  column_to_rownames("SampleID") %>%
  as.matrix()

Whole dataset

set.seed(909)
dir.create("output/phylosymbiosis")

# Matrix correlations
#only use samples present in all
subsample <- Reduce(intersect, list(rownames(otu_table(ps2)), colnames(phylo.dist), colnames(plant.dist), colnames(spat.dist)))

# Mantel test
mantel_results <- distlist %>%
  purrr::map(function(x){
    run_mantel(x, dists = c("phylo.dist", "plant.dist", "spat.dist"),
               subsample = subsample, type  ="mantel", nboot=1000)
  }) %>%
  bind_rows(.id="dist")

write_csv(mantel_results %>%
            dplyr::select(-one_of("pval1","pval2")),#only keep two sided P values
          "output/phylosymbiosis/mantel_fulldata.csv")


# Partial Mantel Test
pmantel_results <- distlist %>%
  purrr::map(function(x){
    run_mantel(x, dists = c("phylo.dist", "plant.dist", "spat.dist"),
               subsample = subsample, type = "partial", nboot=1000)
  }) %>%
  bind_rows(.id="dist") 

write_csv(pmantel_results %>% dplyr::select(-one_of("pval1","pval2")),
          "output/phylosymbiosis/pmantel_fulldata.csv")

## Plot mantels
gg.mantels <- bind_rows(mantel_results, pmantel_results) %>%
              dplyr::mutate(dist1 = case_when(
                str_detect(dist1, "plant.dist") ~ "Hostplant phylogeny",
                str_detect(dist1, "phylo.dist") ~ "Psyllid phylogeny",                
                str_detect(dist1, "spat.dist") ~ "Spatial distance"                   
              )) %>%
  filter(dist == "Aitchison") %>%
  mutate(dist1 = factor(dist1, levels= c("Spatial distance","Hostplant phylogeny",  "Psyllid phylogeny"))) %>%
  mutate(type = type %>%  
           str_replace("mantel", "Mantel") %>%
           str_replace("partial_Mantel", "Partial Mantel")) %>%
              ggplot(aes(x=mantelr, y=dist1, colour=dist1)) + 
    geom_vline(xintercept = 0, colour="black", linetype=2) +
  geom_pointrange(aes(xmin=`llim.2.5%`, xmax=`ulim.97.5%`), size=1) +
  scale_color_manual(values=c("Hostplant phylogeny"="#b2df8a","Spatial distance"="#a6cee3", "Psyllid phylogeny"="#1f78b4"))+
  facet_wrap(~type, ncol=2) +
  labs( x = "Mantel R", y = NULL, colour=NULL) +
  base_theme +
  theme(legend.position = "none",
        panel.grid.major = element_line())

gg.mantels

pdf(file="figs/fig3_mantels.pdf",  width = 8, height = 4, paper="a4r")
  plot(gg.mantels)
try(dev.off(), silent=TRUE)

Without Gammaproteobacteria

# Adonis test
metadata <- sample_data(ps2_subset) %>%
  as("data.frame")

adonis_results <- distlist_subset %>%
  purrr::map(function(x) {
    y <- as.dist(x[metadata$Sample_Name, metadata$Sample_Name])
    bind_rows(
    broom::tidy(adonis2(y~seqrun+psyllid_family+psyllid_genus+psyllid_spp, method="euclidean", data=metadata, 
                       permutations=999)) %>% 
      mutate(test = paste(term[!term %in% c("Residual", "Total")], collapse="-")),
    broom::tidy(adonis2(y~seqrun+hostplant_spp, method="euclidean", data=metadata,
                       permutations=999)) %>% 
      mutate(test = paste(term[!term %in% c("Residual", "Total")], collapse="-"))
    )
})  %>%
  bind_rows(.id="dist")

write_csv(adonis_results, "output/beta/adonis_subset.csv")

# Matrix correlations
#only use samples present in all
subsample <- Reduce(intersect, list(rownames(otu_table(ps2_subset)), colnames(phylo.dist), colnames(plant.dist), colnames(spat.dist)))

# Mantel test
mantel_results <- distlist_subset %>%
  purrr::map(function(x){
    run_mantel(x, dists=c("phylo.dist", "plant.dist", "spat.dist"),
               subsample=subsample, type="mantel")
  }) %>%
  bind_rows(.id="dist") 

write_csv(mantel_results %>% dplyr::select(-pval1, -pval2), "output/phylosymbiosis/mantel_subset.csv")

# Partial Mantel Test
pmantel_results <- distlist_subset %>%
  purrr::map(function(x){
    run_mantel(x, dists=c("phylo.dist", "plant.dist", "spat.dist"),
               subsample=subsample, type="partial")
  }) %>%
  bind_rows(.id="dist") 

write_csv(pmantel_results %>% dplyr::select(-pval1,-pval2), "output/phylosymbiosis/pmantel_subset.csv")

PCA plots

#set distance
pca_dist <- distlist$Aitchison

#PCA 
r.pcx <- prcomp(pca_dist)
pc_samp <- data.frame(Sample_Name = rownames(r.pcx$x), r.pcx$x[, 1:2])%>%
  left_join(sample_data(ps2) %>%
              as("matrix") %>%
              as.data.frame(), by="Sample_Name")
# calculate percent variance explained for the axis labels
pc1 <- round(r.pcx$sdev[1]^2/sum(r.pcx$sdev^2),2)
pc2 <- round(r.pcx$sdev[2]^2/sum(r.pcx$sdev^2),2)
pc_ylab <- paste("PC1: ", pc1, sep="")
pc_xlab <- paste("PC2: ", pc2, sep="")

## colour by psyllid phylogeny
dend <- as.dendrogram(force.ultrametric(pruned.tree))
membership <- as.data.frame(cutree(dend, k=11)) %>%
  rownames_to_column("psyllid_spp") %>%
  magrittr::set_colnames(c("psyllid_spp", "cluster"))

pca_phylo <- pc_samp %>%
  left_join(membership)
gg.pca <- ggplot(data=pca_phylo, aes(x=PC2, y=PC1, colour=as.factor(cluster))) + 
  geom_point(alpha=0.5, size=3) + #, shape=21, colour="black"
  #geom_point(data=pc_otu,aes(PC1, PC2)) +
  theme_classic() +
  scale_colour_brewer(palette="Paired") +
  geom_hline(yintercept = 0, linetype=2, alpha=0.5) +  
  geom_vline(xintercept = 0, linetype=2, alpha=0.5) +
  xlab(pc_xlab) + 
  ylab(pc_ylab) +
  theme(legend.position = "none") +
  scale_y_reverse(position = "right") 
  #coord_fixed(ratio=pc2/pc1) # Scale plot by variance explained

p1 <- ggtree(pruned.tree) +
    scale_x_continuous(expand=c(0, 0.2)) + 
  theme_tree2() +
  theme(legend.position = "none") 

colours_p1 <- p1$data %>%
  left_join(membership %>%
  dplyr::rename(label = psyllid_spp)
    ) %>%
  mutate(new_label = label %>% str_replace_all("_", " "))

p1 <- p1 %<+% colours_p1  + 
  geom_tippoint(aes(colour=as.factor(cluster)))  + 
  geom_tiplab(aes(colour=as.factor(cluster), label=new_label))+
  scale_colour_brewer(palette="Paired") 

gg.psyllid_pca <- p1 + gg.pca + plot_annotation(title="Psyllid phylogenetic distance")
gg.psyllid_pca

pdf(file="figs/psyllid_pca.pdf",  width = 8, height = 11, paper="a4")
  plot(gg.psyllid_pca)
try(dev.off(), silent=TRUE)
  
# Colour by plant phylogeny
plant.tree <- read.tree("sample_data/plant_tree.nwk")
plant.tree  <- drop.tip(plant.tree, "Sophora_microphylla_-kowhai" )
plant.tree <- multi2di(plant.tree)

dend <- as.dendrogram(plant.tree )
#test <- color_branches(dend, k=12)
#plot(test)
membership <- as.data.frame(cutree(dend, k=12)) %>%
  rownames_to_column("hostplant_spp") %>%
  magrittr::set_colnames(c("hostplant_spp", "cluster"))

pca_phylo <- pc_samp %>%
  left_join(membership)
gg.pca <- ggplot(data=pca_phylo, aes(x=PC2, y=PC1, colour=as.factor(cluster))) + 
  geom_point(alpha=0.5, size=3) + #, shape=21, colour="black"
  #geom_point(data=pc_otu,aes(PC1, PC2)) +
  theme_classic() +
  scale_colour_brewer(palette="Paired") +
  geom_hline(yintercept = 0, linetype=2, alpha=0.5) +  
  geom_vline(xintercept = 0, linetype=2, alpha=0.5) +
  xlab(pc_xlab) + 
  ylab(pc_ylab) +
  theme(legend.position = "none") +
  scale_y_reverse(position = "right")
  #coord_fixed(ratio=pc2/pc1) # Scale plot by variance explained

p2 <- ggtree(plant.tree) +
  scale_x_continuous(expand=c(0, 70)) + 
  theme_tree2() +
  theme(legend.position = "none") 

colours_p2 <- p2$data %>%
  left_join(membership %>%
  dplyr::rename(label = hostplant_spp)
    )%>%
  mutate(new_label = label %>% str_replace_all("_", " "))

p2 <- p2 %<+% colours_p2  + 
  geom_tippoint(aes(colour=as.factor(cluster)))  + 
  geom_tiplab(aes(colour=as.factor(cluster), label=new_label))+
  scale_colour_brewer(palette="Paired") 

gg.plant_pca <- p2 + gg.pca + plot_annotation(title="Plant phylogenetic distance")
gg.plant_pca

pdf(file="figs/plant_pca.pdf",  width = 8, height = 11, paper="a4")
  plot(gg.plant_pca)
try(dev.off(), silent=TRUE)

# HC of spatial distance
dend <- hclust(as.dist(spat.dist), method="average")
#test <- color_branches(dend, k=12)
#plot(test)

membership <- as.data.frame(cutree(dend, k=12)) %>%
  rownames_to_column("Sample_Name") %>%
  magrittr::set_colnames(c("Sample_Name", "cluster"))

pca_phylo <- pc_samp %>%
  left_join(membership)
gg.pca <- ggplot(data=pca_phylo, aes(x=PC2, y=PC1, colour=as.factor(cluster))) + 
  geom_point(alpha=0.5, size=3) + #, shape=21, colour="black"
  #geom_point(data=pc_otu,aes(PC1, PC2)) +
  theme_classic() +
  scale_colour_brewer(palette="Paired") +
  geom_hline(yintercept = 0, linetype=2, alpha=0.5) +  
  geom_vline(xintercept = 0, linetype=2, alpha=0.5) +
  xlab(pc_xlab) + 
  ylab(pc_ylab) +
  theme(legend.position = "none") +
  scale_y_reverse(position = "right")
  #coord_fixed(ratio=pc2/pc1) # Scale plot by variance explained

p3 <- ggtree(as.phylo(dend) )+
  scale_x_continuous(expand=c(0, 400)) +
  theme_tree2() +
  theme(legend.position = "none") 

colours_p3 <- p3$data %>%
  left_join(membership %>%
  dplyr::rename(label = Sample_Name)
    )%>%
  mutate(new_label = label %>% str_replace_all("_", " "))

p3 <- p3 %<+% colours_p3  + 
  geom_tippoint(aes(colour=as.factor(cluster)))  + 
  #geom_tiplab(aes(colour=as.factor(cluster), label=new_label))+
  scale_colour_brewer(palette="Paired") 

gg.spat_pca <- p3 + gg.pca + plot_annotation(title="Spatial Distance")
gg.spat_pca

pdf(file="figs/spatial_pca.pdf",  width = 8, height = 11, paper="a4")
  plot(gg.spat_pca)
try(dev.off(), silent=TRUE)

Cophylogeny

Run the scripts in the R subdirectory

cd /group/pathogens/Alexp/Metabarcoding/Psyllid_microbiome/paco_para
dos2unix batch_submit_paco.sh
find $(/usr/bin/ls -d $PWD/*) -name 'paco_*' -type f | grep -v '.rds' > sequence_index.txt
joblength=$(cat sequence_index.txt | wc -l)
sbatch --array=1-$joblength batch_submit_paco.sh

Microbiome 3 genera heatmap

filterfun1 <- function(x){
  x[(x / sum(x)) < (1e-4)] <- 0 #1e-4 is 0.01% threshold
  return(x)
}
ps3_filt  <- transform_sample_counts(ps3, fun = filterfun1)%>%
  filter_taxa(function(x) mean(x) > 0, TRUE) #Drop missing taxa from table

print(paste0((ntaxa(ps3)-ntaxa(ps3_filt)), " taxa under threshold removed"))

#Get co-occurance matrix
coocur <- ps3_filt %>%
    subset_samples(psyllid_genus %in% c("Powellia", "Ctenarytaina", "Psylla")) %>%
    filter_taxa(function(x) mean(x) > 0, TRUE) %>%
    otu_table %>%
    as.matrix() %>% 
  apply(2, function(x) ifelse(x > 0, 1, 0))

colnames(coocur) <- colnames(coocur) %>%
           str_replace_all(" |-", "_")
rownames(coocur) <- rownames(coocur) %>% 
           str_replace_all(" |-", "_")

# H cophenetic distance
h_tree <- pruned.tree
h_tree$tip.label <- h_tree$tip.label %>%
           str_replace_all(" |-", "_")
h_tree <- drop.tip(h_tree, setdiff(h_tree$tip.label, rownames(coocur)))

# P cophenetic distance
s_tree <- phy_tree(ps3)
s_tree$tip.label <- s_tree$tip.label %>%
           str_replace_all(" |-", "_")
s_tree <- drop.tip(s_tree, setdiff(s_tree$tip.label, colnames(coocur)))

coocur <- coocur[h_tree$tip.label, s_tree$tip.label]

# Read in Paco runs 
paco_run_powellia <- readRDS("output/cophylogeny/paco_powellia_microbe_filtered_asym.rds")
paco_run_cten <- readRDS("output/cophylogeny/paco_ctenarytaina_microbe_filtered_asym.rds")
paco_run_psylla <- readRDS("output/cophylogeny/paco_psylla_microbe_filtered_asym.rds")

paco_run_powellia$gof
paco_run_cten$gof
paco_run_psylla$gof

z_trans <- function(x){
  (x - mean(x, na.rm=TRUE)) / sd(x, na.rm=TRUE)
}

# Get link importance
links <- do.call("list", mget(grep("paco_run_",names(.GlobalEnv),value=TRUE))) %>%
  purrr::map(function(x){
    genus_name <- rownames(x$H)[1] %>% str_remove("_.*$")
    data.frame(
      joint=names(x$jackknife),
      values=unname(x$jackknife),
      #upper=unname(x$jackknife$upper),
      genus = genus_name
    )
  }) %>%
  bind_rows() %>%
 separate(joint, into=c("Sample_Name", "OTU"), sep="-", extra="merge", remove=FALSE) %>%
  group_by(genus) %>%
  mutate(values = values^2)%>%
  mutate(mean_val = mean(values)) %>%
  mutate(signif = case_when(
    values < mean_val ~ 1,
    values > mean_val ~ 0
  ))%>%
  group_by(genus) %>%
  mutate(
    #st_dev = sd(values),
    # z_values =  values/st_dev
    z_values =  z_trans(values)
    ) %>%
  ungroup()

# Plot links
links %>%
  dplyr::rename(label = joint) %>%
  mutate(label = as.factor(label),
         label=reorder_within(label, values, genus))%>%
  #arrange(-values) %>%
  ggplot(aes(x=label, y=values, colour=as.factor(signif))) + #
  geom_point(show.legend = FALSE) +
  facet_wrap(~genus, scales = "free")+
  #geom_errorbar(aes(ymin=values, ymax=upper)) +
  geom_hline(aes(yintercept = mean_val), lty=3) +
  scale_x_reordered()+
  scale_color_manual(values=c("steelblue", "darkorange1"), name = "Significant") +
  theme_classic()+ 
  theme(axis.text.x = element_blank(), legend.position = "right")

#Get observed residuals of Procrustean superimposition 
paco_residuals <- do.call("list", mget(grep("paco_run_",names(.GlobalEnv),value=TRUE))) %>%
  purrr::map(function(x){
    res <- residuals_paco(x$proc, type = "interaction")
    genus_name <- rownames(x$H)[1] %>% str_remove("_.*$")
    data.frame(
      OTU=names(res),
      values=unname(res),
      genus=genus_name
    ) 
  })%>%
  bind_rows() 

# Plot residuals
ggplot(paco_residuals, aes(x=values))+
  geom_density(fill='grey70')+
  theme_bw()+
  facet_grid(genus~.) +
  xlab('Procrustes residuals')+
  ylab('Frequency')

# Parafit run
PF_run_powellia <- readRDS("output/cophylogeny/parafit_powellia_microbe_filtered.rds")
PF_run_cten <- readRDS("output/cophylogeny/parafit_ctenarytaina_microbe_filtered.rds")
PF_run_psylla <- readRDS("output/cophylogeny/parafit_psylla_microbe_filtered.rds")

PF_run_powellia$ParaFitGlobal
PF_run_powellia$p.global

PF_run_cten$ParaFitGlobal
PF_run_cten$p.global

PF_run_psylla$ParaFitGlobal
PF_run_psylla$p.global

# Joining isnt working here because they were made separately - so getting the rownames from cooccur no logner works
PF_links <- do.call("list", mget(grep("PF_run_",names(.GlobalEnv),value=TRUE))) %>%
  purrr::map(function(x){
    genus_name <- names(x$para.per.host[1]) %>% str_remove("_.*$")
    as.data.frame(x$link.table) %>%
      left_join(enframe(names(x$para.per.host), name = "Host", value="psyllid_spp") %>%
                  mutate(Host = as.numeric(Host))) %>%
      left_join(enframe(names(x$host.per.para), name = "Parasite", value="OTU") %>%
                  mutate(Parasite = as.numeric(Parasite))) %>%
      mutate(genus = genus_name)
  }) %>%
  bind_rows() %>%
  mutate(signif = case_when(
    p.F1 < 0.05 ~ 1,
    p.F1 > 0.05 ~ 0
  )) %>%
  group_by(genus) %>%
  mutate(z_values =  z_trans(F1.stat)) %>%
  ungroup()

# Proportion of significant links
PF_links %>%
  group_by(genus) %>%
  summarise(s = count(signif > 0), ns = count(signif == 0)) 

# Cophyloplot
coocur.lut <- which(coocur ==1, arr.ind=TRUE)
assoc <- cbind(rownames(coocur)[coocur.lut[,1]], colnames(coocur)[coocur.lut[,2]])

# Rotate the nodes using phytools
library(phytools)
obj <- cophylo(tr1=h_tree, tr2=s_tree, assoc=assoc, rotate=FALSE) 

tree1 <- obj[["trees"]][[1]]
p1 <- ggtree(tree1, ladderize=FALSE, aes(colour=links))
weights_p1 <- p1$data %>%
  left_join(links %>%
    group_by(Sample_Name) %>%
    summarise(values = mean(signif)) %>%
    #summarise(values = mean(z_values)) %>%
    dplyr::rename(label = Sample_Name)
    )  

## Get values for higher nodes
weights_p1 <- weights_p1 %>%
  left_join(data.frame(node=weights_p1$node, links = weights_p1$node %>% purrr::map_dbl(average_descendants, tree=tree1, df=weights_p1)))

p1 <- p1 %<+% weights_p1 +
  scale_color_gradient(low="steelblue", high="darkorange1") +
  #  scale_colour_gradient(high="steelblue", low="darkorange1", trans="log10") +
    theme(legend.position = "none")

# OTU tree
tree2 <- obj[["trees"]][[2]]

# make a clade label list
tax_groups <- as.data.frame(tax_table(ps3)) %>%
  rownames_to_column("label") %>%
  dplyr::mutate(label = label %>% str_replace_all("-", "_") %>%
                  str_replace_all(" ", "_")) %>%
  dplyr::select(label, genus) %>%
  filter(label %in% tree2$tip.label) %>%
  group_by(genus) 

group_name <- group_keys(tax_groups)  %>%
  mutate(group_name = genus %>% str_remove_all("\\[|\\]"))

cls <- tax_groups %>% 
  group_split() %>% 
  purrr::map(pull, label) %>%
  set_names(group_name$group_name)

newtree <- groupOTU(tree2, cls)
p2 <- ggtree(newtree, ladderize=TRUE, aes(colour=links)) 
weights_p2 <- p2$data %>%
  left_join(links %>% 
    group_by(OTU) %>%
    summarise(values = mean(signif)) %>%
    #summarise(values = mean(z_values)) %>%
    dplyr::rename(label = OTU)
    ) 

#Should be able to use castor to make this faster
weights_p2 <- weights_p2 %>%
  left_join(data.frame(node=weights_p2$node, links = weights_p2$node %>% purrr::map_dbl(average_descendants, tree=tree2, df=weights_p2)))

p2 <- p2 %<+% weights_p2  +  
  #scale_colour_gradient(high="steelblue", low="darkorange1", trans="log10") +
  scale_color_gradient(low="steelblue", high="darkorange1") +
  theme(legend.position = "none")

# Plot heatmaps
heatmap_dat <- obj$assoc %>%
  as_data_frame() %>%
  magrittr::set_colnames(c("label.x", "label.y")) %>%
  mutate(genus = label.x %>% str_remove("_.*$")) %>%
  left_join(links %>% 
              dplyr::select(label.x = Sample_Name, label.y = OTU, signif_paco=signif, val_paco = z_values)) %>%
  left_join(PF_links %>% 
              dplyr::select(label.x = psyllid_spp, label.y = OTU, signif_para=signif, val_para = z_values)) %>%
  left_join(p1$data %>% dplyr::select(label, y) %>% dplyr::rename(label.x = label), by="label.x") %>%
  left_join(p2$data %>% dplyr::select(label, y) %>% dplyr::rename(label.y = label), by="label.y") %>%
  rownames_to_column("assoc") %>%
  dplyr::rename(Sample_Name = label.x,
                OTU = label.y,
                pos_x = y.x,
                pos_y = y.y) %>%
  mutate(signif_paco = replace_na(signif_paco, 0),
         signif_para = replace_na(signif_para, 0)) %>%
  mutate(highlight = case_when(
    signif_paco==0 & signif_para==0  ~ "NS",
    signif_paco==0 & signif_para==1 ~ "para",
    signif_paco==1 & signif_para==0 ~ "paco",
    signif_paco==1 & signif_para==1 ~ "both"
  )) 

gg.heatmap <- heatmap_dat %>%
    mutate(OTU = OTU %>% str_replace_all("_", " "),
         Sample_Name =  Sample_Name %>% str_replace_all("_"," ")) %>%
  dplyr::select(Sample_Name, OTU, genus, highlight, pos_x, pos_y, val_para, val_paco) %>%
  dplyr::mutate(Sample_Name = factor(Sample_Name),
                OTU = factor(OTU),
                genus=factor(genus, levels=c("Psylla" ,"Powellia", "Ctenarytaina")))  %>%
   ggplot(aes(x= fct_reorder(Sample_Name, pos_x), y=fct_reorder(OTU, pos_y), fill=highlight )) +
  geom_tile() +
  theme_bw() +
  facet_grid(~genus, drop = TRUE, scales="free", space ="free")+
  theme(axis.text.x = element_text(angle=45, hjust=1),
        axis.title.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        axis.title.y = element_blank(),
        legend.position = "none") +
  scale_y_discrete(expand=c(0,0))+
  scale_y_discrete(expand=c(0,0)) +
  #scale_fill_gradient(high="steelblue", low="darkorange1") 
  scale_fill_manual(values=c("NS"="steelblue","paco"="darkorange1","para"="#da2b91", "both"="#91da2b"), na.translate=FALSE) 

gg.heatmap

# Density plot of mismatch
density_dat <-  heatmap_dat %>%
  left_join(p2$data %>% dplyr::select(OTU = label, y)) %>%
  group_by(OTU, y) %>%
  summarise(sum_paco = sum(signif_paco, na.rm=TRUE), sum_para = sum(signif_para, na.rm=TRUE)) %>%
  mutate(total_sum = sum(sum_paco, sum_para, na.rm=TRUE)) %>%
  #summarise(total_sum = mean(val_paco, na.rm=TRUE)) %>%
  ungroup()

density_labels <- density_dat %>%
  left_join(tax_table(ps3) %>%
              as("matrix") %>%
              as_tibble(rownames="OTU") %>% 
              mutate(OTU = OTU %>% str_replace_all(" |-", "_")))%>%
  arrange(y) 
  
chunk = 50
n <- nrow(density_labels)
r  <- rep(1:ceiling(n/chunk),each=chunk)[1:n]
density_labels_chunked <- split(density_labels,r)

density_labels <- density_labels_chunked %>% 
  purrr::map(function(x){
    df <- x %>%
      mutate(zscore = (total_sum - mean(total_sum, na.rm=TRUE))/sd(total_sum, na.rm=TRUE)) %>%
      filter( total_sum > 3, zscore  > 3,) #, 
    if(nrow(df) > 0){
        out <- df %>%
        summarise(y = mean(y), total_sum  = max(total_sum), annot =  case_when(
        length(unique(genus)) == 1 ~ names(which.max(table(genus))),
        length(unique(genus)) > 1~  names(which.max(table(family)))))
        return(out)
    }
  }) %>%
  bind_rows() 

gg.density <- density_dat %>%
  filter(total_sum > 0) %>%
  ggplot(aes(x = y, y=total_sum, colour=total_sum)) +
  geom_point(size=0.01, alpha=1)+
  scale_color_gradient(low="steelblue", high="darkorange1") +
    geom_text(data=density_labels, aes(label = annot), hjust=0) +
  scale_x_continuous(expand=c(0,0)) +
  scale_y_continuous(expand=c(0,0)) +
  theme_void() +
  theme(legend.position = "none")+
  coord_flip()

gg.density

# Instead of density could i label lineages with the greatest value?

top <- wrap_elements(grid::textGrob('')) +(p1+ coord_flip() + scale_x_reverse(expand=c(0,0))+ scale_y_continuous(expand=c(0,0))) + wrap_elements(grid::textGrob('')) +plot_layout(widths=c(0.5,3, 0.1)) 

bottom <- p2+ scale_y_continuous(expand=c(0,0)) + gg.heatmap + gg.density + plot_layout(widths=c(0.5,3, 0.1))

gg.treemap <- top / bottom + plot_layout(heights=c(0.5,3))  

gg.treemap

pdf(file="figs/3genus_heatmaps.pdf",  width = 8, height = 11, paper="a4")
  plot(gg.treemap)
try(dev.off(), silent=TRUE)
  
# Make plot ranking links, and nodes

gg.host_links <- heatmap_dat %>%
  dplyr::rename(label = Sample_Name) %>%
  drop_na() %>%
  group_by(label) %>%
  summarise(signif_paco = sum(signif_paco), signif_para= sum(signif_para)) %>%
  pivot_longer(starts_with("signif_"),
               names_to="type",
               values_to="values") %>%
  mutate(type = type %>% str_remove("signif_")) %>%
    mutate(label = label %>% 
             str_replace_all("_", " ") %>%
             str_replace(" sp", " sp."))%>%
  mutate(label = as.factor(label))%>%
  filter(values > 0) %>%
  arrange(-values) %>%
  ggplot(aes(x=fct_reorder(label, values, sum ), y=values, fill=type)) +
  geom_col() +  
  scale_fill_manual(values=c("NS"="steelblue","paco"="darkorange1","para"="#da2b91", "both"="#91da2b"), na.translate=FALSE)+
  theme_classic()+ 
  theme(axis.text.y = element_text(face="italic"),
        legend.position = "bottom")+
  labs(y = "Number of Signficant links", x=NULL, title ="Psyllid taxa") + 
  coord_flip()

gg.sym_links <- heatmap_dat %>%
  dplyr::rename(label = OTU) %>%
  left_join(tax_groups %>% dplyr::rename(otu_genus = genus)) %>%
  drop_na() %>%
  group_by(otu_genus) %>%
  summarise(signif_paco = sum(signif_paco), signif_para= sum(signif_para)) %>%
  pivot_longer(starts_with("signif_"),
               names_to="type",
               values_to="values") %>%
  dplyr::rename(label = otu_genus) %>%
  dplyr::mutate(label = label %>% 
                  str_remove("\\/.*$") %>%
                  str_replace("NA_canariense", "Bradyrhizobium_canariense") %>%
                  str_remove("^s__")
                  ) %>%
  mutate(type = type %>% str_remove("signif_")) %>%
  mutate(label = as.factor(label),
         label=fct_reorder(label, values, sum ))%>%
  group_by(label) %>%
  mutate(total = sum(values)) %>%
  ungroup() %>%
   filter(total > 0) %>%
  top_n(80, total) %>%
  arrange(-values) %>%
  ggplot(aes(x=label, y=values, fill=type)) +
  geom_col() +  
  scale_fill_manual(values=c("NS"="steelblue","paco"="darkorange1","para"="#da2b91", "both"="#91da2b"), na.translate=FALSE)+
  theme_classic()+ 
  theme(axis.text.y = element_text(face="italic"),
        legend.position = "bottom")+
  labs(y = "Number of Signficant links", x=NULL, title ="Microbial genera") + 
  coord_flip()

gg.host_links + gg.sym_links

pdf(file="figs/3genus_fit_summary.pdf",  width = 8, height = 11, paper="a4")
  plot(gg.host_links + gg.sym_links)
try(dev.off(), silent=TRUE)

Tanglegrams

Psyllid ~ Carsonella

#Flag top abundance carsonella by sample
top_carson <- ps2 %>%
  transform_sample_counts(function (x) x/sum(x)) %>%
  speedyseq::psmelt() %>%
  filter(psyllid_spp %in% pruned.tree$tip.label) %>%
  filter(genus=="Candidatus Carsonella") %>%
  group_by(Sample) %>%  
  filter(Abundance > 0) %>%
  top_n(1, wt=Abundance) %>%
  mutate(top = TRUE) 

coocur <- ps2 %>%
  transform_sample_counts(function (x) x/sum(x)) %>%
  speedyseq::psmelt() %>%
    filter(psyllid_spp %in% pruned.tree$tip.label) %>%
  left_join(top_carson) %>%
  filter(genus=="Candidatus Carsonella", top==TRUE) %>%
  dplyr::select(OTU, psyllid_spp, SampleID, Abundance) %>%
  mutate(OTU = OTU %>%
           str_replace_all(" |-", "_")) %>%
  dplyr::group_by(OTU, psyllid_spp) %>%
    summarise(Abundance = sum(Abundance)) %>%
  pivot_wider(id_cols = psyllid_spp,
              names_from = OTU,
              values_from=Abundance,
              values_fill = list(Abundance = 0))  %>%
  column_to_rownames("psyllid_spp") %>%
    as.matrix() %>% 
  apply(2, function(x) ifelse(x > 0, 1, 0)) 

# H cophenetic distance
h_tree <- pruned.tree
h_tree$tip.label <- h_tree$tip.label %>%
           str_replace_all(" |-", "_")
h_tree <- drop.tip(h_tree, setdiff(h_tree$tip.label, rownames(coocur)))
h_dist <- sqrt(cophenetic(h_tree))

# P cophenetic distance
s_tree <- phy_tree(ps3)
s_tree$tip.label <- s_tree$tip.label %>%
           str_replace_all(" |-", "_")
s_tree <- drop.tip(s_tree, setdiff(s_tree$tip.label, colnames(coocur)))
s_dist <- sqrt(cophenetic(s_tree)/1e+6 ) ##convert to Mya so integers are small enough for PACO

coocur <- coocur[h_tree$tip.label, s_tree$tip.label]

# prepare paco data
D <- prepare_paco_data(H=h_dist, P=s_dist, HP=coocur)

# Add pcord
D <- add_pcoord(D, correction='none') 
p_host <- ggplot(D$H_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2)) +
    geom_point() +
    theme_bw() +
  ggtitle("H PCA")
p_para <- ggplot(D$P_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2))  +
    geom_point() +
    theme_bw()+
  ggtitle("P PCA")
    
plot(p_host + p_para)

# run paco
paco_run <- PACo(D, nperm=999, seed=909, method='quasiswap', symmetric=FALSE)

#print overall significance
print(paco_run$gof)

# Get interaction-specific cophylogenetic contributions using jacknifing
paco_run <- paco_links(paco_run)
links <- data.frame(joint=names(paco_run$jackknife), #losing sv50 here?
                    values=unname(paco_run$jackknife)#,
                    #upper=unname(paco_run$jackknife)
                    ) %>%
  mutate(OTU = joint) %>%
 separate(OTU, into=c("Sample_Name", "OTU"), sep="-", extra="merge") %>%
  mutate(signif = case_when(
    values < mean(.$values) ~ 1,
    values > mean(.$values) ~ 0
  ))

# Plot links
links %>%
  dplyr::rename(label = joint) %>%
  mutate(label = as.factor(label),
         label=fct_reorder(label, values, sum))%>%
  arrange(-values) %>%
  ggplot(aes(x=label, y=values, colour=as.factor(signif))) +
  geom_point(show.legend = FALSE) +
  #geom_errorbar(aes(ymin=values, ymax=upper)) +
  geom_hline(yintercept = mean(links$values)) +
  scale_color_manual(values=c("steelblue", "darkorange1")) +
  theme_classic()+ 
  theme(axis.text.x = element_blank())

dir.create("output/cophylogeny/psyllid_carsonella")
write_csv(links, "output/cophylogeny/psyllid_carsonella/psyllid_carsonella_links.csv")
write_csv(links %>% 
    group_by(OTU) %>%
    summarise(values = mean(values)), "output/cophylogeny/psyllid_carsonella/carsonella_weights.csv")
write_csv(links %>% 
    group_by(Sample_Name) %>%
    summarise(values = mean(values)), "output/cophylogeny/psyllid_carsonella/psyllid_weights.csv")

#Get observed residuals of Procrustean superimposition 
paco_residuals <- residuals_paco(paco_run$proc, type = "interaction")

# Visualise residuals
res <- data.frame(OTU=names(paco_residuals), values=unname(paco_residuals)) %>%
  separate(OTU, into=c("Sample_Name", "OTU"), sep="-", extra="merge") 
ggplot(res, aes(x=values))+
  geom_density(fill='grey70')+
  theme_bw()+
  xlab('Procrustes residuals')+
  ylab('Frequency')

# Parafit run
PF_run <- parafit(h_dist, s_dist,coocur, nperm=999, test.links=TRUE)
PF_run$ParaFitGlobal
PF_run$p.global

PF_links <- as.data.frame(PF_run$link.table)  %>%
      left_join(enframe(names(PF_run$para.per.host), name = "Host", value="psyllid_spp") %>%
                  mutate(Host = as.numeric(Host))) %>%
      left_join(enframe(names(PF_run$host.per.para), name = "Parasite", value="OTU") %>%
                  mutate(Parasite = as.numeric(Parasite)))%>%
  mutate(signif = case_when(
    p.F1 < 0.05 ~ 1,
    p.F1 > 0.05 ~ 0
  )) 

# Cophyloplot
coocur.lut <- which(coocur ==1, arr.ind=TRUE)
assoc <- cbind(rownames(coocur)[coocur.lut[,1]], colnames(coocur)[coocur.lut[,2]])

# Rotate the nodes using phytools
obj <- cophylo(tr1=h_tree, tr2=s_tree, assoc=assoc, rotate=TRUE) 

# psyllid_tree
tree1 <- obj[["trees"]][[1]]

p1 <- ggtree(tree1)
atmeto_node <- p1$data %>% 
  filter(str_detect(label, "Atmeto")) %>%
  pull(node)
root_node <- p1$data %>% 
  filter(str_detect(label, "Atmeto")) %>%
  pull(parent)

tree1 <- groupOTU(tree1, .node=atmeto_node) # Make the atmeto dotted to indicate outgroup was rescaled

p1 <- ggtree(tree1, ladderize=FALSE, aes(colour=links,linetype=group))
weights_p1 <- p1$data %>%
  left_join(links %>% 
    group_by(Sample_Name) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = Sample_Name)
    )%>%
  left_join(PF_links %>% 
    group_by(psyllid_spp) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = psyllid_spp)
    ) %>%
  mutate(values = PA_values + PF_values)

#Scale the atmetocranium and root nodes to be shorter
p1$data[p1$data$node %in% atmeto_node, "x"] <- max(p1$data$x)
p1$data[p1$data$node %in% root_node, "x"] <- 0.2 #root

p1$data$node[p1$data$node]

## Get values for higher nodes
weights_p1 <- weights_p1 %>%
  left_join(data.frame(node=weights_p1$node, links = weights_p1$node %>% purrr::map_dbl(average_descendants, tree=tree1, df=weights_p1)))
p1 <- p1 %<+% weights_p1 + geom_tippoint(aes(colour=links)) +
  scale_color_gradient(low="steelblue", high="darkorange1")  +
  theme(legend.position = "none")

# OTU tree
tree2 <- obj[["trees"]][[2]]
s_tree <- drop.tip(tree2, setdiff(tree2$tip.label, obj$assoc[,2]))
p2 <- ggtree(tree2 , ladderize=FALSE, aes(colour=links)) 
weights_p2 <- p2$data %>%
  left_join(links %>% 
    group_by(OTU) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = OTU)
    )%>%
  left_join(PF_links %>% 
    group_by(OTU) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = OTU)
    ) %>%
  mutate(values = PA_values + PF_values)

weights_p2 <- weights_p2 %>%
  left_join(data.frame(node=weights_p2$node, links = weights_p2$node %>% purrr::map_dbl(average_descendants, tree=tree2, df=weights_p2)))

p2 <- p2 %<+% weights_p2  + geom_tippoint(aes(colour=links)) + 
  scale_color_gradient(low="steelblue", high="darkorange1") +
  theme(legend.position = "none")

# Tanglegram 
tangle <- obj$assoc %>%
  as_data_frame() %>%
  magrittr::set_colnames(c("label.x", "label.y")) %>%
  left_join(links %>% 
              dplyr::select(label.x = Sample_Name, label.y = OTU, signif_paco=signif)) %>%
  left_join(PF_links %>% 
              dplyr::select(label.x = psyllid_spp, label.y = OTU, signif_para=signif)) %>%
  left_join(p1$data %>% dplyr::select(label, y) %>% dplyr::rename(label.x = label), by="label.x") %>%
  left_join(p2$data %>% dplyr::select(label, y) %>% dplyr::rename(label.y = label), by="label.y") %>%
  rownames_to_column("assoc") %>%
  rename_all(~str_replace(.x,pattern="\\.", replacement="_")) %>%
  pivot_longer(ends_with(c("_x", "_y")),
               names_to=c(".value", "tree"), 
               names_sep = "_"
              )  %>%
  mutate(signif_paco = replace_na(signif_paco, 0),
         signif_para = replace_na(signif_para, 0)) %>%
  mutate(signif = case_when(
    signif_paco==0 & signif_para==0  ~ "NS",
    signif_paco==0 & signif_para==1 ~ "para",
    signif_paco==1 & signif_para==0 ~ "paco",
    signif_paco==1 & signif_para==1 ~ "both"
  )) %>%
  mutate(tree = tree %>% 
           str_replace("x", "host")%>%
           str_replace("y", "microbe")) %>%
  filter(!is.na(label))%>% 
  group_by(tree) %>%
  mutate(y = y / max(y)) %>%
  mutate(label = label %>% str_replace_all("_", " ")) 


gg.tangle <- ggplot(tangle, aes(x=tree, y=y, group=assoc, colour=as.factor(signif))) +
    geom_line(alpha=0.8) +  
  geom_text(data = tangle %>% 
              filter(tree=="host"),
            aes(label=label),stat = 'unique', hjust=1, check_overlap = TRUE)+
  geom_text(data = tangle %>% 
              filter(tree=="microbe"),
            aes(label=label),stat = 'unique', hjust=0, check_overlap = TRUE)+
  scale_colour_manual(values=c("NS"="steelblue", "paco"="darkorange1", "para"="#da2b91", "both"="#91da2b"), na.translate=FALSE)+
    scale_x_discrete(expand = expansion(add=c(0.8,0.8))) + 
    theme_void() +
    scale_y_continuous(expand=c(0.005,0.005))+
    theme(legend.position = "bottom") +
  labs(colour="Significance:")

gg.carson_tangle <- p1 + gg.tangle + (p2 + scale_x_reverse()) #+ plot_layout(widths = c(2, 1, 2))
gg.carson_tangle 

pdf(file="figs/carsonella_tanglegram.pdf",  width = 8, height = 11, paper="a4")
  plot(gg.carson_tangle)
try(dev.off(), silent=TRUE)

Psyllid ~ Sodalis

#Flag top abundance sodalis by sample
top_sodalis <- ps2 %>%
  transform_sample_counts(function (x) x/sum(x)) %>%
  speedyseq::psmelt() %>%
  filter(psyllid_spp %in% pruned.tree$tip.label) %>%
  filter(genus=="Sodalis") %>%
  group_by(Sample) %>%  
  filter(Abundance > 0) %>%
  top_n(1, wt=Abundance) %>%
  mutate(top = TRUE) 

coocur <- ps2 %>%
  transform_sample_counts(function (x) x/sum(x)) %>%
  speedyseq::psmelt() %>%
  filter(psyllid_spp %in% pruned.tree$tip.label) %>%
  left_join(top_sodalis) %>%
  filter(genus=="Sodalis", top==TRUE) %>%
  dplyr::select(OTU, psyllid_spp, SampleID, Abundance) %>%
  mutate(OTU = OTU %>%
           str_replace_all(" |-", "_")) %>%
  dplyr::group_by(OTU, psyllid_spp) %>%
    summarise(Abundance = sum(Abundance)) %>%
  pivot_wider(id_cols = psyllid_spp,
              names_from = OTU,
              values_from=Abundance,
              values_fill = list(Abundance = 0))  %>%
  column_to_rownames("psyllid_spp") %>%
    as.matrix() %>% 
  apply(2, function(x) ifelse(x > 0, 1, 0)) 

coocur <- coocur[ rowSums(coocur) > 0,colSums(coocur) > 0]

# H cophenetic distance
h_tree <- pruned.tree
h_tree$tip.label <- h_tree$tip.label %>%
           str_replace_all(" |-", "_")
h_tree <- drop.tip(h_tree, setdiff(h_tree$tip.label, rownames(coocur)))
h_dist <- sqrt(cophenetic(h_tree))

# P cophenetic distance
s_tree <- phy_tree(ps3)
s_tree$tip.label <- s_tree$tip.label %>%
           str_replace_all(" |-", "_")
s_tree <- drop.tip(s_tree, setdiff(s_tree$tip.label, colnames(coocur)))
s_dist <- sqrt(cophenetic(s_tree)/1e+6 ) ##convert to Mya so integers are small enough for PACO

#alternatively - sqrt(cophenetic(s_tree))
coocur <- coocur[h_tree$tip.label, s_tree$tip.label]

# prepare paco data
D <- prepare_paco_data(H=h_dist, P=s_dist, HP=coocur)
# Add pcord
D <- add_pcoord(D, correction='none') 

p_host <- ggplot(D$H_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2)) +
    geom_point() +
    theme_bw() +
  ggtitle("H PCA")

p_para <- ggplot(D$P_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2))  +
    geom_point() +
    theme_bw()+
  ggtitle("P PCA")
    
plot(p_host + p_para)

# run paco
paco_run <- PACo(D, nperm=999, seed=909, method='quasiswap', symmetric=TRUE)

#print overall significance
print(paco_run$gof)

# Get interaction-specific cophylogenetic contributions using jacknifing
paco_run <- paco_links(paco_run)
links <- data.frame(joint=names(paco_run$jackknife), #losing sv50 here?
                    values=unname(paco_run$jackknife)#,
                    #upper=unname(paco_run$jackknife$upper)
                    ) %>%
  mutate(OTU = joint) %>%
 separate(OTU, into=c("Sample_Name", "OTU"), sep="-", extra="merge") %>%
  mutate(signif = case_when(
    values < mean(.$values) ~ 1,
    values > mean(.$values) ~ 0
  ))

# Plot links
links %>%
  dplyr::rename(label = joint) %>%
  mutate(label = as.factor(label),
         label=fct_reorder(label, values, sum))%>%
  arrange(-values) %>%
  ggplot(aes(x=label, y=values, colour=as.factor(signif))) +
  geom_point(show.legend = FALSE) +
 # geom_errorbar(aes(ymin=values, ymax=upper)) +
  geom_hline(yintercept = mean(links$values)) +
  scale_color_manual(values=c("steelblue", "darkorange1")) +
  theme_classic()+ 
  theme(axis.text.x = element_blank())

dir.create("output/cophylogeny/psyllid_secondary")
write_csv(links, "output/cophylogeny/psyllid_secondary/psyllid_secondary_links.csv")
write_csv(links %>% 
    group_by(OTU) %>%
    summarise(values = mean(values)), "output/cophylogeny/psyllid_secondary/secondary_symbiont_weights.csv")
write_csv(links %>% 
    group_by(Sample_Name) %>%
    summarise(values = mean(values)), "output/cophylogeny/psyllid_secondary/psyllid_weights.csv")

#Get observed residuals of Procrustean superimposition 
paco_residuals <- residuals_paco(paco_run$proc, type = "interaction")
# Visualise residuals
res <- data.frame(OTU=names(paco_residuals), values=unname(paco_residuals)) %>%
  separate(OTU, into=c("Sample_Name", "OTU"), sep="-", extra="merge") 

ggplot(res, aes(x=values))+
  geom_density(fill='grey70')+
  theme_bw()+
  xlab('Procrustes residuals')+
  ylab('Frequency')

# Parafit run
PF_run <- parafit(h_dist, s_dist,coocur, nperm=999, test.links=TRUE)
PF_run$ParaFitGlobal
PF_run$p.global

PF_links <- as.data.frame(PF_run$link.table)  %>%
      left_join(enframe(names(PF_run$para.per.host), name = "Host", value="psyllid_spp") %>%
                  mutate(Host = as.numeric(Host))) %>%
      left_join(enframe(names(PF_run$host.per.para), name = "Parasite", value="OTU") %>%
                  mutate(Parasite = as.numeric(Parasite)))%>%
  mutate(signif = case_when(
    p.F1 < 0.05 ~ 1,
    p.F1 > 0.05 ~ 0
  )) 

# Cophyloplot
coocur.lut <- which(coocur ==1, arr.ind=TRUE)
assoc <- cbind(rownames(coocur)[coocur.lut[,1]], colnames(coocur)[coocur.lut[,2]])

# Rotate the nodes using phytools
library(phytools)
obj <- cophylo(tr1=h_tree, tr2=s_tree, assoc=assoc, rotate=TRUE) 

# psyllid_tree
tree1 <- obj[["trees"]][[1]]

p1 <- ggtree(tree1)
atmeto_node <- p1$data %>% 
  filter(str_detect(label, "Atmeto")) %>%
  pull(node)
root_node <- p1$data %>% 
  filter(str_detect(label, "Atmeto")) %>%
  pull(parent)

tree1 <- groupOTU(tree1, .node=atmeto_node) # Make the atmeto dotted to indicate outgroup was rescaled

p1 <- ggtree(tree1, ladderize=FALSE, aes(colour=links,linetype=group))
weights_p1 <- p1$data %>%
  left_join(links %>% 
    group_by(Sample_Name) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = Sample_Name)
    )%>%
  left_join(PF_links %>% 
    group_by(psyllid_spp) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = psyllid_spp)
    ) %>%
  mutate(values = PA_values + PF_values)

#Scale the atmetocranium and root nodes to be shorter
p1$data[p1$data$node %in% atmeto_node, "x"] <- max(p1$data$x)
p1$data[p1$data$node %in% root_node, "x"] <- 0.2 #root

p1$data$node[p1$data$node]

## Get values for higher nodes
weights_p1 <- weights_p1 %>%
  left_join(data.frame(node=weights_p1$node, links = weights_p1$node %>% purrr::map_dbl(average_descendants, tree=tree1, df=weights_p1)))
p1 <- p1 %<+% weights_p1 + geom_tippoint(aes(colour=links)) +
  scale_color_gradient(low="steelblue", high="darkorange1")  +
  theme(legend.position = "none")

# OTU tree
tree2 <- obj[["trees"]][[2]]
s_tree <- drop.tip(tree2, setdiff(tree2$tip.label, obj$assoc[,2]))
p2 <- ggtree(tree2 , ladderize=FALSE, aes(colour=links)) 
weights_p2 <- p2$data %>%
  left_join(links %>% 
    group_by(OTU) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = OTU)
    )%>%
  left_join(PF_links %>% 
    group_by(OTU) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = OTU)
    ) %>%
  mutate(values = PA_values + PF_values)

weights_p2 <- weights_p2 %>%
  left_join(data.frame(node=weights_p2$node, links = weights_p2$node %>% purrr::map_dbl(average_descendants, tree=tree2, df=weights_p2)))

p2 <- p2 %<+% weights_p2  + geom_tippoint(aes(colour=links)) + 
  scale_color_gradient(low="steelblue", high="darkorange1") +
  theme(legend.position = "none")

# Tanglegram 
tangle <- obj$assoc %>%
  as_data_frame() %>%
  magrittr::set_colnames(c("label.x", "label.y")) %>%
  left_join(links %>% 
              dplyr::select(label.x = Sample_Name, label.y = OTU, signif_paco=signif)) %>%
  left_join(PF_links %>% 
              dplyr::select(label.x = psyllid_spp, label.y = OTU, signif_para=signif)) %>%
  left_join(p1$data %>% dplyr::select(label, y) %>% dplyr::rename(label.x = label), by="label.x") %>%
  left_join(p2$data %>% dplyr::select(label, y) %>% dplyr::rename(label.y = label), by="label.y") %>%
  rownames_to_column("assoc") %>%
  rename_all(~str_replace(.x,pattern="\\.", replacement="_")) %>%
  pivot_longer(ends_with(c("_x", "_y")),
               names_to=c(".value", "tree"), 
               names_sep = "_"
              )  %>%
  mutate(signif_paco = replace_na(signif_paco, 0),
         signif_para = replace_na(signif_para, 0)) %>%
  mutate(signif = case_when(
    signif_paco==0 & signif_para==0  ~ "NS",
    signif_paco==0 & signif_para==1 ~ "para",
    signif_paco==1 & signif_para==0 ~ "paco",
    signif_paco==1 & signif_para==1 ~ "both"
  )) %>%
  mutate(tree = tree %>% 
           str_replace("x", "host")%>%
           str_replace("y", "microbe")) %>%
  filter(!is.na(label))%>% 
  group_by(tree) %>%
  mutate(y = y / max(y))%>%
  mutate(label = label %>% str_replace_all("_", " ")) 


gg.tangle <- ggplot(tangle, aes(x=tree, y=y, group=assoc, colour=as.factor(signif))) +
    geom_line(alpha=0.8) +  
  geom_text(data = tangle %>% 
              filter(tree=="host"),
            aes(label=label),stat = 'unique', hjust=1, check_overlap = TRUE)+
  geom_text(data = tangle %>% 
              filter(tree=="microbe"),
            aes(label=label),stat = 'unique', hjust=0, check_overlap = TRUE)+
  scale_colour_manual(values=c("NS"="steelblue", "paco"="darkorange1", "para"="#da2b91", "both"="#91da2b"), na.translate=FALSE)+
    scale_x_discrete(expand = expansion(add=c(0.8,0.8))) + 
    theme_void() +
    scale_y_continuous(expand=c(0.01,0.01))+
    theme(legend.position = "bottom") +
  labs(colour="Significance:")

gg.sodalis_tangle <- p1 + gg.tangle + (p2 + scale_x_reverse()) #+ plot_layout(widths = c(2, 1, 2))
gg.sodalis_tangle

pdf(file="figs/sodalis_tanglegram.pdf",  width = 8, height = 11, paper="a4")
  plot(gg.sodalis_tangle)
try(dev.off(), silent=TRUE)

Psyllid ~ Arsenophonus

# Subset to top arsenophonus
top_arse <- ps2 %>%
  transform_sample_counts(function (x) x/sum(x)) %>%
  speedyseq::psmelt() %>%
  filter(psyllid_spp %in% pruned.tree$tip.label) %>%
  filter(genus=="Arsenophonus") %>%
  group_by(Sample) %>%  
  filter(Abundance > 0) %>%
  top_n(1, wt=Abundance) %>%
  mutate(top = TRUE) 

coocur <- ps2 %>%
  transform_sample_counts(function (x) x/sum(x)) %>%
  speedyseq::psmelt() %>%
  filter(psyllid_spp %in% pruned.tree$tip.label) %>%
  left_join(top_arse) %>%
  filter(genus=="Arsenophonus", top==TRUE) %>%
  dplyr::select(OTU, psyllid_spp, SampleID, Abundance) %>%
  mutate(OTU = OTU %>%
           str_replace_all(" |-", "_")) %>%
  dplyr::group_by(OTU, psyllid_spp) %>%
    summarise(Abundance = sum(Abundance)) %>%
  pivot_wider(id_cols = psyllid_spp,
              names_from = OTU,
              values_from=Abundance,
              values_fill = list(Abundance = 0))  %>%
  column_to_rownames("psyllid_spp") %>%
    as.matrix() %>% 
  apply(2, function(x) ifelse(x > 0, 1, 0)) 

coocur <- coocur[ rowSums(coocur) > 0,colSums(coocur) > 0]

# H cophenetic distance
h_tree <- pruned.tree
h_tree$tip.label <- h_tree$tip.label %>%
           str_replace_all(" |-", "_")
h_tree <- drop.tip(h_tree, setdiff(h_tree$tip.label, rownames(coocur)))
h_dist <- sqrt(cophenetic(h_tree))

# P cophenetic distance
s_tree <- phy_tree(ps3)
s_tree$tip.label <- s_tree$tip.label %>%
           str_replace_all(" |-", "_")
s_tree <- drop.tip(s_tree, setdiff(s_tree$tip.label, colnames(coocur)))
s_dist <- sqrt(cophenetic(s_tree)/1e+6 ) ##convert to Mya so integers are small enough for PACO

#alternatively - sqrt(cophenetic(s_tree))
coocur <- coocur[h_tree$tip.label, s_tree$tip.label]

# prepare paco data
D <- prepare_paco_data(H=h_dist, P=s_dist, HP=coocur)
# Add pcord
D <- add_pcoord(D, correction='none') 

p_host <- ggplot(D$H_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2)) +
    geom_point() +
    theme_bw() +
  ggtitle("H PCA")

p_para <- ggplot(D$P_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2))  +
    geom_point() +
    theme_bw()+
  ggtitle("P PCA")
    
plot(p_host + p_para)

# run paco
paco_run <- PACo(D, nperm=999, seed=909, method='quasiswap', symmetric=FALSE)

#print overall significance
print(paco_run$gof)

# Get interaction-specific cophylogenetic contributions using jacknifing
paco_run <- paco_links(paco_run)
links <- data.frame(joint=names(paco_run$jackknife),
                    values=unname(paco_run$jackknife)#,
                    #upper=unname(paco_run$jackknife$upper)
                    ) %>%
  mutate(OTU = joint) %>%
 separate(OTU, into=c("Sample_Name", "OTU"), sep="-", extra="merge") %>%
  mutate(signif = case_when(
    values < mean(.$values) ~ 1,
    values > mean(.$values) ~ 0
  ))

# Plot links
links %>%
  dplyr::rename(label = joint) %>%
  mutate(label = as.factor(label),
         label=fct_reorder(label, values, sum))%>%
  arrange(-values) %>%
  ggplot(aes(x=label, y=values, colour=as.factor(signif))) +
  geom_point(show.legend = FALSE) +
  geom_errorbar(aes(ymin=values, ymax=upper)) +
  geom_hline(yintercept = mean(links$values)) +
  scale_color_manual(values=c("steelblue", "darkorange1")) +
  theme_classic()+ 
  theme(axis.text.x = element_blank())

dir.create("output/cophylogeny/psyllid_secondary")
write_csv(links, "output/cophylogeny/psyllid_secondary/psyllid_secondary_links.csv")
write_csv(links %>% 
    group_by(OTU) %>%
    summarise(values = mean(values)), "output/cophylogeny/psyllid_secondary/secondary_symbiont_weights.csv")
write_csv(links %>% 
    group_by(Sample_Name) %>%
    summarise(values = mean(values)), "output/cophylogeny/psyllid_secondary/psyllid_weights.csv")

#Get observed residuals of Procrustean superimposition 
paco_residuals <- residuals_paco(paco_run$proc, type = "interaction")
# Visualise residuals
res <- data.frame(OTU=names(paco_residuals), values=unname(paco_residuals)) %>%
  separate(OTU, into=c("Sample_Name", "OTU"), sep="-", extra="merge") 

ggplot(res, aes(x=values))+
  geom_density(fill='grey70')+
  theme_bw()+
  xlab('Procrustes residuals')+
  ylab('Frequency')

# Parafit run
PF_run <- parafit(h_dist, s_dist,coocur, nperm=999, test.links=TRUE)
PF_run$ParaFitGlobal
PF_run$p.global

PF_links <- as.data.frame(PF_run$link.table)  %>%
      left_join(enframe(names(PF_run$para.per.host), name = "Host", value="psyllid_spp") %>%
                  mutate(Host = as.numeric(Host))) %>%
      left_join(enframe(names(PF_run$host.per.para), name = "Parasite", value="OTU") %>%
                  mutate(Parasite = as.numeric(Parasite)))%>%
  mutate(signif = case_when(
    p.F1 < 0.05 ~ 1,
    p.F1 > 0.05 ~ 0
  )) 

# Cophyloplot
coocur.lut <- which(coocur ==1, arr.ind=TRUE)
assoc <- cbind(rownames(coocur)[coocur.lut[,1]], colnames(coocur)[coocur.lut[,2]])

# Rotate the nodes using phytools
library(phytools)
obj <- cophylo(tr1=h_tree, tr2=s_tree, assoc=assoc, rotate=TRUE) 

# psyllid_tree
tree1 <- obj[["trees"]][[1]]

p1 <- ggtree(tree1)
atmeto_node <- p1$data %>% 
  filter(str_detect(label, "Atmeto")) %>%
  pull(node)
root_node <- p1$data %>% 
  filter(str_detect(label, "Atmeto")) %>%
  pull(parent)

tree1 <- groupOTU(tree1, .node=atmeto_node) # Make the atmeto dotted to indicate outgroup was rescaled

p1 <- ggtree(tree1, ladderize=FALSE, aes(colour=links,linetype=group))
weights_p1 <- p1$data %>%
  left_join(links %>% 
    group_by(Sample_Name) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = Sample_Name)
    )%>%
  left_join(PF_links %>% 
    group_by(psyllid_spp) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = psyllid_spp)
    ) %>%
  mutate(values = PA_values + PF_values)

#Scale the atmetocranium and root nodes to be shorter
p1$data[p1$data$node %in% atmeto_node, "x"] <- max(p1$data$x)
p1$data[p1$data$node %in% root_node, "x"] <- 0.2 #root

p1$data$node[p1$data$node]

## Get values for higher nodes
weights_p1 <- weights_p1 %>%
  left_join(data.frame(node=weights_p1$node, links = weights_p1$node %>% purrr::map_dbl(average_descendants, tree=tree1, df=weights_p1)))
p1 <- p1 %<+% weights_p1 + geom_tippoint(aes(colour=links)) +
  scale_color_gradient(low="steelblue", high="darkorange1")  +
  theme(legend.position = "none")

# OTU tree
tree2 <- obj[["trees"]][[2]]
s_tree <- drop.tip(tree2, setdiff(tree2$tip.label, obj$assoc[,2]))
p2 <- ggtree(tree2 , ladderize=FALSE, aes(colour=links)) 
weights_p2 <- p2$data %>%
  left_join(links %>% 
    group_by(OTU) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = OTU)
    )%>%
  left_join(PF_links %>% 
    group_by(OTU) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = OTU)
    ) %>%
  mutate(values = PA_values + PF_values)

weights_p2 <- weights_p2 %>%
  left_join(data.frame(node=weights_p2$node, links = weights_p2$node %>% purrr::map_dbl(average_descendants, tree=tree2, df=weights_p2)))

p2 <- p2 %<+% weights_p2  + geom_tippoint(aes(colour=links)) + 
  scale_color_gradient(low="steelblue", high="darkorange1") +
  theme(legend.position = "none")

# Tanglegram 
tangle <- obj$assoc %>%
  as_data_frame() %>%
  magrittr::set_colnames(c("label.x", "label.y")) %>%
  left_join(links %>% 
              dplyr::select(label.x = Sample_Name, label.y = OTU, signif_paco=signif)) %>%
  left_join(PF_links %>% 
              dplyr::select(label.x = psyllid_spp, label.y = OTU, signif_para=signif)) %>%
  left_join(p1$data %>% dplyr::select(label, y) %>% dplyr::rename(label.x = label), by="label.x") %>%
  left_join(p2$data %>% dplyr::select(label, y) %>% dplyr::rename(label.y = label), by="label.y") %>%
  rownames_to_column("assoc") %>%
  rename_all(~str_replace(.x,pattern="\\.", replacement="_")) %>%
  pivot_longer(ends_with(c("_x", "_y")),
               names_to=c(".value", "tree"), 
               names_sep = "_"
              )  %>%
  mutate(signif_paco = replace_na(signif_paco, 0),
         signif_para = replace_na(signif_para, 0)) %>%
  mutate(signif = case_when(
    signif_paco==0 & signif_para==0  ~ "NS",
    signif_paco==0 & signif_para==1 ~ "para",
    signif_paco==1 & signif_para==0 ~ "paco",
    signif_paco==1 & signif_para==1 ~ "both"
  )) %>%
  mutate(tree = tree %>% 
           str_replace("x", "host")%>%
           str_replace("y", "microbe")) %>%
  filter(!is.na(label))%>% 
  group_by(tree) %>%
  mutate(y = y / max(y))%>%
  mutate(label = label %>% str_replace_all("_", " ")) 


gg.tangle <- ggplot(tangle, aes(x=tree, y=y, group=assoc, colour=as.factor(signif))) +
    geom_line(alpha=0.8) +  
  geom_text(data = tangle %>% 
              filter(tree=="host"),
            aes(label=label),stat = 'unique', hjust=1, check_overlap = TRUE)+
  geom_text(data = tangle %>% 
              filter(tree=="microbe"),
            aes(label=label),stat = 'unique', hjust=0, check_overlap = TRUE)+
  scale_colour_manual(values=c("NS"="steelblue", "paco"="darkorange1", "para"="#da2b91", "both"="#91da2b"), na.translate=FALSE)+
    scale_x_discrete(expand = expansion(add=c(0.8,0.8))) + 
    theme_void() +
    scale_y_continuous(expand=c(0.005,0.005))+
    theme(legend.position = "bottom") +
  labs(colour="Significance:")
gg.arse_tangle <- p1 + gg.tangle + (p2 + scale_x_reverse()) #+ plot_layout(widths = c(2, 1, 2))
gg.arse_tangle

pdf(file="figs/arsenophonus_tanglegram.pdf",  width = 8, height = 11, paper="a4")
  plot(gg.arse_tangle)
try(dev.off(), silent=TRUE)

Psyllid ~ hostplant

Tanglegram of all plants and psyllids!

plant.tree <- read.tree("sample_data/plant_tree.nwk")
plant.tree  <- drop.tip(plant.tree , "Sophora_microphylla_-kowhai" )

#Prepare co-occurance matrix
coocur <- sample_data(ps2) %>%
  as_tibble() %>%
  dplyr::select(psyllid_spp, hostplant_spp) %>%
  filter(!is.na(psyllid_spp)) %>%
  unique() %>%
  mutate(psyllid_spp = psyllid_spp %>%
           str_replace_all(" |-", "_"),
         hostplant_spp = hostplant_spp %>%
           str_replace_all(" |-", "_"),
         presence = 1
         ) %>%
  pivot_wider(names_from = "hostplant_spp",
              values_from="presence",
              values_fill = list(presence = 0)) %>%
  column_to_rownames("psyllid_spp") %>%
  t()

# H cophenetic distance
h_tree <- plant.tree
h_tree$tip.label <- h_tree$tip.label %>%
           str_replace_all(" |-", "_")
h_tree <- drop.tip(h_tree, setdiff(h_tree$tip.label, rownames(coocur)))
h_dist <- sqrt(cophenetic(h_tree))

# P cophenetic distance
s_tree <- pruned.tree
s_tree$tip.label <- s_tree$tip.label %>%
           str_replace_all(" |-", "_")
s_tree <- drop.tip(s_tree, setdiff(s_tree$tip.label, colnames(coocur)))
s_dist <- sqrt(cophenetic(s_tree))

coocur <- coocur[h_tree$tip.label, s_tree$tip.label]

# prepare paco data
D <- prepare_paco_data(H=h_dist, P=s_dist, HP=coocur)
# Add pcord
D <- add_pcoord(D, correction='none') 

p_host <- ggplot(D$H_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2)) +
    geom_point() +
    theme_bw() +
  ggtitle("H PCA")

p_para <- ggplot(D$P_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2))  +
    geom_point() +
    theme_bw()+
  ggtitle("P PCA")
    
plot(p_host + p_para)

# run paco
paco_run <- PACo(D, nperm=999, seed=909, method='quasiswap', symmetric=FALSE)

#print overall significance
print(paco_run$gof)

# Get interaction-specific cophylogenetic contributions using leave-one-out jacknifing
paco_run <- paco_links(paco_run, .parallel = TRUE)

# get links
links <- data.frame(
  joint=names(paco_run$jackknife),
  values=unname(paco_run$jackknife)#,
  #upper=unname(paco_run$jackknife$upper)
  ) %>%
 mutate(OTU = joint) %>%
 separate(OTU, into=c("hostplant_spp", "psyllid_spp"), sep="-", extra="merge") %>%
  mutate(signif = case_when(
    values < mean(.$values) ~ 1,
    values > mean(.$values) ~ 0
  ))
# Plot links
links %>%
  dplyr::rename(label = joint) %>%
  mutate(label = as.factor(label),
         label=fct_reorder(label, values, sum))%>%
  arrange(-values) %>%
  ggplot(aes(x=label, y=values, colour=as.factor(signif))) +
  geom_point(show.legend = FALSE) +
 # geom_errorbar(aes(ymin=values, ymax=upper)) +
  geom_hline(yintercept = mean(links$values)) +
  scale_color_manual(values=c("steelblue", "darkorange1")) +
  theme_classic()+ 
  theme(axis.text.x = element_blank())

dir.create("output/cophylogeny/psyllid_hostplant")
write_csv(links, "output/cophylogeny/psyllid_hostplant/psyllid_hostplant_links.csv")
write_csv(links %>% 
    group_by(psyllid_spp) %>%
    summarise(values = mean(values)), "output/cophylogeny/psyllid_hostplant/psyllid_weights.csv")
write_csv(links %>% 
    group_by(hostplant_spp) %>%
    summarise(values = mean(values)), "output/cophylogeny/psyllid_hostplant/hostplant_weights.csv")

#Get observed residuals of Procrustean superimposition 
paco_residuals <- residuals_paco(paco_run$proc, type = "interaction")

# Visualise residuals
res <- data.frame(OTU=names(paco_residuals), values=unname(paco_residuals)) %>%
  separate(OTU, into=c("hostplant_spp", "psyllid_spp"), sep="-", extra="merge")

ggplot(res, aes(x=values))+
  geom_density(fill='grey70')+
  #facet_wrap(~psyllid_genus) +
  theme_bw()+
  xlab('Procrustes residuals')+
  ylab('Frequency')

# Parafit run
PF_run <- parafit(h_dist, s_dist, t(coocur), nperm=999, test.links=TRUE, silent=FALSE)
PF_run$ParaFitGlobal
PF_run$p.global

PF_links <- as.data.frame(PF_run$link.table)  %>%
      left_join(enframe(names(PF_run$para.per.host), name = "Host", value="hostplant_spp") %>%
                  mutate(Host = as.numeric(Host))) %>%
      left_join(enframe(names(PF_run$host.per.para), name = "Parasite", value="psyllid_spp") %>%
                  mutate(Parasite = as.numeric(Parasite)))%>%
  mutate(signif = case_when(
    p.F1 < 0.05 ~ 1,
    p.F1 > 0.05 ~ 0
  )) 

# Cophyloplot
coocur.lut <- which(coocur ==1, arr.ind=TRUE)
assoc <- cbind(rownames(coocur)[coocur.lut[,1]], colnames(coocur)[coocur.lut[,2]])

# Rotate the nodes using phytools
obj <- cophylo(tr1=h_tree, tr2=s_tree, assoc=assoc, rotate=TRUE) 

# Extract the goods for ggtree
# plant tree
tree1 <- obj[["trees"]][[1]]

p1 <- ggtree(tree1, ladderize=FALSE, aes(colour=links))
weights_p1 <- p1$data %>%
  left_join(links %>% 
    group_by(hostplant_spp) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = hostplant_spp)
    )%>%
  left_join(PF_links %>% 
    group_by(hostplant_spp) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = hostplant_spp)
    ) %>%
  mutate(values = PA_values + PF_values)

## Get values for higher nodes
weights_p1 <- weights_p1 %>%
  left_join(data.frame(node=weights_p1$node, links = weights_p1$node %>% purrr::map_dbl(average_descendants, tree=tree1, df=weights_p1)))
p1 <- p1 %<+% weights_p1 + geom_tippoint(aes(colour=links)) +
  scale_color_gradient(low="steelblue", high="darkorange1")  +
  theme(legend.position = "none")

# psyllid_tree
tree2 <- obj[["trees"]][[2]]
s_tree <- drop.tip(tree2, setdiff(tree2$tip.label, obj$assoc[,2]))

p2 <- ggtree(tree2)
atmeto_node <- p2$data %>% 
  filter(str_detect(label, "Atmeto")) %>%
  pull(node)
root_node <- p2$data %>% 
  filter(str_detect(label, "Atmeto")) %>%
  pull(parent)

tree2 <- groupOTU(tree2, .node=atmeto_node) # Make the atmeto dotted to indicate outgroup was rescaled

p2 <- ggtree(tree2 , ladderize=TRUE, aes(colour=links, linetype=group)) 
weights_p2 <- p2$data %>%
  left_join(links %>% 
    group_by(psyllid_spp) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = psyllid_spp)
    )%>%
  left_join(PF_links %>% 
    group_by(psyllid_spp) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = psyllid_spp)
    ) %>%
  mutate(values = PA_values + PF_values)

#Scale the atmetocranium and root nodes to be shorter
p2$data[p2$data$node %in% atmeto_node, "x"] <- max(p2$data$x)
p2$data[p2$data$node %in% root_node, "x"] <- 0.2 #root

p2$data$node[p2$data$node]

weights_p2 <- weights_p2 %>%
  left_join(data.frame(node=weights_p2$node, links = weights_p2$node %>% purrr::map_dbl(average_descendants, tree=tree2, df=weights_p2)))

p2 <- p2 %<+% weights_p2  + geom_tippoint(aes(colour=links)) + 
  scale_color_gradient(low="steelblue", high="darkorange1") +
  theme(legend.position = "none")

# Tanglegram 
tangle <- obj$assoc %>%
  as_data_frame() %>%
  magrittr::set_colnames(c("label.x", "label.y")) %>%
  left_join(links %>% 
              dplyr::select(label.x = hostplant_spp, label.y = psyllid_spp, signif_paco=signif)) %>%
  left_join(PF_links %>% 
              dplyr::select(label.x = hostplant_spp, label.y = psyllid_spp, signif_para=signif)) %>%
  left_join(p1$data %>% dplyr::select(label, y) %>% dplyr::rename(label.x = label), by="label.x") %>%
  left_join(p2$data %>% dplyr::select(label, y) %>% dplyr::rename(label.y = label), by="label.y") %>%
  rownames_to_column("assoc") %>%
  rename_all(~str_replace(.x,pattern="\\.", replacement="_")) %>%
  pivot_longer(ends_with(c("_x", "_y")),
               names_to=c(".value", "tree"), 
               names_sep = "_"
              )  %>%
  mutate(signif_paco = replace_na(signif_paco, 0),
         signif_para = replace_na(signif_para, 0)) %>%
  mutate(signif = case_when(
    signif_paco==0 & signif_para==0  ~ "NS",
    signif_paco==0 & signif_para==1 ~ "para",
    signif_paco==1 & signif_para==0 ~ "paco",
    signif_paco==1 & signif_para==1 ~ "both"
  )) %>%
  mutate(tree = tree %>% 
           str_replace("x", "host")%>%
           str_replace("y", "microbe")) %>%
  filter(!is.na(label))%>% 
  group_by(tree) %>%
  mutate(y = y / max(y))%>%
  mutate(label = label %>% str_replace_all("_", " ")) 

gg.tangle <- ggplot(tangle, aes(x=factor(tree, levels=c("microbe", "host")), y=y, group=assoc, colour=as.factor(signif))) +
    geom_line(alpha=0.8) +  
  geom_text(data = tangle %>% 
              filter(tree=="host"),
            aes(label=label),stat = 'unique', hjust=0, check_overlap = TRUE)+
  geom_text(data = tangle %>% 
              filter(tree=="microbe"),
            aes(label=label),stat = 'unique', hjust=1, check_overlap = TRUE)+
  scale_colour_manual(values=c("NS"="steelblue", "paco"="darkorange1", "para"="#da2b91", "both"="#91da2b"), na.translate=FALSE)+
    scale_x_discrete(expand = expansion(add=c(0.8,0.8))) + 
    scale_y_continuous(expand=c(0.005,0.005))+
    theme_void() +
    theme(legend.position = "bottom") +
  labs(colour="Significance")

gg.plant_tangle <- p2 + gg.tangle + (p1 + scale_x_reverse()) #+ plot_layout(widths = c(2, 1, 2))
gg.plant_tangle

pdf(file="figs/plant_tanglegram.pdf",  width = 8, height = 11, paper="a4")
  plot(gg.plant_tangle)
try(dev.off(), silent=TRUE)

Powellia

Powellia ~ Carsonella

#Flag top abundance carsonella by sample
top_carson <- ps2 %>%
  transform_sample_counts(function (x) x/sum(x)) %>%
  speedyseq::psmelt() %>%
  filter(psyllid_spp %in% pruned.tree$tip.label) %>%
  filter(genus=="Candidatus Carsonella") %>%
  group_by(Sample) %>%  
  filter(Abundance > 0) %>%
  top_n(1, wt=Abundance) %>%
  mutate(top = TRUE) 

coocur <- ps2 %>%
  subset_samples(psyllid_genus == "Powellia") %>%
  filter_taxa(function(x) mean(x) > 0, TRUE) %>%
  transform_sample_counts(function (x) x/sum(x)) %>%
  speedyseq::psmelt() %>%
  filter(psyllid_spp %in% pruned.tree$tip.label) %>%
  left_join(top_carson) %>%
  filter(genus=="Candidatus Carsonella", top==TRUE) %>%
  dplyr::select(OTU, psyllid_spp, SampleID, Abundance) %>%
  mutate(OTU = OTU %>%
           str_replace_all(" |-", "_")) %>%
  dplyr::group_by(OTU, psyllid_spp) %>%
    summarise(Abundance = sum(Abundance)) %>%
  pivot_wider(id_cols = psyllid_spp,
              names_from = OTU,
              values_from=Abundance,
              values_fill = list(Abundance = 0))  %>%
  column_to_rownames("psyllid_spp") %>%
    as.matrix() %>% 
  apply(2, function(x) ifelse(x > 0, 1, 0)) 

# H cophenetic distance
h_tree <- pruned.tree
h_tree$tip.label <- h_tree$tip.label %>%
           str_replace_all(" |-", "_")
h_tree <- drop.tip(h_tree, setdiff(h_tree$tip.label, rownames(coocur)))
h_dist <- sqrt(cophenetic(h_tree))

# P cophenetic distance
s_tree <- phy_tree(ps3)
s_tree$tip.label <- s_tree$tip.label %>%
           str_replace_all(" |-", "_")
s_tree <- drop.tip(s_tree, setdiff(s_tree$tip.label, colnames(coocur)))
s_dist <- sqrt(cophenetic(s_tree)/1e+6 ) ##convert to Mya so integers are small enough for PACO

coocur <- coocur[h_tree$tip.label, s_tree$tip.label]

# prepare paco data
D <- prepare_paco_data(H=h_dist, P=s_dist, HP=coocur)
# Add pcord
D <- add_pcoord(D, correction='none') 

p_host <- ggplot(D$H_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2)) +
    geom_point() +
    theme_bw() +
  ggtitle("H PCA")

p_para <- ggplot(D$P_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2))  +
    geom_point() +
    theme_bw()+
  ggtitle("P PCA")
    
plot(p_host + p_para)

# run paco
paco_run <- PACo(D, nperm=999, seed=909, method='quasiswap', symmetric=FALSE)

#print overall significance
print(paco_run$gof)

# Get interaction-specific cophylogenetic contributions using jacknifing
paco_run <- paco_links(paco_run)
links <- data.frame(joint=names(paco_run$jackknife), 
                    values=unname(paco_run$jackknife)#, 
                    #upper=unname(paco_run$jackknife$upper)
                    ) %>%
  mutate(OTU = joint) %>%
 separate(OTU, into=c("Sample_Name", "OTU"), sep="-", extra="merge") %>%
  mutate(signif = case_when(
    values < mean(.$values) ~ 1,
    values > mean(.$values) ~ 0
  ))

# Plot links
links %>%
  dplyr::rename(label = joint) %>%
  mutate(label = as.factor(label),
         label=fct_reorder(label, values, sum))%>%
  arrange(-values) %>%
  ggplot(aes(x=label, y=values, colour=as.factor(signif))) +
  geom_point(show.legend = FALSE) +
  #geom_errorbar(aes(ymin=values, ymax=upper)) +
  geom_hline(yintercept = mean(links$values)) +
  scale_color_manual(values=c("steelblue", "darkorange1")) +
  theme_classic()+ 
  theme(axis.text.x = element_blank())

dir.create("output/cophylogeny/trioza_carsonella")
write_csv(links, "output/cophylogeny/trioza_carsonella/trioza_carsonella_links.csv")
write_csv(links %>% 
    group_by(OTU) %>%
    summarise(values = mean(values)), "output/cophylogeny/trioza_carsonella/carsonella_weights.csv")
write_csv(links %>% 
    group_by(Sample_Name) %>%
    summarise(values = mean(values)), "output/cophylogeny/trioza_carsonella/psyllid_weights.csv")

#Get observed residuals of Procrustean superimposition 
paco_residuals <- residuals_paco(paco_run$proc, type = "interaction")
# Visualise residuals
res <- data.frame(OTU=names(paco_residuals), values=unname(paco_residuals)) %>%
  separate(OTU, into=c("Sample_Name", "OTU"), sep="-", extra="merge") 

ggplot(res, aes(x=values))+
  geom_density(fill='grey70')+
  theme_bw()+
  xlab('Procrustes residuals')+
  ylab('Frequency')

# Parafit run
PF_run <- parafit(h_dist, s_dist, t(coocur), nperm=999, test.links=TRUE, silent=FALSE)
PF_run$ParaFitGlobal
PF_run$p.global

PF_links <- as.data.frame(PF_run$link.table)  %>%
      left_join(enframe(names(PF_run$para.per.host), name = "Host", value="psyllid_spp") %>%
                  mutate(Host = as.numeric(Host))) %>%
      left_join(enframe(names(PF_run$host.per.para), name = "Parasite", value="OTU") %>%
                  mutate(Parasite = as.numeric(Parasite)))%>%
  mutate(signif = case_when(
    p.F1 < 0.05 ~ 1,
    p.F1 > 0.05 ~ 0
  )) 

# Cophyloplot
coocur.lut <- which(coocur ==1, arr.ind=TRUE)
assoc <- cbind(rownames(coocur)[coocur.lut[,1]], colnames(coocur)[coocur.lut[,2]])

# Rotate the nodes using phytools
obj <- cophylo(tr1=h_tree, tr2=s_tree, assoc=assoc, rotate=TRUE) 

# psyllid_tree
tree1 <- obj[["trees"]][[1]]

p1 <- ggtree(tree1)
atmeto_node <- p1$data %>% 
  filter(str_detect(label, "Atmeto")) %>%
  pull(node)
root_node <- p1$data %>% 
  filter(str_detect(label, "Atmeto")) %>%
  pull(parent)

tree1 <- groupOTU(tree1, .node=atmeto_node) # Make the atmeto dotted to indicate outgroup was rescaled

p1 <- ggtree(tree1, ladderize=FALSE, aes(colour=links,linetype=group))
weights_p1 <- p1$data %>%
  left_join(links %>% 
    group_by(Sample_Name) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = Sample_Name)
    )%>%
  left_join(PF_links %>% 
    group_by(psyllid_spp) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = psyllid_spp)
    ) %>%
  mutate(values = PA_values + PF_values)

#Scale the atmetocranium and root nodes to be shorter
p1$data[p1$data$node %in% atmeto_node, "x"] <- max(p1$data$x)
p1$data[p1$data$node %in% root_node, "x"] <- 0.2 #root

p1$data$node[p1$data$node]

## Get values for higher nodes
weights_p1 <- weights_p1 %>%
  left_join(data.frame(node=weights_p1$node, links = weights_p1$node %>% purrr::map_dbl(average_descendants, tree=tree1, df=weights_p1)))
p1 <- p1 %<+% weights_p1 + geom_tippoint(aes(colour=links)) +
  scale_color_gradient(low="steelblue", high="darkorange1")  +
  theme(legend.position = "none")

# OTU tree
tree2 <- obj[["trees"]][[2]]
s_tree <- drop.tip(tree2, setdiff(tree2$tip.label, obj$assoc[,2]))
p2 <- ggtree(tree2 , ladderize=TRUE, aes(colour=links)) 
weights_p2 <- p2$data %>%
  left_join(links %>% 
    group_by(OTU) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = OTU)
    )%>%
  left_join(PF_links %>% 
    group_by(OTU) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = OTU)
    ) %>%
  mutate(values = PA_values + PF_values)

weights_p2 <- weights_p2 %>%
  left_join(data.frame(node=weights_p2$node, links = weights_p2$node %>% purrr::map_dbl(average_descendants, tree=tree2, df=weights_p2)))

p2 <- p2 %<+% weights_p2  + geom_tippoint(aes(colour=links)) + 
  scale_color_gradient(low="steelblue", high="darkorange1") +
  theme(legend.position = "none")

# Tanglegram 
tangle <- obj$assoc %>%
  as_data_frame() %>%
  magrittr::set_colnames(c("label.x", "label.y")) %>%
  left_join(links %>% 
              dplyr::select(label.x = Sample_Name, label.y = OTU, signif_paco=signif)) %>%
  left_join(PF_links %>% 
              dplyr::select(label.x = psyllid_spp, label.y = OTU, signif_para=signif)) %>%
  left_join(p1$data %>% dplyr::select(label, y) %>% dplyr::rename(label.x = label), by="label.x") %>%
  left_join(p2$data %>% dplyr::select(label, y) %>% dplyr::rename(label.y = label), by="label.y") %>%
  rownames_to_column("assoc") %>%
  rename_all(~str_replace(.x,pattern="\\.", replacement="_")) %>%
  pivot_longer(ends_with(c("_x", "_y")),
               names_to=c(".value", "tree"), 
               names_sep = "_"
              )  %>%
  mutate(signif_paco = replace_na(signif_paco, 0),
         signif_para = replace_na(signif_para, 0)) %>%
  mutate(signif = case_when(
    signif_paco==0 & signif_para==0  ~ "NS",
    signif_paco==0 & signif_para==1 ~ "para",
    signif_paco==1 & signif_para==0 ~ "paco",
    signif_paco==1 & signif_para==1 ~ "both"
  )) %>%
  mutate(tree = tree %>% 
           str_replace("x", "host")%>%
           str_replace("y", "microbe")) %>%
  filter(!is.na(label))%>% 
  group_by(tree) %>%
  mutate(y = y / max(y))%>%
  mutate(label = label %>% str_replace_all("_", " ")) 


gg.tangle <- ggplot(tangle, aes(x=tree, y=y, group=assoc, colour=as.factor(signif))) +
    geom_line(alpha=0.8) +  
  geom_text(data = tangle %>% 
              filter(tree=="host"),
            aes(label=label),stat = 'unique', hjust=1, check_overlap = TRUE)+
  geom_text(data = tangle %>% 
              filter(tree=="microbe"),
            aes(label=label),stat = 'unique', hjust=0, check_overlap = TRUE)+
  scale_colour_manual(values=c("NS"="steelblue", "paco"="darkorange1", "para"="#da2b91", "both"="#91da2b"), na.translate=FALSE)+
    scale_x_discrete(expand = expansion(add=c(0.8,0.8))) + 
    theme_void() +
    scale_y_continuous(expand=c(0.005,0.005))+
    theme(legend.position = "bottom") +
  labs(colour="Significance:")

gg.trioza_carson_tangle <- p1 + gg.tangle + (p2 + scale_x_reverse()) 
gg.trioza_carson_tangle

pdf(file="figs/powellia_carsonella_tanglegram.pdf",  width = 8, height = 11, paper="a4")
  plot(gg.trioza_carson_tangle)
try(dev.off(), silent=TRUE)

Powellia ~ hostplant

plant.tree <- read.tree("sample_data/plant_tree.nwk")
plant.tree  <- drop.tip(plant.tree , "Sophora_microphylla_-kowhai" )

#Prepare co-occurance matrix
coocur <- sample_data(ps2) %>%
  as_tibble() %>%
  filter(psyllid_genus == "Powellia") %>%
  dplyr::select(psyllid_spp, hostplant_spp) %>%
  filter(!is.na(psyllid_spp)) %>%
  unique() %>%
  mutate(psyllid_spp = psyllid_spp %>%
           str_replace_all(" |-", "_"),
         hostplant_spp = hostplant_spp %>%
           str_replace_all(" |-", "_"),
         presence = 1
         ) %>%
  pivot_wider(names_from = "hostplant_spp",
              values_from="presence",
              values_fill = list(presence = 0)) %>%
  column_to_rownames("psyllid_spp") %>%
  t()

# H cophenetic distance
h_tree <- plant.tree
h_tree$tip.label <- h_tree$tip.label %>%
           str_replace_all(" |-", "_")
h_tree <- drop.tip(h_tree, setdiff(h_tree$tip.label, rownames(coocur)))
h_dist <- sqrt(cophenetic(h_tree))

# P cophenetic distance
s_tree <- pruned.tree
s_tree$tip.label <- s_tree$tip.label %>%
           str_replace_all(" |-", "_")
s_tree <- drop.tip(s_tree, setdiff(s_tree$tip.label, colnames(coocur)))
s_dist <- sqrt(cophenetic(s_tree))

coocur <- coocur[h_tree$tip.label, s_tree$tip.label]
# prepare paco data
D <- prepare_paco_data(H=h_dist, P=s_dist, HP=coocur)
# Add pcord
D <- add_pcoord(D, correction='none')  

p_host <- ggplot(D$H_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2)) +
    geom_point() +
    theme_bw() +
  ggtitle("H PCA")

p_para <- ggplot(D$P_PCo[,c('Axis.1', 'Axis.2')] %>% as.data.frame, aes(Axis.1, Axis.2))  +
    geom_point() +
    theme_bw()+
  ggtitle("P PCA")
    
plot(p_host + p_para)

# run paco
paco_run <- PACo(D, nperm=999, seed=909, method='quasiswap', symmetric=FALSE) #Symetric - is one meant to track the evolution of another?

#print overall significance
print(paco_run$gof)

# Procrustes diagnostic plots
plot(paco_run$proc)
plot(paco_run$proc, kind=2)

# Get interaction-specific cophylogenetic contributions using jacknifing
paco_run <- paco_links(paco_run)
links <- data.frame(
  joint=names(paco_run$jackknife),
  values=unname(paco_run$jackknife)#,
  #upper=unname(paco_run$jackknife$upper)
  ) %>%
 mutate(OTU = joint) %>%
 separate(OTU, into=c("hostplant_spp", "psyllid_spp"), sep="-", extra="merge") %>%
  mutate(signif = case_when(
    values < mean(.$values) ~ 1,
    values > mean(.$values) ~ 0
  ))

# Plot links
links %>%
  dplyr::rename(label = joint) %>%
  mutate(label = as.factor(label),
         label=fct_reorder(label, values, sum))%>%
  arrange(-values) %>%
  ggplot(aes(x=label, y=values, colour=as.factor(signif))) +
  geom_point(show.legend = FALSE) +
  #geom_errorbar(aes(ymin=values, ymax=upper)) +
  geom_hline(yintercept = mean(links$values)) +
  scale_color_manual(values=c("steelblue", "darkorange1")) +
  theme_classic()+ 
  theme(axis.text.x = element_text(angle=45, hjust=1))

dir.create("output/cophylogeny/trioza_hostplant")
write_csv(links, "output/cophylogeny/trioza_hostplant/psyllid_hostplant_links.csv")
write_csv(links %>% 
    group_by(psyllid_spp) %>%
    summarise(values = mean(values)), "output/cophylogeny/trioza_hostplant/psyllid_weights.csv")
write_csv(links %>% 
    group_by(hostplant_spp) %>%
    summarise(values = mean(values)), "output/cophylogeny/trioza_hostplant/hostplant_weights.csv")

#Get observed residuals of Procrustean superimposition 
paco_residuals <- residuals_paco(paco_run$proc, type = "interaction")

# Visualise residuals
res <- data.frame(OTU=names(paco_residuals), values=unname(paco_residuals)) %>%
  separate(OTU, into=c("hostplant_spp", "psyllid_spp"), sep="-", extra="merge")

ggplot(res, aes(x=values))+
  geom_density(fill='grey70')+
  theme_bw()+
  xlab('Procrustes residuals')+
  ylab('Frequency')

# Parafit run
PF_run <- parafit(h_dist, s_dist, t(coocur), nperm=999, test.links=TRUE, silent=TRUE)
PF_run$ParaFitGlobal
PF_run$p.global


PF_links <- as.data.frame(PF_run$link.table)  %>%
      left_join(enframe(names(PF_run$para.per.host), name = "Host", value="hostplant_spp") %>%
                  mutate(Host = as.numeric(Host))) %>%
      left_join(enframe(names(PF_run$host.per.para), name = "Parasite", value="psyllid_spp") %>%
                  mutate(Parasite = as.numeric(Parasite)))%>%
  mutate(signif = case_when(
    p.F1 < 0.05 ~ 1,
    p.F1 > 0.05 ~ 0
  )) 

# Cophyloplot
coocur.lut <- which(coocur ==1, arr.ind=TRUE)
assoc <- cbind(rownames(coocur)[coocur.lut[,1]], colnames(coocur)[coocur.lut[,2]])

# Rotate the nodes using phytools
obj <- cophylo(tr1=h_tree, tr2=s_tree, assoc=assoc, rotate=TRUE) 

# Extract the goods for ggtree
# plant tree
tree1 <- obj[["trees"]][[1]]

p1 <- ggtree(tree1, ladderize=FALSE, aes(colour=links))
weights_p1 <- p1$data %>%
  left_join(links %>% 
    group_by(hostplant_spp) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = hostplant_spp)
    )%>%
  left_join(PF_links %>% 
    group_by(hostplant_spp) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = hostplant_spp)
    ) %>%
  mutate(values = PA_values + PF_values)

## Get values for higher nodes
weights_p1 <- weights_p1 %>%
  left_join(data.frame(node=weights_p1$node, links = weights_p1$node %>% purrr::map_dbl(average_descendants, tree=tree1, df=weights_p1)))
p1 <- p1 %<+% weights_p1 + geom_tippoint(aes(colour=links)) +
  scale_color_gradient(low="steelblue", high="darkorange1")  +
  theme(legend.position = "none")

# psyllid_tree
tree2 <- obj[["trees"]][[2]]
s_tree <- drop.tip(tree2, setdiff(tree2$tip.label, obj$assoc[,2]))

p2 <- ggtree(tree2 , ladderize=TRUE, aes(colour=links)) 
weights_p2 <- p2$data %>%
  left_join(links %>% 
    group_by(psyllid_spp) %>%
    summarise(PA_values = mean(signif)) %>%
    dplyr::rename(label = psyllid_spp)
    )%>%
  left_join(PF_links %>% 
    group_by(psyllid_spp) %>%
    summarise(PF_values = mean(signif)) %>%
    dplyr::rename(label = psyllid_spp)
    ) %>%
  mutate(values = PA_values + PF_values)

#Scale the atmetocranium and root nodes to be shorter
p2$data[p2$data$node %in% atmeto_node, "x"] <- max(p2$data$x)
p2$data[p2$data$node %in% root_node, "x"] <- 0.2 #root

p2$data$node[p2$data$node]

weights_p2 <- weights_p2 %>%
  left_join(data.frame(node=weights_p2$node, links = weights_p2$node %>% purrr::map_dbl(average_descendants, tree=tree2, df=weights_p2)))

p2 <- p2 %<+% weights_p2  + geom_tippoint(aes(colour=links)) + 
  scale_color_gradient(low="steelblue", high="darkorange1") +
  theme(legend.position = "none")

# Tanglegram 
tangle <- obj$assoc %>%
  as_data_frame() %>%
  magrittr::set_colnames(c("label.x", "label.y")) %>%
  left_join(links %>% 
              dplyr::select(label.x = hostplant_spp, label.y = psyllid_spp, signif_paco=signif)) %>%
  left_join(PF_links %>% 
              dplyr::select(label.x = hostplant_spp, label.y = psyllid_spp, signif_para=signif)) %>%
  left_join(p1$data %>% dplyr::select(label, y) %>% dplyr::rename(label.x = label), by="label.x") %>%
  left_join(p2$data %>% dplyr::select(label, y) %>% dplyr::rename(label.y = label), by="label.y") %>%
  rownames_to_column("assoc") %>%
  rename_all(~str_replace(.x,pattern="\\.", replacement="_")) %>%
  pivot_longer(ends_with(c("_x", "_y")),
               names_to=c(".value", "tree"), 
               names_sep = "_"
              )  %>%
  mutate(signif_paco = replace_na(signif_paco, 0),
         signif_para = replace_na(signif_para, 0)) %>%
  mutate(signif = case_when(
    signif_paco==0 & signif_para==0  ~ "NS",
    signif_paco==0 & signif_para==1 ~ "para",
    signif_paco==1 & signif_para==0 ~ "paco",
    signif_paco==1 & signif_para==1 ~ "both"
  )) %>%
  mutate(tree = tree %>% 
           str_replace("x", "host")%>%
           str_replace("y", "microbe")) %>%
  filter(!is.na(label))%>% 
  group_by(tree) %>%
  mutate(y = y / max(y))%>%
  mutate(label = label %>% str_replace_all("_", " ")) 

gg.tangle <- ggplot(tangle, aes(x=factor(tree, levels=c("microbe", "host")), y=y, group=assoc, colour=as.factor(signif))) +
    geom_line(alpha=0.8) +  
  geom_text(data = tangle %>% 
              filter(tree=="host"),
            aes(label=label),stat = 'unique', hjust=0, check_overlap = TRUE)+
  geom_text(data = tangle %>% 
              filter(tree=="microbe"),
            aes(label=label),stat = 'unique', hjust=1, check_overlap = TRUE)+
  scale_colour_manual(values=c("NS"="steelblue", "paco"="darkorange1", "para"="#da2b91", "both"="#91da2b"), na.translate=FALSE)+
    scale_x_discrete(expand = expansion(add=c(0.8,0.8))) + 
    scale_y_continuous(expand=c(0.005,0.005))+
    theme_void() +
    theme(legend.position = "bottom") +
  labs(colour="Significance")
gg.powellia_tangle <- p2 + gg.tangle + (p1 + scale_x_reverse()) 
gg.powellia_tangle

pdf(file="figs/powellia_plant_tanglegram.pdf",  width = 8, height = 11, paper="a4")
  plot(gg.powellia_tangle)
try(dev.off(), silent=TRUE)

Map of colleciton locations

#Plot on map to confirm points
envData <- sample_data(ps2) %>%
  as_data_frame() %>%
  dplyr::select(Sample_Name,psyllid_spp, lat, long) %>%
  tibble::column_to_rownames("Sample_Name") %>%
  drop_na()

xlim <- c(165,180)
ylim <- c(-50,-30)

nz <- map(database= "world", region= "New Zealand", fill=TRUE, xlim=xlim,
  ylim=ylim) #, mar=c(0,0,0,0)

p1 <- ggtree(pruned.tree, ladderize=TRUE)
map_data <- p1$data%>%
  left_join(envData %>% dplyr::mutate(label =psyllid_spp ))


col <- colorRampPalette(brewer.pal(12, "Paired"))(length(unique(map_data$psyllid_spp)))

gg.nzmap <- ggplot(fortify(nz), aes(y=lat, x=long, group=group)) + 
  geom_polygon(fill="lightgrey", color="#7f7f7f") +
  geom_point(data=map_data, aes(x=long, y=lat, color=psyllid_spp), alpha=.5, size=3, inherit.aes = FALSE) + 
    geom_segment(data=map_data %>%
  mutate(y = scales::rescale(y, to = ylim )), aes(x=min(xlim), y=y, xend= long, yend=lat, color=psyllid_spp), alpha=.2, inherit.aes = FALSE) +
    theme_classic() +
    coord_fixed(ylim =ylim, xlim=xlim) +
    scale_x_continuous(expand = c(0,0)) + 
    scale_colour_manual(values=col) +
    theme(legend.position = "none") +
    scale_y_continuous(position = "right") +
  xlab("Longitude") + 
  ylab("Lattitude")

gg.nzmap 

#print(gg.nzmap, vp = viewport(width = .7, height = .7, angle = 35))
p1 <- p1 %<+% map_data +
  geom_tiplab(align=TRUE, aes(color=psyllid_spp), offset=0.1, hjust=1) +
  scale_x_continuous(expand=c(0, 0))+ 
  scale_colour_manual(values=col) 

gg.phylomap <- p1 + gg.nzmap

pdf(file="figs/phylomap.pdf",  width = 15, height = 15, paper="a4r")
  plot(gg.phylomap)
try(dev.off(), silent=TRUE)

Sessioninfo

devtools::session_info()
## - Session info ---------------------------------------------------------------
##  setting  value                       
##  version  R version 4.1.0 (2021-05-18)
##  os       Windows 10 x64              
##  system   x86_64, mingw32             
##  ui       RTerm                       
##  language (EN)                        
##  collate  English_Australia.1252      
##  ctype    English_Australia.1252      
##  tz       Australia/Sydney            
##  date     2021-10-13                  
## 
## - Packages -------------------------------------------------------------------
##  package     * version date       lib source        
##  bslib         0.3.1   2021-10-06 [1] CRAN (R 4.1.1)
##  cachem        1.0.6   2021-08-19 [1] CRAN (R 4.1.1)
##  callr         3.7.0   2021-04-20 [1] CRAN (R 4.1.0)
##  cli           3.0.1   2021-07-17 [1] CRAN (R 4.1.0)
##  crayon        1.4.1   2021-02-08 [1] CRAN (R 4.1.0)
##  desc          1.4.0   2021-09-28 [1] CRAN (R 4.1.1)
##  devtools      2.4.2   2021-06-07 [1] CRAN (R 4.1.0)
##  digest        0.6.27  2020-10-24 [1] CRAN (R 4.1.0)
##  ellipsis      0.3.2   2021-04-29 [1] CRAN (R 4.1.0)
##  evaluate      0.14    2019-05-28 [1] CRAN (R 4.1.0)
##  fastmap       1.1.0   2021-01-25 [1] CRAN (R 4.1.0)
##  fs            1.5.0   2020-07-31 [1] CRAN (R 4.1.0)
##  glue          1.4.2   2020-08-27 [1] CRAN (R 4.1.0)
##  htmltools     0.5.2   2021-08-25 [1] CRAN (R 4.1.1)
##  jquerylib     0.1.4   2021-04-26 [1] CRAN (R 4.1.1)
##  jsonlite      1.7.2   2020-12-09 [1] CRAN (R 4.1.0)
##  knitr       * 1.36    2021-09-29 [1] CRAN (R 4.1.1)
##  lifecycle     1.0.1   2021-09-24 [1] CRAN (R 4.1.1)
##  magrittr      2.0.1   2020-11-17 [1] CRAN (R 4.1.0)
##  memoise       2.0.0   2021-01-26 [1] CRAN (R 4.1.0)
##  pkgbuild      1.2.0   2020-12-15 [1] CRAN (R 4.1.0)
##  pkgload       1.2.2   2021-09-11 [1] CRAN (R 4.1.0)
##  prettyunits   1.1.1   2020-01-24 [1] CRAN (R 4.1.0)
##  processx      3.5.2   2021-04-30 [1] CRAN (R 4.1.0)
##  ps            1.6.0   2021-02-28 [1] CRAN (R 4.1.0)
##  purrr         0.3.4   2020-04-17 [1] CRAN (R 4.1.0)
##  R6            2.5.1   2021-08-19 [1] CRAN (R 4.1.0)
##  remotes       2.4.1   2021-09-29 [1] CRAN (R 4.1.1)
##  rlang         0.4.11  2021-04-30 [1] CRAN (R 4.1.0)
##  rmarkdown     2.11    2021-09-14 [1] CRAN (R 4.1.0)
##  rprojroot     2.0.2   2020-11-15 [1] CRAN (R 4.1.0)
##  rstudioapi    0.13    2020-11-12 [1] CRAN (R 4.1.0)
##  sass          0.4.0   2021-05-12 [1] CRAN (R 4.1.1)
##  sessioninfo   1.1.1   2018-11-05 [1] CRAN (R 4.1.0)
##  stringi       1.7.3   2021-07-16 [1] CRAN (R 4.1.0)
##  stringr       1.4.0   2019-02-10 [1] CRAN (R 4.1.0)
##  testthat      3.1.0   2021-10-04 [1] CRAN (R 4.1.1)
##  usethis       2.0.1   2021-02-10 [1] CRAN (R 4.1.0)
##  withr         2.4.2   2021-04-18 [1] CRAN (R 4.1.0)
##  xfun          0.25    2021-08-06 [1] CRAN (R 4.1.1)
##  yaml          2.2.1   2020-02-01 [1] CRAN (R 4.1.0)
## 
## [1] C:/Program Files/R/R-4.1.0/library
LS0tDQp0aXRsZTogIlBzeWxsaWQgbWljcm9iaW9tZSINCnN1YnRpdGxlOiAiU3RhdGlzdGljYWwgYW5hbHlzaXMiDQphdXRob3I6ICJBbGV4YW5kZXIgUGlwZXIiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgaW5jbHVkZXM6DQogICAgICBhZnRlcl9ib2R5OiBmb290ZXIuaHRtbA0KICAgIGhpZ2hsaWdodGVyOiBudWxsDQogICAgdGhlbWU6ICJmbGF0bHkiDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogRkFMU0UNCiAgICAgIHNtb290aF9zY3JvbGw6IFRSVUUNCiAgICBkZl9wcmludDogcGFnZWQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlDQotLS0NCg0KDQpgYGB7ciBzZXR1cCwgZXZhbD1UUlVFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRX0NCiMgS25pdHIgZ2xvYmFsIHNldHVwIC0gY2hhbmdlIGV2YWwgdG8gdHJ1ZSB0byBydW4gY29kZQ0KbGlicmFyeShrbml0cikNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZXZhbD1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0UsZmlnLnNob3cgPSAiaG9sZCIsIGZpZy5rZWVwID0gImFsbCIpDQpvcHRzX2NodW5rJHNldChkZXYgPSAncG5nJykNCmBgYA0KDQoNCmBgYHtyIGxvYWQgcGFja2FnZXN9DQojIExvYWQgcGFja2FnZXMNCg0KI1NldCByZXF1aXJlZCBwYWNrYWdlcw0KLmNyYW5fcGFja2FnZXMgPC0gYygidGlkeXZlcnNlIiwgDQogICAgICAgICAgICAgICAgICAgICJwYXRjaHdvcmsiLCANCiAgICAgICAgICAgICAgICAgICAgInZlZ2FuIiwgDQogICAgICAgICAgICAgICAgICAgICJzZXFpbnIiLA0KICAgICAgICAgICAgICAgICAgICAiYXBlIiwgDQogICAgICAgICAgICAgICAgICAgICJzcCIsDQogICAgICAgICAgICAgICAgICAgICJtYXB0b29scyIsDQogICAgICAgICAgICAgICAgICAgICJyZ2VvcyIsDQogICAgICAgICAgICAgICAgICAgICJkYXRhLnRhYmxlIiwgDQogICAgICAgICAgICAgICAgICAgICJSQ29sb3JCcmV3ZXIiLA0KICAgICAgICAgICAgICAgICAgICAiZ2d0cmVlIiwgDQogICAgICAgICAgICAgICAgICAgICJjYXN0b3IiLCANCiAgICAgICAgICAgICAgICAgICAgInBpY2FudGUiLA0KICAgICAgICAgICAgICAgICAgICAicGh5bG9zaWduYWwiLCANCiAgICAgICAgICAgICAgICAgICAgImFkZXBoeWxvIiwNCiAgICAgICAgICAgICAgICAgICAgImRlbmRleHRlbmQiLA0KICAgICAgICAgICAgICAgICAgICAicGFjbyIsDQogICAgICAgICAgICAgICAgICAgICJ0aWR5dGV4dCIsDQogICAgICAgICAgICAgICAgICAgICJwaHl0b29scyIsDQogICAgICAgICAgICAgICAgICAgICJlY29kaXN0IikNCi5iaW9jX3BhY2thZ2VzIDwtIGMoImRhZGEyIiwNCiAgICAgICAgICAgICAgICAgICAgIm1pY3JvYmlvbWUiLA0KICAgICAgICAgICAgICAgICAgICAicGh5bG9zZXEiLCANCiAgICAgICAgICAgICAgICAgICAgIkRFQ0lQSEVSIiwNCiAgICAgICAgICAgICAgICAgICAgIkJpb3N0cmluZ3MiLA0KICAgICAgICAgICAgICAgICAgICAiU2hvcnRSZWFkIiwgDQogICAgICAgICAgICAgICAgICAgICJwaGlsciIsDQogICAgICAgICAgICAgICAgICAgICJBTERFeDIiKQ0KDQojIEluc3RhbGwgYWxsIG1pc3NpbmcgcGFja2FnZXMNCi5pbnN0IDwtIC5jcmFuX3BhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkNCmlmKGFueSghLmluc3QpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKC5jcmFuX3BhY2thZ2VzWyEuaW5zdF0pDQp9DQouaW5zdCA8LSAuYmlvY19wYWNrYWdlcyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpDQppZihhbnkoIS5pbnN0KSkgew0KICBpZiAoIXJlcXVpcmVOYW1lc3BhY2UoIkJpb2NNYW5hZ2VyIiwgcXVpZXRseSA9IFRSVUUpKQ0KICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikNCiAgQmlvY01hbmFnZXI6Omluc3RhbGwoLmJpb2NfcGFja2FnZXNbIS5pbnN0XSwgYXNrID0gRikNCn0NCg0KI0xvYWQgYWxsIHBhY2thZ2VzDQpzYXBwbHkoYyguY3Jhbl9wYWNrYWdlcywuYmlvY19wYWNrYWdlcyksIHJlcXVpcmUsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCg0KIyBHaXRodWIgcGFja2FnZXMNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiYWxleHBpcGVyL3RheHJldHVybiIpDQpsaWJyYXJ5KHRheHJldHVybikNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiYWxleHBpcGVyL3NlcWF0ZXVycyIpDQpsaWJyYXJ5KHNlcWF0ZXVycykNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigibWlrZW1jL3NwZWVkeXNlcSIpDQpsaWJyYXJ5KHNwZWVkeXNlcSkNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YignZ2dsb29yL0NvRGFTZXEvQ29EYVNlcScpDQpsaWJyYXJ5KENvRGFTZXEpDQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImVhc3lzdGF0cy9yZXBvcnQiKQ0KbGlicmFyeShyZXBvcnQpDQoNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigicG1hcnRpbmV6YXJiaXp1L3BhaXJ3aXNlQWRvbmlzL3BhaXJ3aXNlQWRvbmlzIikNCmxpYnJhcnkocGFpcndpc2VBZG9uaXMpDQoNCiNTb3VyY2UgaW50ZXJuYWwgZnVuY3Rpb25zDQpzb3VyY2UoJ1IvQkRUVC5SJykNCnNvdXJjZSgnUi9waHlsb3N5bWJpb3Npcy5SJykNCnNvdXJjZSgnUi9oZWxwZXJfZnVuY3Rpb25zLlInKQ0Kc291cmNlKCdSL3RoZW1lcy5SJykNCm9wdGlvbnMoc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KYGBgDQoNCiMgUmVhZCBpbiBwaHlsb3NlcSBvYmplY3QNCg0KYGBge3IgcmVhZCBpbiBwaHlsb3NlcX0NCnBzMiA8LSByZWFkUkRTKCJvdXRwdXQvcmRzL3BzMi5yZHMiKQ0KDQojIGV4cG9ydCBmaWx0ZXJlZA0KZGlyLmNyZWF0ZSgib3V0cHV0L290dV90YWJsZXMvZmlsdGVyZWQiKQ0Kc2VxYXRldXJzOjpzdW1tYXJpc2VfdGF4YShwczIsICJzcGVjaWVzIiwgIlNhbXBsZUlEIikgJT4lDQogIHNwcmVhZChrZXk9IlNhbXBsZUlEIiwgdmFsdWU9InRvdGFsUkEiKSAlPiUNCiAgd3JpdGUuY3N2KGZpbGUgPSAib3V0cHV0L290dV90YWJsZXMvZmlsdGVyZWQvZmlsdGVyZWRfc3BwX3N1bS5jc3YiKQ0KDQpzZXFhdGV1cnM6OnN1bW1hcmlzZV90YXhhKHBzMiwgImdlbnVzIiwgIlNhbXBsZUlEIikgJT4lDQogIHNwcmVhZChrZXk9IlNhbXBsZUlEIiwgdmFsdWU9InRvdGFsUkEiKSAlPiUNCiAgd3JpdGUuY3N2KGZpbGUgPSAib3V0cHV0L290dV90YWJsZXMvZmlsdGVyZWQvZmlsdGVyZWRfZ2VuX3N1bS5jc3YiKQ0KDQojUmVuYW1lIHRheGEgLSBvbmx5IGtlZXAgZmlyc3QgMzAgY2hhcmFjdGVycw0KdGF4YV9uYW1lcyhwczIpIDwtIHN1YnN0cihwYXN0ZTAoIlNWIiwgc2VxKG50YXhhKHBzMikpLCItIix0YXhfdGFibGUocHMyKVssN10pLCAxLDMwKQ0KDQojQ2hlY2sgY2Fyc29uZWxsYSBwcmVzZW5jZQ0KY2FycyA8LSBzcGVlZHlzZXE6OnBzbWVsdChwczIpICU+JQ0KICBmaWx0ZXIoQWJ1bmRhbmNlID4gMCkgJT4lDQogIGdyb3VwX2J5KHBzeWxsaWRfc3BwKSAlPiUNCiAgc3VtbWFyaXNlKG4gPSBjb3VudChnZW51cz09IkNhbmRpZGF0dXMgQ2Fyc29uZWxsYSIsIG5hLnJtID0gVFJVRSkpDQoNCmBgYA0KDQojIyBDcmVhdGUgc3BlY2llcyBtZXJnZWQgdGFibGUNCg0KYGBge3IgbWVyZ2Vkc3BwfQ0KIyBNZXJnZSBzcGVjaWVzIGZvciBiZXRhIGRpdmVyc2l0eQ0KcHMuc3BwbWVyZ2VkIDwtIHBzMiAlPiUNCiAgICBtZXJnZV9zYW1wbGVzKGdyb3VwID0gInBzeWxsaWRfc3BwIiwgZnVuPW1lYW4pDQoNCiNUaGlzIGxvc2VzIHRoZSBzYW1wbGUgbWV0YWRhdGEgLSBOZWVkIHRvIGFkZCBpdCBhZ2lhbg0Kc2FtcGxlX2RhdGEocHMuc3BwbWVyZ2VkKSA8LSBzYW1wbGVfZGF0YShwczIpICU+JQ0KICBhcygibWF0cml4IikgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgZmlsdGVyKCFkdXBsaWNhdGVkKHBzeWxsaWRfc3BwKSkgJT4lDQogIG1hZ3JpdHRyOjpzZXRfcm93bmFtZXMoLiRwc3lsbGlkX3NwcCkNCg0Kc2VxcyA8LSByZWZzZXEocHMyKQ0KdHJlZSA8LSBwaHlfdHJlZShwczIpDQojbWFrZSBuZXcgcGh5bG9zZXEgb2JqZWN0DQpwczMgPC0gcGh5bG9zZXEodGF4X3RhYmxlKHBzLnNwcG1lcmdlZCksDQogICAgICAgICAgICAgICBzYW1wbGVfZGF0YShwcy5zcHBtZXJnZWQpLA0KICAgICAgICAgICAgICAgb3R1X3RhYmxlKG90dV90YWJsZShwcy5zcHBtZXJnZWQpLCB0YXhhX2FyZV9yb3dzID0gRkFMU0UpLA0KICAgICAgICAgICAgICAgcmVmc2VxKHNlcXMpLA0KICAgICAgICAgICAgICAgcGh5X3RyZWUodHJlZSkpDQoNCmBgYA0KDQojIyBSZWFkIGluIHBzeWxsaWQgcGh5bG9nZW55DQpgYGB7UiBwc3lsbGlkIHBoeWxvZ2VueX0NCnBzeWxsaWRfdHJlZSA8LSByZWFkLnRyZWUodGV4dD1yZWFkTGluZXMoInNhbXBsZV9kYXRhL3BzeWxsaWRfYmVhc3RfdHJlZS5ud2siKSkNCg0KIyBNYXRjaCBuYW1lcyB3aXRoIHNhbXBsZSBzaGVldA0KcHN5bGxpZF90cmVlJHRpcC5sYWJlbCA8LSBwc3lsbGlkX3RyZWUkdGlwLmxhYmVsICU+JQ0KICBzdHJfc3F1aXNoKCkgJT4lDQogIHN0cl9yZXBsYWNlX2FsbChwYXR0ZXJuPSJcXC4iLCByZXBsYWNlbWVudD0iICIpICU+JQ0KICBzdHJfcmVwbGFjZV9hbGwocGF0dGVybj0iQWNpenppYSBoYWthZSIsIHJlcGxhY2VtZW50PSJBY2l6emlhIGhha2VhZSIpICU+JQ0KICBzdHJfcmVwbGFjZV9hbGwocGF0dGVybj0iUE9MTEVOSVNMQU5EIiwgcmVwbGFjZW1lbnQ9IlBPTExFTiBJU0xBTkQiKSAlPiUNCiAgc3RyX3JlcGxhY2VfYWxsKHBhdHRlcm49IkN0ZW5hcnl0YWluYSBmdWNoc2lhZSQiLCByZXBsYWNlbWVudD0iQ3RlbmFyeXRhaW5hIGZ1Y2hzaWEgQSIpICU+JQ0KICBzdHJfcmVwbGFjZV9hbGwocGF0dGVybj0iQ3RlbmFyeXRhaW5hIGZ1Y2hzaWFlQiIsIHJlcGxhY2VtZW50PSJDdGVuYXJ5dGFpbmEgZnVjaHNpYSBCIikgJT4lDQogIHN0cl9yZXBsYWNlX2FsbChwYXR0ZXJuPSJDdGVuYXJ5dGFpbmEgZnVjaHNpYWVDIiwgcmVwbGFjZW1lbnQ9IkN0ZW5hcnl0YWluYSBmdWNoc2lhIEMiKSAlPiUNCiAgc3RyX3JlcGxhY2VfYWxsKHBhdHRlcm49IkN0ZW5hcnl0YWluYSBjbGF2YXRhIiwgcmVwbGFjZW1lbnQ9IkN0ZW5hcnl0YWluYSBjbGF2YXRhIHNwICIpICU+JQ0KICBzdHJfcmVwbGFjZV9hbGwocGF0dGVybj0iQ3RlbmFyeXRhaW5hIGNsYXZhdGEgc3AgJCIsIHJlcGxhY2VtZW50PSJDdGVuYXJ5dGFpbmEgY2xhdmF0YSBzcCBBIikgJT4lDQogIHN0cl9yZXBsYWNlX2FsbChwYXR0ZXJuPSJDdGVuYXJ5dGFpbmEgc3AkIiwgcmVwbGFjZW1lbnQ9IkN0ZW5hcnl0YWluYSBzcCAiKSAlPiUNCiAgc3RyX3JlcGxhY2VfYWxsKHBhdHRlcm49IkN0ZW5hcnl0YWluYSBzcEEiLCByZXBsYWNlbWVudD0iQ3RlbmFyeXRhaW5hIHNwIEEiKSAlPiUNCiAgICBzdHJfcmVwbGFjZV9hbGwocGF0dGVybj0iQ3RlbmFyeXRhaW5hIHNwQiIsIHJlcGxhY2VtZW50PSJDdGVuYXJ5dGFpbmEgc3AgQiIpICU+JQ0KICBzdHJfcmVwbGFjZV9hbGwocGF0dGVybj0iQ3RlbmFyeXRhaW5hIHVua25vd24iLCByZXBsYWNlbWVudD0iQ3RlbmFyeXRhaW5hIGluc3VsYXJpcyIpICU+JSAgDQogIHN0cl9yZXBsYWNlX2FsbChwYXR0ZXJuPSJQc3lsbGEgYXBpY2FsaXNBIiwgcmVwbGFjZW1lbnQ9IlBzeWxsYSBmcm9kb2JhZ2dpbnNpIikgJT4lDQogIHN0cl9yZXBsYWNlX2FsbChwYXR0ZXJuPSJQc3lsbGEgYXBpY2FsaXNCIiwgcmVwbGFjZW1lbnQ9IlBzeWxsYSBhcGljYWxpcyIpICU+JQ0KICBzdHJfcmVwbGFjZV9hbGwocGF0dGVybj0iY2FybWljaGFlbGlhZSIsIHJlcGxhY2VtZW50PSJjYXJtaWNoYWVsaWFlICIpICU+JQ0KICBzdHJfcmVwbGFjZV9hbGwocGF0dGVybj0iVHJpb3phIHNwIiwgcmVwbGFjZW1lbnQ9IlRyaW96YSBzcCAiKSAlPiUNCiAgc3RyX3JlcGxhY2VfYWxsKHBhdHRlcm49IlRyaW96YSBhY3V0YUIiLCByZXBsYWNlbWVudD0iVHJpb3phIGFjdXRhIEIiKSAlPiUNCiAgc3RyX3JlcGxhY2VfYWxsKHBhdHRlcm49IlRyaW96YSBnb3VybGF5IiwgcmVwbGFjZW1lbnQ9IlRyaW96YSBnb3VybGF5aSIpICU+JQ0KICBzdHJfcmVwbGFjZV9hbGwocGF0dGVybj0iQlJFTkRBTUFZIiwgcmVwbGFjZW1lbnQ9IkJSRU5EQSBNQVkiKSAlPiUNCiAgc3RyX3JlcGxhY2VfYWxsKHBhdHRlcm49IlBSSUNFUyIsIHJlcGxhY2VtZW50PSJQUklDRVMgVkFMTEVZIikgJT4lICANCiAgc3RyX3JlcGxhY2VfYWxsKHBhdHRlcm49IkFjaXp6aWEgc3AiLCByZXBsYWNlbWVudD0iQWNpenppYSBlcnJhYnVuZGEiKSAlPiUgDQogIHN0cl9yZXBsYWNlX2FsbChwYXR0ZXJuPSJUcmlvemEgIiwgcmVwbGFjZW1lbnQ9IlBvd2VsbGlhICIpICU+JQ0KICBzdHJfcmVwbGFjZV9hbGwocGF0dGVybj0iVHJpb3ppZCBzcCIsIHJlcGxhY2VtZW50PSJDYXN1YXJpbmljb2xhIHNwIikgJT4lDQogIHN0cl9yZXBsYWNlX2FsbChwYXR0ZXJuPSJQb3dlbGxpYSBhZHZlbnRpY2lhIiwgcmVwbGFjZW1lbnQ9IlRyaW96YSBhZHZlbnRpY2lhIikgJT4lDQogIHN0cl9yZXBsYWNlX2FsbChwYXR0ZXJuPSJQb3dlbGxpYSBjdXJ0YSIsIHJlcGxhY2VtZW50PSJUcmlvemEgY3VydGEiKSAlPiUNCiAgc3RyX3JlcGxhY2VfYWxsKHBhdHRlcm49IiAiLCByZXBsYWNlbWVudD0iXyIpICU+JQ0KICB0cmltd3Mod2hpY2g9InJpZ2h0IikNCg0KDQojIFN1YnNldCB0byBvbmx5IHRob3NlIGluIHNhbXBsZSBkYXRhDQpzZXRkaWZmKHBzeWxsaWRfdHJlZSR0aXAubGFiZWwsc2FtcGxlX2RhdGEocHMyKSRwc3lsbGlkX3NwcCkNCnNldGRpZmYoc2FtcGxlX2RhdGEocHMyKSRwc3lsbGlkX3NwcCwgcHN5bGxpZF90cmVlJHRpcC5sYWJlbCkNCg0KcHN5bGxpZF90cmVlJHRpcC5sYWJlbFshcHN5bGxpZF90cmVlJHRpcC5sYWJlbCAlaW4lIHNhbXBsZV9kYXRhKHBzMikkcHN5bGxpZF9zcHBdDQpwc3lsbGlkX3RyZWUkdGlwLmxhYmVsWyFzYW1wbGVfZGF0YShwczIpJHBzeWxsaWRfc3BwICVpbiUgcHN5bGxpZF90cmVlJHRpcC5sYWJlbCBdDQpwcnVuZWQudHJlZSA8LSBkcm9wLnRpcChwc3lsbGlkX3RyZWUsIHBzeWxsaWRfdHJlZSR0aXAubGFiZWxbIXBzeWxsaWRfdHJlZSR0aXAubGFiZWwgJWluJSBzYW1wbGVfZGF0YShwczIpJHBzeWxsaWRfc3BwXSApDQoNCmBgYA0KDQoNCiMjIFN1bW1hcnkgc3RhdGlzdGljcw0KDQpgYGB7ciBzdW0gdGF4YX0NCiMgTiB1bmlxdWUgc3BlY2llcyBhbmQgc2FtcGxlcw0Kc3BlZWR5c2VxOjpwc21lbHQocHMyKSAlPiUNCiAgc3VtbWFyaXNlKG50YXhhPSBuX2Rpc3RpbmN0KHBzeWxsaWRfc3BwKSwgbl9zYW1wbGVzID0gbl9kaXN0aW5jdChTYW1wbGVfTmFtZSksIG5faG9zdHBsYW50cyA9IG5fZGlzdGluY3QoaG9zdHBsYW50X3NwcCkpDQoNCiMgU3ByZWFkIG9mIHJlYWRzDQpzcGVlZHlzZXE6OnBzbWVsdChwczIpICU+JQ0KICBncm91cF9ieShTYW1wbGVfTmFtZSkgJT4lDQogIHN1bW1hcmlzZShBYnVuZGFuY2UgPSBzdW0oQWJ1bmRhbmNlKSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc3VtbWFyaXNlKG1lYW4gPSBtZWFuKEFidW5kYW5jZSksIA0KICAgICAgICAgICAgc2UgPSBzZChBYnVuZGFuY2UpL3NxcnQobGVuZ3RoKEFidW5kYW5jZSkpLA0KICAgICAgICAgICAgbWF4ID0gbWF4KEFidW5kYW5jZSksDQogICAgICAgICAgICBtaW4gPSBtaW4oQWJ1bmRhbmNlKSkNCg0KIyBTcHJlYWQgb2YgQVNWcw0Kc3BlZWR5c2VxOjpwc21lbHQocHMyKSAlPiUNCiAgZ3JvdXBfYnkoU2FtcGxlX05hbWUpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKEFidW5kYW5jZSA+IDApICU+JQ0KICBzdW1tYXJpc2UoY291bnRzID0gbl9kaXN0aW5jdChPVFUpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzdW1tYXJpc2UobWVhbiA9IG1lYW4oY291bnRzKSwgDQogICAgICAgICAgICBzZSA9IHNkKGNvdW50cykvc3FydChsZW5ndGgoY291bnRzKSksDQogICAgICAgICAgICBtYXggPSBtYXgoY291bnRzKSwNCiAgICAgICAgICAgIG1pbiA9IG1pbihjb3VudHMpKQ0KDQojRnJhY3Rpb24gb2YgcmVhZHMgYXNzaWduZWQgdG8gZWFjaCB0YXhvbm9taWMgcmFuaw0Kc3BlZWR5c2VxOjpwc21lbHQocHMyKSAlPiUNCiAgZ2F0aGVyKCJSYW5rIiwiTmFtZSIscmFua19uYW1lcyhwczIpKSAlPiUNCiAgZ3JvdXBfYnkoUmFuaykgJT4lIA0KICBtdXRhdGUoTmFtZSA9IHJlcGxhY2UoTmFtZSwgc3RyX2RldGVjdChOYW1lLCAiX18iKSxOQSkpICU+JSAjIFRoaXMgbGluZSB0dXJucyB0aGUgIl9fIiB3ZSBhZGRlZCB0byBsb3dlciByYW5rcyBiYWNrIHRvIE5BJ3MNCiAgZHBseXI6OnN1bW1hcmlzZShSZWFkc19jbGFzc2lmaWVkID0gc3VtKEFidW5kYW5jZSAqICFpcy5uYShOYW1lKSkpICU+JQ0KICBtdXRhdGUoRnJhY19yZWFkcyA9IFJlYWRzX2NsYXNzaWZpZWQgLyBzdW0oc2FtcGxlX3N1bXMocHMyKSkpICU+JQ0KICBtdXRhdGUoUmFuayA9IGZhY3RvcihSYW5rLCByYW5rX25hbWVzKHBzMikpKSAlPiUNCiAgYXJyYW5nZShSYW5rKQ0KDQojRnJhY3Rpb24gb2YgQVNWJ3MgYXNzaWduZWQgdG8gZWFjaCB0YXhvbm9taWMgcmFuaw0KdGF4X3RhYmxlKHBzMikgJT4lDQogIGFzKCJtYXRyaXgiKSAlPiUNCiAgYXNfdGliYmxlKHJvd25hbWVzPSJPVFUiKSAlPiUNCiAgZ2F0aGVyKCJSYW5rIiwiTmFtZSIscmFua19uYW1lcyhwczIpKSAlPiUNCiAgZ3JvdXBfYnkoUmFuaykgJT4lDQogIG11dGF0ZShOYW1lID0gcmVwbGFjZShOYW1lLCBzdHJfZGV0ZWN0KE5hbWUsICJfXyIpLCBOQSkpICU+JSAjIFRoaXMgbGluZSB0dXJucyB0aGUgIl9fIiB3ZSBhZGRlZCB0byBsb3dlciByYW5rcyBiYWNrIHRvIE5BJ3MNCiAgZHBseXI6OnN1bW1hcmlzZShPVFVzX2NsYXNzaWZpZWQgPSBzdW0oIWlzLm5hKE5hbWUpKSkgJT4lDQogIG11dGF0ZShGcmFjX09UVXMgPSBPVFVzX2NsYXNzaWZpZWQgLyBudGF4YShwczIpKSAlPiUNCiAgbXV0YXRlKFJhbmsgPSBmYWN0b3IoUmFuaywgcmFua19uYW1lcyhwczIpKSkgJT4lDQogIGFycmFuZ2UoUmFuaykNCg0KIyBVbmlxdWUgdGF4YSBhdCBlYWNoIHJhbmsNCnNwZWVkeXNlcTo6cHNtZWx0KHBzMikgJT4lDQogIGRwbHlyOjpzZWxlY3QocmFua19uYW1lcyhwczIpKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKGV2ZXJ5dGhpbmcoKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIlJhbmsiLA0KICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lDQogIG11dGF0ZSh2YWx1ZSA9IGNhc2Vfd2hlbigNCiAgICBzdHJfZGV0ZWN0KHZhbHVlLCAiX18iKSB+IGFzLmNoYXJhY3RlcihOQSksDQogICAgIXN0cl9kZXRlY3QodmFsdWUsICJfXyIpIH4gdmFsdWUNCiAgKSkgJT4lDQogIGRyb3BfbmEoKSAlPiUNCiAgZ3JvdXBfYnkoUmFuaykgJT4lDQogIHN1bW1hcmlzZV9hbGwoZnVucyhuX2Rpc3RpbmN0KSkgJT4lDQogIG11dGF0ZShSYW5rID0gZmFjdG9yKFJhbmssIHJhbmtfbmFtZXMocHMyKSkpICU+JQ0KICBhcnJhbmdlKFJhbmspDQoNCiMgRWFjaCBkaWZmZXJlbnQgcGh5bHVtIHJhbmtlZCBieSBpdHMgb3ZlcmFsbCByZWxhdGl2ZSBhYnVuZGFuY2UNCnNhbXBsZV9kYXRhKHBzMikgJT4lDQogIGFzKCJtYXRyaXgiKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBwdWxsKHBzeWxsaWRfc3BwKSAlPiUNCiAgdGFibGUoKSAlPiUNCiAgc29ydCgpDQojIFRyYW5zZm9ybSB0byBwZXIgc2FtcGxlIHJlbGF0aXZlIGFidW5kYW5jZSwgdGhlbiB0cmFuc2Zvcm0gdG8gd2hvbGUgZGF0YXNldCByZWxhdGl2ZSBhYnVuZGFuY2UNCmBgYA0KDQojIyBQcmV2YWxlbmNlIC8gQWJ1bmRhbmNlIHN1bW1hcnkNCg0KVmlldyBwcmV2YWxlbmNlIG9mIGRpZmZlcmVudCBwaHlsYSBhY3Jvc3MgdGhlIGRhdGFzZXQNCg0KYGBge3IgcHJldmFsZW5jZS1hc3Nlc3NtZW50fQ0KIyBDYWxjdWxhdGUgdGF4b24gcHJldmFsZW5jZSBhY3Jvc3MgdGhlIGRhdGEgc2V0IGF0IE9UVSBsZXZlbA0KcHJldmRmIDwtIGFwcGx5KFggPSBvdHVfdGFibGUocHMyKSwgTUFSR0lOID0gaWZlbHNlKHRheGFfYXJlX3Jvd3MocHMyKSwgeWVzID0gMSwgbm8gPSAyKSwgRlVOID0gZnVuY3Rpb24oeCl7c3VtKHggPiAwKX0pDQpwcmV2ZGYgPC0gZGF0YS5mcmFtZShQcmV2YWxlbmNlID0gcHJldmRmLCANCiAgICAgICAgICAgICAgICAgICAgIFRvdGFsQWJ1bmRhbmNlID0gdGF4YV9zdW1zKHBzMiksDQogICAgICAgICAgICAgICAgICAgICB0YXhfdGFibGUocHMyKSkNCiNQcmV2YWxlbmNlIHBsb3QNCmdnLnByZXYgPC0gc3Vic2V0KHByZXZkZiwgcGh5bHVtICVpbiUgZ2V0X3RheGFfdW5pcXVlKHBzMiwgInBoeWx1bSIpKSAlPiUNCiAgZ2dwbG90KGFlcyhUb3RhbEFidW5kYW5jZSwgUHJldmFsZW5jZSAvIG5zYW1wbGVzKHBzMiksY29sb3I9b3JkZXIpKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDMsIGFscGhhID0gMC43KSArDQogIHNjYWxlX3hfbG9nMTAoKSArDQogIHhsYWIoIlRvdGFsIEFidW5kYW5jZSIpICsgeWxhYigiUHJldmFsZW5jZSBbRnJhYy4gU2FtcGxlc10iKSArDQogIGZhY2V0X3dyYXAofnBoeWx1bSkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArDQogIGdndGl0bGUoIlBoeWx1bSBQcmV2YWxlbmNlIGluIEFsbCBTYW1wbGVzXG5Db2xvcmVkIGJ5IE9yZGVyIikNCg0KcGRmKGZpbGU9ImZpZ3MvcHJldmFsZW5jZS5wZGYiLCB3aWR0aCA9IDExLCBoZWlnaHQgPSA4ICwgcGFwZXI9ImE0ciIpDQogIHBsb3QoZ2cucHJldikNCnRyeShkZXYub2ZmKCksIHNpbGVudD1UUlVFKQ0KICANCiAgDQojIFByZXZhbGVjbmUgYXQgcGh5bHVtDQpwcy5waHlsdW0gPC0gc3BlZWR5c2VxOjp0YXhfZ2xvbShwczIsIHRheHJhbms9InBoeWx1bSIpDQpwcmV2ZGZfcGh5bHVtIDwtIGFwcGx5KFggPSBvdHVfdGFibGUocHMucGh5bHVtICksIE1BUkdJTiA9IGlmZWxzZSh0YXhhX2FyZV9yb3dzKHBzLnBoeWx1bSApLCB5ZXMgPSAxLCBubyA9IDIpLCBGVU4gPSBmdW5jdGlvbih4KXtzdW0oeCA+IDApfSkNCnByZXZkZl9waHlsdW0gPC0gZGF0YS5mcmFtZShQcmV2YWxlbmNlID0gcHJldmRmX3BoeWx1bSwgDQogICAgICAgICAgICAgICAgICAgICBUb3RhbEFidW5kYW5jZSA9IHRheGFfc3Vtcyhwcy5waHlsdW0pLA0KICAgICAgICAgICAgICAgICAgICAgdGF4X3RhYmxlKHBzLnBoeWx1bSkpICU+JQ0KICBkcGx5cjo6bXV0YXRlKFJBID0gVG90YWxBYnVuZGFuY2UgLyBzdW0oVG90YWxBYnVuZGFuY2UpKSAlPiUNCiAgcmVtb3ZlX3Jvd25hbWVzKCkgJT4lDQogIG1hZ3JpdHRyOjpzZXRfcm93bmFtZXMoLiRwaHlsdW0pICU+JQ0KICBkcGx5cjo6c2VsZWN0KC1yYW5rX25hbWVzKHBzLnBoeWx1bSkpDQoNCiMgUHJldmFsZW5jZSB3aXRoaW4gUHJvdGVvYmFjdGVyaWENCnBzLnByb3QgPC0gc3Vic2V0X3RheGEocHMyLCBwaHlsdW09PSJQcm90ZW9iYWN0ZXJpYSIpICU+JQ0KICAgICAgICAgIHNwZWVkeXNlcTo6dGF4X2dsb20odGF4cmFuaz0ib3JkZXIiKQ0KcHJldmRmX3Byb3QgPC0gYXBwbHkoWCA9IG90dV90YWJsZShwcy5wcm90ICksIE1BUkdJTiA9IGlmZWxzZSh0YXhhX2FyZV9yb3dzKHBzLnByb3QgKSwgeWVzID0gMSwgbm8gPSAyKSwgRlVOID0gZnVuY3Rpb24oeCl7c3VtKHggPiAwKX0pDQpwcmV2ZGZfcHJvdCA8LSBkYXRhLmZyYW1lKFByZXZhbGVuY2UgPSBwcmV2ZGZfcHJvdCwgDQogICAgICAgICAgICAgICAgICAgICBUb3RhbEFidW5kYW5jZSA9IHRheGFfc3Vtcyhwcy5wcm90KSwNCiAgICAgICAgICAgICAgICAgICAgIHRheF90YWJsZShwcy5wcm90KSkgJT4lDQogIGRwbHlyOjptdXRhdGUoUkEgPSBUb3RhbEFidW5kYW5jZSAvIHN1bShUb3RhbEFidW5kYW5jZSkpICU+JQ0KICByZW1vdmVfcm93bmFtZXMoKSAlPiUNCiAgbWFncml0dHI6OnNldF9yb3duYW1lcyguJG9yZGVyKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtcmFua19uYW1lcyhwcy5wcm90KSkNCiAgDQojIEdlbnVzIFByZXZhbGVuY2UNCnBzLmdlbiA8LSBzcGVlZHlzZXE6OnRheF9nbG9tKHBzMiwgdGF4cmFuaz0iZ2VudXMiKSANCnByZXZkZl9nZW4gPC0gYXBwbHkoWCA9IG90dV90YWJsZShwcy5nZW4gKSwgTUFSR0lOID0gaWZlbHNlKHRheGFfYXJlX3Jvd3MocHMuZ2VuICksIHllcyA9IDEsIG5vID0gMiksIEZVTiA9IGZ1bmN0aW9uKHgpe3N1bSh4ID4gMCl9KQ0KcHJldmRmX2dlbiA8LSBkYXRhLmZyYW1lKFByZXZhbGVuY2UgPSBwcmV2ZGZfZ2VuLCANCiAgICAgICAgICAgICAgICAgICAgIFRvdGFsQWJ1bmRhbmNlID0gdGF4YV9zdW1zKHBzLmdlbiksDQogICAgICAgICAgICAgICAgICAgICB0YXhfdGFibGUocHMuZ2VuKSkgJT4lDQogIGRwbHlyOjptdXRhdGUoUkEgPSBUb3RhbEFidW5kYW5jZSAvIHN1bShUb3RhbEFidW5kYW5jZSkpICU+JQ0KICByZW1vdmVfcm93bmFtZXMoKSAlPiUNCiAgbXV0YXRlKGdlbnVzID0gbWFrZS51bmlxdWUoZ2VudXMpKSAlPiUNCiAgbWFncml0dHI6OnNldF9yb3duYW1lcyguJGdlbnVzKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtcmFua19uYW1lcyhwcy5nZW4pKQ0KDQojIEdlbnVzIFByZXZhbGVuY2UgYWNyb3NzIHNwZWNpZXMgcmF0aGVyIHRoYW4gc3BlY2ltZW5zDQpwcy5nZW4gPC0gc3BlZWR5c2VxOjp0YXhfZ2xvbShwczMsIHRheHJhbms9ImdlbnVzIikgDQpwcmV2ZGZfZ2VuIDwtIGFwcGx5KFggPSBvdHVfdGFibGUocHMuZ2VuICksIE1BUkdJTiA9IGlmZWxzZSh0YXhhX2FyZV9yb3dzKHBzLmdlbiApLCB5ZXMgPSAxLCBubyA9IDIpLCBGVU4gPSBmdW5jdGlvbih4KXtzdW0oeCA+IDApfSkNCnByZXZkZl9nZW4gPC0gZGF0YS5mcmFtZShQcmV2YWxlbmNlID0gcHJldmRmX2dlbiwgDQogICAgICAgICAgICAgICAgICAgICBUb3RhbEFidW5kYW5jZSA9IHRheGFfc3Vtcyhwcy5nZW4pLA0KICAgICAgICAgICAgICAgICAgICAgdGF4X3RhYmxlKHBzLmdlbikpICU+JQ0KICBkcGx5cjo6bXV0YXRlKFJBID0gVG90YWxBYnVuZGFuY2UgLyBzdW0oVG90YWxBYnVuZGFuY2UpKSAlPiUNCiAgcmVtb3ZlX3Jvd25hbWVzKCkgJT4lDQogIG11dGF0ZShnZW51cyA9IG1ha2UudW5pcXVlKGdlbnVzKSkgJT4lDQogIG1hZ3JpdHRyOjpzZXRfcm93bmFtZXMoLiRnZW51cykgJT4lDQogIGRwbHlyOjpzZWxlY3QoLXJhbmtfbmFtZXMocHMuZ2VuKSkNCg0KIyBQcmV2YWxlbmNlIG9mIHN5bWJpb250cyBhY3Jvc3MgcHN5bGxpZCBzcGVjaWVzDQpzcGVlZHlzZXE6OnBzbWVsdChwczIpICU+JQ0KICBtdXRhdGUodG90YWxfc3BwICA9IG5fZGlzdGluY3QocHN5bGxpZF9zcHApLCB0b3RhbF9zcGVjaW1lbiA9IG5fZGlzdGluY3QoU2FtcGxlX05hbWUpKSAlPiUNCiAgZmlsdGVyKEFidW5kYW5jZSA+IDApICU+JQ0KICBmaWx0ZXIoZ2VudXMgJWluJSBjKCJDYW5kaWRhdHVzIENhcnNvbmVsbGEiLCAiQXJzZW5vcGhvbnVzIiwgIlNvZGFsaXMiKSkgJT4lDQogIGdyb3VwX2J5KGdlbnVzLCB0b3RhbF9zcHAsIHRvdGFsX3NwZWNpbWVuKSU+JQ0KICBzdW1tYXJpc2Uobl9zcGVjaWVzID0gbl9kaXN0aW5jdChwc3lsbGlkX3NwcCksIG5fc3BlY2ltZW4gPSBuX2Rpc3RpbmN0KFNhbXBsZV9OYW1lKSkgJT4lDQogIHVuZ3JvdXAoKSU+JQ0KICBtdXRhdGUocHJvcF9zcGVjaWVzID0gbl9zcGVjaWVzIC8gdG90YWxfc3BwLA0KICAgICAgICAgcHJvcF9zcGVjaW1lbiA9IG5fc3BlY2ltZW4gLyB0b3RhbF9zcGVjaW1lbikNCg0KIyBOdW1iZXIgb2Ygc3ltYmlvbnQgT1RVcyBwZXIgcHN5bGxpZCBzcGVjaWVzDQpzcGVlZHlzZXE6OnBzbWVsdChwczIpICU+JQ0KICBmaWx0ZXIoQWJ1bmRhbmNlID4gMCkgJT4lDQogIGZpbHRlcihnZW51cyAlaW4lIGMoIkNhbmRpZGF0dXMgQ2Fyc29uZWxsYSIsICJBcnNlbm9waG9udXMiLCAiU29kYWxpcyIpKSAlPiUNCiAgZ3JvdXBfYnkocHN5bGxpZF9zcHAsIGdlbnVzKSAlPiUNCiAgc3VtbWFyaXNlKG4gPSBuX2Rpc3RpbmN0KE9UVSkpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBwc3lsbGlkX3NwcCwgeSA9IG4sIGZpbGw9Z2VudXMpKSsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkrDQogIGZhY2V0X2dyaWQoZ2VudXN+LikrDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCBoanVzdD0xKSkgKw0KICBsYWJzKHggPSAiUHN5bGxpZCBTcGVjaWVzIiwNCiAgICAgICB5ID0gIk51bWJlciBvZiBkaXN0aW5jdCBBU1ZzIikNCg0Kc2UgPC0gZnVuY3Rpb24oeCkgc3FydCh2YXIoeCkvbGVuZ3RoKHgpKQ0KDQojIE1lYW4gYWJ1bmRhbmNlIG9mICBnZW5lcmENCmdlbmVyYV9hYnVuZCA8LSBzcGVlZHlzZXE6OnBzbWVsdChwczIpICU+JQ0KICBmaWx0ZXIoQWJ1bmRhbmNlID4gMCkgJT4lDQogIGdyb3VwX2J5KFNhbXBsZUlEKSAlPiUNCiAgICBtdXRhdGVfYXQodmFycyhBYnVuZGFuY2UpLCB+IC4gLyBzdW0oLikgKSAlPiUNCiAgdW5ncm91cCAlPiUNCiAgZ3JvdXBfYnkoZ2VudXMpICU+JQ0KICBzdW1tYXJpc2UobWVhbl9yYSA9IG1lYW4oQWJ1bmRhbmNlKSwgdXBwZXIgPSBtYXgoQWJ1bmRhbmNlKSwgbG93ZXIgPSBtaW4oQWJ1bmRhbmNlKSwgc2UgPSBzZShBYnVuZGFuY2UpKSANCiAgDQpgYGANCg0KIyMgQWxwaGEgZGl2ZXJzaXR5IG1ldHJpY3MNCg0KYGBge3IgYWxwaGEgZGl2ZXJzaXR5fQ0KZGlyLmNyZWF0ZSgib3V0cHV0L2FscGhhIikNCiMgR2V0IHJpY2huZXNzIG1lYXN1cmVzDQpyaWNobmVzcyA8LSBwaHlsb3NlcTo6ZXN0aW1hdGVfcmljaG5lc3MocHMyLCBtZWFzdXJlcz1jKCJTaGFubm9uIikpICU+JQ0KICByb3duYW1lc190b19jb2x1bW4oIlNhbXBsZV9OYW1lIikgJT4lDQogIG11dGF0ZShTYW1wbGVfTmFtZSA9IFNhbXBsZV9OYW1lICU+JSANCiAgICAgICAgICAgc3RyX3JlbW92ZSgiXlgiKSAlPiUNCiAgICAgICAgICAgc3RyX3JlcGxhY2VfYWxsKCJcXC4iLCAiICIpKQ0KDQojU2V0IG51bWJlciBvZiByYW5kb21pc2F0aW9ucyBmb3IgY2FsY3VsYXRpbmcgc2lnbmlmaWNhbmNlDQojIENhbGN1bGF0ZSBGYWl0aCdzIFBELWluZGV4ICYgU3BlY2llcyByaWNobmVzcyAtIHdpdGggU3RhbmRhcmQgZXJyb3JzDQojc2VzcGQgPC0gcGljYW50ZTo6c2VzLnBkKGFzKHBoeWxvc2VxOjpvdHVfdGFibGUocHMyKSwgIm1hdHJpeCIpLCAgcGh5bG9zZXE6OnBoeV90cmVlKHBzMiksIG51bGwubW9kZWwgPSAidGF4YS5sYWJlbHMiLCBpbmNsdWRlLnJvb3QgPSBGLCBydW5zID0gOTkpDQoNCnBkIDwtIHBpY2FudGU6OnBkKGFzKHBoeWxvc2VxOjpvdHVfdGFibGUocHMyKSwgIm1hdHJpeCIpLCAgcGh5bG9zZXE6OnBoeV90cmVlKHBzMiksIGluY2x1ZGUucm9vdCA9IEZBTFNFKQ0KDQojIEpvaW4gdG9nZXRoZXINCmRpdl90YWJsZSA8LSBwZCAlPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCJTYW1wbGVfTmFtZSIpICU+JQ0KICBkcGx5cjo6c2VsZWN0KFNhbXBsZV9OYW1lLCBhbHBoYSA9IFNSLCBwZCA9IFBEKSAlPiUNCiAgbGVmdF9qb2luKHJpY2huZXNzLCBieT0iU2FtcGxlX05hbWUiKSAlPiUNCiAgbGVmdF9qb2luKHNhbXBsZV9kYXRhKHBzMikgJT4lIA0KICAgICAgICAgICAgICBhcygibWF0cml4IikgJT4lDQogICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgICAgICAgICAgICAgZmlsdGVyKCFkdXBsaWNhdGVkKFNhbXBsZV9OYW1lKSkgJT4lDQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoU2FtcGxlX05hbWUsIHBzeWxsaWRfc3BwLCBwc3lsbGlkX2dlbnVzLCBwc3lsbGlkX2ZhbWlseSwgaG9zdHBsYW50X3NwcCwgc2VxcnVuLCBnZW51c19nZW8pLA0KICAgICAgICAgICAgYnkgPSAiU2FtcGxlX05hbWUiKSANCg0KIyBTdW1tYXJpc2UgbWVhbnMNCmRpdl90YWJsZSAlPiUNCiAgc3VtbWFyaXNlX2lmKGlzLm51bWVyaWMsIG1lYW4pDQoNCg0KDQojIERpZmZlcmVuY2UgYmV0d2VlbiBzcGVjaWVzIGZvciBhbHBoYSBkaXZlcnNpdHkgQU5PVkENCnJlcG9ydDo6cmVwb3J0KGFvdihhbHBoYSB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K3BzeWxsaWRfZ2VudXMrcHN5bGxpZF9zcHAsIGRhdGE9ZGl2X3RhYmxlKSkNCnJlcG9ydDo6cmVwb3J0KGFvdihTaGFubm9uIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cytwc3lsbGlkX3NwcCwgZGF0YT1kaXZfdGFibGUpKQ0KcmVwb3J0OjpyZXBvcnQoYW92KHBkIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cytwc3lsbGlkX3NwcCwgZGF0YT1kaXZfdGFibGUpKQ0KDQojIERpZmZlcmVuY2UgYmV0d2VlbiBhbGwgZ2VuZXJhIGZvciBhbHBoYSBkaXZlcnNpdHkgQU5PVkENCnJlcG9ydDo6cmVwb3J0KGFvdihhbHBoYSB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K3BzeWxsaWRfZ2VudXMsIGRhdGE9ZGl2X3RhYmxlKSkNCnJlcG9ydDo6cmVwb3J0KGFvdihTaGFubm9uIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cywgZGF0YT1kaXZfdGFibGUpKQ0KcmVwb3J0OjpyZXBvcnQoYW92KHBkIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cywgZGF0YT1kaXZfdGFibGUpKQ0KDQojIERpZmZlcmVuY2UgYmV0d2VlbiBnZW51cy9nZW9ncmFwaHkgZmFjdG9ycyBBTk9WQQ0KcmVwb3J0OjpyZXBvcnQoYW92KGFscGhhIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrZ2VudXNfZ2VvLCBkYXRhPWRpdl90YWJsZSkpDQpyZXBvcnQ6OnJlcG9ydChhb3YoU2hhbm5vbiB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K2dlbnVzX2dlbywgZGF0YT1kaXZfdGFibGUpKQ0KcmVwb3J0OjpyZXBvcnQoYW92KHBkIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrZ2VudXNfZ2VvLCBkYXRhPWRpdl90YWJsZSkpDQoNCiMjIE1ham9yIGdlbmVyYSBvbmx5DQojIERpZmZlcmVuY2UgYmV0d2VlbiBhbGwgbWFqb3IgZ2VuZXJhIGZvciBhbHBoYSBkaXZlcnNpdHkgQU5PVkENCmRpdl90YWJsZTIgPC0gZGl2X3RhYmxlICU+JQ0KICBkcGx5cjo6ZmlsdGVyKHBzeWxsaWRfZ2VudXMgJWluJSBjKCJQb3dlbGxpYSIsICJDdGVuYXJ5dGFpbmEiLCAiUHN5bGxhIikpDQoNCm1nX2RpdiA8LSBiaW5kX3Jvd3MoYnJvb206OnRpZHkoVHVrZXlIU0QoYW92KGFscGhhIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9ZGl2X3RhYmxlMikpKSAlPiUgbXV0YXRlKHR5cGU9IlJpY2huZXNzIiksDQogICAgICAgICAgYnJvb206OnRpZHkoVHVrZXlIU0QoYW92KFNoYW5ub24gfnNlcXJ1bitwc3lsbGlkX2ZhbWlseStwc3lsbGlkX2dlbnVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhPWRpdl90YWJsZTIpKSkgJT4lIG11dGF0ZSh0eXBlPSJTaGFubm9uIiksDQogICAgICAgICAgYnJvb206OnRpZHkoVHVrZXlIU0QoYW92KHBkIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT1kaXZfdGFibGUyKSkpICU+JSBtdXRhdGUodHlwZT0iUGh5bG9nZW5ldGljIiksDQogICAgICAgICAgYnJvb206OnRpZHkoVHVrZXlIU0QoYW92KGFscGhhIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrZ2VudXNfZ2VvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhPWRpdl90YWJsZTIpKSkgJT4lIG11dGF0ZSh0eXBlPSJSaWNobmVzcyIpLA0KICAgICAgICAgIGJyb29tOjp0aWR5KFR1a2V5SFNEKGFvdihhbHBoYSB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K2dlbnVzX2dlbywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT1kaXZfdGFibGUyKSkpICU+JSBtdXRhdGUodHlwZT0iU2hhbm5vbiIpLA0KICAgICAgICAgIGJyb29tOjp0aWR5KFR1a2V5SFNEKGFvdihwZCB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K2dlbnVzX2dlbywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT1kaXZfdGFibGUyKSkpICU+JSBtdXRhdGUodHlwZT0iUGh5bG9nZW5ldGljIikNCiAgICAgICAgICApDQp3cml0ZV9jc3YobWdfZGl2LCAib3V0cHV0L2FscGhhL21ham9yX2dlbmVyYV9hbHBoYS5jc3YiKQ0KDQojIERpZmZlcmVuY2UgYmV0d2VlbiBtYWpvciBnZW5lcmEgb25seSBBTk9WQQ0KcmVwb3J0OjpyZXBvcnQoYW92KGFscGhhIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cywgZGF0YT1kaXZfdGFibGUyKSkNCnJlcG9ydDo6cmVwb3J0KGFvdihTaGFubm9uIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cywgZGF0YT1kaXZfdGFibGUyKSkNCnJlcG9ydDo6cmVwb3J0KGFvdihwZCB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K3BzeWxsaWRfZ2VudXMsIGRhdGE9ZGl2X3RhYmxlMikpDQoNCiMgRGlmZmVyZW5jZSBiZXR3ZWVuIGJldHdlZW4gbWFqb3IgZ2VuZXJhL2dlb2dyYXBoeSBmYWN0b3JzIEFOT1ZBDQpyZXBvcnQ6OnJlcG9ydChhb3YoYWxwaGEgfnNlcXJ1bitwc3lsbGlkX2ZhbWlseStnZW51c19nZW8sIGRhdGE9ZGl2X3RhYmxlMikpDQpyZXBvcnQ6OnJlcG9ydChhb3YoU2hhbm5vbiB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K2dlbnVzX2dlbywgZGF0YT1kaXZfdGFibGUyKSkNCnJlcG9ydDo6cmVwb3J0KGFvdihhbHBoYSB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K2dlbnVzX2dlbywgZGF0YT1kaXZfdGFibGUyKSkNCg0KIyBBc3NvY2lhdGlvbiB3aXRoIHBoeWxvZ2VueQ0KZGF0IDwtIGRpdl90YWJsZSAgJT4lDQogIGZpbHRlcihwc3lsbGlkX3NwcCAlaW4lIHBydW5lZC50cmVlJHRpcC5sYWJlbCkgJT4lICNTdWJzZXQgdG8gY29tbW9uIA0KICBncm91cF9ieShwc3lsbGlkX3NwcCkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLXdoZXJlKGlzLmNoYXJhY3RlcikpICU+JQ0KICBzdW1tYXJpc2VfYWxsKG1lYW4pICU+JQ0KICBhcnJhbmdlKG1hdGNoKHBzeWxsaWRfc3BwLCBwcnVuZWQudHJlZSR0aXAubGFiZWwpKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBtYWdyaXR0cjo6c2V0X3Jvd25hbWVzKC4kcHN5bGxpZF9zcHApICU+JQ0KICBkcGx5cjo6c2VsZWN0KC1wc3lsbGlkX3NwcCkNCg0KIyBBZGQgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIGNvbnRyb2xzDQpkYXQkcmFuZG9tIDwtIHJub3JtKGxlbmd0aChkYXQkYWxwaGEpLCBzZCA9IDEwKSAjUmFuZG9tIGFzc29jaWF0aW9uDQpkYXQkYm0gPC0gclRyYWl0Q29udChwcnVuZWQudHJlZSkgI0Jyb3duaWFuIG1vdGlvbg0KDQojIE1ha2UgcGh5bG9zaWduYWwgb2JqZWN0IGFuZCBtZWFzdXJlIHNpZ25hbCBiZXR3ZWVuIHVuaXZhcmlhdGUgdHJhaXRzLg0KcDRkIDwtIHBoeWxvYmFzZTo6cGh5bG80ZChwcnVuZWQudHJlZSwgZGF0KQkNCnNpZ25hbCA8LSBwaHlsb3NpZ25hbDo6cGh5bG9TaWduYWwocDRkID0gcDRkLCBtZXRob2RzID0gYygiSSIsICJMYW1iZGEiLCAiSyIpLCByZXBzID0gOTk5KSU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIHJvd25hbWVzX3RvX2NvbHVtbigibWVhc3VyZSIpDQoNCiMgcHJpbnQgcGh5bG9nZW5ldGljIHNpZ25hbA0Kc2lnbmFsDQp3cml0ZV9jc3Yoc2lnbmFsLCAib3V0cHV0L2FscGhhL3BoeWxvc2lnbmFsLmNzdiIpDQoNCiMgTG9jYXRlIHNpZ25hbA0KbGlwYSA8LSBsaXBhTW9yYW4ocDRkLCByZXBzPTk5OSkNCmxpcGEucDRkIDwtIGxpcGFNb3JhbihwNGQsIGFzLnA0ZCA9IFRSVUUsIHJlcHM9OTk5KQ0KYmFycGxvdC5waHlsbzRkKGxpcGEucDRkLCBiYXIuY29sID0gKGxpcGEkcC52YWx1ZSA8IDAuMDUpICsgMSwgY2VudGVyID0gRkFMU0UsIHNjYWxlID0gRkFMU0UpICsgdGl0bGUoIk5vbi1yYXJlZmllZCIpDQoNCiN3cml0ZSBvdXQgbGlwYQ0KbGlwYV9vdXQgPC0gY2JpbmQobGlwYSRsaXBhICU+JQ0KICAgICAgICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lICU+JQ0KICAgICAgICAgICAgICAgICAgICByZW5hbWVfYWxsKGZ1bnMocGFzdGUwKC4sICJfc3RhdCIpKSksDQogICAgICAgICAgICAgICAgICBsaXBhJHAudmFsdWUgJT4lDQogICAgICAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUgJT4lDQogICAgICAgICAgICAgICAgICAgIHJlbmFtZV9hbGwoZnVucyhwYXN0ZTAoLiwgIl9wdmFsIikpKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICklPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCJwc3lsbGlkX3NwcCIpDQp3cml0ZV9jc3YobGlwYV9vdXQsICJvdXRwdXQvYWxwaGEvbGlwYS5jc3YiKQ0KYGBgDQoNCiMjIFJhcmVmaWVkDQoNClNlZSBpZiB0aGUgcGF0dGVybiBob2xkcyBldmVuIHdpdGggcmFyZWZhY3Rpb24gdG8gbG93ZXN0IHNhbXBsZQ0KDQpgYGB7ciBhbHBoYSByYXJlZmllZH0NCiMgUmFyZWZpZWQgcmljaG5lc3MNCnBzMl9yYXJlIDwtIHJhcmVmeV9ldmVuX2RlcHRoKHBzMiwgc2FtcGxlLnNpemUgPSBtaW4oc2FtcGxlX3N1bXMocHMyKSksDQogIHJuZ3NlZWQgPSA2NjYsIHJlcGxhY2UgPSBUUlVFLCB0cmltT1RVcyA9IFRSVUUsIHZlcmJvc2UgPSBUUlVFKQ0KDQojIEdldCByaWNobmVzcyBtZWFzdXJlcw0KcmljaG5lc3NfcmFyZSA8LSBwaHlsb3NlcTo6ZXN0aW1hdGVfcmljaG5lc3MocHMyX3JhcmUsIG1lYXN1cmVzPWMoIlNoYW5ub24iKSkgJT4lDQogIHJvd25hbWVzX3RvX2NvbHVtbigiU2FtcGxlX05hbWUiKSAlPiUNCiAgbXV0YXRlKFNhbXBsZV9OYW1lID0gU2FtcGxlX05hbWUgJT4lIA0KICAgICAgICAgICBzdHJfcmVtb3ZlKCJeWCIpICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIlxcLiIsICIgIikpDQoNCiNTZXQgbnVtYmVyIG9mIHJhbmRvbWlzYXRpb25zIGZvciBjYWxjdWxhdGluZyBzaWduaWZpY2FuY2UNCiMgQ2FsY3VsYXRlIEZhaXRoJ3MgUEQtaW5kZXggJiBTcGVjaWVzIHJpY2huZXNzIC0gd2l0aCBTdGFuZGFyZCBlcnJvcnMNCiNzZXNwZF9yYXJlIDwtIHBpY2FudGU6OnNlcy5wZChhcyhwaHlsb3NlcTo6b3R1X3RhYmxlKHBzMl9yYXJlKSwgIm1hdHJpeCIpLCAgcGh5bG9zZXE6OnBoeV90cmVlKHBzMl9yYXJlKSwgbnVsbC5tb2RlbCA9ICMidGF4YS5sYWJlbHMiLCBpbmNsdWRlLnJvb3QgPSBGLCBydW5zID0gOTkpDQoNCnBkX3JhcmUgPC0gcGljYW50ZTo6cGQoYXMocGh5bG9zZXE6Om90dV90YWJsZShwczJfcmFyZSksICJtYXRyaXgiKSwgIHBoeWxvc2VxOjpwaHlfdHJlZShwczJfcmFyZSksIGluY2x1ZGUucm9vdCA9IEZBTFNFKQ0KDQojIEpvaW4gdG9nZXRoZXINCmRpdl90YWJsZV9yYXJlIDwtIHBkX3JhcmUgJT4lDQogIHJvd25hbWVzX3RvX2NvbHVtbigiU2FtcGxlX05hbWUiKSAlPiUNCiAgZHBseXI6OnNlbGVjdChTYW1wbGVfTmFtZSwgYWxwaGEgPSBTUiwgcGQgPSBQRCkgJT4lDQogIGxlZnRfam9pbihyaWNobmVzcywgYnk9IlNhbXBsZV9OYW1lIikgJT4lDQogIGxlZnRfam9pbihzYW1wbGVfZGF0YShwczJfcmFyZSkgJT4lIA0KICAgICAgICAgICAgICBhcygibWF0cml4IikgJT4lDQogICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgICAgICAgICAgICAgZmlsdGVyKCFkdXBsaWNhdGVkKFNhbXBsZV9OYW1lKSkgJT4lDQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoU2FtcGxlX05hbWUsIHBzeWxsaWRfc3BwLCBwc3lsbGlkX2dlbnVzLCBnZW51c19nZW8pLA0KICAgICAgICAgICAgYnkgPSAiU2FtcGxlX05hbWUiKSANCg0KIyBTdW1tYXJpc2UgbWVhbnMNCmRpdl90YWJsZV9yYXJlICU+JQ0KICBzdW1tYXJpc2VfaWYoaXMubnVtZXJpYywgbWVhbikNCg0KIyBEaWZmZXJlbmNlIGJldHdlZW4gYWxsIG1ham9yIGdlbmVyYSBmb3IgYWxwaGEgZGl2ZXJzaXR5IEFOT1ZBDQpkaXZfdGFibGVfcmFyZTIgPC0gZGl2X3RhYmxlX3JhcmUgJT4lDQogIGRwbHlyOjpmaWx0ZXIocHN5bGxpZF9nZW51cyAlaW4lIGMoIlBvd2VsbGlhIiwgIkN0ZW5hcnl0YWluYSIsICJQc3lsbGEiKSkNCg0KbWdfZGl2X3JhcmUgPC0gYmluZF9yb3dzKA0KICBicm9vbTo6dGlkeShUdWtleUhTRChhb3YoYWxwaGEgfnNlcXJ1bitwc3lsbGlkX2ZhbWlseStwc3lsbGlkX2dlbnVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT1kaXZfdGFibGVfcmFyZTIpKSkgJT4lIG11dGF0ZSh0eXBlPSJSaWNobmVzcyIpLA0KICBicm9vbTo6dGlkeShUdWtleUhTRChhb3YoU2hhbm5vbiB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K3BzeWxsaWRfZ2VudXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhPWRpdl90YWJsZV9yYXJlMikpKSAlPiUgbXV0YXRlKHR5cGU9IlNoYW5ub24iKSwNCiAgYnJvb206OnRpZHkoVHVrZXlIU0QoYW92KHBkIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9ZGl2X3RhYmxlX3JhcmUyKSkpICU+JSBtdXRhdGUodHlwZT0iUGh5bG9nZW5ldGljIiksDQogIGJyb29tOjp0aWR5KFR1a2V5SFNEKGFvdihhbHBoYSB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K2dlbnVzX2dlbywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9ZGl2X3RhYmxlX3JhcmUyKSkpICU+JSBtdXRhdGUodHlwZT0iUmljaG5lc3MiKSwNCiAgYnJvb206OnRpZHkoVHVrZXlIU0QoYW92KGFscGhhIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrZ2VudXNfZ2VvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT1kaXZfdGFibGVfcmFyZTIpKSkgJT4lIG11dGF0ZSh0eXBlPSJTaGFubm9uIiksDQogIGJyb29tOjp0aWR5KFR1a2V5SFNEKGFvdihwZCB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K2dlbnVzX2dlbywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9ZGl2X3RhYmxlX3JhcmUyKSkpICU+JSBtdXRhdGUodHlwZT0iUGh5bG9nZW5ldGljIikNCiAgICAgICAgICApDQp3cml0ZV9jc3YobWdfZGl2X3JhcmUsICJvdXRwdXQvYWxwaGEvbWFqb3JfZ2VuZXJhX2FscGhhX3JhcmVmaWVkLmNzdiIpDQoNCiMgRGlmZmVyZW5jZSBiZXR3ZWVuIGdlbnVzIGZhY3RvcnMgQU5PVkENCnJlcG9ydDo6cmVwb3J0KGFvdihhbHBoYSB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K3BzeWxsaWRfZ2VudXMsIGRhdGE9ZGl2X3RhYmxlX3JhcmUyKSkNCnJlcG9ydDo6cmVwb3J0KGFvdihTaGFubm9uIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cywgZGF0YT1kaXZfdGFibGVfcmFyZTIpKQ0KcmVwb3J0OjpyZXBvcnQoYW92KHBkIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cywgZGF0YT1kaXZfdGFibGVfcmFyZTIpKQ0KDQojIERpZmZlcmVuY2UgYmV0d2VlbiBnZW51cy9nZW9ncmFwaHkgZmFjdG9ycyBBTk9WQQ0KcmVwb3J0OjpyZXBvcnQoYW92KGFscGhhIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrZ2VudXNfZ2VvLCBkYXRhPWRpdl90YWJsZV9yYXJlMikpDQpyZXBvcnQ6OnJlcG9ydChhb3YoU2hhbm5vbiB+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K2dlbnVzX2dlbywgZGF0YT1kaXZfdGFibGVfcmFyZTIpKQ0KcmVwb3J0OjpyZXBvcnQoYW92KGFscGhhIH5zZXFydW4rcHN5bGxpZF9mYW1pbHkrZ2VudXNfZ2VvLCBkYXRhPWRpdl90YWJsZV9yYXJlMikpDQoNCg0KZGF0IDwtIGRpdl90YWJsZV9yYXJlICAlPiUNCiAgZmlsdGVyKHBzeWxsaWRfc3BwICVpbiUgcHJ1bmVkLnRyZWUkdGlwLmxhYmVsKSAlPiUgI1N1YnNldCB0byBjb21tb24gDQogIGdyb3VwX2J5KHBzeWxsaWRfc3BwKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtd2hlcmUoaXMuY2hhcmFjdGVyKSkgJT4lDQogIHN1bW1hcmlzZV9hbGwobWVhbikgJT4lDQogIGFycmFuZ2UobWF0Y2gocHN5bGxpZF9zcHAsIHBydW5lZC50cmVlJHRpcC5sYWJlbCkpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIG1hZ3JpdHRyOjpzZXRfcm93bmFtZXMoLiRwc3lsbGlkX3NwcCkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLXBzeWxsaWRfc3BwKQ0KDQojIEFkZCBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgY29udHJvbHMNCmRhdCRyYW5kb20gPC0gcm5vcm0obGVuZ3RoKGRhdCRhbHBoYSksIHNkID0gMTApICNSYW5kb20gYXNzb2NpYXRpb24NCmRhdCRibSA8LSByVHJhaXRDb250KHBydW5lZC50cmVlKSAjQnJvd25pYW4gbW90aW9uDQoNCiMgTWFrZSBwaHlsb3NpZ25hbCBvYmplY3QgYW5kIG1lYXN1cmUgc2lnbmFsIGJldHdlZW4gdW5pdmFyaWF0ZSB0cmFpdHMuDQpwNGQgPC0gcGh5bG9iYXNlOjpwaHlsbzRkKHBydW5lZC50cmVlLCBkYXQpCQ0Kc2lnbmFsX3JhcmUgPC0gcGh5bG9zaWduYWw6OnBoeWxvU2lnbmFsKHA0ZCA9IHA0ZCwgbWV0aG9kcyA9IGMoIkkiLCAiTGFtYmRhIiwgIksiKSwgcmVwcyA9IDk5OSkgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCJtZWFzdXJlIikNCg0KIyBwcmludCBwaHlsb2dlbmV0aWMgc2lnbmFsDQpzaWduYWxfcmFyZQ0Kd3JpdGVfY3N2KHNpZ25hbF9yYXJlLCAib3V0cHV0L2FscGhhL3BoeWxvc2lnbmFsX3JhcmVmaWVkLmNzdiIpDQoNCiMgTG9jYXRlIHNpZ25hbA0KbGlwYSA8LSBsaXBhTW9yYW4ocDRkLCByZXBzPTk5OSkNCmxpcGEucDRkIDwtIGxpcGFNb3JhbihwNGQsIGFzLnA0ZCA9IFRSVUUsIHJlcHM9OTk5KQ0KYmFycGxvdC5waHlsbzRkKGxpcGEucDRkLCBiYXIuY29sID0gKGxpcGEkcC52YWx1ZSA8IDAuMDUpICsgMSwgY2VudGVyID0gRkFMU0UsIHNjYWxlID0gRkFMU0UpICsgdGl0bGUoIlJhcmVmaWVkIikNCg0KI3dyaXRlIG91dCBsaXBhDQpsaXBhX291dF9yYXJlIDwtIGNiaW5kKGxpcGEkbGlwYSAlPiUNCiAgICAgICAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSAlPiUNCiAgICAgICAgICAgICAgICAgICAgcmVuYW1lX2FsbChmdW5zKHBhc3RlMCguLCAiX3N0YXQiKSkpLA0KICAgICAgICAgICAgICAgICAgbGlwYSRwLnZhbHVlICU+JQ0KICAgICAgICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lICU+JQ0KICAgICAgICAgICAgICAgICAgICByZW5hbWVfYWxsKGZ1bnMocGFzdGUwKC4sICJfcHZhbCIpKSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApJT4lDQogIHJvd25hbWVzX3RvX2NvbHVtbigicHN5bGxpZF9zcHAiKQ0Kd3JpdGVfY3N2KGxpcGFfb3V0X3JhcmUsICJvdXRwdXQvYWxwaGEvbGlwYV9yYXJlZmllZC5jc3YiKSAgICANCmBgYAkNCg0KIyMgQWxwaGEgbm8gR2FtbWFwcm90ZW9iYWN0ZXJpYQ0KDQpgYGB7ciBObyBnYW1tYSByaWNobmVzc30NCiMgUmFyZWZpZWQgcmljaG5lc3MNCnBzMl9zdWJzZXQgPC0gcHMyICU+JQ0KIHN1YnNldF90YXhhKGNsYXNzICE9ICJHYW1tYXByb3Rlb2JhY3RlcmlhIikgJT4lICNpcyB0aGlzIHdvcmtpbmc/DQogZmlsdGVyX3RheGEoZnVuY3Rpb24oeCkgbWVhbih4KSA+IDAsIFRSVUUpI0Ryb3AgbWlzc2luZyB0YXhhIGZyb20gdGFibGUNCnBzMl9zdWJzZXQgPC0gcHJ1bmVfc2FtcGxlcyhzYW1wbGVfc3VtcyhwczJfc3Vic2V0KSA+MCAsIHBzMl9zdWJzZXQpDQptZXNzYWdlKG5zYW1wbGVzKHBzMikgLSBuc2FtcGxlcyhwczJfc3Vic2V0KSwgIiBTYW1wbGVzIGFuZCAiLCBudGF4YShwczIpIC0gbnRheGEocHMyX3N1YnNldCksICIgdGF4YSBEcm9wcGVkIikNCg0KIyBHZXQgcmljaG5lc3MgbWVhc3VyZXMNCnJpY2huZXNzX3N1YnNldCA8LSBwaHlsb3NlcTo6ZXN0aW1hdGVfcmljaG5lc3MocHMyX3N1YnNldCwgbWVhc3VyZXM9YygiU2hhbm5vbiIpKSAlPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCJTYW1wbGVfTmFtZSIpICU+JQ0KICBtdXRhdGUoU2FtcGxlX05hbWUgPSBTYW1wbGVfTmFtZSAlPiUgDQogICAgICAgICAgIHN0cl9yZW1vdmUoIl5YIikgJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiXFwuIiwgIiAiKSkNCg0KI1NldCBudW1iZXIgb2YgcmFuZG9taXNhdGlvbnMgZm9yIGNhbGN1bGF0aW5nIHNpZ25pZmljYW5jZQ0KIyBDYWxjdWxhdGUgRmFpdGgncyBQRC1pbmRleCAmIFNwZWNpZXMgcmljaG5lc3MgLSB3aXRoIFN0YW5kYXJkIGVycm9ycw0KI3Nlc3BkX3N1YnNldCA8LSBwaWNhbnRlOjpzZXMucGQoYXMocGh5bG9zZXE6Om90dV90YWJsZShwczJfc3Vic2V0KSwgIm1hdHJpeCIpLCAgcGh5bG9zZXE6OnBoeV90cmVlKHBzMl9zdWJzZXQpLCBudWxsLm1vZGVsID0gInRheGEubGFiZWxzIiwgaW5jbHVkZS5yb290ID0gRiwgcnVucyA9IDk5KQ0KDQpwZF9zdWJzZXQgPC0gcGljYW50ZTo6cGQoYXMocGh5bG9zZXE6Om90dV90YWJsZShwczJfc3Vic2V0KSwgIm1hdHJpeCIpLCAgcGh5bG9zZXE6OnBoeV90cmVlKHBzMl9zdWJzZXQpLCBpbmNsdWRlLnJvb3QgPSBGQUxTRSkNCg0KIyBKb2luIHRvZ2V0aGVyDQpkaXZfdGFibGVfc3Vic2V0IDwtIHBkX3N1YnNldCAlPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCJTYW1wbGVfTmFtZSIpICU+JQ0KICBkcGx5cjo6c2VsZWN0KFNhbXBsZV9OYW1lLCBhbHBoYSA9IFNSLCBwZCA9IFBEKSAlPiUNCiAgbGVmdF9qb2luKHJpY2huZXNzLCBieT0iU2FtcGxlX05hbWUiKSAlPiUNCiAgbGVmdF9qb2luKHNhbXBsZV9kYXRhKHBzMl9zdWJzZXQpICU+JSANCiAgICAgICAgICAgICAgYXMoIm1hdHJpeCIpICU+JQ0KICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogICAgICAgICAgICAgIGZpbHRlcighZHVwbGljYXRlZChTYW1wbGVfTmFtZSkpICU+JQ0KICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KFNhbXBsZV9OYW1lLCBwc3lsbGlkX3NwcCwgcHN5bGxpZF9nZW51cywgZ2VudXNfZ2VvKSwNCiAgICAgICAgICAgIGJ5ID0gIlNhbXBsZV9OYW1lIikgDQoNCiMgU3VtbWFyaXNlIG1lYW5zDQpkaXZfdGFibGVfc3Vic2V0ICU+JQ0KICBzdW1tYXJpc2VfaWYoaXMubnVtZXJpYywgfm1lYW4oLngsIG5hLnJtPVRSVUUpKQ0KDQojIERpZmZlcmVuY2UgYmV0d2VlbiBhbGwgbWFqb3IgZ2VuZXJhIGZvciBhbHBoYSBkaXZlcnNpdHkgQU5PVkENCmRpdl90YWJsZV9zdWJzZXQyIDwtIGRpdl90YWJsZV9zdWJzZXQgJT4lDQogIGRwbHlyOjpmaWx0ZXIocHN5bGxpZF9nZW51cyAlaW4lIGMoIlBvd2VsbGlhIiwgIkN0ZW5hcnl0YWluYSIsICJQc3lsbGEiKSkNCg0KbWdfZGl2X3N1YnNldCA8LSBiaW5kX3Jvd3MoDQogIGJyb29tOjp0aWR5KFR1a2V5SFNEKGFvdihhbHBoYSB+cHN5bGxpZF9nZW51cywgZGF0YT1kaXZfdGFibGVfc3Vic2V0MikpKSAlPiUgbXV0YXRlKHR5cGU9IlJpY2huZXNzIiksDQogIGJyb29tOjp0aWR5KFR1a2V5SFNEKGFvdihTaGFubm9uIH5wc3lsbGlkX2dlbnVzLCBkYXRhPWRpdl90YWJsZV9zdWJzZXQyKSkpICU+JSBtdXRhdGUodHlwZT0iU2hhbm5vbiIpLA0KICBicm9vbTo6dGlkeShUdWtleUhTRChhb3YocGQgfnBzeWxsaWRfZ2VudXMsIGRhdGE9ZGl2X3RhYmxlX3N1YnNldDIpKSkgJT4lIG11dGF0ZSh0eXBlPSJQaHlsb2dlbmV0aWMiKSwNCiAgYnJvb206OnRpZHkoVHVrZXlIU0QoYW92KGFscGhhIH5nZW51c19nZW8sIGRhdGE9ZGl2X3RhYmxlX3N1YnNldDIpKSkgJT4lIG11dGF0ZSh0eXBlPSJSaWNobmVzcyIpLA0KICBicm9vbTo6dGlkeShUdWtleUhTRChhb3YoYWxwaGEgfmdlbnVzX2dlbywgZGF0YT1kaXZfdGFibGVfc3Vic2V0MikpKSAlPiUgbXV0YXRlKHR5cGU9IlNoYW5ub24iKSwNCiAgYnJvb206OnRpZHkoVHVrZXlIU0QoYW92KHBkIH5nZW51c19nZW8sIGRhdGE9ZGl2X3RhYmxlX3N1YnNldDIpKSkgJT4lIG11dGF0ZSh0eXBlPSJQaHlsb2dlbmV0aWMiKQ0KICAgICAgICAgICkNCndyaXRlX2NzdihtZ19kaXZfc3Vic2V0LCAib3V0cHV0L2FscGhhL21ham9yX2dlbmVyYV9hbHBoYV9ub2dhbW1hLmNzdiIpDQoNCiMgRGlmZmVyZW5jZSBiZXR3ZWVuIGdlbnVzIGZhY3RvcnMgQU5PVkENCnJlcG9ydDo6cmVwb3J0KGFvdihhbHBoYSB+cHN5bGxpZF9nZW51cywgZGF0YT1kaXZfdGFibGVfc3Vic2V0MikpDQpyZXBvcnQ6OnJlcG9ydChhb3YoU2hhbm5vbiB+cHN5bGxpZF9nZW51cywgZGF0YT1kaXZfdGFibGVfc3Vic2V0MikpDQpyZXBvcnQ6OnJlcG9ydChhb3YocGQgfnBzeWxsaWRfZ2VudXMsIGRhdGE9ZGl2X3RhYmxlX3N1YnNldDIpKQ0KDQoNCiMgRGlmZmVyZW5jZSBiZXR3ZWVuIGdlbnVzL2dlb2dyYXBoeSBmYWN0b3JzIEFOT1ZBDQpyZXBvcnQ6OnJlcG9ydChhb3YoYWxwaGEgfmdlbnVzX2dlbywgZGF0YT1kaXZfdGFibGVfc3Vic2V0MikpDQpyZXBvcnQ6OnJlcG9ydChhb3YoU2hhbm5vbiB+Z2VudXNfZ2VvLCBkYXRhPWRpdl90YWJsZV9zdWJzZXQyKSkNCnJlcG9ydDo6cmVwb3J0KGFvdihhbHBoYSB+Z2VudXNfZ2VvLCBkYXRhPWRpdl90YWJsZV9zdWJzZXQyKSkNCg0KYGBgCQ0KDQojIEJldGEgZGl2ZXJzaXR5DQoNCiMjIE1pY3JvYmUgZGlzdGFuY2VzDQpgYGB7ciBkaXN0bGlzdH0NCnBzMl9kaXN0IDwtIHBzMg0KI3BzMl9kaXN0IDwtIHBzMl9maWx0DQoNCiMgR2V0IE9UVSB0YWJsZXMNCm90dXRhYiA8LSBvdHVfdGFibGUocHMyX2Rpc3QpDQojSW1wdXRlIHplcm9lcyBmb3IgY29tcG9zaXRpb25hbCBkaXN0YW5jZXMNCm90dXRhYl9uMCA8LSBhcy5tYXRyaXgoekNvbXBvc2l0aW9uczo6Y211bHRSZXBsKG90dXRhYiwgbWV0aG9kPSJCTCIsIG91dHB1dD0icC1jb3VudHMiKSkNCg0KI1Jvb3QgJiBsYWJlbCBwaHlsb2dlbmV0aWMgdHJlZQ0KcGh5X3RyZWUocHMyX2Rpc3QpIDwtIG11bHRpMmRpKHBoeV90cmVlKHBzMl9kaXN0KSkNCnBoeV90cmVlKHBzMl9kaXN0KSA8LSBtYWtlTm9kZUxhYmVsKHBoeV90cmVlKHBzMl9kaXN0KSwgbWV0aG9kPSJudW1iZXIiLCBwcmVmaXg9J24nKQ0KbmFtZS5iYWxhbmNlKHBoeV90cmVlKHBzMl9kaXN0KSwgdGF4X3RhYmxlKHBzMl9kaXN0KSwgJ24xJykNCg0KI0NhbGN1bGF0ZSBkaWZmZXJlbnQgZGlzdGFuY2UgbWV0cmljcw0KbWV0cmljcyA8LSBjKCJCcmF5IiwgIkphY2NhcmQiLCAiQWl0Y2hpc29uIiwiUGhpbHIiLCAiVW5pZnJhYyIsICJXVW5pZnJhYyIpICANCmRpc3RsaXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aD1sZW5ndGgobWV0cmljcykpDQpuYW1lcyhkaXN0bGlzdCkgPC0gbWV0cmljcw0KDQpkaXN0bGlzdCRKYWNjYXJkIDwtIGFzLm1hdHJpeCh2ZWdkaXN0KG90dXRhYiwgbWV0aG9kPSJqYWMiLGJpbmFyeSA9IFQpKQ0KZGlzdGxpc3QkQnJheSA8LSBhcy5tYXRyaXgodmVnZGlzdChvdHV0YWIsIG1ldGhvZD0iYnJheSIpKQ0KZGlzdGxpc3QkQWl0Y2hpc29uIDwtIGFzLm1hdHJpeCh2ZWdkaXN0KENvRGFTZXE6OmNvZGFTZXEuY2xyKG90dXRhYl9uMCksIG1ldGhvZD0iZXVjbGlkZWFuIikpDQpkaXN0bGlzdCRQaGlsciA8LSBhcy5tYXRyaXgodmVnZGlzdChwaGlscjo6cGhpbHIob3R1dGFiX24wLCBwaHlfdHJlZShwczJfZGlzdCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJ0LndlaWdodHM9J2Vub3JtLnguZ20uY291bnRzJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlsci53ZWlnaHRzPSdibHcuc3FydCcpLCBtZXRob2Q9ImV1Y2xpZGVhbiIsIG5hLnJtPVRSVUUpKQ0KZGlzdGxpc3QkVW5pZnJhYyA8LSBhcy5tYXRyaXgocGh5bG9zZXE6OlVuaUZyYWMocHMyX2Rpc3QsIHdlaWdodGVkPUZBTFNFLCBwYXJhbGxlbCA9IFRSVUUpKQ0KZGlzdGxpc3QkV1VuaWZyYWMgPC0gYXMubWF0cml4KHBoeWxvc2VxOjpVbmlGcmFjKHBzMl9kaXN0LCB3ZWlnaHRlZD1UUlVFLCBwYXJhbGxlbCA9IFRSVUUpKQ0KDQojIENyZWF0ZSBsb3cgYWJ1bmRhbmNlIGZpbHRlcmVkIGRhdGFzZXQNCmZpbHRlcmZ1bjEgPC0gZnVuY3Rpb24oeCl7DQogIHhbKHggLyBzdW0oeCkpIDwgKDFlLTQpXSA8LSAwDQogIHJldHVybih4KQ0KfQ0KcHMyX2ZpbHQgIDwtIHRyYW5zZm9ybV9zYW1wbGVfY291bnRzKHBzMiwgZnVuID0gZmlsdGVyZnVuMSkgJT4lDQogIGZpbHRlcl90YXhhKGZ1bmN0aW9uKHgpIG1lYW4oeCkgPiAwLCBUUlVFKSAjRHJvcCBtaXNzaW5nIHRheGEgZnJvbSB0YWJsZQ0KDQpwcmludChwYXN0ZTAoKG50YXhhKHBzMiktbnRheGEocHMyX2ZpbHQpKSwgIiB0YXhhIHVuZGVyIHRocmVzaG9sZCByZW1vdmVkIikpDQoNCiMgR2V0IE9UVSB0YWJsZXMNCm90dXRhYiA8LSBvdHVfdGFibGUocHMyX2ZpbHQpDQojSW1wdXRlIHplcm9lcyBmb3IgY29tcG9zaXRpb25hbCBkaXN0YW5jZXMNCm90dXRhYl9uMCA8LSBhcy5tYXRyaXgoekNvbXBvc2l0aW9uczo6Y211bHRSZXBsKG90dXRhYiwgbWV0aG9kPSJCTCIsIG91dHB1dD0icC1jb3VudHMiKSkNCiNSb290ICYgbGFiZWwgcGh5bG9nZW5ldGljIHRyZWUNCnBoeV90cmVlKHBzMl9maWx0KSA8LSBtdWx0aTJkaShwaHlfdHJlZShwczJfZmlsdCkpDQpwaHlfdHJlZShwczJfZmlsdCkgPC0gbWFrZU5vZGVMYWJlbChwaHlfdHJlZShwczJfZmlsdCksIG1ldGhvZD0ibnVtYmVyIiwgcHJlZml4PSduJykNCm5hbWUuYmFsYW5jZShwaHlfdHJlZShwczJfZmlsdCksIHRheF90YWJsZShwczJfZmlsdCksICduMScpDQoNCiNDYWxjdWxhdGUgZGlmZmVyZW50IGRpc3RhbmNlIG1ldHJpY3MNCm1ldHJpY3MgPC0gYygiQnJheSIsICJKYWNjYXJkIiwgIkFpdGNoaXNvbiIsIlBoaWxyIiwgIlVuaWZyYWMiLCAiV1VuaWZyYWMiKSAgDQpkaXN0bGlzdF9maWx0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aD1sZW5ndGgobWV0cmljcykpDQpuYW1lcyhkaXN0bGlzdF9maWx0KSA8LSBtZXRyaWNzDQoNCmRpc3RsaXN0X2ZpbHQkSmFjY2FyZCA8LSBhcy5tYXRyaXgodmVnZGlzdChvdHV0YWIsIG1ldGhvZD0iamFjIixiaW5hcnkgPSBUKSkNCmRpc3RsaXN0X2ZpbHQkQnJheSA8LSBhcy5tYXRyaXgodmVnZGlzdChvdHV0YWIsIG1ldGhvZD0iYnJheSIpKQ0KZGlzdGxpc3RfZmlsdCRBaXRjaGlzb24gPC0gYXMubWF0cml4KHZlZ2Rpc3QoQ29EYVNlcTo6Y29kYVNlcS5jbHIob3R1dGFiX24wKSwgbWV0aG9kPSJldWNsaWRlYW4iKSkNCmRpc3RsaXN0X2ZpbHQkUGhpbHIgPC0gYXMubWF0cml4KHZlZ2Rpc3QocGhpbHI6OnBoaWxyKG90dXRhYl9uMCwgcGh5X3RyZWUocHMyX2ZpbHQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFydC53ZWlnaHRzPSdlbm9ybS54LmdtLmNvdW50cycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbHIud2VpZ2h0cz0nYmx3LnNxcnQnKSwgbWV0aG9kPSJldWNsaWRlYW4iLCBuYS5ybT1UUlVFKSkNCmRpc3RsaXN0X2ZpbHQkVW5pZnJhYyA8LSBhcy5tYXRyaXgocGh5bG9zZXE6OlVuaUZyYWMocHMyX2ZpbHQsIHdlaWdodGVkPUZBTFNFLCBwYXJhbGxlbCA9IFRSVUUpKQ0KZGlzdGxpc3RfZmlsdCRXVW5pZnJhYyA8LSBhcy5tYXRyaXgocGh5bG9zZXE6OlVuaUZyYWMocHMyX2ZpbHQsIHdlaWdodGVkPVRSVUUsIHBhcmFsbGVsID0gVFJVRSkpDQoNCiMgQ3JlYXRlIGRhdGFzZXQgd2l0aG91dCBnYW1tYXByb3Rlb2JhDQpwczJfc3Vic2V0IDwtIHBzMiAlPiUNCiBzdWJzZXRfdGF4YShjbGFzcyAhPSAiR2FtbWFwcm90ZW9iYWN0ZXJpYSIpICU+JSAjaXMgdGhpcyB3b3JraW5nPw0KIGZpbHRlcl90YXhhKGZ1bmN0aW9uKHgpIG1lYW4oeCkgPiAwLCBUUlVFKSNEcm9wIG1pc3NpbmcgdGF4YSBmcm9tIHRhYmxlDQpwczJfc3Vic2V0IDwtIHBydW5lX3NhbXBsZXMoc2FtcGxlX3N1bXMocHMyX3N1YnNldCkgPjAgLCBwczJfc3Vic2V0KQ0KbWVzc2FnZShuc2FtcGxlcyhwczIpIC0gbnNhbXBsZXMocHMyX3N1YnNldCksICIgU2FtcGxlcyBhbmQgIiwgbnRheGEocHMyKSAtIG50YXhhKHBzMl9zdWJzZXQpLCAiIHRheGEgRHJvcHBlZCIpDQoNCiMgR2V0IE9UVSB0YWJsZXMNCm90dXRhYl9zdWJzZXQgPC0gb3R1X3RhYmxlKHBzMl9zdWJzZXQpDQojSW1wdXRlIHplcm9lcyBmb3IgY29tcG9zaXRpb25hbCBkaXN0YW5jZXMNCm90dXRhYl9zdWJzZXRfbjAgPC0gYXMubWF0cml4KHpDb21wb3NpdGlvbnM6OmNtdWx0UmVwbChvdHV0YWJfc3Vic2V0LCBtZXRob2Q9IkJMIiwgb3V0cHV0PSJwLWNvdW50cyIpKQ0KI1Jvb3QgcGh5bG9nZW5ldGljIHRyZWUNCnBoeV90cmVlKHBzMl9zdWJzZXQpIDwtIG11bHRpMmRpKHBoeV90cmVlKHBzMl9zdWJzZXQpKQ0KcGh5X3RyZWUocHMyX3N1YnNldCkgPC0gbWFrZU5vZGVMYWJlbChwaHlfdHJlZShwczJfc3Vic2V0KSwgbWV0aG9kPSJudW1iZXIiLCBwcmVmaXg9J24nKQ0KbmFtZS5iYWxhbmNlKHBoeV90cmVlKHBzMl9zdWJzZXQpLCB0YXhfdGFibGUocHMyX3N1YnNldCksICduMScpDQoNCiNDYWxjdWxhdGUgZGlmZmVyZW50IGRpc3RhbmNlIG1ldHJpY3MNCm1ldHJpY3MgPC0gYygiQnJheSIsICJKYWNjYXJkIiwgIkFpdGNoaXNvbiIsIlBoaWxyIiwgIlVuaWZyYWMiLCAiV1VuaWZyYWMiKSAgDQpkaXN0bGlzdF9zdWJzZXQgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoPWxlbmd0aChtZXRyaWNzKSkNCm5hbWVzKGRpc3RsaXN0X3N1YnNldCkgPC0gbWV0cmljcw0KDQpkaXN0bGlzdF9zdWJzZXQkSmFjY2FyZCA8LSBhcy5tYXRyaXgodmVnZGlzdChvdHV0YWJfc3Vic2V0LCBtZXRob2Q9ImphYyIsYmluYXJ5ID0gVCkpDQpkaXN0bGlzdF9zdWJzZXQkQnJheSA8LSBhcy5tYXRyaXgodmVnZGlzdChvdHV0YWJfc3Vic2V0LCBtZXRob2Q9ImJyYXkiKSkNCmRpc3RsaXN0X3N1YnNldCRBaXRjaGlzb24gPC0gYXMubWF0cml4KHZlZ2Rpc3QoQ29EYVNlcTo6Y29kYVNlcS5jbHIob3R1dGFiX3N1YnNldF9uMCksIG1ldGhvZD0iZXVjbGlkZWFuIikpDQpkaXN0bGlzdF9zdWJzZXQkUGhpbHIgPC0gYXMubWF0cml4KHZlZ2Rpc3QocGhpbHI6OnBoaWxyKG90dXRhYl9zdWJzZXRfbjAsIHBoeV90cmVlKHBzMl9zdWJzZXQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFydC53ZWlnaHRzPSdlbm9ybS54LmdtLmNvdW50cycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbHIud2VpZ2h0cz0nYmx3LnNxcnQnKSwgbWV0aG9kPSJldWNsaWRlYW4iLCBuYS5ybT1UUlVFKSkNCmRpc3RsaXN0X3N1YnNldCRVbmlmcmFjIDwtIGFzLm1hdHJpeChwaHlsb3NlcTo6VW5pRnJhYyhwczJfc3Vic2V0LCB3ZWlnaHRlZD1GQUxTRSwgcGFyYWxsZWwgPSBUUlVFKSkNCmRpc3RsaXN0X3N1YnNldCRXVW5pZnJhYyA8LSBhcy5tYXRyaXgocGh5bG9zZXE6OlVuaUZyYWMocHMyX3N1YnNldCwgd2VpZ2h0ZWQ9VFJVRSwgcGFyYWxsZWwgPSBUUlVFKSkNCg0KIyBNYW50ZWwgdGVzdCB0byBjaGVjayBjb25jb3JkYW5jZSBvZiBiZXRhIGRpdmVyc2l0eSBwcmUgYW5kIHBvc3QgZmlsdGVyaW5nDQoNCnB1cnJyOjptYXAyKGRpc3RsaXN0LCBkaXN0bGlzdF9maWx0LH57DQogIHN1YnNhbXBsZSA8LSBpbnRlcnNlY3QoY29sbmFtZXMoLngpLCBjb2xuYW1lcygueSkpDQogIGFzLmRhdGEuZnJhbWUodmVnYW46Om1hbnRlbCgueFtzdWJzYW1wbGUsIHN1YnNhbXBsZV0sIC55W3N1YnNhbXBsZSwgc3Vic2FtcGxlXSlbYygic3RhdGlzdGljIiwic2lnbmlmIiwicGVybXV0YXRpb25zIildKQ0KfSkgJT4lDQogIGJpbmRfcm93cyguaWQ9ImRpc3QiKQ0KDQojIE1hbnRlbCB0ZXN0IHRvIGNoZWNrIGNvbmNvcmRhbmNlIG9mIGJldGEgZGl2ZXJzaXR5IHByZSBhbmQgcG9zdCBzdWJzZXQNCg0KcHVycnI6Om1hcDIoZGlzdGxpc3QsIGRpc3RsaXN0X3N1YnNldCx+ew0KICBzdWJzYW1wbGUgPC0gaW50ZXJzZWN0KGNvbG5hbWVzKC54KSwgY29sbmFtZXMoLnkpKQ0KICBhcy5kYXRhLmZyYW1lKHZlZ2FuOjptYW50ZWwoLnhbc3Vic2FtcGxlLCBzdWJzYW1wbGVdLCAueVtzdWJzYW1wbGUsIHN1YnNhbXBsZV0pW2MoInN0YXRpc3RpYyIsInNpZ25pZiIsInBlcm11dGF0aW9ucyIpXSkNCn0pICU+JQ0KICBiaW5kX3Jvd3MoLmlkPSJkaXN0IikNCmBgYA0KDQoNCiMjIEFkb25pcyAmIEJldGFkaXNwZXINCg0KYGBge3IgQWRvbmlzfQ0KIyBBRE9OSVMgaXMgY29uc3RydWN0ZWQgaGVpcmFyY2hpYWxseSB0byBtYXJnaW5hbGlzZSB0ZWNoaWNhbCB2YXJpYW5jZSB0aGVuIG1vdmluZyBkb3duIHRoZSB0YXhvbm9taWMgcmFua3MgDQoNCiMgQWRvbmlzIHRlc3QNCm1ldGFkYXRhIDwtIHNhbXBsZV9kYXRhKHBzMikgJT4lDQogIGFzKCJkYXRhLmZyYW1lIikNCmFkb25pc19yZXN1bHRzIDwtIGRpc3RsaXN0ICU+JQ0KICBwdXJycjo6bWFwKGZ1bmN0aW9uKHgpIHsNCiAgICB5IDwtIGFzLmRpc3QoeFttZXRhZGF0YSRTYW1wbGVfTmFtZSwgbWV0YWRhdGEkU2FtcGxlX05hbWVdKQ0KICAgIGJpbmRfcm93cygNCiAgICBicm9vbTo6dGlkeShhZG9uaXMyKHl+c2VxcnVuK3BzeWxsaWRfZmFtaWx5K3BzeWxsaWRfZ2VudXMrcHN5bGxpZF9zcHAsIG1ldGhvZD0iZXVjbGlkZWFuIiwgZGF0YT1tZXRhZGF0YSwgDQogICAgICAgICAgICAgICAgICAgICAgIHBlcm11dGF0aW9ucz05OTksIGJ5PSJ0ZXJtcyIpKSAlPiUgDQogICAgICBtdXRhdGUodGVzdCA9IHBhc3RlKHRlcm1bIXRlcm0gJWluJSBjKCJSZXNpZHVhbCIsICJUb3RhbCIpXSwgY29sbGFwc2U9Ii0iKSksDQogICAgI2Jyb29tOjp0aWR5KGFkb25pczIoeX5zZXFydW4raG9zdHBsYW50X3NwcCtwc3lsbGlkX3NwcCwgbWV0aG9kPSJldWNsaWRlYW4iLCBkYXRhPW1ldGFkYXRhLCANCiAgICAgIyAgICAgICAgICAgICAgICAgIHBlcm11dGF0aW9ucz05OTksIGJ5PSJtYXJnaW4iKSkgJT4lIA0KICAgICMgIG11dGF0ZSh0ZXN0ID0gcGFzdGUodGVybVshdGVybSAlaW4lIGMoIlJlc2lkdWFsIiwgIlRvdGFsIildLCBjb2xsYXBzZT0iLSIpKSwNCiAgICBicm9vbTo6dGlkeShhZG9uaXMyKHl+c2VxcnVuK2hvc3RwbGFudF9zcHAsIG1ldGhvZD0iZXVjbGlkZWFuIiwgZGF0YT1tZXRhZGF0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgcGVybXV0YXRpb25zPTk5OSwgYnk9InRlcm1zIikpICU+JSANCiAgICAgIG11dGF0ZSh0ZXN0ID0gcGFzdGUodGVybVshdGVybSAlaW4lIGMoIlJlc2lkdWFsIiwgIlRvdGFsIildLCBjb2xsYXBzZT0iLSIpKQ0KICAgICkNCn0pICAlPiUNCiAgYmluZF9yb3dzKC5pZD0iZGlzdCIpDQoNCiMgQ2hlY2sgaG9tb2dlbmVpdHkNCmJldGFkaXNwZXJfcmVzdWx0cyA8LSBkaXN0bGlzdCAlPiUNCiAgcHVycnI6Om1hcChmdW5jdGlvbih4KSB7DQogICAgeSA8LSBhcy5kaXN0KHhbbWV0YWRhdGEkU2FtcGxlX05hbWUsIG1ldGFkYXRhJFNhbXBsZV9OYW1lXSkNCiAgYmluZF9yb3dzKA0KICAgIGFzX3RpYmJsZShwZXJtdXRlc3QodmVnYW46OmJldGFkaXNwZXIoeSwgbWV0YWRhdGEkcHN5bGxpZF9zcHApKSR0YWIsIHJvd25hbWVzPSJ0ZXJtIikgJT4lDQogICAgICBtdXRhdGUodGVzdD0icHN5bGxpZF9zcHAiKSwNCiAgICBhc190aWJibGUocGVybXV0ZXN0KHZlZ2FuOjpiZXRhZGlzcGVyKHksIG1ldGFkYXRhJGhvc3RwbGFudF9zcHApKSR0YWIsIHJvd25hbWVzPSJ0ZXJtIikgICU+JQ0KICAgICAgbXV0YXRlKHRlc3Q9Imhvc3RwbGFudF9zcHAiKSwNCiAgKQ0KfSkgICU+JQ0KICBiaW5kX3Jvd3MoLmlkPSJkaXN0IikNCg0KZGlyLmNyZWF0ZSgib3V0cHV0L2JldGEiKQ0Kd3JpdGVfY3N2KGFkb25pc19yZXN1bHRzLCAib3V0cHV0L2JldGEvYWRvbmlzX2Z1bGxkYXRhLmNzdiIpDQp3cml0ZV9jc3YoYmV0YWRpc3Blcl9yZXN1bHRzLCAib3V0cHV0L2JldGEvYmV0YWRpc3Blcl9mdWxsZGF0YS5jc3YiKQ0KYGBgDQoNCiMjIFNhbWUgdHJlZSBkaXNzaW1pbGFyaXRpZXMNCg0KTG9vayBhdCB0aGUgc2ltaWxhcml0aWVzIGluIHRoZSBtaWNyb2Jpb21lIG9mIHRoZSBwc3lsbGlkIHNwZWNpbWVucyBjb2xsZWN0ZWQgZnJvbSB0aGUgc2FtZSBob3N0IHBsYW50DQpgYGB7ciBBdmcgRGlzc2ltaWFyaXR5fQ0KaG9zdHBsYW50X21ldGFkYXRhIDwtIG1ldGFkYXRhICU+JSBtdXRhdGUoaW5ncm91cCA9IGNhc2Vfd2hlbigNCiAgU2FtcGxlX05hbWUgJWluJSBjKCI5NCIsIjEwNyIsICIxMTMiLCI5MyIsIjEwNiIsICIxMTIiKSB+ICJmcmF4aW5pLWZyYXhpbmljb2xhIiwNCiAgU2FtcGxlX05hbWUgJWluJSBjKCIyMDBiaWciLCAiMjAxYmlnIiwgIjIwMHNtYWxsIiwgIjIwMXNtYWxsIikgfiAiYXBpY2FsaXMtZnJvZG9iYWdnaW5zaSIsDQogIFRSVUUgfiAib3RoZXIiDQogICkpDQoNCmJyb29tOjp0aWR5KGFkb25pczIoZGlzdGxpc3QkQWl0Y2hpc29ufmluZ3JvdXAgKyBwc3lsbGlkX3NwcCwgbWV0aG9kPSJldWNsaWRlYW4iLA0KICAgICAgICAgICAgICAgICAgIGRhdGE9aG9zdHBsYW50X21ldGFkYXRhKSkNCg0KcGFpcndpc2UuYWRvbmlzMihkaXN0bGlzdCRBaXRjaGlzb25+aW5ncm91cCArIHBzeWxsaWRfc3BwLCBtZXRob2Q9ImV1Y2xpZGVhbiIsDQogICAgICAgICAgICAgICAgICAgZGF0YT1ob3N0cGxhbnRfbWV0YWRhdGEpDQpgYGANCg0KIyMgQmFycGxvdA0KDQpgYGB7ciBiYXJwbG90fQ0KIyBQbG90IHRyZWUNCnAgPC0gZ2d0cmVlKHBydW5lZC50cmVlKSArIGdlb21fdGlwbGFiKGFsaWduPVRSVUUpICsgZ2VvbV9ub2RlbGFiKGdlb209J2xhYmVsJykgKw0KICAgIHNjYWxlX3hfY29udGludW91cyhleHBhbmQ9YygwLCAwLjEpKSANCg0KIyBQbG90IGJhcg0KcHMzX2JhciA8LSBwczMgJT4lDQogIHNwZWVkeXNlcTo6dGF4X2dsb20odGF4cmFuayA9ICJvcmRlciIpICU+JSAgICAgICAgICAgIyBhZ2dsb21lcmF0ZSBhdCBPcmRlciBsZXZlbA0KICB0cmFuc2Zvcm1fc2FtcGxlX2NvdW50cyhmdW5jdGlvbih4KSB7eC9zdW0oeCl9ICkgJT4lICMgVHJhbnNmb3JtIHRvIHJlbC4gYWJ1bmRhbmNlDQogIHNwZWVkeXNlcTo6cHNtZWx0KCkgJT4lDQogIG11dGF0ZShwbG90bGFiZWwgPSBwaHlsdW0pICU+JQ0KICBtdXRhdGUocGxvdGxhYmVsID0gY2FzZV93aGVuKA0KICAgIEFidW5kYW5jZSA+PSAwLjAxICYgcGh5bHVtPT0iUHJvdGVvYmFjdGVyaWEiIH4gcGFzdGUwKCJQIC0gIiwgb3JkZXIpLCAjIENoYW5nZSB0aGlzIHRvIHdoYXRldmVyIHRheHJhbmsgd2Ugd2FudA0KICAgIEFidW5kYW5jZSA+PSAwLjAxICYgIXBoeWx1bT09IlByb3Rlb2JhY3RlcmlhIn4gcGh5bHVtICwNCiAgICBBYnVuZGFuY2UgPCAwLjAxIH4gIk5BIg0KICAgICkpICU+JQ0KICBkcGx5cjo6bmFfaWYoIk5BIikgJT4lDQogIGRwbHlyOjpzZWxlY3QocHN5bGxpZF9zcHAsIHBsb3RsYWJlbCwgcGh5bHVtLCBvcmRlciwgQWJ1bmRhbmNlKSAlPiUNCiAgbGVmdF9qb2luKHAkZGF0YSAlPiUNCiAgICAgICAgICAgICAgYXNfZGF0YV9mcmFtZSAlPiUNCiAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihpc1RpcCkgJT4lDQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoeSwgbGFiZWwpICU+JQ0KICAgICAgICAgICAgICBkcGx5cjo6cmVuYW1lKHBzeWxsaWRfc3BwPWxhYmVsKSkgDQoNCmdnLmJhciA8LSBnZ3Bsb3QocHMzX2JhciwgYWVzKHg9eSwgeT1BYnVuZGFuY2UsIGZpbGw9cGxvdGxhYmVsKSkgKw0KICBnZW9tX2NvbCgpICArIA0KICBjb29yZF9mbGlwKCkrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jb2xvclJhbXBQYWxldHRlKGJyZXdlci5wYWwoOSwgIlNldDEiKSkobGVuZ3RoKHVuaXF1ZShwczNfYmFyJHBsb3RsYWJlbCkpLTEpLCBuYS52YWx1ZT0iZ3JleSIpICsgDQogICAgYmFzZV90aGVtZSAgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgICAgI3BhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvdXI9ImdyZXk5MiIsIHNpemU9MC41LCBsaW5ldHlwZT0iZGFzaGVkIiksDQogICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiZ3JleTkyIiwgDQogICAgICAgICAgICAgICAgICAgIGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAxKSwNCiAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgYXhpcy50aWNrcy55PWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgIGF4aXMudGl0bGUueT1lbGVtZW50X2JsYW5rKCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZD1jKDAsMCksIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkrDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQ9YygwLDApKSArDQogIGxhYnMoeCA9IE5VTEwgLA0KICAgICAgIHkgPSAiUmVsYXRpdmUgQWJ1bmRhbmNlIiwNCiAgICAgICBmaWxsID0gTlVMTCkNCg0KIyBNYWtlIHJpY2huZXNzIHBsb3RzDQpnZy5yaWNoIDwtICBkaXZfdGFibGUgJT4lDQogIGdyb3VwX2J5KHBzeWxsaWRfc3BwKSAlPiUNCiAgc3VtbWFyaXNlKGFscGhhPW1lYW4oYWxwaGEpLCBwZD1tZWFuKHBkKS8xMGUrNywgU2hhbm5vbj1tZWFuKFNoYW5ub24pKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBwaXZvdF9sb25nZXIoLXBzeWxsaWRfc3BwLCBuYW1lc190bz0ibWVhc3VyZSIsDQogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKSAgJT4lDQogIGxlZnRfam9pbihsaXBhX291dCAlPiUgDQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QocHN5bGxpZF9zcHAsIGFscGhhX3B2YWwsIHBkX3B2YWwsIFNoYW5ub25fcHZhbCkgJT4lDQogICAgICAgICAgICAgIHBpdm90X2xvbmdlcigtcHN5bGxpZF9zcHAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lc190bz0ibWVhc3VyZSIsDQogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAicHZhbCIpICU+JQ0KICAgICAgICAgICAgICBtdXRhdGUobWVhc3VyZSA9IHN0cl9yZW1vdmUobWVhc3VyZSwgIl9wdmFsIikpKSAlPiUNCiAgbGVmdF9qb2luKHAkZGF0YSAlPiUNCiAgICAgICAgICAgICAgYXNfZGF0YV9mcmFtZSAlPiUNCiAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihpc1RpcCkgJT4lDQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoeSwgbGFiZWwpICU+JQ0KICAgICAgICAgICAgICBkcGx5cjo6cmVuYW1lKHBzeWxsaWRfc3BwPWxhYmVsKSkgJT4lDQogIG11dGF0ZV9hdCh2YXJzKG1lYXN1cmUpLCBmdW5zKGZhY3RvciguLCBsZXZlbHM9YygiYWxwaGEiLCJTaGFubm9uIiwicGQiKSkpKSAlPiUNCiAgZ2dwbG90KGFlcyh4PXksIHk9dmFsdWUsIGZpbGw9cHZhbDwwLjA1KSkgKyANCiAgICBnZW9tX2NvbCgpICsNCiAgICBmYWNldF9ncmlkKH5tZWFzdXJlLCBzY2FsZXM9ImZyZWUiKSArIA0KICAgIGNvb3JkX2ZsaXAoKSsNCiAgICBiYXNlX3RoZW1lICArDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsDQogICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKGNvbG91cj0iZ3JleTkyIiwgc2l6ZT0wLjUsIGxpbmV0eXBlPSJkYXNoZWQiKSwNCiAgICMgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImdyZXk5MiIsIA0KICAgICMgICAgICAgICAgICAgIGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAxKSwNCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpLA0KICAgIGF4aXMudGl0bGUueT1lbGVtZW50X2JsYW5rKCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKSArDQogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoImRhcmtncmF5IiwgImRhcmtyZWQiKSkgKw0KICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2NhbGVzOjpwcmV0dHlfYnJlYWtzKG49MSkpKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kPWMoMCwwKSkrDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQ9YygwLDApKQ0KDQojIyBDb2xsZWN0aW9uX2hpc3QNCmdnLnNwcCA8LSBzYW1wbGVfZGF0YShwczIpICU+JQ0KICBhcygibWF0cml4IikgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgZHBseXI6OnJlbmFtZShsYWJlbCA9IHBzeWxsaWRfc3BwKSAlPiUNCiAgZ3JvdXBfYnkobGFiZWwpICU+JQ0KICBzdW1tYXJpc2Uobl9zcGVjaWVzID0gbigpKSAlPiUNCiAgbGVmdF9qb2luKHAkZGF0YSAlPiUNCiAgZmlsdGVyKGlzVGlwKSAlPiUgZHBseXI6OnNlbGVjdChjKGxhYmVsLCB5KSkpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHkpKSAlPiUNCiAgZ2dwbG90KGFlcyh4PXksIHk9MSwgZmlsbD1uX3NwZWNpZXMpKSArDQogICAgZ2VvbV90aWxlKCkgKw0KICAgIGdlb21fdGV4dChhZXMobGFiZWw9bl9zcGVjaWVzKSkrDQogICAgY29vcmRfZmxpcCgpICsNCiAgICB0aGVtZV92b2lkKCkgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArDQogICAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJSZWRzIiwgZGlyZWN0aW9uPTEpKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kPWMoMCwwKSkrDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQ9YygwLDApKQ0KDQojIE1ha2UgdHJlZSB3aXRoIG5vIHVuZGVyc2NvcmVzIGluIG5hbWUNCnAyIDwtIHANCnAyJGRhdGEkbGFiZWwgPC0gc3RyX3JlcGxhY2UocDIkZGF0YSRsYWJlbCwgIl8iLCAiICIpDQoNCiNBcnJhbmdlDQpGaWcxIDwtIHAyICsgZ2cuc3BwICsgZ2cuYmFyICsgZ2cucmljaCArIHBsb3RfbGF5b3V0KG5yb3c9MSwgd2lkdGhzPWMoMSwwLjA4LDIsMC42KSkgIA0KDQpwZGYoZmlsZT0iZmlncy9CZXRhLnBkZiIsIHdpZHRoID0gOCwgaGVpZ2h0ID0gMTEgLCBwYXBlcj0iYTQiKQ0KICBwbG90KEZpZzEpDQp0cnkoZGV2Lm9mZigpLCBzaWxlbnQ9VFJVRSkNCmBgYA0KDQojIyBQaHlsb3N5bWJpb3Npcw0KDQojIyMgUHJlcGFyZSBkaXN0YW5jZSBtYXRyaWNlcw0KYGBge3IgcHJlcGFyZSBkaXN0YW5jZXN9DQojIyBQc3lsbGlkIHBoeWxvZ2VueSBjb3BoZW5ldGljIGRpc3RhbmNlIG1hdHJpeA0KcGh5bG8uZGlzdCA8LSBjb3BoZW5ldGljKHBydW5lZC50cmVlKSAlPiUNCiAgIHNxcnQoKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICByb3duYW1lc190b19jb2x1bW4oInBzeWxsaWRfc3BwLngiKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHM9LXBzeWxsaWRfc3BwLngsDQogICAgICAgICAgICAgICBuYW1lc190bz0icHN5bGxpZF9zcHAueSIsDQogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiZGlzdCIpICU+JQ0KICByaWdodF9qb2luKHNhbXBsZV9kYXRhKHBzMikgJT4lDQogICAgICAgICAgICAgICBhcygibWF0cml4IikgJT4lDQogICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoU2FtcGxlX05hbWUsIHBzeWxsaWRfc3BwKSAlPiUNCiAgICAgICAgICAgICAgZHBseXI6OnJlbmFtZShwc3lsbGlkX3NwcC54ID0gcHN5bGxpZF9zcHAsIFNhbXBsZV9OYW1lLnggPSBTYW1wbGVfTmFtZSksDQogICAgICAgICAgICBieT0icHN5bGxpZF9zcHAueCIpJT4lDQogIHJpZ2h0X2pvaW4oc2FtcGxlX2RhdGEocHMyKSAlPiUNCiAgICAgICAgICAgICAgIGFzKCJtYXRyaXgiKSAlPiUNCiAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChTYW1wbGVfTmFtZSwgcHN5bGxpZF9zcHApICU+JQ0KICAgICAgICAgICAgICBkcGx5cjo6cmVuYW1lKHBzeWxsaWRfc3BwLnkgPSBwc3lsbGlkX3NwcCwgU2FtcGxlX05hbWUueSA9IFNhbXBsZV9OYW1lKSwNCiAgICAgICAgICAgIGJ5PSJwc3lsbGlkX3NwcC55IikgJT4lDQogIGZpbHRlcighaXMubmEoZGlzdCkpICU+JQ0KICBkcGx5cjo6c2VsZWN0KC1wc3lsbGlkX3NwcC55LCAtcHN5bGxpZF9zcHAueCkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBTYW1wbGVfTmFtZS55LCB2YWx1ZXNfZnJvbSA9IGRpc3QpICAlPiUNCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJTYW1wbGVfTmFtZS54IikgJT4lDQogIGFzLm1hdHJpeCgpDQoNCnBsYW50LnRyZWUgPC0gcmVhZC50cmVlKCJzYW1wbGVfZGF0YS9wbGFudF90cmVlLm53ayIpDQpwbGFudC5kaXN0IDwtIGNvcGhlbmV0aWMocGxhbnQudHJlZSkgJT4lDQogIHNxcnQoKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICByb3duYW1lc190b19jb2x1bW4oImhvc3RwbGFudF9zcHAueCIpICU+JQ0KICBwaXZvdF9sb25nZXIoY29scz0taG9zdHBsYW50X3NwcC54LA0KICAgICAgICAgICAgICAgbmFtZXNfdG89Imhvc3RwbGFudF9zcHAueSIsDQogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiZGlzdCIpICU+JQ0KICByaWdodF9qb2luKHNhbXBsZV9kYXRhKHBzMikgJT4lDQogICAgICAgICAgICAgICBhcygibWF0cml4IikgJT4lDQogICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoU2FtcGxlX05hbWUsIGhvc3RwbGFudF9zcHApICU+JQ0KICAgICAgICAgICAgICBkcGx5cjo6cmVuYW1lKGhvc3RwbGFudF9zcHAueCA9IGhvc3RwbGFudF9zcHAsIFNhbXBsZV9OYW1lLnggPSBTYW1wbGVfTmFtZSksDQogICAgICAgICAgICBieT0iaG9zdHBsYW50X3NwcC54IiklPiUNCiAgcmlnaHRfam9pbihzYW1wbGVfZGF0YShwczIpICU+JQ0KICAgICAgICAgICAgICAgYXMoIm1hdHJpeCIpICU+JQ0KICAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KFNhbXBsZV9OYW1lLCBob3N0cGxhbnRfc3BwKSAlPiUNCiAgICAgICAgICAgICAgZHBseXI6OnJlbmFtZShob3N0cGxhbnRfc3BwLnkgPSBob3N0cGxhbnRfc3BwLCBTYW1wbGVfTmFtZS55ID0gU2FtcGxlX05hbWUpLA0KICAgICAgICAgICAgYnk9Imhvc3RwbGFudF9zcHAueSIpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGRpc3QpKSU+JQ0KICBkcGx5cjo6c2VsZWN0KC1ob3N0cGxhbnRfc3BwLnksIC1ob3N0cGxhbnRfc3BwLngpICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gU2FtcGxlX05hbWUueSwgdmFsdWVzX2Zyb20gPSBkaXN0KSAlPiUNCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJTYW1wbGVfTmFtZS54IikgJT4lDQogIGFzLm1hdHJpeCgpDQoNCiMgU3BhdGlhbCBkaXN0YW5jZSBtYXRyaXgNCmVudkRhdGEgPC0gc2FtcGxlX2RhdGEocHMyKSAlPiUNCiAgYXMoIm1hdHJpeCIpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIGRwbHlyOjpzZWxlY3QobG9uZywgbGF0KSAlPiUNCiAgbXV0YXRlKGxvbmcgPSBhcy5udW1lcmljKGxvbmcpLCBsYXQ9YXMubnVtZXJpYyhsYXQpKSU+JQ0KICBkcm9wX25hKCkgDQogIA0Kc3BhdC5kaXN0IDwtIHNwRGlzdHMoYXMubWF0cml4KGVudkRhdGEpLCBsb25nbGF0PVRSVUUpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIG1hZ3JpdHRyOjpzZXRfcm93bmFtZXMocm93bmFtZXMoZW52RGF0YSkgJT4lIHN0cl9yZXBsYWNlKHBhdHRlcm49IlxcX1MoLiopJCIscmVwbGFjZW1lbnQ9IiIpICU+JSBtYWtlLnVuaXF1ZSgpKSAlPiUNCiAgbWFncml0dHI6OnNldF9jb2xuYW1lcyhyb3duYW1lcyhlbnZEYXRhKSAlPiUgc3RyX3JlcGxhY2UocGF0dGVybj0iXFxfUyguKikkIixyZXBsYWNlbWVudD0iIikgJT4lIG1ha2UudW5pcXVlKCkpICU+JQ0KICByb3duYW1lc190b19jb2x1bW4oIlNhbXBsZUlEIikgJT4lDQogIG11dGF0ZShTYW1wbGVJRCA9IFNhbXBsZUlEICU+JSBzdHJfcmVwbGFjZShwYXR0ZXJuPSJcXF9TKC4qKSQiLHJlcGxhY2VtZW50PSIiKSAlPiUgbWFrZS51bmlxdWUoKSkgJT4lDQogIGNvbHVtbl90b19yb3duYW1lcygiU2FtcGxlSUQiKSAlPiUNCiAgYXMubWF0cml4KCkNCg0KYGBgDQoNCiMjIyBXaG9sZSBkYXRhc2V0DQoNCmBgYHtyIHBoeWxvc3ltYmlvc2lzfQ0Kc2V0LnNlZWQoOTA5KQ0KZGlyLmNyZWF0ZSgib3V0cHV0L3BoeWxvc3ltYmlvc2lzIikNCg0KIyBNYXRyaXggY29ycmVsYXRpb25zDQojb25seSB1c2Ugc2FtcGxlcyBwcmVzZW50IGluIGFsbA0Kc3Vic2FtcGxlIDwtIFJlZHVjZShpbnRlcnNlY3QsIGxpc3Qocm93bmFtZXMob3R1X3RhYmxlKHBzMikpLCBjb2xuYW1lcyhwaHlsby5kaXN0KSwgY29sbmFtZXMocGxhbnQuZGlzdCksIGNvbG5hbWVzKHNwYXQuZGlzdCkpKQ0KDQojIE1hbnRlbCB0ZXN0DQptYW50ZWxfcmVzdWx0cyA8LSBkaXN0bGlzdCAlPiUNCiAgcHVycnI6Om1hcChmdW5jdGlvbih4KXsNCiAgICBydW5fbWFudGVsKHgsIGRpc3RzID0gYygicGh5bG8uZGlzdCIsICJwbGFudC5kaXN0IiwgInNwYXQuZGlzdCIpLA0KICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gc3Vic2FtcGxlLCB0eXBlICA9Im1hbnRlbCIsIG5ib290PTEwMDApDQogIH0pICU+JQ0KICBiaW5kX3Jvd3MoLmlkPSJkaXN0IikNCg0Kd3JpdGVfY3N2KG1hbnRlbF9yZXN1bHRzICU+JQ0KICAgICAgICAgICAgZHBseXI6OnNlbGVjdCgtb25lX29mKCJwdmFsMSIsInB2YWwyIikpLCNvbmx5IGtlZXAgdHdvIHNpZGVkIFAgdmFsdWVzDQogICAgICAgICAgIm91dHB1dC9waHlsb3N5bWJpb3Npcy9tYW50ZWxfZnVsbGRhdGEuY3N2IikNCg0KDQojIFBhcnRpYWwgTWFudGVsIFRlc3QNCnBtYW50ZWxfcmVzdWx0cyA8LSBkaXN0bGlzdCAlPiUNCiAgcHVycnI6Om1hcChmdW5jdGlvbih4KXsNCiAgICBydW5fbWFudGVsKHgsIGRpc3RzID0gYygicGh5bG8uZGlzdCIsICJwbGFudC5kaXN0IiwgInNwYXQuZGlzdCIpLA0KICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gc3Vic2FtcGxlLCB0eXBlID0gInBhcnRpYWwiLCBuYm9vdD0xMDAwKQ0KICB9KSAlPiUNCiAgYmluZF9yb3dzKC5pZD0iZGlzdCIpIA0KDQp3cml0ZV9jc3YocG1hbnRlbF9yZXN1bHRzICU+JSBkcGx5cjo6c2VsZWN0KC1vbmVfb2YoInB2YWwxIiwicHZhbDIiKSksDQogICAgICAgICAgIm91dHB1dC9waHlsb3N5bWJpb3Npcy9wbWFudGVsX2Z1bGxkYXRhLmNzdiIpDQoNCiMjIFBsb3QgbWFudGVscw0KZ2cubWFudGVscyA8LSBiaW5kX3Jvd3MobWFudGVsX3Jlc3VsdHMsIHBtYW50ZWxfcmVzdWx0cykgJT4lDQogICAgICAgICAgICAgIGRwbHlyOjptdXRhdGUoZGlzdDEgPSBjYXNlX3doZW4oDQogICAgICAgICAgICAgICAgc3RyX2RldGVjdChkaXN0MSwgInBsYW50LmRpc3QiKSB+ICJIb3N0cGxhbnQgcGh5bG9nZW55IiwNCiAgICAgICAgICAgICAgICBzdHJfZGV0ZWN0KGRpc3QxLCAicGh5bG8uZGlzdCIpIH4gIlBzeWxsaWQgcGh5bG9nZW55IiwgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgc3RyX2RldGVjdChkaXN0MSwgInNwYXQuZGlzdCIpIH4gIlNwYXRpYWwgZGlzdGFuY2UiICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICApKSAlPiUNCiAgZmlsdGVyKGRpc3QgPT0gIkFpdGNoaXNvbiIpICU+JQ0KICBtdXRhdGUoZGlzdDEgPSBmYWN0b3IoZGlzdDEsIGxldmVscz0gYygiU3BhdGlhbCBkaXN0YW5jZSIsIkhvc3RwbGFudCBwaHlsb2dlbnkiLCAgIlBzeWxsaWQgcGh5bG9nZW55IikpKSAlPiUNCiAgbXV0YXRlKHR5cGUgPSB0eXBlICU+JSAgDQogICAgICAgICAgIHN0cl9yZXBsYWNlKCJtYW50ZWwiLCAiTWFudGVsIikgJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlKCJwYXJ0aWFsX01hbnRlbCIsICJQYXJ0aWFsIE1hbnRlbCIpKSAlPiUNCiAgICAgICAgICAgICAgZ2dwbG90KGFlcyh4PW1hbnRlbHIsIHk9ZGlzdDEsIGNvbG91cj1kaXN0MSkpICsgDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgY29sb3VyPSJibGFjayIsIGxpbmV0eXBlPTIpICsNCiAgZ2VvbV9wb2ludHJhbmdlKGFlcyh4bWluPWBsbGltLjIuNSVgLCB4bWF4PWB1bGltLjk3LjUlYCksIHNpemU9MSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIkhvc3RwbGFudCBwaHlsb2dlbnkiPSIjYjJkZjhhIiwiU3BhdGlhbCBkaXN0YW5jZSI9IiNhNmNlZTMiLCAiUHN5bGxpZCBwaHlsb2dlbnkiPSIjMWY3OGI0IikpKw0KICBmYWNldF93cmFwKH50eXBlLCBuY29sPTIpICsNCiAgbGFicyggeCA9ICJNYW50ZWwgUiIsIHkgPSBOVUxMLCBjb2xvdXI9TlVMTCkgKw0KICBiYXNlX3RoZW1lICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKCkpDQoNCmdnLm1hbnRlbHMNCg0KcGRmKGZpbGU9ImZpZ3MvZmlnM19tYW50ZWxzLnBkZiIsICB3aWR0aCA9IDgsIGhlaWdodCA9IDQsIHBhcGVyPSJhNHIiKQ0KICBwbG90KGdnLm1hbnRlbHMpDQp0cnkoZGV2Lm9mZigpLCBzaWxlbnQ9VFJVRSkNCiAgDQpgYGANCg0KIyMjIFdpdGhvdXQgR2FtbWFwcm90ZW9iYWN0ZXJpYQ0KDQpgYGB7ciBwaHlsb3N5bWJpb3NpcyBubyBlbnRlcm99DQojIEFkb25pcyB0ZXN0DQptZXRhZGF0YSA8LSBzYW1wbGVfZGF0YShwczJfc3Vic2V0KSAlPiUNCiAgYXMoImRhdGEuZnJhbWUiKQ0KDQphZG9uaXNfcmVzdWx0cyA8LSBkaXN0bGlzdF9zdWJzZXQgJT4lDQogIHB1cnJyOjptYXAoZnVuY3Rpb24oeCkgew0KICAgIHkgPC0gYXMuZGlzdCh4W21ldGFkYXRhJFNhbXBsZV9OYW1lLCBtZXRhZGF0YSRTYW1wbGVfTmFtZV0pDQogICAgYmluZF9yb3dzKA0KICAgIGJyb29tOjp0aWR5KGFkb25pczIoeX5zZXFydW4rcHN5bGxpZF9mYW1pbHkrcHN5bGxpZF9nZW51cytwc3lsbGlkX3NwcCwgbWV0aG9kPSJldWNsaWRlYW4iLCBkYXRhPW1ldGFkYXRhLCANCiAgICAgICAgICAgICAgICAgICAgICAgcGVybXV0YXRpb25zPTk5OSkpICU+JSANCiAgICAgIG11dGF0ZSh0ZXN0ID0gcGFzdGUodGVybVshdGVybSAlaW4lIGMoIlJlc2lkdWFsIiwgIlRvdGFsIildLCBjb2xsYXBzZT0iLSIpKSwNCiAgICBicm9vbTo6dGlkeShhZG9uaXMyKHl+c2VxcnVuK2hvc3RwbGFudF9zcHAsIG1ldGhvZD0iZXVjbGlkZWFuIiwgZGF0YT1tZXRhZGF0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgcGVybXV0YXRpb25zPTk5OSkpICU+JSANCiAgICAgIG11dGF0ZSh0ZXN0ID0gcGFzdGUodGVybVshdGVybSAlaW4lIGMoIlJlc2lkdWFsIiwgIlRvdGFsIildLCBjb2xsYXBzZT0iLSIpKQ0KICAgICkNCn0pICAlPiUNCiAgYmluZF9yb3dzKC5pZD0iZGlzdCIpDQoNCndyaXRlX2NzdihhZG9uaXNfcmVzdWx0cywgIm91dHB1dC9iZXRhL2Fkb25pc19zdWJzZXQuY3N2IikNCg0KIyBNYXRyaXggY29ycmVsYXRpb25zDQojb25seSB1c2Ugc2FtcGxlcyBwcmVzZW50IGluIGFsbA0Kc3Vic2FtcGxlIDwtIFJlZHVjZShpbnRlcnNlY3QsIGxpc3Qocm93bmFtZXMob3R1X3RhYmxlKHBzMl9zdWJzZXQpKSwgY29sbmFtZXMocGh5bG8uZGlzdCksIGNvbG5hbWVzKHBsYW50LmRpc3QpLCBjb2xuYW1lcyhzcGF0LmRpc3QpKSkNCg0KIyBNYW50ZWwgdGVzdA0KbWFudGVsX3Jlc3VsdHMgPC0gZGlzdGxpc3Rfc3Vic2V0ICU+JQ0KICBwdXJycjo6bWFwKGZ1bmN0aW9uKHgpew0KICAgIHJ1bl9tYW50ZWwoeCwgZGlzdHM9YygicGh5bG8uZGlzdCIsICJwbGFudC5kaXN0IiwgInNwYXQuZGlzdCIpLA0KICAgICAgICAgICAgICAgc3Vic2FtcGxlPXN1YnNhbXBsZSwgdHlwZT0ibWFudGVsIikNCiAgfSkgJT4lDQogIGJpbmRfcm93cyguaWQ9ImRpc3QiKSANCg0Kd3JpdGVfY3N2KG1hbnRlbF9yZXN1bHRzICU+JSBkcGx5cjo6c2VsZWN0KC1wdmFsMSwgLXB2YWwyKSwgIm91dHB1dC9waHlsb3N5bWJpb3Npcy9tYW50ZWxfc3Vic2V0LmNzdiIpDQoNCiMgUGFydGlhbCBNYW50ZWwgVGVzdA0KcG1hbnRlbF9yZXN1bHRzIDwtIGRpc3RsaXN0X3N1YnNldCAlPiUNCiAgcHVycnI6Om1hcChmdW5jdGlvbih4KXsNCiAgICBydW5fbWFudGVsKHgsIGRpc3RzPWMoInBoeWxvLmRpc3QiLCAicGxhbnQuZGlzdCIsICJzcGF0LmRpc3QiKSwNCiAgICAgICAgICAgICAgIHN1YnNhbXBsZT1zdWJzYW1wbGUsIHR5cGU9InBhcnRpYWwiKQ0KICB9KSAlPiUNCiAgYmluZF9yb3dzKC5pZD0iZGlzdCIpIA0KDQp3cml0ZV9jc3YocG1hbnRlbF9yZXN1bHRzICU+JSBkcGx5cjo6c2VsZWN0KC1wdmFsMSwtcHZhbDIpLCAib3V0cHV0L3BoeWxvc3ltYmlvc2lzL3BtYW50ZWxfc3Vic2V0LmNzdiIpDQpgYGANCg0KIyMgUENBIHBsb3RzDQpgYGB7ciBQQ0F9DQojc2V0IGRpc3RhbmNlDQpwY2FfZGlzdCA8LSBkaXN0bGlzdCRBaXRjaGlzb24NCg0KI1BDQSANCnIucGN4IDwtIHByY29tcChwY2FfZGlzdCkNCnBjX3NhbXAgPC0gZGF0YS5mcmFtZShTYW1wbGVfTmFtZSA9IHJvd25hbWVzKHIucGN4JHgpLCByLnBjeCR4WywgMToyXSklPiUNCiAgbGVmdF9qb2luKHNhbXBsZV9kYXRhKHBzMikgJT4lDQogICAgICAgICAgICAgIGFzKCJtYXRyaXgiKSAlPiUNCiAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSgpLCBieT0iU2FtcGxlX05hbWUiKQ0KIyBjYWxjdWxhdGUgcGVyY2VudCB2YXJpYW5jZSBleHBsYWluZWQgZm9yIHRoZSBheGlzIGxhYmVscw0KcGMxIDwtIHJvdW5kKHIucGN4JHNkZXZbMV1eMi9zdW0oci5wY3gkc2Rldl4yKSwyKQ0KcGMyIDwtIHJvdW5kKHIucGN4JHNkZXZbMl1eMi9zdW0oci5wY3gkc2Rldl4yKSwyKQ0KcGNfeWxhYiA8LSBwYXN0ZSgiUEMxOiAiLCBwYzEsIHNlcD0iIikNCnBjX3hsYWIgPC0gcGFzdGUoIlBDMjogIiwgcGMyLCBzZXA9IiIpDQoNCiMjIGNvbG91ciBieSBwc3lsbGlkIHBoeWxvZ2VueQ0KZGVuZCA8LSBhcy5kZW5kcm9ncmFtKGZvcmNlLnVsdHJhbWV0cmljKHBydW5lZC50cmVlKSkNCm1lbWJlcnNoaXAgPC0gYXMuZGF0YS5mcmFtZShjdXRyZWUoZGVuZCwgaz0xMSkpICU+JQ0KICByb3duYW1lc190b19jb2x1bW4oInBzeWxsaWRfc3BwIikgJT4lDQogIG1hZ3JpdHRyOjpzZXRfY29sbmFtZXMoYygicHN5bGxpZF9zcHAiLCAiY2x1c3RlciIpKQ0KDQpwY2FfcGh5bG8gPC0gcGNfc2FtcCAlPiUNCiAgbGVmdF9qb2luKG1lbWJlcnNoaXApDQpnZy5wY2EgPC0gZ2dwbG90KGRhdGE9cGNhX3BoeWxvLCBhZXMoeD1QQzIsIHk9UEMxLCBjb2xvdXI9YXMuZmFjdG9yKGNsdXN0ZXIpKSkgKyANCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUsIHNpemU9MykgKyAjLCBzaGFwZT0yMSwgY29sb3VyPSJibGFjayINCiAgI2dlb21fcG9pbnQoZGF0YT1wY19vdHUsYWVzKFBDMSwgUEMyKSkgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGU9IlBhaXJlZCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGU9MiwgYWxwaGE9MC41KSArICANCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbGluZXR5cGU9MiwgYWxwaGE9MC41KSArDQogIHhsYWIocGNfeGxhYikgKyANCiAgeWxhYihwY195bGFiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBzY2FsZV95X3JldmVyc2UocG9zaXRpb24gPSAicmlnaHQiKSANCiAgI2Nvb3JkX2ZpeGVkKHJhdGlvPXBjMi9wYzEpICMgU2NhbGUgcGxvdCBieSB2YXJpYW5jZSBleHBsYWluZWQNCg0KcDEgPC0gZ2d0cmVlKHBydW5lZC50cmVlKSArDQogICAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZD1jKDAsIDAuMikpICsgDQogIHRoZW1lX3RyZWUyKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpIA0KDQpjb2xvdXJzX3AxIDwtIHAxJGRhdGEgJT4lDQogIGxlZnRfam9pbihtZW1iZXJzaGlwICU+JQ0KICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gcHN5bGxpZF9zcHApDQogICAgKSAlPiUNCiAgbXV0YXRlKG5ld19sYWJlbCA9IGxhYmVsICU+JSBzdHJfcmVwbGFjZV9hbGwoIl8iLCAiICIpKQ0KDQpwMSA8LSBwMSAlPCslIGNvbG91cnNfcDEgICsgDQogIGdlb21fdGlwcG9pbnQoYWVzKGNvbG91cj1hcy5mYWN0b3IoY2x1c3RlcikpKSAgKyANCiAgZ2VvbV90aXBsYWIoYWVzKGNvbG91cj1hcy5mYWN0b3IoY2x1c3RlciksIGxhYmVsPW5ld19sYWJlbCkpKw0KICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGU9IlBhaXJlZCIpIA0KDQpnZy5wc3lsbGlkX3BjYSA8LSBwMSArIGdnLnBjYSArIHBsb3RfYW5ub3RhdGlvbih0aXRsZT0iUHN5bGxpZCBwaHlsb2dlbmV0aWMgZGlzdGFuY2UiKQ0KZ2cucHN5bGxpZF9wY2ENCg0KcGRmKGZpbGU9ImZpZ3MvcHN5bGxpZF9wY2EucGRmIiwgIHdpZHRoID0gOCwgaGVpZ2h0ID0gMTEsIHBhcGVyPSJhNCIpDQogIHBsb3QoZ2cucHN5bGxpZF9wY2EpDQp0cnkoZGV2Lm9mZigpLCBzaWxlbnQ9VFJVRSkNCiAgDQojIENvbG91ciBieSBwbGFudCBwaHlsb2dlbnkNCnBsYW50LnRyZWUgPC0gcmVhZC50cmVlKCJzYW1wbGVfZGF0YS9wbGFudF90cmVlLm53ayIpDQpwbGFudC50cmVlICA8LSBkcm9wLnRpcChwbGFudC50cmVlLCAiU29waG9yYV9taWNyb3BoeWxsYV8ta293aGFpIiApDQpwbGFudC50cmVlIDwtIG11bHRpMmRpKHBsYW50LnRyZWUpDQoNCmRlbmQgPC0gYXMuZGVuZHJvZ3JhbShwbGFudC50cmVlICkNCiN0ZXN0IDwtIGNvbG9yX2JyYW5jaGVzKGRlbmQsIGs9MTIpDQojcGxvdCh0ZXN0KQ0KbWVtYmVyc2hpcCA8LSBhcy5kYXRhLmZyYW1lKGN1dHJlZShkZW5kLCBrPTEyKSkgJT4lDQogIHJvd25hbWVzX3RvX2NvbHVtbigiaG9zdHBsYW50X3NwcCIpICU+JQ0KICBtYWdyaXR0cjo6c2V0X2NvbG5hbWVzKGMoImhvc3RwbGFudF9zcHAiLCAiY2x1c3RlciIpKQ0KDQpwY2FfcGh5bG8gPC0gcGNfc2FtcCAlPiUNCiAgbGVmdF9qb2luKG1lbWJlcnNoaXApDQpnZy5wY2EgPC0gZ2dwbG90KGRhdGE9cGNhX3BoeWxvLCBhZXMoeD1QQzIsIHk9UEMxLCBjb2xvdXI9YXMuZmFjdG9yKGNsdXN0ZXIpKSkgKyANCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUsIHNpemU9MykgKyAjLCBzaGFwZT0yMSwgY29sb3VyPSJibGFjayINCiAgI2dlb21fcG9pbnQoZGF0YT1wY19vdHUsYWVzKFBDMSwgUEMyKSkgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGU9IlBhaXJlZCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGU9MiwgYWxwaGE9MC41KSArICANCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbGluZXR5cGU9MiwgYWxwaGE9MC41KSArDQogIHhsYWIocGNfeGxhYikgKyANCiAgeWxhYihwY195bGFiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBzY2FsZV95X3JldmVyc2UocG9zaXRpb24gPSAicmlnaHQiKQ0KICAjY29vcmRfZml4ZWQocmF0aW89cGMyL3BjMSkgIyBTY2FsZSBwbG90IGJ5IHZhcmlhbmNlIGV4cGxhaW5lZA0KDQpwMiA8LSBnZ3RyZWUocGxhbnQudHJlZSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kPWMoMCwgNzApKSArIA0KICB0aGVtZV90cmVlMigpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSANCg0KY29sb3Vyc19wMiA8LSBwMiRkYXRhICU+JQ0KICBsZWZ0X2pvaW4obWVtYmVyc2hpcCAlPiUNCiAgZHBseXI6OnJlbmFtZShsYWJlbCA9IGhvc3RwbGFudF9zcHApDQogICAgKSU+JQ0KICBtdXRhdGUobmV3X2xhYmVsID0gbGFiZWwgJT4lIHN0cl9yZXBsYWNlX2FsbCgiXyIsICIgIikpDQoNCnAyIDwtIHAyICU8KyUgY29sb3Vyc19wMiAgKyANCiAgZ2VvbV90aXBwb2ludChhZXMoY29sb3VyPWFzLmZhY3RvcihjbHVzdGVyKSkpICArIA0KICBnZW9tX3RpcGxhYihhZXMoY29sb3VyPWFzLmZhY3RvcihjbHVzdGVyKSwgbGFiZWw9bmV3X2xhYmVsKSkrDQogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZT0iUGFpcmVkIikgDQoNCmdnLnBsYW50X3BjYSA8LSBwMiArIGdnLnBjYSArIHBsb3RfYW5ub3RhdGlvbih0aXRsZT0iUGxhbnQgcGh5bG9nZW5ldGljIGRpc3RhbmNlIikNCmdnLnBsYW50X3BjYQ0KDQpwZGYoZmlsZT0iZmlncy9wbGFudF9wY2EucGRmIiwgIHdpZHRoID0gOCwgaGVpZ2h0ID0gMTEsIHBhcGVyPSJhNCIpDQogIHBsb3QoZ2cucGxhbnRfcGNhKQ0KdHJ5KGRldi5vZmYoKSwgc2lsZW50PVRSVUUpDQoNCiMgSEMgb2Ygc3BhdGlhbCBkaXN0YW5jZQ0KZGVuZCA8LSBoY2x1c3QoYXMuZGlzdChzcGF0LmRpc3QpLCBtZXRob2Q9ImF2ZXJhZ2UiKQ0KI3Rlc3QgPC0gY29sb3JfYnJhbmNoZXMoZGVuZCwgaz0xMikNCiNwbG90KHRlc3QpDQoNCm1lbWJlcnNoaXAgPC0gYXMuZGF0YS5mcmFtZShjdXRyZWUoZGVuZCwgaz0xMikpICU+JQ0KICByb3duYW1lc190b19jb2x1bW4oIlNhbXBsZV9OYW1lIikgJT4lDQogIG1hZ3JpdHRyOjpzZXRfY29sbmFtZXMoYygiU2FtcGxlX05hbWUiLCAiY2x1c3RlciIpKQ0KDQpwY2FfcGh5bG8gPC0gcGNfc2FtcCAlPiUNCiAgbGVmdF9qb2luKG1lbWJlcnNoaXApDQpnZy5wY2EgPC0gZ2dwbG90KGRhdGE9cGNhX3BoeWxvLCBhZXMoeD1QQzIsIHk9UEMxLCBjb2xvdXI9YXMuZmFjdG9yKGNsdXN0ZXIpKSkgKyANCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUsIHNpemU9MykgKyAjLCBzaGFwZT0yMSwgY29sb3VyPSJibGFjayINCiAgI2dlb21fcG9pbnQoZGF0YT1wY19vdHUsYWVzKFBDMSwgUEMyKSkgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGU9IlBhaXJlZCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGU9MiwgYWxwaGE9MC41KSArICANCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbGluZXR5cGU9MiwgYWxwaGE9MC41KSArDQogIHhsYWIocGNfeGxhYikgKyANCiAgeWxhYihwY195bGFiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBzY2FsZV95X3JldmVyc2UocG9zaXRpb24gPSAicmlnaHQiKQ0KICAjY29vcmRfZml4ZWQocmF0aW89cGMyL3BjMSkgIyBTY2FsZSBwbG90IGJ5IHZhcmlhbmNlIGV4cGxhaW5lZA0KDQpwMyA8LSBnZ3RyZWUoYXMucGh5bG8oZGVuZCkgKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZD1jKDAsIDQwMCkpICsNCiAgdGhlbWVfdHJlZTIoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgDQoNCmNvbG91cnNfcDMgPC0gcDMkZGF0YSAlPiUNCiAgbGVmdF9qb2luKG1lbWJlcnNoaXAgJT4lDQogIGRwbHlyOjpyZW5hbWUobGFiZWwgPSBTYW1wbGVfTmFtZSkNCiAgICApJT4lDQogIG11dGF0ZShuZXdfbGFiZWwgPSBsYWJlbCAlPiUgc3RyX3JlcGxhY2VfYWxsKCJfIiwgIiAiKSkNCg0KcDMgPC0gcDMgJTwrJSBjb2xvdXJzX3AzICArIA0KICBnZW9tX3RpcHBvaW50KGFlcyhjb2xvdXI9YXMuZmFjdG9yKGNsdXN0ZXIpKSkgICsgDQogICNnZW9tX3RpcGxhYihhZXMoY29sb3VyPWFzLmZhY3RvcihjbHVzdGVyKSwgbGFiZWw9bmV3X2xhYmVsKSkrDQogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZT0iUGFpcmVkIikgDQoNCmdnLnNwYXRfcGNhIDwtIHAzICsgZ2cucGNhICsgcGxvdF9hbm5vdGF0aW9uKHRpdGxlPSJTcGF0aWFsIERpc3RhbmNlIikNCmdnLnNwYXRfcGNhDQoNCnBkZihmaWxlPSJmaWdzL3NwYXRpYWxfcGNhLnBkZiIsICB3aWR0aCA9IDgsIGhlaWdodCA9IDExLCBwYXBlcj0iYTQiKQ0KICBwbG90KGdnLnNwYXRfcGNhKQ0KdHJ5KGRldi5vZmYoKSwgc2lsZW50PVRSVUUpDQpgYGANCg0KIyBDb3BoeWxvZ2VueQ0KDQpSdW4gdGhlIHNjcmlwdHMgaW4gdGhlIFIgc3ViZGlyZWN0b3J5DQoNCmBgYHtiYXNoIHN1Ym1pdCBwYWNvIGpvYnN9DQpjZCAvZ3JvdXAvcGF0aG9nZW5zL0FsZXhwL01ldGFiYXJjb2RpbmcvUHN5bGxpZF9taWNyb2Jpb21lL3BhY29fcGFyYQ0KZG9zMnVuaXggYmF0Y2hfc3VibWl0X3BhY28uc2gNCmZpbmQgJCgvdXNyL2Jpbi9scyAtZCAkUFdELyopIC1uYW1lICdwYWNvXyonIC10eXBlIGYgfCBncmVwIC12ICcucmRzJyA+IHNlcXVlbmNlX2luZGV4LnR4dA0Kam9ibGVuZ3RoPSQoY2F0IHNlcXVlbmNlX2luZGV4LnR4dCB8IHdjIC1sKQ0Kc2JhdGNoIC0tYXJyYXk9MS0kam9ibGVuZ3RoIGJhdGNoX3N1Ym1pdF9wYWNvLnNoDQpgYGANCg0KIyBNaWNyb2Jpb21lIDMgZ2VuZXJhIGhlYXRtYXANCg0KYGBge3IgYWxsIG1pY3JvYmV9DQpmaWx0ZXJmdW4xIDwtIGZ1bmN0aW9uKHgpew0KICB4Wyh4IC8gc3VtKHgpKSA8ICgxZS00KV0gPC0gMCAjMWUtNCBpcyAwLjAxJSB0aHJlc2hvbGQNCiAgcmV0dXJuKHgpDQp9DQpwczNfZmlsdCAgPC0gdHJhbnNmb3JtX3NhbXBsZV9jb3VudHMocHMzLCBmdW4gPSBmaWx0ZXJmdW4xKSU+JQ0KICBmaWx0ZXJfdGF4YShmdW5jdGlvbih4KSBtZWFuKHgpID4gMCwgVFJVRSkgI0Ryb3AgbWlzc2luZyB0YXhhIGZyb20gdGFibGUNCg0KcHJpbnQocGFzdGUwKChudGF4YShwczMpLW50YXhhKHBzM19maWx0KSksICIgdGF4YSB1bmRlciB0aHJlc2hvbGQgcmVtb3ZlZCIpKQ0KDQojR2V0IGNvLW9jY3VyYW5jZSBtYXRyaXgNCmNvb2N1ciA8LSBwczNfZmlsdCAlPiUNCiAgICBzdWJzZXRfc2FtcGxlcyhwc3lsbGlkX2dlbnVzICVpbiUgYygiUG93ZWxsaWEiLCAiQ3RlbmFyeXRhaW5hIiwgIlBzeWxsYSIpKSAlPiUNCiAgICBmaWx0ZXJfdGF4YShmdW5jdGlvbih4KSBtZWFuKHgpID4gMCwgVFJVRSkgJT4lDQogICAgb3R1X3RhYmxlICU+JQ0KICAgIGFzLm1hdHJpeCgpICU+JSANCiAgYXBwbHkoMiwgZnVuY3Rpb24oeCkgaWZlbHNlKHggPiAwLCAxLCAwKSkNCg0KY29sbmFtZXMoY29vY3VyKSA8LSBjb2xuYW1lcyhjb29jdXIpICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIikNCnJvd25hbWVzKGNvb2N1cikgPC0gcm93bmFtZXMoY29vY3VyKSAlPiUgDQogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiIHwtIiwgIl8iKQ0KDQojIEggY29waGVuZXRpYyBkaXN0YW5jZQ0KaF90cmVlIDwtIHBydW5lZC50cmVlDQpoX3RyZWUkdGlwLmxhYmVsIDwtIGhfdHJlZSR0aXAubGFiZWwgJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiIHwtIiwgIl8iKQ0KaF90cmVlIDwtIGRyb3AudGlwKGhfdHJlZSwgc2V0ZGlmZihoX3RyZWUkdGlwLmxhYmVsLCByb3duYW1lcyhjb29jdXIpKSkNCg0KIyBQIGNvcGhlbmV0aWMgZGlzdGFuY2UNCnNfdHJlZSA8LSBwaHlfdHJlZShwczMpDQpzX3RyZWUkdGlwLmxhYmVsIDwtIHNfdHJlZSR0aXAubGFiZWwgJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiIHwtIiwgIl8iKQ0Kc190cmVlIDwtIGRyb3AudGlwKHNfdHJlZSwgc2V0ZGlmZihzX3RyZWUkdGlwLmxhYmVsLCBjb2xuYW1lcyhjb29jdXIpKSkNCg0KY29vY3VyIDwtIGNvb2N1cltoX3RyZWUkdGlwLmxhYmVsLCBzX3RyZWUkdGlwLmxhYmVsXQ0KDQojIFJlYWQgaW4gUGFjbyBydW5zIA0KcGFjb19ydW5fcG93ZWxsaWEgPC0gcmVhZFJEUygib3V0cHV0L2NvcGh5bG9nZW55L3BhY29fcG93ZWxsaWFfbWljcm9iZV9maWx0ZXJlZF9hc3ltLnJkcyIpDQpwYWNvX3J1bl9jdGVuIDwtIHJlYWRSRFMoIm91dHB1dC9jb3BoeWxvZ2VueS9wYWNvX2N0ZW5hcnl0YWluYV9taWNyb2JlX2ZpbHRlcmVkX2FzeW0ucmRzIikNCnBhY29fcnVuX3BzeWxsYSA8LSByZWFkUkRTKCJvdXRwdXQvY29waHlsb2dlbnkvcGFjb19wc3lsbGFfbWljcm9iZV9maWx0ZXJlZF9hc3ltLnJkcyIpDQoNCnBhY29fcnVuX3Bvd2VsbGlhJGdvZg0KcGFjb19ydW5fY3RlbiRnb2YNCnBhY29fcnVuX3BzeWxsYSRnb2YNCg0Kel90cmFucyA8LSBmdW5jdGlvbih4KXsNCiAgKHggLSBtZWFuKHgsIG5hLnJtPVRSVUUpKSAvIHNkKHgsIG5hLnJtPVRSVUUpDQp9DQoNCiMgR2V0IGxpbmsgaW1wb3J0YW5jZQ0KbGlua3MgPC0gZG8uY2FsbCgibGlzdCIsIG1nZXQoZ3JlcCgicGFjb19ydW5fIixuYW1lcyguR2xvYmFsRW52KSx2YWx1ZT1UUlVFKSkpICU+JQ0KICBwdXJycjo6bWFwKGZ1bmN0aW9uKHgpew0KICAgIGdlbnVzX25hbWUgPC0gcm93bmFtZXMoeCRIKVsxXSAlPiUgc3RyX3JlbW92ZSgiXy4qJCIpDQogICAgZGF0YS5mcmFtZSgNCiAgICAgIGpvaW50PW5hbWVzKHgkamFja2tuaWZlKSwNCiAgICAgIHZhbHVlcz11bm5hbWUoeCRqYWNra25pZmUpLA0KICAgICAgI3VwcGVyPXVubmFtZSh4JGphY2trbmlmZSR1cHBlciksDQogICAgICBnZW51cyA9IGdlbnVzX25hbWUNCiAgICApDQogIH0pICU+JQ0KICBiaW5kX3Jvd3MoKSAlPiUNCiBzZXBhcmF0ZShqb2ludCwgaW50bz1jKCJTYW1wbGVfTmFtZSIsICJPVFUiKSwgc2VwPSItIiwgZXh0cmE9Im1lcmdlIiwgcmVtb3ZlPUZBTFNFKSAlPiUNCiAgZ3JvdXBfYnkoZ2VudXMpICU+JQ0KICBtdXRhdGUodmFsdWVzID0gdmFsdWVzXjIpJT4lDQogIG11dGF0ZShtZWFuX3ZhbCA9IG1lYW4odmFsdWVzKSkgJT4lDQogIG11dGF0ZShzaWduaWYgPSBjYXNlX3doZW4oDQogICAgdmFsdWVzIDwgbWVhbl92YWwgfiAxLA0KICAgIHZhbHVlcyA+IG1lYW5fdmFsIH4gMA0KICApKSU+JQ0KICBncm91cF9ieShnZW51cykgJT4lDQogIG11dGF0ZSgNCiAgICAjc3RfZGV2ID0gc2QodmFsdWVzKSwNCiAgICAjIHpfdmFsdWVzID0gIHZhbHVlcy9zdF9kZXYNCiAgICB6X3ZhbHVlcyA9ICB6X3RyYW5zKHZhbHVlcykNCiAgICApICU+JQ0KICB1bmdyb3VwKCkNCg0KIyBQbG90IGxpbmtzDQpsaW5rcyAlPiUNCiAgZHBseXI6OnJlbmFtZShsYWJlbCA9IGpvaW50KSAlPiUNCiAgbXV0YXRlKGxhYmVsID0gYXMuZmFjdG9yKGxhYmVsKSwNCiAgICAgICAgIGxhYmVsPXJlb3JkZXJfd2l0aGluKGxhYmVsLCB2YWx1ZXMsIGdlbnVzKSklPiUNCiAgI2FycmFuZ2UoLXZhbHVlcykgJT4lDQogIGdncGxvdChhZXMoeD1sYWJlbCwgeT12YWx1ZXMsIGNvbG91cj1hcy5mYWN0b3Ioc2lnbmlmKSkpICsgIw0KICBnZW9tX3BvaW50KHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZmFjZXRfd3JhcCh+Z2VudXMsIHNjYWxlcyA9ICJmcmVlIikrDQogICNnZW9tX2Vycm9yYmFyKGFlcyh5bWluPXZhbHVlcywgeW1heD11cHBlcikpICsNCiAgZ2VvbV9obGluZShhZXMoeWludGVyY2VwdCA9IG1lYW5fdmFsKSwgbHR5PTMpICsNCiAgc2NhbGVfeF9yZW9yZGVyZWQoKSsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJzdGVlbGJsdWUiLCAiZGFya29yYW5nZTEiKSwgbmFtZSA9ICJTaWduaWZpY2FudCIpICsNCiAgdGhlbWVfY2xhc3NpYygpKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpDQoNCiNHZXQgb2JzZXJ2ZWQgcmVzaWR1YWxzIG9mIFByb2NydXN0ZWFuIHN1cGVyaW1wb3NpdGlvbiANCnBhY29fcmVzaWR1YWxzIDwtIGRvLmNhbGwoImxpc3QiLCBtZ2V0KGdyZXAoInBhY29fcnVuXyIsbmFtZXMoLkdsb2JhbEVudiksdmFsdWU9VFJVRSkpKSAlPiUNCiAgcHVycnI6Om1hcChmdW5jdGlvbih4KXsNCiAgICByZXMgPC0gcmVzaWR1YWxzX3BhY28oeCRwcm9jLCB0eXBlID0gImludGVyYWN0aW9uIikNCiAgICBnZW51c19uYW1lIDwtIHJvd25hbWVzKHgkSClbMV0gJT4lIHN0cl9yZW1vdmUoIl8uKiQiKQ0KICAgIGRhdGEuZnJhbWUoDQogICAgICBPVFU9bmFtZXMocmVzKSwNCiAgICAgIHZhbHVlcz11bm5hbWUocmVzKSwNCiAgICAgIGdlbnVzPWdlbnVzX25hbWUNCiAgICApIA0KICB9KSU+JQ0KICBiaW5kX3Jvd3MoKSANCg0KIyBQbG90IHJlc2lkdWFscw0KZ2dwbG90KHBhY29fcmVzaWR1YWxzLCBhZXMoeD12YWx1ZXMpKSsNCiAgZ2VvbV9kZW5zaXR5KGZpbGw9J2dyZXk3MCcpKw0KICB0aGVtZV9idygpKw0KICBmYWNldF9ncmlkKGdlbnVzfi4pICsNCiAgeGxhYignUHJvY3J1c3RlcyByZXNpZHVhbHMnKSsNCiAgeWxhYignRnJlcXVlbmN5JykNCg0KIyBQYXJhZml0IHJ1bg0KUEZfcnVuX3Bvd2VsbGlhIDwtIHJlYWRSRFMoIm91dHB1dC9jb3BoeWxvZ2VueS9wYXJhZml0X3Bvd2VsbGlhX21pY3JvYmVfZmlsdGVyZWQucmRzIikNClBGX3J1bl9jdGVuIDwtIHJlYWRSRFMoIm91dHB1dC9jb3BoeWxvZ2VueS9wYXJhZml0X2N0ZW5hcnl0YWluYV9taWNyb2JlX2ZpbHRlcmVkLnJkcyIpDQpQRl9ydW5fcHN5bGxhIDwtIHJlYWRSRFMoIm91dHB1dC9jb3BoeWxvZ2VueS9wYXJhZml0X3BzeWxsYV9taWNyb2JlX2ZpbHRlcmVkLnJkcyIpDQoNClBGX3J1bl9wb3dlbGxpYSRQYXJhRml0R2xvYmFsDQpQRl9ydW5fcG93ZWxsaWEkcC5nbG9iYWwNCg0KUEZfcnVuX2N0ZW4kUGFyYUZpdEdsb2JhbA0KUEZfcnVuX2N0ZW4kcC5nbG9iYWwNCg0KUEZfcnVuX3BzeWxsYSRQYXJhRml0R2xvYmFsDQpQRl9ydW5fcHN5bGxhJHAuZ2xvYmFsDQoNCiMgSm9pbmluZyBpc250IHdvcmtpbmcgaGVyZSBiZWNhdXNlIHRoZXkgd2VyZSBtYWRlIHNlcGFyYXRlbHkgLSBzbyBnZXR0aW5nIHRoZSByb3duYW1lcyBmcm9tIGNvb2NjdXIgbm8gbG9nbmVyIHdvcmtzDQpQRl9saW5rcyA8LSBkby5jYWxsKCJsaXN0IiwgbWdldChncmVwKCJQRl9ydW5fIixuYW1lcyguR2xvYmFsRW52KSx2YWx1ZT1UUlVFKSkpICU+JQ0KICBwdXJycjo6bWFwKGZ1bmN0aW9uKHgpew0KICAgIGdlbnVzX25hbWUgPC0gbmFtZXMoeCRwYXJhLnBlci5ob3N0WzFdKSAlPiUgc3RyX3JlbW92ZSgiXy4qJCIpDQogICAgYXMuZGF0YS5mcmFtZSh4JGxpbmsudGFibGUpICU+JQ0KICAgICAgbGVmdF9qb2luKGVuZnJhbWUobmFtZXMoeCRwYXJhLnBlci5ob3N0KSwgbmFtZSA9ICJIb3N0IiwgdmFsdWU9InBzeWxsaWRfc3BwIikgJT4lDQogICAgICAgICAgICAgICAgICBtdXRhdGUoSG9zdCA9IGFzLm51bWVyaWMoSG9zdCkpKSAlPiUNCiAgICAgIGxlZnRfam9pbihlbmZyYW1lKG5hbWVzKHgkaG9zdC5wZXIucGFyYSksIG5hbWUgPSAiUGFyYXNpdGUiLCB2YWx1ZT0iT1RVIikgJT4lDQogICAgICAgICAgICAgICAgICBtdXRhdGUoUGFyYXNpdGUgPSBhcy5udW1lcmljKFBhcmFzaXRlKSkpICU+JQ0KICAgICAgbXV0YXRlKGdlbnVzID0gZ2VudXNfbmFtZSkNCiAgfSkgJT4lDQogIGJpbmRfcm93cygpICU+JQ0KICBtdXRhdGUoc2lnbmlmID0gY2FzZV93aGVuKA0KICAgIHAuRjEgPCAwLjA1IH4gMSwNCiAgICBwLkYxID4gMC4wNSB+IDANCiAgKSkgJT4lDQogIGdyb3VwX2J5KGdlbnVzKSAlPiUNCiAgbXV0YXRlKHpfdmFsdWVzID0gIHpfdHJhbnMoRjEuc3RhdCkpICU+JQ0KICB1bmdyb3VwKCkNCg0KIyBQcm9wb3J0aW9uIG9mIHNpZ25pZmljYW50IGxpbmtzDQpQRl9saW5rcyAlPiUNCiAgZ3JvdXBfYnkoZ2VudXMpICU+JQ0KICBzdW1tYXJpc2UocyA9IGNvdW50KHNpZ25pZiA+IDApLCBucyA9IGNvdW50KHNpZ25pZiA9PSAwKSkgDQoNCiMgQ29waHlsb3Bsb3QNCmNvb2N1ci5sdXQgPC0gd2hpY2goY29vY3VyID09MSwgYXJyLmluZD1UUlVFKQ0KYXNzb2MgPC0gY2JpbmQocm93bmFtZXMoY29vY3VyKVtjb29jdXIubHV0WywxXV0sIGNvbG5hbWVzKGNvb2N1cilbY29vY3VyLmx1dFssMl1dKQ0KDQojIFJvdGF0ZSB0aGUgbm9kZXMgdXNpbmcgcGh5dG9vbHMNCmxpYnJhcnkocGh5dG9vbHMpDQpvYmogPC0gY29waHlsbyh0cjE9aF90cmVlLCB0cjI9c190cmVlLCBhc3NvYz1hc3NvYywgcm90YXRlPUZBTFNFKSANCg0KdHJlZTEgPC0gb2JqW1sidHJlZXMiXV1bWzFdXQ0KcDEgPC0gZ2d0cmVlKHRyZWUxLCBsYWRkZXJpemU9RkFMU0UsIGFlcyhjb2xvdXI9bGlua3MpKQ0Kd2VpZ2h0c19wMSA8LSBwMSRkYXRhICU+JQ0KICBsZWZ0X2pvaW4obGlua3MgJT4lDQogICAgZ3JvdXBfYnkoU2FtcGxlX05hbWUpICU+JQ0KICAgIHN1bW1hcmlzZSh2YWx1ZXMgPSBtZWFuKHNpZ25pZikpICU+JQ0KICAgICNzdW1tYXJpc2UodmFsdWVzID0gbWVhbih6X3ZhbHVlcykpICU+JQ0KICAgIGRwbHlyOjpyZW5hbWUobGFiZWwgPSBTYW1wbGVfTmFtZSkNCiAgICApICANCg0KIyMgR2V0IHZhbHVlcyBmb3IgaGlnaGVyIG5vZGVzDQp3ZWlnaHRzX3AxIDwtIHdlaWdodHNfcDEgJT4lDQogIGxlZnRfam9pbihkYXRhLmZyYW1lKG5vZGU9d2VpZ2h0c19wMSRub2RlLCBsaW5rcyA9IHdlaWdodHNfcDEkbm9kZSAlPiUgcHVycnI6Om1hcF9kYmwoYXZlcmFnZV9kZXNjZW5kYW50cywgdHJlZT10cmVlMSwgZGY9d2VpZ2h0c19wMSkpKQ0KDQpwMSA8LSBwMSAlPCslIHdlaWdodHNfcDEgKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3c9InN0ZWVsYmx1ZSIsIGhpZ2g9ImRhcmtvcmFuZ2UxIikgKw0KICAjICBzY2FsZV9jb2xvdXJfZ3JhZGllbnQoaGlnaD0ic3RlZWxibHVlIiwgbG93PSJkYXJrb3JhbmdlMSIsIHRyYW5zPSJsb2cxMCIpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCiMgT1RVIHRyZWUNCnRyZWUyIDwtIG9ialtbInRyZWVzIl1dW1syXV0NCg0KIyBtYWtlIGEgY2xhZGUgbGFiZWwgbGlzdA0KdGF4X2dyb3VwcyA8LSBhcy5kYXRhLmZyYW1lKHRheF90YWJsZShwczMpKSAlPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCJsYWJlbCIpICU+JQ0KICBkcGx5cjo6bXV0YXRlKGxhYmVsID0gbGFiZWwgJT4lIHN0cl9yZXBsYWNlX2FsbCgiLSIsICJfIikgJT4lDQogICAgICAgICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiAiLCAiXyIpKSAlPiUNCiAgZHBseXI6OnNlbGVjdChsYWJlbCwgZ2VudXMpICU+JQ0KICBmaWx0ZXIobGFiZWwgJWluJSB0cmVlMiR0aXAubGFiZWwpICU+JQ0KICBncm91cF9ieShnZW51cykgDQoNCmdyb3VwX25hbWUgPC0gZ3JvdXBfa2V5cyh0YXhfZ3JvdXBzKSAgJT4lDQogIG11dGF0ZShncm91cF9uYW1lID0gZ2VudXMgJT4lIHN0cl9yZW1vdmVfYWxsKCJcXFt8XFxdIikpDQoNCmNscyA8LSB0YXhfZ3JvdXBzICU+JSANCiAgZ3JvdXBfc3BsaXQoKSAlPiUgDQogIHB1cnJyOjptYXAocHVsbCwgbGFiZWwpICU+JQ0KICBzZXRfbmFtZXMoZ3JvdXBfbmFtZSRncm91cF9uYW1lKQ0KDQpuZXd0cmVlIDwtIGdyb3VwT1RVKHRyZWUyLCBjbHMpDQpwMiA8LSBnZ3RyZWUobmV3dHJlZSwgbGFkZGVyaXplPVRSVUUsIGFlcyhjb2xvdXI9bGlua3MpKSANCndlaWdodHNfcDIgPC0gcDIkZGF0YSAlPiUNCiAgbGVmdF9qb2luKGxpbmtzICU+JSANCiAgICBncm91cF9ieShPVFUpICU+JQ0KICAgIHN1bW1hcmlzZSh2YWx1ZXMgPSBtZWFuKHNpZ25pZikpICU+JQ0KICAgICNzdW1tYXJpc2UodmFsdWVzID0gbWVhbih6X3ZhbHVlcykpICU+JQ0KICAgIGRwbHlyOjpyZW5hbWUobGFiZWwgPSBPVFUpDQogICAgKSANCg0KI1Nob3VsZCBiZSBhYmxlIHRvIHVzZSBjYXN0b3IgdG8gbWFrZSB0aGlzIGZhc3Rlcg0Kd2VpZ2h0c19wMiA8LSB3ZWlnaHRzX3AyICU+JQ0KICBsZWZ0X2pvaW4oZGF0YS5mcmFtZShub2RlPXdlaWdodHNfcDIkbm9kZSwgbGlua3MgPSB3ZWlnaHRzX3AyJG5vZGUgJT4lIHB1cnJyOjptYXBfZGJsKGF2ZXJhZ2VfZGVzY2VuZGFudHMsIHRyZWU9dHJlZTIsIGRmPXdlaWdodHNfcDIpKSkNCg0KcDIgPC0gcDIgJTwrJSB3ZWlnaHRzX3AyICArICANCiAgI3NjYWxlX2NvbG91cl9ncmFkaWVudChoaWdoPSJzdGVlbGJsdWUiLCBsb3c9ImRhcmtvcmFuZ2UxIiwgdHJhbnM9ImxvZzEwIikgKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3c9InN0ZWVsYmx1ZSIsIGhpZ2g9ImRhcmtvcmFuZ2UxIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCiMgUGxvdCBoZWF0bWFwcw0KaGVhdG1hcF9kYXQgPC0gb2JqJGFzc29jICU+JQ0KICBhc19kYXRhX2ZyYW1lKCkgJT4lDQogIG1hZ3JpdHRyOjpzZXRfY29sbmFtZXMoYygibGFiZWwueCIsICJsYWJlbC55IikpICU+JQ0KICBtdXRhdGUoZ2VudXMgPSBsYWJlbC54ICU+JSBzdHJfcmVtb3ZlKCJfLiokIikpICU+JQ0KICBsZWZ0X2pvaW4obGlua3MgJT4lIA0KICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGxhYmVsLnggPSBTYW1wbGVfTmFtZSwgbGFiZWwueSA9IE9UVSwgc2lnbmlmX3BhY289c2lnbmlmLCB2YWxfcGFjbyA9IHpfdmFsdWVzKSkgJT4lDQogIGxlZnRfam9pbihQRl9saW5rcyAlPiUgDQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QobGFiZWwueCA9IHBzeWxsaWRfc3BwLCBsYWJlbC55ID0gT1RVLCBzaWduaWZfcGFyYT1zaWduaWYsIHZhbF9wYXJhID0gel92YWx1ZXMpKSAlPiUNCiAgbGVmdF9qb2luKHAxJGRhdGEgJT4lIGRwbHlyOjpzZWxlY3QobGFiZWwsIHkpICU+JSBkcGx5cjo6cmVuYW1lKGxhYmVsLnggPSBsYWJlbCksIGJ5PSJsYWJlbC54IikgJT4lDQogIGxlZnRfam9pbihwMiRkYXRhICU+JSBkcGx5cjo6c2VsZWN0KGxhYmVsLCB5KSAlPiUgZHBseXI6OnJlbmFtZShsYWJlbC55ID0gbGFiZWwpLCBieT0ibGFiZWwueSIpICU+JQ0KICByb3duYW1lc190b19jb2x1bW4oImFzc29jIikgJT4lDQogIGRwbHlyOjpyZW5hbWUoU2FtcGxlX05hbWUgPSBsYWJlbC54LA0KICAgICAgICAgICAgICAgIE9UVSA9IGxhYmVsLnksDQogICAgICAgICAgICAgICAgcG9zX3ggPSB5LngsDQogICAgICAgICAgICAgICAgcG9zX3kgPSB5LnkpICU+JQ0KICBtdXRhdGUoc2lnbmlmX3BhY28gPSByZXBsYWNlX25hKHNpZ25pZl9wYWNvLCAwKSwNCiAgICAgICAgIHNpZ25pZl9wYXJhID0gcmVwbGFjZV9uYShzaWduaWZfcGFyYSwgMCkpICU+JQ0KICBtdXRhdGUoaGlnaGxpZ2h0ID0gY2FzZV93aGVuKA0KICAgIHNpZ25pZl9wYWNvPT0wICYgc2lnbmlmX3BhcmE9PTAgIH4gIk5TIiwNCiAgICBzaWduaWZfcGFjbz09MCAmIHNpZ25pZl9wYXJhPT0xIH4gInBhcmEiLA0KICAgIHNpZ25pZl9wYWNvPT0xICYgc2lnbmlmX3BhcmE9PTAgfiAicGFjbyIsDQogICAgc2lnbmlmX3BhY289PTEgJiBzaWduaWZfcGFyYT09MSB+ICJib3RoIg0KICApKSANCg0KZ2cuaGVhdG1hcCA8LSBoZWF0bWFwX2RhdCAlPiUNCiAgICBtdXRhdGUoT1RVID0gT1RVICU+JSBzdHJfcmVwbGFjZV9hbGwoIl8iLCAiICIpLA0KICAgICAgICAgU2FtcGxlX05hbWUgPSAgU2FtcGxlX05hbWUgJT4lIHN0cl9yZXBsYWNlX2FsbCgiXyIsIiAiKSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoU2FtcGxlX05hbWUsIE9UVSwgZ2VudXMsIGhpZ2hsaWdodCwgcG9zX3gsIHBvc195LCB2YWxfcGFyYSwgdmFsX3BhY28pICU+JQ0KICBkcGx5cjo6bXV0YXRlKFNhbXBsZV9OYW1lID0gZmFjdG9yKFNhbXBsZV9OYW1lKSwNCiAgICAgICAgICAgICAgICBPVFUgPSBmYWN0b3IoT1RVKSwNCiAgICAgICAgICAgICAgICBnZW51cz1mYWN0b3IoZ2VudXMsIGxldmVscz1jKCJQc3lsbGEiICwiUG93ZWxsaWEiLCAiQ3RlbmFyeXRhaW5hIikpKSAgJT4lDQogICBnZ3Bsb3QoYWVzKHg9IGZjdF9yZW9yZGVyKFNhbXBsZV9OYW1lLCBwb3NfeCksIHk9ZmN0X3Jlb3JkZXIoT1RVLCBwb3NfeSksIGZpbGw9aGlnaGxpZ2h0ICkpICsNCiAgZ2VvbV90aWxlKCkgKw0KICB0aGVtZV9idygpICsNCiAgZmFjZXRfZ3JpZCh+Z2VudXMsIGRyb3AgPSBUUlVFLCBzY2FsZXM9ImZyZWUiLCBzcGFjZSA9ImZyZWUiKSsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIHNjYWxlX3lfZGlzY3JldGUoZXhwYW5kPWMoMCwwKSkrDQogIHNjYWxlX3lfZGlzY3JldGUoZXhwYW5kPWMoMCwwKSkgKw0KICAjc2NhbGVfZmlsbF9ncmFkaWVudChoaWdoPSJzdGVlbGJsdWUiLCBsb3c9ImRhcmtvcmFuZ2UxIikgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJOUyI9InN0ZWVsYmx1ZSIsInBhY28iPSJkYXJrb3JhbmdlMSIsInBhcmEiPSIjZGEyYjkxIiwgImJvdGgiPSIjOTFkYTJiIiksIG5hLnRyYW5zbGF0ZT1GQUxTRSkgDQoNCmdnLmhlYXRtYXANCg0KIyBEZW5zaXR5IHBsb3Qgb2YgbWlzbWF0Y2gNCmRlbnNpdHlfZGF0IDwtICBoZWF0bWFwX2RhdCAlPiUNCiAgbGVmdF9qb2luKHAyJGRhdGEgJT4lIGRwbHlyOjpzZWxlY3QoT1RVID0gbGFiZWwsIHkpKSAlPiUNCiAgZ3JvdXBfYnkoT1RVLCB5KSAlPiUNCiAgc3VtbWFyaXNlKHN1bV9wYWNvID0gc3VtKHNpZ25pZl9wYWNvLCBuYS5ybT1UUlVFKSwgc3VtX3BhcmEgPSBzdW0oc2lnbmlmX3BhcmEsIG5hLnJtPVRSVUUpKSAlPiUNCiAgbXV0YXRlKHRvdGFsX3N1bSA9IHN1bShzdW1fcGFjbywgc3VtX3BhcmEsIG5hLnJtPVRSVUUpKSAlPiUNCiAgI3N1bW1hcmlzZSh0b3RhbF9zdW0gPSBtZWFuKHZhbF9wYWNvLCBuYS5ybT1UUlVFKSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQpkZW5zaXR5X2xhYmVscyA8LSBkZW5zaXR5X2RhdCAlPiUNCiAgbGVmdF9qb2luKHRheF90YWJsZShwczMpICU+JQ0KICAgICAgICAgICAgICBhcygibWF0cml4IikgJT4lDQogICAgICAgICAgICAgIGFzX3RpYmJsZShyb3duYW1lcz0iT1RVIikgJT4lIA0KICAgICAgICAgICAgICBtdXRhdGUoT1RVID0gT1RVICU+JSBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIikpKSU+JQ0KICBhcnJhbmdlKHkpIA0KICANCmNodW5rID0gNTANCm4gPC0gbnJvdyhkZW5zaXR5X2xhYmVscykNCnIgIDwtIHJlcCgxOmNlaWxpbmcobi9jaHVuayksZWFjaD1jaHVuaylbMTpuXQ0KZGVuc2l0eV9sYWJlbHNfY2h1bmtlZCA8LSBzcGxpdChkZW5zaXR5X2xhYmVscyxyKQ0KDQpkZW5zaXR5X2xhYmVscyA8LSBkZW5zaXR5X2xhYmVsc19jaHVua2VkICU+JSANCiAgcHVycnI6Om1hcChmdW5jdGlvbih4KXsNCiAgICBkZiA8LSB4ICU+JQ0KICAgICAgbXV0YXRlKHpzY29yZSA9ICh0b3RhbF9zdW0gLSBtZWFuKHRvdGFsX3N1bSwgbmEucm09VFJVRSkpL3NkKHRvdGFsX3N1bSwgbmEucm09VFJVRSkpICU+JQ0KICAgICAgZmlsdGVyKCB0b3RhbF9zdW0gPiAzLCB6c2NvcmUgID4gMywpICMsIA0KICAgIGlmKG5yb3coZGYpID4gMCl7DQogICAgICAgIG91dCA8LSBkZiAlPiUNCiAgICAgICAgc3VtbWFyaXNlKHkgPSBtZWFuKHkpLCB0b3RhbF9zdW0gID0gbWF4KHRvdGFsX3N1bSksIGFubm90ID0gIGNhc2Vfd2hlbigNCiAgICAgICAgbGVuZ3RoKHVuaXF1ZShnZW51cykpID09IDEgfiBuYW1lcyh3aGljaC5tYXgodGFibGUoZ2VudXMpKSksDQogICAgICAgIGxlbmd0aCh1bmlxdWUoZ2VudXMpKSA+IDF+ICBuYW1lcyh3aGljaC5tYXgodGFibGUoZmFtaWx5KSkpKSkNCiAgICAgICAgcmV0dXJuKG91dCkNCiAgICB9DQogIH0pICU+JQ0KICBiaW5kX3Jvd3MoKSANCg0KZ2cuZGVuc2l0eSA8LSBkZW5zaXR5X2RhdCAlPiUNCiAgZmlsdGVyKHRvdGFsX3N1bSA+IDApICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSB5LCB5PXRvdGFsX3N1bSwgY29sb3VyPXRvdGFsX3N1bSkpICsNCiAgZ2VvbV9wb2ludChzaXplPTAuMDEsIGFscGhhPTEpKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3c9InN0ZWVsYmx1ZSIsIGhpZ2g9ImRhcmtvcmFuZ2UxIikgKw0KICAgIGdlb21fdGV4dChkYXRhPWRlbnNpdHlfbGFiZWxzLCBhZXMobGFiZWwgPSBhbm5vdCksIGhqdXN0PTApICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZD1jKDAsMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZD1jKDAsMCkpICsNCiAgdGhlbWVfdm9pZCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSsNCiAgY29vcmRfZmxpcCgpDQoNCmdnLmRlbnNpdHkNCg0KIyBJbnN0ZWFkIG9mIGRlbnNpdHkgY291bGQgaSBsYWJlbCBsaW5lYWdlcyB3aXRoIHRoZSBncmVhdGVzdCB2YWx1ZT8NCg0KdG9wIDwtIHdyYXBfZWxlbWVudHMoZ3JpZDo6dGV4dEdyb2IoJycpKSArKHAxKyBjb29yZF9mbGlwKCkgKyBzY2FsZV94X3JldmVyc2UoZXhwYW5kPWMoMCwwKSkrIHNjYWxlX3lfY29udGludW91cyhleHBhbmQ9YygwLDApKSkgKyB3cmFwX2VsZW1lbnRzKGdyaWQ6OnRleHRHcm9iKCcnKSkgK3Bsb3RfbGF5b3V0KHdpZHRocz1jKDAuNSwzLCAwLjEpKSANCg0KYm90dG9tIDwtIHAyKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kPWMoMCwwKSkgKyBnZy5oZWF0bWFwICsgZ2cuZGVuc2l0eSArIHBsb3RfbGF5b3V0KHdpZHRocz1jKDAuNSwzLCAwLjEpKQ0KDQpnZy50cmVlbWFwIDwtIHRvcCAvIGJvdHRvbSArIHBsb3RfbGF5b3V0KGhlaWdodHM9YygwLjUsMykpICANCg0KZ2cudHJlZW1hcA0KDQpwZGYoZmlsZT0iZmlncy8zZ2VudXNfaGVhdG1hcHMucGRmIiwgIHdpZHRoID0gOCwgaGVpZ2h0ID0gMTEsIHBhcGVyPSJhNCIpDQogIHBsb3QoZ2cudHJlZW1hcCkNCnRyeShkZXYub2ZmKCksIHNpbGVudD1UUlVFKQ0KICANCiMgTWFrZSBwbG90IHJhbmtpbmcgbGlua3MsIGFuZCBub2Rlcw0KDQpnZy5ob3N0X2xpbmtzIDwtIGhlYXRtYXBfZGF0ICU+JQ0KICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gU2FtcGxlX05hbWUpICU+JQ0KICBkcm9wX25hKCkgJT4lDQogIGdyb3VwX2J5KGxhYmVsKSAlPiUNCiAgc3VtbWFyaXNlKHNpZ25pZl9wYWNvID0gc3VtKHNpZ25pZl9wYWNvKSwgc2lnbmlmX3BhcmE9IHN1bShzaWduaWZfcGFyYSkpICU+JQ0KICBwaXZvdF9sb25nZXIoc3RhcnRzX3dpdGgoInNpZ25pZl8iKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvPSJ0eXBlIiwNCiAgICAgICAgICAgICAgIHZhbHVlc190bz0idmFsdWVzIikgJT4lDQogIG11dGF0ZSh0eXBlID0gdHlwZSAlPiUgc3RyX3JlbW92ZSgic2lnbmlmXyIpKSAlPiUNCiAgICBtdXRhdGUobGFiZWwgPSBsYWJlbCAlPiUgDQogICAgICAgICAgICAgc3RyX3JlcGxhY2VfYWxsKCJfIiwgIiAiKSAlPiUNCiAgICAgICAgICAgICBzdHJfcmVwbGFjZSgiIHNwIiwgIiBzcC4iKSklPiUNCiAgbXV0YXRlKGxhYmVsID0gYXMuZmFjdG9yKGxhYmVsKSklPiUNCiAgZmlsdGVyKHZhbHVlcyA+IDApICU+JQ0KICBhcnJhbmdlKC12YWx1ZXMpICU+JQ0KICBnZ3Bsb3QoYWVzKHg9ZmN0X3Jlb3JkZXIobGFiZWwsIHZhbHVlcywgc3VtICksIHk9dmFsdWVzLCBmaWxsPXR5cGUpKSArDQogIGdlb21fY29sKCkgKyAgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJOUyI9InN0ZWVsYmx1ZSIsInBhY28iPSJkYXJrb3JhbmdlMSIsInBhcmEiPSIjZGEyYjkxIiwgImJvdGgiPSIjOTFkYTJiIiksIG5hLnRyYW5zbGF0ZT1GQUxTRSkrDQogIHRoZW1lX2NsYXNzaWMoKSsgDQogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGZhY2U9Iml0YWxpYyIpLA0KICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikrDQogIGxhYnMoeSA9ICJOdW1iZXIgb2YgU2lnbmZpY2FudCBsaW5rcyIsIHg9TlVMTCwgdGl0bGUgPSJQc3lsbGlkIHRheGEiKSArIA0KICBjb29yZF9mbGlwKCkNCg0KZ2cuc3ltX2xpbmtzIDwtIGhlYXRtYXBfZGF0ICU+JQ0KICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gT1RVKSAlPiUNCiAgbGVmdF9qb2luKHRheF9ncm91cHMgJT4lIGRwbHlyOjpyZW5hbWUob3R1X2dlbnVzID0gZ2VudXMpKSAlPiUNCiAgZHJvcF9uYSgpICU+JQ0KICBncm91cF9ieShvdHVfZ2VudXMpICU+JQ0KICBzdW1tYXJpc2Uoc2lnbmlmX3BhY28gPSBzdW0oc2lnbmlmX3BhY28pLCBzaWduaWZfcGFyYT0gc3VtKHNpZ25pZl9wYXJhKSkgJT4lDQogIHBpdm90X2xvbmdlcihzdGFydHNfd2l0aCgic2lnbmlmXyIpLA0KICAgICAgICAgICAgICAgbmFtZXNfdG89InR5cGUiLA0KICAgICAgICAgICAgICAgdmFsdWVzX3RvPSJ2YWx1ZXMiKSAlPiUNCiAgZHBseXI6OnJlbmFtZShsYWJlbCA9IG90dV9nZW51cykgJT4lDQogIGRwbHlyOjptdXRhdGUobGFiZWwgPSBsYWJlbCAlPiUgDQogICAgICAgICAgICAgICAgICBzdHJfcmVtb3ZlKCJcXC8uKiQiKSAlPiUNCiAgICAgICAgICAgICAgICAgIHN0cl9yZXBsYWNlKCJOQV9jYW5hcmllbnNlIiwgIkJyYWR5cmhpem9iaXVtX2NhbmFyaWVuc2UiKSAlPiUNCiAgICAgICAgICAgICAgICAgIHN0cl9yZW1vdmUoIl5zX18iKQ0KICAgICAgICAgICAgICAgICAgKSAlPiUNCiAgbXV0YXRlKHR5cGUgPSB0eXBlICU+JSBzdHJfcmVtb3ZlKCJzaWduaWZfIikpICU+JQ0KICBtdXRhdGUobGFiZWwgPSBhcy5mYWN0b3IobGFiZWwpLA0KICAgICAgICAgbGFiZWw9ZmN0X3Jlb3JkZXIobGFiZWwsIHZhbHVlcywgc3VtICkpJT4lDQogIGdyb3VwX2J5KGxhYmVsKSAlPiUNCiAgbXV0YXRlKHRvdGFsID0gc3VtKHZhbHVlcykpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogICBmaWx0ZXIodG90YWwgPiAwKSAlPiUNCiAgdG9wX24oODAsIHRvdGFsKSAlPiUNCiAgYXJyYW5nZSgtdmFsdWVzKSAlPiUNCiAgZ2dwbG90KGFlcyh4PWxhYmVsLCB5PXZhbHVlcywgZmlsbD10eXBlKSkgKw0KICBnZW9tX2NvbCgpICsgIA0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiTlMiPSJzdGVlbGJsdWUiLCJwYWNvIj0iZGFya29yYW5nZTEiLCJwYXJhIj0iI2RhMmI5MSIsICJib3RoIj0iIzkxZGEyYiIpLCBuYS50cmFuc2xhdGU9RkFMU0UpKw0KICB0aGVtZV9jbGFzc2ljKCkrIA0KICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChmYWNlPSJpdGFsaWMiKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpKw0KICBsYWJzKHkgPSAiTnVtYmVyIG9mIFNpZ25maWNhbnQgbGlua3MiLCB4PU5VTEwsIHRpdGxlID0iTWljcm9iaWFsIGdlbmVyYSIpICsgDQogIGNvb3JkX2ZsaXAoKQ0KDQpnZy5ob3N0X2xpbmtzICsgZ2cuc3ltX2xpbmtzDQoNCnBkZihmaWxlPSJmaWdzLzNnZW51c19maXRfc3VtbWFyeS5wZGYiLCAgd2lkdGggPSA4LCBoZWlnaHQgPSAxMSwgcGFwZXI9ImE0IikNCiAgcGxvdChnZy5ob3N0X2xpbmtzICsgZ2cuc3ltX2xpbmtzKQ0KdHJ5KGRldi5vZmYoKSwgc2lsZW50PVRSVUUpDQoNCmBgYA0KDQojIFRhbmdsZWdyYW1zDQoNCiMjIFBzeWxsaWQgfiBDYXJzb25lbGxhIA0KDQpgYGB7ciBjYXJzb25lbGxhIGNvcGh5bG99DQojRmxhZyB0b3AgYWJ1bmRhbmNlIGNhcnNvbmVsbGEgYnkgc2FtcGxlDQp0b3BfY2Fyc29uIDwtIHBzMiAlPiUNCiAgdHJhbnNmb3JtX3NhbXBsZV9jb3VudHMoZnVuY3Rpb24gKHgpIHgvc3VtKHgpKSAlPiUNCiAgc3BlZWR5c2VxOjpwc21lbHQoKSAlPiUNCiAgZmlsdGVyKHBzeWxsaWRfc3BwICVpbiUgcHJ1bmVkLnRyZWUkdGlwLmxhYmVsKSAlPiUNCiAgZmlsdGVyKGdlbnVzPT0iQ2FuZGlkYXR1cyBDYXJzb25lbGxhIikgJT4lDQogIGdyb3VwX2J5KFNhbXBsZSkgJT4lICANCiAgZmlsdGVyKEFidW5kYW5jZSA+IDApICU+JQ0KICB0b3BfbigxLCB3dD1BYnVuZGFuY2UpICU+JQ0KICBtdXRhdGUodG9wID0gVFJVRSkgDQoNCmNvb2N1ciA8LSBwczIgJT4lDQogIHRyYW5zZm9ybV9zYW1wbGVfY291bnRzKGZ1bmN0aW9uICh4KSB4L3N1bSh4KSkgJT4lDQogIHNwZWVkeXNlcTo6cHNtZWx0KCkgJT4lDQogICAgZmlsdGVyKHBzeWxsaWRfc3BwICVpbiUgcHJ1bmVkLnRyZWUkdGlwLmxhYmVsKSAlPiUNCiAgbGVmdF9qb2luKHRvcF9jYXJzb24pICU+JQ0KICBmaWx0ZXIoZ2VudXM9PSJDYW5kaWRhdHVzIENhcnNvbmVsbGEiLCB0b3A9PVRSVUUpICU+JQ0KICBkcGx5cjo6c2VsZWN0KE9UVSwgcHN5bGxpZF9zcHAsIFNhbXBsZUlELCBBYnVuZGFuY2UpICU+JQ0KICBtdXRhdGUoT1RVID0gT1RVICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIikpICU+JQ0KICBkcGx5cjo6Z3JvdXBfYnkoT1RVLCBwc3lsbGlkX3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKEFidW5kYW5jZSA9IHN1bShBYnVuZGFuY2UpKSAlPiUNCiAgcGl2b3Rfd2lkZXIoaWRfY29scyA9IHBzeWxsaWRfc3BwLA0KICAgICAgICAgICAgICBuYW1lc19mcm9tID0gT1RVLA0KICAgICAgICAgICAgICB2YWx1ZXNfZnJvbT1BYnVuZGFuY2UsDQogICAgICAgICAgICAgIHZhbHVlc19maWxsID0gbGlzdChBYnVuZGFuY2UgPSAwKSkgICU+JQ0KICBjb2x1bW5fdG9fcm93bmFtZXMoInBzeWxsaWRfc3BwIikgJT4lDQogICAgYXMubWF0cml4KCkgJT4lIA0KICBhcHBseSgyLCBmdW5jdGlvbih4KSBpZmVsc2UoeCA+IDAsIDEsIDApKSANCg0KIyBIIGNvcGhlbmV0aWMgZGlzdGFuY2UNCmhfdHJlZSA8LSBwcnVuZWQudHJlZQ0KaF90cmVlJHRpcC5sYWJlbCA8LSBoX3RyZWUkdGlwLmxhYmVsICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIikNCmhfdHJlZSA8LSBkcm9wLnRpcChoX3RyZWUsIHNldGRpZmYoaF90cmVlJHRpcC5sYWJlbCwgcm93bmFtZXMoY29vY3VyKSkpDQpoX2Rpc3QgPC0gc3FydChjb3BoZW5ldGljKGhfdHJlZSkpDQoNCiMgUCBjb3BoZW5ldGljIGRpc3RhbmNlDQpzX3RyZWUgPC0gcGh5X3RyZWUocHMzKQ0Kc190cmVlJHRpcC5sYWJlbCA8LSBzX3RyZWUkdGlwLmxhYmVsICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIikNCnNfdHJlZSA8LSBkcm9wLnRpcChzX3RyZWUsIHNldGRpZmYoc190cmVlJHRpcC5sYWJlbCwgY29sbmFtZXMoY29vY3VyKSkpDQpzX2Rpc3QgPC0gc3FydChjb3BoZW5ldGljKHNfdHJlZSkvMWUrNiApICMjY29udmVydCB0byBNeWEgc28gaW50ZWdlcnMgYXJlIHNtYWxsIGVub3VnaCBmb3IgUEFDTw0KDQpjb29jdXIgPC0gY29vY3VyW2hfdHJlZSR0aXAubGFiZWwsIHNfdHJlZSR0aXAubGFiZWxdDQoNCiMgcHJlcGFyZSBwYWNvIGRhdGENCkQgPC0gcHJlcGFyZV9wYWNvX2RhdGEoSD1oX2Rpc3QsIFA9c19kaXN0LCBIUD1jb29jdXIpDQoNCiMgQWRkIHBjb3JkDQpEIDwtIGFkZF9wY29vcmQoRCwgY29ycmVjdGlvbj0nbm9uZScpIA0KcF9ob3N0IDwtIGdncGxvdChEJEhfUENvWyxjKCdBeGlzLjEnLCAnQXhpcy4yJyldICU+JSBhcy5kYXRhLmZyYW1lLCBhZXMoQXhpcy4xLCBBeGlzLjIpKSArDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICB0aGVtZV9idygpICsNCiAgZ2d0aXRsZSgiSCBQQ0EiKQ0KcF9wYXJhIDwtIGdncGxvdChEJFBfUENvWyxjKCdBeGlzLjEnLCAnQXhpcy4yJyldICU+JSBhcy5kYXRhLmZyYW1lLCBhZXMoQXhpcy4xLCBBeGlzLjIpKSAgKw0KICAgIGdlb21fcG9pbnQoKSArDQogICAgdGhlbWVfYncoKSsNCiAgZ2d0aXRsZSgiUCBQQ0EiKQ0KICAgIA0KcGxvdChwX2hvc3QgKyBwX3BhcmEpDQoNCiMgcnVuIHBhY28NCnBhY29fcnVuIDwtIFBBQ28oRCwgbnBlcm09OTk5LCBzZWVkPTkwOSwgbWV0aG9kPSdxdWFzaXN3YXAnLCBzeW1tZXRyaWM9RkFMU0UpDQoNCiNwcmludCBvdmVyYWxsIHNpZ25pZmljYW5jZQ0KcHJpbnQocGFjb19ydW4kZ29mKQ0KDQojIEdldCBpbnRlcmFjdGlvbi1zcGVjaWZpYyBjb3BoeWxvZ2VuZXRpYyBjb250cmlidXRpb25zIHVzaW5nIGphY2tuaWZpbmcNCnBhY29fcnVuIDwtIHBhY29fbGlua3MocGFjb19ydW4pDQpsaW5rcyA8LSBkYXRhLmZyYW1lKGpvaW50PW5hbWVzKHBhY29fcnVuJGphY2trbmlmZSksICNsb3Npbmcgc3Y1MCBoZXJlPw0KICAgICAgICAgICAgICAgICAgICB2YWx1ZXM9dW5uYW1lKHBhY29fcnVuJGphY2trbmlmZSkjLA0KICAgICAgICAgICAgICAgICAgICAjdXBwZXI9dW5uYW1lKHBhY29fcnVuJGphY2trbmlmZSkNCiAgICAgICAgICAgICAgICAgICAgKSAlPiUNCiAgbXV0YXRlKE9UVSA9IGpvaW50KSAlPiUNCiBzZXBhcmF0ZShPVFUsIGludG89YygiU2FtcGxlX05hbWUiLCAiT1RVIiksIHNlcD0iLSIsIGV4dHJhPSJtZXJnZSIpICU+JQ0KICBtdXRhdGUoc2lnbmlmID0gY2FzZV93aGVuKA0KICAgIHZhbHVlcyA8IG1lYW4oLiR2YWx1ZXMpIH4gMSwNCiAgICB2YWx1ZXMgPiBtZWFuKC4kdmFsdWVzKSB+IDANCiAgKSkNCg0KIyBQbG90IGxpbmtzDQpsaW5rcyAlPiUNCiAgZHBseXI6OnJlbmFtZShsYWJlbCA9IGpvaW50KSAlPiUNCiAgbXV0YXRlKGxhYmVsID0gYXMuZmFjdG9yKGxhYmVsKSwNCiAgICAgICAgIGxhYmVsPWZjdF9yZW9yZGVyKGxhYmVsLCB2YWx1ZXMsIHN1bSkpJT4lDQogIGFycmFuZ2UoLXZhbHVlcykgJT4lDQogIGdncGxvdChhZXMoeD1sYWJlbCwgeT12YWx1ZXMsIGNvbG91cj1hcy5mYWN0b3Ioc2lnbmlmKSkpICsNCiAgZ2VvbV9wb2ludChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogICNnZW9tX2Vycm9yYmFyKGFlcyh5bWluPXZhbHVlcywgeW1heD11cHBlcikpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbWVhbihsaW5rcyR2YWx1ZXMpKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9Yygic3RlZWxibHVlIiwgImRhcmtvcmFuZ2UxIikpICsNCiAgdGhlbWVfY2xhc3NpYygpKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpDQoNCmRpci5jcmVhdGUoIm91dHB1dC9jb3BoeWxvZ2VueS9wc3lsbGlkX2NhcnNvbmVsbGEiKQ0Kd3JpdGVfY3N2KGxpbmtzLCAib3V0cHV0L2NvcGh5bG9nZW55L3BzeWxsaWRfY2Fyc29uZWxsYS9wc3lsbGlkX2NhcnNvbmVsbGFfbGlua3MuY3N2IikNCndyaXRlX2NzdihsaW5rcyAlPiUgDQogICAgZ3JvdXBfYnkoT1RVKSAlPiUNCiAgICBzdW1tYXJpc2UodmFsdWVzID0gbWVhbih2YWx1ZXMpKSwgIm91dHB1dC9jb3BoeWxvZ2VueS9wc3lsbGlkX2NhcnNvbmVsbGEvY2Fyc29uZWxsYV93ZWlnaHRzLmNzdiIpDQp3cml0ZV9jc3YobGlua3MgJT4lIA0KICAgIGdyb3VwX2J5KFNhbXBsZV9OYW1lKSAlPiUNCiAgICBzdW1tYXJpc2UodmFsdWVzID0gbWVhbih2YWx1ZXMpKSwgIm91dHB1dC9jb3BoeWxvZ2VueS9wc3lsbGlkX2NhcnNvbmVsbGEvcHN5bGxpZF93ZWlnaHRzLmNzdiIpDQoNCiNHZXQgb2JzZXJ2ZWQgcmVzaWR1YWxzIG9mIFByb2NydXN0ZWFuIHN1cGVyaW1wb3NpdGlvbiANCnBhY29fcmVzaWR1YWxzIDwtIHJlc2lkdWFsc19wYWNvKHBhY29fcnVuJHByb2MsIHR5cGUgPSAiaW50ZXJhY3Rpb24iKQ0KDQojIFZpc3VhbGlzZSByZXNpZHVhbHMNCnJlcyA8LSBkYXRhLmZyYW1lKE9UVT1uYW1lcyhwYWNvX3Jlc2lkdWFscyksIHZhbHVlcz11bm5hbWUocGFjb19yZXNpZHVhbHMpKSAlPiUNCiAgc2VwYXJhdGUoT1RVLCBpbnRvPWMoIlNhbXBsZV9OYW1lIiwgIk9UVSIpLCBzZXA9Ii0iLCBleHRyYT0ibWVyZ2UiKSANCmdncGxvdChyZXMsIGFlcyh4PXZhbHVlcykpKw0KICBnZW9tX2RlbnNpdHkoZmlsbD0nZ3JleTcwJykrDQogIHRoZW1lX2J3KCkrDQogIHhsYWIoJ1Byb2NydXN0ZXMgcmVzaWR1YWxzJykrDQogIHlsYWIoJ0ZyZXF1ZW5jeScpDQoNCiMgUGFyYWZpdCBydW4NClBGX3J1biA8LSBwYXJhZml0KGhfZGlzdCwgc19kaXN0LGNvb2N1ciwgbnBlcm09OTk5LCB0ZXN0LmxpbmtzPVRSVUUpDQpQRl9ydW4kUGFyYUZpdEdsb2JhbA0KUEZfcnVuJHAuZ2xvYmFsDQoNClBGX2xpbmtzIDwtIGFzLmRhdGEuZnJhbWUoUEZfcnVuJGxpbmsudGFibGUpICAlPiUNCiAgICAgIGxlZnRfam9pbihlbmZyYW1lKG5hbWVzKFBGX3J1biRwYXJhLnBlci5ob3N0KSwgbmFtZSA9ICJIb3N0IiwgdmFsdWU9InBzeWxsaWRfc3BwIikgJT4lDQogICAgICAgICAgICAgICAgICBtdXRhdGUoSG9zdCA9IGFzLm51bWVyaWMoSG9zdCkpKSAlPiUNCiAgICAgIGxlZnRfam9pbihlbmZyYW1lKG5hbWVzKFBGX3J1biRob3N0LnBlci5wYXJhKSwgbmFtZSA9ICJQYXJhc2l0ZSIsIHZhbHVlPSJPVFUiKSAlPiUNCiAgICAgICAgICAgICAgICAgIG11dGF0ZShQYXJhc2l0ZSA9IGFzLm51bWVyaWMoUGFyYXNpdGUpKSklPiUNCiAgbXV0YXRlKHNpZ25pZiA9IGNhc2Vfd2hlbigNCiAgICBwLkYxIDwgMC4wNSB+IDEsDQogICAgcC5GMSA+IDAuMDUgfiAwDQogICkpIA0KDQojIENvcGh5bG9wbG90DQpjb29jdXIubHV0IDwtIHdoaWNoKGNvb2N1ciA9PTEsIGFyci5pbmQ9VFJVRSkNCmFzc29jIDwtIGNiaW5kKHJvd25hbWVzKGNvb2N1cilbY29vY3VyLmx1dFssMV1dLCBjb2xuYW1lcyhjb29jdXIpW2Nvb2N1ci5sdXRbLDJdXSkNCg0KIyBSb3RhdGUgdGhlIG5vZGVzIHVzaW5nIHBoeXRvb2xzDQpvYmogPC0gY29waHlsbyh0cjE9aF90cmVlLCB0cjI9c190cmVlLCBhc3NvYz1hc3NvYywgcm90YXRlPVRSVUUpIA0KDQojIHBzeWxsaWRfdHJlZQ0KdHJlZTEgPC0gb2JqW1sidHJlZXMiXV1bWzFdXQ0KDQpwMSA8LSBnZ3RyZWUodHJlZTEpDQphdG1ldG9fbm9kZSA8LSBwMSRkYXRhICU+JSANCiAgZmlsdGVyKHN0cl9kZXRlY3QobGFiZWwsICJBdG1ldG8iKSkgJT4lDQogIHB1bGwobm9kZSkNCnJvb3Rfbm9kZSA8LSBwMSRkYXRhICU+JSANCiAgZmlsdGVyKHN0cl9kZXRlY3QobGFiZWwsICJBdG1ldG8iKSkgJT4lDQogIHB1bGwocGFyZW50KQ0KDQp0cmVlMSA8LSBncm91cE9UVSh0cmVlMSwgLm5vZGU9YXRtZXRvX25vZGUpICMgTWFrZSB0aGUgYXRtZXRvIGRvdHRlZCB0byBpbmRpY2F0ZSBvdXRncm91cCB3YXMgcmVzY2FsZWQNCg0KcDEgPC0gZ2d0cmVlKHRyZWUxLCBsYWRkZXJpemU9RkFMU0UsIGFlcyhjb2xvdXI9bGlua3MsbGluZXR5cGU9Z3JvdXApKQ0Kd2VpZ2h0c19wMSA8LSBwMSRkYXRhICU+JQ0KICBsZWZ0X2pvaW4obGlua3MgJT4lIA0KICAgIGdyb3VwX2J5KFNhbXBsZV9OYW1lKSAlPiUNCiAgICBzdW1tYXJpc2UoUEFfdmFsdWVzID0gbWVhbihzaWduaWYpKSAlPiUNCiAgICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gU2FtcGxlX05hbWUpDQogICAgKSU+JQ0KICBsZWZ0X2pvaW4oUEZfbGlua3MgJT4lIA0KICAgIGdyb3VwX2J5KHBzeWxsaWRfc3BwKSAlPiUNCiAgICBzdW1tYXJpc2UoUEZfdmFsdWVzID0gbWVhbihzaWduaWYpKSAlPiUNCiAgICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gcHN5bGxpZF9zcHApDQogICAgKSAlPiUNCiAgbXV0YXRlKHZhbHVlcyA9IFBBX3ZhbHVlcyArIFBGX3ZhbHVlcykNCg0KI1NjYWxlIHRoZSBhdG1ldG9jcmFuaXVtIGFuZCByb290IG5vZGVzIHRvIGJlIHNob3J0ZXINCnAxJGRhdGFbcDEkZGF0YSRub2RlICVpbiUgYXRtZXRvX25vZGUsICJ4Il0gPC0gbWF4KHAxJGRhdGEkeCkNCnAxJGRhdGFbcDEkZGF0YSRub2RlICVpbiUgcm9vdF9ub2RlLCAieCJdIDwtIDAuMiAjcm9vdA0KDQpwMSRkYXRhJG5vZGVbcDEkZGF0YSRub2RlXQ0KDQojIyBHZXQgdmFsdWVzIGZvciBoaWdoZXIgbm9kZXMNCndlaWdodHNfcDEgPC0gd2VpZ2h0c19wMSAlPiUNCiAgbGVmdF9qb2luKGRhdGEuZnJhbWUobm9kZT13ZWlnaHRzX3AxJG5vZGUsIGxpbmtzID0gd2VpZ2h0c19wMSRub2RlICU+JSBwdXJycjo6bWFwX2RibChhdmVyYWdlX2Rlc2NlbmRhbnRzLCB0cmVlPXRyZWUxLCBkZj13ZWlnaHRzX3AxKSkpDQpwMSA8LSBwMSAlPCslIHdlaWdodHNfcDEgKyBnZW9tX3RpcHBvaW50KGFlcyhjb2xvdXI9bGlua3MpKSArDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0ic3RlZWxibHVlIiwgaGlnaD0iZGFya29yYW5nZTEiKSAgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCiMgT1RVIHRyZWUNCnRyZWUyIDwtIG9ialtbInRyZWVzIl1dW1syXV0NCnNfdHJlZSA8LSBkcm9wLnRpcCh0cmVlMiwgc2V0ZGlmZih0cmVlMiR0aXAubGFiZWwsIG9iaiRhc3NvY1ssMl0pKQ0KcDIgPC0gZ2d0cmVlKHRyZWUyICwgbGFkZGVyaXplPUZBTFNFLCBhZXMoY29sb3VyPWxpbmtzKSkgDQp3ZWlnaHRzX3AyIDwtIHAyJGRhdGEgJT4lDQogIGxlZnRfam9pbihsaW5rcyAlPiUgDQogICAgZ3JvdXBfYnkoT1RVKSAlPiUNCiAgICBzdW1tYXJpc2UoUEFfdmFsdWVzID0gbWVhbihzaWduaWYpKSAlPiUNCiAgICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gT1RVKQ0KICAgICklPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICBncm91cF9ieShPVFUpICU+JQ0KICAgIHN1bW1hcmlzZShQRl92YWx1ZXMgPSBtZWFuKHNpZ25pZikpICU+JQ0KICAgIGRwbHlyOjpyZW5hbWUobGFiZWwgPSBPVFUpDQogICAgKSAlPiUNCiAgbXV0YXRlKHZhbHVlcyA9IFBBX3ZhbHVlcyArIFBGX3ZhbHVlcykNCg0Kd2VpZ2h0c19wMiA8LSB3ZWlnaHRzX3AyICU+JQ0KICBsZWZ0X2pvaW4oZGF0YS5mcmFtZShub2RlPXdlaWdodHNfcDIkbm9kZSwgbGlua3MgPSB3ZWlnaHRzX3AyJG5vZGUgJT4lIHB1cnJyOjptYXBfZGJsKGF2ZXJhZ2VfZGVzY2VuZGFudHMsIHRyZWU9dHJlZTIsIGRmPXdlaWdodHNfcDIpKSkNCg0KcDIgPC0gcDIgJTwrJSB3ZWlnaHRzX3AyICArIGdlb21fdGlwcG9pbnQoYWVzKGNvbG91cj1saW5rcykpICsgDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0ic3RlZWxibHVlIiwgaGlnaD0iZGFya29yYW5nZTEiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KIyBUYW5nbGVncmFtIA0KdGFuZ2xlIDwtIG9iaiRhc3NvYyAlPiUNCiAgYXNfZGF0YV9mcmFtZSgpICU+JQ0KICBtYWdyaXR0cjo6c2V0X2NvbG5hbWVzKGMoImxhYmVsLngiLCAibGFiZWwueSIpKSAlPiUNCiAgbGVmdF9qb2luKGxpbmtzICU+JSANCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChsYWJlbC54ID0gU2FtcGxlX05hbWUsIGxhYmVsLnkgPSBPVFUsIHNpZ25pZl9wYWNvPXNpZ25pZikpICU+JQ0KICBsZWZ0X2pvaW4oUEZfbGlua3MgJT4lIA0KICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGxhYmVsLnggPSBwc3lsbGlkX3NwcCwgbGFiZWwueSA9IE9UVSwgc2lnbmlmX3BhcmE9c2lnbmlmKSkgJT4lDQogIGxlZnRfam9pbihwMSRkYXRhICU+JSBkcGx5cjo6c2VsZWN0KGxhYmVsLCB5KSAlPiUgZHBseXI6OnJlbmFtZShsYWJlbC54ID0gbGFiZWwpLCBieT0ibGFiZWwueCIpICU+JQ0KICBsZWZ0X2pvaW4ocDIkZGF0YSAlPiUgZHBseXI6OnNlbGVjdChsYWJlbCwgeSkgJT4lIGRwbHlyOjpyZW5hbWUobGFiZWwueSA9IGxhYmVsKSwgYnk9ImxhYmVsLnkiKSAlPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCJhc3NvYyIpICU+JQ0KICByZW5hbWVfYWxsKH5zdHJfcmVwbGFjZSgueCxwYXR0ZXJuPSJcXC4iLCByZXBsYWNlbWVudD0iXyIpKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKGVuZHNfd2l0aChjKCJfeCIsICJfeSIpKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvPWMoIi52YWx1ZSIsICJ0cmVlIiksIA0KICAgICAgICAgICAgICAgbmFtZXNfc2VwID0gIl8iDQogICAgICAgICAgICAgICkgICU+JQ0KICBtdXRhdGUoc2lnbmlmX3BhY28gPSByZXBsYWNlX25hKHNpZ25pZl9wYWNvLCAwKSwNCiAgICAgICAgIHNpZ25pZl9wYXJhID0gcmVwbGFjZV9uYShzaWduaWZfcGFyYSwgMCkpICU+JQ0KICBtdXRhdGUoc2lnbmlmID0gY2FzZV93aGVuKA0KICAgIHNpZ25pZl9wYWNvPT0wICYgc2lnbmlmX3BhcmE9PTAgIH4gIk5TIiwNCiAgICBzaWduaWZfcGFjbz09MCAmIHNpZ25pZl9wYXJhPT0xIH4gInBhcmEiLA0KICAgIHNpZ25pZl9wYWNvPT0xICYgc2lnbmlmX3BhcmE9PTAgfiAicGFjbyIsDQogICAgc2lnbmlmX3BhY289PTEgJiBzaWduaWZfcGFyYT09MSB+ICJib3RoIg0KICApKSAlPiUNCiAgbXV0YXRlKHRyZWUgPSB0cmVlICU+JSANCiAgICAgICAgICAgc3RyX3JlcGxhY2UoIngiLCAiaG9zdCIpJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlKCJ5IiwgIm1pY3JvYmUiKSkgJT4lDQogIGZpbHRlcighaXMubmEobGFiZWwpKSU+JSANCiAgZ3JvdXBfYnkodHJlZSkgJT4lDQogIG11dGF0ZSh5ID0geSAvIG1heCh5KSkgJT4lDQogIG11dGF0ZShsYWJlbCA9IGxhYmVsICU+JSBzdHJfcmVwbGFjZV9hbGwoIl8iLCAiICIpKSANCg0KDQpnZy50YW5nbGUgPC0gZ2dwbG90KHRhbmdsZSwgYWVzKHg9dHJlZSwgeT15LCBncm91cD1hc3NvYywgY29sb3VyPWFzLmZhY3RvcihzaWduaWYpKSkgKw0KICAgIGdlb21fbGluZShhbHBoYT0wLjgpICsgIA0KICBnZW9tX3RleHQoZGF0YSA9IHRhbmdsZSAlPiUgDQogICAgICAgICAgICAgIGZpbHRlcih0cmVlPT0iaG9zdCIpLA0KICAgICAgICAgICAgYWVzKGxhYmVsPWxhYmVsKSxzdGF0ID0gJ3VuaXF1ZScsIGhqdXN0PTEsIGNoZWNrX292ZXJsYXAgPSBUUlVFKSsNCiAgZ2VvbV90ZXh0KGRhdGEgPSB0YW5nbGUgJT4lIA0KICAgICAgICAgICAgICBmaWx0ZXIodHJlZT09Im1pY3JvYmUiKSwNCiAgICAgICAgICAgIGFlcyhsYWJlbD1sYWJlbCksc3RhdCA9ICd1bmlxdWUnLCBoanVzdD0wLCBjaGVja19vdmVybGFwID0gVFJVRSkrDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoIk5TIj0ic3RlZWxibHVlIiwgInBhY28iPSJkYXJrb3JhbmdlMSIsICJwYXJhIj0iI2RhMmI5MSIsICJib3RoIj0iIzkxZGEyYiIpLCBuYS50cmFuc2xhdGU9RkFMU0UpKw0KICAgIHNjYWxlX3hfZGlzY3JldGUoZXhwYW5kID0gZXhwYW5zaW9uKGFkZD1jKDAuOCwwLjgpKSkgKyANCiAgICB0aGVtZV92b2lkKCkgKw0KICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQ9YygwLjAwNSwwLjAwNSkpKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArDQogIGxhYnMoY29sb3VyPSJTaWduaWZpY2FuY2U6IikNCg0KZ2cuY2Fyc29uX3RhbmdsZSA8LSBwMSArIGdnLnRhbmdsZSArIChwMiArIHNjYWxlX3hfcmV2ZXJzZSgpKSAjKyBwbG90X2xheW91dCh3aWR0aHMgPSBjKDIsIDEsIDIpKQ0KZ2cuY2Fyc29uX3RhbmdsZSANCg0KcGRmKGZpbGU9ImZpZ3MvY2Fyc29uZWxsYV90YW5nbGVncmFtLnBkZiIsICB3aWR0aCA9IDgsIGhlaWdodCA9IDExLCBwYXBlcj0iYTQiKQ0KICBwbG90KGdnLmNhcnNvbl90YW5nbGUpDQp0cnkoZGV2Lm9mZigpLCBzaWxlbnQ9VFJVRSkNCmBgYA0KDQojIyBQc3lsbGlkIH4gU29kYWxpcw0KDQpgYGB7ciBTb2RhbGlzIGNvcGh5bG99DQojRmxhZyB0b3AgYWJ1bmRhbmNlIHNvZGFsaXMgYnkgc2FtcGxlDQp0b3Bfc29kYWxpcyA8LSBwczIgJT4lDQogIHRyYW5zZm9ybV9zYW1wbGVfY291bnRzKGZ1bmN0aW9uICh4KSB4L3N1bSh4KSkgJT4lDQogIHNwZWVkeXNlcTo6cHNtZWx0KCkgJT4lDQogIGZpbHRlcihwc3lsbGlkX3NwcCAlaW4lIHBydW5lZC50cmVlJHRpcC5sYWJlbCkgJT4lDQogIGZpbHRlcihnZW51cz09IlNvZGFsaXMiKSAlPiUNCiAgZ3JvdXBfYnkoU2FtcGxlKSAlPiUgIA0KICBmaWx0ZXIoQWJ1bmRhbmNlID4gMCkgJT4lDQogIHRvcF9uKDEsIHd0PUFidW5kYW5jZSkgJT4lDQogIG11dGF0ZSh0b3AgPSBUUlVFKSANCg0KY29vY3VyIDwtIHBzMiAlPiUNCiAgdHJhbnNmb3JtX3NhbXBsZV9jb3VudHMoZnVuY3Rpb24gKHgpIHgvc3VtKHgpKSAlPiUNCiAgc3BlZWR5c2VxOjpwc21lbHQoKSAlPiUNCiAgZmlsdGVyKHBzeWxsaWRfc3BwICVpbiUgcHJ1bmVkLnRyZWUkdGlwLmxhYmVsKSAlPiUNCiAgbGVmdF9qb2luKHRvcF9zb2RhbGlzKSAlPiUNCiAgZmlsdGVyKGdlbnVzPT0iU29kYWxpcyIsIHRvcD09VFJVRSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoT1RVLCBwc3lsbGlkX3NwcCwgU2FtcGxlSUQsIEFidW5kYW5jZSkgJT4lDQogIG11dGF0ZShPVFUgPSBPVFUgJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiIHwtIiwgIl8iKSkgJT4lDQogIGRwbHlyOjpncm91cF9ieShPVFUsIHBzeWxsaWRfc3BwKSAlPiUNCiAgICBzdW1tYXJpc2UoQWJ1bmRhbmNlID0gc3VtKEFidW5kYW5jZSkpICU+JQ0KICBwaXZvdF93aWRlcihpZF9jb2xzID0gcHN5bGxpZF9zcHAsDQogICAgICAgICAgICAgIG5hbWVzX2Zyb20gPSBPVFUsDQogICAgICAgICAgICAgIHZhbHVlc19mcm9tPUFidW5kYW5jZSwNCiAgICAgICAgICAgICAgdmFsdWVzX2ZpbGwgPSBsaXN0KEFidW5kYW5jZSA9IDApKSAgJT4lDQogIGNvbHVtbl90b19yb3duYW1lcygicHN5bGxpZF9zcHAiKSAlPiUNCiAgICBhcy5tYXRyaXgoKSAlPiUgDQogIGFwcGx5KDIsIGZ1bmN0aW9uKHgpIGlmZWxzZSh4ID4gMCwgMSwgMCkpIA0KDQpjb29jdXIgPC0gY29vY3VyWyByb3dTdW1zKGNvb2N1cikgPiAwLGNvbFN1bXMoY29vY3VyKSA+IDBdDQoNCiMgSCBjb3BoZW5ldGljIGRpc3RhbmNlDQpoX3RyZWUgPC0gcHJ1bmVkLnRyZWUNCmhfdHJlZSR0aXAubGFiZWwgPC0gaF90cmVlJHRpcC5sYWJlbCAlPiUNCiAgICAgICAgICAgc3RyX3JlcGxhY2VfYWxsKCIgfC0iLCAiXyIpDQpoX3RyZWUgPC0gZHJvcC50aXAoaF90cmVlLCBzZXRkaWZmKGhfdHJlZSR0aXAubGFiZWwsIHJvd25hbWVzKGNvb2N1cikpKQ0KaF9kaXN0IDwtIHNxcnQoY29waGVuZXRpYyhoX3RyZWUpKQ0KDQojIFAgY29waGVuZXRpYyBkaXN0YW5jZQ0Kc190cmVlIDwtIHBoeV90cmVlKHBzMykNCnNfdHJlZSR0aXAubGFiZWwgPC0gc190cmVlJHRpcC5sYWJlbCAlPiUNCiAgICAgICAgICAgc3RyX3JlcGxhY2VfYWxsKCIgfC0iLCAiXyIpDQpzX3RyZWUgPC0gZHJvcC50aXAoc190cmVlLCBzZXRkaWZmKHNfdHJlZSR0aXAubGFiZWwsIGNvbG5hbWVzKGNvb2N1cikpKQ0Kc19kaXN0IDwtIHNxcnQoY29waGVuZXRpYyhzX3RyZWUpLzFlKzYgKSAjI2NvbnZlcnQgdG8gTXlhIHNvIGludGVnZXJzIGFyZSBzbWFsbCBlbm91Z2ggZm9yIFBBQ08NCg0KI2FsdGVybmF0aXZlbHkgLSBzcXJ0KGNvcGhlbmV0aWMoc190cmVlKSkNCmNvb2N1ciA8LSBjb29jdXJbaF90cmVlJHRpcC5sYWJlbCwgc190cmVlJHRpcC5sYWJlbF0NCg0KIyBwcmVwYXJlIHBhY28gZGF0YQ0KRCA8LSBwcmVwYXJlX3BhY29fZGF0YShIPWhfZGlzdCwgUD1zX2Rpc3QsIEhQPWNvb2N1cikNCiMgQWRkIHBjb3JkDQpEIDwtIGFkZF9wY29vcmQoRCwgY29ycmVjdGlvbj0nbm9uZScpIA0KDQpwX2hvc3QgPC0gZ2dwbG90KEQkSF9QQ29bLGMoJ0F4aXMuMScsICdBeGlzLjInKV0gJT4lIGFzLmRhdGEuZnJhbWUsIGFlcyhBeGlzLjEsIEF4aXMuMikpICsNCiAgICBnZW9tX3BvaW50KCkgKw0KICAgIHRoZW1lX2J3KCkgKw0KICBnZ3RpdGxlKCJIIFBDQSIpDQoNCnBfcGFyYSA8LSBnZ3Bsb3QoRCRQX1BDb1ssYygnQXhpcy4xJywgJ0F4aXMuMicpXSAlPiUgYXMuZGF0YS5mcmFtZSwgYWVzKEF4aXMuMSwgQXhpcy4yKSkgICsNCiAgICBnZW9tX3BvaW50KCkgKw0KICAgIHRoZW1lX2J3KCkrDQogIGdndGl0bGUoIlAgUENBIikNCiAgICANCnBsb3QocF9ob3N0ICsgcF9wYXJhKQ0KDQojIHJ1biBwYWNvDQpwYWNvX3J1biA8LSBQQUNvKEQsIG5wZXJtPTk5OSwgc2VlZD05MDksIG1ldGhvZD0ncXVhc2lzd2FwJywgc3ltbWV0cmljPVRSVUUpDQoNCiNwcmludCBvdmVyYWxsIHNpZ25pZmljYW5jZQ0KcHJpbnQocGFjb19ydW4kZ29mKQ0KDQojIEdldCBpbnRlcmFjdGlvbi1zcGVjaWZpYyBjb3BoeWxvZ2VuZXRpYyBjb250cmlidXRpb25zIHVzaW5nIGphY2tuaWZpbmcNCnBhY29fcnVuIDwtIHBhY29fbGlua3MocGFjb19ydW4pDQpsaW5rcyA8LSBkYXRhLmZyYW1lKGpvaW50PW5hbWVzKHBhY29fcnVuJGphY2trbmlmZSksICNsb3Npbmcgc3Y1MCBoZXJlPw0KICAgICAgICAgICAgICAgICAgICB2YWx1ZXM9dW5uYW1lKHBhY29fcnVuJGphY2trbmlmZSkjLA0KICAgICAgICAgICAgICAgICAgICAjdXBwZXI9dW5uYW1lKHBhY29fcnVuJGphY2trbmlmZSR1cHBlcikNCiAgICAgICAgICAgICAgICAgICAgKSAlPiUNCiAgbXV0YXRlKE9UVSA9IGpvaW50KSAlPiUNCiBzZXBhcmF0ZShPVFUsIGludG89YygiU2FtcGxlX05hbWUiLCAiT1RVIiksIHNlcD0iLSIsIGV4dHJhPSJtZXJnZSIpICU+JQ0KICBtdXRhdGUoc2lnbmlmID0gY2FzZV93aGVuKA0KICAgIHZhbHVlcyA8IG1lYW4oLiR2YWx1ZXMpIH4gMSwNCiAgICB2YWx1ZXMgPiBtZWFuKC4kdmFsdWVzKSB+IDANCiAgKSkNCg0KIyBQbG90IGxpbmtzDQpsaW5rcyAlPiUNCiAgZHBseXI6OnJlbmFtZShsYWJlbCA9IGpvaW50KSAlPiUNCiAgbXV0YXRlKGxhYmVsID0gYXMuZmFjdG9yKGxhYmVsKSwNCiAgICAgICAgIGxhYmVsPWZjdF9yZW9yZGVyKGxhYmVsLCB2YWx1ZXMsIHN1bSkpJT4lDQogIGFycmFuZ2UoLXZhbHVlcykgJT4lDQogIGdncGxvdChhZXMoeD1sYWJlbCwgeT12YWx1ZXMsIGNvbG91cj1hcy5mYWN0b3Ioc2lnbmlmKSkpICsNCiAgZ2VvbV9wb2ludChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIyBnZW9tX2Vycm9yYmFyKGFlcyh5bWluPXZhbHVlcywgeW1heD11cHBlcikpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbWVhbihsaW5rcyR2YWx1ZXMpKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9Yygic3RlZWxibHVlIiwgImRhcmtvcmFuZ2UxIikpICsNCiAgdGhlbWVfY2xhc3NpYygpKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpDQoNCmRpci5jcmVhdGUoIm91dHB1dC9jb3BoeWxvZ2VueS9wc3lsbGlkX3NlY29uZGFyeSIpDQp3cml0ZV9jc3YobGlua3MsICJvdXRwdXQvY29waHlsb2dlbnkvcHN5bGxpZF9zZWNvbmRhcnkvcHN5bGxpZF9zZWNvbmRhcnlfbGlua3MuY3N2IikNCndyaXRlX2NzdihsaW5rcyAlPiUgDQogICAgZ3JvdXBfYnkoT1RVKSAlPiUNCiAgICBzdW1tYXJpc2UodmFsdWVzID0gbWVhbih2YWx1ZXMpKSwgIm91dHB1dC9jb3BoeWxvZ2VueS9wc3lsbGlkX3NlY29uZGFyeS9zZWNvbmRhcnlfc3ltYmlvbnRfd2VpZ2h0cy5jc3YiKQ0Kd3JpdGVfY3N2KGxpbmtzICU+JSANCiAgICBncm91cF9ieShTYW1wbGVfTmFtZSkgJT4lDQogICAgc3VtbWFyaXNlKHZhbHVlcyA9IG1lYW4odmFsdWVzKSksICJvdXRwdXQvY29waHlsb2dlbnkvcHN5bGxpZF9zZWNvbmRhcnkvcHN5bGxpZF93ZWlnaHRzLmNzdiIpDQoNCiNHZXQgb2JzZXJ2ZWQgcmVzaWR1YWxzIG9mIFByb2NydXN0ZWFuIHN1cGVyaW1wb3NpdGlvbiANCnBhY29fcmVzaWR1YWxzIDwtIHJlc2lkdWFsc19wYWNvKHBhY29fcnVuJHByb2MsIHR5cGUgPSAiaW50ZXJhY3Rpb24iKQ0KIyBWaXN1YWxpc2UgcmVzaWR1YWxzDQpyZXMgPC0gZGF0YS5mcmFtZShPVFU9bmFtZXMocGFjb19yZXNpZHVhbHMpLCB2YWx1ZXM9dW5uYW1lKHBhY29fcmVzaWR1YWxzKSkgJT4lDQogIHNlcGFyYXRlKE9UVSwgaW50bz1jKCJTYW1wbGVfTmFtZSIsICJPVFUiKSwgc2VwPSItIiwgZXh0cmE9Im1lcmdlIikgDQoNCmdncGxvdChyZXMsIGFlcyh4PXZhbHVlcykpKw0KICBnZW9tX2RlbnNpdHkoZmlsbD0nZ3JleTcwJykrDQogIHRoZW1lX2J3KCkrDQogIHhsYWIoJ1Byb2NydXN0ZXMgcmVzaWR1YWxzJykrDQogIHlsYWIoJ0ZyZXF1ZW5jeScpDQoNCiMgUGFyYWZpdCBydW4NClBGX3J1biA8LSBwYXJhZml0KGhfZGlzdCwgc19kaXN0LGNvb2N1ciwgbnBlcm09OTk5LCB0ZXN0LmxpbmtzPVRSVUUpDQpQRl9ydW4kUGFyYUZpdEdsb2JhbA0KUEZfcnVuJHAuZ2xvYmFsDQoNClBGX2xpbmtzIDwtIGFzLmRhdGEuZnJhbWUoUEZfcnVuJGxpbmsudGFibGUpICAlPiUNCiAgICAgIGxlZnRfam9pbihlbmZyYW1lKG5hbWVzKFBGX3J1biRwYXJhLnBlci5ob3N0KSwgbmFtZSA9ICJIb3N0IiwgdmFsdWU9InBzeWxsaWRfc3BwIikgJT4lDQogICAgICAgICAgICAgICAgICBtdXRhdGUoSG9zdCA9IGFzLm51bWVyaWMoSG9zdCkpKSAlPiUNCiAgICAgIGxlZnRfam9pbihlbmZyYW1lKG5hbWVzKFBGX3J1biRob3N0LnBlci5wYXJhKSwgbmFtZSA9ICJQYXJhc2l0ZSIsIHZhbHVlPSJPVFUiKSAlPiUNCiAgICAgICAgICAgICAgICAgIG11dGF0ZShQYXJhc2l0ZSA9IGFzLm51bWVyaWMoUGFyYXNpdGUpKSklPiUNCiAgbXV0YXRlKHNpZ25pZiA9IGNhc2Vfd2hlbigNCiAgICBwLkYxIDwgMC4wNSB+IDEsDQogICAgcC5GMSA+IDAuMDUgfiAwDQogICkpIA0KDQojIENvcGh5bG9wbG90DQpjb29jdXIubHV0IDwtIHdoaWNoKGNvb2N1ciA9PTEsIGFyci5pbmQ9VFJVRSkNCmFzc29jIDwtIGNiaW5kKHJvd25hbWVzKGNvb2N1cilbY29vY3VyLmx1dFssMV1dLCBjb2xuYW1lcyhjb29jdXIpW2Nvb2N1ci5sdXRbLDJdXSkNCg0KIyBSb3RhdGUgdGhlIG5vZGVzIHVzaW5nIHBoeXRvb2xzDQpsaWJyYXJ5KHBoeXRvb2xzKQ0Kb2JqIDwtIGNvcGh5bG8odHIxPWhfdHJlZSwgdHIyPXNfdHJlZSwgYXNzb2M9YXNzb2MsIHJvdGF0ZT1UUlVFKSANCg0KIyBwc3lsbGlkX3RyZWUNCnRyZWUxIDwtIG9ialtbInRyZWVzIl1dW1sxXV0NCg0KcDEgPC0gZ2d0cmVlKHRyZWUxKQ0KYXRtZXRvX25vZGUgPC0gcDEkZGF0YSAlPiUgDQogIGZpbHRlcihzdHJfZGV0ZWN0KGxhYmVsLCAiQXRtZXRvIikpICU+JQ0KICBwdWxsKG5vZGUpDQpyb290X25vZGUgPC0gcDEkZGF0YSAlPiUgDQogIGZpbHRlcihzdHJfZGV0ZWN0KGxhYmVsLCAiQXRtZXRvIikpICU+JQ0KICBwdWxsKHBhcmVudCkNCg0KdHJlZTEgPC0gZ3JvdXBPVFUodHJlZTEsIC5ub2RlPWF0bWV0b19ub2RlKSAjIE1ha2UgdGhlIGF0bWV0byBkb3R0ZWQgdG8gaW5kaWNhdGUgb3V0Z3JvdXAgd2FzIHJlc2NhbGVkDQoNCnAxIDwtIGdndHJlZSh0cmVlMSwgbGFkZGVyaXplPUZBTFNFLCBhZXMoY29sb3VyPWxpbmtzLGxpbmV0eXBlPWdyb3VwKSkNCndlaWdodHNfcDEgPC0gcDEkZGF0YSAlPiUNCiAgbGVmdF9qb2luKGxpbmtzICU+JSANCiAgICBncm91cF9ieShTYW1wbGVfTmFtZSkgJT4lDQogICAgc3VtbWFyaXNlKFBBX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IFNhbXBsZV9OYW1lKQ0KICAgICklPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICBncm91cF9ieShwc3lsbGlkX3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKFBGX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IHBzeWxsaWRfc3BwKQ0KICAgICkgJT4lDQogIG11dGF0ZSh2YWx1ZXMgPSBQQV92YWx1ZXMgKyBQRl92YWx1ZXMpDQoNCiNTY2FsZSB0aGUgYXRtZXRvY3Jhbml1bSBhbmQgcm9vdCBub2RlcyB0byBiZSBzaG9ydGVyDQpwMSRkYXRhW3AxJGRhdGEkbm9kZSAlaW4lIGF0bWV0b19ub2RlLCAieCJdIDwtIG1heChwMSRkYXRhJHgpDQpwMSRkYXRhW3AxJGRhdGEkbm9kZSAlaW4lIHJvb3Rfbm9kZSwgIngiXSA8LSAwLjIgI3Jvb3QNCg0KcDEkZGF0YSRub2RlW3AxJGRhdGEkbm9kZV0NCg0KIyMgR2V0IHZhbHVlcyBmb3IgaGlnaGVyIG5vZGVzDQp3ZWlnaHRzX3AxIDwtIHdlaWdodHNfcDEgJT4lDQogIGxlZnRfam9pbihkYXRhLmZyYW1lKG5vZGU9d2VpZ2h0c19wMSRub2RlLCBsaW5rcyA9IHdlaWdodHNfcDEkbm9kZSAlPiUgcHVycnI6Om1hcF9kYmwoYXZlcmFnZV9kZXNjZW5kYW50cywgdHJlZT10cmVlMSwgZGY9d2VpZ2h0c19wMSkpKQ0KcDEgPC0gcDEgJTwrJSB3ZWlnaHRzX3AxICsgZ2VvbV90aXBwb2ludChhZXMoY29sb3VyPWxpbmtzKSkgKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3c9InN0ZWVsYmx1ZSIsIGhpZ2g9ImRhcmtvcmFuZ2UxIikgICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQojIE9UVSB0cmVlDQp0cmVlMiA8LSBvYmpbWyJ0cmVlcyJdXVtbMl1dDQpzX3RyZWUgPC0gZHJvcC50aXAodHJlZTIsIHNldGRpZmYodHJlZTIkdGlwLmxhYmVsLCBvYmokYXNzb2NbLDJdKSkNCnAyIDwtIGdndHJlZSh0cmVlMiAsIGxhZGRlcml6ZT1GQUxTRSwgYWVzKGNvbG91cj1saW5rcykpIA0Kd2VpZ2h0c19wMiA8LSBwMiRkYXRhICU+JQ0KICBsZWZ0X2pvaW4obGlua3MgJT4lIA0KICAgIGdyb3VwX2J5KE9UVSkgJT4lDQogICAgc3VtbWFyaXNlKFBBX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IE9UVSkNCiAgICApJT4lDQogIGxlZnRfam9pbihQRl9saW5rcyAlPiUgDQogICAgZ3JvdXBfYnkoT1RVKSAlPiUNCiAgICBzdW1tYXJpc2UoUEZfdmFsdWVzID0gbWVhbihzaWduaWYpKSAlPiUNCiAgICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gT1RVKQ0KICAgICkgJT4lDQogIG11dGF0ZSh2YWx1ZXMgPSBQQV92YWx1ZXMgKyBQRl92YWx1ZXMpDQoNCndlaWdodHNfcDIgPC0gd2VpZ2h0c19wMiAlPiUNCiAgbGVmdF9qb2luKGRhdGEuZnJhbWUobm9kZT13ZWlnaHRzX3AyJG5vZGUsIGxpbmtzID0gd2VpZ2h0c19wMiRub2RlICU+JSBwdXJycjo6bWFwX2RibChhdmVyYWdlX2Rlc2NlbmRhbnRzLCB0cmVlPXRyZWUyLCBkZj13ZWlnaHRzX3AyKSkpDQoNCnAyIDwtIHAyICU8KyUgd2VpZ2h0c19wMiAgKyBnZW9tX3RpcHBvaW50KGFlcyhjb2xvdXI9bGlua3MpKSArIA0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3c9InN0ZWVsYmx1ZSIsIGhpZ2g9ImRhcmtvcmFuZ2UxIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCiMgVGFuZ2xlZ3JhbSANCnRhbmdsZSA8LSBvYmokYXNzb2MgJT4lDQogIGFzX2RhdGFfZnJhbWUoKSAlPiUNCiAgbWFncml0dHI6OnNldF9jb2xuYW1lcyhjKCJsYWJlbC54IiwgImxhYmVsLnkiKSkgJT4lDQogIGxlZnRfam9pbihsaW5rcyAlPiUgDQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QobGFiZWwueCA9IFNhbXBsZV9OYW1lLCBsYWJlbC55ID0gT1RVLCBzaWduaWZfcGFjbz1zaWduaWYpKSAlPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChsYWJlbC54ID0gcHN5bGxpZF9zcHAsIGxhYmVsLnkgPSBPVFUsIHNpZ25pZl9wYXJhPXNpZ25pZikpICU+JQ0KICBsZWZ0X2pvaW4ocDEkZGF0YSAlPiUgZHBseXI6OnNlbGVjdChsYWJlbCwgeSkgJT4lIGRwbHlyOjpyZW5hbWUobGFiZWwueCA9IGxhYmVsKSwgYnk9ImxhYmVsLngiKSAlPiUNCiAgbGVmdF9qb2luKHAyJGRhdGEgJT4lIGRwbHlyOjpzZWxlY3QobGFiZWwsIHkpICU+JSBkcGx5cjo6cmVuYW1lKGxhYmVsLnkgPSBsYWJlbCksIGJ5PSJsYWJlbC55IikgJT4lDQogIHJvd25hbWVzX3RvX2NvbHVtbigiYXNzb2MiKSAlPiUNCiAgcmVuYW1lX2FsbCh+c3RyX3JlcGxhY2UoLngscGF0dGVybj0iXFwuIiwgcmVwbGFjZW1lbnQ9Il8iKSkgJT4lDQogIHBpdm90X2xvbmdlcihlbmRzX3dpdGgoYygiX3giLCAiX3kiKSksDQogICAgICAgICAgICAgICBuYW1lc190bz1jKCIudmFsdWUiLCAidHJlZSIpLCANCiAgICAgICAgICAgICAgIG5hbWVzX3NlcCA9ICJfIg0KICAgICAgICAgICAgICApICAlPiUNCiAgbXV0YXRlKHNpZ25pZl9wYWNvID0gcmVwbGFjZV9uYShzaWduaWZfcGFjbywgMCksDQogICAgICAgICBzaWduaWZfcGFyYSA9IHJlcGxhY2VfbmEoc2lnbmlmX3BhcmEsIDApKSAlPiUNCiAgbXV0YXRlKHNpZ25pZiA9IGNhc2Vfd2hlbigNCiAgICBzaWduaWZfcGFjbz09MCAmIHNpZ25pZl9wYXJhPT0wICB+ICJOUyIsDQogICAgc2lnbmlmX3BhY289PTAgJiBzaWduaWZfcGFyYT09MSB+ICJwYXJhIiwNCiAgICBzaWduaWZfcGFjbz09MSAmIHNpZ25pZl9wYXJhPT0wIH4gInBhY28iLA0KICAgIHNpZ25pZl9wYWNvPT0xICYgc2lnbmlmX3BhcmE9PTEgfiAiYm90aCINCiAgKSkgJT4lDQogIG11dGF0ZSh0cmVlID0gdHJlZSAlPiUgDQogICAgICAgICAgIHN0cl9yZXBsYWNlKCJ4IiwgImhvc3QiKSU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZSgieSIsICJtaWNyb2JlIikpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGxhYmVsKSklPiUgDQogIGdyb3VwX2J5KHRyZWUpICU+JQ0KICBtdXRhdGUoeSA9IHkgLyBtYXgoeSkpJT4lDQogIG11dGF0ZShsYWJlbCA9IGxhYmVsICU+JSBzdHJfcmVwbGFjZV9hbGwoIl8iLCAiICIpKSANCg0KDQpnZy50YW5nbGUgPC0gZ2dwbG90KHRhbmdsZSwgYWVzKHg9dHJlZSwgeT15LCBncm91cD1hc3NvYywgY29sb3VyPWFzLmZhY3RvcihzaWduaWYpKSkgKw0KICAgIGdlb21fbGluZShhbHBoYT0wLjgpICsgIA0KICBnZW9tX3RleHQoZGF0YSA9IHRhbmdsZSAlPiUgDQogICAgICAgICAgICAgIGZpbHRlcih0cmVlPT0iaG9zdCIpLA0KICAgICAgICAgICAgYWVzKGxhYmVsPWxhYmVsKSxzdGF0ID0gJ3VuaXF1ZScsIGhqdXN0PTEsIGNoZWNrX292ZXJsYXAgPSBUUlVFKSsNCiAgZ2VvbV90ZXh0KGRhdGEgPSB0YW5nbGUgJT4lIA0KICAgICAgICAgICAgICBmaWx0ZXIodHJlZT09Im1pY3JvYmUiKSwNCiAgICAgICAgICAgIGFlcyhsYWJlbD1sYWJlbCksc3RhdCA9ICd1bmlxdWUnLCBoanVzdD0wLCBjaGVja19vdmVybGFwID0gVFJVRSkrDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoIk5TIj0ic3RlZWxibHVlIiwgInBhY28iPSJkYXJrb3JhbmdlMSIsICJwYXJhIj0iI2RhMmI5MSIsICJib3RoIj0iIzkxZGEyYiIpLCBuYS50cmFuc2xhdGU9RkFMU0UpKw0KICAgIHNjYWxlX3hfZGlzY3JldGUoZXhwYW5kID0gZXhwYW5zaW9uKGFkZD1jKDAuOCwwLjgpKSkgKyANCiAgICB0aGVtZV92b2lkKCkgKw0KICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQ9YygwLjAxLDAuMDEpKSsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKw0KICBsYWJzKGNvbG91cj0iU2lnbmlmaWNhbmNlOiIpDQoNCmdnLnNvZGFsaXNfdGFuZ2xlIDwtIHAxICsgZ2cudGFuZ2xlICsgKHAyICsgc2NhbGVfeF9yZXZlcnNlKCkpICMrIHBsb3RfbGF5b3V0KHdpZHRocyA9IGMoMiwgMSwgMikpDQpnZy5zb2RhbGlzX3RhbmdsZQ0KDQpwZGYoZmlsZT0iZmlncy9zb2RhbGlzX3RhbmdsZWdyYW0ucGRmIiwgIHdpZHRoID0gOCwgaGVpZ2h0ID0gMTEsIHBhcGVyPSJhNCIpDQogIHBsb3QoZ2cuc29kYWxpc190YW5nbGUpDQp0cnkoZGV2Lm9mZigpLCBzaWxlbnQ9VFJVRSkNCmBgYA0KDQoNCiMjIFBzeWxsaWQgfiBBcnNlbm9waG9udXMNCg0KYGBge3IgQXJzZW5vcGhvbnVzIGNvcGh5bG99DQojIFN1YnNldCB0byB0b3AgYXJzZW5vcGhvbnVzDQp0b3BfYXJzZSA8LSBwczIgJT4lDQogIHRyYW5zZm9ybV9zYW1wbGVfY291bnRzKGZ1bmN0aW9uICh4KSB4L3N1bSh4KSkgJT4lDQogIHNwZWVkeXNlcTo6cHNtZWx0KCkgJT4lDQogIGZpbHRlcihwc3lsbGlkX3NwcCAlaW4lIHBydW5lZC50cmVlJHRpcC5sYWJlbCkgJT4lDQogIGZpbHRlcihnZW51cz09IkFyc2Vub3Bob251cyIpICU+JQ0KICBncm91cF9ieShTYW1wbGUpICU+JSAgDQogIGZpbHRlcihBYnVuZGFuY2UgPiAwKSAlPiUNCiAgdG9wX24oMSwgd3Q9QWJ1bmRhbmNlKSAlPiUNCiAgbXV0YXRlKHRvcCA9IFRSVUUpIA0KDQpjb29jdXIgPC0gcHMyICU+JQ0KICB0cmFuc2Zvcm1fc2FtcGxlX2NvdW50cyhmdW5jdGlvbiAoeCkgeC9zdW0oeCkpICU+JQ0KICBzcGVlZHlzZXE6OnBzbWVsdCgpICU+JQ0KICBmaWx0ZXIocHN5bGxpZF9zcHAgJWluJSBwcnVuZWQudHJlZSR0aXAubGFiZWwpICU+JQ0KICBsZWZ0X2pvaW4odG9wX2Fyc2UpICU+JQ0KICBmaWx0ZXIoZ2VudXM9PSJBcnNlbm9waG9udXMiLCB0b3A9PVRSVUUpICU+JQ0KICBkcGx5cjo6c2VsZWN0KE9UVSwgcHN5bGxpZF9zcHAsIFNhbXBsZUlELCBBYnVuZGFuY2UpICU+JQ0KICBtdXRhdGUoT1RVID0gT1RVICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIikpICU+JQ0KICBkcGx5cjo6Z3JvdXBfYnkoT1RVLCBwc3lsbGlkX3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKEFidW5kYW5jZSA9IHN1bShBYnVuZGFuY2UpKSAlPiUNCiAgcGl2b3Rfd2lkZXIoaWRfY29scyA9IHBzeWxsaWRfc3BwLA0KICAgICAgICAgICAgICBuYW1lc19mcm9tID0gT1RVLA0KICAgICAgICAgICAgICB2YWx1ZXNfZnJvbT1BYnVuZGFuY2UsDQogICAgICAgICAgICAgIHZhbHVlc19maWxsID0gbGlzdChBYnVuZGFuY2UgPSAwKSkgICU+JQ0KICBjb2x1bW5fdG9fcm93bmFtZXMoInBzeWxsaWRfc3BwIikgJT4lDQogICAgYXMubWF0cml4KCkgJT4lIA0KICBhcHBseSgyLCBmdW5jdGlvbih4KSBpZmVsc2UoeCA+IDAsIDEsIDApKSANCg0KY29vY3VyIDwtIGNvb2N1clsgcm93U3Vtcyhjb29jdXIpID4gMCxjb2xTdW1zKGNvb2N1cikgPiAwXQ0KDQojIEggY29waGVuZXRpYyBkaXN0YW5jZQ0KaF90cmVlIDwtIHBydW5lZC50cmVlDQpoX3RyZWUkdGlwLmxhYmVsIDwtIGhfdHJlZSR0aXAubGFiZWwgJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiIHwtIiwgIl8iKQ0KaF90cmVlIDwtIGRyb3AudGlwKGhfdHJlZSwgc2V0ZGlmZihoX3RyZWUkdGlwLmxhYmVsLCByb3duYW1lcyhjb29jdXIpKSkNCmhfZGlzdCA8LSBzcXJ0KGNvcGhlbmV0aWMoaF90cmVlKSkNCg0KIyBQIGNvcGhlbmV0aWMgZGlzdGFuY2UNCnNfdHJlZSA8LSBwaHlfdHJlZShwczMpDQpzX3RyZWUkdGlwLmxhYmVsIDwtIHNfdHJlZSR0aXAubGFiZWwgJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiIHwtIiwgIl8iKQ0Kc190cmVlIDwtIGRyb3AudGlwKHNfdHJlZSwgc2V0ZGlmZihzX3RyZWUkdGlwLmxhYmVsLCBjb2xuYW1lcyhjb29jdXIpKSkNCnNfZGlzdCA8LSBzcXJ0KGNvcGhlbmV0aWMoc190cmVlKS8xZSs2ICkgIyNjb252ZXJ0IHRvIE15YSBzbyBpbnRlZ2VycyBhcmUgc21hbGwgZW5vdWdoIGZvciBQQUNPDQoNCiNhbHRlcm5hdGl2ZWx5IC0gc3FydChjb3BoZW5ldGljKHNfdHJlZSkpDQpjb29jdXIgPC0gY29vY3VyW2hfdHJlZSR0aXAubGFiZWwsIHNfdHJlZSR0aXAubGFiZWxdDQoNCiMgcHJlcGFyZSBwYWNvIGRhdGENCkQgPC0gcHJlcGFyZV9wYWNvX2RhdGEoSD1oX2Rpc3QsIFA9c19kaXN0LCBIUD1jb29jdXIpDQojIEFkZCBwY29yZA0KRCA8LSBhZGRfcGNvb3JkKEQsIGNvcnJlY3Rpb249J25vbmUnKSANCg0KcF9ob3N0IDwtIGdncGxvdChEJEhfUENvWyxjKCdBeGlzLjEnLCAnQXhpcy4yJyldICU+JSBhcy5kYXRhLmZyYW1lLCBhZXMoQXhpcy4xLCBBeGlzLjIpKSArDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICB0aGVtZV9idygpICsNCiAgZ2d0aXRsZSgiSCBQQ0EiKQ0KDQpwX3BhcmEgPC0gZ2dwbG90KEQkUF9QQ29bLGMoJ0F4aXMuMScsICdBeGlzLjInKV0gJT4lIGFzLmRhdGEuZnJhbWUsIGFlcyhBeGlzLjEsIEF4aXMuMikpICArDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICB0aGVtZV9idygpKw0KICBnZ3RpdGxlKCJQIFBDQSIpDQogICAgDQpwbG90KHBfaG9zdCArIHBfcGFyYSkNCg0KIyBydW4gcGFjbw0KcGFjb19ydW4gPC0gUEFDbyhELCBucGVybT05OTksIHNlZWQ9OTA5LCBtZXRob2Q9J3F1YXNpc3dhcCcsIHN5bW1ldHJpYz1GQUxTRSkNCg0KI3ByaW50IG92ZXJhbGwgc2lnbmlmaWNhbmNlDQpwcmludChwYWNvX3J1biRnb2YpDQoNCiMgR2V0IGludGVyYWN0aW9uLXNwZWNpZmljIGNvcGh5bG9nZW5ldGljIGNvbnRyaWJ1dGlvbnMgdXNpbmcgamFja25pZmluZw0KcGFjb19ydW4gPC0gcGFjb19saW5rcyhwYWNvX3J1bikNCmxpbmtzIDwtIGRhdGEuZnJhbWUoam9pbnQ9bmFtZXMocGFjb19ydW4kamFja2tuaWZlKSwNCiAgICAgICAgICAgICAgICAgICAgdmFsdWVzPXVubmFtZShwYWNvX3J1biRqYWNra25pZmUpIywNCiAgICAgICAgICAgICAgICAgICAgI3VwcGVyPXVubmFtZShwYWNvX3J1biRqYWNra25pZmUkdXBwZXIpDQogICAgICAgICAgICAgICAgICAgICkgJT4lDQogIG11dGF0ZShPVFUgPSBqb2ludCkgJT4lDQogc2VwYXJhdGUoT1RVLCBpbnRvPWMoIlNhbXBsZV9OYW1lIiwgIk9UVSIpLCBzZXA9Ii0iLCBleHRyYT0ibWVyZ2UiKSAlPiUNCiAgbXV0YXRlKHNpZ25pZiA9IGNhc2Vfd2hlbigNCiAgICB2YWx1ZXMgPCBtZWFuKC4kdmFsdWVzKSB+IDEsDQogICAgdmFsdWVzID4gbWVhbiguJHZhbHVlcykgfiAwDQogICkpDQoNCiMgUGxvdCBsaW5rcw0KbGlua3MgJT4lDQogIGRwbHlyOjpyZW5hbWUobGFiZWwgPSBqb2ludCkgJT4lDQogIG11dGF0ZShsYWJlbCA9IGFzLmZhY3RvcihsYWJlbCksDQogICAgICAgICBsYWJlbD1mY3RfcmVvcmRlcihsYWJlbCwgdmFsdWVzLCBzdW0pKSU+JQ0KICBhcnJhbmdlKC12YWx1ZXMpICU+JQ0KICBnZ3Bsb3QoYWVzKHg9bGFiZWwsIHk9dmFsdWVzLCBjb2xvdXI9YXMuZmFjdG9yKHNpZ25pZikpKSArDQogIGdlb21fcG9pbnQoc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluPXZhbHVlcywgeW1heD11cHBlcikpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbWVhbihsaW5rcyR2YWx1ZXMpKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9Yygic3RlZWxibHVlIiwgImRhcmtvcmFuZ2UxIikpICsNCiAgdGhlbWVfY2xhc3NpYygpKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpDQoNCmRpci5jcmVhdGUoIm91dHB1dC9jb3BoeWxvZ2VueS9wc3lsbGlkX3NlY29uZGFyeSIpDQp3cml0ZV9jc3YobGlua3MsICJvdXRwdXQvY29waHlsb2dlbnkvcHN5bGxpZF9zZWNvbmRhcnkvcHN5bGxpZF9zZWNvbmRhcnlfbGlua3MuY3N2IikNCndyaXRlX2NzdihsaW5rcyAlPiUgDQogICAgZ3JvdXBfYnkoT1RVKSAlPiUNCiAgICBzdW1tYXJpc2UodmFsdWVzID0gbWVhbih2YWx1ZXMpKSwgIm91dHB1dC9jb3BoeWxvZ2VueS9wc3lsbGlkX3NlY29uZGFyeS9zZWNvbmRhcnlfc3ltYmlvbnRfd2VpZ2h0cy5jc3YiKQ0Kd3JpdGVfY3N2KGxpbmtzICU+JSANCiAgICBncm91cF9ieShTYW1wbGVfTmFtZSkgJT4lDQogICAgc3VtbWFyaXNlKHZhbHVlcyA9IG1lYW4odmFsdWVzKSksICJvdXRwdXQvY29waHlsb2dlbnkvcHN5bGxpZF9zZWNvbmRhcnkvcHN5bGxpZF93ZWlnaHRzLmNzdiIpDQoNCiNHZXQgb2JzZXJ2ZWQgcmVzaWR1YWxzIG9mIFByb2NydXN0ZWFuIHN1cGVyaW1wb3NpdGlvbiANCnBhY29fcmVzaWR1YWxzIDwtIHJlc2lkdWFsc19wYWNvKHBhY29fcnVuJHByb2MsIHR5cGUgPSAiaW50ZXJhY3Rpb24iKQ0KIyBWaXN1YWxpc2UgcmVzaWR1YWxzDQpyZXMgPC0gZGF0YS5mcmFtZShPVFU9bmFtZXMocGFjb19yZXNpZHVhbHMpLCB2YWx1ZXM9dW5uYW1lKHBhY29fcmVzaWR1YWxzKSkgJT4lDQogIHNlcGFyYXRlKE9UVSwgaW50bz1jKCJTYW1wbGVfTmFtZSIsICJPVFUiKSwgc2VwPSItIiwgZXh0cmE9Im1lcmdlIikgDQoNCmdncGxvdChyZXMsIGFlcyh4PXZhbHVlcykpKw0KICBnZW9tX2RlbnNpdHkoZmlsbD0nZ3JleTcwJykrDQogIHRoZW1lX2J3KCkrDQogIHhsYWIoJ1Byb2NydXN0ZXMgcmVzaWR1YWxzJykrDQogIHlsYWIoJ0ZyZXF1ZW5jeScpDQoNCiMgUGFyYWZpdCBydW4NClBGX3J1biA8LSBwYXJhZml0KGhfZGlzdCwgc19kaXN0LGNvb2N1ciwgbnBlcm09OTk5LCB0ZXN0LmxpbmtzPVRSVUUpDQpQRl9ydW4kUGFyYUZpdEdsb2JhbA0KUEZfcnVuJHAuZ2xvYmFsDQoNClBGX2xpbmtzIDwtIGFzLmRhdGEuZnJhbWUoUEZfcnVuJGxpbmsudGFibGUpICAlPiUNCiAgICAgIGxlZnRfam9pbihlbmZyYW1lKG5hbWVzKFBGX3J1biRwYXJhLnBlci5ob3N0KSwgbmFtZSA9ICJIb3N0IiwgdmFsdWU9InBzeWxsaWRfc3BwIikgJT4lDQogICAgICAgICAgICAgICAgICBtdXRhdGUoSG9zdCA9IGFzLm51bWVyaWMoSG9zdCkpKSAlPiUNCiAgICAgIGxlZnRfam9pbihlbmZyYW1lKG5hbWVzKFBGX3J1biRob3N0LnBlci5wYXJhKSwgbmFtZSA9ICJQYXJhc2l0ZSIsIHZhbHVlPSJPVFUiKSAlPiUNCiAgICAgICAgICAgICAgICAgIG11dGF0ZShQYXJhc2l0ZSA9IGFzLm51bWVyaWMoUGFyYXNpdGUpKSklPiUNCiAgbXV0YXRlKHNpZ25pZiA9IGNhc2Vfd2hlbigNCiAgICBwLkYxIDwgMC4wNSB+IDEsDQogICAgcC5GMSA+IDAuMDUgfiAwDQogICkpIA0KDQojIENvcGh5bG9wbG90DQpjb29jdXIubHV0IDwtIHdoaWNoKGNvb2N1ciA9PTEsIGFyci5pbmQ9VFJVRSkNCmFzc29jIDwtIGNiaW5kKHJvd25hbWVzKGNvb2N1cilbY29vY3VyLmx1dFssMV1dLCBjb2xuYW1lcyhjb29jdXIpW2Nvb2N1ci5sdXRbLDJdXSkNCg0KIyBSb3RhdGUgdGhlIG5vZGVzIHVzaW5nIHBoeXRvb2xzDQpsaWJyYXJ5KHBoeXRvb2xzKQ0Kb2JqIDwtIGNvcGh5bG8odHIxPWhfdHJlZSwgdHIyPXNfdHJlZSwgYXNzb2M9YXNzb2MsIHJvdGF0ZT1UUlVFKSANCg0KIyBwc3lsbGlkX3RyZWUNCnRyZWUxIDwtIG9ialtbInRyZWVzIl1dW1sxXV0NCg0KcDEgPC0gZ2d0cmVlKHRyZWUxKQ0KYXRtZXRvX25vZGUgPC0gcDEkZGF0YSAlPiUgDQogIGZpbHRlcihzdHJfZGV0ZWN0KGxhYmVsLCAiQXRtZXRvIikpICU+JQ0KICBwdWxsKG5vZGUpDQpyb290X25vZGUgPC0gcDEkZGF0YSAlPiUgDQogIGZpbHRlcihzdHJfZGV0ZWN0KGxhYmVsLCAiQXRtZXRvIikpICU+JQ0KICBwdWxsKHBhcmVudCkNCg0KdHJlZTEgPC0gZ3JvdXBPVFUodHJlZTEsIC5ub2RlPWF0bWV0b19ub2RlKSAjIE1ha2UgdGhlIGF0bWV0byBkb3R0ZWQgdG8gaW5kaWNhdGUgb3V0Z3JvdXAgd2FzIHJlc2NhbGVkDQoNCnAxIDwtIGdndHJlZSh0cmVlMSwgbGFkZGVyaXplPUZBTFNFLCBhZXMoY29sb3VyPWxpbmtzLGxpbmV0eXBlPWdyb3VwKSkNCndlaWdodHNfcDEgPC0gcDEkZGF0YSAlPiUNCiAgbGVmdF9qb2luKGxpbmtzICU+JSANCiAgICBncm91cF9ieShTYW1wbGVfTmFtZSkgJT4lDQogICAgc3VtbWFyaXNlKFBBX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IFNhbXBsZV9OYW1lKQ0KICAgICklPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICBncm91cF9ieShwc3lsbGlkX3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKFBGX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IHBzeWxsaWRfc3BwKQ0KICAgICkgJT4lDQogIG11dGF0ZSh2YWx1ZXMgPSBQQV92YWx1ZXMgKyBQRl92YWx1ZXMpDQoNCiNTY2FsZSB0aGUgYXRtZXRvY3Jhbml1bSBhbmQgcm9vdCBub2RlcyB0byBiZSBzaG9ydGVyDQpwMSRkYXRhW3AxJGRhdGEkbm9kZSAlaW4lIGF0bWV0b19ub2RlLCAieCJdIDwtIG1heChwMSRkYXRhJHgpDQpwMSRkYXRhW3AxJGRhdGEkbm9kZSAlaW4lIHJvb3Rfbm9kZSwgIngiXSA8LSAwLjIgI3Jvb3QNCg0KcDEkZGF0YSRub2RlW3AxJGRhdGEkbm9kZV0NCg0KIyMgR2V0IHZhbHVlcyBmb3IgaGlnaGVyIG5vZGVzDQp3ZWlnaHRzX3AxIDwtIHdlaWdodHNfcDEgJT4lDQogIGxlZnRfam9pbihkYXRhLmZyYW1lKG5vZGU9d2VpZ2h0c19wMSRub2RlLCBsaW5rcyA9IHdlaWdodHNfcDEkbm9kZSAlPiUgcHVycnI6Om1hcF9kYmwoYXZlcmFnZV9kZXNjZW5kYW50cywgdHJlZT10cmVlMSwgZGY9d2VpZ2h0c19wMSkpKQ0KcDEgPC0gcDEgJTwrJSB3ZWlnaHRzX3AxICsgZ2VvbV90aXBwb2ludChhZXMoY29sb3VyPWxpbmtzKSkgKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3c9InN0ZWVsYmx1ZSIsIGhpZ2g9ImRhcmtvcmFuZ2UxIikgICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQojIE9UVSB0cmVlDQp0cmVlMiA8LSBvYmpbWyJ0cmVlcyJdXVtbMl1dDQpzX3RyZWUgPC0gZHJvcC50aXAodHJlZTIsIHNldGRpZmYodHJlZTIkdGlwLmxhYmVsLCBvYmokYXNzb2NbLDJdKSkNCnAyIDwtIGdndHJlZSh0cmVlMiAsIGxhZGRlcml6ZT1GQUxTRSwgYWVzKGNvbG91cj1saW5rcykpIA0Kd2VpZ2h0c19wMiA8LSBwMiRkYXRhICU+JQ0KICBsZWZ0X2pvaW4obGlua3MgJT4lIA0KICAgIGdyb3VwX2J5KE9UVSkgJT4lDQogICAgc3VtbWFyaXNlKFBBX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IE9UVSkNCiAgICApJT4lDQogIGxlZnRfam9pbihQRl9saW5rcyAlPiUgDQogICAgZ3JvdXBfYnkoT1RVKSAlPiUNCiAgICBzdW1tYXJpc2UoUEZfdmFsdWVzID0gbWVhbihzaWduaWYpKSAlPiUNCiAgICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gT1RVKQ0KICAgICkgJT4lDQogIG11dGF0ZSh2YWx1ZXMgPSBQQV92YWx1ZXMgKyBQRl92YWx1ZXMpDQoNCndlaWdodHNfcDIgPC0gd2VpZ2h0c19wMiAlPiUNCiAgbGVmdF9qb2luKGRhdGEuZnJhbWUobm9kZT13ZWlnaHRzX3AyJG5vZGUsIGxpbmtzID0gd2VpZ2h0c19wMiRub2RlICU+JSBwdXJycjo6bWFwX2RibChhdmVyYWdlX2Rlc2NlbmRhbnRzLCB0cmVlPXRyZWUyLCBkZj13ZWlnaHRzX3AyKSkpDQoNCnAyIDwtIHAyICU8KyUgd2VpZ2h0c19wMiAgKyBnZW9tX3RpcHBvaW50KGFlcyhjb2xvdXI9bGlua3MpKSArIA0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3c9InN0ZWVsYmx1ZSIsIGhpZ2g9ImRhcmtvcmFuZ2UxIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCiMgVGFuZ2xlZ3JhbSANCnRhbmdsZSA8LSBvYmokYXNzb2MgJT4lDQogIGFzX2RhdGFfZnJhbWUoKSAlPiUNCiAgbWFncml0dHI6OnNldF9jb2xuYW1lcyhjKCJsYWJlbC54IiwgImxhYmVsLnkiKSkgJT4lDQogIGxlZnRfam9pbihsaW5rcyAlPiUgDQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QobGFiZWwueCA9IFNhbXBsZV9OYW1lLCBsYWJlbC55ID0gT1RVLCBzaWduaWZfcGFjbz1zaWduaWYpKSAlPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChsYWJlbC54ID0gcHN5bGxpZF9zcHAsIGxhYmVsLnkgPSBPVFUsIHNpZ25pZl9wYXJhPXNpZ25pZikpICU+JQ0KICBsZWZ0X2pvaW4ocDEkZGF0YSAlPiUgZHBseXI6OnNlbGVjdChsYWJlbCwgeSkgJT4lIGRwbHlyOjpyZW5hbWUobGFiZWwueCA9IGxhYmVsKSwgYnk9ImxhYmVsLngiKSAlPiUNCiAgbGVmdF9qb2luKHAyJGRhdGEgJT4lIGRwbHlyOjpzZWxlY3QobGFiZWwsIHkpICU+JSBkcGx5cjo6cmVuYW1lKGxhYmVsLnkgPSBsYWJlbCksIGJ5PSJsYWJlbC55IikgJT4lDQogIHJvd25hbWVzX3RvX2NvbHVtbigiYXNzb2MiKSAlPiUNCiAgcmVuYW1lX2FsbCh+c3RyX3JlcGxhY2UoLngscGF0dGVybj0iXFwuIiwgcmVwbGFjZW1lbnQ9Il8iKSkgJT4lDQogIHBpdm90X2xvbmdlcihlbmRzX3dpdGgoYygiX3giLCAiX3kiKSksDQogICAgICAgICAgICAgICBuYW1lc190bz1jKCIudmFsdWUiLCAidHJlZSIpLCANCiAgICAgICAgICAgICAgIG5hbWVzX3NlcCA9ICJfIg0KICAgICAgICAgICAgICApICAlPiUNCiAgbXV0YXRlKHNpZ25pZl9wYWNvID0gcmVwbGFjZV9uYShzaWduaWZfcGFjbywgMCksDQogICAgICAgICBzaWduaWZfcGFyYSA9IHJlcGxhY2VfbmEoc2lnbmlmX3BhcmEsIDApKSAlPiUNCiAgbXV0YXRlKHNpZ25pZiA9IGNhc2Vfd2hlbigNCiAgICBzaWduaWZfcGFjbz09MCAmIHNpZ25pZl9wYXJhPT0wICB+ICJOUyIsDQogICAgc2lnbmlmX3BhY289PTAgJiBzaWduaWZfcGFyYT09MSB+ICJwYXJhIiwNCiAgICBzaWduaWZfcGFjbz09MSAmIHNpZ25pZl9wYXJhPT0wIH4gInBhY28iLA0KICAgIHNpZ25pZl9wYWNvPT0xICYgc2lnbmlmX3BhcmE9PTEgfiAiYm90aCINCiAgKSkgJT4lDQogIG11dGF0ZSh0cmVlID0gdHJlZSAlPiUgDQogICAgICAgICAgIHN0cl9yZXBsYWNlKCJ4IiwgImhvc3QiKSU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZSgieSIsICJtaWNyb2JlIikpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGxhYmVsKSklPiUgDQogIGdyb3VwX2J5KHRyZWUpICU+JQ0KICBtdXRhdGUoeSA9IHkgLyBtYXgoeSkpJT4lDQogIG11dGF0ZShsYWJlbCA9IGxhYmVsICU+JSBzdHJfcmVwbGFjZV9hbGwoIl8iLCAiICIpKSANCg0KDQpnZy50YW5nbGUgPC0gZ2dwbG90KHRhbmdsZSwgYWVzKHg9dHJlZSwgeT15LCBncm91cD1hc3NvYywgY29sb3VyPWFzLmZhY3RvcihzaWduaWYpKSkgKw0KICAgIGdlb21fbGluZShhbHBoYT0wLjgpICsgIA0KICBnZW9tX3RleHQoZGF0YSA9IHRhbmdsZSAlPiUgDQogICAgICAgICAgICAgIGZpbHRlcih0cmVlPT0iaG9zdCIpLA0KICAgICAgICAgICAgYWVzKGxhYmVsPWxhYmVsKSxzdGF0ID0gJ3VuaXF1ZScsIGhqdXN0PTEsIGNoZWNrX292ZXJsYXAgPSBUUlVFKSsNCiAgZ2VvbV90ZXh0KGRhdGEgPSB0YW5nbGUgJT4lIA0KICAgICAgICAgICAgICBmaWx0ZXIodHJlZT09Im1pY3JvYmUiKSwNCiAgICAgICAgICAgIGFlcyhsYWJlbD1sYWJlbCksc3RhdCA9ICd1bmlxdWUnLCBoanVzdD0wLCBjaGVja19vdmVybGFwID0gVFJVRSkrDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoIk5TIj0ic3RlZWxibHVlIiwgInBhY28iPSJkYXJrb3JhbmdlMSIsICJwYXJhIj0iI2RhMmI5MSIsICJib3RoIj0iIzkxZGEyYiIpLCBuYS50cmFuc2xhdGU9RkFMU0UpKw0KICAgIHNjYWxlX3hfZGlzY3JldGUoZXhwYW5kID0gZXhwYW5zaW9uKGFkZD1jKDAuOCwwLjgpKSkgKyANCiAgICB0aGVtZV92b2lkKCkgKw0KICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQ9YygwLjAwNSwwLjAwNSkpKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArDQogIGxhYnMoY29sb3VyPSJTaWduaWZpY2FuY2U6IikNCmdnLmFyc2VfdGFuZ2xlIDwtIHAxICsgZ2cudGFuZ2xlICsgKHAyICsgc2NhbGVfeF9yZXZlcnNlKCkpICMrIHBsb3RfbGF5b3V0KHdpZHRocyA9IGMoMiwgMSwgMikpDQpnZy5hcnNlX3RhbmdsZQ0KDQpwZGYoZmlsZT0iZmlncy9hcnNlbm9waG9udXNfdGFuZ2xlZ3JhbS5wZGYiLCAgd2lkdGggPSA4LCBoZWlnaHQgPSAxMSwgcGFwZXI9ImE0IikNCiAgcGxvdChnZy5hcnNlX3RhbmdsZSkNCnRyeShkZXYub2ZmKCksIHNpbGVudD1UUlVFKQ0KYGBgDQoNCiMjIFBzeWxsaWQgfiAgaG9zdHBsYW50DQoNClRhbmdsZWdyYW0gb2YgYWxsIHBsYW50cyBhbmQgIHBzeWxsaWRzIQ0KYGBge3IgaG9zdHBsYW50LXBzeWxsaWR9DQpwbGFudC50cmVlIDwtIHJlYWQudHJlZSgic2FtcGxlX2RhdGEvcGxhbnRfdHJlZS5ud2siKQ0KcGxhbnQudHJlZSAgPC0gZHJvcC50aXAocGxhbnQudHJlZSAsICJTb3Bob3JhX21pY3JvcGh5bGxhXy1rb3doYWkiICkNCg0KI1ByZXBhcmUgY28tb2NjdXJhbmNlIG1hdHJpeA0KY29vY3VyIDwtIHNhbXBsZV9kYXRhKHBzMikgJT4lDQogIGFzX3RpYmJsZSgpICU+JQ0KICBkcGx5cjo6c2VsZWN0KHBzeWxsaWRfc3BwLCBob3N0cGxhbnRfc3BwKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShwc3lsbGlkX3NwcCkpICU+JQ0KICB1bmlxdWUoKSAlPiUNCiAgbXV0YXRlKHBzeWxsaWRfc3BwID0gcHN5bGxpZF9zcHAgJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiIHwtIiwgIl8iKSwNCiAgICAgICAgIGhvc3RwbGFudF9zcHAgPSBob3N0cGxhbnRfc3BwICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIiksDQogICAgICAgICBwcmVzZW5jZSA9IDENCiAgICAgICAgICkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSAiaG9zdHBsYW50X3NwcCIsDQogICAgICAgICAgICAgIHZhbHVlc19mcm9tPSJwcmVzZW5jZSIsDQogICAgICAgICAgICAgIHZhbHVlc19maWxsID0gbGlzdChwcmVzZW5jZSA9IDApKSAlPiUNCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJwc3lsbGlkX3NwcCIpICU+JQ0KICB0KCkNCg0KIyBIIGNvcGhlbmV0aWMgZGlzdGFuY2UNCmhfdHJlZSA8LSBwbGFudC50cmVlDQpoX3RyZWUkdGlwLmxhYmVsIDwtIGhfdHJlZSR0aXAubGFiZWwgJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiIHwtIiwgIl8iKQ0KaF90cmVlIDwtIGRyb3AudGlwKGhfdHJlZSwgc2V0ZGlmZihoX3RyZWUkdGlwLmxhYmVsLCByb3duYW1lcyhjb29jdXIpKSkNCmhfZGlzdCA8LSBzcXJ0KGNvcGhlbmV0aWMoaF90cmVlKSkNCg0KIyBQIGNvcGhlbmV0aWMgZGlzdGFuY2UNCnNfdHJlZSA8LSBwcnVuZWQudHJlZQ0Kc190cmVlJHRpcC5sYWJlbCA8LSBzX3RyZWUkdGlwLmxhYmVsICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIikNCnNfdHJlZSA8LSBkcm9wLnRpcChzX3RyZWUsIHNldGRpZmYoc190cmVlJHRpcC5sYWJlbCwgY29sbmFtZXMoY29vY3VyKSkpDQpzX2Rpc3QgPC0gc3FydChjb3BoZW5ldGljKHNfdHJlZSkpDQoNCmNvb2N1ciA8LSBjb29jdXJbaF90cmVlJHRpcC5sYWJlbCwgc190cmVlJHRpcC5sYWJlbF0NCg0KIyBwcmVwYXJlIHBhY28gZGF0YQ0KRCA8LSBwcmVwYXJlX3BhY29fZGF0YShIPWhfZGlzdCwgUD1zX2Rpc3QsIEhQPWNvb2N1cikNCiMgQWRkIHBjb3JkDQpEIDwtIGFkZF9wY29vcmQoRCwgY29ycmVjdGlvbj0nbm9uZScpIA0KDQpwX2hvc3QgPC0gZ2dwbG90KEQkSF9QQ29bLGMoJ0F4aXMuMScsICdBeGlzLjInKV0gJT4lIGFzLmRhdGEuZnJhbWUsIGFlcyhBeGlzLjEsIEF4aXMuMikpICsNCiAgICBnZW9tX3BvaW50KCkgKw0KICAgIHRoZW1lX2J3KCkgKw0KICBnZ3RpdGxlKCJIIFBDQSIpDQoNCnBfcGFyYSA8LSBnZ3Bsb3QoRCRQX1BDb1ssYygnQXhpcy4xJywgJ0F4aXMuMicpXSAlPiUgYXMuZGF0YS5mcmFtZSwgYWVzKEF4aXMuMSwgQXhpcy4yKSkgICsNCiAgICBnZW9tX3BvaW50KCkgKw0KICAgIHRoZW1lX2J3KCkrDQogIGdndGl0bGUoIlAgUENBIikNCiAgICANCnBsb3QocF9ob3N0ICsgcF9wYXJhKQ0KDQojIHJ1biBwYWNvDQpwYWNvX3J1biA8LSBQQUNvKEQsIG5wZXJtPTk5OSwgc2VlZD05MDksIG1ldGhvZD0ncXVhc2lzd2FwJywgc3ltbWV0cmljPUZBTFNFKQ0KDQojcHJpbnQgb3ZlcmFsbCBzaWduaWZpY2FuY2UNCnByaW50KHBhY29fcnVuJGdvZikNCg0KIyBHZXQgaW50ZXJhY3Rpb24tc3BlY2lmaWMgY29waHlsb2dlbmV0aWMgY29udHJpYnV0aW9ucyB1c2luZyBsZWF2ZS1vbmUtb3V0IGphY2tuaWZpbmcNCnBhY29fcnVuIDwtIHBhY29fbGlua3MocGFjb19ydW4sIC5wYXJhbGxlbCA9IFRSVUUpDQoNCiMgZ2V0IGxpbmtzDQpsaW5rcyA8LSBkYXRhLmZyYW1lKA0KICBqb2ludD1uYW1lcyhwYWNvX3J1biRqYWNra25pZmUpLA0KICB2YWx1ZXM9dW5uYW1lKHBhY29fcnVuJGphY2trbmlmZSkjLA0KICAjdXBwZXI9dW5uYW1lKHBhY29fcnVuJGphY2trbmlmZSR1cHBlcikNCiAgKSAlPiUNCiBtdXRhdGUoT1RVID0gam9pbnQpICU+JQ0KIHNlcGFyYXRlKE9UVSwgaW50bz1jKCJob3N0cGxhbnRfc3BwIiwgInBzeWxsaWRfc3BwIiksIHNlcD0iLSIsIGV4dHJhPSJtZXJnZSIpICU+JQ0KICBtdXRhdGUoc2lnbmlmID0gY2FzZV93aGVuKA0KICAgIHZhbHVlcyA8IG1lYW4oLiR2YWx1ZXMpIH4gMSwNCiAgICB2YWx1ZXMgPiBtZWFuKC4kdmFsdWVzKSB+IDANCiAgKSkNCiMgUGxvdCBsaW5rcw0KbGlua3MgJT4lDQogIGRwbHlyOjpyZW5hbWUobGFiZWwgPSBqb2ludCkgJT4lDQogIG11dGF0ZShsYWJlbCA9IGFzLmZhY3RvcihsYWJlbCksDQogICAgICAgICBsYWJlbD1mY3RfcmVvcmRlcihsYWJlbCwgdmFsdWVzLCBzdW0pKSU+JQ0KICBhcnJhbmdlKC12YWx1ZXMpICU+JQ0KICBnZ3Bsb3QoYWVzKHg9bGFiZWwsIHk9dmFsdWVzLCBjb2xvdXI9YXMuZmFjdG9yKHNpZ25pZikpKSArDQogIGdlb21fcG9pbnQoc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICMgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj12YWx1ZXMsIHltYXg9dXBwZXIpKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IG1lYW4obGlua3MkdmFsdWVzKSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoInN0ZWVsYmx1ZSIsICJkYXJrb3JhbmdlMSIpKSArDQogIHRoZW1lX2NsYXNzaWMoKSsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKQ0KDQpkaXIuY3JlYXRlKCJvdXRwdXQvY29waHlsb2dlbnkvcHN5bGxpZF9ob3N0cGxhbnQiKQ0Kd3JpdGVfY3N2KGxpbmtzLCAib3V0cHV0L2NvcGh5bG9nZW55L3BzeWxsaWRfaG9zdHBsYW50L3BzeWxsaWRfaG9zdHBsYW50X2xpbmtzLmNzdiIpDQp3cml0ZV9jc3YobGlua3MgJT4lIA0KICAgIGdyb3VwX2J5KHBzeWxsaWRfc3BwKSAlPiUNCiAgICBzdW1tYXJpc2UodmFsdWVzID0gbWVhbih2YWx1ZXMpKSwgIm91dHB1dC9jb3BoeWxvZ2VueS9wc3lsbGlkX2hvc3RwbGFudC9wc3lsbGlkX3dlaWdodHMuY3N2IikNCndyaXRlX2NzdihsaW5rcyAlPiUgDQogICAgZ3JvdXBfYnkoaG9zdHBsYW50X3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKHZhbHVlcyA9IG1lYW4odmFsdWVzKSksICJvdXRwdXQvY29waHlsb2dlbnkvcHN5bGxpZF9ob3N0cGxhbnQvaG9zdHBsYW50X3dlaWdodHMuY3N2IikNCg0KI0dldCBvYnNlcnZlZCByZXNpZHVhbHMgb2YgUHJvY3J1c3RlYW4gc3VwZXJpbXBvc2l0aW9uIA0KcGFjb19yZXNpZHVhbHMgPC0gcmVzaWR1YWxzX3BhY28ocGFjb19ydW4kcHJvYywgdHlwZSA9ICJpbnRlcmFjdGlvbiIpDQoNCiMgVmlzdWFsaXNlIHJlc2lkdWFscw0KcmVzIDwtIGRhdGEuZnJhbWUoT1RVPW5hbWVzKHBhY29fcmVzaWR1YWxzKSwgdmFsdWVzPXVubmFtZShwYWNvX3Jlc2lkdWFscykpICU+JQ0KICBzZXBhcmF0ZShPVFUsIGludG89YygiaG9zdHBsYW50X3NwcCIsICJwc3lsbGlkX3NwcCIpLCBzZXA9Ii0iLCBleHRyYT0ibWVyZ2UiKQ0KDQpnZ3Bsb3QocmVzLCBhZXMoeD12YWx1ZXMpKSsNCiAgZ2VvbV9kZW5zaXR5KGZpbGw9J2dyZXk3MCcpKw0KICAjZmFjZXRfd3JhcCh+cHN5bGxpZF9nZW51cykgKw0KICB0aGVtZV9idygpKw0KICB4bGFiKCdQcm9jcnVzdGVzIHJlc2lkdWFscycpKw0KICB5bGFiKCdGcmVxdWVuY3knKQ0KDQojIFBhcmFmaXQgcnVuDQpQRl9ydW4gPC0gcGFyYWZpdChoX2Rpc3QsIHNfZGlzdCwgdChjb29jdXIpLCBucGVybT05OTksIHRlc3QubGlua3M9VFJVRSwgc2lsZW50PUZBTFNFKQ0KUEZfcnVuJFBhcmFGaXRHbG9iYWwNClBGX3J1biRwLmdsb2JhbA0KDQpQRl9saW5rcyA8LSBhcy5kYXRhLmZyYW1lKFBGX3J1biRsaW5rLnRhYmxlKSAgJT4lDQogICAgICBsZWZ0X2pvaW4oZW5mcmFtZShuYW1lcyhQRl9ydW4kcGFyYS5wZXIuaG9zdCksIG5hbWUgPSAiSG9zdCIsIHZhbHVlPSJob3N0cGxhbnRfc3BwIikgJT4lDQogICAgICAgICAgICAgICAgICBtdXRhdGUoSG9zdCA9IGFzLm51bWVyaWMoSG9zdCkpKSAlPiUNCiAgICAgIGxlZnRfam9pbihlbmZyYW1lKG5hbWVzKFBGX3J1biRob3N0LnBlci5wYXJhKSwgbmFtZSA9ICJQYXJhc2l0ZSIsIHZhbHVlPSJwc3lsbGlkX3NwcCIpICU+JQ0KICAgICAgICAgICAgICAgICAgbXV0YXRlKFBhcmFzaXRlID0gYXMubnVtZXJpYyhQYXJhc2l0ZSkpKSU+JQ0KICBtdXRhdGUoc2lnbmlmID0gY2FzZV93aGVuKA0KICAgIHAuRjEgPCAwLjA1IH4gMSwNCiAgICBwLkYxID4gMC4wNSB+IDANCiAgKSkgDQoNCiMgQ29waHlsb3Bsb3QNCmNvb2N1ci5sdXQgPC0gd2hpY2goY29vY3VyID09MSwgYXJyLmluZD1UUlVFKQ0KYXNzb2MgPC0gY2JpbmQocm93bmFtZXMoY29vY3VyKVtjb29jdXIubHV0WywxXV0sIGNvbG5hbWVzKGNvb2N1cilbY29vY3VyLmx1dFssMl1dKQ0KDQojIFJvdGF0ZSB0aGUgbm9kZXMgdXNpbmcgcGh5dG9vbHMNCm9iaiA8LSBjb3BoeWxvKHRyMT1oX3RyZWUsIHRyMj1zX3RyZWUsIGFzc29jPWFzc29jLCByb3RhdGU9VFJVRSkgDQoNCiMgRXh0cmFjdCB0aGUgZ29vZHMgZm9yIGdndHJlZQ0KIyBwbGFudCB0cmVlDQp0cmVlMSA8LSBvYmpbWyJ0cmVlcyJdXVtbMV1dDQoNCnAxIDwtIGdndHJlZSh0cmVlMSwgbGFkZGVyaXplPUZBTFNFLCBhZXMoY29sb3VyPWxpbmtzKSkNCndlaWdodHNfcDEgPC0gcDEkZGF0YSAlPiUNCiAgbGVmdF9qb2luKGxpbmtzICU+JSANCiAgICBncm91cF9ieShob3N0cGxhbnRfc3BwKSAlPiUNCiAgICBzdW1tYXJpc2UoUEFfdmFsdWVzID0gbWVhbihzaWduaWYpKSAlPiUNCiAgICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gaG9zdHBsYW50X3NwcCkNCiAgICApJT4lDQogIGxlZnRfam9pbihQRl9saW5rcyAlPiUgDQogICAgZ3JvdXBfYnkoaG9zdHBsYW50X3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKFBGX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IGhvc3RwbGFudF9zcHApDQogICAgKSAlPiUNCiAgbXV0YXRlKHZhbHVlcyA9IFBBX3ZhbHVlcyArIFBGX3ZhbHVlcykNCg0KIyMgR2V0IHZhbHVlcyBmb3IgaGlnaGVyIG5vZGVzDQp3ZWlnaHRzX3AxIDwtIHdlaWdodHNfcDEgJT4lDQogIGxlZnRfam9pbihkYXRhLmZyYW1lKG5vZGU9d2VpZ2h0c19wMSRub2RlLCBsaW5rcyA9IHdlaWdodHNfcDEkbm9kZSAlPiUgcHVycnI6Om1hcF9kYmwoYXZlcmFnZV9kZXNjZW5kYW50cywgdHJlZT10cmVlMSwgZGY9d2VpZ2h0c19wMSkpKQ0KcDEgPC0gcDEgJTwrJSB3ZWlnaHRzX3AxICsgZ2VvbV90aXBwb2ludChhZXMoY29sb3VyPWxpbmtzKSkgKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3c9InN0ZWVsYmx1ZSIsIGhpZ2g9ImRhcmtvcmFuZ2UxIikgICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQojIHBzeWxsaWRfdHJlZQ0KdHJlZTIgPC0gb2JqW1sidHJlZXMiXV1bWzJdXQ0Kc190cmVlIDwtIGRyb3AudGlwKHRyZWUyLCBzZXRkaWZmKHRyZWUyJHRpcC5sYWJlbCwgb2JqJGFzc29jWywyXSkpDQoNCnAyIDwtIGdndHJlZSh0cmVlMikNCmF0bWV0b19ub2RlIDwtIHAyJGRhdGEgJT4lIA0KICBmaWx0ZXIoc3RyX2RldGVjdChsYWJlbCwgIkF0bWV0byIpKSAlPiUNCiAgcHVsbChub2RlKQ0Kcm9vdF9ub2RlIDwtIHAyJGRhdGEgJT4lIA0KICBmaWx0ZXIoc3RyX2RldGVjdChsYWJlbCwgIkF0bWV0byIpKSAlPiUNCiAgcHVsbChwYXJlbnQpDQoNCnRyZWUyIDwtIGdyb3VwT1RVKHRyZWUyLCAubm9kZT1hdG1ldG9fbm9kZSkgIyBNYWtlIHRoZSBhdG1ldG8gZG90dGVkIHRvIGluZGljYXRlIG91dGdyb3VwIHdhcyByZXNjYWxlZA0KDQpwMiA8LSBnZ3RyZWUodHJlZTIgLCBsYWRkZXJpemU9VFJVRSwgYWVzKGNvbG91cj1saW5rcywgbGluZXR5cGU9Z3JvdXApKSANCndlaWdodHNfcDIgPC0gcDIkZGF0YSAlPiUNCiAgbGVmdF9qb2luKGxpbmtzICU+JSANCiAgICBncm91cF9ieShwc3lsbGlkX3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKFBBX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IHBzeWxsaWRfc3BwKQ0KICAgICklPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICBncm91cF9ieShwc3lsbGlkX3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKFBGX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IHBzeWxsaWRfc3BwKQ0KICAgICkgJT4lDQogIG11dGF0ZSh2YWx1ZXMgPSBQQV92YWx1ZXMgKyBQRl92YWx1ZXMpDQoNCiNTY2FsZSB0aGUgYXRtZXRvY3Jhbml1bSBhbmQgcm9vdCBub2RlcyB0byBiZSBzaG9ydGVyDQpwMiRkYXRhW3AyJGRhdGEkbm9kZSAlaW4lIGF0bWV0b19ub2RlLCAieCJdIDwtIG1heChwMiRkYXRhJHgpDQpwMiRkYXRhW3AyJGRhdGEkbm9kZSAlaW4lIHJvb3Rfbm9kZSwgIngiXSA8LSAwLjIgI3Jvb3QNCg0KcDIkZGF0YSRub2RlW3AyJGRhdGEkbm9kZV0NCg0Kd2VpZ2h0c19wMiA8LSB3ZWlnaHRzX3AyICU+JQ0KICBsZWZ0X2pvaW4oZGF0YS5mcmFtZShub2RlPXdlaWdodHNfcDIkbm9kZSwgbGlua3MgPSB3ZWlnaHRzX3AyJG5vZGUgJT4lIHB1cnJyOjptYXBfZGJsKGF2ZXJhZ2VfZGVzY2VuZGFudHMsIHRyZWU9dHJlZTIsIGRmPXdlaWdodHNfcDIpKSkNCg0KcDIgPC0gcDIgJTwrJSB3ZWlnaHRzX3AyICArIGdlb21fdGlwcG9pbnQoYWVzKGNvbG91cj1saW5rcykpICsgDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0ic3RlZWxibHVlIiwgaGlnaD0iZGFya29yYW5nZTEiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KIyBUYW5nbGVncmFtIA0KdGFuZ2xlIDwtIG9iaiRhc3NvYyAlPiUNCiAgYXNfZGF0YV9mcmFtZSgpICU+JQ0KICBtYWdyaXR0cjo6c2V0X2NvbG5hbWVzKGMoImxhYmVsLngiLCAibGFiZWwueSIpKSAlPiUNCiAgbGVmdF9qb2luKGxpbmtzICU+JSANCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChsYWJlbC54ID0gaG9zdHBsYW50X3NwcCwgbGFiZWwueSA9IHBzeWxsaWRfc3BwLCBzaWduaWZfcGFjbz1zaWduaWYpKSAlPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChsYWJlbC54ID0gaG9zdHBsYW50X3NwcCwgbGFiZWwueSA9IHBzeWxsaWRfc3BwLCBzaWduaWZfcGFyYT1zaWduaWYpKSAlPiUNCiAgbGVmdF9qb2luKHAxJGRhdGEgJT4lIGRwbHlyOjpzZWxlY3QobGFiZWwsIHkpICU+JSBkcGx5cjo6cmVuYW1lKGxhYmVsLnggPSBsYWJlbCksIGJ5PSJsYWJlbC54IikgJT4lDQogIGxlZnRfam9pbihwMiRkYXRhICU+JSBkcGx5cjo6c2VsZWN0KGxhYmVsLCB5KSAlPiUgZHBseXI6OnJlbmFtZShsYWJlbC55ID0gbGFiZWwpLCBieT0ibGFiZWwueSIpICU+JQ0KICByb3duYW1lc190b19jb2x1bW4oImFzc29jIikgJT4lDQogIHJlbmFtZV9hbGwofnN0cl9yZXBsYWNlKC54LHBhdHRlcm49IlxcLiIsIHJlcGxhY2VtZW50PSJfIikpICU+JQ0KICBwaXZvdF9sb25nZXIoZW5kc193aXRoKGMoIl94IiwgIl95IikpLA0KICAgICAgICAgICAgICAgbmFtZXNfdG89YygiLnZhbHVlIiwgInRyZWUiKSwgDQogICAgICAgICAgICAgICBuYW1lc19zZXAgPSAiXyINCiAgICAgICAgICAgICAgKSAgJT4lDQogIG11dGF0ZShzaWduaWZfcGFjbyA9IHJlcGxhY2VfbmEoc2lnbmlmX3BhY28sIDApLA0KICAgICAgICAgc2lnbmlmX3BhcmEgPSByZXBsYWNlX25hKHNpZ25pZl9wYXJhLCAwKSkgJT4lDQogIG11dGF0ZShzaWduaWYgPSBjYXNlX3doZW4oDQogICAgc2lnbmlmX3BhY289PTAgJiBzaWduaWZfcGFyYT09MCAgfiAiTlMiLA0KICAgIHNpZ25pZl9wYWNvPT0wICYgc2lnbmlmX3BhcmE9PTEgfiAicGFyYSIsDQogICAgc2lnbmlmX3BhY289PTEgJiBzaWduaWZfcGFyYT09MCB+ICJwYWNvIiwNCiAgICBzaWduaWZfcGFjbz09MSAmIHNpZ25pZl9wYXJhPT0xIH4gImJvdGgiDQogICkpICU+JQ0KICBtdXRhdGUodHJlZSA9IHRyZWUgJT4lIA0KICAgICAgICAgICBzdHJfcmVwbGFjZSgieCIsICJob3N0IiklPiUNCiAgICAgICAgICAgc3RyX3JlcGxhY2UoInkiLCAibWljcm9iZSIpKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShsYWJlbCkpJT4lIA0KICBncm91cF9ieSh0cmVlKSAlPiUNCiAgbXV0YXRlKHkgPSB5IC8gbWF4KHkpKSU+JQ0KICBtdXRhdGUobGFiZWwgPSBsYWJlbCAlPiUgc3RyX3JlcGxhY2VfYWxsKCJfIiwgIiAiKSkgDQoNCmdnLnRhbmdsZSA8LSBnZ3Bsb3QodGFuZ2xlLCBhZXMoeD1mYWN0b3IodHJlZSwgbGV2ZWxzPWMoIm1pY3JvYmUiLCAiaG9zdCIpKSwgeT15LCBncm91cD1hc3NvYywgY29sb3VyPWFzLmZhY3RvcihzaWduaWYpKSkgKw0KICAgIGdlb21fbGluZShhbHBoYT0wLjgpICsgIA0KICBnZW9tX3RleHQoZGF0YSA9IHRhbmdsZSAlPiUgDQogICAgICAgICAgICAgIGZpbHRlcih0cmVlPT0iaG9zdCIpLA0KICAgICAgICAgICAgYWVzKGxhYmVsPWxhYmVsKSxzdGF0ID0gJ3VuaXF1ZScsIGhqdXN0PTAsIGNoZWNrX292ZXJsYXAgPSBUUlVFKSsNCiAgZ2VvbV90ZXh0KGRhdGEgPSB0YW5nbGUgJT4lIA0KICAgICAgICAgICAgICBmaWx0ZXIodHJlZT09Im1pY3JvYmUiKSwNCiAgICAgICAgICAgIGFlcyhsYWJlbD1sYWJlbCksc3RhdCA9ICd1bmlxdWUnLCBoanVzdD0xLCBjaGVja19vdmVybGFwID0gVFJVRSkrDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoIk5TIj0ic3RlZWxibHVlIiwgInBhY28iPSJkYXJrb3JhbmdlMSIsICJwYXJhIj0iI2RhMmI5MSIsICJib3RoIj0iIzkxZGEyYiIpLCBuYS50cmFuc2xhdGU9RkFMU0UpKw0KICAgIHNjYWxlX3hfZGlzY3JldGUoZXhwYW5kID0gZXhwYW5zaW9uKGFkZD1jKDAuOCwwLjgpKSkgKyANCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kPWMoMC4wMDUsMC4wMDUpKSsNCiAgICB0aGVtZV92b2lkKCkgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArDQogIGxhYnMoY29sb3VyPSJTaWduaWZpY2FuY2UiKQ0KDQpnZy5wbGFudF90YW5nbGUgPC0gcDIgKyBnZy50YW5nbGUgKyAocDEgKyBzY2FsZV94X3JldmVyc2UoKSkgIysgcGxvdF9sYXlvdXQod2lkdGhzID0gYygyLCAxLCAyKSkNCmdnLnBsYW50X3RhbmdsZQ0KDQpwZGYoZmlsZT0iZmlncy9wbGFudF90YW5nbGVncmFtLnBkZiIsICB3aWR0aCA9IDgsIGhlaWdodCA9IDExLCBwYXBlcj0iYTQiKQ0KICBwbG90KGdnLnBsYW50X3RhbmdsZSkNCnRyeShkZXYub2ZmKCksIHNpbGVudD1UUlVFKQ0KYGBgDQoNCiMgUG93ZWxsaWENCg0KIyMgUG93ZWxsaWEgfiBDYXJzb25lbGxhDQoNCmBgYHtyIFBvd2VsbGlhIGNhcnNvbmVsbGF9DQojRmxhZyB0b3AgYWJ1bmRhbmNlIGNhcnNvbmVsbGEgYnkgc2FtcGxlDQp0b3BfY2Fyc29uIDwtIHBzMiAlPiUNCiAgdHJhbnNmb3JtX3NhbXBsZV9jb3VudHMoZnVuY3Rpb24gKHgpIHgvc3VtKHgpKSAlPiUNCiAgc3BlZWR5c2VxOjpwc21lbHQoKSAlPiUNCiAgZmlsdGVyKHBzeWxsaWRfc3BwICVpbiUgcHJ1bmVkLnRyZWUkdGlwLmxhYmVsKSAlPiUNCiAgZmlsdGVyKGdlbnVzPT0iQ2FuZGlkYXR1cyBDYXJzb25lbGxhIikgJT4lDQogIGdyb3VwX2J5KFNhbXBsZSkgJT4lICANCiAgZmlsdGVyKEFidW5kYW5jZSA+IDApICU+JQ0KICB0b3BfbigxLCB3dD1BYnVuZGFuY2UpICU+JQ0KICBtdXRhdGUodG9wID0gVFJVRSkgDQoNCmNvb2N1ciA8LSBwczIgJT4lDQogIHN1YnNldF9zYW1wbGVzKHBzeWxsaWRfZ2VudXMgPT0gIlBvd2VsbGlhIikgJT4lDQogIGZpbHRlcl90YXhhKGZ1bmN0aW9uKHgpIG1lYW4oeCkgPiAwLCBUUlVFKSAlPiUNCiAgdHJhbnNmb3JtX3NhbXBsZV9jb3VudHMoZnVuY3Rpb24gKHgpIHgvc3VtKHgpKSAlPiUNCiAgc3BlZWR5c2VxOjpwc21lbHQoKSAlPiUNCiAgZmlsdGVyKHBzeWxsaWRfc3BwICVpbiUgcHJ1bmVkLnRyZWUkdGlwLmxhYmVsKSAlPiUNCiAgbGVmdF9qb2luKHRvcF9jYXJzb24pICU+JQ0KICBmaWx0ZXIoZ2VudXM9PSJDYW5kaWRhdHVzIENhcnNvbmVsbGEiLCB0b3A9PVRSVUUpICU+JQ0KICBkcGx5cjo6c2VsZWN0KE9UVSwgcHN5bGxpZF9zcHAsIFNhbXBsZUlELCBBYnVuZGFuY2UpICU+JQ0KICBtdXRhdGUoT1RVID0gT1RVICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIikpICU+JQ0KICBkcGx5cjo6Z3JvdXBfYnkoT1RVLCBwc3lsbGlkX3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKEFidW5kYW5jZSA9IHN1bShBYnVuZGFuY2UpKSAlPiUNCiAgcGl2b3Rfd2lkZXIoaWRfY29scyA9IHBzeWxsaWRfc3BwLA0KICAgICAgICAgICAgICBuYW1lc19mcm9tID0gT1RVLA0KICAgICAgICAgICAgICB2YWx1ZXNfZnJvbT1BYnVuZGFuY2UsDQogICAgICAgICAgICAgIHZhbHVlc19maWxsID0gbGlzdChBYnVuZGFuY2UgPSAwKSkgICU+JQ0KICBjb2x1bW5fdG9fcm93bmFtZXMoInBzeWxsaWRfc3BwIikgJT4lDQogICAgYXMubWF0cml4KCkgJT4lIA0KICBhcHBseSgyLCBmdW5jdGlvbih4KSBpZmVsc2UoeCA+IDAsIDEsIDApKSANCg0KIyBIIGNvcGhlbmV0aWMgZGlzdGFuY2UNCmhfdHJlZSA8LSBwcnVuZWQudHJlZQ0KaF90cmVlJHRpcC5sYWJlbCA8LSBoX3RyZWUkdGlwLmxhYmVsICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIikNCmhfdHJlZSA8LSBkcm9wLnRpcChoX3RyZWUsIHNldGRpZmYoaF90cmVlJHRpcC5sYWJlbCwgcm93bmFtZXMoY29vY3VyKSkpDQpoX2Rpc3QgPC0gc3FydChjb3BoZW5ldGljKGhfdHJlZSkpDQoNCiMgUCBjb3BoZW5ldGljIGRpc3RhbmNlDQpzX3RyZWUgPC0gcGh5X3RyZWUocHMzKQ0Kc190cmVlJHRpcC5sYWJlbCA8LSBzX3RyZWUkdGlwLmxhYmVsICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIikNCnNfdHJlZSA8LSBkcm9wLnRpcChzX3RyZWUsIHNldGRpZmYoc190cmVlJHRpcC5sYWJlbCwgY29sbmFtZXMoY29vY3VyKSkpDQpzX2Rpc3QgPC0gc3FydChjb3BoZW5ldGljKHNfdHJlZSkvMWUrNiApICMjY29udmVydCB0byBNeWEgc28gaW50ZWdlcnMgYXJlIHNtYWxsIGVub3VnaCBmb3IgUEFDTw0KDQpjb29jdXIgPC0gY29vY3VyW2hfdHJlZSR0aXAubGFiZWwsIHNfdHJlZSR0aXAubGFiZWxdDQoNCiMgcHJlcGFyZSBwYWNvIGRhdGENCkQgPC0gcHJlcGFyZV9wYWNvX2RhdGEoSD1oX2Rpc3QsIFA9c19kaXN0LCBIUD1jb29jdXIpDQojIEFkZCBwY29yZA0KRCA8LSBhZGRfcGNvb3JkKEQsIGNvcnJlY3Rpb249J25vbmUnKSANCg0KcF9ob3N0IDwtIGdncGxvdChEJEhfUENvWyxjKCdBeGlzLjEnLCAnQXhpcy4yJyldICU+JSBhcy5kYXRhLmZyYW1lLCBhZXMoQXhpcy4xLCBBeGlzLjIpKSArDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICB0aGVtZV9idygpICsNCiAgZ2d0aXRsZSgiSCBQQ0EiKQ0KDQpwX3BhcmEgPC0gZ2dwbG90KEQkUF9QQ29bLGMoJ0F4aXMuMScsICdBeGlzLjInKV0gJT4lIGFzLmRhdGEuZnJhbWUsIGFlcyhBeGlzLjEsIEF4aXMuMikpICArDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICB0aGVtZV9idygpKw0KICBnZ3RpdGxlKCJQIFBDQSIpDQogICAgDQpwbG90KHBfaG9zdCArIHBfcGFyYSkNCg0KIyBydW4gcGFjbw0KcGFjb19ydW4gPC0gUEFDbyhELCBucGVybT05OTksIHNlZWQ9OTA5LCBtZXRob2Q9J3F1YXNpc3dhcCcsIHN5bW1ldHJpYz1GQUxTRSkNCg0KI3ByaW50IG92ZXJhbGwgc2lnbmlmaWNhbmNlDQpwcmludChwYWNvX3J1biRnb2YpDQoNCiMgR2V0IGludGVyYWN0aW9uLXNwZWNpZmljIGNvcGh5bG9nZW5ldGljIGNvbnRyaWJ1dGlvbnMgdXNpbmcgamFja25pZmluZw0KcGFjb19ydW4gPC0gcGFjb19saW5rcyhwYWNvX3J1bikNCmxpbmtzIDwtIGRhdGEuZnJhbWUoam9pbnQ9bmFtZXMocGFjb19ydW4kamFja2tuaWZlKSwgDQogICAgICAgICAgICAgICAgICAgIHZhbHVlcz11bm5hbWUocGFjb19ydW4kamFja2tuaWZlKSMsIA0KICAgICAgICAgICAgICAgICAgICAjdXBwZXI9dW5uYW1lKHBhY29fcnVuJGphY2trbmlmZSR1cHBlcikNCiAgICAgICAgICAgICAgICAgICAgKSAlPiUNCiAgbXV0YXRlKE9UVSA9IGpvaW50KSAlPiUNCiBzZXBhcmF0ZShPVFUsIGludG89YygiU2FtcGxlX05hbWUiLCAiT1RVIiksIHNlcD0iLSIsIGV4dHJhPSJtZXJnZSIpICU+JQ0KICBtdXRhdGUoc2lnbmlmID0gY2FzZV93aGVuKA0KICAgIHZhbHVlcyA8IG1lYW4oLiR2YWx1ZXMpIH4gMSwNCiAgICB2YWx1ZXMgPiBtZWFuKC4kdmFsdWVzKSB+IDANCiAgKSkNCg0KIyBQbG90IGxpbmtzDQpsaW5rcyAlPiUNCiAgZHBseXI6OnJlbmFtZShsYWJlbCA9IGpvaW50KSAlPiUNCiAgbXV0YXRlKGxhYmVsID0gYXMuZmFjdG9yKGxhYmVsKSwNCiAgICAgICAgIGxhYmVsPWZjdF9yZW9yZGVyKGxhYmVsLCB2YWx1ZXMsIHN1bSkpJT4lDQogIGFycmFuZ2UoLXZhbHVlcykgJT4lDQogIGdncGxvdChhZXMoeD1sYWJlbCwgeT12YWx1ZXMsIGNvbG91cj1hcy5mYWN0b3Ioc2lnbmlmKSkpICsNCiAgZ2VvbV9wb2ludChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogICNnZW9tX2Vycm9yYmFyKGFlcyh5bWluPXZhbHVlcywgeW1heD11cHBlcikpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbWVhbihsaW5rcyR2YWx1ZXMpKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9Yygic3RlZWxibHVlIiwgImRhcmtvcmFuZ2UxIikpICsNCiAgdGhlbWVfY2xhc3NpYygpKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpDQoNCmRpci5jcmVhdGUoIm91dHB1dC9jb3BoeWxvZ2VueS90cmlvemFfY2Fyc29uZWxsYSIpDQp3cml0ZV9jc3YobGlua3MsICJvdXRwdXQvY29waHlsb2dlbnkvdHJpb3phX2NhcnNvbmVsbGEvdHJpb3phX2NhcnNvbmVsbGFfbGlua3MuY3N2IikNCndyaXRlX2NzdihsaW5rcyAlPiUgDQogICAgZ3JvdXBfYnkoT1RVKSAlPiUNCiAgICBzdW1tYXJpc2UodmFsdWVzID0gbWVhbih2YWx1ZXMpKSwgIm91dHB1dC9jb3BoeWxvZ2VueS90cmlvemFfY2Fyc29uZWxsYS9jYXJzb25lbGxhX3dlaWdodHMuY3N2IikNCndyaXRlX2NzdihsaW5rcyAlPiUgDQogICAgZ3JvdXBfYnkoU2FtcGxlX05hbWUpICU+JQ0KICAgIHN1bW1hcmlzZSh2YWx1ZXMgPSBtZWFuKHZhbHVlcykpLCAib3V0cHV0L2NvcGh5bG9nZW55L3RyaW96YV9jYXJzb25lbGxhL3BzeWxsaWRfd2VpZ2h0cy5jc3YiKQ0KDQojR2V0IG9ic2VydmVkIHJlc2lkdWFscyBvZiBQcm9jcnVzdGVhbiBzdXBlcmltcG9zaXRpb24gDQpwYWNvX3Jlc2lkdWFscyA8LSByZXNpZHVhbHNfcGFjbyhwYWNvX3J1biRwcm9jLCB0eXBlID0gImludGVyYWN0aW9uIikNCiMgVmlzdWFsaXNlIHJlc2lkdWFscw0KcmVzIDwtIGRhdGEuZnJhbWUoT1RVPW5hbWVzKHBhY29fcmVzaWR1YWxzKSwgdmFsdWVzPXVubmFtZShwYWNvX3Jlc2lkdWFscykpICU+JQ0KICBzZXBhcmF0ZShPVFUsIGludG89YygiU2FtcGxlX05hbWUiLCAiT1RVIiksIHNlcD0iLSIsIGV4dHJhPSJtZXJnZSIpIA0KDQpnZ3Bsb3QocmVzLCBhZXMoeD12YWx1ZXMpKSsNCiAgZ2VvbV9kZW5zaXR5KGZpbGw9J2dyZXk3MCcpKw0KICB0aGVtZV9idygpKw0KICB4bGFiKCdQcm9jcnVzdGVzIHJlc2lkdWFscycpKw0KICB5bGFiKCdGcmVxdWVuY3knKQ0KDQojIFBhcmFmaXQgcnVuDQpQRl9ydW4gPC0gcGFyYWZpdChoX2Rpc3QsIHNfZGlzdCwgdChjb29jdXIpLCBucGVybT05OTksIHRlc3QubGlua3M9VFJVRSwgc2lsZW50PUZBTFNFKQ0KUEZfcnVuJFBhcmFGaXRHbG9iYWwNClBGX3J1biRwLmdsb2JhbA0KDQpQRl9saW5rcyA8LSBhcy5kYXRhLmZyYW1lKFBGX3J1biRsaW5rLnRhYmxlKSAgJT4lDQogICAgICBsZWZ0X2pvaW4oZW5mcmFtZShuYW1lcyhQRl9ydW4kcGFyYS5wZXIuaG9zdCksIG5hbWUgPSAiSG9zdCIsIHZhbHVlPSJwc3lsbGlkX3NwcCIpICU+JQ0KICAgICAgICAgICAgICAgICAgbXV0YXRlKEhvc3QgPSBhcy5udW1lcmljKEhvc3QpKSkgJT4lDQogICAgICBsZWZ0X2pvaW4oZW5mcmFtZShuYW1lcyhQRl9ydW4kaG9zdC5wZXIucGFyYSksIG5hbWUgPSAiUGFyYXNpdGUiLCB2YWx1ZT0iT1RVIikgJT4lDQogICAgICAgICAgICAgICAgICBtdXRhdGUoUGFyYXNpdGUgPSBhcy5udW1lcmljKFBhcmFzaXRlKSkpJT4lDQogIG11dGF0ZShzaWduaWYgPSBjYXNlX3doZW4oDQogICAgcC5GMSA8IDAuMDUgfiAxLA0KICAgIHAuRjEgPiAwLjA1IH4gMA0KICApKSANCg0KIyBDb3BoeWxvcGxvdA0KY29vY3VyLmx1dCA8LSB3aGljaChjb29jdXIgPT0xLCBhcnIuaW5kPVRSVUUpDQphc3NvYyA8LSBjYmluZChyb3duYW1lcyhjb29jdXIpW2Nvb2N1ci5sdXRbLDFdXSwgY29sbmFtZXMoY29vY3VyKVtjb29jdXIubHV0WywyXV0pDQoNCiMgUm90YXRlIHRoZSBub2RlcyB1c2luZyBwaHl0b29scw0Kb2JqIDwtIGNvcGh5bG8odHIxPWhfdHJlZSwgdHIyPXNfdHJlZSwgYXNzb2M9YXNzb2MsIHJvdGF0ZT1UUlVFKSANCg0KIyBwc3lsbGlkX3RyZWUNCnRyZWUxIDwtIG9ialtbInRyZWVzIl1dW1sxXV0NCg0KcDEgPC0gZ2d0cmVlKHRyZWUxKQ0KYXRtZXRvX25vZGUgPC0gcDEkZGF0YSAlPiUgDQogIGZpbHRlcihzdHJfZGV0ZWN0KGxhYmVsLCAiQXRtZXRvIikpICU+JQ0KICBwdWxsKG5vZGUpDQpyb290X25vZGUgPC0gcDEkZGF0YSAlPiUgDQogIGZpbHRlcihzdHJfZGV0ZWN0KGxhYmVsLCAiQXRtZXRvIikpICU+JQ0KICBwdWxsKHBhcmVudCkNCg0KdHJlZTEgPC0gZ3JvdXBPVFUodHJlZTEsIC5ub2RlPWF0bWV0b19ub2RlKSAjIE1ha2UgdGhlIGF0bWV0byBkb3R0ZWQgdG8gaW5kaWNhdGUgb3V0Z3JvdXAgd2FzIHJlc2NhbGVkDQoNCnAxIDwtIGdndHJlZSh0cmVlMSwgbGFkZGVyaXplPUZBTFNFLCBhZXMoY29sb3VyPWxpbmtzLGxpbmV0eXBlPWdyb3VwKSkNCndlaWdodHNfcDEgPC0gcDEkZGF0YSAlPiUNCiAgbGVmdF9qb2luKGxpbmtzICU+JSANCiAgICBncm91cF9ieShTYW1wbGVfTmFtZSkgJT4lDQogICAgc3VtbWFyaXNlKFBBX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IFNhbXBsZV9OYW1lKQ0KICAgICklPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICBncm91cF9ieShwc3lsbGlkX3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKFBGX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IHBzeWxsaWRfc3BwKQ0KICAgICkgJT4lDQogIG11dGF0ZSh2YWx1ZXMgPSBQQV92YWx1ZXMgKyBQRl92YWx1ZXMpDQoNCiNTY2FsZSB0aGUgYXRtZXRvY3Jhbml1bSBhbmQgcm9vdCBub2RlcyB0byBiZSBzaG9ydGVyDQpwMSRkYXRhW3AxJGRhdGEkbm9kZSAlaW4lIGF0bWV0b19ub2RlLCAieCJdIDwtIG1heChwMSRkYXRhJHgpDQpwMSRkYXRhW3AxJGRhdGEkbm9kZSAlaW4lIHJvb3Rfbm9kZSwgIngiXSA8LSAwLjIgI3Jvb3QNCg0KcDEkZGF0YSRub2RlW3AxJGRhdGEkbm9kZV0NCg0KIyMgR2V0IHZhbHVlcyBmb3IgaGlnaGVyIG5vZGVzDQp3ZWlnaHRzX3AxIDwtIHdlaWdodHNfcDEgJT4lDQogIGxlZnRfam9pbihkYXRhLmZyYW1lKG5vZGU9d2VpZ2h0c19wMSRub2RlLCBsaW5rcyA9IHdlaWdodHNfcDEkbm9kZSAlPiUgcHVycnI6Om1hcF9kYmwoYXZlcmFnZV9kZXNjZW5kYW50cywgdHJlZT10cmVlMSwgZGY9d2VpZ2h0c19wMSkpKQ0KcDEgPC0gcDEgJTwrJSB3ZWlnaHRzX3AxICsgZ2VvbV90aXBwb2ludChhZXMoY29sb3VyPWxpbmtzKSkgKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3c9InN0ZWVsYmx1ZSIsIGhpZ2g9ImRhcmtvcmFuZ2UxIikgICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQojIE9UVSB0cmVlDQp0cmVlMiA8LSBvYmpbWyJ0cmVlcyJdXVtbMl1dDQpzX3RyZWUgPC0gZHJvcC50aXAodHJlZTIsIHNldGRpZmYodHJlZTIkdGlwLmxhYmVsLCBvYmokYXNzb2NbLDJdKSkNCnAyIDwtIGdndHJlZSh0cmVlMiAsIGxhZGRlcml6ZT1UUlVFLCBhZXMoY29sb3VyPWxpbmtzKSkgDQp3ZWlnaHRzX3AyIDwtIHAyJGRhdGEgJT4lDQogIGxlZnRfam9pbihsaW5rcyAlPiUgDQogICAgZ3JvdXBfYnkoT1RVKSAlPiUNCiAgICBzdW1tYXJpc2UoUEFfdmFsdWVzID0gbWVhbihzaWduaWYpKSAlPiUNCiAgICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gT1RVKQ0KICAgICklPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICBncm91cF9ieShPVFUpICU+JQ0KICAgIHN1bW1hcmlzZShQRl92YWx1ZXMgPSBtZWFuKHNpZ25pZikpICU+JQ0KICAgIGRwbHlyOjpyZW5hbWUobGFiZWwgPSBPVFUpDQogICAgKSAlPiUNCiAgbXV0YXRlKHZhbHVlcyA9IFBBX3ZhbHVlcyArIFBGX3ZhbHVlcykNCg0Kd2VpZ2h0c19wMiA8LSB3ZWlnaHRzX3AyICU+JQ0KICBsZWZ0X2pvaW4oZGF0YS5mcmFtZShub2RlPXdlaWdodHNfcDIkbm9kZSwgbGlua3MgPSB3ZWlnaHRzX3AyJG5vZGUgJT4lIHB1cnJyOjptYXBfZGJsKGF2ZXJhZ2VfZGVzY2VuZGFudHMsIHRyZWU9dHJlZTIsIGRmPXdlaWdodHNfcDIpKSkNCg0KcDIgPC0gcDIgJTwrJSB3ZWlnaHRzX3AyICArIGdlb21fdGlwcG9pbnQoYWVzKGNvbG91cj1saW5rcykpICsgDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0ic3RlZWxibHVlIiwgaGlnaD0iZGFya29yYW5nZTEiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KIyBUYW5nbGVncmFtIA0KdGFuZ2xlIDwtIG9iaiRhc3NvYyAlPiUNCiAgYXNfZGF0YV9mcmFtZSgpICU+JQ0KICBtYWdyaXR0cjo6c2V0X2NvbG5hbWVzKGMoImxhYmVsLngiLCAibGFiZWwueSIpKSAlPiUNCiAgbGVmdF9qb2luKGxpbmtzICU+JSANCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChsYWJlbC54ID0gU2FtcGxlX05hbWUsIGxhYmVsLnkgPSBPVFUsIHNpZ25pZl9wYWNvPXNpZ25pZikpICU+JQ0KICBsZWZ0X2pvaW4oUEZfbGlua3MgJT4lIA0KICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGxhYmVsLnggPSBwc3lsbGlkX3NwcCwgbGFiZWwueSA9IE9UVSwgc2lnbmlmX3BhcmE9c2lnbmlmKSkgJT4lDQogIGxlZnRfam9pbihwMSRkYXRhICU+JSBkcGx5cjo6c2VsZWN0KGxhYmVsLCB5KSAlPiUgZHBseXI6OnJlbmFtZShsYWJlbC54ID0gbGFiZWwpLCBieT0ibGFiZWwueCIpICU+JQ0KICBsZWZ0X2pvaW4ocDIkZGF0YSAlPiUgZHBseXI6OnNlbGVjdChsYWJlbCwgeSkgJT4lIGRwbHlyOjpyZW5hbWUobGFiZWwueSA9IGxhYmVsKSwgYnk9ImxhYmVsLnkiKSAlPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCJhc3NvYyIpICU+JQ0KICByZW5hbWVfYWxsKH5zdHJfcmVwbGFjZSgueCxwYXR0ZXJuPSJcXC4iLCByZXBsYWNlbWVudD0iXyIpKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKGVuZHNfd2l0aChjKCJfeCIsICJfeSIpKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvPWMoIi52YWx1ZSIsICJ0cmVlIiksIA0KICAgICAgICAgICAgICAgbmFtZXNfc2VwID0gIl8iDQogICAgICAgICAgICAgICkgICU+JQ0KICBtdXRhdGUoc2lnbmlmX3BhY28gPSByZXBsYWNlX25hKHNpZ25pZl9wYWNvLCAwKSwNCiAgICAgICAgIHNpZ25pZl9wYXJhID0gcmVwbGFjZV9uYShzaWduaWZfcGFyYSwgMCkpICU+JQ0KICBtdXRhdGUoc2lnbmlmID0gY2FzZV93aGVuKA0KICAgIHNpZ25pZl9wYWNvPT0wICYgc2lnbmlmX3BhcmE9PTAgIH4gIk5TIiwNCiAgICBzaWduaWZfcGFjbz09MCAmIHNpZ25pZl9wYXJhPT0xIH4gInBhcmEiLA0KICAgIHNpZ25pZl9wYWNvPT0xICYgc2lnbmlmX3BhcmE9PTAgfiAicGFjbyIsDQogICAgc2lnbmlmX3BhY289PTEgJiBzaWduaWZfcGFyYT09MSB+ICJib3RoIg0KICApKSAlPiUNCiAgbXV0YXRlKHRyZWUgPSB0cmVlICU+JSANCiAgICAgICAgICAgc3RyX3JlcGxhY2UoIngiLCAiaG9zdCIpJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlKCJ5IiwgIm1pY3JvYmUiKSkgJT4lDQogIGZpbHRlcighaXMubmEobGFiZWwpKSU+JSANCiAgZ3JvdXBfYnkodHJlZSkgJT4lDQogIG11dGF0ZSh5ID0geSAvIG1heCh5KSklPiUNCiAgbXV0YXRlKGxhYmVsID0gbGFiZWwgJT4lIHN0cl9yZXBsYWNlX2FsbCgiXyIsICIgIikpIA0KDQoNCmdnLnRhbmdsZSA8LSBnZ3Bsb3QodGFuZ2xlLCBhZXMoeD10cmVlLCB5PXksIGdyb3VwPWFzc29jLCBjb2xvdXI9YXMuZmFjdG9yKHNpZ25pZikpKSArDQogICAgZ2VvbV9saW5lKGFscGhhPTAuOCkgKyAgDQogIGdlb21fdGV4dChkYXRhID0gdGFuZ2xlICU+JSANCiAgICAgICAgICAgICAgZmlsdGVyKHRyZWU9PSJob3N0IiksDQogICAgICAgICAgICBhZXMobGFiZWw9bGFiZWwpLHN0YXQgPSAndW5pcXVlJywgaGp1c3Q9MSwgY2hlY2tfb3ZlcmxhcCA9IFRSVUUpKw0KICBnZW9tX3RleHQoZGF0YSA9IHRhbmdsZSAlPiUgDQogICAgICAgICAgICAgIGZpbHRlcih0cmVlPT0ibWljcm9iZSIpLA0KICAgICAgICAgICAgYWVzKGxhYmVsPWxhYmVsKSxzdGF0ID0gJ3VuaXF1ZScsIGhqdXN0PTAsIGNoZWNrX292ZXJsYXAgPSBUUlVFKSsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygiTlMiPSJzdGVlbGJsdWUiLCAicGFjbyI9ImRhcmtvcmFuZ2UxIiwgInBhcmEiPSIjZGEyYjkxIiwgImJvdGgiPSIjOTFkYTJiIiksIG5hLnRyYW5zbGF0ZT1GQUxTRSkrDQogICAgc2NhbGVfeF9kaXNjcmV0ZShleHBhbmQgPSBleHBhbnNpb24oYWRkPWMoMC44LDAuOCkpKSArIA0KICAgIHRoZW1lX3ZvaWQoKSArDQogICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZD1jKDAuMDA1LDAuMDA1KSkrDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsNCiAgbGFicyhjb2xvdXI9IlNpZ25pZmljYW5jZToiKQ0KDQpnZy50cmlvemFfY2Fyc29uX3RhbmdsZSA8LSBwMSArIGdnLnRhbmdsZSArIChwMiArIHNjYWxlX3hfcmV2ZXJzZSgpKSANCmdnLnRyaW96YV9jYXJzb25fdGFuZ2xlDQoNCnBkZihmaWxlPSJmaWdzL3Bvd2VsbGlhX2NhcnNvbmVsbGFfdGFuZ2xlZ3JhbS5wZGYiLCAgd2lkdGggPSA4LCBoZWlnaHQgPSAxMSwgcGFwZXI9ImE0IikNCiAgcGxvdChnZy50cmlvemFfY2Fyc29uX3RhbmdsZSkNCnRyeShkZXYub2ZmKCksIHNpbGVudD1UUlVFKQ0KYGBgDQoNCiMjIFBvd2VsbGlhIH4gaG9zdHBsYW50DQoNCmBgYHtyIHRyaW96YSBob3N0cGxhbnQgcHN5bGxpZH0NCnBsYW50LnRyZWUgPC0gcmVhZC50cmVlKCJzYW1wbGVfZGF0YS9wbGFudF90cmVlLm53ayIpDQpwbGFudC50cmVlICA8LSBkcm9wLnRpcChwbGFudC50cmVlICwgIlNvcGhvcmFfbWljcm9waHlsbGFfLWtvd2hhaSIgKQ0KDQojUHJlcGFyZSBjby1vY2N1cmFuY2UgbWF0cml4DQpjb29jdXIgPC0gc2FtcGxlX2RhdGEocHMyKSAlPiUNCiAgYXNfdGliYmxlKCkgJT4lDQogIGZpbHRlcihwc3lsbGlkX2dlbnVzID09ICJQb3dlbGxpYSIpICU+JQ0KICBkcGx5cjo6c2VsZWN0KHBzeWxsaWRfc3BwLCBob3N0cGxhbnRfc3BwKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShwc3lsbGlkX3NwcCkpICU+JQ0KICB1bmlxdWUoKSAlPiUNCiAgbXV0YXRlKHBzeWxsaWRfc3BwID0gcHN5bGxpZF9zcHAgJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiIHwtIiwgIl8iKSwNCiAgICAgICAgIGhvc3RwbGFudF9zcHAgPSBob3N0cGxhbnRfc3BwICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIiksDQogICAgICAgICBwcmVzZW5jZSA9IDENCiAgICAgICAgICkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSAiaG9zdHBsYW50X3NwcCIsDQogICAgICAgICAgICAgIHZhbHVlc19mcm9tPSJwcmVzZW5jZSIsDQogICAgICAgICAgICAgIHZhbHVlc19maWxsID0gbGlzdChwcmVzZW5jZSA9IDApKSAlPiUNCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJwc3lsbGlkX3NwcCIpICU+JQ0KICB0KCkNCg0KIyBIIGNvcGhlbmV0aWMgZGlzdGFuY2UNCmhfdHJlZSA8LSBwbGFudC50cmVlDQpoX3RyZWUkdGlwLmxhYmVsIDwtIGhfdHJlZSR0aXAubGFiZWwgJT4lDQogICAgICAgICAgIHN0cl9yZXBsYWNlX2FsbCgiIHwtIiwgIl8iKQ0KaF90cmVlIDwtIGRyb3AudGlwKGhfdHJlZSwgc2V0ZGlmZihoX3RyZWUkdGlwLmxhYmVsLCByb3duYW1lcyhjb29jdXIpKSkNCmhfZGlzdCA8LSBzcXJ0KGNvcGhlbmV0aWMoaF90cmVlKSkNCg0KIyBQIGNvcGhlbmV0aWMgZGlzdGFuY2UNCnNfdHJlZSA8LSBwcnVuZWQudHJlZQ0Kc190cmVlJHRpcC5sYWJlbCA8LSBzX3RyZWUkdGlwLmxhYmVsICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZV9hbGwoIiB8LSIsICJfIikNCnNfdHJlZSA8LSBkcm9wLnRpcChzX3RyZWUsIHNldGRpZmYoc190cmVlJHRpcC5sYWJlbCwgY29sbmFtZXMoY29vY3VyKSkpDQpzX2Rpc3QgPC0gc3FydChjb3BoZW5ldGljKHNfdHJlZSkpDQoNCmNvb2N1ciA8LSBjb29jdXJbaF90cmVlJHRpcC5sYWJlbCwgc190cmVlJHRpcC5sYWJlbF0NCiMgcHJlcGFyZSBwYWNvIGRhdGENCkQgPC0gcHJlcGFyZV9wYWNvX2RhdGEoSD1oX2Rpc3QsIFA9c19kaXN0LCBIUD1jb29jdXIpDQojIEFkZCBwY29yZA0KRCA8LSBhZGRfcGNvb3JkKEQsIGNvcnJlY3Rpb249J25vbmUnKSAgDQoNCnBfaG9zdCA8LSBnZ3Bsb3QoRCRIX1BDb1ssYygnQXhpcy4xJywgJ0F4aXMuMicpXSAlPiUgYXMuZGF0YS5mcmFtZSwgYWVzKEF4aXMuMSwgQXhpcy4yKSkgKw0KICAgIGdlb21fcG9pbnQoKSArDQogICAgdGhlbWVfYncoKSArDQogIGdndGl0bGUoIkggUENBIikNCg0KcF9wYXJhIDwtIGdncGxvdChEJFBfUENvWyxjKCdBeGlzLjEnLCAnQXhpcy4yJyldICU+JSBhcy5kYXRhLmZyYW1lLCBhZXMoQXhpcy4xLCBBeGlzLjIpKSAgKw0KICAgIGdlb21fcG9pbnQoKSArDQogICAgdGhlbWVfYncoKSsNCiAgZ2d0aXRsZSgiUCBQQ0EiKQ0KICAgIA0KcGxvdChwX2hvc3QgKyBwX3BhcmEpDQoNCiMgcnVuIHBhY28NCnBhY29fcnVuIDwtIFBBQ28oRCwgbnBlcm09OTk5LCBzZWVkPTkwOSwgbWV0aG9kPSdxdWFzaXN3YXAnLCBzeW1tZXRyaWM9RkFMU0UpICNTeW1ldHJpYyAtIGlzIG9uZSBtZWFudCB0byB0cmFjayB0aGUgZXZvbHV0aW9uIG9mIGFub3RoZXI/DQoNCiNwcmludCBvdmVyYWxsIHNpZ25pZmljYW5jZQ0KcHJpbnQocGFjb19ydW4kZ29mKQ0KDQojIFByb2NydXN0ZXMgZGlhZ25vc3RpYyBwbG90cw0KcGxvdChwYWNvX3J1biRwcm9jKQ0KcGxvdChwYWNvX3J1biRwcm9jLCBraW5kPTIpDQoNCiMgR2V0IGludGVyYWN0aW9uLXNwZWNpZmljIGNvcGh5bG9nZW5ldGljIGNvbnRyaWJ1dGlvbnMgdXNpbmcgamFja25pZmluZw0KcGFjb19ydW4gPC0gcGFjb19saW5rcyhwYWNvX3J1bikNCmxpbmtzIDwtIGRhdGEuZnJhbWUoDQogIGpvaW50PW5hbWVzKHBhY29fcnVuJGphY2trbmlmZSksDQogIHZhbHVlcz11bm5hbWUocGFjb19ydW4kamFja2tuaWZlKSMsDQogICN1cHBlcj11bm5hbWUocGFjb19ydW4kamFja2tuaWZlJHVwcGVyKQ0KICApICU+JQ0KIG11dGF0ZShPVFUgPSBqb2ludCkgJT4lDQogc2VwYXJhdGUoT1RVLCBpbnRvPWMoImhvc3RwbGFudF9zcHAiLCAicHN5bGxpZF9zcHAiKSwgc2VwPSItIiwgZXh0cmE9Im1lcmdlIikgJT4lDQogIG11dGF0ZShzaWduaWYgPSBjYXNlX3doZW4oDQogICAgdmFsdWVzIDwgbWVhbiguJHZhbHVlcykgfiAxLA0KICAgIHZhbHVlcyA+IG1lYW4oLiR2YWx1ZXMpIH4gMA0KICApKQ0KDQojIFBsb3QgbGlua3MNCmxpbmtzICU+JQ0KICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gam9pbnQpICU+JQ0KICBtdXRhdGUobGFiZWwgPSBhcy5mYWN0b3IobGFiZWwpLA0KICAgICAgICAgbGFiZWw9ZmN0X3Jlb3JkZXIobGFiZWwsIHZhbHVlcywgc3VtKSklPiUNCiAgYXJyYW5nZSgtdmFsdWVzKSAlPiUNCiAgZ2dwbG90KGFlcyh4PWxhYmVsLCB5PXZhbHVlcywgY29sb3VyPWFzLmZhY3RvcihzaWduaWYpKSkgKw0KICBnZW9tX3BvaW50KHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgI2dlb21fZXJyb3JiYXIoYWVzKHltaW49dmFsdWVzLCB5bWF4PXVwcGVyKSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBtZWFuKGxpbmtzJHZhbHVlcykpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJzdGVlbGJsdWUiLCAiZGFya29yYW5nZTEiKSkgKw0KICB0aGVtZV9jbGFzc2ljKCkrIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT00NSwgaGp1c3Q9MSkpDQoNCmRpci5jcmVhdGUoIm91dHB1dC9jb3BoeWxvZ2VueS90cmlvemFfaG9zdHBsYW50IikNCndyaXRlX2NzdihsaW5rcywgIm91dHB1dC9jb3BoeWxvZ2VueS90cmlvemFfaG9zdHBsYW50L3BzeWxsaWRfaG9zdHBsYW50X2xpbmtzLmNzdiIpDQp3cml0ZV9jc3YobGlua3MgJT4lIA0KICAgIGdyb3VwX2J5KHBzeWxsaWRfc3BwKSAlPiUNCiAgICBzdW1tYXJpc2UodmFsdWVzID0gbWVhbih2YWx1ZXMpKSwgIm91dHB1dC9jb3BoeWxvZ2VueS90cmlvemFfaG9zdHBsYW50L3BzeWxsaWRfd2VpZ2h0cy5jc3YiKQ0Kd3JpdGVfY3N2KGxpbmtzICU+JSANCiAgICBncm91cF9ieShob3N0cGxhbnRfc3BwKSAlPiUNCiAgICBzdW1tYXJpc2UodmFsdWVzID0gbWVhbih2YWx1ZXMpKSwgIm91dHB1dC9jb3BoeWxvZ2VueS90cmlvemFfaG9zdHBsYW50L2hvc3RwbGFudF93ZWlnaHRzLmNzdiIpDQoNCiNHZXQgb2JzZXJ2ZWQgcmVzaWR1YWxzIG9mIFByb2NydXN0ZWFuIHN1cGVyaW1wb3NpdGlvbiANCnBhY29fcmVzaWR1YWxzIDwtIHJlc2lkdWFsc19wYWNvKHBhY29fcnVuJHByb2MsIHR5cGUgPSAiaW50ZXJhY3Rpb24iKQ0KDQojIFZpc3VhbGlzZSByZXNpZHVhbHMNCnJlcyA8LSBkYXRhLmZyYW1lKE9UVT1uYW1lcyhwYWNvX3Jlc2lkdWFscyksIHZhbHVlcz11bm5hbWUocGFjb19yZXNpZHVhbHMpKSAlPiUNCiAgc2VwYXJhdGUoT1RVLCBpbnRvPWMoImhvc3RwbGFudF9zcHAiLCAicHN5bGxpZF9zcHAiKSwgc2VwPSItIiwgZXh0cmE9Im1lcmdlIikNCg0KZ2dwbG90KHJlcywgYWVzKHg9dmFsdWVzKSkrDQogIGdlb21fZGVuc2l0eShmaWxsPSdncmV5NzAnKSsNCiAgdGhlbWVfYncoKSsNCiAgeGxhYignUHJvY3J1c3RlcyByZXNpZHVhbHMnKSsNCiAgeWxhYignRnJlcXVlbmN5JykNCg0KIyBQYXJhZml0IHJ1bg0KUEZfcnVuIDwtIHBhcmFmaXQoaF9kaXN0LCBzX2Rpc3QsIHQoY29vY3VyKSwgbnBlcm09OTk5LCB0ZXN0LmxpbmtzPVRSVUUsIHNpbGVudD1UUlVFKQ0KUEZfcnVuJFBhcmFGaXRHbG9iYWwNClBGX3J1biRwLmdsb2JhbA0KDQoNClBGX2xpbmtzIDwtIGFzLmRhdGEuZnJhbWUoUEZfcnVuJGxpbmsudGFibGUpICAlPiUNCiAgICAgIGxlZnRfam9pbihlbmZyYW1lKG5hbWVzKFBGX3J1biRwYXJhLnBlci5ob3N0KSwgbmFtZSA9ICJIb3N0IiwgdmFsdWU9Imhvc3RwbGFudF9zcHAiKSAlPiUNCiAgICAgICAgICAgICAgICAgIG11dGF0ZShIb3N0ID0gYXMubnVtZXJpYyhIb3N0KSkpICU+JQ0KICAgICAgbGVmdF9qb2luKGVuZnJhbWUobmFtZXMoUEZfcnVuJGhvc3QucGVyLnBhcmEpLCBuYW1lID0gIlBhcmFzaXRlIiwgdmFsdWU9InBzeWxsaWRfc3BwIikgJT4lDQogICAgICAgICAgICAgICAgICBtdXRhdGUoUGFyYXNpdGUgPSBhcy5udW1lcmljKFBhcmFzaXRlKSkpJT4lDQogIG11dGF0ZShzaWduaWYgPSBjYXNlX3doZW4oDQogICAgcC5GMSA8IDAuMDUgfiAxLA0KICAgIHAuRjEgPiAwLjA1IH4gMA0KICApKSANCg0KIyBDb3BoeWxvcGxvdA0KY29vY3VyLmx1dCA8LSB3aGljaChjb29jdXIgPT0xLCBhcnIuaW5kPVRSVUUpDQphc3NvYyA8LSBjYmluZChyb3duYW1lcyhjb29jdXIpW2Nvb2N1ci5sdXRbLDFdXSwgY29sbmFtZXMoY29vY3VyKVtjb29jdXIubHV0WywyXV0pDQoNCiMgUm90YXRlIHRoZSBub2RlcyB1c2luZyBwaHl0b29scw0Kb2JqIDwtIGNvcGh5bG8odHIxPWhfdHJlZSwgdHIyPXNfdHJlZSwgYXNzb2M9YXNzb2MsIHJvdGF0ZT1UUlVFKSANCg0KIyBFeHRyYWN0IHRoZSBnb29kcyBmb3IgZ2d0cmVlDQojIHBsYW50IHRyZWUNCnRyZWUxIDwtIG9ialtbInRyZWVzIl1dW1sxXV0NCg0KcDEgPC0gZ2d0cmVlKHRyZWUxLCBsYWRkZXJpemU9RkFMU0UsIGFlcyhjb2xvdXI9bGlua3MpKQ0Kd2VpZ2h0c19wMSA8LSBwMSRkYXRhICU+JQ0KICBsZWZ0X2pvaW4obGlua3MgJT4lIA0KICAgIGdyb3VwX2J5KGhvc3RwbGFudF9zcHApICU+JQ0KICAgIHN1bW1hcmlzZShQQV92YWx1ZXMgPSBtZWFuKHNpZ25pZikpICU+JQ0KICAgIGRwbHlyOjpyZW5hbWUobGFiZWwgPSBob3N0cGxhbnRfc3BwKQ0KICAgICklPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICBncm91cF9ieShob3N0cGxhbnRfc3BwKSAlPiUNCiAgICBzdW1tYXJpc2UoUEZfdmFsdWVzID0gbWVhbihzaWduaWYpKSAlPiUNCiAgICBkcGx5cjo6cmVuYW1lKGxhYmVsID0gaG9zdHBsYW50X3NwcCkNCiAgICApICU+JQ0KICBtdXRhdGUodmFsdWVzID0gUEFfdmFsdWVzICsgUEZfdmFsdWVzKQ0KDQojIyBHZXQgdmFsdWVzIGZvciBoaWdoZXIgbm9kZXMNCndlaWdodHNfcDEgPC0gd2VpZ2h0c19wMSAlPiUNCiAgbGVmdF9qb2luKGRhdGEuZnJhbWUobm9kZT13ZWlnaHRzX3AxJG5vZGUsIGxpbmtzID0gd2VpZ2h0c19wMSRub2RlICU+JSBwdXJycjo6bWFwX2RibChhdmVyYWdlX2Rlc2NlbmRhbnRzLCB0cmVlPXRyZWUxLCBkZj13ZWlnaHRzX3AxKSkpDQpwMSA8LSBwMSAlPCslIHdlaWdodHNfcDEgKyBnZW9tX3RpcHBvaW50KGFlcyhjb2xvdXI9bGlua3MpKSArDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0ic3RlZWxibHVlIiwgaGlnaD0iZGFya29yYW5nZTEiKSAgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCiMgcHN5bGxpZF90cmVlDQp0cmVlMiA8LSBvYmpbWyJ0cmVlcyJdXVtbMl1dDQpzX3RyZWUgPC0gZHJvcC50aXAodHJlZTIsIHNldGRpZmYodHJlZTIkdGlwLmxhYmVsLCBvYmokYXNzb2NbLDJdKSkNCg0KcDIgPC0gZ2d0cmVlKHRyZWUyICwgbGFkZGVyaXplPVRSVUUsIGFlcyhjb2xvdXI9bGlua3MpKSANCndlaWdodHNfcDIgPC0gcDIkZGF0YSAlPiUNCiAgbGVmdF9qb2luKGxpbmtzICU+JSANCiAgICBncm91cF9ieShwc3lsbGlkX3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKFBBX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IHBzeWxsaWRfc3BwKQ0KICAgICklPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICBncm91cF9ieShwc3lsbGlkX3NwcCkgJT4lDQogICAgc3VtbWFyaXNlKFBGX3ZhbHVlcyA9IG1lYW4oc2lnbmlmKSkgJT4lDQogICAgZHBseXI6OnJlbmFtZShsYWJlbCA9IHBzeWxsaWRfc3BwKQ0KICAgICkgJT4lDQogIG11dGF0ZSh2YWx1ZXMgPSBQQV92YWx1ZXMgKyBQRl92YWx1ZXMpDQoNCiNTY2FsZSB0aGUgYXRtZXRvY3Jhbml1bSBhbmQgcm9vdCBub2RlcyB0byBiZSBzaG9ydGVyDQpwMiRkYXRhW3AyJGRhdGEkbm9kZSAlaW4lIGF0bWV0b19ub2RlLCAieCJdIDwtIG1heChwMiRkYXRhJHgpDQpwMiRkYXRhW3AyJGRhdGEkbm9kZSAlaW4lIHJvb3Rfbm9kZSwgIngiXSA8LSAwLjIgI3Jvb3QNCg0KcDIkZGF0YSRub2RlW3AyJGRhdGEkbm9kZV0NCg0Kd2VpZ2h0c19wMiA8LSB3ZWlnaHRzX3AyICU+JQ0KICBsZWZ0X2pvaW4oZGF0YS5mcmFtZShub2RlPXdlaWdodHNfcDIkbm9kZSwgbGlua3MgPSB3ZWlnaHRzX3AyJG5vZGUgJT4lIHB1cnJyOjptYXBfZGJsKGF2ZXJhZ2VfZGVzY2VuZGFudHMsIHRyZWU9dHJlZTIsIGRmPXdlaWdodHNfcDIpKSkNCg0KcDIgPC0gcDIgJTwrJSB3ZWlnaHRzX3AyICArIGdlb21fdGlwcG9pbnQoYWVzKGNvbG91cj1saW5rcykpICsgDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0ic3RlZWxibHVlIiwgaGlnaD0iZGFya29yYW5nZTEiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KIyBUYW5nbGVncmFtIA0KdGFuZ2xlIDwtIG9iaiRhc3NvYyAlPiUNCiAgYXNfZGF0YV9mcmFtZSgpICU+JQ0KICBtYWdyaXR0cjo6c2V0X2NvbG5hbWVzKGMoImxhYmVsLngiLCAibGFiZWwueSIpKSAlPiUNCiAgbGVmdF9qb2luKGxpbmtzICU+JSANCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChsYWJlbC54ID0gaG9zdHBsYW50X3NwcCwgbGFiZWwueSA9IHBzeWxsaWRfc3BwLCBzaWduaWZfcGFjbz1zaWduaWYpKSAlPiUNCiAgbGVmdF9qb2luKFBGX2xpbmtzICU+JSANCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChsYWJlbC54ID0gaG9zdHBsYW50X3NwcCwgbGFiZWwueSA9IHBzeWxsaWRfc3BwLCBzaWduaWZfcGFyYT1zaWduaWYpKSAlPiUNCiAgbGVmdF9qb2luKHAxJGRhdGEgJT4lIGRwbHlyOjpzZWxlY3QobGFiZWwsIHkpICU+JSBkcGx5cjo6cmVuYW1lKGxhYmVsLnggPSBsYWJlbCksIGJ5PSJsYWJlbC54IikgJT4lDQogIGxlZnRfam9pbihwMiRkYXRhICU+JSBkcGx5cjo6c2VsZWN0KGxhYmVsLCB5KSAlPiUgZHBseXI6OnJlbmFtZShsYWJlbC55ID0gbGFiZWwpLCBieT0ibGFiZWwueSIpICU+JQ0KICByb3duYW1lc190b19jb2x1bW4oImFzc29jIikgJT4lDQogIHJlbmFtZV9hbGwofnN0cl9yZXBsYWNlKC54LHBhdHRlcm49IlxcLiIsIHJlcGxhY2VtZW50PSJfIikpICU+JQ0KICBwaXZvdF9sb25nZXIoZW5kc193aXRoKGMoIl94IiwgIl95IikpLA0KICAgICAgICAgICAgICAgbmFtZXNfdG89YygiLnZhbHVlIiwgInRyZWUiKSwgDQogICAgICAgICAgICAgICBuYW1lc19zZXAgPSAiXyINCiAgICAgICAgICAgICAgKSAgJT4lDQogIG11dGF0ZShzaWduaWZfcGFjbyA9IHJlcGxhY2VfbmEoc2lnbmlmX3BhY28sIDApLA0KICAgICAgICAgc2lnbmlmX3BhcmEgPSByZXBsYWNlX25hKHNpZ25pZl9wYXJhLCAwKSkgJT4lDQogIG11dGF0ZShzaWduaWYgPSBjYXNlX3doZW4oDQogICAgc2lnbmlmX3BhY289PTAgJiBzaWduaWZfcGFyYT09MCAgfiAiTlMiLA0KICAgIHNpZ25pZl9wYWNvPT0wICYgc2lnbmlmX3BhcmE9PTEgfiAicGFyYSIsDQogICAgc2lnbmlmX3BhY289PTEgJiBzaWduaWZfcGFyYT09MCB+ICJwYWNvIiwNCiAgICBzaWduaWZfcGFjbz09MSAmIHNpZ25pZl9wYXJhPT0xIH4gImJvdGgiDQogICkpICU+JQ0KICBtdXRhdGUodHJlZSA9IHRyZWUgJT4lIA0KICAgICAgICAgICBzdHJfcmVwbGFjZSgieCIsICJob3N0IiklPiUNCiAgICAgICAgICAgc3RyX3JlcGxhY2UoInkiLCAibWljcm9iZSIpKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShsYWJlbCkpJT4lIA0KICBncm91cF9ieSh0cmVlKSAlPiUNCiAgbXV0YXRlKHkgPSB5IC8gbWF4KHkpKSU+JQ0KICBtdXRhdGUobGFiZWwgPSBsYWJlbCAlPiUgc3RyX3JlcGxhY2VfYWxsKCJfIiwgIiAiKSkgDQoNCmdnLnRhbmdsZSA8LSBnZ3Bsb3QodGFuZ2xlLCBhZXMoeD1mYWN0b3IodHJlZSwgbGV2ZWxzPWMoIm1pY3JvYmUiLCAiaG9zdCIpKSwgeT15LCBncm91cD1hc3NvYywgY29sb3VyPWFzLmZhY3RvcihzaWduaWYpKSkgKw0KICAgIGdlb21fbGluZShhbHBoYT0wLjgpICsgIA0KICBnZW9tX3RleHQoZGF0YSA9IHRhbmdsZSAlPiUgDQogICAgICAgICAgICAgIGZpbHRlcih0cmVlPT0iaG9zdCIpLA0KICAgICAgICAgICAgYWVzKGxhYmVsPWxhYmVsKSxzdGF0ID0gJ3VuaXF1ZScsIGhqdXN0PTAsIGNoZWNrX292ZXJsYXAgPSBUUlVFKSsNCiAgZ2VvbV90ZXh0KGRhdGEgPSB0YW5nbGUgJT4lIA0KICAgICAgICAgICAgICBmaWx0ZXIodHJlZT09Im1pY3JvYmUiKSwNCiAgICAgICAgICAgIGFlcyhsYWJlbD1sYWJlbCksc3RhdCA9ICd1bmlxdWUnLCBoanVzdD0xLCBjaGVja19vdmVybGFwID0gVFJVRSkrDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoIk5TIj0ic3RlZWxibHVlIiwgInBhY28iPSJkYXJrb3JhbmdlMSIsICJwYXJhIj0iI2RhMmI5MSIsICJib3RoIj0iIzkxZGEyYiIpLCBuYS50cmFuc2xhdGU9RkFMU0UpKw0KICAgIHNjYWxlX3hfZGlzY3JldGUoZXhwYW5kID0gZXhwYW5zaW9uKGFkZD1jKDAuOCwwLjgpKSkgKyANCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kPWMoMC4wMDUsMC4wMDUpKSsNCiAgICB0aGVtZV92b2lkKCkgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArDQogIGxhYnMoY29sb3VyPSJTaWduaWZpY2FuY2UiKQ0KZ2cucG93ZWxsaWFfdGFuZ2xlIDwtIHAyICsgZ2cudGFuZ2xlICsgKHAxICsgc2NhbGVfeF9yZXZlcnNlKCkpIA0KZ2cucG93ZWxsaWFfdGFuZ2xlDQoNCnBkZihmaWxlPSJmaWdzL3Bvd2VsbGlhX3BsYW50X3RhbmdsZWdyYW0ucGRmIiwgIHdpZHRoID0gOCwgaGVpZ2h0ID0gMTEsIHBhcGVyPSJhNCIpDQogIHBsb3QoZ2cucG93ZWxsaWFfdGFuZ2xlKQ0KdHJ5KGRldi5vZmYoKSwgc2lsZW50PVRSVUUpDQpgYGANCg0KDQojIE1hcCBvZiBjb2xsZWNpdG9uIGxvY2F0aW9ucw0KDQpgYGB7ciBDb2xsZWN0aW9uIG1hcH0NCiNQbG90IG9uIG1hcCB0byBjb25maXJtIHBvaW50cw0KZW52RGF0YSA8LSBzYW1wbGVfZGF0YShwczIpICU+JQ0KICBhc19kYXRhX2ZyYW1lKCkgJT4lDQogIGRwbHlyOjpzZWxlY3QoU2FtcGxlX05hbWUscHN5bGxpZF9zcHAsIGxhdCwgbG9uZykgJT4lDQogIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCJTYW1wbGVfTmFtZSIpICU+JQ0KICBkcm9wX25hKCkNCg0KeGxpbSA8LSBjKDE2NSwxODApDQp5bGltIDwtIGMoLTUwLC0zMCkNCg0KbnogPC0gbWFwKGRhdGFiYXNlPSAid29ybGQiLCByZWdpb249ICJOZXcgWmVhbGFuZCIsIGZpbGw9VFJVRSwgeGxpbT14bGltLA0KICB5bGltPXlsaW0pICMsIG1hcj1jKDAsMCwwLDApDQoNCnAxIDwtIGdndHJlZShwcnVuZWQudHJlZSwgbGFkZGVyaXplPVRSVUUpDQptYXBfZGF0YSA8LSBwMSRkYXRhJT4lDQogIGxlZnRfam9pbihlbnZEYXRhICU+JSBkcGx5cjo6bXV0YXRlKGxhYmVsID1wc3lsbGlkX3NwcCApKQ0KDQoNCmNvbCA8LSBjb2xvclJhbXBQYWxldHRlKGJyZXdlci5wYWwoMTIsICJQYWlyZWQiKSkobGVuZ3RoKHVuaXF1ZShtYXBfZGF0YSRwc3lsbGlkX3NwcCkpKQ0KDQpnZy5uem1hcCA8LSBnZ3Bsb3QoZm9ydGlmeShueiksIGFlcyh5PWxhdCwgeD1sb25nLCBncm91cD1ncm91cCkpICsgDQogIGdlb21fcG9seWdvbihmaWxsPSJsaWdodGdyZXkiLCBjb2xvcj0iIzdmN2Y3ZiIpICsNCiAgZ2VvbV9wb2ludChkYXRhPW1hcF9kYXRhLCBhZXMoeD1sb25nLCB5PWxhdCwgY29sb3I9cHN5bGxpZF9zcHApLCBhbHBoYT0uNSwgc2l6ZT0zLCBpbmhlcml0LmFlcyA9IEZBTFNFKSArIA0KICAgIGdlb21fc2VnbWVudChkYXRhPW1hcF9kYXRhICU+JQ0KICBtdXRhdGUoeSA9IHNjYWxlczo6cmVzY2FsZSh5LCB0byA9IHlsaW0gKSksIGFlcyh4PW1pbih4bGltKSwgeT15LCB4ZW5kPSBsb25nLCB5ZW5kPWxhdCwgY29sb3I9cHN5bGxpZF9zcHApLCBhbHBoYT0uMiwgaW5oZXJpdC5hZXMgPSBGQUxTRSkgKw0KICAgIHRoZW1lX2NsYXNzaWMoKSArDQogICAgY29vcmRfZml4ZWQoeWxpbSA9eWxpbSwgeGxpbT14bGltKSArDQogICAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSkgKyANCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jb2wpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgICBzY2FsZV95X2NvbnRpbnVvdXMocG9zaXRpb24gPSAicmlnaHQiKSArDQogIHhsYWIoIkxvbmdpdHVkZSIpICsgDQogIHlsYWIoIkxhdHRpdHVkZSIpDQoNCmdnLm56bWFwIA0KDQojcHJpbnQoZ2cubnptYXAsIHZwID0gdmlld3BvcnQod2lkdGggPSAuNywgaGVpZ2h0ID0gLjcsIGFuZ2xlID0gMzUpKQ0KcDEgPC0gcDEgJTwrJSBtYXBfZGF0YSArDQogIGdlb21fdGlwbGFiKGFsaWduPVRSVUUsIGFlcyhjb2xvcj1wc3lsbGlkX3NwcCksIG9mZnNldD0wLjEsIGhqdXN0PTEpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZD1jKDAsIDApKSsgDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNvbCkgDQoNCmdnLnBoeWxvbWFwIDwtIHAxICsgZ2cubnptYXANCg0KcGRmKGZpbGU9ImZpZ3MvcGh5bG9tYXAucGRmIiwgIHdpZHRoID0gMTUsIGhlaWdodCA9IDE1LCBwYXBlcj0iYTRyIikNCiAgcGxvdChnZy5waHlsb21hcCkNCnRyeShkZXYub2ZmKCksIHNpbGVudD1UUlVFKQ0KYGBgDQoNCiMgU2Vzc2lvbmluZm8NCg0KYGBge3Igc2Vzc2lvbmluZm8sIGV2YWw9VFJVRX0NCmRldnRvb2xzOjpzZXNzaW9uX2luZm8oKQ0KYGBgDQo=
 

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