Read in necessary libraries, functions and look up tables
library(igraph)
library(qgraph)
library(casnet)
library(plyr)
library(dplyr)
library(tidyr)
library(caret)
library(psych)
library(lme4)
library(reshape2)
library(lubridate)
library(parallel)
library(wesanderson)
library(stringr)
library(matrixStats)
library(ggthemes)
library(gridExtra)
library(cowplot)
# save the original plotting space parameters
og_par <- par()
Read in data
Prepare the Outcome measures
for (timepoint in c('m00', 'm03', 'm06', 'm09', 'm12')) {
aumc_df[, paste0(timepoint, "_NHPT_D_avg")] <- rowMeans(aumc_df[, c(paste0(timepoint, "_NHPT_D1"), paste0(timepoint,"_NHPT_D2"))], na.rm=T)
aumc_df[, paste0(timepoint, "_NHPT_ND_avg")] <- rowMeans(aumc_df[, c(paste0(timepoint, "_NHPT_ND1"), paste0(timepoint,"_NHPT_ND2"))], na.rm=T)
aumc_df[, paste0(timepoint, "_NHPT_BH_avg")] <- rowMeans(aumc_df[, c(paste0(timepoint, "_NHPT_D_avg"), paste0(timepoint,"_NHPT_ND_avg"))], na.rm=T)
aumc_df[, paste0(timepoint, "_TWT_avg")] <- rowMeans(aumc_df[, c(paste0(timepoint, "_TWT_1"), paste0(timepoint,"_TWT_2"))], na.rm=T)
}
Get a list of all the outcome measures of interest at each time point
outcomes_of_interest_ls <- c()
for (outcome in outcomes_list) {
outcomes_of_interest_ls <- append(outcomes_of_interest_ls, colnames(aumc_df %>% select(matches(outcome))))
}
Features used in the creation of the complexity measures (taken RQA python notebook)
# features with highest rqa measures (most interesting NLTSA wise)
feature_selection <- c('post_correction_slowing_MEAN_2CD',
'pre_correction_slowing_MEAN_2CD',
'hold_time_STD',
'flight_time_MEDIAN',
'flight_time_MEAN_ABS_CHANGE',
'hold_time_SKEW',
'correction_duration_MEAN_2CD',
'post_correction_slowing_P_AUTO_CORR',
'after_punctuation_pause_MEAN_CHANGE',
'after_punctuation_pause_MEAN_2CD',
'session_duration_MEAN_2CD',
'after_punctuation_pause_MEDIAN',
'flight_time_MAX')
feature_selection
[1] "post_correction_slowing_MEAN_2CD" "pre_correction_slowing_MEAN_2CD" "hold_time_STD"
[4] "flight_time_MEDIAN" "flight_time_MEAN_ABS_CHANGE" "hold_time_SKEW"
[7] "correction_duration_MEAN_2CD" "post_correction_slowing_P_AUTO_CORR" "after_punctuation_pause_MEAN_CHANGE"
[10] "after_punctuation_pause_MEAN_2CD" "session_duration_MEAN_2CD" "after_punctuation_pause_MEDIAN"
[13] "flight_time_MAX"
Identify the different groups (change in MRI, no change in MRI and HC users) and prep the data for the analysis
mri_cols <- colnames(aumc_df %>% select(matches("mri_enhancing")))
diff_df <- NA
for (i in seq(1, length(mri_cols)-1)) {
diff_df <- cbind(diff_df, aumc_df[mri_cols[i+1]] - aumc_df[mri_cols[i]])
}
diff_df <- diff_df[, 2:ncol(diff_df)]
diff_df <- diff_df[rowSums(is.na(diff_df)) < 3, ]
## SPECIFY THE INDICES YOU WISH TO INVESTIGATE
row_amount <- 58
# user indices which have a change in mri
mri_change_index <- as.integer(names(which(rowSums(abs(diff_df), na.rm=TRUE) > 0)))
mri_change_ls <- aumc_df$user_id[mri_change_index]
short_mri_change_ls <- c()
for (user in mri_change_ls) {
if (nrow(key_df[key_df$user_id == user, ]) > row_amount) {
short_mri_change_ls <- c(short_mri_change_ls, user)
}
}
mri_change_index <- which(aumc_df$user_id %in% short_mri_change_ls)
# user indices which don't have a change in mri
mri_no_change_index <- as.integer(names(which(rowSums(abs(diff_df), na.rm=TRUE) == 0)))
mri_no_change_ls <- aumc_df$user_id[mri_no_change_index]
short_mri_no_change_ls <- c()
for (user in mri_no_change_ls) {
if (nrow(key_df[key_df$user_id == user, ]) > row_amount) {
short_mri_no_change_ls <- c(short_mri_no_change_ls, user)
}
}
mri_no_change_index <- which(aumc_df$user_id %in% short_mri_no_change_ls)
# healthy controls
hc_users_index <- which(aumc_df$diagnosis_ms == 0)
hc_users_ls <- aumc_df$user_id[hc_users_index]
short_hc_users_ls <- c()
for (user in hc_users_ls) {
if (nrow(key_df[key_df$user_id == user, ]) > row_amount) {
short_hc_users_ls <- c(short_hc_users_ls, user)
}
}
hc_users_index <- which(aumc_df$user_id %in% short_hc_users_ls)
list_of_user_index_lists=c(data.frame(mri_change=mri_change_index),
data.frame(mri_no_change=mri_no_change_index),
data.frame(hc_users=hc_users_index))
prepped_dfs_list <- list()
for (users_list_number in 1:length(list_of_user_index_lists)){
user_list_name<-names(list_of_user_index_lists[users_list_number])
users_selection_index<-list_of_user_index_lists[[user_list_name]]
print(user_list_name)
# use the chosen index to get a list of users
user_selection <- aumc_df$user_id[users_selection_index]
print(user_selection)
# create a subset of the aumc_df which includes only the users of interest
missing_df <- data.frame("sample size" = colSums(!is.na(aumc_df[aumc_df$diagnosis_ms == 1, c("user_id", mri_cols, visitdate_cols)])))
# subset relevant users' keystroke data
key_sub_df <- key_df[key_df$user_id %in% user_selection, ]
# order based on user id and timestamp
key_sub_df <- key_sub_df[order(key_sub_df$user_id, key_sub_df$timestamp), ]
## Creating a data frame of the complexity measures per user
## Writing code which will parallelize the EWS calculations
dfs_list <- df_user_list(df = key_sub_df, features = c("user_id", "timestamp", feature_selection),
user_column_name = "user_id", users = user_selection)
complexity_dfs_list <- mclapply(dfs_list, EWS_calc) # mclapply is the parallelized version of lapply
# filter only the data frames in the list (drop where there was not enough data)
complexity_dfs_list <- Filter(function(x) is.data.frame(x)[[1]] > 0, complexity_dfs_list)
complex_df <- rbind.fill(complexity_dfs_list)
# merge the complex df back to the keystroke df
key_complex_df <- merge(key_sub_df[, c("user_id", "timestamp", feature_selection)],
complex_df, by = c("user_id", "timestamp"))
# relabel the 10's to 1's in the complexity_peaks factor to avoid issues later
key_complex_df$complexity_peaks[key_complex_df$complexity_peaks == 10] <- 1
# aumc_outcome_selector <- mri_cols
aumc_outcome_selector <- outcomes_of_interest_ls
# create a melted version of the aumc_df with only the variables of interest
aumc_df_m_1 <- melt(aumc_df[c("user_id", aumc_outcome_selector)], id.vars="user_id",
variable.name = "PRO_name", value.name = "PRO_value")
aumc_df[c(date_cols)] <- apply(aumc_df[c(date_cols)], 2, as.character)
aumc_df_m_2 <- melt(aumc_df[c("user_id", date_cols)], id.vars="user_id",
variable.name = "visitdate_cat", value.name = "date")
aumc_df_m_2$date <- as.Date(aumc_df_m_2$date)
aumc_df_m_1 <- aumc_df_m_1 %>% mutate(time_code = substr(PRO_name,1L,4L))
aumc_df_m_2 <- aumc_df_m_2 %>% mutate(time_code = substr(visitdate_cat,1L,4L))
aumc_df_m <- merge(aumc_df_m_1, aumc_df_m_2, by = c("user_id", "time_code"))
# select the outcome measures and their corresponding questionnaire/ visitdates then reassign
aumc_df_m_vs <- aumc_df_m %>%
filter(str_detect(aumc_df_m$PRO_name, paste(visitdate_outcomes, collapse = "|")) &
str_detect(aumc_df_m$visitdate_cat, paste(visitdate_cols, collapse = "|")))
aumc_df_m_qs <- aumc_df_m %>%
filter(str_detect(aumc_df_m$PRO_name, paste(questionnaire_date_outcomes, collapse = "|")) &
str_detect(aumc_df_m$visitdate_cat, paste(questionnare_date_cols, collapse = "|")))
aumc_df_m <- rbind(aumc_df_m_vs, aumc_df_m_qs)
# merge the melted aumc_df with the key_complex_df
key_complex_df$date <- as.Date(key_complex_df$timestamp)
df.mod <- merge(key_complex_df, aumc_df_m, by = c("user_id", "date"), all.x=TRUE, all.y=FALSE)
prepped_dfs_list[[user_list_name]] <- df.mod
}
[1] "mri_change"
[1] 368 377 380 383 389 393 398 409 410 424 433 438 444
[1] "mri_no_change"
[1] 364 365 367 369 382 386 387 388 390 396 400 406 408 411 413 416 429 430 435 449
[1] "hc_users"
[1] 372 373 376 381 384 391 402 403 404 405 412 415 421 423 425 427 431 432 437 439 448 463
Plot the Dynamic Complexity, Cumulative Complexity Peaks, and the corresponding data from the Gd enhancing lesions and Patient Reported Outcome Measures
user_failed_list <- c()
for (users_list_number in 1:length(list_of_user_index_lists)){
user_list_name<-names(list_of_user_index_lists[users_list_number])
users_selection_index<-list_of_user_index_lists[[user_list_name]]
print(user_list_name)
prepped_df <- prepped_dfs_list[[user_list_name]]
# use the chosen index to get a list of users
user_selection <- aumc_df$user_id[users_selection_index]
for (user in user_selection) {
user_dc_df <- prepped_df[prepped_df$user_id == user, ]
sub_aumc_df_m <- aumc_df_m[aumc_df_m$user_id == user, ]
temp <- aumc_df_m[aumc_df_m$user_id %in% users_selection_index, ]
selected_outcomes_list <- c()
for (i in 1:length(outcomes_list)) {
diff_list <- c()
current_outcome_vector <- na.omit(sub_aumc_df_m[grep(outcomes_list[i], sub_aumc_df_m$PRO_name), "PRO_value"])
for (j in 2: length(current_outcome_vector)-1) {
diff_list <- append(diff_list, current_outcome_vector[j + 1] - current_outcome_vector[j])
}
if (outcomes_list[i] %in% c("NHPT_ND_avg", "NHPT_D_avg", "TWT_avg")){
if (any(abs(diff_list) > diff_list[1] * outcome_plus_cutoffs_df[outcome_plus_cutoffs_df$outcome == outcomes_list[i], "cutoffs"], na.rm=T)){
selected_outcomes_list <- append(selected_outcomes_list, outcomes_list[i])
}
} else{
if (any(abs(diff_list) >= outcome_plus_cutoffs_df[outcome_plus_cutoffs_df$outcome == outcomes_list[i], "cutoffs"], na.rm=T)){
selected_outcomes_list <- append(selected_outcomes_list, outcomes_list[i])
}
}
}
clin_matches <- unique(grep(paste(selected_outcomes_list,collapse="|"),
sub_aumc_df_m$PRO_name, value=TRUE))
clin_sub_aumc_df_m <- sub_aumc_df_m
clin_sub_aumc_df_m[clin_sub_aumc_df_m$PRO_name %notin% clin_matches, "PRO_value"] <- NA
clin_sub_aumc_df_m$m_date <- str_split_fixed(clin_sub_aumc_df_m$PRO_name, "_", 2)[,1]
clin_sub_aumc_df_m$m_date <- factor(clin_sub_aumc_df_m$m_date)
clin_sub_aumc_df_m$PRO_group <- str_split_fixed(clin_sub_aumc_df_m$PRO_name, "_", 2)[,2]
# Two scaling options - 1) scale within each outcome measure
# sort data frame on PRO_group and PRO_name otherwise merge of scaled column will be incorrect
clin_sub_aumc_df_m <- clin_sub_aumc_df_m[order(clin_sub_aumc_df_m$PRO_group, clin_sub_aumc_df_m$PRO_name), ]
tst <- split(clin_sub_aumc_df_m$PRO_value, clin_sub_aumc_df_m$PRO_group)
tst_ls <- lapply(tst, elascer)
clin_sub_aumc_df_m<-cbind(clin_sub_aumc_df_m, data.frame(PRO_value_scaled=unlist(tst_ls)))
# Two scaling options - 2) scale all outcome measures without grouping prior
# clin_sub_aumc_df_m$PRO_value_scaled <- elascer(clin_sub_aumc_df_m$PRO_value)
user_dc_df$CCP_dates <- user_dc_df$date
if (all(is.na(user_dc_df$complexity_peaks))) {
print(paste("For user ==", user, "there were ZERO complexity peaks"))
user_failed_list <- c(user_failed_list, user)
}else{
user_dc_df[user_dc_df$complexity_peaks < 0.5, "CCP_dates"] <- NA
}
# make sure the outcomes_list and the user outcomes data frame are sorted the same way otherwise stuff will not match correctly
clin_sub_aumc_df_m <- clin_sub_aumc_df_m[order(clin_sub_aumc_df_m$PRO_group, clin_sub_aumc_df_m$PRO_name), ]
outcomes_list <- sort(outcomes_list)
clinically_relevant_bool_col <- c()
for (i in 1:length(outcomes_list)) {
diff_list <- c()
current_outcome_vector <- clin_sub_aumc_df_m[grep(outcomes_list[i], clin_sub_aumc_df_m$PRO_name), "PRO_value"]
for (j in 2: length(current_outcome_vector)-1) {
diff_list <- append(diff_list, current_outcome_vector[j + 1] - current_outcome_vector[j])
}
if (outcomes_list[i] %in% c("NHPT_ND_avg", "NHPT_D_avg", "TWT_avg")){
clinically_relevant_bool <- abs(diff_list) > diff_list[1] * outcome_plus_cutoffs_df[outcome_plus_cutoffs_df$outcome == outcomes_list[i], "cutoffs"]
clinically_relevant_bool <- c(FALSE, clinically_relevant_bool)
for (j in 2: length(clinically_relevant_bool)) {
if (clinically_relevant_bool[j] & !is.na(clinically_relevant_bool[j])) {
clinically_relevant_bool[j - 1] <- TRUE
}
}
clinically_relevant_bool_col <- append(clinically_relevant_bool_col, clinically_relevant_bool)
} else{
clinically_relevant_bool <- abs(diff_list) >= outcome_plus_cutoffs_df[outcome_plus_cutoffs_df$outcome == outcomes_list[i], "cutoffs"]
clinically_relevant_bool <- c(FALSE, clinically_relevant_bool)
for (j in 2: length(clinically_relevant_bool)) {
if (clinically_relevant_bool[j] & !is.na(clinically_relevant_bool[j])) {
clinically_relevant_bool[j - 1] <- TRUE
}
}
clinically_relevant_bool_col <- append(clinically_relevant_bool_col, clinically_relevant_bool)
}
}
clin_sub_aumc_df_m$clinically_relevant_boolean <- clinically_relevant_bool_col
# tiff(paste0("/home/james/Data_Science/data-science-project/research-papers/2020-NLTSA/latex/Images/", user, "_main_results.tiff"), units="in", width=10, height=4, res=400)
# png(paste0("/home/james/Data_Science/data-science-project/research-papers/2020-NLTSA/latex/Images/", user, "_main_results.png"), units="in", width=10, height=4, res=400)
date_selection <- unique(clin_sub_aumc_df_m[as.character(clin_sub_aumc_df_m$visitdate_cat) %in% questionnare_date_cols, "date"])
plot <- ggplot(clin_sub_aumc_df_m) +
geom_point(data=clin_sub_aumc_df_m[clin_sub_aumc_df_m$clinically_relevant_boolean, ],
aes(x=date, y=PRO_value_scaled, color=PRO_group, group = PRO_group)) +
geom_line(data=clin_sub_aumc_df_m[clin_sub_aumc_df_m$clinically_relevant_boolean, ],
aes(x=date, y=PRO_value_scaled, color=PRO_group, group = PRO_group#, size = PRO_group
)) +
geom_point(data=na.omit(clin_sub_aumc_df_m[!clin_sub_aumc_df_m$clinically_relevant_boolean, ]),
aes(x=date, y=PRO_value_scaled, color=PRO_group, group = PRO_group), alpha=0.1) +
geom_line(data=na.omit(clin_sub_aumc_df_m[!clin_sub_aumc_df_m$clinically_relevant_boolean, ]),
aes(x=date, y=PRO_value_scaled, color=PRO_group, group = PRO_group), alpha=0.1) +
geom_line(data = user_dc_df, aes(date, dynamic_complexity_sum), size=0.5) +
geom_point(data = user_dc_df, aes(date, dynamic_complexity_sum), shape = 16, size=2) +
xlim(min(aumc_df_m[aumc_df_m$user_id == user, "date"], na.rm = TRUE),
max(aumc_df_m[aumc_df_m$user_id == user, "date"], na.rm = TRUE)) +
ylim(0, max(elascer(user_dc_df[user_dc_df$user_id == user, "PRO_value"]), na.rm = TRUE)) +
geom_vline(xintercept = user_dc_df[user_dc_df$complexity_peaks > 0, "date"], col="red", lty=2) +
scale_x_date(breaks = date_selection,
labels = c("m00", "m002", "m03", "m06", "m09", "m12")[1:length(date_selection)]) +
labs(x = "Date", y = "Measure Change (Scaled)") +
scale_color_discrete(name="Outcome measure") +
scale_color_manual("Outcome Measures",
breaks = names(custom_cmap),
values = custom_cmap,
) +
# scale_size_manual(values = c(rep(0.3, 4), 1.5, rep(0.3, 7))) +
theme_minimal() +
# theme(axis.text.x = element_text(angle = 0), legend.title = element_blank())
theme(axis.text.x = element_text(angle = 60, hjust = 0.9, size=10),
axis.text.y = element_text(size=10), legend.title = element_blank(),
plot.title = element_text(hjust = 0.5)) +
ggtitle(paste("Change in time series data for user ==", user))
print(plot)
ggsave(paste0("/home/james/Data_Science/data-science-project/research-papers/2020-NLTSA/latex/Images/", user, "_main_results.eps"))
# dev.off()
}
}
[1] "mri_change"
[1] "mri_no_change"
[1] "hc_users"
[1] "For user == 381 there were ZERO complexity peaks"
[1] "For user == 391 there were ZERO complexity peaks"
[1] "For user == 403 there were ZERO complexity peaks"
[1] "For user == 404 there were ZERO complexity peaks"
[1] "For user == 405 there were ZERO complexity peaks"
[1] "For user == 423 there were ZERO complexity peaks"
[1] "For user == 439 there were ZERO complexity peaks"
[1] "For user == 448 there were ZERO complexity peaks"
Show the missingness within each user and per group
for (users_list_number in 1:length(list_of_user_index_lists)) {
user_list_name<-names(list_of_user_index_lists[users_list_number])
users_selection_index<-list_of_user_index_lists[[user_list_name]]
print(user_list_name)
# use the chosen index to get a list of users
user_selection <- aumc_df$user_id[users_selection_index]
## Show missingness percentages
all_missing_per_user <- data.frame()
for (user in user_selection) {
current_user_missing <- data.frame(user_id = user, t(colMeans(is.na(key_df[key_df$user_id == user, feature_selection]))*100))
all_missing_per_user <- rbind(all_missing_per_user, current_user_missing)
}
print("All percentage missingness per user per feature")
print(all_missing_per_user)
print("Median percentage missingness per user")
median_percent_miss <- data.frame(user_id = user_selection, median_percentage_missing = rowMedians(as.matrix(all_missing_per_user[, feature_selection])))
print(median_percent_miss)
print("Median of the Median percentage missingness per user")
print(median(median_percent_miss$median_percentage_missing, na.rm=TRUE))
cat("\n")
}
[1] "mri_change"
[1] "All percentage missingness per user per feature"
[1] "Median percentage missingness per user"
[1] "Median of the Median percentage missingness per user"
[1] 0.536193
[1] "mri_no_change"
[1] "All percentage missingness per user per feature"
[1] "Median percentage missingness per user"
[1] "Median of the Median percentage missingness per user"
[1] 2.036246
[1] "hc_users"
[1] "All percentage missingness per user per feature"
[1] "Median percentage missingness per user"
[1] "Median of the Median percentage missingness per user"
[1] 1.011029
Plot the CRD and CCP of an example user
LS0tCnRpdGxlOiAiU3VwcGxlbWVudGFsIG1hdGVyaWFsOiBFYXJseS1XYXJuaW5nIFNpZ25hbHMgZm9yIERpc2Vhc2UgQWN0aXZpdHkgaW4gUGF0aWVudHMgRGlhZ25vc2VkIHdpdGggTXVsdGlwbGUgU2NsZXJvc2lzIGJhc2VkIG9uIEtleXN0cm9rZSBEeW5hbWljcyIKc3VidGl0bGU6ICJUaGUgZm9sbG93aW5nIG5vdGVib29rIGlzIHRoZSBjb25zdHJ1Y3Rpb24gb2YgY29tcGxleGl0eSBtZWFzdXJlcyBhbmQgdGhlIGZpbmFsIHJlc3VsdHMgcmVsYXRpdmUgdG8gX19zdWJzZWN0aW9uIElJSS5DX18gYW5kIF9fc2VjdGlvbiBJVl9fLiAiCmF1dGhvcjogCi0gIkphbWVzIFR3b3NlIgotICJqYW1lc0BuZXVyb2Nhc3QubmwiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKIyMjIyBSZWFkIGluIG5lY2Vzc2FyeSBsaWJyYXJpZXMsIGZ1bmN0aW9ucyBhbmQgbG9vayB1cCB0YWJsZXMKCmBgYHtyIGVjaG89VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShpZ3JhcGgpCmxpYnJhcnkocWdyYXBoKQpsaWJyYXJ5KGNhc25ldCkKbGlicmFyeShwbHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KHBzeWNoKQpsaWJyYXJ5KGxtZTQpCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KHBhcmFsbGVsKQpsaWJyYXJ5KHdlc2FuZGVyc29uKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkobWF0cml4U3RhdHMpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KGNvd3Bsb3QpCgojIHNhdmUgdGhlIG9yaWdpbmFsIHBsb3R0aW5nIHNwYWNlIHBhcmFtZXRlcnMKb2dfcGFyIDwtIHBhcigpCmBgYAoKIyMjIyMgU2hvdyB0aGUgc2Vzc2lvbiBpbmZvcm1hdGlvbgpgYGB7cn0KZGV2dG9vbHM6OnNlc3Npb25faW5mbygpCmBgYAoKCmBgYHtyIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgcmVhZCBpbiBoZWxwZnVsIGZ1bmN0aW9ucyBhbmQgbGlzdHMKc291cmNlKCJ+L0RhdGFfU2NpZW5jZS9kYXRhLXNjaWVuY2UtcHJvamVjdC9yZXNlYXJjaC1wYXBlcnMvMjAyMC1OTFRTQS9jb2RlL2F1bWNfZXh0cmFzLlIiKQpoaWdoX2Nvcl9mZWF0dXJlcyA8LSByZWFkLmNzdigifi9EYXRhX1NjaWVuY2UvZGF0YS1zY2llbmNlLXByb2plY3QvcmVzZWFyY2gtcGFwZXJzLzIwMjAtQVVtYy1sb25naXR1ZGluYWwtYW5hbHlzaXMvY29kZS91c2VmdWxfc2NyaXB0cy9zZWxlY3RlZF9mZWF0dXJlc193aXRoX291dGNvbWVfZGYuY3N2IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzPTEpCmBgYAoKIyMjIyBSZWFkIGluIGRhdGEKCmBgYHtyLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKI1JFQUQgSU4gREFUQT09PT0KQVdTX0tFWTwtc2V0X2Vudl9BV1MoIn4vRGF0YV9TY2llbmNlL2RhdGEtc2NpZW5jZS1wcm9qZWN0LyIpCiMgS2V5c3Ryb2tlIGRhdGEKQVdTX2RhdGE8LWdldF9kYXRhX0FXU19uZXVyb192bV9zaGFyZWQoeW91cl9BV1NfZm9sZGVyPSJKYW1lcy8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuX2RhdGFfc2V0PSJWdW1jX3VzZXJzXzM2M181MDBfZGF5XzIwMTgwODIxXzIwMjAwMzEzX05FV19BR0dzLmNzdiIpCmtleV9kZiA8LSBBV1NfZGF0YSRBV1NfZGF0YQoKIyBJRCBkYXRhCkFXU19kYXRhPC1nZXRfZGF0YV9BV1NfbmV1cm9fdm1fc2hhcmVkKHlvdXJfQVdTX2ZvbGRlcj0iVlVtYy8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuX2RhdGFfc2V0PSJ2dW1jX2NvZGVfbmV1cm9rZXlzX2NvZGVfdXNlcl9pZC5jc3YiKQppZF9kZiA8LSBBV1NfZGF0YSRBV1NfZGF0YQoKIyBBVW1jIGRhdGEKQVdTX2RhdGE8LWdldF9kYXRhX0FXU19uZXVyb192dW1jX2thX2hvb19wcml2YXRlKHlvdXJfQVdTX2ZvbGRlcj0iIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbl9kYXRhX3NldD0iMjAyMDA1MTNfQVBQU01TX2V4cG9ydF9EU19GcmllbmRseS5jc3YiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwPSI7Iiwgcm93Lm5hbWVzPU5VTEwpCmF1bWNfZGYgPC0gQVdTX2RhdGEkQVdTX2RhdGEKCiMgbWVyZ2UgdXNlcl9pZCB3aXRoIHRoZSBhdW1jX2RmCmF1bWNfZGYgPC0gbWVyZ2UoaWRfZGZbLCAtYygxLCAyKV0sIGF1bWNfZGYsIGJ5Lng9IlZVbWMuY29kZSIsIGJ5Lnk9IlZVbWNfY29kZSIpWywgLWMoMSldCgojIHJlbmFtZSBhbmQgb3JkZXIgYXVtY19kZiBvbiB1c2VyX2lkCm5hbWVzKGF1bWNfZGYpW25hbWVzKGF1bWNfZGYpID09ICdOZXVyb2tleXMuSUQnXSA8LSAndXNlcl9pZCcKYXVtY19kZiA8LSBhdW1jX2RmW29yZGVyKGF1bWNfZGYkdXNlcl9pZCksIF0KCiMgbWVyZ2Ugc2V0cyB0aGUgbWVyZ2UgY29sdW1uIHRvIGJlIHJvdyBuYW1lcywgcmVzZXQgdGhpcyBmb2xsb3dpbmcgcmVvcmRlcgpyb3duYW1lcyhhdW1jX2RmKSA8LSAxOm5yb3coYXVtY19kZikKCiMgcmVuYW1lIHN0cmF5IHZpc2l0IGRhdGUgY29sdW1uCm5hbWVzKGF1bWNfZGYpW25hbWVzKGF1bWNfZGYpID09ICdtMDBfdmlzaXRfZGF0ZSddIDwtICdtMDBfdmlzaXRkYXRlJwoKIyByZXBsYWNlIGRlZmF1bHQgdmFsdWVzIHdpdGggTkEKYXVtY19kZlthdW1jX2RmID09ICIxLTEtMjk5OSJdIDwtIE5BCmF1bWNfZGZbYXVtY19kZiA8IC05NF0gPC0gTkEKCiMgY29udmVydCB0aGUgdGltZXN0YW1wIGFuZCBkYXRlIGNvbHVtbnMgdG8gYSBodW1hbiByZWFkYWJsZSBmb3JtYXQKIyBrZXlfZGYkdGltZXN0YW1wIDwtIGFzLlBPU0lYY3QoKGtleV9kZiR0aW1lc3RhbXAgKyBrZXlfZGYkdGltZVpvbmVPZmZzZXQpLzEwMDAsIGZvcm1hdCA9ICIlWS0lbS0lZCIsIHR6PSJ1dGMiLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpCmtleV9kZiR0aW1lc3RhbXAgPC0gYXMuUE9TSVhjdChrZXlfZGYkdGltZXN0YW1wLzEwMDAgKyBrZXlfZGYkdGltZVpvbmVPZmZzZXQsIGZvcm1hdCA9ICIlWS0lbS0lZCIsIHR6PSJ1dGMiLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpCiMga2V5X2RmJHRpbWVzdGFtcCA8LSBrZXlfZGYkdGltZXN0YW1wICsga2V5X2RmJHRpbWVab25lT2Zmc2V0CmRhdGVfY29scyA8LSBjb2xuYW1lcyhhdW1jX2RmICU+JSBzZWxlY3QobWF0Y2hlcygiZGF0ZSIpKSkKdmlzaXRkYXRlX2NvbHMgPC0gY29sbmFtZXMoYXVtY19kZiAlPiUgc2VsZWN0KG1hdGNoZXMoInZpc2l0ZGF0ZSIpKSkKcXVlc3Rpb25uYXJlX2RhdGVfY29scyA8LSBjb2xuYW1lcyhhdW1jX2RmICU+JSBzZWxlY3QobWF0Y2hlcygiUXVlc3Rpb25uYWlyZXNfY29tcCIpKSkKCmF1bWNfZGZbZGF0ZV9jb2xzXSA8LSBhcHBseShhdW1jX2RmW2RhdGVfY29sc10sIDIsIGFzLlBPU0lYbHQsIGZvcm1hdCA9ICIlZC0lbS0lWSIsIHR6PSJ1dGMiLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpCgoKYGBgCgojIyMjIFByZXBhcmUgdGhlIE91dGNvbWUgbWVhc3VyZXMKCmBgYHtyLCBlY2hvPVRSVUV9CmZvciAodGltZXBvaW50IGluIGMoJ20wMCcsICdtMDMnLCAnbTA2JywgJ20wOScsICdtMTInKSkgewogIGF1bWNfZGZbLCBwYXN0ZTAodGltZXBvaW50LCAiX05IUFRfRF9hdmciKV0gPC0gcm93TWVhbnMoYXVtY19kZlssIGMocGFzdGUwKHRpbWVwb2ludCwgIl9OSFBUX0QxIiksIHBhc3RlMCh0aW1lcG9pbnQsIl9OSFBUX0QyIikpXSwgbmEucm09VCkKICBhdW1jX2RmWywgcGFzdGUwKHRpbWVwb2ludCwgIl9OSFBUX05EX2F2ZyIpXSA8LSByb3dNZWFucyhhdW1jX2RmWywgYyhwYXN0ZTAodGltZXBvaW50LCAiX05IUFRfTkQxIiksIHBhc3RlMCh0aW1lcG9pbnQsIl9OSFBUX05EMiIpKV0sIG5hLnJtPVQpCiAgYXVtY19kZlssIHBhc3RlMCh0aW1lcG9pbnQsICJfTkhQVF9CSF9hdmciKV0gPC0gcm93TWVhbnMoYXVtY19kZlssIGMocGFzdGUwKHRpbWVwb2ludCwgIl9OSFBUX0RfYXZnIiksIHBhc3RlMCh0aW1lcG9pbnQsIl9OSFBUX05EX2F2ZyIpKV0sIG5hLnJtPVQpCiAgYXVtY19kZlssIHBhc3RlMCh0aW1lcG9pbnQsICJfVFdUX2F2ZyIpXSA8LSByb3dNZWFucyhhdW1jX2RmWywgYyhwYXN0ZTAodGltZXBvaW50LCAiX1RXVF8xIiksIHBhc3RlMCh0aW1lcG9pbnQsIl9UV1RfMiIpKV0sIG5hLnJtPVQpCn0KCmBgYAoKIyMjIyBHZXQgYSBsaXN0IG9mIGFsbCB0aGUgb3V0Y29tZSBtZWFzdXJlcyBvZiBpbnRlcmVzdCBhdCBlYWNoIHRpbWUgcG9pbnQKCmBgYHtyfQpvdXRjb21lc19vZl9pbnRlcmVzdF9scyA8LSBjKCkKZm9yIChvdXRjb21lIGluIG91dGNvbWVzX2xpc3QpIHsKICBvdXRjb21lc19vZl9pbnRlcmVzdF9scyA8LSBhcHBlbmQob3V0Y29tZXNfb2ZfaW50ZXJlc3RfbHMsIGNvbG5hbWVzKGF1bWNfZGYgJT4lIHNlbGVjdChtYXRjaGVzKG91dGNvbWUpKSkpCn0KCmBgYAoKIyMjIyBGZWF0dXJlcyB1c2VkIGluIHRoZSBjcmVhdGlvbiBvZiB0aGUgY29tcGxleGl0eSBtZWFzdXJlcyAodGFrZW4gUlFBIHB5dGhvbiBub3RlYm9vaykKCmBgYHtyfQojIGZlYXR1cmVzIHdpdGggaGlnaGVzdCBycWEgbWVhc3VyZXMgKG1vc3QgaW50ZXJlc3RpbmcgTkxUU0Egd2lzZSkKZmVhdHVyZV9zZWxlY3Rpb24gPC0gYygncG9zdF9jb3JyZWN0aW9uX3Nsb3dpbmdfTUVBTl8yQ0QnLAogJ3ByZV9jb3JyZWN0aW9uX3Nsb3dpbmdfTUVBTl8yQ0QnLAogJ2hvbGRfdGltZV9TVEQnLAogJ2ZsaWdodF90aW1lX01FRElBTicsCiAnZmxpZ2h0X3RpbWVfTUVBTl9BQlNfQ0hBTkdFJywKICdob2xkX3RpbWVfU0tFVycsCiAnY29ycmVjdGlvbl9kdXJhdGlvbl9NRUFOXzJDRCcsCiAncG9zdF9jb3JyZWN0aW9uX3Nsb3dpbmdfUF9BVVRPX0NPUlInLAogJ2FmdGVyX3B1bmN0dWF0aW9uX3BhdXNlX01FQU5fQ0hBTkdFJywKICdhZnRlcl9wdW5jdHVhdGlvbl9wYXVzZV9NRUFOXzJDRCcsCiAnc2Vzc2lvbl9kdXJhdGlvbl9NRUFOXzJDRCcsCiAnYWZ0ZXJfcHVuY3R1YXRpb25fcGF1c2VfTUVESUFOJywKICdmbGlnaHRfdGltZV9NQVgnKQoKZmVhdHVyZV9zZWxlY3Rpb24KCmBgYAoKIyMjIyBJZGVudGlmeSB0aGUgZGlmZmVyZW50IGdyb3VwcyAoY2hhbmdlIGluIE1SSSwgbm8gY2hhbmdlIGluIE1SSSBhbmQgSEMgdXNlcnMpIGFuZCBwcmVwIHRoZSBkYXRhIGZvciB0aGUgYW5hbHlzaXMKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQptcmlfY29scyA8LSBjb2xuYW1lcyhhdW1jX2RmICU+JSBzZWxlY3QobWF0Y2hlcygibXJpX2VuaGFuY2luZyIpKSkKCmRpZmZfZGYgPC0gTkEKZm9yIChpIGluIHNlcSgxLCBsZW5ndGgobXJpX2NvbHMpLTEpKSB7CiAgZGlmZl9kZiA8LSBjYmluZChkaWZmX2RmLCBhdW1jX2RmW21yaV9jb2xzW2krMV1dIC0gYXVtY19kZlttcmlfY29sc1tpXV0pCn0KZGlmZl9kZiA8LSBkaWZmX2RmWywgMjpuY29sKGRpZmZfZGYpXQpkaWZmX2RmIDwtIGRpZmZfZGZbcm93U3Vtcyhpcy5uYShkaWZmX2RmKSkgPCAzLCBdCgojIyBTUEVDSUZZIFRIRSBJTkRJQ0VTIFlPVSBXSVNIIFRPIElOVkVTVElHQVRFCnJvd19hbW91bnQgPC0gNTgKIyB1c2VyIGluZGljZXMgd2hpY2ggaGF2ZSBhIGNoYW5nZSBpbiBtcmkKbXJpX2NoYW5nZV9pbmRleCA8LSBhcy5pbnRlZ2VyKG5hbWVzKHdoaWNoKHJvd1N1bXMoYWJzKGRpZmZfZGYpLCBuYS5ybT1UUlVFKSA+IDApKSkKbXJpX2NoYW5nZV9scyA8LSBhdW1jX2RmJHVzZXJfaWRbbXJpX2NoYW5nZV9pbmRleF0Kc2hvcnRfbXJpX2NoYW5nZV9scyA8LSBjKCkKZm9yICh1c2VyIGluIG1yaV9jaGFuZ2VfbHMpIHsKICBpZiAobnJvdyhrZXlfZGZba2V5X2RmJHVzZXJfaWQgPT0gdXNlciwgXSkgPiByb3dfYW1vdW50KSB7CiAgICBzaG9ydF9tcmlfY2hhbmdlX2xzIDwtIGMoc2hvcnRfbXJpX2NoYW5nZV9scywgdXNlcikKICB9Cn0KbXJpX2NoYW5nZV9pbmRleCA8LSB3aGljaChhdW1jX2RmJHVzZXJfaWQgJWluJSBzaG9ydF9tcmlfY2hhbmdlX2xzKQojIHVzZXIgaW5kaWNlcyB3aGljaCBkb24ndCBoYXZlIGEgY2hhbmdlIGluIG1yaQptcmlfbm9fY2hhbmdlX2luZGV4IDwtIGFzLmludGVnZXIobmFtZXMod2hpY2gocm93U3VtcyhhYnMoZGlmZl9kZiksIG5hLnJtPVRSVUUpID09IDApKSkKbXJpX25vX2NoYW5nZV9scyA8LSBhdW1jX2RmJHVzZXJfaWRbbXJpX25vX2NoYW5nZV9pbmRleF0Kc2hvcnRfbXJpX25vX2NoYW5nZV9scyA8LSBjKCkKZm9yICh1c2VyIGluIG1yaV9ub19jaGFuZ2VfbHMpIHsKICBpZiAobnJvdyhrZXlfZGZba2V5X2RmJHVzZXJfaWQgPT0gdXNlciwgXSkgPiByb3dfYW1vdW50KSB7CiAgICBzaG9ydF9tcmlfbm9fY2hhbmdlX2xzIDwtIGMoc2hvcnRfbXJpX25vX2NoYW5nZV9scywgdXNlcikKICB9Cn0KbXJpX25vX2NoYW5nZV9pbmRleCA8LSB3aGljaChhdW1jX2RmJHVzZXJfaWQgJWluJSBzaG9ydF9tcmlfbm9fY2hhbmdlX2xzKQojIGhlYWx0aHkgY29udHJvbHMKaGNfdXNlcnNfaW5kZXggPC0gd2hpY2goYXVtY19kZiRkaWFnbm9zaXNfbXMgPT0gMCkKaGNfdXNlcnNfbHMgPC0gYXVtY19kZiR1c2VyX2lkW2hjX3VzZXJzX2luZGV4XQpzaG9ydF9oY191c2Vyc19scyA8LSBjKCkKZm9yICh1c2VyIGluIGhjX3VzZXJzX2xzKSB7CiAgaWYgKG5yb3coa2V5X2RmW2tleV9kZiR1c2VyX2lkID09IHVzZXIsIF0pID4gcm93X2Ftb3VudCkgewogICAgc2hvcnRfaGNfdXNlcnNfbHMgPC0gYyhzaG9ydF9oY191c2Vyc19scywgdXNlcikKICB9Cn0KaGNfdXNlcnNfaW5kZXggPC0gd2hpY2goYXVtY19kZiR1c2VyX2lkICVpbiUgc2hvcnRfaGNfdXNlcnNfbHMpCgpsaXN0X29mX3VzZXJfaW5kZXhfbGlzdHM9YyhkYXRhLmZyYW1lKG1yaV9jaGFuZ2U9bXJpX2NoYW5nZV9pbmRleCksCiAgICAgICAgICAgZGF0YS5mcmFtZShtcmlfbm9fY2hhbmdlPW1yaV9ub19jaGFuZ2VfaW5kZXgpLAogICAgICAgICAgIGRhdGEuZnJhbWUoaGNfdXNlcnM9aGNfdXNlcnNfaW5kZXgpKQoKcHJlcHBlZF9kZnNfbGlzdCA8LSBsaXN0KCkKZm9yICh1c2Vyc19saXN0X251bWJlciBpbiAxOmxlbmd0aChsaXN0X29mX3VzZXJfaW5kZXhfbGlzdHMpKXsKICB1c2VyX2xpc3RfbmFtZTwtbmFtZXMobGlzdF9vZl91c2VyX2luZGV4X2xpc3RzW3VzZXJzX2xpc3RfbnVtYmVyXSkKICB1c2Vyc19zZWxlY3Rpb25faW5kZXg8LWxpc3Rfb2ZfdXNlcl9pbmRleF9saXN0c1tbdXNlcl9saXN0X25hbWVdXQogIAogIHByaW50KHVzZXJfbGlzdF9uYW1lKQogIAogICMgdXNlIHRoZSBjaG9zZW4gaW5kZXggdG8gZ2V0IGEgbGlzdCBvZiB1c2VycwogIHVzZXJfc2VsZWN0aW9uIDwtIGF1bWNfZGYkdXNlcl9pZFt1c2Vyc19zZWxlY3Rpb25faW5kZXhdCiAgCiAgcHJpbnQodXNlcl9zZWxlY3Rpb24pCiAgCiAgIyBjcmVhdGUgYSBzdWJzZXQgb2YgdGhlIGF1bWNfZGYgd2hpY2ggaW5jbHVkZXMgb25seSB0aGUgdXNlcnMgb2YgaW50ZXJlc3QKICBtaXNzaW5nX2RmIDwtIGRhdGEuZnJhbWUoInNhbXBsZSBzaXplIiA9IGNvbFN1bXMoIWlzLm5hKGF1bWNfZGZbYXVtY19kZiRkaWFnbm9zaXNfbXMgPT0gMSwgYygidXNlcl9pZCIsIG1yaV9jb2xzLCB2aXNpdGRhdGVfY29scyldKSkpCiAgCiAgIyBzdWJzZXQgcmVsZXZhbnQgdXNlcnMnIGtleXN0cm9rZSBkYXRhCiAga2V5X3N1Yl9kZiA8LSBrZXlfZGZba2V5X2RmJHVzZXJfaWQgJWluJSB1c2VyX3NlbGVjdGlvbiwgXQogICMgb3JkZXIgYmFzZWQgb24gdXNlciBpZCBhbmQgdGltZXN0YW1wCiAga2V5X3N1Yl9kZiA8LSBrZXlfc3ViX2RmW29yZGVyKGtleV9zdWJfZGYkdXNlcl9pZCwga2V5X3N1Yl9kZiR0aW1lc3RhbXApLCBdCiAgCiAgCiAgIyMgQ3JlYXRpbmcgYSBkYXRhIGZyYW1lIG9mIHRoZSBjb21wbGV4aXR5IG1lYXN1cmVzIHBlciB1c2VyCgogICMjIFdyaXRpbmcgY29kZSB3aGljaCB3aWxsIHBhcmFsbGVsaXplIHRoZSBFV1MgY2FsY3VsYXRpb25zCiAgZGZzX2xpc3QgPC0gZGZfdXNlcl9saXN0KGRmID0ga2V5X3N1Yl9kZiwgZmVhdHVyZXMgPSBjKCJ1c2VyX2lkIiwgInRpbWVzdGFtcCIsIGZlYXR1cmVfc2VsZWN0aW9uKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZXJfY29sdW1uX25hbWUgPSAidXNlcl9pZCIsIHVzZXJzID0gdXNlcl9zZWxlY3Rpb24pCiAgY29tcGxleGl0eV9kZnNfbGlzdCA8LSBtY2xhcHBseShkZnNfbGlzdCwgRVdTX2NhbGMpICMgbWNsYXBwbHkgaXMgdGhlIHBhcmFsbGVsaXplZCB2ZXJzaW9uIG9mIGxhcHBseQogIAogICMgZmlsdGVyIG9ubHkgdGhlIGRhdGEgZnJhbWVzIGluIHRoZSBsaXN0IChkcm9wIHdoZXJlIHRoZXJlIHdhcyBub3QgZW5vdWdoIGRhdGEpCiAgY29tcGxleGl0eV9kZnNfbGlzdCA8LSBGaWx0ZXIoZnVuY3Rpb24oeCkgaXMuZGF0YS5mcmFtZSh4KVtbMV1dID4gMCwgY29tcGxleGl0eV9kZnNfbGlzdCkKICAKICBjb21wbGV4X2RmIDwtIHJiaW5kLmZpbGwoY29tcGxleGl0eV9kZnNfbGlzdCkKCiAgIyBtZXJnZSB0aGUgY29tcGxleCBkZiBiYWNrIHRvIHRoZSBrZXlzdHJva2UgZGYKICBrZXlfY29tcGxleF9kZiA8LSBtZXJnZShrZXlfc3ViX2RmWywgYygidXNlcl9pZCIsICJ0aW1lc3RhbXAiLCBmZWF0dXJlX3NlbGVjdGlvbildLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBjb21wbGV4X2RmLCBieSA9IGMoInVzZXJfaWQiLCAidGltZXN0YW1wIikpIAogICMgcmVsYWJlbCB0aGUgMTAncyB0byAxJ3MgaW4gdGhlIGNvbXBsZXhpdHlfcGVha3MgZmFjdG9yIHRvIGF2b2lkIGlzc3VlcyBsYXRlcgogIGtleV9jb21wbGV4X2RmJGNvbXBsZXhpdHlfcGVha3Nba2V5X2NvbXBsZXhfZGYkY29tcGxleGl0eV9wZWFrcyA9PSAxMF0gPC0gMQogIAogICMgYXVtY19vdXRjb21lX3NlbGVjdG9yIDwtIG1yaV9jb2xzCiAgYXVtY19vdXRjb21lX3NlbGVjdG9yIDwtIG91dGNvbWVzX29mX2ludGVyZXN0X2xzCiAgIyBjcmVhdGUgYSBtZWx0ZWQgdmVyc2lvbiBvZiB0aGUgYXVtY19kZiB3aXRoIG9ubHkgdGhlIHZhcmlhYmxlcyBvZiBpbnRlcmVzdAogIGF1bWNfZGZfbV8xIDwtIG1lbHQoYXVtY19kZltjKCJ1c2VyX2lkIiwgYXVtY19vdXRjb21lX3NlbGVjdG9yKV0sIGlkLnZhcnM9InVzZXJfaWQiLAogICAgICAgICAgICAgICAgICAgICAgdmFyaWFibGUubmFtZSA9ICJQUk9fbmFtZSIsIHZhbHVlLm5hbWUgPSAiUFJPX3ZhbHVlIikKICBhdW1jX2RmW2MoZGF0ZV9jb2xzKV0gPC0gYXBwbHkoYXVtY19kZltjKGRhdGVfY29scyldLCAyLCBhcy5jaGFyYWN0ZXIpCiAgYXVtY19kZl9tXzIgPC0gbWVsdChhdW1jX2RmW2MoInVzZXJfaWQiLCBkYXRlX2NvbHMpXSwgaWQudmFycz0idXNlcl9pZCIsIAogICAgICAgICAgICAgICAgICAgICAgdmFyaWFibGUubmFtZSA9ICJ2aXNpdGRhdGVfY2F0IiwgdmFsdWUubmFtZSA9ICJkYXRlIikKICBhdW1jX2RmX21fMiRkYXRlIDwtIGFzLkRhdGUoYXVtY19kZl9tXzIkZGF0ZSkKICAKICBhdW1jX2RmX21fMSA8LSBhdW1jX2RmX21fMSAlPiUgbXV0YXRlKHRpbWVfY29kZSA9IHN1YnN0cihQUk9fbmFtZSwxTCw0TCkpCiAgYXVtY19kZl9tXzIgPC0gYXVtY19kZl9tXzIgJT4lIG11dGF0ZSh0aW1lX2NvZGUgPSBzdWJzdHIodmlzaXRkYXRlX2NhdCwxTCw0TCkpCiAgYXVtY19kZl9tIDwtIG1lcmdlKGF1bWNfZGZfbV8xLCBhdW1jX2RmX21fMiwgYnkgPSBjKCJ1c2VyX2lkIiwgInRpbWVfY29kZSIpKQogIAogICMgc2VsZWN0IHRoZSBvdXRjb21lIG1lYXN1cmVzIGFuZCB0aGVpciBjb3JyZXNwb25kaW5nIHF1ZXN0aW9ubmFpcmUvIHZpc2l0ZGF0ZXMgdGhlbiByZWFzc2lnbgogIGF1bWNfZGZfbV92cyA8LSAgYXVtY19kZl9tICU+JSAKICAgIGZpbHRlcihzdHJfZGV0ZWN0KGF1bWNfZGZfbSRQUk9fbmFtZSwgcGFzdGUodmlzaXRkYXRlX291dGNvbWVzLCBjb2xsYXBzZSA9ICJ8IikpICYgCiAgICAgICAgICAgICBzdHJfZGV0ZWN0KGF1bWNfZGZfbSR2aXNpdGRhdGVfY2F0LCBwYXN0ZSh2aXNpdGRhdGVfY29scywgY29sbGFwc2UgPSAifCIpKSkKICAKICBhdW1jX2RmX21fcXMgPC0gIGF1bWNfZGZfbSAlPiUgCiAgICBmaWx0ZXIoc3RyX2RldGVjdChhdW1jX2RmX20kUFJPX25hbWUsIHBhc3RlKHF1ZXN0aW9ubmFpcmVfZGF0ZV9vdXRjb21lcywgY29sbGFwc2UgPSAifCIpKSAmIAogICAgICAgICAgICAgc3RyX2RldGVjdChhdW1jX2RmX20kdmlzaXRkYXRlX2NhdCwgcGFzdGUocXVlc3Rpb25uYXJlX2RhdGVfY29scywgY29sbGFwc2UgPSAifCIpKSkKICBhdW1jX2RmX20gPC0gcmJpbmQoYXVtY19kZl9tX3ZzLCBhdW1jX2RmX21fcXMpCiAgCiAgIyBtZXJnZSB0aGUgbWVsdGVkIGF1bWNfZGYgd2l0aCB0aGUga2V5X2NvbXBsZXhfZGYKICBrZXlfY29tcGxleF9kZiRkYXRlIDwtIGFzLkRhdGUoa2V5X2NvbXBsZXhfZGYkdGltZXN0YW1wKQogIGRmLm1vZCA8LSBtZXJnZShrZXlfY29tcGxleF9kZiwgYXVtY19kZl9tLCBieSA9IGMoInVzZXJfaWQiLCAiZGF0ZSIpLCBhbGwueD1UUlVFLCBhbGwueT1GQUxTRSkKICAKICBwcmVwcGVkX2Rmc19saXN0W1t1c2VyX2xpc3RfbmFtZV1dIDwtIGRmLm1vZAogIAp9CgoKYGBgCgojIyMjIFBsb3QgdGhlIER5bmFtaWMgQ29tcGxleGl0eSwgQ3VtdWxhdGl2ZSBDb21wbGV4aXR5IFBlYWtzLCBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgZGF0YSBmcm9tIHRoZSBHZCBlbmhhbmNpbmcgbGVzaW9ucyBhbmQgUGF0aWVudCBSZXBvcnRlZCBPdXRjb21lIE1lYXN1cmVzCgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCnVzZXJfZmFpbGVkX2xpc3QgPC0gYygpCmZvciAodXNlcnNfbGlzdF9udW1iZXIgaW4gMTpsZW5ndGgobGlzdF9vZl91c2VyX2luZGV4X2xpc3RzKSl7CiAgdXNlcl9saXN0X25hbWU8LW5hbWVzKGxpc3Rfb2ZfdXNlcl9pbmRleF9saXN0c1t1c2Vyc19saXN0X251bWJlcl0pCiAgdXNlcnNfc2VsZWN0aW9uX2luZGV4PC1saXN0X29mX3VzZXJfaW5kZXhfbGlzdHNbW3VzZXJfbGlzdF9uYW1lXV0KICAKICBwcmludCh1c2VyX2xpc3RfbmFtZSkKICAKICBwcmVwcGVkX2RmIDwtIHByZXBwZWRfZGZzX2xpc3RbW3VzZXJfbGlzdF9uYW1lXV0KICAgIAogICMgdXNlIHRoZSBjaG9zZW4gaW5kZXggdG8gZ2V0IGEgbGlzdCBvZiB1c2VycwogIHVzZXJfc2VsZWN0aW9uIDwtIGF1bWNfZGYkdXNlcl9pZFt1c2Vyc19zZWxlY3Rpb25faW5kZXhdCiAgCiAgZm9yICh1c2VyIGluIHVzZXJfc2VsZWN0aW9uKSB7CgogICAgdXNlcl9kY19kZiA8LSBwcmVwcGVkX2RmW3ByZXBwZWRfZGYkdXNlcl9pZCA9PSB1c2VyLCBdCiAgICBzdWJfYXVtY19kZl9tIDwtIGF1bWNfZGZfbVthdW1jX2RmX20kdXNlcl9pZCA9PSB1c2VyLCBdCiAgICB0ZW1wIDwtIGF1bWNfZGZfbVthdW1jX2RmX20kdXNlcl9pZCAlaW4lIHVzZXJzX3NlbGVjdGlvbl9pbmRleCwgXQogICAgCiAgICBzZWxlY3RlZF9vdXRjb21lc19saXN0IDwtIGMoKQogICAgZm9yIChpIGluIDE6bGVuZ3RoKG91dGNvbWVzX2xpc3QpKSB7CiAgICAgIGRpZmZfbGlzdCA8LSBjKCkKICAgICAgY3VycmVudF9vdXRjb21lX3ZlY3RvciA8LSBuYS5vbWl0KHN1Yl9hdW1jX2RmX21bZ3JlcChvdXRjb21lc19saXN0W2ldLCBzdWJfYXVtY19kZl9tJFBST19uYW1lKSwgIlBST192YWx1ZSJdKQogICAgICBmb3IgKGogaW4gMjogbGVuZ3RoKGN1cnJlbnRfb3V0Y29tZV92ZWN0b3IpLTEpIHsKICAgICAgICBkaWZmX2xpc3QgPC0gYXBwZW5kKGRpZmZfbGlzdCwgY3VycmVudF9vdXRjb21lX3ZlY3RvcltqICsgMV0gLSBjdXJyZW50X291dGNvbWVfdmVjdG9yW2pdKQogICAgICB9CiAgICAgIGlmIChvdXRjb21lc19saXN0W2ldICVpbiUgYygiTkhQVF9ORF9hdmciLCAiTkhQVF9EX2F2ZyIsICJUV1RfYXZnIikpewogICAgICAgIGlmIChhbnkoYWJzKGRpZmZfbGlzdCkgPiBkaWZmX2xpc3RbMV0gKiBvdXRjb21lX3BsdXNfY3V0b2Zmc19kZltvdXRjb21lX3BsdXNfY3V0b2Zmc19kZiRvdXRjb21lID09IG91dGNvbWVzX2xpc3RbaV0sICJjdXRvZmZzIl0sIG5hLnJtPVQpKXsKICAgICAgICAgIHNlbGVjdGVkX291dGNvbWVzX2xpc3QgPC0gYXBwZW5kKHNlbGVjdGVkX291dGNvbWVzX2xpc3QsIG91dGNvbWVzX2xpc3RbaV0pCiAgICAgICAgfQogICAgfSBlbHNlewogICAgICAgICAgaWYgKGFueShhYnMoZGlmZl9saXN0KSA+PSBvdXRjb21lX3BsdXNfY3V0b2Zmc19kZltvdXRjb21lX3BsdXNfY3V0b2Zmc19kZiRvdXRjb21lID09IG91dGNvbWVzX2xpc3RbaV0sICJjdXRvZmZzIl0sIG5hLnJtPVQpKXsKICAgICAgICAgIHNlbGVjdGVkX291dGNvbWVzX2xpc3QgPC0gYXBwZW5kKHNlbGVjdGVkX291dGNvbWVzX2xpc3QsIG91dGNvbWVzX2xpc3RbaV0pCiAgICAgICAgfQogICAgICB9CiAgICB9CiAgICAKICAgIGNsaW5fbWF0Y2hlcyA8LSB1bmlxdWUoZ3JlcChwYXN0ZShzZWxlY3RlZF9vdXRjb21lc19saXN0LGNvbGxhcHNlPSJ8IiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJfYXVtY19kZl9tJFBST19uYW1lLCB2YWx1ZT1UUlVFKSkKICAgIAogICAgY2xpbl9zdWJfYXVtY19kZl9tIDwtIHN1Yl9hdW1jX2RmX20KICAgIGNsaW5fc3ViX2F1bWNfZGZfbVtjbGluX3N1Yl9hdW1jX2RmX20kUFJPX25hbWUgJW5vdGluJSBjbGluX21hdGNoZXMsICJQUk9fdmFsdWUiXSA8LSBOQQogICAgCiAgICBjbGluX3N1Yl9hdW1jX2RmX20kbV9kYXRlIDwtIHN0cl9zcGxpdF9maXhlZChjbGluX3N1Yl9hdW1jX2RmX20kUFJPX25hbWUsICJfIiwgMilbLDFdCiAgICBjbGluX3N1Yl9hdW1jX2RmX20kbV9kYXRlIDwtIGZhY3RvcihjbGluX3N1Yl9hdW1jX2RmX20kbV9kYXRlKQogICAgY2xpbl9zdWJfYXVtY19kZl9tJFBST19ncm91cCA8LSBzdHJfc3BsaXRfZml4ZWQoY2xpbl9zdWJfYXVtY19kZl9tJFBST19uYW1lLCAiXyIsIDIpWywyXQogICAgCiAgICAjIFR3byBzY2FsaW5nIG9wdGlvbnMgLSAxKSBzY2FsZSB3aXRoaW4gZWFjaCBvdXRjb21lIG1lYXN1cmUKICAgICMgc29ydCBkYXRhIGZyYW1lIG9uIFBST19ncm91cCBhbmQgUFJPX25hbWUgb3RoZXJ3aXNlIG1lcmdlIG9mIHNjYWxlZCBjb2x1bW4gd2lsbCBiZSBpbmNvcnJlY3QKICAgIGNsaW5fc3ViX2F1bWNfZGZfbSA8LSBjbGluX3N1Yl9hdW1jX2RmX21bb3JkZXIoY2xpbl9zdWJfYXVtY19kZl9tJFBST19ncm91cCwgY2xpbl9zdWJfYXVtY19kZl9tJFBST19uYW1lKSwgXQogICAgdHN0IDwtIHNwbGl0KGNsaW5fc3ViX2F1bWNfZGZfbSRQUk9fdmFsdWUsIGNsaW5fc3ViX2F1bWNfZGZfbSRQUk9fZ3JvdXApCiAgICB0c3RfbHMgPC0gbGFwcGx5KHRzdCwgZWxhc2NlcikKICAgIGNsaW5fc3ViX2F1bWNfZGZfbTwtY2JpbmQoY2xpbl9zdWJfYXVtY19kZl9tLCAgZGF0YS5mcmFtZShQUk9fdmFsdWVfc2NhbGVkPXVubGlzdCh0c3RfbHMpKSkKICAgIAogICAgIyBUd28gc2NhbGluZyBvcHRpb25zIC0gMikgc2NhbGUgYWxsIG91dGNvbWUgbWVhc3VyZXMgd2l0aG91dCBncm91cGluZyBwcmlvcgogICAgIyBjbGluX3N1Yl9hdW1jX2RmX20kUFJPX3ZhbHVlX3NjYWxlZCA8LSBlbGFzY2VyKGNsaW5fc3ViX2F1bWNfZGZfbSRQUk9fdmFsdWUpCiAgICAKICAgIHVzZXJfZGNfZGYkQ0NQX2RhdGVzIDwtIHVzZXJfZGNfZGYkZGF0ZQogICAgaWYgKGFsbChpcy5uYSh1c2VyX2RjX2RmJGNvbXBsZXhpdHlfcGVha3MpKSkgewogICAgICBwcmludChwYXN0ZSgiRm9yIHVzZXIgPT0iLCB1c2VyLCAidGhlcmUgd2VyZSBaRVJPIGNvbXBsZXhpdHkgcGVha3MiKSkKICAgICAgdXNlcl9mYWlsZWRfbGlzdCA8LSBjKHVzZXJfZmFpbGVkX2xpc3QsIHVzZXIpCiAgICB9ZWxzZXsKICAgICAgdXNlcl9kY19kZlt1c2VyX2RjX2RmJGNvbXBsZXhpdHlfcGVha3MgPCAwLjUsICJDQ1BfZGF0ZXMiXSA8LSBOQQogICAgICB9CiAgICAgIAogICAgICAjIG1ha2Ugc3VyZSB0aGUgb3V0Y29tZXNfbGlzdCBhbmQgdGhlIHVzZXIgb3V0Y29tZXMgZGF0YSBmcmFtZSBhcmUgc29ydGVkIHRoZSBzYW1lIHdheSBvdGhlcndpc2Ugc3R1ZmYgd2lsbCBub3QgbWF0Y2ggY29ycmVjdGx5CiAgICAgIGNsaW5fc3ViX2F1bWNfZGZfbSA8LSBjbGluX3N1Yl9hdW1jX2RmX21bb3JkZXIoY2xpbl9zdWJfYXVtY19kZl9tJFBST19ncm91cCwgY2xpbl9zdWJfYXVtY19kZl9tJFBST19uYW1lKSwgXQogICAgICBvdXRjb21lc19saXN0IDwtIHNvcnQob3V0Y29tZXNfbGlzdCkKICAgICAgY2xpbmljYWxseV9yZWxldmFudF9ib29sX2NvbCA8LSBjKCkKICAgICAgZm9yIChpIGluIDE6bGVuZ3RoKG91dGNvbWVzX2xpc3QpKSB7CiAgICAgICAgZGlmZl9saXN0IDwtIGMoKQogICAgICAgIGN1cnJlbnRfb3V0Y29tZV92ZWN0b3IgPC0gY2xpbl9zdWJfYXVtY19kZl9tW2dyZXAob3V0Y29tZXNfbGlzdFtpXSwgY2xpbl9zdWJfYXVtY19kZl9tJFBST19uYW1lKSwgIlBST192YWx1ZSJdCiAgICAgICAgZm9yIChqIGluIDI6IGxlbmd0aChjdXJyZW50X291dGNvbWVfdmVjdG9yKS0xKSB7CiAgICAgICAgICBkaWZmX2xpc3QgPC0gYXBwZW5kKGRpZmZfbGlzdCwgY3VycmVudF9vdXRjb21lX3ZlY3RvcltqICsgMV0gLSBjdXJyZW50X291dGNvbWVfdmVjdG9yW2pdKQogICAgICAgIH0KICAgICAgICAKICAgICAgICBpZiAob3V0Y29tZXNfbGlzdFtpXSAlaW4lIGMoIk5IUFRfTkRfYXZnIiwgIk5IUFRfRF9hdmciLCAiVFdUX2F2ZyIpKXsKICAgICAgICAgIGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbCA8LSBhYnMoZGlmZl9saXN0KSA+IGRpZmZfbGlzdFsxXSAqIG91dGNvbWVfcGx1c19jdXRvZmZzX2RmW291dGNvbWVfcGx1c19jdXRvZmZzX2RmJG91dGNvbWUgPT0gb3V0Y29tZXNfbGlzdFtpXSwgImN1dG9mZnMiXQogICAgICAgICAgCiAgICAgICAgICBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wgPC0gYyhGQUxTRSwgY2xpbmljYWxseV9yZWxldmFudF9ib29sKQogICAgICAgICAgZm9yIChqIGluIDI6IGxlbmd0aChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wpKSB7CiAgICAgICAgICAgIGlmIChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xbal0gJiAhaXMubmEoY2xpbmljYWxseV9yZWxldmFudF9ib29sW2pdKSkgewogICAgICAgICAgICAgIGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbFtqIC0gMV0gPC0gVFJVRQogICAgICAgICAgICB9CiAgICAgICAgICB9CiAgICAgICAgICBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xfY29sIDwtIGFwcGVuZChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xfY29sLCBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wpCiAgICAgICAgICAKICAgICAgfSBlbHNlewogICAgICAgICAgY2xpbmljYWxseV9yZWxldmFudF9ib29sIDwtIGFicyhkaWZmX2xpc3QpID49IG91dGNvbWVfcGx1c19jdXRvZmZzX2RmW291dGNvbWVfcGx1c19jdXRvZmZzX2RmJG91dGNvbWUgPT0gb3V0Y29tZXNfbGlzdFtpXSwgImN1dG9mZnMiXQogICAgICAgICAgCiAgICAgICAgICBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wgPC0gYyhGQUxTRSwgY2xpbmljYWxseV9yZWxldmFudF9ib29sKQogICAgICAgICAgZm9yIChqIGluIDI6IGxlbmd0aChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wpKSB7CiAgICAgICAgICAgIGlmIChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xbal0gJiAhaXMubmEoY2xpbmljYWxseV9yZWxldmFudF9ib29sW2pdKSkgewogICAgICAgICAgICAgIGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbFtqIC0gMV0gPC0gVFJVRQogICAgICAgICAgICB9CiAgICAgICAgICB9CiAgICAgICAgICBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xfY29sIDwtIGFwcGVuZChjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2xfY29sLCBjbGluaWNhbGx5X3JlbGV2YW50X2Jvb2wpCiAgICAgICAgfQogICAgICB9CiAgICAgIAogICAgICBjbGluX3N1Yl9hdW1jX2RmX20kY2xpbmljYWxseV9yZWxldmFudF9ib29sZWFuIDwtIGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbF9jb2wKICAgICAgCiAgICAgIGRhdGVfc2VsZWN0aW9uIDwtIHVuaXF1ZShjbGluX3N1Yl9hdW1jX2RmX21bYXMuY2hhcmFjdGVyKGNsaW5fc3ViX2F1bWNfZGZfbSR2aXNpdGRhdGVfY2F0KSAlaW4lIHF1ZXN0aW9ubmFyZV9kYXRlX2NvbHMsICJkYXRlIl0pCiAgICAgIHBsb3QgPC0gZ2dwbG90KGNsaW5fc3ViX2F1bWNfZGZfbSkgKwogICAgICAgIGdlb21fcG9pbnQoZGF0YT1jbGluX3N1Yl9hdW1jX2RmX21bY2xpbl9zdWJfYXVtY19kZl9tJGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbGVhbiwgXSwKICAgICAgICAgICAgICAgICAgIGFlcyh4PWRhdGUsIHk9UFJPX3ZhbHVlX3NjYWxlZCwgY29sb3I9UFJPX2dyb3VwLCBncm91cCA9IFBST19ncm91cCkpICsKICAgICAgICBnZW9tX2xpbmUoZGF0YT1jbGluX3N1Yl9hdW1jX2RmX21bY2xpbl9zdWJfYXVtY19kZl9tJGNsaW5pY2FsbHlfcmVsZXZhbnRfYm9vbGVhbiwgXSwKICAgICAgICAgICAgICAgICAgIGFlcyh4PWRhdGUsIHk9UFJPX3ZhbHVlX3NjYWxlZCwgY29sb3I9UFJPX2dyb3VwLCBncm91cCA9IFBST19ncm91cCMsIHNpemUgPSBQUk9fZ3JvdXAKICAgICAgICAgICAgICAgICAgICAgICApKSArCiAgICAgICAgZ2VvbV9wb2ludChkYXRhPW5hLm9taXQoY2xpbl9zdWJfYXVtY19kZl9tWyFjbGluX3N1Yl9hdW1jX2RmX20kY2xpbmljYWxseV9yZWxldmFudF9ib29sZWFuLCBdKSwKICAgICAgICAgICAgICAgICAgIGFlcyh4PWRhdGUsIHk9UFJPX3ZhbHVlX3NjYWxlZCwgY29sb3I9UFJPX2dyb3VwLCBncm91cCA9IFBST19ncm91cCksIGFscGhhPTAuMSkgKwogICAgICAgIGdlb21fbGluZShkYXRhPW5hLm9taXQoY2xpbl9zdWJfYXVtY19kZl9tWyFjbGluX3N1Yl9hdW1jX2RmX20kY2xpbmljYWxseV9yZWxldmFudF9ib29sZWFuLCBdKSwKICAgICAgICAgICAgICAgICAgIGFlcyh4PWRhdGUsIHk9UFJPX3ZhbHVlX3NjYWxlZCwgY29sb3I9UFJPX2dyb3VwLCBncm91cCA9IFBST19ncm91cCksIGFscGhhPTAuMSkgKwogICAgICAgIGdlb21fbGluZShkYXRhID0gdXNlcl9kY19kZiwgYWVzKGRhdGUsIGR5bmFtaWNfY29tcGxleGl0eV9zdW0pLCBzaXplPTAuNSkgKwogICAgICAgIGdlb21fcG9pbnQoZGF0YSA9IHVzZXJfZGNfZGYsIGFlcyhkYXRlLCBkeW5hbWljX2NvbXBsZXhpdHlfc3VtKSwgc2hhcGUgPSAxNiwgc2l6ZT0yKSArCiAgCiAgICAgICAgeGxpbShtaW4oYXVtY19kZl9tW2F1bWNfZGZfbSR1c2VyX2lkID09IHVzZXIsICJkYXRlIl0sIG5hLnJtID0gVFJVRSksCiAgICAgICAgbWF4KGF1bWNfZGZfbVthdW1jX2RmX20kdXNlcl9pZCA9PSB1c2VyLCAiZGF0ZSJdLCBuYS5ybSA9IFRSVUUpKSArCiAgICAgICAgeWxpbSgwLCBtYXgoZWxhc2Nlcih1c2VyX2RjX2RmW3VzZXJfZGNfZGYkdXNlcl9pZCA9PSB1c2VyLCAiUFJPX3ZhbHVlIl0pLCBuYS5ybSA9IFRSVUUpKSArCiAgICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gdXNlcl9kY19kZlt1c2VyX2RjX2RmJGNvbXBsZXhpdHlfcGVha3MgPiAwLCAiZGF0ZSJdLCBjb2w9InJlZCIsIGx0eT0yKSArCiAgICAgICAgc2NhbGVfeF9kYXRlKGJyZWFrcyA9IGRhdGVfc2VsZWN0aW9uLCAKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygibTAwIiwgIm0wMDIiLCAibTAzIiwgIm0wNiIsICJtMDkiLCAibTEyIilbMTpsZW5ndGgoZGF0ZV9zZWxlY3Rpb24pXSkgKwogIAogIAogICAgICAgIGxhYnMoeCA9ICJEYXRlIiwgeSA9ICJNZWFzdXJlIENoYW5nZSAoU2NhbGVkKSIpICsKICAgICAgICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lPSJPdXRjb21lIG1lYXN1cmUiKSArCiAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKCJPdXRjb21lIE1lYXN1cmVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IG5hbWVzKGN1c3RvbV9jbWFwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGN1c3RvbV9jbWFwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgKSArCiAgICAgICAgdGhlbWVfbWluaW1hbCgpICsKICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDYwLCBoanVzdCA9IDAuOSwgc2l6ZT0xMCksCiAgICAgICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogICAgICAgIGdndGl0bGUocGFzdGUoIkNoYW5nZSBpbiB0aW1lIHNlcmllcyBkYXRhIGZvciB1c2VyID09IiwgdXNlcikpCiAgICAgIAogICAgICBwcmludChwbG90KQogICAgICAKICB9Cn0KCmBgYAoKIyMjIyBTaG93IHRoZSBtaXNzaW5nbmVzcyB3aXRoaW4gZWFjaCB1c2VyIGFuZCBwZXIgZ3JvdXAKCmBgYHtyfQpmb3IgKHVzZXJzX2xpc3RfbnVtYmVyIGluIDE6bGVuZ3RoKGxpc3Rfb2ZfdXNlcl9pbmRleF9saXN0cykpIHsKICB1c2VyX2xpc3RfbmFtZTwtbmFtZXMobGlzdF9vZl91c2VyX2luZGV4X2xpc3RzW3VzZXJzX2xpc3RfbnVtYmVyXSkKICB1c2Vyc19zZWxlY3Rpb25faW5kZXg8LWxpc3Rfb2ZfdXNlcl9pbmRleF9saXN0c1tbdXNlcl9saXN0X25hbWVdXQogIAogIHByaW50KHVzZXJfbGlzdF9uYW1lKQogIAogICMgdXNlIHRoZSBjaG9zZW4gaW5kZXggdG8gZ2V0IGEgbGlzdCBvZiB1c2VycwogIHVzZXJfc2VsZWN0aW9uIDwtIGF1bWNfZGYkdXNlcl9pZFt1c2Vyc19zZWxlY3Rpb25faW5kZXhdCgogICMjIFNob3cgbWlzc2luZ25lc3MgcGVyY2VudGFnZXMKICBhbGxfbWlzc2luZ19wZXJfdXNlciA8LSBkYXRhLmZyYW1lKCkKICBmb3IgKHVzZXIgaW4gdXNlcl9zZWxlY3Rpb24pIHsKICAgIGN1cnJlbnRfdXNlcl9taXNzaW5nIDwtIGRhdGEuZnJhbWUodXNlcl9pZCA9IHVzZXIsIHQoY29sTWVhbnMoaXMubmEoa2V5X2RmW2tleV9kZiR1c2VyX2lkID09IHVzZXIsIGZlYXR1cmVfc2VsZWN0aW9uXSkpKjEwMCkpCiAgICBhbGxfbWlzc2luZ19wZXJfdXNlciA8LSByYmluZChhbGxfbWlzc2luZ19wZXJfdXNlciwgY3VycmVudF91c2VyX21pc3NpbmcpCiAgfQogIAogIHByaW50KCJBbGwgcGVyY2VudGFnZSBtaXNzaW5nbmVzcyBwZXIgdXNlciBwZXIgZmVhdHVyZSIpCiAgcHJpbnQoYWxsX21pc3NpbmdfcGVyX3VzZXIpCiAgCiAgcHJpbnQoIk1lZGlhbiBwZXJjZW50YWdlIG1pc3NpbmduZXNzIHBlciB1c2VyIikKICBtZWRpYW5fcGVyY2VudF9taXNzIDwtIGRhdGEuZnJhbWUodXNlcl9pZCA9IHVzZXJfc2VsZWN0aW9uLCBtZWRpYW5fcGVyY2VudGFnZV9taXNzaW5nID0gcm93TWVkaWFucyhhcy5tYXRyaXgoYWxsX21pc3NpbmdfcGVyX3VzZXJbLCBmZWF0dXJlX3NlbGVjdGlvbl0pKSkKICBwcmludChtZWRpYW5fcGVyY2VudF9taXNzKQogIAogIHByaW50KCJNZWRpYW4gb2YgdGhlIE1lZGlhbiBwZXJjZW50YWdlIG1pc3NpbmduZXNzIHBlciB1c2VyIikKICBwcmludChtZWRpYW4obWVkaWFuX3BlcmNlbnRfbWlzcyRtZWRpYW5fcGVyY2VudGFnZV9taXNzaW5nLCBuYS5ybT1UUlVFKSkKICBjYXQoIlxuIikKCn0KYGBgCgojIyMjIFBsb3QgdGhlIENSRCBhbmQgQ0NQIG9mIGFuIGV4YW1wbGUgdXNlcgoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgc3Vic2V0IHVzZXJzJyBrZXlzdHJva2UgZGF0YQprZXlfc3ViX2RmIDwtIGtleV9kZltrZXlfZGYkdXNlcl9pZCA9PSAzOTAsIF0KIyBvcmRlciBiYXNlZCBvbiB1c2VyIGlkIGFuZCB0aW1lc3RhbXAKa2V5X3N1Yl9kZiA8LSBrZXlfc3ViX2RmW29yZGVyKGtleV9zdWJfZGYkdXNlcl9pZCwga2V5X3N1Yl9kZiR0aW1lc3RhbXApLCBdCmRmIDwtIGRmX3VzZXJfbGlzdChkZiA9IGtleV9zdWJfZGYsIGZlYXR1cmVzID0gYygidXNlcl9pZCIsICJ0aW1lc3RhbXAiLCBmZWF0dXJlX3NlbGVjdGlvbiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICB1c2VyX2NvbHVtbl9uYW1lID0gInVzZXJfaWQiLCB1c2VycyA9IDM5MCkkSURfMzkwCgp1c2VyIDwtIGRmJHVzZXJfaWRbMV0KZGYgPC0gc3Vic2V0KGRmLCBzZWxlY3QgPSAtYyh1c2VyX2lkKSkKY2F0KCJDYWxjdWxhdGluZyBFYXJseSBXYXJuaW5nIFNpZ25hbHMgYW5kIEN1bXVsYXRpdmUgQ29tcGxleGl0eSBQZWFrcyBmb3IgdXNlciA9PSAiLCB1c2VyLCAiXG4iKQojIHJlbW92ZSBjb2x1bW5zIHdpdGggdG9vIG1hbnkgTkFzCmRmIDwtIGRmWywgY29sU3Vtcyhpcy5uYShkZikpIDwgbnJvdyhkZikvM10KdmFycyA8LSBjb2xuYW1lcyhkZilbMjpsZW5ndGgoY29sbmFtZXMoZGYpKV0KIyBJbXB1dGUgbWlzc2luZyB2YWx1ZXMgd2l0aCBDbGFzc2lmaWNhdGlvbiBBbmQgUmVncmVzc2lvbiBUcmVlcyAvIFJhbmRvbSBGb3Jlc3RzCiMgUkYgYW5kIENBUlQgcmV0dXJuIChpZGVudGljYWwpIGRpc2NyZXRlIG51bWJlcnMKaW1wLmNhcnQgIDwtIG1pY2U6Om1pY2UoZGZbdmFyc10sIG1ldGhvZCA9ICdjYXJ0JywgcmVtb3ZlLmNvbnN0YW50ID0gVFJVRSwgcmVtb3ZlLmNvbGxpbmVhciA9IFRSVUUsIHByaW50RmxhZyA9IEZBTFNFKQpkZl9pbXAgIDwtIG1pY2U6OmNvbXBsZXRlKGltcC5jYXJ0KQoKIyBwdXQgZWFjaCBjb2x1bW4gYmV0d2VlbiAwIGFuZCAxIHVzaW5nIGVsYXN0aWMgc2NhbGVyCmVsYXNjX2RmIDwtIGRhdGEuZnJhbWUoYXBwbHkoZGZfaW1wLCAyLCBlbGFzY2VyKSkKIyBkeW5hbWljIGNvbXBsZXhpdHkgb2YgdGhlIHZhcmlhYmxlcyB3aXRoIHRoZSBpbXB1dGVkIGRhdGEKd2luID0gMjgKZGMgPC0gZGNfd2luKGVsYXNjX2RmLCB3aW4gPSB3aW4sIHNjYWxlX21pbj0wLCBzY2FsZV9tYXg9MSwgZG9QbG90ID0gRkFMU0UsIGNvbE9yZGVyID0gTkEpCmRhdGVzSU1QIDwtIGRmJHRpbWVzdGFtcApjY3AuY2FzZUlNUCA8LSBkY19jY3AoZGZfd2luID0gZGMsIGFscGhhX2l0ZW0gPSAwLjAwMSwgYWxwaGFfdGltZSA9IDAuMDAxKQoKcGxvdF9wcmUgPSBUUlVFCgppZiAocGxvdF9wcmUgPT0gVFJVRSl7CiAgIyBQbG90IHRoZSBDb21wbGV4aXR5IFJlc29uYW5jZSBEaWFncmFtIFBsb3QKICBkYyA8LSBkcGx5cjo6c2VsZWN0KGRjLCBuYW1lcyhzb3J0KGNvbE1lYW5zKGRjLCBuYS5ybSA9IFRSVUUpKSkpCiAgcGxvdERDX3Jlc19qbXMoZGZfd2luID0gZGMsIHdpbiA9IHdpbiwgY29sT3JkZXIgPSBUUlVFLCAKICAgICAgICAgICAgIHVzZVRpbWVWZWN0b3IgPSBOQSwgI2RhdGVzSU1QLCAKICAgICAgICAgICAgIHRpbWVTdGFtcCA9ICIzMS0wMS05OSIsCiAgICAgICAgICAgICB0aXRsZSA9IHBhc3RlKCJDb21wbGV4aXR5IFJlc29uYW5jZSBEaWFncmFtIHVzZXIgPT0gIiwgdXNlcikpCiAgIyBQbG90IHRoZSBDdW11bGF0aXZlIENvbXBsZXhpdHkgUGVhayBQbG90CiAgcGxvdERDX2NjcF9qbXMoZGZfY2NwID0gY2NwLmNhc2VJTVBbLCBjKGNvbG5hbWVzKGRjKSwgInNpZy5wZWFrcyIpXSwgd2luID0gd2luLCBjb2xPcmRlciA9IFRSVUUsIAogICAgICAgICAgICAgdXNlVGltZVZlY3RvciA9IE5BLCAjZGF0ZXNJTVAsIAogICAgICAgICAgICAgdGltZVN0YW1wID0gIjMxLTAxLTk5IiwKICAgICAgICAgICAgIHRpdGxlID0gcGFzdGUoIkN1bXVsYXRpdmUgQ29tcGxleGl0eSBQZWFrIFBsb3QgdXNlciA9PSAiLCB1c2VyKSkKfQoKYGBgCg==