This is an R Markdown
Notebook. When you execute code within the notebook, the results appear
beneath the code.
Try executing this chunk by clicking the Run button within
the chunk or by placing your cursor inside it and pressing
Ctrl+Shift+Enter.
# /////////////------------ LIMS DATA FORMATTING ------------ /////////////////
# /////////////////
# specify function to standardise format of LIMS data for analysis
lims.stnd.format.fun <- function(x,
field_names = c("specimen number", ## specify all possible field names for each of the essential indicators
"crn (hospital) number","patient hospital number",
"nhs number","patient nhs number",
"forename","patient forename",
"surname","patient surname",
"date of birth","patient date of birth","patient dob",
"sex","patient sex",
"ethnic origin desc","ethnicity",
"post code","patient postcode",
"date of specimen","specimen date",
"specimen type desc","specimen type",
"organism desc","result text","organism comment","organism species name", ## field describing organism isolated, including GBS negative and no organism
"antibiotic desc","antimicrobial",
"result","susceptibility test result")){
names(x) <- tolower(gsub("\\.", "", names(x))) # ensure all field names lower case
x <- x %>% select(any_of(field_names)) # select essential indicators
names(x) <- dplyr::case_when((names(x) == "specimen number") ~ "specno", ## standardise field names for each of the essential indicators
(names(x) == "crn (hospital) number" | names(x) == "patient hospital number") ~ "hospno",
(names(x) == "nhs number" | names(x) == "patient nhs number") ~ "nhsno",
(names(x) == "forename" | names(x) == "patient forename") ~ "forename",
(names(x) == "surname" | names(x) == "patient surname") ~ "surname",
(names(x) == "date of birth" | names(x) == "patient date of birth" | names(x) == "patient dob") ~ "dob",
(names(x) == "sex" | names(x) == "patient sex") ~ "sex",
(names(x) == "ethnic origin desc" | names(x) == "ethnicity") ~ "ethnicity",
(names(x) == "post code" | names(x) == "patient postcode") ~ "patient_postcode",
(names(x) == "date of specimen" | names(x) == "specimen date") ~ "specdate",
(names(x) == "specimen type desc" | names(x) == "specimen type") ~ "spectype",
(names(x) == "organism desc" | names(x) == "result text" | names(x) == "organism comment" | names(x) == "organism species name") ~ "species",
(names(x) == "antibiotic desc" | names(x) == "antimicrobial") ~ "abx",
(names(x) == "result" | names(x) == "susceptibility test result") ~ "ast_result")
x <- x %>%
mutate(species=case_when(
grepl("Beta Haemolytic Streptococcus Group B",species) |
grepl("^Streptococcus agalactiae",species) |
species == "Streptococcus Group B" |
species == "S.agalactiae(Group B strep)" |
species == "Group B Haemolytic Strep" |
species == "Strep.agalactiae (Group B)" |
species == "Group B Streptococcus isolated" |
species == "Group B Streptococcus ISOLATED" ~ "Group B Streptococcus",
species == "Group B Strep NOT isolated" |
species== "Streptococcus group B NOT isolated" |
species=="Group B Streptococcus Not isolated" |
species== "No organism detected" |
species=="Negative after 1 day" |
species=="Organism Species Name" |
grepl("^Candida",species) ~ "GBS not isolated",
TRUE ~ species),
ethnicity=case_when(
grepl("xa0Ethnic", ethnicity) ~ "Any other ethnic group",
TRUE ~ ethnicity)
)
x <- x[, !duplicated(colnames(x))] ## remove duplicate cols
# add in antimicrobial cols if not already in data (for GBS neg)
if(!"abx" %in% colnames(x)){
x$abx <- NA
}
if(!"ast_result" %in% colnames(x)){
x$ast_result <- NA
}
x <- x %>% mutate(abx = case_when(
abx=="" ~ NA,
TRUE ~ abx),
ast_result = case_when(
ast_result=="" ~ NA,
TRUE ~ ast_result)) %>%
mutate(ast_result=toupper(ast_result),
module="LIMS")
## converting specimen date and dob to correct date format
x <- x %>%
mutate(
dob = case_when(
inherits(dob, "POSIXct") ~ as.Date(dob), # POSIXct to Date
inherits(dob, "IDate") ~ as.Date(as.character(dob)), # IDate to Date
TRUE ~ as.Date(dob, format = "%d/%m/%Y") # Character string "DD/MM/YYYY" to Date
)
) %>%
mutate(
specdate = case_when(
inherits(specdate, "POSIXct") ~ as.Date(specdate), # POSIXct to Date
inherits(specdate, "IDate") ~ as.Date(as.character(specdate)), # IDate to Date
TRUE ~ as.Date(specdate, format = "%d/%m/%Y") # Character string "DD/MM/YYYY" to Date
)
) ## converting specimen date to correct date format
return(x) # function output
}
# specify species column formatting function
species_format_fun <- function(x){
x <- x %>%
mutate(species=case_when(
grepl("Beta Haemolytic Streptococcus Group B",species) |
grepl("^Streptococcus agalactiae",species) |
species == "Streptococcus Group B" |
species == "S.agalactiae(Group B strep)" |
species == "Group B Haemolytic Strep" |
species == "Strep.agalactiae (Group B)" |
species == "Group B Streptococcus isolated" |
species == "Group B Streptococcus ISOLATED" |
species== "beta Haemolytic Strep, group B" ~ "Group B Streptococcus",
species == "Group B Strep NOT isolated" |
species== "Streptococcus group B NOT isolated" |
species=="Group B Streptococcus Not isolated" |
species== "No organism detected" |
species=="No organism recorded" |
species=="Negative after 1 day" |
species=="Organism Species Name" |
grepl("^Candida",species) ~ "GBS not isolated",
TRUE ~ species)
)
}
# /////////////------------ GBS CARRIAGE ESTIMATION ------------ /////////////////
# /////////////////
# specify function to reshape and calculate GBS carriage (% total patients in sampling timeframe)
gbs.carr.byethn.fun <- function(x){
outdf <- x %>% mutate(z=1)
# %>% filter(hes_ethnicity!="Unknown" & hes_ethnicity!="Not linked" & hes_ethnicity!="_fromLIMS")
ethn_summary <- outdf %>% group_by(hes_ethnicity) %>% summarize(total=sum(z)) %>% mutate(pct=total/sum(total)*100)
# GBS pos by ethnicity
gbspos_byethn <- outdf %>% group_by(hes_ethnicity) %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # GBS pos by ethnicity
# total ECM by ethnicity
total_byeth <- outdf %>% group_by(hes_ethnicity) %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM by ethnicity
# carriage by ethnicity
carriage_byethn <- merge(gbspos_byethn,total_byeth,by="hes_ethnicity") %>% mutate(carriage_bythn=gbspos/totalecm*100) # carriage estimate
# all GBS pos
gbspos_all <- outdf %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # all GBS pos
# total
total <- outdf %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM
# carriage
gbs_carriage <- gbspos_all/total*100 # carriage estimate
gbs_carriage <- gbs_carriage %>% rename("total_carriage" = gbspos)
carriage_all <- gbspos_all/length(unique(outdf$nhsno))*100
carriage_summary <- cbind(carriage_byethn,gbs_carriage)
carriage_summary <- merge(ethn_summary,carriage_summary, by="hes_ethnicity")
carriage_summary <- carriage_summary %>%
rowwise() %>%
mutate(
conf = list(binom.confint(gbspos, totalecm, methods = "exact")),
lower_ci = conf[[1, "lower"]] * 100,
upper_ci = conf[[1, "upper"]] * 100
) %>%
select(-conf) %>%
ungroup()
return(carriage_summary)
}
# /////////////////
# specify function to reshape and calculate GBS carriage by site and ethnicity (% total patients in sampling timeframe)
gbs.carr.bysite.byethn.fun <- function(x){
outdf <- x %>% mutate(z=1)
# %>% filter(hes_ethnicity!="Unknown" & hes_ethnicity!="Not linked" & hes_ethnicity!="_fromLIMS")
ethn_summary <- outdf %>% group_by(site,hes_ethnicity) %>% summarize(total=sum(z)) %>% mutate(pct=total/sum(total)*100)
# GBS pos by ethnicity
gbspos_byethn <- outdf %>% group_by(site,hes_ethnicity) %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # GBS pos by ethnicity
# total ECM by ethnicity
total_byeth <- outdf %>% group_by(site,hes_ethnicity) %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM by ethnicity
# carriage by ethnicity
carriage_byethn <- merge(gbspos_byethn,total_byeth,by= c("site","hes_ethnicity")) %>% mutate(carriage_bythn=gbspos/totalecm*100) # carriage estimate
# all GBS pos
gbspos_all <- outdf %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # all GBS pos
# total
total <- outdf %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM
# carriage
gbs_carriage <- gbspos_all/total*100 # carriage estimate
gbs_carriage <- gbs_carriage %>% rename("total_carriage" = gbspos)
carriage_all <- gbspos_all/length(unique(outdf$nhsno))*100
carriage_summary <- cbind(carriage_byethn,gbs_carriage)
carriage_summary <- merge(ethn_summary,carriage_summary, by=c("site","hes_ethnicity"))
carriage_summary <- carriage_summary %>%
rowwise() %>%
mutate(
conf = list(binom.confint(gbspos, totalecm, methods = "exact")),
lower_ci = conf[[1, "lower"]] * 100,
upper_ci = conf[[1, "upper"]] * 100
) %>%
select(-conf) %>%
ungroup()
return(carriage_summary)
}
# /////////////////
# specify function to reshape and calculate GBS carriage (% total patients in sampling timeframe) among Asian ethnic subgroups
# Indian, Pakistani and Bangladeshi.
gbs.carr.bysubethn.fun <- function(x){
outdf <- x %>% mutate(z=1) %>% filter(Ethnic_group=="Indian (Asian or Asian British)" |
Ethnic_group=="Bangladeshi (Asian or Asian British)" |
Ethnic_group=="Pakistani (Asian or Asian British)")
ethn_summary <- outdf %>% group_by(Ethnic_group) %>% summarize(total=sum(z)) %>% mutate(pct=total/sum(total)*100)
# GBS pos by ethnicity
gbspos_byethn <- outdf %>% group_by(Ethnic_group) %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # GBS pos by ethnicity
# total ECM by ethnicity
total_byeth <- outdf %>% group_by(Ethnic_group) %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM by ethnicity
# carriage by ethnicity
carriage_byethn <- merge(gbspos_byethn,total_byeth,by="Ethnic_group") %>% mutate(carriage_bythn=gbspos/totalecm*100) # carriage estimate
# all GBS pos
gbspos_all <- outdf %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # all GBS pos
# total
total <- outdf %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM
# carriage
gbs_carriage <- gbspos_all/total*100 # carriage estimate
gbs_carriage <- gbs_carriage %>% rename("total_carriage" = gbspos)
carriage_all <- gbspos_all/length(unique(outdf$nhsno))*100
carriage_summary <- cbind(carriage_byethn,gbs_carriage)
carriage_summary <- merge(ethn_summary,carriage_summary, by="Ethnic_group")
# %>% mutate(across(where(is.numeric), round, 2))
return(carriage_summary)
}
# /////////////////
# specify function to reshape and calculate GBS carriage (% total patients in sampling timeframe) for all 16 ethnicity categories in HES
gbs.carr.byallsubethn.fun <- function(x){
outdf <- x %>% mutate(z=1)
# %>% filter(Ethnic_group!="NA" & Ethnic_group!="99 Not known" & Ethnic_group!="Z Not stated")
ethn_summary <- outdf %>% group_by(Ethnic_group) %>% summarize(total=sum(z)) %>% mutate(pct=total/sum(total)*100)
# GBS pos by ethnicity
gbspos_byethn <- outdf %>% group_by(Ethnic_group) %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # GBS pos by ethnicity
# total ECM by ethnicity
total_byeth <- outdf %>% group_by(Ethnic_group) %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM by ethnicity
# carriage by ethnicity
carriage_byethn <- merge(gbspos_byethn,total_byeth,by="Ethnic_group") %>% mutate(carriage_bythn=gbspos/totalecm*100) # carriage estimate
# all GBS pos
gbspos_all <- outdf %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # all GBS pos
# total
total <- outdf %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM
# carriage
gbs_carriage <- gbspos_all/total*100 # carriage estimate
gbs_carriage <- gbs_carriage %>% rename("total_carriage" = gbspos)
carriage_all <- gbspos_all/length(unique(outdf$nhsno))*100
carriage_summary <- cbind(carriage_byethn,gbs_carriage)
carriage_summary <- merge(ethn_summary,carriage_summary, by="Ethnic_group")
# %>% mutate(across(where(is.numeric), round, 2))
return(carriage_summary)
}
# /////////////////
# specify function to reshape and calculate GBS carriage by IMD (% total patients in sampling timeframe)
gbs.carr.byimd.fun <- function(x){
outdf <- x %>% mutate(z=1)
# pivot_wider( # currently pivots wider as part of obai process
# names_from = abx,
# values_from = ast_result)
imd_summary <- outdf %>% group_by(imd_quintile) %>% summarize(total=sum(z)) %>% mutate(pct=total/sum(total)*100)
# GBS pos by IMD quintile
gbspos_byimd <- outdf %>% group_by(imd_quintile) %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # GBS pos by IMD
# total ECM by IMD quintile
total_byimd <- outdf %>% group_by(imd_quintile) %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM by IMD
# carriage by ethnicity
carriage_byimd <- merge(gbspos_byimd,total_byimd,by="imd_quintile") %>% mutate(carriage_byimd=gbspos/totalecm*100) # carriage estimate
# all GBS pos
gbspos_all <- outdf %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # all GBS pos
# total
total <- outdf %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM
# carriage
gbs_carriage <- gbspos_all/total*100 # carriage estimate
gbs_carriage <- gbs_carriage %>% rename("total_carriage" = gbspos)
carriage_all <- gbspos_all/length(unique(outdf$nhsno))*100
carriage_summary <- cbind(carriage_byimd,gbs_carriage)
carriage_summary <- merge(imd_summary,carriage_summary, by="imd_quintile") %>% mutate(imd_quintile = case_when(
is.na(imd_quintile) ~ "Not linked",
TRUE ~ as.character(imd_quintile)))
carriage_summary <- carriage_summary %>%
rowwise() %>%
mutate(
conf = list(binom.confint(gbspos, totalecm, methods = "exact")),
lower_ci = conf[[1, "lower"]] * 100,
upper_ci = conf[[1, "upper"]] * 100
) %>%
select(-conf) %>%
ungroup()
return(carriage_summary)
}
# /////////////////
# specify function to reshape and calculate GBS carriage (% total patients in sampling timeframe)
gbs.carr.byage.fun <- function(x){
outdf <- x %>% mutate(z=1,
agegroup = cut(age,
breaks = seq(0, 100, by = 5), # Specify the breaks for the age bands
right = FALSE, # Include the left endpoint, exclude the right
labels = paste(seq(0, 95, by = 5), seq(4, 99, by = 5), sep = "-")),
agegroup = case_when(
agegroup %in% c("40-44","45-49") ~ "40-49",
TRUE ~ agegroup
))
# pivot_wider( # currently pivots wider as part of obai process
# names_from = abx,
# values_from = ast_result)
age_summary <- outdf %>% group_by(agegroup) %>% summarize(total=sum(z)) %>% mutate(pct=total/sum(total)*100)
# GBS pos by ethnicity
gbspos_byage <- outdf %>% group_by(agegroup) %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # GBS pos by ethnicity
# total ECM by ethnicity
total_byage <- outdf %>% group_by(agegroup) %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM by ethnicity
# carriage by ethnicity
carriage_byage <- merge(gbspos_byage,total_byage,by="agegroup") %>% mutate(carriage_byage=gbspos/totalecm*100) # carriage estimate
# all GBS pos
gbspos_all <- outdf %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # all GBS pos
# total
total <- outdf %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM
# carriage
gbs_carriage <- gbspos_all/total*100 # carriage estimate
gbs_carriage <- gbs_carriage %>% rename("total_carriage" = gbspos)
carriage_all <- gbspos_all/length(unique(outdf$nhsno))*100
carriage_summary <- cbind(carriage_byage,gbs_carriage)
carriage_summary <- merge(age_summary,carriage_summary, by="agegroup")
carriage_summary <- carriage_summary %>%
rowwise() %>%
mutate(
conf = list(binom.confint(gbspos, totalecm, methods = "exact")),
lower_ci = conf[[1, "lower"]] * 100,
upper_ci = conf[[1, "upper"]] * 100
) %>%
select(-conf) %>%
ungroup()
return(carriage_summary)
}
# /////////////////
# specify function to reshape and calculate GBS carriage (% total patients in sampling timeframe)
gbs.carr.bysite.fun <- function(x){
outdf <- x %>% mutate(z=1)
# pivot_wider( # currently pivots wider as part of obai process
# names_from = abx,
# values_from = ast_result)
site_summary <- outdf %>% group_by(site) %>% summarize(total=sum(z)) %>% mutate(pct=total/sum(total)*100)
# GBS pos by ethnicity
gbspos_bysite <- outdf %>% group_by(site) %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # GBS pos by ethnicity
# total ECM by ethnicity
total_bysite <- outdf %>% group_by(site) %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM by ethnicity
# carriage by ethnicity
carriage_bysite <- merge(gbspos_bysite,total_bysite,by="site") %>% mutate(carriage_bysite=gbspos/totalecm*100) # carriage estimate
# all GBS pos
gbspos_all <- outdf %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # all GBS pos
# total
total <- outdf %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM
# carriage
gbs_carriage <- gbspos_all/total*100 # carriage estimate
gbs_carriage <- gbs_carriage %>% rename("total_carriage" = gbspos)
carriage_all <- gbspos_all/length(unique(outdf$nhsno))*100
carriage_summary <- cbind(carriage_bysite,gbs_carriage)
carriage_summary <- merge(site_summary,carriage_summary, by="site")
carriage_summary <- carriage_summary %>%
rowwise() %>%
mutate(
conf = list(binom.confint(gbspos, totalecm, methods = "exact")),
lower_ci = conf[[1, "lower"]] * 100,
upper_ci = conf[[1, "upper"]] * 100
) %>%
select(-conf) %>%
ungroup()
return(carriage_summary)
}
# /////////////////
# specify function to reshape and calculate GBS carriage (% total patients) by year
gbs.carr.byyear.fun <- function(x){
outdf <- x %>% mutate(z=1)
ethn_summary <- outdf %>% group_by(year) %>% summarize(total=sum(z)) %>% mutate(pct=total/sum(total)*100)
# GBS pos by ethnicity
gbspos_byyear <- outdf %>% group_by(year) %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # GBS pos by ethnicity
# total ECM by ethnicity
total_byyear <- outdf %>% group_by(year) %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM by ethnicity
# carriage by ethnicity
carriage_byyear <- merge(gbspos_byyear,total_byyear,by="year") %>% mutate(carriage_byyear=gbspos/totalecm*100) # carriage estimate
# all GBS pos
gbspos_all <- outdf %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # all GBS pos
# total
total <- outdf %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM
# carriage
gbs_carriage <- gbspos_all/total*100 # carriage estimate
gbs_carriage <- gbs_carriage %>% rename("total_carriage" = gbspos)
carriage_all <- gbspos_all/length(unique(outdf$nhsno))*100
carriage_summary <- cbind(carriage_byyear,gbs_carriage)
carriage_summary <- merge(ethn_summary,carriage_summary, by="year")
# %>% mutate(across(where(is.numeric), round, 2))
return(carriage_summary)
}
# /////////////////
# specify function to reshape and calculate GBS carriage (% total patients) by month
gbs.carr.byym.fun <- function(x){
outdf <- x %>% mutate(z=1)
ethn_summary <- outdf %>% group_by(ym) %>% summarize(total=sum(z)) %>% mutate(pct=total/sum(total)*100)
# GBS pos by ethnicity
gbspos_byym <- outdf %>% group_by(ym) %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # GBS pos by ethnicity
# total ECM by ethnicity
total_byym <- outdf %>% group_by(ym) %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM by ethnicity
# carriage by ethnicity
carriage_byym <- merge(gbspos_byym,total_byym,by="ym") %>% mutate(carriage_byym=gbspos/totalecm*100) # carriage estimate
# all GBS pos
gbspos_all <- outdf %>% mutate(gbspos=ifelse(species=="Group B Streptococcus",1,0)) %>% summarize(gbspos=sum(gbspos)) # all GBS pos
# total
total <- outdf %>% mutate(total=1) %>% summarize(totalecm=sum(total)) # total ECM
# carriage
gbs_carriage <- gbspos_all/total*100 # carriage estimate
gbs_carriage <- gbs_carriage %>% rename("total_carriage" = gbspos)
carriage_all <- gbspos_all/length(unique(outdf$nhsno))*100
carriage_summary <- cbind(carriage_byym,gbs_carriage)
carriage_summary <- merge(ethn_summary,carriage_summary, by="ym")
# %>% mutate(across(where(is.numeric), round, 2))
return(carriage_summary)
}
# /////////////------------ LIMS LINELIST PROCESSING AND AMR RESULT GENERATION ------------ /////////////////
# /////////////////
# specify umbrella QC function
qc.umbrella.fun <- function(x,summary=list()){
x$sn <- seq_len(nrow(x))
x$qc <- sgss.qc.fun(x$hospno, x$specno, x$forename, x$surname)
x$qc[which(!x$qc %in% c(0, NA))] <- 1
summary$qc_samples <- table(x$qc, x$module)
cat(paste0("Removed ",sum(x$qc) , " quality control samples\n\n"))
x <- x[x$qc==0,]
}
# /////////////////
# specify function to QC data (from obai::sgss_qc_records fun)
sgss.qc.fun <- function (hospno, specno, forename, surname)
{
hospno <- ifelse(is.na(hospno), "", toupper(as.character(hospno)))
specno <- ifelse(is.na(specno), "", toupper(as.character(specno)))
forename <- ifelse(is.na(forename), "", toupper(as.character(forename)))
surname <- ifelse(is.na(surname), "", toupper(as.character(surname)))
hospno <- stringr::str_trim(hospno)
hospno <- stringr::str_replace_all(hospno, "^0*0", "")
qc <- ifelse(stringr::str_detect(hospno, "SENS|NEQAS|NEQSA|^QC|QUALITY|IQA|DIST|SPEC|^TBT|LIQAS|TEST-1") |
stringr::str_detect(specno, "SENS|NEQAS|NEQSA|QC|QUALITY|^IQA|^DIST|SPEC") |
stringr::str_detect(surname, "^SENS|NEQAS|NEQSA|QC|IQA|QUALITY|ANTIMICROBIAL SUSCEPTABILITY|ANTIFUNGAL SUSCEPTIBILITY|INTERNAL QC|FEBUARY|IQAMIC2024") |
stringr::str_detect(forename, "^SENS|NEQAS|NEQSA|IQA|FAECES|QUALITY|ANTIMICROBIAL SUSCEPTABILITY|ANTIFUNGAL SUSCEPTIBILITY|ANTIMICROBIAL|BLOOD CULTURE|DIAGNOSTICS"),
1, 0)
qc <- ifelse(forename %in% toupper(month.name),1,0)
return(qc)
}
# /////////////////
# specify customised obai sgss_infections function for LIMS data deduplication and AMR results generation
obai.dedup.fun <- function(allsites,
linkage_strategy = "default",
episode_length=274, # equivalent of 9 months to capture second pregnancies within time frame of ECM testing at a given site
recurrence_length = episode_length,
episode_type = "fixed",
dedup_module_preference = NULL,
abx=TRUE,
abx_preference = "severity_all"){
allsites$mrk <- diyar::combi(
allsites$species,
allsites$nhsno,
allsites$hospno,
allsites$dob,
allsites$specdate,
allsites$sex,
allsites$site,
allsites$surname,
allsites$forename,
allsites$specno)
dedup_level = c("pids", "species")
abx=TRUE
if("LIMS" %in% dedup_level) allsites$mrk <- diyar::combi(allsites$mrk, allsites$module)
if("module" %in% dedup_level) allsites$mrk <- diyar::combi(allsites$mrk, allsites$module)
if(!isFALSE(abx)) allsites$mrk <- diyar::combi(allsites$mrk, allsites$abx, allsites$ast_result)
summary=list()
same_day_dups <- duplicated(allsites$mrk)
summary$same_day_dups <- length(same_day_dups[same_day_dups])
allsites <- allsites[!same_day_dups,]
allsites$mrk <- NULL
cat(summary$same_day_dups,"same-day depulicates removed\n\n")
cat("Creating a patient identifier: `",
linkage_strategy, "` method\n")
# Make configurable
# Generating a patient identifier
wr_nhs <- lapply(0:9, function(x){
paste0(rep(x, 10), collapse = "")
})
df <- allsites
if(linkage_strategy == "default"){
df$soundex <- stringdist::phonetic(df$surname)
# Inclusion criteria
wr_nhs <- as.character(wr_nhs)
df$cri_1 <- df$cri_2 <- df$cri_3 <- NA
lgk <- which(!(df$nhsno %in% c(".","",wr_nhs,"0","1234567890","9876543210",NA) | nchar(df$nhsno) != 10))
# CR1 - Valid NHS no
df$cri_1[lgk] <- df$nhsno[lgk]
lgk <- which(!(df$hosno %in% c("","NO PATIENT ID","UNKNOWN","NO REF GIVEN", "nk", NA) | df$soundex %in% c("", NA)))
# CR2 - Valid HOS no and surname Soundex
df$cri_2[lgk] <- diyar::combi(df$hospno[lgk], df$soundex[lgk])
lgk <- which(!(df$specno %in% c("","NO PATIENT ID","UNKNOWN","NO REF GIVEN", "nk", NA) | df$soundex %in% c("", NA) | df$sex %in% c("U","Unknown", NA) | df$dob %in% c("", NA)))
# CR3 - Valid Specimen number, Soundex NHS and sex
df$cri_3[lgk] <- diyar::combi(df$specno[lgk], df$soundex[lgk], df$sex[lgk])
# AND (Valid DDMM or DDYY or MMYY)
df$cri_pt1 <- df$cri_pt2 <- df$cri_pt3 <- NA
lgk <- which(!(df$dob %in% c("", NA)))
df$cri_pt1[lgk] = diyar::combi(substr(df$dob[lgk],1,4), substr(df$dob[lgk],6,7))
df$cri_pt2[lgk] = diyar::combi(substr(df$dob[lgk],1,4), substr(df$dob[lgk],9,10))
df$cri_pt3[lgk] = diyar::combi(substr(df$dob[lgk],9,10), substr(df$dob[lgk],6,7))
linked_s_criteria <- list(cr3 = diyar::sub_criteria(df$cri_pt1, df$cri_pt2, df$cri_pt3))
# Order; CR1 to CR3
criteria <- list(df$cri_1, df$cri_2, df$cri_3)
df$soundex <- NULL
}else if(linkage_strategy == "uid_gold"){
# Inclusion criteria
wr_nhs <- as.character(wr_nhs)
df$cri_1 <- df$cri_2 <- df$cri_3 <- NA
lgk <- which(!(df$nhsno %in% c(".","",wr_nhs,"0","1234567890","9876543210",NA) |
df$hosno %in% c("","NO PATIENT ID","UNKNOWN","NO REF GIVEN", "nk", NA) |
df$dob %in% c("", "1900-01-01", NA) |
df$surname %in% c("","NO PATIENT ID","UNKNOWN","NO REF GIVEN", "nk", NA) |
df$hosno %in% c("","NO PATIENT ID","UNKNOWN","NO REF GIVEN", "nk", NA)))
# CR1 - Valid NHS no, DOB, forename and surname
df$cri_1[lgk] <- diyar::combi(df$nhsno[lgk], df$dob[lgk], df$forename[lgk], df$surname[lgk])
lgk <- which(!(df$nhsno %in% c(".","",wr_nhs,"0","1234567890","9876543210",NA) |
df$dob %in% c("", "1900-01-01", NA) |
df$sex %in% c("","NO PATIENT ID","UNKNOWN","NO REF GIVEN", "nk", "U","Unknown", NA)))
# CR2 - Valid NHS no, DOB and sex
df$cri_2[lgk] <- diyar::combi(df$nhsno[lgk], df$dob[lgk], df$sex[lgk])
lgk <- which(!(df$nhsno %in% c(".","",wr_nhs,"0","1234567890","9876543210",NA) |
df$dob %in% c("", "1900-01-01", NA)))
# CR3 - Valid NHS no and DOB
df$cri_3[lgk] <- diyar::combi(df$nhsno[lgk], df$dob[lgk])
# Order; CR1 to CR3
criteria <- list(df$cri_1, df$cri_2, df$cri_3)
linked_s_criteria <- NULL
}else if(linkage_strategy == "basic"){
# Inclusion criteria
wr_nhs <- as.character(wr_nhs)
df$cri_1 <- NA
lgk <- which(!(df$nhsno %in% c(".","",wr_nhs,"0","1234567890","9876543210",NA) |
df$dob %in% c("", "1900-01-01", NA)))
# CR1 - Valid NHS no and DOB
df$cri_1[lgk] <- diyar::combi(df$nhsno[lgk], df$dob[lgk])
# Order; CR1
criteria <- list(df$cri_1)
linked_s_criteria <- NULL
}else if(linkage_strategy == "sgss_audit"){
# 9-stage ordered record linkage to identify patients
# Place holders for 9 matching criteria and attributes for sub_criteria
df$cri_1 <- df$cri_2 <- df$cri_3 <-
df$cri_4 <- df$cri_5 <- df$cri_6 <-
df$cri_7 <- df$cri_8 <- df$cri_9 <-
df$cri_dob_pt1 <- df$cri_dob_pt2 <- df$cri_dob_pt3 <- NA
# Place holders for inclusion criteria of each attribute
v_specno <- v_fname <- v_sname <- v_nhs <- v_dob <- v_hos <- v_sex <- rep(FALSE, nrow(df))
wr_nhs <- lapply(0:9, function(x){
paste0(rep(x,10), collapse = "")
})
df$soundex <- stringdist::phonetic(df$surname)
df$forename_initial <- toupper(substr(df$forename,1, 1))
v_nhs[!df$nhsno %in% c(".","","9999999999","0","123456789","9876543210", NA, wr_nhs)] <- TRUE
v_dob[!df$dob %in% c("1900-01-01","", NA)] <- TRUE
v_hos[!toupper(df$hosno) %in% c("UNKNOWN", "NO PATIENT ID", "","NOT SPECIFIED", NA)] <- TRUE
v_sex[!toupper(df$sex) %in% c("UNKNOWN","","NOT SPECIFIED", NA)] <- TRUE
v_sname[!toupper(df$surname) %in% c("", NA)] <- TRUE
v_fname[!toupper(df$forename) %in% c("", NA)] <- TRUE
v_specno[!toupper(df$specno) %in% c("UNKNOWN", "NO PATIENT ID", "","NOT SPECIFIED", NA)] <- TRUE
# Stage 1
lgk <- which(v_nhs & v_dob)
df$cri_1[lgk] <- diyar::combi(df$nhsno[lgk], df$dob[lgk])
# Stage 2
lgk <- which(v_nhs)
df$cri_2[lgk] <- df$nhsno[lgk]
# Stage 3
lgk <- which(v_hos & v_dob & v_sname)
df$cri_3[lgk] <- diyar::combi(df$hosno[lgk],df$dob[lgk], df$soundex[lgk])
# Stage 4
lgk <- which(v_hos)
df$cri_4[lgk] <- df$hosno[lgk]
# Stage 5
lgk <- which(v_specno & v_sex & v_dob & v_fname & v_sname)
df$cri_5[lgk] <- diyar::combi(df$specno[lgk], df$lab_cd[lgk], df$sex[lgk],
df$forename_initial[lgk], df$dob[lgk], df$soundex[lgk])
# Stage 6
lgk <- which(v_specno & v_sex)
df$cri_6[lgk] <- diyar::combi(df$specno[lgk], df$lab_cd[lgk], df$sex[lgk])
# Stage 7
lgk <- which(v_specno & v_dob)
df$cri_7[lgk] <- diyar::combi(df$specno[lgk], df$dob[lgk])
# Stage 8
# df$cri_8 <- df$cri_7
# Stage 9
lgk <- which(v_specno)
df$cri_9[lgk] <- df$specno[lgk]
lgk <- which(v_dob)
df$cri_dob_pt1[lgk] <- diyar::combi(lubridate::year(df$dob[lgk]), lubridate::month(df$dob[lgk]))
df$cri_dob_pt2[lgk] <- diyar::combi(lubridate::year(df$dob[lgk]), lubridate::day(df$dob[lgk]))
df$cri_dob_pt3[lgk] <- diyar::combi(lubridate::day(df$dob[lgk]), lubridate::month(df$dob[lgk]))
# Sub criteria for stage 6. Also used in stage 9
s_cri6 <- diyar::sub_criteria(df$forename_initial, df$soundex)
# Sub criteria for stage 9
s_cri9 <- diyar::sub_criteria(
s_cri6,
diyar::sub_criteria(df$cri_dob_pt1, df$cri_dob_pt2, df$cri_dob_pt3),
operator = "or")
# Order; CR1 to CR9
criteria <- list(df$cri_1, df$cri_2, df$cri_3, df$cri_4,
df$cri_5, df$cri_6, df$cri_7,
# df$cri_8,
df$cri_9)
linked_s_criteria <- list("cr6" = s_cri6, "cr9" = s_cri9)
df$soundex <- NULL
}
df <- df[names(df)[!grepl("cri_", names(df))]]
# Unlinked records are considered separate entities.
# This is an issue when querying AMR data as each unlinked test is considered a separate patient.
# To prevent this, the closest approximation to a patient's sample is used as the final criteria
criteria <- c(criteria,
list(diyar::combi(df$nhsno, df$hospno, df$dob, df$surname, df$forename, df$sex, df$specno))
)
df$pids <- diyar::links(criteria = criteria,
sub_criteria = linked_s_criteria,
display = "progress")
cat(paste0("Categorising episode: ",
episode_length,"-day difference ",
episode_type, " episodes\n"))
df$dt <- lubridate::ymd(df$specdate)
df$pid_str <- diyar::combi(as.list(df[dedup_level]))
if(!is.null(dedup_module_preference)){
mdls <- c("amr", "cdr")
dedup_module_preference <- factor(df$sgss_module, levels = c(mdls[mdls == dedup_module_preference],
mdls[mdls != dedup_module_preference]))
}
df$epids <- diyar::episodes(date = df$dt,
case_length = episode_length,
recurrence_length = recurrence_length,
episode_type = episode_type,
strata = df$pid_str,
group_stats = TRUE,
display = "progress",
custom_sort = dedup_module_preference)
df$dt <- df$qc <- NULL
# Most severe abx result per episode
# convert instances of "P" and "W" to "R" in ast_result field for indicible clindamycin resistance test results
df <- df %>% mutate(ast_result=case_when(
ast_result == "P" | ast_result == "W" ~ "R",
TRUE ~ ast_result
))
if(!isFALSE(abx)){
cat("Determining the most severe abx result per episode\n")
df$amr_severity <- dplyr::case_when(
df$ast_result == "R" ~ 1,
df$ast_result == "I" ~ 2,
df$ast_result == "S" ~ 3,
df$ast_result == "N" ~ 4,
TRUE ~ 5
)
if(c("severity_all") %in% abx_preference){
df$epi_amr <- jurithy::bys_val(df$amr_severity, df$sn, by= paste(as.numeric(df$epids), df$abx), val=df$amr_severity)
}else if (c("severity_cdr") %in% abx_preference){
tp_md <- factor(tolower(df$sgss_module), levels = c("cdr", "amr"))
df$epi_amr <- jurithy::bys_val(tp_md, df$amr_severity, df$sn, by= paste(as.numeric(df$epids), df$abx), val=df$amr_severity)
rm(tp_md)
}else if (c("severity_amr") %in% abx_preference){
tp_md <- factor(tolower(df$sgss_module), levels = c("amr", "cdr"))
df$epi_amr <- jurithy::bys_val(tp_md, df$amr_severity, df$sn, by= paste(as.numeric(df$epids), df$abx), val=df$amr_severity)
rm(tp_md)
}
df$epi_amr <- c("R","I","S","N","")[df$epi_amr]
abx_recoded <- df$amr!=df$epi_amr
summary$abx_recoded <- length(abx_recoded[abx_recoded])
abx_results <- dplyr::select(df, .data$epids, .data$abx, e = .data$epi_amr, r = .data$ast_result) %>%
dplyr::mutate(epids = as.numeric(.data$epids)) %>%
dplyr::distinct() %>%
tidyr::pivot_wider(names_from = .data$abx,
values_from = c(.data$e, .data$r),
values_fn = list(e = ~ paste0(unique(.), collapse = ";"),
r = ~ paste0(unique(.), collapse = ";")),
values_fill = list(e ="", r = ""))
df$amr_severity <- NULL
}else{
summary$abx_recoded <- NULL
abx_results <- NULL
}
df$patients <- as.numeric(!duplicated(df$pids@.Data))
df$episodes <- as.numeric(!duplicated(df$epids@.Data))
deduped_df <- df %>% filter(patients==1)
deduplication <- dplyr::select(janitor::tabyl(df, .data$species, .data$patients), "species", patients = "1")
deduplication <- dplyr::bind_cols(deduplication, dplyr::select(janitor::tabyl(df, .data$species, .data$episodes), episodes = "1"))
summary$deduplication <- deduplication
df$patients <- df$episodes <- df$sn <- df$pid_str <- NULL
output <- list(
extracts = allsites,
processed = df,
abx_results = abx_results,
summary = summary)
# processed_data <- as.data.frame(output[["processed"]])
processed_data <- deduped_df
abx_data <- as.data.frame(output[["abx_results"]])
processed_data$epids <- as.character(processed_data$epids)
processed_data$epids2 <- sapply(processed_data$epids, function(x) {
sub("^[^0-9]*0*([1-9][0-9]*)", "\\1", strsplit(x, " ")[[1]][1])
})
# merge linelist with abx results on epids
abx_results$epids <- as.character(abx_results$epids)
deduped_df <- left_join(processed_data,abx_results,by="epids") %>% select(-c("sn","pids","epi_amr","patients","abx","ast_result","episodes","epids2"))
return(deduped_df)
}
# /////////////------------ ETHNICITY GROUPINGS ------------ /////////////////
# /////////////////
# specify function to regroup LIMS ethnicity categories to espaur groupings and then to 5 categories for age standardisation
lims.ethcat.fun <- function(x){
#create char vec of unique Ethnic groups in ethnicity-linked linelist data
ethn.vec <- sort(unique(x$ethnicity)) # in alphabetical order for consistency when indexing
# vector positions for each ethnicity report in LIMS
# Unknown
# [1] ""
# [39]"Not Asked"
# [40] "Not Given on Form"
# [41]"Not given/declined"
# [42]"Not specified"
# [43] "Not Specified"
# [44]"Not stated"
# [45]"Other - not stated"
# [54]"Unable or unwilling to answer"
# [55] "UNKNOWN"
#
# Black, Black British, Caribbean or African
# [2]"African or African-Caribbean"
# [3]"Any other Black background"
# [19] "Black - any other black background"
# [20]"Black - Caribbean"
# [21]"Black African"
# [22] "Black Caribbean"
# [23]"Black or Black British - African "
# [24]"Black or Black British - Any other Black background "
# [25] "Black/Black British-Caribbean"
# [26]"Black/Black British African"
# [27]"Black/Black British Caribbean"
#
# Mixed
# [29]"Mixed - any other mixed background"
# [30]"Mixed - Any other mixed background "
# [31] "Mixed - White and Asian "
# [32]"Mixed - White and Black African "
# [33]"Mixed Any other mixed background"
# [34] "Mixed White & Black Caribbean"
# [35]"Mixed White and Asian"
# [36]"Mixed White and Black African"
# [37] "Mixed White and Black Caribbean"
#
# Other
# [4] "Any other ethnic group"
# [46] "Other Ethnic Group"
# [47]"Other Non-European"
# [49] "Other Ethnic Groups - Any other ethnic group "
# [53]"Southern & Other European"
# [38]"Northern European"
#
# Asian, Asian British
# [7] "Asian any other Asian background"
# [8]"Asian Bangladeshi"
# [9]"Asian Indian"
# [10] "Asian or Asian British - Any other Asian background"
# [11]"Asian or Asian British - Bangladeshi "
# [12]"Asian or Asian British - Indian "
# [13] "Asian or Asian British - Pakistani "
# [14]"Asian Pakistani"
# [15]"Asian/Asian British-Pakistani"
# [16] "Asian/Asian British any other"
# [17]"Asian/Asian British Bangladeshi"
# [18]"Asian/Asian British Indian"
# [28] "Chinese"
# [50]"Other Ethnic Groups - Chinese "
# [51]"South Asian"
# [52] "South East Asian"
#
#
#
# White
# [5]"Any other white background"
# [6]"Any other White background"
# [48]"Other white background"
# [56]"White"
# [57]"WHITE"
# [58] "White - Any other White background "
# [59]"White - British"
# [60]"White - Irish"
# [61] "White British"
# [62]"White Irish"
x <- x %>% mutate(Ethnicity_group="")
# for unknown group
x <- x %>%
mutate(Ethnicity_group = case_when(
ethnicity %in% c(ethn.vec[1], ethn.vec[54], ethn.vec[55]) ~ "Unknown",
ethnicity %in% ethn.vec[39:45] ~ "Unknown",
is.na(ethnicity) ~ "Unknown",
TRUE ~ Ethnicity_group
))
# for Black, Black British, Caribbean or African group
x <- x %>%
mutate(Ethnicity_group = case_when(
ethnicity %in% c(ethn.vec[2], ethn.vec[3]) ~ "Black",
ethnicity %in% ethn.vec[19:27] ~ "Black",
TRUE ~ Ethnicity_group
))
# for Mixed
x <- x %>%
mutate(Ethnicity_group = case_when(
ethnicity %in% ethn.vec[29:37] ~ "Mixed",
TRUE ~ Ethnicity_group
))
# for Other
x <- x %>%
mutate(Ethnicity_group = case_when(
ethnicity %in% c(ethn.vec[4],ethn.vec[46],ethn.vec[47],ethn.vec[49],ethn.vec[53],ethn.vec[38]) ~ "Other",
TRUE ~ Ethnicity_group
))
# for Asian, Asian British
x <- x %>%
mutate(Ethnicity_group = case_when(
ethnicity %in% ethn.vec[7:9] ~ "Asian",
ethnicity %in% ethn.vec[10:18] ~ "Asian",
ethnicity %in% ethn.vec[28] ~ "Asian",
ethnicity %in% ethn.vec[50:52] ~ "Asian",
TRUE ~ Ethnicity_group
))
# for White
x <- x %>%
mutate(Ethnicity_group = case_when(
ethnicity %in% c(ethn.vec[5], ethn.vec[6], ethn.vec[48]) ~ "White",
ethnicity %in% ethn.vec[56:62] ~ "White",
TRUE ~ Ethnicity_group
)) %>% select(-c("ethnicity")) %>% # drop reduntant Ethnicity assignment columns
mutate(z=1)
# add in count variable prior to grouping by ethnicity category
}
# /////////////------------ DBS TRACING ------------ /////////////////
# /////////////////
# specify function to perform initial data quality check on LIMS linelist and to generate susbet of data which missing one or more key fields for tracing
initial.lims.dq.fun <- function(x){
# nhsno_na <- sum(is.na(x$nhsno))
# dob_na <- sum(is.na(x$dob))
# err_date <- as.Date(c("1900-01-01"))
# dob_err <- sum(x$dob==err_date)
subset <- x %>% filter(is.na(dob) |
dob==as.Date(c("1900-01-01")) |
is.na(nhsno) |
is.na(surname) |
sex=="U" |
patient_postcode=="Z999")
subset <- subset
}
# /////////////////
# specify function to identify and retain QC sample reports from LIMS linelist, as part of DBS tracing
eqa.record.fun <- function(x){
out <- x %>% filter(dob==as.Date(c("1900-01-01")) |
surname=="LQ BLOOD CULTURE" |
surname=="IQAMIC2024" |
forename=="EQA" |
forename=="FOXTROTFIVEEIGHT" |
forename=="NOT USED" |
forename=="UNKNOWN" |
forename=="PORCINE TRACHEA(NEO)" |
surname=="5995753 CTOX" |
patient_postcode=="Z999")
out <- dbs.tracing.fields.fun(out)
}
# /////////////////
# specify nested function to prepare LIMS linelist data field formatting for DBS tracing
dbs.tracing.fields.fun <- function(x){
lims_for_trace <- x %>% arrange(pid_str) %>% # arrange by patient ID
rename(Identifier=pid_str,
Date_of_Birth=dob,
NHS_number=nhsno,
Surname=surname,
Forename=forename,
Sex=sex) %>%
mutate(null1="",
null2="",
null3="",
null4="",
null5="",
Sex=(if_else(Sex=="M","1",Sex)), # 1 = male, 2 = female, NA = unknown
Sex=(if_else(Sex=="F","2",Sex)),
Sex=(if_else(Sex=="U","",Sex)),
Sex=as.numeric(Sex),
Date_of_Birth=as.Date(Date_of_Birth, "%Y%m%d"),
Date_of_Birth=as.character(str_replace_all(string=Date_of_Birth, pattern="-", replacement = "") ),
Identifier=as.numeric(Identifier)) %>%
select(null1,Identifier,Date_of_Birth,null2,null3,NHS_number,Surname,null4,Forename,null5,Sex) %>% arrange(Identifier)
# filter_at(.vars = vars(Date_of_Birth,NHS_number), .vars_predicate = any_vars(!is.na(.))) # remove instances of episode lacking postcode & NHS
lims_for_trace$Forename <- toupper(gsub("\\.", "", lims_for_trace$Forename))
lims_for_trace$Surname <- toupper(gsub("\\.", "", lims_for_trace$Surname))
return(lims_for_trace)
}
# /////////////////
# specify wrap-around function to apply umbrella and nested functions for DBS tracing data prep
dbs.trace.prep.fun <- function(x,
mainfile=mainfile,
excludefile=excludefile){ # where main input is GroupCG_linelist
excluded_records <- initial.lims.dq.fun(x) # generate subset of data where one or more key fields missing for trace - send these for tracing if minimum necessary PII
# generate main df for tracing
lims_for_trace <- lims.fortrace.fun(x,
excluded_records)
# generate excluded records df for tracing
excluded_records_trace <- excl.records.trace.fun(x,
excluded_records)
# identify probable QC reports and exclude these from datasets to trace
eqa_records <- eqa.record.fun(x)
# exclude probable QC sample reports from data to be sent for tracing
lims_for_trace <- lims_for_trace %>% filter(!Identifier %in% eqa_records$Identifier)
excluded_records_trace <- excluded_records_trace %>% filter(!Identifier %in% eqa_records$Identifier)
# export to csv for sending to DBS team
write.csv(lims_for_trace, file = mainfile, row.names = F)
write.csv(excluded_records_trace, file = excludefile, row.names = F)
}
#////////////
#--main umbrella function to prepare linelist data for DBS tracing
lims.fortrace.fun <- function(x, # where x is GroupCG_linelist
excluded_records){
lims_for_trace <- x %>% filter(!pid_str %in% excluded_records$pid_str) # exclude above records from data to be sent for tracing
lims_for_trace <- dbs.tracing.fields.fun(lims_for_trace) } # prepare data formatting for tracing
# /////////////////
# specify function to prepare excluded data for DBS tracing
excl.records.trace.fun <- function(x, # where x is LIMS linelist
excluded_records){
excluded_records_trace <- x %>% filter((pid_str %in% excluded_records$pid_str)) # prepare excluded records for tracing for PII enrichment (these may still be traced although higher probability of failure)
excluded_records_trace <- dbs.tracing.fields.fun(excluded_records_trace) # prepare data formatting for tracing
}
# /////////////////
# specify function to format trace return colnames
header.fun <- function(x, # where x is trace response data
y=import(here("Data_carriage","DBS_tracing","trace_response_headers.xlsx"),
format = "xlsx",
fill = T,
comment.char="",
na = c(""),
skip = 0,
full.names = T)){ # where y is response headers
# # assign header colnames
# colnames(y) <- as.character(y[1,])
# retain only successful traces and drop additional column in trace response data so that ncol is 60
x <- x %>% filter(V1=="20" | V1=="30" | V1=="33" | V1=="40") %>% select(-any_of(c("V61"))) # retaining those cases that have been successfully traced
# define char vec of trace response headers
headers <- colnames(y)
# confirm length of headers is same as number of columns in trace_response
print(length(headers))
# define char vec of redundant col names (V1, V2 etc)
oldnames <- colnames(x)
# rename columns in trace response data based on headers
x <- x %>%
rename_at(vars(oldnames), ~ headers)
}
# /////////////////
# specify function to format trace return colnames for subset of return where trace unsuccessful
notrace.header.fun <- function(x, # where x is trace response data
y=import(here("Data_carriage","DBS_tracing","trace_response_headers.xlsx"),
format = "xlsx",
fill = T,
comment.char="",
na = c(""),
skip = 0,
full.names = T)){ # where y is response headers
# # assign header colnames
# colnames(y) <- as.character(y[1,])
# drop additional column in trace response data so that ncol is 60
x <- x %>% select(-any_of(c("V61")))
# define char vec of trace response headers
headers <- colnames(y)
# confirm length of headers is same as number of columns in trace_response
print(length(headers))
# define char vec of redundant col names (V1, V2 etc)
oldnames <- colnames(x)
# rename columns in trace response data based on headers
x <- x %>%
rename_at(vars(oldnames), ~ headers)
}
# /////////////////
# specify function to clean var names for traced return
clean.names.fun <- function(x){
# clean up col names (replace instances of [...] with spaces)
names(x) <- str_replace(names(x), "\\[", "")
names(x) <- str_replace(names(x), "\\]", "")
strips <- "^\\s+|\\s+$"
names(x) <- gsub(strips,"",names(x))
names(x) <- gsub("[[:space:]]","_",names(x))
x <- x
}
# /////////////////
# specify function to correctly format traced data prior to merge with original linelist
trace.reformat.fun <- function(x){
x <- x %>% mutate(Date_of_Death=as.character(Date_of_Death)) %>%
# mutate(DOB_S=as.character(DOB_S)) %>%
mutate(DOB_S=as.Date(as.character(DOB_S), format = "%Y%m%d")) %>% # takes DBS trace date format (e.g., 19891222) and converts to date e.g., 1989-12-22
rename(traced=`INSERT_INTO_DBSresponse_(_Response_Type`) %>%
rename(pid_str=Unique_Record_ID) %>%
select(pid_str,NHS_Number,DOB_S,First_Name_S,Surname_S,Sex_S,PostCode) %>%
mutate_all(na_if,"")
}
# /////////////////
# specify wrap function to retain records which weren't successfully traced
no.trace.wrap.fun <- function(x){ # where input is lims_trace_return
no_trace <- x %>% filter(V1!="20" & V1!="30" & V1!="33" & V1!="40")
no_trace <- notrace.header.fun(no_trace)
no_trace <- clean.names.fun(no_trace)
no_trace <- trace.reformat.fun(no_trace)
return(no_trace)
}
# /////////////////
# specify wrap function to retain records which had unknown patient sex
unknown.sex.wrap.fun <- function(x){ # where input is lims_trace_return
no_trace <- x %>% filter(V13=="1" | V13=="3")
no_trace <- notrace.header.fun(no_trace)
no_trace <- clean.names.fun(no_trace)
no_trace <- trace.reformat.fun(no_trace)
return(no_trace)
}
# /////////////////
# specify wrap function to prepare and join trace return with LIMS linelist
traced.wrap.fun <- function(x){ # where input is lims_trace_return
lims_trace_return <- header.fun(x)
lims_trace_return <- clean.names.fun(lims_trace_return)
lims_trace_return <- trace.reformat.fun(lims_trace_return)
merged_df <- right_join(lims_trace_return,distinct_patients,by="pid_str") %>%
mutate(NHS_Number=as.numeric(NHS_Number)) %>%
mutate(nhsno = case_when(
!is.na(NHS_Number) ~ NHS_Number,
TRUE ~ nhsno),
sex = case_when(
Sex_S==2 ~ "F",
TRUE ~ sex
),
traced = case_when(
is.na(NHS_Number) ~ "N",
!is.na(NHS_Number) ~ "Y"
)
) %>% select(-c("NHS_Number","DOB_S","First_Name_S","Surname_S","Sex_S")) %>%
mutate(patient_postcode=PostCode) %>% select(-c("PostCode"))
return(merged_df)
}
# /////////////////
# specify function to generate age groupings (8 groups)
agegroup.fun <- function(x){
x <- x %>% mutate(agegroup=case_when(age <1 ~ 1,
(age >=1 & age <= 4) ~ 2,
(age >=5 & age <= 9) ~ 3,
(age >=10 & age <= 14) ~ 4,
(age >=15 & age <= 44) ~ 5,
(age >=45 & age <= 64) ~ 6,
(age >=65 & age <= 74) ~ 7,
(age >=75 & age <=123) ~ 8,
(is.na(age) ~ 0)))
x$agegroup <- factor(x$agegroup,
levels=c('0','1','2','3','4','5','6','7','8'),
labels = c("not known","<1","1 to 4","5 to 9","10 to 14","15 to 44","45 to 64","65 to 74",">= 75"))
out <- x
}
# /////////////////
# specify function to further prepare LIMS linelist data for linkage (ethnicity and IMD)
linelist.prep.fun <- function(x){
x <- x %>% mutate(dob = lubridate::ymd(dob)) %>% # generate date of birth variable in date format
mutate(spec_date = lubridate::ymd(specdate), # generate date variable in date format from existing date char var
# Year = as.numeric(format(Date, '%Y')),
age = as.numeric(difftime(specdate,dob),unit="weeks")/52.25, # generate age variable (years, numeric)
age = round(age, digits=0),
dob_specdate = case_when(dob==specdate ~ "yes",
dob!=specdate ~ "no"),
sex = if_else(sex=="M","Male",sex),
sex = if_else(sex=="F","Female",sex),
sex = if_else(sex=="U","Unknown",sex)) %>%
mutate(n=1)
x <- agegroup.fun(x) # apply agegroup function
out <- x
}
# /////////////------------ IMD ENRICHMENT ------------ /////////////////
# /////////////////
# specify function to link linelist to IMD using lab postcode and CCG LSOA intermediate lookups
imd.linkage.fun <- function(x, # input is allsites (LIMS linelist post-DBS linkage)
lab_lookup_query = "SELECT [Lab_Geography_Code] ,[Lab_Geography_Name_Current] ,[POSTCODE] as labpostcode FROM [SGSSDW].[dbo].[DIMENSION_LAB_GEOGRAPHY] where Logical_Delete_Flag <> ('1') AND POSTCODE <> ('NULL')",
mycon = dbConnect(odbc::odbc(), # connection to SGSSDW
.connection_string = "DRIVER=SQL Server;
DATABASE=SGSSDW;
Trusted_Connection=Yes;
SERVER=sgssdb.phe.gov.uk;"),
# pcd2_lsoa_lookup = read_dta(file='//COLHPAFIL003.HPA.org.uk/ProjectData/HCAI/Scientific resources/GIS/ONS lookups/NHSPD_MAY_2022_UK_FULL/Data/pcd_CCG_lsoa_lookup.dta'),
hes_qry = glue(
"SELECT [pcd2], [ctry] ,[nhser] ,[lsoa11] ,[icb], [oac11] FROM [LookupsShared].[dbo].[vONS_NSPL11_UK_2023.05]"
),
hes_qry2 = glue(
"SELECT [pcd2],[LSOA11CD],[IMD2019_Deciles_LSOA11_England],[IMD2019_Quintiles_LSOA11_England],[ctry] FROM [LookupsShared].[dbo].[vSocioDemog_LSOA11] INNER JOIN [vONS_NSPL_UK_202205] on [lsoa11] = [LSOA11CD] where [ctry] IN ('E92000001')"
),
rx_dsn = "LookupsShared",
data_lake_connect = paste0('driver={SQL Server};server=SQLClusColLK19\\Lake19;',
'database=',rx_dsn,';trusted_connection=true'),
hes_con = odbc::dbConnect(odbc::odbc(),.connection_string=data_lake_connect)){ # connection to HES
# /// Lab postcode lookup for merge
# perform extract
# mycon <- mycon
lab_lookup_out <- dbSendQuery(mycon, lab_lookup_query)
lab_lookup <- dbFetch(lab_lookup_out)
dbClearResult (lab_lookup_out)
odbc::dbDisconnect(mycon)
# NEED TO MANUALLY LOOK UP LABGEOCODE FOR GBS ECM SITES IN LINELIST AND USE THIS TO MERGE ON LOOKUP
# merge linelist with lab_lookup on labgeocode
lab_lookup <- lab_lookup %>% rename(labgeocode=Lab_Geography_Code)
# add in labgeocodes for ECM sites
x <- x %>% mutate(labgeocode = case_when(
site=="Royal Stoke" ~ "597955", #ROYAL STOKE UNIVERSITY HOSPITAL
site=="Bedford" ~ "235615", # LUTON MICROBIOLOGY LABORATORY
site=="Derriford" ~ "526765", # DERRIFORD HOSP. (PLYMOUTH)
site=="Freeman" ~ "056130", # FREEMAN HOSPITAL (NEWCASTLE UPON TYNE)
site=="Royal Blackburn" ~ "670861", # ROYAL BLACKBURN
site=="Epsom" ~ "375120", # EPSOM (EPSOM AND ST HELIER)
site=="Birmingham" ~ "610640") # BIRMINGHAM (BIRMINGHAM WOMEN AND CHILDRENS)
)
x <- merge(x,lab_lookup,by="labgeocode")
# /// PCD2 LSOA lookup for merge
# perform extract
pcd2_lsoa_qry <- dbSendQuery(hes_con, hes_qry)
pcd2_lsoa_lookup <- dbFetch(pcd2_lsoa_qry)
dbClearResult (pcd2_lsoa_qry)
pcd2_lsoa_lookup$pcd2 <- str_replace_all(string=pcd2_lsoa_lookup$pcd2, pattern=" ", repl="") # remove white spaces in postcode field
pcd2_lsoa_lookup$pcd2 <- str_replace_all(string=pcd2_lsoa_lookup$pcd2, pattern=" ", repl="") # remove white spaces in postcode field
pcd2_lsoa_lookup <- pcd2_lsoa_lookup %>% filter(ctry=="E92000001") %>% arrange(lsoa11)
# define derivedpostcode var in linelist and preferentially assign as patient postcode > labpostcode
x <- x %>% mutate(derivedpostcode=case_when(
grepl("^ZZ99",patient_postcode) | patient_postcode=="No Data Avaliable" ~ labpostcode, # no patient postcode in LIMS ECM data
(!is.na(patient_postcode) & !grepl("^ZZ99",patient_postcode)) ~ patient_postcode,
is.na(patient_postcode) ~ labpostcode)) %>%
rename(pcd2=derivedpostcode) %>% # rename derivedpostcode prior to merge
mutate(pcd2=str_replace_all(pcd2, " ", ""))
# ensure instances of patient_postcode=="", labpostcode used
x <- x %>% mutate(pcd2 = case_when(
patient_postcode=="" ~ labpostcode,
TRUE ~ pcd2
))
# merge with CCG LSOA lookup based on pcd2 (derived postcode)
x <- merge(x,pcd2_lsoa_lookup,by="pcd2",all=T)
x <- x %>% filter(!is.na(pid_str))
# /// generate dynamic LSOA IMD code lookup
# perform extract
lsoa_IMD_qry <- dbSendQuery(hes_con, hes_qry2)
lsoa_IMD_lookup <- dbFetch(lsoa_IMD_qry)
dbClearResult (lsoa_IMD_qry)
odbc::dbDisconnect(hes_con)
lsoa_IMD_lookup$pcd2 <- str_replace_all(string=lsoa_IMD_lookup$pcd2, pattern=" ", repl="") # remove white spaces in postcode field
lsoa_IMD_lookup$pcd2 <- str_replace_all(string=lsoa_IMD_lookup$pcd2, pattern=" ", repl="") # remove white spaces in postcode field
lsoa_IMD_lookup <- lsoa_IMD_lookup %>% filter(ctry=="E92000001")
# merge with LSOA IMD lookup based on pcd2 (derived postcode)
x <- merge(x,lsoa_IMD_lookup,by="pcd2",all=T)
x <- x %>% filter(!is.na(pid_str))
x <- x[!duplicated(colnames(x))]
# reassign as linelist
return(x)
}
# /////////////------------ ETHNICITY ENRICHMENT ------------ /////////////////
# /////////////////
# specify function to prepare LIMS linelist data for ethnicity enrichment
datalink.prep.fun <- function(x){
# check names of date of birth and nhs number fields.
xnew <- x %>% mutate(dateofbirth = as.Date(dob, format = "%Y-%m-%d"))
xnew$Patient_NHS_No <- as.character(xnew$nhsno)
out <- xnew
}
# /////////////////
# specify function to link LIMS linelist to HES ethnicity fields by creating a temporary table in SQL
hes.ethnlink.fun <- function(x, # where main input is LIMS linelist
rx_dsn = "Y080_UID_PID",
data_lake_connect = paste0('driver={SQL Server};server=SQLClusColLK19\\Lake19;',
'database=',rx_dsn,';trusted_connection=true'),
data_lake = odbc::dbConnect(odbc::odbc(),.connection_string=data_lake_connect), # create DataLake connection
PII_data_name = paste0("JR_tbl_",format(Sys.Date(), "%y%m%d"),"_",format(Sys.time(), "%H_%M")), # PII to upload to DataLake: table name needs to be unique and not previously on the Database
target_table = paste0("JR_upld_",format(Sys.Date(), "%y%m%d"),"_",format(Sys.time(), "%H_%M")),
hes_qry = glue(
"SELECT *
INTO [Y080_UID_PID].[dbo].[{target_table}]
FROM (
SELECT DISTINCT
uploadeddata.*
, ethntp.TOKEN_PERSON_ID
, ethntp.NEW_ETHNOS -- Ethnicity; code
, ethntp.NEW_ETHNOS_Ethnic_subgroup
, ethntp.NEW_ETHNOS_Ethnic_group
FROM [Y080_UID_PID].[dbo].[{PII_data_name}] uploadeddata
LEFT JOIN [Y080_UID_PID].[dbo].[vY080_UID_HESOP_PID] apcpid ON apcpid.NEWNHSNO = uploadeddata.Patient_NHS_No AND CONVERT(DATE, CAST(apcpid.DOB_DV AS DATE),3) = uploadeddata.dateofbirth -- linked on NHS Number & DoB
LEFT JOIN [Y089_DLakeLinkedRepo_PID].[dbo].[x_HES_Analysis_Pseudo_vY089_HES_Ethnos_TPID] ethntp ON apcpid.TOKEN_PERSON_ID = ethntp.TOKEN_PERSON_ID
) AS hes"
)){
x <- datalink.prep.fun(x) # prepare linelist data for HES linkage
odbc::dbListTables(conn=data_lake, catalog_name=rx_dsn, schema_name="dbo") # Check names of the tables in the database
dbCreateTable(conn=data_lake, name = PII_data_name, fields = x, overwrite = T)
tableid <- Id(schema = "dbo", table = PII_data_name)
dbWriteTable(data_lake, tableid, x, append = TRUE, row.names = F)
linked_ethn <- dbSendQuery(data_lake,hes_qry)
linked_ethn <- dbGetQuery(data_lake,glue("select * from {target_table}"))
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
table_name <- target_table
# output table generated from query
ethnicity_linked_data <- dbReadTable(data_lake,table_name) # Extracting the ethnicity linked data from the DataLake
ethnicity_linked_data <- distinct(ethnicity_linked_data)
# get rid of temporary tables
dbSendQuery(data_lake, glue("drop table {target_table}"))
dbSendQuery(data_lake, glue("drop table {PII_data_name}"))
# disconnect from DL
dbDisconnect(data_lake)
write.csv(ethnicity_linked_data, "Data_carriage/Linelist/ethnlinked_intermediate.csv", row.names=F)
# reassign to data df
x <- as_tibble(ethnicity_linked_data)
# clean ethnicity linked linelist
x <- ethlinked.data.clean.fun(x)
# format IMD quintile variable as factor with labels
x <- imd.fac.label.fun(x)
return(x)
}
# /////////////////
# specify function to link LIMS linelist to ECDS ethnicity fields by creating a temporary table in SQL
ecds.ethnlink.fun <- function(x, # where main input is LIMS linelist
rx_dsn = "Y080_UID_PID",
data_lake_connect = paste0('driver={SQL Server};server=SQLClusColLK19\\Lake19;',
'database=',rx_dsn,';trusted_connection=true'),
data_lake = odbc::dbConnect(odbc::odbc(),.connection_string=data_lake_connect), # create DataLake connection
PII_data_name = paste0("JR_ecds_tbl_",format(Sys.Date(), "%y%m%d"),"_",format(Sys.time(), "%H_%M")), # PII to upload to DataLake: table name needs to be unique and not previously on the Database
target_table = paste0("JR_ecds_upld_",format(Sys.Date(), "%y%m%d"),"_",format(Sys.time(), "%H_%M")),
hes_qry = glue(
"SELECT *
INTO [Y080_UID_PID].[dbo].[{target_table}] -- new table of linked data, make sure it is a new name
FROM (
SELECT DISTINCT espamr.*
-- , ethntp.[PatientUsualAddressImdDecile]
-- , ethntp.[PatientUsualAddressImdDecileDescription]
-- , ethntp.[PatientUsualAddressRuralUrbanIndicator]
, ethntp.[PatientEthnicCategoryCode]
FROM [Y080_UID_PID].[dbo].[{PII_data_name}] espamr
LEFT JOIN [Y080_UID_PID].[dbo].[vY092_ECDS_SUS_PID] ethntp ON ethntp.NhsNumber = espamr.Patient_NHS_No AND CONVERT(DATE, CAST(ethntp.PatientBirthDate AS DATE),3) = espamr.dateofbirth
) AS hes"
)){
x <- datalink.prep.fun(x) # prepare linelist data for HES linkage
odbc::dbListTables(conn=data_lake, catalog_name=rx_dsn, schema_name="dbo") # Check names of the tables in the database
dbCreateTable(conn=data_lake, name = PII_data_name, fields = x, overwrite = T)
tableid <- Id(schema = "dbo", table = PII_data_name)
dbWriteTable(data_lake, tableid, x, append = TRUE, row.names = F)
linked_ecds_ethn <- dbSendQuery(data_lake,hes_qry)
linked_ecds_ethn <- dbGetQuery(data_lake,glue("select * from {target_table}"))
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
table_name <- target_table
# output table generated from query
ecds_ethnicity_linked_data <- dbReadTable(data_lake,table_name) # Extracting the ethnicity linked data from the DataLake
ecds_ethnicity_linked_data <- distinct(ecds_ethnicity_linked_data)
# get rid of temporary tables
dbSendQuery(data_lake, glue("drop table {target_table}"))
dbSendQuery(data_lake, glue("drop table {PII_data_name}"))
# disconnect from DL
dbDisconnect(data_lake)
# reassign to data df
x <- as_tibble(ecds_ethnicity_linked_data)
x <- x %>% rename(ethn_code=PatientEthnicCategoryCode)
return(x)
}
# /////////////////
# specify function to clean ethnicity linked LIMS linelist prior to regrouping ethnicity
ethlinked.data.clean.fun <- function(x){ # where main input is ethnicity-enriched LIMS linelist
# include pid_str and HES token ID in separate lookup
x <- x %>% select(any_of(c("pid_str","year","site","Lab_Geography_Name_Current","specdate","dob_specdate","spectype","species","forename","surname","nhsno","Patient_NHS_No","dob","age","agegroup","dateofbirth","sex","IMD2019_Quintiles_LSOA11_England","Ethnicity_group","NEW_ETHNOS_Ethnic_group","NEW_ETHNOS_Ethnic_subgroup","Fact_of_Death","Date_of_Death"))) %>%
rename_at(vars(starts_with("IMD2019_Quintiles_LSOA11_England")), ~ "imd_quintile") %>% # NEXT: apply rename_at to factofdeath and dateofdeath
# rename_at(vars(starts_with("Fact_of_Death")), ~ "factofdeath") %>% # optional: may not have death field in linelist (less likely for women of child bearing age)
# rename_at(vars(starts_with("Date_of_Death")), ~ "dateofdeath") %>%
rename_at(vars(starts_with("NEW_ETHNOS_Ethnic_subgroup")), ~ "Ethnic_group") %>%
# mutate(across((contains("factofdeath") | contains("Fact_of_Death")), .fns = temp_fun, .names = "factofdeath")) %>%
# mutate(factofdeath=case_when(factofdeath=="D" ~ 1,
# is.na(factofdeath) ~ 0)) %>%
mutate(note = case_when(
dateofbirth=="NA" | is.na(dateofbirth) | nhsno=="NA" | is.na(nhsno) ~ "insufficient PII for ethn trace",
!is.na(nhsno) | !is.na(dateofbirth) ~ "PII available for ethn trace",
!is.na(nhsno) & nhsno!="NA" & !is.na(dateofbirth) & dateofbirth!="NA" ~ "ethnicity not available")
)
}
# /////////////////
# specify function to generate binary (white vs non-white) ethnicity categorical variable for model
ethn.binary.fac.fun <- function(x){
x$hes_ethnicity <- fct_collapse(x$hes_ethnicity,
"white" = c("White"),
"Nonwhite" = c("Black","Asian","Other","Mixed"))
return(x)
}
# /////////////////
# specify function to assign IMD quintile field in linelist as factor with corresponding labels
imd.fac.label.fun <- function(x){
x$imd_quintile <- factor(x$imd_quintile,
levels=c('1','2','3','4','5'),
labels = c("20% most deprived","20% to 40%","40% to 60%","60% to 80%","20% least deprived"))
out <- x
}
linelist.ethcat5.fun <- function(x){
ethn.vec <- sort(unique(x$NEW_ETHNOS_Ethnic_group))
# [1] "99 Not known"
# [2] "Any other ethnic group"
# [3] "Asian / Asian British"
# [4] "Black / African / Caribbean / Black British"
# [5] "Mixed / Multiple ethnic groups"
# [6] "White"
# [7] "Z Not stated"
x <- x %>% mutate(hes_ethnicity="")
# for unknown group and not linked
x <- x %>%
mutate(hes_ethnicity = case_when(
NEW_ETHNOS_Ethnic_group %in% c(ethn.vec[1], ethn.vec[7]) ~ "Unknown",
is.na(NEW_ETHNOS_Ethnic_group) ~ "Not linked",
TRUE ~ hes_ethnicity
))
# for Black, Black British, Caribbean or African group
x <- x %>%
mutate(hes_ethnicity = case_when(
NEW_ETHNOS_Ethnic_group %in% ethn.vec[4] ~ "Black",
TRUE ~ hes_ethnicity
))
# for Mixed
x <- x %>%
mutate(hes_ethnicity = case_when(
NEW_ETHNOS_Ethnic_group %in% ethn.vec[5] ~ "Mixed",
TRUE ~ hes_ethnicity
))
# for Other
x <- x %>%
mutate(hes_ethnicity = case_when(
NEW_ETHNOS_Ethnic_group %in% ethn.vec[2] ~ "Other",
TRUE ~ hes_ethnicity
))
# for Asian, Asian British
x <- x %>%
mutate(hes_ethnicity = case_when(
NEW_ETHNOS_Ethnic_group %in% ethn.vec[3] ~ "Asian",
TRUE ~ hes_ethnicity
))
# for White
x <- x %>%
mutate(hes_ethnicity = case_when(
NEW_ETHNOS_Ethnic_group %in% ethn.vec[6] ~ "White",
TRUE ~ hes_ethnicity
)) %>%
select(-c("Ethnic_group","NEW_ETHNOS_Ethnic_group")) %>% mutate(z=1)
x <- if ("Ethnicity_group" %in% names(x)) {
x %>% rename(lim_ethnicity=Ethnicity_group) } else { # drop reduntant Ethnicity assignment columns
x <- x
}
# add in count variable prior to grouping by ethnicity category
}
# /////////////------------ HES APC LINKAGE FOR PREGNANCY INDICATORS ------------ /////////////////
hesapc.link.fun <- function(x, # where main input is LIMS linelist
rx_dsn = "Y080_UID_PID",
data_lake_connect = paste0('driver={SQL Server};server=SQLClusColLK19\\Lake19;',
'database=',rx_dsn,';trusted_connection=true'),
data_lake = odbc::dbConnect(odbc::odbc(),.connection_string=data_lake_connect), # create DataLake connection
PII_data_name = paste0("JR_tbl_",format(Sys.Date(), "%y%m%d"),"_",format(Sys.time(), "%H_%M")), # PII to upload to DataLake: table name needs to be unique and not previously on the Database
target_table = paste0("JR_upld_",format(Sys.Date(), "%y%m%d"),"_",format(Sys.time(), "%H_%M")),
hes_qry = glue(
"-- Step 1: Create target table if it doesn't exist
CREATE TABLE [Y080_UID_PID].[dbo].[{target_table}] (
Patient_NHS_No VARCHAR(50),
dateofbirth DATE,
specdate DATE,
FYEAR INT,
EPIKEY VARCHAR(50),
DOBBABY_1_DV DATE,
ADMIDATE DATE,
ADMIDATE_DV DATE,
DISDATE DATE,
DISDATE_DV DATE,
EPITYPE VARCHAR(50),
OPERTN_01 VARCHAR(50),
OPDATE_01 DATE,
BIRSTAT_1 VARCHAR(50),
DELMETH_D VARCHAR(50),
GESTAT_1 VARCHAR(50),
DIAG_01 VARCHAR(50),
DIAG_02 VARCHAR(50),
DIAG_03 VARCHAR(50),
DIAG_04 VARCHAR(50),
DIAG_05 VARCHAR(50),
PROCODE3 VARCHAR(50)
);
-- Step 2: Insert data into target table using CTEs
;WITH filtered_hesapc AS (
SELECT FYEAR, EPIKEY, PROCODE3
FROM [HES_APC].[dbo].[vtHES_APC]
WHERE FYEAR IN (2122, 2223, 2324) AND PROCODE3 IN ('RTG', 'RD8', 'RFW', 'R1H', 'RXR', 'RJR', 'RQ3', 'RWY', 'RXF', 'RC9', 'R1K', 'RVR', 'RTD', 'RM3', 'RTK', 'RK9', 'RBT')
),
filtered_admis AS (
SELECT *
FROM [HES_APC].[dbo].[vHES_APC_Flat]
WHERE FYEAR IN (2122, 2223, 2324)
),
filtered_oprtn AS (
SELECT *
FROM [HES_APC].[dbo].[vHES_APC_OPERTN_Flat]
WHERE FYEAR IN (2122, 2223, 2324)
),
filtered_mat AS (
SELECT *
FROM [HES_APC].[dbo].[vtHES_APC_MAT]
WHERE FYEAR IN (2122, 2223, 2324)
),
filtered_diag AS (
SELECT *
FROM [HES_APC].[dbo].[vHES_APC_DIAG_Flat]
WHERE FYEAR IN (2122, 2223, 2324)
),
filtered_apcpid AS (
SELECT *
FROM [Y080_UID_PID].[dbo].[vY080_UID_HESAPC_PID]
)
INSERT INTO [Y080_UID_PID].[dbo].[{target_table}]
SELECT
uploadeddata.Patient_NHS_No,
uploadeddata.dateofbirth,
uploadeddata.specdate,
apcpid.FYEAR,
apcpid.EPIKEY,
apcpid.DOBBABY_1_DV,
admis.ADMIDATE,
admis.ADMIDATE_DV,
admis.DISDATE,
admis.DISDATE_DV,
admis.EPITYPE,
oprtn.OPERTN_01,
oprtn.OPDATE_01,
mat.BIRSTAT_1,
mat.DELMETH_D,
mat.GESTAT_1,
diag.DIAG_01,
diag.DIAG_02,
diag.DIAG_03,
diag.DIAG_04,
diag.DIAG_05,
hesapc.PROCODE3
FROM [Y080_UID_PID].[dbo].[{PII_data_name}] AS uploadeddata
LEFT JOIN
filtered_apcpid AS apcpid ON apcpid.NEWNHSNO = uploadeddata.Patient_NHS_No
AND CONVERT(DATE, apcpid.DOB_DV, 3) = uploadeddata.dateofbirth
INNER JOIN
filtered_oprtn AS oprtn ON apcpid.EPIKEY = oprtn.EPIKEY AND apcpid.FYEAR = oprtn.FYEAR
INNER JOIN
filtered_mat AS mat ON apcpid.EPIKEY = mat.EPIKEY AND apcpid.FYEAR = mat.FYEAR
INNER JOIN
filtered_admis AS admis ON mat.EPIKEY = admis.EPIKEY AND mat.FYEAR = admis.FYEAR
INNER JOIN
filtered_diag AS diag ON admis.EPIKEY = diag.EPIKEY AND diag.FYEAR = admis.FYEAR
INNER JOIN
filtered_hesapc AS hesapc ON diag.EPIKEY = hesapc.EPIKEY AND hesapc.FYEAR = diag.FYEAR;")){
x <- datalink.prep.fun(x) # prepare linelist data for HES linkage
odbc::dbListTables(conn=data_lake, catalog_name=rx_dsn, schema_name="dbo") # Check names of the tables in the database
dbCreateTable(conn=data_lake, name = PII_data_name, fields = x, overwrite = T)
tableid <- Id(schema = "dbo", table = PII_data_name)
dbWriteTable(data_lake, tableid, x, append = TRUE, row.names = F)
linked_data <- dbSendQuery(data_lake,hes_qry)
linked_data <- dbGetQuery(data_lake,glue("select * from {target_table}"))
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
table_name <- target_table
# output table generated from query
hesapc_linked_data <- dbReadTable(data_lake,table_name) # Extracting the ethnicity linked data from the DataLake
hesapc_linked_data <- distinct(hesapc_linked_data)
# get rid of temporary tables
dbSendQuery(data_lake, glue("drop table {target_table}"))
dbSendQuery(data_lake, glue("drop table {PII_data_name}"))
# disconnect from DL
dbDisconnect(data_lake)
# reassign to data df
x <- as_tibble(hesapc_linked_data)
setwd(wd)
# export for further processing (ethnic group assignment according to CHIME methodology) in Stata
write.csv(x, "Data_carriage/Linelist/static_basedata_hesapclinked.csv", row.names=F)
return(x)
}
Add a new chunk by clicking the Insert Chunk button on the
toolbar or by pressing Ctrl+Alt+I.
When you save the notebook, an HTML file containing the code and
output will be saved alongside it (click the Preview button or
press Ctrl+Shift+K to preview the HTML file).
The preview shows you a rendered HTML copy of the contents of the
editor. Consequently, unlike Knit, Preview does not
run any R code chunks. Instead, the output of the chunk when it was last
run in the editor is displayed.
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vay4gV2hlbiB5b3UgZXhlY3V0ZSBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSByZXN1bHRzIGFwcGVhciBiZW5lYXRoIHRoZSBjb2RlLiANCg0KVHJ5IGV4ZWN1dGluZyB0aGlzIGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqUnVuKiBidXR0b24gd2l0aGluIHRoZSBjaHVuayBvciBieSBwbGFjaW5nIHlvdXIgY3Vyc29yIGluc2lkZSBpdCBhbmQgcHJlc3NpbmcgKkN0cmwrU2hpZnQrRW50ZXIqLiANCg0KYGBge3J9DQoNCg0KIyAvLy8vLy8vLy8vLy8vLS0tLS0tLS0tLS0tIExJTVMgREFUQSBGT1JNQVRUSU5HIC0tLS0tLS0tLS0tLSAvLy8vLy8vLy8vLy8vLy8vLw0KDQojIC8vLy8vLy8vLy8vLy8vLy8vDQojIHNwZWNpZnkgZnVuY3Rpb24gdG8gc3RhbmRhcmRpc2UgZm9ybWF0IG9mIExJTVMgZGF0YSBmb3IgYW5hbHlzaXMNCmxpbXMuc3RuZC5mb3JtYXQuZnVuIDwtIGZ1bmN0aW9uKHgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWVsZF9uYW1lcyA9IGMoInNwZWNpbWVuIG51bWJlciIsICAgICAgICAgICAgICAgICAgICAgICAgICMjIHNwZWNpZnkgYWxsIHBvc3NpYmxlIGZpZWxkIG5hbWVzIGZvciBlYWNoIG9mIHRoZSBlc3NlbnRpYWwgaW5kaWNhdG9ycw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjcm4gKGhvc3BpdGFsKSBudW1iZXIiLCJwYXRpZW50IGhvc3BpdGFsIG51bWJlciIsICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibmhzIG51bWJlciIsInBhdGllbnQgbmhzIG51bWJlciIsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZm9yZW5hbWUiLCJwYXRpZW50IGZvcmVuYW1lIiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzdXJuYW1lIiwicGF0aWVudCBzdXJuYW1lIiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJkYXRlIG9mIGJpcnRoIiwicGF0aWVudCBkYXRlIG9mIGJpcnRoIiwicGF0aWVudCBkb2IiLCAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzZXgiLCJwYXRpZW50IHNleCIsICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZXRobmljIG9yaWdpbiBkZXNjIiwiZXRobmljaXR5IiwgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInBvc3QgY29kZSIsInBhdGllbnQgcG9zdGNvZGUiLCAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImRhdGUgb2Ygc3BlY2ltZW4iLCJzcGVjaW1lbiBkYXRlIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNwZWNpbWVuIHR5cGUgZGVzYyIsInNwZWNpbWVuIHR5cGUiLCAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm9yZ2FuaXNtIGRlc2MiLCJyZXN1bHQgdGV4dCIsIm9yZ2FuaXNtIGNvbW1lbnQiLCJvcmdhbmlzbSBzcGVjaWVzIG5hbWUiLCAgICAgICAgICAgICMjIGZpZWxkIGRlc2NyaWJpbmcgb3JnYW5pc20gaXNvbGF0ZWQsIGluY2x1ZGluZyBHQlMgbmVnYXRpdmUgYW5kIG5vIG9yZ2FuaXNtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJhbnRpYmlvdGljIGRlc2MiLCJhbnRpbWljcm9iaWFsIiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicmVzdWx0Iiwic3VzY2VwdGliaWxpdHkgdGVzdCByZXN1bHQiKSl7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgDQogIG5hbWVzKHgpIDwtIHRvbG93ZXIoZ3N1YigiXFwuIiwgIiIsIG5hbWVzKHgpKSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGVuc3VyZSBhbGwgZmllbGQgbmFtZXMgbG93ZXIgY2FzZQ0KICANCiAgeCA8LSB4ICU+JSBzZWxlY3QoYW55X29mKGZpZWxkX25hbWVzKSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgc2VsZWN0IGVzc2VudGlhbCBpbmRpY2F0b3JzDQogIA0KICBuYW1lcyh4KSA8LSBkcGx5cjo6Y2FzZV93aGVuKChuYW1lcyh4KSA9PSAic3BlY2ltZW4gbnVtYmVyIikgfiAic3BlY25vIiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIyBzdGFuZGFyZGlzZSBmaWVsZCBuYW1lcyBmb3IgZWFjaCBvZiB0aGUgZXNzZW50aWFsIGluZGljYXRvcnMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAobmFtZXMoeCkgPT0gImNybiAoaG9zcGl0YWwpIG51bWJlciIgfCBuYW1lcyh4KSA9PSAicGF0aWVudCBob3NwaXRhbCBudW1iZXIiKSB+ICJob3Nwbm8iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChuYW1lcyh4KSA9PSAibmhzIG51bWJlciIgfCBuYW1lcyh4KSA9PSAicGF0aWVudCBuaHMgbnVtYmVyIikgfiAibmhzbm8iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChuYW1lcyh4KSA9PSAiZm9yZW5hbWUiIHwgbmFtZXMoeCkgPT0gInBhdGllbnQgZm9yZW5hbWUiKSB+ICJmb3JlbmFtZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKG5hbWVzKHgpID09ICJzdXJuYW1lIiB8IG5hbWVzKHgpID09ICJwYXRpZW50IHN1cm5hbWUiKSB+ICJzdXJuYW1lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAobmFtZXMoeCkgPT0gImRhdGUgb2YgYmlydGgiIHwgbmFtZXMoeCkgPT0gInBhdGllbnQgZGF0ZSBvZiBiaXJ0aCIgfCBuYW1lcyh4KSA9PSAicGF0aWVudCBkb2IiKSB+ICJkb2IiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChuYW1lcyh4KSA9PSAic2V4IiB8IG5hbWVzKHgpID09ICJwYXRpZW50IHNleCIpIH4gInNleCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKG5hbWVzKHgpID09ICJldGhuaWMgb3JpZ2luIGRlc2MiIHwgbmFtZXMoeCkgPT0gImV0aG5pY2l0eSIpIH4gImV0aG5pY2l0eSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKG5hbWVzKHgpID09ICJwb3N0IGNvZGUiIHwgbmFtZXMoeCkgPT0gInBhdGllbnQgcG9zdGNvZGUiKSB+ICJwYXRpZW50X3Bvc3Rjb2RlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAobmFtZXMoeCkgPT0gImRhdGUgb2Ygc3BlY2ltZW4iIHwgbmFtZXMoeCkgPT0gInNwZWNpbWVuIGRhdGUiKSB+ICJzcGVjZGF0ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKG5hbWVzKHgpID09ICJzcGVjaW1lbiB0eXBlIGRlc2MiIHwgbmFtZXMoeCkgPT0gInNwZWNpbWVuIHR5cGUiKSB+ICJzcGVjdHlwZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKG5hbWVzKHgpID09ICJvcmdhbmlzbSBkZXNjIiB8IG5hbWVzKHgpID09ICJyZXN1bHQgdGV4dCIgfCBuYW1lcyh4KSA9PSAib3JnYW5pc20gY29tbWVudCIgfCBuYW1lcyh4KSA9PSAib3JnYW5pc20gc3BlY2llcyBuYW1lIikgfiAic3BlY2llcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKG5hbWVzKHgpID09ICJhbnRpYmlvdGljIGRlc2MiIHwgbmFtZXMoeCkgPT0gImFudGltaWNyb2JpYWwiKSB+ICJhYngiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChuYW1lcyh4KSA9PSAicmVzdWx0IiB8IG5hbWVzKHgpID09ICJzdXNjZXB0aWJpbGl0eSB0ZXN0IHJlc3VsdCIpIH4gImFzdF9yZXN1bHQiKQ0KICANCiAgeCA8LSB4ICU+JSANCiAgICBtdXRhdGUoc3BlY2llcz1jYXNlX3doZW4oDQogICAgICBncmVwbCgiQmV0YSBIYWVtb2x5dGljIFN0cmVwdG9jb2NjdXMgR3JvdXAgQiIsc3BlY2llcykgfCANCiAgICAgICAgZ3JlcGwoIl5TdHJlcHRvY29jY3VzIGFnYWxhY3RpYWUiLHNwZWNpZXMpIHwgDQogICAgICAgIHNwZWNpZXMgPT0gIlN0cmVwdG9jb2NjdXMgR3JvdXAgQiIgfCANCiAgICAgICAgc3BlY2llcyA9PSAiUy5hZ2FsYWN0aWFlKEdyb3VwIEIgc3RyZXApIiB8IA0KICAgICAgICBzcGVjaWVzID09ICJHcm91cCBCIEhhZW1vbHl0aWMgU3RyZXAiICB8IA0KICAgICAgICBzcGVjaWVzID09ICJTdHJlcC5hZ2FsYWN0aWFlIChHcm91cCBCKSIgfCANCiAgICAgICAgc3BlY2llcyA9PSAiR3JvdXAgQiBTdHJlcHRvY29jY3VzIGlzb2xhdGVkIiB8IA0KICAgICAgICBzcGVjaWVzID09ICJHcm91cCBCIFN0cmVwdG9jb2NjdXMgSVNPTEFURUQiIH4gIkdyb3VwIEIgU3RyZXB0b2NvY2N1cyIsDQogICAgICBzcGVjaWVzID09ICJHcm91cCBCIFN0cmVwIE5PVCBpc29sYXRlZCIgfCANCiAgICAgICAgc3BlY2llcz09ICJTdHJlcHRvY29jY3VzIGdyb3VwIEIgTk9UIGlzb2xhdGVkIiB8IA0KICAgICAgICBzcGVjaWVzPT0iR3JvdXAgQiBTdHJlcHRvY29jY3VzIE5vdCBpc29sYXRlZCIgfCANCiAgICAgICAgc3BlY2llcz09ICJObyBvcmdhbmlzbSBkZXRlY3RlZCIgfCANCiAgICAgICAgc3BlY2llcz09Ik5lZ2F0aXZlIGFmdGVyIDEgZGF5IiB8IA0KICAgICAgICBzcGVjaWVzPT0iT3JnYW5pc20gU3BlY2llcyBOYW1lIiB8DQogICAgICAgIGdyZXBsKCJeQ2FuZGlkYSIsc3BlY2llcykgfiAiR0JTIG5vdCBpc29sYXRlZCIsDQogICAgICBUUlVFIH4gc3BlY2llcyksDQogICAgICBldGhuaWNpdHk9Y2FzZV93aGVuKA0KICAgICAgZ3JlcGwoInhhMEV0aG5pYyIsIGV0aG5pY2l0eSkgfiAiQW55IG90aGVyIGV0aG5pYyBncm91cCIsIA0KICAgICAgVFJVRSB+IGV0aG5pY2l0eSkNCikNCiAgICAgICAgeCA8LSB4WywgIWR1cGxpY2F0ZWQoY29sbmFtZXMoeCkpXSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyMgcmVtb3ZlIGR1cGxpY2F0ZSBjb2xzDQogIA0KICAgICAgICAjIGFkZCBpbiBhbnRpbWljcm9iaWFsIGNvbHMgaWYgbm90IGFscmVhZHkgaW4gZGF0YSAoZm9yIEdCUyBuZWcpDQogICAgICAgIGlmKCEiYWJ4IiAlaW4lIGNvbG5hbWVzKHgpKXsNCiAgICAgICAgICB4JGFieCA8LSBOQQ0KICAgICAgICB9DQogICAgICAgIGlmKCEiYXN0X3Jlc3VsdCIgJWluJSBjb2xuYW1lcyh4KSl7DQogICAgICAgICAgeCRhc3RfcmVzdWx0IDwtIE5BDQogICAgICAgIH0NCiAgICAgICAgICANCiAgeCA8LSB4ICU+JSBtdXRhdGUoYWJ4ID0gY2FzZV93aGVuKA0KICAgIGFieD09IiIgfiBOQSwNCiAgICBUUlVFIH4gYWJ4KSwNCiAgICBhc3RfcmVzdWx0ID0gY2FzZV93aGVuKA0KICAgICAgYXN0X3Jlc3VsdD09IiIgfiBOQSwNCiAgICAgIFRSVUUgfiBhc3RfcmVzdWx0KSkgJT4lIA0KICAgIG11dGF0ZShhc3RfcmVzdWx0PXRvdXBwZXIoYXN0X3Jlc3VsdCksDQogICAgICAgICAgIG1vZHVsZT0iTElNUyIpDQogIA0KICAjIyBjb252ZXJ0aW5nIHNwZWNpbWVuIGRhdGUgYW5kIGRvYiB0byBjb3JyZWN0IGRhdGUgZm9ybWF0DQogIHggPC0geCAlPiUNCiAgICBtdXRhdGUoDQogICAgICBkb2IgPSBjYXNlX3doZW4oDQogICAgICAgIGluaGVyaXRzKGRvYiwgIlBPU0lYY3QiKSB+IGFzLkRhdGUoZG9iKSwgICMgUE9TSVhjdCB0byBEYXRlDQogICAgICAgIGluaGVyaXRzKGRvYiwgIklEYXRlIikgfiBhcy5EYXRlKGFzLmNoYXJhY3Rlcihkb2IpKSwgICMgSURhdGUgdG8gRGF0ZQ0KICAgICAgICBUUlVFIH4gYXMuRGF0ZShkb2IsIGZvcm1hdCA9ICIlZC8lbS8lWSIpICAjIENoYXJhY3RlciBzdHJpbmcgIkREL01NL1lZWVkiIHRvIERhdGUNCiAgICAgICkNCiAgICApICU+JSANCiAgICBtdXRhdGUoDQogICAgICBzcGVjZGF0ZSA9IGNhc2Vfd2hlbigNCiAgICAgICAgaW5oZXJpdHMoc3BlY2RhdGUsICJQT1NJWGN0IikgfiBhcy5EYXRlKHNwZWNkYXRlKSwgICMgUE9TSVhjdCB0byBEYXRlDQogICAgICAgIGluaGVyaXRzKHNwZWNkYXRlLCAiSURhdGUiKSB+IGFzLkRhdGUoYXMuY2hhcmFjdGVyKHNwZWNkYXRlKSksICAjIElEYXRlIHRvIERhdGUNCiAgICAgICAgVFJVRSB+IGFzLkRhdGUoc3BlY2RhdGUsIGZvcm1hdCA9ICIlZC8lbS8lWSIpICAjIENoYXJhY3RlciBzdHJpbmcgIkREL01NL1lZWVkiIHRvIERhdGUNCiAgICAgICkNCiAgICApICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyMgY29udmVydGluZyBzcGVjaW1lbiBkYXRlIHRvIGNvcnJlY3QgZGF0ZSBmb3JtYXQNCiAgDQogIHJldHVybih4KSAgICAgICAjIGZ1bmN0aW9uIG91dHB1dA0KICANCn0NCg0KDQojIHNwZWNpZnkgc3BlY2llcyBjb2x1bW4gZm9ybWF0dGluZyBmdW5jdGlvbg0Kc3BlY2llc19mb3JtYXRfZnVuIDwtIGZ1bmN0aW9uKHgpew0KICB4IDwtIHggJT4lIA0KICAgIG11dGF0ZShzcGVjaWVzPWNhc2Vfd2hlbigNCiAgICAgIGdyZXBsKCJCZXRhIEhhZW1vbHl0aWMgU3RyZXB0b2NvY2N1cyBHcm91cCBCIixzcGVjaWVzKSB8IA0KICAgICAgICBncmVwbCgiXlN0cmVwdG9jb2NjdXMgYWdhbGFjdGlhZSIsc3BlY2llcykgfCANCiAgICAgICAgc3BlY2llcyA9PSAiU3RyZXB0b2NvY2N1cyBHcm91cCBCIiB8IA0KICAgICAgICBzcGVjaWVzID09ICJTLmFnYWxhY3RpYWUoR3JvdXAgQiBzdHJlcCkiIHwgDQogICAgICAgIHNwZWNpZXMgPT0gIkdyb3VwIEIgSGFlbW9seXRpYyBTdHJlcCIgIHwgDQogICAgICAgIHNwZWNpZXMgPT0gIlN0cmVwLmFnYWxhY3RpYWUgKEdyb3VwIEIpIiB8IA0KICAgICAgICBzcGVjaWVzID09ICJHcm91cCBCIFN0cmVwdG9jb2NjdXMgaXNvbGF0ZWQiIHwgDQogICAgICAgIHNwZWNpZXMgPT0gIkdyb3VwIEIgU3RyZXB0b2NvY2N1cyBJU09MQVRFRCIgfA0KICAgICAgICBzcGVjaWVzPT0gImJldGEgSGFlbW9seXRpYyBTdHJlcCwgZ3JvdXAgQiIgfiAiR3JvdXAgQiBTdHJlcHRvY29jY3VzIiwNCiAgICAgIHNwZWNpZXMgPT0gIkdyb3VwIEIgU3RyZXAgTk9UIGlzb2xhdGVkIiB8IA0KICAgICAgICBzcGVjaWVzPT0gIlN0cmVwdG9jb2NjdXMgZ3JvdXAgQiBOT1QgaXNvbGF0ZWQiIHwgDQogICAgICAgIHNwZWNpZXM9PSJHcm91cCBCIFN0cmVwdG9jb2NjdXMgTm90IGlzb2xhdGVkIiB8IA0KICAgICAgICBzcGVjaWVzPT0gIk5vIG9yZ2FuaXNtIGRldGVjdGVkIiB8DQogICAgICAgIHNwZWNpZXM9PSJObyBvcmdhbmlzbSByZWNvcmRlZCIgfA0KICAgICAgICBzcGVjaWVzPT0iTmVnYXRpdmUgYWZ0ZXIgMSBkYXkiIHwgDQogICAgICAgIHNwZWNpZXM9PSJPcmdhbmlzbSBTcGVjaWVzIE5hbWUiIHwNCiAgICAgICAgZ3JlcGwoIl5DYW5kaWRhIixzcGVjaWVzKSB+ICJHQlMgbm90IGlzb2xhdGVkIiwNCiAgICAgIFRSVUUgfiBzcGVjaWVzKQ0KICAgICkNCn0NCg0KDQoNCg0KIyAvLy8vLy8vLy8vLy8vLS0tLS0tLS0tLS0tIEdCUyBDQVJSSUFHRSBFU1RJTUFUSU9OIC0tLS0tLS0tLS0tLSAvLy8vLy8vLy8vLy8vLy8vLw0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byByZXNoYXBlIGFuZCBjYWxjdWxhdGUgR0JTIGNhcnJpYWdlICglIHRvdGFsIHBhdGllbnRzIGluIHNhbXBsaW5nIHRpbWVmcmFtZSkNCmdicy5jYXJyLmJ5ZXRobi5mdW4gPC0gZnVuY3Rpb24oeCl7DQogIA0KICBvdXRkZiA8LSB4ICU+JSBtdXRhdGUoej0xKSANCiAgIyAlPiUgZmlsdGVyKGhlc19ldGhuaWNpdHkhPSJVbmtub3duIiAmIGhlc19ldGhuaWNpdHkhPSJOb3QgbGlua2VkIiAmIGhlc19ldGhuaWNpdHkhPSJfZnJvbUxJTVMiKSANCg0KICANCiAgZXRobl9zdW1tYXJ5IDwtIG91dGRmICU+JSBncm91cF9ieShoZXNfZXRobmljaXR5KSAlPiUgc3VtbWFyaXplKHRvdGFsPXN1bSh6KSkgJT4lIG11dGF0ZShwY3Q9dG90YWwvc3VtKHRvdGFsKSoxMDApDQogIA0KICAjIEdCUyBwb3MgYnkgZXRobmljaXR5DQogIGdic3Bvc19ieWV0aG4gPC0gb3V0ZGYgJT4lIGdyb3VwX2J5KGhlc19ldGhuaWNpdHkpICU+JSBtdXRhdGUoZ2JzcG9zPWlmZWxzZShzcGVjaWVzPT0iR3JvdXAgQiBTdHJlcHRvY29jY3VzIiwxLDApKSAlPiUgc3VtbWFyaXplKGdic3Bvcz1zdW0oZ2JzcG9zKSkgICAjIEdCUyBwb3MgYnkgZXRobmljaXR5DQogIA0KICAjIHRvdGFsIEVDTSBieSBldGhuaWNpdHkNCiAgdG90YWxfYnlldGggPC0gb3V0ZGYgJT4lIGdyb3VwX2J5KGhlc19ldGhuaWNpdHkpICU+JSBtdXRhdGUodG90YWw9MSkgJT4lIHN1bW1hcml6ZSh0b3RhbGVjbT1zdW0odG90YWwpKSAgICMgdG90YWwgRUNNIGJ5IGV0aG5pY2l0eQ0KICANCiAgIyBjYXJyaWFnZSBieSBldGhuaWNpdHkNCiAgY2FycmlhZ2VfYnlldGhuIDwtIG1lcmdlKGdic3Bvc19ieWV0aG4sdG90YWxfYnlldGgsYnk9Imhlc19ldGhuaWNpdHkiKSAlPiUgbXV0YXRlKGNhcnJpYWdlX2J5dGhuPWdic3Bvcy90b3RhbGVjbSoxMDApICAgICMgY2FycmlhZ2UgZXN0aW1hdGUNCiAgDQogICMgYWxsIEdCUyBwb3MNCiAgZ2JzcG9zX2FsbCA8LSBvdXRkZiAlPiUgbXV0YXRlKGdic3Bvcz1pZmVsc2Uoc3BlY2llcz09Ikdyb3VwIEIgU3RyZXB0b2NvY2N1cyIsMSwwKSkgJT4lIHN1bW1hcml6ZShnYnNwb3M9c3VtKGdic3BvcykpICAgIyBhbGwgR0JTIHBvcyANCiAgDQogICMgdG90YWwgDQogIHRvdGFsIDwtIG91dGRmICU+JSBtdXRhdGUodG90YWw9MSkgJT4lIHN1bW1hcml6ZSh0b3RhbGVjbT1zdW0odG90YWwpKSAgICMgdG90YWwgRUNNIA0KICANCiAgIyBjYXJyaWFnZQ0KICBnYnNfY2FycmlhZ2UgPC0gZ2JzcG9zX2FsbC90b3RhbCoxMDAgICAgIyBjYXJyaWFnZSBlc3RpbWF0ZQ0KICBnYnNfY2FycmlhZ2UgPC0gZ2JzX2NhcnJpYWdlICU+JSByZW5hbWUoInRvdGFsX2NhcnJpYWdlIiA9IGdic3BvcykNCiAgDQogIGNhcnJpYWdlX2FsbCA8LSBnYnNwb3NfYWxsL2xlbmd0aCh1bmlxdWUob3V0ZGYkbmhzbm8pKSoxMDANCiAgDQogIGNhcnJpYWdlX3N1bW1hcnkgPC0gY2JpbmQoY2FycmlhZ2VfYnlldGhuLGdic19jYXJyaWFnZSkNCiAgY2FycmlhZ2Vfc3VtbWFyeSA8LSBtZXJnZShldGhuX3N1bW1hcnksY2FycmlhZ2Vfc3VtbWFyeSwgYnk9Imhlc19ldGhuaWNpdHkiKSANCiAgY2FycmlhZ2Vfc3VtbWFyeSA8LSBjYXJyaWFnZV9zdW1tYXJ5ICU+JSANCiAgICByb3d3aXNlKCkgJT4lDQogICAgbXV0YXRlKA0KICAgICAgY29uZiA9IGxpc3QoYmlub20uY29uZmludChnYnNwb3MsIHRvdGFsZWNtLCBtZXRob2RzID0gImV4YWN0IikpLA0KICAgICAgbG93ZXJfY2kgPSBjb25mW1sxLCAibG93ZXIiXV0gKiAxMDAsDQogICAgICB1cHBlcl9jaSA9IGNvbmZbWzEsICJ1cHBlciJdXSAqIDEwMA0KICAgICkgJT4lDQogICAgc2VsZWN0KC1jb25mKSAlPiUNCiAgICB1bmdyb3VwKCkNCiAgICANCiAgcmV0dXJuKGNhcnJpYWdlX3N1bW1hcnkpDQogIA0KfQ0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byByZXNoYXBlIGFuZCBjYWxjdWxhdGUgR0JTIGNhcnJpYWdlIGJ5IHNpdGUgYW5kIGV0aG5pY2l0eSAoJSB0b3RhbCBwYXRpZW50cyBpbiBzYW1wbGluZyB0aW1lZnJhbWUpDQpnYnMuY2Fyci5ieXNpdGUuYnlldGhuLmZ1biA8LSBmdW5jdGlvbih4KXsNCiAgDQogIG91dGRmIDwtIHggJT4lIG11dGF0ZSh6PTEpIA0KICAjICU+JSBmaWx0ZXIoaGVzX2V0aG5pY2l0eSE9IlVua25vd24iICYgaGVzX2V0aG5pY2l0eSE9Ik5vdCBsaW5rZWQiICYgaGVzX2V0aG5pY2l0eSE9Il9mcm9tTElNUyIpIA0KICANCiAgDQogIGV0aG5fc3VtbWFyeSA8LSBvdXRkZiAlPiUgZ3JvdXBfYnkoc2l0ZSxoZXNfZXRobmljaXR5KSAlPiUgc3VtbWFyaXplKHRvdGFsPXN1bSh6KSkgJT4lIG11dGF0ZShwY3Q9dG90YWwvc3VtKHRvdGFsKSoxMDApDQogIA0KICAjIEdCUyBwb3MgYnkgZXRobmljaXR5DQogIGdic3Bvc19ieWV0aG4gPC0gb3V0ZGYgJT4lIGdyb3VwX2J5KHNpdGUsaGVzX2V0aG5pY2l0eSkgJT4lIG11dGF0ZShnYnNwb3M9aWZlbHNlKHNwZWNpZXM9PSJHcm91cCBCIFN0cmVwdG9jb2NjdXMiLDEsMCkpICU+JSBzdW1tYXJpemUoZ2JzcG9zPXN1bShnYnNwb3MpKSAgICMgR0JTIHBvcyBieSBldGhuaWNpdHkNCiAgDQogICMgdG90YWwgRUNNIGJ5IGV0aG5pY2l0eQ0KICB0b3RhbF9ieWV0aCA8LSBvdXRkZiAlPiUgZ3JvdXBfYnkoc2l0ZSxoZXNfZXRobmljaXR5KSAlPiUgbXV0YXRlKHRvdGFsPTEpICU+JSBzdW1tYXJpemUodG90YWxlY209c3VtKHRvdGFsKSkgICAjIHRvdGFsIEVDTSBieSBldGhuaWNpdHkNCiAgDQogICMgY2FycmlhZ2UgYnkgZXRobmljaXR5DQogIGNhcnJpYWdlX2J5ZXRobiA8LSBtZXJnZShnYnNwb3NfYnlldGhuLHRvdGFsX2J5ZXRoLGJ5PSBjKCJzaXRlIiwiaGVzX2V0aG5pY2l0eSIpKSAlPiUgbXV0YXRlKGNhcnJpYWdlX2J5dGhuPWdic3Bvcy90b3RhbGVjbSoxMDApICAgICMgY2FycmlhZ2UgZXN0aW1hdGUNCiAgDQogICMgYWxsIEdCUyBwb3MNCiAgZ2JzcG9zX2FsbCA8LSBvdXRkZiAlPiUgbXV0YXRlKGdic3Bvcz1pZmVsc2Uoc3BlY2llcz09Ikdyb3VwIEIgU3RyZXB0b2NvY2N1cyIsMSwwKSkgJT4lIHN1bW1hcml6ZShnYnNwb3M9c3VtKGdic3BvcykpICAgIyBhbGwgR0JTIHBvcyANCiAgDQogICMgdG90YWwgDQogIHRvdGFsIDwtIG91dGRmICU+JSBtdXRhdGUodG90YWw9MSkgJT4lIHN1bW1hcml6ZSh0b3RhbGVjbT1zdW0odG90YWwpKSAgICMgdG90YWwgRUNNIA0KICANCiAgIyBjYXJyaWFnZQ0KICBnYnNfY2FycmlhZ2UgPC0gZ2JzcG9zX2FsbC90b3RhbCoxMDAgICAgIyBjYXJyaWFnZSBlc3RpbWF0ZQ0KICBnYnNfY2FycmlhZ2UgPC0gZ2JzX2NhcnJpYWdlICU+JSByZW5hbWUoInRvdGFsX2NhcnJpYWdlIiA9IGdic3BvcykNCiAgDQogIGNhcnJpYWdlX2FsbCA8LSBnYnNwb3NfYWxsL2xlbmd0aCh1bmlxdWUob3V0ZGYkbmhzbm8pKSoxMDANCiAgDQogIGNhcnJpYWdlX3N1bW1hcnkgPC0gY2JpbmQoY2FycmlhZ2VfYnlldGhuLGdic19jYXJyaWFnZSkNCiAgY2FycmlhZ2Vfc3VtbWFyeSA8LSBtZXJnZShldGhuX3N1bW1hcnksY2FycmlhZ2Vfc3VtbWFyeSwgYnk9Yygic2l0ZSIsImhlc19ldGhuaWNpdHkiKSkgDQogIGNhcnJpYWdlX3N1bW1hcnkgPC0gY2FycmlhZ2Vfc3VtbWFyeSAlPiUgDQogICAgcm93d2lzZSgpICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIGNvbmYgPSBsaXN0KGJpbm9tLmNvbmZpbnQoZ2JzcG9zLCB0b3RhbGVjbSwgbWV0aG9kcyA9ICJleGFjdCIpKSwNCiAgICAgIGxvd2VyX2NpID0gY29uZltbMSwgImxvd2VyIl1dICogMTAwLA0KICAgICAgdXBwZXJfY2kgPSBjb25mW1sxLCAidXBwZXIiXV0gKiAxMDANCiAgICApICU+JQ0KICAgIHNlbGVjdCgtY29uZikgJT4lDQogICAgdW5ncm91cCgpDQogIA0KICByZXR1cm4oY2FycmlhZ2Vfc3VtbWFyeSkNCiAgDQp9DQoNCg0KDQojIC8vLy8vLy8vLy8vLy8vLy8vDQojIHNwZWNpZnkgZnVuY3Rpb24gdG8gcmVzaGFwZSBhbmQgY2FsY3VsYXRlIEdCUyBjYXJyaWFnZSAoJSB0b3RhbCBwYXRpZW50cyBpbiBzYW1wbGluZyB0aW1lZnJhbWUpIGFtb25nIEFzaWFuIGV0aG5pYyBzdWJncm91cHMNCiMgSW5kaWFuLCBQYWtpc3RhbmkgYW5kIEJhbmdsYWRlc2hpLiANCmdicy5jYXJyLmJ5c3ViZXRobi5mdW4gPC0gZnVuY3Rpb24oeCl7DQogIA0KICBvdXRkZiA8LSB4ICU+JSBtdXRhdGUoej0xKSAlPiUgZmlsdGVyKEV0aG5pY19ncm91cD09IkluZGlhbiAoQXNpYW4gb3IgQXNpYW4gQnJpdGlzaCkiIHwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFdGhuaWNfZ3JvdXA9PSJCYW5nbGFkZXNoaSAoQXNpYW4gb3IgQXNpYW4gQnJpdGlzaCkiIHwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEV0aG5pY19ncm91cD09IlBha2lzdGFuaSAoQXNpYW4gb3IgQXNpYW4gQnJpdGlzaCkiKQ0KICANCiAgZXRobl9zdW1tYXJ5IDwtIG91dGRmICU+JSBncm91cF9ieShFdGhuaWNfZ3JvdXApICU+JSBzdW1tYXJpemUodG90YWw9c3VtKHopKSAlPiUgbXV0YXRlKHBjdD10b3RhbC9zdW0odG90YWwpKjEwMCkNCiAgDQogICMgR0JTIHBvcyBieSBldGhuaWNpdHkNCiAgZ2JzcG9zX2J5ZXRobiA8LSBvdXRkZiAlPiUgZ3JvdXBfYnkoRXRobmljX2dyb3VwKSAlPiUgbXV0YXRlKGdic3Bvcz1pZmVsc2Uoc3BlY2llcz09Ikdyb3VwIEIgU3RyZXB0b2NvY2N1cyIsMSwwKSkgJT4lIHN1bW1hcml6ZShnYnNwb3M9c3VtKGdic3BvcykpICAgIyBHQlMgcG9zIGJ5IGV0aG5pY2l0eQ0KICANCiAgIyB0b3RhbCBFQ00gYnkgZXRobmljaXR5DQogIHRvdGFsX2J5ZXRoIDwtIG91dGRmICU+JSBncm91cF9ieShFdGhuaWNfZ3JvdXApICU+JSBtdXRhdGUodG90YWw9MSkgJT4lIHN1bW1hcml6ZSh0b3RhbGVjbT1zdW0odG90YWwpKSAgICMgdG90YWwgRUNNIGJ5IGV0aG5pY2l0eQ0KICANCiAgIyBjYXJyaWFnZSBieSBldGhuaWNpdHkNCiAgY2FycmlhZ2VfYnlldGhuIDwtIG1lcmdlKGdic3Bvc19ieWV0aG4sdG90YWxfYnlldGgsYnk9IkV0aG5pY19ncm91cCIpICU+JSBtdXRhdGUoY2FycmlhZ2VfYnl0aG49Z2JzcG9zL3RvdGFsZWNtKjEwMCkgICAgIyBjYXJyaWFnZSBlc3RpbWF0ZQ0KICANCiAgIyBhbGwgR0JTIHBvcw0KICBnYnNwb3NfYWxsIDwtIG91dGRmICU+JSBtdXRhdGUoZ2JzcG9zPWlmZWxzZShzcGVjaWVzPT0iR3JvdXAgQiBTdHJlcHRvY29jY3VzIiwxLDApKSAlPiUgc3VtbWFyaXplKGdic3Bvcz1zdW0oZ2JzcG9zKSkgICAjIGFsbCBHQlMgcG9zIA0KICANCiAgIyB0b3RhbCANCiAgdG90YWwgPC0gb3V0ZGYgJT4lIG11dGF0ZSh0b3RhbD0xKSAlPiUgc3VtbWFyaXplKHRvdGFsZWNtPXN1bSh0b3RhbCkpICAgIyB0b3RhbCBFQ00gDQogIA0KICAjIGNhcnJpYWdlDQogIGdic19jYXJyaWFnZSA8LSBnYnNwb3NfYWxsL3RvdGFsKjEwMCAgICAjIGNhcnJpYWdlIGVzdGltYXRlDQogIGdic19jYXJyaWFnZSA8LSBnYnNfY2FycmlhZ2UgJT4lIHJlbmFtZSgidG90YWxfY2FycmlhZ2UiID0gZ2JzcG9zKQ0KICANCiAgY2FycmlhZ2VfYWxsIDwtIGdic3Bvc19hbGwvbGVuZ3RoKHVuaXF1ZShvdXRkZiRuaHNubykpKjEwMA0KICANCiAgY2FycmlhZ2Vfc3VtbWFyeSA8LSBjYmluZChjYXJyaWFnZV9ieWV0aG4sZ2JzX2NhcnJpYWdlKQ0KICBjYXJyaWFnZV9zdW1tYXJ5IDwtIG1lcmdlKGV0aG5fc3VtbWFyeSxjYXJyaWFnZV9zdW1tYXJ5LCBieT0iRXRobmljX2dyb3VwIikgDQogICMgJT4lIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIHJvdW5kLCAyKSkNCiAgDQogIHJldHVybihjYXJyaWFnZV9zdW1tYXJ5KQ0KICANCn0NCg0KDQojIC8vLy8vLy8vLy8vLy8vLy8vDQojIHNwZWNpZnkgZnVuY3Rpb24gdG8gcmVzaGFwZSBhbmQgY2FsY3VsYXRlIEdCUyBjYXJyaWFnZSAoJSB0b3RhbCBwYXRpZW50cyBpbiBzYW1wbGluZyB0aW1lZnJhbWUpIGZvciBhbGwgMTYgZXRobmljaXR5IGNhdGVnb3JpZXMgaW4gSEVTDQpnYnMuY2Fyci5ieWFsbHN1YmV0aG4uZnVuIDwtIGZ1bmN0aW9uKHgpew0KICANCiAgb3V0ZGYgPC0geCAlPiUgbXV0YXRlKHo9MSkgDQogICMgJT4lIGZpbHRlcihFdGhuaWNfZ3JvdXAhPSJOQSIgJiBFdGhuaWNfZ3JvdXAhPSI5OSBOb3Qga25vd24iICYgRXRobmljX2dyb3VwIT0iWiBOb3Qgc3RhdGVkIikNCiAgDQogIGV0aG5fc3VtbWFyeSA8LSBvdXRkZiAlPiUgZ3JvdXBfYnkoRXRobmljX2dyb3VwKSAlPiUgc3VtbWFyaXplKHRvdGFsPXN1bSh6KSkgJT4lIG11dGF0ZShwY3Q9dG90YWwvc3VtKHRvdGFsKSoxMDApDQogIA0KICAjIEdCUyBwb3MgYnkgZXRobmljaXR5DQogIGdic3Bvc19ieWV0aG4gPC0gb3V0ZGYgJT4lIGdyb3VwX2J5KEV0aG5pY19ncm91cCkgJT4lIG11dGF0ZShnYnNwb3M9aWZlbHNlKHNwZWNpZXM9PSJHcm91cCBCIFN0cmVwdG9jb2NjdXMiLDEsMCkpICU+JSBzdW1tYXJpemUoZ2JzcG9zPXN1bShnYnNwb3MpKSAgICMgR0JTIHBvcyBieSBldGhuaWNpdHkNCiAgDQogICMgdG90YWwgRUNNIGJ5IGV0aG5pY2l0eQ0KICB0b3RhbF9ieWV0aCA8LSBvdXRkZiAlPiUgZ3JvdXBfYnkoRXRobmljX2dyb3VwKSAlPiUgbXV0YXRlKHRvdGFsPTEpICU+JSBzdW1tYXJpemUodG90YWxlY209c3VtKHRvdGFsKSkgICAjIHRvdGFsIEVDTSBieSBldGhuaWNpdHkNCiAgDQogICMgY2FycmlhZ2UgYnkgZXRobmljaXR5DQogIGNhcnJpYWdlX2J5ZXRobiA8LSBtZXJnZShnYnNwb3NfYnlldGhuLHRvdGFsX2J5ZXRoLGJ5PSJFdGhuaWNfZ3JvdXAiKSAlPiUgbXV0YXRlKGNhcnJpYWdlX2J5dGhuPWdic3Bvcy90b3RhbGVjbSoxMDApICAgICMgY2FycmlhZ2UgZXN0aW1hdGUNCiAgDQogICMgYWxsIEdCUyBwb3MNCiAgZ2JzcG9zX2FsbCA8LSBvdXRkZiAlPiUgbXV0YXRlKGdic3Bvcz1pZmVsc2Uoc3BlY2llcz09Ikdyb3VwIEIgU3RyZXB0b2NvY2N1cyIsMSwwKSkgJT4lIHN1bW1hcml6ZShnYnNwb3M9c3VtKGdic3BvcykpICAgIyBhbGwgR0JTIHBvcyANCiAgDQogICMgdG90YWwgDQogIHRvdGFsIDwtIG91dGRmICU+JSBtdXRhdGUodG90YWw9MSkgJT4lIHN1bW1hcml6ZSh0b3RhbGVjbT1zdW0odG90YWwpKSAgICMgdG90YWwgRUNNIA0KICANCiAgIyBjYXJyaWFnZQ0KICBnYnNfY2FycmlhZ2UgPC0gZ2JzcG9zX2FsbC90b3RhbCoxMDAgICAgIyBjYXJyaWFnZSBlc3RpbWF0ZQ0KICBnYnNfY2FycmlhZ2UgPC0gZ2JzX2NhcnJpYWdlICU+JSByZW5hbWUoInRvdGFsX2NhcnJpYWdlIiA9IGdic3BvcykNCiAgDQogIGNhcnJpYWdlX2FsbCA8LSBnYnNwb3NfYWxsL2xlbmd0aCh1bmlxdWUob3V0ZGYkbmhzbm8pKSoxMDANCiAgDQogIGNhcnJpYWdlX3N1bW1hcnkgPC0gY2JpbmQoY2FycmlhZ2VfYnlldGhuLGdic19jYXJyaWFnZSkNCiAgY2FycmlhZ2Vfc3VtbWFyeSA8LSBtZXJnZShldGhuX3N1bW1hcnksY2FycmlhZ2Vfc3VtbWFyeSwgYnk9IkV0aG5pY19ncm91cCIpIA0KICAjICU+JSBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCByb3VuZCwgMikpDQogIA0KICByZXR1cm4oY2FycmlhZ2Vfc3VtbWFyeSkNCiAgDQp9DQoNCg0KIyAvLy8vLy8vLy8vLy8vLy8vLw0KIyBzcGVjaWZ5IGZ1bmN0aW9uIHRvIHJlc2hhcGUgYW5kIGNhbGN1bGF0ZSBHQlMgY2FycmlhZ2UgYnkgSU1EICglIHRvdGFsIHBhdGllbnRzIGluIHNhbXBsaW5nIHRpbWVmcmFtZSkNCmdicy5jYXJyLmJ5aW1kLmZ1biA8LSBmdW5jdGlvbih4KXsNCiAgDQogIG91dGRmIDwtIHggJT4lIG11dGF0ZSh6PTEpIA0KICAjIHBpdm90X3dpZGVyKCAgICAgICAgICAgICAgICAgICAgICAgIyBjdXJyZW50bHkgcGl2b3RzIHdpZGVyIGFzIHBhcnQgb2Ygb2JhaSBwcm9jZXNzDQogICMgICBuYW1lc19mcm9tID0gYWJ4LA0KICAjICAgdmFsdWVzX2Zyb20gPSBhc3RfcmVzdWx0KSANCiAgDQogIGltZF9zdW1tYXJ5IDwtIG91dGRmICU+JSBncm91cF9ieShpbWRfcXVpbnRpbGUpICU+JSBzdW1tYXJpemUodG90YWw9c3VtKHopKSAlPiUgbXV0YXRlKHBjdD10b3RhbC9zdW0odG90YWwpKjEwMCkNCiAgDQogICMgR0JTIHBvcyBieSBJTUQgcXVpbnRpbGUNCiAgZ2JzcG9zX2J5aW1kIDwtIG91dGRmICU+JSBncm91cF9ieShpbWRfcXVpbnRpbGUpICU+JSBtdXRhdGUoZ2JzcG9zPWlmZWxzZShzcGVjaWVzPT0iR3JvdXAgQiBTdHJlcHRvY29jY3VzIiwxLDApKSAlPiUgc3VtbWFyaXplKGdic3Bvcz1zdW0oZ2JzcG9zKSkgICAjIEdCUyBwb3MgYnkgSU1EDQogIA0KICAjIHRvdGFsIEVDTSBieSBJTUQgcXVpbnRpbGUNCiAgdG90YWxfYnlpbWQgPC0gb3V0ZGYgJT4lIGdyb3VwX2J5KGltZF9xdWludGlsZSkgJT4lIG11dGF0ZSh0b3RhbD0xKSAlPiUgc3VtbWFyaXplKHRvdGFsZWNtPXN1bSh0b3RhbCkpICAgIyB0b3RhbCBFQ00gYnkgSU1EIA0KICANCiAgIyBjYXJyaWFnZSBieSBldGhuaWNpdHkNCiAgY2FycmlhZ2VfYnlpbWQgPC0gbWVyZ2UoZ2JzcG9zX2J5aW1kLHRvdGFsX2J5aW1kLGJ5PSJpbWRfcXVpbnRpbGUiKSAlPiUgbXV0YXRlKGNhcnJpYWdlX2J5aW1kPWdic3Bvcy90b3RhbGVjbSoxMDApICAgICMgY2FycmlhZ2UgZXN0aW1hdGUNCiAgDQogICMgYWxsIEdCUyBwb3MNCiAgZ2JzcG9zX2FsbCA8LSBvdXRkZiAlPiUgbXV0YXRlKGdic3Bvcz1pZmVsc2Uoc3BlY2llcz09Ikdyb3VwIEIgU3RyZXB0b2NvY2N1cyIsMSwwKSkgJT4lIHN1bW1hcml6ZShnYnNwb3M9c3VtKGdic3BvcykpICAgIyBhbGwgR0JTIHBvcyANCiAgDQogICMgdG90YWwgDQogIHRvdGFsIDwtIG91dGRmICU+JSBtdXRhdGUodG90YWw9MSkgJT4lIHN1bW1hcml6ZSh0b3RhbGVjbT1zdW0odG90YWwpKSAgICMgdG90YWwgRUNNIA0KICANCiAgIyBjYXJyaWFnZQ0KICBnYnNfY2FycmlhZ2UgPC0gZ2JzcG9zX2FsbC90b3RhbCoxMDAgICAgIyBjYXJyaWFnZSBlc3RpbWF0ZQ0KICBnYnNfY2FycmlhZ2UgPC0gZ2JzX2NhcnJpYWdlICU+JSByZW5hbWUoInRvdGFsX2NhcnJpYWdlIiA9IGdic3BvcykNCiAgDQogIGNhcnJpYWdlX2FsbCA8LSBnYnNwb3NfYWxsL2xlbmd0aCh1bmlxdWUob3V0ZGYkbmhzbm8pKSoxMDANCiAgDQogIGNhcnJpYWdlX3N1bW1hcnkgPC0gY2JpbmQoY2FycmlhZ2VfYnlpbWQsZ2JzX2NhcnJpYWdlKQ0KICBjYXJyaWFnZV9zdW1tYXJ5IDwtIG1lcmdlKGltZF9zdW1tYXJ5LGNhcnJpYWdlX3N1bW1hcnksIGJ5PSJpbWRfcXVpbnRpbGUiKSAlPiUgbXV0YXRlKGltZF9xdWludGlsZSA9IGNhc2Vfd2hlbigNCiAgICBpcy5uYShpbWRfcXVpbnRpbGUpIH4gIk5vdCBsaW5rZWQiLA0KICAgIFRSVUUgfiBhcy5jaGFyYWN0ZXIoaW1kX3F1aW50aWxlKSkpDQogIA0KICBjYXJyaWFnZV9zdW1tYXJ5IDwtIGNhcnJpYWdlX3N1bW1hcnkgJT4lIA0KICAgIHJvd3dpc2UoKSAlPiUNCiAgICBtdXRhdGUoDQogICAgICBjb25mID0gbGlzdChiaW5vbS5jb25maW50KGdic3BvcywgdG90YWxlY20sIG1ldGhvZHMgPSAiZXhhY3QiKSksDQogICAgICBsb3dlcl9jaSA9IGNvbmZbWzEsICJsb3dlciJdXSAqIDEwMCwNCiAgICAgIHVwcGVyX2NpID0gY29uZltbMSwgInVwcGVyIl1dICogMTAwDQogICAgKSAlPiUNCiAgICBzZWxlY3QoLWNvbmYpICU+JQ0KICAgIHVuZ3JvdXAoKQ0KICANCiAgcmV0dXJuKGNhcnJpYWdlX3N1bW1hcnkpDQogIA0KfQ0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byByZXNoYXBlIGFuZCBjYWxjdWxhdGUgR0JTIGNhcnJpYWdlICglIHRvdGFsIHBhdGllbnRzIGluIHNhbXBsaW5nIHRpbWVmcmFtZSkNCmdicy5jYXJyLmJ5YWdlLmZ1biA8LSBmdW5jdGlvbih4KXsNCiAgDQogIG91dGRmIDwtIHggJT4lIG11dGF0ZSh6PTEsDQogICAgICAgICAgICAgICAgICAgICAgICBhZ2Vncm91cCA9IGN1dChhZ2UsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKDAsIDEwMCwgYnkgPSA1KSwgICMgU3BlY2lmeSB0aGUgYnJlYWtzIGZvciB0aGUgYWdlIGJhbmRzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByaWdodCA9IEZBTFNFLCAgIyBJbmNsdWRlIHRoZSBsZWZ0IGVuZHBvaW50LCBleGNsdWRlIHRoZSByaWdodA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gcGFzdGUoc2VxKDAsIDk1LCBieSA9IDUpLCBzZXEoNCwgOTksIGJ5ID0gNSksIHNlcCA9ICItIikpLA0KICAgICAgICAgICAgICAgICAgICAgICAgYWdlZ3JvdXAgPSBjYXNlX3doZW4oDQogICAgICAgICAgICAgICAgICAgICAgICAgIGFnZWdyb3VwICVpbiUgYygiNDAtNDQiLCI0NS00OSIpIH4gIjQwLTQ5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IGFnZWdyb3VwDQogICAgICAgICAgICAgICAgICAgICAgICApKSANCiAgIyBwaXZvdF93aWRlciggICAgICAgICAgICAgICAgICAgICAgICMgY3VycmVudGx5IHBpdm90cyB3aWRlciBhcyBwYXJ0IG9mIG9iYWkgcHJvY2Vzcw0KICAjICAgbmFtZXNfZnJvbSA9IGFieCwNCiAgIyAgIHZhbHVlc19mcm9tID0gYXN0X3Jlc3VsdCkgDQogIA0KICBhZ2Vfc3VtbWFyeSA8LSBvdXRkZiAlPiUgZ3JvdXBfYnkoYWdlZ3JvdXApICU+JSBzdW1tYXJpemUodG90YWw9c3VtKHopKSAlPiUgbXV0YXRlKHBjdD10b3RhbC9zdW0odG90YWwpKjEwMCkNCiAgDQogICMgR0JTIHBvcyBieSBldGhuaWNpdHkNCiAgZ2JzcG9zX2J5YWdlIDwtIG91dGRmICU+JSBncm91cF9ieShhZ2Vncm91cCkgJT4lIG11dGF0ZShnYnNwb3M9aWZlbHNlKHNwZWNpZXM9PSJHcm91cCBCIFN0cmVwdG9jb2NjdXMiLDEsMCkpICU+JSBzdW1tYXJpemUoZ2JzcG9zPXN1bShnYnNwb3MpKSAgICMgR0JTIHBvcyBieSBldGhuaWNpdHkNCiAgDQogICMgdG90YWwgRUNNIGJ5IGV0aG5pY2l0eQ0KICB0b3RhbF9ieWFnZSA8LSBvdXRkZiAlPiUgZ3JvdXBfYnkoYWdlZ3JvdXApICU+JSBtdXRhdGUodG90YWw9MSkgJT4lIHN1bW1hcml6ZSh0b3RhbGVjbT1zdW0odG90YWwpKSAgICMgdG90YWwgRUNNIGJ5IGV0aG5pY2l0eQ0KICANCiAgIyBjYXJyaWFnZSBieSBldGhuaWNpdHkNCiAgY2FycmlhZ2VfYnlhZ2UgPC0gbWVyZ2UoZ2JzcG9zX2J5YWdlLHRvdGFsX2J5YWdlLGJ5PSJhZ2Vncm91cCIpICU+JSBtdXRhdGUoY2FycmlhZ2VfYnlhZ2U9Z2JzcG9zL3RvdGFsZWNtKjEwMCkgICAgIyBjYXJyaWFnZSBlc3RpbWF0ZQ0KICANCiAgIyBhbGwgR0JTIHBvcw0KICBnYnNwb3NfYWxsIDwtIG91dGRmICU+JSBtdXRhdGUoZ2JzcG9zPWlmZWxzZShzcGVjaWVzPT0iR3JvdXAgQiBTdHJlcHRvY29jY3VzIiwxLDApKSAlPiUgc3VtbWFyaXplKGdic3Bvcz1zdW0oZ2JzcG9zKSkgICAjIGFsbCBHQlMgcG9zIA0KICANCiAgIyB0b3RhbCANCiAgdG90YWwgPC0gb3V0ZGYgJT4lIG11dGF0ZSh0b3RhbD0xKSAlPiUgc3VtbWFyaXplKHRvdGFsZWNtPXN1bSh0b3RhbCkpICAgIyB0b3RhbCBFQ00gDQogIA0KICAjIGNhcnJpYWdlDQogIGdic19jYXJyaWFnZSA8LSBnYnNwb3NfYWxsL3RvdGFsKjEwMCAgICAjIGNhcnJpYWdlIGVzdGltYXRlDQogIGdic19jYXJyaWFnZSA8LSBnYnNfY2FycmlhZ2UgJT4lIHJlbmFtZSgidG90YWxfY2FycmlhZ2UiID0gZ2JzcG9zKQ0KICANCiAgY2FycmlhZ2VfYWxsIDwtIGdic3Bvc19hbGwvbGVuZ3RoKHVuaXF1ZShvdXRkZiRuaHNubykpKjEwMA0KICANCiAgY2FycmlhZ2Vfc3VtbWFyeSA8LSBjYmluZChjYXJyaWFnZV9ieWFnZSxnYnNfY2FycmlhZ2UpDQogIGNhcnJpYWdlX3N1bW1hcnkgPC0gbWVyZ2UoYWdlX3N1bW1hcnksY2FycmlhZ2Vfc3VtbWFyeSwgYnk9ImFnZWdyb3VwIikgDQogIA0KICBjYXJyaWFnZV9zdW1tYXJ5IDwtIGNhcnJpYWdlX3N1bW1hcnkgJT4lIA0KICAgIHJvd3dpc2UoKSAlPiUNCiAgICBtdXRhdGUoDQogICAgICBjb25mID0gbGlzdChiaW5vbS5jb25maW50KGdic3BvcywgdG90YWxlY20sIG1ldGhvZHMgPSAiZXhhY3QiKSksDQogICAgICBsb3dlcl9jaSA9IGNvbmZbWzEsICJsb3dlciJdXSAqIDEwMCwNCiAgICAgIHVwcGVyX2NpID0gY29uZltbMSwgInVwcGVyIl1dICogMTAwDQogICAgKSAlPiUNCiAgICBzZWxlY3QoLWNvbmYpICU+JQ0KICAgIHVuZ3JvdXAoKQ0KICANCiAgcmV0dXJuKGNhcnJpYWdlX3N1bW1hcnkpDQogIA0KfQ0KDQoNCg0KIyAvLy8vLy8vLy8vLy8vLy8vLw0KIyBzcGVjaWZ5IGZ1bmN0aW9uIHRvIHJlc2hhcGUgYW5kIGNhbGN1bGF0ZSBHQlMgY2FycmlhZ2UgKCUgdG90YWwgcGF0aWVudHMgaW4gc2FtcGxpbmcgdGltZWZyYW1lKQ0KZ2JzLmNhcnIuYnlzaXRlLmZ1biA8LSBmdW5jdGlvbih4KXsNCiAgDQogIG91dGRmIDwtIHggJT4lIG11dGF0ZSh6PTEpDQogIA0KICAjIHBpdm90X3dpZGVyKCAgICAgICAgICAgICAgICAgICAgICAgIyBjdXJyZW50bHkgcGl2b3RzIHdpZGVyIGFzIHBhcnQgb2Ygb2JhaSBwcm9jZXNzDQogICMgICBuYW1lc19mcm9tID0gYWJ4LA0KICAjICAgdmFsdWVzX2Zyb20gPSBhc3RfcmVzdWx0KSANCiAgDQogIHNpdGVfc3VtbWFyeSA8LSBvdXRkZiAlPiUgZ3JvdXBfYnkoc2l0ZSkgJT4lIHN1bW1hcml6ZSh0b3RhbD1zdW0oeikpICU+JSBtdXRhdGUocGN0PXRvdGFsL3N1bSh0b3RhbCkqMTAwKQ0KICANCiAgIyBHQlMgcG9zIGJ5IGV0aG5pY2l0eQ0KICBnYnNwb3NfYnlzaXRlIDwtIG91dGRmICU+JSBncm91cF9ieShzaXRlKSAlPiUgbXV0YXRlKGdic3Bvcz1pZmVsc2Uoc3BlY2llcz09Ikdyb3VwIEIgU3RyZXB0b2NvY2N1cyIsMSwwKSkgJT4lIHN1bW1hcml6ZShnYnNwb3M9c3VtKGdic3BvcykpICAgIyBHQlMgcG9zIGJ5IGV0aG5pY2l0eQ0KICANCiAgIyB0b3RhbCBFQ00gYnkgZXRobmljaXR5DQogIHRvdGFsX2J5c2l0ZSA8LSBvdXRkZiAlPiUgZ3JvdXBfYnkoc2l0ZSkgJT4lIG11dGF0ZSh0b3RhbD0xKSAlPiUgc3VtbWFyaXplKHRvdGFsZWNtPXN1bSh0b3RhbCkpICAgIyB0b3RhbCBFQ00gYnkgZXRobmljaXR5DQogIA0KICAjIGNhcnJpYWdlIGJ5IGV0aG5pY2l0eQ0KICBjYXJyaWFnZV9ieXNpdGUgPC0gbWVyZ2UoZ2JzcG9zX2J5c2l0ZSx0b3RhbF9ieXNpdGUsYnk9InNpdGUiKSAlPiUgbXV0YXRlKGNhcnJpYWdlX2J5c2l0ZT1nYnNwb3MvdG90YWxlY20qMTAwKSAgICAjIGNhcnJpYWdlIGVzdGltYXRlDQogIA0KICAjIGFsbCBHQlMgcG9zDQogIGdic3Bvc19hbGwgPC0gb3V0ZGYgJT4lIG11dGF0ZShnYnNwb3M9aWZlbHNlKHNwZWNpZXM9PSJHcm91cCBCIFN0cmVwdG9jb2NjdXMiLDEsMCkpICU+JSBzdW1tYXJpemUoZ2JzcG9zPXN1bShnYnNwb3MpKSAgICMgYWxsIEdCUyBwb3MgDQogIA0KICAjIHRvdGFsIA0KICB0b3RhbCA8LSBvdXRkZiAlPiUgbXV0YXRlKHRvdGFsPTEpICU+JSBzdW1tYXJpemUodG90YWxlY209c3VtKHRvdGFsKSkgICAjIHRvdGFsIEVDTSANCiAgDQogICMgY2FycmlhZ2UNCiAgZ2JzX2NhcnJpYWdlIDwtIGdic3Bvc19hbGwvdG90YWwqMTAwICAgICMgY2FycmlhZ2UgZXN0aW1hdGUNCiAgZ2JzX2NhcnJpYWdlIDwtIGdic19jYXJyaWFnZSAlPiUgcmVuYW1lKCJ0b3RhbF9jYXJyaWFnZSIgPSBnYnNwb3MpDQogIA0KICBjYXJyaWFnZV9hbGwgPC0gZ2JzcG9zX2FsbC9sZW5ndGgodW5pcXVlKG91dGRmJG5oc25vKSkqMTAwDQogIA0KICBjYXJyaWFnZV9zdW1tYXJ5IDwtIGNiaW5kKGNhcnJpYWdlX2J5c2l0ZSxnYnNfY2FycmlhZ2UpDQogIGNhcnJpYWdlX3N1bW1hcnkgPC0gbWVyZ2Uoc2l0ZV9zdW1tYXJ5LGNhcnJpYWdlX3N1bW1hcnksIGJ5PSJzaXRlIikgDQogIA0KICBjYXJyaWFnZV9zdW1tYXJ5IDwtIGNhcnJpYWdlX3N1bW1hcnkgJT4lIA0KICAgIHJvd3dpc2UoKSAlPiUNCiAgICBtdXRhdGUoDQogICAgICBjb25mID0gbGlzdChiaW5vbS5jb25maW50KGdic3BvcywgdG90YWxlY20sIG1ldGhvZHMgPSAiZXhhY3QiKSksDQogICAgICBsb3dlcl9jaSA9IGNvbmZbWzEsICJsb3dlciJdXSAqIDEwMCwNCiAgICAgIHVwcGVyX2NpID0gY29uZltbMSwgInVwcGVyIl1dICogMTAwDQogICAgKSAlPiUNCiAgICBzZWxlY3QoLWNvbmYpICU+JQ0KICAgIHVuZ3JvdXAoKQ0KICANCiAgcmV0dXJuKGNhcnJpYWdlX3N1bW1hcnkpDQogIA0KfQ0KDQoNCg0KIyAvLy8vLy8vLy8vLy8vLy8vLw0KIyBzcGVjaWZ5IGZ1bmN0aW9uIHRvIHJlc2hhcGUgYW5kIGNhbGN1bGF0ZSBHQlMgY2FycmlhZ2UgKCUgdG90YWwgcGF0aWVudHMpIGJ5IHllYXINCmdicy5jYXJyLmJ5eWVhci5mdW4gPC0gZnVuY3Rpb24oeCl7DQogIA0KICBvdXRkZiA8LSB4ICU+JSBtdXRhdGUoej0xKQ0KICANCiAgZXRobl9zdW1tYXJ5IDwtIG91dGRmICU+JSBncm91cF9ieSh5ZWFyKSAlPiUgc3VtbWFyaXplKHRvdGFsPXN1bSh6KSkgJT4lIG11dGF0ZShwY3Q9dG90YWwvc3VtKHRvdGFsKSoxMDApDQogIA0KICAjIEdCUyBwb3MgYnkgZXRobmljaXR5DQogIGdic3Bvc19ieXllYXIgPC0gb3V0ZGYgJT4lIGdyb3VwX2J5KHllYXIpICU+JSBtdXRhdGUoZ2JzcG9zPWlmZWxzZShzcGVjaWVzPT0iR3JvdXAgQiBTdHJlcHRvY29jY3VzIiwxLDApKSAlPiUgc3VtbWFyaXplKGdic3Bvcz1zdW0oZ2JzcG9zKSkgICAjIEdCUyBwb3MgYnkgZXRobmljaXR5DQogIA0KICAjIHRvdGFsIEVDTSBieSBldGhuaWNpdHkNCiAgdG90YWxfYnl5ZWFyIDwtIG91dGRmICU+JSBncm91cF9ieSh5ZWFyKSAlPiUgbXV0YXRlKHRvdGFsPTEpICU+JSBzdW1tYXJpemUodG90YWxlY209c3VtKHRvdGFsKSkgICAjIHRvdGFsIEVDTSBieSBldGhuaWNpdHkNCiAgDQogICMgY2FycmlhZ2UgYnkgZXRobmljaXR5DQogIGNhcnJpYWdlX2J5eWVhciA8LSBtZXJnZShnYnNwb3NfYnl5ZWFyLHRvdGFsX2J5eWVhcixieT0ieWVhciIpICU+JSBtdXRhdGUoY2FycmlhZ2VfYnl5ZWFyPWdic3Bvcy90b3RhbGVjbSoxMDApICAgICMgY2FycmlhZ2UgZXN0aW1hdGUNCiAgDQogICMgYWxsIEdCUyBwb3MNCiAgZ2JzcG9zX2FsbCA8LSBvdXRkZiAlPiUgbXV0YXRlKGdic3Bvcz1pZmVsc2Uoc3BlY2llcz09Ikdyb3VwIEIgU3RyZXB0b2NvY2N1cyIsMSwwKSkgJT4lIHN1bW1hcml6ZShnYnNwb3M9c3VtKGdic3BvcykpICAgIyBhbGwgR0JTIHBvcyANCiAgDQogICMgdG90YWwgDQogIHRvdGFsIDwtIG91dGRmICU+JSBtdXRhdGUodG90YWw9MSkgJT4lIHN1bW1hcml6ZSh0b3RhbGVjbT1zdW0odG90YWwpKSAgICMgdG90YWwgRUNNIA0KICANCiAgIyBjYXJyaWFnZQ0KICBnYnNfY2FycmlhZ2UgPC0gZ2JzcG9zX2FsbC90b3RhbCoxMDAgICAgIyBjYXJyaWFnZSBlc3RpbWF0ZQ0KICBnYnNfY2FycmlhZ2UgPC0gZ2JzX2NhcnJpYWdlICU+JSByZW5hbWUoInRvdGFsX2NhcnJpYWdlIiA9IGdic3BvcykNCiAgDQogIGNhcnJpYWdlX2FsbCA8LSBnYnNwb3NfYWxsL2xlbmd0aCh1bmlxdWUob3V0ZGYkbmhzbm8pKSoxMDANCiAgDQogIGNhcnJpYWdlX3N1bW1hcnkgPC0gY2JpbmQoY2FycmlhZ2VfYnl5ZWFyLGdic19jYXJyaWFnZSkNCiAgY2FycmlhZ2Vfc3VtbWFyeSA8LSBtZXJnZShldGhuX3N1bW1hcnksY2FycmlhZ2Vfc3VtbWFyeSwgYnk9InllYXIiKSANCiAgIyAlPiUgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgcm91bmQsIDIpKQ0KICANCiAgcmV0dXJuKGNhcnJpYWdlX3N1bW1hcnkpDQogIA0KfQ0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byByZXNoYXBlIGFuZCBjYWxjdWxhdGUgR0JTIGNhcnJpYWdlICglIHRvdGFsIHBhdGllbnRzKSBieSBtb250aA0KZ2JzLmNhcnIuYnl5bS5mdW4gPC0gZnVuY3Rpb24oeCl7DQogIA0KICBvdXRkZiA8LSB4ICU+JSBtdXRhdGUoej0xKQ0KICANCiAgZXRobl9zdW1tYXJ5IDwtIG91dGRmICU+JSBncm91cF9ieSh5bSkgJT4lIHN1bW1hcml6ZSh0b3RhbD1zdW0oeikpICU+JSBtdXRhdGUocGN0PXRvdGFsL3N1bSh0b3RhbCkqMTAwKQ0KICANCiAgIyBHQlMgcG9zIGJ5IGV0aG5pY2l0eQ0KICBnYnNwb3NfYnl5bSA8LSBvdXRkZiAlPiUgZ3JvdXBfYnkoeW0pICU+JSBtdXRhdGUoZ2JzcG9zPWlmZWxzZShzcGVjaWVzPT0iR3JvdXAgQiBTdHJlcHRvY29jY3VzIiwxLDApKSAlPiUgc3VtbWFyaXplKGdic3Bvcz1zdW0oZ2JzcG9zKSkgICAjIEdCUyBwb3MgYnkgZXRobmljaXR5DQogIA0KICAjIHRvdGFsIEVDTSBieSBldGhuaWNpdHkNCiAgdG90YWxfYnl5bSA8LSBvdXRkZiAlPiUgZ3JvdXBfYnkoeW0pICU+JSBtdXRhdGUodG90YWw9MSkgJT4lIHN1bW1hcml6ZSh0b3RhbGVjbT1zdW0odG90YWwpKSAgICMgdG90YWwgRUNNIGJ5IGV0aG5pY2l0eQ0KICANCiAgIyBjYXJyaWFnZSBieSBldGhuaWNpdHkNCiAgY2FycmlhZ2VfYnl5bSA8LSBtZXJnZShnYnNwb3NfYnl5bSx0b3RhbF9ieXltLGJ5PSJ5bSIpICU+JSBtdXRhdGUoY2FycmlhZ2VfYnl5bT1nYnNwb3MvdG90YWxlY20qMTAwKSAgICAjIGNhcnJpYWdlIGVzdGltYXRlDQogIA0KICAjIGFsbCBHQlMgcG9zDQogIGdic3Bvc19hbGwgPC0gb3V0ZGYgJT4lIG11dGF0ZShnYnNwb3M9aWZlbHNlKHNwZWNpZXM9PSJHcm91cCBCIFN0cmVwdG9jb2NjdXMiLDEsMCkpICU+JSBzdW1tYXJpemUoZ2JzcG9zPXN1bShnYnNwb3MpKSAgICMgYWxsIEdCUyBwb3MgDQogIA0KICAjIHRvdGFsIA0KICB0b3RhbCA8LSBvdXRkZiAlPiUgbXV0YXRlKHRvdGFsPTEpICU+JSBzdW1tYXJpemUodG90YWxlY209c3VtKHRvdGFsKSkgICAjIHRvdGFsIEVDTSANCiAgDQogICMgY2FycmlhZ2UNCiAgZ2JzX2NhcnJpYWdlIDwtIGdic3Bvc19hbGwvdG90YWwqMTAwICAgICMgY2FycmlhZ2UgZXN0aW1hdGUNCiAgZ2JzX2NhcnJpYWdlIDwtIGdic19jYXJyaWFnZSAlPiUgcmVuYW1lKCJ0b3RhbF9jYXJyaWFnZSIgPSBnYnNwb3MpDQogIA0KICBjYXJyaWFnZV9hbGwgPC0gZ2JzcG9zX2FsbC9sZW5ndGgodW5pcXVlKG91dGRmJG5oc25vKSkqMTAwDQogIA0KICBjYXJyaWFnZV9zdW1tYXJ5IDwtIGNiaW5kKGNhcnJpYWdlX2J5eW0sZ2JzX2NhcnJpYWdlKQ0KICBjYXJyaWFnZV9zdW1tYXJ5IDwtIG1lcmdlKGV0aG5fc3VtbWFyeSxjYXJyaWFnZV9zdW1tYXJ5LCBieT0ieW0iKSANCiAgIyAlPiUgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgcm91bmQsIDIpKQ0KICANCiAgcmV0dXJuKGNhcnJpYWdlX3N1bW1hcnkpDQogIA0KfQ0KDQoNCiMgLy8vLy8vLy8vLy8vLy0tLS0tLS0tLS0tLSBMSU1TIExJTkVMSVNUIFBST0NFU1NJTkcgQU5EIEFNUiBSRVNVTFQgR0VORVJBVElPTiAtLS0tLS0tLS0tLS0gLy8vLy8vLy8vLy8vLy8vLy8NCg0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSB1bWJyZWxsYSBRQyBmdW5jdGlvbiANCnFjLnVtYnJlbGxhLmZ1biA8LSBmdW5jdGlvbih4LHN1bW1hcnk9bGlzdCgpKXsNCiAgeCRzbiA8LSBzZXFfbGVuKG5yb3coeCkpDQogIHgkcWMgPC0gc2dzcy5xYy5mdW4oeCRob3Nwbm8sIHgkc3BlY25vLCB4JGZvcmVuYW1lLCB4JHN1cm5hbWUpDQogIHgkcWNbd2hpY2goIXgkcWMgJWluJSBjKDAsIE5BKSldIDwtIDENCiAgc3VtbWFyeSRxY19zYW1wbGVzIDwtIHRhYmxlKHgkcWMsIHgkbW9kdWxlKQ0KICBjYXQocGFzdGUwKCJSZW1vdmVkICIsc3VtKHgkcWMpICwgIiBxdWFsaXR5IGNvbnRyb2wgc2FtcGxlc1xuXG4iKSkNCiAgeCA8LSB4W3gkcWM9PTAsXQ0KfQ0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byBRQyBkYXRhIChmcm9tIG9iYWk6OnNnc3NfcWNfcmVjb3JkcyBmdW4pDQpzZ3NzLnFjLmZ1biA8LSBmdW5jdGlvbiAoaG9zcG5vLCBzcGVjbm8sIGZvcmVuYW1lLCBzdXJuYW1lKSANCnsNCiAgaG9zcG5vIDwtIGlmZWxzZShpcy5uYShob3Nwbm8pLCAiIiwgdG91cHBlcihhcy5jaGFyYWN0ZXIoaG9zcG5vKSkpDQogIHNwZWNubyA8LSBpZmVsc2UoaXMubmEoc3BlY25vKSwgIiIsIHRvdXBwZXIoYXMuY2hhcmFjdGVyKHNwZWNubykpKQ0KICBmb3JlbmFtZSA8LSBpZmVsc2UoaXMubmEoZm9yZW5hbWUpLCAiIiwgdG91cHBlcihhcy5jaGFyYWN0ZXIoZm9yZW5hbWUpKSkNCiAgc3VybmFtZSA8LSBpZmVsc2UoaXMubmEoc3VybmFtZSksICIiLCB0b3VwcGVyKGFzLmNoYXJhY3RlcihzdXJuYW1lKSkpDQogIGhvc3BubyA8LSBzdHJpbmdyOjpzdHJfdHJpbShob3Nwbm8pDQogIGhvc3BubyA8LSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwoaG9zcG5vLCAiXjAqMCIsICIiKQ0KICBxYyA8LSBpZmVsc2Uoc3RyaW5ncjo6c3RyX2RldGVjdChob3Nwbm8sICJTRU5TfE5FUUFTfE5FUVNBfF5RQ3xRVUFMSVRZfElRQXxESVNUfFNQRUN8XlRCVHxMSVFBU3xURVNULTEiKSB8IA0KICAgICAgICAgICAgICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KHNwZWNubywgIlNFTlN8TkVRQVN8TkVRU0F8UUN8UVVBTElUWXxeSVFBfF5ESVNUfFNQRUMiKSB8IA0KICAgICAgICAgICAgICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KHN1cm5hbWUsICJeU0VOU3xORVFBU3xORVFTQXxRQ3xJUUF8UVVBTElUWXxBTlRJTUlDUk9CSUFMIFNVU0NFUFRBQklMSVRZfEFOVElGVU5HQUwgU1VTQ0VQVElCSUxJVFl8SU5URVJOQUwgUUN8RkVCVUFSWXxJUUFNSUMyMDI0IikgfCANCiAgICAgICAgICAgICAgICAgc3RyaW5ncjo6c3RyX2RldGVjdChmb3JlbmFtZSwgIl5TRU5TfE5FUUFTfE5FUVNBfElRQXxGQUVDRVN8UVVBTElUWXxBTlRJTUlDUk9CSUFMIFNVU0NFUFRBQklMSVRZfEFOVElGVU5HQUwgU1VTQ0VQVElCSUxJVFl8QU5USU1JQ1JPQklBTHxCTE9PRCBDVUxUVVJFfERJQUdOT1NUSUNTIiksIA0KICAgICAgICAgICAgICAgMSwgMCkNCiAgcWMgPC0gaWZlbHNlKGZvcmVuYW1lICVpbiUgdG91cHBlcihtb250aC5uYW1lKSwxLDApDQogIA0KICByZXR1cm4ocWMpDQp9DQoNCg0KIyAvLy8vLy8vLy8vLy8vLy8vLw0KIyBzcGVjaWZ5IGN1c3RvbWlzZWQgb2JhaSBzZ3NzX2luZmVjdGlvbnMgZnVuY3Rpb24gZm9yIExJTVMgZGF0YSBkZWR1cGxpY2F0aW9uIGFuZCBBTVIgcmVzdWx0cyBnZW5lcmF0aW9uDQpvYmFpLmRlZHVwLmZ1biA8LSBmdW5jdGlvbihhbGxzaXRlcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmthZ2Vfc3RyYXRlZ3kgPSAiZGVmYXVsdCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBlcGlzb2RlX2xlbmd0aD0yNzQsICAgIyBlcXVpdmFsZW50IG9mIDkgbW9udGhzIHRvIGNhcHR1cmUgc2Vjb25kIHByZWduYW5jaWVzIHdpdGhpbiB0aW1lIGZyYW1lIG9mIEVDTSB0ZXN0aW5nIGF0IGEgZ2l2ZW4gc2l0ZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjdXJyZW5jZV9sZW5ndGggPSBlcGlzb2RlX2xlbmd0aCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGVwaXNvZGVfdHlwZSA9ICJmaXhlZCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBkZWR1cF9tb2R1bGVfcHJlZmVyZW5jZSA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBhYng9VFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFieF9wcmVmZXJlbmNlID0gInNldmVyaXR5X2FsbCIpew0KICANCiAgYWxsc2l0ZXMkbXJrIDwtIGRpeWFyOjpjb21iaSgNCiAgICBhbGxzaXRlcyRzcGVjaWVzLA0KICAgIGFsbHNpdGVzJG5oc25vLA0KICAgIGFsbHNpdGVzJGhvc3BubywNCiAgICBhbGxzaXRlcyRkb2IsDQogICAgYWxsc2l0ZXMkc3BlY2RhdGUsDQogICAgYWxsc2l0ZXMkc2V4LA0KICAgIGFsbHNpdGVzJHNpdGUsDQogICAgYWxsc2l0ZXMkc3VybmFtZSwNCiAgICBhbGxzaXRlcyRmb3JlbmFtZSwNCiAgICBhbGxzaXRlcyRzcGVjbm8pDQogIA0KICBkZWR1cF9sZXZlbCA9IGMoInBpZHMiLCAic3BlY2llcyIpDQogIGFieD1UUlVFDQogIA0KICBpZigiTElNUyIgJWluJSBkZWR1cF9sZXZlbCkgYWxsc2l0ZXMkbXJrIDwtIGRpeWFyOjpjb21iaShhbGxzaXRlcyRtcmssIGFsbHNpdGVzJG1vZHVsZSkNCiAgaWYoIm1vZHVsZSIgJWluJSBkZWR1cF9sZXZlbCkgYWxsc2l0ZXMkbXJrIDwtIGRpeWFyOjpjb21iaShhbGxzaXRlcyRtcmssIGFsbHNpdGVzJG1vZHVsZSkNCiAgaWYoIWlzRkFMU0UoYWJ4KSkgYWxsc2l0ZXMkbXJrIDwtIGRpeWFyOjpjb21iaShhbGxzaXRlcyRtcmssIGFsbHNpdGVzJGFieCwgYWxsc2l0ZXMkYXN0X3Jlc3VsdCkNCiAgDQogIHN1bW1hcnk9bGlzdCgpDQogIA0KICBzYW1lX2RheV9kdXBzIDwtIGR1cGxpY2F0ZWQoYWxsc2l0ZXMkbXJrKQ0KICBzdW1tYXJ5JHNhbWVfZGF5X2R1cHMgPC0gbGVuZ3RoKHNhbWVfZGF5X2R1cHNbc2FtZV9kYXlfZHVwc10pDQogIA0KICBhbGxzaXRlcyA8LSBhbGxzaXRlc1shc2FtZV9kYXlfZHVwcyxdDQogIGFsbHNpdGVzJG1yayA8LSBOVUxMDQogIGNhdChzdW1tYXJ5JHNhbWVfZGF5X2R1cHMsInNhbWUtZGF5IGRlcHVsaWNhdGVzIHJlbW92ZWRcblxuIikNCiAgDQogIA0KICBjYXQoIkNyZWF0aW5nIGEgcGF0aWVudCBpZGVudGlmaWVyOiBgIiwNCiAgICAgIGxpbmthZ2Vfc3RyYXRlZ3ksICJgIG1ldGhvZFxuIikNCiAgIyBNYWtlIGNvbmZpZ3VyYWJsZQ0KICAjIEdlbmVyYXRpbmcgYSBwYXRpZW50IGlkZW50aWZpZXINCiAgd3JfbmhzIDwtIGxhcHBseSgwOjksIGZ1bmN0aW9uKHgpew0KICAgIHBhc3RlMChyZXAoeCwgMTApLCBjb2xsYXBzZSA9ICIiKQ0KICB9KQ0KICANCiAgZGYgPC0gYWxsc2l0ZXMNCiAgDQogIGlmKGxpbmthZ2Vfc3RyYXRlZ3kgPT0gImRlZmF1bHQiKXsNCiAgICBkZiRzb3VuZGV4IDwtIHN0cmluZ2Rpc3Q6OnBob25ldGljKGRmJHN1cm5hbWUpDQogICAgIyBJbmNsdXNpb24gY3JpdGVyaWENCiAgICB3cl9uaHMgPC0gYXMuY2hhcmFjdGVyKHdyX25ocykNCiAgICBkZiRjcmlfMSA8LSBkZiRjcmlfMiA8LSBkZiRjcmlfMyA8LSBOQQ0KICAgIGxnayA8LSB3aGljaCghKGRmJG5oc25vICVpbiUgYygiLiIsIiIsd3JfbmhzLCIwIiwiMTIzNDU2Nzg5MCIsIjk4NzY1NDMyMTAiLE5BKSB8IG5jaGFyKGRmJG5oc25vKSAhPSAxMCkpDQogICAgIyBDUjEgLSBWYWxpZCBOSFMgbm8NCiAgICBkZiRjcmlfMVtsZ2tdIDwtIGRmJG5oc25vW2xna10NCiAgICBsZ2sgPC0gd2hpY2goIShkZiRob3NubyAlaW4lIGMoIiIsIk5PIFBBVElFTlQgSUQiLCJVTktOT1dOIiwiTk8gUkVGIEdJVkVOIiwgIm5rIiwgTkEpIHwgZGYkc291bmRleCAlaW4lIGMoIiIsIE5BKSkpDQogICAgIyBDUjIgLSBWYWxpZCBIT1Mgbm8gYW5kIHN1cm5hbWUgU291bmRleA0KICAgIGRmJGNyaV8yW2xna10gPC0gZGl5YXI6OmNvbWJpKGRmJGhvc3Bub1tsZ2tdLCBkZiRzb3VuZGV4W2xna10pDQogICAgbGdrIDwtIHdoaWNoKCEoZGYkc3BlY25vICVpbiUgYygiIiwiTk8gUEFUSUVOVCBJRCIsIlVOS05PV04iLCJOTyBSRUYgR0lWRU4iLCAibmsiLCBOQSkgfCBkZiRzb3VuZGV4ICVpbiUgYygiIiwgTkEpIHwgZGYkc2V4ICVpbiUgYygiVSIsIlVua25vd24iLCBOQSkgfCBkZiRkb2IgJWluJSBjKCIiLCBOQSkpKQ0KICAgICMgQ1IzIC0gVmFsaWQgU3BlY2ltZW4gbnVtYmVyLCBTb3VuZGV4IE5IUyBhbmQgc2V4DQogICAgZGYkY3JpXzNbbGdrXSA8LSBkaXlhcjo6Y29tYmkoZGYkc3BlY25vW2xna10sIGRmJHNvdW5kZXhbbGdrXSwgZGYkc2V4W2xna10pDQogICAgIyBBTkQgKFZhbGlkIERETU0gb3IgRERZWSBvciBNTVlZKQ0KICAgIGRmJGNyaV9wdDEgPC0gZGYkY3JpX3B0MiA8LSBkZiRjcmlfcHQzIDwtIE5BDQogICAgbGdrIDwtIHdoaWNoKCEoZGYkZG9iICVpbiUgYygiIiwgTkEpKSkNCiAgICBkZiRjcmlfcHQxW2xna10gPSBkaXlhcjo6Y29tYmkoc3Vic3RyKGRmJGRvYltsZ2tdLDEsNCksIHN1YnN0cihkZiRkb2JbbGdrXSw2LDcpKQ0KICAgIGRmJGNyaV9wdDJbbGdrXSA9IGRpeWFyOjpjb21iaShzdWJzdHIoZGYkZG9iW2xna10sMSw0KSwgc3Vic3RyKGRmJGRvYltsZ2tdLDksMTApKQ0KICAgIGRmJGNyaV9wdDNbbGdrXSA9IGRpeWFyOjpjb21iaShzdWJzdHIoZGYkZG9iW2xna10sOSwxMCksIHN1YnN0cihkZiRkb2JbbGdrXSw2LDcpKQ0KICAgIGxpbmtlZF9zX2NyaXRlcmlhIDwtIGxpc3QoY3IzID0gZGl5YXI6OnN1Yl9jcml0ZXJpYShkZiRjcmlfcHQxLCBkZiRjcmlfcHQyLCBkZiRjcmlfcHQzKSkNCiAgICANCiAgICAjIE9yZGVyOyBDUjEgdG8gQ1IzDQogICAgY3JpdGVyaWEgPC0gbGlzdChkZiRjcmlfMSwgZGYkY3JpXzIsIGRmJGNyaV8zKQ0KICAgIGRmJHNvdW5kZXggPC0gTlVMTA0KICB9ZWxzZSBpZihsaW5rYWdlX3N0cmF0ZWd5ID09ICJ1aWRfZ29sZCIpew0KICAgICMgSW5jbHVzaW9uIGNyaXRlcmlhDQogICAgd3JfbmhzIDwtIGFzLmNoYXJhY3Rlcih3cl9uaHMpDQogICAgZGYkY3JpXzEgPC0gZGYkY3JpXzIgPC0gZGYkY3JpXzMgPC0gTkENCiAgICBsZ2sgPC0gd2hpY2goIShkZiRuaHNubyAlaW4lIGMoIi4iLCIiLHdyX25ocywiMCIsIjEyMzQ1Njc4OTAiLCI5ODc2NTQzMjEwIixOQSkgfA0KICAgICAgICAgICAgICAgICAgICAgZGYkaG9zbm8gJWluJSBjKCIiLCJOTyBQQVRJRU5UIElEIiwiVU5LTk9XTiIsIk5PIFJFRiBHSVZFTiIsICJuayIsIE5BKSB8DQogICAgICAgICAgICAgICAgICAgICBkZiRkb2IgJWluJSBjKCIiLCAiMTkwMC0wMS0wMSIsIE5BKSB8DQogICAgICAgICAgICAgICAgICAgICBkZiRzdXJuYW1lICVpbiUgYygiIiwiTk8gUEFUSUVOVCBJRCIsIlVOS05PV04iLCJOTyBSRUYgR0lWRU4iLCAibmsiLCBOQSkgfA0KICAgICAgICAgICAgICAgICAgICAgZGYkaG9zbm8gJWluJSBjKCIiLCJOTyBQQVRJRU5UIElEIiwiVU5LTk9XTiIsIk5PIFJFRiBHSVZFTiIsICJuayIsIE5BKSkpDQogICAgIyBDUjEgLSBWYWxpZCBOSFMgbm8sIERPQiwgZm9yZW5hbWUgYW5kIHN1cm5hbWUNCiAgICBkZiRjcmlfMVtsZ2tdIDwtIGRpeWFyOjpjb21iaShkZiRuaHNub1tsZ2tdLCBkZiRkb2JbbGdrXSwgZGYkZm9yZW5hbWVbbGdrXSwgZGYkc3VybmFtZVtsZ2tdKQ0KICAgIGxnayA8LSB3aGljaCghKGRmJG5oc25vICVpbiUgYygiLiIsIiIsd3JfbmhzLCIwIiwiMTIzNDU2Nzg5MCIsIjk4NzY1NDMyMTAiLE5BKSB8DQogICAgICAgICAgICAgICAgICAgICBkZiRkb2IgJWluJSBjKCIiLCAiMTkwMC0wMS0wMSIsIE5BKSB8DQogICAgICAgICAgICAgICAgICAgICBkZiRzZXggJWluJSBjKCIiLCJOTyBQQVRJRU5UIElEIiwiVU5LTk9XTiIsIk5PIFJFRiBHSVZFTiIsICJuayIsICJVIiwiVW5rbm93biIsIE5BKSkpDQogICAgIyBDUjIgLSBWYWxpZCBOSFMgbm8sIERPQiBhbmQgc2V4DQogICAgZGYkY3JpXzJbbGdrXSA8LSBkaXlhcjo6Y29tYmkoZGYkbmhzbm9bbGdrXSwgZGYkZG9iW2xna10sIGRmJHNleFtsZ2tdKQ0KICAgIGxnayA8LSB3aGljaCghKGRmJG5oc25vICVpbiUgYygiLiIsIiIsd3JfbmhzLCIwIiwiMTIzNDU2Nzg5MCIsIjk4NzY1NDMyMTAiLE5BKSB8DQogICAgICAgICAgICAgICAgICAgICBkZiRkb2IgJWluJSBjKCIiLCAiMTkwMC0wMS0wMSIsIE5BKSkpDQogICAgIyBDUjMgLSBWYWxpZCBOSFMgbm8gYW5kIERPQg0KICAgIGRmJGNyaV8zW2xna10gPC0gZGl5YXI6OmNvbWJpKGRmJG5oc25vW2xna10sIGRmJGRvYltsZ2tdKQ0KICAgIA0KICAgICMgT3JkZXI7IENSMSB0byBDUjMNCiAgICBjcml0ZXJpYSA8LSBsaXN0KGRmJGNyaV8xLCBkZiRjcmlfMiwgZGYkY3JpXzMpDQogICAgbGlua2VkX3NfY3JpdGVyaWEgPC0gTlVMTA0KICAgIA0KICB9ZWxzZSBpZihsaW5rYWdlX3N0cmF0ZWd5ID09ICJiYXNpYyIpew0KICAgICMgSW5jbHVzaW9uIGNyaXRlcmlhDQogICAgd3JfbmhzIDwtIGFzLmNoYXJhY3Rlcih3cl9uaHMpDQogICAgZGYkY3JpXzEgPC0gTkENCiAgICBsZ2sgPC0gd2hpY2goIShkZiRuaHNubyAlaW4lIGMoIi4iLCIiLHdyX25ocywiMCIsIjEyMzQ1Njc4OTAiLCI5ODc2NTQzMjEwIixOQSkgfA0KICAgICAgICAgICAgICAgICAgICAgZGYkZG9iICVpbiUgYygiIiwgIjE5MDAtMDEtMDEiLCBOQSkpKQ0KICAgICMgQ1IxIC0gVmFsaWQgTkhTIG5vIGFuZCBET0INCiAgICBkZiRjcmlfMVtsZ2tdIDwtIGRpeWFyOjpjb21iaShkZiRuaHNub1tsZ2tdLCBkZiRkb2JbbGdrXSkNCiAgICAjIE9yZGVyOyBDUjENCiAgICBjcml0ZXJpYSA8LSBsaXN0KGRmJGNyaV8xKQ0KICAgIGxpbmtlZF9zX2NyaXRlcmlhIDwtIE5VTEwNCiAgfWVsc2UgaWYobGlua2FnZV9zdHJhdGVneSA9PSAic2dzc19hdWRpdCIpew0KICAgICMgOS1zdGFnZSBvcmRlcmVkIHJlY29yZCBsaW5rYWdlIHRvIGlkZW50aWZ5IHBhdGllbnRzDQogICAgIyBQbGFjZSBob2xkZXJzIGZvciA5IG1hdGNoaW5nIGNyaXRlcmlhIGFuZCBhdHRyaWJ1dGVzIGZvciBzdWJfY3JpdGVyaWENCiAgICBkZiRjcmlfMSA8LSBkZiRjcmlfMiA8LSBkZiRjcmlfMyA8LQ0KICAgICAgZGYkY3JpXzQgPC0gZGYkY3JpXzUgPC0gZGYkY3JpXzYgPC0NCiAgICAgIGRmJGNyaV83IDwtIGRmJGNyaV84IDwtIGRmJGNyaV85IDwtDQogICAgICBkZiRjcmlfZG9iX3B0MSA8LSBkZiRjcmlfZG9iX3B0MiA8LSBkZiRjcmlfZG9iX3B0MyA8LSBOQQ0KICAgIA0KICAgICMgUGxhY2UgaG9sZGVycyBmb3IgaW5jbHVzaW9uIGNyaXRlcmlhIG9mIGVhY2ggYXR0cmlidXRlDQogICAgdl9zcGVjbm8gPC0gdl9mbmFtZSA8LSB2X3NuYW1lIDwtIHZfbmhzIDwtIHZfZG9iIDwtIHZfaG9zIDwtIHZfc2V4IDwtIHJlcChGQUxTRSwgbnJvdyhkZikpDQogICAgDQogICAgd3JfbmhzIDwtIGxhcHBseSgwOjksIGZ1bmN0aW9uKHgpew0KICAgICAgcGFzdGUwKHJlcCh4LDEwKSwgY29sbGFwc2UgPSAiIikNCiAgICB9KQ0KICAgIA0KICAgIGRmJHNvdW5kZXggPC0gc3RyaW5nZGlzdDo6cGhvbmV0aWMoZGYkc3VybmFtZSkNCiAgICBkZiRmb3JlbmFtZV9pbml0aWFsIDwtIHRvdXBwZXIoc3Vic3RyKGRmJGZvcmVuYW1lLDEsIDEpKQ0KICAgIHZfbmhzWyFkZiRuaHNubyAlaW4lIGMoIi4iLCIiLCI5OTk5OTk5OTk5IiwiMCIsIjEyMzQ1Njc4OSIsIjk4NzY1NDMyMTAiLCBOQSwgd3JfbmhzKV0gPC0gVFJVRQ0KICAgIHZfZG9iWyFkZiRkb2IgJWluJSBjKCIxOTAwLTAxLTAxIiwiIiwgTkEpXSA8LSBUUlVFDQogICAgdl9ob3NbIXRvdXBwZXIoZGYkaG9zbm8pICVpbiUgYygiVU5LTk9XTiIsICJOTyBQQVRJRU5UIElEIiwgIiIsIk5PVCBTUEVDSUZJRUQiLCBOQSldIDwtIFRSVUUNCiAgICB2X3NleFshdG91cHBlcihkZiRzZXgpICVpbiUgYygiVU5LTk9XTiIsIiIsIk5PVCBTUEVDSUZJRUQiLCAgTkEpXSA8LSBUUlVFDQogICAgdl9zbmFtZVshdG91cHBlcihkZiRzdXJuYW1lKSAlaW4lIGMoIiIsIE5BKV0gPC0gVFJVRQ0KICAgIHZfZm5hbWVbIXRvdXBwZXIoZGYkZm9yZW5hbWUpICVpbiUgYygiIiwgTkEpXSA8LSBUUlVFDQogICAgdl9zcGVjbm9bIXRvdXBwZXIoZGYkc3BlY25vKSAlaW4lIGMoIlVOS05PV04iLCAiTk8gUEFUSUVOVCBJRCIsICIiLCJOT1QgU1BFQ0lGSUVEIiwgTkEpXSA8LSBUUlVFDQogICAgDQogICAgIyBTdGFnZSAxDQogICAgbGdrIDwtIHdoaWNoKHZfbmhzICYgdl9kb2IpDQogICAgZGYkY3JpXzFbbGdrXSA8LSBkaXlhcjo6Y29tYmkoZGYkbmhzbm9bbGdrXSwgZGYkZG9iW2xna10pDQogICAgDQogICAgIyBTdGFnZSAyDQogICAgbGdrIDwtIHdoaWNoKHZfbmhzKQ0KICAgIGRmJGNyaV8yW2xna10gPC0gZGYkbmhzbm9bbGdrXQ0KICAgIA0KICAgICMgU3RhZ2UgMw0KICAgIGxnayA8LSB3aGljaCh2X2hvcyAmIHZfZG9iICYgdl9zbmFtZSkNCiAgICBkZiRjcmlfM1tsZ2tdIDwtIGRpeWFyOjpjb21iaShkZiRob3Nub1tsZ2tdLGRmJGRvYltsZ2tdLCBkZiRzb3VuZGV4W2xna10pDQogICAgIyBTdGFnZSA0DQogICAgbGdrIDwtIHdoaWNoKHZfaG9zKQ0KICAgIGRmJGNyaV80W2xna10gPC0gZGYkaG9zbm9bbGdrXQ0KICAgIA0KICAgICMgU3RhZ2UgNQ0KICAgIGxnayA8LSB3aGljaCh2X3NwZWNubyAmIHZfc2V4ICYgdl9kb2IgJiB2X2ZuYW1lICYgdl9zbmFtZSkNCiAgICBkZiRjcmlfNVtsZ2tdIDwtIGRpeWFyOjpjb21iaShkZiRzcGVjbm9bbGdrXSwgZGYkbGFiX2NkW2xna10sIGRmJHNleFtsZ2tdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmJGZvcmVuYW1lX2luaXRpYWxbbGdrXSwgZGYkZG9iW2xna10sIGRmJHNvdW5kZXhbbGdrXSkNCiAgICAjIFN0YWdlIDYNCiAgICBsZ2sgPC0gd2hpY2godl9zcGVjbm8gJiB2X3NleCkNCiAgICBkZiRjcmlfNltsZ2tdIDwtIGRpeWFyOjpjb21iaShkZiRzcGVjbm9bbGdrXSwgZGYkbGFiX2NkW2xna10sIGRmJHNleFtsZ2tdKQ0KICAgICMgU3RhZ2UgNw0KICAgIGxnayA8LSB3aGljaCh2X3NwZWNubyAmIHZfZG9iKQ0KICAgIGRmJGNyaV83W2xna10gPC0gZGl5YXI6OmNvbWJpKGRmJHNwZWNub1tsZ2tdLCBkZiRkb2JbbGdrXSkNCiAgICAjIFN0YWdlIDgNCiAgICAjIGRmJGNyaV84IDwtIGRmJGNyaV83DQogICAgDQogICAgIyBTdGFnZSA5DQogICAgbGdrIDwtIHdoaWNoKHZfc3BlY25vKQ0KICAgIGRmJGNyaV85W2xna10gPC0gZGYkc3BlY25vW2xna10NCiAgICBsZ2sgPC0gd2hpY2godl9kb2IpDQogICAgZGYkY3JpX2RvYl9wdDFbbGdrXSA8LSBkaXlhcjo6Y29tYmkobHVicmlkYXRlOjp5ZWFyKGRmJGRvYltsZ2tdKSwgbHVicmlkYXRlOjptb250aChkZiRkb2JbbGdrXSkpDQogICAgZGYkY3JpX2RvYl9wdDJbbGdrXSA8LSBkaXlhcjo6Y29tYmkobHVicmlkYXRlOjp5ZWFyKGRmJGRvYltsZ2tdKSwgbHVicmlkYXRlOjpkYXkoZGYkZG9iW2xna10pKQ0KICAgIGRmJGNyaV9kb2JfcHQzW2xna10gPC0gZGl5YXI6OmNvbWJpKGx1YnJpZGF0ZTo6ZGF5KGRmJGRvYltsZ2tdKSwgbHVicmlkYXRlOjptb250aChkZiRkb2JbbGdrXSkpDQogICAgDQogICAgIyBTdWIgY3JpdGVyaWEgZm9yIHN0YWdlIDYuIEFsc28gdXNlZCBpbiBzdGFnZSA5DQogICAgc19jcmk2IDwtIGRpeWFyOjpzdWJfY3JpdGVyaWEoZGYkZm9yZW5hbWVfaW5pdGlhbCwgZGYkc291bmRleCkNCiAgICAjIFN1YiBjcml0ZXJpYSBmb3Igc3RhZ2UgOQ0KICAgIHNfY3JpOSA8LSBkaXlhcjo6c3ViX2NyaXRlcmlhKA0KICAgICAgc19jcmk2LA0KICAgICAgZGl5YXI6OnN1Yl9jcml0ZXJpYShkZiRjcmlfZG9iX3B0MSwgZGYkY3JpX2RvYl9wdDIsIGRmJGNyaV9kb2JfcHQzKSwNCiAgICAgIG9wZXJhdG9yID0gIm9yIikNCiAgICANCiAgICAjIE9yZGVyOyBDUjEgdG8gQ1I5DQogICAgY3JpdGVyaWEgPC0gbGlzdChkZiRjcmlfMSwgZGYkY3JpXzIsIGRmJGNyaV8zLCBkZiRjcmlfNCwNCiAgICAgICAgICAgICAgICAgICAgIGRmJGNyaV81LCBkZiRjcmlfNiwgZGYkY3JpXzcsDQogICAgICAgICAgICAgICAgICAgICAjIGRmJGNyaV84LA0KICAgICAgICAgICAgICAgICAgICAgZGYkY3JpXzkpDQogICAgbGlua2VkX3NfY3JpdGVyaWEgPC0gbGlzdCgiY3I2IiA9IHNfY3JpNiwgImNyOSIgPSBzX2NyaTkpDQogICAgZGYkc291bmRleCA8LSBOVUxMDQogIH0NCiAgDQogIA0KICBkZiA8LSBkZltuYW1lcyhkZilbIWdyZXBsKCJjcmlfIiwgbmFtZXMoZGYpKV1dDQogIA0KICAjIFVubGlua2VkIHJlY29yZHMgYXJlIGNvbnNpZGVyZWQgc2VwYXJhdGUgZW50aXRpZXMuDQogICMgVGhpcyBpcyBhbiBpc3N1ZSB3aGVuIHF1ZXJ5aW5nIEFNUiBkYXRhIGFzIGVhY2ggdW5saW5rZWQgdGVzdCBpcyBjb25zaWRlcmVkIGEgc2VwYXJhdGUgcGF0aWVudC4NCiAgIyBUbyBwcmV2ZW50IHRoaXMsIHRoZSBjbG9zZXN0IGFwcHJveGltYXRpb24gdG8gYSBwYXRpZW50J3Mgc2FtcGxlIGlzIHVzZWQgYXMgdGhlIGZpbmFsIGNyaXRlcmlhDQogIGNyaXRlcmlhIDwtIGMoY3JpdGVyaWEsDQogICAgICAgICAgICAgICAgbGlzdChkaXlhcjo6Y29tYmkoZGYkbmhzbm8sIGRmJGhvc3BubywgZGYkZG9iLCBkZiRzdXJuYW1lLCBkZiRmb3JlbmFtZSwgZGYkc2V4LCBkZiRzcGVjbm8pKQ0KICApDQogIGRmJHBpZHMgPC0gZGl5YXI6OmxpbmtzKGNyaXRlcmlhID0gY3JpdGVyaWEsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHN1Yl9jcml0ZXJpYSA9IGxpbmtlZF9zX2NyaXRlcmlhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBkaXNwbGF5ID0gInByb2dyZXNzIikNCiAgDQogIGNhdChwYXN0ZTAoIkNhdGVnb3Jpc2luZyBlcGlzb2RlOiAiLA0KICAgICAgICAgICAgIGVwaXNvZGVfbGVuZ3RoLCItZGF5IGRpZmZlcmVuY2UgIiwNCiAgICAgICAgICAgICBlcGlzb2RlX3R5cGUsICIgZXBpc29kZXNcbiIpKQ0KICBkZiRkdCA8LSBsdWJyaWRhdGU6OnltZChkZiRzcGVjZGF0ZSkNCiAgDQogIA0KICBkZiRwaWRfc3RyIDwtIGRpeWFyOjpjb21iaShhcy5saXN0KGRmW2RlZHVwX2xldmVsXSkpDQogIA0KICANCiAgaWYoIWlzLm51bGwoZGVkdXBfbW9kdWxlX3ByZWZlcmVuY2UpKXsNCiAgICBtZGxzIDwtIGMoImFtciIsICJjZHIiKQ0KICAgIGRlZHVwX21vZHVsZV9wcmVmZXJlbmNlIDwtIGZhY3RvcihkZiRzZ3NzX21vZHVsZSwgbGV2ZWxzID0gYyhtZGxzW21kbHMgPT0gZGVkdXBfbW9kdWxlX3ByZWZlcmVuY2VdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZGxzW21kbHMgIT0gZGVkdXBfbW9kdWxlX3ByZWZlcmVuY2VdKSkNCiAgfQ0KICANCiAgZGYkZXBpZHMgPC0gZGl5YXI6OmVwaXNvZGVzKGRhdGUgPSBkZiRkdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhc2VfbGVuZ3RoID0gZXBpc29kZV9sZW5ndGgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWN1cnJlbmNlX2xlbmd0aCA9IHJlY3VycmVuY2VfbGVuZ3RoLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXBpc29kZV90eXBlID0gZXBpc29kZV90eXBlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyYXRhID0gZGYkcGlkX3N0ciwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX3N0YXRzID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3BsYXkgPSAicHJvZ3Jlc3MiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3VzdG9tX3NvcnQgPSBkZWR1cF9tb2R1bGVfcHJlZmVyZW5jZSkNCiAgDQogIGRmJGR0IDwtIGRmJHFjIDwtIE5VTEwNCiAgDQogICMgTW9zdCBzZXZlcmUgYWJ4IHJlc3VsdCBwZXIgZXBpc29kZQ0KICANCiAgIyBjb252ZXJ0IGluc3RhbmNlcyBvZiAiUCIgYW5kICJXIiB0byAiUiIgaW4gYXN0X3Jlc3VsdCBmaWVsZCBmb3IgaW5kaWNpYmxlIGNsaW5kYW15Y2luIHJlc2lzdGFuY2UgdGVzdCByZXN1bHRzIA0KICBkZiA8LSBkZiAlPiUgbXV0YXRlKGFzdF9yZXN1bHQ9Y2FzZV93aGVuKA0KICAgIGFzdF9yZXN1bHQgPT0gIlAiIHwgYXN0X3Jlc3VsdCA9PSAiVyIgfiAiUiIsDQogICAgVFJVRSB+IGFzdF9yZXN1bHQNCiAgKSkNCiAgDQogIGlmKCFpc0ZBTFNFKGFieCkpew0KICAgIGNhdCgiRGV0ZXJtaW5pbmcgdGhlIG1vc3Qgc2V2ZXJlIGFieCByZXN1bHQgcGVyIGVwaXNvZGVcbiIpDQogICAgZGYkYW1yX3NldmVyaXR5IDwtIGRwbHlyOjpjYXNlX3doZW4oDQogICAgICBkZiRhc3RfcmVzdWx0ID09ICJSIiB+IDEsDQogICAgICBkZiRhc3RfcmVzdWx0ID09ICJJIiB+IDIsDQogICAgICBkZiRhc3RfcmVzdWx0ID09ICJTIiB+IDMsDQogICAgICBkZiRhc3RfcmVzdWx0ID09ICJOIiB+IDQsDQogICAgICBUUlVFIH4gNQ0KICAgICkNCiAgICANCiAgICBpZihjKCJzZXZlcml0eV9hbGwiKSAlaW4lIGFieF9wcmVmZXJlbmNlKXsNCiAgICAgIGRmJGVwaV9hbXIgPC0ganVyaXRoeTo6YnlzX3ZhbChkZiRhbXJfc2V2ZXJpdHksIGRmJHNuLCBieT0gcGFzdGUoYXMubnVtZXJpYyhkZiRlcGlkcyksIGRmJGFieCksIHZhbD1kZiRhbXJfc2V2ZXJpdHkpDQogICAgfWVsc2UgaWYgKGMoInNldmVyaXR5X2NkciIpICVpbiUgYWJ4X3ByZWZlcmVuY2Upew0KICAgICAgdHBfbWQgPC0gZmFjdG9yKHRvbG93ZXIoZGYkc2dzc19tb2R1bGUpLCBsZXZlbHMgPSBjKCJjZHIiLCAiYW1yIikpDQogICAgICBkZiRlcGlfYW1yIDwtIGp1cml0aHk6OmJ5c192YWwodHBfbWQsIGRmJGFtcl9zZXZlcml0eSwgZGYkc24sIGJ5PSBwYXN0ZShhcy5udW1lcmljKGRmJGVwaWRzKSwgZGYkYWJ4KSwgdmFsPWRmJGFtcl9zZXZlcml0eSkNCiAgICAgIHJtKHRwX21kKQ0KICAgIH1lbHNlIGlmIChjKCJzZXZlcml0eV9hbXIiKSAlaW4lIGFieF9wcmVmZXJlbmNlKXsNCiAgICAgIHRwX21kIDwtIGZhY3Rvcih0b2xvd2VyKGRmJHNnc3NfbW9kdWxlKSwgbGV2ZWxzID0gYygiYW1yIiwgImNkciIpKQ0KICAgICAgZGYkZXBpX2FtciA8LSBqdXJpdGh5OjpieXNfdmFsKHRwX21kLCBkZiRhbXJfc2V2ZXJpdHksIGRmJHNuLCBieT0gcGFzdGUoYXMubnVtZXJpYyhkZiRlcGlkcyksIGRmJGFieCksIHZhbD1kZiRhbXJfc2V2ZXJpdHkpDQogICAgICBybSh0cF9tZCkNCiAgICB9DQogICAgDQogICAgZGYkZXBpX2FtciA8LSBjKCJSIiwiSSIsIlMiLCJOIiwiIilbZGYkZXBpX2Ftcl0NCiAgICANCiAgICBhYnhfcmVjb2RlZCA8LSBkZiRhbXIhPWRmJGVwaV9hbXINCiAgICBzdW1tYXJ5JGFieF9yZWNvZGVkIDwtIGxlbmd0aChhYnhfcmVjb2RlZFthYnhfcmVjb2RlZF0pDQogICAgDQogICAgYWJ4X3Jlc3VsdHMgPC0gZHBseXI6OnNlbGVjdChkZiwgLmRhdGEkZXBpZHMsIC5kYXRhJGFieCwgZSA9IC5kYXRhJGVwaV9hbXIsIHIgPSAuZGF0YSRhc3RfcmVzdWx0KSAlPiUNCiAgICAgIGRwbHlyOjptdXRhdGUoZXBpZHMgPSBhcy5udW1lcmljKC5kYXRhJGVwaWRzKSkgJT4lDQogICAgICBkcGx5cjo6ZGlzdGluY3QoKSAlPiUNCiAgICAgIHRpZHlyOjpwaXZvdF93aWRlcihuYW1lc19mcm9tID0gLmRhdGEkYWJ4LA0KICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc19mcm9tID0gYyguZGF0YSRlLCAuZGF0YSRyKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfZm4gPSBsaXN0KGUgPSB+IHBhc3RlMCh1bmlxdWUoLiksIGNvbGxhcHNlID0gIjsiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHIgPSB+IHBhc3RlMCh1bmlxdWUoLiksIGNvbGxhcHNlID0gIjsiKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzX2ZpbGwgPSBsaXN0KGUgPSIiLCByID0gIiIpKQ0KICAgIGRmJGFtcl9zZXZlcml0eSA8LSBOVUxMDQogICAgDQogIH1lbHNlew0KICAgIHN1bW1hcnkkYWJ4X3JlY29kZWQgPC0gTlVMTA0KICAgIGFieF9yZXN1bHRzIDwtIE5VTEwNCiAgfQ0KICANCiAgZGYkcGF0aWVudHMgPC0gYXMubnVtZXJpYyghZHVwbGljYXRlZChkZiRwaWRzQC5EYXRhKSkNCiAgZGYkZXBpc29kZXMgPC0gYXMubnVtZXJpYyghZHVwbGljYXRlZChkZiRlcGlkc0AuRGF0YSkpDQogIA0KICBkZWR1cGVkX2RmIDwtIGRmICU+JSBmaWx0ZXIocGF0aWVudHM9PTEpDQogIA0KICBkZWR1cGxpY2F0aW9uIDwtIGRwbHlyOjpzZWxlY3QoamFuaXRvcjo6dGFieWwoZGYsIC5kYXRhJHNwZWNpZXMsICAuZGF0YSRwYXRpZW50cyksICAic3BlY2llcyIsIHBhdGllbnRzID0gIjEiKQ0KICBkZWR1cGxpY2F0aW9uIDwtIGRwbHlyOjpiaW5kX2NvbHMoZGVkdXBsaWNhdGlvbiwgIGRwbHlyOjpzZWxlY3QoamFuaXRvcjo6dGFieWwoZGYsIC5kYXRhJHNwZWNpZXMsICAuZGF0YSRlcGlzb2RlcyksIGVwaXNvZGVzID0gIjEiKSkNCiAgDQogIHN1bW1hcnkkZGVkdXBsaWNhdGlvbiA8LSBkZWR1cGxpY2F0aW9uDQogIA0KICBkZiRwYXRpZW50cyA8LSBkZiRlcGlzb2RlcyA8LSBkZiRzbiA8LSBkZiRwaWRfc3RyIDwtIE5VTEwNCiAgb3V0cHV0IDwtIGxpc3QoDQogICAgZXh0cmFjdHMgPSBhbGxzaXRlcywNCiAgICBwcm9jZXNzZWQgPSBkZiwNCiAgICBhYnhfcmVzdWx0cyA9IGFieF9yZXN1bHRzLA0KICAgIHN1bW1hcnkgPSBzdW1tYXJ5KQ0KICANCiAgDQogICMgcHJvY2Vzc2VkX2RhdGEgPC0gYXMuZGF0YS5mcmFtZShvdXRwdXRbWyJwcm9jZXNzZWQiXV0pDQogIHByb2Nlc3NlZF9kYXRhIDwtIGRlZHVwZWRfZGYNCiAgYWJ4X2RhdGEgPC0gYXMuZGF0YS5mcmFtZShvdXRwdXRbWyJhYnhfcmVzdWx0cyJdXSkNCiAgDQogIHByb2Nlc3NlZF9kYXRhJGVwaWRzIDwtIGFzLmNoYXJhY3Rlcihwcm9jZXNzZWRfZGF0YSRlcGlkcykNCiAgcHJvY2Vzc2VkX2RhdGEkZXBpZHMyIDwtIHNhcHBseShwcm9jZXNzZWRfZGF0YSRlcGlkcywgZnVuY3Rpb24oeCkgew0KICAgIHN1YigiXlteMC05XSowKihbMS05XVswLTldKikiLCAiXFwxIiwgc3Ryc3BsaXQoeCwgIiAiKVtbMV1dWzFdKQ0KICB9KQ0KICANCiAgIyBtZXJnZSBsaW5lbGlzdCB3aXRoIGFieCByZXN1bHRzIG9uIGVwaWRzDQogIGFieF9yZXN1bHRzJGVwaWRzIDwtIGFzLmNoYXJhY3RlcihhYnhfcmVzdWx0cyRlcGlkcykNCiAgZGVkdXBlZF9kZiA8LSBsZWZ0X2pvaW4ocHJvY2Vzc2VkX2RhdGEsYWJ4X3Jlc3VsdHMsYnk9ImVwaWRzIikgJT4lIHNlbGVjdCgtYygic24iLCJwaWRzIiwiZXBpX2FtciIsInBhdGllbnRzIiwiYWJ4IiwiYXN0X3Jlc3VsdCIsImVwaXNvZGVzIiwiZXBpZHMyIikpDQogIA0KICByZXR1cm4oZGVkdXBlZF9kZikNCiAgDQp9DQoNCg0KIyAvLy8vLy8vLy8vLy8vLS0tLS0tLS0tLS0tIEVUSE5JQ0lUWSBHUk9VUElOR1MgLS0tLS0tLS0tLS0tIC8vLy8vLy8vLy8vLy8vLy8vDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byByZWdyb3VwIExJTVMgZXRobmljaXR5IGNhdGVnb3JpZXMgdG8gZXNwYXVyIGdyb3VwaW5ncyBhbmQgdGhlbiB0byA1IGNhdGVnb3JpZXMgZm9yIGFnZSBzdGFuZGFyZGlzYXRpb24gDQpsaW1zLmV0aGNhdC5mdW4gPC0gZnVuY3Rpb24oeCl7DQoNCiAgI2NyZWF0ZSBjaGFyIHZlYyBvZiB1bmlxdWUgRXRobmljIGdyb3VwcyBpbiBldGhuaWNpdHktbGlua2VkIGxpbmVsaXN0IGRhdGENCiAgZXRobi52ZWMgPC0gc29ydCh1bmlxdWUoeCRldGhuaWNpdHkpKSAgICMgaW4gYWxwaGFiZXRpY2FsIG9yZGVyIGZvciBjb25zaXN0ZW5jeSB3aGVuIGluZGV4aW5nDQogIA0KICAjIHZlY3RvciBwb3NpdGlvbnMgZm9yIGVhY2ggZXRobmljaXR5IHJlcG9ydCBpbiBMSU1TDQogIA0KICAjIFVua25vd24gDQogICMgWzFdICIiICANCiAgIyBbMzldIk5vdCBBc2tlZCINCiAgIyBbNDBdICJOb3QgR2l2ZW4gb24gRm9ybSIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbNDFdIk5vdCBnaXZlbi9kZWNsaW5lZCIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAjIFs0Ml0iTm90IHNwZWNpZmllZCIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbNDNdICJOb3QgU3BlY2lmaWVkIiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbNDRdIk5vdCBzdGF0ZWQiIA0KICAjIFs0NV0iT3RoZXIgLSBub3Qgc3RhdGVkIiANCiAgIyBbNTRdIlVuYWJsZSBvciB1bndpbGxpbmcgdG8gYW5zd2VyIiAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzU1XSAiVU5LTk9XTiIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgDQogICMgQmxhY2ssIEJsYWNrIEJyaXRpc2gsIENhcmliYmVhbiBvciBBZnJpY2FuDQogICMgWzJdIkFmcmljYW4gb3IgQWZyaWNhbi1DYXJpYmJlYW4iICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAjIFszXSJBbnkgb3RoZXIgQmxhY2sgYmFja2dyb3VuZCINCiAgIyBbMTldICJCbGFjayAtIGFueSBvdGhlciBibGFjayBiYWNrZ3JvdW5kIiAgICAgICAgICAgICAgICAgICANCiAgIyBbMjBdIkJsYWNrIC0gQ2FyaWJiZWFuIiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAjIFsyMV0iQmxhY2sgQWZyaWNhbiIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbMjJdICJCbGFjayBDYXJpYmJlYW4iICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbMjNdIkJsYWNrIG9yIEJsYWNrIEJyaXRpc2ggLSBBZnJpY2FuICIgICAgICAgICAgICAgICAgICAgIA0KICAjIFsyNF0iQmxhY2sgb3IgQmxhY2sgQnJpdGlzaCAtIEFueSBvdGhlciBCbGFjayBiYWNrZ3JvdW5kICINCiAgIyBbMjVdICJCbGFjay9CbGFjayBCcml0aXNoLUNhcmliYmVhbiIgICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbMjZdIkJsYWNrL0JsYWNrIEJyaXRpc2ggQWZyaWNhbiIgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAjIFsyN10iQmxhY2svQmxhY2sgQnJpdGlzaCBDYXJpYmJlYW4iICAgDQogICMgDQogICMgTWl4ZWQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzI5XSJNaXhlZCAtIGFueSBvdGhlciBtaXhlZCBiYWNrZ3JvdW5kIiAgICAgICAgICAgICAgICAgICANCiAgIyBbMzBdIk1peGVkIC0gQW55IG90aGVyIG1peGVkIGJhY2tncm91bmQgIiAgICAgICAgICAgICAgICAgDQogICMgWzMxXSAiTWl4ZWQgLSBXaGl0ZSBhbmQgQXNpYW4gIiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzMyXSJNaXhlZCAtIFdoaXRlIGFuZCBCbGFjayBBZnJpY2FuICIgICAgICAgICAgICAgICAgICAgICANCiAgIyBbMzNdIk1peGVkIEFueSBvdGhlciBtaXhlZCBiYWNrZ3JvdW5kIiAgICAgICAgICAgICAgICAgICAgDQogICMgWzM0XSAiTWl4ZWQgV2hpdGUgJiBCbGFjayBDYXJpYmJlYW4iICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzM1XSJNaXhlZCBXaGl0ZSBhbmQgQXNpYW4iICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbMzZdIk1peGVkIFdoaXRlIGFuZCBCbGFjayBBZnJpY2FuIiAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzM3XSAiTWl4ZWQgV2hpdGUgYW5kIEJsYWNrIENhcmliYmVhbiIgICAgICAgICAgICAgICAgICAgICAgDQogICMgDQogICMgT3RoZXINCiAgIyBbNF0gIkFueSBvdGhlciBldGhuaWMgZ3JvdXAiIA0KICAjIFs0Nl0gIk90aGVyIEV0aG5pYyBHcm91cCIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAjIFs0N10iT3RoZXIgTm9uLUV1cm9wZWFuIg0KICAjIFs0OV0gIk90aGVyIEV0aG5pYyBHcm91cHMgLSBBbnkgb3RoZXIgZXRobmljIGdyb3VwICIgDQogICMgWzUzXSJTb3V0aGVybiAmIE90aGVyIEV1cm9wZWFuIiANCiAgIyBbMzhdIk5vcnRoZXJuIEV1cm9wZWFuIiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAjIA0KICAjIEFzaWFuLCBBc2lhbiBCcml0aXNoDQogICMgWzddICJBc2lhbiBhbnkgb3RoZXIgQXNpYW4gYmFja2dyb3VuZCIgICAgICAgICAgICAgICAgICAgICANCiAgIyBbOF0iQXNpYW4gQmFuZ2xhZGVzaGkiICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzldIkFzaWFuIEluZGlhbiIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzEwXSAiQXNpYW4gb3IgQXNpYW4gQnJpdGlzaCAtIEFueSBvdGhlciBBc2lhbiBiYWNrZ3JvdW5kIiAgDQogICMgWzExXSJBc2lhbiBvciBBc2lhbiBCcml0aXNoIC0gQmFuZ2xhZGVzaGkgIiAgICAgICAgICAgICAgICANCiAgIyBbMTJdIkFzaWFuIG9yIEFzaWFuIEJyaXRpc2ggLSBJbmRpYW4gIiAgICAgICAgICAgICAgICAgICAgDQogICMgWzEzXSAiQXNpYW4gb3IgQXNpYW4gQnJpdGlzaCAtIFBha2lzdGFuaSAiICAgICAgICAgICAgICAgICAgDQogICMgWzE0XSJBc2lhbiBQYWtpc3RhbmkiICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbMTVdIkFzaWFuL0FzaWFuIEJyaXRpc2gtUGFraXN0YW5pIiAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzE2XSAiQXNpYW4vQXNpYW4gQnJpdGlzaCBhbnkgb3RoZXIiICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzE3XSJBc2lhbi9Bc2lhbiBCcml0aXNoIEJhbmdsYWRlc2hpIiAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbMThdIkFzaWFuL0FzaWFuIEJyaXRpc2ggSW5kaWFuIiAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzI4XSAiQ2hpbmVzZSIgDQogICMgWzUwXSJPdGhlciBFdGhuaWMgR3JvdXBzIC0gQ2hpbmVzZSAiICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbNTFdIlNvdXRoIEFzaWFuIiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzUyXSAiU291dGggRWFzdCBBc2lhbiIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgDQogICMgDQogICMgDQogICMgV2hpdGUNCiAgIyBbNV0iQW55IG90aGVyIHdoaXRlIGJhY2tncm91bmQiICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzZdIkFueSBvdGhlciBXaGl0ZSBiYWNrZ3JvdW5kIiAgDQogICMgWzQ4XSJPdGhlciB3aGl0ZSBiYWNrZ3JvdW5kIiAgDQogICMgWzU2XSJXaGl0ZSIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbNTddIldISVRFIiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzU4XSAiV2hpdGUgLSBBbnkgb3RoZXIgV2hpdGUgYmFja2dyb3VuZCAiICAgICAgICAgICAgICAgICAgDQogICMgWzU5XSJXaGl0ZSAtIEJyaXRpc2giICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbNjBdIldoaXRlIC0gSXJpc2giICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzYxXSAiV2hpdGUgQnJpdGlzaCIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzYyXSJXaGl0ZSBJcmlzaCIgIA0KICANCiAgDQogIHggPC0geCAlPiUgbXV0YXRlKEV0aG5pY2l0eV9ncm91cD0iIikgDQogIA0KICAjIGZvciB1bmtub3duIGdyb3VwDQogIHggPC0geCAlPiUNCiAgICBtdXRhdGUoRXRobmljaXR5X2dyb3VwID0gY2FzZV93aGVuKA0KICAgICAgZXRobmljaXR5ICVpbiUgYyhldGhuLnZlY1sxXSwgZXRobi52ZWNbNTRdLCBldGhuLnZlY1s1NV0pIH4gIlVua25vd24iLA0KICAgICAgZXRobmljaXR5ICVpbiUgZXRobi52ZWNbMzk6NDVdIH4gIlVua25vd24iLA0KICAgICAgaXMubmEoZXRobmljaXR5KSB+ICJVbmtub3duIiwNCiAgICAgIFRSVUUgfiBFdGhuaWNpdHlfZ3JvdXANCiAgICApKQ0KICANCiAgIyBmb3IgQmxhY2ssIEJsYWNrIEJyaXRpc2gsIENhcmliYmVhbiBvciBBZnJpY2FuIGdyb3VwDQogIHggPC0geCAlPiUNCiAgICBtdXRhdGUoRXRobmljaXR5X2dyb3VwID0gY2FzZV93aGVuKA0KICAgICAgZXRobmljaXR5ICVpbiUgYyhldGhuLnZlY1syXSwgZXRobi52ZWNbM10pIH4gIkJsYWNrIiwNCiAgICAgIGV0aG5pY2l0eSAlaW4lIGV0aG4udmVjWzE5OjI3XSB+ICJCbGFjayIsDQogICAgICBUUlVFIH4gRXRobmljaXR5X2dyb3VwDQogICAgKSkNCiAgDQogICMgZm9yIE1peGVkDQogIHggPC0geCAlPiUNCiAgICBtdXRhdGUoRXRobmljaXR5X2dyb3VwID0gY2FzZV93aGVuKA0KICAgICAgZXRobmljaXR5ICVpbiUgZXRobi52ZWNbMjk6MzddIH4gIk1peGVkIiwNCiAgICAgIFRSVUUgfiBFdGhuaWNpdHlfZ3JvdXANCiAgICApKQ0KICANCiAgIyBmb3IgT3RoZXINCiAgeCA8LSB4ICU+JQ0KICAgIG11dGF0ZShFdGhuaWNpdHlfZ3JvdXAgPSBjYXNlX3doZW4oDQogICAgICBldGhuaWNpdHkgJWluJSBjKGV0aG4udmVjWzRdLGV0aG4udmVjWzQ2XSxldGhuLnZlY1s0N10sZXRobi52ZWNbNDldLGV0aG4udmVjWzUzXSxldGhuLnZlY1szOF0pIH4gIk90aGVyIiwNCiAgICAgIFRSVUUgfiBFdGhuaWNpdHlfZ3JvdXANCiAgICApKQ0KICANCiAgIyBmb3IgQXNpYW4sIEFzaWFuIEJyaXRpc2gNCiAgeCA8LSB4ICU+JQ0KICAgIG11dGF0ZShFdGhuaWNpdHlfZ3JvdXAgPSBjYXNlX3doZW4oDQogICAgICBldGhuaWNpdHkgJWluJSBldGhuLnZlY1s3OjldIH4gIkFzaWFuIiwNCiAgICAgIGV0aG5pY2l0eSAlaW4lIGV0aG4udmVjWzEwOjE4XSB+ICJBc2lhbiIsDQogICAgICBldGhuaWNpdHkgJWluJSBldGhuLnZlY1syOF0gfiAiQXNpYW4iLA0KICAgICAgZXRobmljaXR5ICVpbiUgZXRobi52ZWNbNTA6NTJdIH4gIkFzaWFuIiwNCiAgICAgIFRSVUUgfiBFdGhuaWNpdHlfZ3JvdXANCiAgICApKQ0KICANCiAgIyBmb3IgV2hpdGUNCiAgeCA8LSB4ICU+JQ0KICAgIG11dGF0ZShFdGhuaWNpdHlfZ3JvdXAgPSBjYXNlX3doZW4oDQogICAgICBldGhuaWNpdHkgJWluJSBjKGV0aG4udmVjWzVdLCBldGhuLnZlY1s2XSwgZXRobi52ZWNbNDhdKSB+ICJXaGl0ZSIsDQogICAgICBldGhuaWNpdHkgJWluJSBldGhuLnZlY1s1Njo2Ml0gfiAiV2hpdGUiLA0KICAgICAgVFJVRSB+IEV0aG5pY2l0eV9ncm91cA0KICAgICkpICU+JSBzZWxlY3QoLWMoImV0aG5pY2l0eSIpKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGRyb3AgcmVkdW50YW50IEV0aG5pY2l0eSBhc3NpZ25tZW50IGNvbHVtbnMNCiAgICBtdXRhdGUoej0xKSAgDQogIA0KICAjIGFkZCBpbiBjb3VudCB2YXJpYWJsZSBwcmlvciB0byBncm91cGluZyBieSBldGhuaWNpdHkgY2F0ZWdvcnkNCiAgDQp9DQoNCg0KDQojIC8vLy8vLy8vLy8vLy8tLS0tLS0tLS0tLS0gREJTIFRSQUNJTkcgLS0tLS0tLS0tLS0tIC8vLy8vLy8vLy8vLy8vLy8vDQoNCg0KIyAvLy8vLy8vLy8vLy8vLy8vLw0KIyBzcGVjaWZ5IGZ1bmN0aW9uIHRvIHBlcmZvcm0gaW5pdGlhbCBkYXRhIHF1YWxpdHkgY2hlY2sgb24gTElNUyBsaW5lbGlzdCBhbmQgdG8gZ2VuZXJhdGUgc3VzYmV0IG9mIGRhdGEgd2hpY2ggbWlzc2luZyBvbmUgb3IgbW9yZSBrZXkgZmllbGRzIGZvciB0cmFjaW5nDQppbml0aWFsLmxpbXMuZHEuZnVuIDwtIGZ1bmN0aW9uKHgpew0KICAjIG5oc25vX25hIDwtIHN1bShpcy5uYSh4JG5oc25vKSkNCiAgIyBkb2JfbmEgPC0gc3VtKGlzLm5hKHgkZG9iKSkNCiAgIyBlcnJfZGF0ZSA8LSBhcy5EYXRlKGMoIjE5MDAtMDEtMDEiKSkNCiAgIyBkb2JfZXJyIDwtIHN1bSh4JGRvYj09ZXJyX2RhdGUpDQogIA0KICBzdWJzZXQgPC0geCAlPiUgZmlsdGVyKGlzLm5hKGRvYikgfA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZG9iPT1hcy5EYXRlKGMoIjE5MDAtMDEtMDEiKSkgfA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEobmhzbm8pIHwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKHN1cm5hbWUpIHwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNleD09IlUiIHwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdGllbnRfcG9zdGNvZGU9PSJaOTk5IikNCiAgDQogIHN1YnNldCA8LSBzdWJzZXQNCn0NCg0KDQojIC8vLy8vLy8vLy8vLy8vLy8vDQojIHNwZWNpZnkgZnVuY3Rpb24gdG8gaWRlbnRpZnkgYW5kIHJldGFpbiBRQyBzYW1wbGUgcmVwb3J0cyBmcm9tIExJTVMgbGluZWxpc3QsIGFzIHBhcnQgb2YgREJTIHRyYWNpbmcNCmVxYS5yZWNvcmQuZnVuIDwtIGZ1bmN0aW9uKHgpew0KICANCiAgb3V0IDwtIHggJT4lIGZpbHRlcihkb2I9PWFzLkRhdGUoYygiMTkwMC0wMS0wMSIpKSB8DQogICAgICAgICAgICAgICAgICAgICAgICBzdXJuYW1lPT0iTFEgQkxPT0QgQ1VMVFVSRSIgfCANCiAgICAgICAgICAgICAgICAgICAgICAgIHN1cm5hbWU9PSJJUUFNSUMyMDI0IiB8DQogICAgICAgICAgICAgICAgICAgICAgICBmb3JlbmFtZT09IkVRQSIgfA0KICAgICAgICAgICAgICAgICAgICAgICAgZm9yZW5hbWU9PSJGT1hUUk9URklWRUVJR0hUIiB8DQogICAgICAgICAgICAgICAgICAgICAgICBmb3JlbmFtZT09Ik5PVCBVU0VEIiB8DQogICAgICAgICAgICAgICAgICAgICAgICBmb3JlbmFtZT09IlVOS05PV04iIHwNCiAgICAgICAgICAgICAgICAgICAgICAgIGZvcmVuYW1lPT0iUE9SQ0lORSBUUkFDSEVBKE5FTykiIHwNCiAgICAgICAgICAgICAgICAgICAgICAgIHN1cm5hbWU9PSI1OTk1NzUzIENUT1giIHwNCiAgICAgICAgICAgICAgICAgICAgICAgIHBhdGllbnRfcG9zdGNvZGU9PSJaOTk5IikNCiAgDQogIG91dCA8LSBkYnMudHJhY2luZy5maWVsZHMuZnVuKG91dCkNCn0NCg0KDQojIC8vLy8vLy8vLy8vLy8vLy8vDQojIHNwZWNpZnkgbmVzdGVkIGZ1bmN0aW9uIHRvIHByZXBhcmUgTElNUyBsaW5lbGlzdCBkYXRhIGZpZWxkIGZvcm1hdHRpbmcgZm9yIERCUyB0cmFjaW5nIA0KZGJzLnRyYWNpbmcuZmllbGRzLmZ1biA8LSBmdW5jdGlvbih4KXsNCiAgDQogIGxpbXNfZm9yX3RyYWNlIDwtIHggJT4lIGFycmFuZ2UocGlkX3N0cikgJT4lICAjIGFycmFuZ2UgYnkgcGF0aWVudCBJRA0KICAgIHJlbmFtZShJZGVudGlmaWVyPXBpZF9zdHIsDQogICAgICAgICAgIERhdGVfb2ZfQmlydGg9ZG9iLA0KICAgICAgICAgICBOSFNfbnVtYmVyPW5oc25vLA0KICAgICAgICAgICBTdXJuYW1lPXN1cm5hbWUsDQogICAgICAgICAgIEZvcmVuYW1lPWZvcmVuYW1lLA0KICAgICAgICAgICBTZXg9c2V4KSAlPiUgDQogICAgbXV0YXRlKG51bGwxPSIiLA0KICAgICAgICAgICBudWxsMj0iIiwNCiAgICAgICAgICAgbnVsbDM9IiIsDQogICAgICAgICAgIG51bGw0PSIiLA0KICAgICAgICAgICBudWxsNT0iIiwNCiAgICAgICAgICAgU2V4PShpZl9lbHNlKFNleD09Ik0iLCIxIixTZXgpKSwgICAgICMgMSA9IG1hbGUsIDIgPSBmZW1hbGUsIE5BID0gdW5rbm93bg0KICAgICAgICAgICBTZXg9KGlmX2Vsc2UoU2V4PT0iRiIsIjIiLFNleCkpLA0KICAgICAgICAgICBTZXg9KGlmX2Vsc2UoU2V4PT0iVSIsIiIsU2V4KSksDQogICAgICAgICAgIFNleD1hcy5udW1lcmljKFNleCksDQogICAgICAgICAgIERhdGVfb2ZfQmlydGg9YXMuRGF0ZShEYXRlX29mX0JpcnRoLCAiJVklbSVkIiksDQogICAgICAgICAgIERhdGVfb2ZfQmlydGg9YXMuY2hhcmFjdGVyKHN0cl9yZXBsYWNlX2FsbChzdHJpbmc9RGF0ZV9vZl9CaXJ0aCwgcGF0dGVybj0iLSIsIHJlcGxhY2VtZW50ID0gIiIpICksDQogICAgICAgICAgIElkZW50aWZpZXI9YXMubnVtZXJpYyhJZGVudGlmaWVyKSkgJT4lIA0KICAgIHNlbGVjdChudWxsMSxJZGVudGlmaWVyLERhdGVfb2ZfQmlydGgsbnVsbDIsbnVsbDMsTkhTX251bWJlcixTdXJuYW1lLG51bGw0LEZvcmVuYW1lLG51bGw1LFNleCkgJT4lIGFycmFuZ2UoSWRlbnRpZmllcikNCiAgIyBmaWx0ZXJfYXQoLnZhcnMgPSB2YXJzKERhdGVfb2ZfQmlydGgsTkhTX251bWJlciksIC52YXJzX3ByZWRpY2F0ZSA9IGFueV92YXJzKCFpcy5uYSguKSkpICAjIHJlbW92ZSBpbnN0YW5jZXMgb2YgZXBpc29kZSBsYWNraW5nIHBvc3Rjb2RlICYgTkhTDQogIA0KICANCiAgbGltc19mb3JfdHJhY2UkRm9yZW5hbWUgPC0gdG91cHBlcihnc3ViKCJcXC4iLCAiIiwgbGltc19mb3JfdHJhY2UkRm9yZW5hbWUpKQ0KICBsaW1zX2Zvcl90cmFjZSRTdXJuYW1lIDwtIHRvdXBwZXIoZ3N1YigiXFwuIiwgIiIsIGxpbXNfZm9yX3RyYWNlJFN1cm5hbWUpKQ0KICANCiAgcmV0dXJuKGxpbXNfZm9yX3RyYWNlKQ0KICANCn0NCg0KDQojIC8vLy8vLy8vLy8vLy8vLy8vDQojIHNwZWNpZnkgd3JhcC1hcm91bmQgZnVuY3Rpb24gdG8gYXBwbHkgdW1icmVsbGEgYW5kIG5lc3RlZCBmdW5jdGlvbnMgZm9yIERCUyB0cmFjaW5nIGRhdGEgcHJlcA0KZGJzLnRyYWNlLnByZXAuZnVuIDwtIGZ1bmN0aW9uKHgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFpbmZpbGU9bWFpbmZpbGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXhjbHVkZWZpbGU9ZXhjbHVkZWZpbGUpeyAgIyB3aGVyZSBtYWluIGlucHV0IGlzIEdyb3VwQ0dfbGluZWxpc3QNCiAgDQogIGV4Y2x1ZGVkX3JlY29yZHMgPC0gaW5pdGlhbC5saW1zLmRxLmZ1bih4KSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGdlbmVyYXRlIHN1YnNldCBvZiBkYXRhIHdoZXJlIG9uZSBvciBtb3JlIGtleSBmaWVsZHMgbWlzc2luZyBmb3IgdHJhY2UgLSBzZW5kIHRoZXNlIGZvciB0cmFjaW5nIGlmIG1pbmltdW0gbmVjZXNzYXJ5IFBJSSANCiAgDQogICMgZ2VuZXJhdGUgbWFpbiBkZiBmb3IgdHJhY2luZw0KICBsaW1zX2Zvcl90cmFjZSA8LSBsaW1zLmZvcnRyYWNlLmZ1bih4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4Y2x1ZGVkX3JlY29yZHMpDQogIA0KICAjIGdlbmVyYXRlIGV4Y2x1ZGVkIHJlY29yZHMgZGYgZm9yIHRyYWNpbmcNCiAgZXhjbHVkZWRfcmVjb3Jkc190cmFjZSA8LSBleGNsLnJlY29yZHMudHJhY2UuZnVuKHgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBleGNsdWRlZF9yZWNvcmRzKQ0KICANCiAgIyBpZGVudGlmeSBwcm9iYWJsZSBRQyByZXBvcnRzIGFuZCBleGNsdWRlIHRoZXNlIGZyb20gZGF0YXNldHMgdG8gdHJhY2UNCiAgZXFhX3JlY29yZHMgPC0gZXFhLnJlY29yZC5mdW4oeCkNCiAgDQogICMgZXhjbHVkZSBwcm9iYWJsZSBRQyBzYW1wbGUgcmVwb3J0cyBmcm9tIGRhdGEgdG8gYmUgc2VudCBmb3IgdHJhY2luZw0KICBsaW1zX2Zvcl90cmFjZSA8LSBsaW1zX2Zvcl90cmFjZSAlPiUgZmlsdGVyKCFJZGVudGlmaWVyICVpbiUgZXFhX3JlY29yZHMkSWRlbnRpZmllcikgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgZXhjbHVkZWRfcmVjb3Jkc190cmFjZSA8LSBleGNsdWRlZF9yZWNvcmRzX3RyYWNlICU+JSBmaWx0ZXIoIUlkZW50aWZpZXIgJWluJSBlcWFfcmVjb3JkcyRJZGVudGlmaWVyKQ0KDQogIA0KICAjIGV4cG9ydCB0byBjc3YgZm9yIHNlbmRpbmcgdG8gREJTIHRlYW0NCiAgd3JpdGUuY3N2KGxpbXNfZm9yX3RyYWNlLCBmaWxlID0gbWFpbmZpbGUsIHJvdy5uYW1lcyA9IEYpDQogIHdyaXRlLmNzdihleGNsdWRlZF9yZWNvcmRzX3RyYWNlLCBmaWxlID0gZXhjbHVkZWZpbGUsIHJvdy5uYW1lcyA9IEYpDQp9DQoNCg0KIy8vLy8vLy8vLy8vLw0KIy0tbWFpbiB1bWJyZWxsYSBmdW5jdGlvbiB0byBwcmVwYXJlIGxpbmVsaXN0IGRhdGEgZm9yIERCUyB0cmFjaW5nDQpsaW1zLmZvcnRyYWNlLmZ1biA8LSBmdW5jdGlvbih4LCAjIHdoZXJlIHggaXMgR3JvdXBDR19saW5lbGlzdA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4Y2x1ZGVkX3JlY29yZHMpeyANCiAgDQogIGxpbXNfZm9yX3RyYWNlIDwtIHggJT4lIGZpbHRlcighcGlkX3N0ciAlaW4lIGV4Y2x1ZGVkX3JlY29yZHMkcGlkX3N0cikgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBleGNsdWRlIGFib3ZlIHJlY29yZHMgZnJvbSBkYXRhIHRvIGJlIHNlbnQgZm9yIHRyYWNpbmcNCiAgDQogIGxpbXNfZm9yX3RyYWNlIDwtIGRicy50cmFjaW5nLmZpZWxkcy5mdW4obGltc19mb3JfdHJhY2UpIH0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBwcmVwYXJlIGRhdGEgZm9ybWF0dGluZyBmb3IgdHJhY2luZw0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byBwcmVwYXJlIGV4Y2x1ZGVkIGRhdGEgZm9yIERCUyB0cmFjaW5nDQpleGNsLnJlY29yZHMudHJhY2UuZnVuIDwtIGZ1bmN0aW9uKHgsICMgd2hlcmUgeCBpcyBMSU1TIGxpbmVsaXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4Y2x1ZGVkX3JlY29yZHMpew0KICANCiAgZXhjbHVkZWRfcmVjb3Jkc190cmFjZSA8LSB4ICU+JSBmaWx0ZXIoKHBpZF9zdHIgJWluJSBleGNsdWRlZF9yZWNvcmRzJHBpZF9zdHIpKSAgICAgICAgICAgICAjIHByZXBhcmUgZXhjbHVkZWQgcmVjb3JkcyBmb3IgdHJhY2luZyBmb3IgUElJIGVucmljaG1lbnQgKHRoZXNlIG1heSBzdGlsbCBiZSB0cmFjZWQgYWx0aG91Z2ggaGlnaGVyIHByb2JhYmlsaXR5IG9mIGZhaWx1cmUpDQogIA0KICBleGNsdWRlZF9yZWNvcmRzX3RyYWNlIDwtIGRicy50cmFjaW5nLmZpZWxkcy5mdW4oZXhjbHVkZWRfcmVjb3Jkc190cmFjZSkgICAgICAgICAgICAgICAgICAgICMgcHJlcGFyZSBkYXRhIGZvcm1hdHRpbmcgZm9yIHRyYWNpbmcNCn0NCg0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byBmb3JtYXQgdHJhY2UgcmV0dXJuIGNvbG5hbWVzDQpoZWFkZXIuZnVuIDwtIGZ1bmN0aW9uKHgsICAgIyB3aGVyZSB4IGlzIHRyYWNlIHJlc3BvbnNlIGRhdGENCiAgICAgICAgICAgICAgICAgICAgICAgeT1pbXBvcnQoaGVyZSgiRGF0YV9jYXJyaWFnZSIsIkRCU190cmFjaW5nIiwidHJhY2VfcmVzcG9uc2VfaGVhZGVycy54bHN4IiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvcm1hdCA9ICJ4bHN4IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQuY2hhcj0iIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEgPSBjKCIiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2tpcCA9IDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGwubmFtZXMgPSBUKSl7ICAjIHdoZXJlIHkgaXMgcmVzcG9uc2UgaGVhZGVycyANCiAgDQogIA0KICANCiAgIyAjIGFzc2lnbiBoZWFkZXIgY29sbmFtZXMNCiAgIyBjb2xuYW1lcyh5KSA8LSBhcy5jaGFyYWN0ZXIoeVsxLF0pDQogIA0KICAjIHJldGFpbiBvbmx5IHN1Y2Nlc3NmdWwgdHJhY2VzIGFuZCBkcm9wIGFkZGl0aW9uYWwgY29sdW1uIGluIHRyYWNlIHJlc3BvbnNlIGRhdGEgc28gdGhhdCBuY29sIGlzIDYwDQogIHggPC0geCAlPiUgZmlsdGVyKFYxPT0iMjAiIHwgVjE9PSIzMCIgfCBWMT09IjMzIiB8IFYxPT0iNDAiKSAlPiUgc2VsZWN0KC1hbnlfb2YoYygiVjYxIikpKSAgICAgICAgICAgICAgICAgICMgcmV0YWluaW5nIHRob3NlIGNhc2VzIHRoYXQgaGF2ZSBiZWVuIHN1Y2Nlc3NmdWxseSB0cmFjZWQNCiAgDQogICMgZGVmaW5lIGNoYXIgdmVjIG9mIHRyYWNlIHJlc3BvbnNlIGhlYWRlcnMNCiAgaGVhZGVycyA8LSBjb2xuYW1lcyh5KSAgICAgIA0KICANCiAgIyBjb25maXJtIGxlbmd0aCBvZiBoZWFkZXJzIGlzIHNhbWUgYXMgbnVtYmVyIG9mIGNvbHVtbnMgaW4gdHJhY2VfcmVzcG9uc2UgDQogIHByaW50KGxlbmd0aChoZWFkZXJzKSkNCiAgDQogICMgZGVmaW5lIGNoYXIgdmVjIG9mIHJlZHVuZGFudCBjb2wgbmFtZXMgKFYxLCBWMiBldGMpDQogIG9sZG5hbWVzIDwtIGNvbG5hbWVzKHgpDQogIA0KICAjIHJlbmFtZSBjb2x1bW5zIGluIHRyYWNlIHJlc3BvbnNlIGRhdGEgYmFzZWQgb24gaGVhZGVycw0KICB4IDwtIHggJT4lIA0KICAgIHJlbmFtZV9hdCh2YXJzKG9sZG5hbWVzKSwgfiBoZWFkZXJzKQ0KfQ0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byBmb3JtYXQgdHJhY2UgcmV0dXJuIGNvbG5hbWVzIGZvciBzdWJzZXQgb2YgcmV0dXJuIHdoZXJlIHRyYWNlIHVuc3VjY2Vzc2Z1bA0KIG5vdHJhY2UuaGVhZGVyLmZ1biA8LSBmdW5jdGlvbih4LCAgICMgd2hlcmUgeCBpcyB0cmFjZSByZXNwb25zZSBkYXRhDQogICAgICAgICAgICAgICAgICAgICAgIHk9aW1wb3J0KGhlcmUoIkRhdGFfY2FycmlhZ2UiLCJEQlNfdHJhY2luZyIsInRyYWNlX3Jlc3BvbnNlX2hlYWRlcnMueGxzeCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQgPSAieGxzeCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBULA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21tZW50LmNoYXI9IiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hID0gYygiIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNraXAgPSAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsLm5hbWVzID0gVCkpeyAgIyB3aGVyZSB5IGlzIHJlc3BvbnNlIGhlYWRlcnMgDQogIA0KICANCiAgDQogICMgIyBhc3NpZ24gaGVhZGVyIGNvbG5hbWVzDQogICMgY29sbmFtZXMoeSkgPC0gYXMuY2hhcmFjdGVyKHlbMSxdKQ0KICANCiAgIyBkcm9wIGFkZGl0aW9uYWwgY29sdW1uIGluIHRyYWNlIHJlc3BvbnNlIGRhdGEgc28gdGhhdCBuY29sIGlzIDYwDQogIHggPC0geCAlPiUgc2VsZWN0KC1hbnlfb2YoYygiVjYxIikpKSAgICAgICAgICAgICANCiAgDQogICMgZGVmaW5lIGNoYXIgdmVjIG9mIHRyYWNlIHJlc3BvbnNlIGhlYWRlcnMNCiAgaGVhZGVycyA8LSBjb2xuYW1lcyh5KSAgICAgIA0KICANCiAgIyBjb25maXJtIGxlbmd0aCBvZiBoZWFkZXJzIGlzIHNhbWUgYXMgbnVtYmVyIG9mIGNvbHVtbnMgaW4gdHJhY2VfcmVzcG9uc2UgDQogIHByaW50KGxlbmd0aChoZWFkZXJzKSkNCiAgDQogICMgZGVmaW5lIGNoYXIgdmVjIG9mIHJlZHVuZGFudCBjb2wgbmFtZXMgKFYxLCBWMiBldGMpDQogIG9sZG5hbWVzIDwtIGNvbG5hbWVzKHgpDQogIA0KICAjIHJlbmFtZSBjb2x1bW5zIGluIHRyYWNlIHJlc3BvbnNlIGRhdGEgYmFzZWQgb24gaGVhZGVycw0KICB4IDwtIHggJT4lIA0KICAgIHJlbmFtZV9hdCh2YXJzKG9sZG5hbWVzKSwgfiBoZWFkZXJzKQ0KfQ0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byBjbGVhbiB2YXIgbmFtZXMgZm9yIHRyYWNlZCByZXR1cm4NCmNsZWFuLm5hbWVzLmZ1biA8LSBmdW5jdGlvbih4KXsNCiAgIyBjbGVhbiB1cCBjb2wgbmFtZXMgKHJlcGxhY2UgaW5zdGFuY2VzIG9mIFsuLi5dIHdpdGggc3BhY2VzKQ0KICBuYW1lcyh4KSA8LSBzdHJfcmVwbGFjZShuYW1lcyh4KSwgIlxcWyIsICIiKQ0KICBuYW1lcyh4KSA8LSBzdHJfcmVwbGFjZShuYW1lcyh4KSwgIlxcXSIsICIiKQ0KICBzdHJpcHMgPC0gIl5cXHMrfFxccyskIg0KICBuYW1lcyh4KSA8LSBnc3ViKHN0cmlwcywiIixuYW1lcyh4KSkNCiAgbmFtZXMoeCkgPC0gZ3N1YigiW1s6c3BhY2U6XV0iLCJfIixuYW1lcyh4KSkNCiAgeCA8LSB4DQp9DQoNCg0KIyAvLy8vLy8vLy8vLy8vLy8vLw0KIyBzcGVjaWZ5IGZ1bmN0aW9uIHRvIGNvcnJlY3RseSBmb3JtYXQgdHJhY2VkIGRhdGEgcHJpb3IgdG8gbWVyZ2Ugd2l0aCBvcmlnaW5hbCBsaW5lbGlzdA0KdHJhY2UucmVmb3JtYXQuZnVuIDwtIGZ1bmN0aW9uKHgpew0KICB4IDwtIHggJT4lIG11dGF0ZShEYXRlX29mX0RlYXRoPWFzLmNoYXJhY3RlcihEYXRlX29mX0RlYXRoKSkgJT4lIA0KICAgICMgbXV0YXRlKERPQl9TPWFzLmNoYXJhY3RlcihET0JfUykpICU+JSANCiAgICBtdXRhdGUoRE9CX1M9YXMuRGF0ZShhcy5jaGFyYWN0ZXIoRE9CX1MpLCBmb3JtYXQgPSAiJVklbSVkIikpICU+JSAgIyB0YWtlcyBEQlMgdHJhY2UgZGF0ZSBmb3JtYXQgKGUuZy4sIDE5ODkxMjIyKSBhbmQgY29udmVydHMgdG8gZGF0ZSBlLmcuLCAxOTg5LTEyLTIyDQogICAgcmVuYW1lKHRyYWNlZD1gSU5TRVJUX0lOVE9fREJTcmVzcG9uc2VfKF9SZXNwb25zZV9UeXBlYCkgJT4lIA0KICAgIHJlbmFtZShwaWRfc3RyPVVuaXF1ZV9SZWNvcmRfSUQpICU+JSANCiAgICBzZWxlY3QocGlkX3N0cixOSFNfTnVtYmVyLERPQl9TLEZpcnN0X05hbWVfUyxTdXJuYW1lX1MsU2V4X1MsUG9zdENvZGUpICU+JQ0KICAgIG11dGF0ZV9hbGwobmFfaWYsIiIpDQp9DQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSB3cmFwIGZ1bmN0aW9uIHRvIHJldGFpbiByZWNvcmRzIHdoaWNoIHdlcmVuJ3Qgc3VjY2Vzc2Z1bGx5IHRyYWNlZA0Kbm8udHJhY2Uud3JhcC5mdW4gPC0gZnVuY3Rpb24oeCl7ICAjIHdoZXJlIGlucHV0IGlzIGxpbXNfdHJhY2VfcmV0dXJuDQogIG5vX3RyYWNlIDwtIHggJT4lIGZpbHRlcihWMSE9IjIwIiAmIFYxIT0iMzAiICYgVjEhPSIzMyIgJiBWMSE9IjQwIikNCiAgbm9fdHJhY2UgPC0gbm90cmFjZS5oZWFkZXIuZnVuKG5vX3RyYWNlKQ0KICBub190cmFjZSA8LSBjbGVhbi5uYW1lcy5mdW4obm9fdHJhY2UpDQogIG5vX3RyYWNlIDwtIHRyYWNlLnJlZm9ybWF0LmZ1bihub190cmFjZSkNCiAgcmV0dXJuKG5vX3RyYWNlKQ0KfQ0KDQojIC8vLy8vLy8vLy8vLy8vLy8vDQojIHNwZWNpZnkgd3JhcCBmdW5jdGlvbiB0byByZXRhaW4gcmVjb3JkcyB3aGljaCBoYWQgdW5rbm93biBwYXRpZW50IHNleA0KdW5rbm93bi5zZXgud3JhcC5mdW4gPC0gZnVuY3Rpb24oeCl7ICAjIHdoZXJlIGlucHV0IGlzIGxpbXNfdHJhY2VfcmV0dXJuDQogIG5vX3RyYWNlIDwtIHggJT4lIGZpbHRlcihWMTM9PSIxIiB8IFYxMz09IjMiKQ0KICBub190cmFjZSA8LSBub3RyYWNlLmhlYWRlci5mdW4obm9fdHJhY2UpDQogIG5vX3RyYWNlIDwtIGNsZWFuLm5hbWVzLmZ1bihub190cmFjZSkNCiAgbm9fdHJhY2UgPC0gdHJhY2UucmVmb3JtYXQuZnVuKG5vX3RyYWNlKQ0KICByZXR1cm4obm9fdHJhY2UpDQp9DQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSB3cmFwIGZ1bmN0aW9uIHRvIHByZXBhcmUgYW5kIGpvaW4gdHJhY2UgcmV0dXJuIHdpdGggTElNUyBsaW5lbGlzdA0KdHJhY2VkLndyYXAuZnVuIDwtIGZ1bmN0aW9uKHgpeyAjIHdoZXJlIGlucHV0IGlzIGxpbXNfdHJhY2VfcmV0dXJuDQogIGxpbXNfdHJhY2VfcmV0dXJuIDwtIGhlYWRlci5mdW4oeCkNCiAgbGltc190cmFjZV9yZXR1cm4gPC0gY2xlYW4ubmFtZXMuZnVuKGxpbXNfdHJhY2VfcmV0dXJuKQ0KICBsaW1zX3RyYWNlX3JldHVybiA8LSB0cmFjZS5yZWZvcm1hdC5mdW4obGltc190cmFjZV9yZXR1cm4pDQogIG1lcmdlZF9kZiA8LSByaWdodF9qb2luKGxpbXNfdHJhY2VfcmV0dXJuLGRpc3RpbmN0X3BhdGllbnRzLGJ5PSJwaWRfc3RyIikgJT4lIA0KICAgIG11dGF0ZShOSFNfTnVtYmVyPWFzLm51bWVyaWMoTkhTX051bWJlcikpICU+JSANCiAgICBtdXRhdGUobmhzbm8gPSBjYXNlX3doZW4oDQogICAgIWlzLm5hKE5IU19OdW1iZXIpIH4gTkhTX051bWJlciwNCiAgICBUUlVFIH4gbmhzbm8pLA0KICAgIHNleCA9IGNhc2Vfd2hlbigNCiAgICAgIFNleF9TPT0yIH4gIkYiLA0KICAgICAgVFJVRSB+IHNleA0KICAgICksDQogICAgdHJhY2VkID0gY2FzZV93aGVuKA0KICAgICAgaXMubmEoTkhTX051bWJlcikgfiAiTiIsDQogICAgICAhaXMubmEoTkhTX051bWJlcikgfiAiWSINCiAgICApDQogICkgJT4lIHNlbGVjdCgtYygiTkhTX051bWJlciIsIkRPQl9TIiwiRmlyc3RfTmFtZV9TIiwiU3VybmFtZV9TIiwiU2V4X1MiKSkgJT4lDQogICAgbXV0YXRlKHBhdGllbnRfcG9zdGNvZGU9UG9zdENvZGUpICU+JSBzZWxlY3QoLWMoIlBvc3RDb2RlIikpDQogIHJldHVybihtZXJnZWRfZGYpDQp9DQoNCg0KDQojIC8vLy8vLy8vLy8vLy8vLy8vDQojIHNwZWNpZnkgZnVuY3Rpb24gdG8gZ2VuZXJhdGUgYWdlIGdyb3VwaW5ncyAoOCBncm91cHMpDQphZ2Vncm91cC5mdW4gPC0gZnVuY3Rpb24oeCl7DQogIA0KICB4IDwtIHggJT4lIG11dGF0ZShhZ2Vncm91cD1jYXNlX3doZW4oYWdlIDwxIH4gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChhZ2UgPj0xICYgYWdlIDw9IDQpIH4gMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChhZ2UgPj01ICYgYWdlIDw9IDkpIH4gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChhZ2UgPj0xMCAmIGFnZSA8PSAxNCkgfiA0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGFnZSA+PTE1ICYgYWdlIDw9IDQ0KSB+IDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoYWdlID49NDUgJiBhZ2UgPD0gNjQpIH4gNiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChhZ2UgPj02NSAmIGFnZSA8PSA3NCkgfiA3LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGFnZSA+PTc1ICYgYWdlIDw9MTIzKSB+IDgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoaXMubmEoYWdlKSB+IDApKSkNCiAgDQogIHgkYWdlZ3JvdXAgPC0gZmFjdG9yKHgkYWdlZ3JvdXAsIA0KICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHM9YygnMCcsJzEnLCcyJywnMycsJzQnLCc1JywnNicsJzcnLCc4JyksDQogICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIm5vdCBrbm93biIsIjwxIiwiMSB0byA0IiwiNSB0byA5IiwiMTAgdG8gMTQiLCIxNSB0byA0NCIsIjQ1IHRvIDY0IiwiNjUgdG8gNzQiLCI+PSA3NSIpKQ0KICBvdXQgPC0geA0KfQ0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byBmdXJ0aGVyIHByZXBhcmUgTElNUyBsaW5lbGlzdCBkYXRhIGZvciBsaW5rYWdlIChldGhuaWNpdHkgYW5kIElNRCkNCmxpbmVsaXN0LnByZXAuZnVuIDwtIGZ1bmN0aW9uKHgpew0KICANCiAgeCA8LSB4ICU+JSBtdXRhdGUoZG9iID0gbHVicmlkYXRlOjp5bWQoZG9iKSkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGdlbmVyYXRlIGRhdGUgb2YgYmlydGggdmFyaWFibGUgaW4gZGF0ZSBmb3JtYXQNCiAgICBtdXRhdGUoc3BlY19kYXRlID0gbHVicmlkYXRlOjp5bWQoc3BlY2RhdGUpLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgZ2VuZXJhdGUgZGF0ZSB2YXJpYWJsZSBpbiBkYXRlIGZvcm1hdCBmcm9tIGV4aXN0aW5nIGRhdGUgY2hhciB2YXINCiAgICAgICAgICAgIyBZZWFyID0gYXMubnVtZXJpYyhmb3JtYXQoRGF0ZSwgJyVZJykpLCANCiAgICAgICAgICAgYWdlID0gYXMubnVtZXJpYyhkaWZmdGltZShzcGVjZGF0ZSxkb2IpLHVuaXQ9IndlZWtzIikvNTIuMjUsICAgICAgICAgICAgICAgICAgICAgICMgZ2VuZXJhdGUgYWdlIHZhcmlhYmxlICh5ZWFycywgbnVtZXJpYykNCiAgICAgICAgICAgYWdlID0gcm91bmQoYWdlLCBkaWdpdHM9MCksDQogICAgICAgICAgIGRvYl9zcGVjZGF0ZSA9IGNhc2Vfd2hlbihkb2I9PXNwZWNkYXRlIH4gInllcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkb2IhPXNwZWNkYXRlIH4gIm5vIiksDQogICAgICAgICAgIHNleCA9IGlmX2Vsc2Uoc2V4PT0iTSIsIk1hbGUiLHNleCksDQogICAgICAgICAgIHNleCA9IGlmX2Vsc2Uoc2V4PT0iRiIsIkZlbWFsZSIsc2V4KSwNCiAgICAgICAgICAgc2V4ID0gaWZfZWxzZShzZXg9PSJVIiwiVW5rbm93biIsc2V4KSkgJT4lIA0KICAgIG11dGF0ZShuPTEpDQogIA0KICB4IDwtIGFnZWdyb3VwLmZ1bih4KSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgYXBwbHkgYWdlZ3JvdXAgZnVuY3Rpb24gDQogIA0KICBvdXQgPC0geA0KICANCn0NCg0KDQoNCiMgLy8vLy8vLy8vLy8vLy0tLS0tLS0tLS0tLSBJTUQgRU5SSUNITUVOVCAtLS0tLS0tLS0tLS0gLy8vLy8vLy8vLy8vLy8vLy8NCg0KDQojIC8vLy8vLy8vLy8vLy8vLy8vDQojIHNwZWNpZnkgZnVuY3Rpb24gdG8gbGluayBsaW5lbGlzdCB0byBJTUQgdXNpbmcgbGFiIHBvc3Rjb2RlIGFuZCBDQ0cgTFNPQSBpbnRlcm1lZGlhdGUgbG9va3Vwcw0KaW1kLmxpbmthZ2UuZnVuIDwtIGZ1bmN0aW9uKHgsICMgaW5wdXQgaXMgYWxsc2l0ZXMgKExJTVMgbGluZWxpc3QgcG9zdC1EQlMgbGlua2FnZSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJfbG9va3VwX3F1ZXJ5ID0gIlNFTEVDVCBbTGFiX0dlb2dyYXBoeV9Db2RlXSAsW0xhYl9HZW9ncmFwaHlfTmFtZV9DdXJyZW50XSAsW1BPU1RDT0RFXSBhcyBsYWJwb3N0Y29kZSBGUk9NIFtTR1NTRFddLltkYm9dLltESU1FTlNJT05fTEFCX0dFT0dSQVBIWV0gd2hlcmUgTG9naWNhbF9EZWxldGVfRmxhZyA8PiAoJzEnKSBBTkQgUE9TVENPREUgPD4gKCdOVUxMJykiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG15Y29uID0gZGJDb25uZWN0KG9kYmM6Om9kYmMoKSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGNvbm5lY3Rpb24gdG8gU0dTU0RXDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmNvbm5lY3Rpb25fc3RyaW5nID0gIkRSSVZFUj1TUUwgU2VydmVyOw0KICAgICAgICAgICAgICAgICAgIERBVEFCQVNFPVNHU1NEVzsNCiAgICAgICAgICAgICAgICAgICBUcnVzdGVkX0Nvbm5lY3Rpb249WWVzOw0KICAgICAgICAgICAgICAgICAgIFNFUlZFUj1zZ3NzZGIucGhlLmdvdi51azsiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHBjZDJfbHNvYV9sb29rdXAgPSByZWFkX2R0YShmaWxlPScvL0NPTEhQQUZJTDAwMy5IUEEub3JnLnVrL1Byb2plY3REYXRhL0hDQUkvU2NpZW50aWZpYyByZXNvdXJjZXMvR0lTL09OUyBsb29rdXBzL05IU1BEX01BWV8yMDIyX1VLX0ZVTEwvRGF0YS9wY2RfQ0NHX2xzb2FfbG9va3VwLmR0YScpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlc19xcnkgPSBnbHVlKA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNFTEVDVCBbcGNkMl0sIFtjdHJ5XSAsW25oc2VyXSAsW2xzb2ExMV0gLFtpY2JdLCBbb2FjMTFdIEZST00gW0xvb2t1cHNTaGFyZWRdLltkYm9dLlt2T05TX05TUEwxMV9VS18yMDIzLjA1XSINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICApLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlc19xcnkyID0gZ2x1ZSgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTRUxFQ1QgW3BjZDJdLFtMU09BMTFDRF0sW0lNRDIwMTlfRGVjaWxlc19MU09BMTFfRW5nbGFuZF0sW0lNRDIwMTlfUXVpbnRpbGVzX0xTT0ExMV9FbmdsYW5kXSxbY3RyeV0gRlJPTSBbTG9va3Vwc1NoYXJlZF0uW2Rib10uW3ZTb2Npb0RlbW9nX0xTT0ExMV0gSU5ORVIgSk9JTiBbdk9OU19OU1BMX1VLXzIwMjIwNV0gb24gW2xzb2ExMV0gPSBbTFNPQTExQ0RdIHdoZXJlIFtjdHJ5XSBJTiAoJ0U5MjAwMDAwMScpIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcnhfZHNuID0gIkxvb2t1cHNTaGFyZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfbGFrZV9jb25uZWN0ID0gcGFzdGUwKCdkcml2ZXI9e1NRTCBTZXJ2ZXJ9O3NlcnZlcj1TUUxDbHVzQ29sTEsxOVxcTGFrZTE5OycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2RhdGFiYXNlPScscnhfZHNuLCc7dHJ1c3RlZF9jb25uZWN0aW9uPXRydWUnKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZXNfY29uID0gb2RiYzo6ZGJDb25uZWN0KG9kYmM6Om9kYmMoKSwuY29ubmVjdGlvbl9zdHJpbmc9ZGF0YV9sYWtlX2Nvbm5lY3QpKXsgICAgIyBjb25uZWN0aW9uIHRvIEhFUw0KICANCiAgIyAvLy8gTGFiIHBvc3Rjb2RlIGxvb2t1cCBmb3IgbWVyZ2UNCiAgIyBwZXJmb3JtIGV4dHJhY3QNCiAgIyBteWNvbiA8LSBteWNvbg0KICANCiAgbGFiX2xvb2t1cF9vdXQgPC0gZGJTZW5kUXVlcnkobXljb24sIGxhYl9sb29rdXBfcXVlcnkpDQogIA0KICBsYWJfbG9va3VwIDwtIGRiRmV0Y2gobGFiX2xvb2t1cF9vdXQpDQogIGRiQ2xlYXJSZXN1bHQgKGxhYl9sb29rdXBfb3V0KQ0KICBvZGJjOjpkYkRpc2Nvbm5lY3QobXljb24pDQogIA0KICAjIE5FRUQgVE8gTUFOVUFMTFkgTE9PSyBVUCBMQUJHRU9DT0RFIEZPUiBHQlMgRUNNIFNJVEVTIElOIExJTkVMSVNUIEFORCBVU0UgVEhJUyBUTyBNRVJHRSBPTiBMT09LVVANCiAgDQogICMgbWVyZ2UgbGluZWxpc3Qgd2l0aCBsYWJfbG9va3VwIG9uIGxhYmdlb2NvZGUNCiAgbGFiX2xvb2t1cCA8LSBsYWJfbG9va3VwICU+JSByZW5hbWUobGFiZ2VvY29kZT1MYWJfR2VvZ3JhcGh5X0NvZGUpDQogIA0KICAjIGFkZCBpbiBsYWJnZW9jb2RlcyBmb3IgRUNNIHNpdGVzDQogIHggPC0geCAlPiUgbXV0YXRlKGxhYmdlb2NvZGUgPSBjYXNlX3doZW4oDQogICAgc2l0ZT09IlJveWFsIFN0b2tlIiB+ICI1OTc5NTUiLCAgI1JPWUFMIFNUT0tFIFVOSVZFUlNJVFkgSE9TUElUQUwNCiAgICBzaXRlPT0iQmVkZm9yZCIgfiAiMjM1NjE1IiwgICMgTFVUT04gTUlDUk9CSU9MT0dZIExBQk9SQVRPUlkNCiAgICBzaXRlPT0iRGVycmlmb3JkIiB+ICI1MjY3NjUiLCAjIERFUlJJRk9SRCBIT1NQLiAoUExZTU9VVEgpDQogICAgc2l0ZT09IkZyZWVtYW4iIH4gIjA1NjEzMCIsICAjIEZSRUVNQU4gSE9TUElUQUwgKE5FV0NBU1RMRSBVUE9OIFRZTkUpDQogICAgc2l0ZT09IlJveWFsIEJsYWNrYnVybiIgfiAiNjcwODYxIiwgIyBST1lBTCBCTEFDS0JVUk4NCiAgICBzaXRlPT0iRXBzb20iIH4gIjM3NTEyMCIsICMgRVBTT00gKEVQU09NIEFORCBTVCBIRUxJRVIpDQogICAgc2l0ZT09IkJpcm1pbmdoYW0iIH4gIjYxMDY0MCIpICAgIyBCSVJNSU5HSEFNIChCSVJNSU5HSEFNIFdPTUVOIEFORCBDSElMRFJFTlMpDQogICkgDQogIA0KICB4IDwtIG1lcmdlKHgsbGFiX2xvb2t1cCxieT0ibGFiZ2VvY29kZSIpDQogIA0KICAjIC8vLyBQQ0QyIExTT0EgbG9va3VwIGZvciBtZXJnZSANCiAgIyBwZXJmb3JtIGV4dHJhY3QNCiAgDQogIHBjZDJfbHNvYV9xcnkgPC0gZGJTZW5kUXVlcnkoaGVzX2NvbiwgaGVzX3FyeSkNCiAgDQogIHBjZDJfbHNvYV9sb29rdXAgPC0gZGJGZXRjaChwY2QyX2xzb2FfcXJ5KQ0KICBkYkNsZWFyUmVzdWx0IChwY2QyX2xzb2FfcXJ5KQ0KICANCiAgDQogIHBjZDJfbHNvYV9sb29rdXAkcGNkMiA8LSBzdHJfcmVwbGFjZV9hbGwoc3RyaW5nPXBjZDJfbHNvYV9sb29rdXAkcGNkMiwgcGF0dGVybj0iICAiLCByZXBsPSIiKSAgICMgcmVtb3ZlIHdoaXRlIHNwYWNlcyBpbiBwb3N0Y29kZSBmaWVsZA0KICBwY2QyX2xzb2FfbG9va3VwJHBjZDIgPC0gc3RyX3JlcGxhY2VfYWxsKHN0cmluZz1wY2QyX2xzb2FfbG9va3VwJHBjZDIsIHBhdHRlcm49IiAiLCByZXBsPSIiKSAgICAjIHJlbW92ZSB3aGl0ZSBzcGFjZXMgaW4gcG9zdGNvZGUgZmllbGQNCiAgDQogIHBjZDJfbHNvYV9sb29rdXAgPC0gcGNkMl9sc29hX2xvb2t1cCAlPiUgZmlsdGVyKGN0cnk9PSJFOTIwMDAwMDEiKSAlPiUgYXJyYW5nZShsc29hMTEpDQogIA0KICAjIGRlZmluZSBkZXJpdmVkcG9zdGNvZGUgdmFyIGluIGxpbmVsaXN0IGFuZCBwcmVmZXJlbnRpYWxseSBhc3NpZ24gYXMgcGF0aWVudCBwb3N0Y29kZSA+IGxhYnBvc3Rjb2RlDQogIHggPC0geCAlPiUgbXV0YXRlKGRlcml2ZWRwb3N0Y29kZT1jYXNlX3doZW4oDQogICAgZ3JlcGwoIl5aWjk5IixwYXRpZW50X3Bvc3Rjb2RlKSB8IHBhdGllbnRfcG9zdGNvZGU9PSJObyBEYXRhIEF2YWxpYWJsZSIgfiBsYWJwb3N0Y29kZSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbm8gcGF0aWVudCBwb3N0Y29kZSBpbiBMSU1TIEVDTSBkYXRhDQogICAgKCFpcy5uYShwYXRpZW50X3Bvc3Rjb2RlKSAmICFncmVwbCgiXlpaOTkiLHBhdGllbnRfcG9zdGNvZGUpKSB+IHBhdGllbnRfcG9zdGNvZGUsDQogICAgaXMubmEocGF0aWVudF9wb3N0Y29kZSkgfiBsYWJwb3N0Y29kZSkpICU+JQ0KICAgIHJlbmFtZShwY2QyPWRlcml2ZWRwb3N0Y29kZSkgJT4lICAgICAgICAgIyByZW5hbWUgZGVyaXZlZHBvc3Rjb2RlIHByaW9yIHRvIG1lcmdlDQogICAgbXV0YXRlKHBjZDI9c3RyX3JlcGxhY2VfYWxsKHBjZDIsICIgIiwgIiIpKQ0KICANCiAgIyBlbnN1cmUgaW5zdGFuY2VzIG9mIHBhdGllbnRfcG9zdGNvZGU9PSIiLCBsYWJwb3N0Y29kZSB1c2VkDQogIHggPC0geCAlPiUgbXV0YXRlKHBjZDIgPSBjYXNlX3doZW4oDQogICAgcGF0aWVudF9wb3N0Y29kZT09IiIgfiBsYWJwb3N0Y29kZSwNCiAgICBUUlVFIH4gcGNkMg0KICApKQ0KDQogIA0KICAjIG1lcmdlIHdpdGggQ0NHIExTT0EgbG9va3VwIGJhc2VkIG9uIHBjZDIgKGRlcml2ZWQgcG9zdGNvZGUpDQogIHggPC0gbWVyZ2UoeCxwY2QyX2xzb2FfbG9va3VwLGJ5PSJwY2QyIixhbGw9VCkNCiAgDQogIHggPC0geCAlPiUgZmlsdGVyKCFpcy5uYShwaWRfc3RyKSkNCiAgDQogICMgLy8vIGdlbmVyYXRlIGR5bmFtaWMgTFNPQSBJTUQgY29kZSBsb29rdXANCiAgDQogICMgcGVyZm9ybSBleHRyYWN0DQogIGxzb2FfSU1EX3FyeSA8LSBkYlNlbmRRdWVyeShoZXNfY29uLCBoZXNfcXJ5MikNCiAgDQogIGxzb2FfSU1EX2xvb2t1cCA8LSBkYkZldGNoKGxzb2FfSU1EX3FyeSkNCiAgZGJDbGVhclJlc3VsdCAobHNvYV9JTURfcXJ5KQ0KICBvZGJjOjpkYkRpc2Nvbm5lY3QoaGVzX2NvbikNCiAgDQogIA0KICBsc29hX0lNRF9sb29rdXAkcGNkMiA8LSBzdHJfcmVwbGFjZV9hbGwoc3RyaW5nPWxzb2FfSU1EX2xvb2t1cCRwY2QyLCBwYXR0ZXJuPSIgICIsIHJlcGw9IiIpICAgIyByZW1vdmUgd2hpdGUgc3BhY2VzIGluIHBvc3Rjb2RlIGZpZWxkDQogIGxzb2FfSU1EX2xvb2t1cCRwY2QyIDwtIHN0cl9yZXBsYWNlX2FsbChzdHJpbmc9bHNvYV9JTURfbG9va3VwJHBjZDIsIHBhdHRlcm49IiAiLCByZXBsPSIiKSAgICAjIHJlbW92ZSB3aGl0ZSBzcGFjZXMgaW4gcG9zdGNvZGUgZmllbGQNCiAgDQogIGxzb2FfSU1EX2xvb2t1cCA8LSBsc29hX0lNRF9sb29rdXAgJT4lIGZpbHRlcihjdHJ5PT0iRTkyMDAwMDAxIikgDQogIA0KICAjIG1lcmdlIHdpdGggTFNPQSBJTUQgbG9va3VwIGJhc2VkIG9uIHBjZDIgKGRlcml2ZWQgcG9zdGNvZGUpDQogIHggPC0gbWVyZ2UoeCxsc29hX0lNRF9sb29rdXAsYnk9InBjZDIiLGFsbD1UKQ0KICB4IDwtIHggJT4lIGZpbHRlcighaXMubmEocGlkX3N0cikpDQogIHggPC0geFshZHVwbGljYXRlZChjb2xuYW1lcyh4KSldDQogIA0KICAjIHJlYXNzaWduIGFzIGxpbmVsaXN0DQogIHJldHVybih4KQ0KICANCn0NCg0KDQojIC8vLy8vLy8vLy8vLy8tLS0tLS0tLS0tLS0gRVRITklDSVRZIEVOUklDSE1FTlQgLS0tLS0tLS0tLS0tIC8vLy8vLy8vLy8vLy8vLy8vDQoNCg0KIyAvLy8vLy8vLy8vLy8vLy8vLw0KIyBzcGVjaWZ5IGZ1bmN0aW9uIHRvIHByZXBhcmUgTElNUyBsaW5lbGlzdCBkYXRhIGZvciBldGhuaWNpdHkgZW5yaWNobWVudA0KZGF0YWxpbmsucHJlcC5mdW4gPC0gZnVuY3Rpb24oeCl7DQogICMgY2hlY2sgbmFtZXMgb2YgZGF0ZSBvZiBiaXJ0aCBhbmQgbmhzIG51bWJlciBmaWVsZHMuDQogIHhuZXcgPC0geCAlPiUgbXV0YXRlKGRhdGVvZmJpcnRoID0gIGFzLkRhdGUoZG9iLCBmb3JtYXQgPSAiJVktJW0tJWQiKSkNCiAgeG5ldyRQYXRpZW50X05IU19ObyA8LSBhcy5jaGFyYWN0ZXIoeG5ldyRuaHNubykNCiAgb3V0IDwtIHhuZXcNCn0NCg0KDQojIC8vLy8vLy8vLy8vLy8vLy8vDQojIHNwZWNpZnkgZnVuY3Rpb24gdG8gbGluayBMSU1TIGxpbmVsaXN0IHRvIEhFUyBldGhuaWNpdHkgZmllbGRzIGJ5IGNyZWF0aW5nIGEgdGVtcG9yYXJ5IHRhYmxlIGluIFNRTCANCmhlcy5ldGhubGluay5mdW4gPC0gZnVuY3Rpb24oeCwgICMgd2hlcmUgbWFpbiBpbnB1dCBpcyBMSU1TIGxpbmVsaXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJ4X2RzbiA9ICJZMDgwX1VJRF9QSUQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2xha2VfY29ubmVjdCA9IHBhc3RlMCgnZHJpdmVyPXtTUUwgU2VydmVyfTtzZXJ2ZXI9U1FMQ2x1c0NvbExLMTlcXExha2UxOTsnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnZGF0YWJhc2U9JyxyeF9kc24sJzt0cnVzdGVkX2Nvbm5lY3Rpb249dHJ1ZScpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2xha2UgPSBvZGJjOjpkYkNvbm5lY3Qob2RiYzo6b2RiYygpLC5jb25uZWN0aW9uX3N0cmluZz1kYXRhX2xha2VfY29ubmVjdCksICAgICAgICAjIGNyZWF0ZSBEYXRhTGFrZSBjb25uZWN0aW9uIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQSUlfZGF0YV9uYW1lID0gcGFzdGUwKCJKUl90YmxfIixmb3JtYXQoU3lzLkRhdGUoKSwgIiV5JW0lZCIpLCJfIixmb3JtYXQoU3lzLnRpbWUoKSwgIiVIXyVNIikpLCAgICAgICAgIyBQSUkgdG8gdXBsb2FkIHRvIERhdGFMYWtlOiB0YWJsZSBuYW1lIG5lZWRzIHRvIGJlIHVuaXF1ZSBhbmQgbm90IHByZXZpb3VzbHkgb24gdGhlIERhdGFiYXNlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldF90YWJsZSA9IHBhc3RlMCgiSlJfdXBsZF8iLGZvcm1hdChTeXMuRGF0ZSgpLCAiJXklbSVkIiksIl8iLGZvcm1hdChTeXMudGltZSgpLCAiJUhfJU0iKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlc19xcnkgPSBnbHVlKA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTRUxFQ1QgKg0KSU5UTyBbWTA4MF9VSURfUElEXS5bZGJvXS5be3RhcmdldF90YWJsZX1dICANCg0KRlJPTSAoDQoNClNFTEVDVCBESVNUSU5DVCANCiAgICAgICB1cGxvYWRlZGRhdGEuKiANCgkgLCBldGhudHAuVE9LRU5fUEVSU09OX0lEDQoJICwgZXRobnRwLk5FV19FVEhOT1MgLS0gRXRobmljaXR5OyBjb2RlDQoJICwgZXRobnRwLk5FV19FVEhOT1NfRXRobmljX3N1Ymdyb3VwIA0KCSAsIGV0aG50cC5ORVdfRVRITk9TX0V0aG5pY19ncm91cA0KDQpGUk9NIFtZMDgwX1VJRF9QSURdLltkYm9dLlt7UElJX2RhdGFfbmFtZX1dIHVwbG9hZGVkZGF0YQ0KDQpMRUZUIEpPSU4gW1kwODBfVUlEX1BJRF0uW2Rib10uW3ZZMDgwX1VJRF9IRVNPUF9QSURdIGFwY3BpZCBPTiBhcGNwaWQuTkVXTkhTTk8gPSB1cGxvYWRlZGRhdGEuUGF0aWVudF9OSFNfTm8gQU5EIENPTlZFUlQoREFURSwgQ0FTVChhcGNwaWQuRE9CX0RWIEFTIERBVEUpLDMpID0gdXBsb2FkZWRkYXRhLmRhdGVvZmJpcnRoIC0tIGxpbmtlZCBvbiBOSFMgTnVtYmVyICYgRG9CDQoNCkxFRlQgSk9JTiBbWTA4OV9ETGFrZUxpbmtlZFJlcG9fUElEXS5bZGJvXS5beF9IRVNfQW5hbHlzaXNfUHNldWRvX3ZZMDg5X0hFU19FdGhub3NfVFBJRF0gZXRobnRwIE9OIGFwY3BpZC5UT0tFTl9QRVJTT05fSUQgPSBldGhudHAuVE9LRU5fUEVSU09OX0lEDQoNCikgQVMgaGVzIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKXsNCiAgDQogIHggPC0gZGF0YWxpbmsucHJlcC5mdW4oeCkgICAgIyBwcmVwYXJlIGxpbmVsaXN0IGRhdGEgZm9yIEhFUyBsaW5rYWdlDQogIA0KICBvZGJjOjpkYkxpc3RUYWJsZXMoY29ubj1kYXRhX2xha2UsIGNhdGFsb2dfbmFtZT1yeF9kc24sIHNjaGVtYV9uYW1lPSJkYm8iKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDaGVjayBuYW1lcyBvZiB0aGUgdGFibGVzIGluIHRoZSBkYXRhYmFzZQ0KICANCiAgZGJDcmVhdGVUYWJsZShjb25uPWRhdGFfbGFrZSwgbmFtZSA9IFBJSV9kYXRhX25hbWUsIGZpZWxkcyA9IHgsIG92ZXJ3cml0ZSA9IFQpDQogIA0KICB0YWJsZWlkIDwtIElkKHNjaGVtYSA9ICJkYm8iLCB0YWJsZSA9IFBJSV9kYXRhX25hbWUpDQogIA0KICBkYldyaXRlVGFibGUoZGF0YV9sYWtlLCB0YWJsZWlkLCB4LCBhcHBlbmQgPSBUUlVFLCByb3cubmFtZXMgPSBGKQ0KICANCiAgbGlua2VkX2V0aG4gPC0gZGJTZW5kUXVlcnkoZGF0YV9sYWtlLGhlc19xcnkpDQogIGxpbmtlZF9ldGhuIDwtIGRiR2V0UXVlcnkoZGF0YV9sYWtlLGdsdWUoInNlbGVjdCAqIGZyb20ge3RhcmdldF90YWJsZX0iKSkNCiAgDQogIA0KICAjfn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fiMNCiAgDQogIHRhYmxlX25hbWUgPC0gdGFyZ2V0X3RhYmxlICAgDQogIA0KICAjIG91dHB1dCB0YWJsZSBnZW5lcmF0ZWQgZnJvbSBxdWVyeQ0KICBldGhuaWNpdHlfbGlua2VkX2RhdGEgPC0gZGJSZWFkVGFibGUoZGF0YV9sYWtlLHRhYmxlX25hbWUpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEV4dHJhY3RpbmcgdGhlIGV0aG5pY2l0eSBsaW5rZWQgZGF0YSBmcm9tIHRoZSBEYXRhTGFrZQ0KICBldGhuaWNpdHlfbGlua2VkX2RhdGEgPC0gZGlzdGluY3QoZXRobmljaXR5X2xpbmtlZF9kYXRhKQ0KICANCiAgDQogICMgZ2V0IHJpZCBvZiB0ZW1wb3JhcnkgdGFibGVzIA0KICBkYlNlbmRRdWVyeShkYXRhX2xha2UsIGdsdWUoImRyb3AgdGFibGUge3RhcmdldF90YWJsZX0iKSkNCiAgZGJTZW5kUXVlcnkoZGF0YV9sYWtlLCBnbHVlKCJkcm9wIHRhYmxlIHtQSUlfZGF0YV9uYW1lfSIpKQ0KICANCiAgIyBkaXNjb25uZWN0IGZyb20gREwNCiAgZGJEaXNjb25uZWN0KGRhdGFfbGFrZSkNCiAgDQogIHdyaXRlLmNzdihldGhuaWNpdHlfbGlua2VkX2RhdGEsICJEYXRhX2NhcnJpYWdlL0xpbmVsaXN0L2V0aG5saW5rZWRfaW50ZXJtZWRpYXRlLmNzdiIsIHJvdy5uYW1lcz1GKQ0KICANCiAgIyByZWFzc2lnbiB0byBkYXRhIGRmDQogIHggPC0gYXNfdGliYmxlKGV0aG5pY2l0eV9saW5rZWRfZGF0YSkNCiAgDQogICMgY2xlYW4gZXRobmljaXR5IGxpbmtlZCBsaW5lbGlzdA0KICB4IDwtIGV0aGxpbmtlZC5kYXRhLmNsZWFuLmZ1bih4KQ0KICANCiAgIyBmb3JtYXQgSU1EIHF1aW50aWxlIHZhcmlhYmxlIGFzIGZhY3RvciB3aXRoIGxhYmVscw0KICB4IDwtIGltZC5mYWMubGFiZWwuZnVuKHgpDQogIA0KICByZXR1cm4oeCkNCiAgDQp9DQoNCg0KIyAvLy8vLy8vLy8vLy8vLy8vLw0KIyBzcGVjaWZ5IGZ1bmN0aW9uIHRvIGxpbmsgTElNUyBsaW5lbGlzdCB0byBFQ0RTIGV0aG5pY2l0eSBmaWVsZHMgYnkgY3JlYXRpbmcgYSB0ZW1wb3JhcnkgdGFibGUgaW4gU1FMIA0KZWNkcy5ldGhubGluay5mdW4gPC0gZnVuY3Rpb24oeCwgICMgd2hlcmUgbWFpbiBpbnB1dCBpcyBMSU1TIGxpbmVsaXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICByeF9kc24gPSAiWTA4MF9VSURfUElEIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfbGFrZV9jb25uZWN0ID0gcGFzdGUwKCdkcml2ZXI9e1NRTCBTZXJ2ZXJ9O3NlcnZlcj1TUUxDbHVzQ29sTEsxOVxcTGFrZTE5OycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnZGF0YWJhc2U9JyxyeF9kc24sJzt0cnVzdGVkX2Nvbm5lY3Rpb249dHJ1ZScpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9sYWtlID0gb2RiYzo6ZGJDb25uZWN0KG9kYmM6Om9kYmMoKSwuY29ubmVjdGlvbl9zdHJpbmc9ZGF0YV9sYWtlX2Nvbm5lY3QpLCAgICAgICAgIyBjcmVhdGUgRGF0YUxha2UgY29ubmVjdGlvbiANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBJSV9kYXRhX25hbWUgPSBwYXN0ZTAoIkpSX2VjZHNfdGJsXyIsZm9ybWF0KFN5cy5EYXRlKCksICIleSVtJWQiKSwiXyIsZm9ybWF0KFN5cy50aW1lKCksICIlSF8lTSIpKSwgICAgICAgICMgUElJIHRvIHVwbG9hZCB0byBEYXRhTGFrZTogdGFibGUgbmFtZSBuZWVkcyB0byBiZSB1bmlxdWUgYW5kIG5vdCBwcmV2aW91c2x5IG9uIHRoZSBEYXRhYmFzZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0X3RhYmxlID0gcGFzdGUwKCJKUl9lY2RzX3VwbGRfIixmb3JtYXQoU3lzLkRhdGUoKSwgIiV5JW0lZCIpLCJfIixmb3JtYXQoU3lzLnRpbWUoKSwgIiVIXyVNIikpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVzX3FyeSA9IGdsdWUoDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTRUxFQ1QgKg0KICBJTlRPIFtZMDgwX1VJRF9QSURdLltkYm9dLlt7dGFyZ2V0X3RhYmxlfV0gLS0gbmV3IHRhYmxlIG9mIGxpbmtlZCBkYXRhLCBtYWtlIHN1cmUgaXQgaXMgYSBuZXcgbmFtZQ0KDQpGUk9NICgNCiAgDQogIFNFTEVDVCBESVNUSU5DVCBlc3BhbXIuKg0KICAgIC0tICwgZXRobnRwLltQYXRpZW50VXN1YWxBZGRyZXNzSW1kRGVjaWxlXQ0KICAtLSAsIGV0aG50cC5bUGF0aWVudFVzdWFsQWRkcmVzc0ltZERlY2lsZURlc2NyaXB0aW9uXQ0KICAtLSAsIGV0aG50cC5bUGF0aWVudFVzdWFsQWRkcmVzc1J1cmFsVXJiYW5JbmRpY2F0b3JdDQogICwgZXRobnRwLltQYXRpZW50RXRobmljQ2F0ZWdvcnlDb2RlXQ0KICANCiAgDQogIEZST00gW1kwODBfVUlEX1BJRF0uW2Rib10uW3tQSUlfZGF0YV9uYW1lfV0gZXNwYW1yDQogIA0KICANCiAgTEVGVCBKT0lOIFtZMDgwX1VJRF9QSURdLltkYm9dLlt2WTA5Ml9FQ0RTX1NVU19QSURdIGV0aG50cCBPTiBldGhudHAuTmhzTnVtYmVyID0gZXNwYW1yLlBhdGllbnRfTkhTX05vIEFORCBDT05WRVJUKERBVEUsIENBU1QoZXRobnRwLlBhdGllbnRCaXJ0aERhdGUgQVMgREFURSksMykgPSBlc3BhbXIuZGF0ZW9mYmlydGggIA0KICANCikgQVMgaGVzIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSl7DQogIA0KICB4IDwtIGRhdGFsaW5rLnByZXAuZnVuKHgpICAgICMgcHJlcGFyZSBsaW5lbGlzdCBkYXRhIGZvciBIRVMgbGlua2FnZQ0KICANCiAgb2RiYzo6ZGJMaXN0VGFibGVzKGNvbm49ZGF0YV9sYWtlLCBjYXRhbG9nX25hbWU9cnhfZHNuLCBzY2hlbWFfbmFtZT0iZGJvIikgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ2hlY2sgbmFtZXMgb2YgdGhlIHRhYmxlcyBpbiB0aGUgZGF0YWJhc2UNCiAgDQogIGRiQ3JlYXRlVGFibGUoY29ubj1kYXRhX2xha2UsIG5hbWUgPSBQSUlfZGF0YV9uYW1lLCBmaWVsZHMgPSB4LCBvdmVyd3JpdGUgPSBUKQ0KICANCiAgdGFibGVpZCA8LSBJZChzY2hlbWEgPSAiZGJvIiwgdGFibGUgPSBQSUlfZGF0YV9uYW1lKQ0KICANCiAgZGJXcml0ZVRhYmxlKGRhdGFfbGFrZSwgdGFibGVpZCwgeCwgYXBwZW5kID0gVFJVRSwgcm93Lm5hbWVzID0gRikNCiAgDQogIGxpbmtlZF9lY2RzX2V0aG4gPC0gZGJTZW5kUXVlcnkoZGF0YV9sYWtlLGhlc19xcnkpDQogIGxpbmtlZF9lY2RzX2V0aG4gPC0gZGJHZXRRdWVyeShkYXRhX2xha2UsZ2x1ZSgic2VsZWN0ICogZnJvbSB7dGFyZ2V0X3RhYmxlfSIpKQ0KICANCiAgDQogICN+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+Iw0KICANCiAgdGFibGVfbmFtZSA8LSB0YXJnZXRfdGFibGUgICANCiAgDQogICMgb3V0cHV0IHRhYmxlIGdlbmVyYXRlZCBmcm9tIHF1ZXJ5DQogIGVjZHNfZXRobmljaXR5X2xpbmtlZF9kYXRhIDwtIGRiUmVhZFRhYmxlKGRhdGFfbGFrZSx0YWJsZV9uYW1lKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBFeHRyYWN0aW5nIHRoZSBldGhuaWNpdHkgbGlua2VkIGRhdGEgZnJvbSB0aGUgRGF0YUxha2UNCiAgZWNkc19ldGhuaWNpdHlfbGlua2VkX2RhdGEgPC0gZGlzdGluY3QoZWNkc19ldGhuaWNpdHlfbGlua2VkX2RhdGEpDQogIA0KICANCiAgIyBnZXQgcmlkIG9mIHRlbXBvcmFyeSB0YWJsZXMgDQogIGRiU2VuZFF1ZXJ5KGRhdGFfbGFrZSwgZ2x1ZSgiZHJvcCB0YWJsZSB7dGFyZ2V0X3RhYmxlfSIpKQ0KICBkYlNlbmRRdWVyeShkYXRhX2xha2UsIGdsdWUoImRyb3AgdGFibGUge1BJSV9kYXRhX25hbWV9IikpDQogIA0KICAjIGRpc2Nvbm5lY3QgZnJvbSBETA0KICBkYkRpc2Nvbm5lY3QoZGF0YV9sYWtlKQ0KICANCiAgIyByZWFzc2lnbiB0byBkYXRhIGRmDQogIHggPC0gYXNfdGliYmxlKGVjZHNfZXRobmljaXR5X2xpbmtlZF9kYXRhKQ0KICANCiAgeCA8LSB4ICU+JSByZW5hbWUoZXRobl9jb2RlPVBhdGllbnRFdGhuaWNDYXRlZ29yeUNvZGUpDQogIA0KICByZXR1cm4oeCkNCiAgDQp9DQoNCg0KIyAvLy8vLy8vLy8vLy8vLy8vLw0KIyBzcGVjaWZ5IGZ1bmN0aW9uIHRvIGNsZWFuIGV0aG5pY2l0eSBsaW5rZWQgTElNUyBsaW5lbGlzdCBwcmlvciB0byByZWdyb3VwaW5nIGV0aG5pY2l0eQ0KZXRobGlua2VkLmRhdGEuY2xlYW4uZnVuIDwtIGZ1bmN0aW9uKHgpeyAgICAgICMgd2hlcmUgbWFpbiBpbnB1dCBpcyBldGhuaWNpdHktZW5yaWNoZWQgTElNUyBsaW5lbGlzdA0KICANCiAgIyBpbmNsdWRlIHBpZF9zdHIgYW5kIEhFUyB0b2tlbiBJRCBpbiBzZXBhcmF0ZSBsb29rdXANCiAgDQogIHggPC0geCAlPiUgc2VsZWN0KGFueV9vZihjKCJwaWRfc3RyIiwieWVhciIsInNpdGUiLCJMYWJfR2VvZ3JhcGh5X05hbWVfQ3VycmVudCIsInNwZWNkYXRlIiwiZG9iX3NwZWNkYXRlIiwic3BlY3R5cGUiLCJzcGVjaWVzIiwiZm9yZW5hbWUiLCJzdXJuYW1lIiwibmhzbm8iLCJQYXRpZW50X05IU19ObyIsImRvYiIsImFnZSIsImFnZWdyb3VwIiwiZGF0ZW9mYmlydGgiLCJzZXgiLCJJTUQyMDE5X1F1aW50aWxlc19MU09BMTFfRW5nbGFuZCIsIkV0aG5pY2l0eV9ncm91cCIsIk5FV19FVEhOT1NfRXRobmljX2dyb3VwIiwiTkVXX0VUSE5PU19FdGhuaWNfc3ViZ3JvdXAiLCJGYWN0X29mX0RlYXRoIiwiRGF0ZV9vZl9EZWF0aCIpKSkgJT4lDQogICAgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoIklNRDIwMTlfUXVpbnRpbGVzX0xTT0ExMV9FbmdsYW5kIikpLCB+ICJpbWRfcXVpbnRpbGUiKSAlPiUgICAgICAgIyBORVhUOiBhcHBseSByZW5hbWVfYXQgdG8gZmFjdG9mZGVhdGggYW5kIGRhdGVvZmRlYXRoDQogICAgIyByZW5hbWVfYXQodmFycyhzdGFydHNfd2l0aCgiRmFjdF9vZl9EZWF0aCIpKSwgfiAiZmFjdG9mZGVhdGgiKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3B0aW9uYWw6IG1heSBub3QgaGF2ZSBkZWF0aCBmaWVsZCBpbiBsaW5lbGlzdCAobGVzcyBsaWtlbHkgZm9yIHdvbWVuIG9mIGNoaWxkIGJlYXJpbmcgYWdlKQ0KICAgICMgcmVuYW1lX2F0KHZhcnMoc3RhcnRzX3dpdGgoIkRhdGVfb2ZfRGVhdGgiKSksIH4gImRhdGVvZmRlYXRoIikgJT4lIA0KICAgIHJlbmFtZV9hdCh2YXJzKHN0YXJ0c193aXRoKCJORVdfRVRITk9TX0V0aG5pY19zdWJncm91cCIpKSwgfiAiRXRobmljX2dyb3VwIikgJT4lIA0KICAgICMgbXV0YXRlKGFjcm9zcygoY29udGFpbnMoImZhY3RvZmRlYXRoIikgfCBjb250YWlucygiRmFjdF9vZl9EZWF0aCIpKSwgLmZucyA9IHRlbXBfZnVuLCAubmFtZXMgPSAiZmFjdG9mZGVhdGgiKSkgJT4lIA0KICAgICMgbXV0YXRlKGZhY3RvZmRlYXRoPWNhc2Vfd2hlbihmYWN0b2ZkZWF0aD09IkQiIH4gMSwNCiAgICAjICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEoZmFjdG9mZGVhdGgpIH4gMCkpICU+JQ0KICAgIG11dGF0ZShub3RlID0gY2FzZV93aGVuKA0KICAgICAgZGF0ZW9mYmlydGg9PSJOQSIgfCBpcy5uYShkYXRlb2ZiaXJ0aCkgfCBuaHNubz09Ik5BIiB8IGlzLm5hKG5oc25vKSB+ICJpbnN1ZmZpY2llbnQgUElJIGZvciBldGhuIHRyYWNlIiwNCiAgICAgICFpcy5uYShuaHNubykgfCAhaXMubmEoZGF0ZW9mYmlydGgpIH4gIlBJSSBhdmFpbGFibGUgZm9yIGV0aG4gdHJhY2UiLA0KICAgICAgIWlzLm5hKG5oc25vKSAmIG5oc25vIT0iTkEiICYgIWlzLm5hKGRhdGVvZmJpcnRoKSAmIGRhdGVvZmJpcnRoIT0iTkEiIH4gImV0aG5pY2l0eSBub3QgYXZhaWxhYmxlIikNCiAgICApDQp9DQoNCg0KDQojIC8vLy8vLy8vLy8vLy8vLy8vDQojIHNwZWNpZnkgZnVuY3Rpb24gdG8gZ2VuZXJhdGUgYmluYXJ5ICh3aGl0ZSB2cyBub24td2hpdGUpIGV0aG5pY2l0eSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBmb3IgbW9kZWwNCmV0aG4uYmluYXJ5LmZhYy5mdW4gPC0gZnVuY3Rpb24oeCl7DQogIA0KICB4JGhlc19ldGhuaWNpdHkgPC0gZmN0X2NvbGxhcHNlKHgkaGVzX2V0aG5pY2l0eSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAid2hpdGUiID0gYygiV2hpdGUiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTm9ud2hpdGUiID0gYygiQmxhY2siLCJBc2lhbiIsIk90aGVyIiwiTWl4ZWQiKSkNCiAgcmV0dXJuKHgpDQp9IA0KDQoNCiMgLy8vLy8vLy8vLy8vLy8vLy8NCiMgc3BlY2lmeSBmdW5jdGlvbiB0byBhc3NpZ24gSU1EIHF1aW50aWxlIGZpZWxkIGluIGxpbmVsaXN0IGFzIGZhY3RvciB3aXRoIGNvcnJlc3BvbmRpbmcgbGFiZWxzDQppbWQuZmFjLmxhYmVsLmZ1biA8LSBmdW5jdGlvbih4KXsNCiAgeCRpbWRfcXVpbnRpbGUgPC0gZmFjdG9yKHgkaW1kX3F1aW50aWxlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscz1jKCcxJywnMicsJzMnLCc0JywnNScpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiMjAlIG1vc3QgZGVwcml2ZWQiLCIyMCUgdG8gNDAlIiwiNDAlIHRvIDYwJSIsIjYwJSB0byA4MCUiLCIyMCUgbGVhc3QgZGVwcml2ZWQiKSkNCiAgb3V0IDwtIHgNCn0NCg0KDQpsaW5lbGlzdC5ldGhjYXQ1LmZ1biA8LSBmdW5jdGlvbih4KXsNCiAgDQogIGV0aG4udmVjIDwtIHNvcnQodW5pcXVlKHgkTkVXX0VUSE5PU19FdGhuaWNfZ3JvdXApKSANCiAgDQogICMgWzFdICI5OSBOb3Qga25vd24iICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgIyBbMl0gIkFueSBvdGhlciBldGhuaWMgZ3JvdXAiICAgICAgICAgICAgICAgICAgICAgIA0KICAjIFszXSAiQXNpYW4gLyBBc2lhbiBCcml0aXNoIiAgICAgICAgICAgICAgICAgICAgICAgDQogICMgWzRdICJCbGFjayAvIEFmcmljYW4gLyBDYXJpYmJlYW4gLyBCbGFjayBCcml0aXNoIg0KICAjIFs1XSAiTWl4ZWQgLyBNdWx0aXBsZSBldGhuaWMgZ3JvdXBzIiAgICAgICAgICAgICAgDQogICMgWzZdICJXaGl0ZSIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAjIFs3XSAiWiBOb3Qgc3RhdGVkIiAgIA0KICANCiAgeCA8LSB4ICU+JSBtdXRhdGUoaGVzX2V0aG5pY2l0eT0iIikgDQogIA0KICAjIGZvciB1bmtub3duIGdyb3VwIGFuZCBub3QgbGlua2VkDQogIHggPC0geCAlPiUNCiAgICBtdXRhdGUoaGVzX2V0aG5pY2l0eSA9IGNhc2Vfd2hlbigNCiAgICAgIE5FV19FVEhOT1NfRXRobmljX2dyb3VwICVpbiUgYyhldGhuLnZlY1sxXSwgZXRobi52ZWNbN10pIH4gIlVua25vd24iLA0KICAgICAgaXMubmEoTkVXX0VUSE5PU19FdGhuaWNfZ3JvdXApIH4gIk5vdCBsaW5rZWQiLA0KICAgICAgVFJVRSB+IGhlc19ldGhuaWNpdHkNCiAgICApKQ0KICANCiAgIyBmb3IgQmxhY2ssIEJsYWNrIEJyaXRpc2gsIENhcmliYmVhbiBvciBBZnJpY2FuIGdyb3VwDQogIHggPC0geCAlPiUNCiAgICBtdXRhdGUoaGVzX2V0aG5pY2l0eSA9IGNhc2Vfd2hlbigNCiAgICAgIE5FV19FVEhOT1NfRXRobmljX2dyb3VwICVpbiUgZXRobi52ZWNbNF0gfiAiQmxhY2siLA0KICAgICAgVFJVRSB+IGhlc19ldGhuaWNpdHkNCiAgICApKQ0KICANCiAgIyBmb3IgTWl4ZWQNCiAgeCA8LSB4ICU+JQ0KICAgIG11dGF0ZShoZXNfZXRobmljaXR5ID0gY2FzZV93aGVuKA0KICAgICAgTkVXX0VUSE5PU19FdGhuaWNfZ3JvdXAgJWluJSBldGhuLnZlY1s1XSB+ICJNaXhlZCIsDQogICAgICBUUlVFIH4gaGVzX2V0aG5pY2l0eQ0KICAgICkpDQogIA0KICAjIGZvciBPdGhlcg0KICB4IDwtIHggJT4lDQogICAgbXV0YXRlKGhlc19ldGhuaWNpdHkgPSBjYXNlX3doZW4oDQogICAgICBORVdfRVRITk9TX0V0aG5pY19ncm91cCAlaW4lIGV0aG4udmVjWzJdIH4gIk90aGVyIiwNCiAgICAgIFRSVUUgfiBoZXNfZXRobmljaXR5DQogICAgKSkNCiAgDQogICMgZm9yIEFzaWFuLCBBc2lhbiBCcml0aXNoDQogIHggPC0geCAlPiUNCiAgICBtdXRhdGUoaGVzX2V0aG5pY2l0eSA9IGNhc2Vfd2hlbigNCiAgICAgIE5FV19FVEhOT1NfRXRobmljX2dyb3VwICVpbiUgZXRobi52ZWNbM10gfiAiQXNpYW4iLA0KICAgICAgVFJVRSB+IGhlc19ldGhuaWNpdHkNCiAgICApKQ0KICANCiAgIyBmb3IgV2hpdGUNCiAgeCA8LSB4ICU+JQ0KICAgIG11dGF0ZShoZXNfZXRobmljaXR5ID0gY2FzZV93aGVuKA0KICAgICAgTkVXX0VUSE5PU19FdGhuaWNfZ3JvdXAgJWluJSBldGhuLnZlY1s2XSB+ICJXaGl0ZSIsDQogICAgICBUUlVFIH4gaGVzX2V0aG5pY2l0eQ0KICAgICkpICU+JQ0KICAgIHNlbGVjdCgtYygiRXRobmljX2dyb3VwIiwiTkVXX0VUSE5PU19FdGhuaWNfZ3JvdXAiKSkgJT4lIG11dGF0ZSh6PTEpDQogIA0KICB4IDwtIGlmICgiRXRobmljaXR5X2dyb3VwIiAlaW4lIG5hbWVzKHgpKSB7ICAgICAgICAgICANCiAgICANCiAgICB4ICU+JSByZW5hbWUobGltX2V0aG5pY2l0eT1FdGhuaWNpdHlfZ3JvdXApIH0gZWxzZSB7ICAgICMgZHJvcCByZWR1bnRhbnQgRXRobmljaXR5IGFzc2lnbm1lbnQgY29sdW1ucw0KDQogICAgICB4IDwtIHgNCiAgICAgIA0KICAgIH0NCiAgDQogICMgYWRkIGluIGNvdW50IHZhcmlhYmxlIHByaW9yIHRvIGdyb3VwaW5nIGJ5IGV0aG5pY2l0eSBjYXRlZ29yeQ0KICANCn0NCg0KDQoNCg0KIyAvLy8vLy8vLy8vLy8vLS0tLS0tLS0tLS0tIEhFUyBBUEMgTElOS0FHRSBGT1IgUFJFR05BTkNZIElORElDQVRPUlMgLS0tLS0tLS0tLS0tIC8vLy8vLy8vLy8vLy8vLy8vDQoNCmhlc2FwYy5saW5rLmZ1biA8LSBmdW5jdGlvbih4LCAgIyB3aGVyZSBtYWluIGlucHV0IGlzIExJTVMgbGluZWxpc3QNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByeF9kc24gPSAiWTA4MF9VSURfUElEIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2xha2VfY29ubmVjdCA9IHBhc3RlMCgnZHJpdmVyPXtTUUwgU2VydmVyfTtzZXJ2ZXI9U1FMQ2x1c0NvbExLMTlcXExha2UxOTsnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdkYXRhYmFzZT0nLHJ4X2RzbiwnO3RydXN0ZWRfY29ubmVjdGlvbj10cnVlJyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YV9sYWtlID0gb2RiYzo6ZGJDb25uZWN0KG9kYmM6Om9kYmMoKSwuY29ubmVjdGlvbl9zdHJpbmc9ZGF0YV9sYWtlX2Nvbm5lY3QpLCAgICAgICAgIyBjcmVhdGUgRGF0YUxha2UgY29ubmVjdGlvbiANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBQSUlfZGF0YV9uYW1lID0gcGFzdGUwKCJKUl90YmxfIixmb3JtYXQoU3lzLkRhdGUoKSwgIiV5JW0lZCIpLCJfIixmb3JtYXQoU3lzLnRpbWUoKSwgIiVIXyVNIikpLCAgICAgICAgIyBQSUkgdG8gdXBsb2FkIHRvIERhdGFMYWtlOiB0YWJsZSBuYW1lIG5lZWRzIHRvIGJlIHVuaXF1ZSBhbmQgbm90IHByZXZpb3VzbHkgb24gdGhlIERhdGFiYXNlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0X3RhYmxlID0gcGFzdGUwKCJKUl91cGxkXyIsZm9ybWF0KFN5cy5EYXRlKCksICIleSVtJWQiKSwiXyIsZm9ybWF0KFN5cy50aW1lKCksICIlSF8lTSIpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZXNfcXJ5ID0gZ2x1ZSgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICItLSBTdGVwIDE6IENyZWF0ZSB0YXJnZXQgdGFibGUgaWYgaXQgZG9lc24ndCBleGlzdA0KQ1JFQVRFIFRBQkxFIFtZMDgwX1VJRF9QSURdLltkYm9dLlt7dGFyZ2V0X3RhYmxlfV0gKA0KICAgIFBhdGllbnRfTkhTX05vIFZBUkNIQVIoNTApLA0KICAgIGRhdGVvZmJpcnRoIERBVEUsDQogICAgc3BlY2RhdGUgREFURSwNCiAgICBGWUVBUiBJTlQsDQogICAgRVBJS0VZIFZBUkNIQVIoNTApLA0KICAgIERPQkJBQllfMV9EViBEQVRFLA0KICAgIEFETUlEQVRFIERBVEUsDQogICAgQURNSURBVEVfRFYgREFURSwNCiAgICBESVNEQVRFIERBVEUsDQogICAgRElTREFURV9EViBEQVRFLA0KICAgIEVQSVRZUEUgVkFSQ0hBUig1MCksDQogICAgT1BFUlROXzAxIFZBUkNIQVIoNTApLA0KICAgIE9QREFURV8wMSBEQVRFLA0KICAgIEJJUlNUQVRfMSBWQVJDSEFSKDUwKSwNCiAgICBERUxNRVRIX0QgVkFSQ0hBUig1MCksDQogICAgR0VTVEFUXzEgVkFSQ0hBUig1MCksDQogICAgRElBR18wMSBWQVJDSEFSKDUwKSwNCiAgICBESUFHXzAyIFZBUkNIQVIoNTApLA0KICAgIERJQUdfMDMgVkFSQ0hBUig1MCksDQogICAgRElBR18wNCBWQVJDSEFSKDUwKSwNCiAgICBESUFHXzA1IFZBUkNIQVIoNTApLA0KICAgIFBST0NPREUzIFZBUkNIQVIoNTApDQopOw0KDQotLSBTdGVwIDI6IEluc2VydCBkYXRhIGludG8gdGFyZ2V0IHRhYmxlIHVzaW5nIENURXMNCjtXSVRIIGZpbHRlcmVkX2hlc2FwYyBBUyAoDQogICAgU0VMRUNUIEZZRUFSLCBFUElLRVksIFBST0NPREUzDQogICAgRlJPTSBbSEVTX0FQQ10uW2Rib10uW3Z0SEVTX0FQQ10NCiAgICBXSEVSRSBGWUVBUiBJTiAoMjEyMiwgMjIyMywgMjMyNCkgQU5EIFBST0NPREUzIElOICgnUlRHJywgJ1JEOCcsICdSRlcnLCAnUjFIJywgJ1JYUicsICdSSlInLCAnUlEzJywgJ1JXWScsICdSWEYnLCAnUkM5JywgJ1IxSycsICdSVlInLCAnUlREJywgJ1JNMycsICdSVEsnLCAnUks5JywgJ1JCVCcpDQopLCAgICANCmZpbHRlcmVkX2FkbWlzIEFTICgNCiAgICBTRUxFQ1QgKg0KICAgIEZST00gW0hFU19BUENdLltkYm9dLlt2SEVTX0FQQ19GbGF0XQ0KICAgIFdIRVJFIEZZRUFSIElOICgyMTIyLCAyMjIzLCAyMzI0KSANCiksDQpmaWx0ZXJlZF9vcHJ0biBBUyAoDQogICAgU0VMRUNUICoNCiAgICBGUk9NIFtIRVNfQVBDXS5bZGJvXS5bdkhFU19BUENfT1BFUlROX0ZsYXRdDQogICAgV0hFUkUgRllFQVIgSU4gKDIxMjIsIDIyMjMsIDIzMjQpIA0KKSwNCmZpbHRlcmVkX21hdCBBUyAoDQogICAgU0VMRUNUICoNCiAgICBGUk9NIFtIRVNfQVBDXS5bZGJvXS5bdnRIRVNfQVBDX01BVF0NCiAgICBXSEVSRSBGWUVBUiBJTiAoMjEyMiwgMjIyMywgMjMyNCkNCiksDQpmaWx0ZXJlZF9kaWFnIEFTICgNCiAgICBTRUxFQ1QgKg0KICAgIEZST00gW0hFU19BUENdLltkYm9dLlt2SEVTX0FQQ19ESUFHX0ZsYXRdDQogICAgV0hFUkUgRllFQVIgSU4gKDIxMjIsIDIyMjMsIDIzMjQpIA0KKSwNCmZpbHRlcmVkX2FwY3BpZCBBUyAoDQogICAgU0VMRUNUICoNCiAgICBGUk9NIFtZMDgwX1VJRF9QSURdLltkYm9dLlt2WTA4MF9VSURfSEVTQVBDX1BJRF0NCikNCklOU0VSVCBJTlRPIFtZMDgwX1VJRF9QSURdLltkYm9dLlt7dGFyZ2V0X3RhYmxlfV0NClNFTEVDVCANCiAgICB1cGxvYWRlZGRhdGEuUGF0aWVudF9OSFNfTm8sIA0KICAgIHVwbG9hZGVkZGF0YS5kYXRlb2ZiaXJ0aCwgDQogICAgdXBsb2FkZWRkYXRhLnNwZWNkYXRlLCANCiAgICBhcGNwaWQuRllFQVIsIA0KICAgIGFwY3BpZC5FUElLRVksIA0KICAgIGFwY3BpZC5ET0JCQUJZXzFfRFYsIA0KICAgIGFkbWlzLkFETUlEQVRFLCANCiAgICBhZG1pcy5BRE1JREFURV9EViwgDQogICAgYWRtaXMuRElTREFURSwgDQogICAgYWRtaXMuRElTREFURV9EViwgDQogICAgYWRtaXMuRVBJVFlQRSwgDQogICAgb3BydG4uT1BFUlROXzAxLCANCiAgICBvcHJ0bi5PUERBVEVfMDEsIA0KICAgIG1hdC5CSVJTVEFUXzEsIA0KICAgIG1hdC5ERUxNRVRIX0QsIA0KICAgIG1hdC5HRVNUQVRfMSwNCiAgICBkaWFnLkRJQUdfMDEsDQogICAgZGlhZy5ESUFHXzAyLA0KICAgIGRpYWcuRElBR18wMywNCiAgICBkaWFnLkRJQUdfMDQsDQogICAgZGlhZy5ESUFHXzA1LA0KICAgIGhlc2FwYy5QUk9DT0RFMw0KRlJPTSBbWTA4MF9VSURfUElEXS5bZGJvXS5be1BJSV9kYXRhX25hbWV9XSBBUyB1cGxvYWRlZGRhdGENCkxFRlQgSk9JTiANCiAgICBmaWx0ZXJlZF9hcGNwaWQgQVMgYXBjcGlkIE9OIGFwY3BpZC5ORVdOSFNOTyA9IHVwbG9hZGVkZGF0YS5QYXRpZW50X05IU19ObyANCiAgICBBTkQgQ09OVkVSVChEQVRFLCBhcGNwaWQuRE9CX0RWLCAzKSA9IHVwbG9hZGVkZGF0YS5kYXRlb2ZiaXJ0aA0KSU5ORVIgSk9JTiANCiAgICBmaWx0ZXJlZF9vcHJ0biBBUyBvcHJ0biBPTiBhcGNwaWQuRVBJS0VZID0gb3BydG4uRVBJS0VZIEFORCBhcGNwaWQuRllFQVIgPSBvcHJ0bi5GWUVBUg0KSU5ORVIgSk9JTiANCiAgICBmaWx0ZXJlZF9tYXQgQVMgbWF0IE9OIGFwY3BpZC5FUElLRVkgPSBtYXQuRVBJS0VZIEFORCBhcGNwaWQuRllFQVIgPSBtYXQuRllFQVIgDQpJTk5FUiBKT0lOIA0KICAgIGZpbHRlcmVkX2FkbWlzIEFTIGFkbWlzIE9OIG1hdC5FUElLRVkgPSBhZG1pcy5FUElLRVkgQU5EIG1hdC5GWUVBUiA9IGFkbWlzLkZZRUFSDQpJTk5FUiBKT0lOIA0KICAgIGZpbHRlcmVkX2RpYWcgQVMgZGlhZyBPTiBhZG1pcy5FUElLRVkgPSBkaWFnLkVQSUtFWSBBTkQgZGlhZy5GWUVBUiA9IGFkbWlzLkZZRUFSDQpJTk5FUiBKT0lODQogICAgZmlsdGVyZWRfaGVzYXBjIEFTIGhlc2FwYyBPTiBkaWFnLkVQSUtFWSA9IGhlc2FwYy5FUElLRVkgQU5EIGhlc2FwYy5GWUVBUiA9IGRpYWcuRllFQVI7Iikpew0KICAgICAgDQogICAgICB4IDwtIGRhdGFsaW5rLnByZXAuZnVuKHgpICAgICMgcHJlcGFyZSBsaW5lbGlzdCBkYXRhIGZvciBIRVMgbGlua2FnZQ0KICAgICAgDQogICAgICBvZGJjOjpkYkxpc3RUYWJsZXMoY29ubj1kYXRhX2xha2UsIGNhdGFsb2dfbmFtZT1yeF9kc24sIHNjaGVtYV9uYW1lPSJkYm8iKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDaGVjayBuYW1lcyBvZiB0aGUgdGFibGVzIGluIHRoZSBkYXRhYmFzZQ0KICAgICAgDQogICAgICBkYkNyZWF0ZVRhYmxlKGNvbm49ZGF0YV9sYWtlLCBuYW1lID0gUElJX2RhdGFfbmFtZSwgZmllbGRzID0geCwgb3ZlcndyaXRlID0gVCkNCiAgICAgIA0KICAgICAgdGFibGVpZCA8LSBJZChzY2hlbWEgPSAiZGJvIiwgdGFibGUgPSBQSUlfZGF0YV9uYW1lKQ0KICAgICAgDQogICAgICBkYldyaXRlVGFibGUoZGF0YV9sYWtlLCB0YWJsZWlkLCB4LCBhcHBlbmQgPSBUUlVFLCByb3cubmFtZXMgPSBGKQ0KICAgICAgDQogICAgICBsaW5rZWRfZGF0YSA8LSBkYlNlbmRRdWVyeShkYXRhX2xha2UsaGVzX3FyeSkNCiAgICAgIGxpbmtlZF9kYXRhIDwtIGRiR2V0UXVlcnkoZGF0YV9sYWtlLGdsdWUoInNlbGVjdCAqIGZyb20ge3RhcmdldF90YWJsZX0iKSkNCiAgICAgIA0KICAgICAgDQogICAgICAjfn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fiMNCiAgICAgIA0KICAgICAgdGFibGVfbmFtZSA8LSB0YXJnZXRfdGFibGUgICANCiAgICAgIA0KICAgICAgIyBvdXRwdXQgdGFibGUgZ2VuZXJhdGVkIGZyb20gcXVlcnkNCiAgICAgIGhlc2FwY19saW5rZWRfZGF0YSA8LSBkYlJlYWRUYWJsZShkYXRhX2xha2UsdGFibGVfbmFtZSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRXh0cmFjdGluZyB0aGUgZXRobmljaXR5IGxpbmtlZCBkYXRhIGZyb20gdGhlIERhdGFMYWtlDQogICAgICBoZXNhcGNfbGlua2VkX2RhdGEgPC0gZGlzdGluY3QoaGVzYXBjX2xpbmtlZF9kYXRhKQ0KICAgICAgDQogICAgICANCiAgICAgICMgZ2V0IHJpZCBvZiB0ZW1wb3JhcnkgdGFibGVzIA0KICAgICAgZGJTZW5kUXVlcnkoZGF0YV9sYWtlLCBnbHVlKCJkcm9wIHRhYmxlIHt0YXJnZXRfdGFibGV9IikpDQogICAgICBkYlNlbmRRdWVyeShkYXRhX2xha2UsIGdsdWUoImRyb3AgdGFibGUge1BJSV9kYXRhX25hbWV9IikpDQogICAgICANCiAgICAgICMgZGlzY29ubmVjdCBmcm9tIERMDQogICAgICBkYkRpc2Nvbm5lY3QoZGF0YV9sYWtlKQ0KICAgICAgDQogICAgICAjIHJlYXNzaWduIHRvIGRhdGEgZGYNCiAgICAgIHggPC0gYXNfdGliYmxlKGhlc2FwY19saW5rZWRfZGF0YSkNCiAgICAgIA0KICAgICAgc2V0d2Qod2QpDQogICAgICAjIGV4cG9ydCBmb3IgZnVydGhlciBwcm9jZXNzaW5nIChldGhuaWMgZ3JvdXAgYXNzaWdubWVudCBhY2NvcmRpbmcgdG8gQ0hJTUUgbWV0aG9kb2xvZ3kpIGluIFN0YXRhDQogICAgICB3cml0ZS5jc3YoeCwgIkRhdGFfY2FycmlhZ2UvTGluZWxpc3Qvc3RhdGljX2Jhc2VkYXRhX2hlc2FwY2xpbmtlZC5jc3YiLCByb3cubmFtZXM9RikNCiAgICAgIA0KICAgICAgcmV0dXJuKHgpDQogICAgICANCiAgICB9DQoNCmBgYA0KDQpBZGQgYSBuZXcgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpJbnNlcnQgQ2h1bmsqIGJ1dHRvbiBvbiB0aGUgdG9vbGJhciBvciBieSBwcmVzc2luZyAqQ3RybCtBbHQrSSouDQoNCldoZW4geW91IHNhdmUgdGhlIG5vdGVib29rLCBhbiBIVE1MIGZpbGUgY29udGFpbmluZyB0aGUgY29kZSBhbmQgb3V0cHV0IHdpbGwgYmUgc2F2ZWQgYWxvbmdzaWRlIGl0IChjbGljayB0aGUgKlByZXZpZXcqIGJ1dHRvbiBvciBwcmVzcyAqQ3RybCtTaGlmdCtLKiB0byBwcmV2aWV3IHRoZSBIVE1MIGZpbGUpLg0KDQpUaGUgcHJldmlldyBzaG93cyB5b3UgYSByZW5kZXJlZCBIVE1MIGNvcHkgb2YgdGhlIGNvbnRlbnRzIG9mIHRoZSBlZGl0b3IuIENvbnNlcXVlbnRseSwgdW5saWtlICpLbml0KiwgKlByZXZpZXcqIGRvZXMgbm90IHJ1biBhbnkgUiBjb2RlIGNodW5rcy4gSW5zdGVhZCwgdGhlIG91dHB1dCBvZiB0aGUgY2h1bmsgd2hlbiBpdCB3YXMgbGFzdCBydW4gaW4gdGhlIGVkaXRvciBpcyBkaXNwbGF5ZWQuDQo=