library(RSQLite)
library(DBI)
library(dplyr)
library(ggplot2)

#misc code

#close any open SQLite connections library(DBI) library(RSQLite) lapply(dbListConnections(SQLite()), dbDisconnect)

#check if old database exists if (file.exists(“rnaseq_analysis_results.db”)) { cat(“Database file exists”) cat(“File size:”, file.size(“rnaseq_analysis_results.db”), “bytes”) cat(“Last modified:”, file.mtime(“rnaseq_analysis_results.db”), “”) } else { cat(“Database file does not exist”) }

#delete the old database file.remove(“rnaseq_analysis_results.db”) cat(“Old database deleted”)

#verify cat(“File exists after deletion:”, file.exists(“rnaseq_analysis_results.db”), “”)

#after restarting R, delete file.remove(“rnaseq_analysis_results.db”) file.exists(“rnaseq_analysis_results.db”) # Should be FALSE


#create database schema function 
create_database_schema <- function(db_path = "rnaseq_analysis_results.db") {
  con <- dbConnect(SQLite(), db_path)
  
  #table 1: statistical analysis with gene annotations
  dbExecute(con, "
    CREATE TABLE IF NOT EXISTS differential_expression (
      dataset_id TEXT,
      comparison_name TEXT,
      gene_id TEXT,
      gene_symbol TEXT,
      log2FoldChange REAL,
      pvalue REAL,
      padj REAL,
      baseMean REAL,
      lfcSE REAL,
      stat REAL,
      PRIMARY KEY (dataset_id, comparison_name, gene_id)
    )
  ")
  
  #table 2: normalized expression for whole dataset
  dbExecute(con, "
    CREATE TABLE IF NOT EXISTS normalized_expression (
      sample_id TEXT,
      gene_id TEXT,
      expression_value REAL,
      PRIMARY KEY (sample_id, gene_id)
    )
  ")
  
  #table with extra information
  dbExecute(con, "
    CREATE TABLE IF NOT EXISTS datasets (
      dataset_id TEXT PRIMARY KEY,
      dataset_name TEXT,
      description TEXT,
      organism TEXT,
      n_samples INTEGER,
      n_genes INTEGER,
      date_added DATE,
      study_design TEXT,
      geo_id TEXT
    )
  ")
  
  dbExecute(con, "
    CREATE TABLE IF NOT EXISTS samples (
      sample_id TEXT PRIMARY KEY,
      dataset_id TEXT,
      original_sample_name TEXT,
      condition TEXT,
      treatment TEXT,
      time_point TEXT,
      batch TEXT,
      cell_line TEXT,
      tissue_type TEXT,
      FOREIGN KEY (dataset_id) REFERENCES datasets (dataset_id)
    )
  ")
  
  #indices for fast querying
  dbExecute(con, "CREATE INDEX IF NOT EXISTS idx_de_gene_id ON differential_expression (gene_id)")
  dbExecute(con, "CREATE INDEX IF NOT EXISTS idx_de_gene_symbol ON differential_expression (gene_symbol)")
  dbExecute(con, "CREATE INDEX IF NOT EXISTS idx_de_dataset ON differential_expression (dataset_id)")
  dbExecute(con, "CREATE INDEX IF NOT EXISTS idx_de_padj ON differential_expression (padj)")
  dbExecute(con, "CREATE INDEX IF NOT EXISTS idx_de_lfc ON differential_expression (log2FoldChange)")
  
  dbExecute(con, "CREATE INDEX IF NOT EXISTS idx_norm_gene ON normalized_expression (gene_id)")
  dbExecute(con, "CREATE INDEX IF NOT EXISTS idx_norm_sample ON normalized_expression (sample_id)")
  dbExecute(con, "CREATE INDEX IF NOT EXISTS idx_norm_expr ON normalized_expression (expression_value)")
  
  dbExecute(con, "CREATE INDEX IF NOT EXISTS idx_samples_condition ON samples (condition)")
  dbExecute(con, "CREATE INDEX IF NOT EXISTS idx_samples_dataset ON samples (dataset_id)")
  
  #creating this for common queries
  dbExecute(con, "
    CREATE VIEW IF NOT EXISTS significant_genes AS
    SELECT dataset_id, comparison_name, gene_id, gene_symbol, 
           log2FoldChange, padj, baseMean
    FROM differential_expression 
    WHERE padj < 0.05 AND ABS(log2FoldChange) > 1
  ")
  
  dbExecute(con, "
    CREATE VIEW IF NOT EXISTS gene_expression_summary AS
    SELECT 
      ne.gene_id,
      COUNT(DISTINCT ne.sample_id) as n_samples,
      AVG(ne.expression_value) as mean_expression,
      MIN(ne.expression_value) as min_expression,
      MAX(ne.expression_value) as max_expression,
      (MAX(ne.expression_value) - MIN(ne.expression_value)) as expression_range
    FROM normalized_expression ne
    GROUP BY ne.gene_id
  ")
  
  dbDisconnect(con)
  message("Database schema created with advanced features!")
}

#create the fresh database
create_database_schema()
#verification
con <- dbConnect(SQLite(), "rnaseq_analysis_results.db")
message("Tables created:")
print(dbListTables(con))
dbDisconnect(con)
process_dataset <- function(dataset_id, final_results, vst_data, comparison_name, 
                           sample_metadata = NULL, db_path = "rnaseq_analysis_results.db") {
  
  con <- dbConnect(SQLite(), db_path)
  
  # DUPLICATE PREVENTION: Check if this exact combination already exists
  existing_de <- dbGetQuery(con, paste0("
    SELECT COUNT(*) as count 
    FROM differential_expression 
    WHERE dataset_id = '", dataset_id, "' AND comparison_name = '", comparison_name, "'
  "))
  
  if (existing_de$count > 0) {
    message("Dataset ", dataset_id, " with comparison ", comparison_name, " already exists. Skipping...")
    dbDisconnect(con)
    return()
  }
  
  message(paste("Processing dataset:", dataset_id, "-", comparison_name))
  
  #1st: Statistical analysis results - Table 1
  
  # reading the csv
  de_results <- read.csv(final_results, stringsAsFactors = FALSE, row.names = 1)
  
  # need to convert the rownames to a separate column
  de_results$gene_id <- rownames(de_results)
  
  # adding required database columns
  de_results$dataset_id <- dataset_id
  de_results$comparison_name <- comparison_name
  
  # handle missing columns 
  required_cols <- c("dataset_id", "comparison_name", "gene_id", "gene_symbol", 
                    "log2FoldChange", "pvalue", "padj", "baseMean")
  optional_cols <- c("lfcSE", "stat")
  
  for (col in optional_cols) {
    if (!col %in% names(de_results)) {
      de_results[[col]] <- NA
    }
  }
  
  # handle missing gene_symbol column
  if (!"gene_symbol" %in% names(de_results)) {
    de_results$gene_symbol <- NA
  }
  
  # select and clean data
  de_table <- de_results[, c(required_cols, optional_cols)]
  de_table <- de_table[!is.na(de_table$gene_id), ]
  
  # insert statistical results
  dbWriteTable(con, "differential_expression", de_table, append = TRUE, row.names = FALSE)
  message(paste("  Added", nrow(de_table), "differential expression results"))
  
  #2nd: Normalized expression data - Table 2
  
  # read the long-format VST data 
  norm_expr <- read.csv(vst_data, stringsAsFactors = FALSE)
  
  # Handle the X column issue - remove row numbers column
  if ("X" %in% names(norm_expr)) {
    norm_expr <- norm_expr[, !names(norm_expr) %in% "X"]
  }
  
  # Clean expression data
  norm_table <- norm_expr[!is.na(norm_expr$expression_value), ]
  
  # DUPLICATE PREVENTION: Remove any existing expression data from these samples
  sample_ids <- unique(norm_table$sample_id)
  if (length(sample_ids) > 0) {
    sample_list <- paste0("'", sample_ids, "'", collapse = ",")
    deleted_rows <- dbExecute(con, paste0("DELETE FROM normalized_expression WHERE sample_id IN (", sample_list, ")"))
    if (deleted_rows > 0) {
      message("  Removed ", deleted_rows, " existing expression values to prevent duplicates")
    }
  }
  
  # Insert expression data in chunks
  chunk_size <- 10000
  total_rows <- nrow(norm_table)
  for (i in seq(1, total_rows, chunk_size)) {
    end_i <- min(i + chunk_size - 1, total_rows)
    chunk <- norm_table[i:end_i, ]
    dbWriteTable(con, "normalized_expression", chunk, append = TRUE, row.names = FALSE)
    
    # Progress indicator for large datasets
    if (total_rows > 50000) {
      message("  Processed ", end_i, "/", total_rows, " expression values")
    }
  }
  
  message(paste("  Added", total_rows, "normalized expression values"))
  
  #3rd: Dataset metadata 
  
  # Check if dataset metadata already exists
  existing_dataset <- dbGetQuery(con, paste0("SELECT COUNT(*) as count FROM datasets WHERE dataset_id = '", dataset_id, "'"))
  
  if (existing_dataset$count == 0) {
    n_samples <- length(unique(norm_table$sample_id))
    n_genes <- length(unique(de_table$gene_id))
    
    # GEO ID mapping for each dataset
  geo_ids <- c("dataset1" = "GSE243564", 
               "dataset2" = "GSE130160",
               "dataset3" = "GSE129221", 
               "dataset4" = "GSE94405",
               "dataset5" = "GSE79688")
  
  dataset_num <- substr(dataset_id, nchar(dataset_id), nchar(dataset_id))
    
    dataset_record <- data.frame(
      dataset_id = dataset_id,
      description = comparison_name,
      organism = "Homo sapiens",
      n_samples = n_samples,
      n_genes = n_genes,
      date_added = as.character(Sys.Date()),
      study_design = comparison_name,
      geo_id = geo_ids[dataset_id],
      stringsAsFactors = FALSE
    )
    
    dbWriteTable(con, "datasets", dataset_record, append = TRUE, row.names = FALSE)
    message("  Added dataset metadata")
  } else {
    message("  Dataset metadata already exists, skipping")
  }
  
  dbDisconnect(con)
  message(paste("  Dataset", dataset_id, "processing complete!"))
}
#Dataset 1 - single comparison
process_dataset(
  dataset_id = "dataset1",
  final_results = "D1_results.csv",
  vst_data = "D1_vst_data.csv",
  comparison_name = "untreated_vs_osimertinib"
)

#Dataset 2 - main comparison (wild type vs exon19 deletion)
process_dataset(
  dataset_id = "dataset2",
  final_results = "D2_results.csv",
  vst_data = "D2_vst_data.csv",
  comparison_name = "wildtype_vs_exon19del"
)

#Dataset 3 - single comparison
process_dataset(
  dataset_id = "dataset3",
  final_results = "D3_results.csv",
  vst_data = "D3_vst_data.csv",
  comparison_name = "control_vs_apatinib"
)

#Dataset 4 - single comparison
process_dataset(
  dataset_id = "dataset4",
  final_results = "D4_results.csv",
  vst_data = "D4_vst_data.csv",
  comparison_name = "resistant_vs_parental"
)

#Dataset 5 - main comparison (treatment effect)
process_dataset(
  dataset_id = "dataset5",
  final_results = "D5_results.csv",
  vst_data = "D5_vst_data.csv",
  comparison_name = "gefitinib_vs_dmso"
)
#database verification

con <- dbConnect(SQLite(), "rnaseq_analysis_results.db")

message("=== DATABASE STATUS ===")
message("File size: ", round(file.size("rnaseq_analysis_results.db")/1024/1024, 2), " MB")
message("Tables in database:")
print(dbListTables(con))

message("\n=== DATASETS LOADED ===")
datasets_info <- dbGetQuery(con, "SELECT dataset_id, dataset_name, n_samples, n_genes, study_design FROM datasets")
print(datasets_info)

message("\n=== COMPARISONS IN DATABASE ===")
comparisons <- dbGetQuery(con, "
  SELECT dataset_id, comparison_name, COUNT(*) as n_genes 
  FROM differential_expression 
  GROUP BY dataset_id, comparison_name
")
print(comparisons)

message("\n=== DATA SUMMARY ===")
de_count <- dbGetQuery(con, "SELECT COUNT(*) as total_de_results FROM differential_expression")
expr_count <- dbGetQuery(con, "SELECT COUNT(*) as total_expression_values FROM normalized_expression")
print(paste("Total differential expression results:", de_count$total_de_results))
print(paste("Total expression measurements:", expr_count$total_expression_values))

message("\n=== SIGNIFICANT GENES PREVIEW ===")
sig_genes <- dbGetQuery(con, "SELECT * FROM significant_genes LIMIT 5")
print(sig_genes)

message("\n=== SAMPLE DATABASE QUERIES ===")
# Test query - genes across datasets
test_query <- dbGetQuery(con, "
  SELECT dataset_id, comparison_name, COUNT(*) as significant_genes
  FROM significant_genes 
  GROUP BY dataset_id, comparison_name
  ORDER BY significant_genes DESC
")
print(test_query)

dbDisconnect(con)

message("\n=== DATABASE READY FOR WEB VIEWER ===")
message("Your database file: rnaseq_analysis_results.db")
message("You can now open this file in any SQLite web viewer")
message("Database creation complete!")

# Function to query any gene across all datasets
query_gene_across_datasets <- function(gene_symbol, db_path = "rnaseq_analysis_results.db") {
  con <- dbConnect(SQLite(), db_path)
  result <- dbGetQuery(con, paste0("
    SELECT dataset_id, comparison_name, gene_symbol, gene_id,
           log2FoldChange, padj, baseMean
    FROM differential_expression 
    WHERE gene_symbol = '", gene_symbol, "' OR gene_id = '", gene_symbol, "'
    ORDER BY padj ASC
  "))
  dbDisconnect(con)
  return(result)
}

# Function to find consistently regulated genes
find_consistent_genes <- function(min_datasets = 3, max_padj = 0.05, db_path = "rnaseq_analysis_results.db") {
  con <- dbConnect(SQLite(), db_path)
  result <- dbGetQuery(con, paste0("
    SELECT gene_symbol, 
           COUNT(*) as n_datasets,
           AVG(log2FoldChange) as avg_lfc,
           MIN(padj) as best_padj,
           GROUP_CONCAT(dataset_id || ':' || comparison_name, '; ') as found_in
    FROM differential_expression 
    WHERE padj <= ", max_padj, " AND gene_symbol IS NOT NULL AND gene_symbol != ''
    GROUP BY gene_symbol
    HAVING COUNT(*) >= ", min_datasets, "
    ORDER BY n_datasets DESC, best_padj ASC
  "))
  dbDisconnect(con)
  return(result)
}

message("\n=== EXAMPLE ADVANCED QUERIES ===")
message("Try these functions:")
message("query_gene_across_datasets('BRCA1')")
message("find_consistent_genes(min_datasets = 3)")
#advanced queries functions - good to have


# Function to query any gene across all datasets
query_gene_across_datasets <- function(gene_symbol, db_path = "rnaseq_analysis_results.db") {
  con <- dbConnect(SQLite(), db_path)
  result <- dbGetQuery(con, paste0("
    SELECT dataset_id, comparison_name, gene_symbol, gene_id,
           log2FoldChange, padj, baseMean
    FROM differential_expression 
    WHERE gene_symbol = '", gene_symbol, "' OR gene_id = '", gene_symbol, "'
    ORDER BY padj ASC
  "))
  dbDisconnect(con)
  return(result)
}

# Function to find consistently regulated genes
find_consistent_genes <- function(min_datasets = 3, max_padj = 0.05, db_path = "rnaseq_analysis_results.db") {
  con <- dbConnect(SQLite(), db_path)
  result <- dbGetQuery(con, paste0("
    SELECT gene_symbol, 
           COUNT(*) as n_datasets,
           AVG(log2FoldChange) as avg_lfc,
           MIN(padj) as best_padj,
           GROUP_CONCAT(dataset_id || ':' || comparison_name, '; ') as found_in
    FROM differential_expression 
    WHERE padj <= ", max_padj, " AND gene_symbol IS NOT NULL AND gene_symbol != ''
    GROUP BY gene_symbol
    HAVING COUNT(*) >= ", min_datasets, "
    ORDER BY n_datasets DESC, best_padj ASC
  "))
  dbDisconnect(con)
  return(result)
}

message("\n=== EXAMPLE ADVANCED QUERIES ===")
message("Try these functions:")
message("query_gene_across_datasets('BRCA1')")
message("find_consistent_genes(min_datasets = 3)")

#advanced queries - notes
# Query 1: Dataset overview with GEO information
showcase_dataset_overview <- function(db_path = "rnaseq_analysis_results.db") {
  con <- dbConnect(SQLite(), db_path)
  result <- dbGetQuery(con, "
    SELECT 
      d.dataset_name,
      d.geo_id,
      d.description,
      d.n_samples,
      d.n_genes,
      COUNT(de.gene_id) as total_tested_genes,
      COUNT(CASE WHEN de.padj < 0.05 THEN 1 END) as significant_genes,
      ROUND(COUNT(CASE WHEN de.padj < 0.05 THEN 1 END) * 100.0 / COUNT(de.gene_id), 2) as percent_significant
    FROM datasets d 
    LEFT JOIN differential_expression de ON d.dataset_id = de.dataset_id
    GROUP BY d.dataset_id
    ORDER BY d.dataset_id
  ")
  dbDisconnect(con)
  return(result)
}

# Query 2: Top upregulated and downregulated genes per dataset
showcase_top_genes_per_dataset <- function(top_n = 5, db_path = "rnaseq_analysis_results.db") {
  con <- dbConnect(SQLite(), db_path)
  
  # Top upregulated
  up_genes <- dbGetQuery(con, paste0("
    SELECT 
      dataset_id,
      comparison_name,
      gene_symbol,
      log2FoldChange,
      padj,
      'upregulated' as direction
    FROM differential_expression 
    WHERE padj < 0.05 AND log2FoldChange > 0
    GROUP BY dataset_id
    HAVING log2FoldChange = MAX(log2FoldChange)
    ORDER BY dataset_id
  "))
  
  # Top downregulated  
  down_genes <- dbGetQuery(con, paste0("
    SELECT 
      dataset_id,
      comparison_name,
      gene_symbol,
      log2FoldChange,
      padj,
      'downregulated' as direction
    FROM differential_expression 
    WHERE padj < 0.05 AND log2FoldChange < 0
    GROUP BY dataset_id
    HAVING log2FoldChange = MIN(log2FoldChange)
    ORDER BY dataset_id
  "))
  
  dbDisconnect(con)
  
  # Combine results
  result <- rbind(up_genes, down_genes)
  return(result[order(result$dataset_id, result$direction), ])
}

# Query 3: Cross-dataset gene expression correlation
showcase_expression_patterns <- function(genes = c("EGFR", "TP53", "MYC"), db_path = "rnaseq_analysis_results.db") {
  con <- dbConnect(SQLite(), db_path)
  
  gene_list <- paste0("'", genes, "'", collapse = ",")
  result <- dbGetQuery(con, paste0("
    SELECT 
      de.gene_symbol,
      de.dataset_id,
      d.geo_id,
      de.comparison_name,
      de.log2FoldChange,
      de.padj,
      CASE 
        WHEN de.padj < 0.001 THEN 'highly_significant'
        WHEN de.padj < 0.05 THEN 'significant'
        ELSE 'not_significant'
      END as significance_level
    FROM differential_expression de
    JOIN datasets d ON de.dataset_id = d.dataset_id
    WHERE de.gene_symbol IN (", gene_list, ")
    ORDER BY de.gene_symbol, de.dataset_id
  "))
  
  dbDisconnect(con)
  return(result)
}

# Query 4: Sample expression distribution analysis
showcase_expression_distribution <- function(db_path = "rnaseq_analysis_results.db") {
  con <- dbConnect(SQLite(), db_path)
  result <- dbGetQuery(con, "
    SELECT 
      ROUND(expression_value) as expression_level,
      COUNT(*) as frequency
    FROM normalized_expression 
    WHERE expression_value BETWEEN 0 AND 15
    GROUP BY ROUND(expression_value)
    ORDER BY expression_level
  ")
  dbDisconnect(con)
  return(result)
}

# Query 5: Pathway-relevant genes (focusing on cancer/drug resistance)
showcase_pathway_genes <- function(db_path = "rnaseq_analysis_results.db") {
  # Cancer and drug resistance related genes
  cancer_genes <- c("TP53", "BRCA1", "BRCA2", "EGFR", "KRAS", "PIK3CA", "APC", "MYC", 
                   "PTEN", "RB1", "CDKN2A", "MLH1", "BRAF", "ERBB2", "MDM2")
  
  con <- dbConnect(SQLite(), db_path)
  
  gene_list <- paste0("'", cancer_genes, "'", collapse = ",")
  result <- dbGetQuery(con, paste0("
    SELECT 
      de.gene_symbol,
      COUNT(*) as datasets_found_in,
      AVG(de.log2FoldChange) as avg_log2fc,
      MIN(de.padj) as best_pvalue,
      COUNT(CASE WHEN de.padj < 0.05 THEN 1 END) as significant_datasets
    FROM differential_expression de
    WHERE de.gene_symbol IN (", gene_list, ")
    GROUP BY de.gene_symbol
    HAVING COUNT(*) >= 2
    ORDER BY significant_datasets DESC, best_pvalue ASC
  "))
  
  dbDisconnect(con)
  return(result)
}

message("\n=== SHOWCASE QUERIES FOR DISSERTATION ===")
message("Use these functions to demonstrate your database capabilities:")
message("1. showcase_dataset_overview() - Complete dataset summary with GEO IDs")
message("2. showcase_top_genes_per_dataset() - Most regulated genes per study") 
message("3. showcase_expression_patterns(c('EGFR', 'TP53')) - Track specific genes across datasets")
message("4. showcase_expression_distribution() - Expression level distributions")
message("5. showcase_pathway_genes() - Cancer-relevant genes across all studies")
message("\nThese queries showcase cross-dataset analysis, statistical insights, and biological relevance!")

file.exists("rnaseq_analysis_results.db")
LS0tDQp0aXRsZTogIlNRTGl0ZSBEYXRhYmFzZSAtIFJlc3VsdHMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7cn0NCmxpYnJhcnkoUlNRTGl0ZSkNCmxpYnJhcnkoREJJKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ2dwbG90MikNCmBgYA0KDQojbWlzYyBjb2RlDQoNCiNjbG9zZSBhbnkgb3BlbiBTUUxpdGUgY29ubmVjdGlvbnMNCmxpYnJhcnkoREJJKQ0KbGlicmFyeShSU1FMaXRlKQ0KbGFwcGx5KGRiTGlzdENvbm5lY3Rpb25zKFNRTGl0ZSgpKSwgZGJEaXNjb25uZWN0KQ0KDQojY2hlY2sgaWYgb2xkIGRhdGFiYXNlIGV4aXN0cw0KaWYgKGZpbGUuZXhpc3RzKCJybmFzZXFfYW5hbHlzaXNfcmVzdWx0cy5kYiIpKSB7DQogIGNhdCgiRGF0YWJhc2UgZmlsZSBleGlzdHNcbiIpDQogIGNhdCgiRmlsZSBzaXplOiIsIGZpbGUuc2l6ZSgicm5hc2VxX2FuYWx5c2lzX3Jlc3VsdHMuZGIiKSwgImJ5dGVzXG4iKQ0KICBjYXQoIkxhc3QgbW9kaWZpZWQ6IiwgZmlsZS5tdGltZSgicm5hc2VxX2FuYWx5c2lzX3Jlc3VsdHMuZGIiKSwgIlxuIikNCn0gZWxzZSB7DQogIGNhdCgiRGF0YWJhc2UgZmlsZSBkb2VzIG5vdCBleGlzdFxuIikNCn0NCg0KI2RlbGV0ZSB0aGUgb2xkIGRhdGFiYXNlDQpmaWxlLnJlbW92ZSgicm5hc2VxX2FuYWx5c2lzX3Jlc3VsdHMuZGIiKQ0KY2F0KCJPbGQgZGF0YWJhc2UgZGVsZXRlZFxuIikNCg0KI3ZlcmlmeQ0KY2F0KCJGaWxlIGV4aXN0cyBhZnRlciBkZWxldGlvbjoiLCBmaWxlLmV4aXN0cygicm5hc2VxX2FuYWx5c2lzX3Jlc3VsdHMuZGIiKSwgIlxuIikNCg0KI2FmdGVyIHJlc3RhcnRpbmcgUiwgZGVsZXRlDQpmaWxlLnJlbW92ZSgicm5hc2VxX2FuYWx5c2lzX3Jlc3VsdHMuZGIiKQ0KZmlsZS5leGlzdHMoInJuYXNlcV9hbmFseXNpc19yZXN1bHRzLmRiIikgICMgU2hvdWxkIGJlIEZBTFNFDQoNCg0KYGBge3J9DQoNCiNjcmVhdGUgZGF0YWJhc2Ugc2NoZW1hIGZ1bmN0aW9uIA0KY3JlYXRlX2RhdGFiYXNlX3NjaGVtYSA8LSBmdW5jdGlvbihkYl9wYXRoID0gInJuYXNlcV9hbmFseXNpc19yZXN1bHRzLmRiIikgew0KICBjb24gPC0gZGJDb25uZWN0KFNRTGl0ZSgpLCBkYl9wYXRoKQ0KICANCiAgI3RhYmxlIDE6IHN0YXRpc3RpY2FsIGFuYWx5c2lzIHdpdGggZ2VuZSBhbm5vdGF0aW9ucw0KICBkYkV4ZWN1dGUoY29uLCAiDQogICAgQ1JFQVRFIFRBQkxFIElGIE5PVCBFWElTVFMgZGlmZmVyZW50aWFsX2V4cHJlc3Npb24gKA0KICAgICAgZGF0YXNldF9pZCBURVhULA0KICAgICAgY29tcGFyaXNvbl9uYW1lIFRFWFQsDQogICAgICBnZW5lX2lkIFRFWFQsDQogICAgICBnZW5lX3N5bWJvbCBURVhULA0KICAgICAgbG9nMkZvbGRDaGFuZ2UgUkVBTCwNCiAgICAgIHB2YWx1ZSBSRUFMLA0KICAgICAgcGFkaiBSRUFMLA0KICAgICAgYmFzZU1lYW4gUkVBTCwNCiAgICAgIGxmY1NFIFJFQUwsDQogICAgICBzdGF0IFJFQUwsDQogICAgICBQUklNQVJZIEtFWSAoZGF0YXNldF9pZCwgY29tcGFyaXNvbl9uYW1lLCBnZW5lX2lkKQ0KICAgICkNCiAgIikNCiAgDQogICN0YWJsZSAyOiBub3JtYWxpemVkIGV4cHJlc3Npb24gZm9yIHdob2xlIGRhdGFzZXQNCiAgZGJFeGVjdXRlKGNvbiwgIg0KICAgIENSRUFURSBUQUJMRSBJRiBOT1QgRVhJU1RTIG5vcm1hbGl6ZWRfZXhwcmVzc2lvbiAoDQogICAgICBzYW1wbGVfaWQgVEVYVCwNCiAgICAgIGdlbmVfaWQgVEVYVCwNCiAgICAgIGV4cHJlc3Npb25fdmFsdWUgUkVBTCwNCiAgICAgIFBSSU1BUlkgS0VZIChzYW1wbGVfaWQsIGdlbmVfaWQpDQogICAgKQ0KICAiKQ0KICANCiAgI3RhYmxlIHdpdGggZXh0cmEgaW5mb3JtYXRpb24NCiAgZGJFeGVjdXRlKGNvbiwgIg0KICAgIENSRUFURSBUQUJMRSBJRiBOT1QgRVhJU1RTIGRhdGFzZXRzICgNCiAgICAgIGRhdGFzZXRfaWQgVEVYVCBQUklNQVJZIEtFWSwNCiAgICAgIGRhdGFzZXRfbmFtZSBURVhULA0KICAgICAgZGVzY3JpcHRpb24gVEVYVCwNCiAgICAgIG9yZ2FuaXNtIFRFWFQsDQogICAgICBuX3NhbXBsZXMgSU5URUdFUiwNCiAgICAgIG5fZ2VuZXMgSU5URUdFUiwNCiAgICAgIGRhdGVfYWRkZWQgREFURSwNCiAgICAgIHN0dWR5X2Rlc2lnbiBURVhULA0KICAgICAgZ2VvX2lkIFRFWFQNCiAgICApDQogICIpDQogIA0KICBkYkV4ZWN1dGUoY29uLCAiDQogICAgQ1JFQVRFIFRBQkxFIElGIE5PVCBFWElTVFMgc2FtcGxlcyAoDQogICAgICBzYW1wbGVfaWQgVEVYVCBQUklNQVJZIEtFWSwNCiAgICAgIGRhdGFzZXRfaWQgVEVYVCwNCiAgICAgIG9yaWdpbmFsX3NhbXBsZV9uYW1lIFRFWFQsDQogICAgICBjb25kaXRpb24gVEVYVCwNCiAgICAgIHRyZWF0bWVudCBURVhULA0KICAgICAgdGltZV9wb2ludCBURVhULA0KICAgICAgYmF0Y2ggVEVYVCwNCiAgICAgIGNlbGxfbGluZSBURVhULA0KICAgICAgdGlzc3VlX3R5cGUgVEVYVCwNCiAgICAgIEZPUkVJR04gS0VZIChkYXRhc2V0X2lkKSBSRUZFUkVOQ0VTIGRhdGFzZXRzIChkYXRhc2V0X2lkKQ0KICAgICkNCiAgIikNCiAgDQogICNpbmRpY2VzIGZvciBmYXN0IHF1ZXJ5aW5nDQogIGRiRXhlY3V0ZShjb24sICJDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyBpZHhfZGVfZ2VuZV9pZCBPTiBkaWZmZXJlbnRpYWxfZXhwcmVzc2lvbiAoZ2VuZV9pZCkiKQ0KICBkYkV4ZWN1dGUoY29uLCAiQ1JFQVRFIElOREVYIElGIE5PVCBFWElTVFMgaWR4X2RlX2dlbmVfc3ltYm9sIE9OIGRpZmZlcmVudGlhbF9leHByZXNzaW9uIChnZW5lX3N5bWJvbCkiKQ0KICBkYkV4ZWN1dGUoY29uLCAiQ1JFQVRFIElOREVYIElGIE5PVCBFWElTVFMgaWR4X2RlX2RhdGFzZXQgT04gZGlmZmVyZW50aWFsX2V4cHJlc3Npb24gKGRhdGFzZXRfaWQpIikNCiAgZGJFeGVjdXRlKGNvbiwgIkNSRUFURSBJTkRFWCBJRiBOT1QgRVhJU1RTIGlkeF9kZV9wYWRqIE9OIGRpZmZlcmVudGlhbF9leHByZXNzaW9uIChwYWRqKSIpDQogIGRiRXhlY3V0ZShjb24sICJDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyBpZHhfZGVfbGZjIE9OIGRpZmZlcmVudGlhbF9leHByZXNzaW9uIChsb2cyRm9sZENoYW5nZSkiKQ0KICANCiAgZGJFeGVjdXRlKGNvbiwgIkNSRUFURSBJTkRFWCBJRiBOT1QgRVhJU1RTIGlkeF9ub3JtX2dlbmUgT04gbm9ybWFsaXplZF9leHByZXNzaW9uIChnZW5lX2lkKSIpDQogIGRiRXhlY3V0ZShjb24sICJDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyBpZHhfbm9ybV9zYW1wbGUgT04gbm9ybWFsaXplZF9leHByZXNzaW9uIChzYW1wbGVfaWQpIikNCiAgZGJFeGVjdXRlKGNvbiwgIkNSRUFURSBJTkRFWCBJRiBOT1QgRVhJU1RTIGlkeF9ub3JtX2V4cHIgT04gbm9ybWFsaXplZF9leHByZXNzaW9uIChleHByZXNzaW9uX3ZhbHVlKSIpDQogIA0KICBkYkV4ZWN1dGUoY29uLCAiQ1JFQVRFIElOREVYIElGIE5PVCBFWElTVFMgaWR4X3NhbXBsZXNfY29uZGl0aW9uIE9OIHNhbXBsZXMgKGNvbmRpdGlvbikiKQ0KICBkYkV4ZWN1dGUoY29uLCAiQ1JFQVRFIElOREVYIElGIE5PVCBFWElTVFMgaWR4X3NhbXBsZXNfZGF0YXNldCBPTiBzYW1wbGVzIChkYXRhc2V0X2lkKSIpDQogIA0KICAjY3JlYXRpbmcgdGhpcyBmb3IgY29tbW9uIHF1ZXJpZXMNCiAgZGJFeGVjdXRlKGNvbiwgIg0KICAgIENSRUFURSBWSUVXIElGIE5PVCBFWElTVFMgc2lnbmlmaWNhbnRfZ2VuZXMgQVMNCiAgICBTRUxFQ1QgZGF0YXNldF9pZCwgY29tcGFyaXNvbl9uYW1lLCBnZW5lX2lkLCBnZW5lX3N5bWJvbCwgDQogICAgICAgICAgIGxvZzJGb2xkQ2hhbmdlLCBwYWRqLCBiYXNlTWVhbg0KICAgIEZST00gZGlmZmVyZW50aWFsX2V4cHJlc3Npb24gDQogICAgV0hFUkUgcGFkaiA8IDAuMDUgQU5EIEFCUyhsb2cyRm9sZENoYW5nZSkgPiAxDQogICIpDQogIA0KICBkYkV4ZWN1dGUoY29uLCAiDQogICAgQ1JFQVRFIFZJRVcgSUYgTk9UIEVYSVNUUyBnZW5lX2V4cHJlc3Npb25fc3VtbWFyeSBBUw0KICAgIFNFTEVDVCANCiAgICAgIG5lLmdlbmVfaWQsDQogICAgICBDT1VOVChESVNUSU5DVCBuZS5zYW1wbGVfaWQpIGFzIG5fc2FtcGxlcywNCiAgICAgIEFWRyhuZS5leHByZXNzaW9uX3ZhbHVlKSBhcyBtZWFuX2V4cHJlc3Npb24sDQogICAgICBNSU4obmUuZXhwcmVzc2lvbl92YWx1ZSkgYXMgbWluX2V4cHJlc3Npb24sDQogICAgICBNQVgobmUuZXhwcmVzc2lvbl92YWx1ZSkgYXMgbWF4X2V4cHJlc3Npb24sDQogICAgICAoTUFYKG5lLmV4cHJlc3Npb25fdmFsdWUpIC0gTUlOKG5lLmV4cHJlc3Npb25fdmFsdWUpKSBhcyBleHByZXNzaW9uX3JhbmdlDQogICAgRlJPTSBub3JtYWxpemVkX2V4cHJlc3Npb24gbmUNCiAgICBHUk9VUCBCWSBuZS5nZW5lX2lkDQogICIpDQogIA0KICBkYkRpc2Nvbm5lY3QoY29uKQ0KICBtZXNzYWdlKCJEYXRhYmFzZSBzY2hlbWEgY3JlYXRlZCB3aXRoIGFkdmFuY2VkIGZlYXR1cmVzISIpDQp9DQoNCiNjcmVhdGUgdGhlIGZyZXNoIGRhdGFiYXNlDQpjcmVhdGVfZGF0YWJhc2Vfc2NoZW1hKCkNCmBgYA0KDQoNCmBgYHtyfQ0KI3ZlcmlmaWNhdGlvbg0KY29uIDwtIGRiQ29ubmVjdChTUUxpdGUoKSwgInJuYXNlcV9hbmFseXNpc19yZXN1bHRzLmRiIikNCm1lc3NhZ2UoIlRhYmxlcyBjcmVhdGVkOiIpDQpwcmludChkYkxpc3RUYWJsZXMoY29uKSkNCmRiRGlzY29ubmVjdChjb24pDQpgYGANCg0KYGBge3J9DQpwcm9jZXNzX2RhdGFzZXQgPC0gZnVuY3Rpb24oZGF0YXNldF9pZCwgZmluYWxfcmVzdWx0cywgdnN0X2RhdGEsIGNvbXBhcmlzb25fbmFtZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfbWV0YWRhdGEgPSBOVUxMLCBkYl9wYXRoID0gInJuYXNlcV9hbmFseXNpc19yZXN1bHRzLmRiIikgew0KICANCiAgY29uIDwtIGRiQ29ubmVjdChTUUxpdGUoKSwgZGJfcGF0aCkNCiAgDQogICMgRFVQTElDQVRFIFBSRVZFTlRJT046IENoZWNrIGlmIHRoaXMgZXhhY3QgY29tYmluYXRpb24gYWxyZWFkeSBleGlzdHMNCiAgZXhpc3RpbmdfZGUgPC0gZGJHZXRRdWVyeShjb24sIHBhc3RlMCgiDQogICAgU0VMRUNUIENPVU5UKCopIGFzIGNvdW50IA0KICAgIEZST00gZGlmZmVyZW50aWFsX2V4cHJlc3Npb24gDQogICAgV0hFUkUgZGF0YXNldF9pZCA9ICciLCBkYXRhc2V0X2lkLCAiJyBBTkQgY29tcGFyaXNvbl9uYW1lID0gJyIsIGNvbXBhcmlzb25fbmFtZSwgIicNCiAgIikpDQogIA0KICBpZiAoZXhpc3RpbmdfZGUkY291bnQgPiAwKSB7DQogICAgbWVzc2FnZSgiRGF0YXNldCAiLCBkYXRhc2V0X2lkLCAiIHdpdGggY29tcGFyaXNvbiAiLCBjb21wYXJpc29uX25hbWUsICIgYWxyZWFkeSBleGlzdHMuIFNraXBwaW5nLi4uIikNCiAgICBkYkRpc2Nvbm5lY3QoY29uKQ0KICAgIHJldHVybigpDQogIH0NCiAgDQogIG1lc3NhZ2UocGFzdGUoIlByb2Nlc3NpbmcgZGF0YXNldDoiLCBkYXRhc2V0X2lkLCAiLSIsIGNvbXBhcmlzb25fbmFtZSkpDQogIA0KICAjMXN0OiBTdGF0aXN0aWNhbCBhbmFseXNpcyByZXN1bHRzIC0gVGFibGUgMQ0KICANCiAgIyByZWFkaW5nIHRoZSBjc3YNCiAgZGVfcmVzdWx0cyA8LSByZWFkLmNzdihmaW5hbF9yZXN1bHRzLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UsIHJvdy5uYW1lcyA9IDEpDQogIA0KICAjIG5lZWQgdG8gY29udmVydCB0aGUgcm93bmFtZXMgdG8gYSBzZXBhcmF0ZSBjb2x1bW4NCiAgZGVfcmVzdWx0cyRnZW5lX2lkIDwtIHJvd25hbWVzKGRlX3Jlc3VsdHMpDQogIA0KICAjIGFkZGluZyByZXF1aXJlZCBkYXRhYmFzZSBjb2x1bW5zDQogIGRlX3Jlc3VsdHMkZGF0YXNldF9pZCA8LSBkYXRhc2V0X2lkDQogIGRlX3Jlc3VsdHMkY29tcGFyaXNvbl9uYW1lIDwtIGNvbXBhcmlzb25fbmFtZQ0KICANCiAgIyBoYW5kbGUgbWlzc2luZyBjb2x1bW5zIA0KICByZXF1aXJlZF9jb2xzIDwtIGMoImRhdGFzZXRfaWQiLCAiY29tcGFyaXNvbl9uYW1lIiwgImdlbmVfaWQiLCAiZ2VuZV9zeW1ib2wiLCANCiAgICAgICAgICAgICAgICAgICAgImxvZzJGb2xkQ2hhbmdlIiwgInB2YWx1ZSIsICJwYWRqIiwgImJhc2VNZWFuIikNCiAgb3B0aW9uYWxfY29scyA8LSBjKCJsZmNTRSIsICJzdGF0IikNCiAgDQogIGZvciAoY29sIGluIG9wdGlvbmFsX2NvbHMpIHsNCiAgICBpZiAoIWNvbCAlaW4lIG5hbWVzKGRlX3Jlc3VsdHMpKSB7DQogICAgICBkZV9yZXN1bHRzW1tjb2xdXSA8LSBOQQ0KICAgIH0NCiAgfQ0KICANCiAgIyBoYW5kbGUgbWlzc2luZyBnZW5lX3N5bWJvbCBjb2x1bW4NCiAgaWYgKCEiZ2VuZV9zeW1ib2wiICVpbiUgbmFtZXMoZGVfcmVzdWx0cykpIHsNCiAgICBkZV9yZXN1bHRzJGdlbmVfc3ltYm9sIDwtIE5BDQogIH0NCiAgDQogICMgc2VsZWN0IGFuZCBjbGVhbiBkYXRhDQogIGRlX3RhYmxlIDwtIGRlX3Jlc3VsdHNbLCBjKHJlcXVpcmVkX2NvbHMsIG9wdGlvbmFsX2NvbHMpXQ0KICBkZV90YWJsZSA8LSBkZV90YWJsZVshaXMubmEoZGVfdGFibGUkZ2VuZV9pZCksIF0NCiAgDQogICMgaW5zZXJ0IHN0YXRpc3RpY2FsIHJlc3VsdHMNCiAgZGJXcml0ZVRhYmxlKGNvbiwgImRpZmZlcmVudGlhbF9leHByZXNzaW9uIiwgZGVfdGFibGUsIGFwcGVuZCA9IFRSVUUsIHJvdy5uYW1lcyA9IEZBTFNFKQ0KICBtZXNzYWdlKHBhc3RlKCIgIEFkZGVkIiwgbnJvdyhkZV90YWJsZSksICJkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiByZXN1bHRzIikpDQogIA0KICAjMm5kOiBOb3JtYWxpemVkIGV4cHJlc3Npb24gZGF0YSAtIFRhYmxlIDINCiAgDQogICMgcmVhZCB0aGUgbG9uZy1mb3JtYXQgVlNUIGRhdGEgDQogIG5vcm1fZXhwciA8LSByZWFkLmNzdih2c3RfZGF0YSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KICANCiAgIyBIYW5kbGUgdGhlIFggY29sdW1uIGlzc3VlIC0gcmVtb3ZlIHJvdyBudW1iZXJzIGNvbHVtbg0KICBpZiAoIlgiICVpbiUgbmFtZXMobm9ybV9leHByKSkgew0KICAgIG5vcm1fZXhwciA8LSBub3JtX2V4cHJbLCAhbmFtZXMobm9ybV9leHByKSAlaW4lICJYIl0NCiAgfQ0KICANCiAgIyBDbGVhbiBleHByZXNzaW9uIGRhdGENCiAgbm9ybV90YWJsZSA8LSBub3JtX2V4cHJbIWlzLm5hKG5vcm1fZXhwciRleHByZXNzaW9uX3ZhbHVlKSwgXQ0KICANCiAgIyBEVVBMSUNBVEUgUFJFVkVOVElPTjogUmVtb3ZlIGFueSBleGlzdGluZyBleHByZXNzaW9uIGRhdGEgZnJvbSB0aGVzZSBzYW1wbGVzDQogIHNhbXBsZV9pZHMgPC0gdW5pcXVlKG5vcm1fdGFibGUkc2FtcGxlX2lkKQ0KICBpZiAobGVuZ3RoKHNhbXBsZV9pZHMpID4gMCkgew0KICAgIHNhbXBsZV9saXN0IDwtIHBhc3RlMCgiJyIsIHNhbXBsZV9pZHMsICInIiwgY29sbGFwc2UgPSAiLCIpDQogICAgZGVsZXRlZF9yb3dzIDwtIGRiRXhlY3V0ZShjb24sIHBhc3RlMCgiREVMRVRFIEZST00gbm9ybWFsaXplZF9leHByZXNzaW9uIFdIRVJFIHNhbXBsZV9pZCBJTiAoIiwgc2FtcGxlX2xpc3QsICIpIikpDQogICAgaWYgKGRlbGV0ZWRfcm93cyA+IDApIHsNCiAgICAgIG1lc3NhZ2UoIiAgUmVtb3ZlZCAiLCBkZWxldGVkX3Jvd3MsICIgZXhpc3RpbmcgZXhwcmVzc2lvbiB2YWx1ZXMgdG8gcHJldmVudCBkdXBsaWNhdGVzIikNCiAgICB9DQogIH0NCiAgDQogICMgSW5zZXJ0IGV4cHJlc3Npb24gZGF0YSBpbiBjaHVua3MNCiAgY2h1bmtfc2l6ZSA8LSAxMDAwMA0KICB0b3RhbF9yb3dzIDwtIG5yb3cobm9ybV90YWJsZSkNCiAgZm9yIChpIGluIHNlcSgxLCB0b3RhbF9yb3dzLCBjaHVua19zaXplKSkgew0KICAgIGVuZF9pIDwtIG1pbihpICsgY2h1bmtfc2l6ZSAtIDEsIHRvdGFsX3Jvd3MpDQogICAgY2h1bmsgPC0gbm9ybV90YWJsZVtpOmVuZF9pLCBdDQogICAgZGJXcml0ZVRhYmxlKGNvbiwgIm5vcm1hbGl6ZWRfZXhwcmVzc2lvbiIsIGNodW5rLCBhcHBlbmQgPSBUUlVFLCByb3cubmFtZXMgPSBGQUxTRSkNCiAgICANCiAgICAjIFByb2dyZXNzIGluZGljYXRvciBmb3IgbGFyZ2UgZGF0YXNldHMNCiAgICBpZiAodG90YWxfcm93cyA+IDUwMDAwKSB7DQogICAgICBtZXNzYWdlKCIgIFByb2Nlc3NlZCAiLCBlbmRfaSwgIi8iLCB0b3RhbF9yb3dzLCAiIGV4cHJlc3Npb24gdmFsdWVzIikNCiAgICB9DQogIH0NCiAgDQogIG1lc3NhZ2UocGFzdGUoIiAgQWRkZWQiLCB0b3RhbF9yb3dzLCAibm9ybWFsaXplZCBleHByZXNzaW9uIHZhbHVlcyIpKQ0KICANCiAgIzNyZDogRGF0YXNldCBtZXRhZGF0YSANCiAgDQogICMgQ2hlY2sgaWYgZGF0YXNldCBtZXRhZGF0YSBhbHJlYWR5IGV4aXN0cw0KICBleGlzdGluZ19kYXRhc2V0IDwtIGRiR2V0UXVlcnkoY29uLCBwYXN0ZTAoIlNFTEVDVCBDT1VOVCgqKSBhcyBjb3VudCBGUk9NIGRhdGFzZXRzIFdIRVJFIGRhdGFzZXRfaWQgPSAnIiwgZGF0YXNldF9pZCwgIiciKSkNCiAgDQogIGlmIChleGlzdGluZ19kYXRhc2V0JGNvdW50ID09IDApIHsNCiAgICBuX3NhbXBsZXMgPC0gbGVuZ3RoKHVuaXF1ZShub3JtX3RhYmxlJHNhbXBsZV9pZCkpDQogICAgbl9nZW5lcyA8LSBsZW5ndGgodW5pcXVlKGRlX3RhYmxlJGdlbmVfaWQpKQ0KICAgIA0KICAgICMgR0VPIElEIG1hcHBpbmcgZm9yIGVhY2ggZGF0YXNldA0KICBnZW9faWRzIDwtIGMoImRhdGFzZXQxIiA9ICJHU0UyNDM1NjQiLCANCiAgICAgICAgICAgICAgICJkYXRhc2V0MiIgPSAiR1NFMTMwMTYwIiwNCiAgICAgICAgICAgICAgICJkYXRhc2V0MyIgPSAiR1NFMTI5MjIxIiwgDQogICAgICAgICAgICAgICAiZGF0YXNldDQiID0gIkdTRTk0NDA1IiwNCiAgICAgICAgICAgICAgICJkYXRhc2V0NSIgPSAiR1NFNzk2ODgiKQ0KICANCiAgZGF0YXNldF9udW0gPC0gc3Vic3RyKGRhdGFzZXRfaWQsIG5jaGFyKGRhdGFzZXRfaWQpLCBuY2hhcihkYXRhc2V0X2lkKSkNCiAgICANCiAgICBkYXRhc2V0X3JlY29yZCA8LSBkYXRhLmZyYW1lKA0KICAgICAgZGF0YXNldF9pZCA9IGRhdGFzZXRfaWQsDQogICAgICBkZXNjcmlwdGlvbiA9IGNvbXBhcmlzb25fbmFtZSwNCiAgICAgIG9yZ2FuaXNtID0gIkhvbW8gc2FwaWVucyIsDQogICAgICBuX3NhbXBsZXMgPSBuX3NhbXBsZXMsDQogICAgICBuX2dlbmVzID0gbl9nZW5lcywNCiAgICAgIGRhdGVfYWRkZWQgPSBhcy5jaGFyYWN0ZXIoU3lzLkRhdGUoKSksDQogICAgICBzdHVkeV9kZXNpZ24gPSBjb21wYXJpc29uX25hbWUsDQogICAgICBnZW9faWQgPSBnZW9faWRzW2RhdGFzZXRfaWRdLA0KICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICAgKQ0KICAgIA0KICAgIGRiV3JpdGVUYWJsZShjb24sICJkYXRhc2V0cyIsIGRhdGFzZXRfcmVjb3JkLCBhcHBlbmQgPSBUUlVFLCByb3cubmFtZXMgPSBGQUxTRSkNCiAgICBtZXNzYWdlKCIgIEFkZGVkIGRhdGFzZXQgbWV0YWRhdGEiKQ0KICB9IGVsc2Ugew0KICAgIG1lc3NhZ2UoIiAgRGF0YXNldCBtZXRhZGF0YSBhbHJlYWR5IGV4aXN0cywgc2tpcHBpbmciKQ0KICB9DQogIA0KICBkYkRpc2Nvbm5lY3QoY29uKQ0KICBtZXNzYWdlKHBhc3RlKCIgIERhdGFzZXQiLCBkYXRhc2V0X2lkLCAicHJvY2Vzc2luZyBjb21wbGV0ZSEiKSkNCn0NCmBgYA0KDQpgYGB7cn0NCiNEYXRhc2V0IDEgLSBzaW5nbGUgY29tcGFyaXNvbg0KcHJvY2Vzc19kYXRhc2V0KA0KICBkYXRhc2V0X2lkID0gImRhdGFzZXQxIiwNCiAgZmluYWxfcmVzdWx0cyA9ICJEMV9yZXN1bHRzLmNzdiIsDQogIHZzdF9kYXRhID0gIkQxX3ZzdF9kYXRhLmNzdiIsDQogIGNvbXBhcmlzb25fbmFtZSA9ICJ1bnRyZWF0ZWRfdnNfb3NpbWVydGluaWIiDQopDQoNCiNEYXRhc2V0IDIgLSBtYWluIGNvbXBhcmlzb24gKHdpbGQgdHlwZSB2cyBleG9uMTkgZGVsZXRpb24pDQpwcm9jZXNzX2RhdGFzZXQoDQogIGRhdGFzZXRfaWQgPSAiZGF0YXNldDIiLA0KICBmaW5hbF9yZXN1bHRzID0gIkQyX3Jlc3VsdHMuY3N2IiwNCiAgdnN0X2RhdGEgPSAiRDJfdnN0X2RhdGEuY3N2IiwNCiAgY29tcGFyaXNvbl9uYW1lID0gIndpbGR0eXBlX3ZzX2V4b24xOWRlbCINCikNCg0KI0RhdGFzZXQgMyAtIHNpbmdsZSBjb21wYXJpc29uDQpwcm9jZXNzX2RhdGFzZXQoDQogIGRhdGFzZXRfaWQgPSAiZGF0YXNldDMiLA0KICBmaW5hbF9yZXN1bHRzID0gIkQzX3Jlc3VsdHMuY3N2IiwNCiAgdnN0X2RhdGEgPSAiRDNfdnN0X2RhdGEuY3N2IiwNCiAgY29tcGFyaXNvbl9uYW1lID0gImNvbnRyb2xfdnNfYXBhdGluaWIiDQopDQoNCiNEYXRhc2V0IDQgLSBzaW5nbGUgY29tcGFyaXNvbg0KcHJvY2Vzc19kYXRhc2V0KA0KICBkYXRhc2V0X2lkID0gImRhdGFzZXQ0IiwNCiAgZmluYWxfcmVzdWx0cyA9ICJENF9yZXN1bHRzLmNzdiIsDQogIHZzdF9kYXRhID0gIkQ0X3ZzdF9kYXRhLmNzdiIsDQogIGNvbXBhcmlzb25fbmFtZSA9ICJyZXNpc3RhbnRfdnNfcGFyZW50YWwiDQopDQoNCiNEYXRhc2V0IDUgLSBtYWluIGNvbXBhcmlzb24gKHRyZWF0bWVudCBlZmZlY3QpDQpwcm9jZXNzX2RhdGFzZXQoDQogIGRhdGFzZXRfaWQgPSAiZGF0YXNldDUiLA0KICBmaW5hbF9yZXN1bHRzID0gIkQ1X3Jlc3VsdHMuY3N2IiwNCiAgdnN0X2RhdGEgPSAiRDVfdnN0X2RhdGEuY3N2IiwNCiAgY29tcGFyaXNvbl9uYW1lID0gImdlZml0aW5pYl92c19kbXNvIg0KKQ0KYGBgDQoNCmBgYHtyfQ0KI2RhdGFiYXNlIHZlcmlmaWNhdGlvbg0KDQpjb24gPC0gZGJDb25uZWN0KFNRTGl0ZSgpLCAicm5hc2VxX2FuYWx5c2lzX3Jlc3VsdHMuZGIiKQ0KDQptZXNzYWdlKCI9PT0gREFUQUJBU0UgU1RBVFVTID09PSIpDQptZXNzYWdlKCJGaWxlIHNpemU6ICIsIHJvdW5kKGZpbGUuc2l6ZSgicm5hc2VxX2FuYWx5c2lzX3Jlc3VsdHMuZGIiKS8xMDI0LzEwMjQsIDIpLCAiIE1CIikNCm1lc3NhZ2UoIlRhYmxlcyBpbiBkYXRhYmFzZToiKQ0KcHJpbnQoZGJMaXN0VGFibGVzKGNvbikpDQoNCm1lc3NhZ2UoIlxuPT09IERBVEFTRVRTIExPQURFRCA9PT0iKQ0KZGF0YXNldHNfaW5mbyA8LSBkYkdldFF1ZXJ5KGNvbiwgIlNFTEVDVCBkYXRhc2V0X2lkLCBkYXRhc2V0X25hbWUsIG5fc2FtcGxlcywgbl9nZW5lcywgc3R1ZHlfZGVzaWduIEZST00gZGF0YXNldHMiKQ0KcHJpbnQoZGF0YXNldHNfaW5mbykNCg0KbWVzc2FnZSgiXG49PT0gQ09NUEFSSVNPTlMgSU4gREFUQUJBU0UgPT09IikNCmNvbXBhcmlzb25zIDwtIGRiR2V0UXVlcnkoY29uLCAiDQogIFNFTEVDVCBkYXRhc2V0X2lkLCBjb21wYXJpc29uX25hbWUsIENPVU5UKCopIGFzIG5fZ2VuZXMgDQogIEZST00gZGlmZmVyZW50aWFsX2V4cHJlc3Npb24gDQogIEdST1VQIEJZIGRhdGFzZXRfaWQsIGNvbXBhcmlzb25fbmFtZQ0KIikNCnByaW50KGNvbXBhcmlzb25zKQ0KDQptZXNzYWdlKCJcbj09PSBEQVRBIFNVTU1BUlkgPT09IikNCmRlX2NvdW50IDwtIGRiR2V0UXVlcnkoY29uLCAiU0VMRUNUIENPVU5UKCopIGFzIHRvdGFsX2RlX3Jlc3VsdHMgRlJPTSBkaWZmZXJlbnRpYWxfZXhwcmVzc2lvbiIpDQpleHByX2NvdW50IDwtIGRiR2V0UXVlcnkoY29uLCAiU0VMRUNUIENPVU5UKCopIGFzIHRvdGFsX2V4cHJlc3Npb25fdmFsdWVzIEZST00gbm9ybWFsaXplZF9leHByZXNzaW9uIikNCnByaW50KHBhc3RlKCJUb3RhbCBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiByZXN1bHRzOiIsIGRlX2NvdW50JHRvdGFsX2RlX3Jlc3VsdHMpKQ0KcHJpbnQocGFzdGUoIlRvdGFsIGV4cHJlc3Npb24gbWVhc3VyZW1lbnRzOiIsIGV4cHJfY291bnQkdG90YWxfZXhwcmVzc2lvbl92YWx1ZXMpKQ0KDQptZXNzYWdlKCJcbj09PSBTSUdOSUZJQ0FOVCBHRU5FUyBQUkVWSUVXID09PSIpDQpzaWdfZ2VuZXMgPC0gZGJHZXRRdWVyeShjb24sICJTRUxFQ1QgKiBGUk9NIHNpZ25pZmljYW50X2dlbmVzIExJTUlUIDUiKQ0KcHJpbnQoc2lnX2dlbmVzKQ0KDQptZXNzYWdlKCJcbj09PSBTQU1QTEUgREFUQUJBU0UgUVVFUklFUyA9PT0iKQ0KIyBUZXN0IHF1ZXJ5IC0gZ2VuZXMgYWNyb3NzIGRhdGFzZXRzDQp0ZXN0X3F1ZXJ5IDwtIGRiR2V0UXVlcnkoY29uLCAiDQogIFNFTEVDVCBkYXRhc2V0X2lkLCBjb21wYXJpc29uX25hbWUsIENPVU5UKCopIGFzIHNpZ25pZmljYW50X2dlbmVzDQogIEZST00gc2lnbmlmaWNhbnRfZ2VuZXMgDQogIEdST1VQIEJZIGRhdGFzZXRfaWQsIGNvbXBhcmlzb25fbmFtZQ0KICBPUkRFUiBCWSBzaWduaWZpY2FudF9nZW5lcyBERVNDDQoiKQ0KcHJpbnQodGVzdF9xdWVyeSkNCg0KZGJEaXNjb25uZWN0KGNvbikNCg0KbWVzc2FnZSgiXG49PT0gREFUQUJBU0UgUkVBRFkgRk9SIFdFQiBWSUVXRVIgPT09IikNCm1lc3NhZ2UoIllvdXIgZGF0YWJhc2UgZmlsZTogcm5hc2VxX2FuYWx5c2lzX3Jlc3VsdHMuZGIiKQ0KbWVzc2FnZSgiWW91IGNhbiBub3cgb3BlbiB0aGlzIGZpbGUgaW4gYW55IFNRTGl0ZSB3ZWIgdmlld2VyIikNCm1lc3NhZ2UoIkRhdGFiYXNlIGNyZWF0aW9uIGNvbXBsZXRlISIpDQoNCiMgRnVuY3Rpb24gdG8gcXVlcnkgYW55IGdlbmUgYWNyb3NzIGFsbCBkYXRhc2V0cw0KcXVlcnlfZ2VuZV9hY3Jvc3NfZGF0YXNldHMgPC0gZnVuY3Rpb24oZ2VuZV9zeW1ib2wsIGRiX3BhdGggPSAicm5hc2VxX2FuYWx5c2lzX3Jlc3VsdHMuZGIiKSB7DQogIGNvbiA8LSBkYkNvbm5lY3QoU1FMaXRlKCksIGRiX3BhdGgpDQogIHJlc3VsdCA8LSBkYkdldFF1ZXJ5KGNvbiwgcGFzdGUwKCINCiAgICBTRUxFQ1QgZGF0YXNldF9pZCwgY29tcGFyaXNvbl9uYW1lLCBnZW5lX3N5bWJvbCwgZ2VuZV9pZCwNCiAgICAgICAgICAgbG9nMkZvbGRDaGFuZ2UsIHBhZGosIGJhc2VNZWFuDQogICAgRlJPTSBkaWZmZXJlbnRpYWxfZXhwcmVzc2lvbiANCiAgICBXSEVSRSBnZW5lX3N5bWJvbCA9ICciLCBnZW5lX3N5bWJvbCwgIicgT1IgZ2VuZV9pZCA9ICciLCBnZW5lX3N5bWJvbCwgIicNCiAgICBPUkRFUiBCWSBwYWRqIEFTQw0KICAiKSkNCiAgZGJEaXNjb25uZWN0KGNvbikNCiAgcmV0dXJuKHJlc3VsdCkNCn0NCg0KIyBGdW5jdGlvbiB0byBmaW5kIGNvbnNpc3RlbnRseSByZWd1bGF0ZWQgZ2VuZXMNCmZpbmRfY29uc2lzdGVudF9nZW5lcyA8LSBmdW5jdGlvbihtaW5fZGF0YXNldHMgPSAzLCBtYXhfcGFkaiA9IDAuMDUsIGRiX3BhdGggPSAicm5hc2VxX2FuYWx5c2lzX3Jlc3VsdHMuZGIiKSB7DQogIGNvbiA8LSBkYkNvbm5lY3QoU1FMaXRlKCksIGRiX3BhdGgpDQogIHJlc3VsdCA8LSBkYkdldFF1ZXJ5KGNvbiwgcGFzdGUwKCINCiAgICBTRUxFQ1QgZ2VuZV9zeW1ib2wsIA0KICAgICAgICAgICBDT1VOVCgqKSBhcyBuX2RhdGFzZXRzLA0KICAgICAgICAgICBBVkcobG9nMkZvbGRDaGFuZ2UpIGFzIGF2Z19sZmMsDQogICAgICAgICAgIE1JTihwYWRqKSBhcyBiZXN0X3BhZGosDQogICAgICAgICAgIEdST1VQX0NPTkNBVChkYXRhc2V0X2lkIHx8ICc6JyB8fCBjb21wYXJpc29uX25hbWUsICc7ICcpIGFzIGZvdW5kX2luDQogICAgRlJPTSBkaWZmZXJlbnRpYWxfZXhwcmVzc2lvbiANCiAgICBXSEVSRSBwYWRqIDw9ICIsIG1heF9wYWRqLCAiIEFORCBnZW5lX3N5bWJvbCBJUyBOT1QgTlVMTCBBTkQgZ2VuZV9zeW1ib2wgIT0gJycNCiAgICBHUk9VUCBCWSBnZW5lX3N5bWJvbA0KICAgIEhBVklORyBDT1VOVCgqKSA+PSAiLCBtaW5fZGF0YXNldHMsICINCiAgICBPUkRFUiBCWSBuX2RhdGFzZXRzIERFU0MsIGJlc3RfcGFkaiBBU0MNCiAgIikpDQogIGRiRGlzY29ubmVjdChjb24pDQogIHJldHVybihyZXN1bHQpDQp9DQoNCm1lc3NhZ2UoIlxuPT09IEVYQU1QTEUgQURWQU5DRUQgUVVFUklFUyA9PT0iKQ0KbWVzc2FnZSgiVHJ5IHRoZXNlIGZ1bmN0aW9uczoiKQ0KbWVzc2FnZSgicXVlcnlfZ2VuZV9hY3Jvc3NfZGF0YXNldHMoJ0JSQ0ExJykiKQ0KbWVzc2FnZSgiZmluZF9jb25zaXN0ZW50X2dlbmVzKG1pbl9kYXRhc2V0cyA9IDMpIikNCg0KYGBgDQoNCmBgYHtyfQ0KI2FkdmFuY2VkIHF1ZXJpZXMgZnVuY3Rpb25zIC0gZ29vZCB0byBoYXZlDQoNCg0KIyBGdW5jdGlvbiB0byBxdWVyeSBhbnkgZ2VuZSBhY3Jvc3MgYWxsIGRhdGFzZXRzDQpxdWVyeV9nZW5lX2Fjcm9zc19kYXRhc2V0cyA8LSBmdW5jdGlvbihnZW5lX3N5bWJvbCwgZGJfcGF0aCA9ICJybmFzZXFfYW5hbHlzaXNfcmVzdWx0cy5kYiIpIHsNCiAgY29uIDwtIGRiQ29ubmVjdChTUUxpdGUoKSwgZGJfcGF0aCkNCiAgcmVzdWx0IDwtIGRiR2V0UXVlcnkoY29uLCBwYXN0ZTAoIg0KICAgIFNFTEVDVCBkYXRhc2V0X2lkLCBjb21wYXJpc29uX25hbWUsIGdlbmVfc3ltYm9sLCBnZW5lX2lkLA0KICAgICAgICAgICBsb2cyRm9sZENoYW5nZSwgcGFkaiwgYmFzZU1lYW4NCiAgICBGUk9NIGRpZmZlcmVudGlhbF9leHByZXNzaW9uIA0KICAgIFdIRVJFIGdlbmVfc3ltYm9sID0gJyIsIGdlbmVfc3ltYm9sLCAiJyBPUiBnZW5lX2lkID0gJyIsIGdlbmVfc3ltYm9sLCAiJw0KICAgIE9SREVSIEJZIHBhZGogQVNDDQogICIpKQ0KICBkYkRpc2Nvbm5lY3QoY29uKQ0KICByZXR1cm4ocmVzdWx0KQ0KfQ0KDQojIEZ1bmN0aW9uIHRvIGZpbmQgY29uc2lzdGVudGx5IHJlZ3VsYXRlZCBnZW5lcw0KZmluZF9jb25zaXN0ZW50X2dlbmVzIDwtIGZ1bmN0aW9uKG1pbl9kYXRhc2V0cyA9IDMsIG1heF9wYWRqID0gMC4wNSwgZGJfcGF0aCA9ICJybmFzZXFfYW5hbHlzaXNfcmVzdWx0cy5kYiIpIHsNCiAgY29uIDwtIGRiQ29ubmVjdChTUUxpdGUoKSwgZGJfcGF0aCkNCiAgcmVzdWx0IDwtIGRiR2V0UXVlcnkoY29uLCBwYXN0ZTAoIg0KICAgIFNFTEVDVCBnZW5lX3N5bWJvbCwgDQogICAgICAgICAgIENPVU5UKCopIGFzIG5fZGF0YXNldHMsDQogICAgICAgICAgIEFWRyhsb2cyRm9sZENoYW5nZSkgYXMgYXZnX2xmYywNCiAgICAgICAgICAgTUlOKHBhZGopIGFzIGJlc3RfcGFkaiwNCiAgICAgICAgICAgR1JPVVBfQ09OQ0FUKGRhdGFzZXRfaWQgfHwgJzonIHx8IGNvbXBhcmlzb25fbmFtZSwgJzsgJykgYXMgZm91bmRfaW4NCiAgICBGUk9NIGRpZmZlcmVudGlhbF9leHByZXNzaW9uIA0KICAgIFdIRVJFIHBhZGogPD0gIiwgbWF4X3BhZGosICIgQU5EIGdlbmVfc3ltYm9sIElTIE5PVCBOVUxMIEFORCBnZW5lX3N5bWJvbCAhPSAnJw0KICAgIEdST1VQIEJZIGdlbmVfc3ltYm9sDQogICAgSEFWSU5HIENPVU5UKCopID49ICIsIG1pbl9kYXRhc2V0cywgIg0KICAgIE9SREVSIEJZIG5fZGF0YXNldHMgREVTQywgYmVzdF9wYWRqIEFTQw0KICAiKSkNCiAgZGJEaXNjb25uZWN0KGNvbikNCiAgcmV0dXJuKHJlc3VsdCkNCn0NCg0KbWVzc2FnZSgiXG49PT0gRVhBTVBMRSBBRFZBTkNFRCBRVUVSSUVTID09PSIpDQptZXNzYWdlKCJUcnkgdGhlc2UgZnVuY3Rpb25zOiIpDQptZXNzYWdlKCJxdWVyeV9nZW5lX2Fjcm9zc19kYXRhc2V0cygnQlJDQTEnKSIpDQptZXNzYWdlKCJmaW5kX2NvbnNpc3RlbnRfZ2VuZXMobWluX2RhdGFzZXRzID0gMykiKQ0KYGBgDQoNCmBgYHtyfQ0KDQojYWR2YW5jZWQgcXVlcmllcyAtIG5vdGVzDQojIFF1ZXJ5IDE6IERhdGFzZXQgb3ZlcnZpZXcgd2l0aCBHRU8gaW5mb3JtYXRpb24NCnNob3djYXNlX2RhdGFzZXRfb3ZlcnZpZXcgPC0gZnVuY3Rpb24oZGJfcGF0aCA9ICJybmFzZXFfYW5hbHlzaXNfcmVzdWx0cy5kYiIpIHsNCiAgY29uIDwtIGRiQ29ubmVjdChTUUxpdGUoKSwgZGJfcGF0aCkNCiAgcmVzdWx0IDwtIGRiR2V0UXVlcnkoY29uLCAiDQogICAgU0VMRUNUIA0KICAgICAgZC5kYXRhc2V0X25hbWUsDQogICAgICBkLmdlb19pZCwNCiAgICAgIGQuZGVzY3JpcHRpb24sDQogICAgICBkLm5fc2FtcGxlcywNCiAgICAgIGQubl9nZW5lcywNCiAgICAgIENPVU5UKGRlLmdlbmVfaWQpIGFzIHRvdGFsX3Rlc3RlZF9nZW5lcywNCiAgICAgIENPVU5UKENBU0UgV0hFTiBkZS5wYWRqIDwgMC4wNSBUSEVOIDEgRU5EKSBhcyBzaWduaWZpY2FudF9nZW5lcywNCiAgICAgIFJPVU5EKENPVU5UKENBU0UgV0hFTiBkZS5wYWRqIDwgMC4wNSBUSEVOIDEgRU5EKSAqIDEwMC4wIC8gQ09VTlQoZGUuZ2VuZV9pZCksIDIpIGFzIHBlcmNlbnRfc2lnbmlmaWNhbnQNCiAgICBGUk9NIGRhdGFzZXRzIGQgDQogICAgTEVGVCBKT0lOIGRpZmZlcmVudGlhbF9leHByZXNzaW9uIGRlIE9OIGQuZGF0YXNldF9pZCA9IGRlLmRhdGFzZXRfaWQNCiAgICBHUk9VUCBCWSBkLmRhdGFzZXRfaWQNCiAgICBPUkRFUiBCWSBkLmRhdGFzZXRfaWQNCiAgIikNCiAgZGJEaXNjb25uZWN0KGNvbikNCiAgcmV0dXJuKHJlc3VsdCkNCn0NCg0KIyBRdWVyeSAyOiBUb3AgdXByZWd1bGF0ZWQgYW5kIGRvd25yZWd1bGF0ZWQgZ2VuZXMgcGVyIGRhdGFzZXQNCnNob3djYXNlX3RvcF9nZW5lc19wZXJfZGF0YXNldCA8LSBmdW5jdGlvbih0b3BfbiA9IDUsIGRiX3BhdGggPSAicm5hc2VxX2FuYWx5c2lzX3Jlc3VsdHMuZGIiKSB7DQogIGNvbiA8LSBkYkNvbm5lY3QoU1FMaXRlKCksIGRiX3BhdGgpDQogIA0KICAjIFRvcCB1cHJlZ3VsYXRlZA0KICB1cF9nZW5lcyA8LSBkYkdldFF1ZXJ5KGNvbiwgcGFzdGUwKCINCiAgICBTRUxFQ1QgDQogICAgICBkYXRhc2V0X2lkLA0KICAgICAgY29tcGFyaXNvbl9uYW1lLA0KICAgICAgZ2VuZV9zeW1ib2wsDQogICAgICBsb2cyRm9sZENoYW5nZSwNCiAgICAgIHBhZGosDQogICAgICAndXByZWd1bGF0ZWQnIGFzIGRpcmVjdGlvbg0KICAgIEZST00gZGlmZmVyZW50aWFsX2V4cHJlc3Npb24gDQogICAgV0hFUkUgcGFkaiA8IDAuMDUgQU5EIGxvZzJGb2xkQ2hhbmdlID4gMA0KICAgIEdST1VQIEJZIGRhdGFzZXRfaWQNCiAgICBIQVZJTkcgbG9nMkZvbGRDaGFuZ2UgPSBNQVgobG9nMkZvbGRDaGFuZ2UpDQogICAgT1JERVIgQlkgZGF0YXNldF9pZA0KICAiKSkNCiAgDQogICMgVG9wIGRvd25yZWd1bGF0ZWQgIA0KICBkb3duX2dlbmVzIDwtIGRiR2V0UXVlcnkoY29uLCBwYXN0ZTAoIg0KICAgIFNFTEVDVCANCiAgICAgIGRhdGFzZXRfaWQsDQogICAgICBjb21wYXJpc29uX25hbWUsDQogICAgICBnZW5lX3N5bWJvbCwNCiAgICAgIGxvZzJGb2xkQ2hhbmdlLA0KICAgICAgcGFkaiwNCiAgICAgICdkb3ducmVndWxhdGVkJyBhcyBkaXJlY3Rpb24NCiAgICBGUk9NIGRpZmZlcmVudGlhbF9leHByZXNzaW9uIA0KICAgIFdIRVJFIHBhZGogPCAwLjA1IEFORCBsb2cyRm9sZENoYW5nZSA8IDANCiAgICBHUk9VUCBCWSBkYXRhc2V0X2lkDQogICAgSEFWSU5HIGxvZzJGb2xkQ2hhbmdlID0gTUlOKGxvZzJGb2xkQ2hhbmdlKQ0KICAgIE9SREVSIEJZIGRhdGFzZXRfaWQNCiAgIikpDQogIA0KICBkYkRpc2Nvbm5lY3QoY29uKQ0KICANCiAgIyBDb21iaW5lIHJlc3VsdHMNCiAgcmVzdWx0IDwtIHJiaW5kKHVwX2dlbmVzLCBkb3duX2dlbmVzKQ0KICByZXR1cm4ocmVzdWx0W29yZGVyKHJlc3VsdCRkYXRhc2V0X2lkLCByZXN1bHQkZGlyZWN0aW9uKSwgXSkNCn0NCg0KIyBRdWVyeSAzOiBDcm9zcy1kYXRhc2V0IGdlbmUgZXhwcmVzc2lvbiBjb3JyZWxhdGlvbg0Kc2hvd2Nhc2VfZXhwcmVzc2lvbl9wYXR0ZXJucyA8LSBmdW5jdGlvbihnZW5lcyA9IGMoIkVHRlIiLCAiVFA1MyIsICJNWUMiKSwgZGJfcGF0aCA9ICJybmFzZXFfYW5hbHlzaXNfcmVzdWx0cy5kYiIpIHsNCiAgY29uIDwtIGRiQ29ubmVjdChTUUxpdGUoKSwgZGJfcGF0aCkNCiAgDQogIGdlbmVfbGlzdCA8LSBwYXN0ZTAoIiciLCBnZW5lcywgIiciLCBjb2xsYXBzZSA9ICIsIikNCiAgcmVzdWx0IDwtIGRiR2V0UXVlcnkoY29uLCBwYXN0ZTAoIg0KICAgIFNFTEVDVCANCiAgICAgIGRlLmdlbmVfc3ltYm9sLA0KICAgICAgZGUuZGF0YXNldF9pZCwNCiAgICAgIGQuZ2VvX2lkLA0KICAgICAgZGUuY29tcGFyaXNvbl9uYW1lLA0KICAgICAgZGUubG9nMkZvbGRDaGFuZ2UsDQogICAgICBkZS5wYWRqLA0KICAgICAgQ0FTRSANCiAgICAgICAgV0hFTiBkZS5wYWRqIDwgMC4wMDEgVEhFTiAnaGlnaGx5X3NpZ25pZmljYW50Jw0KICAgICAgICBXSEVOIGRlLnBhZGogPCAwLjA1IFRIRU4gJ3NpZ25pZmljYW50Jw0KICAgICAgICBFTFNFICdub3Rfc2lnbmlmaWNhbnQnDQogICAgICBFTkQgYXMgc2lnbmlmaWNhbmNlX2xldmVsDQogICAgRlJPTSBkaWZmZXJlbnRpYWxfZXhwcmVzc2lvbiBkZQ0KICAgIEpPSU4gZGF0YXNldHMgZCBPTiBkZS5kYXRhc2V0X2lkID0gZC5kYXRhc2V0X2lkDQogICAgV0hFUkUgZGUuZ2VuZV9zeW1ib2wgSU4gKCIsIGdlbmVfbGlzdCwgIikNCiAgICBPUkRFUiBCWSBkZS5nZW5lX3N5bWJvbCwgZGUuZGF0YXNldF9pZA0KICAiKSkNCiAgDQogIGRiRGlzY29ubmVjdChjb24pDQogIHJldHVybihyZXN1bHQpDQp9DQoNCiMgUXVlcnkgNDogU2FtcGxlIGV4cHJlc3Npb24gZGlzdHJpYnV0aW9uIGFuYWx5c2lzDQpzaG93Y2FzZV9leHByZXNzaW9uX2Rpc3RyaWJ1dGlvbiA8LSBmdW5jdGlvbihkYl9wYXRoID0gInJuYXNlcV9hbmFseXNpc19yZXN1bHRzLmRiIikgew0KICBjb24gPC0gZGJDb25uZWN0KFNRTGl0ZSgpLCBkYl9wYXRoKQ0KICByZXN1bHQgPC0gZGJHZXRRdWVyeShjb24sICINCiAgICBTRUxFQ1QgDQogICAgICBST1VORChleHByZXNzaW9uX3ZhbHVlKSBhcyBleHByZXNzaW9uX2xldmVsLA0KICAgICAgQ09VTlQoKikgYXMgZnJlcXVlbmN5DQogICAgRlJPTSBub3JtYWxpemVkX2V4cHJlc3Npb24gDQogICAgV0hFUkUgZXhwcmVzc2lvbl92YWx1ZSBCRVRXRUVOIDAgQU5EIDE1DQogICAgR1JPVVAgQlkgUk9VTkQoZXhwcmVzc2lvbl92YWx1ZSkNCiAgICBPUkRFUiBCWSBleHByZXNzaW9uX2xldmVsDQogICIpDQogIGRiRGlzY29ubmVjdChjb24pDQogIHJldHVybihyZXN1bHQpDQp9DQoNCiMgUXVlcnkgNTogUGF0aHdheS1yZWxldmFudCBnZW5lcyAoZm9jdXNpbmcgb24gY2FuY2VyL2RydWcgcmVzaXN0YW5jZSkNCnNob3djYXNlX3BhdGh3YXlfZ2VuZXMgPC0gZnVuY3Rpb24oZGJfcGF0aCA9ICJybmFzZXFfYW5hbHlzaXNfcmVzdWx0cy5kYiIpIHsNCiAgIyBDYW5jZXIgYW5kIGRydWcgcmVzaXN0YW5jZSByZWxhdGVkIGdlbmVzDQogIGNhbmNlcl9nZW5lcyA8LSBjKCJUUDUzIiwgIkJSQ0ExIiwgIkJSQ0EyIiwgIkVHRlIiLCAiS1JBUyIsICJQSUszQ0EiLCAiQVBDIiwgIk1ZQyIsIA0KICAgICAgICAgICAgICAgICAgICJQVEVOIiwgIlJCMSIsICJDREtOMkEiLCAiTUxIMSIsICJCUkFGIiwgIkVSQkIyIiwgIk1ETTIiKQ0KICANCiAgY29uIDwtIGRiQ29ubmVjdChTUUxpdGUoKSwgZGJfcGF0aCkNCiAgDQogIGdlbmVfbGlzdCA8LSBwYXN0ZTAoIiciLCBjYW5jZXJfZ2VuZXMsICInIiwgY29sbGFwc2UgPSAiLCIpDQogIHJlc3VsdCA8LSBkYkdldFF1ZXJ5KGNvbiwgcGFzdGUwKCINCiAgICBTRUxFQ1QgDQogICAgICBkZS5nZW5lX3N5bWJvbCwNCiAgICAgIENPVU5UKCopIGFzIGRhdGFzZXRzX2ZvdW5kX2luLA0KICAgICAgQVZHKGRlLmxvZzJGb2xkQ2hhbmdlKSBhcyBhdmdfbG9nMmZjLA0KICAgICAgTUlOKGRlLnBhZGopIGFzIGJlc3RfcHZhbHVlLA0KICAgICAgQ09VTlQoQ0FTRSBXSEVOIGRlLnBhZGogPCAwLjA1IFRIRU4gMSBFTkQpIGFzIHNpZ25pZmljYW50X2RhdGFzZXRzDQogICAgRlJPTSBkaWZmZXJlbnRpYWxfZXhwcmVzc2lvbiBkZQ0KICAgIFdIRVJFIGRlLmdlbmVfc3ltYm9sIElOICgiLCBnZW5lX2xpc3QsICIpDQogICAgR1JPVVAgQlkgZGUuZ2VuZV9zeW1ib2wNCiAgICBIQVZJTkcgQ09VTlQoKikgPj0gMg0KICAgIE9SREVSIEJZIHNpZ25pZmljYW50X2RhdGFzZXRzIERFU0MsIGJlc3RfcHZhbHVlIEFTQw0KICAiKSkNCiAgDQogIGRiRGlzY29ubmVjdChjb24pDQogIHJldHVybihyZXN1bHQpDQp9DQoNCm1lc3NhZ2UoIlxuPT09IFNIT1dDQVNFIFFVRVJJRVMgRk9SIERJU1NFUlRBVElPTiA9PT0iKQ0KbWVzc2FnZSgiVXNlIHRoZXNlIGZ1bmN0aW9ucyB0byBkZW1vbnN0cmF0ZSB5b3VyIGRhdGFiYXNlIGNhcGFiaWxpdGllczoiKQ0KbWVzc2FnZSgiMS4gc2hvd2Nhc2VfZGF0YXNldF9vdmVydmlldygpIC0gQ29tcGxldGUgZGF0YXNldCBzdW1tYXJ5IHdpdGggR0VPIElEcyIpDQptZXNzYWdlKCIyLiBzaG93Y2FzZV90b3BfZ2VuZXNfcGVyX2RhdGFzZXQoKSAtIE1vc3QgcmVndWxhdGVkIGdlbmVzIHBlciBzdHVkeSIpIA0KbWVzc2FnZSgiMy4gc2hvd2Nhc2VfZXhwcmVzc2lvbl9wYXR0ZXJucyhjKCdFR0ZSJywgJ1RQNTMnKSkgLSBUcmFjayBzcGVjaWZpYyBnZW5lcyBhY3Jvc3MgZGF0YXNldHMiKQ0KbWVzc2FnZSgiNC4gc2hvd2Nhc2VfZXhwcmVzc2lvbl9kaXN0cmlidXRpb24oKSAtIEV4cHJlc3Npb24gbGV2ZWwgZGlzdHJpYnV0aW9ucyIpDQptZXNzYWdlKCI1LiBzaG93Y2FzZV9wYXRod2F5X2dlbmVzKCkgLSBDYW5jZXItcmVsZXZhbnQgZ2VuZXMgYWNyb3NzIGFsbCBzdHVkaWVzIikNCm1lc3NhZ2UoIlxuVGhlc2UgcXVlcmllcyBzaG93Y2FzZSBjcm9zcy1kYXRhc2V0IGFuYWx5c2lzLCBzdGF0aXN0aWNhbCBpbnNpZ2h0cywgYW5kIGJpb2xvZ2ljYWwgcmVsZXZhbmNlISIpDQoNCmZpbGUuZXhpc3RzKCJybmFzZXFfYW5hbHlzaXNfcmVzdWx0cy5kYiIpDQpgYGANCg0K