This website has three main purposes:

  • Provides the code we used for

  • Loading the data into R;

    • Exploring the UNOS data, (e.g., identifying the number of columns, observations, % data missing within each column);
    • Summarizing and visualizing the data;
    • Data preprocessing;
  • Obtaining the machine learning results; and

  • The statistical analysis of the machine learning results through the use of a regression framework.

  • Provides the results that we have obtained for each stage of the analysis.

  • Allows for reusing our code and reproducing our results, which in our view is a major limitation in the majority of the published literature since they do not provide code and it is often unclear what are some of the decision made in terms of data preprocessing and modeling.

Data was provided from the UNOS registry by staff at the US, United Network for Organ Sharing. Per the UNOS regulations, we cannot share their data. For obtaining the data, the reader should click here. We also highly recommend that the reader gets familiarized with the data dictionary (see Data Definitions for details).

To navigate this site, please feel free to use the navigation bar at the left. The reader can show any code chunk by clicking on the code button. We chose to make the default for the code hidden since we: (a) wanted to improve the readability of this document; and (b) assumed that the readers will not be interested in reading every code chunk.

A list of the customized functions we created for this study can be found in the custom_functions.R.

1 Reading and Exploring the UNOS Dataset

The snippet below documents the list of R packages and functions that were used in this research. For convenience, we used the pacman package since it allows for installing/loading the needed packages in one step.

rm(list = ls()) # clear global environment
graphics.off() # close all graphics
if(require(pacman)==FALSE) install.packages("pacman") # needs to be installed first
# p_load is equivalent to combining both install.packages() and library()
pacman::p_load(Biocomb, car, caret, conflicted, DataExplorer, # important for exploratory data analysis 
               dataPreparation, data.table, DT, effects, emmeans, ggplot2, haven, lmtest, lsmeans, magrittr, mltools, party, readxl, tidyverse, # important analytic packages
               sjmisc, sjPlot, snow, varImp, yhat
)

conflict_prefer("arrange", "dplyr")
conflict_prefer("recode", "dplyr")
conflict_prefer("mutate", "dplyr")
conflict_prefer("makeCluster", "snow")

source("https://raw.githubusercontent.com/Ying-Ju/Explaining-Predictive-Model-Performance.github.io/master/custom_functions.R") # Has our custom functions

1.1 Loading the UNOS Dataset

df <- read_sas("../Data/thoracic_data.sas7bdat")
df$ID <- row.names(df) # Index to be used in creating/tracking the training and test samples
orgTable <- table(df[, 'WL_ORG'])
df %<>% dplyr::filter(WL_ORG == "HR") # filtering to keep only heart transplants

Our original UNOS data consisted of 159318 observations, which contained: (a) 428 unspecified transplant types, (b) 3148 combined heart-lung transplants, (c) 103570 heart transplants, and (d) 52172 lung transplants. Given our focus on heart transplants, we have only retained the observations where only a heart transplant is performed. The resulting data is resaved into our data.frame titled df, which now consists of 103570 observations and 495 variables (which have been unchanged from our filtering operation).

1.2 Exploring the UNOS Dataset

In the following code chunk, we convert the blank cells to “NA” before we study the distribution of missing values in the data.

# The variable YR_ENTRY_US_TCR was recorded with time zone, we removed the time zone.
df$YR_ENTRY_US_TCR <- as.Date(df$YR_ENTRY_US_TCR)
# We convert the blank cells to NA as these variables were well coded. 
index_date <- which(unlist(lapply(df, class))=="Date")
df[,-index_date] %<>% mutate_all(na_if, "") %>% mutate_all(na_if, " ") %>% mutate_all(na_if, "U")

Fraction Data Missing

naVec <- is.na(df) %>% colSums() %>% 
  `/`(nrow(df)) # To make it percent missing per variable
names(naVec) <- colnames(df)
naVec.quantiles <- quantile(naVec) %>% round(2)

ggplot() + geom_histogram(aes(naVec), bins = 10) + 
  xlab("Fraction Data Missing Per Variable") + ylab("Number of Variables")+theme_minimal() 

Based on the heart transplantation data, 457 variables out of the 103570 have at least one data point missing data. Furthermore, we have computed the percent missing data per variable; the values of the first, second, and third quantiles (in terms of percent of data missing per column) correspond to 37%, 63%, and 97%,respectively. Thus, it is imperative to examine how these variables can be preprocessed prior to any analysis.

Missing Data (Sample Columns)

In the plot below, we sample 30 columns at random from the UNOS dataset to show the actual percentage of the data that is missing for each variable. The colors are used to denote the data quality for that column using a traffic light scheme (where green is good and red is bad).

heart.na.plot <- df[,sample(colnames(df), 30)] %>% plot_missing()

Missing Data (Printout for All Variables)

In the chunk below, we report the percent missing per each variable. The printout is arranged in a descending order of missing values.

data.frame(variableName = names(naVec), percentMissing = round(naVec*100, 2), row.names = NULL) %>%
  arrange(desc(percentMissing)) %>% datatable()

Column Types

R initially divides the columns of different types. We summarize these in the table below.

types <- dataTypes(df) # see functions.R file
numeric.index <- which(types$classes.df=="numeric")
character.index <- which(types$classes.df=="character")
datatable(types) # printing the different types

The reader should note that R reads any variable that purely consists of numeric values to be numeric. However, from a data analysis perspective, a lot of these variables will need to be converted into factors since they represent factor levels rather than numeric values (e.g., donor/recipient ethinicity categories, allocation type, HLA mismatch level, education, etc.). Thus, the numeric variables should be examined more closely to ensure that they are properly coded by the software.


2 Data Preparation

In this section, we will detailedly document the data preperation precedures we have done in the study.

2.1 Technically Correct Variables

In this subsection, we will capitalize on the file titled = “STAR File Documentation.xls” provided by UNOS to automatically check and convert any variable that was assigned an incorrect type by the R software. The reader should note that the outcome of this data transformation step can be referred as technically correct data (De Jonge and Van Der Loo 2013). Note that we convert all factor columns to character to facilitate their manipulation in R.

varInfo <- read_excel("../Data/STAR File Documentation.xls", sheet = "THORACIC DATA", skip =1, col_types = c(rep("text",3), rep("date",2), rep("guess",7)) )

# fix an error in the variable information document, there were two start dates: 01-Oct-87, 01-Oct-90 for the variable: CMV_DON. We assign 01-Oct-90 for the start date.

varInfo$`VAR START DATE`[51] <- "1990-10-01"

# pulling names of variables that had type char OR having numeric categories (explained in SAF)
factorVars <- varInfo %>% 
  dplyr::filter(`DATA TYPE`=="CHAR" | (`DATA TYPE`=="NUM" & !is.na(`SAS ANALYSIS FORMAT`)) ) %>%
  pull(`VARIABLE NAME`)

# The following variables are qualitative based on the UNOS documentation but they were not identified. 
Other_categorical_vars <- c("BLOOD_INF_DON", "HBV_CORE_DON", "HBV_SUR_ANTIGEN",   "HCV_SEROSTATUS", "HYPERTENS_DUR_DON", "IABP_TRR", "LAST_INACT_REASON", "OTHER_INF_DON", "STERNOTOMY_TRR", "URINE_INF_DON")

factorVars <- c(factorVars, Other_categorical_vars)

df[, factorVars] %<>%  lapply(as.character) # converting factors to character to facilitate preprocessing

#saveRDS(factorVars, "../Results/factorVars.RDS")

Based on the analysis above, we have identified 322 categorical variables. Note the R has previously only identified character/categorical variables. The names of the 322 variables can be accessed in the factorVars.RDS file. Furthermore, we applied the as.factor() to ensure that R recognizes these variables as categorical.

2.2 Rowwise Operations: Additional Filters

In the code chunk below, we first filter to keep adult patients since our study focuses on only adult transplantation. Then we remove individuals whose weights were lower than .01% of adults’ weights in the data (less than 30lbs) or whose heights were less than .01% adults’ heights in the data (lower than 122 cms).

df <- df %>% subset(AGE>=18) %>% # filtering to keep only Adults
# we excluded too light or too short people in the following code
subset(WGT_KG_DON_CALC >= quantile(WGT_KG_DON_CALC, 0.0001, na.rm = TRUE)) %>% 
subset(WGT_KG_TCR >= quantile(WGT_KG_TCR, 0.0001, na.rm = TRUE)) %>%
subset(HGT_CM_DON_CALC >= quantile(HGT_CM_DON_CALC, 0.0001, na.rm = TRUE)) %>% 
subset(HGT_CM_TCR >= quantile(HGT_CM_TCR, 0.0001, na.rm = TRUE))

2.3 Columnwise Operations: Dropping Variables

In the code chunk below, we fist filter/remove any observations with missing GSTATUS or GTIME. Our rationale for doing this prior to removing uniformative/irrelevant/unreliable variables was based on the fact that these variables would be used in computing our transplantation OUTCOME and hence, we did not want to lose them. After performing remove any variables that meet any of the following criteria:

  • Variables with a starting date on or later than Jan 01, 2000, except the variables that are corresponding to prior cardiac surgery (non-transplant) or lung surgery as these variables have been identified as important information in the literature (Vega et al. (2017), Chen et al. (2018), Guven et al. (2018), Medved et al. (2018), Zhang et al. (2019)). These variables are:
  • PRIOR_CARD_SURG_TCR, PRIOR_CARD_SURG_TRR, PRIOR_CARD_SURG_TYPE_OSTXT_TCR, PRIOR_CARD_SURG_TYPE_OSTXT_TRR, PRIOR_CARD_SURG_TYPE_TCR, PRIOR_CARD_SURG_TYPE_TRR, and PRIOR_LUNG_SURG_TYPE_OSTXT_TRR.
  • Any variables having an end date for data collection since they would not be collected in any future transplant event.
  • Any post-transplant variables, with the exception of GTIME and GSTATUS (which we use later to record the transplantation outcome at 1-year post transplant)
  • Removing the following variables, capturing different types of antigen alleles:
  • DA1, DA2, RA1, RA2, DB1, DB2, RB1, RB2, RDR1, RDR2, DDR1, and DDR2.
  • Note that each of those variables had three possible values: 0 indicating no mismatch, 1 indicating one allele matching, and 2 indicating that both alleles mismatched.
  • Our rationale for removing those variables was based on the observation that the variables HLAMIS, AMIS, BMIS and DRMIS recorded summaries of matches in these antigen alleles. The interested reader is referred to Parham (2014) for a discussion of HLAMIS, and Weisdorf et al. (2008) for a detailed introduction on those four variables.
  • Removing the two variables DEATH_CIRCUM_DON and DEATH_MECH_DON since a cross-tabulation of their values indicated that they are not consistent, which reduces the interpretation/utility from using those variables.
  • Removing the variable TX_YEAR since we wanted to develop a predictive and not an explanatory model. For more details on the difference, the reader is referred to Shmueli and others (2010).
  • Any variables where the number of missing observations exceeded 90%
  • Any numeric variables where the number of missing observations exceeded 30%
varsVec <- read_rds(gzcon(url("https://raw.githubusercontent.com/Ying-Ju/Explaining-Predictive-Model-Performance.github.io/master/dropVars.RDS"))) # a vector of 69 vars to be dropped
alleles = c("DA1","DA2","RA1","RA2","DB1","DB2","RB1","RB2","RDR1","RDR2","DDR1","DDR2")

VarstoBeDropped = varInfo %>% 
dplyr::filter(`VAR START DATE` >= '2000-01-01' | 
!is.na(`VAR END DATE`) | 
str_detect(FORM, "TRF/TRR|TRR/TRF-CALCULATED|TRR/TRF|TRF") |
str_detect(`FORM SECTION`, "POST TRANSPLANT CLINICAL INFORMATION") |
str_detect(DESCRIPTION, "IDENTIFIER| DATE") |
`VARIABLE NAME` %in% varsVec |
`VARIABLE NAME` %in% alleles |
`VARIABLE NAME` %in% c("DEATH_CIRCUM_DON","DEATH_MECH_DON", "TX_YEAR") ) %>% 
pull(`VARIABLE NAME`)

# include variables corresponding to prior cardiac surgery (non-transplant) or lung surgery
VarstoBeDropped <- setdiff(VarstoBeDropped, c("PRIOR_CARD_SURG_TCR", "PRIOR_CARD_SURG_TRR", "PRIOR_CARD_SURG_TYPE_OSTXT_TCR", "PRIOR_CARD_SURG_TYPE_OSTXT_TRR",
"PRIOR_CARD_SURG_TYPE_TCR", "PRIOR_CARD_SURG_TYPE_TRR", "PRIOR_LUNG_SURG_TYPE_OSTXT_TRR"))


df %<>% dplyr::filter(!is.na(GSTATUS) & !is.na(GTIME) ) %>% # remove obs missing graft status/time
select(!all_of(VarstoBeDropped)) %>% # exclude variables to be dropped
discard(~sum(is.na(.x))/length(.x)* 100 >= 90) %>% # remove vars with >= 90 % missing
dropNumericMissing(percent = 30) # drop numeric columns w/ >= 30% missing (see custom_functions.R)

2.4 Creating Variables based on the Literature and Our Exploration of the Data

In the code chunk below, we create the following variables:

  • ISCHTIME was recoded to be in minutes to make the interpretation consistent with Medved et al. (2018)
  • PVR, which calculates the recipent’s pulmonary vascular resistance
  • An ECMO index variable was created to indicate whether the ECMO machine was utilized at either registration or at transplant
  • CARD_SURG, which captures whether the recipent had prior cardiac surgeries (non-transplant)
  • BMI_CHNG, which captures the percent change in the recipient’s BMI from transplantation time to registration/listing
  • WGT_CHNG, which captures the percent change in the recipient’s weight from transplantation time to registration/listing
  • HGT_CHNG, which captures the percent change in the recipient’s height from transplantation time to registration/listing
  • AGE_DIFF, which captures the absolute value of the difference between the recipient’s and deceased donor’s age
  • BMI_DIFF, which captures the absolute value of the difference between the recipient’s and deceased donor’s BMI
  • ETH_MAT, which captures whether the recipent and deceased donor were of the same ethinicity
  • GEN_MAT, which captures whether the recipent and deceased donor were of the same gender
  • ANCEF, which captures whether the deceased donor was prescribed Ancef within 24 hours or procurement
  • DOPAMINE, which captures whether the deceased donor was prescribed Dopamine within 24 hours or procurement
  • HEPARIN, which captures whether the deceased donor was prescribed Herapin within 24 hours or procurement
  • ZOSYN, which captures whether the deceased donor was prescribed Zosyn within 24 hours or procurement
  • OUTCOME, which captures whether the recipient has survived at one-year post transplant based on combining information from GTIME and GSTATUS. The censoring procedure used herein is identical to that applied in Dag et al. (2016); Dag et al. (2017).

After these variables were created, we dropped both GTIME and GSTATUS as well as the four variables capturing the medications prescribed/applied on the deceased donor (i.e. PT_OTH1_OSTXT_DON – PT_OTH4_OSTXT_DON). Furthemore, we converted all indicator columns into characters.

df %<>% mutate(ISCHTIME = ISCHTIME*60) %>%
mutate(PVR = (HEMO_PA_MN_TRR - HEMO_PCW_TRR)*79.72/HEMO_CO_TRR) %>%
mutate(ECMO = ifelse(ECMO_TCR + ECMO_TRR == 0, 0, 1)) %>% 
mutate(CARD_SURG = if_else(PRIOR_CARD_SURG_TCR=="Y"|PRIOR_CARD_SURG_TRR=="Y", true="Y", false=ifelse(PRIOR_CARD_SURG_TCR=="N"&PRIOR_CARD_SURG_TRR=="N", "N", NA))) %>%
mutate(BMI_CHNG = 100*(BMI_CALC- INIT_BMI_CALC)/INIT_BMI_CALC) %>% 
mutate(WGT_CHNG = 100*(WGT_KG_CALC - INIT_WGT_KG_CALC)/INIT_WGT_KG_CALC) %>% 
mutate(HGT_CHNG = 100*(HGT_CM_CALC - INIT_HGT_CM_CALC)/INIT_HGT_CM_CALC) %>% 
mutate(AGE_DIFF = abs(AGE - AGE_DON)) %>% 
mutate(BMI_DIFF = abs(BMI_CALC - BMI_DON_CALC)) %>% 
mutate(ETH_MAT = if_else(ETHCAT == ETHCAT_DON, true = "Y", false = "N")) %>% 
mutate(GEN_MAT = if_else(GENDER == GENDER_DON, true = "Y", false = "N")) %>%
mutate(ANCEF = if_else(str_detect(PT_OTH1_OSTXT_DON, 'ANCEF') |
str_detect(PT_OTH2_OSTXT_DON, 'ANCEF') |
str_detect(PT_OTH3_OSTXT_DON, 'ANCEF'),
true = "Y", false = "N") ) %>% 
mutate(DOPAMINE = if_else(str_detect(PT_OTH1_OSTXT_DON, 'DOPAMINE') | 
str_detect(PT_OTH2_OSTXT_DON, 'DOPAMINE') |
str_detect(PT_OTH3_OSTXT_DON, 'DOPAMINE'),
true = "Y", false = "N") ) %>% 
mutate(HEPARIN = if_else(str_detect(PT_OTH1_OSTXT_DON, 'HEPARIN') | 
str_detect(PT_OTH2_OSTXT_DON, 'HEPARIN') |
str_detect(PT_OTH3_OSTXT_DON, 'HEPARIN'),
true = "Y", false = "N") ) %>% 
mutate(ZOSYN = if_else(str_detect(PT_OTH1_OSTXT_DON, 'ZOSYN') | 
str_detect(PT_OTH2_OSTXT_DON, 'ZOSYN') |
str_detect(PT_OTH3_OSTXT_DON, 'ZOSYN'),
true = "Y", false = "N") ) %>% 
rowwise() %>% 
mutate(OUTCOME = recodeGSTATUS(gStatus = GSTATUS, gTime = GTIME, targetYear = 1)) %>% # see custom_functions.R
select(-c(GSTATUS, GTIME, starts_with("PT_OTH")))

df %<>% ungroup() %>% 
mutate_at(c('ECMO', 'CARD_SURG', 'ETH_MAT', 'GEN_MAT', 'ANCEF', 'DOPAMINE', 'HEPARIN', 'ZOSYN', 'OUTCOME'), as.character)

2.5 Reducing Number of Levels for Categorical Columns

2.5.1 Number of Distinct Values within Each Categorical Variable

Despite the application of the nearZeroVar() from the caret package, several of the remaining character/factor variables still exhibit a large number of unique values. The purpose of the code chunk below is to quantify the variability in the remaining character/factor variables.

df %>% select(-ID) %>%  summarise_if(is.character, n_distinct, na.rm = TRUE) -> numLevels

If we were to exclude the ID column, the number of distinct values in each character variable (equivallently the number of non-NA levels if these variables were encoded as factors) has the following summary statistics:

  • Min. : 1.000
  • 1st Qu.: 2.000
  • Median : 2.000
  • Mean : 4.975
  • 3rd Qu.: 4.750
  • Max. :49.000

Accordingly, we will have to examine how to reduce the number of levels within each variable to be able to use these variables in the machine learning stage; we do not want to generate a very large number of dummy variables and we will group some of those levels by capitalizing on the similarities in interpretting some of these levels.

2.5.2 Grouping Operations

While recipes package provides an excellent pipeline for data pre-processing, we have elected to manually go through the process of grouping variables since this would allow us to (a) utilize the information in the factor levels to combine similar categories (whether due to location, education status or medical diagnosis codes); and (b) the integration of the recipes package into the training of machine learning models would require to train the models repeatdly for each recipe per the documentation of Kuhn (2019). In the code chunk below, we provide the details for how we regrouped several values/levels of each categorical/character vector with the hope of improving both the efficiency and prediction performance of the different machine learning models. In addition, we have also used the recode() to rename some of the categories since R can produce errors when one-hot encoding is applied if the name of the category is numeric; therefore, some of the operations performed below merely changed the name of the category to avoid future errors when processing the data. After regrouping, we performed two additional steps of variable elimination: (a) removing variables that have more than 90% of the non-missing observations in one level; (b) using the caret nearZeroVar() function to remove variables that the percentage of unique values in the variable is less than 0.01%.

df$ABO %<>% recode(A = 'A', A1 = 'A', A2 = 'A', B = 'B', O = 'O', AB = 'AB', A1B = 'AB', A2B = 'AB', .default = "OTHER", .missing = NA_character_) 
df$ABO_DON %<>% recode(A = 'A', A1 = 'A', A2 = 'A', B = 'B', O = 'O', AB = 'AB', A1B = 'AB', A2B = 'AB', .default = "OTHER", .missing = NA_character_)

df$ABO_MAT %<>% recode(`1` = "IDENTICAL", `2` = "NOT_IDENTICAL", `3` = "NOT_IDENTICAL", .default = "OTHER", .missing = NA_character_)

df$AMIS %<>% recode(`0` = "NO_MISMATCH", `1` = "ONE_MISMATCHED", `2` = "TWO_MISMATCHED", .default = "OTHER", .missing = NA_character_)

df$BMIS %<>% recode(`0` = "NO_MISMATCH", `1` = "ONE_MISMATCHED", `2` = "TWO_MISMATCHED", .default = "OTHER", .missing = NA_character_)

df$BRONCHO_LT_DON %<>% recode(`1` = "NO", `2` = "NORMAL", `3` = "ABNORMAL", `4` = "ABNORMAL", `5` = "ABNORMAL",`6` = "ABNORMAL", `7` ="OTHER", `998` = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$BRONCHO_RT_DON %<>% recode(`1` = "NO", `2` = "NORMAL", `3` = "ABNORMAL", `4` = "ABNORMAL", `5` = "ABNORMAL",`6` = "ABNORMAL", `7` ="OTHER", `998` = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$CHEST_XRAY_DON %<>% recode(`0` = "UNKNOWN", `1` = "NO", `2` = "NORMAL", `3` = "ABNORMAL_SINGLE", `4` = "ABNORMAL_SINGLE", `5` = "ABNORMAL_BOTH",
`998` = "UNKNOWN", `999` = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$COD_CAD_DON %<>% recode(`1` = 'ANOXIA', `2` = 'CEREBROVASCULAR_STROKE', `3` = 'HEAD_TRAUMA',`4` = 'OTHER', `999` = 'OTHER', Unknown = 'UNKNOWN', .default = "OTHER", .missing = NA_character_)

df$CORONARY_ANGIO %<>% recode(`1` = "NO", `2` = "YES_NORMAL", `3` = "YES_ABNORMAL", .default = "OTHER", .missing = NA_character_) 

df$CMV_DON %<>% recode(C = "UNKNOWN", I = "UNKNOWN", N = "NEGATIVE", ND = "UNKNOWN", P = "POSITIVE", PD = "UNKNOWN", U = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$DIAB %<>% recode(`1` = 'NO', `2` = 'ONE', `3` = 'TWO', `4` = 'OTHER', `5` = 'OTHER', `998` = 'UNKNOWN', .default = "OTHER", .missing = NA_character_)

df$DIAG %<>% recode(`1000` = 'DILATED_MYOPATHY_IDI', `1001` = 'DILATED_MYOPATHY_OTH', `1002` = 'DILATED_MYOPATHY_OTH', `1003` = 'DILATED_MYOPATHY_OTH',`1004` = 'DILATED_MYOPATHY_OTH', `1005` = 'DILATED_MYOPATHY_OTH',`1006` = 'DILATED_MYOPATHY_OTH', `1007` = 'DILATED_MYOPATHY_ISC', `1049` = 'DILATED_MYOPATHY_OTH',
`1200` = 'CORONARY', `999` = 'OTHER', `1999` = 'UNKNOWN', .default = "OTHER", .missing = NA_character_)

df$TCR_DGN %<>% recode(`1000` = 'DILATED_MYOPATHY_IDI', `1001` = 'DILATED_MYOPATHY_OTH', `1002` = 'DILATED_MYOPATHY_OTH', `1003` = 'DILATED_MYOPATHY_OTH',`1004` = 'DILATED_MYOPATHY_OTH', `1005` = 'DILATED_MYOPATHY_OTH',`1006` = 'DILATED_MYOPATHY_OTH', `1007` = 'DILATED_MYOPATHY_ISC', `1049` = 'DILATED_MYOPATHY_OTH',
`1200` = 'CORONARY', `999` = 'OTHER', `1999` = 'UNKNOWN', .default = "OTHER", .missing = NA_character_)

df$THORACIC_DGN %<>% recode(`1000` = 'DILATED_MYOPATHY_IDI', `1001` = 'DILATED_MYOPATHY_OTH', `1002` = 'DILATED_MYOPATHY_OTH', `1003` = 'DILATED_MYOPATHY_OTH',`1004` = 'DILATED_MYOPATHY_OTH', `1005` = 'DILATED_MYOPATHY_OTH',`1006` = 'DILATED_MYOPATHY_OTH', `1007` = 'DILATED_MYOPATHY_ISC', `1049` = 'DILATED_MYOPATHY_OTH',
`1200` = 'CORONARY', `999` = 'OTHER', `1999` = 'UNKNOWN', .default = "OTHER", .missing = NA_character_)


df$DRMIS %<>% recode(`0` = "NO_MISMATCH", `1` = "ONE_MISMATCHED", `2` = "TWO_MISMATCHED", .default = "OTHER", .missing = NA_character_)

df$EBV_SEROSTATUS %<>% recode(C = "UNKNOWN", I = "UNKNOWN", N = "NEGATIVE", ND = "UNKNOWN", P = "POSITIVE", PD = "UNKNOWN", U = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$EDUCATION %<>% recode(`1` = 'OTHER', `2` = 'GRADE', `3` = 'HIGH', `4` = 'COLLEGE', `5` = 'UNIVERSITY', `6` = 'UNIVERSITY', `996` = 'OTHER', `998` = 'UNKNOWN', .default = "OTHER", .missing = NA_character_)

df$ETHCAT %<>% recode(`1` = 'WHITE', `2` = 'BLACK', `4` = 'HISPANIC', `5` = 'OTHER', `6` = 'OTHER',`7` = 'OTHER', `9` = 'OTHER', `998` = 'UNKNOWN', .default = "OTHER", .missing = NA_character_)

df$ETHCAT_DON %<>% recode(`1` = 'WHITE', `2` = 'BLACK', `4` = 'HISPANIC', `5` = 'OTHER', `6` = 'OTHER',`7` = 'OTHER', `9` = 'OTHER', `998` = 'UNKNOWN', .default = "OTHER", .missing = NA_character_)

df$FUNC_STAT_TCR %<>% recode(`1` = "ABLE", `2` = "ASSISTED", `3` = "DISABLE", `996` = "OTHER", `998` = "UNKNOWN", `2010` = "DISABLE", `2020` = "DISABLE", `2030` = "DISABLE", `2040` = "DISABLE", `2050` = "ASSISTED", `2060` = "ASSISTED" , `2070` = "ASSISTED", `2080` = "ABLE", `2090` = "ABLE", `2100` = "ABLE", .default = "OTHER", .missing = NA_character_)

df$FUNC_STAT_TRR %<>% recode(`1` = "ABLE", `2` = "ASSISTED", `3` = "DISABLE", `996` = "OTHER", `998` = "UNKNOWN", `2010` = "DISABLE", `2020` = "DISABLE", `2030` = "DISABLE", `2040` = "DISABLE",`2050` = "ASSISTED", `2060` = "ASSISTED" , `2070` = "ASSISTED", `2080` = "ABLE", `2090` = "ABLE", `2100` = "ABLE", .default = "OTHER", .missing = NA_character_)

df$HBV_CORE %<>% recode(C = "UNKNOWN", I = "UNKNOWN", N = "NEGATIVE", ND = "UNKNOWN", P = "POSITIVE", PD = "UNKNOWN", U = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$HBV_CORE_DON %<>% recode(C = "UNKNOWN", I = "UNKNOWN", N = "NEGATIVE", ND = "UNKNOWN", P = "POSITIVE", PD = "UNKNOWN", U = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$HBV_SUR_ANTIGEN %<>% recode(C = "UNKNOWN", I = "UNKNOWN", N = "NEGATIVE", ND = "UNKNOWN", P = "POSITIVE", PD = "UNKNOWN", U = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$HCV_SEROSTATUS %<>% recode(C = "UNKNOWN", I = "UNKNOWN", N = "NEGATIVE", ND = "UNKNOWN", P = "POSITIVE", PD = "UNKNOWN", U = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$HEP_C_ANTI_DON %<>% recode(C = "UNKNOWN", I = "UNKNOWN", N = "NEGATIVE", ND = "UNKNOWN", P = "POSITIVE", PD = "UNKNOWN", U = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$HIST_DIABETES_DON %<>% recode(`1` = 'NO', `2` = 'YES', `3` = 'YES', `4` = 'YES' , `5` = 'YES', `998` = 'UNKNOWN', .default = "OTHER", .missing = NA_character_) 

df$HYPERTENS_DUR_DON %<>% recode(`1` = 'NO', `2` = 'YES', `3` = 'YES', `4` = 'YES' , `5` = 'YES', `998` = 'UNKNOWN', .default = "OTHER", .missing = NA_character_) 

df$HIV_SEROSTATUS %<>% recode(C = "UNKNOWN", I = "UNKNOWN", N = "NEGATIVE", ND = "UNKNOWN", P = "POSITIVE", PD = "UNKNOWN", U = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$HLAMIS %<>% recode(`0` = 'LOWEST', `1` = 'LOWEST', `2` = 'LOWEST', `3` = 'LOW', 
`4` = 'MEDIUM', `5` = 'HIGH', `6` = 'HIGHEST', .default = "OTHER", .missing = NA_character_)

df$HTLV2_OLD_DON %<>% recode(C = "UNKNOWN", I = "UNKNOWN", N = "NEGATIVE", ND = "UNKNOWN", P = "POSITIVE", PD = "UNKNOWN", U = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$INIT_STAT %<>%  recode(`1010` = 'ONE', `1020` = 'ONE', `1030` = 'TWO', `1090` ='ONE', `1999` = 'TEMPORARILY_INACTIVE', `2010` = 'ONE', `2020` = 'ONE', `2030` = 'TWO',`2090` = 'ONE', `2999` = 'TEMPORARILY_INACTIVE', `7010` = 'ACTIVE',
`7999` = 'TEMPORARILY_INACTIVE', .default = "OTHER", .missing = NA_character_)

df$END_STAT %<>%  recode(`1010` = 'ONE', `1020` = 'ONE', `1030` = 'TWO', `1090` ='ONE', `1999` = 'TEMPORARILY_INACTIVE', `2010` = 'ONE', `2020` = 'ONE', `2030` = 'TWO',`2090` = 'ONE', `2999` = 'TEMPORARILY_INACTIVE', `7010` = 'ACTIVE',
`7999` = 'TEMPORARILY_INACTIVE', .default = "OTHER", .missing = NA_character_)

df$LAST_INACT_REASON %<>% recode(`7`='HEALTH', `9`='HEALTH', `11`='HEALTH', .missing = NA_character_)

df$STERNOTOMY_TRR %<>% recode(`1`="ONE", `2`="MORE", `3`="MORE", `998`="UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$INOTROPES_TCR %<>% recode(`0` = "NO", `1` = "YES", .default = "OTHER", .missing = NA_character_) 

df$INOTROPES_TRR %<>% recode(`0` = "NO", `1` = "YES",  .default = "OTHER", .missing = NA_character_)

df$MED_COND_TRR %<>% recode(`1` = "ICU_HOSPITALIZED", `2` = "HOSPITALIZED", `3` = "NOT_HOSPITALIZED",  .default = "OTHER", .missing = NA_character_)

df$NUM_PREV_TX %<>% recode(`0` = 'ZERO', .default = 'NON_ZERO', .missing = NA_character_)

df$PRI_PAYMENT_TCR %<>% recode(`1` = "PRIVATE", `2` ="MEDICAID", `3` = "MEDICARE_FREE", `4` = "PUBLIC_OTHER",`5` = "PUBLIC_OTHER", `6` = "PUBLIC_OTHER", `7` = "PUBLIC_OTHER", `8` = "OTHER",`9` = "OTHER", `10` = "OTHER", `11` = "OTHER", `12` = "OTHER", `13` = "PUBLIC_OTHER",`14` = "OTHER", `15` = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$PRI_PAYMENT_TRR %<>% recode(`1` = "PRIVATE", `2` ="MEDICAID", `3` = "MEDICARE_FREE", `4` = "PUBLIC_OTHER",`5` = "PUBLIC_OTHER", `6` = "PUBLIC_OTHER", `7` = "PUBLIC_OTHER", `8` = "OTHER",`9` = "OTHER", `10` = "OTHER", `11` = "OTHER", `12` = "OTHER", `13` = "PUBLIC_OTHER", `14` = "OTHER", `15` = "UNKNOWN", .default = "OTHER", .missing = NA_character_)

df$PROC_TY_HR %<>% recode(`1` = "BICAVAL", `2` = "TRADITIONAL", .default = "OTHER", .missing = NA_character_)

df$PULM_INF_DON %<>% recode(`0` = "NO", `1` = "YES", .default = "OTHER", .missing = NA_character_) 

df$REGION %<>% recode(`1` = "NORTH_EAST", `2` = "NORTH_EAST", `3` = "SOUTH_EAST", `4` = "SOUTH_EAST", `5` = "WEST", `6` = "WEST", `7` = "MIDWEST", `8` = "MIDWEST", `9` = "NORTH_EAST", `10` = "MIDWEST",`11` = "SOUTH_EAST", .default = "OTHER", .missing = NA_character_)

df$SHARE_TY %<>% recode(`3` = "LOCAL", `4` = "REGIONAL", `5` = "NATIONAL", .default = "OTHER", .missing = NA_character_) 

df <- df %>% dropLowVar(percent=90) 
nzvColLoc <- nearZeroVar(df, uniqueCut = 0.01)
df <- df[,-nzvColLoc]

Now, we have 131 variables and 48347 cases in the data.

2.6 Dealing with Missing Values & Encoding Strategy

In this section, we provide some options of dealing with missing values in either categorical or numeric variables and two approaches of encoding categorical variables.

Numerical Imputation: No imputation (Drop), Missing values are replaced with the median

Categorical Imputation: No imputation (Drop), Missing values are replaced with mode category, Missing values are labeled as “missing,” Missing values are labeled with the existing “unknown” category.

We suggest two approaches of encoding categorical variables: Label Encoding where each label is mapped to an integer, and One-Hot Encoding where each label is mapped to a binary variable.

In the following code chunk, we create a list (df_all) of 16 sublists corresponding to the imputation strategies and encoding strategies mentioned above. For example, df_all[[“Median_Drop_Label”]] stores the data after imputing median for missing values in the numerical variables, dropping missing values in the categorical variables, and mapping each level in each categorical variable to an integer.

# first, we need to remove cases with missing values in OUTCOME as it is the response variable in our study.
df <- df[!is.na(df$OUTCOME),]

# find the indices for categorical and numerical variables in the data
index_char <- setdiff(which(unlist(lapply(df, class))=="character"), which(colnames(df)=="ID"|colnames(df)=="OUTCOME"))

index_num <- which(unlist(lapply(df,class))=="numeric")


# define three factors
imputation_num <- c("Drop", "Median")
imputation_cat <- c("Drop", "Missing", "Mode", "Unknown")
encoding <- c("Label", "OneHot")

# Create a list of 16 elements that will store the 16 data sets for all scenarios
df_all <- rep(list(NA), 16)
names(df_all) <- apply(as.data.frame(crossing(imputation_num, imputation_cat, encoding)),1,function(x) paste(x, collapse="_"))

# prepare for imputation cases
char.impute_mode <- df[,index_char] %>% mutate_all(~ifelse(is.na(.x), names(which.max(table(.x))), .x)) 
char.impute_missing <- df[,index_char] %>% mutate_all(~ifelse(is.na(.x), "Missing", .x)) 
char.impute_unknown <- df[,index_char] %>% mutate_all(~ifelse(is.na(.x), "UNKNOWN", .x)) 
num.impute_median <- df[,index_num] %>% mutate_all(~ifelse(is.na(.x), median(.x, na.rm = TRUE), .x)) 

# prepare for encoding with categorical imputations
mode_Label <- encode_category(char.impute_mode, "Label") 
mode_Onehot <- encode_category(char.impute_mode, "Onehot") 
missing_Label <- encode_category(char.impute_missing, "Label") 
missing_Onehot <- encode_category(char.impute_missing, "Onehot") 
unknown_Label <- encode_category(char.impute_unknown, "Label") 
unknown_Onehot <- encode_category(char.impute_unknown, "Onehot")

# prepare for no imputation case
drop_drop <- search_complete(df)  
index_Num_DD <- which(unlist(lapply(drop_drop, class))=="numeric")
index_Char_DD <- setdiff(which(unlist(lapply(drop_drop, class))=="character"), which(colnames(drop_drop)=="ID"|colnames(drop_drop)=="OUTCOME"))
drop_drop_Label <- encode_category(drop_drop[,index_Char_DD], "Label") 
drop_drop_Onehot <- encode_category(drop_drop[,index_Char_DD], "Onehot") 

# prepare for imputing median for numerical variables but dropping categorical missing values
median_notdrop <- bind_cols(df[,c("ID", "OUTCOME")], num.impute_median, df[,index_char])
median_drop <- search_complete(median_notdrop)
index_Num_MD <- which(unlist(lapply(median_drop, class))=="numeric")
index_Char_MD <- setdiff(which(unlist(lapply(median_drop, class))=="character"), which(colnames(median_drop)=="ID"|colnames(median_drop)=="OUTCOME"))
median_drop_Label <- encode_category(median_drop[,index_Char_MD], "Label") 
median_drop_Onehot <- encode_category(median_drop[,index_Char_MD], "Onehot") 

# obtain data for 16 scenarios
df_all$Drop_Drop_Label <- bind_cols(drop_drop[,c("ID", "OUTCOME")], drop_drop[,index_Num_DD], drop_drop_Label)
df_all$Drop_Drop_OneHot <- bind_cols(drop_drop[,c("ID", "OUTCOME")], drop_drop[,index_Num_DD], drop_drop_Onehot)
df_all$Drop_Missing_Label <- bind_cols(df[,c("ID", "OUTCOME")], df[,index_num], missing_Label) %>% drop_na()
df_all$Drop_Missing_OneHot <- bind_cols(df[,c("ID", "OUTCOME")], df[,index_num], missing_Onehot) %>% drop_na()
df_all$Drop_Mode_Label <- bind_cols(df[,c("ID", "OUTCOME")], df[,index_num], mode_Label) %>% drop_na()
df_all$Drop_Mode_OneHot <- bind_cols(df[,c("ID", "OUTCOME")], df[,index_num], mode_Onehot) %>% drop_na()
df_all$Drop_Unknown_Label <- bind_cols(df[,c("ID", "OUTCOME")], df[,index_num], unknown_Label) %>% drop_na()
df_all$Drop_Unknown_OneHot <- bind_cols(df[,c("ID", "OUTCOME")], df[,index_num], unknown_Onehot) %>% drop_na()
df_all$Median_Drop_Label <- bind_cols(median_drop[,c("ID", "OUTCOME")], median_drop[,index_Num_MD], median_drop_Label)
df_all$Median_Drop_OneHot <- bind_cols(median_drop[,c("ID", "OUTCOME")], median_drop[,index_Num_MD], median_drop_Onehot)
df_all$Median_Missing_Label <- bind_cols(df[,c("ID", "OUTCOME")], num.impute_median, missing_Label)
df_all$Median_Missing_OneHot <- bind_cols(df[,c("ID", "OUTCOME")], num.impute_median, missing_Onehot)
df_all$Median_Mode_Label <- bind_cols(df[,c("ID", "OUTCOME")], num.impute_median, mode_Label)
df_all$Median_Mode_OneHot <- bind_cols(df[,c("ID", "OUTCOME")], num.impute_median, mode_Onehot)
df_all$Median_Unknown_Label <- bind_cols(df[,c("ID", "OUTCOME")], num.impute_median, unknown_Label)
df_all$Median_Unknown_OneHot <- bind_cols(df[,c("ID", "OUTCOME")], num.impute_median, unknown_Onehot)

saveRDS(df_all, "../Results/all_data.RDS")

We found that if we simply dropped all missing values in the categorical variables, there were no observations in the data. Thus, we try to maximize remaining cells in the data through a heuristic algorithm: search_complete(), which drops rows and columns based on the percentage of missing values until just non-empty cells are remained. This function can be found in custom_functions.R.

The following table shows the dimensions for the data (including ID) we created for all scenarios.

D <- lapply(df_all, dim)
D <- do.call(rbind.data.frame, D)
colnames(D) <- c("Number_of_Observations", "Number_of_Variables")
D <- data.frame(Scenario=names(df_all), D)
datatable(D)

3 Full Factorial Design

Since the overarching question examined is “can we quantify the combined effect of decisions made using the KDDM process on the predictive performance of the model?” we will explore the six factors summarized in the following.

  1. Categorical Imputation: Drop, Mode, Missing, Unknown

  2. Numerical Imputation: Drop, Median

  3. Encoding: Label, One-Hot

  4. Subsampling: None, Down, Up, SMOTE, ROSE

  5. Feature Selection: FFS, LASSO, RF

  6. Algorithm: ANN, DT, ElasticNet, KPLSR, LDA, LR, NB, RF

Readers are encouraged to refer our paper for the descriptions of each factor.

3.1 Feature Selection

In the following code chunk, we show how the feature selection approaches: Fast Feature Selection, LASSO, and Random Forests are utilized. If there is an error occurs related to “JAVA_HOME cannot be determined from the registry,” the error is often resolved by installing a Java version (i.e. 64-bit Java or 32-bit Java) that fits to the type of R version that you are using (i.e. 64-bit R or 32-bit R). And set the JAVA_HOME path using one of the commends below. “jre1.8.0_251” needs to be adjusted depends on the folder’s name.

Sys.setenv(JAVA_HOME=‘C:/Program Files/Java/jre1.8.0_251’) for 64-bit version

Sys.setenv(JAVA_HOME=‘C:/Program Files (x86)/Java/jre1.8.0_251’) for 32-bit version

We need to create training and holdout data since the feature selection procedure should be applied to the training data only. We will use IDs to distinguish the training and holdout data. Since we would like to have 5 disjoint holdout datasets for the cross validation (corresponding to 5 training datasets), we sample cases by using ID without replacement and obtain each 20% of the sample cases for the holdout data.

set.seed <- 1234
all_data_ID <- lapply(df_all, function(x) sample(x$ID, nrow(x)))
holdout_index <- Get_holdout_index(all_data_ID)

Since there will be \(5\times 16=80\) training datasets, we will use a parallel algorithm to conduct the feature selection procedure for these datasets simultaneously. We apply each feature selection method to each dataset individually and the result is stored in a list with the following structure. The list contains 5 sub-lists which contain the results corresponding to the five fold cross validation training sets. Additionally, each of these 5 sub-lists contains 16 elements corresponding to the 16 data scenarios. Then we store 3 lists: features_FFS, features_LASSO, features_RF into a list: all_features.

For example, all_features[[“LASSO”]][[3]][[“Median_Drop_Label”]] stores the features after applying LASSO to the third training datasets from the data: imputing median for missing values in the numerical variables, dropping missing values in the categorical variables, and mapping each level in each categorical variable to an integer.

numCores <- 4 # this depends on the number of cores and the installed memory (RAM) available in the computer
# Fast Feature selection
features_FFS <- vector(mode="list", 5)
startTime <- proc.time()[3] # set up timer

for (i in 1:5){
  cl <- makeCluster(numCores, type="SOCK")
  features_FFS[[i]] <- parSapply(cl, 1:16, select_variables, df_all, "OUTCOME", "FFS", all_data_ID, holdout_index[[i]], 2090)
  stopCluster(cl)
}

runTime = proc.time()[3] - startTime
print(paste("It took", round(runTime), "seconds to obtain all features used using FFS."))

# Lasso Feature selection for Binomial TARGETS
features_LASSO <- vector(mode="list", 5)
startTime <- proc.time()[3] # set up timer

for (i in 1:5){
  cl <- makeCluster(numCores, type="SOCK")
  features_LASSO[[i]] <- parSapply(cl, 1:16, select_variables, df_all, "OUTCOME", "LASSO", all_data_ID, holdout_index[[i]], 2090)
  stopCluster(cl)
}

runTime = proc.time()[3] - startTime
print(paste("It took", round(runTime), "seconds to obtain all features used using LASSO."))

# Random Forest Feature Selection
features_RF <- vector(mode="list", 5)
startTime <- proc.time()[3] # set up timer

for (i in 1:5){
  cl <- makeCluster(numCores, type="SOCK")
  features_RF[[i]] <- parSapply(cl, 1:16, select_variables, df_all, "OUTCOME", "RF", all_data_ID, holdout_index[[i]], 2090)
  stopCluster(cl)
}

runTime = proc.time()[3] - startTime
print(paste("It took", round(runTime), "seconds to obtain all features used using Random Forest."))

all_features <- list(features_FFS, features_LASSO, features_RF)
names(all_features) <- c("FFS", "LASSO", "RF")

For the reference, the approximate run time (in seconds) for each method to obtain the list using a Windows 10 machine with Intel(R) Core(TM) i7 Processor (4 cores, 8GB RAM) is reported below.

Time <- c(374, 2328, 5576)
names(Time) <- c("FFS", "LASSO", "RF")
data.table(t(Time))

3.2 Obtain Results for All Scenarios

In the following code chunk, a parallel algorithm will be used to obtain the prediction performance of each combination of factors and we save the result in the file: all_scenarios.csv. We report five common performance metrics that are suited for two class classification problems: AUC, accuracy, sensitivity, specificity and G-mean where G-mean \(= \sqrt{\mbox{sensitivity} \times \mbox{specificity}}\).

We use a function DOE_function() to conduct this study. This function can be found in the custom_functions.R. The output of this function could be either a vector or a list depends on if the predicted probability for Survival is returned.

fold <- 1:5
feature_selection <- c("FFS", "LASSO","RF")
subsampling <- c("none", "down", "Up", "smote", "rose")
algorithm <- c("ANN", "DT", "ElasticNet", "KPLSR", "LDA", "LR", "NB", "RF", "XGB")

Design <- crossing(fold, imputation_num, imputation_cat, encoding, feature_selection, subsampling, algorithm)

numCores <- 4 # this depends on the number of cores and the installed memory (RAM) available in the computer

cl <- makeCluster(numCores, type="SOCK")

all_scenarios <- parSapply(cl, 1:nrow(Design), DOE_function, Design, df_all, "OUTCOME", all_data_ID, holdout_index, all_features, return.prediction=FALSE, 2020)

stopCluster(cl)

all_scenarios <- t(all_scenarios)
colnames(all_scenarios) <- c("fold", "imputation_num", "imputation_cat", "encoding", "feature_selection", "subsampling", "algorithm", "auc", "sen", "spec", "accu", "gmean")
all_scenarios <- tbl_df(all_scenarios)
all_scenarios[,c("fold", "auc", "sen", "spec", "accu", "gmean")] %<>% mutate_all( as.numeric)

#write.csv(all_scenarios, "../Results/all_scenarios.csv", row.names = FALSE)

4 Hierarchical Regression

In this section, we analysis the result we obtain for all scenarios using hierarchical regression.

4.1 Read and preprocess the result data for all scenarios

In the following code chunk, we read the result data for all scenarios.

data <- read.csv("https://raw.githubusercontent.com/Ying-Ju/Explaining-Predictive-Model-Performance.github.io/master/all_scenarios.csv")

Next we check if we obtain all values of performance measures for all scenarios by studying the distribution of missing values in the data. Then we remove the cases with missing values since we are not able to use them for the further analysis.

4.1.1 Distribution of Missing Values

Missing Values

We study the distribution of missing values in the result data.

plot_missing(data)

Histograms of Performance Measures

plot_histogram(data[,c("accu", "auc", "gmean", "sen", "spec")])

Cases with Missing Values

We print the cases that are missing in the following table. We note that all 17 missing cases are of algorithm KPLSR.

na_vec <- which(is.na(data$auc))
datatable(data[na_vec,c("algorithm", "subsampling", "feature_selection", "imputation_num", "imputation_cat", "encoding")]) #prints the cases that are missing

We glimpse the cases that are not missing.

head(data[-na_vec,c("algorithm",  "subsampling", "feature_selection", "imputation_num", "imputation_cat", "encoding")]) #glimpses the cases that are not missing

4.1.2 Examination of Complete Data

Below we take a look at the complete data.

Distribution of Complete Data

nonconverge_cases <- data[na_vec,]
complete <- data[-na_vec,]
plot_missing(complete)

We explicitly removed the NA’s from the data, creating a data frame called complete.

Histograms of Performance Measures

plot_histogram(complete[,c("accu", "auc", "gmean", "sen", "spec")])

4.2 Analysis Explanation

We will use “hierarchical Regression.” This is a simple technique of entering variables into a regression in blocks so that we can see the “net effect” of the variables on the response. It is a popular method in explanatory modeling, especially in the behavioral sciences. In the behavioral sciences, researchers will enter, first the demographics or variables that have not been manipulated, and are outside of the control of the researcher (e.g. gender, race), then they may enter existing variables that are known to effect a response, but are not under study in the current research, then they will enter the newly proposed research variables. The goal is to show that the newly proposed variables “explain” a large percentage of additional variation.

We believe that this audience is used to seeing explanatory analyses presented in this way. It is actually fairly informative.

In the examples below, we choose three blocks:

  1. Categorical Imputation, Numerical Imputation, and Encoding Main Effects plus the two-way interactions among these variables.
  2. All variables in #1 plus Subsampling Main Effects and all possible two-way interactions among these variables.
  3. All variables in #1+#2 plus ML method and Variable Selection method. In each step, we will include all possible two-way interactions.

Our reasoning is that imputation and encoding is done first, while data cleaning. Next, subsampling is done prior to any analysis. Finally, the algorithm and variable selection are done in tandem, with choices made simultaneously.

We chose to consider AUC and gmean as both summarize well for the unbalanced example and give different views of the fit. The results differ slightly according to the response.

4.3 AUC Analysis

4.3.1 Block 1: Numeric Imputation, Categorical Imputation, Encoding

In the following code chunk, we obtain the Anova II SS using Block 1.

block1auc <- formula(auc~(imputation_num+imputation_cat+encoding)^2)
aucmod1 <- lm(block1auc,data=complete)
Anova(aucmod1,type="II")

From the above, we see that the interaction between numeric and categorical imputation is significant.

print(paste("R-square = ", round(summary(aucmod1)$r.square, 4)))
## [1] "R-square =  0.01"

Despite the statistical significance of the imputation method interaction, we can see that multiple R-square is very low. We must keep in mind that the error degrees of freedom is 10,770. Power is high, and effect sizes are quite small.

We report the individual coefficients in the following table.

b <- data.frame(coefficients(aucmod1))
b$stderr <- summary(aucmod1)$coefficients[,2]
b$t <- summary(aucmod1)$coefficients[,3] 
b$pval <- summary(aucmod1)$coefficients[,4]
table1 <- round(b, 4)
colnames(table1) <- c("B","Std Error","t","p-val")
datatable(table1)

4.3.2 Block 2: Block 1 + subsampling

In the following code chunk, we obtain the Anova II SS using Block 2.

block2auc <- formula(auc~(imputation_num+imputation_cat+encoding
+subsampling)^2)
aucmod2 <- lm(block2auc,data=complete)
Anova(aucmod2,type="II")

From the above, we see that the numerical and categorical imuptation interaction remains significant in this model. In addition, the main effect for subsampling is significant.

print(paste("R-square = ", round(summary(aucmod2)$r.square, 4)))
## [1] "R-square =  0.2537"
waldtest(aucmod1,aucmod2)
r2diff12 <- round(summary(aucmod2)$r.squared-summary(aucmod1)$r.squared, 4)
print(paste("r-squared difference = ",r2diff12))
## [1] "r-squared difference =  0.2437"

We can see quite a change in the R-sqare from the addition of resampling/subsampling.

The wald test tests for significance of the additional information accounted for by blocks of predictors added to a model. We have statistical significance with F = 146.25, on 24 numerator and 10770 denominator degrees of freedom (p<2.2e-16). This indicates that the model has a significant drop in SSE by including subsampling method in the model that already included encoding and imputation methods (categorical and numerical). This indicates that subsampling is (statistically) important to the AUC of the model.

In addition the increase in R-square is 24.37% when subsampling is included compared to the model with only encoding and imputation methods. Note this is explanatory in terms of the behavior of the experimental data.

We report the individual coefficients in the following table.

b <- data.frame(coefficients(aucmod2))
b$stderr <- summary(aucmod2)$coefficients[,2]
b$t <- summary(aucmod2)$coefficients[,3] 
b$pval <- summary(aucmod2)$coefficients[,4]
table2 <- round(b, 4)
colnames(table2) <- c("B","Std Error","t","p-val")
datatable(table2)

4.3.3 Block 3: Block 1 + Block 2 + ML method + Variable Selection Method

In the following code chunk, we obtain the Anova II SS using Block 3.

block3auc <- formula(auc~(imputation_num+imputation_cat+encoding
+subsampling+feature_selection+algorithm)^2)
aucmod3 <- lm(block3auc,data=complete)
Anova(aucmod3,type="II")

The results are quite complex. All two-way interactions are significant except for interactions that include encoding. It seems that encoding is not relevant to AUC, but all other variables interact.

print(paste("R-square = ", round(summary(aucmod3)$r.square, 4)))
## [1] "R-square =  0.8941"
waldtest(aucmod2,aucmod3)
r2diff23 <- round(summary(aucmod3)$r.squared-summary(aucmod2)$r.squared,4)
print(paste("r-squared difference = ",r2diff23))
## [1] "r-squared difference =  0.6404"

The wald test tests for significance of the additional information accounted for by blocks of predictors added to a model. We have statistical significance with F = 554.32, on 116 numerator and 10746 denominator degrees of freedom (p<2.2e-16). This indicates that the model has a significant drop in SSE by including the ML and Variable Selection methods along with the corresponding interaction terms.

The difference in the model R-square values is 64% between the model in Block 2 and the model in Block 3. This suggests that Algorithm and Variable selection method explain more observed variation in AUC than the Block 1 and 2 factors.

We report the individual coefficients in the following table.

b <- data.frame(coefficients(aucmod3))
b$stderr <- summary(aucmod3)$coefficients[,2]
b$t <- summary(aucmod3)$coefficients[,3] 
b$pval <- summary(aucmod3)$coefficients[,4]
table3 <- round(b, 4)
colnames(table3)=c("B","Std Error","t","p-val")
datatable(table3)

4.3.4 Effect size

Even though the two way interactions are statistically significant, many are not practically significant. We recommend computing a common measure of “effect size” known as partial \(\eta_p^2\). This is equivalent to what statisticians call partial \(R^2\) and is computed as \[\eta_p^2=\frac{SS_{factor}}{SS_{factor}+SS_{error}}.\]

anovaauc <- Anova(aucmod3,type="II")
error <- anovaauc$`Sum Sq`[nrow(anovaauc)]
p.eta.sq <- as.data.frame(anovaauc$`Sum Sq`/(error+anovaauc$`Sum Sq`))
rownames(p.eta.sq) <- rownames(anovaauc)
colnames(p.eta.sq) <- c("effect")
datatable(round(p.eta.sq,4))

In the behavioral sciences, effects that are smaller than 0.02. Based on this analysis, we will retain the following in our model: Main effects: imputation_num, imputation_cat, subsampling, feature_selection, algorithm, and
imputation_num:imputation_cat, imputation_num:feature_selection, subsampling:feature_selection, subsampling:algorithm, feature_selection:algorithm

4.3.5 Block 4: Reduced Model

In the following code chunk, we obtain the Anova II SS using Block 4.

block4auc <- formula(auc~imputation_num+imputation_cat+subsampling+feature_selection+algorithm
+imputation_num*imputation_cat+
imputation_num*feature_selection+
subsampling*feature_selection+
subsampling*algorithm+
feature_selection*algorithm)
aucmod4 <- lm(block4auc,data=complete)
Anova(aucmod4,type="II")
print(paste("R-square = ", round(summary(aucmod4)$r.square, 4)))
## [1] "R-square =  0.8903"
waldtest(aucmod4,aucmod3)
r2diff43 <- round(summary(aucmod4)$r.squared-summary(aucmod3)$r.squared,4)
print(paste("r-squared difference = ",r2diff43))
## [1] "r-squared difference =  -0.0039"

While there is a statistically significant difference between models 3 and 4 (F=5.3301 on 73 and 10711 df, p<2.21e-16), the difference in the \(R^2\) values is less than 1%.

If we wish to explain the interaction terms, this might be an easier model to explain.

We report the individual coefficients in the following table.

b <- data.frame(coefficients(aucmod4))
b$stderr <- summary(aucmod4)$coefficients[,2]
b$t <- summary(aucmod4)$coefficients[,3] 
b$pval <- summary(aucmod4)$coefficients[,4]
table4 <- round(b, 4)
colnames(table4) <- c("B","Std Error","t","p-val")
datatable(table4)

4.3.6 Multiple Comparisons

Here we conducted parwise comparisons among interactions of factors using Tukey-adjusted comparisons using Model 4.

catnum <- lsmeans(aucmod4,pairwise~imputation_cat:imputation_num,adjust="tukey")
plot(catnum[[2]])+theme_minimal() 

The above plot shows the Tukey’s Least Squares adjusted mean pairwise contrasts with default 95% family-wise adjusted confidence intervals. Although there is some statistically significant differences (indicated by intervals that do not cross zero), practically, the differences in AUC are not important. It does, however, appear that Dropping both the numerical and categorical missing is not advisable.

numfs <- lsmeans(aucmod4,pairwise~imputation_num:feature_selection,adjust="tukey")
plot(numfs[[2]])+theme_minimal()

This is pretty difficult to interpret. But it appears that subsampling methods of Down, coupled with Median imputation or dropping the missing altogether seems to work the best. Rose is not advisable.

In the following, we show the parwise comparisons among interactions of sumsampling strategies and machine learning algorithms. Since there are 990 comparison cases, we show every 45 cases in one figure so that it is more readable.

algresamp=lsmeans(aucmod4,pairwise~algorithm:subsampling,adjust="tukey")
#45 choose 2 = 990 cases.
for (i in 0:21){
  assign(paste0("p",(i+1)),plot(algresamp[[2]][(i*45+1):((i+1)*45)])+theme_minimal())
  
  # We introduce a tab for each participant using tabset
  cat('####',paste0("P", (i+1)), "{-}",'\n')
  print(get(paste0("p",(i+1))))
  cat('\n \n')
}

P1

P2

P3

P4

P5

P6

P7

P8

P9

P10

P11

P12

P13

P14

P15

P16

P17

P18

P19

P20

P21

P22

4.3.7 Interaction Plots

In this section, we study the two-way interaction effects on auc values with error bars indicating 95% prediction intervals on each marginal mean.

Num X Cat Impute

theme_set(theme_sjplot())
plot_model(aucmod4, type="pred", terms=c("imputation_cat", "imputation_num"), line.size = 1.5, title="", axis.title = c("Categorical Variable Imputation","AUC"), legend.title = "Numerical Variable Imputation")+font_size(axis_title.x=14, axis_title.y=14,labels.x=12, labels.y=12)+ylim(0.35, 0.65)+theme(legend.position="top")

Feature Select X Num Impute

plot_model(aucmod4, type="pred", terms=c("imputation_num", "feature_selection"), line.size = 1.5, title="", axis.title=c("Numerical Variable Imputation", "AUC"),
           legend.title="Feature Selection")+font_size(axis_title.x=14, axis_title.y=14,labels.x=12, labels.y=12)+ylim(0.35, 0.65)+theme(legend.position="top")

Feature Select X Subsampling

plot_model(aucmod4, type="pred", terms=c("subsampling","feature_selection"), line.size = 1.5, title="", axis.title=c("Subsampling Strategy", "AUC"),
           legend.title="Feature Selection")+font_size(axis_title.x=14, axis_title.y=14,labels.x=12, labels.y=12)+ylim(0.35, 0.65)+theme(legend.position="top")

Subsampling X Algorithm

plot_model(aucmod4, type="pred", terms=c("algorithm", "subsampling"), line.size = 1.5, title="",axis.title=c("Machine Learning Algorithm", "AUC"),
           legend.title="Subsampling Strategy")+font_size(axis_title.x=14, axis_title.y=14,labels.x=12, labels.y=12)+ylim(0.35, 0.65)+theme(legend.position="top")

Feature Select X Algorithm

plot_model(aucmod4, type="pred", terms=c("algorithm","feature_selection"), line.size = 1.5, title="", axis.title=c("Machine Learning Algorithm", "AUC"),
           legend.title="Feature Selection")+font_size(axis_title.x=14, axis_title.y=14,labels.x=12, labels.y=12)+ylim(0.35, 0.65) +theme(legend.position="top")


5 Extending our approach to other problems

We proposed a potential framework for statistical experiments within the KDDM process. The following flowchart shows an overview of how experimental design can be used to inform the decisions made within the KDDM process. Here, AUPRC is the area under the precision-recall curve.

An overview of the proposed framework


References

Chen CK, Manlhiot C, Mital S, Schwartz SM, Van Arsdell GS, Caldarone C, McCrindle BW, Dipchand AI (2018) Prelisting predictions of early postoperative survival in infant heart transplantation using classification and regression tree analysis. Pediatric transplantation 22(2):e13105.
Dag A, Oztekin A, Yucel A, Bulur S, Megahed FM (2017) Predicting heart transplantation outcomes through data analytics. Decision Support Systems 94:42–52.
Dag A, Topuz K, Oztekin A, Bulur S, Megahed FM (2016) A probabilistic data-driven framework for scoring the preoperative recipient-donor heart transplant survival. Decision Support Systems 86:1–12.
De Jonge E, Van Der Loo M (2013) An introduction to data cleaning with R (Statistics Netherlands, The Hague/Heerlen).
Guven G, Brankovic M, Constantinescu AA, Brugts JJ, Hesselink DA, Akin S, Struijs A, et al. (2018) Preoperative right heart hemodynamics predict postoperative acute kidney injury after heart transplantation. Intensive care medicine 44(5):588–597.
Kuhn M (2019) 12 using recipes with train | the caret package.
Medved D, Ohlsson M, Höglund P, Andersson B, Nugues P, Nilsson J (2018) Improving prediction of heart transplantation outcome using deep learning techniques. Scientific Reports 8(1):1–9.
Parham P (2014) The immune system (Garland Science).
Shmueli G, others (2010) To explain or to predict? Statistical science 25(3):289–310.
Vega E, Schroder J, Nicoara A (2017) Postoperative management of heart transplantation patients. Best Practice & Research Clinical Anaesthesiology 31(2):201–213.
Weisdorf D, Spellman S, Haagenson M, Horowitz M, Lee S, Anasetti C, Setterholm M, et al. (2008) Classification of HLA-matching for retrospective analysis of unrelated donor transplantation: Revised definitions to predict survival. Biology of Blood and Marrow Transplantation 14(7):748–758.
Zhang K, Sheu R, Zimmerman NM, Alfirevic A, Sale S, Gillinov AM, Duncan AE (2019) A comparison of global longitudinal, circumferential, and radial strain to predict outcomes after cardiac surgery. Journal of cardiothoracic and vascular anesthesia 33(5):1315–1322.


  1. Clark University, hamid@clarku.edu↩︎

  2. University of Dayton, ychen4@udayton.edu↩︎

  3. Miami University, leonarr@miamioh.edu↩︎

  4. Miami University, fmegahed@miamioh.edu↩︎

  5. Miami University, farmerl2@miamioh.eduazg0074@auburn.edu↩︎

LS0tDQp0aXRsZTogU3VwcGxlbWVudGFyeSBNYXRlcmlhbHMgZm9yIEV4cGxhaW5pbmcgUHJlZGljdGl2ZSBNb2RlbHMtQW4gRXhwZXJpbWVudGFsIFN0dWR5IG9mIERhdGEgUHJlcGFyYXRpb24gYW5kIE1vZGVsIENob2ljZSBvbiBQcmVkaWN0aXZlIFBlcmZvcm1hbmNlDQphdXRob3I6DQotIEhhbWlkcmV6YSBBaGFkeSBEb2xhdHNhcmFeW0NsYXJrIFVuaXZlcnNpdHksIFtoYW1pZEBjbGFya3UuZWR1XShtYWlsdG86aGFtaWRAY2xhcmt1LmVkdSldDQotIFlpbmctSnUgVGVzc2EgQ2hlbl5bVW5pdmVyc2l0eSBvZiBEYXl0b24sIFt5Y2hlbjRAdWRheXRvbi5lZHVdKG1haWx0bzp5Y2hlbjRAdWRheXRvbi5lZHUpXQ0KLSBSb2JlcnQgRC4gTGVvbmFyZF5bTWlhbWkgVW5pdmVyc2l0eSwgW2xlb25hcnJAbWlhbWlvaC5lZHVdKG1haWx0bzpsZW9uYXJyQG1pYW1pb2guZWR1KV0NCi0gRmFkZWwgTS4gTWVnYWhlZF5bTWlhbWkgVW5pdmVyc2l0eSwgW2ZtZWdhaGVkQG1pYW1pb2guZWR1XShtYWlsdG86Zm1lZ2FoZWRAbWlhbWlvaC5lZHUpXQ0KLSBMLiBBbGxpc29uIEpvbmVzLUZhcm1lcl5bTWlhbWkgVW5pdmVyc2l0eSwgW2Zhcm1lcmwyQG1pYW1pb2guZWR1YXpnMDA3NEBhdWJ1cm4uZWR1XShtYWlsdG86ZmFybWVybDJAbWlhbWlvaC5lZHUpXQ0KDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiDQpiaWJsaW9ncmFwaHk6IHJlZnMuYmliDQpjc2w6IGluZm9ybWF0aW9uLXN5c3RlbXMtcmVzZWFyY2guY3NsDQpsaW5rLWNpdGF0aW9uczogeWVzDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICB0aGVtZTogc2ltcGxleA0KLS0tDQogIA0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgY2FjaGUgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgIGNhY2hlLmxhenkgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBvdXQud2lkdGggPSAiMTAwJSIsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBwcm9ncmVzcyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBkcGkgPSA2MDApDQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02KQ0KYGBgDQoNCi0tLQ0KICANClRoaXMgd2Vic2l0ZSBoYXMgPHNwYW4gc3R5bGU9InRleHQtZGVjb3JhdGlvbjp1bmRlcmxpbmUiPnRocmVlPC9zcGFuPiBtYWluIHB1cnBvc2VzOg0KICANCi0gKipQcm92aWRlcyB0aGUgY29kZSB3ZSB1c2VkIGZvcioqIA0KKiBMb2FkaW5nIHRoZSBkYXRhIGludG8gUjsNCiAgKiBFeHBsb3JpbmcgdGhlIFVOT1MgZGF0YSwgKGUuZy4sIGlkZW50aWZ5aW5nIHRoZSBudW1iZXIgb2YgY29sdW1ucywgb2JzZXJ2YXRpb25zLCAlIGRhdGEgbWlzc2luZyB3aXRoaW4gZWFjaCBjb2x1bW4pOw0KICAqIFN1bW1hcml6aW5nIGFuZCB2aXN1YWxpemluZyB0aGUgZGF0YTsgIA0KICAqIERhdGEgcHJlcHJvY2Vzc2luZzsNCiogT2J0YWluaW5nIHRoZSBtYWNoaW5lIGxlYXJuaW5nIHJlc3VsdHM7IGFuZA0KKiBUaGUgc3RhdGlzdGljYWwgYW5hbHlzaXMgb2YgdGhlIG1hY2hpbmUgbGVhcm5pbmcgcmVzdWx0cyB0aHJvdWdoIHRoZSB1c2Ugb2YgYSByZWdyZXNzaW9uIGZyYW1ld29yay4gIA0KDQoNCi0gKipQcm92aWRlcyB0aGUgcmVzdWx0cyB0aGF0IHdlIGhhdmUgb2J0YWluZWQgZm9yIGVhY2ggc3RhZ2Ugb2YgdGhlIGFuYWx5c2lzLioqICANCiAgDQotIEFsbG93cyBmb3IgKipyZXVzaW5nIG91ciBjb2RlIGFuZCByZXByb2R1Y2luZyBvdXIgcmVzdWx0cyoqLCB3aGljaCBpbiBvdXIgdmlldyBpcyBhIG1ham9yIGxpbWl0YXRpb24gaW4gdGhlIG1ham9yaXR5IG9mIHRoZSBwdWJsaXNoZWQgbGl0ZXJhdHVyZSBzaW5jZSB0aGV5IGRvIG5vdCBwcm92aWRlIGNvZGUgYW5kIGl0IGlzIG9mdGVuIHVuY2xlYXIgd2hhdCBhcmUgc29tZSBvZiB0aGUgZGVjaXNpb24gbWFkZSBpbiB0ZXJtcyBvZiBkYXRhIHByZXByb2Nlc3NpbmcgYW5kIG1vZGVsaW5nLiAgDQoNCkRhdGEgd2FzIHByb3ZpZGVkIGZyb20gdGhlIFVOT1MgcmVnaXN0cnkgYnkgc3RhZmYgYXQgdGhlIFVTLCBVbml0ZWQgTmV0d29yayBmb3IgT3JnYW4gU2hhcmluZy4gUGVyIHRoZSBVTk9TIHJlZ3VsYXRpb25zLCB3ZSBjYW5ub3Qgc2hhcmUgdGhlaXIgZGF0YS4gRm9yIG9idGFpbmluZyB0aGUgZGF0YSwgdGhlIHJlYWRlciBzaG91bGQgY2xpY2sgW2hlcmVdKGh0dHBzOi8vb3B0bi50cmFuc3BsYW50Lmhyc2EuZ292L2RhdGEvcmVxdWVzdC1kYXRhLyl7dGFyZ2V0PSJfYmxhbmsifS4gV2UgYWxzbyBoaWdobHkgcmVjb21tZW5kIHRoYXQgdGhlIHJlYWRlciBnZXRzIGZhbWlsaWFyaXplZCB3aXRoIHRoZSBkYXRhIGRpY3Rpb25hcnkgKHNlZSBbRGF0YSBEZWZpbml0aW9uc10oaHR0cHM6Ly93d3cuc3J0ci5vcmcvcmVxdWVzdGluZy1zcnRyLWRhdGEvc2FmLWRhdGEtZGljdGlvbmFyeS8pe3RhcmdldD0iX2JsYW5rIn0gZm9yIGRldGFpbHMpLiAgDQoNClRvIG5hdmlnYXRlIHRoaXMgc2l0ZSwgcGxlYXNlIGZlZWwgZnJlZSB0byB1c2UgdGhlIG5hdmlnYXRpb24gYmFyIGF0IHRoZSBsZWZ0LiBUaGUgcmVhZGVyIGNhbiAqKnNob3cqKiBhbnkgY29kZSBjaHVuayBieSBjbGlja2luZyBvbiB0aGUgY29kZSBidXR0b24uIFdlIGNob3NlIHRvIG1ha2UgdGhlIGRlZmF1bHQgZm9yIHRoZSBjb2RlIGhpZGRlbiBzaW5jZSB3ZTogKGEpIHdhbnRlZCB0byBpbXByb3ZlIHRoZSByZWFkYWJpbGl0eSBvZiB0aGlzIGRvY3VtZW50OyBhbmQgKGIpIGFzc3VtZWQgdGhhdCB0aGUgcmVhZGVycyB3aWxsIG5vdCBiZSBpbnRlcmVzdGVkIGluIHJlYWRpbmcgZXZlcnkgY29kZSBjaHVuay4NCg0KQSBsaXN0IG9mIHRoZSBjdXN0b21pemVkIGZ1bmN0aW9ucyB3ZSBjcmVhdGVkIGZvciB0aGlzIHN0dWR5IGNhbiBiZSBmb3VuZCBpbiB0aGUgW2N1c3RvbV9mdW5jdGlvbnMuUl0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL1lpbmctSnUvRXhwbGFpbmluZy1QcmVkaWN0aXZlLU1vZGVsLVBlcmZvcm1hbmNlLmdpdGh1Yi5pby9tYXN0ZXIvY3VzdG9tX2Z1bmN0aW9ucy5SKXt0YXJnZXQ9Il9ibGFuayJ9Lg0KDQojIFJlYWRpbmcgYW5kIEV4cGxvcmluZyB0aGUgVU5PUyBEYXRhc2V0DQoNClRoZSBzbmlwcGV0IGJlbG93IGRvY3VtZW50cyB0aGUgbGlzdCBvZiAqKlIqKiBwYWNrYWdlcyBhbmQgZnVuY3Rpb25zIHRoYXQgd2VyZSB1c2VkIGluIHRoaXMgcmVzZWFyY2guIEZvciBjb252ZW5pZW5jZSwgd2UgdXNlZCB0aGUgW3BhY21hbiBwYWNrYWdlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcGFjbWFuL3BhY21hbi5wZGYpe3RhcmdldD0iX2JsYW5rIn0gc2luY2UgaXQgYWxsb3dzIGZvciBpbnN0YWxsaW5nL2xvYWRpbmcgdGhlIG5lZWRlZCBwYWNrYWdlcyBpbiBvbmUgc3RlcC4NCg0KYGBge3IgcGFja2FnZXN9DQpybShsaXN0ID0gbHMoKSkgIyBjbGVhciBnbG9iYWwgZW52aXJvbm1lbnQNCmdyYXBoaWNzLm9mZigpICMgY2xvc2UgYWxsIGdyYXBoaWNzDQppZihyZXF1aXJlKHBhY21hbik9PUZBTFNFKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKSAjIG5lZWRzIHRvIGJlIGluc3RhbGxlZCBmaXJzdA0KIyBwX2xvYWQgaXMgZXF1aXZhbGVudCB0byBjb21iaW5pbmcgYm90aCBpbnN0YWxsLnBhY2thZ2VzKCkgYW5kIGxpYnJhcnkoKQ0KcGFjbWFuOjpwX2xvYWQoQmlvY29tYiwgY2FyLCBjYXJldCwgY29uZmxpY3RlZCwgRGF0YUV4cGxvcmVyLCAjIGltcG9ydGFudCBmb3IgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyANCiAgICAgICAgICAgICAgIGRhdGFQcmVwYXJhdGlvbiwgZGF0YS50YWJsZSwgRFQsIGVmZmVjdHMsIGVtbWVhbnMsIGdncGxvdDIsIGhhdmVuLCBsbXRlc3QsIGxzbWVhbnMsIG1hZ3JpdHRyLCBtbHRvb2xzLCBwYXJ0eSwgcmVhZHhsLCB0aWR5dmVyc2UsICMgaW1wb3J0YW50IGFuYWx5dGljIHBhY2thZ2VzDQogICAgICAgICAgICAgICBzam1pc2MsIHNqUGxvdCwgc25vdywgdmFySW1wLCB5aGF0DQopDQoNCmNvbmZsaWN0X3ByZWZlcigiYXJyYW5nZSIsICJkcGx5ciIpDQpjb25mbGljdF9wcmVmZXIoInJlY29kZSIsICJkcGx5ciIpDQpjb25mbGljdF9wcmVmZXIoIm11dGF0ZSIsICJkcGx5ciIpDQpjb25mbGljdF9wcmVmZXIoIm1ha2VDbHVzdGVyIiwgInNub3ciKQ0KDQpzb3VyY2UoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9ZaW5nLUp1L0V4cGxhaW5pbmctUHJlZGljdGl2ZS1Nb2RlbC1QZXJmb3JtYW5jZS5naXRodWIuaW8vbWFzdGVyL2N1c3RvbV9mdW5jdGlvbnMuUiIpICMgSGFzIG91ciBjdXN0b20gZnVuY3Rpb25zDQoNCmBgYA0KDQojIyBMb2FkaW5nIHRoZSBVTk9TIERhdGFzZXQNCg0KYGBge3IgZGlyZWN0b3J5LCBpbmNsdWRlPUZBTFNFfQ0Kc2V0d2QoIkc6L1NoYXJlZCBkcml2ZXMvdHJhbnNwbGFudGF0aW9uL1BhcGVyIDIvQ29kZS9JU1IgQ29kZS9NYXJrZG93biIpICMgc2V0dGluZyB0aGUgd29ya2luZyBkaXJlY3RvcnkNCmBgYA0KDQpgYGB7ciByZWFkRGF0YX0NCmRmIDwtIHJlYWRfc2FzKCIuLi9EYXRhL3Rob3JhY2ljX2RhdGEuc2FzN2JkYXQiKQ0KZGYkSUQgPC0gcm93Lm5hbWVzKGRmKSAjIEluZGV4IHRvIGJlIHVzZWQgaW4gY3JlYXRpbmcvdHJhY2tpbmcgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IHNhbXBsZXMNCm9yZ1RhYmxlIDwtIHRhYmxlKGRmWywgJ1dMX09SRyddKQ0KZGYgJTw+JSBkcGx5cjo6ZmlsdGVyKFdMX09SRyA9PSAiSFIiKSAjIGZpbHRlcmluZyB0byBrZWVwIG9ubHkgaGVhcnQgdHJhbnNwbGFudHMNCmBgYA0KDQpPdXIgb3JpZ2luYWwgVU5PUyBkYXRhIGNvbnNpc3RlZCBvZiBgciBzdW0ob3JnVGFibGUpYCBvYnNlcnZhdGlvbnMsIHdoaWNoIGNvbnRhaW5lZDogKGEpIGByIG9yZ1RhYmxlWzFdYCB1bnNwZWNpZmllZCB0cmFuc3BsYW50IHR5cGVzLCAoYikgYHIgb3JnVGFibGVbMl1gIGNvbWJpbmVkIGhlYXJ0LWx1bmcgdHJhbnNwbGFudHMsIChjKSBgciBvcmdUYWJsZVszXWAgaGVhcnQgdHJhbnNwbGFudHMsIGFuZCAoZCkgYHIgb3JnVGFibGVbNF1gIGx1bmcgdHJhbnNwbGFudHMuIEdpdmVuIG91ciBmb2N1cyBvbiBoZWFydCB0cmFuc3BsYW50cywgd2UgaGF2ZSBvbmx5IHJldGFpbmVkIHRoZSBvYnNlcnZhdGlvbnMgd2hlcmUgb25seSBhIGhlYXJ0IHRyYW5zcGxhbnQgaXMgcGVyZm9ybWVkLiBUaGUgcmVzdWx0aW5nIGRhdGEgaXMgcmVzYXZlZCBpbnRvIG91ciBkYXRhLmZyYW1lIHRpdGxlZCBkZiwgd2hpY2ggbm93IGNvbnNpc3RzIG9mIGByIG5yb3coZGYpYCBvYnNlcnZhdGlvbnMgYW5kIGByIG5jb2woZGYpYCB2YXJpYWJsZXMgKHdoaWNoIGhhdmUgYmVlbiB1bmNoYW5nZWQgZnJvbSBvdXIgZmlsdGVyaW5nIG9wZXJhdGlvbikuIA0KDQojIyBFeHBsb3JpbmcgdGhlIFVOT1MgRGF0YXNldCB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30NCg0KSW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLCB3ZSBjb252ZXJ0IHRoZSBibGFuayBjZWxscyB0byAiTkEiIGJlZm9yZSB3ZSBzdHVkeSB0aGUgZGlzdHJpYnV0aW9uIG9mIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBkYXRhLg0KDQpgYGB7ciBjb252ZXJ0TkF9DQojIFRoZSB2YXJpYWJsZSBZUl9FTlRSWV9VU19UQ1Igd2FzIHJlY29yZGVkIHdpdGggdGltZSB6b25lLCB3ZSByZW1vdmVkIHRoZSB0aW1lIHpvbmUuDQpkZiRZUl9FTlRSWV9VU19UQ1IgPC0gYXMuRGF0ZShkZiRZUl9FTlRSWV9VU19UQ1IpDQojIFdlIGNvbnZlcnQgdGhlIGJsYW5rIGNlbGxzIHRvIE5BIGFzIHRoZXNlIHZhcmlhYmxlcyB3ZXJlIHdlbGwgY29kZWQuIA0KaW5kZXhfZGF0ZSA8LSB3aGljaCh1bmxpc3QobGFwcGx5KGRmLCBjbGFzcykpPT0iRGF0ZSIpDQpkZlssLWluZGV4X2RhdGVdICU8PiUgbXV0YXRlX2FsbChuYV9pZiwgIiIpICU+JSBtdXRhdGVfYWxsKG5hX2lmLCAiICIpICU+JSBtdXRhdGVfYWxsKG5hX2lmLCAiVSIpDQpgYGANCg0KIyMjIEZyYWN0aW9uIERhdGEgTWlzc2luZyB7LX0NCg0KYGBge3IgZnJhY0RhdGFNaXNzaW5nfQ0KbmFWZWMgPC0gaXMubmEoZGYpICU+JSBjb2xTdW1zKCkgJT4lIA0KICBgL2AobnJvdyhkZikpICMgVG8gbWFrZSBpdCBwZXJjZW50IG1pc3NpbmcgcGVyIHZhcmlhYmxlDQpuYW1lcyhuYVZlYykgPC0gY29sbmFtZXMoZGYpDQpuYVZlYy5xdWFudGlsZXMgPC0gcXVhbnRpbGUobmFWZWMpICU+JSByb3VuZCgyKQ0KDQpnZ3Bsb3QoKSArIGdlb21faGlzdG9ncmFtKGFlcyhuYVZlYyksIGJpbnMgPSAxMCkgKyANCiAgeGxhYigiRnJhY3Rpb24gRGF0YSBNaXNzaW5nIFBlciBWYXJpYWJsZSIpICsgeWxhYigiTnVtYmVyIG9mIFZhcmlhYmxlcyIpK3RoZW1lX21pbmltYWwoKSANCmBgYA0KDQpCYXNlZCBvbiB0aGUgaGVhcnQgdHJhbnNwbGFudGF0aW9uIGRhdGEsIGByIHN1bShzYXBwbHkoZGYsIGFueU5BKSlgIHZhcmlhYmxlcyBvdXQgb2YgdGhlIGByIG5yb3coZGYpYCBoYXZlIGF0IGxlYXN0IG9uZSBkYXRhIHBvaW50IG1pc3NpbmcgZGF0YS4gRnVydGhlcm1vcmUsIHdlIGhhdmUgY29tcHV0ZWQgdGhlIHBlcmNlbnQgbWlzc2luZyBkYXRhIHBlciB2YXJpYWJsZTsgdGhlIHZhbHVlcyBvZiB0aGUgZmlyc3QsIHNlY29uZCwgYW5kIHRoaXJkIHF1YW50aWxlcyAoaW4gdGVybXMgb2YgcGVyY2VudCBvZiBkYXRhIG1pc3NpbmcgcGVyIGNvbHVtbikgY29ycmVzcG9uZCB0byBgciAxMDAqbmFWZWMucXVhbnRpbGVzWzJdYCUsIGByIDEwMCpuYVZlYy5xdWFudGlsZXNbM11gJSwgYW5kIGByIDEwMCpuYVZlYy5xdWFudGlsZXNbNF1gJSxyZXNwZWN0aXZlbHkuIFRodXMsIGl0IGlzIGltcGVyYXRpdmUgdG8gZXhhbWluZSBob3cgdGhlc2UgdmFyaWFibGVzIGNhbiBiZSBwcmVwcm9jZXNzZWQgcHJpb3IgdG8gYW55IGFuYWx5c2lzLg0KDQoNCiMjIyBNaXNzaW5nIERhdGEgKFNhbXBsZSBDb2x1bW5zKSB7LX0NCg0KSW4gdGhlIHBsb3QgYmVsb3csIHdlIHNhbXBsZSAzMCBjb2x1bW5zIGF0IHJhbmRvbSBmcm9tIHRoZSBVTk9TIGRhdGFzZXQgdG8gc2hvdyB0aGUgYWN0dWFsIHBlcmNlbnRhZ2Ugb2YgdGhlIGRhdGEgdGhhdCBpcyBtaXNzaW5nIGZvciBlYWNoIHZhcmlhYmxlLiBUaGUgY29sb3JzIGFyZSB1c2VkIHRvIGRlbm90ZSB0aGUgZGF0YSBxdWFsaXR5IGZvciB0aGF0IGNvbHVtbiB1c2luZyBhIHRyYWZmaWMgbGlnaHQgc2NoZW1lICh3aGVyZSBncmVlbiBpcyBnb29kIGFuZCByZWQgaXMgYmFkKS4NCg0KYGBge3IgbWlzc2luZ1Bsb3R9DQpoZWFydC5uYS5wbG90IDwtIGRmWyxzYW1wbGUoY29sbmFtZXMoZGYpLCAzMCldICU+JSBwbG90X21pc3NpbmcoKQ0KYGBgDQoNCg0KIyMjIE1pc3NpbmcgRGF0YSAoUHJpbnRvdXQgZm9yIEFsbCBWYXJpYWJsZXMpIHstfQ0KDQpJbiB0aGUgY2h1bmsgYmVsb3csIHdlIHJlcG9ydCB0aGUgcGVyY2VudCBtaXNzaW5nIHBlciBlYWNoIHZhcmlhYmxlLiBUaGUgcHJpbnRvdXQgaXMgYXJyYW5nZWQgaW4gYSBkZXNjZW5kaW5nIG9yZGVyIG9mIG1pc3NpbmcgdmFsdWVzLiANCg0KYGBge3IgbWlzc2luZ1ByaW50T3V0fQ0KZGF0YS5mcmFtZSh2YXJpYWJsZU5hbWUgPSBuYW1lcyhuYVZlYyksIHBlcmNlbnRNaXNzaW5nID0gcm91bmQobmFWZWMqMTAwLCAyKSwgcm93Lm5hbWVzID0gTlVMTCkgJT4lDQogIGFycmFuZ2UoZGVzYyhwZXJjZW50TWlzc2luZykpICU+JSBkYXRhdGFibGUoKQ0KYGBgDQoNCg0KIyMjIENvbHVtbiBUeXBlcyB7LX0NClIgaW5pdGlhbGx5IGRpdmlkZXMgdGhlIGNvbHVtbnMgb2YgZGlmZmVyZW50IHR5cGVzLiBXZSBzdW1tYXJpemUgdGhlc2UgaW4gdGhlIHRhYmxlIGJlbG93Lg0KDQpgYGB7ciBjb2xUeXBlc30NCnR5cGVzIDwtIGRhdGFUeXBlcyhkZikgIyBzZWUgZnVuY3Rpb25zLlIgZmlsZQ0KbnVtZXJpYy5pbmRleCA8LSB3aGljaCh0eXBlcyRjbGFzc2VzLmRmPT0ibnVtZXJpYyIpDQpjaGFyYWN0ZXIuaW5kZXggPC0gd2hpY2godHlwZXMkY2xhc3Nlcy5kZj09ImNoYXJhY3RlciIpDQpkYXRhdGFibGUodHlwZXMpICMgcHJpbnRpbmcgdGhlIGRpZmZlcmVudCB0eXBlcw0KYGBgDQoNClRoZSByZWFkZXIgc2hvdWxkIG5vdGUgdGhhdCAqKlIqKiByZWFkcyBhbnkgdmFyaWFibGUgdGhhdCBwdXJlbHkgY29uc2lzdHMgb2YgbnVtZXJpYyB2YWx1ZXMgdG8gYmUgbnVtZXJpYy4gSG93ZXZlciwgZnJvbSBhIGRhdGEgYW5hbHlzaXMgcGVyc3BlY3RpdmUsIGEgbG90IG9mIHRoZXNlIHZhcmlhYmxlcyB3aWxsIG5lZWQgdG8gYmUgY29udmVydGVkIGludG8gZmFjdG9ycyBzaW5jZSB0aGV5IHJlcHJlc2VudCBmYWN0b3IgbGV2ZWxzIHJhdGhlciB0aGFuIG51bWVyaWMgdmFsdWVzIChlLmcuLCBkb25vci9yZWNpcGllbnQgZXRoaW5pY2l0eSBjYXRlZ29yaWVzLCBhbGxvY2F0aW9uIHR5cGUsIEhMQSBtaXNtYXRjaCBsZXZlbCwgZWR1Y2F0aW9uLCBldGMuKS4gVGh1cywgdGhlIGByIHR5cGVzW251bWVyaWMuaW5kZXgsMl1gIG51bWVyaWMgdmFyaWFibGVzIHNob3VsZCBiZSBleGFtaW5lZCBtb3JlIGNsb3NlbHkgdG8gZW5zdXJlIHRoYXQgdGhleSBhcmUgcHJvcGVybHkgY29kZWQgYnkgdGhlIHNvZnR3YXJlLg0KDQoNCi0tLQ0KICANCiMgRGF0YSBQcmVwYXJhdGlvbg0KICANCkluIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCBkZXRhaWxlZGx5IGRvY3VtZW50IHRoZSBkYXRhIHByZXBlcmF0aW9uIHByZWNlZHVyZXMgd2UgaGF2ZSBkb25lIGluIHRoZSBzdHVkeS4gDQoNCiMjIFRlY2huaWNhbGx5IENvcnJlY3QgVmFyaWFibGVzDQoNCkluIHRoaXMgc3Vic2VjdGlvbiwgd2Ugd2lsbCBjYXBpdGFsaXplIG9uIHRoZSBmaWxlIHRpdGxlZCA9ICJTVEFSIEZpbGUgRG9jdW1lbnRhdGlvbi54bHMiIHByb3ZpZGVkIGJ5IFVOT1MgdG8gYXV0b21hdGljYWxseSBjaGVjayBhbmQgY29udmVydCBhbnkgdmFyaWFibGUgdGhhdCB3YXMgYXNzaWduZWQgYW4gaW5jb3JyZWN0IHR5cGUgYnkgdGhlIFIgc29mdHdhcmUuIFRoZSByZWFkZXIgc2hvdWxkIG5vdGUgdGhhdCB0aGUgb3V0Y29tZSBvZiB0aGlzIGRhdGEgdHJhbnNmb3JtYXRpb24gc3RlcCBjYW4gYmUgcmVmZXJyZWQgYXMgKnRlY2huaWNhbGx5IGNvcnJlY3QgZGF0YSogW0BkZTIwMTNpbnRyb2R1Y3Rpb25dLiBOb3RlIHRoYXQgd2UgY29udmVydCBhbGwgZmFjdG9yIGNvbHVtbnMgdG8gY2hhcmFjdGVyIHRvIGZhY2lsaXRhdGUgdGhlaXIgbWFuaXB1bGF0aW9uIGluICoqUioqLg0KDQpgYGB7ciB0ZWNoQ29ycmVjdH0NCnZhckluZm8gPC0gcmVhZF9leGNlbCgiLi4vRGF0YS9TVEFSIEZpbGUgRG9jdW1lbnRhdGlvbi54bHMiLCBzaGVldCA9ICJUSE9SQUNJQyBEQVRBIiwgc2tpcCA9MSwgY29sX3R5cGVzID0gYyhyZXAoInRleHQiLDMpLCByZXAoImRhdGUiLDIpLCByZXAoImd1ZXNzIiw3KSkgKQ0KDQojIGZpeCBhbiBlcnJvciBpbiB0aGUgdmFyaWFibGUgaW5mb3JtYXRpb24gZG9jdW1lbnQsIHRoZXJlIHdlcmUgdHdvIHN0YXJ0IGRhdGVzOiAwMS1PY3QtODcsIDAxLU9jdC05MCBmb3IgdGhlIHZhcmlhYmxlOiBDTVZfRE9OLiBXZSBhc3NpZ24gMDEtT2N0LTkwIGZvciB0aGUgc3RhcnQgZGF0ZS4NCg0KdmFySW5mbyRgVkFSIFNUQVJUIERBVEVgWzUxXSA8LSAiMTk5MC0xMC0wMSINCg0KIyBwdWxsaW5nIG5hbWVzIG9mIHZhcmlhYmxlcyB0aGF0IGhhZCB0eXBlIGNoYXIgT1IgaGF2aW5nIG51bWVyaWMgY2F0ZWdvcmllcyAoZXhwbGFpbmVkIGluIFNBRikNCmZhY3RvclZhcnMgPC0gdmFySW5mbyAlPiUgDQogIGRwbHlyOjpmaWx0ZXIoYERBVEEgVFlQRWA9PSJDSEFSIiB8IChgREFUQSBUWVBFYD09Ik5VTSIgJiAhaXMubmEoYFNBUyBBTkFMWVNJUyBGT1JNQVRgKSkgKSAlPiUNCiAgcHVsbChgVkFSSUFCTEUgTkFNRWApDQoNCiMgVGhlIGZvbGxvd2luZyB2YXJpYWJsZXMgYXJlIHF1YWxpdGF0aXZlIGJhc2VkIG9uIHRoZSBVTk9TIGRvY3VtZW50YXRpb24gYnV0IHRoZXkgd2VyZSBub3QgaWRlbnRpZmllZC4gDQpPdGhlcl9jYXRlZ29yaWNhbF92YXJzIDwtIGMoIkJMT09EX0lORl9ET04iLCAiSEJWX0NPUkVfRE9OIiwgIkhCVl9TVVJfQU5USUdFTiIsICAgIkhDVl9TRVJPU1RBVFVTIiwgIkhZUEVSVEVOU19EVVJfRE9OIiwgIklBQlBfVFJSIiwgIkxBU1RfSU5BQ1RfUkVBU09OIiwgIk9USEVSX0lORl9ET04iLCAiU1RFUk5PVE9NWV9UUlIiLCAiVVJJTkVfSU5GX0RPTiIpDQoNCmZhY3RvclZhcnMgPC0gYyhmYWN0b3JWYXJzLCBPdGhlcl9jYXRlZ29yaWNhbF92YXJzKQ0KDQpkZlssIGZhY3RvclZhcnNdICU8PiUgIGxhcHBseShhcy5jaGFyYWN0ZXIpICMgY29udmVydGluZyBmYWN0b3JzIHRvIGNoYXJhY3RlciB0byBmYWNpbGl0YXRlIHByZXByb2Nlc3NpbmcNCg0KI3NhdmVSRFMoZmFjdG9yVmFycywgIi4uL1Jlc3VsdHMvZmFjdG9yVmFycy5SRFMiKQ0KYGBgDQoNCkJhc2VkIG9uIHRoZSBhbmFseXNpcyBhYm92ZSwgd2UgaGF2ZSBpZGVudGlmaWVkIGByIGxlbmd0aChmYWN0b3JWYXJzKWAgY2F0ZWdvcmljYWwgdmFyaWFibGVzLiBOb3RlIHRoZSBSIGhhcyBwcmV2aW91c2x5IG9ubHkgaWRlbnRpZmllZCBgciB0eXBlc1tjaGFyYWN0ZXIuaW5kZXgsMl1gIGNoYXJhY3Rlci9jYXRlZ29yaWNhbCB2YXJpYWJsZXMuIFRoZSBuYW1lcyBvZiB0aGUgYHIgbGVuZ3RoKGZhY3RvclZhcnMpYCB2YXJpYWJsZXMgY2FuIGJlIGFjY2Vzc2VkIGluIHRoZSBbZmFjdG9yVmFycy5SRFNdKC4uL1Jlc3VsdHMvZmFjdG9yVmFycy5SRFMpIGZpbGUuIEZ1cnRoZXJtb3JlLCB3ZSBhcHBsaWVkIHRoZSBgYXMuZmFjdG9yKClgIHRvIGVuc3VyZSB0aGF0ICoqUioqIHJlY29nbml6ZXMgdGhlc2UgdmFyaWFibGVzIGFzIGNhdGVnb3JpY2FsLiANCg0KDQojIyBSb3d3aXNlIE9wZXJhdGlvbnM6IEFkZGl0aW9uYWwgRmlsdGVycw0KDQpJbiB0aGUgY29kZSBjaHVuayBiZWxvdywgd2UgZmlyc3QgZmlsdGVyIHRvIGtlZXAgYWR1bHQgcGF0aWVudHMgc2luY2Ugb3VyIHN0dWR5IGZvY3VzZXMgb24gb25seSBhZHVsdCB0cmFuc3BsYW50YXRpb24uIFRoZW4gd2UgcmVtb3ZlIGluZGl2aWR1YWxzIHdob3NlIHdlaWdodHMgd2VyZSBsb3dlciB0aGFuIC4wMSUgb2YgYWR1bHRzJyB3ZWlnaHRzIGluIHRoZSBkYXRhIChsZXNzIHRoYW4gMzBsYnMpIG9yIHdob3NlIGhlaWdodHMgd2VyZSBsZXNzIHRoYW4gLjAxJSBhZHVsdHMnIGhlaWdodHMgaW4gdGhlIGRhdGEgKGxvd2VyIHRoYW4gMTIyIGNtcykuIA0KDQpgYGB7ciByb3dmaWx0ZXJzfQ0KZGYgPC0gZGYgJT4lIHN1YnNldChBR0U+PTE4KSAlPiUgIyBmaWx0ZXJpbmcgdG8ga2VlcCBvbmx5IEFkdWx0cw0KIyB3ZSBleGNsdWRlZCB0b28gbGlnaHQgb3IgdG9vIHNob3J0IHBlb3BsZSBpbiB0aGUgZm9sbG93aW5nIGNvZGUNCnN1YnNldChXR1RfS0dfRE9OX0NBTEMgPj0gcXVhbnRpbGUoV0dUX0tHX0RPTl9DQUxDLCAwLjAwMDEsIG5hLnJtID0gVFJVRSkpICU+JSANCnN1YnNldChXR1RfS0dfVENSID49IHF1YW50aWxlKFdHVF9LR19UQ1IsIDAuMDAwMSwgbmEucm0gPSBUUlVFKSkgJT4lDQpzdWJzZXQoSEdUX0NNX0RPTl9DQUxDID49IHF1YW50aWxlKEhHVF9DTV9ET05fQ0FMQywgMC4wMDAxLCBuYS5ybSA9IFRSVUUpKSAlPiUgDQpzdWJzZXQoSEdUX0NNX1RDUiA+PSBxdWFudGlsZShIR1RfQ01fVENSLCAwLjAwMDEsIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KIyMgQ29sdW1ud2lzZSBPcGVyYXRpb25zOiBEcm9wcGluZyBWYXJpYWJsZXMNCg0KSW4gdGhlIGNvZGUgY2h1bmsgYmVsb3csIHdlIGZpc3QgZmlsdGVyL3JlbW92ZSBhbnkgb2JzZXJ2YXRpb25zIHdpdGggbWlzc2luZyBHU1RBVFVTIG9yIEdUSU1FLiBPdXIgcmF0aW9uYWxlIGZvciBkb2luZyB0aGlzIHByaW9yIHRvIHJlbW92aW5nIHVuaWZvcm1hdGl2ZS9pcnJlbGV2YW50L3VucmVsaWFibGUgdmFyaWFibGVzIHdhcyBiYXNlZCBvbiB0aGUgZmFjdCB0aGF0IHRoZXNlIHZhcmlhYmxlcyB3b3VsZCBiZSB1c2VkIGluIGNvbXB1dGluZyBvdXIgdHJhbnNwbGFudGF0aW9uIE9VVENPTUUgYW5kIGhlbmNlLCB3ZSBkaWQgbm90IHdhbnQgdG8gbG9zZSB0aGVtLiBBZnRlciBwZXJmb3JtaW5nIHJlbW92ZSBhbnkgdmFyaWFibGVzIHRoYXQgbWVldCBhbnkgb2YgdGhlIGZvbGxvd2luZyBjcml0ZXJpYTogIA0KDQotIFZhcmlhYmxlcyB3aXRoIGEgc3RhcnRpbmcgZGF0ZSBvbiBvciBsYXRlciB0aGFuIEphbiAwMSwgMjAwMCwgZXhjZXB0IHRoZSB2YXJpYWJsZXMgdGhhdCBhcmUgY29ycmVzcG9uZGluZyB0byBwcmlvciBjYXJkaWFjIHN1cmdlcnkgKG5vbi10cmFuc3BsYW50KSBvciBsdW5nIHN1cmdlcnkgYXMgdGhlc2UgdmFyaWFibGVzIGhhdmUgYmVlbiBpZGVudGlmaWVkIGFzIGltcG9ydGFudCBpbmZvcm1hdGlvbiBpbiB0aGUgbGl0ZXJhdHVyZSAoQHZlZ2EyMDE3cG9zdG9wZXJhdGl2ZSwgQGNoZW4yMDE4cHJlbGlzdGluZywgQGd1dmVuMjAxOHByZW9wZXJhdGl2ZSwgQG1lZHZlZDIwMThpbXByb3ZpbmcsIEB6aGFuZzIwMTljb21wYXJpc29uKS4gVGhlc2UgdmFyaWFibGVzIGFyZToNCiogUFJJT1JfQ0FSRF9TVVJHX1RDUiwgUFJJT1JfQ0FSRF9TVVJHX1RSUiwgUFJJT1JfQ0FSRF9TVVJHX1RZUEVfT1NUWFRfVENSLCBQUklPUl9DQVJEX1NVUkdfVFlQRV9PU1RYVF9UUlIsIFBSSU9SX0NBUkRfU1VSR19UWVBFX1RDUiwgUFJJT1JfQ0FSRF9TVVJHX1RZUEVfVFJSLCBhbmQgUFJJT1JfTFVOR19TVVJHX1RZUEVfT1NUWFRfVFJSLg0KLSBBbnkgdmFyaWFibGVzIGhhdmluZyBhbiBlbmQgZGF0ZSBmb3IgZGF0YSBjb2xsZWN0aW9uIHNpbmNlIHRoZXkgd291bGQgbm90IGJlIGNvbGxlY3RlZCBpbiBhbnkgZnV0dXJlIHRyYW5zcGxhbnQgZXZlbnQuDQotIEFueSBwb3N0LXRyYW5zcGxhbnQgdmFyaWFibGVzLCB3aXRoIHRoZSBleGNlcHRpb24gb2YgR1RJTUUgYW5kIEdTVEFUVVMgKHdoaWNoIHdlIHVzZSBsYXRlciB0byByZWNvcmQgdGhlIHRyYW5zcGxhbnRhdGlvbiBvdXRjb21lIGF0IDEteWVhciBwb3N0IHRyYW5zcGxhbnQpICANCi0gUmVtb3ZpbmcgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMsIGNhcHR1cmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgYW50aWdlbiBhbGxlbGVzOiAgDQoqIERBMSwgREEyLCBSQTEsIFJBMiwgREIxLCBEQjIsIFJCMSwgUkIyLCBSRFIxLCBSRFIyLCBERFIxLCBhbmQgRERSMi4gIA0KKiBOb3RlIHRoYXQgZWFjaCBvZiB0aG9zZSB2YXJpYWJsZXMgaGFkIHRocmVlIHBvc3NpYmxlIHZhbHVlczogMCBpbmRpY2F0aW5nIG5vIG1pc21hdGNoLCAxIGluZGljYXRpbmcgb25lIGFsbGVsZSBtYXRjaGluZywgYW5kIDIgaW5kaWNhdGluZyB0aGF0IGJvdGggYWxsZWxlcyBtaXNtYXRjaGVkLiAgDQoqIE91ciByYXRpb25hbGUgZm9yIHJlbW92aW5nIHRob3NlIHZhcmlhYmxlcyB3YXMgYmFzZWQgb24gdGhlIG9ic2VydmF0aW9uIHRoYXQgdGhlIHZhcmlhYmxlcyBITEFNSVMsIEFNSVMsIEJNSVMgYW5kIERSTUlTIHJlY29yZGVkIHN1bW1hcmllcyBvZiBtYXRjaGVzIGluIHRoZXNlIGFudGlnZW4gYWxsZWxlcy4gVGhlIGludGVyZXN0ZWQgcmVhZGVyIGlzIHJlZmVycmVkIHRvIEBwYXJoYW0yMDE0aW1tdW5lIGZvciBhIGRpc2N1c3Npb24gb2YgSExBTUlTLCBhbmQgQHdlaXNkb3JmMjAwOGNsYXNzaWZpY2F0aW9uIGZvciBhIGRldGFpbGVkIGludHJvZHVjdGlvbiBvbiB0aG9zZSBmb3VyIHZhcmlhYmxlcy4gIA0KLSBSZW1vdmluZyB0aGUgdHdvIHZhcmlhYmxlcyBERUFUSF9DSVJDVU1fRE9OIGFuZCBERUFUSF9NRUNIX0RPTiBzaW5jZSBhIGNyb3NzLXRhYnVsYXRpb24gb2YgdGhlaXIgdmFsdWVzIGluZGljYXRlZCB0aGF0IHRoZXkgYXJlIG5vdCBjb25zaXN0ZW50LCB3aGljaCByZWR1Y2VzIHRoZSBpbnRlcnByZXRhdGlvbi91dGlsaXR5IGZyb20gdXNpbmcgdGhvc2UgdmFyaWFibGVzLiAgDQotIFJlbW92aW5nIHRoZSB2YXJpYWJsZSBUWF9ZRUFSIHNpbmNlIHdlIHdhbnRlZCB0byBkZXZlbG9wIGEgcHJlZGljdGl2ZSBhbmQgbm90IGFuIGV4cGxhbmF0b3J5IG1vZGVsLiBGb3IgbW9yZSBkZXRhaWxzIG9uIHRoZSBkaWZmZXJlbmNlLCB0aGUgcmVhZGVyIGlzIHJlZmVycmVkIHRvIEBzaG11ZWxpMjAxMGV4cGxhaW4uDQotIEFueSB2YXJpYWJsZXMgd2hlcmUgdGhlIG51bWJlciBvZiBtaXNzaW5nIG9ic2VydmF0aW9ucyBleGNlZWRlZCA5MCUgIA0KLSBBbnkgbnVtZXJpYyB2YXJpYWJsZXMgd2hlcmUgdGhlIG51bWJlciBvZiBtaXNzaW5nIG9ic2VydmF0aW9ucyBleGNlZWRlZCAzMCUNCg0KYGBge3IgY29sd2lzZX0NCnZhcnNWZWMgPC0gcmVhZF9yZHMoZ3pjb24odXJsKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vWWluZy1KdS9FeHBsYWluaW5nLVByZWRpY3RpdmUtTW9kZWwtUGVyZm9ybWFuY2UuZ2l0aHViLmlvL21hc3Rlci9kcm9wVmFycy5SRFMiKSkpICMgYSB2ZWN0b3Igb2YgNjkgdmFycyB0byBiZSBkcm9wcGVkDQphbGxlbGVzID0gYygiREExIiwiREEyIiwiUkExIiwiUkEyIiwiREIxIiwiREIyIiwiUkIxIiwiUkIyIiwiUkRSMSIsIlJEUjIiLCJERFIxIiwiRERSMiIpDQoNClZhcnN0b0JlRHJvcHBlZCA9IHZhckluZm8gJT4lIA0KZHBseXI6OmZpbHRlcihgVkFSIFNUQVJUIERBVEVgID49ICcyMDAwLTAxLTAxJyB8IA0KIWlzLm5hKGBWQVIgRU5EIERBVEVgKSB8IA0Kc3RyX2RldGVjdChGT1JNLCAiVFJGL1RSUnxUUlIvVFJGLUNBTENVTEFURUR8VFJSL1RSRnxUUkYiKSB8DQpzdHJfZGV0ZWN0KGBGT1JNIFNFQ1RJT05gLCAiUE9TVCBUUkFOU1BMQU5UIENMSU5JQ0FMIElORk9STUFUSU9OIikgfA0Kc3RyX2RldGVjdChERVNDUklQVElPTiwgIklERU5USUZJRVJ8IERBVEUiKSB8DQpgVkFSSUFCTEUgTkFNRWAgJWluJSB2YXJzVmVjIHwNCmBWQVJJQUJMRSBOQU1FYCAlaW4lIGFsbGVsZXMgfA0KYFZBUklBQkxFIE5BTUVgICVpbiUgYygiREVBVEhfQ0lSQ1VNX0RPTiIsIkRFQVRIX01FQ0hfRE9OIiwgIlRYX1lFQVIiKSApICU+JSANCnB1bGwoYFZBUklBQkxFIE5BTUVgKQ0KDQojIGluY2x1ZGUgdmFyaWFibGVzIGNvcnJlc3BvbmRpbmcgdG8gcHJpb3IgY2FyZGlhYyBzdXJnZXJ5IChub24tdHJhbnNwbGFudCkgb3IgbHVuZyBzdXJnZXJ5DQpWYXJzdG9CZURyb3BwZWQgPC0gc2V0ZGlmZihWYXJzdG9CZURyb3BwZWQsIGMoIlBSSU9SX0NBUkRfU1VSR19UQ1IiLCAiUFJJT1JfQ0FSRF9TVVJHX1RSUiIsICJQUklPUl9DQVJEX1NVUkdfVFlQRV9PU1RYVF9UQ1IiLCAiUFJJT1JfQ0FSRF9TVVJHX1RZUEVfT1NUWFRfVFJSIiwNCiJQUklPUl9DQVJEX1NVUkdfVFlQRV9UQ1IiLCAiUFJJT1JfQ0FSRF9TVVJHX1RZUEVfVFJSIiwgIlBSSU9SX0xVTkdfU1VSR19UWVBFX09TVFhUX1RSUiIpKQ0KDQoNCmRmICU8PiUgZHBseXI6OmZpbHRlcighaXMubmEoR1NUQVRVUykgJiAhaXMubmEoR1RJTUUpICkgJT4lICMgcmVtb3ZlIG9icyBtaXNzaW5nIGdyYWZ0IHN0YXR1cy90aW1lDQpzZWxlY3QoIWFsbF9vZihWYXJzdG9CZURyb3BwZWQpKSAlPiUgIyBleGNsdWRlIHZhcmlhYmxlcyB0byBiZSBkcm9wcGVkDQpkaXNjYXJkKH5zdW0oaXMubmEoLngpKS9sZW5ndGgoLngpKiAxMDAgPj0gOTApICU+JSAjIHJlbW92ZSB2YXJzIHdpdGggPj0gOTAgJSBtaXNzaW5nDQpkcm9wTnVtZXJpY01pc3NpbmcocGVyY2VudCA9IDMwKSAjIGRyb3AgbnVtZXJpYyBjb2x1bW5zIHcvID49IDMwJSBtaXNzaW5nIChzZWUgY3VzdG9tX2Z1bmN0aW9ucy5SKQ0KYGBgDQoNCiMjIENyZWF0aW5nIFZhcmlhYmxlcyBiYXNlZCBvbiB0aGUgTGl0ZXJhdHVyZSBhbmQgT3VyIEV4cGxvcmF0aW9uIG9mIHRoZSBEYXRhDQoNCkluIHRoZSBjb2RlIGNodW5rIGJlbG93LCB3ZSBjcmVhdGUgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXM6ICANCg0KLSBJU0NIVElNRSB3YXMgcmVjb2RlZCB0byBiZSBpbiBtaW51dGVzIHRvIG1ha2UgdGhlIGludGVycHJldGF0aW9uIGNvbnNpc3RlbnQgd2l0aCBAbWVkdmVkMjAxOGltcHJvdmluZyAgDQotIFBWUiwgd2hpY2ggY2FsY3VsYXRlcyB0aGUgcmVjaXBlbnQncyBwdWxtb25hcnkgdmFzY3VsYXIgcmVzaXN0YW5jZQ0KLSBBbiBFQ01PIGluZGV4IHZhcmlhYmxlIHdhcyBjcmVhdGVkIHRvIGluZGljYXRlIHdoZXRoZXIgdGhlIEVDTU8gbWFjaGluZSB3YXMgdXRpbGl6ZWQgYXQgZWl0aGVyIHJlZ2lzdHJhdGlvbiBvciBhdCB0cmFuc3BsYW50ICANCi0gQ0FSRF9TVVJHLCB3aGljaCBjYXB0dXJlcyB3aGV0aGVyIHRoZSByZWNpcGVudCBoYWQgcHJpb3IgY2FyZGlhYyBzdXJnZXJpZXMgKG5vbi10cmFuc3BsYW50KQ0KLSBCTUlfQ0hORywgd2hpY2ggY2FwdHVyZXMgdGhlIHBlcmNlbnQgY2hhbmdlIGluIHRoZSByZWNpcGllbnQncyBCTUkgZnJvbSB0cmFuc3BsYW50YXRpb24gdGltZSB0byByZWdpc3RyYXRpb24vbGlzdGluZyAgDQotIFdHVF9DSE5HLCB3aGljaCBjYXB0dXJlcyB0aGUgcGVyY2VudCBjaGFuZ2UgaW4gdGhlIHJlY2lwaWVudCdzIHdlaWdodCBmcm9tIHRyYW5zcGxhbnRhdGlvbiB0aW1lIHRvIHJlZ2lzdHJhdGlvbi9saXN0aW5nICANCi0gSEdUX0NITkcsIHdoaWNoIGNhcHR1cmVzIHRoZSBwZXJjZW50IGNoYW5nZSBpbiB0aGUgcmVjaXBpZW50J3MgaGVpZ2h0IGZyb20gdHJhbnNwbGFudGF0aW9uIHRpbWUgdG8gcmVnaXN0cmF0aW9uL2xpc3RpbmcgIA0KLSBBR0VfRElGRiwgd2hpY2ggY2FwdHVyZXMgdGhlIGFic29sdXRlIHZhbHVlIG9mIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHJlY2lwaWVudCdzIGFuZCBkZWNlYXNlZCBkb25vcidzIGFnZSAgDQotIEJNSV9ESUZGLCB3aGljaCBjYXB0dXJlcyB0aGUgYWJzb2x1dGUgdmFsdWUgb2YgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgcmVjaXBpZW50J3MgYW5kIGRlY2Vhc2VkIGRvbm9yJ3MgQk1JICANCi0gRVRIX01BVCwgd2hpY2ggY2FwdHVyZXMgd2hldGhlciB0aGUgcmVjaXBlbnQgYW5kIGRlY2Vhc2VkIGRvbm9yIHdlcmUgb2YgdGhlIHNhbWUgZXRoaW5pY2l0eSAgDQotIEdFTl9NQVQsIHdoaWNoIGNhcHR1cmVzIHdoZXRoZXIgdGhlIHJlY2lwZW50IGFuZCBkZWNlYXNlZCBkb25vciB3ZXJlIG9mIHRoZSBzYW1lIGdlbmRlciAgDQotIEFOQ0VGLCB3aGljaCBjYXB0dXJlcyB3aGV0aGVyIHRoZSBkZWNlYXNlZCBkb25vciB3YXMgcHJlc2NyaWJlZCBBbmNlZiB3aXRoaW4gMjQgaG91cnMgb3IgcHJvY3VyZW1lbnQgIA0KLSBET1BBTUlORSwgd2hpY2ggY2FwdHVyZXMgd2hldGhlciB0aGUgZGVjZWFzZWQgZG9ub3Igd2FzIHByZXNjcmliZWQgRG9wYW1pbmUgd2l0aGluIDI0IGhvdXJzIG9yIHByb2N1cmVtZW50ICANCi0gSEVQQVJJTiwgd2hpY2ggY2FwdHVyZXMgd2hldGhlciB0aGUgZGVjZWFzZWQgZG9ub3Igd2FzIHByZXNjcmliZWQgSGVyYXBpbiB3aXRoaW4gMjQgaG91cnMgb3IgcHJvY3VyZW1lbnQgDQotIFpPU1lOLCB3aGljaCBjYXB0dXJlcyB3aGV0aGVyIHRoZSBkZWNlYXNlZCBkb25vciB3YXMgcHJlc2NyaWJlZCBab3N5biB3aXRoaW4gMjQgaG91cnMgb3IgcHJvY3VyZW1lbnQNCi0gT1VUQ09NRSwgd2hpY2ggY2FwdHVyZXMgd2hldGhlciB0aGUgcmVjaXBpZW50IGhhcyBzdXJ2aXZlZCBhdCBvbmUteWVhciBwb3N0IHRyYW5zcGxhbnQgYmFzZWQgb24gY29tYmluaW5nIGluZm9ybWF0aW9uIGZyb20gR1RJTUUgYW5kIEdTVEFUVVMuIFRoZSBjZW5zb3JpbmcgcHJvY2VkdXJlIHVzZWQgaGVyZWluIGlzIGlkZW50aWNhbCB0byB0aGF0IGFwcGxpZWQgaW4gQGRhZzIwMTZwcm9iYWJpbGlzdGljOyBAZGFnMjAxN3ByZWRpY3RpbmcuICANCg0KDQpBZnRlciB0aGVzZSB2YXJpYWJsZXMgd2VyZSBjcmVhdGVkLCB3ZSBkcm9wcGVkIGJvdGggR1RJTUUgYW5kIEdTVEFUVVMgYXMgd2VsbCBhcyB0aGUgZm91ciB2YXJpYWJsZXMgY2FwdHVyaW5nIHRoZSBtZWRpY2F0aW9ucyBwcmVzY3JpYmVkL2FwcGxpZWQgb24gdGhlIGRlY2Vhc2VkIGRvbm9yIChpLmUuIFBUX09USDFfT1NUWFRfRE9OIC0tIFBUX09USDRfT1NUWFRfRE9OKS4gRnVydGhlbW9yZSwgd2UgY29udmVydGVkIGFsbCBpbmRpY2F0b3IgY29sdW1ucyBpbnRvIGNoYXJhY3RlcnMuDQoNCmBgYHtyIGNyZWF0ZVZhcnN9DQpkZiAlPD4lIG11dGF0ZShJU0NIVElNRSA9IElTQ0hUSU1FKjYwKSAlPiUNCm11dGF0ZShQVlIgPSAoSEVNT19QQV9NTl9UUlIgLSBIRU1PX1BDV19UUlIpKjc5LjcyL0hFTU9fQ09fVFJSKSAlPiUNCm11dGF0ZShFQ01PID0gaWZlbHNlKEVDTU9fVENSICsgRUNNT19UUlIgPT0gMCwgMCwgMSkpICU+JSANCm11dGF0ZShDQVJEX1NVUkcgPSBpZl9lbHNlKFBSSU9SX0NBUkRfU1VSR19UQ1I9PSJZInxQUklPUl9DQVJEX1NVUkdfVFJSPT0iWSIsIHRydWU9IlkiLCBmYWxzZT1pZmVsc2UoUFJJT1JfQ0FSRF9TVVJHX1RDUj09Ik4iJlBSSU9SX0NBUkRfU1VSR19UUlI9PSJOIiwgIk4iLCBOQSkpKSAlPiUNCm11dGF0ZShCTUlfQ0hORyA9IDEwMCooQk1JX0NBTEMtIElOSVRfQk1JX0NBTEMpL0lOSVRfQk1JX0NBTEMpICU+JSANCm11dGF0ZShXR1RfQ0hORyA9IDEwMCooV0dUX0tHX0NBTEMgLSBJTklUX1dHVF9LR19DQUxDKS9JTklUX1dHVF9LR19DQUxDKSAlPiUgDQptdXRhdGUoSEdUX0NITkcgPSAxMDAqKEhHVF9DTV9DQUxDIC0gSU5JVF9IR1RfQ01fQ0FMQykvSU5JVF9IR1RfQ01fQ0FMQykgJT4lIA0KbXV0YXRlKEFHRV9ESUZGID0gYWJzKEFHRSAtIEFHRV9ET04pKSAlPiUgDQptdXRhdGUoQk1JX0RJRkYgPSBhYnMoQk1JX0NBTEMgLSBCTUlfRE9OX0NBTEMpKSAlPiUgDQptdXRhdGUoRVRIX01BVCA9IGlmX2Vsc2UoRVRIQ0FUID09IEVUSENBVF9ET04sIHRydWUgPSAiWSIsIGZhbHNlID0gIk4iKSkgJT4lIA0KbXV0YXRlKEdFTl9NQVQgPSBpZl9lbHNlKEdFTkRFUiA9PSBHRU5ERVJfRE9OLCB0cnVlID0gIlkiLCBmYWxzZSA9ICJOIikpICU+JQ0KbXV0YXRlKEFOQ0VGID0gaWZfZWxzZShzdHJfZGV0ZWN0KFBUX09USDFfT1NUWFRfRE9OLCAnQU5DRUYnKSB8DQpzdHJfZGV0ZWN0KFBUX09USDJfT1NUWFRfRE9OLCAnQU5DRUYnKSB8DQpzdHJfZGV0ZWN0KFBUX09USDNfT1NUWFRfRE9OLCAnQU5DRUYnKSwNCnRydWUgPSAiWSIsIGZhbHNlID0gIk4iKSApICU+JSANCm11dGF0ZShET1BBTUlORSA9IGlmX2Vsc2Uoc3RyX2RldGVjdChQVF9PVEgxX09TVFhUX0RPTiwgJ0RPUEFNSU5FJykgfCANCnN0cl9kZXRlY3QoUFRfT1RIMl9PU1RYVF9ET04sICdET1BBTUlORScpIHwNCnN0cl9kZXRlY3QoUFRfT1RIM19PU1RYVF9ET04sICdET1BBTUlORScpLA0KdHJ1ZSA9ICJZIiwgZmFsc2UgPSAiTiIpICkgJT4lIA0KbXV0YXRlKEhFUEFSSU4gPSBpZl9lbHNlKHN0cl9kZXRlY3QoUFRfT1RIMV9PU1RYVF9ET04sICdIRVBBUklOJykgfCANCnN0cl9kZXRlY3QoUFRfT1RIMl9PU1RYVF9ET04sICdIRVBBUklOJykgfA0Kc3RyX2RldGVjdChQVF9PVEgzX09TVFhUX0RPTiwgJ0hFUEFSSU4nKSwNCnRydWUgPSAiWSIsIGZhbHNlID0gIk4iKSApICU+JSANCm11dGF0ZShaT1NZTiA9IGlmX2Vsc2Uoc3RyX2RldGVjdChQVF9PVEgxX09TVFhUX0RPTiwgJ1pPU1lOJykgfCANCnN0cl9kZXRlY3QoUFRfT1RIMl9PU1RYVF9ET04sICdaT1NZTicpIHwNCnN0cl9kZXRlY3QoUFRfT1RIM19PU1RYVF9ET04sICdaT1NZTicpLA0KdHJ1ZSA9ICJZIiwgZmFsc2UgPSAiTiIpICkgJT4lIA0Kcm93d2lzZSgpICU+JSANCm11dGF0ZShPVVRDT01FID0gcmVjb2RlR1NUQVRVUyhnU3RhdHVzID0gR1NUQVRVUywgZ1RpbWUgPSBHVElNRSwgdGFyZ2V0WWVhciA9IDEpKSAlPiUgIyBzZWUgY3VzdG9tX2Z1bmN0aW9ucy5SDQpzZWxlY3QoLWMoR1NUQVRVUywgR1RJTUUsIHN0YXJ0c193aXRoKCJQVF9PVEgiKSkpDQoNCmRmICU8PiUgdW5ncm91cCgpICU+JSANCm11dGF0ZV9hdChjKCdFQ01PJywgJ0NBUkRfU1VSRycsICdFVEhfTUFUJywgJ0dFTl9NQVQnLCAnQU5DRUYnLCAnRE9QQU1JTkUnLCAnSEVQQVJJTicsICdaT1NZTicsICdPVVRDT01FJyksIGFzLmNoYXJhY3RlcikNCg0KYGBgDQoNCg0KIyMgUmVkdWNpbmcgTnVtYmVyIG9mIExldmVscyBmb3IgQ2F0ZWdvcmljYWwgQ29sdW1ucw0KDQojIyMgTnVtYmVyIG9mIERpc3RpbmN0IFZhbHVlcyB3aXRoaW4gRWFjaCBDYXRlZ29yaWNhbCBWYXJpYWJsZQ0KRGVzcGl0ZSB0aGUgYXBwbGljYXRpb24gb2YgdGhlIGBuZWFyWmVyb1ZhcigpYCBmcm9tIHRoZSBbY2FyZXQgcGFja2FnZV0oaHR0cHM6Ly90b3BlcG8uZ2l0aHViLmlvL2NhcmV0L3ByZS1wcm9jZXNzaW5nLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0sIHNldmVyYWwgb2YgdGhlIHJlbWFpbmluZyBjaGFyYWN0ZXIvZmFjdG9yIHZhcmlhYmxlcyBzdGlsbCBleGhpYml0IGEgbGFyZ2UgbnVtYmVyIG9mIHVuaXF1ZSB2YWx1ZXMuIFRoZSBwdXJwb3NlIG9mIHRoZSBjb2RlIGNodW5rIGJlbG93IGlzIHRvIHF1YW50aWZ5IHRoZSB2YXJpYWJpbGl0eSBpbiB0aGUgcmVtYWluaW5nIGNoYXJhY3Rlci9mYWN0b3IgdmFyaWFibGVzLiANCg0KYGBge3IgZmFjdG9yTGV2ZWxzfQ0KZGYgJT4lIHNlbGVjdCgtSUQpICU+JSAgc3VtbWFyaXNlX2lmKGlzLmNoYXJhY3Rlciwgbl9kaXN0aW5jdCwgbmEucm0gPSBUUlVFKSAtPiBudW1MZXZlbHMNCmBgYA0KDQpJZiB3ZSB3ZXJlIHRvIGV4Y2x1ZGUgdGhlIElEIGNvbHVtbiwgdGhlIG51bWJlciBvZiBkaXN0aW5jdCB2YWx1ZXMgaW4gZWFjaCBjaGFyYWN0ZXIgdmFyaWFibGUgKGVxdWl2YWxsZW50bHkgdGhlIG51bWJlciBvZiBub24tTkEgbGV2ZWxzIGlmIHRoZXNlIHZhcmlhYmxlcyB3ZXJlIGVuY29kZWQgYXMgZmFjdG9ycykgaGFzIHRoZSBmb2xsb3dpbmcgc3VtbWFyeSBzdGF0aXN0aWNzOiAgDQoNCi0gYHIgc3VtbWFyeSh0KG51bUxldmVscykpWzFdYCAgDQotIGByIHN1bW1hcnkodChudW1MZXZlbHMpKVsyXWAgIA0KLSBgciBzdW1tYXJ5KHQobnVtTGV2ZWxzKSlbM11gICANCi0gYHIgc3VtbWFyeSh0KG51bUxldmVscykpWzRdYCAgDQotIGByIHN1bW1hcnkodChudW1MZXZlbHMpKVs1XWAgIA0KLSBgciBzdW1tYXJ5KHQobnVtTGV2ZWxzKSlbNl1gDQoNCkFjY29yZGluZ2x5LCB3ZSB3aWxsIGhhdmUgdG8gZXhhbWluZSBob3cgdG8gcmVkdWNlIHRoZSBudW1iZXIgb2YgbGV2ZWxzIHdpdGhpbiBlYWNoIHZhcmlhYmxlIHRvIGJlIGFibGUgdG8gdXNlIHRoZXNlIHZhcmlhYmxlcyBpbiB0aGUgbWFjaGluZSBsZWFybmluZyBzdGFnZTsgd2UgZG8gbm90IHdhbnQgdG8gZ2VuZXJhdGUgYSB2ZXJ5IGxhcmdlIG51bWJlciBvZiBkdW1teSB2YXJpYWJsZXMgYW5kIHdlIHdpbGwgZ3JvdXAgc29tZSBvZiB0aG9zZSBsZXZlbHMgYnkgY2FwaXRhbGl6aW5nIG9uIHRoZSBzaW1pbGFyaXRpZXMgaW4gaW50ZXJwcmV0dGluZyBzb21lIG9mIHRoZXNlIGxldmVscy4gDQoNCg0KIyMjIEdyb3VwaW5nIE9wZXJhdGlvbnMNCldoaWxlIFtyZWNpcGVzIHBhY2thZ2VdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yZWNpcGVzL3JlY2lwZXMucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IHByb3ZpZGVzIGFuIGV4Y2VsbGVudCBwaXBlbGluZSBmb3IgZGF0YSBwcmUtcHJvY2Vzc2luZywgd2UgaGF2ZSBlbGVjdGVkIHRvIG1hbnVhbGx5IGdvIHRocm91Z2ggdGhlIHByb2Nlc3Mgb2YgZ3JvdXBpbmcgdmFyaWFibGVzIHNpbmNlIHRoaXMgd291bGQgYWxsb3cgdXMgdG8gKGEpIHV0aWxpemUgdGhlIGluZm9ybWF0aW9uIGluIHRoZSBmYWN0b3IgbGV2ZWxzIHRvIGNvbWJpbmUgc2ltaWxhciBjYXRlZ29yaWVzICh3aGV0aGVyIGR1ZSB0byBsb2NhdGlvbiwgZWR1Y2F0aW9uIHN0YXR1cyBvciBtZWRpY2FsIGRpYWdub3NpcyBjb2Rlcyk7IGFuZCAoYikgdGhlIGludGVncmF0aW9uIG9mIHRoZSBbcmVjaXBlcyBwYWNrYWdlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcmVjaXBlcy9yZWNpcGVzLnBkZil7dGFyZ2V0PSJfYmxhbmsifSBpbnRvIHRoZSB0cmFpbmluZyBvZiBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyB3b3VsZCByZXF1aXJlIHRvIHRyYWluIHRoZSBtb2RlbHMgcmVwZWF0ZGx5IGZvciBlYWNoIHJlY2lwZSBwZXIgdGhlIGRvY3VtZW50YXRpb24gb2YgQGt1aG4yMDE5LWF4LiBJbiB0aGUgY29kZSBjaHVuayBiZWxvdywgd2UgcHJvdmlkZSB0aGUgZGV0YWlscyBmb3IgaG93IHdlIHJlZ3JvdXBlZCBzZXZlcmFsIHZhbHVlcy9sZXZlbHMgb2YgZWFjaCBjYXRlZ29yaWNhbC9jaGFyYWN0ZXIgdmVjdG9yIHdpdGggdGhlIGhvcGUgb2YgaW1wcm92aW5nIGJvdGggdGhlIGVmZmljaWVuY3kgYW5kIHByZWRpY3Rpb24gcGVyZm9ybWFuY2Ugb2YgdGhlIGRpZmZlcmVudCBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscy4gSW4gYWRkaXRpb24sIHdlIGhhdmUgYWxzbyB1c2VkIHRoZSBgcmVjb2RlKClgIHRvIHJlbmFtZSBzb21lIG9mIHRoZSBjYXRlZ29yaWVzIHNpbmNlICoqUioqIGNhbiBwcm9kdWNlIGVycm9ycyB3aGVuIG9uZS1ob3QgZW5jb2RpbmcgaXMgYXBwbGllZCBpZiB0aGUgbmFtZSBvZiB0aGUgY2F0ZWdvcnkgaXMgbnVtZXJpYzsgdGhlcmVmb3JlLCBzb21lIG9mIHRoZSBvcGVyYXRpb25zIHBlcmZvcm1lZCBiZWxvdyBtZXJlbHkgY2hhbmdlZCB0aGUgbmFtZSBvZiB0aGUgY2F0ZWdvcnkgdG8gYXZvaWQgZnV0dXJlIGVycm9ycyB3aGVuIHByb2Nlc3NpbmcgdGhlIGRhdGEuIEFmdGVyIHJlZ3JvdXBpbmcsIHdlIHBlcmZvcm1lZCB0d28gYWRkaXRpb25hbCBzdGVwcyBvZiB2YXJpYWJsZSBlbGltaW5hdGlvbjogKGEpIHJlbW92aW5nIHZhcmlhYmxlcyB0aGF0IGhhdmUgbW9yZSB0aGFuIDkwJSBvZiB0aGUgbm9uLW1pc3Npbmcgb2JzZXJ2YXRpb25zIGluIG9uZSBsZXZlbDsgKGIpIHVzaW5nIHRoZSBbY2FyZXRdKGh0dHBzOi8vdG9wZXBvLmdpdGh1Yi5pby9jYXJldC9wcmUtcHJvY2Vzc2luZy5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9IGBuZWFyWmVyb1ZhcigpYCBmdW5jdGlvbiB0byByZW1vdmUgdmFyaWFibGVzIHRoYXQgdGhlIHBlcmNlbnRhZ2Ugb2YgdW5pcXVlIHZhbHVlcyBpbiB0aGUgdmFyaWFibGUgaXMgbGVzcyB0aGFuIDAuMDElLiANCg0KYGBge3IgZ3JvdXBDYXRzfQ0KZGYkQUJPICU8PiUgcmVjb2RlKEEgPSAnQScsIEExID0gJ0EnLCBBMiA9ICdBJywgQiA9ICdCJywgTyA9ICdPJywgQUIgPSAnQUInLCBBMUIgPSAnQUInLCBBMkIgPSAnQUInLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykgDQpkZiRBQk9fRE9OICU8PiUgcmVjb2RlKEEgPSAnQScsIEExID0gJ0EnLCBBMiA9ICdBJywgQiA9ICdCJywgTyA9ICdPJywgQUIgPSAnQUInLCBBMUIgPSAnQUInLCBBMkIgPSAnQUInLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkQUJPX01BVCAlPD4lIHJlY29kZShgMWAgPSAiSURFTlRJQ0FMIiwgYDJgID0gIk5PVF9JREVOVElDQUwiLCBgM2AgPSAiTk9UX0lERU5USUNBTCIsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRBTUlTICU8PiUgcmVjb2RlKGAwYCA9ICJOT19NSVNNQVRDSCIsIGAxYCA9ICJPTkVfTUlTTUFUQ0hFRCIsIGAyYCA9ICJUV09fTUlTTUFUQ0hFRCIsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRCTUlTICU8PiUgcmVjb2RlKGAwYCA9ICJOT19NSVNNQVRDSCIsIGAxYCA9ICJPTkVfTUlTTUFUQ0hFRCIsIGAyYCA9ICJUV09fTUlTTUFUQ0hFRCIsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRCUk9OQ0hPX0xUX0RPTiAlPD4lIHJlY29kZShgMWAgPSAiTk8iLCBgMmAgPSAiTk9STUFMIiwgYDNgID0gIkFCTk9STUFMIiwgYDRgID0gIkFCTk9STUFMIiwgYDVgID0gIkFCTk9STUFMIixgNmAgPSAiQUJOT1JNQUwiLCBgN2AgPSJPVEhFUiIsIGA5OThgID0gIlVOS05PV04iLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkQlJPTkNIT19SVF9ET04gJTw+JSByZWNvZGUoYDFgID0gIk5PIiwgYDJgID0gIk5PUk1BTCIsIGAzYCA9ICJBQk5PUk1BTCIsIGA0YCA9ICJBQk5PUk1BTCIsIGA1YCA9ICJBQk5PUk1BTCIsYDZgID0gIkFCTk9STUFMIiwgYDdgID0iT1RIRVIiLCBgOTk4YCA9ICJVTktOT1dOIiwgLmRlZmF1bHQgPSAiT1RIRVIiLCAubWlzc2luZyA9IE5BX2NoYXJhY3Rlcl8pDQoNCmRmJENIRVNUX1hSQVlfRE9OICU8PiUgcmVjb2RlKGAwYCA9ICJVTktOT1dOIiwgYDFgID0gIk5PIiwgYDJgID0gIk5PUk1BTCIsIGAzYCA9ICJBQk5PUk1BTF9TSU5HTEUiLCBgNGAgPSAiQUJOT1JNQUxfU0lOR0xFIiwgYDVgID0gIkFCTk9STUFMX0JPVEgiLA0KYDk5OGAgPSAiVU5LTk9XTiIsIGA5OTlgID0gIlVOS05PV04iLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkQ09EX0NBRF9ET04gJTw+JSByZWNvZGUoYDFgID0gJ0FOT1hJQScsIGAyYCA9ICdDRVJFQlJPVkFTQ1VMQVJfU1RST0tFJywgYDNgID0gJ0hFQURfVFJBVU1BJyxgNGAgPSAnT1RIRVInLCBgOTk5YCA9ICdPVEhFUicsIFVua25vd24gPSAnVU5LTk9XTicsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRDT1JPTkFSWV9BTkdJTyAlPD4lIHJlY29kZShgMWAgPSAiTk8iLCBgMmAgPSAiWUVTX05PUk1BTCIsIGAzYCA9ICJZRVNfQUJOT1JNQUwiLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykgDQoNCmRmJENNVl9ET04gJTw+JSByZWNvZGUoQyA9ICJVTktOT1dOIiwgSSA9ICJVTktOT1dOIiwgTiA9ICJORUdBVElWRSIsIE5EID0gIlVOS05PV04iLCBQID0gIlBPU0lUSVZFIiwgUEQgPSAiVU5LTk9XTiIsIFUgPSAiVU5LTk9XTiIsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRESUFCICU8PiUgcmVjb2RlKGAxYCA9ICdOTycsIGAyYCA9ICdPTkUnLCBgM2AgPSAnVFdPJywgYDRgID0gJ09USEVSJywgYDVgID0gJ09USEVSJywgYDk5OGAgPSAnVU5LTk9XTicsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRESUFHICU8PiUgcmVjb2RlKGAxMDAwYCA9ICdESUxBVEVEX01ZT1BBVEhZX0lESScsIGAxMDAxYCA9ICdESUxBVEVEX01ZT1BBVEhZX09USCcsIGAxMDAyYCA9ICdESUxBVEVEX01ZT1BBVEhZX09USCcsIGAxMDAzYCA9ICdESUxBVEVEX01ZT1BBVEhZX09USCcsYDEwMDRgID0gJ0RJTEFURURfTVlPUEFUSFlfT1RIJywgYDEwMDVgID0gJ0RJTEFURURfTVlPUEFUSFlfT1RIJyxgMTAwNmAgPSAnRElMQVRFRF9NWU9QQVRIWV9PVEgnLCBgMTAwN2AgPSAnRElMQVRFRF9NWU9QQVRIWV9JU0MnLCBgMTA0OWAgPSAnRElMQVRFRF9NWU9QQVRIWV9PVEgnLA0KYDEyMDBgID0gJ0NPUk9OQVJZJywgYDk5OWAgPSAnT1RIRVInLCBgMTk5OWAgPSAnVU5LTk9XTicsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRUQ1JfREdOICU8PiUgcmVjb2RlKGAxMDAwYCA9ICdESUxBVEVEX01ZT1BBVEhZX0lESScsIGAxMDAxYCA9ICdESUxBVEVEX01ZT1BBVEhZX09USCcsIGAxMDAyYCA9ICdESUxBVEVEX01ZT1BBVEhZX09USCcsIGAxMDAzYCA9ICdESUxBVEVEX01ZT1BBVEhZX09USCcsYDEwMDRgID0gJ0RJTEFURURfTVlPUEFUSFlfT1RIJywgYDEwMDVgID0gJ0RJTEFURURfTVlPUEFUSFlfT1RIJyxgMTAwNmAgPSAnRElMQVRFRF9NWU9QQVRIWV9PVEgnLCBgMTAwN2AgPSAnRElMQVRFRF9NWU9QQVRIWV9JU0MnLCBgMTA0OWAgPSAnRElMQVRFRF9NWU9QQVRIWV9PVEgnLA0KYDEyMDBgID0gJ0NPUk9OQVJZJywgYDk5OWAgPSAnT1RIRVInLCBgMTk5OWAgPSAnVU5LTk9XTicsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRUSE9SQUNJQ19ER04gJTw+JSByZWNvZGUoYDEwMDBgID0gJ0RJTEFURURfTVlPUEFUSFlfSURJJywgYDEwMDFgID0gJ0RJTEFURURfTVlPUEFUSFlfT1RIJywgYDEwMDJgID0gJ0RJTEFURURfTVlPUEFUSFlfT1RIJywgYDEwMDNgID0gJ0RJTEFURURfTVlPUEFUSFlfT1RIJyxgMTAwNGAgPSAnRElMQVRFRF9NWU9QQVRIWV9PVEgnLCBgMTAwNWAgPSAnRElMQVRFRF9NWU9QQVRIWV9PVEgnLGAxMDA2YCA9ICdESUxBVEVEX01ZT1BBVEhZX09USCcsIGAxMDA3YCA9ICdESUxBVEVEX01ZT1BBVEhZX0lTQycsIGAxMDQ5YCA9ICdESUxBVEVEX01ZT1BBVEhZX09USCcsDQpgMTIwMGAgPSAnQ09ST05BUlknLCBgOTk5YCA9ICdPVEhFUicsIGAxOTk5YCA9ICdVTktOT1dOJywgLmRlZmF1bHQgPSAiT1RIRVIiLCAubWlzc2luZyA9IE5BX2NoYXJhY3Rlcl8pDQoNCg0KZGYkRFJNSVMgJTw+JSByZWNvZGUoYDBgID0gIk5PX01JU01BVENIIiwgYDFgID0gIk9ORV9NSVNNQVRDSEVEIiwgYDJgID0gIlRXT19NSVNNQVRDSEVEIiwgLmRlZmF1bHQgPSAiT1RIRVIiLCAubWlzc2luZyA9IE5BX2NoYXJhY3Rlcl8pDQoNCmRmJEVCVl9TRVJPU1RBVFVTICU8PiUgcmVjb2RlKEMgPSAiVU5LTk9XTiIsIEkgPSAiVU5LTk9XTiIsIE4gPSAiTkVHQVRJVkUiLCBORCA9ICJVTktOT1dOIiwgUCA9ICJQT1NJVElWRSIsIFBEID0gIlVOS05PV04iLCBVID0gIlVOS05PV04iLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkRURVQ0FUSU9OICU8PiUgcmVjb2RlKGAxYCA9ICdPVEhFUicsIGAyYCA9ICdHUkFERScsIGAzYCA9ICdISUdIJywgYDRgID0gJ0NPTExFR0UnLCBgNWAgPSAnVU5JVkVSU0lUWScsIGA2YCA9ICdVTklWRVJTSVRZJywgYDk5NmAgPSAnT1RIRVInLCBgOTk4YCA9ICdVTktOT1dOJywgLmRlZmF1bHQgPSAiT1RIRVIiLCAubWlzc2luZyA9IE5BX2NoYXJhY3Rlcl8pDQoNCmRmJEVUSENBVCAlPD4lIHJlY29kZShgMWAgPSAnV0hJVEUnLCBgMmAgPSAnQkxBQ0snLCBgNGAgPSAnSElTUEFOSUMnLCBgNWAgPSAnT1RIRVInLCBgNmAgPSAnT1RIRVInLGA3YCA9ICdPVEhFUicsIGA5YCA9ICdPVEhFUicsIGA5OThgID0gJ1VOS05PV04nLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkRVRIQ0FUX0RPTiAlPD4lIHJlY29kZShgMWAgPSAnV0hJVEUnLCBgMmAgPSAnQkxBQ0snLCBgNGAgPSAnSElTUEFOSUMnLCBgNWAgPSAnT1RIRVInLCBgNmAgPSAnT1RIRVInLGA3YCA9ICdPVEhFUicsIGA5YCA9ICdPVEhFUicsIGA5OThgID0gJ1VOS05PV04nLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkRlVOQ19TVEFUX1RDUiAlPD4lIHJlY29kZShgMWAgPSAiQUJMRSIsIGAyYCA9ICJBU1NJU1RFRCIsIGAzYCA9ICJESVNBQkxFIiwgYDk5NmAgPSAiT1RIRVIiLCBgOTk4YCA9ICJVTktOT1dOIiwgYDIwMTBgID0gIkRJU0FCTEUiLCBgMjAyMGAgPSAiRElTQUJMRSIsIGAyMDMwYCA9ICJESVNBQkxFIiwgYDIwNDBgID0gIkRJU0FCTEUiLCBgMjA1MGAgPSAiQVNTSVNURUQiLCBgMjA2MGAgPSAiQVNTSVNURUQiICwgYDIwNzBgID0gIkFTU0lTVEVEIiwgYDIwODBgID0gIkFCTEUiLCBgMjA5MGAgPSAiQUJMRSIsIGAyMTAwYCA9ICJBQkxFIiwgLmRlZmF1bHQgPSAiT1RIRVIiLCAubWlzc2luZyA9IE5BX2NoYXJhY3Rlcl8pDQoNCmRmJEZVTkNfU1RBVF9UUlIgJTw+JSByZWNvZGUoYDFgID0gIkFCTEUiLCBgMmAgPSAiQVNTSVNURUQiLCBgM2AgPSAiRElTQUJMRSIsIGA5OTZgID0gIk9USEVSIiwgYDk5OGAgPSAiVU5LTk9XTiIsIGAyMDEwYCA9ICJESVNBQkxFIiwgYDIwMjBgID0gIkRJU0FCTEUiLCBgMjAzMGAgPSAiRElTQUJMRSIsIGAyMDQwYCA9ICJESVNBQkxFIixgMjA1MGAgPSAiQVNTSVNURUQiLCBgMjA2MGAgPSAiQVNTSVNURUQiICwgYDIwNzBgID0gIkFTU0lTVEVEIiwgYDIwODBgID0gIkFCTEUiLCBgMjA5MGAgPSAiQUJMRSIsIGAyMTAwYCA9ICJBQkxFIiwgLmRlZmF1bHQgPSAiT1RIRVIiLCAubWlzc2luZyA9IE5BX2NoYXJhY3Rlcl8pDQoNCmRmJEhCVl9DT1JFICU8PiUgcmVjb2RlKEMgPSAiVU5LTk9XTiIsIEkgPSAiVU5LTk9XTiIsIE4gPSAiTkVHQVRJVkUiLCBORCA9ICJVTktOT1dOIiwgUCA9ICJQT1NJVElWRSIsIFBEID0gIlVOS05PV04iLCBVID0gIlVOS05PV04iLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkSEJWX0NPUkVfRE9OICU8PiUgcmVjb2RlKEMgPSAiVU5LTk9XTiIsIEkgPSAiVU5LTk9XTiIsIE4gPSAiTkVHQVRJVkUiLCBORCA9ICJVTktOT1dOIiwgUCA9ICJQT1NJVElWRSIsIFBEID0gIlVOS05PV04iLCBVID0gIlVOS05PV04iLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkSEJWX1NVUl9BTlRJR0VOICU8PiUgcmVjb2RlKEMgPSAiVU5LTk9XTiIsIEkgPSAiVU5LTk9XTiIsIE4gPSAiTkVHQVRJVkUiLCBORCA9ICJVTktOT1dOIiwgUCA9ICJQT1NJVElWRSIsIFBEID0gIlVOS05PV04iLCBVID0gIlVOS05PV04iLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkSENWX1NFUk9TVEFUVVMgJTw+JSByZWNvZGUoQyA9ICJVTktOT1dOIiwgSSA9ICJVTktOT1dOIiwgTiA9ICJORUdBVElWRSIsIE5EID0gIlVOS05PV04iLCBQID0gIlBPU0lUSVZFIiwgUEQgPSAiVU5LTk9XTiIsIFUgPSAiVU5LTk9XTiIsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRIRVBfQ19BTlRJX0RPTiAlPD4lIHJlY29kZShDID0gIlVOS05PV04iLCBJID0gIlVOS05PV04iLCBOID0gIk5FR0FUSVZFIiwgTkQgPSAiVU5LTk9XTiIsIFAgPSAiUE9TSVRJVkUiLCBQRCA9ICJVTktOT1dOIiwgVSA9ICJVTktOT1dOIiwgLmRlZmF1bHQgPSAiT1RIRVIiLCAubWlzc2luZyA9IE5BX2NoYXJhY3Rlcl8pDQoNCmRmJEhJU1RfRElBQkVURVNfRE9OICU8PiUgcmVjb2RlKGAxYCA9ICdOTycsIGAyYCA9ICdZRVMnLCBgM2AgPSAnWUVTJywgYDRgID0gJ1lFUycgLCBgNWAgPSAnWUVTJywgYDk5OGAgPSAnVU5LTk9XTicsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKSANCg0KZGYkSFlQRVJURU5TX0RVUl9ET04gJTw+JSByZWNvZGUoYDFgID0gJ05PJywgYDJgID0gJ1lFUycsIGAzYCA9ICdZRVMnLCBgNGAgPSAnWUVTJyAsIGA1YCA9ICdZRVMnLCBgOTk4YCA9ICdVTktOT1dOJywgLmRlZmF1bHQgPSAiT1RIRVIiLCAubWlzc2luZyA9IE5BX2NoYXJhY3Rlcl8pIA0KDQpkZiRISVZfU0VST1NUQVRVUyAlPD4lIHJlY29kZShDID0gIlVOS05PV04iLCBJID0gIlVOS05PV04iLCBOID0gIk5FR0FUSVZFIiwgTkQgPSAiVU5LTk9XTiIsIFAgPSAiUE9TSVRJVkUiLCBQRCA9ICJVTktOT1dOIiwgVSA9ICJVTktOT1dOIiwgLmRlZmF1bHQgPSAiT1RIRVIiLCAubWlzc2luZyA9IE5BX2NoYXJhY3Rlcl8pDQoNCmRmJEhMQU1JUyAlPD4lIHJlY29kZShgMGAgPSAnTE9XRVNUJywgYDFgID0gJ0xPV0VTVCcsIGAyYCA9ICdMT1dFU1QnLCBgM2AgPSAnTE9XJywgDQpgNGAgPSAnTUVESVVNJywgYDVgID0gJ0hJR0gnLCBgNmAgPSAnSElHSEVTVCcsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRIVExWMl9PTERfRE9OICU8PiUgcmVjb2RlKEMgPSAiVU5LTk9XTiIsIEkgPSAiVU5LTk9XTiIsIE4gPSAiTkVHQVRJVkUiLCBORCA9ICJVTktOT1dOIiwgUCA9ICJQT1NJVElWRSIsIFBEID0gIlVOS05PV04iLCBVID0gIlVOS05PV04iLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkSU5JVF9TVEFUICU8PiUgIHJlY29kZShgMTAxMGAgPSAnT05FJywgYDEwMjBgID0gJ09ORScsIGAxMDMwYCA9ICdUV08nLCBgMTA5MGAgPSdPTkUnLCBgMTk5OWAgPSAnVEVNUE9SQVJJTFlfSU5BQ1RJVkUnLCBgMjAxMGAgPSAnT05FJywgYDIwMjBgID0gJ09ORScsIGAyMDMwYCA9ICdUV08nLGAyMDkwYCA9ICdPTkUnLCBgMjk5OWAgPSAnVEVNUE9SQVJJTFlfSU5BQ1RJVkUnLCBgNzAxMGAgPSAnQUNUSVZFJywNCmA3OTk5YCA9ICdURU1QT1JBUklMWV9JTkFDVElWRScsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRFTkRfU1RBVCAlPD4lICByZWNvZGUoYDEwMTBgID0gJ09ORScsIGAxMDIwYCA9ICdPTkUnLCBgMTAzMGAgPSAnVFdPJywgYDEwOTBgID0nT05FJywgYDE5OTlgID0gJ1RFTVBPUkFSSUxZX0lOQUNUSVZFJywgYDIwMTBgID0gJ09ORScsIGAyMDIwYCA9ICdPTkUnLCBgMjAzMGAgPSAnVFdPJyxgMjA5MGAgPSAnT05FJywgYDI5OTlgID0gJ1RFTVBPUkFSSUxZX0lOQUNUSVZFJywgYDcwMTBgID0gJ0FDVElWRScsDQpgNzk5OWAgPSAnVEVNUE9SQVJJTFlfSU5BQ1RJVkUnLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkTEFTVF9JTkFDVF9SRUFTT04gJTw+JSByZWNvZGUoYDdgPSdIRUFMVEgnLCBgOWA9J0hFQUxUSCcsIGAxMWA9J0hFQUxUSCcsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkU1RFUk5PVE9NWV9UUlIgJTw+JSByZWNvZGUoYDFgPSJPTkUiLCBgMmA9Ik1PUkUiLCBgM2A9Ik1PUkUiLCBgOTk4YD0iVU5LTk9XTiIsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRJTk9UUk9QRVNfVENSICU8PiUgcmVjb2RlKGAwYCA9ICJOTyIsIGAxYCA9ICJZRVMiLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykgDQoNCmRmJElOT1RST1BFU19UUlIgJTw+JSByZWNvZGUoYDBgID0gIk5PIiwgYDFgID0gIllFUyIsICAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkTUVEX0NPTkRfVFJSICU8PiUgcmVjb2RlKGAxYCA9ICJJQ1VfSE9TUElUQUxJWkVEIiwgYDJgID0gIkhPU1BJVEFMSVpFRCIsIGAzYCA9ICJOT1RfSE9TUElUQUxJWkVEIiwgIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiROVU1fUFJFVl9UWCAlPD4lIHJlY29kZShgMGAgPSAnWkVSTycsIC5kZWZhdWx0ID0gJ05PTl9aRVJPJywgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRQUklfUEFZTUVOVF9UQ1IgJTw+JSByZWNvZGUoYDFgID0gIlBSSVZBVEUiLCBgMmAgPSJNRURJQ0FJRCIsIGAzYCA9ICJNRURJQ0FSRV9GUkVFIiwgYDRgID0gIlBVQkxJQ19PVEhFUiIsYDVgID0gIlBVQkxJQ19PVEhFUiIsIGA2YCA9ICJQVUJMSUNfT1RIRVIiLCBgN2AgPSAiUFVCTElDX09USEVSIiwgYDhgID0gIk9USEVSIixgOWAgPSAiT1RIRVIiLCBgMTBgID0gIk9USEVSIiwgYDExYCA9ICJPVEhFUiIsIGAxMmAgPSAiT1RIRVIiLCBgMTNgID0gIlBVQkxJQ19PVEhFUiIsYDE0YCA9ICJPVEhFUiIsIGAxNWAgPSAiVU5LTk9XTiIsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRQUklfUEFZTUVOVF9UUlIgJTw+JSByZWNvZGUoYDFgID0gIlBSSVZBVEUiLCBgMmAgPSJNRURJQ0FJRCIsIGAzYCA9ICJNRURJQ0FSRV9GUkVFIiwgYDRgID0gIlBVQkxJQ19PVEhFUiIsYDVgID0gIlBVQkxJQ19PVEhFUiIsIGA2YCA9ICJQVUJMSUNfT1RIRVIiLCBgN2AgPSAiUFVCTElDX09USEVSIiwgYDhgID0gIk9USEVSIixgOWAgPSAiT1RIRVIiLCBgMTBgID0gIk9USEVSIiwgYDExYCA9ICJPVEhFUiIsIGAxMmAgPSAiT1RIRVIiLCBgMTNgID0gIlBVQkxJQ19PVEhFUiIsIGAxNGAgPSAiT1RIRVIiLCBgMTVgID0gIlVOS05PV04iLCAuZGVmYXVsdCA9ICJPVEhFUiIsIC5taXNzaW5nID0gTkFfY2hhcmFjdGVyXykNCg0KZGYkUFJPQ19UWV9IUiAlPD4lIHJlY29kZShgMWAgPSAiQklDQVZBTCIsIGAyYCA9ICJUUkFESVRJT05BTCIsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKQ0KDQpkZiRQVUxNX0lORl9ET04gJTw+JSByZWNvZGUoYDBgID0gIk5PIiwgYDFgID0gIllFUyIsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKSANCg0KZGYkUkVHSU9OICU8PiUgcmVjb2RlKGAxYCA9ICJOT1JUSF9FQVNUIiwgYDJgID0gIk5PUlRIX0VBU1QiLCBgM2AgPSAiU09VVEhfRUFTVCIsIGA0YCA9ICJTT1VUSF9FQVNUIiwgYDVgID0gIldFU1QiLCBgNmAgPSAiV0VTVCIsIGA3YCA9ICJNSURXRVNUIiwgYDhgID0gIk1JRFdFU1QiLCBgOWAgPSAiTk9SVEhfRUFTVCIsIGAxMGAgPSAiTUlEV0VTVCIsYDExYCA9ICJTT1VUSF9FQVNUIiwgLmRlZmF1bHQgPSAiT1RIRVIiLCAubWlzc2luZyA9IE5BX2NoYXJhY3Rlcl8pDQoNCmRmJFNIQVJFX1RZICU8PiUgcmVjb2RlKGAzYCA9ICJMT0NBTCIsIGA0YCA9ICJSRUdJT05BTCIsIGA1YCA9ICJOQVRJT05BTCIsIC5kZWZhdWx0ID0gIk9USEVSIiwgLm1pc3NpbmcgPSBOQV9jaGFyYWN0ZXJfKSANCg0KZGYgPC0gZGYgJT4lIGRyb3BMb3dWYXIocGVyY2VudD05MCkgDQpuenZDb2xMb2MgPC0gbmVhclplcm9WYXIoZGYsIHVuaXF1ZUN1dCA9IDAuMDEpDQpkZiA8LSBkZlssLW56dkNvbExvY10NCg0KYGBgDQoNCk5vdywgd2UgaGF2ZSBgciBuY29sKGRmKWAgdmFyaWFibGVzIGFuZCBgciBucm93KGRmKWAgY2FzZXMgaW4gdGhlIGRhdGEuDQoNCiMjIERlYWxpbmcgd2l0aCBNaXNzaW5nIFZhbHVlcyAmIEVuY29kaW5nIFN0cmF0ZWd5DQoNCkluIHRoaXMgc2VjdGlvbiwgd2UgcHJvdmlkZSBzb21lIG9wdGlvbnMgb2YgZGVhbGluZyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluIGVpdGhlciBjYXRlZ29yaWNhbCBvciBudW1lcmljIHZhcmlhYmxlcyBhbmQgdHdvIGFwcHJvYWNoZXMgb2YgZW5jb2RpbmcgY2F0ZWdvcmljYWwgdmFyaWFibGVzLiAgDQoNCioqTnVtZXJpY2FsIEltcHV0YXRpb24qKjogTm8gaW1wdXRhdGlvbiAoRHJvcCksIE1pc3NpbmcgdmFsdWVzIGFyZSByZXBsYWNlZCB3aXRoIHRoZSBtZWRpYW4NCg0KKipDYXRlZ29yaWNhbCBJbXB1dGF0aW9uKio6IE5vIGltcHV0YXRpb24gKERyb3ApLCBNaXNzaW5nIHZhbHVlcyBhcmUgcmVwbGFjZWQgd2l0aCBtb2RlIGNhdGVnb3J5LCBNaXNzaW5nIHZhbHVlcyBhcmUgbGFiZWxlZCBhcyAibWlzc2luZyIsIE1pc3NpbmcgdmFsdWVzIGFyZSBsYWJlbGVkIHdpdGggdGhlIGV4aXN0aW5nICJ1bmtub3duIiBjYXRlZ29yeS4gDQoNCldlIHN1Z2dlc3QgdHdvIGFwcHJvYWNoZXMgb2YgZW5jb2RpbmcgY2F0ZWdvcmljYWwgdmFyaWFibGVzOiBMYWJlbCBFbmNvZGluZyB3aGVyZSBlYWNoIGxhYmVsIGlzIG1hcHBlZCB0byBhbiBpbnRlZ2VyLCBhbmQgT25lLUhvdCBFbmNvZGluZyB3aGVyZSBlYWNoIGxhYmVsIGlzIG1hcHBlZCB0byBhIGJpbmFyeSB2YXJpYWJsZS4NCg0KSW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLCB3ZSBjcmVhdGUgYSBsaXN0ICgqKmRmX2FsbCoqKSBvZiAxNiBzdWJsaXN0cyBjb3JyZXNwb25kaW5nIHRvIHRoZSBpbXB1dGF0aW9uIHN0cmF0ZWdpZXMgYW5kIGVuY29kaW5nIHN0cmF0ZWdpZXMgbWVudGlvbmVkIGFib3ZlLiBGb3IgZXhhbXBsZSwgZGZfYWxsW1siTWVkaWFuX0Ryb3BfTGFiZWwiXV0gc3RvcmVzIHRoZSBkYXRhIGFmdGVyIGltcHV0aW5nIG1lZGlhbiBmb3IgbWlzc2luZyB2YWx1ZXMgaW4gdGhlIG51bWVyaWNhbCB2YXJpYWJsZXMsIGRyb3BwaW5nIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsIGFuZCBtYXBwaW5nIGVhY2ggbGV2ZWwgaW4gZWFjaCBjYXRlZ29yaWNhbCB2YXJpYWJsZSB0byBhbiBpbnRlZ2VyLiANCg0KYGBge3IgSW1wdXRhdGlvbmFuZEVuY29kaW5nfQ0KIyBmaXJzdCwgd2UgbmVlZCB0byByZW1vdmUgY2FzZXMgd2l0aCBtaXNzaW5nIHZhbHVlcyBpbiBPVVRDT01FIGFzIGl0IGlzIHRoZSByZXNwb25zZSB2YXJpYWJsZSBpbiBvdXIgc3R1ZHkuDQpkZiA8LSBkZlshaXMubmEoZGYkT1VUQ09NRSksXQ0KDQojIGZpbmQgdGhlIGluZGljZXMgZm9yIGNhdGVnb3JpY2FsIGFuZCBudW1lcmljYWwgdmFyaWFibGVzIGluIHRoZSBkYXRhDQppbmRleF9jaGFyIDwtIHNldGRpZmYod2hpY2godW5saXN0KGxhcHBseShkZiwgY2xhc3MpKT09ImNoYXJhY3RlciIpLCB3aGljaChjb2xuYW1lcyhkZik9PSJJRCJ8Y29sbmFtZXMoZGYpPT0iT1VUQ09NRSIpKQ0KDQppbmRleF9udW0gPC0gd2hpY2godW5saXN0KGxhcHBseShkZixjbGFzcykpPT0ibnVtZXJpYyIpDQoNCg0KIyBkZWZpbmUgdGhyZWUgZmFjdG9ycw0KaW1wdXRhdGlvbl9udW0gPC0gYygiRHJvcCIsICJNZWRpYW4iKQ0KaW1wdXRhdGlvbl9jYXQgPC0gYygiRHJvcCIsICJNaXNzaW5nIiwgIk1vZGUiLCAiVW5rbm93biIpDQplbmNvZGluZyA8LSBjKCJMYWJlbCIsICJPbmVIb3QiKQ0KDQojIENyZWF0ZSBhIGxpc3Qgb2YgMTYgZWxlbWVudHMgdGhhdCB3aWxsIHN0b3JlIHRoZSAxNiBkYXRhIHNldHMgZm9yIGFsbCBzY2VuYXJpb3MNCmRmX2FsbCA8LSByZXAobGlzdChOQSksIDE2KQ0KbmFtZXMoZGZfYWxsKSA8LSBhcHBseShhcy5kYXRhLmZyYW1lKGNyb3NzaW5nKGltcHV0YXRpb25fbnVtLCBpbXB1dGF0aW9uX2NhdCwgZW5jb2RpbmcpKSwxLGZ1bmN0aW9uKHgpIHBhc3RlKHgsIGNvbGxhcHNlPSJfIikpDQoNCiMgcHJlcGFyZSBmb3IgaW1wdXRhdGlvbiBjYXNlcw0KY2hhci5pbXB1dGVfbW9kZSA8LSBkZlssaW5kZXhfY2hhcl0gJT4lIG11dGF0ZV9hbGwofmlmZWxzZShpcy5uYSgueCksIG5hbWVzKHdoaWNoLm1heCh0YWJsZSgueCkpKSwgLngpKSANCmNoYXIuaW1wdXRlX21pc3NpbmcgPC0gZGZbLGluZGV4X2NoYXJdICU+JSBtdXRhdGVfYWxsKH5pZmVsc2UoaXMubmEoLngpLCAiTWlzc2luZyIsIC54KSkgDQpjaGFyLmltcHV0ZV91bmtub3duIDwtIGRmWyxpbmRleF9jaGFyXSAlPiUgbXV0YXRlX2FsbCh+aWZlbHNlKGlzLm5hKC54KSwgIlVOS05PV04iLCAueCkpIA0KbnVtLmltcHV0ZV9tZWRpYW4gPC0gZGZbLGluZGV4X251bV0gJT4lIG11dGF0ZV9hbGwofmlmZWxzZShpcy5uYSgueCksIG1lZGlhbigueCwgbmEucm0gPSBUUlVFKSwgLngpKSANCg0KIyBwcmVwYXJlIGZvciBlbmNvZGluZyB3aXRoIGNhdGVnb3JpY2FsIGltcHV0YXRpb25zDQptb2RlX0xhYmVsIDwtIGVuY29kZV9jYXRlZ29yeShjaGFyLmltcHV0ZV9tb2RlLCAiTGFiZWwiKSANCm1vZGVfT25laG90IDwtIGVuY29kZV9jYXRlZ29yeShjaGFyLmltcHV0ZV9tb2RlLCAiT25laG90IikgDQptaXNzaW5nX0xhYmVsIDwtIGVuY29kZV9jYXRlZ29yeShjaGFyLmltcHV0ZV9taXNzaW5nLCAiTGFiZWwiKSANCm1pc3NpbmdfT25laG90IDwtIGVuY29kZV9jYXRlZ29yeShjaGFyLmltcHV0ZV9taXNzaW5nLCAiT25laG90IikgDQp1bmtub3duX0xhYmVsIDwtIGVuY29kZV9jYXRlZ29yeShjaGFyLmltcHV0ZV91bmtub3duLCAiTGFiZWwiKSANCnVua25vd25fT25laG90IDwtIGVuY29kZV9jYXRlZ29yeShjaGFyLmltcHV0ZV91bmtub3duLCAiT25laG90IikNCg0KIyBwcmVwYXJlIGZvciBubyBpbXB1dGF0aW9uIGNhc2UNCmRyb3BfZHJvcCA8LSBzZWFyY2hfY29tcGxldGUoZGYpICANCmluZGV4X051bV9ERCA8LSB3aGljaCh1bmxpc3QobGFwcGx5KGRyb3BfZHJvcCwgY2xhc3MpKT09Im51bWVyaWMiKQ0KaW5kZXhfQ2hhcl9ERCA8LSBzZXRkaWZmKHdoaWNoKHVubGlzdChsYXBwbHkoZHJvcF9kcm9wLCBjbGFzcykpPT0iY2hhcmFjdGVyIiksIHdoaWNoKGNvbG5hbWVzKGRyb3BfZHJvcCk9PSJJRCJ8Y29sbmFtZXMoZHJvcF9kcm9wKT09Ik9VVENPTUUiKSkNCmRyb3BfZHJvcF9MYWJlbCA8LSBlbmNvZGVfY2F0ZWdvcnkoZHJvcF9kcm9wWyxpbmRleF9DaGFyX0REXSwgIkxhYmVsIikgDQpkcm9wX2Ryb3BfT25laG90IDwtIGVuY29kZV9jYXRlZ29yeShkcm9wX2Ryb3BbLGluZGV4X0NoYXJfRERdLCAiT25laG90IikgDQoNCiMgcHJlcGFyZSBmb3IgaW1wdXRpbmcgbWVkaWFuIGZvciBudW1lcmljYWwgdmFyaWFibGVzIGJ1dCBkcm9wcGluZyBjYXRlZ29yaWNhbCBtaXNzaW5nIHZhbHVlcw0KbWVkaWFuX25vdGRyb3AgPC0gYmluZF9jb2xzKGRmWyxjKCJJRCIsICJPVVRDT01FIildLCBudW0uaW1wdXRlX21lZGlhbiwgZGZbLGluZGV4X2NoYXJdKQ0KbWVkaWFuX2Ryb3AgPC0gc2VhcmNoX2NvbXBsZXRlKG1lZGlhbl9ub3Rkcm9wKQ0KaW5kZXhfTnVtX01EIDwtIHdoaWNoKHVubGlzdChsYXBwbHkobWVkaWFuX2Ryb3AsIGNsYXNzKSk9PSJudW1lcmljIikNCmluZGV4X0NoYXJfTUQgPC0gc2V0ZGlmZih3aGljaCh1bmxpc3QobGFwcGx5KG1lZGlhbl9kcm9wLCBjbGFzcykpPT0iY2hhcmFjdGVyIiksIHdoaWNoKGNvbG5hbWVzKG1lZGlhbl9kcm9wKT09IklEInxjb2xuYW1lcyhtZWRpYW5fZHJvcCk9PSJPVVRDT01FIikpDQptZWRpYW5fZHJvcF9MYWJlbCA8LSBlbmNvZGVfY2F0ZWdvcnkobWVkaWFuX2Ryb3BbLGluZGV4X0NoYXJfTURdLCAiTGFiZWwiKSANCm1lZGlhbl9kcm9wX09uZWhvdCA8LSBlbmNvZGVfY2F0ZWdvcnkobWVkaWFuX2Ryb3BbLGluZGV4X0NoYXJfTURdLCAiT25laG90IikgDQoNCiMgb2J0YWluIGRhdGEgZm9yIDE2IHNjZW5hcmlvcw0KZGZfYWxsJERyb3BfRHJvcF9MYWJlbCA8LSBiaW5kX2NvbHMoZHJvcF9kcm9wWyxjKCJJRCIsICJPVVRDT01FIildLCBkcm9wX2Ryb3BbLGluZGV4X051bV9ERF0sIGRyb3BfZHJvcF9MYWJlbCkNCmRmX2FsbCREcm9wX0Ryb3BfT25lSG90IDwtIGJpbmRfY29scyhkcm9wX2Ryb3BbLGMoIklEIiwgIk9VVENPTUUiKV0sIGRyb3BfZHJvcFssaW5kZXhfTnVtX0REXSwgZHJvcF9kcm9wX09uZWhvdCkNCmRmX2FsbCREcm9wX01pc3NpbmdfTGFiZWwgPC0gYmluZF9jb2xzKGRmWyxjKCJJRCIsICJPVVRDT01FIildLCBkZlssaW5kZXhfbnVtXSwgbWlzc2luZ19MYWJlbCkgJT4lIGRyb3BfbmEoKQ0KZGZfYWxsJERyb3BfTWlzc2luZ19PbmVIb3QgPC0gYmluZF9jb2xzKGRmWyxjKCJJRCIsICJPVVRDT01FIildLCBkZlssaW5kZXhfbnVtXSwgbWlzc2luZ19PbmVob3QpICU+JSBkcm9wX25hKCkNCmRmX2FsbCREcm9wX01vZGVfTGFiZWwgPC0gYmluZF9jb2xzKGRmWyxjKCJJRCIsICJPVVRDT01FIildLCBkZlssaW5kZXhfbnVtXSwgbW9kZV9MYWJlbCkgJT4lIGRyb3BfbmEoKQ0KZGZfYWxsJERyb3BfTW9kZV9PbmVIb3QgPC0gYmluZF9jb2xzKGRmWyxjKCJJRCIsICJPVVRDT01FIildLCBkZlssaW5kZXhfbnVtXSwgbW9kZV9PbmVob3QpICU+JSBkcm9wX25hKCkNCmRmX2FsbCREcm9wX1Vua25vd25fTGFiZWwgPC0gYmluZF9jb2xzKGRmWyxjKCJJRCIsICJPVVRDT01FIildLCBkZlssaW5kZXhfbnVtXSwgdW5rbm93bl9MYWJlbCkgJT4lIGRyb3BfbmEoKQ0KZGZfYWxsJERyb3BfVW5rbm93bl9PbmVIb3QgPC0gYmluZF9jb2xzKGRmWyxjKCJJRCIsICJPVVRDT01FIildLCBkZlssaW5kZXhfbnVtXSwgdW5rbm93bl9PbmVob3QpICU+JSBkcm9wX25hKCkNCmRmX2FsbCRNZWRpYW5fRHJvcF9MYWJlbCA8LSBiaW5kX2NvbHMobWVkaWFuX2Ryb3BbLGMoIklEIiwgIk9VVENPTUUiKV0sIG1lZGlhbl9kcm9wWyxpbmRleF9OdW1fTURdLCBtZWRpYW5fZHJvcF9MYWJlbCkNCmRmX2FsbCRNZWRpYW5fRHJvcF9PbmVIb3QgPC0gYmluZF9jb2xzKG1lZGlhbl9kcm9wWyxjKCJJRCIsICJPVVRDT01FIildLCBtZWRpYW5fZHJvcFssaW5kZXhfTnVtX01EXSwgbWVkaWFuX2Ryb3BfT25laG90KQ0KZGZfYWxsJE1lZGlhbl9NaXNzaW5nX0xhYmVsIDwtIGJpbmRfY29scyhkZlssYygiSUQiLCAiT1VUQ09NRSIpXSwgbnVtLmltcHV0ZV9tZWRpYW4sIG1pc3NpbmdfTGFiZWwpDQpkZl9hbGwkTWVkaWFuX01pc3NpbmdfT25lSG90IDwtIGJpbmRfY29scyhkZlssYygiSUQiLCAiT1VUQ09NRSIpXSwgbnVtLmltcHV0ZV9tZWRpYW4sIG1pc3NpbmdfT25laG90KQ0KZGZfYWxsJE1lZGlhbl9Nb2RlX0xhYmVsIDwtIGJpbmRfY29scyhkZlssYygiSUQiLCAiT1VUQ09NRSIpXSwgbnVtLmltcHV0ZV9tZWRpYW4sIG1vZGVfTGFiZWwpDQpkZl9hbGwkTWVkaWFuX01vZGVfT25lSG90IDwtIGJpbmRfY29scyhkZlssYygiSUQiLCAiT1VUQ09NRSIpXSwgbnVtLmltcHV0ZV9tZWRpYW4sIG1vZGVfT25laG90KQ0KZGZfYWxsJE1lZGlhbl9Vbmtub3duX0xhYmVsIDwtIGJpbmRfY29scyhkZlssYygiSUQiLCAiT1VUQ09NRSIpXSwgbnVtLmltcHV0ZV9tZWRpYW4sIHVua25vd25fTGFiZWwpDQpkZl9hbGwkTWVkaWFuX1Vua25vd25fT25lSG90IDwtIGJpbmRfY29scyhkZlssYygiSUQiLCAiT1VUQ09NRSIpXSwgbnVtLmltcHV0ZV9tZWRpYW4sIHVua25vd25fT25laG90KQ0KDQpzYXZlUkRTKGRmX2FsbCwgIi4uL1Jlc3VsdHMvYWxsX2RhdGEuUkRTIikNCmBgYA0KDQpXZSBmb3VuZCB0aGF0IGlmIHdlIHNpbXBseSBkcm9wcGVkIGFsbCBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzLCB0aGVyZSB3ZXJlIG5vIG9ic2VydmF0aW9ucyBpbiB0aGUgZGF0YS4gVGh1cywgd2UgdHJ5IHRvIG1heGltaXplIHJlbWFpbmluZyBjZWxscyBpbiB0aGUgZGF0YSB0aHJvdWdoIGEgaGV1cmlzdGljIGFsZ29yaXRobTogc2VhcmNoX2NvbXBsZXRlKCksIHdoaWNoIGRyb3BzIHJvd3MgYW5kIGNvbHVtbnMgYmFzZWQgb24gdGhlIHBlcmNlbnRhZ2Ugb2YgbWlzc2luZyB2YWx1ZXMgdW50aWwganVzdCBub24tZW1wdHkgY2VsbHMgYXJlIHJlbWFpbmVkLiBUaGlzIGZ1bmN0aW9uIGNhbiBiZSBmb3VuZCBpbiAgW2N1c3RvbV9mdW5jdGlvbnMuUl0oDQpodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vWWluZy1KdS9FeHBsYWluaW5nLVByZWRpY3RpdmUtTW9kZWwtUGVyZm9ybWFuY2UuZ2l0aHViLmlvL21hc3Rlci9jdXN0b21fZnVuY3Rpb25zLlIpe3RhcmdldD0iX2JsYW5rIn0uIA0KDQpUaGUgZm9sbG93aW5nIHRhYmxlIHNob3dzIHRoZSBkaW1lbnNpb25zIGZvciB0aGUgZGF0YSAoaW5jbHVkaW5nIElEKSB3ZSBjcmVhdGVkIGZvciBhbGwgc2NlbmFyaW9zLg0KDQpgYGB7ciBkYXRhX2RpbWVuc2lvbn0NCkQgPC0gbGFwcGx5KGRmX2FsbCwgZGltKQ0KRCA8LSBkby5jYWxsKHJiaW5kLmRhdGEuZnJhbWUsIEQpDQpjb2xuYW1lcyhEKSA8LSBjKCJOdW1iZXJfb2ZfT2JzZXJ2YXRpb25zIiwgIk51bWJlcl9vZl9WYXJpYWJsZXMiKQ0KRCA8LSBkYXRhLmZyYW1lKFNjZW5hcmlvPW5hbWVzKGRmX2FsbCksIEQpDQpkYXRhdGFibGUoRCkNCmBgYA0KDQoNCi0tLQ0KDQojIEZ1bGwgRmFjdG9yaWFsIERlc2lnbg0KDQpTaW5jZSB0aGUgb3ZlcmFyY2hpbmcgcXVlc3Rpb24gZXhhbWluZWQgaXMgImNhbiB3ZSBxdWFudGlmeSB0aGUgY29tYmluZWQgZWZmZWN0IG9mIGRlY2lzaW9ucyBtYWRlIHVzaW5nIHRoZSBLRERNIHByb2Nlc3Mgb24gdGhlIHByZWRpY3RpdmUgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsPyIsIHdlIHdpbGwgZXhwbG9yZSB0aGUgc2l4IGZhY3RvcnMgc3VtbWFyaXplZCBpbiB0aGUgZm9sbG93aW5nLiANCg0KKEEpIENhdGVnb3JpY2FsIEltcHV0YXRpb246IERyb3AsIE1vZGUsIE1pc3NpbmcsIFVua25vd24NCg0KKEIpIE51bWVyaWNhbCBJbXB1dGF0aW9uOiBEcm9wLCBNZWRpYW4NCg0KKEMpIEVuY29kaW5nOiBMYWJlbCwgT25lLUhvdA0KDQooRCkgU3Vic2FtcGxpbmc6IE5vbmUsIERvd24sIFVwLCBTTU9URSwgUk9TRQ0KDQooRSkgRmVhdHVyZSBTZWxlY3Rpb246IEZGUywgTEFTU08sIFJGDQoNCihGKSBBbGdvcml0aG06IEFOTiwgRFQsIEVsYXN0aWNOZXQsIEtQTFNSLCBMREEsIExSLCBOQiwgUkYNCg0KKipSZWFkZXJzIGFyZSBlbmNvdXJhZ2VkIHRvIHJlZmVyIG91ciBwYXBlciBmb3IgdGhlIGRlc2NyaXB0aW9ucyBvZiBlYWNoIGZhY3Rvci4qKg0KDQojIyBGZWF0dXJlIFNlbGVjdGlvbg0KDQpJbiB0aGUgZm9sbG93aW5nIGNvZGUgY2h1bmssIHdlIHNob3cgaG93IHRoZSBmZWF0dXJlIHNlbGVjdGlvbiBhcHByb2FjaGVzOiBGYXN0IEZlYXR1cmUgU2VsZWN0aW9uLCBMQVNTTywgYW5kIFJhbmRvbSBGb3Jlc3RzIGFyZSB1dGlsaXplZC4gSWYgdGhlcmUgaXMgYW4gZXJyb3Igb2NjdXJzIHJlbGF0ZWQgdG8gIkpBVkFfSE9NRSBjYW5ub3QgYmUgZGV0ZXJtaW5lZCBmcm9tIHRoZSByZWdpc3RyeSIsIHRoZSBlcnJvciBpcyBvZnRlbiByZXNvbHZlZCBieSBpbnN0YWxsaW5nIGEgSmF2YSB2ZXJzaW9uIChpLmUuIDY0LWJpdCBKYXZhIG9yIDMyLWJpdCBKYXZhKSB0aGF0IGZpdHMgdG8gdGhlIHR5cGUgb2YgUiB2ZXJzaW9uIHRoYXQgeW91IGFyZSB1c2luZyAoaS5lLiA2NC1iaXQgUiBvciAzMi1iaXQgUikuIEFuZCBzZXQgdGhlIEpBVkFfSE9NRSBwYXRoIHVzaW5nIG9uZSBvZiB0aGUgY29tbWVuZHMgYmVsb3cuICJqcmUxLjguMF8yNTEiIG5lZWRzIHRvIGJlIGFkanVzdGVkIGRlcGVuZHMgb24gdGhlIGZvbGRlcidzIG5hbWUuIA0KDQoqKlN5cy5zZXRlbnYoSkFWQV9IT01FPSdDOi9Qcm9ncmFtIEZpbGVzL0phdmEvanJlMS44LjBfMjUxJykqKiAgZm9yIDY0LWJpdCB2ZXJzaW9uDQoNCioqU3lzLnNldGVudihKQVZBX0hPTUU9J0M6L1Byb2dyYW0gRmlsZXMgKHg4NikvSmF2YS9qcmUxLjguMF8yNTEnKSoqICBmb3IgMzItYml0IHZlcnNpb24NCg0KDQpXZSBuZWVkIHRvIGNyZWF0ZSB0cmFpbmluZyBhbmQgaG9sZG91dCBkYXRhIHNpbmNlIHRoZSBmZWF0dXJlIHNlbGVjdGlvbiBwcm9jZWR1cmUgc2hvdWxkIGJlIGFwcGxpZWQgdG8gdGhlIHRyYWluaW5nIGRhdGEgb25seS4gV2Ugd2lsbCB1c2UgSURzIHRvIGRpc3Rpbmd1aXNoIHRoZSB0cmFpbmluZyBhbmQgaG9sZG91dCBkYXRhLiBTaW5jZSB3ZSB3b3VsZCBsaWtlIHRvIGhhdmUgNSBkaXNqb2ludCBob2xkb3V0IGRhdGFzZXRzIGZvciB0aGUgY3Jvc3MgdmFsaWRhdGlvbiAoY29ycmVzcG9uZGluZyB0byA1IHRyYWluaW5nIGRhdGFzZXRzKSwgd2Ugc2FtcGxlIGNhc2VzIGJ5IHVzaW5nIElEIHdpdGhvdXQgcmVwbGFjZW1lbnQgYW5kIG9idGFpbiBlYWNoIDIwJSBvZiB0aGUgc2FtcGxlIGNhc2VzIGZvciB0aGUgaG9sZG91dCBkYXRhLg0KDQpgYGB7ciBjcmVhdGVkYXRhLCBldmFsPUZBTFNFfQ0Kc2V0LnNlZWQgPC0gMTIzNA0KYWxsX2RhdGFfSUQgPC0gbGFwcGx5KGRmX2FsbCwgZnVuY3Rpb24oeCkgc2FtcGxlKHgkSUQsIG5yb3coeCkpKQ0KaG9sZG91dF9pbmRleCA8LSBHZXRfaG9sZG91dF9pbmRleChhbGxfZGF0YV9JRCkNCmBgYA0KDQpTaW5jZSB0aGVyZSB3aWxsIGJlICQ1XHRpbWVzIDE2PTgwJCB0cmFpbmluZyBkYXRhc2V0cywgd2Ugd2lsbCB1c2UgYSBwYXJhbGxlbCBhbGdvcml0aG0gdG8gY29uZHVjdCB0aGUgZmVhdHVyZSBzZWxlY3Rpb24gcHJvY2VkdXJlIGZvciB0aGVzZSBkYXRhc2V0cyBzaW11bHRhbmVvdXNseS4gV2UgYXBwbHkgZWFjaCBmZWF0dXJlIHNlbGVjdGlvbiBtZXRob2QgdG8gZWFjaCBkYXRhc2V0IGluZGl2aWR1YWxseSBhbmQgdGhlIHJlc3VsdCBpcyBzdG9yZWQgaW4gYSBsaXN0IHdpdGggdGhlIGZvbGxvd2luZyBzdHJ1Y3R1cmUuIFRoZSBsaXN0IGNvbnRhaW5zIDUgc3ViLWxpc3RzIHdoaWNoIGNvbnRhaW4gdGhlIHJlc3VsdHMgY29ycmVzcG9uZGluZyB0byB0aGUgZml2ZSBmb2xkIGNyb3NzIHZhbGlkYXRpb24gdHJhaW5pbmcgc2V0cy4gQWRkaXRpb25hbGx5LCBlYWNoIG9mIHRoZXNlIDUgc3ViLWxpc3RzIGNvbnRhaW5zIDE2IGVsZW1lbnRzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIDE2IGRhdGEgc2NlbmFyaW9zLiBUaGVuIHdlIHN0b3JlIDMgbGlzdHM6IGZlYXR1cmVzX0ZGUywgZmVhdHVyZXNfTEFTU08sIGZlYXR1cmVzX1JGIGludG8gYSBsaXN0OiBhbGxfZmVhdHVyZXMuIA0KDQpGb3IgZXhhbXBsZSwgYWxsX2ZlYXR1cmVzW1siTEFTU08iXV1bWzNdXVtbIk1lZGlhbl9Ecm9wX0xhYmVsIl1dIHN0b3JlcyB0aGUgZmVhdHVyZXMgYWZ0ZXIgYXBwbHlpbmcgTEFTU08gdG8gdGhlIHRoaXJkIHRyYWluaW5nIGRhdGFzZXRzIGZyb20gdGhlIGRhdGE6IGltcHV0aW5nIG1lZGlhbiBmb3IgbWlzc2luZyB2YWx1ZXMgaW4gdGhlIG51bWVyaWNhbCB2YXJpYWJsZXMsIGRyb3BwaW5nIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsIGFuZCBtYXBwaW5nIGVhY2ggbGV2ZWwgaW4gZWFjaCBjYXRlZ29yaWNhbCB2YXJpYWJsZSB0byBhbiBpbnRlZ2VyLiANCg0KDQpgYGB7ciBmZWF0dXJlc2VsZWN0LCBldmFsPUZBTFNFfQ0KbnVtQ29yZXMgPC0gNCAjIHRoaXMgZGVwZW5kcyBvbiB0aGUgbnVtYmVyIG9mIGNvcmVzIGFuZCB0aGUgaW5zdGFsbGVkIG1lbW9yeSAoUkFNKSBhdmFpbGFibGUgaW4gdGhlIGNvbXB1dGVyDQojIEZhc3QgRmVhdHVyZSBzZWxlY3Rpb24NCmZlYXR1cmVzX0ZGUyA8LSB2ZWN0b3IobW9kZT0ibGlzdCIsIDUpDQpzdGFydFRpbWUgPC0gcHJvYy50aW1lKClbM10gIyBzZXQgdXAgdGltZXINCg0KZm9yIChpIGluIDE6NSl7DQogIGNsIDwtIG1ha2VDbHVzdGVyKG51bUNvcmVzLCB0eXBlPSJTT0NLIikNCiAgZmVhdHVyZXNfRkZTW1tpXV0gPC0gcGFyU2FwcGx5KGNsLCAxOjE2LCBzZWxlY3RfdmFyaWFibGVzLCBkZl9hbGwsICJPVVRDT01FIiwgIkZGUyIsIGFsbF9kYXRhX0lELCBob2xkb3V0X2luZGV4W1tpXV0sIDIwOTApDQogIHN0b3BDbHVzdGVyKGNsKQ0KfQ0KDQpydW5UaW1lID0gcHJvYy50aW1lKClbM10gLSBzdGFydFRpbWUNCnByaW50KHBhc3RlKCJJdCB0b29rIiwgcm91bmQocnVuVGltZSksICJzZWNvbmRzIHRvIG9idGFpbiBhbGwgZmVhdHVyZXMgdXNlZCB1c2luZyBGRlMuIikpDQoNCiMgTGFzc28gRmVhdHVyZSBzZWxlY3Rpb24gZm9yIEJpbm9taWFsIFRBUkdFVFMNCmZlYXR1cmVzX0xBU1NPIDwtIHZlY3Rvcihtb2RlPSJsaXN0IiwgNSkNCnN0YXJ0VGltZSA8LSBwcm9jLnRpbWUoKVszXSAjIHNldCB1cCB0aW1lcg0KDQpmb3IgKGkgaW4gMTo1KXsNCiAgY2wgPC0gbWFrZUNsdXN0ZXIobnVtQ29yZXMsIHR5cGU9IlNPQ0siKQ0KICBmZWF0dXJlc19MQVNTT1tbaV1dIDwtIHBhclNhcHBseShjbCwgMToxNiwgc2VsZWN0X3ZhcmlhYmxlcywgZGZfYWxsLCAiT1VUQ09NRSIsICJMQVNTTyIsIGFsbF9kYXRhX0lELCBob2xkb3V0X2luZGV4W1tpXV0sIDIwOTApDQogIHN0b3BDbHVzdGVyKGNsKQ0KfQ0KDQpydW5UaW1lID0gcHJvYy50aW1lKClbM10gLSBzdGFydFRpbWUNCnByaW50KHBhc3RlKCJJdCB0b29rIiwgcm91bmQocnVuVGltZSksICJzZWNvbmRzIHRvIG9idGFpbiBhbGwgZmVhdHVyZXMgdXNlZCB1c2luZyBMQVNTTy4iKSkNCg0KIyBSYW5kb20gRm9yZXN0IEZlYXR1cmUgU2VsZWN0aW9uDQpmZWF0dXJlc19SRiA8LSB2ZWN0b3IobW9kZT0ibGlzdCIsIDUpDQpzdGFydFRpbWUgPC0gcHJvYy50aW1lKClbM10gIyBzZXQgdXAgdGltZXINCg0KZm9yIChpIGluIDE6NSl7DQogIGNsIDwtIG1ha2VDbHVzdGVyKG51bUNvcmVzLCB0eXBlPSJTT0NLIikNCiAgZmVhdHVyZXNfUkZbW2ldXSA8LSBwYXJTYXBwbHkoY2wsIDE6MTYsIHNlbGVjdF92YXJpYWJsZXMsIGRmX2FsbCwgIk9VVENPTUUiLCAiUkYiLCBhbGxfZGF0YV9JRCwgaG9sZG91dF9pbmRleFtbaV1dLCAyMDkwKQ0KICBzdG9wQ2x1c3RlcihjbCkNCn0NCg0KcnVuVGltZSA9IHByb2MudGltZSgpWzNdIC0gc3RhcnRUaW1lDQpwcmludChwYXN0ZSgiSXQgdG9vayIsIHJvdW5kKHJ1blRpbWUpLCAic2Vjb25kcyB0byBvYnRhaW4gYWxsIGZlYXR1cmVzIHVzZWQgdXNpbmcgUmFuZG9tIEZvcmVzdC4iKSkNCg0KYWxsX2ZlYXR1cmVzIDwtIGxpc3QoZmVhdHVyZXNfRkZTLCBmZWF0dXJlc19MQVNTTywgZmVhdHVyZXNfUkYpDQpuYW1lcyhhbGxfZmVhdHVyZXMpIDwtIGMoIkZGUyIsICJMQVNTTyIsICJSRiIpDQoNCmBgYA0KDQpGb3IgdGhlIHJlZmVyZW5jZSwgdGhlIGFwcHJveGltYXRlIHJ1biB0aW1lIChpbiBzZWNvbmRzKSBmb3IgZWFjaCBtZXRob2QgdG8gb2J0YWluIHRoZSBsaXN0IHVzaW5nIGEgV2luZG93cyAxMCBtYWNoaW5lIHdpdGggSW50ZWwoUikgQ29yZShUTSkgaTcgUHJvY2Vzc29yICg0IGNvcmVzLCA4R0IgUkFNKSBpcyByZXBvcnRlZCBiZWxvdy4NCg0KYGBge3IgcnVudGltZX0NClRpbWUgPC0gYygzNzQsIDIzMjgsIDU1NzYpDQpuYW1lcyhUaW1lKSA8LSBjKCJGRlMiLCAiTEFTU08iLCAiUkYiKQ0KZGF0YS50YWJsZSh0KFRpbWUpKQ0KYGBgDQoNCiMjIE9idGFpbiBSZXN1bHRzIGZvciBBbGwgU2NlbmFyaW9zIA0KDQpJbiB0aGUgZm9sbG93aW5nIGNvZGUgY2h1bmssIGEgcGFyYWxsZWwgYWxnb3JpdGhtIHdpbGwgYmUgdXNlZCB0byBvYnRhaW4gdGhlIHByZWRpY3Rpb24gcGVyZm9ybWFuY2Ugb2YgZWFjaCBjb21iaW5hdGlvbiBvZiBmYWN0b3JzIGFuZCB3ZSBzYXZlIHRoZSByZXN1bHQgaW4gdGhlIGZpbGU6IGFsbF9zY2VuYXJpb3MuY3N2LiBXZSByZXBvcnQgZml2ZSBjb21tb24gcGVyZm9ybWFuY2UgbWV0cmljcyB0aGF0IGFyZSBzdWl0ZWQgZm9yIHR3byBjbGFzcyBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtczogQVVDLCBhY2N1cmFjeSwgc2Vuc2l0aXZpdHksIHNwZWNpZmljaXR5IGFuZCBHLW1lYW4gd2hlcmUgRy1tZWFuICQ9IFxzcXJ0e1xtYm94e3NlbnNpdGl2aXR5fSBcdGltZXMgXG1ib3h7c3BlY2lmaWNpdHl9fSQuDQoNCldlIHVzZSBhIGZ1bmN0aW9uIERPRV9mdW5jdGlvbigpIHRvIGNvbmR1Y3QgdGhpcyBzdHVkeS4gVGhpcyBmdW5jdGlvbiBjYW4gYmUgZm91bmQgaW4gdGhlIFtjdXN0b21fZnVuY3Rpb25zLlJdKA0KaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL1lpbmctSnUvRXhwbGFpbmluZy1QcmVkaWN0aXZlLU1vZGVsLVBlcmZvcm1hbmNlLmdpdGh1Yi5pby9tYXN0ZXIvY3VzdG9tX2Z1bmN0aW9ucy5SKXt0YXJnZXQ9Il9ibGFuayJ9LiBUaGUgb3V0cHV0IG9mIHRoaXMgZnVuY3Rpb24gY291bGQgYmUgZWl0aGVyIGEgdmVjdG9yIG9yIGEgbGlzdCBkZXBlbmRzIG9uIGlmIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgZm9yIFN1cnZpdmFsIGlzIHJldHVybmVkLiAgDQoNCg0KYGBge3IgZGVzaWduLCBldmFsPUZBTFNFfQ0KZm9sZCA8LSAxOjUNCmZlYXR1cmVfc2VsZWN0aW9uIDwtIGMoIkZGUyIsICJMQVNTTyIsIlJGIikNCnN1YnNhbXBsaW5nIDwtIGMoIm5vbmUiLCAiZG93biIsICJVcCIsICJzbW90ZSIsICJyb3NlIikNCmFsZ29yaXRobSA8LSBjKCJBTk4iLCAiRFQiLCAiRWxhc3RpY05ldCIsICJLUExTUiIsICJMREEiLCAiTFIiLCAiTkIiLCAiUkYiLCAiWEdCIikNCg0KRGVzaWduIDwtIGNyb3NzaW5nKGZvbGQsIGltcHV0YXRpb25fbnVtLCBpbXB1dGF0aW9uX2NhdCwgZW5jb2RpbmcsIGZlYXR1cmVfc2VsZWN0aW9uLCBzdWJzYW1wbGluZywgYWxnb3JpdGhtKQ0KDQpudW1Db3JlcyA8LSA0ICMgdGhpcyBkZXBlbmRzIG9uIHRoZSBudW1iZXIgb2YgY29yZXMgYW5kIHRoZSBpbnN0YWxsZWQgbWVtb3J5IChSQU0pIGF2YWlsYWJsZSBpbiB0aGUgY29tcHV0ZXINCg0KY2wgPC0gbWFrZUNsdXN0ZXIobnVtQ29yZXMsIHR5cGU9IlNPQ0siKQ0KDQphbGxfc2NlbmFyaW9zIDwtIHBhclNhcHBseShjbCwgMTpucm93KERlc2lnbiksIERPRV9mdW5jdGlvbiwgRGVzaWduLCBkZl9hbGwsICJPVVRDT01FIiwgYWxsX2RhdGFfSUQsIGhvbGRvdXRfaW5kZXgsIGFsbF9mZWF0dXJlcywgcmV0dXJuLnByZWRpY3Rpb249RkFMU0UsIDIwMjApDQoNCnN0b3BDbHVzdGVyKGNsKQ0KDQphbGxfc2NlbmFyaW9zIDwtIHQoYWxsX3NjZW5hcmlvcykNCmNvbG5hbWVzKGFsbF9zY2VuYXJpb3MpIDwtIGMoImZvbGQiLCAiaW1wdXRhdGlvbl9udW0iLCAiaW1wdXRhdGlvbl9jYXQiLCAiZW5jb2RpbmciLCAiZmVhdHVyZV9zZWxlY3Rpb24iLCAic3Vic2FtcGxpbmciLCAiYWxnb3JpdGhtIiwgImF1YyIsICJzZW4iLCAic3BlYyIsICJhY2N1IiwgImdtZWFuIikNCmFsbF9zY2VuYXJpb3MgPC0gdGJsX2RmKGFsbF9zY2VuYXJpb3MpDQphbGxfc2NlbmFyaW9zWyxjKCJmb2xkIiwgImF1YyIsICJzZW4iLCAic3BlYyIsICJhY2N1IiwgImdtZWFuIildICU8PiUgbXV0YXRlX2FsbCggYXMubnVtZXJpYykNCg0KI3dyaXRlLmNzdihhbGxfc2NlbmFyaW9zLCAiLi4vUmVzdWx0cy9hbGxfc2NlbmFyaW9zLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQ0KDQpgYGANCg0KDQotLS0NCiAgDQojIEhpZXJhcmNoaWNhbCBSZWdyZXNzaW9uDQoNCkluIHRoaXMgc2VjdGlvbiwgd2UgYW5hbHlzaXMgdGhlIHJlc3VsdCB3ZSBvYnRhaW4gZm9yIGFsbCBzY2VuYXJpb3MgdXNpbmcgaGllcmFyY2hpY2FsIHJlZ3Jlc3Npb24uIA0KDQojIyBSZWFkIGFuZCBwcmVwcm9jZXNzIHRoZSByZXN1bHQgZGF0YSBmb3IgYWxsIHNjZW5hcmlvcw0KDQpJbiB0aGUgZm9sbG93aW5nIGNvZGUgY2h1bmssIHdlIHJlYWQgdGhlIHJlc3VsdCBkYXRhIGZvciBhbGwgc2NlbmFyaW9zLg0KDQpgYGB7ciByZWFkX3NjZW5hcmlvc30NCmRhdGEgPC0gcmVhZC5jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9ZaW5nLUp1L0V4cGxhaW5pbmctUHJlZGljdGl2ZS1Nb2RlbC1QZXJmb3JtYW5jZS5naXRodWIuaW8vbWFzdGVyL2FsbF9zY2VuYXJpb3MuY3N2IikNCmBgYA0KDQpOZXh0IHdlIGNoZWNrIGlmIHdlIG9idGFpbiBhbGwgdmFsdWVzIG9mIHBlcmZvcm1hbmNlIG1lYXN1cmVzIGZvciBhbGwgc2NlbmFyaW9zIGJ5IHN0dWR5aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgbWlzc2luZyB2YWx1ZXMgaW4gdGhlIGRhdGEuIFRoZW4gd2UgcmVtb3ZlIHRoZSBjYXNlcyB3aXRoIG1pc3NpbmcgdmFsdWVzIHNpbmNlIHdlIGFyZSBub3QgYWJsZSB0byB1c2UgdGhlbSBmb3IgdGhlIGZ1cnRoZXIgYW5hbHlzaXMuICANCg0KIyMjIERpc3RyaWJ1dGlvbiBvZiBNaXNzaW5nIFZhbHVlcyB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30NCg0KIyMjIyBNaXNzaW5nIFZhbHVlcyB7LX0NCg0KV2Ugc3R1ZHkgdGhlIGRpc3RyaWJ1dGlvbiBvZiBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgcmVzdWx0IGRhdGEuDQoNCmBgYHtyIG1pc3NpbmdkaXN0fQ0KcGxvdF9taXNzaW5nKGRhdGEpDQpgYGANCg0KIyMjIyBIaXN0b2dyYW1zIG9mIFBlcmZvcm1hbmNlIE1lYXN1cmVzIHstfQ0KDQpgYGB7ciBoaXN0b2dyYW1fbWlzc2luZ30NCnBsb3RfaGlzdG9ncmFtKGRhdGFbLGMoImFjY3UiLCAiYXVjIiwgImdtZWFuIiwgInNlbiIsICJzcGVjIildKQ0KYGBgDQoNCiMjIyMgQ2FzZXMgd2l0aCBNaXNzaW5nIFZhbHVlcyB7LX0NCldlIHByaW50IHRoZSBjYXNlcyB0aGF0IGFyZSBtaXNzaW5nIGluIHRoZSBmb2xsb3dpbmcgdGFibGUuIFdlIG5vdGUgdGhhdCBhbGwgMTcgbWlzc2luZyBjYXNlcyBhcmUgb2YgYWxnb3JpdGhtIEtQTFNSLiANCg0KYGBge3IgZmluZG1pc3Npbmd9DQpuYV92ZWMgPC0gd2hpY2goaXMubmEoZGF0YSRhdWMpKQ0KZGF0YXRhYmxlKGRhdGFbbmFfdmVjLGMoImFsZ29yaXRobSIsICJzdWJzYW1wbGluZyIsICJmZWF0dXJlX3NlbGVjdGlvbiIsICJpbXB1dGF0aW9uX251bSIsICJpbXB1dGF0aW9uX2NhdCIsICJlbmNvZGluZyIpXSkgI3ByaW50cyB0aGUgY2FzZXMgdGhhdCBhcmUgbWlzc2luZw0KYGBgDQoNCg0KDQpXZSBnbGltcHNlIHRoZSBjYXNlcyB0aGF0IGFyZSBub3QgbWlzc2luZy4gDQoNCmBgYHtyIG5vdG1pc3Npbmd9DQpoZWFkKGRhdGFbLW5hX3ZlYyxjKCJhbGdvcml0aG0iLCAgInN1YnNhbXBsaW5nIiwgImZlYXR1cmVfc2VsZWN0aW9uIiwgImltcHV0YXRpb25fbnVtIiwgImltcHV0YXRpb25fY2F0IiwgImVuY29kaW5nIildKSAjZ2xpbXBzZXMgdGhlIGNhc2VzIHRoYXQgYXJlIG5vdCBtaXNzaW5nDQpgYGANCg0KIyMjIEV4YW1pbmF0aW9uIG9mIENvbXBsZXRlIERhdGEgey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9DQoNCkJlbG93IHdlIHRha2UgYSBsb29rIGF0IHRoZSBjb21wbGV0ZSBkYXRhLg0KDQojIyMjIERpc3RyaWJ1dGlvbiBvZiBDb21wbGV0ZSBEYXRhIHstfQ0KDQpgYGB7ciBwbG90Y29tcGxldGV9DQpub25jb252ZXJnZV9jYXNlcyA8LSBkYXRhW25hX3ZlYyxdDQpjb21wbGV0ZSA8LSBkYXRhWy1uYV92ZWMsXQ0KcGxvdF9taXNzaW5nKGNvbXBsZXRlKQ0KYGBgDQpXZSBleHBsaWNpdGx5IHJlbW92ZWQgdGhlIE5BJ3MgZnJvbSB0aGUgZGF0YSwgY3JlYXRpbmcgYSBkYXRhIGZyYW1lIGNhbGxlZCBjb21wbGV0ZS4gIA0KDQojIyMjIEhpc3RvZ3JhbXMgb2YgUGVyZm9ybWFuY2UgTWVhc3VyZXMgey19DQoNCmBgYHtyIGNvbXBsZXRlX2hpc3RvZ3JhbXN9DQpwbG90X2hpc3RvZ3JhbShjb21wbGV0ZVssYygiYWNjdSIsICJhdWMiLCAiZ21lYW4iLCAic2VuIiwgInNwZWMiKV0pDQpgYGANCg0KDQojIyBBbmFseXNpcyBFeHBsYW5hdGlvbg0KDQpXZSB3aWxsIHVzZSAiaGllcmFyY2hpY2FsIFJlZ3Jlc3Npb24iLiAgVGhpcyBpcyBhIHNpbXBsZSB0ZWNobmlxdWUgb2YgZW50ZXJpbmcgdmFyaWFibGVzIGludG8gYSByZWdyZXNzaW9uIGluIGJsb2NrcyBzbyB0aGF0IHdlIGNhbiBzZWUgdGhlICJuZXQgZWZmZWN0IiBvZiB0aGUgdmFyaWFibGVzIG9uIHRoZSByZXNwb25zZS4gIEl0IGlzIGEgcG9wdWxhciBtZXRob2QgaW4gZXhwbGFuYXRvcnkgbW9kZWxpbmcsIGVzcGVjaWFsbHkgaW4gdGhlIGJlaGF2aW9yYWwgc2NpZW5jZXMuICBJbiB0aGUgYmVoYXZpb3JhbCBzY2llbmNlcywgcmVzZWFyY2hlcnMgd2lsbCBlbnRlciwgZmlyc3QgdGhlIGRlbW9ncmFwaGljcyBvciB2YXJpYWJsZXMgdGhhdCBoYXZlIG5vdCBiZWVuIG1hbmlwdWxhdGVkLCBhbmQgYXJlIG91dHNpZGUgb2YgdGhlIGNvbnRyb2wgb2YgdGhlIHJlc2VhcmNoZXIgKGUuZy4gZ2VuZGVyLCByYWNlKSwgdGhlbiB0aGV5IG1heSBlbnRlciBleGlzdGluZyB2YXJpYWJsZXMgdGhhdCBhcmUga25vd24gdG8gZWZmZWN0IGEgcmVzcG9uc2UsIGJ1dCBhcmUgbm90IHVuZGVyIHN0dWR5IGluIHRoZSBjdXJyZW50IHJlc2VhcmNoLCB0aGVuIHRoZXkgd2lsbCBlbnRlciB0aGUgbmV3bHkgcHJvcG9zZWQgcmVzZWFyY2ggdmFyaWFibGVzLiAgVGhlIGdvYWwgaXMgdG8gc2hvdyB0aGF0IHRoZSBuZXdseSBwcm9wb3NlZCB2YXJpYWJsZXMgImV4cGxhaW4iIGEgbGFyZ2UgcGVyY2VudGFnZSBvZiBhZGRpdGlvbmFsIHZhcmlhdGlvbi4NCg0KV2UgYmVsaWV2ZSB0aGF0IHRoaXMgYXVkaWVuY2UgaXMgdXNlZCB0byBzZWVpbmcgZXhwbGFuYXRvcnkgYW5hbHlzZXMgcHJlc2VudGVkIGluIHRoaXMgd2F5LiAgSXQgaXMgYWN0dWFsbHkgZmFpcmx5IGluZm9ybWF0aXZlLiAgDQoNCkluIHRoZSBleGFtcGxlcyBiZWxvdywgd2UgY2hvb3NlIHRocmVlIGJsb2NrczoNCg0KMS4gIENhdGVnb3JpY2FsIEltcHV0YXRpb24sIE51bWVyaWNhbCBJbXB1dGF0aW9uLCBhbmQgRW5jb2RpbmcgTWFpbiBFZmZlY3RzIHBsdXMgdGhlIHR3by13YXkgaW50ZXJhY3Rpb25zIGFtb25nIHRoZXNlIHZhcmlhYmxlcy4NCjIuICBBbGwgdmFyaWFibGVzIGluICMxIHBsdXMgU3Vic2FtcGxpbmcgTWFpbiBFZmZlY3RzIGFuZCBhbGwgcG9zc2libGUgdHdvLXdheSBpbnRlcmFjdGlvbnMgYW1vbmcgdGhlc2UgdmFyaWFibGVzLg0KMy4gIEFsbCB2YXJpYWJsZXMgaW4gIzErIzIgcGx1cyBNTCBtZXRob2QgYW5kIFZhcmlhYmxlIFNlbGVjdGlvbiBtZXRob2QuICBJbiBlYWNoIHN0ZXAsIHdlIHdpbGwgaW5jbHVkZSBhbGwgcG9zc2libGUgdHdvLXdheSBpbnRlcmFjdGlvbnMuDQoNCk91ciByZWFzb25pbmcgaXMgdGhhdCBpbXB1dGF0aW9uIGFuZCBlbmNvZGluZyBpcyBkb25lIGZpcnN0LCB3aGlsZSBkYXRhIGNsZWFuaW5nLiAgTmV4dCwgc3Vic2FtcGxpbmcgaXMgZG9uZSBwcmlvciB0byBhbnkgYW5hbHlzaXMuIEZpbmFsbHksIHRoZSBhbGdvcml0aG0gYW5kIHZhcmlhYmxlIHNlbGVjdGlvbiBhcmUgZG9uZSBpbiB0YW5kZW0sIHdpdGggY2hvaWNlcyBtYWRlIHNpbXVsdGFuZW91c2x5LiANCg0KV2UgY2hvc2UgdG8gY29uc2lkZXIgQVVDIGFuZCBnbWVhbiBhcyBib3RoIHN1bW1hcml6ZSB3ZWxsIGZvciB0aGUgdW5iYWxhbmNlZCBleGFtcGxlIGFuZCBnaXZlIGRpZmZlcmVudCB2aWV3cyBvZiB0aGUgZml0LiAgVGhlIHJlc3VsdHMgZGlmZmVyIHNsaWdodGx5IGFjY29yZGluZyB0byB0aGUgcmVzcG9uc2UuDQoNCg0KIyMgQVVDIEFuYWx5c2lzDQoNCg0KIyMjIEJsb2NrIDE6IE51bWVyaWMgSW1wdXRhdGlvbiwgQ2F0ZWdvcmljYWwgSW1wdXRhdGlvbiwgRW5jb2RpbmcNCg0KSW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLCB3ZSBvYnRhaW4gdGhlIEFub3ZhIElJIFNTIHVzaW5nIEJsb2NrIDEuDQoNCmBgYHtyIGJsb2NrMWFub3ZhfQ0KYmxvY2sxYXVjIDwtIGZvcm11bGEoYXVjfihpbXB1dGF0aW9uX251bStpbXB1dGF0aW9uX2NhdCtlbmNvZGluZyleMikNCmF1Y21vZDEgPC0gbG0oYmxvY2sxYXVjLGRhdGE9Y29tcGxldGUpDQpBbm92YShhdWNtb2QxLHR5cGU9IklJIikNCmBgYA0KDQpGcm9tIHRoZSBhYm92ZSwgd2Ugc2VlIHRoYXQgdGhlIGludGVyYWN0aW9uIGJldHdlZW4gbnVtZXJpYyBhbmQgY2F0ZWdvcmljYWwgaW1wdXRhdGlvbiBpcyBzaWduaWZpY2FudC4gIA0KDQpgYGB7ciBSMm1vZGVsMX0NCnByaW50KHBhc3RlKCJSLXNxdWFyZSA9ICIsIHJvdW5kKHN1bW1hcnkoYXVjbW9kMSkkci5zcXVhcmUsIDQpKSkNCmBgYA0KDQpEZXNwaXRlIHRoZSBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2Ugb2YgdGhlIGltcHV0YXRpb24gbWV0aG9kIGludGVyYWN0aW9uLCB3ZSBjYW4gc2VlIHRoYXQgbXVsdGlwbGUgUi1zcXVhcmUgaXMgdmVyeSBsb3cuICBXZSBtdXN0IGtlZXAgaW4gbWluZCB0aGF0IHRoZSBlcnJvciBkZWdyZWVzIG9mIGZyZWVkb20gaXMgMTAsNzcwLiAgUG93ZXIgaXMgaGlnaCwgYW5kIGVmZmVjdCBzaXplcyBhcmUgcXVpdGUgc21hbGwuIA0KDQpXZSByZXBvcnQgdGhlIGluZGl2aWR1YWwgY29lZmZpY2llbnRzIGluIHRoZSBmb2xsb3dpbmcgdGFibGUuIA0KDQpgYGB7ciBibG9jazFjb2Vmc30NCmIgPC0gZGF0YS5mcmFtZShjb2VmZmljaWVudHMoYXVjbW9kMSkpDQpiJHN0ZGVyciA8LSBzdW1tYXJ5KGF1Y21vZDEpJGNvZWZmaWNpZW50c1ssMl0NCmIkdCA8LSBzdW1tYXJ5KGF1Y21vZDEpJGNvZWZmaWNpZW50c1ssM10gDQpiJHB2YWwgPC0gc3VtbWFyeShhdWNtb2QxKSRjb2VmZmljaWVudHNbLDRdDQp0YWJsZTEgPC0gcm91bmQoYiwgNCkNCmNvbG5hbWVzKHRhYmxlMSkgPC0gYygiQiIsIlN0ZCBFcnJvciIsInQiLCJwLXZhbCIpDQpkYXRhdGFibGUodGFibGUxKQ0KYGBgDQoNCg0KIyMjIEJsb2NrIDI6ICBCbG9jayAxICsgc3Vic2FtcGxpbmcNCg0KSW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLCB3ZSBvYnRhaW4gdGhlIEFub3ZhIElJIFNTIHVzaW5nIEJsb2NrIDIuDQoNCmBgYHtyIGJsb2NrMmFub3ZhfQ0KYmxvY2syYXVjIDwtIGZvcm11bGEoYXVjfihpbXB1dGF0aW9uX251bStpbXB1dGF0aW9uX2NhdCtlbmNvZGluZw0KK3N1YnNhbXBsaW5nKV4yKQ0KYXVjbW9kMiA8LSBsbShibG9jazJhdWMsZGF0YT1jb21wbGV0ZSkNCkFub3ZhKGF1Y21vZDIsdHlwZT0iSUkiKQ0KYGBgDQoNCkZyb20gdGhlIGFib3ZlLCB3ZSBzZWUgdGhhdCB0aGUgbnVtZXJpY2FsIGFuZCBjYXRlZ29yaWNhbCBpbXVwdGF0aW9uIGludGVyYWN0aW9uIHJlbWFpbnMgc2lnbmlmaWNhbnQgaW4gdGhpcyBtb2RlbC4gIEluIGFkZGl0aW9uLCB0aGUgbWFpbiBlZmZlY3QgZm9yIHN1YnNhbXBsaW5nIGlzIHNpZ25pZmljYW50Lg0KDQpgYGB7ciByMmJsb2NrMn0NCnByaW50KHBhc3RlKCJSLXNxdWFyZSA9ICIsIHJvdW5kKHN1bW1hcnkoYXVjbW9kMikkci5zcXVhcmUsIDQpKSkNCndhbGR0ZXN0KGF1Y21vZDEsYXVjbW9kMikNCnIyZGlmZjEyIDwtIHJvdW5kKHN1bW1hcnkoYXVjbW9kMikkci5zcXVhcmVkLXN1bW1hcnkoYXVjbW9kMSkkci5zcXVhcmVkLCA0KQ0KcHJpbnQocGFzdGUoInItc3F1YXJlZCBkaWZmZXJlbmNlID0gIixyMmRpZmYxMikpDQpgYGANCg0KV2UgY2FuIHNlZSBxdWl0ZSBhIGNoYW5nZSBpbiB0aGUgUi1zcWFyZSBmcm9tIHRoZSBhZGRpdGlvbiBvZiByZXNhbXBsaW5nL3N1YnNhbXBsaW5nLiAgDQoNClRoZSB3YWxkIHRlc3QgdGVzdHMgZm9yIHNpZ25pZmljYW5jZSBvZiB0aGUgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBhY2NvdW50ZWQgZm9yIGJ5IGJsb2NrcyBvZiBwcmVkaWN0b3JzIGFkZGVkIHRvIGEgbW9kZWwuICBXZSBoYXZlIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSB3aXRoIEYgPSAxNDYuMjUsIG9uIDI0IG51bWVyYXRvciBhbmQgMTA3NzAgZGVub21pbmF0b3IgZGVncmVlcyBvZiBmcmVlZG9tIChwPDIuMmUtMTYpLiAgVGhpcyBpbmRpY2F0ZXMgdGhhdCB0aGUgbW9kZWwgaGFzIGEgc2lnbmlmaWNhbnQgZHJvcCBpbiBTU0UgYnkgaW5jbHVkaW5nIHN1YnNhbXBsaW5nIG1ldGhvZCBpbiB0aGUgbW9kZWwgdGhhdCBhbHJlYWR5IGluY2x1ZGVkIGVuY29kaW5nIGFuZCBpbXB1dGF0aW9uIG1ldGhvZHMgKGNhdGVnb3JpY2FsIGFuZCBudW1lcmljYWwpLiAgVGhpcyBpbmRpY2F0ZXMgdGhhdCBzdWJzYW1wbGluZyBpcyAoc3RhdGlzdGljYWxseSkgaW1wb3J0YW50IHRvIHRoZSBBVUMgb2YgdGhlIG1vZGVsLg0KDQpJbiBhZGRpdGlvbiB0aGUgaW5jcmVhc2UgaW4gUi1zcXVhcmUgaXMgMjQuMzclIHdoZW4gc3Vic2FtcGxpbmcgaXMgaW5jbHVkZWQgY29tcGFyZWQgdG8gdGhlIG1vZGVsIHdpdGggb25seSBlbmNvZGluZyBhbmQgaW1wdXRhdGlvbiBtZXRob2RzLiBOb3RlIHRoaXMgaXMgZXhwbGFuYXRvcnkgaW4gdGVybXMgb2YgdGhlIGJlaGF2aW9yIG9mIHRoZSBleHBlcmltZW50YWwgZGF0YS4NCg0KV2UgcmVwb3J0IHRoZSBpbmRpdmlkdWFsIGNvZWZmaWNpZW50cyBpbiB0aGUgZm9sbG93aW5nIHRhYmxlLiANCg0KYGBge3IgYmxvY2syY29lZnN9DQpiIDwtIGRhdGEuZnJhbWUoY29lZmZpY2llbnRzKGF1Y21vZDIpKQ0KYiRzdGRlcnIgPC0gc3VtbWFyeShhdWNtb2QyKSRjb2VmZmljaWVudHNbLDJdDQpiJHQgPC0gc3VtbWFyeShhdWNtb2QyKSRjb2VmZmljaWVudHNbLDNdIA0KYiRwdmFsIDwtIHN1bW1hcnkoYXVjbW9kMikkY29lZmZpY2llbnRzWyw0XQ0KdGFibGUyIDwtIHJvdW5kKGIsIDQpDQpjb2xuYW1lcyh0YWJsZTIpIDwtIGMoIkIiLCJTdGQgRXJyb3IiLCJ0IiwicC12YWwiKQ0KZGF0YXRhYmxlKHRhYmxlMikNCmBgYA0KDQojIyMgQmxvY2sgMzogQmxvY2sgMSArIEJsb2NrIDIgKyBNTCBtZXRob2QgKyBWYXJpYWJsZSBTZWxlY3Rpb24gTWV0aG9kDQoNCkluIHRoZSBmb2xsb3dpbmcgY29kZSBjaHVuaywgd2Ugb2J0YWluIHRoZSBBbm92YSBJSSBTUyB1c2luZyBCbG9jayAzLg0KDQpgYGB7ciBibG9jazNhbm92YX0NCmJsb2NrM2F1YyA8LSBmb3JtdWxhKGF1Y34oaW1wdXRhdGlvbl9udW0raW1wdXRhdGlvbl9jYXQrZW5jb2RpbmcNCitzdWJzYW1wbGluZytmZWF0dXJlX3NlbGVjdGlvbithbGdvcml0aG0pXjIpDQphdWNtb2QzIDwtIGxtKGJsb2NrM2F1YyxkYXRhPWNvbXBsZXRlKQ0KQW5vdmEoYXVjbW9kMyx0eXBlPSJJSSIpDQpgYGANCg0KVGhlIHJlc3VsdHMgYXJlIHF1aXRlIGNvbXBsZXguICBBbGwgdHdvLXdheSBpbnRlcmFjdGlvbnMgYXJlIHNpZ25pZmljYW50IGV4Y2VwdCBmb3IgaW50ZXJhY3Rpb25zIHRoYXQgaW5jbHVkZSBlbmNvZGluZy4gIEl0IHNlZW1zIHRoYXQgZW5jb2RpbmcgaXMgbm90IHJlbGV2YW50IHRvIEFVQywgYnV0IGFsbCBvdGhlciB2YXJpYWJsZXMgaW50ZXJhY3QuICANCg0KYGBge3IgcjJibG9jazN9DQpwcmludChwYXN0ZSgiUi1zcXVhcmUgPSAiLCByb3VuZChzdW1tYXJ5KGF1Y21vZDMpJHIuc3F1YXJlLCA0KSkpDQp3YWxkdGVzdChhdWNtb2QyLGF1Y21vZDMpDQpyMmRpZmYyMyA8LSByb3VuZChzdW1tYXJ5KGF1Y21vZDMpJHIuc3F1YXJlZC1zdW1tYXJ5KGF1Y21vZDIpJHIuc3F1YXJlZCw0KQ0KcHJpbnQocGFzdGUoInItc3F1YXJlZCBkaWZmZXJlbmNlID0gIixyMmRpZmYyMykpDQpgYGANCg0KVGhlIHdhbGQgdGVzdCB0ZXN0cyBmb3Igc2lnbmlmaWNhbmNlIG9mIHRoZSBhZGRpdGlvbmFsIGluZm9ybWF0aW9uIGFjY291bnRlZCBmb3IgYnkgYmxvY2tzIG9mIHByZWRpY3RvcnMgYWRkZWQgdG8gYSBtb2RlbC4gIFdlIGhhdmUgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIHdpdGggRiA9IDU1NC4zMiwgb24gMTE2IG51bWVyYXRvciBhbmQgMTA3NDYgZGVub21pbmF0b3IgZGVncmVlcyBvZiBmcmVlZG9tIChwPDIuMmUtMTYpLiAgVGhpcyBpbmRpY2F0ZXMgdGhhdCB0aGUgbW9kZWwgaGFzIGEgc2lnbmlmaWNhbnQgZHJvcCBpbiBTU0UgYnkgaW5jbHVkaW5nIHRoZSBNTCBhbmQgVmFyaWFibGUgU2VsZWN0aW9uIG1ldGhvZHMgYWxvbmcgd2l0aCB0aGUgY29ycmVzcG9uZGluZyBpbnRlcmFjdGlvbiB0ZXJtcy4NCg0KVGhlIGRpZmZlcmVuY2UgaW4gdGhlIG1vZGVsIFItc3F1YXJlIHZhbHVlcyBpcyA2NCUgYmV0d2VlbiB0aGUgbW9kZWwgaW4gQmxvY2sgMiBhbmQgdGhlIG1vZGVsIGluIEJsb2NrIDMuICBUaGlzIHN1Z2dlc3RzIHRoYXQgQWxnb3JpdGhtIGFuZCBWYXJpYWJsZSBzZWxlY3Rpb24gbWV0aG9kIGV4cGxhaW4gIG1vcmUgb2JzZXJ2ZWQgdmFyaWF0aW9uIGluIEFVQyB0aGFuIHRoZSBCbG9jayAxIGFuZCAyIGZhY3RvcnMuICANCg0KV2UgcmVwb3J0IHRoZSBpbmRpdmlkdWFsIGNvZWZmaWNpZW50cyBpbiB0aGUgZm9sbG93aW5nIHRhYmxlLiANCg0KYGBge3IgYmxvY2szY29lZnN9DQpiIDwtIGRhdGEuZnJhbWUoY29lZmZpY2llbnRzKGF1Y21vZDMpKQ0KYiRzdGRlcnIgPC0gc3VtbWFyeShhdWNtb2QzKSRjb2VmZmljaWVudHNbLDJdDQpiJHQgPC0gc3VtbWFyeShhdWNtb2QzKSRjb2VmZmljaWVudHNbLDNdIA0KYiRwdmFsIDwtIHN1bW1hcnkoYXVjbW9kMykkY29lZmZpY2llbnRzWyw0XQ0KdGFibGUzIDwtIHJvdW5kKGIsIDQpDQpjb2xuYW1lcyh0YWJsZTMpPWMoIkIiLCJTdGQgRXJyb3IiLCJ0IiwicC12YWwiKQ0KZGF0YXRhYmxlKHRhYmxlMykNCmBgYA0KDQojIyMgRWZmZWN0IHNpemUNCg0KRXZlbiB0aG91Z2ggdGhlIHR3byB3YXkgaW50ZXJhY3Rpb25zIGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50LCBtYW55IGFyZSBub3QgcHJhY3RpY2FsbHkgc2lnbmlmaWNhbnQuICBXZSByZWNvbW1lbmQgY29tcHV0aW5nIGEgY29tbW9uIG1lYXN1cmUgb2YgImVmZmVjdCBzaXplIiBrbm93biBhcyBwYXJ0aWFsICRcZXRhX3BeMiQuIFRoaXMgaXMgZXF1aXZhbGVudCB0byB3aGF0IHN0YXRpc3RpY2lhbnMgY2FsbCBwYXJ0aWFsICRSXjIkIGFuZCBpcyBjb21wdXRlZCBhcyAkJFxldGFfcF4yPVxmcmFje1NTX3tmYWN0b3J9fXtTU197ZmFjdG9yfStTU197ZXJyb3J9fS4kJCANCg0KYGBge3IgZWZmZWN0fQ0KYW5vdmFhdWMgPC0gQW5vdmEoYXVjbW9kMyx0eXBlPSJJSSIpDQplcnJvciA8LSBhbm92YWF1YyRgU3VtIFNxYFtucm93KGFub3ZhYXVjKV0NCnAuZXRhLnNxIDwtIGFzLmRhdGEuZnJhbWUoYW5vdmFhdWMkYFN1bSBTcWAvKGVycm9yK2Fub3ZhYXVjJGBTdW0gU3FgKSkNCnJvd25hbWVzKHAuZXRhLnNxKSA8LSByb3duYW1lcyhhbm92YWF1YykNCmNvbG5hbWVzKHAuZXRhLnNxKSA8LSBjKCJlZmZlY3QiKQ0KZGF0YXRhYmxlKHJvdW5kKHAuZXRhLnNxLDQpKQ0KYGBgDQoNCkluIHRoZSBiZWhhdmlvcmFsIHNjaWVuY2VzLCBlZmZlY3RzIHRoYXQgYXJlIHNtYWxsZXIgdGhhbiAwLjAyLiAgQmFzZWQgb24gdGhpcyBhbmFseXNpcywgd2Ugd2lsbCByZXRhaW4gdGhlIGZvbGxvd2luZyBpbiBvdXIgbW9kZWw6DQpNYWluIGVmZmVjdHM6DQppbXB1dGF0aW9uX251bSwgDQppbXB1dGF0aW9uX2NhdCwgDQpzdWJzYW1wbGluZywgDQpmZWF0dXJlX3NlbGVjdGlvbiwgDQphbGdvcml0aG0sIGFuZCAgDQppbXB1dGF0aW9uX251bTppbXB1dGF0aW9uX2NhdCwgDQppbXB1dGF0aW9uX251bTpmZWF0dXJlX3NlbGVjdGlvbiwgDQpzdWJzYW1wbGluZzpmZWF0dXJlX3NlbGVjdGlvbiwgDQpzdWJzYW1wbGluZzphbGdvcml0aG0sIA0KZmVhdHVyZV9zZWxlY3Rpb246YWxnb3JpdGhtDQoNCg0KIyMjIEJsb2NrIDQ6IFJlZHVjZWQgTW9kZWwNCg0KSW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLCB3ZSBvYnRhaW4gdGhlIEFub3ZhIElJIFNTIHVzaW5nIEJsb2NrIDQuDQoNCmBgYHtyIGJsb2NrNGFub3ZhfQ0KYmxvY2s0YXVjIDwtIGZvcm11bGEoYXVjfmltcHV0YXRpb25fbnVtK2ltcHV0YXRpb25fY2F0K3N1YnNhbXBsaW5nK2ZlYXR1cmVfc2VsZWN0aW9uK2FsZ29yaXRobQ0KK2ltcHV0YXRpb25fbnVtKmltcHV0YXRpb25fY2F0Kw0KaW1wdXRhdGlvbl9udW0qZmVhdHVyZV9zZWxlY3Rpb24rDQpzdWJzYW1wbGluZypmZWF0dXJlX3NlbGVjdGlvbisNCnN1YnNhbXBsaW5nKmFsZ29yaXRobSsNCmZlYXR1cmVfc2VsZWN0aW9uKmFsZ29yaXRobSkNCmF1Y21vZDQgPC0gbG0oYmxvY2s0YXVjLGRhdGE9Y29tcGxldGUpDQpBbm92YShhdWNtb2Q0LHR5cGU9IklJIikNCmBgYA0KDQpgYGB7ciByMmJsb2NrNH0NCnByaW50KHBhc3RlKCJSLXNxdWFyZSA9ICIsIHJvdW5kKHN1bW1hcnkoYXVjbW9kNCkkci5zcXVhcmUsIDQpKSkNCndhbGR0ZXN0KGF1Y21vZDQsYXVjbW9kMykNCnIyZGlmZjQzIDwtIHJvdW5kKHN1bW1hcnkoYXVjbW9kNCkkci5zcXVhcmVkLXN1bW1hcnkoYXVjbW9kMykkci5zcXVhcmVkLDQpDQpwcmludChwYXN0ZSgici1zcXVhcmVkIGRpZmZlcmVuY2UgPSAiLHIyZGlmZjQzKSkNCmBgYA0KDQpXaGlsZSB0aGVyZSBpcyBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBiZXR3ZWVuIG1vZGVscyAzIGFuZCA0IChGPTUuMzMwMSBvbiA3MyBhbmQgMTA3MTEgZGYsIHA8Mi4yMWUtMTYpLCB0aGUgZGlmZmVyZW5jZSBpbiB0aGUgJFJeMiQgdmFsdWVzIGlzIGxlc3MgdGhhbiAxJS4NCg0KSWYgd2Ugd2lzaCB0byBleHBsYWluIHRoZSBpbnRlcmFjdGlvbiB0ZXJtcywgdGhpcyBtaWdodCBiZSBhbiBlYXNpZXIgbW9kZWwgdG8gZXhwbGFpbi4NCg0KV2UgcmVwb3J0IHRoZSBpbmRpdmlkdWFsIGNvZWZmaWNpZW50cyBpbiB0aGUgZm9sbG93aW5nIHRhYmxlLiANCg0KYGBge3IgYmxvY2s0Y29lZnN9DQpiIDwtIGRhdGEuZnJhbWUoY29lZmZpY2llbnRzKGF1Y21vZDQpKQ0KYiRzdGRlcnIgPC0gc3VtbWFyeShhdWNtb2Q0KSRjb2VmZmljaWVudHNbLDJdDQpiJHQgPC0gc3VtbWFyeShhdWNtb2Q0KSRjb2VmZmljaWVudHNbLDNdIA0KYiRwdmFsIDwtIHN1bW1hcnkoYXVjbW9kNCkkY29lZmZpY2llbnRzWyw0XQ0KdGFibGU0IDwtIHJvdW5kKGIsIDQpDQpjb2xuYW1lcyh0YWJsZTQpIDwtIGMoIkIiLCJTdGQgRXJyb3IiLCJ0IiwicC12YWwiKQ0KZGF0YXRhYmxlKHRhYmxlNCkNCmBgYA0KDQoNCiMjIyBNdWx0aXBsZSBDb21wYXJpc29ucyB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30NCg0KSGVyZSB3ZSBjb25kdWN0ZWQgcGFyd2lzZSBjb21wYXJpc29ucyBhbW9uZyBpbnRlcmFjdGlvbnMgb2YgZmFjdG9ycyB1c2luZyBUdWtleS1hZGp1c3RlZCBjb21wYXJpc29ucyB1c2luZyBNb2RlbCA0LiANCg0KYGBge3IgY29tcGFyZTEsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSIxMDAlIn0NCmNhdG51bSA8LSBsc21lYW5zKGF1Y21vZDQscGFpcndpc2V+aW1wdXRhdGlvbl9jYXQ6aW1wdXRhdGlvbl9udW0sYWRqdXN0PSJ0dWtleSIpDQpwbG90KGNhdG51bVtbMl1dKSt0aGVtZV9taW5pbWFsKCkgDQpgYGANCg0KVGhlIGFib3ZlIHBsb3Qgc2hvd3MgdGhlIFR1a2V5J3MgTGVhc3QgU3F1YXJlcyBhZGp1c3RlZCBtZWFuIHBhaXJ3aXNlIGNvbnRyYXN0cyB3aXRoIGRlZmF1bHQgOTUlIGZhbWlseS13aXNlIGFkanVzdGVkIGNvbmZpZGVuY2UgaW50ZXJ2YWxzLiAgQWx0aG91Z2ggdGhlcmUgaXMgc29tZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGRpZmZlcmVuY2VzIChpbmRpY2F0ZWQgYnkgaW50ZXJ2YWxzIHRoYXQgZG8gbm90IGNyb3NzIHplcm8pLCBwcmFjdGljYWxseSwgdGhlIGRpZmZlcmVuY2VzIGluIEFVQyBhcmUgbm90IGltcG9ydGFudC4gIEl0IGRvZXMsIGhvd2V2ZXIsIGFwcGVhciB0aGF0IERyb3BwaW5nIGJvdGggdGhlIG51bWVyaWNhbCBhbmQgY2F0ZWdvcmljYWwgbWlzc2luZyBpcyBub3QgYWR2aXNhYmxlLg0KDQpgYGB7ciBjb21wYXJlMiwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjEwMCUifQ0KbnVtZnMgPC0gbHNtZWFucyhhdWNtb2Q0LHBhaXJ3aXNlfmltcHV0YXRpb25fbnVtOmZlYXR1cmVfc2VsZWN0aW9uLGFkanVzdD0idHVrZXkiKQ0KcGxvdChudW1mc1tbMl1dKSt0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpUaGlzIGlzIHByZXR0eSBkaWZmaWN1bHQgdG8gaW50ZXJwcmV0LiAgQnV0IGl0IGFwcGVhcnMgdGhhdCBzdWJzYW1wbGluZyBtZXRob2RzIG9mIERvd24sIGNvdXBsZWQgd2l0aCBNZWRpYW4gaW1wdXRhdGlvbiBvciBkcm9wcGluZyB0aGUgbWlzc2luZyBhbHRvZ2V0aGVyIHNlZW1zIHRvIHdvcmsgdGhlIGJlc3QuICBSb3NlIGlzIG5vdCBhZHZpc2FibGUuICANCg0KSW4gdGhlIGZvbGxvd2luZywgd2Ugc2hvdyB0aGUgcGFyd2lzZSBjb21wYXJpc29ucyBhbW9uZyBpbnRlcmFjdGlvbnMgb2Ygc3Vtc2FtcGxpbmcgc3RyYXRlZ2llcyBhbmQgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zLiBTaW5jZSB0aGVyZSBhcmUgOTkwIGNvbXBhcmlzb24gY2FzZXMsIHdlIHNob3cgZXZlcnkgNDUgY2FzZXMgaW4gb25lIGZpZ3VyZSBzbyB0aGF0IGl0IGlzIG1vcmUgcmVhZGFibGUuIA0KDQpgYGB7ciBjb21wYXJlMywgZmlnLmFsaWduPSdjZW50ZXInLCByZXN1bHRzPSdhc2lzJywgb3V0LndpZHRoPSIxMDAlIn0NCmFsZ3Jlc2FtcD1sc21lYW5zKGF1Y21vZDQscGFpcndpc2V+YWxnb3JpdGhtOnN1YnNhbXBsaW5nLGFkanVzdD0idHVrZXkiKQ0KIzQ1IGNob29zZSAyID0gOTkwIGNhc2VzLg0KZm9yIChpIGluIDA6MjEpew0KICBhc3NpZ24ocGFzdGUwKCJwIiwoaSsxKSkscGxvdChhbGdyZXNhbXBbWzJdXVsoaSo0NSsxKTooKGkrMSkqNDUpXSkrdGhlbWVfbWluaW1hbCgpKQ0KICANCiAgIyBXZSBpbnRyb2R1Y2UgYSB0YWIgZm9yIGVhY2ggcGFydGljaXBhbnQgdXNpbmcgdGFic2V0DQogIGNhdCgnIyMjIycscGFzdGUwKCJQIiwgKGkrMSkpLCAiey19IiwnXG4nKQ0KICBwcmludChnZXQocGFzdGUwKCJwIiwoaSsxKSkpKQ0KICBjYXQoJ1xuIFxuJykNCn0NCmBgYA0KDQoNCg0KIyMjIEludGVyYWN0aW9uIFBsb3RzIHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQ0KDQpJbiB0aGlzIHNlY3Rpb24sIHdlIHN0dWR5IHRoZSB0d28td2F5IGludGVyYWN0aW9uIGVmZmVjdHMgb24gYXVjIHZhbHVlcyB3aXRoIGVycm9yIGJhcnMgaW5kaWNhdGluZyA5NSUgcHJlZGljdGlvbiBpbnRlcnZhbHMgb24gZWFjaCBtYXJnaW5hbCBtZWFuLg0KDQojIyMjIE51bSBYIENhdCBJbXB1dGUgey19DQoNCmBgYHtyIGludGVyYWN0aW9uMX0NCnRoZW1lX3NldCh0aGVtZV9zanBsb3QoKSkNCnBsb3RfbW9kZWwoYXVjbW9kNCwgdHlwZT0icHJlZCIsIHRlcm1zPWMoImltcHV0YXRpb25fY2F0IiwgImltcHV0YXRpb25fbnVtIiksIGxpbmUuc2l6ZSA9IDEuNSwgdGl0bGU9IiIsIGF4aXMudGl0bGUgPSBjKCJDYXRlZ29yaWNhbCBWYXJpYWJsZSBJbXB1dGF0aW9uIiwiQVVDIiksIGxlZ2VuZC50aXRsZSA9ICJOdW1lcmljYWwgVmFyaWFibGUgSW1wdXRhdGlvbiIpK2ZvbnRfc2l6ZShheGlzX3RpdGxlLng9MTQsIGF4aXNfdGl0bGUueT0xNCxsYWJlbHMueD0xMiwgbGFiZWxzLnk9MTIpK3lsaW0oMC4zNSwgMC42NSkrdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJ0b3AiKQ0KYGBgDQoNCiMjIyMgRmVhdHVyZSBTZWxlY3QgWCBOdW0gSW1wdXRlIHstfQ0KDQpgYGB7ciBpbnRlcmFjdGlvbjJ9DQpwbG90X21vZGVsKGF1Y21vZDQsIHR5cGU9InByZWQiLCB0ZXJtcz1jKCJpbXB1dGF0aW9uX251bSIsICJmZWF0dXJlX3NlbGVjdGlvbiIpLCBsaW5lLnNpemUgPSAxLjUsIHRpdGxlPSIiLCBheGlzLnRpdGxlPWMoIk51bWVyaWNhbCBWYXJpYWJsZSBJbXB1dGF0aW9uIiwgIkFVQyIpLA0KICAgICAgICAgICBsZWdlbmQudGl0bGU9IkZlYXR1cmUgU2VsZWN0aW9uIikrZm9udF9zaXplKGF4aXNfdGl0bGUueD0xNCwgYXhpc190aXRsZS55PTE0LGxhYmVscy54PTEyLCBsYWJlbHMueT0xMikreWxpbSgwLjM1LCAwLjY1KSt0aGVtZShsZWdlbmQucG9zaXRpb249InRvcCIpDQpgYGANCg0KIyMjIyBGZWF0dXJlIFNlbGVjdCBYIFN1YnNhbXBsaW5nIHstfQ0KDQpgYGB7ciBpbnRlcmFjdGlvbjN9DQpwbG90X21vZGVsKGF1Y21vZDQsIHR5cGU9InByZWQiLCB0ZXJtcz1jKCJzdWJzYW1wbGluZyIsImZlYXR1cmVfc2VsZWN0aW9uIiksIGxpbmUuc2l6ZSA9IDEuNSwgdGl0bGU9IiIsIGF4aXMudGl0bGU9YygiU3Vic2FtcGxpbmcgU3RyYXRlZ3kiLCAiQVVDIiksDQogICAgICAgICAgIGxlZ2VuZC50aXRsZT0iRmVhdHVyZSBTZWxlY3Rpb24iKStmb250X3NpemUoYXhpc190aXRsZS54PTE0LCBheGlzX3RpdGxlLnk9MTQsbGFiZWxzLng9MTIsIGxhYmVscy55PTEyKSt5bGltKDAuMzUsIDAuNjUpK3RoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIikNCmBgYA0KDQojIyMjIFN1YnNhbXBsaW5nIFggQWxnb3JpdGhtIHstfQ0KDQpgYGB7ciBpbnRlcmFjdGlvbjR9DQpwbG90X21vZGVsKGF1Y21vZDQsIHR5cGU9InByZWQiLCB0ZXJtcz1jKCJhbGdvcml0aG0iLCAic3Vic2FtcGxpbmciKSwgbGluZS5zaXplID0gMS41LCB0aXRsZT0iIixheGlzLnRpdGxlPWMoIk1hY2hpbmUgTGVhcm5pbmcgQWxnb3JpdGhtIiwgIkFVQyIpLA0KICAgICAgICAgICBsZWdlbmQudGl0bGU9IlN1YnNhbXBsaW5nIFN0cmF0ZWd5IikrZm9udF9zaXplKGF4aXNfdGl0bGUueD0xNCwgYXhpc190aXRsZS55PTE0LGxhYmVscy54PTEyLCBsYWJlbHMueT0xMikreWxpbSgwLjM1LCAwLjY1KSt0aGVtZShsZWdlbmQucG9zaXRpb249InRvcCIpDQpgYGANCg0KIyMjIyBGZWF0dXJlIFNlbGVjdCBYIEFsZ29yaXRobSB7LX0NCg0KYGBge3IgaW50ZXJhY3Rpb241fQ0KcGxvdF9tb2RlbChhdWNtb2Q0LCB0eXBlPSJwcmVkIiwgdGVybXM9YygiYWxnb3JpdGhtIiwiZmVhdHVyZV9zZWxlY3Rpb24iKSwgbGluZS5zaXplID0gMS41LCB0aXRsZT0iIiwgYXhpcy50aXRsZT1jKCJNYWNoaW5lIExlYXJuaW5nIEFsZ29yaXRobSIsICJBVUMiKSwNCiAgICAgICAgICAgbGVnZW5kLnRpdGxlPSJGZWF0dXJlIFNlbGVjdGlvbiIpK2ZvbnRfc2l6ZShheGlzX3RpdGxlLng9MTQsIGF4aXNfdGl0bGUueT0xNCxsYWJlbHMueD0xMiwgbGFiZWxzLnk9MTIpK3lsaW0oMC4zNSwgMC42NSkgK3RoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIikNCg0KYGBgDQoNCi0tLQ0KICANCiMgRXh0ZW5kaW5nIG91ciBhcHByb2FjaCB0byBvdGhlciBwcm9ibGVtcw0KDQpXZSBwcm9wb3NlZCBhIHBvdGVudGlhbCBmcmFtZXdvcmsgZm9yIHN0YXRpc3RpY2FsIGV4cGVyaW1lbnRzIHdpdGhpbiB0aGUgS0RETSBwcm9jZXNzLiBUaGUgZm9sbG93aW5nIGZsb3djaGFydCBzaG93cyBhbiBvdmVydmlldyBvZiBob3cgZXhwZXJpbWVudGFsIGRlc2lnbiBjYW4gYmUgdXNlZCB0byBpbmZvcm0gdGhlIGRlY2lzaW9ucyBtYWRlIHdpdGhpbiB0aGUgS0RETSBwcm9jZXNzLiBIZXJlLCBBVVBSQyBpcyB0aGUgYXJlYSB1bmRlciB0aGUgcHJlY2lzaW9uLXJlY2FsbCBjdXJ2ZS4gDQoNCg0KIVtBbiBvdmVydmlldyBvZiB0aGUgcHJvcG9zZWQgZnJhbWV3b3JrXShmbG93Y2hhcnQuanBnKXt3aWR0aD04NDAgaGVpZ2h0PTQ3M30NCg0KDQpgYGB7ciBpbmNsdWRlPUZBTFNFfQ0Kd2FybmluZ3MoKQ0KYGBgDQoNCi0tLQ0KICANCiMgUmVmZXJlbmNlcyB7LX0NCiAgDQogIDxkaXYgaWQ9InJlZnMiPjwvZGl2Pg0KICANCi0tLQ0KICA=