R Notebook: Provides reproducible analysis for Gain-of-Function Mutants in the following manuscript:

Citation: Romanowicz KJ, Resnick C, Hinton SR, Plesa C. Exploring antibiotic resistance in diverse homologs of the dihydrofolate reductase protein family through broad mutational scanning. bioRxiv, 2025.

GitHub Repository: https://github.com/PlesaLab/DHFR

NCBI BioProject: https://www.ncbi.nlm.nih.gov/bioproject/1189478

Experiment

This pipeline processes a library of 1,536 DHFR homologs and their associated mutants, with two-fold redundancy (two codon variants per sequence). Fitness scores are derived from a multiplexed in-vivo assay using a trimethoprim concentration gradient, assessing the ability of these homologs and their mutants to complement functionality in an E. coli knockout strain and their tolerance to trimethoprim treatment. This analysis provides insights into how antibiotic resistance evolves across a range of evolutionary starting points. Sequence data were generated using the Illumina NovaSeq platform with 100 bp paired-end sequencing of amplicons.

Methods overview to achieve a broad-mutational scan for DHFR homologs.
Methods overview to achieve a broad-mutational scan for DHFR homologs.

Packages

The following R packages must be installed prior to loading into the R session. See the Reproducibility tab for a complete list of packages and their versions used in this workflow.

# Load the latest version of python (3.10.14) for downstream use:
library(reticulate)
use_python("/Users/krom/miniforge3/bin/python3")

# Make a vector of required packages
required.packages <- c("ape", "bio3d", "Biostrings", "castor", "cowplot", "devtools", "dplyr", "ggExtra", "ggnewscale", "ggplot2", "ggridges", "ggtree", "ggtreeExtra", "glmnet", "gridExtra","igraph", "knitr", "matrixStats", "patchwork", "pheatmap", "purrr", "pscl", "RColorBrewer", "reshape","reshape2", "ROCR", "seqinr", "scales", "stringr", "stringi", "tidyr", "tidytree", "viridis")

# Load required packages with error handling
loaded.packages <- lapply(required.packages, function(package) {
  if (!require(package, character.only = TRUE)) {
    install.packages(package, dependencies = TRUE)
    if (!require(package, character.only = TRUE)) {
      message("Package ", package, " could not be installed and loaded.")
      return(NULL)
    }
  }
  return(package)
})

# Remove NULL entries from loaded packages
loaded.packages <- loaded.packages[!sapply(loaded.packages, is.null)]
Loaded packages: ape, bio3d, Biostrings, castor, cowplot, devtools, dplyr, ggExtra, ggnewscale, ggplot2, ggridges, ggtree, ggtreeExtra, glmnet, gridExtra, igraph, knitr, matrixStats, patchwork, pheatmap, purrr, pscl, RColorBrewer, reshape, reshape2, ROCR, seqinr, scales, stringr, stringi, tidyr, tidytree, viridis 

Import Data Files

Import MUTANTS files generated from DHFR.4.Mutants.RMD relevant for downstream analysis.

# mut_collapse_15
mut_collapse_15 <- read.csv("Mutants/mutants_files_formatted/mut_collapse_15.csv", 
                         header = TRUE, stringsAsFactors = FALSE)

Import BMS files generated from DHFR.5.BMS.RMD relevant for downstream analysis.

# protein_info_1H1T
protein_info_1H1T <- read.csv("BMS/bms_files_formatted/protein_info_1H1T.csv", 
                         header = TRUE, stringsAsFactors = FALSE)

# BMS_matrix15_perfects_and_1_melt
BMS_matrix15_perfects_and_1_melt <- read.csv("BMS/bms_files_formatted/BMS_matrix15_perfects_and_1_melt.csv", 
                         header = TRUE, stringsAsFactors = FALSE)

# BMS_matrix15_perfects_and_1_num_melt
BMS_matrix15_perfects_and_1_num_melt <- read.csv("BMS/bms_files_formatted/BMS_matrix15_perfects_and_1_num_melt.csv", 
                         header = TRUE, stringsAsFactors = FALSE)

GOF Mutants Analysis

This section is based on the R file: “R_dropout_GOF.R”. It describes how to determine if certain mutant versions of a designed homolog increase in fitness under trimethoprim selection (i.e., gain of function after mutation).

Complementation

Start with two histograms showing (1) the distribution of perfects (counts; y-axis) by fitness (x-axis) with fitness < -1 colored blue and fitness > -1 colored gold, and (2) the distribution of mutants (counts; y-axis) by fitness (x-axis) with fitness < -1 colored blue and fitness > -1 colored gold

Perfects: Smooth Histogram

# Subset mutants (mutations != 0)
L15_perfects_complementation <- mut_collapse_15 %>%
  filter(mutations == 0)

# Remove NA and infinite values for x-axis scaling
fitD05D03_perf_clean <- L15_perfects_complementation$fitD05D03[is.finite(L15_perfects_complementation$fitD05D03)]

# Calculate the range of the data
x_min_perf <- floor(min(fitD05D03_perf_clean, na.rm = TRUE))
x_max_perf <- ceiling(max(fitD05D03_perf_clean, na.rm = TRUE))

# Plot smooth density curve
L15_perfects_complementation_density <- ggplot(L15_perfects_complementation, aes(x = fitD05D03)) +
  geom_density(aes(y = after_stat(density * 100), 
                   fill = ifelse(fitD05D03 <= -1, "darkblue", "gold")), 
               alpha = 0.75) +
  geom_vline(xintercept = -1, linetype = "dashed", color = "black") +
  scale_fill_identity() +
  scale_x_continuous(breaks = seq(x_min_perf, x_max_perf, by = 1)) +
  scale_y_continuous(labels = function(x) paste0(x, "%")) +
  labs(x = "Median Fitness (LogFC)", y = "Percentage") +
  theme_minimal() +
  theme(axis.line = element_line(colour = 'black', size = 1.0), 
        axis.ticks = element_line(colour = "black", size = 1.0),
        axis.text.x = element_text(size = 14),
        axis.text.y = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        axis.title.y = element_text(size = 16),
        panel.background = element_blank(),
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        legend.title = element_blank(),
        legend.text = element_text(size = 12),
        legend.position = "none")
Warning: The `size` argument of `element_line()` is deprecated as of ggplot2 3.4.0.
Please use the `linewidth` argument instead.
# Display plot
print(L15_perfects_complementation_density)

# Subset mutants (mutations != 0)
L15_perfects_complementation <- mut_collapse_15 %>%
  filter(mutations == 0)

# Remove NA and infinite values for x-axis scaling
fitD05D03_perf_clean <- L15_perfects_complementation$fitD05D03[is.finite(L15_perfects_complementation$fitD05D03)]

# Calculate the range of the data
x_min_perf <- floor(min(fitD05D03_perf_clean, na.rm = TRUE))
x_max_perf <- ceiling(max(fitD05D03_perf_clean, na.rm = TRUE))

# Create density data
density_data <- density(L15_perfects_complementation$fitD05D03, na.rm = TRUE)

# Create a data frame from the density data
df_density <- data.frame(x = density_data$x, y = density_data$y)

# Split the data at x = -1
df_left <- df_density[df_density$x <= -1, ]
df_right <- df_density[df_density$x >= -1, ]

# Ensure the split point is included in both datasets
df_left <- rbind(df_left, data.frame(x = -1, y = df_left$y[nrow(df_left)]))
df_right <- rbind(data.frame(x = -1, y = df_right$y[1]), df_right)

# Plot using geom_area
L15_perfects_complementation_density <- ggplot() +
  geom_area(data = df_left, aes(x = x, y = y * 100), fill = "darkblue", alpha = 0.75) +
  geom_area(data = df_right, aes(x = x, y = y * 100), fill = "gold", alpha = 0.75) +
  geom_line(data = df_density, aes(x = x, y = y * 100), color = "black", size = 0.5) +  # Add black outline
  geom_vline(xintercept = -1, linetype = "dashed", color = "black") +
  scale_x_continuous(breaks = seq(x_min_perf, x_max_perf, by = 1)) +
  scale_y_continuous(labels = function(x) paste0(x, "%"), 
                     limits = c(0, max(df_density$y) * 100)) +  # Set y-axis limits
  labs(x = "Median Fitness (LogFC)", y = "Percentage") +
  theme_minimal() +
  theme(axis.line = element_line(colour = 'black', size = 1.0), 
        axis.ticks = element_line(colour = "black", size = 1.0),
        axis.text.x = element_text(size = 14),
        axis.text.y = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        axis.title.y = element_text(size = 16),
        panel.background = element_blank(),
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        legend.title = element_blank(),
        legend.text = element_text(size = 12),
        legend.position = "none")
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
Please use `linewidth` instead.
# Display plot
print(L15_perfects_complementation_density)

Percent of Perfects with Strong Depletion (fitness < -2.5)

# Calculate the percentages and counts
results <- L15_perfects_complementation %>%
  filter(!is.na(fitD05D03)) %>%  # Remove rows where fitD05D03 is NA
  summarise(
    total_unique_IDs = n_distinct(ID),
    IDs_below_neg2_5 = n_distinct(ID[fitD05D03 < -2.5]),
    percentage_below_neg2_5 = (IDs_below_neg2_5 / total_unique_IDs) * 100,
    IDs_below_neg1 = n_distinct(ID[fitD05D03 < -1]),
    IDs_above_or_equal_neg1 = n_distinct(ID[fitD05D03 >= -1]),
    percentage_below_neg1 = (IDs_below_neg1 / total_unique_IDs) * 100,
    percentage_above_or_equal_neg1 = (IDs_above_or_equal_neg1 / total_unique_IDs) * 100
  )

# Print the results
print(paste0("Percentage of unique IDs with fitD05D03 < -2.5: ", 
             round(results$percentage_below_neg2_5, 2), "%"))
[1] "Percentage of unique IDs with fitD05D03 < -2.5: 17%"
print(paste("Total unique IDs:", results$total_unique_IDs))
[1] "Total unique IDs: 794"
print(paste("Unique IDs with fitD05D03 < -2.5:", results$IDs_below_neg2_5))
[1] "Unique IDs with fitD05D03 < -2.5: 135"
print("\nAdditional fitness categories:")
[1] "\nAdditional fitness categories:"
print(paste("Unique IDs with fitD05D03 < -1:", results$IDs_below_neg1))
[1] "Unique IDs with fitD05D03 < -1: 378"
print(paste0("Percentage: ", round(results$percentage_below_neg1, 2), "%"))
[1] "Percentage: 47.61%"
print(paste("Unique IDs with fitD05D03 >= -1:", results$IDs_above_or_equal_neg1))
[1] "Unique IDs with fitD05D03 >= -1: 416"
print(paste0("Percentage: ", round(results$percentage_above_or_equal_neg1, 2), "%"))
[1] "Percentage: 52.39%"

Mutants: Smooth Histogram

# Subset mutants (mutations != 0)
L15_mutants_complementation <- mut_collapse_15 %>%
  filter(mutations > 0 & mutations < 6)

# Remove NA and infinite values for x-axis scaling
fitD05D03_mut_clean <- L15_mutants_complementation$fitD05D03[is.finite(L15_mutants_complementation$fitD05D03)]

# Calculate the range of the data
x_min_mut <- floor(min(fitD05D03_mut_clean, na.rm = TRUE))
x_max_mut <- ceiling(max(fitD05D03_mut_clean, na.rm = TRUE))

# Plot smooth density curve
L15_mutants_complementation_density <- ggplot(L15_mutants_complementation, aes(x = fitD05D03)) +
  geom_density(aes(y = after_stat(density * 100), 
                   fill = ifelse(fitD05D03 <= -1, "darkblue", "gold")), 
               alpha = 0.75) +
  geom_vline(xintercept = -1, linetype = "dashed", color = "black") +
  scale_fill_identity() +
  scale_x_continuous(breaks = seq(x_min_mut, x_max_mut, by = 1)) +
  scale_y_continuous(labels = function(x) paste0(x, "%")) +
  labs(x = "Median Fitness (LogFC)", y = "Percentage") +
  theme_minimal() +
  theme(axis.line = element_line(colour = 'black', size = 1.0), 
        axis.ticks = element_line(colour = "black", size = 1.0),
        axis.text.x = element_text(size = 12),
        axis.text.y = element_text(size = 12),
        axis.title.x = element_text(size = 14),
        axis.title.y = element_text(size = 14),
        panel.background = element_blank(),
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        legend.title = element_blank(),
        legend.text = element_text(size = 12),
        legend.position = "none")

# Display plot
print(L15_mutants_complementation_density)

Perfects & Mutants: Smooth Histogram

# Subset mutants (mutations != 0)
L15_perfects_mutants_complementation <- mut_collapse_15 %>%
  filter(mutations >= 0 & mutations < 6)

# Remove NA and infinite values for x-axis scaling
fitD05D03_perf_mut_clean <- L15_perfects_mutants_complementation$fitD05D03[is.finite(L15_perfects_mutants_complementation$fitD05D03)]

# Calculate the range of the data
x_min_perf_mut <- floor(min(fitD05D03_perf_mut_clean, na.rm = TRUE))
x_max_perf_mut <- ceiling(max(fitD05D03_perf_mut_clean, na.rm = TRUE))

# Plot smooth density curve
L15_perfects_mutants_complementation_density <- ggplot(L15_perfects_mutants_complementation, aes(x = fitD05D03)) +
  geom_density(aes(y = after_stat(density * 100), 
                   fill = ifelse(fitD05D03 <= -1, "darkblue", "gold")), 
               alpha = 0.75) +
  geom_vline(xintercept = -1, linetype = "dashed", color = "black") +
  scale_fill_identity() +
  scale_x_continuous(breaks = seq(x_min_perf_mut, x_max_perf_mut, by = 1)) +
  scale_y_continuous(labels = function(x) paste0(x, "%")) +
  labs(x = "Median Fitness (LogFC)", y = "Percentage") +
  theme_minimal() +
  theme(axis.line = element_line(colour = 'black', size = 1.0), 
        axis.ticks = element_line(colour = "black", size = 1.0),
        axis.text.x = element_text(size = 12),
        axis.text.y = element_text(size = 12),
        axis.title.x = element_text(size = 14),
        axis.title.y = element_text(size = 14),
        panel.background = element_blank(),
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        legend.title = element_blank(),
        legend.text = element_text(size = 12),
        legend.position = "none")

# Display plot
print(L15_perfects_mutants_complementation_density)

**Mutant Counts*

# Step 1: Identify IDs that meet the criteria (mutations == 0 and fitD05D03 >= -1)
valid_IDs <- mut_collapse_15 %>%
  filter(mutations == 0 & fitD05D03 >= -1) %>%
  pull(ID)

# Step 2: Filter mutIDs based on the criteria and association with valid IDs
unique_mutIDinfo15_5AA_count <- mut_collapse_15 %>%
  filter(ID %in% valid_IDs) %>%  # Keep only rows associated with valid IDs
  filter(mutations > 0 & mutations < 6) %>%  # Keep mutIDs with 1-5 mutations
  distinct(mutID) %>%
  nrow()

# Format and print the result
formatted_count <- format(unique_mutIDinfo15_5AA_count, big.mark = ",")
print(paste("Number of unique mutIDs with 1-5 mutations associated with complementing perfects:", formatted_count))
[1] "Number of unique mutIDs with 1-5 mutations associated with complementing perfects: 6,697"
# Step 1: Identify IDs that meet the criteria (mutations == 0 and fitD05D03 < -1)
LOF_IDs <- mut_collapse_15 %>%
  filter(mutations == 0 & fitD05D03 < -1) %>%
  pull(ID)

# Step 2: Filter mutIDs based on the criteria and association with LOF_IDs
GOF_mutants <- mut_collapse_15 %>%
  filter(ID %in% LOF_IDs) %>%  # Keep only rows associated with LOF_IDs
  filter(mutations == 1) %>%  # Keep mutIDs with 1 mutation
  filter(fitD05D03 >= -1)  # Keep GOF mutants with fitness >= -1

# Count unique mutIDs
GOF_mutIDinfo15_1AA_count <- GOF_mutants %>%
  distinct(mutID) %>%
  nrow()

# Step 3: Count how many of the original LOF_IDs have associated GOF mutants
LOF_IDs_with_GOF_mutants <- GOF_mutants %>%
  distinct(ID) %>%
  nrow()

# Format and print the results
formatted_LOF_IDs_count <- format(length(LOF_IDs), big.mark = ",")
formatted_mutants_count <- format(GOF_mutIDinfo15_1AA_count, big.mark = ",")
formatted_LOF_IDs_with_GOF_mutants <- format(LOF_IDs_with_GOF_mutants, big.mark = ",")

print(paste("Number of original IDs (mutations == 0) with fitD05D03 < -1:", formatted_LOF_IDs_count))
[1] "Number of original IDs (mutations == 0) with fitD05D03 < -1: 378"
print(paste("Number of unique mutIDs with 1 mutation associated with these IDs and fitD05D03 >= -1:", formatted_mutants_count))
[1] "Number of unique mutIDs with 1 mutation associated with these IDs and fitD05D03 >= -1: 476"
print(paste("Number of original IDs that have associated mutants meeting the criteria:", formatted_LOF_IDs_with_GOF_mutants))
[1] "Number of original IDs that have associated mutants meeting the criteria: 196"

Both Plots: Plot both graphs together:

patch1 <- (L15_perfects_complementation_density | L15_mutants_complementation_density)
patch1


patch2 <- (L15_perfects_complementation_density | L15_mutants_complementation_density) / L15_perfects_mutants_complementation_density
patch2

Count the number of unique perfects with fitness > -1 and fitness < -1:

# Assuming L15_perfects_complementation is already defined
unique_ID_counts_perfects <- L15_perfects_complementation %>%
  group_by(ID) %>%
  summarise(
    min_fitness = min(fitD05D03, na.rm = TRUE),
    max_fitness = max(fitD05D03, na.rm = TRUE),
    all_na = all(is.na(fitD05D03))
  ) %>%
  mutate(fitness_category = case_when(
    all_na ~ "All NA",
    !is.finite(min_fitness) & !is.finite(max_fitness) ~ "All NA",
    max_fitness < -1 ~ "All less than -1",
    min_fitness > -1 ~ "All greater than -1",
    TRUE ~ "Spans -1"
  )) %>%
  group_by(fitness_category) %>%
  summarise(count = n())
Warning: There were 6 warnings in `summarise()`.
The first warning was:
ℹ In argument: `min_fitness = min(fitD05D03, na.rm = TRUE)`.
ℹ In group 1: `ID = "NP_065309"`.
Caused by warning in `min()`:
! no non-missing arguments to min; returning Inf
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 5 remaining warnings.
print("Unique ID counts for perfects by fitness category:")
[1] "Unique ID counts for perfects by fitness category:"
print(unique_ID_counts_perfects)

total_unique_IDs_perfects <- n_distinct(L15_perfects_complementation$ID)
print(paste("Total number of unique IDs in perfects:", total_unique_IDs_perfects))
[1] "Total number of unique IDs in perfects: 797"
IDs_with_valid_fitness_perfects <- L15_perfects_complementation %>%
  filter(!is.na(fitD05D03)) %>%
  n_distinct(.$ID)

print(paste("Number of unique IDs in perfects with at least one valid fitD05D03:", IDs_with_valid_fitness_perfects))
[1] "Number of unique IDs in perfects with at least one valid fitD05D03: 794"
# Additional information: count of rows for each fitness category
rows_per_category_perfects <- L15_perfects_complementation %>%
  mutate(fitness_category = case_when(
    is.na(fitD05D03) ~ "NA",
    fitD05D03 < -1 ~ "Less than -1",
    fitD05D03 > -1 ~ "Greater than -1",
    TRUE ~ "Equal to -1"
  )) %>%
  group_by(fitness_category) %>%
  summarise(row_count = n())

print("Number of rows in each fitness category for perfects:")
[1] "Number of rows in each fitness category for perfects:"
print(rows_per_category_perfects)

Count the number of unique mutants with fitness > -1 and fitness < -1:

# Count mutants from L15_mutants_complementation dataset
unique_mutID_counts <- L15_mutants_complementation %>%
  group_by(mutID) %>%
  summarise(
    min_fitness = min(fitD05D03, na.rm = TRUE),
    max_fitness = max(fitD05D03, na.rm = TRUE),
    all_na = all(is.na(fitD05D03))
  ) %>%
  mutate(fitness_category = case_when(
    all_na ~ "All NA",
    !is.finite(min_fitness) & !is.finite(max_fitness) ~ "All NA",
    max_fitness < -1 ~ "All less than -1",
    min_fitness > -1 ~ "All greater than -1",
    TRUE ~ "Spans -1"
  )) %>%
  group_by(fitness_category) %>%
  summarise(count = n())
Warning: There were 7222 warnings in `summarise()`.
The first warning was:
ℹ In argument: `min_fitness = min(fitD05D03, na.rm = TRUE)`.
ℹ In group 2: `mutID = "NP_065309_G27V_L54X_P55X"`.
Caused by warning in `min()`:
! no non-missing arguments to min; returning Inf
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 7221 remaining warnings.
print(unique_mutID_counts)

total_unique_mutIDs <- n_distinct(L15_mutants_complementation$mutID)
print(paste("Total number of unique mutIDs:", total_unique_mutIDs))
[1] "Total number of unique mutIDs: 12146"
mutIDs_with_valid_fitness <- L15_mutants_complementation %>%
  filter(!is.na(fitD05D03)) %>%
  n_distinct(.$mutID)

print(paste("Number of unique mutIDs with at least one valid fitD05D03:", mutIDs_with_valid_fitness))
[1] "Number of unique mutIDs with at least one valid fitD05D03: 8535"
# Additional information: count of rows for each fitness category
rows_per_category <- L15_mutants_complementation %>%
  mutate(fitness_category = case_when(
    is.na(fitD05D03) ~ "NA",
    fitD05D03 < -1 ~ "Less than -1",
    fitD05D03 > -1 ~ "Greater than -1",
    TRUE ~ "Equal to -1"
  )) %>%
  group_by(fitness_category) %>%
  summarise(row_count = n())

print("Number of rows in each fitness category:")
[1] "Number of rows in each fitness category:"
print(rows_per_category)

Dropout Perfects

Start by retrieving all dropout perfects with a log-fold change value less than -1.0 and corresponding GoF mutants with a log-fold change value greater than -1.0. Use the mut_collapse_15 dataset which includes 797 perfects (mutations = 0; numprunedBCs = 5) and 12,174 mutants with up to 5 AA distance and at least 1 BC (numprunedBCs = 1) matching to a perfect variant in the dataset.

# Step 1: Identify IDs that have rows where mutations == 0 and fitD05D03 < -1.0
dropout15_ids_with_zero_mutations <- mut_collapse_15 %>%
  filter(mutations == 0 & fitD05D03 < -1.0) %>%
  distinct(ID) %>%
  pull(ID)

# Step 2: Filter the main dataset to keep mutants with fitness > -1.0 if they match a corresponding perfect ID
dropout_mutants15_GOF <- mut_collapse_15 %>%
  filter(
    (mutations == 0 & fitD05D03 < -1.0) |
    #(mutations != 0 & ID %in% dropout15_ids_with_zero_mutations & !is.na(fitD05D03))) %>%
    (mutations != 0 & fitD05D03 > -1.0 & ID %in% dropout15_ids_with_zero_mutations)) %>%
  dplyr::select(ID, mutID, numprunedBCs, mutations, fitD05D03, seq)

Validate that rows where mutations != 0 have an ID that matches rows where mutations == 0 and fitD05D03 < -1.0.

# 1. First, create two subsets of the data
zero_mutation_rows <- dropout_mutants15_GOF %>%
  filter(mutations == 0 & fitD05D03 < -1.0)

non_zero_mutation_rows <- dropout_mutants15_GOF %>%
  #filter(mutations != 0)
  filter(mutations != 0 & fitD05D03 > -1.0)

# 2. Check that all IDs in non_zero_mutation_rows are present in zero_mutation_rows
all_valid_ids <- all(non_zero_mutation_rows$ID %in% zero_mutation_rows$ID)
print(paste("All non-zero mutation rows have a matching zero mutation row:", all_valid_ids))
[1] "All non-zero mutation rows have a matching zero mutation row: TRUE"
# 3. If the above is FALSE, find the problematic IDs
if (!all_valid_ids) {
  problematic_ids <- setdiff(non_zero_mutation_rows$ID, zero_mutation_rows$ID)
  print("IDs with non-zero mutations but no matching zero mutation row:")
  print(problematic_ids)
}

# 4. Check for any IDs in zero_mutation_rows that don't have a corresponding non-zero mutation row
ids_without_non_zero <- setdiff(zero_mutation_rows$ID, non_zero_mutation_rows$ID)
print("IDs with zero mutations but no corresponding non-zero mutation rows:")
[1] "IDs with zero mutations but no corresponding non-zero mutation rows:"
print(ids_without_non_zero)
  [1] "NP_459092"    "NP_600072"    "NP_719188"    "NP_773150"    "NP_951629"    "WP_000175745" "WP_000312550" "WP_000312552"
  [9] "WP_000587144" "WP_000637207" "WP_001566069" "WP_001718077" "WP_002136083" "WP_002306927" "WP_002365665" "WP_002384507"
 [17] "WP_002386810" "WP_002451172" "WP_002459164" "WP_002464119" "WP_002470989" "WP_002480941" "WP_002516137" "WP_002635775"
 [25] "WP_002931755" "WP_002950147" "WP_002983619" "WP_003040947" "WP_003140871" "WP_003148742" "WP_003397230" "WP_003660945"
 [33] "WP_003690263" "WP_003710023" "WP_003769360" "WP_003924348" "WP_003937743" "WP_003946393" "WP_004043660" "WP_004057195"
 [41] "WP_004104442" "WP_004223430" "WP_004371455" "WP_004392428" "WP_004435037" "WP_004561476" "WP_004617034" "WP_004629118"
 [49] "WP_004636328" "WP_004680534" "WP_004742552" "WP_004756163" "WP_004813248" "WP_004821027" "WP_004846132" "WP_004855820"
 [57] "WP_004890475" "WP_004958883" "WP_005051336" "WP_005061314" "WP_005180205" "WP_005195010" "WP_005197432" "WP_005200258"
 [65] "WP_005220873" "WP_005236875" "WP_005254207" "WP_005280603" "WP_005291404" "WP_005382259" "WP_005395774" "WP_005544483"
 [73] "WP_005577110" "WP_006015200" "WP_006060602" "WP_006158942" "WP_006277558" "WP_006365071" "WP_006371387" "WP_006464375"
 [81] "WP_006590259" "WP_006731693" "WP_006732882" "WP_006735633" "WP_006743443" "WP_006770119" "WP_006806603" "WP_006817099"
 [89] "WP_006866478" "WP_006896237" "WP_006935896" "WP_007084769" "WP_007115148" "WP_007173507" "WP_007262186" "WP_007378735"
 [97] "WP_007409527" "WP_007548842" "WP_007745442" "WP_007994849" "WP_008039364" "WP_008407301" "WP_008469667" "WP_008494113"
[105] "WP_008640485" "WP_008678172" "WP_008788229" "WP_008856751" "WP_008880180" "WP_008912732" "WP_008914812" "WP_008927974"
[113] "WP_008974514" "WP_009010779" "WP_009226174" "WP_009248943" "WP_009272085" "WP_009332621" "WP_009361210" "WP_009390512"
[121] "WP_009418917" "WP_009541956" "WP_009570978" "WP_009602063" "WP_009707799" "WP_009795644" "WP_009808745" "WP_009873944"
[129] "WP_009887849" "WP_010025427" "WP_010253752" "WP_010361519" "WP_010488407" "WP_010495720" "WP_010622194" "WP_010689012"
[137] "WP_010705407" "WP_010732391" "WP_010752163" "WP_010753941" "WP_010768697" "WP_010976722" "WP_011006950" "WP_011077508"
[145] "WP_011091268" "WP_011095362" "WP_011283540" "WP_011412710" "WP_011466839"
# 5. Summary statistics
print(paste("Number of unique IDs in zero mutation rows:", n_distinct(zero_mutation_rows$ID)))
[1] "Number of unique IDs in zero mutation rows: 378"
print(paste("Number of unique IDs in non-zero mutation rows:", n_distinct(non_zero_mutation_rows$ID)))
[1] "Number of unique IDs in non-zero mutation rows: 229"
# 6. Distribution of mutation counts for non-zero mutation rows
mutation_distribution <- non_zero_mutation_rows %>%
  group_by(mutations) %>%
  summarise(count = n()) %>%
  arrange(mutations)

print("Distribution of mutation counts:")
[1] "Distribution of mutation counts:"
print(mutation_distribution)

# 7. Check for any unexpected mutation values
unexpected_mutations <- dropout_mutants15_GOF %>%
  filter(mutations < 0 | mutations > 5)  # Adjust the upper bound as needed

if (nrow(unexpected_mutations) > 0) {
  print("Rows with unexpected mutation values:")
  print(unexpected_mutations)
} else {
  print("No unexpected mutation values found.")
}
[1] "No unexpected mutation values found."

Remove unique perfect IDs if there is no corresponding mutants with fitness greater than -1.0:

# Step 1: Identify IDs with zero mutations
zero_mutation_ids <- dropout_mutants15_GOF %>%
  filter(mutations == 0 & fitD05D03 < -1.0) %>%
  pull(ID)

# Step 2: Identify IDs with non-zero mutations
non_zero_mutation_ids <- dropout_mutants15_GOF %>%
  filter(mutations != 0 & fitD05D03 > -1.0) %>%
  pull(ID)

# Step 3: Find IDs that have zero mutations but no corresponding non-zero mutation rows
ids_to_remove <- setdiff(zero_mutation_ids, non_zero_mutation_ids)

# Step 4: Remove the rows with these IDs
dropout_mutants15_GOF_cleaned <- dropout_mutants15_GOF %>%
  filter(!(ID %in% ids_to_remove))

# Print summary
print(paste("Number of rows before cleaning:", nrow(dropout_mutants15_GOF)))
[1] "Number of rows before cleaning: 1065"
print(paste("Number of rows after cleaning:", nrow(dropout_mutants15_GOF_cleaned)))
[1] "Number of rows after cleaning: 916"
print(paste("Number of rows removed:", nrow(dropout_mutants15_GOF) - nrow(dropout_mutants15_GOF_cleaned)))
[1] "Number of rows removed: 149"
print(paste("Number of unique IDs removed:", length(ids_to_remove)))
[1] "Number of unique IDs removed: 149"
# Optionally, you can print the removed IDs
print("IDs removed:")
[1] "IDs removed:"
print(ids_to_remove)
  [1] "NP_459092"    "NP_600072"    "NP_719188"    "NP_773150"    "NP_951629"    "WP_000175745" "WP_000312550" "WP_000312552"
  [9] "WP_000587144" "WP_000637207" "WP_001566069" "WP_001718077" "WP_002136083" "WP_002306927" "WP_002365665" "WP_002384507"
 [17] "WP_002386810" "WP_002451172" "WP_002459164" "WP_002464119" "WP_002470989" "WP_002480941" "WP_002516137" "WP_002635775"
 [25] "WP_002931755" "WP_002950147" "WP_002983619" "WP_003040947" "WP_003140871" "WP_003148742" "WP_003397230" "WP_003660945"
 [33] "WP_003690263" "WP_003710023" "WP_003769360" "WP_003924348" "WP_003937743" "WP_003946393" "WP_004043660" "WP_004057195"
 [41] "WP_004104442" "WP_004223430" "WP_004371455" "WP_004392428" "WP_004435037" "WP_004561476" "WP_004617034" "WP_004629118"
 [49] "WP_004636328" "WP_004680534" "WP_004742552" "WP_004756163" "WP_004813248" "WP_004821027" "WP_004846132" "WP_004855820"
 [57] "WP_004890475" "WP_004958883" "WP_005051336" "WP_005061314" "WP_005180205" "WP_005195010" "WP_005197432" "WP_005200258"
 [65] "WP_005220873" "WP_005236875" "WP_005254207" "WP_005280603" "WP_005291404" "WP_005382259" "WP_005395774" "WP_005544483"
 [73] "WP_005577110" "WP_006015200" "WP_006060602" "WP_006158942" "WP_006277558" "WP_006365071" "WP_006371387" "WP_006464375"
 [81] "WP_006590259" "WP_006731693" "WP_006732882" "WP_006735633" "WP_006743443" "WP_006770119" "WP_006806603" "WP_006817099"
 [89] "WP_006866478" "WP_006896237" "WP_006935896" "WP_007084769" "WP_007115148" "WP_007173507" "WP_007262186" "WP_007378735"
 [97] "WP_007409527" "WP_007548842" "WP_007745442" "WP_007994849" "WP_008039364" "WP_008407301" "WP_008469667" "WP_008494113"
[105] "WP_008640485" "WP_008678172" "WP_008788229" "WP_008856751" "WP_008880180" "WP_008912732" "WP_008914812" "WP_008927974"
[113] "WP_008974514" "WP_009010779" "WP_009226174" "WP_009248943" "WP_009272085" "WP_009332621" "WP_009361210" "WP_009390512"
[121] "WP_009418917" "WP_009541956" "WP_009570978" "WP_009602063" "WP_009707799" "WP_009795644" "WP_009808745" "WP_009873944"
[129] "WP_009887849" "WP_010025427" "WP_010253752" "WP_010361519" "WP_010488407" "WP_010495720" "WP_010622194" "WP_010689012"
[137] "WP_010705407" "WP_010732391" "WP_010752163" "WP_010753941" "WP_010768697" "WP_010976722" "WP_011006950" "WP_011077508"
[145] "WP_011091268" "WP_011095362" "WP_011283540" "WP_011412710" "WP_011466839"
# Assign the cleaned data back to dropout_mutants15_GOF if you want to update the original variable
dropout_mutants15_GOF <- dropout_mutants15_GOF_cleaned

Dropout Mutants

Summarize the number of perfects and mutants at each AA distance after filtering:

# Create a function to count unique mutIDs for a given number of mutations
dropout_mutants15_GOF_count <- function(data, mutation_count) {
  length(unique(subset(data, mutations == mutation_count)$mutID))
}

# Create a vector of counts for mutations 1-5
dropout15_counts <- sapply(1:5, function(x) dropout_mutants15_GOF_count(dropout_mutants15_GOF, x))

# Count perfects separately
perfects_count <- length(unique(subset(dropout_mutants15_GOF, mutations == 0 & fitD05D03 < -1.0)$mutID))

# Create a data frame with the results, including the summary row
dropout_mutants15_GOF_table <- data.frame(
  Mutations = c("Perfects (fit < -1.0)", "1 Mutation", "2 Mutations", "3 Mutations", "4 Mutations", "5 Mutations", "Total Mutations"),
  Count = c(perfects_count, dropout15_counts, sum(dropout15_counts))
)

# Print the table
print(dropout_mutants15_GOF_table)

GOF Fitness

GoF Fitness: Separate the dropout_mutants15_GOF dataset into two new dataframes, where DF1 contains perfects and DF2 contains mutants:

# Create a dataframe with mutations == 0
dropout_mutants15_GOF_no_mutations <- dropout_mutants15_GOF[dropout_mutants15_GOF$mutations == 0, ]

# Create a dataframe with mutations != 0
dropout_mutants15_GOF_with_mutations <- dropout_mutants15_GOF[dropout_mutants15_GOF$mutations != 0, ]

# ALTERNATIVE: Create a dataframe with mutations == 1 (only uses 1 aa mutation variants)
#dropout_mutants15_GOF_with_mutations <- dropout_mutants15_GOF[dropout_mutants15_GOF$mutations == 1, ]

Now, re-combine these dataframes to calculate the fitness change (delta) between mutants and their parent homologs:

# Step 1: Prepare the reference dataframe
df_reference <- dropout_mutants15_GOF_no_mutations %>%
  select(ID, fitD05D03) %>%
  rename(reference_fitD05D03 = fitD05D03)

# Step 2: Join and calculate the difference
dropout_mutants15_GOF_fitness <- dropout_mutants15_GOF_with_mutations %>%
  left_join(df_reference, by = "ID") %>%
  mutate(fitD05D03 = fitD05D03 - reference_fitD05D03) %>%
  select(ID, mutID, mutations, fitD05D03)

# Print summary statistics
print(paste("Number of Mutants:", nrow(dropout_mutants15_GOF_fitness)))
[1] "Number of Mutants: 687"
print(paste("Unique IDs:", length(unique(dropout_mutants15_GOF_fitness$ID))))
[1] "Unique IDs: 229"
print(paste("Range of fitD05D03:", 
            paste(round(range(dropout_mutants15_GOF_fitness$fitD05D03, na.rm = TRUE), 1), collapse = " to ")))
[1] "Range of fitD05D03: 0 to 5.9"

Boxplot: Plot mutant fitness relative to parent variant by number of mutations:

GOF_muts_fitness_by_muts_plot <- ggplot(dropout_mutants15_GOF_fitness, 
                                        aes(x = factor(mutations), y = fitD05D03)) +
  geom_boxplot() +
  labs(title = "fitD05D03 by Number of Mutations", x = "Number of Mutations", y = "fitD05D03")

print(GOF_muts_fitness_by_muts_plot)

Histogram of Mutant Fitness: Clearly shows mutant fitness is normally distributed.

GOF_muts_fitness_dist_plot <- ggplot(dropout_mutants15_GOF_fitness, aes(x = fitD05D03)) +
  geom_histogram(binwidth = 0.5, fill = "blue", color = "black") +
  labs(title = "Distribution of fitD05D03", x = "fitD05D03", y = "Count")

print(GOF_muts_fitness_dist_plot)

GOF Alignment

FASTA: Generate a FASTA file from the filtered dropout_mutants15_GoF perfects dataset based on shared perfects IDs with 1-AA mutation for GoF analysis:

# First, let's ensure we have the correct unique IDs for mutations == 1
dropout_mutants15_GOF_1mut_unique_ids <- dropout_mutants15_GOF_fitness %>%
  filter(mutations == 1) %>%
  distinct(ID) %>%
  pull(ID)

# Now, let's use these IDs to filter the dropout_mutants15_GOF dataset
dropout_mutants15_GOF_1mut_unique_id_seq <- dropout_mutants15_GOF %>%
  filter(ID %in% dropout_mutants15_GOF_1mut_unique_ids & mutations == 0) %>%
  select(ID, seq)

# Create the sequences in FASTA format
dropout_mutants15_GOF_fasta_content <- paste(">", dropout_mutants15_GOF_1mut_unique_id_seq$ID, "\n", dropout_mutants15_GOF_1mut_unique_id_seq$seq, "\n", sep = "", collapse = "")

# Define the file path in the working directory
dropout_mutants15_GOF_fasta_file_path <- file.path(getwd(), "GOF/MSA_Dropouts/Comp/FASTA/Lib15.GoF.perfects.complementation.fasta")

# Write the FASTA content to the file
writeLines(dropout_mutants15_GOF_fasta_content, 
           con = dropout_mutants15_GOF_fasta_file_path)

Alignment: Use the clustalo executable to align the protein sequences associated with the dropout perfects. This will align the FASTA file: Lib15.GoF.perfects.complementation.fasta for use in GoF analysis.

./Scripts/clustalo -i GOF/MSA_Dropouts/Comp/FASTA/Lib15.GoF.perfects.complementation.fasta -o GOF/MSA_Dropouts/Comp/FASTA/Lib15.GoF.perfects.complementation.tree.aligned.mod.aln --outfmt=clustal --force

Mapping Residues: Use the following map.aligned.residues.py python script to generate csv files for each designed homolog that maps residue positions of each A.A. from the alignment FASTA:

import time
import csv

##################################
#INPUTS:

base_path = ""
trees_path_prefix = base_path+""

#clustal format alignment file
align_file_in = [trees_path_prefix+"GOF/MSA_Dropouts/Comp/FASTA/Lib15.GoF.perfects.complementation.tree.aligned.mod.aln"]

#number of seqs in each alignment file
num_samples_in_file = [197] #New FASTA w/ mutant fit > -1 (+1 from actual file count)

##################################
#OUTPUTS:

msa_map_out_path = [trees_path_prefix+"GOF/MSA_Dropouts/Comp/"]

# Loop to generate .csv files for each ID
for alni in range(1):#len(align_file_in)):
    #print(alni)
    
    ##################################
    #VARIABLES:
    
    #ID as key, align as value
    align_dict = dict()
    
    #num_samples = 419
    num_samples = num_samples_in_file[alni]
    
    #pos key, consensus pos val
    IDaadictlist = [dict() for x in range(num_samples)]
    
    IDtoindexdict = dict()
    indexdtoIDict = dict()
    
    ##################################
    #CODE:
    
    line_count = 0
    #loop over all alignments:
    print(align_file_in[alni])
    for line in open(align_file_in[alni]):
        #skip header
        if line_count > 1:
            listWords = line.split('    ')
            ID = listWords[0]
            align = line[16:].rstrip()
            if ID.strip() != "":
                align_dict[ID] = align_dict.get(ID, "") + align.replace(" ", "")
        line_count += 1
    
    #print("NP_414590")
    #print(align_dict["NP_414590"])

    counter = 0
    for ID in align_dict:
        #print(ID)
        #print(align_dict[ID])
        IDtoindexdict[ID] = counter
        indexdtoIDict[counter]=ID
        align = align_dict[ID]
        
        aacounter = 1
        
        
        for i in range(len(align)):
            if align[i] != "-":
                
                #print(str(counter)+" "+str(aacounter))
                IDaadictlist[counter][aacounter]=i+1
                aacounter += 1
        counter += 1
        
    #print(len(IDaadictlist))
    for i in range(len(IDaadictlist)-1):
        #print(indexdtoIDict[i])
        #print(i)
        #print(alni)
        #print(indexdtoIDict[i])
        csvfile = open(str(msa_map_out_path[alni]+indexdtoIDict[i]+".csv"), 'w')
        fieldnames = ['orth_aanum','msa_aanum']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        for j in IDaadictlist[i]:
            #print(str(j)+" "+str(IDaadictlist[i][j]))
            #save all data:
            writer.writerow({'orth_aanum':str(j),'msa_aanum':str(IDaadictlist[i][j])})
        csvfile.close()

GOF Plots

Find GoF Perfects for Dropouts

quit
# Create a data frame of unique IDs
mutants15_to_plot <- dropout_mutants15_GOF_fitness %>%
  filter(mutations == 1) %>%
  distinct(ID) %>%
  select(ID)
# Initialize an empty vector to store IDs of mutants to be removed
mutants_to_remove <- character()

# Check for missing MSA files
for (i in 1:nrow(mutants15_to_plot)) {
  mutant15_current_temp <- mutants15_to_plot$ID[i]
  if (!file.exists(paste("GOF/MSA_Dropouts/Comp/", mutant15_current_temp, ".csv", sep = ""))) {
    mutants_to_remove <- c(mutants_to_remove, mutant15_current_temp)
  }
}

# Output the results
if (length(mutants_to_remove) > 0) {
  cat("The following mutants will be removed due to missing MSA files:\n")
  print(mutants_to_remove)
  cat("\nTotal number of mutants to be removed:", length(mutants_to_remove), "\n")
  
  # Remove the mutants without MSA files
  mutants15_to_plot <- mutants15_to_plot[!mutants15_to_plot$ID %in% mutants_to_remove, ]
  cat("\nMutants remaining:", nrow(mutants15_to_plot), "\n")
} else {
  cat("All mutants have corresponding MSA files. No mutants will be removed.\n")
}
All mutants have corresponding MSA files. No mutants will be removed.
# If you want to see the remaining mutants
print(mutants15_to_plot)

Read in the E. coli map:

ecoli_map <- read.csv(file=paste("GOF/MSA_Dropouts/Comp/NP_414590.csv", sep=""), head=TRUE, sep=",")

Make a new data frame which will keep all info

GOF_fitness_map <- data.frame(position=numeric(),
                              aa=character(),
                              mutations=numeric(),
                              fitness=numeric(),
                              posortho=numeric(),
                              ingap=character(),
                              mutID=character(),
                              ID=character())

aminoacids <- data.frame(aa=c('G','P','A','V','L','I','M','C','F','Y','W','H','K','R','Q','N','E','D','S','T','X'),
                         aanum=c(1:21))

MSA Mapping: Map the mutants (fitness difference from perfects > 0.5) over all perfects (fit < -1) for GoF analysis:

#loop over all perfects
for (iii in 1:nrow(mutants15_to_plot)){
  
  #current ortholog:
  mutant_current <- as.character(mutants15_to_plot$ID[iii])
  
  #length of name
  name_size = nchar(paste(mutant_current,"_",sep=""))
  
  #get the MSA mapping
  mutant_map <- read.csv(file=paste("GOF/MSA_Dropouts/Comp/",mutant_current,".csv",sep=""),
                         head=TRUE,sep=",")
  
  #grab the mutants with a fitness increase (GoF) of at least 0.5 (do not include perfects from dataset)
  GOFmutIDinfo_temp <- dropout_mutants15_GOF_fitness %>% ###UPDATED CODE
    filter(ID == mutant_current) %>%
    filter(mutations != 0) %>% ###UPDATED CODE
    filter(fitD05D03 >= 0.5) ###CHANGE VALUE AS NEEDED (>= 0.5 is default)
  
  # Check if GOFmutIDinfo_temp is empty
  if(nrow(GOFmutIDinfo_temp) == 0) {
    warning(paste("No non-zero mutation data found for ID:", mutant_current))
    next  # Skip to the next iteration of the outer loop
  }
  
  #loop over all mutants for this construct:
  for (mn in 1:nrow(GOFmutIDinfo_temp)) {
    
    #this mutants fitness
    gof_fit_temp <- GOFmutIDinfo_temp$fitD05D03[mn]  # or whichever fitness column you're using
    
    #grab the mut name
    mutations_names <- as.character(GOFmutIDinfo_temp$mutID[mn])
    
    #grab only the relevant portion of the name
    mutations_names <- substr(mutations_names, name_size+1, nchar(mutations_names))
    
    ## split mutation string at non-digits
    s <- strsplit(mutations_names, "_")
    
    for (mutnum in 1:GOFmutIDinfo_temp$mutations[mn]){
      
      #grab the corresponding mutation string
      mutcurr<-s[[1]][mutnum]
      
      #get the position
      mutpos <- as.numeric(str_extract(mutcurr, "[0-9]+"))
      
      #get ending aa
      to_aa <- substr(mutcurr, nchar(mutpos)+2, nchar(mutcurr))
      
      #find the number in the consensus seq
      gof_cons_aanum_index <- which(mutant_map$orth_aanum == mutpos)
      
      if (length(gof_cons_aanum_index) > 0) {
        gof_cons_aanum <- mutant_map$msa_aanum[gof_cons_aanum_index]
        
        #does this map to a non-gap
        if (gof_cons_aanum %in% ecoli_map$msa_aanum){
          
          #the corresponding e.coli residue
          e_coli_residue <- ecoli_map$orth_aanum[which(ecoli_map$msa_aanum == gof_cons_aanum)]
          
          #add this point to the data
          GOF_fitness_map <- rbind(GOF_fitness_map,
                                   data.frame(position=e_coli_residue,
                                              aa=to_aa,
                                              mutations=GOFmutIDinfo_temp$mutations[mn],
                                              fitness=gof_fit_temp,
                                              posortho=mutpos,
                                              ingap="No",
                                              mutID=GOFmutIDinfo_temp$mutID[mn],
                                              ID=GOFmutIDinfo_temp$ID[mn]))
          
        } else {
          #if it's here it maps to a gap
          
          #add this point to the data
          GOF_fitness_map <- rbind(GOF_fitness_map,
                                   data.frame(position=-1,
                                              aa=to_aa,
                                              mutations=GOFmutIDinfo_temp$mutations[mn],
                                              fitness=gof_fit_temp,
                                              posortho=mutpos,
                                              ingap="Yes",
                                              mutID=GOFmutIDinfo_temp$mutID[mn],
                                              ID=GOFmutIDinfo_temp$ID[mn]))
          
        }
      } else {
        warning(paste("No matching orth_aanum found for mutpos:", mutpos, "in ID:", mutant_current))
        # You might want to handle this case, perhaps by skipping this mutation or adding it to a separate list for review
      }
    }
  }
}
Warning: No non-zero mutation data found for ID: WP_000175752Warning: No matching orth_aanum found for mutpos: 162 in ID: WP_000637215Warning: No non-zero mutation data found for ID: WP_001408245Warning: No matching orth_aanum found for mutpos: 170 in ID: WP_002820451Warning: No matching orth_aanum found for mutpos: 162 in ID: WP_004570408Warning: No non-zero mutation data found for ID: WP_004832905Warning: No matching orth_aanum found for mutpos: 166 in ID: WP_004907189Warning: No matching orth_aanum found for mutpos: 160 in ID: WP_005917048Warning: No matching orth_aanum found for mutpos: 169 in ID: WP_006923903Warning: No matching orth_aanum found for mutpos: 163 in ID: WP_008170691Warning: No non-zero mutation data found for ID: WP_008985437Warning: No matching orth_aanum found for mutpos: 161 in ID: WP_009435065Warning: No non-zero mutation data found for ID: WP_010934590Warning: No matching orth_aanum found for mutpos: 167 in ID: WP_011581087

Collapse the GOF fitness values by aa position along the protein sequence:

GOF_fitness_collapsed_by_pos <- GOF_fitness_map %>%
  filter(position > 0) %>%
  group_by(position) %>%
  summarise(fitval=median(fitness),
            numpoints=n(),
            stdfit=sd(fitness),
            numortho=length(unique(ID)))

GOF Mutant No. Plot

Plot the number of gain-of-function mutants recovered for each aa position along the protein sequence and include cutoff lines for 1 SD above the mean number of GOF mutants and 2 SD above the mean number of GOF mutants. All positions with GOF mutants above the 2 SD line are considered significant positions positively influencing the parent variants ability to complement metabolic function in the E. coli knockout model.

GoF_plot <- ggplot(GOF_fitness_collapsed_by_pos, aes(x=position, y=numpoints, color=numortho)) +
  geom_segment(aes(x = 0, y = mean(numpoints)+2*sd(numpoints), 
                   xend = 160, 
                   yend = mean(numpoints)+2*sd(numpoints)),linetype=2,colour = "red2")+
  geom_segment(aes(x = 0, y = mean(numpoints), xend = 160, yend = mean(numpoints)),linetype=2,colour = "darkblue")+
  geom_point(size=1.8)+
  labs(x = "Position (aa)", y ="Number of gain-of-function mutants",color="") +
  scale_color_gradientn(colours = c("darkblue", "red"),
                        name="Num.\nUniq.\nHomo.",
                        na.value="grey", 
                        limits = c(0,1.1*max(GOF_fitness_collapsed_by_pos$numortho))) +
  scale_x_continuous(breaks=seq(0,160,20))+
  theme_minimal() +
  theme(axis.line = element_line(colour = 'black', size = 1.0), 
        axis.ticks = element_line(colour = "black", size = 1.0),
        axis.text.x = element_text(size = 12),
        axis.text.y = element_text(size = 12),
        axis.title.x = element_text(size = 14),
        axis.title.y = element_text(size = 14),
        panel.background = element_blank(),
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        legend.title = element_blank(),
        legend.text = element_text(size = 12),
        legend.position = "none")

# Add marginal plot
GoF_plot_with_marginal <- ggExtra::ggMarginal(GoF_plot,
                                              type = "histogram",
                                              margins = "y",
                                              bins=21,
                                              col = 'black',
                                              fill = 'red2')
Warning: All aesthetics have length 1, but the data has 153 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.Warning: All aesthetics have length 1, but the data has 153 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.Warning: All aesthetics have length 1, but the data has 153 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.Warning: All aesthetics have length 1, but the data has 153 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.
# Display the plot
print(GoF_plot_with_marginal)

Print out a summary table of significant aa position along the protein sequence

GOF_fitness_collapsed_by_pos_2sigma <- GOF_fitness_collapsed_by_pos %>%
  filter(numpoints >= (mean(GOF_fitness_collapsed_by_pos$numpoints)+2*sd(GOF_fitness_collapsed_by_pos$numpoints)))
print(GOF_fitness_collapsed_by_pos_2sigma)

Calculate all Data and Stats:

GOF_fitness_collapsed_all <- GOF_fitness_map %>%
  filter(position > 0) %>%
  group_by(position, aa) %>%
  summarise(fitval=median(fitness),
            numpoints=n(),
            stdfit=sd(fitness),
            numortho=length(unique(ID)))
`summarise()` has grouped output by 'position'. You can override using the `.groups` argument.
gof_aa_dim <- nrow(aminoacids)
gof_ref_len <- nrow(ecoli_map)
#these matrices have the fitness/num/sd for each aa at each position:
gof_matrix = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)
gof_matrix_num = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)
gof_matrix_sd = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)
gof_matrix_numortho = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)

#populate matrix
for (i in 1:nrow(GOF_fitness_collapsed_all)){
  
  gof_matrix[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all$aa[i])),GOF_fitness_collapsed_all$position[i]] <- as.numeric(GOF_fitness_collapsed_all$fitval[i])
  gof_matrix_num[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all$aa[i])),GOF_fitness_collapsed_all$position[i]] <- as.numeric(GOF_fitness_collapsed_all$numpoints[i])
  gof_matrix_sd[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all$aa[i])),GOF_fitness_collapsed_all$position[i]] <- as.numeric(GOF_fitness_collapsed_all$stdfit[i])
  gof_matrix_numortho[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all$aa[i])),GOF_fitness_collapsed_all$position[i]] <- as.numeric(GOF_fitness_collapsed_all$numortho[i])
}

rownames(gof_matrix)<-aminoacids$aa
colnames(gof_matrix)<-c(1:gof_ref_len)
rownames(gof_matrix_num)<-aminoacids$aa
colnames(gof_matrix_num)<-c(1:gof_ref_len)
rownames(gof_matrix_sd)<-aminoacids$aa
colnames(gof_matrix_sd)<-c(1:gof_ref_len)
rownames(gof_matrix_numortho)<-aminoacids$aa
colnames(gof_matrix_numortho)<-c(1:gof_ref_len)

gof_matrix_melt <- melt(gof_matrix)
gof_matrix_num_melt <- melt(gof_matrix_num)
gof_matrix_sd_melt <- melt(gof_matrix_sd)
gof_matrix_numortho_melt <- melt(gof_matrix_numortho)

# Rename columns to "X1" and "X2"
names(gof_matrix_melt)[names(gof_matrix_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
names(gof_matrix_num_melt)[names(gof_matrix_num_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
names(gof_matrix_sd_melt)[names(gof_matrix_sd_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
names(gof_matrix_numortho_melt)[names(gof_matrix_numortho_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")

gof_matrix_melt_only_GOFpos <- gof_matrix_melt %>%
  filter(X2 == 17 |
         X2 == 97 |
         X2 == 98 |
         X2 == 102 |
         X2 == 103 |
         X2 == 104 |
         X2 == 107)

gof_matrix_melt_only_GOFpos$mutposnum <- 0
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==17)] <- 1
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==97)] <- 2
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==98)] <- 3
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==102)] <- 4
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==103)] <- 5
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==104)] <- 6
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==107)] <- 7

gof_matrix_melt_only_GOFpos$aanum <- 0
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="A")] <- 12
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="C")] <- 10
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="D")] <- 5
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="E")] <- 4
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="F")] <- 19
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="G")] <- 11
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="H")] <- 3
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="I")] <- 15
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="K")] <- 1
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="L")] <- 14
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="M")] <- 16
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="N")] <- 6
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="P")] <- 17
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="Q")] <- 7
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="R")] <- 2
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="S")] <- 9
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="T")] <- 8
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="V")] <- 13
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="W")] <- 20
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="Y")] <- 18
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="X")] <- 21

gof_matrix_melt_only_GOFpos_wnum <- gof_matrix_melt_only_GOFpos %>%
  inner_join(gof_matrix_num_melt,by=c("X1","X2")) %>%
  dplyr::rename(mutnum=value.y,value=value.x)

GOF Position Plot

Plot the mean fitness of each GoF mutation at the significant positions, with the number of mutants observed at each AA:

# Define the order of amino acids for the rectangles
rect_order <- c("E", "G", "R", "Q", "F", "L", "A")

# Create a data frame for the rectangles
rect_data <- data.frame(
  aanum = match(rect_order, c("K","R","H","E","D","N","Q","T","S","C","G","A","V","L","I","M","P","Y","F","W","X")),
  xmin = seq(0.5, by = 1, length.out = length(rect_order)),
  xmax = seq(1.5, by = 1, length.out = length(rect_order)))

#plot the data from all mutants:
GOF_fit_nummut_plot <- ggplot(gof_matrix_melt_only_GOFpos_wnum, 
       aes(x=mutposnum, y=aanum,
           fill=value,
           label=mutnum)) +
  geom_tile() +
  geom_text() +
  # Add black rectangles
  geom_rect(data = rect_data,
            aes(xmin = xmin, xmax = xmax, ymin = aanum - 0.5, ymax = aanum + 0.5),
            fill = NA, color = "black", inherit.aes = FALSE) +
  labs(x = "Position (aa)",
       y ="Amino acid",color="") +
  scale_fill_gradient(low = "blue", 
                      high = "red",
                      name="Fitness",
                      na.value="grey",
                      limit = c(0, max(gof_matrix_melt_only_GOFpos_wnum$value))) +
  theme_minimal()+
  scale_x_continuous(name="Position (aa)",
                     breaks=c(1,2,3,4,5,6,7),
                     labels=c("17","97","98","102","103","104","107"))+
  scale_y_continuous(name="Amino acid", 
                     breaks=c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21),
                     labels=c("K","R","H","E","D","N","Q","T","S","C","G","A","V","L","I","M","P","Y","F","W","X"))

print(GOF_fit_nummut_plot)

Plot the GOF mutant fitness across the protein sequence:

ggplot(data = gof_matrix_melt, aes(x=X2, y=X1, fill=value)) +
  geom_tile() +
  labs(x = "Position (aa)",
       y ="Amino acid",color="") +
  scale_fill_gradient(low = "blue", 
                       high = "red",
                       name="Fitness",
                       na.value="grey",
                       limit = c(0,max(gof_matrix_melt$value))) +
  theme_minimal() + 
  scale_x_continuous(breaks=seq(0,150,10))

Plot the number of mutants observed at each position along the protein sequence:

ggplot(data = gof_matrix_num_melt, aes(x=X2, y=X1, fill=value)) +
  geom_tile()+ 
  labs(x = "Position (aa)",
       y ="Amino acid",color="") +
  scale_fill_gradient(low = "blue", 
                      high = "red",
                      name="#points",
                      na.value="grey", 
                      limit = c(0,max(gof_matrix_num_melt$value))) +
  theme_minimal() + 
  scale_x_continuous(breaks=seq(0,150,10))

Plot the number of mutants observed at each position along the protein sequence:

ggplot(data = gof_matrix_numortho_melt, aes(x=X2, y=X1, fill=value)) +
  geom_tile()+ 
  labs(x = "Position (aa)",
       y ="Amino acid",color="") +
  scale_fill_gradient(low = "blue", 
                      high = "red",
                      name="#points",
                      na.value="grey", 
                      limit = c(0,max(gof_matrix_numortho_melt$value))) +
  theme_minimal() + 
  scale_x_continuous(breaks=seq(0,150,10))

Plot the standard deviation of fitness for each position along the protein sequence:

ggplot(data = gof_matrix_sd_melt, aes(x=X2, y=X1, fill=value)) +
  geom_tile()+ labs(x = "Position (aa)", y ="Amino acid",color="") +
  scale_fill_gradient2(low = "blue", 
                       high = "red", 
                       mid="gold",
                       name="std(Fitness)",
                       na.value="grey", 
                       limit = c(0,1.1*max(gof_matrix_sd_melt$value))) +
  theme_minimal() + 
  scale_x_continuous(breaks=seq(0,150,10))

GOF BMS Position Plot

GOF_fitness_collapsed_by_pos_2sigma_protein_info <- protein_info_1H1T %>%
  dplyr::rename(position=pos) %>%
  filter(position %in% GOF_fitness_collapsed_by_pos_2sigma$position) %>%
  right_join(GOF_fitness_collapsed_by_pos_2sigma,by="position") %>%
  arrange(position)
print(GOF_fitness_collapsed_by_pos_2sigma_protein_info)
rsa_vs_cons_plot <- ggplot(GOF_fitness_collapsed_by_pos_2sigma_protein_info,
             aes(x=cons, y=RSA, color=as.factor(position), fill=as.factor(position), shape=as.factor(position))) +
  geom_point(alpha=0.9, size=4, stroke=1) +
  labs(x = "Site Conservation", y ="Relative Solvent Accessibility", color="Residue", fill="Residue", shape="Residue") +
  scale_color_manual(name = "Residue",
                     values = c("red", "green", "blue", "purple", "orange", "cyan", "magenta")) +
  scale_fill_manual(name = "Residue",
                    values = c("red", "green", "blue", "purple", "orange", "cyan", "magenta")) +
  scale_shape_manual(name = "Residue",
                     values = c(21, 22, 23, 24, 25, 21, 22)) +
  theme_minimal() +
  theme(legend.position = "right")

rsa_vs_cons_plot

Extract the fitness values for each significant aa position from the BMS analysis:

BMS_matrix_perfects_and_1_melt_GOFonly <- BMS_matrix15_perfects_and_1_melt %>%
  filter(X1 != "X") %>%
  filter(X2 == 17 | X2 == 97 | X2 == 98 | X2 == 102 | X2 == 103 | X2 == 104 | X2 == 107)

BMS_matrix_perfects_and_1_melt_GOFonly$mutposnum <- 0
BMS_matrix_perfects_and_1_melt_GOFonly$mutposnum[which(BMS_matrix_perfects_and_1_melt_GOFonly$X2==17)] <- 1
BMS_matrix_perfects_and_1_melt_GOFonly$mutposnum[which(BMS_matrix_perfects_and_1_melt_GOFonly$X2==97)] <- 2
BMS_matrix_perfects_and_1_melt_GOFonly$mutposnum[which(BMS_matrix_perfects_and_1_melt_GOFonly$X2==98)] <- 3
BMS_matrix_perfects_and_1_melt_GOFonly$mutposnum[which(BMS_matrix_perfects_and_1_melt_GOFonly$X2==102)] <- 4
BMS_matrix_perfects_and_1_melt_GOFonly$mutposnum[which(BMS_matrix_perfects_and_1_melt_GOFonly$X2==103)] <- 5
BMS_matrix_perfects_and_1_melt_GOFonly$mutposnum[which(BMS_matrix_perfects_and_1_melt_GOFonly$X2==104)] <- 6
BMS_matrix_perfects_and_1_melt_GOFonly$mutposnum[which(BMS_matrix_perfects_and_1_melt_GOFonly$X2==107)] <- 7

BMS_matrix_perfects_and_1_melt_GOFonly_wnum <- BMS_matrix_perfects_and_1_melt_GOFonly %>%
  inner_join(BMS_matrix15_perfects_and_1_num_melt,by=c("X1","X2")) %>%
  dplyr::rename(mutnum=value.y,value=value.x)

names(BMS_matrix_perfects_and_1_melt_GOFonly_wnum)
[1] "X1"        "X2"        "value"     "WTcolor"   "aanum"     "mutposnum" "mutnum"   

Determine the minimum and maximum fitness values for plotting:

min(BMS_matrix_perfects_and_1_melt_GOFonly$value, na.rm = TRUE)
[1] -2.83005
max(BMS_matrix_perfects_and_1_melt_GOFonly$value, na.rm = TRUE)
[1] 1.214291

Plot the fitness values of the significant aa positions based on the BMS analysis for Complementation. Black rectangles indicate the aa corresponding to the WT DHFR homolog. White rectangles indicate the aa with the highest number of mutants for each position along the protein sequence.

# Define the order of amino acids for the black rectangles
rect_order <- c("E", "G", "R", "Q", "F", "L", "A")

# Create a data frame for the black rectangles
rect_data <- data.frame(
  aanum = match(rect_order, c("K","R","H","E","D","N","Q","T","S","C","G","A","V","L","I","M","P","Y","F","W")),
  xmin = seq(0.5, by = 1, length.out = length(rect_order)),
  xmax = seq(1.5, by = 1, length.out = length(rect_order)))

# Find the amino acid with the highest mutnum for each position
highest_mutnum <- BMS_matrix_perfects_and_1_melt_GOFonly_wnum %>%
  group_by(mutposnum) %>%
  slice_max(order_by = as.numeric(mutnum), n = 1) %>%
  ungroup()

# Create the plot
BMS_GoF_fit_plot <- ggplot(BMS_matrix_perfects_and_1_melt_GOFonly_wnum, 
       aes(x=mutposnum, y=aanum,
           fill=value,
           label=mutnum)) +
  geom_tile() +
  geom_text() +
  # Add black rectangles
  geom_rect(data = rect_data,
            aes(xmin = xmin, xmax = xmax, ymin = aanum - 0.5, ymax = aanum + 0.5),
            fill = NA, color = "black", inherit.aes = FALSE) +
  # Add white rectangles around the highest mutnum
  geom_rect(data = highest_mutnum,
            aes(xmin = mutposnum - 0.5, xmax = mutposnum + 0.5, 
                ymin = aanum - 0.5, ymax = aanum + 0.5),
            fill = NA, color = "white", size = 1, inherit.aes = FALSE) +
  labs(x = "Position (aa)",
       y ="Amino acid", color="") +
  scale_fill_gradient2(low = "blue", high = "red", mid="gold",
                       name="Fitness", na.value="grey", 
                       limit = c(-3,1)) +
  theme_minimal() +
  scale_x_continuous(name="Position (aa)", 
                     breaks=c(1,2,3,4,5,6,7),
                     labels=c("17","97","98","102","103","104","107")) +
  scale_y_continuous(name="Amino acid", 
                     breaks=c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20),
                     labels=c("K","R","H","E","D","N","Q","T","S","C","G","A","V","L","I","M","P","Y","F","W"))

print(BMS_GoF_fit_plot)

MIC (0.5 ug/mL TMP)

Dropout Perfects

Retrieving all dropout perfects with a log-fold change value less than -1.0 from the COMPLEMENTATION dataset. Then, retrieve the same perfects (ID) from the MIC dataset and all corresponding mutants. Use the mut_collapse_15 dataset which includes 797 perfects (mutations = 0; numprunedBCs = 5) and 12,174 mutants with up to 5 AA distance and at least 1 BC (numprunedBCs = 1) matching to a perfect variant in the dataset.

# Step 1: Identify IDs that have rows where mutations == 0 and fitD05D03 < -1.0 in COMPLEMENTATION
dropout15_ids_with_zero_mutations_complement <- mut_collapse_15 %>%
  filter(mutations == 0 & fitD05D03 > -1.0) %>%
  distinct(ID) %>%
  pull(ID)

# Step 2: Identify the same IDs that have mutations == 0 and fitD07D03 < -1.0 in MIC
dropout15_ids_with_zero_mutations_mic <- mut_collapse_15 %>%
  filter(ID %in% dropout15_ids_with_zero_mutations_complement & 
         mutations == 0 & 
         fitD07D03 < -1.0) %>%
  distinct(ID) %>%
  pull(ID)

# Step 3: Retrieve the rows for these IDs
result_rows <- mut_collapse_15 %>%
  filter(ID %in% dropout15_ids_with_zero_mutations_mic & mutations == 0)

# Step 4: Filter the main dataset to keep mutants (with 1-AA) if they match a corresponding perfect ID
dropout_mutants15_GOF_mic <- mut_collapse_15 %>%
  filter(
    (mutations == 0 & !is.na(fitD05D03) & fitD05D03 > -1.0 & 
     !is.na(fitD07D03) & fitD07D03 < -1.0) |
    (mutations != 0 & fitD07D03 > -1.0 & ID %in% dropout15_ids_with_zero_mutations_mic)) %>%
  dplyr::select(ID, mutID, numprunedBCs, mutations, fitD05D03, fitD07D03, seq)

Validate that rows where mutations != 0 have an ID that matches rows where mutations == 0 and fitD05D03 < -1.0.

# Verification step
verification_result_mic <- dropout_mutants15_GOF_mic %>%
  filter(mutations == 0) %>%
  mutate(
    condition_met = fitD05D03 > -1.0 & fitD07D03 < -1,
    fitD05D03_check = fitD05D03 > -1.0,
    fitD07D03_check = fitD07D03 < -1
  )

# Check if all rows meet the condition
all_conditions_met_mic <- all(verification_result_mic$condition_met)

# Summary of the verification
verification_summary_mic <- verification_result_mic %>%
  summarise(
    total_rows = n(),
    rows_meeting_both_conditions = sum(condition_met),
    rows_meeting_fitD05D03 = sum(fitD05D03_check),
    rows_meeting_fitD07D03 = sum(fitD07D03_check)
  )

# Print results
print("Verification Summary:")
[1] "Verification Summary:"
print(verification_summary_mic)
print(paste("All conditions met:", all_conditions_met_mic))
[1] "All conditions met: TRUE"
# If there are any rows not meeting the conditions, display them
if (!all_conditions_met_mic) {
  print("Rows not meeting both conditions:")
  print(verification_result_mic %>% filter(!condition_met) %>% select(ID, fitD05D03, fitD07D03))
}

Validate that rows where mutations != 0 have an ID that matches rows where mutations == 0 and fitD07D03 < -1.0.

# 1. First, create two subsets of the data
zero_mutation_rows_mic <- dropout_mutants15_GOF_mic %>%
  filter(mutations == 0 & fitD07D03 < -1.0)

non_zero_mutation_rows_mic <- dropout_mutants15_GOF_mic %>%
  filter(mutations != 0 & fitD07D03 > -1.0)

# 2. Check that all IDs in non_zero_mutation_rows are present in zero_mutation_rows
all_valid_ids_mic <- all(non_zero_mutation_rows_mic$ID %in% zero_mutation_rows_mic$ID)
print(paste("All non-zero mutation rows have a matching zero mutation row:", all_valid_ids_mic))
[1] "All non-zero mutation rows have a matching zero mutation row: TRUE"
# 3. If the above is FALSE, find the problematic IDs
if (!all_valid_ids_mic) {
  problematic_ids_mic <- setdiff(non_zero_mutation_rows_mic$ID, zero_mutation_rows_mic$ID)
  print("IDs with non-zero mutations but no matching zero mutation row:")
  print(problematic_ids_mic)
}

# 4. Check for any IDs in zero_mutation_rows that don't have a corresponding non-zero mutation row
ids_without_non_zero_mic <- setdiff(zero_mutation_rows_mic$ID, non_zero_mutation_rows_mic$ID)
print("IDs with zero mutations but no corresponding non-zero mutation rows:")
[1] "IDs with zero mutations but no corresponding non-zero mutation rows:"
print(ids_without_non_zero_mic)
 [1] "NP_229441"    "NP_390064"    "NP_569589"    "WP_000162313" "WP_000162482" "WP_000175741" "WP_000175742" "WP_000175753"
 [9] "WP_002308859" "WP_002602832" "WP_002687527" "WP_003027976" "WP_003035097" "WP_003072921" "WP_003218222" "WP_003321978"
[17] "WP_003325705" "WP_003633095" "WP_003654807" "WP_003748519" "WP_003758501" "WP_004286630" "WP_004357589" "WP_004375525"
[25] "WP_004547470" "WP_004797168" "WP_004838134" "WP_004916485" "WP_004918315" "WP_004920127" "WP_004955442" "WP_004957782"
[33] "WP_005162425" "WP_005166996" "WP_005398549" "WP_005549928" "WP_005593154" "WP_005605215" "WP_005622496" "WP_005635192"
[41] "WP_005647059" "WP_005707354" "WP_005708598" "WP_005765106" "WP_005773221" "WP_006318421" "WP_006493548" "WP_006578126"
[49] "WP_006688837" "WP_006689329" "WP_006711975" "WP_006814098" "WP_006946351" "WP_007121618" "WP_007134290" "WP_007144234"
[57] "WP_007478890" "WP_007500805" "WP_007546669" "WP_008073041" "WP_008106982" "WP_008112253" "WP_008125961" "WP_008136828"
[65] "WP_008174809" "WP_008302089" "WP_008583481" "WP_008754633" "WP_009133007" "WP_009500922" "WP_009677188" "WP_009725265"
[73] "WP_009847829" "WP_010021426" "WP_010737807" "WP_010759385" "WP_010761388" "WP_010780948" "WP_010850622" "WP_011000898"
[81] "WP_011201021" "WP_011272274" "WP_011303097" "WP_011312551" "WP_011399565" "WP_011489445" "WP_011584955" "WP_011608342"
[89] "WP_011627026"
# 5. Summary statistics
print(paste("Number of unique IDs in zero mutation rows:", n_distinct(zero_mutation_rows_mic$ID)))
[1] "Number of unique IDs in zero mutation rows: 162"
print(paste("Number of unique IDs in non-zero mutation rows:", n_distinct(non_zero_mutation_rows_mic$ID)))
[1] "Number of unique IDs in non-zero mutation rows: 73"
# 6. Distribution of mutation counts for non-zero mutation rows
mutation_distribution_mic <- non_zero_mutation_rows_mic %>%
  group_by(mutations) %>%
  summarise(count = n()) %>%
  arrange(mutations)

print("Distribution of mutation counts:")
[1] "Distribution of mutation counts:"
print(mutation_distribution_mic)

# 7. Check for any unexpected mutation values
unexpected_mutations_mic <- dropout_mutants15_GOF_mic %>%
  filter(mutations < 0 | mutations > 5)  # Adjust the upper bound as needed

if (nrow(unexpected_mutations_mic) > 0) {
  print("Rows with unexpected mutation values:")
  print(unexpected_mutations_mic)
} else {
  print("No unexpected mutation values found.")
}
[1] "No unexpected mutation values found."

Remove unique perfect IDs if there is no corresponding mutants:

# Step 1: Identify IDs with zero mutations
zero_mutation_ids_mic <- dropout_mutants15_GOF_mic %>%
  filter(mutations == 0 & fitD07D03 < -1.0) %>%
  pull(ID)

# Step 2: Identify IDs with non-zero mutations
non_zero_mutation_ids_mic <- dropout_mutants15_GOF_mic %>%
  filter(mutations != 0 & fitD07D03 > -1.0) %>%
  pull(ID)

# Step 3: Find IDs that have zero mutations but no corresponding non-zero mutation rows
ids_to_remove_mic <- setdiff(zero_mutation_ids_mic, non_zero_mutation_ids_mic)

# Step 4: Remove the rows with these IDs
dropout_mutants15_GOF_mic_cleaned <- dropout_mutants15_GOF_mic %>%
  filter(!(ID %in% ids_to_remove_mic))

# Print summary
print(paste("Number of rows before cleaning:", nrow(dropout_mutants15_GOF_mic)))
[1] "Number of rows before cleaning: 323"
print(paste("Number of rows after cleaning:", nrow(dropout_mutants15_GOF_mic_cleaned)))
[1] "Number of rows after cleaning: 234"
print(paste("Number of rows removed:", nrow(dropout_mutants15_GOF_mic) - nrow(dropout_mutants15_GOF_mic_cleaned)))
[1] "Number of rows removed: 89"
print(paste("Number of unique IDs removed:", length(ids_to_remove_mic)))
[1] "Number of unique IDs removed: 89"
# Optionally, you can print the removed IDs
print("IDs removed:")
[1] "IDs removed:"
print(ids_to_remove_mic)
 [1] "NP_229441"    "NP_390064"    "NP_569589"    "WP_000162313" "WP_000162482" "WP_000175741" "WP_000175742" "WP_000175753"
 [9] "WP_002308859" "WP_002602832" "WP_002687527" "WP_003027976" "WP_003035097" "WP_003072921" "WP_003218222" "WP_003321978"
[17] "WP_003325705" "WP_003633095" "WP_003654807" "WP_003748519" "WP_003758501" "WP_004286630" "WP_004357589" "WP_004375525"
[25] "WP_004547470" "WP_004797168" "WP_004838134" "WP_004916485" "WP_004918315" "WP_004920127" "WP_004955442" "WP_004957782"
[33] "WP_005162425" "WP_005166996" "WP_005398549" "WP_005549928" "WP_005593154" "WP_005605215" "WP_005622496" "WP_005635192"
[41] "WP_005647059" "WP_005707354" "WP_005708598" "WP_005765106" "WP_005773221" "WP_006318421" "WP_006493548" "WP_006578126"
[49] "WP_006688837" "WP_006689329" "WP_006711975" "WP_006814098" "WP_006946351" "WP_007121618" "WP_007134290" "WP_007144234"
[57] "WP_007478890" "WP_007500805" "WP_007546669" "WP_008073041" "WP_008106982" "WP_008112253" "WP_008125961" "WP_008136828"
[65] "WP_008174809" "WP_008302089" "WP_008583481" "WP_008754633" "WP_009133007" "WP_009500922" "WP_009677188" "WP_009725265"
[73] "WP_009847829" "WP_010021426" "WP_010737807" "WP_010759385" "WP_010761388" "WP_010780948" "WP_010850622" "WP_011000898"
[81] "WP_011201021" "WP_011272274" "WP_011303097" "WP_011312551" "WP_011399565" "WP_011489445" "WP_011584955" "WP_011608342"
[89] "WP_011627026"
# Assign the cleaned data back to dropout_mutants15_GOF if you want to update the original variable
dropout_mutants15_GOF_mic <- dropout_mutants15_GOF_mic_cleaned

Dropout Mutants

Summarize the number of perfects and mutants at each AA distance after filtering:

# Create a function to count unique mutIDs for a given number of mutations
dropout_mutants15_GOF_count_mic <- function(data, mutation_count) {
  length(unique(subset(data, mutations == mutation_count)$mutID))
}

# Create a vector of counts for mutations 1-5
dropout15_counts_mic <- sapply(1:5, function(x) dropout_mutants15_GOF_count_mic(dropout_mutants15_GOF_mic, x))

# Count perfects separately
perfects_count_mic <- length(unique(subset(dropout_mutants15_GOF_mic, mutations == 0 & fitD07D03 < -1.0)$mutID))

# Create a data frame with the results, including the summary row
dropout_mutants15_GOF_table_mic <- data.frame(
  Mutations = c("Perfects (fit < -1.0)", "1 Mutation", "2 Mutations", "3 Mutations", "4 Mutations", "5 Mutations", "Total Mutations"),
  Count = c(perfects_count_mic, dropout15_counts_mic, sum(dropout15_counts_mic))
)

# Print the table
print(dropout_mutants15_GOF_table_mic)

GOF Fitness

GOF Fitness: Separate the dropout_mutants15_GOF_mic dataset into two new dataframes, where DF1 contains perfects and DF2 contains mutants:

# Create a dataframe with mutations == 0
dropout_mutants15_GOF_no_mutations_mic <- dropout_mutants15_GOF_mic[dropout_mutants15_GOF_mic$mutations == 0, ]

# Create a dataframe with mutations != 0
dropout_mutants15_GOF_with_mutations_mic <- dropout_mutants15_GOF_mic[dropout_mutants15_GOF_mic$mutations != 0, ]

Now, re-combine these dataframes to retain only the mutants

# Step 1: Prepare the reference dataframe
df_reference_mic <- dropout_mutants15_GOF_no_mutations_mic %>%
  select(ID, fitD07D03) %>%
  rename(reference_fitD07D03 = fitD07D03)

# Step 2: Join and calculate the difference
dropout_mutants15_GOF_fitness_mic <- dropout_mutants15_GOF_with_mutations_mic %>%
  left_join(df_reference_mic, by = "ID") %>%
  mutate(fitD07D03 = fitD07D03 - reference_fitD07D03) %>%
  select(ID, mutID, mutations, fitD07D03)

# Print summary statistics
print(paste("Number of Mutants:", nrow(dropout_mutants15_GOF_fitness_mic)))
[1] "Number of Mutants: 161"
print(paste("Unique IDs:", length(unique(dropout_mutants15_GOF_fitness_mic$ID))))
[1] "Unique IDs: 73"
print(paste("Range of fitD07D03:", 
            paste(round(range(dropout_mutants15_GOF_fitness_mic$fitD07D03, na.rm = TRUE), 1), collapse = " to ")))
[1] "Range of fitD07D03: 0.1 to 9.2"

Boxplot: Plot mutant fitness relative to parent variant by number of mutations:

GOF_muts_fitness_by_muts_plot_mic <- ggplot(dropout_mutants15_GOF_fitness_mic, 
                                        aes(x = factor(mutations), y = fitD07D03)) +
  geom_boxplot() +
  labs(title = "fitD07D03 by Number of Mutations", x = "Number of Mutations", y = "fitD07D03")

print(GOF_muts_fitness_by_muts_plot_mic)

Histogram of Mutant Fitness: Clearly shows mutant fitness is normally distributed.

GOF_muts_fitness_dist_plot_mic <- ggplot(dropout_mutants15_GOF_fitness_mic, aes(x = fitD07D03)) +
  geom_histogram(binwidth = 0.5, fill = "blue", color = "black") +
  labs(title = "Distribution of fitD07D03", x = "fitD07D03", y = "Count")

print(GOF_muts_fitness_dist_plot_mic)

GOF Alignment

FASTA: Generate a FASTA file from the filtered dropout_mutants15_GoF perfects dataset for GoF analysis:

# First, let's ensure we have the correct unique IDs for mutations == 1
dropout_mutants15_GOF_mic_1mut_unique_ids <- dropout_mutants15_GOF_fitness_mic %>%
  filter(mutations == 1) %>%
  distinct(ID) %>%
  pull(ID)

# Now, let's use these IDs to filter the dropout_mutants15_GOF dataset
dropout_mutants15_GOF_mic_1mut_unique_id_seq <- dropout_mutants15_GOF_mic %>%
  filter(ID %in% dropout_mutants15_GOF_mic_1mut_unique_ids & mutations == 0) %>%
  select(ID, seq)

# Create the sequences in FASTA format
dropout_mutants15_GOF_mic_fasta_content <- paste(">", dropout_mutants15_GOF_mic_1mut_unique_id_seq$ID, "\n", dropout_mutants15_GOF_mic_1mut_unique_id_seq$seq, "\n", sep = "", collapse = "")

# Define the file path in the working directory
dropout_mutants15_GOF_mic_fasta_file_path <- file.path(getwd(), "GOF/MSA_Dropouts/MIC/FASTA/Lib15.GoF.perfects.mic.fasta")

# Write the FASTA content to the file (57 unique IDs)
writeLines(dropout_mutants15_GOF_mic_fasta_content, 
           con = dropout_mutants15_GOF_mic_fasta_file_path)

Alignment: Use the clustalo executable to align the protein sequences associated with the dropout perfects. This will align the FASTA file: Lib15.GoF.perfects.complementation.fasta for use in GoF analysis.

./Scripts/clustalo -i GOF/MSA_Dropouts/MIC/FASTA/Lib15.GoF.perfects.mic.fasta -o GOF/MSA_Dropouts/MIC/FASTA/Lib15.GoF.perfects.mic.tree.aligned.mod.aln --outfmt=clustal --force

Mapping Residues: Use the following map.aligned.residues.py python script to generate csv files for each designed homolog that maps residue positions of each A.A. from the alignment FASTA:

import time
import csv

##################################
#INPUTS:

base_path = ""
trees_path_prefix = base_path+""

#clustal format alignment file
align_file_in = [trees_path_prefix+"GOF/MSA_Dropouts/MIC/FASTA/Lib15.GoF.perfects.mic.tree.aligned.mod.aln"]

#number of seqs in each alignment file
num_samples_in_file = [58] #New FASTA w/ mutant fit > -1 (+1 from actual file count)

##################################
#OUTPUTS:

msa_map_out_path = [trees_path_prefix+"GOF/MSA_Dropouts/MIC/"]

# Loop to generate .csv files for each ID
for alni in range(1):#len(align_file_in)):
    #print(alni)
    
    ##################################
    #VARIABLES:
    
    #ID as key, align as value
    align_dict = dict()
    
    #num_samples = 419
    num_samples = num_samples_in_file[alni]
    
    #pos key, consensus pos val
    IDaadictlist = [dict() for x in range(num_samples)]
    
    IDtoindexdict = dict()
    indexdtoIDict = dict()
    
    ##################################
    #CODE:
    
    line_count = 0
    #loop over all alignments:
    print(align_file_in[alni])
    for line in open(align_file_in[alni]):
        #skip header
        if line_count > 1:
            listWords = line.split('    ')
            ID = listWords[0]
            align = line[16:].rstrip()
            if ID.strip() != "":
                align_dict[ID] = align_dict.get(ID, "") + align.replace(" ", "")
        line_count += 1
    
    #print("NP_414590")
    #print(align_dict["NP_414590"])

    counter = 0
    for ID in align_dict:
        #print(ID)
        #print(align_dict[ID])
        IDtoindexdict[ID] = counter
        indexdtoIDict[counter]=ID
        align = align_dict[ID]
        
        aacounter = 1
        
        
        for i in range(len(align)):
            if align[i] != "-":
                
                #print(str(counter)+" "+str(aacounter))
                IDaadictlist[counter][aacounter]=i+1
                aacounter += 1
        counter += 1
        
    #print(len(IDaadictlist))
    for i in range(len(IDaadictlist)-1):
        #print(indexdtoIDict[i])
        #print(i)
        #print(alni)
        #print(indexdtoIDict[i])
        csvfile = open(str(msa_map_out_path[alni]+indexdtoIDict[i]+".csv"), 'w')
        fieldnames = ['orth_aanum','msa_aanum']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        for j in IDaadictlist[i]:
            #print(str(j)+" "+str(IDaadictlist[i][j]))
            #save all data:
            writer.writerow({'orth_aanum':str(j),'msa_aanum':str(IDaadictlist[i][j])})
        csvfile.close()

GOF Plots

Find GoF Perfects for Dropouts

quit
# Create a data frame of unique IDs
mutants15_to_plot_mic <- dropout_mutants15_GOF_fitness_mic %>%
  filter(mutations == 1) %>%
  distinct(ID) %>%
  select(ID)
# Initialize an empty vector to store IDs of mutants to be removed
mutants_to_remove_mic <- character()

# Check for missing MSA files
for (i in 1:nrow(mutants15_to_plot_mic)) {
  mutant15_current_temp <- mutants15_to_plot_mic$ID[i]
  if (!file.exists(paste("GOF/MSA_Dropouts/MIC/", mutant15_current_temp, ".csv", sep = ""))) {
    mutants_to_remove_mic <- c(mutants_to_remove_mic, mutant15_current_temp)
  }
}

# Output the results
if (length(mutants_to_remove_mic) > 0) {
  cat("The following mutants will be removed due to missing MSA files:\n")
  print(mutants_to_remove_mic)
  cat("\nTotal number of mutants to be removed:", length(mutants_to_remove_mic), "\n")
  
  # Remove the mutants without MSA files
  mutants15_to_plot_mic <- mutants15_to_plot_mic[!mutants15_to_plot_mic$ID %in% mutants_to_remove_mic, ]
  cat("\nMutants remaining:", nrow(mutants15_to_plot_mic), "\n")
} else {
  cat("All mutants have corresponding MSA files. No mutants will be removed.\n")
}
All mutants have corresponding MSA files. No mutants will be removed.
# If you want to see the remaining mutants
print(mutants15_to_plot_mic)

Read in the E. coli map:

ecoli_map <- read.csv(file=paste("GOF/MSA_Dropouts/Comp/NP_414590.csv", sep=""), head=TRUE, sep=",")

Make a new data frame which will keep all info

GOF_fitness_map_mic <- data.frame(position=numeric(),
                              aa=character(),
                              mutations=numeric(),
                              fitness=numeric(),
                              posortho=numeric(),
                              ingap=character(),
                              mutID=character(),
                              ID=character())

aminoacids <- data.frame(aa=c('G','P','A','V','L','I','M','C','F','Y','W','H','K','R','Q','N','E','D','S','T','X'),
                         aanum=c(1:21))

MSA Mapping: Map the mutants (fitness difference from perfects > 0) over all perfects (fit < -1) for GOF analysis:

#loop over all perfects
for (iii in 1:nrow(mutants15_to_plot_mic)){
  
  #current ortholog:
  mutant_current_mic <- as.character(mutants15_to_plot_mic$ID[iii])
  
  #length of name
  name_size_mic = nchar(paste(mutant_current_mic,"_",sep=""))
  
  #get the MSA mapping
  mutant_map_mic <- read.csv(file=paste("GOF/MSA_Dropouts/MIC/",mutant_current_mic,".csv",sep=""),head=TRUE,sep=",")
  
  #grab the mutants with a fitness increase (GoF) greater than zero (do not include perfects from dataset)
  GOFmutIDinfo_temp_mic <- dropout_mutants15_GOF_fitness_mic %>%
    filter(ID == mutant_current_mic) %>%
    filter(mutations != 0) %>%
    filter(fitD07D03 >= 0.5) ###CHANGE VALUE AS NEEDED (>= 0.5 is default)
  
  # Check if GOFmutIDinfo_temp is empty
  if(nrow(GOFmutIDinfo_temp_mic) == 0) {
    warning(paste("No non-zero mutation data found for ID:", mutant_current_mic))
    next  # Skip to the next iteration of the outer loop
  }
  
  #loop over all mutants for this construct:
  for (mn in 1:nrow(GOFmutIDinfo_temp_mic)) {
    
    #this mutants fitness
    gof_fit_temp_mic <- GOFmutIDinfo_temp_mic$fitD07D03[mn]  # or whichever fitness column you're using
    
    #grab the mut name
    mutations_names_mic <- as.character(GOFmutIDinfo_temp_mic$mutID[mn])
    
    #grab only the relevant portion of the name
    mutations_names_mic <- substr(mutations_names_mic, name_size+1, nchar(mutations_names_mic))
    
    ## split mutation string at non-digits
    s <- strsplit(mutations_names_mic, "_")
    
    for (mutnum in 1:GOFmutIDinfo_temp_mic$mutations[mn]){
      
      #grab the corresponding mutation string
      mutcurr<-s[[1]][mutnum]
      
      #get the position
      mutpos <- as.numeric(str_extract(mutcurr, "[0-9]+"))
      
      #get ending aa
      to_aa <- substr(mutcurr, nchar(mutpos)+2, nchar(mutcurr))
      
      #find the number in the consensus seq
      gof_cons_aanum_index <- which(mutant_map_mic$orth_aanum == mutpos)
      
      if (length(gof_cons_aanum_index) > 0) {
        gof_cons_aanum <- mutant_map_mic$msa_aanum[gof_cons_aanum_index]
        
        #does this map to a non-gap
        if (gof_cons_aanum %in% ecoli_map$msa_aanum){
          
          #the corresponding e.coli residue
          e_coli_residue <- ecoli_map$orth_aanum[which(ecoli_map$msa_aanum == gof_cons_aanum)]
          
          #add this point to the data
          GOF_fitness_map_mic <- rbind(GOF_fitness_map_mic,
                                   data.frame(position=e_coli_residue,
                                              aa=to_aa,
                                              mutations=GOFmutIDinfo_temp_mic$mutations[mn],
                                              fitness=gof_fit_temp_mic,
                                              posortho=mutpos,
                                              ingap="No",
                                              mutID=GOFmutIDinfo_temp_mic$mutID[mn],
                                              ID=GOFmutIDinfo_temp_mic$ID[mn]))
          
        } else {
          #if it's here it maps to a gap
          
          #add this point to the data
          GOF_fitness_map_mic <- rbind(GOF_fitness_map_mic,
                                   data.frame(position=-1,
                                              aa=to_aa,
                                              mutations=GOFmutIDinfo_temp_mic$mutations[mn],
                                              fitness=gof_fit_temp_mic,
                                              posortho=mutpos,
                                              ingap="Yes",
                                              mutID=GOFmutIDinfo_temp_mic$mutID[mn],
                                              ID=GOFmutIDinfo_temp_mic$ID[mn]))
          
        }
      } else {
        warning(paste("No matching orth_aanum found for mutpos:", mutpos, "in ID:", mutant_current_mic))
        # You might want to handle this case, perhaps by skipping this mutation or adding it to a separate list for review
      }
    }
  }
}
Warning: No matching orth_aanum found for mutpos: NA in ID: NP_269082Warning: No non-zero mutation data found for ID: WP_005083623Warning: No non-zero mutation data found for ID: WP_005385658Warning: No non-zero mutation data found for ID: WP_007130009Warning: No non-zero mutation data found for ID: WP_007277186Warning: No matching orth_aanum found for mutpos: 169 in ID: WP_007476896Warning: No matching orth_aanum found for mutpos: 162 in ID: WP_008688576Warning: No matching orth_aanum found for mutpos: 161 in ID: WP_010945031

Collapse the GOF fitness values by aa position along the protein sequence:

GOF_fitness_collapsed_by_pos_mic <- GOF_fitness_map_mic %>%
  filter(position > 0) %>%
  group_by(position) %>%
  summarise(fitval=median(fitness),
            numpoints=n(),
            stdfit=sd(fitness),
            numortho=length(unique(ID)))

GOF Mutant No. Plot

Plot the number of gain-of-function mutants recovered for each aa position along the protein sequence and include cutoff lines for 1 SD above the mean number of GOF mutants and 2 SD above the mean number of GOF mutants. All positions with GOF mutants above the 2 SD line are considered significant positions positively influencing the parent variants ability to complement metabolic function in the E. coli knockout model.

GoF_plot_mic <- ggplot(GOF_fitness_collapsed_by_pos_mic, aes(x=position, y=numpoints, color=numortho)) +
  geom_segment(aes(x = 0, y = mean(numpoints)+2*sd(numpoints), 
                   xend = 160, 
                   yend = mean(numpoints)+2*sd(numpoints)),linetype=2,colour = "blue")+
  geom_segment(aes(x = 0, y = mean(numpoints), xend = 160, yend = mean(numpoints)),linetype=2,colour = "red")+
  geom_point(size=1.8)+
  labs(x = "Position (aa)", y ="Number of gain-of-function mutants",color="") +
  scale_color_gradient(low = "blue", 
                       high = "red",
                       name="Num.\nUniq.\nHomo.",
                       na.value="grey", 
                       limit = c(0,1.1*max(GOF_fitness_collapsed_by_pos_mic$numortho))) +
  scale_x_continuous(breaks=seq(0,160,20))+
  theme(legend.position="left")
GoF_plot_mic <- ggExtra::ggMarginal(GoF_plot_mic,type = "histogram",
                    margins = "y",
                    bins=21,
                    col = 'black',
                    fill = 'red')
Warning: All aesthetics have length 1, but the data has 65 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.Warning: All aesthetics have length 1, but the data has 65 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.Warning: All aesthetics have length 1, but the data has 65 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.Warning: All aesthetics have length 1, but the data has 65 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.
GoF_plot_mic

Print out a summary table of significant aa position along the protein sequence

GOF_fitness_collapsed_by_pos_2sigma_mic <- GOF_fitness_collapsed_by_pos_mic %>%
  filter(numpoints >= (mean(GOF_fitness_collapsed_by_pos_mic$numpoints) + 2*sd(GOF_fitness_collapsed_by_pos_mic$numpoints)))
print(GOF_fitness_collapsed_by_pos_2sigma_mic)

Calculate all Data and Stats:

GOF_fitness_collapsed_all_mic <- GOF_fitness_map_mic %>%
  filter(position > 0) %>%
  group_by(position, aa) %>%
  summarise(fitval=median(fitness),
            numpoints=n(),
            stdfit=sd(fitness),
            numortho=length(unique(ID)))
`summarise()` has grouped output by 'position'. You can override using the `.groups` argument.
gof_aa_dim <- nrow(aminoacids)
gof_ref_len <- nrow(ecoli_map)
#these matrices have the fitness/num/sd for each aa at each position:
gof_matrix = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)
gof_matrix_num = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)
gof_matrix_sd = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)
gof_matrix_numortho = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)

#populate matrix
for (i in 1:nrow(GOF_fitness_collapsed_all_mic)){
  
  gof_matrix[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all_mic$aa[i])),GOF_fitness_collapsed_all_mic$position[i]] <- as.numeric(GOF_fitness_collapsed_all_mic$fitval[i])
  gof_matrix_num[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all_mic$aa[i])),GOF_fitness_collapsed_all_mic$position[i]] <- as.numeric(GOF_fitness_collapsed_all_mic$numpoints[i])
  gof_matrix_sd[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all_mic$aa[i])),GOF_fitness_collapsed_all_mic$position[i]] <- as.numeric(GOF_fitness_collapsed_all_mic$stdfit[i])
  gof_matrix_numortho[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all_mic$aa[i])),GOF_fitness_collapsed_all_mic$position[i]] <- as.numeric(GOF_fitness_collapsed_all_mic$numortho[i])
}

rownames(gof_matrix)<-aminoacids$aa
colnames(gof_matrix)<-c(1:gof_ref_len)
rownames(gof_matrix_num)<-aminoacids$aa
colnames(gof_matrix_num)<-c(1:gof_ref_len)
rownames(gof_matrix_sd)<-aminoacids$aa
colnames(gof_matrix_sd)<-c(1:gof_ref_len)
rownames(gof_matrix_numortho)<-aminoacids$aa
colnames(gof_matrix_numortho)<-c(1:gof_ref_len)

gof_matrix_melt <- melt(gof_matrix)
gof_matrix_num_melt <- melt(gof_matrix_num)
gof_matrix_sd_melt <- melt(gof_matrix_sd)
gof_matrix_numortho_melt <- melt(gof_matrix_numortho)

# Rename columns to "X1" and "X2"
names(gof_matrix_melt)[names(gof_matrix_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
names(gof_matrix_num_melt)[names(gof_matrix_num_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
names(gof_matrix_sd_melt)[names(gof_matrix_sd_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
names(gof_matrix_numortho_melt)[names(gof_matrix_numortho_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")

gof_matrix_melt_only_GOFpos <- gof_matrix_melt %>%
  filter(X2 == 89 |
         X2 == 102 |
         X2 == 103 |
         X2 == 121 |
         X2 == 128 |
         X2 == 129 )

gof_matrix_melt_only_GOFpos$mutposnum <- 0
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==89)] <- 1
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==102)] <- 2
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==103)] <- 3
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==121)] <- 4
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==128)] <- 5
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==129)] <- 6

gof_matrix_melt_only_GOFpos$aanum <- 0
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="A")] <- 12
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="C")] <- 10
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="D")] <- 5
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="E")] <- 4
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="F")] <- 19
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="G")] <- 11
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="H")] <- 3
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="I")] <- 15
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="K")] <- 1
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="L")] <- 14
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="M")] <- 16
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="N")] <- 6
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="P")] <- 17
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="Q")] <- 7
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="R")] <- 2
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="S")] <- 9
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="T")] <- 8
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="V")] <- 13
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="W")] <- 20
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="Y")] <- 18
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="X")] <- 21

gof_matrix_melt_only_GOFpos_wnum <- gof_matrix_melt_only_GOFpos %>%
  inner_join(gof_matrix_num_melt,by=c("X1","X2")) %>%
  dplyr::rename(mutnum=value.y,value=value.x)

GOF Position Plot

Plot the mean fitness of each GoF mutation at the significant positions, with the number of mutants observed at each AA:

# Define the order of amino acids for the rectangles
rect_order <- c("P", "Q", "F", "G", "Y", "E")

# Create a data frame for the rectangles
rect_data <- data.frame(
aanum = match(rect_order, c("K","R","H","E","D","N","Q","T","S","C","G","A","V","L","I","M","P","Y","F","W","X")),
xmin = seq(0.5, by = 1, length.out = length(rect_order)),
xmax = seq(1.5, by = 1, length.out = length(rect_order)))

#plot the data from all mutants:
GOF_fit_nummut_plot_mic <- ggplot(gof_matrix_melt_only_GOFpos_wnum, 
       aes(x=mutposnum, y=aanum,
           fill=value,
           label=mutnum)) +
  geom_tile() +
  geom_text() +
  # Add black rectangles
  geom_rect(data = rect_data,
           aes(xmin = xmin, xmax = xmax, ymin = aanum - 0.5, ymax = aanum + 0.5),
           fill = NA, color = "black", inherit.aes = FALSE) +
  labs(x = "Position (aa)",
       y ="Amino acid",color="") +
  scale_fill_gradient(low = "blue", 
                      high = "red",
                      name="Fitness",
                      na.value="grey",
                      limit = c(0, max(gof_matrix_melt_only_GOFpos_wnum$value))) +
  theme_minimal()+
  scale_x_continuous(name="Position (aa)",
                     breaks=c(1,2,3,4,5,6),
                     labels=c("89","102","103","121","128","129"))+
  scale_y_continuous(name="Amino acid", 
                     breaks=c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21),
                     labels=c("K","R","H","E","D","N","Q","T","S","C","G","A","V","L","I","M","P","Y","F","W","X"))

print(GOF_fit_nummut_plot_mic)

400x MIC (200 ug/mL TMP)

Dropout Perfects

Retrieve all dropout perfects with a log-fold change value greater than -1.0 from the COMPLEMENTATION dataset. Then, retrieve the same perfects (ID) from the 400x MIC dataset and retain only those with fitness less than -1. Also, retrieve all corresponding mutants. Use the mut_collapse_15 dataset which includes 797 perfects (mutations = 0; numprunedBCs = 5).

# Step 1: Identify IDs that have rows where mutations == 0 and fitD05D03 > -1.0 in COMPLEMENTATION
dropout15_ids_with_zero_mutations_complement <- mut_collapse_15 %>%
  filter(mutations == 0 & fitD05D03 > -1.0) %>%
  distinct(ID) %>%
  pull(ID)

# Step 2: Identify the same IDs that have mutations == 0 and fitD11D03 < -1.0 in 400x MIC
dropout15_ids_with_zero_mutations_400mic <- mut_collapse_15 %>%
  filter(ID %in% dropout15_ids_with_zero_mutations_complement & 
         mutations == 0 & 
         fitD11D03 < -1.0 &
         !is.na(fitD11D03)) %>%
  distinct(ID) %>%
  pull(ID)

# Step 3: Retrieve the rows for these IDs
result_rows_400mic <- mut_collapse_15 %>%
  filter(ID %in% dropout15_ids_with_zero_mutations_400mic & mutations == 0)

# Step 4: Filter the main dataset to keep mutants if they match a corresponding perfect ID
dropout_mutants15_GOF_400mic <- mut_collapse_15 %>%
  filter(
    (mutations == 0 & !is.na(fitD05D03) & fitD05D03 > -1.0 & 
     !is.na(fitD11D03) & fitD11D03 < -1.0) |
    (mutations != 0 & fitD11D03 > -1.0 & ID %in% dropout15_ids_with_zero_mutations_400mic)) %>%
  dplyr::select(ID, mutID, numprunedBCs, mutations, fitD05D03, fitD11D03, seq)

Validate that rows where mutations != 0 have an ID that matches rows where mutations == 0 and fitD05D03 < -1.0.

# Verification step
verification_result_400mic <- dropout_mutants15_GOF_400mic %>%
  filter(mutations == 0) %>%
  mutate(
    condition_met = fitD05D03 > -1.0 & fitD11D03 < -1,
    fitD05D03_check = fitD05D03 > -1.0,
    fitD11D03_check = fitD11D03 < -1
  )

# Check if all rows meet the condition
all_conditions_met_400mic <- all(verification_result_400mic$condition_met)

# Summary of the verification
verification_summary_400mic <- verification_result_400mic %>%
  summarise(
    total_rows = n(),
    rows_meeting_both_conditions = sum(condition_met),
    rows_meeting_fitD05D03 = sum(fitD05D03_check),
    rows_meeting_fitD11D03 = sum(fitD11D03_check)
  )

# Print results
print("Verification Summary:")
[1] "Verification Summary:"
print(verification_summary_400mic)
print(paste("All conditions met:", all_conditions_met_400mic))
[1] "All conditions met: TRUE"
# If there are any rows not meeting the conditions, display them
if (!all_conditions_met_400mic) {
  print("Rows not meeting both conditions:")
  print(verification_result_400mic %>% filter(!condition_met) %>% select(ID, fitD05D03, fitD11D03))
}

Validate that rows where mutations != 0 have an ID that matches rows where mutations == 0 and fitD11D03 < -1.0.

# 1. First, create two subsets of the data
zero_mutation_rows_400mic <- dropout_mutants15_GOF_400mic %>%
  filter(mutations == 0 & fitD11D03 < -1.0)

non_zero_mutation_rows_400mic <- dropout_mutants15_GOF_400mic %>%
  filter(mutations != 0 & fitD11D03 > -1.0)

# 2. Check that all IDs in non_zero_mutation_rows are present in zero_mutation_rows
all_valid_ids_400mic <- all(non_zero_mutation_rows_400mic$ID %in% zero_mutation_rows_400mic$ID)
print(paste("All non-zero mutation rows have a matching zero mutation row:", all_valid_ids_400mic))
[1] "All non-zero mutation rows have a matching zero mutation row: TRUE"
# 3. If the above is FALSE, find the problematic IDs
if (!all_valid_ids_400mic) {
  problematic_ids_400mic <- setdiff(non_zero_mutation_rows_400mic$ID, zero_mutation_rows_400mic$ID)
  print("IDs with non-zero mutations but no matching zero mutation row:")
  print(problematic_ids_400mic)
}

# 4. Check for any IDs in zero_mutation_rows that don't have a corresponding non-zero mutation row
ids_without_non_zero_400mic <- setdiff(zero_mutation_rows_400mic$ID, non_zero_mutation_rows_400mic$ID)
print("IDs with zero mutations but no corresponding non-zero mutation rows:")
[1] "IDs with zero mutations but no corresponding non-zero mutation rows:"
print(ids_without_non_zero_400mic)
  [1] "NP_269082"    "NP_390064"    "NP_721345"    "NP_747233"    "NP_944205"    "WP_000162304" "WP_000162306" "WP_000162307"
  [9] "WP_000162309" "WP_000162310" "WP_000162311" "WP_000162312" "WP_000162313" "WP_000162437" "WP_000162448" "WP_000162450"
 [17] "WP_000162451" "WP_000162457" "WP_000162462" "WP_000162470" "WP_000162471" "WP_000162477" "WP_000162478" "WP_000162482"
 [25] "WP_000162485" "WP_000162499" "WP_000162501" "WP_000162502" "WP_000162504" "WP_000175741" "WP_000175742" "WP_000175753"
 [33] "WP_000312553" "WP_000637196" "WP_000637199" "WP_000637203" "WP_000637204" "WP_000637211" "WP_000637216" "WP_000973544"
 [41] "WP_001035150" "WP_002065185" "WP_002114723" "WP_002118976" "WP_002216373" "WP_002256361" "WP_002569855" "WP_002578253"
 [49] "WP_002583107" "WP_002587016" "WP_002596555" "WP_002602832" "WP_002664533" "WP_002670343" "WP_002811844" "WP_002841202"
 [57] "WP_002889081" "WP_002904437" "WP_002906638" "WP_002909821" "WP_002917312" "WP_002924855" "WP_002934971" "WP_002984955"
 [65] "WP_002990062" "WP_003011321" "WP_003016297" "WP_003027976" "WP_003032260" "WP_003035097" "WP_003054187" "WP_003065197"
 [73] "WP_003072921" "WP_003088134" "WP_003093756" "WP_003218222" "WP_003268911" "WP_003321978" "WP_003325705" "WP_003441833"
 [81] "WP_003474891" "WP_003479843" "WP_003607688" "WP_003633095" "WP_003649012" "WP_003654807" "WP_003666061" "WP_003674233"
 [89] "WP_003683166" "WP_003717025" "WP_003747727" "WP_003748519" "WP_003758501" "WP_003762131" "WP_003773894" "WP_003779699"
 [97] "WP_003795223" "WP_004079202" "WP_004138800" "WP_004187031" "WP_004232047" "WP_004252176" "WP_004286630" "WP_004288595"
[105] "WP_004297954" "WP_004315768" "WP_004321824" "WP_004336126" "WP_004354535" "WP_004357589" "WP_004359715" "WP_004362212"
[113] "WP_004366090" "WP_004368566" "WP_004375525" "WP_004382106" "WP_004456775" "WP_004547470" "WP_004585598" "WP_004614090"
[121] "WP_004762151" "WP_004794794" "WP_004818861" "WP_004830208" "WP_004838134" "WP_004852952" "WP_004855114" "WP_004916485"
[129] "WP_004920127" "WP_004955442" "WP_004974792" "WP_004993976" "WP_005033778" "WP_005083623" "WP_005166996" "WP_005210368"
[137] "WP_005357877" "WP_005364041" "WP_005369895" "WP_005379345" "WP_005392293" "WP_005398549" "WP_005427260" "WP_005430428"
[145] "WP_005433217" "WP_005450473" "WP_005549928" "WP_005558437" "WP_005593154" "WP_005605215" "WP_005612722" "WP_005622496"
[153] "WP_005635192" "WP_005647059" "WP_005648179" "WP_005707354" "WP_005708598" "WP_005742812" "WP_005765106" "WP_005790642"
[161] "WP_005797943" "WP_005808285" "WP_005823311" "WP_005838503" "WP_005845866" "WP_005858889" "WP_005878541" "WP_005933374"
[169] "WP_005935635" "WP_005956098" "WP_005998635" "WP_006036807" "WP_006044971" "WP_006145153" "WP_006148032" "WP_006154666"
[177] "WP_006256429" "WP_006266370" "WP_006318421" "WP_006427068" "WP_006451480" "WP_006460949" "WP_006493548" "WP_006531276"
[185] "WP_006565566" "WP_006578126" "WP_006596310" "WP_006681821" "WP_006711975" "WP_006716908" "WP_006784524" "WP_006794536"
[193] "WP_006891260" "WP_006907657" "WP_006948792" "WP_006956699" "WP_006995561" "WP_007007746" "WP_007130009" "WP_007134290"
[201] "WP_007231246" "WP_007277186" "WP_007416017" "WP_007420749" "WP_007484265" "WP_007500805" "WP_007517024" "WP_007546669"
[209] "WP_007563269" "WP_007639786" "WP_007667931" "WP_007766775" "WP_007788671" "WP_007836218" "WP_007894152" "WP_007972129"
[217] "WP_008024774" "WP_008044785" "WP_008073041" "WP_008106982" "WP_008112253" "WP_008136828" "WP_008174809" "WP_008209042"
[225] "WP_008244029" "WP_008254783" "WP_008466551" "WP_008487653" "WP_008506181" "WP_008556995" "WP_008664019" "WP_008754633"
[233] "WP_008761062" "WP_008808665" "WP_008811335" "WP_008826323" "WP_008925559" "WP_008989833" "WP_009124418" "WP_009133007"
[241] "WP_009175149" "WP_009193507" "WP_009244499" "WP_009262904" "WP_009268572" "WP_009317804" "WP_009372837" "WP_009412440"
[249] "WP_009416511" "WP_009417176" "WP_009437540" "WP_009443405" "WP_009461124" "WP_009496614" "WP_009500922" "WP_009526511"
[257] "WP_009586372" "WP_009589199" "WP_009641684" "WP_009669573" "WP_009677188" "WP_009731090" "WP_009843318" "WP_009850000"
[265] "WP_010073895" "WP_010359207" "WP_010371780" "WP_010381642" "WP_010735128" "WP_010759385" "WP_010761388" "WP_010780948"
[273] "WP_010850622" "WP_011000898" "WP_011009589" "WP_011162224" "WP_011183282" "WP_011201021" "WP_011213001" "WP_011242231"
[281] "WP_011264555" "WP_011275775" "WP_011282675" "WP_011286345" "WP_011303097" "WP_011346477" "WP_011399565" "WP_011482754"
[289] "WP_011489445" "WP_011584955" "WP_011608342" "WP_011627026" "WP_011643962"
# 5. Summary statistics
print(paste("Number of unique IDs in zero mutation rows:", n_distinct(zero_mutation_rows_400mic$ID)))
[1] "Number of unique IDs in zero mutation rows: 337"
print(paste("Number of unique IDs in non-zero mutation rows:", n_distinct(non_zero_mutation_rows_400mic$ID)))
[1] "Number of unique IDs in non-zero mutation rows: 44"
# 6. Distribution of mutation counts for non-zero mutation rows
mutation_distribution_400mic <- non_zero_mutation_rows_400mic %>%
  group_by(mutations) %>%
  summarise(count = n()) %>%
  arrange(mutations)

print("Distribution of mutation counts:")
[1] "Distribution of mutation counts:"
print(mutation_distribution_400mic)

# 7. Check for any unexpected mutation values
unexpected_mutations_400mic <- dropout_mutants15_GOF_400mic %>%
  filter(mutations < 0 | mutations > 5)  # Adjust the upper bound as needed

if (nrow(unexpected_mutations_400mic) > 0) {
  print("Rows with unexpected mutation values:")
  print(unexpected_mutations_400mic)
} else {
  print("No unexpected mutation values found.")
}
[1] "No unexpected mutation values found."

Remove unique perfect IDs if there is no corresponding mutants:

# Step 1: Identify IDs with zero mutations
zero_mutation_ids_400mic <- dropout_mutants15_GOF_400mic %>%
  filter(mutations == 0 & fitD11D03 < -1.0) %>%
  pull(ID)

# Step 2: Identify IDs with non-zero mutations
non_zero_mutation_ids_400mic <- dropout_mutants15_GOF_400mic %>%
  filter(mutations != 0 & fitD11D03 > -1.0) %>%
  pull(ID)

# Step 3: Find IDs that have zero mutations but no corresponding non-zero mutation rows
ids_to_remove_400mic <- setdiff(zero_mutation_ids_400mic, non_zero_mutation_ids_400mic)

# Step 4: Remove the rows with these IDs
dropout_mutants15_GOF_400mic_cleaned <- dropout_mutants15_GOF_400mic %>%
  filter(!(ID %in% ids_to_remove_400mic))

# Print summary
print(paste("Number of rows before cleaning:", nrow(dropout_mutants15_GOF_400mic)))
[1] "Number of rows before cleaning: 403"
print(paste("Number of rows after cleaning:", nrow(dropout_mutants15_GOF_400mic_cleaned)))
[1] "Number of rows after cleaning: 110"
print(paste("Number of rows removed:", nrow(dropout_mutants15_GOF_400mic) - nrow(dropout_mutants15_GOF_400mic_cleaned)))
[1] "Number of rows removed: 293"
print(paste("Number of unique IDs removed:", length(ids_to_remove_400mic)))
[1] "Number of unique IDs removed: 293"
# Optionally, you can print the removed IDs
print("IDs removed:")
[1] "IDs removed:"
print(ids_to_remove_400mic)
  [1] "NP_269082"    "NP_390064"    "NP_721345"    "NP_747233"    "NP_944205"    "WP_000162304" "WP_000162306" "WP_000162307"
  [9] "WP_000162309" "WP_000162310" "WP_000162311" "WP_000162312" "WP_000162313" "WP_000162437" "WP_000162448" "WP_000162450"
 [17] "WP_000162451" "WP_000162457" "WP_000162462" "WP_000162470" "WP_000162471" "WP_000162477" "WP_000162478" "WP_000162482"
 [25] "WP_000162485" "WP_000162499" "WP_000162501" "WP_000162502" "WP_000162504" "WP_000175741" "WP_000175742" "WP_000175753"
 [33] "WP_000312553" "WP_000637196" "WP_000637199" "WP_000637203" "WP_000637204" "WP_000637211" "WP_000637216" "WP_000973544"
 [41] "WP_001035150" "WP_002065185" "WP_002114723" "WP_002118976" "WP_002216373" "WP_002256361" "WP_002569855" "WP_002578253"
 [49] "WP_002583107" "WP_002587016" "WP_002596555" "WP_002602832" "WP_002664533" "WP_002670343" "WP_002811844" "WP_002841202"
 [57] "WP_002889081" "WP_002904437" "WP_002906638" "WP_002909821" "WP_002917312" "WP_002924855" "WP_002934971" "WP_002984955"
 [65] "WP_002990062" "WP_003011321" "WP_003016297" "WP_003027976" "WP_003032260" "WP_003035097" "WP_003054187" "WP_003065197"
 [73] "WP_003072921" "WP_003088134" "WP_003093756" "WP_003218222" "WP_003268911" "WP_003321978" "WP_003325705" "WP_003441833"
 [81] "WP_003474891" "WP_003479843" "WP_003607688" "WP_003633095" "WP_003649012" "WP_003654807" "WP_003666061" "WP_003674233"
 [89] "WP_003683166" "WP_003717025" "WP_003747727" "WP_003748519" "WP_003758501" "WP_003762131" "WP_003773894" "WP_003779699"
 [97] "WP_003795223" "WP_004079202" "WP_004138800" "WP_004187031" "WP_004232047" "WP_004252176" "WP_004286630" "WP_004288595"
[105] "WP_004297954" "WP_004315768" "WP_004321824" "WP_004336126" "WP_004354535" "WP_004357589" "WP_004359715" "WP_004362212"
[113] "WP_004366090" "WP_004368566" "WP_004375525" "WP_004382106" "WP_004456775" "WP_004547470" "WP_004585598" "WP_004614090"
[121] "WP_004762151" "WP_004794794" "WP_004818861" "WP_004830208" "WP_004838134" "WP_004852952" "WP_004855114" "WP_004916485"
[129] "WP_004920127" "WP_004955442" "WP_004974792" "WP_004993976" "WP_005033778" "WP_005083623" "WP_005166996" "WP_005210368"
[137] "WP_005357877" "WP_005364041" "WP_005369895" "WP_005379345" "WP_005392293" "WP_005398549" "WP_005427260" "WP_005430428"
[145] "WP_005433217" "WP_005450473" "WP_005549928" "WP_005558437" "WP_005593154" "WP_005605215" "WP_005612722" "WP_005622496"
[153] "WP_005635192" "WP_005647059" "WP_005648179" "WP_005707354" "WP_005708598" "WP_005742812" "WP_005765106" "WP_005790642"
[161] "WP_005797943" "WP_005808285" "WP_005823311" "WP_005838503" "WP_005845866" "WP_005858889" "WP_005878541" "WP_005933374"
[169] "WP_005935635" "WP_005956098" "WP_005998635" "WP_006036807" "WP_006044971" "WP_006145153" "WP_006148032" "WP_006154666"
[177] "WP_006256429" "WP_006266370" "WP_006318421" "WP_006427068" "WP_006451480" "WP_006460949" "WP_006493548" "WP_006531276"
[185] "WP_006565566" "WP_006578126" "WP_006596310" "WP_006681821" "WP_006711975" "WP_006716908" "WP_006784524" "WP_006794536"
[193] "WP_006891260" "WP_006907657" "WP_006948792" "WP_006956699" "WP_006995561" "WP_007007746" "WP_007130009" "WP_007134290"
[201] "WP_007231246" "WP_007277186" "WP_007416017" "WP_007420749" "WP_007484265" "WP_007500805" "WP_007517024" "WP_007546669"
[209] "WP_007563269" "WP_007639786" "WP_007667931" "WP_007766775" "WP_007788671" "WP_007836218" "WP_007894152" "WP_007972129"
[217] "WP_008024774" "WP_008044785" "WP_008073041" "WP_008106982" "WP_008112253" "WP_008136828" "WP_008174809" "WP_008209042"
[225] "WP_008244029" "WP_008254783" "WP_008466551" "WP_008487653" "WP_008506181" "WP_008556995" "WP_008664019" "WP_008754633"
[233] "WP_008761062" "WP_008808665" "WP_008811335" "WP_008826323" "WP_008925559" "WP_008989833" "WP_009124418" "WP_009133007"
[241] "WP_009175149" "WP_009193507" "WP_009244499" "WP_009262904" "WP_009268572" "WP_009317804" "WP_009372837" "WP_009412440"
[249] "WP_009416511" "WP_009417176" "WP_009437540" "WP_009443405" "WP_009461124" "WP_009496614" "WP_009500922" "WP_009526511"
[257] "WP_009586372" "WP_009589199" "WP_009641684" "WP_009669573" "WP_009677188" "WP_009731090" "WP_009843318" "WP_009850000"
[265] "WP_010073895" "WP_010359207" "WP_010371780" "WP_010381642" "WP_010735128" "WP_010759385" "WP_010761388" "WP_010780948"
[273] "WP_010850622" "WP_011000898" "WP_011009589" "WP_011162224" "WP_011183282" "WP_011201021" "WP_011213001" "WP_011242231"
[281] "WP_011264555" "WP_011275775" "WP_011282675" "WP_011286345" "WP_011303097" "WP_011346477" "WP_011399565" "WP_011482754"
[289] "WP_011489445" "WP_011584955" "WP_011608342" "WP_011627026" "WP_011643962"
# Assign the cleaned data back to dropout_mutants15_GOF if you want to update the original variable
dropout_mutants15_GOF_400mic <- dropout_mutants15_GOF_400mic_cleaned

Dropout Mutants

Summarize the number of perfects and mutants at each AA distance after filtering:

# Create a function to count unique mutIDs for a given number of mutations
dropout_mutants15_GOF_count_400mic <- function(data, mutation_count) {
  length(unique(subset(data, mutations == mutation_count)$mutID))
}

# Create a vector of counts for mutations 1-5
dropout15_counts_400mic <- sapply(1:5, function(x) dropout_mutants15_GOF_count_400mic(dropout_mutants15_GOF_400mic, x))

# Count perfects separately
perfects_count_400mic <- length(unique(subset(dropout_mutants15_GOF_400mic, mutations == 0 & fitD11D03 < -1.0)$mutID))

# Create a data frame with the results, including the summary row
dropout_mutants15_GOF_table_400mic <- data.frame(
  Mutations = c("Perfects (fit < -1.0)", "1 Mutation", "2 Mutations", "3 Mutations", "4 Mutations", "5 Mutations", "Total Mutations"),
  Count = c(perfects_count_400mic, dropout15_counts_400mic, sum(dropout15_counts_400mic))
)

# Print the table
print(dropout_mutants15_GOF_table_400mic)

GOF Fitness

GOF Fitness: Separate the dropout_mutants15_GOF_mic dataset into two new dataframes, where DF1 contains perfects and DF2 contains mutants:

# Create a dataframe with mutations == 0
dropout_mutants15_GOF_no_mutations_400mic <- dropout_mutants15_GOF_400mic[dropout_mutants15_GOF_400mic$mutations == 0, ]

# Create a dataframe with mutations != 0
dropout_mutants15_GOF_with_mutations_400mic <- dropout_mutants15_GOF_400mic[dropout_mutants15_GOF_400mic$mutations != 0, ]

Now, re-combine these dataframes to retain only the mutants

# Step 1: Prepare the reference dataframe
df_reference_400mic <- dropout_mutants15_GOF_no_mutations_400mic %>%
  select(ID, fitD11D03) %>%
  rename(reference_fitD11D03 = fitD11D03)

# Step 2: Join and calculate the difference
dropout_mutants15_GOF_fitness_400mic <- dropout_mutants15_GOF_with_mutations_400mic %>%
  left_join(df_reference_400mic, by = "ID") %>%
  mutate(fitD11D03 = fitD11D03 - reference_fitD11D03) %>%
  select(ID, mutID, mutations, fitD11D03)

# Print summary statistics
print(paste("Number of Mutants:", nrow(dropout_mutants15_GOF_fitness_400mic)))
[1] "Number of Mutants: 66"
print(paste("Unique IDs:", length(unique(dropout_mutants15_GOF_fitness_400mic$ID))))
[1] "Unique IDs: 44"
print(paste("Range of fitD11D03:", 
            paste(round(range(dropout_mutants15_GOF_fitness_400mic$fitD11D03, na.rm = TRUE), 1), collapse = " to ")))
[1] "Range of fitD11D03: 0.5 to 14.9"
# Count unique IDs with mutations == 0
unique_ids_zero_mutations <- dropout_mutants15_GOF_fitness_400mic %>%
  distinct(ID) %>%
  nrow()

print(paste("Number of unique IDs:", unique_ids_zero_mutations))
[1] "Number of unique IDs: 44"

Boxplot: Plot mutant fitness relative to parent variant by number of mutations:

GOF_muts_fitness_by_muts_plot_400mic <- ggplot(dropout_mutants15_GOF_fitness_400mic, 
                                        aes(x = factor(mutations), y = fitD11D03)) +
  geom_boxplot() +
  labs(title = "fitD11D03 by Number of Mutations", x = "Number of Mutations", y = "fitD11D03")

print(GOF_muts_fitness_by_muts_plot_400mic)

Histogram of Mutant Fitness: Clearly shows mutant fitness is normally distributed.

GOF_muts_fitness_dist_plot_400mic <- ggplot(dropout_mutants15_GOF_fitness_400mic, aes(x = fitD11D03)) +
  geom_histogram(binwidth = 0.5, fill = "blue", color = "black") +
  labs(title = "Distribution of fitD11D03", x = "fitD11D03", y = "Count")

print(GOF_muts_fitness_dist_plot_400mic)

GOF Alignment

FASTA: Generate a FASTA file from the filtered dropout_mutants15_GoF perfects dataset for GoF analysis:

# First, let's ensure we have the correct unique IDs for mutations == 1
dropout_mutants15_GOF_400mic_1mut_unique_ids <- dropout_mutants15_GOF_fitness_400mic %>%
  filter(mutations == 1) %>%
  distinct(ID) %>%
  pull(ID)

# Now, let's use these IDs to filter the dropout_mutants15_GOF dataset
dropout_mutants15_GOF_400mic_1mut_unique_id_seq <- dropout_mutants15_GOF_400mic %>%
  filter(ID %in% dropout_mutants15_GOF_400mic_1mut_unique_ids & mutations == 0) %>%
  select(ID, seq)

# Create the sequences in FASTA format
dropout_mutants15_GOF_400mic_fasta_content <- paste(">", dropout_mutants15_GOF_400mic_1mut_unique_id_seq$ID, "\n", dropout_mutants15_GOF_400mic_1mut_unique_id_seq$seq, "\n", sep = "", collapse = "")

# Define the file path in the working directory
dropout_mutants15_GOF_400mic_fasta_file_path <- file.path(getwd(), "GOF/MSA_Dropouts/400xMIC/FASTA/Lib15.GoF.perfects.400mic.fasta")

# Write the FASTA content to the file (37 unique ID)
writeLines(dropout_mutants15_GOF_400mic_fasta_content, 
           con = dropout_mutants15_GOF_400mic_fasta_file_path)

Alignment: Use the clustalo executable to align the protein sequences associated with the dropout perfects. This will align the FASTA file: Lib15.GoF.perfects.complementation.fasta for use in GoF analysis.

./Scripts/clustalo -i GOF/MSA_Dropouts/400xMIC/FASTA/Lib15.GoF.perfects.400mic.fasta -o GOF/MSA_Dropouts/400xMIC/FASTA/Lib15.GoF.perfects.400mic.tree.aligned.mod.aln --outfmt=clustal --force

Mapping Residues: Use the following map.aligned.residues.py python script to generate csv files for each designed homolog that maps residue positions of each A.A. from the alignment FASTA:

import time
import csv

##################################
#INPUTS:

base_path = ""
trees_path_prefix = base_path+""

#clustal format alignment file
align_file_in = [trees_path_prefix+"GOF/MSA_Dropouts/400xMIC/FASTA/Lib15.GoF.perfects.400mic.tree.aligned.mod.aln"]

#number of seqs in each alignment file
num_samples_in_file = [38] #New FASTA w/ mutant fit > -1 (+1 from actual file count)

##################################
#OUTPUTS:

msa_map_out_path = [trees_path_prefix+"GOF/MSA_Dropouts/400xMIC/"]

# Loop to generate .csv files for each ID
for alni in range(1):#len(align_file_in)):
    #print(alni)
    
    ##################################
    #VARIABLES:
    
    #ID as key, align as value
    align_dict = dict()
    
    #num_samples
    num_samples = num_samples_in_file[alni]
    
    #pos key, consensus pos val
    IDaadictlist = [dict() for x in range(num_samples)]
    
    IDtoindexdict = dict()
    indexdtoIDict = dict()
    
    ##################################
    #CODE:
    
    line_count = 0
    #loop over all alignments:
    print(align_file_in[alni])
    for line in open(align_file_in[alni]):
        #skip header
        if line_count > 1:
            listWords = line.split('    ')
            ID = listWords[0]
            align = line[16:].rstrip()
            if ID.strip() != "":
                align_dict[ID] = align_dict.get(ID, "") + align.replace(" ", "")
        line_count += 1
    
    #print("NP_414590")
    #print(align_dict["NP_414590"])

    counter = 0
    for ID in align_dict:
        #print(ID)
        #print(align_dict[ID])
        IDtoindexdict[ID] = counter
        indexdtoIDict[counter]=ID
        align = align_dict[ID]
        
        aacounter = 1
        
        
        for i in range(len(align)):
            if align[i] != "-":
                
                #print(str(counter)+" "+str(aacounter))
                IDaadictlist[counter][aacounter]=i+1
                aacounter += 1
        counter += 1
        
    #print(len(IDaadictlist))
    for i in range(len(IDaadictlist)-1):
        #print(indexdtoIDict[i])
        #print(i)
        #print(alni)
        #print(indexdtoIDict[i])
        csvfile = open(str(msa_map_out_path[alni]+indexdtoIDict[i]+".csv"), 'w')
        fieldnames = ['orth_aanum','msa_aanum']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        for j in IDaadictlist[i]:
            #print(str(j)+" "+str(IDaadictlist[i][j]))
            #save all data:
            writer.writerow({'orth_aanum':str(j),'msa_aanum':str(IDaadictlist[i][j])})
        csvfile.close()

GOF Plots

Find GoF Perfects for Dropouts

quit
# Create a data frame of unique IDs
mutants15_to_plot_400mic <- dropout_mutants15_GOF_fitness_400mic %>%
  filter(mutations == 1) %>%
  distinct(ID) %>%
  select(ID)
# Initialize an empty vector to store IDs of mutants to be removed
mutants_to_remove_400mic <- character()

# Check for missing MSA files
for (i in 1:nrow(mutants15_to_plot_400mic)) {
  mutant15_current_temp <- mutants15_to_plot_400mic$ID[i]
  if (!file.exists(paste("GOF/MSA_Dropouts/400xMIC/", mutant15_current_temp, ".csv", sep = ""))) {
    mutants_to_remove_400mic <- c(mutants_to_remove_400mic, mutant15_current_temp)
  }
}

# Output the results
if (length(mutants_to_remove_400mic) > 0) {
  cat("The following mutants will be removed due to missing MSA files:\n")
  print(mutants_to_remove_400mic)
  cat("\nTotal number of mutants to be removed:", length(mutants_to_remove_400mic), "\n")
  
  # Remove the mutants without MSA files
  mutants15_to_plot_400mic <- mutants15_to_plot_400mic[!mutants15_to_plot_400mic$ID %in% mutants_to_remove_400mic, ]
  cat("\nMutants remaining:", nrow(mutants15_to_plot_400mic), "\n")
} else {
  cat("All mutants have corresponding MSA files. No mutants will be removed.\n")
}
All mutants have corresponding MSA files. No mutants will be removed.
# If you want to see the remaining mutants
print(mutants15_to_plot_400mic)

Read in the E. coli map:

ecoli_map <- read.csv(file=paste("GOF/MSA_Dropouts/Comp/NP_414590.csv", sep=""), 
                      head=TRUE, sep=",")

Make a new data frame which will keep all info

GOF_fitness_map_400mic <- data.frame(position=numeric(),
                              aa=character(),
                              mutations=numeric(),
                              fitness=numeric(),
                              posortho=numeric(),
                              ingap=character(),
                              mutID=character(),
                              ID=character())

aminoacids <- data.frame(aa=c('G','P','A','V','L','I','M','C','F','Y','W','H','K','R','Q','N','E','D','S','T','X'),
                         aanum=c(1:21))

MSA Mapping: Map the mutants (fitness difference from perfects > 0) over all perfects (fit < -1) for GOF analysis:

#loop over all perfects
for (iii in 1:nrow(mutants15_to_plot_400mic)){
  
  #current ortholog:
  mutant_current_400mic <- as.character(mutants15_to_plot_400mic$ID[iii])
  
  #length of name
  name_size_400mic = nchar(paste(mutant_current_400mic,"_",sep=""))
  
  #get the MSA mapping
  mutant_map_400mic <- read.csv(file=paste("GOF/MSA_Dropouts/400xMIC/",mutant_current_400mic,".csv",sep=""),head=TRUE,sep=",")
  
  #grab the mutants with a fitness increase (GoF) greater than zero (do not include perfects from dataset)
  GOFmutIDinfo_temp_400mic <- dropout_mutants15_GOF_fitness_400mic %>%
    filter(ID == mutant_current_400mic) %>%
    filter(mutations != 0) %>%
    filter(fitD11D03 >= 0.5) ###CHANGE VALUE AS NEEDED (default = 0.5)
  
  # Check if GOFmutIDinfo_temp is empty
  if(nrow(GOFmutIDinfo_temp_400mic) == 0) {
    warning(paste("No non-zero mutation data found for ID:", mutant_current_400mic))
    next  # Skip to the next iteration of the outer loop
  }
  
  #loop over all mutants for this construct:
  for (mn in 1:nrow(GOFmutIDinfo_temp_400mic)) {
    
    #this mutants fitness
    gof_fit_temp_400mic <- GOFmutIDinfo_temp_400mic$fitD11D03[mn]  # or whichever fitness column you're using
    
    #grab the mut name
    mutations_names_400mic <- as.character(GOFmutIDinfo_temp_400mic$mutID[mn])
    
    #grab only the relevant portion of the name
    mutations_names_400mic <- substr(mutations_names_400mic, name_size+1, nchar(mutations_names_400mic))
    
    ## split mutation string at non-digits
    s <- strsplit(mutations_names_400mic, "_")
    
    for (mutnum in 1:GOFmutIDinfo_temp_400mic$mutations[mn]){
      
      #grab the corresponding mutation string
      mutcurr<-s[[1]][mutnum]
      
      #get the position
      mutpos <- as.numeric(str_extract(mutcurr, "[0-9]+"))
      
      #get ending aa
      to_aa <- substr(mutcurr, nchar(mutpos)+2, nchar(mutcurr))
      
      #find the number in the consensus seq
      gof_cons_aanum_index <- which(mutant_map_400mic$orth_aanum == mutpos)
      
      if (length(gof_cons_aanum_index) > 0) {
        gof_cons_aanum <- mutant_map_400mic$msa_aanum[gof_cons_aanum_index]
        
        #does this map to a non-gap
        if (gof_cons_aanum %in% ecoli_map$msa_aanum){
          
          #the corresponding e.coli residue
          e_coli_residue <- ecoli_map$orth_aanum[which(ecoli_map$msa_aanum == gof_cons_aanum)]
          
          #add this point to the data
          GOF_fitness_map_400mic <- rbind(GOF_fitness_map_400mic,
                                   data.frame(position=e_coli_residue,
                                              aa=to_aa,
                                              mutations=GOFmutIDinfo_temp_400mic$mutations[mn],
                                              fitness=gof_fit_temp_400mic,
                                              posortho=mutpos,
                                              ingap="No",
                                              mutID=GOFmutIDinfo_temp_400mic$mutID[mn],
                                              ID=GOFmutIDinfo_temp_400mic$ID[mn]))
          
        } else {
          #if it's here it maps to a gap
          
          #add this point to the data
          GOF_fitness_map_400mic <- rbind(GOF_fitness_map_400mic,
                                   data.frame(position=-1,
                                              aa=to_aa,
                                              mutations=GOFmutIDinfo_temp_400mic$mutations[mn],
                                              fitness=gof_fit_temp_400mic,
                                              posortho=mutpos,
                                              ingap="Yes",
                                              mutID=GOFmutIDinfo_temp_400mic$mutID[mn],
                                              ID=GOFmutIDinfo_temp_400mic$ID[mn]))
          
        }
      } else {
        warning(paste("No matching orth_aanum found for mutpos:", mutpos, "in ID:", mutant_current_400mic))
        # You might want to handle this case, perhaps by skipping this mutation or adding it to a separate list for review
      }
    }
  }
}
Warning: No matching orth_aanum found for mutpos: NA in ID: NP_636179Warning: No matching orth_aanum found for mutpos: NA in ID: NP_831957

Collapse the GOF fitness values by aa position along the protein sequence:

GOF_fitness_collapsed_by_pos_400mic <- GOF_fitness_map_400mic %>%
  filter(position > 0) %>%
  group_by(position) %>%
  summarise(fitval=median(fitness),
            numpoints=n(),
            stdfit=sd(fitness),
            numortho=length(unique(ID)))

GOF Mutant No. Plot

Plot the number of gain-of-function mutants recovered for each aa position along the protein sequence and include cutoff lines for 1 SD above the mean number of GOF mutants and 2 SD above the mean number of GOF mutants. All positions with GOF mutants above the 2 SD line are considered significant positions positively influencing the parent variants ability to complement metabolic function in the E. coli knockout model.

GoF_plot_400mic <- ggplot(GOF_fitness_collapsed_by_pos_400mic, aes(x=position, y=numpoints, color=numortho)) +
  geom_segment(aes(x = 0, y = mean(numpoints)+2*sd(numpoints), 
                   xend = 160, 
                   yend = mean(numpoints)+2*sd(numpoints)),linetype=2,colour = "blue")+
  geom_segment(aes(x = 0, y = mean(numpoints), xend = 160, yend = mean(numpoints)),linetype=2,colour = "red")+
  geom_point(size=1.8)+
  labs(x = "Position (aa)", y ="Number of gain-of-function mutants",color="") +
  scale_color_gradient(low = "blue", 
                       high = "red",
                       name="Num.\nUniq.\nHomo.",
                       na.value="grey", 
                       limit = c(0,1.1*max(GOF_fitness_collapsed_by_pos_400mic$numortho))) +
  scale_x_continuous(breaks=seq(0,160,20))+
  theme(legend.position="left")
GoF_plot_400mic <- ggExtra::ggMarginal(GoF_plot_400mic,type = "histogram",
                    margins = "y",
                    bins=21,
                    col = 'black',
                    fill = 'red')
Warning: All aesthetics have length 1, but the data has 46 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.Warning: All aesthetics have length 1, but the data has 46 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.Warning: All aesthetics have length 1, but the data has 46 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.Warning: All aesthetics have length 1, but the data has 46 rows.
ℹ Please consider using `annotate()` or provide this layer with data containing a single row.
GoF_plot_400mic

Print out a summary table of significant aa position along the protein sequence

GOF_fitness_collapsed_by_pos_2sigma_400mic <- GOF_fitness_collapsed_by_pos_400mic %>%
  filter(numpoints >= (mean(GOF_fitness_collapsed_by_pos_400mic$numpoints) +
                         2*sd(GOF_fitness_collapsed_by_pos_400mic$numpoints)))

print(GOF_fitness_collapsed_by_pos_2sigma_400mic)

Calculate all Data and Stats:

GOF_fitness_collapsed_all_400mic <- GOF_fitness_map_400mic %>%
  filter(position > 0) %>%
  group_by(position, aa) %>%
  summarise(fitval=median(fitness),
            numpoints=n(),
            stdfit=sd(fitness),
            numortho=length(unique(ID)))
`summarise()` has grouped output by 'position'. You can override using the `.groups` argument.
gof_aa_dim <- nrow(aminoacids)
gof_ref_len <- nrow(ecoli_map)
#these matrices have the fitness/num/sd for each aa at each position:
gof_matrix = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)
gof_matrix_num = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)
gof_matrix_sd = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)
gof_matrix_numortho = matrix(rep(NA, gof_ref_len*gof_aa_dim),nrow=gof_aa_dim,ncol=gof_ref_len)

#populate matrix
for (i in 1:nrow(GOF_fitness_collapsed_all_400mic)){
  
  gof_matrix[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all_400mic$aa[i])),GOF_fitness_collapsed_all_400mic$position[i]] <- as.numeric(GOF_fitness_collapsed_all_400mic$fitval[i])
  gof_matrix_num[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all_400mic$aa[i])),GOF_fitness_collapsed_all_400mic$position[i]] <- as.numeric(GOF_fitness_collapsed_all_400mic$numpoints[i])
  gof_matrix_sd[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all_400mic$aa[i])),GOF_fitness_collapsed_all_400mic$position[i]] <- as.numeric(GOF_fitness_collapsed_all_400mic$stdfit[i])
  gof_matrix_numortho[which(aminoacids$aa==as.character(GOF_fitness_collapsed_all_400mic$aa[i])),GOF_fitness_collapsed_all_400mic$position[i]] <- as.numeric(GOF_fitness_collapsed_all_400mic$numortho[i])
}

rownames(gof_matrix)<-aminoacids$aa
colnames(gof_matrix)<-c(1:gof_ref_len)
rownames(gof_matrix_num)<-aminoacids$aa
colnames(gof_matrix_num)<-c(1:gof_ref_len)
rownames(gof_matrix_sd)<-aminoacids$aa
colnames(gof_matrix_sd)<-c(1:gof_ref_len)
rownames(gof_matrix_numortho)<-aminoacids$aa
colnames(gof_matrix_numortho)<-c(1:gof_ref_len)

gof_matrix_melt <- melt(gof_matrix)
gof_matrix_num_melt <- melt(gof_matrix_num)
gof_matrix_sd_melt <- melt(gof_matrix_sd)
gof_matrix_numortho_melt <- melt(gof_matrix_numortho)

# Rename columns to "X1" and "X2"
names(gof_matrix_melt)[names(gof_matrix_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
names(gof_matrix_num_melt)[names(gof_matrix_num_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
names(gof_matrix_sd_melt)[names(gof_matrix_sd_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")
names(gof_matrix_numortho_melt)[names(gof_matrix_numortho_melt) %in% c("Var1", "Var2")] <- c("X1", "X2")

gof_matrix_melt_only_GOFpos <- gof_matrix_melt %>%
  filter(X2 == 35)

gof_matrix_melt_only_GOFpos$mutposnum <- 0
gof_matrix_melt_only_GOFpos$mutposnum[which(gof_matrix_melt_only_GOFpos$X2==35)] <- 1

gof_matrix_melt_only_GOFpos$aanum <- 0
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="A")] <- 12
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="C")] <- 10
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="D")] <- 5
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="E")] <- 4
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="F")] <- 19
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="G")] <- 11
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="H")] <- 3
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="I")] <- 15
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="K")] <- 1
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="L")] <- 14
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="M")] <- 16
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="N")] <- 6
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="P")] <- 17
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="Q")] <- 7
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="R")] <- 2
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="S")] <- 9
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="T")] <- 8
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="V")] <- 13
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="W")] <- 20
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="Y")] <- 18
gof_matrix_melt_only_GOFpos$aanum[which(gof_matrix_melt_only_GOFpos$X1=="X")] <- 21

gof_matrix_melt_only_GOFpos_wnum <- gof_matrix_melt_only_GOFpos %>%
  inner_join(gof_matrix_num_melt,by=c("X1","X2")) %>%
  dplyr::rename(mutnum=value.y,value=value.x)

GOF Position Plot

Plot the mean fitness of each GoF mutation at the significant positions, with the number of mutants observed at each AA:

# Define the order of amino acids for the rectangles
rect_order <- c("T")

# Create a data frame for the rectangles
rect_data <- data.frame(
aanum = match(rect_order, c("K","R","H","E","D","N","Q","T","S","C","G","A","V","L","I","M","P","Y","F","W","X")),
xmin = seq(0.5, by = 1, length.out = length(rect_order)),
xmax = seq(1.5, by = 1, length.out = length(rect_order)))

#plot the data from all mutants:
GOF_fit_nummut_plot_400mic <- ggplot(gof_matrix_melt_only_GOFpos_wnum, 
       aes(x=mutposnum, y=aanum,
           fill=value,
           label=mutnum)) +
  geom_tile() +
  geom_text() +
  # Add black rectangles
  geom_rect(data = rect_data,
           aes(xmin = xmin, xmax = xmax, ymin = aanum - 0.5, ymax = aanum + 0.5),
           fill = NA, color = "black", inherit.aes = FALSE) +
  labs(x = "Position (aa)",
       y ="Amino acid",color="") +
  scale_fill_gradient(low = "blue", 
                      high = "red",
                      name="Fitness",
                      na.value="grey",
                      limit = c(0, max(gof_matrix_melt_only_GOFpos_wnum$value))) +
  theme_minimal()+
  scale_x_continuous(name="Position (aa)",
                     breaks=c(1),
                     labels=c("35"))+
  scale_y_continuous(name="Amino acid", 
                     breaks=c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21),
                     labels=c("K","R","H","E","D","N","Q","T","S","C","G","A","V","L","I","M","P","Y","F","W","X"))

print(GOF_fit_nummut_plot_400mic)

Reproducibility

The session information is provided for full reproducibility.

devtools::session_info()
─ Session info ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.3.2 (2023-10-31)
 os       macOS 15.2
 system   aarch64, darwin20
 ui       RStudio
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       America/Los_Angeles
 date     2025-01-23
 rstudio  2024.09.0+375 Cranberry Hibiscus (desktop)
 pandoc   3.2 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64/ (via rmarkdown)

─ Packages ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 package          * version    date (UTC) lib source
 ade4               1.7-22     2023-02-06 [1] CRAN (R 4.3.0)
 ape              * 5.8        2024-04-11 [1] CRAN (R 4.3.1)
 aplot              0.2.2      2023-10-06 [1] CRAN (R 4.3.1)
 bio3d            * 2.4-5      2024-10-29 [1] CRAN (R 4.3.3)
 BiocGenerics     * 0.46.0     2023-06-04 [1] Bioconductor
 Biostrings       * 2.68.1     2023-05-21 [1] Bioconductor
 bitops             1.0-7      2021-04-24 [1] CRAN (R 4.3.0)
 cachem             1.0.8      2023-05-01 [1] CRAN (R 4.3.0)
 castor           * 1.8.0      2024-01-09 [1] CRAN (R 4.3.1)
 cli                3.6.2      2023-12-11 [1] CRAN (R 4.3.1)
 codetools          0.2-20     2024-03-31 [1] CRAN (R 4.3.1)
 colorspace         2.1-0      2023-01-23 [1] CRAN (R 4.3.0)
 cowplot          * 1.1.3      2024-01-22 [1] CRAN (R 4.3.1)
 crayon             1.5.2      2022-09-29 [1] CRAN (R 4.3.0)
 devtools         * 2.4.5      2022-10-11 [1] CRAN (R 4.3.0)
 digest             0.6.35     2024-03-11 [1] CRAN (R 4.3.1)
 dplyr            * 1.1.4      2023-11-17 [1] CRAN (R 4.3.1)
 ellipsis           0.3.2      2021-04-29 [1] CRAN (R 4.3.0)
 evaluate           0.23       2023-11-01 [1] CRAN (R 4.3.1)
 fansi              1.0.6      2023-12-08 [1] CRAN (R 4.3.1)
 farver             2.1.1      2022-07-06 [1] CRAN (R 4.3.0)
 fastmap            1.1.1      2023-02-24 [1] CRAN (R 4.3.0)
 foreach            1.5.2      2022-02-02 [1] CRAN (R 4.3.0)
 fs                 1.6.3      2023-07-20 [1] CRAN (R 4.3.0)
 generics           0.1.3      2022-07-05 [1] CRAN (R 4.3.0)
 GenomeInfoDb     * 1.36.4     2023-10-08 [1] Bioconductor
 GenomeInfoDbData   1.2.10     2023-09-13 [1] Bioconductor
 ggExtra          * 0.10.1     2023-08-21 [1] CRAN (R 4.3.0)
 ggfun              0.1.4      2024-01-19 [1] CRAN (R 4.3.1)
 ggnewscale       * 0.4.10     2024-02-08 [1] CRAN (R 4.3.1)
 ggplot2          * 3.5.1      2024-04-23 [1] CRAN (R 4.3.1)
 ggplotify          0.1.2      2023-08-09 [1] CRAN (R 4.3.0)
 ggridges         * 0.5.6      2024-01-23 [1] CRAN (R 4.3.1)
 ggtree           * 3.8.2      2023-07-30 [1] Bioconductor
 ggtreeExtra      * 1.10.0     2023-04-25 [1] Bioconductor
 glmnet           * 4.1-8      2023-08-22 [1] CRAN (R 4.3.0)
 glue               1.7.0      2024-01-09 [1] CRAN (R 4.3.1)
 gridExtra        * 2.3        2017-09-09 [1] CRAN (R 4.3.0)
 gridGraphics       0.5-1      2020-12-13 [1] CRAN (R 4.3.0)
 gtable             0.3.5      2024-04-22 [1] CRAN (R 4.3.1)
 htmltools          0.5.8.1    2024-04-04 [1] CRAN (R 4.3.1)
 htmlwidgets        1.6.4      2023-12-06 [1] CRAN (R 4.3.1)
 httpuv             1.6.15     2024-03-26 [1] CRAN (R 4.3.1)
 igraph           * 2.0.3      2024-03-13 [1] CRAN (R 4.3.1)
 IRanges          * 2.34.1     2023-07-02 [1] Bioconductor
 iterators          1.0.14     2022-02-05 [1] CRAN (R 4.3.0)
 jsonlite           1.8.8      2023-12-04 [1] CRAN (R 4.3.1)
 knitr            * 1.45       2023-10-30 [1] CRAN (R 4.3.1)
 labeling           0.4.3      2023-08-29 [1] CRAN (R 4.3.0)
 later              1.3.2      2023-12-06 [1] CRAN (R 4.3.1)
 lattice            0.22-6     2024-03-20 [1] CRAN (R 4.3.1)
 lazyeval           0.2.2      2019-03-15 [1] CRAN (R 4.3.0)
 lifecycle          1.0.4      2023-11-07 [1] CRAN (R 4.3.1)
 magrittr           2.0.3      2022-03-30 [1] CRAN (R 4.3.0)
 MASS               7.3-60.0.1 2024-01-13 [1] CRAN (R 4.3.1)
 Matrix           * 1.6-5      2024-01-11 [1] CRAN (R 4.3.1)
 matrixStats      * 1.3.0      2024-04-11 [1] CRAN (R 4.3.1)
 memoise            2.0.1      2021-11-26 [1] CRAN (R 4.3.0)
 mime               0.12       2021-09-28 [1] CRAN (R 4.3.0)
 miniUI             0.1.1.1    2018-05-18 [1] CRAN (R 4.3.0)
 munsell            0.5.1      2024-04-01 [1] CRAN (R 4.3.1)
 naturalsort        0.1.3      2016-08-30 [1] CRAN (R 4.3.0)
 nlme               3.1-164    2023-11-27 [1] CRAN (R 4.3.1)
 patchwork        * 1.2.0      2024-01-08 [1] CRAN (R 4.3.1)
 pheatmap         * 1.0.12     2019-01-04 [1] CRAN (R 4.3.0)
 pillar             1.9.0      2023-03-22 [1] CRAN (R 4.3.0)
 pkgbuild           1.4.4      2024-03-17 [1] CRAN (R 4.3.1)
 pkgconfig          2.0.3      2019-09-22 [1] CRAN (R 4.3.0)
 pkgload            1.3.4      2024-01-16 [1] CRAN (R 4.3.1)
 plyr               1.8.9      2023-10-02 [1] CRAN (R 4.3.1)
 png                0.1-8      2022-11-29 [1] CRAN (R 4.3.0)
 profvis            0.3.8      2023-05-02 [1] CRAN (R 4.3.0)
 promises           1.3.0      2024-04-05 [1] CRAN (R 4.3.1)
 pscl             * 1.5.9      2024-01-31 [1] CRAN (R 4.3.1)
 purrr            * 1.0.2      2023-08-10 [1] CRAN (R 4.3.0)
 R6                 2.5.1      2021-08-19 [1] CRAN (R 4.3.0)
 ragg               1.3.0      2024-03-13 [1] CRAN (R 4.3.1)
 RColorBrewer     * 1.1-3      2022-04-03 [1] CRAN (R 4.3.0)
 Rcpp             * 1.0.13     2024-07-17 [1] CRAN (R 4.3.3)
 RCurl              1.98-1.14  2024-01-09 [1] CRAN (R 4.3.1)
 remotes            2.5.0      2024-03-17 [1] CRAN (R 4.3.1)
 reshape          * 0.8.9      2022-04-12 [1] CRAN (R 4.3.0)
 reshape2         * 1.4.4      2020-04-09 [1] CRAN (R 4.3.0)
 reticulate       * 1.36.1     2024-04-22 [1] CRAN (R 4.3.1)
 rlang              1.1.3      2024-01-10 [1] CRAN (R 4.3.1)
 rmarkdown          2.26       2024-03-05 [1] CRAN (R 4.3.1)
 ROCR             * 1.0-11     2020-05-02 [1] CRAN (R 4.3.0)
 RSpectra           0.16-1     2022-04-24 [1] CRAN (R 4.3.0)
 rstudioapi         0.16.0     2024-03-24 [1] CRAN (R 4.3.1)
 S4Vectors        * 0.38.2     2023-09-24 [1] Bioconductor
 scales           * 1.3.0      2023-11-28 [1] CRAN (R 4.3.1)
 seqinr           * 4.2-36     2023-12-08 [1] CRAN (R 4.3.1)
 sessioninfo        1.2.2      2021-12-06 [1] CRAN (R 4.3.0)
 shape              1.4.6.1    2024-02-23 [1] CRAN (R 4.3.1)
 shiny              1.8.1.1    2024-04-02 [1] CRAN (R 4.3.1)
 stringi          * 1.8.3      2023-12-11 [1] CRAN (R 4.3.1)
 stringr          * 1.5.1      2023-11-14 [1] CRAN (R 4.3.1)
 survival           3.5-8      2024-02-14 [1] CRAN (R 4.3.1)
 systemfonts        1.0.6      2024-03-07 [1] CRAN (R 4.3.1)
 textshaping        0.3.7      2023-10-09 [1] CRAN (R 4.3.1)
 tibble             3.2.1      2023-03-20 [1] CRAN (R 4.3.0)
 tidyr            * 1.3.1      2024-01-24 [1] CRAN (R 4.3.1)
 tidyselect         1.2.1      2024-03-11 [1] CRAN (R 4.3.1)
 tidytree         * 0.4.6      2023-12-12 [1] CRAN (R 4.3.1)
 treeio             1.24.3     2023-07-30 [1] Bioconductor
 urlchecker         1.0.1      2021-11-30 [1] CRAN (R 4.3.0)
 usethis          * 2.2.3      2024-02-19 [1] CRAN (R 4.3.1)
 utf8               1.2.4      2023-10-22 [1] CRAN (R 4.3.1)
 vctrs              0.6.5      2023-12-01 [1] CRAN (R 4.3.1)
 viridis          * 0.6.5      2024-01-29 [1] CRAN (R 4.3.1)
 viridisLite      * 0.4.2      2023-05-02 [1] CRAN (R 4.3.0)
 withr              3.0.0      2024-01-16 [1] CRAN (R 4.3.1)
 xfun               0.43       2024-03-25 [1] CRAN (R 4.3.1)
 xtable             1.8-4      2019-04-21 [1] CRAN (R 4.3.0)
 XVector          * 0.40.0     2023-05-08 [1] Bioconductor
 yaml               2.3.8      2023-12-11 [1] CRAN (R 4.3.1)
 yulab.utils        0.1.4      2024-01-28 [1] CRAN (R 4.3.1)
 zlibbioc           1.46.0     2023-05-08 [1] Bioconductor

 [1] /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library

─ Python configuration ─────────────────────────────────────────────────────────────────────────────────────────────────────
 python:         /Users/krom/miniforge3/bin/python3
 libpython:      /Users/krom/miniforge3/lib/libpython3.10.dylib
 pythonhome:     /Users/krom/miniforge3:/Users/krom/miniforge3
 version:        3.10.14 | packaged by conda-forge | (main, Mar 20 2024, 12:51:49) [Clang 16.0.6 ]
 numpy:           [NOT FOUND]
 
 NOTE: Python version was forced by use_python() function

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
LS0tCnRpdGxlOiAiR2Fpbi1vZi1GdW5jdGlvbiBNdXRhbnRzIEFuYWx5c2lzIgphdXRob3I6ICdBdXRob3JzOiBbS2FybCBKLiBSb21hbm93aWN6XShodHRwczovL2tyb21hbm93aWN6LmdpdGh1Yi5pby8pLCBDYXJtZW4gUmVzbmljaywgU2FtdWVsIFIuIEhpbnRvbiwgQ2FsaW4gUGxlc2EnCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdGhlbWU6IHNwYWNlbGFiCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA1CiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogeWVzCiAgICAgIHNtb290aF9zY3JvbGw6IHllcwogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAnNScKICAgIGRmX3ByaW50OiBwYWdlZAogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6ICc1JwotLS0KCioqUiBOb3RlYm9vazoqKiA8Zm9udCBjb2xvcj0iZ3JlZW4iPlByb3ZpZGVzIHJlcHJvZHVjaWJsZSBhbmFseXNpcyBmb3IgKipHYWluLW9mLUZ1bmN0aW9uIE11dGFudHMqKiBpbiB0aGUgZm9sbG93aW5nIG1hbnVzY3JpcHQ6PC9mb250PgoKKipDaXRhdGlvbjoqKiBSb21hbm93aWN6IEtKLCBSZXNuaWNrIEMsIEhpbnRvbiBTUiwgUGxlc2EgQy4gRXhwbG9yaW5nIGFudGliaW90aWMgcmVzaXN0YW5jZSBpbiBkaXZlcnNlIGhvbW9sb2dzIG9mIHRoZSBkaWh5ZHJvZm9sYXRlIHJlZHVjdGFzZSBwcm90ZWluIGZhbWlseSB0aHJvdWdoIGJyb2FkIG11dGF0aW9uYWwgc2Nhbm5pbmcuICoqKmJpb1J4aXYqKiosIDIwMjUuIFtdKCkKCioqR2l0SHViIFJlcG9zaXRvcnk6KiogW2h0dHBzOi8vZ2l0aHViLmNvbS9QbGVzYUxhYi9ESEZSXShodHRwczovL2dpdGh1Yi5jb20vUGxlc2FMYWIvREhGUikKCioqTkNCSSBCaW9Qcm9qZWN0OioqIFtodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L2Jpb3Byb2plY3QvMTE4OTQ3OF0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9iaW9wcm9qZWN0LzExODk0NzgpCgojIEV4cGVyaW1lbnQKClRoaXMgcGlwZWxpbmUgcHJvY2Vzc2VzIGEgbGlicmFyeSBvZiAxLDUzNiBESEZSIGhvbW9sb2dzIGFuZCB0aGVpciBhc3NvY2lhdGVkIG11dGFudHMsIHdpdGggdHdvLWZvbGQgcmVkdW5kYW5jeSAodHdvIGNvZG9uIHZhcmlhbnRzIHBlciBzZXF1ZW5jZSkuIEZpdG5lc3Mgc2NvcmVzIGFyZSBkZXJpdmVkIGZyb20gYSBtdWx0aXBsZXhlZCBpbi12aXZvIGFzc2F5IHVzaW5nIGEgdHJpbWV0aG9wcmltIGNvbmNlbnRyYXRpb24gZ3JhZGllbnQsIGFzc2Vzc2luZyB0aGUgYWJpbGl0eSBvZiB0aGVzZSBob21vbG9ncyBhbmQgdGhlaXIgbXV0YW50cyB0byBjb21wbGVtZW50IGZ1bmN0aW9uYWxpdHkgaW4gYW4gKkUuIGNvbGkqIGtub2Nrb3V0IHN0cmFpbiBhbmQgdGhlaXIgdG9sZXJhbmNlIHRvIHRyaW1ldGhvcHJpbSB0cmVhdG1lbnQuIFRoaXMgYW5hbHlzaXMgcHJvdmlkZXMgaW5zaWdodHMgaW50byBob3cgYW50aWJpb3RpYyByZXNpc3RhbmNlIGV2b2x2ZXMgYWNyb3NzIGEgcmFuZ2Ugb2YgZXZvbHV0aW9uYXJ5IHN0YXJ0aW5nIHBvaW50cy4gU2VxdWVuY2UgZGF0YSB3ZXJlIGdlbmVyYXRlZCB1c2luZyB0aGUgSWxsdW1pbmEgTm92YVNlcSBwbGF0Zm9ybSB3aXRoIDEwMCBicCBwYWlyZWQtZW5kIHNlcXVlbmNpbmcgb2YgYW1wbGljb25zLgoKIVtNZXRob2RzIG92ZXJ2aWV3IHRvIGFjaGlldmUgYSBicm9hZC1tdXRhdGlvbmFsIHNjYW4gZm9yIERIRlIgaG9tb2xvZ3MuXShJbWFnZXMvREhGUi5EaWFncmFtLnBuZykKCmBgYHtjc3N9Ci5iYWRDb2RlIHsKYmFja2dyb3VuZC1jb2xvcjogbGlnaHRwaW5rOwpmb250LXdlaWdodDogYm9sZDsKfQoKLmdvb2RDb2RlIHsKYmFja2dyb3VuZC1jb2xvcjogbGlnaHRncmVlbjsKZm9udC13ZWlnaHQ6IGJvbGQ7Cn0KCi5zaGFyZWRDb2RlIHsKYmFja2dyb3VuZC1jb2xvcjogbGlnaHRibHVlOwpmb250LXdlaWdodDogYm9sZDsKfQoKdGFibGUgewogIG1hcmdpbjogYXV0bzsKICBib3JkZXItdG9wOiAxcHggc29saWQgIzY2NjsKICBib3JkZXItYm90dG9tOiAxcHggc29saWQgIzY2NjsKfQp0YWJsZSB0aGVhZCB0aCB7IGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjZGRkOyB9CnRoLCB0ZCB7IHBhZGRpbmc6IDVweDsgfQp0aGVhZCwgdGZvb3QsIHRyOm50aC1jaGlsZChldmVuKSB7IGJhY2tncm91bmQ6ICNlZWU7IH0KYGBgCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyBTZXQgZ2xvYmFsIG9wdGlvbnMgZm9yIG5vdGVib29rCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBUUlVFLCBtZXNzYWdlID0gVFJVRSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBjbGFzcy5zb3VyY2UgPSAiYmctc3VjY2VzcyIpCgojIEdldHRpbmcgdGhlIHBhdGggb2YgeW91ciBjdXJyZW50IG9wZW4gZmlsZSBhbmQgc2V0IGFzIHdkCmN1cnJlbnRfcGF0aCA9IHJzdHVkaW9hcGk6OmdldEFjdGl2ZURvY3VtZW50Q29udGV4dCgpJHBhdGggCnNldHdkKGRpcm5hbWUoY3VycmVudF9wYXRoKSkKcHJpbnQoZ2V0d2QoKSkKYGBgCgojIFBhY2thZ2VzClRoZSBmb2xsb3dpbmcgUiBwYWNrYWdlcyBtdXN0IGJlIGluc3RhbGxlZCBwcmlvciB0byBsb2FkaW5nIGludG8gdGhlIFIgc2Vzc2lvbi4gU2VlIHRoZSAqKlJlcHJvZHVjaWJpbGl0eSoqIHRhYiBmb3IgYSBjb21wbGV0ZSBsaXN0IG9mIHBhY2thZ2VzIGFuZCB0aGVpciB2ZXJzaW9ucyB1c2VkIGluIHRoaXMgd29ya2Zsb3cuCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQojIExvYWQgdGhlIGxhdGVzdCB2ZXJzaW9uIG9mIHB5dGhvbiAoMy4xMC4xNCkgZm9yIGRvd25zdHJlYW0gdXNlOgpsaWJyYXJ5KHJldGljdWxhdGUpCnVzZV9weXRob24oIi9Vc2Vycy9rcm9tL21pbmlmb3JnZTMvYmluL3B5dGhvbjMiKQoKIyBNYWtlIGEgdmVjdG9yIG9mIHJlcXVpcmVkIHBhY2thZ2VzCnJlcXVpcmVkLnBhY2thZ2VzIDwtIGMoImFwZSIsICJiaW8zZCIsICJCaW9zdHJpbmdzIiwgImNhc3RvciIsICJjb3dwbG90IiwgImRldnRvb2xzIiwgImRwbHlyIiwgImdnRXh0cmEiLCAiZ2duZXdzY2FsZSIsICJnZ3Bsb3QyIiwgImdncmlkZ2VzIiwgImdndHJlZSIsICJnZ3RyZWVFeHRyYSIsICJnbG1uZXQiLCAiZ3JpZEV4dHJhIiwiaWdyYXBoIiwgImtuaXRyIiwgIm1hdHJpeFN0YXRzIiwgInBhdGNod29yayIsICJwaGVhdG1hcCIsICJwdXJyciIsICJwc2NsIiwgIlJDb2xvckJyZXdlciIsICJyZXNoYXBlIiwicmVzaGFwZTIiLCAiUk9DUiIsICJzZXFpbnIiLCAic2NhbGVzIiwgInN0cmluZ3IiLCAic3RyaW5naSIsICJ0aWR5ciIsICJ0aWR5dHJlZSIsICJ2aXJpZGlzIikKCiMgTG9hZCByZXF1aXJlZCBwYWNrYWdlcyB3aXRoIGVycm9yIGhhbmRsaW5nCmxvYWRlZC5wYWNrYWdlcyA8LSBsYXBwbHkocmVxdWlyZWQucGFja2FnZXMsIGZ1bmN0aW9uKHBhY2thZ2UpIHsKICBpZiAoIXJlcXVpcmUocGFja2FnZSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkgewogICAgaW5zdGFsbC5wYWNrYWdlcyhwYWNrYWdlLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQogICAgaWYgKCFyZXF1aXJlKHBhY2thZ2UsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkpIHsKICAgICAgbWVzc2FnZSgiUGFja2FnZSAiLCBwYWNrYWdlLCAiIGNvdWxkIG5vdCBiZSBpbnN0YWxsZWQgYW5kIGxvYWRlZC4iKQogICAgICByZXR1cm4oTlVMTCkKICAgIH0KICB9CiAgcmV0dXJuKHBhY2thZ2UpCn0pCgojIFJlbW92ZSBOVUxMIGVudHJpZXMgZnJvbSBsb2FkZWQgcGFja2FnZXMKbG9hZGVkLnBhY2thZ2VzIDwtIGxvYWRlZC5wYWNrYWdlc1shc2FwcGx5KGxvYWRlZC5wYWNrYWdlcywgaXMubnVsbCldCmBgYAoKYGBge3IgY2xhc3Mub3V0cHV0PSJzaGFyZWRDb2RlIiwgZWNobz1GQUxTRX0KIyBQcmludCBsb2FkZWQgcGFja2FnZXMKY2F0KCJMb2FkZWQgcGFja2FnZXM6IiwgcGFzdGUobG9hZGVkLnBhY2thZ2VzLCBjb2xsYXBzZSA9ICIsICIpLCAiXG4iKQpgYGAKCmBgYHtyIGluY2x1ZGU9RkFMU0V9CiMgc2V0LnNlZWQgaXMgdXNlZCB0byBmaXggdGhlIHJhbmRvbSBudW1iZXIgZ2VuZXJhdGlvbiB0byBtYWtlIHRoZSByZXN1bHRzIHJlcGVhdGFibGUKc2V0LnNlZWQoMTIzKQpgYGAKCiMgSW1wb3J0IERhdGEgRmlsZXMKCkltcG9ydCAqKk1VVEFOVFMqKiBmaWxlcyBnZW5lcmF0ZWQgZnJvbSBbREhGUi40Lk11dGFudHMuUk1EXShodHRwczovL2dpdGh1Yi5jb20vUGxlc2FMYWIvREhGUikgcmVsZXZhbnQgZm9yIGRvd25zdHJlYW0gYW5hbHlzaXMuCmBgYHtyfQojIG11dF9jb2xsYXBzZV8xNQptdXRfY29sbGFwc2VfMTUgPC0gcmVhZC5jc3YoIk11dGFudHMvbXV0YW50c19maWxlc19mb3JtYXR0ZWQvbXV0X2NvbGxhcHNlXzE1LmNzdiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVFJVRSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpgYGAKCkltcG9ydCAqKkJNUyoqIGZpbGVzIGdlbmVyYXRlZCBmcm9tIFtESEZSLjUuQk1TLlJNRF0oaHR0cHM6Ly9naXRodWIuY29tL1BsZXNhTGFiL0RIRlIpIHJlbGV2YW50IGZvciBkb3duc3RyZWFtIGFuYWx5c2lzLgpgYGB7cn0KIyBwcm90ZWluX2luZm9fMUgxVApwcm90ZWluX2luZm9fMUgxVCA8LSByZWFkLmNzdigiQk1TL2Jtc19maWxlc19mb3JtYXR0ZWQvcHJvdGVpbl9pbmZvXzFIMVQuY3N2IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBUUlVFLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgojIEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9tZWx0IDwtIHJlYWQuY3N2KCJCTVMvYm1zX2ZpbGVzX2Zvcm1hdHRlZC9CTVNfbWF0cml4MTVfcGVyZmVjdHNfYW5kXzFfbWVsdC5jc3YiLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFRSVUUsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKCiMgQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX251bV9tZWx0CkJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9udW1fbWVsdCA8LSByZWFkLmNzdigiQk1TL2Jtc19maWxlc19mb3JtYXR0ZWQvQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX251bV9tZWx0LmNzdiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVFJVRSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpgYGAKCiMgR09GIE11dGFudHMgQW5hbHlzaXMKCjxmb250IGNvbG9yPSJibHVlIj4qKlRoaXMgc2VjdGlvbiBpcyBiYXNlZCBvbiB0aGUgUiBmaWxlOiAiUl9kcm9wb3V0X0dPRi5SIi4qKjwvZm9udD4gSXQgZGVzY3JpYmVzIGhvdyB0byBkZXRlcm1pbmUgaWYgY2VydGFpbiBtdXRhbnQgdmVyc2lvbnMgb2YgYSBkZXNpZ25lZCBob21vbG9nIGluY3JlYXNlIGluIGZpdG5lc3MgdW5kZXIgdHJpbWV0aG9wcmltIHNlbGVjdGlvbiAoaS5lLiwgZ2FpbiBvZiBmdW5jdGlvbiBhZnRlciBtdXRhdGlvbikuCgojIyBDb21wbGVtZW50YXRpb24KClN0YXJ0IHdpdGggdHdvIGhpc3RvZ3JhbXMgc2hvd2luZyAoMSkgdGhlIGRpc3RyaWJ1dGlvbiBvZiBwZXJmZWN0cyAoY291bnRzOyB5LWF4aXMpIGJ5IGZpdG5lc3MgKHgtYXhpcykgd2l0aCBmaXRuZXNzIDwgLTEgY29sb3JlZCBibHVlIGFuZCBmaXRuZXNzID4gLTEgY29sb3JlZCBnb2xkLCBhbmQgKDIpIHRoZSBkaXN0cmlidXRpb24gb2YgbXV0YW50cyAoY291bnRzOyB5LWF4aXMpIGJ5IGZpdG5lc3MgKHgtYXhpcykgd2l0aCBmaXRuZXNzIDwgLTEgY29sb3JlZCBibHVlIGFuZCBmaXRuZXNzID4gLTEgY29sb3JlZCBnb2xkCgoqKlBlcmZlY3RzOioqIFNtb290aCBIaXN0b2dyYW0KYGBge3J9CiMgU3Vic2V0IG11dGFudHMgKG11dGF0aW9ucyAhPSAwKQpMMTVfcGVyZmVjdHNfY29tcGxlbWVudGF0aW9uIDwtIG11dF9jb2xsYXBzZV8xNSAlPiUKICBmaWx0ZXIobXV0YXRpb25zID09IDApCgojIFJlbW92ZSBOQSBhbmQgaW5maW5pdGUgdmFsdWVzIGZvciB4LWF4aXMgc2NhbGluZwpmaXREMDVEMDNfcGVyZl9jbGVhbiA8LSBMMTVfcGVyZmVjdHNfY29tcGxlbWVudGF0aW9uJGZpdEQwNUQwM1tpcy5maW5pdGUoTDE1X3BlcmZlY3RzX2NvbXBsZW1lbnRhdGlvbiRmaXREMDVEMDMpXQoKIyBDYWxjdWxhdGUgdGhlIHJhbmdlIG9mIHRoZSBkYXRhCnhfbWluX3BlcmYgPC0gZmxvb3IobWluKGZpdEQwNUQwM19wZXJmX2NsZWFuLCBuYS5ybSA9IFRSVUUpKQp4X21heF9wZXJmIDwtIGNlaWxpbmcobWF4KGZpdEQwNUQwM19wZXJmX2NsZWFuLCBuYS5ybSA9IFRSVUUpKQoKIyBQbG90IHNtb290aCBkZW5zaXR5IGN1cnZlCkwxNV9wZXJmZWN0c19jb21wbGVtZW50YXRpb25fZGVuc2l0eSA8LSBnZ3Bsb3QoTDE1X3BlcmZlY3RzX2NvbXBsZW1lbnRhdGlvbiwgYWVzKHggPSBmaXREMDVEMDMpKSArCiAgZ2VvbV9kZW5zaXR5KGFlcyh5ID0gYWZ0ZXJfc3RhdChkZW5zaXR5ICogMTAwKSwgCiAgICAgICAgICAgICAgICAgICBmaWxsID0gaWZlbHNlKGZpdEQwNUQwMyA8PSAtMSwgImRhcmtibHVlIiwgImdvbGQiKSksIAogICAgICAgICAgICAgICBhbHBoYSA9IDAuNzUpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAtMSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmxhY2siKSArCiAgc2NhbGVfZmlsbF9pZGVudGl0eSgpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKHhfbWluX3BlcmYsIHhfbWF4X3BlcmYsIGJ5ID0gMSkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gZnVuY3Rpb24oeCkgcGFzdGUwKHgsICIlIikpICsKICBsYWJzKHggPSAiTWVkaWFuIEZpdG5lc3MgKExvZ0ZDKSIsIHkgPSAiUGVyY2VudGFnZSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAnYmxhY2snLCBzaXplID0gMS4wKSwgCiAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiYmxhY2siLCBzaXplID0gMS4wKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCiMgRGlzcGxheSBwbG90CnByaW50KEwxNV9wZXJmZWN0c19jb21wbGVtZW50YXRpb25fZGVuc2l0eSkKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQojU2F2aW5nIDYgeCA1IGluIGltYWdlcwpnZ3NhdmUoZmlsZT0iR09GL1BMT1RTL0xpYjE1X0NvbXBsZW1lbnRhdGlvbl9QZXJmZWN0c19sb3dfaGlnaF9maXRuZXNzX2RlbnNpdHkucG5nIiwgcGxvdD1MMTVfcGVyZmVjdHNfY29tcGxlbWVudGF0aW9uX2RlbnNpdHksCiAgICAgICB3aWR0aD02LCBoZWlnaHQ9NSwgdW5pdHM9ImluIikKYGBgCgpgYGB7cn0KIyBTdWJzZXQgbXV0YW50cyAobXV0YXRpb25zICE9IDApCkwxNV9wZXJmZWN0c19jb21wbGVtZW50YXRpb24gPC0gbXV0X2NvbGxhcHNlXzE1ICU+JQogIGZpbHRlcihtdXRhdGlvbnMgPT0gMCkKCiMgUmVtb3ZlIE5BIGFuZCBpbmZpbml0ZSB2YWx1ZXMgZm9yIHgtYXhpcyBzY2FsaW5nCmZpdEQwNUQwM19wZXJmX2NsZWFuIDwtIEwxNV9wZXJmZWN0c19jb21wbGVtZW50YXRpb24kZml0RDA1RDAzW2lzLmZpbml0ZShMMTVfcGVyZmVjdHNfY29tcGxlbWVudGF0aW9uJGZpdEQwNUQwMyldCgojIENhbGN1bGF0ZSB0aGUgcmFuZ2Ugb2YgdGhlIGRhdGEKeF9taW5fcGVyZiA8LSBmbG9vcihtaW4oZml0RDA1RDAzX3BlcmZfY2xlYW4sIG5hLnJtID0gVFJVRSkpCnhfbWF4X3BlcmYgPC0gY2VpbGluZyhtYXgoZml0RDA1RDAzX3BlcmZfY2xlYW4sIG5hLnJtID0gVFJVRSkpCgojIENyZWF0ZSBkZW5zaXR5IGRhdGEKZGVuc2l0eV9kYXRhIDwtIGRlbnNpdHkoTDE1X3BlcmZlY3RzX2NvbXBsZW1lbnRhdGlvbiRmaXREMDVEMDMsIG5hLnJtID0gVFJVRSkKCiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSBmcm9tIHRoZSBkZW5zaXR5IGRhdGEKZGZfZGVuc2l0eSA8LSBkYXRhLmZyYW1lKHggPSBkZW5zaXR5X2RhdGEkeCwgeSA9IGRlbnNpdHlfZGF0YSR5KQoKIyBTcGxpdCB0aGUgZGF0YSBhdCB4ID0gLTEKZGZfbGVmdCA8LSBkZl9kZW5zaXR5W2RmX2RlbnNpdHkkeCA8PSAtMSwgXQpkZl9yaWdodCA8LSBkZl9kZW5zaXR5W2RmX2RlbnNpdHkkeCA+PSAtMSwgXQoKIyBFbnN1cmUgdGhlIHNwbGl0IHBvaW50IGlzIGluY2x1ZGVkIGluIGJvdGggZGF0YXNldHMKZGZfbGVmdCA8LSByYmluZChkZl9sZWZ0LCBkYXRhLmZyYW1lKHggPSAtMSwgeSA9IGRmX2xlZnQkeVtucm93KGRmX2xlZnQpXSkpCmRmX3JpZ2h0IDwtIHJiaW5kKGRhdGEuZnJhbWUoeCA9IC0xLCB5ID0gZGZfcmlnaHQkeVsxXSksIGRmX3JpZ2h0KQoKIyBQbG90IHVzaW5nIGdlb21fYXJlYQpMMTVfcGVyZmVjdHNfY29tcGxlbWVudGF0aW9uX2RlbnNpdHkgPC0gZ2dwbG90KCkgKwogIGdlb21fYXJlYShkYXRhID0gZGZfbGVmdCwgYWVzKHggPSB4LCB5ID0geSAqIDEwMCksIGZpbGwgPSAiZGFya2JsdWUiLCBhbHBoYSA9IDAuNzUpICsKICBnZW9tX2FyZWEoZGF0YSA9IGRmX3JpZ2h0LCBhZXMoeCA9IHgsIHkgPSB5ICogMTAwKSwgZmlsbCA9ICJnb2xkIiwgYWxwaGEgPSAwLjc1KSArCiAgZ2VvbV9saW5lKGRhdGEgPSBkZl9kZW5zaXR5LCBhZXMoeCA9IHgsIHkgPSB5ICogMTAwKSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMC41KSArICAjIEFkZCBibGFjayBvdXRsaW5lCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gLTEsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImJsYWNrIikgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoeF9taW5fcGVyZiwgeF9tYXhfcGVyZiwgYnkgPSAxKSkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBmdW5jdGlvbih4KSBwYXN0ZTAoeCwgIiUiKSwgCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgbWF4KGRmX2RlbnNpdHkkeSkgKiAxMDApKSArICAjIFNldCB5LWF4aXMgbGltaXRzCiAgbGFicyh4ID0gIk1lZGlhbiBGaXRuZXNzIChMb2dGQykiLCB5ID0gIlBlcmNlbnRhZ2UiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gJ2JsYWNrJywgc2l6ZSA9IDEuMCksIAogICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiwgc2l6ZSA9IDEuMCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgojIERpc3BsYXkgcGxvdApwcmludChMMTVfcGVyZmVjdHNfY29tcGxlbWVudGF0aW9uX2RlbnNpdHkpCmBgYAoKYGBge3IgZWNobz1GQUxTRX0KI1NhdmluZyA2IHggNSBpbiBpbWFnZXMKZ2dzYXZlKGZpbGU9IkdPRi9QTE9UUy9MaWIxNV9Db21wbGVtZW50YXRpb25fTXV0YW50c19sb3dfaGlnaF9maXRuZXNzX2RlbnNpdHkudjIucG5nIiwgcGxvdD1MMTVfcGVyZmVjdHNfY29tcGxlbWVudGF0aW9uX2RlbnNpdHksCiAgICAgICB3aWR0aD02LCBoZWlnaHQ9NSwgdW5pdHM9ImluIikKYGBgCgoKKipQZXJjZW50IG9mIFBlcmZlY3RzIHdpdGggU3Ryb25nIERlcGxldGlvbiAoZml0bmVzcyA8IC0yLjUpKioKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CiMgQ2FsY3VsYXRlIHRoZSBwZXJjZW50YWdlcyBhbmQgY291bnRzCnJlc3VsdHMgPC0gTDE1X3BlcmZlY3RzX2NvbXBsZW1lbnRhdGlvbiAlPiUKICBmaWx0ZXIoIWlzLm5hKGZpdEQwNUQwMykpICU+JSAgIyBSZW1vdmUgcm93cyB3aGVyZSBmaXREMDVEMDMgaXMgTkEKICBzdW1tYXJpc2UoCiAgICB0b3RhbF91bmlxdWVfSURzID0gbl9kaXN0aW5jdChJRCksCiAgICBJRHNfYmVsb3dfbmVnMl81ID0gbl9kaXN0aW5jdChJRFtmaXREMDVEMDMgPCAtMi41XSksCiAgICBwZXJjZW50YWdlX2JlbG93X25lZzJfNSA9IChJRHNfYmVsb3dfbmVnMl81IC8gdG90YWxfdW5pcXVlX0lEcykgKiAxMDAsCiAgICBJRHNfYmVsb3dfbmVnMSA9IG5fZGlzdGluY3QoSURbZml0RDA1RDAzIDwgLTFdKSwKICAgIElEc19hYm92ZV9vcl9lcXVhbF9uZWcxID0gbl9kaXN0aW5jdChJRFtmaXREMDVEMDMgPj0gLTFdKSwKICAgIHBlcmNlbnRhZ2VfYmVsb3dfbmVnMSA9IChJRHNfYmVsb3dfbmVnMSAvIHRvdGFsX3VuaXF1ZV9JRHMpICogMTAwLAogICAgcGVyY2VudGFnZV9hYm92ZV9vcl9lcXVhbF9uZWcxID0gKElEc19hYm92ZV9vcl9lcXVhbF9uZWcxIC8gdG90YWxfdW5pcXVlX0lEcykgKiAxMDAKICApCgojIFByaW50IHRoZSByZXN1bHRzCnByaW50KHBhc3RlMCgiUGVyY2VudGFnZSBvZiB1bmlxdWUgSURzIHdpdGggZml0RDA1RDAzIDwgLTIuNTogIiwgCiAgICAgICAgICAgICByb3VuZChyZXN1bHRzJHBlcmNlbnRhZ2VfYmVsb3dfbmVnMl81LCAyKSwgIiUiKSkKcHJpbnQocGFzdGUoIlRvdGFsIHVuaXF1ZSBJRHM6IiwgcmVzdWx0cyR0b3RhbF91bmlxdWVfSURzKSkKcHJpbnQocGFzdGUoIlVuaXF1ZSBJRHMgd2l0aCBmaXREMDVEMDMgPCAtMi41OiIsIHJlc3VsdHMkSURzX2JlbG93X25lZzJfNSkpCgpwcmludCgiXG5BZGRpdGlvbmFsIGZpdG5lc3MgY2F0ZWdvcmllczoiKQpwcmludChwYXN0ZSgiVW5pcXVlIElEcyB3aXRoIGZpdEQwNUQwMyA8IC0xOiIsIHJlc3VsdHMkSURzX2JlbG93X25lZzEpKQpwcmludChwYXN0ZTAoIlBlcmNlbnRhZ2U6ICIsIHJvdW5kKHJlc3VsdHMkcGVyY2VudGFnZV9iZWxvd19uZWcxLCAyKSwgIiUiKSkKCnByaW50KHBhc3RlKCJVbmlxdWUgSURzIHdpdGggZml0RDA1RDAzID49IC0xOiIsIHJlc3VsdHMkSURzX2Fib3ZlX29yX2VxdWFsX25lZzEpKQpwcmludChwYXN0ZTAoIlBlcmNlbnRhZ2U6ICIsIHJvdW5kKHJlc3VsdHMkcGVyY2VudGFnZV9hYm92ZV9vcl9lcXVhbF9uZWcxLCAyKSwgIiUiKSkKYGBgCgoqKk11dGFudHM6KiogU21vb3RoIEhpc3RvZ3JhbQpgYGB7cn0KIyBTdWJzZXQgbXV0YW50cyAobXV0YXRpb25zICE9IDApCkwxNV9tdXRhbnRzX2NvbXBsZW1lbnRhdGlvbiA8LSBtdXRfY29sbGFwc2VfMTUgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA+IDAgJiBtdXRhdGlvbnMgPCA2KQoKIyBSZW1vdmUgTkEgYW5kIGluZmluaXRlIHZhbHVlcyBmb3IgeC1heGlzIHNjYWxpbmcKZml0RDA1RDAzX211dF9jbGVhbiA8LSBMMTVfbXV0YW50c19jb21wbGVtZW50YXRpb24kZml0RDA1RDAzW2lzLmZpbml0ZShMMTVfbXV0YW50c19jb21wbGVtZW50YXRpb24kZml0RDA1RDAzKV0KCiMgQ2FsY3VsYXRlIHRoZSByYW5nZSBvZiB0aGUgZGF0YQp4X21pbl9tdXQgPC0gZmxvb3IobWluKGZpdEQwNUQwM19tdXRfY2xlYW4sIG5hLnJtID0gVFJVRSkpCnhfbWF4X211dCA8LSBjZWlsaW5nKG1heChmaXREMDVEMDNfbXV0X2NsZWFuLCBuYS5ybSA9IFRSVUUpKQoKIyBQbG90IHNtb290aCBkZW5zaXR5IGN1cnZlCkwxNV9tdXRhbnRzX2NvbXBsZW1lbnRhdGlvbl9kZW5zaXR5IDwtIGdncGxvdChMMTVfbXV0YW50c19jb21wbGVtZW50YXRpb24sIGFlcyh4ID0gZml0RDA1RDAzKSkgKwogIGdlb21fZGVuc2l0eShhZXMoeSA9IGFmdGVyX3N0YXQoZGVuc2l0eSAqIDEwMCksIAogICAgICAgICAgICAgICAgICAgZmlsbCA9IGlmZWxzZShmaXREMDVEMDMgPD0gLTEsICJkYXJrYmx1ZSIsICJnb2xkIikpLCAKICAgICAgICAgICAgICAgYWxwaGEgPSAwLjc1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gLTEsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImJsYWNrIikgKwogIHNjYWxlX2ZpbGxfaWRlbnRpdHkoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSh4X21pbl9tdXQsIHhfbWF4X211dCwgYnkgPSAxKSkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBmdW5jdGlvbih4KSBwYXN0ZTAoeCwgIiUiKSkgKwogIGxhYnMoeCA9ICJNZWRpYW4gRml0bmVzcyAoTG9nRkMpIiwgeSA9ICJQZXJjZW50YWdlIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICdibGFjaycsIHNpemUgPSAxLjApLCAKICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAxLjApLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKIyBEaXNwbGF5IHBsb3QKcHJpbnQoTDE1X211dGFudHNfY29tcGxlbWVudGF0aW9uX2RlbnNpdHkpCmBgYAoKYGBge3IgZWNobz1GQUxTRX0KI1NhdmluZyA2IHggNSBpbiBpbWFnZXMKZ2dzYXZlKGZpbGU9IkdPRi9QTE9UUy9MaWIxNV9Db21wbGVtZW50YXRpb25fTXV0YW50c19sb3dfaGlnaF9maXRuZXNzX2RlbnNpdHkucG5nIiwgcGxvdD1MMTVfbXV0YW50c19jb21wbGVtZW50YXRpb25fZGVuc2l0eSwKICAgICAgIHdpZHRoPTUsIGhlaWdodD01LCB1bml0cz0iaW4iKQpgYGAKCioqUGVyZmVjdHMgJiBNdXRhbnRzOioqIFNtb290aCBIaXN0b2dyYW0KYGBge3J9CiMgU3Vic2V0IG11dGFudHMgKG11dGF0aW9ucyAhPSAwKQpMMTVfcGVyZmVjdHNfbXV0YW50c19jb21wbGVtZW50YXRpb24gPC0gbXV0X2NvbGxhcHNlXzE1ICU+JQogIGZpbHRlcihtdXRhdGlvbnMgPj0gMCAmIG11dGF0aW9ucyA8IDYpCgojIFJlbW92ZSBOQSBhbmQgaW5maW5pdGUgdmFsdWVzIGZvciB4LWF4aXMgc2NhbGluZwpmaXREMDVEMDNfcGVyZl9tdXRfY2xlYW4gPC0gTDE1X3BlcmZlY3RzX211dGFudHNfY29tcGxlbWVudGF0aW9uJGZpdEQwNUQwM1tpcy5maW5pdGUoTDE1X3BlcmZlY3RzX211dGFudHNfY29tcGxlbWVudGF0aW9uJGZpdEQwNUQwMyldCgojIENhbGN1bGF0ZSB0aGUgcmFuZ2Ugb2YgdGhlIGRhdGEKeF9taW5fcGVyZl9tdXQgPC0gZmxvb3IobWluKGZpdEQwNUQwM19wZXJmX211dF9jbGVhbiwgbmEucm0gPSBUUlVFKSkKeF9tYXhfcGVyZl9tdXQgPC0gY2VpbGluZyhtYXgoZml0RDA1RDAzX3BlcmZfbXV0X2NsZWFuLCBuYS5ybSA9IFRSVUUpKQoKIyBQbG90IHNtb290aCBkZW5zaXR5IGN1cnZlCkwxNV9wZXJmZWN0c19tdXRhbnRzX2NvbXBsZW1lbnRhdGlvbl9kZW5zaXR5IDwtIGdncGxvdChMMTVfcGVyZmVjdHNfbXV0YW50c19jb21wbGVtZW50YXRpb24sIGFlcyh4ID0gZml0RDA1RDAzKSkgKwogIGdlb21fZGVuc2l0eShhZXMoeSA9IGFmdGVyX3N0YXQoZGVuc2l0eSAqIDEwMCksIAogICAgICAgICAgICAgICAgICAgZmlsbCA9IGlmZWxzZShmaXREMDVEMDMgPD0gLTEsICJkYXJrYmx1ZSIsICJnb2xkIikpLCAKICAgICAgICAgICAgICAgYWxwaGEgPSAwLjc1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gLTEsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImJsYWNrIikgKwogIHNjYWxlX2ZpbGxfaWRlbnRpdHkoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSh4X21pbl9wZXJmX211dCwgeF9tYXhfcGVyZl9tdXQsIGJ5ID0gMSkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gZnVuY3Rpb24oeCkgcGFzdGUwKHgsICIlIikpICsKICBsYWJzKHggPSAiTWVkaWFuIEZpdG5lc3MgKExvZ0ZDKSIsIHkgPSAiUGVyY2VudGFnZSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAnYmxhY2snLCBzaXplID0gMS4wKSwgCiAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiYmxhY2siLCBzaXplID0gMS4wKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCiMgRGlzcGxheSBwbG90CnByaW50KEwxNV9wZXJmZWN0c19tdXRhbnRzX2NvbXBsZW1lbnRhdGlvbl9kZW5zaXR5KQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9CiNTYXZpbmcgNiB4IDUgaW4gaW1hZ2VzCmdnc2F2ZShmaWxlPSJHT0YvUExPVFMvTGliMTVfQ29tcGxlbWVudGF0aW9uX1BlcmZlY3RzX011dGFudHNfbG93X2hpZ2hfZml0bmVzc19kZW5zaXR5LnBuZyIsIHBsb3Q9TDE1X3BlcmZlY3RzX211dGFudHNfY29tcGxlbWVudGF0aW9uX2RlbnNpdHksCiAgICAgICB3aWR0aD01LCBoZWlnaHQ9NSwgdW5pdHM9ImluIikKYGBgCgoqKk11dGFudCBDb3VudHMqCmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIFN0ZXAgMTogSWRlbnRpZnkgSURzIHRoYXQgbWVldCB0aGUgY3JpdGVyaWEgKG11dGF0aW9ucyA9PSAwIGFuZCBmaXREMDVEMDMgPj0gLTEpCnZhbGlkX0lEcyA8LSBtdXRfY29sbGFwc2VfMTUgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAwICYgZml0RDA1RDAzID49IC0xKSAlPiUKICBwdWxsKElEKQoKIyBTdGVwIDI6IEZpbHRlciBtdXRJRHMgYmFzZWQgb24gdGhlIGNyaXRlcmlhIGFuZCBhc3NvY2lhdGlvbiB3aXRoIHZhbGlkIElEcwp1bmlxdWVfbXV0SURpbmZvMTVfNUFBX2NvdW50IDwtIG11dF9jb2xsYXBzZV8xNSAlPiUKICBmaWx0ZXIoSUQgJWluJSB2YWxpZF9JRHMpICU+JSAgIyBLZWVwIG9ubHkgcm93cyBhc3NvY2lhdGVkIHdpdGggdmFsaWQgSURzCiAgZmlsdGVyKG11dGF0aW9ucyA+IDAgJiBtdXRhdGlvbnMgPCA2KSAlPiUgICMgS2VlcCBtdXRJRHMgd2l0aCAxLTUgbXV0YXRpb25zCiAgZGlzdGluY3QobXV0SUQpICU+JQogIG5yb3coKQoKIyBGb3JtYXQgYW5kIHByaW50IHRoZSByZXN1bHQKZm9ybWF0dGVkX2NvdW50IDwtIGZvcm1hdCh1bmlxdWVfbXV0SURpbmZvMTVfNUFBX2NvdW50LCBiaWcubWFyayA9ICIsIikKcHJpbnQocGFzdGUoIk51bWJlciBvZiB1bmlxdWUgbXV0SURzIHdpdGggMS01IG11dGF0aW9ucyBhc3NvY2lhdGVkIHdpdGggY29tcGxlbWVudGluZyBwZXJmZWN0czoiLCBmb3JtYXR0ZWRfY291bnQpKQpgYGAKCmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIFN0ZXAgMTogSWRlbnRpZnkgSURzIHRoYXQgbWVldCB0aGUgY3JpdGVyaWEgKG11dGF0aW9ucyA9PSAwIGFuZCBmaXREMDVEMDMgPCAtMSkKTE9GX0lEcyA8LSBtdXRfY29sbGFwc2VfMTUgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAwICYgZml0RDA1RDAzIDwgLTEpICU+JQogIHB1bGwoSUQpCgojIFN0ZXAgMjogRmlsdGVyIG11dElEcyBiYXNlZCBvbiB0aGUgY3JpdGVyaWEgYW5kIGFzc29jaWF0aW9uIHdpdGggTE9GX0lEcwpHT0ZfbXV0YW50cyA8LSBtdXRfY29sbGFwc2VfMTUgJT4lCiAgZmlsdGVyKElEICVpbiUgTE9GX0lEcykgJT4lICAjIEtlZXAgb25seSByb3dzIGFzc29jaWF0ZWQgd2l0aCBMT0ZfSURzCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAxKSAlPiUgICMgS2VlcCBtdXRJRHMgd2l0aCAxIG11dGF0aW9uCiAgZmlsdGVyKGZpdEQwNUQwMyA+PSAtMSkgICMgS2VlcCBHT0YgbXV0YW50cyB3aXRoIGZpdG5lc3MgPj0gLTEKCiMgQ291bnQgdW5pcXVlIG11dElEcwpHT0ZfbXV0SURpbmZvMTVfMUFBX2NvdW50IDwtIEdPRl9tdXRhbnRzICU+JQogIGRpc3RpbmN0KG11dElEKSAlPiUKICBucm93KCkKCiMgU3RlcCAzOiBDb3VudCBob3cgbWFueSBvZiB0aGUgb3JpZ2luYWwgTE9GX0lEcyBoYXZlIGFzc29jaWF0ZWQgR09GIG11dGFudHMKTE9GX0lEc193aXRoX0dPRl9tdXRhbnRzIDwtIEdPRl9tdXRhbnRzICU+JQogIGRpc3RpbmN0KElEKSAlPiUKICBucm93KCkKCiMgRm9ybWF0IGFuZCBwcmludCB0aGUgcmVzdWx0cwpmb3JtYXR0ZWRfTE9GX0lEc19jb3VudCA8LSBmb3JtYXQobGVuZ3RoKExPRl9JRHMpLCBiaWcubWFyayA9ICIsIikKZm9ybWF0dGVkX211dGFudHNfY291bnQgPC0gZm9ybWF0KEdPRl9tdXRJRGluZm8xNV8xQUFfY291bnQsIGJpZy5tYXJrID0gIiwiKQpmb3JtYXR0ZWRfTE9GX0lEc193aXRoX0dPRl9tdXRhbnRzIDwtIGZvcm1hdChMT0ZfSURzX3dpdGhfR09GX211dGFudHMsIGJpZy5tYXJrID0gIiwiKQoKcHJpbnQocGFzdGUoIk51bWJlciBvZiBvcmlnaW5hbCBJRHMgKG11dGF0aW9ucyA9PSAwKSB3aXRoIGZpdEQwNUQwMyA8IC0xOiIsIGZvcm1hdHRlZF9MT0ZfSURzX2NvdW50KSkKcHJpbnQocGFzdGUoIk51bWJlciBvZiB1bmlxdWUgbXV0SURzIHdpdGggMSBtdXRhdGlvbiBhc3NvY2lhdGVkIHdpdGggdGhlc2UgSURzIGFuZCBmaXREMDVEMDMgPj0gLTE6IiwgZm9ybWF0dGVkX211dGFudHNfY291bnQpKQpwcmludChwYXN0ZSgiTnVtYmVyIG9mIG9yaWdpbmFsIElEcyB0aGF0IGhhdmUgYXNzb2NpYXRlZCBtdXRhbnRzIG1lZXRpbmcgdGhlIGNyaXRlcmlhOiIsIGZvcm1hdHRlZF9MT0ZfSURzX3dpdGhfR09GX211dGFudHMpKQpgYGAKCioqQm90aCBQbG90czoqKiBQbG90IGJvdGggZ3JhcGhzIHRvZ2V0aGVyOgpgYGB7cn0KcGF0Y2gxIDwtIChMMTVfcGVyZmVjdHNfY29tcGxlbWVudGF0aW9uX2RlbnNpdHkgfCBMMTVfbXV0YW50c19jb21wbGVtZW50YXRpb25fZGVuc2l0eSkKcGF0Y2gxCgpwYXRjaDIgPC0gKEwxNV9wZXJmZWN0c19jb21wbGVtZW50YXRpb25fZGVuc2l0eSB8IEwxNV9tdXRhbnRzX2NvbXBsZW1lbnRhdGlvbl9kZW5zaXR5KSAvIEwxNV9wZXJmZWN0c19tdXRhbnRzX2NvbXBsZW1lbnRhdGlvbl9kZW5zaXR5CnBhdGNoMgpgYGAKCmBgYHtyIGVjaG89RkFMU0V9CiNTYXZpbmcgNiB4IDUgaW4gaW1hZ2VzCmdnc2F2ZShmaWxlPSJHT0YvUExPVFMvTGliMTVfQ29tcGxlbWVudGF0aW9uX1BlcmZlY3RzX011dGFudHNfVG9nZXRoZXJfbG93X2hpZ2hfZml0bmVzc19kZW5zaXR5LnBuZyIsIHBsb3Q9cGF0Y2gxLAogICAgICAgd2lkdGg9MTAsIGhlaWdodD01LCB1bml0cz0iaW4iKQoKI1NhdmluZyA2IHggNSBpbiBpbWFnZXMKZ2dzYXZlKGZpbGU9IkdPRi9QTE9UUy9MaWIxNV9Db21wbGVtZW50YXRpb25fUGVyZmVjdHNfTXV0YW50c19Cb3RoX2xvd19oaWdoX2ZpdG5lc3NfZGVuc2l0eS5wbmciLCBwbG90PXBhdGNoMiwKICAgICAgIHdpZHRoPTUsIGhlaWdodD01LCB1bml0cz0iaW4iKQpgYGAKCkNvdW50IHRoZSBudW1iZXIgb2YgdW5pcXVlIHBlcmZlY3RzIHdpdGggZml0bmVzcyA+IC0xIGFuZCBmaXRuZXNzIDwgLTE6CmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIEFzc3VtaW5nIEwxNV9wZXJmZWN0c19jb21wbGVtZW50YXRpb24gaXMgYWxyZWFkeSBkZWZpbmVkCnVuaXF1ZV9JRF9jb3VudHNfcGVyZmVjdHMgPC0gTDE1X3BlcmZlY3RzX2NvbXBsZW1lbnRhdGlvbiAlPiUKICBncm91cF9ieShJRCkgJT4lCiAgc3VtbWFyaXNlKAogICAgbWluX2ZpdG5lc3MgPSBtaW4oZml0RDA1RDAzLCBuYS5ybSA9IFRSVUUpLAogICAgbWF4X2ZpdG5lc3MgPSBtYXgoZml0RDA1RDAzLCBuYS5ybSA9IFRSVUUpLAogICAgYWxsX25hID0gYWxsKGlzLm5hKGZpdEQwNUQwMykpCiAgKSAlPiUKICBtdXRhdGUoZml0bmVzc19jYXRlZ29yeSA9IGNhc2Vfd2hlbigKICAgIGFsbF9uYSB+ICJBbGwgTkEiLAogICAgIWlzLmZpbml0ZShtaW5fZml0bmVzcykgJiAhaXMuZmluaXRlKG1heF9maXRuZXNzKSB+ICJBbGwgTkEiLAogICAgbWF4X2ZpdG5lc3MgPCAtMSB+ICJBbGwgbGVzcyB0aGFuIC0xIiwKICAgIG1pbl9maXRuZXNzID4gLTEgfiAiQWxsIGdyZWF0ZXIgdGhhbiAtMSIsCiAgICBUUlVFIH4gIlNwYW5zIC0xIgogICkpICU+JQogIGdyb3VwX2J5KGZpdG5lc3NfY2F0ZWdvcnkpICU+JQogIHN1bW1hcmlzZShjb3VudCA9IG4oKSkKCnByaW50KCJVbmlxdWUgSUQgY291bnRzIGZvciBwZXJmZWN0cyBieSBmaXRuZXNzIGNhdGVnb3J5OiIpCnByaW50KHVuaXF1ZV9JRF9jb3VudHNfcGVyZmVjdHMpCgp0b3RhbF91bmlxdWVfSURzX3BlcmZlY3RzIDwtIG5fZGlzdGluY3QoTDE1X3BlcmZlY3RzX2NvbXBsZW1lbnRhdGlvbiRJRCkKcHJpbnQocGFzdGUoIlRvdGFsIG51bWJlciBvZiB1bmlxdWUgSURzIGluIHBlcmZlY3RzOiIsIHRvdGFsX3VuaXF1ZV9JRHNfcGVyZmVjdHMpKQoKSURzX3dpdGhfdmFsaWRfZml0bmVzc19wZXJmZWN0cyA8LSBMMTVfcGVyZmVjdHNfY29tcGxlbWVudGF0aW9uICU+JQogIGZpbHRlcighaXMubmEoZml0RDA1RDAzKSkgJT4lCiAgbl9kaXN0aW5jdCguJElEKQoKcHJpbnQocGFzdGUoIk51bWJlciBvZiB1bmlxdWUgSURzIGluIHBlcmZlY3RzIHdpdGggYXQgbGVhc3Qgb25lIHZhbGlkIGZpdEQwNUQwMzoiLCBJRHNfd2l0aF92YWxpZF9maXRuZXNzX3BlcmZlY3RzKSkKCiMgQWRkaXRpb25hbCBpbmZvcm1hdGlvbjogY291bnQgb2Ygcm93cyBmb3IgZWFjaCBmaXRuZXNzIGNhdGVnb3J5CnJvd3NfcGVyX2NhdGVnb3J5X3BlcmZlY3RzIDwtIEwxNV9wZXJmZWN0c19jb21wbGVtZW50YXRpb24gJT4lCiAgbXV0YXRlKGZpdG5lc3NfY2F0ZWdvcnkgPSBjYXNlX3doZW4oCiAgICBpcy5uYShmaXREMDVEMDMpIH4gIk5BIiwKICAgIGZpdEQwNUQwMyA8IC0xIH4gIkxlc3MgdGhhbiAtMSIsCiAgICBmaXREMDVEMDMgPiAtMSB+ICJHcmVhdGVyIHRoYW4gLTEiLAogICAgVFJVRSB+ICJFcXVhbCB0byAtMSIKICApKSAlPiUKICBncm91cF9ieShmaXRuZXNzX2NhdGVnb3J5KSAlPiUKICBzdW1tYXJpc2Uocm93X2NvdW50ID0gbigpKQoKcHJpbnQoIk51bWJlciBvZiByb3dzIGluIGVhY2ggZml0bmVzcyBjYXRlZ29yeSBmb3IgcGVyZmVjdHM6IikKcHJpbnQocm93c19wZXJfY2F0ZWdvcnlfcGVyZmVjdHMpCmBgYAoKQ291bnQgdGhlIG51bWJlciBvZiB1bmlxdWUgbXV0YW50cyB3aXRoIGZpdG5lc3MgPiAtMSBhbmQgZml0bmVzcyA8IC0xOgpgYGB7ciBjbGFzcy5vdXRwdXQ9Imdvb2RDb2RlIn0KIyBDb3VudCBtdXRhbnRzIGZyb20gTDE1X211dGFudHNfY29tcGxlbWVudGF0aW9uIGRhdGFzZXQKdW5pcXVlX211dElEX2NvdW50cyA8LSBMMTVfbXV0YW50c19jb21wbGVtZW50YXRpb24gJT4lCiAgZ3JvdXBfYnkobXV0SUQpICU+JQogIHN1bW1hcmlzZSgKICAgIG1pbl9maXRuZXNzID0gbWluKGZpdEQwNUQwMywgbmEucm0gPSBUUlVFKSwKICAgIG1heF9maXRuZXNzID0gbWF4KGZpdEQwNUQwMywgbmEucm0gPSBUUlVFKSwKICAgIGFsbF9uYSA9IGFsbChpcy5uYShmaXREMDVEMDMpKQogICkgJT4lCiAgbXV0YXRlKGZpdG5lc3NfY2F0ZWdvcnkgPSBjYXNlX3doZW4oCiAgICBhbGxfbmEgfiAiQWxsIE5BIiwKICAgICFpcy5maW5pdGUobWluX2ZpdG5lc3MpICYgIWlzLmZpbml0ZShtYXhfZml0bmVzcykgfiAiQWxsIE5BIiwKICAgIG1heF9maXRuZXNzIDwgLTEgfiAiQWxsIGxlc3MgdGhhbiAtMSIsCiAgICBtaW5fZml0bmVzcyA+IC0xIH4gIkFsbCBncmVhdGVyIHRoYW4gLTEiLAogICAgVFJVRSB+ICJTcGFucyAtMSIKICApKSAlPiUKICBncm91cF9ieShmaXRuZXNzX2NhdGVnb3J5KSAlPiUKICBzdW1tYXJpc2UoY291bnQgPSBuKCkpCgpwcmludCh1bmlxdWVfbXV0SURfY291bnRzKQoKdG90YWxfdW5pcXVlX211dElEcyA8LSBuX2Rpc3RpbmN0KEwxNV9tdXRhbnRzX2NvbXBsZW1lbnRhdGlvbiRtdXRJRCkKcHJpbnQocGFzdGUoIlRvdGFsIG51bWJlciBvZiB1bmlxdWUgbXV0SURzOiIsIHRvdGFsX3VuaXF1ZV9tdXRJRHMpKQoKbXV0SURzX3dpdGhfdmFsaWRfZml0bmVzcyA8LSBMMTVfbXV0YW50c19jb21wbGVtZW50YXRpb24gJT4lCiAgZmlsdGVyKCFpcy5uYShmaXREMDVEMDMpKSAlPiUKICBuX2Rpc3RpbmN0KC4kbXV0SUQpCgpwcmludChwYXN0ZSgiTnVtYmVyIG9mIHVuaXF1ZSBtdXRJRHMgd2l0aCBhdCBsZWFzdCBvbmUgdmFsaWQgZml0RDA1RDAzOiIsIG11dElEc193aXRoX3ZhbGlkX2ZpdG5lc3MpKQoKIyBBZGRpdGlvbmFsIGluZm9ybWF0aW9uOiBjb3VudCBvZiByb3dzIGZvciBlYWNoIGZpdG5lc3MgY2F0ZWdvcnkKcm93c19wZXJfY2F0ZWdvcnkgPC0gTDE1X211dGFudHNfY29tcGxlbWVudGF0aW9uICU+JQogIG11dGF0ZShmaXRuZXNzX2NhdGVnb3J5ID0gY2FzZV93aGVuKAogICAgaXMubmEoZml0RDA1RDAzKSB+ICJOQSIsCiAgICBmaXREMDVEMDMgPCAtMSB+ICJMZXNzIHRoYW4gLTEiLAogICAgZml0RDA1RDAzID4gLTEgfiAiR3JlYXRlciB0aGFuIC0xIiwKICAgIFRSVUUgfiAiRXF1YWwgdG8gLTEiCiAgKSkgJT4lCiAgZ3JvdXBfYnkoZml0bmVzc19jYXRlZ29yeSkgJT4lCiAgc3VtbWFyaXNlKHJvd19jb3VudCA9IG4oKSkKCnByaW50KCJOdW1iZXIgb2Ygcm93cyBpbiBlYWNoIGZpdG5lc3MgY2F0ZWdvcnk6IikKcHJpbnQocm93c19wZXJfY2F0ZWdvcnkpCmBgYAoKIyMjIERyb3BvdXQgUGVyZmVjdHMKClN0YXJ0IGJ5IHJldHJpZXZpbmcgYWxsIGRyb3BvdXQgcGVyZmVjdHMgd2l0aCBhIGxvZy1mb2xkIGNoYW5nZSB2YWx1ZSBsZXNzIHRoYW4gLTEuMCBhbmQgY29ycmVzcG9uZGluZyBHb0YgbXV0YW50cyB3aXRoIGEgbG9nLWZvbGQgY2hhbmdlIHZhbHVlIGdyZWF0ZXIgdGhhbiAtMS4wLiBVc2UgdGhlIGBtdXRfY29sbGFwc2VfMTVgIGRhdGFzZXQgd2hpY2ggaW5jbHVkZXMgNzk3IHBlcmZlY3RzIChtdXRhdGlvbnMgPSAwOyBudW1wcnVuZWRCQ3MgPSA1KSBhbmQgMTIsMTc0IG11dGFudHMgd2l0aCB1cCB0byA1IEFBIGRpc3RhbmNlIGFuZCBhdCBsZWFzdCAxIEJDIChudW1wcnVuZWRCQ3MgPSAxKSBtYXRjaGluZyB0byBhIHBlcmZlY3QgdmFyaWFudCBpbiB0aGUgZGF0YXNldC4KYGBge3J9CiMgU3RlcCAxOiBJZGVudGlmeSBJRHMgdGhhdCBoYXZlIHJvd3Mgd2hlcmUgbXV0YXRpb25zID09IDAgYW5kIGZpdEQwNUQwMyA8IC0xLjAKZHJvcG91dDE1X2lkc193aXRoX3plcm9fbXV0YXRpb25zIDwtIG11dF9jb2xsYXBzZV8xNSAlPiUKICBmaWx0ZXIobXV0YXRpb25zID09IDAgJiBmaXREMDVEMDMgPCAtMS4wKSAlPiUKICBkaXN0aW5jdChJRCkgJT4lCiAgcHVsbChJRCkKCiMgU3RlcCAyOiBGaWx0ZXIgdGhlIG1haW4gZGF0YXNldCB0byBrZWVwIG11dGFudHMgd2l0aCBmaXRuZXNzID4gLTEuMCBpZiB0aGV5IG1hdGNoIGEgY29ycmVzcG9uZGluZyBwZXJmZWN0IElECmRyb3BvdXRfbXV0YW50czE1X0dPRiA8LSBtdXRfY29sbGFwc2VfMTUgJT4lCiAgZmlsdGVyKAogICAgKG11dGF0aW9ucyA9PSAwICYgZml0RDA1RDAzIDwgLTEuMCkgfAogICAgIyhtdXRhdGlvbnMgIT0gMCAmIElEICVpbiUgZHJvcG91dDE1X2lkc193aXRoX3plcm9fbXV0YXRpb25zICYgIWlzLm5hKGZpdEQwNUQwMykpKSAlPiUKICAgIChtdXRhdGlvbnMgIT0gMCAmIGZpdEQwNUQwMyA+IC0xLjAgJiBJRCAlaW4lIGRyb3BvdXQxNV9pZHNfd2l0aF96ZXJvX211dGF0aW9ucykpICU+JQogIGRwbHlyOjpzZWxlY3QoSUQsIG11dElELCBudW1wcnVuZWRCQ3MsIG11dGF0aW9ucywgZml0RDA1RDAzLCBzZXEpCmBgYAoKVmFsaWRhdGUgdGhhdCByb3dzIHdoZXJlIG11dGF0aW9ucyAhPSAwIGhhdmUgYW4gSUQgdGhhdCBtYXRjaGVzIHJvd3Mgd2hlcmUgbXV0YXRpb25zID09IDAgYW5kIGZpdEQwNUQwMyA8IC0xLjAuCmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIDEuIEZpcnN0LCBjcmVhdGUgdHdvIHN1YnNldHMgb2YgdGhlIGRhdGEKemVyb19tdXRhdGlvbl9yb3dzIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRiAlPiUKICBmaWx0ZXIobXV0YXRpb25zID09IDAgJiBmaXREMDVEMDMgPCAtMS4wKQoKbm9uX3plcm9fbXV0YXRpb25fcm93cyA8LSBkcm9wb3V0X211dGFudHMxNV9HT0YgJT4lCiAgI2ZpbHRlcihtdXRhdGlvbnMgIT0gMCkKICBmaWx0ZXIobXV0YXRpb25zICE9IDAgJiBmaXREMDVEMDMgPiAtMS4wKQoKIyAyLiBDaGVjayB0aGF0IGFsbCBJRHMgaW4gbm9uX3plcm9fbXV0YXRpb25fcm93cyBhcmUgcHJlc2VudCBpbiB6ZXJvX211dGF0aW9uX3Jvd3MKYWxsX3ZhbGlkX2lkcyA8LSBhbGwobm9uX3plcm9fbXV0YXRpb25fcm93cyRJRCAlaW4lIHplcm9fbXV0YXRpb25fcm93cyRJRCkKcHJpbnQocGFzdGUoIkFsbCBub24temVybyBtdXRhdGlvbiByb3dzIGhhdmUgYSBtYXRjaGluZyB6ZXJvIG11dGF0aW9uIHJvdzoiLCBhbGxfdmFsaWRfaWRzKSkKCiMgMy4gSWYgdGhlIGFib3ZlIGlzIEZBTFNFLCBmaW5kIHRoZSBwcm9ibGVtYXRpYyBJRHMKaWYgKCFhbGxfdmFsaWRfaWRzKSB7CiAgcHJvYmxlbWF0aWNfaWRzIDwtIHNldGRpZmYobm9uX3plcm9fbXV0YXRpb25fcm93cyRJRCwgemVyb19tdXRhdGlvbl9yb3dzJElEKQogIHByaW50KCJJRHMgd2l0aCBub24temVybyBtdXRhdGlvbnMgYnV0IG5vIG1hdGNoaW5nIHplcm8gbXV0YXRpb24gcm93OiIpCiAgcHJpbnQocHJvYmxlbWF0aWNfaWRzKQp9CgojIDQuIENoZWNrIGZvciBhbnkgSURzIGluIHplcm9fbXV0YXRpb25fcm93cyB0aGF0IGRvbid0IGhhdmUgYSBjb3JyZXNwb25kaW5nIG5vbi16ZXJvIG11dGF0aW9uIHJvdwppZHNfd2l0aG91dF9ub25femVybyA8LSBzZXRkaWZmKHplcm9fbXV0YXRpb25fcm93cyRJRCwgbm9uX3plcm9fbXV0YXRpb25fcm93cyRJRCkKcHJpbnQoIklEcyB3aXRoIHplcm8gbXV0YXRpb25zIGJ1dCBubyBjb3JyZXNwb25kaW5nIG5vbi16ZXJvIG11dGF0aW9uIHJvd3M6IikKcHJpbnQoaWRzX3dpdGhvdXRfbm9uX3plcm8pCgojIDUuIFN1bW1hcnkgc3RhdGlzdGljcwpwcmludChwYXN0ZSgiTnVtYmVyIG9mIHVuaXF1ZSBJRHMgaW4gemVybyBtdXRhdGlvbiByb3dzOiIsIG5fZGlzdGluY3QoemVyb19tdXRhdGlvbl9yb3dzJElEKSkpCnByaW50KHBhc3RlKCJOdW1iZXIgb2YgdW5pcXVlIElEcyBpbiBub24temVybyBtdXRhdGlvbiByb3dzOiIsIG5fZGlzdGluY3Qobm9uX3plcm9fbXV0YXRpb25fcm93cyRJRCkpKQoKIyA2LiBEaXN0cmlidXRpb24gb2YgbXV0YXRpb24gY291bnRzIGZvciBub24temVybyBtdXRhdGlvbiByb3dzCm11dGF0aW9uX2Rpc3RyaWJ1dGlvbiA8LSBub25femVyb19tdXRhdGlvbl9yb3dzICU+JQogIGdyb3VwX2J5KG11dGF0aW9ucykgJT4lCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUKICBhcnJhbmdlKG11dGF0aW9ucykKCnByaW50KCJEaXN0cmlidXRpb24gb2YgbXV0YXRpb24gY291bnRzOiIpCnByaW50KG11dGF0aW9uX2Rpc3RyaWJ1dGlvbikKCiMgNy4gQ2hlY2sgZm9yIGFueSB1bmV4cGVjdGVkIG11dGF0aW9uIHZhbHVlcwp1bmV4cGVjdGVkX211dGF0aW9ucyA8LSBkcm9wb3V0X211dGFudHMxNV9HT0YgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA8IDAgfCBtdXRhdGlvbnMgPiA1KSAgIyBBZGp1c3QgdGhlIHVwcGVyIGJvdW5kIGFzIG5lZWRlZAoKaWYgKG5yb3codW5leHBlY3RlZF9tdXRhdGlvbnMpID4gMCkgewogIHByaW50KCJSb3dzIHdpdGggdW5leHBlY3RlZCBtdXRhdGlvbiB2YWx1ZXM6IikKICBwcmludCh1bmV4cGVjdGVkX211dGF0aW9ucykKfSBlbHNlIHsKICBwcmludCgiTm8gdW5leHBlY3RlZCBtdXRhdGlvbiB2YWx1ZXMgZm91bmQuIikKfQpgYGAKClJlbW92ZSB1bmlxdWUgcGVyZmVjdCBJRHMgaWYgdGhlcmUgaXMgbm8gY29ycmVzcG9uZGluZyBtdXRhbnRzIHdpdGggZml0bmVzcyBncmVhdGVyIHRoYW4gLTEuMDoKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CiMgU3RlcCAxOiBJZGVudGlmeSBJRHMgd2l0aCB6ZXJvIG11dGF0aW9ucwp6ZXJvX211dGF0aW9uX2lkcyA8LSBkcm9wb3V0X211dGFudHMxNV9HT0YgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAwICYgZml0RDA1RDAzIDwgLTEuMCkgJT4lCiAgcHVsbChJRCkKCiMgU3RlcCAyOiBJZGVudGlmeSBJRHMgd2l0aCBub24temVybyBtdXRhdGlvbnMKbm9uX3plcm9fbXV0YXRpb25faWRzIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRiAlPiUKICBmaWx0ZXIobXV0YXRpb25zICE9IDAgJiBmaXREMDVEMDMgPiAtMS4wKSAlPiUKICBwdWxsKElEKQoKIyBTdGVwIDM6IEZpbmQgSURzIHRoYXQgaGF2ZSB6ZXJvIG11dGF0aW9ucyBidXQgbm8gY29ycmVzcG9uZGluZyBub24temVybyBtdXRhdGlvbiByb3dzCmlkc190b19yZW1vdmUgPC0gc2V0ZGlmZih6ZXJvX211dGF0aW9uX2lkcywgbm9uX3plcm9fbXV0YXRpb25faWRzKQoKIyBTdGVwIDQ6IFJlbW92ZSB0aGUgcm93cyB3aXRoIHRoZXNlIElEcwpkcm9wb3V0X211dGFudHMxNV9HT0ZfY2xlYW5lZCA8LSBkcm9wb3V0X211dGFudHMxNV9HT0YgJT4lCiAgZmlsdGVyKCEoSUQgJWluJSBpZHNfdG9fcmVtb3ZlKSkKCiMgUHJpbnQgc3VtbWFyeQpwcmludChwYXN0ZSgiTnVtYmVyIG9mIHJvd3MgYmVmb3JlIGNsZWFuaW5nOiIsIG5yb3coZHJvcG91dF9tdXRhbnRzMTVfR09GKSkpCnByaW50KHBhc3RlKCJOdW1iZXIgb2Ygcm93cyBhZnRlciBjbGVhbmluZzoiLCBucm93KGRyb3BvdXRfbXV0YW50czE1X0dPRl9jbGVhbmVkKSkpCnByaW50KHBhc3RlKCJOdW1iZXIgb2Ygcm93cyByZW1vdmVkOiIsIG5yb3coZHJvcG91dF9tdXRhbnRzMTVfR09GKSAtIG5yb3coZHJvcG91dF9tdXRhbnRzMTVfR09GX2NsZWFuZWQpKSkKcHJpbnQocGFzdGUoIk51bWJlciBvZiB1bmlxdWUgSURzIHJlbW92ZWQ6IiwgbGVuZ3RoKGlkc190b19yZW1vdmUpKSkKCiMgT3B0aW9uYWxseSwgeW91IGNhbiBwcmludCB0aGUgcmVtb3ZlZCBJRHMKcHJpbnQoIklEcyByZW1vdmVkOiIpCnByaW50KGlkc190b19yZW1vdmUpCgojIEFzc2lnbiB0aGUgY2xlYW5lZCBkYXRhIGJhY2sgdG8gZHJvcG91dF9tdXRhbnRzMTVfR09GIGlmIHlvdSB3YW50IHRvIHVwZGF0ZSB0aGUgb3JpZ2luYWwgdmFyaWFibGUKZHJvcG91dF9tdXRhbnRzMTVfR09GIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9jbGVhbmVkCmBgYAoKIyMjIERyb3BvdXQgTXV0YW50cwpTdW1tYXJpemUgdGhlIG51bWJlciBvZiBwZXJmZWN0cyBhbmQgbXV0YW50cyBhdCBlYWNoIEFBIGRpc3RhbmNlIGFmdGVyIGZpbHRlcmluZzoKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CiMgQ3JlYXRlIGEgZnVuY3Rpb24gdG8gY291bnQgdW5pcXVlIG11dElEcyBmb3IgYSBnaXZlbiBudW1iZXIgb2YgbXV0YXRpb25zCmRyb3BvdXRfbXV0YW50czE1X0dPRl9jb3VudCA8LSBmdW5jdGlvbihkYXRhLCBtdXRhdGlvbl9jb3VudCkgewogIGxlbmd0aCh1bmlxdWUoc3Vic2V0KGRhdGEsIG11dGF0aW9ucyA9PSBtdXRhdGlvbl9jb3VudCkkbXV0SUQpKQp9CgojIENyZWF0ZSBhIHZlY3RvciBvZiBjb3VudHMgZm9yIG11dGF0aW9ucyAxLTUKZHJvcG91dDE1X2NvdW50cyA8LSBzYXBwbHkoMTo1LCBmdW5jdGlvbih4KSBkcm9wb3V0X211dGFudHMxNV9HT0ZfY291bnQoZHJvcG91dF9tdXRhbnRzMTVfR09GLCB4KSkKCiMgQ291bnQgcGVyZmVjdHMgc2VwYXJhdGVseQpwZXJmZWN0c19jb3VudCA8LSBsZW5ndGgodW5pcXVlKHN1YnNldChkcm9wb3V0X211dGFudHMxNV9HT0YsIG11dGF0aW9ucyA9PSAwICYgZml0RDA1RDAzIDwgLTEuMCkkbXV0SUQpKQoKIyBDcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggdGhlIHJlc3VsdHMsIGluY2x1ZGluZyB0aGUgc3VtbWFyeSByb3cKZHJvcG91dF9tdXRhbnRzMTVfR09GX3RhYmxlIDwtIGRhdGEuZnJhbWUoCiAgTXV0YXRpb25zID0gYygiUGVyZmVjdHMgKGZpdCA8IC0xLjApIiwgIjEgTXV0YXRpb24iLCAiMiBNdXRhdGlvbnMiLCAiMyBNdXRhdGlvbnMiLCAiNCBNdXRhdGlvbnMiLCAiNSBNdXRhdGlvbnMiLCAiVG90YWwgTXV0YXRpb25zIiksCiAgQ291bnQgPSBjKHBlcmZlY3RzX2NvdW50LCBkcm9wb3V0MTVfY291bnRzLCBzdW0oZHJvcG91dDE1X2NvdW50cykpCikKCiMgUHJpbnQgdGhlIHRhYmxlCnByaW50KGRyb3BvdXRfbXV0YW50czE1X0dPRl90YWJsZSkKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQojIFNhdmUgYXMgLmNzdiBmb3IgdXNlIGluIG90aGVyIFJNRCBmaWxlczoKd3JpdGUuY3N2KGRyb3BvdXRfbXV0YW50czE1X0dPRiwgJ0dPRi9PVVRQVVQvQ29tcC9MaWIxNS5EMDVEMDMuZ29mLjFudW1wcnVuZWRCQ3MuNUFBLm11dHMuZml0bmVzcy0xLmNzdicsCiAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSwgcXVvdGU9RkFMU0UpCmBgYAoKIyMjIEdPRiBGaXRuZXNzCgoqKkdvRiBGaXRuZXNzOioqIFNlcGFyYXRlIHRoZSBgZHJvcG91dF9tdXRhbnRzMTVfR09GYCBkYXRhc2V0IGludG8gdHdvIG5ldyBkYXRhZnJhbWVzLCB3aGVyZSBERjEgY29udGFpbnMgcGVyZmVjdHMgYW5kIERGMiBjb250YWlucyBtdXRhbnRzOgpgYGB7cn0KIyBDcmVhdGUgYSBkYXRhZnJhbWUgd2l0aCBtdXRhdGlvbnMgPT0gMApkcm9wb3V0X211dGFudHMxNV9HT0Zfbm9fbXV0YXRpb25zIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRltkcm9wb3V0X211dGFudHMxNV9HT0YkbXV0YXRpb25zID09IDAsIF0KCiMgQ3JlYXRlIGEgZGF0YWZyYW1lIHdpdGggbXV0YXRpb25zICE9IDAKZHJvcG91dF9tdXRhbnRzMTVfR09GX3dpdGhfbXV0YXRpb25zIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRltkcm9wb3V0X211dGFudHMxNV9HT0YkbXV0YXRpb25zICE9IDAsIF0KCiMgQUxURVJOQVRJVkU6IENyZWF0ZSBhIGRhdGFmcmFtZSB3aXRoIG11dGF0aW9ucyA9PSAxIChvbmx5IHVzZXMgMSBhYSBtdXRhdGlvbiB2YXJpYW50cykKI2Ryb3BvdXRfbXV0YW50czE1X0dPRl93aXRoX211dGF0aW9ucyA8LSBkcm9wb3V0X211dGFudHMxNV9HT0ZbZHJvcG91dF9tdXRhbnRzMTVfR09GJG11dGF0aW9ucyA9PSAxLCBdCmBgYAoKTm93LCByZS1jb21iaW5lIHRoZXNlIGRhdGFmcmFtZXMgdG8gY2FsY3VsYXRlIHRoZSBmaXRuZXNzIGNoYW5nZSAoZGVsdGEpIGJldHdlZW4gbXV0YW50cyBhbmQgdGhlaXIgcGFyZW50IGhvbW9sb2dzOgpgYGB7ciBjbGFzcy5vdXRwdXQ9Imdvb2RDb2RlIn0KIyBTdGVwIDE6IFByZXBhcmUgdGhlIHJlZmVyZW5jZSBkYXRhZnJhbWUKZGZfcmVmZXJlbmNlIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9ub19tdXRhdGlvbnMgJT4lCiAgc2VsZWN0KElELCBmaXREMDVEMDMpICU+JQogIHJlbmFtZShyZWZlcmVuY2VfZml0RDA1RDAzID0gZml0RDA1RDAzKQoKIyBTdGVwIDI6IEpvaW4gYW5kIGNhbGN1bGF0ZSB0aGUgZGlmZmVyZW5jZQpkcm9wb3V0X211dGFudHMxNV9HT0ZfZml0bmVzcyA8LSBkcm9wb3V0X211dGFudHMxNV9HT0Zfd2l0aF9tdXRhdGlvbnMgJT4lCiAgbGVmdF9qb2luKGRmX3JlZmVyZW5jZSwgYnkgPSAiSUQiKSAlPiUKICBtdXRhdGUoZml0RDA1RDAzID0gZml0RDA1RDAzIC0gcmVmZXJlbmNlX2ZpdEQwNUQwMykgJT4lCiAgc2VsZWN0KElELCBtdXRJRCwgbXV0YXRpb25zLCBmaXREMDVEMDMpCgojIFByaW50IHN1bW1hcnkgc3RhdGlzdGljcwpwcmludChwYXN0ZSgiTnVtYmVyIG9mIE11dGFudHM6IiwgbnJvdyhkcm9wb3V0X211dGFudHMxNV9HT0ZfZml0bmVzcykpKQpwcmludChwYXN0ZSgiVW5pcXVlIElEczoiLCBsZW5ndGgodW5pcXVlKGRyb3BvdXRfbXV0YW50czE1X0dPRl9maXRuZXNzJElEKSkpKQpwcmludChwYXN0ZSgiUmFuZ2Ugb2YgZml0RDA1RDAzOiIsIAogICAgICAgICAgICBwYXN0ZShyb3VuZChyYW5nZShkcm9wb3V0X211dGFudHMxNV9HT0ZfZml0bmVzcyRmaXREMDVEMDMsIG5hLnJtID0gVFJVRSksIDEpLCBjb2xsYXBzZSA9ICIgdG8gIikpKQpgYGAKCioqQm94cGxvdDoqKiBQbG90IG11dGFudCBmaXRuZXNzIHJlbGF0aXZlIHRvIHBhcmVudCB2YXJpYW50IGJ5IG51bWJlciBvZiBtdXRhdGlvbnM6CmBgYHtyfQpHT0ZfbXV0c19maXRuZXNzX2J5X211dHNfcGxvdCA8LSBnZ3Bsb3QoZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3MsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSBmYWN0b3IobXV0YXRpb25zKSwgeSA9IGZpdEQwNUQwMykpICsKICBnZW9tX2JveHBsb3QoKSArCiAgbGFicyh0aXRsZSA9ICJmaXREMDVEMDMgYnkgTnVtYmVyIG9mIE11dGF0aW9ucyIsIHggPSAiTnVtYmVyIG9mIE11dGF0aW9ucyIsIHkgPSAiZml0RDA1RDAzIikKCnByaW50KEdPRl9tdXRzX2ZpdG5lc3NfYnlfbXV0c19wbG90KQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9Cmdnc2F2ZShmaWxlbmFtZSA9ICJHT0YvUExPVFMvQ29tcC9HT0YubXV0LmZpdG5lc3MuYnkubXV0YXRpb25zLmNvbXBsZW1lbnRhdGlvbi5wbmciLCAKICAgICAgIHBsb3QgPUdPRl9tdXRzX2ZpdG5lc3NfYnlfbXV0c19wbG90LAogICAgICAgd2lkdGggPSA0LjUsIGhlaWdodCA9IDQuNSwgdW5pdHMgPSAiaW4iKQpgYGAKCioqSGlzdG9ncmFtIG9mIE11dGFudCBGaXRuZXNzOioqIENsZWFybHkgc2hvd3MgbXV0YW50IGZpdG5lc3MgaXMgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuCmBgYHtyfQpHT0ZfbXV0c19maXRuZXNzX2Rpc3RfcGxvdCA8LSBnZ3Bsb3QoZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3MsIGFlcyh4ID0gZml0RDA1RDAzKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC41LCBmaWxsID0gImJsdWUiLCBjb2xvciA9ICJibGFjayIpICsKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBmaXREMDVEMDMiLCB4ID0gImZpdEQwNUQwMyIsIHkgPSAiQ291bnQiKQoKcHJpbnQoR09GX211dHNfZml0bmVzc19kaXN0X3Bsb3QpCmBgYAoKYGBge3IgZWNobz1GQUxTRX0KZ2dzYXZlKGZpbGVuYW1lID0gIkdPRi9QTE9UUy9Db21wL0dPRi5tdXQuZml0bmVzcy5kaXN0cmlidXRpb24uY29tcGxlbWVudGF0aW9uLnYyLnBuZyIsIAogICAgICAgcGxvdCA9R09GX211dHNfZml0bmVzc19kaXN0X3Bsb3QsCiAgICAgICB3aWR0aCA9IDQuNSwgaGVpZ2h0ID0gNC41LCB1bml0cyA9ICJpbiIpCmBgYAoKIyMjIEdPRiBBbGlnbm1lbnQKCioqRkFTVEE6KiogR2VuZXJhdGUgYSBGQVNUQSBmaWxlIGZyb20gdGhlIGZpbHRlcmVkIGBkcm9wb3V0X211dGFudHMxNV9Hb0ZgIHBlcmZlY3RzIGRhdGFzZXQgYmFzZWQgb24gc2hhcmVkIHBlcmZlY3RzIElEcyB3aXRoIDEtQUEgbXV0YXRpb24gZm9yIEdvRiBhbmFseXNpczoKYGBge3J9CiMgRmlyc3QsIGxldCdzIGVuc3VyZSB3ZSBoYXZlIHRoZSBjb3JyZWN0IHVuaXF1ZSBJRHMgZm9yIG11dGF0aW9ucyA9PSAxCmRyb3BvdXRfbXV0YW50czE1X0dPRl8xbXV0X3VuaXF1ZV9pZHMgPC0gZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3MgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAxKSAlPiUKICBkaXN0aW5jdChJRCkgJT4lCiAgcHVsbChJRCkKCiMgTm93LCBsZXQncyB1c2UgdGhlc2UgSURzIHRvIGZpbHRlciB0aGUgZHJvcG91dF9tdXRhbnRzMTVfR09GIGRhdGFzZXQKZHJvcG91dF9tdXRhbnRzMTVfR09GXzFtdXRfdW5pcXVlX2lkX3NlcSA8LSBkcm9wb3V0X211dGFudHMxNV9HT0YgJT4lCiAgZmlsdGVyKElEICVpbiUgZHJvcG91dF9tdXRhbnRzMTVfR09GXzFtdXRfdW5pcXVlX2lkcyAmIG11dGF0aW9ucyA9PSAwKSAlPiUKICBzZWxlY3QoSUQsIHNlcSkKCiMgQ3JlYXRlIHRoZSBzZXF1ZW5jZXMgaW4gRkFTVEEgZm9ybWF0CmRyb3BvdXRfbXV0YW50czE1X0dPRl9mYXN0YV9jb250ZW50IDwtIHBhc3RlKCI+IiwgZHJvcG91dF9tdXRhbnRzMTVfR09GXzFtdXRfdW5pcXVlX2lkX3NlcSRJRCwgIlxuIiwgZHJvcG91dF9tdXRhbnRzMTVfR09GXzFtdXRfdW5pcXVlX2lkX3NlcSRzZXEsICJcbiIsIHNlcCA9ICIiLCBjb2xsYXBzZSA9ICIiKQoKIyBEZWZpbmUgdGhlIGZpbGUgcGF0aCBpbiB0aGUgd29ya2luZyBkaXJlY3RvcnkKZHJvcG91dF9tdXRhbnRzMTVfR09GX2Zhc3RhX2ZpbGVfcGF0aCA8LSBmaWxlLnBhdGgoZ2V0d2QoKSwgIkdPRi9NU0FfRHJvcG91dHMvQ29tcC9GQVNUQS9MaWIxNS5Hb0YucGVyZmVjdHMuY29tcGxlbWVudGF0aW9uLmZhc3RhIikKCiMgV3JpdGUgdGhlIEZBU1RBIGNvbnRlbnQgdG8gdGhlIGZpbGUKd3JpdGVMaW5lcyhkcm9wb3V0X211dGFudHMxNV9HT0ZfZmFzdGFfY29udGVudCwgCiAgICAgICAgICAgY29uID0gZHJvcG91dF9tdXRhbnRzMTVfR09GX2Zhc3RhX2ZpbGVfcGF0aCkKYGBgCgoqKkFsaWdubWVudDoqKiBVc2UgdGhlIGBjbHVzdGFsb2AgZXhlY3V0YWJsZSB0byBhbGlnbiB0aGUgcHJvdGVpbiBzZXF1ZW5jZXMgYXNzb2NpYXRlZCB3aXRoIHRoZSBkcm9wb3V0IHBlcmZlY3RzLiBUaGlzIHdpbGwgYWxpZ24gdGhlIEZBU1RBIGZpbGU6ICoqTGliMTUuR29GLnBlcmZlY3RzLmNvbXBsZW1lbnRhdGlvbi5mYXN0YSoqIGZvciB1c2UgaW4gR29GIGFuYWx5c2lzLgpgYGB7YmFzaH0KLi9TY3JpcHRzL2NsdXN0YWxvIC1pIEdPRi9NU0FfRHJvcG91dHMvQ29tcC9GQVNUQS9MaWIxNS5Hb0YucGVyZmVjdHMuY29tcGxlbWVudGF0aW9uLmZhc3RhIC1vIEdPRi9NU0FfRHJvcG91dHMvQ29tcC9GQVNUQS9MaWIxNS5Hb0YucGVyZmVjdHMuY29tcGxlbWVudGF0aW9uLnRyZWUuYWxpZ25lZC5tb2QuYWxuIC0tb3V0Zm10PWNsdXN0YWwgLS1mb3JjZQpgYGAKCioqTWFwcGluZyBSZXNpZHVlczoqKiBVc2UgdGhlIGZvbGxvd2luZyBgbWFwLmFsaWduZWQucmVzaWR1ZXMucHlgIHB5dGhvbiBzY3JpcHQgdG8gZ2VuZXJhdGUgY3N2IGZpbGVzIGZvciBlYWNoIGRlc2lnbmVkIGhvbW9sb2cgdGhhdCBtYXBzIHJlc2lkdWUgcG9zaXRpb25zIG9mIGVhY2ggQS5BLiBmcm9tIHRoZSBhbGlnbm1lbnQgRkFTVEE6CmBgYHtweXRob259CmltcG9ydCB0aW1lCmltcG9ydCBjc3YKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKI0lOUFVUUzoKCmJhc2VfcGF0aCA9ICIiCnRyZWVzX3BhdGhfcHJlZml4ID0gYmFzZV9wYXRoKyIiCgojY2x1c3RhbCBmb3JtYXQgYWxpZ25tZW50IGZpbGUKYWxpZ25fZmlsZV9pbiA9IFt0cmVlc19wYXRoX3ByZWZpeCsiR09GL01TQV9Ecm9wb3V0cy9Db21wL0ZBU1RBL0xpYjE1LkdvRi5wZXJmZWN0cy5jb21wbGVtZW50YXRpb24udHJlZS5hbGlnbmVkLm1vZC5hbG4iXQoKI251bWJlciBvZiBzZXFzIGluIGVhY2ggYWxpZ25tZW50IGZpbGUKbnVtX3NhbXBsZXNfaW5fZmlsZSA9IFsxOTddICNOZXcgRkFTVEEgdy8gbXV0YW50IGZpdCA+IC0xICgrMSBmcm9tIGFjdHVhbCBmaWxlIGNvdW50KQoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojT1VUUFVUUzoKCm1zYV9tYXBfb3V0X3BhdGggPSBbdHJlZXNfcGF0aF9wcmVmaXgrIkdPRi9NU0FfRHJvcG91dHMvQ29tcC8iXQoKIyBMb29wIHRvIGdlbmVyYXRlIC5jc3YgZmlsZXMgZm9yIGVhY2ggSUQKZm9yIGFsbmkgaW4gcmFuZ2UoMSk6I2xlbihhbGlnbl9maWxlX2luKSk6CiAgICAjcHJpbnQoYWxuaSkKICAgIAogICAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICAgI1ZBUklBQkxFUzoKICAgIAogICAgI0lEIGFzIGtleSwgYWxpZ24gYXMgdmFsdWUKICAgIGFsaWduX2RpY3QgPSBkaWN0KCkKICAgIAogICAgI251bV9zYW1wbGVzID0gNDE5CiAgICBudW1fc2FtcGxlcyA9IG51bV9zYW1wbGVzX2luX2ZpbGVbYWxuaV0KICAgIAogICAgI3BvcyBrZXksIGNvbnNlbnN1cyBwb3MgdmFsCiAgICBJRGFhZGljdGxpc3QgPSBbZGljdCgpIGZvciB4IGluIHJhbmdlKG51bV9zYW1wbGVzKV0KICAgIAogICAgSUR0b2luZGV4ZGljdCA9IGRpY3QoKQogICAgaW5kZXhkdG9JRGljdCA9IGRpY3QoKQogICAgCiAgICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgICAjQ09ERToKICAgIAogICAgbGluZV9jb3VudCA9IDAKICAgICNsb29wIG92ZXIgYWxsIGFsaWdubWVudHM6CiAgICBwcmludChhbGlnbl9maWxlX2luW2FsbmldKQogICAgZm9yIGxpbmUgaW4gb3BlbihhbGlnbl9maWxlX2luW2FsbmldKToKICAgICAgICAjc2tpcCBoZWFkZXIKICAgICAgICBpZiBsaW5lX2NvdW50ID4gMToKICAgICAgICAgICAgbGlzdFdvcmRzID0gbGluZS5zcGxpdCgnICAgICcpCiAgICAgICAgICAgIElEID0gbGlzdFdvcmRzWzBdCiAgICAgICAgICAgIGFsaWduID0gbGluZVsxNjpdLnJzdHJpcCgpCiAgICAgICAgICAgIGlmIElELnN0cmlwKCkgIT0gIiI6CiAgICAgICAgICAgICAgICBhbGlnbl9kaWN0W0lEXSA9IGFsaWduX2RpY3QuZ2V0KElELCAiIikgKyBhbGlnbi5yZXBsYWNlKCIgIiwgIiIpCiAgICAgICAgbGluZV9jb3VudCArPSAxCiAgICAKICAgICNwcmludCgiTlBfNDE0NTkwIikKICAgICNwcmludChhbGlnbl9kaWN0WyJOUF80MTQ1OTAiXSkKCiAgICBjb3VudGVyID0gMAogICAgZm9yIElEIGluIGFsaWduX2RpY3Q6CiAgICAgICAgI3ByaW50KElEKQogICAgICAgICNwcmludChhbGlnbl9kaWN0W0lEXSkKICAgICAgICBJRHRvaW5kZXhkaWN0W0lEXSA9IGNvdW50ZXIKICAgICAgICBpbmRleGR0b0lEaWN0W2NvdW50ZXJdPUlECiAgICAgICAgYWxpZ24gPSBhbGlnbl9kaWN0W0lEXQogICAgICAgIAogICAgICAgIGFhY291bnRlciA9IDEKICAgICAgICAKICAgICAgICAKICAgICAgICBmb3IgaSBpbiByYW5nZShsZW4oYWxpZ24pKToKICAgICAgICAgICAgaWYgYWxpZ25baV0gIT0gIi0iOgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAjcHJpbnQoc3RyKGNvdW50ZXIpKyIgIitzdHIoYWFjb3VudGVyKSkKICAgICAgICAgICAgICAgIElEYWFkaWN0bGlzdFtjb3VudGVyXVthYWNvdW50ZXJdPWkrMQogICAgICAgICAgICAgICAgYWFjb3VudGVyICs9IDEKICAgICAgICBjb3VudGVyICs9IDEKICAgICAgICAKICAgICNwcmludChsZW4oSURhYWRpY3RsaXN0KSkKICAgIGZvciBpIGluIHJhbmdlKGxlbihJRGFhZGljdGxpc3QpLTEpOgogICAgICAgICNwcmludChpbmRleGR0b0lEaWN0W2ldKQogICAgICAgICNwcmludChpKQogICAgICAgICNwcmludChhbG5pKQogICAgICAgICNwcmludChpbmRleGR0b0lEaWN0W2ldKQogICAgICAgIGNzdmZpbGUgPSBvcGVuKHN0cihtc2FfbWFwX291dF9wYXRoW2FsbmldK2luZGV4ZHRvSURpY3RbaV0rIi5jc3YiKSwgJ3cnKQogICAgICAgIGZpZWxkbmFtZXMgPSBbJ29ydGhfYWFudW0nLCdtc2FfYWFudW0nXQogICAgICAgIHdyaXRlciA9IGNzdi5EaWN0V3JpdGVyKGNzdmZpbGUsIGZpZWxkbmFtZXM9ZmllbGRuYW1lcykKICAgICAgICB3cml0ZXIud3JpdGVoZWFkZXIoKQogICAgICAgIGZvciBqIGluIElEYWFkaWN0bGlzdFtpXToKICAgICAgICAgICAgI3ByaW50KHN0cihqKSsiICIrc3RyKElEYWFkaWN0bGlzdFtpXVtqXSkpCiAgICAgICAgICAgICNzYXZlIGFsbCBkYXRhOgogICAgICAgICAgICB3cml0ZXIud3JpdGVyb3coeydvcnRoX2FhbnVtJzpzdHIoaiksJ21zYV9hYW51bSc6c3RyKElEYWFkaWN0bGlzdFtpXVtqXSl9KQogICAgICAgIGNzdmZpbGUuY2xvc2UoKQpgYGAKCiMjIyBHT0YgUGxvdHMKCkZpbmQgR29GIFBlcmZlY3RzIGZvciBEcm9wb3V0cwpgYGB7cn0KIyBDcmVhdGUgYSBkYXRhIGZyYW1lIG9mIHVuaXF1ZSBJRHMKbXV0YW50czE1X3RvX3Bsb3QgPC0gZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3MgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAxKSAlPiUKICBkaXN0aW5jdChJRCkgJT4lCiAgc2VsZWN0KElEKQpgYGAKCmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIEluaXRpYWxpemUgYW4gZW1wdHkgdmVjdG9yIHRvIHN0b3JlIElEcyBvZiBtdXRhbnRzIHRvIGJlIHJlbW92ZWQKbXV0YW50c190b19yZW1vdmUgPC0gY2hhcmFjdGVyKCkKCiMgQ2hlY2sgZm9yIG1pc3NpbmcgTVNBIGZpbGVzCmZvciAoaSBpbiAxOm5yb3cobXV0YW50czE1X3RvX3Bsb3QpKSB7CiAgbXV0YW50MTVfY3VycmVudF90ZW1wIDwtIG11dGFudHMxNV90b19wbG90JElEW2ldCiAgaWYgKCFmaWxlLmV4aXN0cyhwYXN0ZSgiR09GL01TQV9Ecm9wb3V0cy9Db21wLyIsIG11dGFudDE1X2N1cnJlbnRfdGVtcCwgIi5jc3YiLCBzZXAgPSAiIikpKSB7CiAgICBtdXRhbnRzX3RvX3JlbW92ZSA8LSBjKG11dGFudHNfdG9fcmVtb3ZlLCBtdXRhbnQxNV9jdXJyZW50X3RlbXApCiAgfQp9CgojIE91dHB1dCB0aGUgcmVzdWx0cwppZiAobGVuZ3RoKG11dGFudHNfdG9fcmVtb3ZlKSA+IDApIHsKICBjYXQoIlRoZSBmb2xsb3dpbmcgbXV0YW50cyB3aWxsIGJlIHJlbW92ZWQgZHVlIHRvIG1pc3NpbmcgTVNBIGZpbGVzOlxuIikKICBwcmludChtdXRhbnRzX3RvX3JlbW92ZSkKICBjYXQoIlxuVG90YWwgbnVtYmVyIG9mIG11dGFudHMgdG8gYmUgcmVtb3ZlZDoiLCBsZW5ndGgobXV0YW50c190b19yZW1vdmUpLCAiXG4iKQogIAogICMgUmVtb3ZlIHRoZSBtdXRhbnRzIHdpdGhvdXQgTVNBIGZpbGVzCiAgbXV0YW50czE1X3RvX3Bsb3QgPC0gbXV0YW50czE1X3RvX3Bsb3RbIW11dGFudHMxNV90b19wbG90JElEICVpbiUgbXV0YW50c190b19yZW1vdmUsIF0KICBjYXQoIlxuTXV0YW50cyByZW1haW5pbmc6IiwgbnJvdyhtdXRhbnRzMTVfdG9fcGxvdCksICJcbiIpCn0gZWxzZSB7CiAgY2F0KCJBbGwgbXV0YW50cyBoYXZlIGNvcnJlc3BvbmRpbmcgTVNBIGZpbGVzLiBObyBtdXRhbnRzIHdpbGwgYmUgcmVtb3ZlZC5cbiIpCn0KCiMgSWYgeW91IHdhbnQgdG8gc2VlIHRoZSByZW1haW5pbmcgbXV0YW50cwpwcmludChtdXRhbnRzMTVfdG9fcGxvdCkKYGBgCgpSZWFkIGluIHRoZSBFLiBjb2xpIG1hcDoKYGBge3J9CmVjb2xpX21hcCA8LSByZWFkLmNzdihmaWxlPXBhc3RlKCJHT0YvTVNBX0Ryb3BvdXRzL0NvbXAvTlBfNDE0NTkwLmNzdiIsIHNlcD0iIiksIGhlYWQ9VFJVRSwgc2VwPSIsIikKYGBgCgpNYWtlIGEgbmV3IGRhdGEgZnJhbWUgd2hpY2ggd2lsbCBrZWVwIGFsbCBpbmZvCmBgYHtyfQpHT0ZfZml0bmVzc19tYXAgPC0gZGF0YS5mcmFtZShwb3NpdGlvbj1udW1lcmljKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFhPWNoYXJhY3RlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9bnVtZXJpYygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXRuZXNzPW51bWVyaWMoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zb3J0aG89bnVtZXJpYygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmdhcD1jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0SUQ9Y2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElEPWNoYXJhY3RlcigpKQoKYW1pbm9hY2lkcyA8LSBkYXRhLmZyYW1lKGFhPWMoJ0cnLCdQJywnQScsJ1YnLCdMJywnSScsJ00nLCdDJywnRicsJ1knLCdXJywnSCcsJ0snLCdSJywnUScsJ04nLCdFJywnRCcsJ1MnLCdUJywnWCcpLAogICAgICAgICAgICAgICAgICAgICAgICAgYWFudW09YygxOjIxKSkKYGBgCgoqKk1TQSBNYXBwaW5nOioqIE1hcCB0aGUgbXV0YW50cyAoZml0bmVzcyBkaWZmZXJlbmNlIGZyb20gcGVyZmVjdHMgPiAwLjUpIG92ZXIgYWxsIHBlcmZlY3RzIChmaXQgPCAtMSkgZm9yIEdvRiBhbmFseXNpczoKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CiNsb29wIG92ZXIgYWxsIHBlcmZlY3RzCmZvciAoaWlpIGluIDE6bnJvdyhtdXRhbnRzMTVfdG9fcGxvdCkpewogIAogICNjdXJyZW50IG9ydGhvbG9nOgogIG11dGFudF9jdXJyZW50IDwtIGFzLmNoYXJhY3RlcihtdXRhbnRzMTVfdG9fcGxvdCRJRFtpaWldKQogIAogICNsZW5ndGggb2YgbmFtZQogIG5hbWVfc2l6ZSA9IG5jaGFyKHBhc3RlKG11dGFudF9jdXJyZW50LCJfIixzZXA9IiIpKQogIAogICNnZXQgdGhlIE1TQSBtYXBwaW5nCiAgbXV0YW50X21hcCA8LSByZWFkLmNzdihmaWxlPXBhc3RlKCJHT0YvTVNBX0Ryb3BvdXRzL0NvbXAvIixtdXRhbnRfY3VycmVudCwiLmNzdiIsc2VwPSIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWQ9VFJVRSxzZXA9IiwiKQogIAogICNncmFiIHRoZSBtdXRhbnRzIHdpdGggYSBmaXRuZXNzIGluY3JlYXNlIChHb0YpIG9mIGF0IGxlYXN0IDAuNSAoZG8gbm90IGluY2x1ZGUgcGVyZmVjdHMgZnJvbSBkYXRhc2V0KQogIEdPRm11dElEaW5mb190ZW1wIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9maXRuZXNzICU+JSAjIyNVUERBVEVEIENPREUKICAgIGZpbHRlcihJRCA9PSBtdXRhbnRfY3VycmVudCkgJT4lCiAgICBmaWx0ZXIobXV0YXRpb25zICE9IDApICU+JSAjIyNVUERBVEVEIENPREUKICAgIGZpbHRlcihmaXREMDVEMDMgPj0gMC41KSAjIyNDSEFOR0UgVkFMVUUgQVMgTkVFREVEICg+PSAwLjUgaXMgZGVmYXVsdCkKICAKICAjIENoZWNrIGlmIEdPRm11dElEaW5mb190ZW1wIGlzIGVtcHR5CiAgaWYobnJvdyhHT0ZtdXRJRGluZm9fdGVtcCkgPT0gMCkgewogICAgd2FybmluZyhwYXN0ZSgiTm8gbm9uLXplcm8gbXV0YXRpb24gZGF0YSBmb3VuZCBmb3IgSUQ6IiwgbXV0YW50X2N1cnJlbnQpKQogICAgbmV4dCAgIyBTa2lwIHRvIHRoZSBuZXh0IGl0ZXJhdGlvbiBvZiB0aGUgb3V0ZXIgbG9vcAogIH0KICAKICAjbG9vcCBvdmVyIGFsbCBtdXRhbnRzIGZvciB0aGlzIGNvbnN0cnVjdDoKICBmb3IgKG1uIGluIDE6bnJvdyhHT0ZtdXRJRGluZm9fdGVtcCkpIHsKICAgIAogICAgI3RoaXMgbXV0YW50cyBmaXRuZXNzCiAgICBnb2ZfZml0X3RlbXAgPC0gR09GbXV0SURpbmZvX3RlbXAkZml0RDA1RDAzW21uXSAgIyBvciB3aGljaGV2ZXIgZml0bmVzcyBjb2x1bW4geW91J3JlIHVzaW5nCiAgICAKICAgICNncmFiIHRoZSBtdXQgbmFtZQogICAgbXV0YXRpb25zX25hbWVzIDwtIGFzLmNoYXJhY3RlcihHT0ZtdXRJRGluZm9fdGVtcCRtdXRJRFttbl0pCiAgICAKICAgICNncmFiIG9ubHkgdGhlIHJlbGV2YW50IHBvcnRpb24gb2YgdGhlIG5hbWUKICAgIG11dGF0aW9uc19uYW1lcyA8LSBzdWJzdHIobXV0YXRpb25zX25hbWVzLCBuYW1lX3NpemUrMSwgbmNoYXIobXV0YXRpb25zX25hbWVzKSkKICAgIAogICAgIyMgc3BsaXQgbXV0YXRpb24gc3RyaW5nIGF0IG5vbi1kaWdpdHMKICAgIHMgPC0gc3Ryc3BsaXQobXV0YXRpb25zX25hbWVzLCAiXyIpCiAgICAKICAgIGZvciAobXV0bnVtIGluIDE6R09GbXV0SURpbmZvX3RlbXAkbXV0YXRpb25zW21uXSl7CiAgICAgIAogICAgICAjZ3JhYiB0aGUgY29ycmVzcG9uZGluZyBtdXRhdGlvbiBzdHJpbmcKICAgICAgbXV0Y3Vycjwtc1tbMV1dW211dG51bV0KICAgICAgCiAgICAgICNnZXQgdGhlIHBvc2l0aW9uCiAgICAgIG11dHBvcyA8LSBhcy5udW1lcmljKHN0cl9leHRyYWN0KG11dGN1cnIsICJbMC05XSsiKSkKICAgICAgCiAgICAgICNnZXQgZW5kaW5nIGFhCiAgICAgIHRvX2FhIDwtIHN1YnN0cihtdXRjdXJyLCBuY2hhcihtdXRwb3MpKzIsIG5jaGFyKG11dGN1cnIpKQogICAgICAKICAgICAgI2ZpbmQgdGhlIG51bWJlciBpbiB0aGUgY29uc2Vuc3VzIHNlcQogICAgICBnb2ZfY29uc19hYW51bV9pbmRleCA8LSB3aGljaChtdXRhbnRfbWFwJG9ydGhfYWFudW0gPT0gbXV0cG9zKQogICAgICAKICAgICAgaWYgKGxlbmd0aChnb2ZfY29uc19hYW51bV9pbmRleCkgPiAwKSB7CiAgICAgICAgZ29mX2NvbnNfYWFudW0gPC0gbXV0YW50X21hcCRtc2FfYWFudW1bZ29mX2NvbnNfYWFudW1faW5kZXhdCiAgICAgICAgCiAgICAgICAgI2RvZXMgdGhpcyBtYXAgdG8gYSBub24tZ2FwCiAgICAgICAgaWYgKGdvZl9jb25zX2FhbnVtICVpbiUgZWNvbGlfbWFwJG1zYV9hYW51bSl7CiAgICAgICAgICAKICAgICAgICAgICN0aGUgY29ycmVzcG9uZGluZyBlLmNvbGkgcmVzaWR1ZQogICAgICAgICAgZV9jb2xpX3Jlc2lkdWUgPC0gZWNvbGlfbWFwJG9ydGhfYWFudW1bd2hpY2goZWNvbGlfbWFwJG1zYV9hYW51bSA9PSBnb2ZfY29uc19hYW51bSldCiAgICAgICAgICAKICAgICAgICAgICNhZGQgdGhpcyBwb2ludCB0byB0aGUgZGF0YQogICAgICAgICAgR09GX2ZpdG5lc3NfbWFwIDwtIHJiaW5kKEdPRl9maXRuZXNzX21hcCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKHBvc2l0aW9uPWVfY29saV9yZXNpZHVlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWE9dG9fYWEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9R09GbXV0SURpbmZvX3RlbXAkbXV0YXRpb25zW21uXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpdG5lc3M9Z29mX2ZpdF90ZW1wLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zb3J0aG89bXV0cG9zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5nYXA9Ik5vIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dElEPUdPRm11dElEaW5mb190ZW1wJG11dElEW21uXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElEPUdPRm11dElEaW5mb190ZW1wJElEW21uXSkpCiAgICAgICAgICAKICAgICAgICB9IGVsc2UgewogICAgICAgICAgI2lmIGl0J3MgaGVyZSBpdCBtYXBzIHRvIGEgZ2FwCiAgICAgICAgICAKICAgICAgICAgICNhZGQgdGhpcyBwb2ludCB0byB0aGUgZGF0YQogICAgICAgICAgR09GX2ZpdG5lc3NfbWFwIDwtIHJiaW5kKEdPRl9maXRuZXNzX21hcCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKHBvc2l0aW9uPS0xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWE9dG9fYWEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9R09GbXV0SURpbmZvX3RlbXAkbXV0YXRpb25zW21uXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpdG5lc3M9Z29mX2ZpdF90ZW1wLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zb3J0aG89bXV0cG9zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5nYXA9IlllcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRJRD1HT0ZtdXRJRGluZm9fdGVtcCRtdXRJRFttbl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJRD1HT0ZtdXRJRGluZm9fdGVtcCRJRFttbl0pKQogICAgICAgICAgCiAgICAgICAgfQogICAgICB9IGVsc2UgewogICAgICAgIHdhcm5pbmcocGFzdGUoIk5vIG1hdGNoaW5nIG9ydGhfYWFudW0gZm91bmQgZm9yIG11dHBvczoiLCBtdXRwb3MsICJpbiBJRDoiLCBtdXRhbnRfY3VycmVudCkpCiAgICAgICAgIyBZb3UgbWlnaHQgd2FudCB0byBoYW5kbGUgdGhpcyBjYXNlLCBwZXJoYXBzIGJ5IHNraXBwaW5nIHRoaXMgbXV0YXRpb24gb3IgYWRkaW5nIGl0IHRvIGEgc2VwYXJhdGUgbGlzdCBmb3IgcmV2aWV3CiAgICAgIH0KICAgIH0KICB9Cn0KYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQp3cml0ZS50YWJsZShHT0ZfZml0bmVzc19tYXAsIGZpbGUgPSAiR09GL09VVFBVVC9Db21wL0dPRl9maXRuZXNzX21hcC5jc3YiLCAKICAgICAgICAgICAgc2VwID0gIiwiLCByb3cubmFtZXMgPSBGLHF1b3RlPUYsY29sLm5hbWVzID0gVCkKYGBgCgpDb2xsYXBzZSB0aGUgR09GIGZpdG5lc3MgdmFsdWVzIGJ5IGFhIHBvc2l0aW9uIGFsb25nIHRoZSBwcm90ZWluIHNlcXVlbmNlOgpgYGB7cn0KR09GX2ZpdG5lc3NfY29sbGFwc2VkX2J5X3BvcyA8LSBHT0ZfZml0bmVzc19tYXAgJT4lCiAgZmlsdGVyKHBvc2l0aW9uID4gMCkgJT4lCiAgZ3JvdXBfYnkocG9zaXRpb24pICU+JQogIHN1bW1hcmlzZShmaXR2YWw9bWVkaWFuKGZpdG5lc3MpLAogICAgICAgICAgICBudW1wb2ludHM9bigpLAogICAgICAgICAgICBzdGRmaXQ9c2QoZml0bmVzcyksCiAgICAgICAgICAgIG51bW9ydGhvPWxlbmd0aCh1bmlxdWUoSUQpKSkKYGBgCgojIyMjIEdPRiBNdXRhbnQgTm8uIFBsb3QKClBsb3QgdGhlIG51bWJlciBvZiBnYWluLW9mLWZ1bmN0aW9uIG11dGFudHMgcmVjb3ZlcmVkIGZvciBlYWNoIGFhIHBvc2l0aW9uIGFsb25nIHRoZSBwcm90ZWluIHNlcXVlbmNlIGFuZCBpbmNsdWRlIGN1dG9mZiBsaW5lcyBmb3IgMSBTRCBhYm92ZSB0aGUgbWVhbiBudW1iZXIgb2YgR09GIG11dGFudHMgYW5kIDIgU0QgYWJvdmUgdGhlIG1lYW4gbnVtYmVyIG9mIEdPRiBtdXRhbnRzLiBBbGwgcG9zaXRpb25zIHdpdGggR09GIG11dGFudHMgYWJvdmUgdGhlIDIgU0QgbGluZSBhcmUgY29uc2lkZXJlZCBzaWduaWZpY2FudCBwb3NpdGlvbnMgcG9zaXRpdmVseSBpbmZsdWVuY2luZyB0aGUgcGFyZW50IHZhcmlhbnRzIGFiaWxpdHkgdG8gY29tcGxlbWVudCBtZXRhYm9saWMgZnVuY3Rpb24gaW4gdGhlIEUuIGNvbGkga25vY2tvdXQgbW9kZWwuCmBgYHtyfQpHb0ZfcGxvdCA8LSBnZ3Bsb3QoR09GX2ZpdG5lc3NfY29sbGFwc2VkX2J5X3BvcywgYWVzKHg9cG9zaXRpb24sIHk9bnVtcG9pbnRzLCBjb2xvcj1udW1vcnRobykpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSAwLCB5ID0gbWVhbihudW1wb2ludHMpKzIqc2QobnVtcG9pbnRzKSwgCiAgICAgICAgICAgICAgICAgICB4ZW5kID0gMTYwLCAKICAgICAgICAgICAgICAgICAgIHllbmQgPSBtZWFuKG51bXBvaW50cykrMipzZChudW1wb2ludHMpKSxsaW5ldHlwZT0yLGNvbG91ciA9ICJyZWQyIikrCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMCwgeSA9IG1lYW4obnVtcG9pbnRzKSwgeGVuZCA9IDE2MCwgeWVuZCA9IG1lYW4obnVtcG9pbnRzKSksbGluZXR5cGU9Mixjb2xvdXIgPSAiZGFya2JsdWUiKSsKICBnZW9tX3BvaW50KHNpemU9MS44KSsKICBsYWJzKHggPSAiUG9zaXRpb24gKGFhKSIsIHkgPSJOdW1iZXIgb2YgZ2Fpbi1vZi1mdW5jdGlvbiBtdXRhbnRzIixjb2xvcj0iIikgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvdXJzID0gYygiZGFya2JsdWUiLCAicmVkIiksCiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWU9Ik51bS5cblVuaXEuXG5Ib21vLiIsCiAgICAgICAgICAgICAgICAgICAgICAgIG5hLnZhbHVlPSJncmV5IiwgCiAgICAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwxLjEqbWF4KEdPRl9maXRuZXNzX2NvbGxhcHNlZF9ieV9wb3MkbnVtb3J0aG8pKSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDAsMTYwLDIwKSkrCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gJ2JsYWNrJywgc2l6ZSA9IDEuMCksIAogICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiwgc2l6ZSA9IDEuMCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgojIEFkZCBtYXJnaW5hbCBwbG90CkdvRl9wbG90X3dpdGhfbWFyZ2luYWwgPC0gZ2dFeHRyYTo6Z2dNYXJnaW5hbChHb0ZfcGxvdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiaGlzdG9ncmFtIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbnMgPSAieSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiaW5zPTIxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gJ2JsYWNrJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSAncmVkMicpCgojIERpc3BsYXkgdGhlIHBsb3QKcHJpbnQoR29GX3Bsb3Rfd2l0aF9tYXJnaW5hbCkKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQpnZ3NhdmUoZmlsZW5hbWUgPSAiR09GL1BMT1RTL0NvbXAvR09GLk11dGFudC5ieS5BQS5Qb3NpdGlvbi4yc2lnbWEuY29tcGxlbWVudGF0aW9uLnBuZyIsIAogICAgICAgcGxvdCA9IEdvRl9wbG90X3dpdGhfbWFyZ2luYWwsCiAgICAgICB3aWR0aCA9IDQsIGhlaWdodCA9IDQuNSwgdW5pdHMgPSAiaW4iKQpgYGAKClByaW50IG91dCBhIHN1bW1hcnkgdGFibGUgb2Ygc2lnbmlmaWNhbnQgYWEgcG9zaXRpb24gYWxvbmcgdGhlIHByb3RlaW4gc2VxdWVuY2UKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CkdPRl9maXRuZXNzX2NvbGxhcHNlZF9ieV9wb3NfMnNpZ21hIDwtIEdPRl9maXRuZXNzX2NvbGxhcHNlZF9ieV9wb3MgJT4lCiAgZmlsdGVyKG51bXBvaW50cyA+PSAobWVhbihHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zJG51bXBvaW50cykrMipzZChHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zJG51bXBvaW50cykpKQpwcmludChHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zXzJzaWdtYSkKYGBgCgpDYWxjdWxhdGUgYWxsIERhdGEgYW5kIFN0YXRzOgpgYGB7cn0KR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbCA8LSBHT0ZfZml0bmVzc19tYXAgJT4lCiAgZmlsdGVyKHBvc2l0aW9uID4gMCkgJT4lCiAgZ3JvdXBfYnkocG9zaXRpb24sIGFhKSAlPiUKICBzdW1tYXJpc2UoZml0dmFsPW1lZGlhbihmaXRuZXNzKSwKICAgICAgICAgICAgbnVtcG9pbnRzPW4oKSwKICAgICAgICAgICAgc3RkZml0PXNkKGZpdG5lc3MpLAogICAgICAgICAgICBudW1vcnRobz1sZW5ndGgodW5pcXVlKElEKSkpCgpnb2ZfYWFfZGltIDwtIG5yb3coYW1pbm9hY2lkcykKZ29mX3JlZl9sZW4gPC0gbnJvdyhlY29saV9tYXApCmBgYAoKYGBge3Igd2FybmluZz1GQUxTRX0KI3RoZXNlIG1hdHJpY2VzIGhhdmUgdGhlIGZpdG5lc3MvbnVtL3NkIGZvciBlYWNoIGFhIGF0IGVhY2ggcG9zaXRpb246CmdvZl9tYXRyaXggPSBtYXRyaXgocmVwKE5BLCBnb2ZfcmVmX2xlbipnb2ZfYWFfZGltKSxucm93PWdvZl9hYV9kaW0sbmNvbD1nb2ZfcmVmX2xlbikKZ29mX21hdHJpeF9udW0gPSBtYXRyaXgocmVwKE5BLCBnb2ZfcmVmX2xlbipnb2ZfYWFfZGltKSxucm93PWdvZl9hYV9kaW0sbmNvbD1nb2ZfcmVmX2xlbikKZ29mX21hdHJpeF9zZCA9IG1hdHJpeChyZXAoTkEsIGdvZl9yZWZfbGVuKmdvZl9hYV9kaW0pLG5yb3c9Z29mX2FhX2RpbSxuY29sPWdvZl9yZWZfbGVuKQpnb2ZfbWF0cml4X251bW9ydGhvID0gbWF0cml4KHJlcChOQSwgZ29mX3JlZl9sZW4qZ29mX2FhX2RpbSksbnJvdz1nb2ZfYWFfZGltLG5jb2w9Z29mX3JlZl9sZW4pCgojcG9wdWxhdGUgbWF0cml4CmZvciAoaSBpbiAxOm5yb3coR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbCkpewogIAogIGdvZl9tYXRyaXhbd2hpY2goYW1pbm9hY2lkcyRhYT09YXMuY2hhcmFjdGVyKEdPRl9maXRuZXNzX2NvbGxhcHNlZF9hbGwkYWFbaV0pKSxHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsJHBvc2l0aW9uW2ldXSA8LSBhcy5udW1lcmljKEdPRl9maXRuZXNzX2NvbGxhcHNlZF9hbGwkZml0dmFsW2ldKQogIGdvZl9tYXRyaXhfbnVtW3doaWNoKGFtaW5vYWNpZHMkYWE9PWFzLmNoYXJhY3RlcihHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsJGFhW2ldKSksR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbCRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsJG51bXBvaW50c1tpXSkKICBnb2ZfbWF0cml4X3NkW3doaWNoKGFtaW5vYWNpZHMkYWE9PWFzLmNoYXJhY3RlcihHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsJGFhW2ldKSksR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbCRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsJHN0ZGZpdFtpXSkKICBnb2ZfbWF0cml4X251bW9ydGhvW3doaWNoKGFtaW5vYWNpZHMkYWE9PWFzLmNoYXJhY3RlcihHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsJGFhW2ldKSksR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbCRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsJG51bW9ydGhvW2ldKQp9Cgpyb3duYW1lcyhnb2ZfbWF0cml4KTwtYW1pbm9hY2lkcyRhYQpjb2xuYW1lcyhnb2ZfbWF0cml4KTwtYygxOmdvZl9yZWZfbGVuKQpyb3duYW1lcyhnb2ZfbWF0cml4X251bSk8LWFtaW5vYWNpZHMkYWEKY29sbmFtZXMoZ29mX21hdHJpeF9udW0pPC1jKDE6Z29mX3JlZl9sZW4pCnJvd25hbWVzKGdvZl9tYXRyaXhfc2QpPC1hbWlub2FjaWRzJGFhCmNvbG5hbWVzKGdvZl9tYXRyaXhfc2QpPC1jKDE6Z29mX3JlZl9sZW4pCnJvd25hbWVzKGdvZl9tYXRyaXhfbnVtb3J0aG8pPC1hbWlub2FjaWRzJGFhCmNvbG5hbWVzKGdvZl9tYXRyaXhfbnVtb3J0aG8pPC1jKDE6Z29mX3JlZl9sZW4pCgpnb2ZfbWF0cml4X21lbHQgPC0gbWVsdChnb2ZfbWF0cml4KQpnb2ZfbWF0cml4X251bV9tZWx0IDwtIG1lbHQoZ29mX21hdHJpeF9udW0pCmdvZl9tYXRyaXhfc2RfbWVsdCA8LSBtZWx0KGdvZl9tYXRyaXhfc2QpCmdvZl9tYXRyaXhfbnVtb3J0aG9fbWVsdCA8LSBtZWx0KGdvZl9tYXRyaXhfbnVtb3J0aG8pCgojIFJlbmFtZSBjb2x1bW5zIHRvICJYMSIgYW5kICJYMiIKbmFtZXMoZ29mX21hdHJpeF9tZWx0KVtuYW1lcyhnb2ZfbWF0cml4X21lbHQpICVpbiUgYygiVmFyMSIsICJWYXIyIildIDwtIGMoIlgxIiwgIlgyIikKbmFtZXMoZ29mX21hdHJpeF9udW1fbWVsdClbbmFtZXMoZ29mX21hdHJpeF9udW1fbWVsdCkgJWluJSBjKCJWYXIxIiwgIlZhcjIiKV0gPC0gYygiWDEiLCAiWDIiKQpuYW1lcyhnb2ZfbWF0cml4X3NkX21lbHQpW25hbWVzKGdvZl9tYXRyaXhfc2RfbWVsdCkgJWluJSBjKCJWYXIxIiwgIlZhcjIiKV0gPC0gYygiWDEiLCAiWDIiKQpuYW1lcyhnb2ZfbWF0cml4X251bW9ydGhvX21lbHQpW25hbWVzKGdvZl9tYXRyaXhfbnVtb3J0aG9fbWVsdCkgJWluJSBjKCJWYXIxIiwgIlZhcjIiKV0gPC0gYygiWDEiLCAiWDIiKQoKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zIDwtIGdvZl9tYXRyaXhfbWVsdCAlPiUKICBmaWx0ZXIoWDIgPT0gMTcgfAogICAgICAgICBYMiA9PSA5NyB8CiAgICAgICAgIFgyID09IDk4IHwKICAgICAgICAgWDIgPT0gMTAyIHwKICAgICAgICAgWDIgPT0gMTAzIHwKICAgICAgICAgWDIgPT0gMTA0IHwKICAgICAgICAgWDIgPT0gMTA3KQoKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJG11dHBvc251bSA8LSAwCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgyPT0xNyldIDwtIDEKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJG11dHBvc251bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDI9PTk3KV0gPC0gMgpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkbXV0cG9zbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMj09OTgpXSA8LSAzCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgyPT0xMDIpXSA8LSA0CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgyPT0xMDMpXSA8LSA1CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgyPT0xMDQpXSA8LSA2CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgyPT0xMDcpXSA8LSA3Cgpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW0gPC0gMApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iQSIpXSA8LSAxMgpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iQyIpXSA8LSAxMApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iRCIpXSA8LSA1CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJFIildIDwtIDQKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IkYiKV0gPC0gMTkKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IkciKV0gPC0gMTEKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IkgiKV0gPC0gMwpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iSSIpXSA8LSAxNQpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iSyIpXSA8LSAxCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJMIildIDwtIDE0CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJNIildIDwtIDE2CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJOIildIDwtIDYKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IlAiKV0gPC0gMTcKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IlEiKV0gPC0gNwpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iUiIpXSA8LSAyCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJTIildIDwtIDkKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IlQiKV0gPC0gOApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iViIpXSA8LSAxMwpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iVyIpXSA8LSAyMApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iWSIpXSA8LSAxOApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iWCIpXSA8LSAyMQoKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zX3dudW0gPC0gZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zICU+JQogIGlubmVyX2pvaW4oZ29mX21hdHJpeF9udW1fbWVsdCxieT1jKCJYMSIsIlgyIikpICU+JQogIGRwbHlyOjpyZW5hbWUobXV0bnVtPXZhbHVlLnksdmFsdWU9dmFsdWUueCkKYGBgCgojIyMjIEdPRiBQb3NpdGlvbiBQbG90CgpQbG90IHRoZSBtZWFuIGZpdG5lc3Mgb2YgZWFjaCBHb0YgbXV0YXRpb24gYXQgdGhlIHNpZ25pZmljYW50IHBvc2l0aW9ucywgd2l0aCB0aGUgbnVtYmVyIG9mIG11dGFudHMgb2JzZXJ2ZWQgYXQgZWFjaCBBQToKYGBge3J9CiMgRGVmaW5lIHRoZSBvcmRlciBvZiBhbWlubyBhY2lkcyBmb3IgdGhlIHJlY3RhbmdsZXMKcmVjdF9vcmRlciA8LSBjKCJFIiwgIkciLCAiUiIsICJRIiwgIkYiLCAiTCIsICJBIikKCiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSBmb3IgdGhlIHJlY3RhbmdsZXMKcmVjdF9kYXRhIDwtIGRhdGEuZnJhbWUoCiAgYWFudW0gPSBtYXRjaChyZWN0X29yZGVyLCBjKCJLIiwiUiIsIkgiLCJFIiwiRCIsIk4iLCJRIiwiVCIsIlMiLCJDIiwiRyIsIkEiLCJWIiwiTCIsIkkiLCJNIiwiUCIsIlkiLCJGIiwiVyIsIlgiKSksCiAgeG1pbiA9IHNlcSgwLjUsIGJ5ID0gMSwgbGVuZ3RoLm91dCA9IGxlbmd0aChyZWN0X29yZGVyKSksCiAgeG1heCA9IHNlcSgxLjUsIGJ5ID0gMSwgbGVuZ3RoLm91dCA9IGxlbmd0aChyZWN0X29yZGVyKSkpCgojcGxvdCB0aGUgZGF0YSBmcm9tIGFsbCBtdXRhbnRzOgpHT0ZfZml0X251bW11dF9wbG90IDwtIGdncGxvdChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3Nfd251bSwgCiAgICAgICBhZXMoeD1tdXRwb3NudW0sIHk9YWFudW0sCiAgICAgICAgICAgZmlsbD12YWx1ZSwKICAgICAgICAgICBsYWJlbD1tdXRudW0pKSArCiAgZ2VvbV90aWxlKCkgKwogIGdlb21fdGV4dCgpICsKICAjIEFkZCBibGFjayByZWN0YW5nbGVzCiAgZ2VvbV9yZWN0KGRhdGEgPSByZWN0X2RhdGEsCiAgICAgICAgICAgIGFlcyh4bWluID0geG1pbiwgeG1heCA9IHhtYXgsIHltaW4gPSBhYW51bSAtIDAuNSwgeW1heCA9IGFhbnVtICsgMC41KSwKICAgICAgICAgICAgZmlsbCA9IE5BLCBjb2xvciA9ICJibGFjayIsIGluaGVyaXQuYWVzID0gRkFMU0UpICsKICBsYWJzKHggPSAiUG9zaXRpb24gKGFhKSIsCiAgICAgICB5ID0iQW1pbm8gYWNpZCIsY29sb3I9IiIpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJibHVlIiwgCiAgICAgICAgICAgICAgICAgICAgICBoaWdoID0gInJlZCIsCiAgICAgICAgICAgICAgICAgICAgICBuYW1lPSJGaXRuZXNzIiwKICAgICAgICAgICAgICAgICAgICAgIG5hLnZhbHVlPSJncmV5IiwKICAgICAgICAgICAgICAgICAgICAgIGxpbWl0ID0gYygwLCBtYXgoZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zX3dudW0kdmFsdWUpKSkgKwogIHRoZW1lX21pbmltYWwoKSsKICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZT0iUG9zaXRpb24gKGFhKSIsCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcz1jKDEsMiwzLDQsNSw2LDcpLAogICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiMTciLCI5NyIsIjk4IiwiMTAyIiwiMTAzIiwiMTA0IiwiMTA3IikpKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lPSJBbWlubyBhY2lkIiwgCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcz1jKDEsMiwzLDQsNSw2LDcsOCw5LDEwLDExLDEyLDEzLDE0LDE1LDE2LDE3LDE4LDE5LDIwLDIxKSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIksiLCJSIiwiSCIsIkUiLCJEIiwiTiIsIlEiLCJUIiwiUyIsIkMiLCJHIiwiQSIsIlYiLCJMIiwiSSIsIk0iLCJQIiwiWSIsIkYiLCJXIiwiWCIpKQoKcHJpbnQoR09GX2ZpdF9udW1tdXRfcGxvdCkKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQpnZ3NhdmUoZmlsZW5hbWUgPSAiR09GL1BMT1RTL0NvbXAvR09GX0ZpdG5lc3NfTnVtYmVyX011dGFudHNfQ29tcGxlbWVudGF0aW9uLnBkZiIsIAogICAgICAgcGxvdCA9IEdPRl9maXRfbnVtbXV0X3Bsb3QsCiAgICAgICB3aWR0aCA9IDQuNSwgaGVpZ2h0ID0gNC41LCB1bml0cyA9ICJpbiIpCmBgYAoKUGxvdCB0aGUgR09GIG11dGFudCBmaXRuZXNzIGFjcm9zcyB0aGUgcHJvdGVpbiBzZXF1ZW5jZToKYGBge3J9CmdncGxvdChkYXRhID0gZ29mX21hdHJpeF9tZWx0LCBhZXMoeD1YMiwgeT1YMSwgZmlsbD12YWx1ZSkpICsKICBnZW9tX3RpbGUoKSArCiAgbGFicyh4ID0gIlBvc2l0aW9uIChhYSkiLAogICAgICAgeSA9IkFtaW5vIGFjaWQiLGNvbG9yPSIiKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAiYmx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSAicmVkIiwKICAgICAgICAgICAgICAgICAgICAgICBuYW1lPSJGaXRuZXNzIiwKICAgICAgICAgICAgICAgICAgICAgICBuYS52YWx1ZT0iZ3JleSIsCiAgICAgICAgICAgICAgICAgICAgICAgbGltaXQgPSBjKDAsbWF4KGdvZl9tYXRyaXhfbWVsdCR2YWx1ZSkpKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwxNTAsMTApKQpgYGAKClBsb3QgdGhlIG51bWJlciBvZiBtdXRhbnRzIG9ic2VydmVkIGF0IGVhY2ggcG9zaXRpb24gYWxvbmcgdGhlIHByb3RlaW4gc2VxdWVuY2U6CmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGdvZl9tYXRyaXhfbnVtX21lbHQsIGFlcyh4PVgyLCB5PVgxLCBmaWxsPXZhbHVlKSkgKwogIGdlb21fdGlsZSgpKyAKICBsYWJzKHggPSAiUG9zaXRpb24gKGFhKSIsCiAgICAgICB5ID0iQW1pbm8gYWNpZCIsY29sb3I9IiIpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJibHVlIiwgCiAgICAgICAgICAgICAgICAgICAgICBoaWdoID0gInJlZCIsCiAgICAgICAgICAgICAgICAgICAgICBuYW1lPSIjcG9pbnRzIiwKICAgICAgICAgICAgICAgICAgICAgIG5hLnZhbHVlPSJncmV5IiwgCiAgICAgICAgICAgICAgICAgICAgICBsaW1pdCA9IGMoMCxtYXgoZ29mX21hdHJpeF9udW1fbWVsdCR2YWx1ZSkpKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwxNTAsMTApKQpgYGAKClBsb3QgdGhlIG51bWJlciBvZiBtdXRhbnRzIG9ic2VydmVkIGF0IGVhY2ggcG9zaXRpb24gYWxvbmcgdGhlIHByb3RlaW4gc2VxdWVuY2U6CmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGdvZl9tYXRyaXhfbnVtb3J0aG9fbWVsdCwgYWVzKHg9WDIsIHk9WDEsIGZpbGw9dmFsdWUpKSArCiAgZ2VvbV90aWxlKCkrIAogIGxhYnMoeCA9ICJQb3NpdGlvbiAoYWEpIiwKICAgICAgIHkgPSJBbWlubyBhY2lkIixjb2xvcj0iIikgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gImJsdWUiLCAKICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSAicmVkIiwKICAgICAgICAgICAgICAgICAgICAgIG5hbWU9IiNwb2ludHMiLAogICAgICAgICAgICAgICAgICAgICAgbmEudmFsdWU9ImdyZXkiLCAKICAgICAgICAgICAgICAgICAgICAgIGxpbWl0ID0gYygwLG1heChnb2ZfbWF0cml4X251bW9ydGhvX21lbHQkdmFsdWUpKSkgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDAsMTUwLDEwKSkKYGBgCgpQbG90IHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgZml0bmVzcyBmb3IgZWFjaCBwb3NpdGlvbiBhbG9uZyB0aGUgcHJvdGVpbiBzZXF1ZW5jZToKYGBge3J9CmdncGxvdChkYXRhID0gZ29mX21hdHJpeF9zZF9tZWx0LCBhZXMoeD1YMiwgeT1YMSwgZmlsbD12YWx1ZSkpICsKICBnZW9tX3RpbGUoKSsgbGFicyh4ID0gIlBvc2l0aW9uIChhYSkiLCB5ID0iQW1pbm8gYWNpZCIsY29sb3I9IiIpICsKICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiYmx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSAicmVkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgbWlkPSJnb2xkIiwKICAgICAgICAgICAgICAgICAgICAgICBuYW1lPSJzdGQoRml0bmVzcykiLAogICAgICAgICAgICAgICAgICAgICAgIG5hLnZhbHVlPSJncmV5IiwgCiAgICAgICAgICAgICAgICAgICAgICAgbGltaXQgPSBjKDAsMS4xKm1heChnb2ZfbWF0cml4X3NkX21lbHQkdmFsdWUpKSkgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDAsMTUwLDEwKSkKYGBgCgojIyMjIEdPRiBCTVMgUG9zaXRpb24gUGxvdAoKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CkdPRl9maXRuZXNzX2NvbGxhcHNlZF9ieV9wb3NfMnNpZ21hX3Byb3RlaW5faW5mbyA8LSBwcm90ZWluX2luZm9fMUgxVCAlPiUKICBkcGx5cjo6cmVuYW1lKHBvc2l0aW9uPXBvcykgJT4lCiAgZmlsdGVyKHBvc2l0aW9uICVpbiUgR09GX2ZpdG5lc3NfY29sbGFwc2VkX2J5X3Bvc18yc2lnbWEkcG9zaXRpb24pICU+JQogIHJpZ2h0X2pvaW4oR09GX2ZpdG5lc3NfY29sbGFwc2VkX2J5X3Bvc18yc2lnbWEsYnk9InBvc2l0aW9uIikgJT4lCiAgYXJyYW5nZShwb3NpdGlvbikKcHJpbnQoR09GX2ZpdG5lc3NfY29sbGFwc2VkX2J5X3Bvc18yc2lnbWFfcHJvdGVpbl9pbmZvKQpgYGAKCmBgYHtyfQpyc2FfdnNfY29uc19wbG90IDwtIGdncGxvdChHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zXzJzaWdtYV9wcm90ZWluX2luZm8sCiAgICAgICAgICAgICBhZXMoeD1jb25zLCB5PVJTQSwgY29sb3I9YXMuZmFjdG9yKHBvc2l0aW9uKSwgZmlsbD1hcy5mYWN0b3IocG9zaXRpb24pLCBzaGFwZT1hcy5mYWN0b3IocG9zaXRpb24pKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC45LCBzaXplPTQsIHN0cm9rZT0xKSArCiAgbGFicyh4ID0gIlNpdGUgQ29uc2VydmF0aW9uIiwgeSA9IlJlbGF0aXZlIFNvbHZlbnQgQWNjZXNzaWJpbGl0eSIsIGNvbG9yPSJSZXNpZHVlIiwgZmlsbD0iUmVzaWR1ZSIsIHNoYXBlPSJSZXNpZHVlIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lID0gIlJlc2lkdWUiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCJyZWQiLCAiZ3JlZW4iLCAiYmx1ZSIsICJwdXJwbGUiLCAib3JhbmdlIiwgImN5YW4iLCAibWFnZW50YSIpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICJSZXNpZHVlIiwKICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCJyZWQiLCAiZ3JlZW4iLCAiYmx1ZSIsICJwdXJwbGUiLCAib3JhbmdlIiwgImN5YW4iLCAibWFnZW50YSIpKSArCiAgc2NhbGVfc2hhcGVfbWFudWFsKG5hbWUgPSAiUmVzaWR1ZSIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoMjEsIDIyLCAyMywgMjQsIDI1LCAyMSwgMjIpKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKQoKcnNhX3ZzX2NvbnNfcGxvdApgYGAKCmBgYHtyIGVjaG89RkFMU0V9Cmdnc2F2ZShmaWxlbmFtZSA9ICJHT0YvUExPVFMvQ29tcC9HT0ZfUlNBX3ZzX0NvbnNfQ29tcGxlbWVudGF0aW9uLnBkZiIsIAogICAgICAgcGxvdCA9IHJzYV92c19jb25zX3Bsb3QsCiAgICAgICB3aWR0aCA9IDQuNSwgaGVpZ2h0ID0gNC41LCB1bml0cyA9ICJpbiIpCmBgYAoKRXh0cmFjdCB0aGUgZml0bmVzcyB2YWx1ZXMgZm9yIGVhY2ggc2lnbmlmaWNhbnQgYWEgcG9zaXRpb24gZnJvbSB0aGUgQk1TIGFuYWx5c2lzOgpgYGB7ciBjbGFzcy5vdXRwdXQ9Imdvb2RDb2RlIn0KQk1TX21hdHJpeF9wZXJmZWN0c19hbmRfMV9tZWx0X0dPRm9ubHkgPC0gQk1TX21hdHJpeDE1X3BlcmZlY3RzX2FuZF8xX21lbHQgJT4lCiAgZmlsdGVyKFgxICE9ICJYIikgJT4lCiAgZmlsdGVyKFgyID09IDE3IHwgWDIgPT0gOTcgfCBYMiA9PSA5OCB8IFgyID09IDEwMiB8IFgyID09IDEwMyB8IFgyID09IDEwNCB8IFgyID09IDEwNykKCkJNU19tYXRyaXhfcGVyZmVjdHNfYW5kXzFfbWVsdF9HT0Zvbmx5JG11dHBvc251bSA8LSAwCkJNU19tYXRyaXhfcGVyZmVjdHNfYW5kXzFfbWVsdF9HT0Zvbmx5JG11dHBvc251bVt3aGljaChCTVNfbWF0cml4X3BlcmZlY3RzX2FuZF8xX21lbHRfR09Gb25seSRYMj09MTcpXSA8LSAxCkJNU19tYXRyaXhfcGVyZmVjdHNfYW5kXzFfbWVsdF9HT0Zvbmx5JG11dHBvc251bVt3aGljaChCTVNfbWF0cml4X3BlcmZlY3RzX2FuZF8xX21lbHRfR09Gb25seSRYMj09OTcpXSA8LSAyCkJNU19tYXRyaXhfcGVyZmVjdHNfYW5kXzFfbWVsdF9HT0Zvbmx5JG11dHBvc251bVt3aGljaChCTVNfbWF0cml4X3BlcmZlY3RzX2FuZF8xX21lbHRfR09Gb25seSRYMj09OTgpXSA8LSAzCkJNU19tYXRyaXhfcGVyZmVjdHNfYW5kXzFfbWVsdF9HT0Zvbmx5JG11dHBvc251bVt3aGljaChCTVNfbWF0cml4X3BlcmZlY3RzX2FuZF8xX21lbHRfR09Gb25seSRYMj09MTAyKV0gPC0gNApCTVNfbWF0cml4X3BlcmZlY3RzX2FuZF8xX21lbHRfR09Gb25seSRtdXRwb3NudW1bd2hpY2goQk1TX21hdHJpeF9wZXJmZWN0c19hbmRfMV9tZWx0X0dPRm9ubHkkWDI9PTEwMyldIDwtIDUKQk1TX21hdHJpeF9wZXJmZWN0c19hbmRfMV9tZWx0X0dPRm9ubHkkbXV0cG9zbnVtW3doaWNoKEJNU19tYXRyaXhfcGVyZmVjdHNfYW5kXzFfbWVsdF9HT0Zvbmx5JFgyPT0xMDQpXSA8LSA2CkJNU19tYXRyaXhfcGVyZmVjdHNfYW5kXzFfbWVsdF9HT0Zvbmx5JG11dHBvc251bVt3aGljaChCTVNfbWF0cml4X3BlcmZlY3RzX2FuZF8xX21lbHRfR09Gb25seSRYMj09MTA3KV0gPC0gNwoKQk1TX21hdHJpeF9wZXJmZWN0c19hbmRfMV9tZWx0X0dPRm9ubHlfd251bSA8LSBCTVNfbWF0cml4X3BlcmZlY3RzX2FuZF8xX21lbHRfR09Gb25seSAlPiUKICBpbm5lcl9qb2luKEJNU19tYXRyaXgxNV9wZXJmZWN0c19hbmRfMV9udW1fbWVsdCxieT1jKCJYMSIsIlgyIikpICU+JQogIGRwbHlyOjpyZW5hbWUobXV0bnVtPXZhbHVlLnksdmFsdWU9dmFsdWUueCkKCm5hbWVzKEJNU19tYXRyaXhfcGVyZmVjdHNfYW5kXzFfbWVsdF9HT0Zvbmx5X3dudW0pCmBgYAoKRGV0ZXJtaW5lIHRoZSBtaW5pbXVtIGFuZCBtYXhpbXVtIGZpdG5lc3MgdmFsdWVzIGZvciBwbG90dGluZzoKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9Cm1pbihCTVNfbWF0cml4X3BlcmZlY3RzX2FuZF8xX21lbHRfR09Gb25seSR2YWx1ZSwgbmEucm0gPSBUUlVFKQptYXgoQk1TX21hdHJpeF9wZXJmZWN0c19hbmRfMV9tZWx0X0dPRm9ubHkkdmFsdWUsIG5hLnJtID0gVFJVRSkKYGBgCgpQbG90IHRoZSBmaXRuZXNzIHZhbHVlcyBvZiB0aGUgc2lnbmlmaWNhbnQgYWEgcG9zaXRpb25zIGJhc2VkIG9uIHRoZSBCTVMgYW5hbHlzaXMgZm9yIENvbXBsZW1lbnRhdGlvbi4gQmxhY2sgcmVjdGFuZ2xlcyBpbmRpY2F0ZSB0aGUgYWEgY29ycmVzcG9uZGluZyB0byB0aGUgV1QgREhGUiBob21vbG9nLiBXaGl0ZSByZWN0YW5nbGVzIGluZGljYXRlIHRoZSBhYSB3aXRoIHRoZSBoaWdoZXN0IG51bWJlciBvZiBtdXRhbnRzIGZvciBlYWNoIHBvc2l0aW9uIGFsb25nIHRoZSBwcm90ZWluIHNlcXVlbmNlLgpgYGB7cn0KIyBEZWZpbmUgdGhlIG9yZGVyIG9mIGFtaW5vIGFjaWRzIGZvciB0aGUgYmxhY2sgcmVjdGFuZ2xlcwpyZWN0X29yZGVyIDwtIGMoIkUiLCAiRyIsICJSIiwgIlEiLCAiRiIsICJMIiwgIkEiKQoKIyBDcmVhdGUgYSBkYXRhIGZyYW1lIGZvciB0aGUgYmxhY2sgcmVjdGFuZ2xlcwpyZWN0X2RhdGEgPC0gZGF0YS5mcmFtZSgKICBhYW51bSA9IG1hdGNoKHJlY3Rfb3JkZXIsIGMoIksiLCJSIiwiSCIsIkUiLCJEIiwiTiIsIlEiLCJUIiwiUyIsIkMiLCJHIiwiQSIsIlYiLCJMIiwiSSIsIk0iLCJQIiwiWSIsIkYiLCJXIikpLAogIHhtaW4gPSBzZXEoMC41LCBieSA9IDEsIGxlbmd0aC5vdXQgPSBsZW5ndGgocmVjdF9vcmRlcikpLAogIHhtYXggPSBzZXEoMS41LCBieSA9IDEsIGxlbmd0aC5vdXQgPSBsZW5ndGgocmVjdF9vcmRlcikpKQoKIyBGaW5kIHRoZSBhbWlubyBhY2lkIHdpdGggdGhlIGhpZ2hlc3QgbXV0bnVtIGZvciBlYWNoIHBvc2l0aW9uCmhpZ2hlc3RfbXV0bnVtIDwtIEJNU19tYXRyaXhfcGVyZmVjdHNfYW5kXzFfbWVsdF9HT0Zvbmx5X3dudW0gJT4lCiAgZ3JvdXBfYnkobXV0cG9zbnVtKSAlPiUKICBzbGljZV9tYXgob3JkZXJfYnkgPSBhcy5udW1lcmljKG11dG51bSksIG4gPSAxKSAlPiUKICB1bmdyb3VwKCkKCiMgQ3JlYXRlIHRoZSBwbG90CkJNU19Hb0ZfZml0X3Bsb3QgPC0gZ2dwbG90KEJNU19tYXRyaXhfcGVyZmVjdHNfYW5kXzFfbWVsdF9HT0Zvbmx5X3dudW0sIAogICAgICAgYWVzKHg9bXV0cG9zbnVtLCB5PWFhbnVtLAogICAgICAgICAgIGZpbGw9dmFsdWUsCiAgICAgICAgICAgbGFiZWw9bXV0bnVtKSkgKwogIGdlb21fdGlsZSgpICsKICBnZW9tX3RleHQoKSArCiAgIyBBZGQgYmxhY2sgcmVjdGFuZ2xlcwogIGdlb21fcmVjdChkYXRhID0gcmVjdF9kYXRhLAogICAgICAgICAgICBhZXMoeG1pbiA9IHhtaW4sIHhtYXggPSB4bWF4LCB5bWluID0gYWFudW0gLSAwLjUsIHltYXggPSBhYW51bSArIDAuNSksCiAgICAgICAgICAgIGZpbGwgPSBOQSwgY29sb3IgPSAiYmxhY2siLCBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgIyBBZGQgd2hpdGUgcmVjdGFuZ2xlcyBhcm91bmQgdGhlIGhpZ2hlc3QgbXV0bnVtCiAgZ2VvbV9yZWN0KGRhdGEgPSBoaWdoZXN0X211dG51bSwKICAgICAgICAgICAgYWVzKHhtaW4gPSBtdXRwb3NudW0gLSAwLjUsIHhtYXggPSBtdXRwb3NudW0gKyAwLjUsIAogICAgICAgICAgICAgICAgeW1pbiA9IGFhbnVtIC0gMC41LCB5bWF4ID0gYWFudW0gKyAwLjUpLAogICAgICAgICAgICBmaWxsID0gTkEsIGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDEsIGluaGVyaXQuYWVzID0gRkFMU0UpICsKICBsYWJzKHggPSAiUG9zaXRpb24gKGFhKSIsCiAgICAgICB5ID0iQW1pbm8gYWNpZCIsIGNvbG9yPSIiKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gImJsdWUiLCBoaWdoID0gInJlZCIsIG1pZD0iZ29sZCIsCiAgICAgICAgICAgICAgICAgICAgICAgbmFtZT0iRml0bmVzcyIsIG5hLnZhbHVlPSJncmV5IiwgCiAgICAgICAgICAgICAgICAgICAgICAgbGltaXQgPSBjKC0zLDEpKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZT0iUG9zaXRpb24gKGFhKSIsIAogICAgICAgICAgICAgICAgICAgICBicmVha3M9YygxLDIsMyw0LDUsNiw3KSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIjE3IiwiOTciLCI5OCIsIjEwMiIsIjEwMyIsIjEwNCIsIjEwNyIpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWU9IkFtaW5vIGFjaWQiLCAKICAgICAgICAgICAgICAgICAgICAgYnJlYWtzPWMoMSwyLDMsNCw1LDYsNyw4LDksMTAsMTEsMTIsMTMsMTQsMTUsMTYsMTcsMTgsMTksMjApLAogICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiSyIsIlIiLCJIIiwiRSIsIkQiLCJOIiwiUSIsIlQiLCJTIiwiQyIsIkciLCJBIiwiViIsIkwiLCJJIiwiTSIsIlAiLCJZIiwiRiIsIlciKSkKCnByaW50KEJNU19Hb0ZfZml0X3Bsb3QpCmBgYAoKYGBge3IgZWNobz1GQUxTRX0KZ2dzYXZlKGZpbGVuYW1lID0gIkdPRi9QTE9UUy9Db21wL0dPRl9CTVNfRml0bmVzc19OdW1iZXJfTXV0YW50c19Db21wbGVtZW50YXRpb24ucG5nIiwgCiAgICAgICBwbG90ID0gQk1TX0dvRl9maXRfcGxvdCwKICAgICAgIHdpZHRoID0gNC41LCBoZWlnaHQgPSA0LjUsIHVuaXRzID0gImluIikKYGBgCgojIyBNSUMgKDAuNSB1Zy9tTCBUTVApCgojIyMgRHJvcG91dCBQZXJmZWN0cwoKUmV0cmlldmluZyBhbGwgZHJvcG91dCBwZXJmZWN0cyB3aXRoIGEgbG9nLWZvbGQgY2hhbmdlIHZhbHVlIGxlc3MgdGhhbiAtMS4wIGZyb20gdGhlICoqQ09NUExFTUVOVEFUSU9OKiogZGF0YXNldC4gVGhlbiwgcmV0cmlldmUgdGhlIHNhbWUgcGVyZmVjdHMgKElEKSBmcm9tIHRoZSAqKk1JQyoqIGRhdGFzZXQgYW5kIGFsbCBjb3JyZXNwb25kaW5nIG11dGFudHMuIFVzZSB0aGUgYG11dF9jb2xsYXBzZV8xNWAgZGF0YXNldCB3aGljaCBpbmNsdWRlcyA3OTcgcGVyZmVjdHMgKG11dGF0aW9ucyA9IDA7IG51bXBydW5lZEJDcyA9IDUpIGFuZCAxMiwxNzQgbXV0YW50cyB3aXRoIHVwIHRvIDUgQUEgZGlzdGFuY2UgYW5kIGF0IGxlYXN0IDEgQkMgKG51bXBydW5lZEJDcyA9IDEpIG1hdGNoaW5nIHRvIGEgcGVyZmVjdCB2YXJpYW50IGluIHRoZSBkYXRhc2V0LgpgYGB7cn0KIyBTdGVwIDE6IElkZW50aWZ5IElEcyB0aGF0IGhhdmUgcm93cyB3aGVyZSBtdXRhdGlvbnMgPT0gMCBhbmQgZml0RDA1RDAzIDwgLTEuMCBpbiBDT01QTEVNRU5UQVRJT04KZHJvcG91dDE1X2lkc193aXRoX3plcm9fbXV0YXRpb25zX2NvbXBsZW1lbnQgPC0gbXV0X2NvbGxhcHNlXzE1ICU+JQogIGZpbHRlcihtdXRhdGlvbnMgPT0gMCAmIGZpdEQwNUQwMyA+IC0xLjApICU+JQogIGRpc3RpbmN0KElEKSAlPiUKICBwdWxsKElEKQoKIyBTdGVwIDI6IElkZW50aWZ5IHRoZSBzYW1lIElEcyB0aGF0IGhhdmUgbXV0YXRpb25zID09IDAgYW5kIGZpdEQwN0QwMyA8IC0xLjAgaW4gTUlDCmRyb3BvdXQxNV9pZHNfd2l0aF96ZXJvX211dGF0aW9uc19taWMgPC0gbXV0X2NvbGxhcHNlXzE1ICU+JQogIGZpbHRlcihJRCAlaW4lIGRyb3BvdXQxNV9pZHNfd2l0aF96ZXJvX211dGF0aW9uc19jb21wbGVtZW50ICYgCiAgICAgICAgIG11dGF0aW9ucyA9PSAwICYgCiAgICAgICAgIGZpdEQwN0QwMyA8IC0xLjApICU+JQogIGRpc3RpbmN0KElEKSAlPiUKICBwdWxsKElEKQoKIyBTdGVwIDM6IFJldHJpZXZlIHRoZSByb3dzIGZvciB0aGVzZSBJRHMKcmVzdWx0X3Jvd3MgPC0gbXV0X2NvbGxhcHNlXzE1ICU+JQogIGZpbHRlcihJRCAlaW4lIGRyb3BvdXQxNV9pZHNfd2l0aF96ZXJvX211dGF0aW9uc19taWMgJiBtdXRhdGlvbnMgPT0gMCkKCiMgU3RlcCA0OiBGaWx0ZXIgdGhlIG1haW4gZGF0YXNldCB0byBrZWVwIG11dGFudHMgKHdpdGggMS1BQSkgaWYgdGhleSBtYXRjaCBhIGNvcnJlc3BvbmRpbmcgcGVyZmVjdCBJRApkcm9wb3V0X211dGFudHMxNV9HT0ZfbWljIDwtIG11dF9jb2xsYXBzZV8xNSAlPiUKICBmaWx0ZXIoCiAgICAobXV0YXRpb25zID09IDAgJiAhaXMubmEoZml0RDA1RDAzKSAmIGZpdEQwNUQwMyA+IC0xLjAgJiAKICAgICAhaXMubmEoZml0RDA3RDAzKSAmIGZpdEQwN0QwMyA8IC0xLjApIHwKICAgIChtdXRhdGlvbnMgIT0gMCAmIGZpdEQwN0QwMyA+IC0xLjAgJiBJRCAlaW4lIGRyb3BvdXQxNV9pZHNfd2l0aF96ZXJvX211dGF0aW9uc19taWMpKSAlPiUKICBkcGx5cjo6c2VsZWN0KElELCBtdXRJRCwgbnVtcHJ1bmVkQkNzLCBtdXRhdGlvbnMsIGZpdEQwNUQwMywgZml0RDA3RDAzLCBzZXEpCmBgYAoKVmFsaWRhdGUgdGhhdCByb3dzIHdoZXJlIG11dGF0aW9ucyAhPSAwIGhhdmUgYW4gSUQgdGhhdCBtYXRjaGVzIHJvd3Mgd2hlcmUgbXV0YXRpb25zID09IDAgYW5kIGZpdEQwNUQwMyA8IC0xLjAuCmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIFZlcmlmaWNhdGlvbiBzdGVwCnZlcmlmaWNhdGlvbl9yZXN1bHRfbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWMgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAwKSAlPiUKICBtdXRhdGUoCiAgICBjb25kaXRpb25fbWV0ID0gZml0RDA1RDAzID4gLTEuMCAmIGZpdEQwN0QwMyA8IC0xLAogICAgZml0RDA1RDAzX2NoZWNrID0gZml0RDA1RDAzID4gLTEuMCwKICAgIGZpdEQwN0QwM19jaGVjayA9IGZpdEQwN0QwMyA8IC0xCiAgKQoKIyBDaGVjayBpZiBhbGwgcm93cyBtZWV0IHRoZSBjb25kaXRpb24KYWxsX2NvbmRpdGlvbnNfbWV0X21pYyA8LSBhbGwodmVyaWZpY2F0aW9uX3Jlc3VsdF9taWMkY29uZGl0aW9uX21ldCkKCiMgU3VtbWFyeSBvZiB0aGUgdmVyaWZpY2F0aW9uCnZlcmlmaWNhdGlvbl9zdW1tYXJ5X21pYyA8LSB2ZXJpZmljYXRpb25fcmVzdWx0X21pYyAlPiUKICBzdW1tYXJpc2UoCiAgICB0b3RhbF9yb3dzID0gbigpLAogICAgcm93c19tZWV0aW5nX2JvdGhfY29uZGl0aW9ucyA9IHN1bShjb25kaXRpb25fbWV0KSwKICAgIHJvd3NfbWVldGluZ19maXREMDVEMDMgPSBzdW0oZml0RDA1RDAzX2NoZWNrKSwKICAgIHJvd3NfbWVldGluZ19maXREMDdEMDMgPSBzdW0oZml0RDA3RDAzX2NoZWNrKQogICkKCiMgUHJpbnQgcmVzdWx0cwpwcmludCgiVmVyaWZpY2F0aW9uIFN1bW1hcnk6IikKcHJpbnQodmVyaWZpY2F0aW9uX3N1bW1hcnlfbWljKQpwcmludChwYXN0ZSgiQWxsIGNvbmRpdGlvbnMgbWV0OiIsIGFsbF9jb25kaXRpb25zX21ldF9taWMpKQoKIyBJZiB0aGVyZSBhcmUgYW55IHJvd3Mgbm90IG1lZXRpbmcgdGhlIGNvbmRpdGlvbnMsIGRpc3BsYXkgdGhlbQppZiAoIWFsbF9jb25kaXRpb25zX21ldF9taWMpIHsKICBwcmludCgiUm93cyBub3QgbWVldGluZyBib3RoIGNvbmRpdGlvbnM6IikKICBwcmludCh2ZXJpZmljYXRpb25fcmVzdWx0X21pYyAlPiUgZmlsdGVyKCFjb25kaXRpb25fbWV0KSAlPiUgc2VsZWN0KElELCBmaXREMDVEMDMsIGZpdEQwN0QwMykpCn0KYGBgCgpWYWxpZGF0ZSB0aGF0IHJvd3Mgd2hlcmUgbXV0YXRpb25zICE9IDAgaGF2ZSBhbiBJRCB0aGF0IG1hdGNoZXMgcm93cyB3aGVyZSBtdXRhdGlvbnMgPT0gMCBhbmQgZml0RDA3RDAzIDwgLTEuMC4KYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CiMgMS4gRmlyc3QsIGNyZWF0ZSB0d28gc3Vic2V0cyBvZiB0aGUgZGF0YQp6ZXJvX211dGF0aW9uX3Jvd3NfbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWMgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAwICYgZml0RDA3RDAzIDwgLTEuMCkKCm5vbl96ZXJvX211dGF0aW9uX3Jvd3NfbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWMgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyAhPSAwICYgZml0RDA3RDAzID4gLTEuMCkKCiMgMi4gQ2hlY2sgdGhhdCBhbGwgSURzIGluIG5vbl96ZXJvX211dGF0aW9uX3Jvd3MgYXJlIHByZXNlbnQgaW4gemVyb19tdXRhdGlvbl9yb3dzCmFsbF92YWxpZF9pZHNfbWljIDwtIGFsbChub25femVyb19tdXRhdGlvbl9yb3dzX21pYyRJRCAlaW4lIHplcm9fbXV0YXRpb25fcm93c19taWMkSUQpCnByaW50KHBhc3RlKCJBbGwgbm9uLXplcm8gbXV0YXRpb24gcm93cyBoYXZlIGEgbWF0Y2hpbmcgemVybyBtdXRhdGlvbiByb3c6IiwgYWxsX3ZhbGlkX2lkc19taWMpKQoKIyAzLiBJZiB0aGUgYWJvdmUgaXMgRkFMU0UsIGZpbmQgdGhlIHByb2JsZW1hdGljIElEcwppZiAoIWFsbF92YWxpZF9pZHNfbWljKSB7CiAgcHJvYmxlbWF0aWNfaWRzX21pYyA8LSBzZXRkaWZmKG5vbl96ZXJvX211dGF0aW9uX3Jvd3NfbWljJElELCB6ZXJvX211dGF0aW9uX3Jvd3NfbWljJElEKQogIHByaW50KCJJRHMgd2l0aCBub24temVybyBtdXRhdGlvbnMgYnV0IG5vIG1hdGNoaW5nIHplcm8gbXV0YXRpb24gcm93OiIpCiAgcHJpbnQocHJvYmxlbWF0aWNfaWRzX21pYykKfQoKIyA0LiBDaGVjayBmb3IgYW55IElEcyBpbiB6ZXJvX211dGF0aW9uX3Jvd3MgdGhhdCBkb24ndCBoYXZlIGEgY29ycmVzcG9uZGluZyBub24temVybyBtdXRhdGlvbiByb3cKaWRzX3dpdGhvdXRfbm9uX3plcm9fbWljIDwtIHNldGRpZmYoemVyb19tdXRhdGlvbl9yb3dzX21pYyRJRCwgbm9uX3plcm9fbXV0YXRpb25fcm93c19taWMkSUQpCnByaW50KCJJRHMgd2l0aCB6ZXJvIG11dGF0aW9ucyBidXQgbm8gY29ycmVzcG9uZGluZyBub24temVybyBtdXRhdGlvbiByb3dzOiIpCnByaW50KGlkc193aXRob3V0X25vbl96ZXJvX21pYykKCiMgNS4gU3VtbWFyeSBzdGF0aXN0aWNzCnByaW50KHBhc3RlKCJOdW1iZXIgb2YgdW5pcXVlIElEcyBpbiB6ZXJvIG11dGF0aW9uIHJvd3M6Iiwgbl9kaXN0aW5jdCh6ZXJvX211dGF0aW9uX3Jvd3NfbWljJElEKSkpCnByaW50KHBhc3RlKCJOdW1iZXIgb2YgdW5pcXVlIElEcyBpbiBub24temVybyBtdXRhdGlvbiByb3dzOiIsIG5fZGlzdGluY3Qobm9uX3plcm9fbXV0YXRpb25fcm93c19taWMkSUQpKSkKCiMgNi4gRGlzdHJpYnV0aW9uIG9mIG11dGF0aW9uIGNvdW50cyBmb3Igbm9uLXplcm8gbXV0YXRpb24gcm93cwptdXRhdGlvbl9kaXN0cmlidXRpb25fbWljIDwtIG5vbl96ZXJvX211dGF0aW9uX3Jvd3NfbWljICU+JQogIGdyb3VwX2J5KG11dGF0aW9ucykgJT4lCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUKICBhcnJhbmdlKG11dGF0aW9ucykKCnByaW50KCJEaXN0cmlidXRpb24gb2YgbXV0YXRpb24gY291bnRzOiIpCnByaW50KG11dGF0aW9uX2Rpc3RyaWJ1dGlvbl9taWMpCgojIDcuIENoZWNrIGZvciBhbnkgdW5leHBlY3RlZCBtdXRhdGlvbiB2YWx1ZXMKdW5leHBlY3RlZF9tdXRhdGlvbnNfbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWMgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA8IDAgfCBtdXRhdGlvbnMgPiA1KSAgIyBBZGp1c3QgdGhlIHVwcGVyIGJvdW5kIGFzIG5lZWRlZAoKaWYgKG5yb3codW5leHBlY3RlZF9tdXRhdGlvbnNfbWljKSA+IDApIHsKICBwcmludCgiUm93cyB3aXRoIHVuZXhwZWN0ZWQgbXV0YXRpb24gdmFsdWVzOiIpCiAgcHJpbnQodW5leHBlY3RlZF9tdXRhdGlvbnNfbWljKQp9IGVsc2UgewogIHByaW50KCJObyB1bmV4cGVjdGVkIG11dGF0aW9uIHZhbHVlcyBmb3VuZC4iKQp9CmBgYAoKUmVtb3ZlIHVuaXF1ZSBwZXJmZWN0IElEcyBpZiB0aGVyZSBpcyBubyBjb3JyZXNwb25kaW5nIG11dGFudHM6CmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIFN0ZXAgMTogSWRlbnRpZnkgSURzIHdpdGggemVybyBtdXRhdGlvbnMKemVyb19tdXRhdGlvbl9pZHNfbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWMgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAwICYgZml0RDA3RDAzIDwgLTEuMCkgJT4lCiAgcHVsbChJRCkKCiMgU3RlcCAyOiBJZGVudGlmeSBJRHMgd2l0aCBub24temVybyBtdXRhdGlvbnMKbm9uX3plcm9fbXV0YXRpb25faWRzX21pYyA8LSBkcm9wb3V0X211dGFudHMxNV9HT0ZfbWljICU+JQogIGZpbHRlcihtdXRhdGlvbnMgIT0gMCAmIGZpdEQwN0QwMyA+IC0xLjApICU+JQogIHB1bGwoSUQpCgojIFN0ZXAgMzogRmluZCBJRHMgdGhhdCBoYXZlIHplcm8gbXV0YXRpb25zIGJ1dCBubyBjb3JyZXNwb25kaW5nIG5vbi16ZXJvIG11dGF0aW9uIHJvd3MKaWRzX3RvX3JlbW92ZV9taWMgPC0gc2V0ZGlmZih6ZXJvX211dGF0aW9uX2lkc19taWMsIG5vbl96ZXJvX211dGF0aW9uX2lkc19taWMpCgojIFN0ZXAgNDogUmVtb3ZlIHRoZSByb3dzIHdpdGggdGhlc2UgSURzCmRyb3BvdXRfbXV0YW50czE1X0dPRl9taWNfY2xlYW5lZCA8LSBkcm9wb3V0X211dGFudHMxNV9HT0ZfbWljICU+JQogIGZpbHRlcighKElEICVpbiUgaWRzX3RvX3JlbW92ZV9taWMpKQoKIyBQcmludCBzdW1tYXJ5CnByaW50KHBhc3RlKCJOdW1iZXIgb2Ygcm93cyBiZWZvcmUgY2xlYW5pbmc6IiwgbnJvdyhkcm9wb3V0X211dGFudHMxNV9HT0ZfbWljKSkpCnByaW50KHBhc3RlKCJOdW1iZXIgb2Ygcm93cyBhZnRlciBjbGVhbmluZzoiLCBucm93KGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWNfY2xlYW5lZCkpKQpwcmludChwYXN0ZSgiTnVtYmVyIG9mIHJvd3MgcmVtb3ZlZDoiLCBucm93KGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWMpIC0gbnJvdyhkcm9wb3V0X211dGFudHMxNV9HT0ZfbWljX2NsZWFuZWQpKSkKcHJpbnQocGFzdGUoIk51bWJlciBvZiB1bmlxdWUgSURzIHJlbW92ZWQ6IiwgbGVuZ3RoKGlkc190b19yZW1vdmVfbWljKSkpCgojIE9wdGlvbmFsbHksIHlvdSBjYW4gcHJpbnQgdGhlIHJlbW92ZWQgSURzCnByaW50KCJJRHMgcmVtb3ZlZDoiKQpwcmludChpZHNfdG9fcmVtb3ZlX21pYykKCiMgQXNzaWduIHRoZSBjbGVhbmVkIGRhdGEgYmFjayB0byBkcm9wb3V0X211dGFudHMxNV9HT0YgaWYgeW91IHdhbnQgdG8gdXBkYXRlIHRoZSBvcmlnaW5hbCB2YXJpYWJsZQpkcm9wb3V0X211dGFudHMxNV9HT0ZfbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWNfY2xlYW5lZApgYGAKCiMjIyBEcm9wb3V0IE11dGFudHMKU3VtbWFyaXplIHRoZSBudW1iZXIgb2YgcGVyZmVjdHMgYW5kIG11dGFudHMgYXQgZWFjaCBBQSBkaXN0YW5jZSBhZnRlciBmaWx0ZXJpbmc6CmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIENyZWF0ZSBhIGZ1bmN0aW9uIHRvIGNvdW50IHVuaXF1ZSBtdXRJRHMgZm9yIGEgZ2l2ZW4gbnVtYmVyIG9mIG11dGF0aW9ucwpkcm9wb3V0X211dGFudHMxNV9HT0ZfY291bnRfbWljIDwtIGZ1bmN0aW9uKGRhdGEsIG11dGF0aW9uX2NvdW50KSB7CiAgbGVuZ3RoKHVuaXF1ZShzdWJzZXQoZGF0YSwgbXV0YXRpb25zID09IG11dGF0aW9uX2NvdW50KSRtdXRJRCkpCn0KCiMgQ3JlYXRlIGEgdmVjdG9yIG9mIGNvdW50cyBmb3IgbXV0YXRpb25zIDEtNQpkcm9wb3V0MTVfY291bnRzX21pYyA8LSBzYXBwbHkoMTo1LCBmdW5jdGlvbih4KSBkcm9wb3V0X211dGFudHMxNV9HT0ZfY291bnRfbWljKGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWMsIHgpKQoKIyBDb3VudCBwZXJmZWN0cyBzZXBhcmF0ZWx5CnBlcmZlY3RzX2NvdW50X21pYyA8LSBsZW5ndGgodW5pcXVlKHN1YnNldChkcm9wb3V0X211dGFudHMxNV9HT0ZfbWljLCBtdXRhdGlvbnMgPT0gMCAmIGZpdEQwN0QwMyA8IC0xLjApJG11dElEKSkKCiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSB3aXRoIHRoZSByZXN1bHRzLCBpbmNsdWRpbmcgdGhlIHN1bW1hcnkgcm93CmRyb3BvdXRfbXV0YW50czE1X0dPRl90YWJsZV9taWMgPC0gZGF0YS5mcmFtZSgKICBNdXRhdGlvbnMgPSBjKCJQZXJmZWN0cyAoZml0IDwgLTEuMCkiLCAiMSBNdXRhdGlvbiIsICIyIE11dGF0aW9ucyIsICIzIE11dGF0aW9ucyIsICI0IE11dGF0aW9ucyIsICI1IE11dGF0aW9ucyIsICJUb3RhbCBNdXRhdGlvbnMiKSwKICBDb3VudCA9IGMocGVyZmVjdHNfY291bnRfbWljLCBkcm9wb3V0MTVfY291bnRzX21pYywgc3VtKGRyb3BvdXQxNV9jb3VudHNfbWljKSkKKQoKIyBQcmludCB0aGUgdGFibGUKcHJpbnQoZHJvcG91dF9tdXRhbnRzMTVfR09GX3RhYmxlX21pYykKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQojIFNhdmUgYXMgLmNzdiBmb3IgdXNlIGluIG90aGVyIFJNRCBmaWxlczoKd3JpdGUuY3N2KGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWMsICdHT0YvT1VUUFVUL01JQy9MaWIxNS5EMDdEMDMuZ29mLjFudW1wcnVuZWRCQ3MuNUFBLm11dHMuZml0bmVzcy0xLmNzdicsCiAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSwgcXVvdGU9RkFMU0UpCmBgYAoKIyMjIEdPRiBGaXRuZXNzCgoqKkdPRiBGaXRuZXNzOioqIFNlcGFyYXRlIHRoZSBgZHJvcG91dF9tdXRhbnRzMTVfR09GX21pY2AgZGF0YXNldCBpbnRvIHR3byBuZXcgZGF0YWZyYW1lcywgd2hlcmUgREYxIGNvbnRhaW5zIHBlcmZlY3RzIGFuZCBERjIgY29udGFpbnMgbXV0YW50czoKYGBge3J9CiMgQ3JlYXRlIGEgZGF0YWZyYW1lIHdpdGggbXV0YXRpb25zID09IDAKZHJvcG91dF9tdXRhbnRzMTVfR09GX25vX211dGF0aW9uc19taWMgPC0gZHJvcG91dF9tdXRhbnRzMTVfR09GX21pY1tkcm9wb3V0X211dGFudHMxNV9HT0ZfbWljJG11dGF0aW9ucyA9PSAwLCBdCgojIENyZWF0ZSBhIGRhdGFmcmFtZSB3aXRoIG11dGF0aW9ucyAhPSAwCmRyb3BvdXRfbXV0YW50czE1X0dPRl93aXRoX211dGF0aW9uc19taWMgPC0gZHJvcG91dF9tdXRhbnRzMTVfR09GX21pY1tkcm9wb3V0X211dGFudHMxNV9HT0ZfbWljJG11dGF0aW9ucyAhPSAwLCBdCmBgYAoKTm93LCByZS1jb21iaW5lIHRoZXNlIGRhdGFmcmFtZXMgdG8gcmV0YWluIG9ubHkgdGhlIG11dGFudHMKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CiMgU3RlcCAxOiBQcmVwYXJlIHRoZSByZWZlcmVuY2UgZGF0YWZyYW1lCmRmX3JlZmVyZW5jZV9taWMgPC0gZHJvcG91dF9tdXRhbnRzMTVfR09GX25vX211dGF0aW9uc19taWMgJT4lCiAgc2VsZWN0KElELCBmaXREMDdEMDMpICU+JQogIHJlbmFtZShyZWZlcmVuY2VfZml0RDA3RDAzID0gZml0RDA3RDAzKQoKIyBTdGVwIDI6IEpvaW4gYW5kIGNhbGN1bGF0ZSB0aGUgZGlmZmVyZW5jZQpkcm9wb3V0X211dGFudHMxNV9HT0ZfZml0bmVzc19taWMgPC0gZHJvcG91dF9tdXRhbnRzMTVfR09GX3dpdGhfbXV0YXRpb25zX21pYyAlPiUKICBsZWZ0X2pvaW4oZGZfcmVmZXJlbmNlX21pYywgYnkgPSAiSUQiKSAlPiUKICBtdXRhdGUoZml0RDA3RDAzID0gZml0RDA3RDAzIC0gcmVmZXJlbmNlX2ZpdEQwN0QwMykgJT4lCiAgc2VsZWN0KElELCBtdXRJRCwgbXV0YXRpb25zLCBmaXREMDdEMDMpCgojIFByaW50IHN1bW1hcnkgc3RhdGlzdGljcwpwcmludChwYXN0ZSgiTnVtYmVyIG9mIE11dGFudHM6IiwgbnJvdyhkcm9wb3V0X211dGFudHMxNV9HT0ZfZml0bmVzc19taWMpKSkKcHJpbnQocGFzdGUoIlVuaXF1ZSBJRHM6IiwgbGVuZ3RoKHVuaXF1ZShkcm9wb3V0X211dGFudHMxNV9HT0ZfZml0bmVzc19taWMkSUQpKSkpCnByaW50KHBhc3RlKCJSYW5nZSBvZiBmaXREMDdEMDM6IiwgCiAgICAgICAgICAgIHBhc3RlKHJvdW5kKHJhbmdlKGRyb3BvdXRfbXV0YW50czE1X0dPRl9maXRuZXNzX21pYyRmaXREMDdEMDMsIG5hLnJtID0gVFJVRSksIDEpLCBjb2xsYXBzZSA9ICIgdG8gIikpKQpgYGAKCioqQm94cGxvdDoqKiBQbG90IG11dGFudCBmaXRuZXNzIHJlbGF0aXZlIHRvIHBhcmVudCB2YXJpYW50IGJ5IG51bWJlciBvZiBtdXRhdGlvbnM6CmBgYHtyfQpHT0ZfbXV0c19maXRuZXNzX2J5X211dHNfcGxvdF9taWMgPC0gZ2dwbG90KGRyb3BvdXRfbXV0YW50czE1X0dPRl9maXRuZXNzX21pYywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IGZhY3RvcihtdXRhdGlvbnMpLCB5ID0gZml0RDA3RDAzKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBsYWJzKHRpdGxlID0gImZpdEQwN0QwMyBieSBOdW1iZXIgb2YgTXV0YXRpb25zIiwgeCA9ICJOdW1iZXIgb2YgTXV0YXRpb25zIiwgeSA9ICJmaXREMDdEMDMiKQoKcHJpbnQoR09GX211dHNfZml0bmVzc19ieV9tdXRzX3Bsb3RfbWljKQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9Cmdnc2F2ZShmaWxlbmFtZSA9ICJHT0YvUExPVFMvTUlDL0dPRi5NSUMuTXV0YW50LkZpdG5lc3MucG5nIiwgCiAgICAgICBwbG90ID1HT0ZfbXV0c19maXRuZXNzX2J5X211dHNfcGxvdF9taWMsCiAgICAgICB3aWR0aCA9IDQuNSwgaGVpZ2h0ID0gNC41LCB1bml0cyA9ICJpbiIpCmBgYAoKKipIaXN0b2dyYW0gb2YgTXV0YW50IEZpdG5lc3M6KiogQ2xlYXJseSBzaG93cyBtdXRhbnQgZml0bmVzcyBpcyBub3JtYWxseSBkaXN0cmlidXRlZC4KYGBge3J9CkdPRl9tdXRzX2ZpdG5lc3NfZGlzdF9wbG90X21pYyA8LSBnZ3Bsb3QoZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3NfbWljLCBhZXMoeCA9IGZpdEQwN0QwMykpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuNSwgZmlsbCA9ICJibHVlIiwgY29sb3IgPSAiYmxhY2siKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgZml0RDA3RDAzIiwgeCA9ICJmaXREMDdEMDMiLCB5ID0gIkNvdW50IikKCnByaW50KEdPRl9tdXRzX2ZpdG5lc3NfZGlzdF9wbG90X21pYykKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQpnZ3NhdmUoZmlsZW5hbWUgPSAiR09GL1BMT1RTL01JQy9HT0YuTUlDLk11dGFudC5GaXRuZXNzLkRpc3RyaWJ1dGlvbi5wbmciLCAKICAgICAgIHBsb3QgPUdPRl9tdXRzX2ZpdG5lc3NfZGlzdF9wbG90X21pYywKICAgICAgIHdpZHRoID0gNC41LCBoZWlnaHQgPSA0LjUsIHVuaXRzID0gImluIikKYGBgCgojIyMgR09GIEFsaWdubWVudAoKKipGQVNUQToqKiBHZW5lcmF0ZSBhIEZBU1RBIGZpbGUgZnJvbSB0aGUgZmlsdGVyZWQgYGRyb3BvdXRfbXV0YW50czE1X0dvRmAgcGVyZmVjdHMgZGF0YXNldCBmb3IgR29GIGFuYWx5c2lzOgpgYGB7cn0KIyBGaXJzdCwgbGV0J3MgZW5zdXJlIHdlIGhhdmUgdGhlIGNvcnJlY3QgdW5pcXVlIElEcyBmb3IgbXV0YXRpb25zID09IDEKZHJvcG91dF9tdXRhbnRzMTVfR09GX21pY18xbXV0X3VuaXF1ZV9pZHMgPC0gZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3NfbWljICU+JQogIGZpbHRlcihtdXRhdGlvbnMgPT0gMSkgJT4lCiAgZGlzdGluY3QoSUQpICU+JQogIHB1bGwoSUQpCgojIE5vdywgbGV0J3MgdXNlIHRoZXNlIElEcyB0byBmaWx0ZXIgdGhlIGRyb3BvdXRfbXV0YW50czE1X0dPRiBkYXRhc2V0CmRyb3BvdXRfbXV0YW50czE1X0dPRl9taWNfMW11dF91bmlxdWVfaWRfc2VxIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWMgJT4lCiAgZmlsdGVyKElEICVpbiUgZHJvcG91dF9tdXRhbnRzMTVfR09GX21pY18xbXV0X3VuaXF1ZV9pZHMgJiBtdXRhdGlvbnMgPT0gMCkgJT4lCiAgc2VsZWN0KElELCBzZXEpCgojIENyZWF0ZSB0aGUgc2VxdWVuY2VzIGluIEZBU1RBIGZvcm1hdApkcm9wb3V0X211dGFudHMxNV9HT0ZfbWljX2Zhc3RhX2NvbnRlbnQgPC0gcGFzdGUoIj4iLCBkcm9wb3V0X211dGFudHMxNV9HT0ZfbWljXzFtdXRfdW5pcXVlX2lkX3NlcSRJRCwgIlxuIiwgZHJvcG91dF9tdXRhbnRzMTVfR09GX21pY18xbXV0X3VuaXF1ZV9pZF9zZXEkc2VxLCAiXG4iLCBzZXAgPSAiIiwgY29sbGFwc2UgPSAiIikKCiMgRGVmaW5lIHRoZSBmaWxlIHBhdGggaW4gdGhlIHdvcmtpbmcgZGlyZWN0b3J5CmRyb3BvdXRfbXV0YW50czE1X0dPRl9taWNfZmFzdGFfZmlsZV9wYXRoIDwtIGZpbGUucGF0aChnZXR3ZCgpLCAiR09GL01TQV9Ecm9wb3V0cy9NSUMvRkFTVEEvTGliMTUuR29GLnBlcmZlY3RzLm1pYy5mYXN0YSIpCgojIFdyaXRlIHRoZSBGQVNUQSBjb250ZW50IHRvIHRoZSBmaWxlICg1NyB1bmlxdWUgSURzKQp3cml0ZUxpbmVzKGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWNfZmFzdGFfY29udGVudCwgCiAgICAgICAgICAgY29uID0gZHJvcG91dF9tdXRhbnRzMTVfR09GX21pY19mYXN0YV9maWxlX3BhdGgpCmBgYAoKKipBbGlnbm1lbnQ6KiogVXNlIHRoZSBgY2x1c3RhbG9gIGV4ZWN1dGFibGUgdG8gYWxpZ24gdGhlIHByb3RlaW4gc2VxdWVuY2VzIGFzc29jaWF0ZWQgd2l0aCB0aGUgZHJvcG91dCBwZXJmZWN0cy4gVGhpcyB3aWxsIGFsaWduIHRoZSBGQVNUQSBmaWxlOiAqKkxpYjE1LkdvRi5wZXJmZWN0cy5jb21wbGVtZW50YXRpb24uZmFzdGEqKiBmb3IgdXNlIGluIEdvRiBhbmFseXNpcy4KYGBge2Jhc2h9Ci4vU2NyaXB0cy9jbHVzdGFsbyAtaSBHT0YvTVNBX0Ryb3BvdXRzL01JQy9GQVNUQS9MaWIxNS5Hb0YucGVyZmVjdHMubWljLmZhc3RhIC1vIEdPRi9NU0FfRHJvcG91dHMvTUlDL0ZBU1RBL0xpYjE1LkdvRi5wZXJmZWN0cy5taWMudHJlZS5hbGlnbmVkLm1vZC5hbG4gLS1vdXRmbXQ9Y2x1c3RhbCAtLWZvcmNlCmBgYAoKKipNYXBwaW5nIFJlc2lkdWVzOioqIFVzZSB0aGUgZm9sbG93aW5nIGBtYXAuYWxpZ25lZC5yZXNpZHVlcy5weWAgcHl0aG9uIHNjcmlwdCB0byBnZW5lcmF0ZSBjc3YgZmlsZXMgZm9yIGVhY2ggZGVzaWduZWQgaG9tb2xvZyB0aGF0IG1hcHMgcmVzaWR1ZSBwb3NpdGlvbnMgb2YgZWFjaCBBLkEuIGZyb20gdGhlIGFsaWdubWVudCBGQVNUQToKYGBge3B5dGhvbn0KaW1wb3J0IHRpbWUKaW1wb3J0IGNzdgoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojSU5QVVRTOgoKYmFzZV9wYXRoID0gIiIKdHJlZXNfcGF0aF9wcmVmaXggPSBiYXNlX3BhdGgrIiIKCiNjbHVzdGFsIGZvcm1hdCBhbGlnbm1lbnQgZmlsZQphbGlnbl9maWxlX2luID0gW3RyZWVzX3BhdGhfcHJlZml4KyJHT0YvTVNBX0Ryb3BvdXRzL01JQy9GQVNUQS9MaWIxNS5Hb0YucGVyZmVjdHMubWljLnRyZWUuYWxpZ25lZC5tb2QuYWxuIl0KCiNudW1iZXIgb2Ygc2VxcyBpbiBlYWNoIGFsaWdubWVudCBmaWxlCm51bV9zYW1wbGVzX2luX2ZpbGUgPSBbNThdICNOZXcgRkFTVEEgdy8gbXV0YW50IGZpdCA+IC0xICgrMSBmcm9tIGFjdHVhbCBmaWxlIGNvdW50KQoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojT1VUUFVUUzoKCm1zYV9tYXBfb3V0X3BhdGggPSBbdHJlZXNfcGF0aF9wcmVmaXgrIkdPRi9NU0FfRHJvcG91dHMvTUlDLyJdCgojIExvb3AgdG8gZ2VuZXJhdGUgLmNzdiBmaWxlcyBmb3IgZWFjaCBJRApmb3IgYWxuaSBpbiByYW5nZSgxKTojbGVuKGFsaWduX2ZpbGVfaW4pKToKICAgICNwcmludChhbG5pKQogICAgCiAgICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgICAjVkFSSUFCTEVTOgogICAgCiAgICAjSUQgYXMga2V5LCBhbGlnbiBhcyB2YWx1ZQogICAgYWxpZ25fZGljdCA9IGRpY3QoKQogICAgCiAgICAjbnVtX3NhbXBsZXMgPSA0MTkKICAgIG51bV9zYW1wbGVzID0gbnVtX3NhbXBsZXNfaW5fZmlsZVthbG5pXQogICAgCiAgICAjcG9zIGtleSwgY29uc2Vuc3VzIHBvcyB2YWwKICAgIElEYWFkaWN0bGlzdCA9IFtkaWN0KCkgZm9yIHggaW4gcmFuZ2UobnVtX3NhbXBsZXMpXQogICAgCiAgICBJRHRvaW5kZXhkaWN0ID0gZGljdCgpCiAgICBpbmRleGR0b0lEaWN0ID0gZGljdCgpCiAgICAKICAgICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAgICNDT0RFOgogICAgCiAgICBsaW5lX2NvdW50ID0gMAogICAgI2xvb3Agb3ZlciBhbGwgYWxpZ25tZW50czoKICAgIHByaW50KGFsaWduX2ZpbGVfaW5bYWxuaV0pCiAgICBmb3IgbGluZSBpbiBvcGVuKGFsaWduX2ZpbGVfaW5bYWxuaV0pOgogICAgICAgICNza2lwIGhlYWRlcgogICAgICAgIGlmIGxpbmVfY291bnQgPiAxOgogICAgICAgICAgICBsaXN0V29yZHMgPSBsaW5lLnNwbGl0KCcgICAgJykKICAgICAgICAgICAgSUQgPSBsaXN0V29yZHNbMF0KICAgICAgICAgICAgYWxpZ24gPSBsaW5lWzE2Ol0ucnN0cmlwKCkKICAgICAgICAgICAgaWYgSUQuc3RyaXAoKSAhPSAiIjoKICAgICAgICAgICAgICAgIGFsaWduX2RpY3RbSURdID0gYWxpZ25fZGljdC5nZXQoSUQsICIiKSArIGFsaWduLnJlcGxhY2UoIiAiLCAiIikKICAgICAgICBsaW5lX2NvdW50ICs9IDEKICAgIAogICAgI3ByaW50KCJOUF80MTQ1OTAiKQogICAgI3ByaW50KGFsaWduX2RpY3RbIk5QXzQxNDU5MCJdKQoKICAgIGNvdW50ZXIgPSAwCiAgICBmb3IgSUQgaW4gYWxpZ25fZGljdDoKICAgICAgICAjcHJpbnQoSUQpCiAgICAgICAgI3ByaW50KGFsaWduX2RpY3RbSURdKQogICAgICAgIElEdG9pbmRleGRpY3RbSURdID0gY291bnRlcgogICAgICAgIGluZGV4ZHRvSURpY3RbY291bnRlcl09SUQKICAgICAgICBhbGlnbiA9IGFsaWduX2RpY3RbSURdCiAgICAgICAgCiAgICAgICAgYWFjb3VudGVyID0gMQogICAgICAgIAogICAgICAgIAogICAgICAgIGZvciBpIGluIHJhbmdlKGxlbihhbGlnbikpOgogICAgICAgICAgICBpZiBhbGlnbltpXSAhPSAiLSI6CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICNwcmludChzdHIoY291bnRlcikrIiAiK3N0cihhYWNvdW50ZXIpKQogICAgICAgICAgICAgICAgSURhYWRpY3RsaXN0W2NvdW50ZXJdW2FhY291bnRlcl09aSsxCiAgICAgICAgICAgICAgICBhYWNvdW50ZXIgKz0gMQogICAgICAgIGNvdW50ZXIgKz0gMQogICAgICAgIAogICAgI3ByaW50KGxlbihJRGFhZGljdGxpc3QpKQogICAgZm9yIGkgaW4gcmFuZ2UobGVuKElEYWFkaWN0bGlzdCktMSk6CiAgICAgICAgI3ByaW50KGluZGV4ZHRvSURpY3RbaV0pCiAgICAgICAgI3ByaW50KGkpCiAgICAgICAgI3ByaW50KGFsbmkpCiAgICAgICAgI3ByaW50KGluZGV4ZHRvSURpY3RbaV0pCiAgICAgICAgY3N2ZmlsZSA9IG9wZW4oc3RyKG1zYV9tYXBfb3V0X3BhdGhbYWxuaV0raW5kZXhkdG9JRGljdFtpXSsiLmNzdiIpLCAndycpCiAgICAgICAgZmllbGRuYW1lcyA9IFsnb3J0aF9hYW51bScsJ21zYV9hYW51bSddCiAgICAgICAgd3JpdGVyID0gY3N2LkRpY3RXcml0ZXIoY3N2ZmlsZSwgZmllbGRuYW1lcz1maWVsZG5hbWVzKQogICAgICAgIHdyaXRlci53cml0ZWhlYWRlcigpCiAgICAgICAgZm9yIGogaW4gSURhYWRpY3RsaXN0W2ldOgogICAgICAgICAgICAjcHJpbnQoc3RyKGopKyIgIitzdHIoSURhYWRpY3RsaXN0W2ldW2pdKSkKICAgICAgICAgICAgI3NhdmUgYWxsIGRhdGE6CiAgICAgICAgICAgIHdyaXRlci53cml0ZXJvdyh7J29ydGhfYWFudW0nOnN0cihqKSwnbXNhX2FhbnVtJzpzdHIoSURhYWRpY3RsaXN0W2ldW2pdKX0pCiAgICAgICAgY3N2ZmlsZS5jbG9zZSgpCmBgYAoKIyMjIEdPRiBQbG90cwoKRmluZCBHb0YgUGVyZmVjdHMgZm9yIERyb3BvdXRzCmBgYHtyfQojIENyZWF0ZSBhIGRhdGEgZnJhbWUgb2YgdW5pcXVlIElEcwptdXRhbnRzMTVfdG9fcGxvdF9taWMgPC0gZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3NfbWljICU+JQogIGZpbHRlcihtdXRhdGlvbnMgPT0gMSkgJT4lCiAgZGlzdGluY3QoSUQpICU+JQogIHNlbGVjdChJRCkKYGBgCgpgYGB7ciBjbGFzcy5vdXRwdXQ9Imdvb2RDb2RlIn0KIyBJbml0aWFsaXplIGFuIGVtcHR5IHZlY3RvciB0byBzdG9yZSBJRHMgb2YgbXV0YW50cyB0byBiZSByZW1vdmVkCm11dGFudHNfdG9fcmVtb3ZlX21pYyA8LSBjaGFyYWN0ZXIoKQoKIyBDaGVjayBmb3IgbWlzc2luZyBNU0EgZmlsZXMKZm9yIChpIGluIDE6bnJvdyhtdXRhbnRzMTVfdG9fcGxvdF9taWMpKSB7CiAgbXV0YW50MTVfY3VycmVudF90ZW1wIDwtIG11dGFudHMxNV90b19wbG90X21pYyRJRFtpXQogIGlmICghZmlsZS5leGlzdHMocGFzdGUoIkdPRi9NU0FfRHJvcG91dHMvTUlDLyIsIG11dGFudDE1X2N1cnJlbnRfdGVtcCwgIi5jc3YiLCBzZXAgPSAiIikpKSB7CiAgICBtdXRhbnRzX3RvX3JlbW92ZV9taWMgPC0gYyhtdXRhbnRzX3RvX3JlbW92ZV9taWMsIG11dGFudDE1X2N1cnJlbnRfdGVtcCkKICB9Cn0KCiMgT3V0cHV0IHRoZSByZXN1bHRzCmlmIChsZW5ndGgobXV0YW50c190b19yZW1vdmVfbWljKSA+IDApIHsKICBjYXQoIlRoZSBmb2xsb3dpbmcgbXV0YW50cyB3aWxsIGJlIHJlbW92ZWQgZHVlIHRvIG1pc3NpbmcgTVNBIGZpbGVzOlxuIikKICBwcmludChtdXRhbnRzX3RvX3JlbW92ZV9taWMpCiAgY2F0KCJcblRvdGFsIG51bWJlciBvZiBtdXRhbnRzIHRvIGJlIHJlbW92ZWQ6IiwgbGVuZ3RoKG11dGFudHNfdG9fcmVtb3ZlX21pYyksICJcbiIpCiAgCiAgIyBSZW1vdmUgdGhlIG11dGFudHMgd2l0aG91dCBNU0EgZmlsZXMKICBtdXRhbnRzMTVfdG9fcGxvdF9taWMgPC0gbXV0YW50czE1X3RvX3Bsb3RfbWljWyFtdXRhbnRzMTVfdG9fcGxvdF9taWMkSUQgJWluJSBtdXRhbnRzX3RvX3JlbW92ZV9taWMsIF0KICBjYXQoIlxuTXV0YW50cyByZW1haW5pbmc6IiwgbnJvdyhtdXRhbnRzMTVfdG9fcGxvdF9taWMpLCAiXG4iKQp9IGVsc2UgewogIGNhdCgiQWxsIG11dGFudHMgaGF2ZSBjb3JyZXNwb25kaW5nIE1TQSBmaWxlcy4gTm8gbXV0YW50cyB3aWxsIGJlIHJlbW92ZWQuXG4iKQp9CgojIElmIHlvdSB3YW50IHRvIHNlZSB0aGUgcmVtYWluaW5nIG11dGFudHMKcHJpbnQobXV0YW50czE1X3RvX3Bsb3RfbWljKQpgYGAKClJlYWQgaW4gdGhlIEUuIGNvbGkgbWFwOgpgYGB7cn0KZWNvbGlfbWFwIDwtIHJlYWQuY3N2KGZpbGU9cGFzdGUoIkdPRi9NU0FfRHJvcG91dHMvQ29tcC9OUF80MTQ1OTAuY3N2Iiwgc2VwPSIiKSwgaGVhZD1UUlVFLCBzZXA9IiwiKQpgYGAKCk1ha2UgYSBuZXcgZGF0YSBmcmFtZSB3aGljaCB3aWxsIGtlZXAgYWxsIGluZm8KYGBge3J9CkdPRl9maXRuZXNzX21hcF9taWMgPC0gZGF0YS5mcmFtZShwb3NpdGlvbj1udW1lcmljKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFhPWNoYXJhY3RlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9bnVtZXJpYygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXRuZXNzPW51bWVyaWMoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zb3J0aG89bnVtZXJpYygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmdhcD1jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0SUQ9Y2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElEPWNoYXJhY3RlcigpKQoKYW1pbm9hY2lkcyA8LSBkYXRhLmZyYW1lKGFhPWMoJ0cnLCdQJywnQScsJ1YnLCdMJywnSScsJ00nLCdDJywnRicsJ1knLCdXJywnSCcsJ0snLCdSJywnUScsJ04nLCdFJywnRCcsJ1MnLCdUJywnWCcpLAogICAgICAgICAgICAgICAgICAgICAgICAgYWFudW09YygxOjIxKSkKYGBgCgoqKk1TQSBNYXBwaW5nOioqIE1hcCB0aGUgbXV0YW50cyAoZml0bmVzcyBkaWZmZXJlbmNlIGZyb20gcGVyZmVjdHMgPiAwKSBvdmVyIGFsbCBwZXJmZWN0cyAoZml0IDwgLTEpIGZvciBHT0YgYW5hbHlzaXM6CmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojbG9vcCBvdmVyIGFsbCBwZXJmZWN0cwpmb3IgKGlpaSBpbiAxOm5yb3cobXV0YW50czE1X3RvX3Bsb3RfbWljKSl7CiAgCiAgI2N1cnJlbnQgb3J0aG9sb2c6CiAgbXV0YW50X2N1cnJlbnRfbWljIDwtIGFzLmNoYXJhY3RlcihtdXRhbnRzMTVfdG9fcGxvdF9taWMkSURbaWlpXSkKICAKICAjbGVuZ3RoIG9mIG5hbWUKICBuYW1lX3NpemVfbWljID0gbmNoYXIocGFzdGUobXV0YW50X2N1cnJlbnRfbWljLCJfIixzZXA9IiIpKQogIAogICNnZXQgdGhlIE1TQSBtYXBwaW5nCiAgbXV0YW50X21hcF9taWMgPC0gcmVhZC5jc3YoZmlsZT1wYXN0ZSgiR09GL01TQV9Ecm9wb3V0cy9NSUMvIixtdXRhbnRfY3VycmVudF9taWMsIi5jc3YiLHNlcD0iIiksaGVhZD1UUlVFLHNlcD0iLCIpCiAgCiAgI2dyYWIgdGhlIG11dGFudHMgd2l0aCBhIGZpdG5lc3MgaW5jcmVhc2UgKEdvRikgZ3JlYXRlciB0aGFuIHplcm8gKGRvIG5vdCBpbmNsdWRlIHBlcmZlY3RzIGZyb20gZGF0YXNldCkKICBHT0ZtdXRJRGluZm9fdGVtcF9taWMgPC0gZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3NfbWljICU+JQogICAgZmlsdGVyKElEID09IG11dGFudF9jdXJyZW50X21pYykgJT4lCiAgICBmaWx0ZXIobXV0YXRpb25zICE9IDApICU+JQogICAgZmlsdGVyKGZpdEQwN0QwMyA+PSAwLjUpICMjI0NIQU5HRSBWQUxVRSBBUyBORUVERUQgKD49IDAuNSBpcyBkZWZhdWx0KQogIAogICMgQ2hlY2sgaWYgR09GbXV0SURpbmZvX3RlbXAgaXMgZW1wdHkKICBpZihucm93KEdPRm11dElEaW5mb190ZW1wX21pYykgPT0gMCkgewogICAgd2FybmluZyhwYXN0ZSgiTm8gbm9uLXplcm8gbXV0YXRpb24gZGF0YSBmb3VuZCBmb3IgSUQ6IiwgbXV0YW50X2N1cnJlbnRfbWljKSkKICAgIG5leHQgICMgU2tpcCB0byB0aGUgbmV4dCBpdGVyYXRpb24gb2YgdGhlIG91dGVyIGxvb3AKICB9CiAgCiAgI2xvb3Agb3ZlciBhbGwgbXV0YW50cyBmb3IgdGhpcyBjb25zdHJ1Y3Q6CiAgZm9yIChtbiBpbiAxOm5yb3coR09GbXV0SURpbmZvX3RlbXBfbWljKSkgewogICAgCiAgICAjdGhpcyBtdXRhbnRzIGZpdG5lc3MKICAgIGdvZl9maXRfdGVtcF9taWMgPC0gR09GbXV0SURpbmZvX3RlbXBfbWljJGZpdEQwN0QwM1ttbl0gICMgb3Igd2hpY2hldmVyIGZpdG5lc3MgY29sdW1uIHlvdSdyZSB1c2luZwogICAgCiAgICAjZ3JhYiB0aGUgbXV0IG5hbWUKICAgIG11dGF0aW9uc19uYW1lc19taWMgPC0gYXMuY2hhcmFjdGVyKEdPRm11dElEaW5mb190ZW1wX21pYyRtdXRJRFttbl0pCiAgICAKICAgICNncmFiIG9ubHkgdGhlIHJlbGV2YW50IHBvcnRpb24gb2YgdGhlIG5hbWUKICAgIG11dGF0aW9uc19uYW1lc19taWMgPC0gc3Vic3RyKG11dGF0aW9uc19uYW1lc19taWMsIG5hbWVfc2l6ZSsxLCBuY2hhcihtdXRhdGlvbnNfbmFtZXNfbWljKSkKICAgIAogICAgIyMgc3BsaXQgbXV0YXRpb24gc3RyaW5nIGF0IG5vbi1kaWdpdHMKICAgIHMgPC0gc3Ryc3BsaXQobXV0YXRpb25zX25hbWVzX21pYywgIl8iKQogICAgCiAgICBmb3IgKG11dG51bSBpbiAxOkdPRm11dElEaW5mb190ZW1wX21pYyRtdXRhdGlvbnNbbW5dKXsKICAgICAgCiAgICAgICNncmFiIHRoZSBjb3JyZXNwb25kaW5nIG11dGF0aW9uIHN0cmluZwogICAgICBtdXRjdXJyPC1zW1sxXV1bbXV0bnVtXQogICAgICAKICAgICAgI2dldCB0aGUgcG9zaXRpb24KICAgICAgbXV0cG9zIDwtIGFzLm51bWVyaWMoc3RyX2V4dHJhY3QobXV0Y3VyciwgIlswLTldKyIpKQogICAgICAKICAgICAgI2dldCBlbmRpbmcgYWEKICAgICAgdG9fYWEgPC0gc3Vic3RyKG11dGN1cnIsIG5jaGFyKG11dHBvcykrMiwgbmNoYXIobXV0Y3VycikpCiAgICAgIAogICAgICAjZmluZCB0aGUgbnVtYmVyIGluIHRoZSBjb25zZW5zdXMgc2VxCiAgICAgIGdvZl9jb25zX2FhbnVtX2luZGV4IDwtIHdoaWNoKG11dGFudF9tYXBfbWljJG9ydGhfYWFudW0gPT0gbXV0cG9zKQogICAgICAKICAgICAgaWYgKGxlbmd0aChnb2ZfY29uc19hYW51bV9pbmRleCkgPiAwKSB7CiAgICAgICAgZ29mX2NvbnNfYWFudW0gPC0gbXV0YW50X21hcF9taWMkbXNhX2FhbnVtW2dvZl9jb25zX2FhbnVtX2luZGV4XQogICAgICAgIAogICAgICAgICNkb2VzIHRoaXMgbWFwIHRvIGEgbm9uLWdhcAogICAgICAgIGlmIChnb2ZfY29uc19hYW51bSAlaW4lIGVjb2xpX21hcCRtc2FfYWFudW0pewogICAgICAgICAgCiAgICAgICAgICAjdGhlIGNvcnJlc3BvbmRpbmcgZS5jb2xpIHJlc2lkdWUKICAgICAgICAgIGVfY29saV9yZXNpZHVlIDwtIGVjb2xpX21hcCRvcnRoX2FhbnVtW3doaWNoKGVjb2xpX21hcCRtc2FfYWFudW0gPT0gZ29mX2NvbnNfYWFudW0pXQogICAgICAgICAgCiAgICAgICAgICAjYWRkIHRoaXMgcG9pbnQgdG8gdGhlIGRhdGEKICAgICAgICAgIEdPRl9maXRuZXNzX21hcF9taWMgPC0gcmJpbmQoR09GX2ZpdG5lc3NfbWFwX21pYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKHBvc2l0aW9uPWVfY29saV9yZXNpZHVlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWE9dG9fYWEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9R09GbXV0SURpbmZvX3RlbXBfbWljJG11dGF0aW9uc1ttbl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXRuZXNzPWdvZl9maXRfdGVtcF9taWMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb3NvcnRobz1tdXRwb3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmdhcD0iTm8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0SUQ9R09GbXV0SURpbmZvX3RlbXBfbWljJG11dElEW21uXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElEPUdPRm11dElEaW5mb190ZW1wX21pYyRJRFttbl0pKQogICAgICAgICAgCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICNpZiBpdCdzIGhlcmUgaXQgbWFwcyB0byBhIGdhcAogICAgICAgICAgCiAgICAgICAgICAjYWRkIHRoaXMgcG9pbnQgdG8gdGhlIGRhdGEKICAgICAgICAgIEdPRl9maXRuZXNzX21hcF9taWMgPC0gcmJpbmQoR09GX2ZpdG5lc3NfbWFwX21pYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKHBvc2l0aW9uPS0xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWE9dG9fYWEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9R09GbXV0SURpbmZvX3RlbXBfbWljJG11dGF0aW9uc1ttbl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXRuZXNzPWdvZl9maXRfdGVtcF9taWMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb3NvcnRobz1tdXRwb3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmdhcD0iWWVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dElEPUdPRm11dElEaW5mb190ZW1wX21pYyRtdXRJRFttbl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJRD1HT0ZtdXRJRGluZm9fdGVtcF9taWMkSURbbW5dKSkKICAgICAgICAgIAogICAgICAgIH0KICAgICAgfSBlbHNlIHsKICAgICAgICB3YXJuaW5nKHBhc3RlKCJObyBtYXRjaGluZyBvcnRoX2FhbnVtIGZvdW5kIGZvciBtdXRwb3M6IiwgbXV0cG9zLCAiaW4gSUQ6IiwgbXV0YW50X2N1cnJlbnRfbWljKSkKICAgICAgICAjIFlvdSBtaWdodCB3YW50IHRvIGhhbmRsZSB0aGlzIGNhc2UsIHBlcmhhcHMgYnkgc2tpcHBpbmcgdGhpcyBtdXRhdGlvbiBvciBhZGRpbmcgaXQgdG8gYSBzZXBhcmF0ZSBsaXN0IGZvciByZXZpZXcKICAgICAgfQogICAgfQogIH0KfQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9CndyaXRlLnRhYmxlKEdPRl9maXRuZXNzX21hcF9taWMsIGZpbGUgPSAiR09GL09VVFBVVC9NSUMvR09GX0ZpdG5lc3NfTWFwX01JQy5jc3YiLCAKICAgICAgICAgICAgc2VwID0gIiwiLCByb3cubmFtZXMgPSBGLHF1b3RlPUYsY29sLm5hbWVzID0gVCkKYGBgCgpDb2xsYXBzZSB0aGUgR09GIGZpdG5lc3MgdmFsdWVzIGJ5IGFhIHBvc2l0aW9uIGFsb25nIHRoZSBwcm90ZWluIHNlcXVlbmNlOgpgYGB7cn0KR09GX2ZpdG5lc3NfY29sbGFwc2VkX2J5X3Bvc19taWMgPC0gR09GX2ZpdG5lc3NfbWFwX21pYyAlPiUKICBmaWx0ZXIocG9zaXRpb24gPiAwKSAlPiUKICBncm91cF9ieShwb3NpdGlvbikgJT4lCiAgc3VtbWFyaXNlKGZpdHZhbD1tZWRpYW4oZml0bmVzcyksCiAgICAgICAgICAgIG51bXBvaW50cz1uKCksCiAgICAgICAgICAgIHN0ZGZpdD1zZChmaXRuZXNzKSwKICAgICAgICAgICAgbnVtb3J0aG89bGVuZ3RoKHVuaXF1ZShJRCkpKQpgYGAKCiMjIyMgR09GIE11dGFudCBOby4gUGxvdAoKUGxvdCB0aGUgbnVtYmVyIG9mIGdhaW4tb2YtZnVuY3Rpb24gbXV0YW50cyByZWNvdmVyZWQgZm9yIGVhY2ggYWEgcG9zaXRpb24gYWxvbmcgdGhlIHByb3RlaW4gc2VxdWVuY2UgYW5kIGluY2x1ZGUgY3V0b2ZmIGxpbmVzIGZvciAxIFNEIGFib3ZlIHRoZSBtZWFuIG51bWJlciBvZiBHT0YgbXV0YW50cyBhbmQgMiBTRCBhYm92ZSB0aGUgbWVhbiBudW1iZXIgb2YgR09GIG11dGFudHMuIEFsbCBwb3NpdGlvbnMgd2l0aCBHT0YgbXV0YW50cyBhYm92ZSB0aGUgMiBTRCBsaW5lIGFyZSBjb25zaWRlcmVkIHNpZ25pZmljYW50IHBvc2l0aW9ucyBwb3NpdGl2ZWx5IGluZmx1ZW5jaW5nIHRoZSBwYXJlbnQgdmFyaWFudHMgYWJpbGl0eSB0byBjb21wbGVtZW50IG1ldGFib2xpYyBmdW5jdGlvbiBpbiB0aGUgRS4gY29saSBrbm9ja291dCBtb2RlbC4KYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CkdvRl9wbG90X21pYyA8LSBnZ3Bsb3QoR09GX2ZpdG5lc3NfY29sbGFwc2VkX2J5X3Bvc19taWMsIGFlcyh4PXBvc2l0aW9uLCB5PW51bXBvaW50cywgY29sb3I9bnVtb3J0aG8pKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMCwgeSA9IG1lYW4obnVtcG9pbnRzKSsyKnNkKG51bXBvaW50cyksIAogICAgICAgICAgICAgICAgICAgeGVuZCA9IDE2MCwgCiAgICAgICAgICAgICAgICAgICB5ZW5kID0gbWVhbihudW1wb2ludHMpKzIqc2QobnVtcG9pbnRzKSksbGluZXR5cGU9Mixjb2xvdXIgPSAiYmx1ZSIpKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDAsIHkgPSBtZWFuKG51bXBvaW50cyksIHhlbmQgPSAxNjAsIHllbmQgPSBtZWFuKG51bXBvaW50cykpLGxpbmV0eXBlPTIsY29sb3VyID0gInJlZCIpKwogIGdlb21fcG9pbnQoc2l6ZT0xLjgpKwogIGxhYnMoeCA9ICJQb3NpdGlvbiAoYWEpIiwgeSA9Ik51bWJlciBvZiBnYWluLW9mLWZ1bmN0aW9uIG11dGFudHMiLGNvbG9yPSIiKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobG93ID0gImJsdWUiLCAKICAgICAgICAgICAgICAgICAgICAgICBoaWdoID0gInJlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgbmFtZT0iTnVtLlxuVW5pcS5cbkhvbW8uIiwKICAgICAgICAgICAgICAgICAgICAgICBuYS52YWx1ZT0iZ3JleSIsIAogICAgICAgICAgICAgICAgICAgICAgIGxpbWl0ID0gYygwLDEuMSptYXgoR09GX2ZpdG5lc3NfY29sbGFwc2VkX2J5X3Bvc19taWMkbnVtb3J0aG8pKSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDAsMTYwLDIwKSkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJsZWZ0IikKR29GX3Bsb3RfbWljIDwtIGdnRXh0cmE6OmdnTWFyZ2luYWwoR29GX3Bsb3RfbWljLHR5cGUgPSAiaGlzdG9ncmFtIiwKICAgICAgICAgICAgICAgICAgICBtYXJnaW5zID0gInkiLAogICAgICAgICAgICAgICAgICAgIGJpbnM9MjEsCiAgICAgICAgICAgICAgICAgICAgY29sID0gJ2JsYWNrJywKICAgICAgICAgICAgICAgICAgICBmaWxsID0gJ3JlZCcpCkdvRl9wbG90X21pYwpgYGAKCmBgYHtyIGVjaG89RkFMU0V9Cmdnc2F2ZShmaWxlbmFtZSA9ICJHT0YvUExPVFMvTUlDL0dPRi5NSUMuTXV0YW50LmJ5LkFBLlBvcy4yc2lnbWEucG5nIiwgCiAgICAgICBwbG90ID0gR29GX3Bsb3RfbWljLAogICAgICAgd2lkdGggPSA0LjUsIGhlaWdodCA9IDQuNSwgdW5pdHMgPSAiaW4iKQpgYGAKClByaW50IG91dCBhIHN1bW1hcnkgdGFibGUgb2Ygc2lnbmlmaWNhbnQgYWEgcG9zaXRpb24gYWxvbmcgdGhlIHByb3RlaW4gc2VxdWVuY2UKYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CkdPRl9maXRuZXNzX2NvbGxhcHNlZF9ieV9wb3NfMnNpZ21hX21pYyA8LSBHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zX21pYyAlPiUKICBmaWx0ZXIobnVtcG9pbnRzID49IChtZWFuKEdPRl9maXRuZXNzX2NvbGxhcHNlZF9ieV9wb3NfbWljJG51bXBvaW50cykgKyAyKnNkKEdPRl9maXRuZXNzX2NvbGxhcHNlZF9ieV9wb3NfbWljJG51bXBvaW50cykpKQpwcmludChHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zXzJzaWdtYV9taWMpCmBgYAoKQ2FsY3VsYXRlIGFsbCBEYXRhIGFuZCBTdGF0czoKYGBge3J9CkdPRl9maXRuZXNzX2NvbGxhcHNlZF9hbGxfbWljIDwtIEdPRl9maXRuZXNzX21hcF9taWMgJT4lCiAgZmlsdGVyKHBvc2l0aW9uID4gMCkgJT4lCiAgZ3JvdXBfYnkocG9zaXRpb24sIGFhKSAlPiUKICBzdW1tYXJpc2UoZml0dmFsPW1lZGlhbihmaXRuZXNzKSwKICAgICAgICAgICAgbnVtcG9pbnRzPW4oKSwKICAgICAgICAgICAgc3RkZml0PXNkKGZpdG5lc3MpLAogICAgICAgICAgICBudW1vcnRobz1sZW5ndGgodW5pcXVlKElEKSkpCgpnb2ZfYWFfZGltIDwtIG5yb3coYW1pbm9hY2lkcykKZ29mX3JlZl9sZW4gPC0gbnJvdyhlY29saV9tYXApCmBgYAoKYGBge3Igd2FybmluZz1GQUxTRX0KI3RoZXNlIG1hdHJpY2VzIGhhdmUgdGhlIGZpdG5lc3MvbnVtL3NkIGZvciBlYWNoIGFhIGF0IGVhY2ggcG9zaXRpb246CmdvZl9tYXRyaXggPSBtYXRyaXgocmVwKE5BLCBnb2ZfcmVmX2xlbipnb2ZfYWFfZGltKSxucm93PWdvZl9hYV9kaW0sbmNvbD1nb2ZfcmVmX2xlbikKZ29mX21hdHJpeF9udW0gPSBtYXRyaXgocmVwKE5BLCBnb2ZfcmVmX2xlbipnb2ZfYWFfZGltKSxucm93PWdvZl9hYV9kaW0sbmNvbD1nb2ZfcmVmX2xlbikKZ29mX21hdHJpeF9zZCA9IG1hdHJpeChyZXAoTkEsIGdvZl9yZWZfbGVuKmdvZl9hYV9kaW0pLG5yb3c9Z29mX2FhX2RpbSxuY29sPWdvZl9yZWZfbGVuKQpnb2ZfbWF0cml4X251bW9ydGhvID0gbWF0cml4KHJlcChOQSwgZ29mX3JlZl9sZW4qZ29mX2FhX2RpbSksbnJvdz1nb2ZfYWFfZGltLG5jb2w9Z29mX3JlZl9sZW4pCgojcG9wdWxhdGUgbWF0cml4CmZvciAoaSBpbiAxOm5yb3coR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbF9taWMpKXsKICAKICBnb2ZfbWF0cml4W3doaWNoKGFtaW5vYWNpZHMkYWE9PWFzLmNoYXJhY3RlcihHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsX21pYyRhYVtpXSkpLEdPRl9maXRuZXNzX2NvbGxhcHNlZF9hbGxfbWljJHBvc2l0aW9uW2ldXSA8LSBhcy5udW1lcmljKEdPRl9maXRuZXNzX2NvbGxhcHNlZF9hbGxfbWljJGZpdHZhbFtpXSkKICBnb2ZfbWF0cml4X251bVt3aGljaChhbWlub2FjaWRzJGFhPT1hcy5jaGFyYWN0ZXIoR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbF9taWMkYWFbaV0pKSxHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsX21pYyRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsX21pYyRudW1wb2ludHNbaV0pCiAgZ29mX21hdHJpeF9zZFt3aGljaChhbWlub2FjaWRzJGFhPT1hcy5jaGFyYWN0ZXIoR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbF9taWMkYWFbaV0pKSxHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsX21pYyRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsX21pYyRzdGRmaXRbaV0pCiAgZ29mX21hdHJpeF9udW1vcnRob1t3aGljaChhbWlub2FjaWRzJGFhPT1hcy5jaGFyYWN0ZXIoR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbF9taWMkYWFbaV0pKSxHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsX21pYyRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsX21pYyRudW1vcnRob1tpXSkKfQoKcm93bmFtZXMoZ29mX21hdHJpeCk8LWFtaW5vYWNpZHMkYWEKY29sbmFtZXMoZ29mX21hdHJpeCk8LWMoMTpnb2ZfcmVmX2xlbikKcm93bmFtZXMoZ29mX21hdHJpeF9udW0pPC1hbWlub2FjaWRzJGFhCmNvbG5hbWVzKGdvZl9tYXRyaXhfbnVtKTwtYygxOmdvZl9yZWZfbGVuKQpyb3duYW1lcyhnb2ZfbWF0cml4X3NkKTwtYW1pbm9hY2lkcyRhYQpjb2xuYW1lcyhnb2ZfbWF0cml4X3NkKTwtYygxOmdvZl9yZWZfbGVuKQpyb3duYW1lcyhnb2ZfbWF0cml4X251bW9ydGhvKTwtYW1pbm9hY2lkcyRhYQpjb2xuYW1lcyhnb2ZfbWF0cml4X251bW9ydGhvKTwtYygxOmdvZl9yZWZfbGVuKQoKZ29mX21hdHJpeF9tZWx0IDwtIG1lbHQoZ29mX21hdHJpeCkKZ29mX21hdHJpeF9udW1fbWVsdCA8LSBtZWx0KGdvZl9tYXRyaXhfbnVtKQpnb2ZfbWF0cml4X3NkX21lbHQgPC0gbWVsdChnb2ZfbWF0cml4X3NkKQpnb2ZfbWF0cml4X251bW9ydGhvX21lbHQgPC0gbWVsdChnb2ZfbWF0cml4X251bW9ydGhvKQoKIyBSZW5hbWUgY29sdW1ucyB0byAiWDEiIGFuZCAiWDIiCm5hbWVzKGdvZl9tYXRyaXhfbWVsdClbbmFtZXMoZ29mX21hdHJpeF9tZWx0KSAlaW4lIGMoIlZhcjEiLCAiVmFyMiIpXSA8LSBjKCJYMSIsICJYMiIpCm5hbWVzKGdvZl9tYXRyaXhfbnVtX21lbHQpW25hbWVzKGdvZl9tYXRyaXhfbnVtX21lbHQpICVpbiUgYygiVmFyMSIsICJWYXIyIildIDwtIGMoIlgxIiwgIlgyIikKbmFtZXMoZ29mX21hdHJpeF9zZF9tZWx0KVtuYW1lcyhnb2ZfbWF0cml4X3NkX21lbHQpICVpbiUgYygiVmFyMSIsICJWYXIyIildIDwtIGMoIlgxIiwgIlgyIikKbmFtZXMoZ29mX21hdHJpeF9udW1vcnRob19tZWx0KVtuYW1lcyhnb2ZfbWF0cml4X251bW9ydGhvX21lbHQpICVpbiUgYygiVmFyMSIsICJWYXIyIildIDwtIGMoIlgxIiwgIlgyIikKCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyA8LSBnb2ZfbWF0cml4X21lbHQgJT4lCiAgZmlsdGVyKFgyID09IDg5IHwKICAgICAgICAgWDIgPT0gMTAyIHwKICAgICAgICAgWDIgPT0gMTAzIHwKICAgICAgICAgWDIgPT0gMTIxIHwKICAgICAgICAgWDIgPT0gMTI4IHwKICAgICAgICAgWDIgPT0gMTI5ICkKCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW0gPC0gMApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkbXV0cG9zbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMj09ODkpXSA8LSAxCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgyPT0xMDIpXSA8LSAyCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgyPT0xMDMpXSA8LSAzCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgyPT0xMjEpXSA8LSA0CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgyPT0xMjgpXSA8LSA1CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgyPT0xMjkpXSA8LSA2Cgpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW0gPC0gMApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iQSIpXSA8LSAxMgpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iQyIpXSA8LSAxMApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iRCIpXSA8LSA1CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJFIildIDwtIDQKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IkYiKV0gPC0gMTkKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IkciKV0gPC0gMTEKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IkgiKV0gPC0gMwpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iSSIpXSA8LSAxNQpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iSyIpXSA8LSAxCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJMIildIDwtIDE0CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJNIildIDwtIDE2CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJOIildIDwtIDYKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IlAiKV0gPC0gMTcKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IlEiKV0gPC0gNwpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iUiIpXSA8LSAyCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJTIildIDwtIDkKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IlQiKV0gPC0gOApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iViIpXSA8LSAxMwpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iVyIpXSA8LSAyMApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iWSIpXSA8LSAxOApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iWCIpXSA8LSAyMQoKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zX3dudW0gPC0gZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zICU+JQogIGlubmVyX2pvaW4oZ29mX21hdHJpeF9udW1fbWVsdCxieT1jKCJYMSIsIlgyIikpICU+JQogIGRwbHlyOjpyZW5hbWUobXV0bnVtPXZhbHVlLnksdmFsdWU9dmFsdWUueCkKYGBgCgojIyMjIEdPRiBQb3NpdGlvbiBQbG90CgpQbG90IHRoZSBtZWFuIGZpdG5lc3Mgb2YgZWFjaCBHb0YgbXV0YXRpb24gYXQgdGhlIHNpZ25pZmljYW50IHBvc2l0aW9ucywgd2l0aCB0aGUgbnVtYmVyIG9mIG11dGFudHMgb2JzZXJ2ZWQgYXQgZWFjaCBBQToKYGBge3J9CiMgRGVmaW5lIHRoZSBvcmRlciBvZiBhbWlubyBhY2lkcyBmb3IgdGhlIHJlY3RhbmdsZXMKcmVjdF9vcmRlciA8LSBjKCJQIiwgIlEiLCAiRiIsICJHIiwgIlkiLCAiRSIpCgojIENyZWF0ZSBhIGRhdGEgZnJhbWUgZm9yIHRoZSByZWN0YW5nbGVzCnJlY3RfZGF0YSA8LSBkYXRhLmZyYW1lKAphYW51bSA9IG1hdGNoKHJlY3Rfb3JkZXIsIGMoIksiLCJSIiwiSCIsIkUiLCJEIiwiTiIsIlEiLCJUIiwiUyIsIkMiLCJHIiwiQSIsIlYiLCJMIiwiSSIsIk0iLCJQIiwiWSIsIkYiLCJXIiwiWCIpKSwKeG1pbiA9IHNlcSgwLjUsIGJ5ID0gMSwgbGVuZ3RoLm91dCA9IGxlbmd0aChyZWN0X29yZGVyKSksCnhtYXggPSBzZXEoMS41LCBieSA9IDEsIGxlbmd0aC5vdXQgPSBsZW5ndGgocmVjdF9vcmRlcikpKQoKI3Bsb3QgdGhlIGRhdGEgZnJvbSBhbGwgbXV0YW50czoKR09GX2ZpdF9udW1tdXRfcGxvdF9taWMgPC0gZ2dwbG90KGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvc193bnVtLCAKICAgICAgIGFlcyh4PW11dHBvc251bSwgeT1hYW51bSwKICAgICAgICAgICBmaWxsPXZhbHVlLAogICAgICAgICAgIGxhYmVsPW11dG51bSkpICsKICBnZW9tX3RpbGUoKSArCiAgZ2VvbV90ZXh0KCkgKwogICMgQWRkIGJsYWNrIHJlY3RhbmdsZXMKICBnZW9tX3JlY3QoZGF0YSA9IHJlY3RfZGF0YSwKICAgICAgICAgICBhZXMoeG1pbiA9IHhtaW4sIHhtYXggPSB4bWF4LCB5bWluID0gYWFudW0gLSAwLjUsIHltYXggPSBhYW51bSArIDAuNSksCiAgICAgICAgICAgZmlsbCA9IE5BLCBjb2xvciA9ICJibGFjayIsIGluaGVyaXQuYWVzID0gRkFMU0UpICsKICBsYWJzKHggPSAiUG9zaXRpb24gKGFhKSIsCiAgICAgICB5ID0iQW1pbm8gYWNpZCIsY29sb3I9IiIpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJibHVlIiwgCiAgICAgICAgICAgICAgICAgICAgICBoaWdoID0gInJlZCIsCiAgICAgICAgICAgICAgICAgICAgICBuYW1lPSJGaXRuZXNzIiwKICAgICAgICAgICAgICAgICAgICAgIG5hLnZhbHVlPSJncmV5IiwKICAgICAgICAgICAgICAgICAgICAgIGxpbWl0ID0gYygwLCBtYXgoZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zX3dudW0kdmFsdWUpKSkgKwogIHRoZW1lX21pbmltYWwoKSsKICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZT0iUG9zaXRpb24gKGFhKSIsCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcz1jKDEsMiwzLDQsNSw2KSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIjg5IiwiMTAyIiwiMTAzIiwiMTIxIiwiMTI4IiwiMTI5IikpKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lPSJBbWlubyBhY2lkIiwgCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcz1jKDEsMiwzLDQsNSw2LDcsOCw5LDEwLDExLDEyLDEzLDE0LDE1LDE2LDE3LDE4LDE5LDIwLDIxKSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIksiLCJSIiwiSCIsIkUiLCJEIiwiTiIsIlEiLCJUIiwiUyIsIkMiLCJHIiwiQSIsIlYiLCJMIiwiSSIsIk0iLCJQIiwiWSIsIkYiLCJXIiwiWCIpKQoKcHJpbnQoR09GX2ZpdF9udW1tdXRfcGxvdF9taWMpCmBgYAoKYGBge3IgZWNobz1GQUxTRX0KZ2dzYXZlKGZpbGVuYW1lID0gIkdPRi9QTE9UUy9NSUMvR09GLk1JQy5GaXRuZXNzLk51bWJlci5NdXRhbnRzLnBuZyIsIAogICAgICAgcGxvdCA9IEdPRl9maXRfbnVtbXV0X3Bsb3RfbWljLAogICAgICAgd2lkdGggPSA0LjUsIGhlaWdodCA9IDQuNSwgdW5pdHMgPSAiaW4iKQpgYGAKCiMjIDQwMHggTUlDICgyMDAgdWcvbUwgVE1QKQoKIyMjIERyb3BvdXQgUGVyZmVjdHMKClJldHJpZXZlIGFsbCBkcm9wb3V0IHBlcmZlY3RzIHdpdGggYSBsb2ctZm9sZCBjaGFuZ2UgdmFsdWUgZ3JlYXRlciB0aGFuIC0xLjAgZnJvbSB0aGUgKipDT01QTEVNRU5UQVRJT04qKiBkYXRhc2V0LiBUaGVuLCByZXRyaWV2ZSB0aGUgc2FtZSBwZXJmZWN0cyAoSUQpIGZyb20gdGhlICoqNDAweCBNSUMqKiBkYXRhc2V0IGFuZCByZXRhaW4gb25seSB0aG9zZSB3aXRoIGZpdG5lc3MgbGVzcyB0aGFuIC0xLiBBbHNvLCByZXRyaWV2ZSBhbGwgY29ycmVzcG9uZGluZyBtdXRhbnRzLiBVc2UgdGhlIGBtdXRfY29sbGFwc2VfMTVgIGRhdGFzZXQgd2hpY2ggaW5jbHVkZXMgNzk3IHBlcmZlY3RzIChtdXRhdGlvbnMgPSAwOyBudW1wcnVuZWRCQ3MgPSA1KS4KYGBge3J9CiMgU3RlcCAxOiBJZGVudGlmeSBJRHMgdGhhdCBoYXZlIHJvd3Mgd2hlcmUgbXV0YXRpb25zID09IDAgYW5kIGZpdEQwNUQwMyA+IC0xLjAgaW4gQ09NUExFTUVOVEFUSU9OCmRyb3BvdXQxNV9pZHNfd2l0aF96ZXJvX211dGF0aW9uc19jb21wbGVtZW50IDwtIG11dF9jb2xsYXBzZV8xNSAlPiUKICBmaWx0ZXIobXV0YXRpb25zID09IDAgJiBmaXREMDVEMDMgPiAtMS4wKSAlPiUKICBkaXN0aW5jdChJRCkgJT4lCiAgcHVsbChJRCkKCiMgU3RlcCAyOiBJZGVudGlmeSB0aGUgc2FtZSBJRHMgdGhhdCBoYXZlIG11dGF0aW9ucyA9PSAwIGFuZCBmaXREMTFEMDMgPCAtMS4wIGluIDQwMHggTUlDCmRyb3BvdXQxNV9pZHNfd2l0aF96ZXJvX211dGF0aW9uc180MDBtaWMgPC0gbXV0X2NvbGxhcHNlXzE1ICU+JQogIGZpbHRlcihJRCAlaW4lIGRyb3BvdXQxNV9pZHNfd2l0aF96ZXJvX211dGF0aW9uc19jb21wbGVtZW50ICYgCiAgICAgICAgIG11dGF0aW9ucyA9PSAwICYgCiAgICAgICAgIGZpdEQxMUQwMyA8IC0xLjAgJgogICAgICAgICAhaXMubmEoZml0RDExRDAzKSkgJT4lCiAgZGlzdGluY3QoSUQpICU+JQogIHB1bGwoSUQpCgojIFN0ZXAgMzogUmV0cmlldmUgdGhlIHJvd3MgZm9yIHRoZXNlIElEcwpyZXN1bHRfcm93c180MDBtaWMgPC0gbXV0X2NvbGxhcHNlXzE1ICU+JQogIGZpbHRlcihJRCAlaW4lIGRyb3BvdXQxNV9pZHNfd2l0aF96ZXJvX211dGF0aW9uc180MDBtaWMgJiBtdXRhdGlvbnMgPT0gMCkKCiMgU3RlcCA0OiBGaWx0ZXIgdGhlIG1haW4gZGF0YXNldCB0byBrZWVwIG11dGFudHMgaWYgdGhleSBtYXRjaCBhIGNvcnJlc3BvbmRpbmcgcGVyZmVjdCBJRApkcm9wb3V0X211dGFudHMxNV9HT0ZfNDAwbWljIDwtIG11dF9jb2xsYXBzZV8xNSAlPiUKICBmaWx0ZXIoCiAgICAobXV0YXRpb25zID09IDAgJiAhaXMubmEoZml0RDA1RDAzKSAmIGZpdEQwNUQwMyA+IC0xLjAgJiAKICAgICAhaXMubmEoZml0RDExRDAzKSAmIGZpdEQxMUQwMyA8IC0xLjApIHwKICAgIChtdXRhdGlvbnMgIT0gMCAmIGZpdEQxMUQwMyA+IC0xLjAgJiBJRCAlaW4lIGRyb3BvdXQxNV9pZHNfd2l0aF96ZXJvX211dGF0aW9uc180MDBtaWMpKSAlPiUKICBkcGx5cjo6c2VsZWN0KElELCBtdXRJRCwgbnVtcHJ1bmVkQkNzLCBtdXRhdGlvbnMsIGZpdEQwNUQwMywgZml0RDExRDAzLCBzZXEpCmBgYAoKVmFsaWRhdGUgdGhhdCByb3dzIHdoZXJlIG11dGF0aW9ucyAhPSAwIGhhdmUgYW4gSUQgdGhhdCBtYXRjaGVzIHJvd3Mgd2hlcmUgbXV0YXRpb25zID09IDAgYW5kIGZpdEQwNUQwMyA8IC0xLjAuCmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIFZlcmlmaWNhdGlvbiBzdGVwCnZlcmlmaWNhdGlvbl9yZXN1bHRfNDAwbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWMgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAwKSAlPiUKICBtdXRhdGUoCiAgICBjb25kaXRpb25fbWV0ID0gZml0RDA1RDAzID4gLTEuMCAmIGZpdEQxMUQwMyA8IC0xLAogICAgZml0RDA1RDAzX2NoZWNrID0gZml0RDA1RDAzID4gLTEuMCwKICAgIGZpdEQxMUQwM19jaGVjayA9IGZpdEQxMUQwMyA8IC0xCiAgKQoKIyBDaGVjayBpZiBhbGwgcm93cyBtZWV0IHRoZSBjb25kaXRpb24KYWxsX2NvbmRpdGlvbnNfbWV0XzQwMG1pYyA8LSBhbGwodmVyaWZpY2F0aW9uX3Jlc3VsdF80MDBtaWMkY29uZGl0aW9uX21ldCkKCiMgU3VtbWFyeSBvZiB0aGUgdmVyaWZpY2F0aW9uCnZlcmlmaWNhdGlvbl9zdW1tYXJ5XzQwMG1pYyA8LSB2ZXJpZmljYXRpb25fcmVzdWx0XzQwMG1pYyAlPiUKICBzdW1tYXJpc2UoCiAgICB0b3RhbF9yb3dzID0gbigpLAogICAgcm93c19tZWV0aW5nX2JvdGhfY29uZGl0aW9ucyA9IHN1bShjb25kaXRpb25fbWV0KSwKICAgIHJvd3NfbWVldGluZ19maXREMDVEMDMgPSBzdW0oZml0RDA1RDAzX2NoZWNrKSwKICAgIHJvd3NfbWVldGluZ19maXREMTFEMDMgPSBzdW0oZml0RDExRDAzX2NoZWNrKQogICkKCiMgUHJpbnQgcmVzdWx0cwpwcmludCgiVmVyaWZpY2F0aW9uIFN1bW1hcnk6IikKcHJpbnQodmVyaWZpY2F0aW9uX3N1bW1hcnlfNDAwbWljKQpwcmludChwYXN0ZSgiQWxsIGNvbmRpdGlvbnMgbWV0OiIsIGFsbF9jb25kaXRpb25zX21ldF80MDBtaWMpKQoKIyBJZiB0aGVyZSBhcmUgYW55IHJvd3Mgbm90IG1lZXRpbmcgdGhlIGNvbmRpdGlvbnMsIGRpc3BsYXkgdGhlbQppZiAoIWFsbF9jb25kaXRpb25zX21ldF80MDBtaWMpIHsKICBwcmludCgiUm93cyBub3QgbWVldGluZyBib3RoIGNvbmRpdGlvbnM6IikKICBwcmludCh2ZXJpZmljYXRpb25fcmVzdWx0XzQwMG1pYyAlPiUgZmlsdGVyKCFjb25kaXRpb25fbWV0KSAlPiUgc2VsZWN0KElELCBmaXREMDVEMDMsIGZpdEQxMUQwMykpCn0KYGBgCgpWYWxpZGF0ZSB0aGF0IHJvd3Mgd2hlcmUgbXV0YXRpb25zICE9IDAgaGF2ZSBhbiBJRCB0aGF0IG1hdGNoZXMgcm93cyB3aGVyZSBtdXRhdGlvbnMgPT0gMCBhbmQgZml0RDExRDAzIDwgLTEuMC4KYGBge3IgY2xhc3Mub3V0cHV0PSJnb29kQ29kZSJ9CiMgMS4gRmlyc3QsIGNyZWF0ZSB0d28gc3Vic2V0cyBvZiB0aGUgZGF0YQp6ZXJvX211dGF0aW9uX3Jvd3NfNDAwbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWMgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAwICYgZml0RDExRDAzIDwgLTEuMCkKCm5vbl96ZXJvX211dGF0aW9uX3Jvd3NfNDAwbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWMgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyAhPSAwICYgZml0RDExRDAzID4gLTEuMCkKCiMgMi4gQ2hlY2sgdGhhdCBhbGwgSURzIGluIG5vbl96ZXJvX211dGF0aW9uX3Jvd3MgYXJlIHByZXNlbnQgaW4gemVyb19tdXRhdGlvbl9yb3dzCmFsbF92YWxpZF9pZHNfNDAwbWljIDwtIGFsbChub25femVyb19tdXRhdGlvbl9yb3dzXzQwMG1pYyRJRCAlaW4lIHplcm9fbXV0YXRpb25fcm93c180MDBtaWMkSUQpCnByaW50KHBhc3RlKCJBbGwgbm9uLXplcm8gbXV0YXRpb24gcm93cyBoYXZlIGEgbWF0Y2hpbmcgemVybyBtdXRhdGlvbiByb3c6IiwgYWxsX3ZhbGlkX2lkc180MDBtaWMpKQoKIyAzLiBJZiB0aGUgYWJvdmUgaXMgRkFMU0UsIGZpbmQgdGhlIHByb2JsZW1hdGljIElEcwppZiAoIWFsbF92YWxpZF9pZHNfNDAwbWljKSB7CiAgcHJvYmxlbWF0aWNfaWRzXzQwMG1pYyA8LSBzZXRkaWZmKG5vbl96ZXJvX211dGF0aW9uX3Jvd3NfNDAwbWljJElELCB6ZXJvX211dGF0aW9uX3Jvd3NfNDAwbWljJElEKQogIHByaW50KCJJRHMgd2l0aCBub24temVybyBtdXRhdGlvbnMgYnV0IG5vIG1hdGNoaW5nIHplcm8gbXV0YXRpb24gcm93OiIpCiAgcHJpbnQocHJvYmxlbWF0aWNfaWRzXzQwMG1pYykKfQoKIyA0LiBDaGVjayBmb3IgYW55IElEcyBpbiB6ZXJvX211dGF0aW9uX3Jvd3MgdGhhdCBkb24ndCBoYXZlIGEgY29ycmVzcG9uZGluZyBub24temVybyBtdXRhdGlvbiByb3cKaWRzX3dpdGhvdXRfbm9uX3plcm9fNDAwbWljIDwtIHNldGRpZmYoemVyb19tdXRhdGlvbl9yb3dzXzQwMG1pYyRJRCwgbm9uX3plcm9fbXV0YXRpb25fcm93c180MDBtaWMkSUQpCnByaW50KCJJRHMgd2l0aCB6ZXJvIG11dGF0aW9ucyBidXQgbm8gY29ycmVzcG9uZGluZyBub24temVybyBtdXRhdGlvbiByb3dzOiIpCnByaW50KGlkc193aXRob3V0X25vbl96ZXJvXzQwMG1pYykKCiMgNS4gU3VtbWFyeSBzdGF0aXN0aWNzCnByaW50KHBhc3RlKCJOdW1iZXIgb2YgdW5pcXVlIElEcyBpbiB6ZXJvIG11dGF0aW9uIHJvd3M6Iiwgbl9kaXN0aW5jdCh6ZXJvX211dGF0aW9uX3Jvd3NfNDAwbWljJElEKSkpCnByaW50KHBhc3RlKCJOdW1iZXIgb2YgdW5pcXVlIElEcyBpbiBub24temVybyBtdXRhdGlvbiByb3dzOiIsIG5fZGlzdGluY3Qobm9uX3plcm9fbXV0YXRpb25fcm93c180MDBtaWMkSUQpKSkKCiMgNi4gRGlzdHJpYnV0aW9uIG9mIG11dGF0aW9uIGNvdW50cyBmb3Igbm9uLXplcm8gbXV0YXRpb24gcm93cwptdXRhdGlvbl9kaXN0cmlidXRpb25fNDAwbWljIDwtIG5vbl96ZXJvX211dGF0aW9uX3Jvd3NfNDAwbWljICU+JQogIGdyb3VwX2J5KG11dGF0aW9ucykgJT4lCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUKICBhcnJhbmdlKG11dGF0aW9ucykKCnByaW50KCJEaXN0cmlidXRpb24gb2YgbXV0YXRpb24gY291bnRzOiIpCnByaW50KG11dGF0aW9uX2Rpc3RyaWJ1dGlvbl80MDBtaWMpCgojIDcuIENoZWNrIGZvciBhbnkgdW5leHBlY3RlZCBtdXRhdGlvbiB2YWx1ZXMKdW5leHBlY3RlZF9tdXRhdGlvbnNfNDAwbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWMgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA8IDAgfCBtdXRhdGlvbnMgPiA1KSAgIyBBZGp1c3QgdGhlIHVwcGVyIGJvdW5kIGFzIG5lZWRlZAoKaWYgKG5yb3codW5leHBlY3RlZF9tdXRhdGlvbnNfNDAwbWljKSA+IDApIHsKICBwcmludCgiUm93cyB3aXRoIHVuZXhwZWN0ZWQgbXV0YXRpb24gdmFsdWVzOiIpCiAgcHJpbnQodW5leHBlY3RlZF9tdXRhdGlvbnNfNDAwbWljKQp9IGVsc2UgewogIHByaW50KCJObyB1bmV4cGVjdGVkIG11dGF0aW9uIHZhbHVlcyBmb3VuZC4iKQp9CmBgYAoKUmVtb3ZlIHVuaXF1ZSBwZXJmZWN0IElEcyBpZiB0aGVyZSBpcyBubyBjb3JyZXNwb25kaW5nIG11dGFudHM6CmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIFN0ZXAgMTogSWRlbnRpZnkgSURzIHdpdGggemVybyBtdXRhdGlvbnMKemVyb19tdXRhdGlvbl9pZHNfNDAwbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWMgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAwICYgZml0RDExRDAzIDwgLTEuMCkgJT4lCiAgcHVsbChJRCkKCiMgU3RlcCAyOiBJZGVudGlmeSBJRHMgd2l0aCBub24temVybyBtdXRhdGlvbnMKbm9uX3plcm9fbXV0YXRpb25faWRzXzQwMG1pYyA8LSBkcm9wb3V0X211dGFudHMxNV9HT0ZfNDAwbWljICU+JQogIGZpbHRlcihtdXRhdGlvbnMgIT0gMCAmIGZpdEQxMUQwMyA+IC0xLjApICU+JQogIHB1bGwoSUQpCgojIFN0ZXAgMzogRmluZCBJRHMgdGhhdCBoYXZlIHplcm8gbXV0YXRpb25zIGJ1dCBubyBjb3JyZXNwb25kaW5nIG5vbi16ZXJvIG11dGF0aW9uIHJvd3MKaWRzX3RvX3JlbW92ZV80MDBtaWMgPC0gc2V0ZGlmZih6ZXJvX211dGF0aW9uX2lkc180MDBtaWMsIG5vbl96ZXJvX211dGF0aW9uX2lkc180MDBtaWMpCgojIFN0ZXAgNDogUmVtb3ZlIHRoZSByb3dzIHdpdGggdGhlc2UgSURzCmRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWNfY2xlYW5lZCA8LSBkcm9wb3V0X211dGFudHMxNV9HT0ZfNDAwbWljICU+JQogIGZpbHRlcighKElEICVpbiUgaWRzX3RvX3JlbW92ZV80MDBtaWMpKQoKIyBQcmludCBzdW1tYXJ5CnByaW50KHBhc3RlKCJOdW1iZXIgb2Ygcm93cyBiZWZvcmUgY2xlYW5pbmc6IiwgbnJvdyhkcm9wb3V0X211dGFudHMxNV9HT0ZfNDAwbWljKSkpCnByaW50KHBhc3RlKCJOdW1iZXIgb2Ygcm93cyBhZnRlciBjbGVhbmluZzoiLCBucm93KGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWNfY2xlYW5lZCkpKQpwcmludChwYXN0ZSgiTnVtYmVyIG9mIHJvd3MgcmVtb3ZlZDoiLCBucm93KGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWMpIC0gbnJvdyhkcm9wb3V0X211dGFudHMxNV9HT0ZfNDAwbWljX2NsZWFuZWQpKSkKcHJpbnQocGFzdGUoIk51bWJlciBvZiB1bmlxdWUgSURzIHJlbW92ZWQ6IiwgbGVuZ3RoKGlkc190b19yZW1vdmVfNDAwbWljKSkpCgojIE9wdGlvbmFsbHksIHlvdSBjYW4gcHJpbnQgdGhlIHJlbW92ZWQgSURzCnByaW50KCJJRHMgcmVtb3ZlZDoiKQpwcmludChpZHNfdG9fcmVtb3ZlXzQwMG1pYykKCiMgQXNzaWduIHRoZSBjbGVhbmVkIGRhdGEgYmFjayB0byBkcm9wb3V0X211dGFudHMxNV9HT0YgaWYgeW91IHdhbnQgdG8gdXBkYXRlIHRoZSBvcmlnaW5hbCB2YXJpYWJsZQpkcm9wb3V0X211dGFudHMxNV9HT0ZfNDAwbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWNfY2xlYW5lZApgYGAKCiMjIyBEcm9wb3V0IE11dGFudHMKU3VtbWFyaXplIHRoZSBudW1iZXIgb2YgcGVyZmVjdHMgYW5kIG11dGFudHMgYXQgZWFjaCBBQSBkaXN0YW5jZSBhZnRlciBmaWx0ZXJpbmc6CmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIENyZWF0ZSBhIGZ1bmN0aW9uIHRvIGNvdW50IHVuaXF1ZSBtdXRJRHMgZm9yIGEgZ2l2ZW4gbnVtYmVyIG9mIG11dGF0aW9ucwpkcm9wb3V0X211dGFudHMxNV9HT0ZfY291bnRfNDAwbWljIDwtIGZ1bmN0aW9uKGRhdGEsIG11dGF0aW9uX2NvdW50KSB7CiAgbGVuZ3RoKHVuaXF1ZShzdWJzZXQoZGF0YSwgbXV0YXRpb25zID09IG11dGF0aW9uX2NvdW50KSRtdXRJRCkpCn0KCiMgQ3JlYXRlIGEgdmVjdG9yIG9mIGNvdW50cyBmb3IgbXV0YXRpb25zIDEtNQpkcm9wb3V0MTVfY291bnRzXzQwMG1pYyA8LSBzYXBwbHkoMTo1LCBmdW5jdGlvbih4KSBkcm9wb3V0X211dGFudHMxNV9HT0ZfY291bnRfNDAwbWljKGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWMsIHgpKQoKIyBDb3VudCBwZXJmZWN0cyBzZXBhcmF0ZWx5CnBlcmZlY3RzX2NvdW50XzQwMG1pYyA8LSBsZW5ndGgodW5pcXVlKHN1YnNldChkcm9wb3V0X211dGFudHMxNV9HT0ZfNDAwbWljLCBtdXRhdGlvbnMgPT0gMCAmIGZpdEQxMUQwMyA8IC0xLjApJG11dElEKSkKCiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSB3aXRoIHRoZSByZXN1bHRzLCBpbmNsdWRpbmcgdGhlIHN1bW1hcnkgcm93CmRyb3BvdXRfbXV0YW50czE1X0dPRl90YWJsZV80MDBtaWMgPC0gZGF0YS5mcmFtZSgKICBNdXRhdGlvbnMgPSBjKCJQZXJmZWN0cyAoZml0IDwgLTEuMCkiLCAiMSBNdXRhdGlvbiIsICIyIE11dGF0aW9ucyIsICIzIE11dGF0aW9ucyIsICI0IE11dGF0aW9ucyIsICI1IE11dGF0aW9ucyIsICJUb3RhbCBNdXRhdGlvbnMiKSwKICBDb3VudCA9IGMocGVyZmVjdHNfY291bnRfNDAwbWljLCBkcm9wb3V0MTVfY291bnRzXzQwMG1pYywgc3VtKGRyb3BvdXQxNV9jb3VudHNfNDAwbWljKSkKKQoKIyBQcmludCB0aGUgdGFibGUKcHJpbnQoZHJvcG91dF9tdXRhbnRzMTVfR09GX3RhYmxlXzQwMG1pYykKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQojIFNhdmUgYXMgLmNzdiBmb3IgdXNlIGluIG90aGVyIFJNRCBmaWxlczoKd3JpdGUuY3N2KGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWMsICdHT0YvT1VUUFVULzQwMHhNSUMvTGliMTUuRDExRDAzLmdvZi4xbnVtcHJ1bmVkQkNzLjVBQS5tdXRzLmZpdG5lc3MtMS5jc3YnLAogICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UsIHF1b3RlPUZBTFNFKQpgYGAKCiMjIyBHT0YgRml0bmVzcwoKKipHT0YgRml0bmVzczoqKiBTZXBhcmF0ZSB0aGUgYGRyb3BvdXRfbXV0YW50czE1X0dPRl9taWNgIGRhdGFzZXQgaW50byB0d28gbmV3IGRhdGFmcmFtZXMsIHdoZXJlIERGMSBjb250YWlucyBwZXJmZWN0cyBhbmQgREYyIGNvbnRhaW5zIG11dGFudHM6CmBgYHtyfQojIENyZWF0ZSBhIGRhdGFmcmFtZSB3aXRoIG11dGF0aW9ucyA9PSAwCmRyb3BvdXRfbXV0YW50czE1X0dPRl9ub19tdXRhdGlvbnNfNDAwbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWNbZHJvcG91dF9tdXRhbnRzMTVfR09GXzQwMG1pYyRtdXRhdGlvbnMgPT0gMCwgXQoKIyBDcmVhdGUgYSBkYXRhZnJhbWUgd2l0aCBtdXRhdGlvbnMgIT0gMApkcm9wb3V0X211dGFudHMxNV9HT0Zfd2l0aF9tdXRhdGlvbnNfNDAwbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWNbZHJvcG91dF9tdXRhbnRzMTVfR09GXzQwMG1pYyRtdXRhdGlvbnMgIT0gMCwgXQpgYGAKCk5vdywgcmUtY29tYmluZSB0aGVzZSBkYXRhZnJhbWVzIHRvIHJldGFpbiBvbmx5IHRoZSBtdXRhbnRzCmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQojIFN0ZXAgMTogUHJlcGFyZSB0aGUgcmVmZXJlbmNlIGRhdGFmcmFtZQpkZl9yZWZlcmVuY2VfNDAwbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9ub19tdXRhdGlvbnNfNDAwbWljICU+JQogIHNlbGVjdChJRCwgZml0RDExRDAzKSAlPiUKICByZW5hbWUocmVmZXJlbmNlX2ZpdEQxMUQwMyA9IGZpdEQxMUQwMykKCiMgU3RlcCAyOiBKb2luIGFuZCBjYWxjdWxhdGUgdGhlIGRpZmZlcmVuY2UKZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3NfNDAwbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl93aXRoX211dGF0aW9uc180MDBtaWMgJT4lCiAgbGVmdF9qb2luKGRmX3JlZmVyZW5jZV80MDBtaWMsIGJ5ID0gIklEIikgJT4lCiAgbXV0YXRlKGZpdEQxMUQwMyA9IGZpdEQxMUQwMyAtIHJlZmVyZW5jZV9maXREMTFEMDMpICU+JQogIHNlbGVjdChJRCwgbXV0SUQsIG11dGF0aW9ucywgZml0RDExRDAzKQoKIyBQcmludCBzdW1tYXJ5IHN0YXRpc3RpY3MKcHJpbnQocGFzdGUoIk51bWJlciBvZiBNdXRhbnRzOiIsIG5yb3coZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3NfNDAwbWljKSkpCnByaW50KHBhc3RlKCJVbmlxdWUgSURzOiIsIGxlbmd0aCh1bmlxdWUoZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3NfNDAwbWljJElEKSkpKQpwcmludChwYXN0ZSgiUmFuZ2Ugb2YgZml0RDExRDAzOiIsIAogICAgICAgICAgICBwYXN0ZShyb3VuZChyYW5nZShkcm9wb3V0X211dGFudHMxNV9HT0ZfZml0bmVzc180MDBtaWMkZml0RDExRDAzLCBuYS5ybSA9IFRSVUUpLCAxKSwgY29sbGFwc2UgPSAiIHRvICIpKSkKYGBgCgpgYGB7ciBjbGFzcy5vdXRwdXQ9Imdvb2RDb2RlIn0KIyBDb3VudCB1bmlxdWUgSURzIHdpdGggbXV0YXRpb25zID09IDAKdW5pcXVlX2lkc196ZXJvX211dGF0aW9ucyA8LSBkcm9wb3V0X211dGFudHMxNV9HT0ZfZml0bmVzc180MDBtaWMgJT4lCiAgZGlzdGluY3QoSUQpICU+JQogIG5yb3coKQoKcHJpbnQocGFzdGUoIk51bWJlciBvZiB1bmlxdWUgSURzOiIsIHVuaXF1ZV9pZHNfemVyb19tdXRhdGlvbnMpKQpgYGAKCioqQm94cGxvdDoqKiBQbG90IG11dGFudCBmaXRuZXNzIHJlbGF0aXZlIHRvIHBhcmVudCB2YXJpYW50IGJ5IG51bWJlciBvZiBtdXRhdGlvbnM6CmBgYHtyfQpHT0ZfbXV0c19maXRuZXNzX2J5X211dHNfcGxvdF80MDBtaWMgPC0gZ2dwbG90KGRyb3BvdXRfbXV0YW50czE1X0dPRl9maXRuZXNzXzQwMG1pYywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IGZhY3RvcihtdXRhdGlvbnMpLCB5ID0gZml0RDExRDAzKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBsYWJzKHRpdGxlID0gImZpdEQxMUQwMyBieSBOdW1iZXIgb2YgTXV0YXRpb25zIiwgeCA9ICJOdW1iZXIgb2YgTXV0YXRpb25zIiwgeSA9ICJmaXREMTFEMDMiKQoKcHJpbnQoR09GX211dHNfZml0bmVzc19ieV9tdXRzX3Bsb3RfNDAwbWljKQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9Cmdnc2F2ZShmaWxlbmFtZSA9ICJHT0YvUExPVFMvNDAweE1JQy9HT0YuNDAweE1JQy5NdXRhbnQuRml0bmVzcy5ieS5NdXRhdGlvbnMucG5nIiwgCiAgICAgICBwbG90ID1HT0ZfbXV0c19maXRuZXNzX2J5X211dHNfcGxvdF80MDBtaWMsCiAgICAgICB3aWR0aCA9IDQuNSwgaGVpZ2h0ID0gNC41LCB1bml0cyA9ICJpbiIpCmBgYAoKKipIaXN0b2dyYW0gb2YgTXV0YW50IEZpdG5lc3M6KiogQ2xlYXJseSBzaG93cyBtdXRhbnQgZml0bmVzcyBpcyBub3JtYWxseSBkaXN0cmlidXRlZC4KYGBge3J9CkdPRl9tdXRzX2ZpdG5lc3NfZGlzdF9wbG90XzQwMG1pYyA8LSBnZ3Bsb3QoZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3NfNDAwbWljLCBhZXMoeCA9IGZpdEQxMUQwMykpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuNSwgZmlsbCA9ICJibHVlIiwgY29sb3IgPSAiYmxhY2siKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgZml0RDExRDAzIiwgeCA9ICJmaXREMTFEMDMiLCB5ID0gIkNvdW50IikKCnByaW50KEdPRl9tdXRzX2ZpdG5lc3NfZGlzdF9wbG90XzQwMG1pYykKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQpnZ3NhdmUoZmlsZW5hbWUgPSAiR09GL1BMT1RTLzQwMHhNSUMvR09GLjQwMHhNSUMuTXV0YW50LkZpdG5lc3MuRGlzdHJpYnV0aW9uLnBuZyIsIAogICAgICAgcGxvdCA9R09GX211dHNfZml0bmVzc19kaXN0X3Bsb3RfNDAwbWljLAogICAgICAgd2lkdGggPSA0LjUsIGhlaWdodCA9IDQuNSwgdW5pdHMgPSAiaW4iKQpgYGAKCiMjIyBHT0YgQWxpZ25tZW50CgoqKkZBU1RBOioqIEdlbmVyYXRlIGEgRkFTVEEgZmlsZSBmcm9tIHRoZSBmaWx0ZXJlZCBgZHJvcG91dF9tdXRhbnRzMTVfR29GYCBwZXJmZWN0cyBkYXRhc2V0IGZvciBHb0YgYW5hbHlzaXM6CmBgYHtyfQojIEZpcnN0LCBsZXQncyBlbnN1cmUgd2UgaGF2ZSB0aGUgY29ycmVjdCB1bmlxdWUgSURzIGZvciBtdXRhdGlvbnMgPT0gMQpkcm9wb3V0X211dGFudHMxNV9HT0ZfNDAwbWljXzFtdXRfdW5pcXVlX2lkcyA8LSBkcm9wb3V0X211dGFudHMxNV9HT0ZfZml0bmVzc180MDBtaWMgJT4lCiAgZmlsdGVyKG11dGF0aW9ucyA9PSAxKSAlPiUKICBkaXN0aW5jdChJRCkgJT4lCiAgcHVsbChJRCkKCiMgTm93LCBsZXQncyB1c2UgdGhlc2UgSURzIHRvIGZpbHRlciB0aGUgZHJvcG91dF9tdXRhbnRzMTVfR09GIGRhdGFzZXQKZHJvcG91dF9tdXRhbnRzMTVfR09GXzQwMG1pY18xbXV0X3VuaXF1ZV9pZF9zZXEgPC0gZHJvcG91dF9tdXRhbnRzMTVfR09GXzQwMG1pYyAlPiUKICBmaWx0ZXIoSUQgJWluJSBkcm9wb3V0X211dGFudHMxNV9HT0ZfNDAwbWljXzFtdXRfdW5pcXVlX2lkcyAmIG11dGF0aW9ucyA9PSAwKSAlPiUKICBzZWxlY3QoSUQsIHNlcSkKCiMgQ3JlYXRlIHRoZSBzZXF1ZW5jZXMgaW4gRkFTVEEgZm9ybWF0CmRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWNfZmFzdGFfY29udGVudCA8LSBwYXN0ZSgiPiIsIGRyb3BvdXRfbXV0YW50czE1X0dPRl80MDBtaWNfMW11dF91bmlxdWVfaWRfc2VxJElELCAiXG4iLCBkcm9wb3V0X211dGFudHMxNV9HT0ZfNDAwbWljXzFtdXRfdW5pcXVlX2lkX3NlcSRzZXEsICJcbiIsIHNlcCA9ICIiLCBjb2xsYXBzZSA9ICIiKQoKIyBEZWZpbmUgdGhlIGZpbGUgcGF0aCBpbiB0aGUgd29ya2luZyBkaXJlY3RvcnkKZHJvcG91dF9tdXRhbnRzMTVfR09GXzQwMG1pY19mYXN0YV9maWxlX3BhdGggPC0gZmlsZS5wYXRoKGdldHdkKCksICJHT0YvTVNBX0Ryb3BvdXRzLzQwMHhNSUMvRkFTVEEvTGliMTUuR29GLnBlcmZlY3RzLjQwMG1pYy5mYXN0YSIpCgojIFdyaXRlIHRoZSBGQVNUQSBjb250ZW50IHRvIHRoZSBmaWxlICgzNyB1bmlxdWUgSUQpCndyaXRlTGluZXMoZHJvcG91dF9tdXRhbnRzMTVfR09GXzQwMG1pY19mYXN0YV9jb250ZW50LCAKICAgICAgICAgICBjb24gPSBkcm9wb3V0X211dGFudHMxNV9HT0ZfNDAwbWljX2Zhc3RhX2ZpbGVfcGF0aCkKYGBgCgoqKkFsaWdubWVudDoqKiBVc2UgdGhlIGBjbHVzdGFsb2AgZXhlY3V0YWJsZSB0byBhbGlnbiB0aGUgcHJvdGVpbiBzZXF1ZW5jZXMgYXNzb2NpYXRlZCB3aXRoIHRoZSBkcm9wb3V0IHBlcmZlY3RzLiBUaGlzIHdpbGwgYWxpZ24gdGhlIEZBU1RBIGZpbGU6ICoqTGliMTUuR29GLnBlcmZlY3RzLmNvbXBsZW1lbnRhdGlvbi5mYXN0YSoqIGZvciB1c2UgaW4gR29GIGFuYWx5c2lzLgpgYGB7YmFzaH0KLi9TY3JpcHRzL2NsdXN0YWxvIC1pIEdPRi9NU0FfRHJvcG91dHMvNDAweE1JQy9GQVNUQS9MaWIxNS5Hb0YucGVyZmVjdHMuNDAwbWljLmZhc3RhIC1vIEdPRi9NU0FfRHJvcG91dHMvNDAweE1JQy9GQVNUQS9MaWIxNS5Hb0YucGVyZmVjdHMuNDAwbWljLnRyZWUuYWxpZ25lZC5tb2QuYWxuIC0tb3V0Zm10PWNsdXN0YWwgLS1mb3JjZQpgYGAKCioqTWFwcGluZyBSZXNpZHVlczoqKiBVc2UgdGhlIGZvbGxvd2luZyBgbWFwLmFsaWduZWQucmVzaWR1ZXMucHlgIHB5dGhvbiBzY3JpcHQgdG8gZ2VuZXJhdGUgY3N2IGZpbGVzIGZvciBlYWNoIGRlc2lnbmVkIGhvbW9sb2cgdGhhdCBtYXBzIHJlc2lkdWUgcG9zaXRpb25zIG9mIGVhY2ggQS5BLiBmcm9tIHRoZSBhbGlnbm1lbnQgRkFTVEE6CmBgYHtweXRob259CmltcG9ydCB0aW1lCmltcG9ydCBjc3YKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKI0lOUFVUUzoKCmJhc2VfcGF0aCA9ICIiCnRyZWVzX3BhdGhfcHJlZml4ID0gYmFzZV9wYXRoKyIiCgojY2x1c3RhbCBmb3JtYXQgYWxpZ25tZW50IGZpbGUKYWxpZ25fZmlsZV9pbiA9IFt0cmVlc19wYXRoX3ByZWZpeCsiR09GL01TQV9Ecm9wb3V0cy80MDB4TUlDL0ZBU1RBL0xpYjE1LkdvRi5wZXJmZWN0cy40MDBtaWMudHJlZS5hbGlnbmVkLm1vZC5hbG4iXQoKI251bWJlciBvZiBzZXFzIGluIGVhY2ggYWxpZ25tZW50IGZpbGUKbnVtX3NhbXBsZXNfaW5fZmlsZSA9IFszOF0gI05ldyBGQVNUQSB3LyBtdXRhbnQgZml0ID4gLTEgKCsxIGZyb20gYWN0dWFsIGZpbGUgY291bnQpCgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiNPVVRQVVRTOgoKbXNhX21hcF9vdXRfcGF0aCA9IFt0cmVlc19wYXRoX3ByZWZpeCsiR09GL01TQV9Ecm9wb3V0cy80MDB4TUlDLyJdCgojIExvb3AgdG8gZ2VuZXJhdGUgLmNzdiBmaWxlcyBmb3IgZWFjaCBJRApmb3IgYWxuaSBpbiByYW5nZSgxKTojbGVuKGFsaWduX2ZpbGVfaW4pKToKICAgICNwcmludChhbG5pKQogICAgCiAgICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgICAjVkFSSUFCTEVTOgogICAgCiAgICAjSUQgYXMga2V5LCBhbGlnbiBhcyB2YWx1ZQogICAgYWxpZ25fZGljdCA9IGRpY3QoKQogICAgCiAgICAjbnVtX3NhbXBsZXMKICAgIG51bV9zYW1wbGVzID0gbnVtX3NhbXBsZXNfaW5fZmlsZVthbG5pXQogICAgCiAgICAjcG9zIGtleSwgY29uc2Vuc3VzIHBvcyB2YWwKICAgIElEYWFkaWN0bGlzdCA9IFtkaWN0KCkgZm9yIHggaW4gcmFuZ2UobnVtX3NhbXBsZXMpXQogICAgCiAgICBJRHRvaW5kZXhkaWN0ID0gZGljdCgpCiAgICBpbmRleGR0b0lEaWN0ID0gZGljdCgpCiAgICAKICAgICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAgICNDT0RFOgogICAgCiAgICBsaW5lX2NvdW50ID0gMAogICAgI2xvb3Agb3ZlciBhbGwgYWxpZ25tZW50czoKICAgIHByaW50KGFsaWduX2ZpbGVfaW5bYWxuaV0pCiAgICBmb3IgbGluZSBpbiBvcGVuKGFsaWduX2ZpbGVfaW5bYWxuaV0pOgogICAgICAgICNza2lwIGhlYWRlcgogICAgICAgIGlmIGxpbmVfY291bnQgPiAxOgogICAgICAgICAgICBsaXN0V29yZHMgPSBsaW5lLnNwbGl0KCcgICAgJykKICAgICAgICAgICAgSUQgPSBsaXN0V29yZHNbMF0KICAgICAgICAgICAgYWxpZ24gPSBsaW5lWzE2Ol0ucnN0cmlwKCkKICAgICAgICAgICAgaWYgSUQuc3RyaXAoKSAhPSAiIjoKICAgICAgICAgICAgICAgIGFsaWduX2RpY3RbSURdID0gYWxpZ25fZGljdC5nZXQoSUQsICIiKSArIGFsaWduLnJlcGxhY2UoIiAiLCAiIikKICAgICAgICBsaW5lX2NvdW50ICs9IDEKICAgIAogICAgI3ByaW50KCJOUF80MTQ1OTAiKQogICAgI3ByaW50KGFsaWduX2RpY3RbIk5QXzQxNDU5MCJdKQoKICAgIGNvdW50ZXIgPSAwCiAgICBmb3IgSUQgaW4gYWxpZ25fZGljdDoKICAgICAgICAjcHJpbnQoSUQpCiAgICAgICAgI3ByaW50KGFsaWduX2RpY3RbSURdKQogICAgICAgIElEdG9pbmRleGRpY3RbSURdID0gY291bnRlcgogICAgICAgIGluZGV4ZHRvSURpY3RbY291bnRlcl09SUQKICAgICAgICBhbGlnbiA9IGFsaWduX2RpY3RbSURdCiAgICAgICAgCiAgICAgICAgYWFjb3VudGVyID0gMQogICAgICAgIAogICAgICAgIAogICAgICAgIGZvciBpIGluIHJhbmdlKGxlbihhbGlnbikpOgogICAgICAgICAgICBpZiBhbGlnbltpXSAhPSAiLSI6CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICNwcmludChzdHIoY291bnRlcikrIiAiK3N0cihhYWNvdW50ZXIpKQogICAgICAgICAgICAgICAgSURhYWRpY3RsaXN0W2NvdW50ZXJdW2FhY291bnRlcl09aSsxCiAgICAgICAgICAgICAgICBhYWNvdW50ZXIgKz0gMQogICAgICAgIGNvdW50ZXIgKz0gMQogICAgICAgIAogICAgI3ByaW50KGxlbihJRGFhZGljdGxpc3QpKQogICAgZm9yIGkgaW4gcmFuZ2UobGVuKElEYWFkaWN0bGlzdCktMSk6CiAgICAgICAgI3ByaW50KGluZGV4ZHRvSURpY3RbaV0pCiAgICAgICAgI3ByaW50KGkpCiAgICAgICAgI3ByaW50KGFsbmkpCiAgICAgICAgI3ByaW50KGluZGV4ZHRvSURpY3RbaV0pCiAgICAgICAgY3N2ZmlsZSA9IG9wZW4oc3RyKG1zYV9tYXBfb3V0X3BhdGhbYWxuaV0raW5kZXhkdG9JRGljdFtpXSsiLmNzdiIpLCAndycpCiAgICAgICAgZmllbGRuYW1lcyA9IFsnb3J0aF9hYW51bScsJ21zYV9hYW51bSddCiAgICAgICAgd3JpdGVyID0gY3N2LkRpY3RXcml0ZXIoY3N2ZmlsZSwgZmllbGRuYW1lcz1maWVsZG5hbWVzKQogICAgICAgIHdyaXRlci53cml0ZWhlYWRlcigpCiAgICAgICAgZm9yIGogaW4gSURhYWRpY3RsaXN0W2ldOgogICAgICAgICAgICAjcHJpbnQoc3RyKGopKyIgIitzdHIoSURhYWRpY3RsaXN0W2ldW2pdKSkKICAgICAgICAgICAgI3NhdmUgYWxsIGRhdGE6CiAgICAgICAgICAgIHdyaXRlci53cml0ZXJvdyh7J29ydGhfYWFudW0nOnN0cihqKSwnbXNhX2FhbnVtJzpzdHIoSURhYWRpY3RsaXN0W2ldW2pdKX0pCiAgICAgICAgY3N2ZmlsZS5jbG9zZSgpCmBgYAoKIyMjIEdPRiBQbG90cwoKRmluZCBHb0YgUGVyZmVjdHMgZm9yIERyb3BvdXRzCmBgYHtyfQojIENyZWF0ZSBhIGRhdGEgZnJhbWUgb2YgdW5pcXVlIElEcwptdXRhbnRzMTVfdG9fcGxvdF80MDBtaWMgPC0gZHJvcG91dF9tdXRhbnRzMTVfR09GX2ZpdG5lc3NfNDAwbWljICU+JQogIGZpbHRlcihtdXRhdGlvbnMgPT0gMSkgJT4lCiAgZGlzdGluY3QoSUQpICU+JQogIHNlbGVjdChJRCkKYGBgCgpgYGB7ciBjbGFzcy5vdXRwdXQ9Imdvb2RDb2RlIn0KIyBJbml0aWFsaXplIGFuIGVtcHR5IHZlY3RvciB0byBzdG9yZSBJRHMgb2YgbXV0YW50cyB0byBiZSByZW1vdmVkCm11dGFudHNfdG9fcmVtb3ZlXzQwMG1pYyA8LSBjaGFyYWN0ZXIoKQoKIyBDaGVjayBmb3IgbWlzc2luZyBNU0EgZmlsZXMKZm9yIChpIGluIDE6bnJvdyhtdXRhbnRzMTVfdG9fcGxvdF80MDBtaWMpKSB7CiAgbXV0YW50MTVfY3VycmVudF90ZW1wIDwtIG11dGFudHMxNV90b19wbG90XzQwMG1pYyRJRFtpXQogIGlmICghZmlsZS5leGlzdHMocGFzdGUoIkdPRi9NU0FfRHJvcG91dHMvNDAweE1JQy8iLCBtdXRhbnQxNV9jdXJyZW50X3RlbXAsICIuY3N2Iiwgc2VwID0gIiIpKSkgewogICAgbXV0YW50c190b19yZW1vdmVfNDAwbWljIDwtIGMobXV0YW50c190b19yZW1vdmVfNDAwbWljLCBtdXRhbnQxNV9jdXJyZW50X3RlbXApCiAgfQp9CgojIE91dHB1dCB0aGUgcmVzdWx0cwppZiAobGVuZ3RoKG11dGFudHNfdG9fcmVtb3ZlXzQwMG1pYykgPiAwKSB7CiAgY2F0KCJUaGUgZm9sbG93aW5nIG11dGFudHMgd2lsbCBiZSByZW1vdmVkIGR1ZSB0byBtaXNzaW5nIE1TQSBmaWxlczpcbiIpCiAgcHJpbnQobXV0YW50c190b19yZW1vdmVfNDAwbWljKQogIGNhdCgiXG5Ub3RhbCBudW1iZXIgb2YgbXV0YW50cyB0byBiZSByZW1vdmVkOiIsIGxlbmd0aChtdXRhbnRzX3RvX3JlbW92ZV80MDBtaWMpLCAiXG4iKQogIAogICMgUmVtb3ZlIHRoZSBtdXRhbnRzIHdpdGhvdXQgTVNBIGZpbGVzCiAgbXV0YW50czE1X3RvX3Bsb3RfNDAwbWljIDwtIG11dGFudHMxNV90b19wbG90XzQwMG1pY1shbXV0YW50czE1X3RvX3Bsb3RfNDAwbWljJElEICVpbiUgbXV0YW50c190b19yZW1vdmVfNDAwbWljLCBdCiAgY2F0KCJcbk11dGFudHMgcmVtYWluaW5nOiIsIG5yb3cobXV0YW50czE1X3RvX3Bsb3RfNDAwbWljKSwgIlxuIikKfSBlbHNlIHsKICBjYXQoIkFsbCBtdXRhbnRzIGhhdmUgY29ycmVzcG9uZGluZyBNU0EgZmlsZXMuIE5vIG11dGFudHMgd2lsbCBiZSByZW1vdmVkLlxuIikKfQoKIyBJZiB5b3Ugd2FudCB0byBzZWUgdGhlIHJlbWFpbmluZyBtdXRhbnRzCnByaW50KG11dGFudHMxNV90b19wbG90XzQwMG1pYykKYGBgCgpSZWFkIGluIHRoZSBFLiBjb2xpIG1hcDoKYGBge3J9CmVjb2xpX21hcCA8LSByZWFkLmNzdihmaWxlPXBhc3RlKCJHT0YvTVNBX0Ryb3BvdXRzL0NvbXAvTlBfNDE0NTkwLmNzdiIsIHNlcD0iIiksIAogICAgICAgICAgICAgICAgICAgICAgaGVhZD1UUlVFLCBzZXA9IiwiKQpgYGAKCk1ha2UgYSBuZXcgZGF0YSBmcmFtZSB3aGljaCB3aWxsIGtlZXAgYWxsIGluZm8KYGBge3J9CkdPRl9maXRuZXNzX21hcF80MDBtaWMgPC0gZGF0YS5mcmFtZShwb3NpdGlvbj1udW1lcmljKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFhPWNoYXJhY3RlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9bnVtZXJpYygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXRuZXNzPW51bWVyaWMoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zb3J0aG89bnVtZXJpYygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmdhcD1jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0SUQ9Y2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElEPWNoYXJhY3RlcigpKQoKYW1pbm9hY2lkcyA8LSBkYXRhLmZyYW1lKGFhPWMoJ0cnLCdQJywnQScsJ1YnLCdMJywnSScsJ00nLCdDJywnRicsJ1knLCdXJywnSCcsJ0snLCdSJywnUScsJ04nLCdFJywnRCcsJ1MnLCdUJywnWCcpLAogICAgICAgICAgICAgICAgICAgICAgICAgYWFudW09YygxOjIxKSkKYGBgCgoqKk1TQSBNYXBwaW5nOioqIE1hcCB0aGUgbXV0YW50cyAoZml0bmVzcyBkaWZmZXJlbmNlIGZyb20gcGVyZmVjdHMgPiAwKSBvdmVyIGFsbCBwZXJmZWN0cyAoZml0IDwgLTEpIGZvciBHT0YgYW5hbHlzaXM6CmBgYHtyfQojbG9vcCBvdmVyIGFsbCBwZXJmZWN0cwpmb3IgKGlpaSBpbiAxOm5yb3cobXV0YW50czE1X3RvX3Bsb3RfNDAwbWljKSl7CiAgCiAgI2N1cnJlbnQgb3J0aG9sb2c6CiAgbXV0YW50X2N1cnJlbnRfNDAwbWljIDwtIGFzLmNoYXJhY3RlcihtdXRhbnRzMTVfdG9fcGxvdF80MDBtaWMkSURbaWlpXSkKICAKICAjbGVuZ3RoIG9mIG5hbWUKICBuYW1lX3NpemVfNDAwbWljID0gbmNoYXIocGFzdGUobXV0YW50X2N1cnJlbnRfNDAwbWljLCJfIixzZXA9IiIpKQogIAogICNnZXQgdGhlIE1TQSBtYXBwaW5nCiAgbXV0YW50X21hcF80MDBtaWMgPC0gcmVhZC5jc3YoZmlsZT1wYXN0ZSgiR09GL01TQV9Ecm9wb3V0cy80MDB4TUlDLyIsbXV0YW50X2N1cnJlbnRfNDAwbWljLCIuY3N2IixzZXA9IiIpLGhlYWQ9VFJVRSxzZXA9IiwiKQogIAogICNncmFiIHRoZSBtdXRhbnRzIHdpdGggYSBmaXRuZXNzIGluY3JlYXNlIChHb0YpIGdyZWF0ZXIgdGhhbiB6ZXJvIChkbyBub3QgaW5jbHVkZSBwZXJmZWN0cyBmcm9tIGRhdGFzZXQpCiAgR09GbXV0SURpbmZvX3RlbXBfNDAwbWljIDwtIGRyb3BvdXRfbXV0YW50czE1X0dPRl9maXRuZXNzXzQwMG1pYyAlPiUKICAgIGZpbHRlcihJRCA9PSBtdXRhbnRfY3VycmVudF80MDBtaWMpICU+JQogICAgZmlsdGVyKG11dGF0aW9ucyAhPSAwKSAlPiUKICAgIGZpbHRlcihmaXREMTFEMDMgPj0gMC41KSAjIyNDSEFOR0UgVkFMVUUgQVMgTkVFREVEIChkZWZhdWx0ID0gMC41KQogIAogICMgQ2hlY2sgaWYgR09GbXV0SURpbmZvX3RlbXAgaXMgZW1wdHkKICBpZihucm93KEdPRm11dElEaW5mb190ZW1wXzQwMG1pYykgPT0gMCkgewogICAgd2FybmluZyhwYXN0ZSgiTm8gbm9uLXplcm8gbXV0YXRpb24gZGF0YSBmb3VuZCBmb3IgSUQ6IiwgbXV0YW50X2N1cnJlbnRfNDAwbWljKSkKICAgIG5leHQgICMgU2tpcCB0byB0aGUgbmV4dCBpdGVyYXRpb24gb2YgdGhlIG91dGVyIGxvb3AKICB9CiAgCiAgI2xvb3Agb3ZlciBhbGwgbXV0YW50cyBmb3IgdGhpcyBjb25zdHJ1Y3Q6CiAgZm9yIChtbiBpbiAxOm5yb3coR09GbXV0SURpbmZvX3RlbXBfNDAwbWljKSkgewogICAgCiAgICAjdGhpcyBtdXRhbnRzIGZpdG5lc3MKICAgIGdvZl9maXRfdGVtcF80MDBtaWMgPC0gR09GbXV0SURpbmZvX3RlbXBfNDAwbWljJGZpdEQxMUQwM1ttbl0gICMgb3Igd2hpY2hldmVyIGZpdG5lc3MgY29sdW1uIHlvdSdyZSB1c2luZwogICAgCiAgICAjZ3JhYiB0aGUgbXV0IG5hbWUKICAgIG11dGF0aW9uc19uYW1lc180MDBtaWMgPC0gYXMuY2hhcmFjdGVyKEdPRm11dElEaW5mb190ZW1wXzQwMG1pYyRtdXRJRFttbl0pCiAgICAKICAgICNncmFiIG9ubHkgdGhlIHJlbGV2YW50IHBvcnRpb24gb2YgdGhlIG5hbWUKICAgIG11dGF0aW9uc19uYW1lc180MDBtaWMgPC0gc3Vic3RyKG11dGF0aW9uc19uYW1lc180MDBtaWMsIG5hbWVfc2l6ZSsxLCBuY2hhcihtdXRhdGlvbnNfbmFtZXNfNDAwbWljKSkKICAgIAogICAgIyMgc3BsaXQgbXV0YXRpb24gc3RyaW5nIGF0IG5vbi1kaWdpdHMKICAgIHMgPC0gc3Ryc3BsaXQobXV0YXRpb25zX25hbWVzXzQwMG1pYywgIl8iKQogICAgCiAgICBmb3IgKG11dG51bSBpbiAxOkdPRm11dElEaW5mb190ZW1wXzQwMG1pYyRtdXRhdGlvbnNbbW5dKXsKICAgICAgCiAgICAgICNncmFiIHRoZSBjb3JyZXNwb25kaW5nIG11dGF0aW9uIHN0cmluZwogICAgICBtdXRjdXJyPC1zW1sxXV1bbXV0bnVtXQogICAgICAKICAgICAgI2dldCB0aGUgcG9zaXRpb24KICAgICAgbXV0cG9zIDwtIGFzLm51bWVyaWMoc3RyX2V4dHJhY3QobXV0Y3VyciwgIlswLTldKyIpKQogICAgICAKICAgICAgI2dldCBlbmRpbmcgYWEKICAgICAgdG9fYWEgPC0gc3Vic3RyKG11dGN1cnIsIG5jaGFyKG11dHBvcykrMiwgbmNoYXIobXV0Y3VycikpCiAgICAgIAogICAgICAjZmluZCB0aGUgbnVtYmVyIGluIHRoZSBjb25zZW5zdXMgc2VxCiAgICAgIGdvZl9jb25zX2FhbnVtX2luZGV4IDwtIHdoaWNoKG11dGFudF9tYXBfNDAwbWljJG9ydGhfYWFudW0gPT0gbXV0cG9zKQogICAgICAKICAgICAgaWYgKGxlbmd0aChnb2ZfY29uc19hYW51bV9pbmRleCkgPiAwKSB7CiAgICAgICAgZ29mX2NvbnNfYWFudW0gPC0gbXV0YW50X21hcF80MDBtaWMkbXNhX2FhbnVtW2dvZl9jb25zX2FhbnVtX2luZGV4XQogICAgICAgIAogICAgICAgICNkb2VzIHRoaXMgbWFwIHRvIGEgbm9uLWdhcAogICAgICAgIGlmIChnb2ZfY29uc19hYW51bSAlaW4lIGVjb2xpX21hcCRtc2FfYWFudW0pewogICAgICAgICAgCiAgICAgICAgICAjdGhlIGNvcnJlc3BvbmRpbmcgZS5jb2xpIHJlc2lkdWUKICAgICAgICAgIGVfY29saV9yZXNpZHVlIDwtIGVjb2xpX21hcCRvcnRoX2FhbnVtW3doaWNoKGVjb2xpX21hcCRtc2FfYWFudW0gPT0gZ29mX2NvbnNfYWFudW0pXQogICAgICAgICAgCiAgICAgICAgICAjYWRkIHRoaXMgcG9pbnQgdG8gdGhlIGRhdGEKICAgICAgICAgIEdPRl9maXRuZXNzX21hcF80MDBtaWMgPC0gcmJpbmQoR09GX2ZpdG5lc3NfbWFwXzQwMG1pYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKHBvc2l0aW9uPWVfY29saV9yZXNpZHVlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWE9dG9fYWEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9R09GbXV0SURpbmZvX3RlbXBfNDAwbWljJG11dGF0aW9uc1ttbl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXRuZXNzPWdvZl9maXRfdGVtcF80MDBtaWMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb3NvcnRobz1tdXRwb3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmdhcD0iTm8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0SUQ9R09GbXV0SURpbmZvX3RlbXBfNDAwbWljJG11dElEW21uXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElEPUdPRm11dElEaW5mb190ZW1wXzQwMG1pYyRJRFttbl0pKQogICAgICAgICAgCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICNpZiBpdCdzIGhlcmUgaXQgbWFwcyB0byBhIGdhcAogICAgICAgICAgCiAgICAgICAgICAjYWRkIHRoaXMgcG9pbnQgdG8gdGhlIGRhdGEKICAgICAgICAgIEdPRl9maXRuZXNzX21hcF80MDBtaWMgPC0gcmJpbmQoR09GX2ZpdG5lc3NfbWFwXzQwMG1pYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKHBvc2l0aW9uPS0xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWE9dG9fYWEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGlvbnM9R09GbXV0SURpbmZvX3RlbXBfNDAwbWljJG11dGF0aW9uc1ttbl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXRuZXNzPWdvZl9maXRfdGVtcF80MDBtaWMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb3NvcnRobz1tdXRwb3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmdhcD0iWWVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dElEPUdPRm11dElEaW5mb190ZW1wXzQwMG1pYyRtdXRJRFttbl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJRD1HT0ZtdXRJRGluZm9fdGVtcF80MDBtaWMkSURbbW5dKSkKICAgICAgICAgIAogICAgICAgIH0KICAgICAgfSBlbHNlIHsKICAgICAgICB3YXJuaW5nKHBhc3RlKCJObyBtYXRjaGluZyBvcnRoX2FhbnVtIGZvdW5kIGZvciBtdXRwb3M6IiwgbXV0cG9zLCAiaW4gSUQ6IiwgbXV0YW50X2N1cnJlbnRfNDAwbWljKSkKICAgICAgICAjIFlvdSBtaWdodCB3YW50IHRvIGhhbmRsZSB0aGlzIGNhc2UsIHBlcmhhcHMgYnkgc2tpcHBpbmcgdGhpcyBtdXRhdGlvbiBvciBhZGRpbmcgaXQgdG8gYSBzZXBhcmF0ZSBsaXN0IGZvciByZXZpZXcKICAgICAgfQogICAgfQogIH0KfQpgYGAKCmBgYHtyIGVjaG89RkFMU0V9CndyaXRlLnRhYmxlKEdPRl9maXRuZXNzX21hcF80MDBtaWMsIAogICAgICAgICAgICBmaWxlID0gIkdPRi9PVVRQVVQvNDAweE1JQy9HT0ZfRml0bmVzc19NYXBfNDAwbWljLmNzdiIsIAogICAgICAgICAgICBzZXAgPSAiLCIsIHJvdy5uYW1lcyA9IEYscXVvdGU9Rixjb2wubmFtZXMgPSBUKQpgYGAKCkNvbGxhcHNlIHRoZSBHT0YgZml0bmVzcyB2YWx1ZXMgYnkgYWEgcG9zaXRpb24gYWxvbmcgdGhlIHByb3RlaW4gc2VxdWVuY2U6CmBgYHtyfQpHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zXzQwMG1pYyA8LSBHT0ZfZml0bmVzc19tYXBfNDAwbWljICU+JQogIGZpbHRlcihwb3NpdGlvbiA+IDApICU+JQogIGdyb3VwX2J5KHBvc2l0aW9uKSAlPiUKICBzdW1tYXJpc2UoZml0dmFsPW1lZGlhbihmaXRuZXNzKSwKICAgICAgICAgICAgbnVtcG9pbnRzPW4oKSwKICAgICAgICAgICAgc3RkZml0PXNkKGZpdG5lc3MpLAogICAgICAgICAgICBudW1vcnRobz1sZW5ndGgodW5pcXVlKElEKSkpCmBgYAoKIyMjIyBHT0YgTXV0YW50IE5vLiBQbG90CgpQbG90IHRoZSBudW1iZXIgb2YgZ2Fpbi1vZi1mdW5jdGlvbiBtdXRhbnRzIHJlY292ZXJlZCBmb3IgZWFjaCBhYSBwb3NpdGlvbiBhbG9uZyB0aGUgcHJvdGVpbiBzZXF1ZW5jZSBhbmQgaW5jbHVkZSBjdXRvZmYgbGluZXMgZm9yIDEgU0QgYWJvdmUgdGhlIG1lYW4gbnVtYmVyIG9mIEdPRiBtdXRhbnRzIGFuZCAyIFNEIGFib3ZlIHRoZSBtZWFuIG51bWJlciBvZiBHT0YgbXV0YW50cy4gQWxsIHBvc2l0aW9ucyB3aXRoIEdPRiBtdXRhbnRzIGFib3ZlIHRoZSAyIFNEIGxpbmUgYXJlIGNvbnNpZGVyZWQgc2lnbmlmaWNhbnQgcG9zaXRpb25zIHBvc2l0aXZlbHkgaW5mbHVlbmNpbmcgdGhlIHBhcmVudCB2YXJpYW50cyBhYmlsaXR5IHRvIGNvbXBsZW1lbnQgbWV0YWJvbGljIGZ1bmN0aW9uIGluIHRoZSBFLiBjb2xpIGtub2Nrb3V0IG1vZGVsLgpgYGB7ciBjbGFzcy5vdXRwdXQ9Imdvb2RDb2RlIn0KR29GX3Bsb3RfNDAwbWljIDwtIGdncGxvdChHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zXzQwMG1pYywgYWVzKHg9cG9zaXRpb24sIHk9bnVtcG9pbnRzLCBjb2xvcj1udW1vcnRobykpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSAwLCB5ID0gbWVhbihudW1wb2ludHMpKzIqc2QobnVtcG9pbnRzKSwgCiAgICAgICAgICAgICAgICAgICB4ZW5kID0gMTYwLCAKICAgICAgICAgICAgICAgICAgIHllbmQgPSBtZWFuKG51bXBvaW50cykrMipzZChudW1wb2ludHMpKSxsaW5ldHlwZT0yLGNvbG91ciA9ICJibHVlIikrCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMCwgeSA9IG1lYW4obnVtcG9pbnRzKSwgeGVuZCA9IDE2MCwgeWVuZCA9IG1lYW4obnVtcG9pbnRzKSksbGluZXR5cGU9Mixjb2xvdXIgPSAicmVkIikrCiAgZ2VvbV9wb2ludChzaXplPTEuOCkrCiAgbGFicyh4ID0gIlBvc2l0aW9uIChhYSkiLCB5ID0iTnVtYmVyIG9mIGdhaW4tb2YtZnVuY3Rpb24gbXV0YW50cyIsY29sb3I9IiIpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3cgPSAiYmx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSAicmVkIiwKICAgICAgICAgICAgICAgICAgICAgICBuYW1lPSJOdW0uXG5VbmlxLlxuSG9tby4iLAogICAgICAgICAgICAgICAgICAgICAgIG5hLnZhbHVlPSJncmV5IiwgCiAgICAgICAgICAgICAgICAgICAgICAgbGltaXQgPSBjKDAsMS4xKm1heChHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zXzQwMG1pYyRudW1vcnRobykpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwxNjAsMjApKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImxlZnQiKQpHb0ZfcGxvdF80MDBtaWMgPC0gZ2dFeHRyYTo6Z2dNYXJnaW5hbChHb0ZfcGxvdF80MDBtaWMsdHlwZSA9ICJoaXN0b2dyYW0iLAogICAgICAgICAgICAgICAgICAgIG1hcmdpbnMgPSAieSIsCiAgICAgICAgICAgICAgICAgICAgYmlucz0yMSwKICAgICAgICAgICAgICAgICAgICBjb2wgPSAnYmxhY2snLAogICAgICAgICAgICAgICAgICAgIGZpbGwgPSAncmVkJykKR29GX3Bsb3RfNDAwbWljCmBgYAoKYGBge3IgZWNobz1GQUxTRX0KZ2dzYXZlKGZpbGVuYW1lID0gIkdPRi9QTE9UUy80MDB4TUlDL0dPRi40MDB4TUlDLk11dGFudC5ieS5BQS5Qb3MuMnNpZ21hLnBuZyIsIAogICAgICAgcGxvdCA9IEdvRl9wbG90XzQwMG1pYywKICAgICAgIHdpZHRoID0gNC41LCBoZWlnaHQgPSA0LjUsIHVuaXRzID0gImluIikKYGBgCgpQcmludCBvdXQgYSBzdW1tYXJ5IHRhYmxlIG9mIHNpZ25pZmljYW50IGFhIHBvc2l0aW9uIGFsb25nIHRoZSBwcm90ZWluIHNlcXVlbmNlCmBgYHtyIGNsYXNzLm91dHB1dD0iZ29vZENvZGUifQpHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zXzJzaWdtYV80MDBtaWMgPC0gR09GX2ZpdG5lc3NfY29sbGFwc2VkX2J5X3Bvc180MDBtaWMgJT4lCiAgZmlsdGVyKG51bXBvaW50cyA+PSAobWVhbihHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zXzQwMG1pYyRudW1wb2ludHMpICsKICAgICAgICAgICAgICAgICAgICAgICAgIDIqc2QoR09GX2ZpdG5lc3NfY29sbGFwc2VkX2J5X3Bvc180MDBtaWMkbnVtcG9pbnRzKSkpCgpwcmludChHT0ZfZml0bmVzc19jb2xsYXBzZWRfYnlfcG9zXzJzaWdtYV80MDBtaWMpCmBgYAoKQ2FsY3VsYXRlIGFsbCBEYXRhIGFuZCBTdGF0czoKYGBge3J9CkdPRl9maXRuZXNzX2NvbGxhcHNlZF9hbGxfNDAwbWljIDwtIEdPRl9maXRuZXNzX21hcF80MDBtaWMgJT4lCiAgZmlsdGVyKHBvc2l0aW9uID4gMCkgJT4lCiAgZ3JvdXBfYnkocG9zaXRpb24sIGFhKSAlPiUKICBzdW1tYXJpc2UoZml0dmFsPW1lZGlhbihmaXRuZXNzKSwKICAgICAgICAgICAgbnVtcG9pbnRzPW4oKSwKICAgICAgICAgICAgc3RkZml0PXNkKGZpdG5lc3MpLAogICAgICAgICAgICBudW1vcnRobz1sZW5ndGgodW5pcXVlKElEKSkpCgpnb2ZfYWFfZGltIDwtIG5yb3coYW1pbm9hY2lkcykKZ29mX3JlZl9sZW4gPC0gbnJvdyhlY29saV9tYXApCmBgYAoKYGBge3Igd2FybmluZz1GQUxTRX0KI3RoZXNlIG1hdHJpY2VzIGhhdmUgdGhlIGZpdG5lc3MvbnVtL3NkIGZvciBlYWNoIGFhIGF0IGVhY2ggcG9zaXRpb246CmdvZl9tYXRyaXggPSBtYXRyaXgocmVwKE5BLCBnb2ZfcmVmX2xlbipnb2ZfYWFfZGltKSxucm93PWdvZl9hYV9kaW0sbmNvbD1nb2ZfcmVmX2xlbikKZ29mX21hdHJpeF9udW0gPSBtYXRyaXgocmVwKE5BLCBnb2ZfcmVmX2xlbipnb2ZfYWFfZGltKSxucm93PWdvZl9hYV9kaW0sbmNvbD1nb2ZfcmVmX2xlbikKZ29mX21hdHJpeF9zZCA9IG1hdHJpeChyZXAoTkEsIGdvZl9yZWZfbGVuKmdvZl9hYV9kaW0pLG5yb3c9Z29mX2FhX2RpbSxuY29sPWdvZl9yZWZfbGVuKQpnb2ZfbWF0cml4X251bW9ydGhvID0gbWF0cml4KHJlcChOQSwgZ29mX3JlZl9sZW4qZ29mX2FhX2RpbSksbnJvdz1nb2ZfYWFfZGltLG5jb2w9Z29mX3JlZl9sZW4pCgojcG9wdWxhdGUgbWF0cml4CmZvciAoaSBpbiAxOm5yb3coR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbF80MDBtaWMpKXsKICAKICBnb2ZfbWF0cml4W3doaWNoKGFtaW5vYWNpZHMkYWE9PWFzLmNoYXJhY3RlcihHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsXzQwMG1pYyRhYVtpXSkpLEdPRl9maXRuZXNzX2NvbGxhcHNlZF9hbGxfNDAwbWljJHBvc2l0aW9uW2ldXSA8LSBhcy5udW1lcmljKEdPRl9maXRuZXNzX2NvbGxhcHNlZF9hbGxfNDAwbWljJGZpdHZhbFtpXSkKICBnb2ZfbWF0cml4X251bVt3aGljaChhbWlub2FjaWRzJGFhPT1hcy5jaGFyYWN0ZXIoR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbF80MDBtaWMkYWFbaV0pKSxHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsXzQwMG1pYyRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsXzQwMG1pYyRudW1wb2ludHNbaV0pCiAgZ29mX21hdHJpeF9zZFt3aGljaChhbWlub2FjaWRzJGFhPT1hcy5jaGFyYWN0ZXIoR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbF80MDBtaWMkYWFbaV0pKSxHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsXzQwMG1pYyRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsXzQwMG1pYyRzdGRmaXRbaV0pCiAgZ29mX21hdHJpeF9udW1vcnRob1t3aGljaChhbWlub2FjaWRzJGFhPT1hcy5jaGFyYWN0ZXIoR09GX2ZpdG5lc3NfY29sbGFwc2VkX2FsbF80MDBtaWMkYWFbaV0pKSxHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsXzQwMG1pYyRwb3NpdGlvbltpXV0gPC0gYXMubnVtZXJpYyhHT0ZfZml0bmVzc19jb2xsYXBzZWRfYWxsXzQwMG1pYyRudW1vcnRob1tpXSkKfQoKcm93bmFtZXMoZ29mX21hdHJpeCk8LWFtaW5vYWNpZHMkYWEKY29sbmFtZXMoZ29mX21hdHJpeCk8LWMoMTpnb2ZfcmVmX2xlbikKcm93bmFtZXMoZ29mX21hdHJpeF9udW0pPC1hbWlub2FjaWRzJGFhCmNvbG5hbWVzKGdvZl9tYXRyaXhfbnVtKTwtYygxOmdvZl9yZWZfbGVuKQpyb3duYW1lcyhnb2ZfbWF0cml4X3NkKTwtYW1pbm9hY2lkcyRhYQpjb2xuYW1lcyhnb2ZfbWF0cml4X3NkKTwtYygxOmdvZl9yZWZfbGVuKQpyb3duYW1lcyhnb2ZfbWF0cml4X251bW9ydGhvKTwtYW1pbm9hY2lkcyRhYQpjb2xuYW1lcyhnb2ZfbWF0cml4X251bW9ydGhvKTwtYygxOmdvZl9yZWZfbGVuKQoKZ29mX21hdHJpeF9tZWx0IDwtIG1lbHQoZ29mX21hdHJpeCkKZ29mX21hdHJpeF9udW1fbWVsdCA8LSBtZWx0KGdvZl9tYXRyaXhfbnVtKQpnb2ZfbWF0cml4X3NkX21lbHQgPC0gbWVsdChnb2ZfbWF0cml4X3NkKQpnb2ZfbWF0cml4X251bW9ydGhvX21lbHQgPC0gbWVsdChnb2ZfbWF0cml4X251bW9ydGhvKQoKIyBSZW5hbWUgY29sdW1ucyB0byAiWDEiIGFuZCAiWDIiCm5hbWVzKGdvZl9tYXRyaXhfbWVsdClbbmFtZXMoZ29mX21hdHJpeF9tZWx0KSAlaW4lIGMoIlZhcjEiLCAiVmFyMiIpXSA8LSBjKCJYMSIsICJYMiIpCm5hbWVzKGdvZl9tYXRyaXhfbnVtX21lbHQpW25hbWVzKGdvZl9tYXRyaXhfbnVtX21lbHQpICVpbiUgYygiVmFyMSIsICJWYXIyIildIDwtIGMoIlgxIiwgIlgyIikKbmFtZXMoZ29mX21hdHJpeF9zZF9tZWx0KVtuYW1lcyhnb2ZfbWF0cml4X3NkX21lbHQpICVpbiUgYygiVmFyMSIsICJWYXIyIildIDwtIGMoIlgxIiwgIlgyIikKbmFtZXMoZ29mX21hdHJpeF9udW1vcnRob19tZWx0KVtuYW1lcyhnb2ZfbWF0cml4X251bW9ydGhvX21lbHQpICVpbiUgYygiVmFyMSIsICJWYXIyIildIDwtIGMoIlgxIiwgIlgyIikKCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyA8LSBnb2ZfbWF0cml4X21lbHQgJT4lCiAgZmlsdGVyKFgyID09IDM1KQoKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJG11dHBvc251bSA8LSAwCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRtdXRwb3NudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgyPT0zNSldIDwtIDEKCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bSA8LSAwCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJBIildIDwtIDEyCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJDIildIDwtIDEwCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJEIildIDwtIDUKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IkUiKV0gPC0gNApnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iRiIpXSA8LSAxOQpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iRyIpXSA8LSAxMQpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iSCIpXSA8LSAzCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJJIildIDwtIDE1CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJLIildIDwtIDEKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IkwiKV0gPC0gMTQKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09Ik0iKV0gPC0gMTYKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09Ik4iKV0gPC0gNgpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iUCIpXSA8LSAxNwpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iUSIpXSA8LSA3CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJSIildIDwtIDIKZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJGFhbnVtW3doaWNoKGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRYMT09IlMiKV0gPC0gOQpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkYWFudW1bd2hpY2goZ29mX21hdHJpeF9tZWx0X29ubHlfR09GcG9zJFgxPT0iVCIpXSA8LSA4CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJWIildIDwtIDEzCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJXIildIDwtIDIwCmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJZIildIDwtIDE4CmdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvcyRhYW51bVt3aGljaChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MkWDE9PSJYIildIDwtIDIxCgpnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3Nfd251bSA8LSBnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3MgJT4lCiAgaW5uZXJfam9pbihnb2ZfbWF0cml4X251bV9tZWx0LGJ5PWMoIlgxIiwiWDIiKSkgJT4lCiAgZHBseXI6OnJlbmFtZShtdXRudW09dmFsdWUueSx2YWx1ZT12YWx1ZS54KQpgYGAKCiMjIyMgR09GIFBvc2l0aW9uIFBsb3QKClBsb3QgdGhlIG1lYW4gZml0bmVzcyBvZiBlYWNoIEdvRiBtdXRhdGlvbiBhdCB0aGUgc2lnbmlmaWNhbnQgcG9zaXRpb25zLCB3aXRoIHRoZSBudW1iZXIgb2YgbXV0YW50cyBvYnNlcnZlZCBhdCBlYWNoIEFBOgpgYGB7cn0KIyBEZWZpbmUgdGhlIG9yZGVyIG9mIGFtaW5vIGFjaWRzIGZvciB0aGUgcmVjdGFuZ2xlcwpyZWN0X29yZGVyIDwtIGMoIlQiKQoKIyBDcmVhdGUgYSBkYXRhIGZyYW1lIGZvciB0aGUgcmVjdGFuZ2xlcwpyZWN0X2RhdGEgPC0gZGF0YS5mcmFtZSgKYWFudW0gPSBtYXRjaChyZWN0X29yZGVyLCBjKCJLIiwiUiIsIkgiLCJFIiwiRCIsIk4iLCJRIiwiVCIsIlMiLCJDIiwiRyIsIkEiLCJWIiwiTCIsIkkiLCJNIiwiUCIsIlkiLCJGIiwiVyIsIlgiKSksCnhtaW4gPSBzZXEoMC41LCBieSA9IDEsIGxlbmd0aC5vdXQgPSBsZW5ndGgocmVjdF9vcmRlcikpLAp4bWF4ID0gc2VxKDEuNSwgYnkgPSAxLCBsZW5ndGgub3V0ID0gbGVuZ3RoKHJlY3Rfb3JkZXIpKSkKCiNwbG90IHRoZSBkYXRhIGZyb20gYWxsIG11dGFudHM6CkdPRl9maXRfbnVtbXV0X3Bsb3RfNDAwbWljIDwtIGdncGxvdChnb2ZfbWF0cml4X21lbHRfb25seV9HT0Zwb3Nfd251bSwgCiAgICAgICBhZXMoeD1tdXRwb3NudW0sIHk9YWFudW0sCiAgICAgICAgICAgZmlsbD12YWx1ZSwKICAgICAgICAgICBsYWJlbD1tdXRudW0pKSArCiAgZ2VvbV90aWxlKCkgKwogIGdlb21fdGV4dCgpICsKICAjIEFkZCBibGFjayByZWN0YW5nbGVzCiAgZ2VvbV9yZWN0KGRhdGEgPSByZWN0X2RhdGEsCiAgICAgICAgICAgYWVzKHhtaW4gPSB4bWluLCB4bWF4ID0geG1heCwgeW1pbiA9IGFhbnVtIC0gMC41LCB5bWF4ID0gYWFudW0gKyAwLjUpLAogICAgICAgICAgIGZpbGwgPSBOQSwgY29sb3IgPSAiYmxhY2siLCBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgbGFicyh4ID0gIlBvc2l0aW9uIChhYSkiLAogICAgICAgeSA9IkFtaW5vIGFjaWQiLGNvbG9yPSIiKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAiYmx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgaGlnaCA9ICJyZWQiLAogICAgICAgICAgICAgICAgICAgICAgbmFtZT0iRml0bmVzcyIsCiAgICAgICAgICAgICAgICAgICAgICBuYS52YWx1ZT0iZ3JleSIsCiAgICAgICAgICAgICAgICAgICAgICBsaW1pdCA9IGMoMCwgbWF4KGdvZl9tYXRyaXhfbWVsdF9vbmx5X0dPRnBvc193bnVtJHZhbHVlKSkpICsKICB0aGVtZV9taW5pbWFsKCkrCiAgc2NhbGVfeF9jb250aW51b3VzKG5hbWU9IlBvc2l0aW9uIChhYSkiLAogICAgICAgICAgICAgICAgICAgICBicmVha3M9YygxKSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIjM1IikpKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lPSJBbWlubyBhY2lkIiwgCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcz1jKDEsMiwzLDQsNSw2LDcsOCw5LDEwLDExLDEyLDEzLDE0LDE1LDE2LDE3LDE4LDE5LDIwLDIxKSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIksiLCJSIiwiSCIsIkUiLCJEIiwiTiIsIlEiLCJUIiwiUyIsIkMiLCJHIiwiQSIsIlYiLCJMIiwiSSIsIk0iLCJQIiwiWSIsIkYiLCJXIiwiWCIpKQoKcHJpbnQoR09GX2ZpdF9udW1tdXRfcGxvdF80MDBtaWMpCmBgYAoKYGBge3IgZWNobz1GQUxTRX0KZ2dzYXZlKGZpbGVuYW1lID0gIkdPRi9QTE9UUy80MDB4TUlDL0dPRl80MDB4TUlDLkZpdG5lc3MuYnkuTnVtYmVyLk11dGFudHMucG5nIiwgCiAgICAgICBwbG90ID0gR09GX2ZpdF9udW1tdXRfcGxvdF80MDBtaWMsCiAgICAgICB3aWR0aCA9IDQuNSwgaGVpZ2h0ID0gNC41LCB1bml0cyA9ICJpbiIpCmBgYAoKIyBSZXByb2R1Y2liaWxpdHkKClRoZSBzZXNzaW9uIGluZm9ybWF0aW9uIGlzIHByb3ZpZGVkIGZvciBmdWxsIHJlcHJvZHVjaWJpbGl0eS4KYGBge3J9CmRldnRvb2xzOjpzZXNzaW9uX2luZm8oKQpgYGA=