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