This demo covers some fundamental classification techniques: Logistic Regression, Linear Discriminant Analysis (LDA), and Quadratic Discriminant Analysis (QDA). We’ll also focus on a crucial aspect of model building: robust evaluation using cross-validation.

Learning Objectives:

  • Understand the theoretical underpinnings of Logistic Regression, Linear Discriminant Analysis (LDA), and Quadratic Discriminant Analysis (QDA).
  • Implement these models in R.
  • Apply k-fold cross-validation for robust model evaluation.
  • Compare the performance of different classification models.

What are Classification Models?

  • Goal: Predict a categorical outcome variable (e.g., spam/not spam, disease/no disease, customer churn/no churn).
  • Supervised Learning: We have labeled data (features and known outcomes).

Brief Theoretical Overview

1. Logistic Regression:

  • What’s the core idea? (Predicts the probability of belonging to a class using a logistic function).
  • What type of decision boundary does it create? (Linear).
  • Assumptions? (No strong distributional assumptions on predictors, but linearity in the log-odds is assumed).

2. Linear Discriminant Analysis (LDA):

  • What’s the core idea? (Assumes predictors within each class follow a multivariate Gaussian distribution with equal covariance matrices).
  • How does it classify? (Finds linear combinations of predictors that best separate the classes).
  • Decision boundary? (Linear).
  • When might it be preferred over logistic regression? (When classes are well-separated, or when the normality assumption holds).

3. Quadratic Discriminant Analysis (QDA):

  • How does it differ from LDA? (Assumes predictors within each class follow a multivariate Gaussian distribution but with unequal covariance matrices).
  • Decision boundary? (Quadratic).
  • When might it be preferred over LDA? (When the assumption of equal covariance matrices is violated, and the true decision boundary is non-linear).

Why Cross-Validation?

  • Why don’t we just split our data once into training and testing sets?” (Discuss limitations: sensitivity to split, less robust performance estimate).
  • Cross-validation: A technique to get a more reliable estimate of a model’s performance on unseen data by repeatedly splitting the data into training and validation sets.
  • k-Fold Cross-Validation: Explain the process – divide data into k folds, train on k-1 folds, validate on the remaining fold, repeat k times.
  • Benefits: More robust performance estimate, uses all data for both training and validation.

R Demo

Data Prep

We’ll use the PimaIndiansDiabetes2 dataset from the mlbench package.

# Install necessary packages (if not already installed)
# install.packages("mlbench")
# install.packages("caret") # For cross-validation and convenient model training
# install.packages("MASS")  # For LDA and QDA

# Load packages
library(mlbench)   # For PimaIndiansDiabetes2 dataset
library(caret)     # For cross-validation and model training utilities
Loading required package: ggplot2
Loading required package: lattice
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
library(MASS)      # For lda() and qda() functions

# Load the dataset
data(PimaIndiansDiabetes2)
my_data <- PimaIndiansDiabetes2

# Explore the data
head(my_data)
summary(my_data)
    pregnant         glucose         pressure     
 Min.   : 0.000   Min.   : 44.0   Min.   : 24.00  
 1st Qu.: 1.000   1st Qu.: 99.0   1st Qu.: 64.00  
 Median : 3.000   Median :117.0   Median : 72.00  
 Mean   : 3.845   Mean   :121.7   Mean   : 72.41  
 3rd Qu.: 6.000   3rd Qu.:141.0   3rd Qu.: 80.00  
 Max.   :17.000   Max.   :199.0   Max.   :122.00  
                  NA's   :5       NA's   :35      
    triceps         insulin            mass      
 Min.   : 7.00   Min.   : 14.00   Min.   :18.20  
 1st Qu.:22.00   1st Qu.: 76.25   1st Qu.:27.50  
 Median :29.00   Median :125.00   Median :32.30  
 Mean   :29.15   Mean   :155.55   Mean   :32.46  
 3rd Qu.:36.00   3rd Qu.:190.00   3rd Qu.:36.60  
 Max.   :99.00   Max.   :846.00   Max.   :67.10  
 NA's   :227     NA's   :374      NA's   :11     
    pedigree           age        diabetes 
 Min.   :0.0780   Min.   :21.00   neg:500  
 1st Qu.:0.2437   1st Qu.:24.00   pos:268  
 Median :0.3725   Median :29.00            
 Mean   :0.4719   Mean   :33.24            
 3rd Qu.:0.6262   3rd Qu.:41.00            
 Max.   :2.4200   Max.   :81.00            
                                           
str(my_data)
'data.frame':   768 obs. of  9 variables:
 $ pregnant: num  6 1 8 1 0 5 3 10 2 8 ...
 $ glucose : num  148 85 183 89 137 116 78 115 197 125 ...
 $ pressure: num  72 66 64 66 40 74 50 NA 70 96 ...
 $ triceps : num  35 29 NA 23 35 NA 32 NA 45 NA ...
 $ insulin : num  NA NA NA 94 168 NA 88 NA 543 NA ...
 $ mass    : num  33.6 26.6 23.3 28.1 43.1 25.6 31 35.3 30.5 NA ...
 $ pedigree: num  0.627 0.351 0.672 0.167 2.288 ...
 $ age     : num  50 31 32 21 33 30 26 29 53 54 ...
 $ diabetes: Factor w/ 2 levels "neg","pos": 2 1 2 1 2 1 2 1 2 2 ...
# Check for missing values
colSums(is.na(my_data))
pregnant  glucose pressure  triceps  insulin     mass pedigree 
       0        5       35      227      374       11        0 
     age diabetes 
       0        0 
# Handle missing values: For simplicity in this demo, we'll remove rows with NA.
# In a real-world scenario, imputation would be a better approach for missing data.
my_data <- na.omit(my_data)
colSums(is.na(my_data)) # Verify no more NAs
pregnant  glucose pressure  triceps  insulin     mass pedigree 
       0        0        0        0        0        0        0 
     age diabetes 
       0        0 
# Convert 'diabetes' to a factor with desired levels if not already
# The 'outcome' variable needs to be a factor for classification models in R.
# Let's ensure the levels are meaningful.
my_data$diabetes <- factor(my_data$diabetes, levels = c("neg", "pos"), labels = c("No", "Yes"))

# Ensure other variables are numeric where appropriate
# (PimaIndiansDiabetes2 is generally clean, but good practice to check)
  • What did we just do with na.omit()? What are the implications?
  • Why is it important for our outcome variable (diabetes) to be a factor?
  • What are some other strategies for handling missing data besides na.omit()? (Imputation, etc.)

Model Fitting and Evaluation with Cross-Validation

Now that our data is ready, let’s fit our three models and evaluate them using k-fold cross-validation. We’ll use the caret package, which simplifies the cross-validation process significantly.

Setting up Cross-Validations Control

# Set a random seed for reproducibility
set.seed(123)

# Define cross-validation control
# We'll use 10-fold cross-validation
# 'repeats = 3' means the entire 10-fold CV process is repeated 3 times.
# This gives an even more stable estimate of performance.
train_control <- trainControl(
  method = "repeatedcv",
  number = 10,       # k = 10 folds
  repeats = 3,       # Repeat the 10-fold CV 3 times
  classProbs = TRUE, # Needed for ROC curve later if desired
  summaryFunction = twoClassSummary # For classification, provides AUC, sensitivity, specificity
)
  • Why set.seed()?
  • What’s the difference between number and repeats in trainControl?
  • What other method options are available in trainControl? (e.g., boot, loocv)

Logistic Regression (glm)

# Fit Logistic Regression model using caret's train function
# The 'method = "glm"' specifies a generalized linear model (logistic regression in this case)
# The 'family = "binomial"' is implicitly handled by caret for factor outcomes.
# 'metric = "ROC"' tells caret to optimize/report based on Area Under the ROC Curve.
# This is a good metric for imbalanced classes.

cat("\n--- Fitting Logistic Regression Model ---\n")

--- Fitting Logistic Regression Model ---
model_glm <- train(
  diabetes ~ .,         # Predict 'diabetes' using all other variables
  data = my_data,       # Our pre-processed dataset
  method = "glm",       # Specify Logistic Regression
  family = "binomial",  # For binary classification
  trControl = train_control, # Our defined CV control
  metric = "ROC"        # Evaluate using ROC AUC
)

# Print the model results
print(model_glm)
Generalized Linear Model 

392 samples
  8 predictor
  2 classes: 'No', 'Yes' 

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 352, 353, 353, 353, 353, 353, ... 
Resampling results:

  ROC        Sens       Spec     
  0.8505187  0.8877968  0.5538462
# Access individual folds' performance (optional, for deeper insight)
# model_glm$resample

Linear Discriminant Analysis (LDA)

# Fit LDA model using caret's train function
# 'method = "lda"' for Linear Discriminant Analysis

cat("\n--- Fitting LDA Model ---\n")

--- Fitting LDA Model ---
model_lda <- train(
  diabetes ~ .,
  data = my_data,
  method = "lda",
  trControl = train_control,
  metric = "ROC"
)

# Print the model results
print(model_lda)
Linear Discriminant Analysis 

392 samples
  8 predictor
  2 classes: 'No', 'Yes' 

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 352, 353, 353, 352, 353, 353, ... 
Resampling results:

  ROC        Sens       Spec     
  0.8397984  0.8841406  0.5692308

Quadratic Discriminant (QDA)

# Fit QDA model using caret's train function
# 'method = "qda"' for Quadratic Discriminant Analysis

cat("\n--- Fitting QDA Model ---\n")

--- Fitting QDA Model ---
model_qda <- train(
  diabetes ~ .,
  data = my_data,
  method = "qda",
  trControl = train_control,
  metric = "ROC"
)

# Print the model results
print(model_qda)
Quadratic Discriminant Analysis 

392 samples
  8 predictor
  2 classes: 'No', 'Yes' 

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 353, 353, 353, 352, 353, 353, ... 
Resampling results:

  ROC        Sens       Spec     
  0.8265505  0.8643875  0.6153846

Comparing Models

# Compare the models using resamples
# This provides a statistical comparison of performance metrics across models.
resample_results <- resamples(list(
  Logistic = model_glm,
  LDA = model_lda,
  QDA = model_qda
))

# Summarize the comparison
summary(resample_results)

Call:
summary.resamples(object = resample_results)

Models: Logistic, LDA, QDA 
Number of resamples: 30 

ROC 
              Min.   1st Qu.    Median      Mean   3rd Qu.
Logistic 0.7485207 0.7995562 0.8431953 0.8505187 0.9075444
LDA      0.7236467 0.7914201 0.8284024 0.8397984 0.9016272
QDA      0.6210826 0.7877219 0.8317993 0.8265505 0.8823964
              Max. NA's
Logistic 0.9792899    0
LDA      0.9467456    0
QDA      0.9556213    0

Sens 
              Min.   1st Qu.    Median      Mean   3rd Qu.
Logistic 0.7307692 0.8475783 0.8888889 0.8877968 0.9230769
LDA      0.7407407 0.8461538 0.9038462 0.8841406 0.9230769
QDA      0.7037037 0.8461538 0.8490028 0.8643875 0.9145299
         Max. NA's
Logistic    1    0
LDA         1    0
QDA         1    0

Spec 
              Min.   1st Qu.    Median      Mean   3rd Qu.
Logistic 0.2307692 0.4615385 0.5384615 0.5538462 0.6153846
LDA      0.3076923 0.4615385 0.5384615 0.5692308 0.6730769
QDA      0.3846154 0.4807692 0.6153846 0.6153846 0.6923077
              Max. NA's
Logistic 0.8461538    0
LDA      0.8461538    0
QDA      0.9230769    0
# Visualize the comparison (e.g., box plots of ROC, Sensitivity, Specificity)
scales <- list(x = list(relation = "free"), y = list(relation = "free"))
bwplot(resample_results, scales = scales)


# You can also use a dotplot for a different visualization
dotplot(resample_results, scales = scales)

  • Looking at the output of print(model_glm), what do the ‘ROC’, ‘Sens’, and ‘Spec’ values represent? (Area Under the ROC Curve, Sensitivity/Recall, Specificity).
  • How do the ROC values compare across the three models? Which one performs best on average according to this metric?
  • What does the bwplot or dotplot tell us about the variability of the model’s performance across the folds?
  • Based on these results, which model would you recommend for this dataset and why? (Encourage discussion on trade-offs, interpretability, and assumptions).
  • If a model has a much higher ROC but lower Sens compared to another, what might that imply about its performance on different types of errors (false positives vs. false negatives)?

Predictions and Interpretation

caret handles much of the complexity, it’s important to understand how to make predictions with your trained models and interpret their coefficients.

Making Predictions

# Let's use the best performing model (or pick one for demonstration)
# For example, let's use the Logistic Regression model for prediction.
# We'll predict on the original training data for simplicity,
# but in a real scenario, you'd use a completely unseen test set.

# Predict class labels
predictions_glm <- predict(model_glm, newdata = my_data)
head(predictions_glm)
[1] No  Yes No  Yes Yes Yes
Levels: No Yes
# Predict class probabilities
probabilities_glm <- predict(model_glm, newdata = my_data, type = "prob")
head(probabilities_glm)

# Create a confusion matrix for the logistic regression model on training data
# This is NOT cross-validated performance, but good for understanding.
confusionMatrix(predictions_glm, my_data$diabetes)
Confusion Matrix and Statistics

          Reference
Prediction  No Yes
       No  233  56
       Yes  29  74
                                        
               Accuracy : 0.7832        
                 95% CI : (0.739, 0.823)
    No Information Rate : 0.6684        
    P-Value [Acc > NIR] : 3.836e-07     
                                        
                  Kappa : 0.4839        
                                        
 Mcnemar's Test P-Value : 0.004801      
                                        
            Sensitivity : 0.8893        
            Specificity : 0.5692        
         Pos Pred Value : 0.8062        
         Neg Pred Value : 0.7184        
             Prevalence : 0.6684        
         Detection Rate : 0.5944        
   Detection Prevalence : 0.7372        
      Balanced Accuracy : 0.7293        
                                        
       'Positive' Class : No            
                                        

Interpreting Coefficients (Logistic Regression)

# For Logistic Regression, we can inspect the coefficients
# Note: For LDA/QDA, interpretation is more about discriminant functions,
# not direct coefficient interpretation like GLM.

# Access the fitted GLM model object from the caret train object
glm_object <- model_glm$finalModel
summary(glm_object)

Call:
NULL

Coefficients:
              Estimate Std. Error z value Pr(>|z|)    
(Intercept) -1.004e+01  1.218e+00  -8.246  < 2e-16 ***
pregnant     8.216e-02  5.543e-02   1.482  0.13825    
glucose      3.827e-02  5.768e-03   6.635 3.24e-11 ***
pressure    -1.420e-03  1.183e-02  -0.120  0.90446    
triceps      1.122e-02  1.708e-02   0.657  0.51128    
insulin     -8.253e-04  1.306e-03  -0.632  0.52757    
mass         7.054e-02  2.734e-02   2.580  0.00989 ** 
pedigree     1.141e+00  4.274e-01   2.669  0.00760 ** 
age          3.395e-02  1.838e-02   1.847  0.06474 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 498.10  on 391  degrees of freedom
Residual deviance: 344.02  on 383  degrees of freedom
AIC: 362.02

Number of Fisher Scoring iterations: 5
# Interpret coefficients (e.g., odds ratios)
# For a coefficient 'b', the odds ratio is exp(b)
# Example: For 'glucose', if the coefficient is 'x', then for every unit increase in glucose,
# the odds of having diabetes are multiplied by exp(x), assuming other predictors are constant.
exp(coef(glm_object))
 (Intercept)     pregnant      glucose     pressure 
4.358754e-05 1.085629e+00 1.039011e+00 9.985807e-01 
     triceps      insulin         mass     pedigree 
1.011285e+00 9.991750e-01 1.073085e+00 3.129611e+00 
         age 
1.034535e+00 
  • What’s the difference between type = "raw" (default predict output) and type = "prob"?
  • Why is interpreting coefficients more straightforward for Logistic Regression than for LDA/QDA?
  • What does an odds ratio less than 1 mean? Greater than 1?

Recap and Advanced Topics

  • Logistic Regression: Linear decision boundary, predicts probabilities.
  • LDA: Linear decision boundary, assumes equal covariance matrices.
  • QDA: Quadratic decision boundary, assumes unequal covariance matrices.
  • Cross-validation: Essential for reliable model performance estimation.

Further Exploration

  • Hyperparameter Tuning: For more complex models (like Random Forests, SVMs), caret can also tune hyperparameters automatically during cross-validation. Explore tuneGrid and tuneLength arguments in train().
  • Other Metrics: Investigate other evaluation metrics like Precision, Recall, F1-score, especially for imbalanced datasets.
  • Imbalanced Data: The PimaIndiansDiabetes2 dataset has some class imbalance. Research techniques for handling imbalanced data (e.g., SMOTE, undersampling/oversampling) and how they integrate with caret.
  • Feature Engineering/Selection: How would feature engineering or feature selection impact these models?
  • Visualization: Create ROC curves for each model to visualize their trade-offs between sensitivity and specificity.

Appendix

ROC Curve Plot (requires pROC package or manual calculation)

# install.packages("pROC")
library(pROC)

# Get predictions from one of the models (e.g., Logistic Regression)
# Note: This is on the training data, for illustration. For true ROC, use a held-out test set.
prob_glm <- predict(model_glm, newdata = my_data, type = "prob")

# Create ROC object
roc_glm <- roc(response = my_data$diabetes, predictor = prob_glm$Yes, levels = c("No", "Yes"))
Setting direction: controls < cases
# Plot ROC curve
plot(roc_glm, main = "ROC Curve for Logistic Regression", col = "blue", lwd = 2)

auc(roc_glm) # Area Under Curve
Area under the curve: 0.8624
# You could loop this for all models and plot on the same graph for comparison

Without caret

It’s certainly possible to perform cross-validation and fit these models without caret. While caret simplifies the process significantly, doing it manually provides a deeper understanding of the underlying mechanics.

Here’s the same demo without using the caret package. This version focuses more on manual loops for cross-validation and direct calls to glm, lda, and qda.

Manual K-Fold Cross-Validation Setup

# Set a random seed for reproducibility
set.seed(123)

# Define number of folds (k)
k_folds <- 10
num_repeats <- 3 # We'll repeat the entire CV process multiple times for more stable results

# Create empty lists to store results for each model and repeat
results_glm <- list()
results_lda <- list()
results_qda <- list()

# Store performance metrics (ROC, Sensitivity, Specificity) for each fold/repeat
metrics_glm <- data.frame(ROC = numeric(), Sensitivity = numeric(), Specificity = numeric())
metrics_lda <- data.frame(ROC = numeric(), Sensitivity = numeric(), Specificity = numeric())
metrics_qda <- data.frame(ROC = numeric(), Sensitivity = numeric(), Specificity = numeric())

# Get the number of observations
n_obs <- nrow(my_data)

# Outer loop for repeats
for (rep in 1:num_repeats) {
  cat(paste0("\n--- Cross-Validation Repeat ", rep, " ---\n"))

  # Randomly permute the data to ensure folds are diverse
  shuffled_data <- my_data[sample(n_obs), ]

  # Create folds
  # This creates a vector indicating which fold each row belongs to
  folds <- cut(seq(1, n_obs), breaks = k_folds, labels = FALSE)

  # Inner loop for folds
  for (i in 1:k_folds) {
    cat(paste0("  Fold ", i, "...\n"))

    # Define training and testing sets for this fold
    test_indices <- which(folds == i)
    train_data <- shuffled_data[-test_indices, ]
    test_data <- shuffled_data[test_indices, ]

    # --- Fit Logistic Regression ---
    tryCatch({ # Use tryCatch to handle potential errors (e.g., perfect separation)
      model_glm_fold <- glm(diabetes ~ ., data = train_data, family = "binomial")
      pred_prob_glm <- predict(model_glm_fold, newdata = test_data, type = "response")
      pred_class_glm <- factor(ifelse(pred_prob_glm > 0.5, "Yes", "No"), levels = c("No", "Yes"))

      # Ensure test_data$diabetes has consistent levels
      test_data$diabetes <- factor(test_data$diabetes, levels = c("No", "Yes"))

      # Calculate confusion matrix for GLM
      cm_glm <- table(Predicted = pred_class_glm, Actual = test_data$diabetes)
      # Calculate metrics manually or using pROC for AUC
      # Need to handle cases where classes might be missing in a fold
      if (ncol(cm_glm) == 2 && nrow(cm_glm) == 2) {
          TP_glm <- cm_glm[2,2]
          TN_glm <- cm_glm[1,1]
          FP_glm <- cm_glm[2,1]
          FN_glm <- cm_glm[1,2]

          sensitivity_glm <- TP_glm / (TP_glm + FN_glm)
          specificity_glm <- TN_glm / (TN_glm + FP_glm)

          # Calculate ROC AUC (requires at least two distinct predictions and two classes in test set)
          if (length(unique(test_data$diabetes)) > 1 && length(unique(pred_prob_glm)) > 1) {
              roc_obj_glm <- roc(response = test_data$diabetes, predictor = pred_prob_glm, levels = c("No", "Yes"), quiet = TRUE)
              auc_glm <- auc(roc_obj_glm)
          } else {
              auc_glm <- NA # Cannot compute AUC if only one class or constant predictions
          }
      } else {
          sensitivity_glm <- NA
          specificity_glm <- NA
          auc_glm <- NA
      }

      metrics_glm <- rbind(metrics_glm, data.frame(ROC = auc_glm, Sensitivity = sensitivity_glm, Specificity = specificity_glm))

    }, error = function(e) {
      cat("  GLM Error in fold ", i, ": ", e$message, "\n")
      metrics_glm <- rbind(metrics_glm, data.frame(ROC = NA, Sensitivity = NA, Specificity = NA))
    })

    # --- Fit LDA ---
    tryCatch({
      # Ensure there are at least two distinct classes in the training data for LDA
      if (length(unique(train_data$diabetes)) > 1) {
        model_lda_fold <- lda(diabetes ~ ., data = train_data)
        pred_lda <- predict(model_lda_fold, newdata = test_data)
        pred_prob_lda_yes <- pred_lda$posterior[, "Yes"] # Probability of 'Yes' class
        pred_class_lda <- pred_lda$class

        cm_lda <- table(Predicted = pred_class_lda, Actual = test_data$diabetes)
        if (ncol(cm_lda) == 2 && nrow(cm_lda) == 2) {
            TP_lda <- cm_lda[2,2]
            TN_lda <- cm_lda[1,1]
            FP_lda <- cm_lda[2,1]
            FN_lda <- cm_lda[1,2]

            sensitivity_lda <- TP_lda / (TP_lda + FN_lda)
            specificity_lda <- TN_lda / (TN_lda + FP_lda)

            if (length(unique(test_data$diabetes)) > 1 && length(unique(pred_prob_lda_yes)) > 1) {
                roc_obj_lda <- roc(response = test_data$diabetes, predictor = pred_prob_lda_yes, levels = c("No", "Yes"), quiet = TRUE)
                auc_lda <- auc(roc_obj_lda)
            } else {
                auc_lda <- NA
            }
        } else {
            sensitivity_lda <- NA
            specificity_lda <- NA
            auc_lda <- NA
        }
      } else {
          sensitivity_lda <- NA
          specificity_lda <- NA
          auc_lda <- NA
          cat("  LDA skipped in fold ", i, ": Only one class present in training data.\n")
      }
      metrics_lda <- rbind(metrics_lda, data.frame(ROC = auc_lda, Sensitivity = sensitivity_lda, Specificity = specificity_lda))

    }, error = function(e) {
      cat("  LDA Error in fold ", i, ": ", e$message, "\n")
      metrics_lda <- rbind(metrics_lda, data.frame(ROC = NA, Sensitivity = NA, Specificity = NA))
    })

    # --- Fit QDA ---
    tryCatch({
      # QDA often requires more data per class, can fail if not enough observations
      if (length(unique(train_data$diabetes)) > 1 &&
          sum(train_data$diabetes == "No") > ncol(train_data) &&
          sum(train_data$diabetes == "Yes") > ncol(train_data)) { # Rule of thumb for QDA
        model_qda_fold <- qda(diabetes ~ ., data = train_data)
        pred_qda <- predict(model_qda_fold, newdata = test_data)
        pred_prob_qda_yes <- pred_qda$posterior[, "Yes"]
        pred_class_qda <- pred_qda$class

        cm_qda <- table(Predicted = pred_class_qda, Actual = test_data$diabetes)
        if (ncol(cm_qda) == 2 && nrow(cm_qda) == 2) {
            TP_qda <- cm_qda[2,2]
            TN_qda <- cm_qda[1,1]
            FP_qda <- cm_qda[2,1]
            FN_qda <- cm_qda[1,2]

            sensitivity_qda <- TP_qda / (TP_qda + FN_qda)
            specificity_qda <- TN_qda / (TN_qda + FP_qda)

            if (length(unique(test_data$diabetes)) > 1 && length(unique(pred_prob_qda_yes)) > 1) {
                roc_obj_qda <- roc(response = test_data$diabetes, predictor = pred_prob_qda_yes, levels = c("No", "Yes"), quiet = TRUE)
                auc_qda <- auc(roc_obj_qda)
            } else {
                auc_qda <- NA
            }
        } else {
            sensitivity_qda <- NA
            specificity_qda <- NA
            auc_qda <- NA
        }
      } else {
          sensitivity_qda <- NA
          specificity_qda <- NA
          auc_qda <- NA
          cat("  QDA skipped in fold ", i, ": Not enough observations per class in training data.\n")
      }
      metrics_qda <- rbind(metrics_qda, data.frame(ROC = auc_qda, Sensitivity = sensitivity_qda, Specificity = specificity_qda))

    }, error = function(e) {
      cat("  QDA Error in fold ", i, ": ", e$message, "\n")
      metrics_qda <- rbind(metrics_qda, data.frame(ROC = NA, Sensitivity = NA, Specificity = NA))
    })
  } # End of inner fold loop
} # End of outer repeat loop

--- Cross-Validation Repeat 1 ---
  Fold 1...
  Fold 2...
  Fold 3...
  Fold 4...
  Fold 5...
  Fold 6...
  Fold 7...
  Fold 8...
  Fold 9...
  Fold 10...

--- Cross-Validation Repeat 2 ---
  Fold 1...
  Fold 2...
  Fold 3...
  Fold 4...
  Fold 5...
  Fold 6...
  Fold 7...
  Fold 8...
  Fold 9...
  Fold 10...

--- Cross-Validation Repeat 3 ---
  Fold 1...
  Fold 2...
  Fold 3...
  Fold 4...
  Fold 5...
  Fold 6...
  Fold 7...
  Fold 8...
  Fold 9...
  Fold 10...

Summarizing and Comparing Results

After running all the folds and repeats, we’ll have a collection of performance metrics. Let’s summarize them and compare our models.

# Summarize the cross-validation results
cat("\n--- Summary of Logistic Regression Performance ---\n")

--- Summary of Logistic Regression Performance ---
summary(metrics_glm)
      ROC          Sensitivity      Specificity    
 Min.   :0.6759   Min.   :0.1429   Min.   :0.7727  
 1st Qu.:0.7695   1st Qu.:0.5000   1st Qu.:0.8519  
 Median :0.8514   Median :0.5774   Median :0.8889  
 Mean   :0.8379   Mean   :0.5534   Mean   :0.8877  
 3rd Qu.:0.8992   3rd Qu.:0.6774   3rd Qu.:0.9223  
 Max.   :0.9423   Max.   :0.8000   Max.   :0.9655  
cat("Average ROC: ", mean(metrics_glm$ROC, na.rm = TRUE), "\n")
Average ROC:  0.8378616 
cat("Average Sensitivity: ", mean(metrics_glm$Sensitivity, na.rm = TRUE), "\n")
Average Sensitivity:  0.5534245 
cat("Average Specificity: ", mean(metrics_glm$Specificity, na.rm = TRUE), "\n")
Average Specificity:  0.8877038 
cat("\n--- Summary of LDA Performance ---\n")

--- Summary of LDA Performance ---
summary(metrics_lda)
      ROC          Sensitivity      Specificity    
 Min.   :0.6728   Min.   :0.1429   Min.   :0.7619  
 1st Qu.:0.7708   1st Qu.:0.4659   1st Qu.:0.8519  
 Median :0.8485   Median :0.5833   Median :0.8889  
 Mean   :0.8374   Mean   :0.5521   Mean   :0.8831  
 3rd Qu.:0.8942   3rd Qu.:0.6618   3rd Qu.:0.9200  
 Max.   :0.9444   Max.   :0.8000   Max.   :0.9643  
cat("Average ROC: ", mean(metrics_lda$ROC, na.rm = TRUE), "\n")
Average ROC:  0.8374091 
cat("Average Sensitivity: ", mean(metrics_lda$Sensitivity, na.rm = TRUE), "\n")
Average Sensitivity:  0.5520997 
cat("Average Specificity: ", mean(metrics_lda$Specificity, na.rm = TRUE), "\n")
Average Specificity:  0.8831438 
cat("\n--- Summary of QDA Performance ---\n")

--- Summary of QDA Performance ---
summary(metrics_qda)
      ROC          Sensitivity      Specificity    
 Min.   :0.6667   Min.   :0.1250   Min.   :0.7600  
 1st Qu.:0.7771   1st Qu.:0.5000   1st Qu.:0.8180  
 Median :0.8240   Median :0.6339   Median :0.8596  
 Mean   :0.8235   Mean   :0.5729   Mean   :0.8543  
 3rd Qu.:0.8819   3rd Qu.:0.7044   3rd Qu.:0.8878  
 Max.   :0.9517   Max.   :0.8333   Max.   :0.9655  
cat("Average ROC: ", mean(metrics_qda$ROC, na.rm = TRUE), "\n")
Average ROC:  0.8234612 
cat("Average Sensitivity: ", mean(metrics_qda$Sensitivity, na.rm = TRUE), "\n")
Average Sensitivity:  0.5729406 
cat("Average Specificity: ", mean(metrics_qda$Specificity, na.rm = TRUE), "\n")
Average Specificity:  0.8543147 
# Combine results for easier comparison and visualization
all_metrics <- rbind(
  data.frame(Model = "Logistic", metrics_glm),
  data.frame(Model = "LDA", metrics_lda),
  data.frame(Model = "QDA", metrics_qda)
)

# Remove rows with NA values (from failed folds) for plotting
all_metrics_clean <- na.omit(all_metrics)

# --- Visualization (requires ggplot2, not loaded by default here, but good for discussion) ---
# install.packages("ggplot2")
library(ggplot2)

# Boxplot of ROC values
ggplot(all_metrics_clean, aes(x = Model, y = ROC, fill = Model)) +
  geom_boxplot() +
  labs(title = "Model Performance Comparison (ROC)",
       y = "ROC AUC", x = "Model") +
  theme_minimal()


# Boxplot of Sensitivity values
ggplot(all_metrics_clean, aes(x = Model, y = Sensitivity, fill = Model)) +
  geom_boxplot() +
  labs(title = "Model Performance Comparison (Sensitivity)",
       y = "Sensitivity", x = "Model") +
  theme_minimal()


# Boxplot of Specificity values
ggplot(all_metrics_clean, aes(x = Model, y = Specificity, fill = Model)) +
  geom_boxplot() +
  labs(title = "Model Performance Comparison (Specificity)",
       y = "Specificity", x = "Model") +
  theme_minimal()

Here’s a summary of the average performance metrics for each model:

Model Average ROC Average Sensitivity Average Specificity
Logistic Regression 0.8379 0.5534 0.8877
LDA 0.8374 0.5521 0.8831
QDA 0.8235 0.5729 0.8543

Overall Performance (ROC AUC):

  • Logistic Regression (0.8379) and LDA (0.8374) have almost identical average ROC AUCs. The difference is negligible.
  • QDA (0.8235) has a slightly lower average ROC AUC compared to Logistic Regression and LDA.
  • Interpretation: Based purely on overall discriminative power across all possible thresholds, Logistic Regression and LDA are performing equally well and slightly better than QDA. An AUC above 0.8 is generally considered good, indicating that the models have strong discriminatory power.

Sensitivity (True Positive Rate):

  • QDA (0.5729) has the highest average Sensitivity.
  • Logistic Regression (0.5534) and LDA (0.5521) have very similar and slightly lower average Sensitivities compared to QDA.
  • Interpretation: QDA is slightly better at correctly identifying actual positive cases (e.g., people with diabetes). This means it has fewer false negatives.

Specificity (True Negative Rate):

  • Logistic Regression (0.8877) has the highest average Specificity.
  • LDA (0.8831) is very close to Logistic Regression.
  • QDA (0.8543) has a lower average Specificity compared to Logistic Regression and LDA.
  • Logistic Regression is best at correctly identifying actual negative cases (e.g., people without diabetes). This means it has fewer false positives.

Which one is the “best”?

The “best” model depends on the specific goals and costs associated with False Positives and False Negatives in your application.

  • If overall discriminative power (considering all thresholds) is paramount, and you don’t have a strong preference for minimizing one type of error over another:
    • Logistic Regression or LDA are the top contenders, as their average ROC AUCs are virtually identical and the highest. There’s not enough difference here to declare one definitively better than the other based solely on this metric.
  • If minimizing False Negatives (catching as many positive cases as possible) is more critical (i.e., high Sensitivity is preferred):
    • QDA might be considered slightly better due to its marginally higher average Sensitivity (0.5729). However, keep in mind that this comes at the cost of lower Specificity.
  • If minimizing False Positives (correctly identifying negative cases and avoiding false alarms) is more critical (i.e., high Specificity is preferred):
    • Logistic Regression (or very closely, LDA) is clearly the winner. It has the highest average Specificity, meaning it’s less likely to incorrectly classify a negative case as positive.

Based on these results:

  • For overall balanced performance (highest ROC AUC) and strongest control over False Positives (highest Specificity), Logistic Regression is marginally the “best,” closely followed by LDA.
  • If you prioritize slightly higher Sensitivity (catching more true positives), QDA offers a small advantage, but at the cost of lower Specificity and a slightly lower overall ROC.

Logistic Regression appears to be the most robust choice among these three models for this dataset. The differences between Logistic Regression and LDA are so small that they could be considered effectively equivalent. You’d likely choose between them based on ease of implementation, interpretability, or minor theoretical preferences.

How to interpret ROC, Sensitivity, and Specificity

ROC, Sensitivity, and Specificity are three metrics for evaluating the performance of classification models, especially in situations where you care about different types of errors. To understand these, first understand the four basic outcomes in a binary classification problem, often represented in a confusion matrix:

Actual Positive Actual Negative
Predicted Positive True Positive (TP) False Positive (FP)
Predicted Negative False Negative (FN) True Negative (TN)
  • True Positive (TP): The model correctly predicted a positive outcome (e.g., predicted “has diabetes,” and the person actually has diabetes).
  • False Positive (FP): The model incorrectly predicted a positive outcome (e.g., predicted “has diabetes,” but the person actually does not have diabetes). This is also known as a Type I error or a “false alarm.”
  • False Negative (FN): The model incorrectly predicted a negative outcome (e.g., predicted “does not have diabetes,” but the person actually has diabetes). This is also known as a Type II error or a “miss.”
  • True Negative (TN): The model correctly predicted a negative outcome (e.g., predicted “does not have diabetes,” and the person actually does not have diabetes).

Sensitivity (Recall, True Positive Rate - TPR)

Sensitivity measures the proportion of actual positive cases that were correctly identified by the model. It answers the question: “Out of all the people who truly have the condition, how many did our model correctly find?”

\(Sensitivity = \frac{TP}{TP+FN}\)

  • High Sensitivity (closer to 1.0): Means the model is very good at catching positive cases. It has a low rate of False Negatives (missing actual positives).
  • Low Sensitivity (closer to 0.0): Means the model misses many actual positive cases, leading to a high rate of False Negatives.

Sensitivity is crucial when the cost of a False Negative is high.

  • Medical Diagnosis (e.g., detecting a serious disease like cancer): You want very high sensitivity because missing a disease (false negative) can have severe consequences for the patient. You’d rather have some false alarms (false positives) that can be further investigated.
  • Security Systems: You want to detect all intruders (high sensitivity), even if it means some false alarms.
  • Fraud Detection: You want to catch as many fraudulent transactions as possible.

SNNOUT (Sensitive Negative = Rule OUT)

If a highly sensitive test comes back negative, you can confidently rule out the disease/condition.

Specificity (True Negative Rate - TNR)

Specificity measures the proportion of actual negative cases that were correctly identified by the model. It answers the question: “Out of all the people who truly do NOT have the condition, how many did our model correctly identify as not having it?”

\(Specificity = \frac{TN}{TN+FP}\)

  • High Specificity (closer to 1.0): Means the model is very good at correctly identifying negative cases. It has a low rate of False Positives (incorrectly classifying negatives as positives).
  • Low Specificity (closer to 0.0): Means the model incorrectly flags many negative cases as positive, leading to a high rate of False Positives.

Specificity is crucial when the cost of a False Positive is high.

  • Spam Detection: You want high specificity to avoid marking legitimate emails as spam (false positive), which would be very annoying to users. You might tolerate a few spam emails getting through (false negatives).
  • Legal/Criminal Justice: Incorrectly identifying an innocent person as guilty (false positive) has high societal costs.
  • Expensive or Invasive Follow-up Tests: If a positive test leads to a costly, painful, or risky follow-up procedure, you want high specificity to minimize unnecessary procedures.

SPPIN (Specific Positive = Rule IN)

If a highly specific test comes back positive, you can confidently rule in the disease/condition.

Reciever Operating Characteristic Curve (ROC)

The ROC curve is a graphical plot that illustrates the diagnostic ability of a binary classifier system as its discrimination threshold is varied. It plots the True Positive Rate (Sensitivity) on the y-axis against the False Positive Rate (1 - Specificity) on the x-axis for different possible thresholds.

  • Each point on the ROC curve represents a sensitivity/specificity pair corresponding to a particular classification threshold.

    • Top-left corner (0,1): This represents a perfect classifier: 100% Sensitivity (no false negatives) and 100% Specificity (no false positives). The closer your curve is to this corner, the better the model’s overall performance.
    • Diagonal line (from 0,0 to 1,1): This represents a random classifier (equivalent to flipping a coin). A model whose ROC curve falls along this diagonal is performing no better than chance.
    • Curve below the diagonal: Indicates a model that performs worse than random chance. If this happens, you can often just invert the model’s predictions to get a better-than-chance model!
  • The AUC is a single scalar value that summarizes the overall performance of the classifier across all possible thresholds. It ranges from 0 to 1.

    • AUC = 1: Perfect classification.
    • AUC = 0.5: Performance is no better than random guessing.
    • AUC < 0.5: Performance is worse than random guessing.
    • *A higher AUC indicates a better model. It can be interpreted as the probability that the model ranks a randomly chosen positive instance higher than a randomly chosen negative instance.
# Install necessary packages (if not already installed)
# install.packages("mlbench")
# install.packages("MASS")
# install.packages("dplyr")
# install.packages("ggplot2") # For plotting
# install.packages("pROC")    # For ROC curves

# Load packages
library(mlbench)
library(MASS)
library(dplyr)
library(ggplot2)
library(pROC) # Load pROC for ROC curves and AUC

# Load the dataset
data(PimaIndiansDiabetes2)
my_data <- PimaIndiansDiabetes2

# Data Preparation (using dplyr)
my_data <- my_data %>%
  na.omit() %>%
  mutate(diabetes = factor(diabetes, levels = c("neg", "pos"), labels = c("No", "Yes")))

# --- 1. Fit each model on the *entire* dataset ---
# This is a final model, not part of the cross-validation loop.
# It's used for plotting the "overall" ROC curve for the full dataset.

cat("\n--- Fitting Final Models on Full Dataset for ROC Curve ---\n")

--- Fitting Final Models on Full Dataset for ROC Curve ---
# Logistic Regression
final_model_glm <- glm(diabetes ~ ., data = my_data, family = "binomial")

# LDA
final_model_lda <- lda(diabetes ~ ., data = my_data)

# QDA
# Add a check to ensure QDA can be fitted (sufficient data per class relative to predictors)
# This is similar to the check in the CV loop, but applied to the full dataset.
# The `ncol(my_data) - 1` accounts for all predictors
if (sum(my_data$diabetes == "No") >= (ncol(my_data) - 1) &&
    sum(my_data$diabetes == "Yes") >= (ncol(my_data) - 1)) {
  final_model_qda <- qda(diabetes ~ ., data = my_data)
} else {
  warning("QDA could not be fitted on the full dataset due to insufficient observations per class.")
  final_model_qda <- NULL # Set to NULL if it cannot be fitted
}


# --- 2. Get predicted probabilities for the 'Yes' class from each model ---

# Logistic Regression probabilities
# type = "response" gives probabilities directly for glm
prob_glm <- predict(final_model_glm, newdata = my_data, type = "response")

# LDA probabilities
# predict() for lda/qda returns a list; $posterior contains probabilities
prob_lda <- predict(final_model_lda, newdata = my_data)$posterior[, "Yes"]

# QDA probabilities (check if QDA model was successfully fitted)
prob_qda <- if (!is.null(final_model_qda)) {
  predict(final_model_qda, newdata = my_data)$posterior[, "Yes"]
} else {
  NULL # If QDA wasn't fitted, no probabilities
}

# --- 3. Create ROC objects for each model ---

# For Logistic Regression
roc_obj_glm <- roc(response = my_data$diabetes, predictor = prob_glm,
                   levels = c("No", "Yes")) # Ensure levels are in order: negative first
Setting direction: controls < cases
# For LDA
roc_obj_lda <- roc(response = my_data$diabetes, predictor = prob_lda,
                   levels = c("No", "Yes"))
Setting direction: controls < cases
# For QDA (only if the model was fitted)
if (!is.null(prob_qda)) {
  roc_obj_qda <- roc(response = my_data$diabetes, predictor = prob_qda,
                     levels = c("No", "Yes"))
} else {
  roc_obj_qda <- NULL
}
Setting direction: controls < cases
# --- 4. Plot all three ROC curves on the same graph ---

# Start with the first ROC curve (e.g., Logistic Regression)
plot(roc_obj_glm,
     col = "#3182bd", # Color for GLM
     lwd = 2,      # Line width
     main = "ROC Curves for Classification Models",
     sub = paste0("Logistic Regression AUC: ", round(auc(roc_obj_glm), 3),
                  "\nLDA AUC: ", round(auc(roc_obj_lda), 3),
                  if (!is.null(roc_obj_qda)) { paste0("\nQDA AUC: ", round(auc(roc_obj_qda), 3)) }
                  else { "" }
                  ),
     grid = TRUE # Add a grid for better readability
     )

# Add LDA curve to the existing plot
plot(roc_obj_lda,
     col = "#de2d26", # Color for LDA
     lwd = 2,
     add = TRUE # Add to the current plot
     )

# Add QDA curve to the existing plot (if it was fitted)
if (!is.null(roc_obj_qda)) {
  plot(roc_obj_qda,
       col = "#31a354", # Color for QDA
       lwd = 2,
       add = TRUE
       )
}

# Add a legend to distinguish the curves
legend("bottomright",
       legend = c(paste0("Logistic Regression (AUC = ", round(auc(roc_obj_glm), 3), ")"),
                  paste0("LDA (AUC = ", round(auc(roc_obj_lda), 3), ")"),
                  if (!is.null(roc_obj_qda)) { paste0("QDA (AUC = ", round(auc(roc_obj_qda), 3), ")") }
                  else { "QDA (Not Fitted)" }
                  ),
       col = c("#3182bd", "#de2d26", "#31a354"),
       lwd = 2,
       cex = 0.8 # Font size for legend
       )

# You can also add the diagonal line representing random chance
abline(a = 0, b = 1, lty = 2, col = "#bdbdbd") # Dashed gray line


# --- Optional: Print individual AUCs again for easy reference ---
cat("\n--- AUC Values for Final Models ---\n")

--- AUC Values for Final Models ---
cat("Logistic Regression AUC: ", auc(roc_obj_glm), "\n")
Logistic Regression AUC:  0.8623605 
cat("LDA AUC: ", auc(roc_obj_lda), "\n")
LDA AUC:  0.8605109 
if (!is.null(roc_obj_qda)) {
  cat("QDA AUC: ", auc(roc_obj_qda), "\n")
} else {
  cat("QDA AUC: Not applicable (model not fitted)\n")
}
QDA AUC:  0.8652965 

Why it’s important:

  • Unlike Accuracy, Sensitivity, and Specificity (which depend on a chosen threshold), the ROC curve and AUC provide a view of the model’s performance across all possible thresholds. This is especially useful for models that output probabilities (like logistic regression), as you can choose the optimal threshold later based on your specific needs.
  • AUC is generally a more reliable metric than accuracy when dealing with imbalanced datasets (where one class is much more prevalent than the other). A classifier can achieve high accuracy by simply predicting the majority class all the time, but its AUC would reveal its poor discriminatory power.
  • AUC is excellent for comparing different models because it summarizes their overall discriminative power in a single number. The model with a higher AUC is generally preferred.

Trade-offs between Sensitivity and Specificity (and how ROC shows it)

There’s often a trade-off between sensitivity and specificity.

  • If you set a very low threshold for classifying an outcome as “positive” (e.g., if any probability above 0.1 is considered positive), you’ll likely increase your sensitivity (catch more true positives) but also increase your false positive rate (decrease specificity).
  • Conversely, if you set a very high threshold (e.g., only probabilities above 0.9 are positive), you’ll increase your specificity (reduce false positives) but likely decrease your sensitivity (miss more true positives).
LS0tDQp0aXRsZTogIkxvZ2lzdGljIFJlZ3Jlc3Npb24sIExEQSwgYW5kIFFEQSB3aXRoIENyb3NzLVZhbGlkYXRpb24iDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KLS0tDQoNClRoaXMgZGVtbyBjb3ZlcnMgc29tZSBmdW5kYW1lbnRhbCBjbGFzc2lmaWNhdGlvbiB0ZWNobmlxdWVzOiBbTG9naXN0aWMgUmVncmVzc2lvbl0oaHR0cHM6Ly9zdGF0cy5vYXJjLnVjbGEuZWR1L3IvZGFlL2xvZ2l0LXJlZ3Jlc3Npb24vKSwgW0xpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMgKExEQSldKGh0dHBzOi8vd3d3LnN0aGRhLmNvbS9lbmdsaXNoL2FydGljbGVzLzM2LWNsYXNzaWZpY2F0aW9uLW1ldGhvZHMtZXNzZW50aWFscy8xNDYtZGlzY3JpbWluYW50LWFuYWx5c2lzLWVzc2VudGlhbHMtaW4tci8pLCBhbmQgW1F1YWRyYXRpYyBEaXNjcmltaW5hbnQgQW5hbHlzaXMgKFFEQSldKGh0dHBzOi8vd3d3LnN0aGRhLmNvbS9lbmdsaXNoL2FydGljbGVzLzM2LWNsYXNzaWZpY2F0aW9uLW1ldGhvZHMtZXNzZW50aWFscy8xNDYtZGlzY3JpbWluYW50LWFuYWx5c2lzLWVzc2VudGlhbHMtaW4tci8pLiBXZSdsbCBhbHNvIGZvY3VzIG9uIGEgY3J1Y2lhbCBhc3BlY3Qgb2YgbW9kZWwgYnVpbGRpbmc6IHJvYnVzdCBldmFsdWF0aW9uIHVzaW5nIFtjcm9zcy12YWxpZGF0aW9uXShodHRwczovL3d3dy5nZWVrc2ZvcmdlZWtzLm9yZy9yLWxhbmd1YWdlL2Nyb3NzLXZhbGlkYXRpb24taW4tci1wcm9ncmFtbWluZy8pLiANCg0KIyMgTGVhcm5pbmcgT2JqZWN0aXZlczogIA0KIC0gVW5kZXJzdGFuZCB0aGUgdGhlb3JldGljYWwgdW5kZXJwaW5uaW5ncyBvZiBMb2dpc3RpYyBSZWdyZXNzaW9uLCBMaW5lYXIgRGlzY3JpbWluYW50IEFuYWx5c2lzIChMREEpLCBhbmQgUXVhZHJhdGljIERpc2NyaW1pbmFudCBBbmFseXNpcyAoUURBKS4gIA0KIC0gSW1wbGVtZW50IHRoZXNlIG1vZGVscyBpbiBgUmAuICANCiAtIEFwcGx5ICprKi1mb2xkIGNyb3NzLXZhbGlkYXRpb24gZm9yIHJvYnVzdCBtb2RlbCBldmFsdWF0aW9uLiAgDQogLSBDb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBkaWZmZXJlbnQgY2xhc3NpZmljYXRpb24gbW9kZWxzLiAgDQoNCiMjIFdoYXQgYXJlIENsYXNzaWZpY2F0aW9uIE1vZGVscz8NCg0KIC0gX19Hb2FsOl9fIFByZWRpY3QgYSBjYXRlZ29yaWNhbCBvdXRjb21lIHZhcmlhYmxlIChlLmcuLCBzcGFtL25vdCBzcGFtLCBkaXNlYXNlL25vIGRpc2Vhc2UsIGN1c3RvbWVyIGNodXJuL25vIGNodXJuKS4gIA0KIC0gX19TdXBlcnZpc2VkIExlYXJuaW5nOl9fIFdlIGhhdmUgbGFiZWxlZCBkYXRhIChmZWF0dXJlcyBhbmQga25vd24gb3V0Y29tZXMpLiAgDQoNCiMjIEJyaWVmIFRoZW9yZXRpY2FsIE92ZXJ2aWV3IA0KDQpfXzEuIExvZ2lzdGljIFJlZ3Jlc3Npb246X18gICANCg0KIC0gV2hhdCdzIHRoZSBjb3JlIGlkZWE/IChQcmVkaWN0cyB0aGUgcHJvYmFiaWxpdHkgb2YgYmVsb25naW5nIHRvIGEgY2xhc3MgdXNpbmcgYSBsb2dpc3RpYyBmdW5jdGlvbikuICANCiAtIFdoYXQgdHlwZSBvZiBkZWNpc2lvbiBib3VuZGFyeSBkb2VzIGl0IGNyZWF0ZT8gKExpbmVhcikuICANCiAtIEFzc3VtcHRpb25zPyAoTm8gc3Ryb25nIGRpc3RyaWJ1dGlvbmFsIGFzc3VtcHRpb25zIG9uIHByZWRpY3RvcnMsIGJ1dCBsaW5lYXJpdHkgaW4gdGhlIGxvZy1vZGRzIGlzIGFzc3VtZWQpLiAgDQoNCl9fMi4gTGluZWFyIERpc2NyaW1pbmFudCBBbmFseXNpcyAoTERBKTpfXw0KDQogLSBXaGF0J3MgdGhlIGNvcmUgaWRlYT8gKEFzc3VtZXMgcHJlZGljdG9ycyB3aXRoaW4gZWFjaCBjbGFzcyBmb2xsb3cgYSBtdWx0aXZhcmlhdGUgR2F1c3NpYW4gZGlzdHJpYnV0aW9uIHdpdGggZXF1YWwgY292YXJpYW5jZSBtYXRyaWNlcykuICANCiAtIEhvdyBkb2VzIGl0IGNsYXNzaWZ5PyAoRmluZHMgbGluZWFyIGNvbWJpbmF0aW9ucyBvZiBwcmVkaWN0b3JzIHRoYXQgYmVzdCBzZXBhcmF0ZSB0aGUgY2xhc3NlcykuICANCiAtIERlY2lzaW9uIGJvdW5kYXJ5PyAoTGluZWFyKS4gIA0KIC0gV2hlbiBtaWdodCBpdCBiZSBwcmVmZXJyZWQgb3ZlciBsb2dpc3RpYyByZWdyZXNzaW9uPyAoV2hlbiBjbGFzc2VzIGFyZSB3ZWxsLXNlcGFyYXRlZCwgb3Igd2hlbiB0aGUgbm9ybWFsaXR5IGFzc3VtcHRpb24gaG9sZHMpLiAgDQoNCl9fMy4gUXVhZHJhdGljIERpc2NyaW1pbmFudCBBbmFseXNpcyAoUURBKTpfXyAgDQoNCiAtIEhvdyBkb2VzIGl0IGRpZmZlciBmcm9tIExEQT8gKEFzc3VtZXMgcHJlZGljdG9ycyB3aXRoaW4gZWFjaCBjbGFzcyBmb2xsb3cgYSBtdWx0aXZhcmlhdGUgR2F1c3NpYW4gZGlzdHJpYnV0aW9uIGJ1dCB3aXRoIHVuZXF1YWwgY292YXJpYW5jZSBtYXRyaWNlcykuICANCiAtIERlY2lzaW9uIGJvdW5kYXJ5PyAoUXVhZHJhdGljKS4gIA0KIC0gV2hlbiBtaWdodCBpdCBiZSBwcmVmZXJyZWQgb3ZlciBMREE/IChXaGVuIHRoZSBhc3N1bXB0aW9uIG9mIGVxdWFsIGNvdmFyaWFuY2UgbWF0cmljZXMgaXMgdmlvbGF0ZWQsIGFuZCB0aGUgdHJ1ZSBkZWNpc2lvbiBib3VuZGFyeSBpcyBub24tbGluZWFyKS4gIA0KDQojIyBXaHkgQ3Jvc3MtVmFsaWRhdGlvbj8NCiAtIFdoeSBkb24ndCB3ZSBqdXN0IHNwbGl0IG91ciBkYXRhIG9uY2UgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzPyIgKERpc2N1c3MgbGltaXRhdGlvbnM6IHNlbnNpdGl2aXR5IHRvIHNwbGl0LCBsZXNzIHJvYnVzdCBwZXJmb3JtYW5jZSBlc3RpbWF0ZSkuICANCiAtIF9fQ3Jvc3MtdmFsaWRhdGlvbjpfXyBBIHRlY2huaXF1ZSB0byBnZXQgYSBtb3JlIHJlbGlhYmxlIGVzdGltYXRlIG9mIGEgbW9kZWwncyBwZXJmb3JtYW5jZSBvbiB1bnNlZW4gZGF0YSBieSByZXBlYXRlZGx5IHNwbGl0dGluZyB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHNldHMuICANCiAtIF9fKmsqLUZvbGQgQ3Jvc3MtVmFsaWRhdGlvbjpfXyBFeHBsYWluIHRoZSBwcm9jZXNzIOKAkyBkaXZpZGUgZGF0YSBpbnRvICprKiBmb2xkcywgdHJhaW4gb24gKmsqLTEgZm9sZHMsIHZhbGlkYXRlIG9uIHRoZSByZW1haW5pbmcgZm9sZCwgcmVwZWF0ICprKiB0aW1lcy4gIA0KIC0gX19CZW5lZml0czpfXyBNb3JlIHJvYnVzdCBwZXJmb3JtYW5jZSBlc3RpbWF0ZSwgdXNlcyBhbGwgZGF0YSBmb3IgYm90aCB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbi4gICANCg0KIyBSIERlbW8NCg0KIyMgRGF0YSBQcmVwICANCldlJ2xsIHVzZSB0aGUgW1BpbWFJbmRpYW5zRGlhYmV0ZXMyXShodHRwczovL3JkcnIuaW8vY3Jhbi9tbGJlbmNoL21hbi9QaW1hSW5kaWFuc0RpYWJldGVzLmh0bWwpIGRhdGFzZXQgZnJvbSB0aGUgYG1sYmVuY2hgIHBhY2thZ2UuDQoNCmBgYHtyfQ0KIyBJbnN0YWxsIG5lY2Vzc2FyeSBwYWNrYWdlcyAoaWYgbm90IGFscmVhZHkgaW5zdGFsbGVkKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJtbGJlbmNoIikNCiMgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKSAjIEZvciBjcm9zcy12YWxpZGF0aW9uIGFuZCBjb252ZW5pZW50IG1vZGVsIHRyYWluaW5nDQojIGluc3RhbGwucGFja2FnZXMoIk1BU1MiKSAgIyBGb3IgTERBIGFuZCBRREENCg0KIyBMb2FkIHBhY2thZ2VzDQpsaWJyYXJ5KG1sYmVuY2gpICAgIyBGb3IgUGltYUluZGlhbnNEaWFiZXRlczIgZGF0YXNldA0KbGlicmFyeShjYXJldCkgICAgICMgRm9yIGNyb3NzLXZhbGlkYXRpb24gYW5kIG1vZGVsIHRyYWluaW5nIHV0aWxpdGllcw0KbGlicmFyeShNQVNTKSAgICAgICMgRm9yIGxkYSgpIGFuZCBxZGEoKSBmdW5jdGlvbnMNCg0KIyBMb2FkIHRoZSBkYXRhc2V0DQpkYXRhKFBpbWFJbmRpYW5zRGlhYmV0ZXMyKQ0KbXlfZGF0YSA8LSBQaW1hSW5kaWFuc0RpYWJldGVzMg0KDQojIEV4cGxvcmUgdGhlIGRhdGENCmhlYWQobXlfZGF0YSkNCnN1bW1hcnkobXlfZGF0YSkNCnN0cihteV9kYXRhKQ0KDQojIENoZWNrIGZvciBtaXNzaW5nIHZhbHVlcw0KY29sU3Vtcyhpcy5uYShteV9kYXRhKSkNCg0KIyBIYW5kbGUgbWlzc2luZyB2YWx1ZXM6IEZvciBzaW1wbGljaXR5IGluIHRoaXMgZGVtbywgd2UnbGwgcmVtb3ZlIHJvd3Mgd2l0aCBOQS4NCiMgSW4gYSByZWFsLXdvcmxkIHNjZW5hcmlvLCBpbXB1dGF0aW9uIHdvdWxkIGJlIGEgYmV0dGVyIGFwcHJvYWNoIGZvciBtaXNzaW5nIGRhdGEuDQpteV9kYXRhIDwtIG5hLm9taXQobXlfZGF0YSkNCmNvbFN1bXMoaXMubmEobXlfZGF0YSkpICMgVmVyaWZ5IG5vIG1vcmUgTkFzDQoNCiMgQ29udmVydCAnZGlhYmV0ZXMnIHRvIGEgZmFjdG9yIHdpdGggZGVzaXJlZCBsZXZlbHMgaWYgbm90IGFscmVhZHkNCiMgVGhlICdvdXRjb21lJyB2YXJpYWJsZSBuZWVkcyB0byBiZSBhIGZhY3RvciBmb3IgY2xhc3NpZmljYXRpb24gbW9kZWxzIGluIFIuDQojIExldCdzIGVuc3VyZSB0aGUgbGV2ZWxzIGFyZSBtZWFuaW5nZnVsLg0KbXlfZGF0YSRkaWFiZXRlcyA8LSBmYWN0b3IobXlfZGF0YSRkaWFiZXRlcywgbGV2ZWxzID0gYygibmVnIiwgInBvcyIpLCBsYWJlbHMgPSBjKCJObyIsICJZZXMiKSkNCg0KIyBFbnN1cmUgb3RoZXIgdmFyaWFibGVzIGFyZSBudW1lcmljIHdoZXJlIGFwcHJvcHJpYXRlDQojIChQaW1hSW5kaWFuc0RpYWJldGVzMiBpcyBnZW5lcmFsbHkgY2xlYW4sIGJ1dCBnb29kIHByYWN0aWNlIHRvIGNoZWNrKQ0KYGBgDQoNCiAtIFdoYXQgZGlkIHdlIGp1c3QgZG8gd2l0aCBgbmEub21pdCgpYD8gV2hhdCBhcmUgdGhlIGltcGxpY2F0aW9ucz8gIA0KIC0gV2h5IGlzIGl0IGltcG9ydGFudCBmb3Igb3VyIG91dGNvbWUgdmFyaWFibGUgKGBkaWFiZXRlc2ApIHRvIGJlIGEgZmFjdG9yPyAgDQogLSBXaGF0IGFyZSBzb21lIG90aGVyIHN0cmF0ZWdpZXMgZm9yIGhhbmRsaW5nIG1pc3NpbmcgZGF0YSBiZXNpZGVzIGBuYS5vbWl0KClgPyAoSW1wdXRhdGlvbiwgZXRjLikgIA0KDQojIyBNb2RlbCBGaXR0aW5nIGFuZCBFdmFsdWF0aW9uIHdpdGggQ3Jvc3MtVmFsaWRhdGlvbiANCk5vdyB0aGF0IG91ciBkYXRhIGlzIHJlYWR5LCBsZXQncyBmaXQgb3VyIHRocmVlIG1vZGVscyBhbmQgZXZhbHVhdGUgdGhlbSB1c2luZyBrLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbi4gV2UnbGwgdXNlIHRoZSBjYXJldCBwYWNrYWdlLCB3aGljaCBzaW1wbGlmaWVzIHRoZSBjcm9zcy12YWxpZGF0aW9uIHByb2Nlc3Mgc2lnbmlmaWNhbnRseS4NCg0KIyMjIFNldHRpbmcgdXAgQ3Jvc3MtVmFsaWRhdGlvbnMgQ29udHJvbCAgDQpgYGB7cn0NCiMgU2V0IGEgcmFuZG9tIHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQ0Kc2V0LnNlZWQoMTIzKQ0KDQojIERlZmluZSBjcm9zcy12YWxpZGF0aW9uIGNvbnRyb2wNCiMgV2UnbGwgdXNlIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbg0KIyAncmVwZWF0cyA9IDMnIG1lYW5zIHRoZSBlbnRpcmUgMTAtZm9sZCBDViBwcm9jZXNzIGlzIHJlcGVhdGVkIDMgdGltZXMuDQojIFRoaXMgZ2l2ZXMgYW4gZXZlbiBtb3JlIHN0YWJsZSBlc3RpbWF0ZSBvZiBwZXJmb3JtYW5jZS4NCnRyYWluX2NvbnRyb2wgPC0gdHJhaW5Db250cm9sKA0KICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsDQogIG51bWJlciA9IDEwLCAgICAgICAjIGsgPSAxMCBmb2xkcw0KICByZXBlYXRzID0gMywgICAgICAgIyBSZXBlYXQgdGhlIDEwLWZvbGQgQ1YgMyB0aW1lcw0KICBjbGFzc1Byb2JzID0gVFJVRSwgIyBOZWVkZWQgZm9yIFJPQyBjdXJ2ZSBsYXRlciBpZiBkZXNpcmVkDQogIHN1bW1hcnlGdW5jdGlvbiA9IHR3b0NsYXNzU3VtbWFyeSAjIEZvciBjbGFzc2lmaWNhdGlvbiwgcHJvdmlkZXMgQVVDLCBzZW5zaXRpdml0eSwgc3BlY2lmaWNpdHkNCikNCmBgYA0KDQogLSBXaHkgYHNldC5zZWVkKClgPyAgDQogLSBXaGF0J3MgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBgbnVtYmVyYCBhbmQgYHJlcGVhdHNgIGluIGB0cmFpbkNvbnRyb2xgPyAgDQogLSBXaGF0IG90aGVyIG1ldGhvZCBvcHRpb25zIGFyZSBhdmFpbGFibGUgaW4gYHRyYWluQ29udHJvbGA/IChlLmcuLCBgYm9vdGAsIGBsb29jdmApICAgIA0KIA0KIyMjIExvZ2lzdGljIFJlZ3Jlc3Npb24gKGdsbSkNCmBgYHtyfQ0KIyBGaXQgTG9naXN0aWMgUmVncmVzc2lvbiBtb2RlbCB1c2luZyBjYXJldCdzIHRyYWluIGZ1bmN0aW9uDQojIFRoZSAnbWV0aG9kID0gImdsbSInIHNwZWNpZmllcyBhIGdlbmVyYWxpemVkIGxpbmVhciBtb2RlbCAobG9naXN0aWMgcmVncmVzc2lvbiBpbiB0aGlzIGNhc2UpDQojIFRoZSAnZmFtaWx5ID0gImJpbm9taWFsIicgaXMgaW1wbGljaXRseSBoYW5kbGVkIGJ5IGNhcmV0IGZvciBmYWN0b3Igb3V0Y29tZXMuDQojICdtZXRyaWMgPSAiUk9DIicgdGVsbHMgY2FyZXQgdG8gb3B0aW1pemUvcmVwb3J0IGJhc2VkIG9uIEFyZWEgVW5kZXIgdGhlIFJPQyBDdXJ2ZS4NCiMgVGhpcyBpcyBhIGdvb2QgbWV0cmljIGZvciBpbWJhbGFuY2VkIGNsYXNzZXMuDQoNCmNhdCgiXG4tLS0gRml0dGluZyBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIC0tLVxuIikNCm1vZGVsX2dsbSA8LSB0cmFpbigNCiAgZGlhYmV0ZXMgfiAuLCAgICAgICAgICMgUHJlZGljdCAnZGlhYmV0ZXMnIHVzaW5nIGFsbCBvdGhlciB2YXJpYWJsZXMNCiAgZGF0YSA9IG15X2RhdGEsICAgICAgICMgT3VyIHByZS1wcm9jZXNzZWQgZGF0YXNldA0KICBtZXRob2QgPSAiZ2xtIiwgICAgICAgIyBTcGVjaWZ5IExvZ2lzdGljIFJlZ3Jlc3Npb24NCiAgZmFtaWx5ID0gImJpbm9taWFsIiwgICMgRm9yIGJpbmFyeSBjbGFzc2lmaWNhdGlvbg0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCAjIE91ciBkZWZpbmVkIENWIGNvbnRyb2wNCiAgbWV0cmljID0gIlJPQyIgICAgICAgICMgRXZhbHVhdGUgdXNpbmcgUk9DIEFVQw0KKQ0KDQojIFByaW50IHRoZSBtb2RlbCByZXN1bHRzDQpwcmludChtb2RlbF9nbG0pDQoNCiMgQWNjZXNzIGluZGl2aWR1YWwgZm9sZHMnIHBlcmZvcm1hbmNlIChvcHRpb25hbCwgZm9yIGRlZXBlciBpbnNpZ2h0KQ0KIyBtb2RlbF9nbG0kcmVzYW1wbGUNCmBgYA0KIyMjIExpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMgKExEQSkNCmBgYHtyfQ0KIyBGaXQgTERBIG1vZGVsIHVzaW5nIGNhcmV0J3MgdHJhaW4gZnVuY3Rpb24NCiMgJ21ldGhvZCA9ICJsZGEiJyBmb3IgTGluZWFyIERpc2NyaW1pbmFudCBBbmFseXNpcw0KDQpjYXQoIlxuLS0tIEZpdHRpbmcgTERBIE1vZGVsIC0tLVxuIikNCm1vZGVsX2xkYSA8LSB0cmFpbigNCiAgZGlhYmV0ZXMgfiAuLA0KICBkYXRhID0gbXlfZGF0YSwNCiAgbWV0aG9kID0gImxkYSIsDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogIG1ldHJpYyA9ICJST0MiDQopDQoNCiMgUHJpbnQgdGhlIG1vZGVsIHJlc3VsdHMNCnByaW50KG1vZGVsX2xkYSkNCmBgYA0KIyMjIFF1YWRyYXRpYyBEaXNjcmltaW5hbnQgKFFEQSkNCmBgYHtyfQ0KIyBGaXQgUURBIG1vZGVsIHVzaW5nIGNhcmV0J3MgdHJhaW4gZnVuY3Rpb24NCiMgJ21ldGhvZCA9ICJxZGEiJyBmb3IgUXVhZHJhdGljIERpc2NyaW1pbmFudCBBbmFseXNpcw0KDQpjYXQoIlxuLS0tIEZpdHRpbmcgUURBIE1vZGVsIC0tLVxuIikNCm1vZGVsX3FkYSA8LSB0cmFpbigNCiAgZGlhYmV0ZXMgfiAuLA0KICBkYXRhID0gbXlfZGF0YSwNCiAgbWV0aG9kID0gInFkYSIsDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogIG1ldHJpYyA9ICJST0MiDQopDQoNCiMgUHJpbnQgdGhlIG1vZGVsIHJlc3VsdHMNCnByaW50KG1vZGVsX3FkYSkNCmBgYA0KDQojIyMgQ29tcGFyaW5nIE1vZGVscw0KYGBge3J9DQojIENvbXBhcmUgdGhlIG1vZGVscyB1c2luZyByZXNhbXBsZXMNCiMgVGhpcyBwcm92aWRlcyBhIHN0YXRpc3RpY2FsIGNvbXBhcmlzb24gb2YgcGVyZm9ybWFuY2UgbWV0cmljcyBhY3Jvc3MgbW9kZWxzLg0KcmVzYW1wbGVfcmVzdWx0cyA8LSByZXNhbXBsZXMobGlzdCgNCiAgTG9naXN0aWMgPSBtb2RlbF9nbG0sDQogIExEQSA9IG1vZGVsX2xkYSwNCiAgUURBID0gbW9kZWxfcWRhDQopKQ0KDQojIFN1bW1hcml6ZSB0aGUgY29tcGFyaXNvbg0Kc3VtbWFyeShyZXNhbXBsZV9yZXN1bHRzKQ0KDQojIFZpc3VhbGl6ZSB0aGUgY29tcGFyaXNvbiAoZS5nLiwgYm94IHBsb3RzIG9mIFJPQywgU2Vuc2l0aXZpdHksIFNwZWNpZmljaXR5KQ0Kc2NhbGVzIDwtIGxpc3QoeCA9IGxpc3QocmVsYXRpb24gPSAiZnJlZSIpLCB5ID0gbGlzdChyZWxhdGlvbiA9ICJmcmVlIikpDQpid3Bsb3QocmVzYW1wbGVfcmVzdWx0cywgc2NhbGVzID0gc2NhbGVzKQ0KDQojIFlvdSBjYW4gYWxzbyB1c2UgYSBkb3RwbG90IGZvciBhIGRpZmZlcmVudCB2aXN1YWxpemF0aW9uDQpkb3RwbG90KHJlc2FtcGxlX3Jlc3VsdHMsIHNjYWxlcyA9IHNjYWxlcykNCmBgYA0KDQogLSBMb29raW5nIGF0IHRoZSBvdXRwdXQgb2YgYHByaW50KG1vZGVsX2dsbSlgLCB3aGF0IGRvIHRoZSAnUk9DJywgJ1NlbnMnLCBhbmQgJ1NwZWMnIHZhbHVlcyByZXByZXNlbnQ/IChBcmVhIFVuZGVyIHRoZSBST0MgQ3VydmUsIFNlbnNpdGl2aXR5L1JlY2FsbCwgU3BlY2lmaWNpdHkpLiAgDQogLSBIb3cgZG8gdGhlIFJPQyB2YWx1ZXMgY29tcGFyZSBhY3Jvc3MgdGhlIHRocmVlIG1vZGVscz8gV2hpY2ggb25lIHBlcmZvcm1zIGJlc3Qgb24gYXZlcmFnZSBhY2NvcmRpbmcgdG8gdGhpcyBtZXRyaWM/ICANCiAtIFdoYXQgZG9lcyB0aGUgYGJ3cGxvdGAgb3IgYGRvdHBsb3RgIHRlbGwgdXMgYWJvdXQgdGhlIHZhcmlhYmlsaXR5IG9mIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlIGFjcm9zcyB0aGUgZm9sZHM/ICANCiAtIEJhc2VkIG9uIHRoZXNlIHJlc3VsdHMsIHdoaWNoIG1vZGVsIHdvdWxkIHlvdSByZWNvbW1lbmQgZm9yIHRoaXMgZGF0YXNldCBhbmQgd2h5PyAoRW5jb3VyYWdlIGRpc2N1c3Npb24gb24gdHJhZGUtb2ZmcywgaW50ZXJwcmV0YWJpbGl0eSwgYW5kIGFzc3VtcHRpb25zKS4gIA0KIC0gSWYgYSBtb2RlbCBoYXMgYSBtdWNoIGhpZ2hlciBST0MgYnV0IGxvd2VyIFNlbnMgY29tcGFyZWQgdG8gYW5vdGhlciwgd2hhdCBtaWdodCB0aGF0IGltcGx5IGFib3V0IGl0cyBwZXJmb3JtYW5jZSBvbiBkaWZmZXJlbnQgdHlwZXMgb2YgZXJyb3JzIChmYWxzZSBwb3NpdGl2ZXMgdnMuIGZhbHNlIG5lZ2F0aXZlcyk/ICANCg0KIyMgUHJlZGljdGlvbnMgYW5kIEludGVycHJldGF0aW9uICANCmBjYXJldGAgaGFuZGxlcyBtdWNoIG9mIHRoZSBjb21wbGV4aXR5LCBpdCdzIGltcG9ydGFudCB0byB1bmRlcnN0YW5kIGhvdyB0byBtYWtlIHByZWRpY3Rpb25zIHdpdGggeW91ciB0cmFpbmVkIG1vZGVscyBhbmQgaW50ZXJwcmV0IHRoZWlyIGNvZWZmaWNpZW50cy4gIA0KDQojIyMgTWFraW5nIFByZWRpY3Rpb25zDQpgYGB7cn0NCiMgTGV0J3MgdXNlIHRoZSBiZXN0IHBlcmZvcm1pbmcgbW9kZWwgKG9yIHBpY2sgb25lIGZvciBkZW1vbnN0cmF0aW9uKQ0KIyBGb3IgZXhhbXBsZSwgbGV0J3MgdXNlIHRoZSBMb2dpc3RpYyBSZWdyZXNzaW9uIG1vZGVsIGZvciBwcmVkaWN0aW9uLg0KIyBXZSdsbCBwcmVkaWN0IG9uIHRoZSBvcmlnaW5hbCB0cmFpbmluZyBkYXRhIGZvciBzaW1wbGljaXR5LA0KIyBidXQgaW4gYSByZWFsIHNjZW5hcmlvLCB5b3UnZCB1c2UgYSBjb21wbGV0ZWx5IHVuc2VlbiB0ZXN0IHNldC4NCg0KIyBQcmVkaWN0IGNsYXNzIGxhYmVscw0KcHJlZGljdGlvbnNfZ2xtIDwtIHByZWRpY3QobW9kZWxfZ2xtLCBuZXdkYXRhID0gbXlfZGF0YSkNCmhlYWQocHJlZGljdGlvbnNfZ2xtKQ0KDQojIFByZWRpY3QgY2xhc3MgcHJvYmFiaWxpdGllcw0KcHJvYmFiaWxpdGllc19nbG0gPC0gcHJlZGljdChtb2RlbF9nbG0sIG5ld2RhdGEgPSBteV9kYXRhLCB0eXBlID0gInByb2IiKQ0KaGVhZChwcm9iYWJpbGl0aWVzX2dsbSkNCg0KIyBDcmVhdGUgYSBjb25mdXNpb24gbWF0cml4IGZvciB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBvbiB0cmFpbmluZyBkYXRhDQojIFRoaXMgaXMgTk9UIGNyb3NzLXZhbGlkYXRlZCBwZXJmb3JtYW5jZSwgYnV0IGdvb2QgZm9yIHVuZGVyc3RhbmRpbmcuDQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfZ2xtLCBteV9kYXRhJGRpYWJldGVzKQ0KYGBgDQoNCiMjIyBJbnRlcnByZXRpbmcgQ29lZmZpY2llbnRzIChMb2dpc3RpYyBSZWdyZXNzaW9uKQ0KYGBge3J9DQojIEZvciBMb2dpc3RpYyBSZWdyZXNzaW9uLCB3ZSBjYW4gaW5zcGVjdCB0aGUgY29lZmZpY2llbnRzDQojIE5vdGU6IEZvciBMREEvUURBLCBpbnRlcnByZXRhdGlvbiBpcyBtb3JlIGFib3V0IGRpc2NyaW1pbmFudCBmdW5jdGlvbnMsDQojIG5vdCBkaXJlY3QgY29lZmZpY2llbnQgaW50ZXJwcmV0YXRpb24gbGlrZSBHTE0uDQoNCiMgQWNjZXNzIHRoZSBmaXR0ZWQgR0xNIG1vZGVsIG9iamVjdCBmcm9tIHRoZSBjYXJldCB0cmFpbiBvYmplY3QNCmdsbV9vYmplY3QgPC0gbW9kZWxfZ2xtJGZpbmFsTW9kZWwNCnN1bW1hcnkoZ2xtX29iamVjdCkNCg0KIyBJbnRlcnByZXQgY29lZmZpY2llbnRzIChlLmcuLCBvZGRzIHJhdGlvcykNCiMgRm9yIGEgY29lZmZpY2llbnQgJ2InLCB0aGUgb2RkcyByYXRpbyBpcyBleHAoYikNCiMgRXhhbXBsZTogRm9yICdnbHVjb3NlJywgaWYgdGhlIGNvZWZmaWNpZW50IGlzICd4JywgdGhlbiBmb3IgZXZlcnkgdW5pdCBpbmNyZWFzZSBpbiBnbHVjb3NlLA0KIyB0aGUgb2RkcyBvZiBoYXZpbmcgZGlhYmV0ZXMgYXJlIG11bHRpcGxpZWQgYnkgZXhwKHgpLCBhc3N1bWluZyBvdGhlciBwcmVkaWN0b3JzIGFyZSBjb25zdGFudC4NCmV4cChjb2VmKGdsbV9vYmplY3QpKQ0KYGBgDQoNCiAtIFdoYXQncyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGB0eXBlID0gInJhdyJgIChkZWZhdWx0IHByZWRpY3Qgb3V0cHV0KSBhbmQgYHR5cGUgPSAicHJvYiJgPyAgIA0KIC0gV2h5IGlzIGludGVycHJldGluZyBjb2VmZmljaWVudHMgbW9yZSBzdHJhaWdodGZvcndhcmQgZm9yIExvZ2lzdGljIFJlZ3Jlc3Npb24gdGhhbiBmb3IgTERBL1FEQT8gIA0KIC0gV2hhdCBkb2VzIGFuIG9kZHMgcmF0aW8gbGVzcyB0aGFuIDEgbWVhbj8gR3JlYXRlciB0aGFuIDE/ICANCiANCiMjIFJlY2FwIGFuZCBBZHZhbmNlZCBUb3BpY3MgIA0KIC0gTG9naXN0aWMgUmVncmVzc2lvbjogTGluZWFyIGRlY2lzaW9uIGJvdW5kYXJ5LCBwcmVkaWN0cyBwcm9iYWJpbGl0aWVzLiAgDQogLSBMREE6IExpbmVhciBkZWNpc2lvbiBib3VuZGFyeSwgYXNzdW1lcyBlcXVhbCBjb3ZhcmlhbmNlIG1hdHJpY2VzLiAgDQogLSBRREE6IFF1YWRyYXRpYyBkZWNpc2lvbiBib3VuZGFyeSwgYXNzdW1lcyB1bmVxdWFsIGNvdmFyaWFuY2UgbWF0cmljZXMuICANCiAtIENyb3NzLXZhbGlkYXRpb246IEVzc2VudGlhbCBmb3IgcmVsaWFibGUgbW9kZWwgcGVyZm9ybWFuY2UgZXN0aW1hdGlvbi4gIA0KDQojIyMgRnVydGhlciBFeHBsb3JhdGlvbiANCiAtIF9fSHlwZXJwYXJhbWV0ZXIgVHVuaW5nOl9fIEZvciBtb3JlIGNvbXBsZXggbW9kZWxzIChsaWtlIFJhbmRvbSBGb3Jlc3RzLCBTVk1zKSwgYGNhcmV0YCBjYW4gYWxzbyB0dW5lIGh5cGVycGFyYW1ldGVycyBhdXRvbWF0aWNhbGx5IGR1cmluZyBjcm9zcy12YWxpZGF0aW9uLiBFeHBsb3JlIHR1bmVHcmlkIGFuZCB0dW5lTGVuZ3RoIGFyZ3VtZW50cyBpbiBgdHJhaW4oKWAuIA0KIC0gX19PdGhlciBNZXRyaWNzOl9fIEludmVzdGlnYXRlIG90aGVyIGV2YWx1YXRpb24gbWV0cmljcyBsaWtlIFByZWNpc2lvbiwgUmVjYWxsLCBGMS1zY29yZSwgZXNwZWNpYWxseSBmb3IgaW1iYWxhbmNlZCBkYXRhc2V0cy4gIA0KIC0gX19JbWJhbGFuY2VkIERhdGE6X18gVGhlIGBQaW1hSW5kaWFuc0RpYWJldGVzMmAgZGF0YXNldCBoYXMgc29tZSBjbGFzcyBpbWJhbGFuY2UuIFJlc2VhcmNoIHRlY2huaXF1ZXMgZm9yIGhhbmRsaW5nIGltYmFsYW5jZWQgZGF0YSAoZS5nLiwgU01PVEUsIHVuZGVyc2FtcGxpbmcvb3ZlcnNhbXBsaW5nKSBhbmQgaG93IHRoZXkgaW50ZWdyYXRlIHdpdGggY2FyZXQuICANCiAtIF9fRmVhdHVyZSBFbmdpbmVlcmluZy9TZWxlY3Rpb246X18gSG93IHdvdWxkIGZlYXR1cmUgZW5naW5lZXJpbmcgb3IgZmVhdHVyZSBzZWxlY3Rpb24gaW1wYWN0IHRoZXNlIG1vZGVscz8gIA0KIC0gX19WaXN1YWxpemF0aW9uOl9fIENyZWF0ZSBST0MgY3VydmVzIGZvciBlYWNoIG1vZGVsIHRvIHZpc3VhbGl6ZSB0aGVpciB0cmFkZS1vZmZzIGJldHdlZW4gc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5LiAgDQogDQojIEFwcGVuZGl4IA0KDQojIyBST0MgQ3VydmUgUGxvdCAocmVxdWlyZXMgYHBST0NgIHBhY2thZ2Ugb3IgbWFudWFsIGNhbGN1bGF0aW9uKQ0KDQpgYGB7cn0NCiMgaW5zdGFsbC5wYWNrYWdlcygicFJPQyIpDQpsaWJyYXJ5KHBST0MpDQoNCiMgR2V0IHByZWRpY3Rpb25zIGZyb20gb25lIG9mIHRoZSBtb2RlbHMgKGUuZy4sIExvZ2lzdGljIFJlZ3Jlc3Npb24pDQojIE5vdGU6IFRoaXMgaXMgb24gdGhlIHRyYWluaW5nIGRhdGEsIGZvciBpbGx1c3RyYXRpb24uIEZvciB0cnVlIFJPQywgdXNlIGEgaGVsZC1vdXQgdGVzdCBzZXQuDQpwcm9iX2dsbSA8LSBwcmVkaWN0KG1vZGVsX2dsbSwgbmV3ZGF0YSA9IG15X2RhdGEsIHR5cGUgPSAicHJvYiIpDQoNCiMgQ3JlYXRlIFJPQyBvYmplY3QNCnJvY19nbG0gPC0gcm9jKHJlc3BvbnNlID0gbXlfZGF0YSRkaWFiZXRlcywgcHJlZGljdG9yID0gcHJvYl9nbG0kWWVzLCBsZXZlbHMgPSBjKCJObyIsICJZZXMiKSkNCg0KIyBQbG90IFJPQyBjdXJ2ZQ0KcGxvdChyb2NfZ2xtLCBtYWluID0gIlJPQyBDdXJ2ZSBmb3IgTG9naXN0aWMgUmVncmVzc2lvbiIsIGNvbCA9ICJibHVlIiwgbHdkID0gMikNCmF1Yyhyb2NfZ2xtKSAjIEFyZWEgVW5kZXIgQ3VydmUNCg0KIyBZb3UgY291bGQgbG9vcCB0aGlzIGZvciBhbGwgbW9kZWxzIGFuZCBwbG90IG9uIHRoZSBzYW1lIGdyYXBoIGZvciBjb21wYXJpc29uDQpgYGANCg0KDQojIyBXaXRob3V0IGBjYXJldGAgIA0KSXQncyBjZXJ0YWlubHkgcG9zc2libGUgdG8gcGVyZm9ybSBjcm9zcy12YWxpZGF0aW9uIGFuZCBmaXQgdGhlc2UgbW9kZWxzIHdpdGhvdXQgYGNhcmV0YC4gV2hpbGUgYGNhcmV0YCBzaW1wbGlmaWVzIHRoZSBwcm9jZXNzIHNpZ25pZmljYW50bHksIGRvaW5nIGl0IG1hbnVhbGx5IHByb3ZpZGVzIGEgZGVlcGVyIHVuZGVyc3RhbmRpbmcgb2YgdGhlIHVuZGVybHlpbmcgbWVjaGFuaWNzLg0KDQpIZXJlJ3MgdGhlIHNhbWUgZGVtbyB3aXRob3V0IHVzaW5nIHRoZSBgY2FyZXRgIHBhY2thZ2UuIFRoaXMgdmVyc2lvbiBmb2N1c2VzIG1vcmUgb24gbWFudWFsIGxvb3BzIGZvciBjcm9zcy12YWxpZGF0aW9uIGFuZCBkaXJlY3QgY2FsbHMgdG8gYGdsbWAsIGBsZGFgLCBhbmQgYHFkYWAuDQoNCiMjIE1hbnVhbCBLLUZvbGQgQ3Jvc3MtVmFsaWRhdGlvbiBTZXR1cA0KYGBge3J9DQojIFNldCBhIHJhbmRvbSBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkNCnNldC5zZWVkKDEyMykNCg0KIyBEZWZpbmUgbnVtYmVyIG9mIGZvbGRzIChrKQ0Ka19mb2xkcyA8LSAxMA0KbnVtX3JlcGVhdHMgPC0gMyAjIFdlJ2xsIHJlcGVhdCB0aGUgZW50aXJlIENWIHByb2Nlc3MgbXVsdGlwbGUgdGltZXMgZm9yIG1vcmUgc3RhYmxlIHJlc3VsdHMNCg0KIyBDcmVhdGUgZW1wdHkgbGlzdHMgdG8gc3RvcmUgcmVzdWx0cyBmb3IgZWFjaCBtb2RlbCBhbmQgcmVwZWF0DQpyZXN1bHRzX2dsbSA8LSBsaXN0KCkNCnJlc3VsdHNfbGRhIDwtIGxpc3QoKQ0KcmVzdWx0c19xZGEgPC0gbGlzdCgpDQoNCiMgU3RvcmUgcGVyZm9ybWFuY2UgbWV0cmljcyAoUk9DLCBTZW5zaXRpdml0eSwgU3BlY2lmaWNpdHkpIGZvciBlYWNoIGZvbGQvcmVwZWF0DQptZXRyaWNzX2dsbSA8LSBkYXRhLmZyYW1lKFJPQyA9IG51bWVyaWMoKSwgU2Vuc2l0aXZpdHkgPSBudW1lcmljKCksIFNwZWNpZmljaXR5ID0gbnVtZXJpYygpKQ0KbWV0cmljc19sZGEgPC0gZGF0YS5mcmFtZShST0MgPSBudW1lcmljKCksIFNlbnNpdGl2aXR5ID0gbnVtZXJpYygpLCBTcGVjaWZpY2l0eSA9IG51bWVyaWMoKSkNCm1ldHJpY3NfcWRhIDwtIGRhdGEuZnJhbWUoUk9DID0gbnVtZXJpYygpLCBTZW5zaXRpdml0eSA9IG51bWVyaWMoKSwgU3BlY2lmaWNpdHkgPSBudW1lcmljKCkpDQoNCiMgR2V0IHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zDQpuX29icyA8LSBucm93KG15X2RhdGEpDQoNCiMgT3V0ZXIgbG9vcCBmb3IgcmVwZWF0cw0KZm9yIChyZXAgaW4gMTpudW1fcmVwZWF0cykgew0KICBjYXQocGFzdGUwKCJcbi0tLSBDcm9zcy1WYWxpZGF0aW9uIFJlcGVhdCAiLCByZXAsICIgLS0tXG4iKSkNCg0KICAjIFJhbmRvbWx5IHBlcm11dGUgdGhlIGRhdGEgdG8gZW5zdXJlIGZvbGRzIGFyZSBkaXZlcnNlDQogIHNodWZmbGVkX2RhdGEgPC0gbXlfZGF0YVtzYW1wbGUobl9vYnMpLCBdDQoNCiAgIyBDcmVhdGUgZm9sZHMNCiAgIyBUaGlzIGNyZWF0ZXMgYSB2ZWN0b3IgaW5kaWNhdGluZyB3aGljaCBmb2xkIGVhY2ggcm93IGJlbG9uZ3MgdG8NCiAgZm9sZHMgPC0gY3V0KHNlcSgxLCBuX29icyksIGJyZWFrcyA9IGtfZm9sZHMsIGxhYmVscyA9IEZBTFNFKQ0KDQogICMgSW5uZXIgbG9vcCBmb3IgZm9sZHMNCiAgZm9yIChpIGluIDE6a19mb2xkcykgew0KICAgIGNhdChwYXN0ZTAoIiAgRm9sZCAiLCBpLCAiLi4uXG4iKSkNCg0KICAgICMgRGVmaW5lIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHMgZm9yIHRoaXMgZm9sZA0KICAgIHRlc3RfaW5kaWNlcyA8LSB3aGljaChmb2xkcyA9PSBpKQ0KICAgIHRyYWluX2RhdGEgPC0gc2h1ZmZsZWRfZGF0YVstdGVzdF9pbmRpY2VzLCBdDQogICAgdGVzdF9kYXRhIDwtIHNodWZmbGVkX2RhdGFbdGVzdF9pbmRpY2VzLCBdDQoNCiAgICAjIC0tLSBGaXQgTG9naXN0aWMgUmVncmVzc2lvbiAtLS0NCiAgICB0cnlDYXRjaCh7ICMgVXNlIHRyeUNhdGNoIHRvIGhhbmRsZSBwb3RlbnRpYWwgZXJyb3JzIChlLmcuLCBwZXJmZWN0IHNlcGFyYXRpb24pDQogICAgICBtb2RlbF9nbG1fZm9sZCA8LSBnbG0oZGlhYmV0ZXMgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSwgZmFtaWx5ID0gImJpbm9taWFsIikNCiAgICAgIHByZWRfcHJvYl9nbG0gPC0gcHJlZGljdChtb2RlbF9nbG1fZm9sZCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSwgdHlwZSA9ICJyZXNwb25zZSIpDQogICAgICBwcmVkX2NsYXNzX2dsbSA8LSBmYWN0b3IoaWZlbHNlKHByZWRfcHJvYl9nbG0gPiAwLjUsICJZZXMiLCAiTm8iKSwgbGV2ZWxzID0gYygiTm8iLCAiWWVzIikpDQoNCiAgICAgICMgRW5zdXJlIHRlc3RfZGF0YSRkaWFiZXRlcyBoYXMgY29uc2lzdGVudCBsZXZlbHMNCiAgICAgIHRlc3RfZGF0YSRkaWFiZXRlcyA8LSBmYWN0b3IodGVzdF9kYXRhJGRpYWJldGVzLCBsZXZlbHMgPSBjKCJObyIsICJZZXMiKSkNCg0KICAgICAgIyBDYWxjdWxhdGUgY29uZnVzaW9uIG1hdHJpeCBmb3IgR0xNDQogICAgICBjbV9nbG0gPC0gdGFibGUoUHJlZGljdGVkID0gcHJlZF9jbGFzc19nbG0sIEFjdHVhbCA9IHRlc3RfZGF0YSRkaWFiZXRlcykNCiAgICAgICMgQ2FsY3VsYXRlIG1ldHJpY3MgbWFudWFsbHkgb3IgdXNpbmcgcFJPQyBmb3IgQVVDDQogICAgICAjIE5lZWQgdG8gaGFuZGxlIGNhc2VzIHdoZXJlIGNsYXNzZXMgbWlnaHQgYmUgbWlzc2luZyBpbiBhIGZvbGQNCiAgICAgIGlmIChuY29sKGNtX2dsbSkgPT0gMiAmJiBucm93KGNtX2dsbSkgPT0gMikgew0KICAgICAgICAgIFRQX2dsbSA8LSBjbV9nbG1bMiwyXQ0KICAgICAgICAgIFROX2dsbSA8LSBjbV9nbG1bMSwxXQ0KICAgICAgICAgIEZQX2dsbSA8LSBjbV9nbG1bMiwxXQ0KICAgICAgICAgIEZOX2dsbSA8LSBjbV9nbG1bMSwyXQ0KDQogICAgICAgICAgc2Vuc2l0aXZpdHlfZ2xtIDwtIFRQX2dsbSAvIChUUF9nbG0gKyBGTl9nbG0pDQogICAgICAgICAgc3BlY2lmaWNpdHlfZ2xtIDwtIFROX2dsbSAvIChUTl9nbG0gKyBGUF9nbG0pDQoNCiAgICAgICAgICAjIENhbGN1bGF0ZSBST0MgQVVDIChyZXF1aXJlcyBhdCBsZWFzdCB0d28gZGlzdGluY3QgcHJlZGljdGlvbnMgYW5kIHR3byBjbGFzc2VzIGluIHRlc3Qgc2V0KQ0KICAgICAgICAgIGlmIChsZW5ndGgodW5pcXVlKHRlc3RfZGF0YSRkaWFiZXRlcykpID4gMSAmJiBsZW5ndGgodW5pcXVlKHByZWRfcHJvYl9nbG0pKSA+IDEpIHsNCiAgICAgICAgICAgICAgcm9jX29ial9nbG0gPC0gcm9jKHJlc3BvbnNlID0gdGVzdF9kYXRhJGRpYWJldGVzLCBwcmVkaWN0b3IgPSBwcmVkX3Byb2JfZ2xtLCBsZXZlbHMgPSBjKCJObyIsICJZZXMiKSwgcXVpZXQgPSBUUlVFKQ0KICAgICAgICAgICAgICBhdWNfZ2xtIDwtIGF1Yyhyb2Nfb2JqX2dsbSkNCiAgICAgICAgICB9IGVsc2Ugew0KICAgICAgICAgICAgICBhdWNfZ2xtIDwtIE5BICMgQ2Fubm90IGNvbXB1dGUgQVVDIGlmIG9ubHkgb25lIGNsYXNzIG9yIGNvbnN0YW50IHByZWRpY3Rpb25zDQogICAgICAgICAgfQ0KICAgICAgfSBlbHNlIHsNCiAgICAgICAgICBzZW5zaXRpdml0eV9nbG0gPC0gTkENCiAgICAgICAgICBzcGVjaWZpY2l0eV9nbG0gPC0gTkENCiAgICAgICAgICBhdWNfZ2xtIDwtIE5BDQogICAgICB9DQoNCiAgICAgIG1ldHJpY3NfZ2xtIDwtIHJiaW5kKG1ldHJpY3NfZ2xtLCBkYXRhLmZyYW1lKFJPQyA9IGF1Y19nbG0sIFNlbnNpdGl2aXR5ID0gc2Vuc2l0aXZpdHlfZ2xtLCBTcGVjaWZpY2l0eSA9IHNwZWNpZmljaXR5X2dsbSkpDQoNCiAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICAgIGNhdCgiICBHTE0gRXJyb3IgaW4gZm9sZCAiLCBpLCAiOiAiLCBlJG1lc3NhZ2UsICJcbiIpDQogICAgICBtZXRyaWNzX2dsbSA8LSByYmluZChtZXRyaWNzX2dsbSwgZGF0YS5mcmFtZShST0MgPSBOQSwgU2Vuc2l0aXZpdHkgPSBOQSwgU3BlY2lmaWNpdHkgPSBOQSkpDQogICAgfSkNCg0KDQogICAgIyAtLS0gRml0IExEQSAtLS0NCiAgICB0cnlDYXRjaCh7DQogICAgICAjIEVuc3VyZSB0aGVyZSBhcmUgYXQgbGVhc3QgdHdvIGRpc3RpbmN0IGNsYXNzZXMgaW4gdGhlIHRyYWluaW5nIGRhdGEgZm9yIExEQQ0KICAgICAgaWYgKGxlbmd0aCh1bmlxdWUodHJhaW5fZGF0YSRkaWFiZXRlcykpID4gMSkgew0KICAgICAgICBtb2RlbF9sZGFfZm9sZCA8LSBsZGEoZGlhYmV0ZXMgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSkNCiAgICAgICAgcHJlZF9sZGEgPC0gcHJlZGljdChtb2RlbF9sZGFfZm9sZCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSkNCiAgICAgICAgcHJlZF9wcm9iX2xkYV95ZXMgPC0gcHJlZF9sZGEkcG9zdGVyaW9yWywgIlllcyJdICMgUHJvYmFiaWxpdHkgb2YgJ1llcycgY2xhc3MNCiAgICAgICAgcHJlZF9jbGFzc19sZGEgPC0gcHJlZF9sZGEkY2xhc3MNCg0KICAgICAgICBjbV9sZGEgPC0gdGFibGUoUHJlZGljdGVkID0gcHJlZF9jbGFzc19sZGEsIEFjdHVhbCA9IHRlc3RfZGF0YSRkaWFiZXRlcykNCiAgICAgICAgaWYgKG5jb2woY21fbGRhKSA9PSAyICYmIG5yb3coY21fbGRhKSA9PSAyKSB7DQogICAgICAgICAgICBUUF9sZGEgPC0gY21fbGRhWzIsMl0NCiAgICAgICAgICAgIFROX2xkYSA8LSBjbV9sZGFbMSwxXQ0KICAgICAgICAgICAgRlBfbGRhIDwtIGNtX2xkYVsyLDFdDQogICAgICAgICAgICBGTl9sZGEgPC0gY21fbGRhWzEsMl0NCg0KICAgICAgICAgICAgc2Vuc2l0aXZpdHlfbGRhIDwtIFRQX2xkYSAvIChUUF9sZGEgKyBGTl9sZGEpDQogICAgICAgICAgICBzcGVjaWZpY2l0eV9sZGEgPC0gVE5fbGRhIC8gKFROX2xkYSArIEZQX2xkYSkNCg0KICAgICAgICAgICAgaWYgKGxlbmd0aCh1bmlxdWUodGVzdF9kYXRhJGRpYWJldGVzKSkgPiAxICYmIGxlbmd0aCh1bmlxdWUocHJlZF9wcm9iX2xkYV95ZXMpKSA+IDEpIHsNCiAgICAgICAgICAgICAgICByb2Nfb2JqX2xkYSA8LSByb2MocmVzcG9uc2UgPSB0ZXN0X2RhdGEkZGlhYmV0ZXMsIHByZWRpY3RvciA9IHByZWRfcHJvYl9sZGFfeWVzLCBsZXZlbHMgPSBjKCJObyIsICJZZXMiKSwgcXVpZXQgPSBUUlVFKQ0KICAgICAgICAgICAgICAgIGF1Y19sZGEgPC0gYXVjKHJvY19vYmpfbGRhKQ0KICAgICAgICAgICAgfSBlbHNlIHsNCiAgICAgICAgICAgICAgICBhdWNfbGRhIDwtIE5BDQogICAgICAgICAgICB9DQogICAgICAgIH0gZWxzZSB7DQogICAgICAgICAgICBzZW5zaXRpdml0eV9sZGEgPC0gTkENCiAgICAgICAgICAgIHNwZWNpZmljaXR5X2xkYSA8LSBOQQ0KICAgICAgICAgICAgYXVjX2xkYSA8LSBOQQ0KICAgICAgICB9DQogICAgICB9IGVsc2Ugew0KICAgICAgICAgIHNlbnNpdGl2aXR5X2xkYSA8LSBOQQ0KICAgICAgICAgIHNwZWNpZmljaXR5X2xkYSA8LSBOQQ0KICAgICAgICAgIGF1Y19sZGEgPC0gTkENCiAgICAgICAgICBjYXQoIiAgTERBIHNraXBwZWQgaW4gZm9sZCAiLCBpLCAiOiBPbmx5IG9uZSBjbGFzcyBwcmVzZW50IGluIHRyYWluaW5nIGRhdGEuXG4iKQ0KICAgICAgfQ0KICAgICAgbWV0cmljc19sZGEgPC0gcmJpbmQobWV0cmljc19sZGEsIGRhdGEuZnJhbWUoUk9DID0gYXVjX2xkYSwgU2Vuc2l0aXZpdHkgPSBzZW5zaXRpdml0eV9sZGEsIFNwZWNpZmljaXR5ID0gc3BlY2lmaWNpdHlfbGRhKSkNCg0KICAgIH0sIGVycm9yID0gZnVuY3Rpb24oZSkgew0KICAgICAgY2F0KCIgIExEQSBFcnJvciBpbiBmb2xkICIsIGksICI6ICIsIGUkbWVzc2FnZSwgIlxuIikNCiAgICAgIG1ldHJpY3NfbGRhIDwtIHJiaW5kKG1ldHJpY3NfbGRhLCBkYXRhLmZyYW1lKFJPQyA9IE5BLCBTZW5zaXRpdml0eSA9IE5BLCBTcGVjaWZpY2l0eSA9IE5BKSkNCiAgICB9KQ0KDQoNCiAgICAjIC0tLSBGaXQgUURBIC0tLQ0KICAgIHRyeUNhdGNoKHsNCiAgICAgICMgUURBIG9mdGVuIHJlcXVpcmVzIG1vcmUgZGF0YSBwZXIgY2xhc3MsIGNhbiBmYWlsIGlmIG5vdCBlbm91Z2ggb2JzZXJ2YXRpb25zDQogICAgICBpZiAobGVuZ3RoKHVuaXF1ZSh0cmFpbl9kYXRhJGRpYWJldGVzKSkgPiAxICYmDQogICAgICAgICAgc3VtKHRyYWluX2RhdGEkZGlhYmV0ZXMgPT0gIk5vIikgPiBuY29sKHRyYWluX2RhdGEpICYmDQogICAgICAgICAgc3VtKHRyYWluX2RhdGEkZGlhYmV0ZXMgPT0gIlllcyIpID4gbmNvbCh0cmFpbl9kYXRhKSkgeyAjIFJ1bGUgb2YgdGh1bWIgZm9yIFFEQQ0KICAgICAgICBtb2RlbF9xZGFfZm9sZCA8LSBxZGEoZGlhYmV0ZXMgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSkNCiAgICAgICAgcHJlZF9xZGEgPC0gcHJlZGljdChtb2RlbF9xZGFfZm9sZCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSkNCiAgICAgICAgcHJlZF9wcm9iX3FkYV95ZXMgPC0gcHJlZF9xZGEkcG9zdGVyaW9yWywgIlllcyJdDQogICAgICAgIHByZWRfY2xhc3NfcWRhIDwtIHByZWRfcWRhJGNsYXNzDQoNCiAgICAgICAgY21fcWRhIDwtIHRhYmxlKFByZWRpY3RlZCA9IHByZWRfY2xhc3NfcWRhLCBBY3R1YWwgPSB0ZXN0X2RhdGEkZGlhYmV0ZXMpDQogICAgICAgIGlmIChuY29sKGNtX3FkYSkgPT0gMiAmJiBucm93KGNtX3FkYSkgPT0gMikgew0KICAgICAgICAgICAgVFBfcWRhIDwtIGNtX3FkYVsyLDJdDQogICAgICAgICAgICBUTl9xZGEgPC0gY21fcWRhWzEsMV0NCiAgICAgICAgICAgIEZQX3FkYSA8LSBjbV9xZGFbMiwxXQ0KICAgICAgICAgICAgRk5fcWRhIDwtIGNtX3FkYVsxLDJdDQoNCiAgICAgICAgICAgIHNlbnNpdGl2aXR5X3FkYSA8LSBUUF9xZGEgLyAoVFBfcWRhICsgRk5fcWRhKQ0KICAgICAgICAgICAgc3BlY2lmaWNpdHlfcWRhIDwtIFROX3FkYSAvIChUTl9xZGEgKyBGUF9xZGEpDQoNCiAgICAgICAgICAgIGlmIChsZW5ndGgodW5pcXVlKHRlc3RfZGF0YSRkaWFiZXRlcykpID4gMSAmJiBsZW5ndGgodW5pcXVlKHByZWRfcHJvYl9xZGFfeWVzKSkgPiAxKSB7DQogICAgICAgICAgICAgICAgcm9jX29ial9xZGEgPC0gcm9jKHJlc3BvbnNlID0gdGVzdF9kYXRhJGRpYWJldGVzLCBwcmVkaWN0b3IgPSBwcmVkX3Byb2JfcWRhX3llcywgbGV2ZWxzID0gYygiTm8iLCAiWWVzIiksIHF1aWV0ID0gVFJVRSkNCiAgICAgICAgICAgICAgICBhdWNfcWRhIDwtIGF1Yyhyb2Nfb2JqX3FkYSkNCiAgICAgICAgICAgIH0gZWxzZSB7DQogICAgICAgICAgICAgICAgYXVjX3FkYSA8LSBOQQ0KICAgICAgICAgICAgfQ0KICAgICAgICB9IGVsc2Ugew0KICAgICAgICAgICAgc2Vuc2l0aXZpdHlfcWRhIDwtIE5BDQogICAgICAgICAgICBzcGVjaWZpY2l0eV9xZGEgPC0gTkENCiAgICAgICAgICAgIGF1Y19xZGEgPC0gTkENCiAgICAgICAgfQ0KICAgICAgfSBlbHNlIHsNCiAgICAgICAgICBzZW5zaXRpdml0eV9xZGEgPC0gTkENCiAgICAgICAgICBzcGVjaWZpY2l0eV9xZGEgPC0gTkENCiAgICAgICAgICBhdWNfcWRhIDwtIE5BDQogICAgICAgICAgY2F0KCIgIFFEQSBza2lwcGVkIGluIGZvbGQgIiwgaSwgIjogTm90IGVub3VnaCBvYnNlcnZhdGlvbnMgcGVyIGNsYXNzIGluIHRyYWluaW5nIGRhdGEuXG4iKQ0KICAgICAgfQ0KICAgICAgbWV0cmljc19xZGEgPC0gcmJpbmQobWV0cmljc19xZGEsIGRhdGEuZnJhbWUoUk9DID0gYXVjX3FkYSwgU2Vuc2l0aXZpdHkgPSBzZW5zaXRpdml0eV9xZGEsIFNwZWNpZmljaXR5ID0gc3BlY2lmaWNpdHlfcWRhKSkNCg0KICAgIH0sIGVycm9yID0gZnVuY3Rpb24oZSkgew0KICAgICAgY2F0KCIgIFFEQSBFcnJvciBpbiBmb2xkICIsIGksICI6ICIsIGUkbWVzc2FnZSwgIlxuIikNCiAgICAgIG1ldHJpY3NfcWRhIDwtIHJiaW5kKG1ldHJpY3NfcWRhLCBkYXRhLmZyYW1lKFJPQyA9IE5BLCBTZW5zaXRpdml0eSA9IE5BLCBTcGVjaWZpY2l0eSA9IE5BKSkNCiAgICB9KQ0KICB9ICMgRW5kIG9mIGlubmVyIGZvbGQgbG9vcA0KfSAjIEVuZCBvZiBvdXRlciByZXBlYXQgbG9vcA0KYGBgDQoNCiMjIFN1bW1hcml6aW5nIGFuZCBDb21wYXJpbmcgUmVzdWx0cyAgDQpBZnRlciBydW5uaW5nIGFsbCB0aGUgZm9sZHMgYW5kIHJlcGVhdHMsIHdlJ2xsIGhhdmUgYSBjb2xsZWN0aW9uIG9mIHBlcmZvcm1hbmNlIG1ldHJpY3MuIExldCdzIHN1bW1hcml6ZSB0aGVtIGFuZCBjb21wYXJlIG91ciBtb2RlbHMuDQoNCmBgYHtyfQ0KIyBTdW1tYXJpemUgdGhlIGNyb3NzLXZhbGlkYXRpb24gcmVzdWx0cw0KY2F0KCJcbi0tLSBTdW1tYXJ5IG9mIExvZ2lzdGljIFJlZ3Jlc3Npb24gUGVyZm9ybWFuY2UgLS0tXG4iKQ0Kc3VtbWFyeShtZXRyaWNzX2dsbSkNCmNhdCgiQXZlcmFnZSBST0M6ICIsIG1lYW4obWV0cmljc19nbG0kUk9DLCBuYS5ybSA9IFRSVUUpLCAiXG4iKQ0KY2F0KCJBdmVyYWdlIFNlbnNpdGl2aXR5OiAiLCBtZWFuKG1ldHJpY3NfZ2xtJFNlbnNpdGl2aXR5LCBuYS5ybSA9IFRSVUUpLCAiXG4iKQ0KY2F0KCJBdmVyYWdlIFNwZWNpZmljaXR5OiAiLCBtZWFuKG1ldHJpY3NfZ2xtJFNwZWNpZmljaXR5LCBuYS5ybSA9IFRSVUUpLCAiXG4iKQ0KDQpjYXQoIlxuLS0tIFN1bW1hcnkgb2YgTERBIFBlcmZvcm1hbmNlIC0tLVxuIikNCnN1bW1hcnkobWV0cmljc19sZGEpDQpjYXQoIkF2ZXJhZ2UgUk9DOiAiLCBtZWFuKG1ldHJpY3NfbGRhJFJPQywgbmEucm0gPSBUUlVFKSwgIlxuIikNCmNhdCgiQXZlcmFnZSBTZW5zaXRpdml0eTogIiwgbWVhbihtZXRyaWNzX2xkYSRTZW5zaXRpdml0eSwgbmEucm0gPSBUUlVFKSwgIlxuIikNCmNhdCgiQXZlcmFnZSBTcGVjaWZpY2l0eTogIiwgbWVhbihtZXRyaWNzX2xkYSRTcGVjaWZpY2l0eSwgbmEucm0gPSBUUlVFKSwgIlxuIikNCg0KY2F0KCJcbi0tLSBTdW1tYXJ5IG9mIFFEQSBQZXJmb3JtYW5jZSAtLS1cbiIpDQpzdW1tYXJ5KG1ldHJpY3NfcWRhKQ0KY2F0KCJBdmVyYWdlIFJPQzogIiwgbWVhbihtZXRyaWNzX3FkYSRST0MsIG5hLnJtID0gVFJVRSksICJcbiIpDQpjYXQoIkF2ZXJhZ2UgU2Vuc2l0aXZpdHk6ICIsIG1lYW4obWV0cmljc19xZGEkU2Vuc2l0aXZpdHksIG5hLnJtID0gVFJVRSksICJcbiIpDQpjYXQoIkF2ZXJhZ2UgU3BlY2lmaWNpdHk6ICIsIG1lYW4obWV0cmljc19xZGEkU3BlY2lmaWNpdHksIG5hLnJtID0gVFJVRSksICJcbiIpDQoNCiMgQ29tYmluZSByZXN1bHRzIGZvciBlYXNpZXIgY29tcGFyaXNvbiBhbmQgdmlzdWFsaXphdGlvbg0KYWxsX21ldHJpY3MgPC0gcmJpbmQoDQogIGRhdGEuZnJhbWUoTW9kZWwgPSAiTG9naXN0aWMiLCBtZXRyaWNzX2dsbSksDQogIGRhdGEuZnJhbWUoTW9kZWwgPSAiTERBIiwgbWV0cmljc19sZGEpLA0KICBkYXRhLmZyYW1lKE1vZGVsID0gIlFEQSIsIG1ldHJpY3NfcWRhKQ0KKQ0KDQojIFJlbW92ZSByb3dzIHdpdGggTkEgdmFsdWVzIChmcm9tIGZhaWxlZCBmb2xkcykgZm9yIHBsb3R0aW5nDQphbGxfbWV0cmljc19jbGVhbiA8LSBuYS5vbWl0KGFsbF9tZXRyaWNzKQ0KDQojIC0tLSBWaXN1YWxpemF0aW9uIChyZXF1aXJlcyBnZ3Bsb3QyLCBub3QgbG9hZGVkIGJ5IGRlZmF1bHQgaGVyZSwgYnV0IGdvb2QgZm9yIGRpc2N1c3Npb24pIC0tLQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmxpYnJhcnkoZ2dwbG90MikNCg0KIyBCb3hwbG90IG9mIFJPQyB2YWx1ZXMNCmdncGxvdChhbGxfbWV0cmljc19jbGVhbiwgYWVzKHggPSBNb2RlbCwgeSA9IFJPQywgZmlsbCA9IE1vZGVsKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGxhYnModGl0bGUgPSAiTW9kZWwgUGVyZm9ybWFuY2UgQ29tcGFyaXNvbiAoUk9DKSIsDQogICAgICAgeSA9ICJST0MgQVVDIiwgeCA9ICJNb2RlbCIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgQm94cGxvdCBvZiBTZW5zaXRpdml0eSB2YWx1ZXMNCmdncGxvdChhbGxfbWV0cmljc19jbGVhbiwgYWVzKHggPSBNb2RlbCwgeSA9IFNlbnNpdGl2aXR5LCBmaWxsID0gTW9kZWwpKSArDQogIGdlb21fYm94cGxvdCgpICsNCiAgbGFicyh0aXRsZSA9ICJNb2RlbCBQZXJmb3JtYW5jZSBDb21wYXJpc29uIChTZW5zaXRpdml0eSkiLA0KICAgICAgIHkgPSAiU2Vuc2l0aXZpdHkiLCB4ID0gIk1vZGVsIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBCb3hwbG90IG9mIFNwZWNpZmljaXR5IHZhbHVlcw0KZ2dwbG90KGFsbF9tZXRyaWNzX2NsZWFuLCBhZXMoeCA9IE1vZGVsLCB5ID0gU3BlY2lmaWNpdHksIGZpbGwgPSBNb2RlbCkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBsYWJzKHRpdGxlID0gIk1vZGVsIFBlcmZvcm1hbmNlIENvbXBhcmlzb24gKFNwZWNpZmljaXR5KSIsDQogICAgICAgeSA9ICJTcGVjaWZpY2l0eSIsIHggPSAiTW9kZWwiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQpIZXJlJ3MgYSBzdW1tYXJ5IG9mIHRoZSBhdmVyYWdlIHBlcmZvcm1hbmNlIG1ldHJpY3MgZm9yIGVhY2ggbW9kZWw6DQoNCnwgKipNb2RlbCoqICAgICAgICAgICAgICAgfCAqKkF2ZXJhZ2UgUk9DKiogfCAqKkF2ZXJhZ2UgU2Vuc2l0aXZpdHkqKiB8ICoqQXZlcmFnZSBTcGVjaWZpY2l0eSoqIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfDotLS0tLS0tLS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS06fDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tOnwNCnwgKipMb2dpc3RpYyBSZWdyZXNzaW9uKiogfCAqKl8wLjgzNzlfKiogICAgfCAwLjU1MzQgICAgICAgICAgICAgICAgICB8ICoqXzAuODg3N18qKiAgICAgICAgICAgIHwNCnwgKipMREEqKiAgICAgICAgICAgICAgICAgfCAwLjgzNzQgICAgICAgICAgfCAwLjU1MjEgICAgICAgICAgICAgICAgICB8IDAuODgzMSAgICAgICAgICAgICAgICAgIHwNCnwgKipRREEqKiAgICAgICAgICAgICAgICAgfCAwLjgyMzUgICAgICAgICAgfCAqKl8wLjU3MjlfKiogICAgICAgICAgICB8IDAuODU0MyAgICAgICAgICAgICAgICAgIHwNCg0KX19PdmVyYWxsIFBlcmZvcm1hbmNlIChST0MgQVVDKTpfXyAgDQoNCiAtIExvZ2lzdGljIFJlZ3Jlc3Npb24gKDAuODM3OSkgYW5kIExEQSAoMC44Mzc0KSBoYXZlIGFsbW9zdCBpZGVudGljYWwgYXZlcmFnZSBST0MgQVVDcy4gVGhlIGRpZmZlcmVuY2UgaXMgbmVnbGlnaWJsZS4NCiAtIFFEQSAoMC44MjM1KSBoYXMgYSBzbGlnaHRseSBsb3dlciBhdmVyYWdlIFJPQyBBVUMgY29tcGFyZWQgdG8gTG9naXN0aWMgUmVncmVzc2lvbiBhbmQgTERBLiAgDQogLSBJbnRlcnByZXRhdGlvbjogQmFzZWQgcHVyZWx5IG9uIG92ZXJhbGwgZGlzY3JpbWluYXRpdmUgcG93ZXIgYWNyb3NzIGFsbCBwb3NzaWJsZSB0aHJlc2hvbGRzLCBMb2dpc3RpYyBSZWdyZXNzaW9uIGFuZCBMREEgYXJlIHBlcmZvcm1pbmcgZXF1YWxseSB3ZWxsIGFuZCBzbGlnaHRseSBiZXR0ZXIgdGhhbiBRREEuIEFuIEFVQyBhYm92ZSAwLjggaXMgZ2VuZXJhbGx5IGNvbnNpZGVyZWQgZ29vZCwgaW5kaWNhdGluZyB0aGF0IHRoZSBtb2RlbHMgaGF2ZSBzdHJvbmcgZGlzY3JpbWluYXRvcnkgcG93ZXIuICANCg0KX19TZW5zaXRpdml0eSAoVHJ1ZSBQb3NpdGl2ZSBSYXRlKTpfXyAgDQoNCiAtIFFEQSAoMC41NzI5KSBoYXMgdGhlIGhpZ2hlc3QgYXZlcmFnZSBTZW5zaXRpdml0eS4gIA0KIC0gTG9naXN0aWMgUmVncmVzc2lvbiAoMC41NTM0KSBhbmQgTERBICgwLjU1MjEpIGhhdmUgdmVyeSBzaW1pbGFyIGFuZCBzbGlnaHRseSBsb3dlciBhdmVyYWdlIFNlbnNpdGl2aXRpZXMgY29tcGFyZWQgdG8gUURBLiAgDQogLSBJbnRlcnByZXRhdGlvbjogUURBIGlzIHNsaWdodGx5IGJldHRlciBhdCBjb3JyZWN0bHkgaWRlbnRpZnlpbmcgYWN0dWFsIHBvc2l0aXZlIGNhc2VzIChlLmcuLCBwZW9wbGUgd2l0aCBkaWFiZXRlcykuIFRoaXMgbWVhbnMgaXQgaGFzIGZld2VyIGZhbHNlIG5lZ2F0aXZlcy4gIA0KIA0KX19TcGVjaWZpY2l0eSAoVHJ1ZSBOZWdhdGl2ZSBSYXRlKTpfXyAgDQoNCiAtIExvZ2lzdGljIFJlZ3Jlc3Npb24gKDAuODg3NykgaGFzIHRoZSBoaWdoZXN0IGF2ZXJhZ2UgU3BlY2lmaWNpdHkuDQogLSBMREEgKDAuODgzMSkgaXMgdmVyeSBjbG9zZSB0byBMb2dpc3RpYyBSZWdyZXNzaW9uLg0KIC0gUURBICgwLjg1NDMpIGhhcyBhIGxvd2VyIGF2ZXJhZ2UgU3BlY2lmaWNpdHkgY29tcGFyZWQgdG8gTG9naXN0aWMgUmVncmVzc2lvbiBhbmQgTERBLg0KIC0gTG9naXN0aWMgUmVncmVzc2lvbiBpcyBiZXN0IGF0IGNvcnJlY3RseSBpZGVudGlmeWluZyBhY3R1YWwgbmVnYXRpdmUgY2FzZXMgKGUuZy4sIHBlb3BsZSB3aXRob3V0IGRpYWJldGVzKS4gVGhpcyBtZWFucyBpdCBoYXMgZmV3ZXIgZmFsc2UgcG9zaXRpdmVzLg0KDQpfX1doaWNoIG9uZSBpcyB0aGUgImJlc3QiP19fICANCg0KVGhlICJiZXN0IiBtb2RlbCBkZXBlbmRzIG9uIHRoZSAqc3BlY2lmaWMgZ29hbHMgYW5kIGNvc3RzKiBhc3NvY2lhdGVkIHdpdGggRmFsc2UgUG9zaXRpdmVzIGFuZCBGYWxzZSBOZWdhdGl2ZXMgaW4geW91ciBhcHBsaWNhdGlvbi4gIA0KDQogLSBJZiBvdmVyYWxsIGRpc2NyaW1pbmF0aXZlIHBvd2VyIChjb25zaWRlcmluZyBhbGwgdGhyZXNob2xkcykgaXMgcGFyYW1vdW50LCBhbmQgeW91IGRvbid0IGhhdmUgYSBzdHJvbmcgcHJlZmVyZW5jZSBmb3IgbWluaW1pemluZyBvbmUgdHlwZSBvZiBlcnJvciBvdmVyIGFub3RoZXI6ICANCiAgIC0gTG9naXN0aWMgUmVncmVzc2lvbiBvciBMREEgYXJlIHRoZSB0b3AgY29udGVuZGVycywgYXMgdGhlaXIgYXZlcmFnZSBST0MgQVVDcyBhcmUgdmlydHVhbGx5IGlkZW50aWNhbCBhbmQgdGhlIGhpZ2hlc3QuIFRoZXJlJ3Mgbm90IGVub3VnaCBkaWZmZXJlbmNlIGhlcmUgdG8gZGVjbGFyZSBvbmUgZGVmaW5pdGl2ZWx5IGJldHRlciB0aGFuIHRoZSBvdGhlciBiYXNlZCBzb2xlbHkgb24gdGhpcyBtZXRyaWMuICANCiAtIElmIG1pbmltaXppbmcgRmFsc2UgTmVnYXRpdmVzIChjYXRjaGluZyBhcyBtYW55IHBvc2l0aXZlIGNhc2VzIGFzIHBvc3NpYmxlKSBpcyBtb3JlIGNyaXRpY2FsIChpLmUuLCBoaWdoIFNlbnNpdGl2aXR5IGlzIHByZWZlcnJlZCk6ICANCiAgIC0gUURBIG1pZ2h0IGJlIGNvbnNpZGVyZWQgc2xpZ2h0bHkgYmV0dGVyIGR1ZSB0byBpdHMgbWFyZ2luYWxseSBoaWdoZXIgYXZlcmFnZSBTZW5zaXRpdml0eSAoMC41NzI5KS4gSG93ZXZlciwga2VlcCBpbiBtaW5kIHRoYXQgdGhpcyBjb21lcyBhdCB0aGUgY29zdCBvZiBsb3dlciBTcGVjaWZpY2l0eS4gIA0KIC0gSWYgbWluaW1pemluZyBGYWxzZSBQb3NpdGl2ZXMgKGNvcnJlY3RseSBpZGVudGlmeWluZyBuZWdhdGl2ZSBjYXNlcyBhbmQgYXZvaWRpbmcgZmFsc2UgYWxhcm1zKSBpcyBtb3JlIGNyaXRpY2FsIChpLmUuLCBoaWdoIFNwZWNpZmljaXR5IGlzIHByZWZlcnJlZCk6ICANCiAgIC0gTG9naXN0aWMgUmVncmVzc2lvbiAob3IgdmVyeSBjbG9zZWx5LCBMREEpIGlzIGNsZWFybHkgdGhlIHdpbm5lci4gSXQgaGFzIHRoZSBoaWdoZXN0IGF2ZXJhZ2UgU3BlY2lmaWNpdHksIG1lYW5pbmcgaXQncyBsZXNzIGxpa2VseSB0byBpbmNvcnJlY3RseSBjbGFzc2lmeSBhIG5lZ2F0aXZlIGNhc2UgYXMgcG9zaXRpdmUuICAgDQoNCl9fQmFzZWQgb24gdGhlc2UgcmVzdWx0czpfXyAgDQoNCiAtIEZvciBvdmVyYWxsIGJhbGFuY2VkIHBlcmZvcm1hbmNlIChoaWdoZXN0IFJPQyBBVUMpIGFuZCBzdHJvbmdlc3QgY29udHJvbCBvdmVyIEZhbHNlIFBvc2l0aXZlcyAoaGlnaGVzdCBTcGVjaWZpY2l0eSksIExvZ2lzdGljIFJlZ3Jlc3Npb24gaXMgbWFyZ2luYWxseSB0aGUgImJlc3QsIiBjbG9zZWx5IGZvbGxvd2VkIGJ5IExEQS4gIA0KIC0gSWYgeW91IHByaW9yaXRpemUgc2xpZ2h0bHkgaGlnaGVyIFNlbnNpdGl2aXR5IChjYXRjaGluZyBtb3JlIHRydWUgcG9zaXRpdmVzKSwgUURBIG9mZmVycyBhIHNtYWxsIGFkdmFudGFnZSwgYnV0IGF0IHRoZSBjb3N0IG9mIGxvd2VyIFNwZWNpZmljaXR5IGFuZCBhIHNsaWdodGx5IGxvd2VyIG92ZXJhbGwgUk9DLiAgDQoNCkxvZ2lzdGljIFJlZ3Jlc3Npb24gYXBwZWFycyB0byBiZSB0aGUgbW9zdCByb2J1c3QgY2hvaWNlIGFtb25nIHRoZXNlIHRocmVlIG1vZGVscyBmb3IgdGhpcyBkYXRhc2V0LiBUaGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBMb2dpc3RpYyBSZWdyZXNzaW9uIGFuZCBMREEgYXJlIHNvIHNtYWxsIHRoYXQgdGhleSBjb3VsZCBiZSBjb25zaWRlcmVkIGVmZmVjdGl2ZWx5IGVxdWl2YWxlbnQuIFlvdSdkIGxpa2VseSBjaG9vc2UgYmV0d2VlbiB0aGVtIGJhc2VkIG9uIGVhc2Ugb2YgaW1wbGVtZW50YXRpb24sIGludGVycHJldGFiaWxpdHksIG9yIG1pbm9yIHRoZW9yZXRpY2FsIHByZWZlcmVuY2VzLg0KDQojIyBIb3cgdG8gaW50ZXJwcmV0IFJPQywgU2Vuc2l0aXZpdHksIGFuZCBTcGVjaWZpY2l0eQ0KDQpST0MsIFNlbnNpdGl2aXR5LCBhbmQgU3BlY2lmaWNpdHkgYXJlIHRocmVlIG1ldHJpY3MgZm9yIGV2YWx1YXRpbmcgdGhlIHBlcmZvcm1hbmNlIG9mIGNsYXNzaWZpY2F0aW9uIG1vZGVscywgZXNwZWNpYWxseSBpbiBzaXR1YXRpb25zIHdoZXJlIHlvdSBjYXJlIGFib3V0IGRpZmZlcmVudCB0eXBlcyBvZiBlcnJvcnMuIFRvIHVuZGVyc3RhbmQgdGhlc2UsIGZpcnN0IHVuZGVyc3RhbmQgdGhlIGZvdXIgYmFzaWMgb3V0Y29tZXMgaW4gYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gcHJvYmxlbSwgb2Z0ZW4gcmVwcmVzZW50ZWQgaW4gYSBjb25mdXNpb24gbWF0cml4OiAgDQoNCnwgICAgICAgICAgICAgICAgICAgICAgICB8ICoqQWN0dWFsIFBvc2l0aXZlKiogfCAqKkFjdHVhbCBOZWdhdGl2ZSoqIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLTp8DQp8ICoqUHJlZGljdGVkIFBvc2l0aXZlKiogfCBUcnVlIFBvc2l0aXZlIChUUCkgIHwgRmFsc2UgUG9zaXRpdmUgKEZQKSB8DQp8ICoqUHJlZGljdGVkIE5lZ2F0aXZlKiogfCBGYWxzZSBOZWdhdGl2ZSAoRk4pIHwgVHJ1ZSBOZWdhdGl2ZSAoVE4pICB8ICANCg0KDQogLSBfX1RydWUgUG9zaXRpdmUgKFRQKTpfXyBUaGUgbW9kZWwgY29ycmVjdGx5IHByZWRpY3RlZCBhIHBvc2l0aXZlIG91dGNvbWUgKGUuZy4sIHByZWRpY3RlZCAiaGFzIGRpYWJldGVzLCIgYW5kIHRoZSBwZXJzb24gYWN0dWFsbHkgaGFzIGRpYWJldGVzKS4gIA0KIC0gX19GYWxzZSBQb3NpdGl2ZSAoRlApOl9fIFRoZSBtb2RlbCBpbmNvcnJlY3RseSBwcmVkaWN0ZWQgYSBwb3NpdGl2ZSBvdXRjb21lIChlLmcuLCBwcmVkaWN0ZWQgImhhcyBkaWFiZXRlcywiIGJ1dCB0aGUgcGVyc29uIGFjdHVhbGx5IGRvZXMgbm90IGhhdmUgZGlhYmV0ZXMpLiBUaGlzIGlzIGFsc28ga25vd24gYXMgYSBUeXBlIEkgZXJyb3Igb3IgYSAiZmFsc2UgYWxhcm0uIiAgDQogLSBfX0ZhbHNlIE5lZ2F0aXZlIChGTik6X18gVGhlIG1vZGVsIGluY29ycmVjdGx5IHByZWRpY3RlZCBhIG5lZ2F0aXZlIG91dGNvbWUgKGUuZy4sIHByZWRpY3RlZCAiZG9lcyBub3QgaGF2ZSBkaWFiZXRlcywiIGJ1dCB0aGUgcGVyc29uIGFjdHVhbGx5IGhhcyBkaWFiZXRlcykuIFRoaXMgaXMgYWxzbyBrbm93biBhcyBhIFR5cGUgSUkgZXJyb3Igb3IgYSAibWlzcy4iICANCiAtIF9fVHJ1ZSBOZWdhdGl2ZSAoVE4pOl9fIFRoZSBtb2RlbCBjb3JyZWN0bHkgcHJlZGljdGVkIGEgbmVnYXRpdmUgb3V0Y29tZSAoZS5nLiwgcHJlZGljdGVkICJkb2VzIG5vdCBoYXZlIGRpYWJldGVzLCIgYW5kIHRoZSBwZXJzb24gYWN0dWFsbHkgZG9lcyBub3QgaGF2ZSBkaWFiZXRlcykuICANCiANCiMjIyBTZW5zaXRpdml0eSAoUmVjYWxsLCBUcnVlIFBvc2l0aXZlIFJhdGUgLSBUUFIpICANClNlbnNpdGl2aXR5IG1lYXN1cmVzIHRoZSBwcm9wb3J0aW9uIG9mIGFjdHVhbCBwb3NpdGl2ZSBjYXNlcyB0aGF0IHdlcmUgY29ycmVjdGx5IGlkZW50aWZpZWQgYnkgdGhlIG1vZGVsLiBJdCBhbnN3ZXJzIHRoZSBxdWVzdGlvbjogIk91dCBvZiBhbGwgdGhlIHBlb3BsZSB3aG8gdHJ1bHkgaGF2ZSB0aGUgY29uZGl0aW9uLCBob3cgbWFueSBkaWQgb3VyIG1vZGVsIGNvcnJlY3RseSBmaW5kPyIgIA0KDQokU2Vuc2l0aXZpdHkgPSBcZnJhY3tUUH17VFArRk59JA0KDQogLSBfX0hpZ2ggU2Vuc2l0aXZpdHkgKGNsb3NlciB0byAxLjApOl9fIE1lYW5zIHRoZSBtb2RlbCBpcyB2ZXJ5IGdvb2QgYXQgY2F0Y2hpbmcgcG9zaXRpdmUgY2FzZXMuIEl0IGhhcyBhIGxvdyByYXRlIG9mIEZhbHNlIE5lZ2F0aXZlcyAobWlzc2luZyBhY3R1YWwgcG9zaXRpdmVzKS4gIA0KIC0gX19Mb3cgU2Vuc2l0aXZpdHkgKGNsb3NlciB0byAwLjApOl9fIE1lYW5zIHRoZSBtb2RlbCBtaXNzZXMgbWFueSBhY3R1YWwgcG9zaXRpdmUgY2FzZXMsIGxlYWRpbmcgdG8gYSBoaWdoIHJhdGUgb2YgRmFsc2UgTmVnYXRpdmVzLiAgDQogDQpfX1NlbnNpdGl2aXR5IGlzIGNydWNpYWwgd2hlbiB0aGUgY29zdCBvZiBhIEZhbHNlIE5lZ2F0aXZlIGlzIGhpZ2guX18gIA0KDQogLSBfX01lZGljYWwgRGlhZ25vc2lzIChlLmcuLCBkZXRlY3RpbmcgYSBzZXJpb3VzIGRpc2Vhc2UgbGlrZSBjYW5jZXIpOl9fIFlvdSB3YW50IHZlcnkgaGlnaCBzZW5zaXRpdml0eSBiZWNhdXNlIG1pc3NpbmcgYSBkaXNlYXNlIChmYWxzZSBuZWdhdGl2ZSkgY2FuIGhhdmUgc2V2ZXJlIGNvbnNlcXVlbmNlcyBmb3IgdGhlIHBhdGllbnQuIFlvdSdkIHJhdGhlciBoYXZlIHNvbWUgZmFsc2UgYWxhcm1zIChmYWxzZSBwb3NpdGl2ZXMpIHRoYXQgY2FuIGJlIGZ1cnRoZXIgaW52ZXN0aWdhdGVkLiAgDQogLSBfX1NlY3VyaXR5IFN5c3RlbXM6X18gWW91IHdhbnQgdG8gZGV0ZWN0IGFsbCBpbnRydWRlcnMgKGhpZ2ggc2Vuc2l0aXZpdHkpLCBldmVuIGlmIGl0IG1lYW5zIHNvbWUgZmFsc2UgYWxhcm1zLiAgDQogLSBfX0ZyYXVkIERldGVjdGlvbjpfXyBZb3Ugd2FudCB0byBjYXRjaCBhcyBtYW55IGZyYXVkdWxlbnQgdHJhbnNhY3Rpb25zIGFzIHBvc3NpYmxlLiAgDQoNCiMjIyMgU05OT1VUIChTZW5zaXRpdmUgTmVnYXRpdmUgPSBSdWxlIE9VVCkgIA0KSWYgYSBoaWdobHkgc2Vuc2l0aXZlIHRlc3QgY29tZXMgYmFjayBuZWdhdGl2ZSwgeW91IGNhbiBjb25maWRlbnRseSBydWxlIG91dCB0aGUgZGlzZWFzZS9jb25kaXRpb24uDQoNCiMjIyBTcGVjaWZpY2l0eSAoVHJ1ZSBOZWdhdGl2ZSBSYXRlIC0gVE5SKSAgDQpTcGVjaWZpY2l0eSBtZWFzdXJlcyB0aGUgcHJvcG9ydGlvbiBvZiBhY3R1YWwgbmVnYXRpdmUgY2FzZXMgdGhhdCB3ZXJlIGNvcnJlY3RseSBpZGVudGlmaWVkIGJ5IHRoZSBtb2RlbC4gSXQgYW5zd2VycyB0aGUgcXVlc3Rpb246ICJPdXQgb2YgYWxsIHRoZSBwZW9wbGUgd2hvIHRydWx5IGRvIE5PVCBoYXZlIHRoZSBjb25kaXRpb24sIGhvdyBtYW55IGRpZCBvdXIgbW9kZWwgY29ycmVjdGx5IGlkZW50aWZ5IGFzIG5vdCBoYXZpbmcgaXQ/IiAgDQoNCiRTcGVjaWZpY2l0eSA9IFxmcmFje1ROfXtUTitGUH0kDQoNCiAtIF9fSGlnaCBTcGVjaWZpY2l0eSAoY2xvc2VyIHRvIDEuMCk6X18gTWVhbnMgdGhlIG1vZGVsIGlzIHZlcnkgZ29vZCBhdCBjb3JyZWN0bHkgaWRlbnRpZnlpbmcgbmVnYXRpdmUgY2FzZXMuIEl0IGhhcyBhIGxvdyByYXRlIG9mIEZhbHNlIFBvc2l0aXZlcyAoaW5jb3JyZWN0bHkgY2xhc3NpZnlpbmcgbmVnYXRpdmVzIGFzIHBvc2l0aXZlcykuICANCiAtIF9fTG93IFNwZWNpZmljaXR5IChjbG9zZXIgdG8gMC4wKTpfXyBNZWFucyB0aGUgbW9kZWwgaW5jb3JyZWN0bHkgZmxhZ3MgbWFueSBuZWdhdGl2ZSBjYXNlcyBhcyBwb3NpdGl2ZSwgbGVhZGluZyB0byBhIGhpZ2ggcmF0ZSBvZiBGYWxzZSBQb3NpdGl2ZXMuICANCiANCl9fU3BlY2lmaWNpdHkgaXMgY3J1Y2lhbCB3aGVuIHRoZSBjb3N0IG9mIGEgRmFsc2UgUG9zaXRpdmUgaXMgaGlnaC5fXyAgDQoNCiAtIF9fU3BhbSBEZXRlY3Rpb246X18gWW91IHdhbnQgaGlnaCBzcGVjaWZpY2l0eSB0byBhdm9pZCBtYXJraW5nIGxlZ2l0aW1hdGUgZW1haWxzIGFzIHNwYW0gKGZhbHNlIHBvc2l0aXZlKSwgd2hpY2ggd291bGQgYmUgdmVyeSBhbm5veWluZyB0byB1c2Vycy4gWW91IG1pZ2h0IHRvbGVyYXRlIGEgZmV3IHNwYW0gZW1haWxzIGdldHRpbmcgdGhyb3VnaCAoZmFsc2UgbmVnYXRpdmVzKS4gIA0KIC0gX19MZWdhbC9DcmltaW5hbCBKdXN0aWNlOl9fIEluY29ycmVjdGx5IGlkZW50aWZ5aW5nIGFuIGlubm9jZW50IHBlcnNvbiBhcyBndWlsdHkgKGZhbHNlIHBvc2l0aXZlKSBoYXMgaGlnaCBzb2NpZXRhbCBjb3N0cy4gIA0KIC0gX19FeHBlbnNpdmUgb3IgSW52YXNpdmUgRm9sbG93LXVwIFRlc3RzOl9fIElmIGEgcG9zaXRpdmUgdGVzdCBsZWFkcyB0byBhIGNvc3RseSwgcGFpbmZ1bCwgb3Igcmlza3kgZm9sbG93LXVwIHByb2NlZHVyZSwgeW91IHdhbnQgaGlnaCBzcGVjaWZpY2l0eSB0byBtaW5pbWl6ZSB1bm5lY2Vzc2FyeSBwcm9jZWR1cmVzLiAgDQoNCiMjIyMgU1BQSU4gKFNwZWNpZmljIFBvc2l0aXZlID0gUnVsZSBJTikgIA0KSWYgYSBoaWdobHkgc3BlY2lmaWMgdGVzdCBjb21lcyBiYWNrIHBvc2l0aXZlLCB5b3UgY2FuIGNvbmZpZGVudGx5IHJ1bGUgaW4gdGhlIGRpc2Vhc2UvY29uZGl0aW9uLg0KDQojIyMgUmVjaWV2ZXIgT3BlcmF0aW5nIENoYXJhY3RlcmlzdGljIEN1cnZlIChST0MpICANClRoZSBST0MgY3VydmUgaXMgYSBncmFwaGljYWwgcGxvdCB0aGF0IGlsbHVzdHJhdGVzIHRoZSBkaWFnbm9zdGljIGFiaWxpdHkgb2YgYSBiaW5hcnkgY2xhc3NpZmllciBzeXN0ZW0gYXMgaXRzIGRpc2NyaW1pbmF0aW9uIHRocmVzaG9sZCBpcyB2YXJpZWQuIEl0IHBsb3RzIHRoZSAqVHJ1ZSBQb3NpdGl2ZSBSYXRlIChTZW5zaXRpdml0eSkqIG9uIHRoZSB5LWF4aXMgYWdhaW5zdCB0aGUgRmFsc2UgUG9zaXRpdmUgUmF0ZSAqKDEgLSBTcGVjaWZpY2l0eSkqIG9uIHRoZSB4LWF4aXMgZm9yIGRpZmZlcmVudCBwb3NzaWJsZSB0aHJlc2hvbGRzLg0KDQogLSBFYWNoIHBvaW50IG9uIHRoZSBST0MgY3VydmUgcmVwcmVzZW50cyBhIHNlbnNpdGl2aXR5L3NwZWNpZmljaXR5IHBhaXIgY29ycmVzcG9uZGluZyB0byBhIHBhcnRpY3VsYXIgY2xhc3NpZmljYXRpb24gdGhyZXNob2xkLiAgDQogDQogICAtICpUb3AtbGVmdCBjb3JuZXIgKDAsMSk6KiBUaGlzIHJlcHJlc2VudHMgYSBwZXJmZWN0IGNsYXNzaWZpZXI6IDEwMCUgU2Vuc2l0aXZpdHkgKG5vIGZhbHNlIG5lZ2F0aXZlcykgYW5kIDEwMCUgU3BlY2lmaWNpdHkgKG5vIGZhbHNlIHBvc2l0aXZlcykuIFRoZSBjbG9zZXIgeW91ciBjdXJ2ZSBpcyB0byB0aGlzIGNvcm5lciwgdGhlIGJldHRlciB0aGUgbW9kZWwncyBvdmVyYWxsIHBlcmZvcm1hbmNlLiAgDQogICAtICpEaWFnb25hbCBsaW5lIChmcm9tIDAsMCB0byAxLDEpOiogVGhpcyByZXByZXNlbnRzIGEgcmFuZG9tIGNsYXNzaWZpZXIgKGVxdWl2YWxlbnQgdG8gZmxpcHBpbmcgYSBjb2luKS4gQSBtb2RlbCB3aG9zZSBST0MgY3VydmUgZmFsbHMgYWxvbmcgdGhpcyBkaWFnb25hbCBpcyBwZXJmb3JtaW5nIG5vIGJldHRlciB0aGFuIGNoYW5jZS4gIA0KICAgLSAqQ3VydmUgYmVsb3cgdGhlIGRpYWdvbmFsOiogSW5kaWNhdGVzIGEgbW9kZWwgdGhhdCBwZXJmb3JtcyB3b3JzZSB0aGFuIHJhbmRvbSBjaGFuY2UuIElmIHRoaXMgaGFwcGVucywgeW91IGNhbiBvZnRlbiBqdXN0IGludmVydCB0aGUgbW9kZWwncyBwcmVkaWN0aW9ucyB0byBnZXQgYSBiZXR0ZXItdGhhbi1jaGFuY2UgbW9kZWwhICANCiAgIA0KIC0gVGhlIEFVQyBpcyBhIHNpbmdsZSBzY2FsYXIgdmFsdWUgdGhhdCBzdW1tYXJpemVzIHRoZSBvdmVyYWxsIHBlcmZvcm1hbmNlIG9mIHRoZSBjbGFzc2lmaWVyIGFjcm9zcyBhbGwgcG9zc2libGUgdGhyZXNob2xkcy4gSXQgcmFuZ2VzIGZyb20gMCB0byAxLiAgDQogDQogICAtICpBVUMgPSAxOiogUGVyZmVjdCBjbGFzc2lmaWNhdGlvbi4gIA0KICAgLSAqQVVDID0gMC41OiogUGVyZm9ybWFuY2UgaXMgbm8gYmV0dGVyIHRoYW4gcmFuZG9tIGd1ZXNzaW5nLiAgDQogICAtICpBVUMgPCAwLjU6KiBQZXJmb3JtYW5jZSBpcyB3b3JzZSB0aGFuIHJhbmRvbSBndWVzc2luZy4gIA0KICAgLSAqQSBoaWdoZXIgQVVDIGluZGljYXRlcyBhIGJldHRlciBtb2RlbC4gSXQgY2FuIGJlIGludGVycHJldGVkIGFzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBtb2RlbCByYW5rcyBhIHJhbmRvbWx5IGNob3NlbiBwb3NpdGl2ZSBpbnN0YW5jZSBoaWdoZXIgdGhhbiBhIHJhbmRvbWx5IGNob3NlbiBuZWdhdGl2ZSBpbnN0YW5jZS4NCg0KYGBge3J9DQojIEluc3RhbGwgbmVjZXNzYXJ5IHBhY2thZ2VzIChpZiBub3QgYWxyZWFkeSBpbnN0YWxsZWQpDQojIGluc3RhbGwucGFja2FnZXMoIm1sYmVuY2giKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJNQVNTIikNCiMgaW5zdGFsbC5wYWNrYWdlcygiZHBseXIiKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikgIyBGb3IgcGxvdHRpbmcNCiMgaW5zdGFsbC5wYWNrYWdlcygicFJPQyIpICAgICMgRm9yIFJPQyBjdXJ2ZXMNCg0KIyBMb2FkIHBhY2thZ2VzDQpsaWJyYXJ5KG1sYmVuY2gpDQpsaWJyYXJ5KE1BU1MpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShwUk9DKSAjIExvYWQgcFJPQyBmb3IgUk9DIGN1cnZlcyBhbmQgQVVDDQoNCiMgTG9hZCB0aGUgZGF0YXNldA0KZGF0YShQaW1hSW5kaWFuc0RpYWJldGVzMikNCm15X2RhdGEgPC0gUGltYUluZGlhbnNEaWFiZXRlczINCg0KIyBEYXRhIFByZXBhcmF0aW9uICh1c2luZyBkcGx5cikNCm15X2RhdGEgPC0gbXlfZGF0YSAlPiUNCiAgbmEub21pdCgpICU+JQ0KICBtdXRhdGUoZGlhYmV0ZXMgPSBmYWN0b3IoZGlhYmV0ZXMsIGxldmVscyA9IGMoIm5lZyIsICJwb3MiKSwgbGFiZWxzID0gYygiTm8iLCAiWWVzIikpKQ0KDQojIC0tLSAxLiBGaXQgZWFjaCBtb2RlbCBvbiB0aGUgKmVudGlyZSogZGF0YXNldCAtLS0NCiMgVGhpcyBpcyBhIGZpbmFsIG1vZGVsLCBub3QgcGFydCBvZiB0aGUgY3Jvc3MtdmFsaWRhdGlvbiBsb29wLg0KIyBJdCdzIHVzZWQgZm9yIHBsb3R0aW5nIHRoZSAib3ZlcmFsbCIgUk9DIGN1cnZlIGZvciB0aGUgZnVsbCBkYXRhc2V0Lg0KDQpjYXQoIlxuLS0tIEZpdHRpbmcgRmluYWwgTW9kZWxzIG9uIEZ1bGwgRGF0YXNldCBmb3IgUk9DIEN1cnZlIC0tLVxuIikNCg0KIyBMb2dpc3RpYyBSZWdyZXNzaW9uDQpmaW5hbF9tb2RlbF9nbG0gPC0gZ2xtKGRpYWJldGVzIH4gLiwgZGF0YSA9IG15X2RhdGEsIGZhbWlseSA9ICJiaW5vbWlhbCIpDQoNCiMgTERBDQpmaW5hbF9tb2RlbF9sZGEgPC0gbGRhKGRpYWJldGVzIH4gLiwgZGF0YSA9IG15X2RhdGEpDQoNCiMgUURBDQojIEFkZCBhIGNoZWNrIHRvIGVuc3VyZSBRREEgY2FuIGJlIGZpdHRlZCAoc3VmZmljaWVudCBkYXRhIHBlciBjbGFzcyByZWxhdGl2ZSB0byBwcmVkaWN0b3JzKQ0KIyBUaGlzIGlzIHNpbWlsYXIgdG8gdGhlIGNoZWNrIGluIHRoZSBDViBsb29wLCBidXQgYXBwbGllZCB0byB0aGUgZnVsbCBkYXRhc2V0Lg0KIyBUaGUgYG5jb2wobXlfZGF0YSkgLSAxYCBhY2NvdW50cyBmb3IgYWxsIHByZWRpY3RvcnMNCmlmIChzdW0obXlfZGF0YSRkaWFiZXRlcyA9PSAiTm8iKSA+PSAobmNvbChteV9kYXRhKSAtIDEpICYmDQogICAgc3VtKG15X2RhdGEkZGlhYmV0ZXMgPT0gIlllcyIpID49IChuY29sKG15X2RhdGEpIC0gMSkpIHsNCiAgZmluYWxfbW9kZWxfcWRhIDwtIHFkYShkaWFiZXRlcyB+IC4sIGRhdGEgPSBteV9kYXRhKQ0KfSBlbHNlIHsNCiAgd2FybmluZygiUURBIGNvdWxkIG5vdCBiZSBmaXR0ZWQgb24gdGhlIGZ1bGwgZGF0YXNldCBkdWUgdG8gaW5zdWZmaWNpZW50IG9ic2VydmF0aW9ucyBwZXIgY2xhc3MuIikNCiAgZmluYWxfbW9kZWxfcWRhIDwtIE5VTEwgIyBTZXQgdG8gTlVMTCBpZiBpdCBjYW5ub3QgYmUgZml0dGVkDQp9DQoNCg0KIyAtLS0gMi4gR2V0IHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIGZvciB0aGUgJ1llcycgY2xhc3MgZnJvbSBlYWNoIG1vZGVsIC0tLQ0KDQojIExvZ2lzdGljIFJlZ3Jlc3Npb24gcHJvYmFiaWxpdGllcw0KIyB0eXBlID0gInJlc3BvbnNlIiBnaXZlcyBwcm9iYWJpbGl0aWVzIGRpcmVjdGx5IGZvciBnbG0NCnByb2JfZ2xtIDwtIHByZWRpY3QoZmluYWxfbW9kZWxfZ2xtLCBuZXdkYXRhID0gbXlfZGF0YSwgdHlwZSA9ICJyZXNwb25zZSIpDQoNCiMgTERBIHByb2JhYmlsaXRpZXMNCiMgcHJlZGljdCgpIGZvciBsZGEvcWRhIHJldHVybnMgYSBsaXN0OyAkcG9zdGVyaW9yIGNvbnRhaW5zIHByb2JhYmlsaXRpZXMNCnByb2JfbGRhIDwtIHByZWRpY3QoZmluYWxfbW9kZWxfbGRhLCBuZXdkYXRhID0gbXlfZGF0YSkkcG9zdGVyaW9yWywgIlllcyJdDQoNCiMgUURBIHByb2JhYmlsaXRpZXMgKGNoZWNrIGlmIFFEQSBtb2RlbCB3YXMgc3VjY2Vzc2Z1bGx5IGZpdHRlZCkNCnByb2JfcWRhIDwtIGlmICghaXMubnVsbChmaW5hbF9tb2RlbF9xZGEpKSB7DQogIHByZWRpY3QoZmluYWxfbW9kZWxfcWRhLCBuZXdkYXRhID0gbXlfZGF0YSkkcG9zdGVyaW9yWywgIlllcyJdDQp9IGVsc2Ugew0KICBOVUxMICMgSWYgUURBIHdhc24ndCBmaXR0ZWQsIG5vIHByb2JhYmlsaXRpZXMNCn0NCg0KIyAtLS0gMy4gQ3JlYXRlIFJPQyBvYmplY3RzIGZvciBlYWNoIG1vZGVsIC0tLQ0KDQojIEZvciBMb2dpc3RpYyBSZWdyZXNzaW9uDQpyb2Nfb2JqX2dsbSA8LSByb2MocmVzcG9uc2UgPSBteV9kYXRhJGRpYWJldGVzLCBwcmVkaWN0b3IgPSBwcm9iX2dsbSwNCiAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJObyIsICJZZXMiKSkgIyBFbnN1cmUgbGV2ZWxzIGFyZSBpbiBvcmRlcjogbmVnYXRpdmUgZmlyc3QNCg0KIyBGb3IgTERBDQpyb2Nfb2JqX2xkYSA8LSByb2MocmVzcG9uc2UgPSBteV9kYXRhJGRpYWJldGVzLCBwcmVkaWN0b3IgPSBwcm9iX2xkYSwNCiAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJObyIsICJZZXMiKSkNCg0KIyBGb3IgUURBIChvbmx5IGlmIHRoZSBtb2RlbCB3YXMgZml0dGVkKQ0KaWYgKCFpcy5udWxsKHByb2JfcWRhKSkgew0KICByb2Nfb2JqX3FkYSA8LSByb2MocmVzcG9uc2UgPSBteV9kYXRhJGRpYWJldGVzLCBwcmVkaWN0b3IgPSBwcm9iX3FkYSwNCiAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIk5vIiwgIlllcyIpKQ0KfSBlbHNlIHsNCiAgcm9jX29ial9xZGEgPC0gTlVMTA0KfQ0KDQoNCiMgLS0tIDQuIFBsb3QgYWxsIHRocmVlIFJPQyBjdXJ2ZXMgb24gdGhlIHNhbWUgZ3JhcGggLS0tDQoNCiMgU3RhcnQgd2l0aCB0aGUgZmlyc3QgUk9DIGN1cnZlIChlLmcuLCBMb2dpc3RpYyBSZWdyZXNzaW9uKQ0KcGxvdChyb2Nfb2JqX2dsbSwNCiAgICAgY29sID0gIiMzMTgyYmQiLCAjIENvbG9yIGZvciBHTE0NCiAgICAgbHdkID0gMiwgICAgICAjIExpbmUgd2lkdGgNCiAgICAgbWFpbiA9ICJST0MgQ3VydmVzIGZvciBDbGFzc2lmaWNhdGlvbiBNb2RlbHMiLA0KICAgICBzdWIgPSBwYXN0ZTAoIkxvZ2lzdGljIFJlZ3Jlc3Npb24gQVVDOiAiLCByb3VuZChhdWMocm9jX29ial9nbG0pLCAzKSwNCiAgICAgICAgICAgICAgICAgICJcbkxEQSBBVUM6ICIsIHJvdW5kKGF1Yyhyb2Nfb2JqX2xkYSksIDMpLA0KICAgICAgICAgICAgICAgICAgaWYgKCFpcy5udWxsKHJvY19vYmpfcWRhKSkgeyBwYXN0ZTAoIlxuUURBIEFVQzogIiwgcm91bmQoYXVjKHJvY19vYmpfcWRhKSwgMykpIH0NCiAgICAgICAgICAgICAgICAgIGVsc2UgeyAiIiB9DQogICAgICAgICAgICAgICAgICApLA0KICAgICBncmlkID0gVFJVRSAjIEFkZCBhIGdyaWQgZm9yIGJldHRlciByZWFkYWJpbGl0eQ0KICAgICApDQoNCiMgQWRkIExEQSBjdXJ2ZSB0byB0aGUgZXhpc3RpbmcgcGxvdA0KcGxvdChyb2Nfb2JqX2xkYSwNCiAgICAgY29sID0gIiNkZTJkMjYiLCAjIENvbG9yIGZvciBMREENCiAgICAgbHdkID0gMiwNCiAgICAgYWRkID0gVFJVRSAjIEFkZCB0byB0aGUgY3VycmVudCBwbG90DQogICAgICkNCg0KIyBBZGQgUURBIGN1cnZlIHRvIHRoZSBleGlzdGluZyBwbG90IChpZiBpdCB3YXMgZml0dGVkKQ0KaWYgKCFpcy5udWxsKHJvY19vYmpfcWRhKSkgew0KICBwbG90KHJvY19vYmpfcWRhLA0KICAgICAgIGNvbCA9ICIjMzFhMzU0IiwgIyBDb2xvciBmb3IgUURBDQogICAgICAgbHdkID0gMiwNCiAgICAgICBhZGQgPSBUUlVFDQogICAgICAgKQ0KfQ0KDQojIEFkZCBhIGxlZ2VuZCB0byBkaXN0aW5ndWlzaCB0aGUgY3VydmVzDQpsZWdlbmQoImJvdHRvbXJpZ2h0IiwNCiAgICAgICBsZWdlbmQgPSBjKHBhc3RlMCgiTG9naXN0aWMgUmVncmVzc2lvbiAoQVVDID0gIiwgcm91bmQoYXVjKHJvY19vYmpfZ2xtKSwgMyksICIpIiksDQogICAgICAgICAgICAgICAgICBwYXN0ZTAoIkxEQSAoQVVDID0gIiwgcm91bmQoYXVjKHJvY19vYmpfbGRhKSwgMyksICIpIiksDQogICAgICAgICAgICAgICAgICBpZiAoIWlzLm51bGwocm9jX29ial9xZGEpKSB7IHBhc3RlMCgiUURBIChBVUMgPSAiLCByb3VuZChhdWMocm9jX29ial9xZGEpLCAzKSwgIikiKSB9DQogICAgICAgICAgICAgICAgICBlbHNlIHsgIlFEQSAoTm90IEZpdHRlZCkiIH0NCiAgICAgICAgICAgICAgICAgICksDQogICAgICAgY29sID0gYygiIzMxODJiZCIsICIjZGUyZDI2IiwgIiMzMWEzNTQiKSwNCiAgICAgICBsd2QgPSAyLA0KICAgICAgIGNleCA9IDAuOCAjIEZvbnQgc2l6ZSBmb3IgbGVnZW5kDQogICAgICAgKQ0KDQojIFlvdSBjYW4gYWxzbyBhZGQgdGhlIGRpYWdvbmFsIGxpbmUgcmVwcmVzZW50aW5nIHJhbmRvbSBjaGFuY2UNCmFibGluZShhID0gMCwgYiA9IDEsIGx0eSA9IDIsIGNvbCA9ICIjYmRiZGJkIikgIyBEYXNoZWQgZ3JheSBsaW5lDQoNCiMgLS0tIE9wdGlvbmFsOiBQcmludCBpbmRpdmlkdWFsIEFVQ3MgYWdhaW4gZm9yIGVhc3kgcmVmZXJlbmNlIC0tLQ0KY2F0KCJcbi0tLSBBVUMgVmFsdWVzIGZvciBGaW5hbCBNb2RlbHMgLS0tXG4iKQ0KY2F0KCJMb2dpc3RpYyBSZWdyZXNzaW9uIEFVQzogIiwgYXVjKHJvY19vYmpfZ2xtKSwgIlxuIikNCmNhdCgiTERBIEFVQzogIiwgYXVjKHJvY19vYmpfbGRhKSwgIlxuIikNCmlmICghaXMubnVsbChyb2Nfb2JqX3FkYSkpIHsNCiAgY2F0KCJRREEgQVVDOiAiLCBhdWMocm9jX29ial9xZGEpLCAiXG4iKQ0KfSBlbHNlIHsNCiAgY2F0KCJRREEgQVVDOiBOb3QgYXBwbGljYWJsZSAobW9kZWwgbm90IGZpdHRlZClcbiIpDQp9DQpgYGANCg0KX19XaHkgaXQncyBpbXBvcnRhbnQ6X18gIA0KDQogLSBVbmxpa2UgQWNjdXJhY3ksIFNlbnNpdGl2aXR5LCBhbmQgU3BlY2lmaWNpdHkgKHdoaWNoIGRlcGVuZCBvbiBhIGNob3NlbiB0aHJlc2hvbGQpLCB0aGUgUk9DIGN1cnZlIGFuZCBBVUMgcHJvdmlkZSBhIHZpZXcgb2YgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UgYWNyb3NzICphbGwgcG9zc2libGUgdGhyZXNob2xkcy4qIFRoaXMgaXMgZXNwZWNpYWxseSB1c2VmdWwgZm9yIG1vZGVscyB0aGF0IG91dHB1dCBwcm9iYWJpbGl0aWVzIChsaWtlIGxvZ2lzdGljIHJlZ3Jlc3Npb24pLCBhcyB5b3UgY2FuIGNob29zZSB0aGUgb3B0aW1hbCB0aHJlc2hvbGQgbGF0ZXIgYmFzZWQgb24geW91ciBzcGVjaWZpYyBuZWVkcy4gIA0KIC0gQVVDIGlzIGdlbmVyYWxseSBhIG1vcmUgcmVsaWFibGUgbWV0cmljIHRoYW4gYWNjdXJhY3kgd2hlbiBkZWFsaW5nIHdpdGggW2ltYmFsYW5jZWQgZGF0YXNldHNdKGh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL21hY2hpbmUtbGVhcm5pbmcvY3Jhc2gtY291cnNlL292ZXJmaXR0aW5nL2ltYmFsYW5jZWQtZGF0YXNldHMpICh3aGVyZSBvbmUgY2xhc3MgaXMgbXVjaCBtb3JlIHByZXZhbGVudCB0aGFuIHRoZSBvdGhlcikuIEEgY2xhc3NpZmllciBjYW4gYWNoaWV2ZSBoaWdoIGFjY3VyYWN5IGJ5IHNpbXBseSBwcmVkaWN0aW5nIHRoZSBtYWpvcml0eSBjbGFzcyBhbGwgdGhlIHRpbWUsIGJ1dCBpdHMgQVVDIHdvdWxkIHJldmVhbCBpdHMgcG9vciBkaXNjcmltaW5hdG9yeSBwb3dlci4gIA0KIC0gQVVDIGlzIGV4Y2VsbGVudCBmb3IgY29tcGFyaW5nIGRpZmZlcmVudCBtb2RlbHMgYmVjYXVzZSBpdCBzdW1tYXJpemVzIHRoZWlyIG92ZXJhbGwgZGlzY3JpbWluYXRpdmUgcG93ZXIgaW4gYSBzaW5nbGUgbnVtYmVyLiBUaGUgbW9kZWwgd2l0aCBhIGhpZ2hlciBBVUMgaXMgZ2VuZXJhbGx5IHByZWZlcnJlZC4NCg0KIyMjIyBUcmFkZS1vZmZzIGJldHdlZW4gU2Vuc2l0aXZpdHkgYW5kIFNwZWNpZmljaXR5IChhbmQgaG93IFJPQyBzaG93cyBpdCkgIA0KDQpUaGVyZSdzIG9mdGVuIGEgdHJhZGUtb2ZmIGJldHdlZW4gc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5LiAgDQoNCiAtIElmIHlvdSBzZXQgYSB2ZXJ5IGxvdyB0aHJlc2hvbGQgZm9yIGNsYXNzaWZ5aW5nIGFuIG91dGNvbWUgYXMgInBvc2l0aXZlIiAoZS5nLiwgaWYgYW55IHByb2JhYmlsaXR5IGFib3ZlIDAuMSBpcyBjb25zaWRlcmVkIHBvc2l0aXZlKSwgeW91J2xsIGxpa2VseSBpbmNyZWFzZSB5b3VyIHNlbnNpdGl2aXR5IChjYXRjaCBtb3JlIHRydWUgcG9zaXRpdmVzKSBidXQgYWxzbyBpbmNyZWFzZSB5b3VyIGZhbHNlIHBvc2l0aXZlIHJhdGUgKGRlY3JlYXNlIHNwZWNpZmljaXR5KS4gIA0KIC0gQ29udmVyc2VseSwgaWYgeW91IHNldCBhIHZlcnkgaGlnaCB0aHJlc2hvbGQgKGUuZy4sIG9ubHkgcHJvYmFiaWxpdGllcyBhYm92ZSAwLjkgYXJlIHBvc2l0aXZlKSwgeW91J2xsIGluY3JlYXNlIHlvdXIgc3BlY2lmaWNpdHkgKHJlZHVjZSBmYWxzZSBwb3NpdGl2ZXMpIGJ1dCBsaWtlbHkgZGVjcmVhc2UgeW91ciBzZW5zaXRpdml0eSAobWlzcyBtb3JlIHRydWUgcG9zaXRpdmVzKS4gIA==