View source on GitHub

1. Introduction

This is an R Markdown Notebook. The main package used to generate the heatmap is ‘pheatmap’ in RStudio. Use updateR() if needed from the ‘installr’ package.

version
getRversion()
options(repos = c(CRAN = "https://cran.r-project.org"))
install.packages("installr")
library(installr)
install.packages("rstudioapi")
library(rstudioapi)
rstudio_version <- rstudioapi::versionInfo()$version
cat("RStudio version used:", as.character(rstudio_version), "\n")

1.1. Load the necessary packages

install.packages("pheatmap")
library(pheatmap)
install.packages("extrafont")
library(extrafont)
font_import(pattern="[A/a]rial")
install.packages("tidyverse") #include ggplot2, tidyr and dplyr
library(tidyverse)
install.packages("RColorBrewer")
library(RColorBrewer)
install.packages("compositions")
library(compositions)

1.2. References

citation()
version$version.string
citation("pheatmap")
packageVersion("pheatmap")
#https://cran.r-project.org/web/packages/pheatmap/

1.3. Set and get working directory

By default, the working directory for R code chunks is the directory that contains the Rmd document.

getwd()

2. Metadata

The metadata file contains the sample names as rows and sample features as columns.

2.1. Load the metadata file

Rename function from dplyr package.

metadata <- read.csv("GH22_combined_Metadata_withsimple_Finetuned_Productivity4.csv", check.names = FALSE)
colnames(metadata)
rownames(metadata)
metadata
metadata <- metadata %>% rename(Sample_Name = Name)
metadata <- metadata %>% rename(Metabolism = Fermentation_phase_finetuned)
colnames(metadata)
listmet <- metadata$Metabolism
listmet
listmetunique <- unique(listmet)
listmetunique

Specify row names and select samples if required. For the article ““, we select samples from reactors #1 and #2, BSG samples and inoculum samples (102 samples, without R3 and R4 reactors).

row.names(metadata) <- metadata$Sample_Name
metadataarticle <- metadata %>% filter(grepl('R2|R1|/', Reactor))
metadataarticle[1:10,1:10]
listarticle <- metadataarticle$Sample_Name
listarticle
listmetabolism <- metadataarticle$Metabolism
listmetabolism
unique(listmetabolism)
colnames(metadataarticle)

Add metabolic stages for homoacetogenesis and Elong_H2CO2 (auto. vs. mixo.) in a new column “Main_Metabolic_Phase”

metadataarticle$Main_Metabolic_Phase <- listmetabolism
metadataarticle$Main_Metabolic_Phase
colnames(metadataarticle)

old_name <- "Homoacetogenesis"
# Specify the new name
new_name <- "Homoacetogenesis_Mixo"

if (any(metadataarticle$Main_Metabolic_Phase == old_name & metadataarticle$Metabolism_Carbon == "Mixotrophic")) {
  metadataarticle$Main_Metabolic_Phase[metadataarticle$Main_Metabolic_Phase == old_name & metadataarticle$Metabolism_Carbon == "Mixotrophic"] <- new_name
}

metadataarticle$Main_Metabolic_Phase

old_name <- "Homoacetogenesis"
# Specify the new name
new_name <- "Homoacetogenesis_Auto"

if (any(metadataarticle$Main_Metabolic_Phase == old_name & metadataarticle$Metabolism_Carbon == "Autotrophic")) {
  metadataarticle$Main_Metabolic_Phase[metadataarticle$Main_Metabolic_Phase == old_name & metadataarticle$Metabolism_Carbon == "Autotrophic"] <- new_name
}

metadataarticle$Main_Metabolic_Phase

old_name <- "Elong_H2CO2"
# Specify the new name
new_name <- "Elong_H2CO2_Mixo"

if (any(metadataarticle$Main_Metabolic_Phase == old_name & metadataarticle$Metabolism_Carbon == "Mixotrophic")) {
  metadataarticle$Main_Metabolic_Phase[metadataarticle$Main_Metabolic_Phase == old_name & metadataarticle$Metabolism_Carbon == "Mixotrophic"] <- new_name
}

metadataarticle$Main_Metabolic_Phase

old_name <- "Elong_H2CO2"
# Specify the new name
new_name <- "Elong_H2CO2_Auto"

if (any(metadataarticle$Main_Metabolic_Phase == old_name & metadataarticle$Metabolism_Carbon == "Autotrophic")) {
  metadataarticle$Main_Metabolic_Phase[metadataarticle$Main_Metabolic_Phase == old_name & metadataarticle$Metabolism_Carbon == "Autotrophic"] <- new_name
}

metadataarticle$Main_Metabolic_Phase
unique(metadataarticle$Main_Metabolic_Phase)
unique(listmetabolism)

2.2. Metadata file cleaning

colnames(metadataarticle)
metadataarticle2 <- metadataarticle[, !colnames(metadataarticle) %in% c("Sample","Reverse_primer","Barcode_i7","Index i7 Leuven","Experiment","Replicate","Name","Sample_ID")]
colnames(metadataarticle2)
metadataarticle2[1:10,1:10]
metadataarticle2$Fermentation_phase_simplified
metadataarticle2$Metabolism
unique(metadataarticle2$Fermentation_phase_simplified)
unique(metadataarticle2$Metabolism)
unique(metadataarticle2$Metabolism_Carbon)
unique(metadataarticle2$Main_Metabolic_Phase)

2.3. Metadata file subsetting

Without lag phases, and the inoculum/BSG samples

metadataarticle3 <- metadataarticle2 %>% filter(grepl('Elong_H2CO2_Auto|Homoacetogenesis_Auto|Homoacetogenesis_Mixo|Elong_H2CO2_Mixo|Elong_EtOH&Lactate|Acidogenesis', Main_Metabolic_Phase))
rownames(metadataarticle3)

#With Methanogens
metadataarticle4 <- metadataarticle2 %>% filter(grepl('Elong_H2CO2_Auto|Homoacetogenesis_Auto|Homoacetogenesis_Mixo|Elong_H2CO2_Mixo|Elong_EtOH&Lactate|Acidogenesis|Methanogenesis', Main_Metabolic_Phase))
rownames(metadataarticle4)

3. Read the abundance table (taxa)

The abundance file contains the ASV numbers as rows and sample names as columns. The abundance table contains 123 samples and 1866 ASVs.

taxa <- read.table(file = "feature_table_final_rarefied.tsv", sep = "\t", header = TRUE, check.names=F)
taxa
head(taxa[, 125], 20)
colnames(taxa)
head(rownames(taxa), 20)
length(rownames(taxa))
dim(taxa)

4. Read the taxonomy table (taxonomic classification)

The taxonomy file contains the ASV identifiers as rows and taxonomic ranks as columns, from Kingdom to Genus. The ASV_table file was extracted from the rarefied abundance table.

taxanames <- read.table(file = "ASV_table.csv", sep = ",", header = TRUE, check.names=F)
asv_table <- taxanames
asv_table
colnames(asv_table)
df <- separate(asv_table, taxonomy, into = c("Kingdom", "Phylum", "Class", "Order","Family","Genus"), sep = ";", convert = TRUE)
colnames(df)
df[1:50,1:7]
class(df)
fill_value <- "NA"
df$Family[df$Family == ""] <- fill_value
df$Kingdom[df$Kingdom == ""] <- fill_value
df$Phylum[df$Phylum == ""] <- fill_value
df$Class[df$Class == ""] <- fill_value
df$Order[df$Order == ""] <- fill_value
df$Genus[df$Genus == ""] <- fill_value
df[1:50,1:7]
df$Genus <- df$Genus %>% replace_na('NA')
df$Family <- df$Family %>% replace_na('NA')
df$Class <- df$Class %>% replace_na('NA')
df$Order <- df$Order %>% replace_na('NA')
df$Phylum <- df$Phylum %>% replace_na('NA')
df$Kingdom <- df$Kingdom %>% replace_na('NA')
df[1:50,1:7]
old_char <- "NA"
new_char <- "Unclassified"
df_replaced <- data.frame(lapply(df, function(x) gsub(old_char, new_char, x)))
df_replaced
df_replaced[1:50,1:7]
asv_table2 <- df_replaced
asv_table2

Create “ASV_#” and “ASV_#_Genus” identifiers

listnumber <- asv_table$FEATURE_ID
head(listnumber,20)
#Pay attention: This line defines a function called modify_vector_elements that takes two arguments: vec (the input vector) and ASV (the Additional String Vector).
#ASV_Additional String Vector
modify_vector_elements <- function(vec, ASV) {
  modified_vec <- paste0(ASV, vec)
  return(modified_vec)
}
FEATURE_ID2 <- modify_vector_elements(listnumber, "ASV_")
head(FEATURE_ID2,20)

length(FEATURE_ID2)
length(listnumber)

row.names(asv_table2) <- FEATURE_ID2
asv_table2
asv_table2 <- asv_table2[,-1]
colnames(asv_table2)
asv_table2$ASV <- rownames(asv_table2)
colnames(asv_table2)
asv_table2$ASV_Genus <- paste(asv_table2$ASV, asv_table2$Genus, sep = "_")
colnames(asv_table2)
head(asv_table2$ASV_Genus,20)
asv_table2$ASV_Genus <- gsub("g__", "", asv_table2$ASV_Genus)
colnames(asv_table2)
head(asv_table2$ASV_Genus,20)

listnumber2 <- asv_table2$ASV_Genus
head(listnumber2,20)

5. Object summary and characteristics

From the summary of abundance table, the value of 0.5359 corresponds to the mean, i.e., 1000 counts / 1866 detected ASVs.

is.object(metadataarticle2)
is.object(taxa)
is.object(asv_table2)

class(metadataarticle2)
class(taxa)
class(asv_table2)

summary(metadataarticle2)
head(row.names(metadataarticle2),20)
head(colnames(metadataarticle2),20)
dim(taxa)
summary(taxa)
length(unique(taxa$FEATURE_ID))
max(taxa$FEATURE_ID)
min(taxa$FEATURE_ID)
head(row.names(taxa),20)
head(colnames(taxa),20)
summary(asv_table2)
head(row.names(asv_table2),20)
head(colnames(asv_table2),20)

Change column names in taxa to match row names of metadata (before selecting samples) and change ASV identifiers using ASV_#_Genus.

taxa
colnames(taxa)
head(taxa$FEATURE_ID,25)
head(taxa$taxonomy,25)
taxa$ASV_Genus <- listnumber2
head(taxa$ASV_Genus,25)
head(row.names(taxa),20)
row.names(taxa) <- taxa$ASV_Genus
colnames(taxa)
taxa <- taxa[,-1]
colnames(taxa)
taxa <- taxa[,-124]
taxa <- taxa[,-124]
colnames(taxa)
taxa[1:10,1:10]

#Change column names of taxa based on row names of metadata. Distinctions between Hyphen or Dash, and Underscores! 
colnames(taxa)
dim(taxa)
rownames(metadata)
dim(metadata)

colnames(taxa) <- rownames(metadata)
colnames(taxa)

#the identical() function in R does check both the values and their order.
identical(colnames(taxa), rownames(metadata))

6. Sample selection for heatmap construction

To construct the heatmap, we select metadataarticle3 including the metabolic phases: Elong_H2CO2_Auto, Homoacetogenesis_Auto, Homoacetogenesis_Mixo, Elong_H2CO2_Mixo, Elong_EtOH&Lactate, and Acidogenesis.

If methanogenesis included, make reference to metadataarticle4.

columns_to_keep <- rownames(metadataarticle3)
columns_to_keep

columns_to_keep2 <- rownames(metadataarticle4)
columns_to_keep2

# Subset the dataframe to include only the specified columns
taxa_subset <- taxa[, columns_to_keep]
taxa_subset
dim(taxa_subset)

taxa_subset2 <- taxa[, columns_to_keep2]
taxa_subset2
dim(taxa_subset2)
taxa_subset2[1:20,1:20]

head(rowSums(taxa),20)
head(rowSums(taxa_subset),20)
head(rowSums(taxa_subset2),20)

length(rowSums(taxa))
length(rowSums(taxa_subset))
length(rowSums(taxa_subset2))

In taxa file, select ASVs with a relative abundance of 0 in all samples.

asvs_zero_sum <- rownames(taxa)[rowSums(taxa) == 0]
length(asvs_zero_sum)

asvs_zero_sum2 <- rownames(taxa_subset)[rowSums(taxa_subset) == 0]
length(asvs_zero_sum2)
asvs_zero_sum2

asvs_zero_sum3 <- rownames(taxa_subset2)[rowSums(taxa_subset2) == 0]
length(asvs_zero_sum3)

In taxa file, select ASVs with a relative abundance greather than 0 in all samples. 635 ASVs detected in Elong_H2CO2_Auto, Homoacetogenesis_Auto, Homoacetogenesis_Mixo, Elong_H2CO2_Mixo, Elong_EtOH&Lactate, and Acidogenesis.

asvs_zero_sum4 <- rownames(taxa_subset)[rowSums(taxa_subset) != 0]
length(asvs_zero_sum4)
asvs_zero_sum4

Select ASVs greater than zero in taxa_subset

taxa_subset
asvs_zero_sum4
class(taxa_subset)
class(asvs_zero_sum4)

selected_rows <- taxa_subset[rownames(taxa_subset) %in% asvs_zero_sum4, ]
selected_rows
dim(selected_rows)

summary(selected_rows)

asv_counts_per_sample <- apply(selected_rows, 2, function(x) sum(x > 0))
asv_counts_per_sample

If you want to determine how many times an ASV is detected in selected samples, you can use the R expression function(x) sum(x > 0). This defines an anonymous function that counts the number of elements in x that are greater than 0.

asv_detection_counts <- apply(selected_rows, 1, function(x) sum(x > 0))
asv_detection_counts
asv_detection_counts_sorted <- sort(asv_detection_counts, decreasing = TRUE)
asv_detection_counts_sorted

7. Annotations and color palettes for heatmap construction

#Define annotations
df <- metadataarticle3[,c("Main_Metabolic_Phase","Metabolism_Carbon","Reactor")]
colnames(df)
df
class(df)

df2 <- metadataarticle4[,c("Main_Metabolic_Phase","Metabolism_Carbon","Reactor")]
colnames(df2)

#Color palette
mypal5 <- colorRampPalette(rev(brewer.pal(n = 7, name = "RdYlBu")))(100)
mypal5

length(mypal5)

# Visualize the palette as a horizontal strip
image(1:100, 1, as.matrix(1:100), col = mypal5, axes = FALSE, xlab = "", ylab = "")

annotation_colors <- list(
  Reactor = c(R1 = "#b1b1b1", R2 = "#7F1734"),
  Metabolism_Carbon = c(Autotrophic = "#1e7dff", Mixotrophic = "#ff7b00"),
  Main_Metabolic_Phase = c(
    Acidogenesis = "#30d5c8", 
    `Elong_EtOH&Lactate` = "#ffd700", 
    `Elong_H2CO2_Auto` = "#ff6cda", 
    `Elong_H2CO2_Mixo` = "#9a009a", 
    Homoacetogenesis_Auto = "#008100", 
    Homoacetogenesis_Mixo = "#93c571"  )
)

annotation_colors2 <- list(
  Reactor = c(R1 = "#b1b1b1", R2 = "#7F1734"),
  Metabolism_Carbon = c(Autotrophic = "#1e7dff", Mixotrophic = "#ff7b00"),
  Main_Metabolic_Phase = c(
    Acidogenesis = "#30d5c8", 
    `Elong_EtOH&Lactate` = "#ffd700", 
    `Elong_H2CO2_Auto` = "#ff6cda", 
    `Elong_H2CO2_Mixo` = "#9a009a", 
    Homoacetogenesis_Auto = "#008100", 
    Homoacetogenesis_Mixo = "#93c571",
    Methanogenesis = "blue")
)

annotation_colors3 <- list(
  Reactor = c(R1 = "#b1b1b1", R2 = "#7F1734"),
  Metabolism_Carbon = c(Autotrophic = "#1e7dff", Mixotrophic = "#ff7b00"),
  Main_Metabolic_Phase = c(
    Acidogenesis = "#30d5c8", 
    `Elong_EtOH&Lactate` = "#ffd700", 
    `Elong_H2CO2_Auto` = "#ff6cda", 
    `Elong_H2CO2_Mixo` = "#9a009a", 
    Homoacetogenesis_Auto = "#008100", 
    Homoacetogenesis_Mixo = "#93c571"  ),
  Phylum = c(
  "p__Actinobacteriota"             = "#ff0000",
  "p__Bacteroidota"                 = "#000000",
  "p__Firmicutes"                   = "#ffff00")
)

To add row annotations in the heatmap.

asv_annotation <- asv_table2[, c("ASV_Genus", "Phylum")]
rownames(asv_annotation) <- asv_annotation$ASV_Genus
head(asv_annotation)
asv_annotation <- asv_annotation[, "Phylum", drop = FALSE]
head(asv_annotation)
unique(asv_annotation$Phylum)

identical(row.names(asv_annotation),row.names(taxa_subset))

class(asv_annotation)

taxatop25f
list <- row.names(taxatop25f)
list
asv_annotation
rownames(asv_annotation)
dim(asv_annotation)

asv_annotation_subset <- asv_annotation[rownames(asv_annotation) %in% rownames(taxatop25f), , drop = FALSE]
asv_annotation_subset
class(asv_annotation_subset)

8. Select the TOP25/30 taxa in all selected samples

taxa_subset
taxa_subsett <- t(taxa_subset)
taxa_subsett_decr <- taxa_subsett[,order(colSums(taxa_subsett),decreasing=TRUE)]
rownames(taxa_subsett_decr)
colnames(taxa_subsett_decr)
top25list <- colnames(taxa_subsett_decr)[1:25]
top25list
taxatop25 <- data.frame(taxa_subsett_decr[,colnames(taxa_subsett_decr) %in% top25list], check.names = FALSE)
taxatop25
taxatop25f <- t(taxatop25)
taxatop25f
colnames(taxatop25f)
colSums(taxatop25f)
rowSums(taxatop25f)
write.csv(taxatop25f, "taxatop25f.csv", row.names = TRUE)

w <- taxatop25f+1
write.csv(w, "taxatop25fwithone.csv", row.names = TRUE)

taxa_subset2
taxa_subset2t <- t(taxa_subset2)
taxa_subset2t_decr <- taxa_subset2t[,order(colSums(taxa_subset2t),decreasing=TRUE)]
rownames(taxa_subset2t_decr)
colnames(taxa_subset2t_decr)
top30listm <- colnames(taxa_subset2t_decr)[1:30]
top30listm
taxatop30m <- data.frame(taxa_subset2t_decr[,colnames(taxa_subset2t_decr) %in% top30listm], check.names = FALSE)
taxatop30m
taxatop30mf <- t(taxatop30m)
taxatop30mf
colSums(taxatop30mf)

9. Data transformation and heatmap construction

Add 1 to your rarefied counts before log transformation: log(x + 1) is a standard preprocessing step.

taxatop25nozero <- taxatop25f+1
write.csv(taxatop25nozero, "taxatop25fnozero_2.csv", row.names = TRUE)

range(taxatop25nozero, na.rm = TRUE)

Euclidean distances are used by default in pheatmap, for both rows and columns (clustering_distance_rows = “euclidean”; clustering_distance_cols = “euclidean”)

9.1. Log2 transformation

LOG2(1) = 0 LOG2(0) undefined

datlog2 <- log2(taxatop25f+1)

range(datlog2, na.rm = TRUE)
summary(as.vector(datlog2))

str(datlog2)
is.matrix(datlog2)
is.numeric(datlog2)

This command uses the pheatmap package in R to generate a heatmap and stores the result (including dendrogram information) in the object plog2_average. pheatmap() uses here average linkage (UPGMA) for hierarchical clustering of rows and columns. cutree_cols = 7 cuts the column dendrogram into 7 clusters. This means: Columns (samples) will be grouped into 7 clusters, and these clusters are shown with colored bars based on defined annotations. cutree_rows = 10 cuts the row dendrogram into 10 clusters. This groups the taxa (rows) into 10 clusters, and helps visually identify groups of similar features.

tiff("plog2_average_2.tiff", width = 16, height = 8, units = "in", res = 300)
plog2_average <- pheatmap(datlog2, mypal5, clustering_method="average", breaks = NA, scale = "none", fontsize=10, fontsize_row=8, fontsize_col=8, annotation_col=df, annotation_colors = annotation_colors, cutree_cols = 7, cutree_rows = 10)
dev.off()
tiff("plog2_average_2_rows.tiff", width = 16, height = 8, units = "in", res = 300)
plog2_average <- pheatmap(datlog2, mypal5, clustering_method="average", breaks = NA, scale = "none", fontsize=10, fontsize_row=8, fontsize_col=8, annotation_col=df, annotation_row=asv_annotation_subset, annotation_colors = annotation_colors3, cutree_cols = 7, cutree_rows = 10)
dev.off()

Define scale of color palette.

my_breaks <- seq(0, 10, by = 0.5)
mypal6 <- colorRampPalette(RColorBrewer::brewer.pal(9, "YlGnBu"))(length(my_breaks) - 1)
mypal6 <- colorRampPalette(rev(brewer.pal(n = 7, name = "RdYlBu")))(20)
mypal6

length(mypal5)
length(mypal6)

str(my_breaks)
is.numeric(my_breaks)             
all(is.finite(my_breaks))          
length(mypal5) == length(my_breaks) - 1
print(length(mypal5))

tiff("plog2_average_2_scalecolor.tiff", width = 16, height = 8, units = "in", res = 300)
plog2_average <- pheatmap(mat = datlog2, color = mypal6, clustering_method="average", breaks = my_breaks, scale = "none", fontsize=10, fontsize_row=8, fontsize_col=8, annotation_col=df, annotation_colors = annotation_colors, cutree_cols = 7, cutree_rows = 10)
dev.off()

9.2. Log10 transformation

datlog10 <- log10(taxatop25f+1)

range(datlog10, na.rm = TRUE)
summary(as.vector(datlog10))

str(datlog10)
is.matrix(datlog10)
is.numeric(datlog10)
tiff("plog10_average_2.tiff", width = 16, height = 8, units = "in", res = 300)
plog10_average <- pheatmap(datlog10, mypal5, clustering_method="average", breaks = NA, scale = "none", fontsize=10, fontsize_row=8, fontsize_col=8, annotation_col=df, annotation_colors = annotation_colors, cutree_cols = 7, cutree_rows = 10)
dev.off()

9.3. clr transformation

clr (Centered Log-Ratio) transformation from the package compositions. clr uses the geometric mean of all components (in microbiome data, each component in a compositional vector corresponds to a specific ASV or OTU).

taxatop25f
row.names(taxatop25f)
colnames(taxatop25f)

taxatop25f2 <- taxatop25f+1
write.csv(taxatop25f2, "taxatop25fplusone.csv", row.names = TRUE)

datclr <- clr(acomp(taxatop25f+1))

range(datclr, na.rm = TRUE)
summary(as.vector(datclr))

class(datclr)
is.matrix(datclr)
is.numeric(datclr)

datclr_mat <- as.matrix(datclr)
datclr_mat
write.csv(datclr_mat, "datclr_mat.csv", row.names = TRUE)
tiff("pclr_average_2.tiff", width = 16, height = 8, units = "in", res = 300)
pclr_average <- pheatmap(datclr, mypal5, clustering_method="complete", breaks = NA, scale = "none", fontsize=10, fontsize_row=8, fontsize_col=8, annotation_col=df, annotation_colors = annotation_colors, cutree_cols = 7, cutree_rows = 10)
dev.off()
tiff("pclr_average_2_mat.tiff", width = 16, height = 8, units = "in", res = 300)
pclr_average <- pheatmap(datclr_mat, mypal5, clustering_method="complete", breaks = NA, scale = "none", fontsize=10, fontsize_row=8, fontsize_col=8, annotation_col=df, annotation_colors = annotation_colors, cutree_cols = 7, cutree_rows = 10)
dev.off()
LS0tDQp0aXRsZTogJ01pY3JvYmlvbWUgYW5hbHlzaXM6IGNsdXN0ZXJlZCBoZWF0bWFwcycNCmF1dGhvcjogIkIuIFN0ZW51aXQsIFVDTG91dmFpbiwgRWFydGggJiBMaWZlIEluc3RpdHV0ZSwgTG91dmFpbi1sYS1OZXV2ZSINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCltWaWV3IHNvdXJjZSBvbiBHaXRIdWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9CZW5TdGVudWl0L0hlYXRtYXBfQ2hhaW5fZWxvbmcpDQoNCg0KIyAxLiA8dT5JbnRyb2R1Y3Rpb248L3U+DQoNClRoaXMgaXMgYW4gW1IgTWFya2Rvd25dKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20pIE5vdGVib29rLiBUaGUgbWFpbiBwYWNrYWdlIHVzZWQgdG8gZ2VuZXJhdGUgdGhlIGhlYXRtYXAgaXMgJ3BoZWF0bWFwJyBpbiBSU3R1ZGlvLiBVc2UgdXBkYXRlUigpIGlmIG5lZWRlZCBmcm9tIHRoZSAnaW5zdGFsbHInIHBhY2thZ2UuDQoNCmBgYHtyfQ0KdmVyc2lvbg0KZ2V0UnZlcnNpb24oKQ0Kb3B0aW9ucyhyZXBvcyA9IGMoQ1JBTiA9ICJodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZyIpKQ0KaW5zdGFsbC5wYWNrYWdlcygiaW5zdGFsbHIiKQ0KbGlicmFyeShpbnN0YWxscikNCmluc3RhbGwucGFja2FnZXMoInJzdHVkaW9hcGkiKQ0KbGlicmFyeShyc3R1ZGlvYXBpKQ0KcnN0dWRpb192ZXJzaW9uIDwtIHJzdHVkaW9hcGk6OnZlcnNpb25JbmZvKCkkdmVyc2lvbg0KY2F0KCJSU3R1ZGlvIHZlcnNpb24gdXNlZDoiLCBhcy5jaGFyYWN0ZXIocnN0dWRpb192ZXJzaW9uKSwgIlxuIikNCmBgYA0KDQojIyAxLjEuIExvYWQgdGhlIG5lY2Vzc2FyeSBwYWNrYWdlcw0KDQpgYGB7cn0NCmluc3RhbGwucGFja2FnZXMoInBoZWF0bWFwIikNCmxpYnJhcnkocGhlYXRtYXApDQppbnN0YWxsLnBhY2thZ2VzKCJleHRyYWZvbnQiKQ0KbGlicmFyeShleHRyYWZvbnQpDQpmb250X2ltcG9ydChwYXR0ZXJuPSJbQS9hXXJpYWwiKQ0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikgI2luY2x1ZGUgZ2dwbG90MiwgdGlkeXIgYW5kIGRwbHlyDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmluc3RhbGwucGFja2FnZXMoIlJDb2xvckJyZXdlciIpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmluc3RhbGwucGFja2FnZXMoImNvbXBvc2l0aW9ucyIpDQpsaWJyYXJ5KGNvbXBvc2l0aW9ucykNCmBgYA0KDQojIyAxLjIuIFJlZmVyZW5jZXMNCg0KYGBge3J9DQpjaXRhdGlvbigpDQp2ZXJzaW9uJHZlcnNpb24uc3RyaW5nDQpjaXRhdGlvbigicGhlYXRtYXAiKQ0KcGFja2FnZVZlcnNpb24oInBoZWF0bWFwIikNCiNodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcGhlYXRtYXAvDQpgYGANCg0KIyMgMS4zLiBTZXQgYW5kIGdldCB3b3JraW5nIGRpcmVjdG9yeQ0KDQpCeSBkZWZhdWx0LCB0aGUgd29ya2luZyBkaXJlY3RvcnkgZm9yIFIgY29kZSBjaHVua3MgaXMgdGhlIGRpcmVjdG9yeSB0aGF0IGNvbnRhaW5zIHRoZSBSbWQgZG9jdW1lbnQuDQoNCmBgYHtyfQ0KZ2V0d2QoKQ0KYGBgDQoNCg0KIyAyLiA8dT5NZXRhZGF0YTwvdT4NCg0KVGhlIG1ldGFkYXRhIGZpbGUgY29udGFpbnMgdGhlIHNhbXBsZSBuYW1lcyBhcyByb3dzIGFuZCBzYW1wbGUgZmVhdHVyZXMgYXMgY29sdW1ucy4NCg0KIyMgMi4xLiBMb2FkIHRoZSBtZXRhZGF0YSBmaWxlDQoNClJlbmFtZSBmdW5jdGlvbiBmcm9tIGRwbHlyIHBhY2thZ2UuDQoNCmBgYHtyfQ0KbWV0YWRhdGEgPC0gcmVhZC5jc3YoIkdIMjJfY29tYmluZWRfTWV0YWRhdGFfd2l0aHNpbXBsZV9GaW5ldHVuZWRfUHJvZHVjdGl2aXR5NC5jc3YiLCBjaGVjay5uYW1lcyA9IEZBTFNFKQ0KY29sbmFtZXMobWV0YWRhdGEpDQpyb3duYW1lcyhtZXRhZGF0YSkNCm1ldGFkYXRhDQptZXRhZGF0YSA8LSBtZXRhZGF0YSAlPiUgcmVuYW1lKFNhbXBsZV9OYW1lID0gTmFtZSkNCm1ldGFkYXRhIDwtIG1ldGFkYXRhICU+JSByZW5hbWUoTWV0YWJvbGlzbSA9IEZlcm1lbnRhdGlvbl9waGFzZV9maW5ldHVuZWQpDQpjb2xuYW1lcyhtZXRhZGF0YSkNCmxpc3RtZXQgPC0gbWV0YWRhdGEkTWV0YWJvbGlzbQ0KbGlzdG1ldA0KbGlzdG1ldHVuaXF1ZSA8LSB1bmlxdWUobGlzdG1ldCkNCmxpc3RtZXR1bmlxdWUNCmBgYA0KDQpTcGVjaWZ5IHJvdyBuYW1lcyBhbmQgc2VsZWN0IHNhbXBsZXMgaWYgcmVxdWlyZWQuDQpGb3IgdGhlIGFydGljbGUgIiIsIHdlIHNlbGVjdCBzYW1wbGVzIGZyb20gcmVhY3RvcnMgIzEgYW5kICMyLCBCU0cgc2FtcGxlcyBhbmQgaW5vY3VsdW0gc2FtcGxlcyAoMTAyIHNhbXBsZXMsIHdpdGhvdXQgUjMgYW5kIFI0IHJlYWN0b3JzKS4NCg0KYGBge3J9DQpyb3cubmFtZXMobWV0YWRhdGEpIDwtIG1ldGFkYXRhJFNhbXBsZV9OYW1lDQptZXRhZGF0YWFydGljbGUgPC0gbWV0YWRhdGEgJT4lIGZpbHRlcihncmVwbCgnUjJ8UjF8LycsIFJlYWN0b3IpKQ0KbWV0YWRhdGFhcnRpY2xlWzE6MTAsMToxMF0NCmxpc3RhcnRpY2xlIDwtIG1ldGFkYXRhYXJ0aWNsZSRTYW1wbGVfTmFtZQ0KbGlzdGFydGljbGUNCmxpc3RtZXRhYm9saXNtIDwtIG1ldGFkYXRhYXJ0aWNsZSRNZXRhYm9saXNtDQpsaXN0bWV0YWJvbGlzbQ0KdW5pcXVlKGxpc3RtZXRhYm9saXNtKQ0KY29sbmFtZXMobWV0YWRhdGFhcnRpY2xlKQ0KYGBgDQoNCkFkZCBtZXRhYm9saWMgc3RhZ2VzIGZvciBob21vYWNldG9nZW5lc2lzIGFuZCBFbG9uZ19IMkNPMiAoYXV0by4gdnMuIG1peG8uKSBpbiBhIG5ldyBjb2x1bW4gIk1haW5fTWV0YWJvbGljX1BoYXNlIg0KDQpgYGB7cn0NCm1ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZSA8LSBsaXN0bWV0YWJvbGlzbQ0KbWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlDQpjb2xuYW1lcyhtZXRhZGF0YWFydGljbGUpDQoNCm9sZF9uYW1lIDwtICJIb21vYWNldG9nZW5lc2lzIg0KIyBTcGVjaWZ5IHRoZSBuZXcgbmFtZQ0KbmV3X25hbWUgPC0gIkhvbW9hY2V0b2dlbmVzaXNfTWl4byINCg0KaWYgKGFueShtZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2UgPT0gb2xkX25hbWUgJiBtZXRhZGF0YWFydGljbGUkTWV0YWJvbGlzbV9DYXJib24gPT0gIk1peG90cm9waGljIikpIHsNCiAgbWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlW21ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZSA9PSBvbGRfbmFtZSAmIG1ldGFkYXRhYXJ0aWNsZSRNZXRhYm9saXNtX0NhcmJvbiA9PSAiTWl4b3Ryb3BoaWMiXSA8LSBuZXdfbmFtZQ0KfQ0KDQptZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2UNCg0Kb2xkX25hbWUgPC0gIkhvbW9hY2V0b2dlbmVzaXMiDQojIFNwZWNpZnkgdGhlIG5ldyBuYW1lDQpuZXdfbmFtZSA8LSAiSG9tb2FjZXRvZ2VuZXNpc19BdXRvIg0KDQppZiAoYW55KG1ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZSA9PSBvbGRfbmFtZSAmIG1ldGFkYXRhYXJ0aWNsZSRNZXRhYm9saXNtX0NhcmJvbiA9PSAiQXV0b3Ryb3BoaWMiKSkgew0KICBtZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2VbbWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlID09IG9sZF9uYW1lICYgbWV0YWRhdGFhcnRpY2xlJE1ldGFib2xpc21fQ2FyYm9uID09ICJBdXRvdHJvcGhpYyJdIDwtIG5ld19uYW1lDQp9DQoNCm1ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZQ0KDQpvbGRfbmFtZSA8LSAiRWxvbmdfSDJDTzIiDQojIFNwZWNpZnkgdGhlIG5ldyBuYW1lDQpuZXdfbmFtZSA8LSAiRWxvbmdfSDJDTzJfTWl4byINCg0KaWYgKGFueShtZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2UgPT0gb2xkX25hbWUgJiBtZXRhZGF0YWFydGljbGUkTWV0YWJvbGlzbV9DYXJib24gPT0gIk1peG90cm9waGljIikpIHsNCiAgbWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlW21ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZSA9PSBvbGRfbmFtZSAmIG1ldGFkYXRhYXJ0aWNsZSRNZXRhYm9saXNtX0NhcmJvbiA9PSAiTWl4b3Ryb3BoaWMiXSA8LSBuZXdfbmFtZQ0KfQ0KDQptZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2UNCg0Kb2xkX25hbWUgPC0gIkVsb25nX0gyQ08yIg0KIyBTcGVjaWZ5IHRoZSBuZXcgbmFtZQ0KbmV3X25hbWUgPC0gIkVsb25nX0gyQ08yX0F1dG8iDQoNCmlmIChhbnkobWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlID09IG9sZF9uYW1lICYgbWV0YWRhdGFhcnRpY2xlJE1ldGFib2xpc21fQ2FyYm9uID09ICJBdXRvdHJvcGhpYyIpKSB7DQogIG1ldGFkYXRhYXJ0aWNsZSRNYWluX01ldGFib2xpY19QaGFzZVttZXRhZGF0YWFydGljbGUkTWFpbl9NZXRhYm9saWNfUGhhc2UgPT0gb2xkX25hbWUgJiBtZXRhZGF0YWFydGljbGUkTWV0YWJvbGlzbV9DYXJib24gPT0gIkF1dG90cm9waGljIl0gPC0gbmV3X25hbWUNCn0NCg0KbWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlDQp1bmlxdWUobWV0YWRhdGFhcnRpY2xlJE1haW5fTWV0YWJvbGljX1BoYXNlKQ0KdW5pcXVlKGxpc3RtZXRhYm9saXNtKQ0KYGBgDQoNCiMjIDIuMi4gTWV0YWRhdGEgZmlsZSBjbGVhbmluZw0KDQpgYGB7cn0NCmNvbG5hbWVzKG1ldGFkYXRhYXJ0aWNsZSkNCm1ldGFkYXRhYXJ0aWNsZTIgPC0gbWV0YWRhdGFhcnRpY2xlWywgIWNvbG5hbWVzKG1ldGFkYXRhYXJ0aWNsZSkgJWluJSBjKCJTYW1wbGUiLCJSZXZlcnNlX3ByaW1lciIsIkJhcmNvZGVfaTciLCJJbmRleCBpNyBMZXV2ZW4iLCJFeHBlcmltZW50IiwiUmVwbGljYXRlIiwiTmFtZSIsIlNhbXBsZV9JRCIpXQ0KY29sbmFtZXMobWV0YWRhdGFhcnRpY2xlMikNCm1ldGFkYXRhYXJ0aWNsZTJbMToxMCwxOjEwXQ0KbWV0YWRhdGFhcnRpY2xlMiRGZXJtZW50YXRpb25fcGhhc2Vfc2ltcGxpZmllZA0KbWV0YWRhdGFhcnRpY2xlMiRNZXRhYm9saXNtDQp1bmlxdWUobWV0YWRhdGFhcnRpY2xlMiRGZXJtZW50YXRpb25fcGhhc2Vfc2ltcGxpZmllZCkNCnVuaXF1ZShtZXRhZGF0YWFydGljbGUyJE1ldGFib2xpc20pDQp1bmlxdWUobWV0YWRhdGFhcnRpY2xlMiRNZXRhYm9saXNtX0NhcmJvbikNCnVuaXF1ZShtZXRhZGF0YWFydGljbGUyJE1haW5fTWV0YWJvbGljX1BoYXNlKQ0KYGBgDQoNCiMjIDIuMy4gTWV0YWRhdGEgZmlsZSBzdWJzZXR0aW5nDQoNCldpdGhvdXQgbGFnIHBoYXNlcywgYW5kIHRoZSBpbm9jdWx1bS9CU0cgc2FtcGxlcw0KDQpgYGB7cn0NCm1ldGFkYXRhYXJ0aWNsZTMgPC0gbWV0YWRhdGFhcnRpY2xlMiAlPiUgZmlsdGVyKGdyZXBsKCdFbG9uZ19IMkNPMl9BdXRvfEhvbW9hY2V0b2dlbmVzaXNfQXV0b3xIb21vYWNldG9nZW5lc2lzX01peG98RWxvbmdfSDJDTzJfTWl4b3xFbG9uZ19FdE9IJkxhY3RhdGV8QWNpZG9nZW5lc2lzJywgTWFpbl9NZXRhYm9saWNfUGhhc2UpKQ0Kcm93bmFtZXMobWV0YWRhdGFhcnRpY2xlMykNCg0KI1dpdGggTWV0aGFub2dlbnMNCm1ldGFkYXRhYXJ0aWNsZTQgPC0gbWV0YWRhdGFhcnRpY2xlMiAlPiUgZmlsdGVyKGdyZXBsKCdFbG9uZ19IMkNPMl9BdXRvfEhvbW9hY2V0b2dlbmVzaXNfQXV0b3xIb21vYWNldG9nZW5lc2lzX01peG98RWxvbmdfSDJDTzJfTWl4b3xFbG9uZ19FdE9IJkxhY3RhdGV8QWNpZG9nZW5lc2lzfE1ldGhhbm9nZW5lc2lzJywgTWFpbl9NZXRhYm9saWNfUGhhc2UpKQ0Kcm93bmFtZXMobWV0YWRhdGFhcnRpY2xlNCkNCmBgYA0KDQoNCiMgMy4gPHU+UmVhZCB0aGUgYWJ1bmRhbmNlIHRhYmxlICh0YXhhKTwvdT4NCg0KVGhlIGFidW5kYW5jZSBmaWxlIGNvbnRhaW5zIHRoZSBBU1YgbnVtYmVycyBhcyByb3dzIGFuZCBzYW1wbGUgbmFtZXMgYXMgY29sdW1ucy4NClRoZSBhYnVuZGFuY2UgdGFibGUgY29udGFpbnMgMTIzIHNhbXBsZXMgYW5kIDE4NjYgQVNWcy4NCg0KYGBge3J9DQp0YXhhIDwtIHJlYWQudGFibGUoZmlsZSA9ICJmZWF0dXJlX3RhYmxlX2ZpbmFsX3JhcmVmaWVkLnRzdiIsIHNlcCA9ICJcdCIsIGhlYWRlciA9IFRSVUUsIGNoZWNrLm5hbWVzPUYpDQp0YXhhDQpoZWFkKHRheGFbLCAxMjVdLCAyMCkNCmNvbG5hbWVzKHRheGEpDQpoZWFkKHJvd25hbWVzKHRheGEpLCAyMCkNCmxlbmd0aChyb3duYW1lcyh0YXhhKSkNCmRpbSh0YXhhKQ0KYGBgDQoNCg0KIyMgNC4gPHU+UmVhZCB0aGUgdGF4b25vbXkgdGFibGUgKHRheG9ub21pYyBjbGFzc2lmaWNhdGlvbik8L3U+DQoNClRoZSB0YXhvbm9teSBmaWxlIGNvbnRhaW5zIHRoZSBBU1YgaWRlbnRpZmllcnMgYXMgcm93cyBhbmQgdGF4b25vbWljIHJhbmtzIGFzIGNvbHVtbnMsIGZyb20gS2luZ2RvbSB0byBHZW51cy4gVGhlIEFTVl90YWJsZSBmaWxlIHdhcyBleHRyYWN0ZWQgZnJvbSB0aGUgcmFyZWZpZWQgYWJ1bmRhbmNlIHRhYmxlLg0KDQpgYGB7cn0NCnRheGFuYW1lcyA8LSByZWFkLnRhYmxlKGZpbGUgPSAiQVNWX3RhYmxlLmNzdiIsIHNlcCA9ICIsIiwgaGVhZGVyID0gVFJVRSwgY2hlY2submFtZXM9RikNCmFzdl90YWJsZSA8LSB0YXhhbmFtZXMNCmFzdl90YWJsZQ0KY29sbmFtZXMoYXN2X3RhYmxlKQ0KZGYgPC0gc2VwYXJhdGUoYXN2X3RhYmxlLCB0YXhvbm9teSwgaW50byA9IGMoIktpbmdkb20iLCAiUGh5bHVtIiwgIkNsYXNzIiwgIk9yZGVyIiwiRmFtaWx5IiwiR2VudXMiKSwgc2VwID0gIjsiLCBjb252ZXJ0ID0gVFJVRSkNCmNvbG5hbWVzKGRmKQ0KZGZbMTo1MCwxOjddDQpjbGFzcyhkZikNCmZpbGxfdmFsdWUgPC0gIk5BIg0KZGYkRmFtaWx5W2RmJEZhbWlseSA9PSAiIl0gPC0gZmlsbF92YWx1ZQ0KZGYkS2luZ2RvbVtkZiRLaW5nZG9tID09ICIiXSA8LSBmaWxsX3ZhbHVlDQpkZiRQaHlsdW1bZGYkUGh5bHVtID09ICIiXSA8LSBmaWxsX3ZhbHVlDQpkZiRDbGFzc1tkZiRDbGFzcyA9PSAiIl0gPC0gZmlsbF92YWx1ZQ0KZGYkT3JkZXJbZGYkT3JkZXIgPT0gIiJdIDwtIGZpbGxfdmFsdWUNCmRmJEdlbnVzW2RmJEdlbnVzID09ICIiXSA8LSBmaWxsX3ZhbHVlDQpkZlsxOjUwLDE6N10NCmRmJEdlbnVzIDwtIGRmJEdlbnVzICU+JSByZXBsYWNlX25hKCdOQScpDQpkZiRGYW1pbHkgPC0gZGYkRmFtaWx5ICU+JSByZXBsYWNlX25hKCdOQScpDQpkZiRDbGFzcyA8LSBkZiRDbGFzcyAlPiUgcmVwbGFjZV9uYSgnTkEnKQ0KZGYkT3JkZXIgPC0gZGYkT3JkZXIgJT4lIHJlcGxhY2VfbmEoJ05BJykNCmRmJFBoeWx1bSA8LSBkZiRQaHlsdW0gJT4lIHJlcGxhY2VfbmEoJ05BJykNCmRmJEtpbmdkb20gPC0gZGYkS2luZ2RvbSAlPiUgcmVwbGFjZV9uYSgnTkEnKQ0KZGZbMTo1MCwxOjddDQpvbGRfY2hhciA8LSAiTkEiDQpuZXdfY2hhciA8LSAiVW5jbGFzc2lmaWVkIg0KZGZfcmVwbGFjZWQgPC0gZGF0YS5mcmFtZShsYXBwbHkoZGYsIGZ1bmN0aW9uKHgpIGdzdWIob2xkX2NoYXIsIG5ld19jaGFyLCB4KSkpDQpkZl9yZXBsYWNlZA0KZGZfcmVwbGFjZWRbMTo1MCwxOjddDQphc3ZfdGFibGUyIDwtIGRmX3JlcGxhY2VkDQphc3ZfdGFibGUyDQpgYGANCg0KQ3JlYXRlICJBU1ZfIyIgYW5kICJBU1ZfI19HZW51cyIgaWRlbnRpZmllcnMNCg0KYGBge3J9DQpsaXN0bnVtYmVyIDwtIGFzdl90YWJsZSRGRUFUVVJFX0lEDQpoZWFkKGxpc3RudW1iZXIsMjApDQojUGF5IGF0dGVudGlvbjogVGhpcyBsaW5lIGRlZmluZXMgYSBmdW5jdGlvbiBjYWxsZWQgbW9kaWZ5X3ZlY3Rvcl9lbGVtZW50cyB0aGF0IHRha2VzIHR3byBhcmd1bWVudHM6IHZlYyAodGhlIGlucHV0IHZlY3RvcikgYW5kIEFTViAodGhlIEFkZGl0aW9uYWwgU3RyaW5nIFZlY3RvcikuDQojQVNWX0FkZGl0aW9uYWwgU3RyaW5nIFZlY3Rvcg0KbW9kaWZ5X3ZlY3Rvcl9lbGVtZW50cyA8LSBmdW5jdGlvbih2ZWMsIEFTVikgew0KICBtb2RpZmllZF92ZWMgPC0gcGFzdGUwKEFTViwgdmVjKQ0KICByZXR1cm4obW9kaWZpZWRfdmVjKQ0KfQ0KRkVBVFVSRV9JRDIgPC0gbW9kaWZ5X3ZlY3Rvcl9lbGVtZW50cyhsaXN0bnVtYmVyLCAiQVNWXyIpDQpoZWFkKEZFQVRVUkVfSUQyLDIwKQ0KDQpsZW5ndGgoRkVBVFVSRV9JRDIpDQpsZW5ndGgobGlzdG51bWJlcikNCg0Kcm93Lm5hbWVzKGFzdl90YWJsZTIpIDwtIEZFQVRVUkVfSUQyDQphc3ZfdGFibGUyDQphc3ZfdGFibGUyIDwtIGFzdl90YWJsZTJbLC0xXQ0KY29sbmFtZXMoYXN2X3RhYmxlMikNCmFzdl90YWJsZTIkQVNWIDwtIHJvd25hbWVzKGFzdl90YWJsZTIpDQpjb2xuYW1lcyhhc3ZfdGFibGUyKQ0KYXN2X3RhYmxlMiRBU1ZfR2VudXMgPC0gcGFzdGUoYXN2X3RhYmxlMiRBU1YsIGFzdl90YWJsZTIkR2VudXMsIHNlcCA9ICJfIikNCmNvbG5hbWVzKGFzdl90YWJsZTIpDQpoZWFkKGFzdl90YWJsZTIkQVNWX0dlbnVzLDIwKQ0KYXN2X3RhYmxlMiRBU1ZfR2VudXMgPC0gZ3N1YigiZ19fIiwgIiIsIGFzdl90YWJsZTIkQVNWX0dlbnVzKQ0KY29sbmFtZXMoYXN2X3RhYmxlMikNCmhlYWQoYXN2X3RhYmxlMiRBU1ZfR2VudXMsMjApDQoNCmxpc3RudW1iZXIyIDwtIGFzdl90YWJsZTIkQVNWX0dlbnVzDQpoZWFkKGxpc3RudW1iZXIyLDIwKQ0KYGBgDQoNCg0KIyMgNS4gPHU+T2JqZWN0IHN1bW1hcnkgYW5kIGNoYXJhY3RlcmlzdGljczwvdT4NCg0KRnJvbSB0aGUgc3VtbWFyeSBvZiBhYnVuZGFuY2UgdGFibGUsIHRoZSB2YWx1ZSBvZiAwLjUzNTkgY29ycmVzcG9uZHMgdG8gdGhlIG1lYW4sIGkuZS4sIDEwMDAgY291bnRzIC8gMTg2NiBkZXRlY3RlZCBBU1ZzLg0KDQpgYGB7cn0NCmlzLm9iamVjdChtZXRhZGF0YWFydGljbGUyKQ0KaXMub2JqZWN0KHRheGEpDQppcy5vYmplY3QoYXN2X3RhYmxlMikNCg0KY2xhc3MobWV0YWRhdGFhcnRpY2xlMikNCmNsYXNzKHRheGEpDQpjbGFzcyhhc3ZfdGFibGUyKQ0KDQpzdW1tYXJ5KG1ldGFkYXRhYXJ0aWNsZTIpDQpoZWFkKHJvdy5uYW1lcyhtZXRhZGF0YWFydGljbGUyKSwyMCkNCmhlYWQoY29sbmFtZXMobWV0YWRhdGFhcnRpY2xlMiksMjApDQpkaW0odGF4YSkNCnN1bW1hcnkodGF4YSkNCmxlbmd0aCh1bmlxdWUodGF4YSRGRUFUVVJFX0lEKSkNCm1heCh0YXhhJEZFQVRVUkVfSUQpDQptaW4odGF4YSRGRUFUVVJFX0lEKQ0KaGVhZChyb3cubmFtZXModGF4YSksMjApDQpoZWFkKGNvbG5hbWVzKHRheGEpLDIwKQ0Kc3VtbWFyeShhc3ZfdGFibGUyKQ0KaGVhZChyb3cubmFtZXMoYXN2X3RhYmxlMiksMjApDQpoZWFkKGNvbG5hbWVzKGFzdl90YWJsZTIpLDIwKQ0KYGBgDQoNCkNoYW5nZSBjb2x1bW4gbmFtZXMgaW4gdGF4YSB0byBtYXRjaCByb3cgbmFtZXMgb2YgbWV0YWRhdGEgKGJlZm9yZSBzZWxlY3Rpbmcgc2FtcGxlcykgYW5kIGNoYW5nZSBBU1YgaWRlbnRpZmllcnMgdXNpbmcgQVNWXyNfR2VudXMuDQoNCmBgYHtyfQ0KdGF4YQ0KY29sbmFtZXModGF4YSkNCmhlYWQodGF4YSRGRUFUVVJFX0lELDI1KQ0KaGVhZCh0YXhhJHRheG9ub215LDI1KQ0KdGF4YSRBU1ZfR2VudXMgPC0gbGlzdG51bWJlcjINCmhlYWQodGF4YSRBU1ZfR2VudXMsMjUpDQpoZWFkKHJvdy5uYW1lcyh0YXhhKSwyMCkNCnJvdy5uYW1lcyh0YXhhKSA8LSB0YXhhJEFTVl9HZW51cw0KY29sbmFtZXModGF4YSkNCnRheGEgPC0gdGF4YVssLTFdDQpjb2xuYW1lcyh0YXhhKQ0KdGF4YSA8LSB0YXhhWywtMTI0XQ0KdGF4YSA8LSB0YXhhWywtMTI0XQ0KY29sbmFtZXModGF4YSkNCnRheGFbMToxMCwxOjEwXQ0KDQojQ2hhbmdlIGNvbHVtbiBuYW1lcyBvZiB0YXhhIGJhc2VkIG9uIHJvdyBuYW1lcyBvZiBtZXRhZGF0YS4gRGlzdGluY3Rpb25zIGJldHdlZW4gSHlwaGVuIG9yIERhc2gsIGFuZCBVbmRlcnNjb3JlcyEgDQpjb2xuYW1lcyh0YXhhKQ0KZGltKHRheGEpDQpyb3duYW1lcyhtZXRhZGF0YSkNCmRpbShtZXRhZGF0YSkNCg0KY29sbmFtZXModGF4YSkgPC0gcm93bmFtZXMobWV0YWRhdGEpDQpjb2xuYW1lcyh0YXhhKQ0KDQojdGhlIGlkZW50aWNhbCgpIGZ1bmN0aW9uIGluIFIgZG9lcyBjaGVjayBib3RoIHRoZSB2YWx1ZXMgYW5kIHRoZWlyIG9yZGVyLg0KaWRlbnRpY2FsKGNvbG5hbWVzKHRheGEpLCByb3duYW1lcyhtZXRhZGF0YSkpDQpgYGANCg0KDQojIyA2LiA8dT5TYW1wbGUgc2VsZWN0aW9uIGZvciBoZWF0bWFwIGNvbnN0cnVjdGlvbjwvdT4NCg0KVG8gY29uc3RydWN0IHRoZSBoZWF0bWFwLCB3ZSBzZWxlY3QgbWV0YWRhdGFhcnRpY2xlMyBpbmNsdWRpbmcgdGhlIG1ldGFib2xpYyBwaGFzZXM6IEVsb25nX0gyQ08yX0F1dG8sIEhvbW9hY2V0b2dlbmVzaXNfQXV0bywgSG9tb2FjZXRvZ2VuZXNpc19NaXhvLCBFbG9uZ19IMkNPMl9NaXhvLCBFbG9uZ19FdE9IJkxhY3RhdGUsIGFuZCBBY2lkb2dlbmVzaXMuDQoNCklmIG1ldGhhbm9nZW5lc2lzIGluY2x1ZGVkLCBtYWtlIHJlZmVyZW5jZSB0byBtZXRhZGF0YWFydGljbGU0Lg0KDQpgYGB7cn0NCmNvbHVtbnNfdG9fa2VlcCA8LSByb3duYW1lcyhtZXRhZGF0YWFydGljbGUzKQ0KY29sdW1uc190b19rZWVwDQoNCmNvbHVtbnNfdG9fa2VlcDIgPC0gcm93bmFtZXMobWV0YWRhdGFhcnRpY2xlNCkNCmNvbHVtbnNfdG9fa2VlcDINCg0KIyBTdWJzZXQgdGhlIGRhdGFmcmFtZSB0byBpbmNsdWRlIG9ubHkgdGhlIHNwZWNpZmllZCBjb2x1bW5zDQp0YXhhX3N1YnNldCA8LSB0YXhhWywgY29sdW1uc190b19rZWVwXQ0KdGF4YV9zdWJzZXQNCmRpbSh0YXhhX3N1YnNldCkNCg0KdGF4YV9zdWJzZXQyIDwtIHRheGFbLCBjb2x1bW5zX3RvX2tlZXAyXQ0KdGF4YV9zdWJzZXQyDQpkaW0odGF4YV9zdWJzZXQyKQ0KdGF4YV9zdWJzZXQyWzE6MjAsMToyMF0NCg0KaGVhZChyb3dTdW1zKHRheGEpLDIwKQ0KaGVhZChyb3dTdW1zKHRheGFfc3Vic2V0KSwyMCkNCmhlYWQocm93U3Vtcyh0YXhhX3N1YnNldDIpLDIwKQ0KDQpsZW5ndGgocm93U3Vtcyh0YXhhKSkNCmxlbmd0aChyb3dTdW1zKHRheGFfc3Vic2V0KSkNCmxlbmd0aChyb3dTdW1zKHRheGFfc3Vic2V0MikpDQpgYGANCg0KSW4gdGF4YSBmaWxlLCBzZWxlY3QgQVNWcyB3aXRoIGEgcmVsYXRpdmUgYWJ1bmRhbmNlIG9mIDAgaW4gYWxsIHNhbXBsZXMuDQoNCmBgYHtyfQ0KYXN2c196ZXJvX3N1bSA8LSByb3duYW1lcyh0YXhhKVtyb3dTdW1zKHRheGEpID09IDBdDQpsZW5ndGgoYXN2c196ZXJvX3N1bSkNCg0KYXN2c196ZXJvX3N1bTIgPC0gcm93bmFtZXModGF4YV9zdWJzZXQpW3Jvd1N1bXModGF4YV9zdWJzZXQpID09IDBdDQpsZW5ndGgoYXN2c196ZXJvX3N1bTIpDQphc3ZzX3plcm9fc3VtMg0KDQphc3ZzX3plcm9fc3VtMyA8LSByb3duYW1lcyh0YXhhX3N1YnNldDIpW3Jvd1N1bXModGF4YV9zdWJzZXQyKSA9PSAwXQ0KbGVuZ3RoKGFzdnNfemVyb19zdW0zKQ0KYGBgDQoNCkluIHRheGEgZmlsZSwgc2VsZWN0IEFTVnMgd2l0aCBhIHJlbGF0aXZlIGFidW5kYW5jZSBncmVhdGhlciB0aGFuIDAgaW4gYWxsIHNhbXBsZXMuDQo2MzUgQVNWcyBkZXRlY3RlZCBpbiBFbG9uZ19IMkNPMl9BdXRvLCBIb21vYWNldG9nZW5lc2lzX0F1dG8sIEhvbW9hY2V0b2dlbmVzaXNfTWl4bywgRWxvbmdfSDJDTzJfTWl4bywgRWxvbmdfRXRPSCZMYWN0YXRlLCBhbmQgQWNpZG9nZW5lc2lzLg0KDQpgYGB7cn0NCmFzdnNfemVyb19zdW00IDwtIHJvd25hbWVzKHRheGFfc3Vic2V0KVtyb3dTdW1zKHRheGFfc3Vic2V0KSAhPSAwXQ0KbGVuZ3RoKGFzdnNfemVyb19zdW00KQ0KYXN2c196ZXJvX3N1bTQNCmBgYA0KDQpTZWxlY3QgQVNWcyBncmVhdGVyIHRoYW4gemVybyBpbiB0YXhhX3N1YnNldA0KDQpgYGB7cn0NCnRheGFfc3Vic2V0DQphc3ZzX3plcm9fc3VtNA0KY2xhc3ModGF4YV9zdWJzZXQpDQpjbGFzcyhhc3ZzX3plcm9fc3VtNCkNCg0Kc2VsZWN0ZWRfcm93cyA8LSB0YXhhX3N1YnNldFtyb3duYW1lcyh0YXhhX3N1YnNldCkgJWluJSBhc3ZzX3plcm9fc3VtNCwgXQ0Kc2VsZWN0ZWRfcm93cw0KZGltKHNlbGVjdGVkX3Jvd3MpDQoNCnN1bW1hcnkoc2VsZWN0ZWRfcm93cykNCg0KYXN2X2NvdW50c19wZXJfc2FtcGxlIDwtIGFwcGx5KHNlbGVjdGVkX3Jvd3MsIDIsIGZ1bmN0aW9uKHgpIHN1bSh4ID4gMCkpDQphc3ZfY291bnRzX3Blcl9zYW1wbGUNCmBgYA0KDQpJZiB5b3Ugd2FudCB0byBkZXRlcm1pbmUgaG93IG1hbnkgdGltZXMgYW4gQVNWIGlzIGRldGVjdGVkIGluIHNlbGVjdGVkIHNhbXBsZXMsIHlvdSBjYW4gdXNlIHRoZSBSIGV4cHJlc3Npb24gZnVuY3Rpb24oeCkgc3VtKHggPiAwKS4gVGhpcyBkZWZpbmVzIGFuIGFub255bW91cyBmdW5jdGlvbiB0aGF0IGNvdW50cyB0aGUgbnVtYmVyIG9mIGVsZW1lbnRzIGluIHggdGhhdCBhcmUgZ3JlYXRlciB0aGFuIDAuDQoNCmBgYHtyfQ0KYXN2X2RldGVjdGlvbl9jb3VudHMgPC0gYXBwbHkoc2VsZWN0ZWRfcm93cywgMSwgZnVuY3Rpb24oeCkgc3VtKHggPiAwKSkNCmFzdl9kZXRlY3Rpb25fY291bnRzDQphc3ZfZGV0ZWN0aW9uX2NvdW50c19zb3J0ZWQgPC0gc29ydChhc3ZfZGV0ZWN0aW9uX2NvdW50cywgZGVjcmVhc2luZyA9IFRSVUUpDQphc3ZfZGV0ZWN0aW9uX2NvdW50c19zb3J0ZWQNCmBgYA0KDQojIyA3LiA8dT5Bbm5vdGF0aW9ucyBhbmQgY29sb3IgcGFsZXR0ZXMgZm9yIGhlYXRtYXAgY29uc3RydWN0aW9uPC91Pg0KDQpgYGB7cn0NCiNEZWZpbmUgYW5ub3RhdGlvbnMNCmRmIDwtIG1ldGFkYXRhYXJ0aWNsZTNbLGMoIk1haW5fTWV0YWJvbGljX1BoYXNlIiwiTWV0YWJvbGlzbV9DYXJib24iLCJSZWFjdG9yIildDQpjb2xuYW1lcyhkZikNCmRmDQpjbGFzcyhkZikNCg0KZGYyIDwtIG1ldGFkYXRhYXJ0aWNsZTRbLGMoIk1haW5fTWV0YWJvbGljX1BoYXNlIiwiTWV0YWJvbGlzbV9DYXJib24iLCJSZWFjdG9yIildDQpjb2xuYW1lcyhkZjIpDQoNCiNDb2xvciBwYWxldHRlDQpteXBhbDUgPC0gY29sb3JSYW1wUGFsZXR0ZShyZXYoYnJld2VyLnBhbChuID0gNywgbmFtZSA9ICJSZFlsQnUiKSkpKDEwMCkNCm15cGFsNQ0KDQpsZW5ndGgobXlwYWw1KQ0KDQojIFZpc3VhbGl6ZSB0aGUgcGFsZXR0ZSBhcyBhIGhvcml6b250YWwgc3RyaXANCmltYWdlKDE6MTAwLCAxLCBhcy5tYXRyaXgoMToxMDApLCBjb2wgPSBteXBhbDUsIGF4ZXMgPSBGQUxTRSwgeGxhYiA9ICIiLCB5bGFiID0gIiIpDQoNCmFubm90YXRpb25fY29sb3JzIDwtIGxpc3QoDQogIFJlYWN0b3IgPSBjKFIxID0gIiNiMWIxYjEiLCBSMiA9ICIjN0YxNzM0IiksDQogIE1ldGFib2xpc21fQ2FyYm9uID0gYyhBdXRvdHJvcGhpYyA9ICIjMWU3ZGZmIiwgTWl4b3Ryb3BoaWMgPSAiI2ZmN2IwMCIpLA0KICBNYWluX01ldGFib2xpY19QaGFzZSA9IGMoDQogICAgQWNpZG9nZW5lc2lzID0gIiMzMGQ1YzgiLCANCiAgICBgRWxvbmdfRXRPSCZMYWN0YXRlYCA9ICIjZmZkNzAwIiwgDQogICAgYEVsb25nX0gyQ08yX0F1dG9gID0gIiNmZjZjZGEiLCANCiAgICBgRWxvbmdfSDJDTzJfTWl4b2AgPSAiIzlhMDA5YSIsIA0KICAgIEhvbW9hY2V0b2dlbmVzaXNfQXV0byA9ICIjMDA4MTAwIiwgDQogICAgSG9tb2FjZXRvZ2VuZXNpc19NaXhvID0gIiM5M2M1NzEiICApDQopDQoNCmFubm90YXRpb25fY29sb3JzMiA8LSBsaXN0KA0KICBSZWFjdG9yID0gYyhSMSA9ICIjYjFiMWIxIiwgUjIgPSAiIzdGMTczNCIpLA0KICBNZXRhYm9saXNtX0NhcmJvbiA9IGMoQXV0b3Ryb3BoaWMgPSAiIzFlN2RmZiIsIE1peG90cm9waGljID0gIiNmZjdiMDAiKSwNCiAgTWFpbl9NZXRhYm9saWNfUGhhc2UgPSBjKA0KICAgIEFjaWRvZ2VuZXNpcyA9ICIjMzBkNWM4IiwgDQogICAgYEVsb25nX0V0T0gmTGFjdGF0ZWAgPSAiI2ZmZDcwMCIsIA0KICAgIGBFbG9uZ19IMkNPMl9BdXRvYCA9ICIjZmY2Y2RhIiwgDQogICAgYEVsb25nX0gyQ08yX01peG9gID0gIiM5YTAwOWEiLCANCiAgICBIb21vYWNldG9nZW5lc2lzX0F1dG8gPSAiIzAwODEwMCIsIA0KICAgIEhvbW9hY2V0b2dlbmVzaXNfTWl4byA9ICIjOTNjNTcxIiwNCiAgICBNZXRoYW5vZ2VuZXNpcyA9ICJibHVlIikNCikNCg0KYW5ub3RhdGlvbl9jb2xvcnMzIDwtIGxpc3QoDQogIFJlYWN0b3IgPSBjKFIxID0gIiNiMWIxYjEiLCBSMiA9ICIjN0YxNzM0IiksDQogIE1ldGFib2xpc21fQ2FyYm9uID0gYyhBdXRvdHJvcGhpYyA9ICIjMWU3ZGZmIiwgTWl4b3Ryb3BoaWMgPSAiI2ZmN2IwMCIpLA0KICBNYWluX01ldGFib2xpY19QaGFzZSA9IGMoDQogICAgQWNpZG9nZW5lc2lzID0gIiMzMGQ1YzgiLCANCiAgICBgRWxvbmdfRXRPSCZMYWN0YXRlYCA9ICIjZmZkNzAwIiwgDQogICAgYEVsb25nX0gyQ08yX0F1dG9gID0gIiNmZjZjZGEiLCANCiAgICBgRWxvbmdfSDJDTzJfTWl4b2AgPSAiIzlhMDA5YSIsIA0KICAgIEhvbW9hY2V0b2dlbmVzaXNfQXV0byA9ICIjMDA4MTAwIiwgDQogICAgSG9tb2FjZXRvZ2VuZXNpc19NaXhvID0gIiM5M2M1NzEiICApLA0KICBQaHlsdW0gPSBjKA0KICAicF9fQWN0aW5vYmFjdGVyaW90YSIgICAgICAgICAgICAgPSAiI2ZmMDAwMCIsDQogICJwX19CYWN0ZXJvaWRvdGEiICAgICAgICAgICAgICAgICA9ICIjMDAwMDAwIiwNCiAgInBfX0Zpcm1pY3V0ZXMiICAgICAgICAgICAgICAgICAgID0gIiNmZmZmMDAiKQ0KKQ0KDQpgYGANCg0KVG8gYWRkIHJvdyBhbm5vdGF0aW9ucyBpbiB0aGUgaGVhdG1hcC4NCg0KYGBge3J9DQphc3ZfYW5ub3RhdGlvbiA8LSBhc3ZfdGFibGUyWywgYygiQVNWX0dlbnVzIiwgIlBoeWx1bSIpXQ0Kcm93bmFtZXMoYXN2X2Fubm90YXRpb24pIDwtIGFzdl9hbm5vdGF0aW9uJEFTVl9HZW51cw0KaGVhZChhc3ZfYW5ub3RhdGlvbikNCmFzdl9hbm5vdGF0aW9uIDwtIGFzdl9hbm5vdGF0aW9uWywgIlBoeWx1bSIsIGRyb3AgPSBGQUxTRV0NCmhlYWQoYXN2X2Fubm90YXRpb24pDQp1bmlxdWUoYXN2X2Fubm90YXRpb24kUGh5bHVtKQ0KDQppZGVudGljYWwocm93Lm5hbWVzKGFzdl9hbm5vdGF0aW9uKSxyb3cubmFtZXModGF4YV9zdWJzZXQpKQ0KDQpjbGFzcyhhc3ZfYW5ub3RhdGlvbikNCg0KdGF4YXRvcDI1Zg0KbGlzdCA8LSByb3cubmFtZXModGF4YXRvcDI1ZikNCmxpc3QNCmFzdl9hbm5vdGF0aW9uDQpyb3duYW1lcyhhc3ZfYW5ub3RhdGlvbikNCmRpbShhc3ZfYW5ub3RhdGlvbikNCg0KYXN2X2Fubm90YXRpb25fc3Vic2V0IDwtIGFzdl9hbm5vdGF0aW9uW3Jvd25hbWVzKGFzdl9hbm5vdGF0aW9uKSAlaW4lIHJvd25hbWVzKHRheGF0b3AyNWYpLCAsIGRyb3AgPSBGQUxTRV0NCmFzdl9hbm5vdGF0aW9uX3N1YnNldA0KY2xhc3MoYXN2X2Fubm90YXRpb25fc3Vic2V0KQ0KYGBgDQoNCiMjIDx1PjguIFNlbGVjdCB0aGUgVE9QMjUvMzAgdGF4YSBpbiBhbGwgc2VsZWN0ZWQgc2FtcGxlczwvdT4NCg0KYGBge3J9DQp0YXhhX3N1YnNldA0KdGF4YV9zdWJzZXR0IDwtIHQodGF4YV9zdWJzZXQpDQp0YXhhX3N1YnNldHRfZGVjciA8LSB0YXhhX3N1YnNldHRbLG9yZGVyKGNvbFN1bXModGF4YV9zdWJzZXR0KSxkZWNyZWFzaW5nPVRSVUUpXQ0Kcm93bmFtZXModGF4YV9zdWJzZXR0X2RlY3IpDQpjb2xuYW1lcyh0YXhhX3N1YnNldHRfZGVjcikNCnRvcDI1bGlzdCA8LSBjb2xuYW1lcyh0YXhhX3N1YnNldHRfZGVjcilbMToyNV0NCnRvcDI1bGlzdA0KdGF4YXRvcDI1IDwtIGRhdGEuZnJhbWUodGF4YV9zdWJzZXR0X2RlY3JbLGNvbG5hbWVzKHRheGFfc3Vic2V0dF9kZWNyKSAlaW4lIHRvcDI1bGlzdF0sIGNoZWNrLm5hbWVzID0gRkFMU0UpDQp0YXhhdG9wMjUNCnRheGF0b3AyNWYgPC0gdCh0YXhhdG9wMjUpDQp0YXhhdG9wMjVmDQpjb2xuYW1lcyh0YXhhdG9wMjVmKQ0KY29sU3Vtcyh0YXhhdG9wMjVmKQ0Kcm93U3Vtcyh0YXhhdG9wMjVmKQ0Kd3JpdGUuY3N2KHRheGF0b3AyNWYsICJ0YXhhdG9wMjVmLmNzdiIsIHJvdy5uYW1lcyA9IFRSVUUpDQoNCncgPC0gdGF4YXRvcDI1ZisxDQp3cml0ZS5jc3YodywgInRheGF0b3AyNWZ3aXRob25lLmNzdiIsIHJvdy5uYW1lcyA9IFRSVUUpDQoNCnRheGFfc3Vic2V0Mg0KdGF4YV9zdWJzZXQydCA8LSB0KHRheGFfc3Vic2V0MikNCnRheGFfc3Vic2V0MnRfZGVjciA8LSB0YXhhX3N1YnNldDJ0WyxvcmRlcihjb2xTdW1zKHRheGFfc3Vic2V0MnQpLGRlY3JlYXNpbmc9VFJVRSldDQpyb3duYW1lcyh0YXhhX3N1YnNldDJ0X2RlY3IpDQpjb2xuYW1lcyh0YXhhX3N1YnNldDJ0X2RlY3IpDQp0b3AzMGxpc3RtIDwtIGNvbG5hbWVzKHRheGFfc3Vic2V0MnRfZGVjcilbMTozMF0NCnRvcDMwbGlzdG0NCnRheGF0b3AzMG0gPC0gZGF0YS5mcmFtZSh0YXhhX3N1YnNldDJ0X2RlY3JbLGNvbG5hbWVzKHRheGFfc3Vic2V0MnRfZGVjcikgJWluJSB0b3AzMGxpc3RtXSwgY2hlY2submFtZXMgPSBGQUxTRSkNCnRheGF0b3AzMG0NCnRheGF0b3AzMG1mIDwtIHQodGF4YXRvcDMwbSkNCnRheGF0b3AzMG1mDQpjb2xTdW1zKHRheGF0b3AzMG1mKQ0KYGBgDQoNCg0KIyA8dT45LiBEYXRhIHRyYW5zZm9ybWF0aW9uIGFuZCBoZWF0bWFwIGNvbnN0cnVjdGlvbjwvdT4NCg0KQWRkIDEgdG8geW91ciByYXJlZmllZCBjb3VudHMgYmVmb3JlIGxvZyB0cmFuc2Zvcm1hdGlvbjoNCmxvZyh4ICsgMSkgaXMgYSBzdGFuZGFyZCBwcmVwcm9jZXNzaW5nIHN0ZXAuDQoNCmBgYHtyfQ0KdGF4YXRvcDI1bm96ZXJvIDwtIHRheGF0b3AyNWYrMQ0Kd3JpdGUuY3N2KHRheGF0b3AyNW5vemVybywgInRheGF0b3AyNWZub3plcm9fMi5jc3YiLCByb3cubmFtZXMgPSBUUlVFKQ0KDQpyYW5nZSh0YXhhdG9wMjVub3plcm8sIG5hLnJtID0gVFJVRSkNCmBgYA0KDQpFdWNsaWRlYW4gZGlzdGFuY2VzIGFyZSB1c2VkIGJ5IGRlZmF1bHQgaW4gcGhlYXRtYXAsIGZvciBib3RoIHJvd3MgYW5kIGNvbHVtbnMgKGNsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9ICJldWNsaWRlYW4iOyBjbHVzdGVyaW5nX2Rpc3RhbmNlX2NvbHMgPSAiZXVjbGlkZWFuIikNCg0KIyMgOS4xLiBMb2cyIHRyYW5zZm9ybWF0aW9uDQoNCkxPRzIoMSkgPSAwDQpMT0cyKDApIHVuZGVmaW5lZA0KDQpgYGB7cn0NCmRhdGxvZzIgPC0gbG9nMih0YXhhdG9wMjVmKzEpDQoNCnJhbmdlKGRhdGxvZzIsIG5hLnJtID0gVFJVRSkNCnN1bW1hcnkoYXMudmVjdG9yKGRhdGxvZzIpKQ0KDQpzdHIoZGF0bG9nMikNCmlzLm1hdHJpeChkYXRsb2cyKQ0KaXMubnVtZXJpYyhkYXRsb2cyKQ0KYGBgDQoNClRoaXMgY29tbWFuZCB1c2VzIHRoZSBwaGVhdG1hcCBwYWNrYWdlIGluIFIgdG8gZ2VuZXJhdGUgYSBoZWF0bWFwIGFuZCBzdG9yZXMgdGhlIHJlc3VsdCAoaW5jbHVkaW5nIGRlbmRyb2dyYW0gaW5mb3JtYXRpb24pIGluIHRoZSBvYmplY3QgcGxvZzJfYXZlcmFnZS4gcGhlYXRtYXAoKSB1c2VzIGhlcmUgYXZlcmFnZSBsaW5rYWdlIChVUEdNQSkgZm9yIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIHJvd3MgYW5kIGNvbHVtbnMuIGN1dHJlZV9jb2xzID0gNyBjdXRzIHRoZSBjb2x1bW4gZGVuZHJvZ3JhbSBpbnRvIDcgY2x1c3RlcnMuIFRoaXMgbWVhbnM6IENvbHVtbnMgKHNhbXBsZXMpIHdpbGwgYmUgZ3JvdXBlZCBpbnRvIDcgY2x1c3RlcnMsIGFuZCB0aGVzZSBjbHVzdGVycyBhcmUgc2hvd24gd2l0aCBjb2xvcmVkIGJhcnMgYmFzZWQgb24gZGVmaW5lZCBhbm5vdGF0aW9ucy4gY3V0cmVlX3Jvd3MgPSAxMCBjdXRzIHRoZSByb3cgZGVuZHJvZ3JhbSBpbnRvIDEwIGNsdXN0ZXJzLiBUaGlzIGdyb3VwcyB0aGUgdGF4YSAocm93cykgaW50byAxMCBjbHVzdGVycywgYW5kIGhlbHBzIHZpc3VhbGx5IGlkZW50aWZ5IGdyb3VwcyBvZiBzaW1pbGFyIGZlYXR1cmVzLg0KDQpgYGB7cn0NCnRpZmYoInBsb2cyX2F2ZXJhZ2VfMi50aWZmIiwgd2lkdGggPSAxNiwgaGVpZ2h0ID0gOCwgdW5pdHMgPSAiaW4iLCByZXMgPSAzMDApDQpwbG9nMl9hdmVyYWdlIDwtIHBoZWF0bWFwKGRhdGxvZzIsIG15cGFsNSwgY2x1c3RlcmluZ19tZXRob2Q9ImF2ZXJhZ2UiLCBicmVha3MgPSBOQSwgc2NhbGUgPSAibm9uZSIsIGZvbnRzaXplPTEwLCBmb250c2l6ZV9yb3c9OCwgZm9udHNpemVfY29sPTgsIGFubm90YXRpb25fY29sPWRmLCBhbm5vdGF0aW9uX2NvbG9ycyA9IGFubm90YXRpb25fY29sb3JzLCBjdXRyZWVfY29scyA9IDcsIGN1dHJlZV9yb3dzID0gMTApDQpkZXYub2ZmKCkNCmBgYA0KDQpgYGB7cn0NCnRpZmYoInBsb2cyX2F2ZXJhZ2VfMl9yb3dzLnRpZmYiLCB3aWR0aCA9IDE2LCBoZWlnaHQgPSA4LCB1bml0cyA9ICJpbiIsIHJlcyA9IDMwMCkNCnBsb2cyX2F2ZXJhZ2UgPC0gcGhlYXRtYXAoZGF0bG9nMiwgbXlwYWw1LCBjbHVzdGVyaW5nX21ldGhvZD0iYXZlcmFnZSIsIGJyZWFrcyA9IE5BLCBzY2FsZSA9ICJub25lIiwgZm9udHNpemU9MTAsIGZvbnRzaXplX3Jvdz04LCBmb250c2l6ZV9jb2w9OCwgYW5ub3RhdGlvbl9jb2w9ZGYsIGFubm90YXRpb25fcm93PWFzdl9hbm5vdGF0aW9uX3N1YnNldCwgYW5ub3RhdGlvbl9jb2xvcnMgPSBhbm5vdGF0aW9uX2NvbG9yczMsIGN1dHJlZV9jb2xzID0gNywgY3V0cmVlX3Jvd3MgPSAxMCkNCmRldi5vZmYoKQ0KYGBgDQoNCg0KRGVmaW5lIHNjYWxlIG9mIGNvbG9yIHBhbGV0dGUuDQoNCmBgYHtyfQ0KbXlfYnJlYWtzIDwtIHNlcSgwLCAxMCwgYnkgPSAwLjUpDQpteXBhbDYgPC0gY29sb3JSYW1wUGFsZXR0ZShSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoOSwgIllsR25CdSIpKShsZW5ndGgobXlfYnJlYWtzKSAtIDEpDQpteXBhbDYgPC0gY29sb3JSYW1wUGFsZXR0ZShyZXYoYnJld2VyLnBhbChuID0gNywgbmFtZSA9ICJSZFlsQnUiKSkpKDIwKQ0KbXlwYWw2DQoNCmxlbmd0aChteXBhbDUpDQpsZW5ndGgobXlwYWw2KQ0KDQpzdHIobXlfYnJlYWtzKQ0KaXMubnVtZXJpYyhteV9icmVha3MpICAgICAgICAgICAgIA0KYWxsKGlzLmZpbml0ZShteV9icmVha3MpKSAgICAgICAgICANCmxlbmd0aChteXBhbDUpID09IGxlbmd0aChteV9icmVha3MpIC0gMQ0KcHJpbnQobGVuZ3RoKG15cGFsNSkpDQoNCnRpZmYoInBsb2cyX2F2ZXJhZ2VfMl9zY2FsZWNvbG9yLnRpZmYiLCB3aWR0aCA9IDE2LCBoZWlnaHQgPSA4LCB1bml0cyA9ICJpbiIsIHJlcyA9IDMwMCkNCnBsb2cyX2F2ZXJhZ2UgPC0gcGhlYXRtYXAobWF0ID0gZGF0bG9nMiwgY29sb3IgPSBteXBhbDYsIGNsdXN0ZXJpbmdfbWV0aG9kPSJhdmVyYWdlIiwgYnJlYWtzID0gbXlfYnJlYWtzLCBzY2FsZSA9ICJub25lIiwgZm9udHNpemU9MTAsIGZvbnRzaXplX3Jvdz04LCBmb250c2l6ZV9jb2w9OCwgYW5ub3RhdGlvbl9jb2w9ZGYsIGFubm90YXRpb25fY29sb3JzID0gYW5ub3RhdGlvbl9jb2xvcnMsIGN1dHJlZV9jb2xzID0gNywgY3V0cmVlX3Jvd3MgPSAxMCkNCmRldi5vZmYoKQ0KYGBgDQoNCiMjIDkuMi4gTG9nMTAgdHJhbnNmb3JtYXRpb24NCg0KYGBge3J9DQpkYXRsb2cxMCA8LSBsb2cxMCh0YXhhdG9wMjVmKzEpDQoNCnJhbmdlKGRhdGxvZzEwLCBuYS5ybSA9IFRSVUUpDQpzdW1tYXJ5KGFzLnZlY3RvcihkYXRsb2cxMCkpDQoNCnN0cihkYXRsb2cxMCkNCmlzLm1hdHJpeChkYXRsb2cxMCkNCmlzLm51bWVyaWMoZGF0bG9nMTApDQpgYGANCg0KYGBge3J9DQp0aWZmKCJwbG9nMTBfYXZlcmFnZV8yLnRpZmYiLCB3aWR0aCA9IDE2LCBoZWlnaHQgPSA4LCB1bml0cyA9ICJpbiIsIHJlcyA9IDMwMCkNCnBsb2cxMF9hdmVyYWdlIDwtIHBoZWF0bWFwKGRhdGxvZzEwLCBteXBhbDUsIGNsdXN0ZXJpbmdfbWV0aG9kPSJhdmVyYWdlIiwgYnJlYWtzID0gTkEsIHNjYWxlID0gIm5vbmUiLCBmb250c2l6ZT0xMCwgZm9udHNpemVfcm93PTgsIGZvbnRzaXplX2NvbD04LCBhbm5vdGF0aW9uX2NvbD1kZiwgYW5ub3RhdGlvbl9jb2xvcnMgPSBhbm5vdGF0aW9uX2NvbG9ycywgY3V0cmVlX2NvbHMgPSA3LCBjdXRyZWVfcm93cyA9IDEwKQ0KZGV2Lm9mZigpDQpgYGANCg0KIyMgOS4zLiBjbHIgdHJhbnNmb3JtYXRpb24NCg0KY2xyIChDZW50ZXJlZCBMb2ctUmF0aW8pIHRyYW5zZm9ybWF0aW9uIGZyb20gdGhlIHBhY2thZ2UgY29tcG9zaXRpb25zLiBjbHIgdXNlcyB0aGUgZ2VvbWV0cmljIG1lYW4gb2YgYWxsIGNvbXBvbmVudHMgKGluIG1pY3JvYmlvbWUgZGF0YSwgZWFjaCBjb21wb25lbnQgaW4gYSBjb21wb3NpdGlvbmFsIHZlY3RvciBjb3JyZXNwb25kcyB0byBhIHNwZWNpZmljIEFTViBvciBPVFUpLg0KDQpgYGB7cn0NCnRheGF0b3AyNWYNCnJvdy5uYW1lcyh0YXhhdG9wMjVmKQ0KY29sbmFtZXModGF4YXRvcDI1ZikNCg0KdGF4YXRvcDI1ZjIgPC0gdGF4YXRvcDI1ZisxDQp3cml0ZS5jc3YodGF4YXRvcDI1ZjIsICJ0YXhhdG9wMjVmcGx1c29uZS5jc3YiLCByb3cubmFtZXMgPSBUUlVFKQ0KDQpkYXRjbHIgPC0gY2xyKGFjb21wKHRheGF0b3AyNWYrMSkpDQoNCnJhbmdlKGRhdGNsciwgbmEucm0gPSBUUlVFKQ0Kc3VtbWFyeShhcy52ZWN0b3IoZGF0Y2xyKSkNCg0KY2xhc3MoZGF0Y2xyKQ0KaXMubWF0cml4KGRhdGNscikNCmlzLm51bWVyaWMoZGF0Y2xyKQ0KDQpkYXRjbHJfbWF0IDwtIGFzLm1hdHJpeChkYXRjbHIpDQpkYXRjbHJfbWF0DQp3cml0ZS5jc3YoZGF0Y2xyX21hdCwgImRhdGNscl9tYXQuY3N2Iiwgcm93Lm5hbWVzID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCnRpZmYoInBjbHJfYXZlcmFnZV8yLnRpZmYiLCB3aWR0aCA9IDE2LCBoZWlnaHQgPSA4LCB1bml0cyA9ICJpbiIsIHJlcyA9IDMwMCkNCnBjbHJfYXZlcmFnZSA8LSBwaGVhdG1hcChkYXRjbHIsIG15cGFsNSwgY2x1c3RlcmluZ19tZXRob2Q9ImNvbXBsZXRlIiwgYnJlYWtzID0gTkEsIHNjYWxlID0gIm5vbmUiLCBmb250c2l6ZT0xMCwgZm9udHNpemVfcm93PTgsIGZvbnRzaXplX2NvbD04LCBhbm5vdGF0aW9uX2NvbD1kZiwgYW5ub3RhdGlvbl9jb2xvcnMgPSBhbm5vdGF0aW9uX2NvbG9ycywgY3V0cmVlX2NvbHMgPSA3LCBjdXRyZWVfcm93cyA9IDEwKQ0KZGV2Lm9mZigpDQpgYGANCg0KYGBge3J9DQp0aWZmKCJwY2xyX2F2ZXJhZ2VfMl9tYXQudGlmZiIsIHdpZHRoID0gMTYsIGhlaWdodCA9IDgsIHVuaXRzID0gImluIiwgcmVzID0gMzAwKQ0KcGNscl9hdmVyYWdlIDwtIHBoZWF0bWFwKGRhdGNscl9tYXQsIG15cGFsNSwgY2x1c3RlcmluZ19tZXRob2Q9ImNvbXBsZXRlIiwgYnJlYWtzID0gTkEsIHNjYWxlID0gIm5vbmUiLCBmb250c2l6ZT0xMCwgZm9udHNpemVfcm93PTgsIGZvbnRzaXplX2NvbD04LCBhbm5vdGF0aW9uX2NvbD1kZiwgYW5ub3RhdGlvbl9jb2xvcnMgPSBhbm5vdGF0aW9uX2NvbG9ycywgY3V0cmVlX2NvbHMgPSA3LCBjdXRyZWVfcm93cyA9IDEwKQ0KZGV2Lm9mZigpDQpgYGANCg==