1 Introduction

1.1 Background

The Framingham Heart Study (FHS), backed by the National Institute of Health (NIH) is known as the longest running and most important epidemiological studies in the history of medical research. This landmark prospective research study centered around cross sectional data, following over ten thousand patients throughout the greater part of the past century.

Staring in 1948 the FHS study began by surveying five thousand (5,000) Framingham, Massachusetts residents, ages 30 through 60, regarding various aspects of their current lifestyle while conducting various physical health examinations. Patients were then followed, with regular re-examinations conducted every two years. As the life cycle of this research has progressed, additional examinations have been included in the FHS as well as enrollment of an additional five thousand (5,000) new patients directly related to the original FHS patient cohort. The FHS continues to evolve and seeks to provide a living and growing longitudinal dataset for a variety of health conditions and their risk factors, both behavioral and genetic.

1.2 Data Description and Details

A minor subset of the longitudinal FHS data was obtained from the Course Project Data Repository for use. At the time of utilization, this subset contained 4240 observations and 16 variables.

fhs_raw <- read.csv('https://nlepera.github.io/sta551/HW02/data/fhs_data.csv')

#link to variable descriptions https://pengdsci.github.io/datasets/FraminghamHeartStudy/FraminghamHeartStudy-description.pdf#

A full description of the variables can be obtained at the following link. A short form summary is included below for ease of reference. Numerical data is indicated with a bold [N], while categorical data is indicated with a bold [C]. Categorical variables are originally encoded with integer values for data storage and utilization considerations.

  • sex Gender of the pt. (Male = 1 | Non-Male = 0) [C]
  • age Age at time of pt. exam (years) [N]
  • education Level of pt’s education. (Some high school = 1 | High school / GED = 2 | Some college / vocational = 3 | College = 4) [C]
  • currentSmoker Smoker status at time of pt. exam (Smoker = 1 | Non-smoker = 2) [C]
  • cigsPerDay Number of cigarettes smoked per day at time of pt. exam [N]
  • BPmeds Pt medical history: blood pressure medicine / anti-hypertensive (Yes BPmeds = 1 | No BPmeds = 2) [C]
  • prevalentStroke Pt medical history: stroke (Yes stroke = 1 | No stroke = 0) [C]
  • prevalentHyp Pt medical history: hypertension (Yes hypertension = 1 | No hypertension = 0) [C]
  • diabetes Pt medical history: diabetes (Yes diabetes = 1 | No diabetes = 2) [C]
  • totCol Total cholesterol (mg/dL) [N]
  • sysBP Systolic blood pressure (mmHg) [N]
  • diaDP Diastolic blood pressure (mmHg) [N]
  • BMI Body Mass Index (kg/m2) [N]
  • heart rate Heart rate (beats/minute) [N]
  • glucose Blood glucose level (mg/dL) [N]
  • TenYearCHD Calculated 10 year risk of coronary heart disease (At Risk = 1 / Not at risk = 0) [C]

1.2.1 Summary Statistics

A brief summary of the raw data set (fhs_raw) is included below for reference. Categorical variables have not yet been transformed and are still represented as integer values.

summary(fhs_raw)
      male             age          education     currentSmoker   
 Min.   :0.0000   Min.   :32.00   Min.   :1.000   Min.   :0.0000  
 1st Qu.:0.0000   1st Qu.:42.00   1st Qu.:1.000   1st Qu.:0.0000  
 Median :0.0000   Median :49.00   Median :2.000   Median :0.0000  
 Mean   :0.4292   Mean   :49.58   Mean   :1.979   Mean   :0.4941  
 3rd Qu.:1.0000   3rd Qu.:56.00   3rd Qu.:3.000   3rd Qu.:1.0000  
 Max.   :1.0000   Max.   :70.00   Max.   :4.000   Max.   :1.0000  
                                  NA's   :105                     
   cigsPerDay         BPMeds        prevalentStroke     prevalentHyp   
 Min.   : 0.000   Min.   :0.00000   Min.   :0.000000   Min.   :0.0000  
 1st Qu.: 0.000   1st Qu.:0.00000   1st Qu.:0.000000   1st Qu.:0.0000  
 Median : 0.000   Median :0.00000   Median :0.000000   Median :0.0000  
 Mean   : 9.006   Mean   :0.02962   Mean   :0.005896   Mean   :0.3106  
 3rd Qu.:20.000   3rd Qu.:0.00000   3rd Qu.:0.000000   3rd Qu.:1.0000  
 Max.   :70.000   Max.   :1.00000   Max.   :1.000000   Max.   :1.0000  
 NA's   :29       NA's   :53                                           
    diabetes          totChol          sysBP           diaBP      
 Min.   :0.00000   Min.   :107.0   Min.   : 83.5   Min.   : 48.0  
 1st Qu.:0.00000   1st Qu.:206.0   1st Qu.:117.0   1st Qu.: 75.0  
 Median :0.00000   Median :234.0   Median :128.0   Median : 82.0  
 Mean   :0.02571   Mean   :236.7   Mean   :132.4   Mean   : 82.9  
 3rd Qu.:0.00000   3rd Qu.:263.0   3rd Qu.:144.0   3rd Qu.: 90.0  
 Max.   :1.00000   Max.   :696.0   Max.   :295.0   Max.   :142.5  
                   NA's   :50                                     
      BMI          heartRate         glucose         TenYearCHD    
 Min.   :15.54   Min.   : 44.00   Min.   : 40.00   Min.   :0.0000  
 1st Qu.:23.07   1st Qu.: 68.00   1st Qu.: 71.00   1st Qu.:0.0000  
 Median :25.40   Median : 75.00   Median : 78.00   Median :0.0000  
 Mean   :25.80   Mean   : 75.88   Mean   : 81.96   Mean   :0.1519  
 3rd Qu.:28.04   3rd Qu.: 83.00   3rd Qu.: 87.00   3rd Qu.:0.0000  
 Max.   :56.80   Max.   :143.00   Max.   :394.00   Max.   :1.0000  
 NA's   :19      NA's   :1        NA's   :388                      
str(fhs_raw)
'data.frame':   4240 obs. of  16 variables:
 $ male           : int  1 0 1 0 0 0 0 0 1 1 ...
 $ age            : int  39 46 48 61 46 43 63 45 52 43 ...
 $ education      : int  4 2 1 3 3 2 1 2 1 1 ...
 $ currentSmoker  : int  0 0 1 1 1 0 0 1 0 1 ...
 $ cigsPerDay     : int  0 0 20 30 23 0 0 20 0 30 ...
 $ BPMeds         : int  0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentStroke: int  0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentHyp   : int  0 0 0 1 0 1 0 0 1 1 ...
 $ diabetes       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ totChol        : int  195 250 245 225 285 228 205 313 260 225 ...
 $ sysBP          : num  106 121 128 150 130 ...
 $ diaBP          : num  70 81 80 95 84 110 71 71 89 107 ...
 $ BMI            : num  27 28.7 25.3 28.6 23.1 ...
 $ heartRate      : int  80 95 75 65 85 77 60 79 76 93 ...
 $ glucose        : int  77 76 70 103 85 99 85 78 79 88 ...
 $ TenYearCHD     : int  0 0 0 1 0 0 1 0 0 0 ...

2 Methodology

In order to determine if easily measurable patient health measures such as glucose levels and total cholesterol levels can act as predictor values for more salient health measures such as patient BMI and patient 10 year CHD risk diagnosis, multiple regression models were created utilizing the fhs subset data. Before any data could be analyzed or models built, feature variables were engineered to improve analysis capabilities and all missing values were imputed using the mice package.

The feasibility of utilizing glucose and total cholesterol levels as BMI predictors was explored first. Multiple linear regression models were built and subsequently tested with 5-fold validation, comparing the mean square error (MSE) for each validation loop. Once a final best fit model with the lowest average MSE and least variation across all rounds of validation was selected, the selected prediciton model was utilized to complete test predictions based on hypothetical patient values.

Secondly the feasibility of utilizing glucose and total cholesterol levels as 10 year CHD risk diagnosis predictors was explored. Two logistic regression models were built and subsequently compared utilizing their Receiving Operating Characteristic (ROC) curves and calculated area under the curve (AUC) values. As a part of the model comparison, best fit cutoff values, \(\a\) (type 1 error rates), and \(\b\) (type 2 error rates) were calculated to determine the point of model best fit.

3 EDA and Feature Engineering

3.1 Feature Engineering

3.1.1 Categorical Variable Transformation

In order to prevent categorical values stored as integers from being incorporated into analysis of true numerical variables, the following variables were transformed from integer to logical values.

  • male
  • currentSmoker
  • BPMeds
  • prevalentStroke
  • prevalentHyp
  • diabetes
  • TenYearCHD

Additionally, the education variable was transformed from an integer to a factor, using the following replacement values:

  • education = 1 -> “SomeHS”
  • education = 2 -> “HS-GED”
  • education = 3 -> “SomeCol-Voc”
  • education = 4 -> “College”

A summary of the transformed variables is included to illustrate the transformation and to capture the overall retention of all variables and missing values.

fhs <- fhs_raw

fhs$male <- as.logical(as.integer(fhs$male))
fhs$currentSmoker <- as.logical(as.integer(fhs$currentSmoker))
fhs$BPMeds <- as.logical(as.integer(fhs$BPMeds))
fhs$prevalentStroke <- as.logical(as.integer(fhs$prevalentStroke))
fhs$prevalentHyp <- as.logical(as.integer(fhs$prevalentHyp))
fhs$diabetes <- as.logical(as.integer(fhs$diabetes))
fhs$TenYearCHD <- as.logical(as.integer(fhs$TenYearCHD))
fhs$education <- factor(fhs$education,
                        levels = c(1:4),
                        labels = c("SomeHS", "HS-GED", "SomeCol-Voc", "College"))
str(fhs)
'data.frame':   4240 obs. of  16 variables:
 $ male           : logi  TRUE FALSE TRUE FALSE FALSE FALSE ...
 $ age            : int  39 46 48 61 46 43 63 45 52 43 ...
 $ education      : Factor w/ 4 levels "SomeHS","HS-GED",..: 4 2 1 3 3 2 1 2 1 1 ...
 $ currentSmoker  : logi  FALSE FALSE TRUE TRUE TRUE FALSE ...
 $ cigsPerDay     : int  0 0 20 30 23 0 0 20 0 30 ...
 $ BPMeds         : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ prevalentStroke: logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ prevalentHyp   : logi  FALSE FALSE FALSE TRUE FALSE TRUE ...
 $ diabetes       : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ totChol        : int  195 250 245 225 285 228 205 313 260 225 ...
 $ sysBP          : num  106 121 128 150 130 ...
 $ diaBP          : num  70 81 80 95 84 110 71 71 89 107 ...
 $ BMI            : num  27 28.7 25.3 28.6 23.1 ...
 $ heartRate      : int  80 95 75 65 85 77 60 79 76 93 ...
 $ glucose        : int  77 76 70 103 85 99 85 78 79 88 ...
 $ TenYearCHD     : logi  FALSE FALSE FALSE TRUE FALSE FALSE ...

3.1.2 prevalentCond (Prevalent Conditions) Feature Variable

In order to reduce the number of categorical variables for analysis, the variables prevalentHyp and prevalentStroke were combined to create a new feature variable entitled prevalentCond. These variables were able to be combined into a single feature variable, as they were both binary categorical values measuring past patient medical history. This new feature variable prevalentCond serves to record patient medical history for both hypertension and stroke, utilizing the following potential values:

  • Hyp: Indicates the patient has a history of Hypertension only
  • HypStroke: Indicates the patient has a history of both Hypertension and Stroke
  • None: Indicates the patient does not have a history of Hypertension or Stroke
  • Stroke: Indicates the patient has a history of Stroke only

Provisions for missing values were included, but as will be discussed in later sections, neither prevalentHyp nor prevalentStroke have missing values.

fhs$prevalentCond <- NA

for (i in 1:nrow(fhs)) {
  if (fhs$prevalentHyp[i] == TRUE && fhs$prevalentStroke[i] == FALSE) {
    fhs$prevalentCond[i] <-  "Hyp"
  }
  else if (fhs$prevalentHyp[i] == TRUE && fhs$prevalentStroke[i] == TRUE) {
    fhs$prevalentCond[i] <-  "HypStroke"
  }
  else if (fhs$prevalentHyp[i] == FALSE && fhs$prevalentStroke[i] == FALSE) {
    fhs$prevalentCond[i] <-  "None"
  }
  else if (fhs$prevalentHyp[i] == FALSE && fhs$prevalentStroke[i] == TRUE) {
    fhs$prevalentCond[i] <-  "Stroke"
  }
  else if (fhs$prevalentHyp[i] == "" || fhs$prevalentStroke[i] == "") {
    fhs$prevalentCond[i] <- "Missing-Val"
  } 
  else fhs$prevalentCond[i] <- "Error"
}

In order to allow for proper handing in later logistic regression modeling, the characher variable categories as assigned were converted to factor values. A quick summary check of the newly created feature variable illustrates that all 4240 observations were retained, and no values are missing.

fhs$prevalentCond <- as.factor(as.character(fhs$prevalentCond))
str(fhs$prevalentCond)
 Factor w/ 4 levels "Hyp","HypStroke",..: 3 3 3 1 3 1 3 3 1 1 ...
summary(fhs$prevalentCond)
      Hyp HypStroke      None    Stroke 
     1298        19      2917         6 

3.1.3 highBP (High Blood Pressure) Feature Variable

In order to reduce the number of categorical variables for analysis, the variables sysBP (systolic blood pressure) and diaBP (diastolic blood pressure) were binned and combined to create a new feature variable entitled highBP. The parameters to bin and combine both systolic and diastolic blood pressure readings into a single categorical variable describing the patient’s high blood pressure status were obtained from the American Heart Association at the following link

fhs$highBP <- NA

for (i in 1:nrow(fhs)) {
  if (fhs$sysBP[i] < 120 && fhs$diaBP[i] < 80) {
    fhs$highBP[i] <-  "Normal"
  }
  else if (fhs$sysBP[i] < 130 && fhs$diaBP[i] < 80) {
    fhs$highBP[i] <-  "Elevated"
  }
    else if (fhs$sysBP[i] < 140 | fhs$diaBP[i] < 90) {
    fhs$highBP[i] <-  "HBP1"
  }
  else if (fhs$sysBP[i] >= 140 | fhs$diaBP[i] >= 90) {
    fhs$highBP[i] <-  "HBP2"
  }
  else if (fhs$sysBP[i] >= 180 | fhs$diaBP[i] >= 120) {
    fhs$highBP[i] <-  "Crisis"
  }
}

In order to allow for proper handing in later logistic regression modeling, the character variable categories as assigned were converted to factor values.

fhs$highBP <- as.factor(as.character(fhs$highBP))

str(fhs)
'data.frame':   4240 obs. of  18 variables:
 $ male           : logi  TRUE FALSE TRUE FALSE FALSE FALSE ...
 $ age            : int  39 46 48 61 46 43 63 45 52 43 ...
 $ education      : Factor w/ 4 levels "SomeHS","HS-GED",..: 4 2 1 3 3 2 1 2 1 1 ...
 $ currentSmoker  : logi  FALSE FALSE TRUE TRUE TRUE FALSE ...
 $ cigsPerDay     : int  0 0 20 30 23 0 0 20 0 30 ...
 $ BPMeds         : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ prevalentStroke: logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ prevalentHyp   : logi  FALSE FALSE FALSE TRUE FALSE TRUE ...
 $ diabetes       : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ totChol        : int  195 250 245 225 285 228 205 313 260 225 ...
 $ sysBP          : num  106 121 128 150 130 ...
 $ diaBP          : num  70 81 80 95 84 110 71 71 89 107 ...
 $ BMI            : num  27 28.7 25.3 28.6 23.1 ...
 $ heartRate      : int  80 95 75 65 85 77 60 79 76 93 ...
 $ glucose        : int  77 76 70 103 85 99 85 78 79 88 ...
 $ TenYearCHD     : logi  FALSE FALSE FALSE TRUE FALSE FALSE ...
 $ prevalentCond  : Factor w/ 4 levels "Hyp","HypStroke",..: 3 3 3 1 3 1 3 3 1 1 ...
 $ highBP         : Factor w/ 4 levels "Elevated","HBP1",..: 4 2 2 3 2 3 2 4 2 3 ...

3.2 Identifying and Handling Missing Values

Prior to utilizing the FHS data in any capacity, an understanding of the missing values for each category was obtained. A total count of missing values per variable as well as a total percentage of missing values for each variable was calculated.

3.2.1 Identifying Scope of Missing Values

Count of missing values per variable
sapply(fhs, function (x) sum(is.na(x))) #count of missing
           male             age       education   currentSmoker      cigsPerDay 
              0               0             105               0              29 
         BPMeds prevalentStroke    prevalentHyp        diabetes         totChol 
             53               0               0               0              50 
          sysBP           diaBP             BMI       heartRate         glucose 
              0               0              19               1             388 
     TenYearCHD   prevalentCond          highBP 
              0               0               0 
Percentage of missing values per variable
sapply(fhs, function (x) round((mean(is.na(x))*100), 3)) #percent of total missing
           male             age       education   currentSmoker      cigsPerDay 
          0.000           0.000           2.476           0.000           0.684 
         BPMeds prevalentStroke    prevalentHyp        diabetes         totChol 
          1.250           0.000           0.000           0.000           1.179 
          sysBP           diaBP             BMI       heartRate         glucose 
          0.000           0.000           0.448           0.024           9.151 
     TenYearCHD   prevalentCond          highBP 
          0.000           0.000           0.000 
Pattern of missing values

The overall structure and pattern of missing values is represented in the below table to allow for trend visualization. Each column of the below plot represents a variable from the fhs_raw dataset. Each row represents a different pattern of present vs. missing data for each variable. Blue filled cells indicate a data point is available for that variable in the referenced pattern, while red squares indicate a missing value for that variable in the referenced pattern. For example, the 2nd to the top row of the pattern table includes blue cells for all variables, except the red cell in the glucose column, indicating the only missing value for that pattern is within the glucose variable.

The numeric values on the left hand side represent the frequency in which that pattern of present vs. missing data is seen in the dataset fhs_raw. The right hand values indicate the total number of missing values in that data pattern. For example, the top most row has a left hand value of 3658, and a right hand value of zero (0), indicating there are 3658 observations in which there are zero (0) missing values. The 2nd to top row has a left hand missing value of 331, a right hand value of one (1), and a red cell in the glucose variable, indicating there are 331 observations with one (1) missing value, specifically in the glucose variable.

md.pattern(fhs, rotate.names = TRUE)

     male age currentSmoker prevalentStroke prevalentHyp diabetes sysBP diaBP
3658    1   1             1               1            1        1     1     1
331     1   1             1               1            1        1     1     1
93      1   1             1               1            1        1     1     1
8       1   1             1               1            1        1     1     1
51      1   1             1               1            1        1     1     1
1       1   1             1               1            1        1     1     1
9       1   1             1               1            1        1     1     1
38      1   1             1               1            1        1     1     1
1       1   1             1               1            1        1     1     1
1       1   1             1               1            1        1     1     1
23      1   1             1               1            1        1     1     1
4       1   1             1               1            1        1     1     1
2       1   1             1               1            1        1     1     1
13      1   1             1               1            1        1     1     1
4       1   1             1               1            1        1     1     1
1       1   1             1               1            1        1     1     1
1       1   1             1               1            1        1     1     1
1       1   1             1               1            1        1     1     1
        0   0             0               0            0        0     0     0
     TenYearCHD prevalentCond highBP heartRate BMI cigsPerDay totChol BPMeds
3658          1             1      1         1   1          1       1      1
331           1             1      1         1   1          1       1      1
93            1             1      1         1   1          1       1      1
8             1             1      1         1   1          1       1      1
51            1             1      1         1   1          1       1      0
1             1             1      1         1   1          1       1      0
9             1             1      1         1   1          1       0      1
38            1             1      1         1   1          1       0      1
1             1             1      1         1   1          1       0      1
1             1             1      1         1   1          1       0      0
23            1             1      1         1   1          0       1      1
4             1             1      1         1   1          0       1      1
2             1             1      1         1   1          0       1      1
13            1             1      1         1   0          1       1      1
4             1             1      1         1   0          1       1      1
1             1             1      1         1   0          1       1      1
1             1             1      1         1   0          1       0      1
1             1             1      1         0   1          1       1      1
              0             0      0         1  19         29      50     53
     education glucose    
3658         1       1   0
331          1       0   1
93           0       1   1
8            0       0   2
51           1       1   1
1            1       0   2
9            1       1   1
38           1       0   2
1            0       1   2
1            1       0   3
23           1       1   1
4            1       0   2
2            0       1   2
13           1       1   1
4            1       0   2
1            0       1   2
1            1       0   3
1            1       1   1
           105     388 645

A total of 18 unique patterns of present vs. missing data combinations are observed in the FHS subset, fhs_raw. The most frequently seen pattern is all values present, the second most frequent pattern is 1 missing value in the glucose variable, and the most frequent pattern with more than one missing value is a combination of both missing totChol and glucose values, as seen in thirty eight (38) instances.

3.2.2 Imputing the Data

As the variable with the largest total percentage of blank values (9.151% missing) was glucose, an imputation model was built around the glucose variable to create the best possible fit. For this imputation model fitting, the fhs data was subset for only numerical categories to allow for ease of analysis and simple prediction utilizing the MICE imputation package. Once this subset (fhs_num) was created, three different MICE imputation models were created: Predictive mean matching (PMM) Imputation, Classification and Regression Trees (CART) Imputation, and Lasso Linear Regression Imputation.

fhs_num <- fhs %>% 
  dplyr::select(age, cigsPerDay, totChol, sysBP, diaBP, BMI, heartRate, glucose)

set.seed(123) #set seed to maintain consistency with imputation

fhs_imp <- data.frame(
  original = fhs_num$glucose,
  imputed_pmm = complete(mice(fhs_num, method = "pmm", print = FALSE))$glucose,
  imputed_cart = complete(mice(fhs_num, method = "cart", print = FALSE))$glucose,
  imputed_lasso = complete(mice(fhs_num, method = "lasso.norm", print = FALSE))$glucose
)

To determine the best fit of the three MICE imputation models tested on the glucose variable, the original glucose distribution with missing values omitted was plotted on a histogram to visualize the distribution, density, and mean value. The three imputation models were plotted with the same configuration and axis scales to allow for ease of visual comparison. This allows for visual comparison of the distribution of the original glucose variable data, compared to the imputed glucose data. Additionally, the mean glucose value can be compared between the original variable data, and the imputed glucose data.

y_lim_imp <- range(0,0.04)
imp.orig <- ggplot(fhs_imp, aes(x=original)) + 
  geom_histogram(aes(y = ..density..), fill = "orange", alpha = 0.25, color = "orange") +
  geom_density(aes(x = original), color = "darkorange4") +   
  ylim(y_lim_imp) +
  geom_vline(aes(xintercept = mean(original, na.rm = TRUE)), color="darkorange4", size = 0.5) +
  geom_text(aes(x=mean(original, na.rm = TRUE), label = round(mean(original, na.rm = TRUE), 2)), y= 0.03, x = 120, angle = 0)
  
imp.pmm <- ggplot(fhs_imp, aes(x = imputed_pmm)) + 
  geom_histogram(aes(y=..density..), fill = "blue", alpha = 0.25, color = "blue") +
  geom_density(aes(x=imputed_pmm), color = "darkblue") +   
  ylim(y_lim_imp) +
  geom_vline(aes(xintercept = mean(imputed_pmm)), color="darkblue", size = 0.5) +
  geom_text(aes(x=mean(imputed_pmm, na.rm = TRUE), label = round(mean(imputed_pmm, na.rm = TRUE), 2)), y= 0.03, x = 120, angle = 0)

imp.cart <- ggplot(fhs_imp, aes(x = imputed_cart)) + 
  geom_histogram(aes(y=..density..), fill = "red", alpha = 0.25, color = "red") +
  geom_density(aes(x=imputed_cart), color = "darkred") +   
  ylim(y_lim_imp) +
  geom_vline(aes(xintercept = mean(imputed_cart)), color="darkred", size = 0.5) +
  geom_text(aes(x=mean(imputed_cart, na.rm = TRUE), label = round(mean(imputed_cart, na.rm = TRUE), 2)), y= 0.03, x = 120, angle = 0)

imp.lasso <- ggplot(fhs_imp, aes(x = imputed_lasso)) + 
  geom_histogram(aes(y = ..density..), fill = "magenta", alpha = 0.25, color = "magenta") +
  geom_density(aes(x=imputed_lasso), color = "darkmagenta") +   
  ylim(y_lim_imp) +
  geom_vline(aes(xintercept = mean(imputed_lasso)), color="darkmagenta", size = 0.5) +
  geom_text(aes(x=mean(imputed_lasso, na.rm = TRUE), label = round(mean(imputed_lasso, na.rm = TRUE), 2)), y= 0.03, x = 120, angle = 0)



grid.arrange(imp.orig, imp.pmm, imp.cart, imp.lasso, nrow = 2, ncol = 2, top = textGrob("Comparrison of Glucose Imputation Models"))

Upon reviewing the various imputed glucose data outputs of the above MICE imputation, the CART imputation was determined as the best fit for the imputation model. As visible in the above histogram data, the CART imputation model is the only model that retained the original glucose data mean, with a near identical distribution and density.

As the remaining variables with missing values contain less than 2.5% missing data, compared to the 9.151% missing data from the glucose variable, the best fit CART imputation is applied to all variables in the fhs dataset. To note, the imputation was completed on all variables for this step, not just the numeric variable subset utilized for imputation model selection.

The newly imputed dataset (fhs_imp2) was then verified to contain zero missing values

set.seed(123)
fhs_imp2 <- fhs
fhs_imp2 <- complete(mice(fhs, method = "cart", print = FALSE))
fhs_imp2$BPMeds <- as.logical(as.numeric(fhs_imp2$BPMeds))
sapply(fhs_imp2, function (x) sum(is.na(x)))
           male             age       education   currentSmoker      cigsPerDay 
              0               0               0               0               0 
         BPMeds prevalentStroke    prevalentHyp        diabetes         totChol 
              0               0               0               0               0 
          sysBP           diaBP             BMI       heartRate         glucose 
              0               0               0               0               0 
     TenYearCHD   prevalentCond          highBP 
              0               0               0 
summary(fhs_imp2)
    male              age              education    currentSmoker  
 Mode :logical   Min.   :32.00   SomeHS     :1758   Mode :logical  
 FALSE:2420      1st Qu.:42.00   HS-GED     :1283   FALSE:2145     
 TRUE :1820      Median :49.00   SomeCol-Voc: 711   TRUE :2095     
                 Mean   :49.58   College    : 488                  
                 3rd Qu.:56.00                                     
                 Max.   :70.00                                     
   cigsPerDay       BPMeds        prevalentStroke prevalentHyp   
 Min.   : 0.000   Mode :logical   Mode :logical   Mode :logical  
 1st Qu.: 0.000   FALSE:4114      FALSE:4215      FALSE:2923     
 Median : 0.000   TRUE :126       TRUE :25        TRUE :1317     
 Mean   : 9.084                                                  
 3rd Qu.:20.000                                                  
 Max.   :70.000                                                  
  diabetes          totChol          sysBP           diaBP      
 Mode :logical   Min.   :107.0   Min.   : 83.5   Min.   : 48.0  
 FALSE:4131      1st Qu.:206.0   1st Qu.:117.0   1st Qu.: 75.0  
 TRUE :109       Median :234.0   Median :128.0   Median : 82.0  
                 Mean   :236.8   Mean   :132.4   Mean   : 82.9  
                 3rd Qu.:263.0   3rd Qu.:144.0   3rd Qu.: 90.0  
                 Max.   :696.0   Max.   :295.0   Max.   :142.5  
      BMI          heartRate         glucose       TenYearCHD     
 Min.   :15.54   Min.   : 44.00   Min.   : 40.00   Mode :logical  
 1st Qu.:23.07   1st Qu.: 68.00   1st Qu.: 71.00   FALSE:3596     
 Median :25.40   Median : 75.00   Median : 78.00   TRUE :644      
 Mean   :25.80   Mean   : 75.88   Mean   : 81.87                  
 3rd Qu.:28.04   3rd Qu.: 83.00   3rd Qu.: 87.00                  
 Max.   :56.80   Max.   :143.00   Max.   :394.00                  
   prevalentCond       highBP    
 Hyp      :1298   Elevated: 410  
 HypStroke:  19   HBP1    :1965  
 None     :2917   HBP2    : 832  
 Stroke   :   6   Normal  :1033  
                                 
                                 

3.3 Distribution Review and Pairwise Analysis

3.3.1 Categorical Variables

The distributions and frequencies of the categorical variables (both feature variables and native variables) highBP, prevalentCond, TenYearCHD, BPMeds, diabetes, and male were visualized utilizing bar charts

y_lim <- range(0,4200)
highbp.den <- ggplot(fhs_imp2, aes(x = highBP)) +
  geom_bar(fill = "lightblue") +  
  ylim(y_lim) +
  labs(title = "High Blood Pressure
       ")
prevcond.den <- ggplot(fhs_imp2, aes(x = prevalentCond)) + 
  ylim(y_lim) +
  geom_bar(fill = "lightblue") + 
  labs(title = "Prev. Conditions
(Stroke / Hypertension / Both)") 
tenyear.den <- ggplot(fhs_imp2, aes(x = TenYearCHD)) + 
  ylim(y_lim) +
  geom_bar(fill = "lightblue") +  
  labs(title = "Chronic Heart Disease 
  @ 10 Year Follow Up")
BPmed.den <- ggplot(fhs_imp2, aes(x = BPMeds)) + 
  ylim(y_lim) +
  geom_bar(fill = "lightblue") +  
  labs(title = "Blood Pressure Medicine
       ")

diabet.den <- ggplot(fhs_imp2, aes(x = diabetes)) + 
  ylim(y_lim) +
  geom_bar(fill = "lightblue") +  
  labs(title = "Diabetes
       ")

male.den <- ggplot(fhs_imp2, aes(x = male)) + 
  ylim(y_lim) +
  geom_bar(fill = "lightblue") +  
  labs(title = "Male
       ")

grid.arrange(highbp.den, prevcond.den, tenyear.den, BPmed.den, diabet.den, male.den, nrow = 2, ncol = 3)

age.den <- ggplot(fhs_imp2, aes(x = age)) +
  geom_histogram(aes(y = ..density..), fill = "lightblue") +
  geom_density(aes (x = age), color = "darkblue") +
  labs(title = "Age")

cigs.den <- ggplot(fhs_imp2, aes(x = cigsPerDay)) +
  geom_histogram(aes(y = ..density..), fill = "lightblue") +
  geom_density(aes (x = cigsPerDay), color = "darkblue") +
  labs(title = "Num. Cigs Smoked Per Day")

chol.den <- ggplot(fhs_imp2, aes(x = totChol)) +
  geom_histogram(aes(y = ..density..), fill = "lightblue") +
  geom_density(aes (x = totChol), color = "darkblue") +
  labs(title = "Total Cholesterol")

glu.den <- ggplot(fhs_imp2, aes(x = glucose)) +
  geom_histogram(aes(y = ..density..), fill = "lightblue") +
  geom_density(aes (x = glucose), color = "darkblue") +
  labs(title = "Glucose")

bp.den <- ggplot(fhs_imp2) +
  geom_histogram(aes(x=sysBP, y=..density.., fill = "Systolic" ), alpha = 0.75) +
  geom_histogram(aes(x=diaBP, y=..density.., fill = "Diastolic"), alpha = 0.75) +
  geom_density(aes(x = sysBP, y = ..density..), color = "darkgreen") +
  geom_density(aes(x = diaBP, y = ..density..), color = "darkblue") +
  labs(title = "Systolic & Diastolic Blood Pressure", fill = "Blood Pressure 
       Values", x = "Blood Pressure Reading (mmHG)") +
  scale_fill_manual(values = c("lightblue", "lightgreen"))

bmi.den <- ggplot(fhs_imp2, aes(x = BMI)) +
  geom_histogram(aes(y = ..density..), fill = "lightblue") +
  geom_density(aes (x = BMI), color = "darkblue") +
  labs(title = "Body Mass Index (BMI)")

hr.den <- ggplot(fhs_imp2, aes(x = heartRate)) +
  geom_histogram(aes(y = ..density..), fill = "lightblue") +
  geom_density(aes (x = heartRate), color = "darkblue") +
  labs(title = "Heart Rate")

3.4 A cursory review of the distribution and density data for all numerical variables reveals multiple non-normal distributions.

grid.arrange(age.den, cigs.den, chol.den, glu.den, bmi.den, hr.den, nrow = 2, ncol = 3)

grid.arrange(bp.den, nrow = 1, ncol = 2)

In order to address the practical question of assessing the impact of various health measures on patient Body Mass Index (BMI) value, only clinically related measures will be retained for further analysis.

  • Systolic sysBP and diastolic diaBP variables were combined into a single categorical feature variable highBP, both of these variables will be omitted from further analysis

  • The Body Mass Index (BMI) scale already accounts for age in BMI calculation, so the age variable will be omitted from further analysis

  • Total number of cigarettes smoked per day (cigsPerDay) is not clinically linked to BMI and will thus be omitted from further analysis. While cigarette consumption may impact cardiovascular health or other health indicators, there is no clinical significance to BMI.

  • Total cholesterol totChol is a common measure of cardiovascular fitness. As cardiovascular fitness rates are often associated with lower BMI values, total cholesterol will be included in further analysis to examine if total cholesterol values are a good predictor value for BMI.

  • Glucose glucose levels are often clinically related to weight related disorders, such as type-II diabetes. As weight is clinically associated with higher BMI values, the glucose data will be included in further analysis to examine if glucose is a good predictor value for BMI.

ggpairs(fhs_imp2, columns = c(10,13:15), aes(alpha = 0.05), lower = list(continuous = wrap("smooth", method = "lm", se = FALSE, colour = "hotpink", alpha = 0.25)), upper = list(continuous = wrap("cor")))

In reviewing the pairwise analysis, the following patterns were identified:

  • Both glucose (glucose) and total cholesterol (totChol) are linearly correlated with the response variable Body Mass Index (BMI)
  • glucose and totChol are not linearly correlated, indicating there will be no co-linearity issues when comparing with a linear regression.
  • Minimal outliers identified for potential removal, and no significantly skewed distributions seen.

4 Linear Regression

Linear regression can be utilized to assess correlation between the dependent variable, Body Mass Index (BMI), and a variety of predictor variables. Multiple linear regression models utilizing clinically associated predictor variables were created and analyzed for their respective goodness of fit using R2 values.

The following assumptions have been met to support linear regression:

  • Predictor variables are linearly correlated to the response variable
  • No serious colinearity identified between predictor variables
  • Response variable is normally distributed
  • Response variable variance is constant
  • No missing predictor variables

4.1 Creating Multiple Candidate Models

To start, two linear regression models utilizing totChol and glucose predictor variables with a BMI as a response variable were created. The first model compares BMI with total cholesterol (totChol), glucose (glucose), and heart rate (heartRate) in their native formats, while the second model compares the log transformation of BMI with with total cholesterol (totChol) and glucose (glucose) in their native formats.

hr.chol.glu <- (lm(BMI ~ heartRate + totChol + glucose, data = fhs_imp2))

4.1.1 Initial Model (hr.glu.chol)

The below pander table indicates that totChol has the regression coefficient with the most statistical significance (p-value < 0.05 indicates significantly different from zero (0) and p-value|totChol = 1.251e-12 indicating strong statistic significance). With a regression coefficient of 0.009918, this indicates that for each increase of 1 mg/dL in a patient’s total cholesterol value, the BMI will increase by 0.009918. Additionally, the second most significant variable was identified as glucose, with a p-value|glucose = 6.6e-5 with a regression coefficient of 0.01182. This indicates that for every 1 mg/dL increase in patient glucose, BMI will increase 0.01182. Finally the third most significant variable was identified as heart rate, with a p-value|heartrate = 0.0008007 indicating statistical significance. With a regression coefficient of 0.01724, an increase of 1 bpm in a patient’s heart rate is associated with an 0.01724 increase in BMI.

An overall R2 value of 0.0217795 indicates that 2.14795% of the variance in BMI data is associated with the combination of glucose, heart rate, and total cholesterol levels.

In viewing the residual plots the following information is obtained regarding the initial linear regression model (hr.glu.chol):

  • Three observations exist as outliers (OBS # 79, 2658, & 3928)
  • There is a minor violation of the assumption of constant variance, with both the Residuals vs Fitted & Q-Q residuals graphs indicating near constant variance, with outliers.
  • There is a moderate violation of the assumption of normal distribution of the residuals as evident by the shape of the Q-Q residuals plot.
pander::pander(summary(hr.chol.glu)$coef, caption = "Summary statistics of the regression coefficients of the initial 3 predictor variable model")
Summary statistics of the regression coefficients of the initial 3 predictor variable model
  Estimate Std. Error t value Pr(>|t|)
(Intercept) 21.19 0.5223 40.56 3.32e-304
heartRate 0.01737 0.005193 3.345 0.0008295
totChol 0.009883 0.001397 7.075 1.738e-12
glucose 0.01168 0.002621 4.457 8.514e-06
kable(summary(hr.chol.glu)$r.squared, caption = "R^2^ value of Intial Model")
R2 value of Intial Model
x
0.0211883
par(mfrow=c(2,2), mar=c(2,3,2,2))
plot(hr.chol.glu)

4.1.2 Second Model (glu.chol)

Before completing a log transformation, a Box-Cox transformation to identify a potential power transformation of the response variable, BMI, was completed. Utilizing the 95% confidence interval, it was determined that the natural log is not the best transformation for this regression model. This output occurs when the dependent variable, BMI is not normally distributed. The difference in actual BMI distribution and the hypothetical BMI normal distribution utilizing the real BMI standard deviation and means values are visible in the distribution plot below.

bc.bmi <- boxcox(BMI ~ totChol + glucose,
       data = fhs_imp2,
       lambda = seq(-1, 1, length = 20),
       xlab = expression(paste(lambda)))
title(main = 'Box-Cox Transformation: 95% CI of lambda')

bmi.den + 
  geom_line(stat = "density", aes(x = BMI, color = "BMI Density Distribution"), size = 0.7) +
  stat_function(fun = dnorm, args = list(mean=mean(fhs_imp2$BMI), sd=sd(fhs_imp2$BMI)), aes(color = "Standard Normal Curve"), size = 0.7, linetype = "dashed") + 
  scale_color_manual(values = c( "BMI Density Distribution" = "blue", "Standard Normal Curve" = "black")) + 
  labs( x = "BMI", y = "Density", title = "Distribution of BMI data vs Normal Distribution", color = "Legend")

As shown in the previous sections along with the 95% confidence interval from the Box-Cox analysis, the BMI data is near normally distributed, but not quite. In order to determine the best fit lambda value, x value associated with the the maximum y value from the box-cox transformation plot was utilized [-0.5959596].

[1] -0.5959596

Utilizing the calculated best fit \(\lambda\) value of -0.5959596, the following Box-Cox transformation was completed on the fhs_imp2$BMI data:

\[\mathbf{y} = \frac{{x^\lambda}-1}{\lambda}\]

if

\[\mathbf{\lambda}\neq0\] thus creating the power transformed BMI data (y), under the bc2.bmi variable.

fhs_imp2$bc2.bmi <- ((fhs_imp2$BMI**op.lambda)-1)/(op.lambda)
chol.glu <- lm(bc2.bmi ~ totChol * glucose, data = fhs_imp2)

The below pander table indicates that totChol has the regression coefficient with the most statistical significance (p-value < 0.05 indicates significantly different from zero (0) and p-value|totChol = 3.364e-12 indicating strong statistic significance). With a regression coefficient of 0.0001657, this indicates that for each increase of 1 mg/dL in a patient’s total cholesterol value, the Box-Cox transformed BMI value will increase by 0.0001657. Additionally, the second most significant variable was identified as glucose, with a p-value|glucose = 5.839e-08 with a regression coefficient of 0.0003744. This indicates that for every 1 mg/dL increase in patient glucose,the Box-Cox transformed BMI value will increase 0.0003744. Finally the interaction between totChol and glucose was examined, and found to have statistical significance per the evaluated p value (p value < 0.05 indicates significantly different from zero (0) and p value|totalChol:glucose = 4.118e-06). The significance of this relationship indicates that the Box-Cox transformed BMI value is impacted by the product of both the total cholesterol and glucose values; meaning the overall impact of the net change in the product of totChol and glucose on BMI is impacted by changes to either the totChol, glucose, or both variables. The impact of this relationship is captured in the regression coefficient of This interaction’s regression coefficient value of -1.242e-06 indicates that as the product of totChol and glucose increased by 1 mL2/dL2, the Box-Cox transformed BMI value decreases by -1.242e-06.

An overall R2 value of 0.0261903 indicates that 2.61903% of the variance in the Box-Cox transformed BMI data is associated with the product of glucose and total cholesterol levels.

In viewing the residual plots the following information is obtained regarding the second linear regression model (chol.glu):

  • Three observations exist as outliers (OBS # 378, 2658, & 3928)
  • There is a minor violation of the assumption of constant variance, with both the Residuals vs Fitted & Q-Q residuals graphs indicating near constant variance, with outliers. The variance is visibly improved from that seen for the initial model.
  • There is a moderate violation of the assumption of normal distribution of the residuals as evident by the shape of the Q-Q residuals plot.The variance is visibly improved from that seen for the initial model.
pander::pander(summary(chol.glu)$coef, caption = "Summary statistics of the regression coefficients of the Second Model")
Summary statistics of the regression coefficients of the Second Model
  Estimate Std. Error t value Pr(>|t|)
(Intercept) 1.394 0.006052 230.3 0
totChol 0.0001439 2.421e-05 5.944 2.997e-09
glucose 0.0002968 6.978e-05 4.254 2.146e-05
totChol:glucose -9.633e-07 2.764e-07 -3.485 0.0004963
kable(summary(chol.glu)$r.squared, caption = "R^2^ value of Second Model")
R2 value of Second Model
x
0.0241806
par(mfrow=c(2,2), mar=c(2,3,2,2))
plot(chol.glu)

### Third Model (chol.glu2) An influential Value (OBS 3845) was removed based on the results indicated in the above Residuals vs Leverage plot for the second model chol.glu2. The resultant the model was re-reviewed as chol.glu2, and all observations were now shown to remain within Cook’s distance, indicating no influential values remain. This third model illustrated a potential better fit with an increased R2 value of 0.0773365 indicating 2.73365% variance in Box-Cox transformed BMI variable attributable to the product of glucose and total cholesterol values, reduced variance as seen in the Q-Q Residuals plot, and reduced outliers on the Residuals vs Fitted plot.

fhs_imp2mod3 <- fhs_imp2[-3845,]
chol.glu2 <- lm(bc2.bmi ~ totChol * glucose, data = fhs_imp2mod3)
pander::pander(summary(chol.glu2)$coef, caption = "Summary statistics of the regression coefficients of the Second Model Without Influential Variable")
Summary statistics of the regression coefficients of the Second Model Without Influential Variable
  Estimate Std. Error t value Pr(>|t|)
(Intercept) 1.387 0.006445 215.2 0
totChol 0.0001732 2.617e-05 6.619 4.066e-11
glucose 0.0003777 7.496e-05 5.038 4.892e-07
totChol:glucose -1.324e-06 3.023e-07 -4.381 1.21e-05
kable(summary(chol.glu2)$r.squared, caption = "R^2^ value of Second Model without Influential Value")
R2 value of Second Model without Influential Value
x
0.0249064
par(mfrow=c(2,2), mar=c(2,3,2,2))
plot(chol.glu2)

4.2 Cross Validation of Models Using MSE

To cross validate all three proposed linear regression models, the data was first split 80:20 for training:test data, the training data was then run through a five fold validation to compare the mean square error (MSE) of all three models. The MSE values were plotted and the overall values for each fold of validation were compared to determine the best fit model.

set.seed(123)
#shuffling the data
n <- dim(fhs_imp2)[1] #set sample size  
obs.ID <- 1:n   #create observation ID to allow for random splitting
n.train <- round(0.8*n)  #create sample size for training data. round to keep whole number since no partial observations

shuffle.id  <-  sample(obs.ID, n, replace = FALSE)  #randomize created observation ID, does not allow for dupes in list.

shuffle.fhs <- fhs_imp2[shuffle.id, ] #orders rows of dataframe to be in order of shuffle.id created random obs.id list

#splitting the data

train.data.linear <- shuffle.fhs[1:n.train, ] #splitting shuffled data from 1st obs to end of training data (80%)
test.data.linear <- shuffle.fhs[(n.train + 1):n, ] #splitting shuffled data from 1st obs after end of training data to end of remaining data (test)

n.fold <- round(n.train/5)-1   #number of observations in each of the 5 folds of validation


#validation loop - 5 fold

mse.lin.1 <- rep(0,5)
mse.lin.2 <- rep(0,5)

for (i in 1:5){
  valid.id = ((i-1) * n.fold + 1):(1 * n.fold)  #breaks down folds into 5 groups 
  lin.train = train.data.linear[-valid.id, ]
  lin.valid = train.data.linear[valid.id, ]
  
  #building models to run off of folded data
  m1 = lm(BMI ~ heartRate + totChol + glucose, data = lin.train)
  m2 = lm(bc2.bmi ~ totChol * glucose, data = lin.train)
  
  #predicting BMI using models
  predm1 = predict(m1, newdata = lin.train)
  predm2 = predict(m2, newdata = lin.train)
  
  #calculate mean square error between predicted values and values from validation data
  mse.lin.1[i] = mean((predm1 - lin.valid$BMI)^2)
  mse.lin.2[i] = mean((predm2 - lin.valid$bc2.bmi)^2)
}
set.seed(123)
#repeat with data that has influential value removed (model 3)

#shuffling the data
n.3 <- dim(fhs_imp2mod3)[1] #set sample size  
obs.ID.3 <- 1:n.3   #create observation ID to allow for random splitting
n.train.3 <- round(0.8*n)  #create sample size for training data. round to keep whole number since no partial observations

# n.train = n.train.3 - sample spiting retained from first validation run through
shuffle.id.3  <-  sample(obs.ID.3, n.3, replace = FALSE)  #randomize created observation ID, does not allow for dupes in list.

shuffle.fhs.3 <- fhs_imp2mod3[shuffle.id.3, ] #orders rows of dataframe to be in order of shuffle.id created random obs.id list

#splitting the data

train.data.linear.3 <- shuffle.fhs.3[1:n.train.3, ] #splitting shuffled data from 1st obs to end of training data (80%)
test.data.linear.3 <- shuffle.fhs.3[(n.train.3 + 1):n.3, ] #splitting shuffled data from 1st obs after end of training data to end of remaining data (test)

n.fold.3 <- round(n.train.3/5)-1   #number of observations in each of the 5 folds of validation


#validation loop - 5 fold
mse.lin.3 <- rep(0,5)

for (i in 1:5){
  valid.id.3 = ((i-1) * n.fold.3 + 1):(1 * n.fold.3)  #breaks down folds into 5 groups 
  lin.train.3 = train.data.linear.3[-valid.id.3, ]
  lin.valid.3 = train.data.linear.3[valid.id.3, ]
  
  #building models to run off of folded data
  m3 = lm(bc2.bmi ~ totChol * glucose, data = lin.train.3)
  
  #predicting BMI using models
  predm3 = predict(m3, newdata = lin.train.3)
  
  #calculate mean square error between predicted values and values from validation data
  mse.lin.3[i] = mean((predm3 - lin.valid.3$bc2.bmi)^2)
}


mse.1 = mean(mse.lin.1)
mse.2 = mean(mse.lin.2)
mse.3 = mean(mse.lin.3)
mse.mod1 <- data.frame (
  x = 1:5,
  y = mse.lin.1
)
mse.mod2 <- data.frame (
  x = 1:5,
  y = mse.lin.2
)
mse.mod3 <- data.frame (
  x = 1:5,
  y = mse.lin.3
)
mse.mod1.plot <- ggplot (mse.mod1, aes(x = x, y = y)) +
  geom_point(aes(color = "Model 1"))+
  geom_line(aes(color = "Model 1")) +
  geom_point (aes (x = mse.mod2$x, y = mse.mod2$y, color = "Model 2")) + 
  geom_line (aes (x = mse.mod2$x, y = mse.mod2$y, color = "Model 2")) +
  geom_point(aes(x = mse.mod3$x, y = mse.mod3$y, color = "Model 3"))+ 
  geom_line (aes(x = mse.mod3$x, y = mse.mod3$y, color = "Model 3")) +
  labs (x = "Validation Fold", y = "Calculated MSE", title = "Mean Square Error (MSE)
  Per Validation Round
  ") +
  scale_color_manual (values = c("Model 1" = "orange", "Model 2" = "blue", "Model 3" = "green"  ))

mse.mod2.plot <- ggplot (mse.mod2, aes(x = x, y = y)) +
  geom_point (aes (x = mse.mod2$x, y = mse.mod2$y), color = "blue") + 
  geom_line (aes (x = mse.mod2$x, y = mse.mod2$y), color = "blue") +
  geom_point(aes(x = mse.mod3$x, y = mse.mod3$y), color = "green")+ 
  geom_line (aes(x = mse.mod3$x, y = mse.mod3$y), color = "green") +
  labs (x = "Validation Fold", y = "Calculated MSE", title = "Mean Square Error (MSE)
  Per Validation Round: 
  Models 2 & 3 Closer Look") 


mse.table <- data.frame (
  x = c("Model 1", "Model 2", "Model 3"),
  y = c(mse.1, mse.2, mse.3)
)
colnames(mse.table) <- c("Predictive Model", "Mean MSE Across all Validation Folds")

grid.arrange(mse.mod1.plot, mse.mod2.plot, nrow = 1, ncol = 2)

Based on the visual representation of the MSE values for each fold of validation paired with the mean MSE for each model, it has been determined that Model 2, chol.glu, the linear regression of the Box-Cox transformed BMI values compared to the product of the glucose and total cholesterol values, is the best fit for the data. Utilizing Model 2 as the predictive model will give the closest to accurate prediction values for a patient’s BMI based on their measured total cholesterol and glucose values, as evidenced by the lowest mean MSE value of all three models across all five folds of validation.

Utilizing the final model, fit to all data, the predicted BMI for a glucose of 85 and total cholesterol of 212 was assessed. Due to the BMI data undergoing a box cox transformation, the predicted value produced by the chol.glu model must undergo back transformation using the following equation.

\[\mathbf{x} = (y * \lambda + 1)^\frac{1}{\lambda}\] Without back transformation the final value will not be an actual BMI value. The previously calculated \(\lambda\) value of -0.5959597 was utilized for the back transformation.

predictor.data.linear <- data.frame (totChol = 212,glucose = 85)
pred.boxBMI <- predict(chol.glu, newdata = predictor.data.linear, type = "response")
pred.BMI <- (pred.boxBMI * op.lambda + 1)**(1/op.lambda)

kable(pred.BMI, caption = "Predicted patient BMI with a Glucose of 85 and Total Cholesterol of 212")
Predicted patient BMI with a Glucose of 85 and Total Cholesterol of 212
x
25.10196

5 Logistic Regression

Logistic regression can be utilized to assess the correlation between a binary response variable, 10 Year CHD risk diagnosis (TenYearCHD), and a variety of predictor variables. Multiple logistic regression models utilizing clinically associated predictor variables were created and analyzed for their respective goodness of fit using their calculated Receiving Operating Characteristic (ROC) curves and Area Under the Curve (AUC) values.

The following assumptions have been met to support linear regression:

  • Response variable is binary
  • No serious colinearity identified between predictor variables
  • Predictor values’ functional forms are correctly specified

To complete the logistic regression analyses and create logistic recession models, the TenYearCHD variable was selected as response variable, overall looking to determine correlation with odds of patient diagnosis with a 10 year CHD risk, characterized by a binary of TRUE for diagnosed at risk and FALSE for diagnosed not at risk.

5.1 Creating Multiple Candidate Models

5.1.1 Initial Model (simple.model)

First a simple logistic regression model was created to compare the odds of patient diagnosis with 10 year CHD risk as predicted by the previously identified predictor variables used in the best fit linear regression model: glucose levels (glucose) and total cholesterol levels (totChol).

simple.model <- glm(TenYearCHD ~ totChol + glucose, family = binomial, data = fhs_imp2)
kable(summary(simple.model)$coef, caption = "Summary table of significance test for glucose & total cholesterol impact on Ten Year CHD Risk diagnosis")
Summary table of significance test for glucose & total cholesterol impact on Ten Year CHD Risk diagnosis
Estimate Std. Error z value Pr(>|z|)
(Intercept) -3.6517943 0.2609017 -13.996822 0.0e+00
totChol 0.0044513 0.0009294 4.789449 1.7e-06
glucose 0.0103189 0.0015144 6.813706 0.0e+00

Utilizing the simple logistic regression model, both glucose and total cholesterol illustrated statistically significant positive correlation with 10 year CHD risk diagnosis. In layman’s terms, as a patient’s glucose and total cholesterol values increase, the odds the patient will be diagnosed with a 10 year CHD risk increases. By exponentiating the model, it is found that the exponentiated slope coefficients for glucose and total cholesterol levels are 1.01037237 and 1.00446124 respectively. Thus we estimate a 1.037% increase in the odds of a patient being diagnosed with 10 year CHD risk for each 1 mg/dL increase in said patient’s glucose levels. Additionally, we estimate a 0.446% increase in the odds of a patient being diagnosed with 10 year CHD risk for each 1 mg/dL increase in said patient’s total cholesterol levels.

exp(simple.model$coeff)
(Intercept)     totChol     glucose 
 0.02594453  1.00446124  1.01037237 
exp(confint(simple.model))
                 2.5 %     97.5 %
(Intercept) 0.01548977 0.04310716
totChol     1.00263070 1.00629401
glucose     1.00742438 1.01344303

5.1.2 Second Model (final.model)

Rather than utilizing the approach of pre-determining the predictor variables similar to that utilized in the initial model simple.model, a cumulative model entitled full.model was created to utilize as the starting point for automatically identifying the significant and non-significant variables based on AUC values automatically calculated by the step() function.

The same predictor values from the initial model simple.model were utilized as the reduced model for the step function. After shrinking to the reduced model, only glucose has demonstrated statistical significance with p values of 0.0000006 while total cholesterol measured in with a p value of and 0.1204717 indicating a lack of significance. Reviewing the regression coefficients, both glucose and total cholesterol indicate a positive correlation of 1.0079240235 with the odds of a patient being diagnosed with 10 year CHD risk. This indicates that for every 1 mg/dL a patient’s glucose levels increase, the odds of diagnosis with a 10 year risk factor for chronic heart disease increases by 0.792%.

full.model <- glm(TenYearCHD ~ male + age + education + currentSmoker + cigsPerDay + BPMeds + prevalentCond + diabetes + totChol + sysBP + diaBP + BMI + heartRate + glucose + highBP, family = binomial, data = fhs_imp2)

reduced.model <- simple.model
final.model <-  step(full.model,
                   scope = list(lower = formula(reduced.model), upper = formula(full.model)),
                   data = fhs_imp2,
                   direction = "backward",
                   trace = 0)
kable(summary(final.model)$coef, caption = "Summary table of significance test for variables impacting Ten Year CHD Risk diagnosis in the reduced model")
Summary table of significance test for variables impacting Ten Year CHD Risk diagnosis in the reduced model
Estimate Std. Error z value Pr(>|z|)
(Intercept) -8.1097060 0.5460834 -14.8506734 0.0000000
maleTRUE 0.5120966 0.0985004 5.1989289 0.0000002
age 0.0633016 0.0059870 10.5731389 0.0000000
cigsPerDay 0.0210554 0.0038440 5.4774737 0.0000000
BPMedsTRUE 0.3165451 0.2170697 1.4582645 0.1447677
prevalentCondHypStroke 1.0279179 0.4882259 2.1054148 0.0352552
prevalentCondNone -0.2200811 0.1266463 -1.7377612 0.0822529
prevalentCondStroke 0.3556615 1.1321075 0.3141587 0.7534005
totChol 0.0015846 0.0010205 1.5527965 0.1204717
sysBP 0.0128276 0.0026910 4.7668855 0.0000019
glucose 0.0078928 0.0015773 5.0041302 0.0000006
exp(final.model$coeff)
           (Intercept)               maleTRUE                    age 
          0.0003006072           1.6687863145           1.0653481073 
            cigsPerDay             BPMedsTRUE prevalentCondHypStroke 
          1.0212786039           1.3723780869           2.7952399361 
     prevalentCondNone    prevalentCondStroke                totChol 
          0.8024537155           1.4271243469           1.0015858956 
                 sysBP                glucose 
          1.0129102239           1.0079240235 
exp(confint(final.model))
                              2.5 %       97.5 %
(Intercept)            0.0001020661 0.0008686866
maleTRUE               1.3761292419 2.0249376415
age                    1.0529774542 1.0779904685
cigsPerDay             1.0135894547 1.0289865266
BPMedsTRUE             0.8900118566 2.0878543179
prevalentCondHypStroke 1.0694788453 7.4454903517
prevalentCondNone      0.6265342414 1.0294962687
prevalentCondStroke    0.0717671519 9.7659883733
totChol                0.9995702905 1.0035822724
sysBP                  1.0075875923 1.0182799243
glucose                1.0048311932 1.0110828877

5.2 ROC and AUC Comparrisons

A reliable way to compare the efficacy of the model fit overall and in comparison to another model is the ROC curve and associated Area Under the Curve (AUC) values.

5.2.1 Predicting Cutoff Values

Cutoff values were automatically calculated and are demonstrated below on the ROC curve plot. These cutoff points represent the best possible values at which the model fits the original data best. These values represent the respective false positive rates (\(\alpha = 1 - Specificity\)) and false negative rates (\(\beta = 1 - Sensitivity\)) that will produce the best fit outcomes for the predictor model.

no10yr <- which(fhs_imp2$TenYearCHD == FALSE)
yes10yr <-  which(fhs_imp2$TenYearCHD == TRUE)

fhs_imp2$CHD = 0
fhs_imp2$CHD[yes10yr] = 1
cutoff <- seq(0, 1, length = 100)
sensitiv = NULL
specif = NULL
sensitiv2 = NULL
specif2 = NULL

newdata.simple <- data.frame(fhs_imp2)
predict.data.log <- predict.glm(simple.model, newdata.simple, type = "response")
predict.data.log2 <- predict.glm(final.model, newdata.simple, type = "response")

for (i in 1:100){
  
fhs_imp2$CHDtrain = as.numeric(predict.data.log > cutoff[i])

TN = sum(fhs_imp2$CHDtrain == 0 & fhs_imp2$CHD == 0) # true negative
FN = sum(fhs_imp2$CHDtrain == 0 & fhs_imp2$CHD == 1) # false negative
FP = sum(fhs_imp2$CHDtrain == 1 & fhs_imp2$CHD == 0) # false positive
TP = sum(fhs_imp2$CHDtrain == 1 & fhs_imp2$CHD == 1) # true positive

sensitiv[i] = TP / (TP + FN)
specif[i] = TN / (TN + FP)
}

for (i in 1:100){
  
fhs_imp2$CHDtrain2 = as.numeric(predict.data.log2 > cutoff[i])

TN2 = sum(fhs_imp2$CHDtrain2 == 0 & fhs_imp2$CHD == 0) # true negative
FN2 = sum(fhs_imp2$CHDtrain2 == 0 & fhs_imp2$CHD == 1) # false negative
FP2 = sum(fhs_imp2$CHDtrain2 == 1 & fhs_imp2$CHD == 0) # false positive
TP2 = sum(fhs_imp2$CHDtrain2 == 1 & fhs_imp2$CHD == 1) # true positive

sensitiv2[i] = TP2 / (TP2 + FN2)
specif2[i] = TN2 / (TN2 + FP2)
}

specificity = 1 - specif
sensitivity = sensitiv

specificity2 = 1 - specif2
sensitivity2 = sensitiv2


prediction <-  predict.data.log
category <-  fhs_imp2$CHD == 1
ROCobj <- roc(category, prediction)
AUC.simple <- round(auc(ROCobj), 5)

prediction2 <-  predict.data.log2
category2 <-  fhs_imp2$CHD == 1
ROCobj2 <- roc(category2, prediction2)
AUC.final <- round(auc(ROCobj2), 5)

specif.ori <- ROCobj$specificities
specif.ori2 <- ROCobj2$specificities
sens.ori <- ROCobj$sensitivities
sens.ori2 <- ROCobj2$sensitivities

diff <- abs(sens.ori - specif.ori)
diff2 <- abs(sens.ori2 - specif.ori2)

min.simple <- which(diff == min(diff))
min.final <- which(diff2 == min(diff2))


roc.plot <- plot(specificity, sensitivity,
     type = "l",
     main = "Model Comparrison Using ROC and AUC",
     xlab="1-specificity",
     ylab="sensitivity") +
  segments (0,0,1,1, lty=2, col = "red") +
  lines(specificity, sensitivity, lwd = 1, col = "navy") +
  lines(specificity2, sensitivity2, lwd = 1, col = "darkgreen") +
  text(0.8, 0.2, paste("Simple Model AUC = ", AUC.simple), col = "navy", cex = 0.8) +
  text(0.8, 0.25, paste("Final Model AUC = ", AUC.final), col = "darkgreen", cex = 0.8) +
  points((1-specif.ori[min.simple]), specif.ori[min.simple], col = "navy", pch = 17) +
  points((1-specif.ori2[min.final]), specif.ori2[min.final], col = "darkgreen", pch = 17) 
  text(0.8, 0.18, paste( "Simple Model Calculated Cutoff = (", round((1-specif.ori[min.simple]),4), ", " , round(sens.ori[min.simple],4),")"), col = "navy", cex = 0.8) +
    text(0.8, 0.23, paste( "Final Model Calculated Cutoff = (", round((1-specif.ori2[min.final]),4), ", " , round(sens.ori2[min.final],4),")"), col = "darkgreen", cex = 0.8) 

integer(0)
pred.table = cbind(Error.Type = c('False Positive ($alpha$)', "False Negative ($beta$)"), Error.Value = c((1-specif.ori[min.simple]), sens.ori[min.simple]))
kable(pred.table, caption = "Error values for Simple Model Calculated Cutoff")
Error values for Simple Model Calculated Cutoff
Error.Type Error.Value
False Positive (\(alpha\)) 0.442714126807564
False Negative (\(beta\)) 0.557453416149068
pred.table2 = cbind(Error.Type = c('False Positive ($alpha$)', "False Negative ($beta$)"), Error.Value = c((1-specif.ori2[min.final]), sens.ori2[min.final]))
kable(pred.table2, caption = "Error values for Final Model Calculated Cutoff")
Error values for Final Model Calculated Cutoff
Error.Type Error.Value
False Positive (\(alpha\)) 0.324805339265851
False Negative (\(beta\)) 0.675465838509317

Overall the final.model returned a fair AUC value of 0.73244 indicating the model preformed fairly with acceptable discrimination between classes. The simple.model returned a lower AUC value of 0.57987 indicating a near fail in the model prediction. The closer a model’s AUC value is to a full 1.000 value, the better fit the model is in terms of predictive ability.

This indicates the second logistic regression model final.model created utilizing the step() function produced the best fit predictor model for predicting a patient’s 10 year Chronic Heart Disease risk diagnosis TenYearCHD.

6 Discussion

Patient health has been at the forefront of physician and patient concerns for decades, as evidenced by the search for predictive measures for long term patient health. The FHS study has underscored this desire for understanding what factors will impact a patient long term’s prognosis.

After the creation and analysis of various prediction models for a patient’s impactful health factors: BMI and 10 year CHD risk diagnosis, it was found that some models preform better than others. While full causation cannot be conferred from these results, it was found that the linear regression model of Box-cox transformed BMI data utilizing the product of glucose and total cholesterol produced the most accurate predictions of patient BMI. Additionally it was found that the final logistic regression model comparing the significant variables as selected by the step() function produced the most accurate predicitons of a patients 10 year CHD risk diagnosis when held to \(\alpha = 0.324805339265851\) and \(\beta = 0.675465838509317\)

7 Appendix

Heart.org - American Heart Association blood pressure ranges https://www.heart.org/en/health-topics/high-blood-pressure/understanding-blood-pressure-readings

LS0tDQp0aXRsZTogIkNhbiBXZSBQcmVkaWN0IHRoZSBGdXR1cmUgaW4gSGVhbHRoPzo8aW1nIHNyYz1cImh0dHBzOi8vbmxlcGVyYS5naXRodWIuaW8vc3RhNTUxL0hXMDEvaW1nL3Blbmd1aW5fY3V0ZS5wbmdcIiBzdHlsZT1cImZsb2F0OiByaWdodDsgd2lkdGg6IDEyJVwiLz4iDQpzdWJ0aXRsZTogIkZyYW1pbmdoYW0gSGVhcnQgU3R1ZHkgTW9kZWwgRml0dGluZyBhbmQgQ3Jvc3MgVmFsaWRhdGlpb24gZm9yIEJNSSBQcmVkaWN0aW9ucyINCmF1dGhvcjoNCi0gbmFtZTogTmF0YWxpZSBMZVBlcmENCiAgYWZmaWxpYXRpb246IFdlc3QgQ2hlc3RlciBVbml2ZXJzaXR5IHwgU1RBNTExIC0gSFcgMDINCmRhdGU6ICIyMyBPY3QgMjAyNCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIHRvY19jb2xsYXBzZTogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogICAgdGhlbWU6IHJlYWRhYmxlDQogICAgZmlnX2FsaWduOiBjZW50ZXINCi0tLQ0KDQpgYGB7Y3NzIGVjaG8gPSBGQUxTRX0NCmgxLnRpdGxlIHsgIC8qIFRpdGxlIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiB0aGUgcmVwb3J0IHRpdGxlICovDQogIGZvbnQtd2VpZ2h0OmJvbGQ7DQogIGNvbG9yOiBkYXJrbWFnZW50YSA7DQp9DQpoMS5zdWJ0aXRsZSB7ICAvKiBUaXRsZSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgdGhlIHJlcG9ydCB0aXRsZSAqLw0KICBmb250LXdlaWdodDpib2xkOw0KICBjb2xvcjogZGFya21hZ2VudGEgOw0KfQ0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBhdXRob3JzICAqLw0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogbmF2eTsNCn0NCmg0LmRhdGUgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIHRoZSBkYXRlICAqLw0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogbmF2eTsNCn0NCmgxIHsgLyogSGVhZGVyIDEgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAxIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC13ZWlnaHQ6Ym9sZDsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KaDIgeyAvKiBIZWFkZXIgMiAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGxldmVsIDIgc2VjdGlvbiB0aXRsZSAqLw0KICAgIGZvbnQtd2VpZ2h0OmJvbGQ7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDMgeyAvKiBIZWFkZXIgMyAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgMyBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtd2VpZ2h0OmJvbGQ7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDQgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgNCBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmJvZHkgew0KICBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOw0KfQ0KDQouaGlnaGxpZ2h0bWUgeyANCiAgYmFja2dyb3VuZC1jb2xvcjp5ZWxsb3c7IA0KfQ0KDQpwIHsgDQogIGJhY2tncm91bmQtY29sb3I6d2hpdGU7IA0KfQ0KDQpoNSB7DQogIGNvbG9yOiBuYXZ5Ow0KfQ0KDQouaWZyYW1lIHsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KDQphOmxpbmsgew0KICBjb2xvcjogZGFya21hZ2VudGE7DQp9DQoNCmBgYA0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmlmICghcmVxdWlyZSgiZHBseXIiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImRwbHlyIikNCn0NCg0KaWYgKCFyZXF1aXJlKCJzdHJpbmdyIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJzdHJpbmdyIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInN0cmluZ3IiKQ0KfQ0KDQppZiAoIXJlcXVpcmUoInBsb3RseSIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygicGxvdGx5IikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInBsb3RseSIpDQp9DQoNCmlmICghcmVxdWlyZSgicGFuZG9jIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJwYW5kb2MiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgicGFuZG9jIikNCn0NCg0KaWYgKCFyZXF1aXJlKCJncmlkRXh0cmEiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImdyaWRFeHRyYSIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJncmlkRXh0cmEiKQ0KfQ0KDQppZiAoIXJlcXVpcmUoImdyaWQiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImdyaWQiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiZ3JpZCIpDQp9DQppZiAoIXJlcXVpcmUoInJhc3RlciIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygicmFzdGVyIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInJhc3RlciIpDQp9DQppZiAoIXJlcXVpcmUoIm1pY2UiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIm1pY2UiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgibWljZSIpDQp9DQppZiAoIXJlcXVpcmUoInBST0MiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoInBST0MiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgicFJPQyIpDQp9DQppZiAoIXJlcXVpcmUoImdncmlkZ2VzIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3JpZGdlcyIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJnZ3JpZGdlcyIpDQp9DQppZiAoIXJlcXVpcmUoImtuaXRyIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJrbml0ciIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJrbml0ciIpDQp9DQppZiAoIXJlcXVpcmUoIkdHYWxseSIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiR0dhbGx5IikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoIkdHYWxseSIpDQp9DQppZiAoIXJlcXVpcmUoIk1BU1MiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIk1BU1MiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiTUFTUyIpDQp9DQoNCg0KDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSwgICANCiAgICAgICAgICAgICAgICAgICAgICByZXN1bHQgPSBUUlVFLCAgIA0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBjb21tZW50ID0gTkEsDQogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gJ2NlbnRlcicpDQoNCm9wdGlvbnMoRFQub3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDUsIHNjcm9sbFggPSBUUlVFKSkNCmBgYA0KDQojIEludHJvZHVjdGlvbg0KDQojIyBCYWNrZ3JvdW5kDQoNClRoZSBGcmFtaW5naGFtIEhlYXJ0IFN0dWR5IChGSFMpLCBiYWNrZWQgYnkgdGhlIE5hdGlvbmFsIEluc3RpdHV0ZSBvZiBIZWFsdGggKE5JSCkgaXMga25vd24gYXMgdGhlIGxvbmdlc3QgcnVubmluZyBhbmQgbW9zdCBpbXBvcnRhbnQgZXBpZGVtaW9sb2dpY2FsIHN0dWRpZXMgaW4gdGhlIGhpc3Rvcnkgb2YgbWVkaWNhbCByZXNlYXJjaC4gVGhpcyBsYW5kbWFyayBwcm9zcGVjdGl2ZSByZXNlYXJjaCBzdHVkeSBjZW50ZXJlZCBhcm91bmQgY3Jvc3Mgc2VjdGlvbmFsIGRhdGEsIGZvbGxvd2luZyBvdmVyIHRlbiB0aG91c2FuZCBwYXRpZW50cyB0aHJvdWdob3V0IHRoZSBncmVhdGVyIHBhcnQgb2YgdGhlIHBhc3QgY2VudHVyeS4NCg0KU3RhcmluZyBpbiAxOTQ4IHRoZSBGSFMgc3R1ZHkgYmVnYW4gYnkgc3VydmV5aW5nIGZpdmUgdGhvdXNhbmQgKDUsMDAwKSBGcmFtaW5naGFtLCBNYXNzYWNodXNldHRzIHJlc2lkZW50cywgYWdlcyAzMCB0aHJvdWdoIDYwLCByZWdhcmRpbmcgdmFyaW91cyBhc3BlY3RzIG9mIHRoZWlyIGN1cnJlbnQgbGlmZXN0eWxlIHdoaWxlIGNvbmR1Y3RpbmcgdmFyaW91cyBwaHlzaWNhbCBoZWFsdGggZXhhbWluYXRpb25zLiBQYXRpZW50cyB3ZXJlIHRoZW4gZm9sbG93ZWQsIHdpdGggcmVndWxhciByZS1leGFtaW5hdGlvbnMgY29uZHVjdGVkIGV2ZXJ5IHR3byB5ZWFycy4gQXMgdGhlIGxpZmUgY3ljbGUgb2YgdGhpcyByZXNlYXJjaCBoYXMgcHJvZ3Jlc3NlZCwgYWRkaXRpb25hbCBleGFtaW5hdGlvbnMgaGF2ZSBiZWVuIGluY2x1ZGVkIGluIHRoZSBGSFMgYXMgd2VsbCBhcyBlbnJvbGxtZW50IG9mIGFuIGFkZGl0aW9uYWwgZml2ZSB0aG91c2FuZCAoNSwwMDApIG5ldyBwYXRpZW50cyBkaXJlY3RseSByZWxhdGVkIHRvIHRoZSBvcmlnaW5hbCBGSFMgcGF0aWVudCBjb2hvcnQuIFRoZSBGSFMgY29udGludWVzIHRvIGV2b2x2ZSBhbmQgc2Vla3MgdG8gcHJvdmlkZSBhIGxpdmluZyBhbmQgZ3Jvd2luZyBsb25naXR1ZGluYWwgZGF0YXNldCBmb3IgYSB2YXJpZXR5IG9mIGhlYWx0aCBjb25kaXRpb25zIGFuZCB0aGVpciByaXNrIGZhY3RvcnMsIGJvdGggYmVoYXZpb3JhbCBhbmQgZ2VuZXRpYy4NCg0KIyMgRGF0YSBEZXNjcmlwdGlvbiBhbmQgRGV0YWlscw0KDQpBIG1pbm9yIHN1YnNldCBvZiB0aGUgbG9uZ2l0dWRpbmFsIEZIUyBkYXRhIHdhcyBvYnRhaW5lZCBmcm9tIHRoZSA8YSBocmVmPSJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy8iPmBDb3Vyc2UgUHJvamVjdCBEYXRhIFJlcG9zaXRvcnlgPC9hPiBmb3IgdXNlLiBBdCB0aGUgdGltZSBvZiB1dGlsaXphdGlvbiwgdGhpcyBzdWJzZXQgY29udGFpbmVkIGA0MjQwIG9ic2VydmF0aW9uc2AgYW5kIGAxNiB2YXJpYWJsZXNgLg0KDQpgYGB7cn0NCmZoc19yYXcgPC0gcmVhZC5jc3YoJ2h0dHBzOi8vbmxlcGVyYS5naXRodWIuaW8vc3RhNTUxL0hXMDIvZGF0YS9maHNfZGF0YS5jc3YnKQ0KDQojbGluayB0byB2YXJpYWJsZSBkZXNjcmlwdGlvbnMgaHR0cHM6Ly9wZW5nZHNjaS5naXRodWIuaW8vZGF0YXNldHMvRnJhbWluZ2hhbUhlYXJ0U3R1ZHkvRnJhbWluZ2hhbUhlYXJ0U3R1ZHktZGVzY3JpcHRpb24ucGRmIw0KYGBgDQoNCkEgZnVsbCBkZXNjcmlwdGlvbiBvZiB0aGUgdmFyaWFibGVzIGNhbiBiZSBvYnRhaW5lZCBhdCB0aGUgZm9sbG93aW5nIDxhIGhyZWY9Imh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL2RhdGFzZXRzL0ZyYW1pbmdoYW1IZWFydFN0dWR5L0ZyYW1pbmdoYW1IZWFydFN0dWR5LWRlc2NyaXB0aW9uLnBkZiI+YGxpbmtgPC9hPi4gQSBzaG9ydCBmb3JtIHN1bW1hcnkgaXMgaW5jbHVkZWQgYmVsb3cgZm9yIGVhc2Ugb2YgcmVmZXJlbmNlLiBOdW1lcmljYWwgZGF0YSBpcyBpbmRpY2F0ZWQgd2l0aCBhIGJvbGQgPGI+W05dPC9CPiwgd2hpbGUgY2F0ZWdvcmljYWwgZGF0YSBpcyBpbmRpY2F0ZWQgd2l0aCBhIGJvbGQgPGI+W0NdPC9iPi4gQ2F0ZWdvcmljYWwgdmFyaWFibGVzIGFyZSBvcmlnaW5hbGx5IGVuY29kZWQgd2l0aCBpbnRlZ2VyIHZhbHVlcyBmb3IgZGF0YSBzdG9yYWdlIGFuZCB1dGlsaXphdGlvbiBjb25zaWRlcmF0aW9ucy4NCg0KLSAgIGBzZXhgIEdlbmRlciBvZiB0aGUgcHQuIChNYWxlID0gMSBcfCBOb24tTWFsZSA9IDApIDxiPltDXTwvYj4NCi0gICBgYWdlYCBBZ2UgYXQgdGltZSBvZiBwdC4gZXhhbSAoeWVhcnMpIDxiPltOXTwvQj4NCi0gICBgZWR1Y2F0aW9uYCBMZXZlbCBvZiBwdCdzIGVkdWNhdGlvbi4gKFNvbWUgaGlnaCBzY2hvb2wgPSAxIFx8IEhpZ2ggc2Nob29sIC8gR0VEID0gMiBcfCBTb21lIGNvbGxlZ2UgLyB2b2NhdGlvbmFsID0gMyBcfCBDb2xsZWdlID0gNCkgPGI+W0NdPC9iPg0KLSAgIGBjdXJyZW50U21va2VyYCBTbW9rZXIgc3RhdHVzIGF0IHRpbWUgb2YgcHQuIGV4YW0gKFNtb2tlciA9IDEgXHwgTm9uLXNtb2tlciA9IDIpIDxiPltDXTwvYj4NCi0gICBgY2lnc1BlckRheWAgTnVtYmVyIG9mIGNpZ2FyZXR0ZXMgc21va2VkIHBlciBkYXkgYXQgdGltZSBvZiBwdC4gZXhhbSA8Yj5bTl08L0I+DQotICAgYEJQbWVkc2AgUHQgbWVkaWNhbCBoaXN0b3J5OiBibG9vZCBwcmVzc3VyZSBtZWRpY2luZSAvIGFudGktaHlwZXJ0ZW5zaXZlIChZZXMgQlBtZWRzID0gMSBcfCBObyBCUG1lZHMgPSAyKSA8Yj5bQ108L2I+DQotICAgYHByZXZhbGVudFN0cm9rZWAgUHQgbWVkaWNhbCBoaXN0b3J5OiBzdHJva2UgKFllcyBzdHJva2UgPSAxIFx8IE5vIHN0cm9rZSA9IDApIDxiPltDXTwvYj4NCi0gICBgcHJldmFsZW50SHlwYCBQdCBtZWRpY2FsIGhpc3Rvcnk6IGh5cGVydGVuc2lvbiAoWWVzIGh5cGVydGVuc2lvbiA9IDEgXHwgTm8gaHlwZXJ0ZW5zaW9uID0gMCkgPGI+W0NdPC9iPg0KLSAgIGBkaWFiZXRlc2AgUHQgbWVkaWNhbCBoaXN0b3J5OiBkaWFiZXRlcyAoWWVzIGRpYWJldGVzID0gMSBcfCBObyBkaWFiZXRlcyA9IDIpIDxiPltDXTwvYj4NCi0gICBgdG90Q29sYCBUb3RhbCBjaG9sZXN0ZXJvbCAobWcvZEwpIDxiPltOXTwvQj4NCi0gICBgc3lzQlBgIFN5c3RvbGljIGJsb29kIHByZXNzdXJlIChtbUhnKSA8Yj5bTl08L0I+DQotICAgYGRpYURQYCBEaWFzdG9saWMgYmxvb2QgcHJlc3N1cmUgKG1tSGcpIDxiPltOXTwvQj4NCi0gICBgQk1JYCBCb2R5IE1hc3MgSW5kZXggKGtnL21eMl4pIDxiPltOXTwvQj4NCi0gICBgaGVhcnQgcmF0ZWAgSGVhcnQgcmF0ZSAoYmVhdHMvbWludXRlKSA8Yj5bTl08L0I+DQotICAgYGdsdWNvc2VgIEJsb29kIGdsdWNvc2UgbGV2ZWwgKG1nL2RMKSA8Yj5bTl08L0I+DQotICAgYFRlblllYXJDSERgIENhbGN1bGF0ZWQgMTAgeWVhciByaXNrIG9mIGNvcm9uYXJ5IGhlYXJ0IGRpc2Vhc2UgKEF0IFJpc2sgPSAxIC8gTm90IGF0IHJpc2sgPSAwKSA8Yj5bQ108L2I+DQoNCiMjIyBTdW1tYXJ5IFN0YXRpc3RpY3MNCg0KQSBicmllZiBzdW1tYXJ5IG9mIHRoZSByYXcgZGF0YSBzZXQgKGBmaHNfcmF3YCkgaXMgaW5jbHVkZWQgYmVsb3cgZm9yIHJlZmVyZW5jZS4gQ2F0ZWdvcmljYWwgdmFyaWFibGVzIGhhdmUgbm90IHlldCBiZWVuIHRyYW5zZm9ybWVkIGFuZCBhcmUgc3RpbGwgcmVwcmVzZW50ZWQgYXMgaW50ZWdlciB2YWx1ZXMuDQoNCmBgYHtyfQ0Kc3VtbWFyeShmaHNfcmF3KQ0Kc3RyKGZoc19yYXcpDQoNCmBgYA0KDQojIE1ldGhvZG9sb2d5DQoNCkluIG9yZGVyIHRvIGRldGVybWluZSBpZiBlYXNpbHkgbWVhc3VyYWJsZSBwYXRpZW50IGhlYWx0aCBtZWFzdXJlcyBzdWNoIGFzIGdsdWNvc2UgbGV2ZWxzIGFuZCB0b3RhbCBjaG9sZXN0ZXJvbCBsZXZlbHMgY2FuIGFjdCBhcyBwcmVkaWN0b3IgdmFsdWVzIGZvciBtb3JlIHNhbGllbnQgaGVhbHRoIG1lYXN1cmVzIHN1Y2ggYXMgcGF0aWVudCBCTUkgYW5kIHBhdGllbnQgMTAgeWVhciBDSEQgcmlzayBkaWFnbm9zaXMsIG11bHRpcGxlIHJlZ3Jlc3Npb24gbW9kZWxzIHdlcmUgY3JlYXRlZCB1dGlsaXppbmcgdGhlIGBmaHNgIHN1YnNldCBkYXRhLiAgQmVmb3JlIGFueSBkYXRhIGNvdWxkIGJlIGFuYWx5emVkIG9yIG1vZGVscyBidWlsdCwgZmVhdHVyZSB2YXJpYWJsZXMgd2VyZSBlbmdpbmVlcmVkIHRvIGltcHJvdmUgYW5hbHlzaXMgY2FwYWJpbGl0aWVzIGFuZCBhbGwgbWlzc2luZyB2YWx1ZXMgd2VyZSBpbXB1dGVkIHVzaW5nIHRoZSBgbWljZWAgcGFja2FnZS4gDQoNClRoZSBmZWFzaWJpbGl0eSBvZiB1dGlsaXppbmcgZ2x1Y29zZSBhbmQgdG90YWwgY2hvbGVzdGVyb2wgbGV2ZWxzIGFzIEJNSSBwcmVkaWN0b3JzIHdhcyBleHBsb3JlZCBmaXJzdC4gTXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWxzIHdlcmUgYnVpbHQgYW5kIHN1YnNlcXVlbnRseSB0ZXN0ZWQgd2l0aCA1LWZvbGQgdmFsaWRhdGlvbiwgY29tcGFyaW5nIHRoZSBtZWFuIHNxdWFyZSBlcnJvciAoTVNFKSBmb3IgZWFjaCB2YWxpZGF0aW9uIGxvb3AuICBPbmNlIGEgZmluYWwgYmVzdCBmaXQgbW9kZWwgd2l0aCB0aGUgbG93ZXN0IGF2ZXJhZ2UgTVNFIGFuZCBsZWFzdCB2YXJpYXRpb24gYWNyb3NzIGFsbCByb3VuZHMgb2YgdmFsaWRhdGlvbiB3YXMgc2VsZWN0ZWQsIHRoZSBzZWxlY3RlZCBwcmVkaWNpdG9uIG1vZGVsIHdhcyB1dGlsaXplZCB0byBjb21wbGV0ZSB0ZXN0IHByZWRpY3Rpb25zIGJhc2VkIG9uIGh5cG90aGV0aWNhbCBwYXRpZW50IHZhbHVlcy4gIA0KDQpTZWNvbmRseSB0aGUgZmVhc2liaWxpdHkgb2YgdXRpbGl6aW5nIGdsdWNvc2UgYW5kIHRvdGFsIGNob2xlc3Rlcm9sIGxldmVscyBhcyAxMCB5ZWFyIENIRCByaXNrIGRpYWdub3NpcyBwcmVkaWN0b3JzIHdhcyBleHBsb3JlZC4gIFR3byBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVscyB3ZXJlIGJ1aWx0IGFuZCBzdWJzZXF1ZW50bHkgY29tcGFyZWQgdXRpbGl6aW5nIHRoZWlyIFJlY2VpdmluZyBPcGVyYXRpbmcgQ2hhcmFjdGVyaXN0aWMgKFJPQykgY3VydmVzIGFuZCBjYWxjdWxhdGVkIGFyZWEgdW5kZXIgdGhlIGN1cnZlIChBVUMpIHZhbHVlcy4gIEFzIGEgcGFydCBvZiB0aGUgbW9kZWwgY29tcGFyaXNvbiwgYmVzdCBmaXQgY3V0b2ZmIHZhbHVlcywgJFxhJCAodHlwZSAxIGVycm9yIHJhdGVzKSwgYW5kICRcYiQgKHR5cGUgMiBlcnJvciByYXRlcykgd2VyZSBjYWxjdWxhdGVkIHRvIGRldGVybWluZSB0aGUgcG9pbnQgb2YgbW9kZWwgYmVzdCBmaXQuDQoNCiMgRURBIGFuZCBGZWF0dXJlIEVuZ2luZWVyaW5nDQoNCiMjIEZlYXR1cmUgRW5naW5lZXJpbmcNCg0KIyMjIENhdGVnb3JpY2FsIFZhcmlhYmxlIFRyYW5zZm9ybWF0aW9uDQoNCkluIG9yZGVyIHRvIHByZXZlbnQgY2F0ZWdvcmljYWwgdmFsdWVzIHN0b3JlZCBhcyBpbnRlZ2VycyBmcm9tIGJlaW5nIGluY29ycG9yYXRlZCBpbnRvIGFuYWx5c2lzIG9mIHRydWUgbnVtZXJpY2FsIHZhcmlhYmxlcywgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMgd2VyZSB0cmFuc2Zvcm1lZCBmcm9tIGludGVnZXIgdG8gbG9naWNhbCB2YWx1ZXMuDQoNCi0gICBgbWFsZWANCi0gICBgY3VycmVudFNtb2tlcmANCi0gICBgQlBNZWRzYA0KLSAgIGBwcmV2YWxlbnRTdHJva2VgDQotICAgYHByZXZhbGVudEh5cGANCi0gICBgZGlhYmV0ZXNgDQotICAgYFRlblllYXJDSERgDQoNCkFkZGl0aW9uYWxseSwgdGhlIGBlZHVjYXRpb25gIHZhcmlhYmxlIHdhcyB0cmFuc2Zvcm1lZCBmcm9tIGFuIGludGVnZXIgdG8gYSBmYWN0b3IsIHVzaW5nIHRoZSBmb2xsb3dpbmcgcmVwbGFjZW1lbnQgdmFsdWVzOg0KDQotICAgYGVkdWNhdGlvbmAgPSAxIC1cPiAiU29tZUhTIg0KLSAgIGBlZHVjYXRpb25gID0gMiAtXD4gIkhTLUdFRCINCi0gICBgZWR1Y2F0aW9uYCA9IDMgLVw+ICJTb21lQ29sLVZvYyINCi0gICBgZWR1Y2F0aW9uYCA9IDQgLVw+ICJDb2xsZWdlIg0KDQpBIHN1bW1hcnkgb2YgdGhlIHRyYW5zZm9ybWVkIHZhcmlhYmxlcyBpcyBpbmNsdWRlZCB0byBpbGx1c3RyYXRlIHRoZSB0cmFuc2Zvcm1hdGlvbiBhbmQgdG8gY2FwdHVyZSB0aGUgb3ZlcmFsbCByZXRlbnRpb24gb2YgYWxsIHZhcmlhYmxlcyBhbmQgbWlzc2luZyB2YWx1ZXMuDQoNCmBgYHtyfQ0KZmhzIDwtIGZoc19yYXcNCg0KZmhzJG1hbGUgPC0gYXMubG9naWNhbChhcy5pbnRlZ2VyKGZocyRtYWxlKSkNCmZocyRjdXJyZW50U21va2VyIDwtIGFzLmxvZ2ljYWwoYXMuaW50ZWdlcihmaHMkY3VycmVudFNtb2tlcikpDQpmaHMkQlBNZWRzIDwtIGFzLmxvZ2ljYWwoYXMuaW50ZWdlcihmaHMkQlBNZWRzKSkNCmZocyRwcmV2YWxlbnRTdHJva2UgPC0gYXMubG9naWNhbChhcy5pbnRlZ2VyKGZocyRwcmV2YWxlbnRTdHJva2UpKQ0KZmhzJHByZXZhbGVudEh5cCA8LSBhcy5sb2dpY2FsKGFzLmludGVnZXIoZmhzJHByZXZhbGVudEh5cCkpDQpmaHMkZGlhYmV0ZXMgPC0gYXMubG9naWNhbChhcy5pbnRlZ2VyKGZocyRkaWFiZXRlcykpDQpmaHMkVGVuWWVhckNIRCA8LSBhcy5sb2dpY2FsKGFzLmludGVnZXIoZmhzJFRlblllYXJDSEQpKQ0KZmhzJGVkdWNhdGlvbiA8LSBmYWN0b3IoZmhzJGVkdWNhdGlvbiwNCiAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoMTo0KSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIlNvbWVIUyIsICJIUy1HRUQiLCAiU29tZUNvbC1Wb2MiLCAiQ29sbGVnZSIpKQ0Kc3RyKGZocykNCmBgYA0KDQojIyMgcHJldmFsZW50Q29uZCAoUHJldmFsZW50IENvbmRpdGlvbnMpIEZlYXR1cmUgVmFyaWFibGUNCg0KSW4gb3JkZXIgdG8gcmVkdWNlIHRoZSBudW1iZXIgb2YgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGZvciBhbmFseXNpcywgdGhlIHZhcmlhYmxlcyBgcHJldmFsZW50SHlwYCBhbmQgYHByZXZhbGVudFN0cm9rZWAgd2VyZSBjb21iaW5lZCB0byBjcmVhdGUgYSBuZXcgZmVhdHVyZSB2YXJpYWJsZSBlbnRpdGxlZCBgcHJldmFsZW50Q29uZGAuIFRoZXNlIHZhcmlhYmxlcyB3ZXJlIGFibGUgdG8gYmUgY29tYmluZWQgaW50byBhIHNpbmdsZSBmZWF0dXJlIHZhcmlhYmxlLCBhcyB0aGV5IHdlcmUgYm90aCBiaW5hcnkgY2F0ZWdvcmljYWwgdmFsdWVzIG1lYXN1cmluZyBwYXN0IHBhdGllbnQgbWVkaWNhbCBoaXN0b3J5LiBUaGlzIG5ldyBmZWF0dXJlIHZhcmlhYmxlIGBwcmV2YWxlbnRDb25kYCBzZXJ2ZXMgdG8gcmVjb3JkIHBhdGllbnQgbWVkaWNhbCBoaXN0b3J5IGZvciBib3RoIGh5cGVydGVuc2lvbiBhbmQgc3Ryb2tlLCB1dGlsaXppbmcgdGhlIGZvbGxvd2luZyBwb3RlbnRpYWwgdmFsdWVzOg0KDQotICAgYEh5cGA6IEluZGljYXRlcyB0aGUgcGF0aWVudCBoYXMgYSBoaXN0b3J5IG9mIDxiPkh5cGVydGVuc2lvbiBvbmx5PC9iPg0KLSAgIGBIeXBTdHJva2VgOiBJbmRpY2F0ZXMgdGhlIHBhdGllbnQgaGFzIGEgaGlzdG9yeSBvZiA8Yj5ib3RoIEh5cGVydGVuc2lvbiBhbmQgU3Ryb2tlPC9iPg0KLSAgIGBOb25lYDogSW5kaWNhdGVzIHRoZSBwYXRpZW50IDxiPmRvZXMgbm90IGhhdmUgYSBoaXN0b3J5PC9iPiBvZiBIeXBlcnRlbnNpb24gb3IgU3Ryb2tlDQotICAgYFN0cm9rZWA6IEluZGljYXRlcyB0aGUgcGF0aWVudCBoYXMgYSBoaXN0b3J5IG9mIDxiPlN0cm9rZSBvbmx5PC9iPg0KDQpQcm92aXNpb25zIGZvciBtaXNzaW5nIHZhbHVlcyB3ZXJlIGluY2x1ZGVkLCBidXQgYXMgd2lsbCBiZSBkaXNjdXNzZWQgaW4gbGF0ZXIgc2VjdGlvbnMsIG5laXRoZXIgYHByZXZhbGVudEh5cGAgbm9yIGBwcmV2YWxlbnRTdHJva2VgIGhhdmUgbWlzc2luZyB2YWx1ZXMuDQoNCmBgYHtyfQ0KZmhzJHByZXZhbGVudENvbmQgPC0gTkENCg0KZm9yIChpIGluIDE6bnJvdyhmaHMpKSB7DQogIGlmIChmaHMkcHJldmFsZW50SHlwW2ldID09IFRSVUUgJiYgZmhzJHByZXZhbGVudFN0cm9rZVtpXSA9PSBGQUxTRSkgew0KICAgIGZocyRwcmV2YWxlbnRDb25kW2ldIDwtICAiSHlwIg0KICB9DQogIGVsc2UgaWYgKGZocyRwcmV2YWxlbnRIeXBbaV0gPT0gVFJVRSAmJiBmaHMkcHJldmFsZW50U3Ryb2tlW2ldID09IFRSVUUpIHsNCiAgICBmaHMkcHJldmFsZW50Q29uZFtpXSA8LSAgIkh5cFN0cm9rZSINCiAgfQ0KICBlbHNlIGlmIChmaHMkcHJldmFsZW50SHlwW2ldID09IEZBTFNFICYmIGZocyRwcmV2YWxlbnRTdHJva2VbaV0gPT0gRkFMU0UpIHsNCiAgICBmaHMkcHJldmFsZW50Q29uZFtpXSA8LSAgIk5vbmUiDQogIH0NCiAgZWxzZSBpZiAoZmhzJHByZXZhbGVudEh5cFtpXSA9PSBGQUxTRSAmJiBmaHMkcHJldmFsZW50U3Ryb2tlW2ldID09IFRSVUUpIHsNCiAgICBmaHMkcHJldmFsZW50Q29uZFtpXSA8LSAgIlN0cm9rZSINCiAgfQ0KICBlbHNlIGlmIChmaHMkcHJldmFsZW50SHlwW2ldID09ICIiIHx8IGZocyRwcmV2YWxlbnRTdHJva2VbaV0gPT0gIiIpIHsNCiAgICBmaHMkcHJldmFsZW50Q29uZFtpXSA8LSAiTWlzc2luZy1WYWwiDQogIH0gDQogIGVsc2UgZmhzJHByZXZhbGVudENvbmRbaV0gPC0gIkVycm9yIg0KfQ0KYGBgDQoNCkluIG9yZGVyIHRvIGFsbG93IGZvciBwcm9wZXIgaGFuZGluZyBpbiBsYXRlciBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsaW5nLCB0aGUgY2hhcmFjaGVyIHZhcmlhYmxlIGNhdGVnb3JpZXMgYXMgYXNzaWduZWQgd2VyZSBjb252ZXJ0ZWQgdG8gZmFjdG9yIHZhbHVlcy4gQSBxdWljayBzdW1tYXJ5IGNoZWNrIG9mIHRoZSBuZXdseSBjcmVhdGVkIGZlYXR1cmUgdmFyaWFibGUgaWxsdXN0cmF0ZXMgdGhhdCBhbGwgNDI0MCBvYnNlcnZhdGlvbnMgd2VyZSByZXRhaW5lZCwgYW5kIG5vIHZhbHVlcyBhcmUgbWlzc2luZy4NCg0KYGBge3IsIGVjaG89VFJVRX0NCmZocyRwcmV2YWxlbnRDb25kIDwtIGFzLmZhY3Rvcihhcy5jaGFyYWN0ZXIoZmhzJHByZXZhbGVudENvbmQpKQ0Kc3RyKGZocyRwcmV2YWxlbnRDb25kKQ0Kc3VtbWFyeShmaHMkcHJldmFsZW50Q29uZCkNCmBgYA0KDQojIyMgaGlnaEJQIChIaWdoIEJsb29kIFByZXNzdXJlKSBGZWF0dXJlIFZhcmlhYmxlDQoNCkluIG9yZGVyIHRvIHJlZHVjZSB0aGUgbnVtYmVyIG9mIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBmb3IgYW5hbHlzaXMsIHRoZSB2YXJpYWJsZXMgYHN5c0JQYCAoc3lzdG9saWMgYmxvb2QgcHJlc3N1cmUpIGFuZCBgZGlhQlBgIChkaWFzdG9saWMgYmxvb2QgcHJlc3N1cmUpIHdlcmUgYmlubmVkIGFuZCBjb21iaW5lZCB0byBjcmVhdGUgYSBuZXcgZmVhdHVyZSB2YXJpYWJsZSBlbnRpdGxlZCBgaGlnaEJQYC4gVGhlIHBhcmFtZXRlcnMgdG8gYmluIGFuZCBjb21iaW5lIGJvdGggc3lzdG9saWMgYW5kIGRpYXN0b2xpYyBibG9vZCBwcmVzc3VyZSByZWFkaW5ncyBpbnRvIGEgc2luZ2xlIGNhdGVnb3JpY2FsIHZhcmlhYmxlIGRlc2NyaWJpbmcgdGhlIHBhdGllbnQncyBoaWdoIGJsb29kIHByZXNzdXJlIHN0YXR1cyB3ZXJlIG9idGFpbmVkIGZyb20gdGhlIEFtZXJpY2FuIEhlYXJ0IEFzc29jaWF0aW9uIGF0IHRoZSBmb2xsb3dpbmcgPGEgaHJlZj0iaHR0cHM6Ly93d3cuaGVhcnQub3JnL2VuL2hlYWx0aC10b3BpY3MvaGlnaC1ibG9vZC1wcmVzc3VyZS91bmRlcnN0YW5kaW5nLWJsb29kLXByZXNzdXJlLXJlYWRpbmdzXCI+IGBsaW5rYCA8L2E+DQoNCjxpbWcgc3JjPSJodHRwczovL3d3dy5oZWFydC5vcmcvLS9tZWRpYS9IZWFsdGgtVG9waWNzLUltYWdlcy9IQlAvYmxvb2QtcHJlc3N1cmUtcmVhZGluZ3MtY2hhcnQtRW5nbGlzaC5qcGc/c2NfbGFuZz1lbiIvPg0KDQpgYGB7cn0NCmZocyRoaWdoQlAgPC0gTkENCg0KZm9yIChpIGluIDE6bnJvdyhmaHMpKSB7DQogIGlmIChmaHMkc3lzQlBbaV0gPCAxMjAgJiYgZmhzJGRpYUJQW2ldIDwgODApIHsNCiAgICBmaHMkaGlnaEJQW2ldIDwtICAiTm9ybWFsIg0KICB9DQogIGVsc2UgaWYgKGZocyRzeXNCUFtpXSA8IDEzMCAmJiBmaHMkZGlhQlBbaV0gPCA4MCkgew0KICAgIGZocyRoaWdoQlBbaV0gPC0gICJFbGV2YXRlZCINCiAgfQ0KICAgIGVsc2UgaWYgKGZocyRzeXNCUFtpXSA8IDE0MCB8IGZocyRkaWFCUFtpXSA8IDkwKSB7DQogICAgZmhzJGhpZ2hCUFtpXSA8LSAgIkhCUDEiDQogIH0NCiAgZWxzZSBpZiAoZmhzJHN5c0JQW2ldID49IDE0MCB8IGZocyRkaWFCUFtpXSA+PSA5MCkgew0KICAgIGZocyRoaWdoQlBbaV0gPC0gICJIQlAyIg0KICB9DQogIGVsc2UgaWYgKGZocyRzeXNCUFtpXSA+PSAxODAgfCBmaHMkZGlhQlBbaV0gPj0gMTIwKSB7DQogICAgZmhzJGhpZ2hCUFtpXSA8LSAgIkNyaXNpcyINCiAgfQ0KfQ0KYGBgDQpJbiBvcmRlciB0byBhbGxvdyBmb3IgcHJvcGVyIGhhbmRpbmcgaW4gbGF0ZXIgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbGluZywgdGhlIGNoYXJhY3RlciB2YXJpYWJsZSBjYXRlZ29yaWVzIGFzIGFzc2lnbmVkIHdlcmUgY29udmVydGVkIHRvIGZhY3RvciB2YWx1ZXMuDQpgYGB7cn0NCmZocyRoaWdoQlAgPC0gYXMuZmFjdG9yKGFzLmNoYXJhY3RlcihmaHMkaGlnaEJQKSkNCg0Kc3RyKGZocykNCmBgYA0KDQojIyBJZGVudGlmeWluZyBhbmQgSGFuZGxpbmcgTWlzc2luZyBWYWx1ZXMNCg0KUHJpb3IgdG8gdXRpbGl6aW5nIHRoZSBGSFMgZGF0YSBpbiBhbnkgY2FwYWNpdHksIGFuIHVuZGVyc3RhbmRpbmcgb2YgdGhlIG1pc3NpbmcgdmFsdWVzIGZvciBlYWNoIGNhdGVnb3J5IHdhcyBvYnRhaW5lZC4gQSB0b3RhbCBjb3VudCBvZiBtaXNzaW5nIHZhbHVlcyBwZXIgdmFyaWFibGUgYXMgd2VsbCBhcyBhIHRvdGFsIHBlcmNlbnRhZ2Ugb2YgbWlzc2luZyB2YWx1ZXMgZm9yIGVhY2ggdmFyaWFibGUgd2FzIGNhbGN1bGF0ZWQuDQoNCiMjIyBJZGVudGlmeWluZyBTY29wZSBvZiBNaXNzaW5nIFZhbHVlcw0KDQo8aDU+Q291bnQgb2YgbWlzc2luZyB2YWx1ZXMgcGVyIHZhcmlhYmxlPC9oNT4NCg0KYGBge3J9DQpzYXBwbHkoZmhzLCBmdW5jdGlvbiAoeCkgc3VtKGlzLm5hKHgpKSkgI2NvdW50IG9mIG1pc3NpbmcNCmBgYA0KDQo8aDU+UGVyY2VudGFnZSBvZiBtaXNzaW5nIHZhbHVlcyBwZXIgdmFyaWFibGU8L2g1Pg0KDQpgYGB7cn0NCnNhcHBseShmaHMsIGZ1bmN0aW9uICh4KSByb3VuZCgobWVhbihpcy5uYSh4KSkqMTAwKSwgMykpICNwZXJjZW50IG9mIHRvdGFsIG1pc3NpbmcNCmBgYA0KDQo8aDU+UGF0dGVybiBvZiBtaXNzaW5nIHZhbHVlczwvaDU+DQoNClRoZSBvdmVyYWxsIHN0cnVjdHVyZSBhbmQgcGF0dGVybiBvZiBtaXNzaW5nIHZhbHVlcyBpcyByZXByZXNlbnRlZCBpbiB0aGUgYmVsb3cgdGFibGUgdG8gYWxsb3cgZm9yIHRyZW5kIHZpc3VhbGl6YXRpb24uIEVhY2ggY29sdW1uIG9mIHRoZSBiZWxvdyBwbG90IHJlcHJlc2VudHMgYSB2YXJpYWJsZSBmcm9tIHRoZSBgZmhzX3Jhd2AgZGF0YXNldC4gRWFjaCByb3cgcmVwcmVzZW50cyBhIGRpZmZlcmVudCBwYXR0ZXJuIG9mIHByZXNlbnQgdnMuIG1pc3NpbmcgZGF0YSBmb3IgZWFjaCB2YXJpYWJsZS4gPGZvbnQgY29sb3IgPSAiYmx1ZSI+Qmx1ZTwvZm9udD4gZmlsbGVkIGNlbGxzIGluZGljYXRlIGEgZGF0YSBwb2ludCBpcyBhdmFpbGFibGUgZm9yIHRoYXQgdmFyaWFibGUgaW4gdGhlIHJlZmVyZW5jZWQgcGF0dGVybiwgd2hpbGUgPGZvbnQgY29sb3IgPSAicmVkIj5yZWQ8L2ZvbnQ+IHNxdWFyZXMgaW5kaWNhdGUgYSBtaXNzaW5nIHZhbHVlIGZvciB0aGF0IHZhcmlhYmxlIGluIHRoZSByZWZlcmVuY2VkIHBhdHRlcm4uIEZvciBleGFtcGxlLCB0aGUgMm5kIHRvIHRoZSB0b3Agcm93IG9mIHRoZSBwYXR0ZXJuIHRhYmxlIGluY2x1ZGVzIDxmb250IGNvbG9yID0gImJsdWUiPmJsdWU8L2ZvbnQ+IGNlbGxzIGZvciBhbGwgdmFyaWFibGVzLCBleGNlcHQgdGhlIDxmb250IGNvbG9yID0gInJlZCI+cmVkPC9mb250PiBjZWxsIGluIHRoZSBgZ2x1Y29zZWAgY29sdW1uLCBpbmRpY2F0aW5nIHRoZSBvbmx5IG1pc3NpbmcgdmFsdWUgZm9yIHRoYXQgcGF0dGVybiBpcyB3aXRoaW4gdGhlIGBnbHVjb3NlYCB2YXJpYWJsZS4NCg0KVGhlIG51bWVyaWMgdmFsdWVzIG9uIHRoZSBsZWZ0IGhhbmQgc2lkZSByZXByZXNlbnQgdGhlIGZyZXF1ZW5jeSBpbiB3aGljaCB0aGF0IHBhdHRlcm4gb2YgcHJlc2VudCB2cy4gbWlzc2luZyBkYXRhIGlzIHNlZW4gaW4gdGhlIGRhdGFzZXQgYGZoc19yYXdgLiBUaGUgcmlnaHQgaGFuZCB2YWx1ZXMgaW5kaWNhdGUgdGhlIHRvdGFsIG51bWJlciBvZiBtaXNzaW5nIHZhbHVlcyBpbiB0aGF0IGRhdGEgcGF0dGVybi4gRm9yIGV4YW1wbGUsIHRoZSB0b3AgbW9zdCByb3cgaGFzIGEgbGVmdCBoYW5kIHZhbHVlIG9mIDM2NTgsIGFuZCBhIHJpZ2h0IGhhbmQgdmFsdWUgb2YgemVybyAoMCksIGluZGljYXRpbmcgdGhlcmUgYXJlIDM2NTggb2JzZXJ2YXRpb25zIGluIHdoaWNoIHRoZXJlIGFyZSB6ZXJvICgwKSBtaXNzaW5nIHZhbHVlcy4gVGhlIDJuZCB0byB0b3Agcm93IGhhcyBhIGxlZnQgaGFuZCBtaXNzaW5nIHZhbHVlIG9mIDMzMSwgYSByaWdodCBoYW5kIHZhbHVlIG9mIG9uZSAoMSksIGFuZCBhIHJlZCBjZWxsIGluIHRoZSBgZ2x1Y29zZWAgdmFyaWFibGUsIGluZGljYXRpbmcgdGhlcmUgYXJlIDMzMSBvYnNlcnZhdGlvbnMgd2l0aCBvbmUgKDEpIG1pc3NpbmcgdmFsdWUsIHNwZWNpZmljYWxseSBpbiB0aGUgYGdsdWNvc2VgIHZhcmlhYmxlLg0KDQpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9OSwgZmlnLmFsaWduPSdjZW50ZXInfQ0KbWQucGF0dGVybihmaHMsIHJvdGF0ZS5uYW1lcyA9IFRSVUUpDQoNCmBgYA0KDQpBIHRvdGFsIG9mIDE4IHVuaXF1ZSBwYXR0ZXJucyBvZiBwcmVzZW50IHZzLiBtaXNzaW5nIGRhdGEgY29tYmluYXRpb25zIGFyZSBvYnNlcnZlZCBpbiB0aGUgRkhTIHN1YnNldCwgYGZoc19yYXdgLiBUaGUgbW9zdCBmcmVxdWVudGx5IHNlZW4gcGF0dGVybiBpcyBhbGwgdmFsdWVzIHByZXNlbnQsIHRoZSBzZWNvbmQgbW9zdCBmcmVxdWVudCBwYXR0ZXJuIGlzIDEgbWlzc2luZyB2YWx1ZSBpbiB0aGUgYGdsdWNvc2VgIHZhcmlhYmxlLCBhbmQgdGhlIG1vc3QgZnJlcXVlbnQgcGF0dGVybiB3aXRoIG1vcmUgdGhhbiBvbmUgbWlzc2luZyB2YWx1ZSBpcyBhIGNvbWJpbmF0aW9uIG9mIGJvdGggbWlzc2luZyBgdG90Q2hvbGAgYW5kIGBnbHVjb3NlYCB2YWx1ZXMsIGFzIHNlZW4gaW4gdGhpcnR5IGVpZ2h0ICgzOCkgaW5zdGFuY2VzLg0KDQojIyMgSW1wdXRpbmcgdGhlIERhdGENCg0KQXMgdGhlIHZhcmlhYmxlIHdpdGggdGhlIGxhcmdlc3QgdG90YWwgcGVyY2VudGFnZSBvZiBibGFuayB2YWx1ZXMgKDkuMTUxJSBtaXNzaW5nKSB3YXMgYGdsdWNvc2VgLCBhbiBpbXB1dGF0aW9uIG1vZGVsIHdhcyBidWlsdCBhcm91bmQgdGhlIGBnbHVjb3NlYCB2YXJpYWJsZSB0byBjcmVhdGUgdGhlIGJlc3QgcG9zc2libGUgZml0LiBGb3IgdGhpcyBpbXB1dGF0aW9uIG1vZGVsIGZpdHRpbmcsIHRoZSBgZmhzYCBkYXRhIHdhcyBzdWJzZXQgZm9yIG9ubHkgbnVtZXJpY2FsIGNhdGVnb3JpZXMgdG8gYWxsb3cgZm9yIGVhc2Ugb2YgYW5hbHlzaXMgYW5kIHNpbXBsZSBwcmVkaWN0aW9uIHV0aWxpemluZyB0aGUgYE1JQ0VgIGltcHV0YXRpb24gcGFja2FnZS4gT25jZSB0aGlzIHN1YnNldCAoYGZoc19udW1gKSB3YXMgY3JlYXRlZCwgdGhyZWUgZGlmZmVyZW50IE1JQ0UgaW1wdXRhdGlvbiBtb2RlbHMgd2VyZSBjcmVhdGVkOiA8Yj5QcmVkaWN0aXZlIG1lYW4gbWF0Y2hpbmcgKFBNTSkgSW1wdXRhdGlvbjwvYj4sIDxiPkNsYXNzaWZpY2F0aW9uIGFuZCBSZWdyZXNzaW9uIFRyZWVzIChDQVJUKSBJbXB1dGF0aW9uPC9iPiwgYW5kIDxiPkxhc3NvIExpbmVhciBSZWdyZXNzaW9uIEltcHV0YXRpb248L2I+Lg0KDQpgYGB7cn0NCmZoc19udW0gPC0gZmhzICU+JSANCiAgZHBseXI6OnNlbGVjdChhZ2UsIGNpZ3NQZXJEYXksIHRvdENob2wsIHN5c0JQLCBkaWFCUCwgQk1JLCBoZWFydFJhdGUsIGdsdWNvc2UpDQoNCnNldC5zZWVkKDEyMykgI3NldCBzZWVkIHRvIG1haW50YWluIGNvbnNpc3RlbmN5IHdpdGggaW1wdXRhdGlvbg0KDQpmaHNfaW1wIDwtIGRhdGEuZnJhbWUoDQogIG9yaWdpbmFsID0gZmhzX251bSRnbHVjb3NlLA0KICBpbXB1dGVkX3BtbSA9IGNvbXBsZXRlKG1pY2UoZmhzX251bSwgbWV0aG9kID0gInBtbSIsIHByaW50ID0gRkFMU0UpKSRnbHVjb3NlLA0KICBpbXB1dGVkX2NhcnQgPSBjb21wbGV0ZShtaWNlKGZoc19udW0sIG1ldGhvZCA9ICJjYXJ0IiwgcHJpbnQgPSBGQUxTRSkpJGdsdWNvc2UsDQogIGltcHV0ZWRfbGFzc28gPSBjb21wbGV0ZShtaWNlKGZoc19udW0sIG1ldGhvZCA9ICJsYXNzby5ub3JtIiwgcHJpbnQgPSBGQUxTRSkpJGdsdWNvc2UNCikNCg0KYGBgDQoNClRvIGRldGVybWluZSB0aGUgYmVzdCBmaXQgb2YgdGhlIHRocmVlIE1JQ0UgaW1wdXRhdGlvbiBtb2RlbHMgdGVzdGVkIG9uIHRoZSBgZ2x1Y29zZWAgdmFyaWFibGUsIHRoZSBvcmlnaW5hbCBgZ2x1Y29zZWAgZGlzdHJpYnV0aW9uIHdpdGggbWlzc2luZyB2YWx1ZXMgb21pdHRlZCB3YXMgcGxvdHRlZCBvbiBhIGhpc3RvZ3JhbSB0byB2aXN1YWxpemUgdGhlIGRpc3RyaWJ1dGlvbiwgZGVuc2l0eSwgYW5kIG1lYW4gdmFsdWUuIFRoZSB0aHJlZSBpbXB1dGF0aW9uIG1vZGVscyB3ZXJlIHBsb3R0ZWQgd2l0aCB0aGUgc2FtZSBjb25maWd1cmF0aW9uIGFuZCBheGlzIHNjYWxlcyB0byBhbGxvdyBmb3IgZWFzZSBvZiB2aXN1YWwgY29tcGFyaXNvbi4gVGhpcyBhbGxvd3MgZm9yIHZpc3VhbCBjb21wYXJpc29uIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIG9yaWdpbmFsIGBnbHVjb3NlYCB2YXJpYWJsZSBkYXRhLCBjb21wYXJlZCB0byB0aGUgaW1wdXRlZCBgZ2x1Y29zZWAgZGF0YS4gQWRkaXRpb25hbGx5LCB0aGUgbWVhbiBgZ2x1Y29zZWAgdmFsdWUgY2FuIGJlIGNvbXBhcmVkIGJldHdlZW4gdGhlIG9yaWdpbmFsIHZhcmlhYmxlIGRhdGEsIGFuZCB0aGUgaW1wdXRlZCBgZ2x1Y29zZWAgZGF0YS4NCg0KYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTksIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp5X2xpbV9pbXAgPC0gcmFuZ2UoMCwwLjA0KQ0KaW1wLm9yaWcgPC0gZ2dwbG90KGZoc19pbXAsIGFlcyh4PW9yaWdpbmFsKSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksIGZpbGwgPSAib3JhbmdlIiwgYWxwaGEgPSAwLjI1LCBjb2xvciA9ICJvcmFuZ2UiKSArDQogIGdlb21fZGVuc2l0eShhZXMoeCA9IG9yaWdpbmFsKSwgY29sb3IgPSAiZGFya29yYW5nZTQiKSArICAgDQogIHlsaW0oeV9saW1faW1wKSArDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtZWFuKG9yaWdpbmFsLCBuYS5ybSA9IFRSVUUpKSwgY29sb3I9ImRhcmtvcmFuZ2U0Iiwgc2l6ZSA9IDAuNSkgKw0KICBnZW9tX3RleHQoYWVzKHg9bWVhbihvcmlnaW5hbCwgbmEucm0gPSBUUlVFKSwgbGFiZWwgPSByb3VuZChtZWFuKG9yaWdpbmFsLCBuYS5ybSA9IFRSVUUpLCAyKSksIHk9IDAuMDMsIHggPSAxMjAsIGFuZ2xlID0gMCkNCiAgDQppbXAucG1tIDwtIGdncGxvdChmaHNfaW1wLCBhZXMoeCA9IGltcHV0ZWRfcG1tKSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHk9Li5kZW5zaXR5Li4pLCBmaWxsID0gImJsdWUiLCBhbHBoYSA9IDAuMjUsIGNvbG9yID0gImJsdWUiKSArDQogIGdlb21fZGVuc2l0eShhZXMoeD1pbXB1dGVkX3BtbSksIGNvbG9yID0gImRhcmtibHVlIikgKyAgIA0KICB5bGltKHlfbGltX2ltcCkgKw0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVhbihpbXB1dGVkX3BtbSkpLCBjb2xvcj0iZGFya2JsdWUiLCBzaXplID0gMC41KSArDQogIGdlb21fdGV4dChhZXMoeD1tZWFuKGltcHV0ZWRfcG1tLCBuYS5ybSA9IFRSVUUpLCBsYWJlbCA9IHJvdW5kKG1lYW4oaW1wdXRlZF9wbW0sIG5hLnJtID0gVFJVRSksIDIpKSwgeT0gMC4wMywgeCA9IDEyMCwgYW5nbGUgPSAwKQ0KDQppbXAuY2FydCA8LSBnZ3Bsb3QoZmhzX2ltcCwgYWVzKHggPSBpbXB1dGVkX2NhcnQpKSArIA0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeT0uLmRlbnNpdHkuLiksIGZpbGwgPSAicmVkIiwgYWxwaGEgPSAwLjI1LCBjb2xvciA9ICJyZWQiKSArDQogIGdlb21fZGVuc2l0eShhZXMoeD1pbXB1dGVkX2NhcnQpLCBjb2xvciA9ICJkYXJrcmVkIikgKyAgIA0KICB5bGltKHlfbGltX2ltcCkgKw0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVhbihpbXB1dGVkX2NhcnQpKSwgY29sb3I9ImRhcmtyZWQiLCBzaXplID0gMC41KSArDQogIGdlb21fdGV4dChhZXMoeD1tZWFuKGltcHV0ZWRfY2FydCwgbmEucm0gPSBUUlVFKSwgbGFiZWwgPSByb3VuZChtZWFuKGltcHV0ZWRfY2FydCwgbmEucm0gPSBUUlVFKSwgMikpLCB5PSAwLjAzLCB4ID0gMTIwLCBhbmdsZSA9IDApDQoNCmltcC5sYXNzbyA8LSBnZ3Bsb3QoZmhzX2ltcCwgYWVzKHggPSBpbXB1dGVkX2xhc3NvKSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksIGZpbGwgPSAibWFnZW50YSIsIGFscGhhID0gMC4yNSwgY29sb3IgPSAibWFnZW50YSIpICsNCiAgZ2VvbV9kZW5zaXR5KGFlcyh4PWltcHV0ZWRfbGFzc28pLCBjb2xvciA9ICJkYXJrbWFnZW50YSIpICsgICANCiAgeWxpbSh5X2xpbV9pbXApICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1lYW4oaW1wdXRlZF9sYXNzbykpLCBjb2xvcj0iZGFya21hZ2VudGEiLCBzaXplID0gMC41KSArDQogIGdlb21fdGV4dChhZXMoeD1tZWFuKGltcHV0ZWRfbGFzc28sIG5hLnJtID0gVFJVRSksIGxhYmVsID0gcm91bmQobWVhbihpbXB1dGVkX2xhc3NvLCBuYS5ybSA9IFRSVUUpLCAyKSksIHk9IDAuMDMsIHggPSAxMjAsIGFuZ2xlID0gMCkNCg0KDQoNCmdyaWQuYXJyYW5nZShpbXAub3JpZywgaW1wLnBtbSwgaW1wLmNhcnQsIGltcC5sYXNzbywgbnJvdyA9IDIsIG5jb2wgPSAyLCB0b3AgPSB0ZXh0R3JvYigiQ29tcGFycmlzb24gb2YgR2x1Y29zZSBJbXB1dGF0aW9uIE1vZGVscyIpKQ0KYGBgDQoNClVwb24gcmV2aWV3aW5nIHRoZSB2YXJpb3VzIGltcHV0ZWQgYGdsdWNvc2VgIGRhdGEgb3V0cHV0cyBvZiB0aGUgYWJvdmUgTUlDRSBpbXB1dGF0aW9uLCB0aGUgQ0FSVCBpbXB1dGF0aW9uIHdhcyBkZXRlcm1pbmVkIGFzIHRoZSBiZXN0IGZpdCBmb3IgdGhlIGltcHV0YXRpb24gbW9kZWwuIEFzIHZpc2libGUgaW4gdGhlIGFib3ZlIGhpc3RvZ3JhbSBkYXRhLCB0aGUgQ0FSVCBpbXB1dGF0aW9uIG1vZGVsIGlzIHRoZSBvbmx5IG1vZGVsIHRoYXQgcmV0YWluZWQgdGhlIG9yaWdpbmFsIGBnbHVjb3NlYCBkYXRhIG1lYW4sIHdpdGggYSBuZWFyIGlkZW50aWNhbCBkaXN0cmlidXRpb24gYW5kIGRlbnNpdHkuDQoNCkFzIHRoZSByZW1haW5pbmcgdmFyaWFibGVzIHdpdGggbWlzc2luZyB2YWx1ZXMgY29udGFpbiBsZXNzIHRoYW4gMi41JSBtaXNzaW5nIGRhdGEsIGNvbXBhcmVkIHRvIHRoZSA5LjE1MSUgbWlzc2luZyBkYXRhIGZyb20gdGhlIGBnbHVjb3NlYCB2YXJpYWJsZSwgdGhlIGJlc3QgZml0IENBUlQgaW1wdXRhdGlvbiBpcyBhcHBsaWVkIHRvIGFsbCB2YXJpYWJsZXMgaW4gdGhlIGBmaHNgIGRhdGFzZXQuIFRvIG5vdGUsIHRoZSBpbXB1dGF0aW9uIHdhcyBjb21wbGV0ZWQgb24gYWxsIHZhcmlhYmxlcyBmb3IgdGhpcyBzdGVwLCBub3QganVzdCB0aGUgbnVtZXJpYyB2YXJpYWJsZSBzdWJzZXQgdXRpbGl6ZWQgZm9yIGltcHV0YXRpb24gbW9kZWwgc2VsZWN0aW9uLg0KDQpUaGUgbmV3bHkgaW1wdXRlZCBkYXRhc2V0IChgZmhzX2ltcDJgKSB3YXMgdGhlbiB2ZXJpZmllZCB0byBjb250YWluIHplcm8gbWlzc2luZyB2YWx1ZXMNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpzZXQuc2VlZCgxMjMpDQpmaHNfaW1wMiA8LSBmaHMNCmZoc19pbXAyIDwtIGNvbXBsZXRlKG1pY2UoZmhzLCBtZXRob2QgPSAiY2FydCIsIHByaW50ID0gRkFMU0UpKQ0KDQpgYGANCg0KYGBge3J9DQpmaHNfaW1wMiRCUE1lZHMgPC0gYXMubG9naWNhbChhcy5udW1lcmljKGZoc19pbXAyJEJQTWVkcykpDQpzYXBwbHkoZmhzX2ltcDIsIGZ1bmN0aW9uICh4KSBzdW0oaXMubmEoeCkpKQ0Kc3VtbWFyeShmaHNfaW1wMikNCmBgYA0KDQojIyBEaXN0cmlidXRpb24gUmV2aWV3IGFuZCBQYWlyd2lzZSBBbmFseXNpcw0KDQojIyMgQ2F0ZWdvcmljYWwgVmFyaWFibGVzDQoNClRoZSBkaXN0cmlidXRpb25zIGFuZCBmcmVxdWVuY2llcyBvZiB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIChib3RoIGZlYXR1cmUgdmFyaWFibGVzIGFuZCBuYXRpdmUgdmFyaWFibGVzKSBgaGlnaEJQYCwgYHByZXZhbGVudENvbmRgLCBgVGVuWWVhckNIRGAsIGBCUE1lZHNgLCBgZGlhYmV0ZXNgLCBhbmQgYG1hbGVgIHdlcmUgdmlzdWFsaXplZCB1dGlsaXppbmcgYmFyIGNoYXJ0cw0KDQpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9OX0NCnlfbGltIDwtIHJhbmdlKDAsNDIwMCkNCmhpZ2hicC5kZW4gPC0gZ2dwbG90KGZoc19pbXAyLCBhZXMoeCA9IGhpZ2hCUCkpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJsaWdodGJsdWUiKSArICANCiAgeWxpbSh5X2xpbSkgKw0KICBsYWJzKHRpdGxlID0gIkhpZ2ggQmxvb2QgUHJlc3N1cmUNCiAgICAgICAiKQ0KcHJldmNvbmQuZGVuIDwtIGdncGxvdChmaHNfaW1wMiwgYWVzKHggPSBwcmV2YWxlbnRDb25kKSkgKyANCiAgeWxpbSh5X2xpbSkgKw0KICBnZW9tX2JhcihmaWxsID0gImxpZ2h0Ymx1ZSIpICsgDQogIGxhYnModGl0bGUgPSAiUHJldi4gQ29uZGl0aW9ucw0KKFN0cm9rZSAvIEh5cGVydGVuc2lvbiAvIEJvdGgpIikgDQp0ZW55ZWFyLmRlbiA8LSBnZ3Bsb3QoZmhzX2ltcDIsIGFlcyh4ID0gVGVuWWVhckNIRCkpICsgDQogIHlsaW0oeV9saW0pICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJsaWdodGJsdWUiKSArICANCiAgbGFicyh0aXRsZSA9ICJDaHJvbmljIEhlYXJ0IERpc2Vhc2UgDQogIEAgMTAgWWVhciBGb2xsb3cgVXAiKQ0KQlBtZWQuZGVuIDwtIGdncGxvdChmaHNfaW1wMiwgYWVzKHggPSBCUE1lZHMpKSArIA0KICB5bGltKHlfbGltKSArDQogIGdlb21fYmFyKGZpbGwgPSAibGlnaHRibHVlIikgKyAgDQogIGxhYnModGl0bGUgPSAiQmxvb2QgUHJlc3N1cmUgTWVkaWNpbmUNCiAgICAgICAiKQ0KDQpkaWFiZXQuZGVuIDwtIGdncGxvdChmaHNfaW1wMiwgYWVzKHggPSBkaWFiZXRlcykpICsgDQogIHlsaW0oeV9saW0pICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJsaWdodGJsdWUiKSArICANCiAgbGFicyh0aXRsZSA9ICJEaWFiZXRlcw0KICAgICAgICIpDQoNCm1hbGUuZGVuIDwtIGdncGxvdChmaHNfaW1wMiwgYWVzKHggPSBtYWxlKSkgKyANCiAgeWxpbSh5X2xpbSkgKw0KICBnZW9tX2JhcihmaWxsID0gImxpZ2h0Ymx1ZSIpICsgIA0KICBsYWJzKHRpdGxlID0gIk1hbGUNCiAgICAgICAiKQ0KDQpncmlkLmFycmFuZ2UoaGlnaGJwLmRlbiwgcHJldmNvbmQuZGVuLCB0ZW55ZWFyLmRlbiwgQlBtZWQuZGVuLCBkaWFiZXQuZGVuLCBtYWxlLmRlbiwgbnJvdyA9IDIsIG5jb2wgPSAzKQ0KYGBgDQoNCmBgYHtyfQ0KYWdlLmRlbiA8LSBnZ3Bsb3QoZmhzX2ltcDIsIGFlcyh4ID0gYWdlKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGdlb21fZGVuc2l0eShhZXMgKHggPSBhZ2UpLCBjb2xvciA9ICJkYXJrYmx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJBZ2UiKQ0KDQpjaWdzLmRlbiA8LSBnZ3Bsb3QoZmhzX2ltcDIsIGFlcyh4ID0gY2lnc1BlckRheSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBnZW9tX2RlbnNpdHkoYWVzICh4ID0gY2lnc1BlckRheSksIGNvbG9yID0gImRhcmtibHVlIikgKw0KICBsYWJzKHRpdGxlID0gIk51bS4gQ2lncyBTbW9rZWQgUGVyIERheSIpDQoNCmNob2wuZGVuIDwtIGdncGxvdChmaHNfaW1wMiwgYWVzKHggPSB0b3RDaG9sKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGdlb21fZGVuc2l0eShhZXMgKHggPSB0b3RDaG9sKSwgY29sb3IgPSAiZGFya2JsdWUiKSArDQogIGxhYnModGl0bGUgPSAiVG90YWwgQ2hvbGVzdGVyb2wiKQ0KDQpnbHUuZGVuIDwtIGdncGxvdChmaHNfaW1wMiwgYWVzKHggPSBnbHVjb3NlKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGdlb21fZGVuc2l0eShhZXMgKHggPSBnbHVjb3NlKSwgY29sb3IgPSAiZGFya2JsdWUiKSArDQogIGxhYnModGl0bGUgPSAiR2x1Y29zZSIpDQoNCmJwLmRlbiA8LSBnZ3Bsb3QoZmhzX2ltcDIpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHg9c3lzQlAsIHk9Li5kZW5zaXR5Li4sIGZpbGwgPSAiU3lzdG9saWMiICksIGFscGhhID0gMC43NSkgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeD1kaWFCUCwgeT0uLmRlbnNpdHkuLiwgZmlsbCA9ICJEaWFzdG9saWMiKSwgYWxwaGEgPSAwLjc1KSArDQogIGdlb21fZGVuc2l0eShhZXMoeCA9IHN5c0JQLCB5ID0gLi5kZW5zaXR5Li4pLCBjb2xvciA9ICJkYXJrZ3JlZW4iKSArDQogIGdlb21fZGVuc2l0eShhZXMoeCA9IGRpYUJQLCB5ID0gLi5kZW5zaXR5Li4pLCBjb2xvciA9ICJkYXJrYmx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJTeXN0b2xpYyAmIERpYXN0b2xpYyBCbG9vZCBQcmVzc3VyZSIsIGZpbGwgPSAiQmxvb2QgUHJlc3N1cmUgDQogICAgICAgVmFsdWVzIiwgeCA9ICJCbG9vZCBQcmVzc3VyZSBSZWFkaW5nIChtbUhHKSIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygibGlnaHRibHVlIiwgImxpZ2h0Z3JlZW4iKSkNCg0KYm1pLmRlbiA8LSBnZ3Bsb3QoZmhzX2ltcDIsIGFlcyh4ID0gQk1JKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGdlb21fZGVuc2l0eShhZXMgKHggPSBCTUkpLCBjb2xvciA9ICJkYXJrYmx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJCb2R5IE1hc3MgSW5kZXggKEJNSSkiKQ0KDQpoci5kZW4gPC0gZ2dwbG90KGZoc19pbXAyLCBhZXMoeCA9IGhlYXJ0UmF0ZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBnZW9tX2RlbnNpdHkoYWVzICh4ID0gaGVhcnRSYXRlKSwgY29sb3IgPSAiZGFya2JsdWUiKSArDQogIGxhYnModGl0bGUgPSAiSGVhcnQgUmF0ZSIpDQpgYGANCg0KIyMgQSBjdXJzb3J5IHJldmlldyBvZiB0aGUgZGlzdHJpYnV0aW9uIGFuZCBkZW5zaXR5IGRhdGEgZm9yIGFsbCBudW1lcmljYWwgdmFyaWFibGVzIHJldmVhbHMgbXVsdGlwbGUgbm9uLW5vcm1hbCBkaXN0cmlidXRpb25zLg0KDQpgYGB7ciwgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9OX0NCmdyaWQuYXJyYW5nZShhZ2UuZGVuLCBjaWdzLmRlbiwgY2hvbC5kZW4sIGdsdS5kZW4sIGJtaS5kZW4sIGhyLmRlbiwgbnJvdyA9IDIsIG5jb2wgPSAzKQ0KYGBgDQoNCmBgYHtyLCAgZmlnLndpZHRoPTl9DQpncmlkLmFycmFuZ2UoYnAuZGVuLCBucm93ID0gMSwgbmNvbCA9IDIpDQpgYGANCg0KSW4gb3JkZXIgdG8gYWRkcmVzcyB0aGUgcHJhY3RpY2FsIHF1ZXN0aW9uIG9mIGFzc2Vzc2luZyB0aGUgaW1wYWN0IG9mIHZhcmlvdXMgaGVhbHRoIG1lYXN1cmVzIG9uIHBhdGllbnQgQm9keSBNYXNzIEluZGV4IChgQk1JYCkgdmFsdWUsIG9ubHkgY2xpbmljYWxseSByZWxhdGVkIG1lYXN1cmVzIHdpbGwgYmUgcmV0YWluZWQgZm9yIGZ1cnRoZXIgYW5hbHlzaXMuDQoNCi0gICBTeXN0b2xpYyBgc3lzQlBgIGFuZCBkaWFzdG9saWMgYGRpYUJQYCB2YXJpYWJsZXMgd2VyZSBjb21iaW5lZCBpbnRvIGEgc2luZ2xlIGNhdGVnb3JpY2FsIGZlYXR1cmUgdmFyaWFibGUgYGhpZ2hCUGAsIGJvdGggb2YgdGhlc2UgdmFyaWFibGVzIHdpbGwgYmUgb21pdHRlZCBmcm9tIGZ1cnRoZXIgYW5hbHlzaXMNCg0KLSAgIFRoZSBCb2R5IE1hc3MgSW5kZXggKGBCTUlgKSBzY2FsZSBhbHJlYWR5IGFjY291bnRzIGZvciBhZ2UgaW4gQk1JIGNhbGN1bGF0aW9uLCBzbyB0aGUgYGFnZWAgdmFyaWFibGUgd2lsbCBiZSBvbWl0dGVkIGZyb20gZnVydGhlciBhbmFseXNpcw0KDQotICAgVG90YWwgbnVtYmVyIG9mIGNpZ2FyZXR0ZXMgc21va2VkIHBlciBkYXkgKGBjaWdzUGVyRGF5YCkgaXMgbm90IGNsaW5pY2FsbHkgbGlua2VkIHRvIEJNSSBhbmQgd2lsbCB0aHVzIGJlIG9taXR0ZWQgZnJvbSBmdXJ0aGVyIGFuYWx5c2lzLiBXaGlsZSBjaWdhcmV0dGUgY29uc3VtcHRpb24gbWF5IGltcGFjdCBjYXJkaW92YXNjdWxhciBoZWFsdGggb3Igb3RoZXIgaGVhbHRoIGluZGljYXRvcnMsIHRoZXJlIGlzIG5vIGNsaW5pY2FsIHNpZ25pZmljYW5jZSB0byBCTUkuDQoNCi0gICBUb3RhbCBjaG9sZXN0ZXJvbCBgdG90Q2hvbGAgaXMgYSBjb21tb24gbWVhc3VyZSBvZiBjYXJkaW92YXNjdWxhciBmaXRuZXNzLiBBcyBjYXJkaW92YXNjdWxhciBmaXRuZXNzIHJhdGVzIGFyZSBvZnRlbiBhc3NvY2lhdGVkIHdpdGggbG93ZXIgQk1JIHZhbHVlcywgdG90YWwgY2hvbGVzdGVyb2wgd2lsbCBiZSBpbmNsdWRlZCBpbiBmdXJ0aGVyIGFuYWx5c2lzIHRvIGV4YW1pbmUgaWYgdG90YWwgY2hvbGVzdGVyb2wgdmFsdWVzIGFyZSBhIGdvb2QgcHJlZGljdG9yIHZhbHVlIGZvciBCTUkuDQoNCi0gICBHbHVjb3NlIGBnbHVjb3NlYCBsZXZlbHMgYXJlIG9mdGVuIGNsaW5pY2FsbHkgcmVsYXRlZCB0byB3ZWlnaHQgcmVsYXRlZCBkaXNvcmRlcnMsIHN1Y2ggYXMgdHlwZS1JSSBkaWFiZXRlcy4gQXMgd2VpZ2h0IGlzIGNsaW5pY2FsbHkgYXNzb2NpYXRlZCB3aXRoIGhpZ2hlciBCTUkgdmFsdWVzLCB0aGUgZ2x1Y29zZSBkYXRhIHdpbGwgYmUgaW5jbHVkZWQgaW4gZnVydGhlciBhbmFseXNpcyB0byBleGFtaW5lIGlmIGdsdWNvc2UgaXMgYSBnb29kIHByZWRpY3RvciB2YWx1ZSBmb3IgQk1JLg0KDQpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9OX0NCmdncGFpcnMoZmhzX2ltcDIsIGNvbHVtbnMgPSBjKDEwLDEzOjE1KSwgYWVzKGFscGhhID0gMC4wNSksIGxvd2VyID0gbGlzdChjb250aW51b3VzID0gd3JhcCgic21vb3RoIiwgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3VyID0gImhvdHBpbmsiLCBhbHBoYSA9IDAuMjUpKSwgdXBwZXIgPSBsaXN0KGNvbnRpbnVvdXMgPSB3cmFwKCJjb3IiKSkpDQpgYGANCg0KSW4gcmV2aWV3aW5nIHRoZSBwYWlyd2lzZSBhbmFseXNpcywgdGhlIGZvbGxvd2luZyBwYXR0ZXJucyB3ZXJlIGlkZW50aWZpZWQ6DQoNCi0gICBCb3RoIGdsdWNvc2UgKGBnbHVjb3NlYCkgYW5kIHRvdGFsIGNob2xlc3Rlcm9sIChgdG90Q2hvbGApIGFyZSBsaW5lYXJseSBjb3JyZWxhdGVkIHdpdGggdGhlIHJlc3BvbnNlIHZhcmlhYmxlIEJvZHkgTWFzcyBJbmRleCAoYEJNSWApDQotICAgYGdsdWNvc2VgIGFuZCBgdG90Q2hvbGAgYXJlIG5vdCBsaW5lYXJseSBjb3JyZWxhdGVkLCBpbmRpY2F0aW5nIHRoZXJlIHdpbGwgYmUgbm8gY28tbGluZWFyaXR5IGlzc3VlcyB3aGVuIGNvbXBhcmluZyB3aXRoIGEgbGluZWFyIHJlZ3Jlc3Npb24uDQotICAgTWluaW1hbCBvdXRsaWVycyBpZGVudGlmaWVkIGZvciBwb3RlbnRpYWwgcmVtb3ZhbCwgYW5kIG5vIHNpZ25pZmljYW50bHkgc2tld2VkIGRpc3RyaWJ1dGlvbnMgc2Vlbi4NCg0KIyBMaW5lYXIgUmVncmVzc2lvbg0KDQpMaW5lYXIgcmVncmVzc2lvbiBjYW4gYmUgdXRpbGl6ZWQgdG8gYXNzZXNzIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIGRlcGVuZGVudCB2YXJpYWJsZSwgQm9keSBNYXNzIEluZGV4IChCTUkpLCBhbmQgYSB2YXJpZXR5IG9mIHByZWRpY3RvciB2YXJpYWJsZXMuIE11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVscyB1dGlsaXppbmcgY2xpbmljYWxseSBhc3NvY2lhdGVkIHByZWRpY3RvciB2YXJpYWJsZXMgd2VyZSBjcmVhdGVkIGFuZCBhbmFseXplZCBmb3IgdGhlaXIgcmVzcGVjdGl2ZSBnb29kbmVzcyBvZiBmaXQgdXNpbmcgUl4yXiB2YWx1ZXMuDQoNClRoZSBmb2xsb3dpbmcgYXNzdW1wdGlvbnMgaGF2ZSBiZWVuIG1ldCB0byBzdXBwb3J0IGxpbmVhciByZWdyZXNzaW9uOg0KDQotICAgUHJlZGljdG9yIHZhcmlhYmxlcyBhcmUgbGluZWFybHkgY29ycmVsYXRlZCB0byB0aGUgcmVzcG9uc2UgdmFyaWFibGUNCi0gICBObyBzZXJpb3VzIGNvbGluZWFyaXR5IGlkZW50aWZpZWQgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQotICAgUmVzcG9uc2UgdmFyaWFibGUgaXMgbm9ybWFsbHkgZGlzdHJpYnV0ZWQNCi0gICBSZXNwb25zZSB2YXJpYWJsZSB2YXJpYW5jZSBpcyBjb25zdGFudA0KLSAgIE5vIG1pc3NpbmcgcHJlZGljdG9yIHZhcmlhYmxlcw0KDQojIyBDcmVhdGluZyBNdWx0aXBsZSBDYW5kaWRhdGUgTW9kZWxzDQoNClRvIHN0YXJ0LCB0d28gbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWxzIHV0aWxpemluZyBgdG90Q2hvbGAgYW5kIGBnbHVjb3NlYCBwcmVkaWN0b3IgdmFyaWFibGVzIHdpdGggYSBgQk1JYCBhcyBhIHJlc3BvbnNlIHZhcmlhYmxlIHdlcmUgY3JlYXRlZC4gVGhlIGZpcnN0IG1vZGVsIGNvbXBhcmVzIEJNSSB3aXRoIHRvdGFsIGNob2xlc3Rlcm9sIChgdG90Q2hvbGApLCBnbHVjb3NlIChgZ2x1Y29zZWApLCBhbmQgaGVhcnQgcmF0ZSAoYGhlYXJ0UmF0ZWApIGluIHRoZWlyIG5hdGl2ZSBmb3JtYXRzLCB3aGlsZSB0aGUgc2Vjb25kIG1vZGVsIGNvbXBhcmVzIHRoZSBsb2cgdHJhbnNmb3JtYXRpb24gb2YgYEJNSWAgd2l0aCB3aXRoIHRvdGFsIGNob2xlc3Rlcm9sIChgdG90Q2hvbGApIGFuZCBnbHVjb3NlIChgZ2x1Y29zZWApIGluIHRoZWlyIG5hdGl2ZSBmb3JtYXRzLg0KDQpgYGB7cn0NCmhyLmNob2wuZ2x1IDwtIChsbShCTUkgfiBoZWFydFJhdGUgKyB0b3RDaG9sICsgZ2x1Y29zZSwgZGF0YSA9IGZoc19pbXAyKSkNCg0KYGBgDQoNCiMjIyBJbml0aWFsIE1vZGVsIChoci5nbHUuY2hvbCkNCg0KVGhlIGJlbG93IHBhbmRlciB0YWJsZSBpbmRpY2F0ZXMgdGhhdCBgdG90Q2hvbGAgaGFzIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50IHdpdGggdGhlIG1vc3Qgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIChwLXZhbHVlIDwgMC4wNSBpbmRpY2F0ZXMgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgZnJvbSB6ZXJvICgwKSBhbmQgcC12YWx1ZXx0b3RDaG9sID0gMS4yNTFlXi0xMl4gaW5kaWNhdGluZyBzdHJvbmcgc3RhdGlzdGljIHNpZ25pZmljYW5jZSkuIFdpdGggYSByZWdyZXNzaW9uIGNvZWZmaWNpZW50IG9mIDAuMDA5OTE4LCB0aGlzIGluZGljYXRlcyB0aGF0IGZvciBlYWNoIGluY3JlYXNlIG9mIDEgbWcvZEwgaW4gYSBwYXRpZW50J3MgdG90YWwgY2hvbGVzdGVyb2wgdmFsdWUsIHRoZSBCTUkgd2lsbCBpbmNyZWFzZSBieSAwLjAwOTkxOC4gQWRkaXRpb25hbGx5LCB0aGUgc2Vjb25kIG1vc3Qgc2lnbmlmaWNhbnQgdmFyaWFibGUgd2FzIGlkZW50aWZpZWQgYXMgZ2x1Y29zZSwgd2l0aCBhIHAtdmFsdWV8Z2x1Y29zZSA9IDYuNmVeLTVeIHdpdGggYSByZWdyZXNzaW9uIGNvZWZmaWNpZW50IG9mIDAuMDExODIuIFRoaXMgaW5kaWNhdGVzIHRoYXQgZm9yIGV2ZXJ5IDEgbWcvZEwgaW5jcmVhc2UgaW4gcGF0aWVudCBnbHVjb3NlLCBCTUkgd2lsbCBpbmNyZWFzZSAwLjAxMTgyLiBGaW5hbGx5IHRoZSB0aGlyZCBtb3N0IHNpZ25pZmljYW50IHZhcmlhYmxlIHdhcyBpZGVudGlmaWVkIGFzIGhlYXJ0IHJhdGUsIHdpdGggYSBwLXZhbHVlfGhlYXJ0cmF0ZSA9IDAuMDAwODAwNyBpbmRpY2F0aW5nIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZS4gV2l0aCBhIHJlZ3Jlc3Npb24gY29lZmZpY2llbnQgb2YgMC4wMTcyNCwgYW4gaW5jcmVhc2Ugb2YgMSBicG0gaW4gYSBwYXRpZW50J3MgaGVhcnQgcmF0ZSBpcyBhc3NvY2lhdGVkIHdpdGggYW4gMC4wMTcyNCBpbmNyZWFzZSBpbiBCTUkuIA0KDQo8Yj5BbiBvdmVyYWxsIFJeMl4gdmFsdWUgb2YgMC4wMjE3Nzk1IGluZGljYXRlcyB0aGF0IDIuMTQ3OTUlIG9mIHRoZSB2YXJpYW5jZSBpbiBCTUkgZGF0YSBpcyBhc3NvY2lhdGVkIHdpdGggdGhlIGNvbWJpbmF0aW9uIG9mIGdsdWNvc2UsIGhlYXJ0IHJhdGUsIGFuZCB0b3RhbCBjaG9sZXN0ZXJvbCBsZXZlbHMuPC9iPg0KDQoNCkluIHZpZXdpbmcgdGhlIHJlc2lkdWFsIHBsb3RzIHRoZSBmb2xsb3dpbmcgaW5mb3JtYXRpb24gaXMgb2J0YWluZWQgcmVnYXJkaW5nIHRoZSBpbml0aWFsIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIChgaHIuZ2x1LmNob2xgKToNCg0KLSAgIFRocmVlIG9ic2VydmF0aW9ucyBleGlzdCBhcyBvdXRsaWVycyAoT0JTICMgNzksIDI2NTgsICYgMzkyOCkNCi0gICBUaGVyZSBpcyBhIG1pbm9yIHZpb2xhdGlvbiBvZiB0aGUgYXNzdW1wdGlvbiBvZiBjb25zdGFudCB2YXJpYW5jZSwgd2l0aCBib3RoIHRoZSBSZXNpZHVhbHMgdnMgRml0dGVkICYgUS1RIHJlc2lkdWFscyBncmFwaHMgaW5kaWNhdGluZyBuZWFyIGNvbnN0YW50IHZhcmlhbmNlLCB3aXRoIG91dGxpZXJzLg0KLSAgIFRoZXJlIGlzIGEgbW9kZXJhdGUgdmlvbGF0aW9uIG9mIHRoZSBhc3N1bXB0aW9uIG9mIG5vcm1hbCBkaXN0cmlidXRpb24gb2YgdGhlIHJlc2lkdWFscyBhcyBldmlkZW50IGJ5IHRoZSBzaGFwZSBvZiB0aGUgUS1RIHJlc2lkdWFscyBwbG90Lg0KDQpgYGB7cn0NCnBhbmRlcjo6cGFuZGVyKHN1bW1hcnkoaHIuY2hvbC5nbHUpJGNvZWYsIGNhcHRpb24gPSAiU3VtbWFyeSBzdGF0aXN0aWNzIG9mIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyBvZiB0aGUgaW5pdGlhbCAzIHByZWRpY3RvciB2YXJpYWJsZSBtb2RlbCIpDQprYWJsZShzdW1tYXJ5KGhyLmNob2wuZ2x1KSRyLnNxdWFyZWQsIGNhcHRpb24gPSAiUl4yXiB2YWx1ZSBvZiBJbnRpYWwgTW9kZWwiKQ0KcGFyKG1mcm93PWMoMiwyKSwgbWFyPWMoMiwzLDIsMikpDQpwbG90KGhyLmNob2wuZ2x1KQ0KYGBgDQoNCiMjIyBTZWNvbmQgTW9kZWwgKGdsdS5jaG9sKQ0KDQpCZWZvcmUgY29tcGxldGluZyBhIGxvZyB0cmFuc2Zvcm1hdGlvbiwgYSBCb3gtQ294IHRyYW5zZm9ybWF0aW9uIHRvIGlkZW50aWZ5IGEgcG90ZW50aWFsIHBvd2VyIHRyYW5zZm9ybWF0aW9uIG9mIHRoZSByZXNwb25zZSB2YXJpYWJsZSwgYEJNSWAsIHdhcyBjb21wbGV0ZWQuIFV0aWxpemluZyB0aGUgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWwsIGl0IHdhcyBkZXRlcm1pbmVkIHRoYXQgdGhlIG5hdHVyYWwgbG9nIGlzIG5vdCB0aGUgYmVzdCB0cmFuc2Zvcm1hdGlvbiBmb3IgdGhpcyByZWdyZXNzaW9uIG1vZGVsLiAgVGhpcyBvdXRwdXQgb2NjdXJzIHdoZW4gdGhlIGRlcGVuZGVudCB2YXJpYWJsZSwgYEJNSWAgaXMgbm90IG5vcm1hbGx5IGRpc3RyaWJ1dGVkLiAgVGhlIGRpZmZlcmVuY2UgaW4gYWN0dWFsIGBCTUlgIGRpc3RyaWJ1dGlvbiBhbmQgdGhlIGh5cG90aGV0aWNhbCBgQk1JYCBub3JtYWwgZGlzdHJpYnV0aW9uIHV0aWxpemluZyB0aGUgcmVhbCBgQk1JYCBzdGFuZGFyZCBkZXZpYXRpb24gYW5kIG1lYW5zIHZhbHVlcyBhcmUgdmlzaWJsZSBpbiB0aGUgZGlzdHJpYnV0aW9uIHBsb3QgYmVsb3cuIA0KYGBge3J9DQpiYy5ibWkgPC0gYm94Y294KEJNSSB+IHRvdENob2wgKyBnbHVjb3NlLA0KICAgICAgIGRhdGEgPSBmaHNfaW1wMiwNCiAgICAgICBsYW1iZGEgPSBzZXEoLTEsIDEsIGxlbmd0aCA9IDIwKSwNCiAgICAgICB4bGFiID0gZXhwcmVzc2lvbihwYXN0ZShsYW1iZGEpKSkNCnRpdGxlKG1haW4gPSAnQm94LUNveCBUcmFuc2Zvcm1hdGlvbjogOTUlIENJIG9mIGxhbWJkYScpDQoNCmJtaS5kZW4gKyANCiAgZ2VvbV9saW5lKHN0YXQgPSAiZGVuc2l0eSIsIGFlcyh4ID0gQk1JLCBjb2xvciA9ICJCTUkgRGVuc2l0eSBEaXN0cmlidXRpb24iKSwgc2l6ZSA9IDAuNykgKw0KICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGRub3JtLCBhcmdzID0gbGlzdChtZWFuPW1lYW4oZmhzX2ltcDIkQk1JKSwgc2Q9c2QoZmhzX2ltcDIkQk1JKSksIGFlcyhjb2xvciA9ICJTdGFuZGFyZCBOb3JtYWwgQ3VydmUiKSwgc2l6ZSA9IDAuNywgbGluZXR5cGUgPSAiZGFzaGVkIikgKyANCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoICJCTUkgRGVuc2l0eSBEaXN0cmlidXRpb24iID0gImJsdWUiLCAiU3RhbmRhcmQgTm9ybWFsIEN1cnZlIiA9ICJibGFjayIpKSArIA0KICBsYWJzKCB4ID0gIkJNSSIsIHkgPSAiRGVuc2l0eSIsIHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBCTUkgZGF0YSB2cyBOb3JtYWwgRGlzdHJpYnV0aW9uIiwgY29sb3IgPSAiTGVnZW5kIikNCiAgDQogIA0KDQpgYGANCg0KQXMgc2hvd24gaW4gdGhlIHByZXZpb3VzIHNlY3Rpb25zIGFsb25nIHdpdGggdGhlIDk1JSBjb25maWRlbmNlIGludGVydmFsIGZyb20gdGhlIEJveC1Db3ggYW5hbHlzaXMsIHRoZSBgQk1JYCBkYXRhIGlzIG5lYXIgbm9ybWFsbHkgZGlzdHJpYnV0ZWQsIGJ1dCBub3QgcXVpdGUuICBJbiBvcmRlciB0byBkZXRlcm1pbmUgdGhlIGJlc3QgZml0IGxhbWJkYSB2YWx1ZSwgeCB2YWx1ZSBhc3NvY2lhdGVkIHdpdGggdGhlIHRoZSBtYXhpbXVtIHkgdmFsdWUgZnJvbSB0aGUgYm94LWNveCB0cmFuc2Zvcm1hdGlvbiBwbG90IHdhcyB1dGlsaXplZCA8Yj5gWy0wLjU5NTk1OTZdYDwvYj4uDQoNCg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFfQ0Kb3AubGFtYmRhIDwtIGJjLmJtaSR4W3doaWNoLm1heChiYy5ibWkkeSldDQoNCnByaW50KG9wLmxhbWJkYSkNCmBgYA0KDQpVdGlsaXppbmcgdGhlIGNhbGN1bGF0ZWQgYmVzdCBmaXQgJFxsYW1iZGEkIHZhbHVlIG9mIC0wLjU5NTk1OTYsIHRoZSBmb2xsb3dpbmcgQm94LUNveCB0cmFuc2Zvcm1hdGlvbiB3YXMgY29tcGxldGVkIG9uIHRoZSBgZmhzX2ltcDIkQk1JYCBkYXRhOg0KDQokJFxtYXRoYmZ7eX0gPSBcZnJhY3t7eF5cbGFtYmRhfS0xfXtcbGFtYmRhfSQkICANCg0KPGZvbnQgYWxpZ24gPSAiY2VudGVyIj48Yj5pZjwvYj48L2ZvbnQ+ICANCg0KJCRcbWF0aGJme1xsYW1iZGF9XG5lcTAkJA0KdGh1cyBjcmVhdGluZyB0aGUgcG93ZXIgdHJhbnNmb3JtZWQgQk1JIGRhdGEgKHkpLCB1bmRlciB0aGUgYGJjMi5ibWlgIHZhcmlhYmxlLiANCmBgYHtyfQ0KDQpmaHNfaW1wMiRiYzIuYm1pIDwtICgoZmhzX2ltcDIkQk1JKipvcC5sYW1iZGEpLTEpLyhvcC5sYW1iZGEpDQpjaG9sLmdsdSA8LSBsbShiYzIuYm1pIH4gdG90Q2hvbCAqIGdsdWNvc2UsIGRhdGEgPSBmaHNfaW1wMikNCmBgYA0KDQoNClRoZSBiZWxvdyBwYW5kZXIgdGFibGUgaW5kaWNhdGVzIHRoYXQgYHRvdENob2xgIGhhcyB0aGUgcmVncmVzc2lvbiBjb2VmZmljaWVudCB3aXRoIHRoZSBtb3N0IHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSAocC12YWx1ZSA8IDAuMDUgaW5kaWNhdGVzIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IGZyb20gemVybyAoMCkgYW5kIHAtdmFsdWV8dG90Q2hvbCA9IDMuMzY0ZV4tMTJeIGluZGljYXRpbmcgc3Ryb25nIHN0YXRpc3RpYyBzaWduaWZpY2FuY2UpLiBXaXRoIGEgcmVncmVzc2lvbiBjb2VmZmljaWVudCBvZiAwLjAwMDE2NTcsIHRoaXMgaW5kaWNhdGVzIHRoYXQgZm9yIGVhY2ggaW5jcmVhc2Ugb2YgMSBtZy9kTCBpbiBhIHBhdGllbnQncyB0b3RhbCBjaG9sZXN0ZXJvbCB2YWx1ZSwgdGhlIEJveC1Db3ggdHJhbnNmb3JtZWQgQk1JIHZhbHVlIHdpbGwgaW5jcmVhc2UgYnkgMC4wMDAxNjU3LiBBZGRpdGlvbmFsbHksIHRoZSBzZWNvbmQgbW9zdCBzaWduaWZpY2FudCB2YXJpYWJsZSB3YXMgaWRlbnRpZmllZCBhcyBnbHVjb3NlLCB3aXRoIGEgcC12YWx1ZXxnbHVjb3NlID0gNS44MzllXi0wOF4gd2l0aCBhIHJlZ3Jlc3Npb24gY29lZmZpY2llbnQgb2YgMC4wMDAzNzQ0LiBUaGlzIGluZGljYXRlcyB0aGF0IGZvciBldmVyeSAxIG1nL2RMIGluY3JlYXNlIGluIHBhdGllbnQgZ2x1Y29zZSx0aGUgQm94LUNveCB0cmFuc2Zvcm1lZCBCTUkgdmFsdWUgd2lsbCBpbmNyZWFzZSAwLjAwMDM3NDQuIEZpbmFsbHkgdGhlIGludGVyYWN0aW9uIGJldHdlZW4gYHRvdENob2xgIGFuZCBgZ2x1Y29zZWAgd2FzIGV4YW1pbmVkLCBhbmQgZm91bmQgdG8gaGF2ZSBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2UgcGVyIHRoZSBldmFsdWF0ZWQgcCB2YWx1ZSAocCB2YWx1ZSA8IDAuMDUgaW5kaWNhdGVzIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IGZyb20gemVybyAoMCkgYW5kIHAgdmFsdWV8dG90YWxDaG9sOmdsdWNvc2UgPSA0LjExOGVeLTA2XikuIFRoZSBzaWduaWZpY2FuY2Ugb2YgdGhpcyByZWxhdGlvbnNoaXAgaW5kaWNhdGVzIHRoYXQgdGhlIEJveC1Db3ggdHJhbnNmb3JtZWQgQk1JIHZhbHVlIGlzIGltcGFjdGVkIGJ5IHRoZSBwcm9kdWN0IG9mIGJvdGggdGhlIHRvdGFsIGNob2xlc3Rlcm9sIGFuZCBnbHVjb3NlIHZhbHVlczsgbWVhbmluZyB0aGUgb3ZlcmFsbCBpbXBhY3Qgb2YgdGhlIG5ldCBjaGFuZ2UgaW4gdGhlIHByb2R1Y3Qgb2YgYHRvdENob2xgIGFuZCBgZ2x1Y29zZWAgb24gYEJNSWAgaXMgaW1wYWN0ZWQgYnkgY2hhbmdlcyB0byBlaXRoZXIgdGhlIGB0b3RDaG9sYCwgYGdsdWNvc2VgLCBvciBib3RoIHZhcmlhYmxlcy4gIFRoZSBpbXBhY3Qgb2YgdGhpcyByZWxhdGlvbnNoaXAgaXMgY2FwdHVyZWQgaW4gdGhlIHJlZ3Jlc3Npb24gY29lZmZpY2llbnQgb2YgIFRoaXMgaW50ZXJhY3Rpb24ncyByZWdyZXNzaW9uIGNvZWZmaWNpZW50IHZhbHVlIG9mIC0xLjI0MmVeLTA2XiBpbmRpY2F0ZXMgdGhhdCBhcyB0aGUgcHJvZHVjdCBvZiBgdG90Q2hvbGAgYW5kIGBnbHVjb3NlYCBpbmNyZWFzZWQgYnkgMSBtTF4yXi9kTF4yXiwgdGhlIEJveC1Db3ggdHJhbnNmb3JtZWQgQk1JIHZhbHVlIGRlY3JlYXNlcyBieSAtMS4yNDJlXi0wNl4uIA0KDQo8Yj5BbiBvdmVyYWxsIFJeMl4gdmFsdWUgb2YgMC4wMjYxOTAzIGluZGljYXRlcyB0aGF0IDIuNjE5MDMlIG9mIHRoZSB2YXJpYW5jZSBpbiB0aGUgQm94LUNveCB0cmFuc2Zvcm1lZCBCTUkgZGF0YSBpcyBhc3NvY2lhdGVkIHdpdGggdGhlIHByb2R1Y3Qgb2YgZ2x1Y29zZSBhbmQgdG90YWwgY2hvbGVzdGVyb2wgbGV2ZWxzLjwvYj4NCg0KSW4gdmlld2luZyB0aGUgcmVzaWR1YWwgcGxvdHMgdGhlIGZvbGxvd2luZyBpbmZvcm1hdGlvbiBpcyBvYnRhaW5lZCByZWdhcmRpbmcgdGhlIHNlY29uZCBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCAoYGNob2wuZ2x1YCk6DQoNCi0gICBUaHJlZSBvYnNlcnZhdGlvbnMgZXhpc3QgYXMgb3V0bGllcnMgKE9CUyAjIDM3OCwgMjY1OCwgJiAzOTI4KQ0KLSAgIFRoZXJlIGlzIGEgbWlub3IgdmlvbGF0aW9uIG9mIHRoZSBhc3N1bXB0aW9uIG9mIGNvbnN0YW50IHZhcmlhbmNlLCB3aXRoIGJvdGggdGhlIFJlc2lkdWFscyB2cyBGaXR0ZWQgJiBRLVEgcmVzaWR1YWxzIGdyYXBocyBpbmRpY2F0aW5nIG5lYXIgY29uc3RhbnQgdmFyaWFuY2UsIHdpdGggb3V0bGllcnMuICBUaGUgdmFyaWFuY2UgaXMgdmlzaWJseSBpbXByb3ZlZCBmcm9tIHRoYXQgc2VlbiBmb3IgdGhlIGluaXRpYWwgbW9kZWwuIA0KLSAgIFRoZXJlIGlzIGEgbW9kZXJhdGUgdmlvbGF0aW9uIG9mIHRoZSBhc3N1bXB0aW9uIG9mIG5vcm1hbCBkaXN0cmlidXRpb24gb2YgdGhlIHJlc2lkdWFscyBhcyBldmlkZW50IGJ5IHRoZSBzaGFwZSBvZiB0aGUgUS1RIHJlc2lkdWFscyBwbG90LlRoZSB2YXJpYW5jZSBpcyB2aXNpYmx5IGltcHJvdmVkIGZyb20gdGhhdCBzZWVuIGZvciB0aGUgaW5pdGlhbCBtb2RlbC4gDQoNCg0KYGBge3J9DQpwYW5kZXI6OnBhbmRlcihzdW1tYXJ5KGNob2wuZ2x1KSRjb2VmLCBjYXB0aW9uID0gIlN1bW1hcnkgc3RhdGlzdGljcyBvZiB0aGUgcmVncmVzc2lvbiBjb2VmZmljaWVudHMgb2YgdGhlIFNlY29uZCBNb2RlbCIpDQprYWJsZShzdW1tYXJ5KGNob2wuZ2x1KSRyLnNxdWFyZWQsIGNhcHRpb24gPSAiUl4yXiB2YWx1ZSBvZiBTZWNvbmQgTW9kZWwiKQ0KcGFyKG1mcm93PWMoMiwyKSwgbWFyPWMoMiwzLDIsMikpDQpwbG90KGNob2wuZ2x1KQ0KYGBgDQojIyMgVGhpcmQgTW9kZWwgKGNob2wuZ2x1MikNCkFuIGluZmx1ZW50aWFsIFZhbHVlIChPQlMgMzg0NSkgd2FzIHJlbW92ZWQgYmFzZWQgb24gdGhlIHJlc3VsdHMgaW5kaWNhdGVkIGluIHRoZSBhYm92ZSBSZXNpZHVhbHMgdnMgTGV2ZXJhZ2UgcGxvdCBmb3IgdGhlIHNlY29uZCBtb2RlbCBgY2hvbC5nbHUyYC4gIFRoZSByZXN1bHRhbnQgdGhlIG1vZGVsIHdhcyByZS1yZXZpZXdlZCBhcyBgY2hvbC5nbHUyYCwgYW5kIGFsbCBvYnNlcnZhdGlvbnMgd2VyZSBub3cgc2hvd24gdG8gcmVtYWluIHdpdGhpbiBDb29rJ3MgZGlzdGFuY2UsIGluZGljYXRpbmcgbm8gaW5mbHVlbnRpYWwgdmFsdWVzIHJlbWFpbi4gIFRoaXMgdGhpcmQgbW9kZWwgaWxsdXN0cmF0ZWQgYSBwb3RlbnRpYWwgYmV0dGVyIGZpdCB3aXRoIGFuIGluY3JlYXNlZCBSXjJeIHZhbHVlIG9mIDAuMDc3MzM2NSBpbmRpY2F0aW5nIDIuNzMzNjUlIHZhcmlhbmNlIGluIEJveC1Db3ggdHJhbnNmb3JtZWQgQk1JIHZhcmlhYmxlIGF0dHJpYnV0YWJsZSB0byB0aGUgcHJvZHVjdCBvZiBnbHVjb3NlIGFuZCB0b3RhbCBjaG9sZXN0ZXJvbCB2YWx1ZXMsIHJlZHVjZWQgdmFyaWFuY2UgYXMgc2VlbiBpbiB0aGUgUS1RIFJlc2lkdWFscyBwbG90LCBhbmQgcmVkdWNlZCBvdXRsaWVycyBvbiB0aGUgUmVzaWR1YWxzIHZzIEZpdHRlZCBwbG90LiANCg0KYGBge3J9DQpmaHNfaW1wMm1vZDMgPC0gZmhzX2ltcDJbLTM4NDUsXQ0KY2hvbC5nbHUyIDwtIGxtKGJjMi5ibWkgfiB0b3RDaG9sICogZ2x1Y29zZSwgZGF0YSA9IGZoc19pbXAybW9kMykNCnBhbmRlcjo6cGFuZGVyKHN1bW1hcnkoY2hvbC5nbHUyKSRjb2VmLCBjYXB0aW9uID0gIlN1bW1hcnkgc3RhdGlzdGljcyBvZiB0aGUgcmVncmVzc2lvbiBjb2VmZmljaWVudHMgb2YgdGhlIFNlY29uZCBNb2RlbCBXaXRob3V0IEluZmx1ZW50aWFsIFZhcmlhYmxlIikNCmthYmxlKHN1bW1hcnkoY2hvbC5nbHUyKSRyLnNxdWFyZWQsIGNhcHRpb24gPSAiUl4yXiB2YWx1ZSBvZiBTZWNvbmQgTW9kZWwgd2l0aG91dCBJbmZsdWVudGlhbCBWYWx1ZSIpDQpwYXIobWZyb3c9YygyLDIpLCBtYXI9YygyLDMsMiwyKSkNCnBsb3QoY2hvbC5nbHUyKQ0KYGBgDQoNCg0KIyMgQ3Jvc3MgVmFsaWRhdGlvbiBvZiBNb2RlbHMgVXNpbmcgTVNFDQoNClRvIGNyb3NzIHZhbGlkYXRlIGFsbCB0aHJlZSBwcm9wb3NlZCBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbHMsIHRoZSBkYXRhIHdhcyBmaXJzdCBzcGxpdCA4MDoyMCBmb3IgdHJhaW5pbmc6dGVzdCBkYXRhLCB0aGUgdHJhaW5pbmcgZGF0YSB3YXMgdGhlbiBydW4gdGhyb3VnaCBhIGZpdmUgZm9sZCB2YWxpZGF0aW9uIHRvIGNvbXBhcmUgdGhlIG1lYW4gc3F1YXJlIGVycm9yIChNU0UpIG9mIGFsbCB0aHJlZSBtb2RlbHMuICBUaGUgTVNFIHZhbHVlcyB3ZXJlIHBsb3R0ZWQgYW5kIHRoZSBvdmVyYWxsIHZhbHVlcyBmb3IgZWFjaCBmb2xkIG9mIHZhbGlkYXRpb24gd2VyZSBjb21wYXJlZCB0byBkZXRlcm1pbmUgdGhlIGJlc3QgZml0IG1vZGVsLiANCg0KYGBge3J9DQoNCnNldC5zZWVkKDEyMykNCiNzaHVmZmxpbmcgdGhlIGRhdGENCm4gPC0gZGltKGZoc19pbXAyKVsxXSAjc2V0IHNhbXBsZSBzaXplICANCm9icy5JRCA8LSAxOm4gICAjY3JlYXRlIG9ic2VydmF0aW9uIElEIHRvIGFsbG93IGZvciByYW5kb20gc3BsaXR0aW5nDQpuLnRyYWluIDwtIHJvdW5kKDAuOCpuKSAgI2NyZWF0ZSBzYW1wbGUgc2l6ZSBmb3IgdHJhaW5pbmcgZGF0YS4gcm91bmQgdG8ga2VlcCB3aG9sZSBudW1iZXIgc2luY2Ugbm8gcGFydGlhbCBvYnNlcnZhdGlvbnMNCg0Kc2h1ZmZsZS5pZCAgPC0gIHNhbXBsZShvYnMuSUQsIG4sIHJlcGxhY2UgPSBGQUxTRSkgICNyYW5kb21pemUgY3JlYXRlZCBvYnNlcnZhdGlvbiBJRCwgZG9lcyBub3QgYWxsb3cgZm9yIGR1cGVzIGluIGxpc3QuDQoNCnNodWZmbGUuZmhzIDwtIGZoc19pbXAyW3NodWZmbGUuaWQsIF0gI29yZGVycyByb3dzIG9mIGRhdGFmcmFtZSB0byBiZSBpbiBvcmRlciBvZiBzaHVmZmxlLmlkIGNyZWF0ZWQgcmFuZG9tIG9icy5pZCBsaXN0DQoNCiNzcGxpdHRpbmcgdGhlIGRhdGENCg0KdHJhaW4uZGF0YS5saW5lYXIgPC0gc2h1ZmZsZS5maHNbMTpuLnRyYWluLCBdICNzcGxpdHRpbmcgc2h1ZmZsZWQgZGF0YSBmcm9tIDFzdCBvYnMgdG8gZW5kIG9mIHRyYWluaW5nIGRhdGEgKDgwJSkNCnRlc3QuZGF0YS5saW5lYXIgPC0gc2h1ZmZsZS5maHNbKG4udHJhaW4gKyAxKTpuLCBdICNzcGxpdHRpbmcgc2h1ZmZsZWQgZGF0YSBmcm9tIDFzdCBvYnMgYWZ0ZXIgZW5kIG9mIHRyYWluaW5nIGRhdGEgdG8gZW5kIG9mIHJlbWFpbmluZyBkYXRhICh0ZXN0KQ0KDQpuLmZvbGQgPC0gcm91bmQobi50cmFpbi81KS0xICAgI251bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gZWFjaCBvZiB0aGUgNSBmb2xkcyBvZiB2YWxpZGF0aW9uDQoNCg0KI3ZhbGlkYXRpb24gbG9vcCAtIDUgZm9sZA0KDQptc2UubGluLjEgPC0gcmVwKDAsNSkNCm1zZS5saW4uMiA8LSByZXAoMCw1KQ0KDQpmb3IgKGkgaW4gMTo1KXsNCiAgdmFsaWQuaWQgPSAoKGktMSkgKiBuLmZvbGQgKyAxKTooMSAqIG4uZm9sZCkgICNicmVha3MgZG93biBmb2xkcyBpbnRvIDUgZ3JvdXBzIA0KICBsaW4udHJhaW4gPSB0cmFpbi5kYXRhLmxpbmVhclstdmFsaWQuaWQsIF0NCiAgbGluLnZhbGlkID0gdHJhaW4uZGF0YS5saW5lYXJbdmFsaWQuaWQsIF0NCiAgDQogICNidWlsZGluZyBtb2RlbHMgdG8gcnVuIG9mZiBvZiBmb2xkZWQgZGF0YQ0KICBtMSA9IGxtKEJNSSB+IGhlYXJ0UmF0ZSArIHRvdENob2wgKyBnbHVjb3NlLCBkYXRhID0gbGluLnRyYWluKQ0KICBtMiA9IGxtKGJjMi5ibWkgfiB0b3RDaG9sICogZ2x1Y29zZSwgZGF0YSA9IGxpbi50cmFpbikNCiAgDQogICNwcmVkaWN0aW5nIEJNSSB1c2luZyBtb2RlbHMNCiAgcHJlZG0xID0gcHJlZGljdChtMSwgbmV3ZGF0YSA9IGxpbi50cmFpbikNCiAgcHJlZG0yID0gcHJlZGljdChtMiwgbmV3ZGF0YSA9IGxpbi50cmFpbikNCiAgDQogICNjYWxjdWxhdGUgbWVhbiBzcXVhcmUgZXJyb3IgYmV0d2VlbiBwcmVkaWN0ZWQgdmFsdWVzIGFuZCB2YWx1ZXMgZnJvbSB2YWxpZGF0aW9uIGRhdGENCiAgbXNlLmxpbi4xW2ldID0gbWVhbigocHJlZG0xIC0gbGluLnZhbGlkJEJNSSleMikNCiAgbXNlLmxpbi4yW2ldID0gbWVhbigocHJlZG0yIC0gbGluLnZhbGlkJGJjMi5ibWkpXjIpDQp9DQpgYGANCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KI3JlcGVhdCB3aXRoIGRhdGEgdGhhdCBoYXMgaW5mbHVlbnRpYWwgdmFsdWUgcmVtb3ZlZCAobW9kZWwgMykNCg0KI3NodWZmbGluZyB0aGUgZGF0YQ0Kbi4zIDwtIGRpbShmaHNfaW1wMm1vZDMpWzFdICNzZXQgc2FtcGxlIHNpemUgIA0Kb2JzLklELjMgPC0gMTpuLjMgICAjY3JlYXRlIG9ic2VydmF0aW9uIElEIHRvIGFsbG93IGZvciByYW5kb20gc3BsaXR0aW5nDQpuLnRyYWluLjMgPC0gcm91bmQoMC44Km4pICAjY3JlYXRlIHNhbXBsZSBzaXplIGZvciB0cmFpbmluZyBkYXRhLiByb3VuZCB0byBrZWVwIHdob2xlIG51bWJlciBzaW5jZSBubyBwYXJ0aWFsIG9ic2VydmF0aW9ucw0KDQojIG4udHJhaW4gPSBuLnRyYWluLjMgLSBzYW1wbGUgc3BpdGluZyByZXRhaW5lZCBmcm9tIGZpcnN0IHZhbGlkYXRpb24gcnVuIHRocm91Z2gNCnNodWZmbGUuaWQuMyAgPC0gIHNhbXBsZShvYnMuSUQuMywgbi4zLCByZXBsYWNlID0gRkFMU0UpICAjcmFuZG9taXplIGNyZWF0ZWQgb2JzZXJ2YXRpb24gSUQsIGRvZXMgbm90IGFsbG93IGZvciBkdXBlcyBpbiBsaXN0Lg0KDQpzaHVmZmxlLmZocy4zIDwtIGZoc19pbXAybW9kM1tzaHVmZmxlLmlkLjMsIF0gI29yZGVycyByb3dzIG9mIGRhdGFmcmFtZSB0byBiZSBpbiBvcmRlciBvZiBzaHVmZmxlLmlkIGNyZWF0ZWQgcmFuZG9tIG9icy5pZCBsaXN0DQoNCiNzcGxpdHRpbmcgdGhlIGRhdGENCg0KdHJhaW4uZGF0YS5saW5lYXIuMyA8LSBzaHVmZmxlLmZocy4zWzE6bi50cmFpbi4zLCBdICNzcGxpdHRpbmcgc2h1ZmZsZWQgZGF0YSBmcm9tIDFzdCBvYnMgdG8gZW5kIG9mIHRyYWluaW5nIGRhdGEgKDgwJSkNCnRlc3QuZGF0YS5saW5lYXIuMyA8LSBzaHVmZmxlLmZocy4zWyhuLnRyYWluLjMgKyAxKTpuLjMsIF0gI3NwbGl0dGluZyBzaHVmZmxlZCBkYXRhIGZyb20gMXN0IG9icyBhZnRlciBlbmQgb2YgdHJhaW5pbmcgZGF0YSB0byBlbmQgb2YgcmVtYWluaW5nIGRhdGEgKHRlc3QpDQoNCm4uZm9sZC4zIDwtIHJvdW5kKG4udHJhaW4uMy81KS0xICAgI251bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gZWFjaCBvZiB0aGUgNSBmb2xkcyBvZiB2YWxpZGF0aW9uDQoNCg0KI3ZhbGlkYXRpb24gbG9vcCAtIDUgZm9sZA0KbXNlLmxpbi4zIDwtIHJlcCgwLDUpDQoNCmZvciAoaSBpbiAxOjUpew0KICB2YWxpZC5pZC4zID0gKChpLTEpICogbi5mb2xkLjMgKyAxKTooMSAqIG4uZm9sZC4zKSAgI2JyZWFrcyBkb3duIGZvbGRzIGludG8gNSBncm91cHMgDQogIGxpbi50cmFpbi4zID0gdHJhaW4uZGF0YS5saW5lYXIuM1stdmFsaWQuaWQuMywgXQ0KICBsaW4udmFsaWQuMyA9IHRyYWluLmRhdGEubGluZWFyLjNbdmFsaWQuaWQuMywgXQ0KICANCiAgI2J1aWxkaW5nIG1vZGVscyB0byBydW4gb2ZmIG9mIGZvbGRlZCBkYXRhDQogIG0zID0gbG0oYmMyLmJtaSB+IHRvdENob2wgKiBnbHVjb3NlLCBkYXRhID0gbGluLnRyYWluLjMpDQogIA0KICAjcHJlZGljdGluZyBCTUkgdXNpbmcgbW9kZWxzDQogIHByZWRtMyA9IHByZWRpY3QobTMsIG5ld2RhdGEgPSBsaW4udHJhaW4uMykNCiAgDQogICNjYWxjdWxhdGUgbWVhbiBzcXVhcmUgZXJyb3IgYmV0d2VlbiBwcmVkaWN0ZWQgdmFsdWVzIGFuZCB2YWx1ZXMgZnJvbSB2YWxpZGF0aW9uIGRhdGENCiAgbXNlLmxpbi4zW2ldID0gbWVhbigocHJlZG0zIC0gbGluLnZhbGlkLjMkYmMyLmJtaSleMikNCn0NCg0KDQptc2UuMSA9IG1lYW4obXNlLmxpbi4xKQ0KbXNlLjIgPSBtZWFuKG1zZS5saW4uMikNCm1zZS4zID0gbWVhbihtc2UubGluLjMpDQoNCmBgYA0KDQpgYGB7cn0NCm1zZS5tb2QxIDwtIGRhdGEuZnJhbWUgKA0KICB4ID0gMTo1LA0KICB5ID0gbXNlLmxpbi4xDQopDQptc2UubW9kMiA8LSBkYXRhLmZyYW1lICgNCiAgeCA9IDE6NSwNCiAgeSA9IG1zZS5saW4uMg0KKQ0KbXNlLm1vZDMgPC0gZGF0YS5mcmFtZSAoDQogIHggPSAxOjUsDQogIHkgPSBtc2UubGluLjMNCikNCm1zZS5tb2QxLnBsb3QgPC0gZ2dwbG90IChtc2UubW9kMSwgYWVzKHggPSB4LCB5ID0geSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSAiTW9kZWwgMSIpKSsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9ICJNb2RlbCAxIikpICsNCiAgZ2VvbV9wb2ludCAoYWVzICh4ID0gbXNlLm1vZDIkeCwgeSA9IG1zZS5tb2QyJHksIGNvbG9yID0gIk1vZGVsIDIiKSkgKyANCiAgZ2VvbV9saW5lIChhZXMgKHggPSBtc2UubW9kMiR4LCB5ID0gbXNlLm1vZDIkeSwgY29sb3IgPSAiTW9kZWwgMiIpKSArDQogIGdlb21fcG9pbnQoYWVzKHggPSBtc2UubW9kMyR4LCB5ID0gbXNlLm1vZDMkeSwgY29sb3IgPSAiTW9kZWwgMyIpKSsgDQogIGdlb21fbGluZSAoYWVzKHggPSBtc2UubW9kMyR4LCB5ID0gbXNlLm1vZDMkeSwgY29sb3IgPSAiTW9kZWwgMyIpKSArDQogIGxhYnMgKHggPSAiVmFsaWRhdGlvbiBGb2xkIiwgeSA9ICJDYWxjdWxhdGVkIE1TRSIsIHRpdGxlID0gIk1lYW4gU3F1YXJlIEVycm9yIChNU0UpDQogIFBlciBWYWxpZGF0aW9uIFJvdW5kDQogICIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsICh2YWx1ZXMgPSBjKCJNb2RlbCAxIiA9ICJvcmFuZ2UiLCAiTW9kZWwgMiIgPSAiYmx1ZSIsICJNb2RlbCAzIiA9ICJncmVlbiIgICkpDQoNCm1zZS5tb2QyLnBsb3QgPC0gZ2dwbG90IChtc2UubW9kMiwgYWVzKHggPSB4LCB5ID0geSkpICsNCiAgZ2VvbV9wb2ludCAoYWVzICh4ID0gbXNlLm1vZDIkeCwgeSA9IG1zZS5tb2QyJHkpLCBjb2xvciA9ICJibHVlIikgKyANCiAgZ2VvbV9saW5lIChhZXMgKHggPSBtc2UubW9kMiR4LCB5ID0gbXNlLm1vZDIkeSksIGNvbG9yID0gImJsdWUiKSArDQogIGdlb21fcG9pbnQoYWVzKHggPSBtc2UubW9kMyR4LCB5ID0gbXNlLm1vZDMkeSksIGNvbG9yID0gImdyZWVuIikrIA0KICBnZW9tX2xpbmUgKGFlcyh4ID0gbXNlLm1vZDMkeCwgeSA9IG1zZS5tb2QzJHkpLCBjb2xvciA9ICJncmVlbiIpICsNCiAgbGFicyAoeCA9ICJWYWxpZGF0aW9uIEZvbGQiLCB5ID0gIkNhbGN1bGF0ZWQgTVNFIiwgdGl0bGUgPSAiTWVhbiBTcXVhcmUgRXJyb3IgKE1TRSkNCiAgUGVyIFZhbGlkYXRpb24gUm91bmQ6IA0KICBNb2RlbHMgMiAmIDMgQ2xvc2VyIExvb2siKSANCg0KDQptc2UudGFibGUgPC0gZGF0YS5mcmFtZSAoDQogIHggPSBjKCJNb2RlbCAxIiwgIk1vZGVsIDIiLCAiTW9kZWwgMyIpLA0KICB5ID0gYyhtc2UuMSwgbXNlLjIsIG1zZS4zKQ0KKQ0KY29sbmFtZXMobXNlLnRhYmxlKSA8LSBjKCJQcmVkaWN0aXZlIE1vZGVsIiwgIk1lYW4gTVNFIEFjcm9zcyBhbGwgVmFsaWRhdGlvbiBGb2xkcyIpDQoNCmdyaWQuYXJyYW5nZShtc2UubW9kMS5wbG90LCBtc2UubW9kMi5wbG90LCBucm93ID0gMSwgbmNvbCA9IDIpDQpgYGANCg0KQmFzZWQgb24gdGhlIHZpc3VhbCByZXByZXNlbnRhdGlvbiBvZiB0aGUgTVNFIHZhbHVlcyBmb3IgZWFjaCBmb2xkIG9mIHZhbGlkYXRpb24gcGFpcmVkIHdpdGggdGhlIG1lYW4gTVNFIGZvciBlYWNoIG1vZGVsLCBpdCBoYXMgYmVlbiBkZXRlcm1pbmVkIHRoYXQgTW9kZWwgMiwgYGNob2wuZ2x1YCwgdGhlIGxpbmVhciByZWdyZXNzaW9uIG9mIHRoZSBCb3gtQ294IHRyYW5zZm9ybWVkIEJNSSB2YWx1ZXMgY29tcGFyZWQgdG8gdGhlIHByb2R1Y3Qgb2YgdGhlIGdsdWNvc2UgYW5kIHRvdGFsIGNob2xlc3Rlcm9sIHZhbHVlcywgaXMgdGhlIGJlc3QgZml0IGZvciB0aGUgZGF0YS4gIFV0aWxpemluZyBNb2RlbCAyIGFzIHRoZSBwcmVkaWN0aXZlIG1vZGVsIHdpbGwgZ2l2ZSB0aGUgY2xvc2VzdCB0byBhY2N1cmF0ZSBwcmVkaWN0aW9uIHZhbHVlcyBmb3IgYSBwYXRpZW50J3MgQk1JIGJhc2VkIG9uIHRoZWlyIG1lYXN1cmVkIHRvdGFsIGNob2xlc3Rlcm9sIGFuZCBnbHVjb3NlIHZhbHVlcywgYXMgZXZpZGVuY2VkIGJ5IHRoZSBsb3dlc3QgbWVhbiBNU0UgdmFsdWUgb2YgYWxsIHRocmVlIG1vZGVscyBhY3Jvc3MgYWxsIGZpdmUgZm9sZHMgb2YgdmFsaWRhdGlvbi4gDQoNClV0aWxpemluZyB0aGUgZmluYWwgbW9kZWwsIGZpdCB0byBhbGwgZGF0YSwgdGhlIHByZWRpY3RlZCBCTUkgZm9yIGEgZ2x1Y29zZSBvZiA4NSBhbmQgdG90YWwgY2hvbGVzdGVyb2wgb2YgMjEyIHdhcyBhc3Nlc3NlZC4gIER1ZSB0byB0aGUgQk1JIGRhdGEgdW5kZXJnb2luZyBhIGJveCBjb3ggdHJhbnNmb3JtYXRpb24sIHRoZSBwcmVkaWN0ZWQgdmFsdWUgcHJvZHVjZWQgYnkgdGhlIGBjaG9sLmdsdWAgbW9kZWwgbXVzdCB1bmRlcmdvIGJhY2sgdHJhbnNmb3JtYXRpb24gdXNpbmcgdGhlIGZvbGxvd2luZyBlcXVhdGlvbi4gIA0KDQokJFxtYXRoYmZ7eH0gPSAoeSAqIFxsYW1iZGEgKyAxKV5cZnJhY3sxfXtcbGFtYmRhfSQkIA0KV2l0aG91dCBiYWNrIHRyYW5zZm9ybWF0aW9uIHRoZSBmaW5hbCB2YWx1ZSB3aWxsIG5vdCBiZSBhbiBhY3R1YWwgQk1JIHZhbHVlLiAgVGhlIHByZXZpb3VzbHkgY2FsY3VsYXRlZCAkXGxhbWJkYSQgdmFsdWUgb2YgPGI+LTAuNTk1OTU5NzwvYj4gd2FzIHV0aWxpemVkIGZvciB0aGUgYmFjayB0cmFuc2Zvcm1hdGlvbi4NCg0KDQpgYGB7cn0NCnByZWRpY3Rvci5kYXRhLmxpbmVhciA8LSBkYXRhLmZyYW1lICh0b3RDaG9sID0gMjEyLGdsdWNvc2UgPSA4NSkNCnByZWQuYm94Qk1JIDwtIHByZWRpY3QoY2hvbC5nbHUsIG5ld2RhdGEgPSBwcmVkaWN0b3IuZGF0YS5saW5lYXIsIHR5cGUgPSAicmVzcG9uc2UiKQ0KcHJlZC5CTUkgPC0gKHByZWQuYm94Qk1JICogb3AubGFtYmRhICsgMSkqKigxL29wLmxhbWJkYSkNCg0Ka2FibGUocHJlZC5CTUksIGNhcHRpb24gPSAiUHJlZGljdGVkIHBhdGllbnQgQk1JIHdpdGggYSBHbHVjb3NlIG9mIDg1IGFuZCBUb3RhbCBDaG9sZXN0ZXJvbCBvZiAyMTIiKQ0KYGBgDQoNCiMgTG9naXN0aWMgUmVncmVzc2lvbg0KDQpMb2dpc3RpYyByZWdyZXNzaW9uIGNhbiBiZSB1dGlsaXplZCB0byBhc3Nlc3MgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gYSBiaW5hcnkgcmVzcG9uc2UgdmFyaWFibGUsIDEwIFllYXIgQ0hEIHJpc2sgZGlhZ25vc2lzIChgVGVuWWVhckNIRGApLCBhbmQgYSB2YXJpZXR5IG9mIHByZWRpY3RvciB2YXJpYWJsZXMuIE11bHRpcGxlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWxzIHV0aWxpemluZyBjbGluaWNhbGx5IGFzc29jaWF0ZWQgcHJlZGljdG9yIHZhcmlhYmxlcyB3ZXJlIGNyZWF0ZWQgYW5kIGFuYWx5emVkIGZvciB0aGVpciByZXNwZWN0aXZlIGdvb2RuZXNzIG9mIGZpdCB1c2luZyB0aGVpciBjYWxjdWxhdGVkIFJlY2VpdmluZyBPcGVyYXRpbmcgQ2hhcmFjdGVyaXN0aWMgKFJPQykgY3VydmVzIGFuZCBBcmVhIFVuZGVyIHRoZSBDdXJ2ZSAoQVVDKSB2YWx1ZXMuDQoNClRoZSBmb2xsb3dpbmcgYXNzdW1wdGlvbnMgaGF2ZSBiZWVuIG1ldCB0byBzdXBwb3J0IGxpbmVhciByZWdyZXNzaW9uOg0KDQotICAgUmVzcG9uc2UgdmFyaWFibGUgaXMgYmluYXJ5DQotICAgTm8gc2VyaW91cyBjb2xpbmVhcml0eSBpZGVudGlmaWVkIGJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlcw0KLSAgIFByZWRpY3RvciB2YWx1ZXMnIGZ1bmN0aW9uYWwgZm9ybXMgYXJlIGNvcnJlY3RseSBzcGVjaWZpZWQNCg0KDQpUbyBjb21wbGV0ZSB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiBhbmFseXNlcyBhbmQgY3JlYXRlIGxvZ2lzdGljIHJlY2Vzc2lvbiBtb2RlbHMsIHRoZSBgVGVuWWVhckNIRGAgdmFyaWFibGUgd2FzIHNlbGVjdGVkIGFzIHJlc3BvbnNlIHZhcmlhYmxlLCBvdmVyYWxsIGxvb2tpbmcgdG8gZGV0ZXJtaW5lIGNvcnJlbGF0aW9uIHdpdGggb2RkcyBvZiBwYXRpZW50IGRpYWdub3NpcyB3aXRoIGEgMTAgeWVhciBDSEQgcmlzaywgY2hhcmFjdGVyaXplZCBieSBhIGJpbmFyeSBvZiBUUlVFIGZvciBkaWFnbm9zZWQgYXQgcmlzayBhbmQgRkFMU0UgZm9yIGRpYWdub3NlZCBub3QgYXQgcmlzay4NCg0KIyMgQ3JlYXRpbmcgTXVsdGlwbGUgQ2FuZGlkYXRlIE1vZGVscw0KDQojIyMgSW5pdGlhbCBNb2RlbCAoc2ltcGxlLm1vZGVsKQ0KRmlyc3QgYSBzaW1wbGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB3YXMgY3JlYXRlZCB0byBjb21wYXJlIHRoZSBvZGRzIG9mIHBhdGllbnQgZGlhZ25vc2lzIHdpdGggMTAgeWVhciBDSEQgcmlzayBhcyBwcmVkaWN0ZWQgYnkgdGhlIHByZXZpb3VzbHkgaWRlbnRpZmllZCBwcmVkaWN0b3IgdmFyaWFibGVzIHVzZWQgaW4gdGhlIGJlc3QgZml0IGxpbmVhciByZWdyZXNzaW9uIG1vZGVsOiBnbHVjb3NlIGxldmVscyAoYGdsdWNvc2VgKSBhbmQgdG90YWwgY2hvbGVzdGVyb2wgbGV2ZWxzIChgdG90Q2hvbGApLiANCg0KDQpgYGB7cn0NCnNpbXBsZS5tb2RlbCA8LSBnbG0oVGVuWWVhckNIRCB+IHRvdENob2wgKyBnbHVjb3NlLCBmYW1pbHkgPSBiaW5vbWlhbCwgZGF0YSA9IGZoc19pbXAyKQ0Ka2FibGUoc3VtbWFyeShzaW1wbGUubW9kZWwpJGNvZWYsIGNhcHRpb24gPSAiU3VtbWFyeSB0YWJsZSBvZiBzaWduaWZpY2FuY2UgdGVzdCBmb3IgZ2x1Y29zZSAmIHRvdGFsIGNob2xlc3Rlcm9sIGltcGFjdCBvbiBUZW4gWWVhciBDSEQgUmlzayBkaWFnbm9zaXMiKQ0KYGBgDQoNClV0aWxpemluZyB0aGUgc2ltcGxlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwsIGJvdGggZ2x1Y29zZSBhbmQgdG90YWwgY2hvbGVzdGVyb2wgaWxsdXN0cmF0ZWQgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBwb3NpdGl2ZSBjb3JyZWxhdGlvbiB3aXRoIDEwIHllYXIgQ0hEIHJpc2sgZGlhZ25vc2lzLiAgSW4gbGF5bWFuJ3MgdGVybXMsIGFzIGEgcGF0aWVudCdzIGdsdWNvc2UgYW5kIHRvdGFsIGNob2xlc3Rlcm9sIHZhbHVlcyBpbmNyZWFzZSwgdGhlIG9kZHMgdGhlIHBhdGllbnQgd2lsbCBiZSBkaWFnbm9zZWQgd2l0aCBhIDEwIHllYXIgQ0hEIHJpc2sgaW5jcmVhc2VzLiBCeSBleHBvbmVudGlhdGluZyB0aGUgbW9kZWwsIGl0IGlzIGZvdW5kIHRoYXQgdGhlIGV4cG9uZW50aWF0ZWQgc2xvcGUgY29lZmZpY2llbnRzIGZvciBnbHVjb3NlIGFuZCB0b3RhbCBjaG9sZXN0ZXJvbCBsZXZlbHMgYXJlIDEuMDEwMzcyMzcgYW5kIDEuMDA0NDYxMjQgcmVzcGVjdGl2ZWx5LiBUaHVzIHdlIGVzdGltYXRlIGEgMS4wMzclIGluY3JlYXNlIGluIHRoZSBvZGRzIG9mIGEgcGF0aWVudCBiZWluZyBkaWFnbm9zZWQgd2l0aCAxMCB5ZWFyIENIRCByaXNrIGZvciBlYWNoIDEgbWcvZEwgaW5jcmVhc2UgaW4gc2FpZCBwYXRpZW50J3MgZ2x1Y29zZSBsZXZlbHMuICBBZGRpdGlvbmFsbHksIHdlIGVzdGltYXRlIGEgMC40NDYlIGluY3JlYXNlIGluIHRoZSBvZGRzIG9mIGEgcGF0aWVudCBiZWluZyBkaWFnbm9zZWQgd2l0aCAxMCB5ZWFyIENIRCByaXNrIGZvciBlYWNoIDEgbWcvZEwgaW5jcmVhc2UgaW4gc2FpZCBwYXRpZW50J3MgdG90YWwgY2hvbGVzdGVyb2wgbGV2ZWxzLiANCg0KYGBge3J9DQpleHAoc2ltcGxlLm1vZGVsJGNvZWZmKQ0KZXhwKGNvbmZpbnQoc2ltcGxlLm1vZGVsKSkNCmBgYA0KDQoNCiMjIyBTZWNvbmQgTW9kZWwgKGZpbmFsLm1vZGVsKQ0KDQpSYXRoZXIgdGhhbiB1dGlsaXppbmcgdGhlIGFwcHJvYWNoIG9mIHByZS1kZXRlcm1pbmluZyB0aGUgcHJlZGljdG9yIHZhcmlhYmxlcyBzaW1pbGFyIHRvIHRoYXQgdXRpbGl6ZWQgaW4gdGhlIGluaXRpYWwgbW9kZWwgYHNpbXBsZS5tb2RlbGAsIGEgY3VtdWxhdGl2ZSBtb2RlbCBlbnRpdGxlZCBgZnVsbC5tb2RlbGAgd2FzIGNyZWF0ZWQgdG8gdXRpbGl6ZSBhcyB0aGUgc3RhcnRpbmcgcG9pbnQgZm9yIGF1dG9tYXRpY2FsbHkgaWRlbnRpZnlpbmcgdGhlIHNpZ25pZmljYW50IGFuZCBub24tc2lnbmlmaWNhbnQgdmFyaWFibGVzIGJhc2VkIG9uIEFVQyB2YWx1ZXMgYXV0b21hdGljYWxseSBjYWxjdWxhdGVkIGJ5IHRoZSBgc3RlcCgpYCBmdW5jdGlvbi4gDQoNClRoZSBzYW1lIHByZWRpY3RvciB2YWx1ZXMgZnJvbSB0aGUgaW5pdGlhbCBtb2RlbCBgc2ltcGxlLm1vZGVsYCB3ZXJlIHV0aWxpemVkIGFzIHRoZSByZWR1Y2VkIG1vZGVsIGZvciB0aGUgc3RlcCBmdW5jdGlvbi4gIEFmdGVyIHNocmlua2luZyB0byB0aGUgcmVkdWNlZCBtb2RlbCwgb25seSBnbHVjb3NlIGhhcyBkZW1vbnN0cmF0ZWQgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIHdpdGggcCB2YWx1ZXMgb2YgMC4wMDAwMDA2IHdoaWxlIHRvdGFsIGNob2xlc3Rlcm9sIG1lYXN1cmVkIGluIHdpdGggYSBwIHZhbHVlIG9mIGFuZCAwLjEyMDQ3MTcgaW5kaWNhdGluZyBhIGxhY2sgb2Ygc2lnbmlmaWNhbmNlLiAgUmV2aWV3aW5nIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cywgYm90aCBnbHVjb3NlIGFuZCB0b3RhbCBjaG9sZXN0ZXJvbCBpbmRpY2F0ZSBhIHBvc2l0aXZlIGNvcnJlbGF0aW9uIG9mIDEuMDA3OTI0MDIzNSB3aXRoIHRoZSBvZGRzIG9mIGEgcGF0aWVudCBiZWluZyBkaWFnbm9zZWQgd2l0aCAxMCB5ZWFyIENIRCByaXNrLiAgVGhpcyBpbmRpY2F0ZXMgdGhhdCBmb3IgZXZlcnkgMSBtZy9kTCBhIHBhdGllbnQncyBnbHVjb3NlIGxldmVscyBpbmNyZWFzZSwgdGhlIG9kZHMgb2YgZGlhZ25vc2lzIHdpdGggYSAxMCB5ZWFyIHJpc2sgZmFjdG9yIGZvciBjaHJvbmljIGhlYXJ0IGRpc2Vhc2UgaW5jcmVhc2VzIGJ5IDAuNzkyJS4gDQoNCg0KYGBge3J9DQpmdWxsLm1vZGVsIDwtIGdsbShUZW5ZZWFyQ0hEIH4gbWFsZSArIGFnZSArIGVkdWNhdGlvbiArIGN1cnJlbnRTbW9rZXIgKyBjaWdzUGVyRGF5ICsgQlBNZWRzICsgcHJldmFsZW50Q29uZCArIGRpYWJldGVzICsgdG90Q2hvbCArIHN5c0JQICsgZGlhQlAgKyBCTUkgKyBoZWFydFJhdGUgKyBnbHVjb3NlICsgaGlnaEJQLCBmYW1pbHkgPSBiaW5vbWlhbCwgZGF0YSA9IGZoc19pbXAyKQ0KDQpyZWR1Y2VkLm1vZGVsIDwtIHNpbXBsZS5tb2RlbA0KZmluYWwubW9kZWwgPC0gIHN0ZXAoZnVsbC5tb2RlbCwNCiAgICAgICAgICAgICAgICAgICBzY29wZSA9IGxpc3QobG93ZXIgPSBmb3JtdWxhKHJlZHVjZWQubW9kZWwpLCB1cHBlciA9IGZvcm11bGEoZnVsbC5tb2RlbCkpLA0KICAgICAgICAgICAgICAgICAgIGRhdGEgPSBmaHNfaW1wMiwNCiAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAiYmFja3dhcmQiLA0KICAgICAgICAgICAgICAgICAgIHRyYWNlID0gMCkNCmthYmxlKHN1bW1hcnkoZmluYWwubW9kZWwpJGNvZWYsIGNhcHRpb24gPSAiU3VtbWFyeSB0YWJsZSBvZiBzaWduaWZpY2FuY2UgdGVzdCBmb3IgdmFyaWFibGVzIGltcGFjdGluZyBUZW4gWWVhciBDSEQgUmlzayBkaWFnbm9zaXMgaW4gdGhlIHJlZHVjZWQgbW9kZWwiKQ0KDQpleHAoZmluYWwubW9kZWwkY29lZmYpDQpleHAoY29uZmludChmaW5hbC5tb2RlbCkpDQpgYGANCg0KDQojIyBST0MgYW5kIEFVQyBDb21wYXJyaXNvbnMNCg0KQSByZWxpYWJsZSB3YXkgdG8gY29tcGFyZSB0aGUgZWZmaWNhY3kgb2YgdGhlIG1vZGVsIGZpdCBvdmVyYWxsIGFuZCBpbiBjb21wYXJpc29uIHRvIGFub3RoZXIgbW9kZWwgaXMgdGhlIFJPQyBjdXJ2ZSBhbmQgYXNzb2NpYXRlZCBBcmVhIFVuZGVyIHRoZSBDdXJ2ZSAoQVVDKSB2YWx1ZXMuDQoNCg0KIyMjIFByZWRpY3RpbmcgQ3V0b2ZmIFZhbHVlcw0KDQpDdXRvZmYgdmFsdWVzIHdlcmUgYXV0b21hdGljYWxseSBjYWxjdWxhdGVkIGFuZCBhcmUgZGVtb25zdHJhdGVkIGJlbG93IG9uIHRoZSBST0MgY3VydmUgcGxvdC4gIFRoZXNlIGN1dG9mZiBwb2ludHMgcmVwcmVzZW50IHRoZSBiZXN0IHBvc3NpYmxlIHZhbHVlcyBhdCB3aGljaCB0aGUgbW9kZWwgZml0cyB0aGUgb3JpZ2luYWwgZGF0YSBiZXN0LiAgVGhlc2UgdmFsdWVzIHJlcHJlc2VudCB0aGUgcmVzcGVjdGl2ZSBmYWxzZSBwb3NpdGl2ZSByYXRlcyAoJFxhbHBoYSA9IDEgLSBTcGVjaWZpY2l0eSQpIGFuZCBmYWxzZSBuZWdhdGl2ZSByYXRlcyAoJFxiZXRhID0gMSAtIFNlbnNpdGl2aXR5JCkgdGhhdCB3aWxsIHByb2R1Y2UgdGhlIGJlc3QgZml0IG91dGNvbWVzIGZvciB0aGUgcHJlZGljdG9yIG1vZGVsLg0KDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTksIGZpZy53aWR0aD05fQ0Kbm8xMHlyIDwtIHdoaWNoKGZoc19pbXAyJFRlblllYXJDSEQgPT0gRkFMU0UpDQp5ZXMxMHlyIDwtICB3aGljaChmaHNfaW1wMiRUZW5ZZWFyQ0hEID09IFRSVUUpDQoNCmZoc19pbXAyJENIRCA9IDANCmZoc19pbXAyJENIRFt5ZXMxMHlyXSA9IDENCmN1dG9mZiA8LSBzZXEoMCwgMSwgbGVuZ3RoID0gMTAwKQ0Kc2Vuc2l0aXYgPSBOVUxMDQpzcGVjaWYgPSBOVUxMDQpzZW5zaXRpdjIgPSBOVUxMDQpzcGVjaWYyID0gTlVMTA0KDQpuZXdkYXRhLnNpbXBsZSA8LSBkYXRhLmZyYW1lKGZoc19pbXAyKQ0KcHJlZGljdC5kYXRhLmxvZyA8LSBwcmVkaWN0LmdsbShzaW1wbGUubW9kZWwsIG5ld2RhdGEuc2ltcGxlLCB0eXBlID0gInJlc3BvbnNlIikNCnByZWRpY3QuZGF0YS5sb2cyIDwtIHByZWRpY3QuZ2xtKGZpbmFsLm1vZGVsLCBuZXdkYXRhLnNpbXBsZSwgdHlwZSA9ICJyZXNwb25zZSIpDQoNCmZvciAoaSBpbiAxOjEwMCl7DQogIA0KZmhzX2ltcDIkQ0hEdHJhaW4gPSBhcy5udW1lcmljKHByZWRpY3QuZGF0YS5sb2cgPiBjdXRvZmZbaV0pDQoNClROID0gc3VtKGZoc19pbXAyJENIRHRyYWluID09IDAgJiBmaHNfaW1wMiRDSEQgPT0gMCkgIyB0cnVlIG5lZ2F0aXZlDQpGTiA9IHN1bShmaHNfaW1wMiRDSER0cmFpbiA9PSAwICYgZmhzX2ltcDIkQ0hEID09IDEpICMgZmFsc2UgbmVnYXRpdmUNCkZQID0gc3VtKGZoc19pbXAyJENIRHRyYWluID09IDEgJiBmaHNfaW1wMiRDSEQgPT0gMCkgIyBmYWxzZSBwb3NpdGl2ZQ0KVFAgPSBzdW0oZmhzX2ltcDIkQ0hEdHJhaW4gPT0gMSAmIGZoc19pbXAyJENIRCA9PSAxKSAjIHRydWUgcG9zaXRpdmUNCg0Kc2Vuc2l0aXZbaV0gPSBUUCAvIChUUCArIEZOKQ0Kc3BlY2lmW2ldID0gVE4gLyAoVE4gKyBGUCkNCn0NCg0KZm9yIChpIGluIDE6MTAwKXsNCiAgDQpmaHNfaW1wMiRDSER0cmFpbjIgPSBhcy5udW1lcmljKHByZWRpY3QuZGF0YS5sb2cyID4gY3V0b2ZmW2ldKQ0KDQpUTjIgPSBzdW0oZmhzX2ltcDIkQ0hEdHJhaW4yID09IDAgJiBmaHNfaW1wMiRDSEQgPT0gMCkgIyB0cnVlIG5lZ2F0aXZlDQpGTjIgPSBzdW0oZmhzX2ltcDIkQ0hEdHJhaW4yID09IDAgJiBmaHNfaW1wMiRDSEQgPT0gMSkgIyBmYWxzZSBuZWdhdGl2ZQ0KRlAyID0gc3VtKGZoc19pbXAyJENIRHRyYWluMiA9PSAxICYgZmhzX2ltcDIkQ0hEID09IDApICMgZmFsc2UgcG9zaXRpdmUNClRQMiA9IHN1bShmaHNfaW1wMiRDSER0cmFpbjIgPT0gMSAmIGZoc19pbXAyJENIRCA9PSAxKSAjIHRydWUgcG9zaXRpdmUNCg0Kc2Vuc2l0aXYyW2ldID0gVFAyIC8gKFRQMiArIEZOMikNCnNwZWNpZjJbaV0gPSBUTjIgLyAoVE4yICsgRlAyKQ0KfQ0KDQpzcGVjaWZpY2l0eSA9IDEgLSBzcGVjaWYNCnNlbnNpdGl2aXR5ID0gc2Vuc2l0aXYNCg0Kc3BlY2lmaWNpdHkyID0gMSAtIHNwZWNpZjINCnNlbnNpdGl2aXR5MiA9IHNlbnNpdGl2Mg0KDQoNCnByZWRpY3Rpb24gPC0gIHByZWRpY3QuZGF0YS5sb2cNCmNhdGVnb3J5IDwtICBmaHNfaW1wMiRDSEQgPT0gMQ0KUk9Db2JqIDwtIHJvYyhjYXRlZ29yeSwgcHJlZGljdGlvbikNCkFVQy5zaW1wbGUgPC0gcm91bmQoYXVjKFJPQ29iaiksIDUpDQoNCnByZWRpY3Rpb24yIDwtICBwcmVkaWN0LmRhdGEubG9nMg0KY2F0ZWdvcnkyIDwtICBmaHNfaW1wMiRDSEQgPT0gMQ0KUk9Db2JqMiA8LSByb2MoY2F0ZWdvcnkyLCBwcmVkaWN0aW9uMikNCkFVQy5maW5hbCA8LSByb3VuZChhdWMoUk9Db2JqMiksIDUpDQoNCnNwZWNpZi5vcmkgPC0gUk9Db2JqJHNwZWNpZmljaXRpZXMNCnNwZWNpZi5vcmkyIDwtIFJPQ29iajIkc3BlY2lmaWNpdGllcw0Kc2Vucy5vcmkgPC0gUk9Db2JqJHNlbnNpdGl2aXRpZXMNCnNlbnMub3JpMiA8LSBST0NvYmoyJHNlbnNpdGl2aXRpZXMNCg0KZGlmZiA8LSBhYnMoc2Vucy5vcmkgLSBzcGVjaWYub3JpKQ0KZGlmZjIgPC0gYWJzKHNlbnMub3JpMiAtIHNwZWNpZi5vcmkyKQ0KDQptaW4uc2ltcGxlIDwtIHdoaWNoKGRpZmYgPT0gbWluKGRpZmYpKQ0KbWluLmZpbmFsIDwtIHdoaWNoKGRpZmYyID09IG1pbihkaWZmMikpDQoNCg0Kcm9jLnBsb3QgPC0gcGxvdChzcGVjaWZpY2l0eSwgc2Vuc2l0aXZpdHksDQogICAgIHR5cGUgPSAibCIsDQogICAgIG1haW4gPSAiTW9kZWwgQ29tcGFycmlzb24gVXNpbmcgUk9DIGFuZCBBVUMiLA0KICAgICB4bGFiPSIxLXNwZWNpZmljaXR5IiwNCiAgICAgeWxhYj0ic2Vuc2l0aXZpdHkiKSArDQogIHNlZ21lbnRzICgwLDAsMSwxLCBsdHk9MiwgY29sID0gInJlZCIpICsNCiAgbGluZXMoc3BlY2lmaWNpdHksIHNlbnNpdGl2aXR5LCBsd2QgPSAxLCBjb2wgPSAibmF2eSIpICsNCiAgbGluZXMoc3BlY2lmaWNpdHkyLCBzZW5zaXRpdml0eTIsIGx3ZCA9IDEsIGNvbCA9ICJkYXJrZ3JlZW4iKSArDQogIHRleHQoMC44LCAwLjIsIHBhc3RlKCJTaW1wbGUgTW9kZWwgQVVDID0gIiwgQVVDLnNpbXBsZSksIGNvbCA9ICJuYXZ5IiwgY2V4ID0gMC44KSArDQogIHRleHQoMC44LCAwLjI1LCBwYXN0ZSgiRmluYWwgTW9kZWwgQVVDID0gIiwgQVVDLmZpbmFsKSwgY29sID0gImRhcmtncmVlbiIsIGNleCA9IDAuOCkgKw0KICBwb2ludHMoKDEtc3BlY2lmLm9yaVttaW4uc2ltcGxlXSksIHNwZWNpZi5vcmlbbWluLnNpbXBsZV0sIGNvbCA9ICJuYXZ5IiwgcGNoID0gMTcpICsNCiAgcG9pbnRzKCgxLXNwZWNpZi5vcmkyW21pbi5maW5hbF0pLCBzcGVjaWYub3JpMlttaW4uZmluYWxdLCBjb2wgPSAiZGFya2dyZWVuIiwgcGNoID0gMTcpIA0KICB0ZXh0KDAuOCwgMC4xOCwgcGFzdGUoICJTaW1wbGUgTW9kZWwgQ2FsY3VsYXRlZCBDdXRvZmYgPSAoIiwgcm91bmQoKDEtc3BlY2lmLm9yaVttaW4uc2ltcGxlXSksNCksICIsICIgLCByb3VuZChzZW5zLm9yaVttaW4uc2ltcGxlXSw0KSwiKSIpLCBjb2wgPSAibmF2eSIsIGNleCA9IDAuOCkgKw0KICAgIHRleHQoMC44LCAwLjIzLCBwYXN0ZSggIkZpbmFsIE1vZGVsIENhbGN1bGF0ZWQgQ3V0b2ZmID0gKCIsIHJvdW5kKCgxLXNwZWNpZi5vcmkyW21pbi5maW5hbF0pLDQpLCAiLCAiICwgcm91bmQoc2Vucy5vcmkyW21pbi5maW5hbF0sNCksIikiKSwgY29sID0gImRhcmtncmVlbiIsIGNleCA9IDAuOCkgDQoNCnByZWQudGFibGUgPSBjYmluZChFcnJvci5UeXBlID0gYygnRmFsc2UgUG9zaXRpdmUgKCRhbHBoYSQpJywgIkZhbHNlIE5lZ2F0aXZlICgkYmV0YSQpIiksIEVycm9yLlZhbHVlID0gYygoMS1zcGVjaWYub3JpW21pbi5zaW1wbGVdKSwgc2Vucy5vcmlbbWluLnNpbXBsZV0pKQ0Ka2FibGUocHJlZC50YWJsZSwgY2FwdGlvbiA9ICJFcnJvciB2YWx1ZXMgZm9yIFNpbXBsZSBNb2RlbCBDYWxjdWxhdGVkIEN1dG9mZiIpDQpwcmVkLnRhYmxlMiA9IGNiaW5kKEVycm9yLlR5cGUgPSBjKCdGYWxzZSBQb3NpdGl2ZSAoJGFscGhhJCknLCAiRmFsc2UgTmVnYXRpdmUgKCRiZXRhJCkiKSwgRXJyb3IuVmFsdWUgPSBjKCgxLXNwZWNpZi5vcmkyW21pbi5maW5hbF0pLCBzZW5zLm9yaTJbbWluLmZpbmFsXSkpDQprYWJsZShwcmVkLnRhYmxlMiwgY2FwdGlvbiA9ICJFcnJvciB2YWx1ZXMgZm9yIEZpbmFsIE1vZGVsIENhbGN1bGF0ZWQgQ3V0b2ZmIikNCg0KYGBgDQoNCk92ZXJhbGwgdGhlIGBmaW5hbC5tb2RlbGAgcmV0dXJuZWQgYSBmYWlyIEFVQyB2YWx1ZSBvZiA8Yj4wLjczMjQ0PC9iPiBpbmRpY2F0aW5nIHRoZSBtb2RlbCBwcmVmb3JtZWQgZmFpcmx5IHdpdGggYWNjZXB0YWJsZSBkaXNjcmltaW5hdGlvbiBiZXR3ZWVuIGNsYXNzZXMuIFRoZSBgc2ltcGxlLm1vZGVsYCByZXR1cm5lZCBhIGxvd2VyIEFVQyB2YWx1ZSBvZiA8Yj4wLjU3OTg3PC9iPiBpbmRpY2F0aW5nIGEgbmVhciBmYWlsIGluIHRoZSBtb2RlbCBwcmVkaWN0aW9uLiBUaGUgY2xvc2VyIGEgbW9kZWwncyBBVUMgdmFsdWUgaXMgdG8gYSBmdWxsIDEuMDAwIHZhbHVlLCB0aGUgYmV0dGVyIGZpdCB0aGUgbW9kZWwgaXMgaW4gdGVybXMgb2YgcHJlZGljdGl2ZSBhYmlsaXR5Lg0KDQpUaGlzIGluZGljYXRlcyB0aGUgc2Vjb25kIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgYGZpbmFsLm1vZGVsYCBjcmVhdGVkIHV0aWxpemluZyB0aGUgYHN0ZXAoKWAgZnVuY3Rpb24gcHJvZHVjZWQgdGhlIGJlc3QgZml0IHByZWRpY3RvciBtb2RlbCBmb3IgcHJlZGljdGluZyBhIHBhdGllbnQncyAxMCB5ZWFyIENocm9uaWMgSGVhcnQgRGlzZWFzZSByaXNrIGRpYWdub3NpcyBgVGVuWWVhckNIRGAuIA0KDQojIERpc2N1c3Npb24NCg0KUGF0aWVudCBoZWFsdGggaGFzIGJlZW4gYXQgdGhlIGZvcmVmcm9udCBvZiBwaHlzaWNpYW4gYW5kIHBhdGllbnQgY29uY2VybnMgZm9yIGRlY2FkZXMsIGFzIGV2aWRlbmNlZCBieSB0aGUgc2VhcmNoIGZvciBwcmVkaWN0aXZlIG1lYXN1cmVzIGZvciBsb25nIHRlcm0gcGF0aWVudCBoZWFsdGguICBUaGUgRkhTIHN0dWR5IGhhcyB1bmRlcnNjb3JlZCB0aGlzIGRlc2lyZSBmb3IgdW5kZXJzdGFuZGluZyB3aGF0IGZhY3RvcnMgd2lsbCBpbXBhY3QgYSBwYXRpZW50IGxvbmcgdGVybSdzIHByb2dub3Npcy4gDQoNCkFmdGVyIHRoZSBjcmVhdGlvbiBhbmQgYW5hbHlzaXMgb2YgdmFyaW91cyBwcmVkaWN0aW9uIG1vZGVscyBmb3IgYSBwYXRpZW50J3MgaW1wYWN0ZnVsIGhlYWx0aCBmYWN0b3JzOiBCTUkgYW5kIDEwIHllYXIgQ0hEIHJpc2sgZGlhZ25vc2lzLCBpdCB3YXMgZm91bmQgdGhhdCBzb21lIG1vZGVscyBwcmVmb3JtIGJldHRlciB0aGFuIG90aGVycy4gIFdoaWxlIGZ1bGwgY2F1c2F0aW9uIGNhbm5vdCBiZSBjb25mZXJyZWQgZnJvbSB0aGVzZSByZXN1bHRzLCBpdCB3YXMgZm91bmQgdGhhdCB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgb2YgQm94LWNveCB0cmFuc2Zvcm1lZCBCTUkgZGF0YSB1dGlsaXppbmcgdGhlIHByb2R1Y3Qgb2YgZ2x1Y29zZSBhbmQgdG90YWwgY2hvbGVzdGVyb2wgcHJvZHVjZWQgdGhlIG1vc3QgYWNjdXJhdGUgcHJlZGljdGlvbnMgb2YgcGF0aWVudCBCTUkuICBBZGRpdGlvbmFsbHkgaXQgd2FzIGZvdW5kIHRoYXQgdGhlIGZpbmFsIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgY29tcGFyaW5nIHRoZSBzaWduaWZpY2FudCB2YXJpYWJsZXMgYXMgc2VsZWN0ZWQgYnkgdGhlIGBzdGVwKClgIGZ1bmN0aW9uICBwcm9kdWNlZCB0aGUgbW9zdCBhY2N1cmF0ZSBwcmVkaWNpdG9ucyBvZiBhIHBhdGllbnRzIDEwIHllYXIgQ0hEIHJpc2sgZGlhZ25vc2lzIHdoZW4gaGVsZCB0byAkXGFscGhhID0gMC4zMjQ4MDUzMzkyNjU4NTEkIGFuZCAkXGJldGEgPSAwLjY3NTQ2NTgzODUwOTMxNyQNCg0KDQojIEFwcGVuZGl4DQoNCkhlYXJ0Lm9yZyAtIEFtZXJpY2FuIEhlYXJ0IEFzc29jaWF0aW9uIGJsb29kIHByZXNzdXJlIHJhbmdlcyA8aHR0cHM6Ly93d3cuaGVhcnQub3JnL2VuL2hlYWx0aC10b3BpY3MvaGlnaC1ibG9vZC1wcmVzc3VyZS91bmRlcnN0YW5kaW5nLWJsb29kLXByZXNzdXJlLXJlYWRpbmdzPg0K